🔥알림🔥
① 테디노트 유튜브 - 구경하러 가기!
② LangChain 한국어 튜토리얼 바로가기 👀
③ 랭체인 노트 무료 전자책(wikidocs) 바로가기 🙌

12 분 소요

본 포스팅은 데이콘(dacon.io)에서 2023.02.06 ~ 2023.03.13 기간 동안 진행하는 자동차 충돌 분석 AI경진대회에 제출한 베이스라인 코드 입니다.

코드


대회소개

제 1회 코스포 x 데이콘 자동차 충돌 분석 AI경진대회(채용 연계형)

본 대회는 코스포와 데이콘에서 주관한 대회로 블랙박스 영상을 활용하여 자동차의 충돌 상황을 분석하는 AI 모델을 생성하는 것을 목표로 합니다.

대회의 평가 방식은 Macro F1 Score를 사용합니다.

data 압축 해제

대회 링크의 데이터 탭에서 다운로드 받은 데이터를 압축 해제 합니다.

train / test 폴더로 구분되어 있으며, 각 폴더 안에는 .mp4 영상이 있습니다.

# import urllib.request
# import zipfile
# import os

# def unzip(source_file, dest_path):
#     with zipfile.ZipFile(source_file, 'r') as zf:
#         zipInfo = zf.infolist()
#         for member in zipInfo:
#             try:
#                 member.filename = member.filename.encode('cp437').decode('euc-kr', 'ignore')
#                 zf.extract(member, dest_path)
#             except:
#                 print(source_file)
#                 raise Exception('unzipping error')
                

# unzip('./data/open.zip', './data')

SEED 고정

import random
import torch
import numpy as np
import os

SEED = 123

def seed_everything(seed):
    random.seed(seed)
    os.environ['PYTHONHASHSEED'] = str(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = True

# Seed 고정
seed_everything(SEED) 

하이퍼파라미터 설정

CFG = {
    'VIDEO_LENGTH': 50,      # 10프레임 * 5초
    'IMG_SIZE': 224,         # 224 x 224
    'EPOCHS': 10,            # Epochs
    'LEARNING_RATE': 3e-4,   # Learning Rate
    'BATCH_SIZE': 4,         # batch_size
    'SEED': 123,             # SEED 값
    'MODEL_NAME': 'Base-Model'
}

데이터 로드

import pandas as pd
import os
import warnings

warnings.filterwarnings('ignore')

DATA_DIR = 'data'
SUBMISSION_DIR = 'submit'
MODEL_DIR = 'model'
PRETRAINED_DIR = 'pretrained'

train = pd.read_csv(os.path.join(DATA_DIR, 'train.csv'))
test = pd.read_csv(os.path.join(DATA_DIR, 'test.csv'))
# 5개의 행 출력
train.head()
sample_id video_path label
0 TRAIN_0000 ./train/TRAIN_0000.mp4 7
1 TRAIN_0001 ./train/TRAIN_0001.mp4 7
2 TRAIN_0002 ./train/TRAIN_0002.mp4 0
3 TRAIN_0003 ./train/TRAIN_0003.mp4 0
4 TRAIN_0004 ./train/TRAIN_0004.mp4 1

video_path 디렉토리 수정

비디오 경로를 수정

# train set의 video_path 수정
train['video_path'] = train['video_path'].apply(lambda x: os.path.join(DATA_DIR, x[2:]))
train['video_path'].head()
0    data/train/TRAIN_0000.mp4
1    data/train/TRAIN_0001.mp4
2    data/train/TRAIN_0002.mp4
3    data/train/TRAIN_0003.mp4
4    data/train/TRAIN_0004.mp4
Name: video_path, dtype: object
# test set의 video_path 수정
test['video_path'] = test['video_path'].apply(lambda x: os.path.join(DATA_DIR, x[2:]))
test['video_path'].head()
0    data/test/TEST_0000.mp4
1    data/test/TEST_0001.mp4
2    data/test/TEST_0002.mp4
3    data/test/TEST_0003.mp4
4    data/test/TEST_0004.mp4
Name: video_path, dtype: object

Label 값의 데이터 Imbalance 확인

본 대회는 총 13개로 이루어진 label 분류 대회입니다.

각각의 label 은 다음의 표 형식에 따라 분류 됩니다.

0번 label의 분포가 가장 많습니다. 기타 label은 매우 imbalance 한 분포를 가집니다.

# label의 분포를 출력합니다
train['label'].value_counts()
0     1783
1      318
7      317
3       78
2       51
9       34
11      33
8       30
5       28
4       13
12       6
10       4
6        3
Name: label, dtype: int64

학습/검증 셋 분할

from sklearn.model_selection import train_test_split

# 데이터셋 분할
train_data, valid_data = train_test_split(train, 
                                          test_size=0.2, 
                                          stratify=train['label'], 
                                          random_state=SEED)
# 분할된 데이터셋 shape 출력
train_data.shape, valid_data.shape
((2158, 3), (540, 3))

영상 로드 (opencv2)

train.csv 파일에는 video_path 컬럼이 존재하는데 video 영상을 로드하기 위한 경로가 있습니다.

cv2VideoCapture() 함수로 영상을 로드합니다. 그리고, 로드한 영상에서 Frame을 추출하는 작업을 진행합니다.

# 5개 출력
train_data.head()
sample_id video_path label
2491 TRAIN_2491 data/train/TRAIN_2491.mp4 7
236 TRAIN_0236 data/train/TRAIN_0236.mp4 0
1591 TRAIN_1591 data/train/TRAIN_1591.mp4 0
151 TRAIN_0151 data/train/TRAIN_0151.mp4 0
1683 TRAIN_1683 data/train/TRAIN_1683.mp4 0
# 0번째 (셔플이 되어 있으므로 2491번 index) 데이터의 video_path 를 가져옵니다
sample_idx = 0
sample_video_path = train_data['video_path'].iloc[sample_idx]
sample_label = train_data['label'].iloc[sample_idx]
sample_video_path, sample_label
('data/train/TRAIN_2491.mp4', 7)

영상 로드를 위한 cv2 라이브러리를 import 한 뒤 VideoCapture() 함수로 영상을 로드 합니다.

import cv2

captured_output = cv2.VideoCapture(sample_video_path)

아래 코드는 영상을 열어 Frame을 읽어들여 frames list 변수에 frame 한 장씩 추가하는 코드입니다.

1초10 frame, 그리고 5초짜리 영상이니 총 50 frame이 추출됩니다.

frames = []
IMG_SIZE = 224

# 영상 로드
captured_output = cv2.VideoCapture(sample_video_path)

# 5*10 => 5초 * 10프레임
for _ in range(5*10):
    try:
        # 1장 frame
        _, img = captured_output.read()
        # image resize
        img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))
        # 정규화
        img = img / 255.
        # 프레임에 추가
        frames.append(img)
    except Exception as e:
        print(str(e))

총 50장의 frame, 그리고 1장의 frame(사진)은 (224, 224, 3) 의 형태를 가집니다.

# 50 프레임, 1 프레임 (224 X 224)
len(frames), frames[0].shape
(50, (224, 224, 3))

1개 영상 프레임별 시각화

import matplotlib.pyplot as plt


def visualize_frames(frames, label):
    print(f'Label: {label}')
    fig, axes = plt.subplots(10, 5)
    fig.set_size_inches(10, 20)
    for i in range(50):
        axes[i//5, i%5].imshow(frames[i])
        axes[i//5, i%5].axis('off')
        axes[i//5, i%5].set_title(f'frame {i}')

    plt.tight_layout()
    plt.show()
# 1개 동영상 프레임별 시각화
visualize_frames(frames, sample_label)
Label: 7

CustomDataset 생성

from torch.utils.data import Dataset, DataLoader

class CustomDataset(Dataset):
    def __init__(self, dataframe, frame_count=5*10, image_size=224, mode='train'):
        super(CustomDataset, self).__init__()
        self.data = dataframe
        self.image_size = image_size
        self.frame_count = frame_count
        self.mode = mode
        
    def __getitem__(self, idx):
        video_path = self.data.iloc[idx]['video_path']
        # frame 로드
        frames = self.load_video(video_path)
        if self.mode == 'train':
            label = self.data.iloc[idx]['label']
            # mode='train'
            return frames, label
        else:
            # mode='test'
            return frames
        
    def __len__(self):
        return len(self.data)
    
    # 영상 로드
    def load_video(self, video_path):
        frames = []
        cap = cv2.VideoCapture(video_path)
        for _ in range(self.frame_count):
            _, img = cap.read()
            # resize
            img = cv2.resize(img, (self.image_size, self.image_size))
            # normalize
            img = img / 255.
            frames.append(img)
            
            # depth, height, width, channel => channel, depth, height, width 로 순서 변경
            # (50, 224, 224, 3)             => (3, 50, 224, 224)
            # Conv3D 에 입력으로 넣기 위함
        return torch.FloatTensor(np.array(frames)).permute(3, 0, 1, 2)
# train dataset 생성
train_dataset = CustomDataset(train_data, image_size=CFG['IMG_SIZE'], mode='train')

# train dataloader 생성
train_loader = DataLoader(train_dataset, 
                          batch_size=CFG['BATCH_SIZE'], 
                          shuffle=True, 
                          num_workers=0)
# validation dataset 생성
valid_dataset = CustomDataset(valid_data, image_size=CFG['IMG_SIZE'], mode='train')

# validation dataloader 생성
valid_loader = DataLoader(valid_dataset, 
                          batch_size=CFG['BATCH_SIZE'], 
                          shuffle=True, 
                          num_workers=0)
# 1개의 데이터 추출
x, y = next(iter(train_loader))
x.shape
# 출력 shape
# (batch_size, channel, depth, height, width)
torch.Size([4, 3, 50, 224, 224])

모델

import torch.nn as nn
import torch.optim as optim
import torch.functional as F
from torchvision import models
# 베이스라인 모델
class BaseModel(nn.Module):
    def __init__(self, num_classes=13):
        super(BaseModel, self).__init__()
        self.net = models.video.mc3_18(pretrained=True)
        for param in self.net.parameters():
            param.requires_grad = False
        for param in self.net.fc.parameters():
            param.requires_grad = True
        
            
        self.classifier = nn.Sequential(
            nn.Dropout(0.2),
            nn.Linear(400, 128), 
            nn.ReLU(),
            nn.Linear(128, num_classes)
        )
#         self.classifier = nn.Linear(400, num_classes)
        
    def forward(self, x):
        batch_size = x.size(0)
        x = self.net(x)
        x = x.view(batch_size, -1)
        x = self.classifier(x)
        return x

모델구조 (summary)

import torchsummary


device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')

# model 생성 후 device 로 이동
model = BaseModel().to(device)

# (3 channel, 50 frames, 224, 224)
torchsummary.summary(model, input_size=(3, 50, 224, 224), device='cuda')
----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv3d-1     [-1, 64, 50, 112, 112]          28,224
       BatchNorm3d-2     [-1, 64, 50, 112, 112]             128
              ReLU-3     [-1, 64, 50, 112, 112]               0
      Conv3DSimple-4     [-1, 64, 50, 112, 112]         110,592
       BatchNorm3d-5     [-1, 64, 50, 112, 112]             128
              ReLU-6     [-1, 64, 50, 112, 112]               0
      Conv3DSimple-7     [-1, 64, 50, 112, 112]         110,592
       BatchNorm3d-8     [-1, 64, 50, 112, 112]             128
              ReLU-9     [-1, 64, 50, 112, 112]               0
       BasicBlock-10     [-1, 64, 50, 112, 112]               0
     Conv3DSimple-11     [-1, 64, 50, 112, 112]         110,592
      BatchNorm3d-12     [-1, 64, 50, 112, 112]             128
             ReLU-13     [-1, 64, 50, 112, 112]               0
     Conv3DSimple-14     [-1, 64, 50, 112, 112]         110,592
      BatchNorm3d-15     [-1, 64, 50, 112, 112]             128
             ReLU-16     [-1, 64, 50, 112, 112]               0
       BasicBlock-17     [-1, 64, 50, 112, 112]               0
 Conv3DNoTemporal-18      [-1, 128, 50, 56, 56]          73,728
      BatchNorm3d-19      [-1, 128, 50, 56, 56]             256
             ReLU-20      [-1, 128, 50, 56, 56]               0
 Conv3DNoTemporal-21      [-1, 128, 50, 56, 56]         147,456
      BatchNorm3d-22      [-1, 128, 50, 56, 56]             256
           Conv3d-23      [-1, 128, 50, 56, 56]           8,192
      BatchNorm3d-24      [-1, 128, 50, 56, 56]             256
             ReLU-25      [-1, 128, 50, 56, 56]               0
       BasicBlock-26      [-1, 128, 50, 56, 56]               0
 Conv3DNoTemporal-27      [-1, 128, 50, 56, 56]         147,456
      BatchNorm3d-28      [-1, 128, 50, 56, 56]             256
             ReLU-29      [-1, 128, 50, 56, 56]               0
 Conv3DNoTemporal-30      [-1, 128, 50, 56, 56]         147,456
      BatchNorm3d-31      [-1, 128, 50, 56, 56]             256
             ReLU-32      [-1, 128, 50, 56, 56]               0
       BasicBlock-33      [-1, 128, 50, 56, 56]               0
 Conv3DNoTemporal-34      [-1, 256, 50, 28, 28]         294,912
      BatchNorm3d-35      [-1, 256, 50, 28, 28]             512
             ReLU-36      [-1, 256, 50, 28, 28]               0
 Conv3DNoTemporal-37      [-1, 256, 50, 28, 28]         589,824
      BatchNorm3d-38      [-1, 256, 50, 28, 28]             512
           Conv3d-39      [-1, 256, 50, 28, 28]          32,768
      BatchNorm3d-40      [-1, 256, 50, 28, 28]             512
             ReLU-41      [-1, 256, 50, 28, 28]               0
       BasicBlock-42      [-1, 256, 50, 28, 28]               0
 Conv3DNoTemporal-43      [-1, 256, 50, 28, 28]         589,824
      BatchNorm3d-44      [-1, 256, 50, 28, 28]             512
             ReLU-45      [-1, 256, 50, 28, 28]               0
 Conv3DNoTemporal-46      [-1, 256, 50, 28, 28]         589,824
      BatchNorm3d-47      [-1, 256, 50, 28, 28]             512
             ReLU-48      [-1, 256, 50, 28, 28]               0
       BasicBlock-49      [-1, 256, 50, 28, 28]               0
 Conv3DNoTemporal-50      [-1, 512, 50, 14, 14]       1,179,648
      BatchNorm3d-51      [-1, 512, 50, 14, 14]           1,024
             ReLU-52      [-1, 512, 50, 14, 14]               0
 Conv3DNoTemporal-53      [-1, 512, 50, 14, 14]       2,359,296
      BatchNorm3d-54      [-1, 512, 50, 14, 14]           1,024
           Conv3d-55      [-1, 512, 50, 14, 14]         131,072
      BatchNorm3d-56      [-1, 512, 50, 14, 14]           1,024
             ReLU-57      [-1, 512, 50, 14, 14]               0
       BasicBlock-58      [-1, 512, 50, 14, 14]               0
 Conv3DNoTemporal-59      [-1, 512, 50, 14, 14]       2,359,296
      BatchNorm3d-60      [-1, 512, 50, 14, 14]           1,024
             ReLU-61      [-1, 512, 50, 14, 14]               0
 Conv3DNoTemporal-62      [-1, 512, 50, 14, 14]       2,359,296
      BatchNorm3d-63      [-1, 512, 50, 14, 14]           1,024
             ReLU-64      [-1, 512, 50, 14, 14]               0
       BasicBlock-65      [-1, 512, 50, 14, 14]               0
AdaptiveAvgPool3d-66         [-1, 512, 1, 1, 1]               0
           Linear-67                  [-1, 400]         205,200
      VideoResNet-68                  [-1, 400]               0
          Dropout-69                  [-1, 400]               0
           Linear-70                  [-1, 128]          51,328
             ReLU-71                  [-1, 128]               0
           Linear-72                   [-1, 13]           1,677
================================================================
Total params: 11,748,445
Trainable params: 258,205
Non-trainable params: 11,490,240
----------------------------------------------------------------
Input size (MB): 28.71
Forward/backward pass size (MB): 9493.77
Params size (MB): 44.82
Estimated Total Size (MB): 9567.29
----------------------------------------------------------------

모델 생성 / 옵티마이저 / 손실함수 / 스케줄러

# device 설정
device = torch.device('cuda:1') if torch.cuda.is_available() else torch.device('cpu')

# 베이스라인 모델 생성
model = BaseModel().to(device)

# 옵티마이저를 정의합니다. 옵티마이저에는 model.parameters()를 지정해야 합니다.
optimizer = optim.Adam(model.parameters(), lr=CFG["LEARNING_RATE"])

# 손실함수(loss function)을 지정합니다. Multi-Class Classification 이기 때문에 CrossEntropy 손실을 지정하였습니다.
loss_fn = nn.CrossEntropyLoss()

# Learning Rate Scheduler
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer,
                                                       mode='max', 
                                                       factor=0.5, 
                                                       patience=3,
                                                       threshold_mode='abs',
                                                       min_lr=1e-8, 
                                                       verbose=True)

training 함수

from tqdm import tqdm
from sklearn.metrics import f1_score


def model_train(model, data_loader, loss_fn, optimizer, device):
    # 모델을 훈련모드로 설정합니다. training mode 일 때 Gradient 가 업데이트 됩니다. 반드시 train()으로 모드 변경을 해야 합니다.
    model.train()
    
    # loss와 accuracy 계산을 위한 임시 변수 입니다. 0으로 초기화합니다.
    running_size = 0
    running_loss = 0
    
    # trues, preds
    trues, preds = [], []
    
    # 예쁘게 Progress Bar를 출력하면서 훈련 상태를 모니터링 하기 위하여 tqdm으로 래핑합니다.
    prograss_bar = tqdm(data_loader)
    
    # mini-batch 학습을 시작합니다.
    for batch_idx, (video, lbl) in enumerate(prograss_bar, start=1):
        # image, label 데이터를 device에 올립니다.
        video, lbl = video.to(device), lbl.to(device)
        
        # 누적 Gradient를 초기화 합니다.
        optimizer.zero_grad()
        
        # Forward Propagation을 진행하여 결과를 얻습니다.
        output = model(video)
        
        # 손실함수에 output, label 값을 대입하여 손실을 계산합니다.
        loss = loss_fn(output, lbl)
        
        # 오차역전파(Back Propagation)을 진행하여 미분 값을 계산합니다.
        loss.backward()
        
        # 계산된 Gradient를 업데이트 합니다.
        optimizer.step()
        
        # output의 max(dim=1)은 max probability와 max index를 반환합니다.
        # max probability는 무시하고, max index는 pred에 저장하여 label 값과 대조하여 정확도를 도출합니다.
        _, pred = output.max(dim=1)
        
        # loss 값은 1개 배치의 평균 손실(loss) 입니다. video.size(0)은 배치사이즈(batch size) 입니다.
        # loss 와 video.size(0)를 곱하면 1개 배치의 전체 loss가 계산됩니다.
        # 이를 누적한 뒤 Epoch 종료시 전체 데이터셋의 개수로 나누어 평균 loss를 산출합니다.
        running_loss += loss.item() * video.size(0)
        running_size += video.size(0)
        
        # 배치별 labels, preds 을 추출합니다.
        batch_lbls = lbl.detach().cpu().numpy().tolist()
        batch_preds = pred.detach().cpu().numpy().tolist()
        
        # 전역 trues, preds 에 배치별 labels, preds 를 추가합니다.
        trues += batch_lbls
        preds += batch_preds
        
        # 배치별 f1 score를 계산합니다.
        f1 = f1_score(batch_lbls, batch_preds, average='macro')
        
        prograss_bar.set_description(f'[Training] loss: {running_loss / running_size:.4f}, f1 score: {f1:.4f}')
    
    # Macro F1 Score 계산합니다.
    f1 = f1_score(trues, preds, average='macro')
    
    # 평균 손실(loss)를 반환합니다.
    # train_loss
    return running_loss / len(data_loader.dataset), f1

Evaluation 함수

def model_evaluate(model, data_loader, loss_fn, device):
    # model.eval()은 모델을 평가모드로 설정을 바꾸어 줍니다. 
    # dropout과 같은 layer의 역할 변경을 위하여 evaluation 진행시 꼭 필요한 절차 입니다.
    model.eval()
    
    trues, preds = [], []
    
    # Gradient가 업데이트 되는 것을 방지 하기 위하여 반드시 필요합니다.
    with torch.no_grad():
        # loss와 accuracy 계산을 위한 임시 변수 입니다. 0으로 초기화합니다.
        corr = 0
        running_loss = 0
        
        # 배치별 evaluation을 진행합니다.
        for video, lbl in data_loader:
            # device에 데이터를 올립니다.
            video, lbl = video.to(device), lbl.to(device)
            
            # 모델에 Forward Propagation을 하여 결과를 도출합니다.
            output = model(video)
            
            # output의 max(dim=1)은 max probability와 max index를 반환합니다.
            # max probability는 무시하고, max index는 pred에 저장하여 label 값과 대조하여 정확도를 도출합니다.
            _, pred = output.max(dim=1)
            
            # loss 값은 1개 배치의 평균 손실(loss) 입니다. video.size(0)은 배치사이즈(batch size) 입니다.
            # loss 와 video.size(0)를 곱하면 1개 배치의 전체 loss가 계산됩니다.
            # 이를 누적한 뒤 Epoch 종료시 전체 데이터셋의 개수로 나누어 평균 loss를 산출합니다.
            running_loss += loss_fn(output, lbl).item() * video.size(0)
            
            trues += lbl.detach().cpu().numpy().tolist()
            preds += pred.detach().cpu().numpy().tolist()
        
        # Macro F1 Score 계산합니다.
        f1 = f1_score(trues, preds, average='macro')
        
        # 결과를 반환합니다.
        # val_loss, f1 score
        return running_loss / len(data_loader.dataset), f1

학습 & 검증

min_loss = np.inf

# Epoch 별 훈련 및 검증을 수행합니다.
for epoch in range(CFG['EPOCHS']):
    # Model Training
    # 훈련 손실과 정확도를 반환 받습니다.
    train_loss, train_f1 = model_train(model, train_loader, loss_fn, optimizer, device)

    # 검증 손실과 검증 정확도를 반환 받습니다.
    val_loss, val_f1 = model_evaluate(model, valid_loader, loss_fn, device)   
    
    # val_loss 가 개선되었다면 min_loss를 갱신하고 model의 가중치(weights)를 저장합니다.
    if val_loss < min_loss:
        print(f'[INFO] val_loss has been improved from {min_loss:.5f} to {val_loss:.5f}. Saving Model!')
        min_loss = val_loss
        torch.save(model.state_dict(), os.path.join(MODEL_DIR, f"{CFG['MODEL_NAME']}.pth"))
        
    # scheduler 업데이트
    scheduler.step(val_loss)
    
    # Epoch 별 결과를 출력합니다.
    print(f'epoch {epoch+1:02d}, loss: {train_loss:.5f}, f1: {train_f1:.5f}, val_loss: {val_loss:.5f}, val_f1: {val_f1:.5f}')
[Training] loss: 0.9517, f1 score: 0.0000: 100% 540/540 [05:32<00:00,  1.63it/s]
[INFO] val_loss has been improved from inf to 0.55552. Saving Model!
epoch 01, loss: 0.95168, f1: 0.12684, val_loss: 0.55552, val_f1: 0.22429
[Training] loss: 0.8033, f1 score: 1.0000: 100% 540/540 [05:31<00:00,  1.63it/s]
[INFO] val_loss has been improved from 0.55552 to 0.48684. Saving Model!
epoch 02, loss: 0.80332, f1: 0.16869, val_loss: 0.48684, val_f1: 0.26408
[Training] loss: 0.7257, f1 score: 0.3333: 100% 540/540 [05:30<00:00,  1.63it/s]
[INFO] val_loss has been improved from 0.48684 to 0.44429. Saving Model!
epoch 03, loss: 0.72572, f1: 0.20106, val_loss: 0.44429, val_f1: 0.26896
[Training] loss: 0.6948, f1 score: 1.0000: 100% 540/540 [05:31<00:00,  1.63it/s]
[INFO] val_loss has been improved from 0.44429 to 0.43688. Saving Model!
epoch 04, loss: 0.69478, f1: 0.21016, val_loss: 0.43688, val_f1: 0.29846
[Training] loss: 0.6577, f1 score: 1.0000: 100% 540/540 [05:30<00:00,  1.63it/s]
Epoch     5: reducing learning rate of group 0 to 1.5000e-04.
epoch 05, loss: 0.65772, f1: 0.25570, val_loss: 0.44672, val_f1: 0.28249
[Training] loss: 0.6090, f1 score: 1.0000: 100% 540/540 [05:31<00:00,  1.63it/s]
[INFO] val_loss has been improved from 0.43688 to 0.40093. Saving Model!
epoch 06, loss: 0.60899, f1: 0.29056, val_loss: 0.40093, val_f1: 0.33927
[Training] loss: 0.5938, f1 score: 0.3333: 100% 540/540 [05:30<00:00,  1.63it/s]
epoch 07, loss: 0.59377, f1: 0.29322, val_loss: 0.40938, val_f1: 0.31991
[Training] loss: 0.5918, f1 score: 0.3333: 100% 540/540 [05:31<00:00,  1.63it/s]
[INFO] val_loss has been improved from 0.40093 to 0.38329. Saving Model!
epoch 08, loss: 0.59179, f1: 0.28987, val_loss: 0.38329, val_f1: 0.35430
[Training] loss: 0.5738, f1 score: 0.3333: 100% 540/540 [05:34<00:00,  1.62it/s]
Epoch     9: reducing learning rate of group 0 to 7.5000e-05.
epoch 09, loss: 0.57380, f1: 0.31578, val_loss: 0.39068, val_f1: 0.32359
[Training] loss: 0.5184, f1 score: 1.0000: 100% 540/540 [05:35<00:00,  1.61it/s]
[INFO] val_loss has been improved from 0.38329 to 0.37852. Saving Model!
epoch 10, loss: 0.51844, f1: 0.31606, val_loss: 0.37852, val_f1: 0.34960

저장한 체크포인트 로드 및 추론

# 모델에 저장한 가중치 로드
model.load_state_dict(torch.load(os.path.join(MODEL_DIR, f"{CFG['MODEL_NAME']}.pth")))
# test 데이터셋 로드
test_dataset = CustomDataset(test, mode='test')
test_loader = DataLoader(test_dataset, 
                         batch_size=CFG['BATCH_SIZE'], 
                         shuffle=False, 
                         num_workers=16)
# 추론을 위한 함수
def inference(model, test_loader, device):
    model.to(device)
    model.eval()
    preds = []
    with torch.no_grad():
        for videos in tqdm(iter(test_loader)):
            videos = videos.to(device)
            output = model(videos)
            preds += output.argmax(1).detach().cpu().numpy().tolist()
    return preds
# 추론(inference)
preds = inference(model, test_loader, device)
100% 450/450 [02:48<00:00,  2.67it/s]

제출

# 제출 양식 로드
submit = pd.read_csv(os.path.join(DATA_DIR, 'sample_submission.csv'))
# 정답 기입
submit['label'] = preds
submit.head()
sample_id label
0 TEST_0000 0
1 TEST_0001 0
2 TEST_0002 0
3 TEST_0003 0
4 TEST_0004 0
# csv 생성
submit.to_csv(os.path.join(SUBMISSION_DIR, 'submission-baseline.csv'), index=False)

참고

댓글남기기