【機械学習入門】「SVMでクラス分類」というのをやってみる♬

機械学習入門第三弾は、SVM（SuportVectorMachine）です。
K-means（K-近傍法）、　RFC（RandomForestClassification）、そしてSVMがクラス分類における機械学習御三家と言える手法だと思います。そこにDL（DeepLearning）が殴り込みをしたような構図です。
※【追記】K-meansとK-近傍法は、以下の参考の通り異なる手法でした。そのため本文も一部加筆修正しました
【参考】
・[k近傍法とk平均法の違いと詳細](https://qiita.com/NoriakiOshita/items/698056cb74819624461f)
###当面の目標
機械学習のクラス分類という面についてまとめます。
・[K-meansクラスタリング](https://qiita.com/MuAuan/items/8d7de5a420880b07c83f)
・[ランダムフォレストでクラス分類](https://qiita.com/MuAuan/items/01af7cfc9a535aa13b3a)
・SVMでクラス分類（今回の記事）
・DeepLearningでクラス分類
※DeepLearningは昨年集中してやりましたが、対比するためにまとめる予定です
###今回やったこと
・SVM
・SVMでMNISTライクな手書き数字分類
・SVMでランダムフォレストに殴り込み；K-meansもランダムフォレストに殴り込みしました（追記）
・~~SVMでK-meansに殴り込み~~；参考だけ紹介しました(逆センスですが…)
###・SVM
SVMの紹介的なアプリを動かしてみます。解説は省略します。
【参考】
・[SVM（多クラス分類）](https://qiita.com/yshi12/items/668e699ed70906f9868b)
参考のアプリがまんま動きましたが、あんまりおもしろくないので、データ出力等少しだけ変更しました。

```py
import numpy as np
import matplotlib.pyplot as plt
#%matplotlib inline
from sklearn import datasets
from sklearn.cross_validation import train_test_split # クロスバリデーション用
from sklearn.svm import SVC # SVM用
from sklearn import metrics       # 精度検証用
import pandas as pd

# データ用意
iris = datasets.load_iris()    # データロード
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['target'] = iris.target_names[iris.target] #これないと名前が出ない
#df.head() #これだけじゃ出力しない
print(df.head())

X = iris.data                  # 説明変数セット
Y = iris.target                # 目的変数セット
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, random_state=0) # random_stateはseed値。
print(len(X_train),len(Y_train),len(X_test),len(Y_test))

# SVM実行
model = SVC()               # インスタンス生成
model.fit(X_train, Y_train) # SVM実行

# 予測実行
predicted = model.predict(X_test) # テストデータでの予測実行
acc=metrics.accuracy_score(Y_test, predicted)
print("acc=",acc)
```
これ実行すると、以下の警告がでますが、とりあえず以下の結果が得られます。

```
cross_validation.py:41: DeprecationWarning:．．．
```

```
   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)  \
0                5.1               3.5                1.4               0.2
1                4.9               3.0                1.4               0.2
2                4.7               3.2                1.3               0.2
3                4.6               3.1                1.5               0.2
4                5.0               3.6                1.4               0.2

   target
0  setosa
1  setosa
2  setosa
3  setosa
4  setosa
112 112 38 38
acc= 0.973684210526
```
精度はまあまあですが、データ１５０個（学習１１２個、テスト３８個）です。
※データ出力はpandas使っています。iris.head()だけだと出力できません
【参考】
・[irisのデータセットをpandasで使う](http://dragstar.hatenablog.com/entry/2016/07/24/150534)
###・SVMでMNISTライクな手書き数字分類
次は以下の参考を動かします。これもまんま動きました。
【参考】
・[Recognizing hand-written digits](https://scikit-learn.org/stable/auto_examples/classification/plot_digits_classification.html#sphx-glr-auto-examples-classification-plot-digits-classification-py)
コードは、肝心なところだけ抜き出しますので、コメントは原文見てください。
また、少しだけ改変しています。

```py
"""
================================
Recognizing hand-written digits
================================
"""
print(__doc__)
# Author: Gael Varoquaux <gael dot varoquaux at normalesup dot org>
# License: BSD 3 clause
import matplotlib.pyplot as plt
from sklearn import datasets, svm, metrics
digits = datasets.load_digits()

plt.figure(figsize=(12, 6)) #ここ追加しました
images_and_labels = list(zip(digits.images, digits.target))
for index, (image, label) in enumerate(images_and_labels[:8]): #4から8に変更
    plt.subplot(2, 8, index + 1)  #4から8に変更
    plt.axis('off')  #これコメントアウトすると枠と座標が出る
    plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
    plt.title('Training: %i' % label)

# To apply a classifier on this data, we need to flatten the image, to
# turn the data in a (samples, feature) matrix:
n_samples = len(digits.images)
print('n_samples=',n_samples)
data = digits.images.reshape((n_samples, -1))

# Create a classifier: a Support Vector Classifier
classifier = svm.SVC(gamma=0.001)
# We learn the digits on the first half of the digits；学習データは前半半分
classifier.fit(data[:n_samples // 2], digits.target[:n_samples // 2])

# Now predict the value of the digit on the second half:Testデータは後半半分
expected = digits.target[n_samples // 2:]
predicted = classifier.predict(data[n_samples // 2:])

print("Classification report for classifier %s:\n%s\n"
      % (classifier, metrics.classification_report(expected, predicted)))
print("Confusion matrix:\n%s" % metrics.confusion_matrix(expected, predicted))

images_and_predictions = list(zip(digits.images[n_samples // 2:], predicted))
for index, (image, prediction) in enumerate(images_and_predictions[:8]):
    plt.subplot(2, 8, index + 9)
    plt.axis('off')
    plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
    plt.title('Prediction: %i' % prediction)
plt.savefig('svm_mnistlike.jpg')
plt.pause(1)
```
実行例は以下のとおり、実行は瞬時に終わります。
サンプル数はn_samples= 1797です。
![svm_mnistlike.jpg](https://qiita-image-store.s3.amazonaws.com/0/233744/890d085e-eab2-ccab-1caf-224a2d33adae.jpeg)

```
>python svm_mnistlike.py

================================
Recognizing hand-written digits
================================

n_samples= 1797
Classification report for classifier SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma=0.001, kernel='rbf',
  max_iter=-1, probability=False, random_state=None, shrinking=True,
  tol=0.001, verbose=False):
             precision    recall  f1-score   support

          0       1.00      0.99      0.99        88
          1       0.99      0.97      0.98        91
          2       0.99      0.99      0.99        86
          3       0.98      0.87      0.92        91
          4       0.99      0.96      0.97        92
          5       0.95      0.97      0.96        91
          6       0.99      0.99      0.99        91
          7       0.96      0.99      0.97        89
          8       0.94      1.00      0.97        88
          9       0.93      0.98      0.95        92

avg / total       0.97      0.97      0.97       899


Confusion matrix:
[[87  0  0  0  1  0  0  0  0  0]
 [ 0 88  1  0  0  0  0  0  1  1]
 [ 0  0 85  1  0  0  0  0  0  0]
 [ 0  0  0 79  0  3  0  4  5  0]
 [ 0  0  0  0 88  0  0  0  0  4]
 [ 0  0  0  0  0 88  1  0  0  2]
 [ 0  1  0  0  0  0 90  0  0  0]
 [ 0  0  0  0  0  1  0 88  0  0]
 [ 0  0  0  0  0  0  0  0 88  0]
 [ 0  0  0  1  0  1  0  0  0 90]]
```
###・SVMでランダムフォレストに殴り込み
上記は、ほとんどランダムフォレストでやったことと同じなので、ランダムフォレストのコードにSVMを定義して実行してみました。
※K-meansの分類の話も同じように出来ると思いますが、"まんま"だとできないのでパスしました。
また、参考の通りK-近傍法ではクラスタリングできているので紹介します。
参考はK-近傍法でirisの分類をやっています
※上記のirisの分類については"まんまK-近傍法で動いたのでおまけに追記しました
※併せて、同じセンスでK-近傍法でランダムフォレストのコードに殴り込みもできたのでさらに追記しておきます（冒頭の参考の通り、K-meansとK-近傍法は異なる手法です）
【参考】
・[K近傍法(多クラス分類)](https://qiita.com/yshi12/items/26771139672d40a0be32)

コード全体はおまけに掲載しますが、要は以下のコードだけ入れ替えています。
※つまり、clf=RFC...をclf=svm.SVC...に変更しただけで動きました。

```py
"""
clf = RFC(verbose=True,       # 学習中にログを表示します。この指定はなくてもOK
          n_jobs=-1,          # 複数のCPUコアを使って並列に学習します。-1は最大値。
          random_state=2525)  # 乱数のシードです。
clf.fit(train_images, train_labels)

print(clf.feature_importances_)
"""
# Create a classifier: a support vector classifier
clf = svm.SVC(gamma=0.001)
# We learn the digits on the first half of the digits
clf.fit(train_images, train_labels)
```
実行結果は以下のとおりで、ランダムフォレストの結果を超えています。

```
>python RFC.py
acc:98.33 %
```
![dataset_input.jpg](https://qiita-image-store.s3.amazonaws.com/0/233744/b0e3bf9f-80bf-130e-c064-2216b2803474.jpeg)
![RFC_results.jpg](https://qiita-image-store.s3.amazonaws.com/0/233744/cb6b916a-1b49-561a-4660-854a3dd41219.jpeg)
###まとめ
・SVMでクラス分類をやってみた
・ランダムフォレストのコードにclsの入れ替えだけで手書き文字のクラス分類動いた
・~~K-means、ランダムフォレスト、そしてSVMだとSVMが一番精度よさそうである~~
※理由はおまけの追記を確認してください

・【追記】それぞれの理論（K-means、K-近傍法、ランダムフォレスト、そしてSVM）を簡単に解説しようと思う
・DLと合わせて、自前データを分類して精度問題、データの食わせ方の整理をしたい
###おまけ

```py
from sklearn import datasets
import numpy as np
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.model_selection import train_test_split, GridSearchCV
# Import datasets, classifiers and performance metrics
from sklearn import datasets, svm, metrics

mnist = datasets.load_digits()
 
train_images, test_images, train_labels, test_labels = \
train_test_split(mnist.data, mnist.target, test_size=0.2)

plt.figure(figsize=(15,15))
for i in range(25):
    plt.subplot(5,5,i+1)
    plt.xticks([])
    plt.yticks([])
    plt.imshow(train_images[i].reshape((8,8)), cmap=plt.cm.binary)
    plt.xlabel(train_labels[i], fontsize=18)
plt.savefig('RFC/dataset_input.jpg')
plt.pause(1)
plt.close()
"""
clf = RFC(verbose=True,       # 学習中にログを表示します。この指定はなくてもOK
          n_jobs=-1,          # 複数のCPUコアを使って並列に学習します。-1は最大値。
          random_state=2525)  # 乱数のシードです。
clf.fit(train_images, train_labels)

print(clf.feature_importances_)
"""
# Create a classifier: a support vector classifier
clf = svm.SVC(gamma=0.001)
# We learn the digits on the first half of the digits
clf.fit(train_images, train_labels)

#print(f"acc: {clf.score(test_images, test_labels)}")
acc=clf.score(test_images, test_labels)*100
print("acc:{:.2f} %".format(acc))

predicted_labels = clf.predict(test_images)

plt.figure(figsize=(15,15))
# 先頭から25枚テストデータを可視化
for i in range(25):
 
    # 画像を作成
    plt.subplot(5,5,i+1)
    plt.xticks([])
    plt.yticks([])
    plt.imshow(test_images[i].reshape((8,8)), cmap=plt.cm.binary)
 
    # 今プロットを作っている画像データの予測ラベルと正解ラベルをセット
    predicted_label = predicted_labels[i]
    true_label      = test_labels[i]
 
    # 予測ラベルが正解なら緑、不正解なら赤色を使う
    if predicted_label == true_label:
        color = 'green' # True label color
    else:
        color = 'blue'   # False label color red
    plt.xlabel("{} True({})".format(predicted_label,
                                  true_label), color=color, fontsize=18)
    if i==0:
        plt.title("acc:{:.2f} %".format(acc), fontsize=36)  
 
plt.savefig('RFC/RFC_results.jpg')
plt.pause(1)
plt.close()
```
####【追記】K-近傍法によるirisの分類は、以下のコードを上記のSVMのコードに追記するだけで動きました。なぜか、得られた精度も同じでした。
※冒頭の参考の通り、K-近傍法はK-meansとは異なる手法です

```py
from sklearn.neighbors import KNeighborsClassifier
#from sklearn.cross_validation import train_test_split # trainとtest分割用
# train用とtest用のデータ用意。test_sizeでテスト用データの割合を指定。random_stateはseed値を適当にセット。
#X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size = 0.4, random_state=3) 

knn = KNeighborsClassifier(n_neighbors=6) # インスタンス生成。n_neighbors:Kの数
knn.fit(X_train, Y_train)                 # モデル作成実行
Y_pred = knn.predict(X_test)              # 予測実行

# 精度確認用のライブラリインポートと実行
#from sklearn import metrics
acc1=metrics.accuracy_score(Y_test, Y_pred)    # 予測精度計測
print("acc1=",acc1)
```
実行結果は以下のとおり（SVMの結果も併せて表示しています）
※つまり、データ読み込みはSVCのコードのものを利用しており、最後のacc1だけがこのコードの実行結果です

```
   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)  \
0                5.1               3.5                1.4               0.2
1                4.9               3.0                1.4               0.2
2                4.7               3.2                1.3               0.2
3                4.6               3.1                1.5               0.2
4                5.0               3.6                1.4               0.2

   target
0  setosa
1  setosa
2  setosa
3  setosa
4  setosa
112 112 38 38
acc= 0.973684210526
acc1= 0.973684210526
```
####【追記】ランダムフォレストのコードにK-近傍法で殴り込みできました
上記のランダムフォレストのコードを以下の部分のみ変更します。
※精度はSVMとほぼ同一の精度になっています
※冒頭の参考の通り、K-近傍法はK-meansとは異なる手法です

```py
"""
clf = RFC(verbose=True,       # 学習中にログを表示します。この指定はなくてもOK
          n_jobs=-1,          # 複数のCPUコアを使って並列に学習します。-1は最大値。
          random_state=2525)  # 乱数のシードです。
clf.fit(train_images, train_labels)

print(clf.feature_importances_)

# Create a classifier: a support vector classifier
clf = svm.SVC(gamma=0.001)
# We learn the digits on the first half of the digits
clf.fit(train_images, train_labels)
"""
from sklearn.neighbors import KNeighborsClassifier
clf = KNeighborsClassifier(n_neighbors=6) # インスタンス生成。n_neighbors:Kの数
clf.fit(train_images, train_labels)
```
![K-means_results.jpg](https://qiita-image-store.s3.amazonaws.com/0/233744/264d0e77-c122-6f28-4757-1e95ca6b124f.jpeg)

```
>python RFC.py
acc:98.61 %
```