最邻近算法(KNN)识别数字验证码

应用场景

  对于简单的数字型验证码的自动识别。前期已经完成的工作是通过切割将验证码图片切割成一个一个的单个数字的图片,并按照对应的数字表征类别进行分类(即哪些图片表示数字7,哪些表示8),将各种数字的图片转换成32×32的二值矩阵,并存放在.txt中,每一种数字表示所对应的.txt的文件名为:“数字类标号_序号.txt”。取一部分这样的.txt作为已知样本集,另一部分作为验证集。使用最邻近算法KNN实现对数字进行识别。

最邻近算法(KNN,K-Nearest Neighbor)

  可以说KNN是最简单的分类算法了。已知的数据集是带类标号的数据。KNN本质是基于一种数据统计的方法!其实很多机器学习算法也是基于数据统计的。

  KNN是一种memory-based learning,也叫instance-based learning,属于lazy learning。即它没有明显的前期训练过程,而是程序开始运行时,把数据集加载到内存后,不需要进行训练,就可以开始分类了。下面的源码部分的注释,我用到了“训练”这样的表述,是为了在这个领域方便交流的表达。

  具体是每次来一个未知的样本点,就在附近找K个最近的点进行投票。或者说,就是计算这个未知的样本点(或者在数据挖掘领域叫“元组”)和所有已知点的欧几里得距离,选择距离最近的前k个已知样本进行投票,即这k个已知样本中,哪一种类型占的比例最大,那么就认为这个未知的样本属于哪一个分类。

源码

from numpy import *
import operator
from os import listdir


def knn(k, testdata, traindata, labels):	#k为人为设定的阈值,表示在最接近的k个训练元组中获得类标号
    
	# testdata:[特征1,特征2,特征3,……,特征1024]
    # traindata:[[特征1,特征2,特征3,……,特征1024],[特征1,特征2,特征3,……,特征1024],[特征1,特征2,特征3,……,特征1024],……]
    #调用(numpy中的)shape函数来返回traindata的长度,也就是traindata.shape[0]返回的是训练集的元组个数
    traindatasize = traindata.shape[0]

    #调用(numpy中的)tile将一维的testdata扩展成一个二维和traindata规模相同的矩阵。
    #tile得到的矩阵和traindata作差,得到的矩阵为dif
    dif = tile(testdata, (traindatasize, 1)) - traindata
   
    sqdif = dif ** 2		 #计算每个特征的差的平方
    
	# sumsqdif已经成为一维列表
    sumsqdif = sqdif.sum(axis=1)		# axis=1 是按行求和(axis=0是列求和)
    
	#每个和数作开平方操作,存放到一维的distance中
    distance = sumsqdif ** 0.5
    
	#对distance中的元素进行排序,argsort函数返回的是数组值从小到大的索引值,即返回的第一个元素是distance中最小值的索引值,以此类推
    sortdistance = distance.argsort()	# sortdistance指的是测试数据与各训练数据的距离由近到远排序之后的结果列表

    #声明一个字典count存放排序前k小的类标号及其出现频数
    count = {}  # {"类别":"次数"}
    for i in range(0, k):
        vote = labels[sortdistance[i]]  # 当前距离的类别是谁(由近至远)
        #count.get(vote, 0)的作用是在字典count中查找vote并返回其值,如果vote键不存在则返回0
        count[vote] = count.get(vote, 0) + 1
    
	#使用sorted()产生一个新的排序后的列表,当reverse参数置为真时,这个列表是降序排序的
    #item()方法把字典中每对key和value组成一个元组,并把这些元组放在列表中返回。
    #key=operator.itemgetter(1)表示按第2个域排序
    #因为count.items()返回一个二维列表,所以排序后sortcount也是二维的。
    sortcount = sorted(count.items(), key=operator.itemgetter(1), reverse=True)
    
	#取sortcount第一行(出现次数最多的键值对)的第一个元素就是对应的预测的分类标号。
    return sortcount[0][0]


# 数据加载
#预处理,将源训练数据处理成一个长度为1024的整型列表
def datatoarray(fname):
    arr = []
    fh = open(fname)
    for i in range(0, 32):
        #逐行读入
        thisline = fh.readline()
        for j in range(0, 32):
            arr.append(int(thisline[j]))
    return arr



# 取文件名前缀(类别)
def seplabel(fname):
    #用split来切割训练元组的文件名,分隔符是下划线,split返回的是一个字符串列表,我们只需要这个列表的第一个元素,来作为这个元组的类标号,并强转为整型
    label = int(fname.split("_")[0])

    return label


# 建立训练数据
# labels:[类别,类别,类别,类别,……]有多少个训练元组就有多少个类别标号class label
# trainarr:[[特征1,特征2,特征3,……,特征1024],[特征1,特征2,特征3,……,特征1024],[特征1,特征2,特征3,……,特征1024],……]一个字符用一个特征表示
# 可见,trainarr是一个矩阵(二维数组),有num行,1024列。num是训练元组个数,也等于lables数组的长度。一个字符用一个特征表示
def traindata():
    labels = []

    #trainfile是一个存储训练元组对应的文件名的一维数组
    trainfile = listdir("./traindata")

    num = len(trainfile)
    #(numpy中的)zeros函数功能是创建给定类型的矩阵,并初始化为0。在这里用来初始化一个num行,1024列的矩阵trainarr。
    trainarr = zeros((num, 1024))

    #遍历每一个训练元组
    for i in range(0, num):
        thisname = trainfile[i]
        
		#提取当前这个训练元组的类标号(分类)
        thislabel = seplabel(thisname)
       
		#将当前训练元组的类标号存放进labels数组
        labels.append(thislabel)
        
		#将当前这个训练元组(第i个)的内容转换成一个一维的特征序列覆盖trainarr的第i行
        trainarr[i, :] = datatoarray("./traindata/" + thisname)
    
	#函数返回训练数据集trainarr和对应的类标号数组
    return trainarr, labels



#执行测试
trainarr, labels = traindata()

#当前要测试的元组
thistestfile = "6_21.txt"

#将这个测试元组转换成一维的特征序列(长度为1024)
testarr = datatoarray("./testdata/" + thistestfile)

#调用函数knn,返回结果
rst = knn(3, testarr, trainarr, labels)
print(rst)
原文地址:https://www.cnblogs.com/d0main/p/6683427.html