【MindSpore易点通】精度调优之梯度裁剪法

一、背景信息

用户在训练脚本时往往会遇到loss不收敛或者其他精度问题,而精度问题产生的主要原因之一是因为者梯度更新过程中梯度值过大或过小导致调参不准确所造成,针对梯度值在更新过程中过大或过小原因所造成的精度问题,故在此介绍一种梯度裁剪方法;顾名思义,梯度裁剪(gradient clip)是指当梯度小于或大于某个阈值时,强制调整梯度使其变大或变小的技术。

二、低阶模型梯度裁剪示例代码段

...
...
GRADIENT_CLIP_TYPE = 0
GRADIENT_CLIP_VALUE = 1.0

class ClipGradients(nn.Cell):
    """
    Clip gradients.
    Inputs:
        grads (tuple[Tensor]): Gradients.
        clip_type (int): The way to clip, 0 for 'value', 1 for 'norm'.
        clip_value (float): Specifies how much to clip.

    Outputs:
        tuple[Tensor], clipped gradients.
    """

    def __init__(self):
        super(ClipGradients, self).__init__()
        self.clip_by_norm = nn.ClipByNorm()
        self.cast = P.Cast()
        self.dtype = P.DType()

    def construct(self, grads, clip_type, clip_value):
        if clip_type != 0 and clip_type != 1:
            return grads

        new_grads = ()
        for grad in grads:
            dt = self.dtype(grad)
            if clip_type == 0:
                t = ops.clip_by_value(grad, self.cast(F.tuple_to_array((clip_value,)),dt))
            else:
                t = self.clip_by_norm(grad, self.cast(F.tuple_to_array((clip_value,)),dt))
                new_grads = new_grads + (t,)

        return new_grads

class TrainOneStepCellV2(TrainOneStepCell):
    '''Build train network.'''
    def __init__(self, network, optimizer, sens=1.0):
        super(TrainOneStepCellV2, self).__init__(network, optimizer, sens=1.0)
        self.clip_gradients = ClipGradients()

    def construct(self, *inputs):
        ...
        grads = self.grad(self.network, weights)(*inputs, sens)
        grads = self.clip_gradients(grads, GRADIENT_CLIP_TYPE, GRADIENT_CLIP_VALUE)
        ...

...
...
    # Construct model
    model_constructed = TrainOneStepCellV2(model_constructed, opt)

    # Train
    train_net(model_constructed, net, loss_function, CHECKPOINT_MAX, EPOCH_MAX, TRAIN_PATH, VAL_PATH, TRAIN_BATCH_SIZE, VAL_BATCH_SIZE, REPEAT_SIZE)

三、高阶模型梯度裁剪示例代码段

...
GRADIENT_CLIP_TYPE = 0
GRADIENT_CLIP_VALUE = 1.0class ClipGradients(nn.Cell):
    ...
    #同低阶代码模型梯度裁剪
    ...
...

class TrainOneStepCellV2(TrainOneStepCell):
    ...
    def __init__(self, network, optimizer, sens=1.0):
        super(TrainOneStepCellV2, self).__init__(network, optimizer, sens=1.0)
        self.clip_gradients = ClipGradients()

    def construct(self, *inputs):
        ...
        grads = self.clip_gradients(grads, GRADIENT_CLIP_TYPE, GRADIENT_CLIP_VALUE)
        ...

注:目前仅支持非下沉模式

梯度裁剪的低阶模型示例代码以及高阶模型示例代码请至附件处下载。

### 梯度裁剪的概念 梯度裁剪是一种用于解决梯度爆炸问题的技术。其核心思想是对误差梯度向量进行约束,使其保持在一定范围内[^1]。具体而言,当计算得到的梯度过大时,将其整到预设的最大值或最小值之间,从而防止模型训练过程中因梯度过大而导致数值不稳定。 梯度裁剪可以过两种主要方式实现:一种是基于范数的缩放(Norm-based Scaling),另一种是基于固定区间的裁剪(Value-based Clipping)。这两种方都可以有效控制梯度的大小,避免其超出合理范围[^2]。 --- ### 基于范数的梯度裁剪 在这种方中,梯度被看作是一个向量,并对其整体范数进行限制。如果梯度的范数超过了设定的阈值,则按比例缩小整个梯度向量,使得其范数等于该阈值。这种方可以表示为: \[ g_{clipped} = \begin{cases} g & \text{if } ||g||_2 \leq threshold \\ \frac{threshold}{||g||_2} g & \text{otherwise} \end{cases} \] 其中 \(g\) 是原始梯度向量,\(threshold\) 是预先定义的阈值,而 \(||g||_2\) 表示梯度向量的 L2 范数。 以下是 TensorFlow 中基于范数的梯度裁剪的一个简单实现: ```python import tensorflow as tf optimizer = tf.keras.optimizers.Adam() gradients, variables = zip(*optimizer.compute_gradients(loss)) clipped_grads, _ = tf.clip_by_global_norm(gradients, clip_norm=1.0) train_op = optimizer.apply_gradients(zip(clipped_grads, variables)) ``` 上述代码片段展示了如何利用 `tf.clip_by_global_norm` 函数来执行全局范数裁剪操作[^3]。 --- ### 基于固定区间的梯度裁剪 此方直接对梯度中的每个元素施加上下限约束。任何超过指定最大值的部分会被截断至最大值;同样地,低于最小值的部分则被设置为最小值。这种形式的梯度裁剪可以用如下伪代码描述: \[ g' = \max(\min(g, max\_value), min\_value) \] 下面是在 PyTorch 中实现逐元素梯度裁剪的例子: ```python import torch.nn.utils as utils utils.clip_grad_value_(model.parameters(), clip_value=1.0) ``` 这里用了 `clip_grad_value_` 方,它会对模型参数对应的梯度应用固定的区间裁剪逻辑。 --- ### 梯度裁剪的作用 梯度裁剪的主要作用在于稳定神经网络训练过程,尤其是在处理循环神经网络 (RNN) 或深度前馈网络时尤为重要。由于这些架构容遭遇梯度消失或梯度爆炸现象,因此引入梯度裁剪能够显著改善收敛性能并提升最终效果。 此外,在某些情况下,即使不完全消除梯度爆炸问题,适当程度上的梯度裁剪也可以帮助平滑损失函数曲线,减少化路径中的剧烈波动。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值