1.pytorch自动求导机制
在Pytorch中,Tensor是其最重要的数据类型。
每个Tensor都有一个requires_grad参数,代表这个Tensor是否会被跟踪自动微分。
这里我们先介绍一个简单的前向计算的例子:
import torch
# requires_grad代表此Tensor会被跟踪自动微分
x = torch.tensor([[1,2],[3,4]],dtype=torch.double,requires_grad=True)
y = x + 2
z = y * y * 3
out = z.mean()
上述计算的输入量为二维矩阵 x ,输出为标量 out ,对于这个公式,如何求解out对输入量 x 的导数呢?
在 Pytorh 中只需要如下代码,无需进行任何手工数学推导:
out.backward()
# 这里就是out对x的导数,out必须是标量,会自动求得y关于x的导数
print(x.grad)
tensor([[4.5000, 6.0000],
[7.5000, 9.0000]], dtype=torch.float64)
需要注意的是,上述例子中 out 为标量。
如果 out 为一个向量或者Tensor,在反向传播时需要传入一个系数矩阵,backward 方法会对 out 加权求和成一个标量,再进行反向传播。
在实际应用中,我们的 loss_function 一般也是标量。
#雅各比向量,y不是标量
x = torch.tensor([1, 2, 3], dtype=torch.double, requires_grad=True)
y = x * 2
while y.data.norm() < 1000:
y = y * 2
print(y)
#这里y不是标量,所以求导是针对y中每个元素做一次求导,并且需要传入一个系数
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)
2.利用Pytorch进行反向传播和梯度下降
这里我们介绍一下如何使用Pytoch定义一个numpy定义的两层神经网络模型并对数据进行拟合。在Pytorch中只需要一行代码即可完成反向传播。
import torch
#numpy 两层神经网络
# N为训练数据个数,D_in为输入层大小,H为隐藏层大小,D_out为输出层大小
# 我们定义一个输入层为1000维向量,隐藏层为100个神经元,输出层层10个神经元的神经网络
N, D_in, H, D_out = 64, 1000, 100, 10
# 随机创建训练数据
x = torch.randn(N,D_in)
y = torch.randn(N,D_out)
w1 = torch.randn(D_in,H,requires_grad = True)
w2 = torch.randn(H,D_out,requires_grad = True)
lr = 1e-6
for it in range(500):
# 前向传播
y_pred = x.mm(w1).mm(w2)
# loss
loss = (y_pred - y).pow(2).sum() # 计算损失
print("\r epoch={} loss={}".format(it,loss), end=' ')
# 反向传播
loss.backward()
# 如果计算需要求导,那就无法赋值,所以这里必须使用no_grad
with torch.no_grad():
w1 -= lr * w1.grad
w2 -= lr * w2.grad
# 清空导数,否者下次反向传播求得的导数会累加到这里
w1.grad.zero_()
w2.grad.zero_()
上面这种写法还是非常接近于 Numpy 的写法,我们只是将 Numpy 矩阵运算换成了 Pytoch 的 Tensor 运算,并且利用了 Pytorch 的自动求导机制来求解梯度。
实际在 Pytorch 中,有更加优雅的写法,我们可以使用 optimizer 来进行梯度下降,使用它内置的 MSELoss 来实现均方差损失函数:
N, D_in, H, D_out = 64, 1000, 100, 10
# 随机创建训练数据(在Pytorch中,一般把batch放在第一个维度,这一点与上面的不同)
x = torch.randn(N,D_in)
y = torch.randn(N,D_out)
model = torch.nn.Sequential(
torch.nn.Linear(D_in,H),
torch.nn.Linear(H, D_out),
)
# 定义学习率
lr = 1e-4
loss_fn = torch.nn.MSELoss(reduction='sum')
# 梯度下降策略
optimizer = torch.optim.Adam(model.parameters(),lr=lr)
for it in range(500):
y_pred = model(x) # 前向传播
loss = loss_fn(y_pred,y)
print("\r epoch={} loss={}".format(it,loss), end=' ')
# 注意这一步不能少
optimizer.zero_grad()
loss.backward()
optimizer.step()
在实际应用中,我们可以将前向传播过程封装成一个类,下面介绍两种以对象形式实现前向传播,并使用自动求导 optimizer 来实现模型的梯度下降的方法:
方式1.使用 Sequential
N, D_in, H, D_out = 64, 1000, 100, 10
# 随机创建训练数据
x = torch.randn(N,D_in)
y = torch.randn(N,D_out)
# 方式1,使用Sequential(序列)函数来构建我们的model
model = torch.nn.Sequential(
torch.nn.Linear(D_in,H),
torch.nn.Linear(H, D_out),
)
#torch.nn.init.normal_(model[0].weight)
lr = 1e-4
loss_fn = torch.nn.MSELoss(reduction='sum')
# 梯度下降策略
optimizer = torch.optim.Adam(model.parameters(),lr=lr)
for it in range(500):
y_pred = model(x) #model.forward()
loss = loss_fn(y_pred,y)
print("\r epoch={} loss={}".format(it,loss), end=' ')
optimizer.zero_grad()
loss.backward()
optimizer.step()
方式2.继承 torch.nn.Module
N, D_in, H, D_out = 64, 1000, 100, 10
# 随机创建训练数据
x = torch.randn(N,D_in)
y = torch.randn(N,D_out)
# 方式2,实现自己的类,继承torch.nn.Module
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_pred = self.linear2(self.linear1(X))
return y_pred
model = TwoLayerNet(D_in, H, D_out)
lr = 1e-4
loss_fn = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.Adam(model.parameters(),lr=lr)
for it in range(500):
y_pred = model(x) #model.forward()
loss = loss_fn(y_pred,y)
print("\r epoch={} loss={}".format(it,loss), end=' ')
optimizer.zero_grad()
loss.backward()
optimizer.step()