第七章 卷积神经网络2(代码实现)
文章目录
- 7.1卷积层和池化层实现
- 7.1.1 4维数组
- 7.1.2基于im2col的展开
- 7.1.3卷积层的实现
- 7.1.4池化层的是实现
- 7.2CNN实现
- 7.2.1目录结构如下:
- 7.2.2结果如下:
- 7.2.3代码实现:
- 7.2.3.1simple_convnet.py
- 7.2.3.2train_convnet
- 7.3CNN可视化
- 7.3.1第一层权重的可视化
- 7.3.2基于分层结构的信息
- 7.4代表性的CNN
- 7.4.1LeNet
- 7.4.2AlexNet
7.1卷积层和池化层实现
前面我们详细介绍了卷积层和池化层,本节我们就用Python来实现这两个层。也给进行实现的类赋予 forward 和 backward 方法,并使其可以作为模块使用。
7.1.1 4维数组
CNN中各层间传递的数据是4维数据(N,C,H,W),
例如:数据的形状是(10, 1, 28, 28),则它对应10个高为28、长为28、通道为1的数据。
import numpy as npx = np.random.randn(10, 1, 2, 2) # 随机生成数据,10个2*2的矩阵
# print(x.shape) # (10, 1, 2, 2)
# print(x)
# print(x[0]) # 理解为第一张图片,输出的是图片像素点
print(x[0].shape) # (1, 2, 2)
7.1.2基于im2col的展开
使用简单的程序实现卷积运算,估计要重复好几层的 for 语句。这样的实现有点麻烦,而使用 im2col 这个便利的函数进行简单的实现。
im2col 是一个函数,将输入数据展开以适合滤波器(权重)。如下图所示,对3维的输入数据应用 im2col 后,数据转换为2维矩阵(正确地讲,是把包含批数量的4维数据转换成了2维数据)。
对于输入数据:
即
对于滤波器:
计算结果如下:
代码实现如下:
import numpy as npdef im2col(input_data, filter_h, filter_w, stride=1, pad=0):""":param input_data: 由( 数据量,通道,高,长 )的4维数组构成的输入数据:param filter_h: 滤波器的高:param filter_w: 滤波器的长:param stride: 步幅:param pad: 填充:return:"""N, C, H, W = input_data.shapeout_h = (H + 2*pad - filter_h)//stride + 1out_w = (W + 2*pad - filter_w)//stride + 1img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))for y in range(filter_h):y_max = y + stride*out_hfor x in range(filter_w):x_max = x + stride*out_wcol[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)return colif __name__ == "__main__":x1 = np.random.rand(1, 3, 7, 7)col1 = im2col(x1, 5, 5, stride=1, pad=0)print(col1.shape) # (9, 75)x2 = np.random.rand(10, 3, 7, 7)col2 = im2col(x2, 5, 5, stride=1, pad=0)print(col2.shape) # (90, 75)
7.1.3卷积层的实现
由 im2col 、col2im来实现卷积层的前向传播、反向传播计算:
col2im实现如下: im2col的逆处理。
import numpy as npdef col2im(col, input_shape, filter_h, filter_w, stride=1, pad=0):""":param col:输入:param input_shape: 由( 数据量,通道,高,长 )的维数参数:param filter_h: 滤波器的高:param filter_w: 滤波器的长:param stride: 步幅:param pad: 填充:return:"""N, C, H, W = input_shapeout_h = (H + 2*pad - filter_h)//stride + 1out_w = (W + 2*pad - filter_w)//stride + 1col = col.reshape(N, out_h, out_w, C, filter_h, filter_w).transpose(0, 3, 4, 5, 1, 2)img = np.zeros((N, C, H + 2*pad + stride - 1, W + 2*pad + stride - 1))for y in range(filter_h):y_max = y + stride*out_hfor x in range(filter_w):x_max = x + stride*out_wimg[:, :, y:y_max:stride, x:x_max:stride] += col[:, :, y, x, :, :]return img[:, :, pad:H + pad, pad:W + pad]
代码实现如下(functions,util见前面博文):
import numpy as np
from common.functions import *
from common.util import im2col, col2imclass Convolution:def __init__(self, W, b, stride=1, pad=0):self.W = Wself.b = bself.stride = strideself.pad = pad# 中间数据(backward时使用)self.x = Noneself.col = Noneself.col_W = None# 权重/偏置参数的梯度self.dW = Noneself.db = Nonedef forward(self, x):FN, C, FH, FW = self.W.shapeN, C, H, W = x.shapeout_h = 1 + int((H + 2 * self.pad - FH) / self.stride)out_w = 1 + int((W + 2 * self.pad - FW) / self.stride)col = im2col(x, FH, FW, self.stride, self.pad)col_W = self.W.reshape(FN, -1).T # 滤波器的展开out = np.dot(col, col_W) + self.bout = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)self.x = xself.col = colself.col_W = col_Wreturn outdef backward(self, dout):FN, C, FH, FW = self.W.shapedout = dout.transpose(0, 2, 3, 1).reshape(-1, FN)self.db = np.sum(dout, axis=0)self.dW = np.dot(self.col.T, dout)self.dW = self.dW.transpose(1, 0).reshape(FN, C, FH, FW)dcol = np.dot(dout, self.col_W.T)dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)return dx
【注】。这里通过 reshape(FN,-1) 将参数指定为 -1 ,这是reshape 的一个便利的功能。通过在 reshape 时指定为 -1 , reshape 函数会自
动计算 -1 维度上的元素个数,以使多维数组的元素个数前后一致。比如,(10, 3, 5, 5)形状的数组的元素个数共有750个,指定 reshape(10,-1) 后,就会转换成(10, 75)形状的数组。
forward 的实现中,最后会将输出大小转换为合适的形状。转换时使用了NumPy的 transpose 函数。 transpose 会更改多维数组的轴的顺序。如下图所示,通过指定从0开始的索引(编号)序列,就可以更改轴的顺序。
【注】以上就是卷积层的 forward 处理的实现。通过使用 im2col 进行展开,基本上可以像实现全连接层的Affine层一样来实现。
7.1.4池化层的是实现
池化层的实现和卷积层相同,都使用 im2col 展开输入数据。不过,池化的情况下,在通道方向上是独立的,这一点和卷积层不同。
步骤如下:
1.展开输入数据。
2.求各行的最大值。
3.转换为合适的输出大小。
例:池化的应用区域按通道单独展开
只需对展开的矩阵求各行的最大值,并转换为合适的形状即可,如下图
代码实现如下:
class Pooling:def __init__(self, pool_h, pool_w, stride=1, pad=0):self.pool_h = pool_hself.pool_w = pool_wself.stride = strideself.pad = padself.x = Noneself.arg_max = Nonedef forward(self, x):N, C, H, W = x.shapeout_h = int(1 + (H - self.pool_h) / self.stride)out_w = int(1 + (W - self.pool_w) / self.stride)# 展开col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)col = col.reshape(-1, self.pool_h * self.pool_w)# 最大值arg_max = np.argmax(col, axis=1)out = np.max(col, axis=1)# 转换out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)self.x = xself.arg_max = arg_maxreturn outdef backward(self, dout):dout = dout.transpose(0, 2, 3, 1)pool_size = self.pool_h * self.pool_wdmax = np.zeros((dout.size, pool_size))dmax[np.arange(self.arg_max.size), self.arg_max.flatten()] = dout.flatten()dmax = dmax.reshape(dout.shape + (pool_size,))dcol = dmax.reshape(dmax.shape[0] * dmax.shape[1] * dmax.shape[2], -1)dx = col2im(dcol, self.x.shape, self.pool_h, self.pool_w, self.stride, self.pad)return dx
【注】最大值的计算可以使用NumPy的 np.max 方法。 np.max 可以指定axis 参数,并在这个参数指定的各个轴方向上求最大值。比如,如果写成 np.max(x, axis=1) ,就可以在输入 x 的第1维的各个轴方向上求最大值。
7.2CNN实现
例:搭建进行手写数字识别的CNN,网络结构如下。
7.2.1目录结构如下:
funtions.py, gradient.py, layers.py, multi_layer_net.py, optimizer.py, util.py)见前面博文
trainer.py见该博文
7.2.2结果如下:
执行train_convnet.py文件的以下结果:
绘图如下:
【注】如上所述,卷积层和池化层是图像识别中必备的模块。CNN可以有效读取图像中的某种特性,在手写数字识别中,还可以实现高精度的识别。
7.2.3代码实现:
7.2.3.1simple_convnet.py
# coding: utf-8
import sys, os
sys.path.append(os.pardir)
import pickle
import numpy as np
from collections import OrderedDict
from common.layers import *
from common.gradient import numerical_gradientclass SimpleConvNet:def __init__(self, input_dim=(1, 28, 28), conv_param={'filter_num':30, 'filter_size':5, 'pad':0, 'stride':1},hidden_size=100, output_size=10, weight_init_std=0.01):filter_num = conv_param['filter_num']filter_size = conv_param['filter_size']filter_pad = conv_param['pad']filter_stride = conv_param['stride']input_size = input_dim[1]conv_output_size = (input_size - filter_size + 2*filter_pad) / filter_stride + 1pool_output_size = int(filter_num * (conv_output_size/2) * (conv_output_size/2))# 权重初始化self.params = {}self.params['W1'] = weight_init_std * \np.random.randn(filter_num, input_dim[0], filter_size, filter_size)self.params['b1'] = np.zeros(filter_num)self.params['W2'] = weight_init_std * \np.random.randn(pool_output_size, hidden_size)self.params['b2'] = np.zeros(hidden_size)self.params['W3'] = weight_init_std * \np.random.randn(hidden_size, output_size)self.params['b3'] = np.zeros(output_size)# 生成必要的层self.layers = OrderedDict()self.layers['Conv1'] = Convolution(self.params['W1'], self.params['b1'],conv_param['stride'], conv_param['pad'])self.layers['Relu1'] = Relu()self.layers['Pool1'] = Pooling(pool_h=2, pool_w=2, stride=2)self.layers['Affine1'] = Affine(self.params['W2'], self.params['b2'])self.layers['Relu2'] = Relu()self.layers['Affine2'] = Affine(self.params['W3'], self.params['b3'])self.last_layer = SoftmaxWithLoss()def predict(self, x):for layer in self.layers.values():x = layer.forward(x)return xdef loss(self, x, t):"""损失函数"""y = self.predict(x)return self.last_layer.forward(y, t)def accuracy(self, x, t, batch_size=100):if t.ndim != 1 : t = np.argmax(t, axis=1)acc = 0.0for i in range(int(x.shape[0] / batch_size)):tx = x[i*batch_size:(i+1)*batch_size]tt = t[i*batch_size:(i+1)*batch_size]y = self.predict(tx)y = np.argmax(y, axis=1)acc += np.sum(y == tt) return acc / x.shape[0]def numerical_gradient(self, x, t):loss_w = lambda w: self.loss(x, t)grads = {}for idx in (1, 2, 3):grads['W' + str(idx)] = numerical_gradient(loss_w, self.params['W' + str(idx)])grads['b' + str(idx)] = numerical_gradient(loss_w, self.params['b' + str(idx)])return gradsdef gradient(self, x, t):"""求斜度(误差反传播法)"""# forwardself.loss(x, t)# backwarddout = 1dout = self.last_layer.backward(dout)layers = list(self.layers.values())layers.reverse()for layer in layers:dout = layer.backward(dout)# 保存权值grads = {}grads['W1'], grads['b1'] = self.layers['Conv1'].dW, self.layers['Conv1'].dbgrads['W2'], grads['b2'] = self.layers['Affine1'].dW, self.layers['Affine1'].dbgrads['W3'], grads['b3'] = self.layers['Affine2'].dW, self.layers['Affine2'].dbreturn gradsdef save_params(self, file_name="params.pkl"):params = {}for key, val in self.params.items():params[key] = valwith open(file_name, 'wb') as f:pickle.dump(params, f)def load_params(self, file_name="params.pkl"):with open(file_name, 'rb') as f:params = pickle.load(f)for key, val in params.items():self.params[key] = valfor i, key in enumerate(['Conv1', 'Affine1', 'Affine2']):self.layers[key].W = self.params['W' + str(i+1)]self.layers[key].b = self.params['b' + str(i+1)]
7.2.3.2train_convnet
# coding: utf-8
import sys, os
sys.path.append(os.pardir)
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from ch07.simple_convnet import SimpleConvNet
from common.trainer import Trainer# 读取数据
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=False)# 处理需要花费时间的情况下削减数据
#x_train, t_train = x_train[:5000], t_train[:5000]
#x_test, t_test = x_test[:1000], t_test[:1000]max_epochs = 20network = SimpleConvNet(input_dim=(1,28,28), conv_param = {'filter_num': 30, 'filter_size': 5, 'pad': 0, 'stride': 1},hidden_size=100, output_size=10, weight_init_std=0.01)trainer = Trainer(network, x_train, t_train, x_test, t_test,epochs=max_epochs, mini_batch_size=100,optimizer='Adam', optimizer_param={'lr': 0.001},evaluate_sample_num_per_epoch=1000)
trainer.train()# 保存参数值
network.save_params("params.pkl")
print("Saved Network Parameters!")# 绘制图表
markers = {'train': 'o', 'test': 's'}
x = np.arange(max_epochs)
plt.plot(x, trainer.train_acc_list, marker='o', label='train', markevery=2)
plt.plot(x, trainer.test_acc_list, marker='s', label='test', markevery=2)
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()
7.3CNN可视化
7.3.1第一层权重的可视化
上面使用MNIST数据集进行了CNN学习,第1层的卷积层的权重的形状是(30, 1, 5, 5),即30个大小为5 × 5、通道为1的滤波器。滤波器大小是5 × 5、通道数是1,意味着滤波器可以可视化为1通道的灰度图像。
**学习前的滤波器是随机进行初始化的,所以在黑白的浓淡上没有规律可循,但学习后的滤波器变成了有规律的图像。**我们发现,通过学习,滤波器被更新成了有规律的滤波器,比如从白到黑渐变的滤波器、含有块状区域(称为blob)的滤波器等。如下图:
【注】学习前和学习后的第1层的卷积层的权重:虽然权重的元素是实数,但是在图像的显示上,统一将最小值显示为黑色(0),最大值显示为白色(255)。如果要问上图中右边的有规律的滤波器在“观察”什么,答案就是它在观察边缘(颜色变化的分界线)和斑块(局部的块状区域)等。
比如,左半部分为白色、右半部分为黑色的滤波器的情况下,如下图所示,会对垂直方向上的边缘有响应。
【注】对水平方向上和垂直方向上的边缘有响应的滤波器:输出图像1中,垂直方向的边缘上出现白色像素,输出图像2中,水平方向的边缘上出现很多白色像素
上图中示了选择两个学习完的滤波器对输入图像进行卷积处理时的结果。我们发现**“滤波器1”对垂直方向上的边缘有响应,“滤波器2”对水平方向上的边缘有响应。**
由此可知,卷积层的滤波器会提取边缘或斑块等原始信息。而刚才实现的CNN会将这些原始信息传递给后面的层。
例:将卷积层(第1层)的滤波器显示为图像,代码如下:
# coding: utf-8
import numpy as np
import matplotlib.pyplot as plt
from simple_convnet import SimpleConvNetdef filter_show(filters, nx=8, margin=3, scale=10):"""c.f. https://gist.github.com/aidiary/07d530d5e08011832b12#file-draw_weight-py"""FN, C, FH, FW = filters.shapeny = int(np.ceil(FN / nx))fig = plt.figure()fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)for i in range(FN):ax = fig.add_subplot(ny, nx, i+1, xticks=[], yticks=[])ax.imshow(filters[i, 0], cmap=plt.cm.gray_r, interpolation='nearest')plt.show()network = SimpleConvNet()
# 随机初始化后的权重
filter_show(network.params['W1'])# 学习后的分量
network.load_params("params.pkl")
filter_show(network.params['W1'])
随机化权重结果如下:
学习后的权重如下:
7.3.2基于分层结构的信息
上面第1层的卷积层中提取了边缘或斑块等“低级”信息,那么在堆叠了多层的CNN中,各层中又会提取什么样的信息呢?根据深度学习的可视化相关的研究 ,随着层次加深,提取的信息(正确地讲,是反映强烈的神经元)也越来越抽象。
下图展示了进行一般物体识别(车或狗等)的8层CNN。这个网络结构的名称是下一节要介绍的AlexNet。AlexNet网络结构堆叠了多层卷积层和池化层,最后经过全连接层输出结果。其方块表示的是中间数据,对于这些中间数据,会连续应用卷积运算。
【注】CNN的卷积层中提取的信息。第1层的神经元对边缘或斑块有响应,第3层对纹理有响应,第5层对物体部件有响应,最后的全连接层对物体的类别(狗或车)有响应。
如上图所示,如果堆叠了多层卷积层,则随着层次加深,提取的信息也愈加复杂、抽象,这是深度学习中很有意思的一个地方。最开始的层对简单的边缘有响应,接下来的层对纹理有响应,再后面的层对更加复杂的物体部件有响应。也就是说,随着层次加深,神经元从简单的形状向“高级”信息变化。换句话说,就像我们理解东西的“含义”一样,响应的对象在逐渐变化。
7.4代表性的CNN
7.4.1LeNet
LeNet在1998年被提出,是进行手写数字识别的网络。如图7-27所示,它有连续的卷积层和池化层(正确地讲,是只“抽选元素”的子采样层),最后经全连接层输出结果。
【注】LeNet的网络结构
和“现在的CNN”相比,LeNet有几个不同点。第一个不同点在于激活函数。LeNet中使用sigmoid函数,而现在的CNN中主要使用ReLU函数。此外,原始的LeNet中使用子采样(subsampling)缩小中间数据的大小,而现在的CNN中Max池化是主流。
7.4.2AlexNet
AlexNet是引发深度学习热潮的导火线,不过它的网络结构和LeNet基本上没有什么不同,如下图。
【注】AlexNet
AlexNet叠有多个卷积层和池化层,最后经由全连接层输出结果。虽然结构上AlexNet和LeNet没有大的不同,但有以下几点差异。
• 激活函数使用ReLU。
• 使用进行局部正规化的LRN(Local Response Normalization)层。
• 使用Dropout。
如上所述,关于网络结构,LeNet和AlexNet没有太大的不同。但是,围绕它们的环境和计算机技术有了很大的进步。具体地说,现在任何人都可以获得大量的数据。而且,擅长大规模并行计算的GPU得到普及,高速进行大量的运算已经成为可能。大数据和GPU已成为深度学习发展的巨大的原动力。
第七章 卷积神经网络2(代码实现)相关推荐
- 第五章 卷积神经网络(CNN)
文章目录 5.1 卷积神经网络的组成层 5.2 卷积如何检测边缘信息? 5.3 卷积层中的几个基本参数? 5.3.1 卷积核大小 5.3.2 卷积核的步长 5.3.3 边缘填充 5.3.4 输入和输出 ...
- 第七章 其他神经网络类型
第七章其他神经网络类型 理解Elman神经网络 理解Jordan神经网络 ART1神经网络 不断发展的NEAT 到目前为止在这本书中我们主要看了前馈神经网络,神经网络中的所有连接并不是都需要前馈.还可 ...
- 第11章 卷积神经网络(CNNs)
第11章 卷积神经网络(CNNs) 我们回顾了整个机器学习和深度学习知识,现在我们学习CNNs(Convolutional Neural Networks)以及它在深度学习中的作用.在传统的前馈神经网 ...
- 第十二章 卷积神经网络实战--猫狗识别
1.介绍 我们已经学习了如何用传统的神经网络进行机器学习,在本章我们学习一下如何使用简单的神经网络进行图像分类.数据集用的是Kaggle的猫狗数据集.这里只有前100张,如果需要更多的可以去Kaggl ...
- 《Scikit-Learn与TensorFlow机器学习实用指南》第13章 卷积神经网络
第13章 卷积神经网络 来源:ApacheCN<Sklearn 与 TensorFlow 机器学习实用指南>翻译项目 译者:@akonwang @WilsonQu 校对: @飞龙 尽管 ...
- 第3.1章 卷积神经网络(CNN)——Conv、Pool、FC、Activation Function、BN各个层的作用及原理
第3.1章 卷积神经网络CNN-不同层的作用 一.Convolution(CONV) 二.Pooling(POOL) 三.Fully Connected(FC) 四.Activation Functi ...
- 深度学习实战 第6章卷积神经网络笔记
第6章 卷积神经网络 **卷积神经网络(Convolutional Neural Network,CNN)**是在实际应用中最为成功的一种神经网络,其专门用于处理格状结构数据,比如图片数据就可以看成是 ...
- 简单的卷积神经网络编程,卷积神经网络算法代码
关于AlphaGo的一些错误说法 最近看了一些关于alphago围棋对弈的一些人工智能的文章,尤其是美国人工智能方面教授的文章,发现此前媒体宣传的东西几乎都是错的,都是夸大了alpha狗.我做了一个阅 ...
- 曹健老师 TensorFlow2.1 —— 第五章 卷积神经网络
第一章 第二章 第三章 第四章 本章目的:用图卷积神经网络实现离散数据的分类 ( 以图像分类为例 ) . 5.1 卷积计算过程 在实际项目中,输入神经网络的是具有更高分辨率的彩色图片,使得送入全连接网 ...
最新文章
- 近两年跟踪速度较快的算法小结
- 屏幕滑动_Appium滑动引导页swipe函数
- 《图像超分》一些论文走读(SRCNN ,ESPCN ,VDSR ,SRGAN)
- java 蓝桥杯历届试题 分糖果(题解)
- java panel 所有事件_java-侦听/处理JPanel事件
- 偷偷告诉你,互联网公司理想的技术架构!
- php gmssl,GmSSL是什么
- 室内 Beacon定位室外 GPS 定位 大型场馆融合定位方案
- 为什么uzi排到古手羽就秒_Uzi排到古手羽秒退,网友争相发表看法,直播间弹幕疑似给出答案...
- 软件测试需求分析步骤
- 信通院 移动安全蓝皮书 数据安全管理案例 学习笔记
- Unity 调用系统自带的虚拟键盘
- java实现抛物线轨迹计算_JavaFX中抛物线轨迹的时间线
- 惠普linux进入bios设置u盘启动,hp惠普笔记本进入bios设置u盘启动装系统的方法步骤详细教程 - 系统家园...
- 苹果手机解压缩软件_BetterZip 5 for Mac(苹果专用解压缩软件)
- 程序员的十层楼,比尔盖茨仅第四层,你能到第几层?
- C语言 深度剖析数据在内存中的存储(2)
- 英伟达凭借GPU与AI笑傲本届CES展会
- 如何在Windows 10 上安装SQL Server 2000数据库?
- python爬虫翻页代码 豆瓣_Python爬虫 豆瓣动态页面的爬取