(一)定义
对于简单的线性模型,我们把他的运算转换为神经元的运算则是如下图:
我们对于通过求导,在更新权重的过程中,让损失函数达到最小值(不是让y最小),但这是对于比较简单的模型,对于复杂的模型,我们涉及到的权重很多,如果每个函数挨个写解析式是很复杂的,那么我们就尝试把这个神经网络看作是一个图,在图上来传播梯度,最终根据链式法则把梯度给求出来,这种算法就叫做反向传播算法Backpropagation,也叫作BP算法。
(二)结构
我们在输入的时候可以知道输入的数据维度是多少,在定义输出数据的维度,我们就可由此得知权重的维度,其中有的教材上会写H=WTX,前面写一个矩阵转置,这样写无外乎是初始定义时矩阵形状的不同所导致的是否要转置。
对于这样的双层网络,其中的MM是矩阵乘法(Matrix multiplication),其中b1表示偏置量(bias),它的维度是由MM后的向量维度决定的,以上就是计算图的过程,以上计算图中的绿色模块即为计算模块,但要注意的是,不同的计算模块他们求局部偏导的计算方法是不一样的,比如矩阵的求导,详细可以通过matrix-cook-book这本书来寻找具体的矩阵计算公式。
有一个问题值得我们注意,当我们进行这样的两层计算时,通过对公式的化简,我们会发现复合函数又变回了原本函数的形式,为了解决这个问题,也就是要增加模型的复杂度,让模型无法化简,所以我们对每一层的输出要增加一个非线性的变化函数(比如sigmoid),这样才可以做成一个真正的神经网络
(三)链式求导
神经网络的每一个神经元都涉及到计算求导,多个神经元的求导就会涉及到链式求导法则。链式求导的第一步是构建计算图,第二步是前馈操作(forward)即沿着箭头方向进行计算,从输入一直算到loss
得到了loss后,我们再用loss往回求导,由于我们知道f函数是个什么样的形式,所以关于x和w的求导我们都可以算出来,所以反馈的时候我们根据链式法则都可以求出导数。在pytorch中一般我们都把梯度存在变量里面而不是存在计算模块里,在计算loss时,我们是对于w求导的,所以结构图应该从w开始,到loss结束。
由此法则我们可以推导整个的链式求导法则:
虽然每次都会前馈再反馈,但我们一般会将每次得到的loss函数值保留,以供我们后续判断训练是否到了收敛的程度,下面是老师提出的思考题解答:
(四)pytorch中进行反向传播
1. 注意创建的时候要用torch.Tensor,而且不管权重有几个值,一定要拿中括号将其括起来,这样才可以创建一个tensor类,注意一定要大写T,如果小写则使用的是一个函数而不是张量类。对于权重的tensor类要设置其属性w.requires_grad为true,这是因为默认的tensor创建后是不会进行梯度计算的,所以将来构建计算图进行计算的时候不会去保留当前关于w的梯度值
2. 在forward函数中进行的是tensor之间的数乘,所以也要把x变为一个tensor,由于我们对于w是要计算梯度的,所以对y_hat(即x*w)也是要计算梯度的,在看到整体的代码时,我们要先在脑子里构建计算图,才会对整个的代码有所理解
3. 对于tensor类我们可以构建计算图,使用backward函数可以自动进行反向传播,但是我们每次反向传播后,这个计算图就被释放了。由于每次进行神经网络计算的时候,可能每一次反向传播的计算过程都不一样,所以每次都要释放。
4. 注意梯度的计算是个标量,而w.grad是个tensor,要取其data才可以计算为标量,包括里面的item()函数也是。有的时候我们在写代码的时候,为了计算全局的损失函数会定义一个sum,但由于sum加上了tensor后会变为一个tensor,并对其构建计算图,由于没有设置他的requires_grad,所以不会反向传播后释放,导致这个tensor的计算图在内存中不断累积最后占满内存,所以我们要算应该算 l.data而不是l。
5. 由于每次虽然backward把计算图给释放了,但是对于每个权重计算的导数仍然存在,如果不清零就会导致后面backward时loss函数对w的导数变得有多个了,即把前面的loss函数对w的导数也算进来了。由于有的时候我们对于权重w的求导就是需要进行一个累加,所以默认设置为了不清零,如果要清零需要我们显式的进行清零。因此在w这个tensor中,不仅存放了权重值,而且存放了loss对w的权重值。
6.x.data和x.detach()在requires_grad=False,即不可求导时两者之间没有区别当requires_grad=True的时候的两者之间的是有不同:x.data不能被autograd追踪求微分,但是x.detach可以被autograd()追踪求导。
import torch
x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]
w = torch.Tensor([1.0]) # w的初值为1.0
w.requires_grad = True # 需要计算梯度
def forward(x):
return x*w # w是一个Tensor
def loss(x, y):
y_pred = forward(x)
return (y_pred - y)**2
print("predict (before training)", 4, forward(4).item())
for epoch in range(100):
for x, y in zip(x_data, y_data):
l =loss(x,y) # l是一个张量,tensor主要是在建立计算图 forward, compute the loss
l.backward() # backward,compute grad for Tensor whose requires_grad set to True
print('\tgrad:', x, y, w.grad.item())
w.data = w.data - 0.01 * w.grad.data # 权重更新时,需要用到标量,注意grad也是一个tensor
w.grad.data.zero_() # after update, remember set the grad to zero
print('progress:', epoch, l.item()) # 取出loss使用l.item,不要直接使用l(l是tensor会构建计算图)
print("predict (after training)", 4, forward(4).item())
(五)实例作业
构建二次模型 y=w1x2+w2x+b和损失函数 loss=(ŷ-y)2的计算图,并手动推到反向传播过程,并用pytorch代码实现