CNN理解与实现
CNN理解与实现
组成部分
- Convolution Layer
- Pool Layer:
- Max-pooling layer
- Average-pooling layer
- Full Connected(FC) Layer
需要的函数
注意
- 参数\(W\), \(b\)和数据\(X\)它们的维度是一样的, 这个非常重要, 在使用代码实现的时候不至于搞懵; 如果\(X\)是RGB图像, 它的维度假设为\((100, 24, 24, 3)\), 100表示样本数量, 24与24位图像的高与宽(我们人类习惯讲成宽与高, 但是在编程语言领域中, 如Matlab和Python中, 都是先锁定y轴, 再锁定x轴的, 同样迁移过来, 我们应该采用高与宽), 此时的W也是4个维度的, 加入为\((3, 3, 3, 8)\), 这里前两个3表示W(filter)的高与宽, 第三个3位通道数量, 要与X的通道数一致, 8表示W(filter)的个数, 可就是这次过滤器过滤之后得到的结果的通道数, 为b就简单了, 为\((1, 1, 1, 8)\), 每一个filter对应一个b
实现
zero_pad
: 使用0填充原始图像, 在水平方向与垂直方向添加paddingconv_single
: 使用一个过滤器\(W\)对原始图像中的slice进行卷积conv_forward
: 卷积的前向传播- 根据公式计算出过滤之后的维度: \(n_h=int({(n_{hprev}+2p-f)\over{s}}+1)\), \(n_w=int({(n_{wprev}+2p-f)\over{s}}+1)\), 其中\(n_{hprev}\)为上一层的高度, \(n_{wprev}\)为上一层的宽度, \(f\)为filter的维度, filter一般为\(f \times f\), \(p\)为padding, \(n_h\)表示通过过滤器之后得到的结果的高度, \(n_w\)表示通过过滤器之后得到的结果的宽度, \(s\)表示stride, 为步幅
- 调用
zero_pad
函数为\(A_{prev}\)添加padding, 使用\(int()\)是为了向下取整 - 由于卷积比较复杂, 这里不适用矩阵的计算, 而是采用多个嵌套的for循环
for i in range(m): # 获取到一个样本, 因为我们不打算使用矩阵化提高计算速率, 这样简单一点 a_prev_pad = A_prev_pad[i, :, :, :] for h in range(n_h): for w in range(n_w): for c in range(n_C): # 此公式将从原始的a_prev_pad单样本的图像中获取到一个Convolution Window vert_start = h * stride vert_end = vert_start + f horiz_start = w * stride horiz_end = horiz_start + f # 得到在原始图像中与filter同样大小窗口 a_slice_prev = a_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :] # 调用conv_single进行卷积计算 Z[i, h, w, c] = conv_single(a_slice_prev, W[:, :, :, c], b[:, :, :, c]) # 计算激活函数 A[i, h, w, c] = activate(Z[i, h, w, c])
pool_forward
: 池化层的前向传播- 根据公式计算出\(n_h\)与\(n_w\)
- 同样不使用矩阵计算, 使用多个for循环迭代, 这样简单
for i in range(m): a_prev = A_prev[i, :, :, :] for h in range(n_h): for w in range(n_w): for c in range(n_c): # 计算窗口坐标 vert_start = h * stride vert_end = vert_start + f horiz_start = w * stride horiz_end = horiz_start + f a_slice = a_prev[vert_start:vert_end, horiz_start:horiz_end, c] # 这里采用Max-pooling的计算方法 # 其中A为我们函数的返回矩阵 A[i, h, w, c] = np.max(a_slice)
compute_cost
: 计算损失函数\(J\)conv_backward
:- 计算dA, dW和db
for i in range(m): da_prev_pad = dA_prev_pad[i, :, :, :] a_prev_pad = A_prev_pad[i, :, :, :] for h in range(n_h): for w in range(n_w): for c in range(n_C): vert_start = h * stride vert_end = vert_start + f horiz_start = h * stride horiz_end = horiz_start + f a_slice = a_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :] da_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :] += dZ[i, h, w, c] * W[:, :, :, c] dW[:, :, :, c] += dZ[i, h, w, c] * a_slice db[:, :, :, c] += dZ[i, h, w, c] dA_prev[i, :, :, :] = da_prev_pad[pad:-pad, pad:-pad, :]
- 计算dA, dW和db
pool_backward
:- 与
conv_backward
类似
for i in range(m): a_prev = A_prev[i, :, :, :] for h in range(n_h): for w in range(n_w): for c in range(n_C): vert_start = h * stride vert_end = vert_start + f horiz_start = w * stride horiz_end = horiz_start + f a_slice = a_prev[vert_start:vert_end, horiz_start:horiz_end, c] mask = create_mask_from_window(a_slice) dA_prev[i, vert_start: vert_end, horiz_start: horiz_end, c] += np.multiply(mask, dA[i,h,w,c])
- 与
总结反向传播用到的公式
- 对于单个样本来说, 不使用矩阵表达式
- \(da += dw \times dz\)
- \(dw += dz \times a\)
- \(db += dz\)
使用的结构
- Conv->ReLU->Pool->Conv->ReLU->Pool->FC