Day9 学习日志:Embedding 与向量数据库
📅 日期:2026-03-25
📌 定位:在「三国演义」语料上跑通 本地 Word2Vec 与 DashScope 文本向量 API 两条链路,理解 词级 vs 段级 表示,以及 Embedding 与向量索引在 RAG 链路中的位置;能把 问句 接到各自的检索方式上,并复盘 切块与额度 等工程坑。
一、语料与目录约定(和 PathLineSentences 的关系)
- 原文:
data/课程练习/source/{语料名}/下各文件。 - 分词结果:写在
segmented/{语料名}/,与source同级(不要放在source/.../segmented),避免PathLineSentences把未分词正文和分词行混在一起读。 - Word2Vec 模型:
vectorModels/{语料名}/{语料名}_word2vec.model。 - Gensim
model.save:参数必须是 字符串路径 或带write的文件对象;传pathlib.Path会在内部.endswith等处报错。
二、Word2Vec:词表、most_similar 与「句子查询」
wv.most_similar("曹操"):只接受 词表里存在的 token;不能把整句当作一个词 key(会KeyError)。- 想用「句子 / 问句」在词向量空间里找近邻词:
jieba分词 → 过滤 OOV → 对剩余词向量 取平均 →wv.similar_by_vector。练习里封装为similar_words_for_sentence。 - 局限:得到的是与句向量方向接近的 词,不是「另一段原文句子」排序;句对段 检索更适合下面的 API 向量库或 Doc2Vec。
类比任务:demo_query_changban_adou_similarity 与 API 侧同名函数使用同一问句 「谁在长坂坡救了阿斗?」,便于两条路线对照(默认不在 __main__ 里自动调用)。
三、DashScope Text Embedding:批量建库、本地复用与 TopK
- 建库:枚举语料目录下文件 → 长文按字符上限 切块 → 分批
TextEmbedding.call→ 默认pickle写入vectorModels/{语料名}/{语料名}_API.model(脚本:embedding_API_pickle.py)。 embeddings结构:output["embeddings"]是 列表,每项含embedding(浮点向量) 与text_index;取单条输入的向量用[0]["embedding"]。skip_if_exists:本地已有*_API.model时 跳过重新编码,省 API;但对 新问句 做相似度仍要对 query 再调一次 Embedding(除非自行传入已算好的query_vector)。- 检索:对 query 与库中各块向量算 余弦相似度,取 TopK;条目里可带
snippet便于肉眼扫结果。
额度(403 / AllocationQuota.FreeTierOnly):免费额度用尽时需在控制台关闭「仅使用免费额度」或开通按量付费;脚本可对失败信息做 中文说明,并用 本地第一条语料向量 做 离线 TopK 演示(不调 API)。开发阶段应 避免对同一语料反复全量重算,优先复用本地 *.model / *.faiss;已有本地模型时务必 skip_if_exists=True 以免无谓请求。
四、从 Pickle 到 FAISS:索引与元数据分工
- 直观理解:Embedding 把语义压进高维向量;检索是在向量空间里找「近邻」,再靠 id 找回原文。LLM 负责理解与生成;向量库 + 元数据负责「索引 → 查 id → 拼进上下文」。
- 练习中的落盘形态:
- Pickle:
{语料名}_API.model(向量与条目一锅端,便于起步)。 - FAISS:
{语料名}_API_FAISS.faiss(二进制索引)+{语料名}_API_FAISS.meta.json(file/chunk_index/snippet等,不含向量);可从 pickle 迁移建索引,无需再调 Embedding。 FaissVectorStore类(faiss_vector_store.py):封装 索引 + 与 id 对齐的 items,提供save/load/search。
- Pickle:
- 工程上常见组合:FAISS(或同类 ANN)管快速近邻,JSON / SQLite 管 id → 原文与业务字段。
五、检索踩坑与参数教训(与切块强相关)
5.1 语义稀释(chunk 过大)
- 现象:
max_chars_per_chunk很大(例如 3500)时,搜「桃园结义」可能排到与主题无关的段落(核心情节被同块里的序言、史论等 冲淡)。 - 理解:一块里信息太多时,单条向量更像整块的「平均语义」,难以对齐短查询的「特写」。
- 做法:把单块 缩小(练习中曾调到约 500 字量级)可显著改善「对准某一情节」的召回;代价是 块数变多、建库 API 次数与存储增加,需在成本与效果间平衡。
5.2 语义断层与「强行匹配」
- 现象:搜「谁在长坂坡救了阿斗」却排到 姜维 等段落。
- 理解:若 语料里根本没有对应回目(节选、删节),RAG 无法召回到不存在的内容;此时向量检索仍会返回 相对最近 的块(例如同样有「救人」「落马」等表面相近叙述),容易 误导。
- 做法:生产上宜设 相似度阈值,分数过低时宁可回答 未知,并保证 语料覆盖 与问题域一致;可再叠加 关键词 / 混合检索 稳住专名。
5.3 查询太短
- 只输入 「赵云」 等极短查询时,向量方向 含糊,TopK 容易飘到泛化的「武将、征战」段落;完整问句 通常更稳。
六、两条路线怎么选(心里要有数)
| 维度 | Word2Vec(本地) | 文本 Embedding API |
|---|---|---|
| 粒度 | 词 | 整段 / 切块文本 |
| 典型查询 | 单词近邻、加减类比 | 问句 → 与语料块相似度 |
| 依赖 | 分词语料、训练时间 | 网络、Key、配额 |
| 离线 | 训练后可完全本地 | 库可离线读;新 query 一般仍要联网 |
七、遗留与后续可做
- Word2Vec 句向量 仅用平均词向量是基线;可了解 加权(TF-IDF)、Doc2Vec、或多语言 句向量模型 与 RAG 里 chunk / overlap 策略。
- API 侧
text_type(query / document)、切块与 overlap 对排序的影响,可在有额度时做小实验。 - 数据:用更完整、对齐问题域的文本测全量检索与压力。
- Query:白话问句 改写为古籍高频关键词(可借助 LLM)以提升专名召回。
- Rerank:引入重排序,缓解仅向量相似带来的 表面字词或场景混淆。
八、相关文件(便于回跳)
data/课程练习/Embeddings和向量数据库/embedding_Word2Vec.pydata/课程练习/Embeddings和向量数据库/embedding_API_pickle.py(原 pickle 建库与 TopK)data/课程练习/Embeddings和向量数据库/embedding_API_FAISS.py(FAISS 建库 / 检索)data/课程练习/Embeddings和向量数据库/faiss_vector_store.py(FAISS + 元数据封装类)- 语料与产物:
source/、segmented/、vectorModels/(含*.model、*.faiss、*.meta.json等,均在data/课程练习/下)
九、小结
前端往往追求 像素级确定;检索与生成链路更多是在 概率与阈值 上做权衡——同一套代码,换 chunk 大小或语料覆盖,结果可从「看似胡扯」变为「基本可用」。Day9 把这条因果链从目录结构一直拉到了 额度、本地复用与 FAISS 落盘,便于以后接正式向量库与 RAG 时少踩重复坑。