多标签文本分类是指一个输入文本样本对应有多种标签。本文是一个训练多标签文本分类任务的实例:
训练过程
main.py
导入相关的库:
# coding=utf-8
import os
# os.environ["CUDA_VISIBLE_DEVICES"] = "2"
os.environ["WANDB_DISABLED"] = "true" # 禁用wandb
'''
from huggingface_hub import snapshot_download
# 将预训练模型下载到指定目录(cache_dir参数指定保存目录)
snapshot_download(repo_id='bert-base-chinese', cache_dir='.')
'''
import json
import torch
from sklearn.preprocessing import MultiLabelBinarizer
from transformers import AutoModelForSequenceClassification, AutoTokenizer, DataCollatorWithPadding, TrainingArguments
from custom_trainer import CustomTrainer
from datasets import load_dataset
tokenizer及数据加载:
tokenizer = AutoTokenizer.from_pretrained('models--bert-base-chinese')
data = load_dataset("json", data_files='preprocessed_data/labeled_data.json')
利用MultiLabelBinarizer处理标签,利用MultiLabelBinarizer处理标签的好处是:在后续利用MultiLabelBinarizer相关方法向模型传入label时,模型会自动识别为多标签模型(在模型的全连接层使用sigmoid函数而不是softmax函数)
label_encoder = MultiLabelBinarizer()
label_encoder = label_encoder.fit(data['train']['labels'])
保存一个标签ID到标签名的映射文件,便于后续得到测试文本对应的标签名
save_batch = 64 # batch size
epochs = 100 # epoch num
if not os.path.exists(f"model_save_epochs{epochs}_batch{save_batch}"):
os.mkdir(f"model_save_epochs{epochs}_batch{save_batch}")
with open(f"model_save_epochs{epochs}_batch{save_batch}/labelmap.json", 'w', encoding='utf-8') as fw:
json.dump({'LABEL_' + str(ind): label_name for ind, label_name in enumerate(label_encoder.classes_)}, fw,
ensure_ascii=False, indent=1)
将加载到的数据集分割成训练集和验证集
data = data['train'].train_test_split(test_size=0.1, shuffle=True)
构造模型的输入输出数据
def preprocess_function(examples):
model_inputs = tokenizer(examples['text'], max_length=300, truncation=True)
labels = [labels for labels in examples['labels']]
model_inputs['labels'] = label_encoder.transform(labels).astype(float) # 将标签名转换成标签id
return model_inputs
data = data.map(preprocess_function, batched=True, batch_size=100)
加载预训练模型
model = AutoModelForSequenceClassification.from_pretrained('models--bert-base-chinese',
num_labels=label_encoder.classes_.size, # 标签类别数
# problem_type 指明模型训练类型为多标签文本分类,但在实际测试时发现:
# 即使不设置此参数,模型也仍使用的是多标签文本分类方式训练
# 可能是因为前面用MultiLabelBinarizer处理标签后,模型识别出要训练多标签文本分类
problem_type='multi_label_classification'
)
使用huggingface/Datasets方式加载数据时,可以用DataCollator达到批处理的效果。这里可能会有读者好奇,数据并没有做填充处理,后面如何以batch输入模型进行训练。事实上,在tokenizer时做填充处理是没有问题的。但是如果在这个阶段中做填充,会将所有数据的长度都填充到300,如果训练时一个batch中的数据中都是短文本,那数据中将有大量的填充值,影响计算效率。该如何解决这一问题呢?
我们可以让其在取出一个batch的数据之后再根据batch内数据的最大长度进行填充,例如用Dataloader加载数据,我们可以指定collate_fn,但是这个函数需要我们自行实现。在transformers中,我们则可以使用DataCollatorWithPadding,实例化该类并在Trainer中指定即可。
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
获取模型训练参数:
args = TrainingArguments(
learning_rate=2e-5,
per_device_train_batch_size=save_batch,
per_device_eval_batch_size=128,
num_train_epochs=epochs,
weight_decay=0.01,
output_dir=f"model_save_epochs{epochs}_batch{save_batch}",
logging_steps=10,
evaluation_strategy="epoch",
save_strategy="epoch",
save_total_limit=3,
load_best_model_at_end=True,
metric_for_best_model="eval_loss",
fp16=True,
disable_tqdm=False,
)
自定义验证
def compute_metrics(eval_pred):
predictions, labels = eval_pred
# BCEWithLogitsLoss 用于计算多标签损失,后文将具体介绍
loss = torch.nn.BCEWithLogitsLoss()(torch.Tensor(predictions), torch.tensor(labels))
return {'eval_loss': loss.cpu().numpy()}
进行训练:
# CustomTrainer中重写了compute_loss()函数,目的是检验模型训练过程中的损失计算结果是否与BCEWithLogitsLoss计算结果一致
# 经测试,结果是一致的,大家可以直接使用Trainer进行训练即可
trainer = CustomTrainer(
model,
args=args,
train_dataset=data["train"],
eval_dataset=data["test"],
tokenizer=tokenizer,
compute_metrics=compute_metrics,
data_collator=DataCollatorWithPadding(tokenizer=tokenizer)
)
trainer.train()
custom_trainer.py
from transformers import Trainer
import torch
class CustomTrainer(Trainer):
def compute_loss(self, model, inputs, return_outputs=False):
"""
How the loss is computed by Trainer. By default, all models return the loss in the first element.
Subclass and override for custom behavior.
"""
labels = inputs.get('labels')
outputs = model(**inputs)
logits = outputs.get('logits')
loss = torch.nn.BCEWithLogitsLoss()(logits, labels)
return (loss, outputs) if return_outputs else loss
Pytorch之BCELoss和BCEWithLogitsLoss
BCELoss是
$$
-\frac{1}{n}\sum (y_{n} \times ln X_{n} + (1-y_{n})\times ln(1-X_{n}))
$$
其中y是标签(target),x是模型输出的值(logits)。
所以对于给定
x = [[0.3992 , 0.2232, 0.6435 ],
[0.3800 , 0.3044, 0.3241],
[0.6281, 0.4689, 0.3834]]
y = [[0, 1, 1],
[0, 0, 1],
[1, 0, 1]]
所以其计算过程
第一行:
第一列 0 × ln 0.3992 + ( 1 − 0 ) × ln ( 1 − 0.3992 ) = − 0.5095
第二列 1 × ln 0.2232 + ( 1 − 1 ) × ln ( 1 − 0.2232 ) = − 1.4997
第三列 1 × ln 0.6435 + ( 1 − 1 ) × ln ( 1 − 0.6435 ) = − 0.4408
第二行:
第一列 0 × ln 0.3800 + ( 1 − 0 ) × ln ( 1 − 0.3800 ) = − 0.4780
第二列 0 × ln 0.3044 + ( 1 − 0 ) × ln ( 1 − 0.3044 ) = − 0.3630
第三列 1 × ln 0.3241 + ( 1 − 1 ) × ln ( 1 − 0.3241 ) = − 1.1267
第三行:
第一列 1 × ln 0.6281 + ( 1 − 1 ) × ln ( 1 − 0.6281 ) = − 0.4651
第二列 0 × ln 0.4689 + ( 1 − 0 ) × ln ( 1 − 0.4689 ) = − 0.6328
第三列 1 × ln 0.3834 + ( 1 − 1 ) × ln ( 1 − 0.3834 ) = − 0.9587
$$
-\frac{-0.5095 - 1.4997 - 0.4408}{3} = 0.8167
$$
$$
-\frac{-0.4780 - 0.3630 - 1.1267 }{3} = 0.6559
$$
$$
-\frac{-0.4651 - 0.6328 - 0.9587}{3} = 0.6855
$$
故而BCE损失为:
$$
BCELoss \approx \frac{-0.8167 - 0.6559- 0.9587}{3} = 0.7194
$$
利用BCELoss代码实现如下:
from torch import nn
import torch
m = nn.Sigmoid()
loss = nn.BCELoss()
logits = torch.Tensor([[-0.4089, -1.2471, 0.5907],
[-0.4897, -0.8267, -0.7349],
[0.5241, -0.1246, -0.4751]])
target = torch.Tensor([[0, 1, 1],
[0, 0, 1],
[1, 0, 1]])
pre = m(logits)
output = loss(pre, target)
print(output)
# 输出为:0.7193
利用BCEWithLogitsLoss实现:
print(nn.BCEWithLogitsLoss()(logits, target))
# 输出为:0.7193
BCEWithLogitsLoss() 中包含了sigmoid过程
BCELoss参考链接:https://blog.csdn.net/qq_22210253/article/details/85222093
标签:labels,ln,标签,分类,batch,model,文本,data From: https://www.cnblogs.com/teanon/p/16803324.html