我对使用GAN进行艺术创作的想法很感兴趣,因此我开始研究人们设法创造的东西,并且遇到了Mike Tyka的工作,他是Google的研究人员,我发现他对此非常着迷,这促使我开始创建自己的GAN项目来从事艺术创作。
当我开始这个项目时,我并没有意识到训练GAN模型有多困难,并且花费了很多试验和错误才能得出令人满意的结果。因此,在本文中,我将经历构建DCGAN模型的历程,包括我所面临的挑战以及为达到最终模型所做的工作。
虽然并不完美,但结果却是不错的,因为我们仅使用本文提出的具有随机图像增强和特征匹配功能的无监督DCGAN。
我不会在更高的层次上讨论GANs,因为我认为有很多文章和视频都涉及到这一点,相反,我将只关注技术细节和我的整个过程。项目的完整代码在本文的最后提供。
数据集和预处理
我使用的维基艺术数据集可以在这里下载(https://www.kaggle.com/ipythonx/wikiart-gangogh-creating-art-gan)。这里不需要下载所有的东西,因为需要做的时肖像画,所以我只下载了portrait文件夹,,但你可以随意尝试其他东西。
接下来,我编写了一个python脚本preprocessing.py,以获取数据集以进行训练。我们可以将原始图像的尺寸调整为所需的64x64尺寸,但是如果您查看数据集,您会发现它可能会出错,因为就不希望有人像被压扁,或者他们的手被剪成正方形。理想情况下,我们希望正方形图像的中间带有人脸,所以我使用的“face-recognition”库,可以通过pip进行安装,并使用它遍历每张图像,找到人脸坐标,将其偏移指定的数量 并保存它们。
#cropfaceswithoffsetdefcrop_faces(in_path, out_path, offset): files=os.listdir(in_path) forindex, fileinenumerate(files): try: img=Image.open(in_path+file) img_arr=np.array(img) top, right, bottom, left=face_recognition.face_locations(img_arr)[0] face_image=img_arr[top-offset:bottom+offset, left-offset:right+offset] img=Image.fromarray(face_image) img.save(out_path+str(index)+'.jpg') except: pass
然后应用resize()方法,尽管人脸识别工具通常会创建方形或几乎为方形的图像,所以简单地跳过这一部分,只使用pytorch的resize方法不会有太大区别。PIL的调整大小方法与下面定义的方法的区别在于,它会调整图像的大小并对其进行裁剪,以使它们适合指定尺寸的正方形图像,从而避免挤压。但是无论如何,我已经为其他项目构建了此方法,因此不妨使用它。
defresize(in_path, out_path, dim, aspect_ratio=True): ifaspect_ratio: files=os.listdir(path) forindex, fileinenumerate(files): img=Image.open(path+file) ifimg.size[0]<img.size[1]: ratio=dim/float(img.size[0]) new_x=dimnew_y=int(ratio*img.size[1]) img=img.resize((new_x, new_y)) val=int((new_y-dim)/2.0) ltrb= (0,val,img.size[0],dim+val) img=img.crop(ltrb) elifimg.size[0]>img.size[1]: ratio=dim/float(img.size[1]) new_x=int(ratio*img.size[0]) new_y=dimimg=img.resize((new_x, new_y)) val=int((new_x-dim)/2.0) ltrb= (val,0,dim+val,img.size[1]) #croppingimg=img.crop(ltrb) #ifimageissquareelse: img=img.resize((dim, dim)) img=img.convert('RGB') img.save(out_path+str(index)+'.jpg') else: files=os.listdir(path) forindex, fileinenumerate(files): try: img=Image.open(path+file) new_img=img.resize((dim,dim)) new_img.save(out_path+'_imgs/'+str(index)+'.jpg') except: pass
下载数据集后,创建另一个文件夹,在其中输出调整大小后的图像。然后只需运行上面的方法,指定原始数据集的路径,刚刚创建的输出文件夹以及所需的维度(64)
最后,我只是运行了rename_images方法以按数字顺序重命名文件,这是不必要的,但只是为了使所有内容变得有条理
#renameimagesnumerically0.jpg, 1.jpg, 2.jpg... defrename_images(path): files=os.listdir(path) forindex, fileinenumerate(files): os.rename(os.path.join(path, file), os.path.join(path, ''.join([str(index), '.jpg'])))
DCGAN模型
鉴别器
这是我们项目的核心。我创建了一个新文件“ models.py”来保存我们的鉴别器,生成器和一种初始化权重的方法。下图表示具有功能匹配的DCGAN鉴别器架构。
在最初的DCGAN论文中,定义了一系列具有BatchNorm和LeakyReLU的2D卷积层,以从图像中提取特征,并最终输出单个值来预测输入的图像是假的还是真实的。但是,使用此改进的版本,最终的卷积层之后是完整连接的线性层,因为生成器将使用该层来尝试匹配。
新目标不是直接使鉴别器的输出最大化,而是要求生成器生成与实际数据的统计信息匹配的数据,在这种情况下,我们仅使用鉴别器来指定我们认为值得匹配的统计信息。具体来说,我们训练生成器以使其与鉴别器中间层上的要素的期望值匹配。
classDiscriminator(nn.Module): def__init__(self, channels_img, features_d): super(Discriminator, self).__init__() self.disc=nn.Sequential( #input: Nxchannels_imgx64x64nn.Conv2d(channels_img, features_d, kernel_size=4, stride=2, padding=1), nn.LeakyReLU(0.2), #_block(in_channels, out_channels, kernel_size, stride, padding) self._block(features_d, features_d*2, 4, 2, 1), self._block(features_d*2, features_d*4, 4, 2, 1), self._block(features_d*4, features_d*8, 4, 2, 1), #Afterall_blockimgoutputis4x4 (Conv2dbelowmakesinto1x1) ) self.output=nn.Sequential( nn.Sigmoid(), ) def_block(self, in_channels, out_channels, kernel_size, stride, padding): returnnn.Sequential( nn.Conv2d( in_channels, out_channels, kernel_size, stride, padding, bias=False, ), nn.InstanceNorm2d(out_channels, affine=True), nn.LeakyReLU(0.2), ) defforward(self, x, feature_matching=False): features=self.disc(x) output=self.output(features) iffeature_matching: returnfeatures.view(-1, 512*4*4), outputelse: returnoutput
在这种情况下,特征是张量形状时(batch_size,1024),一旦开始训练,feature_matching参数的使用将变得更加清晰。但是为了快速运行此代码,我们定义了一个辅助函数_block(),它添加了DCGAN论文中建议的一层,输入通过顺序模型作为(batch_size,3,64,64)张量并卷积为 一个具有512 * 4 * 4个特征的向量,之后我仅使用了S型激活函数就为假图像返回0,为实数返回1。甚至使用模型,减去图层,观察输出的尺寸等。我始终在我的项目中保留一个park.py文件以进行打印 变量并测试我不确定的任何内容。