滑动平均算法的原理
滑动平均模型又称为指数加权平均算法,是一种对数据的估测方式,它的好处是可以使数据更加平滑,数据噪声更少,不会出现异常值。
举例来说,这是来自吴恩达老师的深度学习课程中的一个例子,上图表示了一年365天的温度散点图,以天数为横坐标,温度为纵坐标,大致看来图中的点遵循一定的曲线规律,但不明显。
为了使温度变化的趋势更加明显,需要用到滑动平均模型,具体的计算方式如下:
- 首先给定一个初始值 V0,
- 然后对于图中每一天的温度,记为a1,a2,a3…
- 接着计算 V1,V2,V3…来代替每一天的温度
- 计算方法为:V1 = V0 * 0.9 + (1 - 0.9) * a1,V2 = V1 * 0.9 + (1 - 0.9) * a2,V3 = V2 * 0.9 + (1 - 0.9) * a3…
- 计算得到所有的 V 值,然后画图,如图所示,红线就是对应的 V 的曲线,它很好地把温度变化曲线拟合了出来
滑动平均模型对应的计算公式如下,上面的计算过程就是取 β = 0.9,而 β 指的就是滑动平均模型的衰减率,衰减率可以用来控制模型的更新速度,衰减率越大模型越稳定,所以一般 β 取接近 1 的数(0.99 或 0.999)
另外,Vt 的值实际上可以代表 1 / (1-β) 天的平均值,例如当 β = 0.9,就是对应前十天的平均值,β = 0.98,对应前50天的平均值,对应的绿色曲线如下,很明显绿线的变化更加缓慢,但由于 β 值过大容易使曲线右移,降低对原数据的拟合度,因此需要不断尝试得出一个合适的 β 值,既能拟合数据集,又可以减少噪声
当 β 取值为 0.5,对应前两天的平均值,得到如下的黄色曲线,明显黄线变化很不稳定
误差修正
在上面的例子中,在初始阶段定义 V0 = 0,β = 0.98,,但一开始并没有前50天的数据,这使得 Vt 偏小,因此实际得到的拟合曲线应该是下图中的紫色曲线
通常会使用 Vt / (1 - β^t)的方法修正,即 V2 改为 V2 / (1 - 0.9^2)
也可以使用
使用 num_updates(即更新次数) 来动态设置衰减率 β 的大小,在前期数据量比较小时会使用 (1+num_updates) / (10+num_updates) 作为衰减率,如第五天,β=0.98,(1+num_updates) / (10+num_updates) = (1 + 5) / (10 + 5) = 6 / 15 = 0.4,相当于最近1.6天的平均温度。
在 Tensorflow 中实现滑动平均算法
首先明确一点,TensorFlow 中的 ExponentialMovingAverage()
是针对权重weight和偏差bias的,而不是针对训练集的。
为什么要对w和b使用滑动平均模型呢?
因为在神经网络中, 更新的参数时候不能太大也不能太小,更新的参数跟你之前的参数有联系,不能发生突变。一旦训练的时候遇到个“疯狂”的参数,有了滑动平均模型,疯狂的参数就会被抑制下来,回到正常的队伍里。这种对于突变参数的抑制作用,用专业术语讲叫鲁棒性,鲁棒性就是对突变的抵抗能力,鲁棒性越好,这个模型对恶性参数的抵抗能力就越强。
在TensorFlow中,ExponentialMovingAverage()
可以传入两个参数:衰减率(decay)和数据的迭代次数(step),这里的decay和step分别对应我们的β和num_updates,代码实现如下:
import tensorflow as tf
v1 = tf.Variable(0, dtype=tf.float32)
step = tf.Variable(0, trainable=False)
#定义滑动平均类
ema = tf.train.ExponentialMovingAverage(0.99, step)
#定义一个更新变量滑动平均的操作。这里需要给定一个列表
maintain_average_op = ema.apply([v1])
with tf.Session() as sess:
tf.global_variables_initializer().run()
# 通过ema.average(v1)获取滑动平均之后变量的取值
print(sess.run([v1, ema.average(v1)])) # [0.0, 0.0]
# 更新v1的值到5
sess.run(tf.assign(v1, 5))
sess.run(maintain_average_op)
print(sess.run([v1, ema.average(v1)])) # [5.0, 4.5]
#更新step的值为10000
sess.run(tf.assign(step, 10000))
sess.run(tf.assign(v1, 10))
sess.run(maintain_average_op)
print(sess.run([v1, ema.average(v1)])) # [10.0, 4.555]
sess.run(maintain_average_op)
print(sess.run([v1, ema.average(v1)])) # [10.0, 4.60945]
补充
滑动平均实际上就是对前 n 个数据进行加和并平均,假设有100天的温度数据,则