728x90
반응형

 이전 포스트에서 타이타닉 데이터가 어떻게 구성되어 있는지 확인해보았다. 이번 포스트에서는 타이타닉 데이터를 전처리해보고, 생존자 분류 모델을 만들어보자.

 

 

타이타닉 데이터 생존자 분류 모델 만들기

  • 모든 데이터 분석에서도 그렇듯 딥 러닝 모델 생성에서도 제일 우선 되는 것은 데이터 전처리다.
  • 머신러닝 모델을 만들 때의 순서는 다음과 같다.
  1. 데이터 셋의 특징을 잘 나타낼 수 있게 전처리를 한다(Data Handling).
  2. 학습이 제대로 되도록 데이터 셋을 잘 쪼갠다(Train, Validation, Test).
  3. 목적과 데이터에 맞는 모델을 생성한다.
  4. 학습 후, 모델의 성능을 평가하고, 성능을 업그레이드한다.
  • 이번엔 각 영역이 미치는 영향이 얼마나 큰지를 시각적으로 보도록 하겠다.

 

 

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%에 가까운 성능이 나온 것을 볼 수 있다!

  • 대체 무슨 차이가 있길래 이 사람들이 만든 모델과 이번에 만든 모델의 성능 차이가 이토록 많이 날까?

 

 

 머신러닝은 보시다시피 마법의 상자가 아니라, 사용자가 얼마나 잘 설계를 하냐에 따라 전혀 다른 결과가 나오게 된다. 다음 포스트부턴 타이타닉 생존자 분류 모델의 성능을 올릴 수 있는 방법에 대해 학습해보도록 하겠다.

728x90
반응형

+ Recent posts