Kaggle: Credit risk (Model: Random Forest)

A commonly used model for exploring classification problems is the random forest classifier.

It is called a random forest as it an ensemble (i.e., multiple) of decision trees and merges them to obtain a more accurate and stable prediction. Random forests lead to less overfit compared to a single decision tree especially if there are sufficient trees in the forest. It is also called 'random' as a random subset of features are considered by the algorithim each time a node is being split. In addition, where a decision tree uses the best possible thresholds for splitting a node, you can use a random threshold in a random forest. Random forests are ideal as a predictive tool, and not a descriptive tool. A decision tree is more suitable if you are evaluating relationships within the data.

Random forests are usually trained using the "bagging" approach (i.e., bootstrap aggregation). The "bagging" approach is such that given an initial training dataset $D$ of size $n$, bagging generates $m$ new datasets $D_i$ each of size $n$ by sampling from $D$ uniformly with replacement. Thus, $m$ models can be fitted on the $m$ new datasets that have been created from the initial training dataset $D$ via bootstrapping with replacement. These $m$ models are then combined by averaging the output (i.e., regression) or voting (i.e., classification).

Random forests are also useful as it is possible the measure the relative importance of each feaure on the prediction. This is performed by analyzing a feature's importance based on how often the tree nodes, and how many trees use that feature. Understanding which features are important allows us to drop those that add little or no value to our classification problem.

Loading in required modules

In [1]:
# importing all system modules
import os
import sys
import warnings
from pathlib import Path
warnings.filterwarnings('ignore')
if sys.platform == 'linux':
    sys.path.append('/home/randlow/github/blog2/listings/machine-learning/') # linux
elif sys.platform == 'win32':
    sys.path.append('\\Users\\randl\\github\\blog2\\listings\\machine-learning\\') # win32

# importing data science modules
import pandas as pd
import numpy as np
import sklearn
import scipy as sp
import pickleshare

# importing graphics modules
import matplotlib.pyplot as plt
import seaborn as sns
import bokeh as bk

# importing personal data science modules
import rand_eda as eda

Loading pickled dataframes

To see how the below dataframes were obtained see the post on the Kaggle: Credit risk (Feature Engineering)

In [2]:
home = str(Path.home())
if sys.platform == 'linux':
    inputDir = "/datasets/kaggle/home-credit-default-risk" # linux
elif sys.platform == 'win32':
    inputDir = "\datasets\kaggle\home-credit-default-risk" # windows

storeDir = home+inputDir+'/pickleshare'

db = pickleshare.PickleShareDB(storeDir)
print(db.keys())

df_app_test_align = db['df_app_test_align'] 
df_app_train_align = db['df_app_train_align'] 
#df_app_train_align_expert  = db['df_app_train_align_expert'] 
#df_app_test_align_expert = db['df_app_test_align_expert'] 
#df_app_train_poly_align = db['df_app_train_poly_align']
#df_app_test_poly_align = db['df_app_test_poly_align'] 
['df_app_test_align', 'df_app_train_align', 'df_app_train_corr_target', 'df_app_train_align_expert', 'df_app_test_align_expert', 'df_app_train_poly_align', 'df_app_test_poly_align']

Selection of feature set for model training & testing

Assign which ever datasets you want to train and test. This is because as part of feature engineering, you will often build new and different feature datasets and would like to test each one out to evaluate whether it improves model performance.

As the imputer is being fitted on the training data and used to transform both the training and test datasets, the training data needs to have the same number of features as the test dataset. This means that the TARGET column must be removed from the training dataset, and stored in train_labels for use later.

In [3]:
train = df_app_train_align.copy()
test = df_app_test_align.copy()

train_labels = train.pop('TARGET')
feat_names = list(train.columns)

Feature set preprocessing

In [4]:
from sklearn.impute import SimpleImputer
imputer = SimpleImputer(missing_values=np.nan,strategy='median')

from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler(feature_range= (0,1))

We fit the imputer and scaler on the training data, and perform the imputer and scaling transformations on both the training and test datasets.

Scikit-learn models only accept arrays. So the imputer and scalers can accept DataFrames as inputs and they output the train and test variables as arrays for use into Scikit-Learn's machine learning models.

In [5]:
imputer.fit(train)
train = imputer.transform(train)
test = imputer.transform(test)

scaler.fit(train)
train = scaler.transform(train)
test = scaler.transform(test)
---------------------------------------------------------------------------
MemoryError                               Traceback (most recent call last)
<ipython-input-5-e0eecd0cc321> in <module>
----> 1 imputer.fit(train)
      2 train = imputer.transform(train)
      3 test = imputer.transform(test)
      4 
      5 scaler.fit(train)

~/anaconda3/lib/python3.7/site-packages/sklearn/impute.py in fit(self, X, y)
    257                                                self.strategy,
    258                                                self.missing_values,
--> 259                                                fill_value)
    260 
    261         return self

~/anaconda3/lib/python3.7/site-packages/sklearn/impute.py in _dense_fit(self, X, strategy, missing_values, fill_value)
    315         # Median
    316         elif strategy == "median":
--> 317             median_masked = np.ma.median(masked_X, axis=0)
    318             # Avoid the warning "Warning: converting a masked element to nan."
    319             median = np.ma.getdata(median_masked)

~/anaconda3/lib/python3.7/site-packages/numpy/ma/extras.py in median(a, axis, out, overwrite_input, keepdims)
    692 
    693     r, k = _ureduce(a, func=_median, axis=axis, out=out,
--> 694                     overwrite_input=overwrite_input)
    695     if keepdims:
    696         return r.reshape(k)

~/anaconda3/lib/python3.7/site-packages/numpy/lib/function_base.py in _ureduce(a, func, **kwargs)
   3248         keepdim = (1,) * a.ndim
   3249 
-> 3250     r = func(a, **kwargs)
   3251     return r, keepdim
   3252 

~/anaconda3/lib/python3.7/site-packages/numpy/ma/extras.py in _median(a, axis, out, overwrite_input)
    713             asorted = a
    714     else:
--> 715         asorted = sort(a, axis=axis, fill_value=fill_value)
    716 
    717     if axis is None:

~/anaconda3/lib/python3.7/site-packages/numpy/ma/core.py in sort(a, axis, kind, order, endwith, fill_value)
   6710     if isinstance(a, MaskedArray):
   6711         a.sort(axis=axis, kind=kind, order=order,
-> 6712                endwith=endwith, fill_value=fill_value)
   6713     else:
   6714         a.sort(axis=axis, kind=kind, order=order)

~/anaconda3/lib/python3.7/site-packages/numpy/ma/core.py in sort(self, axis, kind, order, endwith, fill_value)
   5560 
   5561         sidx = self.argsort(axis=axis, kind=kind, order=order,
-> 5562                             fill_value=fill_value, endwith=endwith)
   5563 
   5564         self[...] = np.take_along_axis(self, sidx, axis=axis)

~/anaconda3/lib/python3.7/site-packages/numpy/ma/core.py in argsort(self, axis, kind, order, endwith, fill_value)
   5407 
   5408         filled = self.filled(fill_value)
-> 5409         return filled.argsort(axis=axis, kind=kind, order=order)
   5410 
   5411     def argmin(self, axis=None, fill_value=None, out=None):

MemoryError: 

Model implementation (Random Forest)

In this implementation of random forest, we are using a 100 trees (n_estimators=100) using all processors (n_jobs=-1)

In [ ]:
from sklearn.ensemble import RandomForestClassifier

rf = RandomForestClassifier(n_estimators = 50, random_state=10, verbose = 1, n_jobs = -1)
In [ ]:
rf.fit(train,train_labels)

Exploring random forest feature importances

Decision trees are non-parametric supervised learning models that infer the value of a target variable by analyzing decision rules from the features of the dataset. Since the random forest consists of many decision trees, a random forest can be used to produce what the most important features are to predict the target variable by analzying all the trees for which features use that tree to node

We can see here that our random forest selected EXT_SOURCE_2/3, DAYS_BIRTH as the top 3 most important features. These feature importances produced by the random forest can be used for further feature engineering and culling features that are of low importance (e.g., FLAG_DOCUMENT_x)

In [9]:
feat_importance_values = rf.feature_importances_
df_feat_importance = pd.DataFrame({'Feature':feat_names,'Importance': feat_importance_values})
eda.plot_feat_importance(df_feat_importance)

We apply our fitted random forest model to predict the TARGET outcomes from the test dataset

In [10]:
rf_pred = rf.predict_proba(test)[:,1]
[Parallel(n_jobs=4)]: Using backend ThreadingBackend with 4 concurrent workers.
[Parallel(n_jobs=4)]: Done  42 tasks      | elapsed:    0.7s
[Parallel(n_jobs=4)]: Done 100 out of 100 | elapsed:    1.5s finished

Kaggle submission

We create the submission dataframe as per the Kaggle home-credit-default-risk competition guidelines

In [11]:
submit = pd.DataFrame()
submit['SK_ID_CURR'] = df_app_test_align.index
submit['TARGET'] = rf_pred
print(submit.head())
print(submit.shape)
   SK_ID_CURR  TARGET
0      100001    0.09
1      100005    0.05
2      100013    0.02
3      100028    0.02
4      100038    0.07
(48744, 2)

Submit the csv file to Kaggle for scoring

In [12]:
submit.to_csv('random-forest-home-loan-credit-risk.csv',index=False)
!kaggle competitions submit -c home-credit-default-risk -f random-forest-home-loan-credit-risk.csv -m 'submitted'
100%|█████████████████████████████████████████| 567k/567k [00:00<00:00, 734kB/s]
Successfully submitted to Home Credit Default Risk

We review our random forest scores from Kaggle and find that there is a slight improvement to 0.687 compared to 0.662 based upon the logit model (publicScore). We will try other featured engineering datasets and other more sophisticaed machine learning models in the next posts.

In [13]:
!kaggle competitions submissions -c home-credit-default-risk
fileName                                 date                 description  status    publicScore  privateScore  
---------------------------------------  -------------------  -----------  --------  -----------  ------------  
random-forest-home-loan-credit-risk.csv  2019-02-11 17:31:03  submitted    complete  0.68694      0.68886       
random-forest-home-loan-credit-risk.csv  2019-02-11 05:24:55  submitted    complete  0.68694      0.68886       
random-forest-home-loan-credit-risk.csv  2019-02-11 05:10:40  submitted    complete  0.68694      0.68886       
logit-home-loan-credit-risk.csv          2019-02-11 04:52:51  submitted    complete  0.66223      0.67583       
random-forest-home-loan-credit-risk.csv  2019-02-11 04:44:50  submitted    complete  0.68694      0.68886       
logit-home-loan-credit-risk.csv          2019-02-08 04:08:33  submitted    complete  0.66223      0.67583       

Converting iPython notebook to Python code

This allows us to run the code in Spyder.

In [14]:
!jupyter nbconvert ml_kaggle-home-loan-credit-risk-model-random-forest.ipynb --output-dir='~/github/blog2/listings/machine-learning/' --to python
[NbConvertApp] Converting notebook ml_kaggle-home-loan-credit-risk-model-random-forest.ipynb to python
[NbConvertApp] Writing 7711 bytes to /home/randlow/github/blog2/listings/machine-learning/ml_kaggle-home-loan-credit-risk-model-random-forest.py

Comments

Comments powered by Disqus