본문 바로가기
카테고리 없음

bifpn,regnet,cnn 층을 활용하여 이미지 분류하기

by 차분한 공돌이 2024. 1. 7.

프로그램 시작 이유

테슬라 AI Day 자료를 보던 중 테슬라는 Backbone으로 regnet을, Neck으로 BiFPN을 사용하여 객체인지를 하고 있음을 알게 되었다.

테슬라의 Vision Network

 

현재 자율주행 자동차 회사에서 실제 사용하는 모델이라는 사실이 흥미로웠고, 자세한 구조나 구현방식에 대한 정보는 없어서 직접 regnet과 BiFPN을 이용한 이미지 분류 모델을 만드는 프로젝트를 시작하게 되었다. (원래는 데이콘 데이터를 활용한 객체 탐지를 목표하였으나 ram용량으로 인한 에러가 발생하여 cifar10 dataset을 이용한 이미지 분류로 목표를 수정하였다.)

 

프로젝트 계획

1. regnet과 BiFPN에 대한 배경지식이 없기에 이들의 탄생 목적과 작동 과정을 학습한다.

2. 학습한 개념을 바탕으로 regnet과 BiFPN의 코드를 이해하고 목적에 맞게 변형 및 추가하여 이미지 분류 모델을 만든다.

3.  train 과 test과정을 거치며 모델이 정상적으로 작동하는지 확인하고, 문제가 발생시 해결해 나간다.

4.결과 및 아쉬운 점

 

 

1.regnet 및 BiFPN 학습

regnet 의 탄생목적

 기존의 네트워크 구조들은 구체적인 특정 상황에 대하여 설계된 고정된 구조를 가졌다. 이 네트워크들은 해당 상황에 대해서는 잘 작동하지만, 새로운 상황에 대해서는 대처능력이 떨어지는 문제가 있었다. 이러한 범용성이 떨어지는 기존의 고정된 네트워크들의 문제를 해결하기 위한 목적으 파라메터를 통해 깊이, 너비, 해상도 등의 구조를 쉽게 변형할 수 있도록 설계된 가변형 네트워크 구조가 regnet이다. 이러한 가변성을 통해 regnet은 다양한 상황에 대한 범용성을 갖는다.

 

regnet 의 구조 및 작동 과정

  1. Stem Layer (시작 레이어): 입력 데이터를 받아들이고 초기 특징을 추출하는 역할을 합니다.
  2. Stage (스테이지): 모델의 주요 블록으로, 각 스테이지는 여러 레이어로 구성되어 있습니다. 깊이, 너비, 해상도 등이 각 스테이지에서 조절됩니다.
  3. Bottleneck Blocks (병목 블록): ResNet과 유사한 병목 아키텍처를 사용하여, 모델의 효율성을 높이고 계산 비용을 줄입니다.
  4. Width Scaling (너비 확장): 각 스테이지에서 블록의 채널 수를 조절하여 네트워크의 너비를 조절합니다.
  5. Depth Scaling (깊이 확장): 스테이지의 수를 조절하여 전체 네트워크의 깊이를 조절합니다.
  6. Resolution Scaling (해상도 확장): 스테이지의 스트라이드를 조절하여 입력 이미지의 해상도를 조절합니다.

BiFPN의 탄생목적

  이미지 상에는 서로 다른 크기의 객체가 존재하기 때문에 다양한 scale의 feature를 참조하는 것이 효과적이다. 이를 위해 기존에 사용되던 FPN(Feature Pyramid Network)는 scale에 따라 feature를 피라미드 형태로 쌓은 후 낮은 scale의 feature를 높은 scale의 feature에 더하는 방식으로 feature fusion을 진행한다. 이 방식은  각 feature 별로 BiFPN의 output에 미치는 영향력의 크기가 다름에도 단순히 단방향으로 더하는 문제점이 있으며 이를 개선하기 위해 각 feature별로 가중치를 다르게 적용하고, 양방향으로 더하는 BiFPN층이 만들어졌다.

FPN의 구조

 

BiFPN의 구조 및 작동 과정

BiFPN구조

  1. Bottom-Up Pathway:
    • 이미지에서 추출된 특성 맵은 다양한 스케일에서 생성됩니다.
  2. Top-Down Pathway:
    • Bottom-Up Pathway에서 생성된 특성 맵은 상위 수준의 스케일에서 업샘플링됩니다.
  3. Bi-directional & lateral connection 을 통한 Feature Fusion :
    • 각 레이어에서, 해당 레이어의 특성 맵과 다운샘플링된 특성 맵, 업샘플링된 샘플링을 가중치에 따라 결합한 후(Bi-directional connection), 해당 레이어 특성 맵의 edge를 가중치에 따라 추가한다(lateral connection).

 

2.regnet 및 BiFPN 코드를 이용한 이미지 분류 모델 생성

 

regnet 코드

 
'''RegNet in PyTorch.

Paper: "Designing Network Design Spaces".

'''
import torch
import torch.nn as nn
import torch.nn.functional as F


class SE(nn.Module) #입력 특징맵의 특정 채널에 대한 중요성을 강조하는 역할
    '''Squeeze-and-Excitation block.'''

    def __init__(self, in_planes, se_planes):
        super(SE, self).__init__()
        self.se1 = nn.Conv2d(in_planes, se_planes, kernel_size=1, bias=True)
        self.se2 = nn.Conv2d(se_planes, in_planes, kernel_size=1, bias=True)

    def forward(self, x):
        out = F.adaptive_avg_pool2d(x, (1, 1))
        out = F.relu(self.se1(out))
        out = self.se2(out).sigmoid() #특정 채널의 중요성에 대한 정보  #형태 : 채널수= in_planes, 높이너비= 1x1
        out = x * out
        return out 


class Block(nn.Module):
    def __init__(self, w_in, w_out, stride, group_width, bottleneck_ratio, se_ratio):
        super(Block, self).__init__()
        # 1x1   # Bottleneck Blocks (병목 블록) :  모델의 효율성을 높이고 계산 비용을 줄입니다
        w_b = int(round(w_out * bottleneck_ratio))
        self.conv1 = nn.Conv2d(w_in, w_b, kernel_size=1, bias=False)
        self.bn1 = nn.BatchNorm2d(w_b)
        # 3x3 
        num_groups = w_b // group_width 
        self.conv2 = nn.Conv2d(w_b, w_b, kernel_size=3,
                               stride=stride, padding=1, groups=num_groups, bias=False)
        self.bn2 = nn.BatchNorm2d(w_b)
        # se
        self.with_se = se_ratio > 0  
        if self.with_se:
            w_se = int(round(w_in * se_ratio))
            self.se = SE(w_b, w_se)
        # 1x1
        self.conv3 = nn.Conv2d(w_b, w_out, kernel_size=1, bias=False)
        self.bn3 = nn.BatchNorm2d(w_out)

        self.shortcut = nn.Sequential()
        if stride != 1 or w_in != w_out: #
            self.shortcut = nn.Sequential(
                nn.Conv2d(w_in, w_out,
                          kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(w_out)
            )

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = F.relu(self.bn2(self.conv2(out)))
        if self.with_se:
            out = self.se(out)
        out = self.bn3(self.conv3(out))
        out += self.shortcut(x) # x에 shortcut을 적용하여 out 과 x의 채널 수 및 높이,너비를 같게 만든다.                
        out = F.relu(out)
        return out


class RegNet(nn.Module):
    def __init__(self, cfg, num_classes=10):
        super(RegNet, self).__init__()
        self.cfg = cfg
        self.in_planes = 64 
        self.conv1 = nn.Conv2d(3, 64, kernel_size=3,
                               stride=1, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.layer1 = self._make_layer(0)
        self.layer2 = self._make_layer(1)
        self.layer3 = self._make_layer(2)
        self.layer4 = self._make_layer(3)
        self.linear = nn.Linear(self.cfg['widths'][-1], num_classes)

    def _make_layer(self, idx):
        depth = self.cfg['depths'][idx]
        width = self.cfg['widths'][idx]
        stride = self.cfg['strides'][idx]
        group_width = self.cfg['group_width']
        bottleneck_ratio = self.cfg['bottleneck_ratio']
        se_ratio = self.cfg['se_ratio']

        layers = []
        for i in range(depth):
            s = stride if i == 0 else 1
            layers.append(Block(self.in_planes, width,
                                s, group_width, bottleneck_ratio, se_ratio))
            self.in_planes = width
        return nn.Sequential(*layers)

    def forward(self, x):
 
      # Stem Layer (시작 레이어): 입력 데이터를 받아들이고 초기 특징을 추출하는 역할을 합니다
        out = F.relu(self.bn1(self.conv1(x)))  
 
      # Stage (스테이지): 모델의 주요 블록으로, 각 스테이지는 여러 레이어로 구성되어 있습니다. 깊이, 너비, 해상도 등이         각 스테이지에서 조절됩니다
        out = self.layer1(out) #Stage의 layer1
        out = self.layer2(out) #Stage의 layer2
        p3 = out
        out = self.layer3(out) #Stage의 layer3
        p4 = out
        out = self.layer4(out) #Stage의 layer4
        p5 = out
              
        #out = F.adaptive_avg_pool2d(out, (1, 1))
        #out = out.view(out.size(0), -1)
        #out = self.linear(out)     <=  이미지 분류는 이후의 과정에서 실행할 것이므로 해당 코드는 주석처리 하였습니다.
       
        return p3,p4,p5    #반환되는 p3,p4,p5 특성 맵은 BiFPN에 입력됩니다.
        
        

#regnet의 다양한 종류와 파라메터 입니다.  
#cfg 안의 숫자에 따라 스테이지의 여러 레이어들의 깊이,너비,해상도가 조절됩니다.
def RegNetX_200MF():
    cfg = {
        'depths': [1, 1, 4, 7],
        'widths': [24, 56, 152, 368],
        'strides': [1, 1, 2, 2],
        'group_width': 8,
        'bottleneck_ratio': 1,
        'se_ratio': 0,
    }
    return RegNet(cfg)


def RegNetX_400MF():
    cfg = {
        'depths': [1, 2, 7, 12],
        'widths': [32, 64, 160, 384],
        'strides': [1, 1, 2, 2],
        'group_width': 16,
        'bottleneck_ratio': 1,
        'se_ratio': 0,
    }
    return RegNet(cfg)


def RegNetY_400MF():
    cfg = {
        'depths': [1, 2, 7, 12],
        'widths': [32, 64, 160, 384],
        'strides': [1, 1, 2, 2],
        'group_width': 16,
        'bottleneck_ratio': 1,
        'se_ratio': 0.25,
    }
    return RegNet(cfg)

 

BiFPN 코드

#이 코드는 GitHub 사용자 [tristandb][EfficientDet-PyTorch] 프로젝트에서 가져와서 약간의 변형 후 사용하였습니다. [https://github.com/tristandb/EfficientDet-PyTorch/blob/master/LICENSE]
 
import torch
import torch.nn as nn
import torch.nn.functional as F

from torch.autograd import Variable

class DepthwiseConvBlock(nn.Module): #Feature Fusion에 사용됩니다.
    """
    Depthwise seperable convolution.
   
   
    """
    def __init__(self, in_channels, out_channels, kernel_size=1, stride=1, padding=0, dilation=1, freeze_bn=False):
        super(DepthwiseConvBlock,self).__init__()
        self.depthwise = nn.Conv2d(in_channels, in_channels, kernel_size, stride,
                               padding, dilation, groups=in_channels, bias=False)
        self.pointwise = nn.Conv2d(in_channels, out_channels, kernel_size=1,
                                   stride=1, padding=0, dilation=1, groups=1, bias=False)
       
       
        self.bn = nn.BatchNorm2d(out_channels, momentum=0.9997, eps=4e-5)
        self.act = nn.ReLU()
       
    def forward(self, inputs):
        x = self.depthwise(inputs)
        x = self.pointwise(x)
        x = self.bn(x)
        return self.act(x)
   
class ConvBlock(nn.Module): # 입력 이미지를 다운스케일 합니다.
    """
    Convolution block with Batch Normalization and ReLU activation.
   
    """
    def __init__(self, in_channels, out_channels, kernel_size=1, stride=1, padding=0, dilation=1, freeze_bn=False):
        super(ConvBlock,self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride=stride, padding=padding)
        self.bn = nn.BatchNorm2d(out_channels, momentum=0.9997, eps=4e-5)
        self.act = nn.ReLU()

    def forward(self, inputs):
        x = self.conv(inputs)
        x = self.bn(x)
        return self.act(x)

class BiFPNBlock(nn.Module):
    """
    Bi-directional Feature Pyramid Network
    """
    def __init__(self, feature_size=64, epsilon=0.0001):
        super(BiFPNBlock, self).__init__()
        self.epsilon = epsilon   #가중치를 계산할 때, 분모에 더해져서 분모가 0이 되는 것을 방지합니다.
       
        self.p3_td = DepthwiseConvBlock(feature_size, feature_size)  
        self.p4_td = DepthwiseConvBlock(feature_size, feature_size)  
        self.p5_td = DepthwiseConvBlock(feature_size, feature_size)  
        self.p6_td = DepthwiseConvBlock(feature_size, feature_size)  
       
        self.p4_out = DepthwiseConvBlock(feature_size, feature_size)
        self.p5_out = DepthwiseConvBlock(feature_size, feature_size)
        self.p6_out = DepthwiseConvBlock(feature_size, feature_size)
        self.p7_out = DepthwiseConvBlock(feature_size, feature_size)
       
        # TODO: Init weights    #Feature Fusion 시 각 feature에 곱해집니다.
        self.w1 = nn.Parameter(torch.Tensor(2, 4))
        self.w1_relu = nn.ReLU()
        self.w2 = nn.Parameter(torch.Tensor(3, 4))
        self.w2_relu = nn.ReLU()
   
    def forward(self, inputs):
        p3_x, p4_x, p5_x, p6_x, p7_x = inputs
       
        # Calculate Top-Down Pathway
        w1 = self.w1_relu(self.w1)
        w1 /= torch.sum(w1, dim=0) + self.epsilon
        w2 = self.w2_relu(self.w2)
        w2 /= torch.sum(w2, dim=0) + self.epsilon
       
        p7_td = p7_x
        p6_td = self.p6_td(w1[0, 0] * p6_x + w1[1, 0] * F.interpolate(p7_td, scale_factor=2))
        p5_td = self.p5_td(w1[0, 1] * p5_x + w1[1, 1] * F.interpolate(p6_td, scale_factor=2))
        p4_td = self.p4_td(w1[0, 2] * p4_x + w1[1, 2] * F.interpolate(p5_td, scale_factor=2))
        p3_td = self.p3_td(w1[0, 3] * p3_x + w1[1, 3] * F.interpolate(p4_td, scale_factor=2))
       
        # Calculate Bottom-Up Pathway
        p3_out = p3_td
        p4_out = self.p4_out(w2[0, 0] * p4_x + w2[1, 0] * p4_td + w2[2, 0] * nn.Upsample(scale_factor=0.5)(p3_out))
        p5_out = self.p5_out(w2[0, 1] * p5_x + w2[1, 1] * p5_td + w2[2, 1] * nn.Upsample(scale_factor=0.5)(p4_out))
        p6_out = self.p6_out(w2[0, 2] * p6_x + w2[1, 2] * p6_td + w2[2, 2] * nn.Upsample(scale_factor=0.5)(p5_out))
        p7_out = self.p7_out(w2[0, 3] * p7_x + w2[1, 3] * p7_td + w2[2, 3] * nn.Upsample(scale_factor=0.5)(p6_out))

        return [p3_out, p4_out, p5_out, p6_out, p7_out]
   
class BiFPN(nn.Module):
    def __init__(self, size, feature_size=64, num_layers=2, epsilon=0.0001):
        super(BiFPN, self).__init__()
        self.p3 = nn.Conv2d(size[0], feature_size, kernel_size=1, stride=1, padding=0)
        self.p4 = nn.Conv2d(size[1], feature_size, kernel_size=1, stride=1, padding=0)
        self.p5 = nn.Conv2d(size[2], feature_size, kernel_size=1, stride=1, padding=0)
       
        # p6 is obtained via a 3x3 stride-2 conv on C5
        self.p6 = nn.Conv2d(size[2], feature_size, kernel_size=3, stride=2, padding=1)
       
        # p7 is computed by applying ReLU followed by a 3x3 stride-2 conv on p6
        self.p7 = ConvBlock(feature_size, feature_size, kernel_size=3, stride=2, padding=1)

        bifpns = []
        for _ in range(num_layers):  #BiFPNBlock을 num_layers만큼 쌓습니다.
            bifpns.append(BiFPNBlock(feature_size))
        self.bifpn = nn.Sequential(*bifpns)
   
    def forward(self, inputs):
        c3, c4, c5 = inputs        #이미지를 regnet 층에 넣었을 때 나오는 세 개의 feature map을 inputs으로 입력 예정입니다.
       
        # Calculate the input column of BiFPN
        p3_x = self.p3(c3)
        p4_x = self.p4(c4)
        p5_x = self.p5(c5)
        p6_x = self.p6(c5)
        p7_x = self.p7(p6_x)
               
        features = [p3_x, p4_x, p5_x, p6_x, p7_x]
        # output has 5 features
        output = self.bifpn(features)
        return output       #BiFPN층에서 나온 output은 이후 cnn층으로 입력됩니다.

 

 

regnet_bifpn 코드(regnet과 bifpn층을 합쳐서 이미지를 여러 스케일로 변환 후 Feature Fusion을 수행합니다.)

class regnet_bifpn(nn.Module)
  def __init__(self):
    super(regnet_bifpn, self).__init__()
    self.cfg = {
        'depths': [1, 2, 7, 12],
        'widths': [32, 64, 160, 384],
        'strides': [1, 1, 2, 2],
        'group_width': 16,
        'bottleneck_ratio': 1,
        'se_ratio': 0.25,
    #여러 regnet 중 RegNetY_400MF 모델을 사용합니다.
    self.regnet_object = RegNet(self.cfg)
    self.bifpn_object = BiFPN([64, 160, 384])

  def forward(self,x):
    p3,p4,p5 = self.regnet_object(x)
    outputs = self.bifpn_object([p3,p4,p5])
    for i in range(5):
      outputs[i] = F.interpolate(outputs[i], size=(32, 32), mode='nearest')
    stacked_tensor = torch.cat([outputs[0],outputs[1],outputs[2],outputs[3],
                              outputs[4]],dim=1) #이후 cnn층에 입력하기 위해 채널 차원을 기준으로 합친다. 
    return stacked_tensor
    #return outputs
regnet_bifpn_object = regnet_bifpn()

 

모델구성 및 학습률, 옵티마이저 설정

device = "cuda" if torch.cuda.is_available() else "cpu"
 
model = nn.Sequential(      
    regnet_bifpn_object,    
    nn.Conv2d(320,80, kernel_size=3, stride=1, padding=1),
    nn.AdaptiveAvgPool2d(output_size=(1, 1)),  # 공간적인 정보를 보존하기 위해 사용한다.
    nn.Flatten(),
    nn.ReLU(),
    nn.Linear(80,32),
     nn.ReLU(),
    nn.Linear(32,10),
 )

lr=1e-5   #학습률 설정
parameters = list(model.parameters())
optim = Adam(parameters,lr=lr# 옵티마이저 설정

 

3.모델 학습, 테스트 및 문제 발생 시 해결하기

사용 데이터 : cifar10

 

모델 학습 및 테스트

 

문제 1. inplace error 발생

 

모델 학습을 시작하니 
"
RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation: [torch.FloatTensor [3, 4]], which is output 0 of ReluBackward0, is at version 1; expected version 0 instead. Hint: enable anomaly detection to find the operation that failed to compute its gradient, with torch.autograd.set_detect_anomaly(True).
"

에러가 발생하였다.  이는 Autograd가 기울기를 계산하는 동안 어떤 변수가 inplace 연산으로 수정되었거나 다른 곳에서 수정되었음을 의미한다. 일반적으로 PyTorch의 Autograd는 inplace 연산을 추적할 수 없기 때문에 inplace 연산을 사용하는 경우에는 gradient 계산에 문제가 발생할 수 있다. 따라서 오류를 해결하려면 inplace연산이 일어나지 않게 코드를 수정하여야 한다. " [torch.FloatTensor [3, 4]]  " 정보를 통해

# TODO: Init weights
        self.w1 = nn.Parameter(torch.Tensor(2, 4))
        self.w1_relu = nn.ReLU()
        self.w2 = nn.Parameter(torch.Tensor(3, 4))
        self.w2_relu = nn.ReLU()
   
    def forward(self, inputs):
        p3_x, p4_x, p5_x, p6_x, p7_x = inputs
       
        # Calculate Top-Down Pathway
        w1 = self.w1_relu(self.w1)
        w1 /= torch.sum(w1, dim=0) + self.epsilon
        w2 = self.w2_relu(self.w2)
        w2 /= torch.sum(w2, dim=0) + self.epsilon

이 부분의 코드에 문제가 있다고 판단하였다.

 

문제 1에 대한 해결시도 1. clone() 사용하여 연산하기 전에 텐서의 독립적인 복사본을 생성한다.

# TODO: Init weights
        self.w1 = nn.Parameter(torch.Tensor(2, 4))
        self.w1_1 = self.w1.clone()
        self.w1_relu = nn.ReLU()
        self.w2 = nn.Parameter(torch.Tensor(3, 4))
        self.w2_1 = self.w2.clone()
        self.w2_relu = nn.ReLU()
   
    def forward(self, inputs):
        p3_x, p4_x, p5_x, p6_x, p7_x = inputs
       
        # Calculate Top-Down Pathway
        w1 = self.w1_relu(self.w1_1)
        w1 /= torch.sum(w1, dim=0) + self.epsilon
        w2 = self.w2_relu(self.w2_1)
        w2 /= torch.sum(w2, dim=0) + self.epsilon

 

결과. 여전히 inplace error 발생

 

문제 1에 대한 해결시도 2. detach() 사용하여 텐서의 gradients를 분리하여 gradient 계산을 수행하지 않도록 한다.

 

# TODO: Init weights
        self.w1 = nn.Parameter(torch.Tensor(2, 4))
        self.w1_1 = self.w1.detach()
        self.w1_relu = nn.ReLU()
        self.w2 = nn.Parameter(torch.Tensor(3, 4))
        self.w2_1 = self.w2.detach()
        self.w2_relu = nn.ReLU()
   
    def forward(self, inputs):
        p3_x, p4_x, p5_x, p6_x, p7_x = inputs
       
        # Calculate Top-Down Pathway
        w1 = self.w1_relu(self.w1_1)
        w1 /= torch.sum(w1, dim=0) + self.epsilon
        w2 = self.w2_relu(self.w2_1)
        w2 /= torch.sum(w2, dim=0) + self.epsilon

 

결과. inplace error 발생 문제는 해결되었다. 그러나 모델의 훈련 정확도가 0.2 이상으로 올라가지 않는 문제가 발생하였다.

 

문제 2. 모델의 훈련 정확도가 0.2 이하

 

원인추론. 파라미터인 self.w1 과 self.w2의 값이 훈련과정에서 변하지 않음이 발견되었고, 이로 인한 정확도 문제가 발생했을 것이다.

 

파라미터의 값이 훈련을 진행하였음에도 변하지 않았다.

문제 1에 대한 해결시도 2는 inplace error는 해결하였으나 파라미터의 값을 변하지 않게 만드는 문제가 있다. 따라서, inplace error도 해결하는 동시에 파라미터의 값을 변하도록 놔두는 해결 방법이 필요하다.

 

문제 2에 대한 해결시도 1. 연산자 축약 방식을 사용하지 않는다.

 

stackoverflow에서 찾은 해결방법

stackoverflow에서 나와 같은 문제에 대한 질문을 여러 개 찾아보았고 그 중 연산자 축약을 사용한 코드를 수정하여 문제를 해결하는 방법을 발견하였다.  이 방법을 적용해보았다.

 

# TODO: Init weights
        self.w1 = nn.Parameter(torch.Tensor(2, 4))
        self.w1_relu = nn.ReLU()
        self.w2 = nn.Parameter(torch.Tensor(3, 4))
        self.w2_relu = nn.ReLU()
   
    def forward(self, inputs):
        p3_x, p4_x, p5_x, p6_x, p7_x = inputs
       
        # Calculate Top-Down Pathway
        w1 = self.w1_relu(self.w1)
        #w1 /= torch.sum(w1, dim=0) + self.epsilon    <== 연산자 축약 방식의 코드
        w1 = w1 / ( torch.sum(w1, dim=0) + self.epsilon )
        w2 = self.w2_relu(self.w2)
        # w2 /= torch.sum(w2, dim=0) + self.epsilon   <== 연산자 축약 방식의 코드
        w2 = w2 / ( torch.sum(w2, dim=0) + self.epsilon )

 

결과. inplace error 문제와 self.w1, self.w2가 변하지 않던 문제 둘 다 해결되었다. 그러나 여전히 모델의 훈련 정확도가 0.2 이상으로 올라가지 않았다.

epoch가 진행됨에 따라 self.w1, self.w2의 값이 변화하였다.

 

문제 2에 대한 해결시도 2. model에서 nn.AdaptiveAvgPool2d(output_size=(1,1)) 층을 제거한다.

nn.AdaptiveAvgPool2d(output_size=(1,1)) 층은 특성 맵의 공간적인 구조를 유지해주는 장점이 있으나,  정보 손실을 발생시키는 단점이 존재한다. 현 상황에서는 장점보다는 단점이 크다고 판단하여 제거하였다.

 

device = "cuda" if torch.cuda.is_available() else "cpu" 
model = nn.Sequential(       
    regnet_bifpn_object,
    nn.Conv2d(320,80, kernel_size=3, stride=1, padding=1), 
    nn.Conv2d(80,10, kernel_size=3, stride=1, padding=1), 
    nn.Flatten(),
    nn.ReLU(),
    nn.Linear(10240,200),
    nn.ReLU(),
    nn.Linear(200,10),
   
)

 

결과. 훈련 정확도가 0.95까지 증가하였다. 문제2 해결.  그러나 모델의 테스트 정확도는 0.6262를 기록하며 과적합 문제가 발생하였다. 

epoch : 1 accuracy:0.36196 loss:1.323064923286438

epoch : 5 accuracy:0.53162 loss:1.1802284717559814

epoch : 10 accuracy:0.61332 loss:1.1879396438598633

epoch : 15 accuracy:0.67308 loss:0.5629745125770569

epoch : 20 accuracy:0.72436 loss:0.5725672245025635

epoch : 25 accuracy:0.77654 loss:1.2071887254714966

epoch : 30 accuracy:0.82432 loss:0.44686126708984375

epoch : 35 accuracy:0.86744 loss:0.2067648470401764

epoch : 40 accuracy:0.90376 loss:0.13235348463058472

epoch : 45 accuracy:0.93116 loss:0.08301331847906113

epoch : 50 accuracy:0.95126 loss:0.07578739523887634

 

문제 3. 모델의 과적합

 

문제 3에 대한 해결시도 1. 모델에 Dropout층 추가하기

model = nn.Sequential(       
    regnet_bifpn_object,
    nn.Conv2d(320,80, kernel_size=3, stride=1, padding=1),
    nn.Conv2d(80,10, kernel_size=3, stride=1, padding=1),
    nn.Flatten(),
    nn.ReLU(),
    nn.Dropout(0.2),                        
    nn.Linear(10240,200),                 
    nn.ReLU(),
    nn.Dropout(0.2),                        
    nn.Linear(200,10),
  

)

 

결과. 테스트 정확도 0.6 ,  해결되지 않음

 

문제 3에 대한 해결시도 2. Dropout비율 높이고, 출력층 직전 은닉층의 노드 수를 줄이기, L2 regularization과 BatchNormalization  추가하기

model = nn.Sequential(       
    regnet_bifpn_object,
    nn.Conv2d(320,80, kernel_size=3, stride=1, padding=1), 
    nn.BatchNorm2d(80),
    nn.Conv2d(80,10, kernel_size=3, stride=1, padding=1),
    nn.Flatten(),
    nn.ReLU(),
    nn.Dropout(0.7),
    nn.Linear(10240,200),
    nn.BatchNorm1d(200),

    nn.ReLU(),
    nn.Dropout(0.5),
    nn.Linear(200,32),
    nn.BatchNorm1d(32),
    nn.ReLU(),
    nn.Dropout(0.5),
    nn.Linear(32,10),
    

)

 

결과. 테스트 정확도 Accuracy:0.5499 ,  과적합 문제 해결되지 않음

4.결과 및 아쉬운 점

  훈련 정확도는 accuracy:0.81508 를 기록하였으나 테스트 정확도는 Accuracy:0.5499 를 기록하였다. 과적합 문제를 해결하기 위해 Dropout층 추가, 은닉층의 노드 수 조절, L2 regularization, BatchNormalization 을 시도하였으나 해결되지 않았다. 더 다양한 조합을 시도하고 싶지만 학습에 걸리는 시간이 길어서 다양하게 시도할 수 없다는 것이 아쉽다. 
비록 과적합 문제는 해결하지 못했지만 inplace error, 파라메터가 학습되지 않아서 훈련 정확도가 올라가지 않는 문제를 

연산자 축약 방식 사용하지 않음, nn.AdaptiveAvgPool2층 제거를 통해 해결하는 방법을 알아냈다. 조금 더 여유로운 시기가 되면 Dropout층 개수와 비율, 은닉층의 노드 수, L2 regularization의 비율을 조절하여 과적합 문제를 해결하려고 한다.