SVM梯度求導及實現
- 2019 年 10 月 5 日
- 筆記

SVM梯度求導及實現
0.說在前面1.梯度推導2.實現3.作者的話
0.說在前面
昨晚看了一部電影,叫做我是馬布里,非常正能量,推薦給各位,看完這部電影的總結話是:

冠軍與非冠軍的區別在於你一直並沒有將兩者進行明確界定,只是模糊了兩者的邊緣,我們不是適應邊緣化的人,而是打破邊緣化的創造者!
今天重點來推導SVM梯度及程式碼實現,下面一起來實戰吧!
1.梯度推導
loss fuction
SVM損失函數想要SVM在正確分類上的得分始終比不正確的得分高出一個邊界值!
下面來進行梯度下降的推導!
loss vector
數學推導如下:
X表示如下(1式):

W表示如下(2式):

S表示如下(3式):

X的shape為NxD,W的shape為DxC,S的shape為NxC,
X*W=S f(S)=L # f表示loss function
這裡使用鏈式求導法對w進行求導(4式):

由上面知道X的shape為NxD,由於L對S求導的shape為NxC,而NxD矩陣與NxC矩陣不能直接相乘,故要對X進行轉置!
對(4)內部進行求導拆分,如(5)式
s1隻跟L1有關,si只跟Lj有關,於是求和可以去掉,轉化為後面那個Lj對Sj求導!
(5式)

對Lj求和展開(6式)

將Lj式子帶入(5)式,我們知道,與Sj1相關的只有第一項,那麼就是1,當max函數算出的值大於0,對於Sjk(k不等於yj)的時候,求導為1,否則為0;而對於Sjyj的時候,也就是k等於yj的時候求導,當max函數算出的值大於0,求導為-1,否則為0,將所有的值相加就是最後的k=yj求導結果。
2.實現
loss native
def svm_loss_vectorized(W, X, y, reg): loss = 0.0 dW = np.zeros(W.shape) # initialize the gradient as zero # (N,D) # N num_train = X.shape[0] # (D,C) # C num_classes = W.shape[1] # (N,C) scores = X.dot(W) # 獲取每個樣本對應label的得分 # (N,1) y_score =scores[np.arange(num_train),y].reshape(-1,1) mask = (scores-y_score+1)>0 scores = (scores-y_score+1)*mask loss =(np.sum(scores)-num_train*1)/num_train loss += reg * np.sum(W * W) # 初始化ds, ds = np.ones_like(scores) # 有效的score梯度為1,無效的為0 ds*=mask # 去掉k=yj的情況 ds[np.arange(num_train),y]=-1*(np.sum(mask,axis=1)-1) # 對應公式(4) dW=np.dot(X.T,ds)/num_train dW+=2*reg*W return loss, dW
這裡的程式碼引自訓練營老師程式碼,這裡寫的很精闢,非常值得學習,特別是對於這個邏輯處理,下面來深入分析一下。
模擬實驗
首先是找到有效分數,也就是上述max()函數大於0與小於0的情況,這裡直接通過先判斷,這裡來模擬一下操作:
首先定義兩個二維數組,分別是x1與x2:x1表示上面公式推導的(3)式子,S矩陣,也就是通過X與W相乘後的矩陣!x2表示預測結果為真實label的yj。
下面一起來學習一下模擬操作以及老師在這裡的運算精髓!
In
x1 = np.array(np.arange(6)).reshape(3,2) x2 = np.array([2,3,3]).reshape(-1,1) x1 x2
Out
array([[0, 1], [2, 3], [4, 5]]) array([[2], [3], [3]])
In
mask = x1-x2+1>0
通過這個操作可以得到什麼?
Out
array([[False, False], [False, True], [ True, True]])
發現沒,得到了一個布爾類型的同shape多維數組,那麼是不是可以這樣說,思路就是首先通過得到有效無效分數的布爾高維數組,有效就是True,無效就是False,在運算的時候,直接可以將False看作0,True看作1。
那麼我們接下來計算真實的得分:
In
scores=(x1-x2+1)*mask scores
Out
array([[0, 0], [0, 1], [2, 3]])
這個就是我們期望通過下面公式得到的結果!

這裡實現的方法非常巧妙!!!
這樣做的好處是可以避免循環及使用max函數!!
上述的程式碼等價於:
In
for i in range(x1.shape[0]): for j in range(x1.shape[1]): x1[i][j]=max(0,x1[i][j]-x2[i][0]+1)
Out
array([[0, 0], [0, 1], [2, 3]])
注意點
由於上述公式(6)在計算得分時候不涉及yj也就是預測為真正label這一項,而在計算的時候,實際上把所有的都計算了,所以得減去這一項,對於每一行操作都多算了一個:

總共num_train行,所以是num_train*1個!
loss =(np.sum(scores)-num_train*1)/num_train
另外就是在計算ds的時候,這個ds就是上述(5)式,L對S求導,最終求導結論就是:
(1)當對Sjk求導時(k不等於yj),若為有效得分,則為1,否則為0;
(2)當對Sjk求導時(k等於yj)時,若為有效得分,則為多個-1,否則為0;
第(1)點主要說的是下面這個式子當中的Sjk,第(2)主要說的是下面這個式子中的Sjyj求導,因為是多個max函數累加,那麼對於Sjyj求導的話是每一個max都有一項,所以如果max得分是正數,則表示求導結果是-1,將多個求導的-1疊加就是最後對Sjyj的求導總和;而對於Sjk求導,雖然是多個max,以求Sj1為例,只有一個max中存在Sj1,所以總的求導要麼為1,要麼為0。

所以最後的L對S求導結果用程式碼實現就是:
# 有效的score梯度為1,無效的為0 ds*=mask # 去掉k=yj的情況 ds[np.arange(num_train),y]=-1*(np.sum(mask,axis=1)-1)
上面說了mask是將預測為yj的也算進去了,對於當碰到下面這個式子:

由於每一個mask中都多算了一個這個式子,而這個式子總是有效的!!!所以直接減去這次的結果就是去掉k等於yj的情況!