|
计算图的作用范围 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