微调大模型时,如何进行数据预处理? 将<input, output>转换为模型所需的<input_ids, labels, attention_mask>

原始训练数据集格式如下:

<input, output>

形式:字符

模型训练所需数据格式如下:

# tokenizer处理后
return {
    "input_ids": example,
    "labels": labels,
    "attention_mask": example_mask,
}

将字符转换为id,生成三个部分。

讲解

在大模型训练中,input_idslabelsattention_mask 是标准的数据格式,用于表示输入文本和相关的辅助信息。这些数据是模型训练中必需的,尤其是在自然语言处理(NLP)任务中。下面是这些字段的详细解释:

1. input_ids

  • 含义input_ids 是输入文本的 Token IDs(即词汇表中的索引)。
  • 用途:它们是模型输入的实际内容。模型通常需要将文本转换为数字形式来处理,input_ids 就是将每个词(或子词)映射到一个整数,这个整数表示该词在预训练模型的词汇表中的位置。
  • 例子
    • 假设输入文本是:"I love AI."
    • 经过分词和映射后,可能得到 input_ids = [101, 1045, 2293, 9474, 102],其中:
      • 101102 是特殊的标记(如开始和结束标记)。
      • 1045 是词 "I" 对应的 ID。
      • 2293 是词 "love" 对应的 ID,依此类推。
  • 注意:每个模型都有自己独特的词汇表,因此同样的文本在不同模型中得到的 input_ids 可能不同。

2. labels

  • 含义labels 是模型输出的目标数据,通常用于监督学习任务中的目标标签。
  • 用途labels 用于计算损失函数(比如交叉熵损失)来优化模型。它通常与 input_ids 具有相同的格式,用于生成目标预测。在一些任务中,labels 是输入文本的某种变换,比如机器翻译的目标句子,或者是文本分类任务中的标签。
  • 例子
    • 对于语言模型的训练,labels 通常与 input_ids 相同,代表的是下一个词的预测。即,模型在每个位置预测下一个词的 input_id
    • 在问答任务中,labels 可能是模型应当输出的答案。
    • 对于分类任务,labels 可以是一个整数值,表示文本的类别(如 012 等)。

语言模型的例子

  • 输入句子:"I love AI."
  • input_ids = [101, 1045, 2293, 9474, 102]
  • labels 对于语言模型任务,可能和 input_ids 一样:
    • labels = [1045, 2293, 9474, 102, -100]
    • 这里,-100 是一个特殊的标记,用于掩盖某些位置的标签,避免在计算损失时对某些位置进行更新。

3. attention_mask

  • 含义attention_mask 是一个与 input_ids 等长的向量,指示模型在哪些位置需要关注(即哪些位置是有效的)以及哪些位置应忽略(即哪些位置是填充的)。
  • 用途:在许多 NLP 模型中,文本可能需要进行填充(padding),特别是在处理不同长度的文本时。attention_mask 用于指示模型应该计算注意力的哪些位置,而哪些位置是填充,应该被忽略。
    • 1 表示该位置是有效的,模型应该关注这个位置。
    • 0 表示该位置是填充,模型应该忽略这个位置。
  • 例子
    • 假设输入文本是两句话,分别是:"I love AI." 和 "I enjoy machine learning."
    • input_ids[101, 1045, 2293, 9474, 102, 101, 1045, 2829, 4974, 102]
    • 由于句子长度不同,第二句需要填充。假设最大长度是 10,所以填充的位置会使用 0,并且 attention_mask 会标明哪些位置需要关注:
    • attention_mask = [1, 1, 1, 1, 1, 1, 1, 1, 1, 0]
    • 这里,1 表示模型应该关注的位置,0 表示填充的位置。

总结:

这些字段在训练时的意义:

  • input_ids:模型的输入序列,表示文本的词汇索引。
  • labels:目标输出序列,通常与 input_ids 相同,用于计算损失函数。
  • attention_mask:指示哪些位置是有效的,哪些是填充,帮助模型避免处理填充部分的数据。

这种格式在 NLP 任务中广泛使用,尤其是在语言模型训练(如 GPT、BERT 等)和序列到序列任务(如机器翻译、问答系统)中。

在大多数情况下,input_ids不包含 训练数据中的真实输出(如目标标签)。input_ids 主要用于表示输入序列,即模型的输入,而真实的输出通常会在 labels 中提供。我们可以通过具体的任务来更好地理解它们之间的关系。

input_ids 和 labels

1. 语言建模任务(如 GPT)

对于语言建模任务,input_idslabels 是非常相似的,甚至有时完全相同。这是因为语言模型的任务是根据前面的上下文预测下一个词。因此,模型的输入(input_ids)和目标输出(labels)是相同的,且 labels 的每个位置都表示目标词。

举例

  • 输入句子: "I love AI"

  • 假设使用的词汇表索引(简化表示):I -> 1045, love -> 2293, AI -> 9474

  • input_ids = [1045, 2293, 9474](这就是模型的输入)

  • labels = [2293, 9474, -100]-100 是一个占位符,表示忽略该位置)

在语言模型任务中,模型的目标是预测每个位置的下一个词。所以 labels 会从第一个词开始,包含实际的目标词。input_idslabels 对于每个位置来说,在训练时是同步的,只是模型预测的是 下一个 词。

2. 序列标注任务(如命名实体识别 NER 或 POS 标注)

在序列标注任务中,input_ids 仍然是输入序列的表示,但 labels 是每个输入单词或标记的标签。这时,input_ids 仅包含输入文本的词汇索引,而真实标签(比如实体类别或词性标签)则在 labels 中。

举例

  • 输入文本: "I love AI"

  • 目标标签(假设为命名实体识别任务):I -> O, love -> O, AI -> B-ORG

  • input_ids = [1045, 2293, 9474](表示输入的文本)

  • labels = [0, 0, 1](表示每个词的标签,其中 0 是普通词,1 表示 "AI" 是一个组织实体)

在这种任务中,input_idslabels 是不同的,input_ids 表示输入,而 labels 表示这些输入对应的标签。

3. 序列到序列任务(如机器翻译或文本生成)

在机器翻译或文本生成任务中,input_idslabels 也会有明显的区别:

  • input_ids 是源语言的文本表示。
  • labels 是目标语言的文本表示。

例如,在翻译任务中:

  • 输入文本(源语言): "I love AI"

  • 目标文本(目标语言): "J'aime l'IA"

  • input_ids = [1045, 2293, 9474](表示源语言)

  • labels = [2013, 1244, 1849](表示目标语言)

在这种情况下,input_idslabels 完全不同,因为它们分别表示源语言和目标语言。

4. 总结:

  • input_ids:表示模型的输入文本的 token 化结果,通常是对文本进行分词、编码后的词汇索引。它仅包含输入数据,不包含目标输出。
  • labels:表示模型的目标输出,通常用于训练期间计算损失。在语言建模任务中,labels 可能和 input_ids 一样;但在其他任务(如分类、序列标注、机器翻译等)中,labelsinput_ids 完全不同,表示模型应该生成或预测的目标结果。

因此,input_ids 不包含 训练数据中的真实输出,而 labels 才是训练时用来计算损失和评估模型性能的目标值。

处理代码

crop_train.json训练数据集格式如下:

{
    "instruction": "你是农作物领域专门进行关系抽取的专家。请从给定的文本中抽取出关系三元组,不存在的关系返回空列表。请按照JSON字符串的格式回答。",
    "input": "煤是一种常见的化石燃料,家庭用煤经过了从\"煤球\"到\"蜂窝煤\"的演变。",
    "output": "[{\"head\": \"煤\", \"relation\": \"use\", \"tail\": \"燃料\"}]"
},

数据预处理代码如下: 

import json
from datasets import load_dataset
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer


dataset = load_dataset("json", data_files="./crop_train.json", split="train")
print(f"dataset: {dataset}")

tokenizer = AutoTokenizer.from_pretrained("./glm-4-9b-chat", trust_remote_code=True)
print(f"tokenizer: {tokenizer}")

def process_func(example):
    MAX_LENGTH = 256
    input_ids, attention_mask, labels = [], [], []
    # 合并example的instruction和input字段为一个字符串
    instruction = f"{example['instruction']} {example['input']}".strip()  # query
    instruction = tokenizer.apply_chat_template([{"role": "user", "content": instruction}],
                                                add_generation_prompt=True,
                                                tokenize=True,
                                                return_tensors="pt",
                                                return_dict=True
                                                )  # '[gMASK] <sop> <|user|> \nquery <|assistant|>'

    # 检查example["output"]是否是列表,并相应地处理
    if isinstance(example["output"], list):
        response_text = "\n".join(example["output"])
    else:
        response_text = "\n" + example["output"]

    response = tokenizer(response_text, add_special_tokens=False)  # \n response, 缺少eos token
    # input_ids = input + output
    input_ids = instruction["input_ids"][0].numpy().tolist() + response["input_ids"] + [tokenizer.eos_token_id]
    attention_mask = instruction["attention_mask"][0].numpy().tolist() + response["attention_mask"] + [1]
    # labels = input(-100) + output
    labels = [-100] * len(instruction["input_ids"][0].numpy().tolist()) + response["input_ids"] + [tokenizer.eos_token_id]
    if len(input_ids) > MAX_LENGTH:
        input_ids = input_ids[:MAX_LENGTH]
        attention_mask = attention_mask[:MAX_LENGTH]
        labels = labels[:MAX_LENGTH]
    return {
        "input_ids": input_ids,
        "attention_mask": attention_mask,
        "labels": labels
    }

# 训练数据集经过预处理后生成<input_ids, labels, attention_mask>
tokenized_ds = dataset.map(process_func, remove_columns=['instruction', 'input', 'output'])

print(f"All tokenizer tokens ids: {tokenized_ds}")     # features: ['input_ids', 'attention_mask', 'labels'],

# tokenized_ds: 包含input_ids, attention_mask, labels = [], [], []
input_ids_1 = tokenized_ds[0]["input_ids"]
attention_mask_1 = tokenized_ds[0]["attention_mask"]
labels_1 = tokenized_ds[0]["labels"]
print(f"input_ids_1: {input_ids_1}")
print(f"attention_mask_1: {attention_mask_1}")
print(f"labels_1: {labels_1}")

input_text_1 = tokenizer.decode(input_ids_1)
print(f"input_ids_1_decode: {input_text_1}")

tokenized_ds:里面包含所有的训练数据经过转换后的 ['input_ids', 'attention_mask', 'labels']集合,用于model直接使用来训练。

模型训练所需数据格式如下: 

input_ids_1: [151331, 151333, 151336, 198, 103408, 112687, 99788, 102014, 98638, 99172, 115023, 98314, 100153, 1773, 98964, 98484, 98602, 100966, 103231, 98322, 100319, 107325, 99172, 120673, 98555, 3837, 107399, 102189, 104559, 98745, 106522, 1773, 98964, 99928, 5370, 121478, 98314, 104714, 99770, 1773, 10231, 227, 97, 100375, 104250, 112075, 106512, 3837, 99716, 98340, 100855, 114094, 98484, 1, 100855, 98781, 1, 98344, 1, 125272, 100855, 1, 98314, 110001, 1773, 151337, 198, 58, 4913, 1983, 788, 330, 100855, 497, 330, 22166, 788, 330, 810, 497, 330, 14576, 788, 330, 106512, 9204, 60, 151329]
attention_mask_1: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
labels_1: [-100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, 198, 58, 4913, 1983, 788, 330, 100855, 497, 330, 22166, 788, 330, 810, 497, 330, 14576, 788, 330, 106512, 9204, 60, 151329]

input_ids_1_decode: 
[gMASK] <sop> <|user|> 
你是农作物领域专门进行关系抽取的专家。请从给定的文本中抽取出关系三元组,不存在的关系返回空列表。请按照JSON字符串的格式回答。 煤是一种常见的化石燃料,家庭用煤经过了从"煤球"到"蜂窝煤"的演变。 
<|assistant|> [{"head": "煤", "relation": "use", "tail": "燃料"}] <|endoftext|>

模型训练

# 模型训练参数
args = TrainingArguments(
    output_dir="./chatbot",
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,
    gradient_checkpointing=True,
    logging_steps=100,
    num_train_epochs=10,
    learning_rate=1e-4,
    remove_unused_columns=False,
    save_strategy="epoch"
)

# 开始训练
trainer = Trainer(
    model=model,
    args=args,
    # 使用 <input_ids, labels, attention_mask>
    train_dataset=tokenized_ds.select(range(10000)),
    data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
)

总结

LLM的训练或者微调都是需要<input_ids, labels, attention_mask>形式的数据,e. g. Structured IE中的三个task的dataset仍是如此。

class MOFDataset(Dataset):
    def __init__(self, dataset_config, tokenizer, split_name, max_words=1024):
        #self.data = json.load(open(dataset_config.data_path))

        if split_name == "train":
            self.data = json.load(open(dataset_config.data_path+"/train.json")) # self.data[0]["train"]  # Adjust this based on your dataset's structure
        else:
            self.data = json.load(open(dataset_config.data_path+"/val.json"))# self.data[0]["validation"]  # Adjust this based on your dataset's structure

        self.max_words = max_words
        self.tokenizer = tokenizer

    def __len__(self):
        return len(self.data)

    def __getitem__(self, index):
        IGNORE_INDEX = -100  # The default setting in CrossEntropyLoss

        item = self.data[index]

        #prompt = f"### Instruction:\n{item['instruction']}\n\n### Input:\n{item['input']}\n\n### Response:"
        prompt = item['input']#f"item['input']\n\n"

        # example = input+ output
        example = prompt + item["output"]
#        print(example)
        prompt = torch.tensor(self.tokenizer.encode(prompt), dtype=torch.int64)

        # example = input_ids + output_ids
        example = self.tokenizer.encode(example)    # input+output
#        print(example)
        # example = input_ids + output_ids + <eos>
        example.append(self.tokenizer.eos_token_id)
        example = torch.tensor(example, dtype=torch.int64)
        padding = self.max_words - example.shape[0]
        # 用 -1 填充
        if padding > 0:
            example = torch.cat((example, torch.zeros(padding, dtype=torch.int64) - 1))
        # 截断
        elif padding < 0:
            example = example[: self.max_words]
        # labels = example
        labels = copy.deepcopy(example)

        # labels = input(-1) + output
        # 复制 example,并将 example 中与 prompt 对应的部分设置为 -1。这样,模型在训练时只会关注 output 部分作为标签,而忽略掉输入部分。
        labels[: len(prompt)] = -1

        # 创建一个 mask,用于标记 example 中不为 -1(即有效)的部分。
        example_mask = example.ge(0)

        # 创建一个 mask,用于标记 labels 中不为 IGNORE_INDEX(即有效)的部分。
        label_mask = labels.ge(0)

        # 将无效的部分(填充部分)置为 0,这样它们不会对损失计算产生影响。
        example[~example_mask] = 0
        labels[~label_mask] = IGNORE_INDEX
        example_mask = example_mask.float()
        label_mask = label_mask.float()

        return {
            "input_ids": example,   # example = input_ids + output_ids + <eos>
            "labels": labels,       # labels = input(-1) + output,输入部分被替换为 -1,只保留 output 部分作为目标标签。
            "attention_mask": example_mask,  # 返回一个 mask,指示哪些位置是有效的(即不是填充部分)。
        }

在自然语言处理的领域,BERT模型已经成为一种标准工具,其双向编码器表示能够捕捉到丰富的文本信息。针对特定任务进行微调是提升模型表现的关键步骤。以下是如何在Python中使用BERT模型进行自定义文本微调,提高任务准确率的详细步骤和代码示例: 参考资源链接:[使用BERT预训练模型与自定义模型进行文本微调的Python实战](https://wenku.youkuaiyun.com/doc/521c3ik9k2?spm=1055.2569.3001.10343) 首先,确保安装了BERT模型和TensorFlow库,可以通过pip命令安装预训练模型和TensorFlow: ```bash pip install tensorflow pip install bert-tensorflow # 或者其他适用于BERT的库 ``` 接着,加载BERT模型以及Tokenizer,用于文本的预处理: ```python from bert import BertModel, BertTokenizer import tensorflow as tf # 加载预训练的BERT模型和Tokenizer model = BertModel() tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') ``` 对于自定义数据源,需要进行数据预处理,包括分词、添加特殊字符、序列填充等: ```python # 示例数据预处理代码省略 ``` 在微调阶段,构建模型并在有标签的数据集上进行训练。通常会在BERT模型的顶部添加一个或多个全连接层,用于分类任务: ```python from tensorflow.keras.layers import Dense from tensorflow.keras.models import Model # 假设已经有了处理好的输入和输出 input_ids = ... # 输入序列的Token ID attention_masks = ... # 注意力掩码 labels = ... # 标签数据 # 构建微调模型 bert_output = model(input_ids, attention_mask=attention_masks) dense_layer = Dense(units=2, activation='softmax', name='dense_layer')(bert_output['pooled_output']) model = Model(inputs=input_ids, outputs=dense_layer) # 编译模型,使用SparseCategoricalCrossentropy损失函数和Adam优化器 ***pile(optimizer=Adam(learning_rate=3e-5), loss=SparseCategoricalCrossentropy(from_logits=True), metrics=['accuracy']) # 训练模型,指定epoch和batch_size model.fit([input_ids, attention_masks], labels, epochs=3, batch_size=32) ``` 在上述过程中,需要注意模型的输出层激活函数和损失函数要与任务的类型相匹配。例如,对于多分类任务,输出层使用softmax激活函数,损失函数使用SparseCategoricalCrossentropy。 微调完成后,你可以使用`model.evaluate()`和`model.predict()`来进行模型评估和预测。 通过上述步骤,你可以在Python环境中使用BERT模型进行文本微调,并针对具体任务提高模型的准确率。为了深入理解BERT模型的预训练微调过程,建议阅读这份资料:《使用BERT预训练模型与自定义模型进行文本微调的Python实战》,其中包含了实际操作中的代码片段和详细步骤,能帮助你更好地掌握BERT模型的实际应用。 参考资源链接:[使用BERT预训练模型与自定义模型进行文本微调的Python实战](https://wenku.youkuaiyun.com/doc/521c3ik9k2?spm=1055.2569.3001.10343)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值