Young87

当前位置:首页 >个人收藏

MNIST手写数字识别(二)几种模型优化方式介绍

本篇的主要内容有:

  • 动态衰减法设置可变学习率
  • 为损失函数添加正则项
  • 滑动平均模型介绍

为了让MNIST数字识别模型更准确,学习几种常用的模型优化手段:

学习率的优化

学习率的设置一定程度上也会影响模型的训练,如果学习率过小,那么将会经过很长时间才会收敛到想要的结果,反之,学习率过大则可能会导致不收敛的结果,比如:优化 J(x) = x^{2}, 如果设置学习率为 1,那么整个过程:

在这里插入图片描述

可以看到整个过程不管迭代多少次,都不会改变参数值了。这里多提醒一句,我前几天刚刚接触到线性模型也遇到了这个问题,之前处理数据,我使用的学习率都是0.2, 当时遇到的数据集是这样的:

train_x = np.array([3.3, 4.4,5.5,6.71,6.93,4.168,9.779,6.182,7.59,2.167,
                    7.042,10.791,5.313,7.997,5.654,9.27,3.1])
train_y = np.array([1.7,2.76,2.09,3.19,1.694,1.573,3.366,2.596,2.53,1.221,
                    2.827,3.465,1.65,2.904,2.42,2.94,1.3])

结果使用0.2 的学习率的使用就出问题了,模型无法收敛到一个正确的范围,当时纠结了好一会才意识到是学习率设置错了,毕竟数据的范围就已经很小了,再使用0.2就相对大了,所以一定要根据具体的数据进行设置学习率!

言归正传,在TensorFlow中,为了解决固定学习率的这种弊端,提供了一种很灵活的学习率设置方法–指数衰减法,使用这个方法可以先用较大的学习率得到一个比较好的位置(也就是图像上由比较“陡峭”的地方快速来到比较“平缓”的位置),随着迭代的过程,学习率会逐渐减小,从而使模型变得更稳定。

TensorFlow中实现了指数衰减法的函数是 tf.train.exponential_decay,这个函数可以指数级地减小学习率,实现功能如下:

decay_learning_rate = learning_rate * decay_rate ^(global_step / decay_steps)
参数:
    decayed_learning_rate: 每一轮结束后更新地学习率
    learning_rate: 最初设置地学习率
    decay_rate: 衰减系数
    decay_steps: 衰减速度

样例

global_step = tf.Variable(0)

learning_rate = tf.train.exponential_decay(
    0.1, global_step, 100, 0.96, staircase=True)

learning_step = tf.train.GradientDescentOptimizer(learning_rate).
minimize(loss, global_step=globalstep)

我们应用指数衰减法解决一个简单地线性回归问题:
首先在没有动态设置学习率的时候:

# 线性回归模型
import tensorflow as tf
import numpy as np

x_data = np.random.rand(100)
noise = np.random.normal(0.0, 0.02, x_data.shape)
y_data = 1.5 * x_data + 2.3 + noise     # 定义 w: 1.5  b: 2.3

# 定义线性模型
k = tf.Variable(np.random.rand(1))
b = tf.Variable(np.random.rand(1))
y = k * x_data + b

# 损失函数以及优化器定义
learning_rate = 0.2
loss = tf.reduce_mean(tf.square(y - y_data))
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
train = optimizer.minimize(loss)

init_op = tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init_op)
    for i in range(201):   
        sess.run(train)
        if i % 10 == 0:   # 10次就输出一组 方便比较两次的收敛速度
            print(sess.run([k, b]), learning_rate)

这时候的输出

[array([0.83753064]), array([1.47330253])] 0.2
[array([1.39016995]), array([2.3620908])] 0.2
[array([1.41398799]), array([2.3490679])] 0.2
[array([1.4323059]), array([2.33850712])] 0.2
[array([1.44657744]), array([2.33027885])] 0.2
[array([1.45769655]), array([2.32386812])] 0.2
[array([1.46635957]), array([2.31887346])] 0.2
[array([1.47310902]), array([2.31498206])] 0.2
[array([1.47836758]), array([2.31195023])] 0.2
[array([1.48246459]), array([2.3095881])] 0.2
[array([1.48565661]), array([2.30774774])] 0.2
[array([1.48814355]), array([2.30631389])] 0.2
[array([1.49008115]), array([2.30519677])] 0.2   w 更新到1.49
[array([1.49159075]), array([2.3043264])] 0.2
[array([1.4927669]), array([2.30364829])] 0.2
[array([1.49368325]), array([2.30311997])] 0.2
[array([1.49439719]), array([2.30270835])] 0.2
[array([1.49495343]), array([2.30238765])] 0.2
[array([1.4953868]), array([2.30213779])] 0.2
[array([1.49572444]), array([2.30194312])] 0.2
[array([1.4959875]), array([2.30179145])] 0.2

使用指数衰减法之后,修改损失函数部分:

# 损失函数以及优化器定义
global_steps = tf.Variable(0)
learning_rate = tf.train.exponential_decay(
    0.2, global_steps, 100, 0.96, staircase=True)
loss = tf.reduce_mean(tf.square(y - y_data))
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
train = optimizer.minimize(loss, global_step=global_steps)

使用指数衰减法之后的输出:

[array([1.24186946]), array([1.37994397]), 0.2]
[array([1.64187716]), array([2.22352439]), 0.2]
[array([1.60681132]), array([2.24353267]), 0.2]
[array([1.58028094]), array([2.25804498]), 0.2]
[array([1.56040553]), array([2.26891649]), 0.2]
[array([1.54551588]), array([2.27706087]), 0.2]
[array([1.53436132]), array([2.28316223]), 0.2]
[array([1.52600488]), array([2.28773306]), 0.2]
[array([1.51974467]), array([2.29115729]), 0.2]   w 更新到1.51左右
[array([1.51505484]), array([2.29372254]), 0.2]
[array([1.51155375]), array([2.29563758]), 0.192]
[array([1.50901135]), array([2.29702823]), 0.192]
[array([1.50708427]), array([2.29808231]), 0.192]
[array([1.50562359]), array([2.29888128]), 0.192]
[array([1.50451642]), array([2.29948688]), 0.192]
[array([1.50367722]), array([2.29994591]), 0.192]
[array([1.50304112]), array([2.30029384]), 0.192]
[array([1.50255898]), array([2.30055757]), 0.192]
[array([1.50219352]), array([2.30075746]), 0.192]
[array([1.50191652]), array([2.30090898]), 0.192]
[array([1.50170729]), array([2.30102343]), 0.18432]

可以看到,设置了动态学习率之后,模型更快地收敛到了同样准确率的地方。

过拟合问题的优化

过拟合现象与欠拟合现象相对,欠拟合是模型太弱,不能很好地对数据进行处理,过拟合则是模型学习能力太强,以至于记住了模型中的噪音部分,这样模型不具有“泛化”的能力,为了避免过拟合,一种非常常用的方法是正则化(regularization),简单来说就是修改原来的损失函数,加入模型复杂度衡量的指标,这时候,进行优化就是 J ( θ ) + λ R ( w ) J(\theta) + \lambda R(w) J(θ)+λR(w),通常使用的这个 R(w) 有两种,一种是 L1 正则化,计算方式是:
R ( w ) = ∥ w ∥ 1 = ∑ ∣ w ∣ R(w) = \left \| w \right \|_{1} = \sum\left | w \right | R(w)=w1=w

另一种是L2 正则化,计算方式:
R ( w ) = ∥ w ∥ 1 2 = ∑ ∣ w ∣ 2 R(w)=\left \| w \right \|_{1}^{2} = \sum\left | w \right |^{2} R(w)=w12=w2
两种方式都是通过限制权重的大小,使得模型不能任意地拟合训练数据中地随机噪音,其中L1正则化方法会使得参数变得稀疏(也就是会有更多地参数变为0)并且不能求导,所以感觉大部分使用地都是L2正则化方式,在TensorFlow中使用方式如下:

loss = tf.reduce_mean(tf.square(y - y_data)) + tf.contrib.layers.l2_regularizer(lambda)(w)
                                               # 这就是定义的 l2 正则化
                                               # lambda 是正则化系数  w 是 需要计算正则化的参数
                                               # 如果手工计算一下某个权重数组的 L2 范式 会与这个函数的 
                                               # 输出不一样 因为这个函数会自动除以2 方便求导

滑动平均模型

滑动平均模型稳定模型的原理很简单,变量变化的同时会有一个与之对应的 shadow_variable 逼近变化后的变量值,这样,通过记录过去一段时间的平均值,消除数值波动对参数变化的影响,从而使得模型的泛化能力更好(提高 robust)。

在TensorFlow中提供了使用滑动平均的函数:

tf.train.ExponentialMovingAverage
# 调用结构:
tensorflow.python.training.moving_averages.ExponentialMovingAverage def __init__(self,
             decay: Any,    # 衰减率 用来控制模型更新的速度
             num_updates: Any = None,    # 模型迭代的轮数
             zero_debias: bool = False,
             name: str = "ExponentialMovingAverage") -> None

滑动平均模型对每一个变量都会维护一个影子变量(shadow_variable),或者称它为滑动平均,这个shadow_variable的初始值就是对应的变量的初始值,在每次运行时,shadow_variable的更新公式是:

shadow_variable = decay * shadow_variable + (1 - decay) * variable

从这个更新公式中可以看出来,decay 决定了模型的更新速度,decay较大的模型趋于稳定,在实际使用的时候一般会将decay设置为比较大(0.99左右),这样可以使模型前期更新较快,而后期趋于稳定,这就需要一个动态的decay,所以,函数还提供了num_undates 参数,可以用来动态设置decay,动态衰减率的更新公式是:
d e c a y = m i n { d e c a y , 1 + n u m u p d a t e s 10 + n u m u p d a t e s } decay =min \begin{Bmatrix} decay ,& \frac{1+num_updates}{10+num_updates} \end{Bmatrix} decay=min{decay,10+numupdates1+numupdates}
很显然,在上面的公式中在 num_updates 比较小的时候, decay会选择后者,之后随着训练次数的增加,decay会变大,也就使得模型趋于稳定。

下面看一个例子,演示滑动平均模型如何使用:

import tensorflow as tf

# 定义计算滑动平均的变量 变量的类型必须是 实数
v1 = tf.Variable(0, dtype=tf.float32)

# 模拟神经网络中迭代的轮数的变量
step = tf.Variable(10, trainable=False)

# 定义一个滑动平均的类 初始化的衰减率为0.99, 衰减率的变量是step
ema = tf.train.ExponentialMovingAverage(0.99, step)

# 定义每次滑动平均所更新的列表 这里
maintrain_average_op = ema.apply([v1])


with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())

    # 更新 v1 的滑动平均值
    # 衰减率为 min(0.99, (1+step) / (10 + step)=0.1) = 0.1
    sess.run(maintrain_average_op)

    # 下面演示三种情况下的滑动平均输出
    # 1
    # 刚开始滑动平均 与 原变量 值一样
    print("1: ", sess.run([v1, ema.average(v1)]))


    # 2
    # 只变量更新为5 step保持不变
    sess.run(tf.assign(v1, 5))
    # 此时运行 decay会更新为 min(0.99, (1+step)/ (10 + step)=0.1) = 0.1
    sess.run(maintrain_average_op)
    print("2: ", sess.run([v1, ema.average(v1)]))


    # 3
    # 这里模拟训练中更为一般的情况:
    # step v1 都改变
    sess.run(tf.assign(v1, 10))
    sess.run(tf.assign(step, 10000))
    # 这时候计算的 decay 应该是 0.99 了
    print("3: ",sess.run([v1, ema.average(v1)]))

输出:

1:  [0.0, 0.0]
2:  [5.0, 2.25]
3:  [10.0, 2.25]

以上~

除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog

上一篇: 给初学者的链接器指南

下一篇: Shell 编程快速入门

精华推荐