def main(): z = np.ones((5, 5)) k = np.ones((3, 3)) b = 3 # print(_single_channel_conv(z, k,padding=(1,1))) # print(_single_channel_conv(z, k, strides=(2, 2))) assert _single_channel_conv(z, k).shape == (3, 3) assert _single_channel_conv(z, k, padding=(1, 1)).shape == (5, 5) assert _single_channel_conv(z, k, strides=(2, 2)).shape == (2, 2) assert _single_channel_conv(z, k, strides=(2, 2), padding=(1, 1)).shape == (3, 3) assert _single_channel_conv(z, k, strides=(2, 2), padding=(1, 0)).shape == (3, 2) assert _single_channel_conv(z, k, strides=(2, 1), padding=(1, 1)).shape == (3, 5) dz = np.ones((1, 1, 3, 3)) assert _insert_zeros(dz, (1, 1)).shape == (1, 1, 3, 3) print(_insert_zeros(dz, (3, 2))) assert _insert_zeros(dz, (1, 2)).shape == (1, 1, 3, 5) assert _insert_zeros(dz, (2, 2)).shape == (1, 1, 5, 5) assert _insert_zeros(dz, (2, 4)).shape == (1, 1, 5, 9) z = np.ones((8, 16, 5, 5)) k = np.ones((16, 32, 3, 3)) b = np.ones((32)) assert conv_forward(z, k, b).shape == (8, 32, 3, 3) print(conv_forward(z, k, b)[0, 0]) print(np.argmax(np.array([[1, 2], [3, 4]])))
def test_conv_and_max_pooling(): # 测试卷积和最大池化 z = np.random.randn(3, 3, 28, 28).astype(np.float64) K = np.random.randn(3, 4, 3, 3).astype(np.float64) * 1e-3 b = np.zeros(4).astype(np.float64) next_z = conv_forward(z, K, b) y_pred = max_pooling_forward_bak(next_z, pooling=(2, 2)) y_true = np.ones_like(y_pred) from nn.losses import mean_squared_loss for i in range(10000): # 前向 next_z = conv_forward(z, K, b) y_pred = max_pooling_forward_bak(next_z, pooling=(2, 2)) # 反向 loss, dy = mean_squared_loss(y_pred, y_true) next_dz = max_pooling_backward_bak(dy, next_z, pooling=(2, 2)) dK, db, _ = conv_backward(next_dz, K, z) K -= 0.001 * dK b -= 0.001 * db if i % 10 == 0: print("i:{},loss:{},mindy:{},maxdy:{}".format( i, loss, np.mean(dy), np.max(dy))) if np.allclose(y_true, y_pred): print("yes") break
def conv_backward(next_dz, K, z, padding=(0, 0), strides=(1, 1)): """ 多通道卷积层的反向过程 :param next_dz: 卷积输出层的梯度,(N,D,H,W),H,W为卷积输出层的高度和宽度 :param K: 当前层卷积核,(C,D,k1,k2) :param z: 卷积层矩阵,形状(N,C,H,W),N为batch_size,C为通道数 :param padding: padding :param strides: 步长 :return: """ N, C, H, W = z.shape C, D, k1, k2 = K.shape # 卷积核梯度 # dK = np.zeros((C, D, k1, k2)) padding_next_dz = _insert_zeros(next_dz, strides) # 卷积核高度和宽度翻转180度 flip_K = np.flip(K, (2, 3)) # 交换C,D为D,C;D变为输入通道数了,C变为输出通道数了 swap_flip_K = np.swapaxes(flip_K, 0, 1) # 增加高度和宽度0填充 ppadding_next_dz = np.lib.pad(padding_next_dz, ((0, 0), (0, 0), (k1 - 1, k1 - 1), (k2 - 1, k2 - 1)), 'constant', constant_values=0) dz = conv_forward(ppadding_next_dz.astype(np.float64), swap_flip_K.astype(np.float64), np.zeros((C, ), dtype=np.float64)) # 求卷积和的梯度dK swap_z = np.swapaxes(z, 0, 1) # 变为(C,N,H,W)与 dK = conv_forward(swap_z.astype(np.float64), padding_next_dz.astype(np.float64), np.zeros((D, ), dtype=np.float64)) # 偏置的梯度 db = np.sum(np.sum(np.sum(next_dz, axis=-1), axis=-1), axis=0) # 在高度、宽度上相加;批量大小上相加 # 把padding减掉 dz = _remove_padding( dz, padding) # dz[:, :, padding[0]:-padding[0], padding[1]:-padding[1]] return dK / N, db / N, dz