前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >BERT+P-Tuning方式数据处理

BERT+P-Tuning方式数据处理

作者头像
@小森
发布2024-06-08 09:53:41
670
发布2024-06-08 09:53:41
举报
文章被收录于专栏:xiaosenxiaosen

基于BERT+P-Tuning方式数据预处理介绍

一、 查看项目数据集
  • 数据存放位置:/Users/**/PycharmProjects/llm/prompt_tasks/P-Tuning/data
  • data文件夹里面包含4个txt文档,分别为:train.txt、dev.txt、verbalizer.txt

1.1 train.txt
  • train.txt为训练数据集,其部分数据展示如下:
代码语言:javascript
复制
水果	脆脆的,甜味可以,可能时间有点长了,水分不是很足。
平板	华为机器肯定不错,但第一次碰上京东最糟糕的服务,以后不想到京东购物了。
书籍	为什么不认真的检查一下, 发这么一本脏脏的书给顾客呢!
衣服	手感不错,用料也很好,不知道水洗后怎样,相信大品牌,质量过关,五星好评!!!
水果	苹果有点小,不过好吃,还有几个烂的。估计是故意的放的。差评。
衣服	掉色掉的厉害,洗一次就花了

train.txt一共包含63条样本数据,每一行用\t分开,前半部分为标签(label),后半部分为原始输入 (用户评论)。 如果想使用自定义数据训练,只需要仿照上述示例数据构建数据集即可。


1.2 dev.txt
  • dev.txt为验证数据集,其部分数据展示如下:
代码语言:javascript
复制
书籍	"一点都不好笑,很失望,内容也不是很实用"
衣服	完全是一条旧裤子。
手机	相机质量不错,如果阳光充足,可以和数码相机媲美.界面比较人性化,容易使用.软件安装简便
书籍	明明说有货,结果送货又没有了。并且也不告诉我,怎么评啊
洗浴	非常不满意,晚上洗的头发,第二天头痒痒的不行了,还都是头皮屑。
水果	这个苹果感觉是长熟的苹果,没有打蜡,不错,又甜又脆

dev.txt一共包含417条样本数据,每一行用\t分开,前半部分为标签(label),后半部分为原始输入 (用户评论)。 如果想使用自定义数据训练,只需要仿照上述示例数据构建数据集即可。

1.3 verbalizer.txt
  • verbalizer.txt 主要用于定义「真实标签」到「标签预测词」之间的映射。在有些情况下,将「真实标签」作为 [MASK] 去预测可能不具备很好的语义通顺性,因此,我们会对「真实标签」做一定的映射。
  • 例如:
代码语言:javascript
复制
"中国爆冷2-1战胜韩国"是一则[MASK][MASK]新闻。	体育
  • 这句话中的标签为「体育」,但如果我们将标签设置为「足球」会更容易预测。
  • 因此,我们可以对「体育」这个 label 构建许多个子标签,在推理时,只要预测到子标签最终推理出真实标签即可,如下:
代码语言:javascript
复制
体育 -> 足球,篮球,网球,棒球,乒乓,体育
  • 项目中标签词映射数据展示如下:
代码语言:javascript
复制
电脑	电脑
水果	水果
平板	平板
衣服	衣服
酒店	酒店
洗浴	洗浴
书籍	书籍
蒙牛	蒙牛
手机	手机
电器	电器

verbalizer.txt 一共包含10个类别,上述数据中,我们使用了1对1的verbalizer, 如果想定义一对多的映射,只需要在后面用","分割即可, eg:

代码语言:javascript
复制
水果	苹果,香蕉,橘子

若想使用自定义数据训练,只需要仿照示例数据构建数据集

二、 编写Config类项目文件配置代码
  • 代码路径:/Users/**/PycharmProjects/llm/prompt_tasks/P-Tuning/ptune_config.py
  • config文件目的:配置项目常用变量,一般这些变量属于不经常改变的,比如:训练文件路径、模型训练次数、模型超参数等等

具体代码实现:

代码语言:javascript
复制
# coding:utf-8
import 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)
三 编写数据处理相关代码
  • 代码路径:/Users/***/PycharmProjects/llm/prompt_tasks/P-Tuning/data_handle.
  • data_handle文件夹中一共包含两个py脚本:data_preprocess.py、data_loader.py

3.1 data_preprocess.py
  • 目的: 将样本数据转换为模型接受的输入数据
  • 导入必备的工具包
代码语言:javascript
复制
# 导入必备工具包
import torch
import numpy as np
from rich import print
from datasets import load_dataset
from transformers import AutoTokenizer
import sys
sys.path.append('..')
from ptune_config import *
from functools import partial

  • 定义数据转换方法convert_example()
  • 目的:将模板与原始输入文本进行拼接,构造模型接受的输入数据
代码语言:javascript
复制
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']))

打印结果展示:

代码语言:javascript
复制
{
    '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
  • 目的:定义数据加载器
  • 导入必备的工具包
代码语言:javascript
复制
# coding:utf-8
from torch.utils.data import DataLoader
from transformers import default_data_collator, AutoTokenizer
from data_handle.data_preprocess import *
from ptune_config import *

pc = ProjectConfig() # 实例化项目配置文件

tokenizer = AutoTokenizer.from_pretrained(pc.pre_model)

  • 定义获取数据加载器的方法get_data()
代码语言:javascript
复制
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

打印结果展示:

代码语言:javascript
复制
{
    '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方式实现文本分类任务时数据处理步骤,并且通过代码实现:提示模板数据格式的转换,数据加载器的编码等。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-06-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基于BERT+P-Tuning方式数据预处理介绍
    • 一、 查看项目数据集
      • 二、 编写Config类项目文件配置代码
        • 三 编写数据处理相关代码
          • 小结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档