这一章讲高阶分类,自然我们还学过其他一些分类器,无论是高阶还是低阶的,它们是:
那我们怎么知道哪两个人适合成为情侣呢?也许我们用人的思维来看就我们想把不吸烟的男的和女的介绍到一起。但是我们在利用机器学习的原理来判断两人是否适合成为情侣的时候,使用了训练集来训练我们的算法,也就说,这是一个监督类算法。书中为我们提供了训练集,我们可以把这个训练集看成是历史悠久的婚姻介绍所过往的记录。书中用csv格式为我们提高了500条数据,文件名:matchmaker.csv,我们来看其中一条:
39 yes no skiing:knitting:dancing220 W 42nd St New York NY43noyessoccer:reading:scrabble824 3rd Ave New York NY0
请注意,这只是一行。也许排版问题,过长会导致换行,但是这在matchmaker.csv文件中是一行的。数据我们有了,我们做的就是利用这个数据集,使用合适的算法,为新来的用户找出和他配对的对象。
class matchrow:
def __init__(self,row,allnum=False):
if allnum:
self.data=[float(row[i]) for i in range(len(row)-1)]
else:
self.data=row[0:len(row)-1]
self.match=int(row[len(row)-1])#match表示是否配对成功的1或者0
#allnum参数表示是只加载年龄还是加载全部数据
def loadmatch(f,allnum=False):
rows=[]
for line in file(f):
rows.append(matchrow(line.split(','),allnum))
return rows
而且这样导致我打印的时候打印的这个类的一个对象的地址,不是实际拿到了这个年龄之类的数值。
from pylab import *
def plotagematches(rows):
#读出配对成功的x坐标和y坐标
xdm,ydm=[r.data[0] for r in rows if r.match==1],[r.data[1] for r in rows if r.match==1]
#读出不配对成功的x坐标和y坐标
xdn,ydn=[r.data[0] for r in rows if r.match==0],[r.data[1] for r in rows if r.match==0]
#画绿点
plot(xdm,ydm,'go')
#画红点
plot(xdn,ydn,'ro')
show()
执行代码:
a=loadmatch('agesonly.csv')
plotagematches(a)
执行结果,如下图所示:
很显然,结果是糟糕的。因为,成不成对关键是看差距,不能简单的认为男的大于多少,女的小于多少就容易成对。如果从图像的角度来看,就是这样的:
决策树做的分类就在图中画了一条垂直线或者是水平线,但是最后水平线的上线或者垂直线的左右,都混杂了两种分类。所以,决策是不成功的。
所以,有两点非常重要:虽然简单,但是是基础。
def lineartrain(rows):
averages={}
counts={}
for row in rows:
#得到该坐标点所属的分类
c1=row.match
#下面两句应该只有第一次出现的时候才会有用把
averages.setdefault(c1,[0.0]*(len(row.data)))
counts.setdefault(c1,0)
#将该坐标点加入averages中
for i in range(len(row.data)):
averages[c1][i]+=float(row.data[i])
#记录该分类有多少个坐标点
counts[c1]+=1
#将总和除以计数值以求得平均值
for c1,avg in averages.items():
for i in range(len(avg)):
avg[i]/=counts[c1]
return averages
执行代码:
agesonly=loadmatch('agesonly.csv')
print lineartrain(agesonly)
结果:
>>>
{0: [26.914529914529915, 35.888888888888886], 1: [35.48041775456919, 33.01566579634465]}
>>>
我没有把上面的点绘制到图上去,所以借用了书中图,如下:
其中X点就计算出来的均指点,而且还有一条划分数据的直线,处于两个X点的中间位置。因此,所有在直接左侧的坐标点都表示不想匹配,而右侧的坐标点更接近于相匹配。
#两个向量的点积
def dotproduct(v1,v2):
return sum([v1[i]*v2[i] for i in range(len(v1))])
点积可以,利用两个向量的长度乘积,再乘以两者夹角的余弦求得。注意,夹角大于90度,那么夹角余弦值为负,此时点积结果也为负值。
举例说明,分类点M0(相匹配)和M1(不想匹配),新点X1和X2,C点是M0->M1的中点。
于是可以看出,X1->C与M0->M1的角度为45度,所以点积为正,所以属于M0。X2->C与M0->M1的角度为105度,所以点积为负,所以属于M1。注意C是M0和M1的均值点,说实话,这一部分,我被绕晕了:
公式相乘:
def dpclassify(point,avgs):
#这里之所以avgs[1]是代表分类点0的,因为在去读agesonly的数据时,分类1先被读进去来了。
b=(dotproduct(avgs[1],avgs[1])-dotproduct(avgs[0],avgs[0]))/2
y=dotproduct(point,avgs[0])-dotproduct(point,avgs[1])+b
if y>0:return 0
else:return 1
问题先留在这里吧,反正执行起来是没有问题的。执行代码:
agesonly=loadmatch('agesonly.csv')
avgs=lineartrain(agesonly)
print "两位是30岁",dpclassify([30,30],avgs)
print "男25岁和女40岁",dpclassify([25,40],avgs)
结果:
>>>
两位是30岁 1
25岁和40岁 0
>>>
作为一个线性分类器,它只能找出一条分界线。如果一条直线分不好的话,就不会成功,至少分类不精准。比如说,如果判断48岁和20岁,该算法就会给出匹配的结果,但是这是错误的,那么我们使用核方法来对此进行改进。
def rbf(v1,v2,gamma=20):
dv=[int(v1[i])-int(v2[i])for i in range(len(v1))]#v1[i]需要被转换为int型,但是书中没提。
l=veclength(dv)
return math.e**(-gamma*l)#**代表幂这个地方是小写L,不是1
#书中未提到,但是必须用的函数
def veclength(v):
return sum([p**2 for p in v])
#利用径向基函数判断新点到分类点的距离
#分类点也是在本函数中计算出来的。
#point为新点
#rows为训练数据集
def nlclassify(point,rows,offset,gamma=10):
sum0=0.0
sum1=0.0
count0=0
count1=0
for row in rows:
if row.match==0:
sum0+=rbf(point,row.data,gamma)
count0+=1
else:
sum1+=rbf(point,row.data,gamma)
count1+=1
y=(1.0/count0)*sum0-(1.0/count1)*sum1+offset
if y>0: return 0#和书上不一样,但是和书提供的源代码一直
else: return 1
#偏移量的计算可以先于计算好,算好之后直接传入,不必每次转换空间都换一下
def getoffset(rows,gamma=10):
l0=[]
l1=[]
for row in rows:
if row.match==0:l0.append(row.data)
else:l1.append(row.data)
sum0=sum(sum([rbf(v1,v2,gamma) for v1 in l0])for v2 in l0)
sum1=sum(sum([rbf(v1,v2,gamma) for v1 in l1])for v2 in l1)
return (1.0/(len(l1)**2))*sum1-(1.0/(len(l0)**2))*sum0
agesonly=loadmatch('agesonly.csv')
offset=getoffset(agesonly)
print "[30,30]",nlclassify([30,30],agesonly,offset)
print "[30,25]",nlclassify([30,25],agesonly,offset)
print "[25,40]",nlclassify([25,40],agesonly,offset)
print "[48,20]",nlclassify([48,20],agesonly,offset)
>>>
[30,30] 1
[30,25] 1
[25,40] 0
[48,20] 0
>>>
我们得到了正确的结果,最关键的就是最后一组数据,在核技法的方法下,会判断其不会配对。而在基本的线性分类里,会判断其成功配对。
from svm import *
from svmutil import *
prob = svm_problem([1,-1],[[1,0,1],[-1,0,-1]])#两个参数,第一个参数是分类的类型,第二个参数是输入数据,其中1对应[1,0,1]
param = svm_parameter()#与书中不同,这是新版本的改进,用下面的方法来指定使用的核方法
param.kernel_type = LINEAR#跟多内容参见博客
param.C = 10
model = svm_train(prob, param)#训练
svm_predict([1],[[1,1,1]],model)#第一个参数我是自己预测的,第二个参数是需要判断的数据,第三个就是训练模型
结果
>>>
Accuracy = 100% (1/1) (classification)
>>>
# -*- coding: cp936 -*-
class matchrow:
def __init__(self,row,allnum=False):
if allnum:
self.data=[float(row[i]) for i in range(len(row)-1)]
else:
self.data=row[0:len(row)-1]
self.match=int(row[len(row)-1])#match表示是否配对成功的1或者0
#allnum参数表示是只加载年龄还是加载全部数据
def loadmatch(f,allnum=False):
rows=[]
for line in file(f):
rows.append(matchrow(line.split(','),allnum))
return rows
from pylab import *
def plotagematches(rows):
#读出配对成功的x坐标和y坐标
xdm,ydm=[r.data[0] for r in rows if r.match==1],[r.data[1] for r in rows if r.match==1]
#读出不配对成功的x坐标和y坐标
xdn,ydn=[r.data[0] for r in rows if r.match==0],[r.data[1] for r in rows if r.match==0]
#画绿点
plot(xdm,ydm,'go')
#画红点
plot(xdn,ydn,'ro')
show()
def lineartrain(rows):
averages={}
counts={}
for row in rows:
#得到该坐标点所属的分类
c1=row.match
#下面两句应该只有第一次出现的时候才会有用把
averages.setdefault(c1,[0.0]*(len(row.data)))
counts.setdefault(c1,0)
#将该坐标点加入averages中
for i in range(len(row.data)):
averages[c1][i]+=float(row.data[i])
#记录该分类有多少个坐标点
counts[c1]+=1
#将总和除以计数值以求得平均值
for c1,avg in averages.items():
for i in range(len(avg)):
avg[i]/=counts[c1]
return averages
#两个向量的和点积
def dotproduct(v1,v2):
return sum([v1[i]*v2[i] for i in range(len(v1))])
def dpclassify(point,avgs):
#这里之所以avgs[1]是代表分类点0的,因为在去读agesonly的数据时,分类1先被读进去来了。
b=(dotproduct(avgs[1],avgs[1])-dotproduct(avgs[0],avgs[0]))/2
y=dotproduct(point,avgs[0])-dotproduct(point,avgs[1])+b
if y>0:return 0
else:return 1
def rbf(v1,v2,gamma=20):
dv=[int(v1[i])-int(v2[i])for i in range(len(v1))]#v1[i]需要被转换为int型,但是书中没提。
l=veclength(dv)
return math.e**(-gamma*l)#**代表幂这个地方是小写L,不是1
#书中未提到,但是必须用的函数
def veclength(v):
return sum([p**2 for p in v])
#利用径向基函数判断新点到分类点的距离
#分类点也是在本函数中计算出来的。
#point为新点
#rows为训练数据集
def nlclassify(point,rows,offset,gamma=10):
sum0=0.0
sum1=0.0
count0=0
count1=0
for row in rows:
if row.match==0:
sum0+=rbf(point,row.data,gamma)
count0+=1
else:
sum1+=rbf(point,row.data,gamma)
count1+=1
y=(1.0/count0)*sum0-(1.0/count1)*sum1+offset
if y>0: return 0#和书上不一样,但是和书提供的源代码一直
else: return 1
#偏移量的计算可以先于计算好,算好之后直接传入,不必每次转换空间都换一下
def getoffset(rows,gamma=10):
l0=[]
l1=[]
for row in rows:
if row.match==0:l0.append(row.data)
else:l1.append(row.data)
sum0=sum(sum([rbf(v1,v2,gamma) for v1 in l0])for v2 in l0)
sum1=sum(sum([rbf(v1,v2,gamma) for v1 in l1])for v2 in l1)
return (1.0/(len(l1)**2))*sum1-(1.0/(len(l0)**2))*sum0
#agesonly=loadmatch('agesonly.csv')
#offset=getoffset(agesonly)
#print "[30,30]",nlclassify([30,30],agesonly,offset)
#print "[30,25]",nlclassify([30,25],agesonly,offset)
#print "[25,40]",nlclassify([25,40],agesonly,offset)
#print "[48,20]",nlclassify([48,20],agesonly,offset)
from svm import *
from svmutil import *
prob = svm_problem([1,-1],[[1,0,1],[-1,0,-1]])#两个参数,第一个参数是分类的类型,第二个参数是输入数据,其中1对应[1,0,1]
param = svm_parameter()#与书中不同,这是新版本的改进,用下面的方法来指定使用的核方法
param.kernel_type = LINEAR
param.C = 10
model = svm_train(prob, param)#训练
svm_predict([1],[[1,1,1]],model)#第一个参数我是自己预测的,第二个参数是需要判断的数据,第三个就是训练模型
一份代码,两份数据已经上传至网盘: