합성곱 신경망
합성곱층의 필요성
합성곱 신경망은 이미지 전체를 한 번에 계산하는 것이 아닌 이미지의 국소적 부분을 계산함으로써 시간과 자원을 절약하여 이미지의 세밀한 부분까지 분석할 수 있는 신경망이다. 만약 (3 x 3) 배열을 펼쳐서(flattening) 각 픽셀에 가중치를 곱하여 은닉층에 전달하는 경우 데이터의 공간적 구조를 무시하게 된다. 이것을 방지하기 위해 도입된 것이 합성곱층이다.
합성곱 신경망의 구조
합성곱 신경망은 합성곱층과 풀링층을 거치면서 입력 이미지의 주요 `특성 벡터(feature vector)`를 추출한다. 이후 추출된 주요 특성 벡터들은 완전연결층을 거쳐 1차원 벡터로 변환되며, 마지막으로 출력층에서 활성화 함수인 `softmax` 함수를 사용하여 최종 결과가 출력된다.
입력층(input layer)
입력층은 입력 이미지 데이터가 최초로 거치게 되는 계층이다. 이미지는 단순 1차원의 데이터가 아닌 높이(height), 너비(width), 채널(channel)의 값을 갖는 3차원 데이터이다. 채널은 그레이스케일(gray scale)이면 1, 컬러(RGB)이면 3이다.
합성곱층(convolutional layer)
합성곱층은 입력 데이터에서 특성을 추출하는 역할을 수행한다. 이러한 특성 추출은 `커널(kernel)` 혹은 필터를 사용하여 이미지의 모든 영역을 훑으면서 특성을 추출한다. 이렇게 추출된 결과물이 `특성 맵(feature map)`이다. 커널은 주로 (3 x 3), (5 x 5) 크기로 적용하는 것이 일반적이며, `스트라이드(stride)`라는 지정된 간격에 따라 순차적으로 이동한다. 컬러 이미지의 합성곱의 경우 필터 채널이 3이며, RGB 각각에 서로 다른 가중치로 합성곱을 적용한 후 결과를 더해 준다. 참고로 필터 채널이 3이라고 필터 개수도 3인 것은 아니다. 실제로는 필터 개수가 한 개이다.
만약 필터 개수가 두 개 이상인 경우, 필터 각각은 특성 추출 결과의 채널이 된다. 즉, 이전 계층의 필터 개수는 다음 계층의 특성 맵의 채널이 된다.
합성곱 층을 요약하면 다음과 같다.
풀링층(pooling layer)
풀링층은 합성곱층과 유사하게 특성 맵의 차원을 다운 샘플링하여 연산량을 감소시키고, 주요한 특성 벡터를 추출하여 학습을 효과적으로 할 수 있게 한다. 다운 샘플링(down sampling)이란 이미지를 축소하는 것을 의미한다. 풀링 연산에는 대상 영역에서 최댓값을 추출하는 `최대 풀링(max pooling)`, 대상 영역에서 평균을 반환하는 `평균 풀링(average pooling)`을 사용한다. 그러나 평균 풀링은 각 커널 값을 평균화시켜 중요한 가중치를 갖는 값의 특성이 희미해질 수 있기 때문에 합성곱 신경망에서는 주로 최대 풀링을 사용한다. 최대 풀링과 평균 풀링을 비교하면 다음과 같다.
최대 풀링과 평균 풀링은 계산 과정은 다르지만 사용하는 파라미터는 동일하다. 이를 요약하면 다음과 같다.
완전연결층(fully connected layer)
합성곱층과 풀링층을 거치면서 차원이 축소된 특성 맵은 최종적으로 완전연결층(fully connected layer)으로 전달된다. 이 과정에서 이미지는 3차원 벡터에서 1차원 벡터로 펼쳐지게(flatten) 된다.
출력층(output layer)
출력층에서는 소프트맥스 활성화 함수가 사용되는데, 입력받은 값을 0~1 사이의 값으로 출력한다. 이 값은 이미지가 각 레이블에 속할 확률 값이 되며, 가장 높은 확률 값을 갖는 레이블이 최종 값으로 선정된다.
합성곱 신경망 맛보기
torchvision에 내장된 예제 데이터인 fashion_mnist 데이터셋을 통해 간단한 합성곱 신경망을 파이토치로 구현해 보았다. fashion_mnist는 (28x28) 크기의 이미지 데이터 7만 장으로 구성되어 있다.
class FashionCNN(nn.Module):
def __init__(self):
super(FashionCNN, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.layer2 = nn.Sequential(
nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.fc1 = nn.Linear(in_features=64*6*6, out_features=600)
self.drop = nn.Dropout2d(0.25)
self.fc2 = nn.Linear(in_features=600, out_features=120)
self.fc3 = nn.Linear(in_features=120, out_features=10)
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = out.view(out.size(0), -1) # flatten, (100, 2304)
out = self.fc1(out)
out = self.drop(out)
out = self.fc2(out)
out = self.fc3(out)
return out
- `nn.Sequential()` : __init()__에서 사용할 네트워크 모델들을 정의해 줄 뿐만 아니라 forward() 함수에서 구현될 순전파를 계층 형태로 좀 더 가독성이 뛰어난 코드로 작성
- 데이터가 각 계층을 순차적으로 지나갈 때 사용하면 좋은 방법
- 여러 개의 계층을 컨테이너에 구현하는 방법
- `BatchNorm2d()` : 학습 과정에서 각 배치 단위별로 데이터가 다양한 분포를 가지더라도 평균과 분산을 이용하여 정규화
- 입력 값의 분포가 모두 달라도 정규화를 통해 분포를 가우시안 형태로 만들어 평균은 0, 표준편차는 1로 데이터의 분포 조정
- 출력 크기를 계산하는 공식
- Conv2d : (W-F+2P)/S + 1
- MaxPool2d : IF/F
- IF : input filter size로 앞의 Conv2d의 출력 크기 / F : 커널 크기(kernel_size)
- `size()`는 (batch_size, channel, height, width)로 구성
이후 합성곱 네트워크를 구성하기 위한 파라미터를 지정한다.
# 합성곱 네트워크를 위한 파라미터 정의
learning_rate = 0.001
model = FashionCNN()
model.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
print(model)
이렇게 구성한 모델을 통해 학습 데이터를 이용하여 모델을 학습시킨다.
# 모델 학습 및 평가
num_epochs = 5
count = 0
loss_list = []
iteration_list = []
accuracy_list = []
predictions_list = []
labels_list = []
for epoch in range(num_epochs):
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
train = Variable(images.view(100, 1, 28, 28))
labels = Variable(labels)
outputs = model(train)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
count += 1
if not (count % 50):
total = 0
correct = 0
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
labels_list.append(labels)
test = Variable(images.view(100, 1, 28, 28))
outputs = model(test)
predictions = torch.max(outputs, 1)[1].to(device)
predictions_list.append(predictions)
correct += (predictions == labels).sum()
total += len(labels)
accuracy = correct * 100 / total
loss_list.append(loss.data)
iteration_list.append(count)
accuracy_list.append(accuracy)
if not (count % 500):
print("Iteration: {}, Loss: {}, Accuracy: {}%".format(count, loss.data, accuracy))
전이 학습(transfer learning)
일반적으로 합성곱 신경망 기반의 딥러닝 모델을 제대로 훈련시키기 위해선 많은 양의 데이터가 필요하지만 이는 많은 돈과 시간이 필요하기 때문에 쉽지 않다. 이러한 현실적인 어려움을 해결한 것이 바로 `전이 학습(transfer learning)`이다. 전이 학습은 이미지넷(ImageNet)과 같은 아주 큰 데이터를 통해 훈련된 모델의 가중치를 가져와 해결하려는 과제에 맞게 보정해서 사용하는 것을 의미한다. 이렇게 아주 큰 데이터셋을 통해 훈련된 모델을 사전 훈련된 모델이라고 한다. 이러한 전이 학습을 위한 방법에는 `특성 추출(feature extractor)`과 `미세 조정(fine-tuning)` 기법이 있다.
특성 추출 기법(feature extractor)
특성 추출 기법은 사전 훈련된 모델을 가져온 후 완전연결층 부분만 새로 만든다. 즉, 학습할 때는 마지막 완전연결층만 학습하고 나머지 계층들은 학습되지 않도록 한다. 특성 추출은 이미지 분류를 위해 두 부분으로 구성된다.
- 합성곱층 : 합성곱층 + 풀링층으로 구성
- 완전연결층(데이터 분류기) : 추출된 특성을 입력받아 최종적으로 이미지에 대한 클래스 분류 수행
결론적으로 사전 훈련된 네트워크의 합성곱층(가중치 고정)에 새로운 데이터를 통과시키고, 그 출력을 데이터 분류기에서 훈련시킨다. 여기에서 사용 가능한 이미지 분류 모델에는 `Xception`, `InceptionV3`, `ResNet50`, `VGG16`, `VGG19`, `MobileNet` 등이 있다.
특성 추출 기법을 파이토치를 통해 구현해 보았다. 먼저, 모델 객체를 불러들인다.
# 모델 객체 생성 및 손실 함수 정의
model = models.resnet18(pretrained=True)
for param in model.parameters(): # 모델의 합성곱층 가중치 고정
param.requires_grad = False
model.fc = torch.nn.Linear(512, 2)
for param in model.fc.parameters():
param.requires_grad = True # 완전연결층은 학습
print(model)
사전 학습된 `ResNet18` 모델을 사용하려면 torchvision.models에서 pretrained=True로 지정해 사용할 수 있다. 이는 이미 다양한 특징을 추출할 수 있는 가중치를 보유하고 있다. 사전 학습된 모델은 새로운 작업에 적용할 때, 특히 작은 데이터셋에 유리하다. pretrained=False로 모델을 사용할 경우 무작위로 초기화된 가중치가 주어지며 새로운 데이터셋에 맞게 학습이 필요하다. 이렇게 사전 학습된 모델을 생성한 후에는 완전 연결층에 대해 적용할 문제에 맞게 모델을 수정한다. 사전 학습된 ResNet18 모델은 1000개의 클래스가 존재하는 ImageNet 데이터셋을 통해 학습한 것이기 때문에 완전연결층의 out_features를 1000에서 2로 수정한다(모델을 적용할 문제는 개와 고양이를 구분하는 문제이므로 클래스는 2이다).
모델 객체를 생성한 이후에는 반복문을 통해 `model.parameters()`의 파라미터들에 대해 `requires_grad`를 False로 지정함으로써 합성곱층의 가중치를 고정시킨다. 반대로 `model.fc.parameters()`, 즉 완전연결층에 대한 파라미터들은 requires_grad를 True로 지정함으로써 학습을 진행한다.
미세 조정(fine-tuning) 기법
미세 조정 기법은 특성 추출 기법에서 더 나아가 사전 훈련된 모델과 합성곱층, 데이터 분류기의 가중치를 업데이트하여 훈련시키는 방식이다. 특성 추출 기법은 문제에 사용할 데이터와 ImageNet 데이터셋의 이미지 특징이 비슷하다는 전제하에 좋은 성능을 낼 수 있다. 만약 이미지 특징이 다를 경우 새로운 이미지 데이터를 사용하여 네트워크의 가중치를 업데이트해서 특성을 다시 추출할 수 있다. 즉, 사전 학습된 모델을 목적에 맞게 재학습시키거나 학습된 가중치의 일부를 재학습시키는 것이다. 데이터셋의 크기와 사전 훈련된 모델에 따른 미세 조정 기법의 상황별 전략은 다음과 같다.
- 데이터셋이 크고 사전 훈련된 모델과 유사성이 작을 경우 → 모델 전체를 재학습
- 데이터셋이 크고 사전 훈련된 모델과 유사성이 클 경우 → 합성곱층에서 완전연결층과 가까운 부분과 데이터 분류기를 학습
- 데이터셋이 유사하기 때문에 전체를 학습시키는 것보다 강한 특징이 나타나는 부분만 새로 학습해도 최적의 성능
- 데이터셋이 작고 사전 훈련된 모델과 유사성이 작을 경우 → 합성곱층의 일부분과 데이터 분류기를 학습
- 데이터셋이 작고 사전 훈련된 모델과 유사성이 클 경우 → 데이터 분류기만 학습
미세 조정은 파라미터 업데이트 과정에서 파라미터에 큰 변화를 주게 되면 과적합 문제가 발생할 수 있기 때문에 정교하고 미세한 파라미터 업데이트가 필요하다.
이 포스팅은 '딥러닝 파이토치 교과서' 교재를 공부하며 작성한 글입니다. (이미지 출처 : 더북(The Book))