中文预训练模型选型指南:大模型、小模型与相似度模型实战对比
简介这份资源面向中文自然语言处理方向的开发者与研究者提供一套可直接上手的高质量中文预训练模型集合覆盖大模型、小模型与语义相似度模型三类需求。大模型在中文任务上达到当前最佳效果部分任务表现更优小模型速度较Bert-base提升约8倍与albert_tiny相当但效果更好语义相似度模型专为句子对与相似度问题设计通常优于直接使用预训练模型。一期已支持6个分类与句子对任务后续将覆盖CLUE benchmark全部任务。压缩包共211个文件以123个Python脚本和52个Shell脚本为主体辅以md说明、ipynb示例、txt配置及license等整体约1004KB结构清晰便于按模块查阅。已有339人学习下载适合希望快速复现、对比与落地中文预训练模型的读者参考。1. 中文预训练模型选型大模型、小模型与相似度模型到底怎么挑做中文 NLP 项目最先卡住的往往不是算法而是模型选型。你手里有一批标注数据任务可能是文本分类也可能是句子对匹配这时候摆在面前的选择通常有三条路上大模型冲效果、用小模型保速度、或者单独训一个语义相似度模型。这三条路的取舍逻辑完全不同选错了轻则效果上不去重则推理成本翻倍。这份资源包把三条路都覆盖了大模型对标当前中文效果最佳水平小模型速度比 Bert-base 提升约 8 倍、与 albert_tiny 速度持平但效果更好另外还有一个专门处理语义相似度或句子对问题的模型。一期支持 6 个分类和句子对任务后续会覆盖 CLUE benchmark 全部任务。适合正在做中文 NLP 落地、需要快速验证方案可行性的从业者也适合拿来做课程设计或毕业设计里的人工智能大作业。2. 模型体系拆解三类模型的能力边界与选型依据2.1 大模型效果优先场景下的默认选择大模型在这份资源里的定位很明确——对标当前中文上效果最佳的模型并且在部分任务上表现更好。这意味着如果你的任务对精度要求高、推理延迟不敏感大模型应该是首选。从技术路线看这类模型通常基于 Transformer 的 Encoder 架构参数量在亿级以上。常见做法是采用 RoBERTa 中文预训练模型的技术路线在预训练阶段使用更大规模的语料和更充分的训练步数。RoBERTa 相比原始 BERT 的核心改进在于去掉 NSP 任务、使用动态 Mask、增大 Batch Size、使用更多训练数据。这些改进在中文场景下同样有效。实际使用时你需要关注的是下游任务的适配方式。分类任务一般直接在 [CLS] 向量后接一个全连接层句子对任务则需要把两个句子拼接后送入模型通过 [CLS] 或池化后的向量做判断。大模型的优势在于预训练阶段学到的语义表示足够丰富微调时收敛快、对标注数据量的要求相对低。但大模型不是没有代价。参数量大意味着显存占用高、推理速度慢。如果你打算做本地部署需要提前算好显存预算。以常见的 6 层小模型为参照大模型的推理耗时通常是其 5 到 10 倍。所以选大模型之前先确认你的场景能不能接受这个延迟。2.2 小模型速度提升 8 倍背后的工程取舍小模型是这份资源里工程价值最高的部分。摘要里写得很清楚速度比 Bert-base 提升 8 倍左右与 albert_tiny 速度一致但效果更佳。这句话的信息量很大。先看速度。Bert-base 是 12 层、768 隐藏维度、110M 参数。要做到 8 倍加速通常意味着层数降到 4 到 6 层隐藏维度降到 256 到 512参数量压缩到 10M 到 30M 量级。albert_tiny 是 4 层、312 隐藏维度、约 4M 参数速度确实快但效果损失明显。这份资源的小模型在同等速度下效果更好说明它在结构设计或训练策略上做了优化。常见做法是采用知识蒸馏用大模型作为教师模型小模型作为学生模型让学生模型在输出分布上逼近教师模型。这样小模型不仅能学到硬标签还能学到类间相似性信息效果比直接用小模型微调要好。另一种做法是改进注意力机制或使用更高效的激活函数在减少参数的同时保持表达能力。实际选型时如果你要做的是高并发在线服务比如每秒几百上千次请求的文本分类接口小模型是唯一可行的选择。大模型单次推理可能就要几十毫秒加上批处理也扛不住高 QPS。小模型可以把单次推理压到几毫秒配合 ONNX Runtime 或 TensorRT 还能进一步加速。但小模型不是万能的。它的容量有限对于细粒度分类、长文本理解、复杂推理类任务效果会明显下降。我一般会建议先用小模型跑一版 baseline看效果能不能接受如果差得多再换大模型如果差得不多就针对小模型做数据增强或调参。2.3 相似度模型句子对任务的专用方案语义相似度模型是这份资源里最容易被忽略但实际很实用的部分。摘要里说“用于处理语义相似度或句子对问题有很大概率比直接用预训练模型效果要好”。这句话值得展开。句子对任务包括自然语言推理NLI、语义文本相似度STS、问答匹配等。直接用预训练模型做这类任务通常是把两个句子拼接后送进模型让模型自己学交互。但这种方式对标注数据量要求高数据少的时候容易过拟合。专门的相似度模型通常采用双塔结构或交互式结构。双塔结构分别编码两个句子然后计算余弦相似度或拼接后分类交互式结构则让两个句子在编码阶段就有交互比如 Cross-Encoder。双塔结构推理快可以预先算好句子向量交互式结构效果好但推理慢。这份资源里的相似度模型我推测是在预训练阶段就引入了句子对训练目标比如 Next Sentence Prediction 的改进版、或对比学习目标。这样模型在微调之前就已经具备了判断句子关系的能力下游任务只需要少量数据就能达到不错的效果。实际使用时如果你要做的是语义检索、重复问题判断、或问答对匹配优先试这个相似度模型。它的训练目标更贴近你的任务收敛更快效果上限也更高。3. 上手实操从环境配置到模型微调的完整链路3.1 环境准备与依赖安装拿到资源包后第一步是确认环境。中文预训练模型通常依赖 PyTorch 或 TensorFlow以及 HuggingFace 的 transformers 库。我一般会先建一个干净的虚拟环境避免版本冲突。# 创建虚拟环境 python -m venv nlp_env source nlp_env/bin/activate # Linux/Mac # nlp_env\Scripts\activate # Windows # 安装核心依赖 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 pip install transformers datasets scikit-learn pip install numpy pandas tqdm这里有几个参数需要注意。--index-url指定 CUDA 版本cu118 对应 CUDA 11.8你需要根据自己显卡驱动支持的 CUDA 版本调整。如果只用 CPU 推理可以去掉这个参数直接pip install torch。transformers版本建议用 4.x 的较新版本太老的版本可能不兼容某些模型结构。安装完成后验证一下import torch from transformers import AutoTokenizer, AutoModel print(torch.__version__) print(torch.cuda.is_available()) # 确认 GPU 是否可用如果torch.cuda.is_available()返回 False说明 CUDA 配置有问题需要检查显卡驱动和 CUDA 版本是否匹配。这是最常见的翻车点之一。3.2 加载模型与分词器资源包里的模型通常以 HuggingFace 格式提供包含config.json、pytorch_model.bin、vocab.txt等文件。加载方式很直接from transformers import AutoTokenizer, AutoModelForSequenceClassification model_name_or_path ./path/to/your/model # 替换为实际路径 # 加载分词器 tokenizer AutoTokenizer.from_pretrained(model_name_or_path) # 加载分类模型假设是 6 分类任务 model AutoModelForSequenceClassification.from_pretrained( model_name_or_path, num_labels6 # 根据实际任务修改 ) # 测试分词 text 这份中文预训练模型的效果怎么样 inputs tokenizer(text, return_tensorspt, paddingTrue, truncationTrue, max_length128) print(inputs)num_labels参数必须和你的任务类别数一致。如果是句子对任务num_labels可能是 2相似/不相似或 3蕴含/矛盾/中立。max_length控制截断长度中文任务一般 128 到 512 足够太长会增加显存占用。分词器返回的input_ids是 token 的 ID 序列attention_mask标记哪些位置是真实 token、哪些是 padding。这两个是模型的标准输入。3.3 微调训练脚本微调是让预训练模型适配下游任务的关键步骤。下面是一个完整的训练脚本框架import torch from torch.utils.data import Dataset, DataLoader from transformers import AutoTokenizer, AutoModelForSequenceClassification, AdamW from sklearn.model_selection import train_test_split import pandas as pd # 自定义数据集 class TextDataset(Dataset): def __init__(self, texts, labels, tokenizer, max_len128): self.texts texts self.labels labels self.tokenizer tokenizer self.max_len max_len def __len__(self): return len(self.texts) def __getitem__(self, idx): encoding self.tokenizer( self.texts[idx], max_lengthself.max_len, paddingmax_length, truncationTrue, return_tensorspt ) return { input_ids: encoding[input_ids].squeeze(), attention_mask: encoding[attention_mask].squeeze(), labels: torch.tensor(self.labels[idx], dtypetorch.long) } # 加载数据假设是 CSV两列text, label df pd.read_csv(your_data.csv) train_texts, val_texts, train_labels, val_labels train_test_split( df[text].tolist(), df[label].tolist(), test_size0.2, random_state42 ) tokenizer AutoTokenizer.from_pretrained(./path/to/model) model AutoModelForSequenceClassification.from_pretrained(./path/to/model, num_labels6) # 创建 DataLoader train_dataset TextDataset(train_texts, train_labels, tokenizer) val_dataset TextDataset(val_texts, val_labels, tokenizer) train_loader DataLoader(train_dataset, batch_size16, shuffleTrue) val_loader DataLoader(val_dataset, batch_size32) # 优化器 optimizer AdamW(model.parameters(), lr2e-5, weight_decay0.01) device torch.device(cuda if torch.cuda.is_available() else cpu) model.to(device) # 训练循环 for epoch in range(3): model.train() total_loss 0 for batch in train_loader: input_ids batch[input_ids].to(device) attention_mask batch[attention_mask].to(device) labels batch[labels].to(device) optimizer.zero_grad() outputs model(input_idsinput_ids, attention_maskattention_mask, labelslabels) loss outputs.loss loss.backward() optimizer.step() total_loss loss.item() print(fEpoch {epoch1}, Loss: {total_loss/len(train_loader):.4f}) # 验证 model.eval() correct 0 total 0 with torch.no_grad(): for batch in val_loader: input_ids batch[input_ids].to(device) attention_mask batch[attention_mask].to(device) labels batch[labels].to(device) outputs model(input_idsinput_ids, attention_maskattention_mask) preds torch.argmax(outputs.logits, dim1) correct (preds labels).sum().item() total labels.size(0) print(fValidation Accuracy: {correct/total:.4f})几个关键参数说明lr2e-5是预训练模型微调的经典学习率太大容易破坏预训练学到的表示太小收敛慢。batch_size16是显存和训练稳定性的折中显存不够就降到 8。weight_decay0.01是正则化项防止过拟合。epoch3对于大多数分类任务足够太多会过拟合。验证部分只算了准确率实际项目中还需要看 F1、AUC 等指标特别是类别不平衡的时候。3.4 相似度模型的调用方式相似度模型的使用方式和分类模型略有不同。如果是双塔结构需要分别编码两个句子from transformers import AutoTokenizer, AutoModel import torch import torch.nn.functional as F tokenizer AutoTokenizer.from_pretrained(./path/to/similarity_model) model AutoModel.from_pretrained(./path/to/similarity_model) model.eval() def get_embedding(text): inputs tokenizer(text, return_tensorspt, paddingTrue, truncationTrue, max_length128) with torch.no_grad(): outputs model(**inputs) # 使用 [CLS] 向量或均值池化 return outputs.last_hidden_state[:, 0, :] # [CLS] 向量 def cosine_similarity(text1, text2): emb1 get_embedding(text1) emb2 get_embedding(text2) return F.cosine_similarity(emb1, emb2).item() # 测试 sim cosine_similarity(今天天气怎么样, 明天的天气如何) print(f相似度: {sim:.4f})这里用的是 [CLS] 向量也可以换成均值池化对所有 token 的向量取平均。两种方式在不同模型上效果可能不同建议都试一下。余弦相似度的取值范围是 -1 到 1越接近 1 越相似。实际使用时需要根据业务场景设定阈值比如 0.8 以上算相似。4. 避坑与排查模型加载、训练和推理中的常见问题4.1 模型加载报错KeyError 或 Missing keys现象from_pretrained时报错提示某些权重 key 不存在或多余。原因模型文件与代码中的模型结构不匹配。常见于资源包里的模型是基于自定义结构训练的而加载时用了标准的AutoModel。解决先看资源包里有没有自定义的模型类文件比如modeling_xxx.py如果有需要先 import 再加载。如果没有检查config.json里的architectures字段确认模型类型。实在不行用ignore_mismatched_sizesTrue跳过不匹配的权重但这样会丢失部分预训练信息。4.2 显存不足CUDA out of memory现象训练或推理时显存爆了程序崩溃。原因模型参数量大、batch_size 太大、序列太长三者叠加导致显存超限。解决优先降 batch_size从 16 降到 8 再到 4。如果还不行降 max_length从 512 降到 256 再到 128。还可以开启梯度累积用optimizer.step()前累积多个 batch 的梯度等效增大 batch_size 但不增加显存。混合精度训练torch.cuda.amp也能省不少显存。4.3 训练不收敛Loss 震荡或居高不下现象训练几个 epoch 后 Loss 还在高位震荡验证准确率不涨。原因学习率太大、数据标注有问题、或者模型结构与任务不匹配。解决先把学习率降到 1e-5 甚至 5e-6 试试。然后检查数据看有没有标错、重复、或类别极度不平衡。如果数据没问题换用相似度模型或大模型试试可能是小模型容量不够。4.4 推理速度慢单次请求超过预期现象线上服务单次推理耗时几百毫秒QPS 上不去。原因用了大模型、没做批处理、没开推理优化。解决换小模型是最直接的。如果必须用大模型开启批处理把多个请求攒一批一起推理。还可以用 ONNX Runtime 或 TensorRT 导出模型推理速度能提升 2 到 5 倍。另外检查是不是每次请求都重新加载了模型模型应该常驻内存。4.5 分词结果异常中文被拆成单字或乱码现象分词后 token 全是单字或者出现奇怪的符号。原因分词器与模型不匹配或者 vocab.txt 文件损坏。解决确认分词器是从同一个模型路径加载的。中文模型通常用 WordPiece 或 BPE 分词如果 vocab 里没有常用词可能是下载不完整。重新下载或从资源包里找完整的 vocab 文件。5. 进阶技巧模型蒸馏与推理加速的实操细节小模型效果不够的时候除了换大模型还有一个更工程化的选择知识蒸馏。我一般会先用大模型在训练集上跑一遍把 logits 存下来然后让小模型去拟合大模型的输出分布。这样小模型能学到类间相似性效果比直接用硬标签训练要好。# 蒸馏损失KL 散度 交叉熵 import torch.nn.functional as F def distillation_loss(student_logits, teacher_logits, labels, temperature4.0, alpha0.7): # 软标签损失 soft_loss F.kl_div( F.log_softmax(student_logits / temperature, dim1), F.softmax(teacher_logits / temperature, dim1), reductionbatchmean ) * (temperature ** 2) # 硬标签损失 hard_loss F.cross_entropy(student_logits, labels) return alpha * soft_loss (1 - alpha) * hard_losstemperature控制软标签的平滑程度越大分布越平滑一般取 3 到 5。alpha是软硬损失的权重0.7 表示更依赖教师模型的指导。这个损失函数替换掉原来的交叉熵即可其余训练流程不变。推理加速方面ONNX Runtime 是最容易上手的方案。导出 ONNX 模型后用onnxruntime加载CPU 推理也能比原生 PyTorch 快不少。如果显卡支持TensorRT 效果更好但配置麻烦一些。# 导出 ONNX import torch from transformers import AutoModelForSequenceClassification, AutoTokenizer model AutoModelForSequenceClassification.from_pretrained(./path/to/model) tokenizer AutoTokenizer.from_pretrained(./path/to/model) model.eval() dummy_input tokenizer(测试文本, return_tensorspt, paddingmax_length, max_length128) torch.onnx.export( model, (dummy_input[input_ids], dummy_input[attention_mask]), model.onnx, input_names[input_ids, attention_mask], output_names[logits], dynamic_axes{input_ids: {0: batch, 1: sequence}, attention_mask: {0: batch, 1: sequence}}, opset_version13 )导出时dynamic_axes很关键它让模型支持动态 batch 和序列长度。opset_version13是较新的算子集兼容性好。导出后用onnxruntime推理import onnxruntime as ort import numpy as np session ort.InferenceSession(model.onnx) inputs tokenizer(测试文本, return_tensorsnp, paddingmax_length, max_length128) outputs session.run(None, { input_ids: inputs[input_ids].astype(np.int64), attention_mask: inputs[attention_mask].astype(np.int64) }) print(outputs[0]) # logits从那以后我每次上线模型前都会先用小模型跑一版 baseline再决定要不要上大模型或做蒸馏。这个习惯帮我省了不少显存和延迟上的麻烦。希望帮到你。本文还有配套的精品资源点击获取