機械学習 〜 K−近傍法 〜

今回は scikit-learn を使って K-近傍法 を試してみます。


# K-近傍法とは
- 通称 K-NN（K-Nearest Neighbor Algorithm の略称）
- 特徴空間上において、近くにある K個 オブジェクトのうち、最も一般的なクラスに分類する。
- 距離の算出には、一般的に[ユークリッド距離](https://ja.wikipedia.org/wiki/%E3%83%A6%E3%83%BC%E3%82%AF%E3%83%AA%E3%83%83%E3%83%89%E8%B7%9D%E9%9B%A2)が使われる。（他にマンハッタン距離などがある）
- 次元の呪いのため、高次元データには向かない。
- トレーニングデータ数・特徴量が増えると予測が遅くなる。
- クラス分類や回帰分析に利用可能

例）K = 3 なら クラスA に分類、K = 5 なら クラスB に分類される

![K-kinbou.png](https://qiita-image-store.s3.amazonaws.com/0/134550/6ada1973-ec13-8d5b-d1fc-8481ce28e7ab.png)


# K 値の特徴

K の値によって精度が変わるため、最適な K の値を設定する必要があります。

| K | 特徴 |
|:-----------|:------------:|
| 小さい値 | ノイズに弱い |
| 大きい値 | 精度が下がる |


# クラス分類

クラス分類には、[KNeighborsClassifier](http://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html) クラスを使用します。

#### 引数

| パラメータ名 | 概要 | 備考 |
|:-----------|:------------:|:------------:|
| n_neighbors | K | 近傍オブジェクト数（初期値:5） |
| weights | 重み | ‘uniform’ : 均一の重み（初期値）<br>‘distance’ : 距離に応じた重み |
| algorithm | アルゴリズム選択 | ‘ball_tree’ : BallTreeデータ構造<br>‘kd_tree’ : KD木データ構造<br>‘brute’ : brute-force search（総当たり検索）<br>‘auto’ : 自動選択（初期値） |
| leaf_size | リーフサイズ | BallTree/KDTree のリーフサイズ（初期値:30） |
| n_jobs | 近傍検索ジョブ数 |  -1指定時は CPUコア数が設定される （初期値:1） |


実際に分類を行ってみます。

## アヤメの分類

アヤメという花の分類を行います。
特徴量として、萼(がく)片の長さ・幅、花びらの長さ・幅の４種類があり、
分類クラスとして、Setosa、Versicolor、Virgínia の３種類があります。

#### データ準備

・データ読み込み
sklearn のデータセットより、データを読み込みます。

```python
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from sklearn.model_selection import train_test_split

# アヤメデータセット読み込み
from sklearn.datasets import load_iris
iris = load_iris()

# 特徴量
X = iris.data
# 目的変数
Y = iris.target
```

・特徴量表示

```python
# データ表示（特徴量）
print("データ数 = %d  特徴量 = %d" % (X.shape[0], X.shape[1]))
pd.DataFrame(X, columns=iris.feature_names).head()
```

![s1.png](https://qiita-image-store.s3.amazonaws.com/0/134550/4c500f40-6517-a602-f9f4-61a1c6c0852b.png)

・目的変数表示

```python
# データ表示（目的変数）
print("データ数 = %d" % (Y.shape[0]))
print(Y)
```

![s2.png](https://qiita-image-store.s3.amazonaws.com/0/134550/7b306383-ccec-120b-517a-d1081ea8d01b.png)

#### 分類予測・精度

```python
# トレーニング・テストデータ分割
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, random_state=0)

#
# K-近傍法
#
from sklearn.neighbors import KNeighborsClassifier

list_nn = []
list_score = []
for k in range(1, 31): # K = 1~30
  # KNeighborsClassifier
  knc = KNeighborsClassifier(n_neighbors=k)
  knc.fit(X_train, Y_train)

  # 予測　
  Y_pred = knc.predict(X_test)

  # 評価 R^2
  score = knc.score(X_test, Y_test)
  print("[%d] score: {:.2f}".format(score) % k)

  list_nn.append(k)
  list_score.append(score)

# プロット
plt.ylim(0.9, 1.0)
plt.xlabel("n_neighbors")
plt.ylabel("score")
plt.plot(list_nn, list_score)
```
K = 1~30 まで実行しました。
K = 1~23 までの精度は 97% ですが、K = 24 以降は精度が下がっています。
K の値は、少ない数値で問題なさそうです。

![s3.png](https://qiita-image-store.s3.amazonaws.com/0/134550/48312e72-6feb-3c0e-8d1d-03ec9def3a45.png)

## 乳がんデータの分類

乳がんデータの分類を行います。
特徴量として 30種類があり、分類クラスとして 悪性(malignant)、良性(benign) の２種類があります。

#### データ準備

・データ読み込み

```python
# 乳癌データセット
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()

# 特徴量
X = cancer.data
# 目的変数
Y = cancer.target
```

・特徴量表示

```python
# データ表示（特徴量）
print("データ数 = %d  特徴量 = %d" % (X.shape[0], X.shape[1]))
pd.DataFrame(X, columns=cancer.feature_names).head()
```

以下のようなデータがあり、データ件数は 569件です。

![s4.png](https://qiita-image-store.s3.amazonaws.com/0/134550/d71387fb-2200-4cd7-7211-06cb49137ed2.png)


```python
# データ表示（目的変数）
print("データ数 = %d" % (Y.shape[0]))
print(Y)
```

![s5.png](https://qiita-image-store.s3.amazonaws.com/0/134550/49893d05-b693-e81e-b11f-48be84cd6bd1.png)


#### 分類予測・精度

```python
# トレーニング・テストデータ分割
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, random_state=0)

#
# K-最近傍法
#
from sklearn.neighbors import KNeighborsClassifier

list_k = []
list_score = []
for k in range(1, 11):
  # KNeighborsClassifier
  knc = KNeighborsClassifier(n_neighbors=k)
  knc.fit(X_train, Y_train)

  # 予測　
  Y_pred = knc.predict(X_test)

  # 評価 R^2
  score = knc.score(X_test, Y_test)
  print("[%d] score: {:.2f}".format(score) % k)

  list_k.append(k)
  list_score.append(score)

# プロット
plt.ylim(0.9, 1.0)
plt.xlabel("k")
plt.ylabel("score")
plt.plot(list_k, list_score)
```

K = 9 で 精度が 96% になりました。

![s6.png](https://qiita-image-store.s3.amazonaws.com/0/134550/51153b4e-0030-bb9e-aee9-3ffebdf3af6a.png)




# 回帰分析

回帰分析には、[KNeighborsRegressor](http://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsRegressor.html) クラスを使用します。

#### 引数

| パラメータ名 | 概要 | 備考 |
|:-----------|:------------:|:------------:|
| n_neighbors | K | 近傍オブジェクト数（初期値:5） |
| weights | 重み | ‘uniform’ : 均一の重み（初期値）<br>‘distance’ : 距離に応じた重み |
| algorithm | アルゴリズム選択 | ‘ball_tree’ : BallTreeデータ構造<br>‘kd_tree’ : KD木データ構造<br>‘brute’ : brute-force search（総当たり検索）<br>‘auto’ : 自動選択（初期値） |
| leaf_size | リーフサイズ | BallTree/KDTree のリーフサイズ（初期値:30） |
| n_jobs | 近傍検索ジョブ数 |  -1指定時は CPUコア数が設定される （初期値:1） |

## ボストン住宅価格データの回帰分析

ボストン住宅価格データの回帰分類を行います。
特徴量として 犯罪発生率や住宅区画の密集度など 13種類があり、
目的変数として、住宅価格があります。
今回は、特徴量として部屋数を使い住宅価格を予測します。

#### データ準備

```python
# ボストン住宅価格データセット
from sklearn import datasets
boston = datasets.load_boston()

# 説明変数
X = boston.data
# 目的変数
Y = boston.target
```

・特徴量表示

```python
# データ表示（特徴量）
print("データ数 = %d  特徴量 = %d" % (X.shape[0], X.shape[1]))
pd.DataFrame(X, columns=boston.feature_names).head()
```

![s7.png](https://qiita-image-store.s3.amazonaws.com/0/134550/c016ca70-ff72-f557-5733-e865c7ac5596.png)

・目的変数表示

```python
# データ表示（目的変数）
print("データ数 = %d" % (Y.shape[0]))
print(Y[:10]) # 先頭 10件表示
```

![s8.png](https://qiita-image-store.s3.amazonaws.com/0/134550/4512be5f-a8b3-c065-d222-f7417b3c83e1.png)

・トレーニングデータプロット

横軸に部屋数、縦軸に住宅価格を指定してプロットします。

```python
# 説明変数に部屋数のみ使用
X = boston.data[:, [5]] # 部屋数

# トレーニング・テストデータ分割
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, random_state=0)

# プロット
plt.xlabel("RM")
plt.ylabel("Target")
plt.plot(X_train, Y_train, "o")
```

部屋数の増加に比例して、住宅価格が上がっています。
右肩上がりの正の相関関係になっています。

![s9.png](https://qiita-image-store.s3.amazonaws.com/0/134550/e5b24c9e-20b4-33e0-530e-174b59229a29.png)


#### 予測・精度

住宅価格の予測、及び、精度を確認します。

```python
from sklearn.neighbors import KNeighborsRegressor
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_error

list_k = []
list_score = []
for k in range(1, 21):
  # KNeighborsClassifier
  knr = KNeighborsRegressor(n_neighbors=k)
  knr.fit(X_train, Y_train)

  # 予測　
  Y_pred = knr.predict(X_test)

  #
  # 評価
  #
  # 平均絶対誤差(MAE)
  mae = mean_absolute_error(Y_test, Y_pred)
  # 平方根平均二乗誤差（RMSE）
  rmse = np.sqrt(mean_squared_error(Y_test, Y_pred))  
  # スコア R^2
  score = knr.score(X_test, Y_test)

  print("[%d] MAE = %.2f,  RMSE = %.2f,  score = %.2f" % (k, mae, rmse, score))

  list_k.append(k)
  list_score.append(score)

# プロット
plt.ylim(0, 0.7)
plt.xlabel("k")
plt.ylabel("score")
plt.plot(list_k, list_score)
```

>・平均絶対誤差(MAE)
正解値と予測値の差分の絶対値を平均したもの
>・平方根平均二乗誤差（RMSE）
正解値と予測値の差分の二乗を平均し、平方したもの

K = 6 以降、ほとんど変化がありません。
K 値は 6 で良いようです。

![s10.png](https://qiita-image-store.s3.amazonaws.com/0/134550/20c8575b-4ace-f719-a120-96b693c62008.png)

テストデータの部屋数を使った住宅価格の予測値をプロットしてみます。
青が実際の価格、赤が予測価格になります。

```python
# テストデータ上での正解値（青）と予測値（赤）をプロット
K6_Pred = np.array(list_pred)[5]

plt.xlabel("RM")
plt.ylabel("Target")
plt.plot(X_test, K6_Pred, "ro")
plt.plot(X_test, Y_test, "o")
```

正解値と同様に右肩上がりのグラフになりました。
大まかな予測は出来ているようです。

![s11.png](https://qiita-image-store.s3.amazonaws.com/0/134550/9aaa67d3-44dc-0c58-4a52-f103d1fa9aa2.png)

以上、今回は scikit-learn を用いた K-近傍法 を試しました。