Residual Network (ResNet) 개요와 핵심 내용 정리
Residual Network(ResNet)는 딥러닝 모델에서 신경망을 깊게 쌓을 때 발생하는 문제를 해결하기 위해 2015년 He et al.이 제안한 네트워크입니다. ResNet은 특히 딥러닝 모델의 깊이 증가에 따른 성능 저하 문제와 그래디언트 소실 문제를 해결하는 데 중요한 기여를 했습니다. 이 글에서는 ResNet의 기본 개념과 논문에서 강조된 중요한 내용들을 정리해 보겠습니다.
1. ResNet의 주요 문제 해결 접근
딥러닝 모델의 깊이 문제: 딥러닝에서는 일반적으로 모델의 깊이를 늘리면 더 높은 수준의 표현 학습이 가능해지지만, 지나치게 깊은 모델은 학습이 어려워지고 성능이 오히려 저하될 수 있습니다. 이러한 문제의 주요 원인 중 하나는 그래디언트 소실/폭주 문제와 기울기 손실 문제입니다. ResNet은 이러한 문제를 해결하기 위해 새로운 네트워크 구조인 잔차 블록(Residual Block) 을 도입했습니다.
2. 잔차 블록(Residual Block)
잔차 블록은 입력값을 그대로 출력값에 더하는 스킵 연결(skip connection)을 특징으로 합니다. 이러한 연결을 통해 모델은 학습 과정에서 각 레이어가 입력과 출력 사이의 변화를 학습하는 대신, 단순히 입력을 그대로 전달하는 지름길을 만들 수 있습니다. 이는 다음과 같은 주요 효과가 있습니다:
- 그래디언트 흐름 개선: 스킵 연결 덕분에 그래디언트가 역전파될 때 소실되지 않고 깊은 네트워크를 안정적으로 학습할 수 있습니다.
- 식별 함수 학습: 일반적인 레이어가 아닌 잔차를 학습하기 때문에 레이어는 항상 입력을 그대로 전달하거나 그와 약간 다른 출력을 만들 수 있습니다. 즉, 모델이 학습하지 않아야 할 경우 네트워크가 스스로 식별 함수(identity function)를 학습하도록 돕습니다.
수식적으로: 입력을 x라 할 때, Residual Block의 출력은 F(x) + x 형태로 표현됩니다. 여기서 F(x)는 여러 레이어를 통과한 뒤의 변환 결과입니다. 스킵 연결을 통해 x가 그대로 더해지므로, 네트워크는 F(x)와 x의 합을 학습하는 방식으로 안정적인 학습을 할 수 있게 됩니다.
3. ResNet의 구조적 특징
- 네트워크 깊이의 증가: ResNet은 잔차 블록을 반복적으로 쌓아서 네트워크의 깊이를 극대화할 수 있습니다. ResNet-50, ResNet-101, ResNet-152와 같은 모델은 각각 50, 101, 152개의 레이어를 사용하여 구성됩니다. 깊은 네트워크임에도 불구하고 스킵 연결 덕분에 성능 저하 없이 안정적으로 학습이 가능합니다.
- 잔차 연결의 다양한 활용: 논문에서는 간단한 두 개의 convolution layer로 구성된 기본 잔차 블록 외에도, 입력과 출력 차원이 다를 경우 이를 맞춰주기 위한 1x1 컨볼루션을 사용하는 방법도 설명합니다. 이를 통해 네트워크 구조 내에서 유연하게 잔차 연결을 활용할 수 있습니다.
4. 논문의 주요 기여
- 깊은 네트워크의 성공적인 학습: ResNet은 최대 152개 레이어를 사용해 ImageNet 데이터셋에서 최고의 성능을 달성했습니다. 이는 딥러닝 모델이 단순히 레이어 수를 늘리는 것만으로는 성능이 개선되지 않는다는 점을 보여줬으며, 모델의 깊이를 효과적으로 사용하기 위해서는 잔차 연결과 같은 기술이 필요하다는 것을 입증했습니다.
- Residual Learning의 개념 도입: 논문에서는 딥러닝 모델이 본질적으로 학습해야 하는 문제를 잔차(Residual)로 재정의함으로써, 깊이 증가에 따른 비선형 표현력의 제한 문제를 해결했습니다. 이를 통해 학습의 수렴 속도를 개선하고 훈련 효율성을 극대화할 수 있음을 보였습니다.
K. He, X. Zhang, S. Ren and J. Sun, "Deep Residual Learning for Image Recognition," 2016 IEEE Conference on Computer Vision and Pattern Recognition (CVPR), Las Vegas, NV, USA, 2016, pp. 770-778, doi: 10.1109/CVPR.2016.90.
import tensorflow as tf
import numpy as np
Residual Unit 구현
class ResidualUnit(tf.keras.Model):
def __init__(self, filter_in, filter_out, kernel_size):
super(ResidualUnit, self).__init__()
self.bn1 = tf.keras.layers.BatchNormalization()
self.conv1 = tf.keras.layers.Conv2D(filter_out, kernel_size, padding='same')
self.bn2 = tf.keras.layers.BatchNormalization()
self.conv2 = tf.keras.layers.Conv2D(filter_out, kernel_size, padding='same')
if filter_in == filter_out:
self.identity = lambda x: x
else:
self.identity = tf.keras.layers.Conv2D(filter_out, (1,1), padding='same')
def call(self, x, training=False, mask=None):
h = self.bn1(x, training=training)
h = tf.nn.relu(h)
h = self.conv1(h)
h = self.bn2(h, training=training)
h = tf.nn.relu(h)
h = self.conv2(h)
return self.identity(x) + h
Residual Layer 구현
class ResnetLayer(tf.keras.Model):
def __init__(self, filter_in, filters, kernel_size):
super(ResnetLayer, self).__init__()
self.sequence = list()
for f_in, f_out in zip([filter_in] + list(filters), filters):
self.sequence.append(ResidualUnit(f_in, f_out, kernel_size))
def call(self, x, training=False, mask=None):
for unit in self.sequence:
x = unit(x, training=training)
return x
모델 정의
class ResNet(tf.keras.Model):
def __init__(self):
super(ResNet, self).__init__()
self.conv1 = tf.keras.layers.Conv2D(8, (3, 3), padding='same', activation='relu') # 28x28x8
self.res1 = ResnetLayer(8, (16, 16), (3, 3)) # 28x28x16
self.pool1 = tf.keras.layers.MaxPool2D((2, 2)) # 14x14x16
self.res2 = ResnetLayer(16, (32, 32), (3, 3)) # 14x14x32
self.pool2 = tf.keras.layers.MaxPool2D((2, 2)) # 7x7x32
self.res3 = ResnetLayer(32, (64, 64), (3, 3)) # 7x7x64
self.flatten = tf.keras.layers.Flatten()
self.dense1 = tf.keras.layers.Dense(128, activation='relu')
self.dense2 = tf.keras.layers.Dense(10, activation='softmax')
def call(self, x, training=False, mask=None):
x = self.conv1(x)
x = self.res1(x, training=training)
x = self.pool1(x)
x = self.res2(x, training=training)
x = self.pool2(x)
x = self.res3(x, training=training)
x = self.flatten(x)
x = self.dense1(x)
return self.dense2(x)
학습, 테스트 루프 정의
# Implement training loop
@tf.function
def train_step(model, images, labels, loss_object, optimizer, train_loss, train_accuracy):
with tf.GradientTape() as tape:
predictions = model(images, training=True)
loss = loss_object(labels, predictions)
gradients = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(gradients, model.trainable_variables))
train_loss(loss)
train_accuracy(labels, predictions)
# Implement algorithm test
@tf.function
def test_step(model, images, labels, loss_object, test_loss, test_accuracy):
predictions = model(images, training=False)
t_loss = loss_object(labels, predictions)
test_loss(t_loss)
test_accuracy(labels, predictions)
데이터셋 준비
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
x_train = x_train[..., tf.newaxis].astype(np.float32)
x_test = x_test[..., tf.newaxis].astype(np.float32)
train_ds = tf.data.Dataset.from_tensor_slices((x_train, y_train)).shuffle(10000).batch(32)
test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)
학습 환경 정의
모델 생성, 손실함수, 최적화 알고리즘, 평가지표 정의
# Create model
model = ResNet()
# Define loss and optimizer
loss_object = tf.keras.losses.SparseCategoricalCrossentropy()
optimizer = tf.keras.optimizers.Adam()
# Define performance metrics
train_loss = tf.keras.metrics.Mean(name='train_loss')
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')
test_loss = tf.keras.metrics.Mean(name='test_loss')
test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='test_accuracy')
학습 루프 동작
EPOCHS = 10
for epoch in range(EPOCHS):
for images, labels in train_ds:
train_step(model, images, labels, loss_object, optimizer, train_loss, train_accuracy)
for test_images, test_labels in test_ds:
test_step(model, test_images, test_labels, loss_object, test_loss, test_accuracy)
template = 'Epoch {}, Loss: {}, Accuracy: {}, Test Loss: {}, Test Accuracy: {}'
print(template.format(epoch + 1,
train_loss.result(),
train_accuracy.result() * 100,
test_loss.result(),
test_accuracy.result() * 100))
train_loss.reset_states()
train_accuracy.reset_states()
test_loss.reset_states()
test_accuracy.reset_states()
Epoch 1, Loss: 0.15172302722930908, Accuracy: 95.86000061035156, Test Loss: 0.08555025607347488, Test Accuracy: 97.44999694824219
Epoch 2, Loss: 0.0681067630648613, Accuracy: 98.1050033569336, Test Loss: 0.07039548456668854, Test Accuracy: 98.25999450683594
Epoch 3, Loss: 0.05503721907734871, Accuracy: 98.45333099365234, Test Loss: 0.05514393374323845, Test Accuracy: 98.36000061035156
Epoch 4, Loss: 0.040750861167907715, Accuracy: 98.83833312988281, Test Loss: 0.037818700075149536, Test Accuracy: 98.94999694824219
Epoch 5, Loss: 0.036504119634628296, Accuracy: 99.00166320800781, Test Loss: 0.034332212060689926, Test Accuracy: 99.02999877929688
Epoch 6, Loss: 0.029598338529467583, Accuracy: 99.1066665649414, Test Loss: 0.04355289414525032, Test Accuracy: 98.83999633789062
Epoch 7, Loss: 0.025894133374094963, Accuracy: 99.25, Test Loss: 0.05185529962182045, Test Accuracy: 98.7699966430664
Epoch 8, Loss: 0.02600380778312683, Accuracy: 99.288330078125, Test Loss: 0.04667523875832558, Test Accuracy: 98.83999633789062
Epoch 9, Loss: 0.02270493097603321, Accuracy: 99.35832977294922, Test Loss: 0.054312728345394135, Test Accuracy: 99.05999755859375
Epoch 10, Loss: 0.020289067178964615, Accuracy: 99.42500305175781, Test Loss: 0.04631120711565018, Test Accuracy: 98.97999572753906
'AI' 카테고리의 다른 글
DenseNet 구현 및 학습 (1) | 2024.11.22 |
---|---|
TensorFlow 함수형 API 로 VGGNet 논문 구현 (1) | 2024.11.19 |
TensorFlow 함수형 API 로 AlexNet 논문 구현 (0) | 2024.11.18 |
Colab 에서 Kaggle 데이터 API 로 받기 (3) | 2024.11.15 |
ViT 에서 언급한 CNN 의 Inductive bias 에 대해 알아보자 (3) | 2024.11.13 |