k-近邻算法 From Machine Learning

Python 3.6 WIN10环境下,PyCharm IDE写代码,前半部分(电影分类问题)使用cmd执行数据输入,后半部分(约会网站问题开始)直接在IDE的Console区域执行数据输入;path环境路径使用aconda,暂时发现其他的环境路径遇到matplotlib无法安装成功的问题:

k-近邻算法思想如下:

1.计算已知类别数据集中的点与当前点之间的距离

2.按照距离递增次序排序

3.选取与当前点距离最小的k个点

4.确定前k个点所在类别的出现频率

5.返回前k个点出现频率最高的类别作为当前点的预测分类

主要运用到高中数学知识,求欧几里得空间两点距离:



创建kNN.py文件,cmd环境需要cd到kNN.py所在目录。

1.电影分类问题,代码如下:

①收集数据:可使用任何方法

②准备数据:距离计算所需要的数值,最好是结构化的数据格式

③分析数据:可使用任何方法

④训练算法:此步骤不适用于k-近邻算法

⑤测试算法:计算错误率

⑥使用算法:首先需要输入样本数据和结构化的输出结果,然后运行k-近邻算法判定输入数据分别属于哪个分类,最后应用对计算出的分类执行后续的处理。


首先创建数据集:

from numpy import *
import operator
def createDataSet():
    group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]]) #创建数据集
    labels = ['A','A','B','B'] #创建标签
    return group,labels

其cmd对应执行命令如下:

>>> group,labels = kNN.createDataSet()
>>> group
array([[ 1. ,  1.1],
       [ 1. ,  1. ],
       [ 0. ,  0. ],
       [ 0. ,  0.1]])
>>> labels
['A', 'A', 'B', 'B']

分类方法,在cmd中调用执行:inX-要分类的输入量; dataSet-输入的训练样本集;labels-标签向量;k-旋转最近邻居的数目

最后返回的就是,该输入量最可能所属的元素标签,即发生频率最高的标签。

def classify0(inX, dataSet, labels, k):
    #求出样本集的行数,也就是labels标签的数目
    dataSetSize = dataSet.shape[0]
    #构造输入值和样本集的差值矩阵
    diffMat = tile(inX, (dataSetSize,1)) - dataSet
    #计算欧式距离
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5
    #求距离从小到大排序的序号
    sortedDistIndicies = distances.argsort()
    #对距离最小的k个点统计对应的样本标签
    classCount={}
    for i in range(k): #选择距离最小的k个点
        #取第i+1邻近的样本对应的类别标签
        voteIlabel = labels[sortedDistIndicies[i]]
        #以标签为key,标签出现的次数为value将统计到的标签及出现次数写进字典
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    #对字典按value从大到小排序
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1), reverse=True) #排序
    #返回排序后字典中最大value对应的key
    return sortedClassCount[0][0]

其对应的cmd命令执行操作:

>>> kNN.classify0([0,0], group, labels, 3)
'B'
>>> kNN.classify0([0,1], group, labels, 3)
'B'
>>> kNN.classify0([1,1.1], group, labels, 3)
'A'

2.约会网站问题

解析已知样本数据,样本数据包含特征值和目标值。将未知对象(由特征值定义)归类(目标值),三类:1.不喜欢的人 2.魅力一般的人 3.极具魅力的人

流程如下:

①收集数据:提供文本文件datingTestSet.txt

②准备数据:使用Python解析文本文件

③分析数据:使用Matplotlib画二维扩散图

④训练算法:此步骤不适用于k-近邻算法

⑤测试算法:使用部分数据作为测试样本。

    测试样本和非测试样本的区别在于:测试样本时已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误。

⑥使用算法:产生简单的命令行程序,然后可输入一些特征数据以判断对方是否为自己喜欢的类型

代码如下:

def file2matrix(filename):
    #打开文件
    fr = open(filename)
    #得到文件的行数
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines)
    #创建以零填充的矩阵,为了简化处理,将该矩阵的另一维度设置为固定值3,可按照自己的实际需求增加相应的代码以适应变化的输入值
    returnMat = zeros((numberOfLines, 3))
    classLabelVector = []
    index = 0
    for line in arrayOLines:
        line = line.strip()#截取掉所有的回车字符
        listFromLine = line.split('\t') #然后使用tab字符\t将上一步得到的整行数据分割成一个元素列表
        returnMat[index,:] = listFromLine[0:3] #选取前3个元素,将它们存储到特征矩阵中
        if(listFromLine[-1] == 'largeDoses'): #Python语言可使用索引值-1表示列表中的最后一列元素,利用该负索引,可方便地将列表的最后一列存储到向量classLabelVector中
            classLabelVector.append(3)#listFromLine[-1] = '3' #为之后的使用该算法来判断是否喜欢一个人时而改
        elif (listFromLine[-1] == 'smallDoses'):
            classLabelVector.append(2)#listFromLine[-1] = '2' #为之后的使用该算法来判断是否喜欢一个人时而改
        else:
            classLabelVector.append(1)#listFromLine[-1] = '1' #为之后的使用该算法来判断是否喜欢一个人时而改
        #classLabelVector.append(float(listFromLine[-1]))#必须明确地通知解释器,告诉它列表中存储的元素值为整型,否则Python语言会将这些元素当做字符串处理
        index += 1

    return returnMat,classLabelVector


cmd下命令如下,包括使用matplotlib图像化:

>>> import kNN
>>> from numpy import *
>>> datingDataMat,datingLabels = kNN.file2matrix('datingTestSet.txt')
>>> import matplotlib
>>> import matplotlib.pyplot as plt
>>> fig = plt.figure()
>>> ax = fig.add_subplot(111)
>>> ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
<matplotlib.collections.PathCollection object at 0x000002B853F7F860>
>>> plt.show()


若报错:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'array' is not defined

则添加:

>>>from numpy import *


效果如下,带有样本分类标签的约会数据散点图,虽然能够比较容易地区分数据点从属类别,但依然很难根据这张图得出结论性信息:



将数据列1,2改为0,1:

ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
改为:

ax.scatter(datingDataMat[:,0], datingDataMat[:,1], 15.0*array(datingLabels), 15.0*array(datingLabels))


效果如下,每年获取的飞行常客里程数与玩视频游戏所占百分比的约会数据散点图。约会数据有三个特征,通过图中展示的两个特征更容易区分数据点从属的类别:





归一化处理:

由欧式距离公式可知,每年获得的飞行常客里程数,对计算结果影响最大。而一般认为这三种特征值应该是同等重要的,因此作为三个等权重的特征之一,飞行常客里程数不应该如此严重地影响到计算结果,因此要对数值进行归一化处理,如将取值范围处理为0到1或-1到1之间。下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:

newValue = (oldValue - min)/(max - min)

其中min和max分别是数据集中的最小特征值和最大特征值。虽然改变数值取值范围增加了分类器的复杂度,但为了得到准确结果,必须这么做。因此就需要再增加一个新函数autoNorm()来将数字特征值转化为0到1的区间

代码如下:

def autoNorm(dataSet):
    minVals = dataSet.min(0) #每列最小值
    maxVals = dataSet.max(0) #每列最大值
    ranges = maxVals - minVals #函数计算可能的取值范围
    normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - tile(minVals, (m,1)) #tile将变量内容复制成输入矩阵同样大小的矩阵
    normDataSet = normDataSet/tile(ranges, (m,1)) #特征值相除,为了归一化特征值,必须使用当前值减去最小值,然后除以取值范围;在某些数值处理软件包,/可能意味着矩阵除法,NumPy库中,
    return normDataSet,ranges,minVals            #矩阵除法需要使用函数linalg.solve(matA,matB)


cmd:

>>> import kNN
>>> datingDataMat,datingLabels=kNN.file2matrix('datingTestSet.txt')
>>> normMat,ranges,minVals = kNN.autoNorm(datingDataMat)
>>> normMat
array([[ 0.44832535,  0.39805139,  0.56233353],
       [ 0.15873259,  0.34195467,  0.98724416],
       [ 0.28542943,  0.06892523,  0.47449629],
       ...,
       [ 0.29115949,  0.50910294,  0.51079493],
       [ 0.52711097,  0.43665451,  0.4290048 ],
       [ 0.47940793,  0.3768091 ,  0.78571804]])
>>> ranges
array([  9.12730000e+04,   2.09193490e+01,   1.69436100e+00])
>>> minVals
array([ 0.      ,  0.      ,  0.001156])

测试算法,验证分类器:

测试分类器,若分类器的正确率满足要求,则可使用该软件来处理约会网站提供的约会名单了。机器学习算法一个很重要的工作就是评估算法的正确率,通常只提供已有数据的90%作为训练样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率。需注意的是,10%的测试数据应该是随机选择的,由于datingTestSet.txt提供的数据并没有按照特定目的来排序,所以可随意选择10%数据而影响其随机性。

要测试分类器效果,在文件中再添加函数datingClassTest,该函数是自包含的,可在任何时候在Python运行环境中使用该函数测试分类器效果,代码如下:

def datingClassTest():
    hoRatio = 0.1 #前10%的数据作为测试数据集,后90%数据作为训练数据集
    #从文件中读取数据并将其转换为归一化特征值
    datingDataMat,datingLabels = file2matrix('datingTestSet.txt')
    normMat,ranges,minVals = autoNorm(datingDataMat)
    #计算测试向量的数量,决定了normMat向量中哪些数据用于测试,哪些数据用于分类器的训练样本
    m = normMat.shape[0]
    numTestVecs = int(m*hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        #将测试数据和训练数据输入到分类器中
        #normMat[i,:] 取出第i行的所有数据
        #normMat[numTestVecs:m,:]取出numTestVecs之后到m的每行数据
        #datingLabels[numTestVecs:m]取出numTestVecs之后到m的每行的标签
        #k值为3
        classifierResult = classify0(normMat[i,:], normMat[numTestVecs:m,:],datingLabels[numTestVecs:m], 3)
        print("the classifier came back with: %d, the real answer is: %d" %(classifierResult, datingLabels[i]))
        #如果错误不一致,则错误数加1
        if(classifierResult != datingLabels[i]):errorCount += 1.0
    #计算出错误率并输出结果
    print("the total error rate is:%f"%(errorCount/float(numTestVecs)))

cmd,错误率为5%:

>>> import kNN
>>> from numpy import *
>>> kNN.datingClassTest()
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 1
the total error rate is:0.050000

其中,若改变hoRatio的值为0.01,也就是前1%的数据作为测试数据集,后99%的数据作为训练数据集,可看到,测试数据样本为10个,同时错误率也变为了0%:

>>> import kNN
>>> from numpy import *
>>> kNN.datingClassTest()
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the total error rate is:0.000000
若hoRation改为0.2,也就是前20%数据作为测试数据集,后80%作为样本数据集,则可看到错误率为8%。

最后使用该算法来判断是否喜欢一个人:直接在PyCharm IDE中运行了,不再cmd中去运行了,再添加如下代码:

def classifyPerson():
	#输出结果
	resultList = ['不喜欢','有些喜欢','非常喜欢']
	#三维特征用户输入
	precentTats = float(input("玩视频游戏所耗时间百分比:"))
	ffMiles = float(input("每年获得的飞行常客里程数:"))
	iceCream = float(input("每周消费的冰激淋公升数:"))
	#打开的文件名
	filename = "datingTestSet.txt"
	#打开并处理数据
	datingDataMat, datingLabels = file2matrix(filename)
	#训练集归一化
	normMat, ranges, minVals = autoNorm(datingDataMat)
	#生成NumPy数组,测试集
	inArr = np.array([ffMiles, precentTats, iceCream])
	#测试集归一化
	norminArr = (inArr - minVals) / ranges
	#返回分类结果
	classifierResult = classify0(norminArr, normMat, datingLabels, 3)
	#打印结果
	print("你可能%s这个人" % (resultList[classifierResult-1]))

if __name__ == '__main__':
    # datingClassTest()
    classifyPerson()

直接Run kNN.py,在Console区域输出结果如下:

玩视频游戏所耗时间百分比:10
每年获得的飞行常客里程数:10000
每周消费的冰激淋公升数:0.5
你可能有些喜欢这个人


到此,约会网站的算法已基本完成,之后只要具有某个人的三个特征值:玩视频游戏所耗时间百分比,每年获得的飞行常客里程数,每周消费的冰激凌公升数;然后将它们输入该算法,即可判断出对此人的喜欢程度。该模型所谓的喜欢程度的判断标准完全基于之前的样本训练数据,因此这个数据选择的前提也很重要。


整个完整代码如下:

from numpy import *
import numpy as np
import operator

#分类器,k-近邻算法
def classify0(inX, dataSet, labels, k):
    #求出样本集的行数,也就是labels标签的数目
    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()
    #对距离最小的k个点统计对应的样本标签
    classCount={}
    for i in range(k): #选择距离最小的k个点
        #取第i+1邻近的样本对应的类别标签
        voteIlabel = labels[sortedDistIndicies[i]]
        #以标签为key,标签出现的次数为value将统计到的标签及出现次数写进字典
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    #对字典按value从大到小排序
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1), reverse=True) #排序
    #返回排序后字典中最大value对应的key
    return sortedClassCount[0][0]

def createDataSet():
    group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]]) #创建数据集
    labels = ['A','A','B','B'] #创建标签
    return group,labels

#解析文本记录
def file2matrix(filename):
    #打开文件
    fr = open(filename)
    #得到文件的行数
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines)
    #创建以零填充的矩阵,为了简化处理,将该矩阵的另一维度设置为固定值3,可按照自己的实际需求增加相应的代码以适应变化的输入值
    returnMat = zeros((numberOfLines, 3))
    classLabelVector = []
    index = 0
    for line in arrayOLines:
        line = line.strip()#截取掉所有的回车字符
        listFromLine = line.split('\t') #然后使用tab字符\t将上一步得到的整行数据分割成一个元素列表
        returnMat[index,:] = listFromLine[0:3] #选取前3个元素,将它们存储到特征矩阵中
        if(listFromLine[-1] == 'largeDoses'): #Python语言可使用索引值-1表示列表中的最后一列元素,利用该负索引,可方便地将列表的最后一列存储到向量classLabelVector中
            classLabelVector.append(3)# listFromLine[-1] = '3'
        elif (listFromLine[-1] == 'smallDoses'):
            classLabelVector.append(2)#listFromLine[-1] = '2'
        else:
            classLabelVector.append(1)#listFromLine[-1] = '1'
      #  classLabelVector.append(float(listFromLine[-1]))#必须明确地通知解释器,告诉它列表中存储的元素值为整型,否则Python语言会将这些元素当做字符串处理
        index += 1

    return returnMat,classLabelVector

#归一化特征值
def autoNorm(dataSet):
    minVals = dataSet.min(0) #每列最小值
    maxVals = dataSet.max(0) #每列最大值
    ranges = maxVals - minVals #函数计算可能的取值范围
    normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - np.tile(minVals, (m,1)) #tile将变量内容复制成输入矩阵同样大小的矩阵
    normDataSet = normDataSet/np.tile(ranges, (m,1)) #特征值相除,为了归一化特征值,必须使用当前值减去最小值,然后除以取值范围;在某些数值处理软件包,/可能意味着矩阵除法,NumPy库中,
    return normDataSet,ranges,minVals            #矩阵除法需要使用函数linalg.solve(matA,matB)

#测试算法:作为完整程序验证分类器
def datingClassTest():
    hoRatio = 0.1
    #从文件中读取数据并将其转换为归一化特征值
    datingDataMat,datingLabels = file2matrix('datingTestSet.txt')
    normMat,ranges,minVals = autoNorm(datingDataMat)
    #计算测试向量的数量,决定了normMat向量中哪些数据用于测试,哪些数据用于分类器的训练样本
    m = normMat.shape[0]
    numTestVecs = int(m*hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        #将测试数据和训练数据输入到分类器中
        #normMat[i,:] 取出第i行的所有数据
        #normMat[numTestVecs:m,:]取出numTestVecs之后到m的每行数据
        #datingLabels[numTestVecs:m]取出numTestVecs之后到m的每行的标签
        #k值为3
        classifierResult = classify0(normMat[i,:], normMat[numTestVecs:m,:],datingLabels[numTestVecs:m], 3)
        print("the classifier came back with: %d, the real answer is: %d" %(classifierResult, datingLabels[i]))
        #如果错误不一致,则错误数加1
        if(classifierResult != datingLabels[i]):errorCount += 1.0
    #计算出错误率并输出结果
    print("the total error rate is:%f"%(errorCount/float(numTestVecs)))

#预测函数:
"""
def classifyPerson():
    resultList = ['不喜欢', '有点喜欢', '非常喜欢']
    percentTats = float(input("玩视频游戏所耗时间百分比: "))
    ffMiles = float(input("每年获得飞行常客里程数: "))
    iceCream = float(input("每周消费冰激凌公升数: "))
    datingDataMat,datingLabels = file2matrix('datingTestSet.txt')
    normMat,ranges,minVals = autoNorm(datingDataMat)
    inArr = np.array([percentTats, ffMiles, iceCream])
    norminArr = (inArr - minVals) / ranges
    classifierResult = classify0(norminArr, normMat, datingLabels, 3)
    print("你可能对这个人:", resultList[classifierResult-1])
"""
def classifyPerson():
	#输出结果
	resultList = ['不喜欢','有些喜欢','非常喜欢']
	#三维特征用户输入
	precentTats = float(input("玩视频游戏所耗时间百分比:"))
	ffMiles = float(input("每年获得的飞行常客里程数:"))
	iceCream = float(input("每周消费的冰激淋公升数:"))
	#打开的文件名
	filename = "datingTestSet.txt"
	#打开并处理数据
	datingDataMat, datingLabels = file2matrix(filename)
	#训练集归一化
	normMat, ranges, minVals = autoNorm(datingDataMat)
	#生成NumPy数组,测试集
	inArr = np.array([ffMiles, precentTats, iceCream])
	#测试集归一化
	norminArr = (inArr - minVals) / ranges
	#返回分类结果
	classifierResult = classify0(norminArr, normMat, datingLabels, 3)
	#打印结果
	print("你可能%s这个人" % (resultList[classifierResult-1]))

if __name__ == '__main__':
    # datingClassTest()
    classifyPerson()





手写识别系统:

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

#分类器,k-近邻算法
def classify0(inX, dataSet, labels, k):
    #求出样本集的行数,也就是labels标签的数目
    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()
    #对距离最小的k个点统计对应的样本标签
    classCount={}
    for i in range(k): #选择距离最小的k个点
        #取第i+1邻近的样本对应的类别标签
        voteIlabel = labels[sortedDistIndicies[i]]
        #以标签为key,标签出现的次数为value将统计到的标签及出现次数写进字典
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    #对字典按value从大到小排序
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1), reverse=True) #排序
    #返回排序后字典中最大value对应的key
    return sortedClassCount[0][0]

def createDataSet():
    group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]]) #创建数据集
    labels = ['A','A','B','B'] #创建标签
    return group,labels

#解析文本记录
def file2matrix(filename):
    #打开文件
    fr = open(filename)
    #得到文件的行数
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines)
    #创建以零填充的矩阵,为了简化处理,将该矩阵的另一维度设置为固定值3,可按照自己的实际需求增加相应的代码以适应变化的输入值
    returnMat = zeros((numberOfLines, 3))
    classLabelVector = []
    index = 0
    for line in arrayOLines:
        line = line.strip()#截取掉所有的回车字符
        listFromLine = line.split('\t') #然后使用tab字符\t将上一步得到的整行数据分割成一个元素列表
        returnMat[index,:] = listFromLine[0:3] #选取前3个元素,将它们存储到特征矩阵中
        if(listFromLine[-1] == 'largeDoses'): #Python语言可使用索引值-1表示列表中的最后一列元素,利用该负索引,可方便地将列表的最后一列存储到向量classLabelVector中
            classLabelVector.append(3)# listFromLine[-1] = '3'
        elif (listFromLine[-1] == 'smallDoses'):
            classLabelVector.append(2)#listFromLine[-1] = '2'
        else:
            classLabelVector.append(1)#listFromLine[-1] = '1'
      #  classLabelVector.append(float(listFromLine[-1]))#必须明确地通知解释器,告诉它列表中存储的元素值为整型,否则Python语言会将这些元素当做字符串处理
        index += 1

    return returnMat,classLabelVector

#归一化特征值
def autoNorm(dataSet):
    minVals = dataSet.min(0) #每列最小值
    maxVals = dataSet.max(0) #每列最大值
    ranges = maxVals - minVals #函数计算可能的取值范围
    normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - np.tile(minVals, (m,1)) #tile将变量内容复制成输入矩阵同样大小的矩阵
    normDataSet = normDataSet/np.tile(ranges, (m,1)) #特征值相除,为了归一化特征值,必须使用当前值减去最小值,然后除以取值范围;在某些数值处理软件包,/可能意味着矩阵除法,NumPy库中,
    return normDataSet,ranges,minVals            #矩阵除法需要使用函数linalg.solve(matA,matB)

#测试算法:作为完整程序验证分类器
def datingClassTest():
    hoRatio = 0.1
    #从文件中读取数据并将其转换为归一化特征值
    datingDataMat,datingLabels = file2matrix('datingTestSet.txt')
    normMat,ranges,minVals = autoNorm(datingDataMat)
    #计算测试向量的数量,决定了normMat向量中哪些数据用于测试,哪些数据用于分类器的训练样本
    m = normMat.shape[0]
    numTestVecs = int(m*hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        #将测试数据和训练数据输入到分类器中
        #normMat[i,:] 取出第i行的所有数据
        #normMat[numTestVecs:m,:]取出numTestVecs之后到m的每行数据
        #datingLabels[numTestVecs:m]取出numTestVecs之后到m的每行的标签
        #k值为3
        classifierResult = classify0(normMat[i,:], normMat[numTestVecs:m,:],datingLabels[numTestVecs:m], 3)
        print("the classifier came back with: %d, the real answer is: %d" %(classifierResult, datingLabels[i]))
        #如果错误不一致,则错误数加1
        if(classifierResult != datingLabels[i]):errorCount += 1.0
    #计算出错误率并输出结果
    print("the total error rate is:%f"%(errorCount/float(numTestVecs)))

#预测函数:
"""
def classifyPerson():
    resultList = ['不喜欢', '有点喜欢', '非常喜欢']
    percentTats = float(input("玩视频游戏所耗时间百分比: "))
    ffMiles = float(input("每年获得飞行常客里程数: "))
    iceCream = float(input("每周消费冰激凌公升数: "))
    datingDataMat,datingLabels = file2matrix('datingTestSet.txt')
    normMat,ranges,minVals = autoNorm(datingDataMat)
    inArr = np.array([percentTats, ffMiles, iceCream])
    norminArr = (inArr - minVals) / ranges
    classifierResult = classify0(norminArr, normMat, datingLabels, 3)
    print("你可能对这个人:", resultList[classifierResult-1])
"""
def classifyPerson():
	#输出结果
	resultList = ['不喜欢','有些喜欢','非常喜欢']
	#三维特征用户输入
	precentTats = float(input("玩视频游戏所耗时间百分比:"))
	ffMiles = float(input("每年获得的飞行常客里程数:"))
	iceCream = float(input("每周消费的冰激淋公升数:"))
	#打开的文件名
	filename = "datingTestSet.txt"
	#打开并处理数据
	datingDataMat, datingLabels = file2matrix(filename)
	#训练集归一化
	normMat, ranges, minVals = autoNorm(datingDataMat)
	#生成NumPy数组,测试集
	inArr = np.array([ffMiles, precentTats, iceCream])
	#测试集归一化
	norminArr = (inArr - minVals) / ranges
	#返回分类结果
	classifierResult = classify0(norminArr, normMat, datingLabels, 3)
	#打印结果
	print("你可能%s这个人" % (resultList[classifierResult-1]))

def img2vector(filename):
    returnVect = zeros((1,1024))
    fr = open(filename)
    for i in range(32):
        lineStr = fr.readline()
        for j in range(32):
            returnVect[0,32*i+j] = int(lineStr[j])
    return returnVect

#把一个32x32的二进制图像矩阵转换为1x1024的向量,就可使用前面的分类器处理数字图像信息。
def handwritingClassTest():
    cpu_start = time.time()
    print('start:%f' % cpu_start)
    hwLabels = []
    #获取目录内容
    trainingFileList = listdir('trainingDigits')
    m = len(trainingFileList)
    trainingMat = 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('trainingDigits/%s' % fileNameStr)
    testFileList = listdir('testDigits')
    errorCount = 0.0
    mTest = len(testFileList)
    #循环读出文件的前32个字符值存储在NumPy数组中,最后返回数组
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileNameStr.split('_')[0])
        vectorUnderTest = img2vector('testDigits/%s' %fileNameStr)
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print("分类器返回值为:%d, 实际值为:%d" %(classifierResult, classNumStr))
        if(classifierResult != classNumStr): errorCount += 1.0
    print("\n错误总数为: %d" % errorCount)
    print("\n总错误率为: %f" %(errorCount/float(mTest)))
    cpu_end = time.time()
    print('end:%f' % cpu_end)
    print("total time: %f S" % (cpu_end - cpu_start))
if __name__ == '__main__':
    # datingClassTest()
    #classifyPerson()
   # testVector=img2vector('testDigits/0_13.txt')
    #print(testVector[0,0:31])
   # print(testVector[0,32:63])
    handwritingClassTest()


运行结果类似如下:

分类器返回值为:9, 实际值为:9
分类器返回值为:9, 实际值为:9
分类器返回值为:9, 实际值为:9

错误总数为: 10

总错误率为: 0.010571
end:1516780726.425686
total time: 33.872470 S

错误个数10个,错误率1.05%,耗时太长33.8s,这个还可以用其他方法改进。修改变量k值,函数handwritingClassTest随机选取训练样本、改变训练样本的数目,都会对k-近邻算法的错误率产生影响。

执行效率不高,k决策树就是k-近邻算法的优化版,可节省大量的计算开销。

testDigits以及trainingDigits

datingTestSet.txt如下:

第1列为每年获得的飞行常客里程数,第2列为玩视频游戏所耗时间百分比,第3列为每周消费的冰淇淋公升数,第4列为目标值:非常喜欢-largeDoses 有点喜欢-smallDoses 不喜欢-didntLike:

40920	8.326976	0.953952	largeDoses
14488	7.153469	1.673904	smallDoses
26052	1.441871	0.805124	didntLike
75136	13.147394	0.428964	didntLike
38344	1.669788	0.134296	didntLike


总结:k-近邻算法是分类数据最简单最有效的算法。本篇通过约会问题和手写识别系统使用k-近邻算法构造的分类器。k-近邻算法是基于实例的学习,使用算法时需要有接近实际数据的训练样本数据。k-近邻算法必须保存全部数据集,若训练数据集越大,则使用的存储空间也会越大,此外,由于必须对数据集中的每个数据计算距离值,实际使用时会非常耗时,由上面的手写识别系统可知。
k-近邻算法的另一个缺陷是它无法给出如何数据的基础结构信息,因此也无法知晓平均实例样本和典型是两样本具有什么特征。在使用概率测量方法处理分类问题时,可解决该问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值