2.2 使用k-近邻算法改进约会网站的配对效果
Helen交往过三种类型的人:
不喜欢的人
魅力一般的人
极具魅力的人
示例:在约会网站上使用k-近邻算法
(1)收集数据:提供文本文件; datingTestSet2.txt链接 https://files.cnblogs.com/files/Lamfai/datingTestSet2.rar
(2)准备数据:使用Python解析文本文件;
(3)分析数据:使用Matplotlib画二维扩散图;
(4)训练算法:此步骤不适用于k-近邻算法;
(5)测试算法:使用Helen提供的部分数据作为测试样本,测试样本和非测试样本的区别在于:测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误;
(6)使用算法:产生简单的命令行程序,然后Helen可以输入一些特征数据判断对方是否为自己喜欢的类型。
2.1.1 准备数据:从文本文件中解析数据
Helen把约会数据收集存放在文本文件datingTestSet2.txt中,每个样本数据占据一行,总共有1000行。Helen的样本主要包含以下3中特征:
每年获得的飞行常客里程数
玩视频游戏所耗时间百分比
每周消费的冰淇淋公斤数
在将上述特征数据输入到分类器之前,必须将待处理数据的格式改变为分类器可以接受的格式。在kNN.py中创建名为file2matrix的函数,以此来处理输入格式问题。该函数的输入为文件名字符串,输出为训练样本矩阵和类标签向量。
将下面的代码增加到kNN.py中:
def file2matrix(filename): fr = open(filename) # 得到文件行数 numberOfLines = len(fr.readlines()) # 创建返回的NumPy矩阵 returnMat = zeros((numberOfLines,3)) # 解析文件数据到列表 classLabelVector = [] fr = open(filename) index = 0 for line in fr.readlines(): line = line.strip() listFromLine = line.split('\t') returnMat[index,:] = listFromLine[0:3] classLabelVector.append(int(listFromLine[-1])) index += 1 return returnMat,classLabelVector
Python处理文本文件非常容易。
首先,我们需要知道文本文件包含多少行。打开文件,得到文件的行数。
然后创建以零填充的矩阵NumPy,为了简化处理,我们将该矩阵的另一维度设置为固定值3,你可以按照自己的实际需求增加相应的代码以适应变化的输入值。
循环处理文件中的每行数据,首先使用line.strip()截取掉所有的回车字符,然后使用tab字符\t将上一步得到的整行数据分割成一个元素列表。接着我们选取前3个元素,将它们存储到特征矩阵中。Python语言可以使用索引值-1表示列表中的最后一列元素,利用这种负索引,我们可以很方便地将列表的最后一列存储到向量classLabelVector中。我们必须明确地通知解析器,告诉它列表中存储的元素值为整型,否则Python语言将这些元素当作字符串处理。以前我们必须自己处理这些变量值类型问题,现在这些细节问题完全可以交给NumPy函数库来处理。
在Terminal中输入以下命令:
from imp import reload
reload(kNN) datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
使用函数file2matrix读取文件数据,必须确保文件datingTestSet2.txt存储在我们的工作目录中。
输入:
datingDataMat
输出:
NumPy数组和Python数组:可以在Python命令行环境中输入from numpy import array将其导入。
2.2.2 分析数据:使用Matplotlib创建散点图
首先,使用Matplotlib制作原始数据的散点图,在Terminal中输入以下命令:
import matplotlib import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_subplot(111) ax.scatter(datingDataMat[:,1],datingDataMat[:,2]) plt.show()
输出效果:
散点图使用datingDataMat矩阵的第二、第三列数据,分别表示特征值“玩视频游戏所耗时间百分比”和“每周所消费的冰淇淋公升数”。但是没有使用样本分类的特征值,我们很难从上图中看到任何有用的数据模式信息。一般来说,我们会采用色彩或其他的记号来标记不同样本分类,以便更好地理解数据信息。Matplotlib库提供的scatter函数支持个性化标记散点图上的点。输入以下的代码:
import kNN from numpy import array import matplotlib import matplotlib.pyplot as plt 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)) plt.show()
上述代码利用了变量datingLabels存储的类标签属性,在散点图上绘制了色彩不等、尺寸不同的点。你可以看到一个与上图类似的散点图。在上图中,我们很难看到任何有用的信息,然而由于下图利用颜色及尺寸标识了数据点的属性类别,因此我们基本上可以从下图看到数据点所属三个样本分类的区域轮廓。
上图是带有样本分类标签的约会数据散点图。虽然能够比较容易地区分数据点从属类别,但依然很难根据这张图得到结论性信息。
2.2.3 准备数据:归一化数据
上表给出了提取的四组数据,如果想要计算样本3和样本4之间的距离,可以使用下面的办法:
可以很容易发现上面方程中数字差值最大的属性对结算结果的影响最大,也就是说,每年获取的飞行常客里程数对于计算结果的影响将远远大于上表其他两个特征——玩视频游戏的和每周消费冰淇淋公升数的影响,而产生这种现象的唯一原因,仅仅是因为飞行常客里程数远大于其他特征值。
在处理这种不同的取值范围的特征值时,我们通常采用的方法时将数值归一化,如取值范围处理为0到1或者-1到1之间。
下面的公式可以将任意范围的特征值转化为0到1区间内的值:
newValue = (oldValue-min)/(max-min)
其中min和max分别是数据集中的最小特征值和最大特征值。虽然改变了数值取值范围增加了分类器的复杂度,但为了得到准确结果,我们必须这样做。
在kNN.py中增加一个新函数autoNorm(),该函数可以自动将数字特征值转换为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)) #element wise divide return normDataSet, ranges, minVals
在函数sutoNorm()中,将每列的最小值放在变量minVals中,将最大值放在变量maxVals中,其中dataSet.min(0)中的参数0使得函数可以从列中选取最小值,而不是选取当前行的最小值。然后,函数计算可能的取值范围,并创建新的返回矩阵。
为了归一化特征值,我们必须使用当前值减去最小值,然后除以取值范围。需要注意的是,特征值矩阵由1000×3个值,而minVals和range的值都为1×3。为了解决这个问题,我们使用Numpy库中title()函数将变量内容复制成输入矩阵同样大小的矩阵,注意这是具体特征值相除,而对于某些数值处理软件包,/可能意味着矩阵除法,但在NumPy库中,矩阵除法需要使用函数