svm 損失函式以及其梯度推導
一般而言,score_matrix=WX
W是係數矩陣,X是data_matrix,這兒是學習cs231n的筆記,為了與其程式碼內w,x的含義保持一致,
以下統一使用XW來計算score_matrix。背景是用svm實現圖片分類,輸入引數如下:
N 代表樣品個數,D 代表畫素個數,C代表一共的種類數。
X=(N, D)
注:如果原輸入為(500,32,32,3)[即有500個樣品(圖片),每個影象32行,32列,並有3個color chanel],那麼應該進行預處理,轉變為(500,32*32*3)的結構。
W=(D, C)
損失函式
損失函式的計算方法為:
其中,常用的數學表示式為:,但為了與程式碼中的統一,從而稍微變動以下,對於y_i來說同理。
具體的例子如下:
w cat duck frog |p1 0.1 0.2 0.2 n_i=第i個樣品 |p2 0.2 0.3 0.1 p=pixel |p3 0.5 0.1 0.1 ------------------------- x | score p1 p2 p3| cat duck frog n1 10 14 10| 8.8 [7.2] 4.4 -->第一個樣品得分 n2 5 10 8| 6.5 4.8 2.8 -->第二個樣品 n3 10 5 5| 4.5 4.0 3.0 -->第三個樣品 [7.2]代表第一個樣品的真實類別為duck,分數是7.2 那麼按照損失函式的計算方法: L_i=max(0,8.8-7.2+1)+max(0,4.4-7.2+1) =1.8+0=1.8
梯度推導
現在需要計算梯度,因為最開始的w是隨機生成的很小的值(注意,這樣做是有原因的,因為若w都大致為0,那麼第一步計算出的score矩陣的每一個元素也約等於0,因此按照損失函式的計算方法,每個i樣品的L_i=1*(N-1),即最後的平均損失函式為N-1,這樣可以作為debug的依據),我們需要知道讓w怎樣變化才能讓損失最少,
以數值分析為例:
對於第一個樣品,我們想知道若w在cat種類上的數增加一點點,損失會改變多少
w cat duck frog |p1 0.1+0.01 0.2 0.2 n_i=第i個樣品 |p2 0.2 0.3 0.1 p=pixel |p3 0.5 0.1 0.1 ------------------------- x | score p1 p2 p3| cat duck frog n1 10 14 10| 8.8+0.1 [7.2] 4.4 -->第一個樣品得分 n2 5 10 8| 6.5 4.8 2.8 -->第二個樣品 n3 10 5 5| 4.5 4.0 3.0 -->第三個樣品
因此,按照數值的分析方法,L_1對cat種類的偏導數為: ==>
但這樣計算會很慢,因此我們藉助於微分公式,可以方便用分析的方式計算出L_i對各個種類的偏導數,也就是計算梯度。
又因為L_i如下,注意,這兒w的下標為實際含義,而不是行、列
因此
最後,上面的梯度矩陣就變成了(注意,若j=yi時為負)
程式碼表示如下
方式一:Non-vectorized implementation
dW = np.zeros(W.shape) # initialize the gradient as zero
# compute the loss and the gradient
...
for i in xrange(num_train):
...
for j in xrange(num_classes):
...
if margin > 0:
...
dW[:,y[i]] -= X[i,:]
dW[:,j] += X[i,:]
方式二:Vectorized implementation
從方式一中不難發現,對於每一個樣品而言,最後的梯度就是X的轉置,只不過若分類正確,乘以0,分類錯誤時,為當前類別乘以-1,否則乘以1。對於第二種方式而言,可以理解為一次性算出梯度。因為最後的梯度結果即為X的轉置進行幾次加幾次減的操作,方式二的核心在於如何得到分類錯誤的矩陣描述。
損失函式進行二值化處理,即可得到分類錯誤的情況,但分類錯誤時有兩種操作:yi=j時,係數需要為-1,否則為1,因此需要一點小技巧。
下面具體舉例說明:
#假設scores為我們得到的分數,scores=(N,C)
scores=np.array([[1, 2, 3],[4, 5, 6],[7, 8, 9]])
#y表示每一個樣本真正的類別
y=np.array([2,1,1])
#這兒選出每行,對應的y的值
#https://mlxai.github.io/2017/01/06/vectorized-implementation-of-svm-loss-and-#
yi_scores = scores[np.arange(scores.shape[0]),y]
# yi_scores=>array([3, 5, 8])
#計算邊界函式
margins = np.maximum(0, scores - np.matrix(yi_scores).T + 1)
"""
matrix([[0, 0, 1],
[0, 1, 2],
[0, 1, 2]])
"""
#這兒就是一個小技巧,因為若j=yi時,係數是需要為-1的
margins[np.arange(3),y] = 0
"""
matrix([[0, 0, 0],
[0, 0, 2],
[0, 0, 2]])
"""
loss = np.mean(np.sum(margins, axis=1))
binary = margins
#二值化處理
binary[margins > 0] = 1
"""
matrix([[0, 0, 0],
[0, 0, 1],
[0, 0, 1]])
"""
#計算每個樣本分類錯誤的個數
row_sum = np.sum(binary, axis=1)
"""
matrix([[0],
[1],
[1]])
"""
#這兒的技巧同上面的技巧結合起來,就可以實現分類錯誤時,係數可以根據yi是否等於j
#進行梯度-(X轉置),或梯度+(X轉置)的操作
binary[np.arange(3), y] = -row_sum.T
"""
matrix([[ 0, 0, 0],
[ 0, -1, 1],
[ 0, -1, 1]])
"""
#相當於一次性做完方式一的迴圈操作
dW = np.dot(X.T, binary)