美文网首页
统计学习方法——修炼学习笔记2:感知机

统计学习方法——修炼学习笔记2:感知机

作者: Sam_L | 来源:发表于2020-03-25 09:36 被阅读0次

    一、感知机的定义

    感知机是1957年由Rosenblatt提出,是神经网络与支持向量机的基础,是二分类线性分类模型,输入为实例的特征,输出为实例类别,实例类别取+1和-1。感知机是属于判别模型,因为其求出分离超平面直接将输入实例划分为正例和负例。

    导入基于误分类的损失函数,利用梯度下降法对损失函数进行极小化,求得感知机模型

    二、感知机的数学表达式

    感知机的数学表达式可以由下列式子进行表达:

    f=sign(w⋅x+b)
    其中,sign(x) 是一个记号函数,表示
    当x>=0,sign(x)=+1 ;
    当x<0,sign(x)=−1 ;

    三、感知机的几何意义

    几何意义是将几何坐标系上的点通过分离超平面将其划分为两个类别:正例和负例。【对于二维坐标系来说】
    如果是多维坐标系,相当于是一个多维面了。比如,二维空间是一条线,三维空间是一个面等等。

    四、感知机的目标函数

    数据集线性可分性

    感知机的目标:在训练数据集线性可分的假设前提下,求得一个能够将训练集中的正负样本实例点完全正确分开的分离超平面。
    看到这,有人说,训练数据集线性可分啥意思啊,听不懂啊,其实就是针对

    所有正实例y=+1,w⋅x+b>0 ;
    所有负实例y=−1,w⋅x+b<0 ;

    一张图表示其实就是这个样子的:


    image.png

    这个数据集就是可分的,中间一条虚线即为超平面,将整个数据集分为虚线右上和左下部分。

    目标函数推导

    通常意义上来说,损失函数我们可以看误分类点的总数。但是这样来看,这样的损失函数不是参数w和b的连续可导函数,不易优化。
    因此,感知机选择另一种形式的损失函数:
    误分类点到超平面S的总距离。
    而平面上一点(x0,y0) 到超平面S 的距离distance0为:


    image.png

    其中, ||w||是w的L2范数。

    对于误分类点有两种情况:

    image.png

    五、感知机的优化方法

    目前,感知机的优化方法采用的是梯度下降的方法,也就是我们所熟知的随机梯度下降或者批量梯度下降。因为感知机有原始形式和对偶形式两种方式,故而分开来说。

    1、 原始形式

    image.png

    随机梯度下降

    image.png

    批量梯度下降

    image.png

    为什么用随机梯度下降而不用批量梯度下降
    这一点在李航大大的书上并没有提及,只是根据笔者自己理解来说明原因。
    主要是从时间复杂方向来考虑。
    假设我们有m个样本【准确来说,这里说m个误分类样本更为合适,但是对于我们来说,我们需要对所有样本进行判断,如果误分类,更新w和b。如果不是误分类,那么不更新。其实,判断误分类样本的过程也相当于遍历整个样本】,每个样本有n维特征向量,我们来分别计算在该场景下的时间复杂度。
    我们写一下伪代码:

    for i in range(0, m+1):
    if y[i] * (w*x[i]+b) < 0:
    更新w, b
    else:
    w, b不更新
    

    这里时间复杂度相当于是样本数*更新w,b 的时间复杂度。
    因此,
    对于随机梯度下降来说,每次随机选取一个误分类样本进行更新,样本数为1,其计算的时间复杂度为O(n),
    而对于批量梯度下降来说,每次需要对所有误分类点进行求和更新,每一个样本更新w,b的时间复杂度为O(n) ,那么对于m 个样本来说,时间复杂度为O(m∗n)。
    我们分3种情况分别讨论不同情况下的时间复杂度情况:
    1)当样本量特别少的时候,即n>>m 时,其实两者时间复杂度应该差不多。如果更在意精度的话,建议选择批量梯度下降,因为其更新信息是利用全局误分类点信息,精度相对于随机梯度下降更高一些,更新方向更接近全局解。
    2)当样本量特别特别大的时候,即m>>n ,此时批量梯度下降的更新时间的劣势凸显更为明显一些,时间复杂度陡增。在可以适当舍弃精度的同时,建议选择随机梯度下降。
    3)当样本量与特征维度近似时,这个时候批量梯度下降的时间复杂度约为随机梯度下降的一倍。需要进行准确性和性能两个方面的综合考量。
    综上所述,整体上来说,从工程和性能两个方面考虑来说,可以以牺牲较小的准确性为代价,选择随机梯度下降。

    2、对偶形式

    我们回顾下w,b 的更新方式【这里只看随机梯度下降方式】:


    image.png

    这里是每次通过修改误分类点来更新下w,b,这里可以这么来考虑,我们通过修改次数来表达w,b的更新。


    image.png

    为什么要转化成对偶形式
    这个建议先看完对偶形式的这个小节再回头来看原因,当然,如果对原始形式和对偶形式都非常熟悉的话,也可以直接看。
    既然有了原始形式,而且用随机梯度下降也能达到较好的效果,那么为什么还要考虑对偶形式,对偶形式的优势具体体现在什么地方呢?
    我们对比下原始形式和对偶形式的w,b 更新形式:
    原始形式:

    image.png

    六、【代码】

    适用问题:二类分类

    实验数据:
    由于是二分类器,所以将MINST数据集train.csv的label列进行了一些微调,label等于0的继续等于0,label大于0改为1。这样就将十分类的数据改为二分类的数据。获取地址train_binary.csv

    实现代码:

    # encoding=utf-8
    
    import pandas as pd
    import random
    import time
    
    from sklearn.cross_validation import train_test_split
    from sklearn.metrics import accuracy_score
    
    
    class Perceptron(object):
    
        def __init__(self):
            self.learning_step = 0.001  # 学习率
            self.max_iteration = 5000  # 分类正确上界,当分类正确的次数超过上界时,认为已训练好,退出训练
    
        def train(self, features, labels):        
    
            # 初始化w,b为0,b在最后一位
            self.w = [0.0] * (len(features[0]) + 1)
    
            correct_count = 0  # 分类正确的次数
    
            while correct_count < self.max_iteration:
    
                # 随机选取数据(xi,yi)
                index = random.randint(0, len(labels) - 1) 
                x = list(features[index])
                x.append(1.0)  # 加上1是为了与b相乘
                y = 2 * labels[index] - 1  # label为1转化为正实例点+1,label为0转化为负实例点-1
    
                # 计算w*xi+b
                wx = sum([self.w[j] * x[j] for j in range(len(self.w))])
    
                # 如果yi(w*xi+b) > 0 则分类正确的次数加1
                if wx * y > 0:
                    correct_count += 1
                    continue
    
                # 如果yi(w*xi+b) <= 0 则更新w(最后一位实际上b)的值
                for i in range(len(self.w)):
                    self.w[i] += self.learning_step * (y * x[i])
    
        def predict_(self, x):
            wx = sum([self.w[j] * x[j] for j in range(len(self.w))])
            return int(wx > 0)  # w*xi+b>0则返回返回1,否则返回0
    
        def predict(self, features):
            labels = []
            for feature in features:
                x = list(feature)
                x.append(1)
                labels.append(self.predict_(x))
            return labels
    
    
    if __name__ == '__main__':
    
        print("Start read data")
    
        time_1 = time.time()
    
        raw_data = pd.read_csv('../data/train_binary.csv', header=0)  # 读取csv数据,并将第一行视为表头,返回DataFrame类型
        data = raw_data.values
    
        features = data[::, 1::]
        labels = data[::, 0]
    
        # 避免过拟合,采用交叉验证,随机选取33%数据作为测试集,剩余为训练集
        train_features, test_features, train_labels, test_labels = train_test_split(features, labels, test_size=0.33, random_state=0)
    
        time_2 = time.time()
        print('read data cost %f seconds' % (time_2 - time_1))
    
        print('Start training')
        p = Perceptron()
        p.train(train_features, train_labels)
    
        time_3 = time.time()
        print('training cost %f seconds' % (time_3 - time_2))
    
        print('Start predicting')
        test_predict = p.predict(test_features)
        time_4 = time.time()
        print('predicting cost %f seconds' % (time_4 - time_3))
    
        score = accuracy_score(test_labels, test_predict)
    print("The accruacy score is %f" % score)
    
    

    实现代码(用sklearn实现):

    # encoding=utf-8
    
    import pandas as pd
    import time
    
    from sklearn.cross_validation import train_test_split
    from sklearn.metrics import accuracy_score
    
    from sklearn.linear_model import Perceptron
    
    
    
    if __name__ == '__main__':
    
        print("Start read data...")
        time_1 = time.time()
    
        raw_data = pd.read_csv('../data/train_binary.csv', header=0)  # 读取csv数据,并将第一行视为表头,返回DataFrame类型
        data = raw_data.values
    
        features = data[::, 1::]
        labels = data[::, 0]
    
        # 随机选取33%数据作为测试集,剩余为训练集
        train_features, test_features, train_labels, test_labels = train_test_split(features, labels, test_size=0.33, random_state=0)
    
        time_2 = time.time()
        print('read data cost %f seconds' % (time_2 - time_1))
    
        print('Start training...')
        clf = Perceptron(alpha=0.0001,max_iter=2000) # 设置步长及最大迭代次数
        clf.fit(train_features,train_labels)
        time_3 = time.time()
        print('training cost %f seconds' % (time_3 - time_2))
    
        print('Start predicting...')
        test_predict = clf.predict(test_features)
        time_4 = time.time()
        print('predicting cost %f seconds' % (time_4 - time_3))
    
        score = accuracy_score(test_labels, test_predict)
    print("The accruacy score is %f" % score)
    
    
    

    【总结】

    感知器模型

     适用问题:二类分类
    
     模型特点:分离超平面
    
     模型类型:判别模型
    

    感知机学习策略

     学习策略:极小化误分点到超平面距离    
    
     学习的损失函数:误分点超平面距离
    

    感知机学习算法

     学习算法:随机梯度下降
    
    image.png

    算法的收敛性

      证明:Novikoff定理
    
    image.png image.png image.png image.png image.png

    算法的对偶形式

      为了求解更简单,所以用对偶形式
    
    image.png
    image.png

    注:统计学习方法——修炼学习笔记系列参考学习资料:
    《统计学习方法》第2版 李航
    补充学习资料:
    https://www.jianshu.com/p/db7f5e841a56 李威威学习笔记
    https://blog.csdn.net/weixin_43374508/article/details/102784079 城序猿
    https://blog.csdn.net/qfire/article/details/77905787
    代码学习资料:https://github.com/WenDesi/lihang_book_algorithm

    相关文章

      网友评论

          本文标题:统计学习方法——修炼学习笔记2:感知机

          本文链接:https://www.haomeiwen.com/subject/sznzyhtx.html