自学内容网 自学内容网

dl学习笔记:(7)完整神经网络流程

反向传播

由于本节的公式比较多,所以如果哪里写错了漏写了,还请帮忙指出以便进行改正,谢谢。
在前面的章节已经介绍过梯度下降的两个关键:1.方向 2.步长,下面我们来讲解反向传播的原理。在介绍反向传播的原理之前,我们先看一下,如果没有这个方法应该如何进行求导工作,这样就能体现出反向传播的作用和厉害之处。
下面我们先用单层神经网络来举一个例子,下面是计算的流程图:
在这里插入图片描述
∂ L o s s ∂ w , 其中 \frac{\partial Loss}{\partial w}, \quad \text{其中} wLoss,其中 L o s s = − ∑ i = 1 m ( y i ⋅ ln ⁡ ( σ i ) + ( 1 − y i ) ⋅ ln ⁡ ( 1 − σ i ) ) Loss = -\sum_{i=1}^{m} \left( y_i \cdot \ln(\sigma_i) + (1 - y_i) \cdot \ln(1 - \sigma_i) \right) Loss=i=1m(yiln(σi)+(1yi)ln(1σi)),所以我们可以带入得到:
= − ∑ i = 1 m ( y i ⋅ ln ⁡ ( 1 1 + e − X i w ) + ( 1 − y i ) ⋅ ln ⁡ ( 1 − 1 1 + e − X i w ) ) -\sum_{i=1}^{m} \left( y_i \cdot \ln \left( \frac{1}{1 + e^{-X_i w}} \right) + (1 - y_i) \cdot \ln \left( 1 - \frac{1}{1 + e^{-X_i w}} \right) \right) i=1m(yiln(1+eXiw1)+(1yi)ln(11+eXiw1)),可以看出式子由于多个函数的嵌套已经变得很复杂了,所以下面我们就不进行对w的求导操作了。
下面我们继续看一个双层的神经网络:
在这里插入图片描述
∂ L o s s ∂ w ( 1 → 2 ) , 其中一样的 \frac{\partial Loss}{\partial w^{(1 \rightarrow 2)}}, \quad \text{其中一样的} w(12)Loss,其中一样的 L o s s = − ∑ i = 1 m ( y i ⋅ ln ⁡ ( σ i ( 2 ) ) + ( 1 − y i ) ⋅ ln ⁡ ( 1 − σ i ( 2 ) ) ) Loss = -\sum_{i=1}^{m} \left( y_i \cdot \ln(\sigma_i^{(2)}) + (1 - y_i) \cdot \ln(1 - \sigma_i^{(2)}) \right) Loss=i=1m(yiln(σi(2))+(1yi)ln(1σi(2))),带入得到:
= − ∑ i = 1 m ( y i ⋅ ln ⁡ ( 1 1 + e − σ i ( 1 ) w ( 1 → 2 ) ) + ( 1 − y i ) ⋅ ln ⁡ ( 1 − 1 1 + e − σ i ( 1 ) w ( 1 → 2 ) ) ) = -\sum_{i=1}^{m} \left( y_i \cdot \ln \left( \frac{1}{1 + e^{-\sigma_i^{(1)} w^{(1 \rightarrow 2)}}} \right) + (1 - y_i) \cdot \ln \left( 1 - \frac{1}{1 + e^{-\sigma_i^{(1)} w^{(1 \rightarrow 2)}}} \right) \right) =i=1m(yiln(1+eσi(1)w(12)1)+(1yi)ln(11+eσi(1)w(12)1))
可以看到这里和前面基本一致,但是当我们继续向前求导式子就会愈发复杂:
∂ L o s s ∂ w ( 0 → 1 ) , 其中 \frac{\partial Loss}{\partial w^{(0 \rightarrow 1)}}, \quad \text{其中} w(01)Loss,其中 L o s s = − ∑ i = 1 m ( y i ⋅ ln ⁡ ( σ i ( 2 ) ) + ( 1 − y i ) ⋅ ln ⁡ ( 1 − σ i ( 2 ) ) ) Loss = -\sum_{i=1}^{m} \left( y_i \cdot \ln(\sigma_i^{(2)}) + (1 - y_i) \cdot \ln(1 - \sigma_i^{(2)}) \right) Loss=i=1m(yiln(σi(2))+(1yi)ln(1σi(2))),继续带入可以得到:
= − ∑ i = 1 m ( y i ln ⁡ ( 1 1 + e − 1 1 + e − X i w ( 0 → 1 ) w ( 1 → 2 ) ) + ( 1 − y i ) ln ⁡ ( 1 − 1 1 + e − 1 1 + e − X i w ( 0 → 1 ) w ( 1 → 2 ) ) ) = - \sum_{i=1}^{m} \left( y_i \ln \left( \frac{1}{1 + e^{-\frac{1}{1 + e^{-X_i w^{(0 \rightarrow 1)}}} w^{(1 \rightarrow 2)}} } \right) + (1 - y_i) \ln \left( 1 - \frac{1}{1 + e^{-\frac{1}{1 + e^{-X_i w^{(0 \rightarrow 1)}}} w^{(1 \rightarrow 2)}} } \right) \right) =i=1m(yiln(1+e1+eXiw(01)1w(12)1)+(1yi)ln(11+e1+eXiw(01)1w(12)1))
我们现在就可以看到当函数进行层层嵌套之后,式子就会变得非常复杂,是很不利于我们进行求导的操作的,并且这还只是一个双层的简单神经网络,可以想象当我们后面遇到更加复杂的网络结构的时候,式子就会变得超出能理解和求导的范围了。所以求导过程的复杂的确一直都是神经网络的难题,直到1986年由Rumelhart、Williams和“神经网络之父”Hinton提出的反向传播算法才得到较好的解决。

链式求导

下面我们具体来看一下反向传播算法是如何解决这个问题的:
在高等数学中我们都学过,假设有一个复合函数y = f(g(x)),链式法则告诉我们,复合函数的导数是外层函数的导数与内层函数的导数的乘积,即: d y d x = d f d u ⋅ d u d x \frac{dy}{dx} = \frac{df}{du} \cdot \frac{du}{dx} dxdy=dudfdxdu。我们利用这个规则进行求导如下:
∂ Loss ∂ w ( 1 → 2 ) = ∂ L ( σ ) ∂ σ ⋅ ∂ σ ( z ) ∂ z ⋅ ∂ z ( w ) ∂ w \frac{\partial \text{Loss}}{\partial w^{(1 \rightarrow 2)}} = \frac{\partial L(\sigma)}{\partial \sigma} \cdot \frac{\partial \sigma(z)}{\partial z} \cdot \frac{\partial z(w)}{\partial w} w(12)Loss=σL(σ)zσ(z)wz(w)
∂ L ( σ ) ∂ σ = ∂ ( − ∑ i = 1 m ( y i ⋅ ln ⁡ ( σ i ) + ( 1 − y i ) ⋅ ln ⁡ ( 1 − σ i ) ) ) ∂ σ \frac{\partial L(\sigma)}{\partial \sigma} = \frac{\partial \left( -\sum_{i=1}^{m} \left( y_i \cdot \ln(\sigma_i) + (1 - y_i) \cdot \ln(1 - \sigma_i) \right) \right)}{\partial \sigma} σL(σ)=σ(i=1m(yiln(σi)+(1yi)ln(1σi)))

= ∑ i = 1 m ∂ ( − ( y i ⋅ ln ⁡ ( σ i ) + ( 1 − y i ) ⋅ ln ⁡ ( 1 − σ i ) ) ) ∂ σ = \sum_{i=1}^{m} \frac{\partial \left( -(y_i \cdot \ln(\sigma_i) + (1 - y_i) \cdot \ln(1 - \sigma_i)) \right)}{\partial \sigma} =i=1mσ((yiln(σi)+(1yi)ln(1σi)))

= − ( y ⋅ 1 σ + ( 1 − y ) ⋅ 1 1 − σ ⋅ ( − 1 ) ) = -(y \cdot \frac{1}{\sigma} + (1 - y) \cdot \frac{1}{1 - \sigma} \cdot (-1)) =(yσ1+(1y)1σ1(1))

= − ( y σ + y − 1 1 − σ ) = -( \frac{y}{\sigma} + \frac{y - 1}{1 - \sigma} ) =(σy+1σy1)

= − y ( 1 − σ ) + ( y − 1 ) σ σ ( 1 − σ ) = - \frac{y(1 - \sigma) + (y - 1) \sigma}{\sigma(1 - \sigma)} =σ(1σ)y(1σ)+(y1)σ

= − y σ − y σ + y σ − σ σ ( 1 − σ ) = - \frac{y \sigma - y \sigma + y \sigma - \sigma}{\sigma(1 - \sigma)} =σ(1σ)yσyσ+yσσ

= σ − y σ ( 1 − σ ) = \frac{\sigma - y}{\sigma(1 - \sigma)} =σ(1σ)σy
其他部分也是同理得到:
∂ σ ( z ) ∂ z = ∂ 1 1 + e − z ∂ z \frac{\partial \sigma(z)}{\partial z} = \frac{\partial \frac{1}{1 + e^{-z}}}{\partial z} zσ(z)=z1+ez1

= ∂ ( 1 + e − z ) − 1 ∂ z = \frac{\partial (1 + e^{-z})^{-1}}{\partial z} =z(1+ez)1

= − 1 ⋅ ( 1 + e − z ) − 2 ⋅ e − z ⋅ ( − 1 ) = -1 \cdot (1 + e^{-z})^{-2} \cdot e^{-z} \cdot (-1) =1(1+ez)2ez(1)

= e − z ( 1 + e − z ) 2 = \frac{e^{-z}}{(1 + e^{-z})^2} =(1+ez)2ez

= 1 + e − z − 1 ( 1 + e − z ) 2 = \frac{1 + e^{-z} - 1}{(1 + e^{-z})^2} =(1+ez)21+ez1

= 1 ( 1 + e − z ) ⋅ ( 1 − 1 ( 1 + e − z ) ) = \frac{1}{(1 + e^{-z})} \cdot \left( 1 - \frac{1}{(1 + e^{-z})} \right) =(1+ez)1(1(1+ez)1)

= σ ( 1 − σ ) = \sigma(1 - \sigma) =σ(1σ)
最后一部分:
∂ z ( w ) ∂ w = ∂ σ ( 1 ) w ∂ w \frac{\partial z(w)}{\partial w} = \frac{\partial \sigma^{(1)}w}{\partial w} wz(w)=wσ(1)w
将三块分别带入链式求导得到:
∂ Loss ∂ w ( 1 → 2 ) = ∂ L ( σ ) ∂ σ ⋅ ∂ σ ( z ) ∂ z ⋅ ∂ z ( w ) ∂ w \frac{\partial \text{Loss}}{\partial w^{(1 \rightarrow 2)}} = \frac{\partial L(\sigma)}{\partial \sigma} \cdot \frac{\partial \sigma(z)}{\partial z} \cdot \frac{\partial z(w)}{\partial w} w(12)Loss=σL(σ)zσ(z)wz(w)

= σ ( 2 ) − y σ 2 ( 1 − σ ( 2 ) ) ⋅ σ ( 2 ) ⋅ ( 1 − σ ( 2 ) ) ⋅ σ ( 1 ) = \frac{\sigma^{(2)} - y}{\sigma^{2} (1 - \sigma^{(2)})} \cdot \sigma^{(2)} \cdot (1 - \sigma^{(2)}) \cdot \sigma^{(1)} =σ2(1σ(2))σ(2)yσ(2)(1σ(2))σ(1)

= σ ( 1 ) ⋅ ( σ ( 2 ) − y ) = \sigma^{(1)} \cdot (\sigma^{(2)} - y) =σ(1)(σ(2)y)

下面我们可以继续向前传播:
∂ Loss ∂ w ( 0 → 1 ) = ∂ L ( σ ) ∂ σ ( 2 ) ⋅ ∂ σ ( z ) ∂ z ( 2 ) ⋅ ∂ z ( w ) ∂ σ ( 1 ) ⋅ ∂ σ ( z ) ∂ z ( 1 ) ⋅ ∂ z ( w ) ∂ w ( 0 → 1 ) \frac{\partial \text{Loss}}{\partial w^{(0 \rightarrow 1)}} = \frac{\partial L(\sigma)}{\partial \sigma^{(2)}} \cdot \frac{\partial \sigma(z)}{\partial z^{(2)}} \cdot \frac{\partial z(w)}{\partial \sigma^{(1)}} \cdot \frac{\partial \sigma(z)}{\partial z^{(1)}} \cdot \frac{\partial z(w)}{\partial w^{(0 \rightarrow 1)}} w(01)Loss=σ(2)L(σ)z(2)σ(z)σ(1)z(w)z(1)σ(z)w(01)z(w)
我们可以发现其中有好几项在前面的求导过程中已经求解完了,所以这里直接带入即可

= ( σ ( 2 ) − y ) ⋅ ∂ z ( σ ) ∂ σ ( 1 ) ⋅ ∂ z ( z ) ∂ z ( 1 ) ⋅ ∂ z ( w ) ∂ w ( 0 → 1 ) = (\sigma^{(2)} - y) \cdot \frac{\partial z(\sigma)}{\partial \sigma^{(1)}} \cdot \frac{\partial z(z)}{\partial z^{(1)}} \cdot \frac{\partial z(w)}{\partial w^{(0 \rightarrow 1)}} =(σ(2)y)σ(1)z(σ)z(1)z(z)w(01)z(w)

= ( σ ( 2 ) − y ) ⋅ w 1 → 2 ⋅ ( σ ( 1 ) ( 1 − σ ( 1 ) ) ) ⋅ X = (\sigma^{(2)} - y) \cdot w^{1 \rightarrow 2} \cdot \left( \sigma^{(1)} \left( 1 - \sigma^{(1)} \right) \right) \cdot X =(σ(2)y)w12(σ(1)(1σ(1)))X

代码实现反向传播

至于这里如何使用代码实现,在前面的章节已经具体介绍过了,所以这里就再复习一遍:
任务和架构:3分类,500个样本,20个特征,共3层,第一层13个神经元,第二层8个神经元
首先还是先导入库:

import torch
import torch.nn as nn
from torch.nn import functional as F

下一步是确定数据:

torch.manual_seed(250)
X = torch.rand((500,20),dtype=torch.float32) * 100
y = torch.randint(low=0,high=3,size=(500,1),dtype=torch.float32)

定义model类:

class Model(nn.Module):
    def __init__(self,in_features=10,out_features=2):
        super(Model,self).__init__() 
        self.linear1 = nn.Linear(in_features,13,bias=False) 
        self.linear2 = nn.Linear(13,8,bias=False)
        self.output = nn.Linear(8,out_features,bias=True)     
    def forward(self, x):
        sigma1 = torch.relu(self.linear1(x))
        sigma2 = torch.sigmoid(self.linear2(sigma1))
        zhat = self.output(sigma2)
        return zhat

确定参数:

input = X.shape[1]
output = len(y.unique())

实例化:

torch.manual_seed(250)
net = model(in_features=input,out_features=output)

前向传播:

zhat = net.forward(X)
zhat

结果如下:
在这里插入图片描述
定义损失函数:

criterion = nn.CrossEntropyLoss()
loss = criterion(zhat,y.reshape(500).long())
loss

结果如下:
在这里插入图片描述
这里有一个小坑,这里的交叉熵损失函数只接受一维张量,并且要求标签必须是整型,所以需要添加reshape和long的操作
反向传播过程:

net.linear1.weight.grad
loss.backward()
net.linear1.weight.grad

在这里插入图片描述
我们可以看到反向传播前是查看不到梯度的,只有在backward之后才行

动量法Momentum

动量法的基本原理:
动量法通过引入“动量”的概念来解决这一问题,类似于物理中的动量:前一时刻的梯度信息会在更新中保留下来,影响当前的更新。这样,优化算法不仅依赖于当前的梯度,还“记住”之前的梯度,从而能够在更新过程中累积梯度的“惯性”,加速收敛。
动量法的核心思想是利用过去的梯度信息来加速当前的梯度更新。如果梯度在某一方向上稳定,动量会将更新步骤“加速”并朝着这个方向移动。而如果梯度在某一方向上发生变化,动量会帮助减小这种变化,避免过多的震荡。
具体来说,动量法有以下几个优点:

  1. 加速收敛:尤其是在坡度比较平缓的方向上,动量法能够加速收敛,因为它结合了过去的梯度信息。
  2. 减少震荡:在梯度方向上具有震荡的情况下,动量法通过加权平均来减少震荡,使得优化更加稳定。
  3. 适应不同的局部极小值:动量法能够帮助优化算法越过某些局部极小值,尤其在非凸优化问题中,它有助于找到更好的解。
    转化为公式:
    v ( t ) = γ v ( t − 1 ) − η ∂ L ∂ w v_{(t)} = \gamma v_{(t-1)} - \eta \frac{\partial L}{\partial w} v(t)=γv(t1)ηwL
    w ( t + 1 ) = w ( t ) + v ( t ) w_{(t+1)} = w_{(t)} + v_{(t)} w(t+1)=w(t)+v(t)

在这里插入图片描述
我们很容易可以在pytorch中简单实现一下动量法的过程:
先定义参数和超参数

lr = 0.1
gamma = 0.9
dw = net.linear1.weight.grad
w = net.linear1.weight.data
v = torch.zeros(dw.shape[0],dw.shape[1])

进行迭代:

v = gamma * v - lr * dw
w -= v
w

我们运行几轮之后就可以得到迭代过的结果,可以发现迭代过程的确比原来快很多:
在这里插入图片描述
当然,pytorch也有内置的动量法实现:
在这里插入图片描述
在之前的章节中已经介绍过pytorch框架的各个模块,动量法就在优化算法模块optim中。
为了实现优化算法,首先我们需要导入optim模块,其他的步骤和前面基本一致:

import torch.optim as optim

启动:

opt = optim.SGD(net.parameters() , lr=lr , momentum = gamma)
zhat = net.forward(X) 
loss = criterion(zhat,y.reshape(500).long())
loss.backward() 
opt.step() 
opt.zero_grad() 
print(loss)
print(net.linear1.weight.data[0][:10])

需要解释的是我们在进行一轮梯度下降之后,为了不浪费存储空间,并且我们一般也不会去查看梯度下降的历史记录,我们可以将梯度清空opt.zero_grad() 。其余的步骤和前面基本一致,就不再重复了,迭代几轮之后的结果如下:
在这里插入图片描述
现在只是进行了一轮梯度下降,下一步就是循环这个迭代过程。

开始迭代

为什么选择小批量

在我们的前面的代码中,都是将所有的特征矩阵x传入,但是在实际的深度学习工作中,我们所面临的数据量都是大量的高维数据,如果每次进行梯度下降都要对所有的矩阵进行求导,那么将会非常耗费计算资源。所以下面我来介绍小批量随机梯度下降(mini-batch stochastic gradient descent,简写为mini-batch SGD)。小批量梯度下降和传统的梯度下降的迭代流程基本一致,唯一不同的地方在于迭代使用的数据,小批量采用的方法是每次迭代前都对整体的样本进行采样,形成一个批次(batch),以便减少样本量和计算量。你可能会问每次只选出一批样本,模型能学到东西吗或者效果真的比全部样本都学好吗?
为什么会选择mini-batch SGD作为神经网络的入门级优化算法呢?比起传统梯度下降,mini-batch SGD更可能找到全局最小值。
在最小化损失函数 L(w) 时,目标是找到函数的最小值。然而,函数可能有不同类型的最小值:局部极小值和全局最小值。
传统梯度下降是每次迭代时都使用全部数据的梯度下降,所以每次使用的数据是一致的,因此梯度向量的方向和大小都只受到权重的影响,所以梯度方向的变化相对较小,很多时候看起来梯度甚至是指向一个方向。这样带来的优势是可以使用较大的步长,快速迭代直到找到最小值。但是缺点也很明显,由于梯度方向不容易发生巨大变化,所以一旦在迭代过程中落入局部最优的范围,传统梯度下降就很难跳出局部最优,再去寻找全局最优解了。
而mini-batch SGD在每次迭代前都会随机抽取一批数据,所以每次迭代时带入梯度向量表达式的数据是不同的,梯度的方向同时受到系数 和带入的训练数据的影响,因此每次迭代时梯度向量的方向都会发生较大变化。所以优点就是不会轻易陷入局部最优,但是相对的缺点就是需要的迭代次数变得不明。所以对于mini-batch SGD而言,它的梯度下降路线看起来往往是曲折的折线。极端情况下,当我们每次随机选取的批量中只有一个样本时,梯度下降的迭代轨迹就会变得异常不稳定。我们称这样的梯度下降为随机梯度下降(stochastic gradient descent,SGD)。
下图展示了三种梯度下降方法的不同:
在这里插入图片描述
所以在mini-batch SGD中,我们选择的批量batch含有的样本数被称为batch_size,批量尺寸,而一个epoch代表对所有训练数据进行一次完整的迭代,完成一个epoch所需要的迭代次数等于总样本量除以batch_size。

TensorDataset与DataLoader

下面我们用代码来实现分批的操作,这就需要介绍一下另外两个工具TensorDataset与DataLoader。
在这里插入图片描述
我们再次搬出这张图片可以发现,左边有一个专门用来做预处理工作的模块叫做utils,下面就有负责导入和处理的TensorDataset与DataLoader。想要实现小批量随机梯度下降,我们就需要对数据进行采样、分割等操作。通常的来说,特征张量与标签几乎总是分开的,所以我们需要将数据划分为许多组特征张量+对应标签的形式,要将数据的特征张量与标签打包成一个对象。而合并张量与标签,我们所使用的类就是utils.data.TensorDataset,负责将最外面的维度一致的tensor进行打包,下面用代码展示:

import torch
from torch.utils.data import TensorDataset
a = torch.randn(500,2,3)
b = torch.randn(500,3,4,5)
c = torch.randn(500,1)
TensorDataset(a,b,c)[0]

结果如下:
在这里插入图片描述
我们可以通过结果看出来,TensorDataset将abc中各拿了一份元素并合并起来了,例如这里第一部分是属于a的23矩阵,第二部分是属于b的34*5的矩阵,第三部分是属于c的一维张量。需要注意的是如果只输入函数,返回的是迭代器,结果如下:
在这里插入图片描述
也可以通过循环的方式遍历迭代器,方法如下:
在这里插入图片描述
另外需要注意的是这个函数必须要求第一个维度一致,否则就会出现报错,如下:在这里插入图片描述
如果我们用同样的方法加上dataloader,可以发现输出只不过把最后的元组形式变成列表:
在这里插入图片描述
下面介绍一下dataloader的几个参数的使用:

dataset = DataLoader(data
           , batch_size=100
           , shuffle=True
           , drop_last = True)

在这里插入图片描述
这里我们定义了一个500*2的矩阵,batch_size就是每一个batch分多大,这里我们取100个。shuffle的含义是是否需要每次都打乱随机抽取,如果这里是false的话,就会按照顺序依次分好,例如[1,100],[100,200]这样依次排下去。drop_last代表是否丢掉最后那个除不尽的小批次。
同样的这里返回的依然是一个迭代器,我们还是可以通过循环打印出来里面的内容:

dataset = DataLoader(data
           , batch_size=100
           , shuffle=True
           , drop_last = True)
for i in dataset:
    print(i[0].shape)

在这里插入图片描述
可以发现500份已经被我们分成了五个100份的张量了。
最后我们梳理一下整个流程:
1)设置步长 ,动量值 ,迭代次数 ,batch_size等信息,(如果需要)设置初始权重
2)导入数据,将数据切分成batches
3)定义神经网络架构
4)定义损失函数 ,如果需要的话,将损失函数调整成凸函数,以便求解最小值
5)定义所使用的优化算法
6)开始在epoches和batch上循环,执行优化算法:
6.1)调整数据结构,确定数据能够在神经网络、损失函数和优化算法中顺利运行
6.2)完成向前传播,计算初始损失
6.3)利用反向传播,在损失函数上求偏导数
6.4)迭代当前权重
6.5)清空本轮梯度
6.6)完成模型进度与效果监控
7)输出结果

在这一节中只是介绍了TensorDataset与DataLoader的常用用法,下一节会在fashion-minist数据集做一个具体的展示,以上就是本篇文章所有内容,谢谢大家看到这里!


原文地址:https://blog.csdn.net/heixiu_heixiuhei/article/details/145281247

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!