1. 情境导入:
我的朋友海伦一直使用在线约会网站寻找适合自己的约会对象。尽管约会网站会推荐不同的人选,但她并不是喜欢每一个人。经过一番总结,她发现曾交往过三种类型的人:
- 不喜欢的人
- 魅力一般的人
- 极具魅力的人
尽管发现了上述规律,但海伦依然无法将约会网站推荐的匹配对象归入恰当的类别。她觉得可以在周一到周五约会那些魅力一般的人,而周末则更喜欢与那些极具魅力的人为伴。海伦希望我们的分类软件可以更好地帮助她将匹配对象划分到确切的分类中。
海伦收集约会数据已经有了一段时间,她把这些数据存放在文本文件datingTestSet2.txt中,每个样本数据占据一行,总共有1000行。海伦的样本主要包含以下3种特征:
- 每年获得的飞行常客里程数
- 玩视频游戏所耗时间百分比
- 每周消费的冰淇淋公升数
2. 准备数据:从文本文件中解析数据
在将上述特征数据输入到分类器之前,必须将待处理数据的格式改变为分类器可以接受的格式。 创建名为file2matrix的函数,以此来处理输入格式问题。该函数的输入为文件名字符串,输出为训练样本矩阵和类标签向量。
def file2matrix(filename):
fr = open(filename)
arrayolines = fr.readlines() # 得到文件的行数
numberoflines = len(arrayolines)
returnmat = np.zeros((numberoflines,3))
# 创建以0填充的NumPy矩阵,为了简化处理,将另一维度设置为3
classlabelvector = []
index = 0
for line in arrayolines:
line = line.strip() # 截取掉所有的回车字符
listfromline = line.split('\t') # 将上一步得到的整行数据分割成一个元素列表
returnmat[index, :] = listfromline[0 : 3] # 选取前三个元素
labels = {'didntLike':1,'smallDoses':2,'largeDoses':3}
classlabelvector.append(labels[listfromline[-1]]) # -1表示列表的最后一列元素
index += 1
return returnmat, classlabelvector
使用函数file2matrix读取文件数据后,可以简单检查一下数据内容:
datingdatamat, datinglabels = file2matrix('datingTestSet2.txt')
print("datingdatamat:", datingdatamat)
print("datinglabels:", datinglabels[0 : 20])
datingdatamat: [[4.0920000e+04 8.3269760e+00 9.5395200e-01]
[1.4488000e+04 7.1534690e+00 1.6739040e+00]
[2.6052000e+04 1.4418710e+00 8.0512400e-01]
...
[2.6575000e+04 1.0650102e+01 8.6662700e-01]
[4.8111000e+04 9.1345280e+00 7.2804500e-01]
[4.3757000e+04 7.8826010e+00 1.3324460e+00]]
datinglabels: [3, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 2, 3]
3.分析数据:使用Matplotlib创建散点图
使用Matplotlib制作原始数据的散点图:
import matplotlib
import matplotlib.pyplot as plt
# 支持中文字符展示
from pylab import *
mpl.rcParams['font.sans-serif'] = ['SimHei']
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(datingdatamat[:,1], datingdatamat[:,2],
15.0*np.array(datinglabels), 15.0*np.array(datinglabels))
plt.xlabel('玩视频游戏所耗时间百分比')
plt.ylabel('每周所消费的冰淇淋公升数')
plt.show()
散点图使用datingdatamat的第二、三列数据,即特征“玩视频游戏所耗时间百分比”和“每周所消费的冰淇淋公升数”。
上图使用datingdatamat的第二、三列属性展示数据,下图为datingdatamat矩阵第一、二列属性展示数据,具有更好的展示效果。
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
from pylab import *
mpl.rcParams['font.sans-serif'] = ['SimHei']
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(datingdatamat[:,0], datingdatamat[:,1],
15.0*np.array(datinglabels), 15.0*np.array(datinglabels))
plt.xlabel('每年获取的飞行常客里程数')
plt.ylabel('玩视频游戏所耗时间百分比')
type1_x = []
type1_y = []
type2_x = []
type2_y = []
type3_x = []
type3_y = []
for i in range(len(datinglabels)):
if datinglabels[i] == 1:
type1_x.append(datingdatamat[i][0])
type1_y.append(datingdatamat[i][1])
if datinglabels[i] == 2:
type2_x.append(datingdatamat[i][0])
type2_y.append(datingdatamat[i][1])
if datinglabels[i] == 3:
type3_x.append(datingdatamat[i][0])
type3_y.append(datingdatamat[i][1])
plt.scatter(type1_x, type1_y, s = 30, label = u'不喜欢')
plt.scatter(type2_x, type2_y, s = 30, label = u'魅力一般')
plt.scatter(type3_x, type3_y, s = 30, label = u'极具魅力')
plt.legend()
plt.show()
图中清晰地标识了三个不同的样本分类区域,具有不同爱好的人其类别区域也不同。
4.准备数据:归一化数值
在处理不同取值范围的特征值时,通常采用的方法是将数值归一化,如将取值范围处理为0到1或-1到1之间。下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:
def autonorm(dataset):
minvals = dataset.min(0)
maxvals = dataset.max(0)
ranges = maxvals - minvals
normdataset = zeros(shape(dataset))
m = dataset.shape[0]
normdataset = dataset - tile(minvals, (m, 1))
normdataset = normdataset/tile(ranges, (m, 1))
return normdataset, ranges, minvals
normmat, ranges, minvals = autonorm(datingdatamat)
在函数autonorm()中,将每列的最小值放在变量minvals中,最大值放在变量maxvals中,其中dataset.min(0)中的参数0使函数从列中选取最小值。
为了归一化特征值,必须使用当前值减去最小值,然后除以取值范围。需要注意的是,特征值矩阵有1000×3个值,而minvals和range的值都为1x3。为解决这个问题,使用NumPy库中tile()函数将变量内容复制成输入矩阵同样大小的矩阵(这是具体特征值相除),而对于某些数值处理软件包,/可能意味着矩阵除法,但在NumPy库中,矩阵除法需要使用函数linalg.solve(matA,matB)。
print("normmat:", normmat)
print("ranges:", ranges)
print("minvals:", minvals)
normmat: [[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: [9.1273000e+04 2.0919349e+01 1.6943610e+00]
minvals: [0. 0. 0.001156]
5. 测试算法:作为完整程序验证分类器
def datingclasstest():
horatio = 0.1
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)))
首先使用了file2matrix和autonorm函数从文件中读取数据并将其转换为归一化特征值。接着计算测试向量的数量,决定了normmat向量中哪些数据用于测试,哪些数据用于分类器的训练样本;然后将这两部分数据输入到原始 kNN分类器函数classify0。最后,函数计算错误率并输出结果。
datingclasstest()
the classifier came back with: 2, the real answer is 2
the classifier came back with: 1, the real answer is 1
………………
the classifier came back with: 1, the real answer is 1
the classifier came back with: 3, the real answer is 1
the total error rate is : 0.050000
分类器处理约会数据的错误率为5%。可以通过改变datingclasstest内变量horatio和变量k的值,检测错误率是否会随着变量值的变化而增加。
6. 使用算法:构建完整可用系统
上面我们已经在数据上对分类器进行了测试,现在终于可以使用这个分类器为海伦来对人们
分类。我们会给海伦一小段程序,通过该程序海伦会在约会网站上找到某个人并输入他的信息。
程序会给出她对对方喜欢程度的预测值。
def classifyperson():
resultlist = ['not at all', 'in small doses', 'in large doses']
percenttats = float(input(\
"percecntage of time spent playing video game?"))
ffmiles = float(input("frequent flier miles earned per year?"))
icecream = float(input("liters of ice cream consumed per year?"))
normmat, ranges, minvals = autonorm(datingdatamat)
inarr = np.array([ffmiles, percenttats, icecream])
classifierresult = classify0((inarr-\
minvals)/ranges, normmat, datinglabels, 3)
print("You will probably like this person: ",\
resultlist[classifierresult-1])
classifyperson()
percecntage of time spent playing video game?10
frequent flier miles earned per year?10000
liters of ice cream consumed per year?0.5
You will probably like this person: in small doses