网站建设中采用的技术方案,嘉兴网站建设网站建设,wordpress交易主题,怀化网站制作PyTorch的自动微分(autograd)
计算图
计算图是用来描述运算的有向无环图 计算图有两个主要元素#xff1a;结点#xff08;Node#xff09;和边#xff08;Edge#xff09; 结点表示数据#xff0c;如向量、矩阵、张量 边表示运算#xff0c;如加减乘除卷积等
用计算…PyTorch的自动微分(autograd)
计算图
计算图是用来描述运算的有向无环图 计算图有两个主要元素结点Node和边Edge 结点表示数据如向量、矩阵、张量 边表示运算如加减乘除卷积等
用计算图表示y (x w) * (w 1) 令 a x wb w 1 则 y a * b 使用计算图可以更方便的求导
在计算图中y对w求导就是找到所有y到w的边然后分别进行求导。
w torch.tensor([1.], requires_gradTrue)
x torch.tensor([2.], requires_gradTrue)
a torch.add(w, x)
b torch.add(w, 1)
y torch.mul(a, b)
y.backward()
print(w.grad)
tensor([5.])
叶子节点用户创建的结点成为叶子结点如X与W is_leaf指示张量是否为叶子结点
叶子结点的作用节省内存非叶子结点的梯度会被释放
print(is_leaf:\n, w.is_leaf, x.is_leaf, a.is_leaf, b.is_leaf, y.is_leaf)
is_leaf:
True True False False Falseprint(gradient:\n, w.grad, x.grad, a.grad, b.grad, y.grad)
gradient:tensor([5.]) tensor([2.]) None None None如果想要保存非叶子结点的梯度需要在反向传播前前使用a.retain_grad()(以张量a为例)
grad_fn记录创建该张量时所用的方法函数
print(grad_fn:\n, w.grad_fn, x.grad_fn, a.grad_fn, b.grad_fn, y.grad_fn)
grad_fn:None None AddBackward0 object at 0x00000254C1C6C7B8 AddBackward0 object at 0x00000254C334DDD8 MulBackward0 object at 0x00000254C334D828这里w和x是用户创建的所以grad_fn为Nonea、b、y都是有grad_fn的其grad_fn的作用主要是在求导时可以知道其是使用哪种计算方式得到的以便确认求导法则。
动态图 VS 静态图
根据计算图搭建方式可将计算图分为动态图和静态图 动态图运算和搭建同时进行特点灵活易调解以pytorch为代表 静态图先搭建图后运算特点高效但不灵活以tensorflow为代表
autograd–自动求导系统
torch.autograd.backward方法介绍
torch.autograd.backward自动求取梯度参数
inputs用于求导的张量如lossretain_graph保存计算图create_graph创建导数计算图用于高阶求导gradient多梯度权重
tensor.backward()调用的就是torch.autograd.backward()
在梯度求导之后计算图会被释放无法执行两次backward(),要想执行两次backward(),就需要将retain_graph设置为True。 一般中间结点会遇到需要多次backward的情况
下面代码解释多梯度权重
w torch.tensor([1.], requires_gradTrue)
x torch.tensor([2.], requires_gradTrue)
a torch.add(w, x)
b torch.add(w, 1)
y torch.mul(a ,b)
y1 torch.add(a, b) # dy1/dw 2
loss torch.cat([y, y1], dim0)grad_tensors torch.tensor([1, 1])
loss.backward(gradientgrad_tensors)
print(w.grad)
tensor([7.])这里同时求了dy/dw和dy1/dww.grad(1 x dy/dw) (1 x dy1/dw) 52
grad_tensors torch.tensor([1, 2])
loss.backward(gradientgrad_tensors)
print(w.grad)
tensor([7.])w.grad (1 x dy/dw) (2 x dy1/dw) 5 2x2 9
torch.autograd.grad()方法介绍
torch.autograd.grad()求取梯度
outputs用于求导的张量如lossinputs需要梯度的张量create_graph创建导数计算图用于高阶求导retain_graph保存计算图grad_outputs多梯度权重
# x需要设置requires_gradTrue才可以后续求导
x torch.tensor([3.], requires_gradTrue)
y torch.pow(x, 2)
# 创建导数计算图用于高阶求导即后续可以求二阶导数
grad_1 torch.autograd.grad(y, x, create_graphTrue)
print(grad_1)
(tensor([6.], grad_fnMulBackward0),)
# 求2阶导数
grad_2 torch.autograd.grad(grad_1[0], x)
print(grad_2)
(tensor([2.]),)autograd小贴士
梯度不会自动清零比如w会一直叠加手动清零w.grad.zero_()依赖于叶子结点的结点(比如a, b, y)其requires_gradTrue叶子结点不可以执行in-place操作原地操作在原始内存地址中改变数据。
自动求导系统实现
torch.Tensor 是包的核心类。如果将其属性 .requires_grad 设置为 True则会开始跟踪针对 tensor 的所有操作。完成计算后您可以调用 .backward() 来自动计算所有梯度。该张量的梯度将累积到 .grad 属性中。
如果你想计算导数你可以调用 Tensor.backward()。如果 Tensor 是标量即它包含一个元素数据则不需要指定任何参数backward()但是如果它有更多元素则需要指定一个gradient 参数 来指定张量的形状。
这两段话非常重要我们借助下面这个例子来帮助理解
import torchx torch.ones(2, 2, requires_gradTrue)
print(x)tensor([[1., 1.],[1., 1.]], requires_gradTrue)y x 2
print(y)tensor([[3., 3.],[3., 3.]], grad_fnAddBackward0)print(x.grad_fn) # None
print(y.grad_fn) # y 作为操作的结果被创建所以它有 grad_fn None
AddBackward0 object at 0x000001F7739B1BB0每个张量都有一个 .grad_fn属性保存着创建了张量的 Function 的引用如果用户自己创建张量则grad_fn 是 None 。
针对y做更多的操作
z y*y*3
out z.mean()
print(z)
print(out)
print(out.backward()) # 这里是没有返回值的
print(x.grad) # 需要先backward才能得到x的gradtensor([[27., 27.],[27., 27.]], grad_fnMulBackward0)
tensor(27., grad_fnMeanBackward0)
None
tensor([[4.5000, 4.5000],[4.5000, 4.5000]])这里的重点是x.grad的计算 通过这个例子理解上面的两段话就是这里x的requires_grad 属性为True后续跟踪针对x的所有操作之后调用backward自动计算所有梯度x的梯度累积到.grad属性中。
接下来我们再看一个pytorch自动微分的例子如果对于张量手动计算梯度的话代码是这样的
import torchdtype torch.float
device torch.device(cpu)
# device torch.device(cuda:0) # 取消注释以在GPU上运行# N是批量大小D_in是输入维度H是隐藏层维度D_out是输出维度
N, D_in, H, D_out 64, 1000, 100, 10# 创建随机输入和输出数据
x torch.randn(N, D_in, devicedevice, dtypedtype)
y torch.randn(N, D_out, devicedevice, dtypedtype)# 随机初始化权重
w1 torch.randn(D_in, H, devicedevice, dtypedtype)
w2 torch.randn(H, D_out, devicedevice, dtypedtype)learning_rate 1e-6
for t in range(500):# 前向传递计算预测yh x.mm(w1) # mm表示tensor相乘# 将输入input张量的每个元素夹紧到区间[min, max]h_relu h.clamp(min0)y_pred h_relu.mm(w2)# 计算和打印损失loss (y_pred - y).pow(2).sum().item() # 求平方和print(t, loss)# Backprop计算w1和w2相对于损耗的梯度grad_y_pred 2.0 * (y_pred - y)grad_w2 h_relu.t().mm(grad_y_pred)grad_h_relu grad_y_pred.mm(w2.t())grad_h grad_h_relu.clone()grad_h[h 0] 0grad_w1 x.t().mm(grad_h)# 使用梯度下降更新权重w1 - learning_rate * grad_w1w2 - learning_rate * grad_w2
这段代码最核心的点在于Backprop部分首先根据
loss(y_pred-y)^2
容易到loss对于y_pred的偏导数即grad_y_pred
而loss对于w2的偏导数即grad_w2就稍复杂一些涉及到矩阵求导、雅可比矩阵和链式法则。
根据在网上查阅资料得到查到一个矩阵求导相关的文章
https://blog.sina.com.cn/s/blog_51c4baac0100xuww.html
说实话没怎么看懂以前没有学过矩阵求导。
关于雅可比矩阵和链式法则 上面的内容简而言之雅可比矩阵是一阶偏导数以一定方式排列成的矩阵根据求导的链式法则(y对x的偏导)x(l对y的偏导) (l对x的偏导)。
现在可以想到
grad_w2 y_pred对w2的偏导 x loss对y_pred的偏导y_pred对w2的偏导 h_relu的转置即 grad_w2 h_relu.t().mm(grad_y_pred)
这里两个矩阵的前后顺序我不知道有没有什么规则但是根据其size可以pytorch官方文档给出前后顺序是合理的
h_relue.t()的size是(100, 64)
grad_y_pred的size是(64, 10)grad_h_relu loss对y_pred的偏导 x y_pred对h_relu的偏导
y_pred对h_relue的偏导 w2的转置
即grad_h_relue grad_y_pred.mm(w2.t())
而且你看这里相乘的两个矩阵顺序调整了调整的原因是因为
grad_y_pred的size是(64, 10)
w2.t()的size是(10, 100)
只有按照给出的位置才能得到相乘而且正好得到(64, 100)的grad_h_relu
同理对于loss对于w1的偏导
grad_w1 h_relu对于w1的偏导 x y_pred对h_relu的偏导 x loss对y_pred的偏导
后两项的乘积就是grad_h_relu
h_relue对w1的偏导 x.t()
而x.t()的size为(1000, 64)
所以grad_w1 x.t().mm(grad_h)
现在我们已经理解了上述求导和反向传播的过程如果使用pytorch的自动求导则可以利用下述方式来实现。
import torchdtype torch.float
device torch.device(cpu)
# device torch.device(cuda:0) # 取消注释以在GPU上运行# N是批量大小D_in是输入维度H是隐藏层维度D_out是输出维度
N, D_in, H, D_out 64, 1000, 100, 10# 创建随机输入和输出数据
x torch.randn(N, D_in, devicedevice, dtypedtype)
y torch.randn(N, D_out, devicedevice, dtypedtype)# 随机初始化权重
w1 torch.randn(D_in, H, devicedevice, dtypedtype, requires_gradTrue)
w2 torch.randn(H, D_out, devicedevice, dtypedtype, requires_gradTrue)learning_rate 1e-6
for t in range(500):# 前向传播使用tensors上的操作计算预测值y;# 由于w1和w2有requires_gradTrue涉及这些张量的操作将让PyTorch构建计算图# 从而允许自动计算梯度。由于我们不再手工实现反向传播所以不需要保留中间值的引用。y_pred x.mm(w1).clamp(min0).mm(w2)# 使用Tensors上的操作计算和打印丢失。# loss是一个形状为(1,)的张量# loss.item() 得到这个张量对应的python数值loss (y_pred - y).pow(2).sum()print(t, loss.item())# 使用autograd计算反向传播。这个调用将计算loss对所有requires_gradTrue的tensor的梯度。# 这次调用后w1.grad和w2.grad将分别是loss对w1和w2的梯度张量。loss.backward()# 使用梯度下降更新权重。对于这一步我们只想对w1和w2的值进行原地改变不想为更新阶段构建计算图# 所以我们使用torch.no_grad()上下文管理器防止PyTorch为更新构建计算图with torch.no_grad():w1 - learning_rate * w1.gradw2 - learning_rate * w2.grad# 反向传播后手动将梯度设置为零w1.grad.zero_()w2.grad.zero_()