KNN算法简单的来说就是计算两个数据之间的差异,就如同计算机视觉中,使用knn算法就是计算两张图片的像素差,在今天学习机器学习的过程中也是如此,用的是欧式距离公式。
首先是编写基本的函数。
from numpy import *
import operator
def createDataSet():
group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]]) #可以看作4行2列的矩阵
labels = ['A', 'A', 'B', 'B'] #每一行数据的标签
return group, labels
kNN算法的伪代码:
1.计算拥有标签数据集的点与当前点的距离
2.按照距离递增的顺序排序
3.选取距离最小的前k个点
4.确定前k个点所在类别(也就是label)出现的频率
5.返回前k个点出现频率最高的类别作为当前点的预测分类
# inX是输入向量,dataSet是训练样本集,labels是标签向量,k是选择的邻居数
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0] #shape[0]的意思是训练集的行数,也就是有多少个向量
diffMat = tile(inX, (dataSetSize, 1)) - dataSet #tile()是把inX向量向列复制一次,行复制dataSetSize次,就是做个和训练集样本一样的矩阵,得出差异矩阵
sqDiffMat = diffMat ** 2 # 计算差值平方
sqDistances = sqDiffMat.sum(axis=1) #这里要记住的是axis=1表示计算行和
Distances = sqDistances ** 0.5 #到此为止,输入向量和样本集之间的距离已经算完
sortedDistIndices = Distances.argsort() #按距离递增排序,记录的是他们的位置号
classCount = {} #申请一个字典,好存放前k个标签和他们出现的频率关系
for i in range(k):
voteIlabel = labels[sortedDistIndices[i]] # 提取前k个的标签
#理解sorted函数,第一个参数是可迭代对象,比较方法由key决定,operator.itemgetter(1)获取了迭代对象的下标为1的值,也就是字典里面的频率,reverse=True表示从大到小排
#operator.itemgetter(1)是定义了一个函数,每次取迭代对象标为1的值
sortedClassCount[voteIlabel] = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0] #返回出现频率最高的标签
运行代码
groups, labels = createDataSet()
label = classify0([0, 0], groups, labels, 3)
print(label)
# 结果为B
准备数据:从文本文件中解析数据
def file2matrix(filename):
fr = open(filename) #打开文件,获得句柄
arrayOfLines = fr.readlines() # 调用方法,将文本中的每一行放入一个列表中
numberOfLines = len(arrayOfLines) # 计算文本中有多少行
returnMat = zeros((numberOfLines, 3)) #创建一个返回矩阵,元素为0
classLabelVector = [] # 创建一个标签列表
index = 0
for line in arrayOfLines:
line = line.strip() # 将每一行的空格去头去尾
listFromLine = line.split('\t') # 将每一行的数据划分开存入列表
returnMat[index, :] = listFromLine[:3] # 将每一行前三个数据存入返回矩阵
labels = {'didntLike': 1, 'smallDoses': 2, 'largeDoses': 3} # 书上代码报错,故加入一个列表来使代码运行正确
classLabelVector.append(labels[listFromLine[-1]]) # 将每一行数据的标签存入
index += 1
return returnMat, classLabelVector
归一化数值
为啥要归一化数值?因为按照欧式公式来计算的话,不同属性上的差异很大,差异大的属性上计算的值几乎决定了结果。而假设不同属性的权重应该是同等重要的,所以应该要解决这个问题。(这里说属性是习惯了数据库的说法,反正意思差不多,是这个东西就行了)
#公式
newValue = (oldValue - min) / (max - min)
def autoNorm(dataSet):
minVals = dataSet.min(0) #min(0)是按列返回最小值,也就是返回一个属性上的最小值
maxVals = dataSet.max(0)
ranges = maxVals - minVals #每个属性上的最大值和最小值差
normDataSet = zeros(shape(dataSet)) #创建一个和dataSet型号一样的矩阵,元素全为0
m = dataSet.shape[0] # 返回dataSet有多少个数据
normDataSet = dataSet - tile(minVals, (m, 1)) # 计算每个数据和最小值的差
normDataSet = normDataSet / tile(ranges, (m, 1)) # 除(max - min)
return normDataSet, ranges, minVals
针对约会网站的测试代码,计算错误率,因为海伦给的数据本身就是随机值,所以取前10%的数据为测试数据,后90%的数据为dataSet
def datingClassTest():
hoRatio = 0.1 # 设置要测试的数据集
datingDataMat, datingLabels = file2matrix('datingTestSet.txt') # 文本中分析数据
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) # 调用classify0函数,返回分类结果,参数上面写了
print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i]))
if (classifierResult != datingLabels[i]): # 如果分类结果和标签不符,errorCount加1
errorCount += 1.0
print("the total error rate is: %f" % (errorCount / float(numTestVecs)))
约会网站预测函数
def classifyPerson():
resultList = ['not at all', 'in small doese', 'in large doses']
# 下面三行是输入各个属性的数据
percentTats = float(input("percentage of time spent playing video games?"))
ffMiles = float(input("frequent flier miles earned per year?"))
iceCream = float(input("liters of ice cream consumed per year?"))
# 生成矩阵,并且归一化
datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = array([ffMiles, percentTats, iceCream]) # 将输入的数据合并成一个向量
# 调用classify0函数生成预测结果,这里我一开始没有想到的点是输入数据也要归一化
classifyResult = classify0((inArr - minVals) / ranges, normMat, datingLabels, 3)
print("You will probably like this person: ", resultList[classifyResult - 1])
准备数据:将图像转换为测试向量
# 将一幅32x32的图像转换成一个一维的向量
def img2vector(filename):
returnVect = zeros((1, 1024)) # 申请一个一维向量
fr = open(filename)
for i in range(32): # 遍历行
lineStr = fr.readline() # 逐行读取,readline()的效果
for j in range(32): # 遍历每一行,将字符串的数字转换为真正的数字
returnVect[0, 32*i+j] = int(lineStr[j])
return returnVect
测试算法:使用k-近邻算法识别手写数字
def handwritingClassTest():
hwLabels = [] # 记录标签列表
trainingFileList = listdir('trainingDigits') # listdir()方法返回文件夹中文件或文件夹的名字,但不包括.和..
m = len(trainingFileList) # 记录该文件夹的文件数
trainingMat = zeros((m, 1024))
for i in range(m):
fileNameStr = trainingFileList[i] # 获取文件名的全称,包括.txt
fileStr = fileNameStr.split('.')[0] # 获取文件名,不包括.txt
classNumStr = int(fileStr.split('_')[0]) # 获取该图片的标签
hwLabels.append(classNumStr)
trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr) # 将图片转化为一维向量
testFileList = listdir('testDigits')
errorCount = 0.0
mTest = len(testFileList)
for i in range(mTest):
fileNameStr = testFileList[i] # 获取文件名的全称,包括.txt
fileStr = fileNameStr.split('.')[0] # 获取文件名,不包括.txt
classNumStr = int(fileStr.split('_')[0]) # 获取该图片的标签
vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
# 调用函数classify0来获取结果
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
print("\nthe total number of errors is: %d" % errorCount)
print("\nthe total error rate is: %f" % (errorCount / float(mTest)))