Pythonで機械学習はじめました クラスタリング＆次元圧縮＆可視化編 

# <i class="fa fa-forward" style="font-size:1em;"></i> はじめに
前回の[「データ前処理編」](http://qiita.com/yuuki_1204_/items/c26cb09fba8aad35dc0a)から時間が空いてしまいましたが、今回はTwitterのテキストデータをクラスタリングをしてみます。

# <i class="fa fa-bolt" style="font-size:1em;"></i> 3行でまとめ
- （やっと）クラスタリングした。
- クラスタリングした結果をmatplotlibで可視化した。
- 次回は脇道で可視化の小技紹介になるかも。

# <i class="fa fa-file-code-o" style="font-size:1em;"></i> いきなりソースコード（可視化以外）

[前回](http://qiita.com/yuuki_1204_/items/c26cb09fba8aad35dc0a)の「ベクトライズ」の実装に**「クラスタリング」****「次元圧縮」**の実装を追加してみました。（「可視化」のソースはちょっと長いので後で）

```py:tw_ml.py(抜粋)
#! /usr/bin/env python
# -*- coding:utf-8 -*-

import MeCab as mc
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import KMeans
from sklearn.decomposition import TruncatedSVD

MECAB_OPT = "-Ochasen -d C:\\tmp\\mecab-ipadic-neologd\\"

NUM_CLUSTER = 3
SAMPLE_DATA = [
    # 前回と同じなので省略
]

def mecab_tokenizer(text):
    # 前回と同じなので省略

def main():

    # ベクトライザクラス初期化（前回と同じ）
    vectorizer = TfidfVectorizer(
        min_df=1, stop_words=[u"Perfume", u"HTTPS"],
        tokenizer=mecab_tokenizer)
    # サンプルデータのベクトル化（前回と同じ）
    tfidf_weighted_matrix = vectorizer.fit_transform(SAMPLE_DATA)

    # K-Meansのクラスタ分析クラス初期化
    km_model = KMeans(n_clusters=NUM_CLUSTER)
    # ベクトル情報を食わせて、クラスタ分析を実行
    km_model.fit(tfidf_weighted_matrix)

    # 次元圧縮（特異値分解）クラス初期化
    lsa = TruncatedSVD(2)
    # サンプルデータ、及びクラスタ中心点のベクトル情報を2次元に圧縮
    compressed_text_list = lsa.fit_transform(tfidf_weighted_matrix)
    compressed_center_list = lsa.fit_transform(km_model.cluster_centers_)

if __name__ == '__main__':
    main()
```

何をやっているか、ちょっとずつ見ていきます。

## <i class="fa fa-cubes" style="font-size:1em;"></i> クラスタリング

```py:
# K-Meansのクラスタ分析クラス初期化
km_model = KMeans(n_clusters=NUM_CLUSTER)
# ベクトル情報を食わせて、クラスタ分析を実行
km_model.fit(tfidf_weighted_matrix)
```

処理内容はコメントに書いてある通り。ここで悩むのはKMeansクラス初期化時に渡すパラメタですが、とりあえず動かすだけならチューニングはほぼ不要です。今回は扱うデータの件数が5件と少ない為、クラスタ数はデフォルトの**8**から**3**に変更していますが、他はデフォルトのままです。
（その他のパラメタの詳細: [sklearn.cluster.KMeans](http://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html)）

初期化後はfitでデータを食わせて分析するだけ。
主な分析結果は、km_modelの以下を参照して確認できます。

|パラメタ|内容|値の例|
|:-----------|:-----------|:-----------|
|km_model.cluster_centers_|クラスタごとの中心点のベクトル情報|[[0, 0, 0.46369322, 0.46369322, 0, 0.46369322, 0, 0, 0, 0, 0, 0, 0, 0.37410477]...（クラスタの数だけ）]|
|km_model.labels_|分析対象データの要素ごとのラベル（どのクラスタに属するかを示す値）|[2 1 1 0 1] |

ただ、この結果（数値の羅列）を見せられてもナンノコッチャ過ぎるので、クラスタを可視化してみます。
そのために必要なのが次の**次元圧縮**です。

## <i class="fa fa-compress" style="font-size:1em;"></i> （可視化の為の）次元圧縮

```py:
# 次元圧縮（特異値分解）クラス初期化
lsa = TruncatedSVD(2)
# サンプルデータ、及びクラスタ中心点のベクトル情報を2次元に圧縮
compressed_text_list = lsa.fit_transform(tfidf_weighted_matrix)
compressed_center_list = lsa.fit_transform(km_model.cluster_centers_)
```

"次元圧縮"と聞くと反射的にファイナル○ァンタジーⅤやⅧが脳裏に浮かぶ世代です。どうもSF的な何かを彷彿させる字面ですね。ただ、目的は「高次元な情報なんて図示できないから、（近似で良いので）**xyの2次元に変換したい**」というだけで、（かなり強引な理解だけど）字面ほど数学界隈の尖った言葉という事でもないのです。

具体的には、今回次元数は「全テキストに登場する単語の種類」となっている為、サンプルデータは以下のような14次元のデータになっています。これを図示するのは難しいですね。
**[0, 0, 0.46369322, 0.46369322, 0, 0.46369322, 0, 0, 0, 0, 0, 0, 0, 0.37410477]**

これを次元圧縮によって、以下のような2次元にしてしまいます。これならxyの2軸の図にプロットできますね。
**[9.98647967e-01, 0.00000000e+00]**

なお次元圧縮の手法も色々とあるのですが今回は**潜在意味解析(LSA)**というものを使います。これまた強そうな字面ですが、上記ソースコードの通り、scikit-leanを使えば簡単です（今回はTruncatedSVDを使いますが、他にもあるみたいです）。上記の実装では14次元のサンプルデータのベクトル情報、クラスタ分析結果の中心点のベクトル情報を2次元に変換しています。

これで可視化の準備が整いました。

# <i class="fa fa-file-code-o" style="font-size:1em;"></i> ソースコード（可視化部分）

ここまでの実装で、クラスタを可視化するために必要な情報は揃ったので、matplotlibを使った描画処理を追加してみます。

```py:tw_ml.py(可視化部分の抜粋)

import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib.font_manager as fm

FP = fm.FontProperties(
    fname=r'C:\WINDOWS\Fonts\YuGothL.ttc',
    size=7)

def draw_km(text_list, km_text_labels,
            compressed_center_list, compressed_text_list):

    # 描画開始。
    fig = plt.figure()
    axes = fig.add_subplot(111)
    for label in range(NUM_CLUSTER):

        # ラベルごとに色を分ける。
        # 色はカラーマップ（cool）におまかせする。
        color = cm.cool(float(label) / NUM_CLUSTER)

        # ラベルの中心をプロット
        xc, yc = compressed_center_list[label]
        axes.plot(xc, yc,
                  color=color,
                  ms=6.0, zorder=3, marker="o")

        # クラスタのラベルもプロット
        axes.annotate(
            label, xy=(xc, yc), fontproperties=FP)

        for text_num, text_label in enumerate(km_text_labels):

            if text_label == label:
                # labelが一致するテキストをプロット
                x, y = compressed_text_list[text_num]
                axes.plot(x, y,
                          color=color,
                          ms=5.0, zorder=2, marker="x")

                # テキストもプロット
                axes.annotate(
                    text_list[text_num], xy=(x, y), fontproperties=FP)

                # ラベルの中心点からの線をプロット
                axes.plot([x, xc], [y, yc],
                          color=color,
                          linewidth=0.5, zorder=1, linestyle="--")

    plt.axis('tight')
    plt.show()

def main():
    # 次元圧縮までは同じなので省略

    # データを可視化
    # ※km_model.labels_にSAMPLE_DATAの各要素のラベル情報が格納されている。
    draw_km(SAMPLE_DATA, km_model.labels_,
            compressed_center_list, compressed_text_list)

if __name__ == '__main__':
    main()
```

結果はこんな感じになりました！
![figure_1.png](https://qiita-image-store.s3.amazonaws.com/0/82760/236ef8c6-fc1f-d59a-2352-c4964c07eb19.png)


|テキスト|クラスタ|
|:-----------|:-----------|
|のっち可愛い #Perfume https://t.co/xxx|2|
|Perfumeの演出スゴイ #prfm #Perfume_um https://t.co/xxx|1|
|チョコレイト・ディスコ / Perfume #NowPlaying https://t.co/xxx|1|
|Perfume A Gallery Experience in Londonに行ってきました https://t.co/xxx|0|
|チョコレイトディスコの演出カッコいい。ライブ行きたい。#Perfume https://t.co/xxx|1|

データ数が少ないから見栄えが微妙…。
でもなんとなーく、分類出来てる気がする！

# <i class="fa fa-hand-rock-o" style="font-size:1em;"></i> ついにクラスタリング出来た！

K-means法を使ったテキストデータのクラスタリングの触り部分は出来ました。km_modelsに新しいデータを食わせれば、どのクラスタに分類するか解を返してくれるはず。（つまり*テキスト分類器！*）

次回は実際に新規データを分類、データ数を増やした結果を見てみます。
あと脇道に逸れちゃいますがmatplotlibでの見せ方をちょっとだけ工夫してみたいと思います。