老板:小韩啊,别忘了去改进一下约会网站的配对效果。前端
我:好嘞好嘞!立刻工做!!python
好了,又要开始一天的工做啦。接着上篇文章老板布置的任务,咱们来看一下此次实战的相关信息。程序员
老板的朋友,卡特琳娜一直在使用约会网站寻找适合本身的约会对象。尽管约会网站会给她推荐不一样的人选,但她并非喜欢每个人。通过一番总结,她发现她曾经交往过三种类型的人:算法
尽管发现了这样的规律,可是卡特琳娜仍是没办法及那个网站推荐的人分类,这可把她给愁坏了!因此,她找到咱们,但愿咱们能够写一个分类软件,来帮助她将匹配对象划分到确切的分类中。app
此外,她还手机了一些约会网站不曾记录的信息,她认为这能过够帮到咱们!机器学习
示例:在约会网站上使用k-近邻算法 (1) 收集数据:卡特琳娜提供的文本文件。ide
(2) 准备数据:使用Python解析文本文件。函数
(3) 分析数据:使用Matplotlib画二维扩散图。工具
(4) 训练算法:此步骤不适用于k-近邻算法。性能
(5) 测试算法:使用卡特琳娜提供的部分数据做为测试样本。 测试样本和非测试样本的区别在于:测试样本是已经完成分类的数据,若是预测分类与实际类别不一样,则标记为一个错误。
(6) 使用算法:产生简单的命令行程序,而后卡特琳娜能够输入一些特征数据以判断对方是否为本身喜欢的类型。
咱们须要进行的第一步就是数据解析。咱们必须弄明白咱们的数据以什么形式存储的,数据中的每一项都表明什么含义。
卡特琳娜提供给咱们一个txt文件,叫作datingSet.txt,其中每一个样本占一行,总共有1000行。每一个样本主要包括如下三个特征:
也就是下面这样的形式:
在将上述特征数据输入到分类器以前,必须先进行数据处理,将待处理数据的格式改变为分类器能够接受的格式。接着咱们上次写过的kNN.py文件,咱们在这个文件中建立名为file2matrix的函数,以此来处理输入格式问题。
该函数的输入为文件名字符串,输出为训练样本矩阵和类标签向量。
def file2matrix(filename): # 1.获得文件行数 fr = open(filename) arrayOLines = fr.readlines() numberOfLines = len(arrayOLines) # 2.建立返回的NumPy矩阵 returnMat = zeros((numberOfLines, 3)) classLabelVector = [] index = 0 # 3.解析文件数据到列表 for line in arrayOLines: line = line.strip() listFromLine = line.split('\t') returnMat[index, :] = listFromLine[0:3] # 样本每一行的前三列表示特征 classLabelVector.append(int(listFromLine[-1])) # 样本每一行的最后一列表示分类标签 index += 1 return returnMat, classLabelVector
代码贴在上面了,很简单的解析文件,详细介绍我都放在注释里了,相信这对你们没有什么难度。
而后,咱们在Python命令提示符下输入下面的命令:
>>> reload(kNN) >>> datingDataMat, datingLabels = kNN.file2matrix('datingSet.txt')
注意:由于书上使用的python2,因此在这里直接使用reload便可。
可是对于 python版本 <= Python 3.3,则须要使用如下命令:
import imp imp.reload(kNN)对于python版本 >= Python 3.4,则须要使用如下命令:
import importlib importlib.reload(sys)
好了,在输入命令的时候,还有下面两点须要注意:
在不报错的状况下,咱们看一下咱们生成的数据:
>>> datingDataMat array([[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, ......]
咱们如今已经从文本文件中导入了数据,并将其转换成了咱们须要的格式。可是这样有一个问题,咱们没办法直观地看出数据的含义,因此咱们接下来就使用Python工具来图形化展现数据内容。
注意:在分析数据的时候,图形化是一个很是好用的方法。Python中的Matplotlib模块为咱们提供了这样的功能。
这里咱们使用Matplotlib来制做原始数据的散点图,我本人也是刚入手机器学习和Python语言,因此对于这个库的使用还不是很了解,后期我也会慢慢出一个Matplotlib的教程,最好可以以通俗的语言来为你们讲明白。
(给本身立的flag,哭着也要让它站稳)
好了,咱们继续在上面的Python命令行环境中,输入如下命令:
>>> import matplotlib >>> import matplotlib >>> import matplotlib.pyplot as plt >>> fig = plt.figure() >>> ax = fig.add_subplot(111) >>> ax.scatter(datingDataMat[:,1], datingDataMat[:, 2]) <matplotlib.collections.PathCollection object at 0x00000111E23668D0> >>> plt.show()
输出效果以下图所示(我是使用Pycharm下的终端输出的,输入命令的时候有些粗心,你们千万不要向我这样):
这样看,生成的图片虽然能看,可是咱们不懂它是什么含义,这咱们就得从源码入手了。
datingDataMat[:, 1] 这个表明的是datingDataMat矩阵的第二列数据,表明特征‘玩视频游戏所耗时间百分比’ datingDataMat[:, 2] 这个表明的是datingDataMat矩阵的第三列数据,表明特征‘每周所消费的冰淇淋公升数’
而后,咱们看看ax.scatter()这个函数,其原型是:
scatter(x, y, s=None, c=None, marker=None, cmap=None, norm=None, vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, edgecolors=None, hold=None, data=None, **kwargs)
暂且不看后面的参数(后面的教程会详细介绍),咱们看到,一个x表明横轴,一个y表明纵轴。
好了,这样咱们就能够知道这个图的基本含义了,用书上的图表示以下:
因为咱们没有使用样本分类的特征值,咱们很难从上面的图中看出有用的信息来。通常来讲,咱们会采用色彩或其余的记号来标记不一样样本分类,以便更好地理解数据信息。
Matplotlib 库提供的scatter函数支持个性化标记散点图上的点。从新输入上面的代码,调用scatter函数 时使用下列参数:
>>> fig = plt.figure() >>> ax = fig.add_subplot(111) >>> ax.scatter(datingDataMat[:,1], datingDataMat[:, 2], 15.0 * array(datingLabels), 15.0 * array(datingLabels)) <matplotlib.collections.PathCollection object at 0x00000111E41CC9B0> >>> plt.show()
咱们就是利用了变量datingLabels存储的类标签属性,在散点图上绘制了色彩不等,尺寸不一样的点,输出效果以下:
这样,咱们就能够基本上看到数据点所属三个样本分类的区域轮廓。
为何要归一化数值?这个问题咱们慢慢来解决!
咱们先来看一组数据:
玩视频游戏所耗时间百分比 | 每一年得到的飞行常客里程数 | 每周消费的冰淇淋公升数 | 样本分类 | |
---|---|---|---|---|
1 | 0.8 | 400 | 0.5 | 1 |
2 | 12 | 134000 | 0.9 | 3 |
3 | 0 | 20000 | 1.1 | 2 |
4 | 67 | 32000 | 1 | 2 |
计算如下样本3和样本4之间的距离,以下:
$$
d = \sqrt{(0-67)^2 + (20000 - 32000)^2 + (1.1 - 1.0)^2}
$$
咱们很容易发现,上面方程中数字差值最大的属性对计算结果的影响最大。也就是说,每一年获取的飞行常客里程数对于计算结果的影响将远远大于其余两个特征——玩视频游戏所耗时间百分比和每周消费冰淇淋公升数——的影响。而产生这种现象的惟一缘由,仅仅是由于飞行常客里程数远大于其余特征值。
但卡特琳娜认为这三种特征是同等重要的,所以做为三个等权重的特征之一,飞行常客里程数并不该该如此严重地影响到计算结果。
因此,在处理这种不一样取值范围的特征值时,咱们一般就须要采用归一化,如将取值范围处理为0到1或者-1到1之间。下面的公式能够将任意取值范围的特征值转化为0-到1区间的值:
$$
newValue = (oldValue - min) / (max - min)
$$
其中min和max分别是数据集中当前特征的最小值和最大值。虽然改变数值取值范围增长了分类器的复杂度,但为了获得准确结果,咱们必须这样作。
好了,理论讲了一大堆,咱们仍是来写代码。继续在kNN.py中编写代码,建立一个函数autoNorm()。
def autoNum(dataSet): minVals = dataSet.min(0) # 存储每列的最小值,参数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
上面的代码,有一点须要咱们注意:特征值矩阵有1000×3个值,而minVals和ranges的值都为1×3。为了解决这个问题,咱们使用NumPy库中tile()函数将变量内容复制成输入矩阵一样大小的矩阵,注意这是具体特征值相除 ,而 对于某些数值处理软件包,/可能意味着矩阵除法。
好了,写完以后,咱们继续在Pyhon命令提示符下,从新载入kNN.py模块,执行autoNorm函数:
>>> reload(kNN) >>> 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.1273000e+04, 2.0919349e+01, 1.6943610e+00]) >>> minVals array([0. , 0. , 0.001156])
这里咱们也能够只返回normMat矩阵,可是下一节咱们须要将取值范围和最小值归一化测试数据。
老板:小韩啊,写的怎么样了啊?
我:数据可视化,数据处理以及数据归一化我都作完了,接下来,把数据丢到昨天写的分类器就能看到效果了!!
老板:不错不错!继续努力啊!年终奖到时候少不了你的!
我:(美滋滋)没问题,你就放100个心吧。
好了,总算是处理完数据了。接下来咱们就能够去看一下咱们的分类器效果怎么样了,想一想就很开心呢!,若是分类器的正确率知足要求,卡特琳娜就可使用这个软件来处理约会网站提供的约会名单了!!!
机器学习算法一个很重要的工做就是评估算法的正确率,一般咱们只提供已有数据的90%做为训练样原本训练分类器,而使用其他的10%数据去测试分类器,检测分类器的正确率。须要注意的是,10%的测试数据应该是随机选择的,因为卡特琳娜提供的数据并无按照特定目的来排序,因此咱们能够随意选择10%数据而不影响其随机性。
话很少说,直接上代码,咱们仍是在kNN.py中继续编写:
def datingClassTest(): hoRatio = 0.10 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)))
其实,代码流程仍是很简单的。
写完以后,咱们直接在咱们的Python命令提示符下从在kNN.py模块,而后输入kNN.datingClassTest(),执行分类测试程序,咱们能够获得如下结果:
Yes Yes Yes!!! 效果仍是不错的,只有5%的错误率!!!固然,咱们也能够改变函数 datingClassTest内变量hoRatio和变量k的值,检测错误率是否随着变量值的变化而增长。不一样的参数,分类器的性能会有很大不一样的。
老板:哎呦,不错哦!只有5%的错误率
我:那是必须的!
老板:那你在改改,咱们就能够给卡特琳娜用了!!
我:没问题,您就等着给我加鸡腿吧!!
好了,程序员测试完了,准备发布吧!!!
这里咱们就使用这个简答的分类器来帮助卡特琳娜对网站给出的候选人进行分类!!!
def classifyPerson(): 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 year?')) 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 will probably like this person: ', resultList[classifierResult - 1])
代码,我也就很少解释了,大部分咱们都见过,惟一要注意的就是raw_input(),这是Python 2的函数,Python3就直接使用input就行。
咱们直接看结果:
总算是写完了,虽然都是黑乎乎的控制台程序,可是算法的核心及应用方法咱们都会了,前端的包装就不是咱们的锅了!!!hiahiahia!!!
最后,仍是熟悉的配方!
欢迎你们关注个人公众号,有什么问题也能够给我留言哦!