本文主要针对于第一次运行代码中遇到的问题总结的汇总,由于自己是小白,第一次运行的时候遇到了各种各样的问题,经过多次的尝试终于解决
1 算法概述
1.1 算法特点
简单地说,k-近邻算法采用测量不同特征值之间的距离方法进行分类。
优点:精度高、对异常值不敏感、无数据输入假定
缺点:计算复杂度高、空间复杂度高
适用数据范围:数值型和标称型
标称型目标变量的结果只在有限目标集中取值,如真与假、动物分类集合{ 爬行类、鱼类、哺乳类、两栖类} ;数值型目标变量则可以从无限的数值集合中取值,如0.100、42.001、1000.743 等。
1.2 工作原理
存在一个训练样本集,并且每个样本都存在标签(有监督学习)。输入没有标签的新样本数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取出与样本集中特征最相似的数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,而且k通常不大于20。最后选择k个最相似数据中出现次数最多的分类,作为新数据的分类。(简化:给定训练数据样本和标签,对于某测试的一个样本数据,选择距离其最近的k个训练样本,这k个训练样本中所属类别最多的类即为该测试样本的预测标签。这里的距离一般是欧氏距离。)
1.3 k近邻算法的一般流程
(2)准备数据:距离计算所需要的数值,最好是结构化的数据格式。
(3)分析数据:可以使用任何方法。
(4)训练算法:此步骤不适用于k-近部算法。
(5)测试算法:计算错误率。
(6)使用算法:首先需要输入样本数据和结构化的输出结果,然后运行k-近邻算法判定输入数据分别属于哪个分类,最后应用对计算出的分类执行后续的处理。
1.4 从文本文件中解析数据的伪代码
(2)按照距离递增排序
(3)选取与当前距离最小的前K个点
(4)确定k个点所在类别的出现频率
(5)返回出现频率最高的类别作为预测分类
k-近邻算法必须保存全部数据集,这样训练数据集很大的话,必须使用大量的存储空间。由于必须对数据集中每个数据计算距离值,实际使用时可能非常耗时
k-近邻算法的另一个缺陷是无法给出任何数据的基础结构信息,因此无法知晓平均实例样本和典型实例样本具有什么特征
1.5 算法背景
什么是监督学习,什么又是无监督学习呢。监督学习就是我们知道目标向量的情况下所使用的算法,无监督学习就是当我们不知道具体的目标变量的情况下所使用的。而监督学习又根据目标变量的类别(离散或连续)分为分类器算法和回归算法。
k-Nearest Neighbor。k是算法中的一个约束变量,整个算法的总体思想是比较简单的,就是将数据集的特征值看作是一个个向量。我们给程序一组特征值,假设有三组特征值,就可以看做是(x1,x2,x3)。系统原有的特征值就可以看做是一组组的(y1,y2,y3)向量。通过求两向量间的距离,我们找出前k个距离最短的y的特征值对。这些y值所对应的目标变量就是这个x特征值的分类。
1.6 python基础之numpy
array:是numpy自带的数组表示,比如本例中的4行2列数字可以这样输入
group=array([[9,400],[200,5],[100,77],[40,300]])
shape:显示(行,列)例:shape(group)=(4,2)
zeros:列出一个相同格式的空矩阵,例:zeros(group)=([[0,0],[0,0],[0,0],[0,0]])
tile函数位于python模块 numpy.lib.shape_base中,他的功能是重复某个数组。比如tile(A,n),功能是将数组A重复n次,构成一个新的数组
sum(axis=1)矩阵每一行向量相加
2 代码实现
打开pycharm,创建项目,同时创建一个kNN.py文件,所有的代码都放在这里面。
代码分三部分:创建数据集(createDataSet或者导入数据file2matrix)、数据归一化(autoNorm)、分类函数(classify)
2.1 创建数据集和标签
#_*_ coding:utf-8 _*_
#上面那一行是为了能够在代码中添加中文注释
from numpy import *#导入科学计算包numpy
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
#上面那一行是为了能够在代码中添加中文注释
from numpy import *#导入科学计算包numpy
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
2.2 构造kNN分类器
#第一个kNN分类器 inX-测试数据的输入向量 dataSet-训练样本数据集 labels-标签向量 k-邻近的k个样本
def classify0(inX, dataSet, labels, k):
# 使用欧氏距离来计算距离
dataSetSize = dataSet.shape[0]#训练集大小即数组大小
diffMat = tile(inX, (dataSetSize, 1))-dataSet#把待分类数据向量复制成与训练集等阶,对应项求差
sqDiffMat = diffMat ** 2#各项平方,上一项是为了这一步,因为欧氏距离就是对应项间的操作
sqDistance = sqDiffMat.sum(axis=1)#axis=1,将一个矩阵的每一行向量相加(平方和想加)
# print(sqdistance)
distances = sqDistance ** 0.5#开方,求出距离
#按照距离递增次序排列 计算完所有点之间的距离后,可以对数据按照从小到大的次序进行排序,然后确定前k个距
#离最小元素所在的主要分类,输入k总是正整数;最后,将classCount字典分解为元祖列表,然后使用程序第二行导
#入运算符模块的itemgetter方法,按照第二个元素的次序对元祖进行排序
sortedDistIndex = distances.argsort()#距离从小到大排序,返回数组的索引,因为索引对应标签
classCount = {}#字典分解为元祖列表
# 选择距离最小的k个点
for i in range(k):#在字典里每次都是遍历前K个样本训练集,确定前k个距离最小的元素所对应的分类
voteIlabel = labels[sortedDistIndex[i]]#对应分类
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1#出现过的计数,没出现的找分类
# 排序
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
# 此处排序为逆序,按照从大到小
#输出在前k个里面出现频率最高的元素标签
return sortedClassCount[0][0]#返回统计最多的分类
2.3 代码运行讲解
因为是在python开发环境下运行代码,需要打开终端,在命令提示符在完成相关操作(我的python安装路径在C:\Python27)。而我下载的pycharm把编写爱的代码默认路径为C:\Users\common\PycharmProjects。接下来需要把python运行环境C:\Python27改变到代码文件kNN.py存储路径C:\Users\common\PycharmProjects\untitled1。
操作如下:其中python代码路径,需要导入os文件,os.getcwd()显示当前目录,os.chdir(‘’)改变目录,listdir()显示当前目录的所有文件。
接下来所有的代码都是在上面的那个kNN.py文件中进行添加即可,每当修改一次kNN.py代码文件,运行前都需要reload(kNN)重新加载一下kNN模块。
2.4 准备数据(从文本文件解析数据)
可以在pycharm中直接复制也可以在C:\Users\common\PycharmProjects\untitled1路径下直接拖放
#输入为文件名字符串输出为训练样本矩阵和类标签向量
def file2matrix(filename):
fr = open(filename)
arrayOnLines = fr.readlines()#获取所有数据,以行为单位切割
numberOfLines = len(arrayOnLines)#得到文件行数1000
returnMat = zeros((numberOfLines, 3))#构建矩阵NumPy(默认二维),以0填充,我们将矩阵另一维设置成固定值3
classLabelVector = []
index=0
for line in arrayOnLines:#循环处理文件每行数据
line = line.strip()#截取掉所有的回车字符
listFromLine = line.split('\t')#以tab分割为数组(将上一步得到的整行数据分割成一个元素列表)
returnMat[index, :] = listFromLine[0:3]#copy数据,选取前3个元素存储到矩阵
classLabelVector.append(int(listFromLine[-1]))#保存对应的分类(-1表示列表最后一列元素)
return returnMat, classLabelVector
添加以上代码,然后运行
datingDataMat
datingLabels[0:20]
2.5 分析数据(使用matplotlib创建散点图)
import matplotlib.pyplot as plt
fig=plt.figure()
ax=fig.add_subplot(111)
ax.scatter(datingDataMat[:,1],datingDataMat[:,2])
plt.show()
#s:散列点的大小,c:散列点的颜色,marker:形状,linewidths:边框宽度
重新输入上面所有的代码(从头再来),因为由于没有使用样本分类特征值,为了在图中用不用颜色标记不同样本分类,来更好理解数据信息。scatter函数支持个性化标记散点图的点:
from numpy import array注意不加这个会报错,因为python的array和numpy的array有差别
ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))
plt.show()
利用颜色以及尺寸标识了数据点的属性类别,带有养病呢分类标签的约会数据散点图,虽然能够比较容易的区分数据点从属类别,但依然很难根据这张图给出结论性的信息
2.6 准备数据(归一化数据)
可以用公式:newValue = (oldValue-min)/(max-min)即当前值减去最小值再除以取值范围
max和min分别是该项数据集特征中的最大值和最小值。
据此可写出归一化函数
def autoNorm(dataSet):
minVals = dataSet.min(0)#一维数组,值为各项特征(列)中的最小值,1*3。每列的最小值 参数0可以从列中选取最小值而不是选取当前行的最小值
maxVals = dataSet.max(0)#每列最大值
ranges = maxVals - minVals#1*3。函数计算可能的取值范围,并创建新的返回矩阵,为了归一化特征值,必须使用当前值减去最小值,然后除以取值范围
normDataSet = zeros(shape(dataSet))
m = dataSet.shape[0]#特征值大小为1000*3。注意事项:特征值矩阵有1000*3个值。而minVals和range的值都为1*3.为了解决这个问题使用numpy中tile函数将变量内容复制成输入矩阵同样大小的矩阵
normDataSet = dataSet - tile(minVals, (m,1))
normDataSet = normDataSet/tile(ranges, (m,1))#特征值相除,使用tile函数使得矩阵大小一致
return normDataSet, ranges, minVals
注意事项:特征值矩阵有1000*3个值。而minVals和range的值都为1*3.为了解决这个问题使用numpy中tile函数将变量内容复制成输入矩阵同样大小的矩阵
normDataSet = dataSet - tile(minVals, (m,1))
normDataSet = normDataSet/tile(ranges, (m,1))#特征值相除,使用tile函数使得矩阵大小一致
return normDataSet, ranges, minVals
normMat,ranges,minVals=kNN.autoNorm(datingDataMat)
normMat
ranges
minVals
2.7 测试算法(完整程序验证分类器)
def datingClassTest():
hoRatio = 0.50 # hold out 10%
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt') # load data setfrom file
normMat, ranges, minVals = autoNorm(datingDataMat)
m = normMat.shape[0]
numTestVecs = int(m * hoRatio)
errorCount = 0.0
for i in range(numTestVecs):
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]))
if (classifierResult != datingLabels[i]): errorCount += 1.0
print ("the total error rate is: %f" % (errorCount / float(numTestVecs)))
print (errorCount)
我们可以改变hoRatio和k的值来检测错误率有无变化。
kNN.datingClassTest()
2.8 使用算法(约会网站预测)
def classifyPerson():
resultList = ['not at all', 'in small doses', 'in large doses']
percentTats = float(raw_input(\
"percentage of time spent playing video games?"))
ffMiles = float(raw_input("frequent flier miles earned per year?"))#raw_input允许用户输入文本行命令并返回用户输入的命令
iceCream = float(raw_input("liters of ice cream consumed per year?"))
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = array([ffMiles, percentTats, iceCream])
classifierResult = classify0((inArr -\
minVals) / ranges, normMat, datingLabels, 3)
print "You will probably like this person: ", \
resultList[classifierResult - 1]
运行代码
kNN.classifyPerson()
3 手写识别系统
构造的系统只能识别数字0~9,需要是别的数字已经使用图像处理软件,处理成具有相同的色彩和大小:
宽高是32*32的黑白图像
1、收集数据 提供文本文件
2、准备数据 编写函数classify0(),将图像格式转换成分类器使用的list格式
3、分析数据 在Python命令提示符中检查数据,确保它符合要求
4、训练算法 此步骤不适合k-近邻算法
5、测试算法 测试样本和非测试样本区别在于:测试样本已经完成分类的数据,如果预测分类与实际类别不同,则标为error
6、使用算法
3.1 准备数据
def img2vector(filename):
returnVect = zeros((1, 1024))#创建1*1024的numpy数组
fr = open(filename)
for i in range(32):#循环读出文件的前32行
lineStr = fr.readline()
for j in range(32):#将每行的头32个字符值存储在numpy数组中,最后返还给数组
returnVect[0, 32 * i + j] = int(lineStr[j])
return returnVect
运行代码
reload(kNN)
testVector=kNN.img2vector('testDigits/0_13.txt')
testVector[0,0:31]
testVector[0,32:63]
3.2 测试算法
# 手写识别系统测试代码
def handwritingClassTest():
hwLabels = []
trainingFileList = dir('trainingDigits')#获取testDigits目录中的文件内容存储在列表中
m = len(trainingFileList)#得到目录中有多少文件,便将其存储到变量m中
trainingMat = zeros((m, 1024))#创建一个m*1024的训练矩阵,该矩阵的每行数据存储一个图像,可以从文件名中解析出分类数字
for i in range(m):
fileNameStr = trainingFileList[i] #分割得到标签 从文件名解析得到分类数据
fileStr = fileNameStr.split('.')[0]
classNumStr = int(fileStr.split('_')[0])
hwLabels.append(classNumStr) #测试样例标签,该目录下的文件按照规则命名,如文件9_45.txt的分类是9,它是数字9的第45个实例
#将类代码存储在hwLabels向量中
trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr )#使用img2vector载入图像
#对testDigits目录中的文件执行相似的操作,不同之处在于我们并不将这个目录下的文件载入矩阵中
#而是利用classify0()函数测试该目录下每个文件,由于文件中的值已在0~1之间,所以不需要autoNorm()函数
testFileList = dir('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('testDigits/%s' % fileNameStr)
classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
print ("the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr))
if(classifierResult != classNumStr): errorCount += 1.0
print ("\nthe total number of errors is: %d" % errorCount)
print ("\nthe total error rate is: %f" % (errorCount / float(mTest)))
运行代码
reload(kNN)
kNN.handwritingClassTest()
该算法执行效率不高,因为算法需要为每个测试向量做2000词距离计算,每个距离计算包括了1024个维度浮点计算,总计执行900次,此外还需要为向量准备2M的存储空间 k决策树是k-近邻算法的改进版
所有的代码操作:
cmd
cd c:\python27
python
import os
os.getcwd()
os.chdir("C:\\Users\\common\\PycharmProjects\\1kNN")注意不同的代码这个位置不同
os.getcwd()
import kNN
group, labels = kNN.createDataSet()
group
labels
reload(kNN)
kNN.classify0([0,0],group,labels,3)
datingDataMat,datingLabels=kNN.file2matrix('datingTestSet2.txt')注意这个引号是英文的,否则报错
datingDataMat
datingLabels[0:20]
import matplotlib报错的时候,直接重新打开cmd安装,不要在python环境下
import matplotlib.pyplot as plt
fig=plt.figure()
ax=fig.add_subplot(111)
ax.scatter(datingDataMat[:,1],datingDataMat[:,2])
plt.show()
from numpy import array注意不加这个会报错
ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))
plt.show()
normMat,ranges,minVals=kNN.autoNorm(datingDataMat)
normMat
ranges
minVals
reload(kNN)
kNN.datingClassTest()
reload(kNN)
kNN.classifyPerson()
testVector=kNN.img2vector('testDigits/0_13.txt')
testVector[0,0:31]
testVector[0,32:63]
reload(kNN)
kNN.handwritingClassTest()