---
# System prepended metadata

title: 筆記

---

# 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個元素作為輸出。