25丨KNN(下):如何对手写数字进行识别?

今天我来带你进行KNN的实战。上节课,我讲了KNN实际上是计算待分类物体与其他物体之间的距离,然后通过统计最近的K个邻居的分类情况,来决定这个物体的分类情况。

这节课,我们先看下如何在sklearn中使用KNN算法,然后通过sklearn中自带的手写数字数据集来进行实战。

之前我还讲过SVM、朴素贝叶斯和决策树分类,我们还可以用这个数据集来做下训练,对比下这四个分类器的训练结果。

如何在sklearn中使用KNN

在Python的sklearn工具包中有KNN算法。KNN既可以做分类器,也可以做回归。如果是做分类,你需要引用:

from sklearn.neighbors import KNeighborsClassifier

如果是做回归,你需要引用:

from sklearn.neighbors import KNeighborsRegressor

从名字上你也能看出来Classifier对应的是分类,Regressor对应的是回归。一般来说如果一个算法有Classifier类,都能找到相应的Regressor类。比如在决策树分类中,你可以使用DecisionTreeClassifier,也可以使用决策树来做回归DecisionTreeRegressor。

好了,我们看下如何在sklearn中创建KNN分类器。

这里,我们使用构造函数KNeighborsClassifier(n_neighbors=5, weights=‘uniform’, algorithm=‘auto’, leaf_size=30),这里有几个比较主要的参数,我分别来讲解下:

1.n_neighbors:即KNN中的K值,代表的是邻居的数量。K值如果比较小,会造成过拟合。如果K值比较大,无法将未知物体分类出来。一般我们使用默认值5。

2.weights:是用来确定邻居的权重,有三种方式:

  • weights=uniform,代表所有邻居的权重相同;

  • weights=distance,代表权重是距离的倒数,即与距离成反比;

  • 自定义函数,你可以自定义不同距离所对应的权重。大部分情况下不需要自己定义函数。

3.algorithm:用来规定计算邻居的方法,它有四种方式:

  • algorithm=auto,根据数据的情况自动选择适合的算法,默认情况选择auto;

  • algorithm=kd_tree,也叫作KD树,是多维空间的数据结构,方便对关键数据进行检索,不过KD树适用于维度少的情况,一般维数不超过20,如果维数大于20之后,效率反而会下降;

  • algorithm=ball_tree,也叫作球树,它和KD树一样都是多维空间的数据结果,不同于KD树,球树更适用于维度大的情况;

  • algorithm=brute,也叫作暴力搜索,它和KD树不同的地方是在于采用的是线性扫描,而不是通过构造树结构进行快速检索。当训练集大的时候,效率很低。

4.leaf_size:代表构造KD树或球树时的叶子数,默认是30,调整leaf_size会影响到树的构造和搜索速度。

创建完KNN分类器之后,我们就可以输入训练集对它进行训练,这里我们使用fit()函数,传入训练集中的样本特征矩阵和分类标识,会自动得到训练好的KNN分类器。然后可以使用predict()函数来对结果进行预测,这里传入测试集的特征矩阵,可以得到测试集的预测分类结果。

如何用KNN对手写数字进行识别分类

手写数字数据集是个非常有名的用于图像识别的数据集。数字识别的过程就是将这些图片与分类结果0-9一一对应起来。完整的手写数字数据集MNIST里面包括了60000个训练样本,以及10000个测试样本。如果你学习深度学习的话,MNIST基本上是你接触的第一个数据集。

今天我们用sklearn自带的手写数字数据集做KNN分类,你可以把这个数据集理解成一个简版的MNIST数据集,它只包括了1797幅数字图像,每幅图像大小是8*8像素。

好了,我们先来规划下整个KNN分类的流程:


整个训练过程基本上都会包括三个阶段:

  1. 数据加载:我们可以直接从sklearn中加载自带的手写数字数据集;

  2. 准备阶段:在这个阶段中,我们需要对数据集有个初步的了解,比如样本的个数、图像长什么样、识别结果是怎样的。你可以通过可视化的方式来查看图像的呈现。通过数据规范化可以让数据都在同一个数量级的维度。另外,因为训练集是图像,每幅图像是个8*8的矩阵,我们不需要对它进行特征选择,将全部的图像数据作为特征值矩阵即可;

  3. 分类阶段:通过训练可以得到分类器,然后用测试集进行准确率的计算。

好了,按照上面的步骤,我们一起来实现下这个项目。

首先是加载数据和对数据的探索:

# 加载数据
digits = load_digits()
data = digits.data
# 数据探索
print(data.shape)
# 查看第一幅图像
print(digits.images[0])
# 第一幅图像代表的数字含义
print(digits.target[0])
# 将第一幅图像显示出来
plt.gray()
plt.imshow(digits.images[0])
plt.show()

运行结果:

(1797, 64)
[[ 0.  0.  5. 13.  9.  1.  0.  0.]
 [ 0.  0. 13. 15. 10. 15.  5.  0.]
 [ 0.  3. 15.  2.  0. 11.  8.  0.]
 [ 0.  4. 12.  0.  0.  8.  8.  0.]
 [ 0.  5.  8.  0.  0.  9.  8.  0.]
 [ 0.  4. 11.  0.  1. 12.  7.  0.]
 [ 0.  2. 14.  5. 10. 12.  0.  0.]
 [ 0.  0.  6. 13. 10.  0.  0.  0.]]
0


我们对原始数据集中的第一幅进行数据可视化,可以看到图像是个8*8的像素矩阵,上面这幅图像是一个“0”,从训练集的分类标注中我们也可以看到分类标注为“0”。

sklearn自带的手写数字数据集一共包括了1797个样本,每幅图像都是8*8像素的矩阵。因为并没有专门的测试集,所以我们需要对数据集做划分,划分成训练集和测试集。因为KNN算法和距离定义相关,我们需要对数据进行规范化处理,采用Z-Score规范化,代码如下:

# 分割数据,将25%的数据作为测试集,其余作为训练集(你也可以指定其他比例的数据作为训练集)
train_x, test_x, train_y, test_y = train_test_split(data, digits.target, test_size=0.25, random_state=33)
# 采用Z-Score规范化
ss = preprocessing.StandardScaler()
train_ss_x = ss.fit_transform(train_x)
test_ss_x = ss.transform(test_x)

然后我们构造一个KNN分类器knn,把训练集的数据传入构造好的knn,并通过测试集进行结果预测,与测试集的结果进行对比,得到KNN分类器准确率,代码如下:

# 创建KNN分类器
knn = KNeighborsClassifier() 
knn.fit(train_ss_x, train_y) 
predict_y = knn.predict(test_ss_x) 
print("KNN准确率: %.4lf" % accuracy_score(test_y, predict_y))

运行结果:

KNN准确率: 0.9756

好了,这样我们就构造好了一个KNN分类器。之前我们还讲过SVM、朴素贝叶斯和决策树分类。我们用手写数字数据集一起来训练下这些分类器,然后对比下哪个分类器的效果更好。代码如下:

# 创建SVM分类器
svm = SVC()
svm.fit(train_ss_x, train_y)
predict_y=svm.predict(test_ss_x)
print('SVM准确率: %0.4lf' % accuracy_score(test_y, predict_y))
# 采用Min-Max规范化
mm = preprocessing.MinMaxScaler()
train_mm_x = mm.fit_transform(train_x)
test_mm_x = mm.transform(test_x)
# 创建Naive Bayes分类器
mnb = MultinomialNB()
mnb.fit(train_mm_x, train_y) 
predict_y = mnb.predict(test_mm_x) 
print("多项式朴素贝叶斯准确率: %.4lf" % accuracy_score(test_y, predict_y))
# 创建CART决策树分类器
dtc = DecisionTreeClassifier()
dtc.fit(train_mm_x, train_y) 
predict_y = dtc.predict(test_mm_x) 
print("CART决策树准确率: %.4lf" % accuracy_score(test_y, predict_y))

运行结果如下:

SVM准确率: 0.9867
多项式朴素贝叶斯准确率: 0.8844
CART决策树准确率: 0.8556

这里需要注意的是,我们在做多项式朴素贝叶斯分类的时候,传入的数据不能有负数。因为Z-Score会将数值规范化为一个标准的正态分布,即均值为0,方差为1,数值会包含负数。因此我们需要采用Min-Max规范化,将数据规范化到[0,1]范围内。

好了,我们整理下这4个分类器的结果。


你能看出来KNN的准确率还是不错的,和SVM不相上下。

你可以自己跑一遍整个代码,在运行前还需要import相关的工具包(下面的这些工具包你都会用到,所以都需要引用):

from sklearn.model_selection import train_test_split
from sklearn import preprocessing
from sklearn.metrics import accuracy_score
from sklearn.datasets import load_digits
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.naive_bayes import MultinomialNB
from sklearn.tree import DecisionTreeClassifier
import matplotlib.pyplot as plt

代码中,我使用了train_test_split做数据集的拆分,使用matplotlib.pyplot工具包显示图像,使用accuracy_score进行分类器准确率的计算,使用preprocessing中的StandardScaler和MinMaxScaler做数据的规范化。

完整的代码你可以从GitHub上下载。

总结

今天我带你一起做了手写数字分类识别的实战,分别用KNN、SVM、朴素贝叶斯和决策树做分类器,并统计了四个分类器的准确率。在这个过程中你应该对数据探索、数据可视化、数据规范化、模型训练和结果评估的使用过程有了一定的体会。在数据量不大的情况下,使用sklearn还是方便的。

如果数据量很大,比如MNIST数据集中的6万个训练数据和1万个测试数据,那么采用深度学习+GPU运算的方式会更适合。因为深度学习的特点就是需要大量并行的重复计算,GPU最擅长的就是做大量的并行计算。


最后留两道思考题吧,请你说说项目中KNN分类器的常用构造参数,功能函数都有哪些,以及你对KNN使用的理解?如果把KNN中的K值设置为200,数据集还是sklearn中的手写数字数据集,再跑一遍程序,看看分类器的准确率是多少?

欢迎在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

精选留言

  • Ricardo

    2019-04-10 17:05:29

    accuracy_score的参数顺序都错了,由于是计算真实标签和预测标签重合个数与总个数的比值,总能得到正确的答案,但是官方文档中写明的正确顺序应该是(y_true,y_pred)
    作者回复

    看的很认真,我刚查了下官方文档确实是先写y_true,然后是y_pred,也就是:accuracy_score(y_true, y_pred, normalize=True, sample_weight=None)
    关于score的计算,是判断y_true和y_pred是否相等,也就是 score = y_true == y_pred,然后再根据样本的权重做归一化处理,调用_weighted_sum(score, sample_weight, normalize)
    所以我刚用代码测试了下accuracy_score(y_true, y_pred)和accuracy_score(y_pred, y_true)的结果是一样的。Anyway,规范的话应该按照官方文档的顺序来调用参数。多谢反馈

    2019-07-08 10:41:22

  • 不做键盘侠

    2019-02-08 15:47:02

    为什么test只需要使用transform就可以了?test_ss_x = ss.transform(test_x)
    作者回复

    一个很好的问题。我在train的时候用到了:train_ss_x = ss.fit_transform(train_x)
    实际上:fit_transform是fit和transform两个函数都执行一次。所以ss是进行了fit拟合的。只有在fit拟合之后,才能进行transform
    在进行test的时候,我们已经在train的时候fit过了,所以直接transform即可。
    另外,如果我们没有fit,直接进行transform会报错,因为需要先fit拟合,才可以进行transform。

    2019-02-28 10:37:15

  • 牛奶布丁

    2019-02-13 21:49:04

    老师,为什么做多项式朴素贝叶斯分类的时候,传入的数据不能有负数呢,之前老师讲文本分类的时候好像没有提到这一点?
    作者回复

    多项式朴素贝叶斯实际上是符合多项式分布,不会存在负数。而高斯朴素贝叶斯呈现的是高斯分布,也就是正态分布,比如均值为0,方差为1的标准正态分布,可以存在负数。

    2019-02-28 10:36:40

  • Geek_12bqpn

    2019-09-30 15:06:05

    在做项目的时候,应该什么时候用Min-Max,什么时候用Z-Score呢?当我不做规范化的时候,反而准确率更高,这是为什么呢?在数据规范化该什么时候做不太理解,希望得到回复!
    作者回复

    数据比较零散的话可以使用Min-Max规范化,如果数据符合高斯分布,可以使用Z-Score规范化。
    有些分类方法对归一化比较敏感,比如GaussianNB,效果就不一定好。不过大部分情况下,还是需要先对数据做规范化处理

    2019-10-07 13:00:08

  • 2019-04-18 21:24:43

    用代码计算来以下准确率:
    knn默认k值为5 准确率:0.9756
    knn的k值为200的准确率:0.8489
    SVM分类准确率:0.9867
    高斯朴素贝叶斯准确率:0.8111
    多项式朴素贝叶斯分类器准确率:0.8844
    CART决策树准确率:0.8400

    K值的选取如果过大,正确率降低。
    算法效率排行 SVM > KNN(k值在合适范围内) >多项式朴素贝叶斯 > CART > 高斯朴素贝叶斯
    作者回复

    对的 Good Job 滢离数据总监越来越近了

    2019-12-29 18:25:11

  • third

    2019-02-18 21:18:19

    KNN常用的构造参数
    KNeighborsClassifier(n_neighbors=5,weights='uniform',algorithm='auto',leaf_size=30)
    n_neighbors是邻居的数目

    weights是权重
    uniform是权重相同,求平均值
    distance是根据距离的倒数
    自定义

    algorithm规定邻居的方式
    auto根据数据自动选择
    kd_tree,多维空间的数据结构,一般不超过20维,对关键数据检索很方便
    ball_tree,适用于维度大的
    brute包里搜索,线性扫描

    leaf_size是叶子数

  • Geek_dd384f

    2019-07-01 16:13:49

    #preprocessing.StandardScaler 和preprocessing.scale的区别
    #使用sklearn.preprocessing.StandardScaler类,使用该类的好处在于可以保存训练集中的参数(均值、方差)直接使用其对象转换测试集数据。
    ss = preprocessing.StandardScaler()
    train_ss_x = ss.fit_transform(train_x) #这里的fit_transform相当于先fit 再 transform
    test_ss_x = ss.transform(test_x) #这里没有使用fit_transform 就是因为使用了StandardScaler()
    #使用sklearn.preprocessing.scale()函数,可以直接将给定数据进行标准化。
    #train_ss_x = preprocessing.scale(train_x)
    #test_ss_x = preprocessing.scale(test_x)
  • Lee

    2019-02-14 22:08:44

    KNN 中的 K 值设置为 200,KNN 准确率: 0.8489,k值过大,导致部分未知物体没有分类出来,所以准确率下降了
    作者回复

    对的,K值过大,无法将未知物体分类出来,会降低准确率。

    2019-07-08 10:40:08

  • FORWARD―MOUNT

    2019-02-16 09:21:39

    train_x与train_y都是训练集?
    作者回复

    对 训练集的特征矩阵和分类结果。对应test_x和test_y是测试集的特征矩阵和分类结果。

    2019-02-28 10:33:25

  • JingZ

    2019-02-15 16:25:41

    #knn 将K值调为200,准确率变为0.8489了,相比较默认K=5的准确率 0.9756,下降13%

    from sklearn.datasets import load_digits
    import matplotlib.pyplot as plt
    from sklearn.model_selection import train_test_split
    from sklearn import preprocessing
    from sklearn.neighbors import KNeighborsClassifier
    from sklearn.metrics import accuracy_score

    #加载数据
    digits = load_digits()
    data = digits.data

    #数据探索
    print(data.shape)

    #查看第一幅图像
    print(digits.images[0])
    print(digits.target[0])

    #数据可视化
    plt.gray()
    plt.imshow(digits.images[0])
    plt.show()

    #训练集 测试集
    train_x, test_x, train_y, test_y = train_test_split(data, digits.target, test_size=0.25, random_state=33)

    #采用 Z-Score 规范化
    ss = preprocessing.StandardScaler()
    train_ss_x = ss.fit_transform(train_x)
    test_ss_x = ss.transform(test_x)

    #创建 KNN 分类器
    knn = KNeighborsClassifier(n_neighbors=200)

    #用训练集训练
    knn.fit(train_ss_x, train_y)

    #用测试集预测
    predict_y = knn.predict(test_ss_x)

    #模型评估
    print('KNN 准确率:%.4lf' % accuracy_score(predict_y, test_y))
    作者回复

    对的K值大未必好

    2019-02-28 10:36:04

  • Geek_35a6a8

    2022-05-18 13:12:04

    运行出的图像是什么意思,应该怎么看呢
  • Ronnyz

    2019-11-14 21:03:10

    老师能解释下数据分割时random_state的取值有什么规范吗?
    我自己测试的random_state=666与老师=33得出的准确度还是有一些差距的:
    KNN准确率:0.9778
    SVM准确率:0.9733
    多项式朴素贝叶斯准确率:0.9067
    CART决策树准确率:0.8489
    作者回复

    random_state 就是随机数种子,没有必要调整个参数,每次运算结果不同 还是正常的

    2019-11-26 16:15:11

  • 从未在此

    2019-02-12 17:55:56

    那个标准化函数已经在训练集上拟合并产生了平均值和标准差。所以测试集用同样的标准直接拿来用就行了
    作者回复

    可以

    2019-12-29 20:20:07

  • Fan

    2021-07-21 15:29:49

    train_x, test_x, train_y, test_y = train_test_split(data, target, test_size=0.25, random_state=33) 这句代码中random_state=33 是什么意思呢?
  • Fan

    2021-07-21 15:25:15

    贴下代码

    from operator import imod
    from scipy.sparse.construct import random
    from sklearn.model_selection import train_test_split
    from sklearn import preprocessing
    from sklearn.metrics import accuracy_score
    from sklearn.datasets import load_digits
    from sklearn.neighbors import KNeighborsClassifier
    from sklearn.svm import SVC
    from sklearn.naive_bayes import MultinomialNB
    from sklearn.tree import DecisionTreeClassifier
    import matplotlib.pyplot as plt

    #加载数据
    digits = load_digits()
    data = digits.data

    # 数据探索
    print(data.shape)
    # print(data[0])

    # 查看第一幅图像
    print(digits.images[0])
    print(digits.target[0])

    plt.gray()
    plt.imshow(digits.images[0])
    plt.show()

    train_x, test_x, train_y, test_y = train_test_split(data, digits.target, test_size=0.25, random_state=33)
    # 采用Z-Score规范化
    ss = preprocessing.StandardScaler()
    train_ss_x = ss.fit_transform(train_x)
    test_ss_x=ss.transform(test_x)

    # 创建KNN分类器
    knn=KNeighborsClassifier()
    knn.fit(train_ss_x, train_y)
    predict_y=knn.predict(test_ss_x)
    print("KNN准确率: %.4lf" % accuracy_score(test_y, predict_y))

    # 创建SVM分类器
    svm = SVC()
    svm.fit(train_ss_x, train_y)
    predict_y=svm.predict(test_ss_x)
    print("SVM准确率: %0.4lf" % accuracy_score(test_y, predict_y))

    # 采用Min-Max规范化
    mm=preprocessing.MinMaxScaler()
    train_mm_x=mm.fit_transform(train_x)
    test_mm_x=mm.transform(test_x)
    # 创建Naive Bayes分类器
    mnb=MultinomialNB()
    mnb.fit(train_mm_x, train_y)
    predict_y=mnb.predict(test_mm_x)
    print("多项式朴素贝叶斯准确率: %.4lf" % accuracy_score(test_y, predict_y))

    # 创建CART决策树分类器
    dtc=DecisionTreeClassifier()
    dtc.fit(train_mm_x, train_y)
    predict_y=dtc.predict(test_mm_x)
    print("CART决策树准确率: %.4lf" % accuracy_score(test_y, predict_y))

    ===============
    KNN准确率: 0.9756
    SVM准确率: 0.9867
    多项式朴素贝叶斯准确率: 0.8844
    CART决策树准确率: 0.8578
  • 彭涛

    2021-06-16 20:25:53

    老师您好,请问这里的 KNN 分类器没看见 K 值是如何设置的,请问是使用了默认值吗?
  • 完美坚持

    2021-04-11 12:58:55

    1. 如何实现用CV来选择合适的k
    2. 深度学习+GPU运算怎么实现
  • McKee Chen

    2020-12-23 17:12:14

    KNN常用构造函数为KNeighborsClassifier(n_neighbors=5, weights='uniform', algorithm='auto', leaf_size=30)
    其中:
    n_neighbors代表K值,K值不宜太大,会欠拟合;也不宜太小,会过拟合;
    weights代表邻居的权重;
    algorithm代表计算邻居的方法
    leaf_size达标构造KD树或球树的叶子数

    计算逻辑: 对样本数据集按一定比例进行划分,训练集通过fit函数对模型进行训练,然后将测试集的数据输入训练好的模型中,再通过predict函数预测测试集的分类结果,最后通过accuracy_score计算模型的准确率

    KNN是一种监督学习,可以用于图像识别、字符识别、文本分类,K值通过交叉验证得出,且K值不能太大

    #当K值=200时,计算KNN分类器的准确率
    #导入包
    import matplotlib.pyplot as plt
    from sklearn.model_selection import train_test_split
    from sklearn.preprocessing import StandardScaler
    from sklearn.preprocessing import MinMaxScaler
    from sklearn.metrics import accuracy_score
    from sklearn.svm import SVC
    from sklearn.neighbors import KNeighborsClassifier
    from sklearn.datasets import load_digits
    from sklearn.naive_bayes import MultinomialNB
    from sklearn.tree import DecisionTreeClassifier
    #数据加载
    digits = load_digits()
    #获得图像和分类结果
    data = digits.data
    target = digits.target
    #将样本集进行划分,选取25%作为测试集,其余为训练集
    train_x, test_x, train_y, test_y = train_test_split(data, target, test_size=0.25, random_state=33)
    #由于数据量级不统一,对数据进行标准化处理
    ss = StandardScaler()
    train_ss_x = ss.fit_transform(train_x)
    test_ss_x = ss.transform(test_x)
    #创建KNN分类器
    knn = KNeighborsClassifier(n_neighbors=200)#取K值=200
    #训练分类器
    knn.fit(train_ss_x, train_y)
    #预测测试集结果
    predict_y = knn.predict(test_ss_x)
    #计算准确率
    print('KNN分类器准确率为:', accuracy_score(test_y, predict_y))

    #输出结果
    KNN分类器准确率为: 0.8488888888888889




  • 斯盖丸

    2020-11-01 10:48:16

    老师,这个z-score规范化,把数据变成标准正态分布,在这个例子里的作用是什么?也就是说数据变化前是什么样的,变化后又是什么样的……如果不这么变化会带来什么结果?
    作者回复

    当模型中存在对不同特征进行线性加权求和这种操作的时候通常都需要对特征进行规范化操作,例如:StandardScaler(标准化)或MinMaxScaler(归一化)等)。
    进行规范化后,有利于加速模型收敛,且可以避免某列特征由于值较大而被模型判断为具有较大权重。
    建议你利用线性回归模型,对未规范化和规范化后的特征,分别建模,然后打印出拟合后的模型权重,看看每个特征权重的差别。

    2021-03-17 23:50:52

  • §mc²ompleXWr

    2020-06-27 16:58:37

    为什么每次计算KNN和SVM分类器的准确率都是一样的?而朴素贝叶斯和决策树分类器每次计算的准确率都不一样呢?
    作者回复

    CART决策树如果不使用random_state参数指定随机数种子,则每次准确率将会不同。
    即使分割器设置为“best”,每个分割中的特征也始终是随机排列的。
    当max_features <n_features时,算法将在每个分割处随机选择max_features,然后再在其中找到最佳分割。
    但即使max_features = n_features,找到的最佳分割也可能因不同的运行而有所不同。
    就是这种情况,如果对于几个分割而言标准的改进是相同的,并且必须随机选择一个分割。

    2021-03-30 00:26:46