记录几个时间序列生成的工具

142 阅读9分钟

时间序列生成的工具探索

在数据驱动的时代,时间序列数据发挥着至关重要的作用,从金融市场的价格波动到传感器数据的监控,再到天气预测等众多领域。随着深度学习技术的迅猛发展,时间序列生成不仅成为了分析和预测的重要手段,还在生成对抗网络(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()

代码说明

  1. 数据处理: 使用 torchvision.datasets.MNIST 加载 MNIST 数据集,并进行归一化处理。定义数据加载器以便于批处理。
  2. VAE模型: 定义 VAE 类,包括编码器和解码器。编码器将输入图像编码为潜在空间的均值和方差,解码器从潜在空间重构图像。
  3. 重参数化: 在 reparameterize 方法中,通过采样潜在空间的标准正态分布来生成潜在变量。
  4. 损失函数: 定义 VAE 损失函数,包括重构误差和 Kullback-Leibler 散度。
  5. 训练过程: 训练模型并输出每个 Epoch 的损失。
  6. 生成样本: 从潜在空间生成新样本,并使用 Matplotlib 显示结果。

代码位置

github.com/cerlymarco/…

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)

代码说明

  1. 数据生成:

    • 使用 generate_real_data 函数生成包含噪声的正弦波数据,作为真实样本。
  2. 生成器:

    • Generator 类定义生成器。输入是潜在空间的随机向量,输出是生成的时间序列。
  3. 判别器:

    • Discriminator 类定义判别器。输入是时间序列,输出是判别结果,即其为真实数据的概率。
  4. 训练过程:

    • 使用二元交叉熵损失函数训练 GAN。首先训练判别器,然后训练生成器以欺骗判别器。
  5. 生成并可视化:

    • 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)

代码说明

  1. 数据生成:

    • 使用 generate_real_data 函数生成包含噪声的正弦波数据,作为真实样本。
  2. 生成器:

    • Generator 类定义生成器。输入是潜在空间的随机向量,输出是生成的时间序列。
  3. 判别器(Critic) :

    • Critic 类定义判别器。输入是时间序列,输出是一个值,表示其“真实程度”。
  4. 训练过程:

    • 使用 RMSprop 优化器。主要流程是先多次训练判别器 (critic),然后训练生成器。为保证 Lipschitz 连续性,裁剪判别器的权重。
  5. 生成并可视化:

    • generate_and_plot 函数用来生成新的时间序列并可视化。

代码参考库:

github.com/zhangsunny/…