이미지 분류(Image Classification)
이미지는 기본적으로 3차원 배열로 구성된다. 즉, 높이 x 넓이 x RGB(0~255) 이렇게 3차원 배열로 구성된다.
하지만 이미지를 보는 시각에 따라서 이미지는 다르게 보일 수 있다. 예를 들면 밝기의 차이, 형태의 차이, 물체에 가려진 유무, 배경과 같은 색상의 이미지 객체, 여러 이미지 객체 등 시각에 따라서 다르게 보인다.
각기 다른 시각에서 나오는 이미지들을 3차원 배열로 구성했을 때 숫자값의 큰 격차가 생기게 된다. 이를 Sematice gap이라고 한다.
우선, 기본적인 Image Classification 함수는 아래와 같다.
def predict(image):
# Some magic here
return class_label
즉, 이미지 데이터를 인자로 받고 그 인자를 어떠한 알고리즘을 통해서 class_label(이미지가 속하는 카테고리)를 출력하는 것이 기본 함수 형태이다.
숫자를 배열하는 등의 알고리즘과는 다르게 물체의 카테고리를 인식하는데에는 명백한 알고리즘이 존재하지 않는다.
물론, 이미지에서 edges, shape를 추출하고 귀모양, 코모양과 같은 고양이에게 있는 것들을 하나하나 찾아내서 라이브러리화를 해서 한 카테고리에 필요한 모든 것들이 이미지에 있으면 해당 카테고리를 출력하는 시도가 있었다. 하지만 이 방법은 정확하지 않고, 비효율 적이다.
Data-driven appoach
이 방법은 아래와 같다.
1. train 함수에 주어진 데이터와 그 데이터의 정답을 인자로 받아서 모델에게 학습시킨 후 모델을 출력한다.
2. predict 함수에 주어진 모델과 테스트 이미지를 인자로 받아서 테스트 이미지의 카테고리를 출력시킨다.
코드로 작성해보면 아래와 같다.
// 모델 학습
def train(images, lables):
...
return model
// 모델 활용
def predict(model, test_images):
...
return test_labels
이제 이 방법을 기반으로 해볼 첫 이미지 분류는 Nearest Neighbor Classifier이다.
Nearest Neighbor Classifier - 현재는 사용되지 않는 방법
: NNC는 간단하게 말해서 모델 내부에 있는 이미지들 중 가장 test_image와 관계가 있는(가까운) 이미지 카테고리를 출력하는 방식이다.
: 학습시간은 짧지만, 분류 시간은 긴 방법
💡 이미지는 어떻게 비교할까?
test 이미지와 모델 내부에 있는 이미지(training image)를 전부 비교한다.
비교는 각 셀의 값을 차를 합해서 가장 값이 적은 이미지를 도출한다. 사진으로 살펴보자.
테스트 이미지와 트레이닝 이미지의 픽셀 차이의 총 합을 기준으로 가장 값이 작은 것을 가장 유사하다고 판단한다.
[NCC를 코드로 나타낸 것]
import numpy as np
class NearestNeighbor:
def __init__(self):
pass
def train(self, X, y):
# 모든 트레이닝 이미지를 메모리상에 저장한다.
# X는 이미지, y는 해당 이미지의 label
self.Xtr = X
self.ytr = y
def predict(self, X):
num_test = X.shape[0]
# lets make sure that the output type matches the input type
Ypred = np.zeros(num_test, dtype=self.ytr.dtype)
for i in range(num_test):
# L1을 코드 한줄로 구현하였다 - distance
distances = np.sum(np.abs(self.Xtr - X[i,:]), axis=1)
min_index = np.argmin(distances)
Ypred[i] = self.ytr[min_index]
return Ypred
위 코드를 잘 보면 NNC의 단점이 보이게 된다.
이 방식의 단점은 이미지 분류를 유연하게 하기 위해서는 많은 이미지 데이터가 입력되어야하지만 이미지 데이터의 입력이 늘어나면 분류 작업의 시간도 늘어난다는 것이다. 이미지 1개가 늘어났을 떄는 별차이 없겠지만 수만개 이상이 늘어나면 시간적으로 많은 문제가 생긴다.
NNC을 좀 더 괜찮게 만들기 위한 방법으로 KNN이 있다.
K-Nearest Neighbor
: 테스트 이미지의 L1이 가장 작은 이미지를 선정하고 그 선정된 이미지와 비슷한(가까운) 이미지를 K개 추출히고, 추출된 이미지 중 가장 많은 카테고리가 예측값으로 출력되는 방식
KNN으로 사진을 예측한 결과이다. 해당 결과물은 K를 10으로 설정했을 때 나온다.
💡 이미지 분류에 영향을 주는 Hyper parmeters인 k와 distance가 있는데 각각 어떤 것을 사용해야 더 결과가 좋을까?
Hyper parmeters
: 머신러닝 모델에서 모델이 학습하는 동안 직접 학습되지 않고 사람이 미리 설정해줘야 하는 변수
방법1) train data와 test data가 있을 때 Hyper parmeters를 변경하면서 test data에 적용한다.
🚨 절대하면 안되는 행위이다.🚨 test data는 최후의 성능평가 도구로 남겨놓아야한다.
방법2) validation
: train data와 test data가 있을 때 최적의 Hyper parmeters를 구하기 위한 데이터로 기존 train data의 20%(validation)만 사용한다.
validation을 이용해 찾은 최적의 Hyper parmeters를 test data에 한번만 사용한다.
방법3) corss validation
: 마지막 한번 사용할 테스트 데이터를 제외하고 나머지 데이터를 분할하고 트레이닝을 해준다.
예를 들면 5번을 남기고 나머지 1 2 3 4에서 트레이닝을 수행, 1번을 남기고 나머지 2 3 4 5에서 수행해준다,
아래는 5-fold cross-validation을 했을 때의 정확도 그래표를 나타낸다.
K값 하나당 정확도 값이 5개가 있고 그 값의 평균낸 곳을 이은 것이 선이다.
따라서 가장 정확도가 높을 떄는 7이다.
KNN의 한계
1. 테스트 타임에서 성능이 매우 좋지 않다. (오래 걸림)
2. distance를 구하는 방식 자체가 현실적으로 정확할 수 없다.
따라서 NNC, KNN은 잘 사용하지 않는다
Linear Classification(선형 분류)
: NN(Neural Network)과 CNN의 기반이 되는 알고리즘이다.
Parametric Approach(파라메트릭 접근법)
: 고정된 수의 파라미터(W)로 데이터를 설명하는 모델
: 쉽게 말해서, 이 모델은 데이터를 표현하기 위한 공식이나 수식이 있고, 그 수식의 형태는 고정되어 있다. 우리가 해야 할 일은 W라는 파라미터(가중치)를 잘 찾아서 입력 데이터 x와 곱해주어야한다.
가중치란?
: 입력 데이터 x가 결과에 미치는 영향을 숫자로 표현한 것이다. (사실 확실하게 와닿지는 않는다)
💡 가중치는 어떻게 찾을까?
가중치(W)를 구하는 과정은 모델을 학습하는 과정에서 이루진다.
자세하게 말하면 아래와 같다.
- 초기값 설정:
- 학습을 시작할 때, W(가중치)는 보통 임의의 값(랜덤 값)으로 설정
- 예측값 계산:
- 입력 데이터(x)를 모델에 넣고, 초기 가중치(W)와 입력을 사용해서 예측값(y')을 계산
- 오차 계산:
- 모델이 계산한 예측값(y')과 실제 정답(y) 사이의 **오차(손실)**를 계산
- W 업데이트:
- 오차를 줄이기 위해 W를 업데이트해야 한다. 이때, 경사 하강법을 사용해 손실 함수의 기울기(gradient)를 계산
- 기울기는 W를 어느 방향으로, 얼마나 많이 변경해야 오차를 줄일 수 있는지를 알려준다.
- 경사 하강법은 W를 조금씩 수정해서, 오차가 최소화되는 방향으로 W를 조정해준다..
- 반복:
- 이 과정은 여러 번 반복된다. 각 반복(iteration)마다 W는 조금씩 더 나은 값으로 업데이트되며, 오차가 점점 감소.
Parametric Approach을 쉽게 이해하기 위해서 함수에 빗대어 살펴보자.
모델을 함수로 설명해보면:
- 모델을 수학적인 함수로 보면, f(x) 형태로 표현
- f는 모델 자체(함수)
- x는 외부에서 주어지는 입력
- f(x)의 결과는 예측값(y')
예를 들어, 선형 회귀 모델을 함수로 표현하면:
- f(x) = Wx + b
- f는 모델이고,
- x는 주어지는 입력값,
- Wx에서 x는 입력 값을 받기 위한 빈 그릇 같은 존재
- W와 b는 학습을 통해 얻은 매개변수(가중치)
위에서 설명했다시피, x는 입력 이미지, W는 가중치(파라미터)이다.
예를 들어 고양이 사진을 x라고 했을 때 함수 f는 x와 W를 가지고 숫자들을 출력한다. 이 숫자들은 각 클래스(카테고리)에 해당하는 스코어로 각 스코어에 숫자가 높을 수록 유사하다고 판단한다.
만약에 내가 이미지를 넣었을 떄 출력된 클래스(카테고리)들의 스코어중 내가 넣은 이미지의 클래스 스코어가 가장 높다면 해당 모델을 성능이 좋은 모델이라고 생각할 수 있다.
실제로 고양이 이미지를 넣어서 알아보자.
32 x 32 x 3인 3차원 배열을 1차원 배열로 변경하면 3072나온다. 이때 f의 값은 10개가 나와야하기 때문에 (3072 x 1) · W = 10x1이 나와야한다.
따라서, W 는 10x3072가 된다.
B는 데이터와 무관한 고정된 값이다.
이 방식을 축소화해서 W를 3x4이라고 가정하고, x를 4x1이라고 생각해보자. 이렇게 두개를 곱하고 B를 더한 것의 스코어를 보면 각 클래스에 해당 이미지가 얼마나 가까운지를 알 수 있다.
스코어를 보면 강아지에 더 가깝다는 결과가 나온다. 따라서 W는 잘못된 값이 할당되어 있는 것이다.
[이미지에 따른 각 클래스들의 스코어 예시]
위 결과를 보고 알 수 있는 점은 자동차에 대한 모델 성능이 가장 좋다는 것이다.
[가중치를 시각적으로 표현한 것]
여기서 문제점은 각 클래스(카테고리)에서 하나의 이미지 유형만 학습한다는 점이 보인다. 예를 들면 차는 빨간색 차, 말은 왼쪽을 보는 말, 개구리는 노란색 개구리 등 이렇게 한 이미지 유형만 학습한다.
이게 문제가 되는 이유는 예를 들어 노란차를 모델에 넣는다면 Car라는 클래스보다 Flog라는 클래스로 예측될 가능성이 높기 떄문에서 성능상 문제를 준다.
NN에서는 해당 부분이 개선되었다.
위 이미지는 다중 클래스 분류 문제를 시각적으로 설명하고 있다. 각각의 클래스에 대해 별도의 결정 경계(선)가 설정되어 있다.
위의 이미지를 다른 블로그에서는 "이미지를 고차원 공간의 한 점이라고 생각하면, 선형 분류는 위 사진과 같이 클래스를 구분 시켜주는 선형 boundary 역할을 한다. 위 공간의 차원은 이미지 픽셀의 차원과 동일하다." 라고 설명했다. 이 말을 쉽게 해석하자면 아래와 같다.
이미지를 고차원 공간의 한 점이라고 생각한다는 것은, 이미지를 숫자로 바꿔서, 각각의 숫자들이 하나의 큰 공간 안에 좌표처럼 있다고 생각하는 것이다. 예를 들어, 32x32 크기의 이미지를 생각하면, 1,024개의 픽셀(32 x 32)이 각각 하나의 차원을 만들게 돼. 이 이미지 자체는 그 고차원 공간 안에 있는 하나의 점으로 볼 수 있다.
이제, Linear classifier는 이 점들이 모여 있는 공간을 두 개 이상의 그룹으로 나누는 "직선 같은 경계"를 만드는 분류기이다. 이 직선 또는 경계는 이미지를 "비행기", "자동차", "사슴"처럼 여러 가지 카테고리로 나눠주는 역할을 한다. 쉽게 말해, 이 선이 어떤 이미지를 비행기인지 자동차인지 구분해준다는 것이다.
이미지의 픽셀 차원이라는 말은, 고차원 공간의 차원이 이미지의 크기, 즉 픽셀 수와 같다는 의미이다. 예를 들어, 이미지가 32x32 크기라면 1,024차원의 공간에서 이미지를 표현한다고 생각할 수 있다.
💡 쉽게 말하면, 이미지를 픽셀화해서 각각의 픽셀을 숫자로 바꾸고, 그 숫자들이 모인 것을 하나의 점으로 본다고 생각하면 된다. 그리고 그 점들이 모여서 여러 개의 점들이 있는 고차원 공간이 만들어지고, 선형 분류기는 그 공간에서 각 점들을 그룹(예: 비행기, 자동차, 사슴)으로 나누기 위한 "직선(또는 경계)"을 만든다. 그래서 이 경계를 기준으로 어떤 점이 어떤 그룹에 속하는지를 결정한다.
하나 문제가 더 있는데, 아래와 같은 데이터 셋은 선형분류가 어렵다.
L1과 L2의 차이점
L1과 L2는 머신러닝에서 손실 함수나 정규화 기법으로 자주 사용되는 용어인데, 각기 다른 방법으로 모델의 가중치를 제어한다.
L1 정규화 (L1 Norm, Lasso): L1은 가중치의 절댓값의 합을 최소화하려고 한다. 이를 통해 가중치 중 일부가 0이 될 수 있는데, 이는 모델이 중요하지 않은 특성을 제거하고 희소한(0이 많은) 가중치를 갖도록 한다. 즉, 변수를 선택하는 효과가 있어서 모델을 단순하게 만드는 데 도움이 된다. / 이미지를 봤을 때 원점으로 부터의 거리가 L1 좌표계 회전에 영향을 받는다.
단점: 중요한 특성이더라도 잘못처리되면 가중치가 0이되어 버릴 수 있어 성능이 떨어질 가능성이 있다.
L2 정규화 (L2 Norm, Ridge): L2는 가중치의 제곱합을 최소화하려고 한다. 모든 가중치를 0에 가깝게 만들지만 완전히 0으로 만들지는 않는다. 이를 통해 모델이 모든 특성을 사용할 수 있게 하되, 그 영향력을 작게 만드는 방식이다. 이는 모델이 너무 복잡해지는 것을 방지하고, 과적합(overfitting)을 줄이는 데 유용하다. / 이미지를 봤을 때 원점으로 부터의 거리가 L2좌표계 회전에 영향을 받지 않는다.
* 과적합: 기계 학습(machine learning)에서 학습 데이터를 과하게 학습(overfitting)하는 것
단점: 불필요한 특성까지도 가중치가 0이 되지않기 때문에 모델에 남아있을 수 있고, 해석이 어렵다.
간단히 말해, L1은 가중치를 0으로 만들 수 있는 가능성이 있고, L2는 가중치를 작게 만들어 모델을 더 일반화시키는 데 중점을 둔다.
'CVLab > cs231n (2016)' 카테고리의 다른 글
cs231n - lecture05(Trainit NN Part 1) (7) | 2024.09.28 |
---|---|
2024.09.27 Q&A (1) | 2024.09.27 |
cs231n - lecture04(역전파, 신경망) (0) | 2024.09.27 |
cs231n - lecture03(손실함수, 경사하강법) (1) | 2024.09.23 |
cs231n - lecture01(컴퓨터 비전의 역사) (3) | 2024.09.21 |