[텐서플로우로 시작하는 딥러닝 기초] Lab 06-1: Softmax classifier 를 TensorFlow 로 구현하기

2020. 12. 5. 23:26머신러닝

import tensorflow as tf
import numpy as np

print(tf.__version__)

tf.random.set_seed(777)  # for reproducibility

x_data = [[1, 2, 1, 1],
          [2, 1, 3, 2],
          [3, 1, 3, 4],
          [4, 1, 5, 5],
          [1, 7, 5, 5],
          [1, 2, 5, 6],
          [1, 6, 6, 6],
          [1, 7, 7, 7]]
y_data = [[0, 0, 1],
          [0, 0, 1],
          [0, 0, 1],
          [0, 1, 0],
          [0, 1, 0],
          [0, 1, 0],
          [1, 0, 0],
          [1, 0, 0]]

#convert into numpy and float format
x_data = np.asarray(x_data, dtype=np.float32)
y_data = np.asarray(y_data, dtype=np.float32)

코드를 이해해 봅시다~

4개의 Feature, 8개의 Batch와 3개의 Label이네요~  x_data는 (8, 4) y_data는 (8, 3).

 

#Weight and bias setting
W = tf.Variable(tf.random.normal(4, 3), name='weight')
b = tf.Variable(tf.random.normal(3), name='bias')
variables = [W, b]

그럼 Weight는 (4, 3)인 행렬일테고, Bias는 3 짜리 벡터겠지요~ 4개의 feature와 3개의 label이니까.

<tf.Variable 'weight:0' shape=(4, 3) dtype=float32, numpy=

array([[ 0.7706481 , 0.37335402, -0.05576323],

[ 0.00358377, -0.5898363 , 1.5702795 ],

[ 0.2460895 , -0.09918973, 1.4418385 ],

[ 0.3200988 , 0.526784 , -0.7703731 ]], dtype=float32)>

<tf.Variable 'bias:0' shape=(3,) dtype=float32, numpy=array([-1.3080608 , -0.13253094, 0.5513761 ], dtype=float32)>

# tf.nn.softmax computes softmax activations
# softmax = exp(logits) / reduce_sum(exp(logits), dim)
def hypothesis(X):
    return tf.nn.softmax(tf.matmul(X, W) + b)

print(hypothesis(x_data))

hypothesis를 정의해주는데, 저번에 Sigmoid 처럼 직접 함수를 만들 수 있지만 이번에는 softmax를 씌워줍니다. tf.nn.softmax()를 이용하네요.

여기에 x_data를 넣어봤더니

tf.Tensor(

[[1.36571955e-02 7.90162385e-03 9.78441238e-01]

[3.92597765e-02 1.70347411e-02 9.43705440e-01]

[3.80385250e-01 1.67723164e-01 4.51891571e-01]

[3.23390484e-01 5.90759404e-02 6.17533624e-01]

[3.62997412e-06 6.20727292e-08 9.99996305e-01]

[2.62520202e-02 1.07279615e-02 9.63019967e-01]

[1.56525111e-05 4.21802781e-07 9.99983907e-01]

[2.94076904e-06 3.81133276e-08 9.99997020e-01]], shape=(8, 3), dtype=float32)

궁금해서 더해봤다.

1.36571955e-02 + 7.90162385e-03 + 9.78441238e-01 = 1.00000005735

3.92597765e-02 + 1.70347411e-02 + 9.43705440e-01 = 0.99999995760 (여기까지~ softmax가 잘 되는구나~)

 

근데 다음 줄에서 이게 되나 테스트를 하네? ㅋㅋㅋ

# Softmax onehot test
sample_db = [[8,2,1,4]]
sample_db = np.asarray(sample_db, dtype=np.float32)

print(hypothesis(sample_db))

tf.Tensor([[0.9302204 0.06200533 0.00777428]], shape=(1, 3), dtype=float32)

 

다음은 cost function. Cross entropy를 떠올려보자. Label(정답, y_data)과 우리 hypothesis의 예측 결과에 -log씌운 값을 곱해준다. -log(hypothesis(x_data)) -> -(y_data*log(hypothesis(x_data))

def cost_fn(X, Y):
    logits = hypothesis(X)
    cost = -tf.reduce_sum(Y * tf.math.log(logits), axis=1)
    cost_mean = tf.reduce_mean(cost)
    
    return cost_mean

print(cost_fn(x_data, y_data))

세 번째 줄 cost = -tf.reduce_sum(Y * tf.math.log(logits), axis=1) 에서 axis = 1이 의미하는 바는 뭘까? 찾아보니 tf.reduce_sum()할 때처럼 2차원 이상의 행렬 두 개를 만질 때 필요한 개념인 것 같다.

우리의 코드에 맞춰 생각해보자. Y*tf.math.log(logists) 로 element wise 곱을 하면 8*3의 행렬이 나오겠지? 

ex) [[0, 0, 1], [0, 0, 2], [0, 0, 3], [0, 40], [0, 5, 0], [0, 6, 0], [7, 0, 0], [9, 0, 0]] 이런 모양.

그럼 이걸 sum 할 때 각각의 성분을 모조리 더해야 할지, 3개씩 묶인 행 단위로 더해야 할 지 알려줘야 한다. 이걸 axis로 알려줘야 하는데 0은 가장 바깥. 한 차원씩 들어갈 때마다 1씩 올라간다. 그러니까 만약 위 행렬을 axis = 0으로 tf.reduce_sum 하면 [16, 15, 6] 이 결과가 될 테고, axis = 1으로 tf.reduce_sum 한 차원 더 들어가서 각 성분끼리 더하면 37이 될 것이다. 

행렬 만질 때 기본적인 개념들이 있는 글 : https://doorbw.tistory.com/135

 

이걸 우리의 hypothesis에 적용했더니  tf.Tensor(6.07932, shape=(), dtype=float32)

 

'''
x = tf.constant(3.0)
with tf.GradientTape() as g:
    g.watch(x)
    y = x * x # x^2
dy_dx = g.gradient(y, x) # Will compute to 6.0
print(dy_dx)  >> tf.Tensor(6.0, shape=(), dtype=float32)
'''

def grad_fn(X, Y):
    with tf.GradientTape() as tape:
        loss = cost_fn(X, Y)
        grads = tape.gradient(loss, variables)

        return grads

print(grad_fn(x_data, y_data))

[<tf.Tensor: shape=(4, 3), dtype=float32, numpy=

array([[ 0.06914607, -0.6509784 , 0.5818323 ],

              [-1.5221257  , -1.214863   , 2.7369885 ],

              [-1.2473828  , -1.7611003 , 3.008483   ],

              [-1.2014606  , -1.8659233 , 3.0673838 ]], dtype=float32)>,

<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-0.15212913, -0.34219202, 0.4943211 ], dtype=float32)>]

위의 cost_fn을 GradeintDescent 해주려고 GradientTape를 이용한다. 주석 박스는 GradientTape가 뭔지 보여주는 예제이다.

 

def fit(X, Y, epochs=2000, verbose=100):
    optimizer =  tf.keras.optimizers.SGD(learning_rate=0.1)

    for i in range(epochs):
        grads = grad_fn(X, Y)
        optimizer.apply_gradients(zip(grads, variables))
        if (i==0) | ((i+1)%verbose==0):
            print('Loss at epoch %d: %f' %(i+1, cost_fn(X, Y).numpy()))
            
fit(x_data, y_data)

Loss at epoch 1: 2.849417

Loss at epoch 100: 0.684151

Loss at epoch 200: 0.613813

Loss at epoch 300: 0.558204

Loss at epoch 400: 0.508306

Loss at epoch 500: 0.461059

Loss at epoch 600: 0.415072

Loss at epoch 700: 0.369636

Loss at epoch 800: 0.324533

Loss at epoch 900: 0.280720

Loss at epoch 1000: 0.246752

Loss at epoch 1100: 0.232798

Loss at epoch 1200: 0.221645

Loss at epoch 1300: 0.211476

Loss at epoch 1400: 0.202164

Loss at epoch 1500: 0.193606

Loss at epoch 1600: 0.185714

Loss at epoch 1700: 0.178415

Loss at epoch 1800: 0.171645

Loss at epoch 1900: 0.165351

Loss at epoch 2000: 0.159483

keras 옵티마이저로 최적화.

 

테스트를 해보자.

b = hypothesis(x_data)
print(b)
print(tf.argmax(b, 1))
print(tf.argmax(y_data, 1)) # matches with y_data

여기서 tf.argmax에 들어가는 숫자는 axis이다. 1이니까 하나의 행을 한 단위로 잘라서 그 안의 최댓값의 인덱스를 뽑는다.

tf.Tensor( [[2.1975952e-06 1.2331170e-03 9.9876475e-01]

[1.1288594e-03 8.1546687e-02 9.1732442e-01]

[2.2205539e-07 1.6418624e-01 8.3581358e-01]

[6.3921816e-06 8.5045439e-01 1.4953922e-01]

[2.6150808e-01 7.2644734e-01 1.2044534e-02]

[1.3783246e-01 8.6214006e-01 2.7417480e-05]

[7.4242145e-01 2.5754160e-01 3.6978410e-05]

[9.2197549e-01 7.8023903e-02 6.0005692e-07]], shape=(8, 3), dtype=float32)

tf.Tensor([2 2 2 1 1 1 0 0], shape=(8,), dtype=int64)

tf.Tensor([2 2 2 1 1 1 0 0], shape=(8,), dtype=int64)

 

우리의 모델이 뽑은 결과를 보면 각각의 행에서 2,2,2,1,1,1,0,0 의 값들이 가장 높다고 나온다. 실제 y_data도 같은 결과다. 


feature를 갖는 데이터를 넣어서 그에 따른 출력을 뽑고 그게 정답과 비슷하게 유도해가는 과정. 단순한 것 같은데 단순하게 이해는 안 된다. 그리고 단순하게 만들어지지도 않았겠지? 그리고 강력하다.