网站图片设计兼职,中国市场调查网,暴利灰色偏门项目,临沂网站建设首选浩瀚网络如果你对MindSpore感兴趣#xff0c;可以关注昇思MindSpore社区
1. 写在前面
生成式对抗网络#xff08;GAN#xff09;自2014年由Ian Goodfellow提出以来#xff0c;一直是深度学习领域最引人注目的技术之一。它就像是两个AI模型在进行一场“猫鼠游戏”#xff1a;一…如果你对MindSpore感兴趣可以关注昇思MindSpore社区1. 写在前面生成式对抗网络GAN自2014年由Ian Goodfellow提出以来一直是深度学习领域最引人注目的技术之一。它就像是两个AI模型在进行一场“猫鼠游戏”一个负责制造假币生成器另一个负责识别假币判别器。随着博弈的进行造假者的手段越来越高明鉴别专家的眼力也越来越毒辣最终我们就能得到一个能够以假乱真的生成模型。本篇教程将带你使用MindSpore框架从零开始构建并训练一个GAN模型。我们的目标很单纯让模型学会自己“写”出数字来。我们将使用经典的MNIST手写数字数据集作为训练素材通过全连接网络来实现生成器和判别器。GAN图像生成https://www.mindspore.cn/tutorials/zh-CN/master/generative/gan.html1.1 准备工作与数据处理在开始构建模型之前我们需要先把数据准备好。MNIST数据集包含6万张训练图片和1万张测试图片都是28x28像素的灰度图。1.1.1 数据下载与加载首先我们需要下载数据集并进行解压。MindSpore提供了便捷的数据下载工具可以轻松搞定这一步。fromdownloadimportdownload# 下载并解压MNIST数据集urlhttps://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/MNIST_Data.zipdownload(url,.,kindzip,replaceTrue)下载完成后我们需要构建数据管道。这里使用MindSpore的MnistDataset接口来加载数据并进行必要的预处理比如将像素值归一化、打乱顺序以及分批次Batch。此外我们还需要为生成器准备“原材料”——隐码Latent Code。隐码通常是从高斯分布中随机采样的向量生成器就是根据这些随机噪声来生成图像的。importnumpyasnpimportmindspore.datasetasds batch_size128latent_size100# 隐码的长度即输入生成器的随机向量维度train_datasetds.MnistDataset(dataset_dir./MNIST_Data/train)test_datasetds.MnistDataset(dataset_dir./MNIST_Data/test)defdata_load(dataset):# 将数据集转换为生成器数据集指定列名dataset1ds.GeneratorDataset(dataset,[image,label],shuffleTrue,python_multiprocessingFalse)# 数据增强与预处理# 1. 将图像数据转换为float32# 2. 生成对应的高斯分布随机噪声作为隐码mnist_dsdataset1.map(operationslambdax:(x.astype(float32),np.random.normal(sizelatent_size).astype(float32)),output_columns[image,latent_code])# 只保留图像和隐码丢弃标签因为GAN是无监督学习不需要标签mnist_dsmnist_ds.project([image,latent_code])# 批量操作drop_remainderTrue表示丢弃最后不足一个batch的数据mnist_dsmnist_ds.batch(batch_size,True)returnmnist_ds mnist_dsdata_load(train_dataset)iter_sizemnist_ds.get_dataset_size()print(Iter size: %d%iter_size)名词解释隐码 (Latent Code): 可以理解为生成图像的“种子”。它是一个低维的随机向量包含了生成图像的潜在特征信息。生成器的作用就是把这个看不懂的“种子”解码成我们能看懂的图像。1.1.2 数据可视化与固定噪声构造为了直观地看到我们正在处理什么样的数据我们可以从数据集中取出一个Batch并显示出来。importmatplotlib.pyplotasplt# 创建字典迭代器获取一组数据data_iternext(mnist_ds.create_dict_iterator(output_numpyTrue))figureplt.figure(figsize(3,3))cols,rows5,5# 展示前25张图片foridxinrange(1,cols*rows1):imagedata_iter[image][idx]figure.add_subplot(rows,cols,idx)plt.axis(off)plt.imshow(image.squeeze(),cmapgray)plt.show()在训练过程中为了能客观地评估生成器的进步我们需要一组固定的“考题”。我们在训练开始前就生成一批固定的随机噪声在每个Epoch结束后都用这同一批噪声让生成器生成图像。这样我们就能通过肉眼观察图像质量的变化来判断生成器是不是真的在变强。importrandomfrommindsporeimportTensor,dtype# 设置随机种子保证每次运行结果一致np.random.seed(2323)# 创建25个长度为100的随机向量作为测试用的固定隐码test_noiseTensor(np.random.normal(size(25,100)),dtype.float32)random.shuffle(test_noise)1.2 模型构建GAN的核心在于两个网络的博弈生成器Generator和判别器Discriminator。考虑到MNIST图片比较简单单通道、尺寸小我们不需要使用复杂的卷积网络简单的全连接网络Dense Layer配合ReLU激活函数就足以胜任。1.2.1 生成器 (Generator)生成器的任务是“无中生有”。它接收一个100维的随机向量经过层层放大和变换最终输出一个28x28的图像矩阵。我们在输出层使用Tanh激活函数将像素值映射到[-1, 1]区间。这是一种常见的做法因为Tanh函数的输出中心是0有助于模型的收敛。frommindsporeimportnnimportmindspore.opsasops img_size28# 图像尺寸classGenerator(nn.Cell):def__init__(self,latent_size,auto_prefixTrue):super(Generator,self).__init__(auto_prefixauto_prefix)self.modelnn.SequentialCell()# 第一层将100维隐码映射到128维self.model.append(nn.Dense(latent_size,128))self.model.append(nn.ReLU())# 第二层128 - 256self.model.append(nn.Dense(128,256))self.model.append(nn.BatchNorm1d(256))self.model.append(nn.ReLU())# 第三层256 - 512self.model.append(nn.Dense(256,512))self.model.append(nn.BatchNorm1d(512))self.model.append(nn.ReLU())# 第四层512 - 1024self.model.append(nn.Dense(512,1024))self.model.append(nn.BatchNorm1d(1024))self.model.append(nn.ReLU())# 输出层1024 - 784 (28*28)self.model.append(nn.Dense(1024,img_size*img_size))# 使用Tanh将输出值压缩到[-1, 1]self.model.append(nn.Tanh())defconstruct(self,x):imgself.model(x)# 将平铺的向量重塑回图像形状 (N, 1, 28, 28)returnops.reshape(img,(-1,1,28,28))net_gGenerator(latent_size)net_g.update_parameters_name(generator)1.2.2 判别器 (Discriminator)判别器的任务是“明辨真伪”。它接收一张图片无论是真实的还是生成的输出一个0到1之间的概率值。1代表它认为这是真图0代表是假图。这里我们使用LeakyReLU作为激活函数它在负值区间也有一个很小的斜率可以避免神经元“死亡”的问题。输出层使用Sigmoid函数将结果压缩成概率值。classDiscriminator(nn.Cell):def__init__(self,auto_prefixTrue):super().__init__(auto_prefixauto_prefix)self.modelnn.SequentialCell()# 输入层接收平铺后的784维图像向量# [N, 784] - [N, 512]self.model.append(nn.Dense(img_size*img_size,512))self.model.append(nn.LeakyReLU())# 中间层512 - 256self.model.append(nn.Dense(512,256))self.model.append(nn.LeakyReLU())# 输出层256 - 1self.model.append(nn.Dense(256,1))# Sigmoid输出概率self.model.append(nn.Sigmoid())defconstruct(self,x):# 将图像展平x_flatops.reshape(x,(-1,img_size*img_size))returnself.model(x_flat)net_dDiscriminator()net_d.update_parameters_name(discriminator)1.2.3 损失函数与优化器我们要同时训练两个网络所以需要定义两个优化器。这里都选用Adam优化器。损失函数使用二进制交叉熵损失BCELoss这在二分类问题中非常标准。lr0.0002# 学习率# 二进制交叉熵损失函数adversarial_lossnn.BCELoss(reductionmean)# 分别为生成器和判别器定义优化器optimizer_dnn.Adam(net_d.trainable_params(),learning_ratelr,beta10.5,beta20.999)optimizer_gnn.Adam(net_g.trainable_params(),learning_ratelr,beta10.5,beta20.999)# 更新参数名称防止冲突optimizer_g.update_parameters_name(optim_g)optimizer_d.update_parameters_name(optim_d)1.3 训练过程训练GAN就像是在维持一种微妙的平衡。我们需要交替训练判别器和生成器训练判别器给它看真图希望它输出1。给它看生成器造的假图希望它输出0。计算两部分的损失更新判别器的参数。训练生成器生成一批假图给判别器看。这次我们希望骗过判别器也就是希望判别器输出1。计算损失更新生成器的参数。MindSpore的函数式编程风格在这里体现得很明显我们定义了前向计算函数然后利用value_and_grad自动获取梯度。importosimporttimeimportmindsporeasmsfrommindsporeimportsave_checkpoint# 训练配置total_epoch200checkpoints_path./result/checkpointsimage_path./result/images# 确保目录存在os.makedirs(checkpoints_path,exist_okTrue)os.makedirs(image_path,exist_okTrue)# 生成器的前向计算与损失defgenerator_forward(test_noises):fake_datanet_g(test_noises)fake_outnet_d(fake_data)# 生成器的目标是让判别器认为这些假图是真图标签为1loss_gadversarial_loss(fake_out,ops.ones_like(fake_out))returnloss_g# 判别器的前向计算与损失defdiscriminator_forward(real_data,test_noises):# 造假图fake_datanet_g(test_noises)# 判别器看假图fake_outnet_d(fake_data)# 判别器看真图real_outnet_d(real_data)# 真图的标签应该是1real_lossadversarial_loss(real_out,ops.ones_like(real_out))# 假图的标签应该是0fake_lossadversarial_loss(fake_out,ops.zeros_like(fake_out))loss_dreal_lossfake_lossreturnloss_d# 自动微分获取梯度函数grad_gms.value_and_grad(generator_forward,None,net_g.trainable_params())grad_dms.value_and_grad(discriminator_forward,None,net_d.trainable_params())deftrain_step(real_data,latent_code):# 1. 训练判别器loss_d,grads_dgrad_d(real_data,latent_code)optimizer_d(grads_d)# 2. 训练生成器loss_g,grads_ggrad_g(latent_code)optimizer_g(grads_g)returnloss_d,loss_g# 辅助函数保存生成的图片defsave_imgs(gen_imgs1,idx):fori3inrange(gen_imgs1.shape[0]):plt.subplot(5,5,i31)# 将像素值从[-1, 1]还原到[0, 1]用于显示plt.imshow(gen_imgs1[i3,0,:,:]/20.5,cmapgray)plt.axis(off)plt.savefig(image_path/test_{}.png.format(idx))# 开启训练模式net_g.set_train()net_d.set_train()losses_g,losses_d[],[]print(开始训练...)forepochinrange(total_epoch):starttime.time()for(iter,data)inenumerate(mnist_ds):image,latent_codedata# 将图片数据归一化到[-1, 1]image(image-127.5)/127.5imageimage.reshape(image.shape[0],1,image.shape[1],image.shape[2])# 执行一步训练d_loss,g_losstrain_step(image,latent_code)ifiter%1000:print(fEpoch:[{epoch:3d}/{total_epoch:3d}], step:[{iter:4d}], floss_d:{d_loss.asnumpy():.4f}, loss_g:{g_loss.asnumpy():.4f})# 记录损失losses_d.append(d_loss.asnumpy())losses_g.append(g_loss.asnumpy())# 每个epoch结束后生成一张测试图看看效果gen_imgsnet_g(test_noise)save_imgs(gen_imgs.asnumpy(),epoch)# 保存模型权重ifepoch%100:# 每10个epoch保存一次避免文件过多save_checkpoint(net_g,checkpoints_path/Generator%d.ckpt%(epoch))save_checkpoint(net_d,checkpoints_path/Discriminator%d.ckpt%(epoch))print(训练结束)1.4 结果分析与推理训练完成后我们最关心的当然是效果。1.4.1 损失曲线观察损失曲线可以帮助我们判断模型是否收敛。理想情况下生成器和判别器的损失应该在某个值附近波动呈现出一种胶着状态。如果一方的损失迅速降为0说明另一方太弱了博弈失败。plt.figure(figsize(6,4))plt.title(Generator and Discriminator Loss During Training)plt.plot(losses_g,labelG,colorblue)plt.plot(losses_d,labelD,colororange)plt.xlabel(iterations)plt.ylabel(Loss)plt.legend()plt.show()1.4.2 加载模型进行推理既然模型已经训练好了我们就可以把训练好的权重加载进来随时生成我们需要的手写数字。这一步在实际应用中非常重要被称为“推理”。# 加载之前保存的第199个epoch的权重test_ckpt./result/checkpoints/Generator199.ckptparameterms.load_checkpoint(test_ckpt)ms.load_param_into_net(net_g,parameter)# 生成新的随机噪声# 这里的噪声维度必须和训练时保持一致100维test_dataTensor(np.random.normal(0,1,(25,100)).astype(np.float32))# 推理让生成器生成图片# transpose是将数据格式从(N, C, H, W)转换为(N, H, W, C)以便matplotlib显示imagesnet_g(test_data).transpose(0,2,3,1).asnumpy()# 展示结果figplt.figure(figsize(3,3),dpi120)foriinrange(25):fig.add_subplot(5,5,i1)plt.axis(off)plt.imshow(images[i].squeeze(),cmapgray)plt.show()如果一切顺利你应该能看到25个像模像样的手写数字。虽然它们可能不如人类书写得那么完美但考虑到这是由一堆随机数“凭空”变出来的这已经足够神奇了。2. 总结通过这个实战项目我们完整地走过了GAN的开发流程从数据准备、模型搭建到对抗训练和最终推理。GAN的训练通常比普通神经网络要困难因为它涉及两个网络的动态博弈容易出现模式崩塌Mode Collapse或不收敛的问题。但在本例中通过简单的全连接网络和MNIST数据集我们成功验证了GAN的强大能力。这只是生成式AI的冰山一角。掌握了基础的GAN之后你可以进一步探索DCGAN深度卷积GAN、CycleGAN图像风格迁移等更高级的模型去创造更多不可思议的内容。