共计 3719 个字符,预计需要花费 10 分钟才能阅读完成。
最近尝试利用 HuggingFace🤗的 transformers 库在 pytorch 下进行 Bert 文本分类的微调,找了很多中文 blog,次要是对数据的解决这块没有比拟具体的阐明,不晓得怎么解决 dataset 的格局,因而在这里做一下记录。
依赖包
pytorch
transformers
scikit-learn
预训练模型加载
预训练模型加载这块 HuggingFace 在 transformers 库中封装得十分好,没什么太多要讲的:
args.pretrain 这里填写模型名称或者你本人筹备好的预训练模型,各种预训练模型能够从 https://huggingface.co/models 查找和下载,须要蕴含三个文件(config.json、vocab.txt、pytorch_model.bin)。此处以 Bert 为例,筹备 bert-base-chinese 模型,args.pretrain 为寄存模型三个文件的门路。
因为上游工作是文本分类工作,因而 model 应用 transformer.BertForSequenceClassification,也能够依据须要抉择其余模型。
from from transformers import BertForSequenceClassification, BertTokenizerFast
tokenizer = BertTokenizerFast.from_pretrained(args.pretrain)
model = BertForSequenceClassification.from_pretrained(args.pretrain, num_labels=2, output_hidden_states=False)
模型微调
模型微调这里应用 transformers 封装好的 Trainer 模块,参数含意基本上都比拟高深莫测,这里设置了早停、依据 precision 加载最佳模型。值得注意的是在模型保留时会保留多个 checkpoint,因而 evaluation_strategy、save_total_limit 要设置一下免得保留过程中爆硬盘,Bert 一个 checkpoint 保留下来差不多要 1GB……
from transformers import Trainer, TrainingArguments, EarlyStoppingCallback
from sklearn.metrics import classification_report, precision_score, \
recall_score, f1_score, accuracy_score, precision_recall_fscore_support
def compute_metrics(pred):
labels = pred.label_ids
preds = pred.predictions.argmax(-1)
precision, recall, f1, _ = precision_recall_fscore_support(labels, preds, average='binary')
acc = accuracy_score(labels, preds)
return {
'accuracy': acc,
'f1': f1,
'precision': precision,
'recall': recall
}
training_args = TrainingArguments(
output_dir=args.save_path, # 存储后果文件的目录
overwrite_output_dir=True,
num_train_epochs=args.epoch,
per_device_train_batch_size=args.batch_size,
per_device_eval_batch_size=args.batch_size,
learning_rate=1e-5,
eval_steps=500,
load_best_model_at_end=True,
metric_for_best_model="precision", # 最初载入最优模型的评判规范,这里选用 precision 最高的那个模型参数
weight_decay=0.01,
warmup_steps=500,
evaluation_strategy="steps", # 这里设置每 100 个 batch 做一次评估,也能够为“epoch”,也就是每个 epoch 进行一次
logging_strategy="steps",
save_strategy='steps',
logging_steps=100,
save_total_limit=3,
seed=2021,
logging_dir=args.logging_dir # 存储 logs 的目录
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_set,
eval_dataset=valid_set,
tokenizer=tokenizer,
compute_metrics=compute_metrics,
callbacks=[EarlyStoppingCallback(early_stopping_patience=3)], # 早停 Callback
)
数据预处理
终于要说一下数据预处理阶段了,为什么最先进行的操作要放在最初讲呢?因为读者敌人可能也发现了,transformers🤗封装得十分好,以至于整个 pipeline 中须要进行自定义的就是数据预处理这块。其实也非常简单,你须要在创立 Trainer
的时候传入 train_dataset
和 eval_dataset
,这两个数据集的类型都是 torch.utils.data.Dataset
,PyTorch 的 Dataset 解决详见另一篇文章。 那么这里须要对 __getitem__
办法进行一些批改,使其返回一个 dict,外面有蕴含 Bert 输出所需的元素。
Talk is cheap,this is the code:
from torch.utils.data import Dataset
class MyDataset(Dataset):
def __init__(self, file, tokenizer, max_len=512):
assert os.path.exists(file)
data = open(file, 'r', encoding='utf-8').read().strip().split('\n')
texts = [x.split('\t')[0][:max_len-2] for x in data]
labels = [int(x.split('\t')[1]) for x in data]
self.encodings = tokenizer(texts, padding=True, truncation=True, return_tensors='pt')
self.labels = torch.tensor(labels)
def __getitem__(self, idx):
item = {key: val[idx] for key, val in self.encodings.items()}
item['labels'] = self.labels[idx]
return item
def __len__(self):
return len(self.labels)
假如数据格式为“text\tlabel”,即文本和标签两头用制表符 \t 隔开,每行一条数据,形如:
我的幻想是星辰大海 1
雄心万丈躺在床上 0
在 __init__
初始化办法中读入数据,之后截断至最大设置长度 max_len
(因为tokenizer
会主动补上 [CLS]
和[SEP]
,因而这里须要对最大长度做 - 2 解决。对字符串宰割出文本与标签后,应用 tokenizer(texts, padding=True, truncation=True, return_tensors='pt')
失去 Bert 所须要的输出 encoding(transformers.BatchEncoding
对象,encoding.data
蕴含了 'input_ids'
,'token_type_ids'
,'attention_mask'
三个张量对象,通常状况下在微调工作中不须要进行额定解决),之后将标签转为张量对象就根本实现了数据集初始化流程。
比拟要害的是 __getitem__
办法的解决 ,这里须要返回一个dict
对象,外面须要蕴含 input_ids, token_tpye_ids, attention_mask, labels
四个 key(以 Bert 为例,其余模型可能会有稍许不同,留神这里尽管是单条数据,然而标签的 key 称为“labels”),而后返回该 dict
对象即可(即代码示例中的item
。
开始训练!
最初一步,开始训练!静静期待模型训练完即可。
trainer.train()
代码示例
还没有 push 到 GitHub 下来,后续改。
Reference
Fine-tuning pretrained NLP models with Huggingface’s Trainer