k近傍法(k-NN)を実装してみよう!わかりやすく解説
【スマホからでも実行可能】k近傍法(k-NN)アルゴリズムについて、Pythonでの具体的な実装方法をステップバイステップで丁寧に解説します。機械学習の基本的な手法を実際に手を動かして理解したい方、データ分類タスクに挑戦したい方におすすめです。
記事の最後に、環境構築なしでスマホからでも即実行可能なGoogle Colabノートブックをご用意しています。
k近傍法(k-NN)とは?
k近傍法(k-Nearest Neighbors、略してk-NN)は、機械学習のアルゴリズムの中でも特にシンプルで理解しやすい手法の一つです。新しいデータがどちらのグループに属するかを判断する際に、「ご近所さん」のデータを見て多数決で決める、というイメージです。
具体的には、以下のステップで分類を行います。
- 距離の計算: 新しいデータと、すでにグループ分けされている既存のデータそれぞれとの距離を計算します。
- 近傍の選択: 新しいデータから距離が近い順に、指定された「k個」のデータを選び出します。この「k」が、アルゴリズムの名前の由来です。
- 多数決: 選ばれたk個のデータの中で、最も多くのデータが属しているグループに、新しいデータを分類します。
例えば、新しい果物をリンゴとミカンのどちらに分類するか迷ったとします。k-NNでは、その新しい果物の特徴(色、形、重さなど)を、すでにリンゴやミカンと分類されている他の果物の特徴と比較します。そして、特徴が最も似ている(距離が近い)果物をk個選び、その中で多数派だったグループ(リンゴが多ければリンゴ、ミカンが多ければミカン)に新しい果物を分類する、といった具合です。
この「k」の値をどう設定するかが、k-NNの性能を左右する重要なポイントになります。小さすぎるとノイズの影響を受けやすくなり、大きすぎると異なるグループの特徴を拾ってしまい、うまく分類できなくなることがあります。
Pythonでk-NNを実装してみよう
それでは、実際にPythonを使ってk-NNアルゴリズムを実装してみましょう。ここでは、機械学習ライブラリであるscikit-learn
(サイキットラーン)を使用します。scikit-learn
には、k-NNをはじめとする多くの機械学習アルゴリズムが用意されており、比較的簡単に利用することができます。
今回は、アヤメの品種を分類する有名なデータセット「Iris(アイリス)データセット」を使って、k-NNモデルを構築し、未知のアヤメがどの品種に属するかを予測するプログラムを作成します。
1. 必要なライブラリのインポート
まず、プログラムの最初で、今回使用するライブラリをインポートします。
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
それぞれのライブラリの役割は以下の通りです。
numpy
: 数値計算を効率的に行うためのライブラリです。主に配列データを扱います。sklearn.datasets.load_iris
: Irisデータセットを読み込むための関数です。sklearn.model_selection.train_test_split
: データを訓練用とテスト用に分割するための関数です。モデルの性能を正しく評価するために重要です。sklearn.preprocessing.StandardScaler
: データの特徴量のスケールを揃える(標準化する)ためのクラスです。k-NNのように距離を計算するアルゴリズムでは、特徴量ごとのスケールの違いが結果に影響を与えることがあるため、標準化を行うことが一般的です。sklearn.neighbors.KNeighborsClassifier
: k-NNアルゴリズム(分類用)のクラスです。sklearn.metrics.accuracy_score
: モデルの予測精度を評価するための関数です。
2. データセットの準備
次に、Irisデータセットを読み込み、中身を確認してみましょう。
# Irisデータセットの読み込み
iris = load_iris()
X = iris.data # 特徴量 (がくの長さ、がくの幅、花びらの長さ、花びらの幅)
y = iris.target # 目的変数 (アヤメの品種: 0, 1, 2 のいずれか)
# データの中身を確認 (最初の5行)
print("特徴量 (X) の最初の5行:")
print(X[:5])
print("\n目的変数 (y) の最初の5行:")
print(y[:5])
print("\nアヤメの品種名:")
print(iris.target_names)
Irisデータセットには、アヤメの「がくの長さ」「がくの幅」「花びらの長さ」「花びらの幅」という4つの特徴量(X
)と、そのアヤメがどの品種(Setosa、Versicolor、Virginicaの3種類、それぞれ0、1、2という数値で表されます)に属するかを示す目的変数(y
)が含まれています。
3. データの分割(訓練用とテスト用)
モデルを訓練するためのデータ(訓練データ)と、訓練済みモデルの性能を評価するためのデータ(テストデータ)に分割します。これにより、モデルが未知のデータに対しても正しく予測できるか(汎化性能)を確認できます。
# 訓練データとテストデータに分割 (テストデータの割合を30%に設定)
# random_stateを指定することで、毎回同じように分割されるため、結果の再現性が得られます。
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)
print(f"訓練データのサンプル数: {X_train.shape[0]}")
print(f"テストデータのサンプル数: {X_test.shape[0]}")
train_test_split
関数のtest_size=0.3
は、全データの30%をテストデータとして使用することを意味します。random_state
は分割の際の乱数のシードで、これを固定すると何度実行しても同じ分割結果が得られます。stratify=y
は、分割後の訓練データとテストデータの各クラスの割合が、元のデータセットのクラスの割合と等しくなるようにするための指定です。これは、クラスのデータ数に偏りがある場合に特に重要です。
4. データの前処理(標準化)
k-NNアルゴリズムは、データポイント間の距離に基づいて分類を行います。そのため、特徴量のスケール(単位や値の範囲)が異なると、スケールの大きな特徴量の影響を強く受けてしまう可能性があります。これを避けるために、各特徴量の平均が0、標準偏差が1になるように変換する「標準化」を行います。
# 特徴量の標準化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # 訓練データでスケーラーを学習し、訓練データを変換
X_test_scaled = scaler.transform(X_test) # 学習済みのスケーラーを使ってテストデータを変換
# 標準化後のデータを確認 (最初の5行)
print("\n標準化後の訓練データ (X_train_scaled) の最初の5行:")
print(X_train_scaled[:5])
ここで重要なのは、StandardScaler
のfit_transform
メソッドを訓練データにのみ適用し、その学習結果(平均と標準偏差)を使ってテストデータをtransform
メソッドで変換する点です。テストデータから新たに平均や標準偏差を計算してしまうと、訓練データとテストデータで基準が異なってしまい、正しい評価ができなくなるためです。
5. k-NNモデルの構築と訓練
いよいよk-NNモデルを構築し、訓練データを使って学習させます。KNeighborsClassifier
クラスのインスタンスを作成する際に、近傍の数 k
を指定します。ここでは、一般的に良い結果が得られやすいとされる奇数で、k=3
としてみましょう。
# k-NNモデルのインスタンスを作成 (k=3)
knn_model = KNeighborsClassifier(n_neighbors=3)
# モデルの訓練
knn_model.fit(X_train_scaled, y_train)
print("\nk-NNモデルの訓練が完了しました。")
fit
メソッドに標準化された訓練データの特徴量(X_train_scaled
)と目的変数(y_train
)を渡すことで、モデルが学習を行います。k-NNの場合、厳密には「学習」というよりは、訓練データを内部に記憶しておく、という処理になります。
6. モデルによる予測
訓練済みのモデルを使って、テストデータのアヤメの品種を予測してみましょう。
# テストデータを使って予測
y_pred = knn_model.predict(X_test_scaled)
# 予測結果と実際の品種を比較 (最初の5件)
print("\n予測結果 (y_pred) と実際の品種 (y_test) の比較 (最初の5件):")
for i in range(5):
print(f"予測: {iris.target_names[y_pred[i]]}, 実際: {iris.target_names[y_test[i]]}")
predict
メソッドに標準化されたテストデータの特徴量(X_test_scaled
)を渡すと、各テストサンプルがどの品種に分類されるかの予測結果(y_pred
)が返されます。
7. モデルの評価
最後に、モデルの予測がどの程度正しかったのかを評価します。ここでは、正解率(Accuracy)を計算します。正解率は、全データのうち、正しく分類できたデータの割合です。
# モデルの正解率を計算
accuracy = accuracy_score(y_test, y_pred)
print(f"\nモデルの正解率: {accuracy:.4f}")
accuracy_score
関数に、実際の品種(y_test
)とモデルの予測結果(y_pred
)を渡すことで、正解率が計算されます。
(補足) 最適なkの値を見つけるには?
今回の例では k=3
としましたが、実際にはどの k
の値が最も良い性能を発揮するかは、データセットによって異なります。最適な k
の値を見つけるためには、いくつかの異なる k
の値でモデルを訓練・評価し、最も性能の良い k
を選択するという方法が一般的です。
例えば、k
の値を1から10まで変化させ、それぞれの正解率を記録して比較することができます。
# 最適なkの値を探す (例: k=1から10まで)
k_range = range(1, 11)
accuracies = []
for k_val in k_range:
knn_temp = KNeighborsClassifier(n_neighbors=k_val)
knn_temp.fit(X_train_scaled, y_train)
y_pred_temp = knn_temp.predict(X_test_scaled)
accuracies.append(accuracy_score(y_test, y_pred_temp))
# kの値ごとの正解率をプロット (matplotlibが必要)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))
plt.plot(k_range, accuracies, marker='o', linestyle='dashed')
plt.title('kの値と正解率の関係')
plt.xlabel('kの値 (n_neighbors)')
plt.ylabel('正解率 (Accuracy)')
plt.xticks(k_range)
plt.grid(True)
plt.show()
# 最も正解率の高かったkの値
best_k = k_range[np.argmax(accuracies)]
print(f"\n最も高い正解率を示したkの値: {best_k} (正解率: {max(accuracies):.4f})")
このようにして、データに対してより適切な k
の値を見つけ出すことができます。ただし、テストデータを使って最適な k
を選んでしまうと、その k
はテストデータに過剰に適合してしまう(過学習)可能性があります。より厳密には、訓練データをさらに訓練用と検証用に分割し、検証用データで最適な k
を見つけ、最後にテストデータで最終的な性能を評価する、といった手順(交差検証など)が用いられます。
実装する上での注意点とコツ
k-NNを実際に使う上で、いくつか気をつけておきたい点や、よりうまく活用するためのコツがあります。
- 特徴量のスケーリング: 前述の通り、k-NNは距離計算に基づいているため、特徴量のスケールを揃えることが非常に重要です。標準化(StandardScaler)や正規化(MinMaxScalerなど)といった前処理を適切に行いましょう。
k
の値の選択:k
の値はモデルの性能に大きく影響します。小さすぎるとノイズに敏感になり、大きすぎると境界がぼやけてしまいます。一般的には奇数を選ぶことが多いです(多数決で同数になるのを避けるため)。交差検証などの手法を用いて、データに適したk
の値を見つけることが推奨されます。- 計算コスト: k-NNは、予測を行う際に全ての訓練データとの距離を計算する必要があります。そのため、訓練データの数や特徴量の次元数が非常に大きい場合、予測に時間がかかることがあります。大規模なデータセットに対しては、計算効率の良い他のアルゴリズムを検討するか、近似近傍探索などのテクニックを利用する必要があるかもしれません。
- 次元の呪い: 特徴量の次元数が非常に多くなると、データ点がまばらになり、最近傍という概念が曖昧になってしまう「次元の呪い」と呼ばれる問題が発生しやすくなります。この場合、事前に次元削減(主成分分析など)を行うことが有効な場合があります。
- 適切な距離尺度の選択:
KNeighborsClassifier
では、デフォルトでミンコフスキー距離(p=2
の場合はユークリッド距離、p=1
の場合はマンハッタン距離)が使用されます。データの特性によっては、他の距離尺度(コサイン類似度など)の方が適している場合もあります。metric
パラメータで指定可能です。 - 不均衡データへの配慮: クラスごとのデータ数に大きな偏りがある(不均衡データ)場合、多数派のクラスに引っ張られやすくなることがあります。このような場合は、k個の近傍点に距離に応じた重み付けをしたり(
KNeighborsClassifier
のweights='distance'
)、オーバーサンプリングやアンダーサンプリングといった不均衡データ対策の手法を併用することを検討しましょう。
k近傍法は、そのシンプルさから機械学習の入門としてよく取り上げられますが、適切に扱うことで様々な問題に応用できる強力なアルゴリズムです。実際にコードを書いて動かしてみることで、その仕組みや特性についての理解がより深まるはずです。ぜひ、色々なデータセットで試してみてください。
今すぐ試したい! 機械学習・深層学習(ディープラーニング) 画像認識プログラミングレシピ
本書は、機械学習や深層学習の分野から画像認識に重点をおいて、難しい数式をつかわず、図や写真を多用して解説する入門書です。必要な概念、用語、キーワードも網羅的に説明します。
▶ Amazonで見る環境構築なしで
実行できるファイルはこちら!
このボタンからGoogle Colabを開き、すぐにコードをお試しいただけます。
関連する記事
エクストラツリー(ExtraTrees)を実装してみよう!わかりやすく解説
【スマホからでも実行可能】この記事では、機械学習のアルゴリズムであるエクストラツリー(ExtraTrees)について、その仕組みからPythonによる実装方法までを丁寧に解説します。ランダムフォレストとの違いも理解しながら、実践的なスキルを身につけましょう。
勾配ブースティング木(Gradient Boosted Trees)を実装してみよう!わかりやすく解説
【スマホからでも実行可能】勾配ブースティング木は、機械学習の分野で非常に強力な予測モデルの一つです。この記事では、その仕組みとPythonによる具体的な実装方法を、初心者にも分かりやすく解説します。実際にコードを動かしながら、この強力なアルゴリズムを体験してみましょう。
ランダムフォレストを実装してみよう!わかりやすく解説
【スマホからでも実行可能】ランダムフォレストの実装方法をPythonコード付きで丁寧に解説。機械学習のアンサンブル学習を実際に動かして理解を深めましょう。初心者にもわかりやすい実践ガイドです。
決定木(Decision Tree)を実装してみよう!わかりやすく解説
【スマホからでも実行可能】決定木の実装方法をPythonコード付きでステップバイステップ解説。データの準備からモデル構築、可視化、評価、チューニングまで、実践的なスキルが身につきます。機械学習の基本である決定木をマスターしましょう。
粒子群最適化(PSO)を実装してみよう!わかりやすく解説
【スマホからでも実行可能】粒子群最適化(PSO)のアルゴリズムをPythonで実装し、その仕組みを初心者にもわかりやすく解説します。最適化問題や機械学習に興味がある方におすすめです。パラメータ設定のコツや応用例も紹介し、実践的な理解を深めます。