書籍「15Stepで踏破 自然言語処理アプリケーション開発入門」をやってみる - 2章Step05メモ「特徴量変換」

# 内容
[15stepで踏破 自然言語処理アプリケーション入門](https://www.amazon.co.jp/dp/4865941320) を読み進めていくにあたっての自分用のメモです。
今回は2章Step05で、自分なりのポイントをメモります。

# 準備
- 個人用MacPC：MacOS Mojave バージョン10.14.6
- docker version：Client, Server共にバージョン19.03.2

# 章の概要
Step04では特徴抽出手法を学び、次のStep06では抽出した特徴ベクトルから学習して識別器を作成する。
Step05ではその間の処理で、特徴ベクトルを識別器にとって望ましい形へと加工する次元圧縮の手法について学ぶ。

- 潜在意味解析（LSA）
- 主成分分析（PCA）

# 05.1 特徴量の前処理
BoWは単語の出現頻度をベクトル化したもので、「特徴ベクトルの値の分布が非常に偏りがち」である。

- 特徴抽出で解決
    - Step04のTF-IDFなど
- 抽出後に特徴ベクトルを加工して解決
    - sklearn.preprocessing.QuantileTransformerで値を0以上1以下の範囲に収め、かつ値の分布を一様にする。

参考書の例がわかりづらかったので、自前で確認してみる。

```python:test_quantileTransformer.py
import numpy as np
import MeCab
import pprint

from sklearn.preprocessing import QuantileTransformer
from sklearn.feature_extraction.text import CountVectorizer

def _tokenize(text):
~~

texts = [
    '車は車は車は速く走る',
    'バイクは速く走る',
    '自転車は自転車はゆっくり走る',
    '三輪車はゆっくり走る',
    'プログラミングは楽しい',
    'PythonはPythonはPythonはPythonはPythonは楽しい',
]

vectorizer = CountVectorizer(tokenizer=_tokenize, max_features = 5)
bow = vectorizer.fit_transform(texts)
pprint.pprint(bow.toarray())

qt = QuantileTransformer()
qtd = qt.fit_transform(bow)
pprint.pprint(qtd.toarray())
```

```bash:実行例
array([[0, 3, 0, 1, 3],
       [0, 1, 0, 1, 0],
       [0, 2, 1, 1, 0],
       [0, 1, 1, 1, 0],
       [0, 1, 0, 0, 0],
       [5, 5, 0, 0, 0]], dtype=int64)
array([[0.00000000e+00, 7.99911022e-01, 0.00000000e+00, 9.99999900e-01,
        9.99999900e-01],
       [0.00000000e+00, 9.99999998e-08, 0.00000000e+00, 9.99999900e-01,
        0.00000000e+00],
       [0.00000000e+00, 6.00000000e-01, 9.99999900e-01, 9.99999900e-01,
        0.00000000e+00],
       [0.00000000e+00, 9.99999998e-08, 9.99999900e-01, 9.99999900e-01,
        0.00000000e+00],
       [0.00000000e+00, 9.99999998e-08, 0.00000000e+00, 0.00000000e+00,
        0.00000000e+00],
       [9.99999900e-01, 9.99999900e-01, 0.00000000e+00, 0.00000000e+00,
        0.00000000e+00]])
```

- (5,0)や(0,4)は出現回数が多いが、他の文には出現していないので、変換後の値はほぼ1になっている
- (2,:)や(3,:)は出現回数が1だが、他の文も多くて1なので、変換後の値はほぼ1になっている
- (1,:)は出現回数が1,2,3,5と様々あり、変換後の値もバラバラになっている
    - 変換前1：変換後ほぼ0
    - 変換前2：変換後ほぼ0.6
    - 変換前3：変換後ほぼ0.8
    - 変換前5：変換後ほぼ1


# 05.2 潜在意味解析（LSA） 05.3 主成分分析（PCA）

|内容 |LAS |PCA |
|---|---|---|
|概要 |BoWのような文書と単語の関係を表した特徴ベクトル群を元にして、「単語」の背後にある「意味」レベルで文書を表現するベクトルを得る手法|「データ点が広く散らばっている方向」を求める手法|
|数学的操作 |SVD（特異値分解） |EVD（固有値分解）|
|実装 |svd = sklearn.decomposition.TruncatedSVD()<br>svd.fit_transform(<ベクトル>) |evd = sklearn.decomposition.PCA()<br>evd.fit_transform(<ベクトル>) |
|各次元の重要度 |singular_values_を参照し、圧縮後の各次元の重要度がわかる。|explained_variance_ratio_を参照し、累積寄与率がわかる。|
|次元削減 |インスタンス生成時に、n_componentsを指定 |インスタンス生成時に、n_componentsを指定 |

## 両手法で考慮すべき点
- メリット
    - 次元数減少による識別器の計算コスト低減
    - 冗長な情報を削ることによる性能向上
- デメリット
    - 必要な情報を削ってしまうと性能が下がってしまう

## LSA - トピックモデル
明示的に学習データにクラスIDを与えるのではなく「ある文と別のある文が同じ意味を表しているか」という問題であるトピックモデルは、明示的に正解（クラスID）を与えないという点で「教師なし学習」の一種である。

## PCA - 白色化
ベクトルの各成分を無相関化（対象ベクトルにPCAで得られた固有ベクトルを乗ずる）して平均0分散1にすることで、データが元々持っていた「各軸方向への広がり具合」を消し、識別性能の向上を期待できることがある。

## PCA - 可視化手法
高次元ベクトルを低次元ベクトルに変換できるため、可視化の手法としても利用できる。

# 05.4 アプリケーションへの応用・実装
TruncatedSVDのところをPCAに書き換えるだけだと実行できなかった。（PCAへはsparseを入力できない）

```python:実行例
    def train(self, texts, labels):
        vectorizer = TfidfVectorizer(tokenizer=self._tokenize, ngram_range=(1, 3))
        bow = vectorizer.fit_transform(texts).toarray()

        pca = PCA(n_components = 500)
        pca_feat = pca.fit_transform(bow)

        classifier = SVC()
        classifier.fit(pca_feat, labels)

        self.vectorizer = vectorizer
        self.pca = pca
        self.classifier = classifier

    def predict(self, texts):
        bow = self.vectorizer.transform(texts).toarray()
        pca_feat = self.pca.transform(bow)
        return self.classifier.predict(pca_feat)
```

pipeline表記をやめて、vectorizerの結果（sparse）をtoarray()してからPCAへ入力すれば実行できる。