【问题标题】:Is it possible to update the learning rate, each batch, based on batch label (y_true) distribution?是否可以根据批次标签(y_true)分布更新每个批次的学习率?
【发布时间】:2020-05-17 22:33:55
【问题描述】:

编辑:请参阅此问题的结尾以获取解决方案

TL;DR:我需要找到一种方法来计算每批的标签分布,并更新学习率。有没有办法访问当前模型的优化器来更新每个批次的 learning_rate?

下面是如何计算标签分布。它可以在损失函数中完成,因为默认情况下,损失是分批计算的。可以在哪里执行还可以访问模型优化器的代码?

def loss(y_true, y_pred):
    y = math_ops.argmax(y_true, axis=1)
    freqs = tf.gather(lf, y)  # equal to lf[y] if `lf` and `y` were numpy array's
    inv_freqs = math_ops.pow(freqs, -1)
    E = 1 / math_ops.reduce_sum(inv_freqs)  # value to use when updating learning rate

更多详情

为了实现学习率计划,如this paper 中所述,我相信我需要一种方法来更新训练期间的学习率,每批,通过从真实标签的标签分布计算的值 在批处理中y_true 通常在 keras/tensorflow 中表示)

在哪里...

x 模型的输出

y对应的ground truth标签

Β m 个样本的 minibatch(例如 64

ny 地面实况标签 y 的整个训练样本大小

ny-1逆标签频率

我关注的公式部分是α和Δ之间的部分θ

我可以在自定义损失函数中轻松实现这一点,但我不知道如何从损失函数中更新学习率——如果你可以的话。

def loss(y_true, y_pred):
    y = math_ops.argmax(y_true, axis=1)
    freqs = tf.gather(lf, y)  # equal to lf[y] if `lf` and `y` were numpy array's
    inv_freqs = math_ops.pow(freqs, -1)
    E = 1 / math_ops.reduce_sum(inv_freqs)  # value to use when updating learning rate

在哪里...

lf 每个类的采样频率。例如2 个类,c0 = 10 个示例,c1 = 100 --> lf == [10, 100]

有什么奇特的方法可以更新优化器的学习率,比如可以从回调中做什么?

def on_batch_begin(self, batch, log):
    # note: batch is just an incremented value to indicate batch index
    self.model.optimizer.lr  # learning rate, can be modified from callback

提前感谢您的帮助!


解决方案

非常感谢 @mrk 推动我朝着正确的方向解决这个问题!

为了计算每批次的标签分布,然后使用该值来更新优化器的学习率,必须...

  1. 创建一个自定义指标,计算每批次的标签分布,并返回频率数组(默认情况下,keras 是按批次优化的,因此每批次都会计算指标)。
  2. 通过继承 keras.callbacks.History 类创建一个典型的学习率调度程序
  3. 覆盖调度程序的on_batch_end 函数,logs dict 将包含批处理的所有计算指标包括我们的自定义标签分布指标!

创建自定义指标

class LabelDistribution(tf.keras.metrics.Metric):
    """
    Computes the per-batch label distribution (y_true) and stores the array as
    a metric which can be accessed via keras CallBack's

    :param n_class: int - number of distinct output class(es)
    """

    def __init__(self, n_class, name='batch_label_distribution', **kwargs):
        super(LabelDistribution, self).__init__(name=name, **kwargs)
        self.n_class = n_class
        self.label_distribution = self.add_weight(name='ld', initializer='zeros',
                                                  aggregation=VariableAggregation.NONE,
                                                  shape=(self.n_class, ))

    def update_state(self, y_true, y_pred, sample_weight=None):
        y_true = mo.cast(y_true, 'int32')
        y = mo.argmax(y_true, axis=1)
        label_distrib = mo.bincount(mo.cast(y, 'int32'))

        self.label_distribution.assign(mo.cast(label_distrib, 'float32'))

    def result(self):
        return self.label_distribution

    def reset_states(self):
        self.label_distribution.assign([0]*self.n_class)

创建 DRW 学习率调度器

class DRWLearningRateSchedule(keras.callbacks.History):
    """
    Used to implement the Differed Re-weighting strategy from
    [Kaidi Cao, et al. "Learning Imbalanced Datasets with Label-Distribution-Aware Margin Loss." (2019)]
    (https://arxiv.org/abs/1906.07413)

    To be included as a metric to model.compile
    `model.compile(..., metrics=[DRWLearningRateSchedule(.01)])`
    """

    def __init__(self, base_lr, ld_metric='batch_label_distribution'):
        super(DRWLearningRateSchedule, self).__init__()

        self.base_lr = base_lr
        self.ld_metric = ld_metric  # name of the LabelDistribution metric

    def on_batch_end(self, batch, logs=None):
        ld = logs.get(self.ld_metric)  # the per-batch label distribution
        current_lr = self.model.optimizer.lr
        # example below of updating the optimizers learning rate
        K.set_value(self.model.optimizer.lr, current_lr * (1 / math_ops.reduce_sum(ld)))

【问题讨论】:

    标签: python tensorflow machine-learning math keras


    【解决方案1】:

    Keras 基于损失的学习率自适应

    经过一些研究,我发现this,与其触发衰减,不如为学习率定义另一个函数或值。

    from __future__ import absolute_import
    from __future__ import print_function
    
    import keras
    from keras import backend as K
    import numpy as np
    
    
    class LossLearningRateScheduler(keras.callbacks.History):
        """
        A learning rate scheduler that relies on changes in loss function
        value to dictate whether learning rate is decayed or not.
        LossLearningRateScheduler has the following properties:
        base_lr: the starting learning rate
        lookback_epochs: the number of epochs in the past to compare with the loss function at the current epoch to determine if progress is being made.
        decay_threshold / decay_multiple: if loss function has not improved by a factor of decay_threshold * lookback_epochs, then decay_multiple will be applied to the learning rate.
        spike_epochs: list of the epoch numbers where you want to spike the learning rate.
        spike_multiple: the multiple applied to the current learning rate for a spike.
        """
    
        def __init__(self, base_lr, lookback_epochs, spike_epochs = None, spike_multiple = 10, decay_threshold = 0.002, decay_multiple = 0.5, loss_type = 'val_loss'):
    
            super(LossLearningRateScheduler, self).__init__()
    
            self.base_lr = base_lr
            self.lookback_epochs = lookback_epochs
            self.spike_epochs = spike_epochs
            self.spike_multiple = spike_multiple
            self.decay_threshold = decay_threshold
            self.decay_multiple = decay_multiple
            self.loss_type = loss_type
    
    
        def on_epoch_begin(self, epoch, logs=None):
    
            if len(self.epoch) > self.lookback_epochs:
    
                current_lr = K.get_value(self.model.optimizer.lr)
    
                target_loss = self.history[self.loss_type] 
    
                loss_diff =  target_loss[-int(self.lookback_epochs)] - target_loss[-1]
    
                if loss_diff <= np.abs(target_loss[-1]) * (self.decay_threshold * self.lookback_epochs):
    
                    print(' '.join(('Changing learning rate from', str(current_lr), 'to', str(current_lr * self.decay_multiple))))
                    K.set_value(self.model.optimizer.lr, current_lr * self.decay_multiple)
                    current_lr = current_lr * self.decay_multiple
    
                else:
    
                    print(' '.join(('Learning rate:', str(current_lr))))
    
                if self.spike_epochs is not None and len(self.epoch) in self.spike_epochs:
                    print(' '.join(('Spiking learning rate from', str(current_lr), 'to', str(current_lr * self.spike_multiple))))
                    K.set_value(self.model.optimizer.lr, current_lr * self.spike_multiple)
    
            else:
    
                print(' '.join(('Setting learning rate to', str(self.base_lr))))
                K.set_value(self.model.optimizer.lr, self.base_lr)
    
    
            return K.get_value(self.model.optimizer.lr)
    
    
    
    
    def main():
        return
    
    if __name__ == '__main__':
        main()
    
    
    

    【讨论】:

    • 太好了,我以前从未遇到过这种情况。看看我是否可以让它工作并会报告。谢谢!
    • 确认这不足以回答我的问题;但是,它使我朝着正确的方向前进-我相信我已经弄清楚了!将更新(如果可行,将选择您的作为正确答案)
    • 太棒了,我很喜欢你的问题和出色的工作,将我的答案转移到你的特定需求。这就是应该使用 SO 的方式。
    猜你喜欢
    • 2020-11-27
    • 1970-01-01
    • 2021-04-07
    • 1970-01-01
    • 2012-07-09
    • 2016-05-31
    • 1970-01-01
    • 1970-01-01
    • 2020-02-09
    相关资源
    最近更新 更多