在 Lucene 中,可以通過 `KnnFloatVectorQuery` 和 `KnnFloatVectorField` 來實現 KNN(k-Nearest Neighbors)搜索。以下是具體介紹:
1. 功能原理
`KnnFloatVectorQuery` 是 Lucene 用于執行最近鄰搜索的查詢類,它可以在一個字段中搜索與目標向量最相似的 k 個向量。其核心是基于 HNSW(Hierarchical Navigable Small World)算法,構建圖索引以實現高效的近似最近鄰(Approximate Nearest Neighbor,ANN)搜索。
2. 代碼示例
2.1 索引向量字段
```java
import org.apache.lucene.document.Document;
import org.apache.lucene.document.KnnFloatVectorField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.ByteBuffersDirectory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class LuceneKNNExample {
? ? public static float[] generateFVector(int dim) {
? ? ? ? float[] vector = new float[dim];
? ? ? ? Random random = new Random();
? ? ? ? for (int i = 0; i < dim; i++) {
? ? ? ? ? ? vector[i] = random.nextFloat();
? ? ? ? }
? ? ? ? return vector;
? ? }
? ? public static void main(String[] args) throws IOException {
? ? ? ? Directory directory = new ByteBuffersDirectory();
? ? ? ? IndexWriterConfig config = new IndexWriterConfig(null);
? ? ? ? IndexWriter indexWriter = new IndexWriter(directory, config);
? ? ? ? int count = 10000;
? ? ? ? int dim = 128;
? ? ? ? List<Document> docs = new ArrayList<>();
? ? ? ? for (int i = 0; i < count; i++) {
? ? ? ? ? ? Document doc = new Document();
? ? ? ? ? ? doc.add(new KnnFloatVectorField("fvecs", generateFVector(dim)));
? ? ? ? ? ? docs.add(doc);
? ? ? ? }
? ? ? ? indexWriter.addDocuments(docs);
? ? ? ? indexWriter.commit();
? ? ? ? System.out.println("索引寫入成功");
? ? }
}
```
2.2 執行 KNN 查詢
```java
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.BytesRef;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Random;
public class KNNQueryExample {
? ? public static float[] generateFVector(int dim) {
? ? ? ? float[] vector = new float[dim];
? ? ? ? Random random = new Random();
? ? ? ? for (int i = 0; i < dim; i++) {
? ? ? ? ? ? vector[i] = random.nextFloat();
? ? ? ? }
? ? ? ? return vector;
? ? }
? ? public static void main(String[] args) throws IOException {
? ? ? ? Directory readDirectory = new ByteBuffersDirectory();
? ? ? ? IndexReader indexReader = DirectoryReader.open(readDirectory);
? ? ? ? IndexSearcher indexSearcher = new IndexSearcher(indexReader);
? ? ? ? float[] queryVector = generateFVector(128);
? ? ? ? int k = 3;
? ? ? ? TopDocs topDocs = indexSearcher.search(new KnnFloatVectorQuery("fvecs", queryVector, k), k);
? ? ? ? for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
? ? ? ? ? ? System.out.println("doc: " + scoreDoc.doc + ", score: " + scoreDoc.score);
? ? ? ? }
? ? }
}
```
3. 查詢原理
- `KnnFloatVectorQuery` 的 rewrite 過程:在 rewrite 之后,`KnnFloatVectorQuery` 會變成 `DocAndScoreQuery`,它內部已經存儲了符合條件的 `docId` 和 `score`。
- HNSW 算法:HNSW 算法將新節點鏈接到 M 個最近鄰,通過反向鏈接和修剪來保留多樣性。M 值越大,精度越高,成本也越高。Beam-width 控制搜索范圍。