今天开始, 要开始学李航的统计机器学习了,虽然现在都是深度学习横着走,但基础的东西还是要学起来的,一直想学这本书,因为太多别的事情,一直在拖,今天决定不再拖延,在五一之前都专心学李航这本书,把该掌握的都掌握了。(立下一个不可能实现的flag)
本博客不会详细介绍书里的内容,主要作为我个人的笔记记录,(毕竟有书,我没必要把书抄一遍)。另外会贴大佬的代码并进行必要的讲解,不过终究是大佬的代码,这次学习不打算自己复现模型,以快速学习为主要,部分深入学习的形式来学这本书。
我不会很细致地讲,因为我没有这个水平。
一. 感知机模型
感知机的公式为:f(x) = sign(w·x + b)
其中:w,b:为模型的训练参数
x:为模型的输入
f(x):为模型的输出(也就是预测结果,注意这里不是正确答案,而是预测答案,我刚开始学就没分清楚这个,为我自己标注)。
sign(x):为符号函数(选择函数):sign(x) = +1 if x>=0, -1 if x < 0。(emmm,这表达式好像有什么不对的样子)
二. 感知机学习策略
为了方便起见,我们认为我们的目标是进行二分类,并且数据是线性可分的,所谓的线性可分,就是数据样本可以被一条直线或者一个超平面(注意是平的)划分成两部分,一部分是正样本,一部分是负样本(正负只是说法上的习惯,并不是说真的一定是正负的样本,所以我为什么这么啰嗦)。
那么,这个超平面S是什么呢?
书里给出的公式是 W·X + B = 0。
这里有一个需要注意的点,刚开始学机器学习的时候,总是会把习惯上的y = kx + b和模型中的y联系在一起,然而,因为我当时这么联系,总是会在脑海中构建坐标系的时候,把y也加进去,导致越学越乱,虽然加进去是对的,但是大多数教材都习惯性不建立这个y轴,毕竟在分类任务中,y的取值很有限,没必要为了y值建立一条坐标轴。所以,我们建立一个没有y轴的坐标系,坐标轴都有X(x1, x2, x3, .. , xn)组成,也就是特征组成。
那么到这里就能清楚了,这个一个在由坐标轴X(x1, x2, x3, .., xn)组成的坐标系中的一个超平面,其中,W(w1, w2, w3, .. , wn),为每个特征的系数(在二维平面上我们称之为斜率,没错,是一样的),B(b1, b2, b3, .. , bn)则是每个坐标系上的截距。其实就是因为变成了向量形式,所以稍微需要转转弯才能想明白(像我就需要转好多好多弯,才想明白)。
OK!有了超平面的公式了,现在的任务就是确定超平面,那么怎么确定超平面呢?
我们首先明确,我们设置这个超平面是为了什么?没错,正确划分数据样本,那么只要能正确划分数据样本,这个超平面就是我们想要的超平面,那么怎么评判这个超平面正确划分了数据样本呢?
这个时候就由大名鼎鼎的损失函数登场了,书中选择的损失函数是统计每个误分类点到超平面S的距离。
其中点到超平面的距离公式为:
误分类点xi到超平面的距离为
由于损失函数是所有误分类点到超平面的距离的和,对应的公式为:
其中M为误分类点集合。
三. 感知机学习算法
学习算法也就是训练模型的方法,书中采用梯度下降的方式最小化损失函数(也就是训练模型)。
梯度下降具体怎么工作的,学过高数的同学肯定都知道,凸函数只有一个极值,且这个极值为最值。为了便于理解,我们只讨论二维平面上的凸函数,且这个极值就是极小值(最小值),当然,极大值也是可以的,只是换个方向,没有太多变化。
那么梯度下降具体怎么工作的呢?(这里不打算讨论细致,感兴趣的同学直接查阅梯度下降),其实梯度下降就是一个找到极值点(损失函数的极值点)的操作,通过计算损失函数关于每个wi的导数,来更新每个wi,bi同理。至于为什么可以这么做呢,因为这样可以让损失变小,更具体的就不讨论了,因为我懒。
更多的细节请参考书(书籍的名字和出版会在最后写明)。
感知机学习算法的原始形式:(其他形式不讨论)步骤如下:
1. 选取初值x0,b0。
2. 在训练集中选取数据(xi, yi)。
3. 如果yi(w·x+b)<=0
w <- w + xiyi
b <- b + yi
4. 转至2,直至训练集中没有误分类点。
四. 代码
# 数据线性可分,二分类数据
# 此处为一元一次线性方程
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.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
wrong_count += 1
if wrong_count == 0:
is_wrong = True
return 'Perceptron Model!'
def score(self):
pass
perceptron = Model()
perceptron.fit(X, y)
更多的代码请参考第五节参考中给出的github连接。
可以看到,Model类中,有初始化init,符号函数sign,训练函数fit。
其中inti为初始化参数W,B,还有设置学习速率l_rate(也就是梯度下降的速度)。
符号函数sign,这个符号函数是假的,由于对于后面的计算,只需要判定结果是否为非负或者负即可,是不是符号函数并不影响。(大佬,我帮你把场子圆回来了)。
训练函数fit,由于这个比较重要,我重点讲一下,(敲黑板!!)。其中
wrong_count:表示模型误分类的个数。
if y * self.sign(X, self.w, self.b) <= 0: 为判断当前样本点是否会被误分类,如果满足条件,则为误分类,则进行梯度下降并增加误分类个数wrong_count。(这里是<=0而不是>0是因为,他没有加负号,所以没有问题,自以为你们会在这里有疑惑,因为跟上面不一样)。
self.w = self.w + self.l_rate*np.dot(y, X)和self.b = self.b + self.l_rate*y:对w,b进行更新,也就是梯度下降。这个公式与我们第三节3对应。注意这里的np.dot是矩阵乘法,不是点乘。所以np.dot(y, X)结果是一个矩阵。
fit的最后就是一个误分类样本都没有了,不过这个太理想了,一般会设置阈值或者是训练次数来提前结束训练。
五. 参考
1.《统计机器学习》 -- 李航,清华大学出版社。(印次:2016年11月第16次印刷)。
2. github: https://github.com/wzyonggege/statistical-learning-method/blob/master/Perceptron/Iris_perceptron.ipynb。