选修了模式识别这门课,完成一个简单的基于KNN的分类项目:手写体分类任务。
首先从网站上下载数据,格式为.idx3-ubyte和.idx1-ubyte,分别对应的是图片和标签的数据格式。首先需要对其进行解码,即转化成可以进行分类的格式,换个说法就是对文件中的数据进行读取。
读取部分按照文件中的存储格式进行读取,分别对两种文件进行解析。函数代码如下:
def decode_idx3_ubyte(idx3_ubyte_file):
bin_data = open(idx3_ubyte_file, 'rb').read()
# 解析文件头信息,依次为魔数、图片数量、每张图片高、每张图片宽
offset = 0
fmt_header = '>iiii'
magic_number, num_images, num_rows, num_cols = struct.unpack_from(fmt_header, bin_data, offset)
print '魔数:%d, 图片数量: %d张, 图片大小: %d*%d' % (magic_number, num_images, num_rows, num_cols)
# 解析数据集
image_size = num_rows * num_cols
offset += struct.calcsize(fmt_header)
fmt_image = '>' + str(image_size) + 'B'
images = np.empty((num_images, num_rows, num_cols))
for i in range(num_images):
if (i + 1) % 10000 == 0:
print '已解析 %d' % (i + 1) + '张'
images[i] = np.array(struct.unpack_from(fmt_image, bin_data, offset)).reshape((num_rows, num_cols))
offset += struct.calcsize(fmt_image)
return images
def decode_idx1_ubyte(idx1_ubyte_file):
bin_data = open(idx1_ubyte_file, 'rb').read()
# 解析文件头信息,依次为魔数和标签数
offset = 0
fmt_header = '>ii'
magic_number, num_images = struct.unpack_from(fmt_header, bin_data, offset)
print '魔数:%d, 图片数量: %d张' % (magic_number, num_images)
# 解析数据集
offset += struct.calcsize(fmt_header)
fmt_image = '>B'
labels = np.empty(num_images)
for i in range(num_images):
if (i + 1) % 10000 == 0:
print '已解析 %d' % (i + 1) + '张'
labels[i] = struct.unpack_from(fmt_image, bin_data, offset)[0]
offset += struct.calcsize(fmt_image)
return labels
完成读取任务以后便进行分类,此处使用KNN方法,选取最近的K的点进行判定。具体原理可以baidu一下。KNN在实际使用的时候还是有很多不太好的地方,比如当训练集较大时,KNN要求每一个测试集的图片都需要与所有的样本进行比较,所以速度较慢。当然,对于MNIST60000+10000的数据量来说,我们还是可以接受的。此处不再赘述。
分类过程包含在classify方法中。前面使用欧氏距离进行计算两个图片向量之间的距离,之后进行排序后查找最小的K个向量,统计其标签后选取最多的一项作为分类结果。最后方法返回标签,即分类结果。
def classify(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):
#sortedDistIndicies[0]返回的是距离最小的数据样本的序号
#labels[sortedDistIndicies[0]]距离最小的数据样本的标签
voteIlabel = labels[sortedDistIndicies[i]]
#以标签为key,支持该标签+1
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
#排序
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
之后在主函数中进行各种函数的调用,解码、设置存储向量、进行分类(调用classify)、统计正确率,此处不再一一赘述,代码中也有注释。读者如有不理解的地方,欢迎讨论。
附完整代码:(文件须去MNIST网站上下载)
# -*- coding:utf-8 -*-
import operator
import struct
import numpy as np
# 训练集
train_images_idx3_ubyte_file = 'train-images.idx3-ubyte'
# 训练集标签
train_labels_idx1_ubyte_file = 'train-labels.idx1-ubyte'
# 测试集
test_images_idx3_ubyte_file = 't10k-images.idx3-ubyte'
# 测试集标签
test_labels_idx1_ubyte_file = 't10k-labels.idx1-ubyte'
def decode_idx3_ubyte(idx3_ubyte_file):
bin_data = open(idx3_ubyte_file, 'rb').read()
# 解析文件头信息,依次为魔数、图片数量、每张图片高、每张图片宽
offset = 0
fmt_header = '>iiii'
magic_number, num_images, num_rows, num_cols = struct.unpack_from(fmt_header, bin_data, offset)
print '魔数:%d, 图片数量: %d张, 图片大小: %d*%d' % (magic_number, num_images, num_rows, num_cols)
# 解析数据集
image_size = num_rows * num_cols
offset += struct.calcsize(fmt_header)
fmt_image = '>' + str(image_size) + 'B'
images = np.empty((num_images, num_rows, num_cols))
for i in range(num_images):
if (i + 1) % 10000 == 0:
print '已解析 %d' % (i + 1) + '张'
images[i] = np.array(struct.unpack_from(fmt_image, bin_data, offset)).reshape((num_rows, num_cols))
offset += struct.calcsize(fmt_image)
return images
def decode_idx1_ubyte(idx1_ubyte_file):
bin_data = open(idx1_ubyte_file, 'rb').read()
# 解析文件头信息,依次为魔数和标签数
offset = 0
fmt_header = '>ii'
magic_number, num_images = struct.unpack_from(fmt_header, bin_data, offset)
print '魔数:%d, 图片数量: %d张' % (magic_number, num_images)
# 解析数据集
offset += struct.calcsize(fmt_header)
fmt_image = '>B'
labels = np.empty(num_images)
for i in range(num_images):
if (i + 1) % 10000 == 0:
print '已解析 %d' % (i + 1) + '张'
labels[i] = struct.unpack_from(fmt_image, bin_data, offset)[0]
offset += struct.calcsize(fmt_image)
return labels
def classify(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):
#sortedDistIndicies[0]返回的是距离最小的数据样本的序号
#labels[sortedDistIndicies[0]]距离最小的数据样本的标签
voteIlabel = labels[sortedDistIndicies[i]]
#以标签为key,支持该标签+1
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
#排序
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
if __name__ == '__main__':
train_images = decode_idx3_ubyte(train_images_idx3_ubyte_file)
train_labels = decode_idx1_ubyte(train_labels_idx1_ubyte_file)
test_images = decode_idx3_ubyte(test_images_idx3_ubyte_file)
test_labels = decode_idx1_ubyte(test_labels_idx1_ubyte_file)
m = 60000 # 创建一个读入数据的数组,进行图片信息的记录
trainingMat = np.zeros((m, 784)) # 置为零
# 文件名下划线_左边的数字是标签
for i in range(m):
for j in range(28):
for k in range(28):
trainingMat[i, 28*j+k] = train_images[i][j][k]
errorCount = 0.0
mTest = 10000
for i in range(mTest):
classNumStr = test_labels[i]
vectorUnderTest = np.zeros(784)
for j in range(28):
for k in range(28):
vectorUnderTest[28*j+k] = test_images[i][j][k] #第i幅测试图
Result = classify(vectorUnderTest, trainingMat, train_labels, 3)
print("识别结果:%d 正确答案:%d" % (Result, classNumStr))
if (Result != classNumStr):
errorCount += 1.0
print("错误")
print("\n错误数: %d" % errorCount)
print("\n错误率: %f" % (errorCount / float(mTest)))
print '数据处理结束'