Guide · 使用教程
Model2Vec Python 教程:本地语义检索、中文样本与召回评估
固定版本与模型快照,用完整代码检查六文档、四查询的本地检索,保留中文首位失败及十项基础验证边界。
先验证检索结果,再接回答模型
本教程用 Model2Vec 建立一个不接向量数据库的小型语义检索实验:六条合成文档、四条人工指定参考答案的查询,输出前三名和命中记录。它演示模型加载、向量校验与失败解释,不是生产搜索系统,也没有多工具共同基准。
固定环境为 Python 3.11、model2vec 0.10.0,模型使用 minishlab/potion-base-8M。该模型面向英文;加入中文样本是为了暴露语言错配,不能把本教程理解为中文模型推荐。实测中“我想申请退款”的首位结果确实错误,后文保留原结果。
第一步:独立安装并准备模型
python3.11 -m venv .venv
source .venv/bin/activate
python -m pip install 'model2vec==0.10.0'
首次准备模型需要联网。保存下列代码为 download_model.py,在有模型访问权限与网络的环境运行。本次固定官方快照,方便复现;其他项目应记录自己使用的模型版本和许可。这里选择 allow_patterns 只下载推理所需的配置、分词器、权重与说明文件,不安装训练或蒸馏组件。
from huggingface_hub import snapshot_download
snapshot_download(
'minishlab/potion-base-8M',
revision='bf8b056651a2c21b8d2565580b8569da283cab23',
local_dir='./potion-base-8m',
allow_patterns=['*.json', '*.safetensors', 'README.md'],
)
python download_model.py
下载文件与后续输入文本是不同数据流。准备完成后从本地目录加载,避免每次推理都依赖远端。网络受限环境不要随意换不明镜像;先核对来源、完整性与目标平台兼容性。本文没有把模型下载速度作为推理延迟。
第二步:保存完整检索程序
把以下代码保存为 retrieve.py。文档 ID 与向量行一一对应;归一化后用矩阵乘法计算余弦相似度。代码拒绝空语料、重复 ID、空文本、非有限数值和零向量,防止坏数据悄悄进入排序。这里只读本地合成文本,不调用生成模型,也没有真实文件或账户操作。
import argparse
import hashlib
import json
import time
from importlib.metadata import version
from pathlib import Path
import numpy as np
from model2vec import StaticModel
DOCS=[
{'id':'refund-en','text':'Request a refund within seven days of purchase.'},
{'id':'password-en','text':'Reset your password from account security settings.'},
{'id':'invoice-en','text':'Download your invoice from the billing page.'},
{'id':'refund-zh','text':'购买后七天内可以申请退款。'},
{'id':'password-zh','text':'在账户安全设置中重置密码。'},
{'id':'invoice-zh','text':'在账单页面下载发票。'},
]
QUERIES=[('How can I get my money back?','refund-en'),
('Where can I reset my password?','password-en'),
('我想申请退款','refund-zh'),('怎么下载发票','invoice-zh')]
def validate_docs(docs):
if not docs:raise ValueError('empty corpus')
ids=[d['id'] for d in docs]
if len(ids)!=len(set(ids)):raise ValueError('duplicate ids')
if any(not d['text'].strip() for d in docs):raise ValueError('empty document')
def unit(matrix):
matrix=np.asarray(matrix,dtype=np.float32)
if matrix.ndim!=2 or not np.isfinite(matrix).all():raise ValueError('bad matrix')
norms=np.linalg.norm(matrix,axis=1,keepdims=True)
if np.any(norms<=1e-12):raise ValueError('zero embedding')
return matrix/norms
def main():
parser=argparse.ArgumentParser()
parser.add_argument('model_directory',type=Path)
args=parser.parse_args()
if not args.model_directory.is_dir():raise SystemExit('prepare local model first')
validate_docs(DOCS)
started=time.perf_counter()
model=StaticModel.from_pretrained(str(args.model_directory))
loaded=time.perf_counter()
corpus=unit(model.encode([d['text'] for d in DOCS],use_multiprocessing=False))
questions=unit(model.encode([q for q,_ in QUERIES],use_multiprocessing=False))
assert corpus.shape[0]==len(DOCS) and questions.shape[1]==corpus.shape[1]
[email protected]
rows=[]
for i,(q,gold) in enumerate(QUERIES):
order=np.argsort(-scores[i],kind='stable')[:3]
top=[{'id':DOCS[j]['id'],'score':round(float(scores[i,j]),6)} for j in order]
rows.append(dict(query=q,expected=gold,top3=top,hit1=top[0]['id']==gold,
hit3=gold in [x['id'] for x in top]))
hashes={p.name:hashlib.sha256(p.read_bytes()).hexdigest() for p in args.model_directory.glob('*.safetensors')}
print(json.dumps(dict(version=version('model2vec'),dimensions=corpus.shape[1],
corpus_count=len(DOCS),model_sha256=hashes,
load_ms=round((loaded-started)*1000,2),
rows=rows,hit1=sum(r['hit1'] for r in rows),hit3=sum(r['hit3'] for r in rows),
note='Four illustrative queries, not a benchmark or cross-tool comparison.'),ensure_ascii=False,indent=2))
if __name__=='__main__':main()
第三步:离线加载并运行
HF_HUB_OFFLINE=1 python retrieve.py ./potion-base-8m > retrieval-output.json
HF_HUB_OFFLINE 约束 Hugging Face Hub 的访问方式,不是操作系统级网络沙箱。本次用本地目录运行,没有付费 API 调用;严格离线部署仍应检查整个程序和其他组件的数据流。输出文件包含查询与名次,真实业务中应受控保存。
程序输出包版本、向量维度、文档数量、模型权重 SHA-256、加载时间以及每条查询的前三名。模型文件摘要有助于确认权重是否变化,但不是来源可信或用户授权的充分证明。加载时间仅是当次计时,不是稳态吞吐量、峰值内存或端到端服务延迟。
第四步:保留真实失败
本次固定版本返回 256 维向量,四条查询结果如下:
| 查询 | 参考文档 | 首位结果 | 参考文档在前三位 |
|---|---|---|---|
| How can I get my money back? | refund-en | refund-en | 是 |
| Where can I reset my password? | password-en | password-en | 是 |
| 我想申请退款 | refund-zh | password-zh | 是,第二位 |
| 怎么下载发票 | invoice-zh | invoice-zh | 是 |
四条中三条首位命中,四条均在前三位找到参考文档。这只是这组教学样本的计数,不代表生产准确率。中文退款查询的错误尤其需要保留,不能因为预期文档在前三位就说系统已经正确回答。
相似度只是当前表示空间中的排序分数,不是答案正确概率。不要将不同模型的 0.7 分直接比较,也不要用一个通用阈值覆盖所有语言。若首位证据不相关,后续回答模型可能沿着错误内容作答,应先解决召回、重排和答案校验。
第五步:加入坏数据检查
可以把下列检查保存为 check_vectors.py,与 retrieve.py 放在同一目录。它验证基础数值与输入拒绝行为,不会伪造一个“全绿”的检索质量结论。
import numpy as np
from retrieve import validate_docs, unit
cases = [
lambda: validate_docs([]),
lambda: validate_docs([{'id':'a','text':'x'}, {'id':'a','text':'y'}]),
lambda: validate_docs([{'id':'a','text':' '}]),
lambda: unit([[float('nan'), 1]]),
lambda: unit([[0, 0]]),
lambda: unit([1, 2]),
]
for check in cases:
try:
check()
except ValueError:
continue
raise AssertionError('invalid input was accepted')
assert np.allclose(np.linalg.norm(unit([[3, 4]]), axis=1), [1])
print('Input and numerical checks passed; inspect retrieval errors separately.')
python check_vectors.py
本次本地验证共十项:真实模型输出形状、四条查询记录、模型权重摘要,六种坏输入拒绝,以及单位向量检查。它们证明示例程序的基础路径得到验证,不证明中文质量或多工具性能排名。真实任务还要检查空查询、超长输入、稳定来源 ID 和相关文档缺失时的行为。
扩展到自己的文档时怎么做
先建立人工确认的参考答案,把训练、调参与最终验收查询分开。保留中文同义表达、否定、数字、专业名词和没有答案的问题,分开统计业务类别。把每次未命中的输入留下来,判断是模型语言、文本抽取、分块、索引还是权限过滤导致。
换成适合中文或多语言的模型后重跑同一组查询。官方有多语言权重,但本次没有测试,不能预测换模型后的分数。若输出维度或预处理改变,应重新编码全部文档,不能只更新查询模型,也不能因为维度相同就混用两个模型的向量。
六条文档适合直接矩阵比较,大语料需要考虑索引、增量更新、删除与内存。近似索引可能进一步影响召回,应分别验收编码质量和索引质量。权限过滤依据可信身份与文档集合实现,不能让模型根据文本判断用户能否访问。
Model2Vec 库与本次模型卡都标注 MIT;其他权重许可需单独核对。软件许可费为 $0,不包含计算、模型准备、存储、维护和人工标注成本。涉及敏感文档时,查询、向量与日志都按组织权限管理,不要直接把真实客户文本放入公开复现文件。
继续阅读:工具介绍、本地嵌入推荐、FastEmbed 对比、Sentence Transformers 对比。官方资料:Model2Vec 原理、源码与许可、固定模型。
常见问题
- 软件免费等于运行免费吗?
- 不等于。许可、权重、计算、存储、下载、索引和维护分别核对。
- 本地一定完全离线吗?
- 首次准备模型可能联网;后续真实数据流按所选组件与配置检查。
- 四条查询能代表生产准确率吗?
- 不能。这是教学合成样本,不是统计估计或共同工具基准。
- 中文向量非空就合格吗?
- 不能。英文模型在退款查询上首位失败,需要按中文任务独立验收。
- 换模型需要重建索引吗?
- 应重新编码并验证,维度相同也不保证向量处于同一表示空间。
- 相似度能做权限控制吗?
- 不能。可访问文档范围应由可信身份和权限规则控制。