不使用任何框架实现CNN网络
文章目录
- 一、 问题描述
- 二、 设计简要描述
- 三、程序清单
- 四、结果分析
- 五、调试报告
- 六、实验小结
一、 问题描述
基于Numpy和函数im2col与col2im来实现一个简单的卷积神经网络,将其用于手写体识别。
二、 设计简要描述
机器学习的三个基本步骤——
程序设计思路——(此图放大可看清)
三、程序清单
1.卷积层实现
import numpy as np
from main 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.shape # 卷积核的形状N, C, H, W = x.shape # 输入数据形状out_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.b # 计算展开后的矩阵乘积# 输出大小转换为合适的形状# transpose会更改多维数组的轴的顺序,将输出数据形状由(N,H,W,C)转变为(N,C,H,W)# 索引(0,1,2,3)对应着(N,H,W,C)out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)# 更新backward过程需要用到的中间数据self.x = xself.col = colself.col_W = col_Wreturn out
2.池化层实现
import numpy as np
from main import im2col, col2imclass Pooling:def __init__(self, pool_h, pool_w, stride=1, pad=0):self.pool_h = pool_hself.pool_w = pool_wself.stride = strideself.pad = pad# 存储backward需用到的中间数据self.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)# 通过reshape方法将数据转换为合适的形状out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)# 保存backward过程中需要用到的中间数据self.x = xself.arg_max = arg_maxreturn out# 反向传播def 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
3.网络搭建
from collections import OrderedDictimport numpy as np
from Convolution import Convolution
from Pooling import Pooling
from SoftmaxWithLoss import SoftmaxWithLoss
from Relu import Relu
from Affine import Affineclass SimpleConvNet:"""简单的ConvNetconv - relu - pool - linear - relu - linear - softmaxParameters----------input_size : 输入大小(MNIST的情况下为784)conv_param : 保存卷积层的超参数(字典)hidden_size_list : 隐藏层的神经元数量的列表(e.g. [100, 100, 100])output_size : 输出大小(MNIST的情况下为10)activation : 'relu' or 'sigmoid'weight_init_std : 指定权重的标准差(e.g. 0.01)指定'relu'或'he'的情况下设定“He的初始值”指定'sigmoid'或'xavier'的情况下设定“Xavier的初始值”"""# input_dim是输入数据的通道,高,长# conv_param是卷积层的超参数# hidden_size是倒数第二个全连接层神经元数量# output_size是最后一个全连接层神经元数量# weight_init_std是权重的标准差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'] # conv_param―卷积层的超参数(字典)。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))# 初始化权重"""学习所需的参数是第1层的卷积层和剩余两个全连接层的权重和偏置。将这些参数保存在实例变量的params字典中。将第1层的卷积层的权重设为关键字W1,偏置设为关键字b1。同样,分别用关键字W2、b2和关键字W3、b3来保存第2个和第3个全连接层的权重和偏置。"""# 卷积层的参数初始化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)# 两个Linear层的参数的初始化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()# 向有序字典(OrderedDict)的layers中添加层# 依次命名为'Conv1'、'Relu1'、'Pool1'、'Linear1'、'Relu2'、'Affine2'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.lastLayer = SoftmaxWithLoss()"""
参数x是输入数据,t是教师标签。
用于推理的predict方法从头开始依次调用已添加的层,并将结果传递给下一层。
在求损失函数的loss方法中,除了使用 forward方法进行的前向传播处理之外,还会继续进行forward处理,直到到达最后的SoftmaxWithLoss层。"""def forward(self, x):for layer in self.layers.values():x = layer.forward(x)return xdef loss(self, x, t):"""求损失函数参数x是输入数据、t是数据标签"""y = self.forward(x)return self.lastLayer.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.forward(tx)y = np.argmax(y, axis=1)acc += np.sum(y == tt)return acc / x.shape[0]"""参数的梯度通过误差反向传播法(反向传播)求出,通过把正向传播和反向传播组装在一起来完
成。因为已经在各层正确实现了正向传播和反向传播的功能,所以这里只需要以合适的顺序调用
即可。最后,把各个权重参数的梯度保存到grads字典中。"""def backward(self, x, t):# 运用误差反向传播法求取梯度# forwardself.loss(x, t)# backwarddout = 1dout = self.lastLayer.backward(dout)layers = list(self.layers.values())layers.reverse()for layer in layers:dout = layer.backward(dout)# 将学习过程中计算出的权重参数梯度保存到grads字典中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 grads
4.训练模型与结果展示
import numpy as np
import matplotlib.pyplot as plt
from mnist import load_mnist
from optimizer import *
from SimpleConvNet import SimpleConvNet# 读入数据
(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]class Trainer:"""进行神经网络的训练的类"""def __init__(self, network, x_train, t_train, x_test, t_test,epochs=20, mini_batch_size=100, optimizer='Adam', optimizer_param={'lr': 0.01},evaluate_sample_num_per_epoch=None, verbose=True):self.network = networkself.verbose = verboseself.x_train = x_trainself.t_train = t_trainself.x_test = x_testself.t_test = t_testself.epochs = epochsself.batch_size = mini_batch_sizeself.evaluate_sample_num_per_epoch = evaluate_sample_num_per_epoch# 选择优化方式optimizer_class_dict = {'sgd': SGD, 'momentum': Momentum, 'nesterov': Nesterov,'adagrad': AdaGrad, 'rmsprpo': RMSprop, 'adam': Adam}self.optimizer = optimizer_class_dict[optimizer.lower()](**optimizer_param)self.train_size = x_train.shape[0]self.iter_per_epoch = max(self.train_size / mini_batch_size, 1)self.max_iter = int(epochs * self.iter_per_epoch)self.current_iter = 0self.current_epoch = 0self.train_loss_list = []self.train_acc_list = []self.test_acc_list = []def train_step(self):batch_mask = np.random.choice(self.train_size, self.batch_size)x_batch = self.x_train[batch_mask]t_batch = self.t_train[batch_mask]# 调用网络的backward函数获取梯度grads = self.network.backward(x_batch, t_batch)# 使用优化器更新参数self.optimizer.update(self.network.params, grads)loss = self.network.loss(x_batch, t_batch)self.train_loss_list.append(loss)if self.verbose: print("train loss:" + str(loss))if self.current_iter % self.iter_per_epoch == 0:self.current_epoch += 1x_train_sample, t_train_sample = self.x_train, self.t_trainx_test_sample, t_test_sample = self.x_test, self.t_testif not self.evaluate_sample_num_per_epoch is None:t = self.evaluate_sample_num_per_epochx_train_sample, t_train_sample = self.x_train[:t], self.t_train[:t]x_test_sample, t_test_sample = self.x_test[:t], self.t_test[:t]# 计算训练精度train_acc和测试精度test_acctrain_acc = self.network.accuracy(x_train_sample, t_train_sample)test_acc = self.network.accuracy(x_test_sample, t_test_sample)self.train_acc_list.append(train_acc)self.test_acc_list.append(test_acc)if self.verbose: print("=== epoch:" + str(self.current_epoch) + ", train acc:" + str(train_acc) +", test acc:" + str(test_acc) + " ===")self.current_iter += 1def train(self):for i in range(self.max_iter): # 训练循环self.train_step()test_acc = self.network.accuracy(self.x_test, self.t_test)if self.verbose:print("=============== Final Test Accuracy ===============")print("test acc:" + str(test_acc))max_epochs = 20# 将之前定义的SimpleConvNet网络实例化
network = 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()# 绘制图形
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()
四、结果分析
实验结果符合预期
五、调试报告
1.控制台报错
Traceback (most recent call last):
File “E:/project/pythonProject/08_CNN/Trainer.py”, line 101, in
trainer = Trainer(network, x_train, t_train, x_test, t_test,
File “E:/project/pythonProject/08_CNN/Trainer.py”, line 35, in init
self.optimizer = optimizer_class_dictoptimizer.lower()
TypeError: type object argument after ** must be a mapping, not float
解决:应该将训练类在初始化参数时的lr = 0.001改为optimizer_param={‘lr’:0.01}
- 控制台报错
Traceback (most recent call last):
File “E:/project/pythonProject/08_CNN/Trainer.py”, line 104, in
trainer.train()
File “E:/project/pythonProject/08_CNN/Trainer.py”, line 85, in train
self.train_step()
File “E:/project/pythonProject/08_CNN/Trainer.py”, line 53, in train_step
grads = self.network.backward(x_batch, t_batch)
File “E:\project\pythonProject\08_CNN\SimpleConvNet.py”, line 124, in backward
self.loss(x, t)
File “E:\project\pythonProject\08_CNN\SimpleConvNet.py”, line 99, in loss
return self.lastLayer.forward(y, t)
AttributeError: ‘SimpleConvNet’ object has no attribute ‘lastLayer’
原因:定义网络时参数用的是
解决:改为lastLayer即可
六、实验小结
本次实验使用numpy和两个展开函数实现了CNN的卷积和池化层,进一步理解了CNN的内部结构,并结合之前的实验搭建了一个简单的卷积神经网络进行手写体识别,采用反向传播和Adam来优化,在较短的时间内得到了较好的实验精度,可见CNN的优势所在。最大的收获是对卷积层和池化层原理的透彻理解。
相关文章:

关于刘冬大侠Spring.NET系列学习笔记3的一点勘正
诚如他第22楼“只因渴求等待”提出的疑问一样,他的下面那一段代码是存在一点点问题的, XElement root XElement.Load(fileName);var objects from obj in root.Elements("object") select obj; 如果照搬照抄刘冬大侠的这段代码那是不会成功读…

什么叫做KDJ指标
什么叫做KDJ指标 KDJ指标的中文名称是随机指数,最早起源于期货市场。 KDJ指标的应用法则KDJ指标是三条曲线,在应用时主要从五个方面进行考虑:KD的取值的绝对数字;KD曲线的形态;KD指标的交叉;KD指标的背离&a…
vim常用命令总结 (转)
在命令状态下对当前行用 (连按两次), 或对多行用n(n是自然数)表示自动缩进从当前行起的下面n行。你可以试试把代码缩进任意打乱再用n排版,相当于一般IDE里的code format。使用ggG可对整篇代码进行排版。 vim 选择文本&…

敏捷过程、极限编程和SCRUM的关系
极限编程是最知名的敏捷开发过程,SCRUM是最经典的极限编程。 层次关系从大到小是:敏捷过程>极限编程>SCRUM

C#双面打印解决方法(打印word\excel\图片)
最近需要按顺序打印word、excel、图片,其中有的需要单面打印,有的双面。网上查了很多方法。主要集中在几个方式解决 1、word的print和excel的printout里设置单双面 2、printdocument里的printsettings的duplex设置单双面 试过之后效果都不好,…

【leetcode】589. N-ary Tree Preorder Traversal
题目如下: 解题思路:凑数题1,话说我这个也是凑数博? 代码如下: class Solution(object):def preorder(self, root):""":type root: Node:rtype: List[int]"""if root None:return []re…

MSDN Visual系列:创建Feature扩展SharePoint列表项或文档的操作菜单项
原文:http://msdn2.microsoft.com/en-us/library/bb418731.aspx在SharePoint中我们可以通过创建一个包含CustomAction元素定义的Feature来为列表项或文档添加一个自定义操作菜单项(Entry Control Block Item)。我们可以添加自定义命令到默认的SharePoint用户界面中。…

评审过程中,A小组发现了5个缺陷,B小组发现了9个缺陷,他们发现的缺陷中有3个是相同的。请问:还有多少个潜在的缺陷没有发现?
分析:这一个“捉-放-捉”问题 背景: 求解: 可以将A看成是第一次捕捉,发现了5个缺陷,全部打上标记 B看成是第二次捕捉,发现了9个缺陷,其中有3个有标记 那么可以算出系统中一共存在的缺陷数量为…

Dell PowerVault TL4000 磁带机卡带问题
最近一段时间Dell PowerVault TL4000 磁带机故障频繁,昨天我在管理系统里面看到Library Status告警:HE: sled blocked, error during sled movement to rotation position Code: 8D 07 ,Dell工程师根据Code: 8D 07判断是磁带卡带了࿰…

【git】git入门之把自己的项目上传到github
1. 首先当然是要有一个GIT账号:github首页 2. 然后在电脑上安装一个git:git首页 注册和安装这里我就不说了。我相信大家做这个都没有问题。 3. 上述两件事情做完了,就登陆到github页面 1)首先我们点标注【1】的小三角,…

Java面试查漏补缺
一、基础 1、&和&&的区别。 【概述】 &&只能用作逻辑与(and)运算符(具有短路功能);但是&可以作为逻辑与运算符(是“无条件与”,即没有短路的功能)…

selenium之frame操作
前言 很多时候定位元素时候总是提示元素定位不到的问题,明明元素就在那里,这个时候就要关注你所定位的元素是否在frame和iframe里面 frame标签包含frameset、frame、iframe三种,frameset和普通的标签一样,不会影响正常的定位&…

(C++)将整型数组所有成员初始化为0的三种简单方法
#include<cstdio> #include<cstring>int main(){//1.方法1 int a[10] {};//2.方法2 int b[10] {0};//3.方法3 注意:需要加 <cstring>头文件 int c[10];memset(c,0,sizeof(c));for(int i0;i<9;i){printf("a[%d]%d\n",i,a[i]);}prin…

(C++)对用户输入的整形数组进行冒泡排序
#include<cstdio>//冒泡排序的本质在于交换 //1.读入数组 //2.排序 //3.输出数组 int main(){int a[10];printf("%s","请依次输入数组的10个整型元素:\n");for(int i0;i<9;i){scanf("%d",&a[i]);} int temp 0;for(int …

U3D的Collider
被tx鄙视的体无完肤,回来默默的继续看书,今天看u3d,试了下collider,发现cube添加了rapidbody和boxcollider后落在terrain后就直接穿过去了... 找了一会原因,看到一个collider的参数说明: 分别选中立方体和树的模型&…

限制程序只打开一个实例(转载)
当我们在做一些管理平台类的程序(比如Windows的任务管理器)时,往往需要限制程序只能打开一个实例。解决这个问题的大致思路很简单,无非是在程序打开的时候判断一下是否有与自己相同的进程开着,如果有,则关闭…

dao.xml
<select id"selectItemkindByPolicyNo" resultMap"BaseResultMap" parameterType"java.util.List"> select * from prpcitemkind kind where kind.PolicyNo in <foreach collection"list" item"item&q…

(C++)字符数组初始化的两种方法
#include<cstdio> //字符数组的两种赋值方法 int main(){//1.方法一char str1[14] {I, ,l,o,v,e, ,m,y, ,m,o,m,.};for(int i 0;i<13;i){printf("%c",str1[i]);}printf("\n");//2.方法二,直接赋值字符串(注意,只有初始化…

SQL Server 中update的小计
update中涉及到多个表的: 1.update TableA set a.ColumnCb.ColumnC from TableA a inner join TableB b on a.ColumnDb.ColumnD 这样是不对的,报错如下: 消息 4104,无法绑定由多个部分组成的标识符 “xxxx” 虽然前面的TableA和后…

MFC中的字符串转换
在VC中有着一大把字符串类型。从传统的char*到std::string到CString,简直是多如牛毛。期间的转换相信也是绕晕了许多的人,我曾就是其中的一个。还好,MS还没有丧失功德心,msdn的一篇文章详细的解析了各种字符串的转换问题ÿ…

tf.nn.relu
tf.nn.relu(features, name None) 这个函数的作用是计算激活函数 relu,即 max(features, 0)。即将矩阵中每行的非最大值置0。 import tensorflow as tfa tf.constant([-1.0, 2.0]) with tf.Session() as sess:b tf.nn.relu(a)print sess.run(b) 以上程序输出的结…

(C++)字符数组的四种输入输出方式
scanf/printf%s getchar()/putchar() 前者不带参数后者带 gets()/puts() 二者都带参数,为一维字符数组或二维字符数组的一维 运用指针scanf/printf或getchar/putchar #include<cstdio> //字符数组的3种输入输出方式int main(){//1.scanf/printf%schar st…

css制作对话框
当你发现好多图都能用css画出来的时候,你就会觉得css很有魅力了。//我是这么觉得的,先不考虑什么兼容问题 像漫画里出现的对话框,往往都是一个对话框然后就加入一个箭头指向说话的那一方,来表示这个内容是谁述说的。 今天认真学了…

Git相关二三事(git reflog 和彩色branch)【转】
转自:https://www.jianshu.com/p/3622ed542c3b 背景 git太常用了,虽然,用起来不难,但也有很多小技巧的东西... 1. 后悔药 哪天不小心,写完代码,没commit,直接reset了或者checkout了,怎么办&…

MS SQL入门基础:备份和恢复系统数据库
系统数据库保存了有关SQL Server 的许多重要数据信息,这些数据的丢失将给系统带来极为严重的后果,所以我们也必须对系统数据库进行备份。这样一旦系统或数据库失败,则可以通过恢复来重建系统数据库。在SQL Server 中重要的系统数据库主要有ma…

(C++)输入输出字符矩阵(二维字符数组)的三种方法
想输出一个这样的字符矩阵 CSU ZJU PKUscanf和printf #include<cstdio> #include<cmath>int main(){char schools[3][3];printf("请输入:\n");for(int i0;i<2;i){scanf("%s",schools[i]);}printf("以下为输出:…

C# async await 学习笔记2
C# async await 学习笔记1(http://www.cnblogs.com/siso/p/3691059.html) 提到了ThreadId是一样的,突然想到在WinForm中,非UI线程是无法直接更新UI线程上的控件的问题。 于是做了如下测试: using System; using System…

不走寻常路 设计ASP.NET应用程序的七大绝招
随着微软.NET的流行,ASP.NET越来越为广大开发人员所接受。作为ASP.NET的开发人员,我们不仅需要掌握其基本的原理,更要多多实践,从实践中获取真正的开发本领。在我们的实际开发中,往往基本的原理满足不了开发需求&#…

记录Linux下的钓鱼提权思路
参考Freebuf上的提权文章(利用通配符进行Linux本地提权):http://www.freebuf.com/articles/system/176255.html 以两个例子的形式进行记录,作为备忘: 0x01 Chown的--reference特性 存在三个用户:root、yuns…

(C++)strlen(),strcmp(),strcpy(),strcat()用法
string.h中包含了许多用于字符数组的函数。使用前需要在程序开头加string.h©或cstring(C)头文件 strlen() 作用:得到字符数组第一个结束符\0前的字符的个数 #include<cstdio> #include<cstring>int main(){char str[50];gets(str);printf("…