PyTorch 深度学习实践-05-[Linear Regression with PyTorch]

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 for x.__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)

测试结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值