今天是参加昇思学习打卡营的第23天,学习内容是Pix2Pix实现图像转换.
以下是内容关键点概要:
Pix2Pix概述
- 基本概念:Pix2Pix使用条件生成对抗网络实现图像到图像的转换。
- 应用场景:包括但不限于语义/标签到真实图片、灰度图到彩色图、航空图到地图、白天到黑夜、线稿图到实物图的转换。
基础原理
- cGAN的生成器:与GAN不同,cGAN的生成器使用输入图片作为指导信息生成图像。
- 判别器:判断生成器输出的图像是否真实。
- 损失函数:cGAN的损失函数包括真实图像的判别损失和生成图像的判别损失。
准备环节
- 环境配置:支持GPU、CPU和Ascend平台。
- 数据准备:使用特定的数据集,例如外墙(facades)数据集。
创建网络
- 生成器G:使用U-Net结构,具有编码和解码路径,并通过跳跃连接保留细节信息。
- 判别器D:使用PatchGAN结构,关注图像的局部区域(patch)。
训练
- 训练过程:包括判别器和生成器的训练,使用不同的损失函数和优化器。
- 损失函数:包括BCEWithLogitsLoss和L1Loss,用于训练判别器和生成器。
推理
- 模型加载:加载训练好的ckpt文件。
- 数据推理:使用模型进行推理并展示结果。
Pix2Pix概述
Pix2Pix是基于条件生成对抗网络(cGAN, Condition Generative Adversarial Networks )实现的一种深度学习图像转换模型,该模型是由Phillip Isola等作者在2017年CVPR上提出的,可以实现语义/标签到真实图片、灰度图到彩色图、航空图到地图、白天到黑夜、线稿图到实物图的转换。Pix2Pix是将cGAN应用于有监督的图像到图像翻译的经典之作,其包括两个模型:生成器和判别器。
传统上,尽管此类任务的目标都是相同的从像素预测像素,但每项都是用单独的专用机器来处理的。而Pix2Pix使用的网络作为一个通用框架,使用相同的架构和目标,只在不同的数据上进行训练,即可得到令人满意的结果,鉴于此许多人已经使用此网络发布了他们自己的艺术作品。
基础原理
cGAN的生成器与传统GAN的生成器在原理上有一些区别,cGAN的生成器是将输入图片作为指导信息,由输入图像不断尝试生成用于迷惑判别器的“假”图像,由输入图像转换输出为相应“假”图像的本质是从像素到另一个像素的映射,而传统GAN的生成器是基于一个给定的随机噪声生成图像,输出图像通过其他约束条件控制生成,这是cGAN和GAN的在图像翻译任务中的差异。Pix2Pix中判别器的任务是判断从生成器输出的图像是真实的训练图像还是生成的“假”图像。在生成器与判别器的不断博弈过程中,模型会达到一个平衡点,生成器输出的图像与真实训练数据使得判别器刚好具有50%的概率判断正确。
在教程开始前,首先定义一些在整个过程中需要用到的符号:
- 𝑥𝑥:代表观测图像的数据。
- 𝑧𝑧:代表随机噪声的数据。
- 𝑦=𝐺(𝑥,𝑧)𝑦=𝐺(𝑥,𝑧):生成器网络,给出由观测图像𝑥𝑥与随机噪声𝑧𝑧生成的“假”图片,其中𝑥𝑥来自于训练数据而非生成器。
- 𝐷(𝑥,𝐺(𝑥,𝑧))𝐷(𝑥,𝐺(𝑥,𝑧)):判别器网络,给出图像判定为真实图像的概率,其中𝑥𝑥来自于训练数据,𝐺(𝑥,𝑧)𝐺(𝑥,𝑧)来自于生成器。
cGAN的目标可以表示为:
𝐿𝑐𝐺𝐴𝑁(𝐺,𝐷)=𝐸(𝑥,𝑦)[𝑙𝑜𝑔(𝐷(𝑥,𝑦))]+𝐸(𝑥,𝑧)[𝑙𝑜𝑔(1−𝐷(𝑥,𝐺(𝑥,𝑧)))]𝐿𝑐𝐺𝐴𝑁(𝐺,𝐷)=𝐸(𝑥,𝑦)[𝑙𝑜𝑔(𝐷(𝑥,𝑦))]+𝐸(𝑥,𝑧)[𝑙𝑜𝑔(1−𝐷(𝑥,𝐺(𝑥,𝑧)))]
该公式是cGAN的损失函数,D
想要尽最大努力去正确分类真实图像与“假”图像,也就是使参数𝑙𝑜𝑔𝐷(𝑥,𝑦)𝑙𝑜𝑔𝐷(𝑥,𝑦)最大化;而G
则尽最大努力用生成的“假”图像𝑦𝑦欺骗D
,避免被识破,也就是使参数𝑙𝑜𝑔(1−𝐷(𝐺(𝑥,𝑧)))𝑙𝑜𝑔(1−𝐷(𝐺(𝑥,𝑧)))最小化。cGAN的目标可简化为:
𝑎𝑟𝑔min𝐺max𝐷𝐿𝑐𝐺𝐴𝑁(𝐺,𝐷)𝑎𝑟𝑔min𝐺max𝐷𝐿𝑐𝐺𝐴𝑁(𝐺,𝐷)
为了对比cGAN和GAN的不同,我们将GAN的目标也进行了说明:
𝐿𝐺𝐴𝑁(𝐺,𝐷)=𝐸𝑦[𝑙𝑜𝑔(𝐷(𝑦))]+𝐸(𝑥,𝑧)[𝑙𝑜𝑔(1−𝐷(𝑥,𝑧))]𝐿𝐺𝐴𝑁(𝐺,𝐷)=𝐸𝑦[𝑙𝑜𝑔(𝐷(𝑦))]+𝐸(𝑥,𝑧)[𝑙𝑜𝑔(1−𝐷(𝑥,𝑧))]
从公式可以看出,GAN直接由随机噪声𝑧𝑧生成“假”图像,不借助观测图像𝑥𝑥的任何信息。过去的经验告诉我们,GAN与传统损失混合使用是有好处的,判别器的任务不变,依旧是区分真实图像与“假”图像,但是生成器的任务不仅要欺骗判别器,还要在传统损失的基础上接近训练数据。假设cGAN与L1正则化混合使用,那么有:
𝐿𝐿1(𝐺)=𝐸(𝑥,𝑦,𝑧)[||𝑦−𝐺(𝑥,𝑧)||1]𝐿𝐿1(𝐺)=𝐸(𝑥,𝑦,𝑧)[||𝑦−𝐺(𝑥,𝑧)||1]
进而得到最终目标:
𝑎𝑟𝑔min𝐺max𝐷𝐿𝑐𝐺𝐴𝑁(𝐺,𝐷)+𝜆𝐿𝐿1(𝐺)𝑎𝑟𝑔min𝐺max𝐷𝐿𝑐𝐺𝐴𝑁(𝐺,𝐷)+𝜆𝐿𝐿1(𝐺)
图像转换问题本质上其实就是像素到像素的映射问题,Pix2Pix使用完全一样的网络结构和目标函数,仅更换不同的训练数据集就能分别实现以上的任务。本任务将借助MindSpore框架来实现Pix2Pix的应用。
准备环节
配置环境文件
本案例在GPU,CPU和Ascend平台的动静态模式都支持。
准备数据
# 导入download模块中的download函数,用于下载文件
from download import download
# 定义数据集的下载链接,该链接指向存放在华为云的对象存储服务上的tar压缩数据集
url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/dataset_pix2pix.tar"
# 调用download函数下载数据集
# 指定下载文件的保存路径为当前目录下的"./dataset"文件夹
# kind参数设置为"tar",指明下载的文件是一个tar格式的压缩包
# replace参数设置为True,表示如果目标文件已存在,下载的文件将替换它
download(url, "./dataset", kind="tar", replace=True)
数据展示
调用Pix2PixDataset
和create_train_dataset
读取训练集,这里我们直接下载已经处理好的数据集
from mindspore import dataset as ds # 从mindspore库导入dataset模块
import matplotlib.pyplot as plt # 导入matplotlib的pyplot模块,用于绘图
# 创建MindDataset数据集对象,指定数据集文件路径、需要的列以及是否需要打乱数据顺序
dataset = ds.MindDataset(
"./dataset/dataset_pix2pix/train.mindrecord", # 数据集文件路径
columns_list=["input_images", "target_images"], # 数据集中需要加载的列名列表
shuffle=True # 是否在读取数据前打乱数据顺序
)
# 使用create_dict_iterator方法创建一个迭代器,该迭代器将按顺序遍历数据集中的所有样本
# output_numpy=True参数表示迭代器将返回NumPy数组格式的数据
data_iter = next(dataset.create_dict_iterator(output_numpy=True))
# 可视化部分训练数据:创建一个图形窗口,并设置图形的大小和分辨率
plt.figure(figsize=(10, 3), dpi=140)
# 遍历数据集中的前10个输入图像
for i, image in enumerate(data_iter['input_images'][:10], 1): # 从1开始计数用于子图索引
# 在图形窗口中添加子图,3行10列的布局,索引i是当前的子图位置
plt.subplot(3, 10, i)
# 关闭子图的坐标轴显示
plt.axis("off")
# 显示图像,图像数据需要进行适当的变换和缩放以适应显示
plt.imshow((image.transpose(1, 2, 0) + 1) / 2)
# 显示所有子图组成的图形
plt.show()
创建网络
当处理完数据后,就可以来进行网络的搭建了。网络搭建将逐一详细讨论生成器、判别器和损失函数。生成器G用到的是U-Net结构,输入的轮廓图𝑥𝑥编码再解码成真是图片,判别器D用到的是作者自己提出来的条件判别器PatchGAN,判别器D的作用是在轮廓图 𝑥𝑥的条件下,对于生成的图片𝐺(𝑥)𝐺(𝑥)判断为假,对于真实判断为真。
生成器G结构
U-Net是德国Freiburg大学模式识别和图像处理组提出的一种全卷积结构。它分为两个部分,其中左侧是由卷积和降采样操作组成的压缩路径,右侧是由卷积和上采样组成的扩张路径,扩张的每个网络块的输入由上一层上采样的特征和压缩路径部分的特征拼接而成。网络模型整体是一个U形的结构,因此被叫做U-Net。和常见的先降采样到低维度,再升采样到原始分辨率的编解码结构的网络相比,U-Net的区别是加入skip-connection,对应的feature maps和decode之后的同样大小的feature maps按通道拼一起,用来保留不同分辨率下像素级的细节信息。
定义UNet Skip Connection Block
import mindspore
import mindspore.nn as nn
import mindspore.ops as ops
class UNetSkipConnectionBlock(nn.Cell):
def __init__(self, outer_nc, inner_nc, in_planes=None, dropout=False,
submodule=None, outermost=False, innermost=False, alpha=0.2, norm_mode='batch'):
super(UNetSkipConnectionBlock, self).__init__() # 调用父类构造函数
# 实例化批量归一化层
down_norm = nn.BatchNorm2d(inner_nc)
up_norm = nn.BatchNorm2d(outer_nc)
# 实例化LeakyReLU激活函数,用于下采样路径
down_relu = nn.LeakyReLU(alpha)
# 实例化ReLU激活函数,用于上采样路径
up_relu = nn.ReLU()
# 根据norm_mode设置是否使用偏置项
use_bias = False
if norm_mode == 'instance':
down_norm = nn.BatchNorm2d(inner_nc, affine=False)
up_norm = nn.BatchNorm2d(outer_nc, affine=False)
use_bias = True
# 如果in_planes未指定,则默认与外部通道数相同
if in_planes is None:
in_planes = outer_nc
# 下采样卷积层
down_conv = nn.Conv2d(in_planes, inner_nc, kernel_size=4,
stride=2, padding=1, has_bias=use_bias, pad_mode='pad')
# 根据是否是外层或内层,构建不同的网络结构
if outermost:
# 外层结构,添加Tanh激活函数用于图像生成
up_conv = nn.Conv2dTranspose(inner_nc * 2, outer_nc,
kernel_size=4, stride=2,
padding=1, pad_mode='pad')
model = [down_conv, down_relu, submodule, up_relu, up_conv, nn.Tanh()]
elif innermost:
# 内层结构,不使用dropout
up_conv = nn.Conv2dTranspose(inner_nc, outer_nc,
kernel_size=4, stride=2,
padding=1, has_bias=use_bias, pad_mode='pad')
model = [down_relu, down_conv, up_relu, up_conv, up_norm]
else:
# 其他层结构,可能使用dropout
up_conv = nn.Conv2dTranspose(inner_nc * 2, outer_nc,
kernel_size=4, stride=2,
padding=1, has_bias=use_bias, pad_mode='pad')
model = [down_relu, down_conv, down_norm, submodule, up_relu, up_conv, up_norm]
if dropout:
model.append(nn.Dropout(p=0.5)) # 添加dropout层防止过拟合
# 将模型各部分组合成一个SequentialCell,以便按顺序执行
self.model = nn.SequentialCell(model)
# 跳跃连接的标志,如果不是最外层,则有跳跃连接
self.skip_connections = not outermost
def construct(self, x):
# 执行网络前向传播
out = self.model(x)
if self.skip_connections:
# 如果有跳跃连接,将上采样的结果与输入沿通道方向拼接
out = ops.concat((out, x), axis=1)
return out
基于UNet的生成器
import mindspore.nn as nn
from mindspore import ops
# 导入自定义的UNetSkipConnectionBlock,用于构建U-Net中的跳跃连接块
from your_module import UNetSkipConnectionBlock
class UNetGenerator(nn.Cell):
def __init__(self, in_planes, out_planes, ngf=64, n_layers=8, norm_mode='bn', dropout=False):
super(UNetGenerator, self).__init__() # 调用父类构造函数
# 初始化最内层的UNetSkipConnectionBlock,设置为innermost=True
unet_block = UNetSkipConnectionBlock(
ngf * 8, ngf * 8,
in_planes=None,
submodule=None,
norm_mode=norm_mode,
innermost=True
)
# 构建U-Net的中间层,使用循环添加UNetSkipConnectionBlock
for _ in range(n_layers - 5):
unet_block = UNetSkipConnectionBlock(
ngf * 8, ngf * 8,
in_planes=None,
submodule=unet_block,
norm_mode=norm_mode,
dropout=dropout
)
# 构建逐步降低特征图尺寸的层,直到达到所需的输出尺寸
unet_block = UNetSkipConnectionBlock(
ngf * 4, ngf * 8,
in_planes=None,
submodule=unet_block,
norm_mode=norm_mode
)
unet_block = UNetSkipConnectionBlock(
ngf * 2, ngf * 4,
in_planes=None,
submodule=unet_block,
norm_mode=norm_mode
)
unet_block = UNetSkipConnectionBlock(
ngf, ngf * 2,
in_planes=None,
submodule=unet_block,
norm_mode=norm_mode
)
# 最外层的UNetSkipConnectionBlock,设置为outermost=True
self.model = UNetSkipConnectionBlock(
out_planes, ngf,
in_planes=in_planes,
submodule=unet_block,
outermost=True,
norm_mode=norm_mode
)
def construct(self, x):
# 定义网络的前向传播
return self.model(x)
原始cGAN的输入是条件x和噪声z两种信息,这里的生成器只使用了条件信息,因此不能生成多样性的结果。因此Pix2Pix在训练和测试时都使用了dropout,这样可以生成多样性的结果。
基于PatchGAN的判别器
判别器使用的PatchGAN结构,可看做卷积。生成的矩阵中的每个点代表原图的一小块区域(patch)。通过矩阵中的各个值来判断原图中对应每个Patch的真假。
import mindspore.nn as nn
import mindspore.ops as ops
class ConvNormRelu(nn.Cell):
"""
卷积、归一化和ReLU激活函数的组合模块。
"""
def __init__(self,
in_planes,
out_planes,
kernel_size=4,
stride=2,
alpha=0.2,
norm_mode='batch',
pad_mode='CONSTANT',
use_relu=True,
padding=None):
super(ConvNormRelu, self).__init__()
# 实例化批量归一化层
norm = nn.BatchNorm2d(out_planes)
# 如果归一化模式是实例归一化,则设置affine=False
if norm_mode == 'instance':
norm = nn.BatchNorm2d(out_planes, affine=False)
# 实例归一化时不使用偏置项
has_bias = (norm_mode == 'instance')
# 如果未指定padding,则根据kernel_size自动计算
if not padding:
padding = (kernel_size - 1) // 2
# 根据pad_mode选择合适的填充方式
if pad_mode == 'CONSTANT':
conv = nn.Conv2d(in_planes, out_planes, kernel_size, stride, pad_mode='pad', has_bias=has_bias, padding=padding)
layers = [conv, norm]
else:
paddings = ((0, 0), (0, 0), (padding, padding), (padding, padding))
pad = nn.Pad(paddings=paddings, mode=pad_mode)
conv = nn.Conv2d(in_planes, out_planes, kernel_size, stride, pad_mode='pad', has_bias=has_bias)
layers = [pad, conv, norm]
# 如果使用ReLU激活函数,根据alpha值选择标准ReLU或LeakyReLU
if use_relu:
relu = nn.ReLU()
if alpha > 0:
relu = nn.LeakyReLU(alpha)
layers.append(relu)
# 将卷积、归一化和激活函数层组合成一个SequentialCell
self.features = nn.SequentialCell(layers)
def construct(self, x):
output = self.features(x)
return output
class Discriminator(nn.Cell):
"""
判别器网络,用于区分生成的图像和真实图像。
"""
def __init__(self, in_planes=3, ndf=64, n_layers=3, alpha=0.2, norm_mode='batch'):
super(Discriminator, self).__init__()
kernel_size = 4
# 初始化第一层卷积和LeakyReLU
layers = [
nn.Conv2d(in_planes, ndf, kernel_size, 2, pad_mode='pad', padding=1),
nn.LeakyReLU(alpha)
]
nf_mult = ndf
# 构建网络中间的ConvNormRelu层
for i in range(1, n_layers):
nf_mult_prev = nf_mult
nf_mult = min(2 ** i, 8) * ndf
layers.append(ConvNormRelu(nf_mult_prev, nf_mult, kernel_size, 2, alpha, norm_mode, padding=1))
# 构建网络的最后一层ConvNormRelu
nf_mult_prev = nf_mult
nf_mult = min(2 ** n_layers, 8) * ndf
layers.append(ConvNormRelu(nf_mult_prev, nf_mult, kernel_size, 1, alpha, norm_mode, padding=1))
# 添加最后的卷积层,输出尺寸为1的张量
layers.append(nn.Conv2d(nf_mult, 1, kernel_size, 1, pad_mode='pad', padding=1))
# 将所有层组合成一个SequentialCell
self.features = nn.SequentialCell(layers)
def construct(self, x, y):
# 将输入的两个张量x和y沿通道方向拼接
x_y = ops.concat((x, y), axis=1)
output = self.features(x_y)
return output
Pix2Pix的生成器和判别器初始化
实例化Pix2Pix生成器和判别器。
训练
训练分为两个主要部分:训练判别器和训练生成器。训练判别器的目的是最大程度地提高判别图像真伪的概率。训练生成器是希望能产生更好的虚假图像。在这两个部分中,分别获取训练过程中的损失,并在每个周期结束时进行统计。
下面进行训练:
import numpy as np
import os
import datetime
from mindspore import value_and_grad, Tensor
from mindspore.train.callback import Callback # 导入Callback
# 定义训练的周期数、检查点目录、数据集大小等参数
epoch_num = 3
ckpt_dir = "results/ckpt"
dataset_size = 400
val_pic_size = 256
lr = 0.0002
n_epochs = 100
n_epochs_decay = 100
# 定义学习率衰减函数
def get_lr():
# 初始化学习率为常量lr的列表
lrs = [lr] * dataset_size * n_epochs
lr_epoch = 0
for epoch in range(n_epochs_decay):
lr_epoch = lr * (n_epochs_decay - epoch) / n_epochs_decay
lrs += [lr_epoch] * dataset_size
lrs += [lr_epoch] * dataset_size * (epoch_num - n_epochs_decay - n_epochs)
return Tensor(np.array(lrs).astype(np.float32))
# 加载数据集
dataset = ds.MindDataset("./dataset/dataset_pix2pix/train.mindrecord", columns_list=["input_images", "target_images"], shuffle=True, num_parallel_workers=1)
steps_per_epoch = dataset.get_dataset_size()
# 定义损失函数
loss_f = nn.BCEWithLogitsLoss()
l1_loss = nn.L1Loss()
# 定义判别器的前向传播函数
def forword_dis(reala, realb):
lambda_dis = 0.5
fakeb = net_generator(reala)
pred0 = net_discriminator(reala, fakeb)
pred1 = net_discriminator(reala, realb)
loss_d = loss_f(pred1, ops.ones_like(pred1)) + loss_f(pred0, ops.zeros_like(pred0))
loss_dis = loss_d * lambda_dis
return loss_dis
# 定义生成器的前向传播函数
def forword_gan(reala, realb):
lambda_gan = 0.5
lambda_l1 = 100
fakeb = net_generator(reala)
pred0 = net_discriminator(reala, fakeb)
loss_1 = loss_f(pred0, ops.ones_like(pred0))
loss_2 = l1_loss(fakeb, realb)
loss_gan = loss_1 * lambda_gan + loss_2 * lambda_l1
return loss_gan
# 初始化优化器
d_opt = nn.Adam(net_discriminator.trainable_params(), learning_rate=get_lr(),
beta1=0.5, beta2=0.999, loss_scale=1)
g_opt = nn.Adam(net_generator.trainable_params(), learning_rate=get_lr(),
beta1=0.5, beta2=0.999, loss_scale=1)
# 使用value_and_grad获取生成器和判别器的梯度
grad_d = value_and_grad(forword_dis, None, net_discriminator.trainable_params())
grad_g = value_and_grad(forword_gan, None, net_generator.trainable_params())
# 定义训练步骤函数
def train_step(reala, realb):
loss_dis, d_grads = grad_d(reala, realb)
loss_gan, g_grads = grad_g(reala, realb)
d_opt(d_grads)
g_opt(g_grads)
return loss_dis, loss_gan
# 确保检查点目录存在
if not os.path.isdir(ckpt_dir):
os.makedirs(ckpt_dir)
# 初始化生成器和判别器的损失列表
g_losses = []
d_losses = []
# 创建数据集的迭代器
data_loader = dataset.create_dict_iterator(output_numpy=True, num_epochs=epoch_num)
# 训练循环
for epoch in range(epoch_num):
for i, data in enumerate(data_loader):
start_time = datetime.datetime.now() # 记录当前时间
# 将数据转换为Tensor
input_image = Tensor(data["input_images"])
target_image = Tensor(data["target_images"])
# 执行一个训练步骤,获取损失
dis_loss, gen_loss = train_step(input_image, target_image)
end_time = datetime.datetime.now() # 记录当前时间
delta = (end_time - start_time).microseconds # 计算训练步骤所需时间
# 每隔两个步骤打印一次训练信息
if i % 2 == 0:
print("ms per step:{:.2f} epoch:{}/{} step:{}/{} Dloss:{:.4f} Gloss:{:.4f} ".format(
(delta / 1000), (epoch + 1), (epoch_num), i, steps_per_epoch, float(dis_loss), float(gen_loss)))
# 将损失添加到列表中
d_losses.append(dis_loss.asnumpy())
g_losses.append(gen_loss.asnumpy())
# 在每个epoch结束时保存生成器的检查点
if (epoch + 1) == epoch_num:
mindspore.save_checkpoint(net_generator, ckpt_dir + "Generator.ckpt")
推理
获取上述训练过程完成后的ckpt文件,通过load_checkpoint和load_param_into_net将ckpt中的权重参数导入到模型中,获取数据进行推理并对推理的效果图进行演示(由于时间问题,训练过程只进行了3个epoch,可根据需求调整epoch)。
import mindspore
from mindspore import load_checkpoint, load_param_into_net
from mindspore.dataset import MindDataset
import matplotlib.pyplot as plt
# 假设net_generator是已经定义的生成器网络
# 加载训练好的ckpt文件
param_g = load_checkpoint(ckpt_dir + "Generator.ckpt")
# 将ckpt中的权重参数导入到生成器网络中
load_param_into_net(net_generator, param_g)
# 加载数据集,这里假设使用与训练相同的数据集
dataset = MindDataset("./dataset/dataset_pix2pix/val.mindrecord", columns_list=["input_images"], shuffle=False)
data_iter = dataset.create_dict_iterator(output_numpy=True)
# 创建图形界面以展示推理结果
fig = plt.figure(figsize=(10, 5))
for i in range(5): # 假设展示5个结果
# 获取数据集中的一个样本
data = next(data_iter)
input_image = mindspore.Tensor(data["input_images"])
# 执行推理
predict = net_generator(input_image)
# 将MindSpore Tensor转换为NumPy数组并进行适当的格式变换
predict = predict.asnumpy().transpose(0, 2, 3, 1)
# 展示原始图像和生成的图像
plt.subplot(1, 2, 1)
plt.imshow((data["input_images"][0].transpose(1, 2, 0) + 1) / 2)
plt.title("Original Image")
plt.axis("off")
plt.subplot(1, 2, 2)
plt.imshow((predict[0] + 1) / 2) # 根据实际情况调整图像的变换
plt.title("Generated Image")
plt.axis("off")
plt.show()
最终的推理结果如下:
学习心得:
-
通过学习Pix2Pix,我了解到了如何实现不同模态之间的转换,例如将结构化的数据(如标签图)转换为高分辨率的自然图像。
-
Pix2Pix展示了cGAN在图像翻译任务中的应用潜力,这不仅局限于艺术创作,还有实际应用,如医学成像分析和卫星图像解释。
-
学习Pix2Pix的过程中,我认识到了适当网络架构设计的重要性,特别是U-Net结构,它通过跳跃连接保留了图像的细节信息。
-
通过对Pix2Pix模型的训练,我掌握了一些关键的训练技巧,包括学习率调整、批次大小的选择以及模型权重初始化。
-
我学习了损失函数在训练过程中的重要作用,特别是在平衡生成器和判别器之间的对抗过程中。
-
通过实践Pix2Pix的训练和调优,我获得了宝贵的经验,这包括如何观察和分析训练过程中的损失变化,以及如何根据这些信息调整模型。
-
通过编写和修改Pix2Pix的代码,我的编程技能得到了提升,尤其是在使用深度学习框架(如MindSpore)方面。
-
我对GAN和Pix2Pix模型的未来发展充满期待,希望能够探索更多改进模型性能、稳定性和应用范围的方法。