发布时间:2026/6/17 5:09:52
RAG项目初期为何不该盲目用向量数据库?NumPy轻量检索实战指南 1. 项目概述为什么“向量数据库”这个词正在被过度消费你最近是不是也频繁看到这几个词扎堆出现RAG、Embedding、向量检索、向量数据库朋友圈里有人刚跑通一个本地知识库配图就是 ChromaDB 的 logo技术群里有人发链接标题赫然写着《用 Qdrant 实现毫秒级语义搜索》就连招聘 JD 里都开始明写“熟悉 Milvus/Pinecone/Weaviate 等向量数据库”。这种氛围下如果你正打算给自己的小团队做个内部文档问答系统或者给客户交付一个轻量级的 AI 助手原型第一反应很可能是——得赶紧搭个向量数据库吧不然显得不够“AI”不够“工程化”。但我想先说一句实话绝大多数中小型 RAG 项目在启动阶段根本不需要、也不应该引入向量数据库。这不是在否定向量数据库的价值而是基于我过去三年亲手落地 27 个 RAG 类项目从单机 Excel 解析到千万级 PDF 文档库的真实经验判断。我见过太多团队在还没搞清自己到底要检索什么、数据量有多大、QPS 要求多高、延迟容忍度是多少的情况下就一头扎进向量数据库的安装文档、Docker Compose 编排、索引参数调优和权限配置里结果花了三周时间把 Chroma 启动起来却发现它比自己用 NumPy 写的 80 行代码还慢——因为默认配置没改内存没调够甚至没关 WAL 日志。这篇文章要讲的就是那个被很多人忽略的“中间地带”当你手头只有几百份合同、几千条客服对话、几万字的产品手册或者一个需要嵌入到 Flask/FastAPI 服务里的轻量级知识助手时NumPy 和 scikit-learn 不是“凑合用”的替代方案而是更合理、更可控、更可调试的第一选择。它们不提供分布式、不支持水平扩展、没有 Web UI、也没有企业级权限管理——但恰恰是因为没有这些你才能在 15 分钟内完成从数据加载、向量化、到返回 top-k 最相似 chunk 的完整闭环。而这个闭环才是验证 RAG 是否真正解决业务问题的最小单元。关键词“Towards AI - Medium”背后代表的是一种务实的技术选型哲学工具服务于问题而不是问题去迁就工具。接下来我会拆解清楚为什么这个判断成立怎么动手做以及踩过哪些坑。2. 核心思路拆解向量数据库的“重”与 NumPy 的“轻”到底重在哪、轻在哪2.1 向量数据库的“重”不只是安装包大小的问题很多人以为“不用向量数据库”只是省了 Docker 命令和配置文件其实远不止如此。向量数据库的“重”体现在四个相互耦合的维度上每一个都会在项目早期带来隐性成本第一重抽象层级的冗余。向量数据库本质上是一个“数据库”它必须模拟传统数据库的抽象表collection、行document、主键id、索引index、事务ACID、查询语言SQL-like 或 REST API。但 RAG 场景下的向量检索核心操作其实非常单一给定一个 query embedding从一个固定向量集合中找出欧氏距离/余弦相似度最小的 k 个向量。这个操作在数学上就是一个矩阵乘法加排序cosine similarity dot product of normalized vectors在工程上就是一个np.dot()加np.argsort()。向量数据库强行套上“数据库”这层壳等于在dot操作外面裹了三层包装纸HTTP 请求解析 → JSON 序列化反序列化 → 存储引擎索引查找 → 结果再序列化返回。我做过对比测试对 5 万条 768 维向量做 top-3 检索纯 NumPy 在单核 CPU 上耗时 8.2msChromaDBin-memory 模式无持久化平均耗时 42.7msPineconeserverless tier网络 RTT 就占了 120ms。这多出来的 30~100ms对一个需要实时响应的聊天界面来说就是用户感知上的“卡顿”。第二重运维心智负担。数据库意味着状态。哪怕你用的是 Chroma 的 in-memory 模式它内部依然有 WALWrite-Ahead Log日志、有内存索引结构HNSW、有缓存策略LRU。当你的检索结果突然不准了你得先查 Chroma 的日志看是不是 HNSW 的 ef_construction 参数设得太低导致召回率下降当服务内存暴涨你要确认是不是persist_directory没配好导致每次重启都重新建索引当同事问“为什么第一次查询特别慢”你得解释 HNSW 的 lazy build 机制。而 NumPy 方案呢整个检索逻辑就一个函数def search(query_vec: np.ndarray, all_vecs: np.ndarray, k: int 3) - np.ndarray: # all_vecs shape: (N, D), query_vec shape: (D,) similarities np.dot(all_vecs, query_vec) # cosine sim for normalized vecs top_k_indices np.argsort(similarities)[-k:][::-1] return top_k_indices出问题了直接print(similarities)看数值print(top_k_indices)看索引两行 debug 就定位到是 embedding 模型输出没归一化还是数据加载时维度错位。没有黑盒没有中间件没有“可能”的故障点。第三重数据流动的割裂。向量数据库天然倾向于成为数据孤岛。你的原始文本存在 PostgreSQL 里embedding 存在 Chroma 里元数据如文档来源、更新时间又存在另一个 Redis 里。三者之间靠 ID 关联一旦某条记录在某个库被删了关联就断了检索返回的 chunk 可能指向一个已删除的 PDF 链接。而 NumPy 方案所有东西都在一个 Python 进程里texts load_texts_from_csv()embeddings model.encode(texts)all_data list(zip(texts, embeddings, metadata))。数据加载、向量化、检索、结果组装全部在一个上下文里完成。你想加个过滤条件比如“只检索 2024 年之后的合同”直接在all_data列表推导式里加if item[year] 2024零额外开销。向量数据库想实现这个得学它的 filter DSL还得确认它是否支持该字段的索引。第四重演进路径的锁定。一旦你把向量数据库作为基础设施写进架构图后续所有改动都绕不开它。你想换 embedding 模型得重新生成所有向量并upsert进数据库。你想合并两个数据源得写脚本把 A 库的向量导出B 库的向量导出再一起upsert到 C 库。而 NumPy 方案embeddings new_model.encode(new_texts)all_vecs np.vstack([old_vecs, new_vecs])all_texts new_texts三行代码搞定。它不阻止你未来升级但也不绑架你现在。2.2 NumPy/scikit-learn 的“轻”轻得恰到好处说它“轻”绝不是说它能力弱。恰恰相反它的“轻”是一种精准的克制把力量集中在 RAG 检索最核心的环节轻在依赖极简。numpy和scikit-learn是 Python 科学计算的基石几乎存在于所有 AI 环境中。你不需要pip install chromadb不需要docker pull chromadb/chroma不需要处理libgomp.so.1这类系统级依赖冲突。一个requirements.txt里只有两行numpy1.24.0 scikit-learn1.3.0部署到客户服务器pip install -r requirements.txt完事。没有端口冲突没有磁盘空间告警没有 root 权限要求。轻在算法透明。scikit-learn的NearestNeighbors类底层就是BallTree或KDTree源码完全公开。你可以精确控制leaf_size影响树的深度和查询速度、algorithm暴力搜索 vs 树搜索、metric欧氏距离、余弦、曼哈顿。当发现BallTree对高维稀疏向量效果差时你可以一行代码切到brute模式用np.dot暴力计算——而这正是我们上面写的那个函数的工业级封装。你不需要理解 HNSW 的ef_search是什么但你需要知道n_neighbors和leaf_size如何权衡内存与速度。轻在调试友好。所有中间态都是 Python 原生对象np.ndarray可以.shape看维度.dtype看精度.min()/max()看数值范围list可以直接print()dict可以用pprint美化输出。我在调试一个金融问答系统时发现某些 query 返回的 top-1 chunk 总是不相关。用 NumPy 方案我直接把 query embedding 和所有 candidate embeddings 的前 10 维打印出来一眼就看出 query embedding 的 L2 norm 是 0.3而所有 document embedding 的 norm 都是 1.0——问题出在 query 没归一化。这个洞察在向量数据库里需要你导出 embedding、写脚本加载、再做同样分析至少多花 20 分钟。提示NumPy 的“轻”不是功能缺失而是把复杂性留给你自己掌控。它假设你懂一点线性代数但绝不假设你懂分布式系统。这对工程师是解放对初学者是门槛——但这个门槛恰恰是区分“会调 API”和“懂原理”的分水岭。3. 核心细节解析与实操要点从零搭建一个生产可用的 NumPy RAG 检索器3.1 数据准备文本切片与元数据设计比你想的更重要很多人的失败始于第一步把整篇 PDF 当作一个 chunk 丢进去。这不是向量数据库的问题是 RAG 方法论的问题。NumPy 方案不会替你做这件事但它会让你立刻暴露这个问题。切片Chunking不是技术问题是信息架构问题。我见过最典型的错误是用固定长度切片如每 512 字符切一刀结果把“甲方应在收到发票后 30 个工作日内付款”这句话硬生生切成两半前半句在 chunk A后半句在 chunk B。检索“付款期限”时两个 chunk 都可能被召回但单独看都不完整。正确做法是语义切片按自然段、按标题、按句子边界。langchain.text_splitter的RecursiveCharacterTextSplitter是个好起点但必须调整separatorsfrom langchain.text_splitter import RecursiveCharacterTextSplitter # 优先按换行符、句号、分号切避免在句子中间切断 splitter RecursiveCharacterTextSplitter( separators[\n\n, \n, 。, , , , , ], chunk_size300, # 目标长度非硬性限制 chunk_overlap50, # 重叠保证上下文连贯 length_functionlen, ) chunks splitter.split_text(full_text)实测下来对中文法律/合同文本chunk_size300效果最好对技术文档chunk_size500更合适。这个数字不是玄学是我统计了 12 个客户文档库后得出的经验值300 字左右的 chunk既能包含一个完整条款或概念又不会因太长而稀释 embedding 的焦点。元数据Metadata设计决定你未来能走多远。别只存source: contract_2024.pdf。至少要包括doc_id: 唯一标识用于关联原始文档如数据库主键page_number: PDF 页码方便前端高亮定位section_title: 章节标题用于结果分组如“所有关于‘违约责任’的条款”updated_at: 更新时间用于增量更新判断为什么重要因为 NumPy 方案的“轻”意味着你必须在检索前就做好过滤。例如用户问“2024 年新合同里关于保密条款的规定”你不能等检索完再遍历 1000 个 chunk 去筛年份而是在构建all_vecs时就只加载metadata[year]2024的 chunk。这要求元数据必须在切片时就提取好。我通常用正则从文件名或文本头部提取年份用 PyPDF2 读取页码用re.search(r第[零一二三四五六七八九十百千]章\s(.?)\n, text)抽取章节标题。注意元数据本身不参与向量化但它决定了哪些文本会被向量化。这是 NumPy 方案“可控性”的核心体现——你永远知道当前检索的向量集合对应着哪些业务实体。3.2 向量化模型选择、批处理与归一化三个致命细节向量化不是“调个 encode API 就完事”。这里有三个极易被忽略、却直接影响检索质量的细节细节一模型必须输出归一化向量L2-normalized。余弦相似度公式是cos(θ) (A·B) / (||A|| * ||B||)。如果 A 和 B 都是单位向量||A|| ||B|| 1那么cos(θ) A·B也就是一个简单的点积。scikit-learn的NearestNeighbors默认使用metriccosine但它内部会先对所有向量做归一化再算欧氏距离因为cosine distance 1 - cosine similarity且euclidean_distance(A_norm, B_norm)^2 2 - 2*cosine_similarity。但很多开源 embedding 模型如bge-small-zh-v1.5输出的向量并未归一化。如果你直接喂给NearestNeighbors它会在每次查询时都做一遍归一化白白消耗 CPU。更糟的是如果你用metricbrutealgorithmbrute它会用未归一化的向量算点积结果完全错误。解决方案在向量化后立即归一化并保存归一化后的向量。import numpy as np from sentence_transformers import SentenceTransformer model SentenceTransformer(BAAI/bge-small-zh-v1.5) texts [合同第3条甲方应..., 保密条款见附件二...] embeddings model.encode(texts, normalize_embeddingsFalse) # 关键设为 False # 手动归一化保留原始精度 norms np.linalg.norm(embeddings, axis1, keepdimsTrue) normalized_embeddings embeddings / norms # 保存 normalized_embeddings后续检索直接用它 np.save(chunks_embeddings.npy, normalized_embeddings)细节二批处理Batching不是为了快是为了稳。model.encode()支持batch_size参数。设成 1太慢。设成 1000可能 OOM。我的经验是根据你的 GPU 显存和模型尺寸动态计算。bge-small-zh384 维在 8GB 显存的 RTX 3060 上batch_size64最稳bge-base-zh768 维则要降到32。如何确定写个简单脚本从batch_size1开始试监控nvidia-smi当显存占用超过 90% 时就减半。不要迷信文档里的推荐值硬件环境千差万别。细节三向量精度用 float32 足够别用 float64。np.float64比np.float32占用双倍内存计算速度慢约 20%而对 RAG 检索的精度影响微乎其微0.001 的 cosine similarity 差异。sentence-transformers默认输出float32但如果你用其他方式加载务必检查print(embeddings.dtype) # 必须是 float32 if embeddings.dtype ! np.float32: embeddings embeddings.astype(np.float32)一个 10 万条、768 维的向量库用float32占 300MB 内存用float64就是 600MB。在内存受限的边缘设备或容器里这 300MB 就是能否跑起来的分界线。3.3 检索实现从暴力搜索到近似搜索何时该升级NumPy 方案的核心检索函数我称之为“三板斧”第一板斧暴力搜索Brute Force——你的默认起点。适用于N 100,000且D 1024的场景即向量总数小于 10 万维度小于 1024。代码就是开头那 4 行但生产环境要加健壮性def brute_search( query_vec: np.ndarray, all_vecs: np.ndarray, k: int 3, threshold: float 0.3 # 相似度阈值低于此认为不相关 ) - list: 暴力搜索计算 query_vec 与 all_vecs 中每个向量的余弦相似度 返回 [(score, chunk_index, chunk_text), ...]按 score 降序 if query_vec.ndim ! 1 or all_vecs.ndim ! 2: raise ValueError(query_vec must be 1D, all_vecs must be 2D) # 确保 query_vec 是单位向量 query_norm np.linalg.norm(query_vec) if query_norm 0: return [] query_unit query_vec / query_norm # 计算点积即余弦相似度 similarities np.dot(all_vecs, query_unit) # shape: (N,) # 过滤低于阈值的 valid_mask similarities threshold if not np.any(valid_mask): return [] # 获取 top-k 索引 top_k_indices np.argsort(similarities[valid_mask])[-k:][::-1] # 映射回原始 all_vecs 的索引 original_indices np.where(valid_mask)[0][top_k_indices] results [] for idx in original_indices: score float(similarities[idx]) # 这里假设你有 texts 列表和 metadata 列表 results.append({ score: score, chunk_index: int(idx), text: texts[idx], metadata: metadata[idx] }) return results这个函数我在线上跑了 18 个月支撑了 3 个日均 5000 查询的客服助手。它快、稳、可预测。N50,000,D768时P95 延迟 12ms。第二板斧scikit-learn 的 NearestNeighbors —— 当 N 超过 10 万。暴力搜索的复杂度是 O(N*D)当 N 到 50 万即使 D384单次查询也要 50ms。这时该用NearestNeighbors的树索引from sklearn.neighbors import NearestNeighbors # 构建索引一次性的离线做 nn NearestNeighbors( n_neighbors10, # 检索时最多返回 10 个实际取 top-k algorithmball_tree, # 对中等维度1000效果好 metriccosine, leaf_size30, # 调整此值平衡内存与速度30 是通用起点 n_jobs-1 # 使用所有 CPU 核心 ) nn.fit(all_vecs) # all_vecs 必须是归一化后的 float32 # 查询 distances, indices nn.kneighbors([query_vec], n_neighborsk) # distances 是余弦距离1-cos_sim所以 score 1 - distances[0] scores 1 - distances[0] results [] for i, idx in enumerate(indices[0]): results.append({ score: float(scores[i]), chunk_index: int(idx), text: texts[idx], metadata: metadata[idx] })注意leaf_size30它控制树的叶子节点大小。设得太小如 10树太深查询慢设得太大如 100每个叶子节点数据太多近似搜索误差大。我测试过 5 个不同数据集leaf_size30在速度和精度间取得了最佳平衡。第三板斧FAISS —— 当你真的需要亚毫秒级响应。scikit-learn的NearestNeighbors在N1,000,000时P95 延迟会升到 80ms。这时可以引入 FAISSFacebook AI Similarity Search它是专为向量搜索优化的 C 库Python 接口极简import faiss import numpy as np # 构建索引 dimension all_vecs.shape[1] index faiss.IndexFlatIP(dimension) # Inner Product即点积等价于余弦相似度因已归一化 # 如果内存紧张可用 IndexIVFFlat 做聚类加速 # index faiss.IndexIVFFlat(faiss.IndexFlatIP(dimension), dimension, 100) index.add(all_vecs.astype(float32)) # FAISS 要求 float32 # 查询 k 3 D, I index.search(np.array([query_vec]).astype(float32), k) # D 是相似度分数I 是索引 results [] for i in range(len(I[0])): idx int(I[0][i]) score float(D[0][i]) results.append({ score: score, chunk_index: idx, text: texts[idx], metadata: metadata[idx] })FAISS 的IndexFlatIP是暴力搜索的极致优化版N1,000,000,D768时P95 延迟稳定在 3ms。但它不提供高级过滤所以元数据过滤仍需在检索前做。实操心得不要一上来就上 FAISS。先用暴力搜索跑通 MVP当监控显示 P95 20ms 且 N 100,000 时再平滑切换到NearestNeighbors当 P95 50ms 且 N 500,000 时再考虑 FAISS。每一次升级都是为了解决一个真实存在的性能瓶颈而不是为了“技术先进”。4. 实操过程与核心环节实现一个完整的端到端案例4.1 场景设定为一家律师事务所构建内部合同审查助手让我们把前面所有理论放进一个真实场景里跑一遍。客户是一家 20 人规模的律所需要一个工具让律师能快速查询历史合同中的特定条款例如“找出所有约定‘不可抗力’免责条款的采购合同并显示具体条文”。需求拆解数据源127 份 PDF 格式的采购合同2020-2024 年平均每份 15 页共约 1800 页。检索目标律师输入自然语言问题系统返回最相关的 3 个合同条款原文及所在合同名称、页码。性能要求P95 延迟 100ms支持并发 5 用户。部署环境一台 16GB 内存、4 核 CPU 的云服务器无 GPU。为什么这个场景完美匹配 NumPy 方案数据总量小127 份 PDF切片后约 8000 个 chunk按 300 字/块估算N8000暴力搜索绰绰有余。业务逻辑简单无需复杂过滤如“2023 年之后签订的”所有合同都有效。部署约束强客户 IT 部门只允许安装 Python 包拒绝 Docker 和任何需要 root 权限的服务。4.2 步骤一数据加载与预处理耗时25 分钟我用pypdf读取 PDFpdfplumber提取更准确的文本尤其对扫描件然后用langchain切片import pdfplumber from langchain.text_splitter import RecursiveCharacterTextSplitter def load_contracts(contract_dir: str) - list: all_chunks [] for pdf_path in Path(contract_dir).glob(*.pdf): with pdfplumber.open(pdf_path) as pdf: full_text for page in pdf.pages: # pdfplumber 的 extract_text() 比 PyPDF2 更准尤其对表格 text page.extract_text() or full_text f\n--- Page {page.page_number} ---\n{text} # 语义切片 splitter RecursiveCharacterTextSplitter( separators[\n\n, \n, 。, , , , , ], chunk_size300, chunk_overlap50, ) chunks splitter.split_text(full_text) # 为每个 chunk 添加元数据 for chunk in chunks: all_chunks.append({ text: chunk.strip(), metadata: { source: pdf_path.name, page_number: get_page_number(chunk, full_text), # 自定义函数用正则找页眉 doc_id: pdf_path.stem, section: extract_section(chunk) # 用正则找“第X条”、“甲方义务”等 } }) return all_chunks chunks load_contracts(./contracts_pdf/) print(fLoaded {len(chunks)} chunks from {len(list(Path(./contracts_pdf/).glob(*.pdf)))} PDFs) # 输出Loaded 7982 chunks from 127 PDFs关键技巧get_page_number()函数不是简单地记页码而是扫描 chunk 文本找类似--- Page 12 ---的标记然后提取数字。这比依赖pdfplumber的page.page_number更可靠因为有些 PDF 页码是图片。4.3 步骤二向量化与存储耗时18 分钟RTX 3060选用BAAI/bge-small-zh-v1.5因为它在中文法律文本上表现优异且体积小、速度快from sentence_transformers import SentenceTransformer import numpy as np model SentenceTransformer(BAAI/bge-small-zh-v1.5) # 提取所有文本 texts [chunk[text] for chunk in chunks] # 批处理编码batch_size64经测试3060 显存刚好 embeddings model.encode( texts, batch_size64, show_progress_barTrue, normalize_embeddingsFalse # 重要 ) # 归一化 norms np.linalg.norm(embeddings, axis1, keepdimsTrue) normalized_embeddings embeddings / norms # 保存 np.save(contracts_embeddings.npy, normalized_embeddings) with open(contracts_chunks.json, w, encodingutf-8) as f: json.dump(chunks, f, ensure_asciiFalse, indent2)实测7982 个 chunkbatch_size64总耗时 18 分钟。生成的contracts_embeddings.npy大小为 11.2MBfloat32, 384 维。4.4 步骤三构建 FastAPI 服务耗时40 分钟核心是把brute_search函数包装成 APIfrom fastapi import FastAPI, HTTPException from pydantic import BaseModel import numpy as np import json app FastAPI(titleLaw Firm Contract Search) # 全局加载 embeddings np.load(contracts_embeddings.npy) with open(contracts_chunks.json, r, encodingutf-8) as f: chunks json.load(f) class SearchRequest(BaseModel): query: str k: int 3 app.post(/search) def search_endpoint(request: SearchRequest): try: # 1. 向量化 query query_vec model.encode([request.query], normalize_embeddingsFalse)[0] query_norm np.linalg.norm(query_vec) if query_norm 0: raise HTTPException(status_code400, detailEmpty query) query_unit query_vec / query_norm # 2. 暴力搜索 similarities np.dot(embeddings, query_unit) top_k_indices np.argsort(similarities)[-request.k:][::-1] # 3. 组装结果 results [] for idx in top_k_indices: score float(similarities[idx]) if score 0.3: # 低分过滤 continue chunk chunks[idx] results.append({ score: score, text: chunk[text], source: chunk[metadata][source], page_number: chunk[metadata][page_number], section: chunk[metadata].get(section, 未知章节) }) return {results: results} except Exception as e: raise HTTPException(status_code500, detailstr(e))启动服务uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2。--workers 2是关键因为 NumPy 计算是 CPU 密集型多进程能更好利用多核。4.5 步骤四性能压测与调优耗时1 小时用locust做压测# locustfile.py from locust import HttpUser, task, between class ContractSearchUser(HttpUser): wait_time between(1, 3) task def search(self): self.client.post(/search, json{ query: 不可抗力事件发生后双方应如何协商处理, k: 3 })结果2 个 worker5 用户并发P95 延迟 8.7msCPU 使用率 45%。完全满足要求。调优点发现首次查询稍慢15ms因为model.encode()有 JIT 编译开销。解决方案在服务启动时用一个 dummy query 预热模型。np.dot在多进程下有时会争抢 GIL。解决方案将embeddings数组设为np.ascontiguousarray(embeddings)确保内存连续提升dot性能。最终整个系统从零到上线耗时 3 小时 20 分钟。客户反馈“比我们之前用的商业软件还快而且所有代码我们都看得懂能自己改。”5. 常见问题与排查技巧实录那些只有亲手踩过才知道的坑5.1 “检索结果完全不相关”——90% 的问题出在这里这是最高频的报错。用户输入“付款方式”返回的却是“违约责任”条款。别急着怀疑模型先按这个清单自查检查项错误示例正确做法为什么重要Query 向量化是否归一化query_vec model.encode([q])[0]未归一化query_vec model.encode([q], normalize_embeddingsTrue)[0]或手动归一化未归一化的 query 与归一化的 doc 向量点积结果无意义Doc 向量是否归一化embeddings model.encode(texts)默认可能未归一化显式normalize_embeddingsFalse再手动embeddings / normsscikit-learn的cosinemetric 会内部归一化但暴力搜索不会相似度计算是否用对了公式用np.linalg.norm(query - doc)欧氏距离用np.dot(query_norm, doc_norm)余弦相似度欧氏距离在高维空间失效余弦相似度才反映语义接近度文本切片是否破坏了语义固定 512 字符切片把“付款”和“30 日内”切到不同 chunk按句号、换行符等语义边界切片检索的最小单元必须是完整语义片段我有个速查脚本每次部署新模型必跑# debug_similarity.py test_queries [付款期限, 保密义务, 不可抗力] test_docs [chunks[0][text], chunks[100][text], chunks[500][text]] for q in test_queries: q_vec model.encode([q], normalize_embeddingsTrue)[0] for d in test_docs: d_vec model.encode([d], normalize_embeddingsTrue)[0] score np.dot(q_vec, d_vec

相关新闻

2026/6/17 5:09:52

机器学习股票方向预测实战:从数据清洗到可解释建模

1. 这不是“炒股秘籍”,而是一份实打实的机器学习入门实战手记我带过不少刚转行做量化分析的朋友,也帮同事从零搭建过多个教学级预测模型。每次有人问“能不能用机器学习预测股票价格”,我第一反应不是讲LSTM或Transformer,而是先…

2026/6/17 4:09:52

机器学习NLP实战:从文本预处理到情感分析模型构建全流程

1. 项目概述:当机器学习遇见自然语言如果你正在学习机器学习,或者对自然语言处理(NLP)感兴趣,那么“头歌机器学习在NLP中的实战”这个项目标题,很可能就是你一直在寻找的、能将理论知识与实际应用连接起来的…

2026/6/17 4:09:52

猫抓浏览器插件:一站式网页媒体资源嗅探与下载解决方案

猫抓浏览器插件:一站式网页媒体资源嗅探与下载解决方案 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 你是否曾为无法保存在线课程视频…

2026/6/17 6:09:53

端侧Qwen3轻量化部署与Skill开发实战

1. 项目概述:为什么要在端侧跑Qwen3?这不是“炫技”,而是真实需求倒逼出的路径最近在好几个硬件厂商的闭门技术会上,被反复问到一个问题:“你们说大模型要落地,可我们连API调用都卡在海外节点上&#xff0c…

2026/6/17 5:09:52

Vue 3跨平台UI框架架构设计:uView-Plus企业级组件库解决方案

Vue 3跨平台UI框架架构设计:uView-Plus企业级组件库解决方案 【免费下载链接】uview-plus 零云uview-plus,是uni-app全面兼容nvue的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水。 项目地址: https://gitcod…

2026/6/17 1:09:50

阿里云国际代理商:如何使用RDS MySQL 构建网站数据库?

在构建企业官方网站、电子商务平台或个人博客系统时,数据库是整个数字基座的核心。以往采用传统方式自行搭建 MySQL 数据库,不仅需要手动进行环境配置、参数调优、备份策略设定,还要面对故障诊断、安全加固等一系列复杂挑战。整个过程常常需要…

2026/6/17 1:09:50

搭建FTP文件共享服务器

1,安装ftp服务器 输入yum install vsftpd (2)修改配置文件 cd /etc/vsftpd 进入vsftp的配置目录 cp vsftpd.conf vsftpd.conf_bak 将原始配置文件备份 vim /etc/vsftpd/vsftpd.conf 修改配置文件anonymous_enableYES anon_upload_enableYES…

2026/6/17 1:09:50

SolidWorks第四部分_直接实体建模特征7_圆角与倒角进阶

圆角与倒角进阶 摘要 在实体建模与计算机辅助设计(CAD)领域,圆角(Fillet)与倒角(Chamfer)是处理实体边线时最基础也最复杂的操作之一。本文将从恒定半径圆角、变半径圆角、面圆角以及拐角倒角四…