aboutsummaryrefslogtreecommitdiffstats
path: root/WMD_matching.py
diff options
context:
space:
mode:
Diffstat (limited to 'WMD_matching.py')
-rw-r--r--WMD_matching.py265
1 files changed, 265 insertions, 0 deletions
diff --git a/WMD_matching.py b/WMD_matching.py
new file mode 100644
index 0000000..c65e6e5
--- /dev/null
+++ b/WMD_matching.py
@@ -0,0 +1,265 @@
1###########################
2# Wasserstein Retrieval #
3###########################
4import argparse
5
6parser = argparse.ArgumentParser(description='run matching using wmd and wasserstein distances')
7parser.add_argument('source_lang', help='source language short name')
8parser.add_argument('target_lang', help='target language short name')
9parser.add_argument('source_vector', help='path of the source vector')
10parser.add_argument('target_vector', help='path of the target vector')
11parser.add_argument('source_defs', help='path of the source definitions')
12parser.add_argument('target_defs', help='path of the target definitions')
13parser.add_argument('-n', '--instances', help='number of instances in each language to retrieve', default=2000, type=int)
14
15args = parser.parse_args()
16
17source_lang = args.source_lang
18target_lang = args.target_lang
19
20def load_embeddings(path, dimension=300):
21 """
22 Loads the embeddings from a word2vec formatted file.
23 word2vec format is one line per word and it's associated embedding
24 (dimension x floating numbers) separated by spaces
25 The first line may or may not include the word count and dimension
26 """
27 vectors = {}
28 with open(path, mode='r', encoding='utf8') as fp:
29 first_line = fp.readline().rstrip('\n')
30 if first_line.count(' ') == 1:
31 # includes the "word_count dimension" information
32 (word_count, dimension) = map(int, first_line.split())
33 else:
34 # assume the file only contains vectors
35 fp.seek(0)
36 for line in fp:
37 elems = line.split()
38 vectors[" ".join(elems[:-dimension])] = " ".join(elems[-dimension:])
39 return vectors
40
41#######################################################################
42# Vectors Load Here #
43#######################################################################
44
45source_vectors_filename = args.source_vector
46target_vectors_filename = args.target_vector
47vectors_source = load_embeddings(source_vectors_filename)
48vectors_target = load_embeddings(target_vectors_filename)
49
50#######################################################################
51# Corpora Load Here #
52#######################################################################
53
54source_defs_filename = args.source_defs
55target_defs_filename = args.target_defs
56defs_source = [line.rstrip('\n') for line in open(source_defs_filename, encoding='utf8')]
57defs_target = [line.rstrip('\n') for line in open(target_defs_filename, encoding='utf8')]
58
59import numpy as np
60from mosestokenizer import *
61
62def clean_corpus_using_embeddings_vocabulary(
63 embeddings_dictionary,
64 corpus,
65 vectors,
66 language,
67 ):
68 '''
69 Cleans corpus using the dictionary of embeddings.
70 Any word without an associated embedding in the dictionary is ignored.
71 Adds '__target-language' and '__source-language' at the end of the words according to their language.
72 '''
73 clean_corpus, clean_vectors, keys = [], {}, []
74 words_we_want = set(embeddings_dictionary)
75 tokenize = MosesTokenizer(language)
76 for key, doc in enumerate(corpus):
77 clean_doc = []
78 words = tokenize(doc)
79 for word in words:
80 if word in words_we_want:
81 clean_doc.append(word + '__%s' % language)
82 clean_vectors[word + '__%s' % language] = np.array(vectors[word].split()).astype(np.float)
83 if len(clean_doc) > 3 and len(clean_doc) < 25:
84 keys.append(key)
85 clean_corpus.append(' '.join(clean_doc))
86 tokenize.close()
87 return np.array(clean_corpus), clean_vectors, keys
88
89import nltk
90clean_src_corpus, clean_src_vectors, src_keys = clean_corpus_using_embeddings_vocabulary(
91 set(vectors_source.keys()),
92 defs_source,
93 vectors_source,
94 source_lang,
95 )
96
97clean_target_corpus, clean_target_vectors, target_keys = clean_corpus_using_embeddings_vocabulary(
98 set(vectors_target.keys()),
99 defs_target,
100 vectors_target,
101 target_lang,
102 )
103
104import random
105take = args.instances
106
107common_keys = set(src_keys).intersection(set(target_keys))
108take = min(len(common_keys), take) # you can't sample more than length
109experiment_keys = random.sample(common_keys, take)
110
111instances = len(experiment_keys)
112
113clean_src_corpus = list(clean_src_corpus[experiment_keys])
114clean_target_corpus = list(clean_target_corpus[experiment_keys])
115
116print(f'{source_lang} - {target_lang} : document sizes: {len(clean_src_corpus)}, {len(clean_target_corpus)}')
117
118del vectors_source, vectors_target, defs_source, defs_target
119
120from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
121
122vec = CountVectorizer().fit(clean_src_corpus + clean_target_corpus)
123common = [word for word in vec.get_feature_names() if word in clean_src_vectors or word in clean_target_vectors]
124W_common = []
125for w in common:
126 if w in clean_src_vectors:
127 W_common.append(np.array(clean_src_vectors[w]))
128 else:
129 W_common.append(np.array(clean_target_vectors[w]))
130
131print(f'{source_lang} - {target_lang}: the vocabulary size is {len(W_common)}')
132
133from sklearn.preprocessing import normalize
134W_common = np.array(W_common)
135W_common = normalize(W_common)
136vect = TfidfVectorizer(vocabulary=common, dtype=np.double, norm=None)
137vect.fit(clean_src_corpus + clean_target_corpus)
138X_train_idf = vect.transform(clean_src_corpus)
139X_test_idf = vect.transform(clean_target_corpus)
140
141vect_tf = CountVectorizer(vocabulary=common, dtype=np.double)
142vect_tf.fit(clean_src_corpus + clean_target_corpus)
143X_train_tf = vect_tf.transform(clean_src_corpus)
144X_test_tf = vect_tf.transform(clean_target_corpus)
145
146import ot
147from lapjv import lapjv
148from sklearn.neighbors import KNeighborsClassifier
149from sklearn.metrics import euclidean_distances
150from sklearn.externals.joblib import Parallel, delayed
151from sklearn.utils import check_array
152from sklearn.metrics.scorer import check_scoring
153from pathos.multiprocessing import ProcessingPool as Pool
154from sklearn.metrics import euclidean_distances
155
156class WassersteinDistances(KNeighborsClassifier):
157 """
158 Implements a nearest neighbors classifier for input distributions using the Wasserstein distance as metric.
159 Source and target distributions are l_1 normalized before computing the Wasserstein distance.
160 Wasserstein is parametrized by the distances between the individual points of the distributions.
161 In this work, we propose to use cross-lingual embeddings for calculating these distances.
162
163 """
164 def __init__(self, W_embed, n_neighbors=1, n_jobs=1, verbose=False, sinkhorn= False, sinkhorn_reg=0.1):
165 """
166 Initialization of the class.
167 Arguments
168 ---------
169 W_embed: embeddings of the words, np.array
170 verbose: True/False
171 """
172 self.sinkhorn = sinkhorn
173 self.sinkhorn_reg = sinkhorn_reg
174 self.W_embed = W_embed
175 self.verbose = verbose
176 super(WassersteinDistances, self).__init__(n_neighbors=n_neighbors, n_jobs=n_jobs, metric='precomputed', algorithm='brute')
177
178 def _wmd(self, i, row, X_train):
179 union_idx = np.union1d(X_train[i].indices, row.indices)
180 W_minimal = self.W_embed[union_idx]
181 W_dist = euclidean_distances(W_minimal)
182 bow_i = X_train[i, union_idx].A.ravel()
183 bow_j = row[:, union_idx].A.ravel()
184 if self.sinkhorn:
185 return ot.sinkhorn2(bow_i, bow_j, W_dist, self.sinkhorn_reg, numItermax=50, method='sinkhorn_stabilized',)[0]
186 else:
187 return ot.emd2(bow_i, bow_j, W_dist)
188
189 def _wmd_row(self, row):
190 X_train = self._fit_X
191 n_samples_train = X_train.shape[0]
192 return [self._wmd(i, row, X_train) for i in range(n_samples_train)]
193
194 def _pairwise_wmd(self, X_test, X_train=None):
195 n_samples_test = X_test.shape[0]
196
197 if X_train is None:
198 X_train = self._fit_X
199 pool = Pool(nodes=self.n_jobs) # Parallelization of the calculation of the distances
200 dist = pool.map(self._wmd_row, X_test)
201 return np.array(dist)
202
203 def fit(self, X, y): # X_train_idf
204 X = check_array(X, accept_sparse='csr', copy=True) # check if array is sparse
205 X = normalize(X, norm='l1', copy=False)
206 return super(WassersteinDistances, self).fit(X, y) # X_train_idf, np_ones(document collection size)
207
208 def predict(self, X):
209 X = check_array(X, accept_sparse='csr', copy=True)
210 X = normalize(X, norm='l1', copy=False)
211 dist = self._pairwise_wmd(X)
212 dist = dist * 1000 # for lapjv, small floating point numbers are evil
213 return super(WassersteinDistances, self).predict(dist)
214
215 def kneighbors(self, X, n_neighbors=1): # X : X_train_idf
216 X = check_array(X, accept_sparse='csr', copy=True)
217 X = normalize(X, norm='l1', copy=False)
218 dist = self._pairwise_wmd(X)
219 dist = dist * 1000 # for lapjv, small floating point numbers are evil
220 return lapjv(dist) # and here is the matching part
221
222def mrr_precision_at_k(golden, preds, k_list=[1,]):
223 """
224 Calculates Mean Reciprocal Error and Hits@1 == Precision@1
225 """
226 my_score = 0
227 precision_at = np.zeros(len(k_list))
228 for key, elem in enumerate(golden):
229 if elem in preds[key]:
230 location = np.where(preds[key]==elem)[0][0]
231 my_score += 1/(1+ location)
232 for k_index, k_value in enumerate(k_list):
233 if location < k_value:
234 precision_at[k_index] += 1
235 return my_score/len(golden), (precision_at/len(golden))[0]
236
237print(f'WMD - tfidf: {source_lang} - {target_lang}')
238clf = WassersteinDistances(W_embed=W_common, n_neighbors=5, n_jobs=14)
239clf.fit(X_train_idf[:instances], np.ones(instances))
240row_ind, col_ind, a = clf.kneighbors(X_test_idf[:instances], n_neighbors=instances)
241result = zip(row_ind, col_ind)
242hit_one = len([x for x,y in result if x == y])
243print(f'{hit_one} definitions have been mapped correctly')
244
245import csv
246percentage = hit_one / instances * 100
247fields = [f'{source_lang}', f'{target_lang}', f'{instances}', f'{hit_one}', f'{percentage}']
248with open('/home/syigit/multilang_results/wmd_matching_result.csv', 'a') as f:
249 writer = csv.writer(f)
250 writer.writerow(fields)
251
252print(f'Sinkhorn - tfidf: {source_lang} - {target_lang}')
253clf = WassersteinDistances(W_embed=W_common, n_neighbors=5, n_jobs=14, sinkhorn=True)
254clf.fit(X_train_idf[:instances], np.ones(instances))
255row_ind, col_ind, a = clf.kneighbors(X_test_idf[:instances], n_neighbors=instances)
256
257result = zip(row_ind, col_ind)
258hit_one = len([x for x,y in result if x == y])
259print(f'{hit_one} definitions have been mapped correctly')
260
261percentage = hit_one / instances * 100
262fields = [f'{source_lang}', f'{target_lang}', f'{instances}', f'{hit_one}', f'{percentage}']
263with open('/home/syigit/multilang_results/sinkhorn_matching_result.csv', 'a') as f:
264 writer = csv.writer(f)
265 writer.writerow(fields)