ConvNet 中的 col2im 实现
col2im implementation in ConvNet
我正在尝试仅使用 numpy 实现 CNN。
在进行反向传播时,我发现我必须使用 col2im 才能重塑 dx,所以我检查了实现来自 https://github.com/huyouare/CS231n/blob/master/assignment2/cs231n/im2col.py.
import numpy as np
def get_im2col_indices(x_shape, field_height, field_width, padding=1, stride=1):
# First figure out what the size of the output should be
N, C, H, W = x_shape
assert (H + 2 * padding - field_height) % stride == 0
assert (W + 2 * padding - field_height) % stride == 0
out_height = (H + 2 * padding - field_height) / stride + 1
out_width = (W + 2 * padding - field_width) / stride + 1
i0 = np.repeat(np.arange(field_height), field_width)
i0 = np.tile(i0, C)
i1 = stride * np.repeat(np.arange(out_height), out_width)
j0 = np.tile(np.arange(field_width), field_height * C)
j1 = stride * np.tile(np.arange(out_width), out_height)
i = i0.reshape(-1, 1) + i1.reshape(1, -1)
j = j0.reshape(-1, 1) + j1.reshape(1, -1)
k = np.repeat(np.arange(C), field_height * field_width).reshape(-1, 1)
return (k, i, j)
def im2col_indices(x, field_height, field_width, padding=1, stride=1):
""" An implementation of im2col based on some fancy indexing """
# Zero-pad the input
p = padding
x_padded = np.pad(x, ((0, 0), (0, 0), (p, p), (p, p)), mode='constant')
k, i, j = get_im2col_indices(x.shape, field_height, field_width, padding,
stride)
cols = x_padded[:, k, i, j]
C = x.shape[1]
cols = cols.transpose(1, 2, 0).reshape(field_height * field_width * C, -1)
return cols
def col2im_indices(cols, x_shape, field_height=3, field_width=3, padding=1,
stride=1):
""" An implementation of col2im based on fancy indexing and np.add.at """
N, C, H, W = x_shape
H_padded, W_padded = H + 2 * padding, W + 2 * padding
x_padded = np.zeros((N, C, H_padded, W_padded), dtype=cols.dtype)
k, i, j = get_im2col_indices(x_shape, field_height, field_width, padding,
stride)
cols_reshaped = cols.reshape(C * field_height * field_width, -1, N)
cols_reshaped = cols_reshaped.transpose(2, 0, 1)
np.add.at(x_padded, (slice(None), k, i, j), cols_reshaped)
if padding == 0:
return x_padded
return x_padded[:, :, padding:-padding, padding:-padding]
pass
当我将 X 放入 im2col_indices 并将输出放回 [ 时,我预计=35=] 将 return 与 X 相同,但事实并非如此。
我不明白 col2im 实际上做了什么。
如果我是对的,输出不是同一个 X,因为 X 的每个单元格都被转换为多个 col
,并且在 im2col_indices
期间被乘以。
假设你有一个像这样的简单图像X
1 2 3
4 5 6
7 8 9
然后用内核大小 3、步幅 1 和 same
填充对其进行转换,结果将是
0 0 0 0 1 2 0 4 5
0 0 0 1 2 3 4 5 6
0 0 0 2 3 0 5 6 0
0 1 2 0 4 5 0 7 8
1 2 3 4 5 6 7 8 9
2 3 0 5 6 0 8 9 0
0 4 5 0 7 8 0 0 0
4 5 6 7 8 9 0 0 0
5 6 0 8 9 0 0 0 0
* * * *
如您所见,第一个值为 1 的单元格出现在四个 col
中:0、1、3、4。
im2col_indices
先零初始化一张填充大小的图片,然后把每个col
加进去。关注第一个单元格,过程应该是这样的
1.zero初始化图片
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
2.add col 0
0 0 0 0 0 0 0 0 - - 0 0 0 0 0
0 0 0 0 0 0 1 2 - - 0 1 2 0 0
0 0 0 0 0 + 0 4 5 - - = 0 4 5 0 0
0 0 0 0 0 - - - - - 0 0 0 0 0
0 0 0 0 0 - - - - - 0 0 0 0 0
3.add col 1
0 0 0 0 0 - 0 0 0 - 0 0 0 0 0
0 1 2 0 0 - 1 2 3 - 0 2 4 3 0
0 4 5 0 0 + - 4 5 6 - = 0 8 10 6 0
0 0 0 0 0 - - - - - 0 0 0 0 0
0 0 0 0 0 - - - - - 0 0 0 0 0
4.add col 3
0 0 0 0 0 - - - - - 0 0 0 0 0
0 2 4 3 0 0 1 2 - - 0 3 6 3 0
0 8 10 6 0 + 0 4 5 - - = 0 12 15 6 0
0 0 0 0 0 0 7 8 - - 0 7 8 0 0
0 0 0 0 0 - - - - - 0 0 0 0 0
5.add col 4
0 0 0 0 0 - - - - - 0 0 0 0 0
0 3 6 3 0 - 1 2 3 - 0 4 8 6 0
0 12 15 6 0 + - 4 5 6 - = 0 16 20 12 0
0 7 8 0 0 - 7 8 9 - 0 14 16 9 0
0 0 0 0 0 - - - - - 0 0 0 0 0
转换回来时,第一个单元格乘以 4。对于这个简单的图像,col2im_indices(im2col_indices(X))
应该给你
4 12 12
24 45 36
28 48 36
对比原图,四个角单元格1 3 7 9
乘以4,四个边缘单元格2 4 6 8
乘以6,中心单元格5
乘以9.
对于大图,大部分单元格都会乘以9,我想这大概意味着你的学习率实际上比你想象的要大9倍。
回复这个 2 年前的帖子,可能对以后的人有所帮助。
这是我的理解。在 CNN 反向传播上下文中,col2im 矩阵是滤波器和反向传播误差 (dout) 的乘积。必须注意的是,矩阵已经是两个矩阵的乘积,这与前向传递中的 im2col 用例不同,我们刚刚将输入拉伸到 im2col 矩阵中,准备乘法(卷积)。由于 im2col 和 col2im 之间的这种差异,在 col2im 中,我们需要将反向传播误差添加到所有有贡献的输入索引中。
让我们考虑一个 1x5x5 输入、单个 1x3x3 过滤器、0 填充、步幅 1 的示例。输入的索引如下所示:
[0,0] [0,1] [0,2] [0,3] [0,4]
[1,0] [1,1] [1,2] [1,3] [1,4]
[2,0] [2,1] [2,2] [2,3] [2,4]
[3,0] [3,1] [3,2] [3,3] [3,4]
[4,0] [4,1] [4,2] [4,3] [4,4]
为前向传播计算的结果 9x9 im2col 索引
矩阵乘法看起来像:
im2col 指数
<----------------------- 9 ----------------------------->
[ 0] [0,0] [0,1] [0,2] [1,0] [1,1] [1,2] [2,0] [2,1] [2,2]
[ 1] [0,1] [0,2] [0,3] [1,1] [1,2] [1,3] [2,1] [2,2] [2,3]
[ 2] [0,2] [0,3] [0,4] [1,2] [1,3] [1,4] [2,2] [2,3] [2,4]
[ 3] [1,0] [1,1] [1,2] [2,0] [2,1] [2,2] [3,0] [3,1] [3,2]
[ 4] [1,1] [1,2] [1,3] [2,1] [2,2] [2,3] [3,1] [3,2] [3,3]
[ 5] [1,2] [1,3] [1,4] [2,2] [2,3] [2,4] [3,2] [3,3] [3,4]
[ 6] [2,0] [2,1] [2,2] [3,0] [3,1] [3,2] [4,0] [4,1] [4,2]
[ 7] [2,1] [2,2] [2,3] [3,1] [3,2] [3,3] [4,1] [4,2] [4,3]
[ 8] [2,2] [2,3] [2,4] [3,2] [3,3] [3,4] [4,2] [4,3] [4,4]
在反向传递中,当我们通过将反向传播的误差 dout 和过滤器相乘生成 col2im 矩阵时,如上所示的结果索引已经是乘法的结果。当我们将其转换回输入错误时,我们需要在输入错误数组的给定位置添加相应的索引。
例如:
input_error[0,0] = im2col_error[0,0]
input_error[0,1] = im2col_error[0,1] + im2col_error[1,0]
input_error[0,2] = im2col_error[0,2] + im2col_error[1,1] + im2col_error[2,0]
....
....
从上面的指数矩阵可以看出这一点。
我正在尝试仅使用 numpy 实现 CNN。
在进行反向传播时,我发现我必须使用 col2im 才能重塑 dx,所以我检查了实现来自 https://github.com/huyouare/CS231n/blob/master/assignment2/cs231n/im2col.py.
import numpy as np
def get_im2col_indices(x_shape, field_height, field_width, padding=1, stride=1):
# First figure out what the size of the output should be
N, C, H, W = x_shape
assert (H + 2 * padding - field_height) % stride == 0
assert (W + 2 * padding - field_height) % stride == 0
out_height = (H + 2 * padding - field_height) / stride + 1
out_width = (W + 2 * padding - field_width) / stride + 1
i0 = np.repeat(np.arange(field_height), field_width)
i0 = np.tile(i0, C)
i1 = stride * np.repeat(np.arange(out_height), out_width)
j0 = np.tile(np.arange(field_width), field_height * C)
j1 = stride * np.tile(np.arange(out_width), out_height)
i = i0.reshape(-1, 1) + i1.reshape(1, -1)
j = j0.reshape(-1, 1) + j1.reshape(1, -1)
k = np.repeat(np.arange(C), field_height * field_width).reshape(-1, 1)
return (k, i, j)
def im2col_indices(x, field_height, field_width, padding=1, stride=1):
""" An implementation of im2col based on some fancy indexing """
# Zero-pad the input
p = padding
x_padded = np.pad(x, ((0, 0), (0, 0), (p, p), (p, p)), mode='constant')
k, i, j = get_im2col_indices(x.shape, field_height, field_width, padding,
stride)
cols = x_padded[:, k, i, j]
C = x.shape[1]
cols = cols.transpose(1, 2, 0).reshape(field_height * field_width * C, -1)
return cols
def col2im_indices(cols, x_shape, field_height=3, field_width=3, padding=1,
stride=1):
""" An implementation of col2im based on fancy indexing and np.add.at """
N, C, H, W = x_shape
H_padded, W_padded = H + 2 * padding, W + 2 * padding
x_padded = np.zeros((N, C, H_padded, W_padded), dtype=cols.dtype)
k, i, j = get_im2col_indices(x_shape, field_height, field_width, padding,
stride)
cols_reshaped = cols.reshape(C * field_height * field_width, -1, N)
cols_reshaped = cols_reshaped.transpose(2, 0, 1)
np.add.at(x_padded, (slice(None), k, i, j), cols_reshaped)
if padding == 0:
return x_padded
return x_padded[:, :, padding:-padding, padding:-padding]
pass
当我将 X 放入 im2col_indices 并将输出放回 [ 时,我预计=35=] 将 return 与 X 相同,但事实并非如此。
我不明白 col2im 实际上做了什么。
如果我是对的,输出不是同一个 X,因为 X 的每个单元格都被转换为多个 col
,并且在 im2col_indices
期间被乘以。
假设你有一个像这样的简单图像X
1 2 3
4 5 6
7 8 9
然后用内核大小 3、步幅 1 和 same
填充对其进行转换,结果将是
0 0 0 0 1 2 0 4 5
0 0 0 1 2 3 4 5 6
0 0 0 2 3 0 5 6 0
0 1 2 0 4 5 0 7 8
1 2 3 4 5 6 7 8 9
2 3 0 5 6 0 8 9 0
0 4 5 0 7 8 0 0 0
4 5 6 7 8 9 0 0 0
5 6 0 8 9 0 0 0 0
* * * *
如您所见,第一个值为 1 的单元格出现在四个 col
中:0、1、3、4。
im2col_indices
先零初始化一张填充大小的图片,然后把每个col
加进去。关注第一个单元格,过程应该是这样的
1.zero初始化图片
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
2.add col 0
0 0 0 0 0 0 0 0 - - 0 0 0 0 0
0 0 0 0 0 0 1 2 - - 0 1 2 0 0
0 0 0 0 0 + 0 4 5 - - = 0 4 5 0 0
0 0 0 0 0 - - - - - 0 0 0 0 0
0 0 0 0 0 - - - - - 0 0 0 0 0
3.add col 1
0 0 0 0 0 - 0 0 0 - 0 0 0 0 0
0 1 2 0 0 - 1 2 3 - 0 2 4 3 0
0 4 5 0 0 + - 4 5 6 - = 0 8 10 6 0
0 0 0 0 0 - - - - - 0 0 0 0 0
0 0 0 0 0 - - - - - 0 0 0 0 0
4.add col 3
0 0 0 0 0 - - - - - 0 0 0 0 0
0 2 4 3 0 0 1 2 - - 0 3 6 3 0
0 8 10 6 0 + 0 4 5 - - = 0 12 15 6 0
0 0 0 0 0 0 7 8 - - 0 7 8 0 0
0 0 0 0 0 - - - - - 0 0 0 0 0
5.add col 4
0 0 0 0 0 - - - - - 0 0 0 0 0
0 3 6 3 0 - 1 2 3 - 0 4 8 6 0
0 12 15 6 0 + - 4 5 6 - = 0 16 20 12 0
0 7 8 0 0 - 7 8 9 - 0 14 16 9 0
0 0 0 0 0 - - - - - 0 0 0 0 0
转换回来时,第一个单元格乘以 4。对于这个简单的图像,col2im_indices(im2col_indices(X))
应该给你
4 12 12
24 45 36
28 48 36
对比原图,四个角单元格1 3 7 9
乘以4,四个边缘单元格2 4 6 8
乘以6,中心单元格5
乘以9.
对于大图,大部分单元格都会乘以9,我想这大概意味着你的学习率实际上比你想象的要大9倍。
回复这个 2 年前的帖子,可能对以后的人有所帮助。
这是我的理解。在 CNN 反向传播上下文中,col2im 矩阵是滤波器和反向传播误差 (dout) 的乘积。必须注意的是,矩阵已经是两个矩阵的乘积,这与前向传递中的 im2col 用例不同,我们刚刚将输入拉伸到 im2col 矩阵中,准备乘法(卷积)。由于 im2col 和 col2im 之间的这种差异,在 col2im 中,我们需要将反向传播误差添加到所有有贡献的输入索引中。
让我们考虑一个 1x5x5 输入、单个 1x3x3 过滤器、0 填充、步幅 1 的示例。输入的索引如下所示:
[0,0] [0,1] [0,2] [0,3] [0,4]
[1,0] [1,1] [1,2] [1,3] [1,4]
[2,0] [2,1] [2,2] [2,3] [2,4]
[3,0] [3,1] [3,2] [3,3] [3,4]
[4,0] [4,1] [4,2] [4,3] [4,4]
为前向传播计算的结果 9x9 im2col 索引 矩阵乘法看起来像:
im2col 指数
<----------------------- 9 ----------------------------->
[ 0] [0,0] [0,1] [0,2] [1,0] [1,1] [1,2] [2,0] [2,1] [2,2]
[ 1] [0,1] [0,2] [0,3] [1,1] [1,2] [1,3] [2,1] [2,2] [2,3]
[ 2] [0,2] [0,3] [0,4] [1,2] [1,3] [1,4] [2,2] [2,3] [2,4]
[ 3] [1,0] [1,1] [1,2] [2,0] [2,1] [2,2] [3,0] [3,1] [3,2]
[ 4] [1,1] [1,2] [1,3] [2,1] [2,2] [2,3] [3,1] [3,2] [3,3]
[ 5] [1,2] [1,3] [1,4] [2,2] [2,3] [2,4] [3,2] [3,3] [3,4]
[ 6] [2,0] [2,1] [2,2] [3,0] [3,1] [3,2] [4,0] [4,1] [4,2]
[ 7] [2,1] [2,2] [2,3] [3,1] [3,2] [3,3] [4,1] [4,2] [4,3]
[ 8] [2,2] [2,3] [2,4] [3,2] [3,3] [3,4] [4,2] [4,3] [4,4]
在反向传递中,当我们通过将反向传播的误差 dout 和过滤器相乘生成 col2im 矩阵时,如上所示的结果索引已经是乘法的结果。当我们将其转换回输入错误时,我们需要在输入错误数组的给定位置添加相应的索引。
例如:
input_error[0,0] = im2col_error[0,0]
input_error[0,1] = im2col_error[0,1] + im2col_error[1,0]
input_error[0,2] = im2col_error[0,2] + im2col_error[1,1] + im2col_error[2,0]
....
....
从上面的指数矩阵可以看出这一点。