Dolandırıcılık Tespiti – Kaggle Yarışması – Bölüm 2

Görsel Kaynak: https://www.bankinfosecurity.com/

Yazımın ikinci kısmına hoşgeldiniz. Bildiğiniz üzere yazımı iki parçaya bölmüştüm. İlk bölümde yarışmanın hikayesini, yol haritamızı, R ve Python kütüphanelerini, Eksik verilerle mücadelemizi ve değişken mühendisliğine biraz giriş yaparak birkaç değişken üretmiştik. Şimdi kaldığımız yerden devam edelim. Eğer yazımın ilk bölümünü okumadıysanız, bu link ile erişebilirsiniz.

Veri setimizde diğer değişkenlerden biri de gözlemin sahip olduğu mail uzantısıydı. Buradan biz ülkeye ulaştık ve bunu yeni bir değişken yaptık. Ardından mail adresleri çok fazla vardı bazılarını birleştirdik

Company yahoo / ymail / frontier / rocketmail -> Yahoo

hotmail / outlook / live / msn -> Microsoft

icloud / mac / me -> Appe

prodigy / att / sbcglobal-> AT&T

centurylink / embarqmail -> Centurylink

aim / aol -> AOL

twc / charter -> Spectrum

gmail.com / gmail -> Google

train_temp$P_company <- NA_character_

train_temp <- train_temp %>%  
  mutate(P_company = 
           as.factor(if_else(P_emaildomain %in% c("yahoo.co.jp", "yahoo.co.uk"," yahoo.com",
                                        "yahoo.com.mx", "yahoo.de", "yahoo.es", "yahoo.fr", "ymail.com",
                                        "frontier.com", "frontiernet.net", "rocketmail.com"), "Yahoo",
                   if_else(P_emaildomain %in% c("hotmail.co.uk", "hotmail.com", "hotmail.de", "hotmail.es",
                                                "hotmail.fr", "live.com", "live.com.mx", "live.fr",
                                                "msn.com","outlook.com", "outlook.es"), "Microsoft",
                           
                           if_else(P_emaildomain %in% c("icloud.com", "mac.com", "me.com"), "Apple",
                                   
                                   if_else(P_emaildomain %in% c("att.net", "prodigy.net.mx", "sbcglobal.net"),
                                           "AT&T", 
                                           
                                           if_else(P_emaildomain %in% c("centurylink.net", "embarqmail.com",
                                                                        "q.com"), "Centurylink",
                                                   
                                                   if_else(P_emaildomain %in% c("aim.com", "aol.com"), "AOL",
                                                           
                                                           if_else(P_emaildomain %in% c("charter.net","twc.com"),
                                                                   "Spectrum", 
                                                                   if_else(P_emaildomain %in% c("gmail.com","gmail"),
                                                                   "Google", "Other"))))))))))

Bu mail adresleri ile kart numaralarını kullanarak farklı değişkenler de ürettik. Her kişinin (tüm kredi kartı numaralarını birleştirdik her tekil numarayı 1 kişi olarak düşündük) kaç farklı mail adresi kullanmış gibi değişkenler yaptık ve hedef değişkendeki dağılımına bakarak bir değer belirledik ve dolandırıcı olma olasılığını yükselttik. 

Değişkenlerimiz arasında C1…C14 değişkenleri vardı. Korelasyon bazlı birbirleriyle birleştirdik-sildik ya da toplamı, ortalaması, eğiklik, basıklık gibi değişkenler ürettik, aynı işlemi D1…D15 değişkeni için de yaptık.

Değişkenlerimizden diğer iki tanesi adres olarak düşündüğümüz addr1 ve addr2 değişkeniydi. Bunlar bizim için önemliydi. Biz bu numaraları ZIP numarası olarak birleştirebilir miyiz diye düşündüğümüzde ise hemen hemen doğru olduğunu gördük. Mail adreslerinden aldığımız ülke ismi ile bu ZIP kodunu internetten incelediğimizde aynı ülkeyi veriyordu. Bu doğrulamayı görünce zipkodu değişkenini oluşturduk. Bu numaralardan ve diğer farklı değişkenlerden acaba IP numarasını bulabilir miyiz düşündük. Muhtemelen yaklaştık ama doğrulayamadığımızdan dolayı bu değişkeni kullanamadık. Farklı internet sitelerine baktığımızda aynı numaraya farklı ülke ve konum gösterildiği için bu değişkeni kullanmadık. Bu zipkodu ile ulaştığımız ülke ismi kendi bulduğumuz ülke ismi eşleşmiyorsa muhtemel dolandırıcı ya da henüz değil gibi değer ürettik.

 # Zip kodundan belirlenen ülke ile mail adresinden eşleştirilen ülkeler farklıysa garip bir durum var. Farklı ise 1, aynıysa 0.

train  %<>% mutate(ZipCountry = paste0(addr1, addr2)) %>% 
    group_by(cardnumber, ZipCountry) %>% 
    mutate(UniqCountry = if_else(row_number() == 1, 1 ,0)) %>% 
    ungroup() %>% 
    group_by(cardnumber) %>% 
    mutate(UniqCountry = cumsum(UniqCountry)) %>% 
    ungroup() %>% 
    mutate(CountryProb = UniqCountry / length(unique(ZipCountry)),
           UniqCountryCat = if_else(UniqCountry < 31, "Not Yet", "Possible Fraud"))

fr3 <- train %>% filter(isFraud == 1) %>% pull(cardnumber) %>% unique()

fr_df3 <- train %>% filter(cardnumber %in% fr3)
nonfr_df3 <- train %>% filter(!cardnumber %in% fr3)

l1 <- fr_df3 %>% group_by(cardnumber) %>% 
    summarise(l = length(unique(ZipCountry))) %>% arrange(-l)  %>% pull(l) %>% unique %>% sort
l2 <- nonfr_df3 %>% group_by(cardnumber) %>% 
    summarise(l = length(unique(ZipCountry))) %>% arrange(-l)  %>% pull(l) %>% unique %>% sort

Bu değişkenler ile dolandırıcı kişilerin davranışlarını modellemeye çalıştık. Oluşturduğumuz bazı değişkenler..

# device_name ismini parçalara bölerek versiyon, browser gibi değerlere ulaşalım
def id_split(dataframe):
    dataframe['device_name'] = dataframe['DeviceInfo'].str.split('/', expand=True)[0]
    dataframe['device_version'] = dataframe['DeviceInfo'].str.split('/', expand=True)[1]

    dataframe['OS_id_30'] = dataframe['id_30'].str.split(' ', expand=True)[0]
    dataframe['version_id_30'] = dataframe['id_30'].str.split(' ', expand=True)[1]

    dataframe['browser_id_31'] = dataframe['id_31'].str.split(' ', expand=True)[0]
    dataframe['version_id_31'] = dataframe['id_31'].str.split(' ', expand=True)[1]

# Ekran boyutlarını alalım

    dataframe['screen_width'] = dataframe['id_33'].str.split('x', expand=True)[0]
    dataframe['screen_height'] = dataframe['id_33'].str.split('x', expand=True)[1]
# Telefon markalarını gruplayalım

    dataframe.loc[dataframe['device_name'].str.contains('SM', na=False), 'device_name'] = 'Samsung'
    dataframe.loc[dataframe['device_name'].str.contains('SAMSUNG', na=False), 'device_name'] = 'Samsung'
    dataframe.loc[dataframe['device_name'].str.contains('GT-', na=False), 'device_name'] = 'Samsung'
    dataframe.loc[dataframe['device_name'].str.contains('Moto G', na=False), 'device_name'] = 'Motorola'
    dataframe.loc[dataframe['device_name'].str.contains('Moto', na=False), 'device_name'] = 'Motorola'
    dataframe.loc[dataframe['device_name'].str.contains('moto', na=False), 'device_name'] = 'Motorola'
    dataframe.loc[dataframe['device_name'].str.contains('LG-', na=False), 'device_name'] = 'LG'
    dataframe.loc[dataframe['device_name'].str.contains('rv:', na=False), 'device_name'] = 'RV'
    dataframe.loc[dataframe['device_name'].str.contains('HUAWEI', na=False), 'device_name'] = 'Huawei'
    dataframe.loc[dataframe['device_name'].str.contains('ALE-', na=False), 'device_name'] = 'Huawei'
    dataframe.loc[dataframe['device_name'].str.contains('-L', na=False), 'device_name'] = 'Huawei'
    dataframe.loc[dataframe['device_name'].str.contains('Blade', na=False), 'device_name'] = 'ZTE'
    dataframe.loc[dataframe['device_name'].str.contains('BLADE', na=False), 'device_name'] = 'ZTE'
    dataframe.loc[dataframe['device_name'].str.contains('Linux', na=False), 'device_name'] = 'Linux'
    dataframe.loc[dataframe['device_name'].str.contains('XT', na=False), 'device_name'] = 'Sony'
    dataframe.loc[dataframe['device_name'].str.contains('HTC', na=False), 'device_name'] = 'HTC'
    dataframe.loc[dataframe['device_name'].str.contains('ASUS', na=False), 'device_name'] = 'Asus'
# Bu isimler dışındaysa “diğer” ataması yapalım
  dataframe.loc[dataframe.device_name.isin(dataframe.device_name.value_counts()[dataframe.device_name.value_counts() < 200].index), 'device_name'] = "Others"
    dataframe['had_id'] = 1
    gc.collect()
    return dataframe

train_1 = id_split(train)
# Kart numaralarına göre gruplayıp keşifçi veri analizleri sırasında gördüğümüz ve önemli olduğuna inandığımız değişkenleri üretelim
train_1['TransactionAmt_to_mean_card1'] = train_1['TransactionAmt'] / train_1.groupby(['card1'])['TransactionAmt'].transform('mean')
train_1['TransactionAmt_to_mean_card4'] = train_1['TransactionAmt'] / train_1.groupby(['card4'])['TransactionAmt'].transform('mean')
train_1['TransactionAmt_to_std_card1'] = train_1['TransactionAmt'] / train_1.groupby(['card1'])['TransactionAmt'].transform('std')
train_1['TransactionAmt_to_std_card4'] = train_1['TransactionAmt'] / train_1.groupby(['card4'])['TransactionAmt'].transform('std')
train_1['TransactionAmt_to_min_card4'] = train_1['TransactionAmt'] / train_1.groupby(['card4'])['TransactionAmt'].transform('min')
train_1['TransactionAmt_to_max_card1'] = train_1['TransactionAmt'] / train_1.groupby(['card1'])['TransactionAmt'].transform('max')

train_1['id_02_to_mean_card1'] = train_1['id_02'] / train_1.groupby(['card1'])['id_02'].transform('mean')
train_1['id_02_to_mean_card4'] = train_1['id_02'] / train_1.groupby(['card4'])['id_02'].transform('mean')
train_1['id_02_to_std_card1'] = train_1['id_02'] / train_1.groupby(['card1'])['id_02'].transform('std')
train_1['id_02_to_std_card4'] = train_1['id_02'] / train_1.groupby(['card4'])['id_02'].transform('std')

train_1['D15_to_mean_card1'] = train_1['D15'] / train_1.groupby(['card1'])['D15'].transform('mean')
train_1['D15_to_mean_card4'] = train_1['D15'] / train_1.groupby(['card4'])['D15'].transform('mean')
train_1['D15_to_std_card1'] = train_1['D15'] / train_1.groupby(['card1'])['D15'].transform('std')
train_1['D15_to_std_card4'] = train_1['D15'] / train_1.groupby(['card4'])['D15'].transform('std')

train_1['D15_to_mean_addr1'] = train_1['D15'] / train_1.groupby(['addr1'])['D15'].transform('mean')
train_1['D15_to_mean_card4'] = train_1['D15'] / train_1.groupby(['card4'])['D15'].transform('mean')
train_1['D15_to_std_addr1'] = train_1['D15'] / train_1.groupby(['addr1'])['D15'].transform('std')
train_1['D15_to_std_card4'] = train_1['D15'] / train_1.groupby(['card4'])['D15'].transform('std')

train_1['TransactionAmt_Log'] = np.log(train_1['TransactionAmt'])
train_1['TransactionAmt_decimal'] = ((train_1['TransactionAmt'] - train_1['TransactionAmt'].astype(int)) * 1000).astype(int)
train_1[['P_emaildomain_1', 'P_emaildomain_2', 'P_emaildomain_3']] = train_1['P_emaildomain'].str.split('.', expand=True)
train_1['P_isproton']=(train_1['P_emaildomain']=='protonmail.com')
a = np.zeros(train_1.shape[0])
train_1["lastest_browser"] = a
del a
# “id_31” değişkenini kullanarak browser versiyonlarını etiketleyelim (dış kaynak veri)
def setbrowser(train_1):
    train_1.loc[train_1["id_31"]=="samsung browser 7.0",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="opera 53.0",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="mobile safari 10.0",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="google search application 49.0",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="firefox 60.0",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="edge 17.0",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="chrome 69.0",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="chrome 67.0 for android",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="chrome 63.0 for android",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="chrome 63.0 for ios",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="chrome 64.0",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="chrome 64.0 for android",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="chrome 64.0 for ios",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="chrome 65.0",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="chrome 65.0 for android",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="chrome 65.0 for ios",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="chrome 66.0",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="chrome 66.0 for android",'lastest_browser']=1
    train_1.loc[train_1["id_31"]=="chrome 66.0 for ios",'lastest_browser']=1
    return train_1

train_1 = setbrowser(train_1)
train_1['cardnumber_mean_last'] = train_1['TransactionAmt'] - train_1.groupby('cardnumber')['TransactionAmt'].transform(lambda x: x.rolling(10, 1).mean())
train_1['cardnumber_min_last'] = train_1.groupby('cardnumber')['TransactionAmt'].transform(lambda x: x.rolling(10, 1).min())
train_1['cardnumber_max_last'] = train_1.groupby('cardnumber')['TransactionAmt'].transform(lambda x: x.rolling(10, 1).max())
train_1['cardnumber_std_last'] = train_1['cardnumber_min_last'] / train_1.groupby('cardnumber')['TransactionAmt'].transform(lambda x: x.rolling(10, 1).std())
train_1['cardnumber_count_last'] = train_1.groupby('cardnumber')['TransactionAmt'].transform(lambda x: x.rolling(30, 1).count())

# Bazı değişkenler için eksik verileri sıfır (0) ile dolduralım

train_1['cardnumber_mean_last'].fillna(0, inplace=True, )
train_1['cardnumber_min_last'].fillna(0, inplace=True, )
train_1['cardnumber_max_last'].fillna(0, inplace=True, )
train_1['cardnumber_std_last'].fillna(0, inplace=True, )
train_1['cardnumber_count_last'].fillna(0, inplace=True, )

train_1['addr1_1'] = train_1['addr1'].fillna(0)
train_1['addr2_2'] = train_1['addr2'].fillna(0)

# Adres verilerini birleştirip kategorik hale döndürelim

train_1['diff_adrr'] = train_1.addr1_1 - train_1.addr2_2
train_1['diff_adrr_plus'] = train_1.addr1_1 + train_1.addr2_2
# Değişken dönüşümü

train_1['first_value_addr1'] = train_1['addr1_1'].astype(str).str[0:1].astype(float)
train_1['two_value_addr1'] = train_1['addr1_1'].astype(str).str[0:2].astype(float)

train_1['Trans_min_mean'] = train_1['TransactionAmt'] - train_1['TransactionAmt'].mean()
train_1['Trans_min_max'] = train_1['TransactionAmt'] - train_1['TransactionAmt'].max()

train_1['Trans_std_min'] = train_1['TransactionAmt'] - train_1['TransactionAmt'].std()
train_1['Trans_min_max_2'] = train_1['Trans_min_mean'] / train_1['TransactionAmt']

train_1['Trans_min_max'] = train_1['Trans_min_mean'] / train_1['TransactionAmt'].std()
train_1['Trans_min_std'] = train_1['Trans_min_mean'] / train_1['TransactionAmt'].std()

train_1['Trans_min_mean'] = train_1['TransactionAmt'] - train_1['TransactionAmt'].mean()
train_1['Trans_min_std'] = train_1['Trans_min_mean'] / train_1['TransactionAmt'].std()

# Kar numarısı ile gruplandırıp farklı değişkenler üretelim. Tüm değişkenleri keşifçi veri analizinde yorumlarımıza göre oluşturduk.

train_1['TransactionAmt_to_mean_card_id'] = train_1['TransactionAmt'] - train_1.groupby(['cardnumber'])['TransactionAmt'].transform('mean')
train_1['TransactionAmt_to_std_card_id'] = train_1['TransactionAmt_to_mean_card_id'] / train_1.groupby(['cardnumber'])['TransactionAmt'].transform('std')

train_1['TransactionAmt_to_max_card_id'] = train_1['TransactionAmt'] - train_1.groupby(['cardnumber'])['TransactionAmt'].transform('max')
train_1['TransactionAmt_to_min_card_id'] = train_1['TransactionAmt'] - train_1.groupby(['cardnumber'])['TransactionAmt'].transform('min')

# Karar bazlı bir model kuracağımız için gözümüzden kaçan olası önemli değişkenler için for döngüsü içinde yeni değişkenler üretelim. Üretilen değişkenler ise ortalama, min, max ve adet.

columns=['TransactionAmt','TransactionFreqDaily', 'TransactionFreqHour', 'ProductAmtRatio', 'Limit']
obj_cols=['P_emaildomain','ProductCD','cardnumber', 'month','day','week','minute','hour', 'ZipCountry','UniqCountry']

for col in columns:
    for feat in obj_cols:
                train_1[f'{col}_mean_group_{feat}']=train_1[col]/train_1.groupby(feat)[col].transform('mean')
        train_1[f'{col}_max_group_{feat}']=train_1[col]/train_1.groupby(feat)[col].transform('max')
        train_1[f'{col}_min_group_{feat}']=train_1[col]/train_1.groupby(feat)[col].transform('min')
        train_1[f'{col}_skew_group_{feat}']=train_1[col]/train_1.groupby(feat)[col].transform('skew')
        train_1[f'{col}_count_group_{feat}']=train_1[col]/train_1.groupby(feat)[col].transform('count')

# Tekrar yüzdesel olarak olarak kayıp verisi %30 üzerinde olanları çıkartalım. Bu işlemi daha önce yapmıştık fakat üretilen yeni değişkenlerde yüksek oranda kayıp değerler vardı.
many_null_cols = [col for col in train_1.columns if train_1[col].isnull().sum() / train_1.shape[0] > 0.30]
# Test verisi ile eğitim verisinin aynı kolonlardan oluşması gerekiyor. Gözümüzden kaçmasın diye kontrol edildi fakat test verisi ile aynı isimleri olan değişkenleri seçiyoruz.
train_1 = train_1[train_1.columns.intersection(test_columns_names)]

# Tüm değişkenleri aldığımızda 450’ in üzerinde değişken oldu. Verilerin boyutu yaklaşık 5 GB’a ulaştığı için tekrar hacim küçültmesi yapıyoruz. Böylelikle verinin boyutu 3 GB’a düştü.

def reduce_mem_usage(df, verbose=True):
    numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
    start_mem = df.memory_usage().sum() / 1024**2    
    for col in df.columns:
        col_type = df[col].dtypes
        if col_type in numerics:
            c_min = df[col].min()
            c_max = df[col].max()
            if str(col_type)[:3] == 'int':
                if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
                    df[col] = df[col].astype(np.int8)
                elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
                    df[col] = df[col].astype(np.int16)
                elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
                    df[col] = df[col].astype(np.int32)
                elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
                    df[col] = df[col].astype(np.int64)  
            else:
                if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
                    df[col] = df[col].astype(np.float16)
                elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
                    df[col] = df[col].astype(np.float32)
                else:
                    df[col] = df[col].astype(np.float64)    
    end_mem = df.memory_usage().sum() / 1024**2
    if verbose: print('Mem. usage decreased to {:5.2f} Mb ({:.1f}% reduction)'.format(end_mem, 100 * (start_mem - end_mem) / start_mem))
    return df
train = reduce_mem_usage(train_1)
del train_1

tekil_parametre_sayisi = []

# Bir değişkenin için 61’den fazla tekil değişken varsa bu numeriktir, altındaysa kategoriktir. Bu değeri keşifçi veri analizi sırasında belirledik. 

for c in train.columns:
    tekil_parametre_sayisi.append(train[c].nunique())
numerical  = [c for c in train.columns if train[c].nunique() > 61]
categorical  = [c for c in train.columns if train[c].nunique() <= 61]
# Programda bazı değişkenler numerik olarak görünse de aslında kategorik. Bunun için bu değerleri ayrı olarak listeden çıkartıyoruz.

from_numeric_to_categoric = ('cardnumber', 'device_version', "id_30", "id_31", "id_33",  'id_02', 'id_05', 'id_06', 'id_07', 'id_08', 'id_10', 'id_11', 'id_17', 'id_19', 'id_30','id_31', 'id_33')

from_categoric_to_numeric = ('Limit_min_group_P_emaildomain', 'Limit_max_group_ProductCD', 'Limit_min_group_ProductCD', 'Limit_mean_group_cardnumber', 'Limit_max_group_cardnumber', 'Limit_min_group_cardnumber', 'Limit_max_group_month', 'Limit_min_group_month', 'Limit_max_group_day', 'Limit_min_group_day', 'Limit_max_group_week', 'Limit_min_group_week', 'Limit_max_group_minute', 'Limit_min_group_minute', 'Limit_max_group_hour', 'Limit_min_group_hour','Limit_max_group_UniqCountry')

# Numerik olarak belirlenen kategorikler değişkenleri, numerik listesinden çıkartalım

numerical = [e for e in numerical if e not in from_numeric_to_categoric]

categorical = [e for e in categorical if e not in from_categoric_to_numeric]

for col in from_numeric_to_categoric:
    categorical.append(col)
    
for col1 in from_categoric_to_numeric:
    numerical.append(col1)
    
categorical.remove("isFraud")

# Kategorik değişkenlerde mevcut olan kayıp verileri “mis” (missing) olarak dolduruyoruz. Bunun sebebini daha önce açıklamıştım.

for col in train[categorical].columns:
    train[col] = train[col].fillna('mis')
    test[col]  = test[col].fillna('mis')
    train[col] = train[col].astype(str)
    test[col] = test[col].astype(str)
    le = LabelEncoder()
    le.fit(list(train[col])+list(test[col]))
    train[col] = le.transform(train[col])
    test[col]  = le.transform(test[col])
    train[col] = train[col].astype('category')
    test[col] = test[col].astype('category')

# Kategorik değişkenlerimize bakalım

kategorik = ['ProductCD', 'card4','card6', 'P_emaildomain', 'M2', 'M3', 'M4', 'M5', 'M6', 'V12', 'V13', 'V14', 'V15', 'V18', 'V19', 'V20', 'V21', 'V23', 'V24', 'V25', 'V26', 'V27', 'V28', 'V30', 'V32', 'V33', 'V35', 'V37', 'V38', 'V39', 'V41', 'V42', 'V43', 'V44', 'V45', 'V46', 'V47', 'V49', 'V50', 'V52', 'V53', 'V54', 'V55', 'V56', 'V57', 'V59', 'V61', 'V62', 'V64', 'V65', 'V66', 'V67', 'V68', 'V70', 'V71', 'V74', 'V75', 'V76', 'V77', 'V78', 'V79', 'V81', 'V82', 'V83', 'V84', 'V85', 'V86', 'V87', 'V88', 'V89', 'V91', 'V93', 'V94', 'V98', 'V100', 'V104', 'V106', 'V107', 'V108', 'V109', 'V110', 'V111', 'V112', 'V113', 'V114', 'V115', 'V116', 'V117', 'V118', 'V119', 'V120', 'V121', 'V122', 'V123', 'V124', 'V125', 'V281', 'V282', 'V286', 'V287', 'V288', 'V289', 'V290', 'V297', 'V299', 'V300', 'V301', 'V303', 'V304','V305', 'id_03', 'id_04', 'id_09', 'id_12', 'id_13', 'id_14', 'id_15', 'id_18', 'id_22', 'id_23', 'id_24', 'id_28', 'id_29', 'id_32', 'id_34', 'id_35', 'id_36', 'id_37', 'id_38', 'DeviceType', 'quarter', 'year', 'month', 'day', 'week', 'weekday', 'weekend', 'hour', 'minute', 'second', 'am_pm', 'P_company', 'Country', 'Dot1', 'Dot', 'UniqEmail', 'UniqEmailProb', 'UniqEmailCat', 'TrVersion', 'OpSystem', 'UniqCountryCat', 'Limit', 'device_name', 'had_id', 'P_emaildomain_1', 'P_emaildomain_2', 'P_isproton', 'lastest_browser', 'cardnumber_count_last', 'first_value_addr1', 'two_value_addr1', 'cardnumber', 'device_version', 'id_30', 'id_31', 'id_33', 'id_02', 'id_05', 'id_06', 'id_07', 'id_08', 'id_10', 'id_11', 'id_17', 'id_19', 'id_30', 'id_31', 'id_33']

train[kategorik] = train[kategorik].astype("object")
test[kategorik] = test[kategorik].astype("object")

train[numerik] = train[numerik].astype("float32")
test[numerik] = test[numerik].astype("float32")
# Daha önce eksik veriler ile mücadele etmiştik. Şimdi diğer kalanları dolduralım. Bu işlemi yaparken korelasyon, dağılım, bağımlı değişkenle olan kırımı ve test verisi incelenerek belirlendi

temp=train['ProductAmtRatio_min_group_P_emaildomain'].isnull()
train.ProductAmtRatio_min_group_P_emaildomain[temp]=train.ProductAmtRatio_min_group_hour[temp]

temp1=test['ProductAmtRatio_min_group_P_emaildomain'].isnull()
test.ProductAmtRatio_min_group_P_emaildomain[temp1]=test.ProductAmtRatio_min_group_hour[temp1]

temp=train['D15_to_std_card4'].isnull()
train.D15_to_std_card4[temp]=train.D15_to_mean_card4[temp]

temp1=test['D15_to_std_card4'].isnull()
test.D15_to_std_card4[temp1]=test.D15_to_mean_card4[temp1]

def nan_to_median(df):
    for x in list(median_doldur):
        df[x] = df[x].fillna(df[x].median())
    return df

def nan_to_mean(df):
    for x in list(mean_doldur):
        df[x] = df[x].fillna(df[x].mean())
    return df

# Ortanca ile doldurma
train = nan_to_median(train)

# Ortalama ile doldurma
train = nan_to_mean(train)

train["D15_to_std_addr1"].fillna(0, inplace = True)

train["D10"].fillna(0, inplace = True)

temp=train['D4'].isnull()
train.D4[temp]=train.D10[temp]

temp1=test['D4'].isnull()
test.D4[temp1]=test.D10[temp1]

train["V283"].fillna(1.4, inplace = True)
train["D15_to_std_card4"].fillna(0.6, inplace = True)

train["D1"].fillna(0, inplace = True)
train["TransactionAmt_to_std_card_id"].fillna(-0.707107, inplace = True)

train["D15"].fillna(0, inplace = True)
train["TransactionFreqDaily_mean_group_P_emaildomain"].fillna(0.1, inplace = True)

train["V39_V52_cor"] = train["V39"] + train["V52"]
train["V44_V86_V87_cor"] = train["V44"] + train["V86"] + train["V87"]
train["V45_V86_V87_cor"] = train["V45"] + train["V86"] + train["V87"]
train["addr1_1_addr2_2"] = train["addr1_1"] + train["addr2_2"]
train["cents"] = np.round(train["TransactionAmt"] - np.floor(train["TransactionAmt"]), 2)

train["dist1"].fillna(8, inplace = True)
# 100’den fazla “V” değişkeni var. Bunlar için Asal Bileşen Analizi(PCA) Yapalım (Not: Varsayım kontrolu yapılmamıştır)

v_features = [x for x in train.columns if x.find("V")!=-1]
v_features = v_features[:-12]
pca = PCA(n_components = 10) # 10 bileşen aldık. Açıklayıcılık varyans oranı 0.98
pca.fit(train[v_features])

pca_frame = pd.DataFrame(pca.transform(train[v_features]))
pca_frame.rename(columns = lambda x: "PCA_" + str(x), inplace = True)
train = pd.concat([train, pca_frame], axis = 1)

Ve tüm bu işlemlerin hepsinin tek tek test verisine uygulanışı ile beraber yeni değişken üretme ve parametre mühendisliği sürecimiz bitmişti. Bundan sonra Model kurma / Hiperparametre ayarlaması ve tahmin süreci devam edecekti.

Model

Bu aşamaya gelmemiz yaklaşık 2 – 2.5 ay sürmüştü. Fakat uyguladığımız her büyük değişim için model kurduk. Her yeni değişken ile ilerleyen başarımızı görünce takımca her seferinde ayrı bir mutlu oluyorduk. Artık nihai modeli kurmanın zamanı gelmişti. Final modelimizde LGBM kullanmaya karar vermiştik fakat bu kararı verene kadar catboost, xgboost, deep neural network, klasik neural network gibi modelleri denemiştik. En son olarak LGBM de karar kıldık. 

Hiper-parametrelerimiz.

lgb_param = {
                    'objective':'binary',
                    'boosting_type':'gbdt',
                    'metric':'auc',
                    'n_jobs':-1,
                    'learning_rate':0.01,
                    'num_leaves': 2**8,
                    'max_depth':-1,
                    'tree_learner':'serial',
                    'colsample_bytree': 0.5,
                    'subsample_freq':1,
                    'subsample':0.7,
                    'n_estimators':800,
                    'max_bin':255,
                    'verbose':-1,
                    'seed': SEED,
                    'early_stopping_rounds':100, 
                }
nfolds = 20
folds = StratifiedKFold(n_splits=nfolds, shuffle=True, random_state=SEED)

feature_importance_df = np.zeros((train.shape[1], nfolds))
mvalid = np.zeros(len(train))
predictions  = np.zeros(len(test))
aucs = list()

for fold_, (trn_idx, val_idx) in enumerate(folds.split(train.values, train.values)):
    print('----')
    print("fold n°{}".format(fold_))
    
    x0,y0 = train.iloc[trn_idx], y[trn_idx]
    x1,y1 = train.iloc[val_idx], y[val_idx]
    
    trn_data = lgb.Dataset(x0, label= y0); val_data = lgb.Dataset(x1, label= y1)
    
    clf = lgb.train(params, trn_data, 
                    num_round, valid_sets = [trn_data, val_data], 
                    verbose_eval=2000, 
                    early_stopping_rounds = 1000)
    
    mvalid[val_idx] = clf.predict(x1, num_iteration=clf.best_iteration)
    
    feature_importance_df[:, fold_] = clf.feature_importance()
    
    predictions += clf.predict(test, num_iteration=clf.best_iteration) / folds.n_splits
    aucs.append(clf.best_score['valid_1']['auc'])
print('Average ROC AUC Score {} [STD:{}]'.format(np.mean(aucs), np.std(aucs)))

Average ROC AUC Score 0.9668568441062284 [STD:0.0036525478830614516]

ximp = pd.DataFrame()
ximp['feature'] = train.columns
ximp['importance'] = feature_importance_df.mean(axis = 1)
plt.figure(figsize=(10,120))
sns.barplot(x="importance",
            y="feature",
            data=ximp.sort_values(by="importance",
                                           ascending=False))
plt.title('LightGBM Features (avg over folds)')
plt.tight_layout()

Sonucunu almıştık. Lokal bilgisayarımızda auc skorumuz 0.968 gelmişti. Kaggle’ a yüklediğimizde Public Skorumuz= 0.952976 iken, Private Skorumuz ise 0.928469 ile 6.381 takım ve 7.416 kişi arasından 482. olarak gümüş madalya aldık. Ödüllerin açıklanması gece saat 3’te idi. Takımda herkes alarm kurmuş gece o saatte uyanmıştı 🙂 Böyle güzel hatıraları tekrar başka bir yarışmada yaşamak dileğiyle.  Takım arkadaşlarıma tekrardan teşekkürler.

Saygılarımla,

Varsayımlarınızın sağlanması dileğiyle,

Veri ile kalın, Hoşça kalın..

Utku Kubilay Çınar –Makine Öğrenmesi ve Yapay Zeka Çözümleri Takımında Data Scientist @ DoğuşTeknoloji

Yazar Hakkında
Toplam 21 yazı
Utku Kubilay ÇINAR
Utku Kubilay ÇINAR
YTÜ - Doktora - Veri Bilimi - Alghanim Industries - Data Scientist
Yorumlar (2 yorum)
Ahmet
Ahmet Yanıtla
- 23:31

Selam,

Sizin yarattığınız değişkenler neler? Gördüğüm kadarıyla paylaştığınız kodların çoğu public kernellardan alınma. Yaptığınız farklılığı anlayamadım. Bir de aldığınız sonuç blending ile olabilir mi?

    Utku Kubilay ÇINAR
    Utku Kubilay ÇINAR Yanıtla
    - 13:52

    Merhaba Ahmet Bey,

    Öncelikle dönütünüz ve yorumunuzu belirttiğiniz için teşekkür ederim. Final modelimizin %25in üzerinde bizim ürettiğimiz değişkenlerdi. Şu an için rakamı hatırlayamıyorum. Önümüzdeki ay yapacağımız etkinlikte bu yazının uygulamasını da anlatacağım. Oraya gelerek daha detaylı bilgi edinebilirsiniz. Çalışmada binlerce satır kod olduğu için seçmece olarak buraya ekledim. Ayrıca Amerika’yı tekrar keşfetmeye gerek yok, public bilgileri tabi ki kullandık çalışmamızda. Blend dediğiniz kavramı ben de yeni öğrendim bu yarışma ile. Kendi çalışmalarımızda blend kullandık. Yanılmıyorsam 1 tane dışarıdan blend yaptık.

    Teşekkür ederim.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

×

Bir Şeyler Ara