2.1 K-近邻算法概述
K-近邻算法应该就是一个分类算法。采用测量不同特征值之间的距离方法进行分类。
优点:精度高、对异常值不敏感、无数据输入假定。
缺点:计算复杂度高、空间复杂度高。
适用范围:数值型和标称型。
书中举了一个电影分类的例子,通过一些镜头来判断这是爱情片还是动作片爱情动作片。
K-近邻算法的一般流程
- 收集数据:可以使用任何方法。
- 准备数据:距离计算所需要的数值,最好是结构化的数据格式。
- 分析数据:可以使用任何方法。
- 训练算法:此步骤不合适K-近邻算法。
- 测试算法:计算错误率。
- 使用算法:首先需要输入样本数据和结构化的输出结果,然后运行K-近邻算法判定输入数据分别属于哪个分类,最后应用对计算出的分类执行后续的处理。
================================================================================
接下来是准备工作,使用 Python 导入数据。推荐手打,不要复制粘贴。
kNN.py
注意我用的文件可以从 http://www.ituring.com.cn/book/1021 下载,而且根据勘误应该用 datingTestSet2.txt
#-*- coding:utf-8 -*-
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
>>> import kNN
>>> group,labels = kNN.createDataSet()
>>> group
>>> labels
验证下与书一样就可以了。结果如下图:
==========================================================================
接下来正式的 kNN 算法,需要说明的是,几个输入参数是什么。
inX 是用于分类的输入向量,dataSet 是输入的训练样本,标签向量为 labels , 参数 k 表示的是用于选择的最近邻居的数目,其中标签向量的元素数目和矩阵 dataSet 的行数相同 。
代码如下:
#-*- coding:utf-8 -*-
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
def classify0(inX, dataSet, labels, k):
# 距离计算
dataSetSize = dataSet.shape[0] # .shape 读取矩阵的长度
diffMat = tile(inX,(dataSetSize,1)) - dataSet
# tile(A,n),功能是将数组A重复n次,构成一个新的数组
sqDiffMat = diffMat ** 2 # **就是乘方
sqDistances = sqDiffMat.sum(axis = 1)
distances = sqDistances ** 0.5
sortedDistIndicies = distances.argsort() # argsort函数返回的是数组值从小到大的索引值
classCount = {}
# 选择距离最小的 K 个点
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
#dict.get(key, default=None) key在字典中查找,在key不存在的情况下返回值None。
# 排序 sorted 函数详见 http://www.cnblogs.com/sysu-blackbear/p/3283993.html
sortedClassCount = sorted(classCount.iteritems(), # iteritems以迭代器对象,返回键值对
key = operator.itemgetter(1), reverse = True)
# itemgetter(1) 使用元组的第二个元素对列表排序
# itemgetter(0) 使用元组的第一个元素对列表排序
return sortedClassCount[0][0]
第一部分的距离计算就是欧氏距离,没什么好说的。
然后,确定前 k 个距离最小元素所在的主要分类,输入 k 总是正整数。
最后,把 classCount 字典分解为元组列表,然后用 itemgetter 方法,按照第二个元素的次序对元组排序。
==========================================================================================
跑一遍试试:
可以看出 [0,0] 分类结果是 B
==========================================================================================
2.2 示例:使用K-近邻算法改进约会网站配对效果
2.2.1准备数据
样本主要包含以下三种特征:
1.每年获得的飞行常客里程数
2.玩视频游戏所花费时间百分比
3.每周消费冰激凌公升数
在 kNN.py 中创建一个 file2matrix 函数处理输入格式问题。
增加一部分代码,并且在本文一开始的地方下载所需要的数据文件。
#-*- coding:utf-8 -*-
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
def classify0(inX, dataSet, labels, k):
# 距离计算
dataSetSize = dataSet.shape[0] # .shape 读取矩阵的长度
diffMat = tile(inX,(dataSetSize,1)) - dataSet
# tile(A,n),功能是将数组A重复n次,构成一个新的数组
sqDiffMat = diffMat ** 2 # **就是乘方
sqDistances = sqDiffMat.sum(axis = 1)
distances = sqDistances ** 0.5
sortedDistIndicies = distances.argsort() # argsort函数返回的是数组值从小到大的索引值
classCount = {}
# 选择距离最小的 K 个点
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
#dict.get(key, default=None) key在字典中查找,在key不存在的情况下返回值None。
# 排序 sorted 函数详见 http://www.cnblogs.com/sysu-blackbear/p/3283993.html
sortedClassCount = sorted(classCount.iteritems(), # iteritems以迭代器对象,返回键值对
key = operator.itemgetter(1), reverse = True)
# itemgetter(1) 使用元组的第二个元素对列表排序
# itemgetter(0) 使用元组的第一个元素对列表排序
return sortedClassCount[0][0]
def file2matrix(filename):
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines) # 文件的行数
returnMat = zeros((numberOfLines, 3)) # 创建返回的 NumPy 矩阵
# 创建给定类型的矩阵,并初始化为0。zeros((A,B)),创建一个A行,B列的0矩阵
classLabelVector = []
index = 0
for line in arrayOLines: # 解析文件数据列表
line = line.strip() # strip() 方法用于移除字符串头尾指定的字符(这里是截掉回车字符)
listFromLine = line.split('\t') # tab 字符
returnMat[index,:] = listFromLine[0:3]
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat,classLabelVector
为了方便起见,我再写了一个 use-kNN.py 文件,不用在命令行里面一个一个输入了,文件如下:
import kNN
group,labels = kNN.createDataSet()
print group
print '**********'
print labels
print '**********'
print kNN.classify0([0,0],group,labels,3)
print '**********'
reload(kNN)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
print datingDataMat
print '**********'
print datingLabels[0:20]
结果如下:
以上就是从文本文件导入数据并且转化为想要的格式。不过结果和书上例子不太一样。
========================================================================================================
2.2.2 分析数据
我先建立一个 plot.py 用于画图,代码如下:
import matplotlib
import matplotlib.pyplot as plt
import kNN
fig = plt.figure()
ax = fig.add_subplot(111)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
ax.scatter(datingDataMat[:,1], datingDataMat[:,2])
plt.show()
那么就可以画出图了:
上图存在的问题是,没有用颜色来标记不同样本分类。于是在执行的时候可以加点代码。
# -*- coding:utf-8 -*-
import matplotlib
import matplotlib.pyplot as plt
import kNN
import usekNN
from numpy import * # 没导入会报错
fig = plt.figure()
ax = fig.add_subplot(111)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
# datingDataMat[:,1], datingDataMat[:,2] 应该指的是二三列?
# ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
plt.show()
结果如下:
稍微改动一下,完善一下,加个xy标签,改动一下读取的数据列:
# -*- coding:utf-8 -*-
import matplotlib
import matplotlib.pyplot as plt
import kNN
import usekNN
from numpy import * # 没导入会报错
fig = plt.figure()
ax = fig.add_subplot(111)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
ax.scatter(datingDataMat[:,0], datingDataMat[:,1], 15.0*array(datingLabels), 15.0*array(datingLabels))
# datingDataMat[:,1], datingDataMat[:,2] 应该指的是二三列?
# ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
plt.xlabel('Air Miles')
plt.ylabel('Video Games')
plt.show()
得到如下图:
============================================================================
2.2.3 归一化数值
原始数据文件里面,飞行常客里程数远大于其他两者,这导致其影响程度也远大于其他两者,而我们希望三者是具有同样权重的,因此需要归一化处理。
通常我们将其取值范围处理为0到1或者-1到1之间,使用的公式如下:
newValue = (oldValue-min) / (max-min)
我们要在 kNN.py 程序里面加上一个归一化的函数,为了简短起见,就只写这一段。
def autoNorm(dataSet):
minVals = dataSet.min(0) # 每列最小变量,参数 0 使得函数可以从列中选取最小值
maxVals = dataSet.max(0) # 每列最大变量
ranges = maxVals - minVals
normDataSet = zeros(shape(dataSet)) # 照着 dataSet 的样子做一个 0 矩阵
m= dataSet.shape[0] # shape[0] 指的是矩阵第一维长度
normDataSet = dataSet - tile(minVals, (m,1)) # tile(A,n),功能是将数组A重复n次,构成一个新的数组
normDataSet = normDataSet/tile(ranges, (m,1))
return normDataSet, ranges, minVals
特征值矩阵有 1000 *3 个值,而 minVals 和 range 的值都为 1*3 ,为此我们使用 tile() 函数将变量内容复制成输入举证同样大小的矩阵。另外这里的 / 是具体特征值相除,而 NumPy 的矩阵除法是 linalg.solve(matA,matB) 。
现在执行 autoNorm 试一试,我们在 usekNN.py 里面加一点
# usekNN.py
import kNN
group,labels = kNN.createDataSet()
print group
print '**********'
print labels
print '**********'
print kNN.classify0([0,0],group,labels,3)
print '**********'
reload(kNN)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
print datingDataMat
print '**********'
print datingLabels[0:20]
print '**********'
reload(kNN)
normMat, ranges, minVals = kNN.autoNorm(datingDataMat)
print normMat
print '**********'
print ranges
print '**********'
print minVals
运行结果就不发了,反正正常...
=====================================================================================
2.2.4 测试算法
这里主要是测试分类器效果,因此要写测试代码:
def datingClassTest():
hoRatio = 0.10
datingDataMat, datingLabels = file2matrix('datingTestSet2.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)
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)) # 写出正确率
接下来在 usekNN.py 里面加上
reload(kNN)
kNN.datingClassTest()
这样运行就知道错误率了,我运行出来的错误率是 5% ,并不算高。
=====================================================================================
2.2.5 使用算法
通过该程序可以在约会网站上面找到某个人输入他的信息,程序会给出预测。
kNN.py 添加代码如下:
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?"))
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]
然后 usekNN.py 添加执行:
print '**********'
reload(kNN)
kNN.classifyPerson()
结果:
===============================================================================================================
最后给一遍完整代码,总共三个文件。
kNN.py:
#-*- coding:utf-8 -*-
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
def classify0(inX, dataSet, labels, k):
# 距离计算
dataSetSize = dataSet.shape[0] # .shape 读取矩阵的长度
diffMat = tile(inX,(dataSetSize,1)) - dataSet
# tile(A,n),功能是将数组A重复n次,构成一个新的数组
sqDiffMat = diffMat ** 2 # **就是乘方
sqDistances = sqDiffMat.sum(axis = 1)
distances = sqDistances ** 0.5
sortedDistIndicies = distances.argsort() # argsort函数返回的是数组值从小到大的索引值
classCount = {}
# 选择距离最小的 K 个点
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
#dict.get(key, default=None) key在字典中查找,在key不存在的情况下返回值None。
# 排序 sorted 函数详见 http://www.cnblogs.com/sysu-blackbear/p/3283993.html
sortedClassCount = sorted(classCount.iteritems(), # iteritems以迭代器对象,返回键值对
key = operator.itemgetter(1), reverse = True)
# itemgetter(1) 使用元组的第二个元素对列表排序
# itemgetter(0) 使用元组的第一个元素对列表排序
return sortedClassCount[0][0]
def file2matrix(filename):
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines) # 文件的行数
returnMat = zeros((numberOfLines, 3)) # 创建返回的 NumPy 矩阵
# 创建给定类型的矩阵,并初始化为0。zeros((A,B)),创建一个A行,B列的0矩阵
classLabelVector = []
index = 0
for line in arrayOLines: # 解析文件数据列表
line = line.strip() # strip() 方法用于移除字符串头尾指定的字符(这里是截掉回车字符)
listFromLine = line.split('\t') # tab 字符
returnMat[index,:] = listFromLine[0:3]
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat,classLabelVector
def autoNorm(dataSet):
minVals = dataSet.min(0) # 每列最小变量,参数 0 使得函数可以从列中选取最小值
maxVals = dataSet.max(0) # 每列最大变量
ranges = maxVals - minVals
normDataSet = zeros(shape(dataSet)) # 照着 dataSet 的样子做一个 0 矩阵
m= dataSet.shape[0] # shape[0] 指的是矩阵第一维长度
normDataSet = dataSet - tile(minVals, (m,1)) # tile(A,n),功能是将数组A重复n次,构成一个新的数组
normDataSet = normDataSet/tile(ranges, (m,1))
return normDataSet, ranges, minVals
def datingClassTest():
hoRatio = 0.10
datingDataMat, datingLabels = file2matrix('datingTestSet2.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)
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(raw_input(\
"percentage of time spent playing video games?"))
ffMiles = float(raw_input("frequent flier miles earned per year?"))
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]
usekNN.py:
# usekNN.py
import kNN
group,labels = kNN.createDataSet()
print group
print '**********'
print labels
print '**********'
print kNN.classify0([0,0],group,labels,3)
print '**********'
reload(kNN)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
print datingDataMat
print '**********'
print datingLabels[0:20]
print '**********'
reload(kNN)
normMat, ranges, minVals = kNN.autoNorm(datingDataMat)
print normMat
print '**********'
print ranges
print '**********'
print minVals
print '**********'
reload(kNN)
kNN.datingClassTest()
print '**********'
reload(kNN)
kNN.classifyPerson()
plot.py:
# -*- coding:utf-8 -*-
import matplotlib
import matplotlib.pyplot as plt
import kNN
import usekNN
from numpy import * # 没导入会报错
fig = plt.figure()
ax = fig.add_subplot(111)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
ax.scatter(datingDataMat[:,0], datingDataMat[:,1], 15.0*array(datingLabels), 15.0*array(datingLabels))
# datingDataMat[:,1], datingDataMat[:,2] 应该指的是二三列?
# ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
plt.xlabel('Air Miles')
plt.ylabel('Video Games')
plt.show()