k-近邻算法2——识别约会对象
本文主要内容来自Peter Harrington的《Machine Learning in Action》的中文版本《机器学习实战》
本文中数据和代码在这里
示例2
任务——海伦一直在约会网站上寻找合适的约会对象,但网站推荐的约会对象并不都合她心意。她把曾约会过的对象分为三个类型:毫无魅力、魅力一般、极具魅力,然后收集到一些有益数据,我们将根据她收集到的数据创建分类器,帮助她把网站推荐的约会对象划分到切确的类。
识别约会对象的k-近邻算法
- 收集数据:海伦提供的文本文件
- 准备数据:使用python解析文本文件
- 分析数据:使用Matplotlib画二维散点图
- 训练算法:此步骤不适合k-近邻算法
- 测试算法:利用留出法把海伦提供的数据,随机提取10%作为测试数据,并统计错误率。
- 使用算法:利用分类器,根据海伦输入的特征数据,对约会对象进行判断
收集数据
海伦收集的数据在datingTestSet2.txt
中,每个样本数据占一行,总共1000行。海伦的样本主要包含以下三种特征:
- 每年获得的飞行常客里程数
- 玩视频游戏所耗时间百分比
- 每周消费的冰淇淋公升数
准备数据
由于数据在文本文件datingTestSet2.txt
中,我们需要解析文本文件,把数据转化为分类器可接受的格式。
##转换文本数据
def fileToMatrix(filename):
file = open(filename) #读入文件
lines = file.readlines() #按行读取
numberOfLines = len(lines) #统计行数
datingMatrix = zeros((numberOfLines, 3)) #约会对象特征矩阵
datingLabels = [] #约会对象类型向量
index = 0
for line in lines:
line = line.strip() #去除首尾空格
listFromLine = line.split('\t') #使用tab字符把整行数据分割为数据列表
datingMatrix[index, :] = listFromLine[0:3] #把前三个数据存储到对象特征矩阵
datingLabels.append(int(listFromLine[-1])) #最后一个数据强制转化为整型后,存储到约会对象类型向量中
index += 1
return datingMatrix, datingLabels
fileToMatrix
把文本中的数据,转化输出到对象特征矩阵datingMatrix
和约会对象类型向量datingLabels
中。
我们写一个测试程序
import kNN
datingMatrix, datingLabel = kNN.fileToMatrix('datingTestSet2.txt')
print(datingMatrix)
print(datingLabel)
运行结果如下
[[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]]
[3, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2, ....2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 1, 3, 3, 3]
分析数据
我们使用二维散点图来分析数据,其中黄色点表示极具魅力,蓝色表示魅力一般,紫色表示毫无魅力
通过三张散点图,我们发现前两个特征更容易区分数据点从属的类别。
数据归一化
由于三个特征数据之间数值相差很大,而海伦认为三者同等重要,所以我们需要进行归一化处理。
我们利用公式\(newValue = (oldValue-min)/(max-min)\),其中\(min\)和\(max\)是最小特征值和最大特征值。
把约会对象的特征数据归一化
def normDatingData(dataSet):
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = zeros(shape(dataSet))
rows = dataSet.shape[0]
normDataSet = dataSet - tile(minVals, (rows, 1))
normDataSet = normDataSet/tile(ranges, (rows, 1))
return normDataSet, minVals, ranges
测试算法
利用留出法进行测试,并计算错误率
def datingClassTest():
hoRatio = 0.10 # 留出法10%
datingMatrix, datingLabels = fileToMatrix('datingTestSet2.txt') # 读数据
normMat = normDatingData(datingMatrix)
rows = normMat.shape[0]
numTestVecs = int(rows * hoRatio)
errorCount = 0.0
for i in range(numTestVecs):
classifierResult = classify0(normMat[i, :], normMat[numTestVecs:rows, :], datingLabels[numTestVecs:rows], 3)
print("分类器返回结果: %d, 真正的结果: %d" % (classifierResult, datingLabels[i]))
if (classifierResult != datingLabels[i]): errorCount += 1.0
print("错误率: %f" % (errorCount / float(numTestVecs)))
运行结果:
分类器返回结果: 3, 真正的结果: 3
分类器返回结果: 2, 真正的结果: 2
分类器返回结果: 1, 真正的结果: 1
分类器返回结果: 1, 真正的结果: 1
分类器返回结果: 1, 真正的结果: 1
分类器返回结果: 1, 真正的结果: 1
...
分类器返回结果: 2, 真正的结果: 2
分类器返回结果: 1, 真正的结果: 1
分类器返回结果: 3, 真正的结果: 3
分类器返回结果: 3, 真正的结果: 3
分类器返回结果: 2, 真正的结果: 2
分类器返回结果: 1, 真正的结果: 1
分类器返回结果: 3, 真正的结果: 1
错误率: 0.050000
使用算法
对单个约会对象进行分类
def classifyPerson():
resultList = ['毫无魅力', '魅力一般', '极具魅力']
percentTats = float(input("玩视频游戏所耗时间百分比:"))
ffMiles = float(input("每年获得的飞行常客里程数:"))
iceCream = float(input("每周消费的冰淇淋公升数:"))
datingMatrix, datingLabels = fileToMatrix('datingTestSet2.txt')
normMat, minVals, ranges = normDatingData(datingMatrix)
person = array([ffMiles, percentTats, iceCream])
normPerson = (person-minVals)/ranges
classifierResult = classify0(normPerson, normMat, datingLabels, 5)
print("此人魅力值:", resultList[classifierResult-1])
运行结果
玩视频游戏所耗时间百分比:15
每年获得的飞行常客里程数:40000
每周消费的冰淇淋公升数:1
此人魅力值: 极具魅力