전통적인 완전 연결 계층(Fully Connected Layer, FC Layer)
만을 가진 신경망이 이미지 처리에 어려움을 겪음
공간 구조 무시
파라미터의 수
CNN(Convolutional Neural Networks)
FC Layer
의 문제점을 극복하고 이미지를 분석하기 위해 많은 양의 공간적 구조 정보를 처리하고자 함
CNN은 이미지 데이터의 공간적 구조를 활용하고, 작은 인식 영역(Receptive Field)을 통해 지역적인 특징을 학습하며, 위치에 무관한 특징을 인식하는 등의 가정이 내포되어 있음
이러한 Inductive Bias
덕분에 CNN은 이미지 데이터에 특화된 강력한 성능을 냄
Inductive Bias
: 주어지지 않은 입력의 출력을 예측하는 것
🤔 필터(Filter)와 커널(Kernel)
필터와 커널은 약간의 의미적 차이가 있지만, 보통 CNN 구조 내에서는 동의어로 사용됩니다. 커널은 여러 층 중 하나의 층에서 인식 영역과 계산하는 작은 가중치 행렬 개념이라면 필터는 이 커널이 중첩되어 여러 층에 적용될 때 활용하는 개념입니다. 쉽게 말하면 커널의 집합을 필터라고 합니다.
풀링 계층은 이미지의 크기를 줄이는 과정에서 주로 Max Pooling을 사용함
Max Pooling은 주어진 영역에서 가장 큰 값을 선택하여,
DownSampling된 특성맵(Feature Map)을 생성하는 과정을 말함
⤬ 이미지 전체에 대한 정보를 종합하고, 이를 사용하여 최종 출력을 생성
⤬ 이 전 계층까지는 공간적인 정보를 담고 있어 출력된 값이 2차원 이상으로 표현되었지만, 이 계층을 통과하기 위해 평탄화(Flattened) 작업이 이루어짐
⤬ 이전 레이어에서 학습한 특징을 기반으로 고수준의 추상성을 포착하고 최종 예측을 수행하는 데 중요한 역할을 함
⤬ 네트워크가 복잡한 비선형 관계를 모델링하고 분류, 회귀 또는 다른 작업을 수행할 수 있도록 해줌
CNN은 이미지의 공간 구조를 활용하기 때문에 효과적으로 파라미터를 공유하고, 훨씬 적은 수의 파라미터로 복잡한 모델을 만들 수 있습니다. 이로 인해 모델의 메모리 사용량이 줄고, 계산 효율성이 향상되며, 과적합의 가능성이 줄어듭니다.
CNN은 이미지 내의 객체가 어디에 위치해 있더라도 그 객체를 인식할 수 있습니다. 이는 합성곱 계층과 풀링 계층 덕분에 가능하며, 이를 통해 CNN은 위치에 무관한 특징을 학습할 수 있습니다.
CNN은 일반적으로 여러 계층으로 구성됩니다. 이를 통해 저수준의 특징(선분, 색상 등)에서부터 고수준의 특징(형상, 객체 등)까지 차례대로 학습할 수 있습니다.
합성곱 레이어에서는 이름에서 알 수 있듯이 합성곱(Convolution) 연산이 이루어집니다. 합성곱은 필터가 이미지를 슬라이드하면서 해당 필터와 이미지 부분 간의 요소별 곱셈을 수행하고 결과를 모두 더하는 과정입니다.
합성곱 계층 연산과정
이 과정을 거치면서 각 위치에서의 합성곱 연산의 결과로 생성된 출력값 행렬이 생성되는데요, 이를 특성맵(Feature Map)이라고 합니다. 이러한 연산은 필터가 탐지한 특정 패턴이 이미지의 어디에 존재하는지를 파악하는 데 도움이 됩니다.
이미지는 상당히 고차원적인 데이터입니다. 예를 들어, 128 × 128 픽셀의 작은 이미지라고 하더라도 컬러 이미지는 각 픽셀마다 빨강, 초록, 파랑의 3개 채널이 있으므로 총 49,152개의 값을 가지게 됩니다. 이렇게 큰 데이터를 처리하려면 많은 계산량이 필요합니다. 그러나 합성곱은 지역적인 영역에 집중하여 계산을 수행하므로 계산량을 크게 줄일 수 있습니다.
import torch
import torch.nn as nn
# 간단한 CNN 모델 정의
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.conv = nn.Conv2d(3, 16, kernel_size=5)
def forward(self, x):
conv = self.conv(x)
return conv
# 모델의 인스턴스 생성
model = CNN()
# 입력 텐서
x = torch.randn(1, 3, 32, 32) # (배치 크기, 채널, 높이, 너비)
# 합성곱 연산
conv = model(x)
print(conv.shape)
import numpy as np
# 입력 텐서
x = np.random.randn(1, 3, 32, 32) # (배치 크기, 채널, 높이, 너비)
# 합성곱 레이어의 가중치
w_conv = np.random.randn(16, 3, 5, 5) # (필터 개수, 입력 채널, 커널 높이, 커널 너비)
b_conv = np.random.randn(16) # (필터 개수)
# 합성곱 연산
conv = np.zeros((1, 16, 28, 28)) # (배치 크기, 필터 개수, 출력 높이, 출력 너비)
for f in range(16):
for i in range(28):
for j in range(28):
conv[0, f, i, j] = np.sum(x[0, :, i:i+5, j:j+5] * w_conv[f]) + b_conv[f]
print(conv.shape)
풀링(Pooling)은 특성맵의 크기를 줄이기 위해 DownSampling하는 과정입니다. Pooling의 방법으로는 Average Pooling과 Max Pooling이 있습니다. 이미지 처리 분야에서는 보통 Max Pooling을 활용합니다. Max Pooling은 특성맵을 구성하는 값 중에서 가장 큰 값을 선택하는 방법입니다.
import torch
import torch.nn as nn
# Create a 2D input tensor
input_tensor = torch.tensor([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]], dtype=torch.float32)
# Define max pooling layer
max_pool = nn.MaxPool2d(kernel_size=2, stride=2)
# Apply max pooling
output = max_pool(input_tensor.unsqueeze(0).unsqueeze(0)) # Add dimensions for batch and channels
print(output.squeeze(0).squeeze(0))
import numpy as np
def max_pooling(x, k):
# x: input feature map
# k: pooling size
height, width = x.shape
h_out = height // k
w_out = width // k
pooled = np.zeros((h_out, w_out))
for i in range(h_out):
for j in range(w_out):
r_start = i * k
r_end = r_start + k
c_start = j * k
c_end = c_start + k
pooled[i, j] = np.max(x[r_start:r_end, c_start:c_end])
return pooled
완전 연결 레이어는 각 입력 노드와 출력 노드가 모두 연결되어 있습니다.
이 레이어에서의 계산은 기본적으로 입력 특징과 가중치 벡터 간의 내적을 계산하고, 편향을 더하는 것입니다. 수식으로 나타내면 아래와 같습니다.
여기서 는 가중치, 는 입력 데이터(특성맵), 는 편향을 나타냅니다.
계산된 출력은 비선형 활성화 함수(주로 Softmax 등)를 통과하여 최종 결괏값이 출력됩니다.
완전 연결 계층은 보통 네트워크의 마지막 부분에서 계산됩니다.
앞선 단계에서 종합한 공간적 특성들로 최종적인 예측을 수행하는 단계라고 볼 수 있습니다.
이미지 분류 문제로 예를 들면, 마지막 완전 연결 계층의 최종 출력 값은 클래스의 수만큼 지정되고 각 클래스에 대한 확률을 계산합니다. 이를 통해 모델은 이미지가 지닌 복잡한 패턴과 클래스 레이블 사이의 관계를 학습할 수 있습니다.
import torch
import torch.nn as nn
# 랜덤한 입력 텐서 생성
x = torch.randn(32, 16) # (배치 크기, 입력 크기)
# 완전 연결 레이어 정의
fc = nn.Linear(16, 10) # (입력 크기, 출력 크기)
# 완전 연결 레이어 적용
output = fc(x)
print(output.shape) # 출력 형태: (32, 10)
import numpy as np
# 입력 텐서
x = np.random.randn(32, 16) # (배치 크기, 입력 크기)
# FC 레이어의 가중치
w_fc = np.random.randn(16, 10) # (입력 크기, 출력 크기)
b_fc = np.random.randn(10) # (출력 크기)
# FC 연산
fc = np.dot(x, w_fc) + b_fc
print(fc.shape)
[출처 | 딥다이브 Code.zip 매거진]