简介:面向中文自然语言处理场景的全套文本向量化模型文件,适用于将中文文本转换为语义向量的开发者,可支撑相似度计算、语义检索、文本聚类等任务。压缩包共198个文件,大小约732.69MB,包含JSON配置、TXT说明、BIN权重及ONNX模型等主要类型;其中配置与说明文件用于定义模型结构、分词器参数,权重文件保存预训练参数,ONNX版本便于跨框架部署,目录结构清晰,易于定位各组件。目前已有1387人学习/下载,由作者taotao_guiwang分享。拿到后可直接配合transformers或sentence-transformers加载,无需额外训练即可生成中文句向量;也可参考参数配置调整微调方案,适配特定领域,适合正在搭建语义服务或研究文本向量化的初中级开发者。
1. 中文句向量模型:为什么要较真一套“模型文件”
shibing624-text2vec-base-chinese 模型文件,说白了就是一套已经把中文“句子语义”编码成向量的模型产物。做语义检索、文本去重、问答召回的人,经常会遇到一个尴尬事:拿通用模型直接算句向量,两个意思完全一样的句子,相似度分数就是上不去,排序一乱,整个检索链路跟着崩。这套模型文件的价值在于,它输出的向量空间是特意为“相似度”校准过的,加载之后用余弦相似度就能直接比较语义远近,不需要再挂一堆后处理。
适合谁看?刚把模型文件下载到本地、却不知道该按哪个目录加载的初级用户,以及在 transformers 和 sentence-transformers 之间反复横跳、被池化方式坑过的熟手。这篇按我实际落地时整理过的路径来写:先讲清原理和选型边界,再给你能直接抄的下载、加载与相似度计算代码,最后把几个高频翻车点单独列出来。
2. 模型原理与选型:先搞清它在算什么
2.1 从 BERT 到句向量:直接取 CLS 为什么不可靠
很多人第一次接触“句向量”,第一反应就是拿 BERT 模型最后一个隐层中 CLS token 的向量来用。这个做法在小规模分类任务上勉强能看,但放到相似度场景里基本翻车。原因是常规 BERT 训练时 CLS 向量承担的是“分类信号”,它只对“有没有成为某类”敏感,对“两个句子有多像”这个连续度量并不敏感。你去算 CLS 向量间的余弦相似度,经常会出现两个语义相反句子得分很高的怪现象。
所以后来大家开始用手工池化:把最后一个隐层的所有 token 向量做平均或最大池化。这比直接用 CLS 稳一些,但仍然是“无监督池化”,模型本身没有见过“相似度”这个监督信号。平均池化会把每个词等权对待,重要词和语气词被一视同仁,长句尤其明显。这就是为什么同样的文本,在不同手工池化策略下结果能差出一大截,行业里管这叫“池化玄学”。
text2vec 系列的思路是把“相似度监督”直接放进训练目标里,用对比学习拉近语义等价的句子、推开语义无关的句子。训练完成后,模型输出的向量空间就是按余弦距离组织的,推理时不需要再依赖分类头,直接比较向量就行。这也是这套模型文件最值得关注的原因:它不是一个普通语言模型,而是一个已经为句向量场景校准过的编码器。
2.2 CoSENT 目标:为什么相似度监督让向量更好用
常见的对比学习目标里,最朴素的是把句子对当成二分类,或者用 triplet loss 区分正负例。这套模型相关的方案里,更值得提的是 CoSENT 这类目标:它直接对句子对的余弦相似度与标注分数做一致性优化,相当于告诉模型“分数高的句子对,向量夹角要更小”。相比 triplet loss,它不需要频繁构造难负例,训练更稳,线上推理也天然兼容余弦相似度。
这对使用者的意义是:你不需要理解复杂训练流程,只要保证推理时的输入预处理和训练时一致即可。比如训练时做了归一化,推理时最好也做;训练时用 mean pooling,推理时就不要擅自换成 CLS。反过来说,如果你下载到的是 pytorch_model.bin 这样的原始权重,而不是某个框架封装好的调用接口,也完全可以直接用 Transformers 加载,因为模型本身没有额外的分类层,输出就是我们要的编码。
2.3 模型选型的边界:什么时候该用,什么时候换
没有万能的句向量模型,text2vec-base-chinese 的定位非常明确:中文、短文本、以相似度为核心。我用它最多的场景是 FAQ 问答召回、对话文本去重、给用户输入找历史相似工单。这些场景输入普遍在一两句话内,语义判断比关键词判断重要,它比通用 BERT 向量更合适。
但如果输入是长文档,超过 BERT 的 512 token 上限,直接灌进去只会被截断,语义丢失严重。常见做法是用滑动窗口切块,或者换专门的长文本检索模型。如果是中英混合甚至多语言内容,它的中文训练占比优势变成劣势,我一般会换多语言或按语言分流。如果对单次推理延迟要求极高,比如线上实时搜索,base 模型也偏重,可以先量化或者蒸馏,再评估分数损失。
模型选型记住一条:看任务的目标函数是不是“语义相似度”。是,就值得用这套;不是,非要硬套,结果往往还不如一个简单分类器。适合的边界永远比模型本身更值钱。
3. 模型文件的获取与目录结构:下载前先看懂清单
3.1 模型目录里通常有哪些文件,各管什么
下载前先搞清楚目录里每个文件是干什么的,省得后面报错找不到北。一套用 Transformers 加载的模型文件,通常至少包含权重文件、结构配置、分词器三块。
| 文件 | 作用 | 缺失时的影响 |
|---|---|---|
| config.json | 记录层数、隐藏层维度、激活函数等结构参数 | Transformers 无法正确初始化模型,直接报错 |
| pytorch_model.bin | 训练好的模型权重,真正干活的部分 | 模型只剩随机参数,输出完全不可用 |
| vocab.txt | 词表,把中文文本切分成 token | 分词器无法构建,加载即报错 |
| tokenizer_config.json | 记录分词器的特殊 token、大小写策略 | 加载时会用默认配置,处理细节可能与训练不一致 |
| special_tokens_map.json | 定义 [CLS]、[SEP] 等特殊 token 对应的实际符号 | 影响输入拼接,严重时输出向量不对 |
| modules.json / sentence_bert_config.json | sentence-transformers 的入口描述文件 | 不影响 Transformers 加载,但用 SentenceTransformer 直接加载会失败 |
下载时我一般直接拉全量目录,不要只挑其中一个文件。很多人习惯只下载 pytorch_model.bin,结果代码一跑,报“tokenizer 缺少 vocab.txt”,这就是文件清单没看全。
3.2 用 huggingface_hub 下载整套文件
常见做法是用 huggingface_hub 的 snapshot_download 拉取整个模型目录。现在新版命令是 huggingface-cli download,直接把仓库名传给 --local-dir 就可以。
bash
拉取完整模型目录到本地
huggingface-cli download shibing624/text2vec-base-chinese
--local-dir ./text2vec-base-chinese
如果下载过程总在中途断掉,可以用 Python 接口,并设置 local_dir_use_symlinks=False,让文件真实落到目录里而不是以符号链接形式存在,后面做离线部署更省心。
python from huggingface_hub import snapshot_download
local_dir = snapshot_download( repo_id="shibing624/text2vec-base-chinese", local_dir="./text2vec-base-chinese", local_dir_use_symlinks=False, ) print(local_dir)
参数不复杂,重点解释两个:local_dir 指定目标目录,不想下载到系统默认缓存就一定要写;local_dir_use_symlinks 在新版里默认行为有变化,明确设成 False 可以避免别人拷走目录时只拷到一堆软链接的尴尬。命令里的 repo_id 就是模型名,只要模型托管平台允许匿名访问,这些代码直接能跑通。
下载完成后别急着写代码,先进目录确认核心文件都在。我一般用下面这条命令做一次快速体检:
bash ls -1 ./text2vec-base-chinese
至少应看到 config.json、pytorch_model.bin、vocab.txt、tokenizer_config.json
如果目录里恰好没有 vocab.txt,但出现了 tokenizer.json,也不用慌,分词器依然能工作。如果是句子对任务,还得特别注意特殊 token 配置,这会影响后面 [SEP] 的拼接方式。
3.3 目录放置与加载路径约定
模型文件下载好了,放置也有讲究。加载时传给 from_pretrained 的路径要指向文件夹,而不是某个权重文件。路径名最好不要带中文、空格和特殊符号,Windows 上尤其容易在分词器加载时碰到编码问题。
我习惯把模型和代码分开目录放:
plaintext your_project/ code/ main.py models/ text2vec-base-chinese/ config.json pytorch_model.bin vocab.txt ...
代码里引用相对路径,可维护性最好。如果部署环境和下载环境不是同一台机器,直接复制整个 models 目录就行,但要确保 config.json 和 tokenizer 文件一起复制,只搬 bin 文件是跑不起来的。复制完后,记得把目录权限设为当前用户可以读,否则加载时也会报莫名其妙的 PermissionError。
3.4 离线环境下载的补充配置
企业内部环境经常不能直连外部网络,下载动作在前置机上做,之后再迁移。这时除了模型目录,还要留意 Transformers 与 huggingface_hub 的缓存目录。如果之前用默认配置下载过,缓存里可能有不少重复文件,迁移时容易遗漏。
常见做法是在下载前把缓存目录指到模型目录旁边,迁移时一起带走:
bash export HF_HOME=/data/hf_cache huggingface-cli download shibing624/text2vec-base-chinese
--local-dir /data/models/text2vec-base-chinese
在离线机器上运行时,设置 TRANSFORMERS_OFFLINE=1,防止代码里某个路径写错后又偷偷去连外网:
bash export TRANSFORMERS_OFFLINE=1
这两个环境变量组合起来,基本能解决八成离线部署的报错。剩下的两成,是你自己路径写错,下面第 5 章会专门讲。
4. 本地加载与向量化:两步出向量,别用错接口
4.1 用 transformers 自己加载:池化方式必须显式写出来
先给出我用得最多的一段加载代码。它不依赖任何模型专用封装,只要目录下的模型文件齐全,就能跑。
python import torch from transformers import AutoTokenizer, AutoModel
model_dir = "./models/text2vec-base-chinese"
tokenizer = AutoTokenizer.from_pretrained(model_dir) model = AutoModel.from_pretrained(model_dir) model.eval() # 切换到推理模式,关闭 dropout
def mean_pooling(last_hidden, attention_mask): # 先按 mask 把 padding 位置标记为 0,再对非 padding 位置求平均 mask_expanded = attention_mask.unsqueeze(-1).float() masked = last_hidden * mask_expanded summed = masked.sum(dim=1) token_counts = mask_expanded.sum(dim=1).clamp(min=1e-9) return summed / token_counts
def encode(texts, max_length=128): inputs = tokenizer( texts, padding=True, truncation=True, max_length=max_length, return_tensors="pt", ) with torch.no_grad(): outputs = model(**inputs) return mean_pooling(outputs.last_hidden_state, inputs["attention_mask"])
sentences = ["你知道订单到哪了吗", "我的货到哪了"] vecs = encode(sentences) print(vecs.shape)
这段代码看起来简单,但每个参数都有坑。首先是 model.eval(),少了它,推理时 dropout 随机生效,同一个句子两次编码能得到不同的向量,第一次跑可能没感觉,批量对比时就会翻车。其次是 mean_pooling,这里用 attention_mask 过滤掉了 padding token,否则后面补齐的 [PAD] 会被平均进去,相似度会被长句的 padding 数量干扰。max_length 我默认写 128,对大多数口语化短文本已经够用,真遇到长文本再往上调,代价是显存和延迟。
为什么不直接取 outputs.last_hidden_state[:, 0]?前面讲过,CLS 向量没有经过相似度监督校准,用 mean pooling 更贴近这套模型的训练态。如果模型在训练时用的就是全 token 平均,那这里的做法就是正确的;如果发布文件里内置了另一个 pooler,那就另说,但至少用 Transformers 直接加载时,mean pooling 是我验证下来最稳的默认值。
4.2 用 sentence-transformers 加载:更省事,但有前提
如果模型目录包含 sentence-transformers 的入口文件,加载还能更方便。
python from sentence_transformers import SentenceTransformer
model = SentenceTransformer("./models/text2vec-base-chinese") vecs = model.encode( ["你知道订单到哪了吗", "我的货到哪了"], normalize_embeddings=True, ) print(vecs.shape)
SentenceTransformer 的 encode 接口把 tokenize、forward、pooling、归一化全包了,代码量少一大半。其中 normalize_embeddings=True 我会一直开着。这个参数决定返回向量是否做了 L2 归一化,开与不开直接影响后面点积是否等于余弦相似度。开着,后续算相似度直接用内积就可以;不开,就跟我手动算一遍余弦一样,麻烦还容易出错。
但前提是目录里必须有 3.1 表格里提到的 modules.json 或 sentence_bert_config.json。没有这些文件,SentenceTransformer 找不到 pooler 配置,会直接报错“找不到模型配置文件”。遇到这种情况,别去网上硬搜一个 modules.json 塞进去,我建议直接退回 4.1 的 Transformers 方案,自己控制池化,反而更稳。强行拼配置,可能加载不报错,但池化策略和模型训练时不一致,输出向量就废了。
4.3 相似度计算:归一化不是可选项
向量出来后,相似度计算也有标准姿势。这套模型训练时用余弦相似度作为监督信号,推理时也要保持一致。
python import torch.nn.functional as F
va = F.normalize(vecs[:1], p=2, dim=1) # 第一个句子向量 vb = F.normalize(vecs[1:], p=2, dim=1) # 第二个句子向量 score = (va * vb).sum(dim=1) print(f"相似度: {score.item():.4f}")
如果想少写两行,直接用 F.cosine_similarity 也可以:
python score = F.cosine_similarity(vecs[:1], vecs[1:], dim=1)
这里提醒一个容易误会的点:F.cosine_similarity 内部已经做了归一化,所以输入向量不需要预先归一化。但如果你把向量存进向量数据库,后面要经常做点积检索,那就应该在写入前统一调用 encode(normalize_embeddings=True) 或 F.normalize,让库里所有向量都是单位向量。否则,点积结果会受向量长度影响,排序可能乱,越长的句子越吃亏。
4.4 批量编码与长度上限
实际落地时很少一条一条编码,基本都是批量跑。我一般会在 encode 外面再套一层批次循环:
python def encode_batch(texts, batch_size=32, max_length=128): outputs = [] for i in range(0, len(texts), batch_size): batch = texts[i:i + batch_size] outputs.append(encode(batch, max_length=max_length)) return torch.cat(outputs, dim=0)
batch_size 不是越大越好。base 模型不加优化大概占 1GB 左右显存,batch_size 设 32 是 16GB 显卡上的保守值;显存小的机器就往 8 或者 4 降。max_length 更敏感,从 128 加到 256,显存占用接近翻倍。处理中文短文本时,128 通常够用,但如果你发现句子经常被硬截断,比如身份证地址、商品详情,优先切分而不是调大长度。
如果对延迟敏感,可以用 torch.cuda.amp 的推理模式加速。不过半精度要注意 CPU 上跑不了,必须在 GPU 上,而且模型权重和输入都要在同一 dtype。这一部分属于性能调优,后续再单独聊。
5. 模型文件相关的 5 个高频坑:从下载到推理逐个排查
5.1 报错 OOM:显存不足但明明才几条数据
现象:用 GPU 加载后,batch_size 才设 16 就报 torch.cuda.OutOfMemoryError。
原因:模型默认 fp32 推理,显存占用比 fp16 高一倍;同时 tokenizer 会把一个 batch 里的所有句子 padding 到本 batch 最长长度,长句拖累整个 batch。
解决:先确认推理确实关闭了梯度,再降 batch 和 max_length,最后考虑半精度。
python model.eval() with torch.no_grad(): outputs = model(**inputs)
如果显存仍紧张,在 GPU 场景下加载后执行 model.half()
model.half() 只适合 GPU 推理,切回 CPU 前要调回 model.float()。半精度会让个别句子相似度有小数点后第三位的波动,一般不影响排序。
5.2 报错缺少 vocab.txt:下载只拉了一半
现象:AutoTokenizer.from_pretrained 报错,提示找不到词汇表文件。
原因:下载时只复制了 pytorch_model.bin 或者只调用了按单文件下载的接口,词表文件根本没落地。
解决:把整个模型目录补齐,重点检查 vocab.txt 或 tokenizer.json。一个快速检查命令是:
bash ls ./models/text2vec-base-chinese | grep -E "pytorch_model|vocab|tokenizer|config"
输出至少要有权重文件、词表和 config.json,缺一样都别急着往下跑。这种问题在多人协作时最常见,别人只传给你一个 bin 文件,还说“模型都在这了”,实际是个哑弹。
5.3 相似度排序一塌糊涂:CLS 向量背锅
现象:代码能跑,向量维度也对,但“你吃饭了吗”和“我今天不想吃饭”的相似度比“你吃饭了吗”和“今天天气真好”还高。
原因:直接取了 last_hidden_state 第一个 token 的向量,没有做 mean pooling;对比学习训练时根本不会用这个位置做句向量,结果自然不受控。
解决:把池化逻辑改成第 4 章的 mean_pooling,并且确认 mask 参与计算。改完之后,正负例的分数差距通常会立刻拉开。这也是我最早踩过的坑,后来所有句向量推理代码里都强制写 mean pooling,再也不相信“CLS 玄学”。
5.4 报错 401 或突然联网:路径写错导致回退下载
现象:本地明明有完整目录,加载却出现网络请求甚至失败提示。
原因:from_pretrained 的第一个参数传成了模型名(比如直接传“text2vec-base-chinese”),而不是本地路径;本地路径不存在时,Transformers 会尝试从模型托管平台在线拉取。
解决:路径统一写 ./models/text2vec-base-chinese;在真正离线的环境里设置:
bash export TRANSFORMERS_OFFLINE=1
设了之后,如果代码还在尝试远程获取,说明路径仍然有问题,把报错里的路径打出来逐级排查,通常比瞎调参数快得多。
5.5 SentenceTransformer 加载报错:缺入口配置文件
现象:同一套模型文件,AutoModel 加载没问题,但 SentenceTransformer 加载就报错,提示找不到 sentence_bert_config.json 或 modules.json 之类。
原因:模型只提供了 Transformers 标准入口,没有按 sentence-transformers 要求的模块化结构暴露配置。这套文件不是不能转,而是需要额外封装。
解决:别硬塞不存在的配置。直接用 AutoModel + mean pooling 的代码,功能完全等价;如果团队统一用 SentenceTransformer 封装,才需要做一次格式转换,把 Transformers 模型包装成 sentence-transformers 能识别的结构。这个转换要在干净环境里验证过再交给别人,否则加载成功也不代表池化策略正确。
6. 进阶用法与验证:在微调前先做一次向量质量体检
6.1 用标注好的句子对自测,别只看几个 DEMO
拿到模型文件后,第一件事不是写微调脚本,而是验证这组向量在你自己数据上的表现。我一般准备十到二十对“语义相同”和“语义不同”的中文句子,打上标签,跑一遍相似度分布。
python from torch.nn.functional import cosine_similarity
pairs = [ ("订单什么时候发货", "货大概什么时候能发出"), ("现在能退款吗", "可以退钱吗"), ("今天天气不错", "明天要降温"), ("我想改收货地址", "这个手机壳多少钱"), ] labels = [1, 1, 0, 0]
va = encode([p[0] for p in pairs], max_length=64) vb = encode([p[1] for p in pairs], max_length=64) scores = cosine_similarity(va, vb)
for pair, label, score in zip(pairs, labels, scores): print(label, f"{score.item():.4f}", pair)
如果正例对的分数没有整体高于负例对,先别急着换模型,回头检查 4.1 的池化和归一化。多数情况下是这两步没对齐,而不是模型本身不行。这个自测脚本也适合做模型文件的“出厂检查”,任何一次拷贝、压缩、换机器之后跑一遍都安心。
6.2 轻量微调的方向与一个现实教训
如果自测发现场景词汇差异太大,比如医疗术语、法律文书,可以在保留预训练权重基础上,用 CoSENT 这类句子对目标做有监督微调。数据格式就是句子对加相似度分数,常见做法是正例给 1,负例给 0,微调时只调编码器和池化层,不需要新增分类头。微调后的模型记住一个原则:推理时的池化与训练时保持一致,否则模型文件的再次校准就白做了。
最后说个教训。我最早拿到同类模型文件时,急着往检索系统里接,跳过自测直接上线,结果线上召回的前几个结果跟用户问题意思完全相反。查了一天,最后发现就是加载时图省事用了 CLS 向量,把一套已经校准过的向量空间硬生生用坏了。从那时起,我每换一套模型文件,都是先下载、体检、再看是否需要微调,顺序乱了就要加班。这步体检不费事,但能省掉后面一大半排错时间。希望帮到你。
本文还有配套的精品资源,点击获取