【LLM大模型】怎么让英文大语言模型支持中文?(三)进行指令微调

代码已上传到github: chinese_llm_sft

Part1前言

在之前讲过的继续预训练之后,我们应该对数据处理到训练、预测的整个流程有所了解,其实,基本上过程是差不多的。我们在选择好一个大语言模型之后。比如chatglm、llama、bloom等,要想使用它,得了解三个方面:输入数据的格式、tokenization、模型的使用方式。接下来我们一一来看。本文主训练代码来自github:Chinese-LLaMA-Alpaca。

Part2数据

数据的输入的话,一般情况下我们要在模型的官方代码上找到数据输入的那部分,或者说找到其它的一些开源的项目里面关于数据预处理的部分。找一份小的数据集,将这部分单独拿出来运行一下,看一下输出是什么。返回的结果是什么。比如一般看一下input_ids里面的特殊标记,labels是怎么构造的。举个例子,cpm-bee在forward里面需要额外传入span和length,与一般的不同只需要传入input_ids和labels。

这里我们看下chatglm的数据格式是怎么样的,在test_dataset.py里面:

import logging
import os
from dataclasses import dataclass
from typing import Optional, Dict, Sequence, Union, List
import datasets
import torch
import logging
from datasets import load_dataset, concatenate_datasets
import copy
import transformers
import random

IGNORE_INDEX = -100

logger = logging.getLogger('__name__')

PROMPT_TEMPLATE = (
    "Below is an instruction that describes a task. "
    "Write a response that appropriately completes the request.\n\n"
    "### Instruction:\n{instruction}\n\n### Response: "
)

def buid_instruction_dataset(data_path: Union[List[str],str],
                tokenizer: transformers.PreTrainedTokenizer,
                max_seq_length: int, data_cache_dir = None,
                preprocessing_num_workers = None,
                ):

    def tokenization(examples):
        sources = []
        targets = []
        # prompt = PROMPT_TEMPLATE
        for instruction, input, output in zip(examples['instruct'],examples['query'],examples['answer']):
            if input is not None and input !="":
                instruction = instruction+'\n'+input
            # source = prompt.format_map({'instruction': instruction})
            source = instruction
            target = f"{tokenizer.bos_token}{output}{tokenizer.eos_token}"

            sources.append(source)
            targets.append(target)

        tokenized_sources = tokenizer(sources,return_attention_mask=False, add_special_tokens=False)
        tokenized_targets = tokenizer(targets,return_attention_mask=False, add_special_tokens=False)
        
        print(tokenized_targets)
        
        all_input_ids = []
        all_labels = []
        for s,t in zip(tokenized_sources['input_ids'],tokenized_targets['input_ids']):
            s = s + [tokenizer.gmask_token_id]
            input_ids = torch.LongTensor(s + t)[:max_seq_length]
            labels = torch.LongTensor([IGNORE_INDEX] * len(s) + t)[:max_seq_length]
            assert len(input_ids) == len(labels)
            all_input_ids.append(input_ids)
            all_labels.append(labels)

        results = {'input_ids':all_input_ids, 'labels': all_labels}
        return results


    logging.warning("building dataset...")
    all_datasets = []

    if not isinstance(data_path,(list,tuple)):
        data_path = [data_path]
    for file in data_path:

        if data_cache_dir is None:
            data_cache_dir = str(os.path.dirname(file))
        cache_path = os.path.join(data_cache_dir,os.path.basename(file).split('.')[0])
        os.makedirs(cache_path, exist_ok=True)
        try:
            processed_dataset = datasets.load_from_disk(cache_path)
            logger.info(f'training datasets-{file} has been loaded from disk')
        except Exception:
            print(file)
            raw_dataset = load_dataset("json", data_files=file, cache_dir=cache_path)
            print(raw_dataset)
            tokenization_func = tokenization
            tokenized_dataset = raw_dataset.map(
                tokenization_func,
                batched=True,
                num_proc=preprocessing_num_workers,
                remove_columns=["instruct","query","answer"],
                keep_in_memory=False,
                desc="preprocessing on dataset",
            )
            processed_dataset = tokenized_dataset
            processed_dataset.save_to_disk(cache_path)
        processed_dataset.set_format('torch')
        all_datasets.append(processed_dataset['train'])
    all_datasets = concatenate_datasets(all_datasets)
    return all_datasets

@dataclass
class DataCollatorForSupervisedDataset(object):
    """Collate examples for supervised fine-tuning."""

    tokenizer: transformers.PreTrainedTokenizer

    def __call__(self, instances: Sequence[Dict]) -> Dict[str, torch.Tensor]:
        input_ids = instances["input_ids"]
        labels = instances["labels"]

        input_ids = torch.nn.utils.rnn.pad_sequence(
            input_ids, batch_first=True, padding_value=self.tokenizer.pad_token_id
        )
        labels = torch.nn.utils.rnn.pad_sequence(labels, batch_first=True, padding_value=-100)
        return dict(
            input_ids=input_ids,
            labels=labels,
        )

if __name__ == "__main__":
  from transformers import AutoModelForCausalLM, AutoTokenizer
  tokenizer = AutoTokenizer.from_pretrained("model_hub/chatglm-6b", trust_remote_code=True)
  all_datasets = buid_instruction_dataset(["data/msra/train.txt"], tokenizer, max_seq_length=256)
  print(all_datasets[0])
  data_collator = DataCollatorForSupervisedDataset(tokenizer=tokenizer)
  data = data_collator(all_datasets[:2])
  print(data) 

指令数据一般由三部分组成:instruction(instruct)、input(query)、output(answer),分别表示提示指令、文本、返回的结果。 构造的时候一般是instruction和input进行拼接,当然input可能是为空的,最终对output进行预测。需要注意的是,除了instruction之外,可能还有特殊的prompt,不同模型的prompt是不一样的,比如:

PROMPT_DICT = {
    "chatglm_input": ("{instruction}{input}"),
    "alpaca_input": (
        "Below is an instruction that describes a task. "
        "Write a response that appropriately completes the request.\n\n"
        "### Instruction:\n{instruction}{input}\n\n### Response: "
    ),
    "bloom_input": ("Human: \n{instruction}{input}\n\nAssistant: \n"),
}

我们在构造的时候最好想之前预训练模型那样构造样本。

接下来再讲讲input_ids和labels。假设我们现在有样本: 我爱北京天安门,你喜欢什么?,分词之后得到["我", "爱", "北京", "天安门", "你", "喜欢", "什么", "?"],之后转换为token_id,[12, 112, 122324, 22323, 23, 2346, 1233, 545],我们有Output:我喜欢故宫,转换为token_id:[12, 2346, 654],一般情况下,output前后会被标识,比如bos_token_id和eos_token_id,假设分别为1和2,那么我们样本的输入就是:[12, 112, 122324, 22323, 23, 2346, 1233, 545] + [1] + [12, 2346, 654] + [2]。至于labels的构建,直接说明为:[-100, -100, -100, -100, -100, -100, -100, -100, 1, 12, 2346, 654, 2],长度和input_ids保持一致。有人可能会疑惑,不是说是根据上一个字预测下一个字吗? 怎么是自己预测自己。这是因为一般的模型内部在前向计算的时候已经帮我们处理了: input_ids = input_ids[-1] labels=labels[1:]。-100是表示在计算损失的时候不考虑标签为-100的位置。如果还设置了文本最大长度,则input_ids后面用pad_token_id进行填充,需要注意可能有的模型的tokenization中pad_token为None,需要自己去设置一个,可以和eos_token_id一样。而标签需要用-100进行填充。

针对于chatglm,除了上述说明的外,它还有一个额外的[gMASK]标记。而它的输入为:

# instruction为instruction + input
# [gmask]等标记转换为id,这里直接展示
input_ids = instruction_ids + [gmask] + <sop> + output_ids + <eop>
# +1是[gmask]
-100 * len(instruction_ids + 1) + <sop> + output_ids + <eop>

所以说不同模型的输入构造可能不大一样,需要注意:

  • 特殊标记的使用。
  • 除了input_ids和labels,是否需要额外的输入。
  • 有的模型内部是帮你自动转换labels和input_ids计算损失,有的没有转换,可能需要自己手动转换,比如cpm-bee。

Part3tokenization

tokenization也很重要,我们一般可以先探索一下,在test_tokenizer.py中:

from transformers import AutoTokenizer, AutoModel
tokenizer = AutoTokenizer.from_pretrained("model_hub/chatglm-6b", trust_remote_code=True)

text = "我爱北京天安门"
print(tokenizer(text))
print(tokenizer.convert_ids_to_tokens([18060, 12247, 14949]))
print(tokenizer.decode([18060, 12247, 14949]))

# 打印特殊 token
print("BOS token: ", tokenizer.bos_token)
print("EOS token: ", tokenizer.eos_token)
print("PAD token: ", tokenizer.pad_token)
print("UNK token: ", tokenizer.unk_token)

# 打印特殊 token_id
print("BOS token: ", tokenizer.bos_token_id)
print("EOS token: ", tokenizer.eos_token_id)
print("PAD token: ", tokenizer.pad_token_id)
print("UNK token: ", tokenizer.unk_token_id)

print(tokenizer.decode([130004,
          67470,     24,  83049,      4,  76699,     24,  83049,      4,  67357,
          65065,     24,  83049,      4,  64484,  68137,  63940,     24,  64539,
          63972,      4,  69670,  72232,  69023,     24,  83049,      4,  64372,
          64149,     24,  83049,      4,  63855,     24,  83049, 130005]))

# 这个是chatglm特有的。
input_ids = tokenizer.build_inputs_with_special_tokens([1], [2])

print(input_ids)

我们要注意看一下特殊标记是否为空,其它的话一些编码、解码、分词、tokenizer(文本)返回什么(input_ids、attention_mask)之类的。可以根据自己的需要进行尝试。

Part4模型

模型加载方式的话,一般使用的是AutoTenizer和AutoModelForCausalLM,但有的模型可能这么加载会报错。比如LLaMA的加载方式就是:LlamaForCausalLM和LlamaTokenizer,。针对于chatglm的话,加载方式为:AutoTenizer和AutoModel,但需要注意的是其加载的时候设置了trust_remote_code=True,该参数会根据映射找到真正使用的模型文件,比如modeling_chatglm.py。下载好模型权重后,我们可以根据情况先看看效果,在test_model.py里面:

from transformers import AutoTokenizer, AutoModel
tokenizer = AutoTokenizer.from_pretrained("model_hub/chatglm-6b", trust_remote_code=True)
model = AutoModel.from_pretrained("model_hub/chatglm-6b", trust_remote_code=True).half().cuda()
model = model.eval()
response, history = model.chat(tokenizer, "你好", history=[])
print(response)
response, history = model.chat(tokenizer, "晚上睡不着应该怎么办", history=history)
print(response)

Part5其它

其它的一些就是结合一些库的使用了,比如:

  • deepspeed
  • transformers
  • peft中使用的lora
  • datasets加载数据

需要注意的是, 我们可以把数据拆分为很多小文件放在一个文件夹下,然后遍历文件夹里面的数据,用datasets加载数据并进行并行处理后保存到磁盘上。如果中间发现处理数据有问题的话要先删除掉保存的处理后的数据,再重新进行处理,否则的话就是直接加载保存的处理好的数据。

在SFT之后其实应该还有对齐这部分,就是对模型的输出进行规范,比如使用奖励模型+基于人类反馈的强化学习等,这里就不作展开了。

如何系统的去学习AI大模型LLM ?

作为一名热心肠的互联网老兵,我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。

但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的 AI大模型资料 包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来

所有资料 ⚡️ ,朋友们如果有需要全套 《LLM大模型入门+进阶学习资源包》,扫码获取~

👉优快云大礼包🎁:全网最全《LLM大模型入门+进阶学习资源包》免费分享(安全链接,放心点击)👈

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

img

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img

在这里插入图片描述

四、AI大模型商业化落地方案

img

阶段1:AI大模型时代的基础理解

  • 目标:了解AI大模型的基本概念、发展历程和核心原理。
  • 内容
    • L1.1 人工智能简述与大模型起源
    • L1.2 大模型与通用人工智能
    • L1.3 GPT模型的发展历程
    • L1.4 模型工程
      - L1.4.1 知识大模型
      - L1.4.2 生产大模型
      - L1.4.3 模型工程方法论
      - L1.4.4 模型工程实践
    • L1.5 GPT应用案例

阶段2:AI大模型API应用开发工程

  • 目标:掌握AI大模型API的使用和开发,以及相关的编程技能。
  • 内容
    • L2.1 API接口
      - L2.1.1 OpenAI API接口
      - L2.1.2 Python接口接入
      - L2.1.3 BOT工具类框架
      - L2.1.4 代码示例
    • L2.2 Prompt框架
      - L2.2.1 什么是Prompt
      - L2.2.2 Prompt框架应用现状
      - L2.2.3 基于GPTAS的Prompt框架
      - L2.2.4 Prompt框架与Thought
      - L2.2.5 Prompt框架与提示词
    • L2.3 流水线工程
      - L2.3.1 流水线工程的概念
      - L2.3.2 流水线工程的优点
      - L2.3.3 流水线工程的应用
    • L2.4 总结与展望

阶段3:AI大模型应用架构实践

  • 目标:深入理解AI大模型的应用架构,并能够进行私有化部署。
  • 内容
    • L3.1 Agent模型框架
      - L3.1.1 Agent模型框架的设计理念
      - L3.1.2 Agent模型框架的核心组件
      - L3.1.3 Agent模型框架的实现细节
    • L3.2 MetaGPT
      - L3.2.1 MetaGPT的基本概念
      - L3.2.2 MetaGPT的工作原理
      - L3.2.3 MetaGPT的应用场景
    • L3.3 ChatGLM
      - L3.3.1 ChatGLM的特点
      - L3.3.2 ChatGLM的开发环境
      - L3.3.3 ChatGLM的使用示例
    • L3.4 LLAMA
      - L3.4.1 LLAMA的特点
      - L3.4.2 LLAMA的开发环境
      - L3.4.3 LLAMA的使用示例
    • L3.5 其他大模型介绍

阶段4:AI大模型私有化部署

  • 目标:掌握多种AI大模型的私有化部署,包括多模态和特定领域模型。
  • 内容
    • L4.1 模型私有化部署概述
    • L4.2 模型私有化部署的关键技术
    • L4.3 模型私有化部署的实施步骤
    • L4.4 模型私有化部署的应用场景

学习计划:

  • 阶段1:1-2个月,建立AI大模型的基础知识体系。
  • 阶段2:2-3个月,专注于API应用开发能力的提升。
  • 阶段3:3-4个月,深入实践AI大模型的应用架构和私有化部署。
  • 阶段4:4-5个月,专注于高级模型的应用和部署。
这份完整版的所有 ⚡️ 大模型 LLM 学习资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费

全套 《LLM大模型入门+进阶学习资源包↓↓↓ 获取~

👉优快云大礼包🎁:全网最全《LLM大模型入门+进阶学习资源包》免费分享(安全链接,放心点击)👈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值