Adventure Time - Finn 3
본문 바로가기
AI/ML

Softmax Classifier : 실제 데이터로 동물 분류하기 (TensorFlow)

by hyun9_9 2026. 3. 11.

지난 포스트에서는 직접 만든 데이터로 Softmax Classifier를 구현했습니다.
이번엔 실제 CSV 파일을 불러와서 동물을 7가지로 분류하는 모델을 만들어보겠습니다.
또한 더 안정적인 방법인 softmax_cross_entropy_with_logits 를 사용해봅니다.


전체 코드

import tensorflow as tf
import numpy as np

xy = np.loadtxt('data-04-zoo.csv', delimiter=',', dtype=np.float32)

x_data = xy[:, 0:-1]
y_data = xy[:, [-1]].astype(np.int32)

nb_classes = 7
Y_one_hot = tf.one_hot(list(y_data), nb_classes)  # shape=(?, 1, 7)
Y_one_hot = tf.reshape(Y_one_hot, [-1, nb_classes])  # shape=(?, 7)

W = tf.Variable(tf.random.normal([16, nb_classes]), name='weight')
b = tf.Variable(tf.random.normal([nb_classes]), name='bias')

variable = [W, b]

def logit_fn(X):
    return tf.matmul(X, W) + b

def hypothesis(X):
    return tf.nn.softmax(logit_fn(X))

def cost_fn(X, Y):
    logits = logit_fn(X)
    cost_i = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=Y)
    cost = tf.reduce_mean(cost_i)
    return cost

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

def prediction(X, Y):
    pred = tf.argmax(hypothesis(X), 1)
    correct_prediction = tf.equal(pred, tf.argmax(Y, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    return accuracy

optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)

n_epochs = 3000
for step in range(n_epochs + 1):
    grads = grad_fn(x_data, Y_one_hot)
    optimizer.apply_gradients(zip(grads, variable))
    if step % 300 == 0:
        acc = prediction(x_data, Y_one_hot).numpy()
        loss = tf.reduce_sum(cost_fn(x_data, Y_one_hot)).numpy()
        print("Iter: {}, Loss: {:.4f}, Acc: {}".format(step, loss, acc))

1. CSV 파일 불러오기

xy = np.loadtxt('data-04-zoo.csv', delimiter=',', dtype=np.float32)

x_data = xy[:, 0:-1]       # 마지막 열 제외 → 입력 데이터 (16개 특성)
y_data = xy[:, [-1]].astype(np.int32)  # 마지막 열만 → 정답 (0~6 클래스)
  • np.loadtxt : CSV 파일을 NumPy 배열로 불러옵니다
  • xy[:, 0:-1] : 모든 행, 마지막 열 제외 → 입력 특성 16개
  • xy[:, [-1]] : 모든 행, 마지막 열만 → 정답 레이블 (0~6)

동물 데이터는 101개의 동물, 16개의 특성(털, 날개, 다리 수 등)으로 이루어져 있고 7가지 클래스로 분류합니다.


2. tf.one_hot으로 One-Hot Encoding 자동화

지난 포스트에서는 One-Hot을 직접 손으로 작성했습니다.
이번엔 tf.one_hot 으로 자동으로 변환합니다.

nb_classes = 7
Y_one_hot = tf.one_hot(list(y_data), nb_classes)  # shape=(101, 1, 7)
Y_one_hot = tf.reshape(Y_one_hot, [-1, nb_classes])  # shape=(101, 7)
  • tf.one_hot(y_data, 7) : 0~6 정수값을 자동으로 One-Hot으로 변환
y_data = [0, 3, 1]

tf.one_hot → [[1, 0, 0, 0, 0, 0, 0],   ← 클래스 0
              [0, 0, 0, 1, 0, 0, 0],   ← 클래스 3
              [0, 1, 0, 0, 0, 0, 0]]   ← 클래스 1
  • tf.reshape(..., [-1, nb_classes]) : shape가 (101, 1, 7) 로 나오기 때문에 (101, 7) 로 차원을 줄여줍니다
  • -1 은 "나머지 차원은 자동으로 계산해줘" 라는 의미입니다

3. W shape 결정

W = tf.Variable(tf.random.normal([16, nb_classes]), name='weight')
b = tf.Variable(tf.random.normal([nb_classes]), name='bias')

입력 특성이 16개, 출력 클래스가 7개이므로:

[n, 16]  *  [16, 7]  =  [n, 7]
  X      *    W     =   각 클래스의 점수

4. logit_fn과 hypothesis 분리

이번 코드에서 새로운 점은 Linear 연산과 Softmax를 분리한 것입니다.

def logit_fn(X):
    return tf.matmul(X, W) + b   # Linear 결과 (Softmax 적용 전)

def hypothesis(X):
    return tf.nn.softmax(logit_fn(X))  # Softmax 적용 후 확률값
  • logit : Softmax를 통과하기 전의 Linear 연산 결과값
  • hypothesis : logit에 Softmax를 적용해 확률로 변환한 값

이렇게 분리하는 이유는 바로 아래의 softmax_cross_entropy_with_logits 때문입니다.


5. softmax_cross_entropy_with_logits

def cost_fn(X, Y):
    logits = logit_fn(X)
    cost_i = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=Y)
    cost = tf.reduce_mean(cost_i)
    return cost

지난 포스트에서는 직접 계산했습니다:

# 이전 방식 (직접 계산)
cost = tf.reduce_mean(-tf.reduce_sum(Y * tf.math.log(hypothesis), axis=1))

softmax_cross_entropy_with_logits 는 이 과정을 한 번에 처리해줍니다:

logit → Softmax → Cross-Entropy

직접 계산하는 것보다 수치적으로 더 안정적입니다.
log(0) 같은 상황에서 발생할 수 있는 수치 불안정 문제를 내부적으로 처리해주기 때문입니다.


6. 정확도 계산

def prediction(X, Y):
    pred = tf.argmax(hypothesis(X), 1)           # 예측 클래스
    correct_prediction = tf.equal(pred, tf.argmax(Y, 1))  # 정답과 비교
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    return accuracy

지난 포스트와 동일한 흐름입니다.

  • tf.argmax(hypothesis(X), 1) : 가장 높은 확률의 클래스 인덱스
  • tf.equal(pred, tf.argmax(Y, 1)) : 예측값과 실제값이 같은지 비교
  • tf.reduce_mean(tf.cast(..., tf.float32)) : 정확도 계산

학습 결과

Iter: 0,    Loss: 4.3794, Acc: 0.158
Iter: 300,  Loss: 1.4979, Acc: 0.534
Iter: 600,  Loss: 0.8671, Acc: 0.702
Iter: 900,  Loss: 0.6412, Acc: 0.792
Iter: 1200, Loss: 0.5244, Acc: 0.811
Iter: 1500, Loss: 0.4463, Acc: 0.831
Iter: 1800, Loss: 0.3887, Acc: 0.900
Iter: 2100, Loss: 0.3444, Acc: 0.910
Iter: 2400, Loss: 0.3093, Acc: 0.930
Iter: 2700, Loss: 0.2810, Acc: 0.940
Iter: 3000, Loss: 0.2577, Acc: 0.940

학습이 진행될수록 Loss는 줄어들고 정확도는 올라갑니다.
3000번 학습 후 약 94%의 정확도로 동물을 분류합니다.


이전 코드와 비교

  지난 포스트 이번 포스트
데이터 직접 작성 CSV 파일 (np.loadtxt)
One-Hot 직접 작성 tf.one_hot 자동 변환
Cost 함수 직접 계산 softmax_cross_entropy_with_logits
안정성 수치 불안정 가능 내부적으로 안정 처리

전체 흐름 정리

CSV 파일 로드
    ↓
x_data (16개 특성), y_data (0~6 클래스)
    ↓
tf.one_hot → One-Hot Encoding (7개 클래스)
    ↓
logit_fn : tf.matmul(X, W) + b    → Linear 연산
    ↓
softmax_cross_entropy_with_logits → Softmax + Cross-Entropy 한번에
    ↓
GradientTape → gradient 계산
    ↓
optimizer.apply_gradients          → W, b 업데이트
    ↓
반복 (3000 epochs)
    ↓
tf.argmax → 최종 클래스 예측 (94% 정확도)

정리

개념 설명 코드
np.loadtxt CSV 파일을 NumPy 배열로 로드 np.loadtxt('file.csv', delimiter=',')
tf.one_hot 정수 레이블을 One-Hot으로 자동 변환 tf.one_hot(y_data, nb_classes)
logit Softmax 적용 전 Linear 결과값 tf.matmul(X, W) + b
softmax_cross_entropy_with_logits Softmax + Cross-Entropy 한번에 처리 tf.nn.softmax_cross_entropy_with_logits(...)

실제 데이터로 학습해보니 모델이 점점 동물을 잘 분류해가는 것을 확인할 수 있었습니다. 🚀