러닝 기반으로 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를 사용하고 있다.