KNN算法
KNN算法思想
KNN简介
- 定义: K-近邻算法(K-Nearest Neighbor, 简称KNN)是一种基于实例的学习,通过测量不同特征值之间的距离进行分类或回归
- 核心思想: 如果一个样本在特征空间中的k个最相似的样本中的大多数属于某一个类别,则该样本也属于这个类别,其中K为超参数
样本相似性
- 衡量标准: 样本距离越近则越相似,通常使用欧氏距离来衡量
- 欧氏距离公式:n维空间点A($x_1, x_2, … x_n$)到B($y_1, y_2,… y_n$)之间的距离公式为$d_{AB} = \sqrt{\sum_{i=1}^{n}(x_i - y_i)^2}$
KNN算法流程
解决分类任务
- STEP 1:计算未知样本与训练集中所有样本的距离
- STEP 2:按照距离的递增关系进行排序
- STEP 3:选取与未知样本距离最小的k个样本
- STEP 4:统计前k个样本所在类别出现的频率
- STEP 5:返回前k个样本中出现频率最高的类别作为未知样本的预测分类
解决回归任务
- STEP 1:计算未知样本与训练集中所有样本的距离
- STEP 2:按照距离的递增关系进行排序
- STEP 3:选取与未知样本距离最小的k个样本
- STEP 4:统计前k个样本的标签值,计算其平均值
- STEP 5:返回前k个样本的平均值作为未知样本的预测值
K值的选择
- K值过小: 模型容易受到异常点的影响,整体模型变得复杂,容易发生过拟合,例如:K=1时,选取了异常点
- K值过大: 模型变得简单,可能忽略数据中的细节,导致欠拟合,例如:当K=N(N为训练样本个数)时,无论输入实例是什么,都会预测为训练集中最多的类别
- K值调优: 需要一些方法来寻找最合适的K值,如交叉验证和网格搜索,一般来说,K值不取类别数的倍数
KNN算法API
分类问题API
- 来自sklearn的API:
sklearn.neighbors.KNeighborsClassifier(n_neighbors=5) - 超参数
n_neighbors:表示要与几个邻居进行判断 - 实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14from sklearn.neighbors import KNeighborsClassifier
def KNNClassifier(X_train, y_train, X_pred, k):
estimater = KNeighborsClassifier(k) # 实例化
estimater.fit(X_train, y_train) # 模型训练
y_pred = estimater.predict(X_pred) # 模型预测
return y_pred
X_train = [[1], [2], [3], [4]] # 数据集
y_train = [0, 0, 1, 1]
X_pred = [[5]]
y_pred = KNNClassifier(X_train, y_train, X_pred, k = 1)
print(y_pred)
回归问题API
- 来自sklearn的API:
sklearn.neighbors.KNeighborsRegressor(n_neighbors=5) - 超参数
n_neighbors:表示要与几个邻居进行判断 - 实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14from sklearn.neighbors import KNeighborsRegressor
def KNNClassifier(X_train, y_train, X_pred, k):
estimater = KNeighborsRegressor(k) # 实例化
estimater.fit(X_train, y_train) # 模型训练
y_pred = estimater.predict(X_pred) # 模型预测
return y_pred
X_train = [[0, 0, 1], [1, 1, 0], [3, 10, 10], [4, 11, 12]] # 数据集
y_train = [0.1, 0.2, 1.5, 1.6]
X_pred = [[5, 10, 16]]
y_pred = KNNClassifier(X_train, y_train, X_pred, k=1)
print(y_pred)
距离度量方式
欧式距离
- 定义:欧氏距离是衡量两个点之间直线距离的一种度量方式
- 计算方式:对于n维空间点A($x_1, x_2, … x_n$)到B($y_1, y_2,… y_n$)之间的距离公式为$d_{AB} = \sqrt{\sum_{i=1}^{n}(x_i - y_i)^2}$

- 应用场景: 欧氏距离广泛应用于各种领域,如机器学习中的聚类算法(如K-means)、分类算法,以及图像处理、模式识别等
曼哈顿距离(城市街区距离)
- 定义:也称为城市街区距离,它表示在一个网格状的城市中,从一点到另一点的最短路径长度,路径只能沿着网格线走,即只能沿着x方向或y方向移动
- 计算方法:曼哈顿距离等于两点在x轴方向上的距离绝对值加上在y轴方向上的距离绝对值,对于n维空间点A($x_1, x_2, … x_n$)到B($y_1, y_2,… y_n$)之间的距离公式为$d_{AB} = \sum_{i=1}^{n} \left | x_i - y_i \right |$

切比雪夫距离
- 定义:切比雪夫距离是指多维空间两点A与B之间,对应位置坐标求差后的最大值
- 计算方法:对于多维空间中A($x_1, x_2,…x_n$)与B($y_1,y_2,…y_n$),$d_{AB} = max(\left | x_1-y_1 \right |, \left | x_2 - y_2 \right |,…,\left | x_n - y_n \right |)$
闵可夫斯基距离
- 定义:闵可夫斯基距离不是一种新的距离的度量方式,而是对多个距离度量公式的概括性表述
- 计算方式:对于空间中A($x_1,x_2,…x_n$)和B($y_1,y_2,…y_n$),闵可夫斯基距离定义为$d_{AB} = (\sum_{i=1}^{n} \left | x_i - y_i \right | ^p )^\frac{1}{p}$,跟据p的不同可以变换为不同种类距离
- p = 1时,是曼哈顿距离
- p = 2时,是欧式距离
- p $\to \infty$时,是切比雪夫距离
特征预处理
- 归一化和标准化原因:特征的单位或大小相差较大,或某特征的方差比其他特征大出几个数量级,容易影响目标结果,使一些模型无法学习到其他特征由于不同特征的取值范围和单位不同,为了消除这种差异,我们需要对数据进行处理。这种处理方式有两种,即归一化和标准化,例如,在判断健康状况时,身高、体重和视力的取值范围和单位不同,如果不做处理,体重对结果的影响可能会更大,因为体重的数值范围比身高和视力的数值范围大
归一化
- 定义:通过特定算法将原始数据变换到一个特定的范围[mi, mx](通常为[0,1]),消除量纲影响
- 归一化公式:$$x_1 = \frac{x_0 - min}{max - min} \qquad x_2 = x_1*(mx - mi) + mi$$其中$x_0$表示原数据,min表示特征中最小值,max表示特征中最大值,变换后$x_2$表示归一化后的值,落在[mi, mx]中
- 归一化API:
sklearn.preprocessing.MinMaxScaler1
2
3
4
5
6
7
8
9from sklearn.preprocessing import MinMaxScaler
# 数据(只包含特征,不包含标签)
data = [[90, 2, 10, 40],
[60, 4, 15, 45],
[75, 3, 13, 46]]
transformer = MinMaxScaler() # 特征工程实例化
data = transformer.fit_transform(data) # 数据变换
print(data)[!note] 归一化适用条件:
归一化适用于数据分布较为集中,无异常值的情况。若数据中存在极端值或异常值,归一化可能会受到较大影响,此时应慎重选择或先进行数据清洗。
标准化
- 定义:将数据变换为均值为零,标准差为一的正态分布,这种变换使得不同特征或数据集中的值具有可比性,且对异常值具有较强的鲁棒性(因为异常值在计算平均值和标准差时的影响会被稀释)
- 标准化公式:$$x_1 = \frac{x_0 - mean}{\sigma }$$其中mean为特征的平均值,$\sigma$为特征的标准差
- 标准化API:
sklearn.preprocessing.StandardScaler1
2
3
4
5
6
7
8
9
10
11from sklearn.preprocessing import StandardScaler
# 数据(只包含特征,不包含标签)
data = [[90, 2, 10, 40],
[60, 4, 15, 45],
[75, 3, 13, 46]]
transformer = StandardScaler() # 实例化
data = transformer.fit_transform(data) # 数据变换
print(data)
print(transformer.mean_) # 均值
print(transformer.var_) # 方差[!note] 归一化与标准化选择
在数据预处理时,优先选择标准化,特别是在数据集中存在异常值或需要消除不同特征量纲影响时,对于无异常值的数据集(如图像数据),可以选择归一化
区别
- 归一化是将数据映射到[0,1]或[mi,mx]等区间,而标准化则是将数据按其均值和标准差进行缩放,使其符合标准正态分布
- 归一化受最大值与最小值非常容易受异常点影响,适合精确的小数据场景,而标准化在样本数量大的情况下,异常值对样本的均值和标准差的影响可以忽略不计
- 归一化应用场景:在数据分布较为集中,且确认数据中无异常值时可以使用,标准化应用场景:适合现代嘈杂大数据场景,因为大数据中往往存在异常值
模型调优
交叉验证(输入调优)
- 定义:交叉验证是一种数据集的分割方法,它将训练集划分为n份,每次选择其中一份做验证集(测试集),其余n-1份做训练集,用于训练模型
- 过程:
- 将数据集划分为n份
- 选择第i份数据作为验证集,其他部分数据用于训练(循环n次,i=1,2,…n)
- 总计训练n次,评估n次
- 使用训练集+验证集多次评估模型,取平均值为模型得分
- 若i=1时模型得分最好,则使用全部训练集(分割前,验证集+训练集)再训练i=1模型,再使用测试集对模型进行评估
- 评估标准: 不选择最高的准确率,而是选择所有准确率的平均值作为模型的评估结果
网格搜索(超参调优)
- 定义:生成超参数组合来训练模型
- 过程:
- 将若干参数传入网格搜索对象,自动进行超参组合,模型训练,模型评估
- 返回一组最优的超参组合
- 模型调优的API:
sklearn.model_selection.GridSearchCV(estimator, param_grid, cv)estimator:评估器对象,param_grid:评估器参数(dict),cv:指定几折交叉验证- 使用
fit方法训练数据,score方法输出准确率 - 结果:
best_score_:在交叉验证中最好结果,best_estimator_:最好的参数模型,best_params_:最好的超参,cv_results_:每次交叉验证后验证集准确率和训练集准确率[!note] 网格搜索+交叉验证的组合:
交叉验证解决模型的数据输入问题(数据集划分),得到更可靠的模型;网格搜索解决超参数的组合问题。两者组合在一起形成一个模型参数调优的解决方案
案例(鸢尾花的分类)
所需库函数
1 | # 加载数据集 |
- 版本:
numpy version: 2.0.1 sklearn version: 1.6.1 matplotlib version: 3.9.4 seaborn version: 0.13.2 pandas version: 2.2.3
加载数据集
1 | def load_dataset(): |
- 数据集来源:sklearn库中的datasets模块中鸢尾花(iris)数据集
- 数据集加载:
load_数据集名称一般是小的数据集(100~1000),fetch_数据集名称一般是大的数据集(>1000)
数据可视化
1 | def show_dataset(dataset): |
sns.scatterplot()函数中:hue参数用于涂色,以label作为颜色划分标准
特征工程(预处理+标准化)
1 | # 划分数据 |
trian_test_split函数中- 第一个参数传入数据特征,第二个参数传入数据标签
test_size参数表示划分数据集中测试集占比random_state参数表示随机划分的种子,确保运行结果一致性- 返回值:先特征后目标,先训练后测试
- 经典划分比例:训练集:测试集 = 7 : 3 or 8 : 2
1
2
3
4
5
6# 标准化
def standard_scaler(X_train, X_test):
transfer = StandardScaler()
X_train = transfer.fit_transform(X_train)
X_test = transfer.transform(X_test)
return X_train, X_test fit_transform(X_train)函数先从训练集中学习训练集的均值和方差,在转化数据transform(X_test)函数直接使用训练集的均值和方差进行转换- 上述代码等效于
1
2
3
4
5
6
7def standard_scaler(X_train, X_test):
transfer = StandardScaler()
transfer.fit(X_train)
X_train = transfer.transform(X_train)
X_test = transfer.transform(X_test)
return X_train, X_test, transfer - 注意:测试集不能重新学习均值和方差,预处理只对特征值进行,不对标签处理
模型训练
1 | def train(X_train, y_train): |
模型评估(准确率)
1 | def estimate(model, X_test, y_test): |
模型调优
1 | def GridCV(model, X_train, y_train): |
模型预测
1 | def predict(model, transfer, feature): |
predict_proba函数将会返回属于不同种类的概率
完整代码
完整版
1 | # 打印版本 |
精简版
1 | from sklearn.datasets import load_iris |
案例(手写字体分类)
所需函数库
1 | import matplotlib |
- 版本:
matplotlib version: 3.9.4,pandas version: 2.2.3,sklearn version: 1.6.1
加载数据
1 | # Step 1: 读取数据 |
- 数据来源为MNIST手写数据集,通过
pd.read_csv(path)读取 - 数据样本为28x28像素灰度图(2-dim),像素点取值范围[0, 255],0表示黑色(最暗),255表示白色(最亮)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18# 显示数据
def show_digit(dataset, idx):
# 防越界
if idx < 0 or idx > len(dataset) - 1:
return
# 打印数据
X = dataset.iloc[:, 1:]
y = dataset.iloc[:, 0]
print("图像集形状:", X.shape)
print("类别占比:", Counter(y)) # 好的数据集类别占比是均衡的
print("当前图像标签:", y[idx])
# 显示指定照片
image = X.iloc[idx].values
image = image.reshape(28, 28) # 转换为图像
plt.axis("off") # 不显示坐标轴
plt.imshow(image, cmap="gray") # 显示灰度图
plt.show() collections.Counter()函数统计各类别的样本数目- 分类任务数据集类别尽量均衡,若不均衡则模型会优先选择占比多的类别
plt.imshow()用于显示图片,cmap显示图像格式
数据预处理(归一化)
1 | X = dataset.iloc[:, 1:] / 255 # 归一化 |
- 在数据集划分时,
stratify参数依据y(标签)进行划分,使得训练集和测试集中的数据分类比例将与y一致1
2
3
4
5
6
7
8
9
10# 展示不同
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=66)
print("类别占比:", Counter(y_train)) # 好的数据集类别占比是均衡的
print("类别占比:", Counter(y_test)) # 好的数据集类别占比是均衡的
print("类别占比:", Counter(y)) # 好的数据集类别占比是均衡的
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=66)
print("类别占比:", Counter(y_train)) # 好的数据集类别占比是均衡的
print("类别占比:", Counter(y_test)) # 好的数据集类别占比是均衡的
print("类别占比:", Counter(y)) # 好的数据集类别占比是均衡的
模型训练
1 | estimator = KNeighborsClassifier(n_neighbors=3) |
模型评估
1 | # Step 4: 模型评估 |
或
1 | y_hat = estimator.predict(X_test) |
模型预测
1 | # Step 5: 模型预测 |
完整代码
1 | import matplotlib |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 LinHao's Pages!


