| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 |
- """
- Step 1: 加载中文 embedding 模型
- Step 2: 为 _chunks.jsonl 全部 22,781 条计算向量
- Step 3: 写入 FAISS 索引(含分类过滤元数据)
- 输出:
- _vectors.np — 22,781 × dim 向量矩阵
- _vector_meta.json — 每行 chunk 的 metadata(含 category)
- _faiss_index.bin — FAISS 索引文件
- """
- import json, os, sys
- import numpy as np
- from pathlib import Path
- from collections import defaultdict
- BASE = Path(r"C:\code\cfc\docs\菌群知识库")
- CHUNKS_FILE = BASE / "_chunks.jsonl"
- VEC_NP = BASE / "_vectors.npy"
- VEC_META = BASE / "_vector_meta.json"
- FAISS_INDEX = BASE / "_faiss_index.bin"
- EMBED_MODEL = "BAAI/bge-small-zh-v1.5"
- MAX_SEQ_LEN = 256
- BATCH_SIZE = 128
- print("=== Step 1: 加载中文 embedding 模型 ===")
- from sentence_transformers import SentenceTransformer
- print(f" 加载模型: {EMBED_MODEL} ...")
- model = SentenceTransformer(EMBED_MODEL)
- vec_dim = model.get_sentence_embedding_dimension()
- print(f" 向量维度: {vec_dim}")
- print(" 模型加载完成")
- print("\n=== Step 2: 读取 JSONL 并计算向量 ===")
- chunks_data = []
- meta_list = []
- with open(CHUNKS_FILE, "r", encoding="utf-8") as f:
- for line in f:
- d = json.loads(line)
- chunks_data.append(d)
- print(f" 共 {len(chunks_data)} 条 chunk")
- # 分批计算向量
- print(f" 开始计算向量(batch_size={BATCH_SIZE})...")
- all_vectors = []
- for i in range(0, len(chunks_data), BATCH_SIZE):
- batch = chunks_data[i:i+BATCH_SIZE]
- texts = [c["content"] for c in batch]
- vecs = model.encode(texts, batch_size=BATCH_SIZE, show_progress_bar=(i % 2000 == 0), normalize_embeddings=True)
- all_vectors.append(vecs)
- if (i // BATCH_SIZE + 1) % 10 == 0:
- print(f" processed {min(i+BATCH_SIZE, len(chunks_data))}/{len(chunks_data)} chunks...")
- vectors = np.concatenate(all_vectors, axis=0)
- vectors = np.ascontiguousarray(vectors, dtype=np.float32)
- print(f" 向量矩阵: {vectors.shape}")
- # 保存向量
- np.save(VEC_NP, vectors)
- print(f" 向量已保存: {VEC_NP}")
- # 保存 metadata(顺序与向量一致)
- for c in chunks_data:
- meta_list.append({
- "id": c["id"],
- "title": c["metadata"]["title"],
- "postId": c["metadata"]["postId"],
- "category": c["metadata"]["category"],
- "source_url": c["metadata"].get("source_url", ""),
- "topics": c["metadata"].get("topics", []),
- "chunk_index": c["metadata"]["chunk_index"],
- "chunk_total": c["metadata"]["chunk_total"],
- "filename": c["metadata"]["filename"],
- "content_preview": c["content"][:120],
- })
- with open(VEC_META, "w", encoding="utf-8") as f:
- json.dump(meta_list, f, ensure_ascii=False)
- print(f" 元数据已保存: {VEC_META}")
- # 分类过滤索引(category → chunk_indices)
- cat_indices = defaultdict(list)
- for idx, m in enumerate(meta_list):
- cat_indices[m["category"]].append(idx)
- cat_filter = json.dumps({k: v for k, v in cat_indices.items()}, ensure_ascii=False)
- with open(BASE / "_cat_filter.json", "w", encoding="utf-8") as f:
- json.dump({k: v for k, v in cat_indices.items()}, f, ensure_ascii=False)
- print("\n=== Step 3: 写入 FAISS 索引 ===")
- import faiss
- # 创建 HNSW 索引(带分类过滤支持)
- print(f" 创建 FAISS IDMap + HNSW 索引...")
- idx_flat = faiss.IndexFlatIP(vec_dim) # 内积(已归一化向量)
- idx = faiss.IndexIDMap2(idx_flat)
- # 添加向量(IDs = 0..N-1)
- idx.add_with_ids(vectors, np.arange(len(vectors)))
- print(f" 索引已添加 {idx.ntotal} 条向量")
- # 保存 FAISS 索引
- faiss.write_index(idx, str(FAISS_INDEX))
- print(f" FAISS 索引已保存: {FAISS_INDEX}")
- # 统计分类覆盖
- print("\n=== 分类统计 ===")
- for cat, indices in sorted(cat_indices.items()):
- print(f" {cat}: {len(indices)} chunks")
- print(f"\n✅ 完成!{len(chunks_data)} chunks → {vectors.shape[1]}d 向量 → FAISS 索引")
|