机器学习经典算法:K近邻算法

K近邻算法简介

K近邻算法(KNN)是一个经典算法,分类 和 回归任务都支持,给定一个样本,根据与训练集样本的距离最近的 $K$ 个样本,来判断该样本的类别。

核心概念

  • 解决问题:分类问题、回归问题
  • 算法思想:若一个样本在特征空间中的 $K$ 个最相似的样本大多数属于某一个类别,则该样本也属于这个类别。
  • 如何找?:距离度量(api底层自动计算),默认欧氏距离

怎么预测

  1. 先算未知样本和所有训练样本的距离,并按从近到远排序。
  2. 取最近的 $K$ 个邻居。
  3. 如果是分类,就看这 $K$ 个邻居里哪个类别人最多,投票决定结果。
  4. 如果是回归,就把这 $K$ 个邻居的目标值取平均,作为预测结果。

数据集划分

将数据切分成了四块,是进行任何监督学习(如KNN、线性回归等)的标准步骤.

数据集划分示意图
  • x_train(训练集特征):训练时输入给模型的特征数据,KNN 会基于它和新样本计算距离,通常占数据集的大部分。
  • y_train(训练集标签)x_train 对应的标准答案,KNN 会保存特征与标签的对应关系。
  • x_test(测试集特征):训练完成后用于预测的输入数据,相当于考题,训练阶段不能提前使用。
  • y_test(测试集标签)x_test 对应的真实答案,用来和预测结果比对并计算准确率等评估指标。

KNN算法API

sklearn框架

scikit-learn 是一个基于 Python 的机器学习框架,它提供了丰富的算法实现,以及方便的工具函数。

官网地址:https://scikit-learn.org/stable/

安装:

pip install scikit-learn

导包:

from sklearn import datasets

分类任务

# 1.导包  此处导入knn分类模型
from sklearn.neighbors import KNeighborsClassifier

# 2.准备数据(此处我们模拟数据)
# 先模拟特征数据x
x_train = [[0], [1], [2], [3]]
x_test = [[4]]
# 再模拟标签数据y (假设2分类 1:垃圾邮件,0正常邮件)
y_train = [0, 0, 0, 1] # 此时0和1是标签索引
# TODO 3.需求: 预测x_test中的4属于垃圾邮件还是正常邮件
# todo 3.1 创建分类模型
# 注意: 如果两个邻居,一个是垃圾,一个是正常邮件,底层根据标签索引选择小的
# 初始化了一个 K近邻(KNN)分类器模型,参数 n_neighbors(即邻居数量 K 值)设置为了 3
# n_neighbors=3:这是 KNN 算法中最关键的参数,代表在预测新数据点时,
# 算法会去寻找距离最近的 3个 训练样本(邻居)来进行投票决策
knn_model = KNeighborsClassifier(n_neighbors=3)
# todo 3.2 模型训练
knn_model.fit(x_train, y_train)
# todo 3.3 模型预测
y_pred = knn_model.predict(x_test)
# todo 3.4 打印预测结果
# 4距离 321最近,这三个y轴中两个是0,所以预测结果是0
print(f"分类预测结果为:{y_pred}") # [0]

回归任务

# 1.导包  此处导入knn回归模型
from sklearn.neighbors import KNeighborsRegressor

# 2.准备数据(此处我们模拟数据)
# 先模拟特征数据x
x_train = [[0], [1], [2], [3]]
x_test = [[4]]
# 再模拟标签数据y (房价0->70w,1->80w,2->100w,3->110w)
y_train = [70, 80, 100, 110] # 此时就是房价
# TODO 3.需求: 预测x_test中的4对应房价
# todo 3.1 创建分类模型
knn_model = KNeighborsRegressor(n_neighbors=3)
# todo 3.2 模型训练
knn_model.fit(x_train, y_train)
# todo 3.3 模型预测
y_pred = knn_model.predict(x_test)
# todo 3.4 打印预测结果
print(f"分类预测结果为:{y_pred}") # [96.66666667]

距离度量

距离度量(distance measure) - 常见距离公式

距离类型 核心思想 关键参数 对异常值敏感性 适用场景
欧氏距离 直线最短距离 $p=2$ 连续数值、低维空间(如KNN)
曼哈顿距离 各维度差异绝对值之和 $p=1$ 高维稀疏数据、离散特征
切比雪夫距离 仅关注最大维度差异 $p \to \infty$ 极高 极值主导场景(如路径规划)
闵可夫斯基距离 通过 $p$ 灵活调整距离性质 任意 $p$ 依赖 $p$ 需平衡不同维度差异的通用场景
什么时候更适合欧氏距离和曼哈顿距离?

可以先把它们想成两种“量距离”的方式:

  • 欧氏距离:像拿尺子直接量两点之间的直线距离。
  • 曼哈顿距离:像在城市街区走路,只能横着走、竖着走,最后把每一段路加起来。

所以一般可以这样理解:

  • 连续数值、低维空间:更常用欧氏距离,因为它更符合“实际远近”的直觉,KNN 里最常见的默认选择就是它。
  • 高维稀疏数据、离散特征:更常用曼哈顿距离,因为它按维度分别累计差异,不容易被某一维的极端偏差带得太厉害。

一句话理解:想看“直线有多近”,偏向欧氏距离;想看“每一维总共差多少”,偏向曼哈顿距离。

图片
常见距离的计算方法(公式和例子)

欧氏距离的计算方法

欧氏距离其实就是两点之间的直线距离。在二维平面里,它本质上就是勾股定理。

假设有两个点:$A(x_1, y_1)$ 和 $B(x_2, y_2)$。

先看两个点在两个方向上差多少:

  • 横向差值:$x_1 - x_2$
  • 纵向差值:$y_1 - y_2$

再把这两个差值当成直角三角形的两条直角边,套用勾股定理,就能得到两点之间的直线距离:

$$ d(A, B) = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2} $$

例如点 $A(1, 2)$ 和点 $B(4, 6)$:

  • 横向差值是 3
  • 纵向差值是 4

所以距离就是:

$$ \sqrt{3^2 + 4^2} = 5 $$

因此,欧氏距离你可以直接理解成:先求“横着差多少、竖着差多少”,再用勾股定理算直线距离。

如果推广到 $n$ 维空间,公式写成:

$$ d(x, y) = \sqrt{\sum_{i=1}^{n}(x_i - y_i)^2} $$

在 KNN 里,欧氏距离很常用,但它会受到量纲影响,所以不同特征的取值范围差别很大时,通常要先做归一化或标准化。

曼哈顿距离的计算方法

曼哈顿距离其实很简单,就是看两个点在每个坐标轴上分别差多少,再把这些差值的绝对值加起来。

假设有两个点:$A(x_1, y_1)$ 和 $B(x_2, y_2)$

那么二维情况下:

$$ d(A, B) = |x_1 - x_2| + |y_1 - y_2| $$

例如点 $A(1,2)$ 和点 $B(4,6)$:

  • x 轴差值:$|1 - 4| = 3$
  • y 轴差值:$|2 - 6| = 4$

所以曼哈顿距离就是:

$$ 3 + 4 = 7 $$

因此,曼哈顿距离你可以直接理解成:不走直线,只按横着和竖着分别走,最后把路程加起来。

了解切比雪夫距离的计算方法

切比雪夫距离(Chebyshev Distance)可以理解为在所有坐标轴的差值中,只取那个最大的差值作为距离。比如点 $A(1,2)$ 和点 $B(4,6)$,横向差 $3$,纵向差 $4$,因为 $4 > 3$,所以它们的切比雪夫距离就是 $4$。它通常用在只关心最大偏差的场景(比如国王在国际象棋棋盘上走步)。

了解闵可夫斯基距离的计算方法

闵可夫斯基距离(Minkowski Distance)并不是一种新的距离,而是一个通用的距离公式模板。它包含一个参数 $p$:当 $p=1$ 时,它就变成了曼哈顿距离;当 $p=2$ 时,它就变成了欧氏距离;当 $p$ 趋于无穷大时,它就变成了切比雪夫距离。所以它其实是前几种距离的“老大哥”。

特征预处理

数据归一化

数据归一化通过对原始数据进行变换,把数据映射到 [mi, mx] 区间内(默认区间为 $[0, 1]$)。

特点与应用场景:

  • 异常值影响:如果数据中出现异常点,会直接影响最大值和最小值,从而导致最终的缩放结果发生明显改变。
  • 应用场景:最大值与最小值非常容易受异常点影响,鲁棒性较差,只适合传统精确小数据场景

归一化
归一化公式和手算示例

计算公式

  1. 第一步:线性缩放到 [0, 1] 区间

    $$ X' = \frac{x - min}{max - min} $$

    (分母为「最大值 - 最小值」,例:max=20、min=3 时,分母为 $20-3$)

  2. 第二步:映射到目标区间 [mi, mx]

    $$ X'' = X' \times (mx - mi) + mi $$

计算示例

原始特征数据
特征1 特征2 特征3 特征4
90 2 10 40
60 4 15 45
75 3 13 46
第一步计算过程
特征1 特征2 特征3 特征4
$\frac{90-60}{90-60}$ $\frac{2-2}{4-2}$ $\frac{10-10}{15-10}$ $\frac{40-40}{46-40}$
$\frac{60-60}{90-60}$ $\frac{4-2}{4-2}$ $\frac{15-10}{15-10}$ $\frac{45-40}{46-40}$
$\frac{75-60}{90-60}$ $\frac{3-2}{4-2}$ $\frac{13-10}{15-10}$ $\frac{46-40}{46-40}$
归一化结果([0, 1] 区间)
特征1 特征2 特征3 特征4
1.0 0.0 0.0 0.0
0.0 1.0 1.0 0.83
0.5 0.5 0.6 1.0

注:上表为第一步计算结果;当目标区间为默认的 [0,1] 时,第二步运算为 $X' \times (1-0) + 0$,结果与第一步一致。

数据归一化 API

  1. sklearn.preprocessing.MinMaxScaler(feature_range=(0,1))
  2. fit_transform(X):将特征进行归一化缩放

代码示例:

# 1.导包  此处导入归一化模型
from sklearn.preprocessing import MinMaxScaler

# 2.准备数据(模拟数据)
x_train = [
    [90, 2, 10, 40],
    [60, 4, 15, 45],
    [75, 3, 13, 46]
]
# TODO 3.需求: 在模型训练特征进行规范化数据
# todo 3.1 创建归一化模型
model = MinMaxScaler()
# todo 3.2 规范化数据
new_x_train = model.fit_transform(x_train)
# todo 3.3 打印结果
print(new_x_train)

打印:

[[1.         0.         0.         0.        ]
 [0.         1.         1.         0.83333333]
 [0.5        0.5        0.6        1.        ]]

数据标准化

使用居多

数据标准化通过对原始数据进行标准化,转换为均值为0、标准差为1的标准正态分布的数据。

通俗解释标准化

通俗解释:数据标准化到底在做什么
做AI模型时,我们经常会遇到不同特征的数值尺度差很大的情况。 比如预测房价:“房间数量”是 2-5 的个位数,“房屋面积”是 50-200 的两位数,“房屋总价”是几十万到几百万的大数。 数值差这么大,模型会误以为“数值越大的特征越重要”,训练时容易跑偏、收敛慢。

数据标准化,就是给所有特征“统一度量衡”,把它们都拉到同一个标准尺度上: 转换后,每一列特征的平均值 = 0,数据的分散程度(标准差) = 1


用大白话拆解两步计算

  1. 第一步:平移归零 每个数据都减去这一列的平均值。 减完之后:比平均水平高的数变成正数,比平均低的变成负数,整列数据的中心点刚好落在 0 上。

  2. 第二步:缩放统一 把上面的结果,再除以这一列的“标准差”(可以理解成数据的“波动幅度/分散程度”)。 除完之后:不管原来的数据是几十、几百还是几万,最终的波动幅度都被统一成 1。


举个生活化的例子
班里两门考试:语文满分 100,分数集中在 60-90;数学满分 150,分数集中在 90-130。 直接拿原始分数对比不公平,标准化就相当于把两门课都换成“相对排名分”:

  • 以全班平均分为 0 基准,高于平均是正、低于平均是负
  • 统一波动幅度,让两门课的“分数差距”权重一样 这样两个科目的成绩,就能公平地放在一起供模型参考了。

一句话总结 标准化的核心作用:消除不同特征之间的数值量级差异,让模型公平对待每个特征,训练更稳定、收敛更快

特点与应用场景:

  • 异常值影响:如果出现异常点,由于具有一定数据量,少量的异常点对于平均值的影响并不大,因此鲁棒性较强。
  • 应用场景:适合现代嘈杂大数据场景。(实际开发中以后基本就是用它了)

计算公式
$$ X' = \frac{x - \text{mean}}{\sigma} $$
  • mean特征的平均值,可以理解成这一列数据的“中心位置”。标准化时先减去它,就是先把整列数据平移到以 0 为中心,方便比较每个样本离平均水平有多远。
  • $\boldsymbol{\sigma}$:特征的标准差,用来表示这一列数据有多分散。标准化时再除以它,就是把不同特征拉到相近的尺度上,避免某一列因为数值范围太大而显得更重要。
数据标准化 API

  1. sklearn.preprocessing.StandardScaler()
  2. fit_transform(X):将特征进行标准化缩放

代码示例:

# 1.导包  此处导入归一化模型
from sklearn.preprocessing import StandardScaler

# 2.准备数据(模拟数据)
x_train = [
    [90, 2, 10, 40],
    [60, 4, 15, 45],
    [75, 3, 13, 46]
]
# TODO 3.需求: 在模型训练特征进行规范化数据
# todo 3.1 创建归一化模型
model = StandardScaler()
# todo 3.2 规范化数据
new_x_train = model.fit_transform(x_train)
# todo 3.3 打印结果
print(new_x_train)

打印:

[[ 1.22474487 -1.22474487 -1.29777137 -1.3970014 ]
 [-1.22474487  1.22474487  1.13554995  0.50800051]
 [ 0.          0.          0.16222142  0.88900089]]

数学扩展

$X'$ 在数学和机器学习的公式中,$X'$(读作 “X prime”)右侧的单引号 ' 通常表示 “变换后的变量”“新变量”

具体含义如下:

  1. 区分原始数据与处理后数据

    • $x$(小写):代表原始数据(还没处理过的数值)。
    • $X'$(带单引号):代表经过第一步计算后得到的中间结果(也就是归一化到 [0, 1] 之间的数值)。
    • $X''$(带双引号):代表经过第二步计算后得到的最终结果(映射到任意区间 [mi, mx] 的数值)。
  2. 为什么不用新字母(比如 Y)? 使用 $X'$ 而不是 $Y$,是为了强调这个新变量仍然属于同一个特征 X,只是它的数值状态发生了变化(被缩放或平移了)。这是一种表示“衍生关系”的常用记号。

公式解读 这两个公式是 数据归一化(Min-Max Normalization) 的核心计算步骤,用于将原始特征值线性映射到指定区间(如 [0,1])。x 右侧的点(·)在数学中表示乘法运算,即“乘以”的意思。

第一个公式:

$$ X' = \frac{x - \min}{\max - \min} $$
  • 作用:将原始数据 x 缩放到 [0, 1] 区间。
  • 含义:
    • $x$:原始数据点;
    • $min$:该特征列的最小值;
    • $max$:该特征列的最大值;
    • $X'$:归一化后的中间值,范围在 [0, 1]。

第二个公式:

$$X'' = X' * (mx - mi) + mi$$
  • 作用:将已归一化到 [0,1] 的数据,进一步映射到任意目标区间 [mi, mx]。
  • 含义:
    • $X'$:第一步得到的 [0,1] 区间值;
    • $mx$:目标区间的最大值;
    • $mi$:目标区间的最小值;
    • $X''$:最终映射到 [mi, mx] 的结果。