自动编码器和极坐标

问题描述 投票:0回答:1

自动编码器可以学习极坐标的变换吗?如果一组二维数据近似位于一个圆上,则存在一个由角度参数化的低维流形,它描述了数据“最佳”。

我尝试了各种版本都没有成功。

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tensorflow as tf

from tensorflow.keras import layers, losses
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam

class DeepAutoEncoder(Model):
  def __init__(self, dim_data: int, num_hidden: int,
               num_comp: int,
               activation: str = 'linear'):
    super(DeepAutoEncoder, self).__init__()
    self.encoder = tf.keras.Sequential([
      layers.Dense(num_hidden, activation=activation),
      layers.Dense(num_comp, activation=activation),
    ])

    self.decoder = tf.keras.Sequential([
      layers.Dense(num_hidden, activation=activation),
      layers.Dense(dim_data, activation='linear')
    ])

  def call(self, x):
    encoded = self.encoder(x)
    decoded = self.decoder(encoded)
    return decoded



# Data
num_obs = 1000
np.random.seed(1238)
e = np.random.randn(num_obs, 1)
t = np.linspace(0, 2*np.pi, num_obs)
x = 1 * np.cos(t)
y = np.sin(t) + 0.2*e[:, 0]
X = np.column_stack((x, y))

num_comp = 1
activations = ['linear', 'sigmoid']
ae = {a: None for a in activations}
for act in activations:
    ae[act] = DeepAutoEncoder(dim_data=2, num_comp=num_comp,
                              num_hidden=3, activation=act)
    ae[act].compile(optimizer=Adam(learning_rate=0.01),
                    loss='mse')
    ae[act].build(input_shape=(None, 2))
    ae[act].summary()
    history = ae[act].fit(X, X, epochs=200,
                          batch_size=32,
                          shuffle=True)
    ae[act].summary()
    plt.plot(history.history["loss"], label=act)
    plt.legend()

f, axs = plt.subplots(2, 2)
for i, a in enumerate(activations):
    axs[0, i].plot(x, y, '.', c='k')
    z = ae[a].encoder(X)
    # x_ae = ae[a].decoder(ae[a].encoder(X))
    x_ae = ae[a](X)
    axs[0, i].plot(x_ae[:, 0], x_ae[:, 1], '.')
    # axs[0, i].plot(x_pca[:, 0], x_pca[:, 1], '.', c='C3')
    axs[1, i].plot(z)
    axs[0, i].axis('equal')
    axs[0, i].set(title=a)


重建的数据如下所示:

enter image description here

我假设原因是变换 sigmoid(W * z + b) 远离非线性矩阵 [[cos(theta) sin(theta)], [-sin(theta) sin(theta)] ] 将潜在变量映射回原始空间所需的。

任何想法都会很棒!

非常感谢。

python tensorflow autoencoder polar-coordinates
1个回答
0
投票

自动编码器可以学习极坐标的变换吗?如果一组二维数据近似位于一个圆上,则存在一个由角度参数化的低维流形,它描述了数据“最佳”。

神经网络可以被训练来学习任意变换,包括分析变换,例如坐标系之间的映射。在本例中,网络将使用任意编码对 (x, y) 坐标进行编码,该编码与角度(如果您愿意,可以称为“伪角度”)密切相关。

问题中的神经网络试图将 3 个关键变量编码到数轴上:x 的符号、y 的符号以及它们的相对大小。这三项信息都是确定正确象限所必需的。我认为网络不学习的主要原因是它的容量太小,无法捕获 arctan2 复杂性。

假设我们将数据限制为 x > 0;在这种情况下,网络唯一需要编码的是 y 的符号及其相对于 x 的大小。在这种情况下,您的网络工作正常,因为它只需要学习 arctan:

enter image description here

下图说明了学习到的编码如何携带有关角度的信息,从而使网络能够唯一确定沿圆的位置。

enter image description here

注意圆周上每个点的编码值是唯一的。输入与其重建之间存在线性关系,表明网络已经学会了重现它们。

一旦允许 x 既为正值又为负值,它就需要学习更复杂的 arctan2 函数。网络失败,只捕获 arctan;您的结果表明 y 已正确编码,但对于任何 y,它无法确定 x 应该位于平面的哪一侧,导致平均 x 为 0,并将点正确投影到 y 上。

enter image description here

左图说明了正在发生的情况。如果从 +90 跟踪到 -90,则编码是正确的,但随后会重复。换句话说,它捕获了右手平面中的正确角度,但它们在左手平面上是重复的。正 x 和负 x 都对应于相同的编码,导致 x 平均为 0。第二张图显示,对于任何 x,它基本上预测为 0,同时它学习第三张图中 y 的正确定位。

我进行了以下更改,我发现所有这些更改对于使用给定模型提高此数据集的性能都很重要:

  • 使编码器更深(在较小程度上也使解码器更深)
  • 使用 tanh 激活而不是 ReLU 或 sigmoid,与数据的范围一致
  • 使用小批量,为网络提供更多步骤来探索损失空间

我试图保持架构接近原始架构,以展示网络深度的要点。下面的结果使用更简单的数据集(噪音更少),显示了 75 个 epoch 后模型的性能:

Model comprises 135 trainable parameters
[epoch  1/75] trn loss: 0.291 [rmse: 0.540] | val loss: 0.284 [rmse: 0.533]
[epoch  5/75] trn loss: 0.252 [rmse: 0.502] | val loss: 0.248 [rmse: 0.498]
...
[epoch 70/75] trn loss: 0.005 [rmse: 0.072] | val loss: 0.005 [rmse: 0.074]
[epoch 75/75] trn loss: 0.009 [rmse: 0.095] | val loss: 0.005 [rmse: 0.070]

enter image description here

网络已经为圆上的每个点学习了唯一的编码。 y=0 附近有一个不连续性,我没有研究过。

enter image description here

侦察通常会跟踪输入。


PyTorch 示例代码。

import numpy as np
from matplotlib import pyplot as plt

#Data for testing as per OP
num_obs = 1000
np.random.seed(1238)
e = np.random.randn(num_obs)
#Shuffle it in advance
t = np.linspace(0, 2 * np.pi, num_obs)[np.random.permutation(num_obs)]

x = np.cos(t)
y = np.sin(t) + 0.2 * e / 10
data = np.column_stack((x, y))
# data = data[x > 0, :] #Limit to RH plane


#
#Split the data (just train & validation for this demo)
#
n_train = int(0.7 * len(data))

data_trn = data[:n_train]
data_val = data[n_train:]

f, (ax_trn, ax_val) = plt.subplots(
    ncols=2, figsize=(5.2, 3), sharex=True, sharey=True, layout='tight'
)

for ax, arr in [[ax_trn, data_trn], [ax_val, data_val]]:
    ax.scatter(arr[:, 0], arr[:, 1], marker='.', s=2, color='dodgerblue')

ax_trn.set_title('train')
ax_trn.set(xlabel='x', ylabel='y')
ax_trn.spines[['top', 'right']].set_visible(False)

ax_val.set_title('validation')
ax_val.set_xlabel('x')
ax_val.spines[['top', 'right', 'left']].set_visible(False)
ax_val.tick_params(axis='y', left=False)

#
# Prepare data for training
#
import torch
from torch import nn

#Data to float tensors
X_trn = torch.tensor(data_trn).float()
X_val = torch.tensor(data_val).float()

#
#Define the model
#
torch.manual_seed(1000)

n_features = X_trn.shape[1]
hidden_size = 3
latent_size = 1

activation_layer = nn.Tanh()

encoder = nn.Sequential(
    nn.Linear(n_features, hidden_size),
    activation_layer,
    
    nn.Linear(hidden_size, hidden_size),
    activation_layer,
    nn.Linear(hidden_size, hidden_size),
    activation_layer,
    nn.Linear(hidden_size, hidden_size),
    activation_layer,
    nn.Linear(hidden_size, hidden_size),
    activation_layer,
    nn.Linear(hidden_size, hidden_size),
    activation_layer,
    nn.Linear(hidden_size, hidden_size),
    activation_layer,
    nn.Linear(hidden_size, hidden_size),
    activation_layer,

    nn.Linear(hidden_size, latent_size),    
)

decoder = nn.Sequential(
    activation_layer,

    nn.Linear(latent_size, hidden_size),
    activation_layer,
    nn.Linear(hidden_size, hidden_size),
    activation_layer,
    nn.Linear(hidden_size, hidden_size),
    activation_layer,
    
    nn.Linear(hidden_size, n_features),
)

autoencoder = nn.Sequential(encoder, decoder)

print(
    'Model comprises',
    sum([p.numel() for p in autoencoder.parameters() if p.requires_grad]),
    'trainable parameters'
)

optimiser = torch.optim.NAdam(autoencoder.parameters())
loss_fn = nn.MSELoss()

#
# Training loop
#
metrics_dict = dict(epoch=[], trn_loss=[], val_loss=[])

for epoch in range(n_epochs := 75):
    autoencoder.train()

    train_shuffled = X_trn[torch.randperm(len(X_trn))]
    for sample in train_shuffled:
        recon = autoencoder(sample).ravel()
        loss = loss_fn(recon, sample)

        optimiser.zero_grad()
        loss.backward()
        optimiser.step()
    #/end of epoch

    if not (epoch == 0 or (epoch + 1) % 5 == 0):
        continue

    autoencoder.eval()
    with torch.no_grad():
        trn_recon = autoencoder(X_trn)
        val_recon = autoencoder(X_val)

        trn_encodings = encoder(X_trn)
        val_encodings = encoder(X_val)
        
    trn_loss = loss_fn(trn_recon, X_trn)
    val_loss = loss_fn(val_recon, X_val)

    print(
        f'[epoch {epoch + 1:>2d}/{n_epochs:>2d}]',
        f'trn loss: {trn_loss:>5.3f} [rmse: {trn_loss**0.5:>5.3f}] |',
        f'val loss: {val_loss:>5.3f} [rmse: {val_loss**0.5:>5.3f}]'
    )

    metrics_dict['epoch'].append(epoch + 1)
    metrics_dict['trn_loss'].append(trn_loss)
    metrics_dict['val_loss'].append(val_loss)

#Overlay results
for ax, recon in [[ax_trn, trn_recon], [ax_val, val_recon]]:
    ax.scatter(recon[:, 0], recon[:, 1], color='crimson', marker='.', s=2)

#Legend
ax_trn.scatter([], [], color='dodgerblue', marker='.', s=5, label='data')
ax_trn.scatter([], [], color='crimson', marker='.', s=5, label='recon')
f.legend(framealpha=1, scatterpoints=5, loc='upper left', labelspacing=0.05)

#View learning curve
f, ax = plt.subplots(figsize=(6, 2))

for key in ['trn_loss', 'val_loss']:
    ax.plot(
        metrics_dict['epoch'][0:], metrics_dict[key][0:],
        marker='o', linestyle='-' if 'trn' in key else '--', label=key[:3]
    )
ax.set(xlabel='epoch', ylabel='MSE loss')
ax.legend(framealpha=0, loc='upper right')
ax.spines[['top', 'right']].set_visible(False)

#View encodings
f, axs = plt.subplots(ncols=4, figsize=(10, 3), layout='tight')
cmap = 'seismic'

ax = axs[0]
im = ax.scatter(X_trn[:, 0], X_trn[:, 1], c=trn_encodings, cmap=cmap, marker='.')
ax.set(xlabel='x', ylabel='y', title='inputs & learnt encoding')

ax = axs[1]
ax.scatter(X_trn[:, 0], trn_recon[:, 0], c=trn_encodings, cmap=cmap, marker='.')
ax.set(xlabel='x', ylabel='recon_x', title='x recon')

ax = axs[2]
ax.scatter(X_trn[:, 1], trn_recon[:, 1], c=trn_encodings, cmap=cmap, marker='.')
ax.set(xlabel='y', ylabel='recon_y', title='y recon')

ax = axs[3]
ax.scatter(trn_recon[:, 0], trn_recon[:, 1], c=trn_encodings, cmap=cmap, marker='.')
ax.set(xlabel='x_recon', ylabel='y_recon', title='recon')

[ax.set(xlim=[-1.5, 1.5], ylim=[-1.5, 1.5]) for ax in axs]
f.colorbar(im, label='encoder output', ax=axs[3])
© www.soinside.com 2019 - 2024. All rights reserved.