机器学习---CNN(创建和训练一个卷积神经网络并评估其性能)上-CSDN博客
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
1. cnn_operations模块
cnn_operations类
@staticmethod
def calc_loss(Y, tilde_Y):
# 训练样本个数
n_samples = Y.shape[0]
# 网络代价
loss = 0
for i in range(n_samples):
loss += np.sum((Y[i, :] - tilde_Y[i, :])**2)
loss /= (2 * n_samples)
return loss
计算网络代价
Y: N * M array各训练样本对应的类别标签N为训练样本个数M为类别数
tilde_Y: N * M array预测出的各训练样本的类别标签
这个函数被定义为静态方法使用了@staticmethod
装饰器所以它可以通过类名直接调
用而不需要创建类的实例。
@staticmethod
def convolute_2d(feature_map, kernel, size_kernel, stride, padding):
# 输入特征图尺寸
size_feature_map = np.asarray(feature_map.shape)
# 输出特征图尺寸
size_output = [int( \
(size_feature_map[0] + 2 * padding - size_kernel[0]) / stride + 1),
int( \
(size_feature_map[1] + 2 * padding - size_kernel[1]) / stride + 1)]
if padding == 0:
feature_map_padded = feature_map
elif padding > 0:
# 补零后的特征图
feature_map_padded = np.zeros(size_feature_map + 2 * padding)
feature_map_padded[padding: -padding, padding: -padding] += \
feature_map
output = np.empty(size_output)
for i in range(0, feature_map_padded.shape[0], stride):
for j in range(0, feature_map_padded.shape[1], stride):
sub_mat = feature_map_padded[i: np.min([i + size_kernel[0],
feature_map_padded.shape[0]]),
j: np.min([j + size_kernel[1],
feature_map_padded.shape[1]])]
shape_sub_mat = sub_mat.shape
if (shape_sub_mat[0] < size_kernel[0]) or \
(shape_sub_mat[1] < size_kernel[1]):
break
output[int(i/stride), int(j/stride)] = \
np.sum(sub_mat * kernel)
return output
二维卷积卷积核不翻转
feature_map: 2-d array输入特征图也可以被看作是输入的图像或前一层输出的特征图
kernel: 2-d array卷积核
size_kernel: array卷积核尺寸
stride: 整数卷积核步长
padding: 整数边缘补零的宽度常用于保持特征图的尺寸。
output: 二维数组表示进行卷积操作后输出的特征图。
操作流程
①计算输入特征图的尺寸size_feature_map。
②计算输出特征图的尺寸size_output。这个尺寸取决于输入特征图的尺寸卷积核的尺寸步长和
补零的宽度。
③如果补零的宽度为0则不进行任何操作如果补零的宽度大于0则在输入特征图的边缘添加相
应宽度的零得到feature_map_padded。
④初始化输出特征图output。
⑤对feature_map_padded进行卷积操作在每个步长位置取出与卷积核相同尺寸的子区域
sub_mat然后将sub_mat与卷积核逐元素相乘并求和得到的结果存储在output的相应位置。
@staticmethod
def pool(feature_map, type_pooling, size_kernel, stride, padding):
# 输入特征图尺寸
size_feature_map = np.asarray(feature_map.shape)
# 输出特征图尺寸
size_output = [int( \
(size_feature_map[0] + 2 * padding - size_kernel[0]) / stride + 1),
int( \
(size_feature_map[1] + 2 * padding - size_kernel[1]) / stride + 1)]
if padding == 0:
feature_map_padded = feature_map
elif padding > 0:
# 补零后的特征图
feature_map_padded = np.zeros(size_feature_map + 2 * padding)
feature_map_padded[padding: -padding, padding: -padding] += \
feature_map
output = np.empty(size_output)
if type_pooling is "max":
func = np.max
elif type_pooling is "average":
#池化层每个神经元有一个权值此处只需求和不需求均值
func = np.sum
for i in range(0, feature_map_padded.shape[0], stride):
for j in range(0, feature_map_padded.shape[1], stride):
sub_mat = feature_map_padded[i: np.min([i + size_kernel[0],
feature_map_padded.shape[0]]),
j: np.min([j + size_kernel[1],
feature_map_padded.shape[1]])]
shape_sub_mat = sub_mat.shape
if (shape_sub_mat[0] < size_kernel[0]) or \
(shape_sub_mat[1] < size_kernel[1]):
break
output[int(i/stride), int(j/stride)] = func(sub_mat)
return output
池化用于降低特征图的维度同时保持重要的特征
feature_map: 2-d array输入特征图
type_pooling: 池化核类型{"max", "average"}
size_kernel: array池化核尺寸
stride: 池化核步长整数
padding: 边缘补零的宽度整数
output: 二维数组表示进行池化操作后输出的特征图。
操作流程
①计算输入特征图的尺寸size_feature_map。
②计算输出特征图尺寸size_output。取决于输入特征图尺寸池化核尺寸步长和补零的宽度。
③如果补零的宽度为0则不进行任何操作如果补零的宽度大于0则在输入特征图的边缘添加相
应宽度的零得到feature_map_padded。
④初始化输出特征图output。
⑤根据池化类型选择相应的函数func。
⑥对feature_map_padded进行池化操作在每个步长位置取出与池化核相同尺寸的子区域
sub_mat然后将func应用于sub_mat得到的结果存储在output的相应位置。
@staticmethod
def upsample_conv_2d(delta_next_layer, kernel, size_kernel, stride):
# 下一层卷积层中一个神经元的灵敏度map的尺寸
size_delta_next_layer = np.asarray(delta_next_layer.shape)
# 边缘补零后当前层一中一个神经元灵敏度map的尺寸
size_delta_padded = \
[size_delta_next_layer[0] * stride + size_kernel[0] - stride,
size_delta_next_layer[1] * stride + size_kernel[1] - stride]
delta_padded = np.zeros(size_delta_padded)
for i in range(delta_next_layer.shape[0]):
for j in range(delta_next_layer.shape[1]):
# 卷积核不翻转
delta_padded[i * stride: i * stride + size_kernel[0],
j * stride: j * stride + size_kernel[1]] += \
delta_next_layer[i, j] * kernel
return delta_padded
当前层为池化层下一层为卷积层时的上采样
delta_next_layer: 2-d array下一层卷积层中一个神经元的灵敏度map
kernel: 2-d array下一层中一个神经元的一个卷积核
size_kernel: array下一层中一个神经元的一个卷积核的尺寸
stride: 下一层中一个神经元的一个卷积核的步长
delta_padded: 2-d array当前池化层中一个神经元的灵敏度map边缘补零后
操作流程
①计算下一层卷积层中一个神经元的灵敏度map的尺寸size_delta_next_layer。
②计算边缘补零后当前层中一个神经元的灵敏度map的尺寸size_delta_padded。这个尺寸取决于
下一层灵敏度map的尺寸卷积核的尺寸和步长。
③初始化当前层的灵敏度mapdelta_padded。
④对下一层的灵敏度map进行遍历在每个位置将该位置的灵敏度值乘以卷积核然后加到当前
层的相应位置。
@classmethod
def upsample_pool(cls, delta_next_layer, type_pooling,
size_kernel, stride):
if type_pooling is "max":
# TODO:
pass
elif type_pooling is "average":
# 池化时实际进行了求和运算故权值均为1
kernel = np.ones(size_kernel)
delta_padded = cls.upsample_conv_2d(delta_next_layer, kernel,
size_kernel, stride)
return delta_padded
当前层为卷积层下一层为池化层时的上采样
delta_next_layer: 2-d array下一层池化层中一个神经元的灵敏度map
type_pooling: 下一层中一个神经元的池化核类型{"max", "average"}
size_kernel: array下一层中一个神经元的池化核尺寸
stride: 下一层中一个神经元的池化核步长
delta_padded: 2-d array当前卷积层中一个神经元的灵敏度map边缘补零后
操作流程
①首先判断池化类型。如果是max那么暂未实现标记为TODO。如果是average则进行
下一步操作。
②对于average池化由于池化过程实际上是求和运算每个元素的贡献相同所以权值均为1
构造一个全为1的kernel。
③调用upsample_conv_2d方法将下一层的灵敏度map扩展到当前层的尺寸。
@staticmethod
def inv_conv_2d(feature_map, size_kernel, stride, padding, delta):
# 输入特征图尺寸
size_feature_map = np.asarray(feature_map.shape)
if padding == 0:
feature_map_padded = feature_map
elif padding > 0:
# 补零后的特征图
feature_map_padded = np.zeros(size_feature_map + 2 * padding)
feature_map_padded[padding: -padding, padding: -padding] += \
feature_map
output = 0
for i in range(0, feature_map_padded.shape[0], stride):
for j in range(0, feature_map_padded.shape[1], stride):
sub_mat = feature_map_padded[i: np.min([i + size_kernel[0],
feature_map_padded.shape[0]]),
j: np.min([j + size_kernel[1],
feature_map_padded.shape[1]])]
shape_sub_mat = sub_mat.shape
if (shape_sub_mat[0] < size_kernel[0]) or \
(shape_sub_mat[1] < size_kernel[1]):
break
output += delta[int(i/stride), int(j/stride)] * sub_mat
return output
计算网络代价对卷积层中一个神经元的一个卷积核的偏导数
feature_map: 2-d array输入特征图
size_kernel: array卷积核尺寸
stride: 卷积核的步长
padding: 边缘补零的宽度
delta: 2-d array卷积层中一个神经元的灵敏度map
output: 2-d array尺寸与卷积核尺寸相同
操作流程
①首先获取输入特征图的尺寸然后根据边缘补零的宽度padding对输入特征图进行补零操作。
②初始化输出output为0。
③进行两层循环以步长`stride`遍历补零后的特征图。
④在每次循环中取出特征图中与卷积核尺寸相同的一个子区域sub_mat。
⑤检查子区域的尺寸如果子区域的尺寸小于卷积核尺寸那么跳过当前循环。
⑥计算子区域与对应的灵敏度值的乘积并累加到输出值output中。
@staticmethod
def _relu(x):
return np.max([0, x])
@staticmethod
def _sigmoid(x):
return 1 / (1 + np.exp(-x))
@classmethod
def _tanh(cls, x):
return 2 * cls._sigmoid(2 * x) - 1
@classmethod
def activate(cls, feature_map, type_activation):
if type_activation is None:
return feature_map
elif type_activation is "relu":
func = cls._relu
elif type_activation is "sigmoid":
func = cls._sigmoid
elif type_activation is "tanh":
func = cls._tanh
size_feature_map = np.asarray(feature_map.shape)
output = np.empty(size_feature_map)
for i in range(size_feature_map[0]):
for j in range(size_feature_map[1]):
output[i, j] = func(feature_map[i, j])
return output
激活
feature_map: 2-d array输入特征图
type_activation: 激活函数类型{"relu", "sigmoid", "tanh", None}
output: 2-d array激活后特征图与输入特征图尺寸相同
根据输入的类型_activation选择对应的激活函数,比如"relu"选择ReLU函数。通过双循环遍历
输入特征图每个元素,将其输入到选择的激活函数中,得到输出特征图每个元素的值。实现了将输入
特征图经过不同激活函数处理,输出经激活后的特征图。
2. test模块
配置和构建一个简单的卷积神经网络模型
def config_net():
# 输入层
size_input = np.array([8, 8])
args_input = ("input", (size_input,))
# C1卷积层
connecting_matrix_C1 = np.ones([1, 2])
size_conv_kernel_C1 = np.array([5, 5])
stride_conv_kernel_C1 = 1
padding_conv_C1 = 0
type_activation_C1 = "sigmoid"
args_C1 = ("convoluting", (connecting_matrix_C1, size_conv_kernel_C1,
stride_conv_kernel_C1, padding_conv_C1,
type_activation_C1))
# S2池化层
type_pooling_S2 = "average"
size_pool_kernel_S2 = np.array([2, 2])
stride_pool_kernel_S2 = 2
padding_pool_S2 = 0
type_activation_S2 = "sigmoid"
args_S2 = ("pooling", (type_pooling_S2, size_pool_kernel_S2,
stride_pool_kernel_S2, padding_pool_S2,
type_activation_S2))
# C3卷积层
connecting_matrix_C3 = np.ones([2, 2])
size_conv_kernel_C3 = np.array([2, 2])
stride_conv_kernel_C3 = 1
padding_conv_C3 = 0
type_activation_C3 = "sigmoid"
args_C3 = ("convoluting", (connecting_matrix_C3, size_conv_kernel_C3,
stride_conv_kernel_C3, padding_conv_C3,
type_activation_C3))
# 输出层
n_nodes_output = 2
type_output = "softmax"
args_output = ("output", (n_nodes_output, type_output))
args = (args_input,
args_C1,
args_S2,
args_C3,
args_output)
cnn_net = cnn()
cnn_net.config(args)
return cnn_net
定义了网络各层的参数:
输入层大小
每个卷积层的连接矩阵、卷积核大小、步长、边缘填充、激活函数
池化层类型、池化核大小、步长、边缘填充、激活函数
输出层节点数和激活函数。
将这些参数打包成元组,形成每个层的参数元组如args_C1定义C1层的参数元组。
将所有层的参数元组组成一个总的参数元组args初始化一个cnn网络对象cnn_net调用cnn_net
的config方法,传入参数元组args,完成网络结构的配置返回配置好结构的cnn_net对象。
n_train = 10000
X_train = 0.2 * np.random.randn(8, 8, n_train)
Y_train = np.random.randint(2, size=n_train)
for i in range(Y_train.shape[0]):
if Y_train[i] == 0:
X_train[1, :, i] += np.ones(8)
elif Y_train[i] == 1:
X_train[:, 1, i] += np.ones(8)
size_batch = 50
n_epochs = 500
cnn_net = config_net()
cnn_net.fit(X_train, Y_train, size_batch=size_batch, n_epochs=n_epochs)
n_test = 1000
X_test = 0.2 * np.random.randn(8, 8, n_test)
Y_test = np.random.randint(2, size=n_test)
for i in range(Y_test.shape[0]):
if Y_test[i] == 0:
X_test[1, :, i] += np.ones(8)
elif Y_test[i] == 1:
X_test[:, 1, i] += np.ones(8)
correct_rate = cnn_net.test(X_test, Y_test)
plt.figure()
for i in range(cnn_net.layers[1].n_nodes):
plt.subplot(1, 2, i + 1)
plt.imshow(cnn_net.layers[1].nodes[i].conv_kernels[0], cmap="gray")
plt.show()
生成训练数据和测试数据:
X_train, X_test shape为(8,8,n)Y_train, Y_test为0或1
根据Y标记,在X的不同位置增加一个单位向量 signal
配置网络结构cnn_net使用训练数据训练网络cnn_net.fit()使用测试数据评估网络准确率
cnn_net.test()可视化第一个卷积层学习得到的卷积核参数。
主要步骤包括数据生成、网络配置、训练、测试、参数可视化
完整的分类任务流程生成带有标记的训练和测试数据、搭建并训练卷积神经网络、测试网络效
果、查看卷积层学习到的特征