4-2. 앙상블 학습
In [1]:
from IPython.core.display import display, HTML
display(HTML("<style> .container{width:90% !important;}</style>"))

1. 앙상블 학습이란?

앙상블(Ensemble) 학습

앙상블이란 여러 개의 알고리즘을 사용하여, 그 예측을 결함함으로써 보다 정확한 예측을 도출하는 기법을 말합니다.
집단지성이 힘을 발휘하는 것처럼 단일의 강한 알고리즘보다 복수의 약한 알고리즘이 더 뛰어날 수 있다는 생각에 기반을 두고 있습니다.

이미지, 영상, 음성 등의 비정형 데이터의 분류는 딥러닝이 뛰어난 성능을 보이지만,
대부분 정형 데이터의 분류에서는 앙상블이 뛰어난 성능을 보이고 있다고 합니다.

앙상블 학습의 유형은 보팅(Voting), 배깅(Bagging), 부스팅(Boosting), 스태킹(Stacking) 등이 있습니다.

보팅은 여러 종류의 알고리즘을 사용한 각각의 결과에 대해 투표를 통해 최종 결과를 예측하는 방식입니다.
배깅은 같은 알고리즘에 대해 데이터 샘플을 다르게 두고 학습을 수행해 보팅을 수행하는 방식입니다.
이 때의 데이터 샘플은 중첩이 허용됩니다. 즉 10000개의 데이터에 대해 10개의 알고리즘이 배깅을 사용할 때,
각 1000개의 데이터 내에는 중복된 데이터가 존재할 수 있습니다.
배깅의 대표적인 방식이 Random Forest 입니다.

부스팅은 여러 개의 알고리즘이 순차적으로 학습을 하되, 앞에 학습한 알고리즘 예측이 틀린 데이터에 대해
올바르게 예측할 수 있도록, 그 다음번 알고리즘에 가중치를 부여하여 학습과 예측을 진행하는 방식입니다.

마지막으로 스태킹은 여러 가지 다른 모델의 예측 결과값을 다시 학습 데이터로 만들어 다른 모델(메타 모델)로
재학습시켜 결과를 예측하는 방법입니다.

2. 하드보팅(Hard Voting)과 소프트보팅(Soft Voting)

하드보팅을 이용한 분류는 다수결 원칙과 비슷합니다.
소프트 보팅은 각 알고리즘이 레이블 값 결정 확률을 예측해서, 이것을 평균하여 이들 중 확률이 가장 높은 레이블 값을 최종 값으로 예측합니다.

일반적으로는 소프트 보팅이 성능이 더 좋아서 많이 적용됩니다.

3. 보팅 분류기(Voting Classifier)

사이킷런은 보팅방식의 앙상블을 구현한 VotingClassifier 클래스를 제공하고 있습니다.

사이킷런에서 제공되는 위스콘신 유방암 데이터 세트를 이용해 보팅방식의 앙상블을 적용해보겠습니다.

In [2]:
# 필요한 모듈과 데이터 불러오기
import pandas as pd

from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

from warnings import filterwarnings
filterwarnings('ignore')

cancer = load_breast_cancer()

data_df = pd.DataFrame(cancer.data, columns = cancer.feature_names)
data_df.head(3)
Out[2]:
mean radius mean texture mean perimeter mean area mean smoothness mean compactness mean concavity mean concave points mean symmetry mean fractal dimension ... worst radius worst texture worst perimeter worst area worst smoothness worst compactness worst concavity worst concave points worst symmetry worst fractal dimension
0 17.99 10.38 122.8 1001.0 0.11840 0.27760 0.3001 0.14710 0.2419 0.07871 ... 25.38 17.33 184.6 2019.0 0.1622 0.6656 0.7119 0.2654 0.4601 0.11890
1 20.57 17.77 132.9 1326.0 0.08474 0.07864 0.0869 0.07017 0.1812 0.05667 ... 24.99 23.41 158.8 1956.0 0.1238 0.1866 0.2416 0.1860 0.2750 0.08902
2 19.69 21.25 130.0 1203.0 0.10960 0.15990 0.1974 0.12790 0.2069 0.05999 ... 23.57 25.53 152.5 1709.0 0.1444 0.4245 0.4504 0.2430 0.3613 0.08758

3 rows × 30 columns

로지스틱회귀와 KNN을 기반으로 하여 소프트 보팅 방식으로 보팅 분류기를 만들어 보겠습니다.

In [3]:
# 보팅 적용을 위한 개별 모델은 로지스틱 회귀와 KNN입니다.
logistic_regression = LogisticRegression()
knn = KNeighborsClassifier(n_neighbors=8)

# 개별모델을 소프트보팅 기반의 앙상블 모델로 구현한 분류기
voting_model = VotingClassifier(estimators=[ ('LogisticRegression', logistic_regression), ('KNN', knn)], voting='soft')

# 데이터를 훈련셋과 테스트셋으로 나누기
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, test_size=0.2, random_state=156)

# 보팅 분류기의 학습/예측/평가
voting_model.fit(X_train, y_train)
pred = voting_model.predict(X_test)
print('보팅 분류기의 정확도: {0: .4f}'.format(accuracy_score(y_test, pred)))

# 개별 모델의 학습/예측/평가
classifiers = [logistic_regression, knn]
for classifier in classifiers:
    classifier.fit(X_train, y_train)
    pred = classifier.predict(X_test)
    class_name = classifier.__class__.__name__
    print('{0} 정확도: {1:.4f}'.format(class_name, accuracy_score(y_test, pred)))
보팅 분류기의 정확도:  0.9561
LogisticRegression 정확도: 0.9474
KNeighborsClassifier 정확도: 0.9386

보팅 분류기의 정확도가 각 개별 모델의 정확도보다 조금 높게 나타났습니다.
하지만 여러 알고리즘을 결합한다고 항상 성능이 향상되는 것은 아닙니다.

(필사커널) 190717 EDA To Prediction(DieTanic)
In [1]:
from IPython.core.display import display, HTML
display(HTML("<style> .container{width:90% !important;}</style>"))

경진대회 : Titanic: Machine Learning from Disaster
https://www.kaggle.com/c/titanic

원본 커널 : EDA To Prediction(DieTanic)
https://www.kaggle.com/ash316/eda-to-prediction-dietanic

Contents of the Notebook

Part 1. EDA:

(1) Feature 분석

(2) 여러 Feature들간의 관계나 트렌드 찾기

Part 2. Feature Engineering and Data Cleansing:

(1) 새로운 Feature 더하기

(2) Redundant한 Feature 제거

(3) Feature를 모델링에 적합한 형태로 변환하기

Part 3. Predictive Modeling

(1) 기본 알고리즘 수행

(2) Cross Validation

(3) Ensembling

(4) 중요한 Feature 추출

Part 1. Exploratory Data Analysis(EDA):

In [2]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
plt.style.use('fivethirtyeight')
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
In [3]:
data = pd.read_csv('input/train.csv')
In [4]:
data.head()
Out[4]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S
In [5]:
data.isnull().sum() # 전체 Null 값 확인
Out[5]:
PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age            177
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64

Age, Cabin, Embarked 에 Null 값이 있습니다. 나중에 이것들을 수정해보겠습니다.

얼마나 생존을 했을까??

In [6]:
f, ax = plt.subplots(1, 2, figsize=(15, 6))

data['Survived'].value_counts().plot.pie(explode = [0,0.1], autopct = '%1.1f%%', ax=ax[0], shadow=True)
ax[0].set_title('Survived')
ax[0].set_ylabel(' ')

sns.countplot('Survived', data=data, ax=ax[1])
ax[1].set_title('Survived')

plt.show()

많은 탑승객들이 생존하지 못했습니다.

Training Set의 891명 탑승객 중 약 350명(38.4%)만이 이 사고로부터 생존했습니다.

데이터로부터 다른 인사이트를 얻고 어떤 유형의 탑승객이 생존했고, 그렇지 못했는지 살펴보기 위해 더 들어가보갰습니다.

데이터셋의 여러 feature들을 사용해서 생존률을 체크해보겠습니다.

다룰 feature들은 Sex(성별), Port of Embarkation(탑승항구), Age(연령) 등 입니다.

먼저 feature들의 유형에 대해 이해해보겠습니다.

Feature의 유형

  • Categorical Features: (카테고리형 Feature)

    카테고리형 변수(Categorical Variable)은 그 값으로 두개 이상의 카테고리를 가지고 각각의 값으로 feature가 카테고리화 될 수 있습니다.
    예를 들어 성별은 두개의 카테고리(남성과 여성)를 가진 카테고리형 변수입니다. 이런 변수는 순서를 부여할 수가 없습니다.
    다른 말로 명목변수(Nomial Variable) 라고도 합니다.

- 데이터셋의 Categorical Feature : Sex, Embarked
  • Ordinal Features: (순서형 Feature)

    : 순서형 변수(Ordinal Variable)은 카테고리형 변수와 비슷하지만, 변수 안 각 값들간 상대적인 순서, 분류를 부여할 수 있다는 점이 다릅니다.
    가령, Tall, Medium, Short의 값을 가진 Height와 같은 feature는 순서형 변수입니다. 이 변수 안에서 우리가 상대적인 분류가 가능하기 때문입니다.

- 데이터셋의 Ordinal Feature : Pclass
  • Continuous Features: (연속형 Feature)

    : 어떤 변수가 특정 두 지점, 혹은 최댓값과 최솟값 사이에 어떤 값이든 가질 수 있다면 그 변수는 연속형입니다.

 - 데이터셋의 Continuous Feature : Age

Feature 분석하기

Sex → Categorical Feature

In [7]:
data.groupby(['Sex','Survived'])['Survived'].count().to_frame()
Out[7]:
Survived
Sex Survived
female 0 81
1 233
male 0 468
1 109
In [8]:
f, ax = plt.subplots(1, 2, figsize=(15,6))

data[['Sex', 'Survived']].groupby(['Sex']).mean().plot.bar(ax=ax[0])
ax[0].set_title('Survived vs Sex')

sns.countplot('Sex', hue='Survived', data=data, ax=ax[1])
ax[1].set_title('Sex : Survived vs Dead')

plt.show()

흥미로운 결과 입니다. 남자 탑승객의 수가 여자 탑승객의 수보다 훨씬 많습니다.
그렇지만 여자 생존 탑승객의 수가 남자 생존 탑승객의 수보다 거의 두배 많습니다.
여성의 생존률은 약 75% 정도인데 반해 남자의 생존률은 18~19% 정도입니다.

때문에 성별은 모델링에 매우 중요한 feature일 것입니다.
하지만 이것이 최선일지 다른 feature들을 살펴보겠습니다.

Pclass --> Ordinal Feature

In [9]:
pd.crosstab(data.Pclass, data.Survived, margins = True).style.background_gradient(cmap='summer_r')
Out[9]:
Survived 0 1 All
Pclass
1 80 136 216
2 97 87 184
3 372 119 491
All 549 342 891
In [10]:
f, ax = plt.subplots(1, 2, figsize= (15,6))

data['Pclass'].value_counts().plot.bar(color=['#CD7F32', '#FFDF00', '#D3D3DE'], ax=ax[0])
ax[0].set_title('Number of Passengers by Pcass')
ax[0].set_ylabel('')

sns.countplot('Pclass', hue='Survived', data=data, ax=ax[1])
ax[1].set_title('Plcass : Survived vs Dead.')
Out[10]:
Text(0.5, 1.0, 'Plcass : Survived vs Dead.')

돈으로 모든것을 살 수 없다고 흔히들 말하지만 Pclass 1 의 생존자가 구조 시에 매우 우선 순위에 있었던 것 같습니다.
Pclass 3의 탑승객 수가 훨씬 많았지만, 생존자 비율은 25% 정도로 매우 낮습니다.

Pclass 1의 생존률은 63%, Pclass 2의 생존률은 48% 정도입니다. 결국, 돈과 지위가 중요한 요소로 작용한 듯 합니다.

다른 흥미로운 점을 찾기 위해 더 들어가보겠습니다. 이번에는 Sex와 Pclass를 함께 두고 생존률을 체크해보겠습니다.

In [11]:
pd.crosstab([data.Sex, data.Survived], data.Pclass, margins = True).style.background_gradient(cmap='summer_r')
Out[11]:
Pclass 1 2 3 All
Sex Survived
female 0 3 6 72 81
1 91 70 72 233
male 0 77 91 300 468
1 45 17 47 109
All 216 184 491 891
In [12]:
sns.factorplot('Pclass', 'Survived', hue='Sex', data=data)

plt.show()

이번 케이스에서는 Categorical Value를 쉽게 보기 위해 Factor Plot을 사용했습니다.

CrossTab과 FactorPlot을 보면 Pclass 1 여성 탑승객의 생존률이 95~96% 가량으로 사망자는 3명 정도만 있습니다.

그리고 Pclass와 무관하게, 여성이 구조에 있어서 우선 순위에 있었습니다. 남성의 경우 Pclass 1라도 생존률은 매우 낮습니다.

Pclass 또한 중요한 feature 로 보입니다. 다른 feature를 또 분석해보겠습니다.

Age → Continuous Feature

In [13]:
print('Oldest Passenger was of: ', data['Age'].max(),'Years')
print('Youngest Passeger was of: ', data['Age'].min(),'Years')
print('Average Age on the ship: ', data['Age'].mean(), 'Years')
Oldest Passenger was of:  80.0 Years
Youngest Passeger was of:  0.42 Years
Average Age on the ship:  29.69911764705882 Years
In [14]:
f, ax = plt.subplots(1, 2, figsize = (15,6))

sns.violinplot('Pclass', 'Age', hue='Survived', data=data,split = True, ax=ax[0])
ax[0].set_title('Pclass and Age vs Survived')
ax[0].set_yticks(range(0,110,10))

sns.violinplot('Sex', 'Age', hue='Survived', data=data, split = True,  ax=ax[1])
ax[1].set_title('Sex and Age vs Survived')
ax[1].set_yticks(range(0,110,10))

plt.show()

관찰결과 :

1) Pclass 등급이 낮아짐(1 to 3)에 따라 어린이의 수는 증가하고, 10세 이하의 탑승객 수는 Pclass 수와 관계 없이 좋아보입니다.

2) 20-50세 사이의 Pclass 1 탑승객 생존률은 높고, 여성의 경우에는 더욱 높습니다.

3) 남성은 연령이 증가할수록 생존 확률이 줄어듭니다.

앞에서 본 것처럼, Age Feature는 177개의 Null 값을 가지고 있습니다.
이 값들을 데이터 셋의 평균 값으로 대체할 수 있습니다.

하지만 사람들의 연령은 많고 다양합니다. 우리는 자칫 4세 아이의 연령에게 평균 연령 29세를 부여할 수도 있습니다.
승객이 어떤 연령대에 속했는지 알 수 있는 방법이 없을까요?

우리는 이를 위해 Name Feature를 체크해볼 수 있습니다. Name을 보면 Mr와 Mrs 와 같은 salutation이 있습니다.
그렇기 때문에 Mr와 Mrs 의 평균 값을 각각의 그룹에 부여할 수 있습니다.

Name을 통해 Feature를 추출할 수 있습니다.

In [15]:
data['Initial'] = 0
data['Initial'] = data.Name.str.extract('([A-aZ-z]+)\.') # salutation을 추출합니다.

정규표현식 ( [A-Za-z]+). 를 사용했습니다. 이 정규표현식은 A-Z 또는 a-z 사이의 문자열과 그 뒤에 있는 .(dot)을 찾아냅니다.
이것으로 Name에서 Salutation을 추출했습니다.

In [16]:
pd.crosstab(data.Initial, data.Sex).T.style.background_gradient(cmap='summer_r') # 성별에 따른 Initial 체크
Out[16]:
Initial Capt Col Countess Don Dr Jonkheer Lady Major Master Miss Mlle Mme Mr Mrs Ms Rev Sir
Sex
female 0 0 1 0 1 0 1 0 0 182 2 1 0 125 1 0 0
male 1 2 0 1 6 1 0 2 40 0 0 0 517 0 0 6 1

Miss를 나타내는 Mlle, Mme와 같은 잘못 적힌 Initial 이 있습니다. 이 값들을 Miss 등의 다른 값들로 대체하겠습니다.

In [17]:
data['Initial'].replace(['Mlle','Mme','Ms','Dr','Major','Lady','Countess','Jonkheer','Col','Rev','Capt', 'Sir','Don'],
                                  ['Miss','Miss','Miss','Mr','Mr','Mrs','Mrs','Other','Other','Other','Mr','Mr','Mr'], inplace=True)
In [18]:
data.groupby('Initial')['Age'].mean() # Initial 에 따른 평균연령 체크
Out[18]:
Initial
Master     4.574167
Miss      21.860000
Mr        32.739609
Mrs       35.981818
Other     45.888889
Name: Age, dtype: float64

연령 NaN 채우기

In [19]:
## 평균의 올림 값들로 NaN 값에 할당
data.loc[(data.Age.isnull()) & (data.Initial == 'Mr') , 'Age'] = 33
data.loc[(data.Age.isnull()) & (data.Initial == 'Mrs'), 'Age'] = 36
data.loc[(data.Age.isnull()) & (data.Initial == 'Master'), 'Age'] = 5
data.loc[(data.Age.isnull()) & (data.Initial == 'Miss'), 'Age'] = 22
data.loc[(data.Age.isnull()) & (data.Initial == 'Other'), 'Age'] = 46
In [20]:
data.Age.isnull().any() # Null 값들이 완전히 제거되었습니다.
Out[20]:
False
In [21]:
f, ax = plt.subplots(1, 2, figsize=(15, 6))

data[data['Survived']==0].Age.plot.hist(ax=ax[0], bins=20, edgecolor= 'black', color ='red')
ax[0].set_title('Survived = 0')
x1 = list(range(0,85,5))
ax[0].set_xticks(x1)

data[data['Survived']==1].Age.plot.hist(ax=ax[1], bins=20, edgecolor = 'black', color='green')
ax[1].set_title('Survived = 1')
x2 = list(range(0,85,5))
ax[1].set_xticks(x2)

plt.show()

관찰결과 :

1) 5세 이하의 아이들은 많이 생존했습니다. (여성과 아이 우선)

2) 가장 고연령 탑승객도 생존했습니다. (80세)

3) 가장 많은 수의 사망자가 있는 연령 그룹은 30-40세 입니다.

In [22]:
sns.factorplot('Pclass', 'Survived', col = 'Initial', data=data)
plt.show()

각 클래스와 관계없이 여성과 아이가 우선되었다는게 명확해 보입니다.

Embarked → Categorical Value

In [23]:
pd.crosstab([data.Embarked, data.Pclass], [data.Sex, data.Survived], margins =True).style.background_gradient(cmap='summer_r')
Out[23]:
Sex female male All
Survived 0 1 0 1
Embarked Pclass
C 1 1 42 25 17 85
2 0 7 8 2 17
3 8 15 33 10 66
Q 1 0 1 1 0 2
2 0 2 1 0 3
3 9 24 36 3 72
S 1 2 46 51 28 127
2 6 61 82 15 164
3 55 33 231 34 353
All 81 231 468 109 889

탑승 항구에 따른 생존확률

In [24]:
sns.factorplot('Embarked', 'Survived', data=data)
fig = plt.gcf()
fig.set_size_inches(5, 3)
plt.show()

C항구의 생존률이 약 0.55 정도로 가장 높고, S항구가 가장 낮습니다.

In [25]:
f, ax = plt.subplots(2, 2, figsize=(15, 12))

sns.countplot('Embarked', data=data, ax=ax[0,0])
ax[0,0].set_title('No. of Passengers Boarded')

sns.countplot('Embarked', hue='Sex', data=data, ax=ax[0,1])
ax[0,1].set_title('Male-Female Split for Embarked')

sns.countplot('Embarked', hue='Survived', data=data, ax=ax[1,0])
ax[1,0].set_title('Embarked vs Survived')

sns.countplot('Embarked', hue = 'Pclass', data=data, ax=ax[1,1])
ax[1,1].set_title('Embarked vs Pclass')

plt.subplots_adjust(wspace=0.2, hspace=0.2)
plt.show()

관찰결과 :

1) S에서 가장 많은 승객이 탑승했습니다. 그리고 그 탑승객들의 대부분은 Pclass 3입니다.

2) C에서 탑승한 승객은 생존률이 높아 운이 좋아보입니다. Pclass 1과 2의 승객이기 때문일거 같습니다.

3) S항구에서 다수의 부유한 사람들이 탑승한 것 같습니다. 이 그룹의 생존률은 낮은데, 약 81%가 생존하지 못한 Pclass 3의 승객이기 때문입니다.

4) Q항구에서 탑승한 승객의 95% 가량이 Pclass 3이다.

In [26]:
sns.factorplot('Pclass', 'Survived', hue = 'Sex', col = 'Embarked', data=data)
plt.show()

관찰결과 :

1) Pclass 1과 Pclass2 여성의 생존률은 Pclass와 관계 없이 거의 1입니다.

2) S 항구에서 탑승한 Pclass 3의 탑승객은 매우 운이 없는 것 같습니다. 남성과 여성의 생존률이 모두 낮습니다. (돈이 중요한 요소입니다.)

3) Q 항구에서 탑승한 남성이 제일 불운해 보입니다. 그들 대부분이 Pclass 3 탑승객이기 때문입니다.

Embarked 의 NaN 채우기

대부분의 탑승객이 S에서 탑승했기 때문에 S로 채워주겠습니다.

In [27]:
data['Embarked'].fillna('S', inplace = True)
In [28]:
data['Embarked'].isnull().any() # NaN이 모두 제거되었습니다.
Out[28]:
False

SibSp → Discrete Feature

이 Feature는 탑승객이 혼자인지 아니면 가족과 함께 탔는지를 나타냅니다.

Sibling → 형제자매, 의붓형제자매

Spouse → 배우자

In [29]:
pd.crosstab([data.SibSp], data.Survived).style.background_gradient(cmap='summer_r')
Out[29]:
Survived 0 1
SibSp
0 398 210
1 97 112
2 15 13
3 12 4
4 15 3
5 5 0
8 7 0
In [30]:
f, ax = plt.subplots(1, 2, figsize=(15,6))

sns.barplot('SibSp', 'Survived', data=data, ax=ax[0])
ax[0].set_title('SibSp vs Survived')

sns.factorplot('SibSp', 'Survived', data=data, ax=ax[1])
ax[1].set_title('SibSp vs Survived')

plt.close(2)
plt.show()