第二章 K-近邻算法 及 约会网站配对

来源:互联网 发布:mac safari缓存文件 编辑:程序博客网 时间:2024/05/18 01:32

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


在 PowerShell 里面先用 cd 命令定位到 kNN.py 的位置,然后 输入 python 进入开发环境。

>>> 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, labelsdef 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, labelsdef 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 kNNgroup,labels = kNN.createDataSet()print groupprint '**********'print labelsprint '**********'print kNN.classify0([0,0],group,labels,3)print '**********'reload(kNN)datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')print datingDataMatprint '**********'print datingLabels[0:20]

结果如下:



以上就是从文本文件导入数据并且转化为想要的格式。不过结果和书上例子不太一样。

========================================================================================================


2.2.2 分析数据 

我先建立一个 plot.py 用于画图,代码如下:

import matplotlibimport matplotlib.pyplot as pltimport kNNfig = 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 matplotlibimport matplotlib.pyplot as pltimport kNNimport usekNNfrom 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 matplotlibimport matplotlib.pyplot as pltimport kNNimport usekNNfrom 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.pyimport kNNgroup,labels = kNN.createDataSet()print groupprint '**********'print labelsprint '**********'print kNN.classify0([0,0],group,labels,3)print '**********'reload(kNN)datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')print datingDataMatprint '**********'print datingLabels[0:20]print '**********'reload(kNN)normMat, ranges, minVals = kNN.autoNorm(datingDataMat)print normMatprint '**********'print rangesprint '**********'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, labelsdef 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,classLabelVectordef 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, minValsdef 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.pyimport kNNgroup,labels = kNN.createDataSet()print groupprint '**********'print labelsprint '**********'print kNN.classify0([0,0],group,labels,3)print '**********'reload(kNN)datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')print datingDataMatprint '**********'print datingLabels[0:20]print '**********'reload(kNN)normMat, ranges, minVals = kNN.autoNorm(datingDataMat)print normMatprint '**********'print rangesprint '**********'print minValsprint '**********'reload(kNN)kNN.datingClassTest()print '**********'reload(kNN)kNN.classifyPerson()

plot.py:

# -*- coding:utf-8 -*-import matplotlibimport matplotlib.pyplot as pltimport kNNimport usekNNfrom 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()


0 0
原创粉丝点击