统计学习方法(一)感知机学习

2018还剩最后几个小时啦!跨年就该做点有意义的事情啊 (。・∀・)ノ。这段时间零零散散地看了一部分《统计学习方法》,作为小白的我真的是看的…头昏脑胀,还是把自己学的一点点东西记录下来好了。
本篇地主要内容:

  • 简要介绍感知机算法
  • 感知机代码地简单实现
感知机(Perceptron)介绍

感知机是进行二类分类地线性分类模型,输入维实例向量,输出维该实例地分类。本质上就是在输入空间中,找到一个合适的超平面将所有的实例进行二类划分。
在《统计学习方法》这本书的第一章概论中提到,统计学习的三要素是模型、策略和算法。所以这里就按照这个顺序介绍了。

1.模型

关于感知机的定义,这里不详细写了,书中有详细的定义(其实是公式太多了,不好打…逃…),对于感知机模型,直观的几何解释就是对于输入空间,找到一个超平面将两侧的实例向量划分为正负两类,这里提一下超平面,毕竟对于我们非数学出身的学生,某些数学概念还真不清楚,如果不严格的说,对于二维平面,那么一条直线就是它的一个超平面,同样,一个平面就是一个三维空间的超平面,对于三维以上的空间我们不好想象,所以就出现了超平面这个词,这里只是理解成找到这样一个“平面”可以对输入空间进行划分就可以了(我大行不顾细谨哈哈)。

2.策略
学习策略也就是确定一个损失函数来衡量,当损失函数最小的时候,显然损失函数最小的时候对应的超平面就是我们所要找的了。损失函数的选择有多种,文中采用的是所有误分类点到超平面的总距离作为损失函数,这里具体的公式不详细罗列,说两个注意的地方:
第一个是空间中某一点到某一平面的距离,我通俗一点说:对于一个超平面,以及它的法向量,空间中某个点到这个超平面的距离等于这个点在法向量上的投影。
第二个是所有的误分类点都有一个特点,损失函数的计算也是基于此的,那就是按照当前的分离超平面计算的时候,该实例的实际分类与模型的输出是异号的,根据这个我们就可以判断某个输入实例向量是否要计入损失函数中了。

3.算法
算法即给出了详细的求解步骤,这里介绍感知机算法的损失函数如何优化。感知机采用的是随机梯度下降法(SGD),当然也可以使用梯度下降,区别就在于梯度下降需要以此计算所有误分类点的损失,这样在进行更新的时候会容易得到全局最优点(损失函数一般都会选择凸函数),但是缺点也是显而易见的,如果数据集过大,那么梯度下降的过程将会很缓慢,所以一般使用随机梯度下降,将数据集划分为多个很小的batch,每次使用一个batch进行梯度下降的过程,这样容易得到局部最优点,所以有时候需要随机多个开始的位置进行随机梯度下降(可能有些人会认为当然是全局最优最好啊,实际上我记得有篇文章介绍了,有些时候局部最优处的模型反而表现反而会好一点,简单理解的话就当作是过拟合了哈~)
这里给出感知机学习算法的原始形式:
在这里插入图片描述
跟着例子手动算一算的话还是很好理解的,毕竟感知机可以理解为没有隐层的神经网络,这时候的反向传播很好计算。后面的证明算法收敛性和对偶形式先不做介绍,个人觉得上面的原始形式是更具有一般性的算法,也更好理解或是实现。

感知机算法简单实现

下面使用一个简单的例子来“表演”一下感知机算法,这里没有用书上的例子,书中例子就三个点…自己手算一下得了,这里是我在别的地方看到的一个例子,使用的数据集是IRIS数据集,这个数据集的介绍可以看这里:IRIS数据集-百度百科,具体数据在这:IRIS数据
首先来看一下这些数据:
如果已经下载好了数据,可以直接使用pandas的read_csv函数进行导入:

iris = pd.read_csv('iris.csv')
col_names = iris.columns.tolist()    # 看一下列名
print(col_names)
#输出:
['Unnamed: 0', 'Sepal.Length', 'Sepal.Width', 'Petal.Length', 'Petal.Width', 'Species']

如何没有提前下载数据,也可以直接在代码中导入,这里先列出必要的包和模块:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris

准备数据:

iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['label'] = iris.target           # 将属性数据与标签数据合并

(关于IRIS数据集的导入,我另外写了一点东西,看起来会清楚一些:IRIS数据集介绍
先看看数据分布是什么样:

plt.scatter(df[:50]['sepal length'], df[:50]['sepal width'], label='0')
plt.scatter(df[50:100]['sepal length'], df[50:100]['sepal width'], label='1')
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()
plt.show()

在这里插入图片描述
可以看到两类数据点还是分的比较清楚的。
这里使用的数据是整个数据集的前100条,其中前50条是0类,后50条为1类:

# 获取数据的前100行,第1,2列作为两个坐标, 最后一列作为分类的标签
data = np.array(df.iloc[:100, [0, 1, -1]])  

X, y = data[:,:-1], data[:,-1]  # X是 (100, 2), y是(100, 1)
 # 这里注意y不可直接作为分类标签,数据类型是float,这里处理为0,1(int)
y = np.array([1 if i == 1 else -1 for i in y])     

模型主体部分:

class Model:
    def __init__(self):
        self.w = np.ones(len(data[0])-1, dtype=np.float32)                # 权重参数
        self.b = 0                         # 偏置
        self.l_rate = 0.1              # 学习率
        
    def sign(self, x, w, b):
        y = np.dot(x, w) + b
        return y
    
    # 随机梯度下降法
    def fit(self, X_train, y_train):
        is_wrong = False
        while not is_wrong:
            wrong_count = 0
            for d in range(len(X_train)):               # SGD其中以每一条数据作为一个batch进行参数更新
                X = X_train[d]
                y = y_train[d]
                if y * self.sign(X, self.w, self.b) <= 0:               # 这里判断是否在当前模型下误分类了
                    self.w = self.w + self.l_rate * np.dot(y, X)
                    self.b = self.b + self.l_rate * y
                    wrong_count += 1
            if wrong_count == 0:
                is_wrong = True
        return "Perceptron ready."

运行模型:

# 运行
perceptron = Model()
perceptron.fit(X, y)

# 绘图
x_points = np.linspace(4, 7, 10)
y_ = -(perceptron.w[0]*x_points + perceptron.b) / perceptron.w[1]
plt.plot(x_points, y_)

plt.plot(data[:50, 0], data[:50, 1], 'bo', color='blue', label='0')
plt.plot(data[50:100, 0], data[50:100, 1], 'bo', color='orange', label='1')
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()

效果:

在这里插入图片描述

这样看的话其实对模型随着SGD的进行所进行的调整展现的不明显,也可以在过程中绘制中间结果。

完整代码如下:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris

iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['label'] = iris.target         # 原本属性数据与标签数据是分离的 这里合并在一起
data = np.array(df.iloc[:100, [0, 1, -1]])  # 获取数据的前100行,分别取得1,2 和最后一列
X, y = data[:,:-1], data[:,-1]  # X是 (100, 2), y是(100, 1)
y = np.array([1 if i == 1 else -1 for i in y])  # 列表解析式 将分类(0., 1.)处理为严格 int类型
print("Data ready.")

class Model:
    def __init__(self):
        self.w = np.ones(len(data[0])-1, dtype=np.float32)
        self.b = 0
        self.l_rate = 0.1
        self.sav_w = []
        self.sav_b = []
        # self.data = data
    
    def sign(self, x, w, b):
        y = np.dot(x, w) + b
        return y
    
    # 随机梯度下降法
    def fit(self, X_train, y_train):
        is_wrong = False
        while not is_wrong:
            wrong_count = 0
            for d in range(len(X_train)):
                X = X_train[d]
                y = y_train[d]
                if y * self.sign(X, self.w, self.b) <= 0:
                    self.w = self.w + self.l_rate*np.dot(y, X)
                    self.b = self.b + self.l_rate*y
                    self.sav_w.append(self.w)
                    self.sav_b.append(self.b)
                    wrong_count += 1
            if wrong_count == 0:
                is_wrong = True
        print("Model ready.")
    
    def sav(self):
        return {'weights':self.sav_w, 'biases':self.sav_b}
        
    
perceptron = Model()
perceptron.fit(X, y)

x_points = np.linspace(4, 7, 10)
y_ = -(perceptron.w[0]*x_points + perceptron.b) / perceptron.w[1]  # 就是将ax+by=c 转化为y=kx+b形式
plt.plot(x_points, y_, label='final line')

# 绘制了一下中间的5个结果
# 从返回的结果来看 总共进行了近1500次迭代
weights = perceptron.sav()['weights']
biases = perceptron.sav()['biases']
l = len(weights)
for i in range(1, 5):
    plt.plot(x_points, -(weights[i*int(l/5)][0]*x_points + biases[i*int(l/5)]) / weights[i*int(l/5)][1])
        
plt.plot(data[:50, 0], data[:50, 1], 'bo', color='blue', label='0')
plt.plot(data[50:100, 0], data[50:100, 1], 'bo', color='orange', label='1')
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.xlim(4.0, 7.0)
plt.ylim(2.0, 4.5)
plt.legend()
plt.show()

效果:
在这里插入图片描述
以上~

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值