基于《从零开始的深度学习》一书
我正在研究卷积神经网络(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_max 和x_max 是什么意思 -
我不确定要从img切片中取出什么内容
什么是y:y_max:stride, 和x:x_max:stride ? - 我不确定为什么有关过滤器大小的循环可以处理它
因此,首先,我尝试用自己的简单想法来实现它。
仿古实现
循环移动滤镜
→循环以复制滤镜中的每个像素
您应该能够按照x方向或Y方向...的顺序执行四重循环处理。
以下
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
从img复制到原始im2col
中的col
通过简化循环处理,它已成为一种实现... ...
有了这种感觉,我的理解平静了下来。