时间序列生成的工具探索
在数据驱动的时代,时间序列数据发挥着至关重要的作用,从金融市场的价格波动到传感器数据的监控,再到天气预测等众多领域。随着深度学习技术的迅猛发展,时间序列生成不仅成为了分析和预测的重要手段,还在生成对抗网络(GAN)和变分自编码器(VAE)等技术的推动下,展现出强大的潜力。
在这篇博客中,我们将探讨一些主流的时间序列生成工具和库,这些工具通过不同的方法和算法,帮助我们生成逼真的时间序列数据。无论您是研究人员、数据科学家,还是商业分析师,这些工具都能为您的项目提供强有力的支持,助您在数据分析、模拟和预测等方面更上一层楼。
接下来,我们将介绍几个时间序列生成工具,探讨它们各自的特点、应用场景以及如何使用它们来生成高质量的时间序列数据。
VAE
- 可以不受数据格式的限制,通过编码解码的步骤,直接对重建序列和原始序列进行比较,但是可能损失一些细节,生成的序列比较平滑
基于LSTM单元构建了一个VAE。编码器由一个LSTM单元组成。它接收3D真实序列作为输入。像在VAE架构中的每个编码器一样,它会产生一个2D输出,用于逼近潜在分布的平均值和方差。解码器从二维潜在分布上采样,形成三维序列。然后重建原始的序列。
对VAE的训练是将两部分组合在一起的损失降至最低。 重构部分(在我们的案例中为比例均方误差MSE),度量估计量与被估计量之间的差异程度,正则化部分(KL散度),度量潜在变量的分布和单位高斯分布的差异。
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
# 超参数
latent_dim = 2
batch_size = 128
learning_rate = 1e-3
num_epochs = 30
# 数据加载
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
# VAE 模型
class VAE(nn.Module):
def __init__(self):
super(VAE, self).__init__()
self.encoder = nn.Sequential(
nn.Flatten(),
nn.Linear(28 * 28, 128),
nn.ReLU(),
nn.Linear(128, latent_dim * 2) # 输出均值和对数方差
)
self.decoder = nn.Sequential(
nn.Linear(latent_dim, 128),
nn.ReLU(),
nn.Linear(128, 28 * 28),
nn.Sigmoid()
)
def encode(self, x):
z = self.encoder(x)
mean, log_var = z.chunk(2, dim=-1)
return mean, log_var
def reparameterize(self, mean, log_var):
std = torch.exp(0.5 * log_var)
epsilon = torch.randn_like(std) # 产生标准正态分布的随机数
return mean + epsilon * std
def decode(self, z):
return self.decoder(z)
def forward(self, x):
mean, log_var = self.encode(x)
z = self.reparameterize(mean, log_var)
return self.decode(z), mean, log_var
# 损失函数
def vae_loss(recon_x, x, mean, log_var):
BCE = nn.functional.binary_cross_entropy(recon_x, x.view(-1, 28 * 28), reduction='sum')
KLD = -0.5 * torch.sum(1 + log_var - mean.pow(2) - log_var.exp())
return BCE + KLD
# 初始化模型和优化器
model = VAE()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# 训练模型
model.train()
for epoch in range(num_epochs):
total_loss = 0
for batch_idx, (data, _) in enumerate(train_loader):
optimizer.zero_grad()
recon_batch, mean, log_var = model(data)
loss = vae_loss(recon_batch, data, mean, log_var)
loss.backward()
total_loss += loss.item()
optimizer.step()
print(f'Epoch {epoch + 1}, Loss: {total_loss / len(train_loader.dataset):.4f}')
# 生成新样本
def generate_images(model, num_samples=10):
with torch.no_grad():
z = torch.randn(num_samples, latent_dim)
samples = model.decode(z).view(-1, 1, 28, 28)
return samples
# 生成并显示图像
generated_images = generate_images(model, num_samples=10)
plt.figure(figsize=(10, 1))
for i in range(10):
ax = plt.subplot(1, 10, i + 1)
plt.imshow(generated_images[i].squeeze(), cmap='gray')
plt.axis('off')
plt.show()
代码说明
- 数据处理: 使用
torchvision.datasets.MNIST
加载 MNIST 数据集,并进行归一化处理。定义数据加载器以便于批处理。 - VAE模型: 定义
VAE
类,包括编码器和解码器。编码器将输入图像编码为潜在空间的均值和方差,解码器从潜在空间重构图像。 - 重参数化: 在
reparameterize
方法中,通过采样潜在空间的标准正态分布来生成潜在变量。 - 损失函数: 定义 VAE 损失函数,包括重构误差和 Kullback-Leibler 散度。
- 训练过程: 训练模型并输出每个 Epoch 的损失。
- 生成样本: 从潜在空间生成新样本,并使用 Matplotlib 显示结果。
代码位置
GAN
- 能够较好的建模数据的分布,较好的保留一些细节,但是不够稳定,难以训练,还有模式崩溃的问题
GAN生成的基本原理
- 生成器Generator是一个生成序列的网络,它接受一个随机的噪声,并通过噪声生成序列
- 判别器DIscriminator是一个判别网络,判别一条序列是不是“真实的“
在训练过程中,生成器用生成序列去欺骗判别网络,而判别起的目标就是把G生成的序列和真实的序列分别开来,二者构成了一个动态的博弈过程。
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
# 设置随机种子以确保可重复性
torch.manual_seed(42)
np.random.seed(42)
# 超参数
num_epochs = 2000
batch_size = 64
learning_rate = 0.0002
latent_dim = 10
sequence_length = 50
# 生成真实数据(正弦波)
def generate_real_data(n_samples):
x = np.linspace(0, 2 * np.pi, sequence_length)
y = np.sin(x)
return np.array([y + 0.1 * np.random.randn(*y.shape) for _ in range(n_samples)])
# 创建数据
n_samples = 1000
real_data = generate_real_data(n_samples)
# 转换为 PyTorch 张量
real_data_tensor = torch.tensor(real_data, dtype=torch.float32)
# 定义生成器
class Generator(nn.Module):
def __init__(self):
super(Generator, self).__init__()
self.fc = nn.Sequential(
nn.Linear(latent_dim, 128),
nn.ReLU(),
nn.Linear(128, sequence_length),
nn.Tanh() # 输出范围[-1, 1]
)
def forward(self, z):
return self.fc(z)
# 定义判别器
class Discriminator(nn.Module):
def __init__(self):
super(Discriminator, self).__init__()
self.fc = nn.Sequential(
nn.Linear(sequence_length, 128),
nn.ReLU(),
nn.Linear(128, 1),
nn.Sigmoid() # 输出范围[0, 1]
)
def forward(self, x):
return self.fc(x)
# 初始化模型
generator = Generator()
discriminator = Discriminator()
# 定义优化器
optimizer_G = optim.Adam(generator.parameters(), lr=learning_rate)
optimizer_D = optim.Adam(discriminator.parameters(), lr=learning_rate)
# 损失函数
criterion = nn.BCELoss()
# 训练模型
for epoch in range(num_epochs):
for i in range(0, len(real_data_tensor), batch_size):
# 真实数据
real_sequences = real_data_tensor[i:i + batch_size]
batch_size_current = real_sequences.size(0)
# 标签
real_labels = torch.ones(batch_size_current, 1)
fake_labels = torch.zeros(batch_size_current, 1)
# 训练判别器
optimizer_D.zero_grad()
outputs = discriminator(real_sequences)
d_loss_real = criterion(outputs, real_labels)
z = torch.randn(batch_size_current, latent_dim)
fake_sequences = generator(z)
outputs = discriminator(fake_sequences.detach())
d_loss_fake = criterion(outputs, fake_labels)
d_loss = d_loss_real + d_loss_fake
d_loss.backward()
optimizer_D.step()
# 训练生成器
optimizer_G.zero_grad()
outputs = discriminator(fake_sequences)
g_loss = criterion(outputs, real_labels)
g_loss.backward()
optimizer_G.step()
if epoch % 100 == 0:
print(f'Epoch [{epoch}/{num_epochs}], d_loss: {d_loss.item():.4f}, g_loss: {g_loss.item():.4f}')
# 生成并显示时间序列
def generate_and_plot(num_samples=5):
z = torch.randn(num_samples, latent_dim)
generated_sequences = generator(z).detach().numpy()
plt.figure(figsize=(15, 5))
for i in range(num_samples):
plt.subplot(1, num_samples, i + 1)
plt.plot(generated_sequences[i], label='Generated Sequence')
plt.title(f'Sample {i + 1}')
plt.ylim(-1.5, 1.5)
plt.tight_layout()
plt.show()
# 生成并可视化时间序列
generate_and_plot(5)
代码说明
-
数据生成:
- 使用
generate_real_data
函数生成包含噪声的正弦波数据,作为真实样本。
- 使用
-
生成器:
Generator
类定义生成器。输入是潜在空间的随机向量,输出是生成的时间序列。
-
判别器:
Discriminator
类定义判别器。输入是时间序列,输出是判别结果,即其为真实数据的概率。
-
训练过程:
- 使用二元交叉熵损失函数训练 GAN。首先训练判别器,然后训练生成器以欺骗判别器。
-
生成并可视化:
generate_and_plot
函数用来生成新的时间序列并可视化。
WGAN
WGAN在DCGAN的架构的基础上又进一步改进,一定程度上解决了模式崩溃的问题,可以生成多样性样本,并且模型更加稳定,改进主要有:
- 判别器最后一层去掉sigmoid(GAN的判别器做的是真假二分类任务,所以最后一层是sigmoid,但是现在WGAN中的判别器做的是近似拟合Wasserstein距离,属于回归任务,所以要把最后一层的sigmoid拿掉。)
- 生成器和判别器的loss不取log (loss指示了训练进程)。对更新后的权重强制截断到一定范围内(比如[-0.01,0.01],以满足论文中提到的lipschitz连续性条件)
- 推荐使用SGD, RMSprop等优化器,不要基于使用动量的优化算法,比如adam,感觉是个trick。(实验中使用的还是adam 区别不大 )
在GAN-based方法中,取秒级序列,然后进行数据的最大最小归一化(不用标准化的原因是,需要保留异常形状,也就是最大最小值不变,但是标准化会改变数据的状态分布),输入到生成模型中,生成的序列再进行数据反解,恢复到原有的scale大小。
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
# 设置随机种子以确保可重复性
torch.manual_seed(42)
np.random.seed(42)
# 超参数
num_epochs = 2000
batch_size = 64
learning_rate = 0.0002
latent_dim = 10
sequence_length = 50
critic_iterations = 5 # 判别器的迭代次数
# 生成真实数据(正弦波)
def generate_real_data(n_samples):
x = np.linspace(0, 2 * np.pi, sequence_length)
y = np.sin(x)
return np.array([y + 0.1 * np.random.randn(*y.shape) for _ in range(n_samples)])
# 创建数据
n_samples = 1000
real_data = generate_real_data(n_samples)
# 转换为 PyTorch 张量
real_data_tensor = torch.tensor(real_data, dtype=torch.float32)
# 定义生成器
class Generator(nn.Module):
def __init__(self):
super(Generator, self).__init__()
self.fc = nn.Sequential(
nn.Linear(latent_dim, 128),
nn.ReLU(),
nn.Linear(128, sequence_length),
nn.Tanh() # 输出范围[-1, 1]
)
def forward(self, z):
return self.fc(z)
# 定义判别器(Critic)
class Critic(nn.Module):
def __init__(self):
super(Critic, self).__init__()
self.fc = nn.Sequential(
nn.Linear(sequence_length, 128),
nn.ReLU(),
nn.Linear(128, 1) # 输出单个值
)
def forward(self, x):
return self.fc(x)
# 初始化模型
generator = Generator()
critic = Critic()
# 定义优化器
optimizer_G = optim.RMSprop(generator.parameters(), lr=learning_rate)
optimizer_C = optim.RMSprop(critic.parameters(), lr=learning_rate)
# 训练模型
for epoch in range(num_epochs):
for i in range(0, len(real_data_tensor), batch_size):
# 真实数据
real_sequences = real_data_tensor[i:i + batch_size]
batch_size_current = real_sequences.size(0)
# 训练判别器(Critic)
for _ in range(critic_iterations):
optimizer_C.zero_grad()
real_outputs = critic(real_sequences)
z = torch.randn(batch_size_current, latent_dim)
fake_sequences = generator(z)
fake_outputs = critic(fake_sequences.detach()) # 不要求梯度
loss_C = -torch.mean(real_outputs) + torch.mean(fake_outputs)
loss_C.backward()
optimizer_C.step()
# Clip weights to enforce Lipschitz constraint
for p in critic.parameters():
p.data.clamp_(-0.01, 0.01)
# 训练生成器
optimizer_G.zero_grad()
fake_outputs = critic(fake_sequences)
loss_G = -torch.mean(fake_outputs)
loss_G.backward()
optimizer_G.step()
if epoch % 100 == 0:
print(f'Epoch [{epoch}/{num_epochs}], C_loss: {loss_C.item():.4f}, G_loss: {loss_G.item():.4f}')
# 生成并显示时间序列
def generate_and_plot(num_samples=5):
z = torch.randn(num_samples, latent_dim)
generated_sequences = generator(z).detach().numpy()
plt.figure(figsize=(15, 5))
for i in range(num_samples):
plt.subplot(1, num_samples, i + 1)
plt.plot(generated_sequences[i], label='Generated Sequence')
plt.title(f'Sample {i + 1}')
plt.ylim(-1.5, 1.5)
plt.tight_layout()
plt.show()
# 生成并可视化时间序列
generate_and_plot(5)
代码说明
-
数据生成:
- 使用
generate_real_data
函数生成包含噪声的正弦波数据,作为真实样本。
- 使用
-
生成器:
Generator
类定义生成器。输入是潜在空间的随机向量,输出是生成的时间序列。
-
判别器(Critic) :
Critic
类定义判别器。输入是时间序列,输出是一个值,表示其“真实程度”。
-
训练过程:
- 使用 RMSprop 优化器。主要流程是先多次训练判别器 (critic),然后训练生成器。为保证 Lipschitz 连续性,裁剪判别器的权重。
-
生成并可视化:
generate_and_plot
函数用来生成新的时间序列并可视化。
代码参考库: