旋转角度的参数化回归

数据挖掘 神经网络 深度学习 损失函数 参数估计
2021-09-27 00:52:58

假设我有一张自上而下的箭头图片,我想预测这个箭头的角度。这将介于0360 度,或之间 02π. 问题是这个目标是圆形的,0360度数完全相同,这是我想在我的目标中加入的不变性,这应该有助于显着地泛化(这是我的假设)。问题是我没有看到解决这个问题的干净方法,是否有任何论文试图解决这个问题(或类似问题)?我确实对它们的潜在缺点有一些想法:

  • 使用 sigmoid 或 tanh 激活,将其缩放到 (0,2π)范围并将循环属性合并到损失函数中。我认为这将失败得相当严重,因为如果它在边界上(最坏的预测),只有一点点噪音会推动权重走向一个方向或另一个方向。此外,更接近边界的值02π 将更难达到,因为绝对预激活值需要接近无限。

  • 回归到两个值,a xy值并根据这两个值形成的角度计算损失。我认为这个有更大的潜力,但这个向量的范数是无限的,这可能导致数值不稳定,并可能导致在训练期间爆炸或变为 0。这可以通过使用一些奇怪的正则化器来解决,以防止该范数与 1 相差太远。

其他选项将使用正弦和余弦函数做一些事情,但我觉得多个预激活映射到相同输出的事实也会使优化和泛化变得非常困难。

2个回答

第二种方式,预测x=cos(α)y=sin(α)完全没问题。

是的,预测的范数(x,y)向量不能保证在附近1. 但它不太可能爆发,特别是如果您使用 sigmoid 激活函数(受其性质限制)和/或很好地规范您的模型。如果所有训练样本都在[1,1]?

另一面是矢量(x,y)太接近(0,0). 这有时可能会发生,并且确实可能​​导致预测错误的角度。但这可能被视为您的模型的一个好处-您可以考虑(x,y)作为模型置信度的衡量标准实际上,接近 0 的范数意味着您的模型不确定正确的方向在哪里。

这是 Python 中的一个小例子,它表明最好预测 sin 和 cos,直接预测角度:

# predicting the angle (in radians)
import numpy as np
from sklearn.neural_network import MLPRegressor
from sklearn.model_selection import cross_val_predict
from sklearn.metrics import r2_score
# generate toy data
np.random.seed(1)
X = np.random.normal(size=(100, 2))
y = np.arctan2(np.dot(X, [1,2]), np.dot(X, [3,0.4]))
# simple prediction
model = MLPRegressor(random_state=42, activation='tanh', max_iter=10000)
y_simple_pred = cross_val_predict(model, X, y)
# transformed prediction
joint = cross_val_predict(model, X, np.column_stack([np.sin(y), np.cos(y)]))
y_trig_pred = np.arctan2(joint[:,0], joint[:,1])
# compare
def align(y_true, y_pred):
    """ Add or remove 2*pi to predicted angle to minimize difference from GT"""
    y_pred = y_pred.copy()
    y_pred[y_true-y_pred >  np.pi] += np.pi*2
    y_pred[y_true-y_pred < -np.pi] -= np.pi*2
    return y_pred
print(r2_score(y, align(y, y_simple_pred))) # R^2 about 0.57
print(r2_score(y, align(y, y_trig_pred)))   # R^2 about 0.99

您可以继续绘制预测,以查看正弦-余弦模型的预测几乎是正确的,尽管可能需要进一步校准:

import matplotlib.pyplot as plt
plt.figure(figsize=(12, 3))
plt.subplot(1,4,1)
plt.scatter(X[:,0], X[:,1], c=y)
plt.title('Data (y=color)'); plt.xlabel('x1'); plt.ylabel('x2')
plt.subplot(1,4,2)
plt.scatter(y_simple_pred, y)
plt.title('Direct model'); plt.xlabel('prediction'); plt.ylabel('actual')
plt.subplot(1,4,3)
plt.scatter(y_trig_pred, y)
plt.title('Sine-cosine model'); plt.xlabel('prediction'); plt.ylabel('actual')
plt.subplot(1,4,4)
plt.scatter(joint[:,0], joint[:,1], s=5)
plt.title('Predicted sin and cos'); plt.xlabel('cos'); plt.ylabel('sin')
plt.tight_layout();

在此处输入图像描述

更新一位导航工程师注意到,当角度接近πN2. 事实上,接近 0° 和 180° 的角度α 几乎是线性的 cos(α),在 90° 和 270° 附近,它几乎是线性的 sin(α). 因此,增加两个输出可能是有益的,例如z=(α+π4)w=(α+π4), 使模型分别在 45° 和 135° 附近几乎是线性的。然而,在这种情况下,恢复原始角度并不那么明显。

最好的解决方案可能是提取坐标 (X,是的) 从两种表示中(在第二种表示中,我们需要旋转 (z,w) 要得到 (X,是的)),对它们进行平均,然后才计算arctan2

如上所述,使用笛卡尔坐标效果很好。然而,在我看来,将极坐标数据转换为笛卡尔坐标会在数据中最初不存在的 X 和 Y 坐标之间产生依赖关系。例如,机器人的路径决策模型在极坐标中比笛卡尔更直观。机器人的速度向量在极坐标中的角度和幅度之间的依赖性甚至可能不存在或与笛卡尔坐标中的依赖性不同。

我发现继续使用极坐标的一种解决方法是创建一个自定义误差函数,以使用 MATLAB 中的 angdiff() 函数和通常的幅度差来计算角度差。

对于 -pi 和 pi 之间的差异,此函数返回“0”。是 Mathworks 网站上函数支持页面的链接。

如果您使用 Sigmoid 激活并且您的角度数据在 [0,1] 之间进行归一化,则应在使用 angdiff() 函数之前将其返回到 [-pi,pi] 范围,然后将错误归一化回 [0,1 ] 反向传播过程的范围。

此外,Python 中的等效函数为:

import numpy as np


def angdiff(a, b):
    delta = np.arctan2(np.sin(b-a), np.cos(b-a))
    delta = np.around(delta, 4)  # Since np.sin(pi) result is 1.22e-16
    delta += 0.  # Since np.around return -0.
    return delta


pi = np.pi
a = np.asarray([pi/2, 3*pi/4, 0])
b = np.asarray([pi, pi/2, -pi])

print(angdiff(a, b))
print(angdiff(pi, -pi))
print(angdiff(-pi, pi))

这将返回与 MATLAB 函数类似的结果,并且也适用于数组:

[ 1.5708 -0.7854 -3.1416]
0.0
0.0

希望有帮助。