广告反作弊的业务背景,及常用的数据见我的上一篇博客广告反作弊思路分享,本篇主要讲一下涉及的无监督算法、算法原理、实现方式、评估方法
参考书籍《智能风控——原理、算法与工程实践》
一、算法原理
孤立森林是一种基于空间随机划分思想的集成算法,由多棵二叉树并行得到,再将输出结果进行加权平均。在传统的二叉树中,每一层的分化是基于均方差最小化对特征和特征值进行选择,不断迭代从而得到最终的决策树。在IF的每棵孤立树(iTree)中,特征及特征值的选择是完全从数据中随机选取的。
计算过程可以概括为:
1)从样本空间中随机选择一部分样本,从特征空间中随机选择一个特征;
2)在现有特征维度上随机选择一个特征值作为划分节点,即阈值;
3)分化决策树,左枝放入小于等于该阈值的样本,右枝放入大于该阈值的样本;
4)重复上述过程,直到数据不可再分(比如当前叶子节点所有样本的所有特征维度上的取值都相同),或者当前树的分化达到了开始设定的二叉树深度。
涉及的3个公式:
1)路径长度
样本
x
i
x_i
xi在当前有T个样本的孤立树上的路径长度
h
(
x
i
)
h(x_i)
h(xi)的计算公式为:
h
(
x
i
)
=
e
i
+
C
(
T
)
h(x_i)=e_i+C(T)
h(xi)=ei+C(T)其中,
e
i
e_i
ei表示样本
x
i
x_i
xi从一棵孤立树的根部游走到当前节点的边的个数,
C
(
T
)
C(T)
C(T)是一个偏置项。
2)平均路长
偏置项
C
(
T
)
C(T)
C(T)的计算公式如下:
C
(
n
)
=
2
H
(
n
−
1
)
−
2
(
n
−
1
)
n
C(n) = 2H(n-1)-\frac{2(n-1)}{n}
C(n)=2H(n−1)−n2(n−1)其中,n是当前决策树根节点中的样本数,
H
(
k
)
=
l
n
(
k
)
+
ε
H(k) = ln(k)+\varepsilon
H(k)=ln(k)+ε,
ε
=
0.5772156649
\varepsilon=0.5772156649
ε=0.5772156649为欧拉常数,偏置项表示使用T个样本训练的二叉树的平均路长。
3)异常分
明确了平均路径长度和平均路长之后,IF的异常分可定义为:
S
c
o
r
e
(
x
i
)
=
2
−
E
(
h
(
x
i
)
)
C
(
ϕ
)
Score(x_i)=2^{\frac{-E(h(x_i))}{C(\phi)}}
Score(xi)=2C(ϕ)−E(h(xi))
其中,
E
(
h
(
x
i
)
)
E(h(x_i))
E(h(xi))表示
x
i
x_i
xi在所有孤立树上的路径长度的均值,
ϕ
\phi
ϕ表示一棵孤立树上训练样本的个数,
C
(
ϕ
)
C(\phi)
C(ϕ)表示用
ϕ
\phi
ϕ个样本训练的二叉树的平均路径长度,作为归一化项。
从IF的异常分计算方式看:
得分越接近1,说明该样本越异常,样本x在多棵孤立树中的平均路径长度越短;
得分越接近0,说明该样本越正常,样本x在多棵孤立树中的平均路径长度越长
二. 算法实现方法
2.1 Python实现
github链接https://github.com/SilenceSengoku/IsolationFroest2
import pandas as pd
import numpy as np
from sklearn.ensemble import IsolationForest
import matplotlib.pyplot as plt
from scipy import stats
from sklearn.preprocessing import MinMaxScaler
#read data
dataset = pd.read_csv('customers_nums.csv',engine='python')
#set variable
rs = np.random.RandomState(169);
outliers_fraction = 0.05;lendata = dataset.shape[0]
#label
anomaly = [];test_data = []
#sit normalize limited
nmlz_a = -1;nmlz_b = 1;
#some function is useful
def normalize(dataset,a,b):
scaler = MinMaxScaler(feature_range=(a, b))
normalize_data = scaler.fit_transform(dataset)
return normalize_data
#read dataset x,y
x = normalize(pd.DataFrame(dataset, columns=['cr']), nmlz_a, nmlz_b)
y = normalize(pd.DataFrame(dataset, columns=['7wr']), nmlz_a, nmlz_b)
#
ifm = IsolationForest(n_estimators=100, verbose=2, n_jobs=2,
max_samples=lendata, random_state=rs, max_features=2)
if __name__ == '__main__':
Iso_train_dt = np.column_stack((x, y))
ifm.fit(Iso_train_dt)
scores_pred = ifm.decision_function(Iso_train_dt)
threshold = stats.scoreatpercentile(scores_pred, 100 * outliers_fraction)
# 使用预测值取5%分位数来定义阈值(基于小概率事件5%)
# 根据训练样本中异常样本比例,得到阈值,用于绘图
# matplotlib
# plot the line, the samples, and the nearest vectors to the plane
xx, yy = np.meshgrid(np.linspace(nmlz_a, nmlz_b, 50), np.linspace(nmlz_a, nmlz_b, 50)) # 画格子
Z = ifm.decision_function(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.title("IsolationForest ")# plt.contourf(xx, yy, Z, cmap=plt.cm.Blues_r)
otl_proportion = int(outliers_fraction * len(dataset['Date']))
plt.contourf(xx, yy, Z, levels=np.linspace(Z.min(), threshold, otl_proportion), cmap=plt.cm.hot)# 绘制异常点区域,值从最小的到阈值的那部分
a = plt.contour(xx, yy, Z, levels=[threshold], linewidths=2, colors='red')# 绘制异常点区域和正常点区域的边界
plt.contourf(xx, yy, Z, levels=[threshold, Z.max()], colors='palevioletred')
# palevioletred 紫罗兰
# 绘制正常点区域,值从阈值到最大的那部分
for i in scores_pred:
if i <= threshold:
#print(i)
test_data.append(1)
anomaly.append(i)
else:
test_data.append(0)
ano_lable = np.column_stack(((dataset['Date'],dataset['data'],x,y,scores_pred, test_data)))
df = pd.DataFrame(data=ano_lable, columns=['Date','data','x', 'y', 'IsoFst_Score','label'])
b = plt.scatter(df['x'][df['label'] == 0], df['y'][df['label'] == 0], s=20, edgecolor='k',c='white')
c = plt.scatter(df['x'][df['label'] == 1], df['y'][df['label'] == 1], s=20, edgecolor='k',c='black')
plotlist = df.to_csv('Iso_list.csv')
plt.axis('tight')
plt.xlim((nmlz_a, nmlz_b))
plt.ylim((nmlz_a, nmlz_b))
plt.legend([a.collections[0], b, c],
['learned decision function', 'true inliers', 'true outliers'],
loc="upper left")
print("孤立森林阈值 :",threshold)
print("全量数据样本数:",len(dataset),"个")
print("检测异常样本数:",len(anomaly),"个")
plt.show()
2.1.2 scikit-learn 参数说明
class sklearn.ensemble.IsolationForest(*,
n_estimators=100,
max_samples='auto',
contamination='auto',
max_features=1.0,
bootstrap=False,
n_jobs=None,
behaviour='deprecated',
random_state=None,
verbose=0,
warm_start=False)
源码及参数实例见https://scikit-learn.org.cn/view/631.html