当前位置:   article > 正文

带你快速使用PaddleNLP构建基于Prompt实体情感分类_prompt_model情感分类数据

prompt_model情感分类数据

转自AI Studio,原文链接:带你快速使用PaddleNLP构建基于Prompt实体情感分类 - 飞桨AI Studio

背景简介

由于个人水平有限,欢迎各位大佬指正不足之处!欢迎 Fork一键运行~~

主题来源:2022搜狐校园 情感分析&推荐排序 算法大赛中的NLP赛道。

任务目的:完成实体对象的情感极性分类,即文本中需要分析的实体对象及每个对象的情感极性(2代表极正向,1代表正向,0代表中立,-1代表负向,-2代表极负向)。

解决方案基于飞桨PaddleNLP的Ernie模型实现PET的Prompt范式,将实体对象的标签映射为自然语言(模板设计)作为[MASK]进行预测。

线上成绩:在采样数据上训练2个Epoch,线上成绩达到0.66几,在未调参的情况下,相比使用预训练模型的Finetune效果较好,但prompt对模板设计比较敏感,一字之差便产生较大差距。

(在第一版的模板中线上只达到0.4几,调整模板后便上升到0.6几)


提分点:1.采用R-Drop训练策略,对抗扰动等;2.设计有效的模板输入(这点比较靠经验,需要不断实践尝试!!!);3.将标签映射为合适的字、词等(实践中很关键)。

接下来从以下几个方面详细介绍快速使用飞桨PaddleNLP预训练模型构建Prompt的模板输入、模型训练、预测全流程:

  • 代码结构
  • 环境配置
  • 数据读取定义
  • Prompt模板设计
  • 模型搭建
  • 训练损失定义
  • 模型训练
  • 模型预测

代码结构

  1. |—— pet_prompt
  2. |—— label_normalized/template.json # 自定义标签映射
  3. |—— train.py # PET 策略的训练、评估主脚本
  4. |—— data.py # PET 策略针对sohu数据集的任务转换逻辑,以及明文 -> 训练数据的转换
  5. |—— model.py # PET 的网络结构
  6. |—— evaluate.py # 评估函数
  7. |—— predict.py # 数据集进行预测

环境配置

GPU环境

  • paddlepaddle-gpu == 2.2.2

  • paddlenlp == 2.2.6

In [ ]

  1. # 在Aistudio项目中运行时,启动项目后需要更新paddlenlp
  2. !pip3 install --upgrade paddlenlp

数据读取定义

  1. // 自定义数据读取方式: 输出文本内容、实体对象及标签
  2. def read_data(data, is_test=False):
  3. for line in data:
  4. if is_test:
  5. entity = list(line['entity'].keys())
  6. for e in entity:
  7. yield {'text':e, 'text_pair':line['content']}
  8. else:
  9. entity = list(line['entity'].keys())
  10. random.shuffle(entity)
  11. for e in entity:
  12. yield {'text':e, 'text_pair':line['content'], 'label':str(line['entity'][e])}
  1. // 输入DataLoader
  2. def create_dataloader(dataset_origin,
  3. mode='train',
  4. batch_size=1,
  5. batchify_fn=None,
  6. trans_fn=None):
  7. if trans_fn:
  8. dataset = dataset_origin.map(trans_fn)
  9. shuffle = True if mode == 'train' else False
  10. if mode == 'train':
  11. batch_sampler = paddle.io.DistributedBatchSampler(
  12. dataset, batch_size=batch_size, shuffle=shuffle)
  13. else:
  14. batch_sampler = paddle.io.BatchSampler(
  15. dataset, batch_size=batch_size, shuffle=shuffle)
  16. return paddle.io.DataLoader(
  17. dataset=dataset,
  18. batch_sampler=batch_sampler,
  19. collate_fn=batchify_fn,
  20. return_list=True)

Prompt模板设计

PET (Exploiting Cloze Questions for Few Shot Text Classification and Natural Language Inference) 提出将输入示例转换为完形填空式短语,以帮助语言模型理解给定的任务。

Prompt关键点就是构造成完型填空式的输入形式,将待预测的标签(字、词、短语、句子)作为[MASK]掩盖,发挥预训练模型的优势进行预测。根据任务形式构建标签映射。

本项目实体情感分析举例:

  • 训练样本形式: {"id": 3, "content": "选择Tips:露背连衣裙露肤元素是女性穿裙必备,只不过在选择这些性感元素的时候,尺寸一定要拿捏得当,避免出现艳俗感,当然也可以像高圆圆这样,背部展现,大面积的露肤不会出现一丝丝的瑕疵,同时在黑色的展现下,肤白貌美不说,还带着满满的性感范。声明:文字原创,图片来自网络,如有侵权,请联系我们删除,谢谢。如果你喜欢本篇文字,欢迎分享转发。", "entity": {"高圆圆": 1}}

其中 content是文本内容,entity包含多个实体对象及对应的标签(2代表极正向,1代表正向,0代表中立,-1代表负向,-2代表极负向),这条样例中,实体“高圆圆”的标签就是 1。这是典

型的情感实体分类任务,如何根据分类标签构建prompt模板学习呢?在构造模板输入之前,就需要将数字标签映射为自然语言,语言模型才能学习理解(换个角度理解,如果一句话连人类理解都

含糊不清,那模型当然非常吃力,不要把模型当作很智能的,数据才是王道!)。

  • 标签映射

这里将标签映射为一个字的形式,创建文件template.json放在文件夹label_normalized下:

  1. // label_normalized/template.json
  2. {
  3. "-2":"坏",
  4. "-1":"差",
  5. "0":"平",
  6. "1":"行",
  7. "2":"好",
  8. }
  • 定义模板转化函数

模板1:u'这句话中,{}是'.format(example["text"]) + example["text_pair"],概括为: 这句话中,[entity]是[MASK],[x]。

模板2:u'综合来看,{}是的,'.format(example["text"]) + example["text_pair"],概括为: 综合来看,[entity]是[MASK],[x]。

其中,[MASK]位置就是映射的标签,如:实体“高圆圆”标签为1,可输入为模板1: 这句话中,高圆圆是[MASK](标签映射:行),content。

  1. def transform_sohu(example,
  2. label_normalize_dict=None,
  3. is_test=False,
  4. pattern_id=0):
  5. '''
  6. 定义模板转换函数
  7. pattern_id: 定义不同的模板设计
  8. '''
  9. if is_test:
  10. example["label_length"] = 1
  11. if pattern_id == 0:
  12. example["sentence1"] = u'这句话中,{}是<unk>'.format(example["text"]) + example["text_pair"]
  13. elif pattern_id == 1:
  14. example["sentence1"] = u'综合来看,{}是<unk>的,'.format(example["text"]) + example["text_pair"]
  15. return example
  16. else:
  17. origin_label = example["label"]
  18. # Normalize some of the labels, eg. English -> Chinese
  19. example['text_label'] = label_normalize_dict[origin_label]
  20. if pattern_id == 0:
  21. example["sentence1"] = u'这句话中,{}是<unk>'.format(example["text"]) + example["text_pair"]
  22. elif pattern_id == 1:
  23. example["sentence1"] = u'综合来看,{}是<unk>的,'.format(example["text"]) + example["text_pair"]
  24. del example["text"]
  25. del example["text_pair"]
  26. del example["label"]
  27. return example

如下是将MASK位置转为 token_id 连接到文本token中,首先将[MASK]作为待预测标签的Token插入到模板输入的token中,然后计算MASK的位置。详细的代码请看data.py中,

下面是关键的核心代码:

  1. # Replace <unk> with '[MASK]'
  2. # Step1: gen mask ids
  3. if is_test:
  4. label_length = example["label_length"]
  5. else:
  6. text_label = example["text_label"]
  7. label_length = len(text_label)
  8. mask_tokens = ["[MASK]"] * label_length
  9. mask_ids = tokenizer.convert_tokens_to_ids(mask_tokens)
  10. sentence1 = example["sentence1"]
  11. if "<unk>" in sentence1:
  12. start_mask_position = sentence1.index("<unk>") + 1
  13. sentence1 = sentence1.replace("<unk>", "")
  14. encoded_inputs = tokenizer(text=sentence1, max_seq_len=max_seq_length)
  15. src_ids = encoded_inputs["input_ids"]
  16. token_type_ids = encoded_inputs["token_type_ids"]
  17. # Step2: Insert "[MASK]" to src_ids based on start_mask_position
  18. src_ids = src_ids[0:start_mask_position] + mask_ids + src_ids[
  19. start_mask_position:]
  20. token_type_ids = token_type_ids[0:start_mask_position] + [0] * len(
  21. mask_ids) + token_type_ids[start_mask_position:]
  22. # calculate mask_positions
  23. mask_positions = [
  24. index + start_mask_position for index in range(label_length)
  25. ]

模型搭建

定义好模板输入后,数据输入就完成了,整个项目完成了一大半!接下来就是搭建模型,详细的代码详见model.py,基于飞桨PaddleNLP的Ernie模型实现PET的Prompt范式,只需完成数据转换就能训练。

下面是关键的核心代码:

  1. // 通过 ErniePretrainingHeads 输出文本中的MASK预测输出概率和下一个句子概率,只需返回[MASK]预测的prediction_scores;
  2. class ErnieForPretraining(ErniePretrainedModel):
  3. def __init__(self, ernie):
  4. super(ErnieForPretraining, self).__init__()
  5. self.ernie = ernie # 可以选择不同的预训练模型得到输出
  6. weight_attr = paddle.ParamAttr(
  7. initializer=nn.initializer.TruncatedNormal(
  8. mean=0.0, std=self.ernie.initializer_range))
  9. self.cls = ErniePretrainingHeads(
  10. self.ernie.config["hidden_size"],
  11. self.ernie.config["vocab_size"],
  12. self.ernie.config["hidden_act"],
  13. embedding_weights=self.ernie.embeddings.word_embeddings.weight,
  14. weight_attr=weight_attr,)
  15. self.apply(self.init_weights)
  16. def forward(self,
  17. input_ids,
  18. token_type_ids=None,
  19. position_ids=None,
  20. attention_mask=None,
  21. masked_positions=None):
  22. with paddle.static.amp.fp16_guard():
  23. outputs = self.ernie(
  24. input_ids,
  25. token_type_ids=token_type_ids,
  26. position_ids=position_ids,
  27. attention_mask=attention_mask)
  28. sequence_output, pooled_output = outputs[:2]
  29. # max_len = input_ids.shape[1]
  30. new_masked_positions = masked_positions
  31. # prediction_scores的输出维度是[batch_size, vocab_size],vocab_size根据选择的预训练模型而不同,如ernie是18000,输出根据每个词的最大概率进行输出
  32. prediction_scores, seq_relationship_score = self.cls(
  33. sequence_output, pooled_output, new_masked_positions)
  34. return prediction_scores

整个模型流程比较简单,关键是ErniePretrainingHeads的构建,可以根据不同的预训练模型自定义PretrainingHeads模型,感兴趣的可以看源代码:Ernie

  1. class ErnieLMPredictionHead(nn.Layer):
  2. r"""
  3. Ernie Model with a `language modeling` head on top.
  4. """
  5. def __init__(
  6. self,
  7. hidden_size,
  8. vocab_size,
  9. activation,
  10. embedding_weights=None,
  11. weight_attr=None, ):
  12. super(ErnieLMPredictionHead, self).__init__()
  13. self.transform = nn.Linear(
  14. hidden_size, hidden_size, weight_attr=weight_attr)
  15. self.activation = getattr(nn.functional, activation)
  16. self.layer_norm = nn.LayerNorm(hidden_size)
  17. self.decoder_weight = self.create_parameter(
  18. shape=[vocab_size, hidden_size],
  19. dtype=self.transform.weight.dtype,
  20. attr=weight_attr,
  21. is_bias=False) if embedding_weights is None else embedding_weights
  22. self.decoder_bias = self.create_parameter(
  23. shape=[vocab_size], dtype=self.decoder_weight.dtype, is_bias=True)
  24. def forward(self, hidden_states, masked_positions=None):
  25. if masked_positions is not None:
  26. hidden_states = paddle.reshape(hidden_states,
  27. [-1, hidden_states.shape[-1]])
  28. hidden_states = paddle.tensor.gather(hidden_states,
  29. masked_positions)
  30. # gather masked tokens might be more quick
  31. hidden_states = self.transform(hidden_states)
  32. hidden_states = self.activation(hidden_states)
  33. hidden_states = self.layer_norm(hidden_states)
  34. hidden_states = paddle.tensor.matmul(
  35. hidden_states, self.decoder_weight,
  36. transpose_y=True) + self.decoder_bias
  37. return hidden_states
  38. class ErniePretrainingHeads(nn.Layer):
  39. def __init__(
  40. self,
  41. hidden_size,
  42. vocab_size,
  43. activation,
  44. embedding_weights=None,
  45. weight_attr=None, ):
  46. super(ErniePretrainingHeads, self).__init__()
  47. self.predictions = ErnieLMPredictionHead(
  48. hidden_size, vocab_size, activation, embedding_weights, weight_attr)
  49. self.seq_relationship = nn.Linear(
  50. hidden_size, 2, weight_attr=weight_attr)
  51. def forward(self, sequence_output, pooled_output, masked_positions=None):
  52. prediction_scores = self.predictions(sequence_output, masked_positions)
  53. seq_relationship_score = self.seq_relationship(pooled_output)
  54. return prediction_scores, seq_relationship_score

训练损失定义

根据[MASK]的token_ids作为标签,预测输出的概率计算分类交叉熵损失。

  1. class ErnieMLMCriterion(paddle.nn.Layer):
  2. def __init__(self):
  3. super(ErnieMLMCriterion, self).__init__()
  4. def forward(self, prediction_scores, masked_lm_labels, masked_lm_scale=1.0):
  5. masked_lm_labels = paddle.reshape(masked_lm_labels, shape=[-1, 1])
  6. with paddle.static.amp.fp16_guard():
  7. masked_lm_loss = paddle.nn.functional.softmax_with_cross_entropy(
  8. prediction_scores, masked_lm_labels, ignore_index=-1)
  9. masked_lm_loss = masked_lm_loss / masked_lm_scale
  10. return paddle.mean(masked_lm_loss)

模型训练

模型训练流程与一般的NLP的Finetune流程一样,详细查看代码train.py。

  • 加载数据

  • 构建DataLoader

  • 加载预训练模型、词典

  • 超参数设置、优化器、训练策略等

  1. // 训练命令
  2. python -u -m paddle.distributed.launch --gpus "0" \
  3. pet.py \
  4. --task_name "sohu" \
  5. --device gpu \
  6. --pattern_id 1 \
  7. --save_dir ./sohu \
  8. --index 0 \
  9. --batch_size 16 \
  10. --learning_rate 5E-5 \
  11. --epochs 2 \
  12. --max_seq_length 512 \
  13. --language_model "ernie-1.0" \
  14. --rdrop_coef 0

主要参数含义:

  • task_name: 数据集名字
  • device: 使用 cpu/gpu 进行训练
  • pattern_id 完形填空的模式
  • save_dir: 模型存储路径
  • max_seq_length: 文本的最大截断长度
  • rdrop_coef: R-Drop 策略 Loss 的权重系数,默认为 0, 若为 0 则未使用 R-Drop 策略

In [ ]

  1. %cd work/pet_prompt
  2. !ls
  3. python -u -m paddle.distributed.launch --gpus "0" \
  4. train.py \
  5. --task_name "sohu" \
  6. --device gpu \
  7. --pattern_id 1 \
  8. --save_dir ./sohu \
  9. --index 0 \
  10. --batch_size 16 \
  11. --learning_rate 5E-5 \
  12. --epochs 2 \
  13. --max_seq_length 512 \
  14. --language_model "ernie-1.0" \
  15. --rdrop_coef 0

模型预测

  1. python -u -m paddle.distributed.launch --gpus "0" predict.py \
  2. --task_name "sohu" \
  3. --device gpu \
  4. --pattern_id 1 \
  5. --init_from_ckpt "./sohu/model_12345/model_state.pdparams" \ # 保存的模型文件
  6. --output_dir "./sohu/output" \
  7. --batch_size 32 \
  8. --max_seq_length 512

In [ ]

  1. python -u -m paddle.distributed.launch --gpus "0" predict.py \
  2. --task_name "sohu" \
  3. --device gpu \
  4. --pattern_id 1 \
  5. --init_from_ckpt "./sohu/model_123456/model_state.pdparams" \ # 保存的模型文件
  6. --output_dir "./sohu/output" \
  7. --batch_size 32 \
  8. --max_seq_length 512

致谢

百度飞桨Aistudio提供的免费GPU算力!

百度飞桨https://github.com/PaddlePaddle/PaddleNLP.

欢迎访问PaddleNLP

PaddleNLP是飞桨自然语言处理开发库,具备易用的文本领域API,多场景的应用示例、和高性能分布式训练三大特点,旨在提升开发者在文本领域的开发效率,并提供丰富的NLP应用示例。

  • 易用的文本领域API 提供丰富的产业级预置任务能力Taskflow和全流程的文本领域API:支持丰富中文数据集加载的Dataset API;灵活高效地完成数据预处理的Data API;提供100+预训练模型的Transformers API等,可大幅提升NLP任务建模的效率。
  • 多场景的应用示例 覆盖从学术到产业级的NLP应用示例,涵盖NLP基础技术、NLP系统应用以及相关拓展应用。全面基于飞桨核心框架2.0全新API体系开发,为开发者提供飞桨文本领域的最佳实践。
  • 高性能分布式训练 基于飞桨核心框架领先的自动混合精度优化策略,结合分布式Fleet API,支持4D混合并行策略,可高效地完成大规模预训练模型训练。

References

[1] Schick, Timo, and Hinrich Schütze. “Exploiting Cloze Questions for Few Shot Text Classification and Natural Language Inference.” ArXiv:2001.07676 [Cs], January 25, 2021.http://arxiv.org/abs/2001.07676.

请点击此处查看本环境基本用法.
Please click here for more detailed instructions.

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/376719?site
推荐阅读
相关标签
  

闽ICP备14008679号