一、神经网络
(1)人工神经网络
人工神经网络(ANN),简称神经网络(NN),是一种模仿生物神经网络结构和功能的计算模型。
神经网络:
神经网络中信息只向一个方向移动,即从输入节点向前移动,通过隐藏节点,再向输出节点移动。
神经网络的构成:
输入层:即输入x的那一层
隐藏层:即输入层和输出层之间的都是隐藏层
输出层:即输入y的那一层
特点:
1.同一层神经元之间没有连接
2.第N层的每个神经元和第N-1层的所有神经元相连(full connected),这就是全连接神经网络。
3.第N-1层神经元的输出就是第N层神经元的输入
4.每一个连接都有一个权重值(w系数和b系数)
深度学习和机器学习的关系:
深度学习是机器学习的一种特殊方法,机器学习 = 传统机器学习(有特征工程的操作)+ 深度学习(无特征工程的操作)
(2)激活函数(重点)
激活函数用于对每层的输出数据进行变换,进而为整个网络注入了非线性因素。此时,神经网络就可以拟合各种曲线。
1.没有引入非线性因素的网络等价于使用一个线性模型来拟合。
2.通过给网络输出增加激活函数,实现引入非线性因素,使得网络模型可以更加的逼近任意函数,提升网络模型对复杂问题的拟合能力。
1.sigmoid激活函数
import torch import matplotlib.pyplot as plt # 创建画布和坐标轴 _, axes = plt.subplots(1, 2) # 函数图像 x = torch.linspace(-20, 20, 1000) # 输入值x通过sigmoid函数转换成激活值y y = torch.sigmoid(x) axes[0].plot(x, y) axes[0].grid() axes[0].set_title('Sigmoid 函数图像') # 导数图像 x = torch.linspace(-20, 20, 1000, requires_grad=True) torch.sigmoid(x).sum().backward() # x.detach():输入值x的数值 # x.grad:计算梯度,求导 axes[1].plot(x.detach(), x.grad) axes[1].grid() axes[1].set_title('Sigmoid 导数图像') plt.show()
- 原函数值域范围:[0,1]
- 导数值域范围:[0,0.25]
- 有效区间:[-6,6]有明显的差异、[-3,3]有比较好的效果
- 适用类型:二分类
- 导数图像分析:
- 1.当输入<-6或者>6的时候,sigmoid激活函数图像的导数接近为0,此时网络模型的参数将更新的非常慢,或者无法更新
- 2.一般来说,sigmoid网络模型在五层之内就会产生梯度消失的现象
2.tanh激活函数
# 创建画布和坐标轴 _, axes = plt.subplots(1, 2) # 函数图像 x = torch.linspace(-20, 20, 1000) y = torch.tanh(x) axes[0].plot(x, y) axes[0].grid() axes[0].set_title('Tanh 函数图像') # 导数图像 x = torch.linspace(-20, 20, 1000, requires_grad=True) torch.tanh(x).sum().backward() axes[1].plot(x.detach(), x.grad) axes[1].grid() axes[1].set_title('Tanh 导数图像') plt.show()
- 原函数值域范围:[-1,1]
- 导数值域范围:[0,0.25]
- 中心对称(收敛的速度非常的快)
- tanh函数与sigmoid函数的比较:
- 1.tanh函数是以0为中心的,梯度相对于sigmoid函数大,收敛的速度也比sigmoid函数的大。
- 2.和sigmoid函数一样,tanh两侧的导数也为0,同样会造成梯度的消失。
- 3.可以在隐藏层使用tanh函数,在输出层使用sigmoid函数。
3.ReLU激活函数
# 创建画布和坐标轴 _, axes = plt.subplots(1, 2) # 函数图像 x = torch.linspace(-20, 20, 1000) y = torch.relu(x) axes[0].plot(x, y) axes[0].grid() axes[0].set_title('ReLU 函数图像') # 导数图像 x = torch.linspace(-20, 20, 1000, requires_grad=True) torch.relu(x).sum().backward() axes[1].plot(x.detach(), x.grad) axes[1].grid() axes[1].set_title('ReLU 导数图像') plt.show()
- ReLU函数将小于0的值映射为0,而大于0的值则保持不变,这种激活函数的运算更为简单。
- 当x<0时,ReLU导数为0,而当x>0时,则不存在饱和问题。所以,ReLU 能够在x>0时保持梯 度不衰减,从而缓解梯度消失问题。然而,随着训练的推进,部分输入会落入小于0区域,导致对应权重无法更新。这种现象被称为“神经元死亡” 。
- ReLU函数与sigmoid函数相比
采用sigmoid函数,计算量大(指数运算),反向传播求误差梯度时,计算量相对大,而采用 Relu激活函数,整个过程的计算量节省很多。 sigmoid函数反向传播时,很容易就会出现梯度消失的情况,从而无法完成深层网络的训练。 Relu会使一部分神经元的输出为0,这样就造成了 网络的稀疏性,并且减少了参数的相互依存关系,缓解了过拟合问题的发生。
4.SoftMax激活函数
scores = torch.tensor([0.2, 0.02, 0.15, 0.15, 1.3, 0.5, 0.06, 1.1, 0.05, 3.75]) # dim = 0,按行计算 probabilities = torch.softmax(scores, dim=0) print(probabilities) #输出结果: #tensor([0.0212, 0.0177, 0.0202, 0.0202, 0.0638, 0.0287, 0.0185, 0.0522, 0.0183,0.7392])
Softmax 就是将网络输出的 logits 通过 softmax 函数,就映射成为(0,1)的值,而 这些值的累和 为1 (满足概率的性质),那么我们将它理解成概率, 选取概率最大(也就是值对应最大的)节 点 ,作为我们的 预测目标类别 。
5.其他激活函数
总结
对于隐藏层:
- 优先选择ReLU函数
- 如果ReLU函数的效果不好,那么就尝试其他的激活函数,如Leaky ReLU等
- 如果你使用了ReLU,需要注意一下Dead ReLU问题,避免出现了大的梯度从而导致过多的神经元死亡。
- 少用sigmoid激活函数,可以尝试使用tanh激活函数
对于输出层:
- 二分类问题选择sigmoid激活函数
- 多分类问题选择softmax激活函数
- 回归问题选择identity激活函数
激活函数的作用:
向神经网络添加非线性的元素
(3)参数初始化
均匀分布初始化
权重参数初始化从区间均匀随机取值。即在(-1/
d,1/
d)均匀分布中生存当前神经元的权重,其中d为每个神经元的输入数量。
正态分布初始化
随机初始化从均值为0,标准差是1的高斯分布(标准正态分布)中,使用一些很小的值对参数w进行优化处理。
全0初始化
将神经网络中的所有权重参数初始化为0
全1初始化
将神经网络中的所有权重参数初始化为1
固定值初始化
将神经网络中的所有权重参数初始化为某个固定值
kaiming初始化
xavier初始化
kaiming初始化和xavier初始化(重点)
import torch import torch.nn.functional as F import torch.nn as nn # 1. 均匀分布随机初始化 def test01(): linear = nn.Linear(5, 3) # 从0-1均匀分布产生参数 nn.init.uniform_(linear.weight) print(linear.weight.data) # 2.固定初始化 def test02(): linear = nn.Linear(5, 3) nn.init.constant_(linear.weight, 5) print(linear.weight.data) # 3. 全0初始化 def test03(): linear = nn.Linear(5, 3) nn.init.zeros_(linear.weight) print(linear.weight.data) # 4. 全1初始化 def test04(): linear = nn.Linear(5, 3) nn.init.ones_(linear.weight) print(linear.weight.data) # 5. 正态分布随机初始化 def test05(): linear = nn.Linear(5, 3) nn.init.normal_(linear.weight, mean=0, std=1) print(linear.weight.data) # 6. kaiming 初始化 def test06(): # kaiming 正态分布初始化 linear = nn.Linear(5, 3) nn.init.kaiming_normal_(linear.weight) print(linear.weight.data) # kaiming 均匀分布初始化 linear = nn.Linear(5, 3) nn.init.kaiming_uniform_(linear.weight) print(linear.weight.data) # 7. xavier 初始化 def test07(): # xavier 正态分布初始化 linear = nn.Linear(5, 3) nn.init.xavier_normal_(linear.weight) print(linear.weight.data) # xavier 均匀分布初始化 linear = nn.Linear(5, 3) nn.init.xavier_uniform_(linear.weight) print(linear.weight.data)
总结
一般我们在使用Pytorch构建网络模型的时候,每个网络层的参数都有默认的初始化方法,优先会选择kaiming的初始化方法,xavier初始化方法
(4)神经网络的搭建方法
- 定义继承来自nn.Module的模型类
- 在__init__方法中定义网络中的层结构
- 在forward()方法中定义数据的传输方式(激活函数)
- 网格参数量的统计方法:统计每一层的权重w和偏置b的数量
(5)神经网络的优缺点
优点:
- 精度高、性能优于其他的机器学习算法,甚至在某些领域超过了人类
- 可以近似任意的非线性函数随之计算机硬件的发展
- 有大量的框架和库可以调用
缺点:
- 黑箱,很难解释模型是怎么工作的
- 训练时间长,需要大量的计算资源
- 网路结构复杂,需要调整参数
- 部分数据集上表现不佳,容易发生过拟合
二、损失函数
概念:损失函数就是用来衡量模型参数的质量的函数,比较的是网络模型的预测值与真实值之间的误差
别名:论文可能提到的不一样,损失函数(loss function)、代价函数(cost function)、目标函数(objective function)、误差函数(error function)
(1)多任务的损失函数
在多任务任务中通常使用softmax()将逻辑值转换成概率值的形式,所以多分类的交叉熵损失也叫做softmax损失。
- y是样本x属于某一个类别的真实概率
- 而f(x)是样本属于某一类别的预测分数
- S是softmax()激活函数,将属于某一类别的预测分数转换成概率
- L是用来衡量真实值y和预测值f(x)之间差异性的损失结果
API:多分类损失函数在pytroch中使用nn.CrossEntropyLoss()实现
# 分类损失函数:交叉熵损失使用nn.CrossEntropyLoss()实现。 # nn.CrossEntropyLoss()=softmax + 损失计算 def test(): # 设置真实值: 可以是热编码后的结果也可以不进行热编码 # y_true = torch.tensor([[0, 1, 0], [0, 0, 1]], dtype=torch.float32) # 注意的类型必须是64位整型数据 y_true = torch.tensor([1, 2], dtype=torch.int64) y_pred = torch.tensor([[0.2, 0.6, 0.2], [0.1, 0.8, 0.1]], dtype=torch.float32) # 实例化交叉熵损失 loss = nn.CrossEntropyLoss() # 计算损失结果 my_loss = loss(y_pred, y_true).numpy() print('loss:', my_loss)
(2)二分类损失函数
在处理二分类任务时,我们不再使用softmax()激活函数,而是使用sigmoid()激活函数,损失函数也会进行相应的调整
- y是样本x属于某一个类别的真实数据
- 而
是样本属于某一类别的预测数据
- L是用来衡量真实值y与预测值
之间差异性的损失结果
API:在pytorch中实现时使用nn.BCELoss()
def test2(): # 1 设置真实值和预测值 # 预测值是sigmoid输出的结果 y_pred = torch.tensor([0.6901, 0.5459, 0.2469], requires_grad=True) y_true = torch.tensor([0, 1, 0], dtype=torch.float32) # 2 实例化二分类交叉熵损失 criterion = nn.BCELoss() # 3 计算损失 my_loss = criterion(y_pred, y_true).detach().numpy() print('loss:', my_loss)
(3)回归任务损失函数-MAE损失函数
Mean absolute loss(MAE)也被称为L1 Loss,是以绝对误差作为距离的损失函数
特点:
- 由于L1 Loss具有稀疏性,为了惩罚较大的值,因此常常将其作为正则项添加到其他loss中作为约束。
- L1 Loss的最大问题就是梯度在零点不平滑,导致会跳过极小值。
API:在pytorch中使用nn.L1Loss()实现
# 计算算inputs与target之差的绝对值def test3(): # 1 设置真实值和预测值 y_pred = torch.tensor([1.0, 1.0, 1.9], requires_grad=True) y_true = torch.tensor([2.0, 2.0, 2.0], dtype=torch.float32) # 2 实例MAE损失对象 loss = nn.L1Loss() # 3 计算损失 my_loss = loss(y_pred, y_true).detach().numpy() print('loss:', my_loss)
(4)回归任务损失函数-MSE损失函数
Mean Squared Loss/ Quadratic Loss(MSE loss)也被称为L2 loss,或欧氏距离,它以误差的平方和的均值作为距离
特点:
- L2 Loss也是常常作为正则项
- 当预测值与目标值差距很大的时候,容易梯度爆炸
API:在pytorch中使用nn.MSELoss()实现
def test4(): # 1 设置真实值和预测值 y_pred = torch.tensor([1.0, 1.0, 1.9], requires_grad=True) y_true = torch.tensor([2.0, 2.0, 2.0], dtype=torch.float32) # 2 实例MSE损失对象 loss = nn.MSELoss() # 3 计算损失 my_loss = loss(y_pred, y_true).detach().numpy() print('myloss:', my_loss)
(5)回归任务损失函数-smooth L1损失函数
smooth L1就是光滑之后的L1
其中:x = f(x)- y为真实值与预测值之间的差值
从上图可以看出,这是一个分段函数:
- 在[-1,1]之间实际上就是L2损失,这样解决了L1的不光滑问题
- 在[-1,1]区间外,实际就是L1损失,这样就解决了梯度爆炸的问题
API:在pytorch中使用nn.smoothL1Loss()实现
def test5(): # 1 设置真实值和预测值 y_true = torch.tensor([0, 3]) y_pred = torch.tensor ([0.6, 0.4], requires_grad=True) # 2 实例smmothL1损失对象 loss = nn.SmoothL1Loss() # 3 计算损失 my_loss = loss(y_pred, y_true).detach().numpy() print('loss:', my_loss)
总结
分类任务的损失函数
- 多分类的交叉熵损失函数:nn.softmax() 将逻辑值转换成概率值
- 二分类的交叉熵损失函数:nn.sigmoid()
回归任务的损失函数
- MAE:nn.L1Loss() 绝对值误差的均值 零点不平滑
- MSE:nn.MSELoss() 均方误差的均值 容易梯度爆炸
- smoothL1:nn.smoothL1Loss() 结合了MAE和MSE,光滑的L1
三、网络的优化方法
(1)梯度下降算法
概念:梯度下降法是一种寻找损失函数最小化的网络优化方法。
数学角度分析:梯度的方向就是函数增长速度最快的方向,那么梯度的反方向就是函数减少最快的方向。
学习率如果太小,那么每次训练之后得到的效果都太小,增大训练的时间成本。
学习率如果太大,那么就有可能直接跳过最优解,进入无限的训练当中去(振荡)。
解决办法:学习率也要随着训练的进行不断地更新。
(2)网络训练过程中的epoch,batch,iter
进行模型训练时的三个基础的概念:
- Epoch:使用全部数据对模型进行完整训练
- Batch_size:使用训练集的小部分样本对模型权重进行以此反向传播的参数更新,每次训练每批次样本数量。
- Iteration:使用一个Batch数据对一个模型进行一次参数更新的过程
在深度学习中,梯度下降的几种方式的根本区别在于Batch Size不同
(3)反向传播的算法过程(了解)
了解反向传播之前我们要先了解前向传播的过程
前向传播:指的就是数据在输入的神经网络中,逐层向前传输,一直到运算到输出层为止(输入层-->隐藏层-->输出层)
反向传播(Back Propagation):利用损失函数ERROR,从后往前,结合梯度下降算法,依次求各个参数的偏导,并且进行参数更新的过程。
反向传播
反向传播对神经网络中的各个节点的权重进行更新。
以下用一个简单的神经网络举例:激活函数为sigmoid()
import torch from torch import nn from torch import optim # 创建神经网络类 class Model(nn.Module): # 初始化参数 def __init__(self): # 调用父类方法 super(Model, self).__init__() # 创建网络层 self.linear1 = nn.Linear(2, 2) self.linear2 = nn.Linear(2, 2) # 初始化神经网络参数 self.linear1.weight.data = torch.tensor([[0.15, 0.20], [0.25, 0.30]]) self.linear2.weight.data = torch.tensor([[0.40, 0.45], [0.50, 0.55]]) self.linear1.bias.data = torch.tensor([0.35, 0.35]) self.linear2.bias.data = torch.tensor([0.60, 0.60]) # 前向传播方法 def forward(self, x): # 数据经过第一层隐藏层 x = self.linear1(x) # 计算第一层激活值 x = torch.sigmoid(x) # 数据经过第二层隐藏层 x = self.linear2(x) # 计算第二层激活值 x = torch.sigmoid(x) return x if __name__ == '__main__': # 定义网络输入值和目标值 inputs = torch.tensor([[0.05, 0.10]]) target = torch.tensor([[0.01, 0.99]]) # 实例化神经网络对象 model = Model() output = model(inputs) print("output-->", output) loss = torch.sum((output - target) ** 2) / 2 # 计算误差 print("loss-->", loss) # 优化方法和反向传播算法 optimizer = optim.SGD(model.parameters(), lr=0.5) optimizer.zero_grad() loss.backward() print("w1,w2,w3,w4-->", model.linear1.weight.grad.data) print("w5,w6,w7,w8-->", model.linear2.weight.grad.data) optimizer.step() # 打印神经网络参数 print(model.state_dict()) # output--> tensor([[0.7514, 0.7729]], grad_fn=<SigmoidBackward0>) # loss--> tensor(0.2984, grad_fn=<DivBackward0>) # w1,w2,w3,w4--> tensor([[0.0004, 0.0009], # [0.0005, 0.0010]]) # w5,w6,w7,w8--> tensor([[ 0.0822, 0.0827], # [-0.0226, -0.0227]]) # OrderedDict([('linear1.weight', tensor([[0.1498, 0.1996], # [0.2498, 0.2995]])), ('linear1.bias', tensor([0.3456, 0.3450])), # ## # ('linear2.weight', tensor([[0.3589, 0.4087], # [0.5113, 0.5614]])), ('linear2.bias', tensor([0.5308, 0.6190]))])
总结
前向传播:
指的是数据输入的神经网络中,逐层向前传输,一直运算到输出层为止。
反向传播(Back Propagation):
利用损失函数ERROR,从后往前,结合梯度下降算法,依次求各个参数的偏导,并进行参数更新的过程。
(4)梯度下降的优化方法
梯度下降优化算法中,可能会遇到的问题:
- 碰到平缓的区域,梯度值较小,参数优化比较慢
- 碰到“鞍点”,梯度为0,参数无法优化
- 碰到局部最小值,参数不是最优
1.梯度下降的优化方法-指数加权平均
指数移动加权平均则是参考各数值,并且各数值的权重都不同,距离越远的数字对平均数计算的贡献就越小(权重较小),距离越近的则对平均数的计算贡献越大(权重越大)
2.梯度下降的优化方法-动量算法(Momentum)
def test01(): # 1 初始化权重参数 w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32) y = ((w ** 2) / 2.0).sum() # 2 实例化优化方法:SGD 指定参数beta=0.9 optimizer = torch.optim.SGD([w], lr=0.01, momentum=0.9) # 3 第1次更新 计算梯度,并对参数进行更新 optimizer.zero_grad() y.backward() optimizer.step() print('第1次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy())) # 4 第2次更新 计算梯度,并对参数进行更新 # 使用更新后的参数机选输出结果 y = ((w ** 2) / 2.0).sum() optimizer.zero_grad() y.backward() optimizer.step() print('第2次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy())) # 第1次: 梯度w.grad: 1.0000000000, 更新后的权重:0.9900000095 # 第2次: 梯度w.grad: 0.9900000095, 更新后的权重:0.9711000323
3.梯度下降的优化方法-adaGrad
缺点:
AdaGrad缺点就是可能会使的学习率过早、过量的降低,导致模型训练后期学习率太小,难以找到最优解。
def test02(): # 1 初始化权重参数 w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32) y = ((w ** 2) / 2.0).sum() # 2 实例化优化方法:adagrad优化方法 optimizer = torch.optim.Adagrad ([w], lr=0.01) # 3 第1次更新 计算梯度,并对参数进行更新 optimizer.zero_grad() y.backward() optimizer.step() print('第1次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy())) # 4 第2次更新 计算梯度,并对参数进行更新 # 使用更新后的参数机选输出结果 y = ((w ** 2) / 2.0).sum() optimizer.zero_grad() y.backward() optimizer.step() print('第2次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy())) # 第1次: 梯度w.grad: 1.000000, 更新后的权重:0.990000 # 第2次: 梯度w.grad: 0.990000, 更新后的权重:0.982965
4.梯度下降的优化方法-RMSProp
def test03(): # 1 初始化权重参数 w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32) y = ((w ** 2) / 2.0).sum() # 2 实例化优化方法:RMSprop算法,其中alpha对应这beta optimizer = torch.optim.RMSprop([w], lr=0.01,alpha=0.9) # 3 第1次更新 计算梯度,并对参数进行更新 optimizer.zero_grad() y.backward() optimizer.step() print('第1次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy())) # 4 第2次更新 计算梯度,并对参数进行更新 # 使用更新后的参数机选输出结果 y = ((w ** 2) / 2.0).sum() optimizer.zero_grad() y.backward() optimizer.step() print('第2次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy())) # 第1次: 梯度w.grad: 1.000000, 更新后的权重:0.968377 # 第2次: 梯度w.grad: 0.968377, 更新后的权重:0.945788
5.梯度下降的优化方法-Adam(重要使用)
Momentum使用指数加权平均计算当前的梯度值
AdaGrad、RMSProp使用自适应的学习率
Adam优化算法(Adaptive Moment Estimation,自适应矩估计)将Momentum和RMSProp算法结合在一起
- 修正梯度:使用梯度的指数加权平均
- 修正学习率:使用梯度平方的指数加权平均
def test04(): # 1 初始化权重参数 w = torch.tensor([1.0], requires_grad=True) y = ((w ** 2) / 2.0).sum() # 2 实例化优化方法:Adam算法,其中betas是指数加权的系数 optimizer = torch.optim.Adam([w], lr=0.01,betas=[0.9,0.99]) # 3 第1次更新 计算梯度,并对参数进行更新 optimizer.zero_grad() y.backward() optimizer.step() print('第1次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy())) # 4 第2次更新 计算梯度,并对参数进行更新 # 使用更新后的参数机选输出结果 y = ((w ** 2) / 2.0).sum() optimizer.zero_grad() y.backward() optimizer.step() print('第2次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy())) # 第1次: 梯度w.grad: 1.000000, 更新后的权重:0.990000 # 第2次: 梯度w.grad: 0.990000, 更新后的权重:0.980003