统计学习方法 学习笔记(二):感知机
在这一篇笔记中,来了解一下感知机模型(perceptron),我们将要了解感知机的模型,感知机的学习策略和感知机的学习算法,并且使用Python编程来实现书中的例子。
感知机:
感知机是神经网络与支持向量机的基础,是二类分类的线性分类模型,其输入为实列的特征向量,输出为实列的类别,取+1和-1二值(这里为啥取+1和-1呢?为啥不取+1和0呢?下面会解释,稍安勿躁)。感知机对应于特征空间中将数据进行线性划分的分离超平面,属于判别模型。感知机学习旨在求出将训练数据进行线性划分的分离超平面,为此,导入基于误分类的损失函数,利用梯度下降法对损失函数进行极小化,求得感知机模型。
感知机模型:
定义:假设输入空间(特征空间)是$\mathcal{X}\subseteq R^n$,输出空间是$\mathcal{Y} = \{+1,-1\}$。输入$x\in\mathcal{X}$表示实列的特征向量,对应于输入空间(特征空间)的点;输出$y\in\mathcal{Y}$表示实例的类别。由输入空间到输出空间的如下函数:
$$f(x) = sign(w\cdot x + b)$$
称为感知机。其中,$w$和$b$为感知机模型参数,$w\in R^n$叫作权值(weight)或权值向量(weight vector),$b\in R$叫做偏置(bias),$w\cdot x$表示$w$和$x$的内积。$sign$是符号函数,即
$$sign(x) = \left\{\begin{matrix}
+1, & x\geqslant 0\\
-1, & x<0
\end{matrix}\right.$$
感知机有如下几何解释:线性方程
$$w\cdot x + b = 0$$
对应于特征空间$R^n$中的一个超平面$S$,其中$w$是超平面的法向量,$b$是超平面的截距。这个超平面将特征空间划分为两个部分。位于两部分的点(特征向量)分别被分为正、负两类。因此,超平面$S$称为分离超平面:如图,
由中学知识得知:点到直线的距离为:$d = \frac{Ax_0 + By_0 + C}{\sqrt{A^2 + B^2}}$,所以,坐标原点到$w\cdot x + b = 0$的距离为$\frac{|b|}{||w||}$,图中直线可以写为:$w^{(1)}x^{(1)}+ w^{(2)}x^{(2)} + b = 0$,所以:$x^{(2)} = -\frac{w^{(1)}}{w^{(2)}}x^{(1)} - b$,由于图中的直线与$x^{(2)}$的正轴相交,所以:$-b>0$
感知机学习策略:
首先介绍一下数据集的线性可分性定义(为啥要特别介绍这个呢?因为感知机模型处理的数据集只能是线性可分的,这是感知机模型的弊端,那如何解决这个弊端呢?在本篇中不作介绍,请参考支持向量机):
数据集的线性可分性:给定一个数据集
$$T = \{(x_1,y_1),(x_2,y_2),...,(x_N,y_N)\}$$
其中,$x_i \in \mathcal{X} = R^n, y_i \in \mathcal{Y} = {+1,-1},i=1,2,...,N$。如果存在某个超平面$S$
$$w\cdot x + b = 0$$
能够将数据集的正实例点和负实例点完全正确地划分到超平面的两侧,即对所有的$y_i = +1$的实例$i$,有$w\cdot x_i + b >0$,对所有$y_i = -1$的实例$i$,有$w\cdot x_i + b<0$,则称数据集$T$为线性可分数据集;否则,称数据集$T$线性不可分。
损失函数:损失函数的一个自然选择是误分类点的总数。但是,这样的损失函数不是参数$w,b$的连续可导函数,不易优化。损失函数的另一个选择是误分类点到超平面$S$的总距离,这是感知机所采用的损失函数。为此,输入空间$R^n$中任一点$x_0$到超平面$S$的距离:
$$\frac{1}{||w||}|w\cdot x_0 + b|$$
这里,$||w||$是$w$的$L_2$范数,$L_p$范数公式为:
$$||x|| = (\sum_{i}|x_i|^{p})^{\frac{1}{p}}$$
我们把$||w||_2$简写为$||w||$。在这里补充一点:
一个非零向量除以它的模的结果是一个与原向量同方向的单位向量:$\frac{\vec{a}}{|\vec{a}|} = \vec{e_a}$
如上述图:
$$x_0 = x_1 + s\cdot \frac{w}{||w||}$$
求得,点$x_0$到直线的距离$s$为:
$$s = \frac{1}{||w||}|w\cdot x_0 + b|$$
对于误分类的数据$(x_i,y_i)$来说,$-y_i(w\cdot x_i + b) > 0$成立,因为当$w\cdot x_i + b > 0$时,$y_i = -1$,因此,误分类点$x_i$到超平面$S$的距离是:
$$-\frac{1}{||w||}y_i(w\cdot x_i + b)$$
这样,假设超平面$S$的误分类点集合为$M$,那么所有误分类点到超平面$S$的总距离为(看到这里,就能明白为啥标签取+1和-1了吧,为了能够得到下面这个统一优美的损失函数):
$$-\frac{1}{||w||}\sum_{x_i\in M}y_i(w\cdot x_i + b)$$
不考虑$\frac{1}{||w||}$,就得到感知机学习的损失函数为:
$$L(w,b) = -\sum_{x_i\in M}y_i(w\cdot x_i + b)$$
这个损失函数就是感知机学习的经验风险函数。如果没有误分类点,损失函数值是0
感知机学习算法:
感知机学习问题转化为求解损失函数的最优化问题,最优化的方法是随机梯度下降法.
感知机学习算法的原始形式:
感知机学习算法的目标函数为:
$$\min_{w,b}L(w,b) = -\sum_{x_i\in M}y_i(w\cdot x_i + b)$$
其中$M$为误分类点的集合。感知机学习算法是误分类驱动的,具体采用随机梯度下降法。首先,任意选取一个超平面$w_0,b_0$,然后用梯度下降法不断地极小化目标函数。极小化过程中不是一次使$M$中所有误分类点的梯度下降,而是一次随机选取一个误分类点使其梯度下降。
假设误分类点集合$M$是固定的,那么损失函数$L(w,b)$的梯度由:
$$\bigtriangledown _wL(w,b) = - \sum_{x_i\in M}y_ix_i\\\bigtriangledown _bL(w,b) = - \sum_{x_i\in M}y_i$$
给出。
随机选取一个误分类点$(x_i,y_i)$,对$w,b$进行更新:
$$w\leftarrow w + \eta y_ix_i\\b\leftarrow b + \eta y_i$$
式中$\eta(0<\eta\leq 1)$是步长,又称为学习率。这样,通过迭代可以期待损失函数$L(w,b)$不断减小,直到为0。
所以,感知机学习算法的原始形式:
输入:训练数据集$T = {(x_1,y_1),(x_2,y_2),...,(x_N,y_N)}$,其中$x_i\in\mathcal{X} = R^n,y_i \in \mathcal{Y}=\{-1,+1\},i=1,2,...,N$;学习率$\eta(0<\eta\leq 1)$;
输出: $w,b$;感知机模型$f(x)=sign(w\cdot x + b)$
(1)选取初值$w_0,b_0$
(2)在训练集中选取数据$(x_i,y_i)$
(3)如果$y_i(w\cdot x_i + b)\leq 0$
$$w\leftarrow w + \eta y_ix_i\\b\leftarrow b + \eta y_i$$
(4)转至(2),直至训练集中没有误分类点。
感知机学习算法的对偶形式:
感知机学习算法的原始形式和对偶形式与支持向量机学习算法的原始形式和对偶形式想对应。对偶形式的基本想法是,将$w$和$b$表示为实例$x_i$和标记$y_i$的线性组合的形式,通过求解其系数而求得$w$和$b$。对误分类点$(x_i,y_i)$通过
$$w\leftarrow w + \eta y_ix_i\\b\leftarrow b + \eta y_i$$
逐步修改$w,b$,设修改$n$次,则$w,b$关于$(x_i,y_i)$的增量分别是$\alpha_iy_ix_i$和$\alpha_iy_i$,这里,$\alpha_i = n_i\eta$。这样,最后学习到的$w,b$可以分别表示为
$$w = \sum_{i=1}^{N}\alpha_iy_ix_i\\b = \sum_{i=1}^{N}\alpha_iy_i$$
这里,$\alpha_i\geq 0,i=1,2,...,N$,当$\eta = 1$时,表示第$i$个实例点由于误分而进行更新的次数。实例点更新次数越多,意味着它距离分离超平面越近,也就越难正确分类。
感知机学习算法的对偶形式:
输入:训练数据集$T = {(x_1,y_1),(x_2,y_2),...,(x_N,y_N)}$,其中$x_i\in\mathcal{X} = R^n,y_i \in \mathcal{Y}=\{-1,+1\},i=1,2,...,N$;学习率$\eta(0<\eta\leq 1)$;
输出:$\alpha,b$;感知机模型$f(x) = sign(\sum_{j=1}^{N}\alpha_{i}y_jx_j\cdot x + b)$,其中$\alpha = (\alpha_1,\alpha_2,...,\alpha_N)^T$
(1)$\alpha \leftarrow 0,b\leftarrow 0$
(2)在训练集中选取数据$(x_i,y_i)$
(3)如果$y_i(\sum_{j=1}^{N}\alpha_jy_jx_j\cdot x_i + b) \leq 0$
$$\alpha_i \leftarrow \alpha_i + \eta\\b \leftarrow b + \eta y_i$$
(4)转至(2)直到没有误分类数据。
对偶形式中训练实例仅以内积的形式出现,为了方便,可以预先将训练集中实例间的内积计算出来并以矩阵的形式存储,这个矩阵就是所谓的Gram矩阵。
*******************************************哈哈我是分割线****************************************
感知机学习算法原始形式python代码示例:
我通过下面五个阶段来介绍代码:
(1)收集数据:提供文本文件
(2)准备数据:使用python解析文本文件
(3)分析数据:使用Matplotlib画出二维散点图
(4)训练算法:通过学习算法学习到参数
(5)观察结果:使用Matplotlib画出二维散点图及学到的模型
1、收集数据:使用书中例2.1所示的训练数据集:构造一个文件名为“train.txt”文本文件,数据之间使用一个tab键来隔开,一个训练样本作为一行,第三列是标签,看下图所示:
2、准备数据:通过解析“train.txt”文件,将训练样本的信息放入到数组train_feature中,将训练样本的标签放入到列表train_label中(和train_feature按顺序对应)
def file2array(filename): with open(filename) as f: lines = f.readlines() # lines is list type, every element in lines is a str type num_rows = len(lines) num_columns = len(lines[0].strip().split()) train_feature = np.zeros((num_rows,num_columns-1)) # every train sample have 2 dimensions train_label = [] index = 0 for line in lines: line = line.strip().split() # for every line, first remove Head and tail spaces and newline characters('\n'), then use '\t' split to get a list, train_feature[index,:] = line[0:2] train_label.append(int(line[-1])) # every element in line is a str type, so, use int() index += 1 return train_feature,train_label
3、分析数据:画散点图来观察训练样本分布,红点表示正样本
def plot_scatter(train_feature,train_label): color_list = [] for label in train_label: if label == 1: color_list.append('r') else: color_list.append('k') plt.scatter(train_feature[:,0],train_feature[:,1],15.0,color_list,'o') plt.axis([0, 6, 0, 6]) plt.show()
4、训练算法:通过下面代码,学习到参数$w$和$b$
def algorithm(feature,label,learn_rate): rows,columns = feature.shape w = np.zeros(columns) b = 0 #pdb.set_trace() while(1): count = 0 for i in range(0,rows): if label[i] * (np.dot(feature[i,:],w)+b)<= 0: w = w + learn_rate*label[i]*feature[i,:] b = b + learn_rate*label[i] print(w,b) else: count += 1 if count == len(feature): return w,b
5、观察结果:画出训练样本散点图和学到的参数组成的直线
def draw_result(w,b,train_feature,train_label): x = np.arange(-10,10) y = - w[0]/w[1]*x - b color_list = [] for label in train_label: if label == 1: color_list.append('r') else: color_list.append('k') plt.plot(x,y) plt.scatter(train_feature[:,0],train_feature[:,1],15.0,color_list,'o') plt.axis([0, 6, 0, 6]) plt.show()
6、运行主程序
import numpy as np import matplotlib.pyplot as plt import argparse def main(filename): ''' (1): 加载训练数据 (2):分析数据 (3):学习参数 (4): 画出结果 ''' train_feature,train_label = file2array(filename) plot_scatter(train_feature,train_label) w,b = algorithm(train_feature,train_label,1) print(w,b) draw_result(w,b,train_feature,train_label) if __name__ == '__main__': parser = argparse.ArgumentParser('load filename') parser.add_argument("--filename",default='train.txt') args = parser.parse_args() main(args.filename)
感知机学习算法对偶形式python代码示例:
和原始形式的代码比较有以下几点不同:
(1)新添加了一个求gram矩阵的代码
(2)修改了训练算法
(3)修改了运行主程序
1、求gram矩阵:
def gram_matrix(feature): rows,_ = feature.shape gram = np.zeros((rows,rows)) for i in range(0,rows): for j in range(0,rows): gram[i,j] = np.dot(feature[i],feature[j]) return gram
2、训练算法
def algorithm(feature,label,learn_rate,gram): rows,columns = feature.shape alpha = np.zeros(rows) b = 0 #pdb.set_trace() while(1): count = 0 for i in range(0,rows): sum = 0 for j in range(0,rows): sum+=alpha[j]*label[j]*gram[i,j] if label[i] * (sum+b)<= 0: alpha[i]= alpha[i] + learn_rate b = b + learn_rate*label[i] print(alpha,b) else: count += 1 if count == len(feature): return alpha,b
3、运行主程序
import numpy as np import matplotlib.pyplot as plt import argparse def main(filename,learn_rate): ''' (1): 加载训练数据 (2):分析数据 (3):学习参数 (4): 画出结果 ''' train_feature,train_label = file2array(filename) plot_scatter(train_feature,train_label) gram = gram_matrix(train_feature) alpha,b = algorithm(train_feature,train_label,learn_rate,gram) rows,colums = train_feature.shape w = np.zeros(colums) for i in range(0,rows): w+=alpha[i]*train_label[i]*train_feature[i,:] print(w,b) draw_result(w,b,train_feature,train_label) if __name__ == '__main__': parser = argparse.ArgumentParser('load filename') parser.add_argument("--filename",default='train.txt') parser.add_argument("--learn_rate",default=1) args = parser.parse_args() main(args.filename,args.learn_rate)
感知机存在的问题,或者说约束:
(1)感知机对训练数据集要求较高,只能处理线性可分的数据集,并且解不唯一,存在多个解。那如果对于数据集大部分是线性可分的,并且想要求解一个固定唯一的解,如何解决这个问题呢?请参见线性可分支持向量机和线性支持向量机
(2)对于不是线性可分的数据集,该怎么办呢?请参见非线性支持向量机