728x90
반응형

 지난 포스트에서 MNIST 데이터 셋을 사용하여, 검증 셋(Validation set)을 생성해보았다. 이번 포스트에서는 Keras의 중심인 모델을 만들어보도록 하겠다.

 

 

모델 생성

0. 이전 코드 정리

# Import Module
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.layers import (Dense, BatchNormalization, Dropout)
from tensorflow.keras.datasets.mnist import load_data

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
################################### Function ###################################

# Data 시각화
def show_images(dataset, label, nrow, ncol):

    # 캔버스 설정
    fig, axes = plt.subplots(nrows=nrow, ncols=ncol, figsize=(2*ncol,2*nrow))
    ax = axes.ravel()

    xlabels = label[0:nrow*ncol]

    for i in range(nrow*ncol):

        image = dataset[i]
        ax[i].imshow(image, cmap='gray')
        ax[i].set_xticks([])
        ax[i].set_yticks([])
        ax[i].set_xlabel(xlabels[i])

    # 빈 칸 없이 꽉 채우기
    plt.tight_layout()
    plt.show()
    
################################################################################
# Dataset 준비
(train_images, train_labels), (test_images, test_labels)= load_data()

# 무작위로 샘플 추출
np.random.seed(1234)
index_list = np.arange(0, len(train_labels))
valid_index = np.random.choice(index_list, size = 5000, replace = False)

# 검증셋 추출
valid_images = train_images[valid_index]
valid_labels = train_labels[valid_index]

# 학습셋에서 검증셋 제외
train_index = set(index_list) - set(valid_index)
train_images = train_images[list(train_index)]
train_labels = train_labels[list(train_index)]

# min-max scaling
min_key = np.min(train_images)
max_key = np.max(train_images)
train_images = (train_images - min_key)/(max_key - min_key)
valid_images = (valid_images - min_key)/(max_key - min_key)
test_images = (test_images - min_key)/(max_key - min_key)

 

 

 

 

1. 모델 생성

  • 이전에 만들었던 모델들에 들어간 데이터들은 1차원 배열이 n개의 row로 구성된 형태였다.
>>> train_images.shape
(60000, 28, 28)
  • 그러나, 이번 데이터셋은 28*28인 행렬이 60000개 row로 쌓인 형태다.
  • 이때 평활(Flatten)이라는 개념이 추가로 등장한다.
  • 모델을 먼저 만들어보자.
# 모델 생성
model = keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=[28, 28], name="Flatten"))
model.add(Dense(300, activation="relu", name="Hidden1"))
model.add(Dense(200, activation="relu", name="Hidden2"))
model.add(Dense(100, activation="relu", name="Hidden3"))
model.add(Dense(10, activation="softmax", name="Output"))
  • 각 Layer를 구분하기 쉽도록 name이라는 parameter를 추가해주었다.
  • 이전과 달리 Flatten이라는 Layer가 새로 추가되었다.
  • Flatten Layer는 입력된 2차원 배열을 1차원 배열로 만들어주는 전처리용 Layer다.
  • 한 Row가 X = (1, 28, 28)인 데이터셋을 X.reshape(-1, 28*28)으로 형 변환해준다고 생각하면 된다. 
>>> X = train_images[0]
>>> X.reshape(-1, 28*28).shape
(1, 784)
  • 평활 Layer 통과는 각 Row에 적용되므로, 전체 데이터 셋의 형태가 (60000, 28, 28)에서 (60000, 784)로 바뀐다고 생각해도 좋다.

 

 

 

 

2. 은닉층 설정하기

  • 지금까지의 포스팅에선 은닉층의 수와 은닉층에 있는 노드의 수를 정할 때, 어째서 이렇게 구성하였는지 설명하지 않았다. 이번에는 은닉층을 설정할 때, 무엇을 인지한 상태로 은닉층을 만들어야 하는지에 대해 학습해보도록 하겠다.

 

2.1. 은닉층의 개수

  • 은닉층의 개수가 1개인 경우를 얕은 신경망이라고 하며, 2개 이상인 경우를 심층 신경망이라고 한다.
  • 이론적으로 입력층, 은닉층, 출력층으로 3개의 층만 있는 경우에도 뉴런수만 충분하다면 아주 복잡한 함수도 모델링할 수 있다.
    George Cybenko(1989), Approximation by superpositions of a sigmoidal function - 조지 시벤코의 시벤코 정리: 뉴런 수만 무한하다면 은닉층 하나로 어떤 함수도 근사할 수 있다.
  • 그러나, 심층 신경망은 얕은 신경망보다 적은 노드를 사용하여, 과적합 문제에서 비교적 자유롭고, 얕은 신경망보다 적은 epochs로 최적해에 수렴하게 된다.
  • 이는 은닉층의 수가 늘어날수록, 저수준에서 고수준으로 체계적으로 구조를 모델링할 수 있기 때문이다.
  • 예를 들어, 3개의 은닉층을 사용해 사람을 구분하는 모델을 만들고자 한다면, Hidden1에서는 가장 저수준의 구조인 사람과 배경을 구분하는 일을, Hidden2에서는 사람의 머리, 몸, 키 등을 구분하는 일을, Hidden3에서는 가장 고수준인 사람의 얼굴과 머리스타일을 구분하도록 모델링하게 된다.
  • 이러한 계층 구조는 심층 신경망이 새로운 데이터에 대해 일반화하는 능력도 향상하게 해 준다.
  • 계층 구조는 기존 모델에 추가 기능이 생긴 업그레이드된 모델을 만들 때, 기존 모델의 파라미터를 하위 은닉층에서 재사용해 훈련을 진행할 수 있다. 이를 전이 학습(Transfer Learning)이라 한다.

 

2.2. 은닉층의 뉴런 개수

  • 데이터 셋에 따라 다르긴 하지만 모델에서 은닉층의 뉴런 수를 정할 땐, 다음과 같은 경향이 있다.
  • 일반적으로 첫 번째 은닉층을 제일 크게 하는 것이 도움된다.
  • 만약 한 층의 뉴런 수가 지나치게 적다면, 전달되는 정보 중 일부가 사라져 버릴 수 있다. 
  • 깔때기 형태: 은닉층의 구성은 일반적으로 각 층의 뉴런의 수를 점차 줄여가며, 깔때기처럼 구성한다. 이는 저수준의 많은 특성이 고수준의 적은 특성으로 합쳐질 수 있기 때문이다.
  • 직사각형 형태: 모든 은닉층의 뉴런 수를 같게 함. 이렇게 모델링을 하는 경우, 깔때기 형태와 동일하거나 더 나은 성능을 내는 경우도 있다고 한다.
  • 일반적으로 은닉층의 뉴런 수를 늘리는 것보다 은닉층의 수를 늘리는 쪽이 유리하다.

 

2.3. 스트레치 팬츠(Stretch pants) 방식

  • 실제 필요한 것보다 은닉층의 수와 뉴런의 수를 크게 하고, 과대 적합이 발생하지 않도록, 조기 종료를 하거나 규제 기법을 사용하는 방법
  • 말 그대로 "자기에게 맞는 바지를 찾는 것이 아닌, 큰 스트레치 팬츠를 사고 나중에 나에 맞게 줄이는 기법"이라 할 수 있다.
  • 해당 방식 사용 시, 모델에서 문제를 일으키는 병목층을 피할 수 있다.

 

 

 

 

3. 생성된 모델 정보

  • 이전 포스트까지는 단순하게 모델을 생성하고 바로 학습으로 뛰어들었지만, 이번엔 모델의 요약 정보를 보고 진행해보자.
>>> model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
Flatten (Flatten)            (None, 784)               0         
_________________________________________________________________
Hidden1 (Dense)              (None, 300)               235500    
_________________________________________________________________
Hidden2 (Dense)              (None, 200)               60200     
_________________________________________________________________
Hidden3 (Dense)              (None, 100)               20100     
_________________________________________________________________
Output (Dense)               (None, 10)                1010      
=================================================================
Total params: 316,810
Trainable params: 316,810
Non-trainable params: 0
_________________________________________________________________
  • model.summary(): 모델의 요약 정보를 보여준다.
  • Layer (type)을 보면, 앞서 설정한 Layer의 이름과 어떤 Layer인지가 나온다.
  • Output shape에서 None은 아직 모르는 값으로, Input 될 데이터의 양을 의미한다.
  • Input Data는 2-d 배열인 행렬이지만, 1-d 배열로 학습이 진행되므로 shape은 1-d 배열인 것을 알 수 있다.
  • Param은 각 Layer별 파라미터의 수로, Dense Layer는 Input layer와 Output layer의 모든 노드를 연결하는 완전 연결 계층이기 때문에 연결된 선(Param)이 많다.
  • Hidden1은 이전 층의 Node가 784개이고, 자신의 Node가 300개이므로, 가중치(Weight)의 엣지가 784*300=235,200개 생성된다. 여기에 편향(Bias)의 엣지가 자신의 Node 수만큼 존재하므로, +300을 하여, 235,500개의 Param이 존재하게 된다.
  • 위 모델 같이 파라미터의 수가 많은 경우, 과대 적합(Overfitting)의 위험이 올라갈 수 있으며, 특히 훈련 데이터의 양이 많지 않은 경우 이 위험이 증가하게 된다.

 

 

[참고 서적]

 

 

 지금까지 모델을 생성하고, 그 정보를 보는 방법에 대해 학습해보았다. 다음 포스팅에서는 모델 컴파일과 학습을 진행해보도록 하겠다.

 

728x90
반응형
728x90
반응형

 지금까지의 포스팅에서는 검증 셋을 따로 생성하지 않았으나, 이번엔 검증 셋을 데이터에서 추출해보도록 하겠다. 검증 셋 추출 시, 주의할 점은 검증 셋에서 편향이 발생해서는 안된다는 것이다.

 

 

검증 셋(Validation set)

1. 검증 셋이란?

 검증 셋은 앞선 "Tensorflow-1.0. 기초(1)-데이터 셋 만들기"에서 한 번 언급하긴 하였으나, 이번엔 좀 더 자세히 설명해보도록 하겠다.

  • 검증 셋은 학습 도중에 학습된 내용을 평가하는 "가짜 최종 시험"이다.
  • 예를 들어, 수능을 준비하는 고3 학생에게 모의고사 문제가 5개가 있다면, 4개는 공부를 할 때 사용하고, 나머지 1개는 수능 전에 자신이 얼마나 잘 공부를 했는지 평가하는 용도로 사용하는 것이라 생각하면 된다.
  • 최종 목표인 수능(Test set)을 보기 전에 자신의 실력을 평가하는 용도(학습된 파라미터 평가)로 사용되기 때문에 검증 셋을 얼마나 잘 추출하느냐는 꽤 중요한 문제다.
  • 검증 셋은 파라미터 갱신에 영향을 주는 것이 아니라, 학습 과정에서 생성된 여러 모델 중 어느 모델이 가장 좋은지를 평가하는 용도로 사용된다.
  • 검증 셋(Validation set)과 학습 셋(Train set)이 중복되면, 편향이 발생하여, 제대로 된 평가가 이루어지지 않을 수 있다. 이렇게 검증 셋과 학습 셋이 중복된 현상을 Leakage라고 한다.

 

 

 

2. 검증 셋의 효과

  • 검증 셋을 사용한 학습 데이터의 평가는, 학습 과정에서 생긴 여러 모델들이 만들어낸 수많은 파라미터 중 최적의 파라미터를 선택하므로, 파라미터를 튜닝하는 효과가 있다고 할 수 있다.
  • 만약 학습 셋(Train set)으로만 학습하고, 시험 셋(Test set)으로만 모델을 검증한다면, 모델은 시험 셋(Test set)에 과적합(Overfitting)된 모델이 될 수 있다.
  • 시험 셋(Test set)은 모델의 성능을 평가하기 위해 사용되는 것이긴 하지만, 모델 성능 향상의 기준이 시험 셋이 돼버린다면, 시험 셋의 "모델 성능 평가"라는 목적이 "모델 성능을 맞추는 기준"으로 변질되게 된다. 이 경우, 검증 셋을 사용한다면, 이 문제를 해결할 수 있다.

 

 

 

3. 검증 셋 추출 방법

  • 검증 셋 추출 방법에서 핵심은 "어떻게 검증 셋의 편향을 피하는가"이다.
  • 예를 들어, 총 10개의 시험 단원이 있고, 여기서 랜덤 하게 문제를 뽑아, 수능 시험을 보러 가기 전 최종 평가를 하려고 한다. 그런데, 우연히 1~6단원 문제가 90% 가까이 나왔고, 7~10단원 문제가 10%밖에 나오지 않았다고 가정해보자. 이 시험 문제를 최종 기준으로 사용하는 것은 꼭 피해야 할 문제다.
  • 즉, 최대한 검증 셋의 편향을 없애는 것이 다양한 검증 셋 추출 방법들이 생기게 된 이유라고 할 수 있다.

3.1. Hold-out

  • 단순하게 일정 비율의 데이터 셋을 분리해내는 방법이다.
  • 데이터의 양이 적을수록, 전체 데이터를 대표하지 못할 가능성이 높으며, 편향된 결과를 얻을 가능성이 있다.

3.2. Random subsampling

  • Hold-out을 완전 무작위 표본 추출로 반복 시행하고, 정확도의 평균으로 성능을 평가한다.

3.3. K-fold cross validation

  • 데이터 셋을 중복되지 않는 K개의 집단으로 나눈다.
  • K개의 집단에서 1개의 집단을 검증 셋으로 사용하며, K번 검증 셋을 집단이 중복되지 않게 바꿔가며 정확도를 계산하고, 그 평균으로 성능을 평가한다.
  • 데이터 셋이 많으면 많을수록 지나치게 시간을 많이 소모하므로, 빅데이터를 사용하는 현 트렌드에는 맞지 않다.
  • 물론, 데이터의 양이 매우 적다면, 가지고 있는 모든 데이터를 학습과 평가에 사용할 수 있다는 장점이 있다.

3.4. Leave p-out cross validation

  • 중복되지 않은 전체 데이터 n개에서 p개의 샘플을 검증 셋으로 사용하여 정확도를 계산하고, 그 결과의 평균으로 성능을 평가한다.
  • 전체 경우의 수가 ${n}C{p}$개이기 때문에 k-fold cross validation보다 소모되는 시간이 K-fold cross validation보다 많다.
  • 데이터 셋의 양이 매우 적은 경우에나 사용할만하다.
  • 여기서 p=1로 하면 Leave one-out cross validation(LOOCV)라 하며, 소모 시간과 성능 모두 Leave p-out cross validation보다 우수하다고 한다.

3.5. Stratified Sampling

  • 전체 데이터 셋을 구성하는 클래스별로 데이터를 일부 추출한다.
  • 전체 데이터셋에서 클래스의 비율이 불균형할수록 편향을 줄여준다는 장점이 있다.
  • 완전 무작위 표본 추출 시, 우연히 특정 클래스에 표본이 편중되는 현상을 피할 수 있다.
  • Stratified Sampling 역시 k-fold 방식처럼 k개의 집단을 생성하여 그 평균을 낼 수도 있다(Stratified k-fold cross validation).

3.6. Bootstrap

  • 전체 데이터 셋에서 중복을 허용한 샘플링을 반복 실시해, 모집단으로부터 새로운 데이터 셋을 만들어 냄
  • 크기가 n인 데이터셋에 대하여 부트스트랩을 b번 한다고 가정할 때, 공식은 다음과 같다.

$$ACC_{boot} = \frac{1}{b}\sum_{j=1}^{b}\frac{1}{n}\sum_{i=1}^{n}(1-L(\hat{y_i},y_i))$$

  1. 전체 데이터 셋에서 하나의 샘플을 뽑고, $j$번째 부트스트랩에 할당
  2. 1번 과정을 부트스트랩 샘플 크기가 원본 데이터 셋 크기인 n이 될 때까지 반복한다.
    (중복을 허용하여 완전 무작위 추출하므로 부트스트랩 샘플에 한번 이상 포함되었거나, 아예 없을 수도 있다.)
  3. b개의 부트스트랩 샘플 하나하나를 모델에 학습시키고 훈련에 사용된 데이터를 이용하여 성능을 평가한다(재치환 정확도).
  4. b개의 부트스트랩 샘플의 정확도의 평균으로 모델 정확도를 구한다.
  • 부트스트랩은 데이터의 분포를 알 수 없고, 추가적인 데이터를 구할 수 없는 경우 추정량의 통계적 속성을 결정하기 위해 사용한다.
  • 보다 자세한 설명을 보고 싶은 사람은 다음 출처를 확인하기 바란다.
    출처: 텐서 플로우 블로그 (Tensor ≈ Blog)
 

머신 러닝의 모델 평가와 모델 선택, 알고리즘 선택 – 2장. 부트스트래핑과 불확실성

이 글은 파이썬 머신 러닝의 저자 세바스찬 라쉬카(Setabstian Raschka)가 쓴 ‘Model evaluation, model selection, and algorithm selection in machine learning Part II – Bootstrapping and uncertainties‘를 원저자의 동의하에 번

tensorflow.blog

 

 

 

 

4. 검증 셋 추출

  • 검증 셋 추출 방법은 데이터 셋의 크기가 크면 클수록 교차 검증 방식을 사용하지 않는 것이 좋다.
  • k-fold 교차 검증이나 LOOCV은 데이터의 양이 늘어나면 늘어날수록 연산량이 지나치게 늘어나게 되는데, 현대 같이 빅데이터를 사용하여 딥러닝을 실시하는 경우에는 개인적으로 추천하지 않는다.
  • 검증 셋을 추출하기 전에 Label의 빈도를 보도록 하자.
>>> pd.Series(train_labels).value_counts()
1    6742
7    6265
3    6131
2    5958
9    5949
0    5923
6    5918
8    5851
4    5842
5    5421
dtype: int64
# 히스토그램을 보자
plt.hist(train_labels)
plt.show()

  • 데이터셋의 분포는 특정 클래스에 치우치지 않은 평탄한 상태인 것을 알 수 있다.
  • 그러므로, Hold-Out을 사용하되 완전 무작위 표본 추출로 검증 셋을 생성하도록 하겠다.
# 무작위로 샘플 추출
np.random.seed(1234)
index_list = np.arange(0, len(train_labels))
valid_index = np.random.choice(index_list, size = 5000, replace = False)

# 검증셋 추출
valid_images = train_images[valid_index]
valid_labels = train_labels[valid_index]

# 학습셋에서 검증셋 제외
train_index = set(index_list) - set(valid_index)
train_images = train_images[list(train_index)]
train_labels = train_labels[list(train_index)]
  • 완전 무작위 표본 추출을 위해 index를 무작위로 추출하였다.
  • 차집합을 이용해서 간단하게 index의 차를 구하였다.
>>> pd.Series(valid_labels).value_counts()
1    616
9    518
3    514
7    497
4    491
8    488
6    482
2    481
0    480
5    433
dtype: int64
  • valid set의 빈도 표를 통해 데이터가 어느 정도 이쁘게 뽑힌 것을 볼 수 있다.
  • 혹시 모르니 표준편차도 뽑아보도록 하자.
>>> np.std(train_labels)
2.887480385250541

>>> np.std(valid_labels)
2.9085488065356584
  • 검증 셋과 학습 셋의 표준편차가 거의 유사하게 나온 것으로 볼 때, 검증 셋이 대표성을 갖고 있다고 할 수 있다.

 

 

 

 

5. 스케일 조정

  • 앞서 이야기 하긴 했으나, 다시 한번 이야기하자면, 데이터의 범위 차이를 줄여 최적해에 보다 쉽게 수렴하게 하는 방법이다.
  • 스케일 조정에서 사용되는 값의 기준은 Train set이 되어야 한다.
  • 보다 자세한 내용은 "Tensorflow-1.3. 기초(4)-특성 스케일 조정"을 참고하기 바란다.
  • min-max scaling을 이용해 표준화시키도록 하겠다.
# min-max scaling
min_key = np.min(train_images)
max_key = np.max(train_images)

train_images = (train_images - min_key)/(max_key - min_key)
valid_images = (valid_images - min_key)/(max_key - min_key)
test_images = (test_images - min_key)/(max_key - min_key)

 

 

 

 지금까지 MNIST 데이터셋에서 검증 셋을 추출해보았다. 다음 포스트에서는 모델을 만들어보도록 하겠다.

728x90
반응형
728x90
반응형

 지난 포스트에서 Tensorflow에서 왜 Keras를 사용하는지와 Keras의 코드 흐름이 어떻게 흘러가는지를 알아보았다. 지금까지의 Tensorflow 과제에서는 진행 과정을 큰 시야에서 보았다면, 이번 포스트부턴 디테일하게 각 부분이 어떻게 흘러가는지를 보도록 하겠다.

 

 

MNIST Dataset

 LeCun 교수가 만든 MNIST Dataset은 머신러닝 학습에서 가장 기본적으로 사용되는 데이터로, Tensorflow, Pytorch와 같은 수많은 딥러닝 라이브러리의 예제에서 해당 데이터를 다루는 것을 볼 수 있다.

 이번 학습에서는 MNIST 데이터에서 가장 대표적인 데이터인 손으로 쓴 숫자를 분류하는 모델을 만들어보도록 하겠다.

# Import Module
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.layers import (Dense, BatchNormalization, Dropout)
from tensorflow.keras.datasets.mnist import load_data

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Import Dataset
(train_images, train_labels), (test_images, test_labels)= load_data()
  • 해당 코드를 처음 실행한다면, Dataset이 다운로드 된다.
  • 때문에 인터넷이 안 되는 환경에서는 해당 데이터를 다운로드할 수 없으므로, 외부망에서 미리 다운로드를 하여 가상 환경을 반입하도록 하자.
  • 데이터가 어떻게 생겼는지 보도록 하자.
# Dataset의 모양
>>> train_images.shape
(60000, 28, 28)

>>> train_labels.shape
(60000,)

>>> test_images.shape
(10000, 28, 28)

>>> test_labels.shape
(10000,)
  • train set은 총 60,000개, test set은 10,000개의 Data로 이루어져 있으며, 각각 28*28의 형태로 구성되어 있다.
  • Label Data는 각 Row가 무슨 숫자인지를 의미한다.
  • 이미지 데이터이므로, 이미지가 어떻게 생겼는지 봐보자.
def show_images(dataset, label, nrow, ncol):

    # 캔버스 설정
    fig, axes = plt.subplots(nrows=nrow, ncols=ncol, figsize=(2*ncol,2*nrow))
    ax = axes.ravel()

    xlabels = label[0:nrow*ncol]

    for i in range(nrow*ncol):

        image = dataset[i]
        ax[i].imshow(image, cmap='gray')
        ax[i].set_xticks([])
        ax[i].set_yticks([])
        ax[i].set_xlabel(xlabels[i])

    # 빈 칸 없이 꽉 채우기
    plt.tight_layout()
    plt.show()
show_images(train_images, train_labels, 4, 5)

  • 이미지 데이터의 모습은 위와 같다. 아래에 있는 숫자는 각 Data에 해당하는 Label을 붙인 것이다.
  • 실제 데이터인 텐서의 모습은 다음과 같다.
>>> print(train_images[0])
[[  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   3  18  18  18 126 136 175  26 166 255 247 127   0   0   0   0]
 [  0   0   0   0   0   0   0   0  30  36  94 154 170 253 253 253 253 253 225 172 253 242 195  64   0   0   0   0]
 [  0   0   0   0   0   0   0  49 238 253 253 253 253 253 253 253 253 251  93  82  82  56  39   0   0   0   0   0]
 [  0   0   0   0   0   0   0  18 219 253 253 253 253 253 198 182 247 241   0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0  80 156 107 253 253 205  11   0  43 154   0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0  14   1 154 253  90   0   0   0   0   0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0 139 253 190   2   0   0   0   0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0  11 190 253  70   0   0   0   0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0  35 241 225 160 108   1   0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0  81 240 253 253 119  25   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0  45 186 253 253 150  27   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0  16  93 252 253 187   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0 249 253 249  64   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0  46 130 183 253 253 207   2   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0  39 148 229 253 253 253 250 182   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0  24 114 221 253 253 253 253 201  78   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0  23  66 213 253 253 253 253 198  81   2   0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0  18 171 219 253 253 253 253 195  80   9   0   0   0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0  55 172 226 253 253 253 253 244 133  11   0   0   0   0   0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0 136 253 253 253 212 135 132  16   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0]]
  • 이미지를 어떻게 텐서로 만드는 가에 대해 의문이 들 수 있는데, 위 데이터와 실제 사진을 비교해보면 굉장히 단순한 원리로 만들어졌음을 알 수 있다.
  • 위 텐서에서 0에 가까울수록 사진에서는 검게 나왔으며, 숫자가 최댓값인 255에 가까울수록 희게 나온 것을 알 수 있다.
  • 즉, 흑백 사진의 텐서화는 색의 농도로 나타나는 것을 알 수 있다.
  • 만약, 이 것이 칼라 사진인 경우, RGB 총 3개의 채널(Channel)에 대해 각 색의 농도를 행렬로 만들어, 3차원 배열로 만들면, 텐서가 생성된다.

 

 

 

 다음 포스트에서는 검증셋(Validation set)에 대해 자세히 알아보고, 검증셋을 직접 추출해보도록 하겠다.

728x90
반응형
728x90
반응형

 지금까지 Tensorflow를 사용해서 머신러닝을 사용하는 과정의 전반을 살펴보았다. 이 과정에서 꽤 빼먹은 내용이 많은데, 이번엔 천천히 모두 살펴보도록 하자.

 우리가 Tensorflow를 사용해서 머신러닝을 사용하긴 했지만, 실제 우리가 사용한 코드는 keras 코드였다. 이번 포스트에서는 왜 우리가 Keras를 사용하였고, 그로 인해 우리가 얻는 이익과 Keras의 작동 과정을 정리해보자.

 

 

 

Keras란?

1. 케라스의 특징

  • 케라스는 사용자 친화적이다. - 코드가 간결하여, 사용자가 익히기 쉽다.
  • 케라스는 모듈화가 쉽다.
  • 케라스는 다양한 딥러닝 백엔드 엔진을 지원하므로, 특정 생태계에 종속되지 않는다.
  • 케라스는 다중 GPU 연산과 학습의 분산처리를 지원한다.
  • 참고: keras.io/ko/why-use-keras/

1.1. 멀티 백엔드 케라스(Multibackend Keras)

  • 케라스(Keras)는 Tensorflow, CNTKm Theano 등 다양한 딥러닝 라이브러리를 선택하여 사용할 수 있다. 이를 멀티 백엔드 케라스(Multibackend Keras)라고 한다.
  • Keras는 벡엔드에 의존하여 연산하며, 벡엔드에 사용된 딥러닝 라이브러리의 장점을 사용할 수 있다.
  • 만약, Keras의 백엔드를 Tensorflow가 아닌 다른 라이브러리를 사용하고 싶다면, "참고"를 읽기 바란다.

1.2. Tensorflow와 케라스

  • 이전 포스트에서 Keras로 코드를 구현했지만, 우리 눈에 보이지 않는 내부에선 Tensorflow로 연산이 진행된 것이다.
  • Tensorflow는 1.x 버전까지 Tensorflow 함수를 사용하여 코드를 작성하였다. 머신러닝에 대한 이해도가 이미 높은 사람이라면, 큰 어려움 없이 사용할 수 있으나, 그렇지 않은 사람이 접근하기 어려웠다.
  • 그러나 Tensorflow 2.x부터 직관적으로 사용할 수 있는 Keras를 Tensorflow 내에서 제공하므로, Tensorflow의 사용 난이도가 크게 내려갔다.

 

 

 

 

2. 케라스의 작동 순서

  • 케라스는 모델(Model) 중심이다.
  • 케라스는 기본 모델(Sequential 모델 등)을 생성하고, 레이어를 쌓아 모델을 생성한다.
  • 케라스는 모델 생성부터 모델을 사용하는 모든 과정에 고유 API를 제공한다.
  • 즉, 케라스는 제대로 된 모델을 생성하고, 그 모델을 평가 및 관리하는데 최적화되어 있다.

2.0 데이터셋 생성

  • 머신러닝에 있어 아주 중요한 부분이지만, 케라스에서는 비중이 크지 않은 부분이다.
  • sklearn 같은 다른 머신러닝 라이브러리는 데이터 전처리에 관련된 다양한 API를 제공하지만, 케라스는 관련 API를 제공하지 않는다.
  • 데이터셋 생성 및 전처리는 sklearn이나, Numpy 등을 활용하길 바란다.

2.1. 모델 만들기

  • 선형 모델인 Sequantial model을 기본적으로 사용한다.
  • 좀 더 복잡한 모델이 필요한 경우 케라스 함수 API를 통해 모델을 만든다.
  • 다양한 layer를 추가하여, 입맛에 맞게 모델을 생성한다.

2.2 모델 학습 방법 설정

  • compile() 함수 사용
  • "optimizer: 최적화 함수, loss: 손실 함수, metric: 분류 시 기준" 설정

2.3. 모델 학습

  • fit() 함수 사용
  • 학습 시, 학습 단위(epochs, batch_size)나 검증 셋(validation) 등 설정

2.4. 학습과정 확인

  • fit() 함수 사용 시, 히스토리 객체가 반환되며, 다음과 같은 내용이 포함된다.
  1. loss: 매 에포크마다 훈련 손실 값
  2. acc: 매 에포크마다 훈련 정확도
  3. val_loss: 매 에포크마다 검증 손실 값
  4. val_acc: 매 에포크마다 검증 정확도
  • 해당 내용을 통해, 적합한 학습량을 설정할 수 있다.
  • 히스토리의 시각화나 콜백 함수, 텐서 보드를 사용하여, 학습 과정을 모니터링하며, 특정 조건을 만족 시, 학습이 끝나지 않았더라도 조기 종료할 수 있다.

2.5. 모델 평가

  • evaluate() 함수 사용
  • 시험 셋으로 학습이 끝난 모델 평가
  • 모델 평가가 evaluate() 함수만으로는 힘든 경우도 있으므로, 이 때는 이를 위한 알고리즘을 생성하길 바란다.
  • 하이퍼 파라미터 튜닝(Hyper Parameter Tuning)이 일어나는 부분이다.

2.6. 모델 사용

  • predict(): 모델 사용
  • save(): 모델 저장
  • load_model(): 모델 불러오기

 

 

 

 지금까지 Keras에 대한 기본 개념을 학습해보았다. 다음 포스트부터는 MNIST 데이터셋을 사용해 위 과정들을 천천히 그리고 상세히 살펴보도록 하겠다.

728x90
반응형
728x90
반응형

 이전 포스트에서는 기본적인 딥러닝을 통해 타이타닉 데이터의 생존자 분류 모델을 생성해보았다. 이번에는 이전에 했던 이진 분류가 아닌, 3가지 이상의 군으로 나누는 다중 분류 모델을 만들어보도록 하겠다.

 

 

다중 분류 모델

  • 다중 분류 모델과 이진 분류 모델의 가장 큰 차이는 출력층에서 사용하는 활성화 함수와 손실 함수가 다르다는 것이다.
  • 통계학에 익숙한 사람이라면, 이진 분류를 할 때는 일반적인 로지스틱 회귀 모형을 사용하지만, 다중 분류 시, 다중 로지스틱 회귀 모형을 사용한다는 것을 알 수 있다.
  • 손실 함수는 큰 차이가 없으니 넘어가더라도, 활성화 함수는 Sigmoid에서 Softmax로 바뀌게 되는데, 이 Softmax 함수는 Sigmoid 함수에서 발전한 함수다.
  • Softmax 함수에 대한 추가 설명은 다음 포스팅을 참고하기 바란다(참고).
  • 이번 포스팅에서는 타이타닉 데이터의 Name 변수에 있는 Mr, Mrs, Ms를 추출해 Class라는 변수를 생성하고, 이를 Label로 사용하여 분류기를 만들어보도록 하겠다.

 

 

 

 

1. Class 추출.

  • 이전에 만들었던 함수들을 사용해서 쉽게 데이터셋을 만들어보자.
  • Name 변수의 내용은 다음과 같다.
# Name 데이터의 생김새
>>> Rawdata.Name.head(20)
0                               Braund, Mr. Owen Harris
1     Cumings, Mrs. John Bradley (Florence Briggs Th...
2                                Heikkinen, Miss. Laina
3          Futrelle, Mrs. Jacques Heath (Lily May Peel)
4                              Allen, Mr. William Henry
5                                      Moran, Mr. James
6                               McCarthy, Mr. Timothy J
7                        Palsson, Master. Gosta Leonard
8     Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg)
9                   Nasser, Mrs. Nicholas (Adele Achem)
10                      Sandstrom, Miss. Marguerite Rut
11                             Bonnell, Miss. Elizabeth
12                       Saundercock, Mr. William Henry
13                          Andersson, Mr. Anders Johan
14                 Vestrom, Miss. Hulda Amanda Adolfina
15                     Hewlett, Mrs. (Mary D Kingcome) 
16                                 Rice, Master. Eugene
17                         Williams, Mr. Charles Eugene
18    Vander Planke, Mrs. Julius (Emelia Maria Vande...
19                              Masselmani, Mrs. Fatima
Name: Name, dtype: object
  • 데이터를 보면, 처음 등장하는 ", "와 ". " 사이에 해당 인물이 속하는 Class가 나온다.
  • 이를 뽑아내 보자.
# Inport Module
import pandas as pd
import numpy as np
import os
from tensorflow.keras.layers import (Dense, Dropout, BatchNormalization)
from tensorflow import keras
from copy import copy



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

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




# Rawdata Import
file_path = "./Dataset"
Rawdata_dict = import_Data(file_path)

# Rawdata 생성
Rawdata = make_Rawdata(Rawdata_dict)

# Name에서 Class 추출
Class1 = Rawdata["Name"].str.partition(", ")[2]
Rawdata["Class"] = Class1.str.partition(". ")[0]
  • 판다스의 str 모듈에 있는 partition 함수를 사용하여, 원하는 문자를 가지고 왔다.
  • Series.str.partition(sep): 함수는 맨 처음 등장하는 sep의 단어로 해당 열의 데이터를 분리하여, 3개의 열을 생성한다.
  • Class에 어떤 데이터들이 존재하는지 빈도 표를 출력하여 확인해보자.
# Class 데이터 빈도분석 결과
>>> Rawdata.Class.value_counts()

Mr              757
Miss            260
Mrs             197
Master           61
Rev               8
Dr                8
Col               4
Ms                2
Major             2
Mlle              2
Jonkheer          1
Capt              1
Don               1
Sir               1
the Countess      1
Mme               1
Dona              1
Lady              1
Name: Class, dtype: int64
  • 해당 데이터는 Mr, Miss, Mrs뿐만 아니라 95개 데이터가 15개의 분류에 속하는 것을 볼 수 있다.
  • 확실하게 Miss에 속하는 Ms, Mlle, Lady를 하나로, Mrs에 속하는 것이 확실한 the Countess, Dona, Jonkheer, Mme를 하나로 묶고, Mr를 제외한 나머지는 버리도록 하자.
# Class를 숫자로 치환하자.
Class_a = Rawdata["Class"].to_numpy()

Class_b = np.where(Class_a == "Mr", 0,
                   np.where(np.isin(Class_a, ['Miss','Mlle','Ms','Lady']), 1,
                            np.where(np.isin(Class_a, ["Mrs", 'the Countess', 'Dona', 'Mme']), 2, 9)))

Rawdata["Class"] = Class_b
# 변환된 결과 확인
>>> Rawdata["Class"].value_counts()
0    757
1    265
2    200
9     87
Name: Class, dtype: int64
  • 쓸모없는 변수들을 제거하겠다.
  • Index와 PassengerId는 거의 일치하므로 제거하자.
  • Name, Ticket, Cabin은 사용하지 않으므로 제거하자.
  • Class에서 9로 지정된 경우는 결측 값이므로 제거하자.

 

 

 

 

2. 전체 코드

# Inport Module
import pandas as pd
import numpy as np
import os
from tensorflow.keras.layers import (Dense, Dropout, BatchNormalization)
from tensorflow import keras
from copy import copy




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


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


# 원-핫 벡터
def one_hot_Encoding(data, column):

    # 한 변수 내 빈도
    freq = data[column].value_counts()

    # 빈도가 큰 순서로 용어 사전 생성
    vocabulary = freq.sort_values(ascending = False).index

    # DataFrame에 용어 사전 크기의 column 생성
    for word in vocabulary:

        new_column = column + "_" + str(word)
        data[new_column] = 0

    # 생성된 column에 해당하는 row에 1을 넣음
    for word in vocabulary:

        target_index = data[data[column] == word].index
        new_column = column + "_" + str(word)
        data.loc[target_index, new_column] = 1

    # 기존 컬럼 제거
    del(data[column])

    
# 스케일 조정
def scale_adjust(X_test, X_train, C_number, key="min_max"):
    
    if key == "min_max":
        
        min_key = np.min(X_train[:,C_number])
        max_key = np.max(X_train[:,C_number])
        
        X_train[:,C_number] = (X_train[:,C_number] - min_key)/(max_key - min_key)
        X_test[:,C_number] = (X_test[:,C_number] - min_key)/(max_key - min_key)
        
    elif key =="norm":
        
        mean_key = np.mean(X_train[:,C_number])
        std_key = np.std(X_train[:,C_number])
        
        X_train[:,C_number] = (X_train[:,C_number] - mean_key)/std_key
        X_test[:,C_number] = (X_test[:,C_number] - mean_key)/std_key
        
    return X_test, X_train
######################################################################################
######################################## Model #######################################
# 모델 생성
model = keras.Sequential()
model.add(BatchNormalization())
model.add(Dense(128, activation = 'relu'))
model.add(Dropout(0.10))
model.add(Dense(64, activation = 'relu'))
model.add(Dropout(0.10))
model.add(Dense(32, activation = 'relu'))
model.add(Dropout(0.10))
model.add(Dense(16, activation = 'relu'))
# 마지막 Dropout은 좀 크게 주자
model.add(Dropout(0.50))
model.add(Dense(3, activation = 'softmax'))

# 모델 Compile
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer=opt,
              loss = "sparse_categorical_crossentropy",
              metrics=["accuracy"])
######################################################################################
  • 이진 분류와 다중 분류는 크게 3가지 부분에서 다르다.
  1. 출력층 활성화 함수를 sigmoid에서 softmax 함수 사용
    (Softmax의 출력층은 Label의 분류 수와 동일하므로, Node size를 거기에 맞게 맞춰줘야 한다.)
  2. 손실 함수(loss)를 binary_crossentropy에서 sparse_categorical_crossentropy를 사용
  3. metrics를 binary_accuracy에서 accuracy를 사용해서 비교
>>> model.fit(X_train, y_train, epochs = 200)

Epoch 1/200
23/23 [==============================] - 1s 2ms/step - loss: 0.8308 - accuracy: 0.5861
Epoch 2/200
23/23 [==============================] - 0s 2ms/step - loss: 0.4274 - accuracy: 0.7672
Epoch 3/200
23/23 [==============================] - 0s 2ms/step - loss: 0.3213 - accuracy: 0.8454
Epoch 4/200
23/23 [==============================] - 0s 2ms/step - loss: 0.2921 - accuracy: 0.8486
Epoch 5/200
23/23 [==============================] - 0s 2ms/step - loss: 0.2337 - accuracy: 0.8824

...

Epoch 196/200
23/23 [==============================] - 0s 1ms/step - loss: 0.1437 - accuracy: 0.9250
Epoch 197/200
23/23 [==============================] - 0s 1ms/step - loss: 0.1127 - accuracy: 0.9434
Epoch 198/200
23/23 [==============================] - 0s 2ms/step - loss: 0.0937 - accuracy: 0.9581
Epoch 199/200
23/23 [==============================] - 0s 2ms/step - loss: 0.1252 - accuracy: 0.9274
Epoch 200/200
23/23 [==============================] - 0s 1ms/step - loss: 0.1411 - accuracy: 0.9296
<tensorflow.python.keras.callbacks.History at 0x27862b0be20>
>>> test_loss, test_acc = model.evaluate(X_test, y_test, verbose = 2)
>>> print("Accuracy:", np.round(test_acc, 5))
8/8 - 0s - loss: 0.2408 - accuracy: 0.9400
Accuracy: 0.94
  • 결과를 보면 정확도 Accuracy가 0.94로 매우 높은 것을 볼 수 있다.
  • 이는 거의 같은 변수인 성별(Sex)이 존재해 그러는 것으로 보인다.
  • 만약 성별을 제외하고 모델을 학습시킨다면, Accuracy가 0.8 이하로 크게 감소하는 것을 볼 수 있다.
# 데이터에서 Sex를 제외하고 학습
>>> test_loss, test_acc = model.evaluate(X_test, y_test, verbose = 2)
>>> print("Accuracy:", np.round(test_acc, 5))
8/8 - 0s - loss: 1.0323 - accuracy: 0.8000
Accuracy: 0.8

 

 

 

 

3. Softmax의 결과

  • Softmax의 결과는 Sigmoid와 달리, 분류하고자 하는 집합의 수와 형태가 같다.
  • 10개 Dataset의 결과를 보자
>>> model.predict(X_test)[:10]
array([[7.8218585e-01, 1.8440728e-01, 3.3406798e-02],
       [2.3066834e-01, 1.6199030e-01, 6.0734141e-01],
       [2.6888084e-01, 6.2357849e-01, 1.0754070e-01],
       [4.0409532e-01, 1.8306581e-02, 5.7759809e-01],
       [9.4835693e-01, 5.1634710e-02, 8.4140474e-06],
       [9.9992132e-01, 7.8680758e-05, 2.3134878e-11],
       [2.3000217e-04, 2.4639613e-07, 9.9976975e-01],
       [5.7762786e-04, 3.2117957e-01, 6.7824280e-01],
       [2.4147890e-13, 9.9999237e-01, 7.5892681e-06],
       [6.8085140e-01, 3.1174924e-02, 2.8797367e-01]], dtype=float32)
  • 각 행에서 가장 큰 값의 위치를 반환하면, 가장 확률이 높은 값의 위치를 반환한다.
>>> np.argmax(model.predict(X_test), axis = 1)[:10]
array([0, 2, 1, 2, 0, 0, 2, 2, 1, 0], dtype=int64)
  • np.argmax(array, axis=0): array에서 가장 큰 값의 위치를 반환한다.

 

 

 

 

 지금까지 타이타닉 데이터를 이용한 기초적인 분류 모델을 만들고, 그 성능을 평가하는 부분에 대해 학습해보았다. 지금까지는 기계 학습의 대략적인 흐름을 보는 것과 흥미를 끌기 위해 빠르게 넘어갔다면, 다음 포스트부터는 조금 천천히 그리고 자세히 알아보자.

728x90
반응형
728x90
반응형

 이전 포스트에서는 범주형 데이터들을 원-핫 벡터로 바꿔서 성능 향상을 이뤄봤다. 그러나 Accuracy 0.78 > 0.79667이라는 기대에 미치지 못하는 성능 향상이 일어났다.

 이번에는 하이퍼 파라미터 튜닝을 하여, 성능을 보다 올려보도록 하겠다.

 

 

하이퍼 파라미터 튜닝

  • 하이퍼 파라미터 튜닝이 무엇인지 이전 포스트(참고)에서 살짝 다뤄보았다.
  • 이전 포스트에서 원-핫 벡터를 사용한, 데이터 셋을 만들었으나, 그 성능이 생각보다 크지 않았다.
  • 데이터 셋의 상태는 실제로 더 좋아졌으나, 적절한 하이퍼 파라미터나, 적합한 모델을 만들지 못해서 발생한 문제일 수 있다.
  • 이번엔 하이퍼 파라미터를 하나하나 잡아보도록 하자.

 

 

0. 학습 이전까지 코드 정리

# Import Module
import pandas as pd
import numpy as np
import os
from tensorflow.keras.layers import Dense
from tensorflow import keras
from copy import copy
# 필요한 Data를 모두 가져온다.
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


# 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


# 불필요한 컬럼 제거
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


# 결측값 처리
def missing_value(DF):

    # Cabin 변수를 제거하자
    del(DF["Cabin"])
    
    # 결측값이 있는 모든 행은 제거한다.
    DF.dropna(inplace = True)
    
    
# 원-핫 벡터
def one_hot_Encoding(data, column):

    # 한 변수 내 빈도
    freq = data[column].value_counts()

    # 빈도가 큰 순서로 용어 사전 생성
    vocabulary = freq.sort_values(ascending = False).index

    # DataFrame에 용어 사전 크기의 column 생성
    for word in vocabulary:

        new_column = column + "_" + str(word)
        data[new_column] = 0

    # 생성된 column에 해당하는 row에 1을 넣음
    for word in vocabulary:

        target_index = data[data[column] == word].index
        new_column = column + "_" + str(word)
        data.loc[target_index, new_column] = 1

    # 기존 컬럼 제거
    del(data[column])
    

# 스케일 조정
def scale_adjust(X_test, X_train, C_number, key="min_max"):
    
    if key == "min_max":
        
        min_key = np.min(X_train[:,C_number])
        max_key = np.max(X_train[:,C_number])
        
        X_train[:,C_number] = (X_train[:,C_number] - min_key)/(max_key - min_key)
        X_test[:,C_number] = (X_test[:,C_number] - min_key)/(max_key - min_key)
        
    elif key =="norm":
        
        mean_key = np.mean(X_train[:,C_number])
        std_key = np.std(X_train[:,C_number])
        
        X_train[:,C_number] = (X_train[:,C_number] - mean_key)/std_key
        X_test[:,C_number] = (X_test[:,C_number] - mean_key)/std_key
        
    return X_test, X_train
# Data Handling
############ Global Parameter ############
file_path = "./Dataset"
remove_list = ["Name", "Ticket"]
##########################################


# 0. Rawdata 생성
Rawdata_dict = import_Data(file_path)
Rawdata = make_Rawdata(Rawdata_dict)


# 1. 필요 없는 column 제거
DF_Hand = remove_columns(Rawdata, remove_list)


# 2. 결측값 처리
missing_value(DF_Hand)


# 3. One-Hot encoding
one_hot_Encoding(DF_Hand, 'Pclass')
one_hot_Encoding(DF_Hand, 'Sex')
one_hot_Encoding(DF_Hand, 'Embarked')


# 4. 데이터 쪼개기
# Label 생성
y_test, y_train = DF_Hand["Survived"][:300].to_numpy(), DF_Hand["Survived"][300:].to_numpy()


# 5. Dataset 생성
del(DF_Hand["Survived"])
X_test, X_train = DF_Hand[:300].values, DF_Hand[300:].values


# 6. 특성 스케일 조정
X_test, X_train = scale_adjust(X_test, X_train, 0, key="min_max")
X_test, X_train = scale_adjust(X_test, X_train, 3, key="min_max")
# 모델 생성
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"])

 

 

 

 

1. 적절한 Epochs 잡기

  • 혹시 과적합(Overfitting)이 발생한 것일지도 모르니 손실 값의 추이를 보자.
  • 모델은 적합한 epochs를 넘어 학습하게 된다면, train Dataset에 지나치게 맞춰져서, Test set을 제대로 분류하지 못하는 문제가 발생할 수 있다.
>>> model.fit(X_train, y_train, epochs = 500)

Epoch 95/500
24/24 [==============================] - 0s 997us/step - loss: 0.2396 - binary_accuracy: 0.8955
Epoch 96/500
24/24 [==============================] - 0s 1ms/step - loss: 0.2528 - binary_accuracy: 0.8892
Epoch 97/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1841 - binary_accuracy: 0.9220
Epoch 98/500
24/24 [==============================] - 0s 997us/step - loss: 0.2407 - binary_accuracy: 0.8902
Epoch 99/500
24/24 [==============================] - 0s 911us/step - loss: 0.2251 - binary_accuracy: 0.8925
Epoch 100/500
24/24 [==============================] - 0s 1ms/step - loss: 0.2491 - binary_accuracy: 0.8909

...

Epoch 195/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1727 - binary_accuracy: 0.9196
Epoch 196/500
24/24 [==============================] - 0s 997us/step - loss: 0.1872 - binary_accuracy: 0.9266
Epoch 197/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1782 - binary_accuracy: 0.9232
Epoch 198/500
24/24 [==============================] - 0s 954us/step - loss: 0.2125 - binary_accuracy: 0.9080
Epoch 199/500
24/24 [==============================] - 0s 867us/step - loss: 0.1910 - binary_accuracy: 0.9235
Epoch 200/500
24/24 [==============================] - 0s 954us/step - loss: 0.2054 - binary_accuracy: 0.9145

...

Epoch 296/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1784 - binary_accuracy: 0.9128
Epoch 297/500
24/24 [==============================] - 0s 1ms/step - loss: 0.2052 - binary_accuracy: 0.9170
Epoch 298/500
24/24 [==============================] - 0s 997us/step - loss: 0.2241 - binary_accuracy: 0.9151
Epoch 299/500
24/24 [==============================] - 0s 996us/step - loss: 0.2345 - binary_accuracy: 0.9055
Epoch 300/500
24/24 [==============================] - 0s 1ms/step - loss: 0.2218 - binary_accuracy: 0.9008

...

Epoch 395/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1688 - binary_accuracy: 0.9271
Epoch 396/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1840 - binary_accuracy: 0.9213
Epoch 397/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1711 - binary_accuracy: 0.9204
Epoch 398/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1995 - binary_accuracy: 0.9167
Epoch 399/500
24/24 [==============================] - 0s 1ms/step - loss: 0.2236 - binary_accuracy: 0.9166
Epoch 400/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1880 - binary_accuracy: 0.9280

...

Epoch 495/500
24/24 [==============================] - 0s 954us/step - loss: 0.1608 - binary_accuracy: 0.9270
Epoch 496/500
24/24 [==============================] - 0s 997us/step - loss: 0.1601 - binary_accuracy: 0.9334
Epoch 497/500
24/24 [==============================] - 0s 954us/step - loss: 0.1428 - binary_accuracy: 0.9540
Epoch 498/500
24/24 [==============================] - 0s 998us/step - loss: 0.1522 - binary_accuracy: 0.9360
Epoch 499/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1431 - binary_accuracy: 0.9410
Epoch 500/500
24/24 [==============================] - 0s 911us/step - loss: 0.1581 - binary_accuracy: 0.9440
<tensorflow.python.keras.callbacks.History at 0x1e9a8427790>
  • 손실 값의 추이를 보니, 거의 차이가 없는 것을 볼 수 있다.
  • 과적합이 의심되므로, epochs를 100으로 줄여서 다시 학습해보자.
>>> model.fit(X_train, y_train, epochs = 100)

Epoch 1/100
24/24 [==============================] - 1s 1ms/step - loss: 0.5505 - binary_accuracy: 0.7738
Epoch 2/100
24/24 [==============================] - 0s 1ms/step - loss: 0.3769 - binary_accuracy: 0.8455
Epoch 3/100
24/24 [==============================] - 0s 2ms/step - loss: 0.3293 - binary_accuracy: 0.8878
Epoch 4/100
24/24 [==============================] - 0s 2ms/step - loss: 0.3400 - binary_accuracy: 0.8485
Epoch 5/100
24/24 [==============================] - 0s 1ms/step - loss: 0.3340 - binary_accuracy: 0.8688

...

Epoch 96/100
24/24 [==============================] - 0s 963us/step - loss: 0.2319 - binary_accuracy: 0.9013
Epoch 97/100
24/24 [==============================] - 0s 1ms/step - loss: 0.2237 - binary_accuracy: 0.9102
Epoch 98/100
24/24 [==============================] - 0s 953us/step - loss: 0.2326 - binary_accuracy: 0.9151
Epoch 99/100
24/24 [==============================] - 0s 997us/step - loss: 0.2412 - binary_accuracy: 0.8963
Epoch 100/100
24/24 [==============================] - 0s 1ms/step - loss: 0.2346 - binary_accuracy: 0.9002
<tensorflow.python.keras.callbacks.History at 0x119d9265790>
>>> 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.81
  • epochs만 100으로 줄였을 뿐인데, Accuracy가 0.79667에서 0.81로 상승하였다.

 

 

 

 

2. 적절한 모델 만들기

  • 연구자의 감에 의존하는 부분이라고도 할 수 있으나, 적절한 모델 작성은 전혀 다른 결과를 가져온다.
  • 이번엔 DropoutBatchnormalization을 추가하여 모델을 학습시켜보자.
  • 위 두 내용은 꽤 심도 깊은 내용이므로 추후 자세히 설명하겠으나, 이번엔 아주 간략하게 설명하고 넘어가 보겠다.

 

Dropout

  • Dropout은 Overfitting, model combination 문제를 해결하기 위해 등장한 개념으로, 신경망의 뉴런을 랜덤 하게 부분적으로 생략시킨다.
  • 간단하게 말하자면, 신경망을 망각시킨다고 생각하는 게 좋다.

 

Bachnormalization:

  • 배치 정규화라 불린다.
  • 활성화 함수의 활성화 값이나 출력 값을 정규분포로 만들어줘, Noise를 추가하는 개념으로, 학습을 할 때마다 활성화 값이나 출력 값을 정규화하므로, 초기화(가중치 초깃값) 문제의 영향을 덜 받게 해 준다.
  • 학습률(Learning Rate)을 높게 설정할 수 있으므로, 학습 속도가 개선된다.
  • Overfitting 위험을 줄일 수 있다.
  • 가중치 소실(Gradient Vanishing) 문제를 해결해준다.

 

# module 추가 Import
from tensorflow.keras.layers import (Dense, Dropout, BatchNormalization)
# 모델 생성
model = keras.Sequential()
model.add(BatchNormalization())
model.add(Dense(128, activation = 'relu'))
model.add(Dropout(0.10))
model.add(Dense(64, activation = 'relu'))
model.add(Dropout(0.10))
model.add(Dense(32, activation = 'relu'))
model.add(Dropout(0.10))
model.add(Dense(16, activation = 'relu'))
# 마지막 Dropout은 좀 크게 주자
model.add(Dropout(0.50))
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 = 100)

Epoch 1/100
24/24 [==============================] - 1s 2ms/step - loss: 0.6060 - binary_accuracy: 0.6829
Epoch 2/100
24/24 [==============================] - 0s 2ms/step - loss: 0.4085 - binary_accuracy: 0.8484
Epoch 3/100
24/24 [==============================] - 0s 2ms/step - loss: 0.3800 - binary_accuracy: 0.8719
Epoch 4/100
24/24 [==============================] - 0s 2ms/step - loss: 0.3713 - binary_accuracy: 0.8580
Epoch 5/100
24/24 [==============================] - 0s 2ms/step - loss: 0.3626 - binary_accuracy: 0.8666
Epoch 6/100

...

Epoch 96/100
24/24 [==============================] - 0s 1ms/step - loss: 0.3144 - binary_accuracy: 0.8592
Epoch 97/100
24/24 [==============================] - 0s 2ms/step - loss: 0.3060 - binary_accuracy: 0.8716
Epoch 98/100
24/24 [==============================] - 0s 2ms/step - loss: 0.2887 - binary_accuracy: 0.8794
Epoch 99/100
24/24 [==============================] - 0s 1ms/step - loss: 0.2879 - binary_accuracy: 0.8695
Epoch 100/100
24/24 [==============================] - 0s 2ms/step - loss: 0.2805 - binary_accuracy: 0.8969
<tensorflow.python.keras.callbacks.History at 0x1fa4c5b0220>
>>> 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.8200000000000001
  • 성능이 소폭 상승하긴 하였으나, 손실 값이 이전보다 떨어진 폭이 작다.
  • 혹시나, 손실 값을 더 떨어뜨릴 수 있을지도 모르니, epochs를 올려보자.
  • Dropout과 Batchnormalization 둘 모두 overfitting 문제를 해결해주므로, 현재 필요한 epochs보다 낮은 상태일 가능성이 있다.
>>> model.fit(X_train, y_train, epochs = 200)

Epoch 1/200
24/24 [==============================] - 1s 2ms/step - loss: 0.6264 - binary_accuracy: 0.6648
Epoch 2/200
24/24 [==============================] - 0s 2ms/step - loss: 0.4572 - binary_accuracy: 0.8413
Epoch 3/200
24/24 [==============================] - 0s 2ms/step - loss: 0.3927 - binary_accuracy: 0.8649
Epoch 4/200
24/24 [==============================] - 0s 2ms/step - loss: 0.3488 - binary_accuracy: 0.8736
Epoch 5/200
24/24 [==============================] - 0s 2ms/step - loss: 0.3500 - binary_accuracy: 0.8776

...

Epoch 196/200
24/24 [==============================] - 0s 2ms/step - loss: 0.2589 - binary_accuracy: 0.8917
Epoch 197/200
24/24 [==============================] - 0s 2ms/step - loss: 0.3108 - binary_accuracy: 0.8813
Epoch 198/200
24/24 [==============================] - 0s 2ms/step - loss: 0.2486 - binary_accuracy: 0.9010
Epoch 199/200
24/24 [==============================] - 0s 1ms/step - loss: 0.2808 - binary_accuracy: 0.9001
Epoch 200/200
24/24 [==============================] - 0s 1ms/step - loss: 0.2638 - binary_accuracy: 0.9069
<tensorflow.python.keras.callbacks.History at 0x1b772b801f0>
>>> 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.8266666666666667
  • 정확도가 0.82667로 소폭 성능이 상승한 것을 볼 수 있다.

 

 

 

 

3. 최종 코드

  • 결측 값이 가장 많은 연령에 대하여, 평균 대체, 중윗값 대체, 사용하지 않음(Default)이 가능하게 코드를 수정하였다.
  • 변수 SibSp와 Parch는 등간 척도이므로, 연속형 척도이나, 표준화를 하지 않았다.
  • 그 값의 편차가 매우 작으므로, 미치는 영향은 작다고 판단되지만, 통일성을 위해 스케일 조정을 해주었다.
# Inport Module
import pandas as pd
import numpy as np
import os
from tensorflow.keras.layers import (Dense, Dropout, BatchNormalization)
from tensorflow import keras
from copy import copy




###################################### Function ######################################
# 필요한 Data를 모두 가져온다.
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


# 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


# 불필요한 컬럼 제거
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


# 결측값 처리
def missing_value(DF, key=None):

    # Cabin 변수를 제거하자
    del(DF["Cabin"])
    
    if key == "mean":
        DF["Age"] = DF["Age"].fillna(np.mean(DF["Age"]))
        
    elif key == "median":
        DF["Age"] = DF["Age"].fillna(np.median((DF["Age"].dropna())))
    
    # 결측값이 있는 모든 행은 제거한다.
    DF.dropna(inplace = True)
    
    
# 원-핫 벡터
def one_hot_Encoding(data, column):

    # 한 변수 내 빈도
    freq = data[column].value_counts()

    # 빈도가 큰 순서로 용어 사전 생성
    vocabulary = freq.sort_values(ascending = False).index

    # DataFrame에 용어 사전 크기의 column 생성
    for word in vocabulary:

        new_column = column + "_" + str(word)
        data[new_column] = 0

    # 생성된 column에 해당하는 row에 1을 넣음
    for word in vocabulary:

        target_index = data[data[column] == word].index
        new_column = column + "_" + str(word)
        data.loc[target_index, new_column] = 1

    # 기존 컬럼 제거
    del(data[column])
    

# 스케일 조정
def scale_adjust(X_test, X_train, C_number, key="min_max"):
    
    if key == "min_max":
        
        min_key = np.min(X_train[:,C_number])
        max_key = np.max(X_train[:,C_number])
        
        X_train[:,C_number] = (X_train[:,C_number] - min_key)/(max_key - min_key)
        X_test[:,C_number] = (X_test[:,C_number] - min_key)/(max_key - min_key)
        
    elif key =="norm":
        
        mean_key = np.mean(X_train[:,C_number])
        std_key = np.std(X_train[:,C_number])
        
        X_train[:,C_number] = (X_train[:,C_number] - mean_key)/std_key
        X_test[:,C_number] = (X_test[:,C_number] - mean_key)/std_key
        
    return X_test, X_train
######################################################################################






################################## Global Variable ###################################
file_path = "./Dataset"
remove_list = ["Name", "Ticket"]
######################################################################################
# Data Handling
# 0. Rawdata 생성
Rawdata_dict = import_Data(file_path)
Rawdata = make_Rawdata(Rawdata_dict)


# 1. 필요 없는 column 제거
DF_Hand = remove_columns(Rawdata, remove_list)


# 2. 결측값 처리
missing_value(DF_Hand)


# 3. One-Hot encoding
one_hot_Encoding(DF_Hand, 'Pclass')
one_hot_Encoding(DF_Hand, 'Sex')
one_hot_Encoding(DF_Hand, 'Embarked')


# 4. 데이터 쪼개기
# Label 생성
y_test, y_train = DF_Hand["Survived"][:300].to_numpy(), DF_Hand["Survived"][300:].to_numpy()


# 5. Dataset 생성
del(DF_Hand["Survived"])
X_test, X_train = DF_Hand[:300].values, DF_Hand[300:].values


# 6. 특성 스케일 조정
X_test, X_train = scale_adjust(X_test, X_train, 0, key="min_max")
X_test, X_train = scale_adjust(X_test, X_train, 1, key="min_max")
X_test, X_train = scale_adjust(X_test, X_train, 2, key="min_max")
X_test, X_train = scale_adjust(X_test, X_train, 3, key="min_max")
######################################################################################






######################################## Model #######################################
# 모델 생성
model = keras.Sequential()
model.add(BatchNormalization())
model.add(Dense(128, activation = 'relu'))
model.add(Dropout(0.10))
model.add(Dense(64, activation = 'relu'))
model.add(Dropout(0.10))
model.add(Dense(32, activation = 'relu'))
model.add(Dropout(0.10))
model.add(Dense(16, activation = 'relu'))
# 마지막 Dropout은 좀 크게 주자
model.add(Dropout(0.50))
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 = 200)

Epoch 1/200
24/24 [==============================] - 1s 2ms/step - loss: 0.6264 - binary_accuracy: 0.6648
Epoch 2/200
24/24 [==============================] - 0s 2ms/step - loss: 0.4572 - binary_accuracy: 0.8413
Epoch 3/200
24/24 [==============================] - 0s 2ms/step - loss: 0.3927 - binary_accuracy: 0.8649
Epoch 4/200
24/24 [==============================] - 0s 2ms/step - loss: 0.3488 - binary_accuracy: 0.8736
Epoch 5/200
24/24 [==============================] - 0s 2ms/step - loss: 0.3500 - binary_accuracy: 0.8776

...

Epoch 196/200
24/24 [==============================] - 0s 2ms/step - loss: 0.2589 - binary_accuracy: 0.8917
Epoch 197/200
24/24 [==============================] - 0s 2ms/step - loss: 0.3108 - binary_accuracy: 0.8813
Epoch 198/200
24/24 [==============================] - 0s 2ms/step - loss: 0.2486 - binary_accuracy: 0.9010
Epoch 199/200
24/24 [==============================] - 0s 1ms/step - loss: 0.2808 - binary_accuracy: 0.9001
Epoch 200/200
24/24 [==============================] - 0s 1ms/step - loss: 0.2638 - binary_accuracy: 0.9069
<tensorflow.python.keras.callbacks.History at 0x1b772b801f0>
>>> test_loss, test_acc = model.evaluate(X_test, y_test, verbose = 2)
>>> print("Accuracy:", np.round(test_acc, 5))
10/10 - 0s - loss: 0.7177 - binary_accuracy: 0.8200
Accuracy: 0.82
  • model.evaluate(test_set, test_label, verbose=2): 위에서 직접 만들었던, Accuracy를 한 줄의 코드로 추출 가능하다.
  • test_loss는 손실 값(loss)을 의미한다.
  • test_acc는 정확도(accuracy)를 의미한다.

 

 

 지금까지 가장 기본적인 방법을 사용해서 타이타닉 데이터를 이진 분류해보았다. 최초 정확도가 0.78이 나왔으나, 최종적으로는 0.82가 나왔다.

 타이타닉 데이터를 사용한 생존자 분류 모델의 정확도는 기본적으로 Accuracy = 0.8을 기준으로 하며, 이를 얼마나 더 잘 모델링하느냐에 따라 그 결과가 Accuracy = 1.0까지 나오기도 한다.

 이보다 더 좋은 해결 방법을 참고하고자 한다면, 캐글에서 다른 사람들의 코드를 참고해보는 것도 좋은 생각이다. 그러나, 개인적으로는 이 내용을 기반으로, 더 발전시켜보기를 바란다.

 다음 포스팅에서는 타이타닉 데이터를 사용해서 더 많은 작업을 해보도록 하겠다.

728x90
반응형
728x90
반응형

 이전 포스트에서 타이타닉 데이터를 사용해 생존자 분류 모델을 만들어보았다. 이번 포스트에서는 이전 모델보다 성능 향상을 일으켜보자.

 

 

타이타닉 데이터 생존자 분류 모델 성능 향상

  • 이전 데이터셋 생성 과정에서 결측 값 처리까지는 동일하게 실시하도록 하겠다.
  • 그러나, 문자 데이터(숫자로 표기되지만, 실제론 문자인 데이터)는 원-핫 벡터로 바꿔 모델에 학습시켜보도록 하자.

 

 

0. 이전 코드 정리

  • 문자형 데이터 처리 이전까지의 코드를 정리하자.
import pandas as pd
import numpy as np
import os
from tensorflow.keras.layers import Dense
from tensorflow import keras
from copy import copy
# csv파일을 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


# 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


# 필요 없는 컬럼 제거(DataHandling 시작)
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


# 결측값 제거
def missing_value(DF):

    # Cabin 변수를 제거하자
    del(DF["Cabin"])
    
    # 결측값이 있는 모든 행은 제거한다.
    DF.dropna(inplace = True)
# Global Parameter
file_path = "./Dataset"
remove_list = ["Name", "Ticket"]


# 0. Rawdata 생성
Rawdata_dict = import_Data(file_path)
Rawdata = make_Rawdata(Rawdata_dict)


# 1. Data Handling 시작
# 필요 없는 column 제거
DF_Hand = remove_columns(Rawdata, remove_list)

# 결측값 처리
missing_value(DF_Hand)
DF_Hand

 

 

 

 

1. 문자형 데이터 원-핫 벡터 처리

  • 위 데이터 핸들링 결과에서 문자형 데이터는 다음과 같다.
  • Pclass, Sex, Embarked 이 3개 변수는 숫자로 치환한다 할지라도, 그 숫자는 실제 숫자가 아니다.
  • 이러한, 변수를 범주형 변수라고 한다.

 

1.1. 범주형 변수(Categorical Variable)

  • 범주형 변수란, 숫자로 치환한다 할지라도, 실제로는 숫자의 정보를 갖고 있지 않는 변수를 의미한다.
  • 범주형 변수에는 문자 그 자체인 명목 척도(Nominal scale)와 순서의 정보가 존재하는 서열 척도(Ordical scale)가 존재한다.
  • 예를 들어, "물컵", "주전자", "식칼", "도마", "프라이팬", "주걱", "행주"라는 변수가 있다고 생각해보자.
  • 이 변수들을 순서대로 숫자로 치환해줬을 때, "물컵" = 1, "주전자" = 2, "식칼" = 3, "도마" = 4, "프라이팬" = 5, "주걱" = 6, "행주" = 7로 하였다고 가정해보자.
  • 이 숫자는 우리의 눈에는 숫자로 보이지만, 실제론 숫자의 특성인 연산이 불가능하며, 비교할 수가 없다.
  • 물컵의 1이 프라이팬의 5보다 우월하다고 할 수 없으며, 프라이팬의 5가 물컵 1의 5개만큼의 가치가 있다고 할 수 없다.
  • 이 예시가 "초등학교", "중학교", "고등학교", "대학교", "대학원"으로 서열의 개념이 생긴다 할지라도, 그 간격이 일정하지 않으므로, 이러한 데이터를 연산할 수 없다.
  • 즉, 문자형 데이터는 단순하게 숫자로 치환해주는 걸로 끝내선, 실제 그들이 가지고 있는 의미를 제대로 담아낼 수가 없다는 소리다!

 

 

 

 

2. 원-핫 벡터(One-Hot Vector)

  • 나중에 인코딩 파트에서 다시 한번 다루겠지만, 원-핫 벡터는 가장 대표적인 문자를 벡터로 바꾸는 기법 중 하나다.
  • 원-핫 벡터를 만드는 과정은 원-핫 인코딩(One-Hot Encoding)이라 부른다.
  • 원-핫 벡터의 순서는 다음과 같다.
  1. 한 변수 안에 있는 중복을 제거한(Unique) 문자들을 대상으로 고유 번호를 매긴다.
  2. 이를 기반으로 희소 벡터를 생성한다.

 

2.1 정수 인코딩

  • 1. 과정을 "정수 인코딩"이라 한다.
  • 정수 인코딩은 앞서 우리가 범주형 변수(Categorical Variable)를 숫자로 치환해주는 과정과 동일하다.
  • 때론 이 정수 인코딩 시, 빈도를 고려하여, 인코딩 순서를 바꾸기도 한다.
  • 중복이 없는 단어와 숫자를 매칭 시켜 나온 결과물을 용어사전(Vocabulary)라고 한다.

 

2.2 희소 벡터 만들기

  • 희소 벡터란 표현하고자 하는 인덱스는 1로 나머지는 0으로 이루어진 벡터를 의미한다.
  • 원-핫 벡터는 생성된 용어사전(Vocabulary)을 기반으로 희소 벡터를 만드는 방법이다.
  • 예를 들어 다음과 같은 용어사전이 있다고 가정해보자.
  • Vocabulary = ["감자", "고구마", "피망", "사과", "딸기"] = [0,1,2,3,4]
  • 여기서 "피망"의 희소 벡터는 다음과 같다.
  • 피망 = [0, 0, 1, 0, 0]

 

2.3. 원-핫 인코딩의 한계점

  • 용어 사전의 크기가 크면 클수록 벡터의 크기가 커지므로, 벡터 저장을 위한 필요 공간이 커진다.
  • 즉, 단어가 1,000개라면, 단어 1,000개 모두 벡터의 크기가 1,000이므로, 입력될 텐서가 지나치게 커진다.
  • 단어를 단순하게 숫자로 바꾸고 해당 인덱스를 1로 나머지를 0으로 만든 것이므로, 의미, 단어 간 유사도를 표현하지 못한다.

 

 

 

 

3. 문자형 변수를 One-Hot 벡터로 치환해보자.

  • 원-핫 벡터 생성은 그 알고리즘이 상당히 단순하므로, 직접 구현해보도록 하겠다.
  • 생성될 원-핫 벡터는 대상 변수의 구성 원소의 빈도를 감안하여 생성하도록 하겠다.
  • DataFrame을 기반으로 작업하였으므로, DataFrame의 성질을 이용해보자.
def one_hot_Encoding(data, column):

    # 한 변수 내 빈도
    freq = data[column].value_counts()

    # 빈도가 큰 순서로 용어 사전 생성
    vocabulary = freq.sort_values(ascending = False).index

    # DataFrame에 용어 사전 크기의 column 생성
    for word in vocabulary:

        new_column = column + "_" + str(word)
        data[new_column] = 0

    # 생성된 column에 해당하는 row에 1을 넣음
    for word in vocabulary:

        target_index = data[data[column] == word].index
        new_column = column + "_" + str(word)
        data.loc[target_index, new_column] = 1

    # 기존 컬럼 제거
    del(data[column])
one_hot_Encoding(DF_Hand, 'Pclass')
one_hot_Encoding(DF_Hand, 'Sex')
one_hot_Encoding(DF_Hand, 'Embarked')
DF_Hand1

  • 위 코드는 DataFrame의 특징을 이용한 것으로, 각 변수별로 원소의 수가 많은 칼럼 순으로 먼저 생성한다.
  • 생성한 칼럼은 0으로 가득 채운다.
  • 원본 칼럼에서 각 원소에 해당하는 칼럼에 1을 채운다.

 

 

 

 

4. 데이터를 쪼개고 연속형 데이터의 스케일 조정을 해보자.

# 데이터 쪼개기
# Label 생성
y_test, y_train = DF_Hand["Survived"][:300].to_numpy(), DF_Hand["Survived"][300:].to_numpy()

# Dataset 생성
del(DF_Hand["Survived"])
X_test, X_train = DF_Hand[:300].values, DF_Hand[300:].values

 

  • 이전에는 연속형 데이터 셋에 최소-최대 스케일 변환만 적용하였으나, 이번엔 표준 정규 분포화도 할 수 있도록 짜 보자.
def scale_adjust(X_test, X_train, C_number, key="min_max"):
    
    if key == "min_max":
        
        min_key = np.min(X_train[:,C_number])
        max_key = np.max(X_train[:,C_number])
        
        X_train[:,C_number] = (X_train[:,C_number] - min_key)/(max_key - min_key)
        X_test[:,C_number] = (X_test[:,C_number] - min_key)/(max_key - min_key)
        
    elif key =="norm":
        
        mean_key = np.mean(X_train[:,C_number])
        std_key = np.std(X_train[:,C_number])
        
        X_train[:,C_number] = (X_train[:,C_number] - mean_key)/std_key
        X_test[:,C_number] = (X_test[:,C_number] - mean_key)/std_key
        
    return X_test, X_train
X_test, X_train = scale_adjust(X_test, X_train, 0, key="min_max")
X_test, X_train = scale_adjust(X_test, X_train, 3, key="min_max")
>>> X_test[0]
array([0.27345609, 1.        , 0.        , 0.01415106, 1.        ,
       0.        , 0.        , 1.        , 0.        , 1.        ,
       0.        , 0.        ])
  • 원-핫 벡터를 사용했을 때, 이전 모델과의 차이를 보기 위해, 이번에도 변수의 표준화는 최소-최대 스케일 변화를 실시하였다.

 

 

 

 

5. 학습 후 결과를 비교해보자.

# 모델 생성
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 1ms/step - loss: 0.5498 - binary_accuracy: 0.7345
Epoch 2/500
24/24 [==============================] - 0s 1ms/step - loss: 0.4263 - binary_accuracy: 0.8497
Epoch 3/500
24/24 [==============================] - 0s 1ms/step - loss: 0.2957 - binary_accuracy: 0.8976
Epoch 4/500
24/24 [==============================] - 0s 1ms/step - loss: 0.3229 - binary_accuracy: 0.8750
Epoch 5/500
24/24 [==============================] - 0s 1ms/step - loss: 0.2964 - binary_accuracy: 0.8851
Epoch 6/500
24/24 [==============================] - 0s 1ms/step - loss: 0.3451 - binary_accuracy: 0.8758

...

Epoch 496/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1697 - binary_accuracy: 0.9294
Epoch 497/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1827 - binary_accuracy: 0.9142
Epoch 498/500
24/24 [==============================] - 0s 997us/step - loss: 0.1731 - binary_accuracy: 0.9337
Epoch 499/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1876 - binary_accuracy: 0.9143
Epoch 500/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1641 - binary_accuracy: 0.9322
<tensorflow.python.keras.callbacks.History at 0x21c06cd4790>
>>> 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.7966666666666666
  • 이전 모델의 Accuracy가 0.78이 나왔으며, 범주형 데이터를 One-Hot Vector로 바꾼 이번 모델은 Accuracy가 0.79667로 소폭 상승하였다.

 

 

 범주형 데이터를 원-핫 벡터로 바꿔 성능이 소폭 상승 하긴 하였으나, 만족스러운 수준까지 성장하진 않았다. 다음 포스트에서는 하이퍼 파라미터 튜닝을 통해 성능을 보다 올려보도록 하자.

728x90
반응형
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
반응형
728x90
반응형

 이전 포스트에서 캐글에서 타이타닉 데이터를 다운로드하였다. 이번 포스트에서는 타이타닉 데이터를 파이썬으로 불러오고, 데이터가 어떻게 생겼고, 어떤 변수가 있는지를 확인해보자.

 

 

타이타닉 데이터 가져오기

  • 이전에 받았던 타이타닉 데이터가 어떻게 생겼는지 보고, 변수들을 파악해보자.

 

 

1. 작업 파일 이동시키기

  • 만약, 작성자와 같은 주피터 노트북 사용자라면, 아래와 같이 작업 파일과 같은 경로 안에 Data를 넣는 폴더를 만들어, 데이터를 넣어놓자.

  • 현재 작업 중인 주피터 노트북 파일인 Report04_210209.ipynb와 같은 경로에 Dataset이라는 파일을 새로 만들었다.

  • 이전에 다운로드하였던 titanic 압축파일 안에 있던 3 파일 "gender_submission.csv", "test.csv", "train.csv"을 Upload 시키자.

 

 

 

 

2. 데이터 불러오기

import pandas as pd
import numpy as np
import os
# Global Variable
file_path = "./Dataset"
# Function
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
Rawdata_dict = import_Data(file_path)
  • os.listdir(디렉터리): 있는 파일 list를 가지고 온다.
  • pd.read_csv(파일 경로): 있는 csv파일을 가지고 온다.
  • 데이터를 이름으로 하나하나 불러오지 않고, 특정 디렉터리 안에 있는 모든 파일들을 해당 파일의 이름으로 딕셔너리에 넣어 가지고 왔다.
  • 이렇게 데이터 프레임을 딕셔너리로 관리하는 경우, 특정 목적에 맞는 데이터들을 보다 쉽게 관리할 수 있으며, 데이터의 이름을 특정 패턴을 가진 상태로 부여할 수 있다.
  • 또한, 한 번에 특정 디렉터리 내 모든 파일들을 모두 가져올 수 있으므로, 데이터를 가지고 올 때도 꽤 편하다.

 

 

 

 

3. 데이터가 어떻게 생겼는지 보도록 하자.

>>> dict_key = list(Rawdata_dict.keys())
>>> dict_key
['gender_submission', 'test', 'train']

>>> Rawdata_dict[dict_key[0]]

  • gender_submission은 Row(행) 418개, Column(열, 변수) 2개로 구성된 데이터다.
  • PassengerID: 승객 ID이다.
  • Survived: 생존 여부로, 0 = 사망, 1 = 생존이다.

 

test.csv에 담긴 데이터를 보자

>>> Rawdata_dict[dict_key[1]]

  • test는 Row(행) 418개, Column(열, 변수) 11개로 구성된 데이터다.
  • PassengerID: 고객 번호
  • Pclass: 티켓의 등급이다. 1 =1st(Upper), 2 = 2nd(Middle), 3 = 3rd(Lower)이다.
  • Name: 승객의 이름
  • Sex: 성별
  • Age: 연령
  • SibSp: 타이타닉호에 탑승한 형제/배우자의 수
  • Parch: 타이타닉호에 탑승한 부모/자녀의 수
  • Ticket: 티켓 번호
  • Fare: 승객 요금
  • Cabin: 객실 번호
  • Embarked: 기항지 위치, C(Cherbourg), Q(Queenstown), S(Southampton)으로 3곳이 있다.

 

train.csv에 담긴 데이터를 보자

>>> Rawdata_dict[dict_key[2]]

  • train은 Row(행) 891개, Column(열, 변수) 12개로 구성된 데이터다.
  • 변수의 구성은 test와 동일하나, test와 달리 Survived라는 변수가 있다.
  • test와 gender_submission은 PassengerID도 동일하며 Row의 수도 418개로 동일하다.
  • 즉, test Dataset의 Survived는 gender_submission에 있는 것임을 알 수 있다.

 

 

 

 지금까지 타이타닉 데이터를 불러와서 구성하고 있는 데이터에 대해 차근차근 살펴보았다. 다음 포스트에서는 본격적으로 데이터 핸들링을 하여, 생존자 예측 모델을 만들어보도록 하겠다.

728x90
반응형
728x90
반응형

캐글(Kaggle)이란?

 빅데이터 분석에 관심이 있는 사람이라면, 한 번쯤 캐글(Kaggle)에 대해 들어봤을 것이다.

 캐글은 2010년 설립된 예측모델 및 분석대회 플랫폼으로, 기업 및 단체에서 데이터와 해결과제를 등록하면, 데이터 과학자들이 이를 해결하는 모델을 개발하고 경쟁하는 곳이다. 2017년 3월 구글에 인수되었다(위키피디아)

  • 캐글(Kaggle)은 말 그대로 빅데이터 분석가들의 사냥터라고 할 수 있는데, 빅데이터 분석가들의 사냥감인 데이터가 널려 있으며, 서로서로 그 데이터를 얼마나 잘 요리했는지를 비교할 수도 있다.

 

 

 

1. Competitions

  • 캐글 입장 후, Compete를 보면, 수많은 상금이 걸린 도전 과제들이 있는 것을 볼 수 있다.
  • 여기서 마음에 드는 과제를 선택하면 "Join Competition"이라는 버튼이 생기는 것을 볼 수 있는데, 이를 클릭해서, 해당 대회에 참여할 수 있다.

  • 위 사진에서 각 버튼은 다음 기능을 한다.
  1. Overview: 문제에 대한 소개와 정의
  2. Data: 예측 모델 생성에 필요한 데이터셋과 Feature가 되는 Fields가 설명되어 있으며, 대회에 쓰일 데이터 셋을 다운로드할 수 있다.
  3. Code: 대회 참가 시, 캐글에서 제공하는 서버에서 작업할 수 있게 해 주며, 다른 사람의 코드를 참고할 수 있음.
  4. Discussion: 질의응답 공간
  5. Leaderboard: 모델의 정확도를 기준으로 랭킹이 매겨지는 곳
  6. Dadataset: 관련 데이터 셋을 볼 수 있다.
  • 대회 진행 방식은 데이터를 다운로드하여 내 PC에서 작업하거나 캐글에서 제공하는 서버에 접속해 작업을 하는 방식이 있다.

 

  • 대회 참가 후, Code를 클릭하면 New Notebook을 눌러, 커널에 접속할 수 있다.

  • 여기서 코드를 작성할 수 있으며, 그 코드가 정상적으로 실행된다면, Commit 하여, 결과를 업로드하고 정확도를 기반으로 점수를 확인할 수 있다.
  • 상위 랭킹에 들어간다면, 그에 대한 대회의 보상을 받을 수도 있다고 하니, 실력도 늘리고, 용돈 벌이도 할 겸 해서 한 번쯤 해보는 것을 추천한다.

 

 

 

 

2. 타이타닉 데이터

  • 이번에 학습에 사용해볼 데이터인 타이타닉 데이터를 구해보자.
  • 위 과정을 통해 직접 찾아갈 수도 있으나, 이 버튼을 눌러서 바로 이동할 수도 있다.

 

  • 타이타닉 데이터의 변수별 정보는 다음과 같다.

  • 이곳에서 Titanic Data를 다운로드할 수 있으며, 데이터의 칼럼 별 개형 등을 볼 수도 있다.
  • 데이터의 각 변수에 대한 정보를 최대한 얻은 다음 분석을 시작하도록 하자.

 

 

 

 이밖에도 캐글은 커뮤니티나 빅데이터 분석의 기반이 되는 것들을 공부할 수 있는(Courses) 공간도 따로 제공하므로, 많이 사용해보도록 하자.

 다음 포스트에서는 이번에 받은 타이타닉 데이터를 이용해서, 생존자 예측 모델을 만들어보도록 하겠다.

728x90
반응형

+ Recent posts