MindSpore学习实践——基于MindNLP的Roberta Prompt Turning

前言

之前我们学过,GPT主要是预测下一个单词,也就是类似文字接龙的方式,而BERT模型只要是类似于完形填空的运作方式。这两个模型都是通过模型微调去完成特定的下游任务,不过当前预训练+微调还是存在一些挑战,比如说数据稀缺;参数量大带来的训练成本和存储空间要求高;少样本学习能力差、容易过拟合等问题。在模型体量越来越大的时候,模型的微调不再是一个高性价比的方案。所以带来了Prompt方法。

Prompt——提示词

为了让模型调用其训练中获得的特定知识,我们可以使用提示词将指示模型按照我们所想的方法去推理。最简单的方法就是完形填空,比如将若需要的答案挖空让模型去生成答案。

请添加图片描述

  1. 情景训练,可以给模型一个情景,再给出一些例子,最后再让模型基于上面的例子去完成任务,根据例子的数量可以分为Zero-shot、one-shot、few-shot等。这样我们对模型并没有发生什么改变但还是完成了既定目标,可以节约训练成本。

请添加图片描述

  1. 微调和提示词学习有什么区别?
    • 微调:以模型为出发点,通过改变模型结构,牺牲模型的部分参数,使模型适配下游任务。
    • 提示词学习:以不同下游任务为出发点,模型结构不变,通过重构任务描述,使下游任务适配模型。

请添加图片描述

  1. 在提示学习中,最重要的就是编写或获得一个好的提示,在这个过程中发展出了两个概念:硬提示(hard prompt)、软提示(soft prompt)。
    • 硬提示(hard prompt)是由具体的中文或英文词汇组成的文本提示,包含离散的输入标记。它们通常由人类专门为解决特定任务而精心设计。其优点在于人类可读,可解释,灵活性强。但是也有缺点:它需要大量人工精力来设计出好的提示,对于复杂任务来说非常困难。
    • 软提示(soft prompt)通过hard prompt方式初始化(即用人工可阅读的单词序列初始化),然后通过向量空间连续优化得到新的提示。其优点使无需人工设计,可自动优化以适应不同任务,计算效率高而且支持多任务学习。但是缺点也非常显而易见:软提示不可读,我们无法解释为何模型选择这些向量。
  2. Prompt Template(提示词模板):提示词模板的设计要根据任务以及预训练模型来决定
    • 完形填空:适用于使用掩码语言模型的下游任务。
    • 前缀提示:适用于文本生成任务。

代码实训

  1. 环境安装
#安装mindnlp 0.4.0套件
!pip install mindnlp==0.4.0
!pip uninstall soundfile -y
!pip install https://ms-release.obs.cn-north-4.myhuaweicloud.com/2.3.1/MindSpore/unified/aarch64/mindspore-2.3.1-cp39-cp39-linux_aarch64.whl --trusted-host ms-release.obs.cn-north-4.myhuaweicloud.com -i https://pypi.tuna.tsinghua.edu.cn/simple
%env HF_ENDPOINT=https://hf-mirror.com
  1. 模型与数据集加载
    • 本次实训将对roberta-large模型基于GLUE基准数据集进行prompt tuning
import argparse
import os
import mindspore
from mindnlp.core.optim import AdamW
from tqdm import tqdm
import evaluate
from mindnlp.dataset import load_dataset
from mindnlp.engine import set_seed
from mindnlp.transformers import AutoModelForSequenceClassification, AutoTokenizer
from mindnlp.transformers.optimization import get_linear_schedule_with_warmup
from mindnlp.peft import (
    get_peft_config,
    get_peft_model,
    get_peft_model_state_dict,
    set_peft_model_state_dict,
    PeftType,
    PromptTuningConfig,
)
from mindnlp.dataset import BaseMapFunction


batch_size = 32
model_name_or_path = "AI-ModelScope/roberta-large"
task = "mrpc"
peft_type = PeftType.PROMPT_TUNING
# num_epochs = 20
num_epochs = 5

# 我们对prompt tuning配置,任务类型选为"SEQ_CLS", 即序列分类。
# peft config
peft_config = PromptTuningConfig(task_type="SEQ_CLS", num_virtual_tokens=10)
# learning rate
lr = 1e-3

# 加载tokenizer。如模型为GPT、OPT或BLOOM类模型,从序列左侧添加padding,其他情况下从序列右侧添加padding。
# load tokenizer
if any(k in model_name_or_path for k in ("gpt", "opt", "bloom")):
    padding_side = "left"
else:
    padding_side = "right"

tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, padding_side=padding_side, mirror="modelscope")
if getattr(tokenizer, "pad_token_id") is None:
    tokenizer.pad_token_id = tokenizer.eos_token_id
    
datasets = load_dataset("glue", task)
print(next(datasets['train'].create_dict_iterator()))

class MapFunc(BaseMapFunction):
    def __call__(self, sentence1, sentence2, label, idx):
        outputs = tokenizer(sentence1, sentence2, truncation=True, max_length=None)
        return outputs['input_ids'], outputs['attention_mask'], label

def get_dataset(dataset, tokenizer):
    input_colums=['sentence1', 'sentence2', 'label', 'idx']
    output_columns=['input_ids', 'attention_mask', 'labels']
    dataset = dataset.map(MapFunc(input_colums, output_columns),
                          input_colums, output_columns)
    dataset = dataset.padded_batch(batch_size, pad_info={'input_ids': (None, tokenizer.pad_token_id),
                                                         'attention_mask': (None, 0)})
    return dataset

train_dataset = get_dataset(datasets['train'], tokenizer)
eval_dataset = get_dataset(datasets['validation'], tokenizer)

print(next(train_dataset.create_dict_iterator()))

metric = evaluate.load("glue", task)

# 我们使用从model_name_or_path加载了预训练模型,并使用modelscope作为镜像源。
# load model
model = AutoModelForSequenceClassification.from_pretrained(model_name_or_path, return_dict=True, mirror="modelscope")
model = get_peft_model(model, peft_config)
# print number of trainable parameters
model.print_trainable_parameters()
  1. 模型微调
# 我们指定优化器和学习率调整策略
optimizer = AdamW(params=model.trainable_params(), lr=lr)

# Instantiate scheduler
lr_scheduler = get_linear_schedule_with_warmup(
    optimizer=optimizer,
    num_warmup_steps=0.06 * (len(train_dataset) * num_epochs),
    num_training_steps=(len(train_dataset) * num_epochs),
)

# 打印参与微调的模型参数
# print name of trainable parameters
model.trainable_params()

参与微调的模型参数

请添加图片描述

"""
训练逻辑:
1. 构建正向计算函数
2. 函数变换,获取微分函数
3. 定义训练一个step的逻辑
4. 遍历训练数据集进行模型训练,同时每一个epoch后,遍历验证数据集获取当前的评价指标(accuracy、f1 score)
"""

# define forward function
def forward_fn(**batch):
    outputs = model(**batch)
    loss = outputs.loss
    return loss

# Get gradient function
grad_fn = mindspore.value_and_grad(forward_fn, None, model.trainable_params())

# Define function of one-step training
def train_step(**batch):
    loss, grads = grad_fn(**batch)
    optimizer.step(grads)
    return loss

# Start training
for epoch in range(num_epochs):
    model.set_train()
    train_total_size = train_dataset.get_dataset_size()
    # Iterate through the dataset
    for step, batch in enumerate(tqdm(train_dataset.create_dict_iterator(), total=train_total_size)):
        loss = train_step(**batch)
        lr_scheduler.step()

    # Evaluate while training
    model.set_train(False)
    eval_total_size = eval_dataset.get_dataset_size()
    for step, batch in enumerate(tqdm(eval_dataset.create_dict_iterator(), total=eval_total_size)):
        outputs = model(**batch)
        predictions = outputs.logits.argmax(axis=-1)
        predictions, references = predictions, batch["labels"]
        metric.add_batch(
            predictions=predictions,
            references=references,
        )
    
    # Calculate accuracy and f1 score
    eval_metric = metric.compute()
    print(f"epoch {epoch}:", eval_metric)
  1. 实训结果

请添加图片描述

总结

本节课学习了提示词,虽然本人之前在学习使用LangChain调用GPT-3.5-turbo时也简单使用过提示词,不过对其定义分类以及意义的了解不算深刻。本次学习后深化了本人对提示词的了解,尤其是提示词工程在某些情况下能大大减轻训练资源的消耗,这项优点足以让提示词工程有重大意义。不过提示词工程有时也会使模型越狱,带来不良影响,这是个值得深究的问题。

Logo

昇腾计算产业是基于昇腾系列(HUAWEI Ascend)处理器和基础软件构建的全栈 AI计算基础设施、行业应用及服务,https://devpress.csdn.net/organization/setting/general/146749包括昇腾系列处理器、系列硬件、CANN、AI计算框架、应用使能、开发工具链、管理运维工具、行业应用及服务等全产业链

更多推荐