# 聊聊Python transformers这个库
做了几年NLP相关的工作,接触过的框架和库少说也有十几个。但要说哪个库让我觉得“这个团队是真的在认真做工程”,那Hugging Face的transformers绝对排在前列。它不是那种学术原型代码,而是真正能直接扔到生产环境用的东西。
它是个什么东西
简单来说,transformers是一个把各种预训练模型打包好的工具库。你可能会想,这不就是模型动物园吗?类似的想法我之前也有,但实际用下来,它的设计理念要更深一些。
它的核心其实是一个统一的接口层。不管你用的是BERT、GPT、T5还是LLaMA,调用方式几乎是相同的。这种设计听起来简单,做起来却不容易。因为不同模型的结构差异非常大:有的是encoder-only,有的是decoder-only,有的是encoder-decoder。Transformers通过一套精心设计的抽象,把这些差异都藏在了背后。
举个例子,对于序列分类任务,不管是BERT还是RoBERTa,你都是这么写:
fromtransformersimportAutoModelForSequenceClassification,AutoTokenizer model=AutoModelForSequenceClassification.from_pretrained("bert-base-uncased")tokenizer=AutoTokenizer.from_pretrained("bert-base-uncased")换了模型,只要改个名字就行。这个设计理念背后,其实是Hugging Face团队对NLP领域数年的积累——他们很清楚哪些差异是可以抽象的,哪些差异必须暴露给用户。
能解决什么问题
Transformers覆盖的场景其实比大多数人想象的要广。除了常见的文本分类、命名实体识别、问答系统这些,它还能做:
- 多模态任务:比如图像描述生成、视觉问答。CLIP、BLIP这些模型都是直接支持的。
- 语音处理:Whisper、Wav2Vec2这些语音模型也在里面。你可以用同一个API做语音识别或语音分类。
- 代码生成:CodeGen、StarCoder这些代码模型也一样能用。
我最近在做一个项目,需要把客服对话中的语音转成文本,然后用实体识别提取关键信息,最后用摘要模型生成工单。如果不用transformers,你可能需要拼凑四五个不同的库,每个库的接口习惯都不同。但用了transformers,所有模型都遵循同样的加载和使用模式,开发效率提升的不是一星半点。
实际怎么用
说真的,transformers的API设计是我见过的ML库里最直观的之一。不过,要想用得顺手,还是有几点需要注意。
首先要明白一个概念:transformers里的模型是分成“类”的。AutoModel是一个基类,但实际使用时,你很可能要用它的子类。比如做文本分类用AutoModelForSequenceClassification,做生成用AutoModelForCausalLM。每个子类会自动在模型顶上加上对应的输出头。
加载模型时的内存管理是个容易踩坑的地方。默认情况下,模型是全精度加载到CPU再转到GPU的。如果你的显卡只有8G显存,想跑个LLaMA-7B就很吃力。这时候可以用这些技巧:
# 半精度加载model=AutoModel.from_pretrained("模型名",torch_dtype=torch.float16)# 设备映射model=AutoModel.from_pretrained("模型名",device_map="auto")# 量化,需要安装bitsandbytesmodel=AutoModel.from_pretrained("模型名",load_in_8bit=True)Tokenizer的处理也值得留意。很多新手会误以为tokenizer就是一个简单的分词器,但实际上它做的事情远比你想象的多:它会自动添加特殊标记、处理截断和填充、生成attention mask。所以:
# 正确的做法,让tokenizer自己处理inputs=tokenizer(text,max_length=512,truncation=True,padding=True,return_tensors="pt")一些实践经验
用transformers做项目这些年,积累了一些经验,不一定对每个人都适用,但可以参考。
微调的时候,不要一上来就调全模型。特别是对于BERT这种大模型,全参数微调不仅慢,而且容易过拟合。可以先用特征提取的方式,冻结大部分层,只训练顶部的分类头。等确定这个架构能用,再考虑全参数微调。这在transformers里实现起来很简单:
forparaminmodel.base_model.parameters():param.requires_grad=False# 然后只训练分类头Pipeline是个好东西,但不要过度依赖。Pipeline把模型加载、tokenizer、后处理都封装好了,拿来跑demo非常方便。但一旦进了生产环境,你会发现它少了很多控制能力。比如你想调整beam search的参数,或者想要中间层的输出,Pipeline就不太灵活了。我的习惯是:原型阶段用Pipeline,正式开发直接操作model和tokenizer。
多个模型串联时要留意内存。假设你在做一个流程:先用一个模型做实体识别,再把结果传给另一个模型做分类。如果你同时加载两个模型,显存可能就不够用了。可以用torch.no_grad()和适时清空缓存来解决:
withtorch.no_grad():outputs1=model1(**inputs1)# 处理完第一个模型,清空缓存delmodel1 torch.cuda.empty_cache()# 再加载第二个模型model2=AutoModel.from_pretrained(...)和其他方案比比看
说到同类技术,其实可选项并不多。Facebook的fairseq曾经是主流,但现在已经没什么人维护了。Google的TensorFlow Models虽然还在更新,但生态远不如transformers丰富。
真正能跟transformers掰掰手腕的,大概只有OpenAI的API和Google Cloud的Vertex AI。但它们是商业服务,背后是封闭的模型。你用OpenAI的API,其实是用别人的模型,虽然方便,但数据要经过第三方,模型也无法自己微调。
另一种选择是直接用PyTorch或TensorFlow搭模型。这种方式的控制力最强,但工作量也最大。你要自己实现模型结构、写训练循环、处理数据加载。而且,当你想换一个模型时,几乎要重写一半的代码。Transformers的价值恰恰在于,它把那些通用的工作都替你做了,同时又保留了定制的空间。
当然,transformers也不是完美的。它的抽象层有时候会带来性能损耗,特别是在推理阶段。如果你追求极致性能,可能需要手动优化。另外,它的依赖比较重,一个transformers会带来几十个子依赖,有时候版本冲突挺烦人的。
说到底,选什么工具要看场景。如果你在做一个快速原型或者中小规模的项目,transformers是个极好的选择。如果是在工业级场景下追求毫秒级延迟,可能需要在其基础上做进一步优化。但无论如何,它都是现在NLP领域绕不开的一个重要工具。