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

 지난 포스트에서 데이터 셋에 대해 간략히 설명해보았다. 이번 포스트부터 본격적으로 텐서플로우를 사용해서, 내가 찾아내고 싶은 알고리즘을 찾아내 보자.

 

 

학습 목표

  • 분석가가 알고 있는 패턴으로 데이터를 생성하고, 그 패턴을 찾아내는 모델을 만들어보자.
  • Input이 1개, Output이 1개인 연속형 데이터에서 패턴을 찾아보자.

 

 

 

1. 데이터 셋 생성

  • 패턴: $f(x) = x + 10$
# Module 설정
import pandas as pd
import numpy as np
from tensorflow import keras
from tensorflow.keras.layers import Dense
def f(x):
    return x + 10
    
# Data set 생성
np.random.seed(1234)   # 동일한 난수가 나오도록 Seed를 고정한다.
X_train = np.random.randint(0, 100, (100, 1))
X_test = np.random.randint(100, 200, (20, 1))

# Label 생성
y_train = f(X_train)
y_test = f(X_test)

 

데이터 셋 생성 코드의 함수 설명

  1. np.random.seed(int):  난수(랜덤 한 데이터) 생성 시, 그 값은 생성할 때마다 바뀌게 된다. 데이터 셋이 바뀌게 되면, 일관된 결과를 얻기가 힘들어, 제대로 된 비교가 힘들어지므로, 난수를 생성하는 방식을 고정시킨다. 이를 시드 결정(Set seed)이라 하며, 숫자는 아무 숫자나 넣어도 상관없다.
  2. np.random.randint(시작 int, 끝 int, shape): 시작 숫자(포함)부터 끝 숫자(미포함)까지 shape의 형태대로 array를 생성한다.

 

데이터 셋 생성 코드 설명

  1. Train set은 0~100까지의 숫자를 랜덤으로 (100, 1)의 형태로 추출하였다.
  2. Test set은 100~200까지의 숫자로 랜덤으로 (20, 1)의 형태로 추출했다. 여기서 값은 Train set과 절대 겹쳐선 안된다.
  3. Label 데이터인 y_train과 y_test는 위에서 설정된 함수 f(x)에 의해 결정되었다.

 

  • train 데이터 생김새(가시성을 위해 10개까지만 출력)
# train Dataset을 10개까지만 가져와보자
>>> X_train[:10]

array([[47],
       [83],
       [38],
       [53],
       [76],
       [24],
       [15],
       [49],
       [23],
       [26]])
       
>>> X_train.shape
(100, 1)
  • 생성된 데이터 셋의 형태는 "(데이터 셋 수, 변수의 수)"라고 인지해도 좋다.
  • 여기서 "변수의 수"는 "데이터 하나의 벡터 크기"라고 생각하는 것이 더 적합하다.
  • 기본적으로 Tensorflow에 Input 되고 Output 되는 데이터의 형태는 이렇다고 생각하자.

 

 

 

 

2. 모델 생성하기

  • tensorflow를 사용해 모델을 생성하는 경우, tensorflow가 아닌 keras를 사용하게 된다.
  • 위에서 tensorflow의 기능을 가져올 때, 아래와 같은 코드로 가져왔다.
  • from tensorflow import keras
  • 이는, tensorflow라는 프레임워크에서 keras라는 모듈을 가지고 온다는 의미이다.
  • keras는 추후 설명하게 될지도 모르지만, 모델 생성 및 학습에 있어 직관적으로 코드를 짤 수 있게 해 주므로, 쉽게 tensorflow를 사용할 수 있게 해 준다.
  • 물론, keras와 tensorflow는 태생적으로 서로 다른 프레임워크이므로, 이 둘이 따로 에러를 일으켜, 에러 해결을 어렵게 한다는 단점이 있긴 하지만, 그걸 감안하고 쓸만한 가치가 있다.
model = keras.Sequential()
model.add(Dense(16, activation='relu'))
model.add(Dense(1, activation='linear'))
  • keras를 사용해서 모델을 만드는 방법은 크게 2가지가 있다.
  • 하나는 위 같이 add를 이용해서 layer를 하나씩 추가해 가는 방법이 있고
model = keras.Sequential([
    Dense(16, activation='relu'),
    Dense(1, activation='linear')
])
  • 이렇게 keras.Sequential([]) 안에 층(layer)을 직접 넣는 방법이 있다.
  • 처음 방법처럼 add를 사용하는 방법은 API 사용 방법이고, 아래와 같이 층을 Sequential([])에 직접 넣는 방식은 Layer 인스턴스를 생성자에게 넘겨주는 방법이라 하는데, 전자인 API를 사용하는 방법을 개인적으로 추천한다.
  • 그 이유는 다중-아웃풋 모델, 비순환 유향 그래프, 레이어 공유 모델 같이 복잡한 모델 정의 시, 매우 유리하기 때문으로, 이는 나중에 다루겠으나, 이 것이 Tensorflow의 장점이다.

 

모델 생성 코드 함수 설명

  1. keras.Sequential(): 순차 모델이라 하며, 레이어를 선형으로 연결해 구성한다. 일반적으로 사용하는 모델로 하나의 텐서가 입력되고 출력되는 단일 입력, 단일 출력에 사용된다. 다중 입력, 다중 출력을 하는 경우나, 레이어를 공유하는 등의 경우엔 사용하지 않는다.
  2. model.add(layer): layer를 model에 층으로 쌓는다. 즉, 위 모델은 2개의 층을 가진 모델이다.
  3. Dense(노드 수, 활성화 함수): 완전 연결 계층으로, 전, 후 층을 완전히 연결해주는 Layer다. 가장 일반적으로 사용되는 Layer다.

 

모델 생성 코드 설명

  1. 해당 모델은 Input 되는 tensor도 1개 Output 되는 tensor도 1개이므로, Sequential()로 모델을 구성했다.
  2. 은닉층에는 일반적으로 ReLU 활성화 함수가 사용된다고 하니, ReLU를 넣었다.
  3. 출력층에는 출력 결과가 입력 값과 같은 노드 1개이므로, 노드 1개로 출력층을 만들었다. 
  4. 일반적으로 Node의 수를 $2^n$으로 해야 한다고 하지만, 크게 상관없다는 말이 있으므로, 굳이 신경 쓰지 않아도 된다. 처음엔 자기가 넣고 싶은 값을 넣다가, 성능이 안 나온다 싶으면 바꿔보는 수준이니 크게 신경 쓰지 말자.
  5. 사용된 활성화 함수(activation)는 일반적으로 은닉층에 ReLU를 넣고, 연속형 데이터이므로 출력층에 Linear를 넣어보았다.

 

 

 

 

3. 모델 컴파일하기

  • 컴파일은 모델을 학습시키기 전에 어떤 방식으로 학습을 시킬지를 설정하는 과정이다.
opt = keras.optimizers.Adam(learning_rate=0.01)
model.compile(optimizer=opt, loss = 'mse')

 

코드 설명

  1. keras.optimizers.Adam(): 최적화에 사용할 함수를 위처럼 외부에서 만들어서 넣는 경우, 학습률, 모멘텀 같은 인자들을 입맛에 맞게 바꿀 수 있다.
  2. model.complie(): 학습 방식을 설정한다.

 

compile은 기본적으로 3가지 인자를 입력으로 받는다.

  1. optimizer: 최적화하는 방법으로, 경사 하강법(GD)을 어떤 방법을 통해 사용할지를 결정한다. 일반적으로 Adam이 많이 사용된다.
  2. loss: 손실 함수를 설정한다. 일반적으로 연속형 데이터라면 제곱 오차 시리즈를, 분류 데이터라면 교차 엔트로피 오차 시리즈를 사용한다.
  3. metric: 기준이 되는 것으로, 분류를 할 때 주로 사용한다.
  • 손실 함수와 최적화에 관심이 있다면 다음 포스트(손실 함수, 최적화)를 참고하길 바란다.

 

 

 

 자, 지금까지 학습을 위한 모델 세팅을 완료하였다. 다음 포스트에서는 위 코드들을 깔끔하게 정리하고, 실제 학습을 해보겠다.

728x90
반응형
728x90
반응형

 Tensorflow를 사용하는 사람 중 상당 수가 Github에서 다른 사람들이 어떤 목적을 위해 만들어놓은 모델을 그저 가져오거나, 남이 만든 모델에서 노드 크기를 수정하거나, 상황에 맞게 레이어를 바꿔보고, 내가 인공지능을 사용할 수 있다고 생각하는 경우가 많다.

 마치 통계 분석을 할 때, "서로 다른 두 집단이 있고, 그 집단에 대한 평균을 비교해보고 싶다면, t-test를 사용해야한다."라 생각하듯, 머신러닝에 접근하면, 인공지능을 단순한 마법의 상자로 생각해버릴 수 있다.

 흔히들 인공지능을 "내가 무언가를 넣으면, 원리는 잘 모르겠지만, 정답이 나오는 마법의 상자"라고 생각하는 경향이 있는데, 인공지능은 단순한 블랙박스가 아닌, 사용자가 의도를 가지고 설계한 것에 맞는 결과를 도출해주는 알고리즘이다.

 그러므로, 제대로 인공지능을 다루고자 한다면, 인공지능이 할 수 있는 영역 안에서 내가 원하는 결과를 이끌어낼 수 있어야 한다.

 

 

 

 

1. 데이터셋 생성하기

  • 분석가라면, 상황에 맞는 실험용 데이터 셋 만들기는 기본 중 기본이다.
  • 데이터셋은 크게 훈련(Train), 시험(Tes), 검증(Validation) 데이터 셋으로 나뉜다.

 

훈련 데이터 셋(Train Dataset)

  • 신경망 훈련 시 사용되는, 모델 학습 용 데이터 셋으로, 수능을 보기 위해 공부하는 문제집에 해당한다.
  • 과도하게 훈련 데이터셋을 학습시키는 경우, 과적합(Overfitting) 현상이 발생하여, 훈련 데이터 셋은 잘 분류하나, 시험 데이터 셋이나 실제 데이터에는 적합하지 않을 수 있다.
  • 훈련 데이터 셋은 모델의 기준이 된다!

 

시험 데이터 셋(Test Dataset)

  • 모델의 성능을 최종적으로 평가하기 위한 데이터 셋으로 실제 데이터 셋이다. 고등학교의 최종 목적 시험인 수능에 해당한다.
  • 훈련 데이터 셋과 시험 데이터 셋은 중첩되지 않는 것이 좋다.
  • 예를 들어, 데이터를 날짜별로 뽑아낼 수 있다면, 시험 데이터 셋은 다른 날짜의 데이터 셋을 사용하는 것이 좋다.
  • 시험 데이터 셋과 모델이 예측한 결과를 비교해 정확도(Accuarcy), 정밀도(Precision), 재현율(Recall), F1 점수를 계산하여, 모델이 얼마나 잘 만들어졌는지를 확인해볼 수 있다.

 

검증 데이터 셋(Validation Dataset)

  • 학습을 할 때, 학습이 얼마나 잘 돼는지를 평가하는 수단으로, 공부가 잘되었는지를 평가하는 모의고사에 해당 한다.
  • Development Dataset이라고도 불린다.
  • 검증 데이터 셋은 학습 시, 학습된 모델의 성능 평가에 사용되며, 그 결과가 파라미터에 반영된다.
  • 즉, 검증 데이터 셋의 목적은 학습 데이터에 의해 학습된 파라미터 중, 실제 데이터에도 잘 맞을 수 있도록 최적의 파라미터를 찾아낼 수 있도록, 파라미터를 튜닝하기 위해 존재한다고 할 수 있다.
  • 검증 데이더 셋은 학습 데이터 셋에서 분리되며, 때에 따라 검증 데이터 셋을 만들지 않고, 전부 훈련 데이터에 사용할 수도 있다.
  • 물론, 검증 데이터 셋을 사용하는 경우 성능이 더 좋다고 한다.
  • 학습 데이터 셋과 검증 데이터 셋은 그 내용이 중첩되지 말아야 한다. 만약 중첩되는 경우, 이 현상을 leakage라고 한다(학습 데이터 셋과 검증 데이터셋에 교집합 존재).

 

 

 

 

 

2. 과대 적합 피하기

  • 모델이 훈련(Train) 데이터 셋에 대해선 분류가 잘되었으나, 시험(Test) 데이터 셋에 대해 구분을 지나치게 못한다면, 과대 적합일 가능성이 있다.
  • 이는, 훈련 데이터 셋에 모델이 지나치게 맞춰져, 새로운 데이터에 대해 일반화가 되지 못한다는 소리로, 모델이 지나치게 훈련 데이터 셋에만 맞춰진, 모델의 분산이 큰 상태라고 할 수 있다.
  • 이를 해결하는 방법은 다음과 같다.
  1. 훈련 데이터를 늘린다.
  2. Dropout과 같은 규제를 실시하여 복잡도를 줄인다.
  3. 데이터의 차원을 줄인다.
  4. 모델을 보다 간략화시켜 파라미터의 수를 줄인다.
  • 위 내용은 꽤 심도 깊은 영역이므로, 이는 추후 자세히 다루도록 하겠다.

 

 

 

 

3. 데이터 셋의 비율

  • 일반적으로 훈련(Train) 데이터셋과 시험(Test) 데이터셋의 비율은 7:3으로 나누며, 훈련 데이터의 안에서도 학습 도중 모델을 평가할 검증(Validation) 셋을 학습 데이터 셋에서 떼어내기도 한다. 이 경우, 일반적으로 훈련 데이터셋과 검증 데이터셋의 비율을 8:2로 한다고 한다.

  • 그러나, 위 비율은 절대로 절대적인 것이 아니며, 총데이터의 양과 훈련 데이터 셋과 시험 데이터 셋의 형태 차이 등에 따라 그 비율은 위와 크게 다를 수 있다.
  • 학습 데이터는 내가 원하는 특징이 잘 들어가 있는 깔끔한 데이터일 수 있으나, 실제 이 모델을 이용해 분류될 대상인 시험 데이터 셋엔 상당한 노이즈가 들어가 있을 수 있다.
  • 예를 들어, 우리가 학습에 사용한 데이터는 증명사진이지만, 실제 사람들이 이 인공지능에 사용할 사진은 온갖 바탕과 포토샵 등 우리가 학습 시 고려하지 않은 노이즈가 들어가 있을 수 있다.
  • 이를 방지하기 위해, 때에 따라 시험 데이터 셋이 없이 모두 학습 데이터로 사용하거나, 학습 데이터에 의도적으로 노이즈를 부여하기도 한다.
  • 데이터 셋의 양이 매우 적다면(예를 들어, 데이터의 수가 1만에도 못 미친다면), 위 비율대로 나눠도 상관없으나, 데이터의 양이 매우 많다면(데이터의 수가 100만 이상이라면), 테스트 데이터 셋이나 검증 데이터 셋의 비율을 0.1~1%로 잡기도 한다.
  • 즉, Valid Dataset과 Test Datset의 목적은 생소한 데이터를 이용해 모델을 일반화시키기 위한 것이므로, 그 비중이 그리 크지 않아도 된다(물론 Test Dataset은 최종 평가지만, 간접적으로 영향을 미치므로).

 

 

 

 다음 포스트에서는 실제로 데이터 셋을 생성하고, 이를 이용해서 학습을 해보고, 그 성능을 평가해보자!

728x90
반응형

+ Recent posts