那么现在来开始调试
不过有了之前CycleGAN的食用经验,这里就重点说变动
先看数据集
dataset = DataLoader(opt)
还是 UnalignedDataset
主要变动是这里
self.dirs = sorted(glob.glob(datapath)) 在原本的datapath下遍历对应不同风格的目录
self.paths = [sorted(make_dataset(d)) for d in self.dirs] 储存在self.paths里
self.sizes = [len(p) for p in self.paths] 存放每类的长度
然后在 def __getitem__(self, index): 中
本质上没有特别大变换.并不是一次拿出来4个,而是一次拿出来两个随机的(大雾!)
DA, DB = random.sample(range(len(self.dirs)), 2) 随机获取0~N-1的索引值 比如这里是四类就是0~3中间的两个数
index_A = random.randint(0, self.sizes[DA] – 1) 获取该类别下的一个索引
A_img, A_path = self.load_image(DA, index_A) 通过类别和索引获取A图片和路径信息
bundle = {‘A’: A_img, ‘DA’: DA, ‘path’: A_path} 打包组成字典
B图同上, 不过
bundle.update( {‘B’: B_img, ‘DB’: DB} ) 没有添加B图的路径B_path,有点怪,不过本来也没用的上不管了

然后进模型:
model = ComboGANModel(opt)
其实主要变动只是这里
定义网络结构
train.py | model = ComboGANModel(opt) | 实例化ComboGAN的模型类 |
models/combogan_model.py | class ComboGANModel(BaseModel) | 主要需要看的是定义生成器 self.netG = networks.define_G |
models/networks.py | def define_G() | 生成器定义函数,主要是个组合参数的工具函数 返回生成好的 plex_netG 对象 给 self.netG |
models/networks.py | class G_Plexer(Plexer): | 真正的生成器定义函数 n_domains 风格数量 字面意思一个int encoder 编码器类 ResnetGenEncoder enc_args 编码器参数 元组 enc_args decoder 解码器类 ResnetGenDecoder dec_args 解码器参数 元组 dec_args |
models/networks.py | class ResnetGenEncoder() class ResnetGenDecoder() | 编码器和解码器定义使用的类 定义方式很原生torch,就不细讲了 简单来说就是一个model数组不断叠,最后甩个序列化 |
models/networks.py | class ResnetBlock() | 言简意赅…… |
- define_G()内部
- 首先选择norm_layer
- n_blocks_enc 定义编码器残差快数量
- n_blocks_dec 定义解码器残差快数量
- dup_args 相关dropout,norm_layer,bias等参数 元组格式
- enc_args 编码器参数 元组格式
- dec_args 解码器参数 元组格式
- plex_netG = G_Plexer() 进行定义(好家伙才开始) 然后看右面
- 设置有关cuda,权重初始化等并返回 plex_netG 对象
- G_Plexer(Plexer)内部
- encoders 通过 encoder 和 enc_args 配合 组成一个n_domains长度的列表
- decoders 同上
- networks 把 encoders 和 decoders 合并

定义生成器后定义鉴别器self.netD
这个就不细说了,因为它根本没改就是之前CycleGAN的 NLayerDiscriminator
大概也是self.netD > define_D() > D_Plexer > NLayerDiscriminator
但是这里有一部分我没有看懂,该函数分为一个model_gray和一个model_rgb两个都有这个问题
这里我以model_rgb举例
(model_rgb): SequentialOutput(
(0): Sequential(
(0): Conv2d(3, 64, kernel_size=(4, 4), stride=(2, 2), padding=(2, 2))
(1): PReLU(num_parameters=1)
)
(1): Sequential(
(0): Conv2d(64, 129, kernel_size=(4, 4), stride=(2, 2), padding=(2, 2))
(1): InstanceNorm2d(129, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(2): PReLU(num_parameters=1)
) # ------output------
(2): Sequential(
(0): Conv2d(128, 257, kernel_size=(4, 4), stride=(2, 2), padding=(2, 2))
(1): InstanceNorm2d(257, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(2): PReLU(num_parameters=1)
) # ------output------
(3): Sequential(
(0): Conv2d(256, 513, kernel_size=(4, 4), stride=(2, 2), padding=(2, 2))
(1): InstanceNorm2d(513, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(2): PReLU(num_parameters=1)
) # ------output------
(4): Sequential(
(0): Conv2d(512, 512, kernel_size=(4, 4), stride=(1, 1), padding=(2, 2))
(1): InstanceNorm2d(512, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(2): PReLU(num_parameters=1)
(3): Conv2d(512, 1, kernel_size=(4, 4), stride=(1, 1), padding=(2, 2))
)
)
) # ------output------
如果没记错,stride>1有裕量时向下取整
出 = floor((入-核+2*边)/步)+1
首先是自带函数中刻意错开了通道
可以很明显看出, 如果是 1,3,156,256的输入
conv2d(3,64) > 1,64,129,129
conv2d(64,129) > 1,129,65,65
conv2d(128,257) > 1,257,33,33
conv2d(256,513) > 1,513,17,17
conv2d(512,512) > 1,512,18,18
conv2d(512,1) > 1,1,19,19
这每个都是特意+1错开一个让我十分看不懂

而且后面输出的时候会直接经过这个序列化对象直接出4个数组,分别是(当然,在经历一次在维度1的拼接,第一维度的长度会变成2倍)
1,65,65
1,33,33
1,17,17
1,19,19
我怀疑这种刻意不匹配会导致在左式标记 # ——output—— 部分输出的
(当然,不一定准),而且作者并没有仔细论述这里如此处理的必要性!

This is 《The discriminators remain untouched in your experiment》?!
定义损失函数
criterionCycle | torch.nn.SmoothL1Loss() 简单来说就是介于L1和MSE之间的一个损失函数, 损失小于某值使用MSE计算,损失大于某值使用L1计算 |
*criterionIdt | 经过调整的 torch.nn.SmoothL1Loss() 本质同上 # 需要 –lambda_identity 设置为非 0 元素 |
*criterionLatent | 经过调整的 torch.nn.SmoothL1Loss()本质同上 # 两个中间经过编码后的(BN,256.64.64)数组求损失 |
criterionGAN | GANLoss() 和本质我看了一下代码 经过了魔改不过 本质还是个可以根据真图和伪图计算的MSE |
但是部分地方出现了问题,criterionLatent想使用,必须要求lambda_enc大于0
疑惑,为啥还特意×0,是怕使用这个损失嘛?那我就不试毒了


定义优化器
均为Adam 没什么多说的,直接引用的函数
最后进迭代
额,怎么说呢,默认是不使用idt的前向G的时候
包括了 criterionGAN 和 criterionCycle 代替 G_Loss 和 Cycle_Loss
当前向D的时候
直接用 criterionGAN 代替 D_Loss
这个我觉得之后视频会更仔细说说,等做出来再说咯