NSDT工具推荐Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - AI模型在线查看 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割 - 3D道路快速建模

异常检测是机器学习产生如此大影响的领域之一,以至于今天几乎不言而喻的是,异常检测系统必须基于某种形式的自动模式学习算法,而不是基于一组规则或描述性统计数据(尽管 许多可靠的异常检测系统都使用此类方法非常成功且高效地运行)。

1、异常检测概述

事实上,在过去十年左右的时间里,各种机器学习异常检测方法变得越来越流行。 一些方法(例如 One-Class SVM)尝试识别数据分布的维度空间中的“正常”区域或平面,然后将位于该区域之外的任何样本标记为异常。 其他方法尝试估计代表训练数据的分布(或分布混合)的参数,然后将在该分布下看起来不太可能的任何样本指定为异常样本。 每种方法都有自己需要考虑的假设和弱点,这就是为什么测试异常检测算法并将其适合特定领域很重要的部分原因。

随着深度学习的广泛应用,另一种流行的异常检测方法基于重建方法,该方法获得了广泛的关注。 基本思想基于这样的假设:如果模型可以学习压缩和重建正常数据的函数,那么当遇到异常数据时它将无法做到这一点,因为它的函数仅在正常数据上进行训练。 因此,无法重建数据,或更准确地说,重建错误的范围可能表明存在异常数据。

异常检测的重构方法已经使用深度自动编码器(AE)实现,并取得了非常好的结果,尽管越来越多的文献表明使用更复杂和概率性的变分自动编码器(由 Diederik Kingma 和 Max Welling(2014)首次提出)可以改善结果。 尽管 VAE 主要被设计为图像和文本生成的生成模型,但我们将看到它们的一些特性也可以在异常检测领域中利用。

关于 VAE 的理论和数学有很多文章。 然而,这篇文章的目的是采取一种更实用或更实际的方法,使读者只需一点背景知识和一些代码即可快速构建可测试模型的原型。 完整的实现链接在一个可重复的笔记本中,该笔记本使用 KDDCup99 数据集,该数据集通常用作异常检测文献中的基准,并显示接近 SOTA 的结果。

这篇文章的内容如下:第二节非常简要地讨论了自动编码器和异常检测的重建方法。 请注意,这部分旨在快速回顾并假设读者了解自动编码器的工作原理。 如果没有,我不久前写了一篇关于自动编码器的简短文章,作为开始可能会有所帮助。 第三节重点介绍变分自动编码器以及它们与传统自动编码器的区别。 第四节深入研究代码和实现细节。 第五节总结。

2、自编码器和异常检测

自编码器(AutoEncoder)是一种深度学习模型,通常基于两个主要组件:学习输入数据的低维表示的编码器,以及尝试使用编码器生成的低维表示在其原始维度中再现输入数据的解码器。

该架构的基本思想与图像压缩的思想非常相似:训练好的编码器学习以捕获其包含的最重要信息的方式对输入数据进行编码,因此该信息将足够由解码器再现它。

AE 通过尝试最小化再现误差或原始输入向量与解码器从编码数据再现的输出向量之间的差异来学习对输入数据进行编码。 如下图所示,虽然使用自动编码器再现的图像缺少一些细节,但其总体形状被保留,这意味着输入图像中包含的至少一些重要信息被编码器捕获。

这与异常检测有什么关系? 简短而简单的答案是,如果我们的 AE 经过充分的训练,能够对其所训练的输入数据产生良好的再现,并假设它接受了足够的数据训练,那么它应该会产生或多或少稳定且最小的再现 当它输入的数据与其训练数据“相似”时,就会出错。 然而,这也意味着异常或极端的再现错误可能意味着 AE 遇到的输入向量与其训练时的输入非常不同,因此无法正确再现它。 如果向我们的 AE 显示的数据应该与它所训练的数据相似,那么产生极端再现错误的输入可能是异常的。

例如,如果我们在猫的图像上训练 AE,那么它可能会很难再现大象的图像,如果将这样的大象图像输入到在猫的图像上训练的 AE,那么它就会 可能会产生相对较高的再现错误,这可以正确指示异常。 当数据维度很高并且很难识别正常数据的行为以及哪些行为过于极端而不能被视为正常时,这种方法的最大好处就出现了。

3、变分自编码器

自编码器(AE) 以它认为最有效的方式将输入数据编码到潜在空间中以重现它。 简而言之,编码器学习一个“函数”,该函数采用大小为 n 的向量并输出大小为 m 的向量(使得 m < n),解码器函数可以轻松地使用该函数来重现原始输入向量。 例如,这意味着即使两个数据点非常相似,编码器也可以选择将它们放置在潜在空间中彼此相对较远的位置(如果这样可以最小化重建损失)。 这种架构中编码器函数的输出生成一个非常离散的潜在空间,并且通常类似于过度拟合的模型。 因此,虽然 AE 可以形成一个潜在空间,使其能够非常准确地执行其任务,但我们无法对其生成的潜在空间的分布和拓扑或数据的组织方式做出太多假设。

在 变分自编码器中(VAE:Variational AutoEncoder)中,编码器类似地学习一个函数,该函数将大小为 n 的向量作为输入。 然而,VAE 不是像传统 AE 那样学习如何生成解码器函数可以重现的潜在向量,而是学习生成两个向量(大小为 m),这两个向量表示分布的参数(均值和方差),其中 潜在向量被采样,并且解码器函数可以将其变换回原始输入向量。 简而言之,AE 的学习任务是学习一个函数,将数据转换为解码器可以轻松重现的潜在向量,而 VAE 的学习任务是学习一个函数,该函数将生成分布参数,从该分布参数中可以生成潜在向量。 解码器可以轻松地再现和采样。 更技术性地说:

In an AE: 
(*) encoder(input_vector[]) => latent_v[]
latent_v[] is our latent features vector
In a VAE: 
(*) encoder(input_vector[]) => latent_v_mu[], latent_v_lvar[]
So that - latent_v[0] ~  N(latent_v_mu[0], latent_v_lvar[0])
and latent_v[1] ~  N(latent_v_mu[1], latent_v_lvar[1])
As the other elements in the latent feature vector, latent_v[0] is sampled from a distribution parameterized by the mean and variance produced by the encoder (and which are forced by the KL loss function to be closer to N(0, 1))

因此,VAE 的潜在空间实际上是从编码器为每个潜在特征学习的分布中采样的。 上面没有提到的另一个重要细节是,VAE 使用由 2 个组件组成的损失函数:(1) 重建损失组件 — 它迫使编码器生成最小化重建损失的潜在特征,就像使用 AE,否则处罚; (2) KL 损失分量——强制编码器生成的分布与输入向量的先验概率相似,假设输入向量是正态的,因此将潜在特征空间推向正态。

因此,VAE 生成的潜在空间更加“驯服”并且趋于常态。 由于编码器经过严格正则化以生成正态分布,并且潜在向量本身是从正态分布中采样的,因此潜在空间将更加连续和平滑。 下图巧妙地显示了(1)仅基于重建损失生成潜在空间的编码器的潜在空间; (2) 编码器仅尝试最小化 KL 损失并强制分布呈正态性(即均值 0 和 var 1 ),这就是所有数据点都围绕同一中间区域分组的原因; (3) 结合重建损失和 KL 损失的 VAE 的潜在空间。

所有这些并不一定意味着 VAE 在每个异常检测任务中都会比 AE 表现得更好。 VAE 主要作为生成模型而发光,但生成平滑且连续的潜在空间的优势对于异常检测任务也很有价值,因为其结果将更加稳定,并且以此类任务经常需要的方式达到预期。

4、基于VAE的异常检测器实现

这部分的目的是快速深入研究一个可以检测异常的VAE的实现代码。 我使用了 KDDCup99 杯子异常检测数据集,该数据集通常用作异常检测文献中的基准。 在这里,我展示了代码的主要部分,而完整的实现可以在链接的笔记本中找到。

对于此实现,我基本上遵循了 VAE 上 Keras 博客中的代码示例,并进行了一些调整。 我们将从模型的实现开始,然后寻找异常情况。

4.1 VAE模型

编码器:第一个重要部分是编码器,它将大小为 n 的向量作为输入并生成潜在向量 (z)。 然而,回想一下,在 VAE 中,编码器首先学习构成潜在向量的分布参数,然后通过从该分布中采样来生成潜在向量 z。 如下所示(第 5-6 行),编码器首先学习 z 分布的均值和(对数)方差(即分别为 z_mean 和 z_log_var)。 然后,它使用 lambda 层对这个分布中的 z 进行采样,调用函数sample(z_mean, z_log_var),我们稍后会看到该函数返回采样的向量 z。

# encoder model
inputs = Input(shape=input_shape, name='encoder_input')
x = Dense(intermediate_dim, activation='relu')(inputs)
z_mean = Dense(latent_dim, name='z_mean')(x)
z_log_var = Dense(latent_dim, name='z_log_var')(x)
# use the reparameterization trick and get the output from the sample() function
z = Lambda(sample, output_shape=(latent_dim,), name='z')([z_mean, z_log_var])
encoder = Model(inputs, z, name='encoder')
encoder.summary()

Sample() 函数的目的是通过返回平均值 + sigma * epsilon 对正态分布 z 进行采样。 正如 Francois Chollet 解释的那样,epsilon 在这里的作用是确保潜在空间的连续性:

由于 epsilon 是随机的,因此该过程确保靠近编码 输入向量 的潜在位置的每个点都可以解码为类似于 [输入向量] 的内容,从而迫使潜在空间连续有意义 。 潜在空间中的任何两个接近点都将解码为高度相似的图像。 连续性与潜在空间的低维性相结合,迫使潜在空间中的每个方向对数据的有意义的变化轴进行编码,使潜在空间非常结构化,因此非常适合通过概念向量进行操作(使用Python进行深度学习) ,第二版)。

Chollet 显然试图让事情变得简单,尽管没有过多地深入理论(我自己并不完全理解),epsilon 引入的正态随机性也被认为对于网络通过反向传播不断纠正其参数至关重要。

def sample(args):
    z_mean, z_log_var = args
    batch = K.shape(z_mean)[0]
    dim = K.int_shape(z_mean)[1]
    epsilon = K.random_normal(shape=(batch, dim))
    return z_mean + K.exp(0.5 * z_log_var) * epsilon

解码器:解码器相当简单。 与传统的自编码器一样,它采用采样的潜在向量 z 作为输入并尝试重现它,只是在 VAE 的情况下,它实际上是生成组件。

# decoder model
latent_inputs = Input(shape=(latent_dim,), name='z_sampling')
x = Dense(intermediate_dim, activation='relu')(latent_inputs)
outputs = Dense(original_dim, activation='sigmoid')(x)
# Instantiate the decoder model:
decoder = Model(latent_inputs, outputs, name='decoder')
decoder.summary()

以及最终的模型。

# full VAE model
outputs = decoder(encoder(inputs))
vae_model = Model(inputs, outputs, name='vae_mlp')

VAE 的主要组成部分之一是损失函数,如上所述,它试图在两个优化任务之间取得平衡:(1)最小化重建误差——这可以通过误差项来完成,例如 MSE 作为 I 使用下面或与其他差异函数; (2) 最小化 KL 散度——本质上迫使 z 的分布趋于正态(例如,参见此处)。 这可能是您想要根据结果进行调整的参数。 两者之间的适当平衡将迫使 z 的分布趋于正态,同时确保网络能够重现输入向量。 这将确保我们创建一个平滑且连续的潜在空间(由于强加的正态性),并形成数据的准确表示,我们可以用它来检测异常和测量相似性。

# the KL loss function:
def vae_loss(x, x_decoded_mean):
    # compute the average MSE error, then scale it up, ie. simply sum on all axes
    reconstruction_loss = K.sum(K.square(x - x_decoded_mean))
    # compute the KL loss
    kl_loss = - 0.5 * K.sum(1 + z_log_var - K.square(z_mean) - K.square(K.exp(z_log_var)), axis=-1)
    # return the average loss over all 
    total_loss = K.mean(reconstruction_loss + kl_loss)    
    return total_loss

然后我们用组合损失函数拟合模型。

opt = optimizers.Adam(learning_rate=0.0001, clipvalue=0.5)

vae_model.compile(optimizer=opt, loss=vae_loss)
vae_model.summary()
# Finally, we train the model:
results = vae_model.fit(X_train, X_train,
                        shuffle=True,
                        epochs=32,
                        batch_size=256)

4.2 检测异常情况

异常检测的重建方法通过相对较高的重建误差来识别异常。 因此,当模型可以首先在正常或大部分正常数据上进行训练时,这些方法效果最佳。 通过这种方式,我们可以增加我们的信心,即相对较高的重建误差是由真正的异常引起的。

具体来说,以下通常是一个好的开始:

  • 测量原始训练(干净/正常)集与模型输出之间的误差,并生成表示每个样本的误差项的误差向量。
  • 找到该向量的相对极值用作错误阈值。 假设某些异常可能会在训练集中引入一些噪声,因此最好选择第 99 个百分位之类的值作为阈值(而不是最极端的值)。
  • 在测试或真实数据上运行模型,其中异常数据可能与正常数据混合在一起。
    测量重建误差并将误差项高于误差阈值的样本标记为异常。

X_train_pred = vae_model.predict(X_train)
error_thresh = np.quantile(mae_vector, 0.99)
mae_vector = get_error_term(X_train_pred, X_train, _rmse=False)

X_test_pred = vae_model.predict(X_test)
mae_vector_test = get_error_term(X_pred, X_test, _rmse=False)
anomalies = (mae_vector_test > error_thresh)

如下所示,在 KDDCup99 数据集上使用这个非常简化且直接的模型会产生非常令人印象深刻的结果(其中一些结果与该数据集报告的 SOTA 结果相差不远)。

我们还可以仅使用编码器模型而不使用解码器来检查编码器生成的潜在空间。

from sklearn.decomposition import PCA
X_encoded = encoder.predict(X_test)
pca = PCA(n_components=2)
X_transform = pca.fit_transform(X_encoded)

下图显示了编码器生成的潜在空间的散点图(在暗淡减少到 2 暗淡之后)。 每个点的颜色反映了其相关的重建误差项(在 mae_vector 中)。 较暗的点意味着较大的误差项。 我们可以清楚地看到一大群看起来很正常的点(误差项相对较小),周围有 3 个误差项相对较高的主簇。

我们可以通过下面的图来确认这一点,该图在将每个超过误差阈值的点标记为异常(橙色)后绘制了与上面相同的点。

最后,我们可以将上面的内容与下面的地面实况图进行比较,该图实际上显示了数据的真实标签。 也就是说,该图中橙色的点实际上是异常现象——网络网络攻击期间发送的网络数据包。 我们可以看到,虽然我们正确识别了绝大多数异常(其中 98%),但仍有一小部分我们未能识别,如图所示,可能是因为与正常点有一些相似之处。

5、结束语

变分自编码器被广泛认为对于各种机器学习任务非常有效。 关于变分自编码器的文章有很多,但在异常检测领域却没有太多实际例子。 这篇文章的目的是通过提供一个可用于原型设计和测试的简单示例来帮助填补这一空白。

VAE 的最大优点来自于对生成的潜在空间施加的正则化。 使用更平滑、更连续的向量空间的能力可以带来更稳定和准确的结果,因为它确保相似的数据点更靠近,并使相似性度量更可靠。 在提出一个简单的架构之后,我展示了 VAE 网络的这些品质如何通过相对较少的调整产生相当令人印象深刻的结果,尽管我试图强调如果结果不令人满意,则需要在哪里进行调整和实验。

我认为,在异常检测领域,能够试验和有效测试不同的方法非常重要,因为每个领域都有自己的“特征”,这些“特征”有时允许我们做出某些假设,有时禁止我们做出其他假设。 我希望这篇文章能让一些人在他们的工具箱中添加另一个工具,并鼓励更丰富的实验。


原文链接:Hands-on Anomaly Detection with Variational Autoencoders

BimAnt翻译整理,转载请标明出处