使用DistilBert二分类时Recall等指标报形状不兼容错误求解
问题描述
基于DistilBert开展二分类项目(垃圾短信/IMDB数据集均可复现),使用accuracy和sparse_categorical_accuracy指标时代码正常运行,但使用tf.keras.metrics.Recall()或tf.keras.metrics.AUC()时触发以下错误:
ValueError: Shapes (None, 2) and (None, 1) are incompatible
当前使用SparseCategoricalCrossentropy损失函数与Adam优化器,若更换为BinaryCrossentropy损失函数,会出现新的形状不匹配错误:
ValueError: logits and labels must have the same shape ((None, 2) vs (None, 1))
相关代码如下:
import pandas as pd import tensorflow as tf import transformers from transformers import DistilBertTokenizer from transformers import TFAutoModelForSequenceClassification pd.set_option('display.max_colwidth', None) MODEL_NAME = 'distilbert-base-uncased' BATCH_SIZE = 8 N_EPOCHS = 3 train = pd.read_csv("train_set.csv", error_bad_lines=False) test = pd.read_csv("test_set.csv", error_bad_lines=False) X_train = train.text X_test = test.text y_train = train.label y_test = test.label tokenizer = DistilBertTokenizer.from_pretrained(MODEL_NAME) train_encodings = tokenizer(list(X_train.values), truncation=True, padding=True) test_encodings = tokenizer(list(X_test.values), truncation=True, padding=True) train_dataset = tf.data.Dataset.from_tensor_slices((dict(train_encodings),list(y_train.values))) test_dataset = tf.data.Dataset.from_tensor_slices((dict(test_encodings),list(y_test.values))) test_dataset2 = test_dataset.shuffle(buffer_size=1024).take(1000).batch(16) model = TFAutoModelForSequenceClassification.from_pretrained(MODEL_NAME) optimizerr = tf.keras.optimizers.Adam(learning_rate=5e-5) losss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) model.compile(optimizer=optimizerr, loss=losss, metrics= ['accuracy','sparse_categorical_accuracy',tf.keras.metrics.Recall()]) print("Evaluate Base model on test data") results = model.evaluate(test_dataset2) print("test loss, test acc:", results)
核心原因
TFAutoModelForSequenceClassification.from_pretrained(MODEL_NAME)默认输出2维logits((batch_size, 2)),而你的标签是1维稀疏格式((batch_size, 1))。accuracy和sparse_categorical_accuracy专门适配稀疏标签与多类输出的组合,但Recall、AUC等指标默认需要输入与标签形状一致,或需显式配置参数适配该场景。
解决方案
方案1:保留稀疏标签,调整指标参数
继续使用SparseCategoricalCrossentropy损失,为Recall、AUC指定适配参数,让它们兼容2维logits和1维稀疏标签:
修改模型编译部分的metrics参数:
# 定义适配稀疏标签的指标:指定正类ID,或明确类别数 recall = tf.keras.metrics.Recall(class_id=1) # 计算类别1的召回率(根据你的正类标签调整) auc = tf.keras.metrics.AUC(num_classes=2, from_logits=True) # 基于logits计算二分类AUC model.compile(optimizer=optimizerr, loss=losss, metrics=['accuracy', 'sparse_categorical_accuracy', recall, auc])
方案2:转换标签为One-Hot格式,匹配模型输出形状
将1维稀疏标签转为2维one-hot编码,使标签形状与模型输出((batch_size,2))一致,可搭配CategoricalCrossentropy损失使用:
步骤1:转换标签格式
# 将标签转为one-hot编码 y_train_onehot = tf.one_hot(y_train.values, depth=2) y_test_onehot = tf.one_hot(y_test.values, depth=2) # 更新数据集 train_dataset = tf.data.Dataset.from_tensor_slices((dict(train_encodings), y_train_onehot)) test_dataset = tf.data.Dataset.from_tensor_slices((dict(test_encodings), y_test_onehot)) test_dataset2 = test_dataset.shuffle(buffer_size=1024).take(1000).batch(16)
步骤2:调整损失与指标
losss = tf.keras.losses.CategoricalCrossentropy(from_logits=True) model.compile(optimizer=optimizerr, loss=losss, metrics=['accuracy', tf.keras.metrics.Recall(), tf.keras.metrics.Precision(), tf.keras.metrics.AUC(from_logits=True)])
方案3:改用二分类专属模型输出,搭配BinaryCrossentropy
加载模型时指定num_labels=1,让模型输出1维logits((batch_size,1)),此时可使用BinaryCrossentropy损失,标签保持原始1维格式即可:
# 加载模型时指定num_labels=1,输出1维logits model = TFAutoModelForSequenceClassification.from_pretrained(MODEL_NAME, num_labels=1) losss = tf.keras.losses.BinaryCrossentropy(from_logits=True) # 将标签转为float32类型(BinaryCrossentropy要求标签为浮点型) y_train = tf.cast(y_train.values, tf.float32) y_test = tf.cast(y_test.values, tf.float32) # 更新数据集 train_dataset = tf.data.Dataset.from_tensor_slices((dict(train_encodings), y_train)) test_dataset = tf.data.Dataset.from_tensor_slices((dict(test_encodings), y_test)) test_dataset2 = test_dataset.shuffle(buffer_size=1024).take(1000).batch(16) model.compile(optimizer=optimizerr, loss=losss, metrics=['accuracy', tf.keras.metrics.Recall(), tf.keras.metrics.Precision(), tf.keras.metrics.AUC(from_logits=True)])
内容的提问来源于stack exchange,提问作者Injarapu Sri Sharanya

