Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MRG] Implement 'Translation Matrix' #1434

Merged
merged 48 commits into from
Sep 13, 2017
Merged

Conversation

robotcator
Copy link
Contributor

This PR adds translation matrix feature to gensim model.

@gojomo
Copy link
Collaborator

gojomo commented Jul 12, 2017

Very excited to see this! Note that it may be useful for more than just sets of word-vectors – for example, if you were to Doc2Vec-train a batch of new documents, but include a bunch (100s? 1000s? TBD) of reference documents from a prior larger run, you could presumably translate the new doc-vecs back into the older/larger space using those reference docs to learn the back-mapping.

The comments & documentation need a lot of review/improvement for clarity/completeness. (Variable and method names might benefit from some careful consideration, too.) When it's thought to be ready for merging – passing tests and feature complete – I'll be happy to give it a close top-to-bottom read.

@robotcator
Copy link
Contributor Author

@gojomo Thanks, happy to have you help. You mean the translation matrix not only restrict to translate the word, but can apply to the document translation? Am I right?

@gojomo
Copy link
Collaborator

gojomo commented Jul 12, 2017

Not document 'translation' as in from one natural-language to another, but from alternate Doc2Vec runs on different sets of (same-language) documents - but featuring enough overlapping texts to learn the mapping.

@robotcator
Copy link
Contributor Author

You mean when I were to train on a batch of new documents, and get the new doc vector. And There are also many reference documents, we can use those to learn to back-mapping. But I don't quite understand the reference documents( in same language), can you explain in more detail? Thanks.

@gojomo
Copy link
Collaborator

gojomo commented Jul 13, 2017

Let's say you train doc-vecs for 10 million documents. Then you get a batch of 500,000 more.

If you train the old model on just the new 500K, the model is improving for those – but drifting away from modelling the older docs. (If some docs/doc-types are not re-presented, in interleaved fashion, the model will lose its 'tuning' for them.)

Alternatively, if you train a new model on just the new 500K, the doc-vectors from this new model will have no essential relationship with the older model. (There's no absolute vector positioning from any training session - the exact same document can wind up in very different places based on the inherent randomness of initialization and training. It's only the relative positions of doc-vectors from the same combined training that is meaningful.)

So, I'm proposing it could be effective to train the new 500K, with some small set of "reference point" documents from the older set mixed-in. (Let's say for now: 10,000 docs.) When the training of the all-new model is done, those 10K ref docs will have points in the "new" space that render them comparable to the 500K new docs. These points will quite probably be very unlike their points in the older space. But, using this TranslationMatrix, those 10K points can be used to learn a mapping back to the older (10M doc) space, and then transform all 500K new docs into the original, older space for comparison to any of the now 10.5M documents.

@robotcator
Copy link
Contributor Author

Thank for you explaining. I understand what you said now. The "reference documents" from the older set will be mixed into the new training set, those can be seen as pivot to learn the "TranslationMatrix". It is worth trying.

@robotcator
Copy link
Contributor Author

Hi, @gojomo recently, I have doing the experiment about produce new document vectors via the translation matrix which learned by the back-mapping method. If I train on new 500K document , with 10k of "reference point" documents taken from the 10 million docs. Then, by using TranslationMatrix, we can transform all 500K new docs into the original, older space. So we got 500k more document vector in the original space. But Is there any need measure/evaluate the new docs' vector in older space (perform better)? Or we just use this method to produce more doc vectors rather than train the entire corpus(10.5 million document )again?

@piskvorky
Copy link
Owner

Hi guys, what's the context here? Can you please add some description of the purpose of this PR?

What are its use-cases, from gensim users' POV?

@robotcator
Copy link
Contributor Author

@piskvorky This PR is to implement the translation matrix. If needed, I will add more description in this PR.

@gojomo
Copy link
Collaborator

gojomo commented Jul 31, 2017

@robotcator - Yes, an experiment to evaluate the usefulness of such reference-projected doc-vectors would be good – especially in comparison to inferring-doc-vectors. (Maybe inference works better. Or maybe if there are a lot of new words in the incremental docs, the back-projection works better... it'd certainly be able to bootstrap word-vectors, in the original space, that inference alone would not.) But, I don't have a proposed experiment design ready – my suggestion was conjectural.

@robotcator
Copy link
Contributor Author

Hi, I use the IMDB sentiment dataset to train two models, one with 15k documents and the other with 25k documents, all other parameters are the same. The code is below(some are from the notebook)

import gensim
from gensim.models.doc2vec import TaggedDocument
from gensim.models import Doc2Vec
from collections import namedtuple
from gensim import utils

def read_sentimentDocs():
    SentimentDocument = namedtuple('SentimentDocument', 'words tags split sentiment')

    alldocs = []  # will hold all docs in original order
    with utils.smart_open('alldata-id.txt', encoding='utf-8') as alldata:
        for line_no, line in enumerate(alldata):
            tokens = gensim.utils.to_unicode(line).split()
            words = tokens[1:]
            tags = [line_no] # `tags = [tokens[0]]` would also work at extra memory cost
            split = ['train','test','extra','extra'][line_no//25000]  # 25k train, 25k test, 25k extra
            sentiment = [1.0, 0.0, 1.0, 0.0, None, None, None, None][line_no//12500] # [12.5K pos, 12.5K neg]*2 then unknown
            alldocs.append(SentimentDocument(words, tags, split, sentiment))

    train_docs = [doc for doc in alldocs if doc.split == 'train']
    test_docs = [doc for doc in alldocs if doc.split == 'test']
    doc_list = alldocs[:]  # for reshuffling per pass

    print('%d docs: %d train-sentiment, %d test-sentiment' % (len(doc_list), len(train_docs), len(test_docs)))

    return train_docs, test_docs, doc_list

train_docs, test_docs, doc_list = read_sentimentDocs()

import multiprocessing
from random import shuffle

cores = multiprocessing.cpu_count()
# PV-DM w/concatenation - window=5 (both sides) approximates paper's 10-word total window size
model = Doc2Vec(dm=1, dm_concat=1, size=100, window=5, negative=5, hs=0, min_count=2, workers=cores)

small_train_docs = train_docs[:15000]

# train for small corpus
model.build_vocab(small_train_docs)
for epoch in xrange(50):
    shuffle(small_train_docs)
    model.train(small_train_docs, total_examples=len(small_train_docs), epochs=1)
# save the model
model.save("small_doc_15000_iter50.bin")


# train for large corpus
model.build_vocab(train_docs)
for epoch in xrange(50):
    shuffle(train_docs)
    model.train(train_docs, total_examples=len(train_docs), epochs=1)
# save the model
model.save("large_doc_25000_iter50.bin")

I use those 15k documents as reference document to mix in the new 15k documents, so I got the model2 with 25k documents. Then using those 15k documents and their corresponding doc vectors in model1 and model2, we can train a translation matrix. The code is below:

# coding=utf-8
from gensim.models import Doc2Vec
from sklearn.linear_model import LogisticRegression
from collections import namedtuple

import numpy as np

model1 = Doc2Vec.load("small_doc_15000_iter50.bin")
model2 = Doc2Vec.load("large_doc_25000_iter50.bin")

l = model1.docvecs.count
l2 = model2.docvecs.count
m1 = np.array([model1.docvecs[i] for i in range(l)])
m2 = np.array([model2.docvecs[i] for i in range(l)])

# train the translation matrix
tm = np.linalg.lstsq(m2, m1, -1)[0]
# back-mapping for the 15k documents
model1_15k_docvec = []
for i in range(l, l2):
    x = model2.docvecs[i]
    y = np.dot(x, tm)
    model1_15k_docvec.append(y)

The problem is how to evaluate the 15k document vector learned by the method, I think we can train a classifier to see whether those vector works. But we need a benchmark for comparison. The code is rough but did I explain the problem clear?@menshikh-iv

@@ -0,0 +1,344 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove spaces in the filename Translation_Matrix_Revisit.ipynb

" train_array[i] = m2[i]\n",
" train_label[i] = 1\n",
"\n",
" train_array[i+12500] = m2[i+12500]\n",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spaces before/after + (here and anywhere)

]
}
],
"source": [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need more explanation between cells (what happens)

],
"source": [
"from sklearn.decomposition import PCA\n",
"import matplotlib.pyplot as plt\n",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use plotly for all viz

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -0,0 +1,1352 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also please add a description before each image (what I see now).

},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA68AAAJPCAYAAACTq+LfAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3Xu8XWV9J/7Pk4RgjEFRAwhKDtKWBCFAQhJEgUQFRLQU\nBgcjKPUGQrF0aKfC/KyGVqbUljAgBsFJFUpELlYHVEaBEhFBCEQSlJMLDYlBxYlykVvI7fn9cUII\nkEBk75y9cs77/XqdF2uv/ey1viuC33zO86y1S601AAAA0GQDOl0AAAAAvBThFQAAgMYTXgEAAGg8\n4RUAAIDGE14BAABoPOEVAACAxmtLeC2lvLuUMq+UsqCU8ukNvP+6Usp1pZS7Syn3lFL+vB3nBQA2\nrJQyvZTym1LK3BcZc34pZeHa/rx3b9YHAH+olsNrKWVAkguSHJrkLUkml1JGPm/YKUnurrXunWRS\nknNKKYNaPTcAsFFfTU9v3qBSymFJdq21/nGSE5N8ubcKA4CXox0zr+OTLKy1Lqm1rkzyjSRHPG/M\ng0mGrd0eluR3tdZVbTg3ALABtdZbkjz8IkOOSHLp2rG3J3l1KWX73qgNAF6OdoTXnZIsXe/1A2v3\nre8rSd5SSvlVkjlJTm3DeQGAl+/5/fuXeWH/BoDG6K0HNp2RZE6tdcck+yT5UinlVb10bgAAALZw\n7bjv9JdJdl7v9RvX7lvf25KclSS11v8spdyfZGSSO59/sFJKbUNNAJAkqbWWTtfQUL9M8qb1Xm+o\nfyfRmwFor5fbm9sx8zoryR+VUkaUUgYn+UCSa543pjvJu5Jk7f00f5Jk0cYOWGv108LP5z73uY7X\nsKX/+DP0Z9iEH3+Grf+QsvZnQ65J8uEkKaXsl+SRWutvNnagTv9v2Rs//eW/uf5wnf3hGl1n3/vp\nL9fZipZnXmutq0sppyT5QXrC8PRaa3cp5cSet+vFSf4xyVdLKXPS00T/ttb6UKvnBgA2rJTy9SQT\nk7yulPKLJJ9LMjhre3Ot9XullPeUUu5L8kSSj3SuWgB4aW35uppa6/9Nstvz9l203vZvk7yvHecC\nAF5arfWDmzDmlN6oBQDaobce2EQvmjhxYqdL2OL5M2ydP8PW+TOE3tVf/pvrD9fZH64xcZ19TX+5\nzlaUVtcdt1sppTatJgC2TKWUVA9sapneDEC7tNKbzbwCAADQeMIrAAAAjSe8AgAA0HjCKwAAAI0n\nvAIAANB4wisAAACNJ7wCAADQeMIrAAAAjSe8AgAA0HjCKwAAAI0nvAIAANB4wisAAACNJ7wCAADQ\neMIrAAAAjSe8AgAA0HjCKwAAAI0nvAIAANB4wisAAACNJ7wCAADQeMIrAAAAjSe8AgAA0HjCKwAA\nAI0nvAIAANB4wiuN8eijj+bCCy/sdBkAAEADCa80xsMPP5xp06Z1ugwAAKCBhFca44wzzsiiRYsy\nZsyYfPSjH813vvOdJMmRRx6Zj3/840mSr371q/m7v/u7JMnUqVOz5557ZvTo0TnvvPM6VjcAALD5\nCa80xtlnn51dd901s2fPzqGHHpof/ehHSZJf/epXuffee5MkP/rRj3LggQdm9uzZueSSSzJr1qzc\ndttt+cpXvpI5c+Z0snwAAGAzEl5ppAMOOCA333xzuru7s/vuu2f77bfPgw8+mNtuuy37779/brnl\nlhx55JF5xStekaFDh+aoo45aF3YBAIC+Z1CnC4AkWbZsWebMmZNVq1YlSXbcccc88sgj+f73v5+D\nDjooDz30UK688soMGzYsQ4cO7XC1AABAbzPzSsddfvkVGTFiZI477u8yb978XH75FUmS/fbbL+ee\ne24OPPDAvP3tb8+//Mu/5IADDkjSMzP77W9/O8uXL88TTzyRb33rW+veAwAA+h4zr3TUsmXL8rGP\nnZynnropyegkh+fYY4/NrbfekgMOOCDXX3993vzmN2fnnXfOww8/nAMPPDBJss8+++TP//zPM27c\nuJRScsIJJ2Svvfbq6LUAAACbT6m1drqG5yil1KbVxOYza9asHHzwJ/Poo3et27fNNmNyww0XZdy4\ncR2sDOgLSimptZZO17Gl05sBaJdWerNlw3RUV1dXVqxYnGTu2j1zs3LlknR1dXWuKAAAoHGEVzpq\n+PDhmT59WoYMmZRtthmTIUMmZfr0aRk+fHinSwMAABrEsmEaYdmyZVm8eHG6uroEV6BtLBtuD70Z\ngHZppTcLrwD0WcJre+jNALSLe14BAADo04RXAAAAGk94BQAAoPGEVwAAABpPeAUAAKDxhFcAAAAa\nT3gFAACg8YRXAAAAGk94BQAAoPGEVwCAfuzMM8/M1KlTO10GwEsSXgEAAGg84RUAoJ8566yzsttu\nu+XAAw/M/PnzU2vNpEmTMnv27CTJ7373u+yyyy5JkjVr1uRv//ZvM2HChOy99975yle+0snSgX5s\n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\n+uijufDCC5MkP/zhD/O+972vwxUBQDN9/atfzeTly3Pm0KG59JJLzLoC9BLhlSTJww8/nGnTpiXp\nWQ5VykvP5K9Zs2ZzlwUAjbJ69ep8Y8aMbLV6dVbsuGOOPe64TpcE0G8IryRJzjjjjCxatChjxozJ\npz/96Tz22GN5//vfn1GjRuVDH/rQunG77LJLTj/99Oy77765+uqrs2jRohx22GEZN25cDjrooCxY\nsCBJ8tvf/jZHH310JkyYkAkTJuTWW2/t1KUBQNv88Ic/zGtXrcrXXvnK/MuXv5wBA/xVCqC3DOp0\nATTD2WefnZ///OeZPXt2fvjDH+bP/uzPcu+992aHHXbI2972ttx6663Zf//9kySvf/3rc+eddyZJ\n3vWud+Wiiy7KrrvumjvuuCMnnXRSbrzxxpx66qk57bTTsv/++2fp0qU59NBDc++993byEgGgZV+f\nPj2PPvFERh90UN7xjnd0uhyAfkV4ZYPGjx+fN7zhDUmSvffeO4sXL14XXo855pgkyRNPPJFbb701\n73//+/PMUyhXrlyZJLnhhhvS3d29bv/jjz+eJ598Mq985St7+1IAoC2efvrp/Ovll6cmue6CCzpd\nDkC/I7ySZcuWZc6cOVm1atW6fVtvvfW67YEDBz7nvaFDhybpued12223zezZs19wzFprbr/9dg+x\nAKDPuO6661JrzcePPTZ77LHHyzrGkiVLcuutt2by5MlJkjlz5uRXv/pVDjvssHaWCtAnuVGjn7v8\n8isyYsTIHHfc32XevPm5/PIrsqnf5Tds2LDssssuufrqq9ftmzt3bpLkkEMOyXnnnbdu/5w5c9pb\nOAD0sn+cMiVJ8vf//M8v+xj3339/vv71r697fffdd+d73/veBseuXr36ZZ8HoC8SXvuxZcuW5WMf\nOzlPPXVTHntsTmp9d4499tj89V//9XPGrf/k4ec/hXjGjBmZPn169t577+yxxx655pprkiTnnXde\n7rzzzuy1117ZY489ctFFF23+CwKAzeSxxx7LHXPm5HWvfnWOOeaYfPCDH8w555yTSZMmrVuB9Lvf\n/S677LJLkp4Z1gMPPDD77rtv9t133/zkJz9J0vOAxFtuuSVjxozJF77whXz2s5/NlVdemTFjxuSq\nq67KmWeemQ9/+MN5+9vfng9/+MN5+umn89GPfjSjR4/O2LFjM3PmzE79EQB0nGXD/djixYszeHBX\nnnpq9No9382wYWPy5S9/OePGjVs37vzzz1+3vWjRouccY8SIEbnuuutecOzXve51+cY3vrFZ6gaA\n3nbCCSckSeb/539m8ODBGTNmTPbdd98X/FL3mdfbbbddbrjhhgwePDj33XdfJk+enFmzZuXss8/O\nOeecs+6Xvdtvv33uuuuudb32zDPPTHd3d3784x9n8ODBmTp1agYMGJC5c+dm/vz5OeSQQ7Jw4cIM\nHjy4F68eoBmE136sq6srK1YsTjI3yegkc7Ny5ZJ0dXV1tC4AaJLu7u5cc801+aNdd83rXve6JMkR\nRxzxorfZrFy5MieeeGLuvvvuDBw4MAsXLtzk8/3pn/7punB6yy235C//8i+TJLvttlu6urqyYMGC\nl33PLcCWzLLhfmz48OGZPn1ahgyZlG22GZMhQyZl+vRpGT58eKdLA4BG+NSn/iq77z42Tz75ytz3\nn0vyqU+dmiTrguugQYOyZs2aJMny5cvXfe7cc8/NDjvskLlz5+bOO+/MihUrNvmczzwYcUM29bkU\nAH2R8NrPTZ58TJYsmZcbbrgoS5bMy+TJx3S6JABohO7u7lxwwcVJfpLkB0l2zQUXXJw777wz1157\nbUop6erqWvfd51ddddW6zz766KPrvnLu0ksvXffwpWHDhuWxxx5bN27YsGH5/e9/v9EaDjjggMyY\nMSNJsmDBgixdujS77bZbey8UYAshvJLhw4dn3LhxZlwBYD133HFHkjel59aafZIcn2R1jj322Iwf\nPz5J8jd/8ze58MILM3bs2Dz00EPrPnvyySfna1/7WvbZZ58sWLBg3Wzq6NGjM2DAgOyzzz4577zz\nMmnSpNx7773rHtj0/HtoTz755KxevTqjR4/O5MmTc8kll/gaOqDfKk1bflJKqU2rCYAtUykltdby\n0iN5Mf21N3d3d2f33cemZ+a159kQyX659967cuWVV2bYsGE57bTTOlskwBamld5s5hUAYANGjRqV\nU075RJL9kvxJkv1yyimfyKhRozpcGUD/ZOYVgD7LzGt79Pfe3N3dnTvuuCPjx48XXAFa1EpvFl4B\n6LOE1/bQmwFoF8uGAQAA6NOEVwAAABpPeAUAAKDxhFcAAAAaT3gFAACg8YRXAAAAGk94BQAAoPGE\nVwAAABpPeAUAAKDxhFcAAAAaT3gFAACg8YRXAAAAGk94BQAAoPGEVwAAABqvLeG1lPLuUsq8UsqC\n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\nJKmgIjNbHcMBIiKLFp
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E251 unexpected spaces around keyword / parameter equals (here and anywhere)

},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA68AAAJPCAYAAACTq+LfAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3Xu8XWV9J/7Pk4RgjEFRAwhKDtKWBCFAQhJEgUQFRLQU\nBgcjKPUGQrF0aKfC/KyGVqbUljAgBsFJFUpELlYHVEaBEhFBCEQSlJMLDYlBxYlykVvI7fn9cUII\nkEBk75y9cs77/XqdF2uv/ey1viuC33zO86y1S601AAAA0GQDOl0AAAAAvBThFQAAgMYTXgEAAGg8\n4RUAAIDGE14BAABoPOEVAACAxmtLeC2lvLuUMq+UsqCU8ukNvP+6Usp1pZS7Syn3lFL+vB3nBQA2\nrJQyvZTym1LK3BcZc34pZeHa/rx3b9YHAH+olsNrKWVAkguSHJrkLUkml1JGPm/YKUnurrXunWRS\nknNKKYNaPTcAsFFfTU9v3qBSymFJdq21/nGSE5N8ubcKA4CXox0zr+OTLKy1Lqm1rkzyjSRHPG/M\ng0mGrd0eluR3tdZVbTg3ALABtdZbkjz8IkOOSHLp2rG3J3l1KWX73qgNAF6OdoTXnZIsXe/1A2v3\nre8rSd5SSvlVkjlJTm3DeQGAl+/5/fuXeWH/BoDG6K0HNp2RZE6tdcck+yT5UinlVb10bgAAALZw\n7bjv9JdJdl7v9RvX7lvf25KclSS11v8spdyfZGSSO59/sFJKbUNNAJAkqbWWTtfQUL9M8qb1Xm+o\nfyfRmwFor5fbm9sx8zoryR+VUkaUUgYn+UCSa543pjvJu5Jk7f00f5Jk0cYOWGv108LP5z73uY7X\nsKX/+DP0Z9iEH3+Grf+QsvZnQ65J8uEkKaXsl+SRWutvNnagTv9v2Rs//eW/uf5wnf3hGl1n3/vp\nL9fZipZnXmutq0sppyT5QXrC8PRaa3cp5cSet+vFSf4xyVdLKXPS00T/ttb6UKvnBgA2rJTy9SQT\nk7yulPKLJJ9LMjhre3Ot9XullPeUUu5L8kSSj3SuWgB4aW35uppa6/9Nstvz9l203vZvk7yvHecC\nAF5arfWDmzDmlN6oBQDaobce2EQvmjhxYqdL2OL5M2ydP8PW+TOE3tVf/pvrD9fZH64xcZ19TX+5\nzlaUVtcdt1sppTatJgC2TKWUVA9sapneDEC7tNKbzbwCAADQeMIrAAAAjSe8AgAA0HjCKwAAAI0n\nvAIAANB4wisAAACNJ7wCAADQeMIrAAAAjSe8AgAA0HjCKwAAAI0nvAIAANB4wisAAACNJ7wCAADQ\neMIrAAAAjSe8AgAA0HjCKwAAAI0nvAIAANB4wisAAACNJ7wCAADQeMIrAAAAjSe8AgAA0HjCKwAA\nAI0nvAIAANB4wiuN8eijj+bCCy/sdBkAAEADCa80xsMPP5xp06Z1ugwAAKCBhFca44wzzsiiRYsy\nZsyYfPSjH813vvOdJMmRRx6Zj3/840mSr371q/m7v/u7JMnUqVOz5557ZvTo0TnvvPM6VjcAALD5\nCa80xtlnn51dd901s2fPzqGHHpof/ehHSZJf/epXuffee5MkP/rRj3LggQdm9uzZueSSSzJr1qzc\ndttt+cpXvpI5c+Z0snwAAGAzEl5ppAMOOCA333xzuru7s/vuu2f77bfPgw8+mNtuuy37779/brnl\nlhx55JF5xStekaFDh+aoo45aF3YBAIC+Z1CnC4AkWbZsWebMmZNVq1YlSXbcccc88sgj+f73v5+D\nDjooDz30UK688soMGzYsQ4cO7XC1AABAbzPzSsddfvkVGTFiZI477u8yb978XH75FUmS/fbbL+ee\ne24OPPDAvP3tb8+//Mu/5IADDkjSMzP77W9/O8uXL88TTzyRb33rW+veAwAA+h4zr3TUsmXL8rGP\nnZynnropyegkh+fYY4/NrbfekgMOOCDXX3993vzmN2fnnXfOww8/nAMPPDBJss8+++TP//zPM27c\nuJRScsIJJ2Svvfbq6LUAAACbT6m1drqG5yil1KbVxOYza9asHHzwJ/Poo3et27fNNmNyww0XZdy4\ncR2sDOgLSimptZZO17Gl05sBaJdWerNlw3RUV1dXVqxYnGTu2j1zs3LlknR1dXWuKAAAoHGEVzpq\n+PDhmT59WoYMmZRtthmTIUMmZfr0aRk+fHinSwMAABrEsmEaYdmyZVm8eHG6uroEV6BtLBtuD70Z\ngHZppTcLrwD0WcJre+jNALSLe14BAADo04RXAAAAGk94BQAAoPGEVwAAABpPeAUAAKDxhFcAAAAa\nT3gFAACg8YRXAAAAGk94BQAAoPGEVwCAfuzMM8/M1KlTO10GwEsSXgEAAGg84RUAoJ8566yzsttu\nu+XAAw/M/PnzU2vNpEmTMnv27CTJ7373u+yyyy5JkjVr1uRv//ZvM2HChOy99975yle+0snSgX5s\n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\n+uijufDCC5MkP/zhD/O+972vwxUBQDN9/atfzeTly3Pm0KG59JJLzLoC9BLhlSTJww8/nGnTpiXp\nWQ5VykvP5K9Zs2ZzlwUAjbJ69ep8Y8aMbLV6dVbsuGOOPe64TpcE0G8IryRJzjjjjCxatChjxozJ\npz/96Tz22GN5//vfn1GjRuVDH/rQunG77LJLTj/99Oy77765+uqrs2jRohx22GEZN25cDjrooCxY\nsCBJ8tvf/jZHH310JkyYkAkTJuTWW2/t1KUBQNv88Ic/zGtXrcrXXvnK/MuXv5wBA/xVCqC3DOp0\nATTD2WefnZ///OeZPXt2fvjDH+bP/uzPcu+992aHHXbI2972ttx6663Zf//9kySvf/3rc+eddyZJ\n3vWud+Wiiy7KrrvumjvuuCMnnXRSbrzxxpx66qk57bTTsv/++2fp0qU59NBDc++993byEgGgZV+f\nPj2PPvFERh90UN7xjnd0uhyAfkV4ZYPGjx+fN7zhDUmSvffeO4sXL14XXo855pgkyRNPPJFbb701\n73//+/PMUyhXrlyZJLnhhhvS3d29bv/jjz+eJ598Mq985St7+1IAoC2efvrp/Ovll6cmue6CCzpd\nDkC/I7ySZcuWZc6cOVm1atW6fVtvvfW67YEDBz7nvaFDhybpued12223zezZs19wzFprbr/9dg+x\nAKDPuO6661JrzcePPTZ77LHHyzrGkiVLcuutt2by5MlJkjlz5uRXv/pVDjvssHaWCtAnuVGjn7v8\n8isyYsTIHHfc32XevPm5/PIrsqnf5Tds2LDssssuufrqq9ftmzt3bpLkkEMOyXnnnbdu/5w5c9pb\nOAD0sn+cMiVJ8vf//M8v+xj3339/vv71r697fffdd+d73/veBseuXr36ZZ8HoC8SXvuxZcuW5WMf\nOzlPPXVTHntsTmp9d4499tj89V//9XPGrf/k4ec/hXjGjBmZPn169t577+yxxx655pprkiTnnXde\n7rzzzuy1117ZY489ctFFF23+CwKAzeSxxx7LHXPm5HWvfnWOOeaYfPCDH8w555yTSZMmrVuB9Lvf\n/S677LJLkp4Z1gMPPDD77rtv9t133/zkJz9J0vOAxFtuuSVjxozJF77whXz2s5/NlVdemTFjxuSq\nq67KmWeemQ9/+MN5+9vfng9/+MN5+umn89GPfjSjR4/O2LFjM3PmzE79EQB0nGXD/djixYszeHBX\nnnpq9No9382wYWPy5S9/OePGjVs37vzzz1+3vWjRouccY8SIEbnuuutecOzXve51+cY3vrFZ6gaA\n3nbCCSckSeb/539m8ODBGTNmTPbdd98X/FL3mdfbbbddbrjhhgwePDj33XdfJk+enFmzZuXss8/O\nOeecs+6Xvdtvv33uuuuudb32zDPPTHd3d3784x9n8ODBmTp1agYMGJC5c+dm/vz5OeSQQ7Jw4cIM\nHjy4F68eoBmE136sq6srK1YsTjI3yegkc7Ny5ZJ0dXV1tC4AaJLu7u5cc801+aNdd83rXve6JMkR\nRxzxorfZrFy5MieeeGLuvvvuDBw4MAsXLtzk8/3pn/7punB6yy235C//8i+TJLvttlu6urqyYMGC\nl33PLcCWzLLhfmz48OGZPn1ahgyZlG22GZMhQyZl+vRpGT58eKdLA4BG+NSn/iq77z42Tz75ytz3\nn0vyqU+dmiTrguugQYOyZs2aJMny5cvXfe7cc8/NDjvskLlz5+bOO+/MihUrNvmczzwYcUM29bkU\nAH2R8NrPTZ58TJYsmZcbbrgoS5bMy+TJx3S6JABohO7u7lxwwcVJfpLkB0l2zQUXXJw777wz1157\nbUop6erqWvfd51ddddW6zz766KPrvnLu0ksvXffwpWHDhuWxxx5bN27YsGH5/e9/v9EaDjjggMyY\nMSNJsmDBgixdujS77bZbey8UYAshvJLhw4dn3LhxZlwBYD133HFHkjel59aafZIcn2R1jj322Iwf\nPz5J8jd/8ze58MILM3bs2Dz00EPrPnvyySfna1/7WvbZZ58sWLBg3Wzq6NGjM2DAgOyzzz4577zz\nMmnSpNx7773rHtj0/HtoTz755KxevTqjR4/O5MmTc8kll/gaOqDfKk1bflJKqU2rCYAtUykltdby\n0iN5Mf21N3d3d2f33cemZ+a159kQyX659967cuWVV2bYsGE57bTTOlskwBamld5s5hUAYANGjRqV\nU075RJL9kvxJkv1yyimfyKhRozpcGUD/ZOYVgD7LzGt79Pfe3N3dnTvuuCPjx48XXAFa1EpvFl4B\n6LOE1/bQmwFoF8uGAQAA6NOEVwAAABpPeAUAAKDxhFcAAAAaT3gFAACg8YRXAAAAGk94BQAAoPGE\nVwAAABpPeAUAAKDxhFcAAAAaT3gFAACg8YRXAAAAGk94BQAAoPGEVwAAABqvLeG1lPLuUsq8UsqC\n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\nJKmgIjNbHcMBIiKLFp
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E226 missing whitespace around arithmetic operator (here and anywhere)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

def test_translation_matrix(self):
model = translation_matrix.BackMappingTranslationMatrix(self.train_docs[:5], self.source_doc_vec, self.target_doc_vec)
transmat = model.train(self.train_docs[:5])
self.assertEqual(transmat.shape, (100, 100))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why only "sanity check"? Please add another tests (similar with translation tests from previous class)?

Copy link
Contributor Author

@robotcator robotcator Sep 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a little different with the translation of word. We build the mapping F between the target model and original model(old). Given a document vector x from the target model, we infer the vector by multiple F with x to get a new vector for the original vector. By now, we didn't have a good evaluation metric to assess the vector. So I only give the "sanity check".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can calculate cosine distance between vector from model and vector from translation

return translated_word


class BackMappingTranslationMatrix(utils.SaveLoad):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add example with viz for it (to translation matrix notebook)

def test_translation_matrix(self):
model = translation_matrix.BackMappingTranslationMatrix(self.train_docs[:5], self.source_doc_vec, self.target_doc_vec)
transmat = model.train(self.train_docs[:5])
self.assertEqual(transmat.shape, (100, 100))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can calculate cosine distance between vector from model and vector from translation

@@ -0,0 +1,366 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add this test to translation_matrix.ipynb (as another section) and remove Translation_Matrix_Revisit.ipynb

@menshikh-iv
Copy link
Contributor

menshikh-iv commented Sep 8, 2017

In general, LGTM for me (after fixing last comments), have you any suggestions @gojomo @piskvorky?

@menshikh-iv menshikh-iv changed the title [WIP] Implement 'Translation Matrix' [MRG] Implement 'Translation Matrix' Sep 13, 2017
@menshikh-iv menshikh-iv merged commit 1c0098c into piskvorky:develop Sep 13, 2017

"""
Produce translation matrix to translate the word from one language to another language, using either
standard nearest neighbour method or globally corrected neighbour retrieval method [1].
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could note that this is potentially more useful than just word-to-word translation; it'll work for any two sets of named-vectors where there are some paired-guideposts to learn the transformation.


Train a model with e.g.::

>>> transmat.train(word_pair)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API that supplies word_pair twice - both at construction & a later necessary train() step – seems odd to me, and in contrast to other gensim practice (where training can be combined with initialization, if training data is supplied.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


Attributes:
`mat` (ndarray): each row is the word vector of the lexicon
`index2word` (list): a list of lexicon
Copy link
Collaborator

@gojomo gojomo Sep 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wording like 'list of lexicon' unclear: is index2word a list of strings?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, index2word is a list of words(strings).

return Space(mat, words)

def normalize(self):
""" normalized the word vector's matrix """
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capitalization, verb-tense: 'Normalize...'

Examples: [("one", "uno"), ("two", "due")]

Args:
`word_pair` (list): a list pair of words
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a list of many word-pairs, a more natural name for this parameter would be plural word_pairs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

self.source_space = None
self.target_space = None

def train(self, word_pair):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This word_pair argument, which I noted seemed odd above, doesn't seem to be consulted at all in the following code. (Only the word-pairs supplied at initialization have any effect.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you said in the second comment, if training was combined with initialization, then this parameter would be used in train method.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the code body of the train() method, I see no references to this word_pair parameter. Hence, the parameter is unused and (unless the code changes) unnecessary here. If the intent was that this be an alternate way to supply the list-of-word-pairs, the code would have to change to reflect that intent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I change the code where the word_pairs is optional parameter in the initialization, and if provided, then train method will run followed.


def save(self, *args, **kwargs):
"""
Save the model to file but ignoring the souce_space and target_space
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

source_space

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I will fix all the grammar errors and typos here and below.


def apply_transmat(self, words_space):
"""
mapping the source word vector to the target word vector using translation matrix
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capitalization, tense: "Map…"

model = translation_matrix.TranslationMatrix(self.word_pair, self.source_word_vec, self.target_word_vec)
model.train(self.word_pair)

test_word_pair = [("one", "uno"), ("two", "due"), ("apple", "mela"), ("orange", "aranicione"), ("dog", "cane"), ("pig", "maiale"), ("cat", "gatto")]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every one of these word-pairs is in the training mappings. A 'truer' test of 'translation' functionality would verify that using these known signpost-equivalences creates a mapping that's useful for getting other word-to-word translations approximately right.

test_source_word, test_target_word = zip(*test_word_pair)
translated_words = model.translate(test_source_word, topn=3)

self.assertTrue("uno" in translated_words["one"])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a loop driven by test_word_pair.

@gojomo
Copy link
Collaborator

gojomo commented Sep 13, 2017

The constant stream of tiny tweak change-notifications, without any deeper review of the docs/naming issues I'd mentioned way-back, made me think this work was still far from review-readiness.

I just now see it was it marked for [MRG] and merged in the last 10 hours.

I think the API, naming, comments, and testing are all still a bit off. I've added some comments, but all of the code-doc-comments should get additional style/clarity proofreading.

@robotcator
Copy link
Contributor Author

@gojomo Thank you for reviewing, there still need more improvements. And I will fix the problem according to your comments.

@robotcator
Copy link
Contributor Author

@gojomo Hi, I open a new PR to fix the comment according to you. I change the train method to make it compulsory and in the __init__ method to optional, but If the word_pairs is provided, the train method will also run. And I will make change in the notebook later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants