深度学习在表格和图像数据异常值检测中的应用

2025年01月06日 由 alex 发表 1031 0

在本文中,我将探讨为什么基于深度学习的方法在某些数据形式(特别是图像数据,但同样的思路也适用于视频、音频和其他一些数据类型)的异常检测中往往效果非常好,以及在表格数据中为何其应用会受到限制。


同时,我还将阐述几个尽管如此仍应认真考虑将深度学习用于表格数据异常检测的原因。一是该领域发展迅速,取得了大量进展,我们很有可能在未来几年内看到表格数据异常检测方面的一些最大进步就来自于此。


另一个是,虽然更传统的方法(包括基于z分数、四分位距、直方图等的统计测试,以及经典机器学习技术如孤立森林、k近邻、局部异常因子(LOF)和ECOD)往往更为可取,但也存在一些例外情况,即使在今天,也有一些情况下基于深度学习的方法可能是表格数据异常检测的最佳选择。我们也将对此进行探讨。


图像数据的异常检测

如前所述,对于一些数据形式,包括图像数据,今天除了基于深度学习的方法外,没有其他可行的异常检测选项,因此我们将首先从图像数据的基于深度学习的异常检测开始探讨。


本文假设你对神经网络和嵌入的概念有一定了解。如果不了解,我建议你阅读一些在线的入门文章,并跟上这一领域的步伐。


有许多这样的方法,但所有方法都以某种方式涉及深度神经网络,并且大多数方法都是通过生成嵌入来表示图像的。


一些最常见的基于深度学习的异常检测技术是基于自编码器、变分自编码器(VAE)和生成对抗网络(GAN)的。本文将介绍几种异常检测方法,但自编码器、VAE和GAN是一个很好的起点。


这些是比较陈旧但已确立的观点,是异常检测中一个常见主题的例子:工具或技术往往最初是为某一目的而开发的,后来发现它们在异常检测中也很有效。许多其他例子还包括聚类、频繁项集、马尔可夫模型、空间填充曲线和关联规则。


本文我将只讨论自编码器,自编码器实际上是一种最初为数据压缩而设计的神经网络。(其他一些压缩算法有时也用于异常检测。)


与聚类、频繁项集、关联规则、马尔可夫模型等一样,其思路是:我们可以使用某种类型的模型来建模数据,从而创建数据主要模式的简洁摘要。例如,我们可以通过描述聚类(如果数据聚类良好)、数据中的频繁项集、特征之间的线性关系等来建模数据。而使用自编码器时,我们通过数据的压缩向量表示来建模数据。


这些模型通常能够很好地表示数据中的典型项(假设模型构建良好),但往往无法很好地建模异常项,因此可以用来帮助识别异常项。例如,在聚类中(即使用一组聚类来建模数据时),异常项是那些不适合任何聚类的记录。在频繁项集中,异常项是包含很少频繁项集的记录。而在自编码器中,异常项是那些压缩效果不佳的记录。


当这些模型是深度神经网络的形式时,它们具有能够表示几乎任何类型数据(包括图像)的优势。因此,自编码器(以及其他深度神经网络,如VAE和GAN)在图像数据的异常检测中非常重要。


许多异常检测器还使用了一种称为自监督学习(SSL)的技术构建。这些技术在异常检测中的使用可能不如自编码器、VAE和GAN广泛,但它们非常有趣,值得至少快速了解一下。我将在下面介绍这些技术,但首先我将探讨一些使用图像数据进行异常检测的动机。


使用图像数据进行异常检测的动机

一个应用是在自动驾驶汽车中。汽车会有多个摄像头,每个摄像头检测一个或多个对象。然后,系统会预测图像中出现的每个对象是什么。这些系统面临的一个问题是,当摄像头检测到对象时,系统可能会对其类型做出错误的预测。而且,系统可能会错误地预测,但置信度很高;神经网络尤其倾向于对最佳匹配项表现出高置信度,即使在错误的情况下也是如此,这使得仅从分类器本身很难确定系统是否应该对检测到的对象更加谨慎。这种情况最容易发生在看到的对象与用于训练系统的任何训练示例都不同的情况下。


为了解决这个问题,异常检测系统可以与图像分类系统并行运行,当以这种方式使用时,它们通常会专门寻找似乎超出训练数据分布的项目,这被称为分布外数据(OOD)。


也就是说,任何视觉分类系统都是基于一些可能非常大但有限的对象集合进行训练的。在自动驾驶汽车中,这可能包括交通信号灯、停车标志、其他汽车、公交车、摩托车、行人、狗、消防栓等(模型将被训练为识别这些类别中的每一个,并在每个类别上进行大量实例训练)。但是,无论系统被训练识别多少种类型的项目,在道路上行驶时可能会遇到其他类型的(分布外)对象,因此确定系统何时遇到未识别的对象非常重要。


这实际上是图像数据异常检测中的一个常见主题:我们通常对识别不寻常的对象感兴趣,而不是不寻常的图像。也就是说,不寻常的照明、色彩、相机角度、模糊等图像本身的属性通常不那么有趣。通常,背景也可能分散我们对主要目标(即识别不寻常项目)的注意力。当然也有例外,但这种情况相当常见,即我们真正感兴趣的是图片中显示的主要对象(或少数几个相关对象)的性质。


自动驾驶汽车中对象分类错误可能是一个相当严重的问题——车辆可能会得出结论,认为一个新颖对象(例如在训练期间未见过的一种车辆)是完全另一种类型的对象,可能是视觉上最接近训练期间见过的任何对象类型。例如,它可能会预测这个新颖车辆是一个广告牌、电话杆或其他静止物体。但是,如果一个并行运行的异常检测器识别出这个对象是不寻常的(并且可能是分布外的,OOD),那么整个系统可以采取更加保守和谨慎的方法来处理这个对象,并可以激活任何相关的故障安全机制。


图像数据异常检测的另一个常见用途是在医学成像中,其中图像中出现的任何不寻常之物都可能令人担忧,并值得进一步调查。同样,我们对图像本身的不寻常属性不感兴趣——我们只关心图像中的任何对象是否是OOD:即与训练期间见过的任何对象都不同(或仅在训练期间很少见过),因此是罕见的,可能是个问题。


其他例子包括检测安全摄像头或监控工业流程的摄像头中出现的不寻常物体。同样,任何不寻常的事物都值得注意。


在自动驾驶汽车领域,检测异常分布(Out-Of-Distribution, OOD)物体可能有助于团队增强其训练数据。在医学成像或工业流程中,任何不寻常的事物往往都可能是问题的风险所在。而且,就像汽车一样,仅仅知道我们检测到了OOD物体,就可以让系统更加保守,不假定分类预测是正确的。


由于图像中OOD物体的检测是视觉领域异常检测的关键,因此相关的训练和测试通常都与此相关。在图像数据中,异常检测系统通常是在一组数据集合的图像上进行训练,而测试则使用另一组相似的数据集进行,假设这些图像足够不同,可以认为它们来自不同的分布(并包含不同类型的物体)。这样,就能测试检测OOD数据的能力。


例如,训练可能使用一组包含100种鸟类的图像进行,而测试则使用另一组鸟类的图像进行。我们通常假设,如果图像来源不同,那么第二组中的任何图像都会至少略有不同,并可以假定为异常分布,尽管也可以使用标签来更好地限定这一点:如果训练集包含,比如欧洲绿雀,而测试集也同样包含,那么合理地认为它们不是OOD。


自编码器

为了更具体地探讨如何使用神经网络进行异常检测,我们首先将介绍一种最实用且直接的方法:即自编码器。


如前所述,自编码器是一种神经网络形式,传统上用作压缩工具,但人们发现它们在异常检测方面也很有用。自编码器接收输入并学习以尽可能小的损失进行压缩,以便可以重建得接近原始输入。对于表格数据,自编码器一次处理一行,输入神经元对应于表格的列。对于图像数据,它们一次处理一张图像,输入神经元对应于图片的像素(尽管图像也可以以嵌入格式给出)。


下图提供了一个自编码器的示例。这是一种特定的神经网络形式,旨在不是预测一个单独的目标,而是再现输入给自编码器的数据。我们可以看到,网络的输入(网络最左边的神经元,显示为橙色)和输出(网络最右边的神经元,显示为绿色)的元素数量相同,但在中间,层的神经元数量较少。中间层具有最少的神经元;这一层表示每个对象的嵌入(也称为瓶颈或潜在表示)。


2


中间层的大小是我们尝试将所有数据压缩到的尺寸,以便可以在后续层中重新创建(或几乎重新创建)它。创建的嵌入本质上是一个简洁的浮点数向量,可以表示每个项目。


自编码器有两个主要部分:网络的前几层被称为编码器。这些层将数据逐渐压缩到更少的神经元,直到到达网络的中间。网络的第二部分被称为解码器:这是一组与编码器层对称的层,它们接收每个输入的压缩形式,并尝试尽可能接近地将其重建为原始形式。


如果我们能够训练一个倾向于具有低重建误差(网络的输出倾向于非常接近地匹配输入)的自编码器,那么如果某些记录具有高的重建误差,它们就是异常值——它们不遵循允许压缩的数据的一般模式。


压缩之所以可能,是因为表格数据中的特征之间、文本中的单词之间、图像中的概念之间等通常存在一些关系。当项目是典型的时,它们遵循这些模式,并且压缩可以相当有效(损失最小)。当项目不典型时,它们不遵循这些模式,并且无法在不造成更大损失的情况下进行压缩。


层的数量和大小是一个建模决策。数据中包含的模式越多(特征之间的规律关联越多),我们就越能够压缩数据,这意味着我们可以在中间层中使用更少的神经元。这通常需要一些实验,但我们希望设置网络的大小,以便大多数记录可以以非常小的误差(但有一些误差)被重建。


如果大多数记录可以以零误差被重建,那么网络可能容量过大——中间层能够完全描述正在传递的对象。我们希望任何不寻常的记录都有较大的重建误差,但同时也希望能够将其与典型记录的适度误差进行比较;如果几乎所有其他记录的误差都是0.0,那么很难判断一条记录的重建误差有多不寻常。如果发生这种情况,我们知道需要缩减模型的容量(减少神经元数量),直到不再可能为止。事实上,这可以是一种调整自编码器的实用方法——例如,从中间层有很多神经元开始,然后逐渐调整参数,直到得到想要的结果。


通过这种方式,自编码器能够为每个对象创建一个嵌入(项目的压缩形式),但我们通常不会在自编码器之外使用这个嵌入;异常分数通常完全基于重建误差。


然而,这并不总是如此。在中间层创建的嵌入是对象的合法表示,可以用于异常检测。下图显示了一个示例,其中我们使用两个神经元作为中间层,这允许将潜在空间绘制为散点图。x维表示出现在一个神经元中的值,y维表示出现在另一个神经元中的值。每个点表示一个对象的嵌入(可能是图像、声音片段、文档或表格行)。


然后,可以在潜在空间上使用任何标准的异常检测器(例如KNN、隔离森林、凸包、马氏距离等)。如果限制在两个或三个维度,这种异常检测系统具有一定的可解释性,但就像主成分分析(PCA)和其他降维方法一样,潜在空间本身是不可解释的。


3


假设我们使用重构误差来识别异常值,为了计算误差,可以使用任何距离度量来测量输入向量和输出向量之间的距离。通常使用余弦距离、欧氏距离或曼哈顿距离,同时还有其他一些也相当常见。在大多数情况下,最好在进行异常检测之前对数据进行标准化,这既能让神经网络更好地拟合,也能更公平地测量重构误差。基于此,每条记录的异常分数可以计算为重构误差除以某个参考数据集的中值重构误差。


另一种可能更稳健的方法是,不使用单一的重构误差度量,而是使用多个。这使我们能够有效地利用自编码器为每条记录生成一组特征(每个特征都与一种重构误差测量相关),并将这些特征传递给标准的异常检测工具,该工具将找出由一个或多个重构误差度量给出的异常大值的记录。


总的来说,自编码器可以是定位数据中异常值的有效手段,即使在特征众多且异常值复杂的情况下也是如此——例如,在跨越多个特征的表格数据中。自编码器的一个挑战是需要设置网络架构(网络的层数以及每层的神经元数量),以及与网络相关的许多参数(激活方法、学习率、丢弃率等),这可能很难做到。


任何基于神经网络的模型都必然比其他模型更难以调整。自编码器的另一个局限性是,它们可能并不适用于所有类型的异常检测。例如,在图像数据中,它们将在像素级别测量重构(至少如果像素被用作输入),这可能并不总是相关。


有趣的是,在这方面生成对抗网络(GAN)可能表现得更好。将GAN应用于异常检测的一般方法在某些方面相似,但稍微复杂一些。不过,这里的主要思想是,这种深度网络可以有效地用于异常检测,并且它们对任何类型的数据都同样有效,尽管不同的检测器会标记不同类型的异常值,而这些异常值可能比其他异常值更有趣或更无趣。


图像数据的自监督学习

如前所述,自监督学习(SSL)是另一种用于图像数据(以及所有其他类型数据)的异常检测技术,也值得一看。


如果你已经习惯于在其他上下文中使用深度学习,那么你可能已经熟悉SSL了。它在深度学习的大多数领域都相当标准,包括那些最终用于分类、回归、生成或其他任务的大型神经网络。而且,如果你对大型语言模型有所了解,那么你可能熟悉掩码词预测的想法,这是SSL的一种形式。


在处理图像时,我们的想法是,我们通常拥有大量图像集合,或者可以轻松地在网上获取大量图像。实际上,我们通常会使用本身以自监督方式训练的基础模型,但原则上我们可以自己做这件事,而且无论如何,我们在这里描述的大致就是创建基础模型的团队所做的。


一旦我们拥有了大量图像,这些图像几乎肯定是没有标签的,这意味着它们不能立即用于训练模型(训练模型需要定义某种损失函数,这需要为每个项目提供真实标签)。我们需要以某种方式为每张图像分配标签。一种方法是手动标注数据,但这既昂贵又耗时,还容易出错。另一种可能是使用自监督学习,而且在很多时候,这要实际得多。


在SSL中,我们找到一种方法来安排数据,使其可以以某种方式自动标记。如前所述,掩码是一种方式,在训练大型语言模型时非常常见,同样的掩码技术也可以用于图像数据。在图像中,我们不是掩码一个单词,而是掩码图像的一个区域(如下面的马克杯图像所示),并训练神经网络猜测被掩码区域的内容。


4


利用图像数据,还有其他几种自监督学习技术同样可行。


一般来说,这些技术都是基于创建所谓的代理任务或前置任务的原则。也就是说,我们训练模型去预测某些东西(比如图像的缺失区域),假装这是我们感兴趣的内容,而实际上我们的真正目标是训练一个能够理解图像的神经网络。我们也可以说,这个任务是实现这一目标的代理。


这一点很重要,因为无法直接针对异常检测进行训练;代理任务是必要的。通过这些任务,我们可以创建一个对图像有良好通用理解的基础模型(这种理解足够好,能够完成代理任务)。就像语言的基础模型一样,这些模型随后可以进行微调,用于其他任务。这些任务可以包括分类、回归等,也包括异常检测。


也就是说,通过这种方式训练(使用自监督学习创建标签,并训练模型完成代理任务以预测这个标签),可以创建一个强大的基础模型——为了完成代理任务(例如,估计图像被遮挡区域的内容),模型需要对所处理的图像类型有深刻的理解。这也意味着,它可能很好地设置好了,能够识别图像中的异常。


自监督学习在异常检测中的关键在于识别出好的代理任务,这些任务能够让我们创建出所建模领域的良好表示,并使我们能够可靠地识别出数据中的任何有意义异常。


在图像数据中,有很多机会定义有用的前置任务。我们有一个在其他许多模态中不具备的巨大优势:如果我们有一张物体的图片,并且以任何方式扭曲这张图片,它仍然是同一张物体的图片。而且,如前所述,我们最感兴趣的通常是物体本身,而不是图片。这允许我们对图像进行许多操作,这些操作即使不是直接地,也能支持我们最终的异常检测目标。


这些操作包括:旋转图像、调整颜色、裁剪和拉伸,以及其他类似的图像扰动。执行这些变换后,图像可能看起来相当不同,在像素级别上也确实有很大不同,但所显示的物体是相同的。


这至少为异常检测打开了两种方法。一种是利用这些变换为图像创建嵌入,并将嵌入异常的图像识别为异常。另一种是更直接地使用这些变换。我将在接下来的部分中描述这两种方法。


创建嵌入并使用特征建模

有许多方法可以为图像创建可能对异常检测有用的嵌入。我将在这里描述其中一种称为对比学习的方法。


这种方法利用了这样一个事实:同一图像的不同扰动版本将表示相同的物体,因此应该具有相似的嵌入。鉴于此,我们可以训练一个神经网络,给定同一图像的两个或多个变体,为它们提供相似的嵌入,同时为不同的图像分配不同的嵌入。这鼓励神经网络关注每个图像中的主要物体,而不是图像本身,并且对颜色、方向、大小等变化具有鲁棒性。


然而,对比学习只是为图像创建嵌入的一种方法,对于任何给定的异常检测任务,包括任何自监督方法在内的许多其他方法可能效果更好。


一旦我们有了图像的嵌入,就可以识别出嵌入异常的物体,即那些嵌入与其他大多数嵌入相距异常远的物体。为此,我们可以使用嵌入空间中的欧几里得距离、余弦距离或其他距离度量。


直接使用前置任务

另一种既有趣又相当有效的方法是更直接地使用这些扰动来识别异常。例如,考虑旋转图像。


给定一张图像,我们可以将其旋转0度、90度、180度和270度,因此得到同一图像的四个版本。然后,我们可以训练一个神经网络来预测,给定任何一张图像,它是被旋转了0度、90度、180度还是270度。就像上面的一些例子一样(其中异常可能是那些不能很好地拟合到簇中的项目、不包含频繁项模式、不能很好地压缩等),这里的异常是那些神经网络无法很好地预测其各个版本旋转了多少度的图像。


5


对于典型图像,当我们将图像的四种变体输入网络(假设网络训练良好)时,网络通常会准确预测出每种变体的旋转角度。但对于非典型图像,网络可能无法准确预测,或者预测置信度较低。


同样的方法也可以应用于其他扰动,包括翻转图像、缩放、拉伸等。在这些例子中,模型会预测图像的翻转方式、缩放比例或拉伸程度。


其中一些方法也可能适用于其他模态。例如,掩码几乎可以应用于任何模态。然而,有些方法并不具有普遍适用性;例如,翻转对于音频数据可能并不有效。


图像数据的异常检测技术


这里我将回顾一些最常见的选项:

  • 自编码器、变分自编码器和生成对抗网络。这些方法已经相当成熟,并且很可能是异常检测中最常见的方法。
  • 特征建模——为每个对象创建嵌入,并在嵌入上使用标准的异常检测算法(如孤立森林、局部异常因子(LOF)、k近邻(KNN)或类似算法)。
  • 直接使用预训练任务。例如,预测图像的旋转、拉伸、缩放等。这是一个有趣的方法,并且可能是异常检测中最有用的方法之一。
  • 置信度评分——在这里,我们考虑使用一个分类器,并且所有类别的置信度都很低。如果训练一个分类器来识别100种鸟类,那么当输入一张新图像时,它会为这100种鸟类中的每一种生成一个概率。如果这些概率都非常低,那么该对象在某种程度上是不寻常的,很可能是分布外的。如前文所述,分类器即使在错误时也经常提供高置信度,因此这种方法并不总是可靠。但即使在不可靠的情况下,当返回低置信度评分时,系统可以利用这一点并认识到图像在某种程度上是不寻常的。


神经网络与图像数据

对于图像数据,我们可以充分利用深度神经网络,它们可以创建非常复杂的数据模型:我们有大量数据可用,可以使用自编码器、变分自编码器和生成对抗网络等工具,并且自监督学习是相当可行的。


深度神经网络的一个重要特性是它们可以扩展到非常大的规模,这使它们能够利用更多数据并创建更复杂的模型。


这与更传统的异常检测模型(如频繁模式异常因子(FPOF)、关联规则、k近邻、孤立森林、LOF、半径等)形成对比:随着它们训练更多数据,它们可能会开发出对正常数据稍微更准确的模型,但往往会在某个时间点之后趋于平稳,从训练更多数据中获得的收益会大大减少。另一方面,深度学习模型往往会继续利用更多数据的优势,即使在已经使用了大量数据之后也是如此。


然而,我们应该注意到,尽管在图像异常检测方面已经取得了很大进展,但这仍然是一个未解决的问题。至少当严格处理分布外数据时,它比其他模态的主观性要小一些(尽管当对象真的与训练期间看到的对象类型不同时,仍然有些模糊——例如,在鸟类中,松鸦和蓝松鸦是否是不同的类别)。图像数据处理起来具有挑战性,而异常检测仍然是一个具有挑战性的领域。


基于深度学习的异常检测工具

有几种工具可用于基于深度学习的异常检测。其中三个是PyOD、DeepOD和Alibi-Detect。


PyOD,它可能是可用于Python中表格数据异常检测的最全面的工具。它包含几个标准的异常检测器(孤立森林、局部异常因子、核密度估计(KDE)、基于直方图的异常检测(HBOS)、高斯混合模型(GMM)和其他几个),以及一些基于自编码器、变分自编码器、生成对抗网络及其变体的深度学习模型。


DeepOD提供表格数据和时间序列数据的异常检测。


Alibi-Detect涵盖表格数据、时间序列数据和图像数据的异常检测。下面是一个使用图像数据的示例。


大多数深度学习工作都是基于TensorFlow/Keras或PyTorch(其中PyTorch的市场份额越来越大)。同样,大多数基于深度学习的异常检测也使用这两者之一。


在我看来,PyOD可能是这三个库中最简单易用的,但它们都相当易于管理且文档齐全。


使用PyOD的示例

本节展示了使用PyOD的自编码器异常检测器对表格数据集(具体来说是KDD数据集,该数据集以公共许可证提供)的示例。


在使用PyOD之前,需要安装它,可以通过以下方式完成:


pip install pyod


然后,如果尚未安装TensorFlow或PyTorch(具体取决于所使用的检测器),你需要安装其中之一。我使用了Google Colab来完成这项工作,它已经预装了TensorFlow和PyTorch。本示例使用的是PyOD的AutoEncoder异常检测器,该检测器在底层使用了PyTorch。


import pandas as pd
import numpy as np
from sklearn.datasets import fetch_kddcup99
from pyod.models.auto_encoder import AutoEncoder
# Load the data
X, y = fetch_kddcup99(subset="SA", percent10=True, random_state=42, 
                      return_X_y=True, as_frame=True)
# Convert categorical columns to numeric, using one-hot encoding
cat_columns = ["protocol_type", "service", "flag"]
X = pd.get_dummies(X, columns=cat_columns)
det = AutoEncoder()
det.fit(X)
scores = det.decision_scores_


尽管自动编码器比PyOD支持的许多其他检测器(例如,基于直方图的HBOS、基于线性回归的Cook距离;其他一些检测器也相对简单)更复杂,但在PyOD中使用自动编码器检测器的接口同样简单。特别是在本示例中,我们使用了默认参数,这一点尤为明显。对于PyOD提供的基于变分自编码器(VAE)和生成对抗网络(GAN)的检测器也是如此,这些检测器在底层实现上甚至比自动编码器还要复杂一些,但除了参数之外,其应用程序接口(API)是相同的。


在本示例中,我们只需加载数据,将分类列转换为数值格式(这对于任何神经网络模型都是必要的),创建一个自动编码器检测器,拟合数据,并评估数据中的每条记录。


Alibi-Detect

Alibi-Detect也支持用于异常检测的自动编码器。在创建检测器时,它比PyOD需要更多的编码;这可能会稍微增加一些工作量,但也提供了更大的灵活性。Alibi-Detect的文档提供了几个示例,这些示例对你入门非常有用。


下面的列表提供了一个示例,可以帮助解释总体思路,但最好阅读他们的文档和示例,以全面了解该过程。该示例也使用了自动编码器异常检测器。由于Alibi-Detect可以支持图像数据,因此我们提供了一个使用图像数据的示例。


使用深度神经网络可能会很慢。为此,我建议如果可能的话使用GPU。例如,Alibi-Detect文档中的一些示例,或我测试过的这些示例的变体,在使用CPU运行时可能需要大约1小时,但在使用T4 GPU运行时只需大约3分钟。


图像数据的异常检测示例

在本示例中,我仅提供了一些通用代码,这些代码可以用于任何数据集,尽管需要调整层的维度以匹配所使用图像的大小。本示例仅调用一个未定义的方法load_data()来获取相关数据(下一个示例将更详细地查看特定数据集——在这里我只是展示Alibi-Detect使用的通用系统)。


本示例首先使用Keras(如果你更熟悉PyTorch,使用Keras时的思路是相似的)来创建自动编码器所使用的编码器和解码器,然后将这些作为参数传递给Alibi-Detect提供的OutlierAE对象。


与图像数据一样,神经网络包括卷积层。这些层有时也用于其他类型的数据,包括文本和时间序列数据,但很少用于表格数据。它还使用了一个全连接层。


代码假设图像是32x32的。如果使用其他尺寸,解码器必须组织成能够输出相同尺寸的图像。OutlierAE类通过比较输入图像和输出图像(输入图像通过编码器和解码器后)来工作,因此输出图像必须与输入图像具有相同的尺寸。当使用本示例中的Conv2D和Conv2DTranspose层时,这一点比使用全连接层时更加棘手。


然后我们调用fit()和predict()。对于fit(),我们指定了五个训练周期。使用更多周期可能会获得更好的效果,但也需要更多时间。Alibi-detect的OutlierAE使用重建误差(具体来说是重建图像与原始图像之间的均方误差)。


import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
tf.keras.backend.clear_session()
from tensorflow.keras.layers import Conv2D, Conv2DTranspose, \
    Dense, Layer, Reshape, InputLayer, Flatten
from alibi_detect.od import OutlierAE
# Loads the data used
train, test = load_data() 
X_train, y_train = train
X_test, y_test = test
X_train = X_train.astype('float32') / 255
X_test = X_test.astype('float32') / 255
encoding_dim = 1024
# Defines the encoder portion of the AE
encoder_net = tf.keras.Sequential([ 
      InputLayer(input_shape=(32, 32, 3)),
      Conv2D(64, 4, strides=2, padding='same', activation=tf.nn.relu),
      Conv2D(128, 4, strides=2, padding='same', activation=tf.nn.relu),
      Conv2D(512, 4, strides=2, padding='same', activation=tf.nn.relu),
      Flatten(),
      Dense(encoding_dim,)])
# Defines the decoder portion of the AE
decoder_net = tf.keras.Sequential([ 
      InputLayer(input_shape=(encoding_dim,)),
      Dense(4*4*128),
      Reshape(target_shape=(4, 4, 128)),
      Conv2DTranspose(256, 4, strides=2, padding='same',
          activation=tf.nn.relu),
      Conv2DTranspose(64, 4, strides=2, padding='same', 
          activation=tf.nn.relu),
      Conv2DTranspose(3, 4, strides=2, padding='same', 
          activation='sigmoid')])
# Specifies the threshold for outlier scores
od = OutlierAE(threshold=.015,  
               encoder_net=encoder_net,  
               decoder_net=decoder_net)
od.fit(X_train, epochs=5, verbose=True)
# Makes predictions on the records
X = X_train
od_preds = od.predict(X,
                      outlier_type='instance',    
                      return_feature_score=True,  
                      return_instance_score=True)
print("Number of outliers with normal data:",  
   od_preds['data']['is_outlier'].tolist().count(1))


这段代码对训练数据中的行进行预测。理想情况下,这些行中没有异常值。


使用PyTorch的示例

由于自动编码器创建起来相当简单,因此通常可以直接创建,也可以使用Alibi-Detect或PyOD等工具来创建。在本示例中,我们将使用MNIST数据集(该数据集以公共许可证提供,在此情况下随PyTorch的torchvision一起分发),并展示一个使用PyTorch的快速示例。


import numpy as np
import torch
from torchvision import datasets, transforms
from matplotlib import pyplot as plt
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision.utils import make_grid
# Collect the data
train_dataset = datasets.MNIST(root='./mnist_data/', train=True, 
  transform=transforms.ToTensor(), download=True)
test_dataset = datasets.MNIST(root='./mnist_data/', train=False, 
  transform=transforms.ToTensor(), download=True)
# Define DataLoaders
batchSize=128
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batchSize, shuffle=True) 
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=batchSize, shuffle=False)  
# Display a sample of the data
inputs, _ = next(iter(test_loader))
fig, ax = plt.subplots(nrows=1, ncols=10, figsize=(12, 4))
for i in range(10):
  ax[i].imshow(inputs[i][0])
plt.tight_layout()
plt.show()
# Define the properties of the autoencoder
num_input_pixels = 784  
num_neurons_1 = 256  
num_neurons_2 = 64   
# Define the Autoencoder
class Autoencoder(nn.Module):
    def __init__(self, x_dim, h_dim1, h_dim2):
        super(Autoencoder, self).__init__()
        
        # Encoder
        self.layer1 = nn.Linear(x_dim, h_dim1)
        self.layer2 = nn.Linear(h_dim1, h_dim2)
        # Decoder
        self.layer3 = nn.Linear(h_dim2, h_dim1)
        self.layer4 = nn.Linear(h_dim1, x_dim)
    def encoder(self, x):
        x = torch.sigmoid(self.layer1(x))
        x = torch.sigmoid(self.layer2(x))
        return x
    def decoder(self, x):
        x = torch.sigmoid(self.layer3(x))
        x = torch.sigmoid(self.layer4(x))
        return x
    def forward(self, x):
        x = self.encoder(x)
        x = self.decoder(x)
        return x
model = Autoencoder(num_input_pixels, num_neurons_1, num_neurons_2)
model.cuda()
optimizer = optim.Adam(model.parameters())
n_epoch = 20
loss_function = nn.MSELoss()
for i in range(n_epoch):
    train_loss = 0
    for batch_idx, (data, _) in enumerate(train_loader):
        data = data.cuda()        
        inputs = torch.reshape(data,(-1, 784)) 
        optimizer.zero_grad()
        # Get the result of passing the input through the network      
        recon_x = model(inputs)        
        # The loss is in terms of the difference between the input and
        # output of the model
        loss = loss_function(recon_x, inputs)        
        loss.backward()
        train_loss += loss.item()
        optimizer.step()
        
    if i % 5 == 0:    
        print(f'Epoch: {i:>3d} Average loss: {train_loss:.4f}')
print('Training complete...')


本示例使用了CUDA,但在没有GPU的情况下可以移除这部分。


在这个示例中,我们收集数据,为训练数据和测试数据创建DataLoader(这是大多数使用PyTorch的项目都会做的),并展示数据的一个样本,我们在这里看到:


6


数据包含手写数字。


接下来,我们定义一个自动编码器,其中清晰地定义了编码器和解码器。任何通过自动编码器的数据都会经过这两个部分。


自动编码器的训练方式与PyTorch中的大多数神经网络相似。我们定义一个优化器和损失函数,并在一定数量的epoch(这里使用20个)中迭代数据,每次以一定数量的批次覆盖数据(这里使用的批次大小为128,因此给定完整数据大小,每个epoch有16个批次)。在每个批次之后,我们计算损失,该损失基于输入向量和输出向量之间的差异,然后更新权重,并继续训练。


执行以下代码后,我们可以看到,对于大多数数字,重构误差非常小:


inputs, _ = next(iter(test_loader))next(iter(test_loader))
fig, ax = plt.subplots(nrows=1, ncols=10, figsize=(12, 4))
for i in range(10):
  ax[i].imshow(inputs[i][0])
plt.tight_layout()
plt.show()
inputs=inputs.cuda()
inputs=torch.reshape(inputs,(-1,784))
outputs=model(inputs)
outputs=torch.reshape(outputs,(-1,1,28,28))
outputs=outputs.detach().cpu()
fig, ax = plt.subplots(nrows=1, ncols=10, figsize=(12, 4))
for i in range(10):
  ax[i].imshow(outputs[i][0])
plt.tight_layout()
plt.show()


7


8


然后,我们可以使用非分布内的数据进行测试,在这个例子中,传入一个接近“X”的字符(因此不同于它训练时所用的任何一个数字0到9)。


inputs, _ = next(iter(test_loader))next(iter(test_loader))
for i in range(28):
  for j in range(28):
    inputs[0][0][i][j] = 0
    if i == j:
      inputs[0][0][i][j] = 1
    if i == j+1:
      inputs[0][0][i][j] = 1
    if i == j+2:
      inputs[0][0][i][j] = 1
    if j == 27-i:
      inputs[0][0][i][j] = 1
fig, ax = plt.subplots(nrows=1, ncols=10, figsize=(12, 4))
for i in range(10):
  ax[i].imshow(inputs[i][0])
plt.tight_layout()
plt.show()
inputs=inputs.cuda()
inputs=torch.reshape(inputs,(-1,784))
outputs=model(inputs)
outputs=torch.reshape(outputs,(-1,1,28,28))
outputs=outputs.detach().cpu()
fig, ax = plt.subplots(nrows=1, ncols=10, figsize=(12, 4))
for i in range(10):
  ax[i].imshow(outputs[i][0])
plt.tight_layout()
plt.show()   


输出:


9


10


在这个例子中,我们看到“X”的重构误差并不是特别大——它能够重构出看起来像“X”的字符,但相对于其他字符,这个误差异常地大。


结论

深度学习对于包括图像数据在内的多种模态的异常检测是必要的,并且在其他尚未完全确立的领域(如表格数据)也显示出前景。然而,目前,对于表格数据,更传统的异常检测方法仍然倾向于效果最好。


尽管如此,现在已经有一些情况,基于深度学习的异常检测方法可以是识别表格数据中异常的最有效方法,或者至少可以作为所考察方法中有用的一部分(并可能包含在更大的检测器集合中)。


使用深度学习进行异常检测有多种方法,我们可能会在未来几年看到更多方法的发展。其中最成熟的一些方法包括自编码器、变分自编码器和生成对抗网络(GAN),并且在主要的异常检测库(包括PyOD和Alibi-Detect)中对这些方法有良好的支持。



文章来源:https://medium.com/towards-data-science/deep-learning-for-outlier-detection-on-tabular-and-image-data-90ae518a27b3
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
热门职位
Maluuba
20000~40000/月
Cisco
25000~30000/月 深圳市
PilotAILabs
30000~60000/年 深圳市
写评论取消
回复取消