You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在TensorFlow中为多分类与二分类任务实现Macro F1 Score?

修复TensorFlow Addons F1Score使用问题及Macro F1计算方案

错误原因

tfa.metrics.F1Score默认要求输入的标签为one-hot编码格式,且预测输出的维度需与类别数一致。而你的两个任务中:

  • 多分类任务使用了稀疏整数标签(y_train形状为(N,)),损失函数为sparse_categorical_crossentropy,但F1Score未配置对应参数处理稀疏标签;
  • 二分类任务使用了sigmoid输出(形状为(N,1)),但F1Score(num_classes=2)期望预测输出为(N,2)的one-hot格式,导致形状不匹配。

针对两个任务的修复方案

1. 多分类任务(3类)

方案A:转换标签为one-hot编码

将整数标签转为one-hot格式,同时修改损失函数为categorical_crossentropy:

from tensorflow.keras.utils import to_categorical
from tensorflow_addons.metrics import F1Score

# 转换标签
y_train_onehot = to_categorical(y_train, num_classes=3)
y_val_onehot = to_categorical(y_val, num_classes=3)

# 模型定义与编译
preds = layers.Dense(3, activation="softmax")(x)
model = keras.Model(int_sequences_input, preds)

f1_macro = F1Score(num_classes=3, average='macro')
model.compile(
    loss='categorical_crossentropy',
    optimizer='adam',
    metrics=['accuracy', f1_macro]
)

# 训练
model.fit(X_train, y_train_onehot, validation_data=(X_val, y_val_onehot), ...)

方案B:使用稀疏标签参数(TensorFlow Addons 0.12+支持)

无需修改标签,直接给F1Score添加sparse=True参数,适配稀疏整数标签:

from tensorflow_addons.metrics import F1Score

preds = layers.Dense(3, activation="softmax")(x)
model = keras.Model(int_sequences_input, preds)

# 配置sparse参数处理整数标签
f1_macro = F1Score(num_classes=3, average='macro', sparse=True)
model.compile(
    loss='sparse_categorical_crossentropy',
    optimizer='adam',
    metrics=['accuracy', f1_macro]
)

# 训练
model.fit(X_train, y_train, ...)

2. 二分类任务(2类)

方案A:改用softmax输出+one-hot标签

调整网络输出为2类softmax,转换标签为one-hot,适配F1Score:

from tensorflow.keras.utils import to_categorical
from tensorflow_addons.metrics import F1Score

# 转换标签
y_train_onehot = to_categorical(y_train, num_classes=2)
y_val_onehot = to_categorical(y_val, num_classes=2)

# 模型定义与编译
preds = layers.Dense(2, activation="softmax")(x)
model = keras.Model(int_sequences_input, preds)

f1_macro = F1Score(num_classes=2, average='macro')
model.compile(
    loss='categorical_crossentropy',
    optimizer='adam',
    metrics=['accuracy', f1_macro]
)

# 训练
model.fit(X_train, y_train_onehot, ...)

方案B:使用BinaryF1Score适配sigmoid输出

保持sigmoid输出不变,改用tfa.metrics.BinaryF1Score(二分类的Macro F1与Binary F1结果一致):

from tensorflow_addons.metrics import BinaryF1Score

preds = layers.Dense(1, activation="sigmoid")(x)
model = keras.Model(int_sequences_input, preds)

# 设置分类阈值(可根据需求调整)
f1_macro = BinaryF1Score(threshold=0.5)
model.compile(
    loss='binary_crossentropy',
    optimizer='adam',
    metrics=['accuracy', f1_macro]
)

# 训练
model.fit(X_train, y_train, ...)

自定义Macro F1指标(替代方案)

如果不想依赖TensorFlow Addons,可以自定义Keras指标类,更灵活适配不同标签格式:

多分类自定义Macro F1

import tensorflow as tf

class MacroF1(tf.keras.metrics.Metric):
    def __init__(self, num_classes, name='macro_f1', **kwargs):
        super().__init__(name=name, **kwargs)
        self.num_classes = num_classes
        self.precisions = self.add_weight(name='precisions', shape=(num_classes,), initializer='zeros')
        self.recalls = self.add_weight(name='recalls', shape=(num_classes,), initializer='zeros')
        self.class_counts = self.add_weight(name='class_counts', shape=(num_classes,), initializer='zeros')

    def update_state(self, y_true, y_pred, sample_weight=None):
        # 处理稀疏整数标签转one-hot
        if y_true.shape.rank == 1:
            y_true = tf.one_hot(tf.cast(y_true, tf.int32), depth=self.num_classes)
        # 预测结果转one-hot
        y_pred = tf.one_hot(tf.argmax(y_pred, axis=-1), depth=self.num_classes)
        
        true_pos = tf.reduce_sum(y_true * y_pred, axis=0)
        pred_pos = tf.reduce_sum(y_pred, axis=0)
        actual_pos = tf.reduce_sum(y_true, axis=0)
        
        # 避免除以0
        precision = tf.where(pred_pos > 0, true_pos / pred_pos, 0.0)
        recall = tf.where(actual_pos > 0, true_pos / actual_pos, 0.0)
        
        self.precisions.assign_add(precision)
        self.recalls.assign_add(recall)
        self.class_counts.assign_add(tf.cast(actual_pos > 0, tf.float32))

    def result(self):
        f1_scores = 2 * (self.precisions * self.recalls) / (self.precisions + self.recalls + tf.keras.backend.epsilon())
        return tf.reduce_sum(f1_scores) / tf.reduce_sum(self.class_counts)

# 使用示例
f1_macro = MacroF1(num_classes=3)
model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['accuracy', f1_macro])

二分类自定义Macro F1

import tensorflow as tf

class BinaryMacroF1(tf.keras.metrics.Metric):
    def __init__(self, threshold=0.5, name='binary_macro_f1', **kwargs):
        super().__init__(name=name, **kwargs)
        self.threshold = threshold
        self.tp = self.add_weight(name='tp', initializer='zeros')
        self.tn = self.add_weight(name='tn', initializer='zeros')
        self.fp = self.add_weight(name='fp', initializer='zeros')
        self.fn = self.add_weight(name='fn', initializer='zeros')

    def update_state(self, y_true, y_pred, sample_weight=None):
        y_pred = tf.cast(y_pred >= self.threshold, tf.float32)
        y_true = tf.cast(y_true, tf.float32)
        
        self.tp.assign_add(tf.reduce_sum(y_true * y_pred))
        self.tn.assign_add(tf.reduce_sum((1 - y_true) * (1 - y_pred)))
        self.fp.assign_add(tf.reduce_sum((1 - y_true) * y_pred))
        self.fn.assign_add(tf.reduce_sum(y_true * (1 - y_pred)))

    def result(self):
        # 计算两个类的F1并平均
        precision_0 = self.tn / (self.tn + self.fn + tf.keras.backend.epsilon())
        recall_0 = self.tn / (self.tn + self.fp + tf.keras.backend.epsilon())
        f1_0 = 2 * (precision_0 * recall_0) / (precision_0 + recall_0 + tf.keras.backend.epsilon())
        
        precision_1 = self.tp / (self.tp + self.fp + tf.keras.backend.epsilon())
        recall_1 = self.tp / (self.tp + self.fn + tf.keras.backend.epsilon())
        f1_1 = 2 * (precision_1 * recall_1) / (precision_1 + recall_1 + tf.keras.backend.epsilon())
        
        return (f1_0 + f1_1) / 2

# 使用示例
f1_macro = BinaryMacroF1(threshold=0.5)
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy', f1_macro])

基于Sklearn的回调计算方案

如果只需要在验证集上计算Macro F1,可以结合Sklearn的f1_score和Keras回调:

from sklearn.metrics import f1_score
from tensorflow.keras.callbacks import Callback

class MacroF1Callback(Callback):
    def __init__(self, X_val, y_val, is_binary=False):
        super().__init__()
        self.X_val = X_val
        self.y_val = y_val
        self.is_binary = is_binary

    def on_epoch_end(self, epoch, logs=None):
        y_pred = self.model.predict(self.X_val, verbose=0)
        if self.is_binary:
            y_pred = (y_pred >= 0.5).astype(int).flatten()
        else:
            y_pred = y_pred.argmax(axis=1)
        macro_f1 = f1_score(self.y_val, y_pred, average='macro')
        logs['val_macro_f1'] = macro_f1
        print(f"\nEpoch {epoch+1}: Validation Macro F1 = {macro_f1:.4f}")

# 多分类使用
val_callback = MacroF1Callback(X_val, y_val)
model.fit(X_train, y_train, validation_data=(X_val, y_val), callbacks=[val_callback], ...)

# 二分类使用
val_callback = MacroF1Callback(X_val, y_val, is_binary=True)
model.fit(X_train, y_train, validation_data=(X_val, y_val), callbacks=[val_callback], ...)

内容的提问来源于stack exchange,提问作者Filcenzo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 05:25:20