class ConvLayer(CLayer): # define the number of input and output channel, also the filter size def __init__(self, input_shape, # (InputChannelCount, H, W) kernal_shape, # (OutputChannelCount, FH, FW) conv_param, # (stride, padding) hp): self.InC = input_shape[0] # input channel count self.InH = input_shape[1] # input image height self.InW = input_shape[2] # input image width self.OutC = kernal_shape[0] # output channel count self.FH = kernal_shape[1] # kernal/filter height self.FW = kernal_shape[2] # kernal/filter width self.stride = conv_param[0] self.padding = conv_param[1] self.hp = hp def initialize(self, folder, name, create_new=False): self.WB = ConvWeightsBias( self.OutC, self.InC, self.FH, self.FW, self.hp.init_method, self.hp.optimizer_name, self.hp.eta) self.WB.Initialize(folder, name, create_new) (self.OutH, self.OutW) = calculate_output_size( self.InH, self.InW, self.FH, self.FW, self.padding, self.stride) self.output_shape = (self.OutC, self.OutH, self.OutH) def set_filter(self, w, b): if w is not None: self.WB.W = w if b is not None: self.WB.B = b def forward(self, x, train=True): return self.forward_img2col(x, train) def backward(self, delta_in, layer_idx): delta_out, dw, db = self.backward_col2img(delta_in, layer_idx) return delta_out def forward_img2col(self, x, train=True): self.x = x self.batch_size = self.x.shape[0] assert(self.x.shape == (self.batch_size, self.InC, self.InH, self.InW)) self.col_x = img2col(x, self.FH, self.FW, self.stride, self.padding) self.col_w = self.WB.W.reshape(self.OutC, -1).T self.col_b = self.WB.B.reshape(-1, self.OutC) out1 = np.dot(self.col_x, self.col_w) + self.col_b out2 = out1.reshape(self.batch_size, self.OutH, self.OutW, -1) self.z = np.transpose(out2, axes=(0, 3, 1, 2)) return self.z def backward_col2img(self, delta_in, layer_idx): col_delta_in = np.transpose(delta_in, axes=(0,2,3,1)).reshape(-1, self.OutC) self.WB.dB = np.sum(col_delta_in, axis=0, keepdims=True).T / self.batch_size col_dW = np.dot(self.col_x.T, col_delta_in) / self.batch_size self.WB.dW = np.transpose(col_dW, axes=(1, 0)).reshape(self.OutC, self.InC, self.FH, self.FW) col_delta_out = np.dot(col_delta_in, self.col_w.T) delta_out = col2img(col_delta_out, self.x.shape, self.FH, self.FW, self.stride, self.padding, self.OutH, self.OutW) return delta_out, self.WB.dW, self.WB.dB def forward_numba(self, x, train=True): assert(x.ndim == 4) self.x = x assert(self.x.shape[1] == self.InC) assert(self.x.shape[2] == self.InH) assert(self.x.shape[3] == self.InW) self.batch_size = self.x.shape[0] if self.padding > 0: self.padded = np.pad(self.x, ((0,0), (0,0), (self.padding,self.padding), (self.padding,self.padding)), 'constant') #self.padded = np.pad(self.x, mode="constant", constant_value=0, pad_width=(0,0,0,0,self.padding,self.padding,self.padding,self.padding)) else: self.padded = self.x #end if self.z = jit_conv_4d(self.padded, self.WB.W, self.WB.B, self.OutH, self.OutW, self.stride) return self.z def backward_numba(self, delta_in, flag): assert(delta_in.ndim == 4) assert(delta_in.shape == self.z.shape) # 如果正向计算中的stride不是1,转换成是1的等价误差数组 dz_stride_1 = expand_delta_map(delta_in, self.batch_size, self.OutC, self.InH, self.InW, self.OutH, self.OutW, self.FH, self.FW, self.padding, self.stride) # 计算本层的权重矩阵的梯度 self._calculate_weightsbias_grad(dz_stride_1) # 求本层的输出误差矩阵时,应该用本层的输入误差矩阵互相关计算本层的卷积核的旋转 # 由于输出误差矩阵的尺寸必须与本层的输入数据的尺寸一致,所以必须根据卷积核的尺寸,调整本层的输入误差矩阵的尺寸 (pad_h, pad_w) = calculate_padding_size( dz_stride_1.shape[2], dz_stride_1.shape[3], self.FH, self.FW, self.InH, self.InW) dz_padded = np.pad(dz_stride_1, ((0,0),(0,0),(pad_h, pad_h),(pad_w, pad_w)), 'constant') # 计算本层输出到下一层的误差矩阵 delta_out = self._calculate_delta_out(dz_padded, flag) #return delta_out return delta_out, self.WB.dW, self.WB.dB # 用输入数据乘以回传入的误差矩阵,得到卷积核的梯度矩阵 def _calculate_weightsbias_grad(self, dz): self.WB.ClearGrads() # 先把输入矩阵扩大,周边加0 (pad_h, pad_w) = calculate_padding_size( self.InH, self.InW, dz.shape[2], dz.shape[3], self.FH, self.FW, 1) input_padded = np.pad(self.x, ((0,0),(0,0),(pad_h, pad_h),(pad_w,pad_w)), 'constant') # 输入矩阵与误差矩阵卷积得到权重梯度矩阵 (self.WB.dW, self.WB.dB) = calcalate_weights_grad( input_padded, dz, self.batch_size, self.OutC, self.InC, self.FH, self.FW, self.WB.dW, self.WB.dB) self.WB.MeanGrads(self.batch_size) # 用输入误差矩阵乘以(旋转180度后的)卷积核 def _calculate_delta_out(self, dz, layer_idx): if layer_idx == 0: return None # 旋转卷积核180度 rot_weights = self.WB.Rotate180() # 定义输出矩阵形状 delta_out = np.zeros(self.x.shape).astype(np.float32) # 输入梯度矩阵卷积旋转后的卷积核,得到输出梯度矩阵 delta_out = calculate_delta_out(dz, rot_weights, self.batch_size, self.InC, self.OutC, self.InH, self.InW, delta_out) return delta_out def pre_update(self): pass def update(self): self.WB.Update() def save_parameters(self): self.WB.SaveResultValue() def load_parameters(self): self.WB.LoadResultValue()
class ConvLayer(CLayer): # define the number of input and output channel, also the filter size def __init__( self, input_shape, # (InputChannelCount, H, W) kernal_shape, # (OutputChannelCount, FH, FW) conv_param, # (stride, padding) activator, param): self.num_input_channel = input_shape[0] self.input_height = input_shape[1] self.input_width = input_shape[2] self.num_output_channel = kernal_shape[0] self.filter_height = kernal_shape[1] self.filter_width = kernal_shape[2] self.stride = conv_param[0] self.padding = conv_param[1] self.activator = activator self.WeightsBias = ConvWeightsBias(self.num_output_channel, self.num_input_channel, self.filter_height, self.filter_width, param.init_method, param.optimizer_name, param.eta) (self.output_height, self.output_width) = calculate_output_size( self.input_height, self.input_width, self.filter_height, self.filter_width, self.padding, self.stride) self.output_shape = (self.num_output_channel, self.output_height, self.output_height) """ 输入数据 N:样本图片数量(比如一次计算10张图片) C:图片通道数量(比如红绿蓝三通道) H:图片高度(比如224) W:图片宽度(比如224) 思维卷积操作 """ def forward(self, x): assert (x.ndim == 4) self.x = x assert (self.x.shape[1] == self.num_input_channel) assert (self.x.shape[2] == self.input_height) assert (self.x.shape[3] == self.input_width) self.batch_size = self.x.shape[0] if self.padding > 0: self.padded = np.pad(self.x, ((0, 0), (0, 0), (self.padding, self.padding), (self.padding, self.padding)), 'constant') else: self.padded = self.x #end if self.z = jit_conv_4d(self.padded, self.WeightsBias.W, self.WeightsBias.B, self.output_height, self.output_width, self.stride) self.a = self.activator.forward(self.z) return self.a def forward_fast(self, x): FN, C, FH, FW = self.WeightsBias.W.shape N, C, H, W = x.shape out_h = 1 + int((H + 2 * self.padding - FH) / self.stride) out_w = 1 + int((W + 2 * self.padding - FW) / self.stride) col_x = im2col(x, FH, FW, self.stride, self.padding) col_W = self.WeightsBias.W.reshape(FN, -1).T out = np.dot(col_x, col_W) + self.WeightsBias.B.reshape(-1, FN) self.z = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2) self.x = x self.col_x = col_x self.col_W = col_W self.a = self.activator.forward(self.z) return self.z, self.a # 把激活函数算做是当前层,上一层的误差传入后,先经过激活函数的导数,而得到本层的针对z值的误差 def backward(self, delta_in, flag): assert (delta_in.ndim == 4) assert (delta_in.shape == self.a.shape) # 计算激活函数的导数 dz, _ = self.activator.backward(self.z, self.a, delta_in) # 转换误差矩阵尺寸 dz_stride_1 = expand_delta_map(dz, self.batch_size, self.num_output_channel, self.input_height, self.input_width, self.output_height, self.output_width, self.filter_height, self.filter_width, self.padding, self.stride) # 求本层的输出误差矩阵时,应该用本层的输入误差矩阵互相关计算本层的卷积核的旋转 # 由于输出误差矩阵的尺寸必须与本层的输入数据的尺寸一致,所以必须根据卷积核的尺寸,调整本层的输入误差矩阵的尺寸 (pad_h, pad_w) = calculate_padding_size(dz_stride_1.shape[2], dz_stride_1.shape[3], self.filter_height, self.filter_width, self.input_height, self.input_width) dz_padded = np.pad(dz_stride_1, ((0, 0), (0, 0), (pad_h, pad_h), (pad_w, pad_w)), 'constant') # 计算本层的权重矩阵的梯度 self._calculate_weightsbias_grad(dz_stride_1) # 计算本层输出到下一层的误差矩阵 delta_out = self._calculate_delta_out(dz_padded, flag) return delta_out # 用输入数据乘以回传入的误差矩阵,得到卷积核的梯度矩阵 def _calculate_weightsbias_grad(self, dz): self.WeightsBias.ClearGrads() (pad_h, pad_w) = calculate_padding_size(self.input_height, self.input_width, dz.shape[2], dz.shape[3], self.filter_height, self.filter_width, 1) input_padded = np.pad(self.x, ((0, 0), (0, 0), (pad_h, pad_h), (pad_w, pad_w)), 'constant') for bs in range(self.batch_size): for oc in range(self.num_output_channel): # == kernal count for ic in range(self.num_input_channel): # == filter count w_grad = np.zeros((self.filter_height, self.filter_width)) conv2d(input_padded[bs, ic], dz[bs, oc], 0, w_grad) self.WeightsBias.W_grad[oc, ic] += w_grad #end ic self.WeightsBias.B_grad[oc] += dz[bs, oc].sum() #end oc #end bs self.WeightsBias.MeanGrads(self.batch_size) # 用输入误差矩阵乘以(旋转180度后的)卷积核 def _calculate_delta_out(self, dz, flag): delta_out = np.zeros(self.x.shape) if flag != LayerIndexFlags.FirstLayer: rot_weights = self.WeightsBias.Rotate180() for bs in range(batch_size): for oc in range(self.num_output_channel): # == kernal count delta_per_input = np.zeros( (self.input_height, self.input_width)) for ic in range(self.num_input_channel): # == filter count conv2d(dz[bs, oc], rot_weights[oc, ic], 0, delta_per_input) delta_out[bs, ic] += delta_per_input #END IC #end oc #end bs # end if return delta_out def pre_update(self): self.weights.pre_Update() def update(self): self.WeightsBias.Update() def save_parameters(self, name): self.WeightsBias.Save(name) def load_parameters(self, name): self.WeightsBias.Load(name)
class ConvLayer(CLayer): # define the number of input and output channel, also the filter size def __init__(self, input_shape, # (InputChannelCount, H, W) kernal_shape, # (OutputChannelCount, FH, FW) conv_param, # (stride, padding) hp): self.num_input_channel = input_shape[0] self.input_height = input_shape[1] self.input_width = input_shape[2] self.num_output_channel = kernal_shape[0] self.filter_height = kernal_shape[1] self.filter_width = kernal_shape[2] self.stride = conv_param[0] self.padding = conv_param[1] self.hp = hp def initialize(self, folder, name, create_new=False): self.WB = ConvWeightsBias( self.num_output_channel, self.num_input_channel, self.filter_height, self.filter_width, self.hp.init_method, self.hp.optimizer_name, self.hp.eta) self.WB.Initialize(folder, name, create_new) (self.output_height, self.output_width) = ConvLayer.calculate_output_size( self.input_height, self.input_width, self.filter_height, self.filter_width, self.padding, self.stride) self.output_shape = (self.num_output_channel, self.output_height, self.output_height) def forward(self, x, train=True): return self.forward_img2col(x, train) def backward(self, delta_in, layer_idx): delta_out, dw, db = self.backward_col2img(delta_in, layer_idx) return delta_out def forward_img2col(self, x, train=True): self.x = x assert(self.x.shape[1] == self.num_input_channel) assert(self.x.shape[2] == self.input_height) assert(self.x.shape[3] == self.input_width) self.batch_size = self.x.shape[0] FN, C, FH, FW = self.WB.W.shape N, C, H, W = x.shape out_h = 1 + int((H + 2 * self.padding - FH) / self.stride) out_w = 1 + int((W + 2 * self.padding - FW) / self.stride) self.col_x = img2col(x, FH, FW, self.stride, self.padding) self.col_w = self.WB.W.reshape(FN, -1).T out1 = np.dot(self.col_x, self.col_w) + self.WB.B.reshape(-1,FN) out2 = out1.reshape(N, out_h, out_w, -1) self.z = np.transpose(out2, axes=(0, 3, 1, 2)) return self.z def backward_col2img(self, delta_in, layer_idx): FN, C, FH, FW = self.WB.W.shape dout = np.transpose(delta_in, axes=(0,2,3,1)).reshape(-1, FN) self.WB.dB = np.sum(dout, axis=0, keepdims=True).T / self.batch_size dW = np.dot(self.col_x.T, dout) self.WB.dW = np.transpose(dW, axes=(1, 0)).reshape(FN, C, FH, FW) / self.batch_size dcol = np.dot(dout, self.col_w.T) delta_out = col2img(dcol, self.x.shape, FH, FW, self.stride, self.padding) return delta_out, self.WB.dW, self.WB.dB def forward_numba(self, x, train=True): assert(x.ndim == 4) self.x = x assert(self.x.shape[1] == self.num_input_channel) assert(self.x.shape[2] == self.input_height) assert(self.x.shape[3] == self.input_width) self.batch_size = self.x.shape[0] if self.padding > 0: self.padded = np.pad(self.x, ((0,0), (0,0), (self.padding,self.padding), (self.padding,self.padding)), 'constant') #self.padded = np.pad(self.x, mode="constant", constant_value=0, pad_width=(0,0,0,0,self.padding,self.padding,self.padding,self.padding)) else: self.padded = self.x #end if self.z = jit_conv_4d(self.padded, self.WB.W, self.WB.B, self.output_height, self.output_width, self.stride) return self.z def backward_numba(self, delta_in, flag): assert(delta_in.ndim == 4) assert(delta_in.shape == self.z.shape) # 转换误差矩阵尺寸 dz_stride_1 = expand_delta_map(delta_in, self.batch_size, self.num_output_channel, self.input_height, self.input_width, self.output_height, self.output_width, self.filter_height, self.filter_width, self.padding, self.stride) # 求本层的输出误差矩阵时,应该用本层的输入误差矩阵互相关计算本层的卷积核的旋转 # 由于输出误差矩阵的尺寸必须与本层的输入数据的尺寸一致,所以必须根据卷积核的尺寸,调整本层的输入误差矩阵的尺寸 (pad_h, pad_w) = calculate_padding_size( dz_stride_1.shape[2], dz_stride_1.shape[3], self.filter_height, self.filter_width, self.input_height, self.input_width) dz_padded = np.pad(dz_stride_1, ((0,0),(0,0),(pad_h, pad_h),(pad_w, pad_w)), 'constant') # 计算本层的权重矩阵的梯度 self._calculate_weightsbias_grad(dz_stride_1) # 计算本层输出到下一层的误差矩阵 delta_out = self._calculate_delta_out(dz_padded, flag) #return delta_out return delta_out, self.WB.dW, self.WB.dB # 用输入数据乘以回传入的误差矩阵,得到卷积核的梯度矩阵 def _calculate_weightsbias_grad(self, dz): self.WB.ClearGrads() # 先把输入矩阵扩大,周边加0 (pad_h, pad_w) = calculate_padding_size( self.input_height, self.input_width, dz.shape[2], dz.shape[3], self.filter_height, self.filter_width, 1) input_padded = np.pad(self.x, ((0,0),(0,0),(pad_h, pad_h),(pad_w,pad_w)), 'constant') # 输入矩阵与误差矩阵卷积得到权重梯度矩阵 (self.WB.dW, self.WB.dB) = calcalate_weights_grad( input_padded, dz, self.batch_size, self.num_output_channel, self.num_input_channel, self.filter_height, self.filter_width, self.WB.dW, self.WB.dB) self.WB.MeanGrads(self.batch_size) # 用输入误差矩阵乘以(旋转180度后的)卷积核 def _calculate_delta_out(self, dz, layer_idx): if layer_idx == 0: return None # 旋转卷积核180度 rot_weights = self.WB.Rotate180() delta_out = np.zeros(self.x.shape).astype(np.float32) # 输入梯度矩阵卷积旋转后的卷积核,得到输出梯度矩阵 delta_out = calculate_delta_out(dz, rot_weights, self.batch_size, self.num_input_channel, self.num_output_channel, self.input_height, self.input_width, delta_out) return delta_out def pre_update(self): self.weights.pre_Update() def update(self): self.WB.Update() def save_parameters(self): self.WB.SaveResultValue() def load_parameters(self): self.WB.LoadResultValue() @staticmethod def calculate_output_size(input_h, input_w, filter_h, filter_w, padding, stride=1): output_h = (input_h - filter_h + 2 * padding) // stride + 1 output_w = (input_w - filter_w + 2 * padding) // stride + 1 return (output_h, output_w)