🔥알림🔥
① 테디노트 유튜브 -
구경하러 가기!
② LangChain 한국어 튜토리얼
바로가기 👀
③ 랭체인 노트 무료 전자책(wikidocs)
바로가기 🙌
④ RAG 비법노트 LangChain 강의오픈
바로가기 🙌
⑤ 서울대 PyTorch 딥러닝 강의
바로가기 🙌
자동차 충돌 분석 AI경진대회 - 베이스라인
본 포스팅은 데이콘(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 영상을 로드하기 위한 경로가 있습니다.
cv2
의 VideoCapture()
함수로 영상을 로드합니다. 그리고, 로드한 영상에서 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)
참고
댓글남기기