TF-IDFについて書いてみる。

主に情報検索の分野で使われるTF-IDFについて勉強したので、そのメモ。

さらに、scikit-learnで用意されているものを使ってTF-IDFを計算してみます。

環境

MeCabについては

www.pytry3g.com

関連リンク

sklearn.feature_extraction.text.CountVectorizer — scikit-learn 0.19.2 documentation

sklearn.feature_extraction.text.TfidfTransformer — scikit-learn 0.19.2 documentation

sklearn.feature_extraction.text.TfidfVectorizer — scikit-learn 0.19.2 documentation

4.2. Feature extraction — scikit-learn 0.19.2 documentation

TF-IDF

TF-IDFについてかるくまとめてみます。

TF-IDFとは、Term Frequency - Inverse Document Frequencyの略で自然言語をベクトルで表現する方法のひとつ、ある文書を特徴づける重要な単語を抽出したいときに有効な手法です。

TF-IDFの求め方は次のとおりです。

tfidf = tf x idf

tfidfを掛けることによりtfidfを求められます。

Term Frequency

tfとはある単語のある文書における出現頻度のことです。tfでは出現頻度が多い単語ほど重要になります。

tfの求め方は次のとおりです。

tf = 単語iの文書dにおける出現回数 / 文書dにおける全単語の出現回数の和

以下の例からtfを求めてみます。

例えば、2つの文A, Bがあったとします。

 A: 私は醤油ラーメンととんこつラーメンが好きです。

 B: 私はとんこつラーメンと味噌ラーメンが好きです。

この2つの文を形態素解析して名詞だけを取り出すと下のようになります。

 A: ['私', '醤油', 'ラーメン', 'とんこつ', 'ラーメン', '好き']

 B: ['私', 'とんこつ', 'ラーメン', '味噌', 'ラーメン', '好き']

文書Aにおける単語ラーメン醤油tfを求めてみます。

式より、

tf(ラーメン, 文書A)= 2 / 6 = 0.33

tf(醤油, 文書A)= 1 / 6 = 0.17

ラーメンのほうが値が高くなっており文書Aではラーメンが重要な単語であることを意味します。

文書Bでも同じことが言えます。

Inverse Document Frequency

idfとはある単語がいくつの文書で使われているかを表し、色々な文書によく使われる単語は重要ではありません。すなわち、めったにお目にかかれない単語ほど高い値になります。

idfの求め方は次のとおりです。

idf = log(総文書数 / ある単語iを含む文書の数) + 1

tfと同じ例でidfを求めてみます。

総文書数は文書Aと文書Bだけなので2になります。

idf(ラーメン)= log( 2 / 2 ) + 1 = 1

idf(醤油)= log( 2 / 1 ) + 1 = 1.3

ラーメンより醤油のほうが出現頻度が低いので高い値になっています。

Term Frequency - Inverse Document Frequency

再びTF-IDFに話は戻ります。

上の例の文書からtfidfについて求めることができたので、今度はTF-IDFの計算をしてみます。

TF-IDF = tf(ラーメン, 文書A)* idf(ラーメン)= 0.33 * 1 = 0.33

TF-IDF = tf(醤油, 文書A)* idf(醤油)= 0.17 * 1.3 = 0.22

ラーメンのほうが若干値が高くなりました。

tfよりもTF-IDFで計算したほうが醤油の値が大きくなり、ラーメンと醤油の値の差が小さくなりました。

出現頻度が多く(tfの値が大きい)さらに、いくつもの文書で出現しない(idfの値が大きい)単語ほどTF-IDFの値は大きくなり、ある文書Dを特徴づける単語となります。

そのため、出現頻度が多いラーメンの値が大きいままで、出現頻度が少ないものの文書Bにしか出現しない醤油の値がtfよりも大きくなったのではないかと思います。

データの用意

ここからはプログラムを使ってTF-IDFを計算してみます。

まずは、TF-IDFを計算するデータ(文書)を用意します。今回用いるデータは以前書いた記事で作成したデータです。

データの中身はTwitterの対話データから抽出されたツイートになっていて、ロシアW杯日本代表に関するツイートになっています。全部で742のツイートがあります。

これら742のツイートを形態素解析したのちに、print()を使ってファイルに書き込んでいます。

今回は新たに作成したtfidf-dataset.txtを使ってTFIDFを計算します。

import MeCab
import codecs

with codecs.open("positive.txt", "r", "utf-8") as f:
    corpus = f.read().splitlines()

tagger = MeCab.Tagger('-Owakati')
corpus = [tagger.parse(line).strip() for line in corpus]
print(*corpus, sep="\n", file=codecs.open('tfidf-dataset.txt', 'w', 'utf-8'))

下は生成されたtfidf-dataset.txtの中身の一例。

原口 と 槙野 も 先発 だ と 期待 し て ます 。
槙野 は 出る と 思う ん です が 、 原口 は もしか する と 乾 の サブ かも ねー 。 どちら に し て も 頑張っ て ー !
今日日 本戦 ! ! ! たのしみ ! 頑張れ ! コロンビア !
それ ! もう 日本 出 なく て いい 。 経費 の 無駄
本田 スタメン 外し た の は 英断 だ けど 中盤 誰 が 守備 すん ねん w
この メンツ なら 原口 だ な 。

プログラムを書く

scikit-learnを使ったTFIDFの計算方法は2通りあります。ひとつはCountVectorizerTfidfTransformerを使う方法、もうひとつはTfidfVectorizerを使う方法です。

それぞれについて説明する前にまずはデータを読み込みます。

import codecs
corpus = codecs.open('tfidf-dataset.txt', 'r', 'utf-8').read().splitlines()
# corpus[0]
# '原口 と 槙野 も 先発 だ と 期待 し て ます 。'
# corpus[:3]
# ['原口 と 槙野 も 先発 だ と 期待 し て ます 。',
#'槙野 は 出る と 思う ん です が 、 原口 は もしか する と 乾 の サブ かも ねー 。 どちら に し て も 頑張っ て ー !',
# '今日日 本戦 ! ! ! たのしみ ! 頑張れ ! コロンビア !']

データはリストになっていて、中身は分かち書きされた文字列になっています。

方法① CountVectoizerとTfidfTransformer

CountVectorizer

CountVectorizerはテキストを単語に分割し、その出現頻度をカウントして行列に変換してくれる。

TfidfTransformer

TfidfTransformerはCountVectorizerで作った行列からtfもしくはtfidfを正規化して計算してくれる。デフォルトでは、tfidfを計算するようになっている。

使い方

tfidfを計算するプログラム。

import codecs
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer

# データの用意
corpus = codecs.open('tfidf-dataset.txt', 'r', 'utf-8').read().splitlines()

vectorizer = CountVectorizer(token_pattern=u'(?u)\\b\\w+\\b')
transformer = TfidfTransformer()

tf = vectorizer.fit_transform(corpus)
tfidf = transformer.fit_transform(tf)
# tfidfが計算された結果の表示
print(tfidf.toarray())
"""
[[0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 ...
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]]
"""

print(tfidf.shape)
# (742, 2309)
# (テキストの数、出現した単語の数)

手順としては

  1. CountVectorizerで単語の出現頻度を計算
  2. TfidfTransformerで単語の出現頻度からtfidfを計算

あとはtfidfを使って煮るなり焼くなり好きにする。

オマケ

tokens_pattern

CountVectorizerのインスタンスを作るときにtokens_patternにu'(?u)\\b\\w+\\b'を渡しています。これをすることにより単語をカウントする際、長さ1の単語もカウントするようになります。デフォルトでは長さ1の単語はカウントされません。

vocabulary_

カウントした単語が辞書になっている。keyは単語でvalueは単語ID。

print(vectorizer.vocabulary_)
"""
{'原口': 1165,
 '槙野': 1531,
 '先発': 1072,
 '期待': 1507,
 'ます': 527,,,
"""
get_feature_names()

出現した単語がリストに入っている。

print(vectorizer.get_feature_names())
"""
['00',
 '10',
 '100',
 '1000',
 '11',
 '13',,,,
"""
toarray()

すべての文書における単語の出現頻度をnumpy型の配列に変換する。

下の例では10個の文書における単語の出現頻度を表示している。

print(vectorizer.toarray()[:10])
"""
array([[0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       ...,
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0]], dtype=int64)
"""

方法② TfidfVectorizer

TfidfVectorizer

CountVectorizerとTfidfTransformerの役割をこれひとつでしてくれる。

使い方

データを渡す、だたそれだけ。

import codecs
from sklearn.feature_extraction.text import TfidfVectorizer

# データの用意
corpus = codecs.open('tfidf-dataset.txt', 'r', 'utf-8').read().splitlines()

vectorizer = TfidfVectorizer()
tfidf = vectorizer.fit_transform(corpus)

CountVectorizerとTfidfTransformerで紹介したオマケはTfidfVectorizerでも使用可です。

次回

TF-IDFからCosine Similarityを計算したい。

計算してみました。

www.pytry3g.com