|
|
모델명: centerpointpillars
데이터셋 : 2025 데이터셋
테스트 코드
import _init_path
import argparse
import datetime
import glob
import os
import re
import time
from pathlib import Path
import numpy as np
import torch
from tensorboardX import SummaryWriter
from eval_utils import eval_utils
from pcdet.config import cfg, cfg_from_list, cfg_from_yaml_file, log_config_to_file
from pcdet.datasets import build_dataloader
from pcdet.models import build_network
from pcdet.utils import common_utils
os.environ["NCCL_P2P_DISABLE"] = "1"
def parse_config():
parser = argparse.ArgumentParser(description='arg parser')
parser.add_argument('--cfg_file', type=str, default=None, help='specify the config for training')
parser.add_argument('--batch_size', type=int, default=None, required=False, help='batch size for training')
parser.add_argument('--workers', type=int, default=4, help='number of workers for dataloader')
parser.add_argument('--extra_tag', type=str, default='default', help='extra tag for this experiment')
parser.add_argument('--ckpt', type=str, default=None, help='checkpoint to start from')
parser.add_argument('--pretrained_model', type=str, default=None, help='pretrained_model')
parser.add_argument('--launcher', choices=['none', 'pytorch', 'slurm'], default='none')
parser.add_argument('--tcp_port', type=int, default=18888, help='tcp port for distrbuted training')
parser.add_argument('--local_rank', type=int, default=None, help='local rank for distributed training')
parser.add_argument('--set', dest='set_cfgs', default=None, nargs=argparse.REMAINDER,
help='set extra config keys if needed')
parser.add_argument('--max_waiting_mins', type=int, default=30, help='max waiting minutes')
parser.add_argument('--start_epoch', type=int, default=0, help='')
parser.add_argument('--eval_tag', type=str, default='default', help='eval tag for this experiment')
parser.add_argument('--eval_all', action='store_true', default=False, help='whether to evaluate all checkpoints')
parser.add_argument('--ckpt_dir', type=str, default=None, help='specify a ckpt directory to be evaluated if needed')
parser.add_argument('--save_to_file', action='store_true', default=False, help='')
parser.add_argument('--infer_time', action='store_true', default=False, help='calculate inference latency')
args = parser.parse_args()
# args.cfg_file = "cfgs/custom_av/centerpoint_pillar_1x_long_epoch.yaml"
# args.batch_size = 1
# args.workers = 1
# args.ckpt = "../output/custom_av/centerpoint_pillar_1x_long_epoch/default/ckpt/checkpoint_epoch_10.pth"
cfg_from_yaml_file(args.cfg_file, cfg)
cfg.TAG = Path(args.cfg_file).stem
cfg.EXP_GROUP_PATH = '/'.join(args.cfg_file.split('/')[1:-1]) # remove 'cfgs' and 'xxxx.yaml'
np.random.seed(1024)
if args.set_cfgs is not None:
cfg_from_list(args.set_cfgs, cfg)
return args, cfg
def eval_single_ckpt(model, test_loader, args, eval_output_dir, logger, epoch_id, dist_test=False):
# load checkpoint
model.load_params_from_file(filename=args.ckpt, logger=logger, to_cpu=dist_test,
pre_trained_path=args.pretrained_model)
model.cuda()
# start evaluation
eval_utils.eval_one_epoch(
cfg, args, model, test_loader, epoch_id, logger, dist_test=dist_test,
result_dir=eval_output_dir
)
def get_no_evaluated_ckpt(ckpt_dir, ckpt_record_file, args):
ckpt_list = glob.glob(os.path.join(ckpt_dir, '*checkpoint_epoch_*.pth'))
ckpt_list.sort(key=os.path.getmtime)
evaluated_ckpt_list = [float(x.strip()) for x in open(ckpt_record_file, 'r').readlines()]
for cur_ckpt in ckpt_list:
num_list = re.findall('checkpoint_epoch_(.*).pth', cur_ckpt)
if num_list.__len__() == 0:
continue
epoch_id = num_list[-1]
if 'optim' in epoch_id:
continue
if float(epoch_id) not in evaluated_ckpt_list and int(float(epoch_id)) >= args.start_epoch:
return epoch_id, cur_ckpt
return -1, None
def repeat_eval_ckpt(model, test_loader, args, eval_output_dir, logger, ckpt_dir, dist_test=False):
# evaluated ckpt record
ckpt_record_file = eval_output_dir / ('eval_list_%s.txt' % cfg.DATA_CONFIG.DATA_SPLIT['test'])
with open(ckpt_record_file, 'a'):
pass
# tensorboard log
if cfg.LOCAL_RANK == 0:
tb_log = SummaryWriter(log_dir=str(eval_output_dir / ('tensorboard_%s' % cfg.DATA_CONFIG.DATA_SPLIT['test'])))
total_time = 0
first_eval = True
while True:
# check whether there is checkpoint which is not evaluated
cur_epoch_id, cur_ckpt = get_no_evaluated_ckpt(ckpt_dir, ckpt_record_file, args)
if cur_epoch_id == -1 or int(float(cur_epoch_id)) < args.start_epoch:
wait_second = 30
if cfg.LOCAL_RANK == 0:
print('Wait %s seconds for next check (progress: %.1f / %d minutes): %s \r'
% (wait_second, total_time * 1.0 / 60, args.max_waiting_mins, ckpt_dir), end='', flush=True)
time.sleep(wait_second)
total_time += 30
if total_time > args.max_waiting_mins * 60 and (first_eval is False):
break
continue
total_time = 0
first_eval = False
model.load_params_from_file(filename=cur_ckpt, logger=logger, to_cpu=dist_test)
model.cuda()
# start evaluation
cur_result_dir = eval_output_dir / ('epoch_%s' % cur_epoch_id) / cfg.DATA_CONFIG.DATA_SPLIT['test']
tb_dict = eval_utils.eval_one_epoch(
cfg, args, model, test_loader, cur_epoch_id, logger, dist_test=dist_test,
result_dir=cur_result_dir
)
if cfg.LOCAL_RANK == 0:
for key, val in tb_dict.items():
tb_log.add_scalar(key, val, cur_epoch_id)
# record this epoch which has been evaluated
with open(ckpt_record_file, 'a') as f:
print('%s' % cur_epoch_id, file=f)
logger.info('Epoch %s has been evaluated' % cur_epoch_id)
def main():
args, cfg = parse_config()
if args.infer_time:
os.environ['CUDA_LAUNCH_BLOCKING'] = '1'
if args.launcher == 'none':
dist_test = False
total_gpus = 1
else:
if args.local_rank is None:
args.local_rank = int(os.environ.get('LOCAL_RANK', '0'))
total_gpus, cfg.LOCAL_RANK = getattr(common_utils, 'init_dist_%s' % args.launcher)(
args.tcp_port, args.local_rank, backend='nccl'
)
dist_test = True
if args.batch_size is None:
args.batch_size = cfg.OPTIMIZATION.BATCH_SIZE_PER_GPU
else:
assert args.batch_size % total_gpus == 0, 'Batch size should match the number of gpus'
args.batch_size = args.batch_size // total_gpus
output_dir = cfg.ROOT_DIR / 'output' / cfg.EXP_GROUP_PATH / cfg.TAG / args.extra_tag
output_dir.mkdir(parents=True, exist_ok=True)
eval_output_dir = output_dir / 'eval'
if not args.eval_all:
num_list = re.findall(r'\d+', args.ckpt) if args.ckpt is not None else []
epoch_id = num_list[-1] if num_list.__len__() > 0 else 'no_number'
eval_output_dir = eval_output_dir / ('epoch_%s' % epoch_id) / cfg.DATA_CONFIG.DATA_SPLIT['test']
else:
eval_output_dir = eval_output_dir / 'eval_all_default'
if args.eval_tag is not None:
eval_output_dir = eval_output_dir / args.eval_tag
eval_output_dir.mkdir(parents=True, exist_ok=True)
log_file = eval_output_dir / ('log_eval_%s.txt' % datetime.datetime.now().strftime('%Y%m%d-%H%M%S'))
logger = common_utils.create_logger(log_file, rank=cfg.LOCAL_RANK)
# log to file
logger.info('**********************Start logging**********************')
gpu_list = os.environ['CUDA_VISIBLE_DEVICES'] if 'CUDA_VISIBLE_DEVICES' in os.environ.keys() else 'ALL'
logger.info('CUDA_VISIBLE_DEVICES=%s' % gpu_list)
if dist_test:
logger.info('total_batch_size: %d' % (total_gpus * args.batch_size))
for key, val in vars(args).items():
logger.info('{:16} {}'.format(key, val))
log_config_to_file(cfg, logger=logger)
ckpt_dir = args.ckpt_dir if args.ckpt_dir is not None else output_dir / 'ckpt'
test_set, test_loader, sampler = build_dataloader(
dataset_cfg=cfg.DATA_CONFIG,
class_names=cfg.CLASS_NAMES,
batch_size=args.batch_size,
dist=dist_test, workers=args.workers, logger=logger, training=False
)
model = build_network(model_cfg=cfg.MODEL, num_class=len(cfg.CLASS_NAMES), dataset=test_set)
with torch.no_grad():
if args.eval_all:
repeat_eval_ckpt(model, test_loader, args, eval_output_dir, logger, ckpt_dir, dist_test=dist_test)
else:
eval_single_ckpt(model, test_loader, args, eval_output_dir, logger, epoch_id, dist_test=dist_test)
if __name__ == '__main__':
main()
훈련 증식(1)
centerpoint_pillar_1x_long_epoch.ymal
DENSE_HEAD:
NAME: CenterHead
CLASS_AGNOSTIC: False
CLASS_NAMES_EACH_HEAD: [
['Vehicle', 'Pedestrian', 'Cyclist']
]
SHARED_CONV_CHANNEL: 64
USE_BIAS_BEFORE_NORM: True
NUM_HM_CONV: 2 #2
SEPARATE_HEAD_CFG:
HEAD_ORDER: ['center', 'center_z', 'dim', 'rot']
HEAD_DICT: {
'center': {'out_channels': 2, 'num_conv': 2},
'center_z': {'out_channels': 1, 'num_conv': 2},
'dim': {'out_channels': 3, 'num_conv': 2},
'rot': {'out_channels': 2, 'num_conv': 2},
}
TARGET_ASSIGNER_CONFIG:
FEATURE_MAP_STRIDE: 1
NUM_MAX_OBJS: 500
GAUSSIAN_OVERLAP: 0.1
MIN_RADIUS: 1 # 2->1
LOSS_CONFIG:
LOSS_WEIGHTS: {
'cls_weight': 2.0,
'loc_weight': 2.3,
'code_weights': [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
}
POST_PROCESSING:
SCORE_THRESH: 0.15
POST_CENTER_LIMIT_RANGE: [-80, -80, -10.0, 80, 80, 10.0]
MAX_OBJ_PER_SAMPLE: 500
NMS_CONFIG:
NMS_TYPE: nms_gpu
NMS_THRESH: 0.6 # 0.7 -> 0.6
NMS_PRE_MAXSIZE: 4096
NMS_POST_MAXSIZE: 500
custom_av_dataset.ymal
DATA_AUGMENTOR:
DISABLE_AUG_LIST: ['placeholder']
AUG_CONFIG_LIST:
- NAME: gt_sampling
USE_ROAD_PLANE: False
DB_INFO_PATH:
- custom_av_dbinfos_train.pkl
PREPARE: {
filter_by_min_points: ['Vehicle:5', 'Pedestrian:5', 'Cyclist:5'],
}
SAMPLE_GROUPS: ['Vehicle:2', 'Pedestrian:14', 'Cyclist:10']
NUM_POINT_FEATURES: 4
DATABASE_WITH_FAKELIDAR: False
REMOVE_EXTRA_WIDTH: [0.0, 0.0, 0.0]
LIMIT_WHOLE_SCENE: True
- NAME: random_world_flip
ALONG_AXIS_LIST: ['x', 'y']
- NAME: random_world_rotation
WORLD_ROT_ANGLE: [-0.78539816, 0.78539816]
- NAME: random_world_scaling
WORLD_SCALE_RANGE: [0.95, 1.05]
- NAME: random_world_translation #translation 증강 추가
NOISE_TRANSLATE_STD: [0.2, 0.2, 0.2]
개선방안 1
NMS_THRESH: 0.6 # 0.7 -> 0.6
NMS_THRESH 0.7 -> 0.6
임계값을 낮춤으로서 기준을 좀 완화시킴
LOSS_CONFIG:
LOSS_WEIGHTS: {
'cls_weight': 2.0,
'loc_weight': 2.3,
'code_weights': [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
}
cls_weight 1.0 -> 2.0
loc_weight 2.0 -> 2.3
손실에 대한 가중치 값 변경, 각 클래스가 불균형이기 때문에 각각 주는 형식으로 바꿀 예정임
MIN_RADIUS: 1 # 2->1
Min_radius 2 -> 1
계산된 반경이 너무 작지 않도록 설정하는 최소 반경임. 작은 박스(보행자, 자전거) 반경이 작을 확률이 높음 이를 최소한의 너비를 보장하는 역할
- NAME: random_world_translation
NOISE_TRANSLATE_STD: [0.2, 0.2, 0.2]
증강 random_world_translation 추가
이동변환에 관한 증강, 전역 증강에 같이 사용하면 효과가 좋았음, voxel-rcnn에서.
SAMPLE_GROUPS: ['Vehicle:2', 'Pedestrian:14', 'Cyclist:10']
'Vehicle:2', 'Pedestrian:14', 'Cyclist:10'
Vehicle은 어짜피 잘찾고
Cyclist 와 Pedestrian이 더 찾기 어렵기 때문에 이 두개에 대한 비율을 높임
2024 검증
2025 검증
훈련 증식(2)
centerpoint_pillar_1x_long_epoch.ymal
DENSE_HEAD:
NAME: CenterHead
CLASS_AGNOSTIC: False
CLASS_NAMES_EACH_HEAD: [
['Vehicle', 'Pedestrian', 'Cyclist']
]
SHARED_CONV_CHANNEL: 64
USE_BIAS_BEFORE_NORM: True
NUM_HM_CONV: 2 #2
SEPARATE_HEAD_CFG:
HEAD_ORDER: ['center', 'center_z', 'dim', 'rot']
HEAD_DICT: {
'center': {'out_channels': 2, 'num_conv': 2},
'center_z': {'out_channels': 1, 'num_conv': 2},
'dim': {'out_channels': 3, 'num_conv': 2},
'rot': {'out_channels': 2, 'num_conv': 2},
}
TARGET_ASSIGNER_CONFIG:
FEATURE_MAP_STRIDE: 1
NUM_MAX_OBJS: 500
GAUSSIAN_OVERLAP: 0.2 # 0.1 -> 0.2 / 예측값이 실제 중심보다 떨어져있는걸 살짝 엄격하게 보겠다.
MIN_RADIUS: 1 # 2->1
LOSS_CONFIG:
LOSS_WEIGHTS: {
'cls_weight': 2.0, # weight 수정
'loc_weight': 2.3,
'code_weights': [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
}
POST_PROCESSING:
SCORE_THRESH: 0.15
POST_CENTER_LIMIT_RANGE: [-80, -80, -10.0, 80, 80, 10.0]
MAX_OBJ_PER_SAMPLE: 500
NMS_CONFIG:
NMS_TYPE: nms_gpu
NMS_THRESH: 0.6 # 0.7
NMS_PRE_MAXSIZE: 4096
NMS_POST_MAXSIZE: 500
POST_PROCESSING:
RECALL_THRESH_LIST: [0.3, 0.5, 0.7]
EVAL_METRIC: waymo
OPTIMIZATION:
BATCH_SIZE_PER_GPU: 1
NUM_EPOCHS: 100
OPTIMIZER: adam_onecycle
LR: 0.0015 #0.003
WEIGHT_DECAY: 0.01
MOMENTUM: 0.9
MOMS: [0.95, 0.85]
PCT_START: 0.4
DIV_FACTOR: 10
DECAY_STEP_LIST: [35, 45]
LR_DECAY: 0.1
LR_CLIP: 0.0000001
LR_WARMUP: False
WARMUP_EPOCH: 1
GRAD_NORM_CLIP: 10
custom_av_dataset.yaml
- NAME: transform_points_to_voxels
VOXEL_SIZE: [0.08, 0.08, 0.15] #0.1,0.1,0.15 -> 0.08,0.08,0.15로 추가
MAX_POINTS_PER_VOXEL: 5
MAX_NUMBER_OF_VOXELS: {
'train': 150000,
'test': 150000
}
개선방법 2
LR: 0.0015 #0.003 /학습을 좀더 안정적으로 하기위해 추가
VOXEL_SIZE: [0.08, 0.08, 0.15] #0.1,0.1,0.15 -> 0.08,0.08,0.15로 추가
GAUSSIAN_OVERLAP: 0.2 # 0.1 -> 0.2 / 예측값이 실제 중심보다 떨어져있는걸 살짝 엄격하게 보겠다.
2024 검증
2025 검증
성능지표
2024
| 성능지표 (2024) | BaseLine | 개선방안 1 | 개선방안 2 |
| VEHICLE_AP/L1 | 0.8911 | 0.5903 | 0.6356 |
| VEHICLE_AP/L2 | 0.8801 | 0.5814 | 0.6260 |
| PEDESTRIAN_AP/L1 | 0.9023 | 0.6213 | 0.6740 |
| PEDESTRIAN_AP/L2 | 0.8920 | 0.6087 | 0.6605 |
| CYCLIST_AP/L1 | 0.8962 | 0.6266 | 0.6727 |
| CYCLIST_AP/L2 | 0.8829 | 0.6165 | 0.6618 |
2025
| 성능지표 (2025) | BaseLine | 개선방안 1 | 개선방안 2 |
| VEHICLE_AP/L1 | 0.8611 | 0.4722 | 0.5301 |
| VEHICLE_AP/L2 | 0.8433 | 0.4606 | 0.5250 |
| PEDESTRIAN_AP/L1 | 0.8396 | 0.4366 | 0.5062 |
| PEDESTRIAN_AP/L2 | 0.8185 | 0.4204 | 0.4876 |
| CYCLIST_AP/L1 | 0.8784 | 0.6036 | 0.6710 |
| CYCLIST_AP/L2 | 0.8600 | 0.5897 | 0.6557 |
성능을 떨어뜨리는 요소라 생각한 부분
SAMPLE_GROUPS: ['Vehicle:2', 'Pedestrian:14', 'Cyclist:10']
Vehicle이 찾기 쉽긴 하더라도 값을 너무 과감하게 낮게 하여서 오히려더 성능을 떨어뜨린것 같다.
NMS_THRESH: 0.6 # 0.7 -> 0.6
NMS에 대한 값을 좀더 엄격하게 보기위해 낮췄는데 이것 또한 성능을 낮춘 요소가 된것같다.
MIN_RADIUS: 1 # 2->1
cyclIst나 pedestrian을 조금더 잘 찾기 위해 값을 낮췄는데 2라는 값이 가장 적당한 값이 였던것 같다.
VOXEL_SIZE: [0.08, 0.08, 0.15] #0.1,0.1,0.15
모델 파일에서 복셀사이즈를 바꾸기 때문에 의미가 없음.
- NAME: transform_points_to_voxels
VOXEL_SIZE: [ 0.25, 0.25, 8.0 ]
GAUSSIAN_OVERLAP: 0.2 # 0.1 -> 0.2
예측값을 실제 중심보다 떨어져있는 것을 조금더 엄격하게 보기 위해서 값을 높혔는데 이로 인해서 성능이 떨어진 것같다
한꺼번에 많은 증식을 넣은 것이 성능을 저하시키는 요소가 되었던것 같다.
++추가
훈련 증식(3)
성능을 떨어뜨리는 요소라 생각한 부분을 고쳐서 다시 학습 중입니다.
centerpoint_pillar_1x_long_epoch.ymal
TARGET_ASSIGNER_CONFIG:
FEATURE_MAP_STRIDE: 1
NUM_MAX_OBJS: 500
GAUSSIAN_OVERLAP: 0.1
MIN_RADIUS: 2
LOSS_CONFIG:
LOSS_WEIGHTS: {
'cls_weight': 2.0, #1.0
'loc_weight': 2.3, # 2.0
'code_weights': [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
}
CLASS_WEIGHT : [1.0 , 3.5 , 2.5]
POST_PROCESSING:
SCORE_THRESH: 0.1
POST_CENTER_LIMIT_RANGE: [-80, -80, -10.0, 80, 80, 10.0]
MAX_OBJ_PER_SAMPLE: 500
NMS_CONFIG:
NMS_TYPE: nms_gpu
NMS_THRESH: 0.7
NMS_PRE_MAXSIZE: 4096
NMS_POST_MAXSIZE: 500
POST_PROCESSING:
RECALL_THRESH_LIST: [0.3, 0.5, 0.7]
EVAL_METRIC: waymo
OPTIMIZATION:
BATCH_SIZE_PER_GPU: 1
NUM_EPOCHS: 100
OPTIMIZER: adam_onecycle
LR: 0.0015 #0.003 -> 0.0015
WEIGHT_DECAY: 0.01
MOMENTUM: 0.9
MOMS: [0.95, 0.85]
PCT_START: 0.4
DIV_FACTOR: 10
DECAY_STEP_LIST: [35, 45]
LR_DECAY: 0.1
LR_CLIP: 0.0000001
LR_WARMUP: False
WARMUP_EPOCH: 1
GRAD_NORM_CLIP: 10
custom_av_dataset.yaml
DATA_AUGMENTOR:
DISABLE_AUG_LIST: ['placeholder']
AUG_CONFIG_LIST:
- NAME: gt_sampling
USE_ROAD_PLANE: False
DB_INFO_PATH:
- custom_av_dbinfos_train.pkl
PREPARE: {
filter_by_min_points: ['Vehicle:5', 'Pedestrian:5', 'Cyclist:5'],
}
SAMPLE_GROUPS: ['Vehicle:2', 'Pedestrian:14', 'Cyclist:10']
NUM_POINT_FEATURES: 4
DATABASE_WITH_FAKELIDAR: False
REMOVE_EXTRA_WIDTH: [0.0, 0.0, 0.0]
LIMIT_WHOLE_SCENE: True
- NAME: random_world_flip
ALONG_AXIS_LIST: ['x', 'y']
- NAME: random_world_rotation
WORLD_ROT_ANGLE: [-0.78539816, 0.78539816]
- NAME: random_world_scaling
WORLD_SCALE_RANGE: [0.95, 1.05]
- NAME: random_world_translation #
NOISE_TRANSLATE_STD: [0.2, 0.2, 0.2]
DATA_PROCESSOR:
- NAME: mask_points_and_boxes_outside_range
REMOVE_OUTSIDE_BOXES: True
- NAME: shuffle_points
SHUFFLE_ENABLED: {
'train': True,
'test': True
}
- NAME: transform_points_to_voxels
VOXEL_SIZE: [0.1, 0.1, 0.15] #0.1,0.1,0.15 -> 0.08,0.08,0.15
MAX_POINTS_PER_VOXEL: 5
MAX_NUMBER_OF_VOXELS: {
'train': 150000,
'test': 150000
}
center_head.py 에 추가
def get_loss(self):
pred_dicts = self.forward_ret_dict['pred_dicts']
target_dicts = self.forward_ret_dict['target_dicts']
tb_dict = {}
loss = 0
# 클래스별 가중치 불러오기 (없으면 None)
class_weights = self.model_cfg.LOSS_CONFIG.LOSS_WEIGHTS.get('class_weights', None)
for idx, pred_dict in enumerate(pred_dicts):
pred_dict['hm'] = self.sigmoid(pred_dict['hm'])
# --- 클래스별 weight 적용 ---
if class_weights is not None:
cw = pred_dict['hm'].new_tensor(class_weights).view(1, -1, 1, 1) # (1, C, 1, 1)
hm_loss_per_class = self.hm_loss_func(pred_dict['hm'], target_dicts['heatmaps'][idx])
hm_loss = (hm_loss_per_class * cw).mean()
hm_loss *= self.model_cfg.LOSS_CONFIG.LOSS_WEIGHTS['cls_weight']
else:
hm_loss = self.hm_loss_func(pred_dict['hm'], target_dicts['heatmaps'][idx])
hm_loss *= self.model_cfg.LOSS_CONFIG.LOSS_WEIGHTS['cls_weight']
target_boxes = target_dicts['target_boxes'][idx]
pred_boxes = torch.cat(
[pred_dict[head_name] for head_name in self.separate_head_cfg.HEAD_ORDER], dim=1
)
reg_loss = self.reg_loss_func(
pred_boxes, target_dicts['masks'][idx], target_dicts['inds'][idx], target_boxes
)
loc_loss = (reg_loss * reg_loss.new_tensor(self.model_cfg.LOSS_CONFIG.LOSS_WEIGHTS['code_weights'])).sum()
loc_loss = loc_loss * self.model_cfg.LOSS_CONFIG.LOSS_WEIGHTS['loc_weight']
loss += hm_loss + loc_loss
tb_dict['hm_loss_head_%d' % idx] = hm_loss.item()
tb_dict['loc_loss_head_%d' % idx] = loc_loss.item()
# --- IoU 관련 loss ---
if 'iou' in pred_dict or self.model_cfg.get('IOU_REG_LOSS', False):
batch_box_preds = centernet_utils.decode_bbox_from_pred_dicts(
pred_dict=pred_dict,
point_cloud_range=self.point_cloud_range,
voxel_size=self.voxel_size,
feature_map_stride=self.feature_map_stride
) # (B, H, W, 7 or 9)
if 'iou' in pred_dict:
batch_box_preds_for_iou = batch_box_preds.permute(0, 3, 1, 2) # (B, 7 or 9, H, W)
iou_loss = loss_utils.calculate_iou_loss_centerhead(
iou_preds=pred_dict['iou'],
batch_box_preds=batch_box_preds_for_iou.clone().detach(),
mask=target_dicts['masks'][idx],
ind=target_dicts['inds'][idx],
gt_boxes=target_dicts['target_boxes_src'][idx]
)
loss += iou_loss
tb_dict['iou_loss_head_%d' % idx] = iou_loss.item()
if self.model_cfg.get('IOU_REG_LOSS', False):
iou_reg_loss = loss_utils.calculate_iou_reg_loss_centerhead(
batch_box_preds=batch_box_preds_for_iou,
mask=target_dicts['masks'][idx],
ind=target_dicts['inds'][idx],
gt_boxes=target_dicts['target_boxes_src'][idx]
)
if target_dicts['masks'][idx].sum().item() != 0:
iou_reg_loss = iou_reg_loss * self.model_cfg.LOSS_CONFIG.LOSS_WEIGHTS['loc_weight']
loss += iou_reg_loss
tb_dict['iou_reg_loss_head_%d' % idx] = iou_reg_loss.item()
else:
loss += (batch_box_preds_for_iou * 0.).sum()
tb_dict['iou_reg_loss_head_%d' % idx] = (batch_box_preds_for_iou * 0.).sum()
tb_dict['rpn_loss'] = loss.item()
return loss, tb_dict
def get_loss(self):
pred_dicts = self.forward_ret_dict['pred_dicts']
target_dicts = self.forward_ret_dict['target_dicts']
tb_dict = {}
loss = 0
# 클래스별 가중치 불러오기 (없으면 None)
class_weights = self.model_cfg.LOSS_CONFIG.LOSS_WEIGHTS.get('class_weights', None)
for idx, pred_dict in enumerate(pred_dicts):
pred_dict['hm'] = self.sigmoid(pred_dict['hm'])
# --- 클래스별 weight 적용 ---
if class_weights is not None:
cw = pred_dict['hm'].new_tensor(class_weights).view(1, -1, 1, 1) # (1, C, 1, 1)
hm_loss_per_class = self.hm_loss_func(pred_dict['hm'], target_dicts['heatmaps'][idx])
hm_loss = (hm_loss_per_class * cw).mean()
hm_loss *= self.model_cfg.LOSS_CONFIG.LOSS_WEIGHTS['cls_weight']
else:
hm_loss = self.hm_loss_func(pred_dict['hm'], target_dicts['heatmaps'][idx])
hm_loss *= self.model_cfg.LOSS_CONFIG.LOSS_WEIGHTS['cls_weight']
target_boxes = target_dicts['target_boxes'][idx]
pred_boxes = torch.cat(
[pred_dict[head_name] for head_name in self.separate_head_cfg.HEAD_ORDER], dim=1
)
reg_loss = self.reg_loss_func(
pred_boxes, target_dicts['masks'][idx], target_dicts['inds'][idx], target_boxes
)
loc_loss = (reg_loss * reg_loss.new_tensor(self.model_cfg.LOSS_CONFIG.LOSS_WEIGHTS['code_weights'])).sum()
loc_loss = loc_loss * self.model_cfg.LOSS_CONFIG.LOSS_WEIGHTS['loc_weight']
loss += hm_loss + loc_loss
tb_dict['hm_loss_head_%d' % idx] = hm_loss.item()
tb_dict['loc_loss_head_%d' % idx] = loc_loss.item()
# --- IoU 관련 loss ---
if 'iou' in pred_dict or self.model_cfg.get('IOU_REG_LOSS', False):
batch_box_preds = centernet_utils.decode_bbox_from_pred_dicts(
pred_dict=pred_dict,
point_cloud_range=self.point_cloud_range,
voxel_size=self.voxel_size,
feature_map_stride=self.feature_map_stride
) # (B, H, W, 7 or 9)
if 'iou' in pred_dict:
batch_box_preds_for_iou = batch_box_preds.permute(0, 3, 1, 2) # (B, 7 or 9, H, W)
iou_loss = loss_utils.calculate_iou_loss_centerhead(
iou_preds=pred_dict['iou'],
batch_box_preds=batch_box_preds_for_iou.clone().detach(),
mask=target_dicts['masks'][idx],
ind=target_dicts['inds'][idx],
gt_boxes=target_dicts['target_boxes_src'][idx]
)
loss += iou_loss
tb_dict['iou_loss_head_%d' % idx] = iou_loss.item()
if self.model_cfg.get('IOU_REG_LOSS', False):
iou_reg_loss = loss_utils.calculate_iou_reg_loss_centerhead(
batch_box_preds=batch_box_preds_for_iou,
mask=target_dicts['masks'][idx],
ind=target_dicts['inds'][idx],
gt_boxes=target_dicts['target_boxes_src'][idx]
)
if target_dicts['masks'][idx].sum().item() != 0:
iou_reg_loss = iou_reg_loss * self.model_cfg.LOSS_CONFIG.LOSS_WEIGHTS['loc_weight']
loss += iou_reg_loss
tb_dict['iou_reg_loss_head_%d' % idx] = iou_reg_loss.item()
else:
loss += (batch_box_preds_for_iou * 0.).sum()
tb_dict['iou_reg_loss_head_%d' % idx] = (batch_box_preds_for_iou * 0.).sum()
tb_dict['rpn_loss'] = loss.item()
return loss, tb_dict
개선방법 3
LOSS_CONFIG:
LOSS_WEIGHTS: {
'cls_weight': 2.0, #1.0
'loc_weight': 2.3, # 2.0
'code_weights': [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
}
CLASS_WEIGHT : [1.0 , 3.5 , 2.5]
CLASS_WEIGHT 를 사용해서 클래스에 대한 가중치를 다르게줌
2025 검증
성능지표
| 성능지표 (2025) | BaseLine | centerpoint_pillar_1x_long_epoch(88epoch) 학습중 |
| VEHICLE_AP/L1 | 0.8611 | 0.7676 |
| VEHICLE_AP/L2 | 0.8433 | 0.7494 |
| PEDESTRIAN_AP/L1 | 0.8396 | 0.7104 |
| PEDESTRIAN_AP/L2 | 0.8185 | 0.6872 |
| CYCLIST_AP/L1 | 0.8784 | 0.7884 |
| CYCLIST_AP/L2 | 0.8600 | 0.7709 |

첫댓글 베이스라인은 어떤 조건으로 훈련한건지?
개선방법을 적용해도 베이스라인보다 왜 낮은지?
베이스라인은 2025 dataset으로 128ch+64ch, 증강 기법으로는 world flip, world rotation, world scaling으로 기본적인 3가지가 들어갔습니다.
나머지 설정은 Waymo dataset에 대해 훈련한 model 설정과 똑같은데 학습할 POINT CLOUD RANGE와 VOXEL SIZE만 다릅니다.
개선방법을 적용해도 낮은 이유로는 class 별로 가중치를 주는 방법을 택했는데 이게 80ckpt에서 다시 가중치를 높여서 훈련하다보니 loss도 올라가고 정확도도 낮아질수 있다고 생각합니다.
따라서 loss 수렴까지 학습을 해보던가 아니면 model에 대해서 더 공부해서 voxel_size를 변경하는 등 맞는 값을 좀 찾아보도록 할 예정입니다.