指数加权移动平均法(EWMA)详解及其在TensorFlow中的应用
指数加权移动平均法(EWMA)详解及其在TensorFlow中的应用
指数加权移动平均法(EWMA)是一种常用的数据平滑和预测方法,广泛应用于时间序列分析、信号处理和机器学习等领域。本文将详细介绍EWMA的原理、算法实现以及在TensorFlow中的具体应用,帮助读者深入理解这一重要技术。
指数加权移动平均法(EWMA)
指数加权移动平均法,是对观察值分别给予不同的权数,按不同权数求得移动平均值,并以最后的移动平均值为基础,确定预测值的方法。采用加权移动平均法,是因为观察期的近期观察值对预测值有较大影响,它更能反映近期变化的趋势。
指数移动加权平均法,是指各数值的加权系数随时间呈指数式递减,越靠近当前时刻的数值加权系数就越大。指数移动加权平均较传统的平均法来说,一是不需要保存过去所有的数值;二是计算量显著减小。
算法理解
引入一个例子,例子为美国一年内每天的温度分布情况,具体如下图所示
EWMA 的表达式如下:
$$
v_t = \beta v_{t-1} + (1-\beta) \theta_t
$$
上式中 $\theta_t$ 为时刻 $t$ 的实际温度;系数 $\beta$ 表示加权下降的速率,其值越小下降的越快;$v_t$ 为 $t$ 时刻 EWMA 的值。
在上图中有两条不同颜色的线,分别对应着不同的 $\beta$ 值。
当 $\beta=0.9$ 时,有 $v_t=0.9v_{t-1}+0.1\theta_t$,对应着图中的红线,此时虽然曲线有些波动,但总体能拟合真实数据
当 $\beta=0.98$ 时,有 $v_t=0.98v_{t-1}+0.02\theta_t$,对应着图中的绿线,此时曲线较平,但却有所偏离真实数据
在 $t=0$ 时刻,一般初始化 $v_0=0$,对 EWMA 的表达式进行归纳可以将 $t$ 时刻的表达式写成:
$$
v_t = (1-\beta)(\theta_t + \beta\theta_{t-1} + \ldots + \beta^{t-1}\theta_1)
$$
从上面式子中可以看出,数值的加权系数随着时间呈指数下降。在数学中一般会以 $1e$ 来作为一个临界值,小于该值的加权系数的值不作考虑,接着来分析上面 $\beta=0.9$ 和 $\beta=0.98$ 的情况。
当 $\beta=0.9$ 时,$0.9^{10}$ 约等于 $1e$,因此认为此时是近10个数值的加权平均。
当 $\beta=0.98$ 时,$0.950$ 约等于 $1e$,因此认为此时是近50个数值的加权平均。这种情况也正是移动加权平均的来源。
具体的分析如下图所示:
偏差修正
在初始化 $v_0=0$ 时实际上会存在一个问题。具体的如下图所示:
从上图中可以看出有一条绿色和紫色的曲线,都是对应于 $\beta=0.98$ 时的曲线。理想状况下应该是绿色的曲线,但当初始化 $v_0=0$ 时却会得到紫色的曲线,这是因为初始化的值太小,导致初期的数值都偏小,而随着时间的增长,初期的值的影响减小,紫色的曲线就慢慢和绿色的曲线重合。我们对公式做一些修改:
$$
v_t = \frac{\beta v_{t-1} + (1-\beta) \theta_t}{1-\beta^t}
$$
当 $t$ 很小时,分母可以很好的放大当前的数值;当 $t$ 很大时,分母的数值趋于1,对当前数值几乎没有影响。
EWMA 主要是被应用在动量优化算法中,比如Adam算法中的一阶矩和二阶矩都采用了上面修改后的EWMA算法。
深入解析TensorFlow中滑动平均模型与代码实现
因为本人是自学深度学习的,有什么说的不对的地方望大神指出
指数加权平均算法的原理
TensorFlow中的滑动平均模型使用的是滑动平均(Moving Average)算法,又称为指数加权移动平均算法(exponenentially weighted average),这也是ExponentialMovingAverage()函数的名称由来。
先来看一个简单的例子,这个例子来自吴恩达老师的DeepLearning课程,个人强烈推荐初学者都看一下。
开始例子。首先这是一年365天的温度散点图,以天数为横坐标,温度为纵坐标,你可以看见各个小点分布在图上,有一定的曲线趋势,但是并不明显
接着,如果我们要看出这个温度的变化趋势,很明显需要做一点处理,也即是我们的主题,用滑动平均算法处理。
首先给定一个值 $v_0$,然后我们定义每一天的温度是 $a_1$,$a_2$,$a_3$……
接着,我们计算出 $v_1$,$v_2$,$v_3$……来代替每一天的温度,也就是上面的 $a_1$,$a_2$,$a_3$
计算方法是:$v_1 = v_0 * 0.9 + a_1(1-0.9)$,$v_2= v_10.9 + a_2(1-0.9)$,$v_3= v_20.9 + a_3(1-0.9)$……,也就是说,每一天的温度改变为前一天的 $v$ 值 $0.9 + $ 当天的温度 $* 0.1$,$v_t = v_{t-1} * 0.9 + a_t * 0.1$,把所有的 $v$ 计算完之后画图,红线就是 $v$ 的曲线:
$v$ 值就是指数加权平均数,整个过程就是指数加权平均算法,它很好的把一年的温度曲线给拟合了出来。把 $0.9$ 抽象为 $\beta$,总结为 $v_t = v_{t-1} * \beta + a_t * (1-\beta)$。
$\beta$ 这个值的意义是什么?实际上 $v_t \approx 1/(1 - \beta)$ 天的平均温度,例如:假设 $\beta$ 等于 $0.9$,$1/(1 - \beta)$ 就等于 $10$,也就是 $v_t$ 等于前十天的平均温度,这个说可能不太看得出来;假设把 $\beta$ 值调大道接近 $1$,例如,将 $\beta$ 等于 $0.98$,$1/(1-\beta)=50$,按照刚刚的说法也就是前 $50$ 天的平均温度,然后求出 $v$ 值画出曲线,如图所示:
绿线就是 $\beta$ 等于 $0.98$ 时候的曲线,可以明显看到绿线比红线的变化更迟,红线达到某一温度,绿线要过一阵子才能达到相同温度。因为绿线是前 $50$ 天的平均温度,变化就会更加缓慢,而红线是最近十天的平均温度,只要最近十天的温度都是上升,红线很快就能跟着变化。所以直观的理解就是,$v_t$ 是前 $1/(1-\beta)$ 天的平均温度。
再看看另一个极端情况:$\beta$ 等于 $0.5$,意味着 $v_t \approx$ 最近两天的平均温度,曲线如下黄线:
和原本的温度很相似,但曲线的波动幅度也相当大!
然后说一下这个滑动平均模型和深度学习有什么关系:通常来说,我们的数据也会像上面的温度一样,具有不同的值,如果使用滑动平均模型,就可以使得整体数据变得更加平滑——这意味着数据的噪音会更少,而且不会出现异常值。但是同时 $\beta$ 太大也会使得数据的曲线右移,和数据不拟合。需要不断尝试出一个 $\beta$ 值,既可以拟合数据集,又可以减少噪音。
滑动平均模型在深度学习中还有另一个优点:它只占用极少的内存
当你在模型中计算最近十天(有些情况下远大于十天)的平均值的时候,你需要在内存中加载这十天的数据然后进行计算,但是指数加权平均值约等于最近十天的平均值,而且根据 $v_t = v_{t-1} * \beta + a_t * (1-\beta)$,你只需要提供 $a_t$ 这一天的数据,再加上 $v_{t-1}$ 的值和 $\beta$ 值,相比起十天的数据这是相当小的数据量,同时占用更少的内存。
偏差修正
指数加权平均值通常都需要偏差修正,TensorFlow中提供的 ExponentialMovingAverage() 函数也带有偏差修正。
首先看一下为什么会出现偏差,再来说怎么修正。当 $\beta$ 等于 $0.98$ 的时候,还是用回上面的温度例子,曲线实际上不是像绿线一样,而是像紫线:
你可以注意到在紫线刚刚开始的时候,曲线的值相当的低,这是因为在一开始的时候并没有 $50$ 天($1/(1-\beta)$ 为 $50$)的数据,而是只有寥寥几天的数据,相当于少加了几十天的数据,所以 $v_t$ 的值很小,这和实际情况的差距是很大的,也就是出现的偏差。
而在 TensorFlow 中的 ExponentialMovingAverage() 采取的偏差修正方法是:使用 num_updates 来动态设置 $\beta$ 的大小
在数据迭代的前期,数据量比较少的时候,$(1+num_updates)/(10+num_updates)$ 的值比较小,使用这个值作为 $\beta$ 来进行 $v_t$ 的计算,所以在迭代前期就会像上面的红线一样,和原数据更加接近。举个例子,当天数是第五天,$\beta$ 为 $0.98$,那么 $(1+num_updates)/(10+num_updates) = 6/15 = 0.4$,相当于最近 $1.6$ 天的平均温度,而不是 $\beta=0.98$ 时候的 $50$ 天,这样子就做到了偏差修正。
滑动平均模型的代码实现
看到这里你应该大概了解了滑动平均模型和偏差修正到底是怎么回事了,接下来把这个想法对应到 TensorFlow 的代码中。
首先明确一点,TensorFlow 中的 ExponentialMovingAverage() 是针对权重 weight 和偏差 bias 的,而不是针对训练集的。如果你现在训练集中实现这个效果,需要自己设计代码。
为什么要对 $w$ 和 $b$ 使用滑动平均模型呢?因为在神经网络中,
更新的参数时候不能太大也不能太小,更新的参数跟你之前的参数有联系,不能发生突变。一旦训练的时候遇到个“疯狂”的参数,有了滑动平均模型,疯狂的参数就会被抑制下来,回到正常的队伍里。这种对于突变参数的抑制作用,用专业术语讲叫鲁棒性,鲁棒性就是对突变的抵抗能力,鲁棒性越好,这个模型对恶性参数的提抗能力就越强。
在 TensorFlow 中,ExponentialMovingAverage() 可以传入两个参数:衰减率(decay)和数据的迭代次数(step),这里的 decay 和 step 分别对应我们的 $\beta$ 和 num_updates,所以在实现滑动平均模型的时候,步骤如下:
- 定义训练轮数 step
- 然后定义滑动平均的类
- 给这个类指定需要用到滑动平均模型的变量(w 和 b)
- 执行操作,把变量变为指数加权平均值
# 1、定义训练的轮数,需要用trainable=False参数指定不训练这个变量,
# 避免这个变量被计算滑动平均值
global_step = tf.Variable(0, trainable=False)
# 2、给定滑动衰减率和训练轮数,初始化滑动平均类
# 定训练轮数的变量可以加快训练前期的迭代速度
variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY,
global_step)
# 3、用tf.trainable_variable()获取所有可以训练的变量列表,也就是所有的w和b
# 全部指定为使用滑动平均模型
variables_averages_op = variable_averages.apply(tf.trainable_variables())
# 反向传播更新参数之后,再更新每一个参数的滑动平均值,用下面的代码可以一次完成这两个操作
with tf.control_dependencies([train_step, variables_averages_op]):
train_op = tf.no_op(name="train")
设置完使用滑动平均模型之后,只需要在每次使用反向传播的时候改为使用 run.(train_op) 就可以正常执行了。