https://arxiv.org/pdf/2001.07676.pdf
如上图所示,是一个文本情绪分类的任务:判别“Best pizza ever!” 是正面情绪还是负面情绪?
文本提出的创新思路是:
1、可以先对文本进行完心填空任务的构造,如变成“Best pizza ever! It was __”。该方案可以进行零样本学习,利用MLM(即Masked Language Model)预训练的掩码语言模型模型(如BERT),预测得到缺失的token预测为“great”或“bad”的概率。即预训练模型能在预训练数据上学习到标签“great”或“bad”语义,以及其填入文中的语义合理性。
2、其实在第1步就能进行文本分类了,但是作者认为还可以进一步训练出分类器。我们利用集成学习的方式,多个预训练模型得到集成的soft-label
3、利用无监督文本数据和soft-label train一个文本分类模型
总结思考
PET的思想还可以进行有监督的少样本学习:对于有标签的数据,构造样本的时候,我们先给每个句子补上Pattern,除了Pattern自带的Mask位置之外,再还随机Mask其他一部分,以增强对模型的正则。
实操
接下来,我们使用 OpenPrompt 架构来进行实操,下面OpenPrompt 的整体架构图:
请注意,不是所有模块都必须使用 Prompt Learning。例如,在生成任务中,学习过程中没有进行 verbalizers 的学习。 PromptTrainer 是一个控制数据流和训练过程的控制器,具有一些独特的属性,用户也可以以常规方式实现训练过程。
情感分类
步骤 1. 定义任务
第一步是确定当前的 NLP 任务,想想你的数据是什么样的,你想从数据中得到什么!也就是说,这一步的本质是确定任务的classses和InputExample。为简单起见,我们以情绪分析为例。
代码语言:javascript复制from openprompt.data_utils import InputExample
classes = [ # 情绪分析分为两类,一类是负面的,一类是正面的
"负面",
"正面"
]
dataset = [ # 为简单起见,仅举两个例子
# text_a 是数据的输入文本,其他一些数据集在一个样本中可能有多个输入句子。
InputExample(
guid=0,
text_a="今天阳光灿烂。",
label=1
),
InputExample(
guid=1,
text_a="最近天天下雨。",
label=0
), ]
步骤 2. 获取 PLM
代码语言:javascript复制from openprompt.plms import load_plm
plm, tokenizer, model_config, WrapperClass = load_plm("bert", "uer/chinese_roberta_L-4_H-256")
步骤 3. 定义Template
Template是原始输入文本的修饰符,也是提示学习中最重要的模块之一。
代码语言:javascript复制from openprompt.prompts import ManualTemplate
promptTemplate = ManualTemplate(
text='天气{"mask"}:{"placeholder":"text_a"}',
tokenizer=tokenizer,
)
其中<text_a>将替换为text_ain InputExample,并且将用于预测标签词。
步骤 4. 定义 Verbalizer
Verbalizer是提示学习中另一个重要的(但不是必要的,例如在生成中),它将原始标签投影到一组标签词。
代码语言:javascript复制from openprompt.prompts import ManualVerbalizer
promptVerbalizer = ManualVerbalizer(
classes=classes,
label_words={
"负面": ["不好", "不佳"],
"正面": ["不错", "真好"],
},
tokenizer=tokenizer,
)
步骤 5. 构建 PromptModel
给定任务,现在我们有一个PLM、一个Template和一个Verbalizer,我们将它们组合成一个PromptModel。
代码语言:javascript复制from openprompt import PromptForClassification
prompt_model = PromptForClassification(
template=promptTemplate,
plm=plm,
verbalizer=promptVerbalizer,
)
请注意,尽管此示例简单地结合了三个模块,但实际上可以在它们之间定义一些复杂的交互。
步骤 6. 定义 DataLoader
PromptDataLoader基本上是 pytorch Dataloader 的 prompt 版本,它还包括 一个Tokenizer和 一个Template:
代码语言:javascript复制from openprompt import PromptDataLoader
data_loader = PromptDataLoader(
dataset=dataset,
tokenizer=tokenizer,
template=promptTemplate,
tokenizer_wrapper_class=WrapperClass,
)
步骤 7. 训练和推理
代码语言:javascript复制import torch
from transformers import AdamW
no_decay = ['bias', 'LayerNorm.weight']
# 对偏差和 LayerNorm 参数设置不衰减总是一个好实践经验
optimizer_grouped_parameters = [
{'params': [p for n, p in prompt_model.named_parameters() if not any(nd in n for nd in no_decay)],
'weight_decay': 0.01},
{'params': [p for n, p in prompt_model.named_parameters() if any(nd in n for nd in no_decay)], 'weight_decay': 0.0}
]
optimizer = AdamW(optimizer_grouped_parameters, lr=1e-4)
loss_func = torch.nn.CrossEntropyLoss()
for epoch in range(10):
tot_loss = 0
for step, inputs in enumerate(data_loader):
logits = prompt_model(inputs)
labels = inputs['label']
loss = loss_func(logits, labels)
loss.backward()
tot_loss = loss.item()
optimizer.step()
optimizer.zero_grad()
if step % 100 == 1:
print("Epoch {}, average loss: {}".format(epoch, tot_loss / (step 1)), flush=True)
# 验证
validation_dataloader = data_loader
allpreds = []
alllabels = []
for step, inputs in enumerate(validation_dataloader):
logits = prompt_model(inputs)
print(logits)
labels = inputs['label']
alllabels.extend(labels.cpu().tolist())
allpreds.extend(torch.argmax(logits, dim=-1).cpu().tolist())
acc = sum([int(i == j) for i, j in zip(allpreds, alllabels)]) / len(allpreds)
print(acc)
实验发现以上的样例,不经过训练,0样本直接验证,也能进行准确的预测出结果。