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的情况!