单指标时间序列异常检测——基于重构概率的变分自编码(VAE)代码实现(详细解释)
Posted smile-yan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单指标时间序列异常检测——基于重构概率的变分自编码(VAE)代码实现(详细解释)相关的知识,希望对你有一定的参考价值。
1. 编写目的
不少论文都是基于VAE完成的异常检测,比如 Donut 、Bagel。尽管 Donut 实现的模型很容易通过继承于重写父类方法的方式实现一个 VAE-baseline,并且 Bagel 中自带了一个 VAE-baselina(感兴趣的小伙伴可以前去查看一下源码),但为了简化过程,详细解释 VAE 用于单指标时间序列异常检测的方法,我重新实现了一个简单的 VAE-baselina,并进行了详细的解释,希望可以帮助到需要的小伙伴们。
可以的话,点个赞并发表评论吧 ~ 一定回复 ~~
2. 参考资料
名称 | 链接 |
---|---|
vae + 重构概率 => 异常检测 | https://blog.csdn.net/smileyan9/article/details/109255466 |
donut 进行了一定的改进的 vae | https://blog.csdn.net/smileyan9/article/details/112307506 |
Bagel 条件变分自编码 | https://smileyan.blog.csdn.net/article/details/113463339 |
tensorflow官网实现->卷积 vae | https://tensorflow.google.cn/tutorials/generative/cvae |
龙书作者 vae 源码 | https://github.com/dragen1860/TensorFlow-2.x-Tutorials/blob/master/12-VAE/main.py |
版权声明
未经本人 (smile-yan) 允许,不得转发与转载。
3. 源代码
整个项目的源码地址为:https://github.com/smile-yan/vae-anomaly-detection-for-timeseries,觉得可以的话,顺手点个星星吧,感谢~
3.1 网络结构
图片源地址为龙书作者开源地址 https://github.com/dragen1860/TensorFlow-2.x-Tutorials/tree/master/12-VAE
我们简化整个过程,写到 VAE 的初始化函数中如下所示:
class VAE(tf.keras.Model):
def __init__(self, latent_size=4):
super(VAE, self).__init__()
# 与输入数据对接
self.fc1 = tf.keras.layers.Dense(100)
# fc1 => μ and log σ^2
self.fc2 = tf.keras.layers.Dense(latent_size)
self.fc3 = tf.keras.layers.Dense(latent_size)
# decode
self.fc4 = tf.keras.layers.Dense(100)
# 试图还原原始数据
self.fc5 = tf.keras.layers.Dense(120)
所以这个部分可以概述为 fc1 与 fc2 构成了 encode 的过程,此过程完成以后,我们可以得到由 μ \\mu μ 和 log σ 2 \\log \\sigma^2 logσ2 组成的隐变量。
而 decode 过程则是分两步还原原始数据。
3.2 encode 过程
初始化过程只是初始化我们要用的变量,真正的 编码过程从这个函数开始,这个过程是非常简单的,可以看作盲盒降维操作。
def encode(self, x):
"""encode过程,返回 μ 和 log σ^2
:param x: 单窗口数据
:return: μ 和 log σ^2
"""
h = tf.nn.relu(self.fc1(x))
# mu, log_variance
return self.fc2(h), self.fc3(h)
3.3 decode 过程
为了重用方便,将 decode 按照是否使用 sigmoid 函数分为两个过程,这个与 https://tensorflow.google.cn/tutorials/generative/cvae 的 decode 函数添加一个参数的效果是一样的。
def decode_logits(self, z):
h = tf.nn.relu(self.fc4(z))
return self.fc5(h)
def decode(self, z):
return tf.nn.sigmoid(self.decode_logits(z))
3.4 概率密度方法(Probability Density Function)
decode 过程可以分为两个步骤,如下面右图所示,可以理解为
g
(
z
)
g(z)
g(z) 对应的是一个数据分布,而
g
(
z
)
g(z)
g(z) 以后的就是重构数据,也就是说重构数据可以理解为从这个分布中采样而得到的,当然,深度神经网络采样过程是模糊的,所以这样的对应关系可以理解为采样得到的,至于怎么采样就是神经网络的参数调整过程了。
所以训练目标,就是使得得到的分布
g
(
z
)
g(z)
g(z) 中采样得到的
x
′
x'
x′ 尽可能地接近于观测数据
x
x
x。接下来我们计算重构概率就是基于这个理论:
使用正常数据训练模型后,对于测试数据 x t x_t xt:
- 如果它是正常数据,那么它在 g ( z ) g(z) g(z) 对应的分布中,分布密度较大;
- 如果它是异常数据,那么它在 g ( z ) g(z) g(z) 对应的分布中,分布密度较小。
所以计算一个测试数据的重构概率,实质就是计算这个 g ( z ) g\\ (z) g (z) 分布中的分布密度。
考虑到没有基础的小伙伴们,现在我们快速需要介绍一下分布密度的计算:
这里是参考维基百科的内容 https://en.wikipedia.org/wiki/Probability_density_function :
更一般的情况,我们会计算分布密度的对数值,这里做一个简单的公式推导:
log_normal_pdf ( x ; μ , σ 2 ) = log 1 σ 2 π e − 1 2 ( x − μ σ ) 2 = − log ( σ 2 π ) − 1 2 ( x − μ σ ) 2 = − log σ − 1 2 log 2 π − 1 2 ( x − μ σ ) 2 = − 1 2 ( log σ 2 + log 2 π + ( x − μ ) 2 σ 2 ) (1) \\textlog\\_normal\\_pdf\\ (x; \\mu, \\sigma^2) = \\log \\frac1\\sigma\\sqrt2\\pi \\ e^-\\frac12(\\fracx-\\mu\\sigma)^2 \\\\ = - \\log\\ (\\sigma \\sqrt2\\pi) - \\frac12(\\fracx-\\mu\\sigma)^2 \\\\ = - \\log \\sigma - \\frac12 \\log 2\\pi - \\frac12(\\fracx-\\mu\\sigma)^2 \\\\ = -\\frac12\\bigl(\\log \\sigma^2 + \\log 2\\pi + \\frac(x-\\mu)^2\\sigma^2\\bigr) \\tag1 log_normal_pdf (x;μ,σ2)=logσ2π1 e−21(σx−μ)2=−log (σ2π)−21(σx−μ)2=−logσ−21log2π−21(σx−μ)2=−21(logσ2+log2π+σ2(x−μ)2)(1)
所以我们可以得到一个 log_normal_pdf 的 python 代码,如下所示:
def log_normal_pdf(sample, mean, log_var, axis=1):
log2pi = tf.math.log(2. * np.pi)
return tf.reduce_sum(-.5 * ((sample - mean) ** 2. * tf.exp(-log_var) + log_var + log2pi), axis=axis)
在后面的内容中我们会多次用到这个计算方法。
3.5 重参数化
重参数化过程就是建立 z z z 与 μ \\mu μ 、 σ \\sigma σ 之间的关系,为了简化这个关系一般都会直接使用最简单的线性关系(同时也要考虑到梯度下降的需要),所以会令
z = μ + ε ⋅ σ (2) z = \\mu + \\varepsilon \\cdot \\sigma \\tag2 z=μ+ε⋅σ(2)
这个对应的python 代码实现非常简单:
def reparameterize(mu, log_var):
"""重参数化,计算隐变量 z = μ + ε ⋅ σ
:param mu: 均值
:param log_var: 方差的 log 值
:return: 隐变量 z
"""
std = tf.exp(log_var * 0.5)
eps = tf.random.normal(std.shape)
return mu + eps * std
3.6 VAE 的损失函数 ELBO 的计算
在 《VAE 模型基本原理简单介绍》 中我们已经比较了解了VAE的面貌,VAE 的损失函数 ELBO 的计算方法如下公式:
log p ( x ) ≥ ELBO = E q ( z ∣ x ) [ log p ( x , z ) q ( z ∣ x ) ] (3) \\log p(x) \\ge \\textELBO = \\mathbbE_q(z|x)\\left[\\log \\fracp(x, z)q(z|x)\\right] \\tag3 logp(x)≥ELBO=Eq(z∣x)[logq(z∣x)p(x,z)](3)
为了方便我们利用对数函数的性质拆解成三个式子的和:
ELBO = E q ( z ∣ x ) [ log p ( x ∣ z ) + log p ( z ) − log q ( z ∣ x ) ] (4) \\textELBO = \\mathbbE_q(z|x)\\left[ \\log p(x| z) + \\log p(z) - \\log q(z|x) \\right] \\tag4 ELBO=Eq(z∣x)[logp(x∣z)+logp(z)−logq(z∣x)](4)
像这种条件概率计算一般可以使用蒙特卡洛方法进行求解,也就转换成 从特定分布
q
(
z
∣
x
)
q(z|x)
q(z∣x) 中采样得到
z
z
z ,然后计算一下式子的值:
log
p
(
x
∣
z
)
+
log
p
(
z
)
−
log
q
(
z
∣
x
)
(5)
\\log p(x| z) + \\log p(z) - \\log q(z|x) \\tag5
log以上是关于单指标时间序列异常检测——基于重构概率的变分自编码(VAE)代码实现(详细解释)的主要内容,如果未能解决你的问题,请参考以下文章
单指标时间序列异常检测——基于重构概率的变分自编码(VAE)代码实现(详细解释)