Date
:2021-12-20
Repositity
: Gitee
0. 回顾
前述模型为:
y
^
=
x
∗
ω
+
b
\hat{y} = x * \omega + b
y^=x∗ω+b ,损失函数为MSE
:
l
o
s
s
=
(
x
∗
ω
−
y
)
2
loss = (x*\omega - y)^2
loss=(x∗ω−y)2 ,优化器为梯度下降GD
。
for epoch in range(100):
# Use Tensor: x_data, y_dta
for x, y in zip(x_data, y_data):
# forward(loss) and backward(gradient)
loss = loss_func(x, y)
loss.backward()
print('\t x: {}, y: {}, grad: {}'.format(x, y, w.grad.item()))
# update by GD
w.data = w.data - 0.01 * w.grad.data
w.grad.data.zero_()
1. 使用PyTorch
通常使用PyTorch
搭建深度学习模型按以下流程进行:
- Prepare dataset:准备数据集,配合官方接口完成数据集的一系列操作;
- Design model using Class:设计算法模型,继承自
torch.nn.Module
; - Construct loss and optimizer:可使用
PyTorch
的API,搭建合适的损失函数和优化方法; - Training cycle:训练过程(前馈、反向传播、权重参数更新);
- Evaluating cycle:训练过程中使用验证集验证当前节点训练的参数的性能指标;
- Testing cycle:训练完成后,在测试集上评估模型的泛化能力、精度指标等;
- Deployment:模型部署,涉及定点化等操作。
因为这里只是简单介绍,因此只就前4部分展开。
1.1 Prepare dataset
在PyTorch
中,计算图是基于Mini-batch
风格,而前面章节我们实际是每次迭代一组样本和标签。因此,实际使用时我们的数据实际是呈批次送入网络训练的。那么以线性模型为例
y
^
=
x
∗
ω
+
b
\hat{y} = x * \omega + b
y^=x∗ω+b 这里的标量
b
b
b 如何参与矩阵的相关运算?
答案是broadcasting
,也就广播。这里参考numpy
官网对broadcasting的介绍。如下图所示:
那么需要满足什么规则呢?
Two tensors are “broadcastable” if the following rules hold:
- Each tensor has at least one dimension.
- When iterating over the dimension sizes, starting at the trailing dimension, the dimension sizes must either be equal, one of them is 1, or one of them does not exist.
参与的张量维度要大于等于1,当迭代超过维度尺寸,从尾维度开始,保证尺寸大小一致(如上图右上和左下),其中一个维度是1或者其中一个维度不存在。故可以发现上述可用的三个子图,均在维度为1的行或列上复制成满足可加减或点积的矩阵状态。
回到模型 y ^ = x ∗ ω + b \hat{y} = x * \omega + b y^=x∗ω+b ,故可知这里的 x , y ^ x, \hat{y} x,y^ 都是矩阵, b b b 和 ω \omega ω 的维度刚好可以满足广播机制。后续展开自定义数据集的接口的使用说明。
1.2 Design model
之前的模型我们的梯度求导我们都是通过求解析式后编程计算,在PyTorch
中通过构造计算图即可。回到之前的仿射模型:
y
^
=
x
∗
ω
+
b
\hat{y} = x * \omega + b
y^=x∗ω+b ,其在PyTorch
中即Linear Unit
,如下图所示(Linear Unit
的参数
ω
\omega
ω 和
b
b
b 未括入)。
我们需要确定 ω \omega ω 和 b b b 的形状,如何确定?通过输入的 x , y x,y x,y 的维度和尺寸。
代码如下:
""" 将模型创建为一个类,继承自torch.nn.Module """
class LinearModel(torch.nn.Module):
""" 构造函数,用来初始化 """
def __init__(self):
super().__init__()
self.linear = torch.nn.Linear(1, 1)
""" 前馈函数 """
def forward(self, x):
# use __call__()
y_pred = self.linear(x)
return y_pred
model = LinearModel()
注意:
-
Python的
__init__
方法类似于C++
和Java
中的constructor
。构造函数用于初始化对象的状态。构造函数的任务是在创建类的对象时对类的数据成员进行初始化(赋值)。与方法一样,构造函数也包含在创建对象时执行的语句(即指令)的集合。一旦类的对象被实例化,它就会运行。该方法可用于执行您想要对对象进行的任何初始化。 -
The
__call__
method enables Python programmers to write classes where the instances behave like functions and can be called like a function. When the instance is called as a function. If this method is defined,x(arg1, arg2, ...)
is a shorthand forx.__call__(arg1, arg2, ...)
. 实例的行为类似函数且可以像函数一样被调用。__call__()
不影响实例本身的生命周期(不影响一个实例的构造和析构),但是__call__()
可以用来改变实例的内部成员的值。因此,第四节代码中的
model()
相当于model.__call__()
class Example: def __init__(self): print('>>>> Hello', end='') def __call__(self, in_name): print(' ' + str(in_name)) # init,类后面加'()',构造实例对象 test = Example() # call == test.__call__() test('PyTorch')
-
torch.nn.Linear(in_features, out_features, bias=True, device=None, dtype=None)
""" - linear transformation: y = x * A^T + b. - Parameters: - in_features: 输入样本的size - out_features: 输出样本的size - bias: layer是否添加偏置,默认是打开 """
之前章节我们都会在forward()
后单独求梯度,这里不需要了。PyTorch
中继承nn.Module
都内部整合了backward()
,会自动完成求导计算。
1.3 Construct Loss and Optimizer
loss
还是和之前一样用的MSE
,这里使用的是PyTorch
自带的,如下:
""" Design loss: nn.MSELoss也是集成自nn.Module """
criterion = torch.nn.MSELoss(size_average=False)
""" Select optimizer """
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
注意:
- 对于优化而言,一般
MSE
是不需要求均值的,有为常数项会被优化掉。但是对于某一个mini-batch
其数量不够一个batch_size
时,可能会存在一点点可以忽略的影响。 SGD
优化器不会构建计算图。- 父类的
model.parameters()
会去检查model
的所有成员,如果该成员有对应的权重参数时,他会将其拿出来优化。
1.4 Train Cycle
流程同上一讲:
- 前馈计算预测值
- 预测值和真实值计算
Loss
- 梯度清零
- 反向传播
- 更新权重
num = 1000
for epoch in range(num):
# use __call__()
y_pred = model(x_data)
# cal loss
loss = criterion(y_pred, y_data)
# clear grad
optimizer.zero_grad()
# backward
loss.backward()
# update
optimizer.step()
完整代码:
import torch
""" 1. prepare train data """
x_data = torch.Tensor([[1.0], [2.0], [3.0], [4.0], [5.0]])
y_data = torch.Tensor([[1.5], [3.0], [4.5], [6.0], [7.5]])
class LinearModel(torch.nn.Module):
def __init__(self):
super(LinearModel, self).__init__()
self.linear = torch.nn.Linear(1, 1)
def forward(self, x):
y_pred = self.linear(x)
return y_pred
""" 2. design model """
model = LinearModel()
""" 3. design loss """
criterion = torch.nn.MSELoss(size_average=False)
""" 4. select optimizer """
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
""" 5. train """
num = 1000
for epoch in range(num):
y_pred = model(x_data)
loss = criterion(y_pred, y_data)
print('\r Epoch: {:>3.0f}%[{}->{}], loss: {}'.format(epoch * 100 / (num - 1), int(epoch/10) * '*',
(int(num/10) - 1 - int(epoch/10)) * '.', loss.item()), end='')
optimizer.zero_grad()
loss.backward()
optimizer.step()
print('\n>> check parameter: w = {}, b = {}'.format(model.linear.weight.item(),
model.linear.bias.item()))
""" test """
x = torch.Tensor([[10.]])
y_gt = 15.
y = model(x)
print('>> Test x = {}, prediction: y = {}, loss: {}'.format(x.numpy(), y.data.numpy(), (y_gt-y.data.numpy()) / y_gt))
测试结果:
Epoch: 100%[***************************************************************************************************->], loss: 0.0210854715202004e-14
>> check parameter: w = 1.5, b = 5.870514385719616e-08
>> Test x = [[10.]], prediction: y = [[15.]], loss: [[0.]]
换个优化器测试一下,共计13组,6组数据没调整,出来的Loss曲线有点问题,未附图。
import torch
import matplotlib.pyplot as plt
""" 1. prepare train data """
x_data = torch.Tensor([[1.0], [2.0], [3.0], [4.0], [5.0]])
y_data = torch.Tensor([[1.5], [3.0], [4.5], [6.0], [7.5]])
class LinearModel(torch.nn.Module):
def __init__(self):
super(LinearModel, self).__init__()
self.linear = torch.nn.Linear(1, 1)
def forward(self, x):
y_pred = self.linear(x)
return y_pred
""" 2. design model """
model = LinearModel()
""" 3. design loss """
criterion = torch.nn.MSELoss(reduction='sum')
""" 4. select optimizer """
optimizer_a = torch.optim.SGD(model.parameters(), lr=0.01)
optimizer_b = torch.optim.Adagrad(model.parameters(), lr=0.01)
optimizer_c = torch.optim.Adam(model.parameters(), lr=0.01)
optimizer_d = torch.optim.ASGD(model.parameters(), lr=0.01)
optimizer_e = torch.optim.AdamW(model.parameters(), lr=0.01)
optimizer_f = torch.optim.Adadelta(model.parameters(), lr=0.01)
optimizer_g = torch.optim.Adamax(model.parameters(), lr=0.01)
optimizer_list = [
optimizer_a,
optimizer_b,
optimizer_c,
optimizer_d,
optimizer_e,
optimizer_f,
]
optimizer_name = [
'SGD',
'Adagrad',
'Adam',
'ASGD',
'AdamW',
'Adadelta',
'Adamax',
]
def train(epoch_num, net, loss_func, optimizer):
loss_list, epoch_list = [], []
for epoch in range(epoch_num):
y_pred = net(x_data)
loss = loss_func(y_pred, y_data)
optimizer.zero_grad()
loss.backward()
optimizer.step()
loss_list.append(loss.item())
epoch_list.append(epoch)
return loss_list, epoch_list
def draw_optimizer_result(model, loss_func, optimizer, optimizer_name, num):
""" draw all result. """
fig, ax = plt.subplots()
l_list, e_list = train(num, model, loss_func, optimizer)
x = e_list[0:num]
y = l_list[0:num]
ax.plot(x, y, label=optimizer_name)
ax.set_xlabel('epoch')
ax.set_ylabel('loss')
ax.set_title('Loss')
ax.legend()
plt.show()
for idx in range(len(optimizer_list)):
draw_optimizer_result(model, criterion, optimizer_list[idx], optimizer_name[idx], 100)
测试结果: