ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Transformers实战指南:从预训练模型加载到文本生成

Transformers实战指南:从预训练模型加载到文本生成 1. 为什么我上手第一件事是放弃自己训练、转向下载预训练模型1.1 我最初的思路完全跑偏了先还原一下我自己的经历。早期做 NLP 项目的时候我以为所谓的掌握 Transformers就是要从零把注意力机制搭出来再拿自己的语料训一个像样的模型。那会儿我连 Transformer 的论文都读了好几遍手写了部分前向传播然后发现一个残酷的现实光是一个中型规模的模型跑一轮训练就要几个小时而且我手里的数据量撑死也就几万条。训完效果一测准确率还不如我用正则硬写的规则版。这件事让我意识到在 2024 年以后做 NLP绝大多数场景下训练一个模型和使用一个模型完全是两码事。后来我把思路改成了用 Python 加载别人已经训好的预训练大模型。预训练大模型这个词听起来很高端其实说白了就是有人已经花了大量算力和数据把语言的通用知识塞进了一组权重里。我们要做的只是把这些权重下载下来加载进内存然后把自己手头的文本喂给模型拿回结果。这条路才是大部分业务场景真正需要学会的。1.2 算一笔自己从头训模型的账为什么不建议自己训拿一个经典的 Base 级模型来说参数量大概在 1 亿到 3 亿之间。看上去不算夸张但注意这还只是模型结构部分的参数。要训出有意义的语言理解能力消耗比模型参数量高好几个量级数据一个像样的预训练语料通常要达到几十 GB 甚至上 TB 的纯文本。你手头的几万条评论、几千份文档在学语言这个层面连毛毛雨都算不上。算力就算是 1 亿参数的小模型在单张消费级显卡上训一次完整的预训练周期往往要连续跑几十天。我见过朋友拿 8 张卡训一个中小模型跑了一周多光是电费和云服务器费用就够买一台不错的电脑了。效果你自训的模型由于数据量和训练步数远不如社区开源模型通常学到的东西更浅。最后很可能得到一个能跑但不好用的模型反而浪费时间。当然我这么说不是否定自己训练。如果你是发论文、做特定领域研究、或者在根本没有开源模型的语种上做项目那确实需要从零训。但对 90% 的工程师、数据分析师、学生来说直接加载别人训好的模型来完成推理是回报率最高的路径。1.3 现成预训练模型能直接帮你干哪些活举几个我实际用过的例子情感分析判断一段评论是正面、负面还是中性。文本分类把客服工单自动分到退款、咨询、故障、投诉等类别。命名实体识别从合同、新闻里抽出人名、地名、公司名。文本摘要把长文章压缩成几句话。翻译中英、英中以及不少语种间的互译。问答给定一段上下文让模型回答具体问题。文本生成续写故事、写邮件草稿、生成代码片段。这些能力绝大多数都已经有开源社区公开的权重直接用即可。真正需要你动手微调的通常是领域非常专门的场景比如你有一套自己标注的行业数据希望模型更懂你们公司的业务术语。即便是微调起点也不是随机初始化而是在一个强大的预训练权重上继续学成本低得多这就是 Transformers 这套开源库存在的核心意义。2. 环境准备把 Python 装到能顺利 import transformers 的状态2.1 先建虚拟环境别往全局 Python 里硬灌第一步是装环境这一环看着简单实际上坑最多。我建议你从现在开始养成一个习惯每个项目一个虚拟环境。原因很简单transformers 和 torch 这两个包的体积很大而且对版本要求相当敏感。如果你直接在系统 Python 里装改天做个其他项目需要换 torch 版本很容易变成灾难现场。创建虚拟环境的方法很直接以 Python 3.9 及以上版本为例python -m venv .venv source .venv/bin/activate # Windows 下用 .venv\Scripts\activate激活之后命令行前面会出现一个(.venv)标识说明你已经在虚拟环境里了。后面装的包都只会进入这个环境不会污染系统全局。2.2 安装 torch 和 transformers 的正确姿势Transformers 库本身只是模型加载和推理的上层封装真正干重活的是背后的深度学习框架。目前最常见的是 PyTorch也就是torch这个包。安装命令本身不复杂pip install torch transformers sentencepiece acceleratetransformers负责加载模型和分词器torch负责矩阵计算sentencepiece是很多大模型用到的分词工具accelerate在多卡、混合精度这些场景下会用到。一次性装齐能省去后面很多缺一个依赖的报错。如果你有 NVIDIA 显卡并且确定要用 GPU 加速建议先访问 PyTorch 官网按其对应 CUDA 版本给出的命令单独安装 torch之后再装 transformers。很多人直接pip install torch也能装上但装出来的通常是 CPU 版显存完全用不上推理速度慢到怀疑人生。2.3 装完之后最该做的三件事第一件事确认版本号一致python -c import transformers; print(transformers.__version__)第二件事检查自己的硬件能不能用 GPUimport torch print(torch.cuda.is_available())输出True说明可以用 CUDA 加速如果输出False也别灰心代码照样能跑只是用 CPU速度会慢不少。CPU 也不是完全不能干活我用 Apple 芯片的 Mac 跑小型模型效果也可以接受只是大模型会吃力。第三件事确认磁盘空间。现在的预训练模型不是一个小文件一个模型目录常常 1GB 到 7GB 不等。下载之前先看看自己的磁盘剩余空间够不够我见过有人下载到一半磁盘满了然后缓存文件损坏最后还是得删掉重来。3. 第一段代码pipeline() 把整个推理过程封装成了一行调用3.1 用两三行代码跑通情感分析环境装好之后就可以接触 Transformers 库最友好的入口——pipeline()。我第一次用的时候还挺惊讶因为这比我预想的简单太多from transformers import pipeline classifier pipeline( sentiment-analysis, modeldistilbert-base-uncased-finetuned-sst-2-english ) result classifier(I really love this new feature!) print(result)输出效果类似这样的结构[{label: POSITIVE, score: 0.9997}]意思是模型认为这句话是正向情绪置信度 99.97%。咱们不需要手写分词、不需要自己构造输入张量、不需要处理输出概率三五行代码就把一个完整的预训练模型调用跑通了。3.2 为什么 pipeline 能把步骤压缩成这样很多人第一次看到这代码会好奇pipeline 到底帮我们做了什么。其实它内部依次完成了三件事把输入文本交给对应的分词器转成模型能读的 token id 序列。把 token id 交给模型做前向计算得到每个类别的分数。把分数转成可读的标签和置信度完成输出。换句话说pipeline()就是分词器 模型 后处理的打包组合。你完全可以自己一步步实现这三步但用 pipeline 可以让你快速验证想法先跑通再深入理解细节我认为这种学习顺序比我反过来去啃源码效率高很多。3.3 怎么更换模型来获得不同能力pipeline()的第一参数是任务名第二个参数可以指定具体的模型名称。我用过的几个高频组合需求任务名推荐模型示例情感分析sentiment-analysisdistilbert-base-uncased-finetuned-sst-2-english文本生成text-generationgpt2 或者更大的开源生成模型文本摘要summarizationfacebook/bart-large-cnn命名实体识别nerdslim/bert-base-NER问答question-answeringdistilbert-base-cased-distilled-squad翻译translationHelsinki-NLP 系列模型这里有必要提醒一句同样的任务不同模型效果差异可能非常大。比如用默认的情感分析模型处理中文评论几乎必翻车因为很多默认模型是英文语料训出来的。碰到中文场景就去模型库按语言筛选选合适的中文模型。模型名通常长成这样uer/roberta-base-finetuned-chinanews-chinese把这样的名字直接填进model参数即可。4. 拨开 pipeline 外壳分词器和模型到底各自干了什么4.1 Tokenizer给文本切块再翻译成数字当你不再满足于 pipeline开始想自己在代码里加载模型的时候第一个必须搞懂的概念就是 Tokenizer分词器。可以这样理解神经网络吃不了原始的字符串它只能处理数字矩阵。分词器要做的事情就是把一个句子按规则切成一个个 token再把每个 token 映射到一个唯一的数字 id。比如I love transformers这个句子可能会被切成[i, love, transform, ers]这样的 token 序列然后变成[1045, 2293, 19081, 5652]这样的数字数组。注意transformers 在这里被切成了两个 token这是子词切分的常见操作目的是让模型应对没见过的新词时也能拼出大致含义。使用分词器也很简单from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(bert-base-uncased) inputs tokenizer(I love transformers, return_tensorspt) print(inputs)return_tensorspt的意思是让分词器返回 PyTorch 张量这样可以直接送给模型。你会发现输出里不只有input_ids往往还有个attention_mask。这个 mask 的作用是告诉模型哪些位置是真正的文本哪些位置是补齐用的填充。因为批量处理时同一个 batch 里的句子会有长有短我们会把短句子统一填充到和最长句子一样的长度这个 mask 就是用来防止模型在填充符上浪费注意力的。4.2 前向传播model() 是如何把 token 变成分数的拿到 token 后下一步就是把输入扔给模型from transformers import AutoModelForSequenceClassification model AutoModelForSequenceClassification.from_pretrained(bert-base-uncased) outputs model(**inputs) logits outputs.logits print(logits)这里的AutoModelForSequenceClassification是带分类头的版本也就是说它在预训练语言模型基础上额外加了一层全连接层把最后一层输出映射到类别分数。这个分数叫 logits它还不是概率若要转成概率交给 softmax 即可。至于模型内部确实非常复杂但咱们入门阶段只需要建立一个粗印象文本 token 经过嵌入层变成向量再经过多层注意力机制每一层都会让每个 token 的表示参考整个序列的信息越深层的表示越富含上下文语义最后我们用最后一层的结果去做分类、生成或其他任务。注意力机制可以打个比方读一句话时有些词的重要性是上下文决定的。比如在警察抓住了小偷因为他跑得很快里他指代谁要看前文提到小偷这个词时模型的注意力集中在哪个 token 上。Transformer 就是通过这种动态加权的方式让文本中任意两个位置都能直接互动从而解决老式 RNN 难以处理长距离依赖的问题。4.3 为什么推荐用 AutoClass 系列接口你可能注意到我一直用AutoTokenizer、AutoModelForXxx这种带Auto开头的类。原因是这类接口会根据你传入的模型名自动判断该用哪个具体的类。不同预训练模型的代码路径差别很大有的用 BERT 结构有的用 GPT 结构有的用 T5 结构如果每次都手动指定类一来你需要记住大量细节二来容易出错。AutoClass 系列的价值就是把这层判断自动化让你把注意力放在业务逻辑上。还有个实战经验尽量用和预训练时一致的AutoTokenizer不要自己拼分词器。很多模型发布时会一起公布配套分词器你自己换一个token 序列对不上后面整个结果都会变得很怪。我最初图省事手动用一个通用分词器去加载 BERT效果惨不忍睹换回配套分词器之后一切正常。5. 文本生成实操GPT-2 等生成式模型要怎么调用、参数怎么调5.1 先跑通最基础的续写功能情感分类只是理解类任务生成类任务才是现在大模型最有魅力的地方。所谓文本生成就是给模型一个开头让它顺着语言规律继续往下写。先看一段最基础的代码from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM model_name gpt2 tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForCausalLM.from_pretrained(model_name) prompt Once upon a time, inputs tokenizer(prompt, return_tensorspt) outputs model.generate( **inputs, max_new_tokens50, do_sampleTrue, temperature0.9 ) print(tokenizer.decode(outputs[0], skip_special_tokensTrue))这里的generate()是整个生成流程的关键所在。max_new_tokens50表示最多生成 50 个新 token。do_sampleTrue表示采用概率采样而不是贪心策略让生成的文本更有变化。temperature是采样温度它控制着随机性的大小。5.2 temperature、top_p、top_k 分别控制什么生成模型的核心是每一步都计算出下一个 token 的概率分布然后从中挑一个 token 输出再把输出拼回输入继续算下一步。参数不同挑 token 的策略不同。temperature0几乎变成确定性选择每次选概率最高的词结果稳定但容易重复temperature偏高比如 1.2 以上输出会更发散、更有创意但也更容易胡言乱语top_k50表示只在概率最高的前 50 个 token 里采样避免选中那些概率极低的生僻词top_p0.9表示从累计概率达到 0.9 的那部分 token 里采样是一个动态筛选策略。我实践下来写正式文案时常用temperature0.7, top_p0.9跑代码生成的时候甚至会把 temperature 调到 0.2 左右让它更保守更可控。如果你发现生成的文本不断重复同一个词通常就是 temperature 太低或重复惩罚权重没调好可以试试repetition_penalty1.2这类参数。5.3 显存和序列长度生成任务最容易踩的坎生成式模型和分类模型有个显著区别分类只需要一次前向传播生成却需要一步步反复前向传播每生成一个 token 都要把整个序列重新过一遍模型。所以在 CPU 上跑文本生成尤其慢GPU 上也得注意显存占用。显存不够的典型报错是 CUDA out of memory。解决办法我按优先级排列换更小的模型比如从 7B 降到 1B 级别降低序列长度max_new_tokens别一下子设太高使用半精度加载model AutoModelForCausalLM.from_pretrained(model_name, torch_dtypetorch.float16)显存占用几乎减半开启加速库的 CPU 模式下放Keras 场景下也能跑但速度要打折扣。其实对我个人来说生成式模型入门前最该记住的一件事是模型看到的上下文越长生成质量未必越高但计算成本和显存占用一定越高。所以生成之前先想清楚开头到底需要多长不要无脑把全文都塞进去。6. 从能跑到耐久放进业务前我整理出的几个注意点6.1 改掉每次运行都从网络下载模型的坏习惯用from_pretrained第一次加载模型时transformers 会把它下载到本地缓存目录默认是在用户主目录下的.cache/huggingface。这个机制本身是好的但有几个小问题。第一如果你换了机器或者换了用户缓存目录变了就得重新下载一遍几 GB 的文件。第二有些离线环境不允许访问外网模型根本拉不下来。我的做法是把常用模型固定放到项目目录里的一个 models 文件夹下然后用本地路径加载# 先单独把模型下载到本地目录 huggingface-cli download gpt2 --local-dir ./models/gpt2加载的时候改为model AutoModelForCausalLM.from_pretrained(./models/gpt2)这样等于把下载和运行拆开。项目部署到内网或者容器时直接把 models 文件夹一起打包带过去既稳又快。尤其是团队合作时避免每个人各下载一套多 GB 的权重是在替大家省时间。6.2 批量推理的 hidden gem一次性处理多条文本业务场景里很少只处理一条文本。拿情感分析来说你可能有一整个 CSV 文件几千条评论要跑。这时候最直观的想法是写个循环一条条处理但速度会比较慢因为每条都重新加载模型。更好的做法是复用同一个模型对象把多条文本组成一个 batch 一起推进去。texts [I love this!, This is terrible., Not bad at all.] inputs tokenizer(texts, paddingTrue, truncationTrue, return_tensorspt) outputs model(**inputs)注意这里的paddingTrue和truncationTrue前者会补齐短句到整批最长长度后者会把超长句子截断。这正是前面说的 attention_mask 发挥作用的时候。批处理能大幅提高吞吐但一次塞太多文本也可能撑爆显存需要根据实际情况找到一个合理的 batch size我常用的是 16 或 32具体得看模型大小和文本长度。6.3 推理速度不够先做量化再做优化如果你发现模型跑得太慢或者显存一直不宽裕优先考虑量化。所谓量化简单理解就是把模型的浮点参数转成更低精度的整数表示比如从 32 位浮点转成 8 位整数。这样模型体积变小计算速度也会提升代价是精度有一点损失但在很多任务上并不明显。Transformers 配合bitsandbytes库可以几行代码加载一个 4-bit 量化模型from transformers import BitsAndBytesConfig import torch quant_config BitsAndBytesConfig(load_in_4bitTrue) model AutoModelForCausalLM.from_pretrained( model_name, quantization_configquant_config )我自己的体验是7B 模型不开量化个人电脑基本没戏开了 4-bit 量化之后显存占用降到原来的一半以下推理速度反而因为内存宽裕了有所提升。当然量化不是免费的如果在实际评估中精度明显掉了还是要回到浮点版本。6.4 遇到乱七八槽的报错先做这三件事初次接触 transformers 的朋友几乎都会在某一步卡住。我总结过最常见的三种情况缺包报错提示ModuleNotFoundError别怀疑直接pip install对应的包常见的是sentencepiece和accelerate。模型与任务不匹配用AutoModelForCausalLM去加载一个纯编码器模型会直接报错。解决办法是看清楚模型卡上的说明确认它属于 encoder、decoder 还是 encoder-decoder 架构然后选对应的 AutoModel 子类。tokenizer 和模型版本不一致如果你下载的时候是随便配的很可能出现生成的 token 无法解码成正常文本。解决方法是把模型的config.json和tokenizer_config.json放在一起加载同一个目录里的两个文件。我的习惯是在任何新模型上跑正式业务之前先拿两条样本数据快速验证一条正常文本一条极端长的文本把输入、输出、耗时都打出来看一眼。这一步几乎不需要额外时间却能避免很多只在正式运行时才暴露的坑。说到最后还想再分享一个小技巧如果你用pipeline()只是为了验证想法那非常好但项目一旦要进入批量处理或者线上服务阶段我建议直接换到底层 API也就是用AutoTokenizer加AutoModel自己管理输入输出。原因只有一个——pipeline 虽然方便但它内部的默认参数并不总是适合你的场景你很难精准控制 batch、padding 和采样参数。底层 API 看起来多写几行代码但换来的是完全的可控性和更清楚的问题定位能力。先靠 pipeline 跑通再逐步拆开自己接管这条路线我用了很多年始终觉得很划算。
返回列表