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