PEFT 之 Prompt-Tuning 2

目录

1. PromptTuningInit.RANDOM for generate prompts

1.1 codes

1.2 results

2. PromptTuningInit.TEXT for detect hate in sentences

2.1 codes

2.2 results


这篇prompt微调主要聚焦的是:两种虚拟 token 的初始化方式​(Random or Text)

1. PromptTuningInit.RANDOM for generate prompts

1.1 codes

代码主体与上一篇(PEFT 之 Prompt-Tuning 1-CSDN博客)中的Prompt-Tuning1一致,主要区别在于数据处理的不同。

(1)data prepare

def concatenate_columns_prompt(dataset):
    def concatenate(example):
        example['prompt'] = "Act as a {}. Prompt: {}".format(example['act'], example['prompt'])
        return example

    dataset = dataset.map(concatenate)
    return dataset

#Create the Dataset to create prompts.
data_prompt = load_dataset(dataset_prompt)
data_prompt['train'] = concatenate_columns_prompt(data_prompt['train'])

data_prompt = data_prompt.map(lambda samples: tokenizer(samples["prompt"]), batched=True)
train_sample_prompt = data_prompt["train"].remove_columns('act')

lambda samples: tokenizer(samples["prompt"])

lambda是匿名函数(无需显式定义函数名的轻量级函数,lambda 参数: 表达式),相当于:

def tokenize_batch(samples):  
    return tokenizer(samples["prompt"])  

 输入参数(samples)是一个批次的字典格式的样本:

samples = {"prompt": ["prompt1", "prompt2", ...], "act": ["act1", "act2", ...]}  

tokenizer(samples["prompt"]) 会对该批次中所有prompt字段进行分词,返回包含input_ids、attention_mask等字段的字典。 最终数据集的结构变为类似:

{"input_ids": [[...], ...], "attention_mask": [[...], ...], "act": [...]}  

(2)prompt 微调配置

generation_config_prompt = PromptTuningConfig(
    task_type=TaskType.CAUSAL_LM, 
    prompt_tuning_init=PromptTuningInit.RANDOM,  #The added virtual tokens are initializad with random numbers
    num_virtual_tokens=NUM_VIRTUAL_TOKENS, 
    tokenizer_name_or_path=model_name 
)

1.2 results

(1)input1 = "Act as a fitness Trainer. Prompt: "

pretrained_model:
['Act as a fitness Trainer. Prompt:  Follow up with your trainer']

560M Trained for 50 Epochs & 8 virtual tokens:
'train_loss': 2.69047605074369
['Act as a fitness Trainer. Prompt:  I want you to act like an expert in the field of exercise and nutrition, but also have some fun with your own life! You will be teaching me how my body works when i do not eat enough or if it is too hot for what we']

560M Trained for 50 Epochs & 20 virtual tokens:
'train_loss': 2.5929998485858623
['Act as a fitness Trainer. Prompt: 输入你的问题,并给出答案。我是一对夫妻的健身教练和瑜伽老师、有小孩两岁半的孩子以及一个5岁的孩子在家里做运动训练需要用到专业的指导及建议!请用你最擅长的内容来']

 最后一条输出的prompt为中文,这让主包有点意外了。但是 ai 说这种情况是正常的,不过可能的原因有:

  1. 根本原因: Bloomz-560M 是一个多语言模型。微调过程​​未明确限制输出语言​​,导致模型自由切换语言。

  2. 次要原因:训练参数的副作用​。高学习率(3e-2)可能导致模型​​过度拟合训练数据中的噪声​​。训练轮次(50 epochs)过长可能放大噪声影响,使模型偏离预期目标。

 稍微对参数进行了一些修改,几次输出都是英文的,机制很黑盒了:

参数:epoch = 30, learning_rate = 3e-3

output1:
['Act as a fitness Trainer. Prompt:  I want you to act like an expert in your field and help others improve their health by doing exercises.  You can be anything from running, swimming or cycling for example.   My first request is "What\'s the best way of improving my body?" If']

output2:
['Act as a fitness Trainer. Prompt:  I want you to act like an expert in your field and provide recommendations for the best products, services or training methods available on this market.  You will also be responsible for: 1) providing advice about what is right at home; 2)  helping customers make']

output3:
['Act as a fitness Trainer. Prompt:  I want you to act like an expert in your field and be able to:   Answer questions about the subject of this exercise, such as: "what is my favorite sport?" or "the best way for me [to] get fitter ... what are']

(2)input2 = "Act as a Motivational Coach. Prompt: "

 由于上一篇中输入“I want you to act as a motivational coach”得到的输出非常莫名其妙,于是主包选择重新尝试一下。

pretrained_model:
['Act as a Motivational Coach. Prompt:  Ask for feedback']

560M Trained for 50 Epochs & 8 virtual tokens:
'train_loss': 2.6983046018160306
['Act as a Motivational Coach. Prompt: 输入问题,并给出建议。写出答案后写下动机和目标。  使用正向激励法来达到最佳效果:使用积极、正面的语气鼓励自己实现自己的目标和愿望;用消极的言语贬低自身或他人时要避免']

560M Trained for 50 Epochs & 20 virtual tokens:
'train_loss': 2.6259666560246395
['Act as a Motivational Coach. Prompt: + Addictive, motivating and inspiring; + I would like to help people feel more confident in themselves by helping them achieve their goals through actionable strategies that are practical for the real world; i want you always have something new or interesting happening with']

从上述结果能明显感觉到,经过当前数据处理的input,微调后输出的效果要好很多。

  • 结构化输入​​(Act as {act}, Prompt:{prompt} )
    通过显式标注任务角色 act 和指令内容 prompt,​​增强了模型对任务结构的感知​​。这种格式更贴近原始数据集(每条样本包含 act  和 prompt),这种对齐可能让模型​​更快学习到“角色-任务”的映射关系​​,从而在微调中更高效地捕捉任务模式。
    显式的结构​​可能更适合需要严格遵循指令的场景(如客服对话、模板化回复),但可能​​限制模型的创造性​​。

  • 原始输入​​(“I want you to act as a motivational coach”)
    依赖模型自行推断任务目标,可能导致生成结果的多样性强,但​​对指令的遵循性较弱​​。 ​直接使用 prompt 字段,省略了 act  信息,可能导致模型​​忽略角色定义对生成结果的影响​​。例如,模型可能更关注“生成激励性内容”,而非“作为教练”这一身份约束。
    隐式指令​​则更适合开放域生成(如创意写作),模型能更灵活地结合上下文续写内容,但可能因指令模糊导致输出不稳定。

2. PromptTuningInit.TEXT for detect hate in sentences

通过 ​​Prompt Tuning ​​,将生成式语言模型(BLOOMZ-560M)改造成一个 ​​二分类的仇恨言论检测器​​,使用的数据集是 SetFit/ethos_binary(包含仇恨言论和非仇恨言论的文本标注)。

2.1 codes

(1)data prepare

SetFit/ethos_binary 数据集:

concatenate_columns_classifier 将原始数据转换为指令式提示,为了训练模型根据输入句子生成对应的标签(hate or no_hate)。 

def concatenate_columns_classifier(dataset):
    def concatenate(example):
        example['text'] = "Sentence : {} Label : {}".format(example['text'], example['label_text'])
        return example

    dataset = dataset.map(concatenate)
    return dataset

data_classifier = load_dataset(dataset_classifier)
data_classifier['train'] = concatenate_columns_classifier(data_classifier['train'])

data_classifier = data_classifier.map(lambda samples: tokenizer(samples["text"]), batched=True)
train_sample_classifier = data_classifier["train"].remove_columns(['label', 'label_text', 'text'])

(2)prompt 微调配置

PromptTuningInit.TEXT :表示用一段自然语言文本的 ​​嵌入向量(Embedding)​​ 来初始化虚拟令牌的嵌入空间。实现过程:

  1. 文本分词​​:将 prompt_tuning_init_text 通过分词器转换为 token_ids

  2. 截断或填充​​:根据 num_virtual_tokens 调整长度:
    若文本分词后长度 ​​>8​​ → 截断前8个词。
    若长度 ​​<8​​ → 填充 <pad> token至8个。

  3. 嵌入映射​​:取这8个token对应的预训练模型的​​词嵌入矩阵(Embedding Layer)​​中的向量,作为虚拟令牌的初始值。

假设输入的"Indicates whether the sentence contains hate speech or not"分词后得到5个词,那么得到的嵌入向量为:[Indicates_emb, whether_emb, ..., <pad>_emb, <pad>_emb, <pad>_emb] 

(3)load model for test samples(存疑)

loaded_model_peft = PeftModel.from_pretrained(foundational_model, output_directory_classifier, is_trainable=False)
loaded_model_peft.load_adapter(output_directory_classifier, adapter_name="classifier")
loaded_model_peft.set_adapter("classifier")
loaded_model_sentences_outputs = get_outputs(loaded_model_peft, input_classifier.to(device), max_new_tokens=3)
print(tokenizer.batch_decode(loaded_model_sentences_outputs, skip_special_tokens=True))

这块比其他load_model的部分多了load_adapter和set_adapter,主包不太理解这么做的原因是什么,感觉是有点多此一举。我们进行了测试,两次的输出结果是一样的。from_pretrained​​已经从output_directory_classifier中加载了微调后的 adapter,而后续的load_adapter又从同一路径加载了adapter,只不过是将其重命名为了classifier。不太懂原作者在编写这块代码时的目的是什么,个人认为是冗余了。期待大家的解答(☆▽☆)

loaded_model_peft = PeftModel.from_pretrained(foundational_model, output_directory_classifier, is_trainable=False)
loaded_model_sentences_outputs1 = get_outputs(loaded_model_peft, input_classifier.to(device), max_new_tokens=3)
print("before classifier:", tokenizer.batch_decode(loaded_model_sentences_outputs1, skip_special_tokens=True))
## before classifier: ['Sentence : Head is the shape of a light bulb. Label :  no hate speech']

loaded_model_peft.load_adapter(output_directory_classifier, adapter_name="classifier")
loaded_model_peft.set_adapter("classifier")
loaded_model_sentences_outputs2 = get_outputs(loaded_model_peft, input_classifier.to(device), max_new_tokens=3)
print("after classifier:", tokenizer.batch_decode(loaded_model_sentences_outputs2, skip_special_tokens=True))
## after classifier: ['Sentence : Head is the shape of a light bulb. Label :  no hate speech']

2.2 results

pretrained_model:
['Sentence : Head is the shape of a light bulb. Label :  head']
["Sentence : I don't liky short people, no idea why they exist. Label : No"]

50 epochs & 8 virtual tokens:
['Sentence : Head is the shape of a light bulb. Label :  no hate speech']
["Sentence : I don't liky short people, no idea why they exist. Label : hate speech ."]

10 epochs & 20 virtual tokens:
['Sentence : Head is the shape of a light bulb. Label :  hate speech no']
["Sentence : I don't liky short people, no idea why they exist. Label : hate speech"]

 第一个sentence和原文档中的输出不同,而且输出的顺序比较怪,尝试对学习率和token数进行修改,发现输出就恢复正常顺序了

token = 20, lr = 3e-3
['Sentence : Head is the shape of a light bulb. Label :  no hate speech']

token = 10, lr = 3e-2
['Sentence : Head is the shape of a light bulb. Label :  no hate speech']
<think>好的,我现在需要帮用户整理关于P-Tuning v2和LoRA微调的典型面试问题及回答。首先,我得回忆一下这两种微调方法的基本概念和区别。P-Tuning v2主要是通过引入可训练的连续提示向量(prompt tuning),而LoRA则是通过低秩适配器在模型的权重矩阵中注入可训练参数。接下来,用户可能想知道面试中常被问到的问题类型,比如两者的对比、优缺点、适用场景等等。 用户可能希望这些问题能够覆盖理论理解、实际应用以及比较分析。因此,我需要分门别类地整理问题,比如基础概念题、对比分析题、实现细节题、应用场景题等。每个问题下需要简明扼要的回答,同时确保技术细节准确无误。例如,可能会问“P-Tuning v2的核心改进是什么?”这时候需要回答它解决了v1中的哪些问题,比如深度提示和参数效率。 另外,考虑到面试中可能出现的实战题,例如如何选择这两种方法,需要结合具体场景来分析。比如,如果资源有限,LoRA可能更适合,因为它参数更少,训练更快。而需要更好效果时,P-Tuning v2可能更优。同时,还要注意两者的结合使用,这在面试中可能是一个加分点。 还需要检查是否有常见的误解需要澄清,比如是否LoRA只能用于注意力层,或者P-Tuning v2是否需要修改模型结构。回答时要准确指出这些误解,并提供正确的信息。此外,面试官可能会问及参数效率、训练速度、显存占用等实际因素,这些都需要详细对比。 最后,要确保回答结构清晰,层次分明,使用适当的Markdown格式,如表格和代码块,来增强可读性。同时,语言要简洁,避免冗长,但又要覆盖关键点。可能还需要补充一些示例,比如代码片段,展示如何实现这两种方法,帮助用户更好地理解。</think>以下是针对P-Tuning v2和LoRA微调技术的典型面试问题及参考答案整理,采用结构化分类组织: --- ### 一、基础概念题 **Q1: 简述P-Tuning v2的核心改进** *A1:* - 解决v1版本的两大痛点: 1. **深度连续性**:在每一层Transformer都插入可训练提示向量(Prefix-tuning思路) 2. **参数效率**:通过多层共享提示参数减少参数量(典型配置:每层共享256维向量) - 数学形式: 对第$l$层输入的增强: $$ h^{(l)} = [P^{(l)}; h^{(l)}_{original}]W^{(l)} $$ 其中$P^{(l)}$为可训练提示向量 --- ### 二、对比分析题 **Q2: LoRA与P-Tuning v2的本质区别** *A2:* | 维度 | LoRA | P-Tuning v2 | |------------|-------------------------------|------------------------------| | **修改位置** | 权重矩阵旁路($W + \Delta W$) | 输入空间扩展(添加prefix) | | **参数分布** | 分散在多层线性层 | 集中在输入/各层prefix | | **显存占用** | 较高(需缓存适配器梯度) | 较低(仅提示向量相关参数) | | **适配速度** | 更快收敛(直接修改权重) | 较慢(依赖prompt语义学习) | **Q3: 为什么LoRA常采用较小的rank值?** *A3:* 1. **低秩假设**:语言模型的权重更新具有低秩特性(rank=8时可保留95%以上信息) 2. **计算优势**:分解矩阵$B \cdot A$的参数量为$r(d_{in}+d_{out})$,当$r \ll min(d_{in},d_{out})$时显著减少参数 3. **过拟合控制**:限制秩相当于隐式正则化(实验表明rank=8在多数任务表现最佳) --- ### 三、实现细节题 **Q4: 如何为LoRA选择目标层?** *A4:* 1. **经验法则**: - 优先选择`query`和`value`投影层(对语义理解最关键) - 输出层`dense`适配对生成任务更有效 2. **实验策略**: ```python # Hugging Face实现示例 peft_config = LoraConfig( target_modules=["query", "value", "dense"], # 关键层 r=8, lora_alpha=32, lora_dropout=0.1 ) ``` **Q5: P-Tuning v2如何实现参数共享?** *A5:* 通过**分块共享策略**: - 将深度网络划分为多个块(如每4层为一组) - 同一块内共享相同的提示向量 - 代码实现示例: ```python class PrefixEncoder(nn.Module): def __init__(self, num_layers, hidden_size): self.prefix = nn.Parameter( torch.randn(num_layers//4, prefix_length, hidden_size) ) # 每4层共享一个prefix ``` --- ### 四、应用场景题 **Q6: 何时选择LoRA/P-Tuning v2?** *A6:* 决策树分析 ```mermaid graph TD A[目标任务需求] --> B{需要快速迭代?} B -->|是| C[LoRA] B -->|否| D{数据量小于1万条?} D -->|是| E[P-Tuning v2] D -->|否| F[Full Fine-tuning] ``` **Q7: 如何结合两种方法?** *A7:* 混合微调策略(Hybrid-tuning): 1. **底层用LoRA**:增强基础语义理解能力 2. **高层用P-Tuning**:精细控制生成方向 3. 参数分配比例建议:7:3(LoRA层数 : P-Tuning层数) --- ### 五、实战陷阱题 **Q8: 为什么LoRA微调后模型会出现输出重复?** *A8:* 典型问题与解决方案: - **根本原因**:低秩适配导致生成多样性下降 - **调试方法**: 1. 增加`lora_dropout`值(0.1→0.3) 2. 调整`lora_alpha`(建议保持alpha/r=4) 3. 添加生成惩罚项: ```python model.generate(..., repetition_penalty=1.2) ``` **Q9: P-Tuning v2在长文本任务中效果差的可能原因** *A9:* 1. **位置编码冲突**:添加的prefix挤占原始token位置信息 2. **解决方案**: - 使用RoPE等相对位置编码 - 限制prefix长度(建议不超过总长度的10%) --- ### 六、前沿扩展题 **Q10: 如何理解LoRA与QLoRA的关系?** *A10:* - **QLoRA三要素**: 1. **4-bit量化**:通过NF4数据类型压缩原始模型 2. **双适配器**:在量化和反量化步骤插入LoRA 3. **分页优化器**:解决显存峰值问题 - **关键优势**:相比原始LoRA显存占用减少33%(7B模型可在24GB显存运行) --- ### 附:高频考点速记表 | 考点 | LoRA要点 | P-Tuning v2要点 | |----------------------|-------------------------------------|-----------------------------------| | 核心创新点 | 低秩矩阵分解 | 深度连续提示向量 | | 参数效率 | 约0.1%原始参数量 | 约0.3%原始参数量 | | 显存占用 | 较高(需存储适配器梯度) | 较低(仅提示向量) | | 典型应用场景 | 数据量中等(1万-10万条) | 小样本(<1千条) | | 与全量微调的效果差距 | 差距较小(平均差2-3%) | 差距较大(差5-8%) | 掌握这些核心要点,可以应对90%以上的技术面试相关问题。实际回答时建议结合具体实验数据(如「在XX数据集的实验中,LoRA仅用1.5%参数即达到97%的全量微调效果」)增强说服力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值