Naver Ai Boostcamp

[03/31] Model

잡담연구소 2021. 3. 31. 14:35
목표 

 

  1. 거만하게 공부하지말기 : 교수님이 시키는 대로 하기 
  2. 피어세션 이끌고, 새로운 정보 얻어오기
  3. sampler 사용해보기  
  4. augmentation 조합 해보기
  • 내일은 augmentation 조합을 좀 도전 해보자!
    • RandomResizedCrop
    • One of [ gaussian noise , MotionBlur ,  jpegcompression]
    • horizontal flip
    • rotate
  • Fc layer를 한 단 더 추가해보기
  • Model 바꿔보기 B3 , B4 등

 

강의 정리 

 

nn.Module 을 상속받은 모든 클래스의 공통된 특징 

- 모든 nn.Module은 childe modules를 가질 수 있다.

- 모든 nn.Module은 forward()를 가진다.

 

nn.Modules는 일종의 저장소 역할을 한다!

parameter에 tensor값이 저장되어있는데 어떻게 확인할까? 

 

- Model.state_dict() : 딕셔너리 형태로 key값은 파라미터 이름 , value 값은 파라미터 값을 출력해준다.  

특정 파라미터의 tensor값을 보고 싶다면 dic 형태니까 불러오면 된다. 

 

- named_parameters : state_dict와 비슷하게  generator로 하나씩 출력할 수 있다. 

이렇게 특정 파라미터의 requires_grad(boolean)으로 볼 수있고, 어떤 grad를 가지고 있는지도 볼 수 있다. 

시간이 부족할 수도 있다. 누군가 잘 만들어놓은 pretrained model을 사용해보자. => torchvision , timm 

 

Pretraining할 때 설정했던 문제와 현재 문제와의 유사성을 고려! 

1.문제를 해결하기 위한 학습데이터가 충분하다. 

- 유사성이 높다면 -> Feature Extract Freeze 하기

 

💡 Freeze 하는 법

후후 ,,, 이 날을 위해 예전에 공부했었다. 

requires_grad = False 설정하면 됨 ㅎㅎ 

# Model_FREEZE 
def model_freeze(model):
    for name, param in model.named_parameters():
        if name not in ['fc1.weight', 'fc1.bias']:
            param.requires_grad = False
    return model

- 유사성이 낮다면 -> finetunning 하기 

 

 

2. 학습 데이터가 충분하지 않은 경우

- 유사성이 높으면 -> 우리가 가진 데이터로 backbone을 학습시키는 것은 무리 -> freeze해서 사용

- 유사성이 낮으면 -> 추천도 안함;

 

우리의 경우는 어떨까? 

내 생각에는 문제를 해결하기 위한 데이터가 충분...잘 모르겠다 18900장 정도면 적당한가,,,?

유사성은 낮은 거 같다 . 그러므로 freeze 하기 보다는 fine-tunning 이 좋을 거 같다. 

 

 

 

목표를 위한 행동

 

1. 마스터님이 하라는 대로 좀 해보기 

last Mission

어제는 마스터님이 시키신 augmentation을 여러가지 적용해보고 실험해봤다. 그

결과랑 마스터님이 하신 augmentation이랑 비교를 좀 해보자.

 

❓ Albumentation 이랑 Transforms랑 같이 할 수 있는 방법은 없을까 Fivecrop하고 싶은데 없다. ㅜㅜ

- 헐 validation에는 최소한의 전처리만 해줘야한다..... 처음 알았네

- denormalize 시도해봤는데 안돼서 visualize할 때는 그냥 normalize 적용 안한 데이터를 했는데,,,, 

아,,, 그냥 표준편차 곱하고 평균을 더해서 다시 normalize 해주면 된다.,.. 그냥 다시 normalize,,,

 

 

Mission 1.

위 테이블의 예시를 보고 해당하는 스펙에 맞게 모델 직접 구현해보세요. 그리고 현재 우리 문제 정의에 맞게 Classifier도 설계해보세요.

Neutron을 보고 따라 구현했다. Neutron으로 보니까 filter, kernel ,stride가 한 번에 보이니까 편하다. 

최대한 남의 코드를 참고하지 않고, 내 힘으로 짜려고 노력했다.

forward문에 for문이 있으니까 꼴뵈기가 싫어서,, 사람들이 주로 쓰는 make_layer로 대체해봤다. 

음,, DarkNet이라고 Resnet보다 빠르다는데 왜 더 느리지 난??;;

class ResidualBlock(nn.Module):
    def __init__(self,in_channels):
        super(ResidualBlock ,self).__init__()
        self.in_channels = in_channels
        self.out_channels = in_channels // 2
        self.conv1x1 = nn.Conv2d(in_channels = self.in_channels,
                              out_channels = self.out_channels,
                               kernel_size = 1,
                               stride = 1,
                               bias = True
                              ) # 64d -> 1x1 32d

        self.conv3x3 = nn.Conv2d(in_channels = self.out_channels,
                              out_channels = self.in_channels,
                               kernel_size = 3,
                               stride = 1,
                               padding = 1,
                               bias = True
                              ) # 32d -> 1x1, 64d
        self.bn1x1 = nn.BatchNorm2d(num_features = self.out_channels)
        self.bn3x3 = nn.BatchNorm2d(num_features =self.in_channels)
        self.relu = nn.LeakyReLU(True)
        
    def forward(self,x):
        base = x 
        x = self.conv1x1(x)
        x = self.bn1x1(x)
        x = self.relu(x)
        x = self.conv3x3(x)
        x = self.bn3x3(x)
        x = self.relu(x)
        return x + base 
        
        
class ConvS2(nn.Module):
    def __init__(self, in_channels , out_channels):
        super(ConvS2 , self).__init__()
        self.conv= nn.Conv2d(in_channels = in_channels,
                                out_channels = out_channels,
                                kernel_size = 3,
                                stride = 2,
                                padding = 1,
                                bias = True)
        self.bn = nn.BatchNorm2d(num_features = out_channels)
        self.relu = nn.LeakyReLU(True)
        
    def forward(self,x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        return x

        
class DarkModel(nn.Module):
    def __init__(self, num_class , ResBlock , ConvBlock):
        super(DarkModel,self).__init__()
        self.num_class = num_class

        self.conv0 = nn.Conv2d(in_channels = 3 , 
                                out_channels = 32,
                                kernel_size = 3,
                                stride = 1,
                                padding = 1,
                                bias = True)
        self.bn = nn.BatchNorm2d(num_features = 32)
        self.conv1 = ConvBlock(in_channels = 32 , out_channels =64)
        self.ResBlock1 = self.make_layer(ResBlock(in_channels = 64), 1)
        self.conv2 = ConvBlock(in_channels = 64 , out_channels = 128)  
        self.ResBlock2 = self.make_layer(ResBlock(in_channels = 128), 2)
        self.conv3 = ConvBlock(in_channels = 128 , out_channels = 256)
        self.ResBlock3 = self.make_layer(ResBlock(in_channels = 256), 8)
        self.conv4 = ConvBlock(in_channels = 256 , out_channels  =512)
        self.ResBlock4 =self.make_layer(ResBlock(in_channels = 512), 8)
        self.conv5 = ConvBlock(in_channels = 512 , out_channels = 1024)
        self.ResBlock5 = self.make_layer(ResBlock(in_channels = 1024), 4)
        self.relu = nn.LeakyReLU(True)
        self.GAP = nn.AdaptiveAvgPool2d((1,1))
        self.fc = nn.Linear(1024, self.num_class)



    def forward(self,x):
        x = self.conv0(x)
        x = self.bn(x)
        x = self.relu(x)
        x = self.conv1(x)
        x = self.ResBlock1(x)
        x = self.conv2(x)
        x = self.ResBlock2(x)
        x = self.conv3(x)
        x = self.ResBlock3(x)
        x = self.conv4(x)
        x = self.ResBlock4(x)
        x = self.conv5(x)
        x = self.ResBlock5(x)
        x = self.GAP(x)
        x = x.squeeze()
        x = self.fc(x)
        return x         

    
    
    def make_layer(self,block,repeat):
        layers = []
        for i in range(repeat):
            layers.append(block)
        return nn.Sequential(*layers)
    

def DarkNet53(num_class):
    return DarkModel(num_class , ResidualBlock , ConvS2) 

 

Mission 2.

임의의 원하는 Pretrained Model을 하나 골라서 ImageNet Pretrained Weight가 업데이트(다운로드)된 상태에서

  1. Fine-tuning 시도

  2. Feature Extraction 시도 (Backbone Freeze)

해보고 둘 차이가 어떤지 한번 비교해보세요. 과연 내 문제와 ImageNet의 문제가 주제가 비슷한 문제일까요?

 

내 가정 : 주제가 다른 문제다! Imagenet은 세상 물체를 분류하는 거지만 , 우리의 문제는 사람의 얼굴 내에서 나이 & 성별 & 마스크 여부 모두를 고려해야하므로 다르다고 생각했다. 

 

둘 다 Normalize만 했을 때의 결과다 .

Fine-tunning 했을 때, 결과 : 73.84% 0.67% 

Feature Extract 했을 때 :  67.13% 0.58%

역시 내 예상대로 Fine-tunning이 더 결과가 좋았고, 우리 문제는 ImageNet과 다른 주제의 문제다. 

 

 

2. 피어세션 

피어세션을 하면 카메라 끄고 마이크 끄고 입꾹닫 너무 많은 거 같다.

남이 이끌어주길 바라는 건지, 아니면 참여 하지 않고 모델링 하고 싶은데 그러자고 말할 용기가 없는 건지 뭐가 되든 ;; 

그래도 나는 3일 동안 그런 경우는 없던 거 같은데,,,조원들 얘기 들어보면 심각한 거 같다. 

 

오늘 피어세션 수확이 생각보다 좋다. 

- wandb.ai/site : 이름이 기억이 안나지만 천정명 닮으신 캠퍼분이 하이퍼파라미터 찾아주는 곳이라고 소개해주심 

- 캠퍼분들 대부분이 centercrop만 하신 듯하다. 

- Densenet, EfficientNet 쓴 이유를 얘기하고 있었는데, 어떤 캠퍼분(박,,,뭐드라)이 어차피 앙상블 하면 다 쓰게 된다고 지금은 그러거보단 구성요소들에 신경쓰라 하심 오! 일리있다! 

- val을 unseendata로 해야된다. val이 균등분포여야할까? train의 분포를 따라야할까? 

난 val이 균등분포여야 한다고 생각하는데,,,, 오히려 균등분포로 하면 imbalance가 더 심해질 거 같다고 train의 분포를 따라야한다고 말씀하셨다. 

 

더 있던 거 같은데 기억이 잘,,,,, 내일부턴 필기하면서 얘기해야겠다😂😂

 

 

 

3. Sampler 이용해보기 

DynamicBalanceSampler사용 : 🌞천사 보현님🌞이 알려주셨다. 

: 76.65% , 0.71%

# DynamicBalanceSampler
train_x , val_x , train_y , val_y = train_test_split(df , df['label'] , test_size=0.3, shuffle = True )

train_dataset = CustomDataset(img_path , train_x , train_transforms)
val_dataset = CustomDataset(img_path , val_x , val_transforms)

sampler = data_catal.DynamicBalanceSampler(labels=train_dataset.get_labels())

train_loader = torch.utils.data.DataLoader(train_dataset,sampler=sampler, batch_size= batch_size ,  num_workers = num_workers)
val_loader = torch.utils.data.DataLoader(val_dataset, batch_size = batch_size , shuffle = False , num_workers = num_workers )

 

4. FC layer 추가해보기 

FC (1280 , 128 ) + RELU + FC(128,18)을 적용했는데 오히려 더 안좋아졌다.

결과 : 70.79% 0.63%

 

 

5. Model 바꿔보기 

NFnet을 써보려했는데 자꾸 OOM이 나서 포기 -> 알고보니 resize를 안했더라;;;;;

EfficientNetB2를 썼는데 같은 조건에서 B0보다 성능이,,,,,안나왔다. 

결과 73.92% 0.69% 

 

 

BOUNS 

피어세션에서 의견을 나누다보니 나는 randomcrop으로 효과봤고, 조원들은 centercrop으로 효과를 봤다.

center를 중심으로 randomcrop은 어떨까 싶어 transform을 직접 짜봤다. 

AlbumentationGitHubDaconCustomAlbumentation 참고 했다.

from typing import List, Union, Tuple, Sequence, Optional
        
def get_center_crop_coords(img , crop_height: int, crop_width: int, ratio : int):
    transformed_img = img.copy()
    height = img.shape[1]
    width = img.shape[0]
    delta = random.uniform(-ratio,ratio)							# 비율 조절 
    y1 = int(np.round((1 + delta) * (height - crop_height) // 2))	# 인덱싱을 하려면 정수여야함
    y2 = y1 + crop_height
    x1 = int(np.round((1 + delta) * (height - crop_width) // 2))
    x2 = x1 + crop_width
    return transformed_img[y1:y2 , x1:x2]

class CustomRandomCrop(ImageOnlyTransform):
    def __init__(self, height, width, ratio, always_apply=False, p=1.0):
        super().__init__(always_apply, p)
        self.crop_height = height
        self.crop_width = width
        self.ratio = ratio
        
    def apply(self, img, **params):									# Albumentation
        return get_center_crop_coords(img,self.crop_height, self.crop_width , self.ratio )

이렇게 적용하면 된다. 

train_transforms = A.Compose([
    CustomRandomCrop(224,224,0.5),
    A.Normalize([0.485, 0.456, 0.406],[0.229, 0.224, 0.225]),
    A.pytorch.transforms.ToTensor()
])

속도는 10초 정도 더 빨라진 거 같다. 

 

 

오늘의 달성 및 깨달음 

리더보드에 연연하지 않고, 이것 저것 해보려고 노력하니까 스트레스 받지도 않고 되게 재밌다.

근데 이것도 어느 정도 성적이 나왔으니까 그런 거 같다,,,ㅋㅋㅋㅋ

f1기준 5등 정도 되는 거 같다.

직접 Model도 짜보고, transfom도 짜보고! 

내가 성장하는데 큰 거름이 되지 않을까 싶다. 

 

교수님이 시키시는 대로 차근차근 하나하나씩 해보고 있다. 

 

Overview 너무 좋은 거 같다. Overview가 있으니 내일의 목표, 일주일 목표를 짜기가 너무 쉽다. 

  • 내일도 augmentation 조합을 새롭게 좀 도전해보자
    • RandomResizedCrop
    • One of [ gaussian noise , MotionBlur ,  jpegcompression]
    • horizontal flip
    • rotate
  • 그냥 DataLoader에서 제공하는 weight sampler 사용해보자
  • Focal loss 사용해보기 + imbalance에 좋은 loss 찾아보기 
  • optimizer : SGD VS ADAM 실험해보기 
  • lr scheduler 적용해보기 
    • 내 최애 AdamW + CosineAnnelaingwithwarstart
    • ReduceLROnPlateau

'Naver Ai Boostcamp' 카테고리의 다른 글

인공지능과 자연어처리 (1)  (0) 2021.04.13
[04/02] More...  (0) 2021.04.03
[03/30] Data Feeding  (0) 2021.03.31
[03/29] P Stage Start !  (0) 2021.03.30
[Day 38] 빠르게 & 가지치기  (0) 2021.03.28