ARTICLE DETAIL

资讯详情

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

Transformers实用指南:文本分类、问答、摘要与翻译落地实践

Transformers实用指南:文本分类、问答、摘要与翻译落地实践 做NLP落地这几年我越来越觉得Transformers已经不算是一个单纯的模型库而更像是一套完整的工程生态。前一篇写了基础的环境准备和模型加载这篇“实用AI解决方案二”我想直接围绕几个真实业务场景来展开文本分类、抽取式问答、文本摘要、翻译以及大家最常踩的坑。目标读者是那些已经跑通Hello World、准备拿Transformer做活儿但又不知道从哪下手的Python开发者。别急着上大模型这里的每个方案都能直接复用在轻型业务里。1. 内容整体设计与思路拆解1.1 这次要解决的到底是什么问题很多初学者拿到Transformers库之后第一反应是去跑一个sentiment-analysis的Pipeline看到输出“positiveconfidence 0.98”就以为自己会NLP了。但一旦进入真实项目问题马上变样老板要的是把客服工单自动分到十几个部门里或者是让系统从几千页合同里找出违约金条款对应的原文段落再或者每天凌晨自动把一堆新闻压缩成50字摘要推到群里。这些需求看起来各不相同本质上是四类任务序列分类、抽取式问答、生成式摘要和机器翻译。这四类恰好对应Transformers库中最成熟、最不容易翻车的四个能力点。我这一篇会按照实际项目的推进节奏来讲先讲清楚每个任务适合用什么模型再把能够直接跑通的代码贴出来最后把我在真实环境里遇到的那些奇奇怪怪的问题列个速查表。1.2 为什么仍然选择Transformers这两年大模型火得不行很多人一上来就想直接调GPT级别的接口。但对于绝大多数中小型项目微调或直接推理一个本地的基础模型成本、延迟和数据私密性都是更有优势的方案。项目里典型的场景是数据量不大但领域特征明确比如法律文本、医疗文本、客服话术通用大模型的提示词模式反而容易把语义搞偏。Transformers库的价值在于抽象层次非常舒服。它把tokenizer、模型、训练器、推理管线全都封装成标准接口你不需要关心attention机制到底怎么算但也保留了下钻修改的空间。我在实际项目里有一半时间是在写数据清洗和结果后处理模型本身反而不用动这就是这个生态最让我舒服的地方。注意这里说的“不用动模型”是指推理和微调层面。如果你的任务领域差异特别大该做领域微调还是得做后面我会具体说什么时候必须微调。1.3 方案选型的关键取舍选型这件事我一般只看三个维度任务类型、数据规模、响应速度要求。文本分类我首选蒸馏版或轻量版模型比如distilbert准确率只略低于完整版但推理速度快一大截抽取式问答优先选择mrc类的模型比如bert-large-uncased-whole-word-masking-finetuned-squad中文对应的是哈工大讯飞的中文模型或macbert-large摘要任务我会看内容长度短文本用t5-small或中文cpm系列长文本干脆走抽取式摘要逻辑先掐关键句再生成。翻译这块如果是常见语种中英、英日这类Helsinki-NLP的opus系列和mbart都是稳妥选择。注意翻译模型对语言前缀敏感加载时记得设置src_lang和tgt_lang。具体代码下一节给全。2. 核心细节解析与实操要点2.1 Pipeline机制最容易被高估也最容易被低估的入口我刚接触Transformers时觉得Pipeline就是个玩具后来才发现它其实是工程落地的第一道标准接口。Pipeline把tokenizer、模型、解码策略、后处理全部统一起来输入原始文本直接出结构化结果。对于生产环境里的第一版我经常直接用Pipeline做业务验证确认效果没问题后再慢慢替换成底层调用以便做性能优化。Pipeline的构造其实非常灵活你可以显式指定模型和tokenizer而不是总用默认的。比如做中文情感分析时默认模型的效果通常一般手动指定一个中文预训练模型就好很多from transformers import pipeline classifier pipeline( sentiment-analysis, modeluer/roberta-base-finetuned-jd-binary-chinese, tokenizeruer/roberta-base-finetuned-jd-binary-chinese ) result classifier(这个手机的电池续航非常满意但屏幕边框有点宽) print(result)Pipeline的任务名决定了解码头的行为常见的有sentiment-analysis、text-classification、question-answering、summarization、translation_en_to_zh、ner、text-generation。我建议把所有可复用的Pipeline实例化一次放到模块里而不是每次请求都重新加载模型毕竟模型加载的耗时在CPU环境里可能占掉整个接口90%的延迟。2.2 Tokenizer与模型的三步调用法出了Pipeline的舒适区就要面对三件套tokenizer、model、processor。很多从别的框架转过来的同学最容易犯的错是直接用model(text)这在老版本里还能跑新版会直接抛错。标准做法是先走tokenizer再走模型再做后处理。我先给一个分类任务的标准实操片段from transformers import AutoTokenizer, AutoModelForSequenceClassification import torch model_name IDEA-CCNL/Erlangshen-Roberta-110M-Sentiment tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForSequenceClassification.from_pretrained(model_name) texts [产品质量很好下次还会回购, 发货速度太慢了体验很差] inputs tokenizer( texts, paddingTrue, truncationTrue, max_length128, return_tensorspt ) with torch.no_grad(): logits model(**inputs).logits preds torch.argmax(logits, dim-1) print(preds)这里三个参数我解释一下。paddingTrue保证一个batch里所有样本长度一致才能拼成张量truncationTrue处理超长文本超过max_length的直接截断return_tensorspt返回PyTorch格式。这三步看起来简单但每个参数都有讲究max_length选64还是512直接影响效果和速度长文本分类通常需要128以上但也不是越大越好显存压力高还容易过拟合。2.3 关于显卡、显存与精度的一个理性选择很多教程一上来就默认你有N卡但真实项目里面CPU推理占了很大比例。如果业务量不大或者任务允许一定的延迟我建议先用CPU跑通整条链路再做性能优化。CPU上做torch.set_num_threads()调整线程数把model model.eval()和torch.no_grad()用起来推理速度能提升不少。GPU场景下最大的问题是显存溢出。一个BERT-base是110M参数FP32约需440MB显存看起来不大但batch一上去加上中间激活值很容易吃掉好几GB。如果你的显存是4GB这种尴尬级别记住几个办法降低max_length、减小batch_size到1或2、开启model.half()用FP16推理但注意FP16在某些CPU或老显卡上不支持测的时候要小心。提示如果显存不够又必须上大batch可以试试gradient_accumulation_steps但这是在训练场景下用的推理场景老老实实减小batch即可。推理时显存OOM可以考虑分批传入不要一次性把所有文本都丢进去。3. 实操过程与核心环节实现3.1 环境准备与依赖安装我默认你已经装好了Python 3.8以上版本并且有一个干净的conda环境。强烈建议用虚拟环境装Transformers因为它的依赖torch、tokenizers、safetensors、huggingface-hub版本锁得比较紧装在全局环境里极易跟其他项目打架。安装指令按用途区分# 基础推理 pip install transformers torch # 如果要用训练器做微调 pip install transformers[torch] datasets accelerate # 需要词表扩展或是音频/视觉模型时 pip install transformers[sentencepiece]装完后第一件事不是跑模型而是先确认一下版本python -c from transformers import __version__; print(__version__)官方文档里说Transformers要求Python 3.8但实际体验3.10和3.11更省心3.7会遇到一堆依赖兼容问题。PyTorch的安装最好参照官网给出的对应CUDA版本命令不要用pip默认源直接装容易装成CPU版。3.2 文本分类场景的完整实现文本分类是我个人认为最适合作为Transformers第一个落地任务的方向因为模型选择多、性能容易达标、效果评估直观。这里的业务背景是有一堆用户评论需要区分正面和负面同时还想知道具体是哪个方面出了问题。完整代码如下包含输出格式化直接就可以接进任何业务逻辑里from transformers import pipeline classifier pipeline( text-classification, modelIDEA-CCNL/Erlangshen-Roberta-110M-Sentiment, top_kNone ) def classify_comments(comment_list): results [] for comment in comment_list: raw classifier(comment, truncationTrue, max_length128)[0] # 有时模型返回的是列表套字典做一层兜底 if isinstance(raw, list): raw raw[0] results.append({comment: comment, label: raw[label], score: round(raw[score], 4)}) return results comments [ 摄像头清晰度不错夜景模式尤其惊艳。, 开机广告太多了而且跳过按钮藏得很深。, 客服处理退换货很迅速点赞 ] for item in classify_comments(comments): print(item)这段代码看起来简单里面有两个实战细节。第一truncationTrue和max_length128必须显式传否则超长评论会被截断而某些模型在超长文本上会直接报错别等到线上跑挂了再回来加参数。第二不同模型的label格式不统一有些返回positive/negative有些返回LABEL_0/LABEL_1所以输出前做一层映射非常重要。我习惯在初始化后打印一次分类器的model.config.id2label把所有LABEL编号对应到业务术语后再上线。3.3 抽取式问答与摘要生成的实现问答和摘要是我在业务里经常放在一起用的两个能力因为上游都是文档处理下游结果一个用来定位答案一个用来压缩内容。抽取式问答的经典模型是deepset/bert-base-cased-squad2中文对应可以用bert-base-chinese在CMRC2018上微调的版本。我把模型名放在配置里方便切换from transformers import pipeline qa_pipeline pipeline( question-answering, modeldeepset/bert-base-cased-squad2 ) context 南方航空公司是中国运输飞机最多、航线网络最发达、年客运量最大的航空公司。 公司坚持安全第一、客户至上的理念2023年全年运输旅客超过1.2亿人次。 question 南方航空2023年运输旅客多少人次 answer qa_pipeline(questionquestion, contextcontext, top_k3) for item in answer: print(f答案: {item[answer]}, 置信度: {item[score]:.4f})top_k3可以返回多个候选答案这在长篇文档里特别好用不会因为模型第一次预测偏差就整个失败。不过要注意抽取式问答只能给出原文里存在的片段如果问题的答案需要推理、归纳它就给不出东西了。这种情况要么换生成式模型要么在业务侧做规则兜底。摘要生成这块短文本直接上t5-small或者中文cpm-t5就能看效果。但如果是几千字的长文本t5输入长度有上限直接用会丢信息我的做法是先按段落切块再选用TextRank抽取关键句合并到1024字以内最后交给模型生成。这样既保持关键词的覆盖又控制了计算量from transformers import pipeline summarizer pipeline(summarization, modelt5-small) long_text 这里放一篇很长的新闻正文或者会议纪要... # 业务里先切片再送入 for chunk in [long_text[i:i800] for i in range(0, len(long_text), 800)]: summary summarizer(chunk, max_length60, min_length20, do_sampleFalse)[0][summary_text] print(summary)生成式模型里do_sampleFalse意味着贪婪解码结果稳定、适合生产do_sampleTrue则会引入随机性适合需要多样文案的场景。我见过有人接口里用do_sampleTrue但没固定随机种子同一段话每次返回结果都不一样客服工单摘要搞得对不上记录这种事得提前避免。4. 常见问题与排查技巧实录4.1 模型加载慢与连接中断模型加载慢的根因多半是首次从Hugging Face Hub拉权重。如果服务器在国外网络环境还好国内经常出现超时或者下载到一半失败。解决办法是在代码里显式设镜像或本地路径我在项目里一般这么处理export HF_ENDPOINThttps://hf-mirror.com或者更稳妥一点干脆提前在本地把模型下载好然后把from_pretrained传本地路径。用snapshot_download方式可以只拉特定模型文件不用整个仓库全下载。实测下来模型加载时间从分钟级降到秒级接口冷启动不再成为瓶颈。如果你在本地测试时发现每次加载都重新走一遍下载检查一下~/.cache/huggingface目录是否被清理过或者环境变量HF_HOME有没有指向正确位置。4.2 显存不足CUDA OOM推理时最常见的是CUDA out of memory。出现这个错误先别加显存先看batch size和输入长度。我优化过的一个项目输入评论平均长度只有200字但代码里默认max_length是512batch还送16条一下子占掉了近4GB显存。把max_length改成128、batch降到8之后显存占用直接掉了65%。还有一个容易被忽略的点模型推理完没有释放显存。循环里每次推理都构造新的输入tensor并且累积了梯度历史虽然推理模式可以不计算梯度但如果不加with torch.no_grad():显存会缓慢增长直到爆炸。建议把推理封装成函数内部统一走no_grad上下文。如果必须在有限显存里跑更大的模型可以考虑model.half()转半精度收益和风险并存半精度下某些算子会有精度损失但QA和分类这类任务通常影响不大。提示显存里的torch.cuda.empty_cache()只能清理缓存显存并不能解决真正的OOM。频繁调用清缓存反而降低性能尽量从batch和长度入手。4.3 中文分词与效果不佳的问题排查用Transformers做中文任务最容易翻车的是分词和词表不匹配。不要默认所有中文模型都自带中文分词。很多多语言模型如bert-base-multilingual-cased用的是字符级WordPiece对中文支持确实没问题但有些基于英文语料的模型会把中文按空格或者标点切得稀碎导致效果断崖式下跌。经验是中文任务优先选专门的中文预训练模型不光是效果词表大小和tokenizer逻辑都对得上。判断一个模型适不适合中文直接看模型的tokenizer在中文句子上的切分结果如果被切成了单字或UNK占多数果断换模型。另外一份数据里可能有简繁混用或者繁体中文口语别忘了统一转简体别把这种脏数据问题甩给Tokenizer。还有一种很隐蔽的情况数据里有特殊字符比如\u3000全角空格、不可见字符进了模型后tokenizer输出的input_ids完全错位。我的习惯是所有文本进模型之前先做一层清洗删除控制字符和自动把全角标点转半角这层逻辑虽小但在生产环境帮了大忙。4.4 常见问题速查表症状根因处理方式首次加载模型特别慢网络拉取权重设置HF_ENDPOINT镜像或本地缓存推理时CUDA OOMbatch过大或max_length过长减小batch与长度改用half精度输入直接报错忘记tokenizer转换使用tokenizer(...)传参并return_tensors中文效果明显偏差模型词表不适合中文换用专门的中文预训练模型输出label理解不了标签映射未处理检查model.config.id2label并做映射同一输入多次结果不同do_sampleTrue导致随机输出设置固定seed或改do_sampleFalse长文档摘要内容丢失输入长度限制导致截断先按段切块抽取关键句再生成接口冷启动延迟高模型在每次请求时重新加载使用全局单例初始化Pipeline5. 一些更接地气的建议这篇写的所有代码我自己基本都是在CPU上先跑通再上GPU的不是不信任GPU而是CPU排查问题方便内存溢出、语法问题能立即暴露。等逻辑稳定后再切GPU做性能压测两轮下来踩坑成本最低。另外不管项目多小我都建议把模型名、任务类型、max_length这些参数放到项目配置里而不是散落在代码各处。因为模型更新换代太快了今天用的模型下周可能就该换了改一处配置总比全局替换字符串安全。如果你想把这一套能力真正接到业务里我个人体会是把Pipeline实例化放在服务启动阶段业务请求只走推理不要每次都做tokenizer初始化和模型加载。这里没有太玄的东西就是工程上“初始化一次复用到底”的常识但在NLP项目里格外重要。
返回列表