赞
踩
学习文章:https://zybuluo.com/hanbingtao/note/485480
(在移动端可以看到图片,在pc上就不行,有大佬知道什么原因吗)
卷积的三种模式:full, same, valid:https://blog.csdn.net/leviopku/article/details/80327478
笔记,字丑勿怪
Python实现代码:
#!/usr/bin/env python # -*- coding: UTF-8 -*- import numpy as np class IdentityActivator(object): # forward方法实现了前向计算,而backward方法则是计算导数 def forward(self, weighted_input): return weighted_input def backward(self, output): return 1 # 获取卷积区域 def get_patch(input_array, i, j, filter_width, filter_height, stride): ''' 从输入数组中获取本次卷积的区域, 自动适配输入为2D和3D的情况 ''' start_i = i * stride start_j = j * stride if input_array.ndim == 2: return input_array[ start_i: start_i + filter_height, start_j: start_j + filter_width] elif input_array.ndim == 3: return input_array[:, start_i: start_i + filter_height, start_j: start_j + filter_width] # 获取一个2D区域的最大值所在的索引 def get_max_index(array): max_i = 0 max_j = 0 max_value = array[0, 0] for i in range(array.shape[0]): for j in range(array.shape[1]): if array[i, j] > max_value: max_value = array[i, j] max_i, max_j = i, j return max_i, max_j # 计算卷积 def conv(input_array, kernel_array, output_array, stride, bias): """ 计算卷积,自动适配输入为2D和3D的情况 """ # 输入矩阵维度 channel_number = input_array.ndim output_width = output_array.shape[1] output_height = output_array.shape[0] kernel_width = kernel_array.shape[-1] kernel_height = kernel_array.shape[-2] for i in range(output_height): for j in range(output_width): output_array[i][j] = \ (get_patch(input_array, i, j, kernel_width, kernel_height, stride) * kernel_array).sum() + bias # 为数组增加Zero padding def padding(input_array, zp): """ 为数组增加Zero padding,自动适配输入为2D和3D的情况 """ zp = int(zp) if zp == 0: return input_array else: if input_array.ndim == 3: input_width = input_array.shape[2] input_height = input_array.shape[1] input_depth = input_array.shape[0] padded_array = np.zeros(( int(input_depth), int(input_height + 2 * zp), int(input_width + 2 * zp))) padded_array[:, zp: zp + input_height, zp: zp + input_width] = input_array return padded_array elif input_array.ndim == 2: input_width = input_array.shape[1] input_height = input_array.shape[0] padded_array = np.zeros(( input_height + 2 * zp, input_width + 2 * zp)) padded_array[zp: zp + input_height, zp: zp + input_width] = input_array return padded_array # 对numpy数组进行element wise操作 def element_wise_op(array, op): for i in np.nditer(array, op_flags=['readwrite']): i[...] = op(i) class Filter(object): def __init__(self, width, height, depth): # 权重矩阵 self.weights = np.random.uniform(-1e-4, 1e-4, (depth, height, width)) # 偏置项 self.bias = 0 # 权重梯度矩阵 self.weights_grad = np.zeros(self.weights.shape) # 偏置项梯度 self.bias_grad = 0 def __repr__(self): return 'filter weights:\n%s\nbias:\n%s' % ( repr(self.weights), repr(self.bias)) def get_weights(self): return self.weights def get_bias(self): return self.bias def update(self, learning_rate): # 梯度下降 self.weights -= learning_rate * self.weights_grad self.bias -= learning_rate * self.bias_grad class ConvLayer(object): def __init__(self, input_width, input_height, channel_number, filter_width, filter_height, filter_number, zero_padding, stride, activator, learning_rate): self.input_width = int(input_width) self.input_height = int(input_height) self.channel_number = int(channel_number) self.filter_width = int(filter_width) self.filter_height = int(filter_height) self.filter_number = int(filter_number) self.zero_padding = int(zero_padding) self.stride = int(stride) self.output_width = int(ConvLayer.calculate_output_size( self.input_width, filter_width, zero_padding, stride)) self.output_height = int(ConvLayer.calculate_output_size( self.input_height, filter_height, zero_padding, stride)) self.output_array = np.zeros((self.filter_number, self.output_height, self.output_width)) self.filters = [] for i in range(filter_number): self.filters.append(Filter(filter_width, filter_height, self.channel_number)) self.activator = activator self.learning_rate = learning_rate def forward(self, input_array): """ 计算卷积层的输出 输出结果保存在self.output_array """ self.input_array = input_array # 对输入矩阵进行padding self.padded_input_array = padding(input_array, self.zero_padding) for f in range(self.filter_number): filter = self.filters[f] # net = sum(w * a) + wb conv(self.padded_input_array, filter.get_weights(), self.output_array[f], self.stride, filter.get_bias()) # print("__self.output_array:\n", self.output_array) # a = f(net) element_wise_op(self.output_array, self.activator.forward) # print("input_array:\n", input_array) # print("padded:\n", self.padded_input_array) # print("filters:\n", self.filters) # print("self.output_array:\n", self.output_array) def backward(self, input_array, sensitivity_array, activator): """ 计算传递给前一层的误差项,以及计算每个权重的梯度 前一层的误差项保存在self.delta_array 梯度保存在Filter对象的weights_grad """ self.forward(input_array) self.bp_sensitivity_map(sensitivity_array, activator) self.bp_gradient(sensitivity_array) def update(self): """ 按照梯度下降,更新权重 """ for filter in self.filters: filter.update(self.learning_rate) def bp_sensitivity_map(self, sensitivity_array, activator): """ 计算传递到上一层的sensitivity map sensitivity_array: 本层的sensitivity map activator: 上一层的激活函数 """ # 处理卷积步长,对原始sensitivity map进行扩展 expanded_array = self.expand_sensitivity_map(sensitivity_array) # full卷积,对sensitivity map进行zero padding # 虽然原始输入的zero padding单元也会获得残差 # 但这个残差不需要继续向上传递,因此就不计算了 expanded_width = expanded_array.shape[2] zp = (self.input_width + self.filter_width - 1 - expanded_width) / 2 padded_array = padding(expanded_array, zp) # 初始化delta_array,用于保存传递到上一层的 # sensitivity map self.delta_array = self.create_delta_array() # 对于具有多个filter的卷积层来说,最终传递到上一层的 # sensitivity map相当于所有的filter的 # sensitivity map之和 for f in range(self.filter_number): filter = self.filters[f] # 将filter权重翻转180度 flipped_weights = np.array(list(map(lambda i: np.rot90(i, 2), filter.get_weights()))) # print("filter:\n", filter.get_weights()) # 计算与一个filter对应的delta_array # delta(l-1) = delta(l) * filter(l) delta_array = self.create_delta_array() # print("padded_array:\n", padded_array) # print("flipped_weights:\n", flipped_weights) for d in range(delta_array.shape[0]): # print("flipped_weights:\n", flipped_weights[d]) conv(padded_array[f], flipped_weights[d], delta_array[d], 1, 0) self.delta_array += delta_array # 将计算结果与激活函数的偏导数做element-wise乘法操作 # delta(l-1)点乘f'(net(l-1)) derivative_array = np.array(self.input_array) element_wise_op(derivative_array, activator.backward) self.delta_array *= derivative_array def bp_gradient(self, sensitivity_array): # 处理卷积步长,对原始sensitivity map进行扩展 expanded_array = self.expand_sensitivity_map(sensitivity_array) for f in range(self.filter_number): # 计算每个权重的梯度 filter = self.filters[f] for d in range(filter.weights.shape[0]): conv(self.padded_input_array[d], expanded_array[f], filter.weights_grad[d], 1, 0) # 计算偏置项的梯度 filter.bias_grad = expanded_array[f].sum() # 将步长为S的sensitivity map『还原』为步长为1的sensitivity map def expand_sensitivity_map(self, sensitivity_array): depth = sensitivity_array.shape[0] # 确定扩展后sensitivity map的大小 # 计算stride为1时sensitivity map的大小 expanded_width = self.calculate_output_size(self.input_width, self.filter_width, self.zero_padding, 1) expanded_height = self.calculate_output_size(self.input_height, self.filter_height, self.zero_padding, 1) # 构建新的sensitivity_map expand_array = np.zeros((depth, expanded_height, expanded_width)) # 从原始sensitivity map拷贝误差值 for i in range(self.output_height): for j in range(self.output_width): i_pos = i * self.stride j_pos = j * self.stride expand_array[:, i_pos, j_pos] = sensitivity_array[:, i, j] return expand_array # 创建用来保存传递到上一层的sensitivity map的数组 def create_delta_array(self): return np.zeros((self.channel_number, self.input_height, self.input_width)) @staticmethod def calculate_output_size(input_size, filter_size, zero_padding, stride): # 确定卷积层输出的大小 # W2 = (W1 - F + 2 * P) / S + 1 return int((input_size - filter_size + 2 * zero_padding) / stride + 1) class MaxPoolingLayer(object): def __init__(self, input_width, input_height, channel_number, filter_width, filter_height, stride): self.input_width = int(input_width) self.input_height = int(input_height) self.channel_number = int(channel_number) self.filter_width = int(filter_width) self.filter_height = int(filter_height) self.stride = int(stride) self.output_width = int((input_width - filter_width) / self.stride + 1) self.output_height = int((input_height - filter_height) / self.stride + 1) self.output_array = np.zeros((self.channel_number, self.output_height, self.output_width)) def forward(self, input_array): for d in range(self.channel_number): for i in range(self.output_height): for j in range(self.output_width): self.output_array[d, i, j] = ( get_patch(input_array[d], i, j, self.filter_width, self.filter_height, self.stride).max()) def backward(self, input_array, sensitivity_array): self.delta_array = np.zeros(input_array.shape) for d in range(self.channel_number): for i in range(self.output_height): for j in range(self.output_width): patch_array = get_patch( input_array[d], i, j, self.filter_width, self.filter_height, self.stride) k, l = get_max_index(patch_array) self.delta_array[d, i * self.stride + k, j * self.stride + l] = \ sensitivity_array[d, i, j] # 卷积层的梯度检查 def init_test(): a = np.array( [[[0, 1, 1, 0, 2], [2, 2, 2, 2, 1], [1, 0, 0, 2, 0], [0, 1, 1, 0, 0], [1, 2, 0, 0, 2]], [[1, 0, 2, 2, 0], [0, 0, 0, 2, 0], [1, 2, 1, 2, 1], [1, 0, 0, 0, 0], [1, 2, 1, 1, 1]], [[2, 1, 2, 0, 0], [1, 0, 0, 1, 0], [0, 2, 1, 0, 1], [0, 1, 2, 2, 2], [2, 1, 0, 0, 1]]]) b = np.array( [[[0, 1, 1], [2, 2, 2], [1, 0, 0]], [[1, 0, 2], [0, 0, 0], [1, 2, 1]]]) cl = ConvLayer(5, 5, 3, 3, 3, 2, 1, 2, IdentityActivator(), 0.001) cl.filters[0].weights = np.array( [[[-1, 1, 0], [0, 1, 0], [0, 1, 1]], [[-1, -1, 0], [0, 0, 0], [0, -1, 0]], [[0, 0, -1], [0, 1, 0], [1, -1, -1]]], dtype=np.float64) cl.filters[0].bias = 1 cl.filters[1].weights = np.array( [[[1, 1, -1], [-1, -1, 1], [0, -1, 1]], [[0, 1, 0], [-1, 0, -1], [-1, 1, 0]], [[-1, 0, 0], [-1, 0, 1], [-1, 0, 0]]], dtype=np.float64) return a, b, cl def test(): a, b, cl = init_test() cl.forward(a) print(cl.output_array) def test_bp(): a, b, cl = init_test() cl.backward(a, b, IdentityActivator()) cl.update() print(cl.filters[0]) print(cl.filters[1]) def gradient_check(): ''' 梯度检查 ''' # 设计一个误差函数,取所有节点输出项之和 error_function = lambda o: o.sum() # 计算forward值 a, b, cl = init_test() cl.forward(a) # 求取sensitivity map sensitivity_array = np.ones(cl.output_array.shape, dtype=np.float64) # 计算梯度 cl.backward(a, sensitivity_array, IdentityActivator()) # 检查梯度 epsilon = 10e-4 for d in range(cl.filters[0].weights_grad.shape[0]): for i in range(cl.filters[0].weights_grad.shape[1]): for j in range(cl.filters[0].weights_grad.shape[2]): cl.filters[0].weights[d, i, j] += epsilon cl.forward(a) err1 = error_function(cl.output_array) cl.filters[0].weights[d, i, j] -= 2 * epsilon cl.forward(a) err2 = error_function(cl.output_array) expect_grad = (err1 - err2) / (2 * epsilon) cl.filters[0].weights[d, i, j] += epsilon print('weights(%d,%d,%d): expected - actural %f - %f' % ( d, i, j, expect_grad, cl.filters[0].weights_grad[d, i, j])) def init_pool_test(): a = np.array( [[[1, 1, 2, 4], [5, 6, 7, 8], [3, 2, 1, 0], [1, 2, 3, 4]], [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 0, 1], [3, 4, 5, 6]]], dtype=np.float64) b = np.array( [[[1, 2], [2, 4]], [[3, 5], [8, 2]]], dtype=np.float64) mpl = MaxPoolingLayer(4, 4, 2, 2, 2, 2) return a, b, mpl def test_pool(): a, b, mpl = init_pool_test() mpl.forward(a) print('input array:\n%s\noutput array:\n%s' % (a, mpl.output_array)) def test_pool_bp(): a, b, mpl = init_pool_test() mpl.backward(a, b) print('input array:\n%s\nsensitivity array:\n%s\ndelta array:\n%s' % ( a, b, mpl.delta_array)) if __name__ == '__main__': gradient_check() # test_pool() test_pool_bp()
输入输出结果:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。