import numpy as np
import Activators # 引入自定义的激活器模块
import math
# 获取卷积区域。input_array为单通道或多通道的矩阵顺。i为横向偏移,j为纵向偏移,stride为步幅,filter_width为过滤器宽度,filter_height为过滤器的高度
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):
location = np.where(array == np.max(array)) # 获取最大值的位置where
return location[0], location[1]
# 计算一个过滤器的卷积运算,输出一个二维数据。每个通道的输入是图片,但是可能不是一个通道,所以这里自动适配输入为2D和3D的情况。
def conv(input_array, kernel_array, output_array, stride, bias):
output_width = output_array.shape[1] # 获取输出的宽度。一个过滤器产生的输出一定是一个通道
output_height = output_array.shape[0] # 获取输出的高度
kernel_width = kernel_array.shape[-1] # 过滤器的宽度。有可能有多个通道。多通道时shape=[深度、高度、宽度],单通道时shape=[高度、宽度]
kernel_height = kernel_array.shape[-2] # 过滤器的高度。有可能有多个通道。多通道时shape=[深度、高度、宽度],单通道时shape=[高度、宽度]
# print(output_width,output_height,kernel_width,kernel_height)
for i in range(output_height):
for j in range(output_width):
juanjiqu = get_patch(input_array, i, j, kernel_width, kernel_height, stride) # 获取输入的卷积区。(单通道或多通道)
# 这里是对每个通道的两个矩阵对应元素相乘求和,再将每个通道的和值求和
kernel_values = (
np.multiply(juanjiqu, kernel_array)).sum() # 卷积区与过滤器卷积运算。1,一个通道内,卷积区矩阵与过滤器矩阵对应点相乘后,求和值。2、将每个通道的和值再求和。
output_array[i][j] = kernel_values + bias # 将卷积结果加上偏量
def conv1(input_array, kernel_array, output_array, stride, bias):
output_width = output_array.shape[1] # 获取输出的宽度。一个过滤器产生的输出一定是一个通道
output_height = output_array.shape[0] # 获取输出的高度
kernel_width = kernel_array.shape[-1] # 过滤器的宽度。有可能有多个通道。多通道时shape=[深度、高度、宽度],单通道时shape=[高度、宽度]
kernel_height = kernel_array.shape[-2] # 过滤器的高度。有可能有多个通道。多通道时shape=[深度、高度、宽度],单通道时shape=[高度、宽度]
# print(output_width,output_height,kernel_width,kernel_height)
for i in range(output_height):
for j in range(output_width):
juanjiqu = get_patch(input_array, i, j, kernel_width, kernel_height, stride) # 获取输入的卷积区。(单通道或多通道)
# 这里是对每个通道的两个矩阵对应元素相乘求和,再将每个通道的和值求和
kernel_values = (
np.multiply(juanjiqu, kernel_array)).sum() # 卷积区与过滤器卷积运算。1,一个通道内,卷积区矩阵与过滤器矩阵对应点相乘后,求和值。2、将每个通道的和值再求和。
output_array[i][j] = kernel_values + bias # 将卷积结果加上偏量
return output_array
# 为数组增加Zero padding。zp步长,自动适配输入为2D和3D的情况
def padding(input_array, zp):
if zp == 0: # 如果不补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((input_depth, input_height + 2 * zp, input_width + 2 * zp)) # 先定义一个补0后大小的全0矩阵
# print(input_height) # 8
# print(padded_array.shape[2])
padded_array[:, zp: zp + input_height,
zp: zp + input_width] = input_array # 每个通道上,将中间部分替换成输入,这样就变成了原矩阵周围补0 的形式
return padded_array # 12*16*16
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)) # 先定义一个补0后大小的全0矩阵
padded_array[zp:
zp + input_height, zp: zp + input_width] = input_array # 将中间部分替换成输入,这样就变成了原矩阵周围补0 的形式
return padded_array
# 对numpy数组进行逐个元素的操作。op为函数。element_wise_op函数实现了对numpy数组进行按元素操作,并将返回值写回到数组中
def element_wise_op(array, op):
for i in np.nditer(array, op_flags=['readwrite']):
i[...] = op(i) # 将元素i传入op函数,返回值,再修改i
# Filter类保存了卷积层的参数以及梯度,并且实现了用梯度下降算法来更新参数。
class Filter(object):
def __init__(self, width, height, depth, filter_num): # 5*5*6*12
# 卷积核每个元素初始化为[-sqrt(6 / (fan_in + fan_out)), sqrt(6 / (fan_in + fan_out))]。
# 其中fan_in为输入通道数与滤波器宽高的乘机,即width*height*depth
# 其中fan_out为输出通道数与滤波器宽高的乘机,即width*height*filter_num
wimin = -math.sqrt(6 / (width * height * depth + width * height * filter_num))
wimax = -wimin
self.weights = np.random.uniform(wimin, wimax, (depth, height, width)) # 随机初始化卷基层权重一个很小的值,
# self.weights = np.random.uniform(-1e-2, 1e-2,(depth, height, width)) # 随机初始化卷基层权重一个很小的值,
self.bias = 0 # 初始化偏量为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):
# a=self.weights
self.weights += learning_rate * self.weights_grad # 加还是减????是加号 因为不同的误差算法有的是目标值减去输出值,有的是相反的,所以要看准正负号
# b=self.weights
# print(self.weights_grad)
self.bias += learning_rate * self.bias_grad
# 用ConvLayer类来实现一个卷积层。下面的代码是初始化一个卷积层,可以在构造函数中设置卷积层的超参数
class ConvLayer(object): # (28, 28, 1, 5, 5, 6, 0, 1, Activators.SigmoidActivator(),0.02) (12, 12, 6, 5, 5, 12, 0, 1, Activators.SigmoidActivator(),0.02)
# 初始化构造卷积层:输入宽