本文为学习Datawhale 2021.8组队学习NLP入门之Transformer笔记
1 数据的读入
1.1 Transformer Datasets
使用Transformers Datasets库读取网络数据,可以用于在公开数据集上验证模型的好坏。
除了mnli-mm以外,其他任务都可以直接通过任务名字进行加载。数据加载之后会自动缓存。
from datasets import list_datasets, load_dataset, list_metrics, load_metric
actual_task = "mnli" if task == "mnli-mm" else task
dataset = load_dataset("glue", actual_task)
metric = load_metric('glue', actual_task)
注意容易出现网络问题,根据报错信息在hosts文件中设置github网址的端口,更新最新的datasets库版本,可以解决这个问题。
也可以下载好数据集后,手动放到cache里面,如 C:\Users\用户名.cache\huggingface\datasets\glue\cola\1.0.0\dacbe3125aa31d7f70367a07a8a9e72a5a0bfeb5fc42e75c9db75b96da6053ad
datasets对象本身是一种DatasetDict数据结构,对于训练集、验证集和测试集,只需要使用对应的key(train,validation,test)即可得到相应的数据。
print(dataset)
DatasetDict({
train: Dataset({
features: ['sentence', 'label', 'idx'],
num_rows: 8551
})
validation: Dataset({
features: ['sentence', 'label', 'idx'],
num_rows: 1043
})
test: Dataset({
features: ['sentence', 'label', 'idx'],
num_rows: 1063
})
})
就是一个嵌套字典。 dataset[‘train’][0][‘sentence’] 可以这样来调用训练集里第一个数据的sentence
1.1.1 datasets.Metric
可以输入metric查看其使用方法 举例:
import numpy as np
fake_preds = np.random.randint(0, 2, size=(64,))
fake_labels = np.random.randint(0, 2, size=(64,))
metric.compute(predictions=fake_preds, references=fake_labels)
注意不同的文本分类方法对应的metic有所不同,datasets.Metric会根据读取的数据集不同选择不同的指标计算。
2 数据预处理
在数据预处理上,Bert模型与其他的模型不同。使用的是WordPiece,所以在上一步读入数据后,直接使用Bert模型的Tokenizer,注意这里其实有很多类型的Tokenizer,要和后面使用的预训练模型写一样的。
2.1 定义一个tokenizer
tokenizer = BertTokenizer.from_pretrained(model_checkpoint)
除了BertTokenizer还有个它的更高级封装Autotokenizer,有个use_fast的参数可以多线程快速tokenizer。
2.2 Datasets
2.2.1 检查数据格式(可选)
因为对于分类任务,有一个输入的有两个输入的
Cola的任务就是鉴别一个句子的语法是否正确
task_to_keys = {
"cola": ("sentence", None),
"mnli": ("premise", "hypothesis"),
"mnli-mm": ("premise", "hypothesis"),
"mrpc": ("sentence1", "sentence2"),
"qnli": ("question", "sentence"),
"qqp": ("question1", "question2"),
"rte": ("sentence1", "sentence2"),
"sst2": ("sentence", None),
"stsb": ("sentence1", "sentence2"),
"wnli": ("sentence1", "sentence2"),
}
sentence1_key, sentence2_key = task_to_keys[task]
if sentence2_key is None:
print(f"Sentence: {dataset['train'][0][sentence1_key]}")
else:
print(f"Sentence 1: {dataset['train'][0][sentence1_key]}")
print(f"Sentence 2: {dataset['train'][0][sentence2_key]}")
2.2.2 Datasets预处理
定义一个预处理的函数
def preprocess_function(examples):
if sentence2_key is None:
return tokenizer(examples[sentence1_key], truncation=True)
return tokenizer(examples[sentence1_key], examples[sentence2_key], truncation=True)
批量处理多个样本
preprocess_function(dataset['train'][:5])
就会返回一个list
处理所有样本,处理的方式是使用map函数,将预处理函数prepare_train_features应用到(map)所有样本上。
encoded_dataset = dataset.map(preprocess_function, batched=True)
3 Trainer微调预训练模型
from transformers import AutoModelForSequenceClassification, TrainingArguments, Trainer
num_labels = 3 if task.startswith("mnli") else 1 if task=="stsb" else 2
model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=num_labels,mirror='tuna')
ModelForSequenceClassification需要传入num_labels参数
为了能够得到一个Trainer训练工具,我们还需要3个要素,其中最重要的是训练的设定/参数 TrainingArguments。这个训练设定包含了能够定义训练过程的所有属性。
不同的任务有不同的评价指标
metric_name = "pearson" if task == "stsb" else "matthews_correlation" if task == "cola" else "accuracy"
args = TrainingArguments(
"test-glue",
evaluation_strategy = "epoch",
save_strategy = "epoch",
learning_rate=2e-5,
per_device_train_batch_size=batch_size,
per_device_eval_batch_size=batch_size,
num_train_epochs=5,
weight_decay=0.01,
load_best_model_at_end=True,
metric_for_best_model=metric_name,
log_level='error',
logging_strategy="no",
report_to="none"
)
上面evaluation_strategy = "epoch"参数告诉训练代码:我们每个epcoh会做一次验证评估。
def compute_metrics(eval_pred):
predictions, labels = eval_pred
if task != "stsb":
predictions = np.argmax(predictions, axis=1)
else:
predictions = predictions[:, 0]
return metric.compute(predictions=predictions, references=labels)
全部传给Trainer
validation_key = "validation_mismatched" if task == "mnli-mm" else "validation_matched" if task == "mnli" else "validation"
trainer = Trainer(
model,
args,
train_dataset=encoded_dataset["train"],
eval_dataset=encoded_dataset[validation_key],
tokenizer=tokenizer,
compute_metrics=compute_metrics
)
训练和评估
import numpy as np
trainer.train()
trainer.evaluate()
结果:
{'eval_loss': 0.9473464488983154, 'eval_matthews_correlation': 0.5538778174700708, 'eval_runtime': 0.7201, 'eval_samples_per_second': 1448.465, 'eval_steps_per_second': 91.657, 'epoch': 5.0}
4 超参数搜索
超参搜索时,Trainer将会返回多个训练好的模型,所以需要传入一个定义好的模型从而让Trainer可以不断重新初始化该传入的模型:
def model_init():
return AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=num_labels,mirror='tuna')
和之前调用 Trainer类似:
trainer = Trainer(
model_init=model_init,
args=args,
train_dataset=encoded_dataset["train"],
eval_dataset=encoded_dataset[validation_key],
tokenizer=tokenizer,
compute_metrics=compute_metrics,
)
调用方法hyperparameter_search。注意,这个过程可能很久,我们可以先用部分数据集进行超参搜索,再进行全量训练。 比如使用1/10的数据进行搜索:
best_run = trainer.hyperparameter_search(n_trials=10, direction="maximize")
hyperparameter_search会返回效果最好的模型相关的参数:
best_run
BestRun(run_id='7', objective=0.5343023846000738, hyperparameters={'learning_rate': 2.605784236781612e-05, 'num_train_epochs': 5, 'seed': 39, 'per_device_train_batch_size': 32})
将Trainner设置为搜索到的最好参数,进行训练:
for n, v in best_run.hyperparameters.items():
setattr(trainer.args, n, v)
trainer.train()
结果
TrainOutput(global_step=1340, training_loss=0.27208363547253966, metrics={'train_runtime': 103.5852, 'train_samples_per_second': 412.752, 'train_steps_per_second': 12.936, 'train_loss': 0.27208363547253966, 'epoch': 5.0})
评估
trainer.evaluate()
{'eval_loss': 0.7151344418525696, 'eval_matthews_correlation': 0.5343023846000738, 'eval_runtime': 0.7261, 'eval_samples_per_second': 1436.443, 'eval_steps_per_second': 90.897, 'epoch': 5.0}
为什么结果还没有上面设置的好。。。
|