이미지 분류가 어려운 이유
이미지 분류는 컴퓨터 비전에서의 가장 중점이 되는 분야이다.
단순히 생각하면 그렇게 어려운 과제인가? 싶지만 컴퓨터의 특성을 생각하면 매우 어렵다.
컴퓨터는 이미지를 입력받을 때 픽셀의 값을 입력을 받는다.
이미지는 0에서 255의 숫자를 가지는 n x n x 3의 행렬로 이루어져 있으며 이 숫자들을 보며 해당 이미지가 어떠한 이미지인지 알아내는 것은 상당히 어렵다.
또한, 조금의 변화(예를 들어 카메라 각도, 빛, 부분가림, 뭉개짐 등)가 있어도 시각적으로는 변화가 적을지라도 픽셀 값은 매우 달라진다. 해당 변화가 있어도 그 이미지가 그대로 (예를 들어) 고양이라는 것을 인지하도록 모델을 만드는 것은 생각만해도 상당히 복잡해진다.
이미지 분류 API
이미지 분류를 위한 API를 작성한다고 생각해보자.
RSA 암호화나 단순 계산 API는 일단 어디서부터 시작할지 상상이나 간다만 이미지 분류 API는 어디서 부터 시작해야할지 어떤 과정을 거쳐야 할지 아무 생각도 들지 않는다.
def classify_image(image):
#some magic here?
return class_label
어떠한 코드를 작성해야 될까...
한 가지 방법은 저번 1강에서 언급했던 방법으로 접근 하는 것이다.
먼저 이미지의 경계선등 직선의 특성을 가지고 있는 부분을 먼저 분류하고 그 이후 곡선을 찾고 해당 특징을 이용해 눈,코,입 등을 분류하는 것이다.
하지만 해당 방법은 매 개체마다 완전히 다른 코드를 짜야하므로 너무나 비효율적이고 성능 자체도 만족스럽지 않다.
데이터 중심 접근법 (Data-Driven Approach)
API 개발을 위해 새로나온 접근법이 데이터 중심 접근법이다.
해당 접근법은 먼저 라벨링이 완료된 데이터셋을 만드는 것이다.
그 이후 기계 학습을 통해 분류기를 학습 시키고 마지막으로 분류기를 통해 새로운 이미지를 분류시켜보는 것이다.
해당 방법은 매 개체마다 새로운 코드를 만들 필요도 없고 복잡성도 줄어든다.
def train(images, labels):
#machine Learning
return model
def predict(model, test_images):
#use model to predict images
return test_labels
두 개의 메서드를 통해 이미지 분류를 할수 있다.
근접 이웃 알고리즘 (Nearest-Neighbor Algorithm)
근접 이웃 알고리즘은 가장 기본적인 기계 학습 알고리즘 중 하나이다.
먼저 학습 단계에서 주어진 모든 데이터 셋을 외우도록 한다. 그 이후 추측 단계에서 학습 단계에서 외운 데이터 셋중 가장 비슷한 이미지를 고르는 방법이다.
해당 분류 방법은 이미지 분류를 하기엔 가장 바보 같은 방법이지만 모든 배움은 기본에서부터 시작이기 때문에 배워 두는 것이 좋다.
CIFAR-10
CIFAR-10은 간단한 이미지 분류를 위해 사용하기 좋은 데이터 셋이다.
해당 데이터 셋은 10개의 카테고리, 5만 개의 학습 데이터, 1만 개의 시험 데이터를 포함하고 있다.
(10개의 카테고리는 비행기, 자동차, 새, 고양이, 사슴, 개, 개구리, 말, 배, 트럭이다)

보다시피 근접 이웃 알고리즘을 사용할 경우 단순히 비슷한 이미지를 고르는 것이기 때문에 결과물이 정확하지는 않다.
근접 이웃 알고리즘의 비교 방법
위에서 설명했듯이 근접 이웃 알고리즘은 가장 비슷한 이미지를 골라내는 것인데 과연 어떻게 비교하여 비슷한 사진을 골라내는 것일까?
해당 방법은 여러가지 존재하는데 그중 하나는 L1 혹은 맨해튼 거리라고 불리는 것이다.
L1 Distance / Manhattan Distance
L1은 두 개의 사진 (즉, 학습 데이터와 시험 데이터)의 픽셀 값을 서로 뺀 값의 절댓값을 다 더한 값을 표현한다.
해당 방법으로 도출된 값으로 두 데이터가 비슷한지 아닌지 판단하는 것이다.
$$d_{1}(I_{1}, I_{2}) = \sum_{p}^{}\left|I_{1}^{p} - I_{2}^{p}\right|$$
L1의 공식이다.

근접 이웃 알고리즘의 특징
근접 이웃의 학습 단계 같은 경우 단순히 모든 데이터셋을 암기하는 것이기 때문에 시간 복잡도는 O(1)이며 매우 단순한 작업이다. 하지만 예측 단계는 모든 학습 데이터와 비교를 해야되기 때문에 O(N)이라는 시간 복잡도를 가지게 된다.
import numpy as np
class NearestNeighbor:
def __init__(self):
pass
def train(self, X, y):
"""X is N x D where each row is an example. Y is 1-dimension of size N"""
#the nearest neighbor classifier simply remembers all the training data
self.Xtr = X
self.ytr = y
def predict(self, X):
"""X is N x D where each row is an example we wish to predict label for"""
num_test = X.shape[0]
#lets make sure that the ouput type matches the input type
Ypred = np.zeros(num_test, dtype = self.ytr.dtype)
#loop over all test rows
for i in range(num_test):
#find the nearest training image to the ith test image
#using the L1 distance (sum of absolute value differences)
distances = np.sum(np.abs(self.Xtr - X[i,:]), axis = 1)
min_index = np.argmin(distances) # get the index with smallest distance
Ypred[i] = self.ytr[min_index] # predict the label of the nearest example
return Ypred
근접 이웃 알고리즘의 기본 모델이다.
추가적으로 필자는 numpy가 익숙치 않기 때문에 몰랐던 문법도 공부를 겸했다.
- shape() : 해당 행렬의 디멘션을 리턴하는 함수이다. tuple형식이기에 인덱싱으로 원하는 디멘션만 불러올수 있다.
- dtype() : 데이터 타입을 리턴하는 함수이다. 위 예시의 dtype은 int64이다.
- argmin() : 최솟값이 있는 인덱스 값을 리턴하는 함수이다.
위 모델을 이해하는데 가장 애먹었던건 입력받는 X의 디멘션이 50000 x 3072 라는 것이다.
5만 개의 학습 데이터의 픽셀을 1열로 데이터를 reshape한다는 것을 가정에 두고 코드를 만든 것이라 L1을 구하는 코드에서 저게 논리적으로 어떻게 돌아가지 라는 생각을 몇십분 동안 한 것 같다... (그냥 졸려서 머리가 안돌아간건가...)
일단 본론으로 넘어가 근접 이웃 알고리즘은 예측 단계에서 O(N)이라는 시간 복잡도를 갖는데 이건 실용성이 매우 떨어진다. 기계 학습 모델 같은 경우 학습하는 데에 시간이 오래 걸리는 것은 괜찮으나 예측 단계에서 오랜 시간이 걸리다보면 의미가 없다.
학습을 마친 모델이 핸드폰이나 노트북 같은 곳에 적용되어 예측을 진행해야 되는데 예측이 오래 걸리면 사용자가 오랫동안 기다려야하기 때문이다.

위의 그림은 근접 이웃 알고리즘을 통해 5개의 카테고리로 나눈 그림이다.
바로 눈에 띄이는 문제점은 중간에 초록색 영역에 노란색 영역이 침투해 있는 것 그리고 영역들의 경계선이 부드럽지 않고 들쭉날쭉 한것.
위와 같이 모델이 학습이 되면 학습 데이터에 과적합 된 것으로 완전히 새로운 데이터를 가지고 예측했을 때 좋은 결과를 가지고 올 수 없다.
K-근접 이웃 알고리즘 (K-Nearest Neighbor Algorithm / KNN)
위에 제시된 문제를 해결하기 위해 새로 나온 알고리즘이 K-근접 이웃 알고리즘이다 (편리성을 위해 앞으로는 KNN이라고 칭하겠다.)
KNN은 주어진 데이터 주변의 k개의 데이터의 분류 값을 보고 다수결로 값을 도출한다.
예시를 들어주자면 k값이 5라고 가정하고 예측을 해야하는 데이터 주변에 빨간색으로 분류된 데이터가 3개 초록색으로 분류된 데이터가 2개면 최종적으로 빨간색으로 분류된다.

k값이 증가할수록 영역의 경계선이 명확해지고 아까와 같이 중간에 꼽사리끼는 데이터도 없어졌다.
L2 Distance / Euclidean Distance
위에서 언급한 L1말고도 두 사진을 비교하는 다른 방법이 있다.
L2 거리 혹은 유클리드 거리라고 불리는 방법이다.
$$d_{2}(I_{1}, I_{2}) = \sqrt{\sum_{p}^{}(I_{1}^{p} - I_{2}^{p})^{2}}$$
L2의 공식이다.

L1을 사용한 예시 같은 경우 좌표계 축에 영향을 받았다고 한다면 L2는 축의 영향을 받지 않고 자연스레 있어야 할 위치에 경계선을 만들 수 있다.

다음과 같이 L1과 L2같은 함수들을 거리 함수(Distance Metrics)라고 하는데 거리 함수를 다르게 할수록 KNN은 이미지 분류뿐만 아니라 다양한 곳에 적용될 수도 있다.
만약 텍스트 사이의 거리를 나타내는 함수를 이용한다면 텍스트 분류에도 KNN 알고리즘이 사용될 수도 있다.
하이퍼파라미터 (Hyperparameter)
하이퍼파라미터란 학습 알고리즘과 관계없이 단독적으로 결정되는 변수들을 말한다.
위의 KNN같은 경우 k값과 거리함수가 하이퍼파라미터이다.
그러면 하이퍼파라미터의 값은 어떻게 결정해야할까?
이는 어떠한 문제를 해결하느냐에 따라 다르다. 그저 많은 값을 시도해보고 가장 잘맞는 하이퍼파라미터 값을 찾는 수 밖에 없다.
그러면 따라오는 질문이 있다.
하이퍼파라미터 값이 잘 맞는다는 건 어떻게 알까?
1. 그저 학습 데이터의 결과를 잘 찾는 값이 좋은 값일까?
-> No. 학습 데이터에 과적합이 되는 것은 모델의 일반화를 막는다.
2. 데이터셋을 학습 데이터와 시험 데이터로 나누어 시험 데이터의 값을 잘 예측하는 값일까?
-> No. 시험 데이터는 말 그대로 모델의 능력을 평가하는 용도지 하이퍼파라미터의 값을 조정하는 용도가 아니다.
3. 데이터셋을 학습, 검증, 시험 데이터로 나누어 시험 데이터의 값을 잘 예측하는 값일까?
-> Yes. 모델을 학습시키는 것의 목적에 맞게 검증 데이터셋에서 하이퍼파라미터의 값을 조정하고 최종적으로 시험 데이터에서 모델의 능력을 평가하는 것이 가장 적절한 방법이다.

교차 검증(Cross-Validation)
교차 검증은 주로 작은 사이즈의 데이터셋에서 사용하는 방법이다.
먼저 데이터셋을 학습용과 시험용으로 나누고 학습용을 n개의 데이터셋으로 나눈다.
그리곤 나누어진 데이터셋 중 하나를 검증으로 사용하고 매 사이클 마다 다른 데이터셋을 검증용으로 사용하며 나온 값의 평균을 사용하여 하이퍼파라미터의 값을 조정하는 것이다.
하지만 해당 방법은 딥러닝에서는 주로 사용되는 방법은 아니라고 한다.

이미지 분류의 KNN
사실상 이미지 분류 작업에서 KNN 알고리즘은 절대 사용되지 않는다.
우선적으로 위에서 언급했듯이 예측 시간이 너무 오래 걸리고 L2 측정 같은 경우도 사진을 구별하는데에 큰 도움이 되지 않는다.

왼쪽의 사진과 우측의 세 사진의 L2 거리를 계산 해봤을 때 세 사진의 L2값은 모두 동일하게 나오는 것을 알 수 있고 이것은 L2가 사진을 분별하는데에 도움이 되지 않는다는 것이다.
또한 KNN같은 경우 디멘션이 늘어날수록 필요한 데이터 혹은 픽셀의 갯수가 기하급수적으로 늘어난다.
개발같은 경우 기하급수적으로 늘어나는 것은 절대적으로 피해야한다.

선형 분류 (Linear Classifier)
신경망 (neural network)를 레고 블럭이라고 비유했을 때 선형 분류기는 기본적인 블럭이라고 표현할 수 있다.
그만큼 선형 분류기는 신경망에서 빠질 수 없는 가장 기초라고 보면 된다.
선형 분류는 가장 기본적인 매개변수 모델(parametric model)인데 이때 사용되는 매개변수는 X와 W이다.
X는 CIFAR-10 데이터셋을 봤을 때 32*32*3 = 3072 픽셀의 배열이고 W는 가중치, 즉 하이퍼파라미터이다.
$$f(x,W)$$
해당 함수는 10x1의 배열을 리턴하는데 이는 각 카테고리의 점수이다. (CIFAR-10를 사용한다고 가정했을 때이다.)
선형 분류 같은 경우 가장 간단한 분류기이기 때문에 함수도 매우 간단하다.
$$f(x,W) = Wx$$
x같은 경우 3072x1의 배열을 가지고 있고 함수값은 10x1의 배열이기 때문에 W는 10x3072의 사이즈를 갖게 된다.
하지만 함수 식에 상수가 들어가는 경우도 있다.
$$f(x,W) = Wx + b$$
b라는 상수는 bias라고 불리우는데 해당 상수가 들어가는 이유는 만약 준비된 데이터셋에서 개와 고양이의 데이터가 다른 데이터에 비해 많다면 해당 카테고리에 추가적인 점수를 넣기 위해 상수를 더해주는 것이다.

하지만 그렇다고 선형 분류가 무적은 아니다.

위와 같은 경우는 직선 하나만으로 분류를 할 수가 없기에 선형 분류기로는 분류가 불가능하다.
'Deep Learning > CS231n' 카테고리의 다른 글
| [3-2강] Optimization (0) | 2024.05.12 |
|---|---|
| [3-1강] Loss Function & Regularization (0) | 2024.05.11 |
| [과제 1] K-Nearest Neighbor (KNN) (2) | 2024.04.25 |
| [1강] Introduction to Convolutional Neural Networks for Visual Recognition (0) | 2024.04.10 |