机器学习实战(MACHINE LEARNING IN ACTION) 之 K近邻(KNN)

kNN(k近邻)方法在《机器学习实战》这本书里是最先介绍的算法,估计也是最简单的了。。。k-近邻算法(kNN)工作原理:存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输入没有标签的数据之后,将新的数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中最相似数据(最近邻)的分类标签。一般来说,我们只选取样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常来说k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。

举个栗子,如果没有告诉坐标平面内一二三四象限的定义(其实我已经忘了咋定义的了),已知(1, 1),(-1, 1),(-1, -1),(1, -1)分别在第一二三四象限,现在再给一个点(x, y),你猜这个点最可能是在第几象限。。。我猜离已知那四个点哪个最近,就是他对应的第几象限。为什么?我猜的,只可意会不可言传。。。俗话说近朱者赤近墨者黑,也就是跟啥走得近就是啥一个道理。。。

根据上面所说的,推广到多维,也是可以的,先来一小段代码实现kNN分类(需要先安装python和pip然后在pip install numpy),过程的解释都在注释中,创建kNN.py:

# -*- coding: utf8 -*-

from numpy import *
import operator

def createDataSet():
    group = array([[1.0, 1.0], [-1.0, 1.0], [-1.0, -1.0], [1.0, -1.0]])
    labels = ['第一象限', '第二象限', '第三象限', '第四象限']

    return group, labels


def classify0(inX, dataSet, labels, k):
    '''
    funtion: 使用kNN的方法计算标签为lables的dataSet中的数据为样本,inX的前k个近邻的计算结果
    args:
        inX: 待分类的向量
        dataSet: 已有标签的向量集合
        labels: 与dataSet对应的标签
        k: kNN中的前k个
    return:
        kNN的计算结果,lables中的一个
    '''
    # 获取dataSet中向量数量,shape返回numpy.array的维度,返回值为tuple类型
    dataSetSize = dataSet.shape[0]
    # 把inX重复至与dataSet数量相同并与其做差,其中tile把inX重复(dataSetSize, 1)次构造成一个numpy.array,第二个参数指定维度
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet
    # 将diffMat中的每个元素都乘方
    sqDiffMat = diffMat ** 2
    # 将sqDiffMat中的向量在第二维度上累加,axis的值就是维度,从0开始,与shape返回的纬度顺序对应
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances ** 0.5
    # numpy.array.argsort() 返回array的排序后的键值数字顺序,而非是array中的数据
    sortedDistIndicies = distances.argsort()
    classCount = {}
    for i in range(k):
        # 每个label出现一次就在classCount里计数一次
        voteLabel = labels[sortedDistIndicies[i]]
        classCount[voteLabel] = classCount.get(voteLabel, 0) + 1
    # 因为classCount是个字典,则key=operator.itemgetter(1)就是按照字典里的值排序
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)

    return sortedClassCount[0][0]


def file2matrix(filename):
    file = open(filename)
    arrayOfLines = file.readlines()
    # 看看有几行数据,就是多少样本
    numberOfLines = len(arrayOfLines)
    # 数据里面前3列是特征值
    returnMat = zeros((numberOfLines, 3))
    # 数据里面最后一列是标签
    classLabelVector = []
    index = 0
    for line in arrayOfLines:
        # 删除一行中的首尾的空白字符(如结束换行的\r\n等)
        line = line.strip()
        # 每行用 \t 分割放进list
        listFromLine = line.split('\t')
        # list的前3项就是特征值
        returnMat[index, :] = listFromLine[0:3]
        # list的最后一项是标签
        classLabelVector.append(listFromLine[-1])
        index += 1

    return returnMat, classLabelVector


if __name__ == '__main__':
    group, labels = createDataSet()
    print classify0([0.01, -0.3], group, labels, 1)

运行上述程序执行结果是’第四象限’,哎呦不错还说明。。。因为已知标签中每类都只有一个,所以k就只能是1了。当然实际情况不会是这样的这都木有啥意义了。

这本书告诉我,k近邻算法流程有:收集数据、准备数据、分析数据、测试算法、使用算法(kNN就木有训练算法了,把N个已知的标签以摆只管算就好了。。。)。书中的例子是给海伦找对象,海伦是一个很屌的人,自己收集的约会数据就有1000条,样本主要包含3种特征:每年获得的飞行常客里程数、玩视频游戏所消耗时间百分比、每周消费的冰淇淋公升数,并且为每条都标注有”didntLike”、”smallDoses”或”largeDoses”。

首先就是收集数据,海伦已经收集好了。。。不用咱们自己去约1000个了。。。拿去下载:datingTestSet(这个页面里有下载链接,不跳转别的网站不要积分。。。无毒无公害的文本文件。。。)

然后是准备数据,新建一个文件夹,就叫kNN吧,然后把datingTestSet.txt放进去。。。好的准备好了。。。还得把文件里面的数据能够格式化的读出来。打开文件看看发现第一列应该是”每年获得的飞行常客里程数”,第二列应该是”玩视频游戏所消耗时间百分比”,第三列应该是”每周消费的冰淇淋公升数”,第四列就是标签了,每列数据之间tab分割。我们把数据用程序读出来,还是在kNN.py中加入下面的函数:

def file2matrix(filename):
    file = open(filename)
    arrayOfLines = file.readlines()
    # 看看有几行数据,就是多少样本
    numberOfLines = len(arrayOfLines)
    # 数据里面前3列是特征值
    returnMat = zeros((numberOfLines, 3))
    # 数据里面最后一列是标签
    classLabelVector = []
    index = 0
    for line in arrayOfLines:
        # 删除一行中的首尾的空白字符(如结束换行的\r\n等)
        line = line.strip()
        listFromLine = line.split('\t')
        returnMat[index, :] = listFromLine[0:3]
        classLabelVector.append(listFromLine[-1])
        index += 1

    return returnMat, classLabelVector

然后在if name == ‘main’:下面修改一下就可以测试file2matrix()函数好不好使了。

if __name__ == '__main__':
    #group, labels = createDataSet()
    #print classify0([0.01, -0.3], group, labels, 1)
    # 此处文件位置改成刚才datingTestSet.txt保存的目录即可
    group, labels = file2matrix('C:\Users\CPY\Desktop\MLiA_SourceCode\Ch02\datingTestSet.txt')
    print group[0:10]
    print labels[0:10]

运行一下观察输出,就是文件的前10行内容,说明数据解析的还可以。

然后是分析数据,通过画图的方式最直观明了,接下来用matplotlib画图。新建show.py与kNN.py同目录,要是引入时报错”ImportError: cannot import name unpack_labeled_data“就pip install matplotlib –upgrade更新一下就好了,代码里顺路解决了一个matplotlib显示中文是框框或者直接报错的问题:

# -*- coding: utf8 -*-

import kNN
import matplotlib
import matplotlib.pyplot as plt
from ImageEnhance import Color

# 借助上一步所写的函数读取文件分别获得特征和标签
# group 三列分别是:每年获得的飞行常客里程数、玩视频游戏所消耗时间百分比、每周消费的冰淇淋公升数
group, labels = kNN.file2matrix('C:\Users\CPY\Desktop\MLiA_SourceCode\Ch02\datingTestSet.txt')
# 选一个系统里面有的字体文件,要不中文显示很坑
myfont = matplotlib.font_manager.FontProperties(fname='C:/Windows/Fonts/msyh.ttc') 
# 创建一个figure,参数为窗口的显示名字
fig = plt.figure(u'海伦的口味')
# 创建一个subplot,等同于ax = fig.add_subplot(1, 2, 1)
ax = fig.add_subplot(121)
ax1 = fig.add_subplot(122)
# 颜色越深越喜欢
colors = []
for label in labels:
    if label == 'didntLike':
        colors.append("#AAAAAA")
    elif label == 'smallDoses':
        colors.append("#666666")
    else:
        colors.append("#000000")
# 将玩视频游戏所消耗时间百分比和每周消费的冰淇淋公升数分别作为y轴和x轴描点
ax.scatter(group[:, 1], group[:, 2], c=colors)
# 设置fontproperties为选的myfont,要不中文显示就哭了,还有字符串前必须有u
ax.set_title(u'视频游戏 - 冰淇淋', fontproperties=myfont)
ax.set_xlabel(u'玩视频游戏所消耗时间百分比', fontproperties=myfont)
ax.set_ylabel(u'每周消费的冰淇淋公升数', fontproperties=myfont)

ax1.scatter(group[:, 0], group[:, 1], c=colors)
ax1.set_title(u'飞行常客里程 - 视频游戏', fontproperties=myfont)
ax1.set_xlabel(u'每年获得的飞行常客里程数', fontproperties=myfont)
ax1.set_ylabel(u'玩视频游戏所消耗时间百分比', fontproperties=myfont)

plt.show()

观察如下一部分数据发现,三个数据大小很不一样,这时候在计算样本之间距离的时候大的数很明显会影响很大,小的都没啥感觉了。。。所以得对数据进行归一化处理。就是把这些数据都转换成0-1之间的数。方法就是newValue = (oldValue – minValue) / (maxValue – minValue)

40920   8.326976    0.953952    largeDoses
14488   7.153469    1.673904    smallDoses
26052   1.441871    0.805124    didntLike
75136   13.147394   0.428964    didntLike
38344   1.669788    0.134296    didntLike

再接着在kNN.py中再加入一个归一化函数,并适当修改测试:

def autoNormal(dataSet):
    # 计算每一列的最大值和最小值
    minValue = dataSet.min(0)
    maxValue = dataSet.max(0)
    ranges = maxValue - minValue
    # 创建归一化后的值
    normalDataSet = zeros(shape(dataSet))
    # 对于每一个样本都与各个特征的最小值相减再除以范围,得到归一化的值
    m = dataSet.shape[0]
    normalDataSet = dataSet - tile(minValue, (m, 1))
    normalDataSet = normalDataSet / tile(ranges, (m, 1))
    
    return normalDataSet, ranges, minValue


if __name__ == '__main__':
    # 此处文件位置改成刚才datingTestSet.txt保存的目录即可
    group, labels = file2matrix('C:\Users\CPY\Desktop\MLiA_SourceCode\Ch02\datingTestSet.txt')
    print autoNormal(group)

然后就来到了测试算法的步骤:把数据集中的前一部分数据作为待分类样本,其余的作为训练样本,同样在kNN.py中加入新的测试函数。

def datingClassTest():
    # 测试数据所占的比例
    hoRatio = 0.1
    datingDataMat, datingLables = file2matrix('C:\Users\CPY\Desktop\MLiA_SourceCode\Ch02\datingTestSet.txt')
    normalMat, ranges, minValues = autoNormal(datingDataMat)
    m = normalMat.shape[0]
    numTestVectors = int(m * hoRatio)
    # 浮点方便除法
    errorCounter = 0.0
    for i in range(numTestVectors):
        classifierResult = classify0(normalMat[i, :], normalMat[numTestVectors:m, :], datingLables[numTestVectors:m], 3)
        print "the classifier came back with: %s, and the real label is: %s" % (classifierResult, datingLables[i])
        if classifierResult != datingLables[i]:
            errorCounter += 1.0
    print "the total error rate is: %f" % (errorCounter / float(numTestVectors)) 

if __name__ == '__main__':
    datingClassTest()

结果错误率是5%,应该还可以的,所以可以进一步使用算法,给海伦一个方便用的程序,省得他再约会发愁,,,

# -*- coding: utf8 -*-
import kNN
import numpy

def classifyPerson():
    # 海伦的输入,获取待分类人的各项特征值
    percentTats = float(raw_input("percentage of time spent in playing video games?"))
    ffMiles = float(raw_input("frequent flier miles earned per year?"))
    iceCream = float(raw_input("liters of ice cream consumed per week?"))
    
    # 获取海伦已标注过的约会对象的数据
    datingDataMat, datingLabels = kNN.file2matrix('C:\Users\CPY\Desktop\MLiA_SourceCode\Ch02\datingTestSet.txt')
    # 进行归一化处理
    normalMat, ranges, minValues = kNN.autoNormal(datingDataMat)
    inArr = numpy.array([ffMiles, percentTats, iceCream])
    # 将输入的数据进行kNN算法计算
    classifierResult = kNN.classify0(inArr, datingDataMat, datingLabels, 3)
    # 为海伦输出结果
    print "you will probably like this person: ", classifierResult
    

if __name__ == '__main__':
    classifyPerson()

运行程序,玩玩试试,还可以,海伦可以放心大胆的约会去了。。。

percentage of time spent in playing video games?10
frequent flier miles earned per year?10000
liters of ice cream consumed per week?0.5
you will probably like this person:  smallDoses
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容