异常检测从入门到应用

点击上方“小白学视觉”,选择加"星标"或“置顶”

重磅干货,第一时间送达da702faaf214435ac485295a00955008.jpeg

作者:成森@知乎

来源:https://zhuanlan.zhihu.com/p/116235115

异常检测(Anomaly Detection), 作为机器学习的一个重要分支,实际应用领域广泛,本文作者通过一些有趣的卡通形象作为例子讲解了异常检测的基本概念及分类,并介绍了带标签与不代表标签的异常检测方法和计算过程。

01

什么是 Anomaly(异常)?

虽然说是异常,但其实是以训练集为核心,判断输入数据是否与训练集中的数据 “类似”。在不同的领域可以有不同的叫法,比如:outlier Detection,novelty Detection,exceptions Detection。

至于什么才是“类似”,它的定义取决于你所用的方法。如下图所示,如果你给的训练集只有雷丘,那么比卡丘就是“异常”;相反,如果你给的训练集是比卡丘,那雷丘就是“异常”。

c2534333d620a44f9ce5b75a046db387.jpeg

什么是异常取决于你所给的训练集

1.1 问题定义 Problem Formulation

  • 给定一个训练集 

  • 我们要找到一个函数来检测 输入 x 是不是属于训练集(是否和训练集的数据属于同一类)

fc77e1234b544ce2d2e58becd97d5ebc.jpeg

用来判断是否和训练集属于一类

1.2 为什么不能用二分类来解决这个问题?

如上面所说的,所谓“异常”,其实就是看是否和训练集“相似”,虽然我们很容易获得正训练集(如上面的雷丘),但是负训练集我们无法来决定,如果我们用 宝可梦 来作为负训练集来训练二分类,那下次输入一个 亚古兽 呢?这时候二分类模型就无法识别这个没见过的 负样本,而这样的负样本实在是太多了,我们没法穷举。如下图所示。

3965e78c987811670349d20a39606885.jpeg

负样本的种类太多反而无法用来训练二分类器

更坏的情况就是,很多情景下,我们没法收集到负样本。比如说刷卡行为,大多数情况都只是正常的交易行为,而盗刷这一类情况就少之又少,甚至(目前)没有。

所以异常检测无法简化成二分类来实现,这是一个独立的研究主题。

1.3 异常检测模型分类

根据给出的训练集,我们可以大致将其分成两大类三小类:

  • (labeled)训练集中每个样本都有标签,用这些样本来训练一个分类器,这个分类器除了能够识别训练集中已有样本标签外,还能输出 “unknown” 标签,用来表示该输入是“没见过的”、不在训练集中的。我们把它叫做 “open-set recognition(开放式识别)”

  • (unlabeled)另一种情况是,我的训练集是没有标签的

    • (clean)但这个训练集是“干净”的,我们可以将这个训练集里所有的样本都视为“正样本“;

    • (polluted)然而干净的数据集在现实应用中很少,大多数都是或多或少参杂着”异常样本“,而且你无法知道,比如说银行给你大量刷卡数据进行训练,而这些数据里有可能有盗刷的数据且没有标注出来。

02ef7e34dd4ae6afb2321b389098ef53.jpeg

异常检测的两大类三小类


02

Labeled(带标签)

在这里就用 辛普森家族 来举例子。这里有一堆辛普森家族的人物形象及其对应的人物名称(视为标签),这样我们就能训练一个“辛普森角色分类器”,输入一个人物的形象,输出该人物的名称(标签)。

06a960812e49d9ecfec1c0074312934c.jpeg

辛普森家族的人物形象及其名称标签,训练一个分类器

那我们训练好“辛普森角色分类器”后,这个分类器会输出两类数据:类别(预测的名字)、信心值;然后给定一个阈值  ,当信心度大于这个阈值,就视为“正常值(属于辛普森家族)”,低于则视为“异常值(不属于辛普森家族)”

41340dfc1c17e753d788751d2bb04047.jpeg

分类器的使用

分类器的输出其实是一个概率分布(distribution),输出前经过一个 softmax,使得这个分布中的值之和为1,其输出每一项 表示 每一个类别及其对应的信心值;我们将其中的最大值,视为分类器对该输入的信心值.

除了最高值,我们还可以用 熵(entropy)来决定分类器最后的信心值。

如下图所示,第一个分类器中,霸子的信心值很高(总体熵低),且其他很低,就说明分类器能够很好地把这个人物形象进行分类,故认为该形象是“正常值”;

而第二个分类器中,每一类的信心值都不高且均匀(最大值不高,总体熵高),说明分类器没见过该形象,信心不够,没法很好地区分,则可以认为该形象是“异常值”。

5025a9fc2e0a34e3da0afa8a1f248236.jpeg

辛普森家族分类器,输出了类别和信心度

除了用分类器与其输出的分布来 判断 该分类器的信心度,当然还有其他的方式,如下图所说的,不仅输出分布(用来分类),还教分类器直接输出其信心度(直接可以判断是否异常)。

eb6aa2c68f746f16cde501f9265f6bf3.jpeg

可以直接输出分类和信心值的分类器

关于上面模型训练部分,一般来说我们有个“验证集”来调节模型的超参数,在“训练集”中,所有的样本都是“正样本“且有各自的标签(如都是辛普森和他们的人物名字),而在”验证集“中就没必要每个样本都有其“人物标签”,只需要判断其“是否属于辛普森家族”就行了(两个标签:属于、不属于)。

2.1 评价标准

在上面的例子中,“辛普森家族异常检测模型“其实是一个普通分类器,那我们是否也可以用准确率(Accuracy)来评估这个模型的好坏呢?答案是:这不是一个好的选择。

正如我们前面所说的,异常检测的数据集的标签分布是不均匀的,也就是说我们很大概率能够找到“正样本”而缺少“负样本”;这种情况下,如果模型“无情地”把所有的样本都预测成正样本,那准确率也会很高,这样显然是不对的。

98b5fa2666d3d5e508660a25617d39d8.jpeg在这里,只有5张“异常图片”,模型准确率却很高

准确率对于不平衡数据没法很好地评估,其实也有很多方法来解决,比如说成本积分制:当正常数据没有检测出,则成本为100,异常数据没有检测出,成本为1(见Cost Table A),这时候左边模型的成本则会低于右边模型的成本(红色字体),则左边的模型更优秀; 而如果正常数据没检测出的成本为1,异常数据没检测出的成本为100,这时候右边的模型就更优秀(蓝色字体);

积分制的使用要取决于你对业务的理解:异常数据没检测出、正常数据没检测出,哪个更加重要?比如在医疗上,对于癌细胞的异常检测,宁愿检测错正常人,也不要漏过一个病人,这时候“异常数据没检测出的成本更高”。

除了积分制,还有很多方法,比如AUC、Macro-F1等等。

5cfe694e8746e995286cbb1ae5b7ff24.jpeg

积分制,左边模型和右边模型谁更优秀取决于Cost Table的定制

2.2 存在的问题

分类器也许可以能够对“一般异常”进行“识别”,比如下面猫狗的分类器,就能够把羊驼和马来貘识别为“异常”,然而有一些“异常数据”则没那么容易了,比如说老虎和狼,这就是模型泛化问题;

因为模型一般只会抽取出一类图片中的共同特征,而此时“异常数据”无意中也具备了这一共同特征,那么就会出现把狼识别成狗的情况了。

ade6322d74d722bfaf4ce3ce8f8a7ff9.jpeg

下面的模型把老虎识别成猫,把狼识别成狗

另一个例子就是上面的“辛普森家族分类器”,将人物涂黄后,模型就会进一步识别成“丽莎”,这就说明了模型识别丽莎靠的是颜色,这显然是不正确的。

282f6fa2acdf753d17dc630ffc7aacfd.jpeg

图片涂改后,就会识别成其他人物

针对以上存在的问题,也有很多工作试图去解决,比如说收集一些“异常数据”,让分类器去学习给它们更低的信心值;然而我们一开始就说了,异常数据很难获取,那我们就想:能不能自动生成“异常数据”?

这时候我们就可以用GAN来尝试生成“有点像正常数据却又没那么像”的异常数据。

下面给出了相关文献,有兴趣的可以去了解一下。

c0c8048f8d9c3b0141b1bcf724b5784e.jpeg

解决该问题的相关文献


03

Without Labels(不带标签的)

这一部分,就是得到了没有标签的数据。该问题的定义和带标签的分类器一样,都是根据训练集训练模型,然后帮我分析输入数据相较于训练集是否属于异常数据。与分类器给出的信心值不同,这一类的模型给出的是一个概率,如果概率大于某个阈值,才认为是正常值;

在这一部分,用一个游戏举个例子:Twitch Plays Pokemon。这个游戏是一个多人同时“玩一个角色”的在线游戏,然而和我们平时玩的网游不一样,在这个游戏中,下一步动作取决于所有在线用户的操作(如下图的右边,是每一个用户按下的指令)。

b41f71ddd48c312c7eddb53ea0dfbaea.jpeg众多用户的指令靠某种机制共同决定游戏角色下一步操作

然而玩这个游戏的人非常崩溃,因为这个游戏很难进行下去(因为要所有在线玩家都给一个角色发送指令,而每个玩家的指令又不相同,而游戏只会执行其中一个指令)。所以玩家们就在想:是不是有些“恶意玩家”在乱发送指令,阻止游戏进程?也就是说是不是有人不想让这个游戏结束。

这样我们就有需求——找出“恶意用户”。在这之前,我们先有个假设:大部分玩家都希望完成这个游戏(也就是说大部分都是正常数据),而这部分数据我们会用来训练。然后我们使用异常检测,找出其中的“恶意玩家”(异常数据)。

9841d2ed8aeff4720ffc93e1d53e66e5.jpeg

由该游戏产生的思考

接下来,我们就要对其进行建模。在这里,我们的需求是把一堆“无标签”玩家分为正常用户和异常用户,这时我们需要把用户表示成一个向量,这样才能输入进我们的模型;而向量中的每一项可以表示这个用户的一种行为。

如下图,  表示这个用户过去一段时间内说垃圾话的频率(垃圾话是指游戏指令之外的话,多余的,不影响游戏进程),  表示的是这个用户过去一段时间内,随机机制下的发言频率。

这个游戏有两种机制:投票机制和随机机制 投票机制:20秒内最多玩家输入的指令,则作为游戏下一步的指令;随机机制:随机选择在线玩家输入的指令,作为游戏下一步的指令。

1fea7365911f13a7a331f3f99ce1a37f.jpeg

用向量表示用户

输入定义好了,我们就可以看看输出:模型会输出一个概率  ,和分类器不一样,无标签训练模型没有对应的Y值(标签)和信心值,只会输出一个概率  ;而和分类器相似的是,我们一样有一个  ,当  时,视为正常数据;当  时,视为异常数据。如下图所示。

a65e1bdce770cc85c4bec16d51a94e8f.jpeg

输出一个概率值,需要设定一个阈值

假设我们现在已经获取了大量用户的数据,下图是这些数据可视化展示。从可视化中可以获得一些信息:

  • 在随机机制下,用户就越喜欢发指令(左上图);

  • 大部分用户都会或多或少地乱输入指令(说垃圾话,右下图)。

这时候,我们就可以很直观地看到,但用户落在左上角的位置,则很大可能这就是一个“正常玩家”,而用户落在靠右或者靠下的位置,则很大可能是“异常玩家”。

2940d2eacdbf79148e36089c4026d732.jpeg

已知用户行为数据的可视化图

然而,我们需要一个数值化的表示方法,给每一个玩家一个分数。

假设我们之前看到的图,图上面所有的点都是由一个概率密度函数  生成的(不懂也没关系,就当它是一个函数就行了),  是该函数的参数,决定了这个函数“长什么样”,是未知的,需要从数据中学习。

而我们的工作就变成了:  它究竟长什么样?这时候我们就需要一个“Likelihood”的概念,意思就是说,根据我们的概率密度函数  ,能够产生这样的已知数据的概率有多大。

如果严格来说,  输出的不是概率,而是概率密度,它的值也不是介于0~1之间,而是有可能大于1的。在这里,为了简化问题,我们简单地认为是概率就好。

而这个“Likelihood”要怎么算呢,它其实就等于每项已知数据根据  这个函数所产生出来的概率的乘积;于是我们就有下面这条公式,很显然,这个公式是由  来控制的,不同的  ,就会有不同的  ,就会算出不同的“Likelihood”

这时候,我们并不知道  是多少,但我们知道,这个  ,能够使我们的“Likelihood”最大化

1326a515184b78c23d4c50512a57b340.jpeg

我们使用Likelihood来训练模型

上面是只是一个抽象的说法。在这里,我们为了让大家更好理解,我们就假设概率密度函数  为常用的高斯分布(Gaussian Distribution),这个概率密度函数并不简单,大家看不懂也没关系,就当它是一个普通函数就可以了,输入一个向量x,输出这个x被采样到的概率  ;而我们前面提到需要学习的  ,在这里就等于这里需要学习的均值  和协方差矩阵。

为什么这里选择高斯分布?其实这只是举例子,  甚至可以是一个神经网络,而此时  就是神经网络的参数,所以我们没必要在这里纠结,我们只需要清楚无标签时,异常检测是如何处理的,从而触类旁通。

6ac73a8fe02c887e32e84510d2b3833f.jpeg

高斯分布,及其需要学习的参数

这时候,Likelihood方程就会置换成下图的形式,用来找出能使Likelihood最大化的  和  。

左图给了一个很好的示例,告诉我们,  的取值,如何影响Likelihood的取值;左上角时,数据落在这个区域的概率就很大,这时候Likelihood就很大,而右下角时,落在这个区域的概率就小,这是Likelihood就小。

 和  其实是有公式算出来的,  就是所有的训练数据的向量做一个平均(输入向量是二维的,所以  也是二维的),而  就看图的右下角这个公式,这里很简单。

abef674583d9364de0a7985926bfde47.jpeg

抽象的概念替换为具体的高斯分布时的计算方法

在这里,我们已经得到了  和  ,我们可以用来做异常检测,我们把测试数据代入我们的高斯分布方程,我们就能算出其概率,如果这个概率大于阈值  ,是认为是正常值,否则视为异常值。

如果我们用这个训练好的方程,大概就是下图右下角的样子,颜色越深代表这个方程输出的数值就越大,就越代表“正常玩家”,而颜色越浅越蓝的,就代表“异常玩家”;而这个阈值  ,其实其中一条等高线;右下图就给出了正常点和异常点的位置示意。

9dbd76ef11e0f4eb085e1deef25c138c.jpeg

根据公式,可以获得其对应的概率,从而判断是否异常

以上的例子,我们只是使用了两个特征,也就是输入向量x只是二维;而机器学习的好处就是可以处理更多特征,只要你想到的,都可以加进去。下图就增加到5个特征,再训练一个Likelihood,从而获得“更准确的”异常检测效果。

3c0972a1ab49569ef1bfd9a47dd8fad2.jpeg

我们可以添加更多的特征

3.1 更多的方法

除了上面的方法,还有一种常用方法是:Auto-encoder(自动编码器),如下图所示,Encoder(编码器)先把辛普森的照片编码成一个code(隐含层),然后Decoder(解码器)把code解码回原来的的照片,训练时会同时训练编码器和解码器,尽量让解码后的照片和原照片尽可能相似(甚至相同)。这时候如果用Auto-encoder来识别一张“异常图片”,这时候的解码器是无法重构回原来的照片,通过计算重构后的照片和原照片的“距离”(或者说是相似度、还原度),就可以区分该照片是不是“异常值”。

在我看来,Auto-encoder比分类器多了一重保障(指Decoder解码器),在分类器上,找到对应的特征,就进行判断,比如颜色、轮廓等等,上面就有例子说明这样的情况并不可靠;而解码器就是一种利用特征的过程,我利用编码器提取的特征,看是否能够重构回原来照片,就能知道这是不是异常值;

53035ea91eb33864d3d3096f9a2a827f.jpeg

自动编码器的工作流程

下图是正常的图片,可以看到模型很容易就重构了回来,而且和原图非常接近。因为这个模型看过辛普森,所以非常“擅长”还原辛普森。

2ec34b6fccce5bfd2bca48c47fb5c8f3.jpeg

辛普森家族的照片就能很好地复原

如下图的Testing阶段,对比原图和重构图,我们可以看到,编码器提取到的应该是“黄色”和“棕色”这两个特征,然而编码器用两个特征构建出来的图片,显然和原图相差甚远,这时候就可以识别为“异常值”。(从重构图可以隐约看到,这大概也是一个辛普森角色)

87097a454eb3312d7d7095c4a386a958.jpeg

其他图片就会复原到模型所“理解”到的辛普森角色形象

除了自动编码器,还有很多其他模型可以做这样的事,比如one-class SVM,Isolated Forest。有兴趣的可以自行进一步学习。

45c7a9a373d6ed404c231f2868c38037.jpeg

更多的模型


04

应用

异常检测的应用非常广泛,下面几项是非常常见的:

  • Fraud Detection(欺诈识别)

    • 训练集:正常的刷卡行为;输入x:盗刷行为?

  • Network Intrusion Detection(网络入侵检测)

    • 训练集:正常的访问;输入x:攻击行为?

  • Cancer Detection(癌症检测)

    • 训练集:正常细胞;输入x:癌细胞

参考文献:

https://www.bilibili.com/video/BV1Gb411n7dE?p=8

下载1:OpenCV-Contrib扩展模块中文版教程

在「小白学视觉」公众号后台回复:扩展模块中文教程,即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。


下载2:Python视觉实战项目52讲
在「小白学视觉」公众号后台回复:Python视觉实战项目,即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。


下载3:OpenCV实战项目20讲
在「小白学视觉」公众号后台回复:OpenCV实战项目20讲,即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。


交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~