이전 포스트에서 타이타닉 데이터가 어떻게 구성되어 있는지 확인해보았다. 이번 포스트에서는 타이타닉 데이터를 전처리해보고, 생존자 분류 모델을 만들어보자.
타이타닉 데이터 생존자 분류 모델 만들기
- 모든 데이터 분석에서도 그렇듯 딥 러닝 모델 생성에서도 제일 우선 되는 것은 데이터 전처리다.
- 머신러닝 모델을 만들 때의 순서는 다음과 같다.
- 데이터 셋의 특징을 잘 나타낼 수 있게 전처리를 한다(Data Handling).
- 학습이 제대로 되도록 데이터 셋을 잘 쪼갠다(Train, Validation, Test).
- 목적과 데이터에 맞는 모델을 생성한다.
- 학습 후, 모델의 성능을 평가하고, 성능을 업그레이드한다.
- 이번엔 각 영역이 미치는 영향이 얼마나 큰지를 시각적으로 보도록 하겠다.
0. 데이터 불러오기
- 이전 포스트에서 만들었던 데이터를 가져오는 코드를 정리해보자.
# Import Module
import pandas as pd
import numpy as np
import os
from tensorflow.keras.layers import Dense
from tensorflow import keras
# 모든 Data를 DataFrame의 형태로 dictionary에 넣어 가지고 온다.
def import_Data(file_path):
result = dict()
for file in os.listdir(file_path):
file_name = file[:-4]
result[file_name] = pd.read_csv(file_path + "/" + file)
return result
# 해당 경로에 있는 모든 파일을 DataFrame으로 가지고 왔다.
file_path = "./Dataset"
Rawdata_dict = import_Data(file_path)
1. 데이터 전처리
- 이전 포스트에서 파악한 데이터 셋의 내용을 기반으로, 데이터 셋을 전처리해보자.
1.1. 데이터 셋 전처리가 쉽도록 한 덩어리로 만들자.
# 흩어져 있는 데이터를 모아 하나의 Rawdata로 만든다.
def make_Rawdata(dict_data):
dict_key = list(dict_data.keys())
test_Dataset = pd.merge(dict_data["gender_submission"], dict_data["test"], how='outer', on="PassengerId")
Rawdata = pd.concat([dict_data["train"], test_Dataset])
Rawdata.reset_index(drop=True, inplace=True)
return Rawdata
- pd.merge(): 두 DataFrame을 동일한 Column을 기준(열 기준)으로 하나로 합친다.
- pd.concat(): 모든 Column이 동일한 두 DataFrame을 행 기준으로 하나로 합친다.
- DataFrame.reset_index(): DataFrame의 index를 초기화한다.
Rawdata = make_Rawdata(Rawdata_dict)
Rawdata
1.2. 불필요한 Column을 제거하자.
- 생존 여부에 절대 영향을 줄 수 없는 Column을 제거하여, Feature가 두드러지도록 만들자.
- 고객의 ID(PassengerId), 고객의 이름(Name), 티켓 번호(Tiket)는 생존 여부에 영향을 줄 가능성이 거의 없다고 판단된다. 그러므로, Dataset에서 제거하자.
from copy import copy
def remove_columns(DF, remove_list):
# 원본 정보 유지를 위해 copy하여, 원본 Data와의 종속성을 끊었다.
result = copy(Rawdata)
# PassengerId를 Index로 하자.
result.set_index("PassengerId", inplace = True)
# 불필요한 column 제거
for column in remove_list:
del(result[column])
return result
- copy(Data): Data를 복사하여, 데이터의 종속성이 없는 데이터를 만들어낸다.
- DataFrame.set_index(): 특정 column을 Index로 설정한다.
- del(DataFrame[column]): DataFrame에서 해당 column을 제거한다.
remove_list = ["Name", "Ticket"]
DF_Hand1 = remove_columns(Rawdata, remove_list)
DF_Hand1
1.3. 칼럼 별 결측 값의 현황을 파악하자.
- 결측 값은 다른 행의 데이터들을 없애버릴 수 있으므로, 최우선 해결해야 할 과제다.
- 먼저, 각 칼럼 별 결측 값이 존재하는 칼럼과 그 개수를 파악하자.
# 컬럼별 결측값의 갯수 파악
>>> DF_Hand1.isnull().sum()
Survived 0
Pclass 0
Sex 0
Age 263
SibSp 0
Parch 0
Fare 1
Cabin 1014
Embarked 2
dtype: int64
- df.isnull(): DataFrame에서 결측 값(NaN)인 원소는 True로, 그렇지 않은 원소는 False로 나타낸다.
- df.sum(): DataFrame의 각 칼럼 별 합을 낸다.
- 위 결과를 보니, Cabin은 결측 값의 수가 지나치게 많아, 사용하지 않는 것이 좋다고 판단된다.
- Cabin은 객실 번호로, 객실 번호가 배에서 탈출하기 좋은 위치에 영향을 줄 수도 있다고 판단되나, 이미 이 정보를 담을 수 있는 다른 변수인 Pclass(티켓 등급), Fare(승객 요금), Embarked(기항지 위치)가 있으니, 제거해도 괜찮을 것으로 판단된다.
- Age는 총 데이터 1309개 중 263개에 해당하여, 차지하는 비중이 20%나 되지만, 생존에 큰 영향을 줄 수 있다고 판단되어, 보류하도록 하겠다.
- 결측 값은 Single Imputation으로 대체하지 않고, 일단 행 제거를 하여, 데이터의 양을 줄이는 쪽으로 방향을 잡도록 하겠다.
- 단순 대체로 평균을 넣는다거나, 의사 결정 나무, 회귀 모형을 통한 결측 값 추정이 가능하긴 하나, 일단은 이는 뒤로 미루자.
def missing_value(DF):
# Cabin 변수를 제거하자
del(DF["Cabin"])
# 결측값이 있는 모든 행은 제거한다.
DF.dropna(inplace = True)
- DataFrame.dropna(): 결측 값이 있는 행을 모두 제거한다.
# 결측값 처리
missing_value(DF_Hand1)
DF_Hand1
- 결측 값이 있는 행들을 제거하여, 총 행의 수가 1309개에서 1043개로 감소하였다.
1.4. 문자열 처리
- 머신러닝에 들어가는 Tensor에는 문자가 들어갈 수 없다.
- 모든 문자를 숫자로 바꾸도록 하자.
- 단순하게, 각 문자를 특정 숫자로 바꾸도록 하자.
- Sex: male = 0, female = 1
- Embarked: C = 0, Q = 1, S = 2
# 문자 데이터 처리
DF_Hand1["Sex"] = np.where(DF_Hand1["Sex"].to_numpy() == "male", 0, 1)
DF_Hand1["Embarked"] = np.where(DF_Hand1["Embarked"].to_numpy() == "C", 0,
np.where(DF_Hand1["Embarked"].to_numpy() == "Q", 1, 2))
>>> DF_Hand1
2. 데이터셋 분리 및 표준화
- 기본적인 데이터 전처리는 끝났으므로, 데이터셋을 Train과 Test, Label Dataset으로 분리하자.
2.1. 데이터셋 분리
- Train:Test = 7:3으로 분리해보자.
- Label Data도 분리하자.
# Label 생성
y_test, y_train = DF_Hand1["Survived"][:300].to_numpy(), DF_Hand1["Survived"][300:].to_numpy()
# Dataset 생성
del(DF_Hand1["Survived"])
X_test, X_train = DF_Hand1[:300].values, DF_Hand1[300:].values
- Series.to_numpy(), DataFrame.values 이 두 함수를 사용하면, 쉽게 array로 만들 수 있다.
>>> X_train
array([[ 1. , 0. , 27. , ..., 2. , 211.5 , 0. ],
[ 3. , 0. , 20. , ..., 0. , 4.0125, 0. ],
[ 3. , 0. , 19. , ..., 0. , 7.775 , 2. ],
...,
[ 3. , 1. , 28. , ..., 0. , 7.775 , 2. ],
[ 1. , 1. , 39. , ..., 0. , 108.9 , 0. ],
[ 3. , 0. , 38.5 , ..., 0. , 7.25 , 2. ]])
>>> y_test
array([0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0,
0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0,
1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,
1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0,
1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1,
1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1], dtype=int64)
2.2. 표준화시켜주자.
- 숫자가 큰 Age, Fare를 Train Dataset에 맞춰 최소-최대 스케일 변환해주자.
# 표준화
age_min = np.min(X_test[:,2])
age_max = np.max(X_test[:,2])
Fare_min = np.min(X_test[:,5])
Fare_max = np.max(X_test[:,5])
X_train[:,2] = (X_train[:,2] - age_min)/(age_max - age_min)
X_test[:,2] = (X_test[:,2] - age_min)/(age_max - age_min)
X_train[:,5] = (X_train[:,5] - Fare_min)/(Fare_max - Fare_min)
X_test[:,5] = (X_test[:,5] - Fare_min)/(Fare_max - Fare_min)
>>> X_train
array([[1. , 0. , 0.3729514 , ..., 2. , 0.41282051,
0. ],
[3. , 0. , 0.27319367, ..., 0. , 0.00783188,
0. ],
[3. , 0. , 0.25894257, ..., 0. , 0.01517579,
2. ],
...,
[3. , 1. , 0.38720251, ..., 0. , 0.01517579,
2. ],
[1. , 1. , 0.54396466, ..., 0. , 0.21255864,
0. ],
[3. , 0. , 0.53683911, ..., 0. , 0.01415106,
2. ]])
>>> X_test
array([[3. , 0. , 0.30169588, ..., 0. , 0.01415106,
2. ],
[1. , 1. , 0.52971355, ..., 0. , 0.13913574,
0. ],
[3. , 1. , 0.3587003 , ..., 0. , 0.01546857,
2. ],
...,
[1. , 0. , 0.30169588, ..., 0. , 0.26473857,
0. ],
[3. , 1. , 0.0309249 , ..., 1. , 0.04113566,
2. ],
[3. , 1. , 0.30169588, ..., 0. , 0.01415106,
2. ]])
- 이제 학습 준비가 어느 정도 완료되었다.
3. 모델 생성 및 학습하기
- 생존자 분류는 생존 or 사망으로 이진 분류이다.
- 이진 분류는 맨 마지막 출력층에서 Sigmoid 함수를 활성화 함수로 사용한다(참고: Sigmoid 함수).
- 손실 함수로는 binary cross Entropy를 사용한다(참고: binary crossentropy 함수).
- 분류이므로 compile에 metrics를 넣어 기준을 정해준다.
# 모델 생성
model = keras.Sequential()
model.add(Dense(128, activation = "relu"))
model.add(Dense(64, activation = "relu"))
model.add(Dense(32, activation = "relu"))
model.add(Dense(16, activation = "relu"))
model.add(Dense(1, activation = "sigmoid"))
# 모델 Compile
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer=opt,
loss = "binary_crossentropy",
metrics=["binary_accuracy"])
>>> model.fit(X_train, y_train, epochs = 500)
Epoch 1/500
24/24 [==============================] - 1s 2ms/step - loss: 0.6112 - binary_accuracy: 0.6089
Epoch 2/500
24/24 [==============================] - 0s 2ms/step - loss: 0.3803 - binary_accuracy: 0.8494
Epoch 3/500
24/24 [==============================] - 0s 1ms/step - loss: 0.3446 - binary_accuracy: 0.8722
Epoch 4/500
24/24 [==============================] - 0s 1ms/step - loss: 0.3261 - binary_accuracy: 0.8778
Epoch 5/500
24/24 [==============================] - 0s 1ms/step - loss: 0.3600 - binary_accuracy: 0.8678
...
Epoch 496/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1601 - binary_accuracy: 0.9350
Epoch 497/500
24/24 [==============================] - 0s 954us/step - loss: 0.1759 - binary_accuracy: 0.9169
Epoch 498/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1753 - binary_accuracy: 0.9257
Epoch 499/500
24/24 [==============================] - 0s 997us/step - loss: 0.1667 - binary_accuracy: 0.9264
Epoch 500/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1576 - binary_accuracy: 0.9289
<tensorflow.python.keras.callbacks.History at 0x15afa302b20>
- epochs를 500으로 주었으나, 손실 값이 0.1576으로 만족할 만큼 떨어지진 않은 것을 볼 수 있다.
- 그러나 이 손실 값은 상대적인 값이므로, 단순하게 접근해선 안된다.
4. 모델 평가하기
- 분류이므로, 모델을 평가하는 기준인 정확도는 실제 분류와 예측한 분류가 얼마나 일치하는지를 보면 될 것이다.
>>> pred = model.predict(X_test).reshape(X_test.shape[0])
>>> pred = np.where(pred > 0.5, 1, 0)
>>> accuracy = 1 - (np.where((pred - y_test) == 0, 0, 1).sum()/len(y_test))
>>> print("Accuracy:", accuracy)
Accuracy: 0.78
- 정확도가 0.78로 78%의 예측값만 실제와 일치하는 것으로 나타났다.
- 모델이 78%만 맞췄다는 것은, 모델의 분류 성능이 기대할만한 수준이라 보기가 힘들다고 할 수 있다.
- 그러나, 우리는 캐글의 Leaderboard를 보면 Titanic Dataset의 생존 분류의 예측률이 1.000으로 100%에 가까운 성능이 나온 것을 볼 수 있다!
- 대체 무슨 차이가 있길래 이 사람들이 만든 모델과 이번에 만든 모델의 성능 차이가 이토록 많이 날까?
머신러닝은 보시다시피 마법의 상자가 아니라, 사용자가 얼마나 잘 설계를 하냐에 따라 전혀 다른 결과가 나오게 된다. 다음 포스트부턴 타이타닉 생존자 분류 모델의 성능을 올릴 수 있는 방법에 대해 학습해보도록 하겠다.
'Machine Learning > TensorFlow' 카테고리의 다른 글
Tensorflow-2.4. 타이타닉 생존자 분류 모델(3)-하이퍼 파라미터 튜닝 (0) | 2021.02.10 |
---|---|
Tensorflow-2.3. 타이타닉 생존자 분류 모델(2)-원-핫 벡터 (0) | 2021.02.10 |
Tensorflow-2.1. 타이타닉 데이터 설명 (0) | 2021.02.09 |
Tensorflow-2.0. 캐글(Kaggle)과 타이타닉 데이터 (0) | 2021.02.09 |
Tensorflow-1.6. 기초(7)-기초 모델 만들기(2)-Input 4개, Output 2개 (0) | 2021.02.09 |