지난 포스트에서는 직접 만든 데이터로 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(...) |
실제 데이터로 학습해보니 모델이 점점 동물을 잘 분류해가는 것을 확인할 수 있었습니다. 🚀
'AI > ML' 카테고리의 다른 글
| Overfitting : 과적합 이해하고 해결하기 (0) | 2026.03.13 |
|---|---|
| 머신러닝 학습 팁 : Learning Rate, 데이터 전처리 (0) | 2026.03.12 |
| Softmax Classifier 코드로 구현해보기 (TensorFlow) (0) | 2026.03.11 |
| Softmax Classifier : 여러 클래스를 분류하기 (0) | 2026.03.10 |
| Logistic Regression 코드로 구현해보기 (TensorFlow) (0) | 2026.03.09 |