BERT+P-Tuning方式数据处理介绍
4.6 BERT+P-Tuning方式数据预处理
Section titled “4.6 BERT+P-Tuning方式数据预处理”基于BERT+P-Tuning方式数据预处理介绍
Section titled “基于BERT+P-Tuning方式数据预处理介绍”- 了解本项目数据类型和表现格式
- 掌握数据处理的工具函数代码实现
BERT+P-Tuning方式数据预处理
Section titled “BERT+P-Tuning方式数据预处理”- 本项目中对数据部分的预处理步骤如下:
- 一、查看项目数据集
- 二、编写Config类项目文件配置代码
- 三、编写数据处理相关代码
一、 查看项目数据集
Section titled “一、 查看项目数据集”-
数据存放位置:/Users/**/PycharmProjects/llm/prompt_tasks/P-Tuning/data
-
data文件夹里面包含4个txt文档,分别为:train.txt、dev.txt、verbalizer.txt
1.1 train.txt
Section titled “1.1 train.txt”- train.txt为训练数据集,其部分数据展示如下:
水果 脆脆的,甜味可以,可能时间有点长了,水分不是很足。平板 华为机器肯定不错,但第一次碰上京东最糟糕的服务,以后不想到京东购物了。书籍 为什么不认真的检查一下, 发这么一本脏脏的书给顾客呢!衣服 手感不错,用料也很好,不知道水洗后怎样,相信大品牌,质量过关,五星好评!!!水果 苹果有点小,不过好吃,还有几个烂的。估计是故意的放的。差评。衣服 掉色掉的厉害,洗一次就花了train.txt一共包含63条样本数据,每一行用
\t分开,前半部分为标签(label),后半部分为原始输入 (用户评论)。如果想使用自定义数据训练,只需要仿照上述示例数据构建数据集即可。
1.2 dev.txt
Section titled “1.2 dev.txt”- dev.txt为验证数据集,其部分数据展示如下:
书籍 "一点都不好笑,很失望,内容也不是很实用"衣服 完全是一条旧裤子。手机 相机质量不错,如果阳光充足,可以和数码相机媲美.界面比较人性化,容易使用.软件安装简便书籍 明明说有货,结果送货又没有了。并且也不告诉我,怎么评啊洗浴 非常不满意,晚上洗的头发,第二天头痒痒的不行了,还都是头皮屑。水果 这个苹果感觉是长熟的苹果,没有打蜡,不错,又甜又脆dev.txt一共包含417条样本数据,每一行用
\t分开,前半部分为标签(label),后半部分为原始输入 (用户评论)。如果想使用自定义数据训练,只需要仿照上述示例数据构建数据集即可。
1.3 verbalizer.txt
Section titled “1.3 verbalizer.txt”-
verbalizer.txt 主要用于定义「真实标签」到「标签预测词」之间的映射。在有些情况下,将「真实标签」作为 [MASK] 去预测可能不具备很好的语义通顺性,因此,我们会对「真实标签」做一定的映射。
-
例如:
"中国爆冷2-1战胜韩国"是一则[MASK][MASK]新闻。 体育-
这句话中的标签为「体育」,但如果我们将标签设置为「足球」会更容易预测。
-
因此,我们可以对「体育」这个 label 构建许多个子标签,在推理时,只要预测到子标签最终推理出真实标签即可,如下:
体育 -> 足球,篮球,网球,棒球,乒乓,体育- 项目中标签词映射数据展示如下:
电脑 电脑水果 水果平板 平板衣服 衣服酒店 酒店洗浴 洗浴书籍 书籍蒙牛 蒙牛手机 手机电器 电器verbalizer.txt 一共包含10个类别,上述数据中,我们使用了1对1的verbalizer, 如果想定义一对多的映射,只需要在后面用”,“分割即可, eg:
水果 苹果,香蕉,橘子若想使用自定义数据训练,只需要仿照示例数据构建数据集
二、 编写Config类项目文件配置代码
Section titled “二、 编写Config类项目文件配置代码”-
代码路径:/Users/**/PycharmProjects/llm/prompt_tasks/P-Tuning/ptune_config.py
-
config文件目的:配置项目常用变量,一般这些变量属于不经常改变的,比如:训练文件路径、模型训练次数、模型超参数等等
具体代码实现:
# coding:utf-8import torch
class ProjectConfig(object): def __init__(self): self.device = 'cuda:0' if torch.cuda.is_available() else 'cpu' self.pre_model = '/Users/**/llm/prompt_tasks/bert-base-chinese' self.train_path = '/Users/**/llm/prompt_tasks/P-Tuning/data/train.txt' self.dev_path = '/Users/**/llm/prompt_tasks/P-Tuning/data/dev.txt' self.verbalizer = '/Users/**/llm/prompt_tasks/P-Tuning/data/verbalizer.txt' self.max_seq_len = 512 self.batch_size = 8 self.learning_rate = 5e-5 self.weight_decay = 0 self.warmup_ratio = 0.06 self.p_embedding_num = 6 self.max_label_len = 2 self.epochs = 50 self.logging_steps = 10 self.valid_steps = 20 self.save_dir = '/Users/**/llm/prompt_tasks/P-Tuning/checkpoints'
if __name__ == '__main__': pc = ProjectConfig() print(pc.verbalizer)三 编写数据处理相关代码
Section titled “三 编写数据处理相关代码”-
代码路径:/Users/***/PycharmProjects/llm/prompt_tasks/P-Tuning/data_handle.
-
data_handle文件夹中一共包含两个py脚本:data_preprocess.py、data_loader.py
3.1 data_preprocess.py
Section titled “3.1 data_preprocess.py”-
目的: 将样本数据转换为模型接受的输入数据
-
导入必备的工具包
# 导入必备工具包import torchimport numpy as npfrom rich import printfrom datasets import load_datasetfrom transformers import AutoTokenizerimport syssys.path.append('..')from ptune_config import *from functools import partial- 定义数据转换方法convert_example()
- 目的:将模板与原始输入文本进行拼接,构造模型接受的输入数据
def convert_example( examples: dict, tokenizer, max_seq_len: int, max_label_len: int, p_embedding_num=6, train_mode=True, return_tensor=False) -> dict: """ 将样本数据转换为模型接收的输入数据。
Args: examples (dict): 训练数据样本, e.g. -> { "text": [ '娱乐 嗨放派怎么停播了', '体育 世界杯为何迟迟不见宣传', ... ] } max_label_len (int): 最大label长度,若没有达到最大长度,则padding为最大长度 p_embedding_num (int): p-tuning token 的个数 train_mode (bool): 训练阶段 or 推理阶段。 return_tensor (bool): 是否返回tensor类型,如不是,则返回numpy类型。
Returns: dict (str: np.array) -> tokenized_output = { 'input_ids': [[101, 3928, ...], [101, 4395, ...]], 'token_type_ids': [[0, 0, ...], [0, 0, ...]], 'mask_positions': [[5, 6, ...], [3, 4, ...]], 'mask_labels': [[183, 234], [298, 322], ...] } """ tokenized_output = { 'input_ids': [], 'attention_mask': [], 'mask_positions': [], # 记录label的位置(即MASK Token的位置) 'mask_labels': [] # 记录MASK Token的原始值(即Label值) }
for i, example in enumerate(examples['text']): try: start_mask_position = 1 # 将 prompt token(s) 插在 [CLS] 之后
if train_mode: label, content = example.strip().split('\t') else: content = example.strip()
encoded_inputs = tokenizer( text=content, truncation=True, max_length=max_seq_len, padding='max_length') except: continue
input_ids = encoded_inputs['input_ids'] # 1.生成 MASK Tokens, 和label长度一致 mask_tokens = ['[MASK]'] * max_label_len
mask_ids = tokenizer.convert_tokens_to_ids(mask_tokens) # token 转 id
# 2.构建 prompt token(s) p_tokens = ["[unused{}]".format(i + 1) for i in range(p_embedding_num)]
p_tokens_ids = tokenizer.convert_tokens_to_ids(p_tokens) # token 转 id
tmp_input_ids = input_ids[:-1] # 根据最大长度-p_token长度-label长度,裁剪content的长度 tmp_input_ids = tmp_input_ids[:max_seq_len-len(mask_ids)-len(p_tokens_ids)-1] # 3.插入 MASK -> [CLS][MASK][MASK]世界杯...[SEP] tmp_input_ids = tmp_input_ids[:start_mask_position] + mask_ids + tmp_input_ids[start_mask_position:]
input_ids = tmp_input_ids + [input_ids[-1]] # 补上[SEP]
# 4.插入 prompt -> [unused1][unused2]...[CLS][MASK]...[SEP] input_ids = p_tokens_ids + input_ids
# 将 Mask Tokens 的位置记录下来 mask_positions = [len(p_tokens_ids) + start_mask_position + i for i in range(max_label_len)]
tokenized_output['input_ids'].append(input_ids)
# 兼容不需要 token_type_id 的模型, e.g. Roberta-Base if 'token_type_ids' in encoded_inputs: tmp = encoded_inputs['token_type_ids'] if 'token_type_ids' not in tokenized_output: tokenized_output['token_type_ids'] = [tmp] else: tokenized_output['token_type_ids'].append(tmp) tokenized_output['attention_mask'].append(encoded_inputs['attention_mask']) tokenized_output['mask_positions'].append(mask_positions)
if train_mode: mask_labels = tokenizer(text=label) # label token 转 id mask_labels = mask_labels['input_ids'][1:-1] # 丢掉[CLS]和[SEP] mask_labels = mask_labels[:max_label_len] # 将 label 补到最长 mask_labels += [tokenizer.pad_token_id] * (max_label_len - len(mask_labels)) tokenized_output['mask_labels'].append(mask_labels)
for k, v in tokenized_output.items(): if return_tensor: tokenized_output[k] = torch.LongTensor(v) else: tokenized_output[k] = np.array(v)
return tokenized_output
if __name__ == '__main__': pc = ProjectConfig() train_dataset = load_dataset('text', data_files={'train': pc.train_path}) # print(type(train_dataset)) # print(train_dataset) # print('*'*80) # print(train_dataset['train']['text']) tokenizer = AutoTokenizer.from_pretrained(pc.pre_model) tokenized_output = convert_example(examples=train_dataset['train'], tokenizer=tokenizer, max_seq_len=20, max_label_len=2, p_embedding_num=6, train_mode=True, return_tensor=False) print(tokenized_output) print(type(tokenized_output['mask_positions']))打印结果展示:
{'input_ids': array([[ 1, 2, 3, ..., 1912, 6225, 102],[ 1, 2, 3, ..., 3300, 5741, 102],[ 1, 2, 3, ..., 6574, 7030, 0],...,[ 1, 2, 3, ..., 8024, 2571, 0],[ 1, 2, 3, ..., 3221, 3175, 102],[ 1, 2, 3, ..., 5277, 3688, 102]]),'attention_mask': array([[1, 1, 1, ..., 1, 1, 1],[1, 1, 1, ..., 1, 1, 1],[1, 1, 1, ..., 0, 0, 0],...,[1, 1, 1, ..., 0, 0, 0],[1, 1, 1, ..., 1, 1, 1],[1, 1, 1, ..., 1, 1, 1]]),'mask_positions': array([[7, 8],[7, 8],[7, 8],...,[7, 8],[7, 8],[7, 8]]),'mask_labels': array([[4510, 5554],[3717, 3362],[2398, 3352],...,[3819, 3861],[6983, 2421],[3819, 3861]]),'token_type_ids': array([[0, 0, 0, ..., 0, 0, 0],[0, 0, 0, ..., 0, 0, 0],[0, 0, 0, ..., 0, 0, 0],...,[0, 0, 0, ..., 0, 0, 0],[0, 0, 0, ..., 0, 0, 0],[0, 0, 0, ..., 0, 0, 0]])}
3.3 data_loader.py
Section titled “3.3 data_loader.py”-
目的:定义数据加载器
-
导入必备的工具包
# coding:utf-8from torch.utils.data import DataLoaderfrom transformers import default_data_collator, AutoTokenizerfrom data_handle.data_preprocess import *from ptune_config import *
pc = ProjectConfig() # 实例化项目配置文件
tokenizer = AutoTokenizer.from_pretrained(pc.pre_model)- 定义获取数据加载器的方法get_data()
def get_data(): dataset = load_dataset('text', data_files={'train': pc.train_path, 'dev': pc.dev_path}) new_func = partial(convert_example, tokenizer=tokenizer, max_seq_len=pc.max_seq_len, max_label_len=pc.max_label_len, p_embedding_num=pc.p_embedding_num)
dataset = dataset.map(new_func, batched=True) train_dataset = dataset["train"] dev_dataset = dataset["dev"] train_dataloader = DataLoader(train_dataset, shuffle=True, collate_fn=default_data_collator, batch_size=pc.batch_size) dev_dataloader = DataLoader(dev_dataset, collate_fn=default_data_collator, batch_size=pc.batch_size) return train_dataloader, dev_dataloader
if __name__ == '__main__': train_dataloader, dev_dataloader = get_data() print(len(train_dataloader)) print(len(dev_dataloader)) for i, value in enumerate(train_dataloader): print(i) print(value) print(value['input_ids'].dtype) break打印结果展示:
{'input_ids': tensor([[1, 2, 3, ..., 0, 0, 0],[1, 2, 3, ..., 0, 0, 0],[1, 2, 3, ..., 0, 0, 0],...,[1, 2, 3, ..., 0, 0, 0],[1, 2, 3, ..., 0, 0, 0],[1, 2, 3, ..., 0, 0, 0]]),'attention_mask': tensor([[1, 1, 1, ..., 0, 0, 0],[1, 1, 1, ..., 0, 0, 0],[1, 1, 1, ..., 0, 0, 0],...,[1, 1, 1, ..., 0, 0, 0],[1, 1, 1, ..., 0, 0, 0],[1, 1, 1, ..., 0, 0, 0]]),'mask_positions': tensor([[7, 8],[7, 8],[7, 8],[7, 8],[7, 8],[7, 8],[7, 8],[7, 8]]),'mask_labels': tensor([[6132, 3302],[2398, 3352],[6132, 3302],[6983, 2421],[3717, 3362],[6983, 2421],[3819, 3861],[6983, 2421]]),'token_type_ids': tensor([[0, 0, 0, ..., 0, 0, 0],[0, 0, 0, ..., 0, 0, 0],[0, 0, 0, ..., 0, 0, 0],...,[0, 0, 0, ..., 0, 0, 0],[0, 0, 0, ..., 0, 0, 0],[0, 0, 0, ..., 0, 0, 0]])}torch.int64
本章节主要介绍了基于BERT+P-Tuning方式实现文本分类任务时数据处理步骤,并且通过代码实现:提示模板数据格式的转换,数据加载器的编码等。