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

+ Recent posts