본문 바로가기
Tensorflow

[Kaggle] Titanic: Machine Learning from Disaster (1)

by 청양호박이 2020. 3. 5.

가끔 생각날때, 지금까지 정리해 본 모델을 가지고 예측모델 및 분석 대회 플랫폼인 Kaggle을 이따금 풀어보겠습니다. Kaggle은 예전에는 독자적인 회사였으나, 2017년 3월에 구글에 인수가 되었습니다. 여기에는 크게 Compete모드와 Data모드가 있으며, 기업이 될 수 있고 혹은 단체에서 문제를 등록하면 Data Scientist들이 이를 해결하기 위한 모델을 개발하고 경쟁하는 장 입니다. 경우에 따라서는 보상이 걸리기도 합니다.

 

지난번에 Logistic Regression을 알아보았기 때문에, 해당 모델로 해결이 가능한 가장 초급적인 문제를 풀어보고자 합니다. 해당문제는 이미 많은 사람들이 풀어본 문제이며, 입문문제로 꼽히기도 합니다.

바로 Titanic 승객들의 정보를 토대로 생존여부를 예측하는 문제입니다. 앞으로 문제는 아래의 단계로 분석해서 모델을 적용하겠습니다.

 

  • 데이터 불러오기
  • Train Data의 컬럼 종류 및 값 유형 분석
  • 결측치 및 문자형 데이터 처리
  • 상관관계가 있을만한 항목을 기준으로 학습에 사용할 컬럼 선정
  • Train Data 정리하기(One-Hot Encoding포함)
  • 모델선택 및 구성
  • 학습 및 평가
  • Test Data 적용 및 제출

 

 

1. 데이터 불러오기


Kaggle에서 제공하는 Train, Test 데이터는 csv 혹은 sql로 제공이 됩니다. 데이터를 받는 방법은 아래처럼 Data Tab을 선택하고 Download All을 해주면 됩니다.

 

그럼 이제 슬슬 Jupyter Notebook을 켜고 시작해 보겠습니다. 우선 자주 사용하는 패키지를 import합니다. 저는 보통 데이터의 시각화를 위해 matplot과 seaborn까지만 하고 분석을 시작합니다.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

그 다음은 다운받은 csv를 pandas의 DataFrame으로 read해주고, row와 column을 확인합니다.

train = pd.read_csv('train.csv')
print('row count : ', len(train))
print(train.columns)

#################################
row count :  891
Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp',
       'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'],
      dtype='object')

이 csv는 [891 rows x 12 columns]의 데이터 입니다.

 

 

2. 컬럼 종류 및 값 유형 분석


위에 12개의 컬럼이 있는 것을 확인했습니다. 각 컬럼이 의미하는 바는 당연히 Kaggle문제에서 제공을 해 줍니다. 확인하면서 해당 컬럼의 데이터 type이 연속형인지 범주형인지는 판단하여 기재하겠습니다.

컬럼명 내용 비고 타입 활용여부
Survived 생존여부 0 = No, 1 = Yes 범주형 -
Pclass 티켓등급 1st, 2nd, 3rd 범주형 Yes
Name 이름   - No
Sex 성별   범주형 Yes
Age 나이   연속형 No (null 과다)
SibSp 형제자매, 배우자 수   연속형 No
Parch 부모/자식 수   연속형 No
Ticket 티켓번호   - No
Fare 요금   연속형 No
Cabin 객실번호   - No
Embarked 탑승항구 C = Cherbourg, Q = Queenstown, S = Southampton 범주형 Yes

우선 범주형으로 판단되는 항목들의 데이터 형태를 확인합니다. 여러가지가 있겠지만, 값과 개수를 파악하기에는 value_counts( )를 사용합니다.

print(train['Survived'].value_counts())
print(train['Sex'].value_counts())
print(train['Pclass'].value_counts())
print(train['Embarked'].value_counts())

#######################################
0    549
1    342
Name: Survived, dtype: int64

male      577
female    314
Name: Sex, dtype: int64

3    491
1    216
2    184
Name: Pclass, dtype: int64

S    644
C    168
Q     77
Name: Embarked, dtype: int64

이 결과로 미루어보아, 우선 Sex와 Embarked는 String으로 되어있기 때문에, 나중에 모델에 넣어서 돌리기 위해서는 수치화 작업이 필요하겠다는 것을 알 수 있습니다 또한, 값의 종류가 3가지 이상인 항목들은 좀 더 나은 성능을 위해서 one-hot encoding이 필요하다는 것도 추가로 알 수 있습니다.

 

그렇다면 각 항목이 생존에 어떤 연관이 있는지 시각화를 해보겠습니다.

 

[Sex, Pclass]

f, ax = plt.subplots(1, 2, figsize=(14,6))
sns.countplot(x='Sex', hue='Survived', data=train, ax=ax[0])
ax[0].set_title('Survived by Sex')
sns.countplot(x='Pclass', hue='Survived', data=train, ax=ax[1])
ax[1].set_title('Survived by Pclass')
plt.show()

Sex의 경우는 male(남성)이 탑승수는 많은데 비해서 Survived한 비율은 상당히 적습니다. 그리고 Pclass의 경우는 3급에 타는 사람이 절대적으로 많았지만, 실제로 Survived한 비율, 수를 보자면 퍼스트클래스 우선적으로 생존이 되었다는 것을 확인할 수 있습니다.

 

실제로 수치로 확인해도...

pd.crosstab([train['Sex'], train['Survived']], train['Pclass'], margins=True, margins_name='Total').style.ba

female(여성)의 Survived수는 male(남성)에 비해서 2배가량 높으며, Pclass에 따라서 Survived도 차이가 남을 확인할 수 있습니다. 따라서 이 두가지는 학습에 유효한 정보로 판단이 가능합니다.

 

[Embarked]

f, ax = plt.subplots(2, 2, figsize=(14,12))
sns.countplot(x='Embarked', data=train, ax=ax[0,0])
ax[0,0].set_title('Embarked Status')
sns.countplot(x='Embarked', hue='Survived', data=train, ax=ax[0,1])
ax[0,1].set_title('Survived by Embarked')
sns.countplot(x='Embarked', hue='Sex', data=train, ax=ax[1,0])
ax[1,0].set_title('Sex by Embarked')
sns.countplot(x='Embarked', hue='Pclass', data=train, ax=ax[1,1])
ax[1,1].set_title('Pclass by Embarked')
plt.show()

해당 결과로 미루어보면, 지역별로 탑승자의 수가 많이 차이가 나지만 생존율로 본다면 Cherbourg에서 탄 승객들의 비율이 높다는 것을 확인할 수 있습니다. 그래서 다른요인인 Sex와 Pclass로 비교해보니, 여성의 비율 및 퍼스트클래스의 비율이 앞도적으로 높아서 이런 결론이 나옴을 알 수 있습니다. 따라서 탑승항구에 따라서 지역별 부의 차이가 있음과 이에 따라 생존에 영향이 미칠 수 있기 때문에, 학습에 유효한 정보로 추가합니다.

 

[Age]

위급사항이 있던, 평상시 던 노약자/어린이/여성은 우대대상입니다. 당연히 재난에 따른 구호 순위도 마찬가지 였을 것입니다. 따라서 Age도 중요한 학습 정보일 것입니다. 하지만, 이 데이터는 연속형 데이터로 약간의 범주화가 필요합니다. 그래야 정확한 활용이 가능할 것 입니다. 하지만 활용이 가능한지 한번 살펴보겠습니다. 왜냐하면 csv파일을 보다가 값이 없는 row를 꽤 봤기 때문이죠.

print(len(train), train['Age'].isnull().sum())

##############################################
891 177

 

확인해보니, 대략 25% 정도가 null인 항목이였습니다. 따라서 이번에는 해당 항목을 제외하겠습니다.

 

 

3. 결측치 및 문자형 데이터 처리


위에서 Age의 경우도 null로 되어있는 필드가 꽤 되어있었습니다. 실제로 모델을 만들어 학습을 진행할때 결측치가 있으면 안됩니다. 따라서 결측치를 보정을 해야합니다. 또한, 머신러닝... 말그대로 기계가 학습을 하기 때문에 사람이 이해하는 문자형으로 되어있는 데이터는 숫자로 변환을 해야합니다.

  • 결측치 보정
  • 문자형 데이터 숫자형으로 변환 (male/female, S/Q/C)

[결측치 보정]

print(train.isnull().sum())

############################
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

Embarked의 경우 isnull인 데이터가 2개가 있습니다. 2개는 작은 비율에 속하기 때문에, Embarked의 원소중에 많은 항목으로 대체해 주겠습니다.

print(train['Embarked'].value_counts())

train['Embarked'] = train['Embarked'].fillna(value='S')
#train['Embarked'].fillna(value='S', inplace=True)

print(train.isnull().sum())

#######################################################
PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age            177
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         0
dtype: int64

다음과 같이 fillna( )메서드를 통해서 'S'로 채워주고, 2가지 방법으로 해당 컬럼에 덮어써 줍니다. 그리고 다시 확인해 보면, Embarked에는 isnull( )항목이 0개가 되었습니다.

 

[숫자형 변환]

male      577
female    314
Name: Sex, dtype: int64

S    644
C    168
Q     77
Name: Embarked, dtype: int64

위와 같이 Sex와 Embarked는 String의 데이터로 구성이 되어있습니다. 따라서 숫자형으로 바꿔주겠습니다.

train['Sex'] = train['Sex'].map({'male':0, 'female':1})
train['Embarked'] = train['Embarked'].map({'S':0, 'C':1, 'Q':2})

print(train['Sex'].value_counts())
print(train['Embarked'].value_counts())

#######################################
0    577
1    314
Name: Sex, dtype: int64

0    646
1    168
2     77
Name: Embarked, dtype: int64

map( )메서드를 통해서 원소를 해당 값으로 바꿔줍니다. 이때 map안에는 dict 형으로 변경방식을 지정해 주면 됩니다.

 

 

4. 학습에 사용할 컬럼 선정


위에서 살펴본 항목들에 대해서 최종적으로 학습에 사용할 컬럼이 맞는지 확인해 보도록 하겠습니다. 상관관계 heatmap을 통해서 알아보고자 합니다. heatmap이란 열을 뜻하는 히트와 지도를 뜻하는 맵을 결합시킨 단어로, 각 컬럼의 상관관계를 열분포 형태의 비쥬얼한 그래픽으로 출력하는 방식입니다.

heat_columns = train[['Survived', 'Pclass', 'Sex', 'Embarked']]

colormap = plt.cm.RdBu
sns.heatmap(heat_columns.astype(float).corr(), square=True, cmap=colormap, annot=True)
plt.show()

다음과 같이, 학습을 결정한 컬럼을 선택하고 해당 데이터의 corr( ) 상관계수를 구하고 그 결과를 heatmap으로 그려주는 방식입니다. 상관계수는 pandas에서 메서드로 제공해주니 안심하셔도 됩니다. 하지만 상관관계의 뜻은 알아야하는데, 두 컬럼의 통계적인 관계가 0< <=1 이면 양의 상관관계가... -1<= <0 이면 음의 상관관계가... 마지막으로 0이면 관계가 없음을 의미합니다.

다음과 같이 보면, Sex와 Survived는 양의 상관관계가 크고, Pclass와 Survivied는 음의 상관관계가 크다는 것을 확인할 수 있습니다. 그리고 나머지 항목도 다 Survived와 어느정도 관계가 있다고 보여지기 때문에 모두 학습에 사용하기로 합니다.

 

 

5. Train Data 정리하기


이제 실제로 학습에 사용할 Train Data로 현재의 데이터를 정리하겠습니다. 정리단계는 아래와 같습니다.

  • 범주형 데이터 중 분류기준이 많아 0과 1이 아닌 더 큰수까지 포함되는 경우, One-Hot Encoding을 적용
  • 사용하지 않는 column은 DataFrame객체에서 삭제
  • 독립변수와 종속변수를 분리하고 ndarray로 변환
  • train.csv 내에서 train용 test용 데이터 random 분리

[범주형 데이터 One-Hot Encoding]

pd.get_dummies(train, columns=['Embarked'], prefix='Embarked')

###############################################################
	PassengerId	Survived	Pclass	Name	Sex	Age	SibSp	Parch	Ticket	Fare	Cabin	Embarked_0	Embarked_1	Embarked_2
0	1	0	3	Braund, Mr. Owen Harris	0	22.0	1	0	A/5 21171	7.2500	NaN	1	0	0
1	2	1	1	Cumings, Mrs. John Bradley (Florence Briggs Th...	1	38.0	1	0	PC 17599	71.2833	C85	0	1	0
2	3	1	3	Heikkinen, Miss. Laina	1	26.0	0	0	STON/O2. 3101282	7.9250	NaN	1	0	0
3	4	1	1	Futrelle, Mrs. Jacques Heath (Lily May Peel)	1	35.0	1	0	113803	53.1000	C123	1	0	0
4	5	0	3	Allen, Mr. William Henry	0	35.0	0	0	373450	8.0500	NaN	1	0	0
...	...	...	...	...	...	...	...	...	...	...	...	...	...	...
886	887	0	2	Montvila, Rev. Juozas	0	27.0	0	0	211536	13.0000	NaN	1	0	0
887	888	1	1	Graham, Miss. Margaret Edith	1	19.0	0	0	112053	30.0000	B42	1	0	0
888	889	0	3	Johnston, Miss. Catherine Helen "Carrie"	1	NaN	1	2	W./C. 6607	23.4500	NaN	1	0	0
889	890	1	1	Behr, Mr. Karl Howell	0	26.0	0	0	111369	30.0000	C148	0	1	0
890	891	0	3	Dooley, Mr. Patrick	0	32.0	0	0	370376	7.7500	NaN	0	0	1
891 rows × 14 columns

와 같이 Embarked가 Embarked_0 / Embarked_1 / Embarked_2 컬럼이 새로 생기고, 각 값에따라 해당 필드에 값이 0 혹은 1로 나타나게 됩니다. 아참!! 이렇게 해주면 실제 train에는 반영이 안되기 때문에, 아래과 같이 꼭 넣어줍니다.

train = pd.get_dummies(train, columns=['Embarked'], prefix='Embarked')

print(train.columns)

################################
Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp',
       'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked_0', 'Embarked_1',
       'Embarked_2'],
      dtype='object')

[사용하지 않는 컬럼 삭제]

해당 방식은 2가지가 있습니다. heatmap을 그릴때 heat_column을 생성할때, DataFrame에 원하는 컬럼을 [ ] 리스트로 묶어서 뽑아 사용했었습니다. 이번에는 drop( )메서드를 통해서 사용하지 않을 컬럼을 뽑아내보겠습니다.

train.drop(labels=['PassengerId', 'Name', 'Age', 'SibSp', 'Parch', 'Ticket', 'Fare', 'Cabin' ], axis=1, inplace=True)
print(train.columns)

#############################
Index(['Survived', 'Pclass', 'Sex', 'Embarked_0', 'Embarked_1', 'Embarked_2'], dtype='object')

[독립변수, 종속변수 분리]

x_train = train.drop(labels='Survived', axis=1).values
y_train = train['Survived'].values

print(x_train)

print(type(x_train), type(y_train))

#######################################
[[3 0 1 0 0]
 [1 1 0 1 0]
 [3 1 1 0 0]
 ...
 [3 1 1 0 0]
 [1 0 0 1 0]
 [3 0 0 0 1]]
<class 'numpy.ndarray'> <class 'numpy.ndarray'>

독립변수에는 Survived만 drop하고, 종속변수는 Survived만 선택하겠습니다. 또한, values를 사용해서 DataFrame이 아닌 실제 학습에 사용할 ndarray로 데이터를 변환합니다.

 

[train용 test용 데이터 random분리]

보통 데이터가 주어지면, 모델학습을 위한용도와 학습된 모델의 검증용도로 데이터를 분리해서 사용합니다. 7:3 정도로 하는데, 이를 분리해주는 패키지를 적용해서 구현해보겠습니다.

from sklearn.model_selection import train_test_split
x_tr, x_te, y_tr, y_te = train_test_split(x_train, y_train, test_size=0.3, random_state=2)

print(x_tr.shape, y_tr.shape, x_te.shape, y_te.shape)

######################################################
(623, 5) (623,) (268, 5) (268,)

다음과 같이, sklearn에서 분리기 메서드를 제공합니다. 이를 통해 test_size와 random을 정해주면 알아서 random하게 샘플링해서 train / test용 x, y를 리턴해줍니다. 리턴순서는 x_train, x_test, y_train, y_test 입니다.

 

이제, 학습에 필요한 데이터 추출도 모두 끝이 났습니다. 바로 모델을 만들어 넣으면 되는 상태입니다. 그럼 이제 모델을 만들어야겠네요.

 

이 부분은 다음시간에 바로 이어서 해보겠습니다. 글이 너무 길어지니 힘들어요...ㅠㅠ

 

-Ayotera Lab-

댓글