tfidfでお手軽なキーワード抽出

そんな大それたものではありません。 単語をtfidfで重み付けするという手法はかなりtraditionalな手法だと思いますが、これを利用してキーワード抽出をやってみました。
環境: python 2.7.2
OSX 10.8.4

tfidf

tfidfというのは以下で定義される量です。自分なりに書くのでwikiを観て頂いた方が良いかと。

\displaystyle  tfidf(w, d, D) = tf(w, d) \cdot idf(w, D) \\ tf(w, d) = \frac{t(w, d)}{N(d)} \\ idf(w, D) = \frac{1}{df(w, D)}

ただし、
t(w, d)は文書dにおける単語wの出現回数、
N(d)は文書dの総単語数(異なり語数ではない)、
df(w, D)は文書集合D=\{d_{1}, \ldots, d_{n}\}の内、単語wが出現する文書の数、
df(w, D)が0の時はidf(w, D)=0 (org2blogでの条件式の書き方がわかりませんでした。
とする。
なぜキーワード抽出にtfidfを使うのか。一つの理由にストップワードを明示的に指定しなくても、ストップワードがキーワードになることを防ぐことかできるという理由がある。(自分なり
言い切ってしまった。 例えば”the”だとか”a”だとかは当然tfが高くなるが、ほとんどの文章で出現するのでidfが0になる。

Implementation

目的は
文書dが与えられたとき、そのキーワードを抽出すること
です。 今回はnltkのロイターコーパス(nltk.corpus.reuters)を使います。 中でも”acq”クラスに属する文書の内200件だけ使います。それ以上使うとnltk too many open filesというエラーが発生したので(僕のMacだけでしょうか? コードを以下に示します。 これは非常に効率の悪いコードです。 「時間と空間のトレードオフ」という言葉がありますが、かなり空間を優先しています。つまり時間を犠牲にしています。 何回も文書を走査するので、当然遅いです。でかいコーパスを使うと、こういう問題にも遭遇するのだな、という良い勉強になりました。

# -*- coding: utf-8 -*-
from nltk.corpus import reuters
import math

def tf(word, words, lower=False):
    if lower:
        words = [word.lower() for word in words]
        word = word.lower()
    if words:
        return float(words.count(word)) / len(words)
    return 0.0

def df(word, docs, lower=False):
    if lower:
        docs = [[word.lower() for word in doc] for doc in docs]
        word = word.lower()
    return float(sum([word in doc for doc in docs])) / len(docs)

def idf(word, docs, lower=False):
    if lower:
        docs = [[word.lower() for word in doc] for doc in docs]
        word = word.lower()
    df_val = df(word, docs)
    if df_val == 0.0:
        return 0.0
    return math.log(1.0 / df_val)

def tfidf(word, doc, docs, lower=False):
    return tf(word, doc, lower=lower) * idf(word, docs, lower=lower)

target_class = "acq"
N = 200
docs = [reuters.words(fileid) for fileid in reuters.fileids(categories=target_class)]

docs = docs[:N]
doc = list(docs[0])
doc_set = set(doc)
doc_set = sorted(doc_set, key=lambda x:tfidf(x, doc, docs), reverse=True)

for word in doc_set[:20]:
    print "%s: %f" % (word, tfidf(word, doc, docs, lower=False))

Result

結果は以下のようになりました。(上位20件) 印象はまぁ悪くなさそうです。”とか,”が入っているのが何とも言えませんが笑。

Sumitomo: 0.091463
Komatsu: 0.058798
bank: 0.032187
Sogo: 0.026132
Heiwa: 0.026132
business: 0.023753
securities: 0.019674
We: 0.018469
": 0.018430
,": 0.017610
will: 0.017266
But: 0.016395
analysts: 0.014775
Gottardo: 0.013066
cases: 0.013066
network: 0.013066
Smithson: 0.013066
deregulation: 0.013066
quickly: 0.013066
Kleinwort: 0.013066    

Summary

nltkのロイターコーパスからtfidfを使って、お手軽なキーワード抽出を実装してみた。
最近ロイターコーパス使って遊んでいるので、その一環としてのポスト。 そういえばtfidfを使ったことがあんまりないなぁと思ったので、その復習もかねました。 今回思ったこと
1.でかいコーパスを扱うと、メモリの問題なども出てくる
2.dfの計算に結構時間がかかる
次回はいろいろ思いつくのですが、とりあえず並列計算をやってみたい。 あと、コーパスのあるカテゴリによく出てくる単語を抽出してみたい。 これにはまたtfidfを使うことになるかも。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中