一般而言,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)
损失函数
损失函数的计算方法为:,其中i代表第i个样品,j代表第j个种类,那么y_i代表第i个样品的真实种类。
其中,常用的数学表达式为:,但为了与代码中的统一,从而稍微变动以下,对于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)