计算图的作用范围 pytorch计算图及求导由pytorch框架自动实现 从import torch这一行开始,计算图就开始生效了, 其后所有requires_grad=True的tensor都会自动被添加到计算图中 计算图的结构 建模的核心是模型设计,所有的tensor都要归于模型model model与target要作为参数放入损失函数loss_fn loss = loss_fn(模型输出,target) 最终计算出一个模型输出 与 目标标签的 偏差 损失函数才是整张计算图的终点 就像一棵倒着的多叉树,损失函数是根 |
求导的大概过程 pytorch求导的过程,则是先从损失函数开始,再到模型, 这与数据先流过模型,再流入损失函数的方向刚好相反, 所以,model的计算方法叫正向传播forward 而自损失函数开始求导时,叫反向传播backward loss.backward()对整个计算图, 因为只有模型中有可变参数定义, 实际就是对模型中的可导参数求一遍导数 |
nn.Parameter作用 nn.Parameter 是一个迭代器,向其加入参数,就可以通过model.parameters()访问了 模型设计时,init方法会初始化一系列的类模板, 这些类模板的参数,都会自动加入model.parameters() 后续会将model.parameters(),学习率 作为参数供优化器调用 如果是自己设计的参数,就需要手工将之加入nn.Parameter nn.Parameter用法示例 import torch from torch import nn class DLModel(nn.Module): def __init__(self,in_features, out_features): super().__init__() self.w = nn.Parameter(torch.ones(2,1, dtype=torch.float32),requires_grad=True) self.b = nn.Parameter(torch.ones(1, dtype=torch.float32),requires_grad=True) def forward(self,X): x = X@self.w + self.b return x model = DLModel(in_features=2,out_features=1) for param in model.parameters(): print(param) Parameter containing: tensor([[1.], [1.]], requires_grad=True) Parameter containing: tensor([1.], requires_grad=True) |
|
|
import torch w1 = torch.tensor([1.0,1.0],requires_grad=True) b = torch.tensor(1.0,requires_grad=True) y = w1 + b # tensor([2., 2.], grad_fn= b.grad #tensor(2.) b.grad这梯度是2不是1,这一点就有点跳跃... b.ndim #0 b实际上是一个标题,维度为0, w1+b首先会自动触发广播机制,b由1.0转化为[1.0,1.0],然后进行[1.0,1.0]+[1.0,1.0]向量的加法 向量的加法是各种元素分别个加 w1[0]+b + w1[1]+b = w1[0] + w1[1] + 2b 深度学习全是矩阵运算,矩阵计算的最小单位是向量计算, 向量计算最终,还是要转化为 相乘相加 ,这里b被加了2次 向量有多少个维度,b就会被加多少次 |
模型验证 import torch from torch import nn class DLModel(nn.Module): def __init__(self,in_features, out_features): super().__init__() self.w = nn.Parameter(torch.ones(2,1, dtype=torch.float32),requires_grad=True) self.b = nn.Parameter(torch.ones(1, dtype=torch.float32),requires_grad=True) def forward(self,X): x = X@self.w + self.b return x model = DLModel(in_features=2,out_features=1) X = torch.tensor([[0.1,0.1]],dtype=torch.float32) y = torch.tensor([[1]],dtype=torch.float32) y_out = model(X) y_out 查看梯度 # 没有backward之前,param.grad没有data属性,所以下面的代码执行就报错 # AttributeError: 'NoneType' object has no attribute 'data' for param in model.parameters(): print(param.grad.data) 损失计算 # 为了方便理解及计算,这里采用L1Loss,就是绝对值,一个一次函数 loss_fn = nn.L1Loss() loss = loss_fn(y_out,y) loss 梯度下降,也就是对整个计算图进行求导运算 loss.backward() 再次查看梯度 for param in model.parameters(): print(param.grad.data) tensor([[0.1000], [0.1000]]) tensor([1.]) 梯度计算理解 L1Loss,计算如下 loss = |y_out - y| = |x@w + b - y | = |x1*w1 + x2*w2 + b - y| 这里x=[0.1,0.1], b=1, w=[1,1], y=1 0.1*1 + 0.1*1 + 1 - 1 = 0.2 是正数 对w1求偏导,其结果就是x1 对w2求偏导,其结果就是x2 |
|
|
|
可导参数梯度计算默认累加
for i in range(3): X = torch.tensor([[0.1,0.1]],dtype=torch.float32) y = torch.tensor([[1]],dtype=torch.float32) y_out = model(X) loss_fn = nn.L1Loss() loss = loss_fn(y_out,y) loss.backward() for param in model.parameters(): print(param.grad.data) tensor([[0.2000], [0.2000]]) tensor([2.]) tensor([[0.3000], [0.3000]]) tensor([3.]) tensor([[0.4000], [0.4000]]) tensor([4.]) 由于损失函数是一次函数,其梯度就是常量x[0.1,0.1] 不管参数是多少,梯度就是x, 但这里梯度每次都增加0.1,就是当前梯度累加到了前一次的梯度上了
解决方法:每次梯度下降前清空现有可导参数的梯度
import torch from torch import nn class DLModel(nn.Module): def __init__(self,in_features, out_features): super().__init__() self.w = nn.Parameter(torch.ones(2,1, dtype=torch.float32),requires_grad=True) self.b = nn.Parameter(torch.ones(1, dtype=torch.float32),requires_grad=True) def forward(self,X): x = X@self.w + self.b return x model = DLModel(in_features=2,out_features=1) X = torch.tensor([[0.1,0.1]],dtype=torch.float32) y = torch.tensor([[1]],dtype=torch.float32) y_out = model(X) loss_fn = nn.L1Loss() loss = loss_fn(y_out,y) has_grad = False for i in range(3): X = torch.tensor([[0.1,0.1*(i+1)]],dtype=torch.float32) print("X:",X) y = torch.tensor([[1]],dtype=torch.float32) y_out = model(X) loss_fn = nn.L1Loss() loss = loss_fn(y_out,y) for param in model.parameters(): if has_grad: param.grad.data.zero_() loss.backward() has_grad = True for param in model.parameters(): print(param.grad.data) break # 只看参数w的梯度 print("----------------------------") X: tensor([[0.1000, 0.1000]]) tensor([[0.1000], [0.1000]]) ---------------------------- X: tensor([[0.1000, 0.2000]]) tensor([[0.1000], [0.2000]]) ---------------------------- X: tensor([[0.1000, 0.3000]]) tensor([[0.1000], [0.3000]]) ----------------------------
先梯度下降产生一个梯度
import torch from torch import nn class DLModel(nn.Module): def __init__(self,in_features, out_features): super().__init__() self.w = nn.Parameter(torch.ones(2,1, dtype=torch.float32),requires_grad=True) self.b = nn.Parameter(torch.ones(1, dtype=torch.float32),requires_grad=True) def forward(self,X): x = X@self.w + self.b return x model = DLModel(in_features=2,out_features=1) X = torch.tensor([[0.1,0.1]],dtype=torch.float32) y = torch.tensor([[1]],dtype=torch.float32) y_out = model(X) loss_fn = nn.L1Loss() loss = loss_fn(y_out,y) loss.backward() for param in model.parameters(): print(param.grad.data) break # 只看参数w的梯度 tensor([[0.1000], [0.1000]])
优化器清空梯度
optim = torch.optim.Adam(params=model.parameters(),lr=0.001) optim.zero_grad() for param in model.parameters(): print(param.grad.data) break # 只看参数w的梯度 tensor([[0.], [0.]]) 被清空的梯度与没有梯度不同, 没有梯度是没有tensor.grad.data这个属性 被清空梯度的是tensor.grad.data为0
优化器可以对没有梯度的tensor执行清空操作
import torch from torch import nn class DLModel(nn.Module): def __init__(self,in_features, out_features): super().__init__() self.w = nn.Parameter(torch.ones(2,1, dtype=torch.float32),requires_grad=True) self.b = nn.Parameter(torch.ones(1, dtype=torch.float32),requires_grad=True) def forward(self,X): x = X@self.w + self.b return x model = DLModel(in_features=2,out_features=1) X = torch.tensor([[0.1,0.1]],dtype=torch.float32) y = torch.tensor([[1]],dtype=torch.float32) y_out = model(X) optim = torch.optim.Adam(params=model.parameters(),lr=0.001) optim.zero_grad() 这还没有没有进行梯度下降,但优化器可以去清空梯度, 显然,是代码内部做了条件判断, 类似于前面的代码: for param in model.parameters(): if has_grad: param.grad.data.zero_()
梯度变化的来源
优化器使用梯度下降法更新参数,大致就是 w = w - 学习率*w.梯度 这个w.梯度是变化的梯度 它的变化来源主要有二: 1. 函数及w本身 2. 数据,变量w在不同的数据处有不同的梯度 比如,y = 3w*w与y=2w*w 的导函数分别是 y = 6w 与 y=4w 这里,主要因素是数据, 不同的数据,会让w产生不同的变化,这种变化会归于w梯度中 再通过 “w = w - 学习率*w.梯度 ” 来调整w
更新参数的提前
提前1:有数据 提前2:从损失开始求导,计算梯度,才有梯度,这个过程就是反向传播 1. 数据代入模型得到模型输出y_out = model(X) 2. 模型输出与标签代入损失函数,形成以损失函数为根的计算图, loss = loss_fn(y_out,标签) 3. loss.backward() 4. 优化器更新参数,更新后清空参数梯度
优化器更新参数
import torch from torch import nn class DLModel(nn.Module): def __init__(self,in_features, out_features): super().__init__() self.w = nn.Parameter(torch.ones(2,1, dtype=torch.float32),requires_grad=True) self.b = nn.Parameter(torch.ones(1, dtype=torch.float32),requires_grad=True) def forward(self,X): x = X@self.w + self.b return x model = DLModel(in_features=2,out_features=1) X = torch.tensor([[0.1,0.1]],dtype=torch.float32) y = torch.tensor([[1]],dtype=torch.float32) y_out = model(X) for param in model.parameters(): print(param) Parameter containing: tensor([[1.], [1.]], requires_grad=True) Parameter containing: tensor([1.], requires_grad=True) loss_fn = nn.L1Loss() loss = loss_fn(y_out,y) loss.backward() optim = torch.optim.Adam(params=model.parameters(),lr=0.001) optim.step() # 更新一次参数后,参数就变化一点 for param in model.parameters(): print(param) Parameter containing: tensor([[0.9990], [0.9990]], requires_grad=True) Parameter containing: tensor([0.9990], requires_grad=True)
SGD:随机梯度下降
import torch from torch import nn class DLModel(nn.Module): def __init__(self,in_features, out_features): super().__init__() self.w = nn.Parameter(torch.ones(2,1, dtype=torch.float32),requires_grad=True) self.b = nn.Parameter(torch.ones(1, dtype=torch.float32),requires_grad=True) def forward(self,X): x = X@self.w + self.b return x model = DLModel(in_features=2,out_features=1) X = torch.tensor([[0.1,0.1]],dtype=torch.float32) y = torch.tensor([[1]],dtype=torch.float32) y_out = model(X)
for param in model.parameters(): print(param) Parameter containing: tensor([[1.], [1.]], requires_grad=True) Parameter containing: tensor([1.], requires_grad=True) loss_fn = nn.L1Loss() loss = loss_fn(y_out,y) loss.backward() for param in model.parameters(): print(param.grad.data) tensor([[0.1000], [0.1000]]) tensor([1.])
随机梯度下降
optim = torch.optim.SGD(params=model.parameters(),lr=0.001) optim.step() for param in model.parameters(): print(param) Parameter containing: tensor([[0.9999], [0.9999]], requires_grad=True) Parameter containing: tensor([0.9990], requires_grad=True) w = w - 学习率*w.grad.data = 1 - 0.001*0.1 = 0.9999 然而,对于偏置b, 它的结果是0.9990与0.9999不符, 这就体现出了随机性
X = torch.tensor([[0.1,0.1]],dtype=torch.float32) y = torch.tensor([[1]],dtype=torch.float32) y_out = model(X) loss = loss_fn(y_out,y) loss.backward() optim.step() optim.zero_grad() for param in model.parameters(): print(param) Parameter containing: tensor([[0.9997], [0.9997]], requires_grad=True) Parameter containing: tensor([0.9970], requires_grad=True) 梯度是0.1,下一次参数更新后,本应该是0.9998,而这里是0.9997, 说明SGD并不是按步就班地执行梯度下降的公式,而在公式中加入了随机因子
自适应
这个自适应体现在, 开始的时候参数是随机初始化的, 认为该参数距离最优化参数,有较大的距离, 所以,开始的步子应该大一些, 优化一段时间后,再逐步减小步子的长度 以免错过最优解
开始时步子大
import torch from torch import nn class DLModel(nn.Module): def __init__(self,in_features, out_features): super().__init__() self.w = nn.Parameter(torch.ones(2,1, dtype=torch.float32),requires_grad=True) self.b = nn.Parameter(torch.ones(1, dtype=torch.float32),requires_grad=True) def forward(self,X): x = X@self.w + self.b return x model = DLModel(in_features=2,out_features=1) X = torch.tensor([[0.1,0.1]],dtype=torch.float32) y = torch.tensor([[1]],dtype=torch.float32) y_out = model(X) loss_fn = nn.L1Loss() loss = loss_fn(y_out,y) loss.backward()
optim = torch.optim.Adam(params=model.parameters(),lr=0.001) optim.step()
for param in model.parameters(): print(param) Parameter containing: tensor([[0.9990], [0.9990]], requires_grad=True) Parameter containing: tensor([0.9990], requires_grad=True)
X = torch.tensor([[0.1,0.1]],dtype=torch.float32) y = torch.tensor([[1]],dtype=torch.float32) y_out = model(X) loss = loss_fn(y_out,y) loss.backward() optim.step() optim.zero_grad() for param in model.parameters(): print(param) Parameter containing: tensor([[0.9980], [0.9980]], requires_grad=True) Parameter containing: tensor([0.9980], requires_grad=True)
w = w - 学习率*w.grad.data = 1 - 0.001*0.1 = 0.9999 而Adam更像是使用的0.01的学习率 w = w - 学习率*w.grad.data = 1 - 0.01*0.1 = 0.9990 开始以10倍预定速度在更新参数
涉及模型或者说涉及任何可导tensor都会自动关联到计算图上
y_out = model(X) loss = loss_fn(y_out,标签) loss.backward() 梯度下降是从损失函数开始的,然后反向传播回去,那到什么时候终止? 模型是可以套模型的,任何调用模型的地方,或者模型内部, 都可以看作一层层的函数嵌套, 换句话说,只要能关联的上,都会被求导 torch.no_grad可以指定一个范围, 不考虑梯度计算,即不修改模型参数,比如,模型预测时
模型预测
def predict(model, X): """模型预测 """ with torch.no_grad(): y_pred = model(X) return y_pred