- 本章内容主要基于机器学习实战
概述
k—近邻算法采用测量不同特征值之间的距离方法进行分类。 | |
---|---|
优点 | 精度高,对异常值不敏感,无数据输入假定 |
缺点 | 计算复杂度高、空间复杂度高 |
适用数据范围 | 数值型和标称型 |
简单来说,就是根据给定的事实,判断样本集中各样本与给定事实的差距,来对样本进行分类。
k-近邻算法分类器
分类器可以满足的功能是:输入三类信息的数值,计算出该值与已知数据之间的距离,对该距离进行排序,取排序前k个数据,统计前k个数据中各种标签所占数量(本任务中,标签为海伦的喜欢程度),数量最多的,为分类器给出的结果,即给定数据的判定结果。
# 分类器
def classify0(inx, dataSet, labels, k):
#
inx:给定点的坐标。dataSet:点集。labels:点的标签集合。k: 选择近邻点的个数
dataSetSize = dataSet.shape[0]
diffMat = tile(inx, (dataSetSize, 1)) - dataSet
# tile()函数:tile(inx,(x,y));将inx重复x行,y列,返回数组array()类型
# 此处将给定点与点集横纵坐标相减,得到△x,△y
sqDiffMat = diffMat ** 2 # 将diffMat各项平方,得到△x^2,△y^2
sqDistances = sqDiffMat.sum(axis=1)
# 数组,array()类型sum()函数表示各项相加,axis = 0,表示按列相加,axis = 1,表示按行相加
# 此处得到△x^2+△y^2
distances = sqDistances ** 0.5 # 求得点集各点到给定点的距离
sortedDistIndicies = distances.argsort()
# shuzu.argsort()numpy库中的函数,用于返回一个数组,数组中存放的是shuzu中元素从小到大排序
# 的索引值
classCount = {}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
# 字典对象的get()函数,get(x,y),返回指定键x的值,如果字典中不存在该指定键,返回y
# 此句统计标签的数目
sortedClassCount = sorted(classCount.items(),
key=operator.itemgetter(1), reverse=True)
# dic.items()返回字典的键,键值元组数组。
# key=operator.itemgetter(1)表示比较的元素为每一个元组的第二项,即数量。
# reverse=True为降序排列,reverse=False为升序排列
return sortedClassCount[0][0]
# 返回第一个元组的第一个元素
在约会网站上使用k-临近算法
从文本文件中解析数据
def file2matrix(filename):
fr = open(filename)
arrayOLines = fr.readlines()
### 读取文件中的每一行全部元素存入列表中,每一行组成列表的一个元素
numberOfLines = len(arrayOLines)
### 获取列表长度,即文件的行数
returnMat = zeros((numberOfLines, 3))
### 新建一个全0的数组。zeros()新建的是一个数组,array()类型
classLabelVector = []
index = 0
for line in arrayOLines:
line = line.strip()
### strip()删除字符串头尾指定的字符(默认为空格或换行符),不能删除中间部分的字符
listFromLine = line.split('\t')
### 对每一行的元素进行分割,得到相应列表。
returnMat[index, :] = listFromLine[0:3]
labels = {'largeDoses': 3, 'smallDoses': 2, 'didntLike': 1}
### 相较书中新增的字典
classLabelVector.append(labels[listFromLine[-1]])
index += 1
### 将listFromLine列表中数值按行,将前三项赋值给returnMat列表
return returnMat, classLabelVector
使用matplotlib创建散点图
import matplotlib
import matplotlib.pyplot as plt
fig = plt.figure()
### 创建图像,采用默认配置
ax = fig.add_subplot(111)
### 规定输出图像的格式,111表示创建1×1的网格,图像位于第一子图
ax.scatter(datingDataMat[:,1],datingDataMat[:,2])
### 绘制散点图,用datingDataMat第一列的值定位x轴坐标,第二列的值定为y轴坐标
plt.show()
散点图颜色处理
###其他语句可不变,改变此句
ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),15*array(datingLabels))
### 新增两项
### 乘15是为了将差异放大
### 前一项为修改点的大小,此处可将datingLabels修改为数组,列表也可以操作,也不要求元素数量与点的数量相同,列表内元素可以循环使用。
### 后一项为修改点的颜色,此处必须将datingLabels修改为数组,或者组元也可,但元素数量必须与绘制的点的数量相同
对文件数据进行归一化
在对数据进行分类过程中,因为有些数据值很大,在求距离时,所占比重较大,但对用户来说,这些数值的重要程度相同,此时就需要对数据进行归一化操作,采用百分比的形式:
N
U
M
n
e
w
=
(
N
U
M
o
l
d
−
N
U
M
m
i
n
)
(
N
U
M
m
a
x
−
N
U
M
m
i
n
)
NUM_{new} = {(NUM_{old}-NUM_{min})\over(NUM_{max}-NUM_{min})}
NUMnew=(NUMmax−NUMmin)(NUMold−NUMmin)
def autoNorm(dataSet):
minVals = dataSet.min(0)
### min(0)求每一列的最小值,min(1)返回每一行的最小值
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = zeros(shape(dataSet))
### 新建一个同dataSet规模相同的零数组
m = dataSet.shape[0]
normDataSet = dataSet - tile(minVals, (m, 1))
normDataSet = normDataSet / tile(ranges, (m, 1))
### 相应元素做除法
### 矩阵除法用函数linalg.solve(matA,matB)
return normDataSet, ranges, minVals
测试代码
我们已经拥有了1000组数据,其中记录了海伦希望知道的三类信息对喜欢程度的影响。为了验证分类器的性能,需要对分类器进行测试。
选取10%的数据用作测试,测试结果与测试数据不符的即为分类器失败的数据,由此可以判断分类器的准确度。
# 分类器针对约会网站的测试代码
def datingClassTest():
hoRatio = 0.10 # 选择10%的数据进行测试
datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
# datingLabels 记录的是不喜欢的人,一般喜欢的人,特别喜欢的人,三类
normMat, ranges, minVals = autoNorm(datingDataMat)
m = normMat.shape[0]
numTestVecs = int(m*hoRatio) # numTestVecs为用做数据测试的数据的数量
errorCount = 0.0 # 记录测试失败的数量
for i in range(numTestVecs):
classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :],
datingLabels[numTestVecs:m], 3)
# 以数据集中第i行的数据为标准,对训练集后90%条进行分类
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)))
约会网站测试函数
面相用户的输入输出界面
def classifyPerson():
resultList = ['not at all', 'in small doses', '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])
classifierReault = classify0((inArr-minVals)/ranges, normMat, datingLabels, 3)
print('You will probably like this person: ',
resultList[classifierReault - 1])
示例:手写识别系统
此处给出的示例是手写识别系统的简化版本,不多做记录。
提供了的文件,是需要识别的数字,已经使用图形处理软件,处理成32×32的黑白图像,全部由0和1构成,如图:
准备数据:将图像转换为测试向量
所谓将图像转换成向量,就是想32×32的0,1数字全部存入一行数组中,构成[x_1,x_2,···,x_1024]的向量。
# 准备数据,将图像转化为测试向量
# 此处将txt文件中的32×32的数字转换成1024的向量,即将所有行放至一行里
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
测试算法
在程序中导入listdir(在代码前,写入from os import listdir)
listdir(path):获取该目录下的所有文件,返回列表
def handwritingClassTest():
hwLabels = [] # 存储训练集向量对应的标签的列表
trainingFileList = listdir('trainingDigits')
# 获取该文件夹中所有文件,返回列表
m = len(trainingFileList)
trainingMat = zeros((m, 1024)) # 用于存储训练集每组数据转化成的向量的数组
for i in range(m): # 得到所有训练集的向量的数组,及该向量对应标签的列表
fileNameStr = trainingFileList[i]
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]
fileStr = fileNameStr.split('.')[0]
classNumStr = int(fileStr.split('_')[0])
# 测试集每组向量对应的正确数字
vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
# 得到向量
classfierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
print('the classifier came back with: %d, the real answer is: %d'
% (classfierResult, classNumStr))
if classfierResult != classNumStr:
errorCount += 1.0
print('\n the total number of errors is: %d' % errorCount)
print('\n the total error rate is: %f' % (errorCount/float(mTest)))
总结
k-近邻算法是分类数据最简单有效的算法。
缺陷:
1、但是对于给定一条需要分类的信息,想要有效对其进行分类,就必须需要大量的训练集,且需要保存。如果训练集很大,就必须使用大量的存储空间。而且必须对训练集中的每个数据计算距离,这在使用时可能很耗时。
2、另一个缺陷,没看懂啥意思,先不写了