解读Depth Map Prediction from a Single Image using a Multi-Scale Deep Network (5)
接着分析CNN的python代码,感谢博客点击打开链接
Activator类实现了激活函数,forward用来做前向计算,backward用来计算导数,
class ReluActivator(object): def forward(self, weighted_input): #return weighted_input return max(0, weighted_input) def backward(self, output): return 1 if output > 0 else 0
ConvLayer 类的forward函数实现了卷积层的前向计算,
def forward(self, input_array): ''' 计算卷积层的输出 输出结果保存在self.output_array ''' self.input_array = input_array self.padded_input_array = padding(input_array, self.zero_padding) for f in range(self.filter_number): filter = self.filters[f] conv(self.padded_input_array, filter.get_weights(), self.output_array[f], self.stride, filter.get_bias()) element_wise_op(self.output_array, self.activator.forward)
从这段代码中可以了解:
1,padding函数作用是zeros padding
2,conv 函数作用是卷积运算
3,element_wise_op函数作用对numpy数组进行操作,还不太明白意思
这些函数的源码含义不难理解,当然深究起来也很麻烦,暂且不深究
现在看一下卷积层反向传播算法的实现
反向传播的三个步骤:
1,将误差项传递到上一层
2,计算每个参数的梯度
3,更新参数
于是先看第一步,误差项的传递,分析ConvLayers类的bp_sensitivity_map函数的实现
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卷积,对sensitivitiy 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(map( lambda i: np.rot90(i, 2), filter.get_weights())) # 计算与一个filter对应的delta_array delta_array = self.create_delta_array() for d in range(delta_array.shape[0]): conv(padded_array[f], flipped_weights[d], delta_array[d], 1, 0) self.delta_array += delta_array # 将计算结果与激活函数的偏导数做element-wise乘法操作 derivative_array = np.array(self.input_array) element_wise_op(derivative_array, activator.backward) self.delta_array *= derivative_array
不敢说把上述的过程都能搞懂,但是知道大概:
1,处理卷积步长,把原始sensitivity map卷积步长为S的情况变为步长为1的情况
2,对sensitivity map进行zeros padding
3,计算f个filter的delta_array,把这f次的结果累加起来,得到self.delta_array
4,把self.delta_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()
这个过程分成几部分:
1,处理卷积步长,把原始sensitivity map卷积步长为S的情况变为步长为1的情况
2,计算每个权重的梯度
3,计算偏置项的梯度
最后进行第三步,梯度下降,
def update(self): ''' 按照梯度下降,更新权重 ''' for filter in self.filters: filter.update(self.learning_rate)
filter类的update函数之前分析过,就是,
def update(self, learning_rate): self.weights -= learning_rate * self.weights_grad self.bias -= learning_rate * self.bias_grad
至此,卷积层的前向计算和方向传播的算法到这里结束
简单看一下Max Pooling层的前向计算和反向传播,
class MaxPoolingLayer(object): def __init__(self, input_width, input_height, channel_number, filter_width, filter_height, stride): self.input_width = input_width self.input_height = input_height self.channel_number = channel_number self.filter_width = filter_width self.filter_height = filter_height self.stride = stride self.output_width = (input_width - filter_width) / self.stride + 1 self.output_height = (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]
至于Max Pooling层反向传播原理,可以看看这篇博客前面的叙述
对于全连接层的前向计算和反向传播,可以看普通BP算法的原理,这些在课堂上听老师讲过。
总之,卷积神经网络分为卷积层,池化层,全连接层,每一层都有各自的前向计算和反向传播的算法,
通过对算法理论和程序实现的了解,可以对CNN的训练有一定了解
也正如这篇博文所说,“对于卷积神经网络,现在有很多优秀的开源实现,因此我们并不需要真的自己去实现一个。贴出这些代码的目的是为了让我们更好的了解卷积神经网络的基本原理。”
最后,再一次感谢这篇博文点击打开链接