文档反馈

问题文档片段

问题文档片段包含公式时,显示为空格。

提交类型
issue

有点复杂...

找人问问吧。

PR

小问题,全程线上修改...

一键搞定!

请选择提交类型

问题类型
规范和低错类

- 规范和低错类:

- 错别字或拼写错误,标点符号使用错误、公式错误或显示异常。

- 链接错误、空单元格、格式错误。

- 英文中包含中文字符。

- 界面和描述不一致,但不影响操作。

- 表述不通顺,但不影响理解。

- 版本号不匹配:如软件包名称、界面版本号。

易用性

- 易用性:

- 关键步骤错误或缺失,无法指导用户完成任务。

- 缺少主要功能描述、关键词解释、必要前提条件、注意事项等。

- 描述内容存在歧义指代不明、上下文矛盾。

- 逻辑不清晰,该分类、分项、分步骤的没有给出。

正确性

- 正确性:

- 技术原理、功能、支持平台、参数类型、异常报错等描述和软件实现不一致。

- 原理图、架构图等存在错误。

- 命令、命令参数等错误。

- 代码片段错误。

- 命令无法完成对应功能。

- 界面错误,无法指导操作。

- 代码样例运行报错、运行结果不符。

风险提示

- 风险提示:

- 对重要数据或系统存在风险的操作,缺少安全提示。

内容合规

- 内容合规:

- 违反法律法规,涉及政治、领土主权等敏感词。

- 内容侵权。

请选择问题类型

问题描述

点击输入详细问题描述,以帮助我们快速定位问题。

梯度累积

在线运行下载Notebook下载样例代码查看源文件

概述

本教程介绍梯度累积的训练算法,目的是为了解决由于内存不足,导致Batch size过大神经网络无法训练,或者网络模型过大无法加载的OOM(Out Of Memory)问题。

梯度累积原理

梯度累积是一种将训练神经网络的数据样本按Batch size拆分为几个小Batch的方式,然后按顺序进行计算。

在进一步讨论梯度累积之前,我们来看看神经网络的计算过程。

深度学习模型由许多相互连接的神经网络单元所组成,在所有神经网络层中,样本数据会不断向前传播。在通过所有层后,网络模型会输出样本的预测值,通过损失函数然后计算每个样本的损失值(误差)。神经网络通过反向传播,去计算损失值相对于模型参数的梯度。最后这些梯度信息用于对网络模型中的参数进行更新。

优化器用于对网络模型权重参数更新的数学公式。以一个简单随机梯度下降(SGD)算法为例。

假设Loss Function函数公式为:

Loss(θ)=12(h(xk)yk)2

在构建模型时,优化器用于计算最小化损失的算法。这里SGD算法利用Loss函数来更新权重参数公式为:

θi=θi1lrgradi

其中θ是网络模型中的可训练参数(权重或偏差),lr是学习率,gradi是相对于网络模型参数的损失。

梯度累积只计算神经网络模型,并不及时更新网络模型的参数,同时在计算的时候累积得到的梯度信息,最后统一使用累积的梯度来对参数进行更新。

accumulated=i=0Ngradi

在不更新模型变量的时候,实际上是把原来的数据Batch size分成几个小的Mini-Batch,每个step中使用的样本实际上是更小的数据集。

在N个step内不更新变量,使所有Mini-Batch使用相同的模型变量来计算梯度,以确保计算出来得到相同的梯度和权重信息,算法上等价于使用原来没有切分的Batch size大小一样。即:

θi=θi1lri=0Ngradi

最终在上面步骤中累积梯度会产生与使用全局Batch size大小相同的梯度总和。

image0

当然在实际工程当中,关于调参和算法上有两点需要注意的:

  1. 学习率 learning rate:一定条件下,Batch size越大训练效果越好,梯度累积则模拟了Batch size增大的效果,如果accumulation steps为4,则Batch size增大了4倍,根据经验,使用梯度累积的时候需要把学习率适当放大。

  2. 归一化 Batch Norm:accumulation steps为4时进行Batch size模拟放大的效果,与真实Batch size相比,数据的分布其实并不完全相同,4倍Batch size的Batch Norm计算出来的均值和方差与实际数据均值和方差不太相同,因此有些实现中会使用Group Norm来代替Batch Norm。

梯度累积实现

基于MindSpore的函数式自动微分机制,正向和反向执行完成后,函数将返回与训练参数相对应的梯度。因此我们需要设计一个梯度累积类Accumulator,对每一个Step产生的梯度值进行存储。下面是Accumulator的实现样例,我们需要维护两份与模型可训练参数的Shape相同的内部属性,分别为inner_grads和zeros。其中inner_grads用于存储累积的梯度值,zeros用于参数优化更新后的清零。同时,Accumulator内部维护了一个counter变量,在每一次正反向执行完成后,counter自增,通过对counter取模的方式来判断是否达到累积步数。

[1]:
import mindspore as ms
from mindspore import Tensor, Parameter, ops

@ms.jit_class
class Accumulator():
    def __init__(self, optimizer, accumulate_step, clip_norm=1.0):
        self.optimizer = optimizer
        self.clip_norm = clip_norm
        self.inner_grads = optimizer.parameters.clone(prefix="accumulate_", init='zeros')
        self.zeros = optimizer.parameters.clone(prefix="zeros_", init='zeros')
        self.counter = Parameter(Tensor(1, ms.int32), 'counter_')
        assert accumulate_step > 0
        self.accumulate_step = accumulate_step
        self.map = ops.HyperMap()

    def __call__(self, grads):
        # 将单步获得的梯度累加至Accumulator的inner_grads
        self.map(ops.partial(ops.assign_add), self.inner_grads, grads)
        if self.counter % self.accumulate_step == 0:
            # 如果达到累积步数,进行参数优化更新
            self.optimizer(self.inner_grads)
            # 完成参数优化更新后,清零inner_grads
            self.map(ops.partial(ops.assign), self.inner_grads, self.zeros)
        # 计算步数加一
        ops.assign_add(self.counter, Tensor(1, ms.int32))

        return True

ms.jit_class为MindSpore即时编译修饰器,可以将普通的Python类作为可编译计算图使用。

接下来,我们使用快速入门中手写数字识别模型验证梯度累加的效果。

[2]:
from mindspore import nn
from mindspore import value_and_grad
from mindspore.dataset import vision, transforms
from mindspore.dataset import MnistDataset

from download import download

url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/" \
      "notebook/datasets/MNIST_Data.zip"
path = download(url, "./", kind="zip", replace=True)


def datapipe(path, batch_size):
    image_transforms = [
        vision.Rescale(1.0 / 255.0, 0),
        vision.Normalize(mean=(0.1307,), std=(0.3081,)),
        vision.HWC2CHW()
    ]
    label_transform = transforms.TypeCast(ms.int32)

    dataset = MnistDataset(path)
    dataset = dataset.map(image_transforms, 'image')
    dataset = dataset.map(label_transform, 'label')
    dataset = dataset.batch(batch_size)
    return dataset

class Network(nn.Cell):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.dense_relu_sequential = nn.SequentialCell(
            nn.Dense(28*28, 512),
            nn.ReLU(),
            nn.Dense(512, 512),
            nn.ReLU(),
            nn.Dense(512, 10)
        )

    def construct(self, x):
        x = self.flatten(x)
        logits = self.dense_relu_sequential(x)
        return logits

model = Network()
Downloading data from https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/MNIST_Data.zip (10.3 MB)

file_sizes: 100%|██████████████████████████| 10.8M/10.8M [00:06<00:00, 1.67MB/s]
Extracting zip file...
Successfully downloaded / unzipped to ./

假设我们在使用快速入门中配置的batch_size=64会导致显存不足,此时我们设置累加步数为2,通过执行两次batch_size=32进行梯度累加。

首先,我们使用Accumulator,传入实例化的optimizer,并配置累加步数。然后定义正向计算函数forward_fn,此时,由于梯度累加的需要,loss值需要进行相应的缩放。

[3]:
accumulate_step = 2

loss_fn = nn.CrossEntropyLoss()
optimizer = nn.SGD(model.trainable_params(), 1e-2)
accumulator = Accumulator(optimizer, accumulate_step)

def forward_fn(data, label):
    logits = model(data)
    loss = loss_fn(logits, label)
    # loss除以累加步数accumulate_step
    return loss / accumulate_step

接下来继续使用value_and_grad函数进行函数变换,并构造单步训练函数train_step。此时我们使用实例化好的accumulator进行梯度累加,由于optimizer作为accumulator的内部属性,不需要单独执行。

[4]:
grad_fn = value_and_grad(forward_fn, None, model.trainable_params())

@ms.jit
def train_step(data, label):
    loss, grads = grad_fn(data, label)
    accumulator(grads)
    return loss

接下来,我们定义训练和评估逻辑,进行训练验证。

[5]:
def train_loop(model, dataset, loss_fn, optimizer):
    size = dataset.get_dataset_size()
    model.set_train()
    for batch, (data, label) in enumerate(dataset.create_tuple_iterator()):
        loss = train_step(data, label)

        if batch % 100 == 0:
            loss, current = loss.asnumpy(), batch
            print(f"loss: {loss:>7f}  [{current:>3d}/{size:>3d}]")
[6]:
def test_loop(model, dataset, loss_fn):
    num_batches = dataset.get_dataset_size()
    model.set_train(False)
    total, test_loss, correct = 0, 0, 0
    for data, label in dataset.create_tuple_iterator():
        pred = model(data)
        total += len(data)
        test_loss += loss_fn(pred, label).asnumpy()
        correct += (pred.argmax(1) == label).asnumpy().sum()
    test_loss /= num_batches
    correct /= total
    print(f"Test: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

接下来同样进行3个epoch的训练,注意此时根据我们的假设,数据集需要设置batch_size=32,每两步进行累加。

[7]:
train_dataset = datapipe('MNIST_Data/train', 32)
test_dataset = datapipe('MNIST_Data/test', 32)

开始训练验证,此时由于batch_size调小需要训练的step数增加至2倍。最终Accuracy验证结果与快速入门结果一致,均为92.0%左右。

[8]:
epochs = 3
for t in range(epochs):
    print(f"Epoch {t+1}\n-------------------------------")
    train_loop(model, train_dataset, loss_fn, optimizer)
    test_loop(model, test_dataset, loss_fn)
print("Done!")
Epoch 1
-------------------------------
loss: 1.150851  [  0/1875]
loss: 1.149633  [100/1875]
loss: 1.145340  [200/1875]
loss: 1.140591  [300/1875]
loss: 1.134244  [400/1875]
loss: 1.125991  [500/1875]
loss: 1.100611  [600/1875]
loss: 1.051961  [700/1875]
loss: 0.925877  [800/1875]
loss: 0.879966  [900/1875]
loss: 0.750192  [1000/1875]
loss: 0.617844  [1100/1875]
loss: 0.470084  [1200/1875]
loss: 0.560856  [1300/1875]
loss: 0.359766  [1400/1875]
loss: 0.502521  [1500/1875]
loss: 0.299145  [1600/1875]
loss: 0.383266  [1700/1875]
loss: 0.239381  [1800/1875]
Test:
 Accuracy: 84.8%, Avg loss: 0.528309

Epoch 2
-------------------------------
loss: 0.390662  [  0/1875]
loss: 0.250778  [100/1875]
loss: 0.570571  [200/1875]
loss: 0.196102  [300/1875]
loss: 0.297634  [400/1875]
loss: 0.192528  [500/1875]
loss: 0.231240  [600/1875]
loss: 0.144425  [700/1875]
loss: 0.113696  [800/1875]
loss: 0.233481  [900/1875]
loss: 0.212078  [1000/1875]
loss: 0.144562  [1100/1875]
loss: 0.220822  [1200/1875]
loss: 0.197890  [1300/1875]
loss: 0.283782  [1400/1875]
loss: 0.219684  [1500/1875]
loss: 0.155505  [1600/1875]
loss: 0.255665  [1700/1875]
loss: 0.155548  [1800/1875]
Test:
 Accuracy: 90.1%, Avg loss: 0.340294

Epoch 3
-------------------------------
loss: 0.176077  [  0/1875]
loss: 0.204260  [100/1875]
loss: 0.339903  [200/1875]
loss: 0.221457  [300/1875]
loss: 0.244668  [400/1875]
loss: 0.089163  [500/1875]
loss: 0.159595  [600/1875]
loss: 0.211632  [700/1875]
loss: 0.096592  [800/1875]
loss: 0.081018  [900/1875]
loss: 0.190852  [1000/1875]
loss: 0.139729  [1100/1875]
loss: 0.049344  [1200/1875]
loss: 0.122041  [1300/1875]
loss: 0.198622  [1400/1875]
loss: 0.133956  [1500/1875]
loss: 0.144801  [1600/1875]
loss: 0.076985  [1700/1875]
loss: 0.103241  [1800/1875]
Test:
 Accuracy: 92.0%, Avg loss: 0.281193

Done!