pytorch求导的初步认识
requires_grad
tensor(data, dtype=None, device=None, requires_grad=False)
requires_grad是torch.tensor类的一个属性。如果设置为True
,它会告诉PyTorch跟踪对该张量的操作,允许在反向传播期间计算梯度。
x.requires_grad 判断一个tensor是否可以求导,返回布尔值
叶子变量-leaf variable
- 对于requires_grad=False 的张量,我们约定俗成地把它们归为叶子张量。
- 对于requires_grad为True的张量,如果他们是由用户创建的,则它们是叶张量。
如果某一个叶子变量,开始时不可导的,后面想设置它可导,该怎么办?
x.requires_grad_(True/False) 设置tensor的可导与不可导
注意:这种方法只适用于设置叶子变量,否则会出现如下错误
x = torch.tensor(2.0, requires_grad=True)
y = torch.pow(x, 2)
z = torch.add(y, 3)
z.backward()
print(x.grad)
print(y.grad)
tensor(4.)
None
-
创建一个浮点型张量x,其值为2.0,并设置
requires_grad=True
,使PyTorch可以跟踪x的计算历史并允许计算它的梯度。 -
创建一个新张量y,y是x的平方。
-
创建一个新张量z,z是y和3的和。
-
调用
z.backward()
进行反向传播,计算z关于x的梯度。 -
打印x的梯度,应该是2*x=4.0。
-
试图打印y的梯度。但是,PyTorch默认只计算并保留叶子节点的梯度。非叶子节点的梯度在计算过程中会被释放掉,因此y的梯度应该为None。
保留中间变量的梯度
tensor.retain_grad()
retain_grad()
和retain_graph
是用来处理两个不同的情况
-
retain_grad()
: 用于保留非叶子节点的梯度。如果你想在反向传播结束后查看或使用非叶子节点的梯度,你应该在非叶子节点上调用.retain_grad()
。 -
retain_graph
: 当你调用.backward()
时,PyTorch会自动清除计算图以释放内存。这意味着你不能在同一个计算图上多次调用.backward()
。但是,如果你需要多次调用.backward()
(例如在某些特定的优化算法中),你可以在调用.backward()
时设置retain_graph=True
来保留计算图。
.grad
通过tensor的grad属性查看所求得的梯度值。
.grad_fn
在PyTorch中,.grad_fn
属性是一个引用到创建该Tensor的Function对象。也就是说,这个属性可以告诉你这个张量是如何生成的。对于由用户直接创建的张量,它的.grad_fn
是None
。对于由某个操作创建的张量,.grad_fn
将引用到一个与这个操作相关的对象。
import torch
x = torch.tensor([1.0, 2.0], requires_grad=True)
y = x * 2
z = y.mean()
print(x.grad_fn)
print(y.grad_fn)
print(z.grad_fn)
这里,x
是由用户直接创建的,所以x.grad_fn
是None
。y
是通过乘法操作创建的,所以y.grad_fn
是一个MulBackward0
对象,这表明y
是通过乘法操作创建的。z
是通过求平均数操作创建的,所以z.grad_fn
是一个MeanBackward0
对象。
pytorch自动求导实现神经网络
numpy手动实现
import numpy as np
import matplotlib.pyplot as plt
N, D_in, H, D_out = 64, 1000, 100, 10 # 64个训练数据(只是一个batch),输入是1000维,hidden是100维,输出是10维
'''随机创建一些训练数据'''
X = np.random.randn(N, D_in)
y = np.random.randn(N, D_out)
W1 = np.random.randn(D_in, H) # 1000维转成100维
W2 = np.random.randn(H, D_out) # 100维转成10维
learning_rate = 1e-6
all_loss = []
epoch = 500
for t in range(500): # 做500次迭代
'''前向传播(forward pass)'''
h = X.dot(W1) # N * H
h_relu = np.maximum(h, 0) # 激活函数,N * H
y_hat = h_relu.dot(W2) # N * D_out
'''计算损失函数(compute loss)'''
loss = np.square(y_hat - y).sum() # 均方误差,忽略了÷N
print("Epoch:{} Loss:{}".format(t, loss)) # 打印每个迭代的损失
all_loss.append(loss)
'''后向传播(backward pass)'''
# 计算梯度(此处没用torch,用最普通的链式求导,最终要得到 d{loss}/dX)
grad_y_hat = 2.0 * (y_hat - y) # d{loss}/d{y_hat},N * D_out
grad_W2 = h_relu.T.dot(grad_y_hat) # 看前向传播中的第三个式子,d{loss}/d{W2},H * D_out
grad_h_relu = grad_y_hat.dot(W2.T) # 看前向传播中的第三个式子,d{loss}/d{h_relu},N * H
grad_h = grad_h_relu.copy() # 这是h>0时的情况,d{h_relu}/d{h}=1
grad_h[h < 0] = 0 # d{loss}/d{h}
grad_W1 = X.T.dot(grad_h) # 看前向传播中的第一个式子,d{loss}/d{W1}
'''参数更新(update weights of W1 and W2)'''
W1 -= learning_rate * grad_W1
W2 -= learning_rate * grad_W2
plt.plot(all_loss)
plt.xlabel("epoch")
plt.ylabel("Loss")
plt.show()
pytorch自动实现
import torch
N, D_in, H, D_out = 64, 1000, 100, 10 # 64个训练数据(只是一个batch),输入是1000维,hidden是100维,输出是10维
'''随机创建一些训练数据'''
X = torch.randn(N, D_in)
y = torch.randn(N, D_out)
W1 = torch.randn(D_in, H, requires_grad=True) # 1000维转成100维
W2 = torch.randn(H, D_out, requires_grad=True) # 100维转成10维
learning_rate = 1e-6
for t in range(500): # 做500次迭代
'''前向传播(forward pass)'''
y_hat = X.mm(W1).clamp(min=0).mm(W2) # N * D_out
'''计算损失函数(compute loss)'''
loss = (y_hat - y).pow(2).sum() # 均方误差,忽略了÷N,loss就是一个计算图(computation graph)
print("Epoch:{} Loss:{}".format(t, loss.item())) # 打印每个迭代的损失
'''后向传播(backward pass)'''
loss.backward()
'''参数更新(update weights of W1 and W2)'''
with torch.no_grad():
W1 -= learning_rate * W1.grad
W2 -= learning_rate * W2.grad
W1.grad.zero_()
W2.grad.zero_()
pytorch手动实现
import torch
import matplotlib.pyplot as plt
N, D_in, H, D_out = 64, 1000, 100, 10 # 64个训练数据(只是一个batch),输入是1000维,hidden是100维,输出是10维
'''随机创建一些训练数据'''
X = torch.randn(N, D_in)
y = torch.randn(N, D_out)
W1 = torch.randn(D_in, H) # 1000维转成100维
W2 = torch.randn(H, D_out) # 100维转成10维
learning_rate = 1e-6
all_loss = []
for t in range(500): # 做500次迭代
'''前向传播(forward pass)'''
h = X.mm(W1) # N * H
h_relu = h.clamp(min=0) # 激活函数,N * H
y_hat = h_relu.mm(W2) # N * D_out
'''计算损失函数(compute loss)'''
loss = (y_hat - y).pow(2).sum().item() # 均方误差,忽略了÷N
print("Epoch:{} Loss:{}".format(t, loss)) # 打印每个迭代的损失
all_loss.append(loss)
'''后向传播(backward pass)'''
# 计算梯度(此处没用torch,用最普通的链式求导,最终要得到 d{loss}/dX)
grad_y_hat = 2.0 * (y_hat - y) # d{loss}/d{y_hat},N * D_out
grad_W2 = h_relu.t().mm(grad_y_hat) # 看前向传播中的第三个式子,d{loss}/d{W2},H * D_out
grad_h_relu = grad_y_hat.mm(W2.t()) # 看前向传播中的第三个式子,d{loss}/d{h_relu},N * H
grad_h = grad_h_relu.clone() # 这是h>0时的情况,d{h_relu}/d{h}=1
grad_h[h < 0] = 0 # d{loss}/d{h}
grad_W1 = X.t().mm(grad_h) # 看前向传播中的第一个式子,d{loss}/d{W1}
'''参数更新(update weights of W1 and W2)'''
W1 -= learning_rate * grad_W1
W2 -= learning_rate * grad_W2
plt.plot(all_loss)
plt.xlabel("epoch")
plt.ylabel("Loss")
plt.show()
torch.nn实现
import torch
import torch.nn as nn # 各种定义 neural network 的方法
N, D_in, H, D_out = 64, 1000, 100, 10 # 64个训练数据(只是一个batch),输入是1000维,hidden是100维,输出是10维
'''随机创建一些训练数据'''
X = torch.randn(N, D_in)
y = torch.randn(N, D_out)
model = torch.nn.Sequential(
torch.nn.Linear(D_in, H, bias=True), # W1 * X + b,默认True
torch.nn.ReLU(),
torch.nn.Linear(H, D_out)
)
# model = model.cuda() #这是使用GPU的情况
loss_fn = nn.MSELoss(reduction='sum')
learning_rate = 1e-4
for t in range(500): # 做500次迭代
'''前向传播(forward pass)'''
y_hat = model(X) # model(X) = model.forward(X), N * D_out
'''计算损失函数(compute loss)'''
loss = loss_fn(y_hat, y) # 均方误差,忽略了÷N,loss就是一个计算图(computation graph)
print("Epoch:{} Loss:{}".format(t, loss.item())) # 打印每个迭代的损失
'''后向传播(backward pass)'''
loss.backward()
'''参数更新(update weights of W1 and W2)'''
with torch.no_grad():
for param in model.parameters():
param -= learning_rate * param.grad # 模型中所有的参数更新
model.zero_grad()
torch.nn的继承类
import torch
import torch.nn as nn # 各种定义 neural network 的方法
from torchsummary import summary
# pip install torchsummary
N, D_in, H, D_out = 64, 1000, 100, 10 # 64个训练数据(只是一个batch),输入是1000维,hidden是100维,输出是10维
'''随机创建一些训练数据'''
X = torch.randn(N, D_in)
y = torch.randn(N, D_out)
'''定义两层网络'''
class TwoLayerNet(torch.nn.Module):
def __init__(self, D_in, H, D_out):
super(TwoLayerNet, self).__init__()
# 定义模型结构
self.linear1 = torch.nn.Linear(D_in, H, bias=False)
self.linear2 = torch.nn.Linear(H, D_out, bias=False)
def forward(self, x):
y_hat = self.linear2(self.linear1(X).clamp(min=0))
return y_hat
model = TwoLayerNet(D_in, H, D_out)
loss_fn = nn.MSELoss(reduction='sum')
learning_rate = 1e-4
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
for t in range(500): # 做500次迭代
'''前向传播(forward pass)'''
y_hat = model(X) # model.forward(), N * D_out
'''计算损失函数(compute loss)'''
loss = loss_fn(y_hat, y) # 均方误差,忽略了÷N,loss就是一个计算图(computation graph)
print("Epoch:{} Loss:{}".format(t, loss.item())) # 打印每个迭代的损失
optimizer.zero_grad() # 求导之前把 gradient 清空
'''后向传播(backward pass)'''
loss.backward()
'''参数更新(update weights of W1 and W2)'''
optimizer.step() # 一步把所有参数全更新
print(summary(model, (64, 1000)))