反欺诈往往看做是二分类问题,但是仔细想想是多分类问题,因为每种不同类型的欺诈都当做是一种单独的类型。欺诈除了多样并且不断变化,欺诈检测还面临一下问题:
1). 由于大部分情况数据是没有标签的,各种成熟的监督学习是没有办法应用
2). 区分噪音和异常点时难度比较大,甚至需要一点点经验
3). 当多种不同的欺诈结合在一起时,区分欺诈类型比较难,因为不了解每一种欺诈定义
4). 即使有真的欺诈数据,即在有标签的情况下用监督学习,也存在很大的不确定性。用这种历史数据学出的模型只能检测当时存在以及比较相似的欺诈,而对于不同的诈骗和从未见过的诈骗,模型预测效果一般比较差。
因此,在实际情况中,一般不直接用任何监督学习,或者说不能至少单独依靠一个监督学习模型来奢求检测到所有的欺诈。没有历史标签和对诈骗的理解,无法做出对诈骗细分的模型。一般常见的做法是使用无监督模型,且需要不同专家根据经验来验证预测,提供反馈,便于及时调整模型。
拿到含有欺诈的数据之后,一般做法有通过关联矩阵分析以及多维尺度变换。
当一个场景需要做预判的时候,有没有标签,往往能做的主要有迁移学习、专家模型、无监督算法。
源域样本和目标域样本分布有比较大的区别,目标域样本量不够的话,通过算法缩小边缘分布之间和条件分布之间的差异。
不足之处:需要拥有与当前目标场景相关的源域数据
主要是根据主观经验进行评判,而不是根据统计分析或者模型算法来进行客观的计算。常规操作过程如下所示:
不足之处:需要大量的行业经验积累,有时候不太具有说服性
缺乏足够的先验知识,无法对数据进行标记时,使用的一种机器学习方法,代表聚类、降维等。在风控领域中主要使用的是聚类和无监督异常检测。聚类是发现样本之间的相似性,异常检测是发现样本之间的差异性。
一般主要是对负样本聚类,将有问题的用户细分为几类。社区发现算法一般是识别团伙欺诈的主要手段之一,主要方法是通过知识图谱将小团体筛选出来。在知识图谱中,聚集意味着风险。
异常点检测,通常也被称为离群点检测,是找出与预期对象的行为差异较大的对象的一个检测过程。检测出来的数据被称为异常点或者离群点。异常点在生产生活中有比较多的应用,例如广告点击反作弊、设备损坏等。异常点是一个数据对象,明显不同于其他的数据对象。如下所示,N1,N2为区域内的正常数据,而离这两个正常区域比较远的地方的点为异常点。
异常检测和不均衡学习的区别是,异常检测一般是无监督的;与普通的二分类区别是异常检测往往看作为是二分类,但是其实是多分类(由于造成异常的原因各不相同)
异常检测算法的前提假设:1) 异常数据跟样本中大多数数据不太一样 2) 异常数据在整体数据样本中占比相对比较小
异常检测的主要思想是基于样本(小群体)之间的相似度: 距离、密度、簇
异常检测算法的意义:
1). 很多场景没有标签或者标签比较少,不能训练监督模型
2). 样本总是在发生变换,只能从一个小群体内部发现异常
3). 异常检测假设异常样本数据量占比较少,并且在某种维度上远离其他样本,符合个体欺诈的先验知识。在团体欺诈不太适用。
4). 样本群体有异构成分,可以对样本进行筛选
常见的异常检测算法有Z-score算法、KNN算法、Local Outlier Factor、孤立森林等
假设样本服从正态分布,用于描述样本偏离正态分布的程度。通过计算μ和σ得到当前样本所属于的正态分布的表达式,然后分别计算每个样本在这个概率密度函数下被生成的概率,当概率小于某一阈值我们认为这个样本是不属于这个分布的,因此定义为异常值。计算公式如下所示:
根据获取平均值和方差的估计值,然后给定一个新的训练实例,根据模型计算p(x):
当p(x) < ep(x) < e时候,数据未异常
缺点是需要假设样本满足正态分布,而大部分场景不满足正态分布假设条件
KNN算法专注于全局异常检测,所以无法检测到局部异常。
首先,对于数据集中的每条记录,必须找到k个最近的邻居。然后使用这K个邻居计算异常分数。
我们有三种方法
在实际方法中后两种的应用度较高。然而,分数的绝对值在很大程度上取决于数据集本身、维度数和规范化。参数k的选择当然对结果很重要。如果选择过低,记录的密度估计可能不可靠。(即过拟合)另一方面,如果它太大,密度估计可能太粗略。K值的选择通常在10 基于密度的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值,然后在二维空间中展示,如下图所示,数据点的LOF值越大表示该点越异常 Trick: LOF算法中关于局部可达密度的定义其实暗含了一个假设,即:不存在大于等于 k 个重复的点。当这样的重复点存在的时候,这些点的平均可达距离为零,局部可达密度就变为无穷大,会给计算带来一些麻烦。在实际应用时,为了避免这样的情况出现,可以把 k-distance 改为 k-distinct-distance,不考虑重复的情况。或者,还可以考虑给可达距离都加一个很小的值,避免可达距离等于零。 先用一个简单的例子来说明 Isolation Forest 的基本想法。假设现在有一组一维数据(如下图所示),我们要对这组数据进行随机切分,希望可以把点 A 和点 B 单独切分出来。具体的,我们先在最大值和最小值之间随机选择一个值 x,然后按照 =x 可以把数据分成左右两组。然后,在这两组数据中分别重复这个步骤,直到数据不可再分。显然,点 B 跟其他数据比较疏离,可能用很少的次数就可以把它切分出来;点 A 跟其他数据点聚在一起,可能需要更多的次数才能把它切分出来。 我们把数据从一维扩展到两维。同样的,我们沿着两个坐标轴进行随机切分,尝试把下图中的点A'和点B'分别切分出来。我们先随机选择一个特征维度,在这个特征的最大值和最小值之间随机选择一个值,按照跟特征值的大小关系将数据进行左右切分。然后,在左右两组数据中,我们重复上述步骤,再随机的按某个特征维度的取值把数据进行细分,直到无法细分,即:只剩下一个数据点,或者剩下的数据全部相同。跟先前的例子类似,直观上,点B'跟其他数据点比较疏离,可能只需要很少的几次操作就可以将它细分出来;点A'需要的切分次数可能会更多一些。 按照先前提到的关于“异常”的两个假设,一般情况下,在上面的例子中,点B和点B' 由于跟其他数据隔的比较远,会被认为是异常数据,而点A和点A' 会被认为是正常数据。直观上,异常数据由于跟其他数据点较为疏离,可能需要较少几次切分就可以将它们单独划分出来,而正常数据恰恰相反。这其实正是Isolation Forest(IF)的核心概念。IF采用二叉树去对数据进行切分,数据点在二叉树中所处的深度反应了该条数据的“疏离”程度。整个算法大致可以分为两步: 训练:构建一棵 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、清洗建模数据集,将异常样本通过无监督算法进行筛选 2、通过样本异常程度进行分析 3、冷启动,假设没有标签 参考文献:https://pyod.readthedocs.io/en/latest/pyod.html Local Outlier Factor
/******** 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)
Isolation Forest
四、应用案例
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()
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()
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()