1 项目介绍
某APP用户一直使用在线约会软件寻找适合自己的约会对象。尽管约会网站会推荐不同的人选,但她并不是喜欢每一个人。经过一番总结,她发现曾交往过三种类型的人:
- 不喜欢的人(3)
- 魅力一般的人(2)
- 极具魅力的人(1)
某APP用户希望分类软件可以更好地帮助她将匹配对象划分到确切的分类中。此外还可以收集了约会软件未曾记录的数据信息,她认为这些数据更有助于匹配对象的归类。收集的部分信息如下图所示:
数据集下载
样本主要包含以下3种特征:
- 每年获得的飞行常客里程数
- 玩视频游戏所耗时间百分比
- 每周消费的冰淇淋公升数
2 准备数据:从文本文件中解析数据
在将上述特征数据输入到分类器之前,必须将待处理数据的格式改变为分类器可以接受的格式。
import numpy as np
def file2matrix(filename):
"""
:param filename: APP用户收集的约会数据的文件名
:return:
returnMat: 用户提供的每行数据信息,三列,
分别是每年获得的飞行常客里程数,
玩视频游戏所耗时间百分比,
每周消费的冰淇淋公升数
classLabelVetor:
用户的评价信息, 一般分为3类(1,2,3)
"""
fr = open(filename)
arrayOfLines = fr.readlines()
# print(arrayOfLines)
# 获得文件行数;
numerOfLines = len(arrayOfLines)
# 创建要返回的Numpy矩阵;
returnMat = np.zeros((numerOfLines, 3))
# 解析文件数据到矩阵中;
classLabelVetor = []
index = 0
for line in arrayOfLines:
line = line.strip()
listFromLine = line.split('\t')
returnMat[index, :] = listFromLine[0:3]
classLabelVetor.append(listFromLine[-1])
index += 1
return returnMat, classLabelVetor
print(file2matrix('data/datingTestSet2'))
返回的值显示:
3 分析数据:使用 Matplotlib 创建散点图
使用Matplotlib库图形化清晰地标识了三个不同的样本分类区域,具有不同爱好的人其类别区域也不同。
def draw_pic(datingDataMat, datingLabels):
"""
每年获取的飞行常客里程数与每周所消费的冰淇淋公升数”构成的散点图。
:param datingDataMat:
:param datingLabels:
:return:
"""
# 中文显示乱码问题;
myfont = font_manager.FontProperties(fname="/usr/share/fonts/cjkuni-uming/uming.ttc", size=12)
# 创建画布
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(datingDataMat[:, 0], datingDataMat[:, 2],
15 * datingLabels, datingLabels)
plt.xlabel("每年的飞行里程数", fontproperties=myfont)
plt.ylabel("每周消费的冰淇淋公升数", fontproperties=myfont)
plt.grid(alpha=0.5)
plt.show()
- 效果展示
4 准备数据:归一化数值
- 计算样本3和样本4之间的距离:
- 问题:
飞行常客里程数对于计算结果的影响将远远大于其他两个特征的影响 - 解决方式:
处理不同取值范围的特征值时,通常采用的方法是将数值归一化,如将取值范围处理为0到1或者-1到1之间。
- 归一化公式: newValue = oldValue / max
def autoNorm(dataSet):
"""
归一化数值,
:param dataSet:用户提供的每行数据信息,三列;
:return:
normDataSet: 归一化的特征信息;
maxVals:每个特征数据的最大值;
"""
# 获取每个特征数据的最大值;
maxVals = dataSet.max(0)
# 获取样本个数;
m = dataSet.shape[0]
# 根据公式生成归一化的特征信息;
normDataSet = dataSet / np.tile(maxVals, (m, 1))
return normDataSet, maxVals
4 实施 kNN 算法
对未知类别属性的数据集中的每个点依次执行以下操作, 与上一个案例代码相同:
(1) 计算已知类别数据集中的点与当前点之间的距离;
(2) 按照距离递增次序排序;
(3) 选取与当前点距离最小的k个点;
(4) 确定前k个点所在类别的出现频率;
(5) 返回前k个点出现频率最高的类别作为当前点的预测分类。
def classify(inX, dataSet, labels, k):
"""
:param inX: 要预测的数据
:param dataSet: 我们要传入的已知数据集
:param labels: 我们要传入的标签
:param k: KNN里的k, 也就是说我们要选几个近邻
:return: 排序的结果
"""
dataSetSize = dataSet.shape[0] # (6,2) 6
# tile会重复inX, 把他重复成(datasetsize, 1)型的矩阵
# print(inX)
# (x1 - y1), (x2- y2)
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
# 平方
sqDiffMat = diffMat ** 2
# 相加, axis=1 行相加
sqDistance = sqDiffMat.sum(axis=1)
# 开根号
distances = sqDistance ** 0.5
# print(distances)
# 排序 输出的是序列号index,并不是值
sortedDistIndicies = distances.argsort()
# print(sortedDistIndicies)
classCount = {}
for i in range(k):
voteLabel = labels[sortedDistIndicies[i]]
classCount[voteLabel] = classCount.get(voteLabel, 0) + 1
# print(classCount)
sortedClassCount = sorted(classCount.items(), key=lambda d: float(d[1]), reverse=True)
return sortedClassCount[0]
5 测试算法:作为完整程序验证分类器
如果分类器的正确率满足要求,就可以使用这个软件来处理约会网站提供的约会名单了。机器学习算法一个很重要的工作就是评估算法的正确率,通常我们只提供已有数据的90%作为训练样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率。
def datingClassTest():
"""
分类器针对约会网站的测试代码, 获取错误率;
:return:
"""
hoRatio = 0.10
datingDataMat, datingLabels = file2matrix('data/datingTestSet2')
normDataSet, maxVals = autoNorm(datingDataMat)
# 样本个数
m = normDataSet.shape[0]
# 测试集个数;
numTestVecs = int(m*hoRatio)
errorCount = 0.0
for i in range(numTestVecs):
classiferResult = classify(normDataSet[i, :],
normDataSet[numTestVecs:m, :],
datingLabels[numTestVecs:m], 3)
# print(classiferResult)
if classiferResult != datingLabels[i]:
errorCount += 1
print("正确结果:", datingLabels[i])
print("预测结果:", classiferResult)
# print("错误个数:", errorCount)
return errorCount
- 执行效果展示:
6 使用算法:构建完整可用的预测系统
def classifyPerson(Person):
"""
使用这个分类器为某APP用户来对人们分类。
:param Person:
:return:
"""
datingDataMat, datingLabels = file2matrix('data/datingTestSet2')
normDataSet, maxVals = autoNorm(datingDataMat)
classiferResult = classify(Person / maxVals, normDataSet, datingLabels, 3)
if classiferResult == '1':
print("不喜欢")
elif classiferResult == '2':
print("有一点喜欢")
else:
print("非常喜欢")
完整代码
# encoding:utf-8
"""
KNN实现,基于KNN分类的约会网站配对改进算法
"""
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import font_manager
def file2matrix(filename):
"""
:param filename: APP用户收集的约会数据的文件名
:return:
returnMat: 用户提供的每行数据信息,三列,
分别是每年获得的飞行常客里程数,
玩视频游戏所耗时间百分比,
每周消费的冰淇淋公升数
classLabelVetor:
用户的评价信息, 一般分为3类(1,2,3)
"""
fr = open(filename)
arrayOfLines = fr.readlines()
# print(arrayOfLines)
# 获得文件行数;
numerOfLines = len(arrayOfLines)
# 创建要返回的Numpy矩阵;
returnMat = np.zeros((numerOfLines, 3))
# 解析文件数据到矩阵中;
classLabelVetor = []
index = 0
for line in arrayOfLines:
line = line.strip()
listFromLine = line.split('\t')
returnMat[index, :] = listFromLine[0:3]
classLabelVetor.append(listFromLine[-1])
index += 1
return returnMat, classLabelVetor
def autoNorm(dataSet):
"""
归一化数值,
计算样本3和样本4之间的距离: [(0-67)**2 + (20000 - 32 000)**2 + (1.1 - 0.1)**2]**0.5
问题:
飞行常客里程数对于计算结果的影响将远远大于其他两个特征的影响
解决方式:
处理不同取值范围的特征值时,
通常采用的方法是将数值归一化,如将取值范围处理为0到1或者-1到1之间。
归一化公式: newValue = oldValue / max
:param dataSet:用户提供的每行数据信息,三列;
:return:
normDataSet: 归一化的特征信息;
maxVals:每个特征数据的最大值;
"""
# 获取每个特征数据的最大值;
maxVals = dataSet.max(0)
# 获取样本个数;
m = dataSet.shape[0]
# 根据公式生成归一化的特征信息;
normDataSet = dataSet / np.tile(maxVals, (m, 1))
return normDataSet, maxVals
def draw_pic(datingDataMat, datingLabels):
"""
每年获取的飞行常客里程数与每周所消费的冰淇淋公升数”构成的散点图。
:param datingDataMat:
:param datingLabels:
:return:
"""
# 中文显示乱码问题;
myfont = font_manager.FontProperties(fname="/usr/share/fonts/cjkuni-uming/uming.ttc", size=12)
# 创建画布
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(datingDataMat[:, 0], datingDataMat[:, 2],
15 * datingLabels, datingLabels)
plt.xlabel("每年的飞行里程数", fontproperties=myfont)
plt.ylabel("每周消费的冰淇淋公升数", fontproperties=myfont)
plt.grid(alpha=0.5)
plt.show()
def classify(inX, dataSet, labels, k):
"""
:param inX: 要预测的数据
:param dataSet: 我们要传入的已知数据集
:param labels: 我们要传入的标签
:param k: KNN里的k, 也就是说我们要选几个近邻
:return: 排序的结果
"""
dataSetSize = dataSet.shape[0] # (6,2) 6
# tile会重复inX, 把他重复成(datasetsize, 1)型的矩阵
# print(inX)
# (x1 - y1), (x2- y2)
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
# 平方
sqDiffMat = diffMat ** 2
# 相加, axis=1 行相加
sqDistance = sqDiffMat.sum(axis=1)
# 开根号
distances = sqDistance ** 0.5
# print(distances)
# 排序 输出的是序列号index,并不是值
sortedDistIndicies = distances.argsort()
# print(sortedDistIndicies)
classCount = {}
for i in range(k):
voteLabel = labels[sortedDistIndicies[i]]
classCount[voteLabel] = classCount.get(voteLabel, 0) + 1
# print(classCount)
sortedClassCount = sorted(classCount.items(), key=lambda d: float(d[1]), reverse=True)
return sortedClassCount[0][0]
def datingClassTest():
"""
分类器针对约会网站的测试代码, 获取错误率;
:return:
"""
hoRatio = 0.10
datingDataMat, datingLabels = file2matrix('data/datingTestSet2')
normDataSet, maxVals = autoNorm(datingDataMat)
# 样本个数
m = normDataSet.shape[0]
# 测试集个数;
numTestVecs = int(m*hoRatio)
errorCount = 0.0
for i in range(numTestVecs):
classiferResult = classify(normDataSet[i, :],
normDataSet[numTestVecs:m, :],
datingLabels[numTestVecs:m], 3)
# print(classiferResult)
if classiferResult != datingLabels[i]:
errorCount += 1
print("正确结果:", datingLabels[i])
print("预测结果:", classiferResult)
# print("错误个数:", errorCount)
return errorCount
def classifyPerson(Person):
"""
使用这个分类器为某APP用户来对人们分类。
:param Person:
:return:
"""
datingDataMat, datingLabels = file2matrix('data/datingTestSet2')
normDataSet, maxVals = autoNorm(datingDataMat)
classiferResult = classify(Person / maxVals, normDataSet, datingLabels, 3)
if classiferResult == '1':
print("不喜欢")
elif classiferResult == '2':
print("有一点喜欢")
else:
print("非常喜欢")
if __name__ == '__main__':
# personData = [30000, 10, 1.3]
personData = [40920, 8.326976, 0.953952]
classifyPerson(personData)
- 执行结果
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。