🔥알림🔥
① 테디노트 유튜브 -
구경하러 가기!
② 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)
참고
댓글남기기