微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

IsolationForest 决策分数转换为概率算法 问题目前的方法最小的、可重复的示例问题这种方法的局限性这种方法的局限性

如何解决IsolationForest 决策分数转换为概率算法 问题目前的方法最小的、可重复的示例问题这种方法的局限性这种方法的局限性

我希望创建一个通用函数来将 sklearn's IsolationForest输出 decision_scores 转换为真实概率 [0.0,1.0]

我知道并阅读了 the original paper 并且我在数学上理解该函数输出不是概率,而是每个基估计器构建的路径长度的平均值,以隔离异常.

问题

我想将该输出转换为 tuple (x,y) 形式的概率,其中 x=P(anomaly)y=1-x

目前的方法

def convert_probabilities(predictions,scores):
    from sklearn.preprocessing import MinMaxScaler

    new_scores = [(1,1) for _ in range(len(scores))]

    anomalous_idxs = [i for i in (range(len(predictions))) if predictions[i] == -1]
    regular_idxs = [i for i in (range(len(predictions))) if predictions[i] == 1]

    anomalous_scores = np.asarray(np.abs([scores[i] for i in anomalous_idxs]))
    regular_scores = np.asarray(np.abs([scores[i] for i in regular_idxs]))

    scaler = MinMaxScaler()

    anomalous_scores_scaled = scaler.fit_transform(anomalous_scores.reshape(-1,1))
    regular_scores_scaled = scaler.fit_transform(regular_scores.reshape(-1,1))

    for i,j in zip(anomalous_idxs,range(len(anomalous_scores_scaled))):
        new_scores[i] = (anomalous_scores_scaled[j][0],1-anomalous_scores_scaled[j][0])
    
    for i,j in zip(regular_idxs,range(len(regular_scores_scaled))):
        new_scores[i] = (1-regular_scores_scaled[j][0],regular_scores_scaled[j][0])

    return new_scores

modified_scores = convert_probabilities(model_predictions,model_decisions)

最小的、可重复的示例

import pandas as pd
from sklearn.datasets import make_classification,load_iris
from sklearn.ensemble import IsolationForest
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split

# Get data
X,y = load_iris(return_X_y=True,as_frame=True)
anomalies,anomalies_classes = make_classification(n_samples=int(X.shape[0]*0.05),n_features=X.shape[1],hypercube=False,random_state=60,shuffle=True)
anomalies_df = pd.DataFrame(data=anomalies,columns=X.columns)

# Split into train/test
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.15,random_state=60)

# Combine testing data
X_test['anomaly'] = 1
anomalies_df['anomaly'] = -1
X_test = X_test.append(anomalies_df,ignore_index=True)
y_test = X_test['anomaly']
X_test.drop('anomaly',inplace=True,axis=1)

# Build a model
model = IsolationForest(n_jobs=1,bootstrap=False,random_state=60)

# Fit it
model.fit(X_train)

# Test it
model_predictions = model.predict(X_test)
model_decisions = model.decision_function(X_test)

# Print results
for a,b,c in zip(y_test,model_predictions,model_decisions):
    print_str = """
    Class: {} | Model Prediction: {} | Model Decision score: {}
    """.format(a,c)

    print(print_str)

问题

modified_scores = convert_probabilities(model_predictions,model_decisions)

# Print results
for a,b in zip(model_predictions,modified_scores):
    ans = False
    if a==-1:
        if b[0] > b[1]:
            ans = True
        else:
            ans = False
    elif a==1:
        if b[1] > b[0]:
            ans=True
        else:
            ans=False
    print_str = """
    Model Prediction: {} | Model Decision score: {} | Correct: {}
    """.format(a,str(ans))

    print(print_str)

显示一些奇怪的结果,例如:

Model Prediction: 1 | Model Decision score: (0.17604259932311161,0.8239574006768884) | Correct: True
Model Prediction: 1 | Model Decision score: (0.7120367886017022,0.28796321139829784) | Correct: False
Model Prediction: 1 | Model Decision score: (0.7251531538304419,0.27484684616955807) | Correct: False
Model Prediction: -1 | Model Decision score: (0.16776449326185877,0.8322355067381413) | Correct: False
Model Prediction: 1 | Model Decision score: (0.8395087028516501,0.1604912971483499) | Correct: False

模型预测:1 |模型决策分数:(0.0,1.0) |正确:正确

怎么可能预测是-1 (anomaly),但概率只有37%?或者预测为 1 (normal),但概率为 26%?

请注意,玩具数据集有标签,但无监督异常检测算法显然不假设任何标签

解决方法

您在这里遇到了三个不同的问题。首先,不能保证您从 IsolationForest 获得的分数越低,样本为异常值的概率也越高。我的意思是,如果对于一堆样本,您在 model_decision(-0.3 : -0.2) 范围内获得 (0.1 : 0.2) 分数,这并不一定意味着第一批是异常值的概率更高(但 通常是这样)。

第二个问题是从分数到概率的实际映射函数。所以假设较低的分数对应于较低的概率作为常规样本(并且样本为异常的概率较高),从分数到概率的映射不一定是线性函数(例如MinMaxScaler)。对于您的数据,您可能需要找到自己的函数。正如@Jon Nordby 建议的那样,它可以是分段线性函数。我个人更喜欢使用 logistic function 将分数映射到概率。在这种情况下,使用 model_decisions 以零为中心特别有益,负值表示异常。所以你可以使用类似

def logf(x,alfa=10): 
    return 1/(1 + np.exp( -alfa * x ))

用于从分数到概率的映射。 Alpha 参数控制值在决策边界周围的紧密程度。同样,这不一定是最好的映射函数,它只是我喜欢使用的东西。

最后一期与第一期相关,可能会回答您的问题。即使一般分数与非异常概率相关,也不能保证对于所有样本来说这都是真的。所以可能会出现分数为0.1的某个点是异常点,而分数为-0.1的点是正常点被误检测为异常点。样本是否异常由model_decisions是否小于零来决定。对于分数接近于零的样本,错误的概率更高。

,

为什么会这样

您正在观察无意义的概率,因为您正在为内点和离群点拟合不同的标度。因此,如果您的决策分数范围是 [0.5,1.5] 的内点,您将这些分数映射到概率 [0,1]。此外,如果异常值的决策分数范围为 [-1.5,-0.5],那么您还将这些分数映射到概率 [0,1]。如果决策分数为 1.5 OR -0.5,您最终将成为内点的概率设置为 1。这显然不是您想要的,您希望决策分数为 -0.5 的观察的概率低于决策分数为 1.5 的观察。

第一个选项

第一个解决方案是为您的所有分数设置一个单独的缩放器。这也将大大简化您的转换功能,如下所示:

def convert_probabilities(predictions,scores):

    scaler = MinMaxScaler()

    scores_scaled = scaler.fit_transform(scores.reshape(-1,1))
    new_scores = np.concatenate((1-scores_scaled,scores_scaled),axis=1)

    return new_scores

这将是具有所需属性的 (probability of being an outlier,probability of being an inlier) 元组。

这种方法的局限性

这种方法的主要限制之一是无法保证内部值和异常值之间的概率截止是 0.5,这是最直观的选择。您最终可能会遇到这样的情况:“如果成为内点的概率小于 60%,那么模型会预测它是外点”。

第二个选项

第二个选项更接近您想要做的事情。您确实为每个类别安装了一个缩放器,但是,与您所做的不同,两个缩放器都不会返回同一范围内的值。您可以将异常值设置为缩放至 [0,0.5],将异常值设置为 [0.5,1]。这样做的好处是它会在 0.5 处创建一个直观的决策边界,其中上述所有概率都是内点,反之亦然。然后它看起来像这样:

def convert_probabilities(predictions,scores):

    scaler_inliers = MinMaxScaler((0.5,1))
    scaler_outliers = MinMaxScaler((0,0.5))

    scores_inliers_scaled = scaler_inliers.fit_transform(scores[predictions == 1].reshape(-1,1))
    scores_outliers_scaled = scaler_outliers.fit_transform(scores[predictions == -1].reshape(-1,1))
    scores_scaled = np.zeros((len(scores),1))
    scores_scaled[predictions == 1] = scores_inliers_scaled
    scores_scaled[predictions == -1] = scores_outliers_scaled
    new_scores = np.concatenate((1-scores_scaled,axis=1)

    return new_scores

这种方法的局限性

主要限制是您如何将两个定标器重新组合在一起。在上面的代码示例中,两者都在 0.5 处连接,这意味着“最佳异常值”和“最差内部值”具有相同的 0.5 概率。但是,它们的决策分数并不相同。因此,一种选择是将缩放范围更改为 [0,0.49],and [0.51,1]` 左右,但正如您所见,这变得更加随意。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。