給你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)。
如果你對數據科學感興趣,不妨閱讀我的系列教程索引貼《如何高效入門數據科學?》,里面還有更多的有趣問題及解法。