欢迎关注我的个人博客blog.timene.com
这个算法真是不太好懂,看了好几遍终于有点入门的感觉,就赶紧记录下这点感觉。我从复习线性分类开始,然后复习点积的含义,再引出核方法。
线性分类是最容易理解的分类方法,两组数据A和B,分别求出A和B的平均值,比如M和N,当判断新数据X是属于A还是属于B呢,就看新数据X到M近还是N近,X属于距离近的那个。为了实现这个算法,我们需要计算出各分类的均值点:
def lineartrain(rows): averages={} counts={} for row in rows: # Get the class of this point cl=row.match averages.setdefault(cl,[0.0]*(len(row.data))) counts.setdefault(cl,0) # Add this point to the averages for i in range(len(row.data)): averages[cl][i]+=float(row.data[i]) # Keep track of how many points in each class counts[cl]+=1 # Divide sums by counts to get the averages for cl,avg in averages.items(): for i in range(len(avg)): avg[i]/=counts[cl] return averages下来就可以给分类了,计算要分类的坐标点到各个分类的均值点的距离,然后从中选择距离最近者。
这里我们采用另一种方法,使用向量和点积。向量具有大小和方向,我们通常将其表示成平面的一个箭头。所谓点积,针对两个向量,将第一个向量中的每个值雨第二个向量中的对应值相乘,然后再将所得的每个乘积相加,最后得到一个总的结果。
def dotproduct(v1,v2): return sum([v1[i]*v2[i] for i in range(len(v1))])
M0和M1分别是分类0和分类1的均值点,C为M0和M1的中心点,还有两个点即将分类的点X1和点X2。很明显X1离M0近一点,应该划归为分类0,X2离M1近一些,应该划归为分类1。同时也注意到,向量X1--->C的向量M0--->M1的夹角小于90度,因此X1--->C和M0--->M1的点积结果为正数;向量X2--->C的向量M0--->M1的夹角大于90度,因此X2--->C和M0--->M1的点积结果为负数。由此,只需通过观察点积结果的正负,就可以判断出新坐标的类属。
点C为M0和M1的均值点,亦即(M0+M1)/2,所以寻找分类的公式如下:
class=sign((X-(M0+M1)/2) . (M0-M1))
class=sign(X.M0 - X.M1 + (M1.M1 - M0.M0)/2)我们可以利用上面的公式来确定分类了:
def dpclassify(point,avgs): 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
可是我们又发现,如果对每个点的x和y求平方,会是什么样呢?这样所有X都偏移到了图上的角落处,所有的O位于角落以外的区域,这样我们就可以用线性分类器来处理了。
上面的两站图说明:通过对坐标点的变换,构造出一个只用一条直线进行划分新数据是有可能的。有时我们需要升级维度,比如将一个x和y坐标的数据集,变换成一个由a,b,c三个坐标构成的新数据集,其中a=x*x,b=x*y,c=y*y。
尽管我们可以把数据依照上面的方式变换到新的坐标系中,但通常我们不会这么去做,下面来聊聊核技法。核技法的思路是用一个新的函数取代上面的点积函数,而不是将数据变换奥新坐标系中。一种备受推崇的方法被成为径向基函数,这个函数于点积类似,接受两个向量作为输入参数,并返回一个标量,与点积不同的是,径向基函数是非线性的,因而它能够将数据映射到更为复杂的空间中。
def rbf(v1,v2,gamma=10): dv=[v1[i]-v2[i] for i in range(len(v1))] l=veclength(dv) return math.e**(-gamma*l)现在我们需要一个新函数,来计算坐标点在变换后的空间中与均值点的距离。遗憾的是,目前的均值是在原始空间中计算得到的,因为此处无法直接使用他们。所幸的是,先对一组向量求均值,然后再计算均值与向量A的点积结果,与先对向量A与该组向量中的每一个向量求点积,然后再计算均值,在效果上是等价的。因此,我们不再尝试对分类的两个坐标点求点积,也不在计算某个分类的均值点,取而代之的是,计算出某个坐标点与分类中其余每个坐标点之间的点积或径向基函数的结果,然后在对他们求均值。 再来看一遍这个函数 class=sign(X.M0 - X.M1 + (M1.M1 - M0.M0)/2)。 我们不直接去求M0和M1了,但心里必须清楚M0,M1是中心点,是径向基函数结果的平均值。
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)/2