K近傍法(KNN)の理論的枠組み
K近傍法(K-Nearest Neighbors, KNN)は、訓練データ空間内の幾何学的構造に基づく非パラメトリックな機械学習アルゴリズムです。未知のサンプルを分類する際、特徴空間内で距離が最も近いK個の近傍点を検索し、それらのラベル分布から決定を行います。回帰タスクでは近傍点の出力値を加重平均することで推定値を算出します。
アルゴリズムの振る舞いは以下の3つの要素によって規定されます:
- 近傍数K:Kが小さいと決定境界が複雑になりノイズに敏感になりますが、大きすぎるとクラスの境界が曖昧化し過学習のリスクが高まります。
- 距離指標:特徴ベクトル間の類似度を数値化します。ユークリッド距離、マンハッタン距離、マハラノビス距離が一般的ですが、特徴量のスケール差異が距離計算に与える影響を排除するため、事前の標準化または正規化が必須です。
- 決定則:分類では単純多数決または距離逆数加重投票が採用されます。回帰では近傍ラベルの距離加重平均が出力されます。
実行環境とデータ前処理
本実装ではMindSpore 2.x系を基盤とし、NumPyによる行列演算とmatplotlibによる可視化を組み合わせてワークフローを構築します。対象データはUCI由来のワイン化学分析データセット(178サンプル、13次元特徴量、3クラス)です。
import csv
import numpy as np
import matplotlib.pyplot as plt
import mindspore as ms
from mindspore import nn, ops
# 計算デバイスの指定
ms.set_context(device_target="CPU")
# CSVファイルのストリーム読み込み
with open("wine.data", mode="r", encoding="utf-8") as file_handle:
raw_entries = list(csv.reader(file_handle))
# 特徴量行列(13次元)とラベルベクトルの抽出・型変換
feature_matrix = np.array(
[[float(val) for val in row[1:]] for row in raw_entries[:178]],
dtype=np.float32
)
label_vector = np.array(
[int(row[0]) for row in raw_entries[:178]],
dtype=np.int32
)
訓練/検証分割と特徴量分布の可視化
モデルの汎化性能を検証するため、全サンプルを128/50の比率で訓練用と検証用に分割します。また、2次元プロットによる特徴量のクラス別分布を確認し、線形分離可能性を定性的に評価します。
# 分割用マスクの生成(再現性のためシード固定)
np.random.seed(42)
total_n = len(feature_matrix)
train_indices = np.random.choice(total_n, size=128, replace=False)
val_indices = np.array([i for i in range(total_n) if i not in train_indices])
X_train, y_train = feature_matrix[train_indices], label_vector[train_indices]
X_val, y_val = feature_matrix[val_indices], label_vector[val_indices]
# 2次元特徴空間でのクラス分布プロット
attr_labels = ["アルコール", "リンゴ酸", "灰分", "灰のアルカリ度", "マグネシウム",
"総フェノール", "フラボノイド", "非フラボノイドフェノール",
"プロアンシアニジン", "色彩強度", "色調", "OD280/OD315", "プロリン"]
plt.figure(figsize=(10, 8))
for subplot_id in range(4):
plt.subplot(2, 2, subplot_id + 1)
dim_x, dim_y = 2 * subplot_id, 2 * subplot_id + 1
# 各クラスのインデックス抽出
for cls_id in [1, 2, 3]:
mask = y_train == cls_id
plt.scatter(X_train[mask, dim_x], X_train[mask, dim_y], label=f"Class {cls_id}")
plt.xlabel(attr_labels[dim_x])
plt.ylabel(attr_labels[dim_y])
plt.legend()
plt.tight_layout()
plt.show()
距離計算法則の実装
KNNの核心は効率的な距離計算と近傍検索です。MindSporeの演算子を活用し、バッチ対応のユークリッド距離計算とTop-K検索をネットワークセルに統合します。変数形状を動的に解決し、ハードコードを排除しています。
class KNNClassifierCell(nn.Cell):
def __init__(self, neighbor_count: int):
super().__init__()
self.k = neighbor_count
def construct(self, query_vec: ms.Tensor, reference_matrix: ms.Tensor):
ref_batch_size = reference_matrix.shape[0]
# クエリベクトルの形状を参照データ数に展開
expanded_query = ops.tile(query_vec, (ref_batch_size, 1))
# 二乗誤差の要素毎計算と次元間和
squared_diff = ops.square(expanded_query - reference_matrix)
dist_squared = ops.sum(squared_diff, axis=1)
# ユークリッド距離の算出
euclidean_dists = ops.sqrt(dist_squared)
# 最小距離のk個を取得(符号反転してTopKで降順ソートを実現)
_, neighbor_idx = ops.topk(-euclidean_dists, self.k)
return neighbor_idx
def execute_vote(model, sample, train_feats, train_labels):
sample_tensor = ms.Tensor(sample)
ref_tensor = ms.Tensor(train_feats)
idx = model(sample_tensor, ref_tensor).asnumpy()
neighbor_cls = train_labels[idx]
# 出現頻度の集計と最大頻度クラスの抽出
unique_vals, freq_counts = np.unique(neighbor_cls, return_counts=True)
return unique_vals[np.argmax(freq_counts)]
モデル推論と性能評価
検証データセットを用いて分類精度を測定します。近傍数K=5を適用し、逐次推論を実行して一致率を算出します。KNNは非パラメトリックなため訓練フェーズは不要であり、推論時の計算コストが主要なボトルネックとなります。
k_param = 5
knn_network = KNNClassifierCell(k_param)
correct_count = 0
total_eval = len(X_val)
for query_feat, true_lbl in zip(X_val, y_val):
pred_lbl = execute_vote(knn_network, query_feat, X_train, y_train)
is_match = (pred_lbl == true_lbl)
correct_count += int(is_match)
print(f"Actual: {true_lbl} | Predicted: {pred_lbl} | Match: {bool(is_match)}")
validation_acc = correct_count / total_eval
print(f"Validation Accuracy: {validation_acc:.4f}")
上記の実行フローにより、ワインの13次元化学指標を基にした品種識別タスクが完結します。距離ベースの分類器では特徴量のスケール正規化とK値のグリッドサーチが性能向上の鍵となります。