技術宅如何進化為女裝大佬

馬上就要過年了,估計大家都會考慮添置一件新女裝吧(逃

說到買衣服,你應該有過這樣的經歷:走在大街上忽然看到某個人穿著很酷炫的衣(nv)服(zhuang),心裡不禁感嘆這麼漂亮的衣服在哪買的,好想買一件穿穿,但你又不認識人家,只好望「衣」興嘆。但是假如現在有個方法能讓你根據衣服的照片就能找到網上的賣家呢?

總不能直接上去問吧?

德國有個程序員小哥 Aleksandr Movchan 給這個問題專門起了個名字——「街道到商店」(Street-to-Shop )購物,並且決定用機器學習中的距離度量學習(Distance Metric Learning,即 DML)解決這個問題。(再也不怕買不到喜歡的女裝了!)

度量學習

在介紹這篇「購物」教程前,先簡單說一說度量學習。

度量學習(Metric Learning)也就是常說的相似度學習。如果需要計算兩張圖片之間的相似度,如何度量圖片之間的相似度使得不同類別的圖片相似度小而相同類別的圖片相似度大就是度量學習的目標。

在數學中,一個度量(或距離函數)是一個定義集合中元素之間距離的函數。一個具有度量的集合被稱為度量空間。例如如果我們的目標是識別人臉,那麼就需要構建一個距離函數去強化合適的特徵(如發色,臉型等);而如果我們的目標是識別姿勢,那麼就需要構建一個捕獲姿勢相似度的距離函數。為了處理各種各樣的特徵相似度,我們可以在特定的任務中通過選擇合適的特徵並手動構建距離函數。然而這種方法需要很大的人工投入,也可能對數據的改變非常不魯棒(robust)。度量學習作為一個理想的替代,可以根據不同的任務來自主學習出針對某個特定任務的度量距離函數。

度量學習方法可以分為通過線性變換的度量學習和度量學習的非線性模型。一些很經典的非監督線性降維演算法也可以看作非監督的馬氏度量學習,如主成分分析、多維尺度變換等。

度量學習已應用於計算機視覺中的圖像檢索和分類、人臉識別、人類活動識別和姿勢估計,文本分析和一些其他領域如音樂分析,自動化的項目調試,微陣列數據分析等。

下面我們就看看具體的教程。

構建數據集

首先,和任何機器學習問題一樣,我們需要一個數據集。實際上,有天我在阿里巴巴速賣通上看到有海量的服裝照片時,我就有了這麼一個想法:可以用這些數據做一個根據照片進行搜索的功能。為了簡單便捷一點,我決定重點關注女裝,而且女孩子(和一部分男孩子)喜歡買衣服。

下面是我爬取圖像的女裝類別:

  • 裙子
  • 女裝襯衫
  • 衛衣&運動衫
  • 毛衣
  • 夾克&外套

我使用了 requests(pypi.python.org/pypi/re)和BeautifulSoup(pypi.python.org/pypi/be)爬取圖像。從服裝品類的主頁面上就可以獲取賣家的服裝照片,但是買家上傳的照片,我們需要在評價區獲取。在服裝頁面上有個「顏色」屬性,它可以用來判斷衣服是否是另一種顏色甚至是另外一種完全不同的服裝。所以我們會把不同顏色的衣服視為不同的商品。

衣服頁面上的「顏色」屬性

你可以點擊這裡

https://github.com/movchan74/street_to_shop_experiments/blob/master/get_item_info.pygithub.com

查看我用來獲取一件服裝所有信息的代碼。

我們只需要按服裝類別搜索服裝頁面,獲取所有服裝的 URL,使用上面的代碼獲得每件服裝的信息。

最後,我們會得到每件服裝的兩個圖像數據集:來自賣家的圖像(item[colors]中每個元素的URL欄位)和來自買家的圖像(item[feedbacks]中每個元素的URL欄位)。

對於每個顏色,我們只獲取來自賣家的一張照片,但來自買家的照片可能不止一張,有時候甚至一張照片都沒有(這和天貓上的買家秀一樣,有的會大秀特秀,有的一張都不秀)。

很好!我們得到了想要的數據。但是,得到的數據集里有很多雜訊數據:

來自買家的圖像數據中有很多雜訊,比如快遞包裝的照片,只秀衣服質地的照片而且只露出了一部分,還有些是剛撕開包裹時拍的照片。

買家的雜訊數據的一些照片樣例

為了減輕這個問題,我們將 5000 張圖像標記成兩個類別:良性照片和雜訊照片。剛開始,我的計劃是針對這兩個類別訓練一個分類器,然後用它來清洗數據集。但隨後我決定將這個工作留在後面,僅僅將乾淨的數據添加進測試數據集和驗證數據集中。

第二個問題是有時候好幾個賣家賣同一樣服裝,而且有時候幾家服裝店展示的衣服照片都一樣(或只是稍微做了些編輯工作)。怎麼解決這種問題呢?

最容易的一種方法就是什麼都不做,用距離度量學習中的一種魯棒演算法。不過這種方式會影響數據集的驗證效果,因為我們在驗證和訓練數據中會有相同的服裝。所以會導致數據外泄。另一種方法是尋找相似的(甚至完全相同的)服裝,然後將它們合併為一件服裝。我們可以用感知哈希(perceptual hashing)尋找相同的服裝照片,或者也可以用雜訊數據訓練一個模型,將模型用於尋找相同的服裝照片。我選擇了第二種方法,因為它能讓相同的照片合併為一張,哪怕是稍微編輯過的照片。

距離度量學習

最常用的一個距離度量學習方法是 Triplet loss:

其中 max(x,0)是 hinge 函數,d(x,y)是x和y之間的距離函數,F(x)是深度神經網路,M 是邊界,a 是 anchor,p 是正點數,n 是負點數。

F(a), F(p), F(n)是有深度神經網路生成的高維度空間(向量)中的點。有必要提一下,為了讓模型在應對照片的光照和對比度變化時更加魯棒,常常需要將向量進行正則化以獲得相同的單元長度,例如||x|| = 1。Anchor 和正樣本屬於同一類比,負樣本是另一個類別中的例子。

那麼 Triplet loss 的主要理念就是用一個距離邊界 M 將正例對(anchor和positive)的向量與負例對(anchor和negative)的向量進行區分。

但是怎樣選擇 triplet(a, p, n)呢?我們可以隨機選擇樣本作為一個 triplet,但這會導致下面的問題。首先,會存在 N3 個可能的 triplet。這意味著我們需要花很多時間來遍歷所有可能的triplet。但是實際上,我們不需要這麼做,因為訓練迭代幾次後,很多元 triplet 已經符合triplet 限制(比如0損失),也就是說這些 triplet 對於訓練沒有用處。

對於選擇 triplet 的一個最常見方式是難分樣本挖掘(hard negative mining):

選擇最難的難樣本在實際中會導致訓練初期出現糟糕的局部最小值。具體來說,就是它會造成一個收縮的模型(例如 F(x) = 0)。要想減輕這種問題,我們可以用半難分樣本挖掘(semi-hard negative mining)。

半難分樣本要比正樣本離 anchor 更遠一些,但它們仍然難以區分(不符合 triplet 限制),因為它們在邊界 M 內部。

生成半難分(和難分)樣本有兩種方式:在線和離線。

  • 在線方式是說我們可以從訓練數據集中隨機選擇一些樣本組成小批量數據,從這裡面的樣本中選擇 triplet。但是用在線方式我們還需要有大批量的數據。在我們這個例子中,沒法做到這一點,因為我只有一個僅 8 G RAM 的 GTX 1070。
  • 離線方式中,我們需要隔段時間暫停訓練,為一定數量的樣本預測向量,選擇 triplet,並用這些 triplet 訓練模型。這意味著我們需要進行兩次正推計算,這也是使用離線方式要付出的一點代價。

很好!我們現在可以用 triplet loss 和離線的半難分樣本挖掘方法訓練模型了。但是!(每當好事將近時,都有出現「但是」,這回也沒拿錯劇本)我們還需要一些方法才能完美地解決「街道到商店」問題。我們的任務是找到賣家和買家相同程度最高的衣服照片。

但是,往往賣家的照片質量要比買家上傳的照片質量好得太多(想想也是,網店發布的照片一般都經過了 N 道 PS 處理程序),所以我們會有兩個域:賣家照片和買家照片。要想獲得一個高效率模型,我們需要縮小這兩個域的差距。這個問題就叫做域適應(domain adaptation)。

左邊為賣家照片,右邊為買家照片(算是很良心了)

我提議一個很簡單的方法來縮小這兩個域的差距:我們從賣家照片中選擇 anchor,從買家照片中選擇正負樣本。就這些!雖然簡單但是很有效。

實現

為了實現我的想法,進行快速試驗,我使用了 Keras 程序庫和 TensorFlow 後端。

我選擇了 Inception V3 作為我的模型的基本卷積神經網路。和正常操作一樣,我用 ImageNet 權重初始化了卷積神經網路。然後在用 L2 正則化後在網路末端添加兩個完全相連的層。向量大小為 128。

def get_model(): no_top_model = InceptionV3(include_top=False, weights=imagenet, pooling=avg) x = no_top_model.output x = Dense(512, activation=elu, name=fc1)(x) x = Dense(128, name=fc2)(x) x = Lambda(lambda x: K.l2_normalize(x, axis=1), name=l2_norm)(x) return Model(no_top_model.inputs, x)

我們同樣也需要實現 triplet 損失函數,可以將 anchor,正負樣本作為一個單獨的小批量數據傳入函數,並將這個小批量數據在函數中分為 3 個張量。距離函數為歐式距離平方。

def margin_triplet_loss(y_true, y_pred, margin, batch_size): out_a = tf.gather(y_pred, tf.range(0, batch_size, 3)) out_p = tf.gather(y_pred, tf.range(1, batch_size, 3)) out_n = tf.gather(y_pred, tf.range(2, batch_size, 3)) loss = K.maximum(margin + K.sum(K.square(out_a-out_p), axis=1) - K.sum(K.square(out_a-out_n), axis=1), 0.0) return K.mean(loss)

並優化模型:

#utility function to freeze some portion of a functions argumentsfrom functools import partial, update_wrapperdef wrapped_partial(func, *args, **kwargs): partial_func = partial(func, *args, **kwargs) update_wrapper(partial_func, func) return partial_funcopt = keras.optimizers.Adam(lr=0.0001)model.compile(loss=wrapped_partial(margin_triplet_loss, margin=margin, batch_size=batch_size), optimizer=opt)

實驗結果

模型的性能衡量指標稱為 R@K。

我們看看怎樣計算 R@K。驗證集中的每個買家照片作為一次查詢,我們需要找到相應的賣家照片。我們每查詢一次照片,就會計算嵌入向量並搜索該向量在所有賣家照片中的最近鄰向量。我們不僅會用到驗證集中的賣家照片,而且也會用到訓練集中的賣家照片,因為這樣可以增加干擾數量,讓我們的任務更有挑戰性。

所以我們會得到一張查詢照片,以及一列最相似的賣家照片。如果在 K 個最相似照片中存在一個相應的賣家照片,我們為該查詢返回 1,如果不是,返回 0。現在我們需要為驗證集中的每一次查詢返回這樣一個結果,然後找到每次查詢的平均得分。這就是 R@K。

搜索結果:第一例-查詢(買家照片),後五列-電商網站上最相似的賣家照片

正如我上文所說,我從雜訊數據中清洗出了小部分買家照片。因而我用兩個驗證集測試了模型的質量:一個完整的驗證集和一個僅有乾淨數據的子集。

驗證數據中的 R@K

模型的結果不是很理想,我們還可以這麼做進行優化:

  • 將買家數據從雜訊數據中清洗出來。在這方面,我已經做了第一步,清洗出了一個小數據集。
  • 更精準地合併服裝照片(至少在驗證集中這麼做)。
  • 進一步縮小域之間的差距。我認為可以用特定域增強方法(例如增強圖像的光照度)和其它特定方法完成(比如這篇論文中的方法:arxiv.org/abs/1409.7495)。
  • 使用另一種距離度量學習方法,我試了這篇論文中的方法(arxiv.org/abs/1703.0746),但效果更糟了。
  • 當然還有收集更多的數據。

Demo,代碼和訓練後的模型

我給模型製作了一個 demo,可以點擊這裡查看

http://vps389544.ovh.net:5555/vps389544.ovh.net:5555

你可以上街拍張你喜歡的妹子的衣服照片(注意安全),或者從驗證集中隨機找一張,傳到模型上,試試效果如何。

代碼和訓練後模型地址:

https://github.com/movchan74/street_to_shop_experimentsgithub.com


推薦閱讀:

今年最後一次發車!
機翻要革命?哦我的老夥計,你是認真的?
快速上手:用你自己的數據集快速打造一個圖像識別器
沒人能躲過我的眼睛!
聽說你想要可以抱著睡覺的人形機器人

TAG:景略集智 | 機器學習 | 人工智慧 |