六、探索生成对抗网络
生成对抗网络(GAN)是一种机器学习技术,其中同时训练两种模型:一种专门用于创建伪造数据,另一种专门用于区分真实数据和伪造数据。 真实数据。 术语生成反映了以下事实:这些神经网络用于创建新数据,而术语对抗来自以下事实:两个模型相互竞争,从而提高了生成的数据的质量。
GAN 中的两个模型称为生成器和判别器,其中生成器负责创建数据,判别器接收数据并将其分类为真实数据或由生成器生成。 生成器的目标是创建与训练集中的真实数据没有区别的数据样本。
我们可以用一个类比来理解 GAN 的概念,即犯罪分子(产生者)想要伪造金钱,而侦探(判别器)则试图抓住他。 假币的外观越真实,侦探在检测到假币时就必须越好,越高效,这意味着伪钞的质量必须提高到足以使侦探无法发现。
生成器从判别器的分类反馈中学习。 判别器的目标是确定其输入是真实的(来自训练数据集)还是伪造的(来自生成器),因此,每当辨别器将虚假图像分类为真实的错误时,生成器都会获得表现良好的正反馈。 相反,每当判别器正确捕捉到生成器生成的图像为伪造图像时,生成器就会收到需要改进的反馈。
判别器基本上是一个分类器,并且像任何分类器一样,它从真实标签(它在这种情况下是真实的或假的)中了解到其预测有多远。 因此,随着生成器在生成逼真的数据方面变得更好,判别器在从真实标签中辨别伪造品方面也必须变得更好。 这样,两个网络都可以同时改善。
从概念上讲,生成器必须能够从训练示例中捕获真实数据的特征,以使其生成的样本与真实数据无法区分。 生成器自己学习创建模式(而不是识别图像分类问题中的模式)。 通常,生成器的输入通常是随机数的向量。
让我们看一下 GAN 的以下架构图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bkbFTOT0-1681786052328)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-ai-fund/img/13480561-764e-43be-b39a-07d7656540db.png)]
在此图中,有一个数据源,其中包含训练图像x
,生成器必须捕获其属性并重新创建。 生成器接收随机向量z
,该向量充当生成器创建伪图像的种子。 生成器获取种子并生成x*
图像,判别器从真实和虚假图像中获取图像,并输出给定输入为真实的概率(假设真实图像用 1 表示,伪图像用 0 表示)。 然后,我们获得分类误差,并使用它来迭代训练判别器和生成器。 判别器的目的是使分类误差最小,而生成器的目的是使分类误差最大化。
从理论上讲,生成器和判别器达到平衡,其中生成器已捕获了生成的伪图像中真实图像的所有特征,而从进一步的训练中没有任何收获。 类似地,判别器只能以 50% 的概率猜测图像是伪造的还是真实的,因为这两个图像就其性质而言完全无法区分。 在那个状态下,GAN 已经收敛。 然而,实际上,这种状态很难实现。 在本章中,我们将探讨 GAN 的概念以及 PyTorch 中各种 GAN 的实现。
在本章中,我们将介绍以下秘籍:
- 创建一个 DCGAN 生成器
- 创建 DCGAN 判别器
- 训练 DCGAN 模型
- 可视化 DCGAN 结果
- 使用 PyTorch Hub 运行 PGGAN
技术要求
强烈建议,对于本章中实现的秘籍,代码应在具有 NVIDIA GPU 且启用了 CUDA 和 CUDNN 的计算机上运行,因为本章中的秘籍需要大量计算。
创建一个 DCGAN 生成器
在本秘籍及其后续秘籍中,我们将实现 DCGAN。 DCGAN 代表“深度卷积 GAN”; 与原始 GAN 相比,它们有很大的改进。 在 DCGAN 中,我们使用卷积神经网络,而不是原始 GAN 中的全连接网络。 在第 3 章,“用于计算机视觉的卷积神经网络”中,我们看到了第 2 章“处理神经网络”中全连接分类器是什么样,是该领域的一项改进; DCGAN 与原始 GAN 的情况相同。 在 DCGAN 中,我们将使用批量归一化,这是一种技术,它可以将作为输入输入到下一层的层的输出归一化。 批量归一化允许网络的每一层独立于其他层进行学习,从而减少了协变量偏移。
批量归一化是通过缩放来实现的,以使平均值为 0,方差为 1。在此秘籍中,我们将生成类似于 MNIST 数据集的手写数字,其中包含来自噪声向量的数据。 我们将扩展此噪声向量,将其转换为 2D 矩阵,最后将其转换为28 x 28
黑白图像。 为了增加高度和宽度,我们必须执行与卷积操作相反的操作,这称为反卷积。 我们将在使用卷积执行分类任务时执行此操作。 在执行反卷积时,我们将增加高度和宽度,同时减少通道数。
以下是我们的 DCGAN 生成器的架构图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BeGUMczT-1681786052329)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-ai-fund/img/89189246-b3a7-49dc-acc5-78bc00c5f9b7.png)]
请注意,我们将使用第 3 章,“用于计算机视觉的卷积神经网络”中的概念,因此再次阅读这些秘籍将非常有用。
操作步骤
在此秘籍中,我们将实现 GAN 网络的生成器端:
1.我们将从导入开始:
>import torch >>import torch.nn as nn >>import torchvision.transforms as transforms
2.然后,我们将定义转换:
>>transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5, ), (0.5, )), ])
3.然后,我们将提供该设备:
>>device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
3.现在,我们将定义生成器类:
>>class Generator_model(nn.Module): def __init__(self, z_dim): super().__init__()
4.然后,我们将定义生成器的单元:
self.fc = nn.Linear(z_dim, 256 * 7 * 7) self.gen = nn.Sequential( nn.ConvTranspose2d(256, 128, 4, 2, 1), nn.BatchNorm2d(128), nn.LeakyReLU(0.01), nn.ConvTranspose2d(128, 64, 3, 1, 1), nn.BatchNorm2d(64), nn.LeakyReLU(0.01), nn.ConvTranspose2d(64, 1, 4, 2, 1), nn.Tanh() )
5.现在我们将定义forward
方法:
def forward(self, input): x = self.fc(input) x = x.view(-1, 256, 7, 7) return self.gen(x)
6.最后,我们将为生成器模型创建对象:
>>generator = Generator_model(z_dim).to(device)
完成此过程后,我们已经准备好 DCGAN 生成器。
工作原理
在此秘籍中,我们进行了变换以将图像转换为张量并对其进行归一化,就像在第 3 章,“用于计算机视觉的卷积神经网络”中所做的一样。 然后,我们确定了机器上的设备:CPU 或 GPU。 然后,我们定义了从nn.Module
类继承的Generator_model
类,就像在所有以前的架构中所做的一样。
在构造器中,我们传递了z_dim
参数,这是我们的噪声向量大小。 然后,我们定义了一个全连接单元self.fc
,我们将噪声向量传递给该单元,并为其提供了256 * 7 * 7
输出。 然后,我们定义了一个称为self.gen
的nn.Sequential
单元,其中包含用于定义生成器的关键组件。 我们使用 PyTorch 中提供的nn.ConvTranspose2d
,nn.BatchNorm2d
和nn.LeakyReLU
使用一组反卷积,批量规范化和激活层。 ConvTranspose2d
接受输入通道,输出通道,核大小,步幅和填充等参数。 BatchNorm2d
接受上一层的特征/通道数作为其参数,而 LeakyReLU 接受负斜率的角度。
与 ReLU 不同,LeakyReLU 允许传递小的梯度信号以获取负值。 它使来自判别器的梯度流入生成器。 我们在输出层中使用了 tanh 激活,但是从 DCGAN 论文中我们观察到,使用有界激活可以使模型学会快速饱和并覆盖训练分布的色彩空间。 tanh 的对称性在这里可能是一个优势,因为网络应该以对称方式处理较深的颜色和较浅的颜色。
让我们看一下forward
方法的工作方式。 z_dim
维度的输入噪声向量经过全连接层以提供 12544 输出。 然后,我们将 12544 输出调整为256 x 7 x 7
,其中 256 是通道数。 256 x 7 x 7
张量然后通过反卷积层以提供128 x 14 x 14
输出,然后通过具有 128 个特征和泄漏 ReLU 的Batchnorm
层。 128 x 14 x 14
然后在第二次反卷积中转换为64 x 14 x 14
张量,在第三次反卷积中变为1 x 28 x 28
张量; 这些只是我们需要的尺寸。 然后,我们创建生成器对象并将其移动到设备。
另见
您可以通过这个页面了解更多有关 DCGAN 的信息。
创建 DCGAN 判别器
在本秘籍中,我们将探讨 GAN 网络的鉴别方。 基本上,判别器是在两个类别之间进行分类的分类器,即根据给定图像是来自数据集的真实图像还是由生成器网络生成的伪图像。 正是基于来自判别器网络的反馈,生成器学会了创建更好的图像,以试图使判别器误以为来自生成器的图像是真实的。 现在,在 DCGAN 中,将使用卷积神经网络构建判别器。
以下是我们的判别器的架构图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z9sEwc0U-1681786052330)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-ai-fund/img/af31d30e-00c9-47e1-a24a-89194da6033f.png)]
准备好
在本秘籍中,我们将严重依赖第 3 章,“用于计算机视觉的卷积神经网络”的秘籍,因此最好快速浏览第 3 章,“用于计算机视觉的卷积神经网络”。
操作步骤
在此秘籍中,我们将构建 GAN 的判别器端:
1.我们将从导入开始:
>>import torch.nn.functional as F
2.然后,我们将定义判别器类:
>>class Discriminator_model(nn.Module): def __init__(self): super().__init__()
3.接下来,我们定义鉴别单元:
self.disc = nn.Sequential( nn.Conv2d(1, 32, 3, 2, 1), nn.LeakyReLU(0.01), nn.Conv2d(32, 64, 3, 2, 1), nn.BatchNorm2d(64), nn.LeakyReLU(0.01), nn.Conv2d(64, 128, 3, 2, 1), nn.BatchNorm2d(128), nn.LeakyReLU(0.01) )
4.然后定义最后一个全连接层:
self.fc = nn.Linear(2048, 1)
5.然后定义forward()
方法:
def forward(self, input): x = self.disc(input) return F.sigmoid(self.fc(x.view(-1, 2048)))
6.然后我们创建鉴别对象:
>>discriminator = Discriminator_model().to(device)
现在我们已经准备好判别器。
工作原理
在本秘籍中,我们定义了一个分类器; 使用nn.Sequential()
定义卷积,激活和批量规范化单元的数组; 并且还定义了最后一个全连接层,该层采用平坦的张量并给出通过 Sigmoid 层的单个输出。 由于只有两个类,因此我们最后使用了 Sigmoid 层。 输入是尺寸为1 x 28 x 28
的图像张量,并经过第一卷积单元以给出尺寸为32 x 14 x 14
的输出张量。 第二个卷积层使它成为64 x 7 x 7
张量,然后从那里变成128 x 4 x 4
。 之后,我们将拉平并使张量穿过全连接层。
另见
您可以在这个页面上了解有关 DCGAN 的信息。
训练 DCGAN 模型
我们在前两个秘籍中定义了生成器,即“创建 DCGan 生成器”和“创建 DCGAN 判别器”。 在本秘籍中,我们将继续训练 GAN 模型。 请记住,生成器的目标是创建与数据集尽可能相似的图像,而判别器的目标是区分真实图像和生成的图像。 从理论上讲,生成器将捕获数据集中图像的所有特征,并且无法学习更多信息,而判别器只能猜测图像是真实的还是生成的。 在本秘籍中,我们将通过整合到目前为止已经创建的生成器和判别器来完成 DCGANs 模型的训练。
准备好
我们将使用torchsummary
库来查看我们的模型层,它们的输出形状和它们的参数。 为此,我们将使用以下命令安装该库:
pip install torchsummary
准备好此安装后,我们将继续进行下一步。
操作步骤
在此秘籍中,我们将完成 GAN 训练:
- 首先导入:
>>from torchsummary import summary >>import torch.optim as optim >>import torchvision.utils as vutils
- 然后我们初始化生成器和判别器的权重:
>>def weights_init(m): classname = m.__class__.__name__ if classname.find('Conv') != -1: nn.init.normal_(m.weight.data, 0.0, 0.02) elif classname.find('BatchNorm') != -1: nn.init.normal_(m.weight.data, 1.0, 0.02) nn.init.constant_(m.bias.data, 0) >>generator.apply(weights_init) >>discriminator.apply(weights_init)
- 然后我们打印生成器的摘要:
>>summary(generator, (100, ))
这给我们以下输出:
---------------------------------------------------------------- Layer (type) Output Shape Param # ================================================================ Linear-1 [-1, 12544] 1,266,944 ConvTranspose2d-2 [-1, 128, 14, 14] 524,416 BatchNorm2d-3 [-1, 128, 14, 14] 256 LeakyReLU-4 [-1, 128, 14, 14] 0 ConvTranspose2d-5 [-1, 64, 14, 14] 73,792 BatchNorm2d-6 [-1, 64, 14, 14] 128 LeakyReLU-7 [-1, 64, 14, 14] 0 ConvTranspose2d-8 [-1, 1, 28, 28] 1,025 Tanh-9 [-1, 1, 28, 28] 0 ================================================================ Total params: 1,866,561 Trainable params: 1,866,561 Non-trainable params: 0 ---------------------------------------------------------------- Input size (MB): 0.00 Forward/backward pass size (MB): 0.97 Params size (MB): 7.12 Estimated Total Size (MB): 8.09 ----------------------------------------------------------------
- 然后,我们将打印判别器摘要:
>>summary(discriminator, (1, 28, 28))
这给我们以下输出:
---------------------------------------------------------------- Layer (type) Output Shape Param # ================================================================ Conv2d-1 [-1, 32, 14, 14] 320 LeakyReLU-2 [-1, 32, 14, 14] 0 Conv2d-3 [-1, 64, 7, 7] 18,496 BatchNorm2d-4 [-1, 64, 7, 7] 128 LeakyReLU-5 [-1, 64, 7, 7] 0 Conv2d-6 [-1, 128, 4, 4] 73,856 BatchNorm2d-7 [-1, 128, 4, 4] 256 LeakyReLU-8 [-1, 128, 4, 4] 0 Linear-9 [-1, 1] 2,049 ================================================================ Total params: 95,105 Trainable params: 95,105 Non-trainable params: 0 ---------------------------------------------------------------- Input size (MB): 0.00 Forward/backward pass size (MB): 0.21 Params size (MB): 0.36 Estimated Total Size (MB): 0.58 ----------------------------------------------------------------
- 接下来,我们将定义损失函数:
>>criterion = nn.BCELoss()
- 我们还将创建一个固定的噪声:
>>fixed_noise = torch.randn(64, z_dim, device=device)
- 现在,我们将定义优化器函数:
>>doptimizer = optim.Adam(discriminator.parameters()) >>goptimizer = optim.Adam(generator.parameters())
- 然后,我们将为判别器设置标签:
>>real_label, fake_label = 1, 0
- 我们还将准备存储我们训练中的指标:
>>image_list = [] >>g_losses = [] >>d_losses = [] >>iterations = 0 >>num_epochs = 50
- 现在,我们开始训练循环:
>>for epoch in range(num_epochs):
- 然后我们遍历数据:
print(f'Epoch : | {epoch+1:03} / {num_epochs:03} |') for i, data in enumerate(train_loader):
- 然后,我们通过清除梯度来开始训练判别器:
discriminator.zero_grad()
- 然后我们获取图像:
real_images = data[0].to(device) size = real_images.size(0)
- 然后,我们为这些图像创建标签:
label = torch.full((size,), real_label, device=device)
- 接下来,我们得到判别器输出:
d_output = discriminator(real_images).view(-1)
- 然后我们计算判别器误差:
derror_real = criterion(d_output, label)
- 接下来,我们计算梯度:
derror_real.backward()
- 现在我们将创建一个噪声向量:
noise = torch.randn(size, z_dim, device=device)
- 接下来,我们将噪声向量传递给生成器:
fake_images = generator(noise)
- 然后,我们为生成的图像创建标签:
label.fill_(0)
- 然后我们将它们传递给判别器:
d_output = discriminator(fake_images.detach()).view(-1)
- 接下来,我们得到误差和梯度:
derror_fake = criterion(d_output, label) derror_fake.backward() derror_total = derror_real + derror_fake
- 然后,我们更新判别器权重:
doptimizer.step()
- 我们通过清除梯度开始训练生成器:
generator.zero_grad()
- 然后,我们将标签从假更改为真:
label.fill_(1)
- 接下来,我们得到判别器输出:
d_output = discriminator(fake_images).view(-1)
- 然后我们计算生成器损失和梯度并更新生成器权重:
gerror = criterion(d_output, label) gerror.backward() goptimizer.step()
- 然后我们保存了损失:
if i % 50 == 0: print(f'| {i:03} / {len(train_loader):03} | G Loss: {gerror.item():.3f} | D Loss: {derror_total.item():.3f} |') g_losses.append(gerror.item()) d_losses.append(derror_total.item())
- 然后,我们从固定噪声中保存图像:
if (iterations % 500 == 0) or ((epoch == num_epochs-1) and (i == len(train_loader)-1)): with torch.no_grad(): fake_images = generator(fixed_noise).detach().cpu() image_list.append(vutils.make_grid(fake_images, padding=2, normalize=True)) iterations += 1
以下是示例输出:
Epoch : | 001 / 050 | | 000 / 469 | G Loss: 1.939 | D Loss: 1.432 | | 050 / 469 | G Loss: 3.920 | D Loss: 0.266 | | 100 / 469 | G Loss: 3.900 | D Loss: 0.406 | | 150 / 469 | G Loss: 3.260 | D Loss: 0.230 | | 200 / 469 | G Loss: 3.856 | D Loss: 0.556 | | 250 / 469 | G Loss: 4.097 | D Loss: 0.123 | | 300 / 469 | G Loss: 2.377 | D Loss: 0.416 | | 350 / 469 | G Loss: 2.984 | D Loss: 0.416 | | 400 / 469 | G Loss: 3.262 | D Loss: 0.140 | | 450 / 469 | G Loss: 3.469 | D Loss: 0.849 | Epoch : | 002 / 050 | | 000 / 469 | G Loss: 2.057 | D Loss: 0.484 | | 050 / 469 | G Loss: 2.108 | D Loss: 0.435 | | 100 / 469 | G Loss: 1.714 | D Loss: 0.862 | | 150 / 469 | G Loss: 3.902 | D Loss: 0.199 | | 200 / 469 | G Loss: 3.869 | D Loss: 0.086 | | 250 / 469 | G Loss: 2.390 | D Loss: 0.208 | | 300 / 469 | G Loss: 3.008 | D Loss: 0.586 | | 350 / 469 | G Loss: 4.662 | D Loss: 0.074 | | 400 / 469 | G Loss: 3.353 | D Loss: 0.368 | | 450 / 469 | G Loss: 5.080 | D Loss: 0.110 | Epoch : | 003 / 050 | | 000 / 469 | G Loss: 7.159 | D Loss: 0.008 | | 050 / 469 | G Loss: 5.087 | D Loss: 0.056 | | 100 / 469 | G Loss: 4.232 | D Loss: 0.184 | | 150 / 469 | G Loss: 5.037 | D Loss: 0.141 | | 200 / 469 | G Loss: 5.636 | D Loss: 0.570 | | 250 / 469 | G Loss: 3.624 | D Loss: 0.304 | | 300 / 469 | G Loss: 4.291 | D Loss: 0.214 | | 350 / 469 | G Loss: 2.901 | D Loss: 0.247 | | 400 / 469 | G Loss: 3.703 | D Loss: 0.643 | | 450 / 469 | G Loss: 1.149 | D Loss: 1.035 | Epoch : | 004 / 050 | | 000 / 469 | G Loss: 3.317 | D Loss: 0.202 | | 050 / 469 | G Loss: 2.990 | D Loss: 0.350 | | 100 / 469 | G Loss: 2.680 | D Loss: 0.162 | | 150 / 469 | G Loss: 2.934 | D Loss: 0.391 | | 200 / 469 | G Loss: 3.736 | D Loss: 0.215 | | 250 / 469 | G Loss: 3.601 | D Loss: 0.199 | | 300 / 469 | G Loss: 4.288 | D Loss: 0.164 | | 350 / 469 | G Loss: 2.978 | D Loss: 0.086 | | 400 / 469 | G Loss: 3.827 | D Loss: 0.189 | | 450 / 469 | G Loss: 4.283 | D Loss: 0.216 | Epoch : | 005 / 050 | | 000 / 469 | G Loss: 4.456 | D Loss: 0.250 | | 050 / 469 | G Loss: 4.886 | D Loss: 0.160 | | 100 / 469 | G Loss: 1.844 | D Loss: 0.447 | | 150 / 469 | G Loss: 3.680 | D Loss: 0.505 | | 200 / 469 | G Loss: 4.428 | D Loss: 0.200 | | 250 / 469 | G Loss: 4.270 | D Loss: 0.222 | | 300 / 469 | G Loss: 4.617 | D Loss: 0.102 | | 350 / 469 | G Loss: 3.920 | D Loss: 0.092 | | 400 / 469 | G Loss: 4.010 | D Loss: 0.392 | | 450 / 469 | G Loss: 1.705 | D Loss: 0.651 |
至此,我们已经完成了 DCGAN 的训练。
工作原理
我们从weights_init
函数开始,该函数用于从均值 0 和标准差 0.02 的正态分布中随机初始化所有权重。 初始化模型后,函数将模型作为输入,并重新初始化所有卷积,卷积转置和批归一化层。
然后,我们使用torchsummary
库打印模型的摘要; 为此,我们将模型与输入维一起传递。 方便地查看我们所有的输出尺寸是否正确,并检查每一层中参数的数量和大小。 接下来,我们定义损失函数,并使用二进制交叉熵损失,因为只有一个输出具有两个可能的状态,分别表示图像是真实的 1 还是伪造的 0。
我们还创建了固定噪声,用于可视化 GAN 模型在迭代过程中的改进。 我们使用 ADAM 优化器分别更新了生成器和判别器goptimizer
和doptimizer
的权重。 然后,我们进行了准备以存储一些模型指标,以查看模型在迭代过程中的变化,然后开始训练循环。
我们遍历了每个小批量并开始训练判别器。 我们仅从 MNIST 数据集中获取图像,然后使用real_images = data[0].to(device)
将其移动到设备中; 由于图像全部来自 MNIST 数据集,因此我们知道它们是真实的,因此我们创建了与小批量相同大小的标签向量,并用真实图像标签 1 进行填充。然后,将这些真实图像传递到判别器来预测,然后使用此预测从准则中得出误差derror_real
并计算梯度。 然后,我们创建了相等数量的噪声向量,并将它们传递给生成器以生成图像,然后将这些生成的图像传递给判别器以获取预测,然后从准则derror_fake
中获取误差。 然后,我们再次进行此操作以计算和累积梯度。 然后,我们从真实图像和伪图像中获得了误差之和,以得出总的判别器误差,并更新判别器的权重。
然后,我们开始训练生成器,并且生成器应该能够欺骗判别器。 生成器必须纠正判别器正确预测生成的图像为假的情况。 因此,只要来自判别器的预测将生成的图像标记为伪造,就会增加生成器损失gerror
。 然后,我们计算梯度并更新生成器权重。
然后,我们定期显示模型指标,并保存固定噪声生成器生成的图像,以可视化模型在各个周期的表现。
更多
您可以利用网络的超参数发挥更多的作用-例如,您可以对判别器优化器使用与生成器不同的学习率,或者对判别器的每次更新训练生成器两次或三次。
另见
您可以在 这个页面和这个页面上看到训练和架构 DCGAN 的另一个示例。
可视化 DCGAN 结果
以前我们已经看到,使用 GAN,生成器和判别器相互竞争,并且这样做可以产生越来越好的图像。 但是,从理论上讲,它们达到了生成器已捕获真实图像的所有特征的程度,并且生成器无法学习。 同样,判别器只能猜测给定图像是真实的还是伪造的,成功机会为 50/50。 在这一点上,据说 GAN 已经收敛。
现在,任何方面的改善都会导致另一面的结果降低,这是零和状态或纳什平衡; 但是在实践中很难实现,因为生成器和判别器都在不断变化,因此检查 GAN 表现的最佳方法是通过图形和图表。 在本秘籍中,我们将快速了解可视化。
准备好
对于此秘籍,您必须具有matplotlib
和numpy
库,可以使用pip
如下安装它们:
pip install matplotlib pip install numpy
安装这些工具后,我们将继续进行操作。
操作步骤
在此秘籍中,我们将快速绘制 GAN 中的图形和图像:
- 我们将从导入开始:
>>import matplotlib.pyplot as plt >>import numpy as np
- 然后,我们将添加图的大小和标题:
>>plt.figure(figsize=(10,5)) >>plt.title("Generator and Discriminator Loss During Training")
- 接下来,我们添加生成器和判别器损失:
>>plt.plot(g_losses,label="Generator") >>plt.plot(d_losses,label="Discriminator")
- 然后,我们添加
x
和y
轴标签:
>>plt.xlabel("iterations") >>plt.ylabel("Loss")
- 接下来,我们为图添加图例:
>>plt.legend()
- 最后,我们显示该图:
>>plt.show()
这将给出以下输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-clY0Jfkn-1681786052330)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-ai-fund/img/184af20e-79e7-4da1-b8b4-5168a57aef8d.png)]
- 然后,我们遍历
image_list
中的图像并显示它们:
>>for image in image_list: plt.imshow(np.transpose(image,(1,2,0))) plt.show()
结果为以下输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xdtdIaSp-1681786052330)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-ai-fund/img/3ea3ae9d-d0f9-413f-b06b-54a248081ddd.png)]
在这里,我们看到了 DCGAN 生成的手写图像。
工作原理
在本秘籍中,我们使用 matplotlib 绘制图形和图像。 我们使用figure()
和title()
方法设置图形尺寸和标题,然后使用plot()
方法绘制生成器和判别器损失。 我们还使用xlabel
和ylabel
方法添加了x
和y
标签。 我们还使用legend()
方法为图添加了图例,最后使用show()
方法显示了图。
我们遍历训练期间保存在image_list
中的图像,并使用 NumPy 的transpose()
方法以所需顺序固定图像的尺寸。 image_list
中的图像是使用torchvision.util.make_grid()
方法生成的,我们根据噪声向量创建了生成图像的网格。
更多
您可以使用其他库(例如 plotly 和 seaborn)来绘制和美化图形。
另见
您可以在这个页面上看到 DCGAN 的可视化效果和动画。
使用 PyTorch Hub 运行 PGGAN
在本秘籍中,我们将研究渐进 GAN(PGGAN),与 DCGAN 相比它们是高级 GAN,并且能够生成逼真的图像。 PGGAN 分多个阶段训练 GAN 网络。 它具有z
的潜在特征,并使用两个反卷积层生成4×4
图像。 在判别器方面,网络使用两个卷积层训练生成的4 x 4
图像。 网络稳定后,它会在判别器中再增加两个卷积层以将图像上采样到8 x 8
,再增加两个卷积层以对图像下采样。
经过 9 个这样的序列后,生成器将生成1024 x 1024
个图像。 PGGAN 的渐进式训练策略相对于常规 GAN 具有优势,因为它可以加快并稳定训练。 之所以如此,是因为大多数训练都是在较低的分辨率下进行的,而在网络达到各个阶段的稳定性之后,会逐渐发展为较高的分辨率。
以下是 PGGAN 的抽象表示形式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B1C8VYIg-1681786052331)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-ai-fund/img/9cd78f4c-0241-467d-92f8-2967611136e7.png)]
PGGAN 的关键创新可以总结如下:
- 高分辨率层中的逐渐增长和平滑淡化:它从低分辨率卷积变为高分辨率卷积,而不是立即跳变分辨率,而是通过参数平滑地淡化了具有更高分辨率的 nedonew 层
α
(介于 0 和 1 之间)的大小,用于控制我们使用旧的还是放大的较大输出。 - 小批量标准差:我们计算判别器的统计信息,即生成器生成或来自真实数据的小批量中所有像素的标准差。 现在,判别器需要了解,如果要评估的批量中图像的标准差较低,则该图像很可能是伪造的,因为真实数据的方差更高。 因此,生成器必须增加所生成样本的方差,以欺骗判别器。
- 均衡的学习率:所有权重(
w
)被归一化(w'
)在某个范围内,以便w' = w / c
的常数c
对于每一层来说都是不同的,具体取决于权重矩阵的形状。 - 逐像素特征归一化:对每个像素中的特征向量进行归一化,因为批量规范最适合大型微型批量,并且占用大量内存。
Nvidia 最初执行 PGGAN 的过程要花一到两个月的时间。 但是,为了让我们看到 PGGAN 的表现,我们将使用 PyTorch Hub,它是使用 PyTorch 构建的预训练模型的存储库。
准备
为了使割炬轮毂正常工作,您需要拥有 PyTorch 版本 1.1.0 或更高版本。
操作步骤
在此秘籍中,我们将从火炬中心运行 PGGAN:
- 首先,我们将设置导入:
>>import torch >>import matplotlib.pyplot as plt >>import torchvision
- 然后我们检查 GPU:
>>use_gpu = True if torch.cuda.is_available() else False
- 接下来,我们加载预训练的 PGGAN 模型:
>>model = torch.hub.load('facebookresearch/pytorch_GAN_zoo:hub', 'PGAN', model_name='celebAHQ-512', pretrained=True, useGPU=use_gpu)
- 然后我们产生噪音:
>>num_images = 5 >>noise, _ = model.buildNoiseData(num_images)
- 接下来,我们获得生成器输出
>>with torch.no_grad(): generated_images = model.test(noise)
- 最后,我们制作图像网格并显示它:
>>grid = torchvision.utils.make_grid(generated_images.clamp(min=-1, max=1), scale_each=True, normalize=True) >>plt.imshow(grid.permute(1, 2, 0).cpu().numpy())
在这里,我们看到了从 PGGAN 生成的面部图像:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9p1dktid-1681786052331)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-ai-fund/img/fbaff817-dad6-4995-b0dc-ae4d42aaf620.png)]
通过前面的步骤,我们学习了如何使用 PyTorch Hub 运行 PGGAN。
工作原理
在此秘籍中,我们加载了在celebAHQ
数据集上训练的 PGGAN 预训练模型; 为此,我们使用了torch.hub
中的load()
方法。 然后,我们定义了创建和生成尺寸为num_images x 512
的噪声向量所需的图像数量,因为此模型使用大小为 512 的噪声向量进行训练,所有噪声向量都由buildNoiseData()
方法内部处理,它在模型对象中可用。
model.test()
方法生成了我们用来制作网格的图像。 钳位方法将所有值限制在min
和max
定义的范围内。 .cpu()
方法将生成的图像移至 CPU,我们使用permute
固定尺寸。 最后,plt.imshow()
显示了我们创建的网格。
更多
您可以在这里探索完整的 PGGAN 实现。
另见
您可以在这个页面上了解有关割炬轮毂的更多信息。
七、深度强化学习
在本章中,我们将探索神经网络(NNs)在使用 PyTorch 进行强化学习(RL)上的应用。
RL 是人工智能(AI)的领域,与我们在前面各章中介绍的其他机器学习格式不同。 RL 是机器学习算法的子类,它通过最大化环境中的奖励来进行学习。 当问题涉及决策或采取行动时,这些算法很有用。
在本章中,我们将介绍以下秘籍:
- OpenAI Gym 简介– CartPole
- DQN 简介
- 实现 DQN 类
- 训练 DQN
- 深度 GA 简介
- 生成智能体
- 选择智能体
- 使智能体突变
- 训练深度 GA
深入了解 RL
智能体是任何 RL 问题的核心。 它是 RL 算法的一部分,它处理输入信息以执行操作。 它探索和利用重复试验中的知识,以学习如何最大化奖励。 智能体必须处理的场景称为环境,而动作是智能体可以在给定环境中进行的动作。 采取行动后从环境中获得的回报称为奖励,智能体根据当前状态决定使用下一行动的行动过程称为策略。 相对于短期奖励,带有折扣的预期长期回报称为值。Q 值与该值相似,但具有附加的当前动作参数。
现在,我们已经了解了 RL 的上下文,让我们理解为什么我们应该使用深度 RL(DRL)而不是 RL。 DRL 将 RL 与深度学习相结合,后者使用 NN 解决 RL 问题。
深度学习算法可以学习抽象出环境状态的细节,然后学习状态的重要特征。 由于深度学习算法仅具有有限数量的参数,因此我们可以使用它将可能的状态压缩为更少的状态,然后使用该新表示形式来选择一个动作。
RL 解决方案涉及将反复试验的结果存储在查找表中,当环境变得越来越复杂时,查找表将变得非常庞大。 深度神经网络可能会学会识别程序员自己必须使用查找表方法手动设计的相同高级特征。
深度学习是 RL 最近取得突破的背后。 它们展现了代表性的功能,效率,灵活性,并为眼前的问题提供了简化的解决方案。
OpenAI Gym 简介 – CartPole
在本秘籍中,我们将实现两种不同的 RL 算法。 我们将需要一个环境来运行我们的算法,以便我们创建的模型将获得最大的回报。
我们将使用 OpenAI 的体育馆库,该库是我们可以用来训练模型的环境的集合。 我们将专注于称为Cartpole-v1
的特定环境。
杆子是一个倒立的摆锤,其重心高于其枢轴点。 为了控制倒立摆的不稳定位置,我们必须将枢轴点移动到质心下方。 目的是施加适当的力,以使柱杆在枢轴点上保持平衡。
下图显示了 OpenAI Gym 的 Cartpole:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UbWd5ABZ-1681786052331)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-ai-fund/img/20943d9b-be6d-4bb1-b4df-0dc04bf896ed.png)]
图 1:OpenAI Gym– Cartpole
撑杆通过未激活的接头连接到推车,该接头在水平轨道上自由移动。 摆锤垂直于水平轨道开始。 目的是通过施加力 +1 和 -1 来防止跌落。
当杆与垂直位置的夹角超过 15 度时,或者手推车从中心移出的单位超过 2.4 个单位时,则认为手推车未能达到目标。
对于杆保持直立的每个时间步,都会获得+1 的奖励。 现在我们有了上下文,我们将尝试解决 OpenAI Gym 的 Cartpole 问题的代码。
准备
首先,我们需要安装gym
。 我们将使用pip
管理器安装它:
pip install gym
现在我们已经安装了gym
,让我们跳入gym
库。
操作步骤
在本秘籍中,我们将了解棘突环境。 请按照以下步骤操作:
- 我们将从导入
gym
模块开始:
>>import gym
- 接下来,我们将创建环境:
>>env = gym.make('CartPole-v0')
- 接下来,我们将重置环境:
>>env.reset()
- 现在,我们将渲染环境:
>>for _ in range(1000): env.render() action = env.action_space.sample() observation, reward, done, info = env.step(action) if done: env.reset() >>env.close()
这将为我们提供以下输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GVrTBuVO-1681786052332)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-ai-fund/img/836ad5fa-0348-4d74-80bc-958a98387a14.png)]
图 2:柱极环境的输出
- 获取可以执行的可能动作的数量:
>>env.action_space.n 2
- 获取观察状态参数的数量:
>>env.observation_space.shape[0] 4
这样,您将看到一个窗口,该窗口显示不稳定的枢轴,并且枢轴点随机移动。
工作原理
在本秘籍中,我们探讨了 OpenAI Gym 的一些功能。 我们首先使用Cartpole-v0
创建一个环境,该环境的最高可得分为 200,然后环境终止。 我们使用env.reset()
命令将环境置于初始状态,即线杆处于直立位置。 然后,我们开始执行 1000 个步骤的循环,其中使用render()
渲染了当前环境的开始,并且使用env.action_space.sample()
为当前状态选择了一个随机动作。 然后,我们将选定的动作传递到环境的step
方法中。 step
方法告诉我们在对环境的当前状态执行当前操作时环境发生了什么。 step
方法返回以下内容:
- 观察:这是一个对象,它告诉我们有关环境的新状态。 该对象特定于我们选择的环境。
- 奖励:这给了我们所选动作所获得的奖励。 如果是直杆,则直杆直立时每个时间步长为 1.0,否则为 0.0。
- 完成:这是一个布尔值,它告诉我们环境是否已达到最终状态,可能是由于手头的任务失败而导致的(如果是小刀杆,则是当刀杆无法保持直立位置时) ,或者从完成手头的任务开始(对于小偷来说,就是达到最大时间步长 200 的时间)。
- 信息:对调试有用的诊断信息。
在循环中,每当极点翻倒时,我们都会将环境重置为其初始状态。
最后,我们关闭了环境。 我们使用env.action_space.n
查看了环境中可能采取的行动,并使用env.observation_space.shape[0]
查看了处于观察状态的参数。 现在我们已经了解了环境,我们可以开始实现各种深度 RL 算法。
更多
您可以通过更改环境名称来尝试其他环境。 试用Cartpole-v1
对您来说将是有益的。
另见
您可以在这个页面上了解有关 OpenAI Gym 的更多信息。
DQN 介绍
在进入下一个秘籍之前,让我们快速看一下 DQN。
DQN 是一种 RL 技术,旨在为给定的观察选择最佳的动作。 有一个 Q 值,它是与每个可能观察到的每个可能动作相关联的给定移动的质量。 在传统的 RL 算法中,此 Q 值来自 Q 表,该表是一个查找表,其中是包含 Q 值的表。 通过反复玩游戏并使用奖励更新表来迭代更新此查找表。 q 学习算法学习要在此表中填充的最佳值。 我们可以简单地查看给定状态的表并选择具有最大 Q 值的动作,以最大程度地赢得游戏。
Q 值可以更新如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JBzxujOI-1681786052332)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-ai-fund/img/d2d686eb-44fe-4767-b842-d01fca99d3cf.png)]
新的 Q 值是两个部分的总和。 第一部分是(1 - 学习率) * 旧 Q 值
,它是多少旧值将被记住。 学习率为 0 时,不会学习任何新内容,学习率为 1 时,所有旧值都将被忘记。
第二部分是学习率*(即时行动奖励+最优未来值的折算估计),其中学习值是即时奖励加上最优未来值的折算估计。 未来奖励的重要性由折扣系数决定。
通过深度 Q 学习,我们可以使用深度神经网络来预测动作的 Q 值,并使用深度神经网络来选择动作,而不是使用 Q 表查找给定状态下具有最大可能 Q 值的动作。 给定动作的最大 Q 值。
操作步骤
在本秘籍中,我们将为卡特彼勒问题定义神经网络模型。 请按照以下步骤操作:
- 首先,我们将导入
torch
:
>>import torch >>import torch.nn as nn
- 接下来,定义一个函数来返回模型:
def cartpole_model(observation_space, action_space): return nn.Sequential( nn.Linear(observation_space, 24), nn.ReLU(), nn.Linear(24, 24), nn.ReLU(), nn.Linear(24, action_space) )
此函数返回模型。
工作原理
在此秘籍中,我们定义了一个名为cartpole_model
的函数,该函数接受observation_ space
和action_space
参数,并返回一个神经网络模型。 在这里,我们使用了torch.nn
,nn.Linear
和nn.ReLU
中的Sequential
模块来完成模型。 给定一个观察值,我们使用该模型来训练和预测每个动作的 Q 值。
更多
我们还可以训练将状态作为图像并学习从图像预测 Q 值的模型。 完成此操作后,我们将使用nn.Conv2d()
来使用卷积神经网络。
另见
您可以在这个页面上查看替代架构。
实现 DQN 类
在本秘籍中,我们将使用神经网络完成 DQN。 为此,我们将执行一些关键任务,包括创建目标和策略网络,损失函数和网络优化器,存储学习过程的状态和奖励,预测行为,经验回放以及控制学习过程。 探索率。
准备
在完成本秘籍之前,您应该完成本章的“OpenAI Gym 介绍 – Cartpole”秘籍,以便设置gym
包。
操作步骤
在本秘籍中,我们将研究可用于执行 DQN 的所有关键功能。 按着这些次序:
- 我们将从必要的导入开始:
>>import random >>from collections import deque >>import numpy as np >>import torch.optim as optim
- 接下来,我们将定义 DQN 类:
>>class DQN:
- 然后,我们将定义构造器:
>>def __init__(self, observation_space, action_space): self.exploration_rate = MAX_EXPLORE self.action_space = action_space self.observation_space = observation_space self.memory = deque(maxlen=MEMORY_LEN)
- 接下来,我们将定义
target_net
和policy_net
:
self.target_net = cartpole_model(self.observation_space, self.action_space) self.policy_net = cartpole_model(self.observation_space, self.action_space)
- 现在,我们将复制权重:
self.target_net.load_state_dict(self.policy_net.state_dict()) self.target_net.eval()
- 在这里,我们定义损失函数,优化器和限制标志:
self.criterion = nn.MSELoss() self.optimizer = optim.Adam(self.policy_net.parameters()) self.explore_limit = False
- 接下来,我们将定义
load_memory
方法:
>>def load_memory(self, state, action, reward, next_state, terminal): self.memory.append((state, action, reward, next_state, terminal))
- 现在,我们将定义
predict_action
方法:
>>def predict_action(self, state): random_number = np.random.rand() if random_number < self.exploration_rate: return random.randrange(self.action_space) q_values = self.target_net(state).detach().numpy() return np.argmax(q_values[0])
- 现在,我们将跳至
experience_replay
方法:
>>def experience_replay(self): if len(self.memory) < BATCH_SIZE: return batch = random.sample(self.memory, BATCH_SIZE)
- 现在,让我们使用批量更新 Q 值:
for state, action, reward, next_state, terminal in batch: q_update = reward if not terminal: q_update = reward + GAMMA * self.target_net(next_state).max(axis=1)[0] q_values = self.target_net(state) q_values[0][action] = q_update
- 接下来,我们计算损失并更新权重:
loss = self.criterion(self.policy_net(state), q_values) self.optimizer.zero_grad() loss.backward() self.optimizer.step()
- 我们还将更新探索率:
if not self.explore_limit: self.exploration_rate *= EXPLORE_DECAY if self.exploration_rate < MIN_EXPLORE: self.exploration_rate = MIN_EXPLORE self.explore_limit = True
至此,我们完成了 DQN 课程。
工作原理
在本秘籍中,我们完成了 DQN 类,并添加了所有必需的函数来训练 DQN。 在构造器中,我们初始化了探索的初始状态,观察空间和动作空间,然后定义了一个存储单元来保存 DQN 的经验。 我们创建了称为policy_net
和target_net
的软骨模型的两个实例。 我们需要两个网络,因为在训练的每个步骤中,Q 网络的值都会移动,并且如果我们使用不断变化的目标值来调整我们的网络,则该网络可能会由于陷入此变化的目标与估计的 Q 值之间的反馈回路而变得不稳定。 网络值。 如果发生这种情况,值估计将失去控制。 因此,我们使用了两个网络并将target_net
保持在eval
模式。 然后,我们使用MSELoss()
作为损失函数以及Adam
优化器来更新权重。
在load_memory()
方法中,我们从环境中存储了状态,操作,奖励,下一个状态和终端,以用于训练网络。 我们使用的下一个方法是predict_action
。 在此方法中,我们使用np.random.rand()
选择了random_number
,这为我们提供了[0,1)
的值。 如果此random_number
小于当前的exploration_rate
,则我们选择一个随机动作,该动作由exploration_rate
控制。 这就是我们合并探索的方式。 但是,如果random_number
大于exploration_rate
,则target_net
会预测q_values
并选择具有最大 Q 值的动作。
最后,我们实现了experience_replay
方法。 在这里,我们等待数据点的数量至少为BATCH_SIZE
,然后从内存中随机采样一批。 这使它可以从一系列不同的观察中学习,而不是从一系列紧密相关的观察中学习。 遍历批量时,我们使用target_net
根据 Q 值更新公式更新了 Q 值。 然后,我们根据新 Q 值与policy_net
预测的 Q 值之间的误差训练了policy_net
。 之后,通过将其乘以探索衰减量逐渐降低探索速率,直到获得最低探索速率。 我们这样做是因为,在训练的初始阶段,我们希望智能体进行更多探索; 但是,随着训练的进行,我们希望算法能够收敛。 至此,我们已经完成了 DQN 类的所有函数。
更多
您可以在 DQN 类中添加一种方法,以用策略网络更新目标网络的权重。
另见
您可以在这个页面上的 Keras 中查看此实现。
训练 DQN
在此秘籍中,我们将完成训练 DQN 增强算法的过程,并在训练模型后可视化我们的小车。 我们将使用 DQN 类来预测操作并将该操作应用于环境以获得奖励。 此秘籍的目的是使奖励最大化。 我们将使用经验回放来训练我们的模型以预测 Q 值。
操作步骤
在本秘籍中,我们将继续使用“DQN 类”秘籍。 按着这些次序:
- 首先,我们需要导入
gym
:
>>import gym
- 接下来,我们需要初始化常量和环境:
>>ENV_NAME = "CartPole-v1" >>BATCH_SIZE = 20 >>GAMMA = 0.95 >>LEARNING_RATE = 0.001 >>MAX_EXPLORE = 1.0 >>MIN_EXPLORE = 0.01 >>EXPLORE_DECAY = 0.995 >>MEMORY_LEN = 1_000_000 >>UPDATE_FREQ = 10
- 现在,我们需要初始化环境和 DQN:
>>env = gym.make(ENV_NAME) >>observation_space = env.observation_space.shape[0] >>action_space = env.action_space.n >>dqn = DQN(observation_space, action_space)
- 现在,让我们开始训练循环:
>>for i in range(100): state = env.reset() state = np.reshape(state, [1, observation_space]) state = torch.from_numpy(state).float()
- 接下来,我们需要逐步了解环境:
score = 0 while True: score += 1 action = dqn.predict_action(state) next_state, reward, terminal, info = env.step(action)
- 在这里,我们必须找到下一个状态:
next_state = torch.from_numpy(np.reshape(next_state, [1, observation_space])).float() dqn.load_memory(state, action, reward, next_state, terminal) state = next_state
- 我们将使用以下代码结束无限循环:
if terminal: print(f'| {i+1:02} | {dqn.exploration_rate:.4f} | {score:03} |') break
- 接下来,我们需要执行一次经验回放:
dqn.experience_replay()
- 接下来,更新权重:
if steps%UPDATE_FREQ == 0: dqn.target_net.load_state_dict(dqn.policy_net.state_dict())
以下代码块显示了一些示例输出:
| Run | Exploration Rate | Score | | 001 | 0.9416 | 032 | | 002 | 0.8956 | 011 | | 003 | 0.8061 | 022 | | 004 | 0.7477 | 016 | | 005 | 0.6936 | 016 | | 006 | 0.6498 | 014 | | 007 | 0.5371 | 039 | . . | 072 | 0.0100 | 256 | | 073 | 0.0100 | 227 | | 074 | 0.0100 | 225 | | 075 | 0.0100 | 238 | | 076 | 0.0100 | 154 | | 077 | 0.0100 | 285 | . . .
- 现在,我们将定义一个函数,该函数将可视化小车的表现:
>>def play_agent(dqn, env): observation = env.reset() total_reward=0
我们需要使用以下代码来迭代最多 500 个步骤:
for _ in range(500): env.render() observation = torch.tensor(observation).type('torch.FloatTensor').view(1,-1) q_values = dqn.target_net(observation).detach().numpy() action = np.argmax(q_values[0]) new_observation, reward, done, _ = env.step(action) total_reward += reward observation = new_observation if(done): break
最后,关闭环境:
env.close() print("Rewards: ",total_reward)
调用play_agent()
函数:
>>play_agent(dqn, env) Rewards: 160.0
这样,我们就对 DQN 进行了训练和可视化。
工作原理
在本秘籍中,我们首先使用超参数导入并初始化环境。 然后,我们创建了DQN
类的实例,并开始训练循环,重置环境,并对状态数组进行整形,以便可以将其作为浮点张量输入到模型中。 然后,我们开始了一个无限循环,当env.step()
方法的返回值将terminal
设置为True
时终止。 predict_action()
方法预测在给定环境当前状态下要采取的措施。 然后,我们使用环境的step()
方法应用了此操作。 我们采用step
方法返回的下一个状态,并将其从numpy
转换为torch.FloatTensor
,并保存了环境参数。 我们一次又一次地将此新状态传递给模型。 我们还每隔几步就将权重从我们的策略网复制到目标网。
最后,我们编写了一个简单的play_agent
函数来可视化平衡并在重置环境后运行循环。 我们要求目标网络预测每个可能动作的 Q 值,选择具有最高 Q 值的动作,并使用step()
将其导入环境。 之后,我们不断增加奖励。 此函数返回柱状体直立的时间步数和柱状体执行平衡动作的视频。
更多
您可以编写一个函数来绘制算法的表现,并且仅在模型的得分始终在 450-500 之间时才停止训练过程。
另见
您可以在这里的 Keras 中看到此实现。
您可以在这个页面上看到替代实现。
Deep GA 简介
在本秘籍中,我们将探讨深层遗传算法(Deep GA),并向您展示当将其应用于 RL 时,它是基于梯度方法的竞争替代品。 我们将使用一组随机生成的网络,而不是使用梯度下降来修改其权重的随机生成的网络,而是创建一个一代,然后对其在给定环境中的表现进行评估。 请注意,一代人中的某些网络将比其他人表现更好。 我们将选择表现最佳的网络,并将其保留给下一代网络。 然后,我们将通过复制它们来创建下一代并对其权重进行随机修改。 由于我们将选择权重较小的最佳网络,因此网络的整体表现将不断提高。
在本秘籍中,我们将定义模型的网络维度,该网络维度将用于预测给定状态时应采取的措施。
操作步骤
在本秘籍中,我们将完成网络模型定义。 您需要安装 OpenAI 的体育馆库。 按着这些次序:
- 我们将从导入开始:
>>import torch.nn as nn >>import torch
- 现在,让我们定义一个返回神经网络的函数:
>>def cartpole_model(observation_space, action_space): return nn.Sequential( nn.Linear(observation_space, 128), nn.ReLU(), nn.Linear(128, action_space), nn.Softmax(dim=1) )
这样,我们就完成了模型的定义。
工作原理
在此秘籍中,函数处于观察状态,并通过两个线性层和一个 ReLU 单元(在隐藏层中有 128 个单元)运行它。 最后一层的输出通过 softmax 函数传递,以将激活转换为概率,并选择概率最高的动作。
更多
对于复杂模型,您还可以具有多个层和不同数量的单元。
另见
您也可以将复杂的网络与卷积层一起使用。 在这个页面中显示了一个示例。
生成智能体
在本秘籍中,我们将着眼于创建一组智能体以开始我们的进化过程,然后初始化这些智能体的权重。 我们将使用这些智能体来评估模型的表现并生成下一代智能体。
操作步骤
在此秘籍中,我们将创建给定数量的智能体。 按着这些次序:
- 首先,我们将定义一个函数来初始化智能体的权重:
>>def init_weight(module): if((type(module) == nn.Linear)): nn.init.xavier_uniform_(module.weight.data) module.bias.data.fill_(0.00)
- 现在,我们将定义一个将创建智能体的函数:
>>def create_agents(num_agents, observation_space, action_space): agents = []
- 接下来,我们将创建
num_agents
个智能体:
for _ in range(num_agents): agent = cartpole_model(observation_space, action_space) agent.apply(init_weight)
- 我们将关闭智能体的每个层的梯度:
for param in agent.parameters(): param.requires_grad = False agent.eval() agents.append(agent)
- 最后,我们将返回智能体:
return agents
现在,我们的智能体已准备好进行评估。
工作原理
在此秘籍中,我们编写了两个函数-第一个函数初始化模型层的权重。 对于模型权重,我们使用torch.nn.init
中的xavier_uniform
并用0
填充偏差。 第二个函数创建num_agents
个智能体,并使用cartpole_model()
函数返回它们。 我们使用init_weight
初始化权重。 然后,对于模型的参数,我们禁用梯度计算,将智能体设置为eval()
模式,然后返回所有智能体。
另见
您可以在这个页面上找到有关其他初始化方法的信息。
选择智能体
在本秘籍中,我们将基于适应度函数着眼于智能体选择,在我们的案例中,这意味着在平衡卡特波勒方面得分很高。 这意味着我们将传播得分最高的智能体,而忽略其余的智能体。 我们将评估给定一代中的每个智能体,并多次评估它们,以确保奖励不是偶然的。 最后,我们将使用每个智能体的平均分数来确定表现最佳的智能体。
操作步骤
在本秘籍中,我们将编写用于评估智能体,多次评估以及按给定序列评估所有智能体的函数。 按着这些次序:
- 我们将从导入开始:
>>import numpy as np
- 接下来,我们需要定义一个函数来评估智能体的表现:
>>def eval_agent(agent, env): observation = env.reset()
- 接下来,我们需要在最大可能的时间步长内运行循环:
total_reward = 0 for _ in range(MAX_STEP): observation = torch.tensor(observation).type('torch.FloatTensor').view(1,-1) action_probablity = agent(observation).detach().numpy()[0] action = np.random.choice(range(env.action_space.n), 1, p=action_probablity).item() next_observation, reward, terminal, _ = env.step(action) total_reward += reward observation = next_observation if terminal: break return total_reward
- 然后,我们需要定义智能体的平均得分:
>>def agent_score(agent, env, runs): score = 0 for _ in range(runs): score += eval_agent(agent, env) return score/runs
- 最后,我们评估所有智能体的分数:
>>def all_agent_score(agents, env, runs): agents_score = [] for agent in agents: agents_score.append(agent_score(agent, env, runs)) return agents_score
现在,我们的函数已准备好进行评估。
工作原理
在本秘籍中,我们完成了深度遗传算法的一些关键函数。 我们研究了三个不同的函数-第一个函数eval_agent()
与我们在“训练 DQN”秘籍中看到的函数非常相似,其中我们使用了智能体,该智能体是一种神经网络模型,它预测要采取的动作,并执行到MAX_STEP
(对于cartpole-v1
为 500)或终端为True
并返回分数的动作。
然后,我们使用第二个函数agent_score()
返回指定数量runs
之上的平均分数,并返回该平均分数,以确保模型的随机表现不佳。 最后一个函数all_agent_score()
仅循环遍历一代中的所有智能体,并获得一代中所有智能体的平均分数。
使智能体突变
在本秘籍中,我们将介绍使智能体突变。 在从给定的一代中选择表现最佳的模型之后,然后再创建下一代智能体,我们将对这些选定智能体的权重进行轻微的随机变化,这使智能体可以探索更多区域以获得更好的回报,就像生物进化的工作原理一样。
操作步骤
在此秘籍中,我们将识别精英智能体并向这些智能体添加突变。 按着这些次序:
- 首先,我们将导入
copy
和numpy
模块:
>>import copy >>import numpy
- 接下来,我们将定义
mutation
函数:
>>def mutation(agent): child_agent = copy.deepcopy(agent)
- 接下来,我们将遍历智能体的参数:
for param in agent.parameters(): mutation_noise = torch.randn_like(param) * MUTATION_POWER
- 然后,我们将变异噪声添加到参数中:
param += mutation_noise return child_agent
- 现在,定义
elite
函数:
>>def elite(agents, top_parents_id, env, elite_id=None, top=10): selected_elites = top_parents_id[:top] if elite_id: selected_elites.append(elite_id) top_score = np.NINF top_id = None
- 接下来,找到
elite
智能体:
for agent_id in selected_elites: score = agent_score(agents[agent_id], env, runs=5) if score > top_score: top_score = score top_id = agent_id return copy.deepcopy(agents[top_id])
- 获取子智能体:
>>def child_agents(agents, top_parents_id, env, elite_id=None): children = [] agent_count = len(agents)-1 selected_agents_id = np.random.choice(top_parents_id, agent_count) selected_agents = [agents[id] for id in selected_agents_id] child_agents = [mutate(agent) for agent in selected_agents] child_agents.append(elite(agents, top_parents_id, env)) elite_id = len(child_agents)-1 return child_agents, elite_id
- 获取顶级父级:
>>def top_parents(scores, num_top_parents): return np.argsort(rewards)[::-1][:num_top_parents]
在这里,我们定义了识别精英智能体的函数和为智能体增加噪音的函数。
工作原理
在本秘籍中,我们研究了四个不同的函数-mutation
函数为一个智能体创建一个重复项,并为每个参数附加一个受MUTATION_POWER
限制的小的随机值。 rand_like
方法从间隔[0, 1)
上以与param
相同的大小从均匀分布返回具有随机值的张量。 最后,该函数返回突变的子智能体。 接下来,我们看到elite
函数返回表现最佳的智能体中最佳智能体的副本。 在elite
函数中,我们重新评估智能体以确保得分最高的智能体被选为精英,并作为子智能体传递给下一代。
child_agent
函数生成的子智能体数量与上一代相同,其中一个子智能体是elite
函数的精英智能体,其余子智能体则使用np.random.choice
随机选择。 selected_agents
保留了表现最佳的选定智能体的列表。 在[mutate(agent) for agent in selected_agents]
步骤中,使用mutation
函数对得分最高的智能体进行突变。
然后,我们将精英智能体附加到下一代智能体。 最后,top_parent
函数返回一代中表现最高的智能体的索引。
训练深度 GA
在本秘籍中,我们将完成深度遗传算法的演化,并可视化执行平衡操作的关键。 我们将使用在本章的秘籍中了解到的所有函数,并针对给定的代数运行这些函数。 这将创建智能体,获取他们的分数,选择表现最佳的智能体,并将其突变为下一代。 在几代人中,我们将看到智能体的分数增加。
操作步骤
按着这些次序:
- 首先,我们将导入
gym
:
>>import gym
- 接下来,我们将声明超参数:
>>ENV_NAME = "CartPole-v1" >>MAX_STEP = 500 >>MUTATION_POWER = 0.02 >>num_agents = 500 >>num_top_parents = 20 >>generations = 25 >>elite_agent = None
- 之后,我们将创建环境并禁用梯度计算:
>>torch.set_grad_enabled(False) >>env = gym.make(ENV_NAME)
- 现在,创建智能体:
>>agents = create_agents(num_agents, env.observation_space.shape[0], env.action_space.n)
- 接下来,遍历几代:
>>print(f'| Generation | Score |') >>for gen in range(generations):
现在,我们可以评估智能体:
rewards = all_agent_score(agents, env, 3)
通过这样做,我们得到了最好的智能体:
top_parents_id = top_parents(rewards, num_top_parents)
反过来,这将创建下一代:
agents, elite_agent = child_agents(agents, top_parents_id, env, elite_agent) print(f'| {gen+1:03} | {np.mean([rewards[i] for i in top_parents_id[:5]]):.4f} |')
以下代码块显示了示例输出:
| Generation | Score | | 001 | 47.0667 | | 002 | 47.3333 | | 003 | 55.7333 | | 004 | 58.2667 | | 005 | 65.3333 | | 006 | 88.0000 | | 007 | 105.5333 | | 008 | 117.4000 | | 009 | 109.4000 | | 010 | 137.6667 | | 011 | 150.3333 | | 012 | 168.6000 | | 013 | 176.2667 | | 014 | 248.0667 | | 015 | 281.6667 | | 016 | 327.9333 | | 017 | 363.5333 | | 018 | 375.4000 | | 019 | 387.0000 | | 020 | 432.2000 | | 021 | 454.6000 | | 022 | 445.9333 | | 023 | 463.7333 | | 024 | 482.1333 | | 025 | 496.2000 |
- 最后,我们将形象化地表现出:
>>def play_agent(agent, env): observation = env.reset() total_reward=0 for _ in range(MAX_STEP): env.render() observation = torch.tensor(observation).type('torch.FloatTensor').view(1,-1) output_probabilities = agent(observation).detach().numpy()[0] action = np.random.choice(range(2), 1, p=output_probabilities).item() new_observation, reward, done, _ = env.step(action) total_reward += reward observation = new_observation if(done): break env.close() print("Rewards: ",total_reward) >>play_agent(agents[num_agents-1],env) Rewards: 350.0
至此,我们已经完成了 DGA 的训练和可视化。
工作原理
在此秘籍中,我们改进了深度遗传算法。 根据论文《深度神经进化:遗传算法是用于训练深度神经网络以进行强化学习的一种竞争选择》,我们将超参数MUTATION_POWER
设置为0.02
。 我们不需要使用 PyTorch 来进行梯度计算,因为我们不需要依靠梯度下降来改善模型和创建环境。
然后,我们创建了一些智能体,以开始我们的进化,并通过遍历generations
将它们运行了预定的代数,在其中我们获得了每一代所有智能体的奖励。 然后,我们选择了得分最高的父母,并通过child_agent
函数传递这些父母指数以获得下一代。 之后,我们打印出前 5 个得分的平均值。
最后,我们使用了与“训练 DQN”秘籍中相同的play_agent
函数,并进行了较小的修改,以补偿模型预测值的差异。 在这里,我们使用精英模型来显示卡特彼勒的表现,每一代之后。 位于智能体列表的末尾。 这是使用play_agent
函数完成的。
更多
您可以控制深度遗传算法的各种超参数,以查看表现差异并将分数存储到图表中。
另见
八、在 PyTorch 中生产 AI 模型
在本章中,我们将学习如何将 PyTorch 模型预测用于实际问题。 PyTorch 已从研究工具发展为可用于生产的框架,在本章中,我们将探讨一些使 PyTorch 可用于生产的功能。 部署模型意味着将模型提供给最终用户或系统。 为此,您可能需要满足多个要求,例如能够通过 Web 访问预测,快速进行预测以降低延迟或确保与其他深度学习框架的互操作性,以便开发人员可以使用正确的工具作为目标。 项目在发展。 所有这些确保了从研究到生产的更快过渡。
在本章中,我们将介绍以下秘籍:
- 使用 Flask 部署模型
- 创建一个 TorchScript
- 导出至 ONNX
技术要求
使用 Python 3.6 中的 PyTorch 1.3 已完成了本章的所有秘籍。
使用 Flask 部署模型
在本秘籍中,我们将使用 Flask 微框架部署图像分类器。 我们使用 Flask 的原因是因为它是一个易于使用的微框架,可用于构建 RESTful 微服务,它是一个非常流行的框架,并且有据可查。 我们将部署一个使用 Densenet-161 预训练模型构建的图像分类器模型,以完成此秘籍。
准备
我们将需要为此秘籍安装 Flask。 使用pip
管理器安装flask
:
pip install flask
这样,我们就可以开始了。
操作步骤
我们将把这个秘籍分成多个文件。 请按照以下步骤操作:
- 创建一个名为
image_classifier.py
的文件。 - 现在,我们需要进行导入:
>>import io >>import torch >>from torchvision import models >>from PIL import Image >>import torchvision.transforms as transforms >>import json
- 读取包含类名称的
.json
文件:
>>with open('idx_class.json') as f: idx_class = json.load(f)
- 定义
create_model
函数:
>>def create_model(): model_path = "densenet161.pth" model = models.densenet161(pretrained=True) model.load_state_dict(torch.load(model_path, map_location='cpu'), strict=False) model.eval() return model
- 定义
image_transformer
函数:
>>def image_transformer(image_data): transform = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) image = Image.open(io.BytesIO(image_data)) return transform(image).unsqueeze(0)
- 接下来,我们需要定义
predict_image
函数:
>>def predict_image(model, image_data): image_tensor = image_transformer(image_data) output = model(image_tensor) _, prediction = output.max(1) object_index = prediction.item() return idx_class[object_index]
- 现在,我们将创建
imageapp.py
。 - 首先,我们将导入所需的模块和 Flask:
>>from flask import Flask, request, jsonify >>from image_classifier import create_model, predict_image
- 现在,我们将创建一个 Flask 应用和分类器模型:
>>app = Flask(__name__) >>model = create_model()
- 现在,让我们创建路由:
>>@app.route('/predict', methods=['POST'])
- 接下来,我们将编写一个将在此路由上调用的函数:
>>@app.route('/predict', methods=['POST']) >>def predicted(): if 'image' not in request.files: return jsonify({'error': 'Image not found'}), 400 image = request.files['image'].read() object_name = predict_image(model, image) return jsonify({'object_name' : object_name})
- 最后,如果运行
imageapp.py
,我们将启动 Flask 应用:
>>if __name__ == '__main__': app.run(debug=True)
- 接下来,您需要使用以下命令运行 Flask 应用:
python imageapp.py
通过运行此命令,Flask 服务器将启动并运行。 您应该可以通过http://127.0.0.1:5000/
访问应用的 URL 并发送POST
请求。
- 使用邮差工具,您可以检查 API。 这是图像的示例响应:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kaYL0OGw-1681786052333)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-ai-fund/img/0cc7beb5-f92e-4caf-bb41-15799b308364.jpeg)]
现在,我们将检查 API 的响应:
{ "object_name": "scorpion" }
这将为我们提供以下输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Vo9bDLn-1681786052333)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-ai-fund/img/1f477c82-4311-4f6f-9b05-04ad7f50f4eb.png)]
在此秘籍中,我们使用 Flask 进行了简单的应用部署。
工作原理
在本秘籍中,我们使用 Flask Python 框架部署了使用 RESTful API 进行推理的模型。 我们首先创建image_classifier.py
并从idx_class.json
加载类名。
在此文件中,第一个函数加载预先训练的densenet161
模型,该模型在 ImageNet 数据集中具有 1,000 个类别的模型上进行训练; 我们将模型设置为评估模式,然后返回模型。 第二个函数将给定的输入图像转换为张量并对其进行变换。 我们使用了PIL
中的Image
模块来读取图像数据。 第三个函数通过将给定图像转换为张量并将其传递到模型中来进行预测。 这将返回图像中对象的名称。
然后,我们切换到imageapp.py
文件,在这里我们使用 Flask 创建了 Web 应用。 在这里,我们使用app = Flask(__name__)
创建了 Flask 应用,并使用create_model
函数创建了模型。 此后,我们创建了一个名为/predict
的路由,该路由使用我们创建的应用实例接收了一个POST
请求。 然后,我们定义了predicted
函数,该函数将在调用/predict
URL 时被调用。 request.files
在 POST 请求中保存文件。 在这里,我们检查了是否使用发布参数名称image
上传了图像文件。
最后,我们将此图像数据传递到我们先前定义的predict_image
函数中。 Flask 中的jsonify
方法可确保响应为.json
格式。 app.run(debug=True)
启动 Flask 服务器并处理请求。
更多
在本秘籍中,我们使用debug=True
将调试模式设置为开启,这在生产中不建议使用。 Flask 服务器的功能不足以支持生产负载。 相反,应使用gunicorn
和nginx
进行正确的部署。
另见
您可以通过以下网址了解有关 gunicorn Nginx 部署的信息。
创建一个 TorchScript
TorchScript 为最初用 PyTorch 编写的模型提供中间表示。 这样,您就可以在高性能环境(例如 C++)中运行模型。 TorchScript 通过 PyTorch 代码创建模型的可序列化和优化版本。 使用 TorchScript 编写的代码可以加载到进程中,而无需任何 Python 依赖项。 TorchScript 提供了可用于捕获模型定义的工具,而 PyTorch 具有动态和灵活的特性,因此足以支持此定义。 可以通过两种方式创建 TorchScript:跟踪或使用脚本编译器。 在本秘籍中,我们将使用跟踪和脚本编译器将 PyTorch 模型转换为 TorchScript。
操作步骤
在此秘籍中,我们将创建一个 TorchScript。 请按照以下步骤操作:
- 首先,我们将编写一个简单的网络:
>>import torch >>import torch.nn as nn >>class MyCell(torch.nn.Module): def __init__(self): super(MyCell, self).__init__() self.linear = torch.nn.Linear(4, 4) def forward(self, x, h): new_h = torch.tanh(self.linear(x) + h) return new_h
- 接下来,我们将根据模型类创建一个模型:
>>my_cell = MyCell()
- 然后,我们将生成两个随机张量传递给模型:
>>x, h = torch.rand(4, 4), torch.rand(4, 4)
- 接下来,我们可以
jit.trace
:
>>traced_cell = torch.jit.trace(my_cell, (x, h)) >>traced_cell TracedModule[MyCell]( original_name=MyCell (linear): TracedModule[Linear](original_name=Linear) )
- 然后,将张量传递给
traced_cell
:
>>traced_cell(x, h) tensor([[ 0.4238, -0.0524, 0.5719, 0.4747], [-0.0059, -0.3625, 0.2658, 0.7130], [ 0.4532, 0.6390, 0.6385, 0.6584]], grad_fn=<DifferentiableGraphBackward>)
- 我们可以使用以下代码访问图:
>>traced_cell.graph graph(%self : ClassType<MyCell>, %input : Float(3, 4), %h : Float(3, 4)): %1 : ClassType<Linear> = prim::GetAttr[name="linear"](%self) %weight : Tensor = prim::GetAttr[name="weight"](%1) %bias : Tensor = prim::GetAttr[name="bias"](%1) %6 : Float(4, 4) = aten::t(%weight), scope: MyCell/Linear[linear] # /home/<user>/.local/lib/python3.6/site-packages/torch/nn/functional.py:1370:0 %7 : int = prim::Constant[value=1](), scope: MyCell/Linear[linear] # /home/<user>/.local/lib/python3.6/site-packages/torch/nn/functional.py:1370:0 %8 : int = prim::Constant[value=1](), scope: MyCell/Linear[linear] # /home/<user>/.local/lib/python3.6/site-packages/torch/nn/functional.py:1370:0 %9 : Float(3, 4) = aten::addmm(%bias, %input, %6, %7, %8), scope: MyCell/Linear[linear] # /home/<user>/.local/lib/python3.6/site-packages/torch/nn/functional.py:1370:0 %10 : int = prim::Constant[value=1](), scope: MyCell # <ipython-input-2-c6e2cd8665ee>:7:0 %11 : Float(3, 4) = aten::add(%9, %h, %10), scope: MyCell # <ipython-input-2-c6e2cd8665ee>:7:0 %12 : Float(3, 4) = aten::tanh(%11), scope: MyCell # <ipython-input-2-c6e2cd8665ee>:7:0 return (%12)
对于可读的版本,我们可以使用以下命令:
>>traced_cell.code import __torch__ import __torch__.torch.nn.modules.linear def forward(self, input: Tensor, h: Tensor) -> Tensor: _0 = self.linear weight = _0.weight bias = _0.bias _1 = torch.addmm(bias, input, torch.t(weight), beta=1, alpha=1) return torch.tanh(torch.add(_1, h, alpha=1))
现在,让我们探索脚本编译器。 按着这些次序:
- 首先,我们将定义一个具有控制流程的子模块:
>>class MyDecisionGate(torch.nn.Module): def forward(self, x): if x.sum() > 0: return x else: return -x
- 然后,我们将在模型定义中使用此子模块:
>>class MyCell(torch.nn.Module): def __init__(self, dg): super(MyCell, self).__init__() self.dg = dg self.linear = torch.nn.Linear(4, 4) def forward(self, x, h): new_h = torch.tanh(self.dg(self.linear(x)) + h) return new_h
- 根据定义创建模型:
>>my_cell = MyCell(MyDecisionGate())
- 现在,我们将执行跟踪:
>>traced_cell = torch.jit.trace(my_cell, (x, h)) >>traced_cell.code import __torch__.___torch_mangle_0 import __torch__ import __torch__.torch.nn.modules.linear.___torch_mangle_1 def forward(self, input: Tensor, h: Tensor) -> Tensor: _0 = self.linear weight = _0.weight bias = _0.bias x = torch.addmm(bias, input, torch.t(weight), beta=1, alpha=1) _1 = torch.tanh(torch.add(torch.neg(x), h, alpha=1)) return _1
接下来,我们将使用jit.script
将其转换为 TorchScript:
>>scripted_gate = torch.jit.script(MyDecisionGate()) >>my_cell = MyCell(scripted_gate) >>traced_cell = torch.jit.script(my_cell) >>print(traced_cell.code) import __torch__.___torch_mangle_3 import __torch__.___torch_mangle_2 import __torch__.torch.nn.modules.linear.___torch_mangle_4 def forward(self, x: Tensor, h: Tensor) -> Tensor: _0 = self.linear _1 = _0.weight _2 = _0.bias if torch.eq(torch.dim(x), 2): _3 = torch.__isnot__(_2, None) else: _3 = False if _3: bias = ops.prim.unchecked_unwrap_optional(_2) ret = torch.addmm(bias, x, torch.t(_1), beta=1, alpha=1) else: output = torch.matmul(x, torch.t(_1)) if torch.__isnot__(_2, None): bias0 = ops.prim.unchecked_unwrap_optional(_2) output0 = torch.add_(output, bias0, alpha=1) else: output0 = output ret = output0 _4 = torch.gt(torch.sum(ret, dtype=None), 0) if bool(_4): _5 = ret else: _5 = torch.neg(ret) return torch.tanh(torch.add(_5, h, alpha=1))
这样,我们研究了创建 TorchScript 的两种不同方法。
工作原理
在本秘籍中,我们使用了跟踪方法来创建 TorchScript。 我们定义了一个简单的模块MyCell
转换为Torchscript
,并创建了两个采样张量x
和h
传递给网络模块的正向方法。 然后,我们使用jit.trace
跟踪 Python 代码并创建 TorchScript。
我们使用跟踪将PyTorch
模型转换为 TorchScript,并传递了我们的模型实例。 jit.trace
通过在模块的前向方法内跟踪模型评估中的操作来创建torch.jit.ScriptModule
对象。 jit.trace
运行网络模块,记录运行该模块时发生的操作,并创建torch.jit.ScriptModule
对象的实例。 TorchScript 以中间表示形式(在深度学习中称为图)记录其定义。 然后,我们检查了具有.graph
属性的图,并使用.code
生成了更具可读性的版本,这是代码的 Python 语法解释。
然后,我们探索了使用脚本编译器创建 TorchScript 的下一种方法。 为此,我们使用以下代码定义了具有控制流的子模块:
>>class MyDecisionGate(torch.nn.Module): def forward(self, x): if x.sum() > 0: return x else: return -x
我们在MyCell
模块中使用了以下子模块:
my_cell = MyCell(MyDecisionGate())
使用跟踪方法,我们失去了控制流,因为通过跟踪,我们运行了代码,记录了操作,并构造了一个ScriptModule
对象,该对象擦除了诸如控制流之类的东西。 可以在以下代码中看到:
>>traced_cell = torch.jit.trace(my_cell, (x, h)) >>traced_cell.code
因此,我们使用jit.script
保留了控制流。 首先,我们在子模块对象上运行jit.script
,如下所示:
>>scripted_gate = torch.jit.script(MyDecisionGate())
然后,我们创建MyCell
对象并使用jit.script
运行它:
>>my_cell = MyCell(scripted_gate) >>traced_cell = torch.jit.script(my_cell)
当使用print(traced_cell.code)
打印 TorchScript 代码时,我们看到仍然保留了控制流。
更多
我们可以将跟踪和脚本编写方法混合在一起。
另见
导出至 ONNX
在本秘籍中,我们将介绍如何将 PyTorch 模型导出到开放神经网络交换(ONNX),该模型为深度学习和传统机器学习模型提供了一种开源格式。 它定义了一个可扩展的计算图模型,以及内置的运算符和标准数据类型。
ONNX 得到了广泛的支持,可以在许多框架,工具和硬件中找到,因为它可以实现不同框架之间的互操作性,并可以实现从研究到生产的过渡。
准备
对于此秘籍,我们需要安装 ONNX,可以使用以下命令进行安装:
pip install onnx
这样,我们就可以进行秘籍了。
对于此秘籍,我们还将需要在第 3 章,“用于计算机视觉的卷积神经网络”中在CIFAR-10
上训练的模型的训练权重。
操作步骤
在本秘籍中,我们将 CIFAR-1o 模型导出为 ONNX 格式,并使用onnxruntime
运行它。 请按照以下步骤操作:
- 我们将从导入开始:
>>import onnx >>import onnxruntime >>import torch.nn as nn >>import torch >>import torch.nn.functional as F >>import numpy as np
- 接下来,我们将定义模型类:
>>class CNN(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(3, 16, 3, padding=1) self.conv2 = nn.Conv2d(16, 32, 3, padding=1) self.conv3 = nn.Conv2d(32, 64, 3, padding=1) self.pool = nn.MaxPool2d(2, 2) self.linear1 = nn.Linear(64 * 4 * 4, 512) self.linear2 = nn.Linear(512, 10) self.dropout = nn.Dropout(p=0.3) def forward(self, x): x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) x = self.pool(F.relu(self.conv3(x))) x = x.view(-1, 64 * 4 * 4) x = self.dropout(x) x = F.relu(self.linear1(x)) x = self.dropout(x) x = self.linear2(x) return x
- 然后,我们将创建模型对象并从我们的训练中加载权重:
>>model = CNN() >>model.load_state_dict(torch.load("cifar10.pth")) <All keys matched successfully>
- 接下来,我们将模型设置为评估模式:
>>model.eval() CNN( (conv1): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (conv2): Conv2d(16, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (conv3): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (linear1): Linear(in_features=1024, out_features=512, bias=True) (linear2): Linear(in_features=512, out_features=10, bias=True) (dropout): Dropout(p=0.3, inplace=False) )
- 现在,我们将创建一个随机变量:
>>x = torch.randn(1, 3, 32, 32, requires_grad=True)
- 接下来,我们将获得随机变量
x
的输出:
>>model_out = model(x)
- 之后,我们将模型及其权重导出到
onnx
模型中:
>torch.onnx.export(model, x, "cifar.onnx", export_params=True, opset_version=10, do_constant_folding=True, input_names = ['input'], output_names = ['output'], dynamic_axes={'input' : {0 : 'batch_size'}, 'output' : {0 : 'batch_size'}})
- 接下来,我们将加载并检查
onnx
模型:
>>onnx_model = onnx.load("cifar.onnx") >>onnx.checker.check_model(onnx_model)
- 我们将
onnx
加载到 ONNX 运行时中:
>>ort_session = onnxruntime.InferenceSession("cifar.onnx")
- 现在,定义
to_numpy()
函数:
>>def to_numpy(tensor): return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy()
- 在这里,我们将把输入变量
x
传递到 ONNX 运行时:
>>ort_inputs = {ort_session.get_inputs()[0].name: to_numpy(x)} >>ort_outs = ort_session.run(None, ort_inputs)
- 最后,我们将检查模型和
onnx
模型的输出是否相等:
>>np.testing.assert_allclose(to_numpy(model_out), ort_outs[0], rtol=1e-03, atol=1e-05)
通过此秘籍,我们已导出为onnx
格式,并使用 ONNX 运行时以onnx
格式运行了模型。
工作原理
在此秘籍中,我们将正常的 PyTorch 模型导出为 ONNX 格式,并使用 ONNX 运行时运行onnx
模型。 为此,我们采用了权重模型。 在这里,我们使用了第 3 章“卷积神经网络”的 CIFAR-10 模型,用于计算机视觉*。 我们从训练中使用了模型的权重,并将模型设置为评估模式,以进行快速,轻便的计算。
然后,我们使用了一个随机变量,其形状与输入张量的形状相同,在本例中为三通道32 x 32
像素图像。 我们将此随机输入传递到模型中并获得输出。 然后,我们使用输出将其与模型的 ONNX 版本中的模型进行比较。
在 PyTorch 中使用跟踪或脚本导出模型。 在本秘籍中,我们在torch.onnx.export()
的帮助下使用了跟踪。 跟踪跟踪用于获取输出的操作。 这就是为什么我们提供x
的原因-因此可以进行跟踪。 x
必须具有正确的类型和大小。 输入尺寸在导出的 ONNX 图中固定为所有输入尺寸,我们必须指定所有动态轴。 在此秘籍中,我们使用第一维的输入导出模型,将批量大小设置为 1,并在torch.onnx.export()
的dynamic_axes
参数中将第一维指定为动态。
第一个参数是 PyTorch 模型,第二个参数是随机变量。 然后,我们有了onnx
格式的路径; export_params
用于将训练后的参数权重存储在模型文件中; opset_version
是onnx
导出版本; do_constant_folding
用于执行常量折叠以进行优化; input_names
是模型的输入名称,output_names
是模型的输出名称。 然后,我们加载了导出的onnx
模型,并检查了模型结构并使用onnx.checker.check_model(onnx_model)
验证了架构。 通过检查模型版本,图的结构,节点及其输入和输出来验证 ONNX 图。
然后,我们将模型加载到onnx
运行时中,并为模型创建一个推理会话。 创建会话后,我们使用run()
API 评估了模型,其中第一个参数是输出名称的列表,第二个参数是输入字典。 此调用的输出是计算 ONNX 运行时之后模型输出的列表。 最后,我们使用numpy.testing.assert_allclose()
比较了 PyTorch 模型和onnx
模型的输出值,如果两个对象不等于期望的公差,则会提高AssertionError
。
更多
我们可以导出onnx
模型,加载其他受支持的框架,并使用torch.onnx.export()
中的其他参数配置导出。
另见
您可以在以下位置阅读有关 Python ONNX 运行时的更多信息。