素人の言語処理100本ノック:85


[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)の挑戦記録です。環境はUbuntu 16.04 LTS ＋ Python 3.5.2 \:\: Anaconda 4.1.1 (64-bit)です。過去のノックの一覧は[こちら](http://qiita.com/segavvy/items/fb50ba8097d59475f760)からどうぞ。

## 第9章: ベクトル空間法 (I)
>[enwiki-20150112-400-r10-105752.txt.bz2](http://www.cl.ecei.tohoku.ac.jp/nlp100/data/enwiki-20150112-400-r10-105752.txt.bz2)は，2015年1月12日時点の英語のWikipedia記事のうち，約400語以上で構成される記事の中から，ランダムに1/10サンプリングした105,752記事のテキストをbzip2形式で圧縮したものである．このテキストをコーパスとして，単語の意味を表すベクトル（分散表現）を学習したい．第9章の前半では，コーパスから作成した単語文脈共起行列に主成分分析を適用し，単語ベクトルを学習する過程を，いくつかの処理に分けて実装する．第9章の後半では，学習で得られた単語ベクトル（300次元）を用い，単語の類似度計算やアナロジー（類推）を行う．
>
>なお，問題83を素直に実装すると，大量（約7GB）の主記憶が必要になる． メモリが不足する場合は，処理を工夫するか，1/100サンプリングのコーパス[enwiki-20150112-400-r100-10576.txt.bz2](http://www.cl.ecei.tohoku.ac.jp/nlp100/data/enwiki-20150112-400-r100-10576.txt.bz2)を用いよ．

### 85. 主成分分析による次元圧縮
>84で得られた単語文脈行列に対して，主成分分析を適用し，単語の意味ベクトルを300次元に圧縮せよ．

#### 出来上がったコード：

```python:main.py
# coding: utf-8
from scipy import sparse, io
import sklearn.decomposition

fname_matrix_x = 'matrix_x'
fname_matrix_x300 = 'matrix_x300'

# 行列読み込み
matrix_x = io.loadmat(fname_matrix_x)['matrix_x']

# 次元圧縮
clf = sklearn.decomposition.TruncatedSVD(300)
matrix_x300 = clf.fit_transform(matrix_x)
io.savemat(fname_matrix_x300, {'matrix_x300': matrix_x300})
```

#### 実行結果：

次元圧縮した行列は「matrix_x300.mat」に出力します。
処理時間は手元のマシンで8分ほどでした。

### 次元圧縮、次元削減

[問題84](http://qiita.com/segavvy/items/21455b802e34a9e49f92)で作った行列は436,428次元になっています。これを300次元に圧縮して取り扱いやすくします。なお、次元を2次元や3次元まで圧縮するとグラフで確認できるようになります。そのため、次元圧縮はデータを可視化したい時にも良く行われます。

次元を減らすということは、当然情報量が減ります。この時に、なるべく失う情報が少なくなるように削減する必要があります。

例えば、生徒の国語、算数、理科、社会のテストの成績データがあったとします。4つの要素があるのでこれは4次元です。これを2次元に圧縮することを考えます。
簡単なのは単純に2要素を捨ててしまって、例えば国語と算数だけにすることです。でもこれでは、理科と社会の成績が完全に失われてしまうので、4科目全部100点の抜群に勉強ができる生徒と、国語算数が100点で理科社会が0点のちょっと極端な生徒の区別すらつかなくなってしまい、どちらも抜群に勉強ができる生徒になってしまいます。
でも、理系の成績と文系の成績の2次元に変換すれば、全科目の情報が残っていますので、そのようなことはなくなります。また、2次元に減らしましたが、理系向きなのか文系向きなのかといった情報も保つことができます。

このように、単純にいくつかの要素を削るのではなく、なるべく多くの情報が維持できるような新たな要素を作って、そこへマッピングする必要があります。

### 主成分分析

次元圧縮の手法はいくつかあるようですが、問題にある主成分分析（PCA）を使って削減します。これは、新たな要素を作る際に、元のデータを変換したら最もばらつきが大きくなるものを選ぶ手法です。ばらつきが大きいということは、元のデータの変化を多く表現していることになるので、失われる情報が少なくなる訳です。

理屈はわかったのですが、実際の算出方法は私も良く理解できていません。
[scikit-learn](http://scikit-learn.org/stable/)という機械学習のライブラリを使うと簡単にできるので、今回はこれを使ってしまいました(^^;

主成分分析については、aya_takaさんの[30分でわかる機械学習用語「次元削減(Dimensionality Reduction)」](http://qiita.com/aya_taka/items/4d3996b3f15aa712a54f)などが分かりやすいかと思います。

### scikit-learnのインストール
scikit-learnはAnacondaでインストールされていたようで、何もせずにそのまま使うことができました。オフィシャルサイトは[こちら](http://scikit-learn.org/stable/)です。

### scikit-learnを使った主成分分析
通常は[`decomposition.PCA`](http://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html#sklearn-decomposition-pca)クラスで主成分分析ができるそうなのですが、これは[問題84](http://qiita.com/segavvy/items/21455b802e34a9e49f92)で作ったような疎行列には対応していません。そのため、疎行列に対応している[`decomposition.TruncatedSVD`](http://scikit-learn.org/stable/modules/generated/sklearn.decomposition.TruncatedSVD.html#sklearn.decomposition.TruncatedSVD)を使いました。

使い方は簡単で、次元数を指定して[`decomposition.TruncatedSVD`](http://scikit-learn.org/stable/modules/generated/sklearn.decomposition.TruncatedSVD.html#sklearn.decomposition.TruncatedSVD)オブジェクトを作り、その[`fit_transform()`](http://scikit-learn.org/stable/modules/generated/sklearn.decomposition.TruncatedSVD.html#sklearn.decomposition.TruncatedSVD.fit_transform)で元の行列を渡して変換させるだけです。

なお、できあがる行列はもう疎行列ではありません。そのため、直列化してできあがるファイルのサイズは大きくなります。結果の「matrix_x300.mat」は約1GBでした。

　
86本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。
<hr>

*実行結果には、[100本ノックで用いるコーパス・データ](http://www.cl.ecei.tohoku.ac.jp/nlp100/#data)で配布されているデータの一部が含まれます。この第9章で用いているデータのライセンスは[クリエイティブ・コモンズ 表示-継承 3.0 非移植](https://creativecommons.org/licenses/by-sa/3.0/legalcode)（[日本語訳](https://creativecommons.org/licenses/by-sa/3.0/deed.ja)）です。*