为神经网络编码角度数据

机器算法验证 神经网络 循环统计
2022-01-27 03:36:11

我正在训练一个神经网络(细节不重要),其中目标数据是角度向量(0 到 2*pi 之间)。我正在寻找有关如何编码此数据的建议。这是我目前正在尝试的(成功有限):

1) 1-of-C 编码:我将设置的可能角度分成 1000 个左右的离散角度,然后通过在相关索引处放置 1 来指示特定角度。问题在于,网络只是学习输出全 0(因为这几乎完全正确)。

2) 简单缩放:我将网络输出范围 ([0,1]) 缩放到 [0,2*pi]。这里的问题是角度自然具有圆形拓扑结构(即 0.0001 和 2*pi 实际上彼此相邻)。使用这种类型的编码,该信息会丢失。

任何建议,将不胜感激!

4个回答

介绍

我觉得这个问题真的很有趣,我假设有人在上面发表了一篇论文,但今天是我的休息日,所以我不想去追逐参考资料。

因此,我们可以将其视为输出的表示/编码,我在这个答案中就是这样做的。我仍然认为有更好的方法,你可以使用稍微不同的损失函数。(也许是平方差的总和,使用减法模 2)。π

但从实际答案开始。

方法

我建议将角度表示为一对值,它的正弦和余弦。θ

所以编码函数为: 解码函数为:因为arctan2是反正切,在所有象限中保持方向)θ(sin(θ),cos(θ))
(y1,y2)arctan2(y1,y2)

从理论上讲,如果您的工具使用支持atan2作为层函数(恰好采用 2 个输入并产生 1 个输出),则您可以等效地直接使用角度。 TensorFlow 现在可以做到这一点,并支持梯度下降,尽管它不适合这种用途。我研究了使用out = atan2(sigmoid(ylogit), sigmoid(xlogit)) 损失函数min((pred - out)^2, (pred - out - 2pi)^2)我发现它训练得比使用outs = tanh(ylogit), outc = tanh(xlogit)) 损失函数差得多0.5((sin(pred) - outs)^2 + (cos(pred) - outc)^2我认为这可以归因于梯度不连续atan2

我在这里的测试将其作为预处理功能运行

为了评估这一点,我定义了一个任务:

给定一个黑白图像,表示空白背景上的一条线 输出该线与“正 x 轴”的角度

我实现了一个随机生成这些图像的函数,线条具有随机角度(注意:这篇文章的早期版本使用随机斜率,而不是随机角度。感谢@Ari Herman 指出。现在已修复)。我构建了几个神经网络来评估任务的性能。完整的实现细节在这个 Jupyter notebook中。代码都在Julia中,我使用了Mocha神经网络库。

为了比较,我将它与缩放到 0,1 的替代方法进行对比。并放入 500 个 bin 并使用软标签 softmax。我对最后一个不是特别满意,我觉得我需要调整它。这就是为什么,与其他人不同,我只试用了 1,000 次迭代,而另外两个分别运行了 1,000 次和 10,000 次

实验装置

图像为 ×101像素,线条从中心开始,一直延伸到边缘。图像中没有噪音等,只有白色背景上的“黑色”线。101×101

对于每条轨迹,随机生成 1,000 个训练和 1,000 个测试图像。

评估网络有一个宽度为 500 的隐藏层。隐藏层中使用了 Sigmoid 神经元。

它由 Stochastic Gradient Decent 训练,固定学习率为 0.01,固定动量为 0.9。

没有使用正则化或 dropout。也不是任何类型的卷积等。一个简单的网络,我希望这表明这些结果将推广

在测试代​​码中调整这些参数非常容易,我鼓励人们这样做。(并在测试中寻找错误)。

结果

我的结果如下:

|                        |  500 bins    |  scaled to 0-1 |  Sin/Cos     |  scaled to 0-1 |  Sin/Cos     |
|                        | 1,000 Iter   | 1,000 Iter     | 1,000 iter   | 10,000 Iter    | 10,000 iter  |
|------------------------|--------------|----------------|--------------|----------------|--------------|
| mean_error             | 0.4711263342 | 0.2225284486   | 2.099914718  | 0.1085846429   | 2.1036656318 |
| std(errors)            | 1.1881991421 | 0.4878383767   | 1.485967909  | 0.2807570442   | 1.4891605068 |
| minimum(errors)        | 1.83E-006    | 1.82E-005      | 9.66E-007    | 1.92E-006      | 5.82E-006    |
| median(errors)         | 0.0512168533 | 0.1291033982   | 1.8440767072 | 0.0562908143   | 1.8491085947 |
| maximum(errors)        | 6.0749693965 | 4.9283551248   | 6.2593307366 | 3.735884823    | 6.2704853962 |
| accurancy              | 0.00%        | 0.00%          | 0.00%        | 0.00%          | 0.00%        |
| accurancy_to_point001  | 2.10%        | 0.30%          | 3.70%        | 0.80%          | 12.80%       |
| accurancy_to_point01   | 21.90%       | 4.20%          | 37.10%       | 8.20%          | 74.60%       |
| accurancy_to_point1    | 59.60%       | 35.90%         | 98.90%       | 72.50%         | 99.90%       |

这里我指的是误差,这是神经网络输出的角度与真实角度之差的绝对值。所以平均误差(例如)是这种差异等的 1,000 个测试用例的平均值。我不确定我是否应该通过使相等的错误来重新调整它的错误)。7π4π4

我还介绍了不同粒度级别的准确性。准确性是它得到纠正的测试用例的一部分。所以accuracy_to_point01意味着如果输出在真实角度的 0.01 以内,则认为它是正确的。这些表示都没有得到任何完美的结果,但考虑到浮点数学的工作原理,这并不奇怪。

如果你看一下这篇文章的历史,你会发现结果确实对他们有一点影响,每次我重新运行它时都会略有不同。但是价值的一般顺序和规模保持不变。从而让我们得出一些结论。

讨论

到目前为止,使用 softmax 的分箱性能最差,正如我所说,我不确定我在实现中没有搞砸什么。不过,它的表现确实略高于猜测率。如果只是猜测我们会得到π

sin/cos 编码的性能明显优于缩放的 0-1 编码。改进的程度在于,在 1,000 次训练迭代时,sin/cos 在大多数指标上的性能比在 10,000 次迭代时的缩放要好约 3 倍。

我认为,这在一定程度上与提高泛化能力有关,因为至少在运行 10,000 次迭代后,两者在训练集上的均方误差都非常相似。

考虑到角度可能或多或少是任何实数,这个任务的最佳性能肯定有一个上限,但并非所有这样的角度都会在像素的分辨率下产生不同的线条。因此,例如,由于角度 45.0 和 45.0000001 都与该分辨率下的同一图像相关联,因此没有任何方法可以完全正确。101×101

似乎也有可能在绝对规模上超越这种性能,需要更好的神经网络。而不是上面在实验设置中概述的非常简单的。

结论。

在我在这里调查的表示中,sin/cos 表示似乎是迄今为止最好的。这确实是有道理的,因为当你绕着圆圈移动时它确实有一个平滑的值。我也喜欢用arctan2来完成逆运算,这很优雅。

我相信所提出的任务足以为网络提出合理的挑战。虽然我猜它实际上只是在学习对进行曲线拟合,所以也许它太容易了。也许更糟糕的是,它可能有利于配对表示。我不认为是,但是这里已经很晚了,所以我可能错过了一些我再次邀请您查看我的代码的内容。提出改进建议或替代任务。f(x)=y1y2x

这是另一个 Python 实现,将Lyndon White提出的编码与分箱方法进行了比较。下面的代码产生了以下输出:

Training Size: 100
Training Epochs: 100
Encoding: cos_sin
Test Error: 0.017772154610047136
Encoding: binned
Test Error: 0.043398792553251526

Training Size: 100
Training Epochs: 500
Encoding: cos_sin
Test Error: 0.015376604917819397
Encoding: binned
Test Error: 0.032942592915322394

Training Size: 1000
Training Epochs: 100
Encoding: cos_sin
Test Error: 0.007544091937411164
Encoding: binned
Test Error: 0.012796594492198667

Training Size: 1000
Training Epochs: 500
Encoding: cos_sin
Test Error: 0.0038051515079569097
Encoding: binned
Test Error: 0.006180633805557207

如您所见,虽然分箱方法在这个玩具任务中表现出色,但(sin(θ),cos(θ))编码在所有训练配置中都表现得更好,有时甚至有相当大的优势。我怀疑随着具体任务变得更加复杂,使用Lyndon White的好处(sin(θ),cos(θ))代表性会更加突出。

import matplotlib.pyplot as plt
import numpy as np
import torch
import torch.nn as nn
import torch.utils.data

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")


class Net(nn.Module):
    def __init__(self, input_size, hidden_size, num_out):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden_size)
        self.sigmoid = nn.Sigmoid()
        self.fc2 = nn.Linear(hidden_size, num_out)

    def forward(self, x):
        out = self.fc1(x)
        out = self.sigmoid(out)
        out = self.fc2(out)
        return out


def gen_train_image(angle, side, thickness):
    image = np.zeros((side, side))
    (x_0, y_0) = (side / 2, side / 2)
    (c, s) = (np.cos(angle), np.sin(angle))
    for y in range(side):
        for x in range(side):
            if (abs((x - x_0) * c + (y - y_0) * s) < thickness / 2) and (
                    -(x - x_0) * s + (y - y_0) * c > 0):
                image[x, y] = 1

    return image.flatten()


def gen_data(num_samples, side, num_bins, thickness):
    angles = 2 * np.pi * np.random.uniform(size=num_samples)
    X = [gen_train_image(angle, side, thickness) for angle in angles]
    X = np.stack(X)

    y = {"cos_sin": [], "binned": []}
    bin_size = 2 * np.pi / num_bins
    for angle in angles:
        idx = int(angle / bin_size)
        y["binned"].append(idx)
        y["cos_sin"].append(np.array([np.cos(angle), np.sin(angle)]))

    for enc in y:
        y[enc] = np.stack(y[enc])

    return (X, y, angles)


def get_model_stuff(train_y, input_size, hidden_size, output_sizes,
                    learning_rate, momentum):
    nets = {}
    optimizers = {}

    for enc in train_y:
        net = Net(input_size, hidden_size, output_sizes[enc])
        nets[enc] = net.to(device)
        optimizers[enc] = torch.optim.SGD(net.parameters(), lr=learning_rate,
                                          momentum=momentum)

    criterions = {"binned": nn.CrossEntropyLoss(), "cos_sin": nn.MSELoss()}
    return (nets, optimizers, criterions)


def get_train_loaders(train_X, train_y, batch_size):
    train_X_tensor = torch.Tensor(train_X)

    train_loaders = {}

    for enc in train_y:
        if enc == "binned":
            train_y_tensor = torch.tensor(train_y[enc], dtype=torch.long)
        else:
            train_y_tensor = torch.tensor(train_y[enc], dtype=torch.float)

        dataset = torch.utils.data.TensorDataset(train_X_tensor, train_y_tensor)
        train_loader = torch.utils.data.DataLoader(dataset=dataset,
                                                   batch_size=batch_size,
                                                   shuffle=True)
        train_loaders[enc] = train_loader

    return train_loaders


def show_image(image, side):
    img = plt.imshow(np.reshape(image, (side, side)), interpolation="nearest",
                     cmap="Greys")
    plt.show()


def main():
    side = 101
    input_size = side ** 2
    thickness = 5.0
    hidden_size = 500
    learning_rate = 0.01
    momentum = 0.9
    num_bins = 500
    bin_size = 2 * np.pi / num_bins
    half_bin_size = bin_size / 2
    batch_size = 50
    output_sizes = {"binned": num_bins, "cos_sin": 2}
    num_test = 1000

    (test_X, test_y, test_angles) = gen_data(num_test, side, num_bins,
                                             thickness)

    for num_train in [100, 1000]:

        (train_X, train_y, train_angles) = gen_data(num_train, side, num_bins,
                                                    thickness)
        train_loaders = get_train_loaders(train_X, train_y, batch_size)

        for epochs in [100, 500]:

            (nets, optimizers, criterions) = get_model_stuff(train_y, input_size,
                                                             hidden_size, output_sizes,
                                                             learning_rate, momentum)

            for enc in train_y:
                optimizer = optimizers[enc]
                net = nets[enc]
                criterion = criterions[enc]

                for epoch in range(epochs):
                    for (i, (images, ys)) in enumerate(train_loaders[enc]):
                        optimizer.zero_grad()

                        outputs = net(images.to(device))
                        loss = criterion(outputs, ys.to(device))
                        loss.backward()
                        optimizer.step()


            print("Training Size: {0}".format(num_train))
            print("Training Epochs: {0}".format(epochs))
            for enc in train_y:
                net = nets[enc]
                preds = net(torch.tensor(test_X, dtype=torch.float).to(device))
                if enc == "binned":
                    pred_bins = np.array(preds.argmax(dim=1).detach().cpu().numpy(),
                                         dtype=np.float)
                    pred_angles = bin_size * pred_bins + half_bin_size
                else:
                    pred_angles = torch.atan2(preds[:, 1], preds[:, 0]).detach().cpu().numpy()
                    pred_angles[pred_angles < 0] = pred_angles[pred_angles < 0] + 2 * np.pi

                print("Encoding: {0}".format(enc))
                print("Test Error: {0}".format(np.abs(pred_angles - test_angles).mean()))

            print()


if __name__ == "__main__":
    main()

这是我的 Python 版本的实验。我保持你实现的许多细节相同,特别是我使用相同的图像大小、网络层大小、学习率、动量和成功指标。

每个测试的网络都有一个带有逻辑神经元的隐藏层(大小 = 500)。如前所述,输出神经元要么是线性的,要么是 softmax。我使用了 1,000 张训练图像和 1,000 张测试图像,它们是独立、随机生成的(因此可能会有重复)。训练包括通过训练集的 50 次迭代。

使用分箱和“高斯”编码(我编的一个名字;类似于分箱,除了目标输出向量的形式为 exp(-pi*([1,2,3,... ,500] - idx)**2) 其中 idx 是对应于正确角度的索引)。代码如下;这是我的结果:

(cos,sin) 编码的测试错误:

1,000 个训练图像,1,000 个测试图像,50 次迭代,线性输出

  • 平均值:0.0911558142071

  • 中位数:0.0429723541743

  • 最低:2.77769843793e-06

  • 最大值:6.2608513539

  • 精确到 0.1:85.2%

  • 精确到 0.01:11.6%

  • 精确到 0.001:1.0%

[-1,1] 编码的测试错误:

1,000 个训练图像,1,000 个测试图像,50 次迭代,线性输出

  • 平均值:0.234181700523

  • 中位数:0.17460197307

  • 最小值:0.000473665840258

  • 最大值:6.00637777237

  • 精确到 0.1:29.9%

  • 精确到 0.01:3.3%

  • 精确到 0.001:0.1%

1-of-500 编码的测试错误:

1000 个训练图像,1000 个测试图像,50 次迭代,softmax 输出

  • 平均值:0.0298767021922

  • 中位数:0.00388858079174

  • 最低:4.08712407829e-06

  • 最大值:6.2784479965

  • 精确到 0.1:99.6%

  • 精确到 0.01:88.9%

  • 精确到 0.001:13.5%

高斯编码的测试错误:

1000 个训练图像,1000 个测试图像,50 次迭代,softmax 输出

  • 平均值:0.0296905377463
  • 中位数:0.00365867335107
  • 最低:4.08712407829e-06
  • 最大值:6.2784479965
  • 精确到 0.1:99.6%
  • 精确到 0.01:90.8%
  • 精确到 0.001:14.3%

我无法弄清楚为什么我们的结果似乎相互矛盾,但似乎值得进一步研究。

# -*- coding: utf-8 -*-
"""
Created on Mon Jun 13 16:59:53 2016

@author: Ari
"""

from numpy import savetxt, loadtxt, round, zeros, sin, cos, arctan2, clip, pi, tanh, exp, arange, dot, outer, array, shape, zeros_like, reshape, mean, median, max, min
from numpy.random import rand, shuffle
import matplotlib.pyplot as plt

###########
# Functions
###########

# Returns a B&W image of a line represented as a binary vector of length width*height
def gen_train_image(angle, width, height, thickness):
    image = zeros((height,width))
    x_0,y_0 = width/2, height/2
    c,s = cos(angle),sin(angle)
    for y in range(height):
        for x in range(width):
            if abs((x-x_0)*c + (y-y_0)*s) < thickness/2 and -(x-x_0)*s + (y-y_0)*c > 0:
                image[x,y] = 1
    return image.flatten()

# Display training image    
def display_image(image,height, width):    
    img = plt.imshow(reshape(image,(height,width)), interpolation = 'nearest', cmap = "Greys")
    plt.show()    

# Activation function
def sigmoid(X):
    return 1.0/(1+exp(-clip(X,-50,100)))

# Returns encoded angle using specified method ("binned","scaled","cossin","gaussian")
def encode_angle(angle, method):
    if method == "binned": # 1-of-500 encoding
        X = zeros(500)
        X[int(round(250*(angle/pi + 1)))%500] = 1
    elif method == "gaussian": # Leaky binned encoding
        X = array([i for i in range(500)])
        idx = 250*(angle/pi + 1)
        X = exp(-pi*(X-idx)**2)
    elif method == "scaled": # Scaled to [-1,1] encoding
        X = array([angle/pi])
    elif method == "cossin": # Oxinabox's (cos,sin) encoding
        X = array([cos(angle),sin(angle)])
    else:
        pass
    return X

# Returns decoded angle using specified method
def decode_angle(X, method):
    if method == "binned" or method == "gaussian": # 1-of-500 or gaussian encoding
        M = max(X)
        for i in range(len(X)):
            if abs(X[i]-M) < 1e-5:
                angle = pi*i/250 - pi
                break
#        angle = pi*dot(array([i for i in range(500)]),X)/500  # Averaging
    elif method == "scaled": # Scaled to [-1,1] encoding
        angle = pi*X[0]
    elif method == "cossin": # Oxinabox's (cos,sin) encoding
        angle = arctan2(X[1],X[0])
    else:
        pass
    return angle

# Train and test neural network with specified angle encoding method
def test_encoding_method(train_images,train_angles,test_images, test_angles, method, num_iters, alpha = 0.01, alpha_bias = 0.0001, momentum = 0.9, hid_layer_size = 500):
    num_train,in_layer_size = shape(train_images)
    num_test = len(test_angles)

    if method == "binned":
        out_layer_size = 500
    elif method == "gaussian":
        out_layer_size = 500
    elif method == "scaled":
        out_layer_size = 1
    elif method == "cossin":
        out_layer_size = 2
    else:
        pass

    # Initial weights and biases
    IN_HID = rand(in_layer_size,hid_layer_size) - 0.5 # IN --> HID weights
    HID_OUT = rand(hid_layer_size,out_layer_size) - 0.5 # HID --> OUT weights
    BIAS1 = rand(hid_layer_size) - 0.5 # Bias for hidden layer
    BIAS2 = rand(out_layer_size) - 0.5 # Bias for output layer

    # Initial weight and bias updates
    IN_HID_del = zeros_like(IN_HID)
    HID_OUT_del = zeros_like(HID_OUT)
    BIAS1_del = zeros_like(BIAS1)
    BIAS2_del = zeros_like(BIAS2)

    # Train
    for j in range(num_iters):
        for i in range(num_train):
            # Get training example
            IN = train_images[i]
            TARGET = encode_angle(train_angles[i],method) 

            # Feed forward and compute error derivatives
            HID = sigmoid(dot(IN,IN_HID)+BIAS1)

            if method == "binned" or method == "gaussian": # Use softmax
                OUT = exp(clip(dot(HID,HID_OUT)+BIAS2,-100,100))
                OUT = OUT/sum(OUT)
                dACT2 = OUT - TARGET
            elif method == "cossin" or method == "scaled": # Linear
                OUT = dot(HID,HID_OUT)+BIAS2 
                dACT2 = OUT-TARGET 
            else:
                print("Invalid encoding method")

            dHID_OUT = outer(HID,dACT2)
            dACT1 = dot(dACT2,HID_OUT.T)*HID*(1-HID)
            dIN_HID = outer(IN,dACT1)
            dBIAS1 = dACT1
            dBIAS2 = dACT2

            # Update the weight updates 
            IN_HID_del = momentum*IN_HID_del + (1-momentum)*dIN_HID
            HID_OUT_del = momentum*HID_OUT_del + (1-momentum)*dHID_OUT
            BIAS1_del = momentum*BIAS1_del + (1-momentum)*dBIAS1
            BIAS2_del = momentum*BIAS2_del + (1-momentum)*dBIAS2

            # Update the weights
            HID_OUT -= alpha*dHID_OUT
            IN_HID -= alpha*dIN_HID
            BIAS1 -= alpha_bias*dBIAS1
            BIAS2 -= alpha_bias*dBIAS2

    # Test
    test_errors = zeros(num_test)
    angles = zeros(num_test)
    target_angles = zeros(num_test)
    accuracy_to_point001 = 0
    accuracy_to_point01 = 0
    accuracy_to_point1 = 0

    for i in range(num_test):

        # Get training example
        IN = test_images[i]
        target_angle = test_angles[i]

        # Feed forward
        HID = sigmoid(dot(IN,IN_HID)+BIAS1)

        if method == "binned" or method == "gaussian":
            OUT = exp(clip(dot(HID,HID_OUT)+BIAS2,-100,100))
            OUT = OUT/sum(OUT)
        elif method == "cossin" or method == "scaled":
            OUT = dot(HID,HID_OUT)+BIAS2 

        # Decode output 
        angle = decode_angle(OUT,method)

        # Compute errors
        error = abs(angle-target_angle)
        test_errors[i] = error
        angles[i] = angle

        target_angles[i] = target_angle
        if error < 0.1:
            accuracy_to_point1 += 1
        if error < 0.01: 
            accuracy_to_point01 += 1
        if error < 0.001:
            accuracy_to_point001 += 1

    # Compute and return results
    accuracy_to_point1 = 100.0*accuracy_to_point1/num_test
    accuracy_to_point01 = 100.0*accuracy_to_point01/num_test
    accuracy_to_point001 = 100.0*accuracy_to_point001/num_test

    return mean(test_errors),median(test_errors),min(test_errors),max(test_errors),accuracy_to_point1,accuracy_to_point01,accuracy_to_point001

# Dispaly results
def display_results(results,method):
    MEAN,MEDIAN,MIN,MAX,ACC1,ACC01,ACC001 = results
    if method == "binned":
        print("Test error for 1-of-500 encoding:")
    elif method == "gaussian":
        print("Test error for gaussian encoding: ")
    elif method == "scaled":
        print("Test error for [-1,1] encoding:")
    elif method == "cossin":
        print("Test error for (cos,sin) encoding:")
    else:
        pass
    print("-----------")
    print("Mean: "+str(MEAN))
    print("Median: "+str(MEDIAN))
    print("Minimum: "+str(MIN))
    print("Maximum: "+str(MAX))
    print("Accuracy to 0.1: "+str(ACC1)+"%")
    print("Accuracy to 0.01: "+str(ACC01)+"%")
    print("Accuracy to 0.001: "+str(ACC001)+"%")
    print("\n\n")


##################
# Image parameters
##################
width = 100 # Image width
height = 100 # Image heigth
thickness = 5.0 # Line thickness

#################################
# Generate training and test data
#################################
num_train = 1000
num_test = 1000
test_images = []
test_angles = []
train_images = []
train_angles = []
for i in range(num_train):
    angle = pi*(2*rand() - 1)
    train_angles.append(angle)
    image = gen_train_image(angle,width,height,thickness)
    train_images.append(image)
for i in range(num_test):
    angle = pi*(2*rand() - 1)
    test_angles.append(angle)
    image = gen_train_image(angle,width,height,thickness)
    test_images.append(image)
train_angles,train_images,test_angles,test_images = array(train_angles),array(train_images),array(test_angles),array(test_images)



###########################
# Evaluate encoding schemes
###########################
num_iters = 50

# Train with cos,sin encoding
method = "cossin"
results1 = test_encoding_method(train_images, train_angles, test_images, test_angles, method, num_iters)
display_results(results1,method)

# Train with scaled encoding
method = "scaled"
results3 = test_encoding_method(train_images, train_angles, test_images, test_angles, method, num_iters)
display_results(results3,method)

# Train with binned encoding
method = "binned"
results2 = test_encoding_method(train_images, train_angles, test_images, test_angles, method, num_iters)
display_results(results2,method)

# Train with gaussian encoding
method = "gaussian"
results4 = test_encoding_method(train_images, train_angles, test_images, test_angles, method, num_iters)
display_results(results4,method)

另一种对角度进行编码的方法是一组两个值:

y1 = 最大值(0,θ)

y2 = max(0,-theta)

theta_out = y1 - y2

这将具有与 arctan2 类似的问题,因为梯度在 theta = 0 处未定义。我没有时间训练网络并与其他编码进行比较,但在本文中,该技术似乎相当成功。