1 NLP任务的四种范式
目前学术界一般将NLP任务的发展分为四个阶段,即NLP四范式:
- 第一范式:采用传统机器学习模型,例如TF-IDF特征结合朴素贝叶斯等算法;
- 第二范式:运用深度学习模型,如word2vec特征与LSTM等算法,相比第一范式,模型准确率提升且减少了特征工程工作量;
- 第三范式:基于预训练模型微调(如BERT微调),相比第二范式,模型准确度显著提高且规模增大,但仅需少量数据即可训练出优质模型;
- 第四范式:采用预训练模型结合Prompt预测(如BERT+Prompt),相比第三范式,所需训练数据量大幅减少。
| 维度 | 第一范式 | 第二范式 | 第三范式 | 第四范式 |
|---|---|---|---|---|
| 主要任务 | 特征工程 | 架构工程 | 目标工程 | 提示工程 (Prompting) |
| 数据需求 | 大量标注 | 大量标注 | 少量标注 | 极少量/无需标注 |
| 预训练 | 无 | 可选 (Word2Vec) | 核心 (BERT) | 核心 (LLM) |
| 任务形式 | 分类/回归 | 序列化输出 | 判别/生成 | 补全/对话 |
1.1 NLP 四范式深度解构
1.1.1 第一范式:非神经网络时代 (Feature Engineering)
核心 :特征工程 + 统计模型。
痛点:极其依赖专家的领域知识。你需要手动定义什么是"名词"、什么是"情感词"。
代表:SVM、朴素贝叶斯、LDA 主题模型。
你的项目关联:如果你在做一些超轻量级的本地关键词过滤,TF-IDF 依然是性能最快、性价比最高的方案。
1.1.2 第二范式:深度学习时代 (Architecture Engineering)
核心 :词向量 + 循环/卷积神经网络。
进步 :消灭了大部分手动特征。通过
Word2Vec或GloVe将文字映射到高维空间。痛点:你需要针对不同任务设计复杂的架构(比如文本分类用 TextCNN,翻译用 Seq2Seq)。
代表:LSTM、GRU、FastText。
1.1.3 第三范式:预训练+微调 (Objective Engineering)
核心 :Transformer + Fine-tuning。
进步:有了"底座"的概念。模型先在海量数据上学好语言规律(预训练),再通过少量标注数据微调(Fine-tune)来适配特定任务。
现状:这是目前工业界最稳健的方案(比如你之前提到的用 BERT 做分类)。
代表:BERT、RoBERTa、Early GPT。
1.1.4 第四范式:Prompt 时代 (Prompt Engineering)
核心 :预训练 + 提示预测 (Zero/Few-shot)。
哲学 :"不再让模型适配任务,而是让任务适配模型"。
优势:极度节省数据。通过设计一个好的 Prompt,甚至不需要任何微调就能让模型处理从未见过的任务。
代表:GPT-3/4、P-Tuning 系列、Llama 3。
在整个NLP领域,整个发展历程是朝着精度更高、少监督,甚至无监督的方向发展的。而 Prompt-Tuning 是目前学术界向这个方向进军最新也是最火的研究成果。
2 Fine-Tuning(微调)

微调(Fine-Tuning)是一种迁移学习方法 ,在自然语言处理领域主要用于使预训练语言模型适应特定任务。其核心思路是:++首先利用大规模通用文本训练基础模型,然后针对小规模专业数据继续优化模型参数。++
传统的Fine-Tuning方法通过使用少量任务相关数据对预训练模型进行继续训练。该方法会更新模型权重以提高任务适配性。所需的调整程度主要取决于预训练数据与目标任务数据之间的相似度:当两者高度相似时仅需微调,差异较大时则需更充分的调整。
然而,在多数下游任务微调过程中,由于任务目标与预训练目标差异过大,往往导致性能提升有限,且需要依赖大量标注数据。针对这一问题,以GPT3、PET为代表的模型提出了一种新型微调范式------Prompt-Tuning。该方法通过设计提示模板,将下游任务转化为与预训练目标(如掩码语言建模)相似的形式,既避免了引入额外参数,又能充分挖掘模型的预训练知识。
Prompt-Tuning主要解决传统Fine-Tuning方式的两个痛点:
大规模参数带来的挑战:全量微调需要更新模型所有参数,对于大模型而言,这不仅导致显存占用过高、训练速度缓慢,还会因每个下游任务都需要保存完整模型权重而产生巨大的存储开销。
多任务迁移的局限性:传统微调方法为每个任务单独训练新模型,无法有效实现大模型参数在多任务间的共享。这种"一任务一模型"的模式,严重制约了模型在多任务、多场景下的高效适配能力。
3 Prompt-Tuning(提示微调)
3.1 什么是Prompt?
"Prompt"顾名思义就是提示的意思。玩过"你画我猜"游戏的人都知道,当对方根据词语作画时,由于画风奇特或缺乏默契,常常让人难以猜测。这时屏幕上出现的提示词就很有帮助,比如"3个字"、"水果"这样的提示,能大大缩小猜测范围。这就是prompt的神奇之处。
Prompt本质上是你与大型语言模型(LLM)交互时的完整输入内容,其核心作用是引导模型生成符合预期的输出。它可以理解为AI对话的"启动指令"或"交互引导"。
你可以把它想象成:
设计考题时,请记住:
- 你的Prompt就是考题,而LLM是那个聪明的学生
- 题目设计得越精准,学生就越能准确理解你的意图
- 你的Prompt就是提问方式,LLM扮演知识渊博的专家
- 恰当的提问技巧能引导专家给出更优质的答案
3.2 Prompt-Tuing定义
Fine-Tuning方法通过调整预训练模型来适应下游任务,而Prompt-Tuning则反其道而行,通过改造下游任务使其适配预训练模型,其核心在于将Fine-Tuning的目标任务转化为预训练的形式。
那么具体如何工作呢?我们以一个二分类的情感分析为例子,进行简单理解:
示例:给定句子[CLS] I like the Disney films very much. [SEP]
传统微调方法: 首先利用BERT的Transformer模型提取[CLS]特征表示,然后将该特征输入新增的MLP分类器进行二分类,判断句子情感倾向为积极(positive)或消极(negative)。这种方法需要较大规模的训练数据来完成模型训练。
Prompt-Tuning实现流程:
模板构建:通过人工设计、自动搜索或文本生成等方式,创建包含[MASK]标记的模板。例如"It was [MASK].",将其与原始文本拼接形成Prompt-Tuning的输入:[CLS] I like the Disney films very much. [SEP] It was [MASK]. [SEP]。将该输入送入BERT模型,直接使用预训练好的MLM分类器(如huggingface中的BertForMaskedLM)预测[MASK]位置各token的概率分布。例子:{great: 0.3,terrible:0.1....}
标签词映射:由于我们仅关注[MASK]位置的特定词汇,需要建立映射关系。例如,当[MASK]预测为"great"时归为positive类,预测为"terrible"时归为negative类。这种方法引入了先验知识。例如,模型本身就知道 "great" 和 "positive" 是强相关的,这比传统微调中随机初始化的分类层(Linear Head)起点要高得多。例子:{great:P, fun:P, terrible:N.......}
模型训练:基于Verbalizer获取指定标签词的预测概率分布,采用交叉熵损失进行优化。相比传统微调方法,这种方案数据需求量更少且效果良好。计算 [MASK] 位置处,经过 Verbalizer 映射后的标签分布与真实标签的交叉熵。
注意思考:不同的句子应该有不同的template和label word,没错,因为每个句子可能期望预测出来的label word都不同,因此如何最大化的寻找当前任务更加合适的template和label word是Prompt-tuning非常重要的挑战。
我们可以认识到,引入模板和标签词本质上是一种数据增强手段,通过添加提示信息来引入先验知识。
4 Prompt-Tuning技术发展历程
Prompt-Tuning的发展历程自GPT-3问世后经历了显著演变,主要呈现以下关键进展:
从In-Context Learning到Prompt Tuning:在In-Context Learning基础上,Prompt Tuning通过训练Prompt Tokens实现了更优的性能和参数效率。
从Hard Prompt到Soft Prompt:采用可学习向量替代自然语言文本作为Prompt,有效解决了人工设计Prompt的难题。
从单层Prompt到多层Prompt:Prefix-Tuning创新性地在模型各层添加Prompt Tokens,显著提升了对模型行为的控制能力。
从手工设计到自动化搜索:研究重点转向Prompt Engineering的自动化探索,以持续优化Prompt Tuning的效率和效果。
5 面向超大规模语言模型的Prompt-Tuning
近两年来,随之Prompt-Tuning技术的发展,有诸多工作发现,对于超过10亿参数量的模型来说,Prompt-Tuning所带来的增益远远高于标准的Fine-tuning,小样本甚至是零样本的性能也能够极大地被激发出来,得益于这些模型的 参数量足够大,训练过程中使用了**足够多的语料**,同时设计的**预训练任务足够有效**。最为经典的大规模语言模型则是2020年提出的GPT-3,其拥有大约1750亿的参数,且发现只需要设计合适的模板或指令即可以**实现免参数训练的零样本学习** 。
介绍几个面向超大规模的Prompt-Tuning方法,分别为:
上下文学习(In-Context Learning,ICL):精选少量训练样本作为任务提示,指导模型完成任务;
指令学习(Instruction-Tuning):通过构建任务指令集,引导模型根据指令生成相应输出;
思维链(Chain-of-Thought,CoT):提供或激发模型产生推理过程,以线性链式结构引导模型生成逻辑合理的结果。
5.1 In-Context Learning(上下文学习)
ICL的核心思想在于借助上下文示例(demonstrations)让模型领悟任务本质,无需显式训练。这一理念在早期的few-shot学习中已有雏形,但传统方法往往需要通过训练来调整模型参数。直到GPT-3(2020)的出现,才真正实现了完全在推理阶段完成这一过程,并正式将其命名为"In-Context Learning"。
常用的In-context learning方法包括:
zero-shot learning
定义 :直接使用预训练模型进行任务测试,仅提供任务描述和测试数据。
示例:输入提示"将中文翻译为英文。销售->",模型应输出"sell"。one-shot learning
定义 :在任务测试前提供一个示例作为指导,帮助模型理解任务要求。
示例:输入提示"将中文翻译为英文。你好->hello,销售->",模型应输出"sell"。few-shot learning
定义 :在任务测试前提供多个示例(通常10-100个)作为指导,使模型更好地掌握任务模式。
示例:输入提示"将中文翻译为英文。你好->hello,再见->goodbye,购买->purchase,销售->",模型应输出"sell"。
当前,In-context Learning 在性能上仍与常规微调方法存在明显差距,其预测结果波动较大且不稳定。此外,该方法还需额外投入时间精力来设计有效的提示模板。
5.2 Instruction-Tuning(指令微调)
面向超大规模模型的第二种Prompt技术是指令学习。简而言之,Prompt-Tuning的核心在于为模型提供明确的任务指令------告诉模型需要执行什么任务以及期望的输出内容。在微调大规模模型时,通过设计多样化的任务指令进行训练,能够显著提升模型处理不同任务的泛化能力。
什么是Instruction-Tuning?让我们暂时放下所有先入为主的概念,把自己想象成一个AI模型。来看两个不同的任务示例:
补全任务: "带女朋友去了一家餐厅,她吃的很开心,这家餐厅太__了!"
判别任务: "判断这句话的情感,回答是积极还是消极:带女朋友去了一家餐厅,她吃的很开心。"
这两种任务形式展现了关键区别:第一种是传统的Prompt方式,而第二种则采用了Instruction模式。前者需要模型自行推断补全内容,后者则明确指示了任务类型和输出要求。
Instruction-Tuning和Prompt-Tuning的核心一样,就是去 发掘语言模型本身具备的知识 。而他们的不同点就在于:
- Prompt是去激发语言模型的 补全能力 ,比如给出上半句生成下半句、或者做完形填空。
- Instruction-Tuning则是激发语言模型的 理解能力 ,通过给出更明显的指令/指示,让模型去理解并做出正确的action.
- Promp-Tuningt在 没有精调 的模型上也能有一定效果,但是Instruct-Tuning则 必须对模型精调 ,让模型知道这种指令模式。
实现Instruction-Tuning的方法
该方法通常在预训练大模型的基础上进行,主要包含以下步骤:
数据准备阶段:
- 收集大规模的指令-响应对数据集
- 将这些指令转化为自然语言提示(prompt)
模型训练:
- 采用监督微调(Supervised Fine-Tuning, SFT)方法
- 将指令与上下文拼接成输入序列
- 训练目标是最大化模型生成正确响应的概率
效果优化:
- 为每个任务设计多个指令模板
- 测试时评估平均表现和最佳表现
5.3 Chain-of-Thought(思维链)
Google在论文《Chain-of-Thought Prompting Elicits Reasoning in Large Language Models》中首次提出了**思维链(Chain-of-Thought,CoT)**的概念。这种创新的提示策略能显著提升大语言模型在复杂推理任务中的表现,包括算术推理、常识推理和符号推理等领域。
与传统的ICL方法不同,CoT并非简单地使用输入输出对构建提示,而是通过引入中间推理步骤来增强模型输入。本质上,思维链是一种离散式提示学习方法。相较于仅将示例前置的上下文学习方式,CoT的创新之处在于加入了推导过程的中间提示环节。
CoT的分类:
Few-shot CoT 是 ICL 的一种特殊形式,它将每个演示样例从简单的〈input, output〉扩展为包含推理过程的〈input, CoT, output〉三元组。
Zero-shot CoT 则采用不同的方法:它无需人工标注的任务演示,而是让模型自主生成推理步骤来推导答案。具体实现分两步:
- 使用"Let's think step by step"提示引导模型生成推理过程
- 通过"Therefore, the answer is"提示得出最终结论
一个有效的思维链应该具有以下特点:
逻辑性:思维链中的每个思考步骤都应该是有逻辑关系的,它们应该相互连接,从而形成一个完整的思考过程。
全面性:思维链应该尽可能地全面和细致地考虑问题,以确保不会忽略任何可能的因素和影响。
可行性:思维链中的每个思考步骤都应该是可行的,也就是说,它们应该可以被实际操作和实施。
可验证性:思维链中的每个思考步骤都应该是可以验证的,也就是说,它们应该可以通过实际的数据和事实来验证其正确性和有效性。
6 面向小规模语言模型的Prompt-Tuning
虽然Prompt-Tuning在超大规模语言模型上表现优异,但这些参数量超过100亿的模型在实际应用中往往难以落地。为此,研究者们开始探索GPT-3的Prompt-Tuning方法在小规模模型(如BERT)上的适用性。研究表明,该方法在小模型上同样可行,但需要注意以下关键差异:
- 小模型的泛化能力显著弱于GPT-3,在few-shot/zero-shot学习场景中表现较差
- 小模型对数据量更敏感,容易出现过拟合问题,且prompt的细微调整可能导致性能大幅波动
这些特性凸显了针对小规模语言模型开展Prompt-Tuning研究的重要性。
6.1 Prompt-Tuning的鼻祖---PET模型
PET模型(Pattern-Exploiting Training) 源自EACL2021论文《Exploiting Cloze Questions for Few Shot Text Classification and Natural Language Inference》。该方法创新性地融合了提示学习与少量样本监督学习(Few-shot Supervised Learning),其核心思想是***++将下游任务(如文本分类)转化为类似预训练任务(如掩码语言建模)的形式。++***
具体实现包含两个关键组件:自然语言模式(pattern)和标签词映射(verbalizer)。首先通过pattern将输入文本转换为包含[MASK]标记的填空句式(例如"这个电影很[MASK]。"),然后利用预训练语言模型(如BERT)预测[MASK]位置的词汇,最后通过verbalizer将预测结果映射为具体的分类标签,从而完成分类任务。
PET模型包含两个核心组件:
Pattern(模板)(记为T):
- 指添加了[mask]标记的短文本模板
- 通常每个样本仅使用一个Pattern,以确保只预测一个[mask]标记
- 研究重点:如何为不同任务和样本设计最优的Pattern
Verbalizer(标签映射器)(记为V):
- 将标签映射到具体的词汇
- 例如情感分析中,可能将"positive"和"negative"映射为对应的标签词
- 研究挑战:Verbalizer的设计需要与Pattern相匹配,且因任务而异
这两个组件被称为Pattern-Verbalizer-Pair(PVP),在后续研究中被广泛采用。PVP的训练目标可定义如下:给定句子及其对应标签,首先通过预定义的PVP组件,按照模板对句子进行处理,将原始任务转换为填空(Masked Language Modeling)形式。然后让模型预测模板占位符处应出现的词,并将预测结果与标签映射进行匹配,据此计算损失并优化模型参数。通过这种训练,模型能够学会通过模板准确预测新句子的对应标签。
在PVP框架下,如何选择或构建合适的Pattern和Verbalizer是当前亟需解决的核心问题。常见的解决方案是根据任务特性和先验知识人工设计模板,比如情感分析任务中常采用"It was [mask]."这样的模板。虽然人工构建方法简单直观,但其存在明显缺陷:研究表明,在相同数据集和训练条件下,不同的Pattern和Verbalizer选择会导致结果存在显著差异。
人工设计方法的缺陷:
- 采用人工构建的方法成本高,需要与领域任务相关的先验知识;
- 人工设计的Pattern和Verbalizer不能保证获得最优解,训练不稳定,不同的PVP对结果产生的差异明显,方差大;
- 在预训练阶段MLM任务并非完全按照PVP的模式进行训练的(比如MLM训练通常都是长文本,mask的数量也并非只有1个,预测的概率分布也并非是有限的),因此人工构建的Pattern和Verbalizer使得Prompt-Tuning与MLM在语义和分布上依然存在差异。
6.2 Prompt-Oriented Fine-Tuning
PET模型在对MLM进行微调时采用了Prompt-Oriented Fine-Tuning方法。这种方法的核心在于将目标任务转化为与预训练模型兼容的形式,使其能够更好地融入预训练模型的学习框架。
POFT方法根据提示类型可分为三种:
**离散提示(硬模版):**使用真实的自然语言词汇或符号构成提示,直接拼接至输入文本。
**连续提示:**采用可训练的向量作为提示,嵌入到输入的embedding序列中。
**混合提示:**结合人工可读的离散token与可训练的连续向量共同构成提示。
按照训练时参数更新的范围不同,POFT方法主要分成三种类型:
- 全量微调(Full Fine-Tuning):模型所有参数都参与更新,包括预训练模型参数和下游任务层参数。如PET模型。
- 部分参数微调(Partial Fine-Tuning):只更新预训练模型中的一部分参数,比如高层 transformer block、某些 attention 层或特定模块,其余参数冻结。如Adapter Tuning。
- 仅提示参数微调(Prompt-Only Tuning):冻结原始预训练模型参数,只训练 prompt 参数。如P-tuning、Prompt Tuning等。
具体来说,PET中使用的方法为: 基于硬模板+ 全量微调
PET方法采用Prompt-Tuning与Fine-Tuning相结合的方式,其本质是对预训练模型参数进行微调的改进版。虽然同样需要优化整个模型参数,但相比传统Fine-Tuning,PET显著降低了数据需求。
对于BERT等规模较小的模型,全量微调效果良好。但随着模型规模扩大,每次针对下游任务更新预训练参数会带来巨大的资源与时间成本。为此,后续出现了仅优化Prompt参数的方法,如Prompt Tuning、P-tuning和Prefix Tuning等基于Soft Prompt的微调技术,这些方法有效降低了任务迁移的成本。
PET方法中采用的硬模版同样存在若干局限:
Hard Prompt(离散提示)是指通过将特定关键词或短语(实际文本字符串)直接嵌入文本中,引导模型生成目标内容的固定提示模板。这类模板通常通过人工经验设计或自动化搜索生成。
该方法的显著特点是模板固定不变,无法根据不同任务需求进行灵活调整。PVP相关研究表明,在使用硬模板时,即使仅修改prompt中的单个词语,也可能导致实验结果产生显著波动。
6.3 Soft Prompt及微调方法
6.3.1 连续提示模板
**软提示(连续提示)**是一种通过可参数化的提示模板来引导模型生成符合特定需求文本的方法。其核心在于提示模板中的参数能够根据不同任务灵活调整,从而优化生成效果。
软提示的关键在于将模板转化为可优化的连续向量。这意味着我们无需明确指定模板中每个token的具体内容,只需在语义空间中用向量表示即可。
这样,不同的任务、数据可以自适应地在语义空间中寻找若干合适的向量,来代表模板中的每一个词,相较于显式的token,这类token称为 伪标记(Pseudo Token) 。下面给出基于连续提示的模板定义:
在分类任务中,给定输入句子x,连续提示模板可表示为T=[x],[v1],[v2],...,[vn][MASK]。其中[v1]等伪标记是抽象token,本质上代表可训练的向量。
简言之,Soft Prompt方法将模板转化为可训练参数,使不同样本能在连续向量空间中找到合适的伪标记,从而提升模型泛化能力。该方法仅需引入少量可训练参数(如prompt token对应的词向量表征),而保持预训练模型参数不变。
当前Prompt-Tuning领域基于连续提示的代表性实现方法主要包括以下三种,分别对应三篇重要论文:
**Prompt Tuning方法:**源自论文《The Power of Scale for Parameter-Efficient Prompt Tuning》
**P-tuning方法:**由论文《GPT Understands, Too》提出
**PPT方法:**论文《PPT: Pre-trained Prompt Tuning for Few-shot Learning》中介绍的方法
6.3.2 Prompt Tuning(NLG任务)
Prompt Tuning(基于T5模型)为每个输入文本添加可学习的固定前缀提示。这些提示由神经网络参数表示,在下游任务微调时进行优化调整,同时保持预训练大模型参数固定不变。
形式化的描述如下:
给定 n 个 tokens(记为 x₁,...,xₙ),通过预训练模型的 embedding table 可将其表示为向量矩阵 Xₑ ∈ ℝⁿˣᵉ,其中 e 为向量维度(由预训练模型配置决定,如 BERT-base 为 768)。连续模板中的每个伪标记 vᵢ 既可视为参数也可视为 token,因此可通过另一个 embedding table 将 p 个伪标记表示为向量矩阵 Pₑ ∈ ℝᵖˣᵉ。将文本和 Prompt 拼接后得到新输入 [Pₑ:Xₑ] ∈ ℝ⁽ᵖ⁺ⁿ⁾ˣᵉ,该输入将被送入预训练模型进行训练。需要注意的是,仅 Prompt 对应的向量表征参数 Pₑ ∈ ℝᵖˣᵉ 会在训练过程中更新。
每个伪标记的初始化可以有下列几种情况:
- 最简单的是随机初始化:即随机初始化一个面向所有伪标记的embedding table,可采用正态分布或者均匀分布等;
- 每个token使用预训练模型已有的embedding table进行初始化,此时,每一个伪标记先随机指定词表中的一个词,并取对应词的embedding作为这个伪标记的初始化;
- 在分类任务上,使用label word(verbalizer)对应的embedding作为初始化,可以有效限制模型输出的是预设的输出类对应的word。
因此,在训练过程中,每个伪标记对应的参数都可以得到训练,对于不同的输入句子 ,这些伪标记对应的embedding也各不相同,达到了预期的目的。
示例代码:
python
# 假设输入文本:x1, x2, ..., xn
# 预训练模型 embedding 维度 = e
# prompt token 数量 = p
# 1. 定义预训练模型 (冻结参数)
class PretrainedModel:
def __init__(self, embedding_dim):
self.embedding_table = load_pretrained_embedding() # 词表 -> 向量
self.encoder = load_pretrained_encoder() # BERT, GPT 等 Transformer
freeze(self.embedding_table) # 冻结
freeze(self.encoder) # 冻结
def forward(self, input_embeddings):
# 输入拼接后的向量 (p+n, e),送入模型
return self.encoder(input_embeddings)
# 2. 定义可训练的 Prompt 参数 (虚拟 token embedding)
class PromptEmbedding:
def __init__(self, num_prompt_tokens, embedding_dim):
# 随机初始化 p 个 prompt 向量
self.prompt_table = Parameter(shape=(num_prompt_tokens, embedding_dim))
def forward(self):
return self.prompt_table # (p, e)
# 3. 构造输入
# prompt_module参数为 PromptEmbedding 的对象
def build_input(text_tokens, model, prompt_module):
# (n, e) 文本 token 的嵌入表示
X_e = model.embedding_table(text_tokens)
# (p, e) prompt token 的嵌入表示
P_e = prompt_module.forward()
# 拼接得到新的输入 (p+n, e)
input_embeddings = concat(P_e, X_e)
return input_embeddings
# 4. 训练流程
def train(prompt_module, model, dataloader, loss_fn, optimizer):
for batch in dataloader:
text_tokens, labels = batch
# 构建输入
input_embeddings = build_input(text_tokens, model, prompt_module)
# 前向传播
outputs = model.forward(input_embeddings)
# 计算任务损失 (例如分类 / MLM)
loss = loss_fn(outputs, labels)
# 反向传播 (只更新 prompt 参数)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# ===============================
# 使用示例
# ===============================
embedding_dim = 768
p = 10 # prompt token 数
# 加载预训练模型 (冻结参数)
model = PretrainedModel(embedding_dim)
# 初始化 Prompt 模块 (可训练参数)
prompt_module = PromptEmbedding(num_prompt_tokens=p, embedding_dim=embedding_dim)
# 优化器 (只更新 prompt 参数)
optimizer = Adam(params=prompt_module.parameters(), lr=1e-3)
# 开始训练
train(prompt_module, model, dataloader, loss_fn, optimizer)
# - 文本 token -> embedding (冻结)
# - prompt token -> embedding (可训练)
# - 拼接后输入模型
# - 反向传播时只更新 prompt embedding
和Lora的区别与联系
| 特性 | Prompt Tuning (你代码里的) | LoRA (Low-Rank Adaptation) |
|---|---|---|
| 修改位置 | 输入层(在 Embedding 前面加料)。 | 模型内部层(在 Transformer 的线性层旁边加旁路)。 |
| 原理 | 寻找一组能引导模型的"虚拟指令"。 | 寻找模型参数矩阵的"增量更新"。 |
| 参数量 | 极小(通常只有几千到几万个参数)。 | 较小(比全量微调小得多,但通常比 Prompt Tuning 大)。 |
| 对序列长度影响 | 占用输入长度。虚拟 Token 会挤占原始文本的长度限制。 | 不占用输入长度。对输入序列完全无感。 |
| 训练难度 | 相对较难收敛,对初始化敏感。 | 非常稳定,效果通常优于 Prompt Tuning。 |
Prompt Tuning 的特点分析:
优点:
- 开创了大模型微调的新范式
- 在大规模参数模型中,通过固定主模型参数并添加少量适配参数即可实现下游任务适配,其性能表现与全参数微调相当
缺点:
- 在小样本学习场景下表现欠佳
- 存在收敛速度较慢的问题
- 参数调优过程较为复杂(需调整 prompt 长度、初始化方式、学习率等多个超参数)
6.3.3 P-tuning(NLU任务)
P-tuning 是一种面向自然语言理解(NLU)任务的连续提示方法,由谷歌团队在2021年提出。其方法示意图如下所示(图中Pi等同于前文提到的vi,代表伪标记)。
该方法的核心创新在于:通过小型可训练模块生成"连续提示向量"并嵌入到原始输入表征中,使得冻结状态的预训练模型能够适配下游任务。这种设计实现了仅需优化提示编码器(或提示向量)的高效微调,显著降低了计算成本。
P-Tuning方法的四个关键技术点:
**建模伪标记依赖关系:**P-Tuning v1通过将Prompt转换为可学习的Embedding层来处理伪标记间的依赖关系。该方法认为[P1]和[P2]存在先后顺序,因此引入由Bi-LSTM和前馈神经网络组成的Prompt Encoder对伪标记进行编码,再将编码结果与其他向量拼接后输入LLM。
**上下文词初始化:**为避免全伪标记模板在训练时难以优化语义对齐,P-Tuning会选取与当前句子语义相关的代表性词语(如示例中的"capital")作为部分伪标记的初始值。
**参数重编码机制:**在实现层面,P-Tuning先通过Prompt Encoder表征伪标记,然后将新表征直接覆盖到原始embedding table上。这种设计使得Prompt Encoder仅在训练阶段使用,推理时则无需调用。
**混合提示策略:**该方法支持将连续提示与离散token混合使用,典型模式如[x1][Britain][x2][mask]的组合形式。
python
# 要点:P-Tuning v1 使用可训练的连续 prompt embedding(软提示),可选地通过一个小的 prompt-encoder(如 LSTM/MLP)对其进行编码;
# 通常冻结预训练模型参数,仅训练 prompt 相关参数,从而实现参数高效微调。
import torch
import torch.nn as nn
import torch.optim as optim
# ---------------------------
# 超参数 / 配置
# ---------------------------
PROMPT_LENGTH = 30 # 软 prompt 的 token 数量
EMBED_DIM = 768 # 与预训练模型的 embedding 维度保持一致
USE_PROMPT_ENCODER = True # 是否使用 prompt-encoder(P-Tuning v1 常见配置)
LR = 1e-3
DEVICE = "cuda"
# ---------------------------
# 软提示模块
# ---------------------------
class SoftPrompt(nn.Module):
"""
保存一组连续的 prompt embedding(可训练)。
可选地通过一个小的 encoder(例如双向 LSTM 或 MLP)对这些 embedding 建模,
以捕捉 prompt token 之间的依赖关系(这是 P-Tuning v1 的做法之一)。
"""
def __init__(self, prompt_length, embed_dim, use_encoder=True):
super().__init__()
self.prompt_length = prompt_length
# 原始的 prompt embedding(可训练参数)
self.prompt_embeddings = nn.Parameter(torch.randn(prompt_length, embed_dim) * 0.02)
self.use_encoder = use_encoder
if use_encoder:
# 示例:一个小的双向 LSTM 作为 prompt-encoder(也可以替换为 MLP)
self.encoder = nn.LSTM(input_size=embed_dim, hidden_size=embed_dim//2,
num_layers=1, batch_first=True, bidirectional=True)
# 如果 encoder 的输出维度不同,投影回 embed_dim
self.proj = nn.Linear(embed_dim, embed_dim)
def forward(self, batch_size):
# 将 prompt_embeddings 扩展到 batch 维度
p = self.prompt_embeddings.unsqueeze(0).expand(batch_size, -1, -1) # (B, P, D)
if self.use_encoder:
out, _ = self.encoder(p) # (B, P, 2*H) 如果是双向 LSTM
out = self.proj(out) # (B, P, D)
return out
else:
return p # (B, P, D)
# ---------------------------
# P-Tuning 高层封装模型
# ---------------------------
class PTuningModel(nn.Module):
def __init__(self, base_model, tokenizer, soft_prompt: SoftPrompt,
prompt_position="prepend"):
"""
base_model: 已加载的预训练 language model(例如 GPT-2 / BERT 等)
tokenizer: 用于分词的 tokenizer(示例中仅用于演示)
soft_prompt: SoftPrompt 实例
prompt_position: 'prepend'(在输入前拼接)或其它插入位置
"""
super().__init__()
self.base = base_model
self.tokenizer = tokenizer
self.soft_prompt = soft_prompt
self.prompt_position = prompt_position
# 通常在 P-Tuning v1 中冻结预训练模型参数,仅训练 prompt 相关的参数
for param in self.base.parameters():
param.requires_grad = False
# 如果是分类任务,可以在这里加一个小的 task head(示例可选)
# self.classifier = nn.Linear(EMBED_DIM, num_labels) # 可选
def forward(self, input_ids, attention_mask=None, labels=None):
"""
input_ids: (B, L)
返回 logits 和(可选)loss
"""
batch_size = input_ids.size(0)
# 1) 获取基础模型的 token embedding(不同模型接口略有差别)
# 假设 base 模型有 get_input_embeddings() 方法
token_emb = self.base.get_input_embeddings()(input_ids) # (B, L, D)
# 2) 获取软提示 embedding: (B, P, D)
prompt_emb = self.soft_prompt(batch_size)
# 3) 根据位置拼接 prompt(此处演示 prepend)
if self.prompt_position == "prepend":
# 新序列为:[P soft tokens] + [L 原始 tokens]
new_emb = torch.cat([prompt_emb, token_emb], dim=1) # (B, P+L, D)
# 如果提供了 attention_mask,需要相应扩展
if attention_mask is not None:
prompt_mask = torch.ones(batch_size, prompt_emb.size(1), device=input_ids.device)
new_attention_mask = torch.cat([prompt_mask, attention_mask], dim=1)
else:
new_attention_mask = None
# 注意:有些模型需要调整位置编码或为 prompt 指定特定的位置索引,
# 具体实现依赖于 base 模型的细节。
else:
raise NotImplementedError("此伪代码仅展示 'prepend' 的情况")
# 4) 将 embeddings 直接传入基础模型(若模型支持 inputs_embeds 参数)
outputs = self.base(inputs_embeds=new_emb, attention_mask=new_attention_mask, return_dict=True)
logits = outputs.logits # 具体 shape 取决于模型与任务
# 5) 若提供 labels 则计算损失(任务相关,例如语言建模或分类)
loss = None
if labels is not None:
# 以语言建模为例:需要对 logits/labels 进行 shift
shift_logits = logits[..., :-1, :].contiguous()
shift_labels = labels[..., 1:].contiguous()
loss_fct = nn.CrossEntropyLoss()
loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
return {"logits": logits, "loss": loss}
# ---------------------------
# 训练主循环
# ---------------------------
def train_ptuning(model: PTuningModel, train_dataloader, num_epochs=3):
# 仅优化需要梯度的参数(即 soft prompt、encoder、以及可选的 task head)
prompt_params = [p for p in model.parameters() if p.requires_grad]
optimizer = optim.AdamW(prompt_params, lr=LR)
model.to(DEVICE)
model.train() # base 模型虽然被冻结,但 soft prompt 模块可能需处于训练模式
for epoch in range(num_epochs):
for batch in train_dataloader:
input_ids = batch["input_ids"].to(DEVICE) # (B, L)
attention_mask = batch.get("attention_mask", None)
labels = batch.get("labels", None)
out = model(input_ids, attention_mask=attention_mask, labels=labels)
loss = out["loss"]
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f"Epoch {epoch} loss: {loss.item():.4f}")
# 训练结束后通常只保存 prompt 参数(体积很小)
torch.save(model.soft_prompt.state_dict(), "soft_prompt.pt")
# ---------------------------
# 推理:加载 prompt 并运行
# ---------------------------
def load_prompt_and_infer(base_model, tokenizer, prompt_path, input_text):
soft_prompt = SoftPrompt(PROMPT_LENGTH, EMBED_DIM, use_encoder=USE_PROMPT_ENCODER)
soft_prompt.load_state_dict(torch.load(prompt_path))
pt_model = PTuningModel(base_model, tokenizer, soft_prompt)
pt_model.to(DEVICE)
pt_model.eval()
# 将输入文本 tokenize -> input_ids
tokens = tokenizer(input_text, return_tensors="pt").to(DEVICE)
out = pt_model(tokens["input_ids"], attention_mask=tokens.get("attention_mask", None))
# 根据任务把 logits 解码为文本或标签
# ...
return out
P-tuning的特点:
- 优点:
- 引入了一个 LSTM +MLP模块对 soft prompt 进行建模,能捕捉 token 之间的顺序和语义关系
- 改进了离散 prompt的不稳定性问题,收敛速度更快
- 缺点
- 仅放在输入层时,对模型内部深层表征的影响有限,面对一些需要深层表示调整的 NLU/序列标注任务表现并不稳定或不足
- 在中小模型(100M--1B)表现较差
P-Tuning v1 vs. Prompt Tuning
| 特性 | Prompt Tuning (基础版) | P-Tuning v1 |
|---|---|---|
| Prompt 构造 | 直接使用 nn.Parameter。 |
原始参数 + Encoder (LSTM/MLP)。 |
| 收敛速度 | 较慢。 | 较快(因为 Encoder 提供了更好的搜索空间)。 |
| 适用场景 | 主要是大参数模型 (10B+)。 | 在 BERT/GPT-2 等中型模型上效果也很好。 |
| 位置灵活性 | 通常只能在开头 (Prepend)。 | 可以在开头、中间或任意位置插入(通过模板定义)。 |
P-Tuning v2是P-Tuning的升级版本,主要解决了P-Tuning v1在小模型上表现不佳的问题。
P-Tuning v2 的做法是: 在 Transformer 的每一层都插入可训练的虚拟 Token 向量(即作为 Prefix Tuning 的一种优化实现)。
• v1 (Input Prompt):只在输入端拼接,影响范围有限。
• v2 (Deep Prompt):在每一层 Self-Attention 的 (Key)和 (Value)矩阵前都拼接可训练的向量。
| 特性 | P-Tuning v1 | P-Tuning v2 |
|---|---|---|
| 插入层数 | 仅第一层(Input Layer)。 | 所有层(Deep Prompting)。 |
| 可训练参数量 | 极少(约 0.01%)。 | 较多(约 0.1% - 3%),但依然远小于全量微调。 |
| 重参数化 | 使用 LSTM/MLP (Encoder)。 | 不再需要。v2 发现层数深了以后,直接随机初始化效果就很稳。 |
| 适用任务 | 主要是分类、选择。 | 通用。在序列标注(如 NER)、问答等复杂任务上效果显著提升。 |
具体细节可参阅论文《P-Tuning v2: Prompt Tuning Can Be Comparable to Fine-tuning Universally Across Scales and Tasks》
该方法的创新点在于:在模型各层都引入可训练的连续prompts,使Prompt Tuning能够适应不同规模的预训练模型,并在各类下游任务中达到与Fine-tuning相当的效果。
6.3.4 PPT(Pre-trained Prompt Tuning)
Prompt-Tuning通常适用于低资源场景,但由于连续模板采用随机初始化会引入新参数,仅凭少量样本可能难以有效优化这些模板。为此,一个可行的解决方案是对这些连续模板进行预训练。PPT方法的核心思路是:首先在大规模无标注预训练语料上对连续提示进行预训练(注意预训练期间保持预训练模型参数固定,仅优化soft prompt),然后将预训练好的提示加载到下游任务的PLM中进行微调。如下图所示(图中P代表连续提示模板,<X>表示mask token):

PPT的特点:
- 优点:
- 预训练soft-prompt带来了 小样本学习场景上的显著提升
- 缓解了prompt-tuning收敛慢的问题
- 缺点
- 高度依赖于源任务集的覆盖度与多样性(一旦目标任务与预训练时用到的源任务在分布、格式或语义上差异较大,通用提示 𝑃就难以提供有效的初始引导,导致下游微调效果大幅下降)
目录
[1 NLP任务的四种范式](#1 NLP任务的四种范式)
[2 Fine-Tuning(微调)](#2 Fine-Tuning(微调))
[3 Prompt-Tuning(提示微调)](#3 Prompt-Tuning(提示微调))
[3.1 什么是Prompt?](#3.1 什么是Prompt?)
[3.2 Prompt-Tuing定义](#3.2 Prompt-Tuing定义)
[4 Prompt-Tuning技术发展历程](#4 Prompt-Tuning技术发展历程)
[5 面向超大规模语言模型的Prompt-Tuning](#5 面向超大规模语言模型的Prompt-Tuning)
[5.1 In-Context Learning(上下文学习)](#5.1 In-Context Learning(上下文学习))
[one-shot learning](#one-shot learning)
[few-shot learning](#few-shot learning)
[5.2 Instruction-Tuning(指令微调)](#5.2 Instruction-Tuning(指令微调))
[5.3 Chain-of-Thought(思维链)](#5.3 Chain-of-Thought(思维链))
[6 面向小规模语言模型的Prompt-Tuning](#6 面向小规模语言模型的Prompt-Tuning)
[6.1 Prompt-Tuning的鼻祖---PET模型](#6.1 Prompt-Tuning的鼻祖—PET模型)
[6.2 Prompt-Oriented Fine-Tuning](#6.2 Prompt-Oriented Fine-Tuning)
[6.3 Soft Prompt及微调方法](#6.3 Soft Prompt及微调方法)
[6.3.1 连续提示模板](#6.3.1 连续提示模板)
[6.3.2 Prompt Tuning(NLG任务)](#6.3.2 Prompt Tuning(NLG任务))
[6.3.3 P-tuning(NLU任务)](#6.3.3 P-tuning(NLU任务))
[6.3.4 PPT(Pre-trained Prompt Tuning)](#6.3.4 PPT(Pre-trained Prompt Tuning))