KNN算法

1、KNN算法基本介绍

 KNN(K-Nearest Neighbors,K - 近邻)是一种简单、直观、无参数的惰性学习算法,核心思想是认为 “物以类聚、人以群分”,通过待预测样本与训练集中 “最近邻” 样本的相似度,推断其类别(分类任务)或数值(回归任务)。它无需训练模型参数,仅在预测时计算距离,广泛应用于分类、回归、推荐系统等场景。

 

1.1、核心原理(3 步理解)

KNN 的工作流程极其简洁,核心分为 “算距离→找邻居→做预测” 三步:

1、计算距离:衡量样本相似度

KNN 通过距离度量判断样本间的 “远近”(相似度),距离越近,相似度越高。

常用距离公式有

  • 欧氏距离(最常用),计算公式为对应维度差值平方和,开平方根

image

  • 曼哈顿距离(城市街区距离),计算公式为对应维度差值的绝对值之和。如[1,2]、[3,5],值则为5
  • 切比雪夫距离,计算公式为对应维度差值的绝对值中的最大值。如[1,2]、[3,5],值则为3
  • 闵可夫斯基距离(闵氏距离),这并不是一种距离度量方式,只是对多种距离度量公式的概括性的表达

 

2、选择 K 值:确定 “参考邻居数量”

K 是算法唯一的超参数,代表 “预测时参考的最近邻样本个数”。K 必须是正整数,且通常取奇数(避免分类时投票平局,如 K=4 时 2:2 投票)。

K 的大小直接影响模型拟合效果:

  • K 过小(如 K=1):过度依赖单个样本,易受噪声 / 异常值影响,导致过拟合;
  • K 过大(如 K≈训练集样本数):过度平滑,忽略局部特征,导致欠拟合;
  • 最优 K:通过交叉验证(如 5 折交叉验证)+ 网格搜索(测试 K=3、5、7...31)选择。

 

3、投票 / 平均:生成预测结果

  • 分类任务:对 K 个最近邻样本的类别进行 “多数投票”,得票最多的类别作为预测结果。例:K=5 时,最近邻样本类别为 [猫、猫、狗、猫、兔],预测结果为 “猫”(3 票胜出)。
  • 回归任务:对 K 个最近邻样本的数值取 “平均值”(或加权平均值),作为预测结果。例:K=3 时,最近邻样本房价为 [100 万、120 万、110 万],预测房价 =(100+120+110)/3=110 万。

 

1.2、KNN 的核心特性(优缺点)

优点:简单易用,门槛低

  1. 无训练过程:属于 “惰性学习”,无需提前训练模型,直接使用训练数据预测,开发成本低;
  2. 适用于多分类 / 回归:天然支持多分类(无需额外改造),也可用于回归任务;
  3. 对异常值不敏感(K 适中时):K 个邻居的投票 / 平均会稀释单个异常值的影响;
  4. 高维数据有效:配合余弦相似度等度量,可处理文本、图像等高维场景。

缺点:性能与数据敏感

  1. 预测速度慢:每次预测需计算待预测样本与所有训练样本的距离,训练集越大(如百万级样本),速度越慢(时间复杂度 O (n));
  2. 对高维数据 “维数灾难” 敏感:特征维度过高时,距离度量的区分度会下降,模型效果变差;
  3. 对数据缩放敏感:未归一化的数据会导致距离计算偏差(如特征 A 范围 [0,1000],特征 B 范围 [0,1],A 的权重会被过度放大);
  4. 内存消耗大:需存储全部训练数据,训练集越大,内存占用越高。

 

1.3、适用场景与不适用场景

适用场景

  1. 小数据集任务:训练集样本数少(如万级以内),预测速度可接受;
  2. 简单分类任务:如垃圾邮件识别、用户性别预测、手写数字识别(MNIST 数据集);
  3. 推荐系统:基于用户 / 物品的协同过滤(如 “你可能喜欢” 的商品推荐,本质是找相似用户 / 物品);
  4. 回归预测:如房价预测、温度预测(对局部特征敏感的场景)。

不适用场景

  1. 超大规模数据集:百万级以上训练集,预测速度极慢;
  2. 高维数据(无优化):如原始图像(维度 = 像素数),未降维时距离度量失效;
  3. 实时预测场景:如高频交易、实时推荐,对预测延迟要求极高的场景;
  4. 数据不平衡场景:如正样本占 90%、负样本占 10%,KNN 易偏向多数类。

 

2、KNN算法API(scikit-learn)

scikit-learn(简称 sklearn)是 Python 生态中最成熟、最流行的开源机器学习库,基于 numpy(数值计算)和 scipy(科学计算)构建,专注于传统机器学习算法的工程化实现,提供了从数据预处理到模型部署的全流程工具链,是入门机器学习、快速验证算法思路、甚至工业级应用的首选工具。scikit-learn 是开源免费的,基于 BSD 许可证,可商业使用。

scikit-learn 的安装参考:https://www.cnblogs.com/wenxuehai/p/18542054#_label2_0

 

2.1、分类API

KNN 分类 API如下,可参考官网api:https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html

class sklearn.neighbors.KNeighborsClassifier(n_neighbors=5, *, weights='uniform', algorithm='auto', leaf_size=30, p=2, metric='minkowski', metric_params=None, n_jobs=None)

 示例代码如下:

"""
KNN算法介绍(K Nearest Neighbors), K近邻算法
    原理:
        基于 欧式距离(或者其它距离计算方式)计算 测试集 和 每个训练集之间的距离, 然后根据距离升序排列, 找到最近的K个样本.
        基于K个样本投票, 票数多的就作为最终预测结果 -> 分类问题.
        基于K个样本计算平均值, 作为最终预测结果 -> 回归问题.
    实现思路:
        1. 分类问题
            适用于: 有特征, 有标签, 且标签是不连续的(离散的)
        2. 回归问题.
            适用于: 有特征, 有标签, 且标签是连续的.
    KNN算法, 分类问题思路如下:
        1. 计算测试集和每个训练的样本之间的 距离.
        2. 基于距离进行升序排列.
        3. 找到最近的K个样本.
        4. K个样本进行投票.
        5. 票数多的结果, 作为最终的预测结果.
    代码实现思路:
        1. 导包.
        2. 准备数据集(测试集 和 训练集)
        3. 创建(KNN 分类模型)模型对象.
        4. 模型训练.
        5. 模型预测.
"""

# 1. 导包.
from sklearn.neighbors import KNeighborsClassifier  # 分类

# 2. 准备数据集(测试集 和 训练集)
x_train = [[0], [1], [2], [3]]      # 训练集的特征数据, 因为特征可以有多个特征, 所以是一个二维数组,如[[1,1], [1,4], [2,1], [3,9]] 
y_train = [0, 0, 1, 1]              # 训练集的标签数据, 因为标签是离散的, 所以是一个一维数组
x_test = [[5]]                      # 测试集的特征数据

# 3. 创建(KNN 分类模型)模型对象.
# estimator: 估计器, 模型对象, 也可以用变量名 model做接收.
estimator = KNeighborsClassifier(n_neighbors=3)

# 4. 模型训练
# 传入: 训练集的特征数据, 训练集的标签数据
estimator.fit(x_train, y_train)

# 5. 模型预测.
# 传入: 测试集的特征数据, 获取到: 预测结果(测试集的标签, y_test)
y_pre = estimator.predict(x_test)

# 6. 打印预测结果.
print(f'预测值为: {y_pre}')

上面执行结果如下:

image

 

2.2、回归API

KNN 回归 API如下,可参考官网api:https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsRegressor.html

class sklearn.neighbors.KNeighborsRegressor(n_neighbors=5, *, weights='uniform', algorithm='auto', leaf_size=30, p=2, metric='minkowski', metric_params=None, n_jobs=None)

 示例代码如下:

"""
KNN算法介绍(K Nearest Neighbors), K近邻算法
    原理:
        基于 欧式距离(或者其它距离计算方式)计算 测试集 和 每个训练集之间的距离, 然后根据距离升序排列, 找到最近的K个样本.
        基于K个样本投票, 票数多的就作为最终预测结果 -> 分类问题.
        基于K个样本计算平均值, 作为最终预测结果 -> 回归问题.
    实现思路:
        1. 分类问题
            适用于: 有特征, 有标签, 且标签是不连续的(离散的)
        2. 回归问题.
            适用于: 有特征, 有标签, 且标签是连续的.
    KNN算法, 回归问题思路如下:
        1. 计算测试集和每个训练的样本之间的 距离.
        2. 基于距离进行升序排列.
        3. 找到最近的K个样本.
        4. 基于K个样本的标签值, 计算平均值.
        5. 将上述计算出来的平均值, 作为最终的预测结果.
    代码实现思路:
        1. 导包.
        2. 准备数据集(测试集 和 训练集)
        3. 创建(KNN 回归模型)模型对象.
        4. 模型训练.
        5. 模型预测.
    总结:
        K值过小, 容易受到异常值的影响, 且会导致模型学到大量的"脏的特征", 导致出现: 过拟合.
        K值过大, 模型会变得简单, 容易发生: 欠拟合.
"""

# 1. 导包.
from sklearn.neighbors import KNeighborsRegressor       # KNN算法的 回归模型

# 2. 准备数据集(测试集 和 训练集)
x_train = [[0, 0, 1], [1, 1, 0], [3, 10, 10], [4, 11, 12]]      # 训练集的特征数据, 因为特征可以有多个特征, 所以是一个二维数组
y_train = [0.1, 0.2, 0.3, 0.4]                                  # 训练集的标签数据, 因为标签是连续的, 所以是一个一维数组
x_test = [[3, 11, 10]]                                          # 测试集的特征数据

# 3. 创建(KNN 回归模型)模型对象.
estimator = KNeighborsRegressor(n_neighbors=2)

# 4. 模型训练.
estimator.fit(x_train, y_train)

# 5. 模型预测.
y_pre = estimator.predict(x_test)

# 6. 打印预测结果.
print(f'预测值为: {y_pre}')

上面执行结果如下:

image

 

3、常见距离计算方法

KNN(K - 近邻)算法的核心是通过距离度量衡量样本间的相似度(距离越近,相似度越高),不同距离计算方法适用于不同数据类型(连续型、高维、稀疏数据等)。

 

3.1、欧式距离(Euclidean Distance)

欧式距离是最经典的距离度量,可以理解为两个点在空间中的距离。

image

计算公式为:

image(即对应维度差值平方和,然后开平方根)

举个例子:AB 两点为 [[1,1],[2,2]] ,计算 AB 距离,经计算得: AB = √(2−1)^2 +(2−1)^2 = √ 2  = 1.414

 

  • 适用场景:特征为连续型数据(如身高、体重、房价等),且各特征量级一致(或已标准化 / 归一化)。
  • 特点:对异常值敏感(平方项会放大差异),计算简单直观。(异常值不一定欧式距离就远,有可能更近(比如某些维度数据很近,但某些维度数据明显异常,但是通过欧式距离算出来比其他正常值还小),导致被识别为近邻,影响预测结果)

典型案例:

  • 基于用户年龄、消费金额预测用户偏好;
  • 鸢尾花、葡萄酒等经典数据集的分类任务(数据分布均匀,无异常)。

 

3.2、曼哈顿距离(Manhattan Distance)

也称为城市街区距离。

image

 计算公式为:

image(对应维度差值的绝对值之和)

举个例子: AB 点为[ [1,1], [2,2]] , 计算AB 曼哈顿距离 经计算得: AB =|2−1|+ ⌈2−1⌉= 2

 

  • 适用场景:高维数据(如文本 TF-IDF 特征、用户行为向量,维度 > 100)、稀疏数据(部分特征值为 0,仅关注非零特征的差异),或数据中存在异常值的场景(如用户行为数据)。
  • 特点:计算量小,对异常值不敏感(无平方项),但丢失了 “直线距离” 的直观性。

典型案例:

  • 高维用户行为数据(浏览、收藏、购买等多维度特征)的相似用户推荐;
  • 存在极端值的传感器数据(如温度偶尔飙升)的分类任务。

 

3.3、切比雪夫距离(Chebyshev Distance)

在国际象棋中,国王可以直行、横行、斜行,所以国王走一步可以移动到相邻8个方格中的任意一个。 国王从格子(x1,y1)走到格子(x2,y2)最少需要多少步?这个距离就叫切比雪夫距离。

image

 计算公式为:

image(即对应维度差值的绝对值中的最大值)

举个例子: AB 点为 [ [1,1], [2,3] ] , 计算AB曼哈顿距离,经计算得: AB =max(|2−1|, ⌈3−1⌉) = 2

 

  • 适用场景:需要关注 “最大差异特征” 的场景(如多指标质量检测,只要有一个指标不达标则判定为不合格)。
  • 特点:只关注最显著的差异,忽略其他维度的小差异。

 

3.4、闵可夫斯基距离(Minkowski Distance)

也称为闵氏距离,闵氏距离不是一种新的距离的度量方式,而是欧氏距离、曼哈顿距离、切比雪夫距离的统一泛化表达,通过参数 p 控制距离类型。

定义为:

image

其中p是一个变参数,根据 p 的不同,闵氏距离可表示某一类种的距离:

  • 当 p=1 时,就是曼哈顿距离;
  • 当 p=2 时,就是欧氏距离;
  • 当 p→∞ 时,就是切比雪夫距离

 

4、特征预处理

特征预处理是特征工程的一部分,特征预处理的两种核心方法 —— 归一化(Min-Max Scaling)和标准化(Standard Scaling),它们的核心目的都是消除特征的量级差异、统一数据尺度,避免因特征范围不一致导致模型误判或性能下降。

比如,假设有两个特征:「身高(m)」范围 [1.5, 2.0],「体重(kg)」范围 [40, 100]。在 KNN 的欧氏距离计算中,经平方值放大后,身高的差异(如 0.5m)权重远小于体重的差异(如 60kg),导致模型过度关注 “体重” 而忽略 “身高”—— 这就是特征量级不一致的危害。归一化 / 标准化的作用就是将所有特征映射到同一尺度,让每个特征对模型的影响更均衡。

 

4.1、归一化(Min-Max Scaling,缩放到固定区间)

归一化就是通过对原始数据进行变换把数据映射到某个数值区间(如 [mi, mx]),默认为 [0,1] 。由于归一化方法非常容易受异常点影响,所以一般比较少用。

转换到 [0, 1] 区间的公式为:

image

如果转换区间不是 [0, 1] 而是其他值,如 [mi, mx],在上面公式基础上继续以下计算:

image

示例:假设特征「体重(kg)」的原始数据为 [40, 60, 80, 100]: min(x)=40 , max(x)=100 ; 对 60kg 归一化: (60−40)/(100−40)=20/60≈0.333 ; 对 100kg 归一化: (100−40)/(100−40)=1 ; 以此类推计算出结果:[0, 0.333, 0.667, 1]。

 

核心特点:

  • 优点:直观、计算简单,结果范围固定,适合对数据范围有明确要求的场景;
  • 缺点:对异常值极其敏感(如体重中出现 200kg 异常值,会将所有正常数据压缩到接近 0 的区间);
  • 适用场景:最大值与最小值非常容易受异常点影响,只适合无明显异常值、需要固定数据范围、传统精确小数据场景数据,一般比较少用。

 

4.1.1、归一化API

API 如下,可参考官网:https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html

class sklearn.preprocessing.MinMaxScaler(feature_range=(0, 1), *, copy=True, clip=False)   # feature_range 缩放区间

示例代码:

# 导包
from sklearn.preprocessing import MinMaxScaler  # 归一化对象

# 1. 准备数据集(归一化之前的原数据).
x_train = [[90, 2, 10, 40], [60, 4, 15, 45], [75, 3, 13, 46]]

# 2. 创建归一化对象.
# 参数feature_range 表示生成范围, 默认为: 0, 1  如果就是这个区间, 则参数可以省略不写.
transfer = MinMaxScaler()
# transfer = MinMaxScaler(feature_range=(3, 5))    # 可以指定区间

# 3. 对原数据集进行归一化操作.
x_train_new = transfer.fit_transform(x_train)

# 4. 打印处理后的数据.
print('归一化后的数据集为: \n')
print(x_train_new)

执行后输出结果如下:

image

 

4.2、标准化(Standard Scaling/Z-Score 标准化,缩放到正态分布)

标准化是将特征值映射到均值为 0、标准差为 1 的正态分布(也叫 Z-Score 转换)。该方法对异常值不敏感,比较常用。

公式:image

  • x :原始特征值
  • x ′ :标准化后的特征值
  • μ (mu):该特征的均值
  • σ (sigma):该特征的标准差,公式为image

示例,特征「体重(kg)」数据 [40, 60, 80, 100]: 均值 μ=(40+60+80+100)/4=70 ; 标准差 σ≈22.36; 对 60kg 标准化: (60−70)/22.36≈−0.447 ; 对 100kg 标准化: (100−70)/25.82≈1.341; 结果:[[-1.34164079], [-0.4472136 ], [ 0.4472136 ], [ 1.34164079]](均值≈0,标准差≈1)。

 

核心特点:

  • 优点:对异常值稳健(异常值的影响被标准差稀释,不会过度压缩正常数据)
  • 缺点:结果范围不固定(通常在 [-3, 3] 之间,因正态分布 99.7% 数据在 ±3σ 内);
  • 适用场景:数据近似正态分布、存在异常值、模型对数据分布有要求的场景(如 KNN、SVM、线性回归、逻辑回归等大多数机器学习模型)。

 

4.2.1、标准化API

API 如下,可参考官网:https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html#

class sklearn.preprocessing.StandardScaler(*, copy=True, with_mean=True, with_std=True)[source]#

使用代码示例:

# 导包
from sklearn.preprocessing import StandardScaler  # 标准化对象

# 1. 准备数据集(标准化之前的原数据).
x_train = [[90, 2, 10, 40], [60, 4, 15, 45], [75, 3, 13, 46]]

# 2. 创建标准化对象.
transfer = StandardScaler()

# 3. 对原数据集进行标准化操作.
x_train_new = transfer.fit_transform(x_train)

# 4. 打印处理后的数据.
print('标准化后的数据集为: \n')
print(x_train_new)

# 5. 打印数据集的均值和方差.
print(f'数据集的均值为: {transfer.mean_}')
print(f'数据集的方差为: {transfer.var_}')
print(f'数据集的标准差为: {transfer.scale_}')

执行后输出结果如下:

image

 

posted @ 2025-11-25 15:38  wenxuehai  阅读(20)  评论(0)    收藏  举报
//右下角添加目录