¡El algoritmo Python realiza el proceso de modelado completo de casos antifraude! ¿Tantos fraudes de telecomunicaciones?

En los últimos años, los casos de fraude de telecomunicaciones nacionales se han vuelto cada vez más intensos. Este artículo utiliza una versión simplificada del modelo antifraude de una empresa provincial de telecomunicaciones como caso, utilizando herramientas de aprendizaje automático de Python, utilizando algoritmos forestales aleatorios, a partir del procesamiento de datos. , desde la ingeniería de características hasta los modelos antifraude. Un simple registro e introducción del proceso completo de construcción y evaluación del modelo.

diagrama de flujo
Inserte la descripción de la imagen aquí

Configuración del entorno, carga del módulo

# coding: utf-8 
import os 
import numpy as np 
import pandas as pd 
from sklearn.ensemble import IsolationForest 
de sklearn.model_selection import train_test_split 
from sklearn.ensemble import RandomForestClassifier 
de sklearn.metrics import confusion_matrix 
de sklearn.externals import joblibtrics 
de 
desde scipy import stats 
import time 
from datetime import datetime 

import warnings 
warnings.filterwarnings ("ignore") 

os.chdir ('home // zj // python // python3.6.9 // bin // python3') 
123456789101112131415161718

Carga de datos

Personalice el directorio de trabajo y cargue datos de muestra

def read_file (filepath): 
    os.chdir (os.path.dirname (filepath))   
    return pd.read_csv (os.path.basename (filepath), encoding = 'utf-8') 

file_pos = "E: \\ archivo de trabajo \\ *** \\ Identificación antifraude \\ data_train.csv " 
data_pos = read_file (file_pos)   
123456

Cambio de nombre de función

data_pos.columns = ['BIL_ACCS_NBR', 'ASSET_ROW_ID', 'CCUST_ROW_ID', 'LATN_ID', 'TOTAL_CNT', 
'TOTAL_DURATION', 'ZJ_CNT', 'ZJ_TOTAL_DURATION', 'Z_CJ_CTAL_ROAM_ ZJ_ROAM_DURATION', 'ZJ_LOCAL_DURATION', 'ZJ_LONG_CNT', 'BJ_LOCAL_CNT', 'WORK_TIME_TH_TT_CNT', 'FREE_TIME_TH_TT_CNT', 'NIGHT_TIME_TH_TT_CNT', 'DURATION_TP_1', 'DURATION_TP_2', 'DURATION_TP_3', 'DURATION_TP_4', 'DURATION_TP_5', 'DURATION_TP_6' , 'DURATION_TP_7', 'DURATION_TP_8', 
'DURATION_TP_9', 'TOTAL_DIS_BJ_NUM', 'DIS_BJ_NUM', 'DIS_OPP_HOME_NUM', 'OPP_HOME_NUM', 'MSC_NUM', 'DIS_MSC_NUM', 'ZJ_AVG_DURATION', 'TOTAL_ROAM_CNT_RATE', 'ZJ_DURATION_RATE', 'ZJ_CNT_RATE', 'ZJ_ROAM_DURATION_RATE', 'ZJ_ROAM_CNT_RATE', 'DURATION_RATIO_0_15', 'DURATION_RATIO_15_30', 
'DURATION_RATIO_30_45', 'DURATION_RATIO_45_60', 'DURATION_RATIO_60_300', 'DUR_30_CNT_RATE',
'DUR_60_CNT_RATE', 'DUR_90_CNT_RATE', 'DUR_120_CNT_RATE', 'DUR_180_CNT_RATE', 'DUR_BIGGER_180_CNT_RATE', 'DIS_BJ_NUM_RATE', 'TOTAL_DIS_BJ_NUM_RATE', 'CALLING_REGION_DISTRI_LEVEL', 'ACT_DAY', 'ACT_DAY_RATE', 'WEEK_DIS_BJ_NUM', 'YY_WORK_DAY_OIDD_23_NUM', 'IS_GJMY ', 'ZJ_DURATION_0_15_CNT', 'ZJ_DURATION_15_30_CNT', 'ZJ_DURATION_30_60_CNT', 'ZJ_DURATION_RATIO_0_15', 'ZJ_DURATION_RATIO_15_30', 'ZJ_DURATION_RATIO_30_60', 'H_MAX_CNT', 'H_MAX_CIRCLE', 'INNER_MONTH', 'MIX_CDSC_FLG', 'CPRD_NAME', 'AMT', 'CUST_ASSET_CNT', 'CUST_TELE_CNT', 'CUST_C_CNT', 'ALL_LL_USE', 
'MY_LL_USE', 'MY_LL_ZB', 'ALL_LL_DUR', 'MY_LL_DUR ',' MY_DUR_ZB ',' EDAD ',' GENDER ',' CUST_TYPE_GRADE_NAME ',' ISP ',' TERM_PRICE ',' SALES_CHANNEL_LVL2_NAME ',' CORP_USER_NAME ',' TOTOL_7_CNT ', 
' TJOL_DUR_7, 'TZ_7_CNT', 'TJOL_DUR_7,' TZ_7 , 'TOTOL_7_ZJ_D_CNT', 'TOTOL_7_BJ_D_DUR',
'TOTOL_7_JZGS_CNT', 'WEEK_CNT', 'WEEK_DUR', 'ZB_WS', 'COUPLE_NUMBER', 'TIME_COUPLE_NUMBER', 'ZJ_0912', 'HB_0912', 'ZJ_1417', 'HBG_1417', 'ZJ_1417', 'HBG_1417', 'CH ',' IS_HARASS '] 
12345678

Vista de datos

Fila / columna de la tabla de datos

data_pos.shape 
1

Inserte la descripción de la imagen aquí

Se puede ver que los datos de la muestra positiva son solo 3436, y hay muchas muestras negativas, que son datos de muestra extremadamente desequilibrados.

Preprocesamiento de datos

Eliminación de campos sin sentido

data_pos_1 = data_pos.drop ([ 
    'BIL_ACCS_NBR', 
    'ASSET_ROW_ID', 
    'CCUST_ROW_ID', 
    'LATN_ID', 
    'CPRD_NAME', 
    'ISP', 
    'edad', 
    'CUST_TYPE_GRADE_NAME', 
    'ETL_DT', 
    'WEEK_DIS_BJ_NUM', 
    'TOTOL_7_ZJ_D_CNT' , 
    'TOTOL_7_JZGS_CNT', 
    'INNER_MONTH' 
], eje = 1) 
123456789101112131415

Tamaño de muestra positivo y negativo

data_pos.IS_HARASS.value_counts () 
1

Inserte la descripción de la imagen aquí

TERM_PRICE para el procesamiento de agrupaciones

data_pos_1 ['TERM_PRICE'] = data_pos_1 ['TERM_PRICE']. apply (lambda x: np.where (x> 5000, '> 5000', 
                                                                             np.where (x> 3000, '(3000,5000]', 
                                                                                      np. donde (x> 2000, '(2000,3000]', 
                                                                                               np . donde (x> 1000, '(1000,2000])', 
                                                                                                        np . where (x> 0, '(0,1000]', '未 识别' )))))) 
123456

Relleno y conversión de campos
Reemplazar valores nulos de variables categóricas y categorías de muy pequeña escala

data_pos_1.TERM_PRICE.value_counts () 
data_pos_1.MIX_CDSC_FLG.value_counts () 
data_pos_1.CORP_USER_NAME.value_counts () 
data_pos_1.SALES_CHANNEL_LVL2_NAME.value_counts () 
1234
#Proceso TERM_PRICE, MIX_CDSC_FLG, CORP_USER_NAME, SALES_CHANNEL_LVL2_NAME 
def CHANGE_SALES_CHANNEL_LVL2_NAME (datos): 
    si hay datos en ['canales sociales', 'canales físicos', 'canales electrónicos', 'canales de venta directa']: 
        devolver datos 
    else: 
        return'unidentified ' 
        
data_pos_1 ['SALES_CHANNEL_LVL2_NAME'] = data_pos_1.SALES_CHANNEL_LVL2_NAME.apply (CHANGE_SALES_CHANNEL_LVL2_NAME) 
12345678

Procesamiento de valor faltante

## 缺失 值 统计
def na_count (datos): 
    data_count = data.count () 
    na_count = len (datos) - data_count 
    na_rate = na_count / len (datos) 
    na_result = pd.concat ([data_count, na_count, na_rate], eje = 1) 
    devuelve na_result 

na_count = na_count (data_pos_1) 
na_count 
12345678910

Inserte la descripción de la imagen aquí
Campo dividido Los
campos se dividen según la categoría continua y

def category_continuous_resolution (datos, variable_category): 
    para clave en la lista (data.columns): 
        si clave no en variable_category: 
            variable_continuous.append (key) 
        else: 
            continue 
    return variable_continuous 


# 字段 按照 类型 拆分
variable_category = ['MIX_CDSC_FLG', 'GENDER ',' TERM_PRICE ',' SALES_CHANNEL_LVL2_NAME ',' CORP_USER_NAME '] 
variable_continuous = [] 

variable_continuous = category_continuous_resolution (data_pos_1, variable_category) 
1234567891011121314

Conversión de tipo de campo

def feture_type_change (datos, variable_category): 
    '' '
    字段 类型 转化
    ' '' 
    para col_key en la lista (data.columns): 
        if col_key en variable_category: 
            datos [col_key] = datos [col_key] .astype (eval ('objeto') , copy = False) 
        else: 
            data [col_key] = data [col_key] .astype (eval ('float'), copy = False) 
    return data 

data_pos_2 = feture_type_change (data_pos_1, variable_category) 
123456789101112

Relleno de valor faltante

def na_fill (data, col_name_1, col_name_2): 
    '' '
    缺失 值 填充
    ' '' 
    para col_key en la lista (data.columns): 
        if col_key en col_name_1: 
            data [col_key] = data [col_key] .fillna (value = '未识别 ') 
        elif col_key en col_name_2: 
            data [col_key] = data [col_key] .fillna (data [col_key] .mean ()) 
        else: 
            data [col_key] = data [col_key] .fillna (valor = 0) 
    return data 

#缺失 值 填充
col_name_1 = variable_category 
col_name_2 = [] 
data_pos_3 = na_fill (data_pos_2, col_name_1, col_name_2) 
1234567891011121314151617

Procesamiento de variables categóricas one_hot

## one_hot 
def data_deliver (data, variable_category): 
    '' ' 
    ont_hot 衍生
    ' '' 
    para col_key en la lista (data.columns): 
        si col_key en variable_category: 
            temp_one_hot_code = pd.get_dummies (data [col_key], prefix = col_key) 
            data = pd.concat ([data, temp_one_hot_code], axis = 1) 
            del data [col_key] 
        else: 
            continuar 
    devuelve 

data_pos_4 = data_deliver (data_pos_3, variable_category) 
123456789101112131415

Ingeniería de características

Análisis de correlación

def max_corr_feture_droped (train_data, variable_continuous, k): 
    '' '
    相关 性 分析
    ' '' 
    table_col = train_data.columns 
    table_col_list = table_col.values.tolist ()           
    all_lines = len (train_data) 
    train_data_number = train_data] # [ 
    #_continuo变量 的 处理 过程 : 数据 的 标准化
    de numpy import array 
    de sklearn import preprocesamiento 
    def normalization (data, method, feature_range = (0,1)): 
        if method == 'MaxMin': 
            train_data_scale = data.apply (lambda x: ( x - np.min (x)) / (np.max (x) - np.min (x))) 
            return train_data_scale 
        if method == 'z_score':
            train_data_scale = data.apply (lambda x: (x-np.mean (x)) / (np.std (x))) 
        return train_data_scale 
    train_data_scale = normalization (train_data_number, method = scale_method) 
    # Genere la correlación entre cada variable Report 
    def data_corr_analysis (raw_data, sigmod = k): 
        corr_data = raw_data.corr () 
        for i in range (len (corr_data)): 
            for j in range (len (corr_data)): 
                if j == i: 
                    corr_data.iloc [i, j] = 0 
        x, y, corr_xishu = [], [], [] 
        for i in list (corr_data.index): 
            for j in list (corr_data.columns):         
                if abs (corr_data.loc [i, j]) > sigmod: # Conserva los atributos cuyo valor absoluto del coeficiente de correlación es mayor que el umbral 
                    x.append (i) 
                    y.append ( j)
                    corr_xishu.append (corr_data.loc [i, j]) 
        z = [[x [i], y [i], corr_xishu [i]] para i en el rango (len (x))] 
        high_corr = pd.DataFrame (z , columnas = [ 'VAR1', 'VAR2', 'CORR_XISHU']) 
        volver high_corr 
    high_corr_data = data_corr_analysis (train_data_number, SIGMOD = k) 
    def data_corr_choice (datos, train_data_scale, high_corr_data): 
        high_corr_data_1 = [] 
        target_var = pd.DataFrame (datos .loc [:, target_col]) 
        para i en rango (high_corr_data.shape [0]): 
            para j en rango (high_corr_data.shape [1] -1):  
                d1 = pd.DataFrame (train_data_scale. loc [:, high_corr_data.iloc [i, j]])
                data1 = pd.concat ([d1, data.loc [: , target_col]], axis = 1, join = 'inner') 
                corr_data = data1.corr () 
                high_corr_data_1.append (corr_data.iloc [0, -1]) # 输出 的 为 各个 变量 与目标 变量 之间 的 相关 关系
        high_corr_data_2 = np.array (high_corr_data_1) .reshape (high_corr_data.shape [0], high_corr_data.shape [1] -1) 
        high_corr_data_2 = pd.DataFrame = high_corr_data_data: columnas 1]) 
        del_var_cor = [] 
        for i in range (high_corr_data_2.shape [0]): 
            if abs (high_corr_data_2.iloc [i, 0])> = abs (high_corr_data_2.iloc [i, 1]): 
                del_var_cor.append ( high_corr_data.iloc [i, 1]) 
            else: 
                del_var_cor.append (high_corr_data.iloc [i, 0]) 
        train_data_number_2.drop (del_var_cor, axis = 1, inplace = True) # estará fuertemente correlacionado Las variables se eliminan directamente       
        return set (high_corr_data_1), set (del_var_cor), train_data_number_2
    train_data_number_2 = pd.concat ([train_data [variable_continuous], train_data [col_fuente]], eje = 1) 
    high_corr_data_1, del_var_cor, train_data_scale = data_corr_choice (train_data_number_2, train_data_scale, high_corr_data)   
    train_data2 = train_data [:] 
    train_data2.drop (conjunto (del_var_cor) , eje = 1, inplace = True) 
    train_data2 retorno, del_var_cor 


#相关性分析,去除高相关变量
scale_method = 'MaxMin' 
col_fuente = 'IS_HARASS' 
data_pos_5, del_var_cor = max_corr_feture_droped (data_pos_4, variable_continuous, k = 0,8) 
del_var_cor #Delete variable view
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768

Análisis de importancia de características

def data_sample (data, target_col, smp): 
    '' '
    数据 平衡
    ' '' 
    data_1 = data [data [target_col] == 1] .sample (frac = 1) 
    data_0 = data [data [target_col] == 0]. sample (n = len (data_1) * smp) 
    # data_1 = data_1.sample (len (data_2) * smp) 
    data = pd.concat ([data_1, data_0]). reset_index () 
    devuelve datos 
123456789
def train_test_spl (data): 
    '' ' 
    Datos de entrenamiento, segmentación de datos de prueba 
    ' '' 
    X_train, X_test, y_train, y_test = train_test_split ( 
        data [ipt_col], data [target_col], test_size = 0.2, random_state = 42) 
    return X_train, X_test , y_train, y_test 
1234567

Defina la función de análisis de importancia de la característica y recorrala para obtener la mejor proporción de muestreo

def feture_extracted (train_data, alpha): 
    '' '
    维度 重要性 判断
    ' '' 
    global ipt_col 
    ipt_col = list (train_data.columns) 
    ipt_col.remove (target_col) 
    sample_present = [1,5] # 定义 抽样 比例
    f1_score_list = [] 
    model_dict = {} 
    para i en sample_present: 
        intente: 
            train_data = data_sample (train_data, target_col, smp = i) 
        excepto ValueError: 
            break 
        X_train, X_test, y_train, y_test = train_test_spl (train_data)    
    # 开始 RF 选取 特征 
        modelo de = RandomForestClassifier () 
        modelo = model.fit (X_train, y_train)
        model_pred = model.predict (X_test ) 
        f1_score = metrics.f1_score (y_test, model_pred)
        f1_score_list.append (f1_score) 
        model_dict [i] = model 
    max_f1_index = f1_score_list.index (max (f1_score_list)) 
    print ('最优 的 抽样 比例 是 : 1:', sample_present [max_f1_index]) 
    d = dict (zip (zip) [float ('%. 3f'% i) para i en model_dict [sample_present [max_f1_index]]. feature_importances_])) 
    f = zip (d.values ​​(), d.keys ()) 
    important_df = pd.DataFrame (sorted ( f, reverso = Verdadero), columnas = ['importancia', 'nombre_feture']) 
    lista_imp = np.cumsum (importancia_df ['importancia']). tolist () 
    para i, j en enumerate (list_imp): 
        si j> = alpha:  
            break 
    print ('大于 alpha 的 特征 及 重要性 如下 : \ n', important_df.Iloc [0: i + 1,:])
    print ('其 特征 如下 :') 
    feture_selected = important_df.iloc [0: i + 1, 1] .tolist () 
    print (feture_selected) 
    return feture_selected #importance 

test, seleccione variables importantes 
data_pos_5_feture = feture_extracted (data_pos_5, alpha = 0.9) 
12345678910111213141516171819202122232425262728293031323334353637383940

Inserte la descripción de la imagen aquí

Entrenamiento de modelos

Balance de datos

data_pos_6 = data_sample (data_pos_5, target_col, smp = 3) 
1

División de muestra positiva y negativa

def model_select (data, rf_feture, target_col, test_size): 
    '' '
    正负 样本 拆分
    ' '' 
    X_train, X_test, y_train, y_test = train_test_split ( 
        data [rf_feture], data [target_col], test_size = test_size, random_state = 42 ) 
    return X_train, X_test, y_train, y_test 

# 拆分 比例 7: 3 
X_train, X_test, y_train, y_test = model_select (data_pos_6, data_pos_5_feture, target_col, test_size = 0.3) 
12345678910

Definir la función del modelo

Dos parámetros principales de RF:

  • min_samples_split: al dividir un nodo interno, se requiere el número mínimo de muestras en el nodo, el valor predeterminado es 2;
  • min_samples_leaf: establece el número mínimo de muestras en el nodo hoja, el valor predeterminado es 1. Cuando se intenta dividir un nodo, solo cuando el número de muestras en las ramas izquierda y derecha después de la división no es menor que el valor especificado por este parámetro, se considera que el nodo está dividido. En otras palabras, cuando el número de muestras en el nodo hoja es menor que el valor Cuando el parámetro especifica el valor, el nodo hoja y sus nodos hermanos se podarán. Cuando la cantidad de datos de muestra es grande, puede considerar aumentar este valor para detener el crecimiento del árbol antes de tiempo.
def model_train (x_train, y_train, model): 
    '' '
    算法 模型 , 默认 为 RF 
    ' '' 
    if model == 'RF': 
        res_model = RandomForestClassifier (min_samples_split = 50, min_samples_leaf = 50) 
        res_model = res_model.fit (x_train, y_train) 
        feature_importances = res_model.feature_importances_ [1] 
    if model == 'LR': 
        res_model = LogisticRegression () 
        res_model = res_model.fit (x_train, y_train) 
        list_feature_importances = [x para x en 
        res_model.coef_index = list (x_train.columns) 
        feature_importances = pd.DataFrame (list_feature_importances, list_index) 
    else: 
        pass
    return res_model, feature_importances 
#training 


model rf_model, feature_importances = model_train (X_train, y_train, model = 'RF') #También puede optar por utilizar LR 
123456789101112131415161718192021

Modelo de validación

def model_predict (res_model, input_data, alpha): 
    # model prediction 
    # input_data: ingresa nuevos datos sin variables de destino 
    data_proba = pd.DataFrame (res_model.predict_proba (input_data) .round (4)) 
    data_proba.columns = ['neg', ' pos '] 
    data_proba [' res '] = data_proba [' pos ']. apply (lambda x: np.where (x> = alpha, 1, 0)) #Ajustar la salida de> 0.5 como positivo a 1 
    return data_proba 

def model_evaluate (y_true, y_pred): 
    y_true = np.array (y_true) 
    y_true.shape = (len (y_true),) 
    y_pred = np.array (y_pred) 
    y_pred.shape = (len (y_pred),) 
    print (métricas. clasificación de clasificación) (y_true, y_pred)) 
1234567891011121314
data_pos_6 = data_sample (data_pos_5, target_col, smp = 50) 
X_train, X_test, y_train, y_test = model_select (data_pos_6, data_pos_5_feture, target_col, test_size = 0.5) 


Precisión = [] 
Recall = [] 
para alfa en np.arange (0, 1 , 0.1): 
    y_pred_rf = model_predict (rf_model, X_test, alpha = alpha) 
    cnf_matrix = confusion_matrix (y_test, y_pred_rf ['res']) 
    Precision.append ((cnf_matrix [1,1] / (cnf_matrix [0,1] + cnf_matrix [0,1] + cnf_matrix [1,1])). Round (4)) 
    Recall.append ((cnf_matrix [1,1] / (cnf_matrix [1,0] + cnf_matrix [1,1])). Round (4)) 


puntuación = pd .DataFrame (np.arange (0, 1, 0.1), columnas = ['score'])  
Precisión = pd.DataFrame (Precisión, columnas = ['Precision']) 
Recall = pd.DataFrame (Recall, columnas = [' Recordar'])
Precision_Recall_F1 = pd.concat ([score, Precision, Recall], eje = 1)
Precision_Recall_F1 [ 'F1'] = (2 * Precision_Recall_F1 [ 'Precision'] * Precision_Recall_F1 [ 'Recall'] / (Precision_Recall_F1 [ 'Precision'] + Precision_Recall_F1 [ 'Recall'])). Ronda (2) 
Precision_Recall_F1 
12345678910111213141516171819

Inserte la descripción de la imagen aquí

Conservación del paquete modelo

start = datetime.now () 
joblib.dump (rf_model, 'model.dmp', compress = 3) 
print ("El tiempo necesario para guardar el modelo:% s segundos"% (datetime.now () - inicio) .seconds ) 
123

El caso anterior es relativamente simple, no implica mucha limpieza y preprocesamiento de datos, incluido el algoritmo de RF, solo define dos parámetros, y no existe un proceso de optimización de parámetros, los interesados ​​pueden profundizar sobre esta base.

Recientemente, muchos amigos consultaron sobre problemas de aprendizaje de Python a través de mensajes privados. Para facilitar la comunicación, haga clic en el azul para unirse a la base de recursos de discusión y respuesta usted mismo

 

Supongo que te gusta

Origin blog.csdn.net/weixin_43881394/article/details/113047901
Recomendado
Clasificación