本系列文章配套代码获取有以下两种途径:
-
通过百度网盘获取:
链接:https://pan.baidu.com/s/1XuxKa9_G00NznvSK0cr5qw?pwd=mnsj
提取码:mnsj
-
前往GitHub获取:
https://github.com/returu/PyTorch
autograd的关闭:
>>> x = torch.ones(1)
>>> w = torch.rand(1 , requires_grad=True)
>>> y = w * x
# y依赖于w,因此其requires_grad属性会被自动设置为True
>>> x.requires_grad , w.requires_grad , y.requires_grad
(False, True, True)
# 关闭autograd
>>> with torch.no_grad():
... x = torch.ones(1)
... w = torch.rand(1 , requires_grad=True)
... y = w * x
...
# y依赖于w,即使w的requires_grad=True,但是y的requires_grad仍为False
>>> x.requires_grad , w.requires_grad , y.requires_grad
(False, True, False)
import torch
model = ... # some pre-trained model
data = ... # some input data
# Evaluate the model
with torch.no_grad():
output = model(data)
获取非叶子节点的梯度:
-
内存效率:深度学习模型通常包含大量的参数和中间计算结果。如果保存所有节点的梯度,将会消耗大量的内存,这在大规模模型或有限资源环境中是不可接受的。只保存叶子节点的梯度可以显著降低内存占用。 -
计算效率:在大多数情况下,我们只对模型的参数(即叶子节点)感兴趣,因为它们是优化过程中需要更新的部分。非叶子节点的梯度通常只是为了计算叶子节点梯度而存在的中间结果,没有直接用途。 -
灵活性:只保存叶子节点的梯度为开发者提供了更大的灵活性。例如,我们可以选择只更新部分参数,或者在需要时手动保留某些非叶子节点的梯度。
>>> x = torch.ones(1)
>>> w = torch.rand(1 , requires_grad=True)
>>> b = torch.rand(1 , requires_grad=True)
>>> y = w * x
>>> z = y**2 + b
>>> z.backward()
# 在反向传播过程中,非叶子节点的梯度不会被保存
>>> x.grad , w.grad , b.grad , y.grad
(None, tensor([1.1053]), tensor([1.]), None)
-
autograd.grad函数:
PyTorch的autograd模块提供了grad函数,可以用来计算并返回非叶子节点相对于某个标量函数的梯度。
>>> x = torch.ones(1)
>>> w = torch.rand(1 , requires_grad=True)
>>> b = torch.rand(1 , requires_grad=True)
>>> y = w * x
>>> z = y**2 + b
# z对y的梯度,隐式调用backward()
>>> torch.autograd.grad(z,y)
(tensor([0.8635]),)
# z = y**2 + b ,因此z对y的梯度 = 2y = w * x *2 ,与autograd.grad()结果一致
>>> w * x *2
tensor([0.8635], grad_fn=<MulBackward0>)
-
hook方法:
# hook方法的输入是梯度,没有返回值
>>> def variable_hook(grad):
... print("y的梯度:" , grad)
...
>>> x = torch.ones(1)
>>> w = torch.rand(1 , requires_grad=True)
>>> b = torch.rand(1 , requires_grad=True)
>>> y = w * x
>>> z = y**2 + b
# 注册hook
>>> hook_handle = y.register_hook(variable_hook)
>>> z.backward()
y的梯度:tensor([0.9646])
# z = y**2 + b ,因此z对y的梯度 = 2y = w * x *2 ,与hook方法结果一致
>>> w * x *2
tensor([0.9646], grad_fn=<MulBackward0>)
# 除非每次都要使用hook,否则要在用完之后移除hook
>>> hook_handle.remove()
实现线性回归:
-
生成数据:
# 设置随机数种子,保证运行结果一致
torch.manual_seed(2024)
def get_fake_data(num):
"""
生成随机数据,y = 0.5 * x +30并加上一些随机噪声
"""
x = torch.randint(low = -5, high=30, size=(num,)).to(dtype=torch.float32)
y = 0.5 * x +30 + torch.randn(num,)
return x,y
x , y = get_fake_data(num=11)
# 对输入数据做归一化处理
x_new = x * 0.1
-
定义函数:
# 定义模型:
def model(x , w , b):
return w * x + b
# 定义损失函数:
def loss_fn(y_p , y):
squared_diffs = (y_p - y)**2
return squared_diffs.mean()
-
循环训练,求解参数值:
# 损失对其输入(模型预测值)的导数
def dloss_fn(y_p , y):
dsq_diffs = 2*(y_p - y)/y_p.shape[0]
return print("梯度:" , dsq_diffs)
# 使用hook方法:
def variable_hook(grad):
print("y_p的梯度:" , grad)
def training_loop(n_epochs , learning_rate , params , x , y):
for epoch in range(1 , n_epochs+1):
#前向传播
y_p = model(x,*params)
print("=====")
# 两者计算的梯度结果一致
dloss_fn(y_p , y)
hook_handle = y_p.register_hook(variable_hook)
loss = loss_fn(y_p ,y)
# 梯度清零,可以在循环调用backward()之前任何时间完成
if params.grad is not None:
params.grad.zero_()
# 反向传播:自动计算梯度
loss.backward()
# 将更新封装在非梯度上下文中
with torch.no_grad():
params -= learning_rate * params.grad
if epoch % 10 ==0:
print(f"Epoch : {epoch} , Loss : {loss}")
print(f"Params : {params} , Grad : {params.grad}")
print("-----")
# # 梯度清零设置在此处也可以
# if params.grad is not None:
# params.grad.zero_()
return params
training_loop(n_epochs=1000 , learning_rate=1e-2 , params=torch.tensor([1.0 , 0.0],requires_grad=True) , x=x_new , y=y)
更多内容可以前往官网查看:
https://pytorch.org/
本篇文章来源于微信公众号: 码农设计师