以前寫過一篇概述: 以圖搜圖系統概述 。html
以圖搜圖系統須要解決的主要問題是:python
對應的工程實踐,具體爲:es6
CNN
提取圖像特徵Milvus
使用卷積神經網路 CNN 去提取圖像特徵是一種主流的方案,具體的模型則可使用 VGG16
,技術實現上則使用 Keras
+ TensorFlow
,參考 Keras 官方示例:數據庫
from keras.applications.vgg16 import VGG16 from keras.preprocessing import image from keras.applications.vgg16 import preprocess_input import numpy as np model = VGG16(weights='imagenet', include_top=False) img_path = 'elephant.jpg' img = image.load_img(img_path, target_size=(224, 224)) x = image.img_to_array(img) x = np.expand_dims(x, axis=0) x = preprocess_input(x) features = model.predict(x)
這裏提取出來的 feature
就是特性向量。segmentfault
爲了方便後續操做,咱們經常會將 feature
進行歸一化的處理:api
from numpy import linalg as LA norm_feat = feat[0]/LA.norm(feat[0])
後續實際使用的也是歸一化後的 norm_feat
。網絡
這裏加載圖像使用的是 keras.preprocessing
的 image.load_img
方法即:數據結構
from keras.preprocessing import image img_path = 'elephant.jpg' img = image.load_img(img_path, target_size=(224, 224))
其實是 Keras
調用的 TensorFlow
的方法,詳情見 TensorFlow 官方文檔 ,而最後獲得的 image
對象實際上是一個 PIL Image 實例( TensorFlow
使用的 PIL
)。app
實際工程中圖像內容經常是經過網絡進行傳輸的,所以相比於從 path 路徑加載圖片,咱們更但願直接將 bytes 數據轉換爲 image 對象即 PIL Image :數據庫設計
import io from PIL import Image # img_bytes: 圖片內容 bytes img = Image.open(io.BytesIO(img_bytes)) img = img.convert('RGB') img = img.resize((224, 224), Image.NEAREST)
以上 img
與前文中的 image.load_img
獲得的結果相同,這裏須要注意的是:
RGB
轉換resize
( load_img
方法的第二個參數也就是 resize )有時候圖像會有比較多的黑邊部分(例如截屏),而這些黑邊的部分即沒有實際價值,又會產生比較大的干擾,所以去除黑邊也是一項常見的操做。
所謂黑邊,本質上就是一行或一列的像素點所有都是 (0, 0, 0)
( RGB 圖像),去除黑邊就是找到這些行或列,而後刪除,實際是一個 numpy 的 3-D Matrix 操做。
移除橫向黑邊示例:
# -*- coding: utf-8 -*- import numpy as np from keras.preprocessing import image def RemoveBlackEdge(img): """移除圖片橫向黑邊 Args: img: PIL image 實例 Returns: PIL image 實例 """ width = img.width img = image.img_to_array(img) img_without_black = img[~np.all(img == np.zeros((1, width, 3), np.uint8), axis=(1, 2))] img = image.array_to_img(img_without_black) return img
CNN 提取圖像特徵以及圖像的其它相關處理先寫這麼多,咱們再看向量搜索引擎。
只有圖像的特徵向量是遠遠不夠的,咱們還須要對這些特徵向量進行動態的管理(增刪改),以及計算向量的類似度並返回最鄰近範圍內的向量數據,而開源的向量搜索引擎 Milvus 則很好的完成這些工做。
下文將會講述具體的實踐,以及要注意的地方。
想要使用 Milvus
,首先必需要求你的 CPU 支持 avx2
指令集,如何查看你的 CPU 支持哪些指令集呢?對於 Linux 系統,輸入指令
cat /proc/cpuinfo | grep flags
你將會看到形如如下的內容:
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand lahf_lm abm cpuid_fault epb invpcid_single pti intel_ppin tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm xsaveopt cqm_llc cqm_occup_llc dtherm ida arat pln pts
flags
後面的這一大堆就是你的 CPU 支持的所有指令集,固然內容太多了,我只想看是否支持具體的某個指令集,好比 avx2
, 再加一個 grep 過濾一下便可:
cat /proc/cpuinfo | grep flags | grep avx2
若是執行結果沒有內容輸出,就是不支持這個指令集,你只能換一臺知足要求的機器。
系統設計時,容量規劃是須要首先考慮的地方,咱們須要存儲多少數據,這些數據須要多少內存以及多大的磁盤空間?
速算,上文中特徵向量的每個維度都是 float32
的數據類型,一個 float32
須要佔用 4 byte
,那麼一個 512 維的向量就須要 2 KB
,依次類推:
若是咱們但願能將數據所有存在內存中,那麼系統就至少須要對應大小的內存容量。
這裏推薦你使用官方的大小計算工具: milvus tools
實際上咱們的內存可能並無那麼大(內存不夠不要緊,milvus 會將數據自動刷寫到磁盤上),另外除了這些原始的向量數據以外,還會有一些其餘的數據例如日誌等的存儲也是咱們須要考慮的地方。
關於系統配置,官方文檔有比較詳細的說明:
在 Milvus 中,數據會按照 collection
和 partition
進行劃分:
collection
就是咱們理解的表。partition
則是 collection
的分區,也就是某個表內部的分區。partition
分區在底層實現上其實與 collection
集合是一致的,只是前者從屬於後者,可是有了分區以後,數據的組織方式變得更加靈活,咱們也能夠指定集合中某個特定分區進行查詢,從而達到一個更高的查詢性能,更多內容參考 分區表詳細說明 。
咱們可使用多少個 collection
和 partition
?
因爲 collection
和 partition
的基本信息都屬於元數據,而 milvus 內部進行元數據管理須要使用 SQLite
( milvus 內部集成)或者 MySQL
(須要外部鏈接) 其中之一,若是你使用默認的 SQLite
去管理元數據的話,當集合和分區的數量過多時,性能損耗會很嚴重,所以集合和分區總數不要超過 50000
,須要設置更多的數量則建議使用外接 MySQL
的方式。
Milvus 的 collection
和 partition
內部支持的數據結構很是簡單,只支持 ID + vector
,換句話說,表只有兩列,一列是 ID ,一列是向量數據。
注意:
collection
的層面是惟一的,而不是 partition
。咱們使用一些傳統的數據庫時,每每能夠指定字段進行條件過濾,可是 Milvus 並不能直接支持這項功能,然而咱們是能夠經過集合和分區的設計去實現簡單的條件過濾,例如,咱們有不少圖片數據,可是這些圖片數據都明確的屬於具體的用戶,那麼咱們就能夠按照用戶去劃分 partition
,這樣查詢的時候以用戶做爲過濾條件其實就是指定 partition
便可。
因爲 milvus 只支持 ID + vector
的數據結構,而實際業務上咱們最終須要的每每是具備業務意義的結構化數據,也就是說,咱們須要經過 vector
向量最終找到結構化數據,所以咱們須要經過 ID
去維護結構化數據與向量之間的映射關係:
結構化數據 ID <--> 映射表 <--> Milvus ID
請參考如下文檔:
Milvus 的搜索結果是 ID + distance
的集合:
ID
: collection
中的 ID
。distance
: 0 ~ 1 的距離值,表示類似性程度,越小越類似。當數據集過少的時候,搜索結果可能會包含 ID 爲 -1 的數據,咱們須要本身去過濾掉。
向量的搜索比較特別,查詢的結果是按照類似性順序,從最類似開始日後選取 topK
個數據( topK
須要搜索時由用戶指定)。
Milvus 的搜索不支持翻頁,若是咱們但願在業務上實現這個功能,那麼只能由咱們本身去處理,好比,我想要每頁 10 條數據,只顯示第 3 頁的數據,那麼咱們須要去取 topK = 30
的數據,而後只返回最後 10 條。
兩張圖片的特徵向量的距離 distance
範圍是 0 ~ 1 ,有些時候咱們須要在業務上去斷定兩張圖片是否類似,這時就須要咱們本身去設置一個距離的閾值,當 distance
小於閾值時就能夠斷定爲類似,大於閾值時斷定爲不類似,這個也是須要根據具體的業務本身去處理。
本文講述了以圖搜圖系統進行工程實踐時比較常見的內容,最後強烈推薦一下 Milvus 。