Python 如何在tensorflow中的EarlyStopping回调中监视度量的筛选版本?
我总是有这个问题。当训练神经网络时,验证损失可能是有噪声的(有时甚至是训练损失,如果你使用的是随机层,比如辍学)。当数据集很小时尤其如此 这使得在使用诸如Python 如何在tensorflow中的EarlyStopping回调中监视度量的筛选版本?,python,tensorflow,keras,Python,Tensorflow,Keras,我总是有这个问题。当训练神经网络时,验证损失可能是有噪声的(有时甚至是训练损失,如果你使用的是随机层,比如辍学)。当数据集很小时尤其如此 这使得在使用诸如earlystoping或reducelRonplation之类的回调时,这些回调被触发得太早(即使使用了很大的耐心)。此外,有时我不想在reducelRonplation回调中使用太多耐心 解决这一问题的方法是,不直接监控某个指标(如val_损失),而是监控该指标的过滤版本(跨时代)(如val_损失的指数移动平均值)。然而,我看不到任何简单的
earlystoping
或reducelRonplation
之类的回调时,这些回调被触发得太早(即使使用了很大的耐心)。此外,有时我不想在reducelRonplation
回调中使用太多耐心
解决这一问题的方法是,不直接监控某个指标(如val_损失),而是监控该指标的过滤版本(跨时代)(如val_损失的指数移动平均值)。然而,我看不到任何简单的方法来解决这个问题,因为回调只接受不依赖于以前时代的指标。我曾尝试使用自定义的训练循环来使用自定义的筛选指标重现这些回调的功能,但我认为这不是正确的方法。是否有另一种(更简单的)方法来监视回调中丢失的过滤版本,而无需重新实现回调的全部功能
编辑:
这就是我所说的监视度量的过滤版本的意思。当前earlystoping
的工作原理如下:
best\u loss=float('inf')
最佳纪元=0
对于范围内的历元(n_历元):
# ...
新损失=#计算当前历元的损失
如果新损失<最佳损失:
最佳损失=新损失
最佳纪元
如果历元-最佳历元>耐心:
打破
监控过滤后的指标如下所示:
best\u loss=float('inf')
过滤损失=10#示例初始值
最佳纪元=0
对于范围内的历元(n_历元):
# ...
新损失=#计算当前历元的损失
过滤损耗=0.1*新损耗+0.9*过滤损耗
如果过滤损耗<最佳损耗:
最佳损耗=过滤损耗
最佳纪元
如果历元-最佳历元>耐心:
打破
我经常遇到这个问题,因此我编写了一个自定义回调函数,最初用于监控训练的准确性,并在此基础上调整学习率。这避免了验证丢失最初会发生根本性变化的问题,我有一个名为“threshold”的参数。一旦训练精度超过阈值,回调将切换到监控验证丢失,并根据该值调整学习速率。在训练结束时,回调也总是将模型的权重设置为损失最低的历元的权重。此代码将在此处提供。然而,要了解如何编写自定义回调,文档是我将沿着编写自定义度量的路径(将keras.Metric子类化)。Keras度量在批处理中是有状态的。要使用逐历元平滑,您需要让此度量引用另一组存储以前历元结果的张量,并手动重置这些值。。。e、 培训开始前。然后,您的回调可以按名称引用此度量
实施示例:
from tensorflow.keras import backend as K
class SmothedMSE(keras.metrics.Metric):
def __init__(self, **kwargs):
super().__init__(name='smothed_mse', **kwargs)
self.history = self.add_weight(name='history', shape=(4,), initializer='zeros')
self.sum = self.add_weight(name='sum', initializer='zeros')
self.count = self.add_weight(name='count', initializer='zeros', dtype=tf.int32)
def clear(self):
K.set_value(self.history, np.zeros(4))
def update_state(self, y_true, y_pred, sample_weight=None):
x = keras.metrics.mse(y_true, y_pred)
self.sum.assign_add(tf.reduce_sum(x, axis=-1))
self.count.assign_add(tf.shape(x)[0])
def result(self):
h = tf.reduce_sum(self.history)
nhistory = tf.roll(self.history, shift=1, axis=0)
x = tf.divide(self.sum, tf.cast(self.count, tf.float32))
nhistory = tf.tensor_scatter_nd_update(nhistory, [[0]], tf.expand_dims(x, 0))
self.history.assign(nhistory)
return (x + h) / 2.
def reset_state(self):
K.set_value(self.sum, 0.)
K.set_value(self.count, 0)
详情请浏览:
这里有一个相对简单的解决问题的方法。您可以创建一个自定义的
tf.keras.metrics.Metric
,它具有一个tf.keras.metrics.Metric
属性和一个特定的内存大小。度量的结果将是内存中值的平均值。您可以在回调中监视该指标
class AveragePreviousMetric(tf.keras.metrics.Metric):
def __init__(self, metric, num_updates, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.num_updates = num_updates
self.accumulator = tf.Variable(tf.zeros(self.num_updates))
self.idx = tf.Variable(0)
self.metric = metric
def update_state(self, *args, **kwargs):
self.metric.update_state(*args, **kwargs)
def reset_states(self, *args, **kwargs):
# a simple queue with modulo indexing
update_op = self.accumulator.scatter_update(
tf.IndexedSlices(self.metric.result(), self.idx % self.num_updates)
)
# we just want to be sure to only increase the counter
# if the variable has been updated
with tf.control_dependencies([update_op]):
self.idx.assign_add(1)
# we reset the state of the internal metric
self.metric.reset_states(*args, **kwargs)
def _result(self):
return self.metric.result()
def _filtered_result(self):
return 0.9 * self.accumulator[: self.idx] + 0.1 * self._result()
def result(self):
# if the idx is zero, the accumulator is empty and _filtered_result will yield an error
return tf.cond(tf.equal(self.idx, 0), self._result, self._filtered_result)
还有一个关于如何使用它的简单示例:
m = tf.keras.Sequential([tf.keras.layers.Dense(1, input_shape=(1,))])
x = tf.random.uniform((100, 1))
y = tf.random.uniform((100, 1))
avg_mse = AveragePreviousMetric(
tf.keras.metrics.MeanSquaredError(), name="avg_mse", num_updates=5
)
m.compile(loss="mse", metrics=[avg_mse, "mse"], optimizer="sgd")
hist = m.fit(x, y, epochs=5, verbose=2)
运行此示例时:
Epoch 1/5
4/4 - 0s - loss: 0.1956 - avg_mse: 0.1956 - mse: 0.1956
Epoch 2/5
4/4 - 0s - loss: 0.1943 - avg_mse: 0.1954 - mse: 0.1943
Epoch 3/5
4/4 - 0s - loss: 0.1932 - avg_mse: 0.1948 - mse: 0.1932
Epoch 4/5
4/4 - 0s - loss: 0.1919 - avg_mse: 0.1941 - mse: 0.1919
Epoch 5/5
4/4 - 0s - loss: 0.1908 - avg_mse: 0.1935 - mse: 0.1908
多亏了@Pedro Marques和@lescurel的贡献,我成功地创建了一个度量类,可以平滑各个时代的任何其他度量。由于使用了不同的状态变量,在培训期间使用验证集时,它也能够工作 有一个基类管理状态逻辑:
类平滑度量(tf.keras.metrics.Metric):
定义初始化(self,metric,name=None,**kwargs):
如果名称为“无”:
name='smooth_uz'+metric.name
super()
self.metric=公制
self.in_test_step=tf.Variable(假)
self.prev\u in\u test\u step=tf.变量(False)
self.train_states={}
self.test_states={}
def add_state_变量(self、name、**kwargs):
self.train_states[name]=tf.Variable(**kwargs)
self.test_states[name]=tf.Variable(**kwargs)
def get_state_变量(self,name):
返回tf.cond(self.in_test_step,lambda:self.test_states[name],lambda:self.train_states[name])
def更新_状态(自身、*args、**kwargs):
self.metric.update_state(*args,**kwargs)
self.prev\u in\u test\u step.assign(self.in\u test\u step)
def更新\过滤器\状态\以前的纪元(自身):
raise NOTEImplementedError('需要覆盖')
def重置_状态(自身、*args、**kwargs):
#更新前一模式的状态
tmp=自测试步骤读取值()
self.in_test_step.assign(self.prev_in_test_step)
self.update\u filter\u state\u prevoius\u epoch()
自我测试步骤分配(tmp)
自我度量重置状态(*args,**kwargs)
然后,我定义了一个从上一个继承的类,它实现了过滤器。在这种情况下,它实现了指数移动平均,但也可以使用其他过滤器,如简单移动平均
类SmoothMetricEMA(SmoothMetric):
def _;初始__;(自我,度量,alpha=0.1,name=None,**kwargs)->无:
super()
self.alpha=alpha
self.add_state_变量('acum',初始值=0.0)
self.add_state_变量('idx',初始值=0)
def更新\过滤器\状态\以前的纪元(自身):
self.get\u state\u变量('acum').assign(self.result())
self.get_state_变量('idx'))
metrics=[
SmoothMetricEMA(tf.metrics.MeanSquaredError(), alpha=0.1, name='smooth'),
]
callbacks = [
tf.keras.callbacks.EarlyStopping(monitor='val_smooth', patience=10, verbose=1),
NotifySmoothMetricsCallback(),
]
model.compile(optimizer='adam', loss='mse', metrics=metrics)
model.fit(x_train, y_train, batch_size=32, epochs=100,
validation_data=(x_val, y_val),
callbacks=callbacks,
)