Posts 타이타닉 탑4% 앙상블 모델링
Post
Cancel

타이타닉 탑4% 앙상블 모델링

  • 1 Introduction
  • 2 Load and check data
    • 2.1 load data
    • 2.2 Outlier detection
    • 2.3 joining train and test set
    • 2.4 check for null and missing values
  • 3 Feature analysis
    • 3.1 Numerical values
    • 3.2 Categorical values
  • 4 Filling missing Values
    • 4.1 Age
  • 5 Feature engineering
    • 5.1 Name/Title
    • 5.2 Family Size
    • 5.3 Cabin
    • 5.4 Ticket
  • 6 Modeling
    • 6.1 Simple modeling
      • 6.1.1 Cross validate models
      • 6.1.2 Hyperparamater tunning for best models
      • 6.1.3 Plot learning curves
      • 6.1.4 Feature importance of the tree based classifiers
    • 6.2 Ensemble modeling
      • 6.2.1 Combining models
    • 6.3 Prediction
      • 6.3.1 Predict and Submit results


1. Introduction

  • 해당 작성자의 첫번째 커널로 타이타닉을 선택한 이유는 데이터 엔지니어링과 모델링에 있어 좋은 feature를 가지고 있다고 한다.
  • 첫번째로 몇몇의 feature를 분석하고 그것에 집중하여 엔지니어링을 할것이다
  • 마지막 부분은 voting procedure를 사용하여 타이타닉의 생존을 모델링하고 예측할것 이다.
  • 해당 노트북은 아래의 스크립트를 따라간다. :

    • Feature analysis
    • Feature engineering
    • Modeling
  • 아래 출처의 notebook을 따라서 필사한것 임
  • 출처 : https://www.kaggle.com/yassineghouzam/titanic-top-4-with-ensemble-modeling


필수 패키지 import

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

from collections import Counter

from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, GradientBoostingClassifier, ExtraTreesClassifier, VotingClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.svm import SVC

from sklearn.model_selection import GridSearchCV, cross_val_score, StratifiedKFold, learning_curve

sns.set(style = 'white', context = 'notebook', palette = 'deep')


2. Load and check data


2.1 Load data

1
2
3
train = pd.read_csv('data/train.csv')
test = pd.read_csv('data/test.csv')
IDtest = test['PassengerId']


2.2 Outlier detection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def detect_outliers(df, n, features):
    outlier_indices = []
    for col in features:
        Q1 = np.percentile(df[col], 25)
        Q3 = np.percentile(df[col], 75)
        IQR = Q3 - Q1
        
        outlier_step = 1.5 * IQR
        outlier_list_col = df[(df[col] < Q1 - outlier_step) | (df[col] > Q3 + outlier_step)].index
        
        outlier_indices.extend(outlier_list_col)
        
    outlier_indices = Counter(outlier_indices)
    multiple_outliers = list(k for k, v in outlier_indices.items() if v > n)
    
    return multiple_outliers
1
Outliers_to_drop = detect_outliers(train, 2, ['Age', 'SibSp', 'Parch', 'Fare'])
  • outlier는 예측에 극적인 영향을 미칠 수 있기 때문에 (퇴행 문제에 대해) 관리하기로 선택했다.

  • 나는 Tukey 방법(Tukey JW, 1977)을 사용하여 분포 값(IQR)의 1 사분위수와 3 사분위수 사이의 범위를 정의하는 outlier를 검출했다. outlier란 피쳐 값이 (IQR +- 특이치 단계) 외부에 있는 행이다.

  • 나는 숫자 값 특징(Age, SibSp, Sarch 및 Fair)에서 outlier를 검출하기로 결정했다. 그리고 outlier를 최소 두 개의 outlier가 있는 행으로 생각했다.

1
2
# outlier를 가지고 있는 행들
train.loc[Outliers_to_drop]
PassengerIdSurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
272801Fortune, Mr. Charles Alexandermale19.03219950263.00C23 C25 C27S
888911Fortune, Miss. Mabel Helenfemale23.03219950263.00C23 C25 C27S
15916003Sage, Master. Thomas HenrymaleNaN82CA. 234369.55NaNS
18018103Sage, Miss. Constance GladysfemaleNaN82CA. 234369.55NaNS
20120203Sage, Mr. FrederickmaleNaN82CA. 234369.55NaNS
32432503Sage, Mr. George John JrmaleNaN82CA. 234369.55NaNS
34134211Fortune, Miss. Alice Elizabethfemale24.03219950263.00C23 C25 C27S
79279303Sage, Miss. Stella AnnafemaleNaN82CA. 234369.55NaNS
84684703Sage, Mr. Douglas BullenmaleNaN82CA. 234369.55NaNS
86386403Sage, Miss. Dorothy Edith "Dolly"femaleNaN82CA. 234369.55NaNS
  • 10개의 outlier를 가지고 있는 행들 중 28, 89, 342는 fare가 너무 높고, 나머지 7명은 SibSp가 너무 높다
1
2
# outlier 삭제
train = train.drop(Outliers_to_drop, axis = 0).reset_index(drop = True)


2.3 joining train and test set

1
2
train_len = len(train)
dataset = pd.concat(objs= [train, test], axis = 0).reset_index(drop = True)
  • categorical 변환 중에 동일한 수의 형상을 얻기 위해 train 및 test dataset을 concat함.


2.4 check for null and missing values

1
2
3
dataset = dataset.fillna(np.nan)

dataset.isnull().sum()
1
2
3
4
5
6
7
8
9
10
11
12
13
PassengerId       0
Survived        418
Pclass            0
Name              0
Sex               0
Age             256
SibSp             0
Parch             0
Ticket            0
Fare              1
Cabin          1007
Embarked          2
dtype: int64
  • Age와 Cabin에 nan이 많음
1
train.info()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 881 entries, 0 to 880
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  881 non-null    int64  
 1   Survived     881 non-null    int64  
 2   Pclass       881 non-null    int64  
 3   Name         881 non-null    object 
 4   Sex          881 non-null    object 
 5   Age          711 non-null    float64
 6   SibSp        881 non-null    int64  
 7   Parch        881 non-null    int64  
 8   Ticket       881 non-null    object 
 9   Fare         881 non-null    float64
 10  Cabin        201 non-null    object 
 11  Embarked     879 non-null    object 
dtypes: float64(2), int64(5), object(5)
memory usage: 82.7+ KB
1
train.isnull().sum()
1
2
3
4
5
6
7
8
9
10
11
12
13
PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age            170
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          680
Embarked         2
dtype: int64
1
train.head()
PassengerIdSurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
0103Braund, Mr. Owen Harrismale22.010A/5 211717.2500NaNS
1211Cumings, Mrs. John Bradley (Florence Briggs Th...female38.010PC 1759971.2833C85C
2313Heikkinen, Miss. Lainafemale26.000STON/O2. 31012827.9250NaNS
3411Futrelle, Mrs. Jacques Heath (Lily May Peel)female35.01011380353.1000C123S
4503Allen, Mr. William Henrymale35.0003734508.0500NaNS
1
train.dtypes
1
2
3
4
5
6
7
8
9
10
11
12
13
PassengerId      int64
Survived         int64
Pclass           int64
Name            object
Sex             object
Age            float64
SibSp            int64
Parch            int64
Ticket          object
Fare           float64
Cabin           object
Embarked        object
dtype: object
1
train.describe()
PassengerIdSurvivedPclassAgeSibSpParchFare
count881.000000881.000000881.000000711.000000881.000000881.000000881.000000
mean446.7139610.3859252.30760529.7316030.4551650.36322431.121566
std256.6170210.4870900.83505514.5478350.8715710.79183947.996249
min1.0000000.0000001.0000000.4200000.0000000.0000000.000000
25%226.0000000.0000002.00000020.2500000.0000000.0000007.895800
50%448.0000000.0000003.00000028.0000000.0000000.00000014.454200
75%668.0000001.0000003.00000038.0000001.0000000.00000030.500000
max891.0000001.0000003.00000080.0000005.0000006.000000512.329200


3. Feature analysis


3.1 Numerical values

1
2
# 상관관계 확인
g = sns.heatmap(train[['Survived', 'SibSp', 'Parch', 'Age', 'Fare']].corr(), annot = True, fmt = '.2f', cmap = 'coolwarm')

  • Fare만 survived에 상관관계가 있어보임 그렇다고, 다른 feature들이 유의미하지 않다는것은 아니기에 하나씩 살펴보자

SibSp

1
2
3
g = sns.factorplot(x = 'SibSp', y = 'Survived', data = train, kind = 'bar', size = 6, palette = 'muted')
g.despine(left = True)
g = g.set_ylabels('survival probability')
1
2
3
4
C:\ProgramData\Anaconda3\lib\site-packages\seaborn\categorical.py:3669: UserWarning: The `factorplot` function has been renamed to `catplot`. The original name will be removed in a future release. Please update your code. Note that the default `kind` in `factorplot` (`'point'`) has changed `'strip'` in `catplot`.
  warnings.warn(msg)
C:\ProgramData\Anaconda3\lib\site-packages\seaborn\categorical.py:3675: UserWarning: The `size` parameter has been renamed to `height`; please update your code.
  warnings.warn(msg, UserWarning)

  • 형제/자매들이 많은 승객들은 생존할 기회가 적은 것 같다.

  • 싱글 승객(0 SibSP) 또는 다른 두 사람(SibSP 1 또는 2)과 함께 생존할 수 있는 기회가 더 많음

  • 이 관찰은 상당히 흥미로우며, 이러한 범주를 설명하는 새로운 특징을 고려할 수 있다.

Parch

1
2
3
g = sns.factorplot(x = 'Parch', y = 'Survived', data = train, kind = 'bar', size = 6, palette = 'muted')
g.despine(left = True)
g = g.set_ylabels('survival probability')
1
2
3
4
C:\ProgramData\Anaconda3\lib\site-packages\seaborn\categorical.py:3669: UserWarning: The `factorplot` function has been renamed to `catplot`. The original name will be removed in a future release. Please update your code. Note that the default `kind` in `factorplot` (`'point'`) has changed `'strip'` in `catplot`.
  warnings.warn(msg)
C:\ProgramData\Anaconda3\lib\site-packages\seaborn\categorical.py:3675: UserWarning: The `size` parameter has been renamed to `height`; please update your code.
  warnings.warn(msg, UserWarning)

  • 핵가족은 독신(Parch 0), 중간(Parch 3,4), 대가족(Parch 5,6)보다 생존할 기회가 더 많다.
  • 부모/자녀가 3명인 승객의 생존에 중요한 표준 편차가 있을 수 있음

Age

1
2
g = sns.FacetGrid(train, col = 'Survived')
g = g.map(sns.distplot, 'Age')

  • 나이 분포는 꼬리가 있는 분포, 어쩌면 가우스 분포인 것 같다.
  • 우리는 생존자와 생존하지 않은 하위 인구에서 연령 분포가 같지 않다는 것을 알아챘다. 실제로 살아남은 젊은 승객에 해당하는 봉우리가 있다. 우리는 또한 60-80세 사이의 승객들이 덜 살아남았다는 것을 안다.
  • 그래서 “나이”가 “생존”과 상관관계가 없다고 해도, 우리는 생존할 기회가 더 많거나 적은 승객들의 연령 범주가 있음을 알 수 있다.
  • 아주 어린 승객들은 생존할 기회가 더 많은 것 같다.
1
2
3
4
5
g = sns.kdeplot(train['Age'][(train['Survived'] == 0) & (train['Age'].notnull())], color = 'Red', shade = True)
g = sns.kdeplot(train['Age'][(train['Survived'] == 1) & (train['Age'].notnull())], ax = g, color = 'Blue', shade = True)
g.set_xlabel('Age')
g.set_ylabel('Frequency')
g = g.legend(['Not Survived', 'Survived'])

  • 두 그래프를 포개놓으니, 0~5세 아이들의 생존율이 높음을 알수 있었다

Fare

1
dataset['Fare'].isnull().sum()
1
1
1
dataset["Fare"] = dataset["Fare"].fillna(dataset["Fare"].median())
  • 요금의 Null값은 중앙값으로 대체 하였다
1
2
g = sns.distplot(dataset['Fare'], color = 'm', label = 'Skewness : %.2f'%(dataset['Fare'].skew()))
g = g.legend(loc = 'best')

  • 요금 분배는 매우 왜곡되어 있다. 이렇게 되면 크기가 조정되더라도 모형의 매우 높은 값이 초과될 수 있다.
  • 이 경우 로그 기능으로 변환해 이 skew를 줄이는 것이 좋다.
1
dataset['Fare'] = dataset['Fare'].map(lambda i : np.log(i) if i > 0  else 0 )
1
2
g = sns.distplot(dataset['Fare'], color = 'b', label = 'Skewness : %.2f'%(dataset['Fare'].skew()))
g = g.legend(loc = 'best')

  • 로그 변환 후 왜도가 명확하게 감소함


3.2 Categorical values

Sex

1
2
g = sns.barplot(x = 'Sex', y = 'Survived', data = train)
g = g.set_ylabel('Survival Probability')

1
train[['Sex','Survived']].groupby('Sex').mean()
Survived
Sex
female0.747573
male0.190559
  • 남성이 여성보다 생존할 기회가 적다는 것은 명백하다.
  • 그래서 Sex는 생존을 예측하는 데 중요한 역할을 할 수도 있다.
  • 타이타닉 영화(1997년)를 본 사람들에게, 나는 우리 모두가 대피 중에 “여성과 아이들이 먼저”라는 문장을 기억할 것이라고 확신한다.

Pclass

1
2
3
g = sns.factorplot(x = 'Pclass', y = 'Survived', data = train, kind = 'bar', size = 6, palette = 'muted')
g.despine(left = True)
g = g.set_ylabels('survival probability')
1
2
3
4
C:\ProgramData\Anaconda3\lib\site-packages\seaborn\categorical.py:3669: UserWarning: The `factorplot` function has been renamed to `catplot`. The original name will be removed in a future release. Please update your code. Note that the default `kind` in `factorplot` (`'point'`) has changed `'strip'` in `catplot`.
  warnings.warn(msg)
C:\ProgramData\Anaconda3\lib\site-packages\seaborn\categorical.py:3675: UserWarning: The `size` parameter has been renamed to `height`; please update your code.
  warnings.warn(msg, UserWarning)

1
2
3
g = sns.factorplot(x = 'Pclass', y = 'Survived', hue = 'Sex', data = train, size = 6, kind = 'bar', palette = 'muted')
g.despine(left = True)
g = g.set_ylabels('survival probability')

  • 승객 생존은 3개 등급에서 같지 않다. 일등석 승객은 이등석, 삼등석 승객보다 생존 기회가 더 많다.
  • 이러한 경향은 남녀 승객 모두를 볼 때 보존된다.

Embarked

1
dataset['Embarked'].isnull().sum()
1
2
1
dataset['Embarked'] = dataset['Embarked'].fillna('S')
  • 2개의 null값은 가장 많은 항구인 S로 넣음
1
2
3
4
g = sns.factorplot(x="Embarked", y="Survived",  data=train,
                   size=6, kind="bar", palette="muted")
g.despine(left=True)
g = g.set_ylabels("survival probability")

  • 체르부르(C)에서 오는 승객의 생존 기회가 더 많아진 것으로 보인다.
  • 내 가설은 1등석 승객의 비율이 퀸스타운(Q), 사우샘프턴(S)보다 더 높다는 것이다.
1
2
3
4
g = sns.factorplot("Pclass", col="Embarked",  data=train,
                   size=6, kind="count", palette="muted")
g.despine(left=True)
g = g.set_ylabels("Count")

  • 실제로 사우샘프턴(S)과 퀸스타운(Q)에서 오는 승객이 3등급을 가장 많이 타는 반면, 체르부르크 승객은 대부분 생존율이 가장 높은 1등석을 타고 있다.
  • 현시점에서 일등석의 생존율이 높은 이유를 설명할 수 없다. 나의 가설은 1등석 승객들이 그들의 영향 때문에 대피하는 동안 우선순위가 결정되었다는 것이다.


4. Filling missing Values


4.1 Age

  • 보시다시피 Age 열에는 전체 데이터 집합에 256개의 결측값이 포함되어 있다.
  • (예를 들어) 생존 기회가 더 많은 하위인구(0세 ~ 5세)가 있기 때문에 연령 특징을 유지하고 누락된 가치를 귀속시키는 것이 바람직하다.
  • 이 문제를 해결하기 위해 나는 에이지(Sex, Parch, Pclass, SibSP)와 가장 상관관계가 있는 특징을 살펴보았다.
1
2
3
4
g = sns.factorplot(y='Age', x='Sex', data=dataset, kind='box')
g = sns.factorplot(y='Age', x='Sex', hue='Pclass', data=dataset, kind='box')
g = sns.factorplot(y='Age', x='Parch', data=dataset, kind='box')
g = sns.factorplot(y='Age', x='SibSp', data=dataset, kind='box')

  • 남녀 하위집단에서 연령 분포가 동일한 것 같아 성별은 나이를 예측하는 데 도움이 되지 않는다.
  • 그러나 1등석 승객은 2등석 승객보다 나이가 많고 3등석 승객보다 나이가 많다.
  • 더욱이, 승객은 나이가 많을수록 부모/자식을 더 많이 가지고 있고, 동생/여동생을 더 많이 가지고 있다.
1
2
# 성별을 categorical로 분류
dataset['Sex'] = dataset['Sex'].map({'male': 0, 'female': 1})
1
2
g = sns.heatmap(
    dataset[['Age', 'Sex', 'SibSp', 'Parch', 'Pclass']].corr(), cmap='summer_r', annot=True)

  • correlation map은 Parch를 제외한 인자 그림 관측치를 확인한다. Age는 Sex와는 상관관계가 없지만, Pclass, Parch, SibSp와 부정적으로 상관관계가 있다.
  • Age는 부모/자녀 수와 함께 성장하고 있다. 그러나 일반적인 상관관계는 부정적이다.
  • 그래서 나는 SibSP, Parch, Pclass를 사용하여 Null Age을 귀속시키기로 결정했다.
  • Pclass, Parch, SibSp에 따라 비슷한 행의 중위연령으로 Age를 채우는 전략이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Filling missing value of Age

# Fill Age with the median age of similar rows according to Pclass, Parch and SibSp
# Index of NaN age rows

index_NaN_age = list(dataset['Age'][dataset['Age'].isnull()].index)

for i in index_NaN_age:
    age_med = dataset['Age'].median()
    age_pred = dataset['Age'][((dataset['SibSp'] == dataset.iloc[i]['SibSp']) & 
                                (dataset['Parch'] == dataset.iloc[i]['Parch']) & 
                                (dataset['Pclass'] == dataset.iloc[i]['Pclass']))].median()
    if not np.isnan(age_pred) :
        dataset['Age'].iloc[i] = age_pred
    else :
        dataset['Age'].iloc[i] = age_med
1
2
g = sns.factorplot(x="Survived", y = "Age",data = train, kind="box")
g = sns.factorplot(x="Survived", y = "Age",data = train, kind="violin")

  • 연령의 중위수 값과 하위 모집단에서의 중위수 사이의 차이는 없었다.
  • 그러나 생존한 승객들의 violin plot에서 우리는 여전히 매우 어린 승객들이 더 높은 생존율을 가지고 있다는 것을 알아차린다.


5. Feature engineering


5.1 Name / Title

1
dataset['Name'].head()
1
2
3
4
5
6
0                              Braund, Mr. Owen Harris
1    Cumings, Mrs. John Bradley (Florence Briggs Th...
2                               Heikkinen, Miss. Laina
3         Futrelle, Mrs. Jacques Heath (Lily May Peel)
4                             Allen, Mr. William Henry
Name: Name, dtype: object
  • 이름 기능에는 승객의 제목에 대한 정보가 포함되어 있다.
  • 이름이 다른 승객은 대피하는데 선호될수 있다
1
2
3
4
# get title from name
dataset_title = [i.split(',')[1].split('.')[0].strip() for i in dataset['Name']]
dataset['Title'] = pd.Series(dataset_title)
dataset['Title'].head()
1
2
3
4
5
6
0      Mr
1     Mrs
2    Miss
3     Mrs
4      Mr
Name: Title, dtype: object
1
2
g = sns.countplot(x = 'Title', data = dataset)
g = plt.setp(g.get_xticklabels(), rotation = 45)

  • 데이터 집합에는 17개의 제목이 있는데, 대부분은 몇개 없다 우리는 4개의 카테고리로 분류할 수 있다.
1
2
3
4
5
6
# Convert to categorical values Title
dataset["Title"] = dataset["Title"].replace(
    ['Lady', 'the Countess', 'Countess', 'Capt', 'Col', 'Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'Rare')
dataset["Title"] = dataset["Title"].map(
    {"Master": 0, "Miss": 1, "Ms": 1, "Mme": 1, "Mlle": 1, "Mrs": 1, "Mr": 2, "Rare": 3})
dataset["Title"] = dataset["Title"].astype(int)
1
2
g = sns.countplot(dataset["Title"])
g = g.set_xticklabels(["Master","Miss/Ms/Mme/Mlle/Mrs","Mr","Rare"])

1
2
3
g = sns.factorplot(x="Title",y="Survived",data=dataset,kind="bar")
g = g.set_xticklabels(["Master","Miss-Mrs","Mr","Rare"])
g = g.set_ylabels("survival probability")

  • “여자와 아이들이 먼저”
  • 희귀한 title 을 가진 승객들이 생존할 수 있는 기회가 더 많다는 점은 흥미롭다.
1
2
# Drop Name variable
dataset.drop(labels = ["Name"], axis = 1, inplace = True)


5.2 Family size

  • 우리는 대가족들이 대피하는 동안 그들의 자매/형제/부모를 찾으면서 대피하는 데 더 많은 어려움을 겪을 것이라고 상상할 수 있다. 그래서, 나는 SibSp , Parch 그리고 1 (승객 포함)의 합인 “Fize” (가족 크기) 특징을 창조하기로 선택했다.
1
2
# create a family size descriptor from SibSp, and Parch
dataset['Fsize'] = dataset['SibSp'] + dataset['Parch'] + 1
1
2
g = sns.factorplot(x = 'Fsize', y = 'Survived', data = dataset)
g = g.set_ylabels('Survival Probability')

  • 가족의 크기가 중요한 역할을 하는 것 같은데, 대가족에게는 생존 확률이 가장 낮다.
  • 4개의 가족사이즈를 만들기로 결심했다.
1
2
3
4
5
# create new feature of family size
dataset['Single'] = dataset['Fsize'].map(lambda s : 1 if s == 1 else 0 )
dataset['SmallF'] = dataset['Fsize'].map(lambda s : 1 if s == 2 else 0 )
dataset['MedF'] = dataset['Fsize'].map(lambda s : 1 if 3 <= s <= 4 else 0 )
dataset['LargeF'] = dataset['Fsize'].map(lambda s : 1 if s >= 5 else 0 )
1
2
3
4
5
6
7
8
g = sns.factorplot(x="Single",y="Survived",data=dataset,kind="bar")
g = g.set_ylabels("Survival Probability")
g = sns.factorplot(x="SmallF",y="Survived",data=dataset,kind="bar")
g = g.set_ylabels("Survival Probability")
g = sns.factorplot(x="MedF",y="Survived",data=dataset,kind="bar")
g = g.set_ylabels("Survival Probability")
g = sns.factorplot(x="LargeF",y="Survived",data=dataset,kind="bar")
g = g.set_ylabels("Survival Probability")

  • Family size plot은 중소 가족이 단일 승객이나 대가족보다 생존할 기회가 더 많다는 것을 보여준다.
1
2
3
# convert to indicator values Title and Embarked 
dataset = pd.get_dummies(dataset, columns= ['Title'])
dataset = pd.get_dummies(dataset, columns= ['Embarked'], prefix = 'Em')
1
dataset.head()
PassengerIdSurvivedPclassSexAgeSibSpParchTicketFareCabin...SmallFMedFLargeFTitle_0Title_1Title_2Title_3Em_CEm_QEm_S
010.03022.010A/5 211711.981001NaN...1000010001
121.01138.010PC 175994.266662C85...1000100100
231.03126.000STON/O2. 31012822.070022NaN...0000100001
341.01135.0101138033.972177C123...1000100001
450.03035.0003734502.085672NaN...0000010001

5 rows × 22 columns

  • 지금까지 23개의 features가 있음

5.3 Cabin

1
dataset['Cabin'].head()
1
2
3
4
5
6
0     NaN
1     C85
2     NaN
3    C123
4     NaN
Name: Cabin, dtype: object
1
dataset["Cabin"].describe()
1
2
3
4
5
count     292
unique    186
top        G6
freq        5
Name: Cabin, dtype: object
1
dataset["Cabin"].isnull().sum()
1
1007
  • Cabin 열에는 292개의 값과 1007개의 결측값이 포함되어 있다.
  • 객실 없는 승객은 객실 번호 대신 누락된 값이 표시되어 있는 줄 알았는데.
1
dataset["Cabin"][dataset["Cabin"].notnull()].head()
1
2
3
4
5
6
1      C85
3     C123
6      E46
10      G6
11    C103
Name: Cabin, dtype: object
1
2
# Replace the Cabin number by the type of cabin 'X' if not
dataset["Cabin"] = pd.Series([i[0] if not pd.isnull(i) else 'X' for i in dataset['Cabin'] ])
  • 선실의 첫 번째 글자는 데스크를 가리키며, 타이타닉호에서 승객의 위치를 나타내기 때문에 나는 이 정보만을 보관하기로 선택했다.
1
g = sns.countplot(dataset["Cabin"],order=['A','B','C','D','E','F','G','T','X'])

1
2
g = sns.factorplot(y="Survived",x="Cabin",data=dataset,kind="bar",order=['A','B','C','D','E','F','G','T','X'])
g = g.set_ylabels("Survival Probability")

  • 객실이 있는 승객의 수가 적기 때문에 생존 확률은 중요한 표준 편차를 가지고 있고 우리는 다른 desk에 있는 승객의 생존 확률을 구별할 수 없다.
  • 그러나 우리는 객실을 가진 승객들이 (X)가 없는 승객들보다 일반적으로 생존할 기회가 더 많다는 것을 알 수 있다.
  • 특히 객실 B, C, D, E, F에 해당된다.
1
dataset = pd.get_dummies(dataset, columns = ['Cabin'], prefix = 'Cabin')


5.4 Ticket

1
dataset['Ticket'].head()
1
2
3
4
5
6
0           A/5 21171
1            PC 17599
2    STON/O2. 3101282
3              113803
4              373450
Name: Ticket, dtype: object
  • 그것은 같은 접두사를 공유하는 티켓이 함께 배치된 선실에 예약될 수 있다는 것을 의미할 수 있다. 따라서 그것은 배 안에 선실을 실제로 배치하는 것으로 이어질 수 있다.
  • 동일한 접두사를 가진 티켓은 등급과 생존이 유사할 수 있다.
  • 그래서 나는 티켓 기능 열을 티켓 접두사로 바꾸기로 결정했다.
1
2
3
4
5
6
7
8
Ticket = []
for i in list(dataset.Ticket):
    if not i.isdigit():
        Ticket.append(i.replace('.','').replace('/','').strip().split(' ')[0])
    else:
        Ticket.append('X')
dataset['Ticket'] = Ticket
dataset['Ticket'].head()
1
2
3
4
5
6
0        A5
1        PC
2    STONO2
3         X
4         X
Name: Ticket, dtype: object
1
dataset = pd.get_dummies(dataset, columns = ["Ticket"], prefix="T")
1
2
3
# Create categorical values for Pclass
dataset["Pclass"] = dataset["Pclass"].astype("category")
dataset = pd.get_dummies(dataset, columns = ["Pclass"],prefix="Pc")
1
2
# Drop useless variables 
dataset.drop(labels = ["PassengerId"], axis = 1, inplace = True)
1
dataset.head()
SurvivedSexAgeSibSpParchFareFsizeSingleSmallFMedF...T_STONOT_STONO2T_STONOQT_SWPPT_WCT_WEPT_XPc_1Pc_2Pc_3
00.0022.0101.9810012010...0000000001
11.0138.0104.2666622010...0000000100
21.0126.0002.0700221100...0100000001
31.0135.0103.9721772010...0000001100
40.0035.0002.0856721100...0000001001

5 rows × 67 columns


6.Modeling

1
2
3
4
5
## Separate train dataset and test dataset

train = dataset[:train_len]
test = dataset[train_len:]
test.drop(labels=["Survived"],axis = 1,inplace=True)
1
2
3
4
5
6
7
## Separate train features and label 

train["Survived"] = train["Survived"].astype(int)

Y_train = train["Survived"]

X_train = train.drop(labels = ["Survived"],axis = 1)


6.1 Simple modeling

6.1.1 Cross validate models

  • popular classifier 10개 을 비교하고 층화된 kfold 교차 검증 절차를 통해 각각의 평균 정확도를 평가할것이다.
    • SVC
    • Decision Tree
    • AdaBoost
    • Random Forest
    • Extra Trees
    • Gradient Boosting
    • Multiple layer perceprton (neural network)
    • KNN
    • Logistic regression
    • Linear Discriminant Analysis
1
2
# Cross validate model with Kfold stratified cross val
kfold = StratifiedKFold(n_splits=10)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
random_state = 2
classifiers = []
classifiers.append(SVC(random_state=random_state))
classifiers.append(DecisionTreeClassifier(random_state=random_state))
classifiers.append(AdaBoostClassifier(DecisionTreeClassifier(random_state=random_state),random_state=random_state, learning_rate=0.1))
classifiers.append(RandomForestClassifier(random_state=random_state))
classifiers.append(ExtraTreesClassifier(random_state=random_state))
classifiers.append(GradientBoostingClassifier(random_state=random_state))
classifiers.append(MLPClassifier(random_state=random_state))
classifiers.append(KNeighborsClassifier())
classifiers.append(LogisticRegression(random_state=random_state))
classifiers.append(LinearDiscriminantAnalysis())

cv_results = []
for classifier in classifiers:
    cv_results.append(cross_val_score(classifier, X_train,
                                      y=Y_train, scoring='accuracy', cv=kfold, n_jobs=4))

cv_means = []
cv_std = []

for cv_result in cv_results:
    cv_means.append(cv_result.mean())
    cv_std.append(cv_result.std())

cv_res = pd.DataFrame({"CrossValMeans": cv_means, "CrossValerrors": cv_std, "Algorithm": ["SVC", "DecisionTree", "AdaBoost",
                                                                                          "RandomForest", "ExtraTrees", "GradientBoosting", "MultipleLayerPerceptron", "KNeighboors", "LogisticRegression", "LinearDiscriminantAnalysis"]})

g = sns.barplot("CrossValMeans", "Algorithm", data=cv_res,
                palette="Set3", orient="h", **{'xerr': cv_std})
g.set_xlabel("Mean Accuracy")
g = g.set_title("Cross validation scores")

  • 앙상블 모델링을 위해 SVC, AdaBoost, Random Forest , ExtraTree, 그리고 GradientBoosting 분류기를 선택하기로 결정했다.


6.1.2 Hyperparameter tunning for best models

  • AdaBoost, ExtraTree, RandomForest, GradientBoosting 및 SVC 분류기에 대한 그리드 검색 최적화를 수행했다.
  • 나는 4개의 cpu가 있기 때문에 “n_jobs” 파라미터를 4로 설정했다. 계산 시간이 분명히 단축되었다.
  • notebook에는 n_jobs를 4로 설정하였지만, 여기서는 -1로 하겠음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Adaboost
DTC = DecisionTreeClassifier()

adaDTC = AdaBoostClassifier(DTC, random_state=7)

ada_param_grid = {"base_estimator__criterion": ["gini", "entropy"],
                  "base_estimator__splitter":   ["best", "random"],
                  "algorithm": ["SAMME", "SAMME.R"],
                  "n_estimators": [1, 2],
                  "learning_rate":  [0.0001, 0.001, 0.01, 0.1, 0.2, 0.3, 1.5]}

gsadaDTC = GridSearchCV(adaDTC, param_grid=ada_param_grid,
                        cv=kfold, scoring="accuracy", n_jobs=-1, verbose=1)

gsadaDTC.fit(X_train, Y_train)

ada_best = gsadaDTC.best_estimator_
1
2
3
4
5
6
7
Fitting 10 folds for each of 112 candidates, totalling 1120 fits


[Parallel(n_jobs=-1)]: Using backend LokyBackend with 12 concurrent workers.
[Parallel(n_jobs=-1)]: Done  26 tasks      | elapsed:    2.4s
[Parallel(n_jobs=-1)]: Done 664 tasks      | elapsed:    3.7s
[Parallel(n_jobs=-1)]: Done 1120 out of 1120 | elapsed:    4.4s finished
1
gsadaDTC.best_score_
1
0.82073544433095
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ExtraTrees 
ExtC = ExtraTreesClassifier()


## Search grid for optimal parameters
ex_param_grid = {"max_depth": [None],
              "max_features": [1, 3, 10],
              "min_samples_split": [2, 3, 10],
              "min_samples_leaf": [1, 3, 10],
              "bootstrap": [False],
              "n_estimators" :[100,300],
              "criterion": ["gini"]}


gsExtC = GridSearchCV(ExtC,param_grid = ex_param_grid, cv=kfold, scoring="accuracy", n_jobs= -1, verbose = 1)

gsExtC.fit(X_train,Y_train)

ExtC_best = gsExtC.best_estimator_

# Best score
gsExtC.best_score_
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 12 concurrent workers.


Fitting 10 folds for each of 54 candidates, totalling 540 fits


[Parallel(n_jobs=-1)]: Done  26 tasks      | elapsed:    1.2s
[Parallel(n_jobs=-1)]: Done 176 tasks      | elapsed:    6.5s
[Parallel(n_jobs=-1)]: Done 426 tasks      | elapsed:   16.4s
[Parallel(n_jobs=-1)]: Done 540 out of 540 | elapsed:   21.0s finished





0.8308733401430031
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# RFC
RFC = RandomForestClassifier()

rf_param_grid = {'max_depth' : [None],
                'max_features' : [1,3,10],
                'min_samples_split' : [2,3,10],
                'min_samples_leaf' : [1,3,10],
                'bootstrap' : [False],
                'n_estimators' : [100,300],
                'criterion' :['gini']}

gsRFC = GridSearchCV(RFC, param_grid = rf_param_grid, cv = kfold, scoring='accuracy', n_jobs = -1, verbose = 1)

gsRFC.fit(X_train, Y_train)
RFC_best = gsRFC.best_estimator_

#bset score
gsRFC.best_score_
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Fitting 10 folds for each of 54 candidates, totalling 540 fits


[Parallel(n_jobs=-1)]: Using backend LokyBackend with 12 concurrent workers.
[Parallel(n_jobs=-1)]: Done  26 tasks      | elapsed:    3.5s
[Parallel(n_jobs=-1)]: Done 176 tasks      | elapsed:    8.8s
[Parallel(n_jobs=-1)]: Done 426 tasks      | elapsed:   19.1s
[Parallel(n_jobs=-1)]: Done 540 out of 540 | elapsed:   24.4s finished





0.8365806945863126
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Gradient boosting

GBC = GradientBoostingClassifier()
gb_param_grid = {'loss' : ['deviance'],
                'n_estimators' : [100, 200, 300],
                'learning_rate' : [0.1, 0.05, 0.01],
                'max_depth' : [4, 8],
                'min_samples_leaf' : [100, 150],
                'max_features' : [0.3, 0.1]}

gsGBC = GridSearchCV(GBC, param_grid= gb_param_grid, cv = kfold, scoring= 'accuracy', n_jobs = -1, verbose = 1)
gsGBC.fit(X_train, Y_train)
GBC_best = gsGBC.best_estimator_
gsGBC.best_score_
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Fitting 10 folds for each of 72 candidates, totalling 720 fits


[Parallel(n_jobs=-1)]: Using backend LokyBackend with 12 concurrent workers.
[Parallel(n_jobs=-1)]: Done  26 tasks      | elapsed:    2.8s
[Parallel(n_jobs=-1)]: Done 176 tasks      | elapsed:    5.5s
[Parallel(n_jobs=-1)]: Done 426 tasks      | elapsed:    9.7s
[Parallel(n_jobs=-1)]: Done 720 out of 720 | elapsed:   14.6s finished





0.8331460674157304
1
2
3
4
5
6
7
8
9
# SVC
SVMC = SVC(probability=True)
svc_param_grid = {'kernel' : ['rbf'],
                 'gamma': [0.001, 0.01, 0.1, 1],
                 'C':[1, 10, 50, 100, 200, 300, 1000]}
gsSVMC = GridSearchCV(SVMC, param_grid = svc_param_grid, cv = kfold, scoring='accuracy', n_jobs = -1, verbose = 1)
gsSVMC.fit(X_train, Y_train)
SVMC_best = gsSVMC.best_estimator_
gsSVMC.best_score_
1
2
3
4
5
6
7
8
9
Fitting 10 folds for each of 28 candidates, totalling 280 fits


[Parallel(n_jobs=-1)]: Using backend LokyBackend with 12 concurrent workers.
[Parallel(n_jobs=-1)]: Done  26 tasks      | elapsed:    0.8s
[Parallel(n_jobs=-1)]: Done 176 tasks      | elapsed:    5.4s
[Parallel(n_jobs=-1)]: Done 280 out of 280 | elapsed:   10.1s finished

0.8331332992849847


6.1.3 Plot learning curves

  • 학습 곡선은 훈련 세트에 대한 오버핏 효과와 훈련 크기가 정확도에 미치는 영향을 확인할 수 있는 좋은 방법이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None, n_jobs=-1, train_sizes=np.linspace(.1, 1.0, 5)):
    plt.figure()
    plt.title(title)
    if ylim is not None:
        plt.ylim(*ylim)
    plt.xlabel('Training examples')
    plt.ylabel('Score')
    train_sizes, train_scores, test_scores = learning_curve(
        estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes)
    train_score_mean = np.mean(train_scores, axis=1)
    train_score_std = np.std(train_scores, axis=1)
    test_score_mean = np.mean(test_scores, axis=1)
    test_score_std = np.std(test_scores, axis=1)
    plt.grid()

    plt.fill_between(train_sizes, train_score_mean - train_score_std, train_score_mean + train_score_std, alpha=0.1, color='r')
    plt.fill_between(train_sizes, test_score_mean - test_score_std, test_score_mean + test_score_std, alpha = 0.1, color = 'g')
    plt.plot(train_sizes, train_score_mean, 'o-', color = 'r', label = 'Training score')
    plt.plot(train_sizes, test_score_mean, 'o-', color = 'g', label = 'Cross-validation score')
    
    plt.legend(loc = 'best')
    return plt
1
2
3
4
5
g = plot_learning_curve(gsRFC.best_estimator_,"RF mearning curves",X_train,Y_train,cv=kfold)
g = plot_learning_curve(gsExtC.best_estimator_,"ExtraTrees learning curves",X_train,Y_train,cv=kfold)
g = plot_learning_curve(gsSVMC.best_estimator_,"SVC learning curves",X_train,Y_train,cv=kfold)
g = plot_learning_curve(gsadaDTC.best_estimator_,"AdaBoost learning curves",X_train,Y_train,cv=kfold)
g = plot_learning_curve(gsGBC.best_estimator_,"GradientBoosting learning curves",X_train,Y_train,cv=kfold)

  • GradientBoosting과 Adaboost 분류자는 훈련 세트에 오버핏하는 경향이 있다. 증가하는 교차 검증 곡선에 따르면 GradientBoosting과 Adaboost는 더 많은 훈련 사례를 통해 더 나은 성과를 낼 수 있다.
  • SVC와 ExtraTree 분류자는 훈련 곡선과 교차 검증 곡선이 서로 가깝기 때문에 예측을 더 잘 일반화하는 것으로 보인다.


6.1.4 Feature importance of tree based classifiers

  • 승객 생존 예측에 가장 유용한 기능을 보기 위해 4개의 트리 기반 분류기에 대한 기능 중요도를 표시했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
nrows = ncols = 2
fig, axes = plt.subplots(nrows = nrows, ncols = ncols, sharex="all", figsize=(15,15))

names_classifiers = [("AdaBoosting", ada_best),("ExtraTrees",ExtC_best),("RandomForest",RFC_best),("GradientBoosting",GBC_best)]

nclassifier = 0
for row in range(nrows):
    for col in range(ncols):
        name = names_classifiers[nclassifier][0]
        classifier = names_classifiers[nclassifier][1]
        indices = np.argsort(classifier.feature_importances_)[::-1][:40]
        g = sns.barplot(y=X_train.columns[indices][:40],x = classifier.feature_importances_[indices][:40] , orient='h',ax=axes[row][col])
        g.set_xlabel("Relative importance",fontsize=12)
        g.set_ylabel("Features",fontsize=12)
        g.tick_params(labelsize=9)
        g.set_title(name + " feature importance")
        nclassifier += 1

  • 4개의 트리 기반 분류자(Adaboost, ExtraTree, RandomForest 및 GradientBoosting)에 대한 피쳐 중요도를 표시한다.
  • 우리는 4개의 분류자가 상대적 중요도에 따라 상위 특징을 가지고 있다는 것을 주목한다. 이들의 예측이 같은 특징을 바탕으로 한 것이 아니라는 뜻이다. 그럼에도 불구하고, 그들은 분류에 대한 몇 가지 공통적인 중요한 특징들을 공유한다. 예를 들어, ‘Par’, ‘Title_2’, ‘Age’, ‘Sex’ 등이 그것이다.
  • Title_2는 Mrs/Mme/Miss/Ms 범주가 Sex와 높은 상관관계를 가지고 있음을 나타낸다.
  • 우리는 다음과 같이 말할 수 있다.
    • pc_1, pc_2, pc_3 및 운임은 승객의 일반적인 사회적 지위를 말한다.
    • Sex and Title_2(Mrs/Mlle/Mme/Miss/Ms)와 Title_3(Mr)은 성별을 가리킨다.
    • Age 및 Title_1(Master)은 승객의 나이를 말한다.
    • Fsize, LargeF, MedF, Single은 승객 가족의 크기를 가리킨다.
  • 이 4가지 분류기의 특징적인 중요성에 따라 생존 예측은 배 안의 위치보다 ‘나이’ ‘성’ ‘가족 규모’ ‘승객의 사회적 지위’와 더 관련이 있는 것으로 보인다.
1
2
3
4
5
6
7
8
9
10
11
test_Survived_RFC = pd.Series(RFC_best.predict(test), name="RFC")
test_Survived_ExtC = pd.Series(ExtC_best.predict(test), name="ExtC")
test_Survived_SVMC = pd.Series(SVMC_best.predict(test), name="SVC")
test_Survived_AdaC = pd.Series(ada_best.predict(test), name="Ada")
test_Survived_GBC = pd.Series(GBC_best.predict(test), name="GBC")


# Concatenate all classifier results
ensemble_results = pd.concat([test_Survived_RFC,test_Survived_ExtC,test_Survived_AdaC,test_Survived_GBC, test_Survived_SVMC],axis=1)

g= sns.heatmap(ensemble_results.corr(),annot=True)

  • 이 예측은 Adaboost 다른 분류자와 비교했을 때를 제외하고는 5개 분류자의 경우 상당히 유사한 것으로 보인다.
  • 5개의 분류자는 거의 같은 예측을 하지만 약간의 차이가 있다. 5가지 분류자 예측 간의 차이점은 통합 투표를 고려하기에 충분하다.


6.2 Ensemble modeling

6.2.1 Combining models

  • 나는 5개의 분류자에서 나온 예측을 결합하기 위해 voting classifier를 선택했다.
  • 나는 각 투표의 확률을 고려하기 위해 투표 매개변수에 “soft” 주장을 전달하는 것을 선호했다.
1
2
3
4
5
# 각 모델별 베스트 파라미터를 뽑고, voting을 시킴
votingC = VotingClassifier(estimators=[('rfc', RFC_best), ('extc', ExtC_best),
('svc', SVMC_best), ('adac',ada_best),('gbc',GBC_best)], voting='soft', n_jobs=4)

votingC = votingC.fit(X_train, Y_train)


6.3 Prediction

6.3.1 Predict and Submit results

1
2
3
4
5
test_Survived = pd.Series(votingC.predict(test), name="Survived")

results = pd.concat([IDtest,test_Survived],axis=1)

results.to_csv("result/03.ensemble_python_voting.csv",index=False)
This post is licensed under CC BY 4.0 by the author.