본문 바로가기
Tensorflow

Softmax Regression Tutorial with Tensorflow Keras

by 청양호박이 2020. 2. 10.

이번에는 Softmax Regression에 대해서 알아보겠습니다. 지난시간에 알아본 Logistic Regression은 범주형 종속변수를 가지는 아이였고, 그 범주는 0 or 1 / True or False 처럼 2가지의 범주를 가지고 있는 특징을 가졌습니다. 그렇다면 범주가 3개 이상일때는 어떻게 해야할까요??

 

그래서 알아보고자 하는 것이... Softmax Regression 입니다. Logistic Regression은 아래의 그림과 같이, 2개의 그룹으로 분류가 가능하고 이를 A라는 hypothesis로 구현이 가능합니다.

Logistic Regression

그렇다면 Softmax Regression은 아래와 같이, 3개 이상의 그룹으로 분류가 가능하고... 이는 분류하고자 하는 그룹의 수만큼의 hypothesis가 필요합니다. 각 hypothesis의 역할은 아래와 같습니다.

  • A : 0인 그룹과 나머지를 구분 (1그룹, 2그룹)
  • B : 1인 그룹과 나머지를 구분 (0그룹, 2그룹)
  • C : 2인 그룹과 나머지를 구분 (0그룹, 1그룹)

Softmax Regression

이제 개념을 알아봤으니... 역시 ML(Machine Learning)을 구현하려면 hypothesis와 Loss를 알아봐야겠죠?? 이번에는 세부적인 공식 보다는 개념만 알아보도록 하겠습니다. 왜냐하면, 이미 Tensorflow에서 Activation Function 및 Loss함수를 메서드로 제공하기 때문입니다.

 

 

1. Softmax (Hypothesis)


기존에 Logistic Regression에서는 다음과 같이 Hypothesis를 구했습니다.

입력변수에 대해서 W(가중치)와 Matrix곱을 구해서 1개의 Label이 도출되면... 이를 0~1사이의 값을 도출할 수 있는 Sigmoid 함수를 적용해서 최종 Label을 구했습니다. 물론 이를 산출하기 위해서 Odds함수를 적용하고 여기에 log를 취해주고 여러가지 계산을 했었지만 결론은 이것 입니다.

 

그렇다면 Softmax Regression에서는, 그냥 나와야 하는 Label이 많아졌고 각 경우에 대한 확률을 도출하는 문제로 생각하면 좀 쉽게 다가와 집니다. 다만 여기에 추가되는게... Label들의 합을 1로 했을때, 각 Label에 대한 확률의 비율로 변경해 주는게 Softmax라고 생각하면 됩니다.

 

여기서 a = A / (A+B+C), b = B / (A+B+C), c = C / (A+B+C) 입니다. 하지만 실제 문제해결때는... tensorflow에서 제공하는 메서드를 사용하겠습니다.

 

 

2. Cross Entropy (Loss)


Logistic Regression에서 Loss는 0이면 0, 1이면 1 이 나올 확률을 최대화 하는 방법으로 구했습니다. Softmax Regression 이라고 다를게 없습니다. 각 범주형 원소마다 각 각 나올 확률을 최대화 하면 됩니다. 설명으로도 간단한데 실제 구현도 간단합니다.

 

기존에 Logistic Regression의 Loss는 아래의 형태를 띄었습니다.

 

Loss = E(w) = - ∑ y * ln h(x) + (1-y) * ln (1 - h(x))

 

그럼 각 범주형 원소마다 나올 확률을 최대화 한 Cross Entropy의 Loss는 아래와 같습니다.

 

Loss = E(w) = -  y * ln h(x)

 

어라... 왜 Sum이 2개나 붙나요!! 실제로 y의 형태는 [0, 1, 2] 이렇게 생기겠지만, 이렇게 하면 프로그램은 이해를 하기 어렵습니다. 따라서 matrix형태로 만들어 줘야하는데 이게 바로 One-hot Encoding이라고 합니다. 이렇게 되면 [0, 1, 2]는 다음과 같이 표시가 됩니다.

 

[[1, 0, 0], [0, 1, 0], [0, 0, 1]]

 

느낌이 오실수도 있는데, 범주의 개수만큼 원소가 필요하고 해당되는 위치의 원소만 1로 하고 나머지는 다 0으로 세팅되는 방식입니다. 따라서, 결국 아무리 범주가 많더라도 해당되는 부분만 ln h(x)가 계산되기 때문에 Sum을 한번 더 적용하는 것입니다.

 

 

3. One-hot Encoding


위에 말했듯이, 이 방법은 원하는 범주에는 인덱스에 1을 부여하고... 나머지들은 0을 부여하는 방식입니다. 이렇게 되면, 단 하나의 값만 True로, 나머지는 False로 인코딩이 됩니다. 결국 원하는 1개의 값을 추출하기 위한 방법이라고 생각하면 됩니다.

 

그렇다면, 이를 어떻게 tensorflow에서 구현하는지에 대해서 알아보겠습니다.

 

[using Numpy]

target = [0,1,2,1,0]

mode = np.eye(3)
print(mode)

one_hot_target = mode[target]
print(one_hot_target)

#############################
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]
 [0. 1. 0.]
 [1. 0. 0.]]

우선 범주의 개수의 크기와 동일한 대각행렬을 만듭니다. 이는 np.eye( )로 생성이 가능합니다. 그리고 target의 원소를 가지고 mode의 위치를 추출해서 새로운 list를 생성합니다. 이렇게 One-hot Encoding이 가능합니다.

 

[using Keras]

from tensorflow.keras.utils import to_categorical

target = [0,1,2,1,0]
one_hot_target2 = to_categorical(target)
print(one_hot_target2)

#################################################
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]
 [0. 1. 0.]
 [1. 0. 0.]]

keras의 utils에서 제공하는 메서드인 to_categorical을 사용하면... 범주 데이터를 넣으면 알아서 One-hot Encoding된 결과를 리턴해줍니다.

 

 

4. with Tensorflow 2.0


그렇다면 지금까지 알아본 내용을 모두 모아서 Tensorflow 2.0으로 구현해 보도록 하겠습니다.

 

[Data 준비]

import numpy as np
import matplotlib.pyplot as plt

train_x = [[1,1],[1,3],[2,2],[2,4],[3,1],[3,3],[4,2],[4,4],
           [1,11],[1,13],[2,12],[2,14],[3,11],[3,13],[4,12],[4,14],
           [5,7],[5,9],[6,6],[6,8],[7,7],[7,9],[8,6],[8,8]]
train_y = [[0],[0],[0],[0],[0],[0],[0],[0],
           [1],[1],[1],[1],[1],[1],[1],[1],
           [2],[2],[2],[2],[2],[2],[2],[2]]

train_x = np.array(train_x, dtype=np.float32)
train_y = np.array(train_y, dtype=np.float32)

plt.scatter(train_x[:,0:1], train_x[:,1:2], c=train_y)
plt.show()

[Label에 대한 One-hot Encoding]

from tensorflow.keras.utils import to_categorical

train_y = to_categorical(train_y)

[Model class 생성]

class softmaxWithTF():
    def __init__(self):
        self.epochs = 1000
        self.learning_rate = 0.015
        self.w = tf.Variable(tf.random.normal(shape=[2,3], dtype=tf.float32))
        self.b = tf.Variable(tf.random.normal(shape=[1,3], dtype=tf.float32))

    def train_on_batch(self, x, y):
        with tf.GradientTape() as tape:
            logit = tf.matmul(x, self.w) + self.b
            hypothesis = tf.nn.softmax(logit)
            loss = -tf.reduce_mean(tf.reduce_sum(y*tf.math.log(hypothesis), 1))

        loss_dw, loss_db = tape.gradient(loss, [self.w, self.b])

        self.w.assign_sub(self.learning_rate * loss_dw)
        self.b.assign_sub(self.learning_rate * loss_db)

        return loss

    def fitModel(self, x, y):
        dataset = tf.data.Dataset.from_tensor_slices((x,y))
        dataset = dataset.shuffle(buffer_size=24).batch(8)

        loss_mem = []

        for e in range(self.epochs):
            for step, (x,y) in enumerate(dataset):
                loss = self.train_on_batch(x,y)
            loss_mem.append(loss)
        return loss_mem

    def predictModel(self, x):
        logit = tf.matmul(x, self.w) + self.b
        hypothesis = tf.nn.softmax(logit)
        return tf.argmax(hypothesis, 1)

tf.nn.softmax( ) 함수를 적용했습니다. 이때 들어가는 인자값은 Logistic Regression에 들어가는 logit과 동일하게 작성합니다. 또한 loss도 앞에 알아본 아이를 그대로 적용합니다.

 

나머지는 기존과 동일하기 때문에 생략하겠습니다.

 

[fitModel]

model = softmaxWithTF()
loss_mem = model.fitModel(train_x, train_y)

epochs_x = list(range(len(loss_mem)))
plt.plot(epochs_x, loss_mem)
plt.show()

학습은 총 1000 epochs를 진행했고... 적당한 수준으로 수렴하였습니다.

 

[predictModel]

res_y = model.predictModel(train_x)

print(res_y.numpy())

res_y = np.expand_dims(res_y.numpy(), axis=1)

plt.scatter(train_x[:,0:1], train_x[:,1:2], c=res_y)
plt.show()

train_x를 넣어서 얼마나 잘 예측했는지 확인해 보겠습니다. 

[0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2]

정확하게 100% 판별해 냈습니다. 또한 scatterplot으로 그려보면,

완전 동일함을 확인 할 수 있습니다.

 

 

5. with Keras


to be continue....

 

-Ayotera Lab-

댓글