給你10萬張圖片,讓你找出與其中某張圖片最為近似的10張,你會怎麼做?不要輕言放棄,也不用一張張瀏覽。使用Python,你也可以輕鬆搞定這個任務。
疑問
《如何用Python和深度神經網路識別影象?》一文寫完後,我收到了不少讀者的反饋。其中一個很普遍的疑問是:
識別相同或相似的影象,有什麼好的方法麼?
我雖然樂於幫助讀者解決問題,但實話實說,一開始不太理解這種需求。
我文章裡的樣例圖片(哆啦a夢和瓦力),都是從網路蒐集來的。如果你需要從網上找到跟某張圖片近似的影象,可以使用Google的“以圖搜圖”功能啊。
很快,我突然醒悟過來。
這種需求,往往不是為了從網際網路上大海撈針,尋找近似圖片。而是在一個私有海量圖片集合中,找到近似影象。
這種圖片集合,也許是你團隊的科研資料。例如你研究鳥類。某天瀏覽野外拍攝裝置傳回來的影象時,突然發現一個新奇品種。
你於是很想搞清楚這種鳥類的出現時間、生活狀態等。這就需要從大量圖片裡,找到與其近似的圖片(最有可能是拍到了同一種鳥)。
這種圖片集合,也許是社會安全資料。例如你在反恐部門,系統突然發現某個疑似恐怖分子出現在敏感區域。這傢伙每一次現身,都伴隨著惡性刑事案件的發生,給人民群眾的生命財產安全帶來嚴重威脅。
這時候無論對其衣著、外貌還是交通工具的相似度搜尋,就顯得至關重要了。
上述例子中,因為你都沒有把影象上傳到網際網路,Google的“以圖搜圖”引擎功能再強大,也無能為力。
好吧,解決這個問題,很有意義。
下一個問題自然是:這種需求,解決起來複雜嗎?
是不是需要跨過很高的技術門檻才能實現?是不是需要花大量經費僱傭專家才能完成?
本文,我為你展示如何用10幾行Python程式碼,解決這個問題。
資料
為了講解的方便,我們依然採用《如何用Python和深度神經網路識別影象?》一文中使用過的哆啦a夢和瓦力圖片集合。
我給你準備好了119張哆啦a夢的照片,和80張瓦力的照片。圖片已經上傳到了這個Github專案。
請點選這個連結,下載壓縮包。然後在本地解壓。作為我們們的演示目錄。
解壓後,你會看到目錄下有個image資料夾,其中包含兩個子目錄,分別是doraemon和walle。
doraemon的目錄下,都是各式各樣的藍胖子圖片。
瓦力目錄下的圖片是這個樣子的:
資料已經有了,下面我們來準備一下環境配置。
環境
本文中,我們需要使用到蘋果公司的機器學習框架TuriCreate。
請注意TuriCreate釋出時間不久,目前支援的作業系統列表如下:
這就意味著,如果你用的作業系統是Windows 7及以下版本,那麼目前TuriCreate還不支援。如需使用,有兩種辦法:
第一種,請升級到Windows 10,並且使用WSL。關於如何使用WSL,我幫你找到了一個中文教程。請按照教程一步步完成安裝。
第二種,採用虛擬機器。推薦採用Virtualbox虛擬機器,開源免費。同樣地,我也幫你找到了很詳盡的Virtualbox安裝Ubuntu Linux的中文教程。你可以參照它安裝好Linux。
解決了系統相容性問題,下面我們在TuriCreate支援的系統中,安裝Python整合執行環境Anaconda。
請到這個網址 下載最新版的Anaconda。下拉頁面,找到下載位置。根據你目前使用的系統,網站會自動推薦給你適合的版本下載。我使用的是macOS,下載檔案格式為pkg。
下載頁面區左側是Python 3.6版,右側是2.7版。請選擇2.7版本。
雙擊下載後的pkg檔案,根據中文提示一步步安裝即可。
裝好Anaconda後,我們安裝TuriCreate。
請到你的“終端”下面,進入我們們剛剛下載解壓後的樣例目錄。
執行以下命令,我們來建立一個Anaconda虛擬環境,名字叫做turi。如果你之前跟隨我在《如何用Python和深度神經網路識別影象?》一文中創立過這個虛擬環境,此處請跳過。
conda create -n turi python=2.7 anaconda
複製程式碼
然後,我們啟用turi虛擬環境。
source activate turi
複製程式碼
在這個環境中,我們安裝(或者升級到)最新版的TuriCreate。
pip install -U turicreate
複製程式碼
安裝完畢後,執行:
jupyter notebook
複製程式碼
這樣就進入到了Jupyter筆記本環境。我們新建一個Python 2筆記本。
瀏覽器裡出現了一個空白筆記本。
點選左上角筆記本名稱,修改為有意義的筆記本名“demo-python-image-similarity”。
準備工作完畢,下面我們就可以開始編寫程式了。
程式碼
首先,我們讀入TuriCreate軟體包。
import turicreate as tc
複製程式碼
我們指定影象所在的資料夾image。讓TuriCreate讀取所有的影象檔案,並且儲存到data資料框。
data = tc.image_analysis.load_images('./image/')
複製程式碼
我們來看看,data資料框的內容:
data
複製程式碼
data包含兩列資訊,第一列是圖片的地址,第二列是圖片的長寬描述。
下面我們要求TuriCreate給資料框中每一行新增一個行號。這將作為圖片的標記,好在後面查詢圖片時使用。
data = data.add_row_number()
複製程式碼
再看看此時的data資料框內容:
data
複製程式碼
我們來看看資料框裡面的這些資訊對應的圖片。
data.explore()
複製程式碼
TuriCreate會彈出一個頁面,給我們展示資料框裡面的內容。
把滑鼠懸停在某張縮圖上面,就可以看到對應清晰大圖。
第一張圖片,是哆啦a夢:
第二張圖片,是瓦力:
下面,是重頭戲。我們讓TuriCreate根據輸入的圖片集合,建立影象相似度判別模型。
model = tc.image_similarity.create(data)
複製程式碼
這個語句執行起來,可能需要一些時間。如果你是第一次使用TuriCreate,它可能還需要從網上下載一些資料。請耐心等待。
Resizing images...
Performing feature extraction on resized images...
Completed 199/199
複製程式碼
注意這裡的提示,TuriCreate自動幫我們做了圖片尺寸調整等預處理工作,並且對每一張圖片,都做了特徵提取。
經過或長或短的等待,模型已經成功建立。
下面,我們來嘗試給模型一張圖片,讓TuriCreate幫我們從目前的圖片集合裡,挑出最為相似的10張來。
為了方便,我們就選擇第一張圖片作為查詢輸入。
我們利用show()
函式展示一下這張圖片。
tc.Image(data[0]['path']).show()
複製程式碼
確認無誤,還是那張哆啦a夢。
下面我們來查詢,我們讓模型尋找出與這張圖片最相似的10張。
similar_images = model.query(data[0:1], k=10)
複製程式碼
很快,系統提示我們,已經找到了。
我們把結果儲存在了similar_images
變數裡面,下面我們來看看其中都有哪些圖片。
similar_images
複製程式碼
返回的結果一共有10行。跟我們的要求一致。
每一行資料,包含4列。分別是:
- 查詢圖片的標記
- 獲得結果的標記
- 結果圖片與查詢圖片的距離
- 結果圖片與查詢圖片近似程度排序值
有了這些資訊,我們就可以檢視到底哪些圖片與輸入查詢圖片最為相似了。
注意其中的第一張結果圖片,其實就是我們的輸入圖片本身。考慮它沒有意義。
我們提取全部結果圖片的標記(索引)值,忽略掉第一張(自身)。
similar_image_index = similar_images['reference_label'][1:]
複製程式碼
剩餘9張圖片的標記都在結果中:
similar_image_index
複製程式碼
dtype: int
Rows: 9
[194, 158, 110, 185, 5, 15, 79, 91, 53]
複製程式碼
下面我們希望TuriCreate能夠視覺化幫我們展示這9張圖片的內容。
我們要把上面9張圖片的標記在所有圖片的索引列表中過濾出來:
filtered_index = data['id'].apply(lambda x : x in similar_image_index)
複製程式碼
看看過濾後的索引結果:
filtered_index
複製程式碼
dtype: int
Rows: 199
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, ... ]
複製程式碼
你可以自己數一數,其中標為1的那些圖片位置,和我們儲存在similar_image_index
中的數字是否一致。
驗證完畢以後,請執行以下語句。我們再次呼叫TuriCreate的explore()
函式,展現相似度查詢結果圖片。
data[filtered_index].explore()
複製程式碼
系統會彈出以下對話方塊:
我們可以看到,全部查詢結果圖片中,只出現了哆啦a夢。瓦力的圖片,一張都沒有出現。
近似圖片查詢成功!
隨著本文操作樣例資料後,你不妨換用自己的資料,來動手嘗試一番。
原理
展示瞭如何用10幾行Python程式碼幫你查詢相似圖形後,我們來聊聊這種強大、簡潔背後的原理。
如果你對原理不感興趣,請跳過這一部分,看“小結”。
雖然我們剛剛只是用了一條語句構建模型:
model = tc.image_similarity.create(data)
複製程式碼
然而實際上,TuriCreate在後臺為我們做了很多事情。
首先,它呼叫了一個非常複雜的,在龐大資料集上訓練好的模型。
《如何用Python和深度神經網路識別影象?》一文中,我們介紹過,這個模型就是上圖中的最後一行。它的名字叫做Resnet-50,足足有50層,訓練的圖片數以百萬計,訓練時長也很久。
這裡,機智的你一定會問個問題:那些數以百萬計的預訓練圖片裡面,是否有哆啦a夢和瓦力呢?
沒有。
那就怪了,不是嗎?
如果這個複雜的模型之前根本就沒有見過哆啦a夢和瓦力,那它怎麼知道如何區分它們呢?又怎麼能夠判別兩張哆啦a夢之間的差別,就一定比哆啦a夢和瓦力之間更小呢?
《如何用Python和深度神經網路識別影象?》一文裡,我已經提示給你一個關鍵詞:遷移學習(transfer learning)。
這裡我們們就不深入技術細節了。我只給你在概念層次講解一下。
還記得這張描述計算機視覺(卷積神經網路)的示意圖嗎?
在全連線層(Fully Connected Layer)之前,你可能進行了多次的卷積、抽樣、卷積、抽樣……這些中間層次,幫我們描繪了圖片的一些基本特徵,例如邊緣大概是個什麼形狀,某個區塊主要的顏色是哪些等。
到了全連線層,你只剩下了一組資料,這組資料可能很長,它抽取了你輸入資料的全部特徵。
如果你的輸入是一隻貓,此時的全連線層裡就描述了這隻貓的各種資訊,例如毛髮顏色、面部組成部分排列方式、邊緣的形狀……
這個模型可以幫你提取貓的特徵,但它並不知道“貓”的概念是什麼。
你自然可以用它幫你提取一條狗的特徵。
同理,哆啦a夢的照片,與貓咪的照片一樣,都是二維圖片,都是用不同顏色分層。
那用其他圖片訓練的模型,能否提取哆啦a夢照片裡的特徵呢?
當然也可以!
使用遷移學習的關鍵,在於凍結中間過程的全部訓練結果,直接把一幅圖,利用在其他圖片集合上訓練的模型,轉化為一個特徵描述結果。
後面的工作,只把這個最後的特徵描述(全連線層),用來處理分類和相似度計算。
前面的好幾十層引數迭代訓練,統統都被省卻了。
難怪可以利用這麼小的資料集獲得如此高的準確度;也難怪可以在這麼短的時間裡,就獲得整合後的模型結果。
把在某種任務上積累下的經驗與認知,遷移到另一種近似的新任務上,這種能力就叫做遷移學習。
比起機器來,人的遷移學習能力更為強大。
雨果獎作者郝景芳在最近的一篇文章裡,描述了人的這種強大學習能力:
小孩子可以快速學習,進行小資料學習,而且可以得到「類」的概念。小孩子輕易分得清「鴨子」這個概念,和每一隻具體不同的鴨子,有什麼不同。前者是抽象的「類」,後者是具體的東西。小孩子不需要看多少張鴨子的照片,就能得到「鴨子」這個抽象「類」的概念。
用成語來描述,大概就是“觸類旁通”吧。
如果人類不善於遷移學習,把生活中的所有事物,全都當成新的東西從頭學起,那後果簡直不堪設想。對比我們一生中所能處理的資訊總量,這種認知負荷將是無法承受的。
回到我們的問題裡,如果模型可以幫我們把每一張圖片,都變成全連線層上的那一長串數字(特徵),那麼我們分辨這些圖片的相似程度,就變得太簡單了。因為這變成了一個簡單的空間向量距離問題。
處理這種簡單的數值計算,我們人類可能覺得很繁瑣。但是計算機算起來,那就很歡快了。
根據距離大小排序,找出其中最小的幾個向量,它們描述的圖片,就被模型判定為相似度最高的。
小結
在《如何用Python和深度神經網路識別影象?》一文的基礎上,本文進一步介紹了以下內容:
- 如何利用TuriCreate快速構建圖片相似度模型;
- 如何查詢與某張圖片最為相似的k張圖片;
- 如何視覺化展示查詢圖片集合結果;
- TuriCreate圖形分類與相似度計算背後的原理;
- 遷移學習的基礎概念。
如果你沒有讀過《如何用Python和深度神經網路識別影象?》,強烈建議你讀一讀。閱讀過程可以幫助你更好地理解基於深度神經網路的計算機視覺工作原理。
討論
你之前遭遇過大海撈針,尋找近似圖片的工作嗎?你是如何處理的?使用過哪些好的工具與方法?與本文相比較,它們的優勢有哪些?歡迎留言,把你的經驗和思考分享給大家,我們一起交流討論。
喜歡請點贊。還可以微信關注和置頂我的公眾號“玉樹芝蘭”(nkwangshuyi)。
如果你對資料科學感興趣,不妨閱讀我的系列教程索引貼《如何高效入門資料科學?》,裡面還有更多的有趣問題及解法。