参数高效微调之Prompt Tuning

简介

Prompt Tuning 微调详解

Prompt Tuning 是一种高效微调技术,旨在通过设计和优化输入提示(prompt)来指导预训练模型完成特定任务,而无需修改模型本身的权重。它是一种参数高效微调(Parameter-Efficient Fine-Tuning, PEFT)方法,尤其适用于大规模预训练模型(如 GPT、BERT、T5 等)的下游任务。

Prompt Tuning 的核心思想

Prompt Tuning在是执行微调的核心思想是一下两种

  • 冻结预训练模型的参数:保持模型本体的所有参数不变。
  • 仅优化输入提示:通过优化特殊的“提示向量”来调整模型对下游任务的表现。
    在这里插入图片描述
什么是Prompt 的定义

Prompt 是模型输入中的额外信息,用于引导模型执行特定任务。根据任务类型,Prompt 可以分为:

手工设计的文本 Prompt:人工撰写的提示语,例如:
  • 对情感分析任务输入 “The movie was __”,模型补全“good”或“bad”。
  • 对翻译任务输入 “Translate to French: Hello”,模型输出“Bonjour”。
可训练的 Prompt:通过训练优化的向量或参数,而非固定的文本。

Prompt Tuning 不直接使用手工文本提示,而是引入 可训练的提示向量。这些向量通常通过以下方式添加到输入中:

1,嵌入空间中的提示向量:
  • 在输入文本的词嵌入(embedding)空间中插入若干特殊向量,称为 Prompt 向量。
    例如,对输入文本 x = [x₁, x₂, …, xₙ],其嵌入序列可能变为:
    x = [p₁,p₂,…pₙ,x₁, x₂, …, xₙ]
    其中p₁,p₂,…pₙ 是可训练的 Prompt 向量。
2,结合特殊标记(tokens):

模型通过增加一些特定标记(如 [PROMPT]),将任务相关信息编码为输入的一部分。

Prompt Tuning 的工作机制
1 冻结预训练模型

Prompt Tuning 假设预训练模型的参数已经包含丰富的知识,因此无需修改模型的权重。预训练模型的所有参数(包括嵌入层、Transformer 层等)均被冻结。

2 优化 Prompt 向量

Prompt Tuning 的训练过程仅更新 Prompt 向量。这些向量作为额外的任务特定参数,与冻结的预训练模型协作完成下游任务。

3 任务适配

Prompt 向量通过训练学习任务相关的特定模式或特征,从而引导模型利用已有的知识来完成新任务。

Prompt Tuning 的优势

参数效率高:只需优化少量的 Prompt 参数,相比全量微调,训练成本大幅降低。
适应性强:可以快速切换任务,无需重新微调整个模型。
模块化设计:Prompt 向量可以视为任务特定模块,与模型本体分离。
易于扩展:支持多任务学习,每个任务只需一组独立的 Prompt。

hard prompt和soft prompt

上部说的Prompt 的定义其实指的两个概念就是hard prompt和soft prompt,是Prompt的两种形式。

Hard Prompt
什么是 Hard Prompt?

Hard Prompt 是指通过显式的自然语言描述作为提示,直接构造输入文本来引导预训练模型完成特定任务。这种提示通常是由人类设计的,使用自然语言表达

  • 示例 1:情感分析任务 输入文本:“The movie was fantastic. Is this sentence positive or negative?” 任务目标:模型输出 “positive”。

  • 示例 2:翻译任务 输入文本:“Translate to French: Hello” 任务目标:模型输出 “Bonjour”。

Hard Prompt 的特性

人类可读性:Hard Prompt 是由自然语言构成的,直观易懂。
任务无关性:可以通过不同的手工编写提示适配多个任务,而无需修改模型结构。
依赖提示设计:性能高度依赖于提示设计的质量。

Hard Prompt 的优缺点
优点缺点
容易实现,直接编写提示即可需要人工经验和大量实验来设计高效的提示
无需修改模型参数,适合快速原型开发对提示的微小变化可能导致显著的性能波动
适用于 Few-shot Learning 等低资源场景复杂任务中可能难以用简单提示覆盖任
Soft Prompt
1 什么是 Soft Prompt?

Soft Prompt 是通过训练优化的连续向量,而不是显式的自然语言提示。它是在输入的嵌入层直接插入若干特殊的向量来引导模型任务,而这些向量本质上是可以优化的参数。

  • 示例 1:情感分析任务 输入嵌入:[p₁,p₂,…pₙ,x₁, x₂, …, xₙ] 其中p₁,p₂,…pₙ是可训练的 Prompt 向量。

  • 示例 2:翻译任务 输入嵌入:[Prompt 向量 p₁, 原始文本嵌入 𝑥]

2 Soft Prompt 的实现

Soft Prompt 不需要自然语言表达,而是通过以下步骤实现:

Prompt 参数化:

  • 用一组可训练的向量(称为 Prompt 向量)表示提示信息。
  • 这些向量的维度与模型的嵌入向量维度相同。

输入扩展:

  • 在原始输入的嵌入序列前后或中间,插入 Prompt 向量。

冻结模型参数:

  • 模型的其余参数保持冻结,只优化 Prompt 向量。
3 Soft Prompt 的特性

参数化提示:通过训练获得的提示,不再依赖人工设计。
不可读性:Soft Prompt 在嵌入空间中表现为连续向量,无法被人类直接解读。
任务定制化:每个任务有独特的 Prompt 向量,针对性更强。

4 Soft Prompt 的优缺点
优点缺点
无需人工设计:Prompt 向量通过训练学习,避免人工设计麻烦难以解释:Soft Prompt 是向量,缺乏人类可读性
适合复杂任务:能捕获任务相关的隐含特征,适用于复杂任务对模型依赖强:需要冻结预训练模型的参数,依赖模型已有能力
高效微调:只需训练少量参数,计算成本低迁移性差:针对特定任务训练的 Soft Prompt 不能直接迁移到其他任务

代码实现

上部理论讲了这么多,那么下面就用代码实现。
这块代码实现其实很简单,主要是使用PEFT来是这一功能

from datasets import Dataset
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq
from transformers import pipeline, TrainingArguments, Trainer
from peft import PromptTuningConfig, get_peft_model, TaskType, PromptTuningInit
from peft import PeftModel

# 分词器
tokenizer = AutoTokenizer.from_pretrained("langboat_bloom-1b4-zh")


# 函数内将instruction和response拆开分词的原因是:
# 为了便于mask掉不需要计算损失的labels, 即代码labels = [-100] * len(instruction["input_ids"]) + response["input_ids"]
def process_func(example):
    MAX_LENGTH = 256
    input_ids, attention_mask, labels = [], [], []
    instruction = tokenizer(
        "\n".join(["Human: " + example["instruction"], example["input"]]).strip() + "\n\nAssistant: ")
    response = tokenizer(example["output"] + tokenizer.eos_token)
    input_ids = instruction["input_ids"] + response["input_ids"]
    attention_mask = instruction["attention_mask"] + response["attention_mask"]
    labels = [-100] * len(instruction["input_ids"]) + response["input_ids"]
    if len(input_ids) > MAX_LENGTH:
        input_ids = input_ids[:MAX_LENGTH]
        attention_mask = attention_mask[:MAX_LENGTH]
        labels = labels[:MAX_LENGTH]
    return {
        "input_ids": input_ids,
        "attention_mask": attention_mask,
        "labels": labels
    }


if __name__ == "__main__":
    # 加载数据集
    dataset = Dataset.load_from_disk("./alpaca_data_zh/")

    # 处理数据
    tokenized_ds = dataset.map(process_func, remove_columns=dataset.column_names)
    # print(tokenizer.decode(tokenized_ds[1]["input_ids"]))
    # print(tokenizer.decode(list(filter(lambda x: x != -100, tokenized_ds[1]["labels"]))))

    # 创建模型
    model = AutoModelForCausalLM.from_pretrained("langboat_bloom-1b4-zh", low_cpu_mem_usage=True)

    # Soft Prompt
    # config = PromptTuningConfig(task_type=TaskType.CAUSAL_LM, num_virtual_tokens=10)
    # config
    # Hard Prompt
    config = PromptTuningConfig(task_type=TaskType.CAUSAL_LM,
                                prompt_tuning_init=PromptTuningInit.TEXT,
                                prompt_tuning_init_text="下面是一段人与机器人的对话。",
                                num_virtual_tokens=len(tokenizer("下面是一段人与机器人的对话。")["input_ids"]),
                                tokenizer_name_or_path="langboat_bloom-1b4-zh")
    print("config:", config)

    model = get_peft_model(model, config)

    print("model:", model)

    print("print_trainable_parameters:", model.print_trainable_parameters())

    # 训练参数
    args = TrainingArguments(
        output_dir="./chatbot",
        per_device_train_batch_size=1,
        gradient_accumulation_steps=8,
        logging_steps=10,
        num_train_epochs=1
    )

    # trainer
    trainer = Trainer(
        model=model,
        args=args,
        train_dataset=tokenized_ds,
        data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True)
    )

    # 训练模型
    trainer.train()

    # 加载训练好的模型进行推理
    model = AutoModelForCausalLM.from_pretrained("langboat_bloom-1b4-zh")
    peft_model = PeftModel.from_pretrained(model=model, model_id="./chatbot/checkpoint-20/") # model_id是训练好的模型本地路径

    peft_model = peft_model.cuda()
    ipt = tokenizer("Human: {}\n{}".format("考试有哪些技巧?", "").strip() + "\n\nAssistant: ", return_tensors="pt").to(
        peft_model.device)
    print(tokenizer.decode(peft_model.generate(**ipt, max_length=128, do_sample=True)[0], skip_special_tokens=True))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值