《机器学习实战》之k近邻算法(2)我们约会吧



前言:

  在本篇博文中,本渣渣要通过约会网站给自己找个满意的妹纸,嘿嘿嘿!当然,一切都只是模拟,数据什么都是现成的······

  下面我们列出算法流程:

  • (1)收集数据:提供文本文件。
  • (2)准备数据:使用Python解析文本文件。
  • (3)分析数据:使用Matplotlib画二维扩散图。
  • (4)训练算法:此步骤不适用于k-近邻算法。
  • (5)测试算法:使用提供的部分数据作为测试样本。
    测试样本和非测试样本的区别在于:测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误。
    
  • (6)使用算法:产生简单的命令行程序,然后可以输入一些特征数据以判断对方是否为自己喜欢的类型。

一、准备数据

  datingTestSet2.txt中,每个样本数据占据一行,总共有1000行。主要包含以下3种特征:

  • 每年获得的飞行常客里程数
  • 玩视频游戏所耗时间百分比
  • 每周消费的冰淇淋公升数

  在将上述特征数据输入到分类器之前,必须将待处理数据的格式改变为分类器可以接受的格式。在kNN.py中创建名为file2matrix的函数,以此来处理输入格式问题。该函数的输入为文件名字符串,输出为训练样本矩阵和类标签向量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# -*- coding: UTF-8 -*-
"""
Created on Aug 18, 2017
kNN: k Nearest Neighbors

Input: inX: vector to compare to existing dataset (1xN)
dataSet: size m data set of known vectors (NxM)
labels: data set labels (1xM vector)
k: number of neighbors to use for comparison (should be an odd number)

Output: the most popular class label

@author: wordzzzz
"""

from numpy import *
import operator
from os import listdir

def file2matrix(filename):
"""
Function: 从文本文件中解析数据

Args: filename:文件名称字符串

Returns: returnMat:训练样本矩阵
classLabelVector:类标签向量
"""
#打开文件
fr = open(filename)
#得到文件行数
numberOFLines = len(fr.readlines())
#创建返回的NumPy矩阵
returnMat = zeros((numberOFLines, 3))
#创建返回的向量列表
classLabelVector = []
fr = open(filename)
index = 0
for line in fr.readlines():
#使用line.strip()截取掉多有的回车符
line = line.strip()
#使用tab字符将上一步得到的整行数据分割成一个元素列表
listFromLine = line.split('\t')
#选取前三个元素,存储到特征矩阵中
returnMat[index, :] = listFromLine[0:3]
#将列表最后一列存储到向量classLabelVector中
classLabelVector.append(int(listFromLine[-1]))
index += 1
#返回训练样本矩阵和类标签向量
return returnMat, classLabelVector

  注意我们要打开的文本文件是datingTestSet2.txt,而不是datingTestSet.txt,否则会出现如下报错:

1
2
3
4
5
6
>>> datingDataMat, datingLabels = kNN.file2matrix('datingTestSet.txt')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "E:\机器学习实战\mycode\Ch02\kNN.py", line 105, in file2matrix
classLabelVector.append(int(listFromLine[-1]))
ValueError: invalid literal for int() with base 10: 'largeDoses'

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
>>> import kNN
>>> datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
>>> datingDataMat
array([[ 4.09200000e+04, 8.32697600e+00, 9.53952000e-01],
[ 1.44880000e+04, 7.15346900e+00, 1.67390400e+00],
[ 2.60520000e+04, 1.44187100e+00, 8.05124000e-01],
...,
[ 2.65750000e+04, 1.06501020e+01, 8.66627000e-01],
[ 4.81110000e+04, 9.13452800e+00, 7.28045000e-01],
[ 4.37570000e+04, 7.88260100e+00, 1.33244600e+00]])
>>> datingLabels[0:20]
[3, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 2, 3]

二、分析数据

  首先我们使用Matplotlib制作原始数据的散点图,在Python命令行环境中输入如下命令:

1
2
3
4
5
6
>>> import matplotlib
>>> import matplotlib.pyplot as plt
>>> fig = plt.figure()
>>> ax = fig.add_subplot(111)
>>> ax.scatter(datingDataMat[:,1], datingDataMat[:,2])
>>> plt.show()

  输出效果如图2-3所示。散点图使用datingDataMat数据矩阵中的第二、第三列数据,分别表示特征值“玩视频游戏所耗时间百分比”和“每周所消费的冰淇淋公升数”。



  本渣渣在上图中看不出任何规律,因为没加标签信息。Matplotlib库提供的scatter函数支持个性化标记散点图上的点。重新输入上面的代码,并加上下面这段代码:

1
2
>>> ax.scatter(datingDataMat[:,1], datingDataMat[:,2],
15.0*array(datingLabels), 15.0*array(datingLabels))

  输出效果如图2-4所示。带有样本分类标签的约会数据散点图虽然能够比较容易地区分数据点从属类别,但依然很难根据这张图得出结论性信息。运行createFirstPlot.py可以直接得到该图。



  createDist2.py则使用数据矩阵中的第一、第二列数据,分别表示特征值“每年获得的飞行常客里程数”和“玩视频游戏所耗时间百分比”,展示效果更好:



  表2-3给出了提取的四组数据,如果想要极端样本3和样本4之间的距离,可以使用下面的方法:
!$\sqrt{(0-67)^2+(20000-32000)^2+(1.1-0.1)^2} $
  本渣渣发现,数字差值最大的特征对计算结果的影响最大。但是三种特征对我们来说是同等重要的,所以,我们需要对数值进行归一化,将取值范围处理为0到1之间(当然-1到1之间也可以)。下面是归一化的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def autoNorm(dataSet):
"""
Function: 归一化特征值

Args: dataSet:训练验本矩阵

Returns: normDataSet:归一化矩阵
ranges:每一列的差值
minVals:每一列的最小值
"""
#求取列的最小值
minVals = dataSet.min(0)
#求取列的最大值
maxVals = dataSet.max(0)
#最大值与最小值做差
ranges = maxVals - minVals
#创建输出矩阵normDataSet
normDataSet = zeros(shape(dataSet))
#m设定为矩阵dataSet的行数
m = dataSet.shape[0]
#对矩阵dataSet每个元素求差
normDataSet = dataSet - tile(minVals, (m, 1))
#对矩阵dataSet每个元素归一化
normDataSet = normDataSet/tile(ranges, (m, 1))
#返回归一化矩阵、差值向量和最小值向量
return normDataSet, ranges, minVals

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> normMat, ranges, minVals = kNN.autoNorm(datingDataMat)
>>> normMat
array([[ 0.44832535, 0.39805139, 0.56233353],
[ 0.15873259, 0.34195467, 0.98724416],
[ 0.28542943, 0.06892523, 0.47449629],
...,
[ 0.29115949, 0.50910294, 0.51079493],
[ 0.52711097, 0.43665451, 0.4290048 ],
[ 0.47940793, 0.3768091 , 0.78571804]])
>>> ranges
array([ 9.12730000e+04, 2.09193490e+01, 1.69436100e+00])
>>> minVals
array([ 0. , 0. , 0.001156])

##三、测试算法

  机器学习算法一个很重要的工作就是评估算法的正确率,通常我们只提供已有数据的90%作为训练样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率。需要注意的是,10%的测试数据应该是随机选择的,由于我们的数据并没有按照特定目的来排序,所以我们可以随意选择10%数据而不影响其随机性。

  代码里我们定义一个计数器变量,每次分类器错误的分类数据,计数器就加1,程序执行完成之后计数器的结果除以数据点总数即是错误率。为了测试分类器效果,在kNN.py文件中创建函数datingClassTest,该函数是自包含的,你可以在任何时候在Python运行环境中使用该函数测试分类器效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def datingClassTest():
"""
Function: 分类器测试代码

Args: 无

Returns: classifierResult:分类器分类结果
datingLabels[i]:真实结果
errorCount:分类误差
"""
#测试集比例设定:10%
hoRatio = 0.1
#从文本文件中解析数据
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
#归一化特征值
normMat, ranges, minVals = autoNorm(datingDataMat)
#计算normMat矩阵行数并赋值给m
m = normMat.shape[0]
#初始化测试向量个数
numTestVecs = int(m*hoRatio)
#初始化错误计数
errorCount = 0.0
#对测试集分类,返回分类结果并打印
for i in range(numTestVecs):
#传参给分类器进行分类,每个for循环改变的参数只有第一项的测试数据而已
classifierResult = classify0(normMat[i,:], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
#打印当前测试数据的分类结果个真实结果
print("the classfier 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)))
#输出测试错误数
print(errorCount)

输出结果:

1
2
3
4
5
6
7
8
9
10
>>> kNN.datingClassTest()
the classfier came back with: 3, the real answer is: 3
the classfier came back with: 2, the real answer is: 2
the classfier came back with: 1, the real answer is: 1
······
the classfier came back with: 2, the real answer is: 2
the classfier came back with: 1, the real answer is: 1
the classfier came back with: 3, the real answer is: 1
the total error rate is: 0.050000
5.0

  分类器处理约会数据集的错误率为5%,这是一个很不错的结果。依赖于分类算法、数据集和程序设置,分类器的输出结果可能有很大的不同。大家可以自行修改变量hoRatio和变量k的数值,看看检测错误率是否会发生变化。

  这个例子表明我们可以正确的预测分类,错误率仅为5%。我们完全可以输入未知对象的属性信息,由分类软件来帮助让判定某一对象的可交往程度。

四、使用算法

  我们把我们的代码封装一下,并加入输入捕捉函数input()进行输入捕捉。该函数允许用户输入文本命令行并返回用户所输入的命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def classifyPerson():
"""
Function: 约会网站测试函数

Args: percentTats:玩视频游戏消耗时间百分比
ffMiles:每年获得的飞行常客里程数
iceCream:每周消费的冰淇淋公升数

Returns: resultList:可交往程度
"""
#建立输出列表
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 week?"))
#从文本文件中解析数据
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 wil probably like this person: ", resultList[classifierResult - 1])

输出结果:

1
2
3
4
5
>>> kNN.classifyPerson()
percentage of time spent playing video games?10
frequent flier miles earned per year?10000
liters of ice cream consumed per year?0.5
You wil probably like this person: in small doses

五、主要区分函数:

详解:
raw_input( )和input( ):
http://blog.csdn.net/u011475210/article/details/77777482

系列教程持续发布中,欢迎订阅、关注、收藏、评论、点赞哦~~( ̄▽ ̄~)~

完的汪(∪。∪)。。。zzz

0%