人工智能【第11篇】K近邻算法KNN:简单有效的分类方法(长文+代码实现)

作者的话 :在前面几篇文章中,我们学习了决策树、随机森林和支持向量机。今天要介绍的**K近邻算法(K-Nearest Neighbors, KNN)**是机器学习中最简单、最直观的算法之一。它没有显式的训练过程,通过计算样本之间的距离来进行分类或回归。本文将带你深入理解KNN的原理、距离度量方法和实际应用!


一、KNN算法概述

1.1 什么是K近邻算法?

K近邻算法(K-Nearest Neighbors, KNN) 是一种基于实例的学习算法,也是最简单的机器学习算法之一。它的核心思想是:如果一个样本在特征空间中的k个最相似(即距离最近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。

KNN的特点

  • 简单直观,易于理解和实现
  • 无需训练过程(惰性学习)
  • 适用于分类和回归问题
  • 对数据分布没有假设
  • 计算复杂度高,存储开销大

1.2 KNN算法流程

KNN算法步骤

  1. 计算距离:计算待分类样本与训练集中所有样本的距离
  2. 选择近邻:选取距离最近的k个样本
  3. 投票决策:根据k个近邻的类别进行投票(分类)或取平均值(回归)

1.3 KNN算法的优缺点

优点 缺点
算法简单,易于实现 计算复杂度高,预测速度慢
无需训练过程 存储开销大
对数据分布没有假设 对异常值敏感
可用于分类和回归 需要选择合适的k值
适合多分类问题 特征尺度影响大

二、距离度量方法

2.1 欧氏距离(Euclidean Distance)

欧氏距离是最常用的距离度量方法,表示两点之间的直线距离:

d(x,y) = sqrt(sum((x_i - y_i)^2))

欧氏距离适用于连续特征,对异常值敏感。

2.2 曼哈顿距离(Manhattan Distance)

曼哈顿距离表示两点在坐标轴上的绝对距离之和:

d(x,y) = sum(|x_i - y_i|)

曼哈顿距离适用于高维数据,对异常值不那么敏感。

2.3 闵可夫斯基距离(Minkowski Distance)

闵可夫斯基距离是欧氏距离和曼哈顿距离的推广:

d(x,y) = (sum(|x_i - y_i|^p))^(1/p)

当p=1时为曼哈顿距离,p=2时为欧氏距离。

2.4 距离度量对比

距离度量 公式特点 适用场景
欧氏距离 直线距离 连续特征,低维数据
曼哈顿距离 坐标轴距离之和 高维数据,网格状数据
切比雪夫距离 最大坐标差 棋盘距离
余弦相似度 向量夹角 文本分类,推荐系统

三、K值的选择

3.1 K值的影响

K值是KNN算法中最重要的超参数,直接影响模型的性能:

  • K值较小:模型复杂,容易过拟合,对噪声敏感
  • K值较大:模型简单,容易欠拟合,决策边界平滑

3.2 K值选择方法

交叉验证法:通过交叉验证选择使验证误差最小的k值

经验法则:k = sqrt(n),其中n为训练样本数

奇数原则:二分类时选择奇数k,避免平局

四、KNN的Python实现

4.1 使用sklearn的KNN

复制代码
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import make_classification, load_iris
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score, classification_report

# 生成数据
X, y = make_classification(n_samples=500, n_features=2, n_redundant=0, 
                           n_informative=2, n_clusters_per_class=1, 
                           random_state=42)

# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, 
                                                    random_state=42)

# 标准化(KNN对特征尺度敏感)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 创建KNN分类器
knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(X_train_scaled, y_train)

# 预测
y_pred = knn.predict(X_test_scaled)
print(f"KNN准确率: {accuracy_score(y_test, y_pred):.4f}")
print(classification_report(y_test, y_pred))

4.2 不同K值的性能对比

复制代码
# 测试不同k值的性能
k_values = range(1, 31)
train_scores = []
test_scores = []

for k in k_values:
    knn = KNeighborsClassifier(n_neighbors=k)
    knn.fit(X_train_scaled, y_train)
    
    train_scores.append(knn.score(X_train_scaled, y_train))
    test_scores.append(knn.score(X_test_scaled, y_test))

# 绘制对比图
plt.figure(figsize=(10, 6))
plt.plot(k_values, train_scores, label="Train", marker="o")
plt.plot(k_values, test_scores, label="Test", marker="s")
plt.xlabel("K Value")
plt.ylabel("Accuracy")
plt.title("KNN: Effect of K Value")
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

# 找到最优k值
best_k = list(k_values)[np.argmax(test_scores)]
print(f"最优k值: {best_k}, 准确率: {max(test_scores):.4f}")

4.3 不同距离度量对比

复制代码
# 测试不同距离度量
distances = ["euclidean", "manhattan", "minkowski", "chebyshev"]
distance_scores = []

for dist in distances:
    if dist == "minkowski":
        knn = KNeighborsClassifier(n_neighbors=5, metric=dist, p=3)
    else:
        knn = KNeighborsClassifier(n_neighbors=5, metric=dist)
    
    knn.fit(X_train_scaled, y_train)
    score = knn.score(X_test_scaled, y_test)
    distance_scores.append(score)
    print(f"{dist}: {score:.4f}")

# 可视化
plt.figure(figsize=(8, 5))
plt.bar(distances, distance_scores, color=["skyblue", "lightcoral", "lightgreen", "gold"])
plt.ylabel("Accuracy")
plt.title("KNN with Different Distance Metrics")
plt.ylim([0.5, 1.0])
for i, v in enumerate(distance_scores):
    plt.text(i, v + 0.01, f"{v:.3f}", ha="center")
plt.show()

4.4 决策边界可视化

复制代码
# 可视化决策边界
def plot_decision_boundary(X, y, model, title):
    h = 0.02
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                         np.arange(y_min, y_max, h))
    
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    
    plt.figure(figsize=(10, 8))
    plt.contourf(xx, yy, Z, alpha=0.4, cmap=plt.cm.RdYlBu)
    scatter = plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.RdYlBu, 
                         edgecolors="black")
    plt.title(title)
    plt.colorbar(scatter)
    plt.show()

# 不同k值的决策边界
for k in [1, 5, 15, 30]:
    knn = KNeighborsClassifier(n_neighbors=k)
    knn.fit(X_train_scaled, y_train)
    plot_decision_boundary(X_train_scaled, y_train, knn, 
                          f"KNN Decision Boundary (k={k})")

五、实战案例:鸢尾花分类

5.1 加载数据

复制代码
# 加载鸢尾花数据集
iris = load_iris()
X_iris = iris.data
y_iris = iris.target

print(f"特征名称: {iris.feature_names}")
print(f"类别: {iris.target_names}")
print(f"数据形状: {X_iris.shape}")

# 划分数据集
X_train_i, X_test_i, y_train_i, y_test_i = train_test_split(
    X_iris, y_iris, test_size=0.3, random_state=42, stratify=y_iris)

# 标准化
scaler_i = StandardScaler()
X_train_i_scaled = scaler_i.fit_transform(X_train_i)
X_test_i_scaled = scaler_i.transform(X_test_i)

5.2 交叉验证选择最优K

复制代码
# 使用交叉验证选择最优k
k_range = range(1, 31)
cv_scores = []

for k in k_range:
    knn = KNeighborsClassifier(n_neighbors=k)
    scores = cross_val_score(knn, X_train_i_scaled, y_train_i, cv=5)
    cv_scores.append(scores.mean())

# 找到最优k
best_k = list(k_range)[np.argmax(cv_scores)]
print(f"最优k值: {best_k}")
print(f"交叉验证最高分: {max(cv_scores):.4f}")

# 绘制k值与准确率关系
plt.figure(figsize=(10, 6))
plt.plot(k_range, cv_scores, marker="o")
plt.xlabel("K Value")
plt.ylabel("Cross-Validation Accuracy")
plt.title("K Selection using Cross-Validation")
plt.axvline(x=best_k, color="r", linestyle="--", 
           label=f"Best k={best_k}")
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

5.3 模型评估

复制代码
# 使用最优k训练模型
best_knn = KNeighborsClassifier(n_neighbors=best_k)
best_knn.fit(X_train_i_scaled, y_train_i)

# 预测
y_pred_i = best_knn.predict(X_test_i_scaled)

# 评估
print(f"训练集准确率: {best_knn.score(X_train_i_scaled, y_train_i):.4f}")
print(f"测试集准确率: {best_knn.score(X_test_i_scaled, y_test_i):.4f}")
print("\n分类报告:")
print(classification_report(y_test_i, y_pred_i, 
                          target_names=iris.target_names))

# 混淆矩阵
from sklearn.metrics import confusion_matrix
import seaborn as sns

cm = confusion_matrix(y_test_i, y_pred_i)
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues",
            xticklabels=iris.target_names,
            yticklabels=iris.target_names)
plt.xlabel("Predicted")
plt.ylabel("Actual")
plt.title("Confusion Matrix - Iris Classification")
plt.show()

六、KNN回归(KNeighborsRegressor)

6.1 KNN回归原理

KNN不仅可以用于分类,也可以用于回归问题。在回归问题中,KNN通过计算k个最近邻的目标值的平均值(或加权平均值)来预测新样本的值。

6.2 KNN回归实现

复制代码
from sklearn.neighbors import KNeighborsRegressor
from sklearn.datasets import make_regression
from sklearn.metrics import mean_squared_error, r2_score

# 生成回归数据
X_reg, y_reg = make_regression(n_samples=500, n_features=1, noise=20, 
                               random_state=42)
X_train_r, X_test_r, y_train_r, y_test_r = train_test_split(
    X_reg, y_reg, test_size=0.3, random_state=42)

# 标准化
scaler_r = StandardScaler()
X_train_r_scaled = scaler_r.fit_transform(X_train_r)
X_test_r_scaled = scaler_r.transform(X_test_r)

# 创建KNN回归器
knn_reg = KNeighborsRegressor(n_neighbors=5)
knn_reg.fit(X_train_r_scaled, y_train_r)

# 预测
y_pred_r = knn_reg.predict(X_test_r_scaled)

# 评估
print(f"MSE: {mean_squared_error(y_test_r, y_pred_r):.4f}")
print(f"R2: {r2_score(y_test_r, y_pred_r):.4f}")

# 可视化
plt.figure(figsize=(10, 6))
plt.scatter(X_test_r, y_test_r, color="blue", label="Actual", alpha=0.5)
plt.scatter(X_test_r, y_pred_r, color="red", label="Predicted", alpha=0.5)

# 排序以便绘制平滑曲线
sort_idx = np.argsort(X_test_r.ravel())
plt.plot(X_test_r[sort_idx], y_pred_r[sort_idx], "g-", 
         linewidth=2, label="KNN Regression")

plt.xlabel("X")
plt.ylabel("y")
plt.title("KNN Regression")
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

七、KNN算法优化

7.1 KD树(KD-Tree)

KD树是一种对k维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。它通过构建二叉树来减少搜索最近邻时的计算量。

适用场景:低维数据(维度小于20)

复制代码
# 使用KD树
knn_kdtree = KNeighborsClassifier(n_neighbors=5, algorithm="kd_tree")
knn_kdtree.fit(X_train_scaled, y_train)
print(f"KD树准确率: {knn_kdtree.score(X_test_scaled, y_test):.4f}")

7.2 球树(Ball Tree)

球树使用超球体而不是超矩形来划分空间,适用于高维数据。

适用场景:高维数据或数据分布不均匀的情况

复制代码
# 使用球树
knn_balltree = KNeighborsClassifier(n_neighbors=5, algorithm="ball_tree")
knn_balltree.fit(X_train_scaled, y_train)
print(f"球树准确率: {knn_balltree.score(X_test_scaled, y_test):.4f}")

7.3 算法对比

算法 适用维度 时间复杂度 特点
暴力法 任意 O(nd) 简单,低维时可用
KD树 低维(d<20) O(log n) 低维高效
球树 中高维 O(log n) 适合不均匀分布

八、特征缩放的重要性

8.1 为什么需要特征缩放?

KNN算法基于距离计算,如果不同特征的尺度差异很大,尺度大的特征会主导距离计算,导致算法偏向于这些特征。

复制代码
# 演示特征缩放的重要性
from sklearn.datasets import make_classification

# 生成具有不同尺度的数据
X_scale, y_scale = make_classification(n_samples=500, n_features=2, 
                                       n_redundant=0, random_state=42)
# 放大第一个特征
X_scale[:, 0] *= 100

X_train_s, X_test_s, y_train_s, y_test_s = train_test_split(
    X_scale, y_scale, test_size=0.3, random_state=42)

# 未缩放
knn_no_scale = KNeighborsClassifier(n_neighbors=5)
knn_no_scale.fit(X_train_s, y_train_s)
acc_no_scale = knn_no_scale.score(X_test_s, y_test_s)

# 标准化后
scaler_s = StandardScaler()
X_train_s_scaled = scaler_s.fit_transform(X_train_s)
X_test_s_scaled = scaler_s.transform(X_test_s)

knn_scaled = KNeighborsClassifier(n_neighbors=5)
knn_scaled.fit(X_train_s_scaled, y_train_s)
acc_scaled = knn_scaled.score(X_test_s_scaled, y_test_s)

print(f"未缩放准确率: {acc_no_scale:.4f}")
print(f"标准化后准确率: {acc_scaled:.4f}")
print(f"提升: {acc_scaled - acc_no_scale:.4f}")

九、KNN与其他算法对比

9.1 分类算法对比

复制代码
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC

# 定义模型
models = {
    "KNN": KNeighborsClassifier(n_neighbors=5),
    "Logistic Regression": LogisticRegression(max_iter=1000),
    "Decision Tree": DecisionTreeClassifier(max_depth=5),
    "Random Forest": RandomForestClassifier(n_estimators=50),
    "SVM": SVC(kernel="rbf")
}

# 训练和评估
results = []
for name, model in models.items():
    model.fit(X_train_scaled, y_train)
    train_acc = model.score(X_train_scaled, y_train)
    test_acc = model.score(X_test_scaled, y_test)
    results.append({"Model": name, "Train": train_acc, "Test": test_acc})

import pandas as pd
results_df = pd.DataFrame(results)
print(results_df.to_string(index=False))

9.2 可视化对比

复制代码
# 绘制对比图
fig, ax = plt.subplots(figsize=(12, 6))

x = np.arange(len(results_df))
width = 0.35

bars1 = ax.bar(x - width/2, results_df["Train"], width, 
               label="Train", color="skyblue")
bars2 = ax.bar(x + width/2, results_df["Test"], width, 
               label="Test", color="lightcoral")

ax.set_ylabel("Accuracy")
ax.set_title("Model Comparison")
ax.set_xticks(x)
ax.set_xticklabels(results_df["Model"], rotation=45, ha="right")
ax.legend()
ax.set_ylim([0.5, 1.0])
ax.grid(True, alpha=0.3, axis="y")

# 添加数值标签
for bars in [bars1, bars2]:
    for bar in bars:
        height = bar.get_height()
        ax.annotate(f"{height:.3f}",
                   xy=(bar.get_x() + bar.get_width() / 2, height),
                   xytext=(0, 3), textcoords="offset points",
                   ha="center", va="bottom", fontsize=8)

plt.tight_layout()
plt.show()

十、总结与学习建议

10.1 核心要点回顾

  • 算法思想:通过计算样本间的距离,选择最近的k个邻居进行投票或平均
  • 距离度量:欧氏距离、曼哈顿距离、闵可夫斯基距离等
  • K值选择:通过交叉验证选择最优k值,避免过拟合或欠拟合
  • 特征缩放:必须进行标准化或归一化,避免尺度影响
  • 算法优化:使用KD树或球树加速最近邻搜索

10.2 适用场景

  • 数据量小:训练样本较少时
  • 数据分布复杂:非线性可分数据
  • 解释性要求高:算法简单,易于理解
  • 多分类问题:天然支持多分类

10.3 不适用场景

  • 大规模数据:计算和存储开销大
  • 高维稀疏数据:维度灾难问题
  • 实时性要求高:预测速度慢
  • 特征维度高:距离度量失效

10.4 进阶学习

  • 加权KNN:根据距离给邻居赋予不同权重
  • 距离度量学习:学习最优的距离度量方法
  • 近似最近邻:使用局部敏感哈希(LSH)等算法加速
  • 集成KNN:结合多个KNN模型提升性能

下一篇预告:【第12篇】朴素贝叶斯分类器:基于概率的分类方法


本文为系列第11篇,深入讲解了KNN算法的原理、距离度量方法和实际应用。有任何问题欢迎在评论区交流!

标签:#KNN #K近邻 #机器学习 #分类算法 #Python #人工智能 #教程

相关推荐
测试员周周1 小时前
【AI测试系统】第5篇:AI 编码工具抛硬币?我们用 LangGraph 做了个“确定性+AI”的测试系统(附自愈架构)
人工智能·python·功能测试·测试工具·架构·langchain·单元测试
初学大模型1 小时前
与机器心智的对话:论人机交互中提问的精确性与描述的详尽性
人工智能
AI木马人1 小时前
18.人工智能实战:LoRA 微调后效果不升反降?从数据清洗到训练参数的完整排查方案
人工智能·深度学习·机器学习
davedeveloper1 小时前
ReAct 论文深度解读:让大模型学会“边想边做“
人工智能
2601_956414141 小时前
2026年5月PCB厂家推荐:TOP5榜产品应对5G基站散热挑战
大数据·人工智能·5g
生成论实验室1 小时前
《源·觉·知·行·事·物:生成论视域下的统一认知语法》导论:在破碎的世界寻找统一语法
人工智能·科技·算法·架构·创业创新
zhutier-1 小时前
国科大交叉学院二次选拔笔试2025 AI方向基础知识整理存档
人工智能
嵌入式老牛2 小时前
液晶段码(米/日字格)识别—预处理
人工智能·opencv·计算机视觉
comcoo2 小时前
本地 AI 智能体 OpenClaw 部署实操教程
人工智能·openclaw安装包·龙虾ai·open claw部署