跳转至

反向传播与 optimizer

到目前为止,我们已经掌握如何使用 OneFlow 加载数据搭建模型自动计算模型参数的梯度,将它们组合在一起,我们就可以利用反向传播算法训练模型。

oneflow.optim 中,有各类 optimizer,它们可以简化实现反向传播的代码。

本文将先介绍反向传播的基本概念,再介绍如何使用 oneflow.optim 类。

numpy 手工实现反向传播

为了读者更方便理解反向传播与自动求导的关系,在这里提供了一份仅用 numpy 实现的简单模型的训练过程:

import numpy as np

ITER_COUNT = 500
LR = 0.01

# 前向传播
def forward(x, w):
    return np.matmul(x, w)


# 损失函数
def loss(y_pred, y):
    return ((y_pred - y) ** 2).sum()


# 计算梯度
def gradient(x, y, y_pred):
    return np.matmul(x.T, 2 * (y_pred - y))


if __name__ == "__main__":
    # 训练目标: Y = 2*X1 + 3*X2
    x = np.array([[1, 2], [2, 3], [4, 6], [3, 1]], dtype=np.float32)
    y = np.array([[8], [13], [26], [9]], dtype=np.float32)

    w = np.array([[2], [1]], dtype=np.float32)
    # 训练循环
    for i in range(0, ITER_COUNT):
        y_pred = forward(x, w)
        l = loss(y_pred, y)
        if (i + 1) % 50 == 0:
            print(f"{i+1}/{500} loss:{l}")

        grad = gradient(x, y, y_pred)
        w -= LR * grad

    print(f"w:{w}")

输出:

50/500 loss:0.0034512376878410578
100/500 loss:1.965487399502308e-06
150/500 loss:1.05524122773204e-09
200/500 loss:3.865352482534945e-12
250/500 loss:3.865352482534945e-12
300/500 loss:3.865352482534945e-12
350/500 loss:3.865352482534945e-12
400/500 loss:3.865352482534945e-12
450/500 loss:3.865352482534945e-12
500/500 loss:3.865352482534945e-12
w:[[2.000001 ]
 [2.9999993]]

注意我们选择的 loss 函数表达式为 \(\sum (y_{p} - y)^2\),因此 loss 对参数 w求梯度的代码为:

def gradient(x, y, y_pred):
    return np.matmul(x.T, 2 * (y_pred - y))

更新参数采用的是 SGD

grad = gradient(x, y, y_pred)
w -= LR*grad

总结而言,训练中的一次完整迭代包括以下步骤:

  1. 模型根据输入、参数,计算得出预测值 (y_pred)
  2. 计算 loss,即预测值与标签之间的误差
  3. 求 loss 对参数的梯度
  4. 更新参数

其中 1~2 为前向传播过程;3~4为反向传播过程。

超参 Hyperparameters

超参数是有关模型训练设置的参数,可以影响到模型训练的效率和结果。如以上代码中的 ITER_COUNTLR 就是超参数。

使用 oneflow.optim 中的优化器类

使用 oneflow.optim 中的优化器类进行反向传播会更简洁方便,接下来,我们展示如何使用。

首先,先准备好数据和模型,使用 Module 的一个方便之处就是,可以把超参放置在 Module 中便于管理。

import oneflow as flow

x = flow.tensor([[1, 2], [2, 3], [4, 6], [3, 1]], dtype=flow.float32)
y = flow.tensor([[8], [13], [26], [9]], dtype=flow.float32)


class MyLrModule(flow.nn.Module):
    def __init__(self, lr, iter_count):
        super().__init__()
        self.w = flow.nn.Parameter(flow.tensor([[2], [1]], dtype=flow.float32))
        self.lr = lr
        self.iter_count = iter_count

    def forward(self, x):
        return flow.matmul(x, self.w)


model = MyLrModule(0.01, 500)

loss 函数

然后,选择好 loss 函数,OneFlow 自带了多种 loss 函数,我们在这里选择 MSELoss

loss = flow.nn.MSELoss(reduction="sum")

构造 optimizer

反向传播的逻辑,都被封装在 optimizer 中。我们在此选择 SGD,你可以根据需要选择其它的优化算法,如 AdamAdamW 等。

optimizer = flow.optim.SGD(model.parameters(), model.lr)

构造 optimizer时,将模型参数及 learning rate 传递给 SGD。之后调用 optimizer.step(),在其内部就会自动完成对模型参数求梯度、并按照 SGD 算法更新模型参数。

训练

以上准备完成后,可以开始训练:

for i in range(0, model.iter_count):
    y_pred = model(x)
    l = loss(y_pred, y)
    if (i + 1) % 50 == 0:
        print(f"{i+1}/{model.iter_count} loss:{l.numpy()}")

    optimizer.zero_grad()
    l.backward()
    optimizer.step()

print(f"\nw: {model.w}")

输出:

50/500 loss:0.003451163647696376
100/500 loss:1.965773662959691e-06
150/500 loss:1.103217073250562e-09
200/500 loss:3.865352482534945e-12
250/500 loss:3.865352482534945e-12
300/500 loss:3.865352482534945e-12
350/500 loss:3.865352482534945e-12
400/500 loss:3.865352482534945e-12
450/500 loss:3.865352482534945e-12
500/500 loss:3.865352482534945e-12

w: tensor([[2.],
        [3.]], dtype=oneflow.float32, grad_fn=<accumulate_grad>)
Back to top