1.向前计算
对于pytorch中的一个tensor,如果设置它的属性.requires_grad
为True
,那么它将会追踪对于该张量的所有操作,或者可以理解为,这个tensor是一个参数,后续会被计算梯度,更新该参数。
1.1计算过程
假设有一下条件(1/4表示平均值,Xi中有4个数),使用torch完成其向前计算的过程
如果x为参数,需要对其进行梯度的计算和更新。那么,在最开始随机设置x的值的过程中,需要设置它的requires_grad属性值为True,其默认值为False。
从上述代码可以看出:
- x的requires_grad属性为True
- 之后的每次计算都会修改其grad_fn属性,用来记录做过的操作
1. 通过这个函数和grad_fn能够组合才能一个和前一小节类似的计算图
1.2requires_grad和grad_fn
注意:
为了防止跟踪历史记录(和使用内存),可以将代码块包装在with torch.no_grad():
中(其中的操作不会被追踪,看上图,并且其requires_grad也会被修改)。在评估模型时特别有用,因为模型可能具有requires_grad = True
的可训练参数,但是我们不需要在此过程中对他们进行梯度计算。
1.3梯度计算
反向传播:output.backward()
获取梯度:x.gard
对于1.1中的out而言,我们可以使用backward方法来进行反向传播,计算梯度
o
u
t
.
b
a
c
k
w
a
r
d
(
)
out.backward()
out.backward(),此时便能够求出倒数
d
o
u
t
/
d
x
dout/dx
dout/dx,调用
x
.
g
r
a
d
x.grad
x.grad能够获取导数值。
注意:在输出是一个标量的情况下,我们可以调用输出
t
e
n
s
o
r
tensor
tensor的
b
a
c
k
w
a
r
d
(
)
backward()
backward()方法,但是在数据是一个向量的时候,调用
b
a
c
k
w
a
r
d
(
)
backward()
backward()的时候还需要传入其他参数。
loss.backward()
就是根据损失函数,对参数requires_grad=True
去计算它的梯度,并且把它累加保存到x.gard
,此时还并未更新其梯度。所以,每次反向传播之前需要先把梯度置为0之后再计算。
2.线性回归实现
下面会用torch实现一个简单的线性回归
假设基础模型是
y
=
w
x
+
b
y=wx+b
y=wx+b,其中w和b均为参数,我们使用
y
=
3
x
+
0.8
y=3x+0.8
y=3x+0.8来构造数据x和y。主要步骤如下:
- 准备数据
- 计算预测值
- 计算损失,把参数的梯度置为0,进行反向传播
- 更新参数
import torch
import matplotlib.pyplot as plt
learning_rate = 0.01
# 1. 准备数据
# y = 3x+0.8
x = torch.rand([500,1])
y_true = 3*x + 0.8
# 2. 通过模型计算y_predict
w = torch.rand([1,1],requires_grad=True)
# 只有浮点型的tensor才可以使用requires_grad=True属性,所以需要指定dtype=torch.float32
b = torch.tensor(0,requires_grad=True,dtype=torch.float32)
# 4. 通过循环,反向传播,更新参数
for i in range(3000):
# 3. 计算loss
# matmul计算矩阵乘积
y_predict = torch.matmul(x,w) + b
# 取均值方差当做损失函数
loss = (y_true-y_predict).pow(2).mean()
if w.grad is not None:
w.grad.data.zero_()
if b.grad is not None:
b.grad.data.zero_()
loss.backward() # 反向传播
w.data = w.data - learning_rate*w.grad
b.data = b.data - learning_rate*b.grad
if i %50 == 0:
print("w:{}, b:{}, loss:{}".format(w.item(),b.item(),loss.item()))
plt.figure(figsize=[20,8])
plt.scatter(x.numpy().reshape(-1),y_true.numpy().reshape(-1))
y_predict = torch.matmul(x,w) + b
plt.plot(x.numpy().reshape(-1),y_predict.detach().numpy().reshape(-1))
plt.show()
模型训练上3000次后得到w和b接近相等
3.Pytorch完成模型常用API
在上面部分,我们实现了通过torch完成反向传播和参数更新,当参数较多时,使用上面方法有点复杂。下面了解以下常用的API
3.1 nn.Module
nn.Module
是torch.nn
提供的一个类,是pytorch中我们自定义网络的一个基类。当我们自定义网络的时候,有两个方法需要注意:
__init__
需要调用super
方法,继承父类的属性和方法。forward
方法必须实现,用来定义我们的网络的向前计算的过程。
用前面的 y = w x + b y=wx+b y=wx+b的模型举例如下:
class lr(nn.Module):
def __init__(self):
super(lr,self).__init__() # 继承父类init的参数
self.linear = nn.Linear(1,1)
def forward(self, x):
out = self.linear(x)
return out
注意:
nn.Linear
为torch预定义好的线性模型,也称为全连接层,传入的参数为输入的数量,输出的数量(in_features,out_features
),其中不算batch_sizer的列数
2.nn.Module
定义了__call__
方法,实现的就是调用forward方法 ,能够直接被传入单数调用,实际上调用的就是forward方法并传入参数。
# 实例化模型
model = lr()
# 传入数据,计算结果
predict = model(x)
3.2优化器类
优化器(optimizer),可以理解为torch为我们封装的用来进行更新参数的方法,比如常见的随机梯度下降。
优化器都是由torch.optim
提供的,例如:
1.torch.optim.SGD(参数,学习率)
2. torch.optim.Adam(参数,学习率)
注意:
- 参数可以使用
model.parameters()
来获取,获取模型中所有requires_grad=True
的参数 - 优化类的使用方法
* 实例化
* 将所有参数的梯度值设为0
* 反向传播计算梯度
* 更新参数值
optimizer = optim.SGD(model.parameters(),lr = 1e-3)# 1.实例化
# 因为PyTorch默认会对梯度进行累加,不想先前的梯度影响到当前梯度的计算时需要手动清零
optimizer.zero_grad()# 2.梯度置为0
loss.backward()# 3.计算梯度
optimizer.step()# 4. 更新参数的值
3.3损失函数
torch中的损失函数:
- 均方误差:
nn.MSELoss()
,常用于分类问题 - 交叉熵损失:
nn.CrossEntropyLoss()
,常用于逻辑回归
使用方法:
model = lt()# 1.实例化模型
criterion = nn.MSELoss()# 2.实例化损失函数
optimizer = optim.SGD(model.parameters(),lr = 1e-3)# 3.实例化优化器类
for i in range(100):
y_predict = model(x_true)# 4.向前计算预测值
loss = criterion(y_true,y_predict)# 5. 调用损失函数传入真实值和预测值,得到损失结果
optimizer.zero_grad()# 5.当前循环参数梯度置为0
loss.backward()# 6.计算梯度
optimizer.step()# 7.更新参数的值
pytorch实现线性回归完整代码:
import torch
from torch import optim
from torch import nn
import matplotlib.pyplot as plt
# 1. 定义数据
x = torch.rand([50,1])
y = x*3 + 0.8
# 2. 定义lr模型
class lr(nn.Module):
def __init__(self):
super(lr,self).__init__() # 继承父类init的参数
self.linear = nn.Linear(1,1)
def forward(self, x):
out = self.linear(x)
return out
def main():
# 2. 实例化模型,loss和优化器
model = lr()
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(),1e-3)
# 3. 训练模型
for i in range(3000):
out = model(x) # 获取预测值
loss = criterion(y,out) # 计算损失
optimizer.zero_grad() # 梯度置0
loss.backward() # 计算梯度
optimizer.step() # 更新梯度
if i%20 == 0:
# 可以通过model.parameters()来说输出参数变化
params = list(model.parameters())
print(params[0].item(),params[1].item())
print("loss:%6f"%loss.data)
print('Epoch[{}/{}],loss:{:.6f}'.format(i,3000,loss.data))
# 4. 模型评估
model.eval()
predict = model(x)
predict = predict.data.numpy()
plt.scatter(x.data.numpy(),y.data.numpy(),c='r')
plt.plot(x.data.numpy(),predict)
plt.show()
if __name__ == '__main__':
main()
model.eval()
表示设置模型为评估模式,即预测模式
model.train(mode=True)
表示设置模型为训练模式
在当前的线性回归中,上述并无区别。
但是在其他的一些模型中,训练的参数和预测的参数会不相同,到时候就需要具体分清楚是在进行训练还是预测,比如模型中存在Dropout,BatchNorm的时候。
3.4 使用GPU训练
当模型太大或者参数太多的时候,可以使用GPU来加快训练速度。
- 判断GPU是否可用
torch.cuda.is_avaiable()
,如果可用创建GPU的device;
device = torch.device("cuda" if torch.cuda.is_avaiable() else "cpu")
- 把模型参数和input数据转化为cuda 的支持类型;
model.to(device)
x_true.to(device)
- 在GPU上计算结果也为cuda数据类型,需要转化为numpy或者torch的cpu的tensor类型;
predict = predict.cpu().detach().numpy()
detach()的效果和data的相似,但是detach()是深拷贝,data是取值,是浅拷贝。
修改之后的代码为:
import torch
from torch import optim
from torch import nn
import matplotlib.pyplot as plt
device = torch.device("cuda" if torch.cuda.is_avaiable() else "cpu")
# 1. 定义数据
x = torch.rand([50,1]).to(device) # 修改为GPU的tensor
y = x*3 + 0.8
# 2. 定义lr模型
class lr(nn.Module):
def __init__(self):
super(lr,self).__init__() # 继承父类init的参数
self.linear = nn.Linear(1,1)
def forward(self, x):
out = self.linear(x)
return out
def main():
# 2. 实例化模型,loss和优化器
model = lr().to(device) # 修改为GPU的模型
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(),1e-3)
# 3. 训练模型
for i in range(3000):
out = model(x) # 获取预测值
loss = criterion(y,out) # 计算损失
optimizer.zero_grad() # 梯度置0
loss.backward() # 计算梯度
optimizer.step() # 更新梯度
if i%20 == 0:
print("loss:%6f"%loss.data)
print('Epoch[{}/{}],loss:{:.6f}'.format(i,3000,loss.data))
# 4. 模型评估
model.eval()
predict = model(x)
predict = predict.data.numpy()
plt.scatter(x.data.numpy(),y.data.numpy(),c='r')
plt.plot(x.data.numpy(),predict)
plt.show()
if __name__ == '__main__':
main()
在GPU上执行程序需要注意:
- 自定义的参数和数据需要转化为cuda支持的tensor
- model需要转化为cuda支持的model
- 执行的结果需要和cpu的tensor计算的时候,
tensor.cpu()
可以把cuda的tensor转化为cpu的tensor。