gensim0.8.6のチュートリアルをやってみた【コーパスとベクトル空間】

gensimの使い方がよく分からないからgensim0.8.6のチュートリアルをやってみた。そのメモ。


Corpora and Vector Spaces」のチュートリアルをやってみました。


gensim関係のやってみたシリーズのリンク一覧


Python GensimでLDAを使うための前準備・パッケージのインストール

gensimを使ったwikipediaのコーパス作成

gensim0.8.6のチュートリアルをやってみた【コーパスとベクトル空間】

gensim0.8.6のチュートリアルをやってみた【Topics and Transformations】

gensim0.8.6のチュートリアルをやってみた【Similarity Queries】



大事なことだけ最初に書いときます


ものすごくざっくり書きます。


下記の意味がなんとなく分かっていれば、gensimのさらに高度な機能(tfidf、LSA、LDA)を理解するのが簡単になります。


dictionary

最初に用意した大きな文章データから各単語の出現回数を計算しておいたもの。テキスト形式で保存して中身を見ればすぐ意味が分かると思います。


コーパス = *.mmファイル(Matrix Marketファイル)

dictionaryを元にして、解析したい文章を変換したもの。コーパスを見れば、解析したい文章にどの単語が何回出現するのかが分かります。


両方ともこの後にでてきます。



文字列からベクトルへの変換


Corpora and Vector Spaces」の順番通りに進んでいきます。


書いているコードは基本的にpythonのコードです。pythonインタプリタから実行してください。


# ロギングの設定。なくてもOK
import logging
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

from gensim import corpora, models, similarities

documents = ["Human machine interface for lab abc computer applications",
             "A survey of user opinion of computer system response time",
             "The EPS user interface management system",
             "System and human system engineering testing of EPS",
             "Relation of user perceived response time to error measurement",
             "The generation of random binary unordered trees",
             "The intersection graph of paths in trees",
             "Graph minors IV Widths of trees and well quasi ordering",
             "Graph minors A survey"]

9個のdocumentからなる配列を読み込みました。一つのdocumentは一つの文章からなります。


# remove common words and tokenize
stoplist = set('for a of the and to in'.split())
texts = [[word for word in document.lower().split() if word not in stoplist]
         for document in documents]

# remove words that appear only once
all_tokens = sum(texts, [])
tokens_once = set(word for word in set(all_tokens) if all_tokens.count(word) == 1)
texts = [[word for word in text if word not in tokens_once]
         for text in texts]

print texts

# 出力結果
[['human', 'interface', 'computer'],
 ['survey', 'user', 'computer', 'system', 'response', 'time'],
 ['eps', 'user', 'interface', 'system'],
 ['system', 'human', 'system', 'eps'],
 ['user', 'response', 'time'],
 ['trees'],
 ['graph', 'trees'],
 ['graph', 'minors', 'trees'],
 ['graph', 'minors', 'survey']]

読み込んだdocumentsをトークンに分割しました。その際、ありふれた単語とあまりにも珍しい単語を除外しています。


最後の出力結果は、各documentがどのようなトークンに分割されたのかを出力したものです。


dictionary = corpora.Dictionary(texts)
dictionary.save('/tmp/deerwester.dict') # store the dictionary, for future reference
# バイナリではなくテキストとして保存する場合
dictionary.save_as_text('/tmp/deerwester_text.dict')

print dictionary

# 出力結果
Dictionary(12 unique tokens)

dictionaryはbag-of-wordsを用いて作成された単語とその出現回数の対応表です。さらにユニークなIDもふられています。


フォーマットは、id[TAB]word_utf8[TAB]document frequency[NEWLINE]」です。(参考ページより)


テキストで保存されたファイルを見れば実際のデータが見れます。


$ less deerwester_text.dict

0	computer	2
8	eps	2
10	graph	3
1	human	2
2	interface	2
11	minors	2
3	response	2
4	survey	2
5	system	3
6	time	2
9	trees	3
7	user	3

print dictionary.token2id

# 出力結果
{'minors': 11, 'graph': 10, 'system': 5, 'trees': 9, 'eps': 8, 'computer': 0,
'survey': 4, 'user': 7, 'human': 1, 'time': 6, 'interface': 2, 'response': 3}

上記の結果から、minorsのIDは11、humanのIDは1であることが分かります。


new_doc = "Human computer interaction"
new_vec = dictionary.doc2bow(new_doc.lower().split())
print new_vec # the word "interaction" does not appear in the dictionary and is ignored
[(0, 1), (1, 1)]

doc2bow()はdistinctした結果から単語の出現回数を数えます。


[(0, 1), (1, 1)]という出力結果から、computerの辞書内のIDは0で今回の出現回数1回、humanの辞書内のIDは1で今回の出現回数は1回、interctionは辞書に入っていない(なので無視された)、ということが分かります。


corpus = [dictionary.doc2bow(text) for text in texts]
corpora.MmCorpus.serialize('/tmp/deerwester.mm', corpus) # store to disk, for later use

# 後から読み込む場合
corpus = corpora.MmCorpus('/tmp/deerwester.mm')
# ファイルから読み込んだ場合はストリームになっているので単純にはprintできないことに注意。ファイルから読み込んだ場合のprintはこれ↓
# print list(corpus)

print corpus

# 出力結果
[(0, 1), (1, 1), (2, 1)]
[(0, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1)]
[(2, 1), (5, 1), (7, 1), (8, 1)]
[(1, 1), (5, 2), (8, 1)]
[(3, 1), (6, 1), (7, 1)]
[(9, 1)]
[(9, 1), (10, 1)]
[(9, 1), (10, 1), (11, 1)]
[(4, 1), (10, 1), (11, 1)]

最初にdictionaryを作ったdocumentsに対して、各単語のIDと出現回数を出力したものが上記になります。入力元にdocumentsを使う必然性はないです。単に分かりやすさのためにdocumentsのテキストを入力に使っているようです。


意味を把握するために、最後の行のみ意味を辿ってみます。


# 辞書内の単語とIDの対応表
{'minors': 11, 'graph': 10, 'system': 5, 'trees': 9, 'eps': 8, 'computer': 0,
'survey': 4, 'user': 7, 'human': 1, 'time': 6, 'interface': 2, 'response': 3}

# 元の文章
"Graph minors A survey"

# トークンに分割後
['graph', 'minors', 'survey']

# 辞書内のIDと出現回数のベクトル(ID順でソートされている)
[(4, 1), (10, 1), (11, 1)]

上記の結果から、元の文章をトークンに分割すると3つの単語になり、それぞれの単語の辞書内のIDは4、10、11であり、最後に読み込ませた文章内での出現回数はそれぞれ1回、ということが分かります。



チュートリアルはさらに続くのですが、ここから先は、「メモリ利用効率を上げるために必要なデータのみを読み込む方法」の解説なので省略します。



まとめ


大学で線形代数やっといてよかった…。


著者プロフィール
Webサイトをいくつか作っています。
著者プロフィール