aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--WMD_matching.py331
1 files changed, 134 insertions, 197 deletions
diff --git a/WMD_matching.py b/WMD_matching.py
index c65e6e5..3b8b1a9 100644
--- a/WMD_matching.py
+++ b/WMD_matching.py
@@ -1,21 +1,27 @@
1###########################
2# Wasserstein Retrieval #
3###########################
4import argparse 1import argparse
2import numpy as np
3from mosestokenizer import *
4import nltk
5import random
6from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
7from sklearn.preprocessing import normalize
8from Wass_Matcher import Wasserstein_Matcher
5 9
6parser = argparse.ArgumentParser(description='run matching using wmd and wasserstein distances') 10if __name__ == "__main__":
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 11
15args = parser.parse_args() 12 parser = argparse.ArgumentParser(description='matching using wmd and wasserstein distance')
13 parser.add_argument('source_lang', help='source language short name')
14 parser.add_argument('target_lang', help='target language short name')
15 parser.add_argument('source_vector', help='path of the source vector')
16 parser.add_argument('target_vector', help='path of the target vector')
17 parser.add_argument('source_defs', help='path of the source definitions')
18 parser.add_argument('target_defs', help='path of the target definitions')
19 parser.add_argument('-b', '--batch', action='store_true', help='running in batch (store results in csv) or running a single instance (output the results)')
20 parser.add_argument('mode', choices=['all', 'wmd', 'snk'], default='all', help='which methods to run')
21 parser.add_argument('-n', '--instances', help='number of instances in each language to retrieve', default=1000, type=int)
22 args = parser.parse_args()
16 23
17source_lang = args.source_lang 24 main(args)
18target_lang = args.target_lang
19 25
20def load_embeddings(path, dimension=300): 26def load_embeddings(path, dimension=300):
21 """ 27 """
@@ -38,27 +44,6 @@ def load_embeddings(path, dimension=300):
38 vectors[" ".join(elems[:-dimension])] = " ".join(elems[-dimension:]) 44 vectors[" ".join(elems[:-dimension])] = " ".join(elems[-dimension:])
39 return vectors 45 return vectors
40 46
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( 47def clean_corpus_using_embeddings_vocabulary(
63 embeddings_dictionary, 48 embeddings_dictionary,
64 corpus, 49 corpus,
@@ -86,139 +71,6 @@ def clean_corpus_using_embeddings_vocabulary(
86 tokenize.close() 71 tokenize.close()
87 return np.array(clean_corpus), clean_vectors, keys 72 return np.array(clean_corpus), clean_vectors, keys
88 73
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,]): 74def mrr_precision_at_k(golden, preds, k_list=[1,]):
223 """ 75 """
224 Calculates Mean Reciprocal Error and Hits@1 == Precision@1 76 Calculates Mean Reciprocal Error and Hits@1 == Precision@1
@@ -234,32 +86,117 @@ def mrr_precision_at_k(golden, preds, k_list=[1,]):
234 precision_at[k_index] += 1 86 precision_at[k_index] += 1
235 return my_score/len(golden), (precision_at/len(golden))[0] 87 return my_score/len(golden), (precision_at/len(golden))[0]
236 88
237print(f'WMD - tfidf: {source_lang} - {target_lang}') 89def main(args):
238clf = WassersteinDistances(W_embed=W_common, n_neighbors=5, n_jobs=14) 90
239clf.fit(X_train_idf[:instances], np.ones(instances)) 91 source_lang = args.source_lang
240row_ind, col_ind, a = clf.kneighbors(X_test_idf[:instances], n_neighbors=instances) 92 target_lang = args.target_lang
241result = zip(row_ind, col_ind) 93
242hit_one = len([x for x,y in result if x == y]) 94 source_vectors_filename = args.source_vector
243print(f'{hit_one} definitions have been mapped correctly') 95 target_vectors_filename = args.target_vector
244 96 vectors_source = load_embeddings(source_vectors_filename)
245import csv 97 vectors_target = load_embeddings(target_vectors_filename)
246percentage = hit_one / instances * 100 98
247fields = [f'{source_lang}', f'{target_lang}', f'{instances}', f'{hit_one}', f'{percentage}'] 99 source_defs_filename = args.source_defs
248with open('/home/syigit/multilang_results/wmd_matching_result.csv', 'a') as f: 100 target_defs_filename = args.target_defs
249 writer = csv.writer(f) 101
250 writer.writerow(fields) 102 batch = args.batch
251 103 mode = args.mode
252print(f'Sinkhorn - tfidf: {source_lang} - {target_lang}') 104 defs_source = [line.rstrip('\n') for line in open(source_defs_filename, encoding='utf8')]
253clf = WassersteinDistances(W_embed=W_common, n_neighbors=5, n_jobs=14, sinkhorn=True) 105 defs_target = [line.rstrip('\n') for line in open(target_defs_filename, encoding='utf8')]
254clf.fit(X_train_idf[:instances], np.ones(instances)) 106
255row_ind, col_ind, a = clf.kneighbors(X_test_idf[:instances], n_neighbors=instances) 107 clean_src_corpus, clean_src_vectors, src_keys = clean_corpus_using_embeddings_vocabulary(
256 108 set(vectors_source.keys()),
257result = zip(row_ind, col_ind) 109 defs_source,
258hit_one = len([x for x,y in result if x == y]) 110 vectors_source,
259print(f'{hit_one} definitions have been mapped correctly') 111 source_lang,
260 112 )
261percentage = hit_one / instances * 100 113
262fields = [f'{source_lang}', f'{target_lang}', f'{instances}', f'{hit_one}', f'{percentage}'] 114 clean_target_corpus, clean_target_vectors, target_keys = clean_corpus_using_embeddings_vocabulary(
263with open('/home/syigit/multilang_results/sinkhorn_matching_result.csv', 'a') as f: 115 set(vectors_target.keys()),
264 writer = csv.writer(f) 116 defs_target,
265 writer.writerow(fields) 117 vectors_target,
118 target_lang,
119 )
120
121 take = args.instances
122
123 common_keys = set(src_keys).intersection(set(target_keys))
124 take = min(len(common_keys), take) # you can't sample more than length
125 experiment_keys = random.sample(common_keys, take)
126
127 instances = len(experiment_keys)
128
129 clean_src_corpus = list(clean_src_corpus[experiment_keys])
130 clean_target_corpus = list(clean_target_corpus[experiment_keys])
131
132 if (not batch):
133 print(f'{source_lang} - {target_lang} : document sizes: {len(clean_src_corpus)}, {len(clean_target_corpus)}')
134
135 del vectors_source, vectors_target, defs_source, defs_target
136
137 vec = CountVectorizer().fit(clean_src_corpus + clean_target_corpus)
138 common = [word for word in vec.get_feature_names() if word in clean_src_vectors or word in clean_target_vectors]
139 W_common = []
140 for w in common:
141 if w in clean_src_vectors:
142 W_common.append(np.array(clean_src_vectors[w]))
143 else:
144 W_common.append(np.array(clean_target_vectors[w]))
145
146 if (not batch):
147 print(f'{source_lang} - {target_lang}: the vocabulary size is {len(W_common)}')
148
149 W_common = np.array(W_common)
150 W_common = normalize(W_common)
151 vect = TfidfVectorizer(vocabulary=common, dtype=np.double, norm=None)
152 vect.fit(clean_src_corpus + clean_target_corpus)
153 X_train_idf = vect.transform(clean_src_corpus)
154 X_test_idf = vect.transform(clean_target_corpus)
155
156 vect_tf = CountVectorizer(vocabulary=common, dtype=np.double)
157 vect_tf.fit(clean_src_corpus + clean_target_corpus)
158 X_train_tf = vect_tf.transform(clean_src_corpus)
159 X_test_tf = vect_tf.transform(clean_target_corpus)
160
161 if (mode == 'wmd' or mode == 'all'):
162 if (not batch):
163 print(f'WMD - tfidf: {source_lang} - {target_lang}')
164
165 clf = WassersteinDistances(W_embed=W_common, n_neighbors=5, n_jobs=14)
166 clf.fit(X_train_idf[:instances], np.ones(instances))
167 row_ind, col_ind, a = clf.kneighbors(X_test_idf[:instances], n_neighbors=instances)
168 result = zip(row_ind, col_ind)
169 hit_one = len([x for x,y in result if x == y])
170 percentage = hit_one / instances * 100
171
172 if (not batch):
173 print(f'{hit_one} definitions have been mapped correctly, {percentage}%')
174
175 if (batch):
176 import csv
177 fields = [f'{source_lang}', f'{target_lang}', f'{instances}', f'{hit_one}', f'{percentage}']
178 with open('wmd_matching_results.csv', 'a') as f:
179 writer = csv.writer(f)
180 writer.writerow(fields)
181
182 if (mode == 'snk' or mode == 'all'):
183 if (not batch):
184 print(f'Sinkhorn - tfidf: {source_lang} - {target_lang}')
185
186 clf = WassersteinDistances(W_embed=W_common, n_neighbors=5, n_jobs=14, sinkhorn=True)
187 clf.fit(X_train_idf[:instances], np.ones(instances))
188 row_ind, col_ind, a = clf.kneighbors(X_test_idf[:instances], n_neighbors=instances)
189
190 result = zip(row_ind, col_ind)
191 hit_one = len([x for x,y in result if x == y])
192
193 if (not batch):
194 print(f'{hit_one} definitions have been mapped correctly')
195
196
197 if (batch):
198 percentage = hit_one / instances * 100
199 fields = [f'{source_lang}', f'{target_lang}', f'{instances}', f'{hit_one}', f'{percentage}']
200 with open('sinkhorn_matching_result.csv', 'a') as f:
201 writer = csv.writer(f)
202 writer.writerow(fields)