这是KNN的一个新例子。
在一个约会网站里,每个约会对象有三个特征:
(1)每年获得的飞行常客里程数(额…这个用来判断你是不是成功人士?)
(2)玩视频游戏所耗时间百分比(额…这个用来判断你是不是肥宅?)
(3)每周消费的冰激凌公升数(额…这个是何用意我真不知道了…)
然后给贴标签。标签种类有三种:
(1)根本不喜欢
(2)有一点喜欢
(3)喜欢得不得了
——————————————————————————————————————
我们首先要从数据文件里面把训练集读入内存。
数据文件大概长这样:
第一列是飞行里程数的特征值,第二列是游戏百分比,第三列是冰淇淋公升数。最后一列是标签,1~3表示喜欢程度。
接下来是代码,file2matrix 函数将文件中的特征数据和标签读到矩阵和list里面
- def file2matrix(filename):
- fr = open(filename)
- arrayOLines = fr.readlines()
- numberOfLines = len(arrayOLines)
- returnMat = zeros((numberOfLines,3))
- classLabelVector = []
- index = 0
- 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
def file2matrix(filename):
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines)
returnMat = zeros((numberOfLines,3))
classLabelVector = []
index = 0
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
值得注意的是这个 zeros 函数,构造一个n×n的零矩阵。
另外第12行,偷懒的话可以这样写:
- returnMat[index] = listFromLine
returnMat[index] = listFromLine
——————————————————————————————————————
然后作者介绍了通过散点图来分析数据的方法,用的是metplotlib这个包。
- import matplotlib.pyplot as plt
- def matrix2plot(dataSet):
- fig = plt.figure()
- ax = fig.add_subplot(111)
- ax.scatter(dataSet[:, 0], dataSet[:, 1], 15 * array(datingLabels), array(datingLabels))
- plt.show()
import matplotlib.pyplot as plt
def matrix2plot(dataSet):
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(dataSet[:, 0], dataSet[:, 1], 15 * array(datingLabels), array(datingLabels))
plt.show()
figure()用来创建一个图的对象。
add_subplot(x,y,z) 是这么个意思:将画布划分为x行y列,图像画在从左到右从上到下的第z块。
scatter() 用来画出散点。这里它接收了4个参数:
(1)横轴数据。这里是dataSet[:, 0],也就是数据集的第1个特征(飞行里程数)
(2)纵轴数据。这里是dataSet[:, 1],也就是数据集的第2个特征(游戏百分比)
(3)每个散点的标度(scalar),从实际上看是散点的半径,或者说有多“粗”。这里是15 * array(datingLabels)。什么意思呢?每个散点对应一个标签datingLabel,要么1,要么2,要么3。把这个值乘以15,变成15,30,45,意思就是标签为1的散点的“粗度”是15,以此类推。其实就是为了在图上面好看出散点的标签。
(4)每个散点的颜色。这里是array(datingLabels)。意思是不同的散点,按照他的标签(1或2或3)给他分配一种颜色。目的也是为了在图上面好看出散点的标签。
效果如下:
横坐标是飞行里程数,纵坐标是游戏百分比。绿色点标签是1(根本不喜欢),黄色点标签是2(有一点不喜欢),紫色点标签是3( 喜欢得不得了)。
——————————————————————————————————————
我们现在有个问题。不是要算欧氏距离吗,那么比如(20000, 0, 0.1)和(32000, 67, 0.1)这两组数据,如果要算欧氏距离的话,那么显然第一个特征的贡献率是最大的,因为其他两个特征的数量级和他没法比啊。
这就需要做一个预处理,叫做“数值归一化”,把任意取值范围的特征值转化为0到1区间内的值。公式如下:
newValue = (oldVlue - min) / (max - min)
代码很容易理解:
- def autoNorm(dataSet):
- minVals = dataSet.min(0) # 每列的最小值
- maxVals = dataSet.max(0) # 每列的最大值
- ranges = maxVals - minVals #取值范围
- normDataSet = zeros(shape(dataSet)) # 按数据的行数、列数构造0
- m = dataSet.shape[0] # 数据的条数
- normDataSet = dataSet - tile(minVals, (m,1)) # tile:构造每一行都是minVals的数据
- normDataSet = normDataSet / tile(ranges, (m,1)) # tile:构造每一行都是ranges的数据
- return normDataSet, ranges, minVals
def autoNorm(dataSet):
minVals = dataSet.min(0) # 每列的最小值
maxVals = dataSet.max(0) # 每列的最大值
ranges = maxVals - minVals #取值范围
normDataSet = zeros(shape(dataSet)) # 按数据的行数、列数构造0
m = dataSet.shape[0] # 数据的条数
normDataSet = dataSet - tile(minVals, (m,1)) # tile:构造每一行都是minVals的数据
normDataSet = normDataSet / tile(ranges, (m,1)) # tile:构造每一行都是ranges的数据
return normDataSet, ranges, minVals
——————————————————————————————————————
现在来测试一下分类器的效果。大概过程是:
(1)从文件中读取数据,并归一化特征值;
(2)抽出一部分来用作分类器的训练样本,剩下的用于测试。
(3)开始训练+测试,并统计错误率
- def datingClassTest():
- hoRatio = 0.50
- 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))
def datingClassTest():
hoRatio = 0.50
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))
normMat 是特征值归一化后的矩阵。m是数据的条数。hoRatio是测试样本的比例,这里是50%,所以有一半的样本拿去训练,另一半做测试。numTestVecs 是测试样本数量。
第9行调用classify0(源码在上篇博客),逐条地将测试样本(前numTestVecs条数据)进行分类,然后验证准确与否。normMat[numTestVecs:m,:] 就是训练集(下标从numTestVecs到m-1的数据)。
测试结果:
错误率为5.0%。还…不错
——————————————————————————————————————
好。现在我们将这个分类器“产品化”,就是可以接受用户输入,然后帮忙分类与预测。
- def classifyPerson():
- resultList = [’压根不喜欢’, ‘有一点喜欢’, ‘喜欢得不得了’]
- ffMiles = float(raw_input(”飞行里程?”))
- percentTats = float(raw_input(”喜欢玩电子游戏的时间?”))
- iceCream = float(raw_input(”吃冰淇淋的量?”))
- datingDataMat, datingLabels = file2matrix(’datingTestSet2.txt’)
- normMat, ranges, minVals = autoNorm(datingDataMat)
- inArr = array([ffMiles, percentTats, iceCream])
- classifierResult = classify0((inArr - minVals)/ranges, normMat, datingLabels, 3)
- print “你喜欢这个人的程度很可能是:”, resultList[classifierResult - 1];
def classifyPerson():
resultList = ['压根不喜欢', '有一点喜欢', '喜欢得不得了']
ffMiles = float(raw_input("飞行里程?"))
percentTats = float(raw_input("喜欢玩电子游戏的时间?"))
iceCream = float(raw_input("吃冰淇淋的量?"))
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = array([ffMiles, percentTats, iceCream])
classifierResult = classify0((inArr - minVals)/ranges, normMat, datingLabels, 3)
print "你喜欢这个人的程度很可能是:", resultList[classifierResult - 1];
resultList枚举了标签的种类。inArr是待预测数据,在传入classify0前进行了归一化。
运行结果:
</div>