写点什么

LLM 大模型学习必知必会系列 (五):数据预处理 (Tokenizer 分词器)、模板(Template)设计以及 LLM 技术选型

作者:AI课程
  • 2024-05-21
    浙江
  • 本文字数:5956 字

    阅读完需:约 20 分钟

LLM 大模型学习必知必会系列(五):数据预处理(Tokenizer分词器)、模板(Template)设计以及LLM技术选型

LLM 大模型学习必知必会系列(五):数据预处理(Tokenizer 分词器)、模板(Template)设计以及 LLM 技术选型

在模型训练过程中,数据及数据处理是最为重要的工作之一。在当前模型训练流程趋于成熟的情况下,数据集的好坏,是决定了该次训练能否成功的最关键因素。


在上一篇中,我们提到了模型训练的基本原理是将文字转换索引再转换为对应的向量,那么文字转为向量的具体过程是什么?

1.分词器(Tokenizer)

在 NLP(自然语言处理)领域中,承担文字转换索引(token)这一过程的组件是 tokenizer。每个模型有自己特定的 tokenizer,但它们的处理过程是大同小异的。


首先我们安装好魔搭的模型库 modelscope 和训练框架 swift:


#激活conda环境后pip install modelscope ms-swift -U
复制代码


我们使用“千问 1.8b”模型将“杭州是个好地方”转为 tokens 的具体方式是在 python 中调用:


from modelscope import AutoTokenizertokenizer = AutoTokenizer.from_pretrained("qwen/Qwen-1_8B-Chat", trust_remote_code=True)print(tokenizer('杭州是个好地方'))#{'input_ids': [104130, 104104, 52801, 100371], 'token_type_ids': [0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1]}
复制代码


其中的 input_ids 就是上面我们说的文字的 token。可以注意到 token 的个数少于实际文字的数量,这是因为在转为 token 的时候,并不是一个汉字一个 token 的,可能会将部分词语变为一个 token,也可能将一个英文转为两部分(如词根和时态),所以 token 数量和文字数量不一定对得上。

2.模板(Template)

每种模型有其特定的输入格式,在小模型时代,这种输入格式比较简单:


[CLS]杭州是个好地方[SEP]
复制代码


[CLS]代表了句子的起始,[SEP]代表了句子的终止。在 BERT 中,[CLS]的索引是 101,[SEP]的索引是 102,加上中间的句子部分,在 BERT 模型中整个的 token 序列是:


101, 100, 1836, 100, 100, 100, 1802, 1863, 102
复制代码


我们可以看到,这个序列和上面千问的序列是不同的,这是因为这两个模型的词表不同。


在 LLM 时代,base 模型的格式和上述的差不多,但 chat 模型的格式要复杂的多,比如千问 chat 模型的 template 格式是:


<|im_start|>systemYou are a helpful assistant!<|im_end|><|im_start|>userHow are you?<|im_end|><|im_start|>assistant
复制代码


其中“You are a helpful assistant!”是 system 字段,“How are you?”是用户问题,其他的部分都是 template 的格式。


system 字段是 chat 模型必要的字段,这个字段会以命令方式提示模型在下面的对话中遵循怎么样的范式进行回答,比如:


“You are a helpful assistant!”“下面你是一个警察,请按照警察的要求来审问我”“假如你是一个爱哭的女朋友,下面的对话中清扮演好这个角色”
复制代码


system 字段规定了模型行为准则,比如当模型作为 Agent 使用时,工具集一般也是定义在 system 中的:


“你是一个流程的执行者,你有如下工具可以使用:工具1:xxx,输入格式是:xxx,输出格式是:xxx,作用是:xxx工具2:xxx,输入格式是:xxx,输出格式是:xxx,作用是:xxx”
复制代码


复杂的 template 有助于模型识别哪部分是用户输入,哪部分是自己之前的回答,哪部分是给自己的要求。


比较麻烦的是,目前各开源模型还没有一个统一的 template 标准。在 SWIFT 中,我们提供了绝大多数模型的 template,可以直接使用:


register_template(    TemplateType.default,    Template([], ['### Human:\n', '{{QUERY}}\n\n', '### Assistant:\n'],             ['\n\n'], [['eos_token_id']], DEFAULT_SYSTEM, ['{{SYSTEM}}\n\n']))
#ou can set the query as '' to serve as a template for pre-training.register_template(TemplateType.default_generation, Template([], ['{{QUERY}}'], None, [['eos_token_id']]))register_template( TemplateType.default_generation_bos, Template([['bos_token_id']], ['{{QUERY}}'], None, [['eos_token_id']]))
qwen_template = Template( [], ['<|im_start|>user\n{{QUERY}}<|im_end|>\n<|im_start|>assistant\n'], ['<|im_end|>\n'], ['<|im_end|>'], DEFAULT_SYSTEM, ['<|im_start|>system\n{{SYSTEM}}<|im_end|>\n'])register_template(TemplateType.qwen, qwen_template)register_template(TemplateType.chatml, deepcopy(qwen_template))...
复制代码


有兴趣的小伙伴可以阅读:https://github.com/modelscope/swift/blob/main/swift/llm/utils/template.py 来获得更细节的信息。


template 拼接好后,直接传入 tokenizer 即可。


微调任务是标注数据集,那么必然有指导性的 labels(模型真实输出)存在,将这部分也按照 template 进行拼接,就会得到类似下面的一组 tokens:


input_ids: [34,   56,   21,   12,   45,   73, 96, 45, 32, 11]           ---------用户输入部分---------   ----模型真实输出----labels:    [-100, -100, -100, -100, -100, 73, 96, 45, 32, 11]
复制代码


在 labels 中,我们将用户输入的部分(问题)替换成了-100,保留了模型输入部分。在模型进行运算时,会根据 input_ids 的前面的 tokens 去预测下一个 token,就比如:


已知token                    预测的下一个token34                        ->1734,56                     ->89...34,56,21,12,45            ->12134,56,21,12,45,73         ->9934,56,21,12,45,73,96      ->4534,56,21,12,45,73,96,45   ->1434,56,21,12,45,73,96,45,32->11
复制代码


可以看到,这个预测不一定每个都预测对了,而且呈现了下三角矩阵的形态。那么训练的时候就可以这样进行对比:


34,   56,   21,   12,   45,  121,  99,  45,  32,  11-100, -100, -100, -100, -100, 73,  96,  45,  14,  11
复制代码


-100 部分计算 loss 时会被忽略,因为这是用户输入,不需要考虑预测值是什么。只要对比下对应的位置对不对就可以计算它们的差异了,这个差异值被称为 loss 或者残差。我们通过计算梯度的方式对参数进行优化,使模型参数一点一点向真实的未知值靠近。使用的残差算法叫做交叉熵


在 SWIFT 中提供了根据模型类型构造 template 并直接转为 token 的方法,这个方法输出的结构可以直接用于模型训练和推理:


from swift.llm.utils import get_template, Templatefrom modelscope import AutoTokenizertokenizer = AutoTokenizer.from_pretrained("qwen/Qwen-1_8B-Chat", trust_remote_code=True)template: Template = get_template(    'qwen',    tokenizer,    max_length=256)resp = template.encode({'query': 'How are you?', "response": "I am fine"})[0]print(resp)#{'input_ids': [151644, 8948, 198, 2610, 525, 264, 10950, 17847, 13, 151645, 198, 151644, 872, 198, 4340, 525, 498, 30, 151645, 198, 151644, 77091, 198, 40, 1079, 6915, 151645], 'labels': [-100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, 40, 1079, 6915, 151645]}
复制代码


input_ids 和 labels 可以直接输入模型来获取模型的输出:


from modelscope import AutoModelForCausalLMimport torchmodel = AutoModelForCausalLM.from_pretrained("qwen/Qwen-1_8B-Chat", trust_remote_code=True).to(0)resp = {key: torch.tensor(value).to(0) for key, value in resp.items()}output = model(**resp)print(output)
复制代码

3.方法选型

判断自己的场景需要什么样的方法是使用 LLM 的第一步。下面我们会对比直接推理(提示词工程)、训练、RAG、Agent 方法的具体场景,讲解这几种方式的特点,并给出适用场景、使用难度、准确性、成本、缺点几个方面的总结。

3.1 直接推理(提示词工程)

这种方式特指直接使用现有 LLM,利用 prompt 范式激活模型不同的能力完成特定需求。直接推理方式对开发的要求较低,一般可以完成通用类型的任务,如通用知识问答、角色扮演等。使用方式如下:


用户:你是一个经验丰富的导游,请使用导游的话术回答游客的问题。模型:当然可以!请问你需要问些什么呢?用户:我想去杭州旅行,请告诉我哪里比较值得去。模型:当然可以!作为一个导游,我可以为你讲解杭州的风景和美食...
复制代码


  • 使用难度

  • 较低,只需要调用模型接口,编写对应的 prompt 即可。但编写好的 prompt 也是具有一定技巧的,具体可以查看我们的教程中的提示词工程部分。

  • 提示词工程无论是直接推理或训练后推理都是需要的

  • 适用场景

  • 视模型本身的能力而定,在采用该方式之前需要对现有模型针对自己的业务领域进行较为充分的评估。

  • 准确性

  • 由于是原始模型只接受了通用知识的训练,因此在特定领域的场景下可能存在胡编乱造的可能性(幻觉问题)。使用者需要注意自己的专业场景下是否使用该通用模型能解决所有问题,一般建议直接试用该模型给出模型能力的具体评估。

  • 成本

  • 开发成本较低。如果是开源模型,需要选用合适的硬件及推理方式。这部分在我们教程中的推理章节会有讲解。如果是闭源调用,只需要使用对应模型的接口 API 即可。

  • 缺点

  • 由于模型没有经过针对特有领域的知识,因此效果会比较不可控。比如,在评测时模型表现尚可,但在实际使用中发现模型出现了严重的幻觉和知识匮乏问题,如果是闭源调用则该问题会比较难以解决(可能涉及到工程架构改变),如果是开源模型可以考虑使用训练和 RAG 的方式解决。

3.2 训练

全量训练和轻量训练是训练的两种方式,它们的区别在于:


全量训练在给定 LLM 模型上冻结一定的参数(或不冻结任何参数)进行训练,一般耗费显存较高,训练周期比较长。受限于成本问题,最近出现了轻量微调方式,主要方案是在模型结构上附着一个额外结构,在训练时冻结原模型并训练额外结构,推理时将额外结构加载起来或合并回原来模型(并不是所有的额外结构都支持合并,支持者比如 LoRA,不支持者比如 Side)。轻量微调目前的最流行结构是 LoRA,该结构理解简单,训练成本较低,在部分任务上可以达到全量微调的效果。


轻量微调另一个方式就是量化(请查看另一篇文章),即对模型的 float32 权重或 float16 权重进行缩放,使其变成 int 类型的整形,节省显存或计算时长。


  • 一般情况下建议选择轻量训练,优先使用 LoRA 等方式

  • 如果效果不好,可以考虑解冻原模型的部分参数,比如 normalizer、embedder 等进行训练,也就是全量训练+轻量训练的方式

  • 如果显存受限,可以考虑使用量化进行训练。量化和轻量训练并不互斥,比如 QLoRA(量化+LoRA),但需要注意量化后训练和推理结果会有所下降


一般来说,预训练或继续训练不建议使用轻量训练,小数据量微调情况下建议优先使用轻量训练。



  • 适用场景


  • 场景存在特殊知识,需要进行知识灌注,可以使用继续训练+全量训练

  • 需要对回复的风格或范式进行定制化,可以使用人类对齐训练或微调+全量/轻量训练

  • 模型原有能力不够,如对读入的 doc 文件进行理解并进行归纳总结,或特有场景的文本进行分类,但原有模型对该任务的回答存在问题,可以使用微调+全量/轻量训练


简单来说,模型的训练是让模型“找规律”的过程。比如告诉模型 1+1=2, 2+2=4,那么让模型分析 3+3=?

如果数据是带有规律的,比如文字顺序、逻辑关系、图片元素(比如斑马总是带有黑白色的条纹),那么训练就可以将这些规律抽象出来;如果数据是“无规律的知识”,比如用 A 解决 B 问题,用 C 解决 D 问题,那么这些数据训练后就几乎不具有泛化性,因为模型无法分析出出现了 E 问题应该用 A 解决还是 B 解决,这时候应当选用 RAG 或者 Agent 方式,或者训练的目标改为让模型熟悉使用工具来解决问题。


  • 使用难度

  • 训练需要对模型结构、训练的流程有所了解。其理解成本比 RAG 高一些。

  • 准确性

  • 准确性依照训练的结果而定,训练后模型会按照训练集的风格和方式回答问题。一般来说训练后模型能力会有较大提升,但仍然可能存在幻觉问题。

  • 成本

  • 可以查看 SWIFT 的benchmark。我们比较了主要模型的训练显存需求和训练速度,用户可以按需评估。




  • 缺点

  • 相比 RAG,输出可解释性不强

  • 存在幻觉问题

  • 在精确问答场景上可能会产出非专业结果(如法律行业)

  • 对知识更新频繁的场景不适用

3.3 RAG

RAG 即检索增强生成,也就是通过模型外挂知识库的方式来辅助模型回答。一般来说会将用户问题变为向量,进向量数据库进行查询,并召回符合条件的文档或回答,之后将回答直接返回或输入模型整理后返回。RAG 可以查看另一篇教程。


RAG 和微调的选型问题一直是被问的较多的问题之一,两种方法的对比可以查看下表:



如果模型本身对专业知识理解不够(比如模型对召回的文档不能进行良好分析的情况),那么使用 RAG 是不够的,需要进行模型训练,或将模型训练和 RAG 结合起来使用。


  • 适用场景

  • 需要根据语料精确回答,比如法律或医疗领域

  • 搜索召回场景,比如搜索引擎

  • 知识频繁更新,灵活性较强的场景

  • 使用难度

  • 需要对 RAG 流程有所了解,选用对应的 RAG 框架或解决方案

  • 准确性

  • 准确性较高,可解释性也较高

  • 成本

  • 除模型本身的成本外,需要额外的向量数据库和工程端开发成本和维护成本

  • 缺点

  • 比模型直接回答多了查询召回步骤,单请求整体 RT 高一些

  • 如果场景和知识无关,比如非知识类问答,或 API 调用,或文档分析,文章总结等,RAG 就不能起到作用

3.4 Agent

Agent 适合于利用模型进行代码编写运行、API 调用的复杂场景。Agent 的主要思路是利用模型的 CoT(思维链)能力进行复杂场景的流程串接。比如“生成一个具有今天天气特征的海报”,模型会先调用天气预报接口获得天气,之后生成海报文案,然后调用文生图模型生成海报。


  • 适用场景

  • 复杂的应用场景,需要模型产生思维过程,将整体任务拆分为具体任务进行执行,比如包含了运行代码、接口调用等过程

  • 使用难度

  • 需要对 Agent 和 CoT 过程有一定了解

  • 熟悉目前的 Agent 框架的能力上限

  • 需要熟悉模型的提示词工程才能做到较好的效果

  • 准确性

  • 一般来说模型越大准确性越高。比如 GPT4(闭源)、Qwen-max(闭源)、Qwen-72b(开源)、ChatGLM4(闭源)等会具有良好的效果,小模型可能需要特殊训练。

  • 在格外复杂的场景下,比如任务复杂、描述含混不清、模型对行业流程不理解的情况下,需要对模型进行额外训练。

  • 成本

  • 一般和模型部署成本相同

  • 缺点

  • 对复杂的业务和人类复杂的行为如博弈、交流->更新的场景支持不好。比如,尚不能用 Agent 编写复杂的 APP 如淘宝,也不能模拟股市大盘。

4.模型选型

目前国内外开源模型已经超过了几百个,挑选合适的模型是一个比较关键的问题。在这里可以给出一些泛泛的意见:


  • Agent 场景尽量选择较大的模型或者闭源 LLM API(如 GPT4、Qwen-max)

  • 训练场景中,数据量较大(比如大于 10000 条)、数据质量较高、专业度较高的训练优先选择 base 模型,数据量较少优先选择 chat 模型。在算力允许条件下可以进行对比训练实验

  • 关注国内外的开源可信模型榜单,选择排名较高或口碑较好的模型


具体参考博客:大模型落地实战指南:从选择到训练,深度解析显卡选型、模型训练技、模型选择巧


更多优质内容请关注公号:汀丶人工智能;会提供一些相关的资源和优质文章,免费获取阅读。

发布于: 刚刚阅读数: 5
用户头像

AI课程

关注

本博客将不定期更新关于NLP等领域相关知识 2022-01-06 加入

本博客将不定期更新关于机器学习、强化学习、数据挖掘以及NLP等领域相关知识,以及分享自己学习到的知识技能,感谢大家关注!

评论

发布
暂无评论
LLM 大模型学习必知必会系列(五):数据预处理(Tokenizer分词器)、模板(Template)设计以及LLM技术选型_人工智能_AI课程_InfoQ写作社区