CS231n課程筆記翻譯:圖像分類筆記

譯者注:本文智能單元首發,譯自斯坦福CS231n課程筆記image classification notes,由課程教師Andrej Karpathy授權進行翻譯。本篇教程由杜客翻譯完成。ShiqingFan對譯文進行了仔細校對,提出了大量修改建議,態度嚴謹,幫助甚多。鞏子嘉對幾處術語使用和翻譯優化也提出了很好的建議。張欣等亦有幫助。

原文如下

這是一篇介紹性教程,面向非計算機視覺領域的同學。教程將向同學們介紹圖像分類問題和數據驅動方法。下面是內容列表:

  • 圖像分類、數據驅動方法和流程
  • Nearest Neighbor分類器
    • k-Nearest Neighbor
  • 驗證集、交叉驗證集和超參數調參
  • Nearest Neighbor的優劣
  • 小結
  • 小結:應用kNN實踐
  • 拓展閱讀

圖像分類

目標:這一節我們將介紹圖像分類問題。所謂圖像分類問題,就是已有固定的分類標籤集合,然後對於輸入的圖像,從分類標籤集合中找出一個分類標籤,最後把分類標籤分配給該輸入圖像。雖然看起來挺簡單的,但這可是計算機視覺領域的核心問題之一,並且有着各種各樣的實際應用。在後面的課程中,我們可以看到計算機視覺領域中很多看似不同的問題(比如物體檢測和分割),都可以被歸結爲圖像分類問題。

例子:以下圖爲例,圖像分類模型讀取該圖片,並生成該圖片屬於集合 {cat, dog, hat, mug}中各個標籤的概率。需要注意的是,對於計算機來說,圖像是一個由數字組成的巨大的3維數組。在這個例子中,貓的圖像大小是寬248像素,高400像素,有3個顏色通道,分別是紅、綠和藍(簡稱RGB)。如此,該圖像就包含了248X400X3=297600個數字,每個數字都是在範圍0-255之間的整型,其中0表示全黑,255表示全白。我們的任務就是把這些上百萬的數字變成一個簡單的標籤,比如“貓”。


這裏寫圖片描述
圖像分類的任務,就是對於一個給定的圖像,預測它屬於的那個分類標籤(或者給出屬於一系列不同標籤的可能性)。圖像是3維數組,數組元素是取值範圍從0到255的整數。數組的尺寸是寬度x高度x3,其中這個3代表的是紅、綠和藍3個顏色通道。


困難和挑戰:對於人來說,識別出一個像“貓”一樣視覺概念是簡單至極的,然而從計算機視覺算法的角度來看就值得深思了。我們在下面列舉了計算機視覺算法在圖像識別方面遇到的一些困難,要記住圖像是以3維數組來表示的,數組中的元素是亮度值。

  • 視角變化(Viewpoint variation):同一個物體,攝像機可以從多個角度來展現。
  • 大小變化(Scale variation):物體可視的大小通常是會變化的(不僅是在圖片中,在真實世界中大小也是變化的)。
  • 形變(Deformation):很多東西的形狀並非一成不變,會有很大變化。
  • 遮擋(Occlusion):目標物體可能被擋住。有時候只有物體的一小部分(可以小到幾個像素)是可見的。
  • 光照條件(Illumination conditions):在像素層面上,光照的影響非常大。
  • 背景干擾(Background clutter):物體可能混入背景之中,使之難以被辨認。
  • 類內差異(Intra-class variation):一類物體的個體之間的外形差異很大,比如椅子。這一類物體有許多不同的對象,每個都有自己的外形。

面對以上所有變化及其組合,好的圖像分類模型能夠在維持分類結論穩定的同時,保持對類間差異足夠敏感。


這裏寫圖片描述


數據驅動方法:如何寫一個圖像分類的算法呢?這和寫個排序算法可是大不一樣。怎麼寫一個從圖像中認出貓的算法?搞不清楚。因此,與其在代碼中直接寫明各類物體到底看起來是什麼樣的,倒不如說我們採取的方法和教小孩兒看圖識物類似:給計算機很多數據,然後實現學習算法,讓計算機學習到每個類的外形。這種方法,就是數據驅動方法。既然該方法的第一步就是收集已經做好分類標註的圖片來作爲訓練集,那麼下面就看看數據庫到底長什麼樣:


這裏寫圖片描述

一個有4個視覺分類的訓練集。在實際中,我們可能有上千的分類,每個分類都有成千上萬的圖像。


圖像分類流程。在課程視頻中已經學習過,圖像分類就是輸入一個元素爲像素值的數組,然後給它分配一個分類標籤。完整流程如下:

  • 輸入:輸入是包含N個圖像的集合,每個圖像的標籤是K種分類標籤中的一種。這個集合稱爲訓練集。
  • 學習:這一步的任務是使用訓練集來學習每個類到底長什麼樣。一般該步驟叫做訓練分類器或者學習一個模型。
  • 評價:讓分類器來預測它未曾見過的圖像的分類標籤,並以此來評價分類器的質量。我們會把分類器預測的標籤和圖像真正的分類標籤對比。毫無疑問,分類器預測的分類標籤和圖像真正的分類標籤如果一致,那就是好事,這樣的情況越多越好。

Nearest Neighbor分類器

作爲課程介紹的第一個方法,我們來實現一個Nearest Neighbor分類器。雖然這個分類器和卷積神經網絡沒有任何關係,實際中也極少使用,但通過實現它,可以讓讀者對於解決圖像分類問題的方法有個基本的認識。

圖像分類數據集:CIFAR-10。一個非常流行的圖像分類數據集是CIFAR-10。這個數據集包含了60000張32X32的小圖像。每張圖像都有10種分類標籤中的一種。這60000張圖像被分爲包含50000張圖像的訓練集和包含10000張圖像的測試集。在下圖中你可以看見10個類的10張隨機圖片。


這裏寫圖片描述

左邊:從CIFAR-10數據庫來的樣本圖像。右邊:第一列是測試圖像,然後第一列的每個測試圖像右邊是使用Nearest Neighbor算法,根據像素差異,從訓練集中選出的10張最類似的圖片。


假設現在我們有CIFAR-10的50000張圖片(每種分類5000張)作爲訓練集,我們希望將餘下的10000作爲測試集並給他們打上標籤。Nearest Neighbor算法將會拿着測試圖片和訓練集中每一張圖片去比較,然後將它認爲最相似的那個訓練集圖片的標籤賦給這張測試圖片。上面右邊的圖片就展示了這樣的結果。請注意上面10個分類中,只有3個是準確的。比如第8行中,馬頭被分類爲一個紅色的跑車,原因在於紅色跑車的黑色背景非常強烈,所以這匹馬就被錯誤分類爲跑車了。

那麼具體如何比較兩張圖片呢?在本例中,就是比較32x32x3的像素塊。最簡單的方法就是逐個像素比較,最後將差異值全部加起來。換句話說,就是將兩張圖片先轉化爲兩個向量I1I2 ,然後計算他們的L1距離

d1(I1,I2)=p|Ip1Ip2|

這裏的求和是針對所有的像素。下面是整個比較流程的圖例:

這裏寫圖片描述

以圖片中的一個顏色通道爲例來進行說明。兩張圖片使用L1距離來進行比較。逐個像素求差值,然後將所有差值加起來得到一個數值。如果兩張圖片一模一樣,那麼L1距離爲0,但是如果兩張圖片很是不同,那L1值將會非常大。


下面,讓我們看看如何用代碼來實現這個分類器。首先,我們將CIFAR-10的數據加載到內存中,並分成4個數組:訓練數據和標籤,測試數據和標籤。在下面的代碼中,Xtr(大小是50000x32x32x3)存有訓練集中所有的圖像,Ytr是對應的長度爲50000的1維數組,存有圖像對應的分類標籤(從0到9):

Xtr, Ytr, Xte, Yte = load_CIFAR10('data/cifar10/') # a magic function we provide
# flatten out all images to be one-dimensional
Xtr_rows = Xtr.reshape(Xtr.shape[0], 32 * 32 * 3) # Xtr_rows becomes 50000 x 3072
Xte_rows = Xte.reshape(Xte.shape[0], 32 * 32 * 3) # Xte_rows becomes 10000 x 3072

現在我們得到所有的圖像數據,並且把他們拉長成爲行向量了。接下來展示如何訓練並評價一個分類器:

nn = NearestNeighbor() # create a Nearest Neighbor classifier class
nn.train(Xtr_rows, Ytr) # train the classifier on the training images and labels
Yte_predict = nn.predict(Xte_rows) # predict labels on the test images
# and now print the classification accuracy, which is the average number
# of examples that are correctly predicted (i.e. label matches)
print 'accuracy: %f' % ( np.mean(Yte_predict == Yte) )

作爲評價標準,我們常常使用準確率,它描述了我們預測正確的得分。請注意以後我們實現的所有分類器都需要有這個API:train(X, y)函數。該函數使用訓練集的數據和標籤來進行訓練。從其內部來看,類應該實現一些關於標籤和標籤如何被預測的模型。這裏還有個predict(X)函數,它的作用是預測輸入的新數據的分類標籤。現在還沒介紹分類器的實現,下面就是使用L1距離的Nearest Neighbor分類器的實現套路:

import numpy as np

class NearestNeighbor(object):
  def __init__(self):
    pass

  def train(self, X, y):
    """ X is N x D where each row is an example. Y is 1-dimension of size N """
    # the nearest neighbor classifier simply remembers all the training data
    self.Xtr = X
    self.ytr = y

  def predict(self, X):
    """ X is N x D where each row is an example we wish to predict label for """
    num_test = X.shape[0]
    # lets make sure that the output type matches the input type
    Ypred = np.zeros(num_test, dtype = self.ytr.dtype)

    # loop over all test rows
    for i in xrange(num_test):
      # find the nearest training image to the i'th test image
      # using the L1 distance (sum of absolute value differences)
      distances = np.sum(np.abs(self.Xtr - X[i,:]), axis = 1)
      min_index = np.argmin(distances) # get the index with smallest distance
      Ypred[i] = self.ytr[min_index] # predict the label of the nearest example

    return Ypred

如果你用這段代碼跑CIFAR-10,你會發現準確率能達到38.6%。這比隨機猜測的10%要好,但是比人類識別的水平(據研究推測是94%)和卷積神經網絡能達到的95%還是差多了。點擊查看基於CIFAR-10數據的Kaggle算法競賽排行榜

距離選擇:計算向量間的距離有很多種方法,另一個常用的方法是L2距離,從幾何學的角度,可以理解爲它在計算兩個向量間的歐式距離。L2距離的公式如下:

d2(I1,I2)=p(Ip1Ip2)2

換句話說,我們依舊是在計算像素間的差值,只是先求其平方,然後把這些平方全部加起來,最後對這個和開方。在Numpy中,我們只需要替換上面代碼中的1行代碼就行:
distances = np.sqrt(np.sum(np.square(self.Xtr - X[i,:]), axis = 1))

注意在這裏使用了np.sqrt,但是在實際中可能不用。因爲求平方根函數是一個單調函數,它對不同距離的絕對值求平方根雖然改變了數值大小,但依然保持了不同距離大小的順序。所以用不用它,都能夠對像素差異的大小進行正確比較。如果你在CIFAR-10上面跑這個模型,正確率是35.4%,比剛纔低了一點。

L1和L2比較。比較這兩個度量方式是挺有意思的。在面對兩個向量之間的差異時,L2比L1更加不能容忍這些差異。也就是說,相對於1個巨大的差異,L2距離更傾向於接受多箇中等程度的差異。L1和L2都是在p-norm常用的特殊形式。

k-Nearest Neighbor分類器

你可能注意到了,爲什麼只用最相似的1張圖片的標籤來作爲測試圖像的標籤呢?這不是很奇怪嗎!是的,使用k-Nearest Neighbor分類器就能做得更好。它的思想很簡單:與其只找最相近的那1個圖片的標籤,我們找最相似的k個圖片的標籤,然後讓他們針對測試圖片進行投票,最後把票數最高的標籤作爲對測試圖片的預測。所以當k=1的時候,k-Nearest Neighbor分類器就是Nearest Neighbor分類器。從直觀感受上就可以看到,更高的k值可以讓分類的效果更平滑,使得分類器對於異常值更有抵抗力。


這裏寫圖片描述

上面示例展示了Nearest Neighbor分類器和5-Nearest Neighbor分類器的區別。例子使用了2維的點來表示,分成3類(紅、藍和綠)。不同顏色區域代表的是使用L2距離的分類器的決策邊界。白色的區域是分類模糊的例子(即圖像與兩個以上的分類標籤綁定)。需要注意的是,在NN分類器中,異常的數據點(比如:在藍色區域中的綠點)製造出一個不正確預測的孤島。5-NN分類器將這些不規則都平滑了,使得它針對測試數據的泛化(generalization)能力更好(例子中未展示)。注意,5-NN中也存在一些灰色區域,這些區域是因爲近鄰標籤的最高票數相同導致的(比如:2個鄰居是紅色,2個鄰居是藍色,還有1個是綠色)。


在實際中,大多使用k-NN分類器。但是k值如何確定呢?接下來就討論這個問題。

用於超參數調優的驗證集

k-NN分類器需要設定k值,那麼選擇哪個k值最合適的呢?我們可以選擇不同的距離函數,比如L1範數和L2範數等,那麼選哪個好?還有不少選擇我們甚至連考慮都沒有考慮到(比如:點積)。所有這些選擇,被稱爲超參數(hyperparameter)。在基於數據進行學習的機器學習算法設計中,超參數是很常見的。一般說來,這些超參數具體怎麼設置或取值並不是顯而易見的。

你可能會建議嘗試不同的值,看哪個值表現最好就選哪個。好主意!我們就是這麼做的,但這樣做的時候要非常細心。特別注意:決不能使用測試集來進行調優。當你在設計機器學習算法的時候,應該把測試集看做非常珍貴的資源,不到最後一步,絕不使用它。如果你使用測試集來調優,而且算法看起來效果不錯,那麼真正的危險在於:算法實際部署後,性能可能會遠低於預期。這種情況,稱之爲算法對測試集過擬合。從另一個角度來說,如果使用測試集來調優,實際上就是把測試集當做訓練集,由測試集訓練出來的算法再跑測試集,自然性能看起來會很好。這其實是過於樂觀了,實際部署起來效果就會差很多。所以,最終測試的時候再使用測試集,可以很好地近似度量你所設計的分類器的泛化性能(在接下來的課程中會有很多關於泛化性能的討論)。

測試數據集只使用一次,即在訓練完成後評價最終的模型時使用。

好在我們有不用測試集調優的方法。其思路是:從訓練集中取出一部分數據用來調優,我們稱之爲驗證集(validation set)。以CIFAR-10爲例,我們可以用49000個圖像作爲訓練集,用1000個圖像作爲驗證集。驗證集其實就是作爲假的測試集來調優。下面就是代碼:

# assume we have Xtr_rows, Ytr, Xte_rows, Yte as before
# recall Xtr_rows is 50,000 x 3072 matrix
Xval_rows = Xtr_rows[:1000, :] # take first 1000 for validation
Yval = Ytr[:1000]
Xtr_rows = Xtr_rows[1000:, :] # keep last 49,000 for train
Ytr = Ytr[1000:]

# find hyperparameters that work best on the validation set
validation_accuracies = []
for k in [1, 3, 5, 10, 20, 50, 100]:

  # use a particular value of k and evaluation on validation data
  nn = NearestNeighbor()
  nn.train(Xtr_rows, Ytr)
  # here we assume a modified NearestNeighbor class that can take a k as input
  Yval_predict = nn.predict(Xval_rows, k = k)
  acc = np.mean(Yval_predict == Yval)
  print 'accuracy: %f' % (acc,)

  # keep track of what works on the validation set
  validation_accuracies.append((k, acc))

程序結束後,我們會作圖分析出哪個k值表現最好,然後用這個k值來跑真正的測試集,並作出對算法的評價。

把訓練集分成訓練集和驗證集。使用驗證集來對所有超參數調優。最後只在測試集上跑一次並報告結果。

交叉驗證。有時候,訓練集數量較小(因此驗證集的數量更小),人們會使用一種被稱爲交叉驗證的方法,這種方法更加複雜些。還是用剛纔的例子,如果是交叉驗證集,我們就不是取1000個圖像,而是將訓練集平均分成5份,其中4份用來訓練,1份用來驗證。然後我們循環着取其中4份來訓練,其中1份來驗證,最後取所有5次驗證結果的平均值作爲算法驗證結果。


這裏寫圖片描述

這就是5份交叉驗證對k值調優的例子。針對每個k值,得到5個準確率結果,取其平均值,然後對不同k值的平均表現畫線連接。本例中,當k=7的時算法表現最好(對應圖中的準確率峯值)。如果我們將訓練集分成更多份數,直線一般會更加平滑(噪音更少)。


實際應用。在實際情況下,人們不是很喜歡用交叉驗證,主要是因爲它會耗費較多的計算資源。一般直接把訓練集按照50%-90%的比例分成訓練集和驗證集。但這也是根據具體情況來定的:如果超參數數量多,你可能就想用更大的驗證集,而驗證集的數量不夠,那麼最好還是用交叉驗證吧。至於分成幾份比較好,一般都是分成3、5和10份。


這裏寫圖片描述

常用的數據分割模式。給出訓練集和測試集後,訓練集一般會被均分。這裏是分成5份。前面4份用來訓練,黃色那份用作驗證集調優。如果採取交叉驗證,那就各份輪流作爲驗證集。最後模型訓練完畢,超參數都定好了,讓模型跑一次(而且只跑一次)測試集,以此測試結果評價算法。


Nearest Neighbor分類器的優劣

現在對Nearest Neighbor分類器的優缺點進行思考。首先,Nearest Neighbor分類器易於理解,實現簡單。其次,算法的訓練不需要花時間,因爲其訓練過程只是將訓練集數據存儲起來。然而測試要花費大量時間計算,因爲每個測試圖像需要和所有存儲的訓練圖像進行比較,這顯然是一個缺點。在實際應用中,我們關注測試效率遠遠高於訓練效率。其實,我們後續要學習的卷積神經網絡在這個權衡上走到了另一個極端:雖然訓練花費很多時間,但是一旦訓練完成,對新的測試數據進行分類非常快。這樣的模式就符合實際使用需求。

Nearest Neighbor分類器的計算複雜度研究是一個活躍的研究領域,若干Approximate Nearest Neighbor (ANN)算法和庫的使用可以提升Nearest Neighbor分類器在數據上的計算速度(比如:FLANN)。這些算法可以在準確率和時空複雜度之間進行權衡,並通常依賴一個預處理/索引過程,這個過程中一般包含kd樹的創建和k-means算法的運用。

Nearest Neighbor分類器在某些特定情況(比如數據維度較低)下,可能是不錯的選擇。但是在實際的圖像分類工作中,很少使用。因爲圖像都是高維度數據(他們通常包含很多像素),而高維度向量之間的距離通常是反直覺的。下面的圖片展示了基於像素的相似和基於感官的相似是有很大不同的:


這裏寫圖片描述

在高維度數據上,基於像素的的距離和感官上的非常不同。上圖中,右邊3張圖片和左邊第1張原始圖片的L2距離是一樣的。很顯然,基於像素比較的相似和感官上以及語義上的相似是不同的。


這裏還有個視覺化證據,可以證明使用像素差異來比較圖像是不夠的。z這是一個叫做t-SNE的可視化技術,它將CIFAR-10中的圖片按照二維方式排布,這樣能很好展示圖片之間的像素差異值。在這張圖片中,排列相鄰的圖片L2距離就小。


這裏寫圖片描述

上圖使用t-SNE的可視化技術將CIFAR-10的圖片進行了二維排列。排列相近的圖片L2距離小。可以看出,圖片的排列是被背景主導而不是圖片語義內容本身主導。


具體說來,這些圖片的排布更像是一種顏色分佈函數,或者說是基於背景的,而不是圖片的語義主體。比如,狗的圖片可能和青蛙的圖片非常接近,這是因爲兩張圖片都是白色背景。從理想效果上來說,我們肯定是希望同類的圖片能夠聚集在一起,而不被背景或其他不相關因素干擾。爲了達到這個目的,我們不能止步於原始像素比較,得繼續前進。

小結

簡要說來:

  • 介紹了圖像分類問題。在該問題中,給出一個由被標註了分類標籤的圖像組成的集合,要求算法能預測沒有標籤的圖像的分類標籤,並根據算法預測準確率進行評價。
  • 介紹了一個簡單的圖像分類器:最近鄰分類器(Nearest Neighbor
    classifier)。分類器中存在不同的超參數(比如k值或距離類型的選取),要想選取好的超參數不是一件輕而易舉的事。
  • 選取超參數的正確方法是:將原始訓練集分爲訓練集和驗證集,我們在驗證集上嘗試不同的超參數,最後保留表現最好那個。
  • 如果訓練數據量不夠,使用交叉驗證方法,它能幫助我們在選取最優超參數的時候減少噪音。
  • 一旦找到最優的超參數,就讓算法以該參數在測試集跑且只跑一次,並根據測試結果評價算法。
  • 最近鄰分類器能夠在CIFAR-10上得到將近40%的準確率。該算法簡單易實現,但需要存儲所有訓練數據,並且在測試的時候過於耗費計算能力。
  • 最後,我們知道了僅僅使用L1和L2範數來進行像素比較是不夠的,圖像更多的是按照背景和顏色被分類,而不是語義主體分身。

在接下來的課程中,我們將專注於解決這些問題和挑戰,並最終能夠得到超過90%準確率的解決方案。該方案能夠在完成學習就丟掉訓練集,並在一毫秒之內就完成一張圖片的分類。

小結:實際應用k-NN

如果你希望將k-NN分類器用到實處(最好別用到圖像上,若是僅僅作爲練手還可以接受),那麼可以按照以下流程:

  1. 預處理你的數據:對你數據中的特徵進行歸一化(normalize),讓其具有零平均值(zero mean)和單位方差(unit
    variance)。在後面的小節我們會討論這些細節。本小節不討論,是因爲圖像中的像素都是同質的,不會表現出較大的差異分佈,也就不需要標準化處理了。
  2. 如果數據是高維數據,考慮使用降維方法,比如PCA(wiki ref, CS229ref, blog ref)或隨機投影
  3. 將數據隨機分入訓練集和驗證集。按照一般規律,70%-90%
    數據作爲訓練集。這個比例根據算法中有多少超參數,以及這些超參數對於算法的預期影響來決定。如果需要預測的超參數很多,那麼就應該使用更大的驗證集來有效地估計它們。如果擔心驗證集數量不夠,那麼就嘗試交叉驗證方法。如果計算資源足夠,使用交叉驗證總是更加安全的(份數越多,效果越好,也更耗費計算資源)。
  4. 在驗證集上調優,嘗試足夠多的k值,嘗試L1和L2兩種範數計算方式。
  5. 如果分類器跑得太慢,嘗試使用Approximate Nearest
    Neighbor庫(比如FLANN)來加速這個過程,其代價是降低一些準確率。
  6. 對最優的超參數做記錄。記錄最優參數後,是否應該讓使用最優參數的算法在完整的訓練集上運行並再次訓練呢?因爲如果把驗證集重新放回到訓練集中(自然訓練集的數據量就又變大了),有可能最優參數又會有所變化。在實踐中,不要這樣做。千萬不要在最終的分類器中使用驗證集數據,這樣做會破壞對於最優參數的估計。直接使用測試集來測試用最優參數設置好的最優模型,得到測試集數據的分類準確率,並以此作爲你的kNN分類器在該數據上的性能表現。

拓展閱讀

下面是一些你可能感興趣的拓展閱讀鏈接:

A Few Useful Things to Know about Machine Learning,文中第6節與本節相關,但是整篇文章都強烈推薦。
Recognizing and Learning Object Categories,ICCV 2005上的一節關於物體分類的課程。

發佈了99 篇原創文章 · 獲贊 27 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章