ナード戦隊データマン

データサイエンスを用いて悪と戦うぞ

knnの汎化性能をkごとに確かめる

knnとは機械学習アルゴリズムの一つで、ある点からk個の最も近い点を選び、その中で最も多いラベルを用いて分類する方法です。このアルゴリズムはとても単純でわかりやすいので、ここでは、knnを用いて汎化性能の意味を確かめます。

Jupyter notebookを用いる

anacondaをインストールし、以下のコマンドを実行します。

$jupyter notebook

ブラウザから新しいpython 3ノートブックを起動します。

mglearnのインストール

$ pip install --user mglearn

必要なライブラリのインポート

In[1]:

%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import mglearn
from IPython.display import display

forgeデータとwaveデータをプロットする

In[2]:

X, y = mglearn.datasets.make_wave(n_samples=40)
plt.plot(X, y, 'o')
plt.ylim(-3, 3)
plt.xlabel("Feature")
plt.ylabel("Target")

f:id:mathgeekjp:20170830205522p:plain

In[3]:

X, y = mglearn.datasets.make_forge()
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
plt.legend(["Class 0", "Class 1"], loc=4)
plt.xlabel("First feature")
plt.ylabel("Second feature")
print("X.shape:{}".format(X.shape))

f:id:mathgeekjp:20170830203731p:plain

このデータをknnによって分類することを試みます。

k=1,3,9における分類

In[4]:

fig, axes = plt.subplots(1, 3, figsize=(10, 3))

for n_neighbors, ax in zip([1,3,9], axes):
    clf = KNeighborsClassifier(n_neighbors=n_neighbors).fit(X,y)
    mglearn.plots.plot_2d_separator(clf, X, fill=True, eps=0.5, ax=ax, alpha=.4)
    mglearn.discrete_scatter(X[:, 0], X[:, 1], y, ax=ax)
    ax.set_title("{} neighbor(s)".format(n_neighbors))
    ax.set_xlabel("feature 0")
    ax.set_ylabel("feature 1")
axes[0].legend(loc=3)

f:id:mathgeekjp:20170830203944p:plain

In[5]:

from sklearn.neighbors import KNeighborsRegressor
X, y = mglearn.datasets.make_wave(n_samples=40)

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

fig, axes = plt.subplots(1, 3, figsize=(15, 4))
line = np.linspace(-3, 3, 1000).reshape(-1,1)
for n_neighbors, ax in zip([1,3,9], axes):
    reg = KNeighborsRegressor(n_neighbors=n_neighbors)
    reg.fit(X_train, y_train)
    ax.plot(line, reg.predict(line))
    ax.plot(X_train, y_train, '^', c=mglearn.cm2(0), markersize=8)
    ax.plot(X_test, y_test, 'v', c=mglearn.cm2(1), markersize=8)
    ax.set_title("{} neighbor(s)\n train score: {:.2f} test score: {:.2f}".format(
        n_neighbors, reg.score(X_train, y_train), reg.score(X_test, y_test)
    ))
    ax.set_xlabel("Feature")
    ax.set_ylabel("Target")
    
    axes[0].legend(["Model predictions", "Training data/target", "Test data/target"], loc="best")

f:id:mathgeekjp:20170830205801p:plain

kが増えるほど、分類は滑らかな線に近づいていくことがわかります。一般的に、k=9のようなシンプルなモデルほど汎化性能が高いと言われます。

kごとの訓練データとテストデータのscoreをプロットする

前述の例ではforgeデータを用いましたが、このscoreを求める例では、load_breast_cancerデータを使用します。

In[4]:

from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()

X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, stratify=cancer.target, random_state=66)

neighbors_settings = range(1, 11)
training_accuracy = []
test_accuracy = []

for n_neighbors in neighbors_settings:
    clf = KNeighborsClassifier(n_neighbors=n_neighbors)
    clf.fit(X_train, y_train)
    training_accuracy.append(clf.score(X_train, y_train))
    test_accuracy.append(clf.score(X_test, y_test))

plt.plot(neighbors_settings, training_accuracy, label="training accuracy")
plt.plot(neighbors_settings, test_accuracy, label="test accuracy")
plt.ylabel("Accuracy")
plt.xlabel("n_neighbors")
plt.legend()

f:id:mathgeekjp:20170830204430p:plain

このグラフを見ればわかるように、k=6あたりでテストに対する予測性能が最大となっています。一方で、k=1のときには、訓練データに対して高い精度を持っていることがわかります。

考察

訓練データに対して高い性能を持つような学習は、一般的にオーバーフィッティングの可能性が高いと言えます。言い換えれば、訓練データ以外のデータに対する予測性能が落ちるということです。

knnの場合、k=1はオーバーフィッティングしやすくなり、適切なkを選択することによって汎化性能が高まると言えます。

参考

  1. elvinouyang.github.io

  2. shop.oreilly.com