编辑:请查看此问题的末尾以获取解决方案
简而言之:我需要找到一种方法来计算每个批次的标签分布,并更新学习率。是否有办法访问当前模型的优化器,以便每批次更新学习率?
以下是如何计算标签分布的方法。可以在损失函数中完成,因为默认情况下损失是按批次计算的。哪里可以执行这段代码,同时也能访问模型的优化器?
def loss(y_true, y_pred): y = math_ops.argmax(y_true, axis=1) freqs = tf.gather(lf, y) # 如果`lf`和`y`是numpy数组,则等于lf[y] inv_freqs = math_ops.pow(freqs, -1) E = 1 / math_ops.reduce_sum(inv_freqs) # 用于更新学习率的值
更多细节
为了实现这篇论文中描述的学习率调度策略,我认为我需要一种方法在训练过程中,每个批次根据批次中真实标签的标签分布计算出的值来更新学习率(在keras/tensorflow中通常表示为y_true
)
其中…
x 模型的输出
y 对应的真实标签
Β m个样本的迷你批次(例如64)
ny 真实标签y的整个训练样本大小
ny-1 逆标签频率
我关注的公式部分是在α和Δθ之间的部分
我可以轻松地在自定义损失函数中实现这一点,但我不知道如何从损失函数中更新学习率——如果可以的话。
def loss(y_true, y_pred): y = math_ops.argmax(y_true, axis=1) freqs = tf.gather(lf, y) # 如果`lf`和`y`是numpy数组,则等于lf[y] inv_freqs = math_ops.pow(freqs, -1) E = 1 / math_ops.reduce_sum(inv_freqs) # 用于更新学习率的值
其中…
lf 每个类别的样本频率。例如,有2个类别,c0 = 10个样本,c1 = 100个样本 –>
lf == [10, 100]
是否有什么巧妙的方法可以更新优化器的学习率,就像从回调中可以做的那样?
def on_batch_begin(self, batch, log): # 注意:batch只是一个递增值,用于指示批次索引 self.model.optimizer.lr # 学习率,可以从回调中修改
提前感谢任何帮助!
解决方案
非常感谢@mrk推动我朝正确的方向解决这个问题!
为了计算每个批次的标签分布,然后使用该值来更新优化器的学习率,必须…
- 创建一个自定义度量,用于计算每个批次的标签分布,并返回频率数组(默认情况下,keras是按批次优化的,因此度量是每个批次计算的)。
- 通过子类化
keras.callbacks.History
类创建一个典型的学习率调度器 - 覆盖调度器的
on_batch_end
函数,logs
字典将包含批次计算的所有度量,包括我们的自定义标签分布度量!
创建自定义度量
class LabelDistribution(tf.keras.metrics.Metric): """ 计算每个批次的标签分布(y_true)并将数组存储为 可以通过keras CallBack访问的度量 :param n_class: int - 不同输出类别的数量 """ 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): """ 用于实现 [Kaidi Cao, et al. "Learning Imbalanced Datasets with Label-Distribution-Aware Margin Loss." (2019)] (https://arxiv.org/abs/1906.07413)中的差异化重新加权策略 作为度量包含在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 # LabelDistribution度量的名称 def on_batch_end(self, batch, logs=None): ld = logs.get(self.ld_metric) # 每个批次的标签分布 current_lr = self.model.optimizer.lr # 更新优化器学习率的示例 K.set_value(self.model.optimizer.lr, current_lr * (1 / math_ops.reduce_sum(ld)))
回答:
Keras基于损失的学习率适应
经过一些研究,我发现了这个,你可以不触发衰减,而是定义另一个函数或值来调整学习率。
from __future__ import absolute_importfrom __future__ import print_functionimport kerasfrom keras import backend as Kimport numpy as npclass LossLearningRateScheduler(keras.callbacks.History): """ 依赖于损失函数值变化的学习率调度器 用于判断学习率是否需要衰减的学习率调度器。 LossLearningRateScheduler具有以下属性: base_lr: 起始学习率 lookback_epochs: 过去的epoch数,用于与当前epoch的损失函数值进行比较,以确定是否取得进展。 decay_threshold / decay_multiple: 如果损失函数值没有改善到decay_threshold * lookback_epochs的因子,则将应用decay_multiple到学习率。 spike_epochs: 你希望提高学习率的epoch编号列表。 spike_multiple: 对当前学习率应用的提高倍数。 """ 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(): returnif __name__ == '__main__': main()