scikit-learn を使った機械学習モデルの構築（アイリスの品種分類）

# scikit-learn を使った機械学習モデルの構築
scikit-learnはPythonのオープンソース機械学習ライブラリで、様々な機械学習アルゴリズムやデータセットが用意されています。

今回は、scikit-learnに標準で付属されているアイリスデータセットを用いて、花の測定値からアイリスのどの品種に属するかを予測する機械学習モデルを実装してみます。

# 必要なライブラリのインストール
```py3
pip install numpy
pip install scipy
pip install pandas
pip install matplotlib
pip install mglearn
pip install scikit-learn
```

# データセットの取得
今回扱うアイリスデータセットは、機械学習や統計分野で古くから用いられている有名なデータセットで、150×4のnumpy.ndarrayに格納された3種類の品種（Setosa, Versicolour, Virginica）の花弁とガクの長さで構成されています。
![iris.jpg](https://qiita-image-store.s3.amazonaws.com/0/179364/e2c6e96a-a3a4-c111-5f54-1e491aeb88a0.jpeg)


このデータセットは、scikit-learnのdatasetsモジュールに含まれており、load_iris関数でデータセットを取得することができます。

```py3
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> import pandas as pld
>>> import mglearn

>>> # irisデータセットのロード
>>> from sklearn.datasets import load_iris
>>> iris_dataset = load_iris()
```

## データセットの説明の取得
```py3
>>> # irisデータセットの説明（description）の取得
>>> print(iris_dataset['DESCR'])
Iris Plants Database
====================

Notes
-----
Data Set Characteristics:
    :Number of Instances: 150 (50 in each of three classes)
    :Number of Attributes: 4 numeric, predictive attributes and the class
    :Attribute Information:
        - sepal length in cm
        - sepal width in cm
        - petal length in cm
        - petal width in cm
        - class:
                - Iris-Setosa
                - Iris-Versicolour
                - Iris-Virginica
    :Summary Statistics:

    ============== ==== ==== ======= ===== ====================
                    Min  Max   Mean    SD   Class Correlation
    ============== ==== ==== ======= ===== ====================
    sepal length:   4.3  7.9   5.84   0.83    0.7826
    sepal width:    2.0  4.4   3.05   0.43   -0.4194
    petal length:   1.0  6.9   3.76   1.76    0.9490  (high!)
    petal width:    0.1  2.5   1.20  0.76     0.9565  (high!)
    ============== ==== ==== ======= ===== ====================

    :Missing Attribute Values: None
    :Class Distribution: 33.3% for each of 3 classes.
    :Creator: R.A. Fisher
    :Donor: Michael Marshall (MARSHALL%PLU@io.arc.nasa.gov)
    :Date: July, 1988
...
```

## データセット内容の確認
```py3
>>> # irisデータセットのキーの確認
>>> print(iris_dataset.keys())
dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names'])

>>> # target_names: 品種（Setosa, Versicolour, Virginica）
>>> print(iris_dataset['target_names'])
['setosa' 'versicolor' 'virginica']

>>> # feature_names: 特微量（ガクの長さ, ガクの幅, 花弁の長さ, 花弁の幅）
>>> print(iris_dataset['feature_names'])
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']

>>> # PandasのDataFrameで特微量を表示
>>> pld.DataFrame(iris_dataset.data, columns=iris_dataset.feature_names)
     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
5                  5.4               3.9                1.7               0.4

...割愛

145                6.7               3.0                5.2               2.3
146                6.3               2.5                5.0               1.9
147                6.5               3.0                5.2               2.0
148                6.2               3.4                5.4               2.3
149                5.9               3.0                5.1               1.8

[150 rows x 4 columns]
```
# 訓練データとテストデータに分割
アイリスデータセットを訓練データ（trainimg data）とテストデータ（test data）に分割します。

訓練データにより機械学習モデルを構築し、構築したモデルがどの程度上手く機能するかをテストデータを使って評価します。

## train_test_split関数
train_test_split関数を使うと、引数で渡したデータセットを並び替えて、好きな割合の評価データとテストデータに分割することができます。

```py3
>>> # 75%を訓練データ、25%をテストデータに分割
>>> from sklearn.model_selection import train_test_split
>>> X_train, X_test, y_train, y_test = train_test_split(iris_dataset['data'], iris_dataset['target'], test_size=0.25,  random_state=0)

>>> # X_trainにデータセットの75%が含まれている
>>> pld.DataFrame(X_train, columns=iris_dataset.feature_names)
     sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)
0                  5.9               3.0                4.2               1.5
1                  5.8               2.6                4.0               1.2
2                  6.8               3.0                5.5               2.1
3                  4.7               3.2                1.3               0.2
4                  6.9               3.1                5.1               2.3

...割愛

107                4.9               3.1                1.5               0.1
108                6.3               2.9                5.6               1.8
109                5.8               2.7                4.1               1.0
110                7.7               3.8                6.7               2.2
111                4.6               3.2                1.4               0.2

[112 rows x 4 columns]

>>> # X_testには残りのデータセット25%が含まれている
>>> pld.DataFrame(X_test, columns=iris_dataset.feature_names)
    sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)
0                 5.8               2.8                5.1               2.4
1                 6.0               2.2                4.0               1.0
2                 5.5               4.2                1.4               0.2
3                 7.3               2.9                6.3               1.8
4                 5.0               3.4                1.5               0.2

...割愛

33                6.1               3.0                4.9               1.8
34                6.4               2.8                5.6               2.2
35                5.2               2.7                3.9               1.4
36                5.7               3.8                1.7               0.3
37                6.0               2.7                5.1               1.6

>>> # iris_dataset['target_names']に対応する品種のラベル値も同様に分割されている
>>> # 0:Setosa, 1:Versicolour , 2:Virginica
>>> print(iris_dataset['target_names'])
['setosa' 'versicolor' 'virginica']
>>> y_train
array([1, 1, 2, 0, 2, 0, 0, 1, 2, 2, 2, 2, 1, 2, 1, 1, 2, 2, 2, 2, 1, 2, 1,
       0, 2, 1, 1, 1, 1, 2, 0, 0, 2, 1, 0, 0, 1, 0, 2, 1, 0, 1, 2, 1, 0, 2,
       2, 2, 2, 0, 0, 2, 2, 0, 2, 0, 2, 2, 0, 0, 2, 0, 0, 0, 1, 2, 2, 0, 0,
       0, 1, 1, 0, 0, 1, 0, 2, 1, 2, 1, 0, 2, 0, 2, 0, 0, 2, 0, 2, 1, 1, 1,
       2, 2, 1, 1, 0, 1, 2, 2, 0, 1, 1, 1, 1, 0, 0, 0, 2, 1, 2, 0])
>>> y_test
array([2, 1, 0, 2, 0, 2, 0, 1, 1, 1, 2, 1, 1, 1, 1, 0, 1, 1, 0, 0, 2, 1, 0,
       0, 2, 0, 0, 1, 1, 0, 2, 1, 0, 2, 2, 1, 0, 1])
```

# 特微量のペアプロット（データ可視化）
機械学習モデルを構築する前に、特微量の組み合わせで散布図をプロットして、データの可視化を実施してみます。

アイリスデータセットの4つの特微量（ガクの長さ、ガクの幅、花弁の長さ、花弁の幅）から、品種の分類ができそうかを目視で確認してみます。

## scatter_matrix関数
scatter_matrix関数は、ペアプロットを作成する関数です。

```py3
>>> # X_train（訓練データ）からDataFrame作成
>>> iris_dataframe = pld.DataFrame(X_train, columns=iris_dataset.feature_names)

>>> # DataFrameからscatter_matrixを作成
>>> from pandas.plotting import scatter_matrix
>>> grr = scatter_matrix(iris_dataframe, c=y_train, figsize=(15, 15), marker='o', hist_kwds={'bins': 20}, s=60, alpha=.8, cmap=mglearn.cm3)
>>> plt.show()
```
<img width="1226" alt="スクリーンショット 2017-10-15 16.07.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/179364/e1a1c98c-c7a7-03f4-6386-c8bc745db4e1.png">

3種類の品種（Setosa, Versicolour, Virginica）のラベルに従い、色がつけられています。

それぞれ、花弁とガクの測定結果でよく分離していることを確認することができました。
品種のわかっていない花のデータから、アイリスの品種分類の予測を、それなりの精度で立てられそうです。

# 機械学習モデルの構築
花のデータから、アイリスのどの品種に属するかを「k-最近傍法」の学習アルゴリズムにより予測するモデルを構築します。

## k-近傍法（k-Nearest Neighbors）
k-近傍法はもっとも単純な学習アルゴリズムであると言われています。

新しいデータポイントに対する予測を行う際に、k=1とした1-最近傍法アルゴリズムの予測では、一番近い近傍点である「最近傍点」のラベルが予測結果のラベルになります。

近傍点は1つとは限らず、近い方から順にk個の近傍点を確認し、もっとも多数派のクラスを予測結果とすることも可能です。

## 訓練データからモデル構築
k-近傍法クラス分類アルゴリズムは、KNeighborsClassifierクラスに実装されています。

このクラスには、モデルの構築をするアルゴリズムと、モデルを用いて予測を行うアルゴリズムが含まれており、n_neighborsによりkの値を指定することができます。

```py3
>>> from sklearn.neighbors import KNeighborsClassifier
>>> knn = KNeighborsClassifier(n_neighbors=1)

>>> # 訓練データからモデルを構築
>>> # （knnオブジェクトそのものが返され、同時にknnも書き換えられる。
>>> # 　モデル構築に用いられたデフォルト値のアラメータなどを確認することができる。）
>>> knn.fit(X_train, y_train)
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
           metric_params=None, n_jobs=1, n_neighbors=1, p=2,
           weights='uniform')
```

## 新規データに対する予測
```py3
>>> # 新しいデータを設定（ガクの長さ, ガクの幅, 花弁の長さ, 花弁の幅）
>>> X_new1 = np.array([[5.0, 2.9, 1.0, 0.2]])
>>> X_new2 = np.array([[6.0, 2.7, 5.1, 1.6]])

>>> # 新しいデータに対して予測を実施
>>> prediction1 = knn.predict(X_new1)
>>> prediction2 = knn.predict(X_new2)

>>> # 予測結果
>>> print(iris_dataset['target_names'][prediction1])
['setosa']
>>> print(iris_dataset['target_names'][prediction2])
['virginica']
```

## モデルの評価
先ほど分割したテストデータを使って、モデルがどのくらいの精度を持っているかを評価します。
予測結果とテストデータの比較により、正解率を算出します。

```py3
>>> # テストデータをモデルに投入
>>> y_pred = knn.predict(X_test)

>>> # テストデータに対するラベル予測結果
>>> y_pred
array([2, 1, 0, 2, 0, 2, 0, 1, 1, 1, 2, 1, 1, 1, 1, 0, 1, 1, 0, 0, 2, 1, 0,
       0, 2, 0, 0, 1, 1, 0, 2, 1, 0, 2, 2, 1, 0, 2])

>>> # テストデータラベル
>>> y_test
array([2, 1, 0, 2, 0, 2, 0, 1, 1, 1, 2, 1, 1, 1, 1, 0, 1, 1, 0, 0, 2, 1, 0,
       0, 2, 0, 0, 1, 1, 0, 2, 1, 0, 2, 2, 1, 0, 1])

>>> # 予測結果とテストデータの合致率から精度を算出
>>> np.mean(y_pred == y_test)
0.97368421052631582
```

このモデルでは、テストデータに対する精度はおよそ0.97でした。

scikit-learnには他にも様々な学習アルゴリズムが用意されているため、こちらを活用して機械学習に触れていきたいと思います。

# 参考
Pythonではじめる機械学習
https://www.oreilly.co.jp/books/9784873117980/index.html

scikit-learn 公式サイト
http://scikit-learn.org/stable/

The Iris Dataset — scikit-learn 0.19.0 documentation
http://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html

Iris flower data set
https://en.wikipedia.org/wiki/Iris_flower_data_set

PyQ 1.0 ドキュメント（train_test_split関数でデータ分割）
http://docs.pyq.jp/python/machine_learning/tips/train_test_split.html

pandas 0.20.3 documentation（Scatter Matrix Plot）
https://pandas.pydata.org/pandas-docs/stable/visualization.html#scatter-matrix-plot