GPT Task
在模型 finetune 中,需要根据不同的下游任务来处理输入,主要的下游任务可分为以下四类:
- 分类(Classification):给定一个输入文本,将其分为若干类别中的一类,如情感分类、新闻分类等;
- 蕴含(Entailment):给定两个输入文本,判断它们之间是否存在蕴含关系(即一个文本是否可以从另一个文本中推断出来);
- 相似度(Similarity):给定两个输入文本,计算它们之间的相似度得分;
- 多项选择题(Multiple choice):给定一个问题和若干个答案选项,选择最佳的答案。
我们使用IMDb数据集,通过finetune GPT进行情感分类任务。
IMDb数据集是一个常用的情感分类数据集,其中包含50,000条影评文本,其中25,000条用作训练数据,另外25,000条用作测试数据。每个样本都有一个二元标签,表示影评的情感是正面还是负面。
import os
import mindspore
from mindspore.dataset import text, GeneratorDataset, transforms
from mindspore import nn
from mindnlp import load_dataset
from mindnlp.transforms import PadTransform, GPTTokenizer
from mindnlp.engine import Trainer, Evaluator
from mindnlp.engine.callbacks import CheckpointCallback, BestModelCallback
from mindnlp.metrics import Accuracy
数据预处理
# 加载数据集
imdb_train, imdb_test = load_dataset('imdb', shuffle=False)
通过load_dataset加载IMDb数据集后,我们需要对数据进行如下处理:
- 将文本内容进行分词,并映射为对应的数字索引;
- 统一序列长度:超过进行截断,不足通过占位符进行补全;
- 按照分类任务的输入要求,在句首和句末分别添加Start与Extract占位符(此处用 <bos>与 <eos>表示);
- 批处理。
import numpy as np
def process_dataset(dataset, tokenizer, max_seq_len=256, batch_size=32, shuffle=False):
"""数据集预处理"""
def pad_sample(text):
if len(text) + 2 >= max_seq_len:
return np.concatenate(
[np.array([tokenizer.bos_token_id]), text[: max_seq_len-2], np.array([tokenizer.eos_token_id])]
)
else:
pad_len = max_seq_len - len(text) - 2
return np.concatenate(
[np.array([tokenizer.bos_token_id]), text,
np.array([tokenizer.eos_token_id]),
np.array([tokenizer.pad_token_id] * pad_len)]
)
column_names = ["text", "label"]
rename_columns = ["input_ids", "label"]
if shuffle:
dataset = dataset.shuffle(batch_size)
dataset = dataset.map(operations=[tokenizer, pad_sample], input_columns="text")
dataset = dataset.rename(input_columns=column_names, output_columns=rename_columns)
dataset = dataset.batch(batch_size)
return dataset
加载 GPT tokenizer,并添加上述使用到的 <bos>, <eos>, <pad>占位符。
gpt_tokenizer = GPTTokenizer.from_pretrained('openai-gpt')
special_tokens_dict = {
"bos_token": "<bos>",
"eos_token": "<eos>",
"pad_token": "<pad>",
}
num_added_toks = gpt_tokenizer.add_special_tokens(special_tokens_dict)
由于IMDb数据集本身不包含验证集,我们手动将其分割为训练和验证两部分,比例取0.7, 0.3。
imdb_train, imdb_val = imdb_train.split([0.7, 0.3])
dataset_train = process_dataset(imdb_train, gpt_tokenizer, shuffle=True)
dataset_val = process_dataset(imdb_val, gpt_tokenizer)
dataset_test = process_dataset(imdb_test, gpt_tokenizer)
模型训练
同BERT课件中的情感分类任务实现,这里我们依旧使用了混合精度。另外需要注意的一点是,由于在前序数据处理中,我们添加了3个特殊占位符,所以在token embedding中需要调整词典的大小(vocab_size + 3)。
from mindnlp.models import GPTForSequenceClassification
from mindnlp._legacy.amp import auto_mixed_precision
model = GPTForSequenceClassification.from_pretrained('openai-gpt', num_labels=2)
model.pad_token_id = gpt_tokenizer.pad_token_id
model.resize_token_embeddings(model.config.vocab_size + 3)
model = auto_mixed_precision(model, 'O1')
loss = nn.CrossEntropyLoss()
optimizer = nn.Adam(model.trainable_params(), learning_rate=2e-5)
metric = Accuracy()
ckpoint_cb = CheckpointCallback(save_path='checkpoint', ckpt_name='sentiment_model', epochs=1, keep_checkpoint_max=2)
best_model_cb = BestModelCallback(save_path='checkpoint', auto_load=True)
trainer = Trainer(network=model, train_dataset=dataset_train,
eval_dataset=dataset_val, metrics=metric,
epochs=3, loss_fn=loss, optimizer=optimizer, callbacks=[ckpoint_cb, best_model_cb], jit=True)
trainer.run(tgt_columns="label")
模型评估
evaluator = Evaluator(network=model, eval_dataset=dataset_test, metrics=metric)
evaluator.run(tgt_columns="label")