optimize_index.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. #!/usr/bin/env python3
  2. """
  3. Optimize RAG keyword index:
  4. 1. Prune useless keywords (too frequent / too rare)
  5. 2. Use pickle for fast loading
  6. 3. Store chunk_id as int index instead of string
  7. """
  8. import json
  9. import pickle
  10. import time
  11. import re
  12. from pathlib import Path
  13. from collections import Counter
  14. BASE = Path("/data/knowledge/maritime")
  15. INDEX_FILE = BASE / "rag_index.json"
  16. KEYWORD_FILE = BASE / "rag_index.keywords.json"
  17. # Optimized outputs
  18. OPT_INDEX = BASE / "rag_index.pkl"
  19. OPT_KEYWORDS = BASE / "rag_keywords.pkl"
  20. print("=== RAG Index Optimizer ===")
  21. # Load original
  22. print("Loading chunk index...")
  23. t0 = time.time()
  24. with open(INDEX_FILE) as f:
  25. data = json.load(f)
  26. chunks = data["chunks"]
  27. print(f" Chunks: {len(chunks)}, loaded in {time.time()-t0:.1f}s")
  28. print("Loading keyword index...")
  29. t0 = time.time()
  30. with open(KEYWORD_FILE) as f:
  31. keywords = json.load(f)
  32. t1 = time.time()
  33. print(f" Keywords: {len(keywords)}, loaded in {t1-t0:.1f}s")
  34. # Analyze
  35. lengths = [len(v) for v in keywords.values()]
  36. print(f"\n--- Before optimization ---")
  37. print(f" Total keywords: {len(keywords)}")
  38. print(f" Total postings: {sum(lengths)}")
  39. print(f" Avg postings/keyword: {sum(lengths)/len(lengths):.1f}")
  40. print(f" Max postings: {max(lengths)}")
  41. # Build chunk_id -> int index mapping
  42. chunk_ids = [c["id"] for c in chunks]
  43. id_to_idx = {cid: i for i, cid in enumerate(chunk_ids)}
  44. # Prune keywords
  45. MIN_FREQ = 2 # Keywords appearing in <2 chunks are noise
  46. MAX_FREQ_PCT = 0.3 # Keywords appearing in >30% of chunks are too common (stopwords)
  47. max_freq = int(len(chunks) * MAX_FREQ_PCT)
  48. pruned = {}
  49. removed_rare = 0
  50. removed_common = 0
  51. for kw, posting_list in keywords.items():
  52. n = len(posting_list)
  53. if n < MIN_FREQ:
  54. removed_rare += 1
  55. continue
  56. if n > max_freq:
  57. removed_common += 1
  58. continue
  59. # Convert string IDs to int indices for compact storage
  60. int_postings = []
  61. for cid in posting_list:
  62. if cid in id_to_idx:
  63. int_postings.append(id_to_idx[cid])
  64. if int_postings:
  65. pruned[kw] = int_postings
  66. print(f"\n--- After pruning ---")
  67. print(f" Removed rare (<{MIN_FREQ}): {removed_rare}")
  68. print(f" Removed common (>{max_freq}): {removed_common}")
  69. print(f" Remaining keywords: {len(pruned)}")
  70. print(f" Total postings: {sum(len(v) for v in pruned.values())}")
  71. # Build optimized chunk data (minimal fields)
  72. opt_chunks = []
  73. for c in chunks:
  74. opt_chunks.append({
  75. "id": c["id"],
  76. "source": c["source"],
  77. "chunk_id": c["chunk_id"],
  78. "text": c["text"],
  79. "length": c["length"],
  80. })
  81. # Save as pickle
  82. print("\nSaving optimized index (pickle)...")
  83. t0 = time.time()
  84. opt_data = {
  85. "total_chunks": len(opt_chunks),
  86. "total_files": data.get("total_files", 0),
  87. "chunk_size": data.get("chunk_size", 512),
  88. "chunks": opt_chunks,
  89. "chunk_ids": chunk_ids, # for reverse lookup
  90. }
  91. with open(OPT_INDEX, "wb") as f:
  92. pickle.dump(opt_data, f, protocol=pickle.HIGHEST_PROTOCOL)
  93. with open(OPT_KEYWORDS, "wb") as f:
  94. pickle.dump(pruned, f, protocol=pickle.HIGHEST_PROTOCOL)
  95. t1 = time.time()
  96. print(f" Saved in {t1-t0:.1f}s")
  97. # File sizes
  98. idx_size = OPT_INDEX.stat().st_size / 1024 / 1024
  99. kw_size = OPT_KEYWORDS.stat().st_size / 1024 / 1024
  100. old_kw_size = KEYWORD_FILE.stat().st_size / 1024 / 1024
  101. print(f"\n--- File sizes ---")
  102. print(f" Chunk index: {idx_size:.1f} MB (pickle)")
  103. print(f" Keyword index: {kw_size:.1f} MB (pickle) vs {old_kw_size:.1f} MB (JSON) = {kw_size/old_kw_size*100:.0f}%")
  104. # Verify load time
  105. print("\n--- Load time verification ---")
  106. t0 = time.time()
  107. with open(OPT_INDEX, "rb") as f:
  108. d1 = pickle.load(f)
  109. t1 = time.time()
  110. with open(OPT_KEYWORDS, "rb") as f:
  111. d2 = pickle.load(f)
  112. t2 = time.time()
  113. print(f" Chunk index load: {(t1-t0)*1000:.0f}ms")
  114. print(f" Keyword index load: {(t2-t1)*1000:.0f}ms")
  115. print(f" Total: {(t2-t0)*1000:.0f}ms")
  116. print(f"\n Speedup: {43000/(t2-t0)/1000:.0f}x faster!")