zoukankan      html  css  js  c++  java
  • 使用核技巧的线性分类器

    . 线性分类:核方法

    • 1.数据封装:记得以前读weka源码的时候,它将样本封装到一个叫Instance的对象里面,整个数据集叫Instances里面存放的是单个样本instance,封装的好处是方便后期对样本的处理,这里将每一个样本封装为一个对象包含datatarget,记做Data
    class Data:
            def __init__(self,row):
                self.data = map(float,row[:-1])
                self.target = int(row[-1])
    

    将整个数据集放入一个列表中

    rows = []
    rows.append(Data(line.split(","))
    
    • 2.基本的线性分类器:原理是寻找每个类别所有数据的平均值,得到一个代表该类别的中心点,有新数据要对其进行分类时,只需要通过判断距离哪个中心点位置近进行分类。
    def train(rows):
            average = {}#用来存放不同类别的中心点
            counts = {}
            for row in rows:
                c1 = row.target
                average.setdefault(c1,[0.0]*len(row.data))#定义一个长度与特征维度相等的list,
                counts.setdefault(c1,0)
                for i in range(len(row.data)):
                    average[c1][i] += float(row.data[i])
                counts[c1] += 1
                
            for c,avg in average.items():
                for i in range(len(avg)):
                    avg[i] /= counts[c]#average的值会修改
            return average
    

    思想很简单,将每一类别样本的值相加,再除以样本的个数。有一点值得注意,average是一个字典,key为类别,value为一个list,在修改value的时候,average的值也跟着变化

    • 3.距离的计算
      在对测试样本进行分类的时候需要计算样本与类别中心点的距离,可以使用欧氏距离,如图:就是计算x到中心点c1、c2哪个更近。不过我们也可以使用向量的夹角,因为向量是矢量,向量的乘积是有符号的,通过判断结果的 正、负来找出距离哪个中心点近。

      class = sign((X - (C1+C2)/2) * (C2 - C1)) ==>sign(xC1-XC2 + (C1C1 - C2C2)/2)
      通过计算向量夹角判断样本的类别
    • 4.特征的处理:针对每一特征维度,定制不同的特征处理方法,最后再形成新的数据集
      • 固定个数标称型特征,如Yes or No,可以化为1,0类型
      • 个数不固定的标称型特征,如足球,篮球,滑雪,看书等等,可以对特征进行按层级排列,例如篮球和足球都属于球类,球类都属于运动,在转化为数值特征的时候就不再是1了,例如:足球;0.8滑雪:0.6
      • 在处理位置特征时候,可以借助地图API计算距离
        最后的合并成新的数据集[f1(row[0]),f2(row[1]),f3(row[2])],其中f1、f2、f3是特征处理方法,row[0]、row[1]是特征项
    • 4.对特征进行缩放:(加黑了,说明很重要),在处理特征的时候,特征的尺度不一样,比如年龄的范围是0~100,薪资的范围为1000~100000,如果直接用作分类器时效果可能会很差,所以我们需要对其归一化,对原始数据进行线性变换,使结果映射到[0-1]之间,通过找出特征的最大值和最小值,将数据都缩放到同一尺度,公式如下:

      其中max为样本的最大值,min为样本的最小值,这个方法有一个缺陷当有新的数据加入时,可能导致max和min发生变化;具体的操作:
    def scala(rows):   
            #[(min,max),(),()]
            ranges = [(min([row.data[i] for row in rows]),max([row.data[i] for row in rows]))
                    for i in range(len(rows[0].data))]
            #(x - min) / (max - min)
            scalaFun = lambda d:[(d[i] - ranges[i][0]) / (ranges[i][1] - ranges[i][0]) for i in range(len(ranges))]
            newrows = [Data(scalaFun (row.data) + [row.target]) for row in rows]
            return newrows, scalaFun 
    

    返回两部分,一个是处理后的数据集,一个是scala函数,用于处理新样本。至于为什么写成匿名函数,这样就能够保存ranges

    • 5.核方法:设想,我们有一堆样本,将数据绘制在二维平面上,发现正负样本呈环状展示,这时候本文上面提到的方法就已经失效了,不能使用一条直线将样本分开,但如果我们将样本投影到一个3维的空间中,那么样本就将变得线性可分,如下图:

      这种方法叫做核技巧,初学者可能听到核方法就会想到SVM,是SVM引用了核方法,而不是它创造了核方法,下文就将使用核函数。核技巧是用一个新的函数替代原来的点积函数,借助某个映射函数将数据变换到更高维度的坐标空间,新函数返回新的内积结果。实际上我们不会去找这个映射函数,因为找到一个符合数据集的高纬函数是很困难的,常常我们使用被受人推崇的径向基函数(radial-basis function,rbf)
      rbf核
    def rbf(v1,v2,gamma=20):
            m = sum([(v1[i] - v2[i])**2 for i in range(len(v1))])
            return math.exp(-gamma * m)
    

    这时候我们将样本映射到新的空间中,我们需要一个新的函数,用以计算坐标点在变换后的空间中与均值点的距离,在新的样本空间中,无法计算均值点。所幸的是,先对一组向量求均值,然后计算均值与向量(a)的点积结果,与先对向量(a)与该组向量中的每一个向量求点积,然后再计算均值是完全等价的。因此

    for row in rows:
            if row.target == 0:
                sum0 += rbf(sample,row.data,gamma)#在计算内积的时候用核函数替代
                count0 += 1
            else:
                sum1 += rbf(sample,row.data,gamma)
                count1 += 1
        y = (1.0/count0) *sum0 - (1.0/count1) *sum1 + offset
    

    完整代码见这里
    总结:本节使用了线性分类器对数据进行二分类,对于不能线性分类的数据引入了核函数,认识了核函数的工作原理,有助于对SVM高级分类器的理解。

  • 相关阅读:
    面向对象六
    面向对象五
    面向对象四
    面向对象三
    面向对象二
    CentOS7下安装Redis4.0
    在亚马逊的EC2环境中创建swap
    centos7安装rabbitmq操作步骤
    在VUE下使用阿里图标
    Centos7-安装telnet服务
  • 原文地址:https://www.cnblogs.com/wxshi/p/6095998.html
Copyright © 2011-2022 走看看