Tensorflow/Keras/BERT 多类文本分类准确率

我正在尝试微调 HuggingFace 的 TFBertModel,以便能够将某些文本分类为单一标签。我已经启动并运行了模型,但从一开始准确率就非常低。我的预期是,由于使用了 BERT 的预训练权重作为起点,准确率应该会很高。我希望能得到一些关于我哪里做错的建议。

我使用的是来自这里的 bbc-text 数据集:

加载数据

df = pd.read_csv(open(<s3 url>),encoding='utf-8', error_bad_lines=False)df = df.sample(frac=1)df = df.dropna(how='any')

值计数

sport            511business         510politics         417tech             401entertainment    386Name: label, dtype: int64

预处理

def preprocess_text(sen):# 将html实体转换为普通文本sentence = unescape(sen)# 移除html标签sentence = remove_tags(sentence)# 移除换行符sentence = remove_newlinechars(sentence)# 移除标点符号和数字sentence = re.sub('[^a-zA-Z]', ' ', sentence)# 转换为小写sentence = sentence.lower()return sentencedef remove_newlinechars(text):    return " ".join(text.splitlines()) def remove_tags(text):    TAG_RE = re.compile(r'<[^>]+>')    return TAG_RE.sub('', text)df['text_prepd'] = df['text'].apply(preprocess_text)

分割数据

train, val = train_test_split(df, test_size=0.30, shuffle=True, stratify=df['label'])

编码标签

from sklearn.preprocessing import LabelEncoderlabel_encoder = LabelEncoder()y_train = np.asarray(le.fit_transform(train['label']))y_val = np.asarray(le.fit_transform(val['label']))

定义 BERT 输入函数

# 初始化 Bert Tokenizerbert_tokenizer_transformer = BertTokenizer.from_pretrained('bert-base-cased')def create_input_array(df, tokenizer, args):    sentences = df.text_prepd.values    input_ids = []    attention_masks = []    token_type_ids = []    for sent in tqdm(sentences):        # `encode_plus` 将会:        #   (1) 对句子进行分词。        #   (2) 在句子开头添加 `[CLS]` 标记。        #   (3) 在句子末尾添加 `[SEP]` 标记。        #   (4) 将标记映射到它们的 ID。        #   (5) 将句子填充或截断至 `max_length`。        #   (6) 为 [PAD] 标记创建注意力掩码。        encoded_dict = tokenizer.encode_plus(            sent,  # 要编码的句子。            add_special_tokens=True,  # 添加 '[CLS]' 和 '[SEP]'            max_length=args.max_seq_len,  # 填充和截断所有句子。                pad_to_max_length=True,                return_attention_mask=True,  # 构建注意力掩码。                return_tensors='tf',  # 返回 tf 张量。            )        # 将编码后的句子添加到列表中。        input_ids.append(encoded_dict['input_ids'])        # 以及它的注意力掩码(简单区分填充和非填充)。        attention_masks.append(encoded_dict['attention_mask'])        token_type_ids.append(encoded_dict['token_type_ids'])    input_ids = tf.convert_to_tensor(input_ids)    attention_masks = tf.convert_to_tensor(attention_masks)    token_type_ids = tf.convert_to_tensor(token_type_ids)    return input_ids, attention_masks, token_type_ids

将数据转换为 BERT 输入

train_inputs = [create_input_array(train[:], tokenizer=tokenizer, args=args)]val_inputs = [create_input_array(val[:], tokenizer=tokenizer, args=args)]

对于 train_inputs, y_trainval_inputs, y_val,我随后应用了下面的函数,该函数会重塑并转换为 numpy 数组。这个函数返回的列表随后作为参数传递给 keras 的 fit 方法。我意识到这样转换为 tf.tensors 然后再转换为 numpy 有点多余,但我认为这不会有影响。我最初尝试使用 tf.datasets,但后来改用了 numpy。

def convert_inputs_to_tf_dataset(inputs,y, args):    # args.max_seq_len = 256    ids = inputs[0][1]    masks = inputs[0][1]    token_types = inputs[0][2]    ids = tf.reshape(ids, (-1, args.max_seq_len))    print("Input ids shape: ", ids.shape)    masks = tf.reshape(masks, (-1, args.max_seq_len))    print("Input Masks shape: ", masks.shape)    token_types = tf.reshape(token_types, (-1, args.max_seq_len))    print("Token type ids shape: ", token_types.shape)    ids=ids.numpy()    masks = masks.numpy()    token_types = token_types.numpy()    return [ids, masks, token_types, y]

Keras 模型

# args.max_seq_len = 256# n_classes = 6model = TFBertForSequenceClassification.from_pretrained('bert-base-uncased', trainable=True, num_labels=n_classes)input_ids_layer = Input(shape=(args.max_seq_len, ), dtype=np.int32)input_mask_layer = Input(shape=(args.max_seq_len, ), dtype=np.int32)input_token_type_layer = Input(shape=(args.max_seq_len,), dtype=np.int32)bert_layer = model([input_ids_layer, input_mask_layer, input_token_type_layer])[0]flat_layer = Flatten()(bert_layer)dropout= Dropout(0.3)(flat_layer)dense_output = Dense(n_classes, activation='softmax')(dropout)model_ = Model(inputs=[input_ids_layer, input_mask_layer, input_token_type_layer], outputs=dense_output)

编译和拟合

loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)metric = tf.keras.metrics.SparseCategoricalAccuracy('accuracy')model.compile(optimizer='adam', loss=loss, metrics=[metric])model.fit(inputs=..., outputs=..., validation_data=..., epochs=50, batch_size = 32, metrics=metric, verbose=1)Epoch 32/501401/1401 [==============================] - 42s 30ms/sample - loss: 1.6103 - accuracy: 0.2327 - val_loss: 1.6042 - val_accuracy: 0.2308

由于我在使用 BERT,只需要几个周期,因此我在32个周期后期望得到的准确率远高于23%。


回答:

主要问题在于这一行:ids = inputs[0][1]。实际上,ids 是 inputs[0] 的第一个元素;所以应该是 ids = inputs[0][0]

但还有另一个问题可能会导致验证准确率不一致:你应该只对标签映射进行一次 LabelEncoder 的拟合;因此,你应该对验证标签使用 transform 方法,而不是 fit_transform 方法。

此外,不要同时在损失函数中使用 softmax 激活和 from_logits=True;只使用其中一个(更多信息请见这里)。

还有一个要点是你可能需要为优化器使用更低的学习率。Adam 优化器的默认学习率为 1e-3,考虑到你正在微调预训练模型,这可能太高了。尝试使用更低的学习率,比如 1e-4 或 1e-5;例如 tf.keras.optimizers.Adam(learning_rate=1e-4)。对于微调预训练模型,使用较高的学习率可能会破坏已学习的权重,并扰乱微调过程(特别是在微调过程开始时,由于生成的大梯度值)。

Related Posts

使用LSTM在Python中预测未来值

这段代码可以预测指定股票的当前日期之前的值,但不能预测…

如何在gensim的word2vec模型中查找双词组的相似性

我有一个word2vec模型,假设我使用的是googl…

dask_xgboost.predict 可以工作但无法显示 – 数据必须是一维的

我试图使用 XGBoost 创建模型。 看起来我成功地…

ML Tuning – Cross Validation in Spark

我在https://spark.apache.org/…

如何在React JS中使用fetch从REST API获取预测

我正在开发一个应用程序,其中Flask REST AP…

如何分析ML.NET中多类分类预测得分数组?

我在ML.NET中创建了一个多类分类项目。该项目可以对…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注