通過對原始數(shù)據(jù)進(jìn)行手工的特征工程,我們可以將模型的準(zhǔn)確性和性能提升到新的水平,為更精確的預(yù)測和更明智的業(yè)務(wù)決策鋪平道路, 可以以前所未有的方式優(yōu)化模型并提升業(yè)務(wù)能力。

原始數(shù)據(jù)就像一個(gè)沒有圖片的拼圖游戲——但通過特征工程,我們可以將這些碎片拼在一起,雖然擁有大量數(shù)據(jù)確實(shí)是尋求建立機(jī)器學(xué)習(xí)模型的金融機(jī)構(gòu)的寶庫,但同樣重要的是要承認(rèn)并非所有數(shù)據(jù)都提供信息。并且手工特征是人工設(shè)計(jì)出來,每一步操作能夠說出理由,也帶來了可解釋性。
特征工程不僅僅是選擇最好的特征。它還涉及減少數(shù)據(jù)中的噪音和冗余,以提高模型的泛化能力。這是至關(guān)重要的,因?yàn)槟P托枰诳床灰姷臄?shù)據(jù)上表現(xiàn)良好才能真正有用。

數(shù)據(jù)集描述
本文中描述的數(shù)據(jù)集經(jīng)過匿名處理和屏蔽,以維護(hù)客戶數(shù)據(jù)的機(jī)密性。特征可分類如下:
D_* = 拖欠變量
  S_* = 支出變量
  P_* = 支付變量
  B_* = 平衡變量
  R_* = 風(fēng)險(xiǎn)變量
總共有 100 個(gè)整數(shù)特征和 100 個(gè)浮點(diǎn)特征代表過去 12 個(gè)月客戶的狀態(tài)。該數(shù)據(jù)集包含有關(guān)客戶報(bào)表的信息,從 1 到 13 不等??蛻舻拿繌埿庞每▓?bào)表之間可能有 30 到 180 天的間隔(即客戶的信用卡報(bào)表可能缺失)。每個(gè)客戶都由一個(gè)客戶 ID 表示。customer_ID=0的客戶前5條的樣本數(shù)據(jù)如下所示:

在 700 萬個(gè) customer_ID 中,98% 的標(biāo)簽為“0”(好客戶,無默認(rèn)),2% 的標(biāo)簽為“1”(壞客戶,默認(rèn))。
數(shù)據(jù)集很大,所以我們使用cudf來加速處理,如果你沒有安裝cudf,那么使用pandas也是一樣的
# LOAD LIBRARIES
 import pandas as pd, numpy as np # CPU libraries
 import cudf # GPU libraries
 import matplotlib.pyplot as plt, gc, os
 
 df = cudf.read_parquet('./data.parquet')
特征生成方法
有數(shù)百種想法可用于生成特征;但是我們還確保這些特征有助于提高模型的性能,下圖顯示了特征工程中使用的一些基本方法:

聚合特征
聚合是理解復(fù)雜數(shù)據(jù)的秘訣。通過計(jì)算分類分組變量(如 customer_ID (C_ID) 或產(chǎn)品類別)的匯總統(tǒng)計(jì)數(shù)據(jù)或數(shù)值變量的聚合,我們可以發(fā)現(xiàn)一些不可見的模式和趨勢。借助均值、最大值、最小值、標(biāo)準(zhǔn)差和中值等匯總統(tǒng)計(jì)數(shù)據(jù),我們可以構(gòu)建更準(zhǔn)確的預(yù)測模型,并從客戶數(shù)據(jù)、交易數(shù)據(jù)或任何其他數(shù)值數(shù)據(jù)中提取有意義的見解。
可以計(jì)算每個(gè)客戶的這些統(tǒng)計(jì)屬性
cat_features = ["B_1","B_2","D_1","D_2","D_10","P_21","D_126","D_3","D_42","R_66","R_68"]
 num_features = [col for col in all_cols if col not in cat_features] #all features accept cateforical features.
 test_num_agg = df.groupby("customer_ID")[num_features].agg(['mean', 'std', 'min', 'max', 'last','median']) #grouping by customerID
 test_num_agg.columns = ['_'.join(x) for x in test_num_agg.columns]
均值:一個(gè)數(shù)值變量的平均值,可以給出數(shù)據(jù)集中趨勢的一般意義。平均值可以捕獲:
 客戶擁有的平均銀行余額。
- 平均客戶支出。
 - 兩個(gè)信用報(bào)表之間的平均時(shí)間(信用付款之間的時(shí)間)。
 - 借錢的平均風(fēng)險(xiǎn)。
 
標(biāo)準(zhǔn)偏差 (Std):衡量數(shù)據(jù)圍繞均值的分布情況,可以深入了解數(shù)據(jù)的變異程度。余額的高度可變性表明客戶有消費(fèi)。
最小值和最大值可以捕獲客戶的財(cái)富,也可以捕獲有關(guān)客戶支出和風(fēng)險(xiǎn)的信息。
中位數(shù):當(dāng)數(shù)據(jù)高度傾斜時(shí),使用平均值并不是一個(gè)更好的主意,因此可以使用中值(可以使用數(shù)值的中間值。
最新值可能是最重要的特征,因?yàn)樗鼈儼嘘P(guān)發(fā)布給客戶的最新已知信用聲明的信息,也就表明目前客戶賬戶的最新狀態(tài)。
獨(dú)熱編碼
對分類變量使用上述統(tǒng)計(jì)屬性是不明智的,因?yàn)橛?jì)算最小值、最大值或標(biāo)準(zhǔn)偏差并不能給我們?nèi)魏斡杏玫男畔?。那么我們?yīng)該怎么做呢?可以使用像count這樣的特征,和唯一的數(shù)量來計(jì)算特征,最新的值也可以使用
cat_features = ["B_1","B_2","D_1","D_2","D_10","P_21","D_126","D_3","D_42","R_66","R_68"]
 test_cat_agg = df.groupby("customer_ID")[cat_features].agg(['count', 'last', 'nunique'])
 test_cat_agg.columns = ['_'.join(x) for x in test_cat_agg.columns]
但是這些信息不會捕獲客戶是否被歸類到特定的類別中。所以我們通過對變量進(jìn)行獨(dú)熱編碼,然后對變量(例如均值、總和和最后)進(jìn)行聚合來實(shí)現(xiàn)。
平均值將捕獲客戶屬于該類別的總次數(shù)/銀行對帳單總數(shù)的比率??偤蛯⒅皇强蛻魧儆谠擃悇e的總次數(shù)。
from cuml.preprocessing import OneHotEncoder
 df_categorical = df_last[cat_features].astype(object)
 ohe = OneHotEncoder(drop='first', sparse=False, dtype=np.float32, handle_unknown='ignore')
 ohe.fit(df_categorical)with open("ohe.pickle", 'wb') as f:
    pickle.dump(ohe, f) #save the encoder so that it can be used for test data as well df_categorical = pd.DataFrame(ohe.transform(df_categorical).astype(np.float16),index=df_categorical.index).rename(columns=str)
 df_categorical['customer_ID']=df['customer_ID']
 df_categorical.groupby('customer_ID').agg(['mean', 'sum', 'last'])

基于排名的特征
在預(yù)測客戶行為方面,基于排名的特征是非常重要的。通過根據(jù)收入或支出等特定屬性對客戶進(jìn)行排名,我們可以深入了解他們的財(cái)務(wù)習(xí)慣并更好地管理風(fēng)險(xiǎn)。
使用 cudf 的 rank 函數(shù),我們可以輕松計(jì)算這些特征并使用它們來為預(yù)測提供信息。例如,可以根據(jù)客戶的消費(fèi)模式、債務(wù)收入比或信用評分對客戶進(jìn)行排名。然后這些特征可用于預(yù)測違約或識別有可能拖欠付款的客戶。
基于排名的特征還可用于識別高價(jià)值客戶、目標(biāo)營銷工作和優(yōu)化貸款優(yōu)惠。例如,可以根據(jù)客戶接受貸款提議的可能性對客戶進(jìn)行排名,然后將排名最高的客戶作為目標(biāo)。
df[feat+'_rank']=df[feat].rank(pct=True, method='min')
PCT用于是否做百分位排名??蛻舻呐琶部梢曰诜诸愄卣鱽碛?jì)算。
df[feat+'_rank']=df.groupby([cat_feat]).rank(pct=True, method='min')
特征組合

特征組合的一種流行方法是線性或非線性組合。這包括采用兩個(gè)或多個(gè)現(xiàn)有特征,將它們組合在一起創(chuàng)建一個(gè)新的復(fù)合特征。然后使用這個(gè)復(fù)合特征來識別單獨(dú)查看單個(gè)特征時(shí)可能不可見的模式、趨勢和相關(guān)性。
例如,假設(shè)我們正在分析客戶消費(fèi)習(xí)慣的數(shù)據(jù)集??梢詮膫€(gè)人特征開始,比如年齡、收入和地點(diǎn)。但是通過以線性或非線性的方式組合這些特性,可以創(chuàng)建新的復(fù)合特性,使我們能夠更多地了解客戶??梢越Y(jié)合收入和位置來創(chuàng)建一個(gè)復(fù)合特征,該特征告訴我們某一地區(qū)客戶的平均支出。
但是并不是所有的特征組合都有用。關(guān)鍵是要確定哪些組合與試圖解決的問題最相關(guān),這需要對數(shù)據(jù)和問題領(lǐng)域有深刻的理解,并仔細(xì)分析創(chuàng)建的復(fù)合特征和試圖預(yù)測的目標(biāo)變量之間的相關(guān)性。
下圖展示了一個(gè)組合特征并將信息用于模型的過程。作為篩選條件,這里只選擇那些與目標(biāo)相關(guān)性大于最大值 0.9 的特征。
features=[col for col in train.columns if col not in ['customer_ID',target]+cat_features]
 for feat1 in features:
  for feat2 in features:
    th=max(np.corr(feat1,Y)[0],np.corr(feat1,Y)[0]) #calculate threshold
    feat3=df[feat1]-df[feat2] #difference feature
    corr3=np.corr(feat3,Y)[0]
    if(corr3>max(th,0.9)): #if correlation greater than max(th,0.9) we add it as feature
      df[feat1+'_'+feat2]=feat3
基于時(shí)間/日期的特征
在數(shù)據(jù)分析方面,基于時(shí)間的特征非常重要。通過根據(jù)時(shí)間屬性(例如月份或星期幾)對數(shù)據(jù)進(jìn)行分組,可以創(chuàng)建強(qiáng)大的特征。這些特征的范圍可以從簡單的平均值(如收入和支出)到更復(fù)雜的屬性(如信用評分隨時(shí)間的變化)。
借助基于時(shí)間的特征,還可以識別在孤立地查看數(shù)據(jù)時(shí)可能看不到的模式和趨勢。下圖演示了如何使用基于時(shí)間的特征來創(chuàng)建有用的復(fù)合屬性。
首先,計(jì)算一個(gè)月內(nèi)的值的平均值(可以使用該月的某天或該月的某周等),將獲得的DF與原始數(shù)據(jù)合并,并取各個(gè)特征之間的差。
features=[col for col in train.columns if col not in ['customer_ID',target]+cat_features]
 month_Agg=df.groupby([month])[features].agg('mean')#grouping based on month feature
 month_Agg.columns = ['_month_'.join(x) for x in month_Agg.columns]
 month_Agg.reset_index(inplace=True)
 df=df.groupby(month_Agg,notallow='month')
 for feat in features: #create composite features b taking difference
  df[feat+'_'+feat+'_month_mean']=df[feat]-df[feat+'_month_mean']

還可以通過使用時(shí)間作為分組變量來創(chuàng)建基于排名的特征,如下所示
features=[col for col in train.columns if col not in ['customer_ID',target]+cat_features]
 month_Agg=df.groupby([month])[features].rank(pct=True) #grouping based on month feature
 month_Agg.columns = ['_month_'.join(x) for x in month_Agg.columns]
 month_Agg.reset_index(inplace=True)
 df=pd.concat([df,month_Agg],axis=1) #concat to original dataframe
滯后特征
滯后特征是有效預(yù)測金融數(shù)據(jù)的重要工具。這些特征包括計(jì)算時(shí)間序列中當(dāng)前值與之前值之間的差值。通過將滯后特征納入分析,可以更好地理解數(shù)據(jù)中的模式和趨勢,并做出更準(zhǔn)確的預(yù)測。
如果滯后特征顯示客戶連續(xù)幾個(gè)月按時(shí)支付信用卡賬單,可能會預(yù)測他們將來不太可能違約。相反,如果延遲特征顯示客戶一直延遲或錯(cuò)過付款,可能會預(yù)測他們更有可能違約。
# difference function calculate the lag difference for numerical features
 #between last value and shift last value.
 def difference(groups,num_features,shift):
    data=(groups[num_features].nth(-1)-groups[num_features].nth(-1*shift)).rename(columns={f: f"{f}_diff{shift}" for f in num_features})
    return data
 #calculate diff features for last -2nd last, last -3rd last, last- 4th last
 def get_difference(data,num_features):
    print("diff features...")
    groups=data.groupby('customer_ID')
    df1=difference(groups,num_features,2).fillna(0)
    df2=difference(groups,num_features,3).fillna(0)
    df3=difference(groups,num_features,4).fillna(0)
    df1=pd.concat([df1,df2,df3],axis=1)
    df1.reset_index(inplace=True)
    df1.sort_values(by='customer_ID')
    del df2,df3
    gc.collect()
    return df1train_diff = get_difference(df, num_features)

基于滾動(dòng)窗口的特性
這些特征只是取最后3(4,5,…x)值的平均值,這取決于數(shù)據(jù),因?yàn)榛跁r(shí)間的最新值攜帶了關(guān)于客戶最新狀態(tài)的信息。
xth=3 #define the window size
 df["cumulative"]=df.groupby('customer_ID').sort_values(by=['time'],ascending=False).cumcount()
 last_info=df[df["cumulative"]<=xth]
 last_info = last_info.groupby("customer_ID")[num_features].agg(['mean', 'std', 'min', 'max', 'last','median']) #grouping by customerID
 last_info.columns = ['_'.join(x) for x in last_info.columns]
其他的特征提取方法
上面的方法已經(jīng)創(chuàng)建了足夠多的特征來構(gòu)建一個(gè)很棒的模型。但是根據(jù)數(shù)據(jù)的性質(zhì),還可以創(chuàng)建更多的特征。例如:可以創(chuàng)建像null計(jì)數(shù)這樣的特征,它可以計(jì)算客戶當(dāng)前的總null值,從而幫助捕獲基于樹的算法無法理解的特征分布。
def calc_nan(df,features):
    print("calculating nan_info...")
    df_nan = (df[features].mul(0) + 1).fillna(0) #marke non_null values as 1 and null as zero
    df_nan['customer_ID'] = df['customer_ID']
    nan_sum = df_nan.groupby("customer_ID").sum().sum(axis=1) #total unknown values for a customer
    nan_last = df_nan.groupby("customer_ID").last().sum(axis=1)#how many last values that are not known
    del df_nan
    gc.collect()
    return nan_sum,nan_last
這里可以不使用平均值,而是使用修正的平均值,如基于時(shí)間的加權(quán)平均值或 HMA(hull moving average)。
總結(jié)
在本文中介紹了一些在現(xiàn)實(shí)世界中用于預(yù)測違約風(fēng)險(xiǎn)的最常見的手工特性策略。但是總是有新的和創(chuàng)新的方法來設(shè)計(jì)特征,并且手工設(shè)置特征的方法是費(fèi)時(shí)費(fèi)力的,所以我們將在后面的文章中介紹如何實(shí)用工具進(jìn)行自動(dòng)的特征生成。