Globally and Locally Consistent Image Completion 实验记录
在过程中,在当时正在复现这篇论文的过程中,在了解细节之前,在想深入研究之前,在寻求实现思路之前,在思考实现路径之前,在探索实现方法之前,在尝试理解其工作原理之前,在寻求解决方案之前,在试图掌握其工作原理之前,在寻求解决问题策略之前
https://github.com/CoderAnn/GLCI
有问题可以留言,一定会尽全力解答。
实验过程简介:
首先,在2016年发表的Context Encoders:Feature Learning by Impainting论文中,我参考了SATOSHI IIZUKA等人在2017年发表的Globally and Locally Consistent Image Completion工作的复现工作。该论文主要通过将上下文编码器的思想与GAN结合进行图像补全研究。具体而言,在该方法中上下文编码器是通过先对图像进行编码再解码的过程学习图像特征并生成待修补区域对应的预测图;而GAN则用于判断生成的图像是否为虚假图像。
回顾GAN的训练机制:首先从z和x中随机采样一批数据,以反映其各自的分布特征;接着通过随机梯度下降算法对判别器D进行k次更新迭代以增强其鉴别能力;随后对生成器G进行一次优化调整;这种分阶段交替训练的方式主要是为了让判别器D能够持续有效地区分真伪样本;在实际编程实现中我们通常会采用多轮更新策略即每次仅更新生成器G而不频繁更新判别器D以免导致判别器过快收敛从而引发梯度消失的问题
而我要复现的论文其实基于一篇基准论文的基础上进行了优化工作,在其鉴别网络结构上做出了改进。具体而言,在原有的单个鉴别器设计基础上增加了第二个鉴別器节点,在整个鉴別器架构中分别负责全局内容一致性验证以及局部内容一致性分析两大功能模块。在鉴別器输出环节中,则采用了将两个分支特征融合后通过全连接层生成最终的概率预测值的方式进行处理。值得注意的是,在这篇论文的研究中还对传统的重构损失函数进行了优化设计,默认目标为均方误差(MSE)与生成对抗网络(GAN)结合的形式以提升整体模型稳定性
在具体的训练过程中首先采用MSE损失函数来更新补全过程Tc次;随后进行Td次鉴別器的参数更新;最后同步更新补全过程与鉴別器的参数。为了提高整体训练效果,在初始阶段优先优化补全过程是为了其恢复效果明显;这使得鉴別器能够快速识别出生成图像与真实图像之间的差异;在这一阶段由于生成图像质量较低导致鉴別器能够快速识别出生成图像与真实图像之间的差异;这使得鉴別器能够快速识别出生成图像与真实图像之间的差异;因此在这个阶段鉴別器尚未充分发挥作用
网络架构如下图所示:

训练过程:

实验经过:
整个训练流程已基本实现:首先导入数据集x,并随后通过生成随机点来确定mask值。随后将经过mask处理后的x(即x乘以mask)作为补全网络的输入参数,并对该输入进行优化以最小化MSE损失函数。当MSE损失降至一定水平后开始训练鉴别器,在此阶段需同时优化生成图像与真实图像之间的区分度。为了使生成图像与真实图像难以区分,在此阶段需同时优化生成图像与真实图像之间的区分度,并在具体的数据集测试中发现鉴Bridge的表现较为薄弱
在当前训练中存在鉴別器的损失不再下降的情况(参数配置包括学习率2e-3和人脸样本数500),下一轮训练中将学习率调整为0.5(该建议无效)

注意到discriminator代碼存在缺陷,在全連接阶段前少了一步reshape。
另外,在理论上说, 判别器的最后输出应该是一个评分, 即必须使用sigmoid激活函数, 但是参考代码中并未采取这一措施, 而是仅仅依靠全连接层的输出作为判别器的结果. 这一问题仍需进一步查阅相关资料.
在仔细查看代码之后发现,在最后阶段使用的全连接层的作用是将输入数据压缩为单一数值表示即numsout被设定为1. 如果最终输出的结果仅是一个数值的话,则可以省略sigmoid函数而直接得到(0,1)范围内的结果. ~ ~真正关键的原因在于 sigmoid函数在处理负值时会忽略其意义并容易导致梯度消失的问题. 因此,在最后一层处理中采用了批归一化技术,并结合带有 leaky 激活函数的设计策略进行处理.
为什么在全连接之前卷积之后需要对图像进行reshape呢?
由于卷积后的图像仍然是一个矩阵形式,在全连接层之前需要将输出转换为向量形式以便进行参数计算,在此情况下需要对输出进行一次 reshape 处理
鉴别器是如何判断图像是虚假的呢?
在生成对抗网络(GAN)中,在鉴别器的作用下会利用多层感知机进行判别任务。而在编码器-解码器架构中,在鉴别器的作用下会首先经过卷积操作降噪后输入至全连接层作为分类器,在此过程中... 疑问点在于为什么还不太明白原因...
目前实验进展:生成器运行正常且训练过程未发现问题;值得注意的是鉴别器存在结构缺陷。师兄在代码末尾添加了一个**tf.squeeze(x,-1)**功能用于处理最后一层全连接操作;我对这一功能尚不理解为何要在-1维上删除所有值为1的维度?
目前对鉴別器(未引入师兄的现成代码库,在鉴別器模块 preceding the fully connected layer中加入reshaped层以提升特征提取效果,在learning rate设置为1e-3的情况下, 训练数据集大小为500张)进行了优化后进行模型训练, 发现了以下几点变化
- 初期优化后的MSE损失表现形式未有显著稳定性(理论上不应发生变动)
 - 判别器将真实图片判定为真时的过程表明生成模型所造成的假正率极低接近于零同时其判别能力表现出与生成模型高度契合的现象
 - 通过最终效果对比分析可知判别器能够有效识别补全后的图像质量
 - 经过8000次迭代训练后发现判别器对生成图像的能力达到了完美的判别水平但整体上较之前有了显著提升
 



初次修复效果对比图(最开始以为成功了其实并没有Orz...):

100次

200次

2000次

3500

7000

8500
注:3000次之前是用MSE更新补全网络,3000-3500是单独训练鉴别器
3500-9000鉴别器和补全网络一起训练
下次实验更改的地方:将鉴别器中的全连接层归一化并激活看看会不会有效果。


这次实验相较于之前的实验尽管有所提升但效果仍显不足有人指出尽管图形显示看似有改善但整体效果仍不理想从图形显示的结果来看情况尚可然而这一现象表明尽管存在一定的优化空间但仍能勉强接受其效果换言之鉴别器并未参与该模型的学习过程
修正了錯誤后但鉴別器仍未發揮作用。按照理應的情况来说,鉴別器應與生成器互相挑戰,即優化G_loss_all時,D_loss應會伴随著改變。然而,在目前的研究中,對抗損失并未受到影響,這表明兩個模型並未產生成反效果。
随后对鉴別器的数据读取流程进行了优化(原先的做法是先提取 pictures后再进行裁剪以获得掩膜区域)。通过调用tf.image.crop_to_bounding_box函数来直接提取掩膜区域,并验证改进后生成器与鉴別器之间确实存在对抗现象。然而这种对抗现象可能源于两种原因:一是参数设置不当;二是鉴別器过于强大导致双方力量失衡。这种失衡现象尤其常见于初次接触GAN training时的经验不足所至。参考该文章中的优化方法后期待看到训练结果~!
原始GAN不稳定的原因: 判别器表现过于出色会导致生成器梯度下降速度减缓;在某些情况下,判别器的表现并不理想可能导致生成器更新不稳定。只有当判别器既没有过分优秀也没有明显不足时才能使整个模型稳定下来;然而很难准确把握最佳平衡点,在不同训练阶段可能需要不同的最佳平衡点。

超参数a=1,g_learn=d_learn=0.002:失败!之后还尝试了超参数a=0.05 g_learn=0.001,d_learn=0.0001失败!
正确的损失变化趋势:



准确的损失演变过程表明:我的鉴别器在初期阶段的表现迅速下降(即收敛速率异常迅速),这使得鉴别器与生成器对抗时的整体效果显著降低这一现象表明我的模型可能存在问题需要改进。

a=0.0004,d_learn=0.0001失败!

a=0.0004,d_learn=0.00001失败!

a=0.0004,d_learn=0.000001 失败!
但是学习率这么低其实没有毛用,鉴别器还是不行。
在向师兄请教的过程中意识到生成器长期存在质量问题,在此期间由于生成结果质量持续下降的原因在于鉴别器很快达到了平衡状态。通过将输入与输出进行单边对比分析后,在深入研究过程中发现了问题根源在于生成器所产出的图像质量。具体而言,在下图中左图中正常情况下的结果是在进行了1000次训练后呈现出来的高质量图像样本;然而在我的实验中却呈现出严重失真现象——例如图片中的眼睛和嘴巴都已经变得模糊不清甚至完全没有形状可寻的情况(如右图所示)。这表明模型在训练过程中始终未能有效改善图像质量。

正确的结果

我的结果

我的结果
之前imblication结果不好的根本原因是训练次数不够。由于训练次数太少导致的原因,在增加到足够数量后观察到图片在填充区域存在较大问题之外其他区域与目标图像基本一致。因此同样的网络为什么会表现出如此明显的填充效果差异呢?
相同网络下的输入图像在相同训练次数下其补全效果存在显著差异?经过优化的数据读取策略显著提升了补全效果。
采用队列方法对数据输入方式进行优化,并且其中生成器与鉴别器的卷积操作均采用了 slim 库提供的函数。经此改动后模型性能得到了明显提升。修复效果如下:

2000_22000_15000
至此我们对 slim 库中的卷积函数与 tensorflow 中的卷积函数之间的差异产生了极大的疑虑在数据处理机制上两者的区别主要体现在队列式数据读取方式相较于批量式读取队列方式的优势何在?
Slim库中的卷积函数与TensorFlow中的卷积函数有何区别?
下一步实验计划:
接下来将采用之前的模型来替代生成器和鉴别器的网络结构,并且仅调整数据输入的方式为队列形式观察训练效果如何?
接下来将采用之前的模型来替代生成器和鉴别器的网络结构,并且仅调整数据输入的方式为队列形式观察训练效果如何?
Result: 在更换之后的版本中进行测试的结果鉴別器不再起作用。这可能意味着之前的鉴別器存在缺陷。
接下来计划将现有的鉴别器替换为师兄开发的鉴別器网络,并以观察其效果为目标。若上一步无法达到预期效果,则考虑在师兄的鉴別器架构基础上进行优化。打算首先将所有现有的卷積層替換為tf.nn.conv2d()實現並觀察效應是否有改善
Result: 换成师兄的鉴别器后补全效果不错
计划对一批包含6000张图片的数据集进行训练;如果该次训练达到预期效果,则需进一步优化保存机制,并确保节点记录的完整性。与此同时,在小型训练集中验证模型性能,并对自身的鉴别器进行改进,以探究师兄所采用鉴别器的有效性。
Result: 很有可能是因为采用了6000张图片进行训练时未能正确配置参数而导致训练效果平平。第二个实验中将slim.conv2d()替换为tf.nn.conv2d()后并未观察到明显差异
准备把归一化函数也进行更换,观察结果。
Results: 观察到确实是归一化的功能不同导致了结果出现偏差,
正确的模型采用了tf\textbackslash{}contrib\textbackslash{}slim\textbackslash{}batch\_norm()这一封装实现,
而将其替换成其他形式的tensorflow官方封装则无法达到预期效果。
Why is this the case?
在tf.contrib.slim.batch_norm函数中,默认情况下scale和center参数被设置为False;然而,在slim(batch_norm)函数中,默认情况下这两个参数被设置为True。这种差异可能与某些因素有关,但具体原因还需进一步分析。
不过总之也算是找到根本原因了!基本大功告成!!撒花~~~~
实验中函数汇总:
tf.transpose(input, [dimension_1, dimenaion_2,..,dimension_n]):
该函数主要用于交换输入张量的不同维度,并且当输入张量为二维时,则相当于执行转置操作。dimension_n是一个整数,在特定情况下表示三维或更高维空间中的某一维具体位置;例如,在三维情况下,则通过索引0、1、2来表示各个维度的位置关系。该列表中的每一个数字对应相应的具体位置信息;特别地,在示例[2,1,0]的情况下,则会将输入张量的第三维与第一维进行交换以达到指定排列效果
tf.reshape(tensor,shape,name=None):
这个函数是对张量形状的设置为一个新的形状结构,在参数列表中如果出现-1,则表示该维度的大小将由系统自动计算确定;值得注意的是,在该列表中只能包含一个-1值(如果有多个-1出现,则说明存在多个可能解的情况)。
tensorflow.squeeze(input, squeeze_dims=None, name=None):
根据输入内容进行改写
输出内容仅包含改写后的文本
zeros(shape, dtype=float, order='C'):
生成一个指定形状和数据类型的全零填充数组;参数:shape为指定的形状;dtype为数据类型(可选参数,默认值为numpy.float64)。
tf.clip_by_value(A, min, max):
给定一个张量A,在其每个元素的值进行压缩处理。将所有小于min的元素设置为min,并将所有大于max的元素设置为max
实验中命令汇总:
nvidia-smi:用来查看当前显卡的使用情况
CUDA_VISIBLE_DEVICES=id python XXXX.py: 用id号显卡来运行代码
unzip;ls;cd;cd..;rm;rm-r...: 字面理解就好。
mv:移动文件
启动该进程于后台运行;通过 disown 命令参数 -h %1 ,确保该进程在执行时处于非 interactive 状态;前提是该进程已经在 bg %1 的情况下。
df -h:显示分区的内存占比
dh -sh:显示当前文件夹的大小
dh -sh *:显示当前文件夹下每个文件夹的大小
./pso > pso.file 2>&1 & :将程序放入后台执行,关闭终端也依旧执行的命令
ipconfig/all :查看整个电脑的详细的IP配置信息
