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

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

 

 

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

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

 

 

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

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

 이전 포스트에서는 학습 단위에 대한 단어인 에포크(Epoch), 배치 크기(Batch size), 이터레이션(Iteration)에 대해 알아보았다. 이번 포스트에서 알아볼 확률적 경사 하강법(SGD)의 키는 배치 크기와 랜덤 추출이다.

 경사 하강법에 다른 식을 붙이지 않고 바로 사용하는 방법은 크게 두 가지인 배치 경사 하강법(BGD)과 확률적 경사 하강법(SGD)이 있는데, 이 둘은 손실 함수의 기울기 계산에 사용되는 데이터 셋의 규모만 제외하고 같다.

 중요한 것은 손실 함수의 경사를 구하는 대상이다!

 

 

1. 배치 경사 하강법(Batch Gradient Descent, BGD)

  • 배치 경사 하강법(BGD)은 경사 하강법의 손실 함수의 기울기 계산에 전체 학습 데이터셋의 크기와 동일하게 잡는 방법이다.
  • 즉, 경사 하강법 대상이 배치 크기와 동일하다는 것이다.
  • 데이터셋 모두를 대상으로 하다 보니 파라미터가 한번 이동할 때마다, 계산해야 할 값이 지나치게 많으므로, 계산 시간도 엄청 길어지고, 소모되는 메모리도 엄청나다.
  • mini batch 안 모든 데이터를 대상으로 경사 하강법을 실시하므로, 안정적으로 수렴한다.

  • 안정적으로 수렴하므로, 수렴까지 발생하는 총 파라미터 업데이트 수는 매우 적다.
  • 안정적으로 수렴하는 것은 좋으나, 안정적으로 움직이기 때문에 지역 최소해(Local Minimum)에 빠지더라도 안정적으로 움직이므로 빠져나오기 힘들다. 즉, Local Optima(minimum) 문제가 발생할 가능성이 높다.
  • 학습 데이터셋이 커지면 커질수록 시간과 리소스 소모가 지나치게 크다.

 

 

 

 

2. 확률적 경사 하강법(Stochastic Gradient Descent, SGD)

  • 전체 훈련 데이터셋을 대상으로 학습하는 것은 한정된 리소스를 가지고 있는 우리의 분석 환경에서 매우 비효율적이며, 파라미터 업데이트 수가 적다는 것은 랜덤 하게 뽑힌 시작 위치의 가중치 수도 적으므로, Local minimum 현상이 발생할 확률도 높다는 것이다.
  • 그래서 나온 방법이 학습 데이터셋에서 무작위로 한 개의 샘플 데이터 셋을 추출하고, 그 샘플에 대해서만 기울기를 계산하는 것이다.
  • 샘플 데이터 셋에 대해서만 경사(Gradient)를 계산하므로, 매 반복에서 다뤄야 할 데이터 수가 매우 적어, 학습 속도가 매우 빠르다.
  • 하나의 샘플만 대상으로 경사를 계산하므로, 메모리 소모량이 매우 낮으며, 매우 큰 훈련 데이터 셋이라 할지라도 학습 가능하다.
  • 그러나, 무작위로 추출된 샘플에 대해서 경사를 구하므로, 배치 경사 하강법보다 훨씬 불안정하게 움직인다.

  • 손실 함수가 최솟값에 다다를 때까지 위아래로 요동치며 움직이다 보니, 학습이 진행되다 보면, 최적해에 매우 근접하게 움직이긴 하겠으나, 최적해(Global minimum)에 정확히 도달하지 못할 가능성이 있다.
  • 그러나, 이렇게 요동치며 움직이므로, 지역 최솟값(Local minimum)에 빠진 다할지라도, 지역 최솟값에서 쉽게 빠져나올 수 있으며, 그로 인해 전역 최솟값(Global minimum)을 찾을 가능성이 BGD에 비해 더 높다.
  • 즉, 확률적 경사 하강법(SGD)은 속도가 매우 빠르고 메모리를 적게 먹는다는 장점이 있으나, 경사를 구할 때, 무작위성을 띄므로 지역 최솟값에서 탈출하기 쉬우나, 전역 최솟값에 다다르기 힘들다는 단점을 가지고 있다.
  • 이 문제를 해결하기 미니 배치 경사 하강법(mini-Batch gradient descent)이 등장하였다.

 

학습률 스케줄(Learning rate schedule)

  • 전역 최솟값에 도달하기 어렵다는 문제를 해결하기 위한 방법으로, 학습률을 천천히 줄여 전역 최솟값에 다다르게 하는 방법이 있다.
  • 학습률은 작아질수록 이동하는 양이 줄어들기 때문에 전역 최솟값에 안정적으로 수렴할 수 있다.
  • 만약 학습률이 너무 급격하게 감소하면, Local Optima 문제나 Plateau 현상이 발생할 가능성이 높아진다.
  • 그렇다고 학습률을 너무 천천히 줄이면 최적해 주변을 맴돌 수 있다.

 

 

 

 

3. 미니 배치 경사 하강법(mini-Batch gradient descent)

  • 앞서 이야기한 배치 경사 하강법(BGD)나 확률적 경사 하강법(SGD)은 모두 배치 크기가 학습 데이터 셋 크기와 동일하였으나, 미니 배치 경사 하강법은 배치 크기를 줄이고, 확률적 경사 하강법을 사용하는 기법이다.
  • 예를 들어, 학습 데이터가 1000개고, batch size를 100으로 잡았다고 할 때, 총 10개의 mini batch가 나오게 된다. 이 mini batch 하나당 한 번씩 SGD를 진행하므로, 1 epoch당 총 10번의 SGD를 진행한다고 할 수 있다.
  • 일반적으로 우리가 부르는 확률적 경사 하강법(SGD)은 실제론 미니 배치 경사 하강법(mini-BGD)이므로, 지금까지 학습했던 차이들은 기억하되, 앞으로 SGD를 말하면, 미니 배치 경사 하강법을 떠올리면 된다.

  • 미니 배치 경사 하강법은 앞서 이야기했던, 전체 데이터셋을 대상으로 한 SGD보다 파라미터 공간에서 Shooting이 줄어들게 되는데, 이는 한 미니 배치의 손실 값 평균에 대해 경사 하강을 진행하기 때문이다.
  • 그로 인해, 최적해에 더 가까이 도달할 수 있으나, Local optima 현상이 발생할 수 있다. 그러나, 앞서 말했듯 Local optima 문제는 무수히 많은 임의의 파라미터로부터 시작되면, 해결되는 문제이며, 학습 속도가 빠른 SGD의 장점을 사용하여, 학습량을 늘리면 해결되는 문제다.
  • 배치 크기는 총 학습 데이터셋의 크기를 배치 크기로 나눴을 때, 딱 떨어지는 크기로 하는 것이 좋다.
  • 만약, 1050개의 데이터에 대하여 100개로 배치 크기를 나누면, 마지막 50개 데이터셋에 대해 과도한 평가를 할 수 있기 때문이다.
  • 그러나, 만약 배치 크기로 나누기 애매한 경우라면, 예를 들어 총 학습 데이터 셋이 1,000,050개가 있고, 배치 크기를 1,000개로 나누고 싶은 경우라면, 나머지인 50개는 버리도록 하자(물론 완전 무작위 하게 50개를 선택해서 버려야 한다.).

 

 

 

  지금까지 확률적 경사 하강법(SGD)에 대해 알아보았다. 본래의 SGD는 "배치 크기 = 학습 데이터 셋 크기"이지만, 일반적으로 통용되는 SGD는 "배치 크기 < 학습 데이터 셋 크기"인 미니 배치를 만들어 학습시키는 미니 배치 경사 하강법이다. 

 경사 하강법의 파이썬 코드화는 경사 하강법 함수 자체는 단순하지만, 학습에서 발생하는 모든 알고리즘이 복합적으로 작동하므로, 코드화시키는 것은 시간 낭비로 판단된다. Optimizer 파트부턴 그 개념과 특징을 이해하고, 텐서플로우로 학습을 해보도록 하자.

 다음 포스트에서는 경사 하강법의 한계점을 보완하기 위한 시도 중 하나인 모멘텀(Momentum)에 대해 학습해보도록 하겠다.

728x90
반응형
728x90
반응형

 이전 포스트에서는 경사 하강법의 한계점에 대해 학습해보았다. 이번 포스트에서는 경사 하강법의 실행 과정을 살펴보고, 기본 사용 방법인 배치 경사 하강법(Batch Gradient Descent)이 어떤 단점을 가지고 있기에 최적화 기법의 기반이 되는 경사 하강법인 확률적 경사 하강법(Stochastic Gradient Descent, SGD)이 나오게 되었는지를 알아보고자 하였으나, 이 과정을 쉽게 이해하려면 먼저 학습이 일어나는 구조와 학습 단위에 대한 개념을 알아야 한다.

 

 

1. 학습의 구조

  • 학습은 기본적으로 다음과 같은 구조로 움직인다.
  1. 임의의 매개변수(가중치)를 정한다.
  2. 선택된 매개변수로 손실 값을 구하고, 손실 함수의 기울기(Gradient)를 계산한다.
  3. 계산된 기울기와 학습률(Learning Rate)을 이용해 다음 가중치의 위치로 이동하여, 파라미터를 업데이트한다.
    이때, 이동 거리는 경사 하강법 공식을 통해 구해진다.
    $$ \theta_{n+1} = \theta_n - \eta \bigtriangledown f(\theta_n) $$
  4. 이동된 지점에서 손실 함수의 기울기(Gradient)를 계산하고, 3.과정을 다시 실시한다.
  5. 손실함수의 기울기가 최솟값에 도달하면, 파라미터 업데이트를 멈춘다.

 

 

 

 

2. 학습 단위

  • 그런데, 위 과정을 보다 보면 한 가지 의문이 든다.
  • 바로, 기울기 계산이 엄청 많이 일어난다는 것인데, 우리가 기계를 학습시킬 때 사용하는 빅 데이터는 일반적으로 최소 1,000만 건 이상을 가리키며, 1억, 10억 건 이상 데이터도 심심치 않게 등장한다는 것이다.
  • 이렇게 많은 데이터를 한 번에 모델에 태우게 된다면, 아무리 좋은 컴퓨터라도 버티지 못할 것이다.
  • 한 번의 학습에 모든 학습 데이터셋을 사용한다면, 여러 문제를 일으킨다.
  1. 데이터의 크기가 너무 큰 경우, 메모리가 너무 많이 필요해진다.
  2. 학습 한 번에 계산돼야 할 파라미터(가중치) 수가 지나치게 많아지므로 계산 시간이 너무 오래 걸린다.
  • 여기서 Epoch, Batch size, iteration라는 개념이 등장하게 된다.

 

 

 

 

3. Epoch(에포크)

  • Epoch의 네이버 영어 사전 뜻은, "(중요한 사건·변화들이 일어난) 시대"라는 뜻이다.
  • 훈련 데이터셋에 포함된 모든 데이터들이 한 번씩 모델을 통과한 횟수로, 모든 학습 데이터셋을 학습하는 횟수를 의미한다.
  • 1 epoch는 전체 학습 데이터셋이 한 신경망에 적용되어 순전파와 역전파를 통해 신경망을 한 번 통과했다는 의미가 된다.
  • 즉 epoch가 10회라면, 학습 데이터 셋 A를 10회 모델에 학습시켰다는 것이다.
  • epoch를 높일수록, 다양한 무작위 가중치로 학습을 해보므로, 적합한 파라미터를 찾을 확률이 올라간다.
    (즉, 손실 값이 내려가게 된다.)
  • 그러나, 지나치게 epoch를 높이게 되면, 그 학습 데이터셋에 과적합(Overfitting)되어 다른 데이터에 대해선 제대로 된 예측을 하지 못할 수 있다.

 

 

 

 

4. Batch size(배치 사이즈)

  • Batch의 네이버 영어 사전 뜻은 "(일괄적으로 처리되는) 집단", "한 회분(한 번에 만들어 내는 음식 기계 등의 양)", "(일괄 처리를 위해) 함께 묶다"라는 의미가 있다.
  • 즉, 연산 한 번에 들어가는 데이터의 크기를 가리킨다.
  • 1 Batch size에 해당하는 데이터 셋을 mini Batch라고 한다.
  • 1회 epoch 안에 m 개($m \geq 1$)의 mini Batch가 들어가게 되며, 만약, m = 1인 경우, 배치 학습법이라고 한다.
  • 배치 사이즈가 너무 큰 경우 한 번에 처리해야 할 데이터의 양이 많아지므로, 학습 속도가 느려지고, 메모리 부족 문제가 발생할 위험이 있다.
  • 반대로, 배치 사이즈가 너무 작은 경우 적은 데이터를 대상으로 가중치를 업데이트하고, 이 업데이트가 자주 발생하므로, 훈련이 불안정해진다.

 

 

 

 

5. Iteration(이터레이션)

  • Iteration은 네이버 영어사전에서 "(계산·컴퓨터 처리 절차의) 반복"이라는 뜻이다.
  • 전체 데이터를 모델에 한번 학습시키는데 필요한 배치의 수를 말한다.
  • 즉, 1 epoch를 마치는데 필요한 파라미터 업데이트 횟수라 할 수 있다.
  • 각 배치마다 파라미터 업데이트가 한 번씩 이루어지므로, Iteration은 "파라미터 업데이트 횟수 = 배치의 수"가 된다.

 

 

 

 

※ 참고

 만약, 데이터셋이 너무 거대해서 전체를 메모리에 올리는 것만으로도 부하가 걸릴 정도라면, 배치 학습 방법을 하되, 한 번에 학습할 학습 데이터 셋의 크기를 줄이고, for문으로 실제 batch를 만들고, pickle로 파일로 만들어 놓은 데이터 셋을 일부씩 불러와 batch에 학습시키고, 모든 데이터 셋을 불러와 한번 학습하는 것을 epoch로 잡는 방식도 있다.

 위 글만으로는 이해가 가지 않을 수 있으므로, 나중에 기회가 된다면 이를 자세히 다뤄보도록 하겠다.

 

 

 

 

 이번 포스트에서는 학습 단위로 사용되는 단어인 Epoch, Batch size, mini batch, Iteration에 대해 알아보았다. 다음 포스트에서는 배치 경사 하강법(BGD)과 확률적 경사 하강법(SGD)에 대해 학습해보도록 하겠다.

 
728x90
반응형
728x90
반응형

 지난 포스트에서는 기계학습에서 사용되는 최적화 알고리즘인 경사 하강법에 대하여 살펴보았다. 이번 포스트에서는 경사 하강법의 한계점에 대해 학습해보도록 하겠다.

 

 

경사 하강법의 한계점

  • 앞서 손실함수를 기반으로 경사 하강법의 개형을 그려보았으나, 실제로는 저렇게 깔끔한 이차 함수 형태를 그리지 않는다.
  • 퍼셉트론의 공식이 활성화 함수를 타게 되면, 손실 함수의 모습은 거시적인 관점에서 봤을 때는 최적해를 1개 가진 이차 함수의 형태를 그리긴 하지만, 그 모습이 울퉁불퉁해져 최적해에 수렴하기 어려워진다.
  • 이번 포스트에서는 경사하강법의 한계점에 대해 하나하나 짚고 넘어가 보도록 하겠다.

 

 

 

 

1. 데이터가 많아질수록 계산량 증가

  • 앞서, 경사하강법(Gradient Descent)은 신경망에서 출력되는 예측값(Predict)과 실제값(Label)의 차이인 손실 함수(Loss Function)의 값을 최소화하는 것이 목적이다.
  • 그러나, 학습용 데이터 셋이 많아진다면, 당연히 계산량도 무지막지하게 많아지게 되는데, 그로 인해 학습 속도가 매우 느려지게 된다.
  • 기계학습에는 아주 거대한 빅데이터가 사용되게 되는데, 이러한 퍼포먼스 문제는 결코 무시할 수 없는 문제다.

 

 

 

 

2. Local minimum(Optima) 문제

  • 앞서 그린 대략적인 손실함수의 개형은 굉장히 매끈하였으나, 활성화 함수로 인해 그 모양이 울퉁불퉁해지게 되고, 그로 인해 최적해에 수렴하지 못할 수 있다.
  • 아래 그래프를 보도록 하자.

  • 실제 손실함수의 모양은 위 그래프보다 울퉁불퉁한 정도가 심하나 이해를 돕기 위해 일부분만 가져와봤다.
  • 위 그래프에서 $\alpha$를 전역 최소해(Global minimum), $\beta$를 지역 최소해(Local minimum)라 한다.

 

  • 경사 하강법의 목적은 손실 함수에서 랜덤 하게 선택한 가중치를 미분하여 나온 결과를 힌트로 해서, 최적해를 찾아가는 것인데, 위 그래프처럼 만약 랜덤 하게 선택된 가중치가 Local minimum 가까이에 있고, Local minimum에 수렴해버리면, 실제 목표인 Global  minimum을 찾지 못하는 문제가 발생할 수 있다.

 

  • 만약, 학습률(Learning Rate)을 너무 크게 설정한다면, Global minimum에 가까운 곳에서 시작한다 할지라도, 구간을 뛰어넘어 Local minimum에서 수렴할 수도 있다.
  • 그러나, 실제로는 모델의 학습이 지역 최소값(Local minimum)에 빠져, 최적의 가중치를 못 찾는 일이 발생할 위험은 그리 크지 않다.

  • 학습 시 가중치를 초기화하여 반복하여 최적해를 찾아가므로, $\beta$에서 수렴하여 Loss값이 0 가까이 떨어지지 못한다할지라도, 시작 위치가 다른 가중치에서 전역 최소값(Global minimum)에 수렴하여 Loss값이 0에 수렴할 수 있다.
  • 즉, 모든 초기화된 가중치가 지역 최솟값에 수렴할 수 있는 위치에 존재하지 않는다면, 지역 최솟값 문제는 발생하지 않는다. 그러므로, Local minimum 현상의 발생 위험은 그리 크지 않다고 할 수 있다.

 

 

 

 

3. Plateau 문제

  • 1. Local minimum 문제의 예시에서는 손실함수의 모양이 전반적으로 곡선을 그렸으나, 손실 함수의 안에는 평탄한 영역이 존재하기도 한다.

  • 위 그래프에서 Plateau(플래튜)라고 불리는 평탄한 영역에서는 학습 속도가 매우 느려지며, 느려지다 못해 정지해버릴 위험이 존재한다.
  • 경사 하강법의 공식을 보면, "현 지점의 기울기 X 학습률"을 통해 다음 가중치를 결정하는데, 평탄한 영역의 기울기는 매우 낮기 때문에 이동거리가 갈수록 줄어들게 되고, 그로 인해 더 이상 학습이 일어나지 않는 가중치 소실(Gradient Vanishing) 현상이 발생할 수 있다.
  • 이러한 Plateau 현상이 발생하면, 극솟값에 수렴하지 못해, 학습 시간이 매우 길어지고, 경사하강법의 랜덤 한 가중치에서 현재의 기울기를 힌트로 기울기가 0인 극솟값에 수렴시켜 최적해를 찾는다는 알고리즘이 제대로 작동하지 못하게 된다.

 

 

 

 

4. Zigzag 문제

  • 지금까지 경사하강법을 설명할 때, 이해하기 용이하도록 가중치($w$)가 1개만 있는 2차원 그래프를 사용했으나, 실제론 가중치의 수가 매우 많다. 이번엔 가중치가 2개인($w_1, w_2$) 3차원 그래프를 등고선으로 그려보자.

  • 위 그래프는 2개의 매개변수($w_1, w_2$)에 대한 손실 함수를 등고선으로 그린 것이다.
  • 가중치의 스케일(크기)이 동일하다면, 최적해로 바로 찾아갈 수 있으나, 가중치는 모르는 임의의 값이므로, 스케일이 동일하리란 보장이 없다.
  • 만약, 가중치 스케일이 다르다면, 다음과 같은 현상이 발생하게 된다.

  • 두 매개변수 $w_1$의 스케일이 $w_2$보다 크다보니, 손실 함수는 $x$축 방향 가중치인 $w_1$의 변화에 매우 둔감하고, $y$축인 $w_2$의 변화에 매우 민감하다.
  • 즉, $w_2$의 크기가 $w_2$에 비해 매우 작다보니, $w_2$가 조금만 변해도 손실 함수는 크게 변하게 되어, 두 매개변수의 변화에 따른 손실 함수 변화가 일정하지 않다.
  • 위 경우는 매개변수가 2개밖에 존재하지 않았으나, 실제에서는 그 수가 수백만개에 달할 수 있을 정도로 많기 때문에 이러한 Zigzag 현상은 더욱 복잡해지며, 그로 인해 최적해를 찾아가기가 어려워지고, 학습 시간 역시 길어지게 된다.

 

 

 

 

 지금까지 경사하강법의 문제점에 대해 알아보았다. 머신러닝에서는 위 문제들을 해결하기 위해 경사 하강법을 효율적으로 사용하기 위한 최적화 기법(Optimizer)들이 매우 많다.

 예를 들어 다음 포스트에서 학습할 SGD나 가장 많이 사용되는 Adam, Momentum, Adagrad 등이 있는데, 각 최적화 알고리즘들은 데이터의 형태에 따라 그에 맞는 방법을 사용하길 바란다.

 다음 포스트에서는 최적화 기법의 가장 기초가 되는 확률적 경사 하강법(Stochastic Gradient Descent, SGD)에 대해 학습해보도록 하겠다.

 

 

[참조]

towardsdatascience.com/demystifying-optimizations-for-machine-learning-c6c6405d3eea

 

Demystifying Optimizations for machine learning

Optimization is the most essential ingredient in the recipe of machine learning algorithms. It starts with defining some kind of loss…

towardsdatascience.com

www.programmersought.com/article/59882346228/

 

[2017CS231n] SEVEN: train the neural network (under) - Programmer Sought

First look at the sixth lecture: Data preprocessing. When we have the data normalized red, classifier weight matrix perturbation is not particularly sensitive, more robust. The left classifier little changes that will undermine the classification results.

www.programmersought.com

nittaku.tistory.com/271?category=742607

 

11. Optimization - local optima / plateau / zigzag현상의 등장

지난시간까지는 weight 초기화하는 방법에 대해 배웠다. activation func에 따라 다른 weight초기화 방법을 썼었다. 그렇게 하면 Layer를 더 쌓더라도 activation value(output)의 평균과 표준편차가 일정하게 유

nittaku.tistory.com

 

728x90
반응형
728x90
반응형

 지금까지 가중치를 평가하는 방법인 손실함수(Loss Function)에 대해 학습해보았다. 그렇다면, 어떻게 손실함수를 기반으로 최적의 가중치를 찾아낼까?

 이번 포스트에서는 손실함수로부터 어떻게 경사 하강법이 나오게 되었는지를 이야기해보고, 경사하강법을 위주로 설명해보도록 하겠다.

 

 

손실함수와 경사하강법

1. 최적화(Optimizer)

  • 최적화는 손실함수(Loss Function)의 결과값을 최소화하는 가중치를 찾아내는 것이 목적이다.
  • 그렇다면, 손실함수의 개형은 어떻게 생겼을까?
  • 예상한 값과 실제값의 차이인 제곱 오차(SE)를 가지고, 손실함수의 개형을 보도록 하자.

$$ SE = (y - \hat{y})^2 $$

  • 이해하기 쉽도록 예측값을 변수(가중치)가 1개만 있는 퍼셉트론을 가져와보자
  • 예측값은 선형이다(변수는 가중치인 $w$이며, $x$와 상관 없이, 가산성과 동차성이 성립한다.)

$$ \hat{y} = wx + b $$

  • 제곱 오차의 $\hat{y}$에 예측값을 대입하고 식을 풀어보자.

$$ (y - \hat{y})^2 = (y - (wx + b))^2 = y^2 -2y(wx + b) + (wx + b)^2 = w^2x^2 + 2wxb + b^2 - 2wxy - 2yb + y^2 $$

  • 위 식에서 변수는 $w$이므로, 위 함수는 1개의 최적해를 갖는 이차 함수 형태인 것을 알 수 있다.
  • 그러므로, 손실함수의 개형은 다음과 같다.

 

 

 

 

2. 경사하강법(Gradient Descent)

  • 경사 하강법은 1차 미분계수를 이용해 함수의 최소값을 찾아가는 방법으로, 함수 값이 낮아지는 방향으로 독립 변수 값을 변형시켜가면서 최종적으로 최소 함수 값을 갖도록 하는 독립 변수 값을 찾는 방법이다.
  • 위에서 보듯, 손실함수의 개형은 1개의 최적해를 갖는 2차 함수의 형태이므로, 경사하강법을 사용하여, 최소 함수 값을 갖도록 하는 최적해(가중치)를 탐색해야한다.
  • 경사 하강법은 임의의 가중치를 설정하여, 그 점에서의 기울기를 계산하고, 그 기울기를 힌트로 기울기가 0인 지점을 찾아간다.
  • 손실 함수의 부호를 반전시켜, 최댓값을 찾는다면 경사 상승법(Gradient Ascent)이 되나, 동일한 것이므로, 굳이 사용하지 않는다.

  • 위 그럼처럼 기울기는 손실함수에서 임의의 가중치에서 시작하며, 기울기가 음수인 경우에는 양의 방향으로 이동하고, 기울기가 양수인 경우에는 음의 방향으로 이동하여, 극솟값을 찾아간다.
  • 여기서 움직이는 기울기(경사)는 가중치에 대하여 편미분 한 벡터이고, 이 가중치를 조금씩 움직인다.

 

 

 

 

3. 경사하강법 공식

  • 경사 하강법을 공식으로 써보면 다음과 같다.

$$ x_{i+1} = x_i - \eta\bigtriangledown f(x_i) $$

  • 여기서 $\eta$(eta, 에타)는 학습률(Learning Rate)이라 하며, 한 번의 학습에서 얼마나 이동할지를 정한다.
  • $\bigtriangledown$는 벡터 미분 연산자로 델(del) 연산자라 하며 이 기호를 나블라(nabla) 기호라고 한다.
  • 스칼라 함수 $f(x)$의 기울기는 $\bigtriangledown f$로 표현한다.
  • 기울기는 $f$의 각 성분의 편미분으로 구성된 열 벡터로 정의하고, 아래와 같이 표시한다.

$$ \bigtriangledown f = (\frac{\partial f}{\partial x_1}, ..., \frac{\partial f}{\partial x_n}) $$

$$ \bigtriangledown f = (\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}) $$

  • 예를 들어 함수 $f(x, y, z) = 2x + 3y^2 - sin(z)$의 기울기는 다음과 같다.

$$ \bigtriangledown f = (\frac{\partial f}{\partial x} + \frac{\partial f}{\partial y} + \frac{\partial f}{\partial z}) =(2, 6y, -coas(z)) $$

 

기울기 (벡터) - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 둘러보기로 가기 검색하러 가기 물매는 여기로 연결됩니다. 무기에 대해서는 무릿매 문서를 참조하십시오. 위의 두 그림에서는 회색의 밝기가 스칼라계의 크

ko.wikipedia.org

  • 즉, 경사하강법 공식은 현재의 위치 $x_i$에 학습률 $\eta$에 그 위치에서의 기울기 $\bigtriangledown f(x_i)$만큼을 곱한 값을 뺀만큼 위치를 이동시켜 다음 위치 $x_{i+1}$로 이동한다는 소리다.
  • 여기서 학습률과 기울기 곱($\eta\bigtriangledown f(x_i)$)을 빼는 이유는 현재의 기울기의 반대방향으로 이동하여, 극소값에 도달하기 위해서이다.

 

 

 

 

4. 학습률(Learning Rate, LR)

4.1. 경사 하강법 공식에서의 학습률의 영향

  • 위 경사 하강법의 공식에서 중요한 것은 학습률(Laerning Rate)인데, 이 학습률에 따라 경사 하강법 시, 이동하는 수준이 달라지게 된다.
  • 예를 들어, 기울기가 2.5이고 학습률이 0.01이라면, 경사하강법 알고리즘은 이전 지점으로부터 0.025 떨어진 지점을 다음 지점으로 결정한다.
  • 즉, "이동 거리 = 학습률 X 기울기"로 움직인다. 이는 기울기가 낮다면 학습률이 높다할지라도 움직이는 거리가 줄어든다는 소리이고, 큰 고랑에 빠진다면, 거기서 나오지 못하고 수렴할 수 있다는 소리다.

 

4.2. 학습률이 낮은 경우

  • 학습률이 낮다면, 이동하는 거리가 짧으며, 경사하강법 공식에 의해 이동할수록 기울기가 더욱 감소하므로, 짧은 이동 거리가 더 짧아진다.
  • 그로 인해, 경사 하강법 알고리즘이 수렴하기 위해 반복해야하는 데이터 양이 많아지므로, 학습 시간이 늘어나게 된다.

 

4.3. 학습률이 높은 경우

  • 학습률이 지나치게 큰 경우, 크게 이동하므로, 수렴이 빨리 발생해 학습 시간이 적게 걸린다.
  • 그러나, 너무 크게 이동하므로, 전역 최솟값(Global minimum)이 있는 영역을 건너 뛰어 지역 최솟값에서 수렴할 수도 있다.
    (이 부분은 다음 포스트에서 세세하게 다루도록 하겠다.)

 

 

 

 

 지금까지 손실함수를 최소화하는 방법으로 어째서 경사하강법을 사용하는지와 경사하강법이 어떠한 구조로 움직이는지에 대하여 학습해보았다.

 다음 포스트에서는 경사하강법의 한계점에 대해 학습해보도록 하겠다.

728x90
반응형

+ Recent posts