異常検出 IF 隔離された森林の実践

目次

1. 原則

2.パラメータの詳細説明

3. 実戦

1. データの紹介

2. データの前処理

3. 特徴の導出

4. 異常分析と機能スクリーニング

5. モデルの構築と評価

6. カテゴリ変数コーディングの最適化

焦点を当てる

参考


注文 - 重要

        異常検出では、アルゴリズムの選択はプロセスの一部にすぎません。初期段階で最も重要なことは、ビジネス シナリオとビジネス目標に基づいてターゲット関連の機能をマイニングすることです (信用/取引詐欺に適用する場合は、次の点に焦点を当てる必要があります)。マイニング詐欺の特徴)、データの分布と特徴を把握し、特徴分布に基づいて適切なアルゴリズムを選別、選択する また、ビジネスシナリオによっては解釈可能性を考慮する必要があり、異常検出自体は教師なしアルゴリズムであり、その実装は独立した意思決定よりも教師付きモデルの支援に適しています。このシリーズでは、これらの点を可能な限り包括的に取り上げます。読者の皆様は、コミュニケーションや議論を歓迎します。

        

        論文の宛先:孤立の森

1. 原則

        Isolation Forest はツリー構造に基づく異常検出アルゴリズムであり、その原理は次のように簡単に説明できます: 特徴をランダムに選択し、分割点をランダムに選択することにより、データ セットは徐々に小さなサブセットに分割され、各サブセットにはデータ ポイントが 1 つだけ含まれます。したがって、多くの孤立したデータ ポイントが形成されますが、外れ値は分離するために通過する分割が少なくて済むため、すべてのツリーの平均で、パスの長さは通常のポイントよりも短くなります。パスが短いサンプルの異常スコアは高くなります。具体的な計算プロセスは次のとおりです。

  1. 孤立した森の構築: 孤立した森の木の数と各木の最大深さを設定し、各木に対してデータセットの一部をサンプルとしてランダムに選択し、そこから特徴と固有値をランダムに選択して実行します各ツリーがサンプル ポイントを 1 つだけ持つか、ツリーの深さのしきい値に達するまでのバイナリ ツリーの再帰的セグメンテーション。

  2. パス長の計算: 各データ点 x について、各ツリーのツリー内のパス長 h(x,T) を計算し、すべてのツリーのパス長の平均 h(x) を求めます。

  3. 異常スコアの計算: パスの長さに応じて、各データ ポイントの異常スコア s(x) を計算します。式は次のとおりです。

     ここで、H(i) は高調波系列、n はデータセット内のサンプル数、c(n) は平均経路長の期待値で、式は次のとおりです。

  4. 異常ポイントのフィルタリング: 異常スコアと所定のしきい値に基づいて異常ポイントをフィルタリングします。$s(x)$ がしきい値より大きい場合、$x$ は外れ値とみなされます。

2.パラメータの詳細説明

params={
    'n_estimators' : 1000 ,   : 迭代次数、孤立树的数量
    'max_samples' : 'auto' ,  : 每个孤立树中采样的样本数量,“auto”表示训练样本量
    'contamination' : 'auto' ,  : 数据集中异常值的比例,“auto”表示训练样本中异常值比例
    'max_features' : 1.0 ,  : 列采样比例
    'bootstrap' : False ,  : 是否使用 bootstrap 重复采样
    'n_jobs' : 4 ,  : 并行核数
    'random_state' : 1 , 
    'verbose' : 0  : 信息显示
}

3. 実戦

1. データの紹介

        データ ソース: DataFoutain 上の UEBA ベースのユーザー異常オンライン行動分析コンテスト データを使用し、以下のリンクからダウンロードすることができます (最初に登録するにはログインする必要があります)、または Weixin パブリック アカウントの Python リスク管理モデルとデータをフォローすることもできます解析応答異常検知IF取得実践

                UEBACompetitions に基づくユーザーの異常なオンライン行動の分析 - DataFountain

        コンテストタイトル紹介:

        企業の情報化レベルが継続的に向上するにつれ、資産としてのデータはますます多くの企業の共通認識となり、企業は産業やサービス、マーケティングサポート、事業運営、リスク管理などの生産、運営、管理活動に関与しています。従業員や顧客の大量の営業秘密、業務秘密、個人情報などの管理、情報漏洩を防止します。
  現在、大多数の企業は機密データ保護に関する関連管理措置と運用行動規範を導入していますが、依然として機密データの漏洩につながる異常な運用行為が存在しており、「Securonix 2020 Insider Threat Report」では、60%の企業が機密データの漏洩につながる異常な運用行為が存在していると指摘しています。内部ネットワークのセキュリティとデータが関係しており、漏洩事件はすべて企業ユーザーの異常な操作動作に関連しています。
  企業の機密データを効果的に保護し、企業のセキュリティ運用行動規範を実践し、異常な動作動作によって引き起こされる企業の機密データ漏洩セキュリティインシデントを防ぐために、ユーザーの異常動作の分析と識別は重要かつ困難なテクノロジーの 1 つとなっています。

        機械学習、ディープラーニング、UEBA、その他の人工知能手法を使用し、ラベルのないユーザーの毎日のオンラインログデータに基づいて、ユーザーのオンライン行動ベースラインとオンライン行動評価モデルを構築し、オンライン行動間の距離に基づいて逸脱の度合いを決定します。そしてベースライン。
  (1) ユーザーの毎日のインターネットサーフィンデータから行動ベースラインを構築する
  (2)教師なし学習モデルを使用して、ユーザーのネットサーフィン行動の特徴に基づいてインターネット行動評価モデルを構築し、ネットサーフィン行動の逸脱度を評価するベースラインから。

2. データの前処理

        トレーニング セットのデータ サイズは 52w+ で、ret は異常スコアです。アカウント、グループ、IP、URL、switchIP、ポート、VLAN、およびその他のフィールドのラベル エンコーディング。時間フィールドから年、月、週、日、時などの時間フィールドを抽出します。

3. 特徴の導出

        グループ、アカウント、および group_IP_nunique などの他のグループに応じて、IP、URL、switchIP、ポート、VLAN、およびその他のフィールドの一意の値の数をカウントします。

        グループおよび IP グループの統計データの数に従って、特徴 group_IP_cnt が取得されます。同様のことが他のフィールドにも当てはまります。        

1-3 コード部分:

# 导包
import re
import os
from sqlalchemy import create_engine
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')
import sklearn
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_curve,roc_auc_score
from sklearn.metrics import roc_curve, auc
import matplotlib.pyplot as plt
import gc
from sklearn import preprocessing
from sklearn.preprocessing import MinMaxScaler
import math
from sklearn import metrics
from sklearn.metrics import mean_absolute_error,mean_squared_error,r2_score
import time
from sklearn.model_selection import KFold,cross_val_score


# 1、数据读取
df=pd.read_csv('数据文件/基于UEBA的用户上网异常行为分析.csv',encoding='gbk')
print(df.shape)
df.head()

# 2、特征编码及特征衍生
def get_label_encode(df,col_list):
    
    def label_encode(df,col):
        value_list=list(df[col].unique())
        return {value:i+1 for value,i in zip(value_list,range(len(value_list)))}

    result={}
    for col in col_list:
        result[col]=label_encode(df,col)
    return result
    
def data_label_encode(df,map_dic):
    df_copy=df.copy()
    for col in map_dic.keys():
        if col in df.columns:
            df_copy[col]=df_copy[col].map(map_dic[col])
    return df_copy

def time_pre(df):
    df.time=pd.to_datetime(df.time)
    df['year']=df.time.dt.year
    df['month']=df.time.dt.month
    df['week']=df.time.dt.week
    df['day']=df.time.dt.day
    df['hour']=df.time.dt.hour
    df['minute']=df.time.dt.minute
    return df

def fea_derive(df_copy):
    result=(
        df_copy
        .merge(df_copy.groupby(['group','IP']).id.count().reset_index().rename(columns={'id':'group_IP_cnt'}),how='left',on=['group','IP'])
        .merge(df_copy.groupby(['group','switchIP']).id.count().reset_index().rename(columns={'id':'group_switchIP_cnt'}),how='left',on=['group','switchIP'])
        .merge(df_copy.groupby(['group','vlan']).id.count().reset_index().rename(columns={'id':'group_vlan_cnt'}),how='left',on=['group','vlan'])
        .merge(df_copy.groupby(['group','port']).id.count().reset_index().rename(columns={'id':'group_port_cnt'}),how='left',on=['group','port'])
        .merge(df_copy.groupby(['account','IP']).id.count().reset_index().rename(columns={'id':'account_IP_cnt'}),how='left',on=['account','IP'])
        .merge(df_copy.groupby(['account','switchIP']).id.count().reset_index().rename(columns={'id':'account_switchIP_cnt'}),how='left',on=['account','switchIP'])
        .merge(df_copy.groupby(['account','vlan']).id.count().reset_index().rename(columns={'id':'account_vlan_cnt'}),how='left',on=['account','vlan'])
        .merge(df_copy.groupby(['account','port']).id.count().reset_index().rename(columns={'id':'account_port_cnt'}),how='left',on=['account','port'])
        
        .merge(df_copy.groupby(['group']).IP.nunique().reset_index().rename(columns={'IP':'group_IP_nunique'}),how='left',on=['group'])
        .merge(df_copy.groupby(['group']).switchIP.nunique().reset_index().rename(columns={'switchIP':'group_switchIP_nunique'}),how='left',on=['group'])
        .merge(df_copy.groupby(['group']).vlan.nunique().reset_index().rename(columns={'vlan':'group_vlan_nunique'}),how='left',on=['group'])
        .merge(df_copy.groupby(['group']).port.nunique().reset_index().rename(columns={'port':'group_port_nunique'}),how='left',on=['group'])
        .merge(df_copy.groupby(['account']).IP.nunique().reset_index().rename(columns={'IP':'account_IP_nunique'}),how='left',on=['account'])
        .merge(df_copy.groupby(['account']).switchIP.nunique().reset_index().rename(columns={'switchIP':'account_switchIP_nunique'}),how='left',on=['account'])
        .merge(df_copy.groupby(['account']).vlan.nunique().reset_index().rename(columns={'vlan':'account_vlan_nunique'}),how='left',on=['account'])
        .merge(df_copy.groupby(['account']).port.nunique().reset_index().rename(columns={'port':'account_port_nunique'}),how='left',on=['account'])

    )
    
    return result

map_dic=get_label_encode(df,['account', 'group', 'IP', 'url', 'switchIP','port','vlan'])
df_copy=df.pipe(data_label_encode,map_dic).pipe(time_pre).pipe(fea_derive)
df_copy.head()

4. 異常分析と機能スクリーニング

        異常検出はデータ分布を対象とするため、ほとんどのデータ分布とは異なるデータ (低頻度の値) は外れ値とみなされ、特徴分布の統計分析が考慮されます。

        まず、IP、URL、switchIP、port、vlanなどの特徴を確認しますが、このうちurlやportには低頻度の値が多く、異常検知にはあまり寄与しないので、それらは排除されます。

        IP、VLAN、および SwitchIP は正常に分散され、これらの特性は保持されます。 

        時間特性は、月、週、日が均等に分散されているため、時間のみが保持され、図に示すように、12 時から 23 時までがインターネット アクセスの頻度が低い時間帯となります。

        cnt と nunique の統計特性は分布に基づいており、account_IP_nunique、account_switchIP_nunique、account_port_nunique のみが保持されます。

import pyecharts.options as opts
from pyecharts.charts import Bar, Line

def distribution_plot_df(df,col,plot='bar'):
    sta=df_copy[col].value_counts().sort_index()
    x,y=list(sta.index),list(sta)
    return distribution_plot(x,y,col,plot)

def distribution_plot(x,y,col,plot='bar'):
    if plot=='bar':
        return distribution_bar(x,y,col)
    else:
        return distribution_line(x,y,col)

def distribution_bar(x,y,col):
    bar = (
        Bar(init_opts=opts.InitOpts(width="700px", height="500px"))
        .add_xaxis(x)
        .add_yaxis(
            col, 
            y,
            label_opts=opts.LabelOpts(is_show=False),
            markpoint_opts=opts.MarkPointOpts(
                label_opts=opts.LabelOpts(
                    font_size=13,
                    border_width=10
                ),
            ),
        )

        .set_global_opts(
#             title_opts=opts.TitleOpts(title=title),
            tooltip_opts=opts.TooltipOpts(trigger="axis"),
            yaxis_opts=opts.AxisOpts(
                splitline_opts=opts.SplitLineOpts(
                    is_show=True, linestyle_opts=opts.LineStyleOpts(opacity=1)
                ),
            ),
        )
    )
    return bar

def distribution_line(x,y,col):
    line = (
        Line(init_opts=opts.InitOpts(width="900px", height="500px"))
        .add_xaxis(x)
        .add_yaxis(
            col, 
            y,
            label_opts=opts.LabelOpts(is_show=False),
            is_smooth=True,
            is_symbol_show=False,
            linestyle_opts=opts.LineStyleOpts(width=1.3),
        )

        .set_global_opts(
#             title_opts=opts.TitleOpts(title=title),
            tooltip_opts=opts.TooltipOpts(trigger="axis"),
            yaxis_opts=opts.AxisOpts(
                type_="value",
                splitline_opts=opts.SplitLineOpts(
                    is_show=True, linestyle_opts=opts.LineStyleOpts(opacity=1)
                ),
            ),
        )
    )
    return line

5. モデルの構築と評価

        最終的な機能リスト: アカウント、グループ、時間、vlan、switchIP、IP、account_IP_nunique、account_switchIP_nunique、account_port_nunique

        トレーニングセットとテストセットを8:2の割合で分割し、予測用のIFモデルを構築します ret異常スコア範囲は0~1、IF予測結果は-0.2~0.1であるため、予測結果は正規化されて変化します。最後に、RMSE とスコアはそれぞれトレーニング セットとテスト セットの評価に使用され、スコアが 1 に近いほど効果が高くなります。

        実際の値と予測値の分布は以下のようになりますので、目標の分布に近づくように予測結果の分布を調整することを検討できます。

from sklearn.ensemble import IsolationForest
def distribution_line2(x,y1,y2):  # 拟合曲线
    line = (
        Line(init_opts=opts.InitOpts(width="650px", height="400px"))
        .add_xaxis(x)
        .add_yaxis(
            series_name="true",
            y_axis=y1,
            label_opts=opts.LabelOpts(is_show=False),
            is_smooth=True,
            is_symbol_show=False,
            linestyle_opts=opts.LineStyleOpts(width=1.1),
        )
        .add_yaxis(
            series_name="pred",
            y_axis=y2,
            label_opts=opts.LabelOpts(is_show=False),
            is_smooth=True,
            is_symbol_show=False,
            linestyle_opts=opts.LineStyleOpts(width=1.1),
        )
        .set_global_opts(
            tooltip_opts=opts.TooltipOpts(trigger="axis"),
            xaxis_opts=opts.AxisOpts(
                type_="category",
                axislabel_opts=opts.LabelOpts(rotate=30)
            ),
            yaxis_opts=opts.AxisOpts(
                type_="value",
                splitline_opts=opts.SplitLineOpts(
                    is_show=True, linestyle_opts=opts.LineStyleOpts(opacity=1)
                ),
            ),
        )
        .set_series_opts(
            areastyle_opts=opts.AreaStyleOpts(opacity=0.05),
            label_opts=opts.LabelOpts(is_show=False),
        )
    )

    return line

def rmse_value(y_true,y_pred):
    mse=mean_squared_error(y_true, y_pred)
    rmse=mse**0.5
    score=1/(1+rmse)
    return rmse,score

def get_if_model(df,fea_list): # 训练模型
    params={
        'n_estimators' : 1000 ,   # 迭代次数、孤立树的数量
        'max_samples' : 'auto' ,  # 每个孤立树中采样的样本数量,“auto”表示训练数据集的样本数量
        'contamination' : 'auto' ,  # 数据集中异常值的比例,“auto”表示训练样本中异常值比例
        'max_features' : 1.0 ,  # 列采样比例
        'bootstrap' : False ,  # 是否使用 bootstrap 重复采样
        'n_jobs' : 4  ,  # 并行核数
        'random_state' : 1 , 
        'verbose' : 0  # 信息显示
    }
    if_model = IsolationForest(**params)
    if_model.fit(df[df['sample']=='train'][fea_list])
    
    return if_model

fea_list=['account', 'group', 'hour', 'vlan', 'switchIP', 'IP', 
           'account_IP_nunique', 'account_switchIP_nunique', 'account_port_nunique']

if_model=get_if_model(df_copy,fea_list)
df_copy['if_pred']=if_model.decision_function(df_copy[fea_list])

scaler = MinMaxScaler()
df_copy['if_pred_adjust']=1-pd.DataFrame(scaler.fit_transform(df_copy[['if_pred']]))[0]
rmse_value(df_copy.ret,df_copy.if_pred_adjust)

6. カテゴリ変数コーディングの最適化

2. データの前処理        では、IP や VLAN などのカテゴリ変数にラベル エンコードを直接使用します。ここでは、頻度に応じてエンコードを試み、低頻度の特徴を末尾に配置してロングテール分布を形成し、分離を容易にします。森林から低頻度の値を持つサンプルは事前に分割されています

         モデルの特徴リストとモデル パラメーターを変更せずに、評価用に IF モデルを再トレーニングすると、テスト セットの効果は 0.8202 から 0.8216 に増加しました。

焦点を当てる

        Weixin パブリック アカウントのPython リスク管理モデルとデータ分析応答異常検出 IF の実践に従って、この記事のデータと完全なコードを取得してください。さらに理論的な知識とコード共有も得られます

参考

        2021 CCF BDCI 結果取りまとめにおけるエントリー提案

おすすめ

転載: blog.csdn.net/a7303349/article/details/130273311