一种解决keras的earlystopping的自定义metric麻烦(比如auc)的方法

本文介绍了一种解决Keras框架中EarlyStopping问题的方法。通过手动实现epoch迭代并利用sklearn评估函数,如AUC,来优化模型选择过程。这种方法避免了编写复杂符号函数,并能在训练停止时保留最佳模型。

keras是一个非常方便的深度学习框架,可以说是最好上手的框架(几乎没有之一吧)

他的earlystopping需要自定义metric才可以使用,存在两个比较麻烦的问题:

1.自定义metric的函数必须写成后端(theano或者TensorFlow)的符号函数的形式,对于一些比较复杂的计算函数,编写符号函数是一个麻烦的事儿,比如auc或者auc相关的gini系数。


2.keras的earlystopping如果你不结合makecheckpoint的方式来使用的话,在stop的时候无法保存最佳的模型。


3.自定义earlystopping可以解决一切问题,不过看代码毕竟稍微有一丢丢麻烦。


为了解决以上两个问题,实验室的师兄想出了以下代码,个人认为是一个非常好的思路,分享出来供参考,也算开源精神吧:

1.首先是有三个list保存最好模型和结果

    cv_models = [] #保存最好的模型
    cv_results = []#保存最好模型的oof预测结果,可以用来做stacking用
    cv_scores = [] #保存最好的预测分数

2.主要思路是自己手动epoch过后做预测然后算预测结果(跟callback.Earlystopping其实是一个道理)

优点是自己可以用各种sklearn的已有的评价函数(例如auc等)

clf=network1(train_X.shape[1])
        best = [0, 0, 0, 0]  # socre, epoch, model.copy , cv_result
        earlystop = 10
        for epoch in range(1000):
            clf.fit(train_X.values, train_Y.values,
                      batch_size=512, epochs=1, verbose=2)
            r = clf.predict(valid_X.values,
                              batch_size=512)
            s = 2 * roc_auc_score(valid_Y, r) - 1
            print("cv, epoch, score:", cv, epoch, s)
            if s > best[0]:
                best = [s, epoch, copy.copy(clf), r]
            if epoch - best[1] > earlystop:
                break
        tmp=pd.DataFrame()
        tmp['id']=valid_data['id']
        tmp['cv_result']=best[-1][:,0]
        cv_results.append(tmp)
        cv_models.append(best[2])
        cv_scores.append(best[0])

再次感谢我的师兄分享他的代码给我,学习了很多!


<think>我们正在讨论Keras自定义评估指标的计算频率以及多个评估指标如何取平均的问题。根据用户的问题,我们需要解释两个关键点:1)自定义评估指标在训练过程中何时被计算;2)当有多个评估指标时,Keras如何对它们进行平均。首先,关于计算频率: 在Keras中,评估指标(无论是内置的还是自定义的)在以下情况下会被计算:-在训练过程中,每个批次(batch)训练结束后,会计算该批次的指标值(用于在进度条中显示)。但是,请注意,这些批次级别的指标值并不是最终用于记录和回调函数的值。实际上,在每个训练周期(epoch)结束时,Keras会计算整个训练集(或验证集)的指标值,这个值是通过对当前周期内所有批次的指标值进行平均得到的(具体平均方式取决于指标类型,但通常默认是加权平均,权重为批次大小)。-此外,当使用验证集(通过`validation_data`或`validation_split`设置)时,在每周期结束时也会计算验证集上的指标值,同样是对整个验证集计算(或分批次计算然后平均)。 -另外,当调用`model.evaluate()`时,也会计算指定数据集上的指标值。其次,多个评估指标如何取平均: 当我们为模型指定多个评估指标(例如:`metrics=['accuracy', custom_metric]`)时,每个指标都是独立计算的。在训练过程中,每个指标在每个批次上都会被计算,然后在每个周期结束时,每个指标都会独立地计算整个周期(或整个验证集)的平均值。 具体来说,Keras在计算每个指标在整个周期上的平均值时,使用的是加权平均。权重是每个批次的样本数。例如,假设一个训练周期有3个批次,批次大小分别为100、100、50(总样本数250),某个指标在这三个批次上的值分别为0.5、0.6、0.7。那么该指标在整个周期上的平均值计算为:$$(100\times0.5 +100\times0.6+50 \times0.7)/250 =(50+60+35)/250=145/250 =0.58$$注意:这种加权平均方式适用于大多数指标,包括准确率(accuracy)等。但是,有些指标可能不适合这样平均(例如AUC),因此Keras提供了`tf.keras.metrics.AUC`这样的指标类,它们会累积整个周期的结果再计算(而不是简单地对批次值平均)。 对于自定义指标,我们有两种方式:1.函数式自定义:它会被当作一个批次级别的函数,然后在周期结束时自动进行加权平均(如上所述)。2.类继承式自定义(继承`tf.keras.metrics.Metric`):我们需要自己实现`update_state`和`result`方法,这样我们可以控制如何累积状态(例如,累积误差平方和与总样本数)和如何计算最终结果(例如,用累积的误差平方和除以总样本数再开方得到RMSE)。这样,在周期结束时,Keras会调用`result`方法得到整个周期的指标值,而不会对批次值进行平均。因此,如果我们希望自定义指标在整个数据集上计算(而不是对批次平均),我们应该使用类继承的方式。例如,我们自定义的RMSE指标(如上文中的`CustomRMSE`)就是通过累积总平方误差和总样本数,然后在`result`中一次性计算整个周期的RMSE。下面我们用一个例子来说明: 假设我们有两个指标:准确率(内置)和自定义的RMSE(用类实现)。在训练过程中:-每个批次结束后,我们会计算该批次的准确率和该批次的RMSE(对于RMSE,我们实际上是更新状态变量,但不会立即计算整个周期的RMSE)。-每个周期结束时:-对于准确率:Keras会计算该周期所有批次的准确率的加权平均(权重为批次大小)。-对于RMSE:Keras会调用`result`方法,该方法使用整个周期累积的总平方误差和总样本数来计算RMSE。因此,两种自定义指标的方式在计算频率和平均方式上是有区别的。总结:-计算频率:每个批次都会计算(用于进度条显示),但每个周期结束时会对整个数据集重新计算(或对批次进行平均)以记录最终值。-多个指标:每个指标独立计算,并在周期结束时独立计算整个周期的值(函数式自定义指标默认进行加权平均,类继承式自定义指标由用户控制如何计算整个周期的值)。参考文献: -关于模型评估和回调函数,可参考引用[1][2]。 -关于损失加权和验证集划分,可参考引用[3]。-关于嵌入层和词向量,可参考引用[4]。-关于回调函数ReduceLROnPlateau,可参考引用[5]。</think>### Keras自定义评估指标的计算频率与平均方式详解 #### 一、评估指标的计算频率 在Keras中,评估指标的计算发生在两个关键阶段: 1. **每个批次结束时**: - 在训练过程中,每个批次(batch)完成后会计算当前批次的指标值 - 这些值用于实时监控训练进度,但不会被永久保存 - 计算频率:每$\frac{总样本数}{批次大小}$个样本计算一次 2. **每个周期结束时**: - 在epoch结束时计算整个训练集/验证集的指标值 - 这是实际记录在`history`对象和回调函数中的值 - 计算频率:每个训练周期(epoch)结束时计算一次[^1][^2] ```python # 示例:训练后查看周期级指标 history = model.fit(...) print(history.history['val_custom_metric']) # 每个epoch的指标值 ``` #### 二、多个指标的平均方式 当指定多个评估指标时,Keras采用**加权平均**策略: 1. **基本平均原理**: $$ \text{epoch指标} = \frac{\sum_{i=1}^{N} (\text{批次指标}_i \times \text{批次大小}_i)}{\text{总样本数}} $$ 其中$N$是批次数量[^3] 2. **不同指标类型的平均方式**: | 指标类型 | 平均方式 | 示例 | |---------|---------|------| | 准确率类 | 样本加权平均 | `(TP+TN)/总样本` | | 回归类 | 误差值加权平均 | `总MSE/总样本` | | 自定义函数 | 自动加权平均 | 用户定义函数返回值 | | Metric子类 | 自定义状态管理 | 通过`update_state()`累积 | 3. **验证集的特殊处理**: - 使用`validation_split`时,取**最后部分样本**作为验证集[^3] - 验证集指标单独计算,不与训练集混合平均 #### 三、自定义指标的平均控制 ##### 方法1:函数式自定义(自动加权平均) ```python def precision(y_true, y_pred): # 系统自动加权平均 tp = K.sum(K.round(K.clip(y_true * y_pred, 0, 1))) pred_pos = K.sum(K.round(K.clip(y_pred, 0, 1))) return tp / (pred_pos + K.epsilon()) ``` ##### 方法2:继承Metric类(手动控制) ```python class F1Score(tf.keras.metrics.Metric): def __init__(self, name='f1', **kwargs): super().__init__(name=name, **kwargs) self.precision = self.add_weight(name='prec', initializer='zeros') self.recall = self.add_weight(name='rec', initializer='zeros') self.count = self.add_weight(name='count', initializer='zeros') def update_state(self, y_true, y_pred, sample_weight=None): # 手动累积状态 prec = precision(y_true, y_pred) rec = recall(y_true, y_pred) if sample_weight: weight = tf.reduce_sum(sample_weight) else: weight = tf.cast(tf.shape(y_true)[0], tf.float32) self.precision.assign_add(prec * weight) self.recall.assign_add(rec * weight) self.count.assign_add(weight) def result(self): # 自定义平均公式 p = self.precision / self.count r = self.recall / self.count return 2 * ((p * r) / (p + r + K.epsilon())) ``` #### 四、最佳实践与验证 1. **指标一致性检查**: ```python # 训练后验证指标计算 test_metrics = model.evaluate(test_data) print(f"自定义指标值: {test_metrics[2]:.4f}") # 假设是第三个指标 # 与history比较应一致 last_epoch_val = history.history['val_custom_metric'][-1] assert abs(test_metrics[2] - last_epoch_val) < 1e-5 ``` 2. **回调函数集成**: ```python # 在回调中使用自定义指标 early_stop = tf.keras.callbacks.EarlyStopping( monitor='val_custom_metric', # 监控自定义指标 patience=5, mode='max' # 根据指标特性设定 ) reduce_lr = tf.keras.callbacks.ReduceLROnPlateau( monitor='val_custom_metric', # 引用[5] factor=0.5, patience=2 ) ``` #### 五、特殊场景处理 1. **非对称加权**: ```python def weighted_accuracy(y_true, y_pred): # 按类别权重平均 weights = tf.constant([0.2, 0.3, 0.5]) # 三类样本权重 class_acc = [] for i in range(3): mask = tf.equal(y_true, i) class_acc.append(tf.reduce_mean( tf.cast(tf.equal(y_true[mask], y_pred[mask]), tf.float32) )) return tf.reduce_sum(weights * tf.stack(class_acc)) ``` 2. **时间序列指标**: ```python class TimeSeriesMAE(tf.keras.metrics.Metric): # 处理时间步权重的平均 def update_state(self, y_true, y_pred, sample_weight=None): # sample_weight可包含时间步权重 errors = tf.abs(y_true - y_pred) if sample_weight is not None: errors *= sample_weight total_weight = tf.reduce_sum(sample_weight) else: total_weight = tf.cast(tf.size(y_true), tf.float32) self.total_error.assign_add(tf.reduce_sum(errors)) self.total_weight.assign_add(total_weight) ``` ### 常见问题解答 **Q:为什么批次指标与周期结束指标不同?** A:批次指标是瞬时值,周期指标是加权平均值,尤其当数据分布不均时差异明显[^3] **Q:如何实现全局指标而非批次平均?** A:需继承`Metric`类,在`update_state`中累积全局状态,在`result`中一次性计算 **Q:多输出模型如何处理?** A:为每个输出指定独立指标: ```python model.compile( optimizer='adam', loss={'output1': 'mse', 'output2': 'binary_crossentropy'}, metrics={'output1': ['mae', custom_metric], 'output2': ['accuracy']} ) ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值