【问题标题】:Gradient Accumulation with Custom model.fit in TF.Keras?TF.Keras中自定义model.fit的梯度累积?
【发布时间】:2021-06-02 22:23:53
【问题描述】:

请对您的想法添加最低限度的评论,以便我改进查询。谢谢你。 -)


我正在尝试使用 梯度累积 (GA) 训练 tf.keras 模型。但是我不想在自定义训练循环(like)中使用它,而是通过覆盖train_step来自定义.fit()方法。这可能吗?如何做到这一点?原因是如果我们想获得 keras 内置功能(如 fitcallbacks)的好处,我们不想使用自定义训练循环,但同时如果我们想覆盖 @987654329 @ 出于某种原因(例如 GA 或其他),我们可以自定义 fit 方法,并且仍然可以利用这些内置函数。

而且,我知道使用 GA 的优点,但使用它的主要缺点是什么?为什么它不是作为框架的默认功能而是作为可选功能提供的?

# overriding train step 
# my attempt 
# it's not appropriately implemented 
# and need to fix 
class CustomTrainStep(tf.keras.Model):
    def __init__(self, n_gradients, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.n_gradients = n_gradients
        self.gradient_accumulation = [tf.zeros_like(this_var) for this_var in \
                                           self.trainable_variables]

    def train_step(self, data):
        x, y = data
        batch_size = tf.cast(tf.shape(x)[0], tf.float32)  
        # Gradient Tape
        with tf.GradientTape() as tape:
            y_pred = self(x, training=True)
            loss = self.compiled_loss(y, y_pred, regularization_losses=self.losses)
        # Calculate batch gradients
        gradients = tape.gradient(loss, self.trainable_variables)
        # Accumulate batch gradients
        accum_gradient = [(acum_grad+grad) for acum_grad, grad in \
               zip(self.gradient_accumulation, gradients)]
        accum_gradient = [this_grad/batch_size for this_grad in accum_gradient]
        # apply accumulated gradients
        self.optimizer.apply_gradients(zip(accum_gradient, self.trainable_variables))
        # TODO: reset self.gradient_accumulation 
        # update metrics
        self.compiled_metrics.update_state(y, y_pred)
        return {m.name: m.result() for m in self.metrics}

请运行并检查以下玩具设置。

# Model 
size = 32
input = tf.keras.Input(shape=(size,size,3))
efnet = tf.keras.applications.DenseNet121(weights=None,
                                          include_top = False, 
                                          input_tensor = input)
base_maps = tf.keras.layers.GlobalAveragePooling2D()(efnet.output) 
base_maps = tf.keras.layers.Dense(units=10, activation='softmax', 
                                             name='primary')(base_maps) 
custom_model = CustomTrainStep(n_gradients=10, inputs=[input], outputs=[base_maps])

# bind all
custom_model.compile(
    loss = tf.keras.losses.CategoricalCrossentropy(),
    metrics = ['accuracy'],
    optimizer = tf.keras.optimizers.Adam() )
# data 
(x_train, y_train), (_, _) = tf.keras.datasets.mnist.load_data()
x_train = tf.expand_dims(x_train, -1)
x_train = tf.repeat(x_train, 3, axis=-1)
x_train = tf.divide(x_train, 255)
x_train = tf.image.resize(x_train, [size,size]) # if we want to resize 
y_train = tf.one_hot(y_train , depth=10) 

# customized fit 
custom_model.fit(x_train, y_train, batch_size=64, epochs=3, verbose = 1)

更新

我发现其他一些人也试图实现这一目标并最终遇到了同样的问题。有一些解决方法,here,但它太乱了,我认为应该有一些更好的方法。

【问题讨论】:

    标签: python tensorflow machine-learning keras deep-learning


    【解决方案1】:

    是的,可以通过覆盖 train_step 来自定义 .fit() 方法,而无需自定义训练循环,下面的简单示例将向您展示如何使用 梯度累积 训练一个简单的 mnist 分类器:

    import tensorflow as tf
    
    # overriding train step 
    # my attempt 
    # it's not appropriately implemented 
    # and need to fix 
    class CustomTrainStep(tf.keras.Model):
        def __init__(self, n_gradients, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.n_gradients = tf.constant(n_gradients, dtype=tf.int32)
            self.n_acum_step = tf.Variable(0, dtype=tf.int32, trainable=False)
            self.gradient_accumulation = [tf.Variable(tf.zeros_like(v, dtype=tf.float32), trainable=False) for v in self.trainable_variables]
    
        def train_step(self, data):
            self.n_acum_step.assign_add(1)
    
            x, y = data
            # Gradient Tape
            with tf.GradientTape() as tape:
                y_pred = self(x, training=True)
                loss = self.compiled_loss(y, y_pred, regularization_losses=self.losses)
            # Calculate batch gradients
            gradients = tape.gradient(loss, self.trainable_variables)
            # Accumulate batch gradients
            for i in range(len(self.gradient_accumulation)):
                self.gradient_accumulation[i].assign_add(gradients[i])
     
            # If n_acum_step reach the n_gradients then we apply accumulated gradients to update the variables otherwise do nothing
            tf.cond(tf.equal(self.n_acum_step, self.n_gradients), self.apply_accu_gradients, lambda: None)
    
            # update metrics
            self.compiled_metrics.update_state(y, y_pred)
            return {m.name: m.result() for m in self.metrics}
    
        def apply_accu_gradients(self):
            # apply accumulated gradients
            self.optimizer.apply_gradients(zip(self.gradient_accumulation, self.trainable_variables))
    
            # reset
            self.n_acum_step.assign(0)
            for i in range(len(self.gradient_accumulation)):
                self.gradient_accumulation[i].assign(tf.zeros_like(self.trainable_variables[i], dtype=tf.float32))
    
    # Model 
    input = tf.keras.Input(shape=(28, 28))
    base_maps = tf.keras.layers.Flatten(input_shape=(28, 28))(input)
    base_maps = tf.keras.layers.Dense(128, activation='relu')(base_maps)
    base_maps = tf.keras.layers.Dense(units=10, activation='softmax', name='primary')(base_maps) 
    custom_model = CustomTrainStep(n_gradients=10, inputs=[input], outputs=[base_maps])
    
    # bind all
    custom_model.compile(
        loss = tf.keras.losses.CategoricalCrossentropy(),
        metrics = ['accuracy'],
        optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3) )
    
    # data 
    (x_train, y_train), (_, _) = tf.keras.datasets.mnist.load_data()
    x_train = tf.divide(x_train, 255)
    y_train = tf.one_hot(y_train , depth=10) 
    
    # customized fit 
    custom_model.fit(x_train, y_train, batch_size=6, epochs=3, verbose = 1)
    

    输出:

    Epoch 1/3
    10000/10000 [==============================] - 13s 1ms/step - loss: 0.5053 - accuracy: 0.8584
    Epoch 2/3
    10000/10000 [==============================] - 13s 1ms/step - loss: 0.1389 - accuracy: 0.9600
    Epoch 3/3
    10000/10000 [==============================] - 13s 1ms/step - loss: 0.0898 - accuracy: 0.9748
    

    优点:

    梯度累积是一种拆分样本批次的机制—— 用于训练神经网络——分成几个小批量 将按顺序运行的示例

    因为 GA 在每个 mini-batch 之后计算损失和梯度,而不是更新模型参数,而是等待并累积连续批次的梯度,因此它可以克服内存限制,即使用更少的内存来训练模型,例如它使用大批量大小。

    示例:如果您以 5 步和批处理运行梯度累积 4张图片的大小,它的目的几乎与运行 批量大小为 20 张图像。

    我们还可以在使用 GA 时并行训练,即从多台机器聚合梯度。

    需要考虑的事项:

    这种技术效果很好,因此被广泛使用,在使用它之前需要考虑的事情很少,我认为它不应该被称为缺点,毕竟 GA 所做的只是将4 + 4 转为2 + 2 + 2 + 2 .

    如果你的机器有足够的内存来容纳已经足够大的batch size那么就没有必要使用它了,因为众所周知batch size太大会导致泛化能力差,如果你使用 GA 来达到你的机器内存已经可以处理的相同批量大小。

    参考:

    What is Gradient Accumulation in Deep Learning?

    【讨论】:

    • 谢谢,伙计 。您能否详细说明GA 流程? tf. cond 如何在这里工作,基于 n_gradients 到 0、1、2、3 步? apply_accu_gradients这里是怎么操作的?如果我使用n_gradients = 10batch_size = 64,我猜train_step 将执行10 次,每次有64 个训练对。而且使用这么大的global batch = batch_size * n_gradients,GPU实用程序应该比n_gradients = 1高很多;但在我的本地机器(2070)中,它几乎消耗相同的东西。
    • this 的回答引发了对使用tf. Variable 的担忧,您对此有何看法?
    • 我将在答案中添加更多关于 GA 的详细信息,tf.cond(condition, true_fn, false_fn) 就像图中的if 条件,它在我上面的代码中的作用是:如果n_acum_step 到达@987654343 @ 然后我们应用累积梯度来更新变量,否则什么也不做。 GPU 实用程序不会受n_gradients 的影响,而是受batch_size 在每一步的影响,这就是我们使用 GA 的全部原因,因此我们可以克服任意大的全局批量大小的内存限制
    • 干杯,有美好的一天,希望我们再次见面:)
    • 原来我的模型的创建方式意味着self.trainable_variables 在第一次调用self(x, trainable=True) 之前是空的,所以self.gradient_accumulation 的创建必须推迟到之后
    【解决方案2】:

    感谢@Mr.For Example 的方便回答。

    通常,我还观察到使用 梯度累积,不会加速训练,因为我们正在做 n_gradientsforward 传递并计算所有梯度.但它会加速我们模型的收敛。我发现在这里使用mixed_precision 技术非常有用。详情here.

    policy = tf.keras.mixed_precision.Policy('mixed_float16')
    tf.keras.mixed_precision.experimental.set_policy(policy)
    

    这是一个完整的gist

    【讨论】:

      猜你喜欢
      • 2019-04-19
      • 2021-01-04
      • 2020-09-15
      • 2023-03-27
      • 2021-06-08
      • 1970-01-01
      • 2022-01-24
      • 2019-11-09
      • 1970-01-01
      相关资源
      最近更新 更多