說明:
本系列文章翻譯斯坦福大學的課程:Convolutional Neural Networks for Visual Recognition的課程講義 原文地址:http://cs231n.github.io/。 最好有Python基礎(但不是必要的),Python的介紹見該課程的module0。
本節的code見地址:
https://github.com/anthony123/cs231n/tree/master/module1-1如果在code中發現bug或者有什么不清楚的地方,可以及時給我留言,因為code沒有經過很嚴格的測試。
這節課的目的在于向來自計算機視覺背景之外的人介紹圖像分類問題和數據驅動方法。這節課的主要內容為:
- 介紹圖像分類,數據驅動方法和流程
- 最近鄰分類法:K最近鄰
- 驗證集,交叉驗證,超參數調節
- 最近鄰方法的優點和缺點
- 總結
圖像分類
動機
在這一小節中, 我們將會介紹圖像分類問題,即給一個輸入圖片賦一個標簽,這個標簽來自于一個固定的標簽集。這是計算機視覺的一個核心的問題,盡管很簡單,卻有很大的實踐應用價值。而且,隨著課程的深入,你會發現,很多看起來非常不一樣的計算機視覺任務(比如物體檢測,分割)都可以化簡為圖像分類問題。
例子
如下圖所示,一個圖像分類的分類器接收一張圖片的輸入,并給四個標簽(貓,狗,帽子,杯子)分別賦一個概率值,對于電腦來說,一張圖片就是一個大的三維數組。在這個例子中,貓的圖片大小是248個像素寬,400個像素長,有三個顏色通道:紅,綠,藍(簡稱RGB)。因此,這張圖片由248x400x3個數字,共計297,600個數字組成。每個數字都是一個從0(黑色)到255(白色)的任意整數。我們的任務就是把這一百萬個數字轉變為一個標簽,比如:貓。
挑戰
既然視覺的識別任務(比如貓)對人類來說比較簡單,那么有必要從計算機視覺的角度來考慮下其中的挑戰。我下面列出一系列的挑戰(不是一個完整的清單),請時刻記住圖像的原始表現就是一個三維數組,每個元素代表一個光照強度。
- 視角變化: 對于觀察者(人或相機)來說,我們可以從不同的角度去觀察一個物體
- 尺度變化: 一個類別的物體通常可以表現出不同的大小(不僅僅是它們在圖像中表現的大小,還可能是它們真實大小的差異)
- 形變:很多物體都不是堅硬的,在極端的情況下可能發生形變。
- 遮擋: 物體可能會被遮擋,只有一部分是可見的。
- 光照條件: 光照會對像素值起到非常大的影響。
- 背景影響: 物體可能會融合到環境當中,使得它們難以辨別
- 類別內的差異: 一個類別內的物體可能形狀各異,比如椅子。有很多類型的椅子,每種椅子都有它們獨特的外形。
一個好的圖像分類器應該對以上這些變化都不敏感,而對不同類之間的變化敏感。
數據驅動方法
我們該如何寫出一個算法,將圖片分到不同的類中呢?和寫一個排序算法不一樣,寫一個圖像識別算法(比如貓)可不是那么容易。寫一個圖像識別算法,不是直接在代碼中寫出每一種貓的形狀,而是使用一種類似于帶小孩的方法:我們提供分類器每一類中的許多例子,然后開發一種學習算法,讓它觀看這些例子,學習每一類的視覺形象。這種方法被稱之為數據驅動算法。因為它依賴于首先累計一個帶有標記的訓練集。下面是一個這種數據集的例子:
圖像分類流程
我們已經知道圖像分類的任務是輸入一個表示圖片的像素數組,我們給這張圖片賦一個標簽。我們完整的流程可以表示為如下的形式:
1)輸入: 我們的輸入是包含N張圖片的集合,每張圖片都被標記為K類中的一種,我們把這個集合稱之為訓練集
2)學習: 我們的任務是利用這個訓練集,來學習一個種類中每個個體長什么樣。我們把這一步稱之為訓練分類器,或者學習一個模型。
3)評價: 最后,我們通過測試一個新的數據集及其標簽,來評價這個分類器的質量。我們希望預測能最大程度上和正確答案一致(ground truth)。
最近鄰分類器
作為我們的第一種方法,我們將開發最近鄰分類器。這種分類器和卷積神經網絡沒有任何關系,而且它很少用于實踐當中,但是它能夠幫助我們了解圖片分類問題的基本的流程。
圖片分類數據集例子: CIFAR-10. 一個著名的小型圖像分類算法數據集是CIFAR-10數據集(下載地址為 https://pan.baidu.com/s/1dFaCKFn, 密碼為wt7m)。這個數據集包括60,000張小圖片,每張圖片32像素寬和32像素高。每張圖片都被標記為10類中的某一類(比如:飛機,電動車,鳥等)。這60,000張圖片被分成50,000張訓練集和10,000張測試集。下圖你可以看到這10類中隨機的10個例子照片。
假設我們現在有CIFAR-10測試集中50,000張圖片(包括它們的標記),我們希望標記剩下的10,000張圖片。最近鄰分類器將接收一張測試圖片,把它與測試集中的每一張照片比較,然后用最相似照片的標記作為該照片的標記。在上面的圖片右邊我們可以看出這種方法的10張測試圖。我們可以看出,在10張照片中,只有3張和最左邊是同一類,其余的七張不是。比如,在第八行中,馬頭的最近鄰圖片是一輛紅色的車,可能是因為很強的黑色背景。結果導致這匹馬被誤標記為車。
你可能已經發現,我們還沒有講出如何比較這兩幅圖的具體的公式,在這個例子中,即如何比較兩個32x32x3的矩陣。一個最簡單的方法是比較兩幅圖像的每一個像素,然后將所有的差異加起來。也就是說,給了兩張照片,給我兩張照片,把它們表示為I1 和I2
, 一個很簡單的比較方法便是L1距離
其中,這個總和包括了所有的像素差異,下面以圖表的方式表達:
讓我們看一下如何在代碼中實現這個分類器。首先我們下載CIFAR-10的數據,把它以四個數組的形式載入內存:訓練數據,訓練標簽,測試數據和測試標簽。在下面的代碼中, Xtr(大小為50,000x32x32x3)為訓練圖片的數據,對應的以為數組Ytr(長度為50,000)為訓練的標簽(從0到9):
Xtr, Ytr, Xte, Yte = load_CIFAR10('data/cifar10') #a magic function
#flatten out all images to be one-dimensional
Xtr_rows = Xtr.reshape(Xtr.shape[0], 32*32*3)
Xte_rows = Xte.reshaoe(Xte.reshape[0], 32*32*3)
現在我們將所有的圖片都延展到一行中,下面是我們訓練和檢驗分類器的方法:
nn = NearestNeighbor() #create a Nearest Neighbor classifier classifier
nn.train(Xtr_rows, Ytr) #train the classifier on the training images
Yte_predict = nn.predict(Xte_rows) #predict labels on the test images
#and now print the classification accuracy, which is the average num
#of examples that are correctly predicted(i.e. label matches)
print 'accuracy: %f' %(np.mean(Yte_predict == Yte))
通常我們使用準確率(預測準確的比例)作為評價的標準,我們建立的所有分類器滿足一個共有的API: 它們有一個train函數,以數據和標簽作為學習的素材。在內部, 這個方法應該能夠建立某種關于標簽及如何預測數據的模型。然后,有一個predict函數,它接收一個新的數據,然后預測它的標簽。當然,我們至今還沒談到最重要的部分,即分類器本身。下面是一個簡單的基于L1距離的最近鄰分類器的一個實現
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
# 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 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%)的表現差很多。
距離的選擇
還有很多其他的方法來計算向量之間的距離,另外一個經常使用的選擇是L2距離 從幾何的角度,可以將其解釋為計算兩個向量的歐式距離(euclidean distance)。 公式如下:
也就是說,我們像以前一樣,還是計算像素之間的差異,但是這一次,我們都求差異的平方,然后把它們相加,最后求開方。使用Python的numpy模塊,使用上面的代碼,我們只需要替換上面一行代碼,也就是計算距離的那段代碼:
distances = np.sqrt(np.sum(np.square(self.Xtr - X[i,:]), axis = 1))
我們可以看到我使用了np.sqrt函數,但是在實踐中,我們可能不使用開方操作,因為開方函數是一個單調函數,即它只縮小了距離的大小,同時它保持了它們之間的順序。所有有沒有開方操作都一樣。如果你使用L2距離在CIFAR-10上運行這個程序,那么你的準確度可以達到35.4%(比L1距離稍低)。
L1 vs. L2
考慮兩個矩陣之間的差異是個很有趣的問題。具體來說,當測量兩個矩陣之間的差異,相比于L1距離,L2距離的容忍性更低。也就是說,L2距離更喜歡很多中等程度的差異,而不是一個非常大的差異。L1距離和L2距離(一對圖片差異的L1/L2范式)是P范式中最經常使用的兩個特例。
K-近鄰分類器
你可能已經注意到當我們做預測時,只使用最近鄰圖片的標記是非常奇怪的。確實,當我們使用k近鄰分類器時,往往能夠得到更好的結果。這個想法很簡單: 不是在測試集中尋找最接近的那張圖片,我們會尋找最近鄰的k張圖片,然后從這k張照片中找出最適合的標記。當k=1時,就變成最近鄰分類器。K的值越高,會使得分類器對異常值(outliers)更有抵抗力。
在實踐中,你幾乎總是需要使用k近鄰。但是我們應該如何決定k的值呢?我們下面來關注這個問題。
用于超參數微調(Hyperparameter tuning)的驗證集(validation sets)
k近鄰分類器需要設定k值。那么設置哪個數字效果更好呢?除此之外,我們看到有很多距離函數可以使用: L1范式 和L2范式,但是也還有很多其他選擇我們沒有考慮(比如:點積)。這些選擇稱之為超參數,在設計機器學習算法中它們經常出現,通常它們的值都不是非常容易確定。
我們可能會被建議嘗試許多值,然后在其中選擇最好的。這是個好方法,也是我們接下來使用的方法,但是我們必須要非常謹慎。特別是,我們不能使用測試集來微調超參數 當你設計機器學習算法時,你都應該將測試集視為一種珍貴的資源,只有到最后面才能使用。否則,你可能調試你的超參數,使得它們在測試集上有很好的表現,但是當你發布你的模型時,你可能發現你的模型效果會大大降低。在實踐中,我們會將這種現象稱之為過擬合(overfit)。另外一個看待這個問題的方式是如果你基于測試集調整了你的參數,那么你就把測試集當成了訓練集。那么你就會對你模型的效果過度樂觀。如果你只在最后使用測試集,那么它就能很好的測量你分類器的可擴展性(generalization)(在接下來的課程中你會看到對可擴展性更過的討論)
只能在最后評估你的測試集一次
幸運的是,有一種正確的方法來調整超參數而且不會使用到測試集。這個想法是將測試集一分為二:一個稍微小的測試集,我們稱之為驗證集,其余部分作為真正的訓練集。使用CIFAR-10 為例子,我們使用49,000的測試圖片作為訓練集,1,000作為驗證集。驗證集可以作為一個偽測試集,來調整超參數。
在CIFAR-10中,可能會是這樣:
# 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 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取哪個值才是最好的。之后會使用這個值,并在真正的測試集中評估一次。
把你的測試集分成測試集和驗證集。使用驗證集來調整所有的超參數。最后,在測試集中測試,并報告結果。
交叉驗證
在訓練集很小的情況下, 人們通常會使用一個更成熟的技術來調節超參數,這種技術稱之為交叉驗證。 以我們之前的例子為例,不是取前1000個點作為驗證集,剩下的作為訓練集,而是使用一種更好的,噪音更少的評估K的方式,這種方式通過遍歷不同的驗證集,然后去這些驗證集的平均值。比如:在5重交叉驗證(5-fold cross-validation)中,我們將測試集分成五等分,使用其中的四份作為測試集,1份作為驗證集。我們分別將其中一份作為驗證集,評估其效果,然后使用平均值作為最終的結果。
實踐
在實踐中,人們傾向于避免交叉驗證,而喜歡一次驗證分割,因為交叉驗證計算量很大。人們一般使用訓練集的50%~90%作為真正的訓練集,剩下的作為驗證集。然而,這也取決于多個因素:比如如果超參數的數量很多,你可能傾向于使用更大的驗證集。如果驗證集的數目太少(只有幾百個),那么使用交叉驗證比較安全。通常的交叉驗證為3重,5重,10重交叉驗證。
最近鄰分類器的優點和缺點
現在來考慮下最近鄰分類器的優點和缺點。很顯然,一個優點是它非常簡單而且很容易理解。除此之外,這個分類器不需要訓練,我們需要做的就是存儲和索引訓練數據。所以。我們計算的花費主要在測試時間上,因為分類一個測試集需要將其與每一個訓練集進行比較。這是不符合常理的(backwards),因為在實踐中,相比于訓練時間,我們經常更關心的是測試的時間效率。實際上,我們接下來開發的深度神經網絡會交換這個計算成本分配,即它們訓練的成本非常高,但是一旦訓練完成,就很容易測試。這種模式在實踐中更可行。
除此之外,最近鄰分類器的計算復雜度也是一個活躍的研究領域。一些近似最近鄰算法(Approximate Nearest Neighbor ANN)可以加速在數據集中對最近鄰的查找(比如:FLANN)。這些算法可以在最近鄰的正確性和空間/時間復雜度上做出一個權衡,通常依賴于一個預處理/索引階段 即建立一個kdtree或運行k-means算法。
最近鄰分類器有時候是一個好的選擇(特別是當數據是低維數據的時候),但是它很少適合運用在圖像分類情境中。其中一個很重要的原因為圖像是高維物體(比如,它一般包含很多像素),高維空間的距離可能是違反直覺的。下面的圖片表明這個觀點: 逐像素L2相似和直覺的相似非常不同:
下面以更可視化的形式向你展示 僅僅使用像素差異比較圖像是遠遠不夠的。我們使用了一個可視化技術(t-SNE)來接收一個CIFAR-10的圖片,并把它們排列在二維空間中以便它們的距離能夠最好的保持。在下圖中,近鄰的圖片是在L2距離非常相近
特別的,臨近的圖片更多的是因為圖像的整體顏色分布,或者背景的類似而不是它們的語義相同。比如,一條狗可能被認為和一只青蛙很相似,因為它們都是在白色背景中。我們希望所有10個類的圖像能夠形成它們各自的聚類,同一個類的物體能夠在各自的附近,而不受不重要的特征或變量的影響(比如背景)。然而,為了獲得這種特質,我們需要做的往往比僅僅比較像素值更多。
總結:
我們引出了圖像分類的問題,即給出一序列的圖片,每張圖片都有一個標記,我們需要預測一個新的測試集中圖片的標記,然后測量預測的準確性。
我們介紹了一個簡單的分類器,最近鄰分類器。我們發現這種分類器有多個超參數(比如k的值,或者距離函數的類型),并且沒有一個簡單的方法決定它們的值
我們發現最簡單的設置這些超參數方式是將測試數據一分為二,一個作為真正的測試數據,一個作為偽測試數據,稱之為驗證數據。我們嘗試不同的超參數,并在驗證集里保持有最好效果的值。
如果訓練數據量不是很多,我們討論了一種方法叫做交叉校驗,它可以在估計超參數的時候幫助減少噪音。
一旦最好的超參數找到了,我們最后在真正的測試集上測試。
我們發現最近鄰可以在CIFAR-10得到得到大約40%的準確度。它很容易實現,但是需要我們存儲整個訓練集,而且在測試階段花銷很大。
最后,我們看到在圖像的像素值上使用L1或L2距離是不夠的,因為距離與背景,圖像的顏色分布更相關,而不是和語義內容。
在下一節課中,我們開始解決這些挑戰,并最終達到約90%的準確度,并允許在我們訓練完成后,可以完全拋棄測試集,并使得我們能夠在少于1毫秒的時間內評價一張測試圖片。