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算法流程

解决分类任务

  1. STEP 1:计算未知样本与训练集中所有样本的距离
  2. STEP 2:按照距离的递增关系进行排序
  3. STEP 3:选取与未知样本距离最小的k个样本
  4. STEP 4:统计前k个样本所在类别出现的频率
  5. STEP 5:返回前k个样本中出现频率最高的类别作为未知样本的预测分类

解决回归任务

  1. STEP 1:计算未知样本与训练集中所有样本的距离
  2. STEP 2:按照距离的递增关系进行排序
  3. STEP 3:选取与未知样本距离最小的k个样本
  4. STEP 4:统计前k个样本的标签值,计算其平均值
  5. 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
    14
    from 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
    14
    from 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]中
  • 归一化APIsklearn.preprocessing.MinMaxScaler
    1
    2
    3
    4
    5
    6
    7
    8
    9
    from 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.StandardScaler
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from 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] 归一化与标准化选择
    在数据预处理时,优先选择标准化,特别是在数据集中存在异常值或需要消除不同特征量纲影响时,对于无异常值的数据集(如图像数据),可以选择归一化

区别

  1. 归一化是将数据映射到[0,1]或[mi,mx]等区间,而标准化则是将数据按其均值和标准差进行缩放,使其符合标准正态分布
  2. 归一化受最大值与最小值非常容易受异常点影响,适合精确的小数据场景,而标准化在样本数量大的情况下,异常值对样本的均值和标准差的影响可以忽略不计
  3. 归一化应用场景:在数据分布较为集中,且确认数据中无异常值时可以使用,标准化应用场景:适合现代嘈杂大数据场景,因为大数据中往往存在异常值

模型调优

交叉验证(输入调优)

  • 定义:交叉验证是一种数据集的分割方法,它将训练集划分为n份,每次选择其中一份做验证集(测试集),其余n-1份做训练集,用于训练模型
  • 过程
    1. 将数据集划分为n份
    2. 选择第i份数据作为验证集,其他部分数据用于训练(循环n次,i=1,2,…n)
    3. 总计训练n次,评估n次
    4. 使用训练集+验证集多次评估模型,取平均值模型得分
    5. 若i=1时模型得分最好,则使用全部训练集(分割前,验证集+训练集)再训练i=1模型,再使用测试集对模型进行评估
  • 评估标准: 不选择最高的准确率,而是选择所有准确率的平均值作为模型的评估结果

网格搜索(超参调优)

  • 定义:生成超参数组合来训练模型
  • 过程
    1. 将若干参数传入网格搜索对象,自动进行超参组合,模型训练,模型评估
    2. 返回一组最优的超参组合
  • 模型调优的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
2
3
4
5
6
7
8
9
10
11
12
13
14
# 加载数据集  
from sklearn.datasets import load_iris
# 可视化
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
# 划分数据集
from sklearn.model_selection import train_test_split
# 标准化
from sklearn.preprocessing import StandardScaler

# 引入模型
from sklearn.neighbors import KNeighborsClassifier
  • 版本: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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def load_dataset():  
# 加载数据集
dataset = load_iris()
# 查看数据集内容前5条
print("数据集实例样本:")
print(dataset.data[:5])
print("查看数据集大小:", dataset.data.shape)
print("查看目标值:", set(dataset.target))
print("查看目标值名称:", dataset.target_names)
# 数据集介绍
print("数据集描述:", dataset.DESCR)
return dataset


load_dataset()
  • 数据集来源:sklearn库中的datasets模块中鸢尾花(iris)数据集
  • 数据集加载load_数据集名称一般是小的数据集(100~1000),fetch_数据集名称一般是大的数据集(>1000)

数据可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def show_dataset(dataset):  
# 转化为DataFrame格式,并设置列名为dataset.feature_names
iris_dataset = pd.DataFrame(dataset["data"], columns=dataset.feature_names)
iris_dataset["Species"] = dataset.target
print("样本实例:")
print(iris_dataset.head(10))

# 数据可视化
xlabel = iris_dataset.columns[0]
ylabel = iris_dataset.columns[3]
sns.scatterplot(x=xlabel, y=ylabel, data=iris_dataset, hue=iris_dataset.columns[-1])
plt.xlabel(xlabel)
plt.ylabel(ylabel)
plt.show()
  • sns.scatterplot()函数中:hue参数用于涂色,以label作为颜色划分标准

特征工程(预处理+标准化)

1
2
3
4
5
6
7
8
9
# 划分数据
def split_dataset(dataset):
X_train, X_test, y_train, y_test = train_test_split(dataset.data, dataset.target, test_size=0.3, random_state=22)

print("数据总数:", len(dataset.data))
print("训练集数据大小:", len(X_train))
print("测试集数据大小:", len(X_test))

return X_train, X_test, y_train, y_test
  • 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
    7
    def 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
2
3
4
def train(X_train, y_train):
model = KNeighborsClassifier(n_neighbors=3)
model.fit(X_train, y_train)
return model

模型评估(准确率)

1
2
3
4
def estimate(model, X_test, y_test):  
score = model.score(X_test, y_test)
print("模型准确率:", score)
return score

模型调优

1
2
3
4
5
6
7
8
9
10
def GridCV(model, X_train, y_train):  
param_grid = {"n_neighbors": [1, 3, 5, 7]}
model = GridSearchCV(estimator=model, param_grid=param_grid, cv=5)
model.fit(X_train, y_train)

print("最高准确率:", model.best_score_)
print("最好模型:", model.best_estimator_)
print("最优参数:", model.best_params_)
print("交叉结果:", model.cv_results_)
return model.best_estimator_

模型预测

1
2
3
4
def predict(model, transfer, feature):  
transfer.transform(feature)
print(model.predict(feature))
print(model.predict_proba(feature))
  • predict_proba函数将会返回属于不同种类的概率

完整代码

完整版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# 打印版本  
import numpy as np
import sklearn

# 加载数据集
from sklearn.datasets import load_iris
# 可视化
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
# 划分数据集
from sklearn.model_selection import train_test_split
# 标准化
from sklearn.preprocessing import StandardScaler
# 引入模型
from sklearn.neighbors import KNeighborsClassifier
# 评估模型
from sklearn.metrics import accuracy_score
# 模型调优
from sklearn.model_selection import GridSearchCV

matplotlib.use("TkAgg")


def print_version():
print("numpy version: ", np.__version__)
print("sklearn version: ", sklearn.__version__)
print("matplotlib version: ", matplotlib.__version__)
print("seaborn version: ", sns.__version__)
print("pandas version: ", pd.__version__)


# step 1: 加载数据
def load_dataset():
# 加载数据集
dataset = load_iris()
# 查看数据集内容前5条
print("数据集实例样本:")
print(dataset.data[:5])
print("查看数据集大小:", dataset.data.shape)
print("查看目标值:", set(dataset.target))
print("查看目标值名称:", dataset.target_names)
# 数据集介绍
print("数据集描述:", dataset.DESCR)
return dataset


# step 2: 可视化
def show_dataset(dataset):
# 转化为DataFrame格式,并设置列名为dataset.feature_names
iris_dataset = pd.DataFrame(dataset["data"], columns=dataset.feature_names)
iris_dataset["Species"] = dataset.target
print("样本实例:")
print(iris_dataset.head(10))

# 数据可视化
xlabel = iris_dataset.columns[0]
ylabel = iris_dataset.columns[3]
sns.scatterplot(x=xlabel, y=ylabel, data=iris_dataset, hue=iris_dataset.columns[-1])
plt.xlabel(xlabel)
plt.ylabel(ylabel)
plt.show()


# step 3: 划分数据集
def split_dataset(dataset):
X_train, X_test, y_train, y_test = train_test_split(dataset.data, dataset.target, test_size=0.3, random_state=22)

print("数据总数:", len(dataset.data))
print("训练集数据大小:", len(X_train))
print("测试集数据大小:", len(X_test))

return X_train, X_test, y_train, y_test


# step 4: 标准化
def 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


# step 5: 模型训练
def train(X_train, y_train):
model = KNeighborsClassifier(n_neighbors=3)
model.fit(X_train, y_train)
return model


# step 6: 模型评估
def estimate(model, X_test, y_test):
score = model.score(X_test, y_test)
y_pred = model.predict(X_test)
acc = accuracy_score(y_test, y_pred)
print("模型准确率:", acc)
return score


# step 7: 模型预测
def predict(model, transfer, feature):
transfer.transform(feature)
print(model.predict(feature))
print(model.predict_proba(feature))

# step 8: 模型调优
def GridCV(model, X_train, y_train):
param_grid = {"n_neighbors": [1, 3, 5, 7]}
model = GridSearchCV(estimator=model, param_grid=param_grid, cv=5)
model.fit(X_train, y_train)

print("最高准确率:", model.best_score_)
print("最好模型:", model.best_estimator_)
print("最优参数:", model.best_params_)
print("交叉结果:", model.cv_results_)
return model.best_estimator_

if __name__ == "__main__":
iris_dataset = load_dataset()
X_train, X_test, y_train, y_test = split_dataset(iris_dataset)
X_train, X_test, transfer = standard_scaler(X_train, X_test)
model = train(X_train, y_train)
estimate(model, X_test, y_test)
# 模型调优
model = GridCV(model, X_train, y_train)
model.fit(X_train, y_train)
estimate(model, X_test, y_test)

x = [[5.1, 3.5, 1.4, 0.2]]
predict(model, transfer, x)

精简版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from sklearn.datasets import load_iris  
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score

# step 1: 加载数据
dataset = load_iris()

# step 2: 数据集切分
X_train, X_test, y_train, y_test = train_test_split(dataset.data, dataset.target, test_size=0.2, random_state=0)

# step 3: 特征处理
transfer = StandardScaler()
transfer.fit(X_train)
transfer.transform(X_train)
transfer.transform(X_test)

# Step 4: 模型实例化
model = KNeighborsClassifier(n_neighbors=1)


# Step 5: 交叉验证 + 网格搜索
params_grid = {"n_neighbors": [4, 5, 7, 8]} # 是3分类故不使用3或3的倍数
estimator = GridSearchCV(estimator=model, param_grid=params_grid, cv=4)
estimator.fit(X_train, y_train)

print(estimator.best_score_)
print(estimator.best_estimator_)
print(estimator.best_params_)
print(estimator.cv_results_)

model = estimator.best_estimator_
model.fit(X_train, y_train)

# step 6: 模型评估
y_hat = model.predict(X_test)
print("acc: ", accuracy_score(y_test, y_hat))

# Step 7: 模型预测
x = [[5.1, 3.5, 1.4, 0.2]]
y_pred = model.predict(x)
print(y_pred)

案例(手写字体分类)

所需函数库

1
2
3
4
5
6
7
import matplotlib  
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
import joblib
from collections import Counter
  • 版本:matplotlib version: 3.9.4pandas version: 2.2.3sklearn version: 1.6.1

加载数据

1
2
3
# Step 1: 读取数据
dataset = pd.read_csv("手写数字识别.csv")
show_digit(dataset, 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
2
3
X = dataset.iloc[:, 1:] / 255   # 归一化  
y = dataset.iloc[:, 0]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=0)
  • 在数据集划分时,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
2
estimator = KNeighborsClassifier(n_neighbors=3)  
estimator.fit(X_train, y_train)

模型评估

1
2
3
4
5
6
7
# Step 4: 模型评估  
acc = estimator.score(X_test, y_test)
print("模型准确率:", acc)
# 保存好的模型
joblib.dump(estimator, "model/knn-mnist.pth")
# 加载模型
estimator = joblib.load("model/knn-mnist.pth")

1
2
y_hat = estimator.predict(X_test)
acc = accuracy_score(y_test, y_hat)

模型预测

1
2
3
4
# Step 5: 模型预测  
img = plt.imread("<path>")
y_pred = estimator.predict(img.reshape(1, -1) / 255)
print("预测结果:", y_pred)

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import matplotlib  
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
import joblib
from collections import Counter

matplotlib.use("TkAgg")


# 显示数据
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()

if __name__ == "__main__":
# Step 1: 读取数据
dataset = pd.read_csv("手写数字识别.csv")
# show_digit(dataset, 1)

print()

# Step 2: 数据预处理(归一化)
X = dataset.iloc[:, 1:] / 255 # 归一化
y = dataset.iloc[:, 0]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=66)

# Step 3: 模型训练
estimator = KNeighborsClassifier(n_neighbors=3)
estimator.fit(X_train, y_train)

# Step 4: 模型评估
acc = estimator.score(X_test, y_test)
print("模型准确率:", acc)
# 保存模型
joblib.dump(estimator, "model/knn-mnist.pth")
# 加载模型
estimator = joblib.load("model/knn-mnist.pth")

# Step 5: 模型预测
img = plt.imread("<path>/<file>.png")
y_pred = estimator.predict(img.reshape(1, -1) / 255)
print("预测结果:", y_pred)