了解im2col函数


基于《从零开始的深度学习》一书
我正在研究卷积神经网络(CNN),但是很难理解中间出现的im2col函数,因此
我写了自己的咀嚼过程。

希望对大家有帮助。

关于im2col函数

为避免在CNN卷积操作中进行复杂的循环处理,
此函数将每个过滤器应用程序区域的数据转换为一行。

通过将此功能应用于输入数据和过滤器,
卷积运算可以通过矩阵的点运算一次执行。

原始实现

"从头开始?"中介绍的实现是这样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
    N, C, H, W = input_data.shape
    out_h = (H + 2*pad - filter_h)//stride + 1
    out_w = (W + 2*pad - filter_w)//stride + 1

    img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
    col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))

    for y in range(filter_h):
        y_max = y + stride*out_h
        for x in range(filter_w):
            x_max = x + stride*out_w
            col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]

    col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
    return col

通过阅读本书的说明,您可以了解您的目标,但是
我只是不明白我在此循环处理部分中正在做什么。

抓点

  • 我不确定y_maxx_max是什么意思

  • 我不确定要从img切片中取出什么内容
    什么是y:y_max:stride,x:x_max:stride
  • 我不确定为什么有关过滤器大小的循环可以处理它

因此,首先,我尝试用自己的简单想法来实现它。

仿古实现

循环移动滤镜
→循环以复制滤镜中的每个像素
您应该能够按照x方向或Y方向...的顺序执行四重循环处理。
以下im2col_slow是仅更改for循环部分的想法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def im2col_slow(input_data, filter_h, filter_w, stride=1, pad=0):
    N, C, H, W = input_data.shape
    out_h = (H + 2*pad - filter_h)//stride + 1
    out_w = (W + 2*pad - filter_w)//stride + 1

    img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
    col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))

    for move_y in range(out_h):
        for move_x in range(out_w):
            for y in range(filter_h):
                for x in range(filter_w):
                    col[:, :, y, x, move_y, move_x] = \
                        img[:, :, y + stride * move_y, x + stride * move_x]

    col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
    return col

尝试运行它。
(为便于查看,我们缩小到1个数据和1个通道)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
data = np.random.rand(1, 1, 7, 7) * 100 // 1

print('========== input ==========\n', data)
print('=====================')
filter_h = 3
filter_w = 3
stride = 2
pad = 0
col = im2col(data, filter_h=filter_h, filter_w=filter_w, stride=stride, pad=pad)
col2 = im2col_slow(data, filter_h=filter_h, filter_w=filter_w, stride=stride, pad=pad)
print('========== col ==========\n', col)
print('=====================')
print('========== col2 ==========\n', col2)
print('=====================')

获得了相似的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
========== input ==========
 [[[[30. 91. 11. 13. 52. 44. 98.]
   [99.  6. 35. 41. 97. 72. 79.]
   [ 5. 92. 15. 95. 72.  8. 10.]
   [68.  5. 86. 25. 69. 46. 70.]
   [95. 32. 98. 49. 51. 19. 46.]
   [32. 15. 39. 44. 76. 58. 49.]
   [43. 47. 95.  1.  1. 12. 21.]]]]
=====================
========== col ==========
 [[30. 91. 11. 99.  6. 35.  5. 92. 15.]
 [11. 13. 52. 35. 41. 97. 15. 95. 72.]
 [52. 44. 98. 97. 72. 79. 72.  8. 10.]
 [ 5. 92. 15. 68.  5. 86. 95. 32. 98.]
 [15. 95. 72. 86. 25. 69. 98. 49. 51.]
 [72.  8. 10. 69. 46. 70. 51. 19. 46.]
 [95. 32. 98. 32. 15. 39. 43. 47. 95.]
 [98. 49. 51. 39. 44. 76. 95.  1.  1.]
 [51. 19. 46. 76. 58. 49.  1. 12. 21.]]
=====================
========== col2 ==========
 [[30. 91. 11. 99.  6. 35.  5. 92. 15.]
 [11. 13. 52. 35. 41. 97. 15. 95. 72.]
 [52. 44. 98. 97. 72. 79. 72.  8. 10.]
 [ 5. 92. 15. 68.  5. 86. 95. 32. 98.]
 [15. 95. 72. 86. 25. 69. 98. 49. 51.]
 [72.  8. 10. 69. 46. 70. 51. 19. 46.]
 [95. 32. 98. 32. 15. 39. 43. 47. 95.]
 [98. 49. 51. 39. 44. 76. 95.  1.  1.]
 [51. 19. 46. 76. 58. 49.  1. 12. 21.]]
=====================

与原始

的比较

多亏了自己实现,所以最初的实现是
我注意到这样做的效率更高,因此不必像上面简单版本中那样对过滤器运动进行双重循环。
(通过以步幅宽度为单位进行切片,您可以移动滤镜并获得一次获取的数量?复制)

我觉得拍照片时看起来像这样。

在原始实施中从img复制到col

im2col_img_slow.gif

从img复制到原始im2col

中的col

im2col_img_org.gif

通过简化循环处理,它已成为一种实现... ...
有了这种感觉,我的理解平静了下来。