PyTorch实战——生成对抗网络数值数据生成
PyTorch实战——生成对抗网络数值数据生成
- 0. 前言
- 1. 独热编码 (One-hot Encoding)
- 2. 使用 GAN 生成数值数据
- 2.1 训练数据处理
- 2.2 模型构建
- 2.3 模型训练
- 3. 生成器保存与加载
- 相关链接
0. 前言
在本节中,构建并训练生成对抗网络 (Generative Adversarial Network
, GAN
),生成一个包含 10
个整数的序列,这些整数在 0
到 99
之间,并且都是 5
的倍数。主要步骤与生成指数增长曲线类似,唯一的区别是训练集不是数据点 (x, y)
,而是一个包含所有介于 0
到 99
之间且为 5 的倍数的整数序列。
在本节,首先学习如何将训练数据转换为神经网络能够理解的格式——独热编码 (one-hot encoding
)。然后,将独热编码变量转换回 0
到 99
之间的整数,便于人类理解。换句话说,实际上是在将数据在可读格式与模型所需的格式之间进行转换。之后,将创建一个判别器和一个生成器,并训练 GAN
,使用提前停止方法来判断训练何时结束。训练完成后,丢弃判别器,使用已训练好的生成器生成具有所需模式的整数序列。
1. 独热编码 (One-hot Encoding)
独热编码是一种在机器学习和数据预处理过程中用于将分类数据表示为二进制向量的技术。分类数据由类别或标签组成,例如颜色、动物种类等,这些数据本身不是数值型的。机器学习算法通常使用数值数据,因此需要将分类数据转换为数值格式。
假设处理颜色分类特征,鲜花的颜色可以是“红色”、“绿色”或“蓝色”。使用独热编码,每个类别表示为一个二进制向量,创建三个二进制列,每个类别对应一列。颜色“红色”的独热编码为 [1, 0, 0]
,颜色“绿色”的独热编码为 [0, 1, 0]
,颜色“蓝色”为 [0, 0, 1]
。这样做能够保留分类信息,而不会引入类别之间的顺序关系,每个类别都是独立的。
(1) 定义 onehot_encoder()
函数,用于将整数转换为独热编码变量:
import torch
device="cuda" if torch.cuda.is_available() else "cpu"def onehot_encoder(position,depth):onehot=torch.zeros((depth,))onehot[position]=1return onehot
该函数接受两个参数:第一个参数 position
是值被设置为 1
的位置索引,第二个参数 depth
是独热编码变量的长度。例如,打印 onehot_encoder(1, 5)
:
print(onehot_encoder(1,5))
结果如下所示,可以看到一个五维张量,其中第二个位置(索引值为 1
)为 1
,其余位置为 0
:
tensor([0., 1., 0., 0., 0.])
(2) 了解了独热编码的工作原理后,可以将 0
到 99
之间的任意整数转换为独热编码变量:
def int_to_onehot(number):onehot=onehot_encoder(number,100)return onehot
(3) 使用函数 int_to_onehot()
将数字 75
转换为一个 100
维的张量:
onehot75=int_to_onehot(75)
print(onehot75)
输出结果如下所示,结果为一个 100
维的张量,其中第 76
位(索引值为 75
) 为 1
,其余所有位置都为 0
:
tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
函数 int_to_onehot()
将整数转换为独热编码变量,即将人类可读的形式转换为模型可接受的形式。
(4) 接下来,将模型可接受的形式转换回人类可读的形式。假设有一个独热编码变量,定义函数函数 onehot_to_int()
将它转换为人类能够理解的整数:
def onehot_to_int(onehot):num=torch.argmax(onehot)return num.item()
函数 onehot_to_int()
接受参数 onehot
,并根据值为 1
的位置转换为一个整数。测试 onehot_to_int()
函数,将刚才创建的独热编码张量 onehot75
作为输入:
print(onehot_to_int(onehot75))
# 75
2. 使用 GAN 生成数值数据
本节的目标是构建并训练一个模型,使得生成器能够生成一个由 10
个整数构成的序列,这些整数都是 5
的倍数。首先准备训练数据,然后将它们转换为模型可接受的数字。最后,使用训练好的生成器来生成所需模式。
2.1 训练数据处理
(1) 函数 gen_sequence()
生成一个由 10
个整数构成的序列,这些整数都是 5
的倍数:
def gen_sequence():indices = torch.randint(0, 20, (10,))values = indices*5return values
首先使用 PyTorch
的 randint()
方法生成 10
个 0
到 19
之间的整数。然后,将这些数字乘以 5
,并将它们转换为 PyTorch
张量,得到 10
个都是 5
的倍数的整数。
(2) 尝试生成一组训练数据:
sequence=gen_sequence()
print(sequence)
# tensor([55, 55, 0, 25, 70, 15, 65, 75, 90, 80])
(3) 接下来,将每个数字转换为独热编码向量,以便可以将其输入到神经网络中:
import numpy as npdef gen_batch():# 创建一个由 10 个数字组成的序列,所有数字都是 5 的倍数sequence=gen_sequence()# 将每个整数转换为一个 100 维的独热编码变量batch=[int_to_onehot(i).numpy() for i in sequence]batch=np.array(batch)return torch.tensor(batch)
batch=gen_batch()
函数 gen_batch()
用于创建批数据,以便将它们输入到神经网络中进行训练。
(4) 定义函数 data_to_num()
将独热编码变量转换为整数序列:
def data_to_num(data):# 根据 100 维向量中的最大值,将向量转换为整数num=torch.argmax(data,dim=-1)return num
numbers=data_to_num(batch)
2.2 模型构建
接下来,创建两个神经网络:判别器 D
和生成器 G
。构建生成对抗网络 (Generative Adversarial Network
, GAN
) 来生成所需的数字模式,判别器网络是一个二分类器,用于区分虚假样本和真实样本,生成器网络用于生成包含 10
个数字的序列。
(1) 首先,创建判别器神经网络:
from torch import nn
D=nn.Sequential(nn.Linear(100,1),nn.Sigmoid()).to(device)
由于将整数转换为 100
维的独热编码变量,因此模型的第一个 Linear
层中使用 100
作为输入大小。最后的 Linear
层只有一个输出特征,使用 sigmoid
激活函数将输出压缩到 [0, 1]
范围内,可以解释为样本是真实样本的概率 p
,概率 1 - p
则表示样本是虚假样本的概率。
(2) 生成器的任务是创建一组数字,使其能够被判别器 D
分类为真实。也就是说,生成器 G
试图创建一组数字,以最大化判别器 D
认为这些数字来自训练数据集的概率。创建生成器神经网络 G
:
G=nn.Sequential(nn.Linear(100,100),nn.ReLU()).to(device)
从一个 100
维的潜空间中获取随机噪声向量输入给生成器。生成器基于输入生成一个包含 100
个值的张量,在最后一层使用 ReLU
激活函数,以确保输出是非负值,因为我们试图生成的 100
个值的取值为 0
或 1
。
(3) 判别器和生成器都使用 Adam
优化器,学习率设为 0.0005
:
loss_fn=nn.BCELoss()
lr=0.0005
optimD=torch.optim.Adam(D.parameters(),lr=lr)
optimG=torch.optim.Adam(G.parameters(),lr=lr)
2.3 模型训练
(1) 定义 train_D_G()
函数:
real_labels=torch.ones((10,1)).to(device)
fake_labels=torch.zeros((10,1)).to(device)def train_D_G(D,G,loss_fn,optimD,optimG):# 生成真实数据样本true_data=gen_batch().to(device)# 由于是真实样本,使用 1 作为标签preds=D(true_data)loss_D1=loss_fn(preds,real_labels.reshape(10,1))optimD.zero_grad()loss_D1.backward()optimD.step()# 在虚假数据上训练判别器noise=torch.randn(10,100).to(device)generated_data=G(noise)# 由于是虚假样本,使用 0 作为标签preds=D(generated_data)loss_D2=loss_fn(preds,fake_labels.reshape(10,1))optimD.zero_grad()loss_D2.backward()optimD.step()# 训练生成器 noise=torch.randn(10,100).to(device)generated_data=G(noise)# 使用 1 作为标签,因为生成器想要欺骗判别器preds=D(generated_data)loss_G=loss_fn(preds,real_labels.reshape(10,1))optimG.zero_grad()loss_G.backward()optimG.step()return generated_data
(2) 使用提前停止类,以便在满足需求时停止训练。同时,定义 distance()
函数衡量训练集与生成数据样本之间的差异,计算每个生成数字除以 5
的余数与全零向量的均方误差 (Mean Squared Error
, MSE
)。当所有生成的数字都是 5
的倍数时,结果为 0
:
class EarlyStop:def __init__(self, patience=1000): # 将 patience 的默认值设置为 1000self.patience = patienceself.steps = 0self.min_gdif = float('inf')def stop(self, gdif): # 定义 stop() 方法# # 如果生成分布与真实分布之间的新差异大于当前最小差异,则更新 min_gdif 的值if gdif < self.min_gdif:self.min_gdif = gdifself.steps = 0elif gdif >= self.min_gdif:self.steps += 1# 如果模型在 1000 个 epoch 内没有改进,则停止训练if self.steps >= self.patience:return Trueelse:return Falsestopper=EarlyStop(800) # 创建 Earlytop() 类实例mse=nn.MSELoss()
real_labels=torch.ones((10,1)).to(device)
fake_labels=torch.zeros((10,1)).to(device)
# 定义 distance() 函数,用于计算生成数字的损失
def distance(generated_data): nums=data_to_num(generated_data)remainders=nums%5ten_zeros=torch.zeros((10,1)).to(device)mseloss=mse(remainders,ten_zeros)return mselossfor i in range(10000):gloss=0dloss=0generated_data=train_D_G(D,G,loss_fn,optimD,optimG) # 训练 GAN 一个 epoch dis=distance(generated_data)if stopper.stop(dis)==True:break # 每训练 50 个 epoch 后,打印生成的整数序列if i % 50 == 0:print(data_to_num(generated_data))
运行代码,输出结果如下所示:
在每一次迭代中,生成包含 10
个数字的数据样本。首先使用真实样本来训练判别器 D
,然后生成器生成一批虚假样本,使用虚假样本再次训练判别器 D
。最后,使用生成器再次生成一批虚假样本用于训练生成器 G
。训练过程中,如果生成器网络从上次达到最小损失以来经过了 800
个 epoch
训练仍未改善,就停止训练。每 50
个 epoch
,会输出生成器生成的 10
个数字序列,观察其是否确实都是 5 的倍数。
3. 生成器保存与加载
(1) 丢弃判别器并将训练好的生成器保存到本地文件夹:
import os
os.makedirs("files", exist_ok=True)
scripted = torch.jit.script(G)
scripted.save('files/num_gen.pt')
(2) 将生成器保存到本地文件夹后,要使用生成器,只需加载模型,并用它来生成一组整数序列:
# 加载已保存的生成器
new_G=torch.jit.load('files/num_gen.pt',map_location=device)
new_G.eval()
# 获取随机噪声向量
noise=torch.randn((10,100)).to(device)
# 将随机噪声向量输入训练好的模型,生成一系列整数
new_data=new_G(noise)
print(data_to_num(new_data))
输出结果如下所示,生成的数字都是 5
的倍数:
tensor([55, 20, 75, 20, 80, 55, 35, 55, 30, 55], device='cuda:0')
相关链接
PyTorch生成式人工智能实战:从零打造创意引擎
PyTorch实战(1)——神经网络与模型训练过程详解
PyTorch实战(2)——PyTorch基础
PyTorch实战(3)——使用PyTorch构建神经网络
PyTorch实战(4)——卷积神经网络详解
PyTorch实战(5)——分类任务详解
PyTorch实战(6)——生成模型(Generative Model)详解
PyTorch实战(7)——生成对抗网络实践详解