딥러닝 학습 향상을 위한 고려 사항들

1. 가중치 감소(Weight Decay)를 통한 가중치 정형화(Weight Regularization)

손실함수에 어떤 제약 조건을 적용해 오버피팅을 최소화하는 방법으로 L1 정형화와 L2 정형화가 있습니다. 오버피팅은 특정 가중치값이 커질수록 발생할 가능성이 높아지므로 이를 해소하기 위해 특정값을 손실함수에 더해주는 것이 정형화 중 가중치 감소(Weight Decay)이며, 더해주는 특정값을 결정하는 것이 L1 정형화와 L2 정형화입니다. 파이토치에서 이 Weight Decay는 다음 코드처럼 적용할 수 있습니다.

optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay=0.1)

결과적으로 weight_decay의 값이 커질수록 가중치 값이 작어지게 되고, 오버피팅 현상을 해소할 수 있지만, weight_decay 값을 너무 크게 하면 언더피팅 현상이 발생하므로 적당한 값을 사용해야 합니다.

2. 데이터 증강(Data Augmentation)

훈련 데이터를 알고리즘에 의해 그 수를 늘리는 방법으로, 오버피팅 해소는 물론 훈련 및 테스트 데이터에 대한 정확도를 높일 수 있는 방법입니다. 다음 코드의 예처럼 데이터셋에 대한 transform 인자에 지정하여 적용할 수 있습니다.

mnist_train = dset.MNIST(
    './', 
    train=True, 
    transform = transforms.Compose([
        transforms.Resize(34),                        # 원래 28x28인 이미지를 34x34로 늘립니다.
        transforms.CenterCrop(28),                    # 중앙 28x28를 뽑아냅니다.
        transforms.RandomHorizontalFlip(),            # 랜덤하게 좌우반전 합니다.
        transforms.Lambda(lambda x: x.rotate(90)),    # 람다함수를 이용해 90도 회전해줍니다.
        transforms.ToTensor(),                        # 이미지를 텐서로 변형합니다.
    ]),
    target_transform=None,
    download=True
)

3. 가중치 초기화(Weight Initialization)

신경망이 깊어질 수록 각 신경망의 가중치값들의 분포가 한쪽으로 쏠리거나, 특정 값 부분으로 심하게 모일 수 있는 현상이 발생합니다. 이런 현상이 발생하면 기울기 소실(Gradient Vanishing)이 발생할 수 있고, 신경망의 표현력에 제한이 생겨 신경망을 깊게 구성하는 의미가 사라집니다. 이를 위해 학습하기 전에 가중치를 적당하게 초기하는 것이 필요합니다. 아래의 코드는 파이토치에서 흔히하게 가중치를 초기하는 코드의 예입니다.

class aDNN(nn.Module):
    def __init__(self):
        super(aDNN,self).__init__()
        self.layer = nn.Sequential(
            # ...
        )             
        
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                '''
                m.weight.data.normal_(0.0, 0.1)     # 가중치를 평균 0, 편차 0.1로 초기화
                m.bias.data.fill_(0)                # 편차를 0으로 초기화
                '''

                '''
                init.xavier_normal(m.weight.data)   # Xavier Initialization
                m.bias.data.fill_(0)                # 편차를 0으로 초기화 
                '''

                '''                
                init.kaiming_normal_(m.weight.data) # Kaming He Initialization
                m.bias.data.fill_(0)                # 편차를 0으로 초기화 
                '''
            elif isinstance(m, nn.Linear):
                # 위의 코드와 유사함

Xavier 초기화는 활성화함수가 Sigmoid나 Tanh일때 적당하며, Kaming He 초기화는 활성화 함수가 ReLU일때 적당합니다.

4. 학습률 스케쥴러(Learning Rate Scheduler)

학습이 진행되면서 학습률을 그 상황에 맞게 가변적으로 적당하게 변경될 수 있다면 더 낮은 손실값을 얻을 수 있습니다. 이를 위해 학습률을 스케쥴이 필요합니다. 이와 관련된 코드는 다음과 같습니다.

# 학습률 스케줄러는 옵티마이져를 통해 적용된다.
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

# 지정한 스텝 단위로 학습률에 감마를 곱해 학습률을 감소시키는 방식
scheduler = lr_scheduler.StepLR(optimizer, step_size=1, gamma= 0.99)       

# 지정한 스텝 지점(예시에서는 10,20,40)마다 학습률에 감마를 곱해줘서 감소시키는 방식
scheduler = lr_scheduler.MultiStepLR(optimizer, milestones=[10,20,40], gamma= 0.1)  

# 매 epoch마다 학습률에 감마를 곱해줌으로써 감소시키는 방식
scheduler = lr_scheduler.ExponentialLR(optimizer, gamma= 0.99)                             

# 원하는 에폭마다, 이전 학습률 대비 변경폭에 따라 학습률을 감소시켜주는 방식
scheduler = lr_scheduler.ReduceLROnPlateau(optimizer,threshold=1,patience=1,mode='min')

위의 스케줄러를 학습에 적용할때 사용방법은 나뉘는데, StepLR, MultiStepLR, ExponentialLR의 경우는 아래 코드를 참고하기 바랍니다.

for i in range(num_epoch):
    scheduler.step() # !!

    optimizer.zero_grad()
    output = model.forward( .. )
    loss = loss( .. )
    loss.backward()
    optimizer.step()

ReduceLRONPlateau의 경우는 아래 코드를 참고하기 바랍니다.

for i in range(num_epoch):
    optimizer.zero_grad()
    output = model.forward( .. )
    loss = loss( .. )
    loss.backward()
    optimizer.step()
    
    scheduler.step(loss) # !!  

5. 학습 데이터의 정규화(Data Normalization)

입력되는 데이터에 대해서 공간상 분포를 정규화시켜주게 되면, 더 높은 정확도를 얻을 수 있게 됩니다. 정규화의 방법은 전체 데이터에 대한 평균과 표준편차를 이용하는데, 데이터에 대해서 평균을 빼고 표준편차로 나눠줍니다. 이를 위한 코드의 예는 아래와 같습니다.

mnist_train = dset.MNIST(
    "./", 
    train=True, 
    transform=transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize(mean=(0.1307,), std=(0.3081,))
    ]),
    download=True
)

mnist_test = dset.MNIST(
    "./", 
    train=False, 
    transform=transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize(mean=(0.1307,), std=(0.3081,))
    ]),
    download=True)

데이터에 대한 평균 및 표준편차를 얻는 것은 아래의 글을 참고하기 바랍니다.

이미지 Dataset에 대한 평균과 표준편차 구하기

6. 다양한 경사하강법(Gradient Descent Variants)

최소의 손실값을 찾기 위해 손실함수의 미분으로 구한 기울기를 따라 이동하게 되는데, 이동하는 방식에 대한 선택에 대한 것입니다. SGD 방식, Adam 방식 등이 존재하는데 그중 Adam에 대한 파이토치의 코드는 다음과 같습니다.

optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

SGD와 Adam 이외에도 Momentum 방식과 AdaGrad 방식의 있으며, Adam이 Momentum 방식과 AdaGrad 방식의 장점을 혼합한 것입니다.

7. 배치 정규화(Batch Normalization)

각 신경망의 활성화 값 분포가 적당히 퍼지도록 개선하여 원할한 학습이 진행되도록 돕는 기법입니다. 학습속도가 빨라지고, 앞서 언급한 가중치 초기값 설정에 신경쓸 필요가 없게 됩니다. 또하는 오버피팅을 억제하게 되어 가중치 감소 기법이나 드롭아웃 기법의 필요성이 감소합니다. 이 배치 정규화는 배치 단위로 입력되는 데이터의 연산 결과를 다시 결과의 평균과 분선이 1이 되도록 재가공합니다. 그리고 또 다시 특정 값을 곱하고 또 다른 특정값을 더하는 간단한 Scaling 및 Shifting 처리가 이루어집니다. Scaling 및 Shifting 처리를 위한 2개의 값은 학습을 통해 산출됩니다.

아래의 코드는 배치 정규화에 대한 예제 코드입니다.

class CNN(nn.Module):
    def __init__(self):
        super(CNN,self).__init__()
        self.layer = nn.Sequential(
            nn.Conv2d(1, 16, 3, padding=1),
            nn.BatchNorm2d(16),
            nn.ReLU(),
            nn.Conv2d(16, 32, 3,padding=1),
            nn.BatchNorm2d(32),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),           
            nn.Conv2d(32, 64, 3,padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)        
        )
        self.fc_layer = nn.Sequential(
            nn.Linear(64*7*7, 128),
            nn.BatchNorm1d(128),
            nn.ReLU(),
            nn.Linear(128, 64),
            nn.BatchNorm1d(64),
            nn.ReLU(),            
            nn.Linear(64, 10),
        )       

    def forward(self,x):
        out = self.layer(x)
        out = out.view(batch_size, -1)
        out = self.fc_layer(out)
        return out

학습이 끝나고 평가 시에는 반드시 아래의 코드 호출이 선행되어야 합니다.

model.eval()

8. 드롭아웃(Drop Out)

이 기법이 적용되면 훈련 데이터에 대한 정확도가 떨어지지만 오버피팅을 억제하기 위한 기법입니다. 즉, 특정 확률로 신경망의 뉴런을 비활성화시켜 연산에서 제외시키는 방식입니다. 드롭아웃에 대한 코드 예는 다음과 같습니다.

layer = nn.Sequential(
     nn.Conv2d(1,16,3,padding=1),
     nn.ReLU(),
    nn.Dropout2d(0.2),
    nn.Conv2d(16,32,3,padding=1),
    nn.ReLU(),
    nn.Dropout2d(0.2),
    nn.MaxPool2d(2,2),
    nn.Conv2d(32,64,3,padding=1),
    nn.ReLU(),
    nn.Dropout2d(0.2),
    nn.MaxPool2d(2,2)
)

학습이 끝나고 평가 시에는 반드시 아래의 코드 호출이 선행되어야 합니다.

model.eval()

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다