两个图像处理问题的实例以及分析思路

这是老潘曾经做过的某个大学笔试题,代码尽可能干净简练并且能够快速解决问题,比较值得记录并且研究。尤其是第二题,在方法的设计上,对于结果的约束需要一些技巧才行,无脑用绝对误差很难行得通。

这些题目没有标准答案和做法,这里只是抛砖引玉,欢迎进一步交流优化改进。

下面是题目和解决思路以及配套完整代码(链接:https://github.com/divertingPan/utility_room/blob/master/task_2022.ipynb)。

第一题

题目:构建一个自编码器,至少包含4个编码层。编码一个至少有500副自然图像的灰度图片,分辨率为600x600

其实这一个做法和很久之前的一篇文章基本一样(复习Pytorch的使用方法,在套路的基础上灵活应变),只不过这里的图像编解码前后,分辨率都比较大(要600x600)。所以在模型的选择上可能要做一些巧妙的处理。我选择使用了Unet结构的模型,虽说这样能够解出题目,但转念一想,这样做的话,如果想要取出编码之后的隐向量,单使用这个编码去还原图像就不太可行。因为在解码器解码过程中需要依赖中间同级的编码层信息。当然也可以把这几个中间层互传的信息也看做隐向量去处理。

做这个用的数据集我拿的是DIV2K,但是这个数据量实际上不大,做做toy example可以,想实用的话还是搞点大的数据集吧。

第二题

题目:从CIFAR10中取出1000副图像,之后给定一个图像对x1x_1x2x_2,构建一个网络模型能够以他们的平均x1+x22\frac{x_1+x_2}{2}作为输入,输出两个原始的图像

最开始用的MAE当损失函数,但是这样是有一点违反直觉的,因为不一定要限制左边分叉只能输出和第一个图接近的结果。换句话说就是,x1x_1x2x_2输入网络之后,输出可以是(x1,x2)(x_1, x_2),也可以是(x2,x1)(x_2, x_1)。所以说MAE没法做到这种能够交换顺序的衡量。并且,真的试了一下实验结果也不好:

这里img_1和img_2是两个输入,img_merge是融合的图像,pred_1和pred_2是两个预测结果。可以看到,模型仿佛在摆烂,只要我仍然输出均值图像,那这个输出就离两个目标都不算太远。其实这样想一下也是有原因的:如果求平均图像xx的真实来源,可能是(x1,x2)(x_1, x_2)或者(x2,x1)(x_2, x_1),结果对于同样的xx,模型一会儿要预测成(x1,x2)(x_1, x_2)一会要(x2,x1)(x_2, x_1),这自然是两个相反的方向,模型摆烂的确是趋于中庸的自然结果。

后来仔细思考了一下,决定施加一些专门的约束。

首先,顺承上面模型摆烂时候发生的情况,一对预测结果的大致位置至少要和真实情况的大致位置相差不大。也就是说,两个输出结果的均值,应该和两个真实图像的均值尽量一样。

反应在图上就是让两个中心点尽量靠近,让pred预测的中心点尽量接近实际的真值xx。用式子表达的话,可以写成

MAE(x1+x22,pred1+pred22)\operatorname{MAE}\left(\frac{x_1+x_2}{2}, \frac{pred_1+pred_2}{2}\right)

就是两个输出的均值,和两个真实值的均值,看均值之间的距离。

第二,进一步,这个位置确定了,但是两个结果之间会有一定的距离,可以想作是,以上面说的那个均值点为中心,各自向两端的距离。这个距离,对于预测和真值也应该是一致的。

反应在图上就是让实线括号所指的距离,尽量接近虚线括号所指的距离。式子表达是

MAE(x1,x2)MAE(pred1,pred2)| \operatorname{MAE}\left(x_1, x_2\right)-\operatorname{MAE}\left(pred_1, pred_2\right) |

意思是,两个预测之间的距离,以及两个真实图之间的距离,看这两个距离之间差别多大,只关心绝对值不看正负号。

这时候很自然的会发现,两个样本点在空间上会有一个夹角。对于我们希望的情况应该是,这两个样本点连成的线应该平行。这样刚好对应了两个输出顺序可以不严格一致的情况。

反应在图上就是让两条线之间最小的夹角尽量小。这个可以用余弦相似度衡量,余弦相似度取值在[1,1][-1, 1]之间,两个向量同向平行是1,反向平行是-1,越垂直越趋向于0。从图上也可以看出来,具体的样本顺序是无所谓的,正好也符合前面所说过的。所以可以这样做损失函数

1cossim(x1x2,pred1pred2)1-\left|\operatorname{cossim}\left(x_1-x_2, pred_1-pred_2\right)\right|

两个样本做差是求出他们之间连线的方向向量。取了余弦相似度的绝对值就是只看平行度,不看方向。用1做减是为了反向他,相当于最小这个式子的时候就是最大化余弦相似度。

这样就可以写代码了,损失函数loss就写成下面这种

loss_func = nn.L1Loss()
parallelism = torch.mean(1 - torch.abs(F.cosine_similarity((img_1-img_2).view(-1, 3*32*32), (pred_1-pred_2).view(-1, 3*32*32), dim=1)))
loss = torch.abs(loss_func(img_1, img_2) - loss_func(pred_1, pred_2)) + loss_func(0.5 * (pred_1 + pred_2), 0.5 * (img_1 + img_2)) + parallelism

在同样的训练次数下,模型显然有了更好的表现: