Home [BoostCamp AI Tech / P Stage 1] Day26 - Project Day 4
Post
Cancel

[BoostCamp AI Tech / P Stage 1] Day26 - Project Day 4

P Stage 1 : Project Day 4


Day 4 list

  1. wandb 연결
  2. train - validation 분리
  3. pre transform 적용으로 데이터 로더 속도 증가
  4. SENet 사용
  5. Multi-head classifier 구현
  6. CutMix 적용

wandb 연결

CLI 창에 뜨는 값으로만 확인하기엔…. 너무 힘들기도 하고 영 눈에 안들어오는 거 같아서 wandb에 연결했습니다. 생각보다 어렵지 않았습니다.

1
2
3
4
5
6
7
8
9
10
11
12
def train(args):
    if args.wandb == "true":
        config = { ... }
        wandb.init(project="project", name=args.name, config=cofing, entity="entity")

    ...

    # validation part
    ...

    if args.wandb == "true":
        wandb.log({"Loss": {"train loss": train_loss, "val loss": val_loss},"F1 Score": {"train f1": train_f1, "val f1": val_f1}})

위처럼 코드 작성했고 물론 1에포크당 validation check를 하므로 1에포크의 평균치로 기록됩니다. wandb.init에 들어가는 name은 사용자 입력으로 받는 모델 이름으로 넘겨주어서 wandb 사이트에서 어떤 모델이 어떤 지표를 나타내는지 보기 쉽게하였습니다.


train-valid 분리

이번 대회에서는 이미지에 대한 데이터프레임을 만들고 sklearntrain_test_split을 쓰면 안되는 구조로 되어있습니다. 일반적으로 validation check를 위해서 데이터 스플릿을 진행하는데, 데이터가 1명에 대해 다양한 클래스로 나눠져있어서 사람별로 스플릿하지 않으면 validation check 과정에서 cheating이 발생합니다.

따라서 train과 valid를 일단 사람별로 인덱스로 구분하고 그 인덱스를 Dataset에 넘겨주어 처리하는 방식을 선택했습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class CustomTrainDataset(Dataset):
    def __init__(self, df: pd.DataFrame, pre_transforms: T = None, transforms: T = None, train: bool = True):
        self.base_url = '/opt/ml'
        self.img_url = '/input/data/train/images'
        self.transforms = transforms
        self.train = train
        self.images = []
        self.df = self.__make_dataframe(df)

        for path in self.df['path'].values:
            image = Image.open(path)
            if pre_transforms:
                image = pre_transforms(image)
            self.images.append(image)

    def __make_dataframe(self, df: pd.DataFrame) -> pd.DataFrame:
        if self.train:
            save_name = 'train_df.csv'
        else:
            save_name = 'valid_df.csv'
        ret_df = pd.DataFrame(columns=['id', 'gender', 'age', 'age_group', 'mask', 'path', 'class'])

        if save_name in list(os.listdir('/opt/ml/outputs')):
            print(f"File {save_name} is Exist!")
            ret_df = pd.read_csv('/opt/ml/outputs/'+save_name)
        else:
            for row in df.iloc:
                for file in list(os.listdir(os.path.join(self.base_url+'/'+self.img_url, row.path))):
                    # 보안상 생략
                        data = {
                            # 보안상 생략
                        }
                        ret_df = ret_df.append(data, ignore_index=True)
            ret_df.to_csv('/opt/ml/outputs/'+save_name, index=False)
        return ret_df

    def __getitem__(self, idx):
        image = self.images[idx]
        classes = self.df.iloc[idx]

        if self.transforms:
            image = self.transforms(image)
        return image, classes['class']

    def __len__(self):
        return len(self.df)

위 방식처럼 특정 데이터프레임을 받아오면 그 데이터프레임에 대해 처리를 전반적으로 진행합니다. 물론 데이터셋을 생성하는 과정에서 오래 걸리는 것이 문제라 이 부분을 해결하기위해 제공된 베이스라인을 분석 및 변형할 예정입니다.

pre transform

전반적인 학습속도가 너무 오래 걸리는 것이 문제였습니다. 이유는 DataLoader가 배치 1개를 불러올때 속도가 오래 걸리는 것이 문제였습니다. 따라서 전반적인 학습 속도의 향상을 위해 GPU 메모리의 사용률을 높이는 방향으로 갔습니다.

1
2
3
4
5
pre_transform = transforms.Compose([
        transforms.Resize((512//4, 384//4)),
    ])

train_dataset = CustomTrainDataset(train_df, pre_transforms=pre_transform, transforms=transform, train=True)

이렇게 pre_transform을 통해 이미지의 전체 비율을 줄입니다. 이 과정을 거치면 1개의 이미지 용량이 줄어들게 됩니다. 그리고 pre_transform이 적용되는 과정을 보면 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CustomTrainDataset(Dataset):
    def __init__(self, df: pd.DataFrame, pre_transforms: T = None, transforms: T = None, train: bool = True):
        self.base_url = '/opt/ml'
        self.img_url = '/input/data/train/images'
        self.transforms = transforms
        self.train = train
        self.images = []
        self.df = self.__make_dataframe(df)

        for path in self.df['path'].values:
            image = Image.open(path)
            if pre_transforms:
                image = pre_transforms(image)
            self.images.append(image)
    
    ...

데이터셋을 구성할 때 모든 이미지를 불러와서 메모리에 올리는 방식을 사용했습니다. 이 방법을 사용하면 데이터셋 구성에서 시간이 오래 걸리지만 1번의 과정만 시간을 소모하면 되므로 학습 시간에서 향상이 나타났습니다. 하지만 이 자체가 그렇게 좋은 것은 아니라 생각되므로 추가적인 방법을 고려해서 데이터셋 로드 시간 자체도 줄여야겠습니다.


SENet 사용, Multi-head classifier 구현

일단 이미지 분류 대회다보니 ILSVRC에서 우승한 모델들 위주로 찾아봤습니다. 그 중 SENet이 성능이 좋은 것으로 나타났단 것을 보고 관련 내용 구현코드를 활용했습니다.
자세한 코드 설명은 하지 않겠습니다. 일단 torchvisionresnet18보다 성능이 좋지 않기 때문에 이를 그대로 사용할 것입니다.

마찬가지로 어제 구상한 multi-head classifier를 구현해봤습니다. 기존 resnet의 out_feature 1000을 유지하고 1000에다가 마지막에 총 3개의 nn.Linear를 추가했습니다. 이를 총 18개의 클래스 위치에 값을 연산하여 forward 처리를 해주는 모델을 구상했습니다. 이 코드도 딱히 의미가 있지 않아서 첨부하진 않겠습니다.
이후 공개되는 깃허브로 공유하겠습니다.

성능 향상은 크게 없었습니다.


CutMix

data augmentation 방법 중 cut mix가 효과가 좋다고 알고 있습니다. 해당 값을 적용했더니 validation loss가 많이 줄어드는 것을 발견했으나 정작 test data에서 좋은 성능이 나타나지 않습니다….

진짜 모르겠습니다 ㅠㅠㅠㅠㅠㅠㅠㅠㅠ 내일 팀원들이랑 상의를 해봐야겠습니다.

This post is licensed under CC BY 4.0 by the author.

[BoostCamp AI Tech] Day25

[BoostCamp AI Tech] Day26

Comments powered by Disqus.