【深度學習CV】SVM, Softmax損失函數

Deep learning在計算機視覺方面具有廣泛的應用,包括圖像分類、目標識別、語義分隔、生成圖像描述等各個方面。本系列博客將分享自己在這些方面的學習和認識,如有問題,歡迎交流。

在使用卷積神經網絡進行分類任務時,往往使用以下幾類損失函數:

  • 平方誤差損失
  • SVM損失
  • softmax損失

其中,平方誤差損失在分類問題中效果不佳,一般用於迴歸問題。softmax損失函數和SVM(多分類)損失函數在實際應用中非常廣泛。本文將對這兩種損失函數做簡單介紹,包括損失函數的計算、梯度的求解以及python中使用Numpy庫函數進行實現。

SVM多分類

1. 損失函數

一般而言,深度學習中使用的SVM損失函數是基於 Weston and Watkins 1999 (pdf)
其損失函數如下:

Li=jyimax(0,fjfyi+Δ)

在實際使用中,Δ 的值一般取1,代表間隔。

在神經網絡中,由於我們的評分函數是:

f=Wx

因此,可以將損失函數改寫如下:
Li=jyimax(0,WTjxiWTyixi+Δ)


如果考慮整個訓練集合上的平均損失,包括正則項,則公式如下:
L=1Nijyi[max(0,f(xi;W)jf(xi;W)yi+Δ)]+λklW2k,l

直觀理解:
多類SVM“想要”正確類別的分類分數比其他不正確分類類別的分數要高,而且至少高出delta的邊界值。如果其他分類分數進入了紅色的區域,甚至更高,那麼就開始計算損失。如果沒有這些情況,損失值爲0。我們的目標是找到一些權重,它們既能夠讓訓練集中的數據樣例滿足這些限制,也能讓總的損失值儘可能地低。
這裏寫圖片描述



舉一個具體的例子:

例子來源於 斯坦福CS231n 課件。第一張圖片是貓,神經網絡計算得出其三個類別的分值分別爲 3.2, 5.1 和 -1.7。很明顯,理想情況下貓的分值應該高與其他兩種類別,但根據計算結果,car的分值最高,因此在當前的權值設置下,該 network 會把這張圖片分類爲 car。此時我們可以根據公式計算損失

損失計算如下:(S代表Score,即分值)

Li=max(0,ScarScat+Δ)+max(0,SfrogScat+Δ)=2.9+0

這裏寫圖片描述


2. 梯度公式推導

設置以下變量:
- 矩陣 W 代表權值,維度是 DC ,其中 D 代表特徵的維度,C 代表類別數目。
- 矩陣 X 代表樣本集合,維度是 ND , 其中 N 代表樣本個數。
- 分值計算公式爲 f=XW ,其維度爲 NC , 每行代表一個樣本的不同類別的分值。

對於第 i 個樣本的損失函數計算如下:

Li=jyimax(0,WT:,jxi,:WT:,yixi,:+Δ)

偏導數計算如下:

LiW:,yi=(jyi1(wT:,jxi,:wT:,yixi,:+Δ>0))xi,:

LiW:,j=1(wT:,jxi,:wT:,yixi,:+Δ>0)xi,:

其中:
- w:,j 代表W矩陣第 j 列,其維度爲 D
- xi,: 代表X矩陣的第 i 行,表示樣本 i 的特徵,其維度也爲 D
二者相乘,得出的是樣本 i 在第 j 個類別上的得分。
- 1 代表示性函數。

3. python實現

包括向量化版本和非向量化版本:


def svm_loss_naive(W, X, y, reg):
    """
    # SVM 損失函數  native版本

    Inputs have dimension D, there are C classes, and we operate on minibatches
    of N examples.

    Inputs:
    - W: A numpy array of shape (D, C) containing weights.
    - X: A numpy array of shape (N, D) containing a minibatch of data.
    - y: A numpy array of shape (N,) containing training labels; y[i] = c means
    that X[i] has label c, where 0 <= c < C.
    - reg: (float) regularization strength

    Returns a tuple of:
    - loss as single float
    - gradient with respect to weights W; an array of same shape as W
    """
    dW = np.zeros(W.shape)    # initialize the gradient as zero

    # compute the loss and the gradient
    num_classes = W.shape[1]
    num_train = X.shape[0]
    loss = 0.0
    # 對於每一個樣本,累加loss
    for i in xrange(num_train):
        scores = X[i].dot(W)     # (1, C)
        correct_class_score = scores[y[i]]
        for j in xrange(num_classes):
            if j == y[i]:
                continue
            # 根據 SVM 損失函數計算
            margin = scores[j] - correct_class_score + 1    # note delta = 1
            # 當 margin>0 時,纔會有損失,此時也會有梯度的累加
            if margin > 0:      # max(0, yi - yc + 1)
                loss += margin
                 # 根據公式:∇Wyi Li = - xiT(∑j≠yi1(xiWj - xiWyi +1>0)) + 2λWyi
                dW[:, y[i]] += -X[i, :]   # y[i] 是正確的類
                #  根據公式: ∇Wj Li = xiT 1(xiWj - xiWyi +1>0) + 2λWj ,
                dW[:, j] += X[i, :]

    # 訓練數據平均損失
    loss /= num_train
    dW /= num_train

    # 正則損失
    loss += 0.5 * reg * np.sum(W * W)
    dW += reg * W

    #
    return loss, dW


#
def svm_loss_vectorized(W, X, y, reg):
    """
    SVM 損失函數   向量化版本
    Structured SVM loss function, vectorized implementation.Inputs and outputs
    are the same as svm_loss_naive.
    """
    loss = 0.0
    dW = np.zeros(W.shape)   # initialize the gradient as zero
    scores = X.dot(W)        # N by C  樣本數*類別數
    num_train = X.shape[0]
    num_classes = W.shape[1]

    scores_correct = scores[np.arange(num_train), y]
    scores_correct = np.reshape(scores_correct, (num_train, 1))  # N*1 每個樣本的正確類別

    margins = scores - scores_correct + 1.0     # N by C   計算scores矩陣中每一處的損失
    margins[np.arange(num_train), y] = 0.0      # 每個樣本的正確類別損失置0
    margins[margins <= 0] = 0.0                 # max(0, x)
    loss += np.sum(margins) / num_train         # 累加所有損失,取平均
    loss += 0.5 * reg * np.sum(W * W)           # 正則

    # compute the gradient
    margins[margins > 0] = 1.0                  # max(0, x)  大於0的梯度計爲1
    row_sum = np.sum(margins, axis=1)           # N*1  每個樣本累加
    margins[np.arange(num_train), y] = -row_sum  # 類正確的位置 = -梯度累加
    dW += np.dot(X.T, margins)/num_train + reg * W     # D by C
    return loss, dW



Softmax 損失函數

1. 損失函數


Softmax 函數是 Logistic 函數的推廣,用於多分類。

分值的計算公式不變:

f(xi;W)=Wx

損失函數使用交叉熵損失函數,第 i 個樣本的損失如下:

Li=log(efyijefj)

其中正確類別得分的概率可以被表示成:

P(yi|xi;W)=efyijefj

在實際使用中,efj 常常因爲指數太大而出現數值爆炸問題,兩個非常大的數相除會出現數值不穩定問題,因此我們需要在分子和分母中同時進行以下處理:

efyijefj=CefyiCjefj=efyi+logCjefj+logC

其中C 的設置是任意的,在實際變成中,往往把C 設置成:
logC=maxfj

即第 i 個樣本所有分值中最大的值,當現有分值減去該最大分值後結果0 ,放在 e 的指數上可以保證分子分佈都在0-1之內。

2. 梯度推導

梯度的推導如下:
這裏寫圖片描述

3. Python實現

def softmax_loss_naive(W, X, y, reg):
    """
    Softmax loss function, naive implementation (with loops)

    Inputs have dimension D, there are C classes, and we operate on minibatches
    of N examples.

    Inputs:
    - W: A numpy array of shape (D, C) containing weights.
    - X: A numpy array of shape (N, D) containing a minibatch of data.
    - y: A numpy array of shape (N,) containing training labels; y[i] = c means
      that X[i] has label c, where 0 <= c < C.
    - reg: (float) regularization strength

    Returns a tuple of:
    - loss as single float
    - gradient with respect to weights W; an array of same shape as W
    """
    # Initialize the loss and gradient to zero.

    loss = 0.0
    dW = np.zeros_like(W)
    dW_each = np.zeros_like(W)
    #
    num_train, dim = X.shape
    num_class = W.shape[1]
    f = X.dot(W)        # 樣本數*類別數   分值
    #
    f_max = np.reshape(np.max(f, axis=1), (num_train, 1))
    # 計算對數概率  prob.shape=N*10  每一行與一個樣本相對應  每一行的概率和爲1
    # 其中 f_max 是每行的最大值,exp(x)中x的值過大而出現數值不穩定問題
    prob = np.exp(f - f_max) / np.sum(np.exp(f - f_max), axis=1, keepdims=True)
    #
    y_trueClass = np.zeros_like(prob)
    y_trueClass[np.arange(num_train), y] = 1.0     # 每行只有正確的類別處爲1,其餘爲0
    #
    for i in range(num_train):
        for j in range(num_class):
            loss += -(y_trueClass[i, j] * np.log(prob[i, j]))
            dW_each[:, j] = -(y_trueClass[i, j] - prob[i, j]) * X[i, :]
        dW += dW_each
    loss /= num_train
    loss += 0.5 * reg * np.sum(W * W)
    dW /= num_train
    dW += reg * W

    return loss, dW


def softmax_loss_vectorized(W, X, y, reg):
    """
    Softmax loss function, vectorized version.
    Inputs and outputs are the same as softmax_loss_naive.
    """
    loss = 0.0
    dW = np.zeros_like(W)    # D by C
    num_train, dim = X.shape

    f = X.dot(W)    # N by C
    # Considering the Numeric Stability
    f_max = np.reshape(np.max(f, axis=1), (num_train, 1))   # N by 1
    prob = np.exp(f - f_max) / np.sum(np.exp(f - f_max), axis=1, keepdims=True)
    y_trueClass = np.zeros_like(prob)
    y_trueClass[range(num_train), y] = 1.0    # N by C

    # 計算損失  y_trueClass是N*C維度  np.log(prob)也是N*C的維度
    loss += -np.sum(y_trueClass * np.log(prob)) / num_train + 0.5 * reg * np.sum(W * W)

    # 計算損失  X.T = (D*N)  y_truclass-prob = (N*C)
    dW += -np.dot(X.T, y_trueClass - prob) / num_train + reg * W

    return loss, dW



Softmax、SVM損失函數用於CIFAR-10圖像分類

CIFAR-10 小圖分類是對於練習而言非常方便的一個數據集。通過在該數據集上實現基本的 softmax 損失函數 和 SVM 損失函數以及可視化部分結果,可以加深對算法的理解。

關於本文的全部代碼可以到GitHub中下載

下面給出代碼運行過程中的輸出結果:

1. 可視化CIFAR-10的部分樣本

這裏寫圖片描述

原始像素作爲特徵使用SVM分類的損失圖

這裏寫圖片描述

兩層神經網絡使用softmax分類的損失和準確率圖

這裏寫圖片描述

兩層神經網絡使用softmax分類的第一個隱含層權重圖:

這裏寫圖片描述

參考資料

[1] http://www.jianshu.com/p/004c99623104
[2] http://deeplearning.stanford.edu/wiki/index.php/Softmax%E5%9B%9E%E5%BD%92
[3] http://blog.csdn.net/acdreamers/article/details/44663305
[4] http://cs231n.github.io/

結束

歡迎郵件交流 [email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章