我正在使用HuggingFace的transformers库、Keras和BERT构建一个多类文本分类模型。
为了将我的输入转换为所需的BERT格式,我使用了BertTokenizer类中的encode_plus
方法,在这里找到
数据是每个特征的一段句子,并且有一个标签(总共45个标签)
转换输入的代码如下:
def create_input_array(df, tokenizer):
sentences = df.text.values
labels = df.label.values
input_ids = []
attention_masks = []
token_type_ids = []
# 对每个句子...
for sent in 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=128, # 填充并截断所有句子。
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'])
return [np.asarray(input_ids, dtype=np.int32),
np.asarray(attention_masks, dtype=np.int32),
np.asarray(token_type_ids, dtype=np.int32)]
模型的最基本形式仍然会重现错误:
model = TFBertForSequenceClassification.from_pretrained("bert-base-uncased",num_labels = labellen,output_attentions = False, output_hidden_states = False)
编译和拟合:
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3, epsilon=1e-08, clipnorm=1.0)
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
metric = tf.keras.metrics.SparseCategoricalAccuracy('accuracy')
model.compile(optimizer=optimizer, loss=loss, metrics=[metric])
model.fit(x_train, y[:100], epochs=1, batch_size=3)
运行时出现的错误:
ValueError: 无法将包含768个元素的张量重塑为形状 [1,1,128,1](128个元素),对于 ‘{{node tf_bert_for_sequence_classification_3/bert/embeddings/LayerNorm/Reshape}} = Reshape[T=DT_FLOAT, Tshape=DT_INT32](tf_bert_for_sequence_classification_3/bert/embeddings/LayerNorm/Reshape/ReadVariableOp, tf_bert_for_sequence_classification_3/bert/embeddings/LayerNorm/Reshape/shape)’,输入形状为 [768],[4],并且输入张量计算为部分形状:输入1 = [1,1,128,1]。
我知道BERT将每个标记转换为一个768值的数组,但这就是我对这个特定数字的全部了解,所以我不知道如何继续进行。
如果有人有使用HuggingFace库的经验,我也非常希望听到您对TFBertForSequenceClassification是否适合段落分类任务的看法。
非常感谢。
回答:
如果其他人也需要帮助解决这个问题,虽然这是一个相当复杂的修复,但我在这里分享了我的做法:
从使用numpy数组改为使用tf数据集
我认为这不是完全必要的,所以如果你仍然使用numpy数组,请忽略这一段,并相应地调整下面的重塑函数(从tf.reshape改为np reshape方法)
从:
return [np.asarray(input_ids, dtype=np.int32),
np.asarray(attention_masks, dtype=np.int32),
np.asarray(token_type_ids, dtype=np.int32)]
到:
input_ids = tf.convert_to_tensor(input_ids)
attention_masks = tf.convert_to_tensor(attention_masks)
return input_ids, attention_masks
(所以列表被转换为张量)
调用转换输入函数(注意省略了token_type_ids)
根据文档,注意力掩码和token类型id对BERT来说是可选的。在这个例子中,我只使用input_ids和attention_masks
train_ids, train_masks = create_input_array(df[:], tokenizer=tokenizer)
重塑输入
train_ids = tf.reshape(train_ids, (-1, 128, 1))
train_masks = tf.reshape(train_masks, (-1, 128, 1))
将标签转换为张量
labels = tf.convert_to_tensor(y[:])
n_classes = np.unique(y).max() + 1
将所有张量导入tf数据集
dataset = tf.data.Dataset.from_tensors(( (train_ids, train_masks), labels ))
加载BERT模型并添加层
之前我只有一个简短的模型定义,现在我为input_ids和掩码创建了输入层,只返回BERT层的第一个输出,扁平化,然后添加一个密集层。
model = TFBertForSequenceClassification.from_pretrained('bert-base-uncased', trainable=False)
# 输入层
input_layer = Input(shape=(128, ), dtype=np.int32)
input_mask_layer = Input(shape=(128, ), dtype=np.int32)
# BERT层,只返回第一个输出
bert_layer = model([input_layer, input_mask_layer])[0]
# 扁平化层
flat_layer = Flatten()(bert_layer)
# 密集层
dense_output = Dense(n_classes, activation='softmax')(flat_layer)
model_ = Model(inputs=[input_layer, input_mask_layer], outputs=dense_output)
编译模型
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3, epsilon=1e-08, clipnorm=1.0)
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
metric = tf.keras.metrics.SparseCategoricalAccuracy('accuracy')
model_.compile(optimizer=optimizer, loss=loss, metrics=[metric])
这里整个数据集作为第一个参数传递,其中也包含了标签。
model_.fit(dataset, epochs=4, batch_size=4, verbose=1)
希望这对你有帮助。