梯度求解学习小结1

问题

在机器学习中,设计合理、有效的目标函数是为人所津津乐道的技能(本小学生尚无此功力)。倘若设计出来的目标函数连自己都不会求解(优化)那就很尴尬了。像我这种小学生瞎鼓捣出来的目标函数,想知道究竟能不能work,不求解一下写成代码在数据集上跑一跑又如何知晓?

纵观求解方法,有贪心的,动态规划的,蒙特卡洛的,期望最大的,梯度的等等(小学生无责任乱分)。前途无限的深度学习其求解方法全都依赖于梯度,在可以预计的将来极有可能成为大一统的求解方法。因此,如何求解损失函数(这里的目标函数可以称作损失函数)的梯度成了小学生心中最关键的一环。本小节将把叙述重心放在微分部分,也一并给出python编码。

如何求解梯度建议先看看cs231n lecture 4 的ppt或者vidio(cs231n的其他学习资料在这里)。

一些表达上的技巧可以帮助我们后面更好地计算梯度.

梯度推导过程中的trick

逻辑判断

把一些分段函数化为便于求导的乘积形式,多少有点用吧。

二值选择

min(x2,ey)=1{x2<ey}x2+1{x2ey}ey
max(0,x3)=1{x30}x3

多值选择

Y7=i1{i=7}Yi
x1{x=2}f(x)=f(2)

分组讨论:

(2s13s2+es3)si={2,i=13,i=2es3,i=3=1{i=1}21{i=2}3+1{i=3}es3

逻辑运算

  1. 与运算

    1{x>5x<10}=1{x>5}1{x<10}
  2. 或运算
    1{x>5x<10}=1{x>5}+1{x<10}

打分向量与打分矩阵

对于分类问题,损失函数的输出是实数,输入是打分向量,该向量的分量为样本属于各类别的打分;而batch的平均损失的输入则是打分矩阵S(scores),如下图所示,这个batch中只有3个样本。

Markdown

实际中的例子

Multiclass SVM loss

L=Kkymax(0,sksy+1)

其中s为某样本属于各类别的打分,若是k分类,s为长度为K的数组,为了表达它是一个行向量,以下标记为sT, sk为该样本属于第j类的打分,y为真实类别,详情请参见该ppt

L/sj

把L中的二值选择用逻辑判断代替
L=Kmymax(0,smsy+1)=Kmy1{smsy+10}(sjsy+1)=Kmyqm,y(smsy+1)


其中qm,y=1{smsy+10}, 则
Lsj=sjmyqm,y(smsy+1)=myqm,y(smsy+1)sj=myqm,y(1{jym=j}1{j=y})=1{jy}myqm,y1{m=j}1{j=y}myqm,y=1{jy}qj,y1{j=y}myqm,y

注意: 上面逆用了多值选择的情况

mqm,y1{m=j}=qj,y

因为有jy的保证,为加上my的限制不会有任何影响

矩阵分析中,学习过,实数函数对向量或矩阵求导就是实数函数对向量或矩阵中的每个分量求偏导,若打分向量s是一个1×K的行向量,则单样本损失对打分向量的梯度有:
Ls=(Ls1,Ls2,...,LsK)

求平均损失对打分矩阵的梯度

我们所谓的损失指的都是期望损失,也就是平均损失。若第i条样本的损失为L(i),则N条样本的平均损失为
ˉL=1NNi=1L(i)

注意前面那张图片,打分矩阵各行为各样本的打分向量,因此打分矩阵为 S=(s(1)s(2)s(N))=(s(1)1s(1)Ks(N)1s(N)K)

因此平均损失对打分矩阵S的梯度为 dˉLdS=1NNi=1dL(i)dS=1NNi=1(dL(i)ds(1)dL(i)ds(i)dL(i)ds(N))=1NNi=1(0dL(i)ds(i)0)=1N(dL(1)ds(1)dL(N)ds(N))=1N(L(1)s(1)1L(1)s(1)KL(N)s(N)1L(N)s(N)K):=dS


定义了一个矩阵dS,其中的元素为
(dS)ij=1NL(i)s(i)j=1N(1{jy(i)}qj,y(i)1{j=y(i)}Kmy(i)qm,y(i))

编码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def svm_loss(S, y):
"""
Computes the loss and gradient using for multiclass SVM classification.
Inputs:
- S: Input data, of shape (N, C) where S[i, j] is the score for the jth
class for the ith input.
- y: Vector of labels, of shape (N,) where y[i] is the label for S[i] and
0 <= y[i] < C
Returns a tuple of:
- loss: Scalar giving the loss
- dS: Gradient of the loss with respect to S
"""
N = S.shape[0]
i = np.arange(N)
# 求svm loss
margins = np.maximum(S - S[i, y][:, np.newaxis] + 1, 0)
margins[i, y] = 0
loss = np.sum(margins) / N
# 求dS
dS = (S - S[i, y][:, np.newaxis] + 1 >= 0).astype('int')
dS[i, y] = 0
dS[i, y] = -dS.sum(axis=1)
dS /= N
return loss, dS

Softmax loss

L=log(esymesm)

除了loss外,其他设定与Multiclass SVM loss一样,求解梯度一样需要以下四个步骤:

  1. L/sj
  2. 求平均损失对打分矩阵的梯度
  3. 编码实现

因为只有loss不同,其他都是一样的,因此只有求L/sj和编码实现不同。

L/sj(方法1)

L=log(esymesm)=logmesmsy
Lsj=1mesm(mesm)sjsysj=1mesmmesmsjsysj=1mesmm1{m=j}esm1{j=y}=esjmesm1{y=j}=pj1{j=y}

其中 pj=esjmesm

L/sj(方法2)

之所以要再多写个方法二,是为了增加对这种逻辑求导运算的熟练度。
L=log(esymesm)=log(py)



Lsj=1pypysj

其中
pysj=sj(esymesm)=1{j=y}esy(mesm)esyesj(mesm)21{jy}esyesj(mesm)2

将其带入L/sj得,
Lsj=mesmesy(1{j=y}esy(mesm)esyesj(mesm)21{jy}esyesj(mesm)2)=(1{j=y}(mesm)esjmesm1{jy}esjmesm)=1{jy}esjmesm1{j=y}(mesm)esjmesm=1{jy}pj1{j=y}(1pj)=1{jy}pj1{j=y}+1{j=y}pj=pj1{j=y}

注意: 上式最后一步用了一个很简单的或运算

1{jy}pj+1{j=y}pj=1{jyj=y}pj=pj

编码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def softmax_loss(S, y):
N = X.shape[0]
i = np.arange(N)
# 避免数值问题
Smax = S.max(axis=1).reshape((N, 1))
Sexp = np.exp(S - Smax)
# 求loss
p = Sexp / Sexp.sum(axis=1, keepdims=True)
loss = -np.sum(p[i, y]) / N
# 求dS
p[i, y] -= 1.0
dS = p / N
return loss, dS

注意: 避免数值问题
esjsmaxmesmsmax=esj/esmaxm(esm/esmax)=esj/esmax(mesm)/esmax=esjmesm


这么做的好处是避免指数运算出现特别大的数而产生溢出

总结

通过上面两个例子,求损失对打分的梯度,关键在于对打分下标的考虑,将选择取值和分组讨论问题转化为逻辑运算,可以简化求导过程,最终简化编码实现。