【はじめての機械学習 実践編】 分類問題を解く:k-最近傍法

実際に機械学習をやってみよう

以前、機械学習についての記事を書きました。
そちらの記事では機械学習の概要や実装の流れを説明しました。

機械学習ってそもそも何?と思われる方は、この記事を読む前にある程度概要を理解しておく必要があります。
先に一読することを推奨します。

Codeship 【聞いたことあるけど分からない単語】 機械学習ってなんだろう?

この記事ではコードを書いて、実際に機械学習を行っていこうと思います。
具体的には、機械学習で解決できる定番の課題である分類問題を「k-最近傍法」という学習モデルで解きます。

ボリュームが大きめなので一度に全て読もうとせずに、時間があるときに少しずつ理解することをおすすめします。

目次

今回使用する環境

機械学習を行うには、Pythonとそれを動かすために機械学習用ツールであるJupyter Notebookが必要です。
これらを開発環境として整える方法は大きく分けて3つあります。

  1. 標準のPythonにJupyter Notebookを入れる
  2. Dockerで機械学習用のコンテナを作成する
  3. Google Colaboratoryを使用する

今回は手軽に機械学習を行うことのできるGoogle社が無償で提供している3つ目の方法のGoogle Colaboratoryを使用する方法で行います。
このサービスではブラウザ上で環境構築なしに機械学習をすることができます。
また、ディープラーニングで必須なGPUやTPUも無料で提供されています。

学習者には非常に優良なサービスなので是非活用しましょう。

使用する際にはGoogleアカウントが必要です。

導入方法や使い方については様々なサイトでまとめられています。
一部サイトのリンクを載せておくので参考にしてください。
Google Colabの知っておくべき使い方 – Google Colaboratoryのメリット・デメリットや基本操作のまとめ

ちらっと出た2つ目のDockerについては、別の記事でまとめたいと思います。

機械学習の流れ

機械学習には以下の6つのステップがあります。

  1. 問題を明確にする
  2. データを入手する
  3. データの内容を理解する
  4. データを加工する
  5. 学習のモデルを選ぶ
  6. 実装する

今回はPythonのsklearnというライブラリが学習用に提供しているデータを使用するので、データを集めてきたり、加工の手間がさほど必要ではありません。

以下では、2種類または3種類のデータを分類する問題を扱っていきます。
そのためにk-最近傍法というモデルを使用します。

k-最近傍法とは

k-最近傍法(k-nearest neighbor algorithm, k-NN)とは、1つのデータがどのデータに一番近いかを判別するモデルです。
主に今回行うような分類問題ではよく使用されるものになります。

k-nniamge1

k-最近傍法では、自分の近くにあるデータを何個にするかをkとして指定します。
上記の例では、1つのテストデータに対してk=3を指定しているので、星印(テストデータ)の近くにある3つのデータを認識しています。

例えば、右下にあるtest pred 0training class 0が3つ選択されています。
つまり、test pred 0はclass 0と判断されます。
これをテストデータ一つ一つの適応し分類していくのがこのモデルです。

今回指定したk値によって分類の精度が変わるので、どのくらいの値にするかは重要です。

実践編1 サンプルデータセット

それでは、実際に行っていきましょう。
まずは必要なライブラリをインポートします。

!pip install mglearn
import mglearn
import matplotlib.pyplot as plt
# matplotib inline

from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split

mglearnはGoogle Colaboratoryには標準で入っていないので、上のようにインストールしてください。
まずはサンプルデータを自分で作成してそれを分類していきます。

x, y = mglearn.datasets.make_forge()

これでデータができました。

データが集まったら一番最初にすることはそのデータの中身がどうなっているか見ることです。
実際に中身を確認してみましょう。

# 数値データ
x

# xのデータの型
x.shape
array([[ 9.96346605,  4.59676542],
       [11.0329545 , -0.16816717],
       [11.54155807,  5.21116083],
       [ 8.69289001,  1.54322016],
       [ 8.1062269 ,  4.28695977],
       [ 8.30988863,  4.80623966],
       [11.93027136,  4.64866327],
       [ 9.67284681, -0.20283165],
       [ 8.34810316,  5.13415623],
       [ 8.67494727,  4.47573059],
       [ 9.17748385,  5.09283177],
       [10.24028948,  2.45544401],
       [ 8.68937095,  1.48709629],
       [ 8.92229526, -0.63993225],
       [ 9.49123469,  4.33224792],
       [ 9.25694192,  5.13284858],
       [ 7.99815287,  4.8525051 ],
       [ 8.18378052,  1.29564214],
       [ 8.7337095 ,  2.49162431],
       [ 9.32298256,  5.09840649],
       [10.06393839,  0.99078055],
       [ 9.50048972, -0.26430318],
       [ 8.34468785,  1.63824349],
       [ 9.50169345,  1.93824624],
       [ 9.15072323,  5.49832246],
       [11.563957  ,  1.3389402 ]])

(26, 2)
# 正解ラベル
y

# yのデータの型
y.shape
array([1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0,
       0, 0, 1, 0])

(26,)

xには26行2列の2次元データ、yにはそのデータの分類が入っています。
今回の目標はxのデータだけでyの分類を予測することです。

ただこのままではデータの全体像がわかりにくいので、散布図も書いてみましょう。

# データの可視化
# discreate_scatter: 行と列、グルーピングを指定する
# 第1引数: 散布図に描写する各データのX値
# 第2引数: 散布図に描写する各データのY値
# 第3引数: 散布図に描写する各データのLABEL 
mglearn.discrete_scatter(x[:, 0], x[:, 1], y)
plt.show()
scatterplot1

生データをそのまま見るよりもこのように図を書いて可視化したほうが理解しやすいのではないでしょうか。
なんとなくこの2種類が2つに別れているように見えるのがお分かり頂けると思います。

それでは今回のデータセットをモデル学習に使うための訓練用データそのモデルをテストするテストデータに分けていきます。

# test用、train用データを生成
X_train, X_test, y_train, y_test = train_test_split(x, y, random_state=0)
X_train.shape

X_test.shape
(19, 2)

(7, 2)

訓練用データとテストデータは8:2ぐらいがちょうどよいです。
train_test_splitを使えば自動で分割してくれます。

これで準備完了しました。
ここからモデルを適応していくのですが、既に便利なメソッドがあるのですごく簡単に実装できます。

# 学習させる(k値は3に選択)
clf = KNeighborsClassifier(n_neighbors=3)
clf.fit(X_train, y_train)

なんとたったこれだけです。

実際の予測がどうなっているか見てみましょう。

# 予測結果と正解ラベルを見比べる
clf.predict(X_test)

y_test
array([1, 0, 1, 0, 1, 0, 0])

array([1, 0, 1, 0, 1, 1, 0])

最後だけ違いますが、ほとんど合っていることが確認できます。
つまり、うまく予測できていることになります。

このモデルの精度も計算できます。

# どのくらい正しいか
clf.score(X_test, y_test)
0.8571428571428571

だいたい85%程度です。
なかなかの精度です。

ただし、この値はk=3で行いました。
他の値にするとどうなるのか確認して、もっとよい精度が上がらないか考えていきましょう。

試しにk=10にしてみます。

clf_10 = KNeighborsClassifier(n_neighbors=10).fit(X_train, y_train)
clf_10.score(X_test, y_test)
0.8571428571428571

全く値に変化がありません。

このままkに数を入れ続けていろんな数を確認するのも一つのやり方ですが、もっと賢く一気にやってしまいましょう。
具体的には繰り返し処理ができるfor文を使います。

# for文を使って1-15までのモデルを試す
for n_neighbors in range(1, 16):
  clf = KNeighborsClassifier(n_neighbors=n_neighbors).fit(X_train, y_train)
  print('Test set accuracy: n_neighbors={}, {:.2f}'.format(n_neighbors, clf.score(X_test, y_test)))
Test set accuracy: n_neighbors=1, 0.86
Test set accuracy: n_neighbors=2, 0.86
Test set accuracy: n_neighbors=3, 0.86
Test set accuracy: n_neighbors=4, 0.86
Test set accuracy: n_neighbors=5, 0.86
Test set accuracy: n_neighbors=6, 0.86
Test set accuracy: n_neighbors=7, 0.86
Test set accuracy: n_neighbors=8, 0.86
Test set accuracy: n_neighbors=9, 0.86
Test set accuracy: n_neighbors=10, 0.86
Test set accuracy: n_neighbors=11, 0.86
Test set accuracy: n_neighbors=12, 0.86
Test set accuracy: n_neighbors=13, 0.86
Test set accuracy: n_neighbors=14, 0.86
Test set accuracy: n_neighbors=15, 0.86

1から15まで試してみましたが、精度に変化がありませんでした。
ただし、これらに何も変化がないわけではありません。

どのように2つのグループの境界線を引いたのか見てみましょう。

# 内部の境界線の変化を図で確認する

# subplot: グラフを書くためのキャンパスを用意(グラフの行、 列、 大きさ)
# fig: ウインドウ、axes: タブ
fig, axes = plt.subplots(1, 5, figsize=(15, 3))

# for文でk値が1,3,5,10,15のときグラフを描写する
for n_neighbors, ax in zip([1,3,5,10,15], axes):
  clf = KNeighborsClassifier(n_neighbors=n_neighbors).fit(X_train, y_train)

    # plot_2d_separatorは領域を2つに分割できる
  mglearn.plots.plot_2d_separator(clf, x, fill=True, ax=ax, alpha=0.5)
  mglearn.discrete_scatter(x[:, 0], x[:, 1], y, ax=ax)
  ax.set_title('{} neighbors'.format(n_neighbors))

plt.show()
knniamge3

これで境界線が変化していることが分かります。
結果に変化がなくても、どのように処理しているかは変わるので最良のモデルを選ぶ際のヒントになります。

実践編2 アイリスデータセット

上記の例は人工的なデータでしたが、次は機械学習用に用意された実際のデータを使います。
古くから使われているものでアイリスというアヤメ科の花のデータセットというものがあります。
今回はそれを使用して、もう一度分類問題を解いていきます。

まずはデータを読み込んで中身を見てみます。

from sklearn.datasets import load_iris
iris_dataset = load_iris()

# iris_datasetのデータごとのタイトル
iris_dataset.keys()
dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names', 'filename'])

keyとはこのデータごとのタイトルです。
全部で6種類あることがわかると思います。

それぞれになにが入っているか確認します。

# データセットの説明
print(iris_dataset['DESCR'][:400] + '\n...')
.. _iris_dataset:

Iris plants dataset
--------------------

**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:

...
# 今回分類する3種類の名前
iris_dataset['target_names']
array(['setosa', 'versicolor', 'virginica'], dtype='<U10')
# それぞれの花の特徴づけ
iris_dataset['feature_names']
['sepal length (cm)',
 'sepal width (cm)',
 'petal length (cm)',
 'petal width (cm)']
# データ型
iris_dataset['data'].shape
(150,4)
# 前から5個確認する
iris_dataset['data'][:5]
array([[5.1, 3.5, 1.4, 0.2],
       [4.9, 3. , 1.4, 0.2],
       [4.7, 3.2, 1.3, 0.2],
       [4.6, 3.1, 1.5, 0.2],
       [5. , 3.6, 1.4, 0.2]])
# 0:setosa, 1:versicolor, 2:virginica
iris_dataset['target']
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])

今回はターゲットネームの3種類をデータから予測してみましょう。

やり方は先ほどと同様です。
データを訓練用とテスト用に分けてから、モデルを適応します。

x_train, x_test, y_train, y_test = train_test_split(iris_dataset['data'], iris_dataset['target'], random_state=0)
knn = KNeighborsClassifier(n_neighbors=1)
knn.fit(x_train, y_train)
knn.predict(x_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, 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])
# 正答状況の確認
knn.predict(x_test) == y_test
array([ True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True, False])
# 精度確認
knn.score(x_test, y_test)
0.9736842105263158

非常に簡単な実装でしたが、97%と非常によい精度で結果が返ってきました。
k値を変更するともっと良い結果が返ってくるかもしれません。
是非皆さんも色々工夫して精度の上下を確認してみてください。

まとめ

今回は機械学習の定番の問題である分類問題をk-最近傍法というモデルを使って解きました。

この記事を読んでおわかりいただけたと思いますが、機械学習を実装するだけならコードを数行書くだけで容易に実装できてしまいます。
そのため機械学習に興味を持っているという方は、まずは難しく考えすぎずこういったところから実際に手を動かして体感してみましょう。

また、今回は機械学習のデータを使ったためありませんでしたが、データの加工やどのモデルを適応するのかを考えることが非常に重要になってきます。

どうすれば課題を解決できるのか、精度を上げるにはどうすればよいかを考えることに、よりよい機械学習の活用法が見出せるでしょう。


おすすめ記事