Assignment 1-4: Higher Level Representations: Image Features

ASSIGNMENT 글 목록
목차

Overview

이번 과제에서는 원본 픽셀을 그대로 사용하는 대신, 이미지에서 hand-crafted feature를 추출한 뒤 분류기를 학습한다.
사용한 feature는 Histogram of Oriented Gradients, HOG와 HSV color histogram이다.

전체 흐름은 다음과 같다.

  1. 각 이미지에서 HOG feature를 추출한다.
  2. 각 이미지에서 HSV hue channel 기반 color histogram을 추출한다.
  3. 두 feature vector를 이어 붙여 하나의 feature vector로 만든다.
  4. 추출된 feature 위에서 Softmax linear classifier를 학습한다.
  5. 같은 feature 위에서 TwoLayerNet을 학습한다.

핵심은 이미지 전체를 픽셀값 그대로 넣는 것이 아니라, 분류에 도움이 될 만한 정보를 먼저 뽑아낸 뒤 모델을 학습한다는 점이다.

핵심 아이디어

HOG는 이미지의 경계와 방향 정보를, HSV histogram은 색상 분포 정보를 담는다. 두 feature를 합치면 texture와 color 정보를 함께 사용할 수 있다.

원본 픽셀 기반 입력은 이미지의 의미를 직접 담고 있지 않다.
고양이가 조금 이동하거나 배경이 바뀌어도 픽셀 값은 크게 달라질 수 있고, 반대로 의미가 다른 이미지라도 픽셀 분포가 비슷할 수 있다.

Feature extraction은 이 문제를 조금 줄이기 위한 방법이다.
이미지를 그대로 벡터화하기 전에, 이미지에서 분류에 유용할 만한 패턴을 먼저 계산한다.

이번 과제에서 사용한 feature는 다음 두 가지이다.

  • HOG: edge, contour, gradient orientation 같은 형태 정보를 담음
  • HSV color histogram: hue channel 기반의 색상 분포 정보를 담음

HOG는 색상 정보는 거의 무시하고 texture와 shape에 가까운 정보를 본다.
반대로 color histogram은 texture는 무시하고 색상이 어떻게 분포하는지를 본다.

따라서 두 feature를 concatenate하면 다음처럼 하나의 feature vector가 된다.

ϕ(x)=[ϕHOG(x),ϕHSV(x)]\phi(x) = \left[ \phi_{\text{HOG}}(x), \phi_{\text{HSV}}(x) \right]

여기서 xx는 입력 이미지이고, ϕ(x)\phi(x)는 이미지에서 추출한 최종 feature vector이다.
모델은 이제 원본 이미지 xx가 아니라 feature vector ϕ(x)\phi(x)를 입력으로 받는다.

구현

Feature extraction

hog_featurecolor_histogram_hsv는 각각 이미지 하나를 입력으로 받아 feature vector 하나를 반환한다.
extract_features는 여러 이미지에 대해 feature function들을 적용한 뒤, 각 이미지의 feature들을 이어 붙여 feature matrix를 만든다.

형태를 간단히 쓰면 다음과 같다.

XXfeatsX \rightarrow X_{\text{feats}}

원본 이미지 하나가 32×32×332 \times 32 \times 3 형태였다면, feature extraction 이후에는 각 이미지가 하나의 feature vector로 표현된다.

XfeatsRN×DfeatX_{\text{feats}} \in \mathbb{R}^{N \times D_{\text{feat}}}

여기서 NN은 이미지 개수이고, DfeatD_{\text{feat}}는 HOG feature dimension과 HSV histogram feature dimension을 합친 값이다.

Feature를 추출한 뒤에는 feature별 평균과 표준편차를 이용해 표준화한다.

X^ij=Xijμjσj\hat X_{ij} = \frac{X_{ij}-\mu_j}{\sigma_j}

여기서 μj\mu_jjj번째 feature의 평균이고, σj\sigma_jjj번째 feature의 표준편차이다.
이렇게 하면 각 feature가 대략 비슷한 scale을 갖게 되어 학습이 더 안정적으로 진행된다.

Feature 기반 Softmax 튜닝

먼저 추출한 feature 위에서 Softmax classifier를 학습했다.
이전 softmax 과제와 마찬가지로 validation set을 이용해 learning rate와 regularization strength를 튜닝한다.

from cs231n.classifiers.linear_classifier import Softmax

# validation set으로 learning rate와 regularization strength를 튜닝
learning_rates = [1e-6, 2e-6]
regularization_strengths = [5e4, 6e4, 6.5e4, 7e4, 7.5e4]

results = {}
best_val = -1
best_softmax = None

for lr in learning_rates:
    for reg in regularization_strengths:
        softmax = Softmax()
        loss_history = softmax.train(
            X_train_feats,
            y_train,
            learning_rate=lr,
            reg=reg,
            num_iters=170,
        )

        y_train_pred = softmax.predict(X_train_feats)
        train_accuracy = np.mean(y_train == y_train_pred)

        y_val_pred = softmax.predict(X_val_feats)
        val_accuracy = np.mean(y_val == y_val_pred)

        results[(lr, reg)] = (train_accuracy, val_accuracy)

        if val_accuracy > best_val:
            best_val = val_accuracy
            best_softmax = softmax

# 결과 출력
for lr, reg in sorted(results):
    train_accuracy, val_accuracy = results[(lr, reg)]
    print(
        "lr %e reg %e train accuracy: %f val accuracy: %f"
        % (lr, reg, train_accuracy, val_accuracy)
    )

print("best validation accuracy achieved: %f" % best_val)

가장 좋은 결과는 다음 설정에서 나왔다.

lr 1.000000e-06 reg 6.000000e+04 train accuracy: 0.411939 val accuracy: 0.431000

원본 픽셀 기반 linear classifier보다 feature 기반 Softmax가 더 좋은 성능을 낸다.
다만 여전히 linear classifier이기 때문에 feature space에서 선형 결정 경계만 만들 수 있다는 한계가 있다.

Misclassification 결과 해석

오분류 결과를 보면 완전히 이상한 결과라기보다는, feature의 한계가 드러나는 경우가 많다.

예를 들어 고양이나 개 이미지가 truck으로 분류되는 경우가 있었다.
모델이 객체의 의미를 이해하는 것이 아니라 HOG와 색상 histogram을 보고 판단하기 때문에, 배경 색상이나 윤곽 패턴이 비슷하면 이런 오분류가 생길 수 있다.

또 하늘을 나는 bird가 airplane으로 분류되는 경우도 자연스럽다.
두 클래스 모두 파란 하늘 배경에 작은 물체가 놓인 형태로 나타날 수 있기 때문이다.

즉 hand-crafted feature는 edge와 color 분포를 잘 잡아내지만, 객체의 semantic meaning까지 직접 이해하지는 못한다.

Feature 기반 TwoLayerNet 학습

다음으로 같은 HOG + HSV feature 위에서 TwoLayerNet을 학습했다.
입력 차원은 원본 픽셀 차원인 3072가 아니라, 추출된 feature dimension이다.

from cs231n.classifiers.fc_net import TwoLayerNet
from cs231n.solver import Solver

input_dim = X_train_feats.shape[1]
hidden_dim = 500
num_classes = 10

data = {
    "X_train": X_train_feats,
    "y_train": y_train,
    "X_val": X_val_feats,
    "y_val": y_val,
    "X_test": X_test_feats,
    "y_test": y_test,
}

net = TwoLayerNet(
    input_dim,
    hidden_dim,
    num_classes,
    weight_scale=1e-2,
    reg=1e-3,
)
best_net = None

solver = Solver(
    data=data,
    model=net,
    update_rule="sgd",
    optim_config={
        "learning_rate": 1e-1,
    },
    lr_decay=0.95,
    num_epochs=30,
    batch_size=200,
    print_every=500,
)

solver.train()

best_net = solver.model

최종 학습 기록은 다음과 같다.

(Epoch 29 / 30) train acc: 0.711000; val_acc: 0.610000
(Epoch 30 / 30) train acc: 0.716000; val_acc: 0.615000

test set에서는 다음 결과가 나왔다.

y_test_pred = np.argmax(best_net.loss(data["X_test"]), axis=1)
test_acc = (y_test_pred == data["y_test"]).mean()
print(test_acc)
0.606

Feature 기반 Softmax의 validation accuracy가 약 43.1%였고, feature 기반 TwoLayerNet의 test accuracy가 약 60.6%까지 올라갔다.
같은 feature를 사용하더라도 비선형 layer를 추가하면 더 복잡한 decision boundary를 만들 수 있기 때문에 성능이 크게 좋아진다.

추가 개념들

초기 학습 실패: regularization을 그대로 가져오면 안 됨

처음에는 선형 Softmax 분류기에서 사용했던 regularization strength를 그대로 TwoLayerNet에 적용했다.

reg = 6e4

이때 학습 결과는 다음과 비슷했다.

초기 loss: 약 2687
이후 loss: 2.302585에 고정
train / validation accuracy: 약 10%

10개 클래스에 같은 확률을 출력하면 softmax loss는 다음 값이 된다.

log(110)=log102.302585-\log\left(\frac{1}{10}\right) = \log 10 \approx 2.302585

따라서 loss가 2.3025852.302585에 고정되고 accuracy가 약 10%10\%라면, 모델이 사실상 무작위 추측을 하고 있다는 뜻이다.

원인은 reg=6e4가 TwoLayerNet에는 지나치게 큰 값이었던 것이다.
강한 regularization gradient가 weight를 거의 0으로 밀어버리면 모든 class score가 비슷해지고, softmax 결과도 거의 uniform distribution이 된다.

선형 분류기에서 찾은 regularization strength는 그 모델의 weight scale, parameter 수, loss 구현에 맞는 값이다.
따라서 다른 구조의 neural network에 그대로 재사용하면 안 된다.

Regularization과 weight initialization 조정

TwoLayerNet에서는 regularization strength를 크게 낮추고, weight initialization scale을 명시했다.

weight_scale = 1e-2
reg = 1e-3

weight_scale은 초기 weight의 표준편차를 결정한다.

  • 너무 작으면 activation과 gradient가 작아져 학습이 느리거나 거의 진행되지 않을 수 있음
  • 너무 크면 activation과 score가 과도하게 커져 학습이 불안정해질 수 있음

따라서 weight_scale도 learning rate, regularization strength와 함께 튜닝해야 하는 하이퍼파라미터이다.

작은 learning rate에서의 학습

처음에는 다음 설정을 사용했다.

learning_rate = 1e-3
weight_scale = 1e-2
reg = 1e-3
num_epochs = 10

학습은 정상적으로 진행되었지만 매우 느렸다.

초기 validation accuracy: 약 10%
10 epoch validation accuracy: 약 35.7%

loss도 천천히 감소했다.

2.306 -> 2.289 -> 2.268

정확도가 계속 상승했으므로 모델이 완전히 망가진 것은 아니다.
다만 learning rate가 너무 작아 update 크기가 작았고, 목표 성능에 도달하기 어려웠다.

가중치 업데이트는 다음과 같다.

Wt+1=WtαWLW_{t+1} = W_t-\alpha\nabla_W L

여기서 α\alpha가 learning rate이다.
α\alpha가 너무 작으면 gradient 방향이 맞더라도 한 번의 update에서 이동하는 거리가 지나치게 작다.

Learning rate 증가

이후 learning rate를 다음처럼 키웠다.

learning_rate = 1e-1

기존 10310^{-3}에서 10110^{-1}로 올렸기 때문에, 같은 gradient에 대한 update 크기는 100배 커진다.

101103=100\frac{10^{-1}}{10^{-3}} = 100

그 결과 초기 몇 epoch만에 validation accuracy가 빠르게 상승했다.

Epoch 1: 49.9%
Epoch 3: 53.5%
Epoch 5: 56.7%
Epoch 8: 58.5%
Epoch 10: 59.6%
Epoch 13: 60.8%
Epoch 15: 62.1%

중간 실험에서는 validation accuracy가 약 62.1%62.1\%까지 올라갔다.
최종 기록 코드에서는 30 epoch 학습 기준으로 validation accuracy 61.5%61.5\%, test accuracy 60.6%60.6\%를 확인했다.

큰 learning rate가 안정적으로 동작한 이유

이번 feature에는 학습 전에 표준화를 적용했다.

X^ij=Xijμjσj\hat X_{ij} = \frac{X_{ij}-\mu_j}{\sigma_j}

표준화를 하면 feature별 scale이 대략 평균 0, 표준편차 1 수준으로 맞춰진다.
feature scale 차이가 크면 특정 방향의 gradient만 지나치게 커져 큰 learning rate에서 발산하기 쉽다.

반대로 표준화를 적용하면 각 feature 방향의 gradient scale이 비교적 균형을 이루기 때문에 더 큰 learning rate를 안정적으로 사용할 수 있다.

또 HOG와 HSV histogram은 원본 픽셀보다 이미 분류에 유용한 정보를 담고 있다.

  • HOG: 경계, 윤곽, 방향 정보
  • HSV histogram: 색상 분포 정보

따라서 TwoLayerNet은 원본 픽셀에서 특징을 처음부터 찾는 것이 아니라, 이미 추출된 feature들의 비선형 조합을 학습하면 된다.
이 때문에 적절한 learning rate를 사용했을 때 빠르게 높은 정확도에 도달할 수 있었다.

하이퍼파라미터별 역할

learning_rate는 한 번의 gradient update 크기를 결정한다.

  • 너무 작음: loss와 accuracy가 매우 천천히 개선됨
  • 적절함: 빠르고 안정적으로 수렴
  • 너무 큼: loss가 진동하거나 발산할 수 있음

이번 실험에서는 10310^{-3}보다 10110^{-1}이 훨씬 적절했다.

weight_scale은 초기 weight의 표준편차를 결정한다.

  • 너무 작음: activation과 gradient가 작아져 학습이 느릴 수 있음
  • 너무 큼: activation과 score가 과도하게 커질 수 있음

이번 실험에서는 10210^{-2}가 정상적인 학습을 가능하게 했다.

reg는 weight가 지나치게 커지는 것을 억제한다.

  • 너무 큼: weight가 0에 가까워져 underfitting 발생
  • 너무 작음: train accuracy만 높아지고 overfitting 가능
  • 적절함: 일반화 성능 개선

선형 분류기에서 사용한 reg=6e4는 TwoLayerNet에 지나치게 강했다.
이번 실험에서는 reg=1e-3 수준에서 정상적으로 학습되었다.

num_epochs는 전체 학습 데이터를 반복해서 학습하는 횟수이다.

하이퍼파라미터가 잘못된 상태에서는 epoch를 늘려도 의미가 없다.
먼저 loss가 정상적으로 감소하고 validation accuracy가 상승하는 설정을 찾은 뒤 epoch 수를 늘려야 한다.

최종 정리

  • 원본 픽셀 대신 HOG와 HSV histogram feature를 사용했다.
  • HOG는 형태와 방향 정보를, HSV histogram은 색상 정보를 담는다.
  • 두 feature를 concatenate하면 texture와 color 정보를 함께 사용할 수 있다.
  • 선형 Softmax에서는 validation accuracy 약 43.1%43.1\%를 얻었다.
  • 선형 분류기에서 찾은 reg를 TwoLayerNet에 그대로 사용하면 안 된다.
  • loss가 2.3025852.302585에 고정되고 accuracy가 10%10\%라면 모델이 무작위 추측 상태일 가능성이 높다.
  • reg, weight_scale, learning_rate는 서로 영향을 주므로 함께 튜닝해야 한다.
  • 표준화된 HOG + HSV feature에서는 비교적 큰 learning rate를 안정적으로 사용할 수 있었다.
  • Feature 기반 TwoLayerNet에서는 최종 test accuracy 약 60.6%60.6\%를 얻었다.