无监督算法与异常检测

一、整体概览

       反欺诈往往看做是二分类问题,但是仔细想想是多分类问题,因为每种不同类型的欺诈都当做是一种单独的类型。欺诈除了多样并且不断变化,欺诈检测还面临一下问题:

      1). 由于大部分情况数据是没有标签的,各种成熟的监督学习是没有办法应用

      2). 区分噪音和异常点时难度比较大,甚至需要一点点经验

      3). 当多种不同的欺诈结合在一起时,区分欺诈类型比较难,因为不了解每一种欺诈定义

      4). 即使有真的欺诈数据,即在有标签的情况下用监督学习,也存在很大的不确定性。用这种历史数据学出的模型只能检测当时存在以及比较相似的欺诈,而对于不同的诈骗和从未见过的诈骗,模型预测效果一般比较差。

       因此,在实际情况中,一般不直接用任何监督学习,或者说不能至少单独依靠一个监督学习模型来奢求检测到所有的欺诈。没有历史标签和对诈骗的理解,无法做出对诈骗细分的模型。一般常见的做法是使用无监督模型,且需要不同专家根据经验来验证预测,提供反馈,便于及时调整模型。

      拿到含有欺诈的数据之后,一般做法有通过关联矩阵分析以及多维尺度变换。

 

二、无监督学习

        当一个场景需要做预判的时候,有没有标签,往往能做的主要有迁移学习、专家模型、无监督算法。

   迁移学习

    源域样本和目标域样本分布有比较大的区别,目标域样本量不够的话,通过算法缩小边缘分布之间和条件分布之间的差异。

  • 基于特征的迁移
  • 基于模型的迁移
  • 基于实例的迁移

   不足之处:需要拥有与当前目标场景相关的源域数据

   专家模型

    主要是根据主观经验进行评判,而不是根据统计分析或者模型算法来进行客观的计算。常规操作过程如下所示:

  •  根据相关经验判断特征重要性
  •  根据相关经验进行变量加权

   不足之处:需要大量的行业经验积累,有时候不太具有说服性

   无监督算法

       缺乏足够的先验知识,无法对数据进行标记时,使用的一种机器学习方法,代表聚类、降维等。在风控领域中主要使用的是聚类和无监督异常检测。聚类是发现样本之间的相似性,异常检测是发现样本之间的差异性。

    聚类

  •  K-Means
  • DBSCAN
  • 层次聚类
  • 社区发现

        一般主要是对负样本聚类,将有问题的用户细分为几类。社区发现算法一般是识别团伙欺诈的主要手段之一,主要方法是通过知识图谱将小团体筛选出来。在知识图谱中,聚集意味着风险。

     异常检测

       异常点检测,通常也被称为离群点检测,是找出与预期对象的行为差异较大的对象的一个检测过程。检测出来的数据被称为异常点或者离群点。异常点在生产生活中有比较多的应用,例如广告点击反作弊、设备损坏等。异常点是一个数据对象,明显不同于其他的数据对象。如下所示,N1,N2为区域内的正常数据,而离这两个正常区域比较远的地方的点为异常点。

                                                          

        异常检测和不均衡学习的区别是,异常检测一般是无监督的;与普通的二分类区别是异常检测往往看作为是二分类,但是其实是多分类(由于造成异常的原因各不相同)

       异常检测算法的前提假设:1) 异常数据跟样本中大多数数据不太一样  2) 异常数据在整体数据样本中占比相对比较小

       异常检测的主要思想是基于样本(小群体)之间的相似度: 距离、密度、簇

       异常检测算法的意义:

        1). 很多场景没有标签或者标签比较少,不能训练监督模型

        2). 样本总是在发生变换,只能从一个小群体内部发现异常

        3). 异常检测假设异常样本数据量占比较少,并且在某种维度上远离其他样本,符合个体欺诈的先验知识。在团体欺诈不太适用。

        4). 样本群体有异构成分,可以对样本进行筛选

三、异常检测的常用算法

         常见的异常检测算法有Z-score算法、KNN算法、Local Outlier Factor、孤立森林等

    Z-score算法

        假设样本服从正态分布,用于描述样本偏离正态分布的程度。通过计算μ和σ得到当前样本所属于的正态分布的表达式,然后分别计算每个样本在这个概率密度函数下被生成的概率,当概率小于某一阈值我们认为这个样本是不属于这个分布的,因此定义为异常值。计算公式如下所示:

       

  根据获取平均值和方差的估计值,然后给定一个新的训练实例,根据模型计算p(x):

  当p(x) < ep(x) < e时候,数据未异常

  缺点是需要假设样本满足正态分布,而大部分场景不满足正态分布假设条件

      KNN异常检测

       KNN算法专注于全局异常检测,所以无法检测到局部异常。

       首先,对于数据集中的每条记录,必须找到k个最近的邻居。然后使用这K个邻居计算异常分数。

我们有三种方法

  • 最大:使用到第k个邻居的距离作为离群值得分
  • 平均值:使用所有k个邻居的平均值作为离群值得分
  • 中位数:使用到k个邻居的距离的中值作为离群值得分

       在实际方法中后两种的应用度较高。然而,分数的绝对值在很大程度上取决于数据集本身、维度数和规范化。参数k的选择当然对结果很重要。如果选择过低,记录的密度估计可能不可靠。(即过拟合)另一方面,如果它太大,密度估计可能太粗略。K值的选择通常在10<k<50这个范围内。所以在分类方法中,选择一个合适的K值,可以用交叉验证法。但是,事实上基于KNN的算法都是不适用于欺诈检测的,因为他们本身就对噪声比较敏感。

     Local Outlier Factor

      基于密度的LOF算法相对比较更加简单、直观以及不需要对数据的分布有太多的要求,还能量化每个数据点的异常程度。该算法是基于密度的算法,主要核心的部分是关于数据点密度的刻画。算法的整个流程涉及如下概念:

     1)k-近邻距离(k-distance): 在距离数据点p最近的几个点中,第k个最近的点跟点p之间的距离称为点p的k-近邻距离,记为k-distance(p).

     2)  可达距离(rechability distance): 可达距离的定义跟k-近邻距离是相关的,给定参数k时,数据点p到数据点o的可达距离reach-dist(p,o)为数据点o的K-近邻距离和数据点p与点o之间的直接距离的最大值。即:

                    reachdist-k(p,o) = max{k-distance(o),d(p,o)}

     3) 局部可达密度(local rechablity density):局部可达密度的定义是基于可达距离的,对于数据点p,那些跟点p的距离小于等于k-distance(p)的数据点称为它的k-nearest-neighor,记为Nk(p),数据点p的局部可达密度为它与邻近的数据点的平均可达距离的倒数,即:

 

     4) 局部异常因子: 根据局部可达密度的定义,如果一个数据点跟其他点比较疏远的话,那么显然它的局部可达密度就小。但LOF算法衡量一个数据点的异常程度,并不是看它的绝对局部密度,而是看它跟周围邻近的数据点的相对密度。这样做的好处是可以允许数据分布不均匀、密度不同的情况。局部异常因子即是用局部相对密度来定义的。数据点 p 的局部相对密度(局部异常因子)为点p的邻居们的平均局部可达密度跟数据点p的局部可达密度的比值,即:

      根据局部异常因子的定义,如果数据点 p 的 LOF 得分在1附近,表明数据点p的局部密度跟它的邻居们差不多;如果数据点 p 的 LOF 得分小于1,表明数据点p处在一个相对密集的区域,不像是一个异常点;如果数据点 p 的 LOF 得分远大于1,表明数据点p跟其他点比较疏远,很有可能是一个异常点。

       小结:整个LOF算法,首先对于每个数据点,计算它与其它所有点的距离,并按从近到远排序,然后对于每个数据点,找到它的k-nearest-neighbor,最后计算LOF得分。

/******** LOF算法 ********/

from pyod.models.lof import LOF
clf = LOF(n_neighbors=20, algorithm='auto', leaf_size=30, metric='minkowski', p=2, 
          metric_params=None, contamination=0.1, n_jobs=1)
clf.fit(x)

  计算出每一个数据点的LOF值,然后在二维空间中展示,如下图所示,数据点的LOF值越大表示该点越异常

       

   Trick: LOF算法中关于局部可达密度的定义其实暗含了一个假设,即:不存在大于等于 k 个重复的点。当这样的重复点存在的时候,这些点的平均可达距离为零,局部可达密度就变为无穷大,会给计算带来一些麻烦。在实际应用时,为了避免这样的情况出现,可以把 k-distance 改为 k-distinct-distance,不考虑重复的情况。或者,还可以考虑给可达距离都加一个很小的值,避免可达距离等于零。

      Isolation Forest

        先用一个简单的例子来说明 Isolation Forest 的基本想法。假设现在有一组一维数据(如下图所示),我们要对这组数据进行随机切分,希望可以把点 A 和点 B 单独切分出来。具体的,我们先在最大值和最小值之间随机选择一个值 x,然后按照 =x 可以把数据分成左右两组。然后,在这两组数据中分别重复这个步骤,直到数据不可再分。显然,点 B 跟其他数据比较疏离,可能用很少的次数就可以把它切分出来;点 A 跟其他数据点聚在一起,可能需要更多的次数才能把它切分出来。

                                                           

       我们把数据从一维扩展到两维。同样的,我们沿着两个坐标轴进行随机切分,尝试把下图中的点A'和点B'分别切分出来。我们先随机选择一个特征维度,在这个特征的最大值和最小值之间随机选择一个值,按照跟特征值的大小关系将数据进行左右切分。然后,在左右两组数据中,我们重复上述步骤,再随机的按某个特征维度的取值把数据进行细分,直到无法细分,即:只剩下一个数据点,或者剩下的数据全部相同。跟先前的例子类似,直观上,点B'跟其他数据点比较疏离,可能只需要很少的几次操作就可以将它细分出来;点A'需要的切分次数可能会更多一些。

                                                        

       按照先前提到的关于“异常”的两个假设,一般情况下,在上面的例子中,点B和点B' 由于跟其他数据隔的比较远,会被认为是异常数据,而点A和点A' 会被认为是正常数据。直观上,异常数据由于跟其他数据点较为疏离,可能需要较少几次切分就可以将它们单独划分出来,而正常数据恰恰相反。这其实正是Isolation Forest(IF)的核心概念。IF采用二叉树去对数据进行切分,数据点在二叉树中所处的深度反应了该条数据的“疏离”程度。整个算法大致可以分为两步:

  • 训练:抽取多个样本,构建多棵二叉树(Isolation Tree,即 iTree);
  • 预测:综合多棵二叉树的结果,计算每个数据点的异常分值。

      训练:构建一棵 iTree 时,先从全量数据中抽取一批样本,然后随机选择一个特征作为起始节点,并在该特征的最大值和最小值之间随机选择一个值,将样本中小于该取值的数据划到左分支,大于等于该取值的划到右分支。然后,在左右两个分支数据中,重复上述步骤,直到满足如下条件:

  • 数据不可再分,即:只包含一条数据,或者全部数据相同。
  • 二叉树达到限定的最大深度。

      预测:计算数据 x 的异常分值时,先要估算它在每棵 iTree 中的路径长度(也可以叫深度)。具体的,先沿着一棵 iTree,从根节点开始按不同特征的取值从上往下,直到到达某叶子节点。假设 iTree 的训练样本中同样落在 x 所在叶子节点的样本数为 T.size,则数据 x 在这棵 iTree 上的路径长度 h(x),可以用下面这个公式计算:

                                                            h(x)=e+C(T.size)

公式中,e 表示数据 x 从 iTree 的根节点到叶节点过程中经过的边的数目,C(T.size) 可以认为是一个修正值,它表示在一棵用 T.size 条样本数据构建的二叉树的平均路径长度。一般的,C(n) 的计算公式如下:

 

                                                     C(n)=2H(n−1)−2(n−1)/n

其中,H(n-1) 可用 ln(n-1)+0.577 估算,这里的常数是欧拉常数。数据 x 最终的异常分值 Score(x) 综合了多棵 iTree 的结果

公式中,E(h(x)) 表示数据 x 在多棵 iTree 的路径长度的均值,φ表示单棵 iTree 的训练样本的样本数,C(φ)表示用φ条数据构建的二叉树的平均路径长度,它在这里主要用来做归一化。

从异常分值的公式看,如果数据 x 在多棵 iTree 中的平均路径长度越短,得分越接近 1,表明数据 x 越异常;如果数据 x 在多棵 iTree 中的平均路径长度越长,得分越接近 0,表示数据 x 越正常;如果数据 x 在多棵 iTree 中的平均路径长度接近整体均值,则打分会在 0.5 附近。

四、应用案例

1、清洗建模数据集,将异常样本通过无监督算法进行筛选

from pyod.models.iforest import IForest
from matplotlib import pyplot as plt

clf = IForest(behaviour='new', bootstrap=False, contamination=0.1, max_features=1.0,
                max_samples='auto', n_estimators=500, n_jobs=-1, random_state=None,verbose=0)
clf.fit(x)

out_pred = clf.predict_proba(x,method ='linear')[:,1]
train['out_pred'] = out_pred

x = train[train.out_pred< 0.7][feature_lst]
y = train[train.out_pred < 0.7]['bad_ind']

val_x =  val[feature_lst]
val_y = val['bad_ind']

lr_model = LogisticRegression(C=0.1,class_weight='balanced')
lr_model.fit(x,y)
y_pred = lr_model.predict_proba(x)[:,1]
fpr_lr_train,tpr_lr_train,_ = roc_curve(y,y_pred)
train_ks = abs(fpr_lr_train - tpr_lr_train).max()
print('train_ks : ',train_ks)

y_pred = lr_model.predict_proba(val_x)[:,1]
fpr_lr,tpr_lr,_ = roc_curve(val_y,y_pred)
val_ks = abs(fpr_lr - tpr_lr).max()
print('val_ks : ',val_ks)

plt.plot(fpr_lr_train,tpr_lr_train,label = 'train LR')
plt.plot(fpr_lr,tpr_lr,label = 'evl LR')
plt.plot([0,1],[0,1],'k--')
plt.xlabel('False positive rate')
plt.ylabel('True positive rate')
plt.title('ROC Curve')
plt.legend(loc = 'best')
plt.show()

                                                                               

2、通过样本异常程度进行分析

train.out_pred.groupby(train.obs_mth).mean()

train.out_pred.groupby(train.obs_mth).max()

train.out_pred.groupby(train.obs_mth).var()

train['for_pred'] = np.where(train.out_pred>0.7,1,0)

train.for_pred.groupby(train.obs_mth).sum()/train.for_pred.groupby(train.obs_mth).count()


#看一下badrate
train.bad_ind.groupby(train.for_pred).sum()/train.bad_ind.groupby(train.for_pred).count()

3、冷启动,假设没有标签

y_pred = clf.predict_proba(x,method ='linear')[:,1]
fpr_lr_train,tpr_lr_train,_ = roc_curve(y,y_pred)
train_ks = abs(fpr_lr_train - tpr_lr_train).max()
print('train_ks : ',train_ks)

y_pred = clf.predict_proba(val_x,method ='linear')[:,1]
fpr_lr,tpr_lr,_ = roc_curve(val_y,y_pred)
val_ks = abs(fpr_lr - tpr_lr).max()
print('val_ks : ',val_ks)
from matplotlib import pyplot as plt
plt.plot(fpr_lr_train,tpr_lr_train,label = 'train LR')
plt.plot(fpr_lr,tpr_lr,label = 'evl LR')
plt.plot([0,1],[0,1],'k--')
plt.xlabel('False positive rate')
plt.ylabel('True positive rate')
plt.title('ROC Curve')
plt.legend(loc = 'best')
plt.show()

参考文献:https://pyod.readthedocs.io/en/latest/pyod.html

  • 14
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
监督异常检测技术一直是机器学习领域的研究热点之一。随着数据量的不断增加和数据种类的不断增多,异常检测技术的应用场景也越来越广泛。下面是无监督异常检测技术发展的几个阶段: 1. 基于统计模型的方法:最早的无监督异常检测技术是基于统计模型的方法,如基于正态分布的方法、基于混合高斯模型的方法等。这些方法通常假设正常数据服从某种分布,然后将异常值定义为不符合该分布的数据点。 2. 基于聚类的方法:随着聚类算法的发展,一些基于聚类的方法也被用于无监督异常检测中。这些方法通常将数据点聚为几个簇,然后将不属于任何簇的数据点定义为异常值。 3. 基于密度的方法:基于密度的方法是一类比较新的无监督异常检测方法,它试图找到数据集中的低密度区域,将这些区域中的数据点定义为异常值。这些方法通常使用密度估计算法来计算数据点的密度,并根据密度选择异常值。 4. 基于深度学习的方法:近年来,深度学习技术的发展使得一些基于深度学习的方法也被用于无监督异常检测中。这些方法通常使用自编码器等深度学习模型来学习数据的表示,并将重构误差作为异常值的度量。 总的来说,无监督异常检测技术的发展经历了从基于统计模型到基于聚类再到基于密度的演化过程,最近几年则开始涌现一些基于深度学习的方法。未来,随着数据量和数据种类的不断增加,无监督异常检测技术还有很大的发展空间。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值