目录
[5.1 神经元模型](#5.1 神经元模型)
[5.2 感知机与多层网络](#5.2 感知机与多层网络)
[5.3 误差逆传播算法](#5.3 误差逆传播算法)
[5.4 全局最小与局部极小](#5.4 全局最小与局部极小)
[5.5 其他常见神经网络](#5.5 其他常见神经网络)
[5.5.1 RBF 网络](#5.5.1 RBF 网络)
[5.5.2 ART网络](#5.5.2 ART网络)
[5.5.3 SOM网络](#5.5.3 SOM网络)
[5.5.4 级联相关网络](#5.5.4 级联相关网络)
[5.5.5 Elman网络](#5.5.5 Elman网络)
[5.6 深度学习](#5.6 深度学习)
5.1 神经元模型
神经网络是由具有适应性简单单元组成的广泛并行互连的网络,它的组织能够模拟生物神经系统对真实世界物体所作出的交互反应,神经网络中最基本的成分是神经元模型,以下为第一个神经元数学模型------M-P神经元模型。
在这个模型中,神经元接收到来自n nn个其他神经元传递过来的输入信号,这些输入信号通过带权重的连接进行传递,神经元接收到的总输入值将与神经元的阈值进行比较,然后通过"激活函数"处理以产生神经元的输出,理想中的激活函数如下图a所示的阶跃函数,它将输入值映射为输出值"0"或"1",其中"1"对应于神经元兴奋,"0"对应于神经元抑制。
5.2 感知机与多层网络
感知机是神经网络最基本的模型,它是由一个或多个神经元组成的,而神经网络是由多个感知机或其他类型的神经元连接而成的复杂模型,它可以实现非线性分类和回归等功能,比如下面就是一个有两个输入神经元的感知机网络结构,可以实现与、或、非运算。
更一般的,给定训练数据集,权重以及阈值可以通过学习得到,而阈值可以看作固定输入为-1的权重,这样就可以统一为权重的学习,对于感知机来说,学习规则如下所示,假设当前感知机的输出为$\mathop y\limits^ \wedge $,感知机权重将这样调整:
\begin{aligned}&w_{i}\leftarrow w_{i}+\Delta w_{i}\\&\Delta w_{i}=\eta(y-\overset{\wedge}{\operatorname*{y}})x_{i}\end{aligned}
其中称为学习率,若感知机预测正确,则感知机不发生变化,否则将根据错误的程度进行权重调整,这也是感知机学习算法,是一种基于误分类的监督学习算法,目标是找到一个能够将训练数据正确分类的超平面。
感知机选择将预测值与实际值作差的形式来进行权重更新的依据是为了使超平面向正确分类的方向移动。当一个实例被误分类时,预测值与实际值的差就是误差的符号,它表示了超平面与实例之间的距离和方向,此时将误差乘以输入向量,就可以得到一个调整量,它表示了超平面在每个特征维度上需要移动的大小和方向,而将调整量加到权重上,就可以使超平面向正确分类的方向移动一定的距离。这样,经过多次迭代,超平面就可以逐渐接近最优的位置了。
上述问题都是线性可分问题,而这样的感知机无法解决非线性可分问题,要解决非线性可分问题,需要考虑使用多层功能神经元。如下所示的两层感知机,就可以解决异或问题,而在输出层与输入层之间的一层神经元则被称为隐层或隐含层,也是具有激活函数的功能神经元。
5.3 误差逆传播算法
多层网络的学习能力比单层感知器强得多,如果想训练多层网络,上述简单感知器的学习规则显然就不够了,需要更强大的学习算法,下面来介绍一下BP算法,BP算法是一种用于训练多层神经网络的梯度下降算法,它利用链式法则计算网络每层的权重对损失函数的梯度,然后更新权重来最小化损失函数。BP算法分为两个阶段:激励传播和权重更新。激励传播阶段包括前向传播和反向传播,前向传播是将训练输入送入网络以获得预测结果,反向传播是计算预测结果与训练目标的误差。权重更新阶段是根据误差和梯度调整网络的权重,以减小误差。
下面对BP算法中的一些符号进行定义,以一个拥有个输入神经元、个输出神经元、个隐层神经元的多层前馈网络结构为例,假设隐层和输出层神经元都是用Sigmoid函数作为激活函数。
- D:训练集,即输入示例由d个属性描述,输出l维实值向量
- :输出层第j个神经元的阈值
- :隐层第h个神经元的阈值
- :输入层第i个神经元与隐层第h个神经元之间的连接权重:隐层第h个神经元与输出层第j个神经元之间的连接权重
:隐层第h个神经元接收到的输入
:输出层第j个神经元接收到的输入,其中为隐层第h个神经元的输出
对于训练例,假定神经网络的输出为,即,则网络在上的均方误差为:
下面以隐层到输出层的连接权重为例来进行权重更新的推导,对于误差,给定学习率,有:
Sigmoid函数有一个很好的性质:,所以可以定义如下:
将上述代入之前的式子即可得到权重的更新公式:,类似也可以得到其他权重的更新公式,如下所示:
下面给出BP算法的工作流程,对于每个训练样例,BP算法执行以下操作:先将输入示例提供给输入层神经元,然后逐层将信号前传,直到产生输出层的结果;然后计算输出层的误差,再将误差逆向传播至隐层神经元,最后根据隐层神经元的误差来对连接权重和阈值进行调整。该迭代过程循环进行,直到达到某些停止条件为止,比如训练误差已达到一个很小的值。
BP算法的目标是要最小化训练集D上的累积误差,在这里要注意虽然BP算法里面有反向传播这一步,但这与反馈神经网络中的反馈是不同的,BP算法里面的反向传播是指根据网络的输出误差,从输出层到输入层逐层计算并更新权重参数的过程,而反馈神经网络中的反馈是指网络的输出会经过一定的变换后,再作为输入传回到网络中,形成一个循环或回路。
下面介绍一下两种BP算法:标准BP算法与累积BP算法。
标准BP算法每次只针对一个训练样例更新参数,也就是随机梯度下降法(stochastic gradient descent),优点是参数更新频繁,可以加快收敛速度和避免局部最优,缺点是参数更新不一致,可能导致震荡和偏离最优方向。累积BP算法每次针对所有训练样例更新参数,也就是批量梯度下降法(batch gradient descent),优点是参数更新稳定,可以减少噪声和波动,缺点是参数更新缓慢,可能导致陷入局部最优或过拟合。比如,假设有n个训练样例,分别使用标准BP和累积BP,那么参数更新的次数取决于迭代轮数,假设迭代轮数为m,那么:
- 标准BP算法每轮迭代会对每个训练样例进行一次参数更新,所以总共会进行m*n次参数更新。
- 累积BP算法每轮迭代会对所有训练样例进行一次参数更新,所以总共会进行m次参数更新。
为了缓解BP网络的过拟合,一共有两种策略-早停和正则化。早停是将数据集分成训练集和验证集,训练集用来计算梯度、更新连接权重和阈值,验证集用来估计误差,若训练集误差降低但验证集误差升高,则停止训练,同时返回具有最小验证集误差的连接权重和阈值。正则化是在误差目标函数中增加一个用于描述网络复杂度的部分,比如连接权重与阈值的平方和。
5.4 全局最小与局部极小
在神经网络的训练过程中,我们往往会谈到两种最优:局部极小与全局最小。显然,参数空间内梯度为零的点,只要其误差函数值小于邻点的误差函数值,就是局部极小点;可能存在多个局部极小值,但是却只会有一个全局最小值。
基于梯度的搜索时使用最为广泛的参数寻优方法,梯度下降法是沿着负梯度方向搜索最优解,因为负梯度方向是函数在当前点的方向导数最小的方向,方向导数是函数沿着某个方向的变化率,它与函数的梯度和该方向的单位向量的点积相等,当两个向量的夹角为180度时,点积最小,也就是说,当单位向量与梯度的反方向一致时,方向导数最小。因此,沿着负梯度方向走,函数值下降最快。
在现实任务中,人们常采用以下策略来试图"四t 出"局部极小,从而进一
步接近全局最小:
- 以多组不同参数值初始化多个神经网络7 按标准方法训练后,取其中误差最小的解作为最终参数.这相当于从多个不同的初始点开始搜索, 这样就可能陷入不同的局部极小从中进行选择有可能获得更接近全局最小的结果
- 使用"模拟*火" (simulated annealing) 技术[Aarts and Korst, 1989].模拟退火在每一步都以二定的概率接受比当前解更差的结果,从而有助于"跳出"局部极小. 在每步i主代过程中7 接受"次优解"的概率要随着时间的推移而逐渐降低7 从而保证算法稳定
- 使用随机梯度下降.与标准梯度下降法精确计算梯度不同, 随机梯度下降法在计算梯度时加入了随机因素.于是?即便陷入局部极小点? 它计算出的梯度仍可能不为零3 这样就有机会跳出局部极小继续搜索.
5.5 其他常见神经网络
5.5.1 RBF 网络
RBF网络是一种单隐层前馈神经网络,它使用径向基函数作为隐层神经元的激活函数,而输出层则是对隐层神经元输出的线性组合。
5.5.2 ART网络
竞争型学习是神经网络中一种常用的无监督学习策略,在使用该策略时,网络的输出神经元相互竞争,每一时刻仅有一个竞争获胜的神经元被激活,其他神经元的状态被抑制。而ART网络是竞争型学习的重要代表,该网络由比较层、识别层、识别阈值和重置模块组成。
在接收到比较层的输入信号后,识别层神经元之间相互竞争以产生获胜神经元,竞争的最简单方式是,计算输入向量与每个识别层神经元所对应的模式类的代表向量之间的距离,距离最小者胜。获胜神经元将向其他识别层神经元发送信号,抑制其激活。若输入向量与获胜神经元所对应的代表向量之间的相似度大于识别阈值,则当前输入样本将被归为该代表向量所属类别,同时,网络连接权重将会更新,使得以后在接收到相似输入样本时该模式类会计算出更大的相似度,从而使该获胜神经元有更大可能获胜;若相似度不大于识别阈值,则重置模块将在识别层增设一个新的神经元,其代表向量就设置为当前输入向量。
5.5.3 SOM网络
SOM网络是一种竞争学习型的无监督神经网络,它能将高维输入数据映射到低维空间,同时保持输入数据在高维空间的拓扑结构,即将高维空间中相似的样本点映射到网络输出层中的邻近神经元。
如下图所示,SOM网络中的输出层神经元以矩阵方式排列在二维空间中,每个神经元都拥有一个权向量,网络在接收输入向量后,将会确定输出层获胜神经元,它决定了该输入向量在低维空间中的位置。SOM的训练目标就是为每个输出层神经元找到合适的权向量,以达到保持拓扑结构的目的。
5.5.4 级联相关网络
一般的神经网络模型通常假定网络结构是事先固定的,训练的目的是利用训练样本来确定合适的连接权重、阈值等参数。与此不同,结构自适应网络则将网络结构也当作学习的目标之一,并希望能在训练过程中找到最符合数据特点的网络结构,级联相关网络是结构自适应网络的重要代表。
级联相关网络有两个主要成分:"级联"和"相关"。级联是指建立层次连接的层级结构,相关是指通过最大化新神经元的输出与网络误差之间的相关性来训练相关的参数。
5.5.5 Elman网络
Elman网络是最常用的递归神经网络之一,如下所示,它的结构与多层前馈网络很相似,但隐层神经元的输出被反馈回来,与下一时刻输入层神经元提供的信号一起,作为隐层神经元在下一时刻的输入。隐层神经元通常采用Sigmoid激活函数,而网络的训练则常通过推广的BP算法进行。
5.6 深度学习
典型的深度学习模型就是很深层的神经网络,深度学习有很多应用场景,例如:
- 语音识别:深度学习可以用于语音识别、语音合成等任务,例如谷歌的语音助手、苹果的Siri等。
- 图像识别:深度学习在图像分类、目标检测、图像语义分割等计算机视觉任务中取得了显著成果,例如谷歌的人脸识别、腾讯的美颜相机等。
- 自然语言处理:深度学习可以用于文本生成、机器翻译、情感分析等自然语言处理任务,例如微软的小冰、百度的翻译等。
- 游戏:深度学习可以用于强化学习,让机器自主地学习和优化策略,例如谷歌的AlphaGo、OpenAI的Dota 2机器人等。
- 医疗:深度学习可以用于医疗图像分析、疾病诊断、药物发现等医疗领域,例如IBM的Watson、阿里的ET医疗大脑等。
- 艺术:深度学习可以用于图像风格迁移、图像生成、音乐生成等艺术领域,例如谷歌的Deep Dream、Prisma等。
以下是卷积神经网络用于手写数字识别的一个例子。
代码:
# 复现神经网络
import time
import numpy as np
from matplotlib import pyplot as plt
from numpy import dot
from numpy.linalg import inv
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
from sklearn.preprocessing import MinMaxScaler
from numpy.random import uniform
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 糖尿病数据集
from sklearn.datasets import load_diabetes
def load_data():
diabetes = load_diabetes()
X = diabetes.data # data
y = diabetes.target # label
# 对所有数据按列归一化
scalar = MinMaxScaler()
for i in range(X.shape[1]):
X[:, i] = scalar.fit_transform(X[:, i].reshape(-1, 1)).reshape(-1)
# print(len(y.shape),y.shape)
if len(y.shape) == 1:
y = y.reshape(-1, 1)
for i in range(y.shape[1]):
y[:, i] = scalar.fit_transform(y[:, i].reshape(-1, 1)).reshape(-1)
# print(X[:5])
# print(y[:5])
return X, y
class MyNet():
# sigmoid
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
# 损失函数
def loss_func(self, output, label): # 输入是列向量
return 1 / 2 * (output - label).T @ (output - label)
def fit(self, X, Y, epochs, alpha=0.01, layers=2, layer_size=3): # 中间的隐藏层层数和隐藏层的大小
if layers < 1:
assert "隐藏层至少为1,当前layers=%d" % (layers)
if len(Y.shape) == 1: # 一维
Y = Y.reshape(-1, 1)
in_features = X.shape[1]
out_features = Y.shape[1]
samples = X.shape[0]
print('in_features:', in_features)
print('out_features:', out_features)
print('samples:', samples)
# 矩阵初始化(随机初始化)
w_first = uniform(0,1,(in_features,layer_size)) # 每一行是一个输入神经元对所有下一层神经元的权值
w_last = uniform(0,1,(layer_size,out_features)) # 每一行是一个输入神经元对所有下一层神经元的权值
self.w=None # 防止后续报错
if layer_size > 1: # 刚好等于1就不需要了
w = uniform(0,1,(layers-1,layer_size,layer_size))
b_last = uniform(0,1,(out_features, 1)) # # 每一列是一层的系数b
b = uniform(0,1,(layers,layer_size,1)) # 隐藏层的
# 运行过程中的变量
delta = np.zeros((layers, layer_size, 1), dtype=float)
delta_last = np.zeros((out_features, 1), dtype=float) # 每一列是一层的δ
out_last = np.zeros((out_features, 1), dtype=float) # 每一列是一层的output
out = np.zeros((layers, layer_size, 1), dtype=float)
# 开始迭代
loss_list = []
for epoch in range(epochs):
loss = 0
for idx in range(samples):
x = X[idx].reshape(-1, 1)
y = Y[idx].reshape(-1, 1)
# 前向传播
for layer in range(layers + 1): # 0其实就对应了0和1层的w和1层的偏置
if layer == 0: # 第一层
out[layer] = self.sigmoid(w_first.T @ x + b[layer])
elif layer < layers: # 不是最后一层
# print('w[%d]'%(layer-1))
out[layer] = self.sigmoid(w[layer - 1].T @ out[layer - 1] + b[layer])
else: # 最后一层
out_last = self.sigmoid(w_last.T @ out[layer - 1] + b_last)
# 反向传播
for layer in range(layers, -1, -1): # layers,...,0
# 计算出每一层的损失
if layer == layers: # 最后一层(输出层)
# print('输出层')
delta_last = out_last * (1 - out_last) * (out_last - y) # 最后一层隐藏层
elif layer == layers - 1: # 隐藏层最后一层,连接输出层
# print('最后一层隐藏')
delta[layer] = out[layer] * (1 - out[layer]) * (w_last @ delta_last)
else: # 最后一层
# print('隐藏')
delta[layer] = out[layer] * (1 - out[layer]) * (w[layer] @ delta[layer + 1])
# 更新系数
for layer in range(layers + 1):
# print(layer)
if layer == 0: # 输入-隐藏
# print('输入-隐藏')
det_w = x @ delta[layer].T
w_first = w_first - alpha * det_w # # O_{Layer-1,i}δ_{Layer,j}
b[layer] = b[layer] - alpha * delta[layer] # δ_{Layer,j}
elif layer < layers: # 隐藏-隐藏
# print('隐藏-隐藏')
det_w = out[layer - 1] @ delta[layer].T
w[layer - 1] = w[layer - 1] - alpha * det_w
b[layer] = b[layer] - alpha * delta[layer]
else: # 隐藏-输出
# print('隐藏-输出')
det_w = out[layer - 1] @ delta_last.T
w_last = w_last - alpha * det_w
b_last = b_last - alpha * delta_last
this_loss = self.loss_func(out_last, y)
# print('this_loss',this_loss,' out=',out_last,' y=',y)
loss += this_loss
loss_list.append(loss[0][0]/samples)
print('epoch %d loss:%.6f' % (epoch + 1, loss / samples))
plt.plot(loss_list)
plt.show()
# 保存参数
self.w_first = w_first
self.w_last = w_last
self.w = w
self.b_last = b_last
self.b = b
self.in_features = in_features
self.out_features = out_features
self.layers = layers
self.layer_size = layer_size
def predict(self, X):
in_features = self.in_features
if X.shape[1] != in_features:
assert "输入维度不正确,应为%d×1" % in_features
w_first = self.w_first
w_last = self.w_last
w = self.w
b_last = self.b_last
b = self.b
layers = self.layers
out_features = self.out_features
layer_size = self.layer_size
samples = X.shape[0] # 数量
ans = np.zeros((samples, out_features))
out_last = np.zeros((out_features, 1), dtype=float) # 每一列是一层的output
out = np.zeros((layers, layer_size, 1), dtype=float)
for i in range(samples):
x = X[i].reshape(-1, 1)
# 前向传播
for layer in range(layers + 1): # 0其实就对应了0和1层的w和1层的偏置
if layer == 0: # 第一层
out[layer] = self.sigmoid(w_first.T @ x + b[layer])
elif layer < layers: # 不是最后一层
out[layer] = self.sigmoid(w[layer - 1].T @ out[layer - 1] + b[layer])
else: # 最后一层
out_last = self.sigmoid(w_last.T @ out[layer - 1] + b_last)
ans[i] = out_last.reshape(-1)
return ans
if __name__ == '__main__':
X, y = load_data() # 获取归一化后的数据
myNet = MyNet()
myNet.fit(X, y, epochs=3000, alpha=5, layers=2, layer_size=10)
pred=myNet.predict(X)
for i in range(y.shape[0]):
print('true=',y[i],'pred=',pred[i])
print('r^2=%.2f'%(r2_score(y,pred)))
plt.plot(y[:,0])
plt.plot(pred[:,0])
plt.show()