Milvus 簡介
1. 概述
在本教程中,我們將探索Milvus ,一個高度可擴展的開源向量資料庫。它旨在儲存和索引來自深度神經網路和其他機器學習模型的大量向量嵌入。 Milvus 支援跨文字、圖像、語音和視訊等多種資料類型進行高效的相似性搜尋。我們將廣泛探索Milvus Java 用戶端 SDK ,用於透過第三方應用程式整合和管理 Milvus DB 。為了進行解釋,我們將以一個應用程式為例,該應用程式儲存書籍的向量化內容以啟用相似性搜尋。
2. 關鍵概念
在探索 Milvus Java SDK 的功能之前,我們先來了解 Milvus 是如何邏輯組織資料的:
-
Collection
:儲存向量的邏輯容器,類似傳統資料庫中的表 -
Field
:集合中標量和向量實體的屬性,定義資料型別和其他屬性 -
Schema
:定義集合中資料的結構和屬性 -
Index
:透過組織向量以實現高效檢索來優化搜尋過程 -
Partition
:集合內的邏輯細分,用於更有效地管理和組織數據
3.前提條件
在探索 Java API 之前,我們將先了解執行範例程式碼的一些先決條件。
3.1. Milvus 資料庫實例
首先,我們需要一個 Milvus DB 實例。最簡單快速的方法是取得Zilliz Cloud提供的完全託管的免費 Milvus DB 執行個體:
為此,我們需要註冊Zilliz 雲端帳戶並按照建立免費資料庫叢集的文件進行操作。
3.2. Maven依賴
在開始探索 Milvus Java API 之前,我們需要匯入必要的Maven 相依性:
<dependency>
<groupId>io.milvus</groupId>
<artifactId>milvus-sdk-java</artifactId>
<version>2.3.6</version>
</dependency>
4. Milvus Java 客戶端 SDK
Milvus DB 服務端點是使用 gRPC 框架編寫的。因此,他們使用不同程式語言(例如 Python、Go 和 Java)的所有客戶端 SDK 都在此 gRPC 框架之上提供 API。 Milvus Java 用戶端 SDK 為任何資料庫一樣的 CRUD(建立、讀取、更新和刪除)操作提供全面支援。此外,它還支援建立集合、索引和分割等管理操作。為了執行各種資料庫操作,API 提供了對應的請求和請求建構器類別。開發人員可以使用建構器類別在請求物件中設定必要的參數。最後,這個請求物件在客戶端類別的幫助下被傳送到後端服務。一旦我們完成接下來的部分,這一點就會變得更加清楚。
5. 建立 Milvus 資料庫用戶端
Java 用戶端 SDK 提供了MilvusClientV2
類,用於 Milvus DB 中的管理和資料操作。 ConnectConfigBuilder
類別可協助建構父ConnectConfig
類,該類別保存創建MilvusClientV2
類實例所需的連接資訊:
讓我們來看看建立
MilvusClientV2
實例的方法,以了解有關所涉及的類別的更多資訊:
MilvusClientV2 createConnection() {
ConnectConfig connectConfig = ConnectConfig.builder()
.uri(CONNECTION_URI)
.token(API_KEY)
.build();
milvusClientV2 = new MilvusClientV2(connectConfig);
return milvusClientV2;
}
ConnectConfig
類別支援使用者名稱和密碼身份驗證,但我們使用了建議的 API 令牌。 ConnectConfigBuilder
類別採用 URI 和 API 令牌來建立ConnectConfig
物件。稍後用於建立MilvusClientV2
物件。
6. 建立集合
在將資料儲存到 Milvus Vector DB 之前,我們必須建立一個集合。它涉及創建字段模式和集合,然後形成創建集合請求物件。最後,用戶端將請求物件傳送到資料庫服務端點以在 Milvus DB 中建立集合。
6.1.建立欄位模式和集合模式
讓我們先來探索 Milvus Java SDK 中的相關關鍵類別:
FieldSchema
類別幫助定義集合的字段,而CollectionSchema
使用FieldSchema
來定義集合。此外, IndexParam
中的IndexParamBuilder
類別在集合上建立索引。我們將透過範例程式碼探索其他類別。首先,讓我們來看看在createFieldSchema()
方法中建立FieldSchema
物件的步驟:
CreateCollectionReq.FieldSchema createFieldSchema(String name, String desc, DataType dataType,
boolean isPrimary, Integer dimension) {
CreateCollectionReq.FieldSchema fieldSchema = CreateCollectionReq.FieldSchema.builder()
.name(name)
.description(desc)
.autoID(false)
.isPrimaryKey(isPrimary)
.dataType(dataType)
.build();
if (null != dimension) {
fieldSchema.setDimension(dimension);
}
return fieldSchema;
}
FieldSchema
類別中的builder()
方法傳回其子FieldSchemaBuilder
類別的實例。此類設定集合欄位的重要屬性,例如名稱、描述和資料類型。建構器類別中的方法isPrimaryKey()
有助於標記主鍵字段,而FieldSchema
類別中的setDimension()
方法設定向量字段的強制維度。例如,讓我們在方法createFieldSchemas()
中設定名為Books
的集合的欄位:
private static List<CreateCollectionReq.FieldSchema> createFieldSchemas() {
List<CreateCollectionReq.FieldSchema> fieldSchemas = List.of(
createFieldSchema("book_id", "Primary key", DataType.Int64, true, null),
createFieldSchema("book_name", "Book Name", DataType.VarChar, false, null),
createFieldSchema("book_vector", "vector field", DataType.FloatVector, false, 5)
);
return fieldSchemas;
}
此方法傳回Books
集合的欄位book_id
、 book_name
和book_vector
的FieldSchema
物件清單。 book_vector
欄位儲存維度為5,
book_id
為主鍵。準確地說,我們將書籍的向量化文字儲存在 book_vector 欄位中。每個FieldSchema
物件都是使用先前定義的createFieldSchema()
方法建立的。建立FieldSchema
物件後,我們將在createCollectionSchema()
方法中使用它們來形成Books
CollectionSchema
物件:
private static CreateCollectionReq.CollectionSchema createCollectionSchema(
List<CreateCollectionReq.FieldSchema> fieldSchemas) {
return CreateCollectionReq.CollectionSchema.builder()
.fieldSchemaList(fieldSchemas)
.build();
}
子CollectionSchemaBuilder
設定欄位架構並最終建構父CollectionSchema
物件。
6.2.建立收藏要求和收藏
現在讓我們看看建立集合的步驟:
void whenCommandCreateCollectionInVectorDB_thenSuccess() {
CreateCollectionReq createCollectionReq = CreateCollectionReq.builder()
.collectionName("Books")
.indexParams(List.of(createIndexParam("book_vector", "book_vector_indx")))
.description("Collection for storing the details of books")
.collectionSchema(createCollectionSchema(createFieldSchemas()))
.build();
milvusClientV2.createCollection(createCollectionReq);
assertTrue(milvusClientV2.hasCollection(HasCollectionReq.builder()
.collectionName("Books")
.build()));
}
}
我們使用CreateCollectionReqBuilder
類別透過設定CollectionSchema
物件和其他參數來建構CreateCollectionReq
物件。然後,我們將此物件傳遞給MilvusClientV2
類別的createCollection()
方法來建立集合。最後,我們透過呼叫MilvusClientV2
的hasCollection(HasCollectionReq)
方法進行驗證。 CreateCollectionReqBuilder
類別也使用indexParams()
方法來定義book_vector
欄位的索引。那麼,讓我們來看看有助於建立IndexParam
物件的createIndexParam()
方法:
IndexParam createIndexParam(String fieldName, String indexName) {
return IndexParam.builder()
.fieldName(fieldName)
.indexName(indexName)
.metricType(IndexParam.MetricType.COSINE)
.indexType(IndexParam.IndexType.AUTOINDEX)
.build();
}
此方法使用IndexParamBuilder
類別來設定 Milvus DB 中索引支援的各種屬性。此外, IndexPram
物件的 COSINE度量類型屬性對於計算向量之間的相似度分數很重要。與關係型資料庫一樣,索引有助於提高 Milvus Vector DB 中頻繁存取欄位的查詢效能。讓我們在 Zilliz 雲端控制台中驗證建立的Books
集合的詳細資訊:
7. 建立分區
建立Books
集合後,我們可以專注於建立分區的類,以有效地組織資料。
子
CreatePartitionReqBuilder
類別透過設定分區和目標集合名稱來幫助建立父CreateParitionReq
物件。然後,請求物件被傳遞到MilvusClientV2
的createPartition()
方法中。讓我們使用之前定義的類別在Books
集合中建立一個分區Health
:
void whenCommandCreatePartitionInCollection_thenSuccess() {
CreatePartitionReq createPartitionReq = CreatePartitionReq.builder()
.collectionName("Books")
.partitionName("Health")
.build();
milvusClientV2.createPartition(createPartitionReq);
assertTrue(milvusClientV2.hasPartition(HasPartitionReq.builder()
.collectionName("Books")
.partitionName("Health")
.build()));
}
在這個方法中, createPartitionReqBuilder
類別會為集合Books
建立CreatePartitionReq
物件。隨後, MilvusClientV2
物件使用請求物件呼叫其createPartition()
方法。這導致在Books
集合中建立 Health 分區。最後,我們透過呼叫MilvusClientV2
類別的hasPartition()
方法來驗證分區是否存在。
8. 將資料插入集合
在 Milvus DB 中建立集合Books
後,我們就可以向其中插入資料了。像往常一樣,首先讓我們來看看關鍵類別:
子
InsertReqBuilder
類別透過設定collectionName
和資料來幫助建立其父InsertReq
物件data.
InsertReqBuilder
類別的方法data()
將List<JSONObject>
中的文件區塊插入 Milvus DB 中。最後,我們將InsertReq
物件傳遞給MilvusClientV2
物件的insert()
方法以在集合中建立條目。為了將資料插入Books
集合的Health
分區中,我們將使用 JSON 檔案book_vectors.json
中的一些虛擬資料:
[
{
"book_id": 1,
"book_vector": [
0.6428583619771759,
0.18717933359890893,
0.045491267667689295,
0.8578131397291819,
0.6431108625406422
],
"book_name": "Yoga"
},
More objects...
...
]
現實世界的應用程式使用 BERT 和 Word2Vec 等嵌入模型來創建文字、圖像、語音樣本等的向量維度。讓我們看看前面定義的類別的實際效果:
void whenCommandInsertDataIntoVectorDB_thenSuccess() throws IOException {
List<JSONObject> bookJsons = readJsonObjectsFromFile("book_vectors.json");
InsertReq insertReq = InsertReq.builder()
.collectionName("Books")
.partitionName("Health")
.data(bookJsons)
.build();
InsertResp insertResp = milvusClientV2.insert(insertReq);
assertEquals(bookJsons.size(), insertResp.getInsertCnt());
}
readJsonObjectsFromFile()
方法從 JSON 檔案讀取資料並將其儲存在bookJsons
物件中。如前所述,我們使用資料建立了InsertReq
對象,然後將其傳遞給MilvusClientV2
對象的insert()
方法。最後, InsertResp
物件中的getInsertCnt()
方法給出插入的記錄總數。我們也可以在 Zilliz 雲端控制台中驗證插入的記錄:
9. 執行向量相似性搜索
Milvus 在一些關鍵類別的幫助下支援對集合進行向量相似性搜尋:
SearchRequestBuilder
設定父SearchReq
類別的 topK 最近鄰居、查詢嵌入和集合名稱等屬性。此外,我們可以在filter()
方法中設定標量表達式來取得符合的實體。最後, MilvusClientV2
類別使用SearchReq
物件呼叫search
()
方法來取得記錄。像往常一樣,讓我們看一下範例程式碼以了解更多資訊:
void givenSearchVector_whenCommandSearchDataFromCollection_thenSuccess() {
List<Float> queryEmbedding = getQueryEmbedding("What are the benefits of Yoga?");
SearchReq searchReq = SearchReq.builder()
.collectionName("Books")
.data(Collections.singletonList(queryEmbedding))
.outputFields(List.of("book_id", "book_name"))
.topK(2)
.build();
SearchResp searchResp = milvusClientV2.search(searchReq);
List<List<SearchResp.SearchResult>> searchResults = searchResp.getSearchResults();
searchResults.forEach(e -> e.forEach(el -> logger.info("book_id: {}, book_name: {}, distance: {}",
el.getEntity().get("book_id"), el.getEntity().get("book_name"), el.getDistance()))
);
}
首先,方法getQueryEmbedding()
將查詢轉換為向量維度或嵌入。然後SearchReqBuilder
物件建立帶有所有與搜尋相關的參數的SearchReq
物件。有趣的是,我們也可以透過在建構器類別的outputFields()
方法中設定來控制結果實體中的欄位名稱。最後,我們呼叫MilvusClientV2
的search()
方法來取得查詢結果:
book_id: 6, book_name: Yoga, distance: 0.8046049
book_id: 3, book_name: Tai Chi, distance: 0.5370003
搜尋結果中的距離屬性表示相似度分數。對於我們的範例,我們考慮使用餘弦相似度 (COSINE) 來衡量相似度分數。 因此,餘弦越大,兩個向量之間的角度越小,表示這兩個向量彼此更相似。此外,Milvus 在浮動類型嵌入上支援更多度量類型,例如歐幾里德距離(L2)和內積(IP)。
10.刪除集合中的數據
讓我們從通常的類別圖開始,了解從集合中刪除資料所涉及的關鍵類別:
MilvusClientV2
中的delete()
方法刪除 Milvus DB 中的記錄。它採用允許使用ids
和filter
欄位指定記錄的DeleteReq
物件。子DeleteRequestBuilder
類別幫助建構父DeleteReq
物件。讓我們藉助一些範例程式碼進行深入研究。讓我們來看看從集合Books
中刪除book_id
等於 1 和 2 的記錄的步驟:
void givenListOfIds_whenCommandDeleteDataFromCollection_thenSuccess() {
DeleteReq deleteReq = DeleteReq.builder()
.collectionName("Books")
.ids(List.of(1, 2))
.build();
DeleteResp deleteResp = milvusClientV2.delete(deleteReq);
assertEquals(2, deleteResp.getDeleteCnt());
}
在MilvusClientV2
物件上呼叫delete()
方法後,我們使用DeleteResp
物件中的getDeleteCnt()
方法來驗證刪除的記錄數。我們也可以使用標量表達式規則和DeleteReqBuilder
物件上的filter()
方法來指定要刪除的符合記錄:
void givenFilterCondition_whenCommandDeleteDataFromCollection_thenSuccess() {
DeleteReq deleteReq = DeleteReq.builder()
.collectionName("Books")
.filter("book_id > 4")
.build();
DeleteResp deleteResp = milvusClientV2.delete(deleteReq);
assertTrue(deleteResp.getDeleteCnt() >= 1 );
}
根據filter()
方法中定義的標量條件, book_id
大於4的記錄將從集合中刪除。
11. 結論
在本文中,我們探討了 Milvus Java SDK。它幾乎涵蓋了與管理向量DB相關的所有主要操作。這些 API 設計精良且易於理解,因此更容易採用和建立人工智慧驅動的應用程式。然而,對向量的基本了解對於有效使用 API 同樣重要。與往常一樣,本文中使用的程式碼可以在 GitHub 上找到。