我的“深度学习论文实现”系列的前三个博客将涵盖2016年由Google Deepmind的Max Jaderberg, Karen Simonyan, Andrew Zisserman and KorayKavukcuoglu提出的空间变换网络概念。空间变换网络是一个可学习模型,旨在提升卷积神经网络在计算和参数方面的空间恒定性。 在第一部分中,我们将介绍两个非常重要的概念,在理解空间变化层次的内在工作机制上起决定性作用。我们将从检验一个基于仿射变换的图像变换技术的子集开始,然后深入到服从双线性插值的一般变换过程。 在第二部分中,我们将细细重温空间变化层次并总结这篇论文。在最后的第三部分中,我们将用scratch在TensorFlow上编程并将其应用到GTSRB数据集(德国交通标志识别标准)上。 完整代码参见我的Github Repository。 我在Github Repository(文章开始时提及了)中上传了2张猫的图片,请下载并存在桌面上叫 我还写了一个 配上函数,加载图片并将它们连接到单输入数组中。为了使代码尽可能通俗简洁,,我们要对两张图片进行操作。 import numpy as np from PIL import Image
# params DIMS = (400, 400) CAT1 = 'cat1.jpg' CAT2 = 'cat2.jpg'
# load both cat images img1 = load_img(CAT1, DIMS) img2 = load_img(CAT2, DIMS, view=True)
# concat into tensor of shape (2, 400, 400, 3) input_img = np.concatenate([img1, img2], axis=0)
# dimension sanity check print('Input Img Shape:{}'.format(input_img.shape)) 我们的批处理规模是2,这表示我们需要等量的变换矩阵M对应批次中的每张图。 请初始化两个恒等变换矩阵。如果正确运用了双线性取样器,那么输出图片应该与输入的近乎一致。 # grab shape num_batch, H, W, C = input_img.shape
# initialize M to identity transform M = np.array([[1., 0., 0.], [0., 1., 0.]])
# repeat num_batch times M = np.resize(M, (num_batch, 2, 3)) (再次声明:如果要包含位移,仿射变换的通用矩阵是2×3的) 现在我们需要写一个产生格点矩阵的函数并输出一个由格点矩阵产生取样网格和变换矩阵M。 我们要创造标准化格点矩阵,即x,y的值在-1到1之间,并且分别有width和height。事实上,对于图片,x代表图片的宽度(矩阵的列数),y代表高度(矩阵的行数)。 # create normalized 2D grid x = np.linspace(-1, 1, W) y = np.linspace(-1, 1, H) x_t, y_t = np.meshgrid(x, y) 然后,我们要增加维度来构建齐次坐标系。 # reshape to (xt, yt, 1) ones = np.ones(np.prod(x_t.shape)) sampling_grid = np.vstack([x_t.flatten(), y_t.flatten(),ones]) 尽管我们已经构建了一个网格,我们仍然需要 num_batch 网格。同上,下一步仍需重复数组num_batch 的次数。 # repeat grid num_batch times sampling_grid = np.resize(sampling_grid, (num_batch, 3, H*W)) 让我们继续图像变换的第2步。 # transform the sampling grid i.e. batch multiply batch_grids = np.matmul(M, sampling_grid) # batch grid has shape (num_batch, 2, H*W)
# reshape to (num_batch, height, width, 2) batch_grids = batch_grids.reshape(num_batch, 2, H, W) batch_grids = np.moveaxis(batch_grids, 1, -1) 最后,我们来写双线性取样器。通过取样网格中的 尝试分离x,y维度并重新调节它们,使之适应高度或宽度的间隔。 x_s = batch_grids[:, :, :, 0:1].squeeze() y_s = batch_grids[:, :, :, 1:2].squeeze()
# rescale x and y to [0, W/H] x = ((x_s + 1.) *W) * 0.5 y = ((y_s + 1.) *H) * 0.5 对任意坐标 (xi,yi)我们希望获得4个角坐标。 # grab 4 nearest corner points for each (x_i, y_i) x0 = np.floor(x).astype(np.int64) x1 = x0 + 1 y0 = np.floor(y).astype(np.int64) y1 = y0 + 1 (注意:我们只能用ceiling函数而不是增量1) 现在我们必须确定没有值超过了图像边界。假设x=399,则x0=399且x1=x0+1=400将导致一个numpy错误。因此我们用如下方法修剪角坐标。 # make sure it's inside img range [0, H] or [0, W] x0 = np.clip(x0, 0, W-1) x1 = np.clip(x1, 0, W-1) y0 = np.clip(y0, 0, H-1) y1 = np.clip(y1, 0, H-1) 我们用进阶的numpy索引来抓取每个角坐标的像素值,与(x0, y0), (x0, y1), (x1, y0) and (x_1, y_1)一致。 # look up pixel values at corner coords Ia = input_img[np.arange(num_batch)[:,None,None], y0, x0] Ib = input_img[np.arange(num_batch)[:,None,None], y1, x0] Ic = input_img[np.arange(num_batch)[:,None,None], y0, x1] Id = input_img[np.arange(num_batch)[:,None,None], y1, x1] 胜利在望!现在计算权重。 # calculate deltas wa = (x1-x) * (y1-y) wb = (x1-x) * (y-y0) wc = (x-x0) * (y1-y) wd = (x-x0) * (y-y0) 最后,用前面提到的公式加一加乘一乘。 # add dimension for addition wa = np.expand_dims(wa, axis=3) wb = np.expand_dims(wb, axis=3) wc = np.expand_dims(wc, axis=3) wd = np.expand_dims(wd, axis=3)
# compute output out = wa*Ia + wb*Ib + wc*Ic +wd*Id
结果 我们快速通关了全部代码,快用不同值的变换矩阵M来找点乐子吧。 要做的第一件事就是复制粘贴所有更加模块化的代码,试试所有的函数是不是正确工作。 恒等变换 在脚本最后加上这两行代码并执行。
plt.imshow(out[1]) plt.show()
位移 假设我们要只在x方向位移0.5,图片将向左移动,按如下方法调整代码。 M = np.array([[1., 0., 0.5], [0., 1., 0.]])
旋转 最后,如果我们想将图片旋转45度,因为cos(45)=sin(45)=2√2≈0.707,所以有: M = np.array([[0.707, -0.707, 0.], [0.707, 0.707, 0.]])
结论 在这篇博文里,我们学习了基本线性变换,如旋转、剪切和伸缩变换,并引申了包括位移在内的仿射变换。然后,我们见识了双线性插值在变换中的重要作用。最后,我们梳理了算法,通过scratch用Python编程并写了2种根据3个步骤将变换过程可视化的方法。 在下一篇博文中,我们将细细讲述空间变换网络层次并总结关于它的论文。 下周见!
参考文献 感谢 Eder Santana向我推荐了论文! · BilinearInterpolation Wikipedia
以上为译文 本文由北邮@爱可可-爱生活 老师推荐,阿里云云栖社区组织翻译。 文章原标题《Deep Learning Paper Implementations: Spatial Transformer Networks - Part I》,作者:Kevin Zakka,译者:NancyWang文章为简译,更为详细的内容,请查看原文 |
|