课程视频地址:https://study.163.com/courses-search?keyword=CS231

课程主页:http://cs231n.stanford.edu/2017/

这一讲继续介绍训练神经网络的一些技巧。

更好的优化

之前我们介绍了随机梯度下降法,但是这个方法有一些问题——如果损失在某个方向降低很快,另一个方向降低很慢,那么图像如下

如果从红点出发,利用随机梯度下降法产生的结果非常震荡,这就会严重影响效率

另一个问题是,随机梯度下降很容易陷入局部最优点和马鞍点

还有一个问题是,随机梯度下降法很容易受到噪声干扰,这会让优化速度减慢

下面介绍几种处理上述问题的优化方法。

Momentum

第一种方法是Momentum,其更新公式如下

伪代码如下

vx = 0
while True:
    dx = compute_gradient(x)
    vx = rho * vx + dx
    x -= learning_rate * vx

Momentum更新可以理解为球从山谷滚下来的过程,假设球位于某个点$x_t$,$\nabla f(x_t)$可以理解为此处的加速度,首先利用$v_{t+1} = \rho v_t + \nabla f(x_t)$更新速度,然后速度回根据$x_{t+1}= x_t -\alpha v_{t+1}$更新球的位置。超参数$\rho$常取$0.9$,效果如下

Nesterov Momentum

Nesterov Momentum和Momentum唯一的不同之处在于其计算的梯度为下一个点的梯度,其更新公式如下

为了让形式更简洁,作如下变换

那么上述更新公式可以改写为

伪代码如下

dx = compute_gradient(x)
old_v = v
v = rho * v - learning_rate * dx
x += -rho * old_v + (1 + rho) * v

Nesterov Momentum一般效果比Momentum更好

AdaGrad

AdaGrad是为了处理之前描述的第一个问题,采用的方法是将梯度归一化,伪代码如下

grad_squared = 0
while True:
    dx = compute_gradient(x)
    grad_squared += dx * dx
    x -= learning_rate * dx / (np.sqrt(grad_squared) + 1e-7)

这个方法有一个问题,当迭代次数越来越多时,grad_squared会越来越大,每次更新的步长就会越来越小,这样就容易陷入局部最优的情况,解决方法是后面介绍的RMSProp。

RMSProp

RMSProp是对AdaGrad的改进,这里增加了一个衰减率,伪代码如下

grad_squared = 0
while True:
    dx = compute_gradient(x)
    grad_squared = decay_rate * grad_squared + (1 - decay_rate) * dx * dx
    x -= learning_rate * dx / (np.sqrt(grad_squared) + 1e-7)

效果如下

Adam

Adam是将Momentum和RMSProp结合,伪代码如下

first_moment = 0
second_moment = 0
for t in range(num_iterations):
    dx = compute_gradient(x)
    first_moment = beta1 * first_moment + (1 - beta1) * dx
    second_moment = beta2 * second_moment + (1 - beta2) * dx * dx
    first_unbias = first_moment / (1 - beta1 ** t)
    second_unbias = second_moment / (1 - beta2 ** t)
    x -= learning_rate * first_unbias / (np.sqrt(second_unbias) + 1e-7)

其中倒数2,3行的步骤是为了处理first_moment和second_moment一开始都接近于$0$的问题,这里默认的超参数为

效果如下

学习率

上面介绍的几种优化方法都有学习率这个参数,之前介绍的方法中学习率都是固定的,实际中随时间衰减的学习率效果更好,一般有如下两种常见的衰减学习率:

  • 指数衰减学习率

  • $1/t$衰减学习率

二阶优化

之前介绍的方法都是一阶优化方法,实际中还有二阶优化方法,其原理是基于泰勒展开

更新公式如下

上述方法被称为牛顿法,该方法的好处在于没有超参数,但是实际中几乎不用,因为计算逆矩阵需要非常大的计算量。实际中,一般利用BGFS或者L-BFGS来近似计算逆矩阵。在实际中,很少使用二阶优化,一般默认的优化器为Adam。

模型集成

模型集成是指训练几个独立的模型,在测试的时候平均他们的结果,这一般能提高$2\%$的效果。在神经网络中,一个简单的方法是计算参数的滑动平均值

while True:
    data_batch = dataset.sample_data_batch()
    loss = network.forward(data_batch)
    dx = network.backward()
    x += - learning_rate * dx
    x_test = 0.995*x_text + 0.005*x

正则化

Dropout

模型集成可以提升整体效果,那么该如何提升单个模型的效果呢?由之前介绍的内容可知,我们应该使用正则化,神经网络中比较常用的正则化方法为Dropout,该方法的思路为让神经元随机失活,这样就可以减少有效的神经元数量

伪代码如下

""" Vanilla Dropout: Not recommended implementation (see notes below) """

p = 0.5 # probability of keeping a unit active. higher = less dropout

def train_step(X):
  """ X contains the data """
  
  # forward pass for example 3-layer neural network
  H1 = np.maximum(0, np.dot(W1, X) + b1)
  U1 = np.random.rand(*H1.shape) < p # first dropout mask
  H1 *= U1 # drop!
  H2 = np.maximum(0, np.dot(W2, H1) + b2)
  U2 = np.random.rand(*H2.shape) < p # second dropout mask
  H2 *= U2 # drop!
  out = np.dot(W3, H2) + b3
  
  # backward pass: compute gradients... (not shown)
  # perform parameter update... (not shown)
  
def predict(X):
  # ensembled forward pass
  H1 = np.maximum(0, np.dot(W1, X) + b1) * p # NOTE: scale the activations
  H2 = np.maximum(0, np.dot(W2, H1) + b2) * p # NOTE: scale the activations
  out = np.dot(W3, H2) + b3

上述方法需要注意一点:在预测的时候要乘以dropout概率$p$,这是因为假设输入为$x$,其期望输出为$px$,所以为了保持一致,预测时要乘以dropout概率$p$。这要会产生一个问题:预测时增加了运算量,一个改进方式如下

""" 
Inverted Dropout: Recommended implementation example.
We drop and scale at train time and don't do anything at test time.
"""

p = 0.5 # probability of keeping a unit active. higher = less dropout

def train_step(X):
  # forward pass for example 3-layer neural network
  H1 = np.maximum(0, np.dot(W1, X) + b1)
  U1 = (np.random.rand(*H1.shape) < p) / p # first dropout mask. Notice /p!
  H1 *= U1 # drop!
  H2 = np.maximum(0, np.dot(W2, H1) + b2)
  U2 = (np.random.rand(*H2.shape) < p) / p # second dropout mask. Notice /p!
  H2 *= U2 # drop!
  out = np.dot(W3, H2) + b3
  
  # backward pass: compute gradients... (not shown)
  # perform parameter update... (not shown)
  
def predict(X):
  # ensembled forward pass
  H1 = np.maximum(0, np.dot(W1, X) + b1) # no scaling necessary
  H2 = np.maximum(0, np.dot(W2, H1) + b2)
  out = np.dot(W3, H2) + b3
数据扩充

还有一个常见的正则化方法为数据扩充——利用原始数据获得新的数据,图像数据中常用的方法为旋转,剪裁,修改颜色等等。

迁移学习

迁移学习解决了在小数据集上使用CNN的问题,其思路很简单,首先在大数据集上训练CNN,然后冻结某些层,在小数据集上训练最后几层,示意图如下

下图很好的概况的使用迁移学习的各种情形