Prompt Learning——basic & 【EACL 2021】PET

2022-09-30 14:27:42 浏览数 (2)

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样本直接验证,也能进行准确的预测出结果

0 人点赞