Web 在线数据建模

机器学习算法( 二、K - 近邻算法)

一、概述  

  k-近邻算法采用测量不同特征值之间的距离方法进行分类。

  工作原理:首先有一个样本数据集合(训练样本集),并且样本数据集合中每条数据都存在标签(分类),即我们知道样本数据中每一条数据与所属分类的对应关系,输入没有标签的数据之后,将新数据的每个特征与样本集的数据对应的特征进行比较(欧式距离运算),然后算出新数据与样本集中特征最相似(最近邻)的数据的分类标签,一般我们选择样本数据集中前k个最相似的数据,然后再从k个数据集中选出出现分类最多的分类作为新数据的分类。

二、优缺点

  优点:精度高、对异常值不敏感、无数据输入假定。

  缺点:计算度复杂、空间度复杂。

  适用范围:数值型和标称型

三、数学公式

   欧式距离:欧氏距离是最易于理解的一种距离计算方法,源自欧氏空间中两点间的距离公式。

  (1)二维平面上两点a(x1,y1)与b(x2,y2)间的欧氏距离:

     

  (2)三维空间两点a(x1,y1,z1)与b(x2,y2,z2)间的欧氏距离:

    

  (3)两个n维向量a(x11,x12,…,x1n)与 b(x21,x22,…,x2n)间的欧氏距离:

    

 三、算法实现

k-近邻算法的伪代码

对未知类型属性的数据集中的每个点依次执行以下操作:
(1) 计算已知类别数据集中的点与当前点之间的距离;
(2) 按照距离增序排序;
(3) 选取与当前点距离最近的k个点;
(4) 决定这k个点所属类别的出现频率;
(5) 返回前k个点出现频率最高的类别作为当前点的预测分类。

  1、构造数据

1 def createDataSet():
2     group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
3     labels = ['A','A','B','B']
4     return group, labels

  这里有4组数据,每组数据的列代表不同属性的特征值,向量labels包含了每个数据点的标签信息,也可以叫分类。这里有两类数据,A和B。

  2、实施算法

    tile:重复某个数组。比如tile(A,n),功能是将数组A重复n次,构成一个新的数组.

 1 >>> tile([1,2],(4))
 2 array([1, 2, 1, 2, 1, 2, 1, 2])
 3 >>> tile([1,2],(4,1))
 4 array([[1, 2],
 5        [1, 2],
 6        [1, 2],
 7        [1, 2]])
 8 >>> tile([1,2],(4,2))
 9 array([[1, 2, 1, 2],
10        [1, 2, 1, 2],
11        [1, 2, 1, 2],
12        [1, 2, 1, 2]])

 

  欧式距离算法实现:

 1 def classify0(inX, dataSet, labels, k):
 2     dataSetSize = dataSet.shape[0]
 3     diffMat = tile(inX, (dataSetSize,1)) - dataSet #新数据与样本数据每一行的值相减  [[x-x1,y-y1],[x-x2,y-y2],[x-x3,y-y3],.....]
 4     sqDiffMat = diffMat**2 #数组每一项进行平方[[(x-x1)^2,(y-y1)^2],........] 
 5     sqDistances = sqDiffMat.sum(axis=1)#数组每个特证求和[[(x-xi)^2+(y-yi)^2],......]
 6     distances = sqDistances**0.5 #数组每个值 开根号   ,,欧式距离公式 完成。。。。
 7     sortedDistIndicies = distances.argsort()  #argsort函数返回的是数组值从小到大的索引值   
 8     classCount={}  #以下是选取 距离最小的前k个值的索引,从k个中选取分类最多的一个作为新数据的分类        
 9     for i in range(k):# 统计前k个点所属的类别
10         voteIlabel = labels[sortedDistIndicies[i]]
11         classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
12     sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
13     return sortedClassCount[0][0]# 返回前k个点中频率最高的类别

   其中 inX:需要分类的新数据,dataSet:样本数据特征,labels:样本数据分类,k:选取前k个最近的距离

   测试算法:

1 >>> group,labels = kNN.createDataSet()
2 >>> group,labels
3 (array([[ 1. ,  1.1],
4        [ 1. ,  1. ],
5        [ 0. ,  0. ],
6        [ 0. ,  0.1]]), ['A', 'A', 'B', 'B'])
7 >>> kNN.classify0([0,0],group,labels,3)
8 'B'
9 >>> 

   测试结果:[0,0]属于分类B.

  3、如何测试分类器

 

四、 示例:使用k-近邻算法改进约会网站的配对效果  

  我的朋友海伦一直使用在线约会网站寻找适合自己的约会对象。尽管约会网站会推荐不同的人选,但她并不是喜欢每一个人。经过一番总结,她发现曾交往过三种类型的人:

  • 不喜欢的人
  • 魅力一般的人
  • 极具魅力的人

  海伦希望我们的分类软件可以更好地帮助她将匹配对象划分到确切的分类中。此外海伦还收集了一些约会网站未曾记录的数据信息,她认为这些数据更有助于匹配对象的归类。

  1、准备数据:从文本文件中解析数据

  数据存放在文本文件datingTestSet.txt中,每个样本数据占据一行,总共有1000行。

  海伦的样本主要包含以下3种特征:

  • 每年获得的飞行常客里程数
  • 玩视频游戏所耗时间百分比
  • 每周消费的冰淇淋公升数

  2、分析数据:使用Matplotlib创建散点图

  散点图使用datingDataMat矩阵的第一、第二列数据,分别表示特征值“每年获得的飞行常客里程数”和“玩视频游戏所耗时间百分比”。

 每年赢得的飞行常客里程数与玩视频游戏所占百分比的约会数据散点图

  3、准备数据:归一化数值

   不同特征值有不同的均值和取值范围,如果直接使用特征值计算距离,取值范围较大的特征将对距离计算的结果产生绝对得影响,而使较小的特征值几乎没有作用,近乎没有用到该属性。如两组特征:{0, 20000, 1.1}和{67, 32000, 0.1},计算距离的算式为:

显然第二个特征将对结果产生绝对得影响,第一个特征和第三个特征几乎不起作用。

然而,对于识别的过程,我们认为这不同特征是同等重要的,因此作为三个等权重的特征之一,飞行常客里程数并不应该如此严重地影响到计算结果。

在处理这种不同取值范围的特征值时,我们通常采用的方法是将数值归一化,如将取值范围处理为0到1或者1到1之间。下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:

newValue = (oldValue – min) / (max – min)

其中min和max分别是数据集中的最小特征值和最大特征值。

添加autoNorm()函数,用于将数字特征值归一化:

1 def autoNorm(dataSet):
2     minVals = dataSet.min(0)# 分别求各个特征的最小值
3     maxVals = dataSet.max(0)# 分别求各个特征的最大值
4     ranges = maxVals - minVals# 各个特征的取值范围
5     normDataSet = zeros(shape(dataSet))
6     m = dataSet.shape[0]
7     normDataSet = dataSet - tile(minVals, (m,1))    # oldValue - min
8     normDataSet = normDataSet/tile(ranges, (m,1))   #element wise divide  (oldValue-min)/(max-min) 数据归一化处理
9     return normDataSet, ranges, minVals

  对这个函数,要注意返回结果除了归一化好的数据,还包括用来归一化的范围值ranges和最小值minVals,这将用于对测试数据的归一化。

  注意,对测试数据集的归一化过程必须使用和训练数据集相同的参数(ranges和minVals),不能针对测试数据单独计算ranges和minVals,否则将造成同一组数据在训练数据集和测试数据集中的不一致。

  4、测试算法:作为完整程序验证分类器

  机器学习算法一个很重要的工作就是评估算法的正确率,通常我们只提供已有数据的90%作为训练样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率。需要注意的是,10%的测试数据应该是随机选择的。由于海伦提供的数据并没有按照特定目的来排序,所以我们可以随意选择10%数据而不影响其随机性。

  创建分类器针对约会网站的测试代码:利用样本集数据进行测试算法

 1 def datingClassTest():
 2     hoRatio = 0.50      #hold out 10%
 3     datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')       #load data setfrom file
 4     normMat, ranges, minVals = autoNorm(datingDataMat)
 5     m = normMat.shape[0]
 6     numTestVecs = int(m*hoRatio)
 7     errorCount = 0.0
 8     for i in range(numTestVecs):
 9         classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
10         print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i])
11         if (classifierResult != datingLabels[i]): errorCount += 1.0
12     print "the total error rate is: %f" % (errorCount/float(numTestVecs))
13     print errorCount

执行分类器测试程序:

 1 >>> kNN.datingClassTest()
 2 
 3 the classifier came back with: 2, the real answer is: 1
 4 
 5 the classifier came back with: 2, the real answer is: 2
 6 
 7 the classifier came back with: 1, the real answer is: 1
 8 
 9 the classifier came back with: 1, the real answer is: 1
10 
11 the classifier came back with: 2, the real answer is: 2
12 
13 .................................................
14 
15 the total error rate is: 0.064000
16 
17 32.0

  分类器处理约会数据集的错误率是6.4%,这是一个相当不错的结果。我们可以改变函数datingClassTest内变量hoRatio和变量k的值,检测错误率是否随着变量值的变化而增加。

这个例子表明我们可以正确地预测分类,错误率仅仅是2.4%。海伦完全可以输入未知对象的属性信息,由分类软件来帮助她判定某一对象的可交往程度:讨厌、一般喜欢、非常喜欢。

  5、使用算法:构建完整可用系统

  综合上述代码,我们可以构建完整的约会网站预测函数:对输入的数据需要 归一化处理

 1 def classifyPerson():
 2     resultList = ['not at all', 'in small doses', 'in large doses']
 3     percentTats = float(raw_input("Percentage of time spent playing video game?"))
 4     ffMiles = float(raw_input("Frequent flier miles earned per year?"))
 5     iceCream = float(raw_input("Liters of ice cream consumed per year?"))
 6     datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
 7     normMat, ranges, minVals = autoNorm(datingDataMat)
 8     inArr = array([ffMiles, percentTats, iceCream]) #新数据 需要归一化处理
 9     classifierResult = classify((inArr - minVals) / ranges, normMat, datingLabels, 3)
10     print "You will probably like this person: ", resultList[classifierResult - 1]

  目前为止,我们已经看到如何在数据上构建分类器。

 

完整代码:

  1 '''
  2     Created on Sep 16, 2010
  3     kNN: k Nearest Neighbors
  4     
  5     Input:      inX: vector to compare to existing dataset (1xN)
  6     dataSet: size m data set of known vectors (NxM)
  7     labels: data set labels (1xM vector)
  8     k: number of neighbors to use for comparison (should be an odd number)
  9     
 10     Output:     the most popular class label
 11     
 12     @author: pbharrin
 13     '''
 14 from numpy import *
 15 import operator
 16 from os import listdir
 17 import matplotlib
 18 import matplotlib.pyplot as plt
 19 def show(d,l):
 20     #d,l=kNN.file2matrix('datingTestSet2.txt')
 21     fig=plt.figure()
 22     ax=fig.add_subplot(111)
 23     ax.scatter(d[:,0],d[:,1],15*array(l),15*array(l))
 24     plt.show()
 25 def show2():
 26     datingDataMat,datingLabels=file2matrix('datingTestSet2.txt')
 27     fig = plt.figure()
 28     ax = fig.add_subplot(111)
 29     l=datingDataMat.shape[0]
 30     X1=[]
 31     Y1=[]
 32     X2=[]
 33     Y2=[]
 34     X3=[]
 35     Y3=[]
 36     for i in range(l):
 37         if datingLabels[i]==1:
 38             X1.append(datingDataMat[i,0]);Y1.append(datingDataMat[i,1])
 39         elif datingLabels[i]==2:
 40             X2.append(datingDataMat[i,0]);Y2.append(datingDataMat[i,1])
 41         else:
 42             X3.append(datingDataMat[i,0]);Y3.append(datingDataMat[i,1])
 43     type1=ax.scatter(X1,Y1,c='red')
 44     type2=ax.scatter(X2,Y2,c='green')
 45     type3=ax.scatter(X3,Y3,c='blue')
 46     #ax.axis([-2,25,-0.2,2.0])
 47     ax.legend([type1, type2, type3], ["Did Not Like", "Liked in Small Doses", "Liked in Large Doses"], loc=2)
 48     plt.xlabel('Percentage of Time Spent Playing Video Games')
 49     plt.ylabel('Liters of Ice Cream Consumed Per Week')
 50     plt.show()
 51 
 52 def classify0(inX, dataSet, labels, k):
 53     dataSetSize = dataSet.shape[0]
 54     diffMat = tile(inX, (dataSetSize,1)) - dataSet
 55     sqDiffMat = diffMat**2
 56     sqDistances = sqDiffMat.sum(axis=1)
 57     distances = sqDistances**0.5
 58     sortedDistIndicies = distances.argsort()
 59     classCount={}
 60     for i in range(k):
 61         voteIlabel = labels[sortedDistIndicies[i]]
 62         classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
 63     sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
 64     return sortedClassCount[0][0]
 65 
 66 def createDataSet():
 67     group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
 68     labels = ['A','A','B','B']
 69     return group, labels
 70 
 71 def file2matrix(filename):
 72     fr = open(filename)
 73     numberOfLines = len(fr.readlines())         #get the number of lines in the file
 74     returnMat = zeros((numberOfLines,3))        #prepare matrix to return
 75     classLabelVector = []                       #prepare labels return
 76     fr = open(filename)
 77     index = 0
 78     for line in fr.readlines():
 79         line = line.strip()
 80         listFromLine = line.split('\t')
 81         returnMat[index,:] = listFromLine[0:3]
 82         classLabelVector.append(int(listFromLine[-1]))
 83         index += 1
 84     return returnMat,classLabelVector
 85 
 86 def autoNorm(dataSet):
 87     minVals = dataSet.min(0)
 88     maxVals = dataSet.max(0)
 89     ranges = maxVals - minVals
 90     normDataSet = zeros(shape(dataSet))
 91     m = dataSet.shape[0]
 92     normDataSet = dataSet - tile(minVals, (m,1))
 93     normDataSet = normDataSet/tile(ranges, (m,1))   #element wise divide
 94     return normDataSet, ranges, minVals
 95 
 96 def datingClassTest():
 97     hoRatio = 0.50      #hold out 10%
 98     datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')       #load data setfrom file
 99     normMat, ranges, minVals = autoNorm(datingDataMat)
100     m = normMat.shape[0]
101     numTestVecs = int(m*hoRatio)
102     errorCount = 0.0
103     for i in range(numTestVecs):
104         classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
105         print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i])
106         if (classifierResult != datingLabels[i]): errorCount += 1.0
107     print "the total error rate is: %f" % (errorCount/float(numTestVecs))
108     print errorCount
109 
110 def img2vector(filename):
111     returnVect = zeros((1,1024))
112     fr = open(filename)
113     for i in range(32):
114         lineStr = fr.readline()
115         for j in range(32):
116             returnVect[0,32*i+j] = int(lineStr[j])
117     return returnVect
118 
119 def handwritingClassTest():
120     hwLabels = []
121     trainingFileList = listdir('trainingDigits')           #load the training set
122     m = len(trainingFileList)
123     trainingMat = zeros((m,1024))
124     for i in range(m):
125         fileNameStr = trainingFileList[i]
126         fileStr = fileNameStr.split('.')[0]     #take off .txt
127         classNumStr = int(fileStr.split('_')[0])
128         hwLabels.append(classNumStr)
129         trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)
130     testFileList = listdir('testDigits')        #iterate through the test set
131     errorCount = 0.0
132     mTest = len(testFileList)
133     for i in range(mTest):
134         fileNameStr = testFileList[i]
135         fileStr = fileNameStr.split('.')[0]     #take off .txt
136         classNumStr = int(fileStr.split('_')[0])
137         vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
138         classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
139         print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr)
140         if (classifierResult != classNumStr): errorCount += 1.0
141     print "\nthe total number of errors is: %d" % errorCount
142     print "\nthe total error rate is: %f" % (errorCount/float(mTest))
View Code

 

posted @ 2016-08-01 09:49  NetUML大数据搜索  阅读(1851)  评论(1编辑  收藏  举报
Web 在线数据建模