TF-IDFを実装してみた

最近TF-IDFについてのコードをPythonで書いたので、それについて自分なりにまとめておきます。解釈違いなところなどありましたら指摘してください。
ソースコードはこちら:[Github](https://github.com/hatopoppoK3/NaturalLanguageProcessing/blob/master/TF-IDF/)
# TF-IDFとは
wikipediaから引用 https://ja.wikipedia.org/wiki/Tf-idf
> tf-idfは、文書中に含まれる単語の重要度を評価する手法の1つであり、主に情報検索やトピック分析などの分野で用いられている。 tf-idfは、tf（英: Term Frequency、単語の出現頻度）とidf（英: Inverse Document Frequency、逆文書頻度）の二つの指標に基づいて計算される。

文書の中での単語の重要度を表すものであり、その文書の特徴などを知りたいときに使います。TF値とIDF値を掛け合わせたものがTF-IDF値になります。

## TF(Term Frequency 単語の出現頻度)
見出しにある通りです。文書を単語ごとに分解して、TF値を求めたい単語の出現頻度を求めます。単語の出現頻度が高いほうが重要度が高いので、この値は大きくなります。

```math
TF = \frac{特定の単語の出現数}{文書の全単語数}
```

## IDF(Inverse Document Frequency 逆文書頻度)
IDF値を求めたい単語の文書頻度の逆数に対数をとって値を求めます。しかし、ただ対数をとった場合だとIDF値が0になってしまうことがあるので、今回は対数取った値に1を足しました。
多くの文書に登場している単語は特徴がない単語であるといえるので、IDF値は小さくなります。逆に、少ない文書でしか使われない単語は特徴のある単語といえるので、IDF値は大きくなります。

```math
IDF = \log \frac{全文書数}{単語が出現した文書数} + 1
```

TF値とIDF値を掛け合わせたものがTF-IDF値になるので、文書内での単語の出現頻度が高く、多くの文書では使われない単語ほど、その文書でのその単語のTF-IDF値は大きくなり、その文書の特徴をあらわす単語であるといえます。

# 実装してみた
今回は[青空文庫](https://www.aozora.gr.jp/)から13作品をとってきてそれを用いて実装した。コードも貼りますが、全体は[Github](https://github.com/hatopoppoK3/NaturalLanguageProcessing/blob/master/TF-IDF/)のほうにあるので、一部を抜粋します。
## 形態素解析
TF-IDF値を求めるにはまず単語ごとに分解しなくてはいけません。今回は単語の中で名詞を対象にしました。形態素解析にはMeCabを使いました。また、いくつかストップワードを設定しました。
ローカルにあるデータを読み込んで、その文書を形態素解析し単語ごとの出現回数をカウントして、そのCounter(dict)を返します。

```Python:main.py
def get_word_counter(filename):
    filepath = './TF-IDF/text_data/'+filename
    with codecs.open(filepath, 'r', encoding='shift-jis') as fp:
        word_counter = Counter()
        text = fp.read()
        text_lines = text.split('\n')
        for text_line in text_lines:
            node = tagger.parseToNode(text_line)
            while node:
                word_type = node.feature.split(',')[0]
                if (word_type == '名詞') and (len(node.surface) != 0) \
                        and (not(node.surface in stop_word_list)):
                    word_counter[node.surface] += 1
                node = node.next
    return word_counter
```

## TF値
先ほど求めた単語の出現頻度を使って文書の単語ごとにTF値を算出します。
(key,value)=(単語,TF値)となってるdictを返します。

```Python:main.py
def get_tf_dict(word_counter):
    tf_dict = {}
    word_total = sum(word_counter.values())
    for word, count in word_counter.items():
        tf_dict[word] = count/word_total
    return tf_dict
```

## IDF値
文書ごとに単語の出現頻度を記録しているdictを用いてIDF値を算出します。
(key,value)=(単語,IDF値)となってるdictを返します。

```Python:main.py
def get_idf_dict(file_word_counter, targetname):
    word_list = file_word_counter[targetname].keys()
    filename_list = file_word_counter.keys()
    idf_dict = {}
    for word in word_list:
        word_count = 0
        for filename in filename_list:
            if word in file_word_counter[filename].keys():
                word_count += 1
        idf_dict[word] = log(len(filename_list)/word_count)+1
    return idf_dict

```

## 実行結果
13作品を用いて作品ごとにTF-IDF値が高い10単語を出力しました。その一部を貼っておきます。作品を象徴するような単語が高い値をとっていることが分かります。たぶんいい感じに実装できたのではないでしょうか。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272204/1bd32dec-b753-94a5-69f8-22b439771018.png)

# まとめ
TF-IDFについて学んだことを自分なりまとめました。Pythonでいい感じに実装ができたので良かったと思います。しかし、固有名詞や複合名詞などはユーザ辞書を定義していないので単語区切りが不自然なところがあるのが課題ですね。
scikit-learnを用いても計算はできるらしいのでそれも試してみようかと思います。