From 88dd226b4c8292829323d176b1d6a2a17f0eba01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radim=20=C5=98eh=C5=AF=C5=99ek?= Date: Thu, 2 Mar 2017 13:15:08 +0100 Subject: [PATCH 01/41] code style fixes --- gensim/corpora/wikicorpus.py | 8 +++++--- gensim/utils.py | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/gensim/corpora/wikicorpus.py b/gensim/corpora/wikicorpus.py index 1a53b282e9..131ddf84fd 100755 --- a/gensim/corpora/wikicorpus.py +++ b/gensim/corpora/wikicorpus.py @@ -30,7 +30,7 @@ from gensim.corpora.dictionary import Dictionary from gensim.corpora.textcorpus import TextCorpus -logger = logging.getLogger('gensim.corpora.wikicorpus') +logger = logging.getLogger(__name__) # ignore articles shorter than ARTICLE_MIN_WORDS characters (after full preprocessing) ARTICLE_MIN_WORDS = 50 @@ -172,8 +172,10 @@ def tokenize(content): that 15 characters (not bytes!). """ # TODO maybe ignore tokens with non-latin characters? (no chinese, arabic, russian etc.) - return [token.encode('utf8') for token in utils.tokenize(content, lower=True, errors='ignore') - if 2 <= len(token) <= 15 and not token.startswith('_')] + return [ + token.encode('utf8') for token in utils.tokenize(content, lower=True, errors='ignore') + if 2 <= len(token) <= 15 and not token.startswith('_') + ] def get_namespace(tag): diff --git a/gensim/utils.py b/gensim/utils.py index 8cc7f9574c..8f543e7d28 100644 --- a/gensim/utils.py +++ b/gensim/utils.py @@ -10,7 +10,8 @@ from __future__ import with_statement -import logging, warnings +import logging +import warnings logger = logging.getLogger(__name__) From 6b082e08e5da5b95685ded5a71e28014b2efa71e Mon Sep 17 00:00:00 2001 From: Lev Konstantinovskiy Date: Fri, 3 Mar 2017 21:30:57 -0300 Subject: [PATCH 02/41] Add 1.0.1 changes --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1874e5de85..0d171fcbec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ Unreleased: ======== +1.0.1, 2017-03-03 + +* Rebuild cumulative table on load. Fix #1180. (@tmylk,[#1181](https://github.com/RaRe-Technologies/gensim/pull/893)) +* most_similar_cosmul bug fix (@dkim010, [#1177](https://github.com/RaRe-Technologies/gensim/pull/1177)) +* Fix loading old word2vec models pre-1.0.0 (@jayantj, [#1179](https://github.com/RaRe-Technologies/gensim/pull/1179)) +* Load utf-8 words in fasttext (@jayantj, [#1176](https://github.com/RaRe-Technologies/gensim/pull/1176)) + 1.0.0, 2017-02-24 From 51bb15b71818ae991034d4b08104bc9a9978059e Mon Sep 17 00:00:00 2001 From: Lev Konstantinovskiy Date: Sun, 5 Mar 2017 11:37:19 -0300 Subject: [PATCH 03/41] Hardcode version number. Fix #1138 --- gensim/__init__.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/gensim/__init__.py b/gensim/__init__.py index 0e3f3db720..7ccf9d307f 100644 --- a/gensim/__init__.py +++ b/gensim/__init__.py @@ -6,11 +6,7 @@ from gensim import parsing, matutils, interfaces, corpora, models, similarities, summarization import logging -try: - __version__ = __import__('pkg_resources').get_distribution('gensim').version -except: - __version__ = '?' - +__version__ = '1.0.1' class NullHandler(logging.Handler): """For python versions <= 2.6; same as `logging.NullHandler` in 2.7.""" @@ -19,4 +15,4 @@ def emit(self, record): logger = logging.getLogger('gensim') if len(logger.handlers) == 0: # To ensure reload() doesn't add another one - logger.addHandler(NullHandler()) \ No newline at end of file + logger.addHandler(NullHandler()) From 000c02aa87b811f3eb3f513308e68a1bf4054e2c Mon Sep 17 00:00:00 2001 From: Prakhar Pratyush Date: Wed, 8 Mar 2017 04:41:53 +0530 Subject: [PATCH 04/41] Fix #824 : no corpus in init, but trim_rule in init (#1186) * no corpus in init, but trim_rule in init logged warning that trim_rule is being ignored for separate model initialization and vocabulary building * log warning only when trim_rule is specified --- gensim/models/word2vec.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gensim/models/word2vec.py b/gensim/models/word2vec.py index 3a1159067d..b8bb24f185 100644 --- a/gensim/models/word2vec.py +++ b/gensim/models/word2vec.py @@ -474,6 +474,12 @@ def __init__( raise TypeError("You can't pass a generator as the sentences argument. Try an iterator.") self.build_vocab(sentences, trim_rule=trim_rule) self.train(sentences) + + else : + if trim_rule is not None : + logger.warning("The rule, if given, is only used prune vocabulary during build_vocab() and is not stored as part of the model. ") + logger.warning("Model initialized without sentences. trim_rule provided, if any, will be ignored." ) + def initialize_word_vectors(self): self.wv = KeyedVectors() From c2f3716f7ff0eb9494d7bad5bb57e750acbd067b Mon Sep 17 00:00:00 2001 From: Prakhar Pratyush Date: Wed, 8 Mar 2017 04:47:03 +0530 Subject: [PATCH 05/41] Update the warning text when building vocab on a trained w2v model (#1190) Updated the error message for the case when build_vocab() is triggered more than once without update parameter. model = gensim.models.Word2Vec(sentences,min_count=3,trim_rule=my_rule) model.build_vocab(sentences) #throws error model.build_vocab(sentences, update = True) #works as expected --- gensim/models/word2vec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gensim/models/word2vec.py b/gensim/models/word2vec.py index b8bb24f185..8c371ea8bd 100644 --- a/gensim/models/word2vec.py +++ b/gensim/models/word2vec.py @@ -727,7 +727,7 @@ def finalize_vocab(self, update=False): def sort_vocab(self): """Sort the vocabulary so the most frequent words have the lowest indexes.""" if len(self.wv.syn0): - raise RuntimeError("must sort before initializing vectors/weights") + raise RuntimeError("cannot sort vocabulary after model weights already initialized.") self.wv.index2word.sort(key=lambda word: self.wv.vocab[word].count, reverse=True) for i, word in enumerate(self.wv.index2word): self.wv.vocab[word].index = i From f66181e81ad4caa3da78e7d13142ae0719615cd2 Mon Sep 17 00:00:00 2001 From: Lev Konstantinovskiy Date: Tue, 7 Mar 2017 20:26:08 -0300 Subject: [PATCH 06/41] Add KeyedVectors, FastText and CoherenceModel to API reference. (#1193) * Fix doc2vec sphinx error * Add fasttext and coherencemodel to apiref * Fix code block in coherencemodel * Add keyedvectors --- docs/src/apiref.rst | 3 +++ docs/src/models/keyedvectors.rst | 9 +++++++ docs/src/models/wrappers/fasttext.rst | 9 +++++++ docs/src/topic_coherence/topic_coherence.rst | 7 ----- gensim/models/coherencemodel.py | 28 +++++++++++--------- gensim/models/doc2vec.py | 11 ++++---- 6 files changed, 41 insertions(+), 26 deletions(-) create mode 100644 docs/src/models/keyedvectors.rst create mode 100644 docs/src/models/wrappers/fasttext.rst delete mode 100644 docs/src/topic_coherence/topic_coherence.rst diff --git a/docs/src/apiref.rst b/docs/src/apiref.rst index fda942d294..a23efb9746 100644 --- a/docs/src/apiref.rst +++ b/docs/src/apiref.rst @@ -39,13 +39,16 @@ Modules: models/lda_worker models/atmodel models/word2vec + models/keyedvectors models/doc2vec models/phrases + models/coherencemodel models/wrappers/ldamallet models/wrappers/dtmmodel models/wrappers/ldavowpalwabbit.rst models/wrappers/wordrank models/wrappers/varembed + models/wrappers/fasttext similarities/docsim similarities/index topic_coherence/aggregation diff --git a/docs/src/models/keyedvectors.rst b/docs/src/models/keyedvectors.rst new file mode 100644 index 0000000000..db07e034e8 --- /dev/null +++ b/docs/src/models/keyedvectors.rst @@ -0,0 +1,9 @@ +:mod:`models.keyedvectors` -- Store and query word vectors +========================================================== + +.. automodule:: gensim.models.keyedvectors + :synopsis: Store and query word vectors + :members: + :inherited-members: + :undoc-members: + :show-inheritance: diff --git a/docs/src/models/wrappers/fasttext.rst b/docs/src/models/wrappers/fasttext.rst new file mode 100644 index 0000000000..84877c2ca3 --- /dev/null +++ b/docs/src/models/wrappers/fasttext.rst @@ -0,0 +1,9 @@ +:mod:`models.wrappers.fasttext` -- FastText Word Embeddings +=========================================================== + +.. automodule:: gensim.models.wrappers.fasttext + :synopsis: FastText Embeddings + :members: + :inherited-members: + :undoc-members: + :show-inheritance: diff --git a/docs/src/topic_coherence/topic_coherence.rst b/docs/src/topic_coherence/topic_coherence.rst deleted file mode 100644 index 430aa47699..0000000000 --- a/docs/src/topic_coherence/topic_coherence.rst +++ /dev/null @@ -1,7 +0,0 @@ -:mod:`topic_coherence` -- Package for the topic coherence pipeline -================================================================== - -.. automodule:: gensim.topic_coherence - :synopsis: Package for the topic coherence pipeline - :members: - :inherited-members: \ No newline at end of file diff --git a/gensim/models/coherencemodel.py b/gensim/models/coherencemodel.py index 20fa9e14bb..161d0257a4 100644 --- a/gensim/models/coherencemodel.py +++ b/gensim/models/coherencemodel.py @@ -6,7 +6,7 @@ """ Module for calculating topic coherence in python. This is the implementation of -the four stage topic coherence pipeline from the paper [1]. +the four stage topic coherence pipeline from the paper [1]_. The four stage pipeline is basically: Segmentation -> Probability Estimation -> Confirmation Measure -> Aggregation. @@ -14,7 +14,7 @@ Implementation of this pipeline allows for the user to in essence "make" a coherence measure of his/her choice by choosing a method in each of the pipelines. -[1] Michael Roeder, Andreas Both and Alexander Hinneburg. Exploring the space of topic +.. [1] Michael Roeder, Andreas Both and Alexander Hinneburg. Exploring the space of topic coherence measures. http://svn.aksw.org/papers/2015/WSDM_Topic_Evaluation/public.pdf. """ @@ -75,15 +75,17 @@ class CoherenceModel(interfaces.TransformationABC): 2. the ``get_coherence()`` method, which returns the topic coherence. One way of using this feature is through providing a trained topic model. A dictionary has to be explicitly - provided if the model does not contain a dictionary already. - >>> cm = CoherenceModel(model=tm, corpus=corpus, coherence='u_mass') # tm is the trained topic model - >>> cm.get_coherence() + provided if the model does not contain a dictionary already:: - Another way of using this feature is through providing tokenized topics such as: - >>> topics = [['human', 'computer', 'system', 'interface'], + cm = CoherenceModel(model=tm, corpus=corpus, coherence='u_mass') # tm is the trained topic model + cm.get_coherence() + + Another way of using this feature is through providing tokenized topics such as:: + + topics = [['human', 'computer', 'system', 'interface'], ['graph', 'minors', 'trees', 'eps']] - >>> cm = CoherenceModel(topics=topics, corpus=corpus, dictionary=dictionary, coherence='u_mass') # note that a dictionary has to be provided. - >>> cm.get_coherence() + cm = CoherenceModel(topics=topics, corpus=corpus, dictionary=dictionary, coherence='u_mass') # note that a dictionary has to be provided. + cm.get_coherence() Model persistency is achieved via its load/save methods. """ @@ -94,11 +96,11 @@ def __init__(self, model=None, topics=None, texts=None, corpus=None, dictionary= model : Pre-trained topic model. Should be provided if topics is not provided. Currently supports LdaModel, LdaMallet wrapper and LdaVowpalWabbit wrapper. Use 'topics' parameter to plug in an as yet unsupported model. - topics : List of tokenized topics. If this is preferred over model, dictionary should be provided. - eg. topics = [['human', 'machine', 'computer', 'interface'], + topics : List of tokenized topics. If this is preferred over model, dictionary should be provided. eg:: + topics = [['human', 'machine', 'computer', 'interface'], ['graph', 'trees', 'binary', 'widths']] - texts : Tokenized texts. Needed for coherence models that use sliding window based probability estimator. - eg. texts = [['system', 'human', 'system', 'eps'], + texts : Tokenized texts. Needed for coherence models that use sliding window based probability estimator, eg:: + texts = [['system', 'human', 'system', 'eps'], ['user', 'response', 'time'], ['trees'], ['graph', 'trees'], diff --git a/gensim/models/doc2vec.py b/gensim/models/doc2vec.py index af32c7d83d..fdf00e430c 100644 --- a/gensim/models/doc2vec.py +++ b/gensim/models/doc2vec.py @@ -606,12 +606,11 @@ def __init__(self, documents=None, dm_mean=None, doc-vector training; default is 0 (faster training of doc-vectors only). `trim_rule` = vocabulary trimming rule, specifies whether certain words should remain - in the vocabulary, be trimmed away, or handled using the default (discard if word count < min_count). - Can be None (min_count will be used), or a callable that accepts parameters (word, count, min_count) and - returns either util.RULE_DISCARD, util.RULE_KEEP or util.RULE_DEFAULT. - Note: The rule, if given, is only used prune vocabulary during build_vocab() and is not stored as part - of the model. - + in the vocabulary, be trimmed away, or handled using the default (discard if word count < min_count). + Can be None (min_count will be used), or a callable that accepts parameters (word, count, min_count) and + returns either util.RULE_DISCARD, util.RULE_KEEP or util.RULE_DEFAULT. + Note: The rule, if given, is only used prune vocabulary during build_vocab() and is not stored as part + of the model. """ super(Doc2Vec, self).__init__( From 65e5bc3ec20a80c38b806cbb872daa960e7be929 Mon Sep 17 00:00:00 2001 From: Jayant Jain Date: Wed, 8 Mar 2017 07:38:47 +0800 Subject: [PATCH 07/41] [MRG] Load FastText models with specified encoding (#1189) * fixes fasttext wrapper file header * allows user specified encoding for loading fasttext models, corresponding tests --- gensim/models/wrappers/fasttext.py | 17 +-- gensim/test/test_data/cp852_fasttext.bin | Bin 0 -> 13159 bytes gensim/test/test_data/cp852_fasttext.vec | 172 +++++++++++++++++++++++ gensim/test/test_fasttext_wrapper.py | 12 +- 4 files changed, 192 insertions(+), 9 deletions(-) create mode 100644 gensim/test/test_data/cp852_fasttext.bin create mode 100644 gensim/test/test_data/cp852_fasttext.vec diff --git a/gensim/models/wrappers/fasttext.py b/gensim/models/wrappers/fasttext.py index 49a6b6a925..6316976f29 100644 --- a/gensim/models/wrappers/fasttext.py +++ b/gensim/models/wrappers/fasttext.py @@ -1,7 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# Copyright (C) 2013 Radim Rehurek +# Author: Jayant Jain +# Copyright (C) 2017 Radim Rehurek # Licensed under the GNU LGPL v2.1 - http://www.gnu.org/licenses/lgpl.html @@ -221,7 +222,7 @@ def load_word2vec_format(cls, *args, **kwargs): return FastTextKeyedVectors.load_word2vec_format(*args, **kwargs) @classmethod - def load_fasttext_format(cls, model_file): + def load_fasttext_format(cls, model_file, encoding='utf8'): """ Load the input-hidden weight matrix from the fast text output files. @@ -234,8 +235,8 @@ def load_fasttext_format(cls, model_file): """ model = cls() - model.wv = cls.load_word2vec_format('%s.vec' % model_file) - model.load_binary_data('%s.bin' % model_file) + model.wv = cls.load_word2vec_format('%s.vec' % model_file, encoding=encoding) + model.load_binary_data('%s.bin' % model_file, encoding=encoding) return model @classmethod @@ -248,11 +249,11 @@ def delete_training_files(cls, model_file): logger.debug('Training files %s not found when attempting to delete', model_file) pass - def load_binary_data(self, model_binary_file): + def load_binary_data(self, model_binary_file, encoding='utf8'): """Loads data from the output binary file created by FastText training""" with utils.smart_open(model_binary_file, 'rb') as f: self.load_model_params(f) - self.load_dict(f) + self.load_dict(f, encoding=encoding) self.load_vectors(f) def load_model_params(self, file_handle): @@ -270,7 +271,7 @@ def load_model_params(self, file_handle): self.wv.max_n = maxn self.sample = t - def load_dict(self, file_handle): + def load_dict(self, file_handle, encoding='utf8'): (vocab_size, nwords, _) = self.struct_unpack(file_handle, '@3i') # Vocab stored by [Dictionary::save](https://github.com/facebookresearch/fastText/blob/master/src/dictionary.cc) assert len(self.wv.vocab) == nwords, 'mismatch between vocab sizes' @@ -283,7 +284,7 @@ def load_dict(self, file_handle): while char_byte != b'\x00': word_bytes += char_byte char_byte = file_handle.read(1) - word = word_bytes.decode('utf8') + word = word_bytes.decode(encoding) count, _ = self.struct_unpack(file_handle, '@ib') _ = self.struct_unpack(file_handle, '@i') assert self.wv.vocab[word].index == i, 'mismatch between gensim word index and fastText word index' diff --git a/gensim/test/test_data/cp852_fasttext.bin b/gensim/test/test_data/cp852_fasttext.bin new file mode 100644 index 0000000000000000000000000000000000000000..5c71cac90d8d97425ba44e694ea5a212217f6659 GIT binary patch literal 13159 zcmZ8{30O^E^mY>wG8BgaN0={*{J*C~^nc1? ze0jn=A)$q<+>DDgRRv60&!}&;kIoSvZp7CzODlEN+%m48C4~tgL!(}HM zO99DU5V^aB{ctDE0!rQ7T`im} zh8x@291w8P%~JrYovo9A-AO>Z zqd-~Es+|B^jso;MItln_XY*h4!``}AJN`!yKuLO@K5v0k=H`%I*Rz|AR2B*+(GwJ}%V) zL)6a3&erO{a7$}}UM{Wzf}MaN!Pr{33#6TeyUVaDJ_JWwK_B=7?y8NWAdCOWbb%=G zQ4QeL$}!$u0G7LjAOrRm0t(%o4>U$}-BEyQcW=R7{1hLy z{V(4ID;*OAXmzr+8g9wQEZFeh5EmS}mfj8m4*o;>|8*4{$`*o&@NY(K9&Ri!Z3<+S zfHncg1O|cUum*uC%-YFDK%bRBjeCNlpgV$a{{a%9i0_$zNjo<|u#N!&A$IfZ5a{Oq zA3FYHjL(i>M!Ekd0!}vjhh5<>Si^$5zoURoM;jjjST2Ktj=BrXK`w%W(=%QGp@rZa zW^e5+kkg*cg1xQ^7~=acpn|_f39@+AA>PSeASnM=7QkX5xDeX8ISQmb-vI$vT?E+^ zaN0tEZYPUo0bEWNF1F2r!M5*YK%_8?kCpguGTF7qtN(1|4M+OeD*oNx-( z6C)zxsY>;Y9mrMhELPOD9bBt7V!%pixb(#z{aO~0E36Ohaj+(POT{3fz9YZ)2$y|d z(ZI~U$)ZHFFG<-gO#dp+hIbW{Xi<$7r`u71mfZ@5s=#MBx66`*$no&f1qryRU;|5n zoe7jJhD7tNyzXNS*iTjc8M<$ZV$^>Tr z^oMD4e_@G{A(Remz+KOZA>P^-H^*J%U3zs3>_rI5q-3M z;kK&+&1>oc7rPs{RUsSl42M0%uqMKYC$X>cSf47Jl zS+;H}Efe|yvJd5G!3;AH36vmV$4627X(Mr{t`eTuTxU!rc9LK%SC(n~368xgRD^RM zZWT_YYQ`VokV*@#sVX3$l3T#qbR^QV#;AI1B&1#S$GnydXj2VfzEANa4F%@xs6bPm zmBl_tE;*Smr16e6Vx9889 zrJ~VjZ>B^{Yd&zaUyH+;5Lr6+oiq{8I>%}%+mU6`67<|0PtrMi3XN@y<#a4wK!q2? z5_$IkxGiT-Y84vrjb9}w37f*$Gx40azoQxM0#WjH(JT=5K29EvjbeU%IYf+UFe*3v z0oy`r=;kfM%Cd=6q*aR4=UnF$b+1Lu)vqv5sfoEII{=}h6^KFC8fNYJ1ae)e2D3{C zU}L2(mjAlTODSz-CM+M1CNXIc6aECutz>Yu!9!@@-i#SiPoVpiIcPL}gBG+(5@G3%bibUv* zX*<}*(-FA~Vm2e_qajU_q-72{xk5zdL^1X)JB^72xnLQMP~VfzX;iI&xxGE?eEI1l z#9xKkJTjb#ekF@1mUe?q<|bxW)-n*c5T@R)g&^go%9_o!#PYKTb>*ok;U)Ot@rn$R z=Dw7ki#Y|$c(Q0S`T#_ys$+O`F8iTn5QRO%h*8u&+;@aWrfjIdzBX}qlb42WoyH{9 z;5M$_(aET7naJF=4#f476EN}QEtq2d5jH*+gY>OmINDPUVdq*Ec5uolBHNhB9Q$ZR z4EnsV{b4!zBeR`xOq~hY0p?Uj&zyU#xgFmoOLO=2-^HnJzJ&SEfeDMh;ON9ko?#?| z0Y1RUBq~DOlwPAQ-tGfZLg6(&UVXNOBOtm=*+l9;U{rrohS??AE51Nnz zupd&7cj1S%-!RI3BDW|mg0-%m#4cYgk6VhuIgzur(jETuxS56}m@j&jw1gC zEgR+O>F~28dv+eIa6SXG0>;shNiJ~DPl7ssvm^mW8t8`Af!xv#W$KiAmE0%C@V)sC zTqU7QC-0SjLJ?c2-_*}O-LFTBbP7n>YFVaHV-dV9AH%Y{WihZ&6rI1sk$bOxVf>X- zHBseEGzdCR1T$b^NsRy>QSeY(f{Z2B4XJ|^P*%qZMbUJpVULGaWg1iJq2 z0qZF@nMG=1Wb?acn3^PsAy!kV=*jhD+oa{X4MuAr=AAGVi|B?u;wczaX8>c;&eJ`U zii!7z5wy3ko47gLMZEQh(P~VB^vJ3B*)}Hh;=g|I+awr^812#(lL!yeX_+SY!*UYB5m&CY_ZjYyz+#`rt%{5$j`6#)rmBy@$ za3}7|gy`P!+GO^d5c~sc$w13t+J2&#^eLPI*J}YFyp)4U_UB3Ci0hy`Du|W3^9|pw zJjaCJSD-sHkCI|hMXGCfove_ZL>uzIKw;t_{(bPCnCgDO39I{g9Y&G(xv~iDjqb1_ zU;N4L*mKO7*HVPtkWSY;-^Fbn+ymJfX>4DuCd2S5ImgVqIP&~Jefhp;J7mp8I#Anu5rM6l~pJ-GoPHBGT-^tMXJ|lHln^xx&LQQcJ815@9_P-^|J^W#Ac@nG~6-(JgQe16Q87h@5%uQtL z*$0PDfpSbDc$xS^@?8G>*6l!sYRu`DwMig7*Mll-7USv%J5ZB1#bh4mEaq7T6OHm` z7}iq5xP+a?{S{K^_1hla_R=m}r)<`%bP$<2U7i ze&4Y`eoKLB%UkZaI0+h4rwWU-BVcb`Cfwt0!SYyZChE;r%vzHHt`h3t5c2`Op$+A4 z@`%4uB7Cv6`8IG$*IHAMx z0BEkO#GsxTB&BC3E(kkDChyb-Jnu*LUv{N?o$eE>!d7No*fx08vI)FA49IB*C)l@Z z5af4G<;ZlV!(h`_#_gOcF=#x;oNj$UZe3dsp$q&;-O(pFchezQU~&#}{i@;6fFno! z!dXblSdN2RWQfhpOw?IzNyb?3VG9CFvDE52tT_1vSDf>sJ|E7J0S!;ipA{kS(5n|` z>Q7=n#C%4h3HpQ+E=%u)G=RgZN_cv`20VqX;F02V5Nq2(Yub~^;Fk(a86!geq`HCl zF?G_pYaFI;I6yp79^%sRw#2^pEqYNq;&bjV=IoN@-apZX*G%m2>+eCF+Y^Di0^F&B zUlO^pa~!If1(B*R(Qx4MIee#F1>3qbQFJm7e(Ln&@7YH5XuwGBm8xCzY-%vcS-*k~ zT;mYqEe7a%`41$P%HWzDaq>a8Er0X?f_#w}%M}YCayLHVCbzNJ`PL%eEqNcbAJ?G@ zAKww)4>Kcy%Bv>@zkHC;5Y9J21%x3dCxPFqu2or11uEA&ZnIr5kzy}JFpY^3CSb#F(>#3 zyjlMV*M2u6FC?4bMwA#i?i&C)A-|Y%gKeBJrv~QC25&lQ%K%Ya5J^ohd2?qrIpb!F zHnt~ifXyBM14`OQ@nY1YN${2k zq%L&=W4(MKr`-A>8)JKkSPpGRe;ZFQ_`9D~vrgmGZ5v77EfwZ|KZ2}N;CF_+NLq-wOmDqaGVR_-JjR_^Fo`X|o7f_Ygf} z-Vqh?5Ad@f4zFgO2kp7vFzI$Co}Y4unQCi9bEcdnF17JkeQyVesL4R?<2<4#kq_j6=eqlUjA90uDtuBlp1d}B&AVrQ3tOzl;Vsd6;-x5z zqVF`xLDwJf=;IK+>2$zHT942=_d0vp*qIcC-{-$4$`fU;J}~0P=c>OG&!c`O8g~W3 zH1TElCRjE9t@~Uy>Y51^u{cd0-I`Ai95_d8HC^cU!V;2v?jGi?mE(^6^%3uuog|9f z>uBuT!?_my2UDU<@N~mp`?fY%e}B#WG!@j z2fy%LD<<#SFt#lW;tq_9dK`kI& zX+?MM5#t^hJCfYK)4(Wum*FZ~H}nv?gD3BwVM3Gcv2MZvWXy>xK#nN0ffWO~ZI>Ql zRS5F^dPs(d63Ax9!jiJ}%)Ep|D2b}VRs3_7#G@Bbe}XV+)03w@X1(BNw;C4f)xpR= zWvuv(Aq>9hk45EY$l1+0_~ly+tep1-->F29@EOXyQ#DJ-oTNKYko*=5CA881`FX;* zGLcSOn@Du~=TqIYmq~Hfb?jD(<$Sp4&Aj;G#f<&k0GWTRA;eD=>uQCF+>VK{!gfgo?KZNv{*>tbb2$Cc69kpl0;-ZHSn7wOmV_c9P zmG50k^lnOlh0GZuUD1lUCQET|!Y0(<^Z)9_M;tF7M@06fz{cGnByO4qzElYzQTwAX z^R*_q`{5>&I%yIKU(RDrn;Q|8K1Vq5j>kkERKjI_EH3j)ft41Okp1WhIQ=xo`-yVA zsk1X6|8puB)+E5u&6lam>~`XEuoi+UQgNc%eER5%7D<(hgwMjk7_MCkBUUuBC*O)f zoz5&+UHFlg?_Le#XBIP!PfsxK%bh?bx)=W%JZ5!j{n>|Qhp?plJ)2ckkDqMind1)M z@rI&0J4Ig^Ml~itzurlB9IDM0u3Jg#A3h?bA`BH8Wk<$GxPbZ48C)PA1Ki(QFz#0# zy;PXN?M_W(dG}so(#ZYj-*la<3jz}1-< zJbU3w%*w+j@bH`cO!<`<#(!xGDu2GlCfsyyv}Kcwg&w#v$__TY ze}UV3f=QA7S;*R#0~4Rc<9ex9XiQiF%Ss|~-^G)hTEieBv=s5X$!n69`4Rn4DoCEiH%q1;>xdcY4+h5vh41{0?z&t?iA7Kw9KfF>~4?b%os`` z6HO=Lj)!67nuiQ7ClAPkTO;X`nd+pX?-|&b=Yi(!SGce1GitdR!3Vcg*73n1s8AOp zA+t4D-%1Zy+%*I0Z%>2c>gsfDupC*}wE>paEXEa=95;0V)F8E5AMQ# zmN*Qcn_d-E{9Q#~)cBLu$IEHr_0#b5c?jOFdO=*PF5=rIUzkdj06N97gLp|AK~DBx z-qVF6si@val4HCd7C%#Bk1UX-54(oob5b7Lefl8blm*bw#*?`74Rs-=Y$Dkl`V@P{ z@z+S>huH2AMl9qDpp^fv{5D*fe)A|HBO)^q41HmV_$0c^brko&nsQccAe?b>{fsJd z6G+DW4UE^c7?N%?i_Ph}%}EVaM?>#4GJ+QhH*^BgaMWnpbnG6P<=l+&%ia>3)h0N1 z;0q*G%%!H%lF))txVdqnZbqmTZ|2dLFwt-MpGx4KuGO`7x1sIF82H@g zW2%q0+kb)4Jr26c5Ax3xFM!+y3Z|uFQFY5Lw6idv8-$g)Qqm{b3Gwpe(84Udthy6y zG%hkfj)cRY*h3tNQe1Xr1wFLPge#h!3%%MEK(F1RM@PITqoPxxeT591y)hS?9P9An z(+XUinopu{-QnzS>IX(b9`&mIh|$ScXgp8>8U8kqez6UAwVh$UnD%2)L^s|vJp+~< zM<8;d79Lr9nuQ5p!7Q*5cLcr#shT-pkUox_c+E1#8liA!%|^QJZVBm-JB8tou92!2 z38?kn08>o=@GkJ`iFnC2CcM~;Jp6P5_025du$MF*vr!_;dahxq-ZkPdr3*72y#<5K z+gPnxVena`59C!7V7#ObzTcorgeS#uBzyN0m;MC&c5)m3Mn9Y?iiJIS|^ z6FC#MuO{~j%%MLv5$MhF1&r}MZmVWH?^o7dwvjmw%EDivKs%L{7eGj zDd#!(GuiOzaRWHs?1Pn76X^K0c|d6Go%KjyIeDMvapT3Q`z&=Eub}w@JJ`b!SiW#yk4paI^lG>`dpqH18 zW(Nb|@BTBKC65#6!`n(+tNeGE{QMSNb*C71P8JUBSb>92gUMCxP~3VkmFRoK(@ir? zxYNG=!aE9MFg5-uOiOBo4a$pfx;BMRw}QZ5trHH8X=YD7xDU&&Wa0EJ(a`wz1e2(( z%y|5s$QXClqlN1LJ~b#M;|=aZ=Kd-;w@4F?#!MxHfiKyuX1!P@ss~1csA)!vwL^8!riOaZ5> z&*86bFkSnonB4b`XJkD~@KLT9#h`dnbXJ_+G>j(Wcg5hZ%Rg8ZnF`)gs}tna&l}Lh z@0(G$l@5dik+^6c<_7zc>T(V2h}9tVbAx#iYEz&sXBw(CjKjSd-=OW1BE2yqjd49( zi;DwS(3~g-^73sg%q#H0#bHWx)6^iMpq!1XLU)rPzn2(w$^kSA53`r!3`zAt4=Q_O z0e8vvI40#}C#p7@QPU%}L|MoOFFdw_Ob0?;UR)<~=9OsIcpjQN)M?qKS!A{E8H`M9 zf+G{kV68|qBvd^DGbc@$#H)l!6F-1kju3mkV?5aM_l@IradFOwRvNg0RG7CC4gn&?~Ce(gugy3`OP!rdRz1L)+T=5%X+*8su zbOCkKZxGKxU682mM4ZCj!%x_$X*m$mQ31Kt7h!vDIJ@#&5FDMeh%69!&iuCO z)Ct)=o`(EXBg#3_1qS5_T-}9wRBVkjx9NB|{CE(@R<}rU-hOX^Jv-HD#-+QY?_@ak zh?l_XonyeeA_-nSsbxHFH{h-PPk2AnGRQ>!y0&xq5YwYE1}-e^hoP1qV6*NsEBm7d zor4JJcXox|@GEeWT?mse#e$L8QaVqgjr>+q#>aDliR9){bX=Smyf#P&$p~x4U2+4g zNpeBILUB~k83lm@Mp$&@Iv!XxnFeHRB)#FGbffZQuEp|NY;N_(_eM?dwjc#Ep^9$)6+3Fx>DpxuN(^WOKDr)j{jL)IPv5Yo zasi{`d>;MumLq4wbGRKMh68Ub(c3B>%niCwCAEa9U#ChBweyJH!YXu0o`+M4O@;%2#+v7M4k4rVFP71Gzqqle1izvL18+@!-R@q2=^oYUcP z-4poy!4TJ|6rho|6uUo!ODxDYdgDkE*Vz6Fd+1jSdjD1chVCLdCw!T|Z^n}%mpTj# z3&HkeTiTj@pG5QL_Z>=Nuq<^tJQ;clawqlZ$QQRs`T5DZ4%PhgsMpf?Aq3wJ?jg01I_lP2O`6@xYguCHpaUuWv&nnh&pB_1ytcPzTiXmw* z5I-)ZOp;3!eJCl;Rjn6->vbDRQG^su>p8_cT{9b$w?DzsO^PtjNt$%7oyhsB^%1+) z#NpV_{v>zbZDjpCNmpJ7x|--mpL9I&!OS_RVM4*}&qka%y__6ceF_t9>>&+)yU-&&3wIAbgy0kI_|e6b zzPo>v+?&)6x5Wn0rZW&v|K3N0dlNXq!ZsxO<5D!1mLLySsX~ig5HaQW(Ilf*;u*b# z#xzdl+WJ<&+KTNU``v=)YOjT#hT3#BKV~v|^YT!)xEgNGe+e7oZo#G6F;sSI5NTOg zjL|=XiPF;^^cg~S)d-OSRqoOp1dmoaH7%TQ&5k$l5T1cTRwd&UVn>@eZ^-VW zMVM0F2&Z$Nu=gEqL*C6%)IDVicb8-)?DT42WRitp!gXI}>H2Xr+jTrVtwJhM9n96t zjKQMlZ!r7dH0ouPPQJ8qX>esPse6%0FKcOVrD5m8<0GRVyjHSXOoXXFY z$);;)g!5zAAmv79E~$juo+qfXXa=eRtD#UokQ|@g{Xh$%Q|2ooV#>=Ri~vsCtGblv&QEew9LG#DXxY?WYc1 z{4<0+HE}XyayC_XdIMt9gXrp__rMz+N~QFYIltAOpk;jvbD5>)0{xE z^782QQNpA({3dnwv4)(nOK7UEFPzFePB+bVhhyU`=?arU&>WjgeS<2YQ$Cz_EE

LELRwoWZP)${e)1P--$_wex-MnzQ zj3FI$cqa4Ju@t8XRfCw!CEAl<4}Q^hRK=weZc1LKb3|v5wZ>1W#Fe|OQu8BDf21s- z(Is^Im}fv8&d|_l@tm#txhO0%l96%i!M-_RoTamBaN*}NnDoe&{&-XmozC$z?qUQ~ z9GXp)K<%4#)JZOb7t*tn-FNURtiN@FKG9MK z`}%2AcycC~DD9x$hfJZ94B(zI>tOmkVH)x6B4~VFK~sZmAZFqmnyGgbR+blGMob4B zyA(_Hi>1i;pVw&+69!TGTJ)6UMle_^MbDcR!z=%FRL?sQdT+Q;_t8cCdw2}Z;-5*J z__>+txsM`!-&1IQdJ-JCH-e_9?gP({Z8&Q}AP7BufZO+df$v@sbc{(0G#s*{tDRyX zY>^E0pZo=SH(#QvACkd5bp<`C9?2|j-+)~em1^VrmCTv+bhBjuufLq!8J0syC zU`Dr7C53TBdd?&IW9mJycG^aJUE{$sdjW0#+yzGi)2WAp0_c4!K{2b*#P(M#tyT<$ zpK|t8SX7MI*2dBItpQ+^F_TVsTm#9M<7xdwJ6JV(3|(C)N@N4?Qni3_BxgCL#^*fX zcBK{_pSA;P`FAOD%x*(uX868= zv&Vd?W=RRuna-t|pJ$L;yaJlHuL8<0Y^1HG@1afHf?D>zgoNzPwC&n?kbZj>xA#_o zzV1?*H0>vdJ`SX1={2CabsM$*^%*3TE$Ov)eZXD|p}JpsAu!mVDoC+l%hjQqe-^;E zEH0Jeo`5SHBWhdq2IePt)4M8S#HBo%-di^gq<{Zl6AFi5)8bH?8F~(|MTT~_`okuj z2iQ{b3N+*_>6qQM;Ihz$j#QT>lh@v*BR|=}6T>>puFQdjiET>l|ZQTC-oR*bA~Jw+38UPH;f!&LWc31lf4(&I)6@cr8w NdSnkD-$^(6e*n!V)8POB literal 0 HcmV?d00001 diff --git a/gensim/test/test_data/cp852_fasttext.vec b/gensim/test/test_data/cp852_fasttext.vec new file mode 100644 index 0000000000..9d1969b0f6 --- /dev/null +++ b/gensim/test/test_data/cp852_fasttext.vec @@ -0,0 +1,172 @@ +171 2 +ji -0.79132 1.9605 +kter� -0.90811 1.6411 +jen -0.91547 2.0157 +podle -0.64689 1.6221 +zde -0.79732 2.4019 +u� -0.69159 1.7167 +b�t -0.455 1.3266 +v�ce -0.75901 1.688 +bude -0.71114 2.0771 +ji� -0.73027 1.267 +ne� -0.97888 1.8332 +v�s -0.72803 1.6653 +by -0.75761 1.9683 +kter� -0.68791 1.6069 +co -1.0059 1.6869 +nebo -0.94393 1.9611 +ten -0.71975 2.124 +tak -0.80566 2.0783 +m� -0.83065 1.3732 +p�i -0.62158 1.8313 +od -0.44113 1.7755 +po -0.7059 2.2615 +tipy -0.60682 1.7247 +je�t� -0.68854 1.7517 +a� -0.63201 1.4618 +bez -0.52021 1.4513 +tak� -0.67762 1.8138 +pouze -0.62611 1.82 +prvn� -0.42235 1.6216 +va�e -0.7407 1.5659 +kter� -0.70914 1.7359 +n�s -0.38286 1.6016 +nov� -0.83421 1.7609 +jsou -0.82699 1.9694 +pokud -0.35516 1.5075 +m��e -0.78928 1.6357 +strana -0.57276 1.4149 +jeho -0.78568 2.0226 +sv� -0.44488 1.459 +jin� -0.90751 1.9602 +zpr�vy -0.90152 1.9703 +nov� -0.78853 1.8593 +nen� -0.63949 1.5191 +tomu -0.68126 1.8729 +ona -0.74442 1.825 +ono -0.78171 1.9268 +oni -0.64023 2.0525 +ony -0.78142 1.7097 +my -0.61062 1.8857 +vy -0.9356 1.8875 +j� -0.44615 0.92715 +m� -0.73676 1.4089 +mne -0.71006 1.7072 +jemu -0.92237 2.1452 +on -0.71417 1.9224 +t�m -0.65242 1.8779 +t�mu -0.83376 2.054 +n�mu -0.79287 1.8645 +n�mu� -0.51786 1.7297 +jeho� -0.88721 1.7431 +j�� -0.12627 0.68014 +jeliko� -0.61809 1.7576 +je� -0.8843 1.6723 +jako� -0.94336 1.827 +na�e� -0.76919 1.8106 +ze -0.8277 2.0542 +jak -0.97146 1.9164 +dal� -0.5719 1.5148 +ale -0.79733 1.8867 +si -0.61439 1.7134 +se -0.80843 1.8957 +ve -0.7186 1.7891 +to -0.84494 2.3933 +jako -1.1045 2.2656 +za -0.7136 1.9602 +zp�t -0.79965 1.6329 +jejich -0.49038 1.6366 +do -0.69806 1.8364 +pro -0.7878 2.2066 +je -1.1291 3.0005 +na -1.0203 2.4399 +atd -0.70418 1.7405 +atp -0.69278 1.5772 +jakmile -0.87231 1.6896 +p�i�em� -0.64617 1.4417 +j� -0.7135 1.5517 +n�m -0.42164 1.7603 +jej -0.77603 1.9544 +zda -0.76742 2.0163 +pro� -0.47241 1.7053 +m�te -0.75963 1.9814 +tato -0.64318 2.0382 +kam -0.45101 1.498 +tohoto -0.73702 1.8305 +kdo -0.80535 1.8551 +kte�� -0.72498 1.6669 +mi -0.46791 1.7784 +tyto -0.50319 1.7659 +tom -0.59138 1.8657 +tomuto -0.74312 1.7725 +m�t -0.27199 1.1315 +nic -0.56441 1.8591 +proto -0.6649 1.946 +kterou -0.84109 1.7498 +byla -0.58737 1.941 +toho -0.76081 1.8002 +proto�e -0.55749 1.6686 +asi -0.51689 1.7079 +bude� -0.55392 1.6052 +s -0.74207 1.8989 +k -0.61082 2.079 +o -0.76465 1.8956 +i -0.85412 1.6611 +u -0.68535 1.5332 +v -0.73033 1.3855 +z -0.60751 1.9108 +dnes -0.6001 1.7531 +cz -0.59754 1.4239 +t�mto -0.69011 1.6643 +ho -0.55961 1.6968 +budem -0.54027 1.7894 +byli -0.60956 1.793 +jse� -0.63127 1.5972 +m�j -0.48904 1.2814 +sv�m -0.48494 1.8751 +ta -0.78131 2.4286 +tomto -0.60948 1.7083 +tohle -0.74747 1.7907 +tuto -0.74687 1.9464 +neg -0.60997 1.7777 +pod -0.49619 1.914 +t�ma -0.55525 1.6668 +mezi -0.46979 1.3583 +p�es -0.5712 1.9908 +ty -0.78637 2.2804 +pak -0.60084 1.7026 +v�m -0.48545 1.4611 +ani -0.65672 1.7897 +kdy� -0.42318 1.4884 +v�ak -0.60908 1.6867 +�i -0.36843 1.7586 +jsem -0.54047 1.827 +tento -0.64813 1.9799 +�l�nku -0.65578 1.9129 +�l�nky -0.55868 1.8642 +aby -0.80989 1.8384 +jsme -0.60673 1.843 +p�ed -0.53861 2.0502 +pta -0.49464 1.714 +a -0.63056 2.2477 +aj -0.62546 1.6357 +na�i -0.5915 1.6066 +napi�te -0.50964 1.777 +re -0.95733 1.9544 +co� -0.54673 1.6466 +t�m -0.70952 1.8565 +tak�e -0.55439 1.8013 +sv�ch -0.36878 1.4883 +jej� -0.7694 1.6612 +sv�mi -0.63149 2.1581 +jste -0.68444 2.0978 +byl -0.57205 1.7836 +tu -0.88384 2.2256 +tedy -0.62474 2.0469 +teto -0.63187 1.884 +bylo -0.56362 2.0282 +kde -0.7308 2.0316 +ke -0.60918 1.9317 +prav� -0.52626 1.9058 +nad -0.54689 1.8666 +nejsou -0.66814 1.8323 diff --git a/gensim/test/test_fasttext_wrapper.py b/gensim/test/test_fasttext_wrapper.py index 75c37c512d..ea7593263c 100644 --- a/gensim/test/test_fasttext_wrapper.py +++ b/gensim/test/test_fasttext_wrapper.py @@ -121,12 +121,22 @@ def testLoadFastTextFormat(self): self.model_sanity(model) def testLoadModelWithNonAsciiVocab(self): + """Test loading model with non-ascii words in vocab""" model = fasttext.FastText.load_fasttext_format(datapath('non_ascii_fasttext')) self.assertTrue(u'který' in model) try: vector = model[u'který'] except UnicodeDecodeError: - self.fail('Unable to access vector for non-ascii word') + self.fail('Unable to access vector for utf8 encoded non-ascii word') + + def testLoadModelNonUtf8Encoding(self): + """Test loading model with words in user-specified encoding""" + model = fasttext.FastText.load_fasttext_format(datapath('cp852_fasttext'), encoding='cp852') + self.assertTrue(u'který' in model) + try: + vector = model[u'který'] + except KeyError: + self.fail('Unable to access vector for cp-852 word') def testNSimilarity(self): """Test n_similarity for in-vocab and out-of-vocab words""" From ed757df04eeeddc98d39adbbfc00d8a7c2d37da9 Mon Sep 17 00:00:00 2001 From: Irina Saparina Date: Wed, 8 Mar 2017 23:20:39 +0300 Subject: [PATCH 08/41] Distributed LDA: checking the length of docs instead of the boolean value, plus int index conversion (#1191) --- gensim/models/ldamodel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gensim/models/ldamodel.py b/gensim/models/ldamodel.py index 30ca93f1b2..a695fec7ff 100755 --- a/gensim/models/ldamodel.py +++ b/gensim/models/ldamodel.py @@ -425,7 +425,7 @@ def inference(self, chunk, collect_sstats=False): # Lee&Seung trick which speeds things up by an order of magnitude, compared # to Blei's original LDA-C code, cool!). for d, doc in enumerate(chunk): - if doc and not isinstance(doc[0][0], six.integer_types): + if len(doc) > 0 and not isinstance(doc[0][0], six.integer_types): # make sure the term IDs are ints, otherwise np will get upset ids = [int(id) for id, _ in doc] else: @@ -730,7 +730,7 @@ def bound(self, corpus, gamma=None, subsample_ratio=1.0): Elogthetad = dirichlet_expectation(gammad) # E[log p(doc | theta, beta)] - score += np.sum(cnt * logsumexp(Elogthetad + Elogbeta[:, id]) for id, cnt in doc) + score += np.sum(cnt * logsumexp(Elogthetad + Elogbeta[:, int(id)]) for id, cnt in doc) # E[log p(theta | alpha) - log q(theta | gamma)]; assumes alpha is a vector score += np.sum((self.alpha - gammad) * Elogthetad) From 2f68ca959975f33212f5e2bb37bbb8def8b6742e Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Wed, 8 Mar 2017 13:59:45 -0800 Subject: [PATCH 09/41] wordrank ipynb corpus link fix (#1195) --- docs/notebooks/WordRank_wrapper_quickstart.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/notebooks/WordRank_wrapper_quickstart.ipynb b/docs/notebooks/WordRank_wrapper_quickstart.ipynb index 8d89e25152..dfea2d81ad 100644 --- a/docs/notebooks/WordRank_wrapper_quickstart.ipynb +++ b/docs/notebooks/WordRank_wrapper_quickstart.ipynb @@ -13,7 +13,7 @@ "\n", "# Train model\n", "\n", - "We'll use [Lee corpus](https://github.com/RaRe-Technologies/gensim/blob/develop/gensim/test/test_data/lee_background.cor) for training which is already available in gensim. Now for Wordrank, two parameters `dump_period` and `iter` needs to be in sync as it dumps the embedding file with the start of next iteration. For example, if you want results after 10 iterations, you need to use `iter=11` and `dump_period` can be anything that gives mod 0 with resulting iteration, in this case 2 or 5.\n" + "We'll use [Lee corpus](https://github.com/RaRe-Technologies/gensim/blob/develop/gensim/test/test_data/lee.cor) for training which is already available in gensim. Now for Wordrank, two parameters `dump_period` and `iter` needs to be in sync as it dumps the embedding file with the start of next iteration. For example, if you want results after 10 iterations, you need to use `iter=11` and `dump_period` can be anything that gives mod 0 with resulting iteration, in this case 2 or 5.\n" ] }, { From d6fbefc9d6f6f158a54250a966fbdc58808b93f7 Mon Sep 17 00:00:00 2001 From: Chinmaya Pancholi Date: Sat, 11 Mar 2017 21:40:19 +0530 Subject: [PATCH 10/41] updated description for worker_loop function used in score function --- gensim/models/word2vec.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gensim/models/word2vec.py b/gensim/models/word2vec.py index 8c371ea8bd..182e1c4dc1 100644 --- a/gensim/models/word2vec.py +++ b/gensim/models/word2vec.py @@ -474,7 +474,7 @@ def __init__( raise TypeError("You can't pass a generator as the sentences argument. Try an iterator.") self.build_vocab(sentences, trim_rule=trim_rule) self.train(sentences) - + else : if trim_rule is not None : logger.warning("The rule, if given, is only used prune vocabulary during build_vocab() and is not stored as part of the model. ") @@ -995,7 +995,7 @@ def score(self, sentences, total_sentences=int(1e6), chunksize=100, queue_factor run word2vec with hs=1 and negative=0 for this to work.") def worker_loop(): - """Train the model, lifting lists of sentences from the jobs queue.""" + """Compute log probability for each sentence, lifting lists of sentences from the jobs queue.""" work = zeros(1, dtype=REAL) # for sg hs, we actually only need one memory loc (running sum) neu1 = matutils.zeros_aligned(self.layer1_size, dtype=REAL) while True: From dd396e3cab79546c227aefb2d7e7358432b40090 Mon Sep 17 00:00:00 2001 From: Chinmaya Pancholi Date: Mon, 13 Mar 2017 00:09:18 +0530 Subject: [PATCH 11/41] Allow training if model is not modified by "_minimize_model". Add deprecation warning. (#1207) --- gensim/models/word2vec.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gensim/models/word2vec.py b/gensim/models/word2vec.py index 182e1c4dc1..df90fb3fe2 100644 --- a/gensim/models/word2vec.py +++ b/gensim/models/word2vec.py @@ -106,6 +106,7 @@ from collections import defaultdict import threading import itertools +import warnings from gensim.utils import keep_vocab_item, call_on_class_only from gensim.utils import keep_vocab_item @@ -1245,6 +1246,9 @@ def __str__(self): return "%s(vocab=%s, size=%s, alpha=%s)" % (self.__class__.__name__, len(self.wv.index2word), self.vector_size, self.alpha) def _minimize_model(self, save_syn1 = False, save_syn1neg = False, save_syn0_lockf = False): + warnings.warn("This method would be deprecated in the future. Keep just_word_vectors = model.wv to retain just the KeyedVectors instance for read-only querying of word vectors.") + if save_syn1 and save_syn1neg and save_syn0_lockf: + return if hasattr(self, 'syn1') and not save_syn1: del self.syn1 if hasattr(self, 'syn1neg') and not save_syn1neg: From ff9cc72b90de3761165df36a9812f63753133869 Mon Sep 17 00:00:00 2001 From: Hemalatha Vakade Date: Sun, 12 Mar 2017 19:15:33 -0700 Subject: [PATCH 12/41] Word2vec error message when update called before train . Fix #1162 (#1205) --- gensim/models/word2vec.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gensim/models/word2vec.py b/gensim/models/word2vec.py index df90fb3fe2..89d6e2ab5d 100644 --- a/gensim/models/word2vec.py +++ b/gensim/models/word2vec.py @@ -1091,6 +1091,13 @@ def update_weights(self): for i in xrange(len(self.wv.syn0), len(self.wv.vocab)): # construct deterministic seed from word AND seed argument newsyn0[i-len(self.wv.syn0)] = self.seeded_vector(self.wv.index2word[i] + str(self.seed)) + + # Raise an error if an online update is run before initial training on a corpus + if not len(self.wv.syn0): + raise RuntimeError("You cannot do an online vocabulary-update of a model which has no prior vocabulary. " \ + "First build the vocabulary of your model with a corpus " \ + "before doing an online update.") + self.wv.syn0 = vstack([self.wv.syn0, newsyn0]) if self.hs: From 9db7feec2611deb909a3497140301c1108b3a39e Mon Sep 17 00:00:00 2001 From: Lev Konstantinovskiy Date: Mon, 13 Mar 2017 17:12:07 -0300 Subject: [PATCH 13/41] Issue template (#1194) * Add issue template from sklearn as first draft * Update ISSUE_TEMPLATE.md * Add word2vec single letter vocab example --- ISSUE_TEMPLATE.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 ISSUE_TEMPLATE.md diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..44eaefb24f --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,48 @@ + + + + +#### Description +TODO: change commented example + + +#### Steps/Code/Corpus to Reproduce + + +#### Expected Results + + +#### Actual Results + + +#### Versions + + + + + From e6405c94ab62bb0f63d602eccca8849590d74a73 Mon Sep 17 00:00:00 2001 From: Lev Konstantinovskiy Date: Mon, 13 Mar 2017 18:05:37 -0300 Subject: [PATCH 14/41] Increase epsilon for comparing in KL tests (#1211) --- gensim/test/test_similarity_metrics.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gensim/test/test_similarity_metrics.py b/gensim/test/test_similarity_metrics.py index 700086d20e..858e388e77 100644 --- a/gensim/test/test_similarity_metrics.py +++ b/gensim/test/test_similarity_metrics.py @@ -188,7 +188,7 @@ def test_distributions(self): vec_2 = [(1, 0.1), (3, 0.8), (4, 0.1)] result = matutils.kullback_leibler(vec_2, vec_1, 8) expected = 0.55451775 - self.assertAlmostEqual(expected, result) + self.assertAlmostEqual(expected, result, places=5) # KL is not symetric; vec1 compared with vec2 will contain log of zeros and return infinity vec_1 = [(2, 0.1), (3, 0.4), (4, 0.1), (5, 0.1), (1, 0.1), (7, 0.2)] @@ -201,14 +201,14 @@ def test_distributions(self): vec_2 = csr_matrix([[1, 0.4], [0, 0.2], [2, 0.2]]) result = matutils.kullback_leibler(vec_1, vec_2, 3) expected = 0.0894502 - self.assertAlmostEqual(expected, result) + self.assertAlmostEqual(expected, result, places=5) # checking ndarray, list as inputs vec_1 = np.array([0.6, 0.1, 0.1, 0.2]) vec_2 = [0.2, 0.2, 0.1, 0.5] result = matutils.kullback_leibler(vec_1, vec_2) expected = 0.40659450877 - self.assertAlmostEqual(expected, result) + self.assertAlmostEqual(expected, result, places=5) # testing LDA distribution vectors np.random.seed(0) @@ -217,7 +217,7 @@ def test_distributions(self): lda_vec2 = model[[(2, 2), (1, 3)]] result = matutils.kullback_leibler(lda_vec1, lda_vec2) expected = 4.283407e-12 - self.assertAlmostEqual(expected, result) + self.assertAlmostEqual(expected, result, places=5) class TestJaccard(unittest.TestCase): def test_inputs(self): From 8c869cbd3e6b820e4d9ffdb55e538a1547ffad9a Mon Sep 17 00:00:00 2001 From: Tomasz Oliwa Date: Mon, 13 Mar 2017 16:55:38 -0500 Subject: [PATCH 15/41] Add the 'keep_tokens' parameter to 'filter_extremes' (#1210) * Add the 'keep_tokens' parameter to 'filter_extremes' and test it Add the optional 'keep_tokens' parameter to the 'filter_extremes' method in dictionary.py. This parameter can contain a list of tokens, which will be kept regardless of the 'no_below' and 'no_above' settings. This can be useful if the research goal is to enforce certain tokens to appear in topics, and still be able to filter all other extremes. If 'keep_tokens' is not given, the functionality of 'filter_extremes' is unchanged. Unit tests are also provided to assert examples of the above. * Create good_ids only once Create good_ids only once as per optimization suggestion, regardless if 'keep_tokens' is provided or not. --- gensim/corpora/dictionary.py | 18 +++++++++++++----- gensim/test/test_corpora_dictionary.py | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/gensim/corpora/dictionary.py b/gensim/corpora/dictionary.py index 1fd7e31e61..484684c26d 100644 --- a/gensim/corpora/dictionary.py +++ b/gensim/corpora/dictionary.py @@ -172,14 +172,16 @@ def doc2bow(self, document, allow_update=False, return_missing=False): else: return result - def filter_extremes(self, no_below=5, no_above=0.5, keep_n=100000): + def filter_extremes(self, no_below=5, no_above=0.5, keep_n=100000, keep_tokens=None): """ Filter out tokens that appear in 1. less than `no_below` documents (absolute number) or 2. more than `no_above` documents (fraction of total corpus size, *not* absolute number). - 3. after (1) and (2), keep only the first `keep_n` most frequent tokens (or + 3. if tokens are given in keep_tokens (list of strings), they will be kept regardless of + the `no_below` and `no_above` settings + 4. after (1), (2) and (3), keep only the first `keep_n` most frequent tokens (or keep all if `None`). After the pruning, shrink resulting gaps in word ids. @@ -190,9 +192,15 @@ def filter_extremes(self, no_below=5, no_above=0.5, keep_n=100000): no_above_abs = int(no_above * self.num_docs) # convert fractional threshold to absolute threshold # determine which tokens to keep - good_ids = ( - v for v in itervalues(self.token2id) - if no_below <= self.dfs.get(v, 0) <= no_above_abs) + if keep_tokens: + keep_ids = [self.token2id[v] for v in keep_tokens if v in self.token2id] + good_ids = (v for v in itervalues(self.token2id) + if no_below <= self.dfs.get(v, 0) <= no_above_abs + or v in keep_ids) + else: + good_ids = ( + v for v in itervalues(self.token2id) + if no_below <= self.dfs.get(v, 0) <= no_above_abs) good_ids = sorted(good_ids, key=self.dfs.get, reverse=True) if keep_n is not None: good_ids = good_ids[:keep_n] diff --git a/gensim/test/test_corpora_dictionary.py b/gensim/test/test_corpora_dictionary.py index bbf5fa339d..16c499b245 100644 --- a/gensim/test/test_corpora_dictionary.py +++ b/gensim/test/test_corpora_dictionary.py @@ -120,6 +120,27 @@ def testFilter(self): d.filter_extremes(no_below=2, no_above=1.0, keep_n=4) expected = {0: 3, 1: 3, 2: 3, 3: 3} self.assertEqual(d.dfs, expected) + + def testFilterKeepTokens_keepTokens(self): + # provide keep_tokens argument, keep the tokens given + d = Dictionary(self.texts) + d.filter_extremes(no_below=3, no_above=1.0, keep_tokens=['human', 'survey']) + expected = set(['graph', 'trees', 'human', 'system', 'user', 'survey']) + self.assertEqual(set(d.token2id.keys()), expected) + + def testFilterKeepTokens_unchangedFunctionality(self): + # do not provide keep_tokens argument, filter_extremes functionality is unchanged + d = Dictionary(self.texts) + d.filter_extremes(no_below=3, no_above=1.0) + expected = set(['graph', 'trees', 'system', 'user']) + self.assertEqual(set(d.token2id.keys()), expected) + + def testFilterKeepTokens_unseenToken(self): + # do provide keep_tokens argument with unseen tokens, filter_extremes functionality is unchanged + d = Dictionary(self.texts) + d.filter_extremes(no_below=3, no_above=1.0, keep_tokens=['unknown_token']) + expected = set(['graph', 'trees', 'system', 'user']) + self.assertEqual(set(d.token2id.keys()), expected) def testFilterMostFrequent(self): d = Dictionary(self.texts) From f98e01299395a720cf869af80c2660b73b3ad226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radim=20=C5=98eh=C5=AF=C5=99ek?= Date: Tue, 14 Mar 2017 23:41:08 +0900 Subject: [PATCH 16/41] fix google scholar link on the about page (#1212) --- docs/src/about.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/about.rst b/docs/src/about.rst index d92855fd81..294c60d52c 100644 --- a/docs/src/about.rst +++ b/docs/src/about.rst @@ -64,7 +64,7 @@ Some honorable mentions are included in the `CHANGELOG.txt `_. When citing gensim, +Gensim has been used in `many students' final theses as well as research papers `_. When citing gensim, please use `this BibTeX entry `_:: @inproceedings{rehurek_lrec, From 3053459c9806bff528ee067a8d8cc141eb23d6b3 Mon Sep 17 00:00:00 2001 From: Bhargav Srinivasa Date: Fri, 17 Mar 2017 02:12:28 +0100 Subject: [PATCH 17/41] Dictionary Coloring notebook update (#1164) * Dictionary Coloring * Clarified incorrect coloring --- docs/notebooks/topic_methods.ipynb | 409 +++++++++++++++++++++-------- 1 file changed, 305 insertions(+), 104 deletions(-) diff --git a/docs/notebooks/topic_methods.ipynb b/docs/notebooks/topic_methods.ipynb index 53fabda694..d140bdad7f 100644 --- a/docs/notebooks/topic_methods.ipynb +++ b/docs/notebooks/topic_methods.ipynb @@ -2,7 +2,10 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "# New Term Topics Methods and Document Coloring" ] @@ -11,14 +14,16 @@ "cell_type": "code", "execution_count": 1, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/Users/partho/anaconda/lib/python2.7/site-packages/matplotlib/font_manager.py:273: UserWarning: Matplotlib is building the font cache using fc-list. This may take a moment.\n", + "/usr/local/lib/python2.7/site-packages/matplotlib/font_manager.py:273: UserWarning: Matplotlib is building the font cache using fc-list. This may take a moment.\n", " warnings.warn('Matplotlib is building the font cache using fc-list. This may take a moment.')\n" ] } @@ -27,12 +32,15 @@ "from gensim.corpora import Dictionary\n", "from gensim.models import ldamodel\n", "import numpy\n", - "%matplotlib inline\n" + "%matplotlib inline" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "We're setting up our corpus now. We want to show off the new `get_term_topics` and `get_document_topics` functionalities, and a good way to do so is to play around with words which might have different meanings in different context.\n", "\n", @@ -44,7 +52,9 @@ "cell_type": "code", "execution_count": 2, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -66,7 +76,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "We set up the LDA model in the corpus. We set the number of topics to be 2, and expect to see one which is to do with river banks, and one to do with financial banks. " ] @@ -75,7 +88,9 @@ "cell_type": "code", "execution_count": 3, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -87,7 +102,9 @@ "cell_type": "code", "execution_count": 4, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -110,21 +127,30 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "And like we expected, the LDA model has given us near perfect results. Bank is the most influential word in both the topics, as we can see. The other words help define what kind of bank we are talking about. Let's now see where our new methods fit in." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "### get_term_topics" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "The function `get_term_topics` returns the odds of that particular word belonging to a particular topic. \n", "A few examples:" @@ -134,13 +160,15 @@ "cell_type": "code", "execution_count": 5, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { "data": { "text/plain": [ - "[(0, 0.12821234071249407), (1, 0.047247458568794552)]" + "[(0, 0.12821234071249418), (1, 0.047247458568794511)]" ] }, "execution_count": 5, @@ -154,7 +182,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Makes sense, the value for it belonging to `topic_0` is a lot more." ] @@ -163,13 +194,15 @@ "cell_type": "code", "execution_count": 6, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { "data": { "text/plain": [ - "[(0, 0.017179349495865623), (1, 0.10331511184214646)]" + "[(0, 0.017179349495865623), (1, 0.10331511184214655)]" ] }, "execution_count": 6, @@ -183,7 +216,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "This also works out well, the word finance is more likely to be in topic_1 to do with financial banks." ] @@ -192,13 +228,15 @@ "cell_type": "code", "execution_count": 7, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { "data": { "text/plain": [ - "[(0, 0.15042435080542083), (1, 0.1804462723220118)]" + "[(0, 0.15042435080542094), (1, 0.18044627232201182)]" ] }, "execution_count": 7, @@ -212,21 +250,30 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "And this is particularly interesting. Since the word bank is likely to be in both the topics, the values returned are also very similar." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "### get_document_topics and Document Word-Topic Coloring" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "`get_document_topics` is an already existing gensim functionality which uses the `inference` function to get the sufficient statistics and figure out the topic distribution of the document.\n", "\n", @@ -240,7 +287,9 @@ "cell_type": "code", "execution_count": 8, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -252,7 +301,9 @@ "cell_type": "code", "execution_count": 9, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -275,14 +326,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Now what does that output mean? It means that like `word_type 1`, our `word_type` `3`, which is the word `bank`, is more likely to be in `topic_0` than `topic_1`." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "You must have noticed that while we unpacked into `doc_topics` and `word_topics`, there is another variable - `phi_values`. Like the name suggests, phi_values contains the phi values for each topic for that particular word, scaled by feature length. Phi is essentially the probability of that word in that document belonging to a particular topic. The next few lines should illustrate this. " ] @@ -291,14 +348,16 @@ "cell_type": "code", "execution_count": 10, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { "data": { "text/plain": [ - "[(0, [(0, 0.9248645556429429), (1, 0.075135444357057019)]),\n", - " (3, [(0, 1.5817120973072436), (1, 0.41828790269275634)])]" + "[(0, [(0, 0.92486455564294345), (1, 0.075135444357056574)]),\n", + " (3, [(0, 1.5817120973072454), (1, 0.41828790269275457)])]" ] }, "execution_count": 10, @@ -312,7 +371,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "This means that `word_type` 0 has the following phi_values for each of the topics. \n", "What is intresting to note is `word_type` 3 - because it has 2 occurences (i.e, the word `bank` appears twice in the bow), we can see that the scaling by feature length is very evident. The sum of the phi_values is 2, and not 1." @@ -320,7 +382,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Now that we know exactly what `get_document_topics` does, let us now do the same with our second document, `bow_finance`." ] @@ -329,7 +394,9 @@ "cell_type": "code", "execution_count": 11, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -352,7 +419,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "And lo and behold, because the word bank is now used in the financial context, it immedietly swaps to being more likely associated with `topic_1`.\n", "\n", @@ -364,7 +434,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "#### get_document_topics for entire corpus\n", "\n", @@ -375,7 +448,9 @@ "cell_type": "code", "execution_count": 12, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -384,41 +459,41 @@ "text": [ "New Document \n", "\n", - "Document topics: [(0, 0.83270647275828491), (1, 0.16729352724171503)]\n", + "Document topics: [(0, 0.83270647275828524), (1, 0.16729352724171473)]\n", "Word topics: [(0, [0, 1]), (1, [0, 1]), (2, [0, 1]), (3, [0, 1])]\n", - "Phi values: [(0, [(0, 0.96021858877561683), (1, 0.03978141122438307)]), (1, [(0, 0.87921979686273721), (1, 0.12078020313726268)]), (2, [(0, 0.94364164103826886), (1, 0.056358358961731088)]), (3, [(0, 0.88116401400740574), (1, 0.11883598599259435)])]\n", + "Phi values: [(0, [(0, 0.96021858877561717), (1, 0.039781411224382883)]), (1, [(0, 0.87921979686273788), (1, 0.12078020313726225)]), (2, [(0, 0.94364164103826909), (1, 0.056358358961730845)]), (3, [(0, 0.88116401400740607), (1, 0.11883598599259393)])]\n", " \n", "-------------- \n", "\n", "New Document \n", "\n", - "Document topics: [(0, 0.90379559943992582), (1, 0.096204400560074232)]\n", + "Document topics: [(0, 0.90379559943992582), (1, 0.096204400560074191)]\n", "Word topics: [(0, [0, 1]), (2, [0, 1]), (4, [0]), (5, [0, 1]), (6, [0])]\n", - "Phi values: [(0, [(0, 0.98551395531215846), (1, 0.014486044687841475)]), (2, [(0, 0.97924982750620215), (1, 0.020750172493797733)]), (4, [(0, 0.99280849901823975)]), (5, [(0, 0.97529774122781776), (1, 0.024702258772182187)]), (6, [(0, 0.99004205057244832)])]\n", + "Phi values: [(0, [(0, 0.98551395531215857), (1, 0.014486044687841437)]), (2, [(0, 0.97924982750620249), (1, 0.020750172493797691)]), (4, [(0, 0.99280849901823975)]), (5, [(0, 0.97529774122781787), (1, 0.024702258772182122)]), (6, [(0, 0.99004205057244832)])]\n", " \n", "-------------- \n", "\n", "New Document \n", "\n", - "Document topics: [(0, 0.87507219282484316), (1, 0.12492780717515684)]\n", + "Document topics: [(0, 0.87507219282484316), (1, 0.12492780717515681)]\n", "Word topics: [(0, [0, 1]), (3, [0, 1]), (4, [0, 1]), (7, [0, 1])]\n", - "Phi values: [(0, [(0, 0.97832342005836559), (1, 0.021676579941634404)]), (3, [(0, 0.93272653621872503), (1, 0.067273463781275078)]), (4, [(0, 0.98919912227661466), (1, 0.010800877723385373)]), (7, [(0, 0.97541896333079636), (1, 0.024581036669203651)])]\n", + "Phi values: [(0, [(0, 0.9783234200583657), (1, 0.021676579941634355)]), (3, [(0, 0.93272653621872503), (1, 0.067273463781275009)]), (4, [(0, 0.98919912227661466), (1, 0.010800877723385368)]), (7, [(0, 0.97541896333079636), (1, 0.024581036669203641)])]\n", " \n", "-------------- \n", "\n", "New Document \n", "\n", - "Document topics: [(0, 0.87853819958920021), (1, 0.12146180041079968)]\n", + "Document topics: [(0, 0.87853819958920043), (1, 0.12146180041079958)]\n", "Word topics: [(0, [0, 1]), (2, [0, 1]), (3, [0, 1]), (8, [0, 1])]\n", - "Phi values: [(0, [(0, 0.9759613424948147), (1, 0.024038657505185197)]), (2, [(0, 0.96571015226994938), (1, 0.034289847730050602)]), (3, [(0, 1.8515455755053762), (1, 0.14845442449462384)]), (8, [(0, 0.97848202469975276), (1, 0.021517975300247412)])]\n", + "Phi values: [(0, [(0, 0.97596134249481492), (1, 0.024038657505185138)]), (2, [(0, 0.96571015226994938), (1, 0.034289847730050525)]), (3, [(0, 1.851545575505376), (1, 0.14845442449462365)]), (8, [(0, 0.97848202469975276), (1, 0.021517975300247363)])]\n", " \n", "-------------- \n", "\n", "New Document \n", "\n", - "Document topics: [(0, 0.8564463740623548), (1, 0.14355362593764526)]\n", + "Document topics: [(0, 0.85644637406235502), (1, 0.14355362593764506)]\n", "Word topics: [(0, [0, 1]), (2, [0, 1]), (5, [0, 1]), (9, [0, 1])]\n", - "Phi values: [(0, [(0, 0.97074863890671403), (1, 0.029251361093286004)]), (2, [(0, 0.95836933362205579), (1, 0.04163066637794411)]), (5, [(0, 0.95064079648593447), (1, 0.049359203514065572)]), (9, [(0, 0.90303582762229029), (1, 0.096964172377709767)])]\n", + "Phi values: [(0, [(0, 0.97074863890671426), (1, 0.029251361093285893)]), (2, [(0, 0.95836933362205601), (1, 0.041630666377943965)]), (5, [(0, 0.95064079648593469), (1, 0.049359203514065378)]), (9, [(0, 0.90303582762229051), (1, 0.096964172377709504)])]\n", " \n", "-------------- \n", "\n", @@ -426,15 +501,15 @@ "\n", "Document topics: [(0, 0.11549963646117178), (1, 0.88450036353882822)]\n", "Word topics: [(3, [1, 0]), (10, [1, 0]), (11, [1]), (12, [1])]\n", - "Phi values: [(3, [(0, 0.040062133454181532), (1, 0.95993786654581847)]), (10, [(0, 0.02010380646799674), (1, 0.97989619353200308)]), (11, [(1, 0.9910494032913304)]), (12, [(1, 0.99174412290358549)])]\n", + "Phi values: [(3, [(0, 0.040062133454181546), (1, 0.95993786654581814)]), (10, [(0, 0.020103806467996775), (1, 0.97989619353200308)]), (11, [(1, 0.9910494032913304)]), (12, [(1, 0.99174412290358549)])]\n", " \n", "-------------- \n", "\n", "New Document \n", "\n", - "Document topics: [(0, 0.44388593146078087), (1, 0.55611406853921919)]\n", + "Document topics: [(0, 0.44388593146078198), (1, 0.55611406853921797)]\n", "Word topics: [(3, [1, 0]), (10, [1, 0]), (13, [0, 1])]\n", - "Phi values: [(3, [(0, 0.38381806344612424), (1, 0.61618193655387588)]), (10, [(0, 0.23442811582700671), (1, 0.76557188417299327)]), (13, [(0, 0.6565173689986924), (1, 0.34348263100130755)])]\n", + "Phi values: [(3, [(0, 0.38381806344612579), (1, 0.61618193655387421)]), (10, [(0, 0.23442811582700812), (1, 0.76557188417299193)]), (13, [(0, 0.65651736899869417), (1, 0.34348263100130588)])]\n", " \n", "-------------- \n", "\n", @@ -442,31 +517,31 @@ "\n", "Document topics: [(0, 0.20199255912939526), (1, 0.79800744087060471)]\n", "Word topics: [(3, [1, 0]), (12, [1, 0])]\n", - "Phi values: [(3, [(0, 0.086998287940481173), (1, 0.91300171205951886)]), (12, [(0, 0.018652395463982244), (1, 0.98134760453601777)])]\n", + "Phi values: [(3, [(0, 0.086998287940481228), (1, 0.91300171205951863)]), (12, [(0, 0.018652395463982233), (1, 0.98134760453601788)])]\n", " \n", "-------------- \n", "\n", "New Document \n", "\n", - "Document topics: [(0, 0.12505726157782684), (1, 0.87494273842217307)]\n", + "Document topics: [(0, 0.12505726157782684), (1, 0.87494273842217329)]\n", "Word topics: [(3, [1, 0]), (10, [1, 0]), (12, [1]), (14, [1, 0])]\n", - "Phi values: [(3, [(0, 0.047837589620218307), (1, 0.95216241037978167)]), (10, [(0, 0.024102914052397426), (1, 0.97589708594760249)]), (12, [(1, 0.99007797561579514)]), (14, [(0, 0.043092845513997537), (1, 0.9569071544860025)])]\n", + "Phi values: [(3, [(0, 0.047837589620218293), (1, 0.95216241037978167)]), (10, [(0, 0.024102914052397447), (1, 0.9758970859476026)]), (12, [(1, 0.99007797561579536)]), (14, [(0, 0.04309284551399737), (1, 0.95690715448600272)])]\n", " \n", "-------------- \n", "\n", "New Document \n", "\n", - "Document topics: [(0, 0.72319610071601981), (1, 0.27680389928398014)]\n", + "Document topics: [(0, 0.72319610071601925), (1, 0.27680389928398069)]\n", "Word topics: [(13, [0, 1]), (14, [0, 1])]\n", - "Phi values: [(13, [(0, 0.91396121153662724), (1, 0.086038788463372665)]), (14, [(0, 0.75627751890080153), (1, 0.24372248109919836)])]\n", + "Phi values: [(13, [(0, 0.91396121153662691), (1, 0.086038788463373025)]), (14, [(0, 0.75627751890079997), (1, 0.24372248109919997)])]\n", " \n", "-------------- \n", "\n", "New Document \n", "\n", - "Document topics: [(0, 0.16978578818257661), (1, 0.83021421181742339)]\n", + "Document topics: [(0, 0.16978578818257647), (1, 0.8302142118174235)]\n", "Word topics: [(3, [1, 0]), (14, [1, 0]), (15, [1, 0])]\n", - "Phi values: [(3, [(0, 0.075528355267194022), (1, 0.92447164473280585)]), (14, [(0, 0.068233937712710677), (1, 0.93176606228728931)]), (15, [(0, 0.035035615878295893), (1, 0.96496438412170416)])]\n", + "Phi values: [(3, [(0, 0.075528355267193981), (1, 0.92447164473280596)]), (14, [(0, 0.068233937712710399), (1, 0.93176606228728964)]), (15, [(0, 0.035035615878295796), (1, 0.96496438412170416)])]\n", " \n", "-------------- \n", "\n" @@ -487,7 +562,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "In case you want to store `doc_topics`, `word_topics` and `phi_values` for all the documents in the corpus in a variable and later access details of a particular document using its index, it can be done in the following manner:" ] @@ -496,7 +574,9 @@ "cell_type": "code", "execution_count": 13, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -506,7 +586,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Now, I can access details of a particular document, say Document #3, as follows: " ] @@ -515,18 +598,20 @@ "cell_type": "code", "execution_count": 14, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Document topic: [(0, 0.16977265560819235), (1, 0.83022734439180768)] \n", + "Document topic: [(0, 0.1697726556081923), (1, 0.83022734439180768)] \n", "\n", "Word topic: [(0, [0, 1]), (3, [0, 1]), (4, [0, 1]), (7, [0, 1])] \n", "\n", - "Phi value: [(0, [(0, 0.97832871059713777), (1, 0.021671289402862077)]), (3, [(0, 0.93274219037812445), (1, 0.067257809621875594)]), (4, [(0, 0.98920178771276146), (1, 0.010798212287238566)]), (7, [(0, 0.97542494494492515), (1, 0.02457505505507479)])]\n" + "Phi value: [(0, [(0, 0.978328710597138), (1, 0.021671289402862035)]), (3, [(0, 0.93274219037812456), (1, 0.067257809621875539)]), (4, [(0, 0.98920178771276146), (1, 0.010798212287238563)]), (7, [(0, 0.97542494494492515), (1, 0.02457505505507478)])]\n" ] } ], @@ -539,7 +624,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "We can print details for all the documents (as shown above), in the following manner:" ] @@ -548,7 +636,9 @@ "cell_type": "code", "execution_count": 15, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -557,9 +647,9 @@ "text": [ "New Document \n", "\n", - "Document topic: [(0, 0.83271544885346704), (1, 0.16728455114653284)]\n", + "Document topic: [(0, 0.83271544885346738), (1, 0.16728455114653268)]\n", "Word topic: [(0, [0, 1]), (1, [0, 1]), (2, [0, 1]), (3, [0, 1])]\n", - "Phi value: [(0, [(0, 0.96022273559375504), (1, 0.039777264406244892)]), (1, [(0, 0.87923132506871837), (1, 0.12076867493128163)]), (2, [(0, 0.94364741442849265), (1, 0.056352585571507421)]), (3, [(0, 0.88117538172166476), (1, 0.11882461827833525)])]\n", + "Phi value: [(0, [(0, 0.96022273559375526), (1, 0.039777264406244746)]), (1, [(0, 0.87923132506871871), (1, 0.12076867493128131)]), (2, [(0, 0.94364741442849287), (1, 0.056352585571507234)]), (3, [(0, 0.8811753817216651), (1, 0.11882461827833496)])]\n", " \n", "-------------- \n", "\n", @@ -567,47 +657,47 @@ "\n", "Document topic: [(0, 0.90379650157173907), (1, 0.096203498428260883)]\n", "Word topic: [(0, [0, 1]), (2, [0, 1]), (4, [0]), (5, [0, 1]), (6, [0])]\n", - "Phi value: [(0, [(0, 0.98551427047222717), (1, 0.014485729527772788)]), (2, [(0, 0.9792502760799594), (1, 0.020749723920040649)]), (4, [(0, 0.99280865663541784)]), (5, [(0, 0.97529827308199024), (1, 0.024701726918009776)]), (6, [(0, 0.99004226821414432)])]\n", + "Phi value: [(0, [(0, 0.98551427047222728), (1, 0.014485729527772766)]), (2, [(0, 0.9792502760799594), (1, 0.020749723920040618)]), (4, [(0, 0.99280865663541784)]), (5, [(0, 0.97529827308199035), (1, 0.024701726918009728)]), (6, [(0, 0.99004226821414432)])]\n", " \n", "-------------- \n", "\n", "New Document \n", "\n", - "Document topic: [(0, 0.87508582200973162), (1, 0.12491417799026835)]\n", + "Document topic: [(0, 0.87508582200973173), (1, 0.12491417799026834)]\n", "Word topic: [(0, [0, 1]), (3, [0, 1]), (4, [0, 1]), (7, [0, 1])]\n", - "Phi value: [(0, [(0, 0.97832871059713777), (1, 0.021671289402862077)]), (3, [(0, 0.93274219037812445), (1, 0.067257809621875594)]), (4, [(0, 0.98920178771276146), (1, 0.010798212287238566)]), (7, [(0, 0.97542494494492515), (1, 0.02457505505507479)])]\n", + "Phi value: [(0, [(0, 0.978328710597138), (1, 0.021671289402862035)]), (3, [(0, 0.93274219037812456), (1, 0.067257809621875539)]), (4, [(0, 0.98920178771276146), (1, 0.010798212287238563)]), (7, [(0, 0.97542494494492515), (1, 0.02457505505507478)])]\n", " \n", "-------------- \n", "\n", "New Document \n", "\n", - "Document topic: [(0, 0.87849699952437454), (1, 0.12150300047562547)]\n", + "Document topic: [(0, 0.87849699952437466), (1, 0.12150300047562536)]\n", "Word topic: [(0, [0, 1]), (2, [0, 1]), (3, [0, 1]), (8, [0, 1])]\n", - "Phi value: [(0, [(0, 0.97594470848740367), (1, 0.024055291512596319)]), (2, [(0, 0.96568667415296994), (1, 0.034313325847029959)]), (3, [(0, 1.8514481357538262), (1, 0.14855186424617392)]), (8, [(0, 0.97846709644293861), (1, 0.021532903557061465)])]\n", + "Phi value: [(0, [(0, 0.97594470848740367), (1, 0.024055291512596246)]), (2, [(0, 0.96568667415296994), (1, 0.034313325847029875)]), (3, [(0, 1.8514481357538264), (1, 0.14855186424617364)]), (8, [(0, 0.97846709644293861), (1, 0.021532903557061403)])]\n", " \n", "-------------- \n", "\n", "New Document \n", "\n", - "Document topic: [(0, 0.85642628246505537), (1, 0.14357371753494458)]\n", + "Document topic: [(0, 0.85642628246505548), (1, 0.14357371753494441)]\n", "Word topic: [(0, [0, 1]), (2, [0, 1]), (5, [0, 1]), (9, [0, 1])]\n", - "Phi value: [(0, [(0, 0.97074011917537673), (1, 0.029259880824623285)]), (2, [(0, 0.95835736297327889), (1, 0.041642637026720976)]), (5, [(0, 0.95062671803078891), (1, 0.049373281969211057)]), (9, [(0, 0.9030095563876398), (1, 0.096990443612360172)])]\n", + "Phi value: [(0, [(0, 0.97074011917537684), (1, 0.02925988082462316)]), (2, [(0, 0.95835736297327923), (1, 0.041642637026720823)]), (5, [(0, 0.95062671803078924), (1, 0.049373281969210848)]), (9, [(0, 0.90300955638764013), (1, 0.096990443612359839)])]\n", " \n", "-------------- \n", "\n", "New Document \n", "\n", - "Document topic: [(0, 0.11553980471363215), (1, 0.88446019528636788)]\n", + "Document topic: [(0, 0.11553980471363219), (1, 0.88446019528636788)]\n", "Word topic: [(3, [1, 0]), (10, [1, 0]), (11, [1]), (12, [1])]\n", - "Phi value: [(3, [(0, 0.040094010013352027), (1, 0.95990598998664789)]), (10, [(0, 0.020120135475541381), (1, 0.97987986452445863)]), (11, [(1, 0.99104205049167049)]), (12, [(1, 0.99173733604900283)])]\n", + "Phi value: [(3, [(0, 0.040094010013352048), (1, 0.95990598998664811)]), (10, [(0, 0.020120135475541409), (1, 0.97987986452445841)]), (11, [(1, 0.9910420504916706)]), (12, [(1, 0.99173733604900283)])]\n", " \n", "-------------- \n", "\n", "New Document \n", "\n", - "Document topic: [(0, 0.44387392752172772), (1, 0.55612607247827239)]\n", + "Document topic: [(0, 0.44387392752172899), (1, 0.55612607247827095)]\n", "Word topic: [(3, [1, 0]), (10, [1, 0]), (13, [0, 1])]\n", - "Phi value: [(3, [(0, 0.38380312832253166), (1, 0.61619687167746828)]), (10, [(0, 0.2344167822754758), (1, 0.76558321772452431)]), (13, [(0, 0.65650312824652346), (1, 0.34349687175347654)])]\n", + "Phi value: [(3, [(0, 0.38380312832253366), (1, 0.61619687167746628)]), (10, [(0, 0.23441678227547744), (1, 0.76558321772452254)]), (13, [(0, 0.65650312824652535), (1, 0.34349687175347449)])]\n", " \n", "-------------- \n", "\n", @@ -615,31 +705,31 @@ "\n", "Document topic: [(0, 0.20190467603529849), (1, 0.79809532396470151)]\n", "Word topic: [(3, [1, 0]), (12, [1, 0])]\n", - "Phi value: [(3, [(0, 0.086912561161162444), (1, 0.91308743883883758)]), (12, [(0, 0.018632641254402973), (1, 0.98136735874559711)])]\n", + "Phi value: [(3, [(0, 0.086912561161162485), (1, 0.91308743883883758)]), (12, [(0, 0.018632641254402959), (1, 0.98136735874559722)])]\n", " \n", "-------------- \n", "\n", "New Document \n", "\n", - "Document topic: [(0, 0.12500947583350869), (1, 0.87499052416649137)]\n", + "Document topic: [(0, 0.12500947583350866), (1, 0.87499052416649137)]\n", "Word topic: [(3, [1, 0]), (10, [1, 0]), (12, [1]), (14, [1, 0])]\n", - "Phi value: [(3, [(0, 0.047797812955744749), (1, 0.95220218704425519)]), (10, [(0, 0.024082373476646421), (1, 0.97591762652335368)]), (12, [(1, 0.9900865539576843)]), (14, [(0, 0.043056835672030884), (1, 0.956943164327969)])]\n", + "Phi value: [(3, [(0, 0.04779781295574477), (1, 0.95220218704425519)]), (10, [(0, 0.024082373476646459), (1, 0.97591762652335345)]), (12, [(1, 0.99008655395768441)]), (14, [(0, 0.043056835672030759), (1, 0.95694316432796922)])]\n", " \n", "-------------- \n", "\n", "New Document \n", "\n", - "Document topic: [(0, 0.72327037334816735), (1, 0.27672962665183259)]\n", + "Document topic: [(0, 0.72327037334816691), (1, 0.27672962665183315)]\n", "Word topic: [(13, [0, 1]), (14, [0, 1])]\n", - "Phi value: [(13, [(0, 0.91400946720135656), (1, 0.085990532798643396)]), (14, [(0, 0.75639064037568193), (1, 0.24360935962431809)])]\n", + "Phi value: [(13, [(0, 0.91400946720135656), (1, 0.085990532798643632)]), (14, [(0, 0.7563906403756806), (1, 0.2436093596243194)])]\n", " \n", "-------------- \n", "\n", "New Document \n", "\n", - "Document topic: [(0, 0.16977265560819235), (1, 0.83022734439180768)]\n", + "Document topic: [(0, 0.1697726556081923), (1, 0.83022734439180768)]\n", "Word topic: [(3, [1, 0]), (14, [1, 0]), (15, [1, 0])]\n", - "Phi value: [(3, [(0, 0.075516159640739211), (1, 0.92448384035926079)]), (14, [(0, 0.068222833001707214), (1, 0.93177716699829283)]), (15, [(0, 0.035029710897900787), (1, 0.96497028910209925)])]\n", + "Phi value: [(3, [(0, 0.075516159640739211), (1, 0.9244838403592609)]), (14, [(0, 0.068222833001706978), (1, 0.93177716699829294)]), (15, [(0, 0.035029710897900725), (1, 0.96497028910209925)])]\n", " \n", "-------------- \n", "\n" @@ -658,35 +748,46 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Coloring topic-terms" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "These methods can come in handy when we want to color the words in a corpus or a document. If we wish to color the words in a corpus (i.e, color all the words in the dictionary of the corpus), then `get_term_topics` would be a better choice. If not, `get_document_topics` would do the trick." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "We'll now attempt to color these words and plot it using `matplotlib`. \n", "This is just one way to go about plotting words - there are more and better ways.\n", "\n", "[WordCloud](https://github.com/amueller/word_cloud) is such a python package which also does this.\n", "\n", - "For our simple illustration, let's keep `topic_0` as red, and `topic_1` as blue." + "For our simple illustration, let's keep `topic_1` as red, and `topic_0` as blue." ] }, { "cell_type": "code", "execution_count": 16, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -702,7 +803,7 @@ " doc_topics, word_topics, phi_values = model.get_document_topics(doc, per_word_topics=True)\n", "\n", " # color-topic matching\n", - " topic_colors = { 0:'red', 1:'blue'}\n", + " topic_colors = { 1:'red', 0:'blue'}\n", " \n", " # set up fig to plot\n", " fig = plt.figure()\n", @@ -726,7 +827,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Let us revisit our old examples to show some examples of document coloring" ] @@ -735,14 +839,16 @@ "cell_type": "code", "execution_count": 17, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAeMAAAFBCAYAAABEo8fdAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAADBFJREFUeJzt3X+sJWddx/HPVwigrbS0BtuKJVGqrn8otkqrFRAXpVUr\nlOBPklZiDCIataQh/kKhhJBoIloTpTFIIiQqVavFGIEWlB8NEddqSinFGrBSqKFIgdql293HP565\n3Xvvnrt7b9rud9t9vZKTs3fmzJw5s8m+Z+aZc7fGGAEA+nxZ9wYAwPFOjAGgmRgDQDMxBoBmYgwA\nzcQYAJqJMQA0E2MAaCbGANBMjAGgmRgDQDMxBoBmYgwAzcQYAJqJMQA0E2MAaCbGANBMjAGgmRgD\nQDMxBoBmYgwAzcQYAJqJMQA0E2MAaCbGANBMjAGgmRgDQDMxBoBmYgwAzcQYAJqJMQA0E2MAaCbG\nANBMjAGgmRgDQDMxBoBmYgwAzcQYAJqJMQA0E2MAaCbGANBMjAGgmRgDQDMxBoBmYgwAzcQYAJqJ\nMQA0E2MAaCbGANBMjAGgmRgDQDMxBoBmYgwAzcQYAJqJMQA0E2MAaCbGANBMjAGgmRgDQDMxPpZU\nPTVVB1L1pu5NeUDVs5dtelX3pgA8Wh2bMa66dAnAJd2bAgAPt2MzxtPo3gAAOBqO1RhX9wYAwNFy\n5BhXnZCq+1L13k3Tn5Cqvcvl5BdvmveyZfpPLT+fnarfS9WNqborVfem6tZU/U6qTt607LuTrI2Z\nvnlZz4FU7U/Vmete95hU/VyqbkjV3am6J1V7UvXyVNWmdR4ci606K1V/nqo7l3U+a3u76iir+sZU\nXbPsry+m6r2p+r5Nr3liqi5P1XWpuj1VX0rV/6Tqb1J13hbrPZCq61N1aqquStUdy9/jTQ/8fW1v\n+x6fqquX9V35YD4qwPHusUd8xRj3pOqDSZ6RqhMyxj3LnPOTPC7zcvLuJG9dt9TuZfq7lp9/JskL\nkvxjkndmHgSck+SyJBek6tx16/2TJP+b5PlJrkly49qWJPlckqTqsUnenuT7k9yyvPfeJM9JcmWS\nZyS5dMWneVqSDyb5aJK3JPnyJJ8/4j44+r4uyQ1J/j3JHyU5PcmPJfn7VP1Exnjb8rpdSV6buV/f\nnrnfzkzyw0kuTNUPZYx3rFj/yUnen+RLSd6W5PFJfiTJm1K1P2P86WG3bh5AXZvkO5O8MmP89oP4\n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\nMQaAZmIMAM3EGACaiTEANBNjAGgmxgDQTIwBoJkYA0AzMQaAZmIMAM3EGACaiTEANBNjAGgmxgDQ\nTIwBoJkYA0AzMQaAZmIMAM3EGACaiTEANBNjAGgmxgDQTIwBoJkYA0AzMQaAZmIMAM3EGACaiTEA\nNBNjAGgmxgDQTIwBoJkYA0AzMQaAZmIMAM3EGACaiTEANBNjAGgmxgDQTIwBoJkYA0AzMQaAZmIM\nAM3EGACaiTEANBNjAGgmxgDQTIwBoJkYA0AzMQaAZmIMAM3EGACaiTEANBNjAGgmxgDQTIwBoJkY\nA0AzMQaAZmIMAM3EGACaiTEANBNjAGgmxgDQTIwBoJkYA0AzMQaAZmIMAM3EGACaiTEANBNjAGgm\nxgDQTIwBoJkYA0AzMQaAZmIMAM3EGACaiTEANBNjAGgmxgDQTIwBoJkYA0AzMQaAZmIMAM3EGACa\niTEANBNjAGgmxgDQTIwBoJkYA0AzMQaAZmIMAM3EGACaiTEANBNjAGgmxgDQTIwBoJkYA0AzMQaA\nZmIMAM3EGACaiTEANBNjAGgmxgDQTIwBoNn/A8+E5csToBBxAAAAAElFTkSuQmCC\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -760,14 +866,16 @@ "cell_type": "code", "execution_count": 18, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAd8AAAFBCAYAAAA2bKVrAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAADDFJREFUeJzt3XuMpXddx/HPVwgEKBRLRCkqpmBka1KwF4sSevlDbcFy\nS1XQSCGGoEFFKU1j0lAUQkzEVCIJWoWQGC6RkiBFCahtRfAGFmJKVQwGU6liBVtK223L7s8/fs+0\np2dndmfs7nfW7uuVnJzM7zzzXM7Z5H2e22yNMQIA9Pmm3V4BADjWiC8ANBNfAGgmvgDQTHwBoJn4\nAkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgm\nvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCa\niS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWA\nZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGi2rfhW5SlV\n2V+Vdx7pFdquqpy9rNPrd3td1lXlF6vyuarcWZV9VXnNsq7X7Pa6AbD7Hr7bK/BQU5WXJPmtJNcn\nuSLJ3iR/k2QsDwCOceJ7+D0vM7LPGyNf3hisyp4kd+7aWgFw1BDfw+/EJFkN7/Lz53dndQA42uz4\ngquqfE9VPliVr1Tl61X5y6r80No0j6vKJVX586rcVJW7q/JfVfmjqjxri/nur8o1VXlCVa6sys1V\n2VuVG6ry8h2s3yOrctUyv9/e6fb9X1Xl8qrsT3JuklqWv78q+5bXDzjnW5U3LONnVeXCqvxtVe5Y\n3tv3Vs2Qr/3OqVV5a1U+u0x3V1U+X5W3VOXxm0x/0bKMl1Xl3KpcW5WvVeW2qny4Kk/fYnseVZVL\nq/KpZfrbq3Ljsuxv2WTaX6nKZ5Z/E7dX5a+WQ/AArNnpnu9JSf46yT8k+Z0kT0ryE0k+UpWXjpH3\nL9PtSfKmJH+R5MNJ/ifJdyZ5fpLzq/KjY+Rjm8z/8Uk+meTuJO9P8sgkP5bknVXZN0b+4GArt8Tn\n6iQ/kOTSMfIbO9y+B+PazMPNr8jc1jckqRz8PO/GeeBXJ7kgyYeSXJfkzMz39ZSqPHOM3LvyO69M\n8sLM9/ZPM79AnZbktUnOq8qZY+SOTZZzQZIXJPmTJG9PcnLmIfLTq3LyGPnqxsTL+3hdklOS/FOS\ndyS5J8lTk7w8yQeS3LJMe/yy7c/IPM/9jmWdfiTJe5Z5H3UXxQHsqjHGIR/JeEoy9idjXzJ+fe21\nU5NxTzK+kozjlrHHJuOETeZzYjK+lIzPbfLaxvx/Nxm1Mr4nGfcm44a16c9efuf1K+t4YzL2JuMl\n29muI/FIxrXJ2LfF9l2zNnb5Mn5rMk5ee+3dy/tx4dr4d6y+Pyvjr1jmdcna+EXL+D3JOGfttTcv\ny3jd2vh7lvG3bbKcRyfjsSs/v2uZ9uK16R6RjI8k4xvJOGW3Pg8PDw+Po/Gx08POtyV54wPjneuT\nvDtzr/VFy9jtY2VPamXam5NcleTpVfn2TeZ/Z5KLx7h/b3GM/GPm3vCeqjx6s5WqyjMy98iflOS8\nMfK+HW7XbnvrGLlxbez3Mvecv391cIzctPr+rHhXkq9l7nFu5r1j5Lq1sSvXl7EcUv7xJP+R5JL1\nmYyRO8fI7cu0JyT5qSSfHiO/uTbdPUkuzdwL/skt1gngmLTTw87XjwMPaSbzEOVFSb4vmYeGq/Ls\nJK9J8qwkT0zyiJXpR5InJ/n3tfn8yxj5+ibzv2l5/uYceMXwc5JcnBme54yRG7a7MUeJkeTvNxlf\n3eb7VOXhSX4287D0yUmOzwPP3T95i+VsdxlnLPP7+Bi566BrPqd9WJJRlcs3eX3jM99ziPkAHFN2\nGt8vbzH+n8vz8UlSlRdlnrO9K/O85BeS3JHcd0HSWZnnc9fdusX8v7E8P2yT156Z5LjMveN/Pvjq\nH7U22+6ttvkPM8/5fiHJBzPf+7uX1345m7+vY7NljJF9VQcsY+OirS9tY72fsDyfsTw2M5I8Zhvz\nAjhm7DS+37rF+Lctz7ctz2/MDMJpY+0Wm+UK3rN2uNyDeVvmnvXPJbm6Ki8cI3sP4/yPGlU5LTO8\nH0vy3DGyf+W1yjzM+2BtRHqrPehVG5/3FWPkdYdh2QDHhJ2e8z21atO9mHMz93CuX35+apIbNwlv\nZR4mPpzGGHl15l+V+uEkf7zVueGHgKctz1evhndxZpJHHYZl/F3mEYqzqg45v41pD/dnCvCQttP4\nHp888NxeVU7PvKDm1szDoEnyxSTfXXXfHvGGX80ROv83Rl6b5M2ZXwQ+WpXjjsRydtkXl+dzVger\n8sTMIwAP2hj57yTvy/xjIW9ZvjCtLusxVXncMu0tmRfbnV6Vy6oO/PdUlZOq8l2HY90AHip2etj5\n40l+pipnZp5jPTHzythK8qqVi6WuyLyX9LNV+UCSe5M8OzO8H8q85/SwGyOXVWVvkl9L8mdVOW+M\nLc8j/3/0qcz3/cVV+WSST2SeCjg/837cm7f4vdpifCs/n+R7My/sOrcqH828z/ekzKMLF2T+W9iY\n9mmZX6x+uiqfyLw24MTMz/v0JC/N/V8cAI55O9nzHUn+NckPJvlqklcluTDJp5OcP0auum/CkSsz\n/9jEzUlelrln/G+Zh0Y/c5D5H+oPUhzyd8bImzJvkTkjM8AnHGrDjoBtres257N629X+zPC9PfO2\nql/I/FJzZeYtRvceZNnbWsaynFszP+fLMqP7yswQ70ny+8n9t0Uttx2dvazLLUlenHnh1zmZV6D/\nUuZFdwAsagz/0Q4AdNrx33YGAB4c8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWA\nZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwB\noJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNf\nAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3E\nFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz\n8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQ\nTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8A\nNBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuIL\nAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4\nAkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgm\nvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCa\niS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWA\nZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwB\noJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNf\nAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3E\nFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz\n8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQ\nTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8ANBNfAGgmvgDQTHwBoJn4AkAz8QWAZuILAM3EFwCaiS8A\nNPtfmq3Vvt7+mtoAAAAASUVORK5CYII=\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAd8AAAFBCAYAAAA2bKVrAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAADCdJREFUeJzt3HuspHddx/HPVwgEKBRLRFhUTMHI1qRgLxYl9PKH2oJV\nIFVBI4UYggYVpTSNSUNRCDERU4kkaBVCYrhESoIUJaDSiuANLMSUVTEYTKWKFWxZ2m5bdn/+8XsO\ne/bsnN1z7O73LN3XK5lMzm+eeS4zJ3nPc5mpMUYAgD7ftNMrAAAnG/EFgGbiCwDNxBcAmokvADQT\nXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDN\nxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJA\nM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A\n0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNthbfqien\n6kCq3nac12frqi5Y1uk1O70qh6n6pVR9JlV3p2p/ql65rOtHdnrVANh5D93pFXjQqXphkt9OcnOS\na5PsS/K3ScZyA+AkJ77H3nMzI/vcjPHFr49W7U5y906tFAAnDvE99nYlySHhnX9/didWBoATz/Yv\nuKr67lS9L1VfStVXU/VXqfrBDdM8JlVXpuovUnVrqu5N1X+n6o9T9cxN5jvPiVY9LlXXpeq2VO1L\n1S2pesk21u/hqbp+md/vbHv7/r+qrknVgSQXJall+QdStX95/PBzvlWvXcbPT9Vlqfq7VN21vLbv\nStWuFcs5K1VvStWnl+nuSdVnU/XGVD12xfSXL8t4caouStWNqfpKqu5M1QdS9bRNtucRqboqVZ9Y\npt+bqj3Lsr9lxbS/mqpPLf8Te1P118sheAA22O6e7+lJ/ibJPyb53SRPTPKTST6YqhdljPcs0+1O\n8vokf5nkA0n+N8l3JPnRJJek6kcyxodXzP+xST6e5N4k70ny8CQ/nuRtqdqfMf7wiGs343NDku9P\nclXG+M1tbt8DcWPm4eaXZm7ra5NUjnyed+088CuSXJrk/UluSnJe5ut6ZqqekTHuX/eclyV5XuZr\n+2eZH6DOTvKqJBen6ryMcdeK5Vya5MeS/GmStyQ5I/MQ+TmpOiNjfPnrU8/X8aYkZyb55yRvTXJf\nkqckeUmS9ya5fZn21GXbn555nvutyzr9cJJ3LvM+8S6KA9hJY4yj35Inj+TASPaP5Dc2PHbWSO4b\nyZdGcsoy9uiRnLZiPrtG8oWRfGbFY2vz/72R1Lrx3SO5fyS3bJj+guU5r1m3jntGsm8kL9zSdh2P\nW3LjSPZvsn0f2TB2zTJ+x0jO2PDYO5bX47IN499+yOtzcPyly7yu3DB++TJ+30gu3PDYG5ZlvHrD\n+DuX8TevWM4jR/LodX+/fZn2ig3TPWwkHxzJ10Zy5o69H25ubm4n4G27h53vTPK6DfW+Ock7Mvda\nn7+M7c36PamD096W5PokT0vVt62Y/91JrsgYY91z/ilzb3h3qh65cq2qnp65R/7EJBdnjHdvb7N2\n3Jsyxp4NY7+fuef8fYeMjnHrIa/PQW9P8pXMPc5V3pUxbtowdt1hy5iHlH8iyX8mufKwuYxxd8bY\nu0x7WpKfTvLJjPFbG6a7L8lVmXvBP7XJOgGclLZ72PnmHH5IM5mHKC9P8r1J5qHhqmcleWWSZyZ5\nfJKHrZt+JHlSkv/YMJ9/zRhfXTH/W5f7b87hVww/O8kVmeF5dsa4ZYvbcqIYSf5hxfj6bT6o6qFJ\nfi7zsPQZSU7Noefun7TJcra6jHOX+X00Y9xzpBVfpn1IkpGqa1Y8vvae7z7KfABOKtuN7xc3Gf+v\n5f7UJEnV8zPP2d6TeV7yc0nuSrJ2QdL5medzN7pjk/l/bbl/yIrHnpHklMy943854tqfuFZt92bb\n/EeZ53w/l+R9ma/9vctjv5LVr+tYuYwx9qdq4zLWLtr6whbW+3HL/bnLbZWR5FFbmBfASWO78f3W\nTcafsNzfudy/LjMIZ2fjV2zmFbznb3O5R/LmzD3rn09yQ6qelzH2HcP5nziqzs4M74eTPCdjHFj3\nWGUe5n2g1iK92R70emvv97UZ49XHYNkAJ4XtnvM9K1Wr9mIuytzDuXn5+ylJ9qwIb2UeJj6WRsZ4\nReavSv1Qkj/Z9NzwN76nLvc3HBLe6bwkjzgGy/j7zCMU56fqaPNbm/ZYv6cAD2rbje+pSQ49t1d1\nTuYFNXdkHgZNks8n+a5UPSGH+rUcr/N/Y7wqyRsyPwh8KFWnHJfl7KzPL/cXHjJa9fjMIwAP3Bj/\nk+TdmT8W8sblA9P6ZT0qVY9Zpr0982K7c1J1daoO/3+qOj1V33lM1g3gQWK7h50/muRnU3Ve5jnW\nXZlXxlaSl6+7WOrazO+SfjpV701yf5JnZYb3/ZnfOT32xrg6VfuS/HqSP0/VxRljs/PI34g+kfm6\nvyBVH0/yscxTAZdkfh/3tk2eV5uMb+YXknxP5oVdF6XqQ5nf8z098+jCpZn/C2vTPjXzg9XPpOpj\nmdcG7Mp8v89J8qIc/OAAcNLbzp7vSPJvSX4gyZeTvDzJZUk+meSSjHH9wSnHdZk/NnFbkhdn7hn/\ne+ah0U8dYf5H+0GKoz9njNdnfkXm3MwAn3bkzToutrauW5vP+q9dHcgM31syv1b1i5kfaq7L/IrR\n/UdY9taWMZdzR+b7fHVmdF+WGeLdSf4gyZ510+5NcsGyLrcneUHmhV8XZl6B/suZF90BsKjVXxkF\nAI6X7f+2MwDwgIgvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8\nAaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQT\nXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDN\nxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJA\nM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A\n0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokv\nADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbi\nCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ\n+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBo\nJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcA\nmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EF\ngGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8\nAaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQT\nXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDN\nxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJA\nM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A\n0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokv\nADQTXwBoJr4A0Ex8AaCZ+AJAM/EFgGbiCwDNxBcAmokvADQTXwBoJr4A0Ex8AaDZ/wGxhdW+xeQK\nwAAAAABJRU5ErkJggg==\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -781,23 +889,28 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ - "What is fun to note here is that while bank was colored red in our first example, it is now blue because of the financial context - something which the numbers proved to us before." + "What is fun to note here is that while bank was colored blue in our first example, it is now red because of the financial context - something which the numbers proved to us before." ] }, { "cell_type": "code", "execution_count": 19, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAApgAAAFBCAYAAADT6N+zAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAG05JREFUeJzt3Xm0ZFddL/Dvj0kgmoQQIqImzDK4giRgQDSE8QEyRMQn\noJCwUAQCiAOCT2QQluITH/KMPuYggzLKGA1CBsYACSHPB5EEglFIEAgJISOQ7v3+2Ke6q+vW7b7d\n2enb3Xw+a9Wq7n12nTrn1D67vlVn177VWgsAAIxynfXeAAAA9iwCJgAAQwmYAAAMJWACADCUgAkA\nwFACJgAAQwmYAAAMJWACADCUgAkAwFACJgAAQwmYAAAMJWACADCUgAkAwFACJgAAQwmYAAAMJWAC\nADCUgAkAwFACJgAAQwmYAAAMJWACADCUgAkAwFACJgAAQwmYAAAMJWACADCUgAkAwFACJgAAQwmY\nAAAMJWACADCUgAkAwFACJgAAQwmYAAAMJWACADCUgAkAwFACJgAAQwmYAAAMJWACADCUgAkAwFAC\nJgAAQwmYAAAMJWACADCUgAkAwFACJgAAQwmYAAAMJWACADCUgAkAwFACJgAAQwmYAAAMJWACADCU\ngAkAwFACJgAAQwmYAAAMJWACADCUgAkAwFACJgAAQwmYAAAMJWACADCUgAkAwFACJgAAQwmYAAAM\nJWDujqpOSdXG9d6M3UlVDqrKxqq8br23ZaYq95626XnrvS3sZqqekarPp+qKVG1M1TPWe5PYM1Xl\nGVX5fFWuqMqGqvz21G+dtN7bxrWnKkdNr/PjF8rPq8qX17KOXTNgVh01dZqP33blH0gtiYDJrqfq\noOnc3WWC/B6n6tFJ/irJlUleluQFST55LT7fvafX1AehHzBVWa2ttenGnm3Za7zm1/16AzdkNI13\ndY9LcuP13ghgXfxiev/4i2nt6+u9MezRNrW11rKprVXljkmuWLetYrewqwbMWu8N2KW19tX13gRY\nhXP32neLJNmJ4dJr+oPrFkkyHy6n/5+zPpvD7mTbl8ir9krV91L10YXyG6bqqunSya8tLHvKVH70\n9P9DUvXyVJ2Zqm+l6spUnZOql6Zq34XHnpxsGif3+mk9G1O1IVUHztW7bqqemqpTU3VJqi5P1Rmp\nOiZVtbDOzZftqm6Xqrem6uvTOg9f26HaiVbf3qtTdfiKMZhVvzrV/8tV1neDVF2cqvNTdZ2FZY9J\n1cnT8itTdVaq/ihVN1iyno2pOilVP5qq16Tqq9M27VZDGaryU1V5d1W+VZXLqvLRqjxgoc7eVXlW\nVU6syleq8t2qfKMq76nKPVZZ78aqnFSVm1blVVW5oCpXVeVzVTl6O7bvh6ryjml9f30Nd3fnqXp+\nki+nf+Nx9Ny524e7zF9qrbp7qo6f+oPFc/vHU3Vsqs6d+pgLU/WeVN1tledde1+wO6t6/nTe3ydJ\nbdE39uVHpuqNqTo7VZdNt9NT9fSlx6HqgKkP/sJU9+Lp38el6pZTneOSnJT+mr5goT/epfrO+XHW\nVbn1dA5dWJXvVOUDVbnzVG//ufPzyqp8uipHLFnf3lX5s6p8Yap3UVVOqMr9ltTdNJ66KnepyvFV\nubgql1fllKrcc5Vtvm5VnlqVU6tyyVT/jKocU7U52E991saqnLiV/f9/Uz/1ozt0ALdc1/Orsqmt\nTc+9sSobpuUrxmBW5QVT+eFVeVRVPjXtz7eq8g9V0wejLR9zSFVeXpUzp3pXVuWcqry0Kvsuqb9p\nXGBV7lOVk6fX95KqvL8qd1hlf25UlWdX5bSp/qVVOWt67pstqfuHVfns9P5waVU+MQ0X2CVV5eHT\ne9XsPef8qd09ZaHeTaY2fVb1MbXfrsqHauH9b5Rtf4PZ2uWp+lSSn03VXmnt8mnJvZLcIL3juV+S\nN8896n5T+Yem//9mkiOTfDjJB9OD7aFJfjfJg1J12Nx6j0tycZJHJHl3kjNnW5Lk20mSqusleX+S\nByb5wvTcV6WfDH+d5GeTHLVkb26b5FNJzk7ypiQ3SvKdbR6D9bO4vTdM397F8S/vTnJJksem6llp\nbXF85pFJ9knyqi2W9XFyRyf5SpJ3pB/feyR5UZL7puoBS9a1X/oYnEuTvDN9LOjudJnu1klOTfKv\nSV6R5MeS/GqSf67KY1rL26d6d0zy4vQ2+/70NnlgkocneXBVHtpa/mXJ+vdN8vEk303y9iQ/lORX\nkryuKhtayxu3tnFTp/q+JPdM8uzW8hfXZGd3spPT29kz08/bd88tOzPJTaZ//1yS/5Hko0lem2T/\nJN9L0j+MJv+Sfhw/kN7G9k9vwx9L1ZFp7YRNa93xvmB3dHL6ef+E9Lb4gvRvF2d9wZ8l2ZB+fp6f\n/lrcN8nLk9wt88eh6kZJPpHkVul98nundR2U3sbfnuS8JO/K7ANDcsp0mzlv4L6NdKv0fvOs9PeT\nWyZ5ZJKTq/JzSU5I7y/fkt6fPSbJP1Xl9q3lq0lSlX3Sj88dkpyW5B/T2+F/T/IvVXlya3n1kue+\ne5JnT499dfrr9KgkH6rKz7SWL84qVmXNbbe1nF2Vk5McUZXbtpYvzT/ptF93TvL2xW8bd9C22toy\ns/elY5I8LL1NnZLksPQ+9uDpGHx/7jFbzQZVOay1XJ4ttWn9j0jyT0n+T5I7pV/Ov1tV7tRaLppV\nnvrUU5IcnH6cX5ve39wmvV2/M8k3p7r7TPt+lyRnTHWvk+S/Jfn7ad271Fjkqjwp/b3sa+nH/MIk\nB6Tv79HpxydVOTD9OB+Y3vf+c5K9kjw0yQlVeVJree3QjWutbfuWvLAlG1ry4LmyP23J91rywZb8\nx1x5teTClnxxruwnW1JL1vuElmxsybMWyo+anu/xq2zPC6bH/dUW6+3P/ZrpsQ+bKz9oqr+hJS9a\n0z6v521b25uc3JINC2WvmOo/ZEn946dld54rO3p6jre35AYL9Z831X/6Qvlsm45ryXXW/Thtxy1p\nByVtY9I2JO0lC8sOSdr3kvatpP3wVPYjSdtvyXpukbTzk/b5Jctm639l0mqu/I5J+37SPrdQ/97T\nY543t41nJe2qpD16vY/ZNWy7r1uy7N5zbeg3liy/bku+1JIrWvLzC8tu3pKvtuT8llx/rnz7+oI9\n4bbs/O/lt1ql/uun43D3ubKHTsftpUvqX68ley153Z637vu+ldvCOf6chWXPnZZ9K2l/s7Ds16dl\nfzlX9sqp7G8X6t4mad9O2pVJO3Cu/N5zz/24hcc8aVp27EL5C6byv1roLyppr5nW9bC58l+e6v/P\nJfv++qn+fQcf05OTtqKtTdtx0kLZ86fybyftTgvL3jxt36MWyn9yft/nyp8wretZC+VHTeXfS9oR\nC8v+dHqO318o//up/Nglz3PjpP3IkuP4ewv1bpC0f07a1Uk7eL3b+sK2nT61x5suWbbf3L9Pmbb/\nVxbq7J20zybt8qTdbOFYb0ja4xfq/3vSvrymbVvTTiSHr+iMkk+15NSWPGXqvG47ld91qvuKNay3\nWvLtlnxoofyotlrA3Bxgz18acpJ9pse+Za7soGmbLtjizWlXvW1re5cHzHtOj3nrQvmPtuT7LTlt\nofyzLfluS/Zesv7rtOSbLfnkQvnGllzZkv3X/Rht523uzeeipO21ZPlxy94cVlnXy6e6P7FQvjFp\nl85C6sKyU6bH3HiubPam9Lyk3SVpFyTt4sWOc7e6rS1gfmaVxz58Wv7nqyx/xnRuP2j6//b3BXvC\nbbWAuXr9Q6bj+ty5slnAfPEaHj973XaXgHnuYmiZgszs/NxrYdl1psBy4vT/6yftsqRdkrR9lzzP\nn0zn8nPnymbn8oeX1L/etP5Pz5VV0i5M/7C6ou0mbZ/pOd4yV3bdqf43knb9hbqXJ+2ca+GY7kjA\nfOGS+kesFo5Xed6aguqHFspnAfPvljzmltOyt82V3WwKVV9N2o228Zz7pX8R8KlVlh88rf8la9mH\nndjuT5/a9Yq2umTb37rK8odP7e3JC8f6GgXMtf7I59T0aQr62JOqvZMckuQl6V8n17TsS9l8eXzz\n+Ix+GevJ6V+T3yn90s38WMAfX+N2JMnt0y9rnJPkj1MrhhbVtK13XPLY/5vWvr+kfFe19u1t7dRU\nnZPkYanaJ61dMi359fRj/fpNdfvlsYPTLwv8zirH8LtZfgzPS2sXbsc+7GrOaCsvuST9EspRSe6a\n9MvYVblXkt9OHzZwQPqQkJmW3m4Xf3D1xdZy2ZL1f2W6v0lW/vryF5L8Xvrwh19oLZ9b687spj69\nSvlsnNotp/Gci26X3jbvmH6Z85r0BXueqv2S/EGSB6cPBdlrbumsvc58OP0y+nNSdWj6pcaPJzkz\nK4fF7G7ObG3FpdwLpvtzFs//1rKxKl9P8hNT0U+lz9LxsdamYVlbOinJc9P7ikWfWSxoLVdP67/J\nXPEWbXdl013ZdlvLhqq8OskfJ/nl9Ev8SfL49OFer1yyPTtby5JjkC37v02mYQI7kg3W+hx3n9b3\nkdZy5Va3vNe9bpJWlWX9z6z/39X6kzcneWmSs6rylvRz++OtZf59eta37rPKvh2QzX3rMGsLmK19\nP1UfS3K/VN00yc+nv2gnprUvpOpr6cHylVkWMJO3pY+zODd9XNZ/pQeYJPmd9HFqa3XT6f52yVbH\nQuy1pOy/tuN5dgXbu71/lz5u8NHZ3NkcleT7Sf5hrt5N0hvTzbL1Y7jYSe/INu1qVhufNNuvfZKk\nKr+UPg7tyvSxQecmuTzZNPD98Cxvt8vekJLk6un+ukuW/UySH05/gz9765u/R1itDc3O7Udt5bEt\n/VjN19+RvmDPUrVPktPTx1B+Or0vuCi93e2bPi52c3tt7dJUHZbkheljLh+Y3idcmKq/TfLitHZ1\ndk+XLBZM4WzpssnVSa4//Xuf6f5rq9Sdla/4EUq2fv7Pn/s72nZfleSPkvxWNgfMJ6W/n75+K+vZ\nmZYdg9X6vx3JBm3Zc8y9xvPPMXuNzl/Dds9ek7tPt2VadrH+pLW8rCrfTPLUJE9P/1IkVflwkme1\nls9k8749YLotXVUG79v2TFN0UpL7pwfIe6UPRv7E3LIHpf/y+OeTfH7Tt1z90/GR6QP3H5Itf2RS\n6QOit8esg3hXWtvaG9EyywLTrmx7t/eN6T/QOSrJK1N11yQ/nX6sLpqrNzuGn01ry3+ZO26bdjWr\n/cLy5tP97Ni8KL2jO7QtTMkx/Rpy5C9oj03/BPmUJO+rypGt5aqB69/VrNaGLpmWPTytHb+G9VyT\nvmBP85vpP2Z5flp70RZLqu6RHjC31NoF0+N+M1V3TP9B0DHpgaeSpd90/CCYtaubr7L8xxbqXZPn\neFdrW/1AtYXWckFV3pvkyKrcPv2HR3dO8g+t5VvXYHt2uqpskQ1a2/zHQ6Zf0G9vNlhmFkTXcpV0\n9pq8rLX8/oDn3mlay5uSvKkqe6f/iPKXkjwx/cc7d8jmffvt1nLsztqu7flLPiemdzr3T++IPpHW\nvje3bL/0N8i9pv/P3Ha6f9+SSy+HpX+1v2jD9FzLvu35Qma/dq5atvwHV58f86Qkh6XqdulBs6V/\nmzFf7/Ikn09y5yxOE7XnO6Rq6ae0+6QfqzOm/98myVlLwmWlX9IeqbWWY9L/YsYDkxxftdtOpL9h\nut+Rc/OT6ef9WsO7vmCz26S3339csuyIbT66tX9La3+T3v6S/sY/c01e093R2enDWO4yvWEvuu90\nf8aSZWu1qe1Wbfdx/dv08+TJ6R8QWnaNy+Pba1M2mA+Xk9Wywfb6dPpVp8Ortrm+Wd3R/ftO01q+\n01pOaC2/lf6N9n7p/ensL33t1H3bnoB5RnoKfkT6WIn5EHlSeoP/w6y8PH7edH/EFmurOiBZNUnP\nPokduGJJaxvSp3C4RZK/TtUNV9Spuvn0ifwH0eun+99Iv1R+YZJl3wb9r/TLD8dNl9e2VLXv9A3o\nnmafLHwzU5W7JXlseoc/m1rnvCS3q1rxLcYLcy2NwWktv5vkT9PD7geqNl0K3p1cnN4HrDx3t+09\n6ZfKjknVg5fWqLrHpnNeXzDvvPQ++IgtSvs5/JwsfmtcdaepD140a+/z4xRX74/3QK1Po/PmJHun\nX8nYpCq3SfKM9Glutjrl2DaeY4u2W5UVbbcqN69a2de0lhPTx24elT5t0tmt5SM7ui3r6Lzp/oj5\nwqpsLRtsl2kc4lvSj/NLq7b8owFV2Wv2IaK1fDP9db9bVZ5btTIfVZ9f9ZYjtm2UWjKH62R2te6K\n6TL5R5M8sipPWGU9P704J+g1tfZL5K1tTNUp6QGzZT5gtvafqTo3/VP01emDTGdOSx9b9shUfTzJ\nx9J3/MHpn+IuyEqnpn+CfGaq9s/mMVv/O61dmn7SH5w+DuVhqTopfYzFAeljWu6VPs/ev615//Yc\n70qfo/KZ6WOKXj69EW+pteOmOQefmuTcVH0gyX+mf+K5VfqnntdNy/ckH0nyxKoclt4ub5HeSVeS\n35r7gc7L0ucPO7Mq70wfx3qv9HD53vR52IZrLc+tylVJ/iR97rwHrfJDg13T5nlzfyFVb0p/I9yQ\nfsy29dirU/XI9B/wHJ+qT6TPn3lFkp9MHxd1q/RLlLMhBPqC7g1JnpXk5am6b5Ivpu//Q9Pn+Vuc\nJPoBSf4iVaemv0bfSP+RyyPSX6/5+VfPTj+mj07V1Un+I/094A1p7SvZMz0n/duep1XlZ9N/zHqz\n9DltfzjJMa3lP67hc2zRdqtPXL7WtvuK9C8JdtdvL5O5bFCVtWaDZPv/stTT0ocRPDnJfarygfQP\nCLdO/8b+YcmmgP609G9WX5jkcVX5WPq4/Vuk9/13S5839bzt3IZr07uqcln6t5TnJZuust09/RjP\n5iN/bHpue01VnpE+V+y308/7g9OP0T0zzQk6uWZ/qGK7fhKfPG2a9uOitjiv5eZ5GD+x5HH7tuTY\nlny59TnuvtiSF7Xkhi3595acu+QxD2zJx1vynWm9G1py4EKdX2t9Hs4LW3JVS77Sko+05Nkt+fG5\negdNj3/tek8psMbjvPXt7dOUXL2Vx796evzVLbnrNp7rIS15b0v+azqGF7Tkk63PfXr7hbobWnLi\nuh+fHbilT2GyIWmvTdpPJe1d6XPiXZa0jyTt/kse8/iknTFNAfGNpL0jaXeepuPYkLTDF+pvyDTV\nyZJ1HTdNl7E4d96GpP3xkvq/Ny07PUvm49ylb8mtW/Ke1qe6urrNphzr091saMmK/V14/P6tz7P7\nry25bOoDzm7J21rymLZ8SqK19QV7wm218z+5Q0vePZ3Ll7bktNbnGj5oRX/S6760JZ9uyddbn37s\nyy15a0vusWTdh07H9+K51/Twa20fd+A2f46vsnxr5+e/J+3chbK9k/ZnSTs7fZ7Bi5J2QtLut+Tx\nq57Lq61/btmvJe2D6dMWXZW0r0x90rOTtrTtJm3fqT+5PGk3uRaP6clJW9HWlh3L1frFrb02034c\nm7QvJ+2KpH0xaS9K2g1XeU2OypKpc7b1GiftRkn7w6Sdmc1TUH0uaX+ZtP0X6l4vaU9N2sfSp427\nMmnnTa/R06/N472Dr9GTkvbOpH1p2rcLk/aZ6T1kcUquvZL2nKSdlrTvTO3n3KS9L2lPzNxUTqsd\n66215cVb9QcAALuD6bLoSUne0Nra/wwt7EzbMwYTAFh/f5B+eXyn/SIYttf2TFMEAKyDqvx0+njB\nQ5M8KMl7W8vp67tVsDoBEwB2fYem/yGN7yR5a/qcpbDLMgYTAIChjMEEAGAoARMAgKEETAAAhhIw\nAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEE\nTAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAo\nARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAY\nSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAA\nhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMA\ngKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAE\nAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIw\nAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEE\nTAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAo\nARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAY\nSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAA\nhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMA\ngKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAE\nAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIw\nAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEE\nTAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAo\nARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAY\nSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAA\nhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMA\ngKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAE\nAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIw\nAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGAoARMAgKEETAAAhhIwAQAYSsAEAGCo/w+PEQc7\nVDsuewAAAABJRU5ErkJggg==\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAApgAAAFBCAYAAADT6N+zAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAG1hJREFUeJzt3Xm0ZVV9J/DvT9SoGEDEIZiA87wwjmhUQI22Jk4xpqMx\nAi4To+KUGFrTMQ7RpemOttISW+OASTTRGOMUE0fAERVFOq1EUBSj4IQgMypVu//Y51Xduu++qveo\nTb2q8vNZ665bdc4+491n3++9Z9/9qrUWAAAY5WrrvQMAAOxeBEwAAIYSMAEAGErABABgKAETAICh\nBEwAAIYSMAEAGErABABgKAETAIChBEwAAIYSMAEAGErABABgKAETAIChBEwAAIYSMAEAGErABABg\nKAETAIChBEwAAIYSMAEAGErABABgKAETAIChBEwAAIYSMAEAGErABABgKAETAIChBEwAAIYSMAEA\nGErABABgKAETAIChBEwAAIYSMAEAGErABABgKAETAIChBEwAAIYSMAEAGErABABgKAETAIChBEwA\nAIYSMAEAGErABABgKAETAIChBEwAAIYSMAEAGErABABgKAETAIChBEwAAIYSMAEAGErABABgKAET\nAIChBEwAAIYSMAEAGErABABgKAETAIChBEwAAIYSMAEAGErABABgKAETAIChBEwAAIYSMAEAGErA\n3AVV5cSqbFzv/dilVB2Yqo2petN678omVYdO+/T89d4Vdi1VeUZVvlyVS6uysSrPWO99YjdV9YxU\nfTlVl6ZqQ6qeObVbx6/3rnEVqjpiep0Pn5t+Vqq+vppV7JQBsypHTI3m4dsu/TOpJQImO5+qHDhd\nuztPkN/NVOUxSV6V5LIkr0zywiSfuQq3d+j0mvog9LOmaqW61qYHu7dFr/GqX/erD9yR0VTelT0+\nyXXWeyeAdfHr6e3jr7eW7633zrBb21TX0trmulZ1uySXrtdOsWvYWQNmrfcO7Mxay7fXex9gBa7d\nq97+SbIDw6XX9GfX/kmyRbjs/z9jPXaGXcs2b5FXZc+q/KQqn5ibfq2qXD7dOnnc3LynTNOPnP5/\nl6ocU5VTq/LDqlxWlTOq8vKq7DO37AnJpttrb57Ws7EqG6pywEy5Pary1KqcVJULqnJJVU6pylFV\nWzaIs7ftqnKrqry9Kt+b1nnIms7YDrCV/b2iKofM98Gsym9P5V+xwvquWZXzq3J21ZaveVUeW5UT\npvmXVeW0qvxpVa65YD0bq3J8VW5UlTdU5dvTPu1aXRmqbpOqd6fqh6m6OFWfSNUD58rslaqjU/XR\nVH0rVT9O1fdT9Z5U3XOF9fZ+SVXXT9Vfp+qcVF2eqi+l6sg17N/PpeqfpvW9ensOdUeqyguSfD39\nG48jZ67djVU5fPZWa1XuXpX3T+3B/LV9k6ocW5Uzpzbm3Kq8pyp3W2G7q24LdmVVecF03d8vSc22\njdP8R1bl76pyelUunh6fr8rTF52HqtxwaoO/MpU9f/r3cVW56VTmuCTHp7+mL5xrj3eutnO2n3XV\nzadr6NxUXZiqD6XqjlO5/VL1hun6vCxVJ6fqsAXr2ytVL0vVV6Zy56XqA6l6wIKym/tTV90pVe9P\n1fmpuiRVJ6bqXivs8x6pemqqTkrVBVP5U1J1VKpqptxtpvV/dCvH//+mdupGazxzi9b1glRtqmvT\ntjemasM0f3kfzKoXTtMPSdWjU/XZ6Xh+mKp/SNX+C7Zzl1Qdk6pTp3KXpeqMVL08VfssKL+5X2DV\n/VJ1wvT6XpCqf0nVbVc4nmun6jnTa31hqi5K1WnTtm+woOyfpOqL0/vDRan6dHp3gZ1T1cOn96ql\n95yzp3r3lLly15vq9GnpfWp/lKqPZP79b5BtfoPZWi6pymeT3KMqe7aWS6ZZ905yzfSG5wFJ3jqz\n2AOm6R+Z/v/7SR6Z5GNJPpwebO+a5I+SPLgqB8+s97gk5yd5RJJ3Jzl1aVeS/ChJqnL1JP+S5EFJ\nvjJt+/L0i+HVSe6R5IgFh3PLJJ9NcnqStyS5dpILt3UO1tH8/l4rfX/n+7+8O8kFSX6nKke3tqx/\n5iOT7J3kr2fnVe8nd2SSbyX5p/Tze88kL05y/6o8cMG69k3vg3NRknem9wXdlW7T3TzJSUn+Pclr\nk/xCkt9O8m+pemxae8dU7nZJXpJeZ/8lvU4ekOThSR6SqoemtQ8tWP8+ST6V5MdJ3pHk55L8VpI3\npWpDWvu7re5db1Tfl+ReSZ6T1v5yO451RzshvZ49K/26fffMvFOTXG/6968k+e9JPpHkjUn2S/KT\npH8YTfKh9PP4wfQ6tl96Hf5kVR7ZWj6wtNLtaAt2RSekX/dPSK+LL0z/dnGpLXhZkg3p1+fZ6a/F\n/ZMck+RumTkPVbl2kk8nuVl6m/zeaV0HptfxdyQ5K8m7pvUfmeTE6bHkrJEHN9DN0tvN09LfT26a\n5FFJTkjVfZL8a3pb97b09uyxSf41VbdOa/3uUNXe6efntklOTvLP6fXwvyb5UKqenNZev2Dbd0/y\nnGnZ16e/To9O8pFU/XJa++qmklWrr7utnZ6qE5IclqpbprWvbbHVql9Jcock71j2beOVs626tsjS\n+9JRSR6WXqdOTHJweht70HQOfjqzzFazQaoOTmuXZEttWv8j0l/L/5Pk9um38++WqtuntfM2le5t\n6olJDko/z29Mb29ukV6v35nkB1PZvadjv1OSU6ayV0vyX5L8/bTunasvctWT0t/LvpN+zs9NcsP0\n4z0y/fwkVQekn+cD0tvef0uyZ5KHJvlAqp6U1t44dN9aa9t8JO1FSduQtIfMTHtp0n6StA8n7Zsz\n0ytp5ybtqzPTfilptWC9T0jaxqQdPTf9iGl7h6+wPy+clnvV7Hqnbb9hWvZhM9MPnMpvSNqLV3PM\n6/nY1v4m7YSkbZib9tqp/K8tKP/+ad4dZqYdOW3jHUm75lz550/lnz43fWmfjkva1db7PK3pkRzY\nko0t2dCSv5ibd5eW/KQlP2zJdadpP9+SfResZ/+WnN2SLy+Yt7T+17WkZqbfriU/bcmX5sofOi3z\n/Jl9PK0ll7fkMet+zrav7r5pwbxDZ+rQ7y2Yv0fSvpa0S5N2n7l5N07at5N2dtKuMTN9TW3B7vBY\ndP1P02+2Qvk3T+fh7jPTHjqdt5cvKH/1pO254HV7/nof+1YfW17jz52b97xp3o9a8ldz8353mveK\nmWmvm6a9Zq7sLaZ1XNaSA2amHzqz7cfPLfOkad6xc9NfOE1/1Vx7US15w7Suh81M/82p/P9ccOxv\nnsrff/A5PaEly+ratB/Hz017wcw5vv3cvLdO+/fouem/tMWxb57+hGldR89NP2Ka/pOWHDY376XT\nNv54bvrfT9OPXbCd67Tk5xecx2fPlbtmS/6tJVe05KB1r+tb7tvnp/p4/QXz9p3594nT/v/WXJm9\nWvLFllzSkhvMnesNLTl8rvw3WvL11ezbqg4gaYfMN0ZJ+2zSTkraU6bG65bT9DtPZV+7ivVW0n6U\ntI/MTT9ipYA5E2DPXhRykrb3tOzbZqYtvemdM/vmtLM+trW/KwTMe03LvH1u+o2S9tOknTw3/YtJ\n+3HS9lqw/qsl7QdJ+8zc9I1Juyxp+633OVrzY/Obz3kt2XPB/OMWvjksXtcxU9lfnJu+sSUXtaWQ\nuuW8E6dlrjMz7dC2FDCTO7XknJacv6zh3IUeqwyYX1hh2YdP8//HCvOfMV3bD57+v+a2YHd4rBQw\nt1L+LtN5fd7MtKWA+ZJVLL/0uu0qAfPMZaGlB5ml63PPuXlXmwLLR6f/X6MlF7fkgpbss2A7fz5d\ny8+bmXbotP6PLSh/9Wn9n5uZVi05t/UPq8s/rCd7T9t428y0PVry7ZZ8vyXXmCt7SUvOuArO6Qlt\n7QHzRQvKH9ZWCseLt1utB9WPzE0/YlrP3yxY5qbTvH+cmXaDKVR9uyXX3sY29239i4DPrjD/oGn9\nf7GqY9hx9f7zU71eXleX7/vbV5j/8Km+PXnuXG9XwFztj3xOSh+m4AFJUpW9ktwlyV+kf51c07yv\nZfPt8U39M6bbWE9O/5r89um3bmb7At5klfuRJLdOv61xRpI/q+U9rGra19stWPb/tpafLpi+s1r1\n/raWk6pyRpKHVWXv1nLBNOt308/1m5fKTrfHDkq/LfCHK5zDH2fxOTyrtZy7loPYyZyS5bdckn4L\n5Ygkd07Sb2NX3TvJM9O7Ddww2aJfakuvt/M/uPpqWrt4wfq/NT1fL8t/fXnfJM9O7/5w37T2pVUe\ny67qcytMX+qndtOpP+e8W6XXzdsl+UC2ry3Y7VRl3yT/LclD0ruC7Dkze6m+LvlY+m3051blrum3\nGj+V5NS2vFvMrubUtNbmpp0zPZ+x7PpvbWOqvpfkF6cpt0kfpeOTae1HC9Z/fJLnpbcV876wbEpr\nV0zrv97M1C3qbpZX3uV1t7UNqXp9kucn+c30W/xJcnh6d6/XLdifHa1l0TnYsv3brHcTuDLZYLXb\nuPu0vo+ntcu2tuNT2T2StFQtan+W2v+drT15a5KXJzktVW9Lv7Y/ldZm36eX2ta9Vzi2G2Zz2zrM\nqgJma/lpVT6Z5AFVuX6S+6S/aB9tLV+pynfSg+XrsiBgJvnH9H4WZ6b3y/pueoBJkj9M76e2Wtef\nnm+VbHVctj0XTPvuGrazM1jr/v5Ner/Bx2RzY3NEkp8m+YeZctdLr0w3yNbP4XwjfWX2aWezUv+k\npePaO0lS9Rvp/dAuS+8bdGaSS5JNP7I4JIvr7aI3pCS5YnreY8G8X05y3fQ3+NO3uve7h5Xq0NK1\n/eitLNvSz9Vs+SvTFuxWqrJ3ks+n96H8XHpbcF56vdsnvV/spvraWi6qysFJXpTe5/JB6W3CuVV5\n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\nvpc+/NjXk/b2pN1zwbrvOp3f82de00OuqmO8Uo/Za3zx/K1dn99oyZlz0/Zqyctacnrr4wye15IP\ntOQBC5Y/dMVreaX1b573uJZ8uPVhiy5vybemNuk5LVlcd5N9pvbkkpZc7yo8pye0ZPl7zaJzuVK7\nuLXXph/HsS35eksubclXW/LillxrhdfkiLZo6JxtvcbJtVvyJy05tW0egupLLXlFS/abK3v1ljy1\nJZ9sfdi4y1py1vQaPf0qPd9X7jV6Ukve2ZKvTcd2bku+ML2HzA/JtWdLntuSk1ty4VR/zmzJ+1ry\nxDY7lNPWhylaXJfnHtVa266ACgDsQFX3T/9m6m/T2pHrvDew0Fr6YAIA6+/o9NvjO+wXwbBWaxmm\nCABYD1V3TO8veNf0v4393rT2+fXdKViZgAkAO7+7pv8hjQuTvD19zFLYaemDCQDAUPpgAgAwlIAJ\nAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVg\nAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJ\nmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQ\nAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAw\nlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAA\nDCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYA\nAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJ\nAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVg\nAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJ\nmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQ\nAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAw\nlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAA\nDCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYA\nAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJ\nAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVg\nAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJ\nmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQ\nAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAw\nlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAA\nDCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYA\nAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJ\nAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVgAgAwlIAJAMBQAiYAAEMJmAAADCVg\nAgAw1P8HG54HQ82HZVgAAAAASUVORK5CYII=\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -813,11 +926,99 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "We see that the document word coloring is done just the way we expected. :)\n", "\n", - "We can do the same for the entire vocabulary, statically. The only difference would be in using `get_term_topics`, and iterating over the dictionary." + "## Word-coloring a dictionary\n", + "\n", + "We can do the same for the entire vocabulary, statically. The only difference would be in using `get_term_topics`, and iterating over the dictionary.\n", + "\n", + "We will use a modified version of the coloring code when passing an entire dictionary." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def color_words_dict(model, dictionary):\n", + " import matplotlib.pyplot as plt\n", + " import matplotlib.patches as patches\n", + "\n", + " word_topics = []\n", + " for word_id in dictionary:\n", + " word = str(dictionary[word_id])\n", + " # get_term_topics returns static topics, as mentioned before\n", + " probs = model.get_term_topics(word)\n", + " # we are creating word_topics which is similar to the one created by get_document_topics\n", + " try:\n", + " if probs[0][1] >= probs[1][1]:\n", + " word_topics.append((word_id, [0, 1]))\n", + " else:\n", + " word_topics.append((word_id, [1, 0]))\n", + " # this in the case only one topic is returned\n", + " except IndexError:\n", + " word_topics.append((word_id, [probs[0][0]]))\n", + " \n", + " # color-topic matching\n", + " topic_colors = { 1:'red', 0:'blue'}\n", + " \n", + " # set up fig to plot\n", + " fig = plt.figure()\n", + " ax = fig.add_axes([0,0,1,1])\n", + "\n", + " # a sort of hack to make sure the words are well spaced out.\n", + " word_pos = 1/len(doc)\n", + " \n", + " # use matplotlib to plot words\n", + " for word, topics in word_topics:\n", + " ax.text(word_pos, 0.8, model.id2word[word],\n", + " horizontalalignment='center',\n", + " verticalalignment='center',\n", + " fontsize=20, color=topic_colors[topics[0]], # choose just the most likely topic\n", + " transform=ax.transAxes)\n", + " word_pos += 0.2 # to move the word for the next iter\n", + "\n", + " ax.set_axis_off()\n", + " plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n/th1mk+oOTbKR80nr/O635n4bGd4/VxR8/G7hl1Q830131zzQ9ay/7SO/5s1r7cx7s1r/s06nc/c\nY/rfvccyXLyO87KaH7pl+EHn+V1r2edtlH3v+ll+v+Zrd22XN9V89dneFnusl21t7a33GP+p6+e+\n00bZfdd194Nbxv+Amjfesj885mx/9kOur9Pa7jU/ZnOf3Sj/inW9PGpX+UPW+T14j+U5UFuwn33l\nyL2OH7vOxPnvRet0zt8yn3us4z9mV/kr1vfcd1f5IzeW6+5nfT3tvf72e7168fp5/nTWeRvlN5r1\n6vV4fcuN8vPnci31tlm33TXdH1mn9eO7ynfm8bJZJ7XDsx66Dn/6rBvuGvaYdV0/8qyv032+rmp/\nO1PtwcYx6jdrfuBG+S1qvrXmW2pe/2yvj2tgfZ/28Wwtv2gt37x22PM871x+7aOOnrROa/74Ov7n\nbxn/t9Zhn7JR9tB1Hk+vecNd4z9mHf+Ru8p3lumizXOyc+J14jHr8buGXTCX6/w3z/qQtezqv7ba\nfXxblvHlaxv+pfuuLwdcEX826x2zbnqKcW6/Ltgv7TH8/usHfdhG2UPm0QhL//wQ073Z+t4n7yr/\nzrX8u7a858K5OyRbwtIr515h7f6X54nruvznG2Xfsk77v+1zGgcLS+uj17K/mXXygal+Zl2mL9u1\nza+c9dNbxr/VOuxZ69/XzN0B3BLKXbnWxxvvGna9dad8zvr/G8x651wC45Prbj1uXb5Hb6kTf7hl\n/A9Yp/8nG2Vj1pvWHfzkBq7OW+fxixtl15/1ullvmHWDXeO+a9arzkjdP3E5Lp4HD0v3V4dPPd8x\nl5PLP9hVvp968MsbZbeYS8D5ulk3uop53mxtPF+8x/Cdturx+/oMV/PrFCc8J4SlNW9Q8501L6t5\nUn2u+bj1YPvojbIHr9N5xEbZ/635CzW/v+Z7a95oLf+iddxvOxOf66y+drdXh2mrTj39k9vbpXyn\nbfqQLe+5ZH3PBx/osxyx16lOojfCoa3H1Jr3X4d/3x7Dv36tw/dZ/78TwLx+2wlkzfPW8X/xMJ/l\nal4/b2kjONsYftG6zF++/v/VLV9S3HbLuP/fOq0nb5n+37cRKu9638XrPO67x/CDzvPu7QoIa764\n5otqfu06r49fyz9jHffHt837LG+brW3tKca/YP0sm23qTlj6Pft4/87+cK6GpVfLdl/367fV/INd\n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\n2efsoPv1ub6uT3nO25y/cnzMa+zaatvx7XtajgV3aglMb3aqDzXmPJe3CVxLjHGvlm+6f6Y5H3qW\nlwYAAADgOunwP/AEnEmPavnmQ589AAAAAGfJQfssBc6UMT615Xb8O7Q8fvLM5tz9aBsAAAAA1xBh\nKZw9d2j59cy3V7/U0qcMAAAAAGeJPksBAAAAANJnKQAAAABAJSwFAAAAAKiEpQAAAAAAlbAUAAAA\nAKASlgIAAAAAVMJSAAAAAIBKWAoAAAAAUAlLAQAAAAAqYSkAAAAAQCUsBQAAAACohKUAAAAAAJWw\nFAAAAACgEpYCAAAAAFTCUgAAAACASlgKAAAAAFAJSwEAAAAAKmEpAAAAAEAlLAUAAAAAqISlAAAA\nAACVsBQAAAAAoBKWAgAAAABUwlIAAAAAgEpYCgAAAABQCUsBAAAAACphKQAAAABAJSwFAAAAAKiE\npQAAAAAAlbAUAAAAAKASlgIAAAAAVMJSAAAAAIBKWAoAAAAAUAlLAQAAAAAqYSkAAAAAQCUsBQAA\nAACohKUAAAAAAJWwFAAAAACgEpYCAAAAAFTCUgAAAACASlgKAAAAAFAJSwEAAAAAKmEpAAAAAEAl\nLAUAAAAAqISlAAAAAACVsBQAAAAAoBKWAgAAAABUwlIAAAAAgEpYCgAAAABQCUsBAAAAACphKQAA\nAABAJSwFAAAAAKiEpQAAAAAAlbAUAAAAAKASlgIAAAAAVMJSAAAAAIBKWAoAAAAAUAlLAQAAAAAq\nYSkAAAAAQCUsBQAAAACohKUAAAAAAJWwFAAAAACgEpYCAAAAAFTCUgAAAACASlgKAAAAAFAJSwEA\nAAAAKmEpAAAAAEAlLAUAAAAAqISlAAAAAACVsBQAAAAAoBKWAgAAAABUwlIAAAAAgEpYCgAAAABQ\nCUsBAAAAACphKQAAAABAJSwFAAAAAKiEpQAAAAAAlbAUAAAAAKASlgIAAAAAVMJSAAAAAIBKWAoA\nAAAAUAlLAQAAAAAqYSkAAAAAQCUsBQAAAACohKUAAAAAAJWwFAAAAACgEpYCAAAAAFTCUgAAAACA\nSlgKAAAAAFAJSwEAAAAAKmEpAAAAAEAlLAUAAAAAqISlAAAAAACVsBQAAAAAoBKWAgAAAABUwlIA\nAAAAgEpYCgAAAABQCUsBAAAAACphKQAAAABAJSwFAAAAAKiEpQAAAAAAlbAUAAAAAKASlgIAAAAA\nVMJSAAAAAIBKWAoAAAAAUAlLAQAAAAAqYSkAAAAAQCUsBQAAAACohKUAAAAAAJWwFAAAAACgEpYC\nAAAAAFTCUgAAAACASlgKAAAAAFAJSwEAAAAAKmEpAAAAAEAlLAUAAAAAqISlAAAAAACVsBQAAAAA\noBKWAgAAAABUwlIAAAAAgEpYCgAAAABQCUsBAAAAACphKQAAAABAJSwFAAAAAKiEpQAAAAAAlbAU\nAAAAAKASlgIAAAAAVMJSAAAAAIBKWAoAAAAAUAlLAQAAAAAqYSkAAAAAQCUsBQAAAACohKUAAAAA\nAJWwFAAAAACgEpYCAAAAAFTCUgAAAACASlgKAAAAAFAJSwEAAAAAKmEpAAAAAEAlLAUAAAAAqISl\nAAAAAACVsBQAAAAAoBKWAgAAAABUwlIAAAAAgEpYCgAAAABQCUsBAAAAACphKQAAAABAJSwFAAAA\nAKiEpQAAAAAAlbAUAAAAAKASlgIAAAAAVMJSAAAAAIBKWAoAAAAAUAlLAQAAAAAqYSkAAAAAQCUs\nBQAAAACohKUAAAAAAJWwFAAAAACgEpYCAAAAAFTCUgAAAACASlgKAAAAAFAJSwEAAAAAKmEpAAAA\nAEAlLAUAAAAAqISlAAAAAACVsBQAAAAAoBKWAgAAAABUwlIAAAAAgEpYCgAAAABQCUsBAAAAACph\nKQAAAABAJSwFAAAAAKiEpQAAAAAAlbAUAAAAAKASlgIAAAAAVMJSAAAAAIBKWAoAAAAAUAlLAQAA\nAAAqYSkAAAAAQCUsBQAAAACohKUAAAAAAJWwFAAAAACgEpYCAAAAAFTCUgAAAACASlgKAAAAAFAJ\nSwEAAAAAKmEpAAAAAEAlLAUAAAAAqISlAAAAAACVsBQAAAAAoBKWAgAAAABUwlIAAAAAgEpYCgAA\nAABQCUsBAAAAACphKQAAAABAJSwFAAAAAKiEpQAAAAAAlbAUAAAAAKASlgIAAAAAVMJSAAAAAIBK\nWAoAAAAAUAlLAQAAAAAqYSkAAAAAQCUsBQAAAACohKUAAAAAAJWwFAAAAACgEpYCAAAAAFTCUgAA\nAACASlgKAAAAAFAJSwEAAAAAKmEpAAAAAEAlLAUAAAAAqISlAAAAAACVsBQAAAAAoBKWAgAAAABU\nwlIAAAAAgEpYCgAAAABQCUsBAAAAACphKQAAAABAJSwFAAAAAKiEpQAAAAAAlbAUAAAAAKASlgIA\nAAAAVMJSAAAAAIBKWAoAAAAAUAlLAQAAAAAqYSkAAAAAQCUsBQAAAACohKUAAAAAAJWwFAAAAACg\nEpYCAAAAAFTCUgAAAACASlgKAAAAAFAJSwEAAAAAKmEpAAAAAEAlLAUAAAAAqISlAAAAAACVsBQA\nAAAAoBKWAgAAAABUwlIAAAAAgEpYCgAAAABQCUsBAAAAACphKQAAAABAVf8/DUP4McpJzWYAAAAA\nSUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "color_words_dict(model, dictionary)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see, the red words are to do with finance, and the blue ones are to do with water. \n", + "\n", + "You can also notice that some words, like mud, shore and borrow seem to be incorrectly colored - however, they are correctly colored according to the LDA model used for coloring. A small corpus means that the LDA algorithm might not assign 'ideal' topic proportions to each word. Fine tuning the model and having a larger corpus would improve the model, and improve the results of the word coloring." ] } ], From 0fd63b857855c00bc98130cc70afe10e3e50e2c2 Mon Sep 17 00:00:00 2001 From: Prakhar Pratyush Date: Mon, 20 Mar 2017 03:28:34 +0530 Subject: [PATCH 18/41] WordRank and Mallet wrappers single vs double quote issue in windows. Fix #671 (#1208) * ldamallet cmd single vs double quote issue removed * cmd double quote changed to single quote * cmd double quote changed to single quote --- gensim/models/wrappers/ldamallet.py | 8 ++++---- gensim/models/wrappers/wordrank.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gensim/models/wrappers/ldamallet.py b/gensim/models/wrappers/ldamallet.py index 9b5cfcaa91..b192bd3881 100644 --- a/gensim/models/wrappers/ldamallet.py +++ b/gensim/models/wrappers/ldamallet.py @@ -156,9 +156,9 @@ def convert_input(self, corpus, infer=False, serialize_corpus=True): def train(self, corpus): self.convert_input(corpus, infer=False) - cmd = self.mallet_path + " train-topics --input %s --num-topics %s --alpha %s --optimize-interval %s "\ - "--num-threads %s --output-state %s --output-doc-topics %s --output-topic-keys %s "\ - "--num-iterations %s --inferencer-filename %s --doc-topics-threshold %s" + cmd = self.mallet_path + ' train-topics --input %s --num-topics %s --alpha %s --optimize-interval %s '\ + '--num-threads %s --output-state %s --output-doc-topics %s --output-topic-keys %s '\ + '--num-iterations %s --inferencer-filename %s --doc-topics-threshold %s' cmd = cmd % ( self.fcorpusmallet(), self.num_topics, self.alpha, self.optimize_interval, self.workers, self.fstate(), self.fdoctopics(), self.ftopickeys(), self.iterations, self.finferencer(), self.topic_threshold) @@ -177,7 +177,7 @@ def __getitem__(self, bow, iterations=100): bow = [bow] self.convert_input(bow, infer=True) - cmd = self.mallet_path + " infer-topics --input %s --inferencer %s --output-doc-topics %s --num-iterations %s --doc-topics-threshold %s" + cmd = self.mallet_path + ' infer-topics --input %s --inferencer %s --output-doc-topics %s --num-iterations %s --doc-topics-threshold %s' cmd = cmd % (self.fcorpusmallet() + '.infer', self.finferencer(), self.fdoctopics() + '.infer', iterations, self.topic_threshold) logger.info("inferring topics with MALLET LDA '%s'", cmd) check_output(args=cmd, shell=True) diff --git a/gensim/models/wrappers/wordrank.py b/gensim/models/wrappers/wordrank.py index ae9746dbf0..c00f50bcdd 100644 --- a/gensim/models/wrappers/wordrank.py +++ b/gensim/models/wrappers/wordrank.py @@ -135,7 +135,7 @@ def train(cls, wr_path, corpus_file, out_path, size=100, window=15, symmetric=1, # run wordrank executable with wr_args cmd = ['mpirun', '-np', '1', '../wordrank'] for option, value in wr_args.items(): - cmd.append("--%s" % option) + cmd.append('--%s' % option) cmd.append(str(value)) logger.info("Running wordrank binary '%s'", cmd) output = utils.check_output(args=cmd) From c5a053c62384f8f4f6a8eb79da18401577d79775 Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Mon, 20 Mar 2017 13:07:09 -0700 Subject: [PATCH 19/41] Fix wordrank max_iter_dump calculation. Fix #1216 (#1217) * fix for max_iter_dump calculation * fix max_iter_dump calculation * set iter default to 90 --- gensim/models/wrappers/wordrank.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gensim/models/wrappers/wordrank.py b/gensim/models/wrappers/wordrank.py index c00f50bcdd..e691a6b757 100644 --- a/gensim/models/wrappers/wordrank.py +++ b/gensim/models/wrappers/wordrank.py @@ -46,7 +46,7 @@ class Wordrank(KeyedVectors): @classmethod def train(cls, wr_path, corpus_file, out_path, size=100, window=15, symmetric=1, min_count=5, max_vocab_size=0, - sgd_num=100, lrate=0.001, period=10, iter=91, epsilon=0.75, dump_period=10, reg=0, alpha=100, + sgd_num=100, lrate=0.001, period=10, iter=90, epsilon=0.75, dump_period=10, reg=0, alpha=100, beta=99, loss='hinge', memory=4.0, cleanup_files=True, sorted_vocab=1, ensemble=0): """ `wr_path` is the path to the Wordrank directory. @@ -113,6 +113,11 @@ def train(cls, wr_path, corpus_file, out_path, size=100, window=15, symmetric=1, with smart_open(meta_file, 'wb') as f: meta_info = "{0} {1}\n{2} {3}\n{4} {5}".format(numwords, numwords, numlines, cooccurrence_shuf_file, numwords, vocab_file) f.write(meta_info.encode('utf-8')) + + if iter % dump_period == 0: + iter += 1 + else: + logger.warning('Resultant embedding would be from %d iteration', iter - iter % dump_period) wr_args = { 'path': 'meta', @@ -141,7 +146,7 @@ def train(cls, wr_path, corpus_file, out_path, size=100, window=15, symmetric=1, output = utils.check_output(args=cmd) # use embeddings from max. iteration's dump - max_iter_dump = iter / dump_period * dump_period - 1 + max_iter_dump = iter - iter % dump_period copyfile('model_word_%d.txt' % max_iter_dump, 'wordrank.words') copyfile('model_context_%d.txt' % max_iter_dump, 'wordrank.contexts') model = cls.load_wordrank_model('wordrank.words', os.path.join('meta', vocab_file), 'wordrank.contexts', sorted_vocab, ensemble) From 854fad6048b4845b44363a277bb6a4a9a70e41e9 Mon Sep 17 00:00:00 2001 From: robotcator Date: Tue, 21 Mar 2017 04:20:35 +0800 Subject: [PATCH 20/41] Make doc2vec imdb ipynb tutorial run in python 2 and 3 (#1220) Make doc2vec imdb ipynb tutorial run in python 2 and 3 --- docs/notebooks/doc2vec-IMDB.ipynb | 283 +++++++++++++++++++++++------- 1 file changed, 223 insertions(+), 60 deletions(-) diff --git a/docs/notebooks/doc2vec-IMDB.ipynb b/docs/notebooks/doc2vec-IMDB.ipynb index d68b68b1a6..92f48e24c5 100644 --- a/docs/notebooks/doc2vec-IMDB.ipynb +++ b/docs/notebooks/doc2vec-IMDB.ipynb @@ -2,14 +2,20 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "# gensim doc2vec & IMDB sentiment dataset" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "TODO: section on introduction & motivation\n", "\n", @@ -24,14 +30,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Load corpus" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Fetch and prep exactly as in Mikolov's go.sh shell script. (Note this cell tests for existence of required files, so steps won't repeat once the final summary file (`aclImdb/alldata-id.txt`) is available alongside this notebook.)" ] @@ -39,7 +51,11 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, "outputs": [], "source": [ "import locale\n", @@ -47,11 +63,17 @@ "import os.path\n", "import requests\n", "import tarfile\n", + "import sys\n", + "import codecs\n", "\n", "dirname = 'aclImdb'\n", "filename = 'aclImdb_v1.tar.gz'\n", "locale.setlocale(locale.LC_ALL, 'C')\n", "\n", + "if sys.version > '3':\n", + " control_chars = [chr(0x85)]\n", + "else:\n", + " control_chars = [unichr(0x85)]\n", "\n", "# Convert text to lower-case and strip punctuation/symbols from words\n", "def normalize_text(text):\n", @@ -66,12 +88,14 @@ "\n", " return norm_text\n", "\n", + "import time\n", + "start = time.clock()\n", "\n", "if not os.path.isfile('aclImdb/alldata-id.txt'):\n", " if not os.path.isdir(dirname):\n", " if not os.path.isfile(filename):\n", " # Download IMDB archive\n", - " url = 'http://ai.stanford.edu/~amaas/data/sentiment/' + filename\n", + " url = u'http://ai.stanford.edu/~amaas/data/sentiment/' + filename\n", " r = requests.get(url)\n", " with open(filename, 'wb') as f:\n", " f.write(r.content)\n", @@ -92,8 +116,7 @@ " txt_files = glob.glob('/'.join([dirname, fol, '*.txt']))\n", "\n", " for txt in txt_files:\n", - " with open(txt, 'r', encoding='utf-8') as t:\n", - " control_chars = [chr(0x85)]\n", + " with codecs.open(txt, 'r', encoding='utf-8') as t:\n", " t_clean = t.read()\n", "\n", " for c in control_chars:\n", @@ -104,21 +127,28 @@ " temp += \"\\n\"\n", "\n", " temp_norm = normalize_text(temp)\n", - " with open('/'.join([dirname, output]), 'w', encoding='utf-8') as n:\n", + " with codecs.open('/'.join([dirname, output]), 'w', encoding='utf-8') as n:\n", " n.write(temp_norm)\n", "\n", " alldata += temp_norm\n", "\n", - " with open('/'.join([dirname, 'alldata-id.txt']), 'w', encoding='utf-8') as f:\n", + " with codecs.open('/'.join([dirname, 'alldata-id.txt']), 'w', encoding='utf-8') as f:\n", " for idx, line in enumerate(alldata.splitlines()):\n", - " num_line = \"_*{0} {1}\\n\".format(idx, line)\n", - " f.write(num_line)" + " num_line = u\"_*{0} {1}\\n\".format(idx, line)\n", + " f.write(num_line)\n", + "\n", + "end = time.clock()\n", + "print (\"total running time: \", end-start)" ] }, { "cell_type": "code", "execution_count": 3, - "metadata": {}, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, "outputs": [], "source": [ "import os.path\n", @@ -127,7 +157,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "The data is small enough to be read into memory. " ] @@ -135,7 +168,11 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, "outputs": [ { "name": "stdout", @@ -171,14 +208,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Set-up Doc2Vec Training & Evaluation Models" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Approximating experiment of Le & Mikolov [\"Distributed Representations of Sentences and Documents\"](http://cs.stanford.edu/~quocle/paragraph_vector.pdf), also with guidance from Mikolov's [example go.sh](https://groups.google.com/d/msg/word2vec-toolkit/Q49FIrNOQRo/J6KG8mUj45sJ):\n", "\n", @@ -196,7 +239,11 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, "outputs": [ { "name": "stdout", @@ -238,7 +285,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Following the paper, we also evaluate models in pairs. These wrappers return the concatenation of the vectors from each model. (Only the singular models are trained.)" ] @@ -246,7 +296,11 @@ { "cell_type": "code", "execution_count": 5, - "metadata": {}, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, "outputs": [], "source": [ "from gensim.test.test_doc2vec import ConcatenatedDoc2Vec\n", @@ -256,14 +310,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Predictive Evaluation Methods" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Helper methods for evaluating error rate." ] @@ -271,7 +331,11 @@ { "cell_type": "code", "execution_count": 8, - "metadata": {}, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, "outputs": [], "source": [ "import numpy as np\n", @@ -323,14 +387,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Bulk Training" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Using explicit multiple-pass, alpha-reduction approach as sketched in [gensim doc2vec blog post](http://radimrehurek.com/2014/12/doc2vec-tutorial/) – with added shuffling of corpus on each pass.\n", "\n", @@ -344,7 +414,11 @@ { "cell_type": "code", "execution_count": 9, - "metadata": {}, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, "outputs": [], "source": [ "from collections import defaultdict\n", @@ -354,7 +428,11 @@ { "cell_type": "code", "execution_count": 10, - "metadata": {}, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, "outputs": [ { "name": "stdout", @@ -555,7 +633,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Achieved Sentiment-Prediction Accuracy" ] @@ -563,7 +644,11 @@ { "cell_type": "code", "execution_count": 12, - "metadata": {}, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, "outputs": [ { "name": "stdout", @@ -590,21 +675,30 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "In my testing, unlike the paper's report, DBOW performs best. Concatenating vectors from different models only offers a small predictive improvement. The best results I've seen are still just under 10% error rate, still a ways from the paper's 7.42%.\n" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Examining Results" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "### Are inferred vectors close to the precalculated ones?" ] @@ -612,7 +706,11 @@ { "cell_type": "code", "execution_count": 13, - "metadata": {}, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, "outputs": [ { "name": "stdout", @@ -638,14 +736,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "(Yes, here the stored vector from 20 epochs of training is usually one of the closest to a freshly-inferred vector for the same words. Note the defaults for inference are very abbreviated – just 3 steps starting at a high alpha – and likely need tuning for other applications.)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "### Do close documents seem more related than distant ones?" ] @@ -653,7 +757,11 @@ { "cell_type": "code", "execution_count": 14, - "metadata": {}, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, "outputs": [ { "name": "stdout", @@ -686,14 +794,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "(Somewhat, in terms of reviewer tone, movie genre, etc... the MOST cosine-similar docs usually seem more like the TARGET than the MEDIAN or LEAST.)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "### Do the word vectors show useful similarities?" ] @@ -701,7 +815,11 @@ { "cell_type": "code", "execution_count": 15, - "metadata": {}, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, "outputs": [], "source": [ "word_models = simple_models[:]" @@ -710,7 +828,11 @@ { "cell_type": "code", "execution_count": 17, - "metadata": {}, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, "outputs": [ { "name": "stdout", @@ -782,8 +904,8 @@ "('mystery/comedy', 0.5020694732666016)]" ] }, - "output_type": "execute_result", - "metadata": {} + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -808,7 +930,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Do the DBOW words look meaningless? That's because the gensim DBOW model doesn't train word vectors – they remain at their random initialized values – unless you ask with the `dbow_words=1` initialization parameter. Concurrent word-training slows DBOW mode significantly, and offers little improvement (and sometimes a little worsening) of the error rate on this IMDB sentiment-prediction task. \n", "\n", @@ -817,7 +942,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "### Are the word vectors from this dataset any good at analogies?" ] @@ -825,7 +953,11 @@ { "cell_type": "code", "execution_count": 26, - "metadata": {}, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, "outputs": [ { "name": "stdout", @@ -850,14 +982,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Even though this is a tiny, domain-specific dataset, it shows some meager capability on the general word analogies – at least for the DM/concat and DM/mean models which actually train word vectors. (The untrained random-initialized words of the DBOW model of course fail miserably.)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Slop" ] @@ -865,7 +1003,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, "outputs": [], "source": [ "This cell left intentionally erroneous." @@ -873,7 +1015,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "To mix the Google dataset (if locally available) into the word tests..." ] @@ -881,7 +1026,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, "outputs": [], "source": [ "from gensim.models import KeyedVectors\n", @@ -892,7 +1041,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "To get copious logging output from above steps..." ] @@ -900,7 +1052,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, "outputs": [], "source": [ "import logging\n", @@ -911,7 +1067,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "To auto-reload python code while developing..." ] @@ -919,7 +1078,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, "outputs": [], "source": [ "%load_ext autoreload\n", @@ -929,23 +1092,23 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 2", "language": "python", - "name": "python3" + "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 3.0 + "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.5.1" + "pygments_lexer": "ipython2", + "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 0 -} \ No newline at end of file +} From cc86005d9c45b8fc6d527d85c2e43b5d63cfb61e Mon Sep 17 00:00:00 2001 From: Chinmaya Pancholi Date: Tue, 21 Mar 2017 03:32:32 +0530 Subject: [PATCH 21/41] [WIP] Add "predict_output_word" to word2vec negative sampling. Fix #863. (#1209) * added function to predict output word in CBOW from context words * handling negative_sampling case * added warnings for out-of-vocabulary and not negative sampling cases * added unit tests for predict_output_word * updated CHANGELOG --- CHANGELOG.md | 42 +++++++++++++++++++----------------- gensim/models/word2vec.py | 26 ++++++++++++++++++++++ gensim/test/test_word2vec.py | 29 ++++++++++++++++++++++--- 3 files changed, 74 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d171fcbec..f04d416158 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ Changes Unreleased: +New features: +* Add output word prediction for negative sampling scheme. (@chinmayapancholi13,[#1209](https://github.com/RaRe-Technologies/gensim/pull/1209)) ======== 1.0.1, 2017-03-03 @@ -35,35 +37,35 @@ Improvements: * Phrases and Phraser allow a generator corpus (ELind77 [#1099](https://github.com/RaRe-Technologies/gensim/pull/1099)) * Ignore DocvecsArray.doctag_syn0norm in save. Fix #789 (@accraze,[#1053](https://github.com/RaRe-Technologies/gensim/pull/1053)) * Fix bug in LsiModel that occurs when id2word is a Python 3 dictionary. (@cvangysel,[#1103](https://github.com/RaRe-Technologies/gensim/pull/1103) -* Fix broken link to paper in readme (@bhargavvader,[#1101](https://github.com/RaRe-Technologies/gensim/pull/1101)) -* Lazy formatting in evaluate_word_pairs (@akutuzov,[#1084](https://github.com/RaRe-Technologies/gensim/pull/1084)) +* Fix broken link to paper in readme (@bhargavvader,[#1101](https://github.com/RaRe-Technologies/gensim/pull/1101)) +* Lazy formatting in evaluate_word_pairs (@akutuzov,[#1084](https://github.com/RaRe-Technologies/gensim/pull/1084)) * Deacc option to keywords pre-processing (@bhargavvader,[#1076](https://github.com/RaRe-Technologies/gensim/pull/1076)) -* Generate Deprecated exception when using Word2Vec.load_word2vec_format (@tmylk, [#1165](https://github.com/RaRe-Technologies/gensim/pull/1165)) -* Fix hdpmodel constructor docstring for print_topics (#1152) (@toliwa, [#1152](https://github.com/RaRe-Technologies/gensim/pull/1152)) -* Default to per_word_topics=False in LDA get_item for performance (@menshikh-iv, [#1154](https://github.com/RaRe-Technologies/gensim/pull/1154)) +* Generate Deprecated exception when using Word2Vec.load_word2vec_format (@tmylk, [#1165](https://github.com/RaRe-Technologies/gensim/pull/1165)) +* Fix hdpmodel constructor docstring for print_topics (#1152) (@toliwa, [#1152](https://github.com/RaRe-Technologies/gensim/pull/1152)) +* Default to per_word_topics=False in LDA get_item for performance (@menshikh-iv, [#1154](https://github.com/RaRe-Technologies/gensim/pull/1154)) * Fix bound computation in Author Topic models. (@olavurmortensen, [#1156](https://github.com/RaRe-Technologies/gensim/pull/1156)) * Write UTF-8 byte strings in tensorboard conversion (@tmylk,[#1144](https://github.com/RaRe-Technologies/gensim/pull/1144)) * Make top_topics and sparse2full compatible with numpy 1.12 strictly int idexing (@tmylk,[#1146](https://github.com/RaRe-Technologies/gensim/pull/1146)) Tutorial and doc improvements: -* Clarifying comment in is_corpus func in utils.py (@greninja,[#1109](https://github.com/RaRe-Technologies/gensim/pull/1109)) +* Clarifying comment in is_corpus func in utils.py (@greninja,[#1109](https://github.com/RaRe-Technologies/gensim/pull/1109)) * Tutorial Topics_and_Transformations fix markdown and add references (@lgmoneda,[#1120](https://github.com/RaRe-Technologies/gensim/pull/1120)) -* Fix doc2vec-lee.ipynb results to match previous behavior (@bahbbc,[#1119](https://github.com/RaRe-Technologies/gensim/pull/1119)) +* Fix doc2vec-lee.ipynb results to match previous behavior (@bahbbc,[#1119](https://github.com/RaRe-Technologies/gensim/pull/1119)) * Remove Pattern lib dependency in News Classification tutorial (@luizcavalcanti,[#1118](https://github.com/RaRe-Technologies/gensim/pull/1118)) * Corpora_and_Vector_Spaces tutorial text clarification (@lgmoneda,[#1116](https://github.com/RaRe-Technologies/gensim/pull/1116)) * Update Transformation and Topics link from quick start notebook (@mariana393,[#1115](https://github.com/RaRe-Technologies/gensim/pull/1115)) * Quick Start Text clarification and typo correction (@luizcavalcanti,[#1114](https://github.com/RaRe-Technologies/gensim/pull/1114)) * Fix typos in Author-topic tutorial (@Fil,[#1102](https://github.com/RaRe-Technologies/gensim/pull/1102)) * Address benchmark inconsistencies in Annoy tutorial (@droudy,[#1113](https://github.com/RaRe-Technologies/gensim/pull/1113)) -* Add note about Annoy speed depending on numpy BLAS setup in annoytutorial.ipynb (@greninja,[#1137](https://github.com/RaRe-Technologies/gensim/pull/1137)) -* Fix dependencies description on doc2vec-IMDB notebook (@luizcavalcanti, [#1132](https://github.com/RaRe-Technologies/gensim/pull/1132)) -* Add documentation for WikiCorpus metadata. (@kirit93, [#1163](https://github.com/RaRe-Technologies/gensim/pull/1163)) +* Add note about Annoy speed depending on numpy BLAS setup in annoytutorial.ipynb (@greninja,[#1137](https://github.com/RaRe-Technologies/gensim/pull/1137)) +* Fix dependencies description on doc2vec-IMDB notebook (@luizcavalcanti, [#1132](https://github.com/RaRe-Technologies/gensim/pull/1132)) +* Add documentation for WikiCorpus metadata. (@kirit93, [#1163](https://github.com/RaRe-Technologies/gensim/pull/1163)) + - 1.0.0RC2, 2017-02-16 -* Add note about Annoy speed depending on numpy BLAS setup in annoytutorial.ipynb (@greninja,[#1137](https://github.com/RaRe-Technologies/gensim/pull/1137)) +* Add note about Annoy speed depending on numpy BLAS setup in annoytutorial.ipynb (@greninja,[#1137](https://github.com/RaRe-Technologies/gensim/pull/1137)) * Remove direct access to properties moved to KeyedVectors (@tmylk,[#1147](https://github.com/RaRe-Technologies/gensim/pull/1147)) * Remove support for Python 2.6, 3.3 and 3.4 (@tmylk,[#1145](https://github.com/RaRe-Technologies/gensim/pull/1145)) * Write UTF-8 byte strings in tensorboard conversion (@tmylk,[#1144](https://github.com/RaRe-Technologies/gensim/pull/1144)) @@ -83,15 +85,15 @@ Improvements: * Ignore DocvecsArray.doctag_syn0norm in save. Fix #789 (@accraze,[#1053](https://github.com/RaRe-Technologies/gensim/pull/1053)) * Move load and save word2vec_format out of word2vec class to KeyedVectors (@tmylk,[#1107](https://github.com/RaRe-Technologies/gensim/pull/1107)) * Fix bug in LsiModel that occurs when id2word is a Python 3 dictionary. (@cvangysel,[#1103](https://github.com/RaRe-Technologies/gensim/pull/1103) -* Fix broken link to paper in readme (@bhargavvader,[#1101](https://github.com/RaRe-Technologies/gensim/pull/1101)) -* Lazy formatting in evaluate_word_pairs (@akutuzov,[#1084](https://github.com/RaRe-Technologies/gensim/pull/1084)) +* Fix broken link to paper in readme (@bhargavvader,[#1101](https://github.com/RaRe-Technologies/gensim/pull/1101)) +* Lazy formatting in evaluate_word_pairs (@akutuzov,[#1084](https://github.com/RaRe-Technologies/gensim/pull/1084)) * Deacc option to keywords pre-processing (@bhargavvader,[#1076](https://github.com/RaRe-Technologies/gensim/pull/1076)) Tutorial and doc improvements: -* Clarifying comment in is_corpus func in utils.py (@greninja,[#1109](https://github.com/RaRe-Technologies/gensim/pull/1109)) +* Clarifying comment in is_corpus func in utils.py (@greninja,[#1109](https://github.com/RaRe-Technologies/gensim/pull/1109)) * Tutorial Topics_and_Transformations fix markdown and add references (@lgmoneda,[#1120](https://github.com/RaRe-Technologies/gensim/pull/1120)) -* Fix doc2vec-lee.ipynb results to match previous behavior (@bahbbc,[#1119](https://github.com/RaRe-Technologies/gensim/pull/1119)) +* Fix doc2vec-lee.ipynb results to match previous behavior (@bahbbc,[#1119](https://github.com/RaRe-Technologies/gensim/pull/1119)) * Remove Pattern lib dependency in News Classification tutorial (@luizcavalcanti,[#1118](https://github.com/RaRe-Technologies/gensim/pull/1118)) * Corpora_and_Vector_Spaces tutorial text clarification (@lgmoneda,[#1116](https://github.com/RaRe-Technologies/gensim/pull/1116)) * Update Transformation and Topics link from quick start notebook (@mariana393,[#1115](https://github.com/RaRe-Technologies/gensim/pull/1115)) @@ -103,9 +105,9 @@ Tutorial and doc improvements: 0.13.4.1, 2017-01-04 * Disable direct access warnings on save and load of Word2vec/Doc2vec (@tmylk, [#1072](https://github.com/RaRe-Technologies/gensim/pull/1072)) -* Making Default hs error explicit (@accraze, [#1054](https://github.com/RaRe-Technologies/gensim/pull/1054)) +* Making Default hs error explicit (@accraze, [#1054](https://github.com/RaRe-Technologies/gensim/pull/1054)) * Removed unnecessary numpy imports (@bhargavvader, [#1065](https://github.com/RaRe-Technologies/gensim/pull/1065)) -* Utils and Matutils changes (@bhargavvader, [#1062](https://github.com/RaRe-Technologies/gensim/pull/1062)) +* Utils and Matutils changes (@bhargavvader, [#1062](https://github.com/RaRe-Technologies/gensim/pull/1062)) * Tests for the evaluate_word_pairs function (@akutuzov, [#1061](https://github.com/RaRe-Technologies/gensim/pull/1061)) 0.13.4, 2016-12-22 @@ -127,8 +129,8 @@ Tutorial and doc improvements: * Remove warning on gensim import "pattern not installed". Fix #1009 (@shashankg7, [#1018](https://github.com/RaRe-Technologies/gensim/pull/1018)) * Add delete_temporary_training_data() function to word2vec and doc2vec models. (@deepmipt-VladZhukov, [#987](https://github.com/RaRe-Technologies/gensim/pull/987)) * Documentation improvements (@IrinaGoloshchapova, [#1010](https://github.com/RaRe-Technologies/gensim/pull/1010), [#1011](https://github.com/RaRe-Technologies/gensim/pull/1011)) -* LDA tutorial by Olavur, tips and tricks (@olavurmortensen, [#779](https://github.com/RaRe-Technologies/gensim/pull/779)) -* Add double quote in commmand line to run on Windows (@akarazeev, [#1005](https://github.com/RaRe-Technologies/gensim/pull/1005)) +* LDA tutorial by Olavur, tips and tricks (@olavurmortensen, [#779](https://github.com/RaRe-Technologies/gensim/pull/779)) +* Add double quote in commmand line to run on Windows (@akarazeev, [#1005](https://github.com/RaRe-Technologies/gensim/pull/1005)) * Fix directory names in notebooks to be OS-independent (@mamamot, [#1004](https://github.com/RaRe-Technologies/gensim/pull/1004)) * Respect clip_start, clip_end in most_similar. Fix #601. (@parulsethi, [#994](https://github.com/RaRe-Technologies/gensim/pull/994)) * Replace Python sigmoid function with scipy in word2vec & doc2vec (@markroxor, [#989](https://github.com/RaRe-Technologies/gensim/pull/989)) diff --git a/gensim/models/word2vec.py b/gensim/models/word2vec.py index 89d6e2ab5d..000eee6976 100644 --- a/gensim/models/word2vec.py +++ b/gensim/models/word2vec.py @@ -1210,6 +1210,32 @@ def similarity(self, w1, w2): def n_similarity(self, ws1, ws2): return self.wv.n_similarity(ws1, ws2) + def predict_output_word(self, context_words_list, topn=10): + """Report the probability distribution of the center word given the context words as input to the trained model.""" + if not self.negative: + raise RuntimeError("We have currently only implemented predict_output_word " + "for the negative sampling scheme, so you need to have " + "run word2vec with negative > 0 for this to work.") + + if not hasattr(self.wv, 'syn0') or not hasattr(self, 'syn1neg'): + raise RuntimeError("Parameters required for predicting the output words not found.") + + word_vocabs = [self.wv.vocab[w] for w in context_words_list if w in self.wv.vocab] + if not word_vocabs: + warnings.warn("All the input context words are out-of-vocabulary for the current model.") + return None + + word2_indices = [word.index for word in word_vocabs] + + l1 = np_sum(self.wv.syn0[word2_indices], axis=0) + if word2_indices and self.cbow_mean: + l1 /= len(word2_indices) + + prob_values = exp(dot(l1, self.syn1neg.T)) # propagate hidden -> output and take softmax to get probabilities + prob_values /= sum(prob_values) + top_indices = matutils.argsort(prob_values, topn=topn, reverse=True) + return [(self.wv.index2word[index1], prob_values[index1]) for index1 in top_indices] #returning the most probable output words with their probabilities + def init_sims(self, replace=False): """ init_sims() resides in KeyedVectors because it deals with syn0 mainly, but because syn1 is not an attribute diff --git a/gensim/test/test_word2vec.py b/gensim/test/test_word2vec.py index 8d13aef380..8c15b9d9a5 100644 --- a/gensim/test/test_word2vec.py +++ b/gensim/test/test_word2vec.py @@ -207,7 +207,7 @@ def testLoadPreKeyedVectorModel(self): model_file_suffix = '_py2' else: model_file_suffix = '_py3' - + # Model stored in one file model_file = 'word2vec_pre_kv%s' % model_file_suffix model = word2vec.Word2Vec.load(datapath(model_file)) @@ -620,6 +620,29 @@ def testNormalizeAfterTrainingData(self): norm_only_model.delete_temporary_training_data(replace_word_vectors_with_normalized=True) self.assertFalse(np.allclose(model['human'], norm_only_model['human'])) + def testPredictOutputWord(self): + '''Test word2vec predict_output_word method handling for negative sampling scheme''' + #under normal circumstances + model_with_neg = word2vec.Word2Vec(sentences, min_count=1) + predictions_with_neg = model_with_neg.predict_output_word(['system', 'human'], topn=5) + self.assertTrue(len(predictions_with_neg)==5) + + #out-of-vobaculary scenario + predictions_out_of_vocab = model_with_neg.predict_output_word(['some', 'random', 'words'], topn=5) + self.assertEqual(predictions_out_of_vocab, None) + + #when required model parameters have been deleted + model_with_neg.init_sims() + model_with_neg.wv.save_word2vec_format(testfile(), binary=True) + kv_model_with_neg = keyedvectors.KeyedVectors.load_word2vec_format(testfile(), binary=True) + binary_model_with_neg = word2vec.Word2Vec() + binary_model_with_neg.wv = kv_model_with_neg + self.assertRaises(RuntimeError, binary_model_with_neg.predict_output_word, ['system', 'human']) + + #negative sampling scheme not used + model_without_neg = word2vec.Word2Vec(sentences, min_count=1, negative=0) + self.assertRaises(RuntimeError, model_without_neg.predict_output_word, ['system', 'human']) + @log_capture() def testBuildVocabWarning(self, l): """Test if warning is raised on non-ideal input to a word2vec model""" @@ -644,14 +667,14 @@ def testTrainWarning(self, l): model.alpha += 0.05 warning = "Effective 'alpha' higher than previous training cycles" self.assertTrue(warning in str(l)) - + def test_sentences_should_not_be_a_generator(self): """ Is sentences a generator object? """ gen = (s for s in sentences) self.assertRaises(TypeError, word2vec.Word2Vec, (gen,)) - + def testLoadOnClassError(self): """Test if exception is raised when loading word2vec model on instance""" self.assertRaises(AttributeError, load_on_instance) From 97cd64fad0507dca121164ed45789cfca1d20460 Mon Sep 17 00:00:00 2001 From: Kris Singh Date: Wed, 22 Mar 2017 00:20:36 +0530 Subject: [PATCH 22/41] Sklearn LDA wrapper now works in sklearn pipeline (#1213) --- .travis.yml | 1 + docs/notebooks/sklearn_wrapper.ipynb | 197 +++++++++++++----- .../sklearn_wrapper_gensim_ldamodel.py | 43 +++- gensim/test/test_data/mini_newsgroup | Bin 0 -> 196956 bytes gensim/test/test_sklearn_integration.py | 42 +++- 5 files changed, 223 insertions(+), 60 deletions(-) create mode 100644 gensim/test/test_data/mini_newsgroup diff --git a/.travis.yml b/.travis.yml index 663f506869..7e67dc09fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ install: - pip install annoy - pip install testfixtures - pip install unittest2 + - pip install scikit-learn - pip install Morfessor==2.0.2a4 - python setup.py install script: python setup.py test diff --git a/docs/notebooks/sklearn_wrapper.ipynb b/docs/notebooks/sklearn_wrapper.ipynb index 34110916dd..0d28429ecf 100644 --- a/docs/notebooks/sklearn_wrapper.ipynb +++ b/docs/notebooks/sklearn_wrapper.ipynb @@ -38,13 +38,13 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [], "source": [ - "from gensim.sklearn_integration.sklearn_wrapper_gensim_ldaModel import SklearnWrapperLdaModel" + "from gensim.sklearn_integration.sklearn_wrapper_gensim_ldamodel import SklearnWrapperLdaModel" ] }, { @@ -56,7 +56,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 21, "metadata": { "collapsed": true }, @@ -85,7 +85,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 22, "metadata": { "collapsed": false }, @@ -100,13 +100,18 @@ { "data": { "text/plain": [ - "[(0,\n", - " u'0.164*\"computer\" + 0.117*\"system\" + 0.105*\"graph\" + 0.061*\"server\" + 0.057*\"tree\" + 0.046*\"malfunction\" + 0.045*\"kernel\" + 0.045*\"complier\" + 0.043*\"loading\" + 0.039*\"hamiltonian\"'),\n", - " (1,\n", - " u'0.102*\"graph\" + 0.083*\"system\" + 0.072*\"tree\" + 0.064*\"server\" + 0.059*\"user\" + 0.059*\"computer\" + 0.057*\"trees\" + 0.056*\"eulerian\" + 0.055*\"node\" + 0.052*\"flow\"')]" + "array([[ 0.85275314, 0.14724686],\n", + " [ 0.12390183, 0.87609817],\n", + " [ 0.4612995 , 0.5387005 ],\n", + " [ 0.84924177, 0.15075823],\n", + " [ 0.49180096, 0.50819904],\n", + " [ 0.40086923, 0.59913077],\n", + " [ 0.28454427, 0.71545573],\n", + " [ 0.88776198, 0.11223802],\n", + " [ 0.84210373, 0.15789627]])" ] }, - "execution_count": 3, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -114,7 +119,8 @@ "source": [ "model=SklearnWrapperLdaModel(num_topics=2,id2word=dictionary,iterations=20, random_state=1)\n", "model.fit(corpus)\n", - "model.print_topics(2)" + "model.print_topics(2)\n", + "model.transform(corpus)" ] }, { @@ -135,9 +141,9 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 23, "metadata": { - "collapsed": true + "collapsed": false }, "outputs": [], "source": [ @@ -146,14 +152,14 @@ "from gensim.models.ldamodel import LdaModel\n", "from sklearn.datasets import fetch_20newsgroups\n", "from sklearn.feature_extraction.text import CountVectorizer\n", - "from gensim.sklearn_integration.sklearn_wrapper_gensim_ldaModel import SklearnWrapperLdaModel" + "from gensim.sklearn_integration.sklearn_wrapper_gensim_ldamodel import SklearnWrapperLdaModel" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 24, "metadata": { - "collapsed": true + "collapsed": false }, "outputs": [], "source": [ @@ -173,9 +179,9 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 25, "metadata": { - "collapsed": true + "collapsed": false }, "outputs": [], "source": [ @@ -196,7 +202,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 26, "metadata": { "collapsed": false }, @@ -205,18 +211,18 @@ "data": { "text/plain": [ "[(0,\n", - " u'0.018*\"cryptography\" + 0.018*\"face\" + 0.017*\"fierkelab\" + 0.008*\"abuse\" + 0.007*\"constitutional\" + 0.007*\"collection\" + 0.007*\"finish\" + 0.007*\"150\" + 0.007*\"fast\" + 0.006*\"difference\"'),\n", + " u'0.085*\"abroad\" + 0.053*\"ciphertext\" + 0.042*\"arithmetic\" + 0.037*\"facts\" + 0.031*\"courtesy\" + 0.025*\"amolitor\" + 0.023*\"argue\" + 0.021*\"asking\" + 0.020*\"agree\" + 0.018*\"classified\"'),\n", " (1,\n", - " u'0.022*\"corporate\" + 0.022*\"accurate\" + 0.012*\"chance\" + 0.008*\"decipher\" + 0.008*\"example\" + 0.008*\"basically\" + 0.008*\"dawson\" + 0.008*\"cases\" + 0.008*\"consideration\" + 0.008*\"follow\"'),\n", + " u'0.098*\"asking\" + 0.075*\"cryptography\" + 0.068*\"abroad\" + 0.033*\"456\" + 0.025*\"argue\" + 0.022*\"bitnet\" + 0.017*\"false\" + 0.014*\"digex\" + 0.014*\"effort\" + 0.013*\"disk\"'),\n", " (2,\n", - " u'0.034*\"argue\" + 0.031*\"456\" + 0.031*\"arithmetic\" + 0.024*\"courtesy\" + 0.020*\"beastmaster\" + 0.019*\"bitnet\" + 0.015*\"false\" + 0.015*\"classified\" + 0.014*\"cubs\" + 0.014*\"digex\"'),\n", + " u'0.023*\"accurate\" + 0.021*\"corporate\" + 0.013*\"clark\" + 0.012*\"chance\" + 0.009*\"consideration\" + 0.008*\"authentication\" + 0.008*\"dawson\" + 0.008*\"candidates\" + 0.008*\"basically\" + 0.008*\"assess\"'),\n", " (3,\n", - " u'0.108*\"abroad\" + 0.089*\"asking\" + 0.060*\"cryptography\" + 0.035*\"certain\" + 0.030*\"ciphertext\" + 0.030*\"book\" + 0.028*\"69\" + 0.028*\"demand\" + 0.028*\"87\" + 0.027*\"cracking\"'),\n", + " u'0.016*\"cryptography\" + 0.007*\"evans\" + 0.006*\"considering\" + 0.006*\"forgot\" + 0.006*\"built\" + 0.005*\"constitutional\" + 0.005*\"fly\" + 0.004*\"cellular\" + 0.004*\"computed\" + 0.004*\"digitized\"'),\n", " (4,\n", - " u'0.022*\"clark\" + 0.019*\"authentication\" + 0.017*\"candidates\" + 0.016*\"decryption\" + 0.015*\"attempt\" + 0.013*\"creation\" + 0.013*\"1993apr5\" + 0.013*\"acceptable\" + 0.013*\"algorithms\" + 0.013*\"employer\"')]" + " u'0.028*\"certain\" + 0.022*\"69\" + 0.021*\"book\" + 0.020*\"demand\" + 0.020*\"cracking\" + 0.020*\"87\" + 0.017*\"farm\" + 0.017*\"fierkelab\" + 0.015*\"face\" + 0.009*\"constitutional\"')]" ] }, - "execution_count": 7, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -233,72 +239,163 @@ "collapsed": true }, "source": [ - "#### Using together with Scikit learn's Logistic Regression" + "### Example for Using Grid Search" ] }, { - "cell_type": "markdown", - "metadata": {}, + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": false + }, + "outputs": [], "source": [ - "Now lets try Sklearn's logistic classifier to classify the given categories into two types.Ideally we should get postive weights when cryptography is talked about and negative when baseball is talked about." + "from sklearn.model_selection import GridSearchCV\n", + "from gensim.models.coherencemodel import CoherenceModel" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 31, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "from sklearn import linear_model" + "def scorer(estimator, X,y=None):\n", + " goodcm = CoherenceModel(model=estimator, texts= texts, dictionary=estimator.id2word, coherence='c_v')\n", + " return goodcm.get_coherence()" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "GridSearchCV(cv=5, error_score='raise',\n", + " estimator=SklearnWrapperLdaModel(alpha='symmetric', chunksize=2000, corpus=None,\n", + " decay=0.5, eta=None, eval_every=10, gamma_threshold=0.001,\n", + " id2word=,\n", + " iterations=50, minimum_probability=0.01, num_topics=5,\n", + " offset=1.0, passes=20, random_state=None, update_every=1),\n", + " fit_params={}, iid=True, n_jobs=1,\n", + " param_grid={'num_topics': (2, 3, 5, 10), 'iterations': (1, 20, 50)},\n", + " pre_dispatch='2*n_jobs', refit=True, return_train_score=True,\n", + " scoring=, verbose=0)" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "obj=SklearnWrapperLdaModel(id2word=dictionary,num_topics=5,passes=20)\n", + "parameters = {'num_topics':(2, 3, 5, 10), 'iterations':(1,20,50)}\n", + "model = GridSearchCV(obj, parameters, scoring=scorer, cv=5)\n", + "model.fit(corpus)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'iterations': 50, 'num_topics': 3}" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.best_params_" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example of Using Pipeline" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 34, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "def print_features(clf, vocab, n=10):\n", + "from sklearn.pipeline import Pipeline\n", + "from sklearn import linear_model\n", + "def print_features_pipe(clf, vocab, n=10):\n", " ''' Better printing for sorted list '''\n", - " coef = clf.coef_[0]\n", + " coef = clf.named_steps['classifier'].coef_[0]\n", + " print coef\n", " print 'Positive features: %s' % (' '.join(['%s:%.2f' % (vocab[j], coef[j]) for j in np.argsort(coef)[::-1][:n] if coef[j] > 0]))\n", - " print 'Negative features: %s' % (' '.join(['%s:%.2f' % (vocab[j], coef[j]) for j in np.argsort(coef)[:n] if coef[j] < 0]))" + " print 'Negative features: %s' % (' '.join(['%s:%.2f' % (vocab[j], coef[j]) for j in np.argsort(coef)[:n] if coef[j] < 0]))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "id2word=Dictionary(map(lambda x : x.split(),data.data))\n", + "corpus = [id2word.doc2bow(i.split()) for i in data.data]" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 38, "metadata": { "collapsed": false }, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:gensim.models.ldamodel:too few updates, training might not converge; consider increasing the number of passes or iterations to improve accuracy\n" + ] + }, { "name": "stdout", "output_type": "stream", "text": [ - "Positive features: clipper:1.50 code:1.24 key:1.04 encryption:0.95 chip:0.37 nsa:0.37 government:0.36 uk:0.36 org:0.23 cryptography:0.23\n", - "Negative features: baseball:-1.32 game:-0.71 year:-0.61 team:-0.38 edu:-0.27 games:-0.26 players:-0.23 ball:-0.17 season:-0.14 phillies:-0.11\n" + "[ -2.95020466e-01 -1.04115352e-01 5.19570267e-01 1.03817059e-01\n", + " 2.72881013e-02 1.35738501e-02 1.89246630e-13 1.89246630e-13\n", + " 1.89246630e-13 1.89246630e-13 1.89246630e-13 1.89246630e-13\n", + " 1.89246630e-13 1.89246630e-13 1.89246630e-13]\n", + "Positive features: Fame,:0.52 Keach:0.10 comp.org.eff.talk,:0.03 comp.org.eff.talk.:0.01 >Pat:0.00 dome.:0.00 internet...:0.00 trawling:0.00 hanging:0.00 red@redpoll.neoucom.edu:0.00\n", + "Negative features: Fame.:-0.30 considered,:-0.10\n", + "0.531040268456\n" ] } ], "source": [ - "clf=linear_model.LogisticRegression(penalty='l1', C=0.1) #l1 penalty used\n", - "clf.fit(X,data.target)\n", - "print_features(clf,vocab)" + "model=SklearnWrapperLdaModel(num_topics=15,id2word=id2word,iterations=50, random_state=37)\n", + "clf=linear_model.LogisticRegression(penalty='l2', C=0.1) #l2 penalty used\n", + "pipe = Pipeline((('features', model,), ('classifier', clf)))\n", + "pipe.fit(corpus, data.target)\n", + "print_features_pipe(pipe, id2word.values())\n", + "print pipe.score(corpus, data.target)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] } ], "metadata": { @@ -317,7 +414,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", - "version": "2.7.6" + "version": "2.7.13" } }, "nbformat": 4, diff --git a/gensim/sklearn_integration/sklearn_wrapper_gensim_ldamodel.py b/gensim/sklearn_integration/sklearn_wrapper_gensim_ldamodel.py index e27a29f886..003d313e6d 100644 --- a/gensim/sklearn_integration/sklearn_wrapper_gensim_ldamodel.py +++ b/gensim/sklearn_integration/sklearn_wrapper_gensim_ldamodel.py @@ -8,12 +8,17 @@ Scikit learn interface for gensim for easy use of gensim with scikit-learn follows on scikit learn API conventions """ +import numpy as np + from gensim import models from gensim import matutils from scipy import sparse +from sklearn.base import TransformerMixin, BaseEstimator + -class SklearnWrapperLdaModel(models.LdaModel): + +class SklearnWrapperLdaModel(models.LdaModel, TransformerMixin, BaseEstimator): """ Base LDA module """ @@ -56,15 +61,14 @@ def get_params(self, deep=True): """ Returns all parameters as dictionary. """ - if deep: - return { - "corpus": self.corpus, "num_topics": self.num_topics, "id2word": self.id2word, + return {"corpus": self.corpus, "num_topics": self.num_topics, "id2word": self.id2word, "chunksize": self.chunksize, "passes": self.passes, - "update_every": self.update_every, "alpha": self.alpha, " eta": self.eta, " decay": self.decay, - "offset": self.offset, "eval_every": self.eval_every, " iterations": self.iterations, + "update_every": self.update_every, "alpha": self.alpha, "eta": self.eta, "decay": self.decay, + "offset": self.offset, "eval_every": self.eval_every, "iterations": self.iterations, "gamma_threshold": self.gamma_threshold, "minimum_probability": self.minimum_probability, "random_state": self.random_state} + def set_params(self, **parameters): """ Set all parameters. @@ -73,7 +77,7 @@ def set_params(self, **parameters): self.parameter = value return self - def fit(self, X): + def fit(self, X, y=None): """ For fitting corpus into the class object. Calls gensim.model.LdaModel: @@ -93,7 +97,28 @@ def fit(self, X): random_state=self.random_state) return self - def transform(self, bow, minimum_probability=None, minimum_phi_value=None, per_word_topics=False): + def transform(self, docs, minimum_probability=None): + """ + Takes as an list of input a documents (documents). + Returns matrix of topic distribution for the given document bow, where a_ij + indicates (topic_i, topic_probability_j). + """ + # The input as array of array + check = lambda x: [x] if isinstance(x[0], tuple) else x + docs = check(docs) + X = [[] for i in range(0,len(docs))]; + for k,v in enumerate(docs): + + doc_topics = self.get_document_topics(v, minimum_probability=minimum_probability) + probs_docs = list(map(lambda x: x[1], doc_topics)) + # Everything should be equal in length + if len(probs_docs) != self.num_topics: + probs_docs.extend([1e-12]*(self.num_topics - len(probs_docs))) + X[k] = probs_docs + probs_docs = [] + return np.reshape(np.array(X), (len(docs), self.num_topics)) + + def get_topic_dist(self, bow, minimum_probability=None, minimum_phi_value=None, per_word_topics=False): """ Takes as an input a new document (bow). Returns the topic distribution for the given document bow, as a list of (topic_id, topic_probability) 2-tuples. @@ -109,4 +134,4 @@ def partial_fit(self, X): if sparse.issparse(X): X = matutils.Sparse2Corpus(X) - self.update(corpus=X) + self.update(corpus=X) \ No newline at end of file diff --git a/gensim/test/test_data/mini_newsgroup b/gensim/test/test_data/mini_newsgroup new file mode 100644 index 0000000000000000000000000000000000000000..65f80e66205aa2b052a41d7dfa0ec6f118fde501 GIT binary patch literal 196956 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 Zw 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
F-v)(4n+|5|= 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 literal 0 HcmV?d00001 diff --git a/gensim/test/test_sklearn_integration.py b/gensim/test/test_sklearn_integration.py index e8ae26bb82..3a6401962b 100644 --- a/gensim/test/test_sklearn_integration.py +++ b/gensim/test/test_sklearn_integration.py @@ -1,12 +1,22 @@ import six import unittest import numpy +import os +import codecs +import pickle from scipy import sparse +from sklearn.pipeline import Pipeline +from sklearn.feature_extraction.text import CountVectorizer +from sklearn.datasets import load_files +from sklearn import linear_model from gensim.sklearn_integration.sklearn_wrapper_gensim_ldamodel import SklearnWrapperLdaModel from gensim.corpora import Dictionary from gensim import matutils +module_path = os.path.dirname(__file__) # needed because sample data files are located in the same folder +datapath = lambda fname: os.path.join(module_path, 'test_data', fname) + texts = [['complier', 'system', 'computer'], ['eulerian', 'node', 'cycle', 'graph', 'tree', 'path'], ['graph', 'flow', 'network', 'graph'], @@ -35,7 +45,21 @@ def testPrintTopic(self): def testTransform(self): texts_new = ['graph','eulerian'] bow = self.model.id2word.doc2bow(texts_new) - doc_topics, word_topics, phi_values = self.model.transform(bow,per_word_topics=True) + X = self.model.transform(bow) + self.assertTrue(X.shape[0], 1) + self.assertTrue(X.shape[1], self.model.num_topics) + texts_new = [['graph','eulerian'],['server', 'flow'], ['path', 'system']] + bow = [] + for i in texts_new: + bow.append(self.model.id2word.doc2bow(i)) + X = self.model.transform(bow) + self.assertTrue(X.shape[0], 3) + self.assertTrue(X.shape[1], self.model.num_topics) + + def testGetTopicDist(self): + texts_new = ['graph','eulerian'] + bow = self.model.id2word.doc2bow(texts_new) + doc_topics, word_topics, phi_values = self.model.get_topic_dist(bow,per_word_topics=True) for k,v in word_topics: self.assertTrue(isinstance(v, list)) @@ -67,5 +91,21 @@ def testCSRMatrixConversion(self): self.assertTrue(isinstance(v, six.string_types)) self.assertTrue(isinstance(k, int)) + def testPipeline(self): + model = SklearnWrapperLdaModel(num_topics=2, passes=10, minimum_probability=0, random_state=numpy.random.seed(0)) + with open(datapath('mini_newsgroup'),'rb') as f: + compressed_content = f.read() + uncompressed_content = codecs.decode(compressed_content, 'zlib_codec') + cache = pickle.loads(uncompressed_content) + data = cache + id2word=Dictionary(map(lambda x : x.split(), data.data)) + corpus = [id2word.doc2bow(i.split()) for i in data.data] + rand = numpy.random.mtrand.RandomState(1) # set seed for getting same result + clf=linear_model.LogisticRegression(penalty='l2', C=0.1) + text_lda = Pipeline((('features', model,), ('classifier', clf))) + text_lda.fit(corpus, data.target) + score = text_lda.score(corpus, data.target) + self.assertGreater(score, 0.50) + if __name__ == '__main__': unittest.main() From 57de5d897aa753dd7c968325b0fa108b6686160a Mon Sep 17 00:00:00 2001 From: Chinmaya Pancholi Date: Wed, 22 Mar 2017 02:18:39 +0530 Subject: [PATCH 23/41] added predict_output_word example --- docs/notebooks/word2vec.ipynb | 231 +++++++++++++++++++++++----------- 1 file changed, 156 insertions(+), 75 deletions(-) diff --git a/docs/notebooks/word2vec.ipynb b/docs/notebooks/word2vec.ipynb index 468cec7c55..4d7344c11c 100644 --- a/docs/notebooks/word2vec.ipynb +++ b/docs/notebooks/word2vec.ipynb @@ -28,7 +28,9 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# import modules & set up logging\n", @@ -39,7 +41,9 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stderr", @@ -69,7 +73,9 @@ { "cell_type": "code", "execution_count": 3, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# create some toy data to use with the following example\n", @@ -89,7 +95,9 @@ { "cell_type": "code", "execution_count": 4, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "class MySentences(object):\n", @@ -105,13 +113,15 @@ { "cell_type": "code", "execution_count": 5, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[['first'], ['sentence'], ['second'], ['sentence']]\n" + "[['second'], ['sentence'], ['first'], ['sentence']]\n" ] } ], @@ -123,7 +133,9 @@ { "cell_type": "code", "execution_count": 6, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stderr", @@ -141,14 +153,16 @@ { "cell_type": "code", "execution_count": 7, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Word2Vec(vocab=3, size=100, alpha=0.025)\n", - "{'second': , 'first': , 'sentence': }\n" + "{'second': , 'sentence': , 'first': }\n" ] } ], @@ -173,7 +187,9 @@ { "cell_type": "code", "execution_count": 8, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stderr", @@ -189,8 +205,8 @@ ] }, "execution_count": 8, - "output_type": "execute_result", - "metadata": {} + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -203,14 +219,16 @@ { "cell_type": "code", "execution_count": 9, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Word2Vec(vocab=3, size=100, alpha=0.025)\n", - "{'second': , 'first': , 'sentence': }\n" + "{'second': , 'sentence': , 'first': }\n" ] } ], @@ -230,7 +248,9 @@ { "cell_type": "code", "execution_count": 10, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# Set file names for train and test data\n", @@ -241,13 +261,15 @@ { "cell_type": "code", "execution_count": 11, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "<__main__.MyText object at 0x7fed6d495810>\n" + "<__main__.MyText object at 0x7f5edcd03b90>\n" ] } ], @@ -276,7 +298,9 @@ { "cell_type": "code", "execution_count": 12, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# default value of min_count=5\n", @@ -286,7 +310,9 @@ { "cell_type": "code", "execution_count": 13, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# default value of size=100\n", @@ -305,7 +331,9 @@ { "cell_type": "code", "execution_count": 14, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stderr", @@ -363,7 +391,9 @@ { "cell_type": "code", "execution_count": 15, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -406,13 +436,14 @@ " (u'LARGE', u'LARGEST', u'GOOD', u'BEST'),\n", " (u'LARGE', u'LARGEST', u'GREAT', u'GREATEST')],\n", " 'section': u'gram4-superlative'},\n", - " {'correct': [(u'LOOK', u'LOOKING', u'SAY', u'SAYING')],\n", + " {'correct': [],\n", " 'incorrect': [(u'GO', u'GOING', u'LOOK', u'LOOKING'),\n", " (u'GO', u'GOING', u'PLAY', u'PLAYING'),\n", " (u'GO', u'GOING', u'RUN', u'RUNNING'),\n", " (u'GO', u'GOING', u'SAY', u'SAYING'),\n", " (u'LOOK', u'LOOKING', u'PLAY', u'PLAYING'),\n", " (u'LOOK', u'LOOKING', u'RUN', u'RUNNING'),\n", + " (u'LOOK', u'LOOKING', u'SAY', u'SAYING'),\n", " (u'LOOK', u'LOOKING', u'GO', u'GOING'),\n", " (u'PLAY', u'PLAYING', u'RUN', u'RUNNING'),\n", " (u'PLAY', u'PLAYING', u'SAY', u'SAYING'),\n", @@ -486,7 +517,7 @@ " (u'MAN', u'MEN', u'CHILD', u'CHILDREN')],\n", " 'section': u'gram8-plural'},\n", " {'correct': [], 'incorrect': [], 'section': u'gram9-plural-verbs'},\n", - " {'correct': [(u'LOOK', u'LOOKING', u'SAY', u'SAYING')],\n", + " {'correct': [],\n", " 'incorrect': [(u'HE', u'SHE', u'HIS', u'HER'),\n", " (u'HIS', u'HER', u'HE', u'SHE'),\n", " (u'GOOD', u'BETTER', u'GREAT', u'GREATER'),\n", @@ -519,6 +550,7 @@ " (u'GO', u'GOING', u'SAY', u'SAYING'),\n", " (u'LOOK', u'LOOKING', u'PLAY', u'PLAYING'),\n", " (u'LOOK', u'LOOKING', u'RUN', u'RUNNING'),\n", + " (u'LOOK', u'LOOKING', u'SAY', u'SAYING'),\n", " (u'LOOK', u'LOOKING', u'GO', u'GOING'),\n", " (u'PLAY', u'PLAYING', u'RUN', u'RUNNING'),\n", " (u'PLAY', u'PLAYING', u'SAY', u'SAYING'),\n", @@ -588,8 +620,8 @@ ] }, "execution_count": 15, - "output_type": "execute_result", - "metadata": {} + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -618,19 +650,21 @@ { "cell_type": "code", "execution_count": 16, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "text/plain": [ - "((0.093652497483521988, 0.51332382076991245),\n", - " SpearmanrResult(correlation=0.079058717231584447, pvalue=0.58131863093325009),\n", + "((0.064272459590938968, 0.65409410348547958),\n", + " (0.041316891146214431, 0.77344654164156579),\n", " 85.55240793201133)" ] }, "execution_count": 16, - "output_type": "execute_result", - "metadata": {} + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -655,7 +689,9 @@ { "cell_type": "code", "execution_count": 17, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "from tempfile import mkstemp\n", @@ -668,7 +704,9 @@ { "cell_type": "code", "execution_count": 18, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "new_model = gensim.models.Word2Vec.load(temp_path) # open the model" @@ -698,7 +736,9 @@ { "cell_type": "code", "execution_count": 19, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stderr", @@ -735,17 +775,19 @@ { "cell_type": "code", "execution_count": 20, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "text/plain": [ - "[('longer', 0.9889242649078369)]" + "[('ensure', 0.9916089773178101)]" ] }, "execution_count": 20, - "output_type": "execute_result", - "metadata": {} + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -755,8 +797,17 @@ { "cell_type": "code", "execution_count": 21, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:gensim.models.keyedvectors:vectors for words set(['lunch', 'input', 'cat']) are not present in the model, ignoring these words\n" + ] + }, { "data": { "text/plain": [ @@ -764,8 +815,8 @@ ] }, "execution_count": 21, - "output_type": "execute_result", - "metadata": {} + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -775,14 +826,16 @@ { "cell_type": "code", "execution_count": 22, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "0.999177346162\n", - "0.9956625533\n" + "0.999128693496\n", + "0.995598721362\n" ] } ], @@ -791,6 +844,32 @@ "print(model.similarity('tree', 'murder'))" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can get the probability distribution for the center word given the context words as input:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[('more', 0.0010214881), ('training', 0.0009804588), ('continue', 0.00094650878), ('can', 0.00092195231), ('it', 0.00089841458), ('australia', 0.00077773805), ('government', 0.00076788972), ('us', 0.00076459395), ('there', 0.00075191096), ('killed', 0.00074792351)]\n" + ] + } + ], + "source": [ + "print(model.predict_output_word(['emergency','beacon','received']))" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -807,37 +886,39 @@ }, { "cell_type": "code", - "execution_count": 23, - "metadata": {}, + "execution_count": 24, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "text/plain": [ - "array([ 0.00437901, 0.02313748, -0.02936309, -0.008752 , -0.03265081,\n", - " -0.03857959, 0.00654852, -0.0923382 , -0.01189023, -0.02187135,\n", - " 0.02219572, -0.02655028, -0.06957301, -0.02087111, -0.02286052,\n", - " 0.04829395, -0.03034872, -0.00357749, 0.02391322, -0.02807236,\n", - " -0.06825348, -0.00669135, 0.06967571, 0.05163613, 0.02806929,\n", - " 0.01239634, -0.00480743, 0.01985245, -0.01433731, 0.00375685,\n", - " 0.00060874, 0.07473379, 0.01165777, -0.00219114, 0.02414591,\n", - " 0.02177458, -0.04234934, 0.01883218, 0.01372305, -0.07125106,\n", - " -0.07948184, 0.00423239, -0.0464657 , -0.02693122, 0.03259234,\n", - " -0.05827391, 0.03877009, 0.00561458, -0.01218846, 0.04278557,\n", - " 0.01462523, 0.007201 , -0.03836477, 0.00855641, 0.0169761 ,\n", - " -0.01902537, 0.05948593, -0.03107592, 0.02312824, -0.03672323,\n", - " -0.03216219, -0.05264312, 0.01833 , -0.03556807, -0.01106968,\n", - " -0.06612992, -0.08438165, 0.04030743, 0.05524538, -0.00266636,\n", - " -0.00996253, 0.00714844, -0.05092834, 0.01251214, -0.05092845,\n", - " -0.05681988, 0.03765561, -0.00957785, 0.04912213, 0.04425321,\n", - " -0.021587 , 0.03699207, -0.01726504, 0.00114953, 0.01761538,\n", - " 0.00806294, -0.09447837, -0.08522288, 0.06803837, -0.02660622,\n", - " -0.06076197, -0.04739827, -0.05012174, -0.0040967 , -0.02732807,\n", - " -0.02925751, -0.02515732, 0.0224477 , -0.05377112, -0.00109009], dtype=float32)" + "array([ 0.00506193, 0.0226855 , -0.02943243, -0.00850953, -0.03299763,\n", + " -0.03874256, 0.00795013, -0.09169962, -0.01347002, -0.02357206,\n", + " 0.02472948, -0.02463134, -0.06745216, -0.02074538, -0.02165207,\n", + " 0.04777974, -0.02944389, -0.00209709, 0.0225853 , -0.02756712,\n", + " -0.06757693, -0.0062337 , 0.06952298, 0.0505537 , 0.02458209,\n", + " 0.0140616 , -0.00495757, 0.0187903 , -0.0156572 , 0.00059901,\n", + " 0.00026355, 0.07304576, 0.00949389, -0.00331612, 0.02460947,\n", + " 0.02132211, -0.04548595, 0.01761133, 0.01257058, -0.06949953,\n", + " -0.07925285, 0.00565318, -0.04476747, -0.02920126, 0.03141577,\n", + " -0.05677001, 0.0391206 , 0.0042906 , -0.01415944, 0.04051396,\n", + " 0.01597693, 0.00671787, -0.03740353, 0.00665488, 0.01475888,\n", + " -0.01941732, 0.05768431, -0.02920702, 0.02015296, -0.03559965,\n", + " -0.02955742, -0.04996177, 0.01774862, -0.031699 , -0.01097541,\n", + " -0.06637666, -0.07993821, 0.03876927, 0.05615626, -0.00116237,\n", + " -0.01270938, 0.00813914, -0.05149486, 0.01389496, -0.04919665,\n", + " -0.05647518, 0.03727042, -0.00600072, 0.04672569, 0.04398456,\n", + " -0.02320013, 0.03545921, -0.01651819, 0.00087945, 0.0174842 ,\n", + " 0.00950102, -0.09364804, -0.08258698, 0.06699577, -0.03158378,\n", + " -0.06168535, -0.04525115, -0.04849502, -0.00481538, -0.02783764,\n", + " -0.02939486, -0.02511807, 0.0215294 , -0.05088007, -0.00214653], dtype=float32)" ] }, - "execution_count": 23, - "output_type": "execute_result", - "metadata": {} + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -863,11 +944,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], - "source": [ - "" - ] + "source": [] } ], "metadata": { @@ -880,16 +961,16 @@ "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2.0 + "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", - "version": "2.7.12" + "version": "2.7.10" } }, "nbformat": 4, "nbformat_minor": 0 -} \ No newline at end of file +} From fe49d983744dbe998e8293a918b8b6a8458422d9 Mon Sep 17 00:00:00 2001 From: Samriddhi Jain Date: Tue, 28 Mar 2017 00:54:31 +0530 Subject: [PATCH 24/41] pep8/pycodestyle fixes for hanging indents in Summarization module (#1202) * Initial fixes with autopep8 * updated for E713 * Updated to hanging indents * added line braek before operator --- gensim/summarization/__init__.py | 2 +- gensim/summarization/bm25.py | 6 +++--- gensim/summarization/keywords.py | 2 +- gensim/summarization/summarizer.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/gensim/summarization/__init__.py b/gensim/summarization/__init__.py index 57c9a7c815..c7efb84d4a 100644 --- a/gensim/summarization/__init__.py +++ b/gensim/summarization/__init__.py @@ -1,4 +1,4 @@ # bring model classes directly into package namespace, to save some typing from .summarizer import summarize, summarize_corpus -from .keywords import keywords \ No newline at end of file +from .keywords import keywords diff --git a/gensim/summarization/bm25.py b/gensim/summarization/bm25.py index 6704146d54..d634a32b54 100644 --- a/gensim/summarization/bm25.py +++ b/gensim/summarization/bm25.py @@ -40,7 +40,7 @@ def initialize(self): self.df[word] += 1 for word, freq in iteritems(self.df): - self.idf[word] = math.log(self.corpus_size-freq+0.5) - math.log(freq+0.5) + self.idf[word] = math.log(self.corpus_size - freq + 0.5) - math.log(freq + 0.5) def get_score(self, document, index, average_idf): score = 0 @@ -48,8 +48,8 @@ def get_score(self, document, index, average_idf): if word not in self.f[index]: continue idf = self.idf[word] if self.idf[word] >= 0 else EPSILON * average_idf - score += (idf*self.f[index][word]*(PARAM_K1+1) - / (self.f[index][word] + PARAM_K1*(1 - PARAM_B+PARAM_B*self.corpus_size / self.avgdl))) + score += (idf * self.f[index][word] * (PARAM_K1 + 1) + / (self.f[index][word] + PARAM_K1 * (1 - PARAM_B + PARAM_B * self.corpus_size / self.avgdl))) return score def get_scores(self, document, average_idf): diff --git a/gensim/summarization/keywords.py b/gensim/summarization/keywords.py index fe09ae1947..b24e6f1f04 100644 --- a/gensim/summarization/keywords.py +++ b/gensim/summarization/keywords.py @@ -164,7 +164,7 @@ def _get_combined_keywords(_keywords, split_text): result.append(word) # appends last word if keyword and doesn't iterate for j in xrange(i + 1, len_text): other_word = _strip_word(split_text[j]) - if other_word in _keywords and other_word == split_text[j] and not other_word in combined_word: + if other_word in _keywords and other_word == split_text[j] and other_word not in combined_word: combined_word.append(other_word) else: for keyword in combined_word: diff --git a/gensim/summarization/summarizer.py b/gensim/summarization/summarizer.py index 0779011999..e749b4cc66 100644 --- a/gensim/summarization/summarizer.py +++ b/gensim/summarization/summarizer.py @@ -198,10 +198,10 @@ def summarize(text, ratio=0.2, word_count=None, split=False): logger.warning("Input text is empty.") return - # If only one sentence is present, the function raises an error (Avoids ZeroDivisionError). + # If only one sentence is present, the function raises an error (Avoids ZeroDivisionError). if len(sentences) == 1: raise ValueError("input must have more than one sentence") - + # Warns if the text is too short. if len(sentences) < INPUT_MIN_LENGTH: logger.warning("Input text is expected to have at least " + str(INPUT_MIN_LENGTH) + " sentences.") From d00b6c1da5788b631e71d00e494bb3a0b1e7bf51 Mon Sep 17 00:00:00 2001 From: Oliver Eberle Date: Mon, 27 Mar 2017 23:18:15 +0200 Subject: [PATCH 25/41] Fix #1230. Fix word2vec reset_from bug in v1.0.1 and added unittest (#1234) * Update CHANGELOG.txt * Update CHANGELOG.txt * Release version typo fix * Typo in version * Upgraded to match word2vec class structure * Added unittest for reset_from * Fixed typo * Positive reset_from() unittest * Change unittest to check if attributes are shared * Formatting fixed --- gensim/models/word2vec.py | 4 ++-- gensim/test/test_word2vec.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/gensim/models/word2vec.py b/gensim/models/word2vec.py index 000eee6976..c29d61126c 100644 --- a/gensim/models/word2vec.py +++ b/gensim/models/word2vec.py @@ -738,8 +738,8 @@ def reset_from(self, other_model): Borrow shareable pre-built structures (like vocab) from the other_model. Useful if testing multiple models in parallel on the same corpus. """ - self.wv.vocab = other_model.vocab - self.wv.index2word = other_model.index2word + self.wv.vocab = other_model.wv.vocab + self.wv.index2word = other_model.wv.index2word self.cum_table = other_model.cum_table self.corpus_count = other_model.corpus_count self.reset_weights() diff --git a/gensim/test/test_word2vec.py b/gensim/test/test_word2vec.py index 8c15b9d9a5..e37968218c 100644 --- a/gensim/test/test_word2vec.py +++ b/gensim/test/test_word2vec.py @@ -678,6 +678,16 @@ def test_sentences_should_not_be_a_generator(self): def testLoadOnClassError(self): """Test if exception is raised when loading word2vec model on instance""" self.assertRaises(AttributeError, load_on_instance) + + def test_reset_from(self): + """Test if reset_from() uses pre-built structures from other model""" + model = word2vec.Word2Vec(sentences, min_count=1) + other_model = word2vec.Word2Vec(new_sentences, min_count=1) + other_vocab = other_model.wv.vocab + model.reset_from(other_model) + self.assertEqual(model.wv.vocab, other_vocab) + + #endclass TestWord2VecModel class TestWMD(unittest.TestCase): From 2afab971feaca5791a5bdd557b4c8b0335d81904 Mon Sep 17 00:00:00 2001 From: Lev Konstantinovskiy Date: Mon, 27 Mar 2017 20:17:40 -0300 Subject: [PATCH 26/41] Add sklearn to appveyor --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 1cec75c722..a68c3036e6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -66,7 +66,7 @@ test_script: # installed library. - "mkdir empty_folder" - "cd empty_folder" - - "pip install pyemd testfixtures unittest2 Morfessor==2.0.2a4" + - "pip install pyemd testfixtures unittest2 sklearn Morfessor==2.0.2a4" - "python -c \"import nose; nose.main()\" -s -v gensim" # Move back to the project folder From a83e61b768d53ab3bab72abe4aa7db9aab66593c Mon Sep 17 00:00:00 2001 From: Chinmaya Pancholi Date: Wed, 29 Mar 2017 01:13:32 +0530 Subject: [PATCH 27/41] scikit_learn wrapper for LSI Model in Gensim (#1244) * removed unnecessary keep_bocab_item import * removed duplicate warnings import * updated warning message for trim_rule * added wrapper class for lsimodel * removed unnecessary print statement * added tests for lsi wrapper * changed name from testPrintTopic to testModelSanity and made defaults explicit * added pipeline example for LsiModel --- docs/notebooks/sklearn_wrapper.ipynb | 136 +++++++++++++----- .../sklearn_wrapper_gensim_lsimodel.py | 99 +++++++++++++ gensim/test/test_sklearn_integration.py | 55 ++++++- 3 files changed, 256 insertions(+), 34 deletions(-) create mode 100644 gensim/sklearn_integration/sklearn_wrapper_gensim_lsimodel.py diff --git a/docs/notebooks/sklearn_wrapper.ipynb b/docs/notebooks/sklearn_wrapper.ipynb index 0d28429ecf..e98047dedc 100644 --- a/docs/notebooks/sklearn_wrapper.ipynb +++ b/docs/notebooks/sklearn_wrapper.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This tutorial is about using gensim models as a part of your scikit learn workflow with the help of wrappers found at ```gensim.sklearn_integration.sklearn_wrapper_gensim_ldaModel```" + "This tutorial is about using gensim models as a part of your scikit learn workflow with the help of wrappers found at ```gensim.sklearn_integration```" ] }, { @@ -19,7 +19,9 @@ "metadata": {}, "source": [ "The wrapper available (as of now) are :\n", - "* LdaModel (```gensim.sklearn_integration.sklearn_wrapper_gensim_ldaModel.SklearnWrapperLdaModel```),which implements gensim's ```LdaModel``` in a scikit-learn interface" + "* LdaModel (```gensim.sklearn_integration.sklearn_wrapper_gensim_ldaModel.SklearnWrapperLdaModel```),which implements gensim's ```LdaModel``` in a scikit-learn interface\n", + "\n", + "* LsiModel (```gensim.sklearn_integration.sklearn_wrapper_gensim_lsiModel.SklearnWrapperLsiModel```),which implements gensim's ```LsiModel``` in a scikit-learn interface" ] }, { @@ -38,7 +40,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 1, "metadata": { "collapsed": false }, @@ -56,7 +58,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 2, "metadata": { "collapsed": true }, @@ -85,7 +87,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 3, "metadata": { "collapsed": false }, @@ -111,7 +113,7 @@ " [ 0.84210373, 0.15789627]])" ] }, - "execution_count": 22, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -129,7 +131,7 @@ "collapsed": true }, "source": [ - "### Integration with Sklearn" + "#### Integration with Sklearn" ] }, { @@ -141,7 +143,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 4, "metadata": { "collapsed": false }, @@ -157,7 +159,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 5, "metadata": { "collapsed": false }, @@ -179,7 +181,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 6, "metadata": { "collapsed": false }, @@ -202,7 +204,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 7, "metadata": { "collapsed": false }, @@ -211,18 +213,18 @@ "data": { "text/plain": [ "[(0,\n", - " u'0.085*\"abroad\" + 0.053*\"ciphertext\" + 0.042*\"arithmetic\" + 0.037*\"facts\" + 0.031*\"courtesy\" + 0.025*\"amolitor\" + 0.023*\"argue\" + 0.021*\"asking\" + 0.020*\"agree\" + 0.018*\"classified\"'),\n", + " u'0.025*\"456\" + 0.021*\"argue\" + 0.016*\"bitnet\" + 0.015*\"beastmaster\" + 0.014*\"cryptography\" + 0.013*\"false\" + 0.012*\"digex\" + 0.011*\"cover\" + 0.011*\"classified\" + 0.010*\"disk\"'),\n", " (1,\n", - " u'0.098*\"asking\" + 0.075*\"cryptography\" + 0.068*\"abroad\" + 0.033*\"456\" + 0.025*\"argue\" + 0.022*\"bitnet\" + 0.017*\"false\" + 0.014*\"digex\" + 0.014*\"effort\" + 0.013*\"disk\"'),\n", + " u'0.142*\"abroad\" + 0.113*\"asking\" + 0.088*\"cryptography\" + 0.044*\"ciphertext\" + 0.043*\"arithmetic\" + 0.032*\"courtesy\" + 0.030*\"facts\" + 0.021*\"argue\" + 0.019*\"amolitor\" + 0.018*\"agree\"'),\n", " (2,\n", - " u'0.023*\"accurate\" + 0.021*\"corporate\" + 0.013*\"clark\" + 0.012*\"chance\" + 0.009*\"consideration\" + 0.008*\"authentication\" + 0.008*\"dawson\" + 0.008*\"candidates\" + 0.008*\"basically\" + 0.008*\"assess\"'),\n", + " u'0.034*\"certain\" + 0.027*\"69\" + 0.025*\"book\" + 0.025*\"demand\" + 0.024*\"87\" + 0.024*\"cracking\" + 0.021*\"farm\" + 0.019*\"fierkelab\" + 0.015*\"face\" + 0.011*\"abroad\"'),\n", " (3,\n", - " u'0.016*\"cryptography\" + 0.007*\"evans\" + 0.006*\"considering\" + 0.006*\"forgot\" + 0.006*\"built\" + 0.005*\"constitutional\" + 0.005*\"fly\" + 0.004*\"cellular\" + 0.004*\"computed\" + 0.004*\"digitized\"'),\n", + " u'0.017*\"decipher\" + 0.017*\"example\" + 0.016*\"cases\" + 0.016*\"follow\" + 0.008*\"considering\" + 0.006*\"forgot\" + 0.006*\"cellular\" + 0.005*\"evans\" + 0.005*\"computed\" + 0.005*\"cia\"'),\n", " (4,\n", - " u'0.028*\"certain\" + 0.022*\"69\" + 0.021*\"book\" + 0.020*\"demand\" + 0.020*\"cracking\" + 0.020*\"87\" + 0.017*\"farm\" + 0.017*\"fierkelab\" + 0.015*\"face\" + 0.009*\"constitutional\"')]" + " u'0.022*\"accurate\" + 0.021*\"corporate\" + 0.013*\"chance\" + 0.012*\"clark\" + 0.009*\"consideration\" + 0.009*\"candidates\" + 0.008*\"dawson\" + 0.008*\"authentication\" + 0.008*\"assess\" + 0.008*\"attempt\"')]" ] }, - "execution_count": 26, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -239,12 +241,12 @@ "collapsed": true }, "source": [ - "### Example for Using Grid Search" + "#### Example for Using Grid Search" ] }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 8, "metadata": { "collapsed": false }, @@ -256,7 +258,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 9, "metadata": { "collapsed": true }, @@ -269,7 +271,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 10, "metadata": { "collapsed": false }, @@ -280,16 +282,16 @@ "GridSearchCV(cv=5, error_score='raise',\n", " estimator=SklearnWrapperLdaModel(alpha='symmetric', chunksize=2000, corpus=None,\n", " decay=0.5, eta=None, eval_every=10, gamma_threshold=0.001,\n", - " id2word=,\n", + " id2word=,\n", " iterations=50, minimum_probability=0.01, num_topics=5,\n", " offset=1.0, passes=20, random_state=None, update_every=1),\n", " fit_params={}, iid=True, n_jobs=1,\n", " param_grid={'num_topics': (2, 3, 5, 10), 'iterations': (1, 20, 50)},\n", " pre_dispatch='2*n_jobs', refit=True, return_train_score=True,\n", - " scoring=, verbose=0)" + " scoring=, verbose=0)" ] }, - "execution_count": 32, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -303,7 +305,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 11, "metadata": { "collapsed": false }, @@ -311,10 +313,10 @@ { "data": { "text/plain": [ - "{'iterations': 50, 'num_topics': 3}" + "{'iterations': 20, 'num_topics': 3}" ] }, - "execution_count": 33, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -327,14 +329,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Example of Using Pipeline" + "#### Example of Using Pipeline" ] }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 12, "metadata": { - "collapsed": true + "collapsed": false }, "outputs": [], "source": [ @@ -350,7 +352,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 13, "metadata": { "collapsed": false }, @@ -362,7 +364,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 14, "metadata": { "collapsed": false }, @@ -396,6 +398,76 @@ "print_features_pipe(pipe, id2word.values())\n", "print pipe.score(corpus, data.target)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### LsiModel" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use LsiModel begin with importing LsiModel wrapper" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from gensim.sklearn_integration.sklearn_wrapper_gensim_lsimodel import SklearnWrapperLsiModel" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Example of Using Pipeline" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 0.13652819 0.00383696 0.02635504 -0.08454895 -0.02356143 0.60020084\n", + " 1.07026252 -0.04072257 0.43732847 0.54913549 -0.20242834 -0.21855402\n", + " -1.30546283 -0.08690711 0.17606255]\n", + "Positive features: 01101001B:1.07 comp.org.eff.talk.:0.60 red@redpoll.neoucom.edu:0.55 circuitry:0.44 >Pat:0.18 Fame.:0.14 Fame,:0.03 considered,:0.00\n", + "Negative features: internet...:-1.31 trawling:-0.22 hanging:-0.20 dome.:-0.09 Keach:-0.08 *best*:-0.04 comp.org.eff.talk,:-0.02\n", + "0.865771812081\n" + ] + } + ], + "source": [ + "model=SklearnWrapperLsiModel(num_topics=15, id2word=id2word)\n", + "clf=linear_model.LogisticRegression(penalty='l2', C=0.1) #l2 penalty used\n", + "pipe = Pipeline((('features', model,), ('classifier', clf)))\n", + "pipe.fit(corpus, data.target)\n", + "print_features_pipe(pipe, id2word.values())\n", + "print pipe.score(corpus, data.target)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/gensim/sklearn_integration/sklearn_wrapper_gensim_lsimodel.py b/gensim/sklearn_integration/sklearn_wrapper_gensim_lsimodel.py new file mode 100644 index 0000000000..753cbaf899 --- /dev/null +++ b/gensim/sklearn_integration/sklearn_wrapper_gensim_lsimodel.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2011 Radim Rehurek +# Licensed under the GNU LGPL v2.1 - http://www.gnu.org/licenses/lgpl.html +# +""" +Scikit learn interface for gensim for easy use of gensim with scikit-learn +Follows scikit-learn API conventions +""" +import numpy as np + +from gensim import models +from gensim import matutils +from scipy import sparse +from sklearn.base import TransformerMixin, BaseEstimator + +class SklearnWrapperLsiModel(models.LsiModel, TransformerMixin, BaseEstimator): + """ + Base LSI module + """ + + def __init__(self, corpus=None, num_topics=200, id2word=None, chunksize=20000, + decay=1.0, onepass=True, power_iters=2, extra_samples=100): + """ + Sklearn wrapper for LSI model. Class derived from gensim.model.LsiModel. + """ + self.corpus = corpus + self.num_topics = num_topics + self.id2word = id2word + self.chunksize = chunksize + self.decay = decay + self.onepass = onepass + self.extra_samples = extra_samples + self.power_iters = power_iters + + # if 'fit' function is not used, then 'corpus' is given in init + if self.corpus: + models.LsiModel.__init__(self, corpus=self.corpus, num_topics=self.num_topics, id2word=self.id2word, chunksize=self.chunksize, + decay=self.decay, onepass=self.onepass, power_iters=self.power_iters, extra_samples=self.extra_samples) + + def get_params(self, deep=True): + """ + Returns all parameters as dictionary. + """ + return {"corpus": self.corpus, "num_topics": self.num_topics, "id2word": self.id2word, + "chunksize": self.chunksize, "decay": self.decay, "onepass": self.onepass, + "extra_samples": self.extra_samples, "power_iters": self.power_iters} + + def set_params(self, **parameters): + """ + Set all parameters. + """ + for parameter, value in parameters.items(): + self.parameter = value + return self + + def fit(self, X, y=None): + """ + For fitting corpus into the class object. + Calls gensim.model.LsiModel: + >>>gensim.models.LsiModel(corpus=corpus, num_topics=num_topics, id2word=id2word, chunksize=chunksize, decay=decay, onepass=onepass, power_iters=power_iters, extra_samples=extra_samples) + """ + if sparse.issparse(X): + self.corpus = matutils.Sparse2Corpus(X) + else: + self.corpus = X + + models.LsiModel.__init__(self, corpus=self.corpus, num_topics=self.num_topics, id2word=self.id2word, chunksize=self.chunksize, + decay=self.decay, onepass=self.onepass, power_iters=self.power_iters, extra_samples=self.extra_samples) + return self + + def transform(self, docs): + """ + Takes a list of documents as input ('docs'). + Returns a matrix of topic distribution for the given document bow, where a_ij + indicates (topic_i, topic_probability_j). + """ + # The input as array of array + check = lambda x: [x] if isinstance(x[0], tuple) else x + docs = check(docs) + X = [[] for i in range(0,len(docs))]; + for k,v in enumerate(docs): + doc_topics = self[v] + probs_docs = list(map(lambda x: x[1], doc_topics)) + # Everything should be equal in length + if len(probs_docs) != self.num_topics: + probs_docs.extend([1e-12]*(self.num_topics - len(probs_docs))) + X[k] = probs_docs + probs_docs = [] + return np.reshape(np.array(X), (len(docs), self.num_topics)) + + def partial_fit(self, X): + """ + Train model over X. + """ + if sparse.issparse(X): + X = matutils.Sparse2Corpus(X) + self.add_documents(corpus=X) \ No newline at end of file diff --git a/gensim/test/test_sklearn_integration.py b/gensim/test/test_sklearn_integration.py index 3a6401962b..2f5497550a 100644 --- a/gensim/test/test_sklearn_integration.py +++ b/gensim/test/test_sklearn_integration.py @@ -11,6 +11,7 @@ from sklearn.datasets import load_files from sklearn import linear_model from gensim.sklearn_integration.sklearn_wrapper_gensim_ldamodel import SklearnWrapperLdaModel +from gensim.sklearn_integration.sklearn_wrapper_gensim_lsimodel import SklearnWrapperLsiModel from gensim.corpora import Dictionary from gensim import matutils @@ -55,7 +56,7 @@ def testTransform(self): X = self.model.transform(bow) self.assertTrue(X.shape[0], 3) self.assertTrue(X.shape[1], self.model.num_topics) - + def testGetTopicDist(self): texts_new = ['graph','eulerian'] bow = self.model.id2word.doc2bow(texts_new) @@ -97,7 +98,7 @@ def testPipeline(self): compressed_content = f.read() uncompressed_content = codecs.decode(compressed_content, 'zlib_codec') cache = pickle.loads(uncompressed_content) - data = cache + data = cache id2word=Dictionary(map(lambda x : x.split(), data.data)) corpus = [id2word.doc2bow(i.split()) for i in data.data] rand = numpy.random.mtrand.RandomState(1) # set seed for getting same result @@ -107,5 +108,55 @@ def testPipeline(self): score = text_lda.score(corpus, data.target) self.assertGreater(score, 0.50) +class TestSklearnLSIWrapper(unittest.TestCase): + def setUp(self): + self.model = SklearnWrapperLsiModel(id2word=dictionary, num_topics=2) + self.model.fit(corpus) + + def testModelSanity(self): + topic = self.model.print_topics(2) + for k, v in topic: + self.assertTrue(isinstance(v, six.string_types)) + self.assertTrue(isinstance(k, int)) + + def testTransform(self): + texts_new = ['graph','eulerian'] + bow = self.model.id2word.doc2bow(texts_new) + X = self.model.transform(bow) + self.assertTrue(X.shape[0], 1) + self.assertTrue(X.shape[1], self.model.num_topics) + texts_new = [['graph','eulerian'],['server', 'flow'], ['path', 'system']] + bow = [] + for i in texts_new: + bow.append(self.model.id2word.doc2bow(i)) + X = self.model.transform(bow) + self.assertTrue(X.shape[0], 3) + self.assertTrue(X.shape[1], self.model.num_topics) + + def testPartialFit(self): + for i in range(10): + self.model.partial_fit(X=corpus) # fit against the model again + doc=list(corpus)[0] # transform only the first document + transformed = self.model[doc] + transformed_approx = matutils.sparse2full(transformed, 2) # better approximation + expected=[1.39, 0.0] + passed = numpy.allclose(sorted(transformed_approx), sorted(expected), atol=1e-1) + self.assertTrue(passed) + + def testPipeline(self): + model = SklearnWrapperLsiModel(num_topics=2) + with open(datapath('mini_newsgroup'),'rb') as f: + compressed_content = f.read() + uncompressed_content = codecs.decode(compressed_content, 'zlib_codec') + cache = pickle.loads(uncompressed_content) + data = cache + id2word=Dictionary(map(lambda x : x.split(), data.data)) + corpus = [id2word.doc2bow(i.split()) for i in data.data] + clf=linear_model.LogisticRegression(penalty='l2', C=0.1) + text_lda = Pipeline((('features', model,), ('classifier', clf))) + text_lda.fit(corpus, data.target) + score = text_lda.score(corpus, data.target) + self.assertGreater(score, 0.50) + if __name__ == '__main__': unittest.main() From 99151db71b27cef5fb58beef3b51d82842cee167 Mon Sep 17 00:00:00 2001 From: Parul Sethi Date: Thu, 30 Mar 2017 18:45:00 +0530 Subject: [PATCH 28/41] glove2word2vec conversion script refactoring (#1247) * merge read/write of py2 py3 and add tests * correct str format * added non-ascii characters to test_glove.txt --- gensim/scripts/glove2word2vec.py | 43 +++------------------ gensim/test/test_data/test_glove.txt | 14 +++---- gensim/test/test_glove2word2vec.py | 56 +++++++++++++++++++--------- 3 files changed, 51 insertions(+), 62 deletions(-) diff --git a/gensim/scripts/glove2word2vec.py b/gensim/scripts/glove2word2vec.py index d7b2439604..7709c48714 100644 --- a/gensim/scripts/glove2word2vec.py +++ b/gensim/scripts/glove2word2vec.py @@ -41,20 +41,12 @@ def glove2word2vec(glove_input_file, word2vec_output_file): """Convert `glove_input_file` in GloVe format into `word2vec_output_file in word2vec format.""" num_lines, num_dims = get_glove_info(glove_input_file) logger.info("converting %i vectors from %s to %s", num_lines, glove_input_file, word2vec_output_file) - if sys.version_info < (3,): - with smart_open(word2vec_output_file, 'wb') as fout: - fout.write("%s %s\n" % (num_lines, num_dims)) - with smart_open(glove_input_file, 'rb') as fin: - for line in fin: - fout.write(line) - return num_lines, num_dims - else: - with smart_open(word2vec_output_file, 'w') as fout: - fout.write("%s %s\n" % (num_lines, num_dims)) - with smart_open(glove_input_file, 'r') as fin: - for line in fin: - fout.write(line) - return num_lines, num_dims + with smart_open(word2vec_output_file, 'wb') as fout: + fout.write("{0} {1}\n".format(num_lines, num_dims).encode('utf-8')) + with smart_open(glove_input_file, 'rb') as fin: + for line in fin: + fout.write(line) + return num_lines, num_dims if __name__ == "__main__": @@ -80,27 +72,4 @@ def glove2word2vec(glove_input_file, word2vec_output_file): # do the actual conversion num_lines, num_dims = glove2word2vec(args.input, args.output) logger.info('Converted model with %i vectors and %i dimensions', num_lines, num_dims) - # test that the converted model loads successfully - model = gensim.models.KeyedVectors.load_word2vec_format(args.output, binary=False) - logger.info('Model %s successfully loaded', model) - try: - logger.info('testing the model....') - if sys.version_info < (3,): - with smart_open(args.output, 'rb') as f: - seed_word1, seed_word2 = random.sample([line.split()[0] for line in f], 2) - else: - with smart_open(args.output, 'r') as f: - seed_word1, seed_word2 = random.sample([line.split()[0] for line in f], 2) - logger.info('top-10 most similar words to "%s" are: %s', seed_word1, model.most_similar(positive=[seed_word1], topn=10)) - logger.info('similarity score between %s and %s: %s', seed_word1, seed_word2, model.similarity(seed_word1, seed_word2)) - except: - logger.error('error encountered. checking for model file creation now....') - if os.path.isfile(os.path.join(args.output)): - logger.info('model file %s was created but could not be loaded.', args.output) - else: - logger.info('model file %s creation failed. ') - logger.info('please check the parameters and input file format.') - raise - - logger.info("finished running %s", program) diff --git a/gensim/test/test_data/test_glove.txt b/gensim/test/test_data/test_glove.txt index a23255f719..6f6477fbc9 100644 --- a/gensim/test/test_data/test_glove.txt +++ b/gensim/test/test_data/test_glove.txt @@ -1,13 +1,13 @@ the 0.418 0.24968 -0.41242 0.1217 0.34527 -0.044457 -0.49688 -0.17862 -0.00066023 -0.6566 0.27843 -0.14767 -0.55677 0.14658 -0.0095095 0.011658 0.10204 -0.12792 -0.8443 -0.12181 -0.016801 -0.33279 -0.1552 -0.23131 -0.19181 -1.8823 -0.76746 0.099051 -0.42125 -0.19526 4.0071 -0.18594 -0.52287 -0.31681 0.00059213 0.0074449 0.17778 -0.15897 0.012041 -0.054223 -0.29871 -0.15749 -0.34758 -0.045637 -0.44251 0.18785 0.0027849 -0.18411 -0.11514 -0.78581 -, 0.013441 0.23682 -0.16899 0.40951 0.63812 0.47709 -0.42852 -0.55641 -0.364 -0.23938 0.13001 -0.063734 -0.39575 -0.48162 0.23291 0.090201 -0.13324 0.078639 -0.41634 -0.15428 0.10068 0.48891 0.31226 -0.1252 -0.037512 -1.5179 0.12612 -0.02442 -0.042961 -0.28351 3.5416 -0.11956 -0.014533 -0.1499 0.21864 -0.33412 -0.13872 0.31806 0.70358 0.44858 -0.080262 0.63003 0.32111 -0.46765 0.22786 0.36034 -0.37818 -0.56657 0.044691 0.30392 -. 0.15164 0.30177 -0.16763 0.17684 0.31719 0.33973 -0.43478 -0.31086 -0.44999 -0.29486 0.16608 0.11963 -0.41328 -0.42353 0.59868 0.28825 -0.11547 -0.041848 -0.67989 -0.25063 0.18472 0.086876 0.46582 0.015035 0.043474 -1.4671 -0.30384 -0.023441 0.30589 -0.21785 3.746 0.0042284 -0.18436 -0.46209 0.098329 -0.11907 0.23919 0.1161 0.41705 0.056763 -6.3681e-05 0.068987 0.087939 -0.10285 -0.13931 0.22314 -0.080803 -0.35652 0.016413 0.10216 -of 0.70853 0.57088 -0.4716 0.18048 0.54449 0.72603 0.18157 -0.52393 0.10381 -0.17566 0.078852 -0.36216 -0.11829 -0.83336 0.11917 -0.16605 0.061555 -0.012719 -0.56623 0.013616 0.22851 -0.14396 -0.067549 -0.38157 -0.23698 -1.7037 -0.86692 -0.26704 -0.2589 0.1767 3.8676 -0.1613 -0.13273 -0.68881 0.18444 0.0052464 -0.33874 -0.078956 0.24185 0.36576 -0.34727 0.28483 0.075693 -0.062178 -0.38988 0.22902 -0.21617 -0.22562 -0.093918 -0.80375 -to 0.68047 -0.039263 0.30186 -0.17792 0.42962 0.032246 -0.41376 0.13228 -0.29847 -0.085253 0.17118 0.22419 -0.10046 -0.43653 0.33418 0.67846 0.057204 -0.34448 -0.42785 -0.43275 0.55963 0.10032 0.18677 -0.26854 0.037334 -2.0932 0.22171 -0.39868 0.20912 -0.55725 3.8826 0.47466 -0.95658 -0.37788 0.20869 -0.32752 0.12751 0.088359 0.16351 -0.21634 -0.094375 0.018324 0.21048 -0.03088 -0.19722 0.082279 -0.09434 -0.073297 -0.064699 -0.26044 +ö 0.013441 0.23682 -0.16899 0.40951 0.63812 0.47709 -0.42852 -0.55641 -0.364 -0.23938 0.13001 -0.063734 -0.39575 -0.48162 0.23291 0.090201 -0.13324 0.078639 -0.41634 -0.15428 0.10068 0.48891 0.31226 -0.1252 -0.037512 -1.5179 0.12612 -0.02442 -0.042961 -0.28351 3.5416 -0.11956 -0.014533 -0.1499 0.21864 -0.33412 -0.13872 0.31806 0.70358 0.44858 -0.080262 0.63003 0.32111 -0.46765 0.22786 0.36034 -0.37818 -0.56657 0.044691 0.30392 +é 0.15164 0.30177 -0.16763 0.17684 0.31719 0.33973 -0.43478 -0.31086 -0.44999 -0.29486 0.16608 0.11963 -0.41328 -0.42353 0.59868 0.28825 -0.11547 -0.041848 -0.67989 -0.25063 0.18472 0.086876 0.46582 0.015035 0.043474 -1.4671 -0.30384 -0.023441 0.30589 -0.21785 3.746 0.0042284 -0.18436 -0.46209 0.098329 -0.11907 0.23919 0.1161 0.41705 0.056763 -6.3681e-05 0.068987 0.087939 -0.10285 -0.13931 0.22314 -0.080803 -0.35652 0.016413 0.10216 +हु 0.70853 0.57088 -0.4716 0.18048 0.54449 0.72603 0.18157 -0.52393 0.10381 -0.17566 0.078852 -0.36216 -0.11829 -0.83336 0.11917 -0.16605 0.061555 -0.012719 -0.56623 0.013616 0.22851 -0.14396 -0.067549 -0.38157 -0.23698 -1.7037 -0.86692 -0.26704 -0.2589 0.1767 3.8676 -0.1613 -0.13273 -0.68881 0.18444 0.0052464 -0.33874 -0.078956 0.24185 0.36576 -0.34727 0.28483 0.075693 -0.062178 -0.38988 0.22902 -0.21617 -0.22562 -0.093918 -0.80375 +ü 0.68047 -0.039263 0.30186 -0.17792 0.42962 0.032246 -0.41376 0.13228 -0.29847 -0.085253 0.17118 0.22419 -0.10046 -0.43653 0.33418 0.67846 0.057204 -0.34448 -0.42785 -0.43275 0.55963 0.10032 0.18677 -0.26854 0.037334 -2.0932 0.22171 -0.39868 0.20912 -0.55725 3.8826 0.47466 -0.95658 -0.37788 0.20869 -0.32752 0.12751 0.088359 0.16351 -0.21634 -0.094375 0.018324 0.21048 -0.03088 -0.19722 0.082279 -0.09434 -0.073297 -0.064699 -0.26044 and 0.26818 0.14346 -0.27877 0.016257 0.11384 0.69923 -0.51332 -0.47368 -0.33075 -0.13834 0.2702 0.30938 -0.45012 -0.4127 -0.09932 0.038085 0.029749 0.10076 -0.25058 -0.51818 0.34558 0.44922 0.48791 -0.080866 -0.10121 -1.3777 -0.10866 -0.23201 0.012839 -0.46508 3.8463 0.31362 0.13643 -0.52244 0.3302 0.33707 -0.35601 0.32431 0.12041 0.3512 -0.069043 0.36885 0.25168 -0.24517 0.25381 0.1367 -0.31178 -0.6321 -0.25028 -0.38097 -in 0.33042 0.24995 -0.60874 0.10923 0.036372 0.151 -0.55083 -0.074239 -0.092307 -0.32821 0.09598 -0.82269 -0.36717 -0.67009 0.42909 0.016496 -0.23573 0.12864 -1.0953 0.43334 0.57067 -0.1036 0.20422 0.078308 -0.42795 -1.7984 -0.27865 0.11954 -0.12689 0.031744 3.8631 -0.17786 -0.082434 -0.62698 0.26497 -0.057185 -0.073521 0.46103 0.30862 0.12498 -0.48609 -0.0080272 0.031184 -0.36576 -0.42699 0.42164 -0.11666 -0.50703 -0.027273 -0.53285 +हि 0.33042 0.24995 -0.60874 0.10923 0.036372 0.151 -0.55083 -0.074239 -0.092307 -0.32821 0.09598 -0.82269 -0.36717 -0.67009 0.42909 0.016496 -0.23573 0.12864 -1.0953 0.43334 0.57067 -0.1036 0.20422 0.078308 -0.42795 -1.7984 -0.27865 0.11954 -0.12689 0.031744 3.8631 -0.17786 -0.082434 -0.62698 0.26497 -0.057185 -0.073521 0.46103 0.30862 0.12498 -0.48609 -0.0080272 0.031184 -0.36576 -0.42699 0.42164 -0.11666 -0.50703 -0.027273 -0.53285 a 0.21705 0.46515 -0.46757 0.10082 1.0135 0.74845 -0.53104 -0.26256 0.16812 0.13182 -0.24909 -0.44185 -0.21739 0.51004 0.13448 -0.43141 -0.03123 0.20674 -0.78138 -0.20148 -0.097401 0.16088 -0.61836 -0.18504 -0.12461 -2.2526 -0.22321 0.5043 0.32257 0.15313 3.9636 -0.71365 -0.67012 0.28388 0.21738 0.14433 0.25926 0.23434 0.4274 -0.44451 0.13813 0.36973 -0.64289 0.024142 -0.039315 -0.26037 0.12017 -0.043782 0.41013 0.1796 -" 0.25769 0.45629 -0.76974 -0.37679 0.59272 -0.063527 0.20545 -0.57385 -0.29009 -0.13662 0.32728 1.4719 -0.73681 -0.12036 0.71354 -0.46098 0.65248 0.48887 -0.51558 0.039951 -0.34307 -0.014087 0.86488 0.3546 0.7999 -1.4995 -1.8153 0.41128 0.23921 -0.43139 3.6623 -0.79834 -0.54538 0.16943 -0.82017 -0.3461 0.69495 -1.2256 -0.17992 -0.057474 0.030498 -0.39543 -0.38515 -1.0002 0.087599 -0.31009 -0.34677 -0.31438 0.75004 0.97065 -'s 0.23727 0.40478 -0.20547 0.58805 0.65533 0.32867 -0.81964 -0.23236 0.27428 0.24265 0.054992 0.16296 -1.2555 -0.086437 0.44536 0.096561 -0.16519 0.058378 -0.38598 0.086977 0.0033869 0.55095 -0.77697 -0.62096 0.092948 -2.5685 -0.67739 0.10151 -0.48643 -0.057805 3.1859 -0.017554 -0.16138 0.055486 -0.25885 -0.33938 -0.19928 0.26049 0.10478 -0.55934 -0.12342 0.65961 -0.51802 -0.82995 -0.082739 0.28155 -0.423 -0.27378 -0.007901 -0.030231 +या 0.25769 0.45629 -0.76974 -0.37679 0.59272 -0.063527 0.20545 -0.57385 -0.29009 -0.13662 0.32728 1.4719 -0.73681 -0.12036 0.71354 -0.46098 0.65248 0.48887 -0.51558 0.039951 -0.34307 -0.014087 0.86488 0.3546 0.7999 -1.4995 -1.8153 0.41128 0.23921 -0.43139 3.6623 -0.79834 -0.54538 0.16943 -0.82017 -0.3461 0.69495 -1.2256 -0.17992 -0.057474 0.030498 -0.39543 -0.38515 -1.0002 0.087599 -0.31009 -0.34677 -0.31438 0.75004 0.97065 +of 0.23727 0.40478 -0.20547 0.58805 0.65533 0.32867 -0.81964 -0.23236 0.27428 0.24265 0.054992 0.16296 -1.2555 -0.086437 0.44536 0.096561 -0.16519 0.058378 -0.38598 0.086977 0.0033869 0.55095 -0.77697 -0.62096 0.092948 -2.5685 -0.67739 0.10151 -0.48643 -0.057805 3.1859 -0.017554 -0.16138 0.055486 -0.25885 -0.33938 -0.19928 0.26049 0.10478 -0.55934 -0.12342 0.65961 -0.51802 -0.82995 -0.082739 0.28155 -0.423 -0.27378 -0.007901 -0.030231 for 0.15272 0.36181 -0.22168 0.066051 0.13029 0.37075 -0.75874 -0.44722 0.22563 0.10208 0.054225 0.13494 -0.43052 -0.2134 0.56139 -0.21445 0.077974 0.10137 -0.51306 -0.40295 0.40639 0.23309 0.20696 -0.12668 -0.50634 -1.7131 0.077183 -0.39138 -0.10594 -0.23743 3.9552 0.66596 -0.61841 -0.3268 0.37021 0.25764 0.38977 0.27121 0.043024 -0.34322 0.020339 0.2142 0.044097 0.14003 -0.20079 0.074794 -0.36076 0.43382 -0.084617 0.1214 - -0.16768 1.2151 0.49515 0.26836 -0.4585 -0.23311 -0.52822 -1.3557 0.16098 0.37691 -0.92702 -0.43904 -1.0634 1.028 0.0053943 0.04153 -0.018638 -0.55451 0.026166 0.28066 -0.66245 0.23435 0.2451 0.025668 -1.0869 -2.844 -0.51272 0.27286 0.0071502 0.033984 3.9084 0.52766 -0.66899 1.8238 0.43436 -0.30084 -0.26996 0.4394 0.69956 0.14885 0.029453 1.4888 0.52361 0.099354 1.2515 0.099381 -0.079261 -0.30862 0.30893 0.11023 that 0.88387 -0.14199 0.13566 0.098682 0.51218 0.49138 -0.47155 -0.30742 0.01963 0.12686 0.073524 0.35836 -0.60874 -0.18676 0.78935 0.54534 0.1106 -0.2923 0.059041 -0.69551 -0.18804 0.19455 0.32269 -0.49981 0.306 -2.3902 -0.60749 0.37107 0.078912 -0.23896 3.839 -0.20355 -0.35613 -0.69185 -0.17497 -0.35323 0.10598 -0.039303 0.015701 0.038279 -0.35283 0.44882 -0.16534 0.31579 0.14963 -0.071277 -0.53506 0.52711 -0.20148 0.0095952 diff --git a/gensim/test/test_glove2word2vec.py b/gensim/test/test_glove2word2vec.py index a4e78cd828..1e83e93154 100644 --- a/gensim/test/test_glove2word2vec.py +++ b/gensim/test/test_glove2word2vec.py @@ -6,22 +6,42 @@ """Test for gensim.scripts.glove2word2vec.py.""" +import logging +import unittest +import os +import sys +import tempfile + +import numpy +import gensim + +from gensim.utils import check_output + +module_path = os.path.dirname(__file__) # needed because sample data files are located in the same folder +datapath = lambda fname: os.path.join(module_path, 'test_data', fname) + +def testfile(): + # temporary model will be stored to this file + return os.path.join(tempfile.gettempdir(), 'glove2word2vec.test') + +class TestGlove2Word2Vec(unittest.TestCase): + def setUp(self): + self.datapath = datapath('test_glove.txt') + self.output_file = testfile() + + def testConversion(self): + output = check_output(args=['python', '-m', 'gensim.scripts.glove2word2vec', '--input', self.datapath, '--output', self.output_file]) + # test that the converted model loads successfully + try: + self.test_model = gensim.models.KeyedVectors.load_word2vec_format(self.output_file) + self.assertTrue(numpy.allclose(self.test_model.n_similarity(['the', 'and'], ['and', 'the']), 1.0)) + except: + if os.path.isfile(os.path.join(self.output_file)): + self.fail('model file %s was created but could not be loaded.' % self.output_file) + else: + self.fail('model file %s creation failed, check the parameters and input file format.' % self.output_file) + +if __name__ == '__main__': + logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.DEBUG) + unittest.main() -# import unittest -# import os -# import sys -# import gensim -# from gensim.utils import check_output - -# class TestGlove2Word2Vec(unittest.TestCase): -# def setUp(self): -# self.module_path = os.path.dirname(gensim.__file__) -# self.datapath = os.path.join(self.module_path, 'test', 'test_data', 'test_glove.txt') # Sample data files are located in the same folder -# self.output_file = 'sample_word2vec_out.txt' - -# def testConversion(self): -# output = check_output(['python', '-m', 'gensim.scripts.glove2word2vec', '-i', self.datapath, '-o', self.output_file]) -# if sys.version_info < (3,): -# self.assertEqual(output, '') -# else: -# self.assertEqual(output, b'') From becc6d3be63627ff7b05906fca8a113ab1e128a0 Mon Sep 17 00:00:00 2001 From: robotcator Date: Thu, 30 Mar 2017 21:50:25 +0800 Subject: [PATCH 29/41] Explicit epochs and corpus size in word2vec train(). Continuing #1139. Fix #1052. (#1237) * fix the compatibility between python2 & 3 * require explicit corpus size, epochs for train() * make all train() calls use explicit count, epochs * add tests to make sure that ValueError is indeed thrown * update test * fix the word2vec's reset_from() * require explicit corpus size, epochs for train() * make all train() calls use explicit count, epochs * fix some error * fix test error --- docs/notebooks/doc2vec-IMDB.ipynb | 2 +- docs/notebooks/doc2vec-lee.ipynb | 192 +++++++++++++++----- docs/notebooks/doc2vec-wikipedia.ipynb | 123 ++++++++++--- docs/notebooks/online_w2v_tutorial.ipynb | 95 +++++++--- docs/notebooks/word2vec.ipynb | 217 +++++++++++++++++------ gensim/models/doc2vec.py | 2 +- gensim/models/word2vec.py | 47 ++--- gensim/test/test_doc2vec.py | 6 +- gensim/test/test_word2vec.py | 37 ++-- 9 files changed, 543 insertions(+), 178 deletions(-) diff --git a/docs/notebooks/doc2vec-IMDB.ipynb b/docs/notebooks/doc2vec-IMDB.ipynb index 92f48e24c5..4fb30b7b93 100644 --- a/docs/notebooks/doc2vec-IMDB.ipynb +++ b/docs/notebooks/doc2vec-IMDB.ipynb @@ -600,7 +600,7 @@ " duration = 'na'\n", " train_model.alpha, train_model.min_alpha = alpha, alpha\n", " with elapsed_timer() as elapsed:\n", - " train_model.train(doc_list)\n", + " train_model.train(doc_list, total_examples=train_model.corpus_count, epochs=train_model.iter)\n", " duration = '%.1f' % elapsed()\n", " \n", " # evaluate\n", diff --git a/docs/notebooks/doc2vec-lee.ipynb b/docs/notebooks/doc2vec-lee.ipynb index cc6279fe86..92d01aa133 100644 --- a/docs/notebooks/doc2vec-lee.ipynb +++ b/docs/notebooks/doc2vec-lee.ipynb @@ -2,7 +2,10 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "# Doc2Vec Tutorial on the Lee Dataset" ] @@ -11,7 +14,9 @@ "cell_type": "code", "execution_count": 1, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -24,7 +29,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## What is it?\n", "\n", @@ -33,7 +41,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Resources\n", "\n", @@ -46,14 +57,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Getting Started" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "To get going, we'll need to have a set of documents to train our doc2vec model. In theory, a document could be anything from a short 140 character tweet, a single paragraph (i.e., journal article abstract), a news article, or a book. In NLP parlance a collection or set of documents is often referred to as a corpus. \n", "\n", @@ -67,7 +84,9 @@ "cell_type": "code", "execution_count": 2, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -79,14 +98,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Define a Function to Read and Preprocess Text" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Below, we define a function to open the train/test file (with latin encoding), read the file line-by-line, pre-process each line using a simple gensim pre-processing tool (i.e., tokenize text into individual words, remove punctuation, set to lowercase, etc), and return a list of words. Note that, for a given file (aka corpus), each continuous line constitutes a single document and the length of each line (i.e., document) can vary. Also, to train the model, we'll need to associate a tag/number with each document of the training corpus. In our case, the tag is simply the zero-based line number." ] @@ -95,7 +120,9 @@ "cell_type": "code", "execution_count": 3, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -113,7 +140,9 @@ "cell_type": "code", "execution_count": 4, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -123,7 +152,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Let's take a look at the training corpus" ] @@ -132,7 +164,9 @@ "cell_type": "code", "execution_count": 5, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -153,7 +187,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "And the testing corpus looks like this:" ] @@ -162,7 +199,9 @@ "cell_type": "code", "execution_count": 6, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -179,28 +218,40 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Notice that the testing corpus is just a list of lists and does not contain any tags." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Training the Model" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "### Instantiate a Doc2Vec Object " ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Now, we'll instantiate a Doc2Vec model with a vector size with 50 words and iterating over the training corpus 55 times. We set the minimum word count to 2 in order to give higher frequency words more weighting. Model accuracy can be improved by increasing the number of iterations but this generally increases the training time. Small datasets with short documents, like this one, can benefit from more training passes." ] @@ -209,7 +260,9 @@ "cell_type": "code", "execution_count": 7, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -218,7 +271,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "### Build a Vocabulary" ] @@ -227,7 +283,9 @@ "cell_type": "code", "execution_count": 8, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -236,14 +294,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Essentially, the vocabulary is a dictionary (accessible via `model.wv.vocab`) of all of the unique words extracted from the training corpus along with the count (e.g., `model.wv.vocab['penalty'].count` for counts for the word `penalty`)." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "### Time to Train\n", "\n", @@ -255,7 +319,9 @@ "cell_type": "code", "execution_count": 9, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -278,19 +344,25 @@ } ], "source": [ - "%time model.train(train_corpus)" + "%time model.train(train_corpus, total_examples=model.corpus_count, epochs=model.iter)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "### Inferring a Vector" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "One important thing to note is that you can now infer a vector for any piece of text without having to re-train the model by passing a list of words to the `model.infer_vector` function. This vector can then be compared with other vectors via cosine similarity." ] @@ -299,7 +371,9 @@ "cell_type": "code", "execution_count": 10, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -328,14 +402,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Assessing Model" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "To assess our new model, we'll first infer new vectors for each document of the training corpus, compare the inferred vectors with the training corpus, and then returning the rank of the document based on self-similarity. Basically, we're pretending as if the training corpus is some new unseen data and then seeing how they compare with the trained model. The expectation is that we've likely overfit our model (i.e., all of the ranks will be less than 2) and so we should be able to find similar documents very easily. Additionally, we'll keep track of the second ranks for a comparison of less similar documents. " ] @@ -344,7 +424,9 @@ "cell_type": "code", "execution_count": 11, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -361,7 +443,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Let's count how each document ranks with respect to the training corpus " ] @@ -371,6 +456,8 @@ "execution_count": 12, "metadata": { "collapsed": false, + "deletable": true, + "editable": true, "scrolled": true }, "outputs": [ @@ -391,7 +478,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Basically, greater than 95% of the inferred documents are found to be most similar to itself and about 5% of the time it is mistakenly most similar to another document. the checking of an inferred-vector against a training-vector is a sort of 'sanity check' as to whether the model is behaving in a usefully consistent manner, though not a real 'accuracy' value.\n", "\n", @@ -402,7 +492,9 @@ "cell_type": "code", "execution_count": 15, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -431,7 +523,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Notice above that the most similar document is has a similarity score of ~80% (or higher). However, the similarity score for the second ranked documents should be significantly lower (assuming the documents are in fact different) and the reasoning becomes obvious when we examine the text itself" ] @@ -440,7 +535,9 @@ "cell_type": "code", "execution_count": 14, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -466,14 +563,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Testing the Model" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Using the same approach above, we'll infer the vector for a randomly chosen test document, and compare the document to our model by eye." ] @@ -482,7 +585,9 @@ "cell_type": "code", "execution_count": 15, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -517,7 +622,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "### Wrapping Up\n", "\n", @@ -541,7 +649,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", - "version": "2.7.12" + "version": "2.7.6" } }, "nbformat": 4, diff --git a/docs/notebooks/doc2vec-wikipedia.ipynb b/docs/notebooks/doc2vec-wikipedia.ipynb index c5cd3e70f5..4e09fa318d 100644 --- a/docs/notebooks/doc2vec-wikipedia.ipynb +++ b/docs/notebooks/doc2vec-wikipedia.ipynb @@ -2,14 +2,20 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "# Doc2Vec to wikipedia articles" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "We conduct the replication to **Document Embedding with Paragraph Vectors** (http://arxiv.org/abs/1507.07998).\n", "In this paper, they showed only DBOW results to Wikipedia data. So we replicate this experiments using not only DBOW but also DM." @@ -17,14 +23,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Basic Setup" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Let's import Doc2Vec module." ] @@ -33,7 +45,9 @@ "cell_type": "code", "execution_count": 1, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -45,14 +59,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Preparing the corpus" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "First, download the dump of all Wikipedia articles from [here](http://download.wikimedia.org/enwiki/) (you want the file enwiki-latest-pages-articles.xml.bz2, or enwiki-YYYYMMDD-pages-articles.xml.bz2 for date-specific dumps).\n", "\n", @@ -65,7 +85,9 @@ "cell_type": "code", "execution_count": 2, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -75,7 +97,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Define **TaggedWikiDocument** class to convert WikiCorpus into suitable form for Doc2Vec." ] @@ -84,7 +109,9 @@ "cell_type": "code", "execution_count": 3, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -101,7 +128,9 @@ "cell_type": "code", "execution_count": 4, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -110,7 +139,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Preprocessing\n", "To set the same vocabulary size with original papar. We first calculate the optimal **min_count** parameter." @@ -120,7 +152,9 @@ "cell_type": "code", "execution_count": 5, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -132,7 +166,9 @@ "cell_type": "code", "execution_count": 6, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -169,14 +205,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "In the original paper, they set the vocabulary size 915,715. It seems similar size of vocabulary if we set min_count = 19. (size of vocab = 898,725)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Training the Doc2Vec Model\n", "To train Doc2Vec model by several method, DBOW and DM, we define the list of models." @@ -187,6 +229,8 @@ "execution_count": 7, "metadata": { "collapsed": false, + "deletable": true, + "editable": true, "scrolled": false }, "outputs": [], @@ -205,7 +249,9 @@ "cell_type": "code", "execution_count": 8, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -226,7 +272,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Now we’re ready to train Doc2Vec of the English Wikipedia. " ] @@ -236,6 +285,8 @@ "execution_count": 9, "metadata": { "collapsed": false, + "deletable": true, + "editable": true, "scrolled": true }, "outputs": [ @@ -252,19 +303,25 @@ ], "source": [ "for model in models:\n", - " %%time model.train(documents)" + " %%time model.train(documents, total_examples=model.corpus_count, epochs=model.iter)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Similarity interface" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "After that, let's test both models! DBOW model show the simillar results with the original paper. First, calculating cosine simillarity of \"Machine learning\" using Paragraph Vector. Word Vector and Document Vector are separately stored. We have to add .docvecs after model name to extract Document Vector from Doc2Vec Model." ] @@ -273,7 +330,9 @@ "cell_type": "code", "execution_count": 10, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -333,7 +392,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "DBOW model interpret the word 'Machine Learning' as a part of Computer Science field, and DM model as Data Science related field.\n", "\n", @@ -345,6 +407,8 @@ "execution_count": 11, "metadata": { "collapsed": false, + "deletable": true, + "editable": true, "scrolled": false }, "outputs": [ @@ -386,7 +450,9 @@ { "cell_type": "markdown", "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "source": [ "DBOW model reveal the similar singer in the U.S., and DM model understand that many of Lady Gaga's songs are similar with the word \"Lady Gaga\".\n", @@ -399,6 +465,8 @@ "execution_count": 12, "metadata": { "collapsed": false, + "deletable": true, + "editable": true, "scrolled": false }, "outputs": [ @@ -440,7 +508,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "As a result, DBOW model demonstrate the similar artists with Lady Gaga in Japan such as 'Perfume', which is the Most famous Idol in Japan. On the other hand, DM model results don't include the Japanese aritsts in top 10 simillar documents. It's almost same with no vector calculated results.\n", "\n", @@ -464,7 +535,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2" + "version": "3.4.3" } }, "nbformat": 4, diff --git a/docs/notebooks/online_w2v_tutorial.ipynb b/docs/notebooks/online_w2v_tutorial.ipynb index b233c67817..ed51565272 100644 --- a/docs/notebooks/online_w2v_tutorial.ipynb +++ b/docs/notebooks/online_w2v_tutorial.ipynb @@ -2,7 +2,10 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "# Online word2vec tutorial\n", "\n", @@ -15,7 +18,9 @@ "cell_type": "code", "execution_count": 1, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -28,7 +33,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "### Download wikipedia dump files\n", "\n", @@ -39,7 +47,9 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -50,7 +60,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "### Convert two wikipedia dump files\n", "To avoid alert when convert old verision of wikipedia dump, you should download alternative wikicorpus.py in my repo." @@ -60,7 +73,9 @@ "cell_type": "code", "execution_count": 2, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -71,7 +86,9 @@ "cell_type": "code", "execution_count": 3, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -89,7 +106,9 @@ "cell_type": "code", "execution_count": 4, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -101,7 +120,9 @@ "cell_type": "code", "execution_count": 5, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -110,7 +131,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "### Initial training\n", "At first we train word2vec using \"enwiki-20101011-pages-articles.xml.bz2\". After that, we update model using \"enwiki-20160820-pages-articles.xml.bz2\"." @@ -120,7 +144,9 @@ "cell_type": "code", "execution_count": 6, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -142,7 +168,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "#### Japanese new idol group, [\"Babymetal\"](https://en.wikipedia.org/wiki/Babymetal), weren't known worldwide in 2010, so that the word, \"babymetal\", is not in oldmodel vocaburary.\n", "Note: In recent years, they became the famous idol group not only in Japan. They won many music awards and run world tour." @@ -152,7 +181,9 @@ "cell_type": "code", "execution_count": 7, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -172,7 +203,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Online update\n", "To use online word2vec feature, set update=True when you use build_vocab using new documents." @@ -182,7 +216,9 @@ "cell_type": "code", "execution_count": 8, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -197,14 +233,17 @@ "source": [ "%%time\n", "model.build_vocab(newwiki, update=True)\n", - "model.train(newwiki)\n", + "model.train(newwiki, total_examples=model.corpus_count, epochs=model.iter)\n", "model.save('newmodel')\n", "# model = Word2Vec.load('newmodel')" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "#### Model Comparison\n", "By the online training, the size of vocaburaries are increased about 3 millions." @@ -214,7 +253,9 @@ "cell_type": "code", "execution_count": 9, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -233,7 +274,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "#### After online training, the word, \"babymetal\", is added in model. This word is simillar with rock and metal bands." ] @@ -242,7 +286,9 @@ "cell_type": "code", "execution_count": 10, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -271,7 +317,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## The word, \"Zootopia\", become disney movie through the years.\n", "In the past, the word, \"Zootopia\", was used just for an annual summer concert put on by New York top-40 radio station Z100, so that the word, \"zootopia\", is simillar with music festival.\n", @@ -284,6 +333,8 @@ "execution_count": 11, "metadata": { "collapsed": false, + "deletable": true, + "editable": true, "scrolled": false }, "outputs": [ @@ -343,7 +394,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", - "version": "2.7.12" + "version": "2.7.6" } }, "nbformat": 4, diff --git a/docs/notebooks/word2vec.ipynb b/docs/notebooks/word2vec.ipynb index 4d7344c11c..1f490950fa 100644 --- a/docs/notebooks/word2vec.ipynb +++ b/docs/notebooks/word2vec.ipynb @@ -2,7 +2,10 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "# Word2Vec Tutorial\n", "\n", @@ -19,7 +22,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Preparing the Input\n", "Starting from the beginning, gensim’s `word2vec` expects a sequence of sentences as its input. Each sentence a list of words (utf8 strings):" @@ -29,7 +35,9 @@ "cell_type": "code", "execution_count": 1, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -42,7 +50,9 @@ "cell_type": "code", "execution_count": 2, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -61,7 +71,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Keeping the input as a Python built-in list is convenient, but can use up a lot of RAM when the input is large.\n", "\n", @@ -74,7 +87,9 @@ "cell_type": "code", "execution_count": 3, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -96,7 +111,9 @@ "cell_type": "code", "execution_count": 4, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -114,7 +131,9 @@ "cell_type": "code", "execution_count": 5, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -134,7 +153,9 @@ "cell_type": "code", "execution_count": 6, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -154,7 +175,9 @@ "cell_type": "code", "execution_count": 7, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -173,7 +196,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Say we want to further preprocess the words from the files — convert to unicode, lowercase, remove numbers, extract named entities… All of this can be done inside the `MySentences` iterator and `word2vec` doesn’t need to know. All that is required is that the input yields one sentence (list of utf8 words) after another.\n", "\n", @@ -188,7 +214,9 @@ "cell_type": "code", "execution_count": 8, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -213,14 +241,17 @@ "# build the same model, making the 2 steps explicit\n", "new_model = gensim.models.Word2Vec(min_count=1) # an empty model, no training\n", "new_model.build_vocab(sentences) # can be a non-repeatable, 1-pass generator \n", - "new_model.train(sentences) # can be a non-repeatable, 1-pass generator" + "new_model.train(sentences, total_examples=new_model.corpus_count, epochs=new_model.iter) \n", + "# can be a non-repeatable, 1-pass generator" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -239,7 +270,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## More data would be nice\n", "For the following examples, we'll use the [Lee Corpus](https://github.com/RaRe-Technologies/gensim/blob/develop/gensim/test/test_data/lee_background.cor) (which you already have if you've installed gensim):" @@ -249,7 +283,9 @@ "cell_type": "code", "execution_count": 10, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -262,7 +298,9 @@ "cell_type": "code", "execution_count": 11, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -287,7 +325,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Training\n", "`Word2Vec` accepts several parameters that affect both training speed and quality.\n", @@ -299,7 +340,9 @@ "cell_type": "code", "execution_count": 12, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -311,7 +354,9 @@ "cell_type": "code", "execution_count": 13, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -321,7 +366,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Bigger size values require more training data, but can lead to better (more accurate) models. Reasonable values are in the tens to hundreds.\n", "\n", @@ -332,7 +380,9 @@ "cell_type": "code", "execution_count": 14, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -350,14 +400,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "The `workers` parameter only has an effect if you have [Cython](http://cython.org/) installed. Without Cython, you’ll only be able to use one core because of the [GIL](https://wiki.python.org/moin/GlobalInterpreterLock) (and `word2vec` training will be [miserably slow](http://rare-technologies.com/word2vec-in-python-part-two-optimizing/))." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Memory\n", "At its core, `word2vec` model parameters are stored as matrices (NumPy arrays). Each array is **#vocabulary** (controlled by min_count parameter) times **#size** (size parameter) of floats (single precision aka 4 bytes).\n", @@ -369,7 +425,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Evaluating\n", "`Word2Vec` training is an unsupervised task, there’s no good way to objectively evaluate the result. Evaluation depends on your end application.\n", @@ -383,7 +442,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Gensim support the same evaluation set, in exactly the same format:" ] @@ -392,7 +454,9 @@ "cell_type": "code", "execution_count": 15, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -630,7 +694,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "This `accuracy` takes an \n", "[optional parameter](http://radimrehurek.com/gensim/models/word2vec.html#gensim.models.word2vec.Word2Vec.accuracy) `restrict_vocab` \n", @@ -640,7 +707,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "In the December 2016 release of Gensim we added a better way to evaluate semantic similarity.\n", "\n", @@ -651,7 +721,9 @@ "cell_type": "code", "execution_count": 16, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -673,14 +745,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "Once again, **good performance on Google's or WS-353 test set doesn’t mean word2vec will work well in your application, or vice versa**. It’s always best to evaluate directly on your intended task. For an example of how to use word2vec in a classifier pipeline, see this [tutorial](https://github.com/RaRe-Technologies/movie-plots-by-genre)." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Storing and loading models\n", "You can store/load models using the standard gensim methods:" @@ -690,7 +768,9 @@ "cell_type": "code", "execution_count": 17, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -705,7 +785,9 @@ "cell_type": "code", "execution_count": 18, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -714,7 +796,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "which uses pickle internally, optionally `mmap`‘ing the model’s internal large NumPy matrices into virtual memory directly from disk files, for inter-process memory sharing.\n", "\n", @@ -727,7 +812,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Online training / Resuming training\n", "Advanced users can load a model and continue training it with more sentences and [new vocabulary words](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/online_w2v_tutorial.ipynb):" @@ -737,7 +825,9 @@ "cell_type": "code", "execution_count": 19, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -753,7 +843,7 @@ "more_sentences = [['Advanced', 'users', 'can', 'load', 'a', 'model', 'and', 'continue', \n", " 'training', 'it', 'with', 'more', 'sentences']]\n", "model.build_vocab(more_sentences, update=True)\n", - "model.train(more_sentences, )\n", + "model.train(more_sentences, total_examples=model.corpus_count, epochs=model.iter)\n", "\n", "# cleaning up temp\n", "os.close(fs)\n", @@ -762,7 +852,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "You may need to tweak the `total_words` parameter to `train()`, depending on what learning rate decay you want to simulate.\n", "\n", @@ -776,7 +869,9 @@ "cell_type": "code", "execution_count": 20, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -798,7 +893,9 @@ "cell_type": "code", "execution_count": 21, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -827,7 +924,9 @@ "cell_type": "code", "execution_count": 22, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -846,7 +945,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "You can get the probability distribution for the center word given the context words as input:" ] @@ -855,7 +957,9 @@ "cell_type": "code", "execution_count": 23, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -872,14 +976,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "The results here don't look good because the training corpus is very small. To get meaningful results one needs to train on 500k+ words." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "If you need the raw output vectors in your application, you can access these either on a word-by-word basis:" ] @@ -888,7 +998,9 @@ "cell_type": "code", "execution_count": 24, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -927,7 +1039,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "…or en-masse as a 2D NumPy matrix from `model.wv.syn0`.\n", "\n", @@ -945,7 +1060,9 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [] @@ -968,7 +1085,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", - "version": "2.7.10" + "version": "2.7.6" } }, "nbformat": 4, diff --git a/gensim/models/doc2vec.py b/gensim/models/doc2vec.py index fdf00e430c..a166d17687 100644 --- a/gensim/models/doc2vec.py +++ b/gensim/models/doc2vec.py @@ -632,7 +632,7 @@ def __init__(self, documents=None, dm_mean=None, self.comment = comment if documents is not None: self.build_vocab(documents, trim_rule=trim_rule) - self.train(documents) + self.train(documents, total_examples=self.corpus_count, epochs=self.iter) @property def dm(self): diff --git a/gensim/models/word2vec.py b/gensim/models/word2vec.py index c29d61126c..d14894ef8a 100644 --- a/gensim/models/word2vec.py +++ b/gensim/models/word2vec.py @@ -474,8 +474,8 @@ def __init__( if isinstance(sentences, GeneratorType): raise TypeError("You can't pass a generator as the sentences argument. Try an iterator.") self.build_vocab(sentences, trim_rule=trim_rule) - self.train(sentences) - + self.train(sentences, total_examples=self.corpus_count, epochs=self.iter, + start_alpha=self.alpha, end_alpha=self.min_alpha) else : if trim_rule is not None : logger.warning("The rule, if given, is only used prune vocabulary during build_vocab() and is not stored as part of the model. ") @@ -761,16 +761,23 @@ def _raw_word_count(self, job): """Return the number of words in a given job.""" return sum(len(sentence) for sentence in job) - def train(self, sentences, total_words=None, word_count=0, - total_examples=None, queue_factor=2, report_delay=1.0): + def train(self, sentences, total_examples=None, total_words=None, + epochs=None, start_alpha=None, end_alpha=None, + word_count=0, + queue_factor=2, report_delay=1.0): """ Update the model's neural weights from a sequence of sentences (can be a once-only generator stream). For Word2Vec, each sentence must be a list of unicode strings. (Subclasses may accept other examples.) - To support linear learning-rate decay from (initial) alpha to min_alpha, either total_examples - (count of sentences) or total_words (count of raw words in sentences) should be provided, unless the - sentences are the same as those that were used to initially build the vocabulary. + To support linear learning-rate decay from (initial) alpha to min_alpha, and accurate + progres-percentage logging, either total_examples (count of sentences) or total_words (count of + raw words in sentences) MUST be provided. (If the corpus is the same as was provided to + `build_vocab()`, the count of examples in that corpus will be available in the model's + `corpus_count` property.) + To avoid common mistakes around the model's ability to do multiple training passes itself, an + explicit `epochs` argument MUST be provided. In the common and recommended case, where `train()` + is only called once, the model's cached `iter` value should be supplied as `epochs` value. """ if (self.model_trimmed_post_training): raise RuntimeError("Parameters for training were discarded using model_trimmed_post_training method") @@ -802,18 +809,18 @@ def train(self, sentences, total_words=None, word_count=0, "Instead start with a blank model, scan_vocab on the new corpus, intersect_word2vec_format with the old model, then train.") if total_words is None and total_examples is None: - if self.corpus_count: - total_examples = self.corpus_count - logger.info("expecting %i sentences, matching count from corpus used for vocabulary survey", total_examples) - else: - raise ValueError("you must provide either total_words or total_examples, to enable alpha and progress calculations") + raise ValueError("you must specify either total_examples or total_words, for proper alpha and progress calculations") + if epochs is None: + raise ValueError("you must specify an explict epochs count") + start_alpha = start_alpha or self.alpha + end_alpha = end_alpha or self.min_alpha job_tally = 0 - if self.iter > 1: - sentences = utils.RepeatCorpusNTimes(sentences, self.iter) - total_words = total_words and total_words * self.iter - total_examples = total_examples and total_examples * self.iter + if epochs > 1: + sentences = utils.RepeatCorpusNTimes(sentences, epochs) + total_words = total_words and total_words * epochs + total_examples = total_examples and total_examples * epochs def worker_loop(): """Train the model, lifting lists of sentences from the job_queue.""" @@ -835,7 +842,7 @@ def job_producer(): """Fill jobs queue using the input `sentences` iterator.""" job_batch, batch_size = [], 0 pushed_words, pushed_examples = 0, 0 - next_alpha = self.alpha + next_alpha = start_alpha if next_alpha > self.min_alpha_yet_reached: logger.warn("Effective 'alpha' higher than previous training cycles") self.min_alpha_yet_reached = next_alpha @@ -858,7 +865,7 @@ def job_producer(): job_queue.put((job_batch, next_alpha)) # update the learning rate for the next job - if self.min_alpha < next_alpha: + if end_alpha < next_alpha: if total_examples: # examples-based decay pushed_examples += len(job_batch) @@ -867,8 +874,8 @@ def job_producer(): # words-based decay pushed_words += self._raw_word_count(job_batch) progress = 1.0 * pushed_words / total_words - next_alpha = self.alpha - (self.alpha - self.min_alpha) * progress - next_alpha = max(self.min_alpha, next_alpha) + next_alpha = start_alpha - (start_alpha - end_alpha) * progress + next_alpha = max(end_alpha, next_alpha) # add the sentence that didn't fit as the first item of a new job job_batch, batch_size = [sentence], sentence_length diff --git a/gensim/test/test_doc2vec.py b/gensim/test/test_doc2vec.py index 55b9b5f3f0..1cc32f0095 100644 --- a/gensim/test/test_doc2vec.py +++ b/gensim/test/test_doc2vec.py @@ -183,7 +183,7 @@ def model_sanity(self, model, keep_training=True): if keep_training: model.save(testfile()) loaded = doc2vec.Doc2Vec.load(testfile()) - loaded.train(sentences) + loaded.train(sentences, total_examples=loaded.corpus_count, epochs=loaded.iter) def test_training(self): """Test doc2vec training.""" @@ -191,7 +191,7 @@ def test_training(self): model = doc2vec.Doc2Vec(size=100, min_count=2, iter=20, workers=1) model.build_vocab(corpus) self.assertEqual(model.docvecs.doctag_syn0.shape, (300, 100)) - model.train(corpus) + model.train(corpus, total_examples=model.corpus_count, epochs=model.iter) self.model_sanity(model) @@ -347,7 +347,7 @@ def testTrainWarning(self, l): model = doc2vec.Doc2Vec(alpha=0.025, min_alpha=0.025, min_count=1, workers=8, size=5) model.build_vocab(sentences) for epoch in range(10): - model.train(sentences) + model.train(sentences, total_examples=model.corpus_count, epochs=model.iter) model.alpha -= 0.002 model.min_alpha = model.alpha if epoch == 5: diff --git a/gensim/test/test_word2vec.py b/gensim/test/test_word2vec.py index e37968218c..bbb09a21ab 100644 --- a/gensim/test/test_word2vec.py +++ b/gensim/test/test_word2vec.py @@ -103,7 +103,7 @@ def testOnlineLearningAfterSave(self): model_neg = word2vec.Word2Vec.load(testfile()) self.assertTrue(len(model_neg.wv.vocab), 12) model_neg.build_vocab(new_sentences, update=True) - model_neg.train(new_sentences) + model_neg.train(new_sentences, total_examples=model_neg.corpus_count, epochs=model_neg.iter) self.assertEqual(len(model_neg.wv.vocab), 14) @@ -116,12 +116,12 @@ def onlineSanity(self, model): others.append(l) self.assertTrue(all(['terrorism' not in l for l in others])) model.build_vocab(others) - model.train(others) + model.train(others, total_examples=model.corpus_count, epochs=model.iter) self.assertFalse('terrorism' in model.wv.vocab) model.build_vocab(terro, update=True) self.assertTrue('terrorism' in model.wv.vocab) orig0 = np.copy(model.wv.syn0) - model.train(terro) + model.train(terro, total_examples=len(terro), epochs=model.iter) self.assertFalse(np.allclose(model.wv.syn0, orig0)) sim = model.n_similarity(['war'], ['terrorism']) self.assertLess(0., sim) @@ -363,7 +363,7 @@ def testTraining(self): self.assertTrue(model.wv.syn0.shape == (len(model.wv.vocab), 2)) self.assertTrue(model.syn1.shape == (len(model.wv.vocab), 2)) - model.train(sentences) + model.train(sentences, total_examples=model.corpus_count, epochs=model.iter) sims = model.most_similar('graph', topn=10) # self.assertTrue(sims[0][0] == 'trees', sims) # most similar @@ -399,7 +399,7 @@ def testLocking(self): # lock the vector in slot 0 against change model.syn0_lockf[0] = 0.0 - model.train(corpus) + model.train(corpus, total_examples=model.corpus_count, epochs=model.iter) self.assertFalse((unlocked1 == model.wv.syn0[1]).all()) # unlocked vector should vary self.assertTrue((locked0 == model.wv.syn0[0]).all()) # locked vector should not vary @@ -428,7 +428,7 @@ def model_sanity(self, model, train=True): if train: model.build_vocab(list_corpus) orig0 = np.copy(model.wv.syn0[0]) - model.train(list_corpus) + model.train(list_corpus, total_examples=model.corpus_count, epochs=model.iter) self.assertFalse((orig0 == model.wv.syn0[1]).all()) # vector should vary after training sims = model.most_similar('war', topn=len(model.wv.index2word)) t_rank = [word for word, score in sims].index('terrorism') @@ -481,7 +481,7 @@ def testTrainingCbow(self): self.assertTrue(model.wv.syn0.shape == (len(model.wv.vocab), 2)) self.assertTrue(model.syn1.shape == (len(model.wv.vocab), 2)) - model.train(sentences) + model.train(sentences, total_examples=model.corpus_count, epochs=model.iter) sims = model.most_similar('graph', topn=10) # self.assertTrue(sims[0][0] == 'trees', sims) # most similar @@ -504,7 +504,7 @@ def testTrainingSgNegative(self): self.assertTrue(model.wv.syn0.shape == (len(model.wv.vocab), 2)) self.assertTrue(model.syn1neg.shape == (len(model.wv.vocab), 2)) - model.train(sentences) + model.train(sentences, total_examples=model.corpus_count, epochs=model.iter) sims = model.most_similar('graph', topn=10) # self.assertTrue(sims[0][0] == 'trees', sims) # most similar @@ -527,7 +527,7 @@ def testTrainingCbowNegative(self): self.assertTrue(model.wv.syn0.shape == (len(model.wv.vocab), 2)) self.assertTrue(model.syn1neg.shape == (len(model.wv.vocab), 2)) - model.train(sentences) + model.train(sentences, total_examples=model.corpus_count, epochs=model.iter) sims = model.most_similar('graph', topn=10) # self.assertTrue(sims[0][0] == 'trees', sims) # most similar @@ -546,7 +546,7 @@ def testSimilarities(self): # The model is trained using CBOW model = word2vec.Word2Vec(size=2, min_count=1, sg=0, hs=0, negative=2) model.build_vocab(sentences) - model.train(sentences) + model.train(sentences, total_examples=model.corpus_count, epochs=model.iter) self.assertTrue(model.n_similarity(['graph', 'trees'], ['trees', 'graph'])) self.assertTrue(model.n_similarity(['graph'], ['trees']) == model.similarity('graph', 'trees')) @@ -660,7 +660,7 @@ def testTrainWarning(self, l): model = word2vec.Word2Vec(min_count=1) model.build_vocab(sentences) for epoch in range(10): - model.train(sentences) + model.train(sentences, total_examples=model.corpus_count, epochs=model.iter) model.alpha -= 0.002 model.min_alpha = model.alpha if epoch == 5: @@ -668,6 +668,17 @@ def testTrainWarning(self, l): warning = "Effective 'alpha' higher than previous training cycles" self.assertTrue(warning in str(l)) + def test_train_with_explicit_param(self): + model = word2vec.Word2Vec(size=2, min_count=1, hs=1, negative=0) + model.build_vocab(sentences) + with self.assertRaises(ValueError): + model.train(sentences, total_examples=model.corpus_count) + + with self.assertRaises(ValueError): + model.train(sentences, epochs=model.iter) + + with self.assertRaises(ValueError): + model.train(sentences) def test_sentences_should_not_be_a_generator(self): """ Is sentences a generator object? @@ -682,11 +693,11 @@ def testLoadOnClassError(self): def test_reset_from(self): """Test if reset_from() uses pre-built structures from other model""" model = word2vec.Word2Vec(sentences, min_count=1) - other_model = word2vec.Word2Vec(new_sentences, min_count=1) + other_model = word2vec.Word2Vec(new_sentences, min_count=1) other_vocab = other_model.wv.vocab model.reset_from(other_model) self.assertEqual(model.wv.vocab, other_vocab) - + #endclass TestWord2VecModel From ed49ea63fb0e49450e9219423d19b929e0402ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=A0m=C3=ADd?= Date: Thu, 30 Mar 2017 16:03:21 +0200 Subject: [PATCH 30/41] FastText faster load. Correct vector_size. Fix #1196. (#1214) * Issue 1196 * Fixed assert * Fixed test * Issue-1196 Updated Unit Test * Issue-1196 Used more fitting assert * Issue-1196 Used more fitting assert * Issue-1196 Incorporated Code Review --- CHANGELOG.md | 3 ++ gensim/models/keyedvectors.py | 2 ++ gensim/models/wrappers/fasttext.py | 8 ++--- gensim/test/test_fasttext_wrapper.py | 45 +++++++++++++++++++++------- 4 files changed, 44 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f04d416158..77f6197a0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ Unreleased: New features: * Add output word prediction for negative sampling scheme. (@chinmayapancholi13,[#1209](https://github.com/RaRe-Technologies/gensim/pull/1209)) +Improvements: +* Fix loading large FastText models on Mac. (@jaksmid,[#1196](https://github.com/RaRe-Technologies/gensim/pull/1214)) + ======== 1.0.1, 2017-03-03 diff --git a/gensim/models/keyedvectors.py b/gensim/models/keyedvectors.py index 601c48e374..51918855a0 100644 --- a/gensim/models/keyedvectors.py +++ b/gensim/models/keyedvectors.py @@ -111,6 +111,7 @@ def __init__(self): self.syn0norm = None self.vocab = {} self.index2word = [] + self.vector_size = None def save(self, *args, **kwargs): # don't bother storing the cached normalized vectors @@ -194,6 +195,7 @@ def load_word2vec_format(cls, fname, fvocab=None, binary=False, encoding='utf8', if limit: vocab_size = min(vocab_size, limit) result = cls() + result.vector_size = vector_size result.syn0 = zeros((vocab_size, vector_size), dtype=datatype) def add_word(word, weights): diff --git a/gensim/models/wrappers/fasttext.py b/gensim/models/wrappers/fasttext.py index 6316976f29..771c09b3de 100644 --- a/gensim/models/wrappers/fasttext.py +++ b/gensim/models/wrappers/fasttext.py @@ -259,7 +259,7 @@ def load_binary_data(self, model_binary_file, encoding='utf8'): def load_model_params(self, file_handle): (dim, ws, epoch, minCount, neg, _, loss, model, bucket, minn, maxn, _, t) = self.struct_unpack(file_handle, '@12i1d') # Parameters stored by [Args::save](https://github.com/facebookresearch/fastText/blob/master/src/args.cc) - self.size = dim + self.vector_size = dim self.window = ws self.iter = epoch self.min_count = minCount @@ -293,7 +293,7 @@ def load_dict(self, file_handle, encoding='utf8'): def load_vectors(self, file_handle): num_vectors, dim = self.struct_unpack(file_handle, '@2q') # Vectors stored by [Matrix::save](https://github.com/facebookresearch/fastText/blob/master/src/matrix.cc) - assert self.size == dim, 'mismatch between model sizes' + assert self.vector_size == dim, 'mismatch between model sizes' float_size = struct.calcsize('@f') if float_size == 4: dtype = np.dtype(np.float32) @@ -301,9 +301,9 @@ def load_vectors(self, file_handle): dtype = np.dtype(np.float64) self.num_original_vectors = num_vectors - self.wv.syn0_all = np.fromstring(file_handle.read(num_vectors * dim * float_size), dtype=dtype) + self.wv.syn0_all = np.fromfile(file_handle, dtype=dtype, count=num_vectors * dim) self.wv.syn0_all = self.wv.syn0_all.reshape((num_vectors, dim)) - assert self.wv.syn0_all.shape == (self.bucket + len(self.wv.vocab), self.size), \ + assert self.wv.syn0_all.shape == (self.bucket + len(self.wv.vocab), self.vector_size), \ 'mismatch between weight matrix shape and vocab/model size' self.init_ngrams() diff --git a/gensim/test/test_fasttext_wrapper.py b/gensim/test/test_fasttext_wrapper.py index ea7593263c..e1fd2cd722 100644 --- a/gensim/test/test_fasttext_wrapper.py +++ b/gensim/test/test_fasttext_wrapper.py @@ -8,7 +8,6 @@ Automated tests for checking transformation algorithms (the models package). """ - import logging import unittest import os @@ -19,7 +18,7 @@ from gensim.models.wrappers import fasttext from gensim.models import keyedvectors -module_path = os.path.dirname(__file__) # needed because sample data files are located in the same folder +module_path = os.path.dirname(__file__) # needed because sample data files are located in the same folder datapath = lambda fname: os.path.join(module_path, 'test_data', fname) logger = logging.getLogger(__name__) @@ -28,6 +27,7 @@ def testfile(): # temporary data will be stored to this file return os.path.join(tempfile.gettempdir(), 'gensim_fasttext.tst') + class TestFastText(unittest.TestCase): @classmethod def setUp(self): @@ -40,8 +40,8 @@ def setUp(self): def model_sanity(self, model): """Even tiny models trained on any corpus should pass these sanity checks""" - self.assertEqual(model.wv.syn0.shape, (len(model.wv.vocab), model.size)) - self.assertEqual(model.wv.syn0_all.shape, (model.num_ngram_vectors, model.size)) + self.assertEqual(model.wv.syn0.shape, (len(model.wv.vocab), model.vector_size)) + self.assertEqual(model.wv.syn0_all.shape, (model.num_ngram_vectors, model.vector_size)) def models_equal(self, model1, model2): self.assertEqual(len(model1.wv.vocab), len(model2.wv.vocab)) @@ -74,7 +74,7 @@ def testMinCount(self): return # Use self.skipTest once python < 2.7 is no longer supported self.assertTrue('forests' not in self.test_model.wv.vocab) test_model_min_count_1 = fasttext.FastText.train( - self.ft_path, self.corpus_file, output_file=testfile(), size=10, min_count=1) + self.ft_path, self.corpus_file, output_file=testfile(), size=10, min_count=1) self.assertTrue('forests' in test_model_min_count_1.wv.vocab) def testModelSize(self): @@ -83,8 +83,8 @@ def testModelSize(self): logger.info("FT_HOME env variable not set, skipping test") return # Use self.skipTest once python < 2.7 is no longer supported test_model_size_20 = fasttext.FastText.train( - self.ft_path, self.corpus_file, output_file=testfile(), size=20) - self.assertEqual(test_model_size_20.size, 20) + self.ft_path, self.corpus_file, output_file=testfile(), size=20) + self.assertEqual(test_model_size_20.vector_size, 20) self.assertEqual(test_model_size_20.wv.syn0.shape[1], 20) self.assertEqual(test_model_size_20.wv.syn0_all.shape[1], 20) @@ -118,6 +118,25 @@ def testLoadFastTextFormat(self): self.assertEqual(self.test_model.wv.syn0.shape, (vocab_size, model_size)) self.assertEqual(len(self.test_model.wv.vocab), vocab_size, model_size) self.assertEqual(self.test_model.wv.syn0_all.shape, (self.test_model.num_ngram_vectors, model_size)) + expected_vec = [-0.5714373588562012, + -0.008556111715734005, + 0.15747803449630737, + -0.6785456538200378, + -0.25458523631095886, + -0.5807671546936035, + -0.09912964701652527, + 1.1446694135665894, + 0.23417705297470093, + 0.06000664085149765] + self.assertTrue(numpy.allclose(self.test_model["hundred"], expected_vec, 0.001)) + self.assertEquals(self.test_model.min_count, 5) + self.assertEquals(self.test_model.window, 5) + self.assertEquals(self.test_model.iter, 5) + self.assertEquals(self.test_model.negative, 5) + self.assertEquals(self.test_model.sample, 0.0001) + self.assertEquals(self.test_model.bucket, 1000) + self.assertEquals(self.test_model.wv.max_n, 6) + self.assertEquals(self.test_model.wv.min_n, 3) self.model_sanity(model) def testLoadModelWithNonAsciiVocab(self): @@ -145,7 +164,8 @@ def testNSimilarity(self): self.assertEqual(self.test_model.n_similarity(['the'], ['and']), self.test_model.n_similarity(['and'], ['the'])) # Out of vocab check self.assertTrue(numpy.allclose(self.test_model.n_similarity(['night', 'nights'], ['nights', 'night']), 1.0)) - self.assertEqual(self.test_model.n_similarity(['night'], ['nights']), self.test_model.n_similarity(['nights'], ['night'])) + self.assertEqual(self.test_model.n_similarity(['night'], ['nights']), + self.test_model.n_similarity(['nights'], ['night'])) def testSimilarity(self): """Test similarity for in-vocab and out-of-vocab words""" @@ -169,10 +189,14 @@ def testMostSimilarCosmul(self): """Test most_similar_cosmul for in-vocab and out-of-vocab words""" # In vocab, sanity check self.assertEqual(len(self.test_model.most_similar_cosmul(positive=['the', 'and'], topn=5)), 5) - self.assertEqual(self.test_model.most_similar_cosmul('the'), self.test_model.most_similar_cosmul(positive=['the'])) + self.assertEqual( + self.test_model.most_similar_cosmul('the'), + self.test_model.most_similar_cosmul(positive=['the'])) # Out of vocab check self.assertEqual(len(self.test_model.most_similar_cosmul(['night', 'nights'], topn=5)), 5) - self.assertEqual(self.test_model.most_similar_cosmul('nights'), self.test_model.most_similar_cosmul(positive=['nights'])) + self.assertEqual( + self.test_model.most_similar_cosmul('nights'), + self.test_model.most_similar_cosmul(positive=['nights'])) def testLookup(self): """Tests word vector lookup for in-vocab and out-of-vocab words""" @@ -226,6 +250,7 @@ def testHash(self): ft_hash = fasttext.FastText.ft_hash('word') self.assertEqual(ft_hash, 1788406269) + if __name__ == '__main__': logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.DEBUG) unittest.main() From 4fcf0d79838cf1713a28b5cf4413d3ca7738b0fc Mon Sep 17 00:00:00 2001 From: Henry Senyondo Date: Thu, 30 Mar 2017 13:36:15 -0400 Subject: [PATCH 31/41] Correct syntax in Tutorials (#1249) --- tutorials.md | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/tutorials.md b/tutorials.md index caaa76d833..648ce5f61c 100644 --- a/tutorials.md +++ b/tutorials.md @@ -1,41 +1,41 @@ -###Tutorials +### Tutorials -#####Quick-start +##### Quick-start * [Getting Started with gensim](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/gensim%20Quick%20Start.ipynb) -#####Text to Vectors +##### Text to Vectors * We first need to transform text to vectors * [String to vectors tutorial](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/Corpora_and_Vector_Spaces.ipynb) * Create a dictionary first that maps words to ids * Transform the text into vectors through ```dictionary.doc2bow(texts)``` * [Corpus streaming tutorial](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/Corpora_and_Vector_Spaces.ipynb) (For very large corpuses) -#####Models and Transformation +##### Models and Transformation * Models (e.g. LsiModel, Word2Vec) are built / trained from a corpus * [Transformation interface tutorial](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/Topics_and_Transformations.ipynb) -#####TF-IDF (Model) +##### TF-IDF (Model) * [Docs](https://radimrehurek.com/gensim/models/tfidfmodel.html), [Source](https://github.com/piskvorky/gensim/blob/develop/gensim/models/tfidfmodel.py) * [tf-idf scores are normalized](http://stackoverflow.com/questions/9470479/how-is-tf-idf-implemented-in-gensim-tool-in-python) (sum of squares of scores = 1) -#####Phrases (Model) +##### Phrases (Model) * Detects words that belong in a phrase, useful for models like Word2Vec ("new", "york" -> "new york") * [Docs](https://radimrehurek.com/gensim/models/phrases.html), [Source](https://github.com/piskvorky/gensim/blob/develop/gensim/models/phrases.py) (uses bigram detectors underneath) * [Phrases example on How I Met Your Mother](http://www.markhneedham.com/blog/2015/02/12/pythongensim-creating-bigrams-over-how-i-met-your-mother-transcripts/) -####Topic Modeling +#### Topic Modeling -#####LSI (Model) +##### LSI (Model) * [Docs](https://radimrehurek.com/gensim/models/lsimodel.html), [Source](https://github.com/piskvorky/gensim/blob/develop/gensim/models/lsimodel.py) (very standard LSI implementation) * [How to interpret negative LSI values](https://www.researchgate.net/post/LSA_SVD_How_to_statistically_interpret_negative_values_in_U_and_Vt) * [Random Projection](https://radimrehurek.com/gensim/models/rpmodel.html) (used as an option to speed up LSI) -#####LDA (Model) +##### LDA (Model) * [Docs](https://radimrehurek.com/gensim/models/ldamodel.html), [Source](https://github.com/piskvorky/gensim/blob/develop/gensim/models/ldamodel.py) * [Example with Android issue reports](http://christop.club/2014/05/06/using-gensim-for-lda/), [Another example](https://rstudio-pubs-static.s3.amazonaws.com/79360_850b2a69980c4488b1db95987a24867a.html), [Another example](http://brandonrose.org/clustering#Latent-Dirichlet-Allocation) -#####Topic Model Tuning +##### Topic Model Tuning * [Colouring words by topic in a document, print words in a topics](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/topic_methods.ipynb) * [Topic Coherence, a metric that correlates that human judgement on topic quality.](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/topic_coherence_tutorial.ipynb) * [Compare topics and documents using Jaccard, Kullback-Leibler and Hellinger similarities](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/distance_metrics.ipynb) @@ -43,25 +43,25 @@ * [Classification of News Articles using Topic Modeling](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/gensim_news_classification.ipynb) * [LDA: pre-processing and training tips](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/lda_training_tips.ipynb) -#####Query Similarities +##### Query Similarities * Tool to get the most similar documents for LDA, LSI * [Similarity queries tutorial](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/Similarity_Queries.ipynb) -#####Dynamic Topic Modeling +##### Dynamic Topic Modeling * Model evolution of topics through time * [Easy intro to DTM. Evolution of Voldemort topic through the 7 Harry Potter books.](http://rare-technologies.com/understanding-and-coding-dynamic-topic-models/) * [Dynamic Topic Modeling and Dynamic Influence Model Tutorial](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/dtm_example.ipynb) * [Python Dynamic Topic Modelling Theory and Tutorial](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/ldaseqmodel.ipynb) -####Word Embeddings +#### Word Embeddings -#####Word2Vec (Model) +##### Word2Vec (Model) * [Docs](https://radimrehurek.com/gensim/models/word2vec.html), [Source](https://github.com/piskvorky/gensim/blob/develop/gensim/models/word2vec.py) (very simple interface) * [Simple word2vec tutorial](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/word2vec.ipynb) (examples of ```most_similar, similarity, doesnt_match```) * [Comparison of FastText and Word2Vec](https://github.com/RaRe-Technologies/gensim/blob/ba1ce894a5192fc493a865c535202695bb3c0424/docs/notebooks/Word2Vec_FastText_Comparison.ipynb) -#####Doc2Vec (Model) +##### Doc2Vec (Model) * [Doc2vec Quick Start on Lee Corpus](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/doc2vec-lee.ipynb) * [Docs](https://radimrehurek.com/gensim/models/doc2vec.html), [Source](https://github.com/piskvorky/gensim/blob/develop/gensim/models/doc2vec.py) (Docs are not very good) * Doc2Vec requires a non-standard corpus (need sentiment label for each document) @@ -70,35 +70,35 @@ * [Doc2Vec on Airline Tweets Sentiment Analysis](https://www.zybuluo.com/HaomingJiang/note/462804) * [Doc2vec to predict IMDB review star rating. Reproducing the Google paper](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/doc2vec-IMDB.ipynb) -#####Similarity Queries +##### Similarity Queries * [Similarity queries using Annoy with word2vec and doc2vec](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/annoytutorial.ipynb) -#####Word Movers Distance +##### Word Movers Distance * Tool to get the most similar documents for word2vec * [Word Movers Distance for Yelp Reviews tutorial](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/WMD_tutorial.ipynb) -#####Deep Inverse Regression +##### Deep Inverse Regression * Document Classification using Bayesian Inversion and several word2vec models(one for each class) * [Deep Inverse Regression with Yelp Reviews](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/deepir.ipynb) -####Other techniques +#### Other techniques -#####Summarization +##### Summarization * Extract most important keywords and sentences from the text * [Tutorial on TextRank summarisation](https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/summarization_tutorial.ipynb) -#####Overviews +##### Overviews * Tutorial showing API for document classification with various techniques: TF-IDF, word2vec averaging, Deep IR, Word Movers Distance and doc2vec * [Movie plots by genre](https://github.com/RaRe-Technologies/movie-plots-by-genre) -###Videos +### Videos * [Radim Řehůřek - Faster than Google? Optimization lessons in Python.](https://www.youtube.com/watch?v=vU4TlwZzTfU) * [MLMU.cz - Radim Řehůřek - Word2vec & friends (7.1.2015)](https://www.youtube.com/watch?v=wTp3P2UnTfQ) * [Making an Impact with NLP](https://www.youtube.com/watch?v=oSSnDeOXTZQ) -- Pycon 2016 Tutorial by Hobsons Lane * [NLP with NLTK and Gensim](https://www.youtube.com/watch?v=itKNpCPHq3I) -- Pycon 2016 Tutorial by Tony Ojeda, Benjamin Bengfort, Laura Lorenz from District Data Labs * [Word Embeddings for Fun and Profit](https://www.youtube.com/watch?v=lfqW46u0UKc) -- Talk at PyData London 2016 talk by Lev Konstantinovskiy. See accompanying [repo](https://github.com/RaRe-Technologies/movie-plots-by-genre) -#Credits +# Credits Based on wonderful [resource](https://github.com/jxieeducation/DIY-Data-Science/blob/master/frameworks/gensim.md) by Jason Xie. From ca510f37f91e38863d6697434721c7a570f7ad01 Mon Sep 17 00:00:00 2001 From: Shubh Vachher Date: Tue, 4 Apr 2017 02:36:14 +0530 Subject: [PATCH 32/41] Test made more robust for sklearn pipeline (#1250) --- gensim/test/test_sklearn_integration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gensim/test/test_sklearn_integration.py b/gensim/test/test_sklearn_integration.py index 2f5497550a..a651a2b055 100644 --- a/gensim/test/test_sklearn_integration.py +++ b/gensim/test/test_sklearn_integration.py @@ -106,7 +106,7 @@ def testPipeline(self): text_lda = Pipeline((('features', model,), ('classifier', clf))) text_lda.fit(corpus, data.target) score = text_lda.score(corpus, data.target) - self.assertGreater(score, 0.50) + self.assertGreater(score, 0.40) class TestSklearnLSIWrapper(unittest.TestCase): def setUp(self): From bed769c1a2724055d835f9588ad0b1daeeb99b00 Mon Sep 17 00:00:00 2001 From: Shubh Vachher Date: Tue, 4 Apr 2017 02:38:44 +0530 Subject: [PATCH 33/41] Make SgNegative test use sg (#1252) --- gensim/test/test_word2vec.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gensim/test/test_word2vec.py b/gensim/test/test_word2vec.py index bbb09a21ab..7826c99f6c 100644 --- a/gensim/test/test_word2vec.py +++ b/gensim/test/test_word2vec.py @@ -499,7 +499,7 @@ def testTrainingSgNegative(self): """Test skip-gram (negative sampling) word2vec training.""" # to test training, make the corpus larger by repeating its sentences over and over # build vocabulary, don't train yet - model = word2vec.Word2Vec(size=2, min_count=1, hs=0, negative=2) + model = word2vec.Word2Vec(size=2, min_count=1, sg=1, hs=0, negative=2) model.build_vocab(sentences) self.assertTrue(model.wv.syn0.shape == (len(model.wv.vocab), 2)) self.assertTrue(model.syn1neg.shape == (len(model.wv.vocab), 2)) @@ -515,7 +515,7 @@ def testTrainingSgNegative(self): self.assertEqual(sims, sims2) # build vocab and train in one step; must be the same as above - model2 = word2vec.Word2Vec(sentences, size=2, min_count=1, hs=0, negative=2) + model2 = word2vec.Word2Vec(sentences, size=2, min_count=1, sg=1, hs=0, negative=2) self.models_equal(model, model2) def testTrainingCbowNegative(self): From 3d9c5f4a234e1704e30da95aff0d8f0fef5c3740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radim=20=C5=98eh=C5=AF=C5=99ek?= Date: Mon, 10 Apr 2017 21:53:07 +0900 Subject: [PATCH 34/41] pep8 fixes (#1262) --- gensim/matutils.py | 96 +++++++++++++++++++-------------------- gensim/models/ldamodel.py | 64 +++++++++++++------------- gensim/utils.py | 84 +++++++++++++++++++--------------- 3 files changed, 126 insertions(+), 118 deletions(-) diff --git a/gensim/matutils.py b/gensim/matutils.py index 7a680d7fcb..93c750efd8 100644 --- a/gensim/matutils.py +++ b/gensim/matutils.py @@ -91,13 +91,13 @@ def corpus2csc(corpus, num_terms=None, dtype=np.float64, num_docs=None, num_nnz= if num_nnz is None: num_nnz = corpus.num_nnz except AttributeError: - pass # not a MmCorpus... + pass # not a MmCorpus... if printprogress: logger.info("creating sparse matrix from corpus") if num_terms is not None and num_docs is not None and num_nnz is not None: # faster and much more memory-friendly version of creating the sparse csc posnow, indptr = 0, [0] - indices = np.empty((num_nnz,), dtype=np.int32) # HACK assume feature ids fit in 32bit integer + indices = np.empty((num_nnz,), dtype=np.int32) # HACK assume feature ids fit in 32bit integer data = np.empty((num_nnz,), dtype=dtype) for docno, doc in enumerate(corpus): if printprogress and docno % printprogress == 0: @@ -139,8 +139,10 @@ def pad(mat, padrow, padcol): if padcol < 0: padcol = 0 rows, cols = mat.shape - return np.bmat([[mat, np.matrix(np.zeros((rows, padcol)))], - [np.matrix(np.zeros((padrow, cols + padcol)))]]) + return np.bmat([ + [mat, np.matrix(np.zeros((rows, padcol)))], + [np.matrix(np.zeros((padrow, cols + padcol)))], + ]) def zeros_aligned(shape, dtype, order='C', align=128): @@ -269,7 +271,6 @@ def corpus2dense(corpus, num_terms, num_docs=None, dtype=np.float32): return result.astype(dtype) - class Dense2Corpus(object): """ Treat dense np array as a sparse, streamed gensim corpus. @@ -306,7 +307,7 @@ def __init__(self, sparse, documents_columns=True): if documents_columns: self.sparse = sparse.tocsc() else: - self.sparse = sparse.tocsr().T # make sure shape[1]=number of docs (needed in len()) + self.sparse = sparse.tocsr().T # make sure shape[1]=number of docs (needed in len()) def __iter__(self): for indprev, indnow in izip(self.sparse.indptr, self.sparse.indptr[1:]): @@ -324,12 +325,14 @@ def veclen(vec): assert length > 0.0, "sparse documents must not contain any explicit zero entries" return length + def ret_normalized_vec(vec, length): if length != 1.0: return [(termid, val / length) for termid, val in vec] else: return list(vec) + def ret_log_normalize_vec(vec, axis=1): log_max = 100.0 if len(vec.shape) == 1: @@ -390,11 +393,11 @@ def unitvec(vec, norm='l2'): return vec try: - first = next(iter(vec)) # is there at least one element? + first = next(iter(vec)) # is there at least one element? except: return vec - if isinstance(first, (tuple, list)) and len(first) == 2: # gensim sparse format + if isinstance(first, (tuple, list)) and len(first) == 2: # gensim sparse format if norm == 'l1': length = float(sum(abs(val) for _, val in vec)) if norm == 'l2': @@ -417,9 +420,9 @@ def cossim(vec1, vec2): vec2len = 1.0 * math.sqrt(sum(val * val for val in itervalues(vec2))) assert vec1len > 0.0 and vec2len > 0.0, "sparse documents must not contain any explicit zero entries" if len(vec2) < len(vec1): - vec1, vec2 = vec2, vec1 # swap references so that we iterate over the shorter vector + vec1, vec2 = vec2, vec1 # swap references so that we iterate over the shorter vector result = sum(value * vec2.get(index, 0.0) for index, value in iteritems(vec1)) - result /= vec1len * vec2len # rescale by vector lengths + result /= vec1len * vec2len # rescale by vector lengths return result @@ -431,10 +434,10 @@ def isbow(vec): if scipy.sparse.issparse(vec): vec = vec.todense().tolist() try: - id_, val_ = vec[0] # checking first value to see if it is in bag of words format by unpacking + id_, val_ = vec[0] # checking first value to see if it is in bag of words format by unpacking id_, val_ = int(id_), float(val_) except IndexError: - return True # this is to handle the empty input case + return True # this is to handle the empty input case except Exception: return False return True @@ -450,9 +453,9 @@ def kullback_leibler(vec1, vec2, num_features=None): if scipy.sparse.issparse(vec1): vec1 = vec1.toarray() if scipy.sparse.issparse(vec2): - vec2 = vec2.toarray() # converted both the vectors to dense in case they were in sparse matrix - if isbow(vec1) and isbow(vec2): # if they are in bag of words format we make it dense - if num_features != None: # if not None, make as large as the documents drawing from + vec2 = vec2.toarray() # converted both the vectors to dense in case they were in sparse matrix + if isbow(vec1) and isbow(vec2): # if they are in bag of words format we make it dense + if num_features is not None: # if not None, make as large as the documents drawing from dense1 = sparse2full(vec1, num_features) dense2 = sparse2full(vec2, num_features) return entropy(dense1, dense2) @@ -480,11 +483,11 @@ def hellinger(vec1, vec2): vec1 = vec1.toarray() if scipy.sparse.issparse(vec2): vec2 = vec2.toarray() - if isbow(vec1) and isbow(vec2): + if isbow(vec1) and isbow(vec2): # if it is a bag of words format, instead of converting to dense we use dictionaries to calculate appropriate distance vec1, vec2 = dict(vec1), dict(vec2) - if len(vec2) < len(vec1): - vec1, vec2 = vec2, vec1 # swap references so that we iterate over the shorter vector + if len(vec2) < len(vec1): + vec1, vec2 = vec2, vec1 # swap references so that we iterate over the shorter vector sim = np.sqrt(0.5*sum((np.sqrt(value) - np.sqrt(vec2.get(index, 0.0)))**2 for index, value in iteritems(vec1))) return sim else: @@ -506,10 +509,10 @@ def jaccard(vec1, vec2): vec1 = vec1.toarray() if scipy.sparse.issparse(vec2): vec2 = vec2.toarray() - if isbow(vec1) and isbow(vec2): + if isbow(vec1) and isbow(vec2): # if it's in bow format, we use the following definitions: # union = sum of the 'weights' of both the bags - # intersection = lowest weight for a particular id; basically the number of common words or items + # intersection = lowest weight for a particular id; basically the number of common words or items union = sum(weight for id_, weight in vec1) + sum(weight for id_, weight in vec2) vec1, vec2 = dict(vec1), dict(vec2) intersection = 0.0 @@ -549,18 +552,18 @@ def qr_destroy(la): because the memory used in `la[0]` is reclaimed earlier. """ a = np.asfortranarray(la[0]) - del la[0], la # now `a` is the only reference to the input matrix + del la[0], la # now `a` is the only reference to the input matrix m, n = a.shape # perform q, r = QR(a); code hacked out of scipy.linalg.qr logger.debug("computing QR of %s dense matrix" % str(a.shape)) geqrf, = get_lapack_funcs(('geqrf',), (a,)) qr, tau, work, info = geqrf(a, lwork=-1, overwrite_a=True) qr, tau, work, info = geqrf(a, lwork=work[0], overwrite_a=True) - del a # free up mem + del a # free up mem assert info >= 0 r = triu(qr[:n, :n]) - if m < n: # rare case, #features < #topics - qr = qr[:, :m] # retains fortran order + if m < n: # rare case, #features < #topics + qr = qr[:, :m] # retains fortran order gorgqr, = get_lapack_funcs(('orgqr',), (qr,)) q, work, info = gorgqr(qr, tau, lwork=-1, overwrite_a=True) q, work, info = gorgqr(qr, tau, lwork=work[0], overwrite_a=True) @@ -569,7 +572,6 @@ def qr_destroy(la): return q, r - class MmWriter(object): """ Store a corpus in Matrix Market format. @@ -586,31 +588,30 @@ class MmWriter(object): """ - HEADER_LINE = b'%%MatrixMarket matrix coordinate real general\n' # the only supported MM format + HEADER_LINE = b'%%MatrixMarket matrix coordinate real general\n' # the only supported MM format def __init__(self, fname): self.fname = fname if fname.endswith(".gz") or fname.endswith('.bz2'): raise NotImplementedError("compressed output not supported with MmWriter") - self.fout = utils.smart_open(self.fname, 'wb+') # open for both reading and writing + self.fout = utils.smart_open(self.fname, 'wb+') # open for both reading and writing self.headers_written = False - def write_headers(self, num_docs, num_terms, num_nnz): self.fout.write(MmWriter.HEADER_LINE) if num_nnz < 0: # we don't know the matrix shape/density yet, so only log a general line logger.info("saving sparse matrix to %s" % self.fname) - self.fout.write(utils.to_utf8(' ' * 50 + '\n')) # 48 digits must be enough for everybody + self.fout.write(utils.to_utf8(' ' * 50 + '\n')) # 48 digits must be enough for everybody else: - logger.info("saving sparse %sx%s matrix with %i non-zero entries to %s" % - (num_docs, num_terms, num_nnz, self.fname)) + logger.info( + "saving sparse %sx%s matrix with %i non-zero entries to %s", + num_docs, num_terms, num_nnz, self.fname) self.fout.write(utils.to_utf8('%s %s %s\n' % (num_docs, num_terms, num_nnz))) self.last_docno = -1 self.headers_written = True - def fake_headers(self, num_docs, num_terms, num_nnz): stats = '%i %i %i' % (num_docs, num_terms, num_nnz) if len(stats) > 50: @@ -618,7 +619,6 @@ def fake_headers(self, num_docs, num_terms, num_nnz): self.fout.seek(len(MmWriter.HEADER_LINE)) self.fout.write(utils.to_utf8(stats)) - def write_vector(self, docno, vector): """ Write a single sparse vector to the file. @@ -627,13 +627,12 @@ def write_vector(self, docno, vector): """ assert self.headers_written, "must write Matrix Market file headers before writing data!" assert self.last_docno < docno, "documents %i and %i not in sequential order!" % (self.last_docno, docno) - vector = sorted((i, w) for i, w in vector if abs(w) > 1e-12) # ignore near-zero entries - for termid, weight in vector: # write term ids in sorted order - self.fout.write(utils.to_utf8("%i %i %s\n" % (docno + 1, termid + 1, weight))) # +1 because MM format starts counting from 1 + vector = sorted((i, w) for i, w in vector if abs(w) > 1e-12) # ignore near-zero entries + for termid, weight in vector: # write term ids in sorted order + self.fout.write(utils.to_utf8("%i %i %s\n" % (docno + 1, termid + 1, weight))) # +1 because MM format starts counting from 1 self.last_docno = docno return (vector[-1][0], len(vector)) if vector else (-1, 0) - @staticmethod def write_corpus(fname, corpus, progress_cnt=1000, index=False, num_terms=None, metadata=False): """ @@ -645,7 +644,7 @@ def write_corpus(fname, corpus, progress_cnt=1000, index=False, num_terms=None, mw = MmWriter(fname) # write empty headers to the file (with enough space to be overwritten later) - mw.write_headers(-1, -1, -1) # will print 50 spaces followed by newline on the stats line + mw.write_headers(-1, -1, -1) # will print 50 spaces followed by newline on the stats line # calculate necessary header info (nnz elements, num terms, num docs) while writing out vectors _num_terms, num_nnz = 0, 0 @@ -696,7 +695,6 @@ def write_corpus(fname, corpus, progress_cnt=1000, index=False, num_terms=None, if index: return offsets - def __del__(self): """ Automatic destructor which closes the underlying file. @@ -705,8 +703,7 @@ def __del__(self): to work! Closing the file explicitly via the close() method is preferred and safer. """ - self.close() # does nothing if called twice (on an already closed file), so no worries - + self.close() # does nothing if called twice (on an already closed file), so no worries def close(self): logger.debug("closing %s" % self.fname) @@ -715,7 +712,6 @@ def close(self): #endclass MmWriter - class MmReader(object): """ Wrap a term-document matrix on disk (in matrix-market format), and present it @@ -756,8 +752,9 @@ def __init__(self, input, transposed=True): self.num_docs, self.num_terms = self.num_terms, self.num_docs break - logger.info("accepted corpus with %i documents, %i features, %i non-zero entries" % - (self.num_docs, self.num_terms, self.num_nnz)) + logger.info( + "accepted corpus with %i documents, %i features, %i non-zero entries", + self.num_docs, self.num_terms, self.num_nnz) def __len__(self): return self.num_docs @@ -793,7 +790,7 @@ def __iter__(self): docid, termid, val = utils.to_unicode(line).split() # needed for python3 if not self.transposed: termid, docid = docid, termid - docid, termid, val = int(docid) - 1, int(termid) - 1, float(val) # -1 because matrix market indexes are 1-based => convert to 0-based + docid, termid, val = int(docid) - 1, int(termid) - 1, float(val) # -1 because matrix market indexes are 1-based => convert to 0-based assert previd <= docid, "matrix columns must come in ascending order" if docid != previd: # change of document: return the document read so far (its id is prevId) @@ -809,7 +806,7 @@ def __iter__(self): previd = docid document = [] - document.append((termid, val,)) # add another field to the current document + document.append((termid, val,)) # add another field to the current document # handle the last document, as a special case if previd >= 0: @@ -820,7 +817,6 @@ def __iter__(self): for previd in xrange(previd + 1, self.num_docs): yield previd, [] - def docbyoffset(self, offset): """Return document at file offset `offset` (in bytes)""" # empty documents are not stored explicitly in MM format, so the index marks @@ -832,19 +828,19 @@ def docbyoffset(self, offset): else: fin = self.input - fin.seek(offset) # works for gzip/bz2 input, too + fin.seek(offset) # works for gzip/bz2 input, too previd, document = -1, [] for line in fin: docid, termid, val = line.split() if not self.transposed: termid, docid = docid, termid - docid, termid, val = int(docid) - 1, int(termid) - 1, float(val) # -1 because matrix market indexes are 1-based => convert to 0-based + docid, termid, val = int(docid) - 1, int(termid) - 1, float(val) # -1 because matrix market indexes are 1-based => convert to 0-based assert previd <= docid, "matrix columns must come in ascending order" if docid != previd: if previd >= 0: return document previd = docid - document.append((termid, val,)) # add another field to the current document + document.append((termid, val,)) # add another field to the current document return document #endclass MmReader diff --git a/gensim/models/ldamodel.py b/gensim/models/ldamodel.py index a695fec7ff..aa85b803cd 100755 --- a/gensim/models/ldamodel.py +++ b/gensim/models/ldamodel.py @@ -3,7 +3,6 @@ # # Copyright (C) 2011 Radim Rehurek # Licensed under the GNU LGPL v2.1 - http://www.gnu.org/licenses/lgpl.html -# """ @@ -32,7 +31,7 @@ import logging -import numpy as np # for arrays, array broadcasting etc. +import numpy as np import numbers import os @@ -292,9 +291,9 @@ def __init__(self, corpus=None, num_topics=100, id2word=None, self.random_state = utils.get_random_state(random_state) - assert (self.eta.shape == (self.num_terms,) or self.eta.shape == (self.num_topics, self.num_terms)), ( - "Invalid eta shape. Got shape %s, but expected (%d, 1) or (%d, %d)" % - (str(self.eta.shape), self.num_terms, self.num_topics, self.num_terms)) + assert self.eta.shape == (self.num_terms,) or self.eta.shape == (self.num_topics, self.num_terms), ( + "Invalid eta shape. Got shape %s, but expected (%d, 1) or (%d, %d)" % + (str(self.eta.shape), self.num_terms, self.num_topics, self.num_terms)) # VB constants self.iterations = iterations @@ -601,17 +600,19 @@ def update(self, corpus, chunksize=None, decay=None, offset=None, evalafter = min(lencorpus, (eval_every or 0) * self.numworkers * chunksize) updates_per_pass = max(1, lencorpus / updateafter) - logger.info("running %s LDA training, %s topics, %i passes over " - "the supplied corpus of %i documents, updating model once " - "every %i documents, evaluating perplexity every %i documents, " - "iterating %ix with a convergence threshold of %f", - updatetype, self.num_topics, passes, lencorpus, - updateafter, evalafter, iterations, - gamma_threshold) + logger.info( + "running %s LDA training, %s topics, %i passes over " + "the supplied corpus of %i documents, updating model once " + "every %i documents, evaluating perplexity every %i documents, " + "iterating %ix with a convergence threshold of %f", + updatetype, self.num_topics, passes, lencorpus, + updateafter, evalafter, iterations, + gamma_threshold) if updates_per_pass * passes < 10: - logger.warning("too few updates, training might not converge; consider " - "increasing the number of passes or iterations to improve accuracy") + logger.warning( + "too few updates, training might not converge; consider " + "increasing the number of passes or iterations to improve accuracy") # rho is the "speed" of updating; TODO try other fncs # pass_ + num_updates handles increasing the starting t for each pass, @@ -906,26 +907,28 @@ def get_document_topics(self, bow, minimum_probability=None, minimum_phi_value=N is_corpus, corpus = utils.is_corpus(bow) if is_corpus: kwargs = dict( - per_word_topics = per_word_topics, - minimum_probability = minimum_probability, - minimum_phi_value = minimum_phi_value + per_word_topics=per_word_topics, + minimum_probability=minimum_probability, + minimum_phi_value=minimum_phi_value ) return self._apply(corpus, **kwargs) gamma, phis = self.inference([bow], collect_sstats=per_word_topics) topic_dist = gamma[0] / sum(gamma[0]) # normalize distribution - document_topics = [(topicid, topicvalue) for topicid, topicvalue in enumerate(topic_dist) - if topicvalue >= minimum_probability] + document_topics = [ + (topicid, topicvalue) for topicid, topicvalue in enumerate(topic_dist) + if topicvalue >= minimum_probability + ] if not per_word_topics: return document_topics else: - word_topic = [] # contains word and corresponding topic - word_phi = [] # contains word and phi values + word_topic = [] # contains word and corresponding topic + word_phi = [] # contains word and phi values for word_type, weight in bow: - phi_values = [] # contains (phi_value, topic) pairing to later be sorted - phi_topic = [] # contains topic and corresponding phi value to be returned 'raw' to user + phi_values = [] # contains (phi_value, topic) pairing to later be sorted + phi_topic = [] # contains topic and corresponding phi value to be returned 'raw' to user for topic_id in range(0, self.num_topics): if phis[topic_id][word_type] >= minimum_phi_value: # appends phi values for each topic for that word @@ -940,7 +943,7 @@ def get_document_topics(self, bow, minimum_probability=None, minimum_phi_value=N sorted_phi_values = sorted(phi_values, reverse=True) topics_sorted = [x[1] for x in sorted_phi_values] word_topic.append((word_type, topics_sorted)) - return (document_topics, word_topic, word_phi) # returns 2-tuple + return (document_topics, word_topic, word_phi) # returns 2-tuple def get_term_topics(self, word_id, minimum_probability=None): """ @@ -962,7 +965,6 @@ def get_term_topics(self, word_id, minimum_probability=None): return values - def __getitem__(self, bow, eps=None): """ Return topic distribution for the given document `bow`, as a list of @@ -1006,16 +1008,16 @@ def save(self, fname, ignore=['state', 'dispatcher'], separately=None, *args, ** if 'id2word' not in ignore: utils.pickle(self.id2word, utils.smart_extension(fname, '.id2word')) - # make sure 'state', 'id2word' and 'dispatcher' are ignored from the pickled object, even if + # make sure 'state', 'id2word' and 'dispatcher' are ignored from the pickled object, even if # someone sets the ignore list themselves if ignore is not None and ignore: if isinstance(ignore, six.string_types): ignore = [ignore] - ignore = [e for e in ignore if e] # make sure None and '' are not in the list + ignore = [e for e in ignore if e] # make sure None and '' are not in the list ignore = list(set(['state', 'dispatcher', 'id2word']) | set(ignore)) else: ignore = ['state', 'dispatcher', 'id2word'] - + # make sure 'expElogbeta' and 'sstats' are ignored from the pickled object, even if # someone sets the separately list themselves. separately_explicit = ['expElogbeta', 'sstats'] @@ -1029,12 +1031,12 @@ def save(self, fname, ignore=['state', 'dispatcher'], separately=None, *args, ** if separately: if isinstance(separately, six.string_types): separately = [separately] - separately = [e for e in separately if e] # make sure None and '' are not in the list + separately = [e for e in separately if e] # make sure None and '' are not in the list separately = list(set(separately_explicit) | set(separately)) else: separately = separately_explicit - super(LdaModel, self).save(fname, ignore=ignore, separately = separately, *args, **kwargs) - + super(LdaModel, self).save(fname, ignore=ignore, separately=separately, *args, **kwargs) + @classmethod def load(cls, fname, *args, **kwargs): """ diff --git a/gensim/utils.py b/gensim/utils.py index 0de26dbdb1..8d5fdb7d7f 100644 --- a/gensim/utils.py +++ b/gensim/utils.py @@ -83,17 +83,17 @@ def smart_open(fname, mode='rb'): def get_random_state(seed): - """ Turn seed into a np.random.RandomState instance. - - Method originally from maciejkula/glove-python, and written by @joshloyal - """ - if seed is None or seed is np.random: - return np.random.mtrand._rand - if isinstance(seed, (numbers.Integral, np.integer)): - return np.random.RandomState(seed) - if isinstance(seed, np.random.RandomState): + """ + Turn seed into a np.random.RandomState instance. + Method originally from maciejkula/glove-python, and written by @joshloyal. + """ + if seed is None or seed is np.random: + return np.random.mtrand._rand + if isinstance(seed, (numbers.Integral, np.integer)): + return np.random.RandomState(seed) + if isinstance(seed, np.random.RandomState): return seed - raise ValueError('%r cannot be used to seed a np.random.RandomState instance' % seed) + raise ValueError('%r cannot be used to seed a np.random.RandomState instance' % seed) def synchronous(tlockname): @@ -108,7 +108,7 @@ def _synchronizer(self, *args, **kwargs): tlock = getattr(self, tlockname) logger.debug("acquiring lock %r for %s" % (tlockname, func.__name__)) - with tlock: # use lock as a context manager to perform safe acquire/release pairs + with tlock: # use lock as a context manager to perform safe acquire/release pairs logger.debug("acquired lock %r for %s" % (tlockname, func.__name__)) result = func(self, *args, **kwargs) logger.debug("releasing lock %r for %s" % (tlockname, func.__name__)) @@ -120,10 +120,13 @@ def _synchronizer(self, *args, **kwargs): class NoCM(object): def acquire(self): pass + def release(self): pass + def __enter__(self): pass + def __exit__(self, type, value, traceback): pass nocm = NoCM() @@ -232,6 +235,7 @@ def any2unicode(text, encoding='utf8', errors='strict'): return unicode(text, encoding, errors=errors) to_unicode = any2unicode + def call_on_class_only(*args, **kwargs): """Raise exception when load methods are called on instance""" raise AttributeError('This method should be called on a class object.') @@ -269,7 +273,6 @@ def load(cls, fname, mmap=None): logger.info("loaded %s", fname) return obj - def _load_specials(self, fname, mmap, compress, subname): """ Loads any attributes that were stored specially, and gives the same @@ -323,7 +326,6 @@ def _load_specials(self, fname, mmap, compress, subname): logger.info("setting ignored attribute %s to None" % (attrib)) setattr(self, attrib, None) - @staticmethod def _adapt_by_suffix(fname): """Give appropriate compress setting and filename formula""" @@ -335,7 +337,6 @@ def _adapt_by_suffix(fname): subname = lambda *args: '.'.join(list(args) + ['npy']) return (compress, subname) - def _smart_save(self, fname, separately=None, sep_limit=10 * 1024**2, ignore=frozenset(), pickle_protocol=2): """ @@ -375,7 +376,6 @@ def _smart_save(self, fname, separately=None, sep_limit=10 * 1024**2, setattr(obj, attrib, val) logger.info("saved %s", fname) - def _save_specials(self, fname, separately, sep_limit, ignore, pickle_protocol, compress, subname): """ Save aside any attributes that need to be handled separately, including @@ -407,9 +407,10 @@ def _save_specials(self, fname, separately, sep_limit, ignore, pickle_protocol, for attrib, val in iteritems(self.__dict__): if hasattr(val, '_save_specials'): # better than 'isinstance(val, SaveLoad)' if IPython reloading recursive_saveloads.append(attrib) - cfname = '.'.join((fname,attrib)) - restores.extend(val._save_specials(cfname, None, sep_limit, ignore, - pickle_protocol, compress, subname)) + cfname = '.'.join((fname, attrib)) + restores.extend(val._save_specials( + cfname, None, sep_limit, ignore, + pickle_protocol, compress, subname)) try: numpys, scipys, ignoreds = [], [], [] @@ -430,10 +431,11 @@ def _save_specials(self, fname, separately, sep_limit, ignore, pickle_protocol, attrib, subname(fname, attrib))) if compress: - np.savez_compressed(subname(fname, attrib, 'sparse'), - data=val.data, - indptr=val.indptr, - indices=val.indices) + np.savez_compressed( + subname(fname, attrib, 'sparse'), + data=val.data, + indptr=val.indptr, + indices=val.indices) else: np.save(subname(fname, attrib, 'data'), val.data) np.save(subname(fname, attrib, 'indptr'), val.indptr) @@ -462,7 +464,6 @@ def _save_specials(self, fname, separately, sep_limit, ignore, pickle_protocol, raise return restores + [(self, asides)] - def save(self, fname_or_handle, separately=None, sep_limit=10 * 1024**2, ignore=frozenset(), pickle_protocol=2): """ @@ -513,7 +514,7 @@ def get_max_id(corpus): """ maxid = -1 for document in corpus: - maxid = max(maxid, max([-1] + [fieldid for fieldid, _ in document])) # [-1] to avoid exceptions from max(empty) + maxid = max(maxid, max([-1] + [fieldid for fieldid, _ in document])) # [-1] to avoid exceptions from max(empty) return maxid @@ -529,11 +530,9 @@ class FakeDict(object): def __init__(self, num_terms): self.num_terms = num_terms - def __str__(self): return "FakeDict(num_terms=%s)" % self.num_terms - def __getitem__(self, val): if 0 <= val < self.num_terms: return str(val) @@ -669,6 +668,7 @@ def __init__(self, corpus, reps): def __iter__(self): return itertools.islice(itertools.cycle(self.corpus), self.reps) + class RepeatCorpusNTimes(SaveLoad): def __init__(self, corpus, n): @@ -687,6 +687,7 @@ def __iter__(self): for document in self.corpus: yield document + class ClippedCorpus(SaveLoad): def __init__(self, corpus, max_docs=None): """ @@ -706,6 +707,7 @@ def __iter__(self): def __len__(self): return min(self.max_docs, len(self.corpus)) + class SlicedCorpus(SaveLoad): def __init__(self, corpus, slice_): """ @@ -743,6 +745,7 @@ def __len__(self): return self.length + def safe_unichr(intval): try: return unichr(intval) @@ -752,6 +755,7 @@ def safe_unichr(intval): # return UTF16 surrogate pair return s.decode('unicode-escape') + def decode_htmlentities(text): """ Decode HTML entities in text, coded as hex, decimal or named. @@ -930,6 +934,7 @@ def unpickle(fname): else: return _pickle.loads(f.read()) + def revdict(d): """ Reverse a dictionary mapping. @@ -952,11 +957,11 @@ def toptexts(query, texts, index, n=10): Return a list of 3-tuples (docid, doc's similarity to the query, texts[docid]). """ - sims = index[query] # perform a similarity query against the corpus + sims = index[query] # perform a similarity query against the corpus sims = sorted(enumerate(sims), key=lambda item: -item[1]) result = [] - for topid, topcosine in sims[:n]: # only consider top-n most similar docs + for topid, topcosine in sims[:n]: # only consider top-n most similar docs result.append((topid, topcosine, texts[topid])) return result @@ -1032,7 +1037,8 @@ def has_pattern(): return False -def lemmatize(content, allowed_tags=re.compile('(NN|VB|JJ|RB)'), light=False, +def lemmatize( + content, allowed_tags=re.compile('(NN|VB|JJ|RB)'), light=False, stopwords=frozenset(), min_length=2, max_length=15): """ This function is only available when the optional 'pattern' package is installed. @@ -1147,9 +1153,12 @@ def keep_vocab_item(word, count, min_count, trim_rule=None): else: return default_res + def check_output(stdout=subprocess.PIPE, *popenargs, **kwargs): - r"""Run command with arguments and return its output as a byte string. + """ + Run command with arguments and return its output as a byte string. Backported from Python 2.7 as it's implemented as pure python on stdlib. + >>> check_output(args=['/usr/bin/python', '--version']) Python 2.6.2 Added extra KeyboardInterrupt handling @@ -1170,11 +1179,12 @@ def check_output(stdout=subprocess.PIPE, *popenargs, **kwargs): process.terminate() raise + def sample_dict(d, n=10, use_random=True): - """ - Pick `n` items from dictionary `d` and return them as a list. - The items are picked randomly if `use_random` is True, otherwise picked - according to natural dict iteration. - """ - selected_keys = random.sample(list(d), min(len(d), n)) if use_random else itertools.islice(iterkeys(d), n) - return [(key, d[key]) for key in selected_keys] + """ + Pick `n` items from dictionary `d` and return them as a list. + The items are picked randomly if `use_random` is True, otherwise picked + according to natural dict iteration. + """ + selected_keys = random.sample(list(d), min(len(d), n)) if use_random else itertools.islice(iterkeys(d), n) + return [(key, d[key]) for key in selected_keys] From 83c33c86df6b3b262f43664433f8f202afda45d3 Mon Sep 17 00:00:00 2001 From: Shubh Vachher Date: Tue, 11 Apr 2017 00:56:36 +0530 Subject: [PATCH 35/41] Add docstrings for word2vec.py forwarding functions (#1251) * Docs for word2vec.py forwarding functions and one more * Fix Pep8 --- gensim/models/word2vec.py | 92 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/gensim/models/word2vec.py b/gensim/models/word2vec.py index d14894ef8a..f93b8d3ea3 100644 --- a/gensim/models/word2vec.py +++ b/gensim/models/word2vec.py @@ -355,6 +355,9 @@ class Word2Vec(utils.SaveLoad): """ Class for training, using and evaluating neural networks described in https://code.google.com/p/word2vec/ + If you're finished training a model (=no more updates, only querying) + then switch to the :mod:`gensim.models.KeyedVectors` instance in wv + The model can be stored/loaded via its `save()` and `load()` methods, or stored/loaded in a format compatible with the original word2vec implementation via `wv.save_word2vec_format()` and `KeyedVectors.load_word2vec_format()`. @@ -1083,6 +1086,11 @@ def worker_loop(): return sentence_scores[:sentence_count] def clear_sims(self): + """ + Removes all L2-normalized vectors for words from the model. + You will have to recompute them using init_sims method. + """ + self.wv.syn0norm = None def update_weights(self): @@ -1188,33 +1196,103 @@ def intersect_word2vec_format(self, fname, lockf=0.0, binary=False, encoding='ut logger.info("merged %d vectors into %s matrix from %s" % (overlap_count, self.wv.syn0.shape, fname)) def most_similar(self, positive=[], negative=[], topn=10, restrict_vocab=None, indexer=None): + """ + Please refer to the documentation for + `gensim.models.KeyedVectors.most_similar` + This is just a forwarding function. + In the future please use the `gensim.models.KeyedVectors` instance in wv + """ + return self.wv.most_similar(positive, negative, topn, restrict_vocab, indexer) def wmdistance(self, document1, document2): + """ + Please refer to the documentation for + `gensim.models.KeyedVectors.wmdistance` + This is just a forwarding function. + In the future please use the `gensim.models.KeyedVectors` instance in wv + """ + return self.wv.wmdistance(document1, document2) def most_similar_cosmul(self, positive=[], negative=[], topn=10): + """ + Please refer to the documentation for + `gensim.models.KeyedVectors.most_similar_cosmul` + This is just a forwarding function. + In the future please use the `gensim.models.KeyedVectors` instance in wv + """ + return self.wv.most_similar_cosmul(positive, negative, topn) def similar_by_word(self, word, topn=10, restrict_vocab=None): + """ + Please refer to the documentation for + `gensim.models.KeyedVectors.similar_by_word` + This is just a forwarding function. + In the future please use the `gensim.models.KeyedVectors` instance in wv + """ + return self.wv.similar_by_word(word, topn, restrict_vocab) def similar_by_vector(self, vector, topn=10, restrict_vocab=None): + """ + Please refer to the documentation for + `gensim.models.KeyedVectors.similar_by_vector` + This is just a forwarding function. + In the future please use the `gensim.models.KeyedVectors` instance in wv + """ + return self.wv.similar_by_vector(vector, topn, restrict_vocab) def doesnt_match(self, words): + """ + Please refer to the documentation for + `gensim.models.KeyedVectors.doesnt_match` + This is just a forwarding function. + In the future please use the `gensim.models.KeyedVectors` instance in wv + """ + return self.wv.doesnt_match(words) def __getitem__(self, words): + """ + Please refer to the documentation for + `gensim.models.KeyedVectors.__getitem__` + This is just a forwarding function. + In the future please use the `gensim.models.KeyedVectors` instance in wv + """ + return self.wv.__getitem__(words) def __contains__(self, word): + """ + Please refer to the documentation for + `gensim.models.KeyedVectors.__contains__` + This is just a forwarding function. + In the future please use the `gensim.models.KeyedVectors` instance in wv + """ + return self.wv.__contains__(word) def similarity(self, w1, w2): + """ + Please refer to the documentation for + `gensim.models.KeyedVectors.similarity` + This is just a forwarding function. + In the future please use the `gensim.models.KeyedVectors` instance in wv + """ + return self.wv.similarity(w1, w2) def n_similarity(self, ws1, ws2): + """ + Please refer to the documentation for + `gensim.models.KeyedVectors.n_similarity` + This is just a forwarding function. + In the future please use the `gensim.models.KeyedVectors` instance in wv + """ + return self.wv.n_similarity(ws1, ws2) def predict_output_word(self, context_words_list, topn=10): @@ -1277,9 +1355,23 @@ def accuracy(self, questions, restrict_vocab=30000, most_similar=None, case_inse @staticmethod def log_evaluate_word_pairs(pearson, spearman, oov, pairs): + """ + Please refer to the documentation for + `gensim.models.KeyedVectors.log_evaluate_word_pairs` + This is just a forwarding function. + In the future please use the `gensim.models.KeyedVectors` instance in wv + """ + return KeyedVectors.log_evaluate_word_pairs(pearson, spearman, oov, pairs) def evaluate_word_pairs(self, pairs, delimiter='\t', restrict_vocab=300000, case_insensitive=True, dummy4unknown=False): + """ + Please refer to the documentation for + `gensim.models.KeyedVectors.evaluate_word_pairs` + This is just a forwarding function. + In the future please use the `gensim.models.KeyedVectors` instance in wv + """ + return self.wv.evaluate_word_pairs(pairs, delimiter, restrict_vocab, case_insensitive, dummy4unknown) def __str__(self): From b1f7696556818fad424477637058eb1d12d992f2 Mon Sep 17 00:00:00 2001 From: Zhaocheng Zhu Date: Tue, 11 Apr 2017 03:40:27 +0800 Subject: [PATCH 36/41] syn0_lockf initialised with zero in intersect_word2vec_format() (#1267) --- gensim/models/word2vec.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gensim/models/word2vec.py b/gensim/models/word2vec.py index f93b8d3ea3..c82ee7bb6b 100644 --- a/gensim/models/word2vec.py +++ b/gensim/models/word2vec.py @@ -1193,6 +1193,7 @@ def intersect_word2vec_format(self, fname, lockf=0.0, binary=False, encoding='ut if word in self.wv.vocab: overlap_count += 1 self.wv.syn0[self.wv.vocab[word].index] = weights + self.syn0_lockf[self.wv.vocab[word].index] = lockf # lock-factor: 0.0 stops further changes logger.info("merged %d vectors into %s matrix from %s" % (overlap_count, self.wv.syn0.shape, fname)) def most_similar(self, positive=[], negative=[], topn=10, restrict_vocab=None, indexer=None): From 3197a9565cac2bac1c001ff718e9cc47ccf58c31 Mon Sep 17 00:00:00 2001 From: Parul Sethi Date: Tue, 11 Apr 2017 01:12:05 +0530 Subject: [PATCH 37/41] Fix hdp show_topic/s docstring (#1264) --- gensim/models/hdpmodel.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gensim/models/hdpmodel.py b/gensim/models/hdpmodel.py index 17c7a6c85d..2c74d15a15 100755 --- a/gensim/models/hdpmodel.py +++ b/gensim/models/hdpmodel.py @@ -438,8 +438,7 @@ def update_expectations(self): def show_topic(self, topic_id, num_words=20, log=False, formatted=False): """ - Print the `num_words` most probable words for `topics` number of topics. - Set `topics=-1` to print all topics. + Print the `num_words` most probable words for topic `topic_id`. Set `formatted=True` to return the topics as a list of strings, or `False` as lists of (weight, word) pairs. @@ -453,8 +452,8 @@ def show_topic(self, topic_id, num_words=20, log=False, formatted=False): def show_topics(self, num_topics=20, num_words=20, log=False, formatted=True): """ - Print the `num_words` most probable words for `topics` number of topics. - Set `topics=-1` to print all topics. + Print the `num_words` most probable words for `num_topics` number of topics. + Set `num_topics=-1` to print all topics. Set `formatted=True` to return the topics as a list of strings, or `False` as lists of (weight, word) pairs. From db7f3413203a176fa08fee4b302718bb09f6c9ea Mon Sep 17 00:00:00 2001 From: Chinmaya Pancholi Date: Tue, 11 Apr 2017 01:28:21 +0530 Subject: [PATCH 38/41] Duplicate imports and typos in word2vec.py (#1240) * removed duplicate import for keep_vocab_item * removed duplicate import for warnings * updated warning message for trim_rule --- gensim/models/word2vec.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/gensim/models/word2vec.py b/gensim/models/word2vec.py index c82ee7bb6b..ce97a15415 100644 --- a/gensim/models/word2vec.py +++ b/gensim/models/word2vec.py @@ -109,7 +109,6 @@ import warnings from gensim.utils import keep_vocab_item, call_on_class_only -from gensim.utils import keep_vocab_item from gensim.models.keyedvectors import KeyedVectors, Vocab try: @@ -426,7 +425,7 @@ def __init__( in the vocabulary, be trimmed away, or handled using the default (discard if word count < min_count). Can be None (min_count will be used), or a callable that accepts parameters (word, count, min_count) and returns either `utils.RULE_DISCARD`, `utils.RULE_KEEP` or `utils.RULE_DEFAULT`. - Note: The rule, if given, is only used prune vocabulary during build_vocab() and is not stored as part + Note: The rule, if given, is only used to prune vocabulary during build_vocab() and is not stored as part of the model. `sorted_vocab` = if 1 (default), sort the vocabulary by descending frequency before @@ -481,7 +480,7 @@ def __init__( start_alpha=self.alpha, end_alpha=self.min_alpha) else : if trim_rule is not None : - logger.warning("The rule, if given, is only used prune vocabulary during build_vocab() and is not stored as part of the model. ") + logger.warning("The rule, if given, is only used to prune vocabulary during build_vocab() and is not stored as part of the model. ") logger.warning("Model initialized without sentences. trim_rule provided, if any, will be ignored." ) @@ -785,7 +784,6 @@ def train(self, sentences, total_examples=None, total_words=None, if (self.model_trimmed_post_training): raise RuntimeError("Parameters for training were discarded using model_trimmed_post_training method") if FAST_VERSION < 0: - import warnings warnings.warn("C extension not loaded for Word2Vec, training will be slow. " "Install a C compiler and reinstall gensim for fast training.") self.neg_labels = [] @@ -988,7 +986,6 @@ def score(self, sentences, total_sentences=int(1e6), chunksize=100, queue_factor """ if FAST_VERSION < 0: - import warnings warnings.warn("C extension compilation failed, scoring will be slow. " "Install a C compiler and reinstall gensim for fastness.") From 14357c182a61c319f591de2cd03b440105144d3a Mon Sep 17 00:00:00 2001 From: Lev Konstantinovskiy Date: Mon, 10 Apr 2017 19:16:25 -0300 Subject: [PATCH 39/41] Docstring clarifications in word2vec (#1271) --- gensim/models/word2vec.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gensim/models/word2vec.py b/gensim/models/word2vec.py index ce97a15415..ad9426bd4c 100644 --- a/gensim/models/word2vec.py +++ b/gensim/models/word2vec.py @@ -76,12 +76,12 @@ and so on. -If you're finished training a model (=no more updates, only querying), then switch to the :mod:`gensim.models.KeyedVectors` instance in wv +If you're finished training a model (i.e. no more updates, only querying), then switch to the :mod:`gensim.models.KeyedVectors` instance in wv >>> word_vectors = model.wv >>> del model -to trim unneeded model memory = use (much) less RAM. +to trim unneeded model memory = use much less RAM. Note that there is a :mod:`gensim.models.phrases` module which lets you automatically detect phrases longer than one word. Using phrases, you can learn a word2vec model @@ -810,9 +810,9 @@ def train(self, sentences, total_examples=None, total_words=None, "Instead start with a blank model, scan_vocab on the new corpus, intersect_word2vec_format with the old model, then train.") if total_words is None and total_examples is None: - raise ValueError("you must specify either total_examples or total_words, for proper alpha and progress calculations") + raise ValueError("You must specify either total_examples or total_words, for proper alpha and progress calculations. The usual value is total_examples=model.corpus_count.") if epochs is None: - raise ValueError("you must specify an explict epochs count") + raise ValueError("You must specify an explict epochs count. The usual value is epochs=model.iter.") start_alpha = start_alpha or self.alpha end_alpha = end_alpha or self.min_alpha From f8d2da2a5cee5d6be6b35322e63d32ede9c70f03 Mon Sep 17 00:00:00 2001 From: Lev Konstantinovskiy Date: Mon, 10 Apr 2017 20:16:41 -0300 Subject: [PATCH 40/41] Add 2.0.0 release notes --- CHANGELOG.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77f6197a0d..8817d0e29c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,66 @@ Changes Unreleased: + +=========== + +2.0.0, 2017-04-10 + +Breaking changes: + +Any direct calls to method train() of Word2Vec/Doc2Vec now require an explicit epochs parameter and explicit estimate of corpus size. The most usual way to call `train` is `vec_model.train(sentences, total_examples=self.corpus_count, epochs=self.iter)` +See the [method documentation](https://github.com/RaRe-Technologies/gensim/blob/develop/gensim/models/word2vec.py#L766) for more information. + + +* Explicit epochs and corpus size in word2vec train(). (@gojomo, @robotcator, [#1139](https://github.com/RaRe-Technologies/gensim/pull/1139), [#1237](https://github.com/RaRe-Technologies/gensim/pull/1237)) + New features: -* Add output word prediction for negative sampling scheme. (@chinmayapancholi13,[#1209](https://github.com/RaRe-Technologies/gensim/pull/1209)) + +* Add output word prediction in word2vec. Only for negative sampling scheme. See [ipynb]( https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/word2vec.ipynb) (@chinmayapancholi13,[#1209](https://github.com/RaRe-Technologies/gensim/pull/1209)) +* scikit_learn wrapper for LSI Model in Gensim (@chinmayapancholi13,[#1244](https://github.com/RaRe-Technologies/gensim/pull/1244)) +* Add the 'keep_tokens' parameter to 'filter_extremes'. (@toliwa,[#1210](https://github.com/RaRe-Technologies/gensim/pull/1210)) +* Load FastText models with specified encoding (@jayantj,[#1210](https://github.com/RaRe-Technologies/gensim/pull/1189)) + Improvements: * Fix loading large FastText models on Mac. (@jaksmid,[#1196](https://github.com/RaRe-Technologies/gensim/pull/1214)) +* Sklearn LDA wrapper now works in sklearn pipeline (@kris-singh,[#1213](https://github.com/RaRe-Technologies/gensim/pull/1213)) +* glove2word2vec conversion script refactoring (@parulsethi,[#1247](https://github.com/RaRe-Technologies/gensim/pull/1247)) +* Word2vec error message when update called before train . Fix #1162 (@hemavakade,[#1205](https://github.com/RaRe-Technologies/gensim/pull/1205)) +* Allow training if model is not modified by "_minimize_model". Add deprecation warning. (@chinmayapancholi13,[#1207](https://github.com/RaRe-Technologies/gensim/pull/1207)) +* Update the warning text when building vocab on a trained w2v model (@prakhar2b,[#1190](https://github.com/RaRe-Technologies/gensim/pull/1190)) + +Bug fixes: + +* Fix word2vec reset_from bug in v1.0.1 Fix #1230. (@Kreiswolke,[#1234](https://github.com/RaRe-Technologies/gensim/pull/1234)) + +* Distributed LDA: checking the length of docs instead of the boolean value, plus int index conversion (@saparina ,[#1191](https://github.com/RaRe-Technologies/gensim/pull/1191)) + +* syn0_lockf initialised with zero in intersect_word2vec_format() (@KiddoZhu,[#1267](https://github.com/RaRe-Technologies/gensim/pull/1267)) + +* Fix wordrank max_iter_dump calculation. Fix #1216 (@ajkl,[#1217](https://github.com/RaRe-Technologies/gensim/pull/1217)) + +* Make SgNegative test use sg (@shubhvachher ,[#1252](https://github.com/RaRe-Technologies/gensim/pull/1252)) + +* pep8/pycodestyle fixes for hanging indents in Summarization module (@SamriddhiJain ,[#1202](https://github.com/RaRe-Technologies/gensim/pull/1202)) + +* WordRank and Mallet wrappers single vs double quote issue in windows.(@prakhar2b,[#1208](https://github.com/RaRe-Technologies/gensim/pull/1208)) + + +* Fix #824 : no corpus in init, but trim_rule in init (@prakhar2b ,[#1186](https://github.com/RaRe-Technologies/gensim/pull/1186)) + +* Hardcode version number. Fix #1138. ( @tmylk, [#1138](https://github.com/RaRe-Technologies/gensim/pull/1138)) + +Tutorial and doc improvements: + +* Color dictionary according to topic notebook update (@bhargavvader, [#1164](https://github.com/RaRe-Technologies/gensim/pull/1164)) + +* Fix hdp show_topic/s docstring ( @parulsethi, [#1264](https://github.com/RaRe-Technologies/gensim/pull/1264)) + +* Add docstrings for word2vec.py forwarding functions ( @shubhvachher, [#1251](https://github.com/RaRe-Technologies/gensim/pull/1251)) + +* updated description for worker_loop function used in score function ( @chinmayapancholi13 , [#1206](https://github.com/RaRe-Technologies/gensim/pull/1206)) -======== 1.0.1, 2017-03-03 * Rebuild cumulative table on load. Fix #1180. (@tmylk,[#1181](https://github.com/RaRe-Technologies/gensim/pull/893)) From c3076d3429ebecca921ab68c6e87b6caf0800087 Mon Sep 17 00:00:00 2001 From: tmylk Date: Mon, 10 Apr 2017 20:40:49 -0300 Subject: [PATCH 41/41] Bump up version. Fix PyPi description --- docs/src/conf.py | 4 ++-- setup.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/conf.py b/docs/src/conf.py index 956474c04f..b0b85005e3 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -52,9 +52,9 @@ # built documents. # # The short X.Y version. -version = '1.0' +version = '2.0' # The full version, including alpha/beta/rc tags. -release = '1.0.1' +release = '2.0.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 8e092243b5..5bf1ceeea2 100644 --- a/setup.py +++ b/setup.py @@ -172,7 +172,7 @@ def finalize_options(self): For alternative modes of installation (without root privileges, development -installation, optional install features), see the `documentation `_. +installation, optional install features), see the `install documentation `_. This version has been tested under Python 2.7, 3.5 and 3.6. Support for Python 2.6, 3.3 and 3.4 was dropped in gensim 1.0.0. Install gensim 0.13.4 if you *must* use Python 2.6, 3.3 or 3.4. Support for Python 2.5 was dropped in gensim 0.10.0; install gensim 0.9.1 if you *must* use Python 2.5). Gensim's github repo is hooked against `Travis CI for automated testing `_ on every commit push and pull request. @@ -228,7 +228,7 @@ def finalize_options(self): setup( name='gensim', - version='1.0.1', + version='2.0.0', description='Python framework for fast Vector Space Modelling', long_description=LONG_DESCRIPTION,