|
|
MODEL : PV-RCNN++
DATASET : 2025 자율주행 AI 챌린지 dataset (128ch+64ch)
TEST CODE : 대회의 baseline code에서 val.py(2025)를 사용해서 확인
TEST DATASET : 2024 dataset에서 train dataset의 64ch data만 뽑아서 사용
개선방법1
downsampling 증강 적용, 클래스 별 가중치 적용, LR + GT-Sampling 조정
pv_rcnn_plusplus_custom.yaml
TARGET_ASSIGNER_CONFIG:
FEATURE_MAP_STRIDE: 8
NUM_MAX_OBJS: 500
GAUSSIAN_OVERLAP: 0.1
MIN_RADIUS: 1 # 2
LOSS_CONFIG:
LOSS_WEIGHTS: {
'cls_weight': 1.5, # 1.0
'loc_weight': 2.5, # 2.0
'code_weights': [ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ]
}
CLASS_WEIGHT: [1.0, 2.0, 2.5] # 클래스 별 가중치 적용 / 불균형 해소
MIN_RADIUS: 1 # 2
: 계산된 반경이 너무 작지 않도록 설정하는 최소 반경.
→ 작은 박스가 보통 반경이 다른 객체에 비해 작아서 최소한의 너비를 보장하게 해줌.
'cls_weight': 1.5, # 1.0
'loc_weight': 2.5, # 2.0
: 분류/회귀의 스케일을 재조정.
→ cls는 분류 신뢰도 상승을 위해... 초기 제안 점수를 끌어올려 NMS 전에 더 많이 살아남게 하기 위함
→ loc는 박스 정밀도 상승을 위해... IoU 안정에 도움됨
CLASS_WEIGHT: [1.0, 2.0, 2.5] # 클래스 별 가중치 적용 / 불균형 해소
: 소수 클래스에 가중을 더 줘서 불균형을 완화하기 위함임.
→ 현재 확인된 데이터 불균형으로는 Cyclist가 가장 적기 때문에 높게 줬음
→ Center_Head를 수정해야 위 값을 조정할 수 있음
center_head.py
클래스 별 가중치 적용하기 위해 def get_loss부분을 수정했음
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
pv_rcnn_plusplus_custom.yaml
OPTIMIZATION:
BATCH_SIZE_PER_GPU: 2
NUM_EPOCHS: 80 # 30
OPTIMIZER: adam_onecycle
LR: 0.003 # 0.01
WEIGHT_DECAY: 0.001
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: True # Flase -> 안정화를 위함
WARMUP_EPOCH: 2 # 1
GRAD_NORM_CLIP: 10
LR: 0.003 # 0.01
: 초기 LR을 낮춰서 안정적으로 학습하기 위함... 에퍽을 길게 잡을거면 학습률을 낮춰도 될 거 같다고 판단했음
custom_av_dataset_pv.yaml
DATA_AUGMENTOR:
DISABLE_AUG_LIST: ['placeholder']
AUG_CONFIG_LIST:
- NAME: random_downsample
THRESHOLD_N: 150000
METHOD: zbin
TARGET_CHANNELS: 64
ASSUMED_BASE_CHANNELS: 128
- 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:15', 'Pedestrian:10', 'Cyclist:10']
SAMPLE_GROUPS: ['Vehicle:5', 'Pedestrian:15', '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]
SAMPLE_GROUPS: ['Vehicle:5', 'Pedestrian:15', 'Cyclist:10']
: GT-Sampling으로 보행자 점수가 가장 적으니까 이를 늘렸음
- NAME: random_downsample
THRESHOLD_N: 150000
METHOD: zbin
TARGET_CHANNELS: 64
ASSUMED_BASE_CHANNELS: 128
: 증강 추가
→ 포인트 수가 150000개 이상인 데이터에 대해서 랜덤으로 포인트 수를 줄임
→ 테스트 데이터셋이 64ch 환경이어서 위 증강을 통해 테스트에 더 좋은 점수 받기 위함을 목표로 넣음
augmentor_utils.py에 다운샘플링 함수 정의 후 yaml에서 사용하면 됨
def random_downsample(data_dict=None, config=None):
"""
Downsample ONLY when the number of points is >= THRESHOLD_N.
Args:
data_dict: dict containing 'points' (NxC) array
config: dict with keys
- THRESHOLD_N or THRSHOLD_N: int, apply downsampling if N_points >= this (default: 150000)
- TARGET_CHANNELS: int, e.g., 64 (default: 64)
- METHOD: str, 'zbin' or 'uniform' (default: 'zbin')
- ASSUMED_BASE_CHANNELS: int, baseline for ratio (default: 128)
Returns:
data_dict with possibly modified 'points'
"""
if data_dict is None or 'points' not in data_dict:
return data_dict
if config is None:
config = {}
points = data_dict['points']
n_points = points.shape[0]
# Accept both spellings to match YAML ('THRSHOLD_N' typo)
threshold_n = int(config.get('THRESHOLD_N',
config.get('THRSHOLD_N', 150000)))
target_channels = int(config.get('TARGET_CHANNELS', 64))
method = config.get('METHOD', 'zbin')
base_channels = int(config.get('ASSUMED_BASE_CHANNELS', 128))
# 조건: 포인트 수가 threshold "이상"일 때만 다운샘플링
if n_points < threshold_n:
return data_dict # 그대로 반환
# 공통 비율 (목표 채널 / 기준 채널), 최대 1.0
keep_ratio = min(1.0, max(0.0, float(target_channels) / float(base_channels)))
if keep_ratio <= 0.0:
# 안전장치: 최소 1포인트는 남기기
keep_idx = np.random.choice(n_points, 1, replace=False)
data_dict['points'] = points[keep_idx]
return data_dict
if method == 'zbin':
# z 축을 기준으로 수직 구간을 나눠 각 bin에서 동일 비율로 샘플링
z_vals = points[:, 2] # assume [x, y, z, intensity, ...]
# bin 개수는 target_channels와 동일하게 맞춤
bins = np.linspace(z_vals.min(), z_vals.max(), target_channels + 1)
keep_idx_list = []
for i in range(target_channels):
idx = np.where((z_vals >= bins[i]) & (z_vals < bins[i + 1]))[0]
if idx.size > 0:
k = max(1, int(np.ceil(idx.size * keep_ratio)))
k = min(k, idx.size)
choose = np.random.choice(idx, k, replace=False)
keep_idx_list.append(choose)
if keep_idx_list:
keep_idx = np.concatenate(keep_idx_list)
points = points[keep_idx]
# 만약 어떤 이유로 하나도 못 뽑았다면 최소 한 점은 보장
if points.shape[0] == 0 and n_points > 0:
keep_idx = np.random.choice(n_points, 1, replace=False)
points = data_dict['points'][keep_idx]
elif method == 'uniform':
# 전체에서 균일 비율로 샘플링
num_keep = max(1, int(points.shape[0] * keep_ratio))
num_keep = min(num_keep, points.shape[0])
keep_idx = np.random.choice(points.shape[0], num_keep, replace=False)
points = points[keep_idx]
else:
# 알 수 없는 방법이면 변경 없이 반환
return data_dict
data_dict['points'] = points
return data_dict
총 학습은 80 epoch 돌렸음
loss는 계속 떨어지지만 수렴하는 속도가 더디고 개선방법에 대해 수정이 필요할 거 같아서 멈추고 성능 확인 진행함.
보행자에 관해 점수가 매우 낮은 것을 확인
불균형에 대해 더 신경쓰고 작은 객체에 대해 더 잘 학습할 수 있도록 하는 장치가 필요하다고 판단함
GT-Sampling은 보행자를 못 찾는다고 올려놓고 가중치는 Cyclist보다 더 적게 잡은 것이 좋지 않은 판단인 듯
또한 차량에 관한 것도 베이스 모델보다 낮기 때문에 이를 개선할 방법을 모색해야 한다고 생각했음.
개선방법2
개선방법1 + 클래스 별 가중치, NMS 임계값, MIN_RADIUS, GRID_SIZE 조정 + translation 증강 추가
pv_rcnn_plusplus_custom.yaml
LOSS_CONFIG:
LOSS_WEIGHTS: {
'cls_weight': 2.0, # 1.0
'loc_weight': 2.0, # 2.0
'code_weights': [ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ]
}
CLASS_WEIGHT: [1.5, 3.0, 2.0] # 클래스 별 가중치 적용 / 불균형 해소
POST_PROCESSING:
SCORE_THRESH: 0.1
POST_CENTER_LIMIT_RANGE: [ -75.2, -75.2, -2, 75.2, 75.2, 4 ]
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
'cls_weight': 2.0, # 1.0
: 가중치에 관해 더 쎄게 비율을 줌. 초기 후보의 점수 상승
→ NMS 전에 후보가 더 많이 살아남아서 recall 올리는 거를 목표로 함
→ 보행자 점수를 개선하기 위함...
CLASS_WEIGHT: [1.5, 3.0, 2.0] # 클래스 별 가중치 적용 / 불균형 해소
: 따라서 클래스 별로 가중치 주는 것에 대해 보행자를 높임
→ 보행자 recall 높임
NMS_THRESH: 0.6 #0.7
: 헤드 단계에서 겹치는 후보를 덜 지워서 recall 올리는 거 목표
ROI_GRID_POOL:
GRID_SIZE: 8 #6
NAME: VectorPoolAggregationModuleMSG
NUM_GROUPS: 2
LOCAL_AGGREGATION_TYPE: voxel_random_choice
NUM_REDUCED_CHANNELS: 30
NUM_CHANNELS_OF_LOCAL_AGGREGATION: 32
MSG_POST_MLPS: [ 128 ]
GROUP_CFG_0:
NUM_LOCAL_VOXEL: [ 3, 3, 3 ]
MAX_NEIGHBOR_DISTANCE: 0.8
NEIGHBOR_NSAMPLE: 32
POST_MLPS: [ 64, 64 ]
GROUP_CFG_1:
NUM_LOCAL_VOXEL: [ 3, 3, 3 ]
MAX_NEIGHBOR_DISTANCE: 1.6
NEIGHBOR_NSAMPLE: 32
POST_MLPS: [ 64, 64 ]
GRID_SIZE: 8 #6
: RoI 내부 샘플 지점 증가시키기 위함임
→ 연산량이 늘지만 형태/방향에 대해서는 더 잘 포착할 수 있음
custom_av_dataset_pv.yaml 증강 추가
- NAME: random_world_translation # 추가
NOISE_TRANSLATE_STD: [0.2, 0.2, 0.2]
: 이미 증강 utils 부분에 정의가 되어있어서 yaml에 설정만 하면 됨. 이동 변환에 관한 증강
→ 다양한 데이터셋에 대해 잘 학습하도록 설정함
SAMPLE_GROUPS: ['Vehicle:5', 'Pedestrian:15', 'Cyclist:5']
: GT-Sampling 갯수 증가, 보행자에 관한 걸 늘림
총 38 epoch 학습 진행함
수렴도가 이전보다 더 빠르게 진행되고 있지만 Labels에 대해 분석하는 중에 범위 수정을 하면
학습하는 객체가 늘어나서 성능이 더 좋아질 것 같다는 판단에 중단하고 성능 확인함
NMS 조정했던게 성능에 영향을 미친 것 같음
임계값을 낮춰서 검출 수를 늘렸는데 score가 낮아 지표가 낮게 나올 수 있음
따라서 NMS 임계값을 다시 조정하고 확인해보려고 함
개선방법3 // 학습 중에 있음
개선방법1 + LOCAL_AGGREGATION_TYPE, PointCloudRange, VoxelSize 조정
Labels의 범위 분포 확인
현재 기존 범위가 -70~70으로 잡혀있는데 -75~75로 늘려서 학습 진행
→ 이런 식으로 바꾸면 70정도 범위에 걸쳐 학습하지 않았던 데이터들을 학습할 수 있음
-75~75 이상으로 넘어가면 객체가 존재해도 노이즈가 심하기 때문에 이를 제외하고 진행함
| 성능지표 | 베이스라인 | 개선방법1 (ckpt80) | 개선방법2 (ckpt38) | 개선방법3 |
| AP/L1 VEHICLE | 0.89 | 0.85 | 0.83 | ... |
| AP/L2 VEHICLE | 0.88 | 0.83 | 0.81 | ... |
| AP/L1 PEDESTRIAN | 0.90 | 0.67 | 0.60 | ... |
| AP/L2 PEDESTRIAN | 0.89 | 0.65 | 0.58 | ... |
| AP/L1 CYCLIST | 0.89 | 0.81 | 0.75 | ... |
| AP/L2 CYCLIST | 0.88 | 0.79 | 0.73 | ... |

첫댓글 베이스라인성능이 승현이 보고서 값과 다른이유는?
저는 2024년도 데이터셋 -> 64ch만 뽑아서 2025 val.py로 돌린 지표입니다
승현 선배가 베이스라인 성능 복붙하다가 잘못 올리셨다고 해서 지금 수정한다고 합니다
지금까지 사용해서 효과가 있다고 생각되는 방법은 무엇인가요?
클래스 가중치, voxel size 조정은 효과 있다고 생각됩니다