새소식

Navigation

Waypoint 정규화 시, 생각없는 min-max 정규화의 함정

  • -

러닝 기반으로 waypoint를 생성하는 모델을 학습할 때 당연히 나도 처음에는 min-max normalize를 했으나 왜인지 생성되는 경로가 일관되게 방향이 이상한 것을 확인했다. 그런데 문제는 몽땅 이런 건 아니고 방향이 정상적으로 나오는 샘플들도 

있기는 해서 의문이었다. 
그런데 샘플들을 공통적으로 살펴보니 Goal point가 1사분면에 있을 때만 멀쩡 경로로 나오는 것 같았다. 그러니까, 경로의 흐름은 괜찮은데 denormalize과정에서 생성된 경로가 모두 1사분면으로만 나오는 게 문제였다. 

def normalize_waypoints(waypoints, goal_point):
    # 모든 점들의 좌표를 첫 번째 점(0,0)에 대한 상대 좌표로 변환
    relative_waypoints = waypoints - waypoints[:, 0:1, :]
    
    # min-max normalization
    valid_points = torch.cat([relative_waypoints[:, 1:], goal_point], dim=1)
    min_xy = valid_points.min(dim=1, keepdim=True)[0]
    max_xy = valid_points.max(dim=1, keepdim=True)[0]
    
    # 스케일링 (첫번째 점은 이미 (0,0)이므로 제외)
    normalized = torch.zeros_like(relative_waypoints)
    normalized[:, 1:] = (relative_waypoints[:, 1:] - min_xy) / (max_xy - min_xy)
    
    return normalized, {'min': min_xy, 'max': max_xy}

 
이렇게 [0,1] 로 정규화를 수행하면 모든 좌표들이 0~1 사이값으로 변환되고, 음수 좌표도 0~1사이로 정규화된다. 또, denormalize 과정은

# 정규화 해제 적용
if 'norm_params' in data_dict and DataDict.prediction in output_dict:
    x = output_dict[DataDict.prediction]
    norm_params = data_dict['norm_params'][0]
    min_xy = norm_params['min'].to(self.device)
    max_xy = norm_params['max'].to(self.device)
    
    # 모든 포인트 정규화 해제
    denorm_x = x * (max_xy - min_xy) + min_xy
    
    # 수정: 첫 번째 점을 (0,0)으로 강제 설정
    first_points = denorm_x[:, 0].clone().unsqueeze(1)
    denorm_x = denorm_x - first_points  # 첫 번째 점 기준으로 상대 좌표 계산

이렇게 하고 있었는데 이 과정에서 원래 좌표계의 음수 영역이 복원되지 않는 것이 문제의 원인이었다. 
따라서 꼭! 정규화와 역정규화를 [-1,1] 사이에서 해 주도록 하자. 

def normalize_waypoints(waypoints, goal_point):
    """waypoints: 모든 점이 (0,0)을 기준으로 상대적인 위치를 가지도록 변환"""
    # 모든 점들의 좌표를 첫 번째 점(0,0)에 대한 상대 좌표로 변환
    relative_waypoints = waypoints - waypoints[:, 0:1, :]  # broadcasting
    
    # min-max normalization to [-1, 1] range
    valid_points = torch.cat([relative_waypoints[:, 1:], goal_point], dim=1)  # 첫번째 점 제외
    min_xy = valid_points.min(dim=1, keepdim=True)[0]
    max_xy = valid_points.max(dim=1, keepdim=True)[0]
    
    # 스케일링 (첫번째 점은 이미 (0,0)이므로 제외)
    normalized = torch.zeros_like(relative_waypoints)
    # Scale to [-1, 1] instead of [0, 1]
    normalized[:, 1:] = 2.0 * (relative_waypoints[:, 1:] - min_xy) / (max_xy - min_xy) - 1.0
    
    return normalized, {'min': min_xy, 'max': max_xy}
                # [-1, 1] 범위에서 바로 원래 스케일로 정규화 해제
                scale = (max_xy - min_xy) / 2.0
                offset = (max_xy + min_xy) / 2.0
                denorm_x = x * scale + offset

 
이걸 찾고 다시 보니까 Diffusion policy도 [-1,1]로 정규화하고 있었네... 이렇게 멍청할 수가. 

그리고 이럴 경우에는 ReLu가 아니라 [-1,1] 정규화 범위에 더 적합한 활성화 함수들로 Activation function도 0이하의 값을 버리지 않는 LeakyReLU, GELU, SiLU로 수정해 주어야 한다. 참고로 DP에서는 GELU를 사용하고 있다. 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.