Menu

Lab6 kNN应用实践

  • 导入数据,归一化数据,距离计算,实现 kNN 分类器
  • 实例学习 kNN 分类器如何改进约会网站,以及识别手写数字

实验目的

利用 python 实现 kNN 分类器

实验环境

硬件

所用机器型号为 VAIO Z Flip 2016

  • Intel(R) Core(TM) i7-6567U CPU @3.30GHZ 3.31GHz
  • 8.00GB RAM

软件

  • Windows 10, 64-bit (Build 17763) 10.0.17763
  • Visual Studio Code 1.39.2
    • Python 2019.10.41019:九月底发布的 VSCode Python 插件支持在编辑器窗口内原生运行 juyter nootbook 了,非常赞!
    • Remote - WSL 0.39.9:配合 WSL,在 Windows 上获得 Linux 接近原生环境的体验。
  • Windows Subsystem for Linux [Ubuntu 18.04.2 LTS]:WSL 是以软件的形式运行在 Windows 下的 Linux 子系统,是近些年微软推出来的新工具,可以在 Windows 系统上原生运行 Linux。
    • Python 3.7.4 64-bit (‘anaconda3’:virtualenv):安装在 WSL 中。

实现 kNN 分类器

导入两个模块:科学计算包 numpy 和运算符模块 operator。在构建完整的 kNN 分类器之前,需要编写一些基本的通用函数。

1
2
3
4
5
6
7
8
9
10
# 使用createDataSet()函数创建一个简单数据集合和标签,此函数包含在knn1模块中:
>>> import knn1
# 测试函数功能:创建变量group和labels
>>> group,labels=knn1.createDataSet()
#查看变量group和labels的值
>>> group
>>> labels
# 通过函数classify()实现kNN分类器
# 测试分类器功能
>>>knn1.classify([0,0],group,labels,3)

输出结果是 B,可以改变输入[0,0]为其他值,测试运行结果。

使用 kNN 改进约会网站的配对效果

利用收集的在线约会网站的约会数据,将约会网站推荐的匹配对象归入恰当的分类(不喜欢的人,魅力一般的人,极具魅力的人)。

准备数据

收集的数据存放在文本文件 datingTestSet.txt 中,每条数据占一行,总共 1000 行。主要包括 3 个特征:每年获得的飞行里程数,玩游戏视频所耗时间百分比,每周消费的冰激凌公升数。在特征数据输入分类器之前,需要将待处理数据的格式转换为分类器可以接受的格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# file2matrix函数解决格式输入问题,函数的输入为文件名字符串,输出为训练样本矩阵和类标签向量,该函数包含在knn2模块中。
>>> import knn2
>>> datingDataMat,datingLabels=knn2.file2matrix('datingTestSet2.txt')
>>> print(datingDataMat)
>>> datingLabels[0:20]
#使用Matplotlib创建散点图
>>> import matplotlib
>>> import matplotlib.pyplot as plt
>>> from numpy import*
>>> plt.scatter(datingDataMat[:,1],datingDataMat[:,2])>>> plt.show()
>>> plt.scatter(datingDataMat[:,1],datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
'''
# 数据归一化处理
在处理不同取值范围的特征值时,通常采用的方法是将数值归一化,将取值范围处理为0到1或-1到1之间。如下公式可将任意取值范围的特征值转化为0到1区间内的值:
newValue=(oldValue-min)/(max-min),其中max和min分别是数据集中的相应维度的最大特征值和最小特征值。
'''
#函数autoNorm()将数字特征值转化为0到1的区间。
>>> normMat, ranges, minVals=knn2.autoNorm(datingDataMat)
>>> normMat
>>> ranges
>>> minVals

测试分类器

利用函数 datingClassTest()测试分类器效果:

1
>>> knn2.datingClassTest()

使用算法:构建完整可用系统

给用户提供程序,通过该程序用户会在约会网站上找到某个人并输出它的信息。程序会给出用户对对方喜欢程度的预测值。

1
2
#函数classifyPerson()完成此功能
>>> knn2.classifyPerson()

使用 kNN 识别手写体

实验所用到的实际图像存储在两个子目录中:目录 trainingDigits 中包含了大约 2000 个例子,命名规则如 9_45.txt,表示该文件的分类是 9,是数字 9 的第 45 个实例,每个数字大概有 200 个实例。目录 testDigits 中包含了大约 900 个测试例子。将使用目录 trainingDigits 中的数据训练分类器,使用目录 testDigits 中的数据测试分类器的效果,两组数据没有重叠。

准备数据

使用 kNN 分类器,首先要将图像处理为一个向量。实验中,将把一个32*32的二进制图像矩阵转换成1*1024的向量,为此首先要编写函数 img2vector,将图像转换为向量,该函数创建1*1024的 Numpy 数组,然后打开给定的文件,循环读出文件的前 32 行,并将每行的前 32 个字符值存储在 Numpy 数组中,最后返回数组。

构建训练数据集

函数 trainingDataTest 利用目录 trainingDigits 中的文本数据构建训练集向量,以及对应的分类标签向量(标签向量可理解为对应的文件中数字的正确分类)。由于文件名的规律命名,可编写函数 classnumCut 以实现从文件名中解析分类数字,提供分类标签。注意在程序开头写上from os import listdir以导入 listdir 函数,它可以列出给定目录的文件名。

测试算法

通过测试 testDigits 目录下的样本,计算准确率。

1
2
3
#handwrtingTest() 函数来实现分类器测试。每个数据文件中的数字按顺序展开成一个1024维的向量,而向量之间的距离用欧式距离。
#切换至文件knn3.py所在目录,并在cmd窗口执行
> python knn3.py

实际运行代码时,会发现 kNN 算法分类器的执行效率并不高,因为算法需要为每个测试向量计算约 2000 次欧氏距离,每个距离计算包括 1024 个维度浮点运算,全部样本要执行 900 多次,可见算法实际耗时长。另外,kNN 算法必须保存全部数据集,每次需为测试向量准备 2MB 的存储空间(2 个1024*1024矩阵的空间)。

操作练习

将 knn1 中的语句「from numpy import * 」用语句「import numpy as np」代替,修改其中对应的代码,使其能够正常执行

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
# -*- coding=utf-8 -*-

# 导入必要的库
import numpy as np
import operator

# 创建简单数据集


def createDataSet():
    group = np.array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
    labels = ['A', 'A', 'B', 'B']
    return group, labels

# k近邻算法


def classify(inX, dataSet, labels, k):
    # inX用于分类的输入向量
    # dataSet输入的训练样本集
    # labels为标签向量
    # k为最近的邻居数目

    # 计算距离
    dataSetSize = dataSet.shape[0]
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances ** 0.5
    sortedDistIndicies = distances.argsort()
    classCount = {}

    # 选择距离最小的k个点
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    # 排序
    sortedClassCount = sorted(
        classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

将 knn2 中的语句「from numpy import * 」用语句「import numpy as np」代替,修改其中对应的代码,使其能够正常执行

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
import numpy as np
import operator
import time

# 将文本记录转换为NumPy的解析程序


def file2matrix(filename):
    fr = open(filename)
    # 得到文件行数
    arrayOfLines = fr.readlines()
    numberOfLines = len(arrayOfLines)
    # 创建返回的Numpy矩阵
    returnMat = np.zeros((numberOfLines, 3))
    classLabelVector = []
    # 解析文件数据到列表
    index = 0
    for line in arrayOfLines:
        line = line.strip()  # 注释1
        listFromLine = line.split('\t')  # 注释2
        returnMat[index, :] = listFromLine[0:3]
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    return returnMat, classLabelVector


# 归一化特征值
def autoNorm(dataSet):
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    # 创建一个与dataSet同大小的零矩阵
    normDataSet = np.zeros(np.shape(dataSet))
    # 数据行数
    m = dataSet.shape[0]
    # tile()函数将变量内容复制成输入矩阵同等的大小
    normDataSet = dataSet - np.tile(minVals, (m, 1))
    # 特征值相除
    normDataSet = normDataSet/np.tile(ranges, (m, 1))
    return normDataSet, ranges, minVals

# k近邻算法


def classify0(inX, dataSet, labels, k):
    # inX用于分类的输入向量
    # dataSet输入的训练样本集,
    # labels为标签向量,
    # k用于选择最近的邻居数目

    # 计算距离
    dataSetSize = dataSet.shape[0]
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances ** 0.5
    sortedDistIndicies = distances.argsort()
    classCount = {}
    # 选择距离最小的k个点
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    # 排序
    sortedClassCount = sorted(
        classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]


# 分类器针对约会网站的测试代码
def datingClassTest(k=4, h=0.10, classify=classify0):
    # 测试样本的比例
    hoRatio = h
    datingDataMat, datingLabels = file2matrix("datingTestSet2.txt")
    normMat, ranges, minVals = autoNorm(datingDataMat)
    m = normMat.shape[0]
    # 测试样本的数量
    numTestVecs = int(m*hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        classifierResult = classify(normMat[i, :], normMat[numTestVecs:m, :],
                                    datingLabels[numTestVecs:m], k)
        print("the classifier came back with: %d, the real answer is :%d" %
              (classifierResult, datingLabels[i]))
        if (classifierResult != datingLabels[i]):
            errorCount += 1.0
    print("the total error rate is: %f" % (errorCount/float(numTestVecs)))
    return errorCount/float(numTestVecs)


def classifyPerson(classify=classify0):
    resultList = ['not at all', 'in small doses', 'in large doses']
    percentTats = float(input("percentage of time spent playing video games?"))
    ffMiles = float(input("frequent flier miles earned per year?"))
    iceCream = float(input("liters of ice cream consumed per year?"))
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    inArr = np.array([ffMiles, percentTats, iceCream])
    classifierResult = classify(
        (inArr - minVals)/ranges, normMat, datingLabels, 3)
    print("You will probably like this person:",
          resultList[classifierResult - 1])

对于 knn2,测试不同 k 值对错误率的影响

1
2
3
4
5
6
7
8
9
10
from matplotlib import pyplot as plt
x = []
y = []
for kk in range(1, 30):
    y.append(datingClassTest(k=kk))
    x.append(kk)

plt.plot(x, y)
plt.xlabel('k')
plt.ylabel('error rates')

运行结果

1
Text(0, 0.5, 'error rates')

1

对于 knn2,使用曼哈顿距离,观察对错误率的影响

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
from matplotlib import pyplot as plt


def classifyManhattan(inX, dataSet, labels, k):
    # inX用于分类的输入向量
    # dataSet输入的训练样本集,
    # labels为标签向量,
    # k用于选择最近的邻居数目

    # 计算距离--曼哈顿距离

    dataSetSize = dataSet.shape[0]
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = abs(diffMat)
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances
    sortedDistIndicies = distances.argsort()
    classCount = {}

    # 选择距离最小的k个点
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    # 排序
    sortedClassCount = sorted(
        classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]


x = []
y = []
for kk in range(1, 30):
    y.append(datingClassTest(k=kk, classify=classifyManhattan))
    x.append(kk)

plt.plot(x, y)
plt.xlabel('k')
plt.ylabel('error rates')

运行结果

1
Text(0, 0.5, 'error rates')

2

对于 knn2,随机选取训练样本,测试不同样本数目对错误率的影响

1
2
3
4
5
6
7
8
9
10
11
12
from matplotlib import pyplot as plt
r1 = []
e1 = []
for i in range(1, 10):
    r = i/10
    e1.append(datingClassTest(h=1-r))
    r1.append(r)

plt.plot(r1, e1)

plt.xlabel('rate')
plt.ylabel('error rates')

运行结果

1
Text(0, 0.5, 'error rates')

2

利用 sklearn 实现使用 kNN 改进约会网站的配对效果,并分别比较应用 PCA 前后的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn import preprocessing
from sklearn.decomposition import PCA

X, y = file2matrix('datingTestSet2.txt')
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
knn = KNeighborsClassifier()
knn.fit(X_train, y_train)
print(knn.score(X_test, y_test, sample_weight=None))

pca1 = PCA(n_components=1)
pca1.fit(X_train)
X_train_re = pca1.transform(X_train)  # 对于训练数据和测试数据进行降维到一维数据
X_test_re = pca1.transform(X_test)
knn1 = KNeighborsClassifier()
knn1.fit(X_train_re, y_train)  # 再对降维到的一维数据进行KNN算法的训练和测试准确度
print(knn1.score(X_test_re, y_test, sample_weight=None))
print(pca1.explained_variance_ratio_)

运行结果

1
2
3
0.815
0.815
[0.99999997]

利用 sklearn 实现使用 kNN 识别手写体应用,并分别比较应用 PCA 前后的效果

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
import numpy as np
from sklearn.neighbors import KNeighborsClassifier as KNN
import time
from os import listdir


def img2vector(filename):
    '''
    filename:文件名字
    将这个文件的所有数据按照顺序写成一个一维向量并返回
    '''
    returnVect = []
    fr = open(filename)
    for i in range(32):
        lineStr = fr.readline()
        for j in range(32):
            returnVect.append(int(lineStr[j]))
    return returnVect

# 从文件名中解析分类数字


def classnumCut(fileName):
    '''
    filename:文件名
    返回这个文件数据代表的实际数字
    '''
    fileStr = fileName.split('.')[0]
    classNumStr = int(fileStr.split('_')[0])
    return classNumStr

# 构建训练集数据向量及对应分类标签向量


def trainingDataSet():
    '''
    从trainingDigits文件夹下面读取所有数据文件,返回:
    trainingMat:所有训练数据,每一行代表一个数据文件中的内容
    hwLabels:每一项表示traningMat中对应项的数据到底代表数字几
    '''
    hwLabels = []
    # 获取目录traningDigits内容(即数据集文件名),并储存在一个list中
    trainingFileList = listdir('trainingDigits')
    m = len(trainingFileList)  # 当前目录文件数
    # 初始化m维向量的训练集,每个向量1024维
    trainingMat = np.zeros((m, 1024))
    for i in range(m):
        fileNameStr = trainingFileList[i]
        # 从文件名中解析分类数字,作为分类标签
        hwLabels.append(classnumCut(fileNameStr))
        # 将图片矩阵转换为向量并储存在新的矩阵中
        trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)
    return hwLabels, trainingMat


def handwritingTest():
    # 构建训练集
    hwLabels, trainingMat = trainingDataSet()

    # 从testDigits里面拿到测试集
    testFileList = listdir('testDigits')

    # 错误数
    errorCount = 0.0

    # 测试集总样本数
    mTest = len(testFileList)

    # 获取程序运行到此处的时间(开始测试)
    t1 = time.time()

    # 构建kNN分类器
    neigh = KNN(n_neighbors=3, algorithm='auto')
    # 拟合模型, trainingMat为训练矩阵,hwLabels为对应的标签
    neigh.fit(trainingMat, hwLabels)

    for i in range(mTest):

        # 得到当前文件名
        fileNameStr = testFileList[i]

        # 从文件名中解析分类数字
        classNumStr = classnumCut(fileNameStr)

        # 将图片矩阵转换为向量
        vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)

        # 调用knn算法进行测试
        classifierResult = neigh.predict([vectorUnderTest])
        print("the classifier came back with: %d, the real answer is: %d" %
              (classifierResult, classNumStr))

        # 预测结果不一致,则错误数+1
        if (classifierResult != classNumStr):
            errorCount += 1.0

    print("\nthe total number of tests is: %d" % mTest)
    print("the total number of errors is: %d" % errorCount)
    print("the total error rate is: %f" % (errorCount/float(mTest)))

    # 获取程序运行到此处的时间(结束测试)
    t2 = time.time()

    # 测试耗时
    print("Cost time: %.2fmin, %.4fs." % ((t2-t1)//60, (t2-t1) % 60))


if __name__ == "__main__":
    handwritingTest()

运行结果

1
2
3
4
the total number of tests is: 946
the total number of errors is: 12
the total error rate is: 0.012685
Cost time: 0.00min, 6.6449s.

将 knn3 中的语句「from numpy import * 」用语句「import numpy as np」代替,修改其中对应的代码,使其能够正常执行

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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# -*- coding:utf-8 -*-

import numpy as np
import operator
import time
from os import listdir

# knn实现


def classify(inputPoint, dataSet, labels, k):
    '''
    inputPoint:待判断的点
    dataSet:数据集合
    labels:标签向量,维数和dataSet行数相同,比如labels[1]代表dataSet[1]的类别
    k:邻居数目
    输出:inputPoint的标签
    '''
    dataSetSize = dataSet.shape[0]

    # 先用tile函数将输入点拓展成与训练集相同维数的矩阵,再计算欧氏距离
    diffMat = np.tile(inputPoint, (dataSetSize, 1))-dataSet
    sqDiffMat = diffMat ** 2

    # 计算每一行元素之和
    sqDistances = sqDiffMat.sum(axis=1)

    # 开方得到欧拉距离矩阵
    distances = sqDistances ** 0.5

    # argsort返回的是数组从小到大的元素的索引
    # 按distances中元素进行升序排序后得到的对应下标的列表
    sortedDistIndicies = distances.argsort()

    # 选择距离最小的k个点,统计每个类别的点的个数
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0)+1

    # 按classCount字典的第2个元素(即类别出现的次数)从大到小排序
    sortedClassCount = sorted(
        classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

# 将图片矩阵转换为向量


def img2vector(filename):
    '''
    filename:文件名字
    将这个文件的所有数据按照顺序写成一个一维向量并返回
    '''
    returnVect = []
    fr = open(filename)
    for i in range(32):
        lineStr = fr.readline()
        for j in range(32):
            returnVect.append(int(lineStr[j]))
    return returnVect

# 从文件名中解析分类数字


def classnumCut(fileName):
    '''
    filename:文件名
    返回这个文件数据代表的实际数字
    '''
    fileStr = fileName.split('.')[0]
    classNumStr = int(fileStr.split('_')[0])
    return classNumStr

# 构建训练集数据向量及对应分类标签向量


def trainingDataSet():
    '''
    从trainingDigits文件夹下面读取所有数据文件,返回:
    trainingMat:所有训练数据,每一行代表一个数据文件中的内容
    hwLabels:每一项表示traningMat中对应项的数据到底代表数字几
    '''
    hwLabels = []
    # 获取目录traningDigits内容(即数据集文件名),并储存在一个list中
    trainingFileList = listdir('trainingDigits')
    m = len(trainingFileList)  # 当前目录文件数
    # 初始化m维向量的训练集,每个向量1024维
    trainingMat = np.zeros((m, 1024))
    for i in range(m):
        fileNameStr = trainingFileList[i]
        # 从文件名中解析分类数字,作为分类标签
        hwLabels.append(classnumCut(fileNameStr))
        # 将图片矩阵转换为向量并储存在新的矩阵中
        trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)
    return hwLabels, trainingMat


def createDataSet():
    '''
    从trainingDigits文件夹下面读取所有数据文件,返回:
    trainingMat:所有训练数据,每一行代表一个数据文件中的内容
    hwLabels:每一项表示traningMat中对应项的数据到底代表数字几
    '''
    hwLabels = []

    # 获取目录traningDigits内容(即数据集文件名),并储存在一个list中
    trainingFileList = listdir('trainingDigits')
    m = len(trainingFileList)  # 当前目录文件数

    # 初始化m维向量的训练集,每个向量1024维
    trainingMat = np.zeros((m, 1024))

    for i in range(m):

        fileNameStr = trainingFileList[i]

        # 从文件名中解析分类数字,作为分类标签
        hwLabels.append(classnumCut(fileNameStr))

        # 将图片矩阵转换为向量并储存在新的矩阵中
        trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)
    return trainingMat, hwLabels

# 测试函数


def handwritingTest():

    # 构建训练集
    hwLabels, trainingMat = trainingDataSet()

    # 从testDigits里面拿到测试集
    testFileList = listdir('testDigits')

    # 错误数
    errorCount = 0.0

    # 测试集总样本数
    mTest = len(testFileList)

    # 获取程序运行到此处的时间(开始测试)
    t1 = time.time()

    for i in range(mTest):

        # 得到当前文件名
        fileNameStr = testFileList[i]

        # 从文件名中解析分类数字
        classNumStr = classnumCut(fileNameStr)

        # 将图片矩阵转换为向量
        vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)

        # 调用knn算法进行测试
        classifierResult = classify(vectorUnderTest, trainingMat, hwLabels, 5)
        print("the classifier came back with: %d, the real answer is: %d" %
              (classifierResult, classNumStr))

        # 预测结果不一致,则错误数+1
        if (classifierResult != classNumStr):
            errorCount += 1.0

    print("\nthe total number of tests is: %d" % mTest)
    print("the total number of errors is: %d" % errorCount)
    print("the total error rate is: %f" % (errorCount/float(mTest)))

    # 获取程序运行到此处的时间(结束测试)
    t2 = time.time()

    # 测试耗时
    print("Cost time: %.2fmin, %.4fs." % ((t2-t1)//60, (t2-t1) % 60))


if __name__ == "__main__":
    handwritingTest()

运行结果

1
2
3
4
the total number of tests is: 946
the total number of errors is: 17
the total error rate is: 0.017970
Cost time: 1.00min, 19.8545s.

实验总结

通过本次实验,我大致熟悉了 kNN 分类器的一些使用场景,原理简单但是非常实用。