搞这个GAN动漫头像生成真的是从入门到放弃再到入土再到诈尸开题的时候觉得不就是个DCGAN嘛老子一天写完结果真正动工的时候才发现到处都是坑。完整源码链接https://pan.quark.cn/s/1e54aa2ae950说数据正经的anime face数据集在kaggle上有但下载下来一看几十个G我硬盘都快爆了。而且有些图根本不是动漫脸混了一堆三次元的图进去我服了。与其在那花时间清洗数据不如自己写个生成器造数据这个项目仅供交差:import numpy as np import cv2 import random def generate_anime_face(img_size64): img np.ones((img_size, img_size, 3), dtypenp.uint8) * 255 skin (random.randint(200,255), random.randint(170,220), random.randint(160,210)) cx, cy img_size//2, img_size//22 axes_x, axes_y img_size//3, img_size//2-4 cv2.ellipse(img, (cx,cy), (axes_x,axes_y), 0,0,360, skin, -1)这代码看着简单吧但跑出来那些脸一个个歪瓜裂枣的眼睛位置不对啊嘴巴歪了啥的调参调了半天。而且opencv的坐标系跟我想的不一样以前用matplotlib用习惯了cv2的坐标是(height, width)跟人家相反的画着画着就画飞了…说到matplotlib这个SimHei字体的问题真的搞了我一个晚上。一开始画图中文全是方框框网上各种教程教你怎么下载字体啊配置啊试了一圈发现就两行代码的事:plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False就这么简单我他*折腾了三小时百度了十几篇文章才试出来很多文章写得云里雾里的根本没说要点。然后说这个DCGAN的模型结构我一开始照着网上一个教程抄的结果那个教程用的老版本torch很多API都变了什么nn.init.normal_参数顺序不一样啊nn.BCELoss和nn.BCEWithLogitsLoss的区别啊真是踩得死死的:class Generator(nn.Module): def __init__(self, latent_dim100, img_channels3, feature_base64): super().__init__() self.main nn.Sequential( nn.ConvTranspose2d(latent_dim, feature_base*8, 4, 1, 0, biasFalse), nn.BatchNorm2d(feature_base*8), nn.ReLU(True), nn.ConvTranspose2d(feature_base*8, feature_base*4, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_base*4), nn.ReLU(True), nn.ConvTranspose2d(feature_base*4, feature_base*2, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_base*2), nn.ReLU(True), nn.ConvTranspose2d(feature_base*2, feature_base, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_base), nn.ReLU(True), nn.ConvTranspose2d(feature_base, img_channels, 4, 2, 1, biasFalse), nn.Tanh() )网上说的那个DCGAN的结构啊什么4x4卷积核 stride2的配置但是不同的文章写出来feature_base的倍数不一样有的从64开始有的从128开始我试了好几种组合最后发现64起步在64x64的图上表现还行。还有那个权重初始化weights_init这个函数我刚开始忘记调用了结果训了20轮loss纹丝不动我还以为是模型写错了debug到凌晨两点才发现是初始化的问题淦!然后是训练GAN的训练是真的玄学。D loss一度降到0.000几G loss飙到十几完全就是mode collapse的经典症状。判别器太强了生成器根本骗不过它。网上说的什么不要训太多判别器、“标签平滑”、加噪声这些trick我试了一部分感觉效果最明显的是把label smoothing加上:self.real_label 0.9 self.fake_label 0.1替换原来的1.0和0.0之后训练稳定了很多D loss不会骤降到0了。训练过程的损失曲线我是这样画的:def plot_loss_curves(losses_g, losses_d, save_dir): fig, ax plt.subplots(figsize(10, 5)) ax.plot(losses_g, label生成器损失 (G), linewidth1.5, color#2196F3) ax.plot(losses_d, label判别器损失 (D), linewidth1.5, color#FF5722) ax.set_xlabel(Epoch) ax.set_ylabel(Loss) ax.set_title(GAN 训练损失曲线) ax.legend() ax.grid(True, alpha0.3) plt.savefig(os.path.join(save_dir, loss_curves.png))跑出来看这个图蓝线是G的loss橙线是D的loss前期D降得飞快G疯狂挣扎到后面慢慢收敛经典GAN训练曲线懂的都懂。还有一个坑是batch size我刚开始设了128结果GPU显存炸了虽然最后用了cpu后来改成32就好了。而且batch size太小的话BN层不稳定这个在DCGAN里特别明显loss一跳一跳的跟心电图一样。可视化那块我画了生成样本的grid和潜空间插值:def interpolate_latent(netG, latent_dim, device, steps10): z1 torch.randn(1, latent_dim, 1, 1, devicedevice) z2 torch.randn(1, latent_dim, 1, 1, devicedevice) with torch.no_grad(): imgs [] for alpha in np.linspace(0, 1, steps): z (1-alpha)*z1 alpha*z2 img netG(z).detach().cpu() imgs.append(denormalize(img))插值图出来的时候还挺好玩的从左到右能看到头像在平滑变化虽然因为训练轮数太少就30轮变化不是很明显但至少能看出脸在转方向啥的。多训几轮50-100轮效果应该会好很多。哦对还有那个数据加载的坑我一开始用ImageFolder那个类结果发现它需要子文件夹分类但我这个无监督学习的哪来的分类啊所有图都在一个文件夹里。后来自己写了个Dataset类:class AnimeFaceDataset(Dataset): def __init__(self, data_dir, img_size64): self.files [os.path.join(data_dir, f) for f in os.listdir(data_dir) if f.lower().endswith((.png, .jpg, .jpeg))] self.transform transforms.Compose([ transforms.Resize(img_size), transforms.CenterCrop(img_size), transforms.ToTensor(), transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5]) ])注意这里的Normalize是[-1,1]的归一化因为生成器最后用的是Tanh输出[-1,1]范围的跟[0,1]不匹配的话生成出来的图颜色会不对。这个坑我刚开始也不知道出来的图全是灰蒙蒙的像蒙了一层雾后来才发现是归一化的问题。还有那个drop_lastTrue这个也很关键如果batch不能整除的话最后一个batch小了BN会报错加上这个就安心了。再说说opencv画图的那个我画头发的时候用cv2.ellipse画了半椭圆当刘海但是不同颜色的头发搭配不同肤色有时候看起来很奇怪。我还加了腮红、高光啥的让数据稍微丰富一点:if random.random() 0.4: blush (190, 140, 140) for side in [-1, 1]: bx cx side*(axes_x//2-3) cv2.circle(img, (bx, mouth_y-1), max(1,int(4random.gauss(0,0.5))), blush, -1)随机加点高斯模糊和噪声让数据不那么干净这样GAN学到的特征会更多样一点。但说实话数据量只有2000张而且都是程序生成的多样性有限GAN很难学到真正的动漫风格只能学到这些几何图形的基本形状。如果要正经做这个课题的话建议用Danbooru或者Safebooru上的真实动漫图至少搞个几万张然后用StyleGAN或者StyleGAN2现在都出到3了DCGAN毕竟是2015年的东西了效果确实一般。最后的成果就是弄了个main.py来统一调度:parser argparse.ArgumentParser(descriptionDCGAN 动漫头像生成系统) parser.add_argument(--mode, typestr, defaultall, choices[gen_data,train,generate,all]) parser.add_argument(--epochs, typeint, default30) parser.add_argument(--batch_size, typeint, default64) parser.add_argument(--lr, typefloat, default0.0002)这样每次跑的时候不用改代码了命令行传参就行。完整的main.py会调data_generator生成数据然后trainer训练再visualizer出图一条龙服务。内存管理也是个坑python那个内存泄漏啊训着训着内存就飙上去了后来发现是matplotlib的figure没有close每次画完图不关就会积在内存里加了plt.close()就好了:plt.savefig(...) plt.close()还有一些零零碎碎的比如tqdm的desc参数中文显示乱码啊这个无所谓反正能看torch的FutureWarning说weights_onlyFalse不安全啊先不管了能跑就行opencv的imwrite不能写中文路径啊啥的每个坑都花了不少时间。总的来说这个项目吧代码没多少踩坑无数。最后跑出来看到那些生成的动漫头像虽然丑是丑了点毕竟就5轮训练但loss曲线和图表都是实实在在跑出来的数据不是瞎编的。生成出来的头像有的像外星人有的像抽象艺术30轮训练完稍微好一点了至少能看出眼睛鼻子嘴巴的位置是对的。我是真的服了GAN这个玩意儿了训练跟炼丹一样同样的代码换个人跑结果都不一样。这个毕设做完我对深度学习有了新的认识——调参才是核心技能模型结构反而是其次的。哦对了最后生成的那张final_generated.png里面16个头像虽然风格很抽象但颜色分布啥的确实跟训练数据相似说明生成器确实学到了数据的分布不是随机噪声。潜空间插值那张图也能看出从一张脸过渡到另一张脸的平滑变化这个是可以写进论文里的实验分析部分的。差不多就这些了能跑、出图、有代码、有文章交差!
基于生成对抗网络GAN的动漫头像生成系统
搞这个GAN动漫头像生成真的是从入门到放弃再到入土再到诈尸开题的时候觉得不就是个DCGAN嘛老子一天写完结果真正动工的时候才发现到处都是坑。完整源码链接https://pan.quark.cn/s/1e54aa2ae950说数据正经的anime face数据集在kaggle上有但下载下来一看几十个G我硬盘都快爆了。而且有些图根本不是动漫脸混了一堆三次元的图进去我服了。与其在那花时间清洗数据不如自己写个生成器造数据这个项目仅供交差:import numpy as np import cv2 import random def generate_anime_face(img_size64): img np.ones((img_size, img_size, 3), dtypenp.uint8) * 255 skin (random.randint(200,255), random.randint(170,220), random.randint(160,210)) cx, cy img_size//2, img_size//22 axes_x, axes_y img_size//3, img_size//2-4 cv2.ellipse(img, (cx,cy), (axes_x,axes_y), 0,0,360, skin, -1)这代码看着简单吧但跑出来那些脸一个个歪瓜裂枣的眼睛位置不对啊嘴巴歪了啥的调参调了半天。而且opencv的坐标系跟我想的不一样以前用matplotlib用习惯了cv2的坐标是(height, width)跟人家相反的画着画着就画飞了…说到matplotlib这个SimHei字体的问题真的搞了我一个晚上。一开始画图中文全是方框框网上各种教程教你怎么下载字体啊配置啊试了一圈发现就两行代码的事:plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False就这么简单我他*折腾了三小时百度了十几篇文章才试出来很多文章写得云里雾里的根本没说要点。然后说这个DCGAN的模型结构我一开始照着网上一个教程抄的结果那个教程用的老版本torch很多API都变了什么nn.init.normal_参数顺序不一样啊nn.BCELoss和nn.BCEWithLogitsLoss的区别啊真是踩得死死的:class Generator(nn.Module): def __init__(self, latent_dim100, img_channels3, feature_base64): super().__init__() self.main nn.Sequential( nn.ConvTranspose2d(latent_dim, feature_base*8, 4, 1, 0, biasFalse), nn.BatchNorm2d(feature_base*8), nn.ReLU(True), nn.ConvTranspose2d(feature_base*8, feature_base*4, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_base*4), nn.ReLU(True), nn.ConvTranspose2d(feature_base*4, feature_base*2, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_base*2), nn.ReLU(True), nn.ConvTranspose2d(feature_base*2, feature_base, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_base), nn.ReLU(True), nn.ConvTranspose2d(feature_base, img_channels, 4, 2, 1, biasFalse), nn.Tanh() )网上说的那个DCGAN的结构啊什么4x4卷积核 stride2的配置但是不同的文章写出来feature_base的倍数不一样有的从64开始有的从128开始我试了好几种组合最后发现64起步在64x64的图上表现还行。还有那个权重初始化weights_init这个函数我刚开始忘记调用了结果训了20轮loss纹丝不动我还以为是模型写错了debug到凌晨两点才发现是初始化的问题淦!然后是训练GAN的训练是真的玄学。D loss一度降到0.000几G loss飙到十几完全就是mode collapse的经典症状。判别器太强了生成器根本骗不过它。网上说的什么不要训太多判别器、“标签平滑”、加噪声这些trick我试了一部分感觉效果最明显的是把label smoothing加上:self.real_label 0.9 self.fake_label 0.1替换原来的1.0和0.0之后训练稳定了很多D loss不会骤降到0了。训练过程的损失曲线我是这样画的:def plot_loss_curves(losses_g, losses_d, save_dir): fig, ax plt.subplots(figsize(10, 5)) ax.plot(losses_g, label生成器损失 (G), linewidth1.5, color#2196F3) ax.plot(losses_d, label判别器损失 (D), linewidth1.5, color#FF5722) ax.set_xlabel(Epoch) ax.set_ylabel(Loss) ax.set_title(GAN 训练损失曲线) ax.legend() ax.grid(True, alpha0.3) plt.savefig(os.path.join(save_dir, loss_curves.png))跑出来看这个图蓝线是G的loss橙线是D的loss前期D降得飞快G疯狂挣扎到后面慢慢收敛经典GAN训练曲线懂的都懂。还有一个坑是batch size我刚开始设了128结果GPU显存炸了虽然最后用了cpu后来改成32就好了。而且batch size太小的话BN层不稳定这个在DCGAN里特别明显loss一跳一跳的跟心电图一样。可视化那块我画了生成样本的grid和潜空间插值:def interpolate_latent(netG, latent_dim, device, steps10): z1 torch.randn(1, latent_dim, 1, 1, devicedevice) z2 torch.randn(1, latent_dim, 1, 1, devicedevice) with torch.no_grad(): imgs [] for alpha in np.linspace(0, 1, steps): z (1-alpha)*z1 alpha*z2 img netG(z).detach().cpu() imgs.append(denormalize(img))插值图出来的时候还挺好玩的从左到右能看到头像在平滑变化虽然因为训练轮数太少就30轮变化不是很明显但至少能看出脸在转方向啥的。多训几轮50-100轮效果应该会好很多。哦对还有那个数据加载的坑我一开始用ImageFolder那个类结果发现它需要子文件夹分类但我这个无监督学习的哪来的分类啊所有图都在一个文件夹里。后来自己写了个Dataset类:class AnimeFaceDataset(Dataset): def __init__(self, data_dir, img_size64): self.files [os.path.join(data_dir, f) for f in os.listdir(data_dir) if f.lower().endswith((.png, .jpg, .jpeg))] self.transform transforms.Compose([ transforms.Resize(img_size), transforms.CenterCrop(img_size), transforms.ToTensor(), transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5]) ])注意这里的Normalize是[-1,1]的归一化因为生成器最后用的是Tanh输出[-1,1]范围的跟[0,1]不匹配的话生成出来的图颜色会不对。这个坑我刚开始也不知道出来的图全是灰蒙蒙的像蒙了一层雾后来才发现是归一化的问题。还有那个drop_lastTrue这个也很关键如果batch不能整除的话最后一个batch小了BN会报错加上这个就安心了。再说说opencv画图的那个我画头发的时候用cv2.ellipse画了半椭圆当刘海但是不同颜色的头发搭配不同肤色有时候看起来很奇怪。我还加了腮红、高光啥的让数据稍微丰富一点:if random.random() 0.4: blush (190, 140, 140) for side in [-1, 1]: bx cx side*(axes_x//2-3) cv2.circle(img, (bx, mouth_y-1), max(1,int(4random.gauss(0,0.5))), blush, -1)随机加点高斯模糊和噪声让数据不那么干净这样GAN学到的特征会更多样一点。但说实话数据量只有2000张而且都是程序生成的多样性有限GAN很难学到真正的动漫风格只能学到这些几何图形的基本形状。如果要正经做这个课题的话建议用Danbooru或者Safebooru上的真实动漫图至少搞个几万张然后用StyleGAN或者StyleGAN2现在都出到3了DCGAN毕竟是2015年的东西了效果确实一般。最后的成果就是弄了个main.py来统一调度:parser argparse.ArgumentParser(descriptionDCGAN 动漫头像生成系统) parser.add_argument(--mode, typestr, defaultall, choices[gen_data,train,generate,all]) parser.add_argument(--epochs, typeint, default30) parser.add_argument(--batch_size, typeint, default64) parser.add_argument(--lr, typefloat, default0.0002)这样每次跑的时候不用改代码了命令行传参就行。完整的main.py会调data_generator生成数据然后trainer训练再visualizer出图一条龙服务。内存管理也是个坑python那个内存泄漏啊训着训着内存就飙上去了后来发现是matplotlib的figure没有close每次画完图不关就会积在内存里加了plt.close()就好了:plt.savefig(...) plt.close()还有一些零零碎碎的比如tqdm的desc参数中文显示乱码啊这个无所谓反正能看torch的FutureWarning说weights_onlyFalse不安全啊先不管了能跑就行opencv的imwrite不能写中文路径啊啥的每个坑都花了不少时间。总的来说这个项目吧代码没多少踩坑无数。最后跑出来看到那些生成的动漫头像虽然丑是丑了点毕竟就5轮训练但loss曲线和图表都是实实在在跑出来的数据不是瞎编的。生成出来的头像有的像外星人有的像抽象艺术30轮训练完稍微好一点了至少能看出眼睛鼻子嘴巴的位置是对的。我是真的服了GAN这个玩意儿了训练跟炼丹一样同样的代码换个人跑结果都不一样。这个毕设做完我对深度学习有了新的认识——调参才是核心技能模型结构反而是其次的。哦对了最后生成的那张final_generated.png里面16个头像虽然风格很抽象但颜色分布啥的确实跟训练数据相似说明生成器确实学到了数据的分布不是随机噪声。潜空间插值那张图也能看出从一张脸过渡到另一张脸的平滑变化这个是可以写进论文里的实验分析部分的。差不多就这些了能跑、出图、有代码、有文章交差!