GAN的项目结构(用李宏毅老师的头像生成hw举例)

GAN的项⽬结构(⽤李宏毅⽼师的头像⽣成hw举例)
⼀、⼀堆废话
研⼀刚开学,选了导师以后开始定⽅向,本来是想去做mhy那个动作⽣成的岗位,然后去给导师说做姿态识别,⽼师给我表⽰⽀持以后叫我开始看双流法啥的,后来想了想,发现应该是学GAN才对
给导师说了以后,导师还是给了⽀持并且告诉我⼤致⽅向(呜呜呜王⽼师太好了,⼜负责⼜包容),所以导师给了推荐就是李宏毅⽼师的GAN教学视频,⽤了快不到两个星期刷了前⾯(后⾯的各类gan我还没看,只想赶紧做点东西出来)然后就看见有个hw是动漫头像的⽣成⼆、真正的CV⼯程师!(ctrl+c&ctrl+v)
我⾃⼰总结了⼀下gan的⼤致内容准备开始凭着⾃⼰的理解写代码,最后倒是写完了,但是⽣成的全是⿊的图⽚,问题应该是我最后⽣成的像素的值由于归⼀化或者sigmoid函数之类的东西把我最后输出来的像素值全部只有0到1的⼤⼩
接着去⽹上扒了别⼈的代码,是csdn⼀个叫的博主,⾥⾯说的很明⽩了,我这⾥把我⾃⼰总结出来的流程⼤概写⼀下
从知乎⼀个叫这个问题⾥有回答总结了流程
模型定义
摩擦搅拌焊接数据处理和加载
训练模型(Train and Validate)
测试模型
训练过程可视化(可选)
但是在GAN⾥好像没有测试模型这⼀步(⽬前我不知道,知道以后会回来补充的),毕竟也不像分类问题那样可以测试,所以我总结的是
各项参数定义
模型定义
训练模型(数据处理和加载也包含在内)
输出⽣成器的⽣成结果
整个项⽬⽂件已经上传到我的了,没有包含数据集(反正也是写给⾃⼰看的),要数据集的话搜⼀下也能到
其中运⾏"main.py"就可以跑起来整个代码
1import train
2import generate
3
4def main():
5# 训练模型
ain()
7# ⽣成图⽚
8    ate()
9
10
11if__name__ == '__main__':
12    main()
View Code
main⽅法中明确了主要就是训练和输出整个训练结束的效果
所以我会依照上⾯的步骤贴上每个代码
三、具体步骤
1、各项参数定义
1class Config(object):
2"""
3定义⼀个配置类
4"""
5# 0.参数调整
6    data_path = '/extra_data'
7    virs = "result"
8    num_workers = 4  # 多线程
9    img_size = 64  # 剪切图⽚的像素⼤⼩
10    batch_size = 256  # 批处理数量
11    max_epoch = 400  # 最⼤轮次
12    lr1 = 2e-4  # ⽣成器学习率
13    lr2 = 2e-4  # 判别器学习率
14    beta1 = 0.5  # 正则化系数,Adam优化器参数
15    gpu = True  # 是否使⽤GPU运算(建议使⽤)
16    nz = 100  # 噪声维度
17    ngf = 64  # ⽣成器的卷积核个数
18    ndf = 64  # 判别器的卷积核个数
19
20# 1.模型保存路径
21    save_path = 'save_img/'# optg_path⽣成图⽚的保存路径
22# 判别模型的更新频率要⾼于⽣成模型
23    d_every = 1  # 每⼀个batch 训练⼀次判别器
24    g_every = 5  # 每1个batch训练⼀次⽣成模型
25    save_every = 5  # 每save_every次保存⼀次模型
26    netd_path = None
27    netg_path = None
28
29# 测试数据
30    gen_img = "result1.png"
31# 选择保存的照⽚
32# ⼀次⽣成保存64张图⽚
33    gen_num = 64
34    gen_search_num = 512
35    gen_mean = 0  # ⽣成模型的噪声均值
环氧大豆油丙烯酸酯36    gen_std = 1
View Code
2、模型定义
这⾥⽤的是最基础的GAN,所以就是vector→⽣成器→图⽚→判别器→打分这样的结构,没有其他的⽹络了
所以定义的就是两个模型Generation和Discrimination
Generation
as nn
2
3class Generation(nn.Module):
4def__init__(self, opt):
5        super(Generation, self).__init__()
6        f = f
7        self.Gene = nn.Sequential(
8            nn.ConvTranspose2d(in_, out_f * 8, kernel_size=4, stride=1, padding=0,
9                                bias=False),
10            nn.f * 8),
11            nn.ReLU(inplace=True),
12
13# 输⼊⼀个4*4*ngf*8
14            nn.ConvTranspose2d(in_f * 8, out_f * 4, kernel_size=4, stride=2, padding=1,
15                                bias=False),
16            nn.f * 4),
17            nn.ReLU(inplace=True),
18
19# 输⼊⼀个8*8*ngf*4
20            nn.ConvTranspose2d(in_f * 4, out_f * 2, kernel_size=4, stride=2, padding=1,
21                                bias=False),
22            nn.f * 2),
23            nn.ReLU(inplace=True),
24
25# 输⼊⼀个16*16*ngf*2
26            nn.ConvTranspose2d(in_f * 2, out_f, kernel_size=4, stride=2, padding=1,
27                                bias=False),
28            nn.f),
29            nn.ReLU(inplace=True),
30
31# 输⼊⼀张32*32*ngf
32            nn.ConvTranspose2d(in_f, out_channels=3, kernel_size=5, stride=3, padding=1, bias=False),
33
34# Tanh收敛速度快于sigmoid,远慢于relu,输出范围为[-1,1],输出均值为0
35            nn.Tanh(),
36
37        )  # 输出⼀张96*96*3
38
39def forward(self, x):
40return self.Gene(x)
View Code
⽤convtranspose2d反卷积做上采样
批标准化层的作⽤,使得每⼀层的输出都尽⼒较为分散的落在数轴的两端,尽量使得数据处于梯度的敏感区域,加速梯度下降的过程如下
这⾥的relu函数⾥有⼀个inplace参数,这个参数作⽤为
产⽣的计算结果不会有影响。利⽤in-place计算可以节省内(显)存,同时还可以省去反复申请和释放内存的时间。但是会对原变量覆盖,所以只要不带来错误就⽤。
Discrimination
as nn
2
3class Discrimination(nn.Module):
4def__init__(self, opt):
5        super(Discrimination, self).__init__()
6        self.ndf = opt.ndf
7        self.Discrim = nn.Sequential(
8            nn.Conv2d(in_channels=3, out_channels=self.ndf, kernel_size=5, stride=3, padding=1, bias=False),
9            nn.LeakyReLU(negative_slope=0.2, inplace=True),
10
11            nn.Conv2d(in_channels=self.ndf, out_channels=self.ndf * 2, kernel_size=4, stride=2, padding=1, bias=False),
12            nn.BatchNorm2d(self.ndf * 2),
13            nn.LeakyReLU(0.2, True),
14
15            nn.Conv2d(in_channels=self.ndf * 2, out_channels=self.ndf * 4, kernel_size=4, stride=2, padding=1,
16                      bias=False),
17            nn.BatchNorm2d(self.ndf * 4),
18            nn.LeakyReLU(0.2, True),
19
20            nn.Conv2d(in_channels=self.ndf * 4, out_channels=self.ndf * 8, kernel_size=4, stride=2, padding=1,
21                      bias=False),
22            nn.BatchNorm2d(self.ndf * 8),
23            nn.LeakyReLU(0.2, True),
24
25
26            nn.Conv2d(in_channels=self.ndf * 8, out_channels=1, kernel_size=4, stride=1, padding=0, bias=True),
27
28            nn.Sigmoid()
29        )
30
31def forward(self, x):
32# 展平后返回
33return self.Discrim(x).view(-1)
View Code
这⾥⽤了leakyrelu(也就是PRelu,parameter relu)的激活函数,GAN的创始⼈Ian Goodfellow说过(知乎看到的,但是我没到出处): Leaky relu helps to make sure the gradient can flow through the entire architecture. That's an important consideration for any machine learning model, but even more important for GAN's.
leakyrelu防⽌了relu由于输出为负时,反向传播梯度为零导致神经元死亡
relu和leakyrelu函数图如下
3、训练模型(数据处理和加载也包含在内)
因为图像全部都在⼀个⽂件夹⾥,所以这⾥没有重写dataset来做数据集⽽⽤的是imagefolder,以后做⾳乐到动作的⽣成的话,还需要⾃⼰重写⼀些dataset
1from tqdm import tqdm
2import torch
3import torchvision as tv
4from torch.utils.data import DataLoader
as nn
6from Config import Config
7from Model.Generation import Generation
8from Model.Discrimination import Discrimination
9
10 opt = Config()
11
12def train(**kwargs):
13# 配置属性
14# 如果函数⽆字典输⼊则使⽤opt中设定好的默认超参数
15for k_, v_ in kwargs.items():
16        setattr(opt, k_, v_)
17
18# device(设备),分配设备
19if opt.gpu:
20        device = torch.device("cuda")
21else:
22        device = torch.device('cpu')
23
24# 数据预处理1
25# transforms 模块提供⼀般图像转换操作类的功能,最后转成floatTensor
冰醋酸溶液
26# tv.transforms.Compose⽤于组合多个tv.transforms操作,定义好transforms组合操作后,直接传⼊图⽚即可进⾏处理 27# tv.transforms.Resize,对PIL Image对象作resize运算,数值保存类型为float64
28# tv.transforms.CenterCrop, 中⼼裁剪
29# tv.transforms.ToTensor,将opencv读到的图⽚转为torch image类型(通道,像素,像素),且把像素范围转为[0,1] 30# tv.transforms.Normalize,执⾏image = (image - mean)/std 数据归⼀化操作,⼀参数是mean,⼆参数std
31# 因为是三通道,所以mean = (0.5, 0.5, 0.5),从⽽转成[-1, 1]范围
32    transforms = tv.transforms.Compose([
33# 3*96*96
34        tv.transforms.Resize(opt.img_size),  # 缩放到 img_size* img_size
35# 中⼼裁剪成96*96的图⽚。因为本实验数据已满⾜96*96尺⼨,可省略
36# tv.transforms.CenterCrop(opt.img_size),
37
消毒干衣机38# ToTensor 和 Normalize 搭配使⽤
39        tv.transforms.ToTensor(),
40        tv.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
41    ])
42
43# 加载数据并使⽤定义好的transforms对图⽚进⾏预处理,这⾥⽤的是直接定义法
44# dataset是⼀个包装类,将数据包装成Dataset类,⽅便之后传⼊DataLoader中
45# 写法2:
46# 定义类Dataset(Datasets)包装类,重写__getitem__(进⾏transforms系列操作)、__len__⽅法(获取样本个数) 47# ### 两种写法有什么区别
48    dataset = tv.datasets.ImageFolder(root=opt.data_path, transform=transforms)
49
50# 数据预处理2
51# 查看drop_last操作,
52    dataloader = DataLoader(
53        dataset,  # 数据加载
54        batch_size=opt.batch_size,  # 批处理⼤⼩设置
55        shuffle=True,  # 是否进⾏洗牌操作
56# num_workers=opt.num_workers,    # 是否进⾏多线程加载数据设置
57        drop_last=True  # 为True时,如果数据集⼤⼩不能被批处理⼤⼩整除,则设置为删除最后⼀个不完整的批处理。
58    )
59
60# 初始化⽹络
61    netg, netd = Generation(opt), Discrimination(opt)
62# 判断⽹络是否有权重数值
63# ### storage存储
64    map_location = lambda storage, loc: storage
65
66# torch.load模型加载,即有模型加载模型在该模型基础上进⾏训练,没有模型则从头开始
67# f:类⽂件对象,如果有模型对象路径,则加载返回
68# map_location:⼀个函数或字典规定如何remap存储位置
69# net.load_state_dict将加载出来的模型数据加载到构建好的net⽹络中去
70if optg_path:
71        netg.load_state_dict(torch.load(f=optg_path, map_location=map_location))
72if optd_path:
73        netd.load_state_dict(torch.load(f=optd_path, map_location=map_location))
74
75# 搬移模型到之前指定设备,本⽂采⽤的是cpu,分配设备
76    (device)
77    (device)
78
79# 定义优化策略
80# torch.optim包内有多种优化算法,
81# Adam优化算法,是带动量的惯性梯度下降算法
82    optimize_g = torch.optim.Adam(netg.parameters(), lr=opt.lr1, betas=(opt.beta1, 0.999))
83    optimize_d = torch.optim.Adam(netd.parameters(), lr=opt.lr2, betas=(opt.beta1, 0.999))
84
85# 计算⽬标值和预测值之间的交叉熵损失函数
86# BCEloss:-w(ylog x +(1 - y)log(1 - x))
87# y为真实标签,x为判别器打分(sigmiod,1为真0为假),加上负号,等效于求对应标签下的最⼤得分
88# to(device),⽤于指定CPU/GPU
89    criterions = nn.BCELoss().to(device)
90
91# 定义标签,并且开始注⼊⽣成器的输⼊noise
92    true_labels = s(opt.batch_size).to(device)
93    fake_labels = s(opt.batch_size).to(device)
94
95# ⽣成满⾜N(1,1)标准正态分布,维(100维),opt.batch_size个数的随机噪声
96    noises = torch.randn(opt.batch_size, , 1, 1).to(device)
97
98# ⽤于保存模型时作⽣成图像⽰例
99    fix_noises = torch.randn(opt.batch_size, , 1, 1).to(device)
100
101# 训练⽹络
102# 设置迭代
103for epoch in range(opt.max_epoch):
104# tqdm(iterator()),函数内嵌迭代器,⽤作循环的进度条显⽰
105for ii_, (img, _) in tqdm((enumerate(dataloader))):
106# 将处理好的图⽚赋值
107            real_img = (device)
108
109# 开始训练⽣成器和判别器
110# 注意要使得⽣成的训练次数⼩于⼀些
111# 每⼀轮更新⼀次判别器
112if ii_ % opt.d_every == 0:
113# 优化器梯度清零
114                _grad()
115
116# 训练判别器
117# 把判别器的⽬标函数分成两段分别进⾏反向求导,再统⼀优化
118# 真图
119# 把所有的真样本传进netd进⾏训练,
120                output = netd(real_img)
121# ⽤之前定义好的交叉熵损失函数计算损失c4烯烃
122                error_d_real = criterions(output, true_labels)
123# 误差反向计算
124                error_d_real.backward()
125
126# 随机⽣成的假图
127# .detach() 返回相同数据的 tensor ,且 requires_grad=False
128#  .detach()做截断操作,⽣成器不记录判别器采⽤噪声的梯度
129                noises = noises.detach()
130# 通过⽣成模型将随机噪声⽣成为图⽚矩阵数据
131                fake_image = netg(noises).detach()
132# 将⽣成的图⽚交给判别模型进⾏判别
133                output = netd(fake_image)
134# 再次计算损失函数的计算损失
135                error_d_fake = criterions(output, fake_labels)
136# 误差反向计算
137# 求导和优化(权重更新)是两个独⽴的过程,只不过优化时⼀定需要对应的已求取的梯度值。
138# 所以求得梯度值很关键,⽽且,经常会累积多种loss对某⽹络参数造成的梯度,⼀并更新⽹络。
139                error_d_fake.backward()
140
141# ‘’‘
142# 关于为什么要分两步计算loss:
143# 我们已经知道,BCEloss相当于计算对应标签下的得分,那么我们
144# 把真样本传⼊时,因为标签恒为1,BCE此时只有第⼀项,即真样本得分项
145# 要补齐成前⽂提到的判别器⽬标函数,需要再添置假样本得分项,故两次分开计算梯度,各⾃最⼤化各⾃的得分(假样本得分是log(1-D(x)))
146# 再统⼀进⾏梯度下降即可
147# ’‘’
148# 计算⼀次Adam算法,完成判别模型的参数迭代
149# 多个不同loss的backward()来累积同⼀个⽹络的grad,计算⼀次Adam即可
150                optimize_d.step()
151
152# 训练判别器
153if ii_ % opt.g_every == 0:
154                _grad()
155# ⽤于netd作判别训练和⽤于netg作⽣成训练两组噪声需不同
156                py_(torch.randn(opt.batch_size, , 1, 1))
157                fake_image = netg(noises)
158                output = netd(fake_image)
自动面膜机159# 此时判别器已经固定住了,BCE的⼀项为定值,再求最⼩化相当于求⼆项即G得分的最⼤化
160                error_g = criterions(output, true_labels)
161                error_g.backward()
162
163# 计算⼀次Adam算法,完成判别模型的参数迭代
164                optimize_g.step()
165
166# 保存模型
167if (epoch + 1) % opt.save_every == 0:
168            fix_fake_image = netg(fix_noises)
169            tv.utils.save_image(fix_fake_image.data[:64], "%s/%s.png" % (opt.save_path, epoch), normalize=True)
170
171            torch.save(netd.state_dict(), 'sava_pra/' + 'netd_{0}.pth'.format(epoch))
172            torch.save(netg.state_dict(), 'sava_pra/' + 'netg_{0}.pth'.format(epoch))
View Code
1 setattr(opt, k_, v_)
这个函数的作⽤是可以更改opt类⾥的属性k_的值为v_
晚上读代码的时候和组内的⼤佬⼀起讨论了⼀下这个transformspose⾥的resize是怎么做到的,我们本来以为是padding⿊边,然后后来

本文发布于:2024-09-22 03:35:17,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/1/239690.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:模型   判别   数据   训练
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议