# 0127 https://docs.google.com/document/d/1nOUNee9v8Wt7AVz50SGxu1fIiHhpVsgXqjVxhVgHSAo/edit?usp=sharing # 0120 https://docs.google.com/presentation/d/1qsTWXTzfT8NvkEJeyKI8gjU-Aooyu9UvFq2lQC6qMSc/edit?usp=sharing # 0113 https://docs.google.com/document/d/1skRGRJF2pReWssrWNrKa00tmYXO6Lxllj5ntHjB2Q_E/edit?usp=sharing # efnet ## summery ![image](https://hackmd.io/_uploads/ByacLagHbg.png) ## 每個 query 的完整搜尋流程 ![image](https://hackmd.io/_uploads/rJzO3nxr-g.png) ![image](https://hackmd.io/_uploads/HyuYn2eHWx.png) ![image](https://hackmd.io/_uploads/BJg53ngSZg.png) ![image](https://hackmd.io/_uploads/HyFq3nxBWe.png) ![image](https://hackmd.io/_uploads/BkQs32xS-l.png) ![image](https://hackmd.io/_uploads/B1f2nhgrZg.png) ![image](https://hackmd.io/_uploads/ryR23hxBWx.png) ![image](https://hackmd.io/_uploads/HyDT2nlSbl.png) ## test python NN_dynamic_efsearch.py --config ./config.json --threads 1 --reach_threshold 0.5 ![image](https://hackmd.io/_uploads/BJpwe_yH-x.png) ## budget ![image](https://hackmd.io/_uploads/ByTaQ2lSWg.png) # Distribution-Aware Exploration for Adaptive HNSW Search https://docs.google.com/presentation/d/1qsTWXTzfT8NvkEJeyKI8gjU-Aooyu9UvFq2lQC6qMSc/edit?usp=sharing ![image](https://hackmd.io/_uploads/Bk0fkttEbx.png) ![image](https://hackmd.io/_uploads/rygEkFtV-g.png) ![image](https://hackmd.io/_uploads/rk4SJKKN-g.png) ![image](https://hackmd.io/_uploads/B1JL1Kt4bx.png) ![image](https://hackmd.io/_uploads/SkPN5yqVZl.png) ![image](https://hackmd.io/_uploads/r1Zr5k5NWl.png) ![image](https://hackmd.io/_uploads/rkx85kqEZx.png) ![image](https://hackmd.io/_uploads/B1qL9kcVWe.png) ![image](https://hackmd.io/_uploads/HJjnVh_V-l.png) ![image](https://hackmd.io/_uploads/BklzR4hd4-e.png) ![image](https://hackmd.io/_uploads/SyWPQB8E-x.png) ![image](https://hackmd.io/_uploads/HJds7BL4-g.png) ![image](https://hackmd.io/_uploads/B1rFVSUEZg.png) ![image](https://hackmd.io/_uploads/SyT9IS84-g.png) ![image](https://hackmd.io/_uploads/rJmALHUNbe.png) ![image](https://hackmd.io/_uploads/HJmtIr84Zx.png) ![image](https://hackmd.io/_uploads/HJnxwBL4Ze.png) ![image](https://hackmd.io/_uploads/rkJ7KYLVWg.png) ![image](https://hackmd.io/_uploads/HyjecKUVZl.png) ![image](https://hackmd.io/_uploads/BJo1ctIVZx.png) ![image](https://hackmd.io/_uploads/S1M7qt8Nbe.png) ![image](https://hackmd.io/_uploads/S1DRnF8EZl.png) ![image](https://hackmd.io/_uploads/ry-p3KUN-x.png) ![image](https://hackmd.io/_uploads/SJ6LVcLV-x.png) ![image](https://hackmd.io/_uploads/BkFDEcIVWe.png) ![image](https://hackmd.io/_uploads/HJM_49IE-l.png) ![image](https://hackmd.io/_uploads/SyyMGhdV-g.png) ![image](https://hackmd.io/_uploads/H1M-M2d4Zx.png) ![image](https://hackmd.io/_uploads/S1YIGnON-g.png) ![image](https://hackmd.io/_uploads/S15wW6OE-e.png) ![image](https://hackmd.io/_uploads/SkGubauVWg.png) ![image](https://hackmd.io/_uploads/B1C3Ga_Ebe.png) ![image](https://hackmd.io/_uploads/BJSnGTdEZe.png) ![image](https://hackmd.io/_uploads/rknfQTuE-e.png) ![image](https://hackmd.io/_uploads/rJG0V6uEZl.png) ![image](https://hackmd.io/_uploads/Sy6frpuE-x.png) ![image](https://hackmd.io/_uploads/rJ7IHT_VZl.png) ![image](https://hackmd.io/_uploads/H1nzL6_VZx.png) ![image](https://hackmd.io/_uploads/Hy47I6uNbe.png) ![image](https://hackmd.io/_uploads/rJ9tIa_EWx.png) ![image](https://hackmd.io/_uploads/SyCcWZF4Zg.png) ![image](https://hackmd.io/_uploads/S1D7qbKEZl.png) ![image](https://hackmd.io/_uploads/Hki3jbYVbe.png) ![image](https://hackmd.io/_uploads/HJNaibF4bx.png) ![image](https://hackmd.io/_uploads/ryi6jWYVbg.png) ![image](https://hackmd.io/_uploads/HkGRibKNWl.png) ![image](https://hackmd.io/_uploads/BydCsbF4bx.png) ![image](https://hackmd.io/_uploads/BJnwhWYNWg.png) ![image](https://hackmd.io/_uploads/B1iu2bFEZx.png) # 1230 https://docs.google.com/document/d/13aS-QEgzCx8C2xiZpdKTSxbhZ7CXAfi6ihKhyQNpqwk/edit?usp=sharing # HNSW 預測efsearch 必須讓「容易 query 的 ef 下降省下的成本」大於「probe + 模型」的額外成本。 ## 為每個 query 做一次 probe-run 並抽特徵 ### 特徵清單 ::: warning ### faiss 文件內容 ![image](https://hackmd.io/_uploads/HkAA41bVZe.png) 1. **nhops**:在圖上走了多少「步」 [nhops](https://faiss.ai/cpp_api/struct/structfaiss_1_1HNSWStats.html?utm_source=chatgpt.com) 2. ndis: 多少個 query「候選清單跑光」而結束 [ndis](https://faiss.ai/cpp_api/struct/structfaiss_1_1HNSWStats.html?utm_source=chatgpt.com) 3. n1 : ??? 4. n2 : 搜尋過程中檢查過的向量數 ![image](https://hackmd.io/_uploads/S1ePQRg4-x.png) ::: :::info ### 實際 1. n1:執行了多少次查詢(每個 query 加 1) 2. n2:多少次查詢在候選集合空掉時結束 3. ndis:整體搜尋過程中做了多少次距離計算(distance computations) 4. nhops:在 HNSW 圖上從一個節點走到另一個節點的總步數 ::: 5. Index::search(...) 的輸出包含 ``` * n: query數 * k: 最近的k個 * distances:輸出距離(大小 n*k) * labels:輸出近鄰的 ID(大小 n*k) ``` 1. d1 = D[:, 0] (取mean, std, min, max) 每個 query 與**第一名**鄰居距離(d1小 → 比較好找、密度高)(大 →可能落在稀疏區/離群) 2. dk = D[:, k-1] (取mean, std, min, max) 每個 query 與**最後一名**鄰居距離 (若 dk 和 d1 很接近,代表 top-10 都在一個很緊密的小半徑內;反之則表示鄰居分散。) 3. margin_k = dk - d1 (取mean, std, min, max) 第 1 名到第 10 名距離拉開多少 (小 margin:top-10 很集中,局部密度高,鄰居彼此距離差不多 大 margin:top-10 不集中) 4. ratio_k = dk / (d1 + eps) (取mean, std, min, max) 最後一名距離是第1名距離的幾倍(接近 1:第 10 名和第 1 名差不多近 → top-k 很緊密。很大:距離分布很不均) ### 只取 (A) Probe (delta over this single search) exhausted (ndis delta) = 929 distcomp (nhops delta) = 37 (B) Distance-shape features (from top-k results) d1 = 58281 dk(k) = 72315 margin_k = 14034 ratio_k = 1.2408 ## 離線學 node embedding 1. 對整張圖做 node embedding(離線) 用 node2vec / DeepWalk / GraphSAGE 之類(或你自己定義 message passing),把每個資料點學成一個 embedding,代表它在 HNSW 圖中的位置與連結型態(可分 layer 做)。 2. query 怎麼用到這些 embedding query 不是圖上的節點,所以不能直接 message pass。常見作法是: 用 efProbe 找到一小撮候選節點 S(例如 50 個) 把它們的 node embedding 聚合(mean/attention pooling) 再和 query 向量特徵一起丟進 predictor → 輸出 efSearch 這樣模型確實「吃到了圖拓撲」,但推論成本仍然可控,因為你只聚合 probe 命中的小子圖。 ## Probe + 難度模型 訓練標籤怎麼定 對每個 query 定義一個目標,例如: 目標:recall@k ≥ 0.95(相對於 exact kNN 或很大的 efMax 當作近似 GT) 標籤:最小 efSearch* 使得 recall@k 達標 做法通常是:對每個 query 在 ef ∈ [ef_min, ef_max] 上 binary search 找到最小 ef*(或用階梯掃描也可以但比較慢)。 特徵怎麼抓(關鍵) 只看 query 向量本身通常不夠,因為「難度」強烈依賴圖與資料分佈。實務上很有效的一招是: 先用很小的 efProbe 跑一次 HNSW(例如 16 或 32),把這次走圖的早期訊號當特徵,再由模型輸出該 query 的 efSearch。 常見有效特徵(幾乎不需要全圖計算): Probe 過程的統計: 訪問節點數、距離計算數 best distance 的下降曲線斜率(前幾步改善速度) 候選隊列大小變化、pop 次數、top 候選的 distance margin(第1名 vs 第k名) Probe 命中的節點的「局部圖特徵」: 平均 degree、layer 分佈(HNSW 多層) probe 命中節點彼此距離的分散程度(代表局部是否混亂) query 的幾何特徵: norm、與資料中心/多個 centroid 的距離(粗略密度指標) 模型本體用 GBDT(LightGBM/XGBoost)或小 MLP 通常就很好;輸出可以是: 迴歸:predict log(ef*) 分類:把 ef 分桶(如 16/32/64/128/256),直接選桶 線上推論流程 先跑 efProbe 小搜尋拿特徵 模型輸出 efPred(可再做 clamp:efProbe,efMax) 用 efPred 跑正式搜尋回傳 top-k # cv final ![image](https://hackmd.io/_uploads/H1-04Cd7-x.png) ![image](https://hackmd.io/_uploads/ryFI8Rd7We.png) ![image](https://hackmd.io/_uploads/SyroICO7Zx.png) ![image](https://hackmd.io/_uploads/r1hvwR_mWe.png) ![image](https://hackmd.io/_uploads/Hk9EuAOXWg.png) ![image](https://hackmd.io/_uploads/Bkfd_RdmZl.png) ![image](https://hackmd.io/_uploads/HynudRuX-e.png) ![image](https://hackmd.io/_uploads/r1PWt0OmZl.png) ![image](https://hackmd.io/_uploads/SJ3TqRuXbg.png) ![image](https://hackmd.io/_uploads/BkpC50_7Wx.png) ![image](https://hackmd.io/_uploads/BkBPC0_7bl.png) ![image](https://hackmd.io/_uploads/ryb_RRu7-g.png) ![image](https://hackmd.io/_uploads/rkYxrytm-l.png) ![image](https://hackmd.io/_uploads/B1V6mTwQWg.png) ![image](https://hackmd.io/_uploads/rJQ3XpPmWe.png) # data mining project 超高分觀察 Epoch 10 Train Loss: 0.0330, Train Acc: 0.9893 --> Epoch 10 Val Loss: 0.0909, Val Acc: 0.9333 Validation Confusion Matrix: [[ 7 0] [ 11 147]] 訓練完成,最佳驗證準確率: 0.9878787878787879 1. 指定lossfunction 比例並不是亂給 ![image](https://hackmd.io/_uploads/r1eJzKZQbe.png) 2. SqueezeNet 3. 標準化 transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) 這組數字是統計了 ImageNet 資料集(包含 100 萬張圖片、1000 個類別)中所有像素的結果: Mean (平均值): 代表 ImageNet 中所有圖片 R、G、B 三個通道的平均亮度。 Std (標準差): 代表這些顏色數值的變動幅度。 因為大多數的自然照片(包括你的蝴蝶照片)顏色分佈都跟 ImageNet 類似,所以這組數字變成了電腦視覺界的「通用密碼」。 # 繪圖 https://excalidraw.com/ # cv論文報告 ![image](https://hackmd.io/_uploads/Hykzb8LzWe.png) ![image](https://hackmd.io/_uploads/Bk1mZILzWg.png) ![image](https://hackmd.io/_uploads/Hy_7WILzWg.png) ![image](https://hackmd.io/_uploads/BJv4bILzbl.png) ![image](https://hackmd.io/_uploads/rJ04-IIzWl.png) ![image](https://hackmd.io/_uploads/SJUHb8Ufbx.png) ![image](https://hackmd.io/_uploads/BkhBZI8MZg.png) ![image](https://hackmd.io/_uploads/r1QUZULzbl.png) ![image](https://hackmd.io/_uploads/SJ58Z8Uzbl.png) ![image](https://hackmd.io/_uploads/HJZPZIIMbx.png) ![image](https://hackmd.io/_uploads/SytPbU8f-l.png) ![image](https://hackmd.io/_uploads/Sk1dbIIzbe.png) ![image](https://hackmd.io/_uploads/HkZKZ8IzZe.png) ![image](https://hackmd.io/_uploads/Sy8FWLIz-x.png) ![image](https://hackmd.io/_uploads/rJV5ZLUz-x.png) # 12/09 https://docs.google.com/document/d/1yXbVIJo30Eo6lUdHu4cude-6z6R-NJoTYi8938QWEDE/edit?usp=sharing # line bot https://hackmd.io/@flagmaker/S1VdzaGSye https://hackmd.io/@flagmaker/S1VdzaGSye # 12/2 報告 https://docs.google.com/presentation/d/1avMVb8QtPXAywe-aRfKUNP2mo8zqfddAbJGa3WCa768/edit?usp=sharing ## Secure Summation ![image](https://hackmd.io/_uploads/B1QFTHjbWe.png) ![image](https://hackmd.io/_uploads/ByJqTSjWWx.png) ![image](https://hackmd.io/_uploads/ryH9pBjWWx.png) ![image](https://hackmd.io/_uploads/SyfsTBobbl.png) ![image](https://hackmd.io/_uploads/HkcjarjWWl.png) ![image](https://hackmd.io/_uploads/ryX2TSiZbx.png) ![image](https://hackmd.io/_uploads/rkt3aBob-g.png) ![image](https://hackmd.io/_uploads/Sy-Tprs-Wx.png) ## Secure Comparison ![image](https://hackmd.io/_uploads/H1R8LLs-Zg.png) ![image](https://hackmd.io/_uploads/rJ4OLLobbe.png) # 11/25 https://docs.google.com/presentation/d/1Q2biGWNwe3V-VI8CwKJ9JsY4zl2OXzqR67T2bdAYSkA/edit?usp=sharing # Attention 1. Attention 是什麼?(一句話版本) Attention = 找出「哪些部分比較重要」並給它們更高權重的機制。 在影像中 → 哪些區域重要 在文字中 → 哪些詞重要 在跨模態中 → 哪些文字片段和哪些影像片段互相關 2. 超白話比喻:自助餐找你最想吃的菜 你走進自助餐(一大堆食物 = 一大堆資訊),你會: - 炸雞腿 → 注意力超高 - 燙青菜 → 注意力中等 - 胡蘿蔔 → 注意力低 你不會每道菜都平均拿,你會根據喜好「加權取樣」。 這就是 Attention 的核心: → 從海量資訊中挑出最相關的部分 → 給不同資訊不同權重 3. Attention 的基本構成:Q、K、V 三大核心向量: Q = Query → 你現在在找什麼 K = Key → 所有資料的「標籤」 V = Value → 真正的資料內容 流程: 拿 Q 去跟所有 K 比對相似度 → 轉成權重 → 加權所有 V 公式: Attention(Q, K, V) = softmax( (Q·K^T) / √d ) × V 4. 超清楚例子 句子:「The man is riding a bicycle.」 想知道 “riding” 跟哪些詞最相關? Q = embedding("riding") K = 所有詞的 embedding V = 所有詞的 embedding 結果:bicycle 和 man 權重最高 → riding 的語意會特別強調「人」和「腳踏車」 5. Attention 的兩大類型(超重要) (1) Self-Attention Q、K、V 都來自同一序列(純文字或純影像) 經典應用:Transformer 內部的注意力 (2) Cross-Attention Q 來自一種模態,K/V 來自另一種模態 經典應用:影像-文字模型(如 CLIP、BLIP、Flamingo) - 文字找圖:Q=text, K/V=image - 圖找文字:Q=image, K/V=text 6. Attention 為什麼強大? - 自動發現關鍵內容(「bicycle」直接找到圖中腳踏車) - 輕鬆處理長距離依賴 - 並行計算,比 RNN 快很多 - 通用性極高:文字、影像、語音、蛋白質序列都行 7. 數學拆解(專業版) Attention(Q, K, V) = softmax( (QK^T)/√d_k) ) V 步驟: 1. QK^T → 算相似度 2. /√d_k → 縮放防止梯度消失 3. softmax → 轉成權重(加總=1) 4. ×V → 加權後輸出 8. 影像–文字 Cross-Attention 範例 句子:「A man is riding a bicycle.」 影像區域:v1=man, v2=bike, v3=wheel, v4=tree, v5=sky 對 token「bicycle」做 attention: 相似度 → bike 0.60、wheel 0.30、man 0.05、tree 0.03、sky 0.02 softmax 後權重 ≈ bike 58%、wheel 29%、其他少少 最終輸出 = 0.58×v2 + 0.29×v3 + … → 這個 token 現在「知道」自己對應到圖中腳踏車 9. Attention 的缺點(為什麼後來有人用 OT) - 每個 token 都會分到一點注意力 → 背景雜訊進來 - 計算量 O(N×M) 很大 - 只能壓低權重,無法真正丟掉不相關的 fragment → OMIT 等新方法用 Optimal Transport + Partial Matching,直接把不重要的影像 fragment 丟掉,乾淨又省算力 一句話總結 Attention 就是「用 Query 去問所有 Key,找出最相關的 Value 並加權」,是 Transformer 和所有現代多模態模型的靈魂。 ******* # 一張圖片和一段文字的語意是否一致。 ## VSE:整體向量匹配方法(粗粒度) VSE(Visual Semantic Embedding)把整張圖片 / 整段文字向量化成一個向量,再用 cosine similarity 來比較。 缺點: 把圖片或句子壓成一個向量,資訊損失嚴重 很難捕捉到「局部語意」(fine-grained semantics) 無法好好處理複雜圖片(例如多個物件) VSE 最後只用兩個平均向量(黑色與灰色)比一次 cosine similarity ## PEM Step 1:抽取影像與文字的局部特徵 Step 2:把局部特徵轉成 “高斯分布” 的參數 Step 3:比較二個分布間的距離(divergence) PEM 把圖片與文字都變成「機率分布」,並用分布之間的 divergence 做相似度。比 VSE 更彈性,但仍無法做真正的細粒度對齊。 ## CAM 可以用一句話總結為: CAM 用 cross-attention 計算每個文字片段與影像片段的對應,再用 cosine similarity 評估整體匹配度,是細粒度的方法,但容易受冗餘片段干擾且成本較高。 **** # 1119 [Link](https://docs.google.com/presentation/d/1-2JSpDa7PBbNKEqqhvyTK3hkqSmPi-E66WihqYXSbj0/edit?usp=sharing) # faiss ![image](https://hackmd.io/_uploads/B1YhfLgg-l.png) ![image](https://hackmd.io/_uploads/Hk4O_v-y-x.png) ![image](https://hackmd.io/_uploads/ryctuDZJ-g.png) ![image](https://hackmd.io/_uploads/BJSJ6uWy-e.png) ![image](https://hackmd.io/_uploads/Byt02db1Zl.png) ![image](https://hackmd.io/_uploads/H1DcdDbJZg.png) ![image](https://hackmd.io/_uploads/S1yg2PZybg.png) ![image](https://hackmd.io/_uploads/SJVJ2v-yZl.png) ![image](https://hackmd.io/_uploads/BJCJJ_ZkZl.png) ![image](https://hackmd.io/_uploads/Skmby_ZkWl.png) ![image](https://hackmd.io/_uploads/SkH1Xd-JWl.png) ![image](https://hackmd.io/_uploads/Bkj1md-1-g.png) ![image](https://hackmd.io/_uploads/HJvlX_byWe.png) ![image](https://hackmd.io/_uploads/BJ6xXOWJ-l.png) ![image](https://hackmd.io/_uploads/rylR1KWy-l.png) ![image](https://hackmd.io/_uploads/H1TxbF-1-e.png) ![image](https://hackmd.io/_uploads/Hk-8Zo30xx.png) ![image](https://hackmd.io/_uploads/r13dZjhRxe.png) ![image](https://hackmd.io/_uploads/Sk56Wi20le.png) ![image](https://hackmd.io/_uploads/S17Vzi3Cgx.png) ![image](https://hackmd.io/_uploads/H1q3Ij20xx.png) ![image](https://hackmd.io/_uploads/HyRYDonAle.png) ![image](https://hackmd.io/_uploads/ByzduinRlg.png) ![image](https://hackmd.io/_uploads/HyH8Yj3Clx.png) ![image](https://hackmd.io/_uploads/HJM5tshRlx.png) ![image](https://hackmd.io/_uploads/BkPnKsnClg.png) ![image](https://hackmd.io/_uploads/ByHW9onAeg.png) ![image](https://hackmd.io/_uploads/B1PfQ33Aex.png) ![image](https://hackmd.io/_uploads/B1wY72hAgx.png) ![image](https://hackmd.io/_uploads/S1iR73n0xe.png) ![image](https://hackmd.io/_uploads/SkigN2hRge.png) ![image](https://hackmd.io/_uploads/BJ1FN2nCee.png) ![image](https://hackmd.io/_uploads/BkqLbCTCxl.png) ![image](https://hackmd.io/_uploads/H1H2Z0aAle.png) ![image](https://hackmd.io/_uploads/H1WzNRT0eg.png) ![image](https://hackmd.io/_uploads/SkQP4Ap0ee.png) ![image](https://hackmd.io/_uploads/H1LhIAa0xg.png) ![image](https://hackmd.io/_uploads/ryAywCp0ll.png) ![image](https://hackmd.io/_uploads/S1Fw_0aCel.png) ![image](https://hackmd.io/_uploads/SJdid060lx.png) ![image](https://hackmd.io/_uploads/B1EeFRaReg.png) ![image](https://hackmd.io/_uploads/H1gz9CpRgg.png) ![image](https://hackmd.io/_uploads/Bydb6R60gl.png) # 資料探勘助教 ## Scikit-learn ```py= ``` ```py= wine = load_wine() X, y = wine.data, wine.target print(f"features: {X.shape[1]}, samples: {X.shape[0]}, classes: {len(wine.target_names)}") print(f"Class names: {wine.target_names}") ``` **** 把 sklearn 載入的原始資料(numpy 格式)轉換成 pandas DataFrame,並把特徵和標籤整合在同一張表格裡,方便後續做探索性資料分析或模型訓練。 ```py= F = pd.DataFrame(wine.data, columns=wine.feature_names) t = pd.Series(wine.target, name="target") # 合併成一個 DataFrame wine_df = pd.concat([F, t], axis=1) wine_df.head() ``` **** 類別分布檢查,確認資料是否平衡。如果類別數差異太大,訓練模型時可能會導致「偏向樣本多的類別」。 ```py= print(np.bincount(y)) # 查看三個classes的分布 ``` (1)是否形成清楚群聚、(2)群聚間是否有橋接點/重疊、(3)明顯離群點。 ![image](https://hackmd.io/_uploads/Hy69kFsnlx.png) **** 決策樹就像一連串的 if-else 判斷,用特徵逐步把資料切分成不同群組,最後在葉節點輸出預測結果。 ```py= from sklearn.tree import DecisionTreeClassifier tree_model = DecisionTreeClassifier( max_depth=5, # 限制最大深度,避免過擬合 min_samples_split=4, # 至少4個樣本才能分裂 min_samples_leaf=2, # 葉子節點至少2個樣本 random_state=42 # 設定隨機種子,確保每次運行的結果一致。 ) tree_model.fit(X_train, y_train) ``` **** 條件(例:x[9] <= 3.82) → 代表用第 9 個特徵來分裂,閾值是 3.82。 → 若條件為真,走左邊;為假,走右邊。 gini:基尼不純度 (Gini Impurity) → 測量這個節點的「混亂程度」。 → 0 代表純粹(只有單一類別),越高代表類別混雜越多。 samples:這個節點包含多少筆樣本。 value:三個類別樣本數,例如 [41, 30, 33] 表示: 類別 0 有 41 筆 類別 1 有 30 筆 類別 2 有 33 筆 ![image](https://hackmd.io/_uploads/SJOwXS5hge.png) **** GaussianNB 假設每個特徵符合 高斯分布(常態分布)。適合連續型特徵的資料 ```py= from sklearn.naive_bayes import GaussianNB NB_model = GaussianNB() NB_model.fit(X_train, y_train) NB_model.score(X_test, y_test) ``` **** 列 (row) → 真實標籤 (True label) 欄 (column) → 模型預測 (Predicted label) ```py= from sklearn.metrics import confusion_matrix y_pred = NB_model.predict(X_test) print(confusion_matrix(y_test, y_pred)) # 印出混淆矩陣 [[18 0 0] [ 0 20 1] [ 0 2 13]] ``` **** SVM 的目標是找出一條最佳「超平面 (hyperplane)」來分隔不同類別,同時最大化邊界 (margin)。 ```py= from sklearn.svm import SVC SVC_model = SVC( C=1, # 增加懲罰參數。值越大,越嚴格(容易過擬合);值越小,允許更多錯誤(可能欠擬合)。 kernel='linear', # 核函數,決定如何將資料映射到高維空間。常用的有 'linear', 'poly', 'rbf', 'sigmoid'。 gamma=0.01, # 控制高斯核(RBF)、多項式核的影響範圍。值越大,影響範圍越小(容易過擬合)。如用linear則不需要gamma。 random_state=42 ) SVC_model.fit(X_train, y_train) SVC_model.score(X_test, y_test) ``` **** MLP(多層感知機, Multi-Layer Perceptron) MLP 是一種前饋式神經網路 (Feedforward Neural Network)。 由 輸入層 → 一個或多個隱藏層 → 輸出層 組成。 每一層由多個「神經元 (neuron)」構成,神經元之間有權重 (weights) 連接。 ```py= from sklearn.ensemble import RandomForestClassifier RF = RandomForestClassifier( n_estimators=20, # 森林中樹的數量,設定20棵樹來增加穩定性 max_depth=5, # 每棵樹的最大深度限制為5,以避免過擬合 min_samples_split=5, # 若一個節點的樣本數少於5,則不再分裂 min_samples_leaf=2, # 每個葉節點至少保留2筆樣本,以避免過度分裂 random_state=42 ) RF.fit(X_train, y_train) RF.score(X_test, y_test) ``` **** 它結合了多棵 決策樹 (Decision Tree),利用 Bagging (Bootstrap Aggregating) 技術提升穩定性與準確度。 ```py= from sklearn.ensemble import RandomForestClassifier RF = RandomForestClassifier( n_estimators=20, # 森林中樹的數量,設定20棵樹來增加穩定性 max_depth=5, # 每棵樹的最大深度限制為5,以避免過擬合 min_samples_split=5, # 若一個節點的樣本數少於5,則不再分裂 min_samples_leaf=2, # 每個葉節點至少保留2筆樣本,以避免過度分裂 random_state=42 ) RF.fit(X_train, y_train) RF.score(X_test, y_test) ``` **** ![image](https://hackmd.io/_uploads/BJTpOIj2lx.png) ![image](https://hackmd.io/_uploads/Bky3uUs3gg.png) ```py= ``` **** # Patience in Proximity A Simple Early Termination Strategy for HNSW Graph Traversal in Approximate k-Nearest Neighbor Search https://docs.google.com/presentation/d/16PR0x36dapTWI7WV9xMDbw4oylv6mlBM0e067kO44tY/edit?usp=sharing 作者提出一個簡單的 saturation-based(飽和)早停策略,稱為 Patience in Proximity,在 HNSW 的圖遍歷/search 過程中動態判定何時停止擴展候選節點,以節省訪問次數和時間(QPS 提升),同時盡量不損失檢索效果。 ![image](https://hackmd.io/_uploads/HyyF8nsige.png) 現有 HNSW 優化方向 1. 圖建構改善 減少記憶體消耗、優化連結結構 2. 搜尋啟發式優化 距離門檻 (distance cutoff):若最佳鄰居距離已足夠小,則提前停止。 固定訪問數 (visit budget per layer):限制每層最多訪問節點數。 缺點:過於激進會 犧牲準確度 或 錯過遠端但相關的鄰居。 本研究定位 提出:將「候選飽和」概念改造,應用於 HNSW 的圖遍歷。 核心特色: 非固定門檻、非固定 budget,而是 依據候選品質改善率動態決定停止點。 減少冗餘計算,同時保持 HNSW 的高準確度。 以下整理「既有的 early-exit(早停)策略」在 HNSW/圖式 ANN 搜尋中的主要做法與技術要點,聚焦就事論事的技術面與取捨分析。 1) 基準:HNSW 的標準停止條件(Baseline) 內部機制:在最底層(layer 0)以最佳優先(best-first)彈出目前候選集中與查詢距離最近的點 c;若 dist(c, q) 已不優於當前結果集(如 top-k 或 efSearch 結果集)中的最差距離,則停止擴展。 含意:這是「必要不充分」的停止條件;後述 early-exit 策略都是在此基礎上「更早」終止以換取效率。 2) 距離門檻式(Distance-threshold Cutoff) 想法 當最佳已知鄰近距離足夠小時,繼續擴展的收益有限,可直接早停。 常見形式 絕對門檻:若 best_dist ≤ τ 則停止(需對資料尺度有先驗或離線估計 τ)。 相對改進率:最近幾次彈出/擴展帶來的 best_dist 改善量低於 ε(如 Δbest / best ≤ ε)持續 M 次,則停止。 邊界比較:若候選佇列最小距離 min_candidate_dist ≥ current_worst_in_topk(較嚴格版本會再加上 margin)。 優缺點 優:簡單、開銷低。 缺:門檻設置錯誤在非均勻分佈時易造成召回掉點;需要資料/任務特定調參。 3) 訪問次數/層級配額(Visit-Budget / Per-Layer Cap) 想法 事先設定彈出(pop)或實際訪問(expand)的上限來控制延遲。 變體 全域訪問上限:總擴展節點數達上限 B 即停。 逐層配額:每一層最多訪問 B_l 個節點後,下切至下一層或直接終止。 時間上限:以牆鐘時間(wall time)或 CPU 配額作為停止條件。 優缺點 優:延遲可預測、易於服務層面的 SLO 控制。 缺:召回受資料難度波動影響大;需要離線選 B/B_l 以平衡效能。 4) 飽和/停滯檢測(Saturation / Patience) 想法 當「結果集幾乎不再改變」時,視為品質飽和並早停。 典型實作(含本文脈絡) 記錄第 h 次訪問後的鄰居集合 N_{h,l}(q) 與前一次 N_{h-1,l}(q) 的重疊率 φ_{h,l}(q) = 100 · |N_{h-1,l} ∩ N_{h,l}| / k。 若連續 Δ 次滿足 φ_{h,l}(q) ≥ γ(如 90–99%),則停止當前層的擴展或整體搜尋。 優缺點 優:自適應於查詢與資料難度;在維持效果下顯著減少訪問次數。 缺:需維護結果集重疊統計;γ, Δ 需以驗證集調參。 5) 候選品質斜率/推進率(Improvement-Slope / Promotion-Rate) 想法 監控「每 N 次訪問帶來的有效提拔(promotion)數」或「top-k 惡化/改善速度」。 指標例子 Promotion rate:在視窗 W 內,進入 top-k 的新節點比例 p = (#promotions)/W;若 p < p_min 持續 M 個視窗則早停。 Frontier quality slope:觀察候選佇列最小距離的移動平均改善率,低於門檻即停。 優缺點 優:能細緻反映「探索前沿」是否仍有收益。 缺:需額外統計/平滑;參數較多。 6) 分支限界/上界裁剪(Branch-and-Bound with Bounds) 想法 利用度量空間的性質(如三角不等式)或外部下界/上界(如量化預估距離)做更強的剪枝與早停。 範式 若對所有待展開候選皆有下界 LB(x, q) ≥ current_worst_in_topk,則可終止(因不可能產生更佳結果)。 與多索引或量化(PQ/OPQ、HNSW-PQ 等)結合時,用粗距離的下界快速判斷無望候選,當剩餘候選皆為無望者即可早停。 優缺點 優:理論上更保守、錯殺少。 缺:需要額外的下界估計機制(量化碼本或索引結構),工程複雜度較高。 7) 層間早停(Inter-Level Early Exit) 想法 在高層就評估是否有必要下行到底層,或在底層限制擴展深度。 做法 若 entry point 與查詢距離已遠低於某臨界,直接以較小 efSearch 進入底層或縮短底層搜尋。 對密集資料集,允許在底層僅做有限次的鄰接展開後停止。 優缺點 優:減少到底層的「長尾」開銷。 缺:對不同資料分佈的魯棒性需驗證。 8) 叢集/區域感知的早退(Cluster-Aware / Region-Aware) 想法 以叢集或區域為單位追蹤改善情況;當某些叢集的質心或代表點對查詢已無改進潛力時,整塊跳過。 適用情境 多段式索引(如 coarse quantizer + HNSW 的混合) 圖與叢集的混成搜尋器(先路由到幾個候選叢集,再在叢集內圖搜尋) 優缺點 優:能「批量」刪除無望區域,速度收益可觀。 缺:需額外叢集結構與路由品質;叢集邊界誤差會影響召回。 9) 服務面導向的策略(Latency-First Heuristics) 想法 以線上延遲為首要目標的混合策略: 動態調整 efSearch/訪問上限,根據目前系統負載或查詢時間進度收斂到一個目標延遲。 針對「簡單查詢」(很快出現高重疊率或高 promotion rate 下降)提早結束;「困難查詢」允許多花些預算。 優缺點 優:在流量高峰時維持整體 SLO。 缺:效果(召回)在壓力情況下會有可預期的退化,需要離線/線上監控對策。 參數調整與實作建議 門檻選擇:先以驗證集畫出「訪問數 ↔ NDCG/Recall ↔ QPS」曲線,確定在可接受的效果損失(如 NDCG@10 ≤ 0.5–1.0pt 掉點)下的參數範圍。 視窗平滑:對「改進率/重疊率」以移動平均或指數平滑,降低單一步驟抖動。 分層搭配:在高層採較嚴格早停(因為本就粗略),底層採較保守早停(以守住召回)。 指標監控:線上追蹤 promotion rate、重疊率 φ、每查詢訪問數分佈與 P95/P99 延遲,必要時動態放寬/收緊。 與「Patience in Proximity」的關係 屬於第 4 類(飽和/停滯檢測) 的具體化:以 top-k 重疊率 φ 與 連續次數 Δ 作為停止條件,γ 設為 90–99% 常見。 可用於只在搜尋時(HNSWP(S))或索引+搜尋(HNSWP(IS));後者可能讓圖更稀疏、進一步省時,但對效果更敏感。 工程成本低、可與其他策略(如全域訪問上限)組合以同時保障延遲與效果下限。 小結 現有 early-exit 策略可概括為:(距離/品質)門檻、訪問/時間預算、飽和/停滯檢測、上界裁剪、層間早停、與叢集感知等路線。實務上常採「一個保守的基準 + 一個自適應的飽和檢測 + 一個延遲上限」的多重保險,以在召回與延遲之間取得可控的權衡。 **** # 9/16 1. 比較 faiss hnswlib nmslib 看看有沒有其他參數能改但都只有efsearch、efconstruction、M | hnswlib | faiss| nmslib | | -------- | -------- | -------- | | 最快的索引構建和最高的recall | 最均衡的整體效能 | 在小規模設定下表現出色 | [A Comparative Study of HNSW Implementations for Scalable Approximate Nearest Neighbor Search](https://www.techrxiv.org/users/922842/articles/1311476-a-comparative-study-of-hnsw-implementations-for-scalable-approximate-nearest-neighbor-search) **** 2. 把用python做的hnsw檢索參數改成query與資料點的距離計算次數 ![image](https://hackmd.io/_uploads/HJwR4cIoex.png) ![image](https://hackmd.io/_uploads/Hkn34q8sxl.png) **** ![image](https://hackmd.io/_uploads/S1oZBqUjeg.png) ![image](https://hackmd.io/_uploads/BkOercUilx.png) **** # PQ https://docs.google.com/presentation/d/1VaXNH7P2xxy25U75JSA_J8_pVq_zrFfMxBniX_OhfgA/edit?usp=sharing * 分段: 把一個高維向量(例如 64 維)分成幾個小段(例如 8 段,每段 8 維)。 **** * 聚類: 對每一小段的數據(8 維小向量)進行 K-means 聚類,生成一組代表性的「中心點」。 假設 K=256 **** * 編碼: 用每個小段的「中心點編號」來表示原始向量,這樣就把高維向量壓縮成一個短向量。 例如: 假設一個原始向量,我們將它平均分成 8 段 每段都會有自己的一組聚類器(k-means),產生 256 個中心(k*=256) 找出該段向量 yᵢ 所屬的中心,得到中心編號 qᵢ(yᵢ) 用 8 bits 表示(因為 2⁸ = 256) 最後得到一組 8 × 8 = 64 bits 的 PQ 編碼(Quantization Index) 例如 [18, 130, 241, 3, 90, 45, 32, 9] ![image](https://hackmd.io/_uploads/ryvibweOel.png) **** ![image](https://hackmd.io/_uploads/S1hIRIluxl.png) * Y軸:平方失真 D(q)(square distortion) , 失真越小,代表壓縮後越接近原始向量 * X軸:編碼長度(code length),即整體 PQ 向量的長度 * 圖中不同線條代表不同的 m 值(分段數): m=1、m=2、m=4、m=8、m=16 * 每條曲線上的點對應於不同的 k* 值(每段聚類中心數目,例如 16, 64, 256, 1024) **** ![image](https://hackmd.io/_uploads/S1hIRIluxl.png) * Code length = m × log2(k) 例如 m=8, k=256 時,code length = 8 × 8 = 64 bits * 失真隨著 code length 增加而下降 越長的編碼越精細,誤差小 * 失真也隨 m 增加而下降 分更多段,更細的局部壓縮,更準確 * 固定碼長時, m 小的曲線比 m 大的曲線誤差小。 例如 64 bits 時,m=8, k=256 比 m=16, k=16 精度高 分太多段會讓每段維度變太小,每段資訊太少,聚類效果下降 **** ## 查詢 (SDC ADC) ![image](https://hackmd.io/_uploads/Bk_FjIedll.png) ### SDC(Symmetric Distance Computation) 指查詢向量和數據庫中的向量都經過 PQ 量化 直接比較查詢向量和數據庫中向量的「短向量」(例如 [18, 130, ...]) * 距離表 計算每段中 256 個聚類中心之間的距離 8 段,每段有 256×256 個距離值,組成一個距離表。 **** 1. 壓縮查詢向量: 將查詢向量(64 維)按相同方式分成 8 段。 每段找到最近的聚類中心,得到一個 8 維短向量,例如 q(x) = [5, 200, 15, 100, 80, 60, 40, 20]。 1. 計算短向量距離: 對於數據庫中的每個短向量(例如 q(y) = [18, 130, 241, 3, 90, 45, 32, 9]),計算 q(x) 和 q(y) 之間的距離。 查詢時,只需根據 q(x) 和 q(y) 的編號,從距離表中查出每段的距離,然後加總。 距離 = 距離表[段1][5][18] + 距離表[段2][200][130] + ... + 距離表[段8][20][9]。 **** * 優點: 簡單、速度快 * 缺點 : 誤差較大 因為查詢向量和數據庫向量都經過量化,丟失了原始向量的細節,導致計算的距離與真實距離偏差較大。 **** ### ADC(Asymmetric Distance Computation) 查詢向量保持原始向量,數據庫中的向量使用 PQ 量化後的短向量 1. 查詢向量分段: 將查詢向量(64 維)按相同方式分成 8 段,每段 8 維,但不進行量化,保留原始數據 2. 生成距離表: 對查詢向量的每段(8 維子向量),計算它與該段 256 個聚類中心的距離 生成一個距離表,包含 8 列 (對應 8 段),每列 256 個距離值 例如: 距離表[1][0] 表示查詢向量第 1 段與第 1 段聚類中心 0 的距離。 距離表[2][100] 表示查詢向量第 2 段與第 2 段聚類中心 100 的距離。 3. 計算距離: 對於數據庫中的短向量(例如 doc_i = [18, 130, 241, 3, 90, 45, 32, 9]),從距離表中查出每段的距離,然後加總: 距離 = 距離表[段1][18] + 距離表[段2][130] + 距離表[段3][241] + ... + 距離表[段8][9]。 **** * 優點: 精度更高 * 缺點 : 計算量稍大、距離表生成成本 因為查詢向量不同,每次查詢都需要生成一個新的距離表,這增加了少量計算負擔 **** ### IVFADC ADC/SDC還是窮舉(掃全部資料)。太慢。 用k-means,把向量分到不同 cell。 查詢時,找到最近的幾個 cell(比如 top-N cell)。 在這些 cell 內,用 ADC 計算距離。 ![image](https://hackmd.io/_uploads/SJd4bcKOge.png) 1. 粗粒度量化(Coarse Quantization) 對數據集做 k-means,減少推理過程的搜索範圍,聚類計算殘差向量 * 對數據集做 k-means 聚類,生成 nlist 個中心 * 每個向量 x 根據歐氏距離分配到最近的中心 c_i 2. 計算殘差 * 計算殘差向量: x - c_i ,表示向量相對於其中心的偏移 * 可以讓所有分區的向量之間更加接近,因為它們分佈集中於原點附近,殘差向量使量化更精確 2. IVF 為每個中心 c_i 創建一個倒排鏈,記錄每個簇的向量資訊,儲存該簇內殘差的 PQ 編碼 3. PQ * 收集所有簇的殘差向量,統一形成殘差數據集。 * 乘積量化不是針對每個分區單獨執行,是同時對所有分區的殘差做乘積量化 4. 查詢 (根據查詢向量 q 找到 top-k 最近鄰) 1. 粗搜索 計算 q 與所有簇中心的距離,選出最近的 nprobe 個簇 3. 精搜索 * 對選中的 nprobe 個簇,計算查詢殘差:q - c_i * 每個分區的q殘差向量都不相同. 需要為每個q的殘差向量單獨計算距離表 * 用ADC, 算 q - c_i 與倒排鏈中殘差向量的 PQ 碼距離 **** * 為甚麼IVFADC 在 PQ 階段對所有簇的殞差向量進行統一處理,而不是分開處理每個簇 ![image](https://hackmd.io/_uploads/ry2lJcYOgg.png) 已處理全局分區,專注於殞差的局部偏移 **** ### SDC ADC IVFADC 比較 ![image](https://hackmd.io/_uploads/B1E2U9Fdee.png) * recall隨著碼長增加而上升。 * 固定碼長時(256),m 小的曲線(如 m=8 )比 m 大的曲線(如 m=16 )recall更高。 **** ![image](https://hackmd.io/_uploads/H14a85Kdgg.png) * recall隨碼長增加上升,類似SDC,但整體值更高。 * 固定碼長時,ADC表現優於SDC **** ![image](https://hackmd.io/_uploads/ryTTI9t_le.png) * recall隨碼長增加上升 * 粗量化參數 k (中心數),分配到最近的w個區域 ,w 不足時,可能漏掉真鄰居 * 固定碼長時, k 和 w 越大,recall越高 **** # 偵測晶體長晶過程中的斷線問題 ## 資料集介紹 ![image](https://hackmd.io/_uploads/HysF_A8wgx.png) * 二層子目錄的資料夾名稱前半段記錄拍攝的時間與給定的序號 * images為一連串連續拍攝影像,以日期時間命名,例如20240602_093811_CCD .jpg * 影像有發生斷線,即會有對應的20240602_093811_CCD.txt ## 資料集統計表 ![image](https://hackmd.io/_uploads/S1oFKAIwex.png) ## 晶向斷線範例 ![image](https://hackmd.io/_uploads/SkvCYALPel.png) ![image](https://hackmd.io/_uploads/rkMkcCIvle.png) ![image](https://hackmd.io/_uploads/Bya1qA8Pll.png) ## 座標框的標記格式 **class x_center y_center width height** * class: 只有斷線,class都是設成0 * x_center: x_center為方框中心點x座標/圖片寬度 * y_center: y_center 為方框中心點y座標/圖片高度 * width: width為方框寬度/圖片寬度 * height: height為方框高度/圖片高度 剛好是YOLO的標註格式 ## 模型評估方式 * 當 IoU ≥ 0.3 時視為命中。以此計算 F1-score作為主要判斷依據。 * 模型能夠在斷線首次出現的圖片或其鄰近幀中正確偵測,即視為及時命中。反之若偵測時間落後越多,分數將相應降低。 * ![image](https://hackmd.io/_uploads/SkyEhAUwlg.png) ## 訓練模型 ### 模型選擇YOLOV8 * 因為題目給的label格式剛好是yolo的標註格式可以直接用不需額外轉換 * 推論速度極快 * YOLOv8 有 ultralytics 套件 ,不用下載其他東西 * 每張影像可能存在多個斷線區域,YOLO 能同時預測多個邊框 ### 前處理 1. 建立資料夾結構 * 晶向分類: classification/ ├── train/ │ ├── 100/ │ ├── 111/ │ └── 776/ ├── val/ └── test/ * 斷線偵測: detection/ ├── 100/ │ ├── train/images + labels/ │ ├── val/images + labels/ │ └── test/images + labels/ ├── 111/ └── 776/ 2. 資料分割 * 訓練集0.8、驗證集0.1、測試集0.1 3. 圖片前處理 * 縮放到640x640 * **提升對比度1.2 倍** ### 訓練模型 1. 訓練分類模型 2. 訓練偵測模型 ## 結果 1. 使用 classification_model 分類晶向 Accuracy: 0.7067 2. 使用 detection_models 偵測斷裂 - 100 晶向:210 張圖像,318 個實例 - Precision: 0.8484(84.84%) - Recall: 0.6509(65.09%) - 111 晶向:212 張圖像,326 個實例 - Precision: 0.7708(77.08%) - Recall: 0.5675(56.75%) - 776 晶向:134 張圖像,195 個實例 - Precision: 0.7941(79.41%) - Recall: 0.2769(27.69%) 3. 使用 classification_model + detection_models - TP(有標註、有預測): 593 - FP(無標註、有預測): 23 - FN(有標註、無預測): 123 - TN(無標註、無預測): 0 ===== 綜合評估結果 ===== - Precision: 0.963 - Recall: 0.828 - F1 Score: 0.890 # NSW ## Small world * 同質性:點與點之間基於相似性連接。 * 弱連接:點與點之間通過隨機長距離邊實現快速導航。 * 導航性:是否能從任意節點,用局部資訊,有效率地「導航」到目標節點的能力。 ![image](https://hackmd.io/_uploads/r1F3BcVDxe.png) ## 在NSW中K近鄰檢索的過程 ![image](https://hackmd.io/_uploads/BkwLL9Nvee.png) 通過黑色鄰邊來找最近鄰節點, 通過紅色長邊來實現不同類節點之間的快速檢索。 q:查詢點。 m:隨機重啟的次數(重覆搜索的輪數)。 k:要返回的最近鄰數量。 輸出: result:包含與 q 最接近的 k 個節點的集合。 數據結構: tempRes:臨時存儲每次搜索的候選結果(TreeSet,按距離排序)。 candidates:存儲當前待檢查的候選節點(priority queue,按距離排序)。 visitedSet:記錄已訪問的節點,避免重覆訪問。 result:存儲最終的 k 個最近鄰結果。 ``` K-NNSearch(object q,integer:m,k) 1 TreeSet[object]tempRes, candidates, visitedSet, result # NSW 的單次貪婪搜索可能陷入局部最優, # 因此做 m 次(每次從不同的隨機入口點開始),增加找到全局最近鄰的機會,提高準確性 2 for(i<-0; i<m; i++) do: # 1. # 隨機選擇1個元素,放入到candidates當中 3 put random entry point in candidates # 臨時存儲設為空 4 tempRes<-null 5 repeat: # 2. # candidates選與查詢點最近的c,把c移出candidates 6 get element c closest from candidates to q 7 remove c from candidates # 3. # 如果目前 candidate 的「最近元素」都已經比 result 裡第最後一名還遠,就視為可以終止擴散了 # 效率與精度之間的 trade-off, # 有一定機率會錯過最佳解,做 m 次可能能夠彌補單次錯過的情況 9 if c is further than k-th element from result 10 than break repeat # 4. 11 # 找出與c連結的所有點,如果不再visited Set 把她加入visited Set, candidates, tempRes 12 for every element e from friends of c do: 13 if e is not in visited Set than 14 add e to visited Set, candidates, tempRes # candidates 變空(無更多節點可檢查)。 # 滿足停止條件(步驟 9:c 的距離大於 result 中第 k 個節點的距離)。 16 end repeat 17 # 把臨時結果存入 result 18 add objects from tempRes to result 19 end for 20 return best k elements from result ``` * 隨機選擇一個點作為查詢起始點entry_point,把該點加入candidates中,同時加入visitedset * 從candidates中刪除點c。 * 從candidates中選擇距離查詢點最近的點c,和results中距離查詢點最遠的點d進行比較,如果c和查詢點q的距離大於d和查詢點q的距離,則結束查詢 * 查詢c的所有鄰居e,如果e已經在visitedset中存在則跳過,不存在則加入visited Set, candidates, tempRes ## Delaunay 三角剖分 德勞內 * 唯一性:無論從點集的任何位置開始建網,Delaunay三角剖分是唯一的 **目標是讓相鄰的點在空間距離上盡可能“近”,並且生成的三角形盡可能“規整”**(避免過於尖銳或扁平的三角形)。這種方法在 NSW 和 HNSW 算法中常用於構建圖的鄰居連接,因為它**能確保節點之間的邊(連接)具有良好的空間接近性**。 * 空圓特性 Delaunay三角剖分是唯一的(任意四點不能共圓),在Delaunay三角剖分中任一三角形的外接圓內不會存在其它點。即滿足Delaunay邊的定義。 * 最大化最小角特性 在所有可能的三角剖分中,Delaunay三角剖分所形成的三角形最小的角度會是最大的。 ![image](https://hackmd.io/_uploads/Hy1LycVvgx.png) ## NSW 網絡構建 * 理想情況: 用 Delaunay 三角剖分把它們連成三角形,然後再加一些“跳躍邊”(隨機長邊),讓圖可以快速從一個點跳到遠處的點。這就形成了一個小世界網絡,適合快速搜索。 * 實際挑戰: Delaunay 三角剖分在高維空間計算量太大,不適合實時構建。因此,NSW 采用了一種近似方法:**隨機插入節點,利用插入的隨機性引入全局跳躍邊並通過最近鄰搜索模擬 Delaunay 邊的局部連通性**。 **透過節點隨機插入來引入隨機性** 建立 NSW 圖時,是一個一個地把資料點加進圖裡(順序是隨機的), 而這些點的加入不是照距離、也不是照ID或排序。 這樣就有機會產生一些非預期、遠距離的連結(第一個加入的節點可能會在圖的一角,結果第 100 個加入的點卻在很遠的地方,因為圖還很稀疏,這兩個就有可能直接連起來)。**幫助搜尋時快速跳躍遠區域**。 **利用已有節點模擬建構 Delaunay 邊來引入同質性** 每插入一個新點,NSW 會找出目前圖中「跟它最接近的幾個點」來連線。彼此距離近、特徵相似。**保證局部的連接精準性**。 * 把一個新的節點(new_object)插入到圖中 ``` # new_object:你要插進圖的那個新節點 # f:想要連接的「最近的 f 個鄰居」數量(連幾條邊) Nearest_Neighbor_Insert(object: new_object, integer: f, integer: w) # 幫這個新節點 new_object 找出圖中距離它最近的 f 個點。 1 SET[object]: neighbors <- k-NNSearch(new_object, w, f); # 把新節點和剛剛找到的 f 個近鄰節點建立「雙向連線」 2 for(i <- 0; i < f; i++) do 3 neighbors[i].connect(new_object); 4 new_object.connect(neighbors[i]); ``` # HNSW ## HNSW 中的 Beam Search 搜索過程:HNSW 使用一種變體的 Beam Search,具體取決於層級: 在高層(除了基層外),Beam Search 的寬度(beam width)為 1。這意味著在每個層級,算法只選擇與查詢向量距離最近的單個節點作為下一個層級的起點。 在基層(Base layer),Beam Search 的寬度為 p (在 Faiss 中對應參數 $ efSearch ),表示在候選優先級隊列中,每次從隊列頂部彈出一個節點,評估其相鄰節點,並將距離查詢向量最近的 p 個節點的相鄰節點加入隊列進行進一步搜索。 工作原理:搜索從頂層的單個節點開始,通過 1 跳 Beam Search 收斂到基層的一個相對接近真實最近鄰的起點。隨後在基層使用寬度為 $ p $ 的 Beam Search,逐步擴展搜索範圍,直到滿足終止條件(例如找到頂部 p 個最佳候選節點)。 ## 基本特性 ![image](https://hackmd.io/_uploads/BkttucEPgx.png) 1. 在 Layer = 0 層中,包含了連通圖中所有的點。 2. 隨著層數的增加,每一層的點數逐漸減少並且遵循指數衰減。 3. 從某個點所在的最高層往下的所有層中均存在該節點。 4. 在對 HNSW 進行查詢的時候,從最高層開始檢索。 * INSERT(): 將新資料點 q 插入 HNSW 圖中 * SEARCH-LAYER(): 在指定層 lc 中,從入口點 ep 開始,找出最多 ef 個離查詢點 q 最近的節點 * SELECT-NEIGHBORS-SIMPLE(): 從候選集合中挑選距離查詢點 q 最近的 M 個節點。 * SELECT-NEIGHBORS-HEURISTIC(): 從候選集合中挑選最多 M 個對後續搜尋最有效率的鄰居點 * K-NN-SEARCH(): 從圖中找出與查詢點 q 最接近的 K 個鄰居 ## Algorithm 1: INSERT() 把新的資料點 q 插入到 HNSW 多層圖中,並建立與其他點的連結,使得整個圖仍然保持「小世界圖」的特性 ``` # M: 節點可以與最多幾個節點相連 INSERT(hnsw, q, M, Mmax, efConstruction, mL) 1 W ← ∅ # 存放目前找到的最近的點 2 ep ← get enter point for hnsw # 進入點 3 L ← level of ep # 進入點的層數 # 1. # 為新的節點 q 隨機產生一個層數 l,決定它會出現在第幾層。 4 l ← ⌊-ln(unif(0..1))∙mL⌋ # 2. # 從最高層 L 一直往下走到 l+1 層 # 每一層選出「離 q 最近的那個」,當作下一層搜尋的起點 5 for lc ← L … l+1 6 W ← SEARCH-LAYER(q, ep, ef=1, lc) 7 ep ← get the nearest element from W to q 8 for lc ← min(L, l) … 0 # 3. # 從 ep(入口點)開始找出最多 ef 個與 q 距離最近的節點當候選人 9 W ← SEARCH-LAYER(q, ep, efConstruction, lc) # 4.  # 以W為候選人選出最多 M 個「對目標點 q 最有幫助」的鄰居點 # 能選alg. 3 or alg. 4 10 neighbors ← SELECT-NEIGHBORS(q, W, M, lc) # 5. # 建立q和neighbors的雙向連接 11 add bidirectionall connectionts from neighbors to q at layer lc # 6. # 確保每個鄰居的連線數不超過 Mmax 12 for each e ∈ neighbors 13 eConn ← neighbourhood(e) at layer lc 14 if │eConn│ > Mmax 15 eNewConn ← SELECT-NEIGHBORS(e, eConn, Mmax, lc) 16 set neighbourhood(e) at layer lc to eNewConn # 入口點從單一節點擴展成多個節點 17 ep ← W # 如果新節點 q 的層數比原本的最高層 L 還高,把整個圖的進入點更新為 q。 # 下次要進圖查詢,就從 q 開始走 18 if l > L 19 set enter point for hnsw to q ``` ## Algorithm 2: SEARCH-LAYER() 在 HNSW 某一層進行「近似鄰居搜尋」 查詢元素 q,從 ep(入口點)開始,在第 lc 層中,找出最多 ef 個與 q 距離最近的節點。 ``` SEARCH-LAYER(q, ep, ef, lc) 1 v ← ep # 已拜訪過的節點,初始只有入口點 ep 2 C ← ep # 候選節點集合(等著被處理的),初始只有入口點。 3 W ← ep # 動態儲存找到的最近鄰居,初始只有入口點。 # 只要候選集合 C 不為空,就持續搜尋。每次挑出目前看起來最接近q的點來探索它的鄰居。 4 while │C│ > 0 # 1. # 從候選集合 C 中取出並移除距離查詢點 q 最近的元素 c,初始只有入口點 5 c ← extract nearest element from C to q # 2. # f 是目前「已經找到的最近鄰」中最遠離 q 的節點。 6 f ← get furthest element from W to q # 3. # 效率關鍵 # C 是按照距離 q 從近到遠排序的,所以接下來從 C 拿出來的點,一定只會比 c 更遠 # 如果 c 比 f 還遠,就能停止了 # 雖然有可能「c 的鄰居更近」,但可能信不高為了效率犧牲他 7 if distance(c, q) > distance(f, q) 8 break # 4. # 探索節點 c 在第 lc 層的所有鄰居 e。 9 for each e ∈ neighbourhood(c) at layer lc # e沒被拜訪過 10 if e ∉ v 11 v ← v ⋃ e # 取得目前最近鄰集合 W 中最遠的節點 f。 12 f ← get furthest element from W to q # 5. # 若 鄰居 比 f 更近,或 W 中元素還沒到要找的目標個數,把鄰居加進W 13 if distance(e, q) < distance(f, q) or │W│ < ef 14 C ← C ⋃ e 15 W ← W ⋃ e # W 的大小不超過 ef 16 if │W│ > ef 17 remove furthest element from W to q 18 return W ``` ## Algorithm 3: SELECT-NEIGHBORS-SIMPLE() **從候選節點中**挑選 M 個最近鄰 ``` # q: 查詢點 # C: 候選集合 # M: 要選出幾個鄰居 SELECT-NEIGHBORS-SIMPLE(q, C, M) Input: base element q, candidate elements C, number of neighbors to return M Output: M nearest elements to q return M nearest elements from C to q ``` ## Algorithm 4: SELECT-NEIGHBORS-HEURISTIC() 在第 lc 層從**候選集合 C 裡**,選出最多 M 個「對目標點 q 最有幫助」的鄰居點(不只是最近的) ``` SELECT-NEIGHBORS-HEURISTIC(q, C, M, lc, extendCandidates, keep- PrunedConnections) 1 R ← ∅ # 用來存放最後選中的鄰居 2 W ← C # working queue for the candidates # 1. # 如果 extendCandidates 設成 true # 擴大候選人選,把候選節點的鄰居加入候選集 3 if extendCandidates 4 for each e ∈ C 5 for each eadj ∈ neighbourhood(e) at layer lc 6 if eadj ∉ W 7 W ← W ⋃ eadj 8 Wd ← ∅ # 用來放被丟棄的 # 當還有候選人且選進來的鄰居還沒滿 M 個 9 while │W│ > 0 and │R│< M # 2. # 從 W 裡拿出一個最接近 q 的節點 e 10 e ← extract nearest element from W to q # 3. # 不要 e 太接近某個已經被選進來的鄰居 # 如果e 和 q 的距離小於 e 和 R 中任何一個節點的距離 # 就要把它加入 R。 # 有助於找到「分布均勻的鄰居集合」,讓未來查詢能更有效率地跳躍整個圖 11 if e is closer to q compared to any element from R 12 R ← R ⋃ e # 先放進丟棄隊列 Wd,之後也許會再考慮。 13 else 14 Wd ← Wd ⋃ e # 4. # 設 keepPrunedConnections = true # 從 Wd 裡按照距離 q 的遠近排序補進 R,直到補滿 M 個為止。 15 if keepPrunedConnections 16 while │Wd│> 0 and │R│< M 17 R ← R ⋃ extract nearest element from Wd to q 18 return R ``` 1. 如果extendCandidates被設置把候選節點的鄰居加入候選集 2. W的大小大於0,R的大小小於M: 3. 從W中提取離q最近的元素e。 4. 如果e 和 q 的距離小於 e 和 R 中任何一個節點的距離,就要把它加入 R 5. 否則,將e加到Wd。 6. 如果設置了keepPrunedConnections(即true): 7. 而Wd的大小大於0,R的大小小於M: 8. 從Wd中提取到q最近的元素e,加到R中。 9. 返回R。 ## Algorithm 5: K-NN-SEARCH() 找q 的 K 個最近鄰 ![image](https://hackmd.io/_uploads/S1jTk5t_ge.png) ``` # K:真正想要的最近鄰個數 # ef:搜尋時保留的候選節點數量 # 通常設得比 K 大很多如:ef = 50,K = 10 # ef 越大,準確率越高,但速度越慢 K-NN-SEARCH(hnsw, q, K, ef) 1 W ← ∅ # 目前找到的最近鄰集合 2 ep ← get enter point for hnsw # 選擇一個進入點 3 L ← level of ep # 取得 ep 所在的最上層 # 1. # 從頂層一直往底層走。找到一個比較好的初始點 # 不好的起點可能會導致找不到真正的最近鄰、搜尋時間變長 4 for lc ← L … 1 # 從 ep(入口點)開始,在第 lc 層中,找出最多 ef=1 個與 q 距離最近的節點。 5 W ← SEARCH-LAYER(q, ep, ef=1, lc) # 取得目前找到的最接近查詢點 q 的節點,這個作為下一層搜尋的起點。 6 ep ← get nearest element from W to q # 2. # 用找到的比較好的初始點ep,進入底層開始認真做 ef 次數的鄰居搜尋 7 W ← SEARCH-LAYER(q, ep, ef, lc =0) 8 return K nearest elements from W to q ``` 1. 對於每一層lc,從L到1 2. 取得目前找到的最接近查詢點 q 的節點,這個作為下一層搜尋的起點。 3. 用找到的比較好的初始點ep,進入底層開始認真做 ef 次數的鄰居搜尋 4. 返回W中最接近的K個元素作為輸出。