学生による学生のためのデータサイエンス勉強会

【さきどりPython#12】kaggleデータで学ぶk-最近傍法(KNN)

https://bdarc.net/wp-content/uploads/2020/05/icon-1.pnghistoroid

こんにちは。清野(@historoid1)です。

今回はk-最近傍法の2周目です。

kaggleのデータで、分類タスクを実装してみましょう。

kaggleデータで学ぶk-最近傍法(KNN)

k-差近傍法(k-Nearest Neighbors, KNN)クラス分類器は、教師あり機械学習クラス分類器の中で、最も単純だが、最も広く用いられている手法だ。

Python機械学習クックブック

今回は、KNNを学んでいきます。

KNNは教師あり学習の中でも特殊で、これまで学習してきたような重みを獲得するタイプのモデルではありません。

KNNは教師あり学習にも関わらず訓練を必要としない遅延学習(怠惰学習、Lazy Learn)に分類されます。

KNNのアルゴリズム

KNNのアルゴリズムは直感的でわかりやすいです。

例えば上の図のように2つのクラスがあるとき、すでにクラスがわかっている(赤と青の点)観測値が教師データになります。

そこへ未知のデータ(緑色の点)が与えられたときに、周囲の観測値から未知データのクラス分類を行う方法がKNNです。

近接するk個の観測値を用いて推測する

例えば、\(k=5\)のとき、最も近い5個の観測値を選ぶと、矢印で示した5個になります。青が2つ、赤が3つなので、未知データはclass 0 に分類されることになります。

理解しやすいアルゴリズムではないでしょうか。これで学習が不要であることの理由もわかったと思います。

しかし、この例では「kによって結果が変わるんじゃない?」と感じるかと思います。それはそのとおりで、最適なkって何だ、という問題がつきまといます。

使用するデータ

今回使用するデータもkaggleのBreast Cancer(乳癌)です。

データの入手方法、Pandasでの読み込み等は、これまでに何度か説明してきたので過去の記事を参考にしてください。

【Pythonによるデータ分析】Pandasの基本操作5選

前回のおさらい

では一周目の内容を確認してみましょう。

ある観測値の近傍k個の観測値を求める方法

モデルの作成

from sklearn import datasets
from sklearn.neighbors import NearestNeighbors
from sklearn.preprocessing import StandardScaler

iris = datasets.load_iris()
features = iris.data

standardizer = StandardScaler()

features_standardized = standardizer.fit_transform(features)

nearest_neibghbors = NearestNeighbors(n_neighbors=2).fit(features_standardized)

scikit-learnの書き方は一貫しているので、過去の記事を読んでいれば、特に問題なく理解できるかと思います。

n_neighbors=2なので近傍2点を求めるモデルになっています。

最近傍の観測値を2つ求める

new_observation = [1, 1, 1, 1]

distances, indices = nearest_neibghbors.kneighbors([new_observation])

features_standardized[indices]

# array([[[1.03800476, 0.55861082, 1.10378283, 1.18556721],
#       [0.79566902, 0.32841405, 0.76275827, 1.05393502]]])

この2つのリストが、[1,1,1,1]に最も近い2つの観測値です。

最近傍の2点までの距離を求める

distances
# array([[0.49140089, 0.74294782]])

新規の観測値のクラスを推測する

from sklearn import datasets
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler

iris = datasets.load_iris()
X = iris.data
Y = iris.target

X_std = StandardScaler().fit_transform(X)

knn = KNeighborsClassifier(n_neighbors=5, n_jobs=-1).fit(X_std, Y)

# 推測
knn.predict([new_observation])
# array([2])

特に難しくないですね。クラスは2であると判定されました。

クラス予測を確率で確認する

knn.predict_proba([new_observation])
# array([[0., 0., 1.]])

このようにすれば、そのクラスに属する確率を確認することができます。

https://bdarc.net/wp-content/uploads/2020/05/icon-1.pnghistoroid

ここまでで、scikit-learnによるKNNの使い方をおさえてください。

kaggleデータで試してみよう

ではkaggleデータで試してみましょう。

変数2つで可視化してみる

# Google Driveのマウント
from google.colab import drive
drive.mount('/content/drive')

# csvファイルの読み込み
import pandas as pd
df = pd.read_csv('/content/drive/My Drive/Colab Notebooks/xxx.csv')

# 説明変数と目的変数
x = df.iloc[:, 2:32]
y = df['diagnosis']

# 可視化
import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(10, 8))
sns.scatterplot(x='radius_mean', y='texture_mean', hue=y, data=x, style=y)

たまたまですが、はじめの2つの変数で散布図を作ってみたら、いい感じにクラスタリングされていたのでこれを使うことにしましょう。

2変数でモデルをつくる

# 検証のための分割
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)

# 標準化
# それぞれ標準化している点に注意
from sklearn.preprocessing import StandardScaler
xtrain_std = StandardScaler().fit_transform(x_train)
xtest_std = StandardScaler().fit_transform(x_test)

# モデルの作成
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=5, n_jobs=-1).fit(xtrain_std[:, 0:2], y_train)

xtrain_std[:, 0:2]は「すべての行の」「0以上2未満の列」を使うことを意味します。

つまり、全データのうちでradius_meanとtexture_meanの2つの説明変数だけを使うということです。

検証データで推測する

knn.predict(xtest_std[:, 0:2])

'''
array(['B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B',
       'B', 'M', 'B', 'B', 'B', 'M', 'B', 'B', 'M', 'B', 'B', 'M', 'M',
       'B', 'B', 'B', 'B', 'M', 'M', 'B', 'B', 'B', 'B', 'B', 'M', 'M',
       'M', 'B', 'B', 'M', 'B', 'B', 'B', 'M', 'B', 'M', 'B', 'M', 'M',
       'B', 'M', 'M', 'B', 'M', 'M', 'B', 'B', 'M', 'M', 'M', 'B', 'B',
       'B', 'B', 'B', 'B', 'B', 'B', 'M', 'B', 'M', 'B', 'B', 'B', 'B',
       'B', 'B', 'B', 'B', 'B', 'M', 'B', 'M', 'M', 'B', 'M', 'M', 'M',
       'M', 'B', 'B', 'M', 'M', 'B', 'B', 'M', 'B', 'B', 'M', 'B', 'M',
       'B', 'M', 'B', 'M', 'M', 'B', 'B', 'B', 'B', 'B'], dtype=object)
'''

検証用データは全体の20%に指定していたので、約120件のデータです。

これに対して、120個の推測クラスが返ってきました。

正解率を確かめる

knn.score(xtest_std[:, 0:2], y_test)
# 0.8421052631578947

84%の正解率でした。2変数だけでこれくらいならまあまあですかね。

ちなみに30個の変数をすべて使ったら、正解率は97%まで上がりました。

knn.score(xtrain_std[:, 0:2], y_train)
# 0.9120879120879121

ちなみにこうやって確かめれば、訓練データでは正解率が91%であることがわかります。

予測が間違ったところを可視化する

推測値と観測値の正誤表をつくる

# 訓練データでの正誤のリストを作る
train_correct = []
for k, i in zip(knn.predict(xtrain_std[:, 0:2]), y_train):
    if k == i:
        train_correct.append('correct')
    else:
        train_correct.append('false')

# 正誤リストを加えたデータフレームを作る
df_train = pd.DataFrame({
    'radius_mean': x_train['radius_mean'],
    'texture_mean': x_train['texture_mean'],
    'ground truth': y_train,
    'correctness': train_correct
})

# 同様に検証データでも繰り返す
test_correct = []
for k, i in zip(knn.predict(xtest_std[:, 0:2]), y_test):
    if k == i:
        test_correct.append('correct')
    else:
        test_correct.append('false')
df_test = pd.DataFrame({
    'radius_mean': x_test['radius_mean'],
    'texture_mean': x_test['texture_mean'],
    'ground truth': y_test,
    'correctness': test_correct
})

ちょっと長いですが中身は簡単です。if文で、推測クラスと実際のクラスが一致しているかどうかを判定しています。

そして判定結果をデータフレームに加えています。

データフレームの結合

上記のコードでは、2つのデータフレームができていますので、まとめます。

df_concat = pd.concat([df_train, df_test])

まず、この状態で可視化してみましょう。

df_concatはデータを切り貼りして作り直したデータフレームなので、もとのデータと違っているかもしれません。ちゃんと確認しましょう。

sns.scatterplot(x='radius_mean', y='texture_mean', hue='ground truth', data=df_concat)

どうでしょうか。はじめに可視化した散布図と同じものが得られましたね(色は逆になりましたが)。

自分で作ったデータフレームですがうまくいっているようです。

予測間違いの可視化

sns.scatterplot(x='radius_mean', y='texture_mean', hue='correctness', data=df_concat)

予測を間違えたところだけが、オレンジ色で表示されています。

当たり前かもしれませんが、境界部分に間違いが多いです。これを可視化できただけでも良い結果だったと言えます。


https://bdarc.net/wp-content/uploads/2020/05/icon-1.pnghistoroid

今回はここまでです。

自分で変数を選び直してみると面白いかもしれません。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です