Tf-idfベクトルってなんだ？

みなさんも自然言語処理をやっていて`Tf-idf`というパッケージを使ったことがあることでしょう。
`Tf-idf`は`doc2vec`同様、自然言語を取り扱っていく上で重要なファクターの一つとなります。


今回はその`Tf-idf`の挙動に焦点を当て、ベクトル化した言語をどう学習していくかは次の機会にでも書いていきたいと思います。

# なんでTf-idfを使うの？
自然言語を機械に学習させたいとき、人々はその慣れ親しんだ言語を自由自在に理解し取り扱うことができますが、機械は真面目なためその自然言語のままでは取り扱えません。

そこで、我々は言語を数値化して機械に読み込ませます。
かといって数値の基準は様々です。単純に頻度を数値にしてテキストマイニングしたり、または意味合いを数値化してメンヘラ度を測るなんてこともできます。

自然言語を取り扱う時、企業のマーケティングに使いたいなんて場合が多いかと思います。
そんな時、文章中に出てくる単語がどれくらい重要なのかを調べたいと考えるでしょう。

今回取り扱う`Tf-idf`というパッケージでは、その*文書内での重要度*と*一般的な希少度*を`ベクトル`として数値化してくれます。

# Tf-idfとは？

![tfidfvectorizer.001.jpeg](https://qiita-image-store.s3.amazonaws.com/0/243995/e8c22c1a-7545-4312-e546-371f2a10b2fc.jpeg)

Tf-idfとは

- 文書内での単語の重要度tf(term frequency)
- 一般的な単語の希少度idf(inverse document frequency)

をかけ合わせたものです。

これを用いることで、複数の文章を比較し

- 任意の単語の傾向
- 任意の文章の傾向

を同時に探ることができます。とっても便利です。

それぞれの公式は

![tfidfvectorizer.002.jpeg](https://qiita-image-store.s3.amazonaws.com/0/243995/484288c9-adbf-c558-9f45-cee9db13ecec.jpeg)

によって得ることができます。内容は説明したとおりです。それぞれの変数の意味がわからない人は<a href="https://ja.wikipedia.org/wiki/Tf-idf">ウィキペディア</a>をご参照ください。

このtfとidfを掛け合わせることによってどれだけ頻出かだけでなく、どれだけ希少かも同時に調べることができます。

頻出かどうかだけ調べた場合、「is」などのそれほど重要でない単語が一番重要だと判断されやすくなってしまいます。
idfには主にこういった単語の重要度を下げる働きがあり、そうすることで正しく重要単語を取り出すことができます。





# Tf-idfを使って自然言語をベクトル化

さて、`Tf-idf`の仕組みもわかったところで、さっそく自然言語をベクトル化してみましょう。

実は、この記事を書いたきっかけは、`Tf-idf`には癖があるからなのです。
詳しくはインタプリタに聞いてみることにします。


`Tf-idf`で自然言語をベクトル化したい時、`TfidfVectorizer()`を用いて以下のようにします。

```Python
from sklearn.feature_extraction.text import TfidfVectorizer

# ベクトル化したい文書
corpus = [
    "This is the first document.",
    "This document is the second document.",
    "And this is the third one.",
    "Is this the first document?"
]


vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(corpus)
```


たったこれだけで`corpus`という文章の集合をベクトル化することに成功します。
ベクトル化したい文書が日本語である場合、先に`MeCab`なり`janome`なりを使って形態素解析して分かち書きに直してください。


さて、この`X`ですが、実はこんな形をしています。

![tfidfvectorizer.003.jpeg](https://qiita-image-store.s3.amazonaws.com/0/243995/987bcaca-ea7c-85a9-614c-53a1b0ad697d.jpeg)

これだけを見てみると混乱するのも無理ありません。私もめちゃくちゃ混乱しました。どこかベクトルなのか、それぞれの値が指す意味がぜんぜんわかりません。

実は、この`X`は`scipy.sparse.csr.csr_matrix`型という`SciPy`特有の型なのです。

せっかくなのでまずはこの`csr_matrix`についてもう少し見ていきましょう。

`ndarray`型と同じように`X.shape`で取り出すことができますが、Xの要素数は`(4, 9)`です。
本当に意味がわかりません。

実は、この意味不明な`scr_matrix`とかいう型は、<b>4×9行列への参照</b>を表しているのです。

さて、型の正体を確かめたい時に調べるものはなんでしょう。
そう。メタデータです。

`csr_matrix`型には3つのメタデータがあります。それは

```markdown
- indices…tf-idf値(ベクトル)
- data…idf値(スカラ)
- indptr...単語の合計(累積度数)
```

です。もちろん`csr_matrix`型は`tf-idf`のベクトルのみを扱っているわけではありませんが今回の例でいうと上の注意書きで書いたようになります。

ではさっそくそれぞれのメタデータを取り出して見ましょう。

さっきのコードの続きです。

```Python
print(X.indices)
print(X.data)
print(X.indptr)
```

すると、

```Python
# indices
[8 3 6 2 1 8 3 6 1 5 8 3 6 0 7 4 8 3 6 2 1]

# data
[0.38408524 0.38408524 0.38408524 0.58028582 0.46979139 0.28108867
 0.28108867 0.28108867 0.6876236  0.53864762 0.26710379 0.26710379
 0.26710379 0.51184851 0.51184851 0.51184851 0.38408524 0.38408524
 0.38408524 0.58028582 0.46979139]

# indptr
[ 0  5 10 16 21]
```

となります。

さて、これを見て`indices`と`data`はすぐ何を指しているのかわかるでしょう。

![tfidfvectorizer.005.jpeg](https://qiita-image-store.s3.amazonaws.com/0/243995/b514984a-a729-d74f-05d3-475f80bfba46.jpeg)

そう、これです。
では、この`0`とはなんでしょう。先程の画像をもう一度見てみてください。


0
0
0
0
0
1
1
.
.
.
3
3
3


と並んでいます。そう、この番号は文章のインデックスを指しています。

でもまだわからないことがあります。
`indices`の値の意味です。

`indices`を理解するには、さっきさらっと言った`X`の要素数が`(4, 9)`であることの意味を理解せねばなりません。

`4`は言うまでもなく文章の数を指していますが、さて`9`とはなんでしょう。

これも、先程言った

>これを用いることで、複数の文章を比較することで
>
>- 任意の単語の傾向
>- 任意の文章の傾向
>
>を同時に探ることができます。

これを思い出してください。

この２つの要素を同時に探るために、行要素が文章であるとするならば列要素は単語であると推測がつくでしょう。

`TfidfVectorizer()`には単語を取りだす`get_feature_names()`というメソッドがあります。

```Python
print(vectorizer.get_feature_names())
```

すると

```Python
['and', 'document', 'first', 'is', 'one', 'second', 'the', 'third', 'this']
```

と、`corpus`にある全ての単語が<b>アルファベット順</b>で格納されていることがわかります。

そして、`indices`はこの単語のインデックスを参照しています。





つまり、`Tf-idf`の`scr_matrix`型は

![tfidfvectorizer.004.jpeg](https://qiita-image-store.s3.amazonaws.com/0/243995/e184a1f0-8e5d-51fd-2182-77876672fd59.jpeg)

となっています。これは、まさに参照って感じがしますね。


さて、`csr_matrix`型の要素の正体がわかった上で、まだ注意したいことがあります。


こちらの縦要素は、実は単純に単語を指していません。なぜならインデックス1の単語数は6であるのに対して、こちらは5しかないからです。

![tfidfvectorizer.003.jpeg](https://qiita-image-store.s3.amazonaws.com/0/243995/987bcaca-ea7c-85a9-614c-53a1b0ad697d.jpeg)

実はこれ、単に一つ一つの単語の`csr_matrix`値を指しているのではなく、その文章の中で重複している単語が見つかった場合スキップしています。

つまり、インデックス1の文章`This document is the second document.`には`document`という単語が2回出てきているので2回目を飛ばしているのです。(賢い)




最後に、`indptr`は文章ごとの単語の種類の累計を指しています。まず0が入り、インデックス0の単語数5が足され、インデックス1の単語数5が足され...

という風になされていきます。このようにデータの総数がわかることでメモリがわかりますね。これはメタデータ上とても大事なことですので実用性はないかもしれませんが気に留めておきましょう。

はい。これでとうとう`Tf-idf`を扱う`csr_matrix`型の正体がわかりましたね。

ですが、この`csr_matrix`型は要は`ndarray`型のメタデータであり、人間が取り扱うには多少、いや大分難易度が高いですよね。

人類のみなさん。安心してください。ここで`ndarray`型の出番です。
実は`csr_matrix`型にはその強力な参照性を生かして`ndarray`型へと変換する`toarray()`関数が備わっています。

```Python
print(X.toarray())
```

とすることでまんまと`ndarray`型の配列が取り出せます。

```Python
[[0.         0.46979139 0.58028582 0.38408524 0.         0.
  0.38408524 0.         0.38408524]
 [0.         0.6876236  0.         0.28108867 0.         0.53864762
  0.28108867 0.         0.28108867]
 [0.51184851 0.         0.         0.26710379 0.51184851 0.
  0.26710379 0.51184851 0.26710379]
 [0.         0.46979139 0.58028582 0.38408524 0.         0.
  0.38408524 0.         0.38408524]]
```

そして、この`ndarray`型の配列こそが4×9の行列なのです。実は`csr_matrix`型の`shape`が参照していたのはこの`ndarray`型なのです。
`shape`はもともと`ndarray`型のメタデータですからね。

薄々気づいたかもしれませんが、各単語のベクトルを並べたものこそがこの行列の正体です。

![tfidfvectorizer.006.jpeg](https://qiita-image-store.s3.amazonaws.com/0/243995/53682a9a-11b3-f43d-8233-7a0398332439.jpeg)

この表記がパっとこない方は線形代数を勉強しましょう。



この行列は機械学習を嗜んでいるみなさんにも馴染み深いと思うのでここまでくればみなさんにもこの行列の意味することがわかるでしょう。


![gyouretu.001.jpeg](https://qiita-image-store.s3.amazonaws.com/0/243995/a878f980-b05f-e003-1c1d-90963294b98b.jpeg)

そう、言ったとおり、行成分で文章の、列成分で単語のベクトルを測ることができます。


いろいろできそうなことが見えてきましたね？それではあとはみなさんの力でがんばってください。