潜在セマンティック分析とPython検索





Googleは最近、キーワード検索から完全な意味検索に移行することを発表しました。 検索アルゴリズムが世界の巨人の間でどれほどクールかはわかりませんが、小さなサンドボックスでの検索は非常に意味があります。 もちろん、多かれ少なかれ大量のデータを検索すると、すべてがそれほどバラ色ではないため、非常に慎重に単語を準備する必要がありますが、それでもなお。



すぐに予約します。理論にのみ興味がある人は、Habréの非常に良い記事を参照してください。







そのため、12個のドキュメントのリストがあります。



titles =[ "      WikiLeaks", "      ,  ", "      19 ", "     Wikileaks  ", "     ", "      Wikileaks", "         ", "    WikiLeaks, ,  ", "        " ]
      
      







実際には、これは入力データです。 次に、3つの準備操作を行う必要があります。

1)テキストにhtmlなどのゴミがある場合は、異なるコンマ、ピリオド、コロンを削除します。

2)すべてを小文字にし、in、on、forなどのすべての前置詞を削除します。

3)単語を通常の形式にします。つまり、プレミアム、プレミアムなどの単語を検索するのは異なる単語になるため、これを修正する必要があります。

4)同様のドキュメントを検索する場合は、1回だけ出現する単語を削除できます。類似性の分析では、それらは役に立たず、削除されるとメモリが大幅に節約されます。



アルゴリズム自体は、Python数学ライブラリのおかげで非常に簡単です。

5)ドキュメント内の単語の有無を表す、それぞれゼロと1のマトリックスをコンパイルします。

6)この行列の特異分解を実行し、その結果、空間内の文書と単語の座標を取得する他の3つの行列を取得します。



最後の段階では、簡略化された形式で、ドキュメントや単語の座標を比較するだけです。互いに最も近く、望ましい結果が得られるもの、遠くにあるものは関連性が低くなります。



numpy



scipy



を使用して行列ですべての操作を実行し、nltkを使用して単語を元の形式に戻します。 インストール中...

pip install numpy

pip install nltk

pip install scipy






scipy



インストール時に問題が発生した場合(BLASSをインストールする必要があります)、おそらく役立つでしょう。

apt-get install gfortran libopenblas-dev liblapack-dev









クラスの初期化。



 class LSI(object): def __init__(self, stopwords, ignorechars, docs): #      ,          self.wdict = {} # dictionary -         self.dictionary = [] #       , ,  self.stopwords = stopwords if type(ignorechars) == unicode: ignorechars = ignorechars.encode('utf-8') self.ignorechars = ignorechars #    for doc in docs: self.add_doc(doc)
      
      







単語の準備、辞書の補充、単語が辞書にある場合、その番号を返し、最初に不要な文字を取り除き、最初の形式にします

 def dic(self, word, add = False): if type(word) == unicode: word = word.encode('utf-8') #     word = word.lower().translate(None, self.ignorechars) word = word.decode('utf-8') #     word = stemmer.stem(word) #         if word in self.dictionary: return self.dictionary.index(word) else: #              if add: #self.ready = False self.dictionary.append(word) return len(self.dictionary) - 1 else: return None
      
      







元のマトリックスの構築

 def build(self): #    self.keys = [k for k in self.wdict.keys() if len(self.wdict[k]) > 0] self.keys.sort() #    self.A = zeros([len(self.keys), len(self.docs)]) #    for i, k in enumerate(self.keys): for d in self.wdict[k]: self.A[i,d] += 1
      
      







残りのマトリックスの構築

 def calc(self): """  U, S Vt -  """ self.U, self.S, self.Vt = svd(self.A)
      
      







マトリックス内の単語の重みまたは重要度を正規化します。 その出現に応じて用語の重要性を計算します。 たとえば、「and」という単語は非常に頻繁に見つかるため、この単語の重要度は低くなります。たとえば、「USA」という単語は大幅にカットされるため、重要度が高くなります。 標準的なスピーチは排除され、まれな用語が残ります。

  def TFIDF(self): #  -    wordsPerDoc = sum(self.A, axis=0) #      docsPerWord = sum(asarray(self.A > 0, 'i'), axis=1) rows, cols = self.A.shape for i in range(rows): for j in range(cols): self.A[i,j] = (self.A[i,j] / wordsPerDoc[j]) * log(float(cols) / docsPerWord[i])
      
      







座標軸上のドキュメントを比較して検索します。



 def find(self, word): self.prepare() idx = self.dic(word) if not idx: print ' ' return [] if not idx in self.keys: print '        stopwords' return [] idx = self.keys.index(idx) print 'word --- ', word, '=', self.dictionary[self.keys[idx]], '.\n' #    wx, wy = (-1 * self.U[:, 1:3])[idx] print 'word {}\t{:0.2f}\t{:0.2f}\t{}\n'.format(idx, wx, wy, word) arts = [] xx, yy = -1 * self.Vt[1:3, :] for k, v in enumerate(self.docs): #    ax, ay = xx[k], yy[k] #      dx, dy = float(wx - ax), float(wy - ay) arts.append((k, v, ax, ay, sqrt(dx * dx + dy * dy))) #      return sorted(arts, key = lambda a: a[4])
      
      







全コード
 class LSI(object): def __init__(self, stopwords, ignorechars, docs): self.wdict = {} self.dictionary = [] self.stopwords = stopwords if type(ignorechars) == unicode: ignorechars = ignorechars.encode('utf-8') self.ignorechars = ignorechars for doc in docs: self.add_doc(doc) def prepare(self): self.build() self.calc() def dic(self, word, add = False): if type(word) == unicode: word = word.encode('utf-8') word = word.lower().translate(None, self.ignorechars) word = word.decode('utf-8') word = stemmer.stem(word) if word in self.dictionary: return self.dictionary.index(word) else: if add: self.dictionary.append(word) return len(self.dictionary) - 1 else: return None def add_doc(self, doc): words = [self.dic(word, True) for word in doc.lower().split()] self.docs.append(words) for word in words: if word in self.stopwords: continue elif word in self.wdict: self.wdict[word].append(len(self.docs) - 1) else: self.wdict[word] = [len(self.docs) - 1] def build(self): self.keys = [k for k in self.wdict.keys() if len(self.wdict[k]) > 0] self.keys.sort() self.A = zeros([len(self.keys), len(self.docs)]) for i, k in enumerate(self.keys): for d in self.wdict[k]: self.A[i,d] += 1 def calc(self): self.U, self.S, self.Vt = svd(self.A) def TFIDF(self): wordsPerDoc = sum(self.A, axis=0) docsPerWord = sum(asarray(self.A > 0, 'i'), axis=1) rows, cols = self.A.shape for i in range(rows): for j in range(cols): self.A[i,j] = (self.A[i,j] / wordsPerDoc[j]) * log(float(cols) / docsPerWord[i]) def dump_src(self): self.prepare() print '    ' for i, row in enumerate(self.A): print self.dictionary[i], row def print_svd(self): self.prepare() print '  ' print self.S print '  3  U  ' for i, row in enumerate(self.U): print self.dictionary[self.keys[i]], row[0:3] print '  3  Vt ' print -1*self.Vt[0:3, :] def find(self, word): self.prepare() idx = self.dic(word) if not idx: print ' ' return [] if not idx in self.keys: print '        stopwords' return [] idx = self.keys.index(idx) print 'word --- ', word, '=', self.dictionary[self.keys[idx]], '.\n' #    wx, wy = (-1 * self.U[:, 1:3])[idx] print 'word {}\t{:0.2f}\t{:0.2f}\t{}\n'.format(idx, wx, wy, word) arts = [] xx, yy = -1 * self.Vt[1:3, :] for k, v in enumerate(self.docs): ax, ay = xx[k], yy[k] dx, dy = float(wx - ax), float(wy - ay) arts.append((k, v, ax, ay, sqrt(dx * dx + dy * dy))) return sorted(arts, key = lambda a: a[4])
      
      











上記のコードを呼び出すことは残っています。



 docs =[ "      WikiLeaks", "       ,  ", "      19 ", "     Wikileaks  ", "     ", "      Wikileaks", "         ", "    WikiLeaks, ,  ", "        " ] ignorechars = ''',:'!''' word = "" lsa = LSI([], ignorechars, docs) lsa.build() lsa.dump_src() lsa.calc() lsa.print_svd() for res in lsa.find(word): print res[0], res[4], res[1], docs[res[0]]
      
      







 lsa.dump_src()

イギリス人[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
警官[1. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
知っている[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]] 
 ...


ドキュメントの列、行、用語。



 lsa.print_svd()

 U行列の最初の3列です 
イギリス[-0.06333698 -0.08969849 0.03023127]
警察官[-0.14969793 -0.20853416 0.07106177]
知っている[-0.06333698 -0.08969849 0.03023127]
 ...

 Vtマトリックスの最初の3行は次のとおりです。
 [[0.25550481 0.47069418 0.27633104 0.39579252 0.21466192 0.26635401 0.32757769 0.3483847 0.3666749]
  [0.34469126 -0.18334417 -0.36995197 0.37444485 -0.29101203 0.27916372 -0.26791709 0.45665895 -0.35715836]
  [-0.10950444 0.64280654 -0.39672464 -0.1011325 -0.36012511 -0.01213328 0.38644373 -0.14789727 -0.32579232]]




 lsa.findの解像度(単語):
	 res [0]、res [4]、res [1]、docs [res [0]]を印刷します

ワード9(辞書内のワードコード)-0.17(最初のワード座標)0.46(2番目の座標)USA(ワード自体)

 リスト内の文書番号| 距離| コードにレイアウトされたドキュメント| ドキュメント自体
 6 0.127328977215 [35、36、9、37、38、39、23、40、12、41] NATOと米国は、ロシアに対するバルト諸国の防衛計画を策定しました。
 1 0.182108022464 [7、8、9、9、10、11、12、13、14、15]米国の裁判所では、スパムを送信したロシア人に対する裁判が開始されます
 5 0.649492914495 [31、8、32、33、34、5、6]スウェーデンの裁判所は、ウィキリークスの創設者の控訴を検討することを拒否しました
 0 0.765573367056 [0、1、2、3、4、5、6]イギリスの警察はウィキリークスの創設者の居場所を知っています
 3 0.779637110377 [7、24、25、5、26、6、27、28]ウィキリークスの創設者ジュリアン・アサンジが英国で逮捕
 8 0.810477163078 [7、45、36、46、47、48、17、18、19]本日、ストックホルムとオスロでノーベル賞が授与されます
 4 0.831319718049 [29、30、16、17、18、19]ウクライナはノーベル賞授賞式を無視
 7 0.870710388156 [1、24、42、5、6、43、44、25]イギリスの警察はウィキリークスの創設者を発見したが、逮捕しなかった
 2 0.88243190531 [16、17、18、19、20、21、22、23] 19か国がノーベル平和賞授賞式をボイコットしています 




それだけです。トピックは非常に広範囲に渡ります。可能な限り簡潔に試しました。



便利なリンク


- 潜在セマンティック検索の簡単な理論 (rus。)

-gensim-Python LSAのライブラリ

-nltk-単語を正規化するためのライブラリ

- 機械学習用のscikit-learnライブラリ



All Articles