异常检测-Anomaly detection
1.1问题动机
异常检测是机器学习算法的一个常见应用,这种算法的一个有趣之处在于它虽然主要用于非监督学习问题,但从某些角度看它又类似于一些监督学习问题。那么什么是异常检测呢?
假想你是一个飞机引擎制造商,当生产的飞机引擎从生产线上流出时,需要进行QA(质量控制测试),而作为这个测试的一部分,测量了飞机引擎的一些特征变量(比如引擎运转时产生的热量或者引擎的振动等),这样会有了一个数据集从x(1)到x(m),可视化后的结果如下:
假设有一个新的飞机引擎从生产线上流出,而新飞机引擎的特征变量为x-test,所谓的异常检测问题就是我们希望知道这个新的飞机引擎是否有某种异常或者说我们希望判断这个引擎是否需要进一步测试。
如果它看起来像一个正常的引擎,那么我们可以直接将它运送到客户那里而不需要进一步的测试。如果新引擎对应的点落在圈1处,那么可以认为它是正常的,然而如果新飞机引擎对应的点落在圈2处,那么我们可以认为这是一个异常。
更为正式的定义如下,有一些数据从x(1)到x(m),({x^{(1)},x^{(2)},...,x^{(m)}}),通常假定这m个样本都是正常的,我们需要一个算法来告诉我们一个新的样本数据x-test是否是异常,所采取的方法是给定无标签的训练集,我们对数据建一个模型p(x),也就是对x的分布概率建模,其中x是这些特征变量例如飞机引擎震动及热量等,在建立了x的概率模型之后,对于新的飞机引擎也就是x-test,如果概率p低于阈值ε那么就将其标记为异常,反之如果x-test的概率p大于给定的阈值ε,我们就认为它是正常的。
因此给定下图中的这个训练集,如果建立了一个模型模型p(x),将会发现在中心区域的这些点有很大的概率值,而稍微远离中心区域的点概率会小一些,更远的地方的点它们的概率将更小,在外侧的点将成为异常点。
异常检测算法的实际应用有很多,最常见的应用是欺诈检测 ,假设有很多用户,每个用户都在从事不同的活动,也许是在个人网站上,也许是在一个实体工厂之类的地方,可以通过对不同的用户活动计算特征变量,如x1是用户登陆的频率,x2也许是用户访问某个页面的次数或者交易次数,x3是用户在论坛上发贴的次数,x4是用户的打字速度等等,然后建立一个模型p(x),这时可以用它来发现网站上的行为异常的用户。只需要看哪些用户的p(x)概率小于ε,接下来拿来这些用户的档案做进一步筛选,或者要求这些用户验证他们的身份等等,从而让你的网站防御异常行为或者欺诈盗号等行为。
异常检测的另一个例子是在工业生产领域,事实上我们之前所说的飞机引擎的问题,通过模型p(x)找到异常的飞机引擎,然后要求进一步细查这些引擎的质量。
此外对于数据中心的计算机监控也是异常检测的应之一,如果你管理一个计算机集群或者一个数据中心,其中有许多计算机,我们可以为每台计算机计算特征变量如:计算机的内存消耗、硬盘访问量、CPU负载或者一些更加复杂的特征,例如一台计算机的CPU负载与网络流量的比值,那么给定正常情况下数据中心中计算机的特征变量,可以建立p(x)模型,如果有一个计算机它的概率p(x)非常小,这时可以认为这个计算机运行不正常,从而进一步要求系统管理员查看其工作状况,目前这种技术实际正在被各大数据中心使用用来监测大量计算机可能发生的异常。
1.2高斯分布-Gaussian distribution
高斯分布也称为正态分布,假设x是一个实数随机变量,如果x的概率分布服从高斯分布,其中均值为μ方差为σ平方,那么将它记作,其中大写字母N表示Normal(正态),因为高斯分布就是正态分布,高斯分布有两个参数一个是均值我们记作μ,,另一个是方差我们记作σ^2,高斯分布的概率密度函数绘制形如一个钟形的曲线:
这个钟形曲线有两个参数分别是μ和σ,其中μ控制这个钟形曲线的中心位置,σ控制这个钟形曲线的宽度,参数σ有时也称作一个标准差。
这条钟形曲线决定了x取不同数值的概率密度分布,因此x取中心这些值的概率相当大因为高斯分布的概率密度在这里很大而x取远处和更远处数值的概率将逐渐降低直至消失。
高斯分布的数学公式:
下图为高斯分布中μ取0 σ取1,μ取0 σ取0.5,μ取0 σ取2,μ取3 σ取0.5的几个例子:
接下来让我们来看参数估计问题,假设如下图所示有一个数据集,其中有m个样本从x(1)到x(m),({x^{(1)},x^{(2)},...,x^{(m)}}),假设他们都是实数,在x轴上以红色的X标明,假设猜测这些样本来自一个高斯分布的总体,从而猜测每一个样本x(i)服从正态分布或者高斯分布,它有两个参数μ和σ平方,不过并不知道这些参数的确切值。参数估计问题就是给定数据集,希望能估算出μ和σ平方的值。
我们从它服从于高斯分布入手,可以绘制下图的钟形曲线,其中μ对应分布函数的中心(样本最密集处),而标准差σ控制高斯分布的宽度。这条曲线似乎很好的拟合了数据,因为看起来这个数据集在中心区域的概率比较大,而在外围在边缘的概率越来越小,因此也许这是对μ和σ平方的一个不错的估计。
参数估计的标准公式,估计μ的方法是对所有样本求平均值,μ就是平均值参数,(mu = frac{1}{m}sum_{i=1}^{m}x^{(i)})
(sigma^2=frac{1}{m}sum_{i=1}^{m}(x^{(i)}-mu)^2) 。
1.3算法实现
假设我们有一个无标签的训练集,共有m个训练样本,且训练集里的每一个样本都是n维的特征,因此你的训练集应该是m个n维的特征构成的样本矩阵,如m个飞机引擎产品的样本,现在我们解决异常检测的方法是从数据中建立一个p(x)概率模型,具体的:
(p(x)=p(x_1;mu_1,sigma_1^2)p(x_2;mu_2,sigma_2^2)p(x_3;mu_3,sigma_3^2)...p(x_n;mu_n,sigma_n^2)=prod_{j=1}^{n}p(x_j;mu_j,sigma_j^2)),这里都是假定特征x1、x2、...xn服从高斯正态分布。顺便要说的是估计p(x)的分布问题通常被称为密度估计问题(Density estimation)。
总结一下:第一步选择特征或者是找出一些具有异常样本的特征xi,这种特征xi这个特征可能会呈现一个特别大或者特别小的数值,因为这些特征本身就有些异常但更为普遍的是尽可能尝试选择能够描述数据相关的属性的特征。接下来给出一组m个无标签数据构成的训练集,从x(1)到x(m)。我们首先要拟合出期望μ_1到μ_n以及方差值(σ_1)2到(σ_n)2:(mu_j = frac{1}{m}sum_{i=1}^{m}x_j^{(i)}) ,(sigma_j^2=frac{1}{m}sum_{i=1}^{m}(x_j^{(i)}-mu_j)^2) 。
最后当给出一个新样本时,比如当有一个新的飞机引擎时,现在想知道这个飞机引擎是否出现异常,我们要做的就是计算出p(x)的值:(p(x)=prod_{j=1}^{n}p(x_j;mu_j,sigma_j^2)=prod_{j=1}^{n}frac{1}{sqrt{2pi}sigma_j}exp(-frac{(x_j-mu_j)^2}{2sigma_j^2})) ,然后看看计算出的概率值大小,若这个概率值小于ε,那么可以将这一项标注为异常项。
如左侧绘制的数据集,对于特征x1,从可视化后数据集中你会发现这个特征的均值大约是5,其标准差可能为2,特征x2平均值可能是3且其标准差值大约为1。
右侧为根据实际公式计算结果,实际上如果绘制p(x)的图像(如下),也就是这两个概率值的乘积,我们可以得出具体的某一点对应的高度值,即p(x)值。
假如在这里有了新样本,上图中的绿色X状,现在这个样本是否异常?要回答这个问题我们可以先给计算机设某个无穷小的数值,假如设置的无穷小量数值为ε=0.02(以后课程里涉及如何取值),先看第一个样本我们把这个称为(X_test^{(1)}),同时称第二个样本为(X_test^{(2)})。
由总结的步骤3可以计算出:
不难发现,x1检测的结果是正常的,而x2确实是一个异常数值因为那已经远远小于了先前我们定义的ε值。
2.1创建与评估异常检测系统
这一节涉及如何开发一个关于异常检测的应用,以解决一个实际问题。具体来说我们将重点关注如何评估一个异常检测算法,前面章节我们已经提到了使用实数评价法的重要性,具体的,当在用某个学习算法来开发一个具体的机器学习应用时,常常需要做出很多决定,如选择用什么样的特征等等。而如果找到某种评估算法的方式能够直接返回一个数字,通过返回值说明算法的好坏,那做这些决定就显得更容易。
我们先假定已有了一些带标签的数据,我们要考虑的异常检测问题是一个非监督问题,使用的也是无标签数据,但如果你有一些带标签的数据能够指明哪些是异常样本,或有哪些是非异常样本,那么这就成为了评价异常检测算法的标准。
以飞机引擎为例,假如有了一些带标签数据,也就是有异常的飞机引擎的样本,同时还有一些无异常的样本,这里用y=0来表示那些完全正常的样本,用y=1来代表那些异常样本。
那么异常检测算法的推导和评价方法如下所示:
我们先考虑训练样本,交叉验证和测试集等下考虑。对于训练集我们还是看成无标签的数据集,训练集是无异常样本的集合。一般来说,我们把训练集都看成无异常的,但可能有一些异常的也被分到训练集中,这也没关系。
接下来定义交叉验证集CV和测试集Test,通过这两个集合我们将得到异常检测算法具体来说对交叉验证集和测试集我们将假设我们的交叉验证集和测试集中有一些样本这些样本都是异常的所以比如测试集里面的样本就是带标签y=1的这表示有异常的飞机引擎。
这里假设有10000个制造的飞机引擎作为样本,据我们所知,这些样本基本都是正常的飞机引擎,但有一小部分有问题的引擎也被混入样本中,不过不用担心,我们假设这10000个样本中大多数都是好的引擎,而且从过去的实际经验来看,无论是制造了多少年引擎的工厂,都会得到大概20个有问题的引擎,即y=0的样本有10000个。此外,对于异常样本的个数,也就是y=1的样本有20个。正常样本的数量要大得多。
把数据分为训练集交叉验证集和测试集一种典型的分法如下:把这10000个正常的引擎放6000个到无标签的训练集中,这里称它为“无标签训练集”,但其实所有这些样本实际上都对应y=0的情况,我们要用它们来拟合p(x)。即通过这6000个样本来估计参数μ1,σ1一直到μn,σn。
然后我们取一些正常的飞机引擎样本(y=0),放2000个样本到交叉验证集,再放2000个样本到测试集中,正好6000+2000+2000=10000个。
同时我们还有20个异常的引擎样本,同样也把它们进行一个分割放10个到交叉验证集,剩下10个放入测试集中,正好10+10=20个。
所以可以把异常检测算法想成是对交叉验证集和测试集中的y进行一个预测,这样可能有种相识的感觉,和监督学习有点类似不是吗?
我们有带标签的测试集而我们的算法就是对这些标签作出预测,所以我们可以通过对标签预测正确的次数来进行评价。这里的标签数据集很有可能出现偏态分布(skewed),因为y=0也就是正常的样本肯定是比出现y=1也就是异常样本的情况更多,此时跟我们在监督学习中用到的评价度量方法非常接近。
因为数据是偏态分布的,如此例中y=0是更加常见,那么总是预测y=0它的分类准确度自然会很高,所以再采用分类准确度考量算法有些不靠谱了,取而代之的我们应该算出真阳性(true postive)、假阳性(false positve)、假阴性(false negative)和真阴性(true negative)的比率来作为评价度量值,还可以算出查准率和召回率(Precision/Recall),或算出F_1-值等等。
对于阈值参数ε的选择:如果有一组交叉验证集样本,一种选择参数ε的方法就是试一试多个不同的ε的取值,然后选出一个使得F1-积分的值最大的那个ε,也就是在交叉验证集中表现最好的。
总结一下,使用训练集、测试集和交叉验证集的方法是:当需要作出决定时,比如要包括哪些特征或者说要确定参数ε取多大合适,我们就可以不断地用交叉验证集来评估这个算法,然后决定应该用哪些特征、怎样选择ε。
本节介绍了如何评价一个异常检测算法,在能够评价算法之后,通过一个简单的的数值的评价方法,用一个简单的F_1-值,这样就能更有效率地在开发异常检测系统时更有效率地利用好时间,尽快确定应该如何选取ε、应该包括哪些特征等等。
2.2异常检测vs.监督学习
在上一小节中,我们谈到了如何评估一个异常检测算法,我们先用一些带标签的数据以及一些我们知道是否异常的样本(y=1或y=0来表示),这引出了一个问题:我们有了这些带标签的数据,那我们为什么不直接用监督学习的方法呢?为什么不直接用逻辑回归或者神经网络的方法来直接学习这些带标签的数据,从而给出预测y=1或y=0呢?
异常检测(anomaly detection) | 监督学习(supervised learning) |
---|---|
正样本数目很少(y=1)通常在0-20之间,甚至50 | 正、负样本数目很多 |
负样本数目很多 | |
异常现象"种类"很多 | 已经给出足够多的正样本,训练的算法已经完全“感知”出正样本的类型,即将出现的新的正样本与训练集中相似 |
换句话,出现的异常可能和训练前的异常完全不同 |
使用异常检测算法还是监督学习算法关键的区别就是在异常检测算法中我们只有一小撮正样本,因此学习算法不可能从这些正样本中学出太多东西,取而代之的,我们使用大量的负样本,比如大量的正常飞机引擎样本中学出p(x)模型。另外我们预留一小部分正样本,用来评价我们的算法,既用于交叉验证集也用于测试集。
因此对于生产中的应用,我们期待看到更多的正常样本,少出现一些异常情况。但某些生产过程来说,如果进行大量的生产作业并且发现了比较多的异常样本,那么这个问题同样也可能转向一个监督学习问题,但若并没有看到太多的异常样本,那么应该将它当做异常检测问题来处理。
相对而言垃圾邮件分类、天气预报以及癌症诊断的问题,在拥有相同数量的正负样本,或者说既有大量的正样本也有大量的负样本情况中,我们倾向于把这些问题当做监督学习。
2.3特征-features的选择
本小节主要概述关于如何设计或选择异常检测算法的特征变量,在我们的异常检测算法中,我们做的事情之一就是使用正态(高斯)分布来对特征向量建模,绘出这些数据或者用直方图表示数据,以确保这些数据在应用异常检测算法前看起来像高斯分布。
这里如果数据看起来不像正态分布,算法也常常可以正常运行。但具体而言,将数据画成柱状图(Octave/MATLAB里面的hist函数),若数据如下左侧图像,这时通常要做的事情是对数据进行一些不同的转换,以确保这些数据看起来更像高斯分布。(虽然不这么做,算法也会运行地很好,但如果使用一些转换方法使数据更像高斯分布的话,算法会工作得更好)
异常检测的误差分析:在异常检测中我们希望p(x)的值对正常样本来说是比较大,而对异常样本来说值是很小,因此一个很常见的问题是p(x)的正常样本和异常样本的值都很大,具体的讲,假设无标签数据x1如下所示,现在用一个高斯分布来拟合它(右侧):
占用内存;磁盘每秒访问次数;CPU负载;网络流量(network traffic)。
现在假如CPU负载和网络流量互为线性关系,那么可能在运行的一组网络服务器中,某一个服务器在对许多用户服务,那么CPU负载和网络流量都会很大。
现在假设计算机在执行一个任务时进入了一个死循环因此被卡住了,因此CPU负载升高但网络流量没有升高,在这种情况下要检测出异常,可以新建一个特征x5,x5等于CPU负载除以网络流量。因此如果某一台机器具有较大的CPU负载但网络流量正常的话,x5的值将会变得非常大。通过这样的方法建立新的特征变量,就可以通过不同特征变量的组合,捕捉到对应的不寻常现象。
3.1多元高斯分布(multivariate Gaussian distribution)
以数据中心监控计算机为例,无标签数据如下图所示,特征变量x1是CPU的负载,特征向量x2是内存使用量。
多元高斯分布的完整公式如下:
(p(x;mu,Sigma)=frac{1}{(2pi)^{n/2}|Sigma|^{1/2}}exp(-frac{1}{2}(x-mu)^TSigma^{-1}(x-mu)))。上式中|Σ|称为Sigma的行列式(determinant),可以在Octave/MATLAB里使用命令det(Sigma)来计算它。
来看一个二维的例子,假设有n等于2两个特征x1和x2,令µ等于0,令Σ等于单位阵(即对角线上的值等于1非对角线上的值等于0),在这个情况下p(x)看起来如下:
试试改变一些参数,假如说缩小一下Σ,Σ是一个协方差矩阵衡量的是方差或者说特征变量x1和x2的变化量,所以如果缩小Σ那么得到的是相对更鼓的包,投影与平面的同心椭圆也缩小了一些。若增加Σ对角线上的值,那么最后会得到一个更宽更扁的高斯分布。
3.2 利用多元高斯分布做异常检测
多元正态分布有两个参数µ和Σ,µ是一个n维向量,协方差矩阵Σ是一个n乘n矩阵,具体的概率分布公式为:
多元高斯分布模型和原来的模型之间的关系,p(x)原来的模型是p(x1)乘以p(x2)一直乘到p(xn)的积,事实上这个模型实际上它对应于一种多元高斯分布的特例。具体来讲:原始模型下的等高线椭圆中它们的轴都是沿着x1,x2的轴的,而对于多元高斯分布的协方差矩阵Σ必须满足非对角线的元素为0,既可以实现原始模型的等高线分布概率。
所以应该在什么时候用哪个模型呢?什么时候该用原来的模型什么时候该用多元高斯模型呢?
实际上,原来的模型可能使用得更加频繁,而多元高斯模型则没有那么常用,但它有能够捕捉特征变量之间的相关性的优势,假设有不同的特征变量,比如说特征变量x1,x2的值的组合是不正常的,在之前的例子中是CPU负载和内存使用量的值的组合是不正常的(虽然二者单独的表征是正常的),如果想用原来的模型捕捉到这个情况,需要建立一个新特征变量比如说x3=x1/x2,在手动建立这样的新特征变量后,原来的模型可以很好地运行,而相对地多元高斯模型可以自动捕捉不同特征变量之间的相关性。
但是原来的模型也有一些其他的很重要的优势:运算量更小,换种说法更适用于n的值非常大,就是说特征变量很多的情况,所以即便n等于10,000或者n等于100,000,原来的模型一般都可以很好地运行,而对于多元高斯模型的计算量会非常大(Sigma为n*n的尺寸),所以多元高斯模型不是非常适合n很大的情况。
最后,对于原来的模型即使训练集相对较小(如m为50或者100),它也能运行得效果不错,而对于多元高斯模型,这个算法的数学性质要求m必须大于n,如果不满足这个条件,那么这个矩阵是不可逆的是奇异矩阵,进而不能使用多元高斯模型,根据经验,一般需要在m大于等于十倍的n时应用良好。
所以在实际应用当中,原始的模型比较常用,当需要捕捉特征变量之间的相关性,一般人就会手动增加额外特征变量,来捕捉特定的不正常的值的组合,但是在训练集很大或者说m很大n不太大的情况下,那么多元高斯模型是值得考虑得,可以省去为了捕捉不正常的特征值组合,而手动建立额外特征变量所花费的时间。
另外,在拟合多元高斯模型时,如果发现协方差矩阵Σ是奇异的(不可逆的),一般只有两种情况,第一种是它没有满足这个m大于n的条件,第二种情况是有冗余特征变量(亦称为线性相关的)。所以,这时候应该首先是确保m比n大很多,其次检查冗余特征变量,如果有两个特征变量相等就删掉其中一个就删掉。