简单地说,K近邻算法采用测量不同特征值之间的距离方法进行分类。该算法具有一下特点。
K近邻算法的工作原理是:存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前 个最相似的数据,这就是K近邻算法中 的出处,通常 是不大于20的整数。最后,选择 个最相似数据中出现次数最多的分类,作为新数据的分类。
K近邻算法的一般流程
下载数据集:手写数字数据集
可以看到该数据集已经切分好了训练集和测试集。其目录结构如下:
digits 目录下有两个文件夹,分别是:
- trainingDigits:训练数据,1934个文件,每个数字大约200个文件。
- testDigits:测试数据,946个文件,每个数字大约100个文件。
打开jupyter,查看一下样本格式。
# 查看一下文件内容
!cat ./data/digits/trainingDigits/0_1.txt # 前面需要加上!,否则会报错
我们需要把一个32x32的二进制图像矩阵转换为1x1024的向量。
import numpy as np
def img2vector(filename):
# 创建向量
returnVect = np.zeros((1, 1024))
# 打开数据文件,读取每行内容
fr = open(filename)
for i in range(32):
# 读取每一行
lineStr = fr.readline()
# 将每行前 32 字符转成 int 存入向量
for j in range(32):
returnVect[0, 32*i+j] = int(lineStr[j])
return returnVect
接着测试一下该方法。
# 测试一下
vect = img2vector('./data/digits/trainingDigits/0_1.txt')
print(vect.shape)
print(vect)
算法实现过程:
本次k-近邻使用欧式距离作为计算公式。
import operator
#分类器采用欧式距离
def classify0(inX, dataSet, labels, k):
"""
参数:
- inX: 用于分类的输入向量
- dataSet: 输入的训练样本集
- labels: 样本数据的类标签向量
- k: 用于选择最近邻居的数目
"""
# 获取样本数据数量
dataSetSize = dataSet.shape[0]
# 矩阵运算,计算测试数据与每个样本数据对应数据项的差值
# tile(a, (10, 1)) 表示构造了10行a这个向量
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
# sqDistances 上一步骤结果平方和
sqDiffMat = diffMat**2
sqDistances = sqDiffMat.sum(axis=1)
# 取平方根,得到距离向量
distances = sqDistances**0.5
# 按照距离从低到高排序
sortedDistIndicies = distances.argsort()
classCount = {}
# 依次取出最近的样本数据
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 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
group, labels = createDataSet()
classify0([0, 0], group, labels, 3)
实验的步骤:
# 进行手写数字测试
from os import listdir
def handwritigCalssTest():
hwLabels = [] # 样本数据的标签
trainingFileList = listdir('./data/digits/trainingDigits') # 读取样本数据列表
m = len(trainingFileList) # 样本数据的实例数
trainingMat = np.zeros((m, 1024)) # 初始化样本数据矩阵
# 依次读取所有样本到数据矩阵中
for i in range(m):
# 提取文件名中的数字
fileNameStr = trainingFileList[i]
fileStr = fileNameStr.split('.')[0]
classNumStr = int(fileStr.split('_')[0])
hwLabels.append(classNumStr)
# 将样本数据存入矩阵
trainingMat[i, :] = img2vector(
'./data/digits/trainingDigits/%s' % fileNameStr)
# 循环读取测试数据
testFileList = listdir('./data/digits/testDigits')
# 初始化错误率
errorCount = 0.0
mTest = len(testFileList)
# 循环测试每个测试数据文件
for i in range(mTest):
# 提取文件名中的数字
fileNameStr = testFileList[i]
fileStr = fileNameStr.split('.')[0]
classNumStr = int(fileStr.split('_')[0])
# 提取数据向量
vectorUnderTest = img2vector('./data/digits/testDigits/%s' % fileNameStr)
# 对数据文件进行分类
classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
# 打印 K 近邻算法分类结果和真实的分类
print("测试样本 %d, 分类器预测: %d, 真实类别: %d" %
(i+1, classifierResult, classNumStr))
# 判断K 近邻算法结果是否准确
if (classifierResult != classNumStr):
errorCount += 1.0
# 打印错误率
print("\n错误分类计数: %d" % errorCount)
print("\n错误分类比例: %f" % (errorCount/float(mTest)))
print('\n分类器准确率: %f' % (1 - (errorCount/float(mTest))))
训练并测试分类器。
handwritigCalssTest()
可以看到我们实现的k-近临分类器在该数据集上的效果达到了98%。接着使用scikit-learn机器学习库自带的k-近临分类器来实现上述过程。
hwLabels = [] # 样本数据的标签
trainingFileList = listdir('./data/digits/trainingDigits') # 读取样本数据列表
m = len(trainingFileList) # 样本数据的实例数
trainingMat = np.zeros((m, 1024)) # 初始化样本数据矩阵
# 依次读取所有样本到数据矩阵中
for i in range(m):
# 提取文件名中的数字
fileNameStr = trainingFileList[i]
fileStr = fileNameStr.split('.')[0]
classNumStr = int(fileStr.split('_')[0])
hwLabels.append(classNumStr)
# 将样本数据存入矩阵
trainingMat[i, :] = img2vector('./data/digits/trainingDigits/%s' % fileNameStr)
# 循环读取测试数据
testFileList = listdir('./data/digits/testDigits')
# 初始化错误率
testLabels = []
mTest = len(testFileList)
print(mTest)
vectorUnderTest = np.zeros((mTest, 1024)) # 初始化样本数据矩阵
print(vectorUnderTest.shape)
# 循环测试每个测试数据文件
for i in range(mTest):
# 提取文件名中的数字
fileNameStr = testFileList[i]
fileStr = fileNameStr.split('.')[0]
classNumStr = int(fileStr.split('_')[0])
testLabels.append(classNumStr)
# 提取数据向量
vectorUnderTest[i, :] = img2vector('./data/digits/testDigits/%s' % fileNameStr)
from sklearn.neighbors import KNeighborsClassifier
model = KNeighborsClassifier()
model = model.fit(trainingMat, hwLabels)
model.score(vectorUnderTest, testLabels)
到这里该demo案例基本完成了,可以看到分类效果基本相当,但是使用sklearn库进行机器学习要简便很多。
demo下载地址:demo案例源码