在多标签分类的问题中,模型的训练集由实例组成,每个实例可以被分配多个类别,表示为一组目标标签,最终任务是准确预测测试数据的标签集。例如:
多标签和多分类有什么区别?
在多分类中,每个样本被分配到一个且只有一个标签:水果可以是苹果或梨,但不能同时是苹果和梨。让我们考虑三个类别的例子C = [“Sun,”Moon,Cloud“]。在多分类中,每个样本只可以属于其中一个C类;在多标签中,每个样本可以属于一个或多个类。
在这篇文章, 我们将使用Kaggle的 Toxic Comment Classification Challenge数据集,该数据集由大量维基百科评论组成,这些评论已经被专业评估者标记为恶意行为。恶意的类型为:
toxic(恶意),severetoxic(穷凶极恶),obscene(猥琐),threat(恐吓),insult(侮辱),identityhate(种族歧视)
例:
“Hi! I am back again! Last warning! Stop undoing my edits or die!”
被标记为[1,0,0,1,0,0]。意思是它同时属于toxic 和threat。
2018 年 10 月,Google 发布了一种名为 BERT 的新语言表示模型, 它代表来自Transformers的双向编码器表示。BERT建立在预训练上下文表示模型—半监督序列学习、生成预训练、ELMo和ULMFit 的基础上。但是,与之前的模型不同,BERT 是第一个深度双向、无监督的语言表示形式。仅使用纯文本语料库(维基百科)进行预训练。
预训练表示可以分为无上下文模型与上下文模型:
基于双向 LSTM 的语言模型会训练一个标准的从左到右的语言模型,并训练从右到左(反向)的语言模型。该模型可预测后续单词(如 ELMO 中的单词)中的先前单词,在ELMo中,前向语言模型和后向语言模型都分别是一个LSTM模型,关键的区别在于,LSTM都不会同时考虑前一个和后一个令牌。
直观地说,深度双向模型比从左到右模型或从左到右和从右到左模型的串联更为严格。遗憾的是,标准条件语言模型只能从左到右或从右到左进行训练,因为双向调节将允许每个单词在多层上下文中间接地“看到自己”。
为了解决这个问题,Bert使用“掩蔽”技术(MASKING)在输入中屏蔽一些单词,然后双向调节每个单词以预测被屏蔽的单词。例如:
BERT 还学会根据一个非常简单的任务对句子之间的关系进行建模, 该任务可以从任何文本语料库生成: 给定两个句子 A 和 B,B 是语料库中 A 之后的实际下一句,还是一个随机句子?例如:
多分类的问题我在上一篇文章中已经详细讨论过: tensorflow 2.0+ 基于BERT模型的文本分类 。本文将重点研究BERT在多标签文本分类中的应用。因此,我们只需修改相应代码,使其适合多标签方案。
现在,我们需要在所有样本中应用 BERT tokenizer 。我们将token映射到词嵌入。这可以通过encode_plus完成。
def convert_example_to_feature(review):
# combine step for tokenization, WordPiece vector mapping, adding special tokens as well as truncating reviews longer than the max length
return tokenizer.encode_plus(review,
add_special_tokens = True, # add [CLS], [SEP]
max_length = max_length, # max length of the text that can go to BERT
pad_to_max_length = True, # add [PAD] tokens
return_attention_mask = True, # add attention mask to not focus on pad tokens
truncation=True
)
# map to the expected input to TFBertForSequenceClassification, see here
def map_example_to_dict(input_ids, attention_masks, token_type_ids, label):
return {
"input_ids": input_ids,
"token_type_ids": token_type_ids,
"attention_mask": attention_masks,
}, label
def encode_examples(ds, limit=-1):
# prepare list, so that we can build up final TensorFlow dataset from slices.
input_ids_list = []
token_type_ids_list = []
attention_mask_list = []
label_list = []
if (limit > 0):
ds = ds.take(limit)
for (i, row) in enumerate(ds.values):
# for index, row in ds.iterrows():
# review = row["text"]
# label = row["y"]
review = row[1]
label = list(row[2:])
bert_input = convert_example_to_feature(review)
input_ids_list.append(bert_input['input_ids'])
token_type_ids_list.append(bert_input['token_type_ids'])
attention_mask_list.append(bert_input['attention_mask'])
label_list.append(label)
return tf.data.Dataset.from_tensor_slices((input_ids_list, attention_mask_list, token_type_ids_list, label_list)).map(map_example_to_dict)
我们可以使用以下函数对数据集进行编码:
# train dataset
ds_train_encoded = encode_examples(train_data).shuffle(10000).batch(batch_size)
# val dataset
ds_val_encoded = encode_examples(val_data).batch(batch_size)
# test dataset
ds_test_encoded = encode_examples(test_data).batch(batch_size)
创建模型
from transformers import TFBertPreTrainedModel,TFBertMainLayer
import tensorflow as tf
from transformers.modeling_tf_utils import (
TFQuestionAnsweringLoss,
TFTokenClassificationLoss,
get_initializer,
keras_serializable,
shape_list,
)
class TFBertForMultilabelClassification(TFBertPreTrainedModel):
def __init__(self, config, *inputs, **kwargs):
super(TFBertForMultilabelClassification, self).__init__(config, *inputs, **kwargs)
self.num_labels = config.num_labels
self.bert = TFBertMainLayer(config, name='bert')
self.dropout = tf.keras.layers.Dropout(config.hidden_dropout_prob)
self.classifier = tf.keras.layers.Dense(config.num_labels,
kernel_initializer=get_initializer(config.initializer_range),
name='classifier',
activation='sigmoid')#--------------------- sigmoid激活函数
def call(self, inputs, **kwargs):
outputs = self.bert(inputs, **kwargs)
pooled_output = outputs[1]
pooled_output = self.dropout(pooled_output, training=kwargs.get('training', False))
logits = self.classifier(pooled_output)
outputs = (logits,) + outputs[2:] # add hidden states and attention if they are here
return outputs # logits, (hidden_states), (attentions)
编译与训练模型
# model initialization
model = TFBertForMultilabelClassification.from_pretrained(model_path, num_labels=6)#------------6个标签
# optimizer Adam recommended
optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate,epsilon=1e-08, clipnorm=1)
# we do not have one-hot vectors, we can use sparce categorical cross entropy and accuracy
loss = tf.keras.losses.BinaryCrossentropy()#-----------------------------------binary_crossentropy 损失函数
metric = tf.keras.metrics.CategoricalAccuracy()
model.compile(optimizer=optimizer, loss=loss, metrics=[metric])
# fit model
bert_history = model.fit(ds_train_encoded, epochs=number_of_epochs, validation_data=ds_val_encoded)
计算每一个标签AUC
def measure_auc(label,pred):
auc = [roc_auc_score(label[:,i],pred[:,i]) for i in list(range(6))]
return pd.DataFrame({"label_name":["toxic","severe_toxic","obscene","threat","insult","identity_hate"],"auc":auc})
pred=model.predict(ds_val_encoded)[0]#------------------------------------------------predict dataset
df_auc = measure_auc(val_data.iloc[:,2:].astype(np.float32).values,pred)
print("val set mean column auc:",df_auc)
以下是2个epochs的训练结果:
Epoch 1/2
4488/4488 [==============================] - 3922s 874ms/step - loss: 0.0500 - categorical_accuracy: 0.9701 - val_loss: 0.0388 - val_categorical_accuracy: 0.9938
Epoch 2/2
4488/4488 [==============================] - 3927s 875ms/step - loss: 0.0333 - categorical_accuracy: 0.9796 - val_loss: 0.0408 - val_categorical_accuracy: 0.9918
val set mean column auc: label_name auc
0 toxic 0.986974
1 severe_toxic 0.991380
2 obscene 0.992404
3 threat 0.993322
4 insult 0.988814
5 identity_hate 0.992388
可以看到,训练集正确率99.38%,验证集正确率99.18%,还有下面每一个标签的auc值
0 | label_name | auc |
---|---|---|
1 | toxic | 0.987 |
2 | severe_toxic | 0.991 |
3 | obscene | 0.992 |
4 | threat | 0.993 |
5 | insult | 0.989 |
6 | identity_hate | 0.992 |
由于类别严重不平衡,auc值(ROC曲线)并不能完全衡量预测效果,可以用precision-recall curve进行评估,详细请参考Precision-Recall
数据
链接:https://pan.baidu.com/s/17BHBSXdtJOUBG402tmWWBw
提取码:kces
bert模型
https://huggingface.co/models : bert-base-uncased > List all files in model
代码
https://github.com/NZbryan/NLP_bert/blob/master/tf2.0_bert_emb_en_MultiLabel.py
linux: CentOS Linux release 7.6.1810
python: Python 3.6.10
packages:
tensorflow==2.3.0
transformers==3.02
pandas==1.1.0
scikit-learn==0.22.2
由于数据量较大,训练时间长,建议在GPU下运行,或者到colab去跑。
多标签分类注意事项:
1.不要使用softmax
2.使用sigmoid函数作为最后输出层
3.使用binary_crossentropy 作为损失函数
4.使用predict对测试集进行评估
参考:
https://towardsdatascience.com/building-a-multi-label-text-classifier-using-bert-and-tensorflow-f188e0ecdc5d