또르르's 개발 Story

[Stage 1 - 07] Ensemble 하기 본문

[P Stage 1] Image Classification/프로젝트

[Stage 1 - 07] Ensemble 하기

또르르21 2021. 4. 8. 03:58

 1️⃣ Goal

 

  • [BaseLine 작성] (추가 : 3/29, 기간 : 3/29 ~ 3/29)

  • [Data Processing]

    - Face Recognition (추가 : 3/29, 기간 : 3/30 ~ 3/31)


    - Cross-validation 사용 (추가 : 3/29)

    -데이터 불균형 해소 / imbalanced Sampler, Focal Loss, OverSampling, Weighted loss (추가 : 3/30, 기간 : 4/1 ~ )

    - Data Augumentation (Affine, Gaussian 등) (추가 : 3/30, 추가 : 4/5 ~ 4/8)

    -
    Generator의 초당 Batch 처리량 측정 및 향상 (추가 : 3/30, 기간 : 3/31 ~ 3/31)

    - Cutmix 시도 (추가 : 4/1)

    - Repeated Agumentation (추가 : 4/1)

    - 
    validation data 사용 (추가 : 4/1, 기간 : 4/1 ~ 4/1)

  • [Model]

    - ResNet 152층 시도 (추가 : 3/29)

    - Efficient Net 시도 (추가 : 3/29, 기간 : 4/1 ~ 4/2)

    - YOLO 시도 (추가 : 3/31)

    - Pre-trained 모델에 Fine-tuning 하기 (추가 : 3/29, 기간 : 4/1 ~ 4/1)

    - Model의 초당 Batch 처리량 측정 및 향상 (추가 : 3/30)

    - dm_nfnet 시도 (추가 : 4/1, 기간 : 4/7 ~ )

  • [Training]

    - 앙상블 시도 (추가 : 3/29, 기간 : 4/7 ~ )

    - Hyperparameter 변경 (추가 : 3/29, 기간 : 3/29 ~)

    - Learning Schedular 사용 (추가 : 3/29, 기간 : 4/1 ~ 4/7 )

    - Model의 초당 Batch 처리량 측정 (추가 : 3/30)

    - 좋은 위치에서 Checkpoint 만들기 (Adam으로 모든 minimum 찾고, SGD로 극소점 찾기) (추가 : 4/1, 새로운 baseline code)

    - Sex 분류 (1번 모델)  -> Age 분류 (2번 모델) -> Mask 분류 (3번 모델) // 모델 나누기 (추가 : 4/1, 기간 : 4/5 ~ )

    - Crop image (mask 분류)와 일반 image(age, sex 등 분류) 둘 다 사용 (추가 : 4/1, 기간 : 4/5 ~4/5 )

    - batch size 작게 쓰면서, SGD 사용 (추가 : 4/2)

    - NNI (Auto ML) 사용 (추가 : 4/2, 기간 : 4/2 ~ 4/4)

  • [Deploy]

    - Python 모듈화 (추가 : 3/30, 새로운 baseline code)

 

 

 

2️⃣ Learning

 

  • Adam보다 SGD (momentum/Nesterov momentum)이 좋을 떄도 있음.

    Adam의 경우 training accuracy 측면에서 다른 optimzier보다 빠르게 도달합니다.

    하지만 SGD는 수렴속도가 느리긴 하지만 Generalization 측면에서 더 우월하고, validation accuracy가 더 높게 나옵니다.

    따라서 처음엔 Adam을 사용하다, 이후 일정 조건 충족 시 SGD로 optimizer를 중간에 변경하는 방법도 있습니다. 

https://shaoanlu.wordpress.com/2017/05/29/sgd-all-which-one-is-the-best-optimizer-dogs-vs-cats-toy-experiment/

shaoanlu.wordpress.com/2017/05/29/sgd-all-which-one-is-the-best-optimizer-dogs-vs-cats-toy-experiment/

 

3️⃣ Main Task

1) dm_nfnet 시도 

Model의 다양성을 주기 위해서 timm에 pre-trained 되어있는 dm_nfnet_0을 사용했습니다.

# model.py

class nfnet_f0(nn.Module):

    def __init__(self, num_classes, pretrained=True):
    
        super().__init__()
        
        if pretrained == True:
        
            self.model = timm.create_model('dm_nfnet_f0', pretrained=True)
            
        else:
        
            self.model = timm.create_model('dm_nfnet_f0', pretrained=False)


        self.model.head.fc = nn.Linear(in_features=3072, out_features=num_classes, bias=True)


    def forward(self, x):
    
        return self.model(x)

 

nf_net의 params가 많아서 가장 적은 F0으로 선택했습니다.

실제로 F2까지 선택할 수는 있지만, 수렴이 너무 너무 느렸습니다.

https://github.com/deepmind/deepmind-research/tree/master/nfnets

 

문제는 f0도 성능이 거의 나오지 않아서 (빨간 박스) nni의 대기열에서 취소하게 되었습니다.

 

 

2) 다양한 pre-trained 모델 시도

  • Efficientnet_b6

    43M parameter로 확실히 적은 parameter입니다.

    train도 확실히 빨라서 pretrained 사용할 때 기준 1 epoch에 78% Accuracy를 보여주었습니다.

class Efficientnet_b6(nn.Module):

    def __init__(self, num_classes, pretrained=True):
    
        super().__init__()
        
        if pretrained == True:
        
            self.model = EfficientNet.from_pretrained('efficientnet-b6', num_classes=num_classes)
        
        else:
        
            self.model = EfficientNet.from_name('efficientnet-b6', num_classes=num_classes)
            

    def forward(self, x):
    
        return self.model(x)

 

  • Efficientnet_b4

    b6보다 가벼운 모델
class Efficientnet_b4(nn.Module):

    def __init__(self, num_classes, pretrained=True):
    
        super().__init__()
        
        if pretrained == True:
        
            self.model = EfficientNet.from_pretrained('efficientnet-b4', num_classes=num_classes)
            
        else:
        
            self.model = EfficientNet.from_name('efficientnet-b4', num_classes=num_classes)
            

    def forward(self, x):
    
        return self.model(x)

 

  • ECAresnet50t

    82.35 top-1 @ 320x320, 81.52 @ 256x256 (
    Feb 8, 2021)

    사용해보니 확실히 빠르게 Accuracy에 도달했고, 모델도 가벼웠습니다.

    ECA Attention과 함께 여러 ResNet 가중치를 추가한 모델입니다.

    ref) github.com/rwightman/pytorch-image-models

 

class ecaresnet50t(nn.Module):

    def __init__(self, num_classes, pretrained=True):
    
        super().__init__()
        
        if pretrained == True:
        
            self.model = timm.create_model('ecaresnet50t', pretrained=True)
            
        else:
        
            self.model = timm.create_model('ecaresnet50t', pretrained=False)
            

        self.model.fc = nn.Linear(in_features=2048, out_features=num_classes, bias=True)
        

    def forward(self, x):
    
        return self.model(x)

 

  • SE - Resnet152d

    256x256 val, 0.94 crop top-1 - 83.75, 320x320 val, 1.0 crop - 84.36 (Jan 3, 2021)

    Squeeze-and-Excitation Networks

    ref) arxiv.org/abs/1709.01507
class seresnet152d(nn.Module):

    def __init__(self, num_classes, pretrained=True):
    
        super().__init__()
        
        if pretrained == True:
        
            self.model = timm.create_model('seresnet152d', pretrained=True)
            
        else:
        
            self.model = timm.create_model('seresnet152d', pretrained=False)
            

        self.model.fc = nn.Linear(in_features=2048, out_features=num_classes, bias=True)
        

    def forward(self, x):
    
        return self.model(x)

 

 

3) 모델 나누기 ( Mask -> Gender -> Age )

nni에서 한 번에 돌리기 위해 Dataset의 num_classes와 model의 num_classes 부분들을 수정해서 search_space.json에 변수로 choice하게 만들었습니다.

// search_space.json

{
    "batch_size": {"_type":"choice", "_value": [8, 16, 32]},
    "model": {"_type" : "choice", "_value" : ["Efficientnet_b6", "Efficientnet_b4", "ecaresnet50t", "seresnet152d"]},
    "criterion": {"_type": "choice", "_value" : ["focal", "label_smoothing", "cross_entropy", "f1"]},
    "classification": {"_type":  "choice", "_value": ["mask", "gender", "age", "multi"]}
}

 

train.py에는 parser를 추가했습니다.

# train.py

parser.add_argument('--classification', type=str, default='multi', help='classification type (default: multi)')

 

nni를 통해 mask, gender, age의 classification에 대한 여러 hyperparameter를 실험할 수 있습니다.

 

 

 

4) 앙상블

앙상블은 Soft predictions votes를 사용했습니다.

 

따라서 NNI를 통해 "train_accuracy가 0.9 이상이면서 loss가 줄어든" epoch에서 soft prediction csv를 출력했습니다.

 

 

아래 사진은 csv로 출력된 soft predictions 입니다.

차례대로 0, 1, 2번 label이며, 이 중 ans는 argmax로 구합니다. 

 

 

따라서 mask, age, gender에 관한 각각의 csv들을 모아서 평균을 내줍니다.

 

아래는 age ensemble에 대한 예시입니다.

 

ensemble하고 싶은 soft predictions csv path를 list로 가지고 옵니다.

_age_list = [
       '/content/csv_file/age_ecaresnet50t_140_focal_0.01/exp/submission29_0.9947_0.0061_soft.csv',
       '/content/csv_file/age_seresnet152d_64_cross_entropy_0.01/exp/submission34_0.9955_0.0188_soft.csv',

]

age = []

 

 age에 pd.read_csv로 넣어줍니다.

for idx, path in enumerate(_age_list):

    age.append(pd.read_csv(path))

 

_age_list의 각각의 column '0', '1', '2' (label)들을 각 행에 맞춰서 더해줍니다.

_0 = 0

_1 = 0

_2 = 0


for idx in range(len(_age_list)):

    _0 += age[idx]['0']
    
    _1 += age[idx]['1']
    
    _2 += age[idx]['2']

 

ensemble_age라는 새로운 dataframe을 만들고, 각 label (column)의 평균을 넣어줍니다.

ensemble_age = pd.DataFrame({'ImageID':age[0]['ImageID'],

                             '0':_0/len(_age_list),
                             
                             '1':_1/len(_age_list),
                             
                             '2':_2/len(_age_list),

})

 

ensemble _age의 각 label들의 argmax (pandas에서는 idxmax)를 구해서 ensemble_age의 'ans' 열에 넣어줍니다.

ensemble_age['ans'] = ensemble_age[['0', '1', '2']].idxmax(axis=1)

>>> ensemble_age

 

나머지 mask와 gender도 같은 방식으로 출력합니다.

 

이후, submission 행에 넣어서 'Image ID'와 'ans'를 출력합니다.

submission['ans'] = pd.to_numeric(ensemble_mask['ans']) *6 + pd.to_numeric(ensemble_gender['ans'])*3 + pd.to_numeric(ensemble_age['ans'])

>>> submission

 

4️⃣ Sub Task

없음.

 

 

5️⃣ Evaluation

 

Data processing Model Training Time Accuracy F1
3/29   -
ResNet-50
-
-
Hyperparmeter
설정
-
7h 32m 61.87% 0.52%
3/31 -
Face Recognition
-
    36m 65.05% 0.56%
4/1   -
(pre-trained)
Efficient-Net-b6
-
-
epoch : 20,
batch size : 128
-
1h 35m 73.67% 0.66%
4/1   -
(pre-trained)
Efficient-Net-b7
-
-
epoch : 20,
batch size : 100
-
1h 31m 73.25% 0.66%
4/1     -
learning scheduler : 
CosineAnnealingLR
-
epoch : 21
-
1h 41m 68.49% 0.60%
4/2 -
 imbalanced-dataset-sampler
-
  -
learning scheduler X
-
epoch : 26
-
1h 39m 72.38% 0.64%
4/2     -
loss :
Focal loss 
(γ = 2)
-
epoch : 16
-
1h 29m 74.43% 0.68%
4/8    -
(pre-trained)
Efficient-net b6
Efficient-net b4
ecaresnet50t
seresnet152d
-
-
Ensemble
-
17h 24m 74.46% 0.68%

 

* F1 Score

 


1) NNI를 사용한 모델 나누기 ( Mask -> Gender -> Age )

 

  • 17시간을 돌려서 Mask, Gender, Age에 대한 각각의 모델을 찾고, hyperparameter들을 파악했습니다.

  • 대부분의 Train Accuracy는 높은 점수를 보였습니다.

  • nf_net은 pre-trained 모델을 사용했지만 훈련시간이 어마어마하게 걸렸고, 그에 비해 좋지 못한 성능을 보였습니다.

    아마 Fine-tuning을 하기에 적합하지 않은 모델일 수도 있습니다.

 

 

 

 

아래 그림은 Age Classification을 수행한 모델들입니다.

 

 

아래 그림은 Mask Classification을 수행한 모델들입니다.

 

 

아래 그림은 Gender Classification을 수행한 모델들입니다.

 

 

Mask, Gender, Age 모든 모델에 대한 Train Accuracy를 나타낸 그래프입니다.

 

 

 

2) 앙상블

  • 앙상블은 Mask, Gender, Age classification에 대한 여러 model들에 대해 진행했습니다.

  • 현재 Mask는 6개, Gender는 1개, Age는 2개의 model을 가지고 ensemble했습니다.


    --- AGE ---

    - model : ecaresnet50t / batch size : 140 / loss : focal / lr : 0.01 (10 epoch 당 1/10)

    - model : seresnet152d / batch size : 64 / loss : cross_entropy lr : 0.01 (10 epoch 당 1/10)


    --- GENDER ---

    - model : seresnet152d / batch size : 64 / loss : cross_entropy / lr : 0.01 (10 epoch 당 1/10)


    --- MASK ---

    - model : Efficientnet_b4 / batch size : 128 / loss : cross_entropy / lr : 0.01 (10 epoch 당 1/10)

    - model : Efficientnet_b4 / batch size : 140 / loss : f1 / lr : 0.01 (10 epoch 당 1/10)
     
    - model : Efficientnet_b4 / batch size : 140 / loss : focal / lr : 0.01 (10 epoch 당 1/10)

    - model : Efficientnet_b6 / batch size : 64 / loss : f1 / lr : 0.01 (10 epoch 당 1/10)

    - model : ecaresnet50t / batch size : 128 / loss : label_smoothing / lr : 0.01 (10 epoch 당 1/10)

    - model : seresnet152d / batch size : 16 / loss : label_smoothing / lr : 0.01 (10 epoch 당 1/10)

 

3) 추가 목표

  • FaceNET crop image가 아닌 일반 image를 가지고 train

  • crop image와 일반 image 앙상블
  • Mask, Age, Gender 앙상블 + Multi 앙상블
Comments