오늘은 머신러닝 데이터셋 중에서 유명한 붓꽃(iris) 데이터셋을 이용하여 여러 개의 sigmoid 함수를 써서 다변수의 classification을 해보겠습니다.
데이터셋은 다음의 링크(http://ai-times.tistory.com/418)에서 다운받으실 수 있습니다.


일단 먼저 붓꽃 데이터셋은 4개의 항목(SepalLength, SepalWidth, PetalLength, PetalWidth)과 그에 맞는 붓꽃의 종류(Species)가 있습니다. 이때 각 항목에 맞는 데이터를 집어 넣고 그 항목에 맞는 붓꽃은 무슨 종류인지 알아 내려면 이 항목들이 어떤 특성을 가지고 있는지 확인해야 합니다. 따라서 각 항목별로 그래프를 그려서 분포를 확인해봅니다.

Sepal Width, Sepal Length 그래프에서 versicolor 종과 virginica 종이 섞여서 분포하는 것을 제외하고는 대부분 각 종마다 특정 좌표에 많이 모여있다는 게 보입니다. 이런 경우에는 Softmax Classification을 적용하여 종을 알고 싶은 붓꽃의 항목 데이터를 입력하여 붓꽃의 종을 알아내는 인공지능을 만들어 낼 수 있는 것이죠.


자, 그러면 이제 프로그래밍을 해볼 차례입니다. 우리의 목표는 붓꽃의 항목 데이터를 받아서 붓꽃의 종(3가지)을 알아내는 겁니다. 이때 Logistic Regression은 어떤 특정 항목에 대한 확률을 보여주는 방법이기 때문에 3가지 종에 각각 Logistic Regression을 적용해주면 됩니다. 즉, setosa인지 아닌지 확률을 판단하는 Logistic Regression, versicolor인지 아닌지 확률을 판단하는 Logistic Regression, virginica인지 아닌지 확률을 판단하는 Logistic Regression을 각각 만들어서 각 함수에 항목 데이터를 입력해서 각 종에 대한 확률을 구하고, 그 확률이 가장 높은 게 그 종이 되는 겁니다.


그러나 이렇게 각 종마다 Logistic Regression을 적용하면 output이 모두 같게 (예: (0.9, 0.9, 0.9)) 나올 수도 있고, 성능도 조금 떨어지기 때문에 각 종에 맞을 확률의 합이 1(예: (0.7, 0.2, 0.1))로 나오도록 sigmoid를 확장시켜 Softmax라는 새로운 식이 등장하게 되었습니다. Softmax는 다음과 같습니다.

이때 K=2라면 원래 sigmoid 형태의 함수가 나오게 되죠.



자, 그러면 이제 프로그래밍을 해볼까요? 일단 총 3개의 종이 있고, 4개의 항목이 있으니 이에 대한 3행 4열의 weight 행렬을 만들어야 합니다. 그리고 3행의 bias 행렬도 필요하겠죠.


이때 input은 ?행(데이터 개수) 4열이기 때문에 편의상 W를 Tranpose 해주고 곱한 다음에 b도 Tranpose 하고 더하면 됩니다. 즉, 간단하게 4행 3열의 weight, 3열의 bias 행렬을 생성해서 사용하면 됩니다.


다음으로는 이 W, b를 최적화시켜서 최대한 올바른 Softmax 함수가 나오도록 해 줄 cost값을 도출할 함수가 필요합니다. 이 함수를 Cross Entropy 함수라고 합니다. 이 함수는 다음과 같습니다.


sigmoid에서 Softmax가 나온 것처럼 이것도 sigmoid에서 쓰인 cost함수랑 비슷하게 생겼습니다. TensorFlow에서 위 식을 직접 구현해서 써도 되긴 하지만, TensorFlow Tutorial에서 MNIST for ML Beginners 문서를 보면 불안정하다고 나와있으니 tf.nn.softmax_cross_entropy_with_logits 함수를 사용할 것입니다. (Note that in the source code, we don't use this formulation, because it is numerically unstable. Instead, we apply tf.nn.softmax_cross_entropy_with_logits on the unnormalized logits (e.g., we call softmax_cross_entropy_with_logits on tf.matmul(x, W) + b), because this more numerically stable function internally computes the softmax activation. In your code, consider using tf.nn.(sparse_)softmax_cross_entropy_with_logits instead)


이제 Softmax 알고리즘은 다 준비되었습니다. 위 내용을 기반으로 프로그래밍을 해보면 아래와 같은 코드가 나옵니다. (이번에는 Jupyter Notebook을 이용하여 코딩을 해서 따로 이 파일도 첨부하겠습니다.)


softmax_classification.ipynb

import tensorflow as tf
import pandas as pd
import numpy as np

keys = ['SepalLength', 'SepalWidth', 'PetalLength', 'PetalWidth']
data = pd.read_csv("iris.csv")
data = data.drop('caseno', axis=1)

species = list(data['Species'].unique())
data['class'] = data['Species'].map(lambda x: np.eye(len(species))[species.index(x)])

testset = data.sample(50)
trainset = data.drop(testset.index)

X = tf.placeholder(tf.float32, [None, 4])
Y = tf.placeholder(tf.float32, [None, 3])

W = tf.Variable(tf.zeros([4, 3]), name="Weight")
b = tf.Variable(tf.zeros([3]), name="Bias")

H = tf.nn.softmax(tf.matmul(X, W) + b)

cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(H, Y, name="Cross_Entropy"))
optimizer = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(H, 1), tf.argmax(Y, 1)), tf.float32))

init = tf.initialize_all_variables()

sess = tf.Session()
sess.run(init)

trainset_class = [y for y in trainset['class'].values]

for step in xrange(1001):
    sess.run(optimizer, feed_dict={X: trainset[keys].values,
                                   Y: trainset_class})
    if step % 100 == 0:
        print step, sess.run(accuracy, feed_dict={X: trainset[keys].values,
                                                  Y: trainset_class})
        print sess.run(cross_entropy, feed_dict={X: trainset[keys].values,
                                                 Y: trainset_class})
# Check which data is error
# trainset_result = sess.run(H, feed_dict={X: trainset[keys].values})

# error_data = []

# for x in xrange(trainset.shape[0]):
#     if np.argmax(trainset_result[x], 0) != np.argmax(trainset_class[x], 0):
#         error_data.append(trainset.values[x])
        
# print error_data

# Check testdata's accuracy
print sess.run(accuracy, feed_dict={X: testset[keys].values,
                                    Y: [y for y in testset['class'].values]})

# Test which species accords this data
species = ["setosa", "versicolor", "virginica"]

sample = data.sample(1)
result = species[np.argmax(sess.run(H, feed_dict={X: sample[keys].values}))]
print "The flower which has", sample[keys].values, "may be", result
if result == sample['Species'].values:
    print "Correct!"
else:
    print "Incorrect!"

실행시켜보면 거의 평균적으로 정확도가 98% 정도 나옵니다.


이렇게 Softmax Classification을 끝내봤습니다. 다음에는 TensorBoard를 소개하면서 NN을 시작하지 않을까 싶네요.


+ Recent posts

티스토리 툴바