目次
gensim のチュートリアル2を日本語にしてみました。 このチュートリアルのコードサンプルを GitHub: gensim-learning で公開しています。
トピックと変換
準備
ログイベントを表示するには次のコードを実行してください。
1 2 |
import logging logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO) |
変換のインターフェース
以前のチュートリアル「コーパスとベクトル空間」で、ベクトルのストリームとして表現されたドキュメントのコーパスを作りました。 ここでもそのコーパスを使います。
1 2 3 4 5 6 7 |
from gensim import corpora, models, similarities if (os.path.exists("/tmp/deerwester.dict")): dictionary = corpora.Dictionary.load('/tmp/deerwester.dict') corpus = corpora.MmCorpus('/tmp/deerwester.mm') print("Used files generated from first tutorial") else: print("Please run first tutorial to generate data set") |
MmCorpus(9 documents, 12 features, 28 non-zero entries)
このチュートリアルでは、どのようにドキュメントをひとつのベクトル表現から他の表現に変換するのかを説明します。 そのプロセスには2つのゴールがあります。
- コーパスの隠れた構造を引き出すために、単語間の関係を発見し、それを使って文書を新しい意味のある方法で記述します。
- 文書表現をよりコンパクトにします。 これは、効率を改善し、効力を向上させます。 新しい表現形式ではリソース消費を少なくして、 限界データ傾向を無視することでノイズを低減します。
変換の作成
変換は標準的な Python のオブジェクトです。 基本的にコーパスを訓練することで初期化されます。
1 |
tfidf = models.TfidfModel(corpus) # step 1 -- initialize a model |
チュートリアル1で使用したコーパスを変換モデルの初期化(トレーニング)に使用しました。 異なる変換には、異なる初期化パラメータが必要になる場合があります。 TfIdf の場合、 “トレーニング” は単純に提供されたコーパスを一度処理し、 ドキュメントの頻度を全ての特徴について計算します。 他のモデル、たとえば Latent Semantic Analysis (潜在意味解析) や Latent Dirichlet Allocation (潜在的ディリクレ配分法) を学習させる場合は、 より多くのコーパスが必要となり、 結果として、はるかに多くの時間がかかります。
メモ
変換は常に特定の2つのベクトル空間の間で行われます。 同じベクトル空間(特徴IDが同じ)はトレーニング及び後続するベクトル変換にも使う必要があります。 同じ特徴空間の入力を使わない場合、 たとえば異なる特徴IDを使用して異なる文字列の前処理をした場合、 または TfIdf が使われるべきところで bag-of-words の入力ベクトルを使用する場合などは、 変換の際に特徴IDのミスマッチが発生し、 結果として出力例外または実行時例外が発生する。
ベクトルの変換
今から tfidf
を古い表現(bag-of-words の出現回数)から新しい表現(TfIdf の実数の重み)にベクトルを変換することができる、読み取り専用のオブジェクトとして扱います。
1 2 3 4 |
# step 2 -- モデルをベクトルの変換に使用する doc_bow = [(0, 1), (1, 1)] print(tfidf[doc_bow]) # [(0, 0.70710678), (1, 0.70710678)] |
変換をコーパス全体に適用するには次のようにします。
1 2 3 4 5 6 7 8 9 10 11 12 |
corpus_tfidf = tfidf[corpus] for doc in corpus_tfidf: print(doc) # [(0, 0.57735026918962573), (1, 0.57735026918962573), (2, 0.57735026918962573)] # [(0, 0.44424552527467476), (3, 0.44424552527467476), (4, 0.44424552527467476), (5, 0.32448702061385548), (6, 0.44424552527467476), (7, 0.32448702061385548)] # [(2, 0.5710059809418182), (5, 0.41707573620227772), (7, 0.41707573620227772), (8, 0.5710059809418182)] # [(1, 0.49182558987264147), (5, 0.71848116070837686), (8, 0.49182558987264147)] # [(3, 0.62825804686700459), (6, 0.62825804686700459), (7, 0.45889394536615247)] # [(9, 1.0)] # [(9, 0.70710678118654746), (10, 0.70710678118654746)] # [(9, 0.50804290089167492), (10, 0.50804290089167492), (11, 0.69554641952003704)] # [(4, 0.62825804686700459), (10, 0.45889394536615247), (11, 0.62825804686700459)] |
この特殊なケースでは、トレーニングに使ったものと同じコーパスを変換していますが、これは単なる偶然です。 変換モデルは一度初期化されたら、どんなベクトルに対しても使用できます(もちろん同じベクトル空間のものであれば)。 たとえそれがトレーニングに使用されたコーパス内で全く使用されていなくても。 これはLSAの畳み込みというプロセスや、LDAのトピックス異論などによって実現されます。
メモ
model[corpus]
を呼び出すことで、 古いコーパス文書ストリームのラッパーを作成します。 実際の変換は文書をイテレートしている間にオンザフライで(リアルタイムに)行われます。 corpus_transformed = model[corpus]
を呼び出すタイミングで、 コーパス全体を変換することは不可能です。 なぜならそれは結果をメインメモリに保存することになり、 gensim のメモリ非依存性の目的に反するからです。 もし変換された corpus_transformed
を複数回イテレートする場合で 変換が負担となる場合には、 結果のコーパスをシリアライズしてディスクに保存し、 それを使い続けるようにします。
変換もまた連鎖の形で別の変換の上にシリアライズできます。
1 2 3 4 |
# LSI 変換 の初期化 lsi = models.LsiModel(corpus_tfidf, id2word=dictionary, num_topics=2) # 二重のラッパーを元あるコーパスの上に作成します: bow->tfidf->fold-in-lsi corpus_lsi = lsi[corpus_tfidf] |
ここで If-Idf コーパス を Latent Semantic Indexing (潜在意味インデキシング) を経由して 潜在的2次元空間 (num_topics=2
としたため2次元となっています) に変換しました。 おそらくこの2つの潜在的次元は何を表しているのか不思議に思っていることでしょう。 models.LsiModel.print_topics()
を使って調べてみましょう。
1 2 3 |
lsi.print_topics(2) # topic #0(1.594): -0.703*"trees" + -0.538*"graph" + -0.402*"minors" + -0.187*"survey" + -0.061*"system" + -0.060*"response" + -0.060*"time" + -0.058*"user" + -0.049*"computer" + -0.035*"interface" # topic #1(1.476): -0.460*"system" + -0.373*"user" + -0.332*"eps" + -0.328*"interface" + -0.320*"response" + -0.320*"time" + -0.293*"computer" + -0.280*"human" + -0.171*"survey" + 0.161*"trees" |
(トピックはログに出力されます。 ログを有効にするには、このページの最上部のメモを参照してください。)
LSI によれば、 “trees”, “graph”, “minors” が全て関連した言葉となっています (そしてほとんどが最初のトピックに影響しています)。 一方2つめのトピックは他の全ての言葉に関係しています。 期待した通り、最初の5つの文書はより強く2番目のトピックに関連しており、 残る4つのドキュメントは最初のトピックに強く関連しています。
1 2 3 4 5 6 7 8 9 10 11 |
for doc in corpus_lsi: # both bow->tfidf and tfidf->lsi transformations are actually executed here, on the fly print(doc) # [(0, -0.066), (1, 0.520)] # "Human machine interface for lab abc computer applications" # [(0, -0.197), (1, 0.761)] # "A survey of user opinion of computer system response time" # [(0, -0.090), (1, 0.724)] # "The EPS user interface management system" # [(0, -0.076), (1, 0.632)] # "System and human system engineering testing of EPS" # [(0, -0.102), (1, 0.574)] # "Relation of user perceived response time to error measurement" # [(0, -0.703), (1, -0.161)] # "The generation of random binary unordered trees" # [(0, -0.877), (1, -0.168)] # "The intersection graph of paths in trees" # [(0, -0.910), (1, -0.141)] # "Graph minors IV Widths of trees and well quasi ordering" # [(0, -0.617), (1, 0.054)] # "Graph minors A survey" |
save()
や load()
といったメソッドでモデルを永続化させることができます。
1 2 |
lsi.save('/tmp/model.lsi') # same for tfidf, lda, ... lsi = models.LsiModel.load('/tmp/model.lsi') |
次の質問はおそらく、 これらのドキュメントはそれぞれどれくらい類似しているのかということでしょう。 与えられた入力文書について、類似度に基づいて他の文書をピックアップできるように、 類似度を定式化する方法はあるのでしょうか。 類似度の計算は次のチュートリアルで扱います。
利用可能な変換
gensim は複数の有名なベクトル空間モデルアルゴリズムを実装しています。
-
Term Frequency * Inverse Document Frequency, Tf-Idf は初期化の際に bag-of-words (整数値) の学習によるコーパスを必要とする。 変換の際には、 ベクトルの入力に対して同一次元の別のベクトルを返します。 ただし学習されたコーパスの中では、滅多に出てこない特徴が大きい値として計算されます。 それゆえ、整数値のベクトルは実数値ベクトルに変換され、次元数はそのまま据え置かれます。(訳注: 学習コーパスの中では必ず整数値になってしまうため、出現回数1回が割合的に大きめの値になってしまう。 そこで、変換後のベクトルを実数値ベクトルにして、 稀に出現する単語の重みを低く計算できるようにする。) 結果のベクトルをユークリッドの単位量に正規化することもできます。
1>>> model = models.TfidfModel(corpus, normalize=True) -
潜在意味インデキシング (Latent Semantic Indexing), LSI (LSA とも呼ばれる) はドキュメントを bug-of-words または (好ましくは) TfIdfで重み付けされた空間から 低次元の潜在空間に変換します。 上のチュートリアルのコーパスでは、 2潜在次元しか使いませんでしたが、 実際のコーパスでは目標次元を 200-500 にするのが “ゴールデンスタンダード” として推奨されています (cf: Bradford. 2008. An empirical study of required dimensionality for large-scale latent semantic indexing applications) 。
1>>> model = models.LsiModel(tfidf_corpus, id2word=dictionary, num_topics=300)LSI学習はどの時点でも、学習に使用する多くのドキュメントを提供すれば学習を継続できるという点で独特です。 これはオンライントレーニングと呼ばれるプロセスの中で、基礎となるモデルの増分更新によって実現されます。 この機能により、 入力ドキュメントストリームは 無限であっても良い。 データ到着時に LSI に 新しいドキュメントを提供し続け、 その間、計算された変換モデルは読み取り専用として使います。
123456789# now LSI has been trained on tfidf_corpus + another_tfidf_corpusmodel.add_documents(another_tfidf_corpus)# convert some new document into the LSI space, without affecting the modellsi_vec = model[tfidf_vec]...# tfidf_corpus + another_tfidf_corpus + more_documentsmodel.add_documents(more_documents)lsi_vec = model[tfidf_vec]...無限ストリームの古い観測値を”忘却”させる LSI の作り方の詳細は
gensim.models.lsimodel
のドキュメントを参照してください。 もし汚くしたいのであれば、 LSI アルゴリズム に置いての スピード、メモリフットプリント、数値精度 を調整するパラメータも調整できます。gensim は、 筆者が “Řehůřek. 2011. Subspace tracking for Latent Semantic Analysis” で公開した、 ノーベルオンラインインクリメンタルストリーム分散学習アルゴリズムを用いています。gensim はまた内部的に Halko らによる確率的マルチパスアルゴリズムを実行します(cf: Halko, Martinsson, Tropp. 2009. Finding structure with randomness)。 そして計算の中核部分を加速します。 クラスタコンピュータに計算を分散させることでさらなるスピードアップを行う、英語版ウィキペディアの実験を参照してください。
-
ランダムプロジェクション (Random Projections), RP はベクトル空間の次元を下げることを目的としています。これは単語・文書間の TfIdf の距離を近似するための (メモリにもCPUにも) とても効果的なアプローチで、 少々のランダム値を利用しています。 おすすめの目標次元は データセットによりますが、 100-1000 次元です。
1>>> model = models.RpModel(tfidf_corpus, num_topics=500) -
潜在的ディリクレ配分法 (Latent Dirichlet Allocation), LDA は bag-of-words の出現数 から低次元のトピック空間への 別の変換方法です。 LDA は LSA (多項式PCAとも呼ばれる) の確率的拡張で、 LDA のトピックは単語の確率分布として解釈できます。 これらの分布は LSA のように、 学習コーパスから自動的に予測できます。 ドキュメントはこれらのトピックの(ソフトな)混合物として解釈されます、 LSA のように。
1>>> model = models.LdaModel(corpus, id2word=dictionary, num_topics=100)gensim は “Hoffman, Blei, Bach. 2010. Online learning for Latent Dirichlet Allocation” に基づいた、 手早いオンラインLDAパラメータ予測を使用します。 そのアルゴリズムは、クラスタ化されたコンピュータの上で、分散モードで実行されるよう手が加えられています。
-
階層ディリクレ過程 (Hierarchical Dirichlet Process), HDP はノンパラメトリックベイズ法です (要求されたトピックの数が不足していることに注意してください) 。
1>>> model = models.HdpModel(corpus, id2word=dictionary)gensim は “Wang, Paisley, Blei. 2011. Online variational inference for the hierarchical Dirichlet process” に基づいて 高速なオンライン実装をしています。 この HDP モデル は gensim の追加機能で、 学術的な立場からはまだ荒いため、 注意して使う必要があります。
新しい VSM 変換 (異なる重み付けスキームなど) を追加することは簡単です。 詳細や例については、APIリファレンスを参照するか、Pythonのコードを見てください。
再度記述しておきますが、これらはすべて独特で漸進的な実装で、トレーニングコーパス全体を一度にメインメモリに格納する必要はありません。 メモリを配慮して、CPU効率も向上させるため、分散コンピューティングを改善しています。 もし、テストしたり、ユースケースやコードを提供するなど、貢献できると思ったら、教えてください。
次のチュートリアル、類似度クエリに続きます。