这是记录自学的过程,目前的理论基础就是:大学高等数学+线性代数+概率论。编程基础:C/C++,python
在观看机器学习实战这本书,慢慢介入。相信有读过以上三门课的人完全可以开始自学机器学习了,当然我上面这三门课学的一般,所以你只知道有这么一个公式或名词,不懂可以百度之深究之。在写这篇文章的时候作者机器学习还没学完,故文章中的错误还请不吝指出。再次声明,系列文章只是分享学习过程,学习点滴,不能保证文章的技术含量。后续再技术的不断完善中,我会重新再捋一遍这个学习过程,纠正其中错误。
目前的学习方法如下:
理论:斯坦福Andrew NG machine learning,系列课程。
代码实战:引用书籍 机器学习实战(python)。
真枪实战: 研究一个案例,建模选择合适的机器学习算法,分析数据。
演变:基于C/C++ 在linux下实现部分机器学习过程。将在学习中期阶段去实现。
一、K近邻算法的概念
K近邻算法(kNN)属于一种分类的算法,与逻辑回归属于同一种。它的工作原理是:存在一组样本数据,它们的每一个样本的组成由若干个特征和一个既定的label与之对应,label也就是标明了这个样本点属于哪个分类,如果是简单的二元分类,那么label可以假设为1或0,以此类推。如果输入新的数据,而算法的目的就是输出新的数据的标签。这个新的数据将会和样本集合中的每个数据进行对比(求两个样本点之间的距离),对比的结果从小排到大,取前k个对比结果,当中出现次数最多的分类就是该新数据的分类。
近邻算法的优缺点:
1.优点:
精度高,对异常值不敏感,无数据输入的假定
2.缺点:
计算复杂度高,空间复杂度高。
3.使用数据范围:
数值型和标称型。
二、k近邻算法简单例子
伪代码:
1.计算样本集合中的已知点与当前点之间的距离
2.按照距离递增的顺序排序
3.选择排序中与当前点距离最近的前k个样本数据
4.计算出每个分类的样本个数并计算频率
5.返回k个点中出现频率最高的点的分类类别作为当前点的预测分类。
下面看一下这个简单的例子说明是如何求两点之间的欧氏距离的:
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0]
diffMat = tile(inX, (dataSetSize,1)) - dataSet
sqDiffMat = diffMat**2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances**0.5
sortedDistIndicies = distances.argsort()
classCount={}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
==深入分析:==
1.dataSetSize = dataSet.shape[0].计算出dataSet 矩阵的行数。
2.diffMat = tile(inX, (dataSetSize,1)) - dataSet 这边主要是将输入数据分别与样本进行相减,这边pyth tile的用法我用自己的理解总结了一下:
上网查了下tile用法的教程,发现看的有点吃力,于是自己亲自研究了一下然后发现可以这理解,与大家分享一下,有什么错误的地方请大家指出:
1.tile的用法总结:
定义:tile(A,R),A和R都可以是数组或者普通单个数据。结果是将A重复R,具体有以下几种机制:
假设:
tile([a1,a2,…,an],[r1,r2,…,rn])=B
得到的结果B的一些属性是由R来决定的,B的第一维数就是r1,第二维数就是r2,…..,B的每一维由重复A数组rn遍的结果组成。当R的长度为1时(类似(1,2)长度2,(1,2,3)长度3,(3)长度为1),此时R控制维数。
例子1.
tile([1,2],2)=
array([1,2],
[1,2]);
tile([1,2],4)=
array([1,2],
[1,2],
[1,2],
[1,2]);
tile([1,2],(1,2))= //1决定结果只是一维,2决定重复A(1,2)两遍
array([[1,2,1,2]])
tile([1,2],(3,3))= //3决定结果是三维,后面的三决定将重复(1,2)三遍
array([[1,2,1,2,1,2],
[1,2,1,2,1,2],
[1,2,1,2,1,2]])
tile([1,2],(2,2,2)) //第一个2决定结果将是一个2维,第二个2决定2为中的每一维都是一个2维,最后一个2决定最里面的维度的元素将是重复两遍A(1,2)的结果,层层嵌套。
结果:
array([[[1, 2, 1, 2],
[1, 2, 1, 2]], //这里是维度分界线,注意中括号个数
[[1, 2, 1, 2],
[1, 2, 1, 2]]])
以此类推。
3.sqDiffMat = diffMat**2 //把刚才得到的结果求平方
4.sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances**0.5 //这两句的意思是将求平方后的结果相加,然后结果开方
5.sortedDistIndicies = distances.argsort() //把求到的输入数据和每个点之间的距离进行递增排序,argsort返回的是每个数据的index。
6.for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
//总体的作用选取距离最小的K个点。for循环中的第一句:sortedDistIndicies[i]记录着一排index比如(2,3,0,1)
//2代表dataSet中的index为2的数据距离输入数据距离最短,以此类推,1代表最长。由于dataSet和labels是对应
//的,所以当i从0>>k时,会找到最小的那几个数据所对应的lable值。第二行就进行对这些label进行分类计数,并
//保存在classCount中
7.sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
//以上是对一个有多个类型的数据进行排序的函数,总之作用就是把刚才得到的保存有距离最短的k个数据中每一个label出现
//个数的数组进行从大到小排序,最后结果被保存在sortedClassCount中,第一个元素就是前K个数据中频率最高的label sortedClassCount[0][0] ,关于sorted的用法详见附录1.
三、k近邻算法的实际应用-约会网站的预测
背景我就不细细描述了,总之目的就是输入一个数据,这个数据包含三个特征:1.每年获得的飞行常客里程数2.玩视频游戏所耗时间百分比3.每周消费的冰淇淋公升数。通过这三个特征对数据进行分类,当然原理还是计算这个数据和其他数据的距离,不同的是这次是三维空间中的距离了。
1.第一步:解析数据。
我们先看下数据的特点:
第一列是飞行里程数,第二列是游戏时间所占百分比,第三列是吃冰淇淋的公升数。第四列是lable值,有三种:不喜欢,魅力一般,极具魅力。书中提供了解析数据的代码,目的就是把这些数据规律性的输入到一个数组中,方便后面处理,具体代码:
def file2matrix(filename):
fr = open(filename)
numberOfLines = len(fr.readlines()) #get the number of lines in the file
returnMat = zeros((numberOfLines,3)) #prepare matrix to return
classLabelVector = [] #prepare labels return
fr = open(filename)
index = 0
for line in fr.readlines():
line = line.strip()
listFromLine = line.split(‘\t‘)
returnMat[index,:] = listFromLine[0:3]
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat,classLabelVector
上面代码运行过程如下:
输入:datingDataMat,dataingLabels = kNN.file2matrix(‘datingTestSet2.txt’) 记住这边参数是datingTestSet2.txt,书本那种会报错误:
==ValueError: invalid literal for int() with base 10: ‘largeDoses’ ==
然后在看看换算的结果:
datingDataMat:
label的结果:
这样子数据就从文件导成我们需要的格式了,后面就开始用算法来处理这些数据。
2.第二步:归一化数值:
如上图数据所示第一列数据与后面两列差了10的4次方级别,这会导致在求点和点的距离的时候,加减运算时,第二第三列的数据影响效果大大减小,甚至不起作用。所以这边要把所有数据之按照一定的计算方式归一化到0-1之间,这样数据之间的加减运算才会有意义。
下面的公式可以将数据转化到0-1之间:
newValue = (oldValue - minValue)/(maxValue - minValue)
其中minValue和maxValue分别是数据中的最小值和最大值。
用python代码实现如下:
def autoNorm(dataSet):
minVals = dataSet.min(0) //获得dataSet中每列的最小值保存到minVals
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = zeros(shape(dataSet))
m = dataSet.shape[0]
normDataSet = dataSet - tile(minVals, (m,1)) //oldValue-minValue
normDataSet = normDataSet/tile(ranges, (m,1)) #element wise divide
return normDataSet, ranges, minVals
运行结果如下:
3.第三步:测试算法
直接撸代码:
def datingClassTest():
hoRatio = 0.10 #hold out 10%
datingDataMat,datingLabels = file2matrix(‘datingTestSet2.txt‘) #load data setfrom file
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))
前面两句主要是从文件加载数据然后再进行归一化转换。m = normMat.shape[0] 算出来的是数据的总个数,后面取出的测试数据的个数就是由 numTestVecs = m*hoRatio ,这边hoRatio为0.1也就是说测试数据取10%。然后将测试数据和已有的数据还有label值分别输入分类器classfy0中,这个函数前面已经分析过。这边输入的第一个参数为待测试的数据:normMat[i,:],这个表达式的意思是取normMat的第i行数据,直到numTestVecs比如:
normMat[0,:]array([ 0.44832535, 0.39805139, 0.56233353])
normMat[1,:]
array([ 0.15873259, 0.34195467, 0.98724416])
第二个参数:datingLabels[numTestVecs:m],表示一直数据从第numTestVecs行开始取知道结尾,前面的都是待测数据。
下面是运行的结果:
可以看到最后的错误率是6.4%,这是一个不错的结果。在此,我们可以通改变hoRotio和变量k的值,来观察错误率的变化情况。
上述只是测试算法,数据直接从现有的数据中取出来的,下面构建完整的算法系统:
下面是完整代码:
def classifyPerson():
resultList = [‘not at all‘,‘in small doses‘,‘in large doess‘]
percentTats = float(raw_input("percentage of time spent playing video games?"))
ffMiles = float(raw_input("frequent flier miles earned per year?"))
iceCream = float(raw_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]
第一行是标签数组化,有三个类别。
2-4行 分别要求输入玩游戏占用时间比、飞行里程数、消费冰淇淋公升数。
接下来两行就是将数据从文件中转化出来并归一化保存的过程。
最后就是输入分类器进行分类,返回分类结果。
下面是运行结果:
附录1:
sorted
sorted函数
Python内置的排序函数sorted可以对list或者iterator进行排序,官网文档见:http://docs.python.org/2/library/functions.html?highlight=sorted#sorted,该函数原型为:
sorted(iterable[items[], cmp, key, reverse)
参数解释:
(1)iterable指定要排序的list或者iterable,不用多说;
(2)cmp为函数,指定排序时进行比较的函数,可以指定一个函数或者lambda函数,如:
students为类对象的list,没个成员有三个域,用sorted进行比较时可以自己定cmp函数,例如这里要通过比较第三个数据成员来排序,代码可以这样写:
students = [(‘john‘, ‘A‘, 15), (‘jane‘, ‘B‘, 12), (‘dave‘, ‘B‘, 10)]
sorted(students, key=lambda student : student[2])
(3)key为函数,指定取待排序元素的哪一项进行排序,函数用上面的例子来说明,代码如下:
sorted(students, key=lambda student : student[2])
key指定的lambda函数功能是去元素student的第三个域(即:student[2]),因此sorted排序时,会以students所有元素的第三个域来进行排序。
有了上面的operator.itemgetter函数,也可以用该函数来实现,例如要通过student的第三个域排序,可以这么写:
sorted(students, key=operator.itemgetter(2))
sorted函数也可以进行多级排序,例如要根据第二个域和第三个域进行排序,可以这么写:
sorted(students, key=operator.itemgetter(1,2))
即先跟句第二个域排序,再根据第三个域排序。
(4)reverse参数就不用多说了,是一个bool变量,表示升序还是降序排列,默认为false(升序排列),定义为True时将按降序排列。