机器学习笔记
第一章 机器学习简介
第二章 感知机
第三章 支持向量机
第四章 朴素贝叶斯分类器
第五章 Logistic回归
第六章 线性回归和岭回归
第七章 多层感知机与反向传播【MLP】
第八章 主成分分析【PCA降维】
第九章 隐马尔可夫模型
第十章 奇异值分解
文章目录
多层感知机是一种基于人工神经元网络的模型,它通过多个层次的神经元组成的网络结构来进行学习和推理。而反向传播算法则是训练多层感知机模型的核心算法之一,它通过迭代优化模型参数来最小化损失函数,从而使得模型能够更好地拟合训练数据。
在本篇博客中,我们将深入探讨多层感知机与反向传播算法的原理和应用。首先,我们将介绍多层感知机的基本原理和结构,包括神经元的组织方式、各层之间的连接方式以及激活函数的作用。随后,我们将详细解释反向传播算法的原理和实现细节,包括正向传播和反向传播的过程,以及如何使用梯度下降方法来更新模型参数。最后,我们将通过一个Python实例来展示如何使用MLP来识别手写数字,从而加深对多层感知机和反向传播算法的理解。
一、多层感知机
1 神经元
回顾感知机、线性可分支持向量机、Logistic回归模型,这些模型都涉及到 x = ( x ( 1 ) , x ( 2 ) , … , x ( n ) ) T x=(x^{(1)},x^{(2)},\ldots,x^{(n)})^T x=(x(1),x(2),…,x(n))T各维特征的线性组合 w ⋅ x + b = ∑ i = 1 n w i x ( i ) + b , w\cdot x+b=\sum_{i=1}^nw_ix^{(i)}+b, w⋅x+b=i=1∑nwix(i)+b,这里 w = ( w 1 , w 2 , ⋯ , w n ) T ∈ R n w=(w_1,w_2,\cdots,w_n)^T\in\mathbb{R}^n w=(w1,w2,⋯,wn)T∈Rn且 b ∈ R b\in\mathbb{R} b∈R为参数,我们记这个输出为 z = w ⋅ x + b z= w\cdot x+b z=w⋅x+b.
- 感知机在线性输出后,加上符号函数,得到最后的输出 f ( x ) = sign ( z ) f(x)=\operatorname{sign}(z) f(x)=sign(z);
- 线性可分支持向量机也是机上符号函数;
- Logistic回归是引入sigmoid函数,输出 σ ( z ) \sigma(z) σ(z).
在感知机部分,我们也指出了感知机可以看做单个“神经元”,这里我们给出一般神经元的定义:
人工神经元(Artificial Neuron),简称神经元(Neuron),是构成神经网络的基本单元,其主要是模拟生物神经元的结构和特性,接收一组输入信号并产生输出.
其结构为:
这样来看,感知机、线性可分支持向量机、Logistic回归模型都可以看做一个神经元,只不过激活函数不同,优化算法不同。
2 激活函数
激活函数在神经元中非常重要的. 为了增强网络的表示能力和学习能力,激活函数需要具备以下几点性质:
- 连续并可导(允许少数点上不可导)的非线性函数.
- 可导的激活函数可以直接利用数值优化的方法来学习网络参数.
- 激活函数及其导函数要尽可能的简单,有利于提高网络计算效率.
- 激活函数的导函数的值域要在一个合适的区间内,不能太大也不能太小,否则会影响训练的效率和稳定性.
为什么要在每个线性输出后加上激活函数呢?因为激活函数是非线性函数,可以给线性网络加入非线性部分,使得网络更复杂,可表示能力更强!常见的激活函数有:线性激活函数、阈值或阶跃激活函数、S形激活函数等。感知机是用sign函数将输出值映射到1,-1。但sign函数不是处处连续,求导会出问题,神经网络中常用sigmoid函数,这个函数连续且导数性质很好。sigmoid函数如下:
σ
(
x
)
=
1
1
+
e
−
x
,
\sigma(x)=\frac{1}{1+e^{-x}},
σ(x)=1+e−x1,其导数为:
d
σ
(
x
)
d
x
=
σ
(
x
)
(
1
−
σ
(
x
)
)
.
\frac{d\sigma(x)}{dx}=\sigma(x)(1-\sigma(x)).
dxdσ(x)=σ(x)(1−σ(x)).
3 前馈神经网络
给定一组神经元,我们可以将神经元作为节点来构建一个网络.不同的神经网络模型有着不同网络连接的拓扑结构.一种比较直接的拓扑结构是前馈网络.前馈神经网络(Feedforward Neural Network,FNN)是最早发明的简单人工神经网络.
- 前馈神经网络也经常称为多层感知器(Multi-Layer Perceptron,MLP).
- 但多层感知器的叫法并不是十分合理,因为前馈神经网络其实是由多层的 Logistic 回归模型(sigmoid函数,连续的非线性函数)组成,而不是由多层的感知器(符号函数,不连续的非线性函数)组成。
给定训练数据集 D = { ( x i , y i ) } i = 1 N D=\{(x_i,y_i)\}_{i=1}^N D={(xi,yi)}i=1N, 其中 x i ∈ R n , y i ∈ R k x_i\in\mathbb{R}^n,\quad y_i\in\mathbb{R}^k xi∈Rn,yi∈Rk。假定我们构造如上图所示的单隐层前馈神经网络,其中
- 输入层有n 个神经元分别用于接受数据 x x x的 n n n维特征向量 ( x ( 1 ) , x ( 2 ) , … , x ( n ) ) T (x^{(1)},x^{(2)},\ldots,x^{(n)})^T (x(1),x(2),…,x(n))T 的每个分量;
- 输出层有 k k k 个功能神经元分别对应于输出 y = ( y ( 1 ) , y ( 2 ) , ⋯ , y ( k ) ) T y=(y^{(1)},y^{(2)},\cdots,y^{(k)})^T y=(y(1),y(2),⋯,y(k))T 的每个分量,且输出层第1个神经元的阈值为 θ l ; \theta_l; θl;
- 隐层有 m m m 个功能神经元,且隐层第 t t t个神经元的阈值为 γ t ; \gamma_t; γt;
- 输入层第 j j j 个神经元与隐层第 t t t个神经元之间的连接权值为 w j t ; w_{jt}; wjt;
- 隐层第 t t t个神经元与输出层第1个神经元之间的连接权值为 v t l ; v_{tl}; vtl;
隐层第
t
t
t个神经元接受到的总输入为
α
t
(
x
)
=
∑
i
=
1
n
w
j
t
x
(
j
)
\alpha_t(x)=\sum_{i=1}^nw_{jt}x^{(j)}
αt(x)=∑i=1nwjtx(j),输出为
z
(
t
)
(
x
)
=
σ
(
α
t
(
x
)
−
γ
t
)
;
z^{(t)}(x)=\sigma(\alpha_t(x)-\gamma_t);
z(t)(x)=σ(αt(x)−γt);输出层第
l
l
l个神经元接受到的总输入为
β
l
(
x
)
=
∑
t
=
1
m
v
t
l
z
(
t
)
(
x
)
\beta_l(x)=\sum_{t=1}^mv_{tl}z^{(t)}(x)
βl(x)=∑t=1mvtlz(t)(x),输出为
y ( l ) = σ ( β l ( x ) − θ l ) . y^{(l)}=\sigma(\beta_l(x)-\theta_l). y(l)=σ(βl(x)−θl).我们以 Θ \Theta Θ表示该单隐层前馈网络中的参数集,即
Θ
=
(
⋃
t
=
1
m
{
w
j
t
}
j
=
1
n
)
∪
(
⋃
l
=
1
k
{
v
t
l
}
t
=
1
m
)
∪
{
θ
l
}
l
=
1
k
∪
{
γ
t
}
t
=
1
m
,
\Theta=(\bigcup_{t=1}^m\{w_{jt}\}_{j=1}^n)\cup(\bigcup_{l=1}^k\{v_{tl}\}_{t=1}^m)\cup\{\theta_l\}_{l=1}^k\cup\{\gamma_t\}_{t=1}^m,
Θ=(t=1⋃m{wjt}j=1n)∪(l=1⋃k{vtl}t=1m)∪{θl}l=1k∪{γt}t=1m,
其中共包含
(
n
+
k
+
1
)
m
+
k
(n+k+1)m+k
(n+k+1)m+k个参数.按照单隐层神经网络的构造,可以不断的增加隐层,构成多层前馈神经网络,数据一层一层向前传递。那么问题是,这一层一层的参数怎么学习呢?求导的链式法则!
二、反向传播
反向传播算法,通常缩写为「BackProp」,是几种训练人工神经网络的方法之一。这是一种监督学习方法,即通过标记的训练数据来学习(有监督者来引导学习)。简单说来,BackProp 就像“从错误中学习”。监督者在人工神经网络犯错误时进行纠正。其主要思想是:
- 将训练集数据输入到ANN的输入层,经过隐藏层,最后达到输出层并输出结果;(前向传播)
- 由于ANN的输出结果与实际结果有误差,则计算估计值与实际值之间的误差,并将该误差从输出层向隐藏层反向传播,直至传播到输入层;(误差反向传播)
- 在反向传播的过程中,根据误差调整各种参数的值;(链式法则更新参数)
- 不断迭代上述过程,直至收敛。
1 单个节点
如上图所示,激活函数我们采用 σ ( x ) \sigma(x) σ(x)函数,我们假设:
- 输入样本为 ( x ⃗ d , t d ) d ∈ D (\vec x_d,t_d)_{d\in D} (xd,td)d∈D,其中输入 x d ⃗ = ( x 1 , x 2 , ⋯ , x n ) \vec{x_d}=( x_1,x_2,\cdots ,x_n) xd=(x1,x2,⋯,xn); t d t_d td标签(已知输出);
- 权重为 w i w_i wi ,而 n e t d = ∑ i w i x i net_d=\sum\limits_{i} w_ix_i netd=i∑wixi, o d = σ ( n e t d ) o_d=\sigma(net_d) od=σ(netd), o d o_d od为单个sigmoid结点的输出;
- 损失函数我们取平方误差, E ( w ) = 1 2 ∑ ( f w ( x d ) − t d ) 2 E(w)=\frac{1}{2}\sum (f_w(x_d)-t_d)^2 E(w)=21∑(fw(xd)−td)2.
所以问题就转化为找到
w
w
w,使得损失函数最小,而这里可得
E
(
w
)
=
1
2
∑
(
t
d
−
o
d
)
2
E(w)=\frac{1}{2}\sum (t_d-o_d)^2
E(w)=21∑(td−od)2我们想用梯度法来进行优化,对
w
w
w求导,则由链式法则可得:
∂
E
∂
w
i
=
∂
E
∂
o
d
⋅
∂
o
d
∂
n
e
t
d
⋅
∂
n
e
t
d
∂
w
i
\frac{\partial E}{\partial w_i}=\frac{\partial E}{\partial o_d}\cdot \frac{\partial o_d}{\partial net_d}\cdot \frac{\partial net_d}{\partial w_i}
∂wi∂E=∂od∂E⋅∂netd∂od⋅∂wi∂netd而由求导法则和sigmoid函数的导数可得:
∂
E
∂
o
d
=
−
(
t
d
−
o
d
)
∂
o
d
∂
n
e
t
d
=
o
d
(
1
−
o
d
)
∂
n
e
t
d
∂
w
i
=
∂
w
⋅
x
d
∂
w
i
=
x
i
\frac{\partial E}{\partial o_d}=-(t_d-o_d)\qquad \frac{\partial o_d}{\partial net_d}=o_d(1-o_d)\qquad \frac{\partial net_d}{\partial w_i} =\frac{\partial w\cdot x_d}{\partial w_i}=x_i
∂od∂E=−(td−od)∂netd∂od=od(1−od)∂wi∂netd=∂wi∂w⋅xd=xi故可得:
∂
E
∂
w
i
=
−
(
t
d
−
o
d
)
o
d
(
1
−
o
d
)
x
i
\frac{\partial E}{\partial w_i}=-(t_d-o_d)o_d(1-o_d)x_i
∂wi∂E=−(td−od)od(1−od)xi记
δ
d
=
(
t
d
−
o
d
)
o
d
(
1
−
o
d
)
\delta_d=(t_d-o_d)o_d(1-o_d)
δd=(td−od)od(1−od),则:
∂
E
∂
w
i
=
−
δ
d
x
i
\frac{\partial E}{\partial w_i}=-\delta_d x_i
∂wi∂E=−δdxi设学习率为
η
\eta
η,则可得到更新规则:
w
i
,
n
−
η
∂
E
∂
w
i
,
n
→
w
i
,
n
+
1
w_{i,n}-\eta \frac{\partial E}{\partial w_{i,n}} \to w_{i,n+1}
wi,n−η∂wi,n∂E→wi,n+1于是,我们得到了单层结点参数的更新规则,下面将单层推广到多层。
2 由单层到多层结点
清楚单个sigmoid结点的性质后,便可将其推广到多层结点,通过前向传播得到输出后,便可得到误差,然后可得到最后一层结点权重的更新值,得到最后一层w更新值后,便可继续反向得到倒数第二层,以此类推反向传播便可更新所有层的参数。下面以一个具体的两层结点为例:
这里以计算单隐层中
∂
E
∂
w
1
\frac{\partial E}{\partial w_1}
∂w1∂E为例,输出有两个结点,故可得:
E
t
o
t
a
l
=
E
o
1
+
E
o
2
⇒
∂
E
t
o
t
a
l
∂
w
1
=
∂
E
o
1
∂
w
1
+
∂
E
o
2
∂
w
1
E_{total}=E_{o1}+E_{o2} \quad \Rightarrow \quad \frac{\partial E_{total}}{\partial w_1}=\frac{\partial E_{o1}}{\partial w_1}+\frac{\partial E_{o2}}{\partial w_1}
Etotal=Eo1+Eo2⇒∂w1∂Etotal=∂w1∂Eo1+∂w1∂Eo2进一步可得:
∂
E
o
1
∂
w
1
=
∂
E
o
1
∂
o
u
t
o
1
⋅
∂
o
u
t
o
1
∂
n
e
t
o
1
⋅
∂
n
e
t
o
1
∂
o
u
t
h
1
⋅
∂
o
u
t
h
1
∂
n
e
t
h
1
⋅
∂
n
e
t
h
1
∂
w
1
\frac{\partial E_{o1}}{\partial w_1}= \frac{\partial E_{o1}}{\partial out_{o1}}\cdot \frac{\partial out_{o1}}{\partial net_{o1}}\cdot \frac{\partial net_{o1}}{\partial out_{h1}}\cdot \frac{\partial out_{h1}}{\partial net_{h1}}\cdot \frac{\partial net_{h1}}{\partial w_1}
∂w1∂Eo1=∂outo1∂Eo1⋅∂neto1∂outo1⋅∂outh1∂neto1⋅∂neth1∂outh1⋅∂w1∂neth1同理可得
∂
E
o
2
∂
w
1
\frac{\partial E_{o2}}{\partial w_1}
∂w1∂Eo2,进而可得:
∂
E
t
o
t
a
l
∂
w
1
=
(
∑
o
∂
E
t
o
t
a
l
∂
o
u
t
o
⋅
∂
o
u
t
o
∂
n
e
t
o
⋅
∂
n
e
t
o
∂
o
u
t
h
1
)
⋅
∂
o
u
t
h
1
∂
n
e
t
h
1
⋅
∂
n
e
t
h
1
∂
w
1
\frac{\partial E_{total}}{\partial w_1}= (\sum\limits_{o}\frac{\partial E_{total}}{\partial out_{o}}\cdot\frac{\partial out_{o}}{\partial net_{o}}\cdot \frac{\partial net_{o}}{\partial out_{h1}})\cdot \frac{\partial out_{h1}}{\partial net_{h1}}\cdot \frac{\partial net_{h1}}{\partial w_1}
∂w1∂Etotal=(o∑∂outo∂Etotal⋅∂neto∂outo⋅∂outh1∂neto)⋅∂neth1∂outh1⋅∂w1∂neth1代入由单个结点计算的结论,可得:
∂
E
t
o
t
a
l
∂
w
1
=
(
∑
o
δ
o
⋅
w
h
o
)
⋅
o
u
t
h
1
(
1
−
o
u
t
h
1
)
⋅
i
1
\frac{\partial E_{total}}{\partial w_1}= (\sum\limits_{o}\delta_o\cdot w_{ho})\cdot out_{h1}(1-out_{h1})\cdot i_1
∂w1∂Etotal=(o∑δo⋅who)⋅outh1(1−outh1)⋅i1令
δ
h
1
=
(
∑
o
δ
o
⋅
w
h
o
)
⋅
o
u
t
h
1
(
1
−
o
u
t
h
1
)
\delta_{h1}=(\sum\limits_{o}\delta_o\cdot w_{ho})\cdot out_{h1}(1-out_{h1})
δh1=(o∑δo⋅who)⋅outh1(1−outh1),可得:
∂
E
t
o
t
a
l
∂
w
1
=
δ
h
1
⋅
i
1
\frac{\partial E_{total}}{\partial w_1}= \delta_{h1}\cdot i_1
∂w1∂Etotal=δh1⋅i1这便是反向传播单隐层的随机梯度下降法,得到倒数第二层,便可以依次反向更新所有的w,而更一般情形,写成矩阵的形式即可,可以看第3个参考资料。
三、Python实例
本节,我们手动实现一个多层感知机,数据集采用经典的手写数字(MINST)。
1 获取数据集
为了演示速度更快,我们只从中选取数字8和9进行建模训练。注意把数据集放在源文件同一目录下。
import numpy as np
import warnings
warnings.filterwarnings("ignore")
import matplotlib.pyplot as plt
from sklearn import preprocessing
f = np.load('./mnist.npz') #加载数据集
x_train, y_train = f['x_train'], f['y_train']
x_test, y_test = f['x_test'], f['y_test']
# 获取标签为8和9的数据,分别为num1和num2个
def get_small_data(x_data, y_data, num1, num2):
cnt1, cnt2, final_x, final_y = 0, 0, [], []
for i in range(x_data.shape[0]):
x = x_data[i, :].reshape(-1).tolist()
y = y_data[i]
if y == 8 and cnt1 < num1:
final_x.append(x)
final_y.append(0)
cnt1 += 1
elif y == 9 and cnt2 < num2:
final_x.append(x)
final_y.append(1)
cnt2 += 1
return np.array(final_x), np.array(final_y)
x_train, y_train = get_small_data(x_train, y_train, num1=250, num2=750)
x_test, y_test = get_small_data(x_test, y_test, num1=100, num2=100)
print(x_train.shape)
print(y_train.shape)
print(x_test.shape)
print(y_test.shape)
2 定义sigmoid函数
def sigmoid(x):
return 1/(1+np.exp(-x))
#导数
def sigmoid_D(x):
return sigmoid(x)*(1-sigmoid(x))
3 定义损失函数
# 平方损失函数
def loss_fn(y,y_pred):
return np.sum((y-y_pred)**2)/len(y)
#作图函数
def plot_loss_acc(Loss, acc,lr):
plt.rcParams['figure.dpi'] = 150
plt.figure(figsize=(20, 5))
plt.subplot(121)
plt.plot(Loss)
plt.title("Train Loss, lr={}".format(lr), fontsize=20)
plt.xlabel("Iteration", fontsize=15)
plt.subplot(122)
plt.plot(acc)
plt.title("Train Accuracy, lr={}".format(lr), fontsize=20)
plt.xlabel("Iteration", fontsize=15)
4 两层前馈网络
# 2层前馈网络,以sigmoid作为激活函数
def forward_pass(x_mat,y,W1,W2,b1,b2):
#前向传播
z1=x_mat @ W1+b1
a1=sigmoid(z1)
z2=a1 @ W2 +b2
a2=sigmoid(z2)
y_pred=a2
#反向传播,倒数第一层
grad_a2=-y+y_pred #L对a2的导数
grad_z2=grad_a2*sigmoid_D(z2) #L对z2的导数,也是对b2的导数
grad_w2=a1.T @ grad_z2 #L对W2的导数
#倒数第二层
grad_a1=grad_z2 @ W2.T #L对a1的导数
grad_z1=grad_a1 * sigmoid_D(z1) #L对z1的导数,也是对b1的导数
grad_w1=x_mat.T @ grad_z1
gradient=(grad_w1,grad_z1,grad_w2,grad_z2)
return y_pred,gradient
5 训练
hiden=60 # 隐层神经元个数
# 初始化权重
W1=np.random.uniform(-1,1,size=(784,hiden))
b1=np.random.uniform(-1,1,size=(1,hiden))
W2=np.random.uniform(-1,1,size=(hiden,1))
b2=np.random.uniform(-1,1,size=(1,1))
# 超参数
num_iter=30
lr=0.01
batchsize=200 # 设置批量大小
x_mat=preprocessing.scale(x_train,axis=1) # 数据标准化
y=y_train.reshape(-1,1)
loss_vals,accs=[],[]
for i in range(num_iter):
total_acc=0
total_loss=0
t=0
for j in range(0,len(x_mat),batchsize):
x_Train=x_mat[j:j+batchsize]
y_Train=y[j:j+batchsize]
y_pred,(grad_w1,grad_z1,grad_w2,grad_z2)=forward_pass(x_Train,y_Train,W1,W2,b1,b2)
#参数更新
W1=W1-lr*grad_w1
W2=W2-lr*grad_w2
b1-=-lr*grad_z1[0,:]
b2-=lr*grad_z2[0,:]
curr_loss=loss_fn(y_Train,y_pred)
total_loss+=curr_loss
total_acc+=np.sum((y_pred>=.5)==y_Train)/len(y_Train)
t+=1
now_acc=total_acc/t
accs.append(now_acc)
now_loss=total_loss/t
loss_vals.append(now_loss)
if (i%10==0):
print("{}|{},loss={},accuracy={}".format(
i,num_iter,curr_loss,now_acc
))
plot_loss_acc(loss_vals,accs,lr)
然后我们在测试集上进行测试,代码如下:
x_mat=preprocessing.scale(x_test)
y=y_test.reshape(-1,1)
y_pred,_=forward_pass(x_mat,y,W1,W2,b1,b2)
acc=np.sum((y_pred>=.5)==y)/len(y)
print(f"准确率:{acc*100} %")
最后在测试集上的准确率能达到86%!如果我们用三层前馈神经网络来拟合,在测试集上的准确率能达到90%以上。
四、参考资料
- 李航. 机器学习方法. 清华大学出版社, 2022.
- 邱锡鹏.神经网络与深度学习.机械工业出版社,2020.
- Henseler, J. (2005). Back propagation. Artificial Neural Networks: An Introduction to ANN Theory and Practice, 37-66.