머신러닝 스터디#1

머신러닝에서 한 단계 더 나아가, 딥러닝의 개념과 응용에 대해 알아보자자

Author: Han Damin
Tags: deep learning machine learning AI
Published: 2024/12/30 14:00 Series: 머신러닝 스터디

딥러닝은 머신러닝의 하위 분야로, 인공신경망(Artificial Neural Networks)을 활용하여 데이터를 학습하는 기술이다.

여러 층의 신경망을 쌓아올려 복잡한 패턴을 학습할 수 있다는 게 특징이다. 특히 컴퓨터 비전, 자연어 처리, 음성 인식 등의 분야에서 뛰어난 성능을 보여준다.



딥러닝의 구조적 이해

딥러닝의 구조를 제대로 이해하기 위해서는 층층이 쌓여있는 각 요소들의 역할을 알아야 한다.

딥러닝 기본 구조

출처: 딥러닝의 구조적 이해 - Freshdesk

1. 입력층 (Input Layer)

CNN 구조

출처: 합성곱 신경망의 기본 개념

입력층은 데이터가 처음 들어오는 곳이다. 예를 들어:

  • 이미지의 경우: 픽셀 값들이 입력된다 (224x224 크기 이미지면 50,176개의 뉴런)
  • 텍스트의 경우: 단어나 문자의 수치화된 값이 들어간다
  • 숫자 데이터의 경우: 정규화된 수치들이 입력된다
# 입력 데이터 준비 예시
x = torch.randn(32, 3, 224, 224)  # 배치크기 32, RGB 3채널, 224x224 이미지

입력 데이터는 항상 전처리가 필요하다. 정규화(Normalization)나 스케일링(Scaling)은 모델 학습에 매우 중요한 영향을 미친다.


2. 은닉층 (Hidden Layer)

다층 퍼셉트론 구조

출처: 다층 신경망 이해하기

은닉층은 딥러닝의 심장이라고 할 수 있다. 필자가 생각하기에 가장 신비로운 부분이다.

class DeepNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.hidden1 = nn.Linear(784, 256)  # 첫 번째 은닉층
        self.hidden2 = nn.Linear(256, 128)  # 두 번째 은닉층
        self.hidden3 = nn.Linear(128, 64)   # 세 번째 은닉층
        self.output = nn.Linear(64, 10)     # 출력층

    def forward(self, x):
        x = F.relu(self.hidden1(x))
        x = F.relu(self.hidden2(x))
        x = F.relu(self.hidden3(x))
        return self.output(x)

은닉층의 특징:

  1. 다양한 크기: 각 층의 뉴런 수는 자유롭게 설정할 수 있다
  2. 활성화 함수: 각 층 뒤에는 보통 활성화 함수가 붙는다
  3. 가중치와 편향: 각 층은 학습 가능한 파라미터를 가진다

은닉층의 개수와 각 층의 크기를 결정하는 것은 일종의 예술이다. 너무 작으면 학습이 부족하고, 너무 크면 과적합이 일어난다.

3. 출력층 (Output Layer)

RNN 구조

출처: 순환 신경망의 구조

출력층은 모델의 최종 예측을 만들어내는 곳이다. 문제의 종류에 따라 다양한 형태를 가질 수 있다.

  1. 분류 문제:
# 다중 클래스 분류
self.output = nn.Linear(hidden_size, num_classes)
output = F.softmax(self.output(x), dim=1)

# 이진 분류
self.output = nn.Linear(hidden_size, 1)
output = torch.sigmoid(self.output(x))
  1. 회귀 문제:
self.output = nn.Linear(hidden_size, 1)  # 단순 선형 출력

딥러닝의 학습 메커니즘

딥러닝 학습 과정

출처: 딥러닝 파이토치 교과서: 1.3.1 딥러닝 학습 과정

1. 가중치 초기화

가중치 초기화는 생각보다 중요하다.

def weight_init(m):
    if isinstance(m, nn.Linear):
        nn.init.xavier_uniform_(m.weight)
        nn.init.zeros_(m.bias)

model.apply(weight_init)  # 모델의 모든 층에 가중치 초기화 적용

Xavier 초기화는 가장 널리 쓰이는 방법 중 하나다. 층이 깊어져도 기울기가 폭발하거나 사라지는 것을 방지해준다.


2. 손실 함수 (Loss Function)

손실 함수는 모델이 얼마나 잘못 예측했는지 측정하는 기준이다. 필자는 이걸 '모델의 성적표'라고 생각한다.

# 다양한 손실 함수들
criterion_bce = nn.BCELoss()           # 이진 분류
criterion_ce = nn.CrossEntropyLoss()   # 다중 분류
criterion_mse = nn.MSELoss()           # 회귀

각각의 특징:

  • BCE: 이진 분류에 사용, 0~1 사이의 예측값 필요
  • CrossEntropy: 다중 분류의 표준, 소프트맥스와 짝꿍
  • MSE: 회귀 문제에서 가장 기본적인 손실 함수

3. 옵티마이저 (Optimizer)

옵티마이저는 손실을 바탕으로 모델의 파라미터를 업데이트하는 방법을 결정한다.

# 다양한 옵티마이저
optimizer_sgd = optim.SGD(model.parameters(), lr=0.01)
optimizer_adam = optim.Adam(model.parameters(), lr=0.001)
optimizer_rmsprop = optim.RMSprop(model.parameters(), lr=0.001)

솔직히 말하면, 대부분의 경우 Adam을 쓰면 잘 동작한다. 하지만 각각의 장단점을 아는 것도 중요하다:

  • SGD: 가장 기본적인 방법, 노이즈가 심하다
  • Adam: 대부분의 경우에 잘 작동, 하이퍼파라미터 튜닝이 비교적 쉽다
  • RMSprop: Adam과 비슷하지만, 모멘텀을 사용하지 않는다

마치며

이렇게 딥러닝의 구조를 자세히 살펴보았다. 필자도 이걸 처음 배울 때는 정말 어려웠는데, 하나씩 이해하다 보니 이제는 꽤 재미있게 느껴진다. 다른 분들도 꼭 한번 도전해보시길 바란다.

다음 글에서는 실제 프로젝트에서 딥러닝을 어떻게 활용하는지, 그리고 자주 마주치는 문제들의 해결 방법에 대해 다뤄보려고 한다.