【导读】本章的结构按着统计学习方法三要素——模型、策略、算法来进行安排。感知机是我们正式学习的第一个模型,算是一个比较简单基础的的二分类的线性分类模型,但是感知机是神经网络和支持向量机的基础,我们一定要把握好。主要内容依然是用思维导图展示,强调知识体系结构。此外,由于我们后面学习的模型很多,我们要重点弄清每个模型的具体应用场景和优缺点。
链接:https://pan.baidu.com/s/1XhWfHDiedlSDWUucgfsPHA
提取码:z0rr
应用 简单的线性分类
优点
缺点
定义 感知机,就是二类分类的线性分类模型,其输入为样本的特征向量,输出为样本的类别,取+1和-1二值,即通过某样本的特征,就可以准确判断该样本属于哪一类。顾名思义,感知机能够解决的问题首先要求特征空间是线性可分的,再者是二类分类,即将样本分为{+1, -1}两类。从比较学术的层面来说,由输入空间到输出空间的函数:
f ( x ) = sign ( w ⋅ x + b ) f(x)=\operatorname{sign}(w \cdot x+b) f(x)=sign(w⋅x+b)
称为感知机,w和b为感知机参数,w为权值(weight),b为偏置(bias)。sign为符号函数:
sign ( x ) = { + 1 , x ⩾ 0 − 1 , x < 0 \operatorname{sign}(x)=\left\{\begin{array}{ll}+1, & x \geqslant 0 \\ -1, & x<0\end{array}\right. sign(x)={+1,−1,x⩾0x<0
感知机模型的假设空间是定义在特征空间中的线性分类模型,即函数集合: { f ∣ f ( x ) = w ⋅ x + b } \{f \mid f(x)=w \cdot x+b\} {f∣f(x)=w⋅x+b}
感知机学习的策略是极小化损失函数:
min w , b L ( w , b ) = − ∑ x i ∈ M y i ( w ⋅ x i + b ) \min _{w, b} L(w, b)=-\sum_{x_{i} \in M} y_{i}\left(w \cdot x_{i}+b\right) w,bminL(w,b)=−xi∈M∑yi(w⋅xi+b)
损失函数对应于误分类点到分离超平面的总距离。
【思考】样本到超平面距离为什么是 1 ∥ w ∥ ∣ w ⋅ x 0 + b ∣ \frac{1}{\|w\|}\left|w \cdot x_{0}+b\right| ∥w∥1∣w⋅x0+b∣?
【回答】我们知道点 ( x 0 , y 0 ) \left(x_{0}, y_{0}\right) (x0,y0)到直线Ax+By+C=0的距离是:
d = A x 0 + B y 0 + C A 2 + B 2 d=\frac{A x_{0}+B y_{0}+C}{\sqrt{A^{2}+B^{2}}} d=A2+B2Ax0+By0+C
将公式推广,把样本点带入公式即可求得。
【思考】为什么可以不考虑 1 ∥ w ∥ \frac{1}{\|w\|} ∥w∥1?
【回答】
感知机处理线性可分数据集,二分类, y i ∈ { − 1 , + 1 } y_i∈\{−1,+1\} yi∈{−1,+1},所以涉及到的乘以 y i y_i yi 的操作实际贡献的是符号;
损失函数 L ( w , b ) = − ∑ x i ∈ M y i ( w ⋅ x i + b ) L(w,b)=-\sum_{x_i\in M}y_i(w\cdot x_i+b) L(w,b)=−∑xi∈Myi(w⋅xi+b),其中 M M M 是错分的点集合,线性可分的数据集肯定能找到超平面 S S S, 所以这个损失函数最值是0。
如果正确分类, y i ( w ⋅ x i + b ) = ∣ w ⋅ x i + b ∣ y_i(w\cdot x_i+b)=|w\cdot x_i+b| yi(w⋅xi+b)=∣w⋅xi+b∣ ,错误分类的话,为了保证正数就加个负号,这就是损失函数里面那个负号,这个就是函数间隔;
1 ∣ ∣ w ∣ ∣ \frac{1}{||w||} ∣∣w∣∣1 用来归一化超平面法向量,得到几何间隔,也就是点到超平面的距离, 函数间隔和几何间隔的差异在于同一个超平面 ( w , b ) (w,b) (w,b) 参数等比例放大成 ( k w , k b ) (kw,kb) (kw,kb) 之后,虽然表示的同一个超平面,但是点到超平面的函数间隔也放大了,但是几何间隔是不变的;
具体算法实现的时候, w w w要初始化,然后每次迭代针对错分点进行调整,既然要初始化,那如果初始化个 ∣ ∣ w ∣ ∣ = 1 ||w||=1 ∣∣w∣∣=1 的情况也就不用纠结了,和不考虑 1 ∣ ∣ w ∣ ∣ \frac{1}{||w||} ∣∣w∣∣1 是一样的了;
针对错分点是这么调整的
w ← w + η y i x i b ← b + η y i \begin{aligned} w&\leftarrow w+\eta y_ix_i\\ b&\leftarrow b+\eta y_i \end{aligned} wb←w+ηyixi←b+ηyi
前面说了 y i y_i yi 就是个符号,那么感知机就可以解释为针对误分类点,通过调整 w , b w,b w,b 使得超平面向该误分类点一侧移动,迭代这个过程最后全分类正确;
感知机的解不唯一,和初值有关系,和误分类点调整顺序也有关系;
这么调整就能找到感知机的解?能,Novikoff还证明了,通过有限次搜索能找到将训练数据完全正确分开的分离超平面。
所以,
如果只考虑损失函数的最值,那没啥影响,线性可分数据集,最后这个损失就是0; 那个分母用来归一化法向量,不归一化也一样用,感知机的解不唯一;说正数不影响的应该考虑的是不影响超平面调整方向吧
感知机学习算法是对上述损失函数进行极小化,求得w和b。但是用普通的基于所有样本的梯度和的均值的批量梯度下降法(BGD)是行不通的,原因在于我们的损失函数里面有限定,只有误分类的M集合里面的样本才能参与损失函数的优化。所以我们不能用最普通的批量梯度下降,只能采用随机梯度下降(SGD)。
随机抽取一个误分类点使其梯度下降。
w = w + η y i x i w = w + \eta y_{i}x_{i} w=w+ηyixi
b = b + η y i b = b + \eta y_{i} b=b+ηyi
当实例点被误分类,即位于分离超平面的错误侧,则调整 w w w, b b b的值,使分离超平面向该无分类点的一侧移动,直至误分类点被正确分类
目标函数如下:
L ( w , b ) = arg min w , b ( − ∑ x i ∈ M y i ( w ⋅ x i + b ) ) L(w, b)=\arg \min _{w, b}\left(-\sum_{x_{i} \in M} y_{i}\left(w \cdot x_{i}+b\right)\right) L(w,b)=argw,bmin(−xi∈M∑yi(w⋅xi+b))
【墙裂推荐阅读】为什么说随机最速下降法 (SGD) 是一个很好的方法?
输入:训练数据集 T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋯ , ( x N , y N ) } T=\left\{\left(x_{1}, y_{1}\right),\left(x_{2}, y_{2}\right), \cdots,\left(x_{N}, y_{N}\right)\right\} T={(x1,y1),(x2,y2),⋯,(xN,yN)}, y i ∈ { − 1 , + 1 } y_i∈\{−1,+1\} yi∈{−1,+1},学习率η(0<η<1)
输出: w , b w, b w,b;感知机模型 f ( x ) = sign ( w ⋅ x + b ) f(x)=\operatorname{sign}(w \cdot x+b) f(x)=sign(w⋅x+b)
w = w + η y i x i w=w+\eta y_{i} x_{i} w=w+ηyixi
b = b + η y i b=b+\eta y_{i} b=b+ηyi
由于 w , b w, b w,b的梯度更新公式:
w = w + η y i x i w=w+\eta y_{i} x_{i} w=w+ηyixi
b = b + η y i b=b+\eta y_{i} b=b+ηyi
我们的 w , b w, b w,b经过了n次修改后的,参数可以变化为下公式,其中 α i = n i η \alpha_{i}=n_{i} \eta αi=niη:
w = ∑ x i ∈ M η y i x i = ∑ i = 1 n α i y i x i w=\sum_{x_{i} \in M} \eta y_{i} x_{i}=\sum_{i=1}^{n} \alpha_{i} y_{i} x_{i} w=xi∈M∑ηyixi=i=1∑nαiyixi
b = ∑ x i ∈ M η y i = ∑ i = 1 n α i y i b=\sum_{x_{i} \in M} \eta y_{i}=\sum_{i=1}^{n} \alpha_{i} y_{i} b=xi∈M∑ηyi=i=1∑nαiyi
这样我们就得出了感知机的对偶算法。
输入:训练数据集 T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋯ , ( x N , y N ) } T=\left\{\left(x_{1}, y_{1}\right),\left(x_{2}, y_{2}\right), \cdots,\left(x_{N}, y_{N}\right)\right\} T={(x1,y1),(x2,y2),⋯,(xN,yN)}, y i ∈ { − 1 , + 1 } y_i∈\{−1,+1\} yi∈{−1,+1},学习率η(0<η<1)
输出: α , b \alpha, b α,b;感知机模型 f ( x ) = sign ( ∑ j = 1 n α j y j x j ⋅ x + b ) f(x)=\operatorname{sign}\left(\sum_{j=1}^{n} \alpha_{j} y_{j} x_{j} \cdot x+b\right) f(x)=sign(∑j=1nαjyjxj⋅x+b)
其中 α = ( α 1 , α 2 , … , α n ) T \alpha=\left(\alpha_{1}, \alpha_{2}, \ldots, \alpha_{n}\right)^{T} α=(α1,α2,…,αn)T
赋初值 α 0 , b 0 \alpha_{0}, b_{0} α0,b0
选取数据点 ( x i , y i ) \left(x_{i}, y_{i}\right) (xi,yi)
判断该数据点是否为当前模型的误分类点,即判断若 y i ( ∑ j = 1 N α j y j x j ⋅ x i + b ) ⩽ 0 y_{i}\left(\sum_{j=1}^{N} \alpha_{j} y_{j} x_{j} \cdot x_{i}+b\right) \leqslant 0 yi(∑j=1Nαjyjxj⋅xi+b)⩽0则更新
α i = α i + η \alpha_{i}=\alpha_{i}+\eta αi=αi+η
b = b + η y i b=b+\eta y_{i} b=b+ηyi
转到2,直到训练集中没有误分类点
为了减少计算量,我们可以预先计算式中的内积,得到Gram矩阵
G = [ x i , x j ] N × N G=\left[x_{i}, x_{j}\right] N \times N G=[xi,xj]N×N
【思考】原始形式和对偶形式的如何选择?
【回答】
【墙裂推荐阅读】如何理解感知机学习算法的对偶形式?
import pandas as pd
import numpy as np
from sklearn.datasets import load_iris #sklearn库里面自带的鸢尾花数据集
import matplotlib.pyplot as plt
%matplotlib inline
# load data
iris = load_iris() #iris包括2个数组data和target,里面还有不少属性,感兴趣可以输出看看,加深对代码的直观理解
df = pd.DataFrame(iris.data, columns=iris.feature_names) #将数组data转成DataFrame
df['label'] = iris.target
df.columns = [
'sepal length', 'sepal width', 'petal length', 'petal width', 'label'
] #对列名重命名
df.label.value_counts()
2 50
1 50
0 50
Name: label, dtype: int64
iris数据集前50个标签为0,51-100标签为1,101-150标签为2
#考虑两个属性sepal length和sepal width的影响
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()
data = np.array(df.iloc[:100, [0, 1, -1]]) #将df中前100行的第一第二列和最后一列(label)抽出来转成数组
X, y = data[:,:-1], data[:,-1] #元组赋值,前两列赋值给X,最后一列赋值给y
y = np.array([1 if i == 1 else -1 for i in y]) #把label为0的换成1
y
array([-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
# 数据线性可分,二分类数据
# 此处为一元一次线性方程
class Model: #定义一个类Model
def __init__(self): #初始化w、b、l_rate(学习率),理论部分用η表示
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)
'Perceptron Model!'
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()
【参考】https://scikit-learn.org/0.19/modules/generated/sklearn.linear_model.Perceptron.html
import sklearn
from sklearn.linear_model import Perceptron
sklearn.__version__
'0.21.3'
clf = Perceptron(fit_intercept=True,
max_iter=1000,
shuffle=True)
clf.fit(X, y)
Perceptron(alpha=0.0001, class_weight=None, early_stopping=False, eta0=1.0,
fit_intercept=True, max_iter=1000, n_iter_no_change=5, n_jobs=None,
penalty=None, random_state=0, shuffle=True, tol=0.001,
validation_fraction=0.1, verbose=0, warm_start=False)
# Weights w assigned to the features. w
print(clf.coef_)
[[ 23.2 -38.7]]
# 截距 Constants b in decision function.
print(clf.intercept_)
[-5.]
# 画布大小
plt.figure(figsize=(10,10))
# 中文标题
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.title('鸢尾花线性数据示例')
plt.scatter(data[:50, 0], data[:50, 1], c='b', label='Iris-setosa',)
plt.scatter(data[50:100, 0], data[50:100, 1], c='orange', label='Iris-versicolor')
# 画感知机的线
x_ponits = np.arange(4, 8)
y_ = -(clf.coef_[0][0]*x_ponits + clf.intercept_)/clf.coef_[0][1]
plt.plot(x_ponits, y_)
# 其他部分
plt.legend() # 显示图例
plt.grid(False) # 不显示网格
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()
注意 !
在上图中,有一个位于左下角的蓝点没有被正确分类,这是因为 SKlearn 的 Perceptron 实例中有一个tol
参数。
tol
参数规定了如果本次迭代的损失和上次迭代的损失之差小于一个特定值时,停止迭代。所以我们需要设置 tol=None
使之可以继续迭代:
clf = Perceptron(fit_intercept=True,
max_iter=1000,
tol=None,
shuffle=True)
clf.fit(X, y)
# 画布大小
plt.figure(figsize=(10,10))
# 中文标题
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.title('鸢尾花线性数据示例')
plt.scatter(data[:50, 0], data[:50, 1], c='b', label='Iris-setosa',)
plt.scatter(data[50:100, 0], data[50:100, 1], c='orange', label='Iris-versicolor')
# 画感知机的线
x_ponits = np.arange(4, 8)
y_ = -(clf.coef_[0][0]*x_ponits + clf.intercept_)/clf.coef_[0][1]
plt.plot(x_ponits, y_)
# 其他部分
plt.legend() # 显示图例
plt.grid(False) # 不显示网格
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()
现在可以看到,所有的两种鸢尾花都被正确分类了。
参考代码:https://github.com/wzyonggege/statistical-learning-method