1. 程式人生 > 其它 >深度學習與CV教程(2) | 影象分類與機器學習基礎

深度學習與CV教程(2) | 影象分類與機器學習基礎


本系列為 斯坦福CS231n《深度學習與計算機視覺(Deep Learning for Computer Vision)》的全套學習筆記,對應的課程視訊可以在 這裡 檢視。更多資料獲取方式見文末。


引言

影象分類是計算機視覺的核心任務,計算機視覺領域中很多問題(比如 目標檢測語義分割),都可以關聯到影象分類問題。影象分類

問題,就是已有固定的分類標籤集合,然後對於輸入的影象,從分類標籤集合中找出一個分類標籤,最後把分類標籤分配給該輸入影象。在本篇內容彙總,ShowMeAI將給大家講解資料驅動的模型演算法,包括簡單的 KNN 模型線性分類模型

本篇重點

  • 資料驅動方法
  • KNN演算法
  • 線性分類

1.影象分類的挑戰

對於計算機而言,影象等同於一個畫素矩陣;而對人類,影象是包含豐富語義資訊的多媒體呈現,對應不同的物體類別,所以對計算機而言存在巨大的語義鴻溝。

比如,給計算機輸入如下小貓的圖片,計算機影象分類模型會讀取該圖片,並計算該圖片屬於集合 \(\{貓, 狗, 帽子, 杯子\}\) 中各個標籤的概率。但讀取的輸入影象資料是一個由數字組成的巨大的 \(3\)

維陣列。

在下圖中,貓的影象大小高 \(600\) 畫素,寬 \(800\) 畫素,有 \(3\) 個顏色通道(紅、綠和藍,簡稱RGB),因此它包含了 \(600 \times 800 \times 3=1440000\) 個數字,每個數字都是在範圍 \(0 \sim 255\) 之間的整型,其中 \(0\) 表示全黑,\(255\) 表示全白。

我們的任務就是把這些數字變成一個簡單的標籤,比如 「貓」 。

影象分類演算法要足夠健壯(魯棒,robust),我們希望它能夠適應下述變化及組合:

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

如下圖所示是一些變化和影象識別的挑戰:

2.資料驅動的方式

一種實現方式是「硬編碼」:先獲取貓影象的邊緣得到一些線條,然後定義規則比如三條線交叉是耳朵之類。然而這種方式的識別效果不好,並且不能識別新的物體。

我們會採用資料驅動演算法:不具體寫出識別每個物體對應的規則,而是針對每一類物體,找到大量樣例圖片,灌給計算機進行機器學習,歸納模式規律,生成一個分類器模型,總結出區分不同類物體的核心知識要素,然後用訓練好的模型,識別新的影象

資料驅動演算法過程如下:

  • 輸入:輸入是包含 \(N\) 個影象的集合,每個影象的標籤是 \(K\) 種分類標籤中的一種。這個集合稱為訓練集。
  • 學習:這一步的任務是使用訓練集來學習每個類的模式規律。一般該步驟叫做分類器訓練或者模型學習。
  • 評價:讓分類器對它未曾見過的影象進行分類,把分類器預測的標籤和影象真正的分類標籤 (基本事實) 對比,並以此來評價分類器的質量。

2.1 最鄰近演算法

本部分內容也可以參考ShowMeAI圖解機器學習教程 中的文章詳解 KNN演算法及其應用

我們這裡介紹第1個分類器演算法:最近鄰演算法。訓練過程只是簡單的記住影象資料和標籤,預測的時候和訓練資料中圖片比較找出最接近的輸出標籤。這個分類器和卷積神經網路沒有任何關係,實際中也極少使用,但通過實現它,可以對解決影象分類問題的方法有個基本認識。

1) 影象分類資料集:CIFAR-10

CIFAR-10 是一個非常流行的影象分類資料集。這個資料集包含 10 種分類標籤,60000 張 \(32 \times 32\) 的小影象,每張圖片含有一個標籤。這 60000 張影象被分為包含 50000 張(每種分類 5000 張)影象的訓練集和包含 10000 張影象的測試集。

假設現在我們用這 50000 張圖片作為訓練集,將餘下的 10000 作為測試集並打上標籤,Nearest Neighbor 演算法將會拿著測試圖片和訓練集中每一張圖片去比較,然後將它認為最相似的那個訓練集圖片的標籤賦給這張測試圖片。

結果如下圖所示,效果並不是特別好。

  • 左邊:CIFAR-10 資料庫的樣本影象;
  • 右邊:第一列是測試影象,後面是使用Nearest Neighbor演算法,根據畫素差異,從訓練集中選出的10張最類似的圖片

那麼具體如何比較兩張圖片呢?我們有一些距離度量計算方法,下面展開介紹一下。

2) L1 距離(曼哈頓距離)

距離度量的數學知識也可以參考ShowMeAI的系列教程 圖解AI數學基礎 中的文章 線性代數與矩陣論 對各種距離度量的展開講解

在本例中,就是比較 \(32 \times 32 \times 3\) 的畫素塊。最簡單的方法就是逐個畫素比較,最後將差異值全部加起來。即將兩張圖片先轉化為兩個向量 \(I_{1}\)\(I_{2}\),然後計算他們的 L1 距離:

\[d_{1} (I_{1} ,I_{2} )=\sum_{p}\vert I_{1}^p -I_{2}^p \vert \]
  • 其中 \(p\) 為畫素點,\(I^p\) 表示第 \(p\) 個畫素點的值。
  • 兩張圖片使用 L1 距離來進行比較,即逐個畫素求差值,然後將所有差值加起來得到一個數值。如果兩張圖片一模一樣,那麼 L1 距離為 \(0\);但是如果兩張圖片很是不同,那 L1 值將會非常大。

下圖是僅一個RGB通道的 \(4 \times 4\) 圖片計算 L1 距離。


下面看具體程式設計如何實現

① 首先,我們將 CIFAR-10 的資料載入到記憶體中,並分成4個數組:訓練資料和標籤,測試資料和標籤

Xtr, Ytr, Xte, Yte = load_CIFAR10('data/cifar10/') # 這個函式可以載入CIFAR10的資料
# Xtr是一個50000x32x32x3的陣列,一共50000個數據,
# 每條資料都是32行32列的陣列,陣列每個元素都是一個三維陣列,表示RGB。
# Xte是一個10000x32x32x3的陣列;
# Ytr是一個長度為50000的一維陣列,Yte是一個長度為10000的一維陣列。
Xtr_rows = Xtr.reshape(Xtr.shape[0], 32 * 32 * 3) 
# Xtr_rows是50000x3072的陣列,按每個畫素點排列,每個畫素點有三個值。
Xte_rows = Xte.reshape(Xte.shape[0], 32 * 32 * 3) 
# Xte_rows是10000x3072的陣列
''' shape會返回陣列的行和列數元組:(行數,列數),shape[0]表示行數, 
Xtr.shape[0]會返回50000;Xtr.shape會返回(50000,32,32,3)
Xtr.reshape(50000,3072)會將Xtr 重構成50000x3072陣列,等於 np.reshape(Xtr, (50000,3072))'''
  • Xtr(大小是50000x32x32x3)存有訓練集中所有的影象
  • Xte(大小是10000x3072)存有測試集中所有的影象
  • Ytr 是對應的長度為50000的1維陣列,存有影象對應的分類標籤(從0到9)
  • Yte 對應長度為10000的1維陣列

現在我們得到所有的影象資料,每張圖片對應一個長度為 3072 的行向量。

② 接下來訓練一個分類器並評估效果。我們常常使用準確率作為評價標準,它描述了我們預測正確的得分

本例中OK,很多其他應用中準確率並不一定是最佳的評估準則,可以參考ShowMeAI圖解機器學習教程 中的文章詳解 模型評估方法與準則

nn = NearestNeighbor() # 建立一個最鄰近分類器物件
nn.train(Xtr_rows, Ytr) # 用訓練圖片資料和標籤訓練分類器
Yte_predict = nn.predict(Xte_rows) # 預測測試圖片的標籤
# 並輸出預測準確率,是一個平均值
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 是 NxD 維的陣列,每一行都是一個樣本,比如一張圖片,D 是樣本的資料維度;
    Y 是長度為 N 的一維陣列。"""
    # 最鄰近分類器只是簡單的記住所有的訓練資料
    self.Xtr = X
    self.ytr = y

  def predict(self, X):
    """ X 是 NxD 維的陣列,每一行都是一個希望預測其標籤的樣本 """
    num_test = X.shape[0]
    # 確保輸出的標籤資料型別和輸入的標籤格式一致,長度是測試樣本數
    Ypred = np.zeros(num_test, dtype = self.ytr.dtype)

    # 迴圈所有測試樣本數,即測試陣列的行數
    for i in range(num_test):
      # 為第 i 張測試圖片找到最接近的訓練圖片
      # 使用 L1 距離 (差值的絕對值求和)
      '''self.Xtr - X[i,:] 利用傳播機制,求測試集第 i 張圖片對應的行向量和
      訓練集所有圖片行向量的差值,得到一個一個50000x3072的差值矩陣;
      abs(self.Xtr - X[i,:] )會將矩陣所有元素求絕對值;
      然後axis = 1 會對差值矩陣按行求和,最終得到一個長度為50000的一維
      陣列,存放第 i 張圖片和訓練集所有50000張圖片的 L1 距離。'''
      distances = np.sum(np.abs(self.Xtr - X[i,:]), axis = 1)
      min_index = np.argmin(distances) # 獲取距離最小的訓練集圖片索引
      Ypred[i] = self.ytr[min_index] # 預測第 i 張測試集圖片的標籤時與其最接近的訓練集圖片索引

    return Ypred

這段程式碼的訓練時間複雜度為 \(O(1)\),因為只是簡單的儲存資料,不管資料多大,都是一個相對固定的時間;如果訓練集有 \(N\) 個樣本,則預測時間複雜度為 \(O(N)\),因為測試圖片要和訓練集每張圖片進行比較。

這是一個不太好的分類器,實際對分類器的要求是,我們希望它預測的時候要快,訓練的時候可以慢

這段程式碼跑 CIFAR-10,準確率能達到 \(38.6\%\)。這比隨機猜測的 \(10\%\) 要好,但是比人類識別的水平和卷積神經網路能達到的 \(95\%\) 還是差很多。

3) L2距離(歐式距離)

距離度量的數學知識也可以參考ShowMeAI的系列教程圖解AI數學基礎中的文章線性代數與矩陣論對各種距離度量的展開講解

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

\[d_{2} (I_{1},I_{2})=\sqrt{\sum_{p}(I_{1}^p - I_{2}^p )^2 } \]
  • 依舊是在計算畫素間的差值,只是先求差值的平方,然後把這些平方全部加起來,最後對這個和開方。

此時的程式碼只需改動計算距離差異的一行:

distances = np.sqrt(np.sum(np.square(self.Xtr - X[i,:]), axis = 1))
'''np.square(self.Xtr - X[i,:]) 會對差值矩陣的每一個元素求平方'''

注意在這裡使用了 np.sqrt,但是在實際中可能不用。因為對不同距離的絕對值求平方根雖然改變了數值大小,但依然保持了不同距離大小的順序。這個模型,正確率是 \(35.4\%\),比剛才低了一點。

4) L1 和 L2比較

在 L1 距離更依賴於座標軸的選定,座標軸選擇不同 L1 距離也會跟著變化,判定的資料歸類的邊界會更趨向於貼近座標系的軸來分割所屬區域,而 L2 的話相對來說與座標系的關聯度沒那麼大,會形成一個圓,不跟隨座標軸變化。

在面對兩個向量之間的差異時,L2比 L1 更加不能容忍這些差異。也就是說,相對於1個巨大的差異,L2 距離更傾向於接受多箇中等程度的差異(因為會把差值平方)

L1 和 L2 都是在 p-norm 常用的特殊形式。

當影象中有特別在意的特徵時可以選擇 L1 距離;當對影象中所有元素未知時,L2距離會更自然一些。最好的方式是兩種距離都嘗試,然後找出最好的那一個。

2.2 k最近鄰分類器

本部分內容也可以參考ShowMeAI圖解機器學習教程中的文章詳解KNN演算法及其應用

只用最相似的 1 張圖片的標籤來作為測試影象的標籤,有時候會因為參照不夠多而效果不好,我們可以使用 k-Nearest Neighbor 分類器KNN的思想是:找最相似的 \(k\) 個圖片的標籤,\(k\) 中數量最多的標籤作為對測試圖片的預測

\(k=1\) 的時候,k-Nearest Neighbor 分類器就是上面所說的最鄰近分類器。

如下圖所示,例子使用了2維的點來表示圖片,分成3類(紅、綠、藍)。不同顏色區域代表的是使用 L2距離的分類器的決策邊界。

上面示例展示了NN分類器和KNN(\(k=5\))分類器的區別。從直觀感受上就可以看到,更高的 \(k\) 值可以讓分類的效果更平滑,使得分類器對於異常值更有抵抗力。

  • \(k=1\) 時,異常的資料點(比如:在藍色區域中的綠點)製造出一個不正確預測的孤島。
  • \(k=5\) 時分類器將這些不規則都平滑了,使得它針對測試資料的泛化(generalization)能力更好。
    • 注意,5-NN 中也存在一些白色區域,這些區域是因為5個近鄰標籤中的最高數相同導致的分類模糊(即影象與兩個以上的分類標籤繫結)。
    • 比如:2 個鄰居是紅色,2 個鄰居是藍色,還有 1 個是綠色,所以無法判定是紅色還是藍色。

1) 超引數調優

模型調優,超引數的實驗選擇方法也可以參考ShowMeAI的文章 圖解機器學習 | 模型評估方法與準則深度學習教程 | 網路優化:超引數調優、正則化、批歸一化和程式框架

  • KNN 分類器需要設定 \(k\) 值,如何選擇 \(k\) 值最合適
  • L1 距離和 L2 距離選哪個比較好(還是使用其他的距離度量準則例如點積)

所有這些選擇,被稱為超引數(hyperparameter)。在基於資料進行學習的機器學習演算法設計中,超引數是很常見的。

超引數是需要提前設定的,設定完成後模型才可以訓練學習,具體的設定方法通常要藉助於實驗,嘗試不同的值,根據效果表現進行選擇。

特別注意:不能使用測試集來進行調優

  • 如果使用測試集來調優,而且演算法看起來效果不錯,真正的危險在於:演算法實際部署後,效能可能會遠低於預期。這種情況,稱之為演算法對測試集過擬合。

  • 大家可以理解為,如果使用測試集來調優,實際上就是把測試集當做訓練集,由測試集訓練出來的演算法再預測測試集,效能自然會看起來很好,但實際部署起來效果就會差很多。

  • 最終測試的時候再使用測試集,可以很好地近似度量分類器的泛化效能。

測試資料集只能使用一次,而且是在訓練完成後評價最終模型時使用,不可用來調優

方法1:設定驗證集

從訓練集中取出一部分資料用來調優,稱之為 驗證集(validation set)。以 CIFAR-10 為例,可以用 49000 個影象作為訓練集,用 1000 個影象作為驗證集。驗證集其實就是作為假的測試集來調優。

程式碼如下:

# 假設 Xtr_rows, Ytr, Xte_rows, Yte 還是和之前一樣
# Xtr_rows 是 50,000 x 3072 的矩陣
Xval_rows = Xtr_rows[:1000, :] # 取前 1000 個訓練集樣本作為驗證集
Yval = Ytr[:1000]
Xtr_rows = Xtr_rows[1000:, :] # 剩下的 49,000 個作為訓練集
Ytr = Ytr[1000:]

# 找出在驗證集表現最好的超引數 k 
validation_accuracies = []
for k in [1, 3, 5, 10, 20, 50, 100]:
  # 使用一個明確的 k 值評估驗證集
  nn = NearestNeighbor()
  nn.train(Xtr_rows, Ytr)
  # 這裡假設一個修正過的 NearestNeighbor 類,可以把 k 值作為引數輸入
  Yval_predict = nn.predict(Xval_rows, k = k)
  acc = np.mean(Yval_predict == Yval)
  print 'accuracy: %f' % (acc,)

  # 把每個 k 值和相應的準確率儲存起來
  validation_accuracies.append((k, acc))

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

方法2:交叉驗證

訓練集數量較小(因此驗證集的數量更小)時,可以使用交叉驗證的方法。還是用剛才的例子,如果是交叉驗證集,我們就不是取 1000 個影象,而是將訓練集平均分成 5 份,每份 10000 張圖片,其中4份用來訓練,1份用來驗證。然後我們迴圈著取其中4份來訓練,其中1份來驗證,最後取所有5次驗證結果的平均值作為演算法驗證結果。

下面是 5 份交叉驗證對 \(k\) 值調優的例子。針對每個 \(k\) 值,得到 5 次驗證的準確率結果,取其平均值,然後對不同 \(k\) 值的平均表現畫線連線。

上圖可以看出,本例中,當 \(k=7\) 的時演算法表現最好(對應圖中的準確率峰值)。如果我們將訓練集分成更多份數,直線一般會更加平滑(噪音更少)。

實際情況下,深度學習不會使用交叉驗證,主要是因為它會耗費較多的計算資源。一般直接把訓練集按照 \(50\% \sim 90\%\) 的比例分成訓練集和驗證集。但是訓練集數量不多時可以使用交叉驗證,一般都是分成3、5和10份。

2) KNN分類器優點

① 易於理解,實現簡單。
② 演算法的訓練不需要花時間,因為其訓練過程只是將訓練集資料儲存起來。

3) KNN分類器缺點

① 測試要花費大量時間

  • 因為每個測試影象需要和所有儲存的訓練影象進行比較在實際應用中,關注測試效率遠遠高於訓練效率;

② 使用畫素差異來比較影象是不夠的,圖片間 L2 距離小,更多的是被背景主導而不是圖片語義內容本身主導,往往背景相似圖片的 L2 距離就會小

  • 也就是說,在高維度資料上,基於畫素的相似和基於感官上的相似非常不同。感官上不同的兩張圖片,可能有相同的 L2 距離。

③ 維度災難

  • KNN 有點像訓練資料把樣本空間分成幾塊,我們需要訓練資料密集的分佈在樣本空間裡,否則測試圖片的最鄰近點可能實際距離會非常遠,導致和最接近的訓練集樣本實際上完全不同。但是如果使訓練資料密集分佈,需要的訓練集數量指數倍增加,是資料維度的平方。

4) 實際應用KNN

下面是一些對於實際應用 KNN 演算法的建議

① 預處理資料

  • 對資料中的特徵進行歸一化(normalize),讓其具有零均值(zero mean)和單位方差(unit variance)。本小節不討論,是因為影象中的畫素都是同質的,不會表現出較大的差異分佈,不需要標準化處理。

② 降維

  • 如果資料是高維資料,考慮使用降維方法,比如 PCA 或者隨機投影。

③ 將資料隨機分入訓練集和驗證集

  • 一般規律,\(70\% \sim 90\%\) 資料作為訓練集。這個比例根據演算法中有多少超引數,以及這些超引數對於演算法的預期影響來決定。
  • 如果需要預測的超引數很多,那麼就應該使用更大的驗證集來有效地估計它們;如果擔心驗證集數量不夠,那麼就嘗試交叉驗證方法;如果計算資源足夠,使用交叉驗證更好(份數越多,效果越好,也更耗費計算資源)。

④ 在驗證集上調優

  • 嘗試足夠多的 \(k\) 值,嘗試 L1 和 L2 兩種範數計算方式。

⑤ 加速分類器

  • 如果分類器跑得太慢,嘗試使用ANN庫(比如 FLANN 來加速這個過程,其代價是降低一些準確率。

⑥ 對最優的超引數做記錄

  • 記錄最優引數後,不要使用最優引數的演算法在完整的訓練集上執行並再次訓練,這樣做會破壞對於最優引數的估計。
  • 直接使用測試集來測試用最優引數設定好的最優模型,得到測試集資料的分類準確率,並以此作為你的KNN分類器在該資料上的效能表現。

3.線性分類:評分函式

3.1 線性分類概述

KNN 模型中訓練過程中沒有使用任何引數,只是單純的把訓練資料儲存起來(引數 k 是在預測中使用的,找出 \(k\) 個接近的圖片,然後找出標籤最多的,並且 \(k\) 是超引數,是人為設定的)。

與之相對的是引數模型,引數模型往往會在訓練完成後得到一組引數,之後就可以完全扔掉訓練資料,預測的時候只需和這組引數做某種運算,即可根據運算結果做出判斷。線性分類器是引數模型裡最簡單的一種,但卻是神經網路裡很重要的基礎模組。

線性分類的方法由兩部分組成:

① 評分函式(score function)

  • 它是原始影象資料到類別分值的對映。

② 損失函式(loss function)

  • 它用來量化評分函式計算的分數與真實標籤之間的一致性。該方法可轉化為一個最優化問題,在最優化過程中,通過更新評分函式的引數來最小化損失函式值。

3.2 評分函式

評分函式將影象的畫素值對映為各個分類類別的得分,得分高低代表影象屬於該類別的可能性高低。上面的所有說明都比較抽象,下面以具體的例子說明。

重新回到 KNN 使用的 CIFAR-10 影象分類資料集。

假設我們的訓練集有 \(N\) 個樣本,這裡 \(N=50000\),每個樣本 \(x_{i}b \in R^D\),其中 \(i = 1,2,\cdots,N\)\(D=3072\);每個 \(x_{i}\) 對應著一個標籤 \(y_{i}\),$ y_{i}$ 在 \([1, K]\) 上取值,\(K\) 表示總分類數,這裡 \(K=10\)。現在可以定義評分函式: \(f:R^D \rightarrow R^K\),即把一個 \(D\) 維的影象對映為 \(K\) 個類別的分數。

最簡單的模型是線性模型:引數和輸入資料相乘。即:

\[f(x_{i},W,b)=Wx_{i}+b \]
  • 上式中引數 \(W\) 被稱為權重\(b\) 被稱為偏置項
  • 在上面的公式中,假設每個影象資料都被拉長為一個長度為 \(D\) 的列向量,大小為 \([D \times 1]\)。其中大小為 \([K \times D]\) 的矩陣 \(W\) 和大小為 \([K \times 1]\) 的列向量 \(b\) 為該函式的引數(parameters)

還是以 CIFAR-10 為例,\(x_{i}\) 就包含了第 \(i\) 個影象的所有畫素資訊,這些資訊被拉成為一個 \([3072 \times 1]\) 的列向量,\(W\) 大小為 \([10 \times 3072]\)\(b\) 的大小為 \([10 \times 1]\)。因此,輸入 \(3072\) 個數字(原始畫素數值),函式輸出 \(10\) 個數字(不同分類得到的分值),是一個 \(3072\) 維到 \(10\) 維的對映。

注意:

  • 常常混用權重(weights)和引數(parameters)這兩個術語,實際上資料和引數相乘,就相當於資料佔的比重,這個權重就是引數值;
  • 該方法的一個優勢是訓練資料是用來學習引數 \(W\)\(b\) 的,一旦訓練完成,訓練資料就可以丟棄,留下學習到的引數即可。當測試影象時可以簡單地把影象資料輸入給函式,函式計算出的分類分值來進行分類;
  • 輸入資料 \((x_{i},y_{i})\) 是給定且不可改變的,但引數 \(W\)\(b\) 是可改變的。目標就是通過改變這些引數,使得計算出來的分類分值情況和訓練集中影象資料的真實類別標籤相符;
  • 只需一個矩陣乘法和一個矩陣加法就能對一個測試資料分類,這比 KNN 中將測試影象和所有訓練資料做比較的方法要高效很多。

3.3 理解線性分類器

1) 理解一:W是所有分類器的組合

如上圖所示,將小貓的影象畫素資料拉伸成一個列向量 \(x_i\),這裡為方便說明,假設影象只有4個畫素(都是黑白畫素,不考慮RGB通道),即 \(D=4\);有 \(3\) 個分類(紅色代表貓,綠色代表狗,藍色代表船,顏色僅代表不同類別,和 RGB 通道沒有關係),即 \(K=3\)\(W\) 矩陣乘列向量 \(x_i\),得到各個分類的分值。

實際上,我們可以看到,引數矩陣 \(W\) 相當於是三個分類器的組合,\(W\) 的每一行都是一個分類器,分別對應貓、狗、船。線上性模型中每個分類器的引數個數與輸入影象的維度相當,每個畫素和對應的引數相乘,就表示該畫素在該分類器中應占的比重。

需要注意的是,這個 \(W\) 一點也不好:貓分類的分值非常低。從上圖來看,演算法倒是覺得這個影象是一隻狗。

我們可以這樣理解,線性分類器會計算影象中 3 個顏色通道中所有畫素的值與權重矩陣的乘積,進而得到每個類別分值。根據我們對權重設定的值,對於影象中的某些位置的某些顏色,函式表現出喜好或者厭惡(根據每個權重的符號而定)。

舉例:可以想象 「船」 分類就是被大量的藍色所包圍(對應的就是水)。那麼 「船」 分類器在藍色通道上的權重就有很多的正權重(它們的出現提高了 「船」 分類的分值),而在綠色和紅色通道上的權重為負的就比較多(它們的出現降低了 「船」 分類的分值)。

結合上面的小貓示例,貓分類器對第二個位置的畫素比較 「厭惡」 ,而恰好輸入的小貓影象第二個位置畫素值很大,最終計算得到一個很低的分數(當然,這個分類器是錯誤的)。

2) 理解二:將線性分類器看做模板匹配

把權重 \(W\) 的每一行看作一個分類的模板,一張影象對應不同分類的得分,是通過使用內積(也叫點積)來比較影象和模板,然後找到和哪個模板最相似

這種理解角度下,線性分類器在利用學習到的模板,和輸入影象做模板匹配。我們設定可以把其視作一種高效的KNN,不同的是不再使用所有的訓練集的影象來比較,而是每個類別只用了一張圖片來表徵(這張圖片是我們學習到的模板,而不存在訓練集中),而且我們會更換度量標準,使用(負)內積來計算向量間的距離,而不是使用 L1 或者 L2 距離。

上圖是以 CIFAR-10 為訓練集,學習結束後的權重的例子。可以看到:

  • 馬的模板看起來似乎是兩個頭的馬,這是因為訓練集中的馬的影象中馬頭朝向各有左右造成的。線性分類器將這兩種情況融合到一起了;
  • 汽車的模板看起來也是將幾個不同的模型融合到了一個模板中,這個模板上的車是紅色的,是因為 CIFAR-10 中訓練集的車大多是紅色的。線性分類器對於不同顏色的車的分類能力是很弱的,但是後面可以看到神經網路是可以完成這一任務的;
  • 船的模板如期望的那樣有很多藍色畫素。如果影象是一艘船行駛在大海上,那麼這個模板利用內積計算影象將給出很高的分數。

3) 理解三:將影象看做高維空間的點

既然定義每個分類類別的分值是權重和影象的矩陣乘積,那麼每個分類類別的分數就是這個空間中的一個線性函式的函式值。我們沒辦法視覺化 \(3072\) 維空間中的線性函式,但假設把這些維度擠壓到二維,那麼就可以看看這些分類器在做什麼了:

在上圖中,每張輸入圖片是一個點,不同顏色的線代表 3 個不同的分類器。以紅色的汽車分類器為例,紅線表示空間中汽車分類分數為 \(0\) 的點的集合,紅色的箭頭表示分值上升的方向。所有紅線右邊的點的分數值均為正,且線性升高。紅線左邊的點分值為負,且線性降低。

從上面可以看到,\(W\) 的每一行都是一個分類類別的分類器。對於這些數字的幾何解釋是:

  • 如果改變 \(W\) 一行的數字取值,會看見分類器在空間中對應的直線開始向著不同方向旋轉。而偏置項 \(b\),則允許分類器對應的直線平移
  • 需要注意的是,如果沒有偏置項,無論權重如何,在 \(x_{i}=0\) 時分類分值始終為 \(0\)。這樣所有分類器的線都不得不穿過原點

3.4 偏置項和權重合並

上面的推導過程大家可以看到:實際我們有權重引數 \(W\) 和偏置項引數 \(b\)兩個引數,分開處理比較冗餘,常用的優化方法是把兩個引數放到同一個矩陣中,同時列向量 \(x_{i}\) 就要增加一個維度,這個維度的數值是常量 \(1\),這就是預設的偏置項維度

如下圖所示,新的公式就簡化成如下形式:

\[f(x_{i},W,b)=Wx_{i} \]

還是以 CIFAR-10 為例,那麼 \(x_{i}\) 的大小就變成 \([3073 \times 1]\),而不是 \([3072 \times 1]\) 了,多出了包含常量1的1個維度; \(W\) 大小就是 \([10 \times 3073]\) 了,\(W\) 中多出來的這一列對應的就是偏差值 \(b\)

經過這樣的處理,最終只需學習一個權重矩陣,無需學習兩個分別裝著權重和偏差的矩陣。

3.5 影象資料預處理

在上面的例子中,所有影象都是使用的原始畫素值(\(0 \sim 255\))。在機器學習中,我們經常會對輸入的特徵做歸一化(normalization)處理,對應到影象分類的例子中,影象上的每個畫素可以看做一個特徵。

在實踐中,我們會有對每個特徵減去平均值來中心化資料這樣一個步驟。

在這些圖片的例子中,該步驟是根據訓練集中所有的影象計算出一個平均影象值,然後每個影象都減去這個平均值,這樣影象的畫素值就大約分佈在 \([-127, 127]\) 之間了。

後續可以操作的步驟包括歸一化,即讓所有數值分佈的區間變為 \([-1, 1]\)

3.6 線性分類器失效的情形

線性分類器的分類能力實際是有限的,例如上圖中的這三種情形都無法找到合適的直線區分開。其中第 1 個 case 是奇偶分類,第 3 個 case 是有多個模型。

4.拓展學習

可以點選 B站 檢視視訊的【雙語字幕】版本

5.要點總結

  • 影象分類中的困難與挑戰
  • 資料驅動方法、最鄰近演算法、 L1 和 L2 距離
  • KNN分類器、超引數調優、KNN的優缺點與實際應用
  • 線性分類的概念、評分函式的理解、引數合併、資料預處理、線性分類器侷限性

斯坦福 CS231n 全套解讀

ShowMeAI 系列教程推薦