아마 모두가 타이타닉호 침몰 사건에 대해 알고 있을 것이다. 혹은, 이 이야기를 바탕으로한 타이타닉 영화가 더 친숙할 수 있겠다. 타이타닉 영화를 처음봤을 때 그 순간을 잊지못하며, 영화 주인공인 잭과 로즈가 주던 감정들은 로봇이라 불리는 나에게도 확 다가왔었다. 잠시 사랑얘기는 접어두고, 타이타닉호는 그 당시 가장 안전한 배로 유명했는데, 침몰하여 더 큰 이슈가 되었는지도 모르겠다. 1912년 4월 15일, 타이타닉호는 잭과 로즈를 태우고 운항중 빙산에 충돌하여 침몰하게 되었다. 영화에서 나온 상황처럼 구명보트는 모든 사람들을 구조하기에 충분하지 않았고, 그 결과로 2224명중 1502명이 사망했다. 어쩌면 생존자는 천운을 받은 사람이라 생각될 수 있을 정도로 많은 사망자가 발생한 사건이었다. 정말 천운에 운명이 정해진걸까? 정말 잭은 죽을 수 밖에 없던 운명인걸까?
아마 데이터를 한 번이라도 공부 해본 사람은 타이타닉 데이터를 모를 수 없을 것이다. 타이타닉 데이터는 당시 타이타닉호 승객들의 정보를 담고 있다. 여담으로, 잭과 로즈의 정보는 없었다. 이 데이터를 통해서, 천운이아닌 생존여부에 영향을 줄 수 있는 요인들을 파악해 생존여부를 예측해보려고 한다. 사실, 이 예측은 Kaggle에서 진행되었으며, 이미 수 많은 사람들이 도전 하였고 심지어는 예측결과가 100%인 모델을 만들기도 했다. 예측결과가 실력을 나타내는 지표라고 말 할 수 없지만, 내가 가진 실력은 얼마인지 평가해 볼 수 있지 않을까? 그럼 같이 시작해보자.
타이타닉 데이터
앞에서 말했듯이, 타이타닉 데이터는 당시 타이타닉호의 승객들의 정보를 담고 있다. 다음 표를 통해 데이터를 직접 보자.
먼저 train data, test data로 구분되며, 총 1309명의 정보가, 12개의 특성으로 나뉘어 담겨 있으다. 그 중에 Survived를 통해서 생존여부를 확인을 할 수 있고, 나의 예측 Target이 된다. train data를 통해서 예측 모델을 훈련하여, test data를 통해 예측결과를 확인하게 된다. 그러면 나머지 특성들에 대해 좀 더 알아보자.
Spouse = husband, wife (mistresses and fiancés were ignored)
Parch : 타이타닉호 밖의 # of parent + child
Parent = mother, father
Child = daughter, son, stepdaughter, stepson
Some children travelled only with a nanny, therefore parch=0 for them.
Ticket : 티켓 고유번호
Fare : 운임
Cabin : 객실 번호
embarked : 승선항
C = Cherbourg
Q = Queenstown
S = Southampton
데이터
간단한 데이터지만, 여러가지 활용해 볼 수 있는 다양한 특성들이 있다. 이제 생존여부에 관련이 있는 특징들이 있나 찾아봐야하는데 그전에 지금 우리가 가진 데이터가 잘 구성되어 있는지, 결측치는 없는지 세세히 확인 할 필요가 있다. 그렇지 않으면, 예측 모델 자체가 의미가 없어진다.
SibSp, Parch는 위의 설명을 보면 타이타닉호에 같이 탑승하지 않은 가족들의 수를 의미하기 때문에, 두 feature를 합쳐서 하나의 가족 수를 나타내는 family라는 특징을 새로 정의하자.
# SibSP 와 Parch를 합하여 총 가족수를 계산
train['family'] = train.SibSp + train.Parch
Age의 경우 전체 891명의 데이터 중 714명의 데이터를 담고 있다. 즉, 결측치가 존재한다는 뜻이다. 결측치를 어떻게 다루면 좋을지 생각해보자.
탑승객들의 나이분포를 보면 대부분의 사람들이 20 ~ 40대에 분포하고 있음을 알 수 있다. 가장 일반적인 평균으로 결측치를 채워넣어도 될 것 같다는 생각이 든다.
# 나이 결측치를 평균으로
train['Age'] = train.Age.replace(np.NaN, train.Age.mean())
Cabin은 객실 번호를 나타내는데, 결측치의 비율이 엄청 높음을 알 수 있다. 따라서 Cabin은 의미가 없다 생각하여 삭제하자. 또, 승객들의 고유값인 PassengerId, Name, Ticket은 생존여부와 관련이 없어서 삭제한다. 마지막으로 SibSp와 Parch를 사용하여 새로운 family를 생성하였기 때문에 삭제하자.
위의 최종데이터가 get_dummies를 통과하고 나면, 다음과 같이 모두 수치형인 데이터로 변환하게 된다. 각 범주형 데이터에 해당하는 value들로 열을 생성하여, 그 value에 해당하면 1 해당하지 않으면 0을 생성함을 알 수 있다.
Survived
Age
Fare
family
Pclass_2nd
Pclass_3rd
Sex_male
Embarked_Queenstown
Embarked_Southampton
0
22.0
7.2500
1
0
1
1
0
1
1
38.0
71.2833
1
0
0
0
0
0
1
26.0
7.9250
0
0
1
0
0
1
1
35.0
53.1000
1
0
0
0
0
1
0
35.0
8.0500
0
0
1
1
0
1
다음의 과정으로는 몇개의 Feature를 사용할 것인가 이다. 여기서 사용한 방법은 SelectKBest를 통하여, 필요한 feature의 갯수를 확인하자.
SelectKBest를 사용하기 위해서는 Hyper Parameter인 k를 우리가 직접 정해줘야 하는데 이를 최적화 하는 방법을 다룬 StackOverFlow를 참고하여 다음 최적화 과정을 실행하였다.
# Feature Selection
from sklearn.feature_selection import SelectKBest
from sklearn.model_selection import GridSearchCV
from sklearn.tree import DecisionTreeClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.pipeline import Pipeline
cols = train_encoded.columns.tolist()
labels = 'Survived'
features = [col for col in cols if col not in labels]
param_grid = [{
'classify': [DecisionTreeClassifier()], #first option use DT
'kbest__k': range(1, 9), #range of n in SelectKBest(n)
#classifier's specific configs
'classify__criterion': ('gini', 'entropy'),
'classify__min_samples_split': range(2,10),
'classify__min_samples_leaf': range(1,10)
},
{
'classify': [GaussianNB()], #second option use NB
'kbest__k': range(1, 9), #range of n in SelectKBest(n)
}]
pipe = Pipeline(steps=[("kbest", SelectKBest()), ("classify", DecisionTreeClassifier())]) #I put DT as default, but eventually the program will ignore this when you use GridSearchCV.
Here the might of GridSearchCV working, this may takes time especially if you have more than one classifiers to be evaluated
Find your best params if you want to use optimal setting later without running the grid search again (by commenting all these grid search lines)
print(grid.bestparams)
>{'classify': DecisionTreeClassifier(ccp_alpha=0.0, class_weight=None, criterion='entropy',
max_depth=None, max_features=None, max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=7, min_samples_split=9,
min_weight_fraction_leaf=0.0, presort='deprecated',
random_state=None, splitter='best'), 'classify__criterion': 'entropy', 'classify__min_samples_leaf': 7, 'classify__min_samples_split': 9, 'kbest__k': 8}
Feature 수가 8개로 많지 않아, 의미가 있나 싶지만 혹시나 하는 마음에 적용을 해봤으나 역시 모든 feature를 사용하는것이 좋다는것을 다시 한번 확인할 수 있었다. 만약 정말 많은 feature수를 상대해야 한다면, 꼭 필요한 과정이지 않을까 생각이 된다.
3. 위의 데이터 설명을 통해 예측할 수 있겠지만 각 특성들은 서로 다른 단위를 가지고 있다. 예를들어, `family`는 명을 단위로 할 것이고, `Fare`는 돈의 단위 일 것이다. 이를 해결하기 위해서 `StandardSclaer`를 통해서 평균이 0, 표준편차가 1인 값들로 단위를 통일 시켜주자.
```py
# Scale
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train, y_train)
X_test_scaled = scaler.transform(X_test)
Scaling을 통하여 위와 같은 Model에 학습시키기전 최종데이터이다. 지금까지 범주형 데이터를 숫자형 데이터로, 몇개의 특성을 선택 할지, 데이터 값들의 단위까지 통일 시켜주어서 좀 더 학습에 도움이 되도록 하였다. 앞 서 선택한 Logistic Regression 모델을 설정해보고, 학습을 통해 예측까지 진행해보자.
# Logistic Model
from sklearn.linear_model import LogisticRegressionCV
logistic = LogisticRegressionCV(cv = 5, random_state=1)
logistic.fit(X_train_scaled, y_train)
y_pred = logistic.predict(X_test_scaled)
Logistic Regression 모델을 정의한 후, train 데이터를 통해서 학습 시키고 test 데이터의 예측값 까지 확인 해보았다. 마지막으로 결과를 확인하기 전에, 다중공산성을 확인해보자. 이는 Feature들 간의 상관관계를 확인해보는 단계이다. 만약, feature들의 상관관계가 높다면 지금까지의 결과가 무의미 해질 수 있다. 그렇기 때문에, 꼭 필요한 과정이라 생각이 된다.
다중공산성을 잘 나타내어주는 Variance Inflation Factor(VIF)를 통해서 확인해보자. 만약 VIF의 값이 10 보다 높은 feature들이 존재한다면, 제외를 하든 변형을 해주든 예측을 하기전에 feature 선택을 다시 해야 할 필요가 있다.
# VIF check
df_X_train_selected = pd.DataFrame(X_train_scaled, columns = features)
from statsmodels.stats.outliers_influence import variance_inflation_factor
# the independent variables set
X = df_X_train_selected
# VIF dataframe
vif_data = pd.DataFrame()
vif_data["feature"] = X.columns
# calculating VIF for each feature
vif_data["VIF"] = [variance_inflation_factor(X.values, i)
for i in range(len(X.columns))]
feature VIF
0 Age 1.218971
1 Fare 1.760508
2 family 1.217117
3 Pclass_2nd 2.076647
4 Pclass_3rd 2.757946
5 Sex_male 1.109791
6 Embarked_Queenstown 1.490315
7 Embarked_Southampton 1.499550
VIF의 값이 모두 10 이하이기 때문에, 다중공산성 문제가 없음을 확인하고 나의 모델링이 잘 되었음을 확인했다. 마지막으로, 캐글에 제출을 위한 데이터를 만들자.
submission['Survived'] = y_pred
submission
PassengerId
Survived
892
0
893
0
894
0
895
0
896
1
...
...
1305
0
1306
1
1307
0
1308
0
1309
0
제출 결과
나는 약 77%의 점수를 확인할 수 있었다. 내가 아는 모든 방법을 총동원했는데, 생각보다 낮은 점수라 실망스럽다. 하지만, 앞으로 배울 모델들이 배운 모델들 보다 훨씬 많기에, 미래의 나는 몇점일까 궁금해지기도 한다. 전혀 알지 못하는 Kaggle이라는 세상에, 한 발자국 내딛었다는 의미를 두고 앞으로의 남은 공부를 더 열심히 하는 동기가 되었으면 한다.
타이타닉
아마 모두가 타이타닉호 침몰 사건에 대해 알고 있을 것이다. 혹은, 이 이야기를 바탕으로한 타이타닉 영화가 더 친숙할 수 있겠다. 타이타닉 영화를 처음봤을 때 그 순간을 잊지못하며, 영화 주인공인 잭과 로즈가 주던 감정들은 로봇이라 불리는 나에게도 확 다가왔었다. 잠시 사랑얘기는 접어두고, 타이타닉호는 그 당시 가장 안전한 배로 유명했는데, 침몰하여 더 큰 이슈가 되었는지도 모르겠다. 1912년 4월 15일, 타이타닉호는 잭과 로즈를 태우고 운항중 빙산에 충돌하여 침몰하게 되었다. 영화에서 나온 상황처럼 구명보트는 모든 사람들을 구조하기에 충분하지 않았고, 그 결과로 2224명중 1502명이 사망했다. 어쩌면 생존자는 천운을 받은 사람이라 생각될 수 있을 정도로 많은 사망자가 발생한 사건이었다. 정말 천운에 운명이 정해진걸까? 정말 잭은 죽을 수 밖에 없던 운명인걸까?
아마 데이터를 한 번이라도 공부 해본 사람은 타이타닉 데이터를 모를 수 없을 것이다. 타이타닉 데이터는 당시 타이타닉호 승객들의 정보를 담고 있다. 여담으로, 잭과 로즈의 정보는 없었다. 이 데이터를 통해서, 천운이아닌 생존여부에 영향을 줄 수 있는 요인들을 파악해 생존여부를 예측해보려고 한다. 사실, 이 예측은 Kaggle에서 진행되었으며, 이미 수 많은 사람들이 도전 하였고 심지어는 예측결과가 100%인 모델을 만들기도 했다. 예측결과가 실력을 나타내는 지표라고 말 할 수 없지만, 내가 가진 실력은 얼마인지 평가해 볼 수 있지 않을까? 그럼 같이 시작해보자.
타이타닉 데이터
앞에서 말했듯이, 타이타닉 데이터는 당시 타이타닉호의 승객들의 정보를 담고 있다. 다음 표를 통해 데이터를 직접 보자.
먼저 train data, test data로 구분되며, 총 1309명의 정보가, 12개의 특성으로 나뉘어 담겨 있으다. 그 중에
Survived
를 통해서 생존여부를 확인을 할 수 있고, 나의 예측 Target이 된다. train data를 통해서 예측 모델을 훈련하여, test data를 통해 예측결과를 확인하게 된다. 그러면 나머지 특성들에 대해 좀 더 알아보자.데이터 설명
데이터
간단한 데이터지만, 여러가지 활용해 볼 수 있는 다양한 특성들이 있다. 이제 생존여부에 관련이 있는 특징들이 있나 찾아봐야하는데 그전에 지금 우리가 가진 데이터가 잘 구성되어 있는지, 결측치는 없는지 세세히 확인 할 필요가 있다. 그렇지 않으면, 예측 모델 자체가 의미가 없어진다.
Pclass
는 티켓의 등급을 의미하는데 위의 표를 보면 숫자형 데이터이다. 물론 티켓의 등급간의 차이가 의미가 있을 수 있지만, 1등급의 차이가 얼마나 차이나는지 애매하기 때문에 범주형 데이터로 바꿔주자.Embarked
는 승선항이 어디인지 알려주는 특성인데, 2개의 결측치가 존재한다. 결측치를 최빈값인 S로 설정하고, 편의를 위해 약자를 Full Name으로 바꿔주자.SibSp
,Parch
는 위의 설명을 보면 타이타닉호에 같이 탑승하지 않은 가족들의 수를 의미하기 때문에, 두 feature를 합쳐서 하나의 가족 수를 나타내는family
라는 특징을 새로 정의하자.Age
의 경우 전체 891명의 데이터 중 714명의 데이터를 담고 있다. 즉, 결측치가 존재한다는 뜻이다. 결측치를 어떻게 다루면 좋을지 생각해보자.탑승객들의 나이분포를 보면 대부분의 사람들이 20 ~ 40대에 분포하고 있음을 알 수 있다. 가장 일반적인 평균으로 결측치를 채워넣어도 될 것 같다는 생각이 든다.
Cabin
은 객실 번호를 나타내는데, 결측치의 비율이 엄청 높음을 알 수 있다. 따라서Cabin
은 의미가 없다 생각하여 삭제하자. 또, 승객들의 고유값인PassengerId
,Name
,Ticket
은 생존여부와 관련이 없어서 삭제한다. 마지막으로SibSp
와Parch
를 사용하여 새로운family
를 생성하였기 때문에 삭제하자.Fare
의 단 하나의 결측치가 존재한다. 따라서,Age
와 같이 평균으로 대치하자.이를 통해서, 간단한 작업들을 통해 최종 데이터를 아래와 같이 얻을 수 있었다.
주어진 데이터를 통해서,
family
와 같은 특성들을 만들기도 했고, 불 필요한 특성들을 삭제해 내가 원하는 특징만을 남겼다.예측 모델
나는 예측 모델을 Machine Learning 중의 지도 학습의 분류의 한 방법인 Logistic Regression Model을 활용하였다.
pd.get_dummies
메서드를 활용하였다.위의 최종데이터가
get_dummies
를 통과하고 나면, 다음과 같이 모두 수치형인 데이터로 변환하게 된다. 각 범주형 데이터에 해당하는 value들로 열을 생성하여, 그 value에 해당하면 1 해당하지 않으면 0을 생성함을 알 수 있다.SelectKBest
를 통하여, 필요한 feature의 갯수를 확인하자.SelectKBest
를 사용하기 위해서는 Hyper Parameter인 k를 우리가 직접 정해줘야 하는데 이를 최적화 하는 방법을 다룬 StackOverFlow를 참고하여 다음 최적화 과정을 실행하였다.cols = train_encoded.columns.tolist() labels = 'Survived' features = [col for col in cols if col not in labels]
X_train = train_encoded[features] y_train = train_encoded[labels]
set your configuration options
param_grid = [{ 'classify': [DecisionTreeClassifier()], #first option use DT 'kbest__k': range(1, 9), #range of n in SelectKBest(n)
}, { 'classify': [GaussianNB()], #second option use NB 'kbest__k': range(1, 9), #range of n in SelectKBest(n) }]
pipe = Pipeline(steps=[("kbest", SelectKBest()), ("classify", DecisionTreeClassifier())]) #I put DT as default, but eventually the program will ignore this when you use GridSearchCV.
Here the might of GridSearchCV working, this may takes time especially if you have more than one classifiers to be evaluated
grid = GridSearchCV(pipe, param_grid=param_grid, cv=3, scoring='f1') grid.fit(X_train, y_train)
Find your best params if you want to use optimal setting later without running the grid search again (by commenting all these grid search lines)
print(grid.bestparams)
Logistic Regression 모델을 정의한 후, train 데이터를 통해서 학습 시키고 test 데이터의 예측값 까지 확인 해보았다. 마지막으로 결과를 확인하기 전에, 다중공산성을 확인해보자. 이는 Feature들 간의 상관관계를 확인해보는 단계이다. 만약, feature들의 상관관계가 높다면 지금까지의 결과가 무의미 해질 수 있다. 그렇기 때문에, 꼭 필요한 과정이라 생각이 된다.
VIF의 값이 모두 10 이하이기 때문에, 다중공산성 문제가 없음을 확인하고 나의 모델링이 잘 되었음을 확인했다. 마지막으로, 캐글에 제출을 위한 데이터를 만들자.
제출 결과
나는 약 77%의 점수를 확인할 수 있었다. 내가 아는 모든 방법을 총동원했는데, 생각보다 낮은 점수라 실망스럽다. 하지만, 앞으로 배울 모델들이 배운 모델들 보다 훨씬 많기에, 미래의 나는 몇점일까 궁금해지기도 한다. 전혀 알지 못하는 Kaggle이라는 세상에, 한 발자국 내딛었다는 의미를 두고 앞으로의 남은 공부를 더 열심히 하는 동기가 되었으면 한다.