Loading...
Navigation
Table of Contents

梯度与反向传播Data

梯度(偏导数向量)与链式法则

梯度:对于多元函数$f(x,y)$,在一个点$(x_0, y_0)$上的梯度$\nabla f=[\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}]$是一个由偏导数组成的向量,在这个方向上,即$(x_0,y_0)\rightarrow (x_0,y_0)+\nabla f$,函数值上升速度最快。

考虑一个简单例子,对于函数$f(x)=x^2$,其导数/梯度为$\nabla f = [2x]$。对于一个点$x_0 < 0$,在这个点上的梯度$\nabla f = [2x_0] < 0$。可以看出函数值的确在梯度方向上上升最快;对于点$x_0 > 0$,显然。

链式法则:$\frac{\partial F(G(X))}{\partial X}=\frac{\partial F}{\partial G}\frac{\partial G}{\partial X}$,这里是矩阵版本,注意先后顺序不能错。


反向传播

下面考虑几种简单的多元函数/操作符$f(x,y)$。假设最后输出层表示为$F(f)$,当$f(x,y)$拿到从后面隐层传过来的梯度$\frac{\partial F}{\partial f}$后,我们想得到$f$对于$(x, y)$的梯度,则需要用到链式法则来计算,即$\nabla F=\frac{\partial F}{\partial f} \cdot [\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}]$

  • 加法二元操作符:考虑加法操作$f(x, y) = x + y$,由于$\nabla f=[1 ,1]$,所以就把从后面传过来的梯度$\frac{\partial F}{\partial f}$直接传给前面每个变量即可;
  • 乘法二元操作符:考虑乘法操作$f(x, y) = x * y$,由于$\nabla f=[y ,x]$,所以做法同上;
  • max二元操作符:考虑$f(x, y) = \max (x, y)$,在往前传梯度时,我们需要判断下$x$和$y$的关系。如果$x>y$,那么$\nabla f=[1 ,0]$,如果$x<y$,那么$\nabla f=[0 ,1]$,然后做法同上;
  • 一元函数:考虑Sigmoid函数$\sigma(x)=1/(1+e^{-x})$,由于$\nabla \sigma = [\sigma(x)(1-\sigma(x))]$,所以$\nabla F = [\frac{\partial F}{\partial \sigma} \cdot \sigma(x)(1-\sigma(x))]$。

下面考虑$F(X, W) = 1/(1+\sigma(-WX))$的前向和反向传播的python样例

w = [2,-3,-3] #假设一些随机数据和权重
x = [-1, -2]
#开始前向传播
dot = w[0] * x[0] + w[1] * x[1] + w[2]
f = 1.0 / (1 + math.exp(-dot)) #sigmoid函数
#开始反向传播
ddot = (1 - f) * f #点积变量的梯度, 使用sigmoid函数求导
dx = [w[0] * ddot, w[1] * ddot] #回传到x
dw = [x[0] * ddot, x[1] * ddot, 1.0 * ddot] #回传到w

矩阵版本反向传播

对于多变量的函数,可以把其自变量看成一个矩阵(向量),所以梯度实际上就是对自变量矩阵求导后得到的矩阵。用矩阵表示来反向传播更易于用编程实现。

例1:以下内容参考自知乎专栏。考虑如下实值损失函数$J = (Xw-y)^T(Xw-y)=||Xw-y||^2$,其中$X\in R^{N\times F}$,$w\in R^{F\times 1}$,$y\in R^{N\times 1}$,求$\frac{\partial J}{\partial X}$,$\frac{\partial J}{\partial w}$,$\frac{\partial J}{\partial y}$。

记住如下两个原则:

  • 如果$f(X)\in R$,那么$\frac{\partial f(X)}{X}$和$X$的shape相同
  • 把矩阵看看成普通参数来求导,忽略转置,最后做维度匹配的工作(即进行换序、转置等操作)

具体来说,首先,$\frac{\partial J}{\partial X} = w(Xw-y) + w(Xw-y)$,由于$\frac{\partial J}{\partial X}\in R^{N\times F}$,$(Xw-y)\in R^{N\times 1}$,$w\in R^{F\times 1}$,所以$\frac{\partial J}{\partial X}=2(Xw-y)\cdot w^T$。

例2:求$\frac{\partial X^TAX}{\partial X}$,其中$x\in R^{1}$。

其它

参考cs231nruder.io

  • Stochatis GD. 容易抖,但适合online learning
  • Mini-batch GD. batch一般50-256,收敛更稳定,速度也更快
  • Batch/Vanilla gradient descent. 一次更新一个batch(即全量数据)
x += - learning_rate * dx
  • Momentum. 当前step的梯度为上一个step的梯度的线性和,有助于抑制摇摆的发生;动量项mu一般为0.9
v = mu * v + learning_rate * dx #本次更新的梯度
x -= v
  • Nesterov momentum. 和momentum不同的是,nesterov先根据上次的动量更新到大致位置,然后再求梯度
xnew = x - mu * v #先更新到对应位置
v = mu * v + learning_rate * d_xnew #本次更新的梯度
x -= v
  • Adagrad. 对于要更新的参数x,Adagrad对不同的x[i][j]采用不同的学习率。具体来说,如果之前再这个点的累计梯度比较大,这次就更新少一点,反之即反。初始学习率learning_rate一般设置为0.01。
cache += dx**2 #cache[i][j] = dx[i][j] ^2, 和dx, x的维度相同
x -= learning_rate * dx / (np.sqrt(cache) + eps) #全都是element-wise操作
  • RMSprop. 注意到Adagrad是存储之前每一个step的梯度,此外学习率每一步都会变小,逐渐变为0。RMSprop则是换成动量的方法来更新cache
cache = decay_rate * cache + (1 - decay_rate) * (dx**2)  #上一次和这一次的cache
x -= learning_rate * dx / (np.sqrt(cache) + eps)
  • Adam. Adam将dxcache都用动量的方法来更新,类似于momentum和RMSprop的结合。
m = beta1 * m + (1 - beta1) * dx
v = beta2 * v + (1 - beta2) * (dx**2)
x -= learning_rate * m / (np.sqrt(v) + eps)
  • FTRL. 参考这篇论文。参数说明:alpha (float),beta (float),lambda1 (float, optional): L1正则(default: 0),lambda2 (float, optional): L2正则(default: 0)

$w_{t,i}=\begin{cases}\text{init_factor} & \text{if } z_{t,i} \le \lambda_1 \ -\frac{z_{t,i}-\text{sgn}(z_{t,i})\lambda_1}{\lambda_2+\frac{\beta+\sqrt{n_{t,i}}}{\text{alpha}}}  & \text{otherwise}\end{cases}$

σt=√nt+g2t−√ntα
zt+1=zt+gt−σt×wt
nt+1=nt+g2t


softmax回归实战

本次实战参考cs231n assignment1里的softmaxtwo_layer_net作业,以及知乎。考虑用只有一层的softmax回归来解决多分类问题,激活函数是softma,损失函数是交叉熵,如何手动求$\frac{\partial l}{\partial W}$和$\frac{\partial l}{\partial b}$?

y = np.dot(X, W) + b #y.shape=[N, C]
#X.shape=[N, F]; W.shape=[F, C]; b.shape=[1, C]其中F是feature个数, C是class个数
y_pred = softmax(y) #每一行归一化, y_pred.shape=[N, C]
l = cross_entropy(y_pred, y_true) #y_true.shape=[N, C]

对W求导:$\frac{\partial{l}}{\partial W} = \frac{\partial l}{\partial y}\cdot \frac{\partial y}{\partial W}$,而$\frac{\partial y}{\partial W}=X$ (先不考虑换序和转置的事情),如何求$\frac{\partial l}{\partial y}$(记为dy)呢?

易知dy是个矩阵,shape与y相同。我们考虑第0行样本,设onehot向量$y_{true}=[0,0,1,\dots, 0]$里第$m$个元素命中1,由于这一行贡献的cross_entropy损失为$l_i=-\log (e^{y_m}/\sum e^{y_i})$,所以

$$dy[0][m]=\frac{\partial l}{\partial y_m}= - \frac{\sum e^{y_i}}{e^{y_m}}\cdot \frac{e^{y_m}\cdot (\sum e^{y_i}) - e^{y_m}\cdot e^{y_m}}{(\sum e^{y_i})^2}=\frac{e^{y_m}}{\sum e^{y_i}}-1$$
而对于$y_n, n\neq m$求导,得
$$dy[0][n]=\frac{\partial l}{\partial y_n}=-\frac{\sum e^{y_i}}{e^{y_n}}\cdot -\frac{e^{y_n}\cdot e^{y_n}}{(\sum e^{y_i})^2}=\frac{e^{y_n}}{\sum e^{y_i}}$$

可以神奇地发现dy[0]=y_pred[0]-y_true[0],所以dy=y_pred-y_true

下明考虑维度匹配,因为dy.shape=[N,C]X.shape=[N, F]dW.shape=[F,C],所以dW=X.T * dy。如果有$0.5\lambda ||W||^2_2$ ($||W||^2_2=\sum W_{i,j}^2$)的正则化的话,那么dW=X.T * dy + reg * W。以下是作业中softmax_loss_vectorized代码

def softmax_loss_vectorized(W, X, y_true, reg):
  N = X.shape[0]
  y = X.dot(W)
  #开始计算损失
  shift_y = y - np.max(y, axis=1).reshape(-1, 1) #一个trick, 防止e^x太大
  y_pred = np.exp(shift_y) / np.sum(np.exp(shift_y), axis=1).reshape(-1, 1)
  loss = -np.sum(np.log(y_pred[range(N), list(y)]))
  loss /= N #reduce_mean
  loss += 0.5 * reg * np.sum(W * W) #正则化
  #开始计算梯度
  dS = y_pred.copy()
  dS[range(N), list(y)] += -1
  dW = (X.T).dot(dS) / N #reduce_mean
  dW = dW + reg * W
  return loss, dW #返回损失和梯度

对b求导:这里有所不同,注意到y = np.dot(X, W) + b中,因为np.dot(X, W)[N, C]的,而b[1, C]的,根据广播机制在实际中是每一行都加上同样的b。所以其实本质上是y = np.dot(X, W) + np.dot(I, b),这里I = np.ones([N, 1])是全1矩阵。所以db = np.dot(I.T, dW),或者写成db = np.sum(dw, axis=0)

指定参数进行梯度更新

def get_train_op(loss, lr=0.01):
    optimizer = tf.train.AdamOptimizer(lr)
    tvars = tf.trainable_variables()
    tvars = [v for v in tvars if 'frozen' not in v.name]
    grads = tf.gradients(loss, tvars)
    train_op = optimizer.apply_gradients(zip(grads, tvars),
                                         global_step=tf.train.get_global_step())
    return train_op

其它

为什么共轭梯度法不适用于深度学习中的网络训练 - 知乎

Last updated on Jan 03, 2020.