神经网络可以看成一个端到端的黑盒,中间是隐藏层(可以很深),两边是输入与输出层,完整的神经网络学习过程如下:
卷积神经网络(CNN)是人工神经网络的一种,是多层感知机(MLP)的一个变种模型,它是从生物学概念中演化而来的。
Hubel和Wiesel早期对猫的视觉皮层的研究中得知在视觉皮层存在一种细胞的复杂分布,这些细胞对于外界的输入局部是很敏感的,它们被称为“感受野”(细胞),它们以某种方法来覆盖整个视觉域。这些细胞就像一些滤波器一样,够更好地挖掘出自然图像中的目标的空间关系信息。
视觉皮层存在两类相关的细胞,S细胞(Simple Cell)和C(Complex Cell)细胞。S细胞在自身的感受野内最大限度地对图像中类似边缘模式的刺激做出响应,而C细胞具有更大的感受野,它可以对图像中产生刺激的模式的空间位置进行精准地定位。
卷积神经网络已成为语音和图像识别的研究热点,80年代末,Yann LeCun就作为贝尔实验室的研究员提出了卷积网络技术,并展示如何使用它来大幅度提高手写识别能力。在图像识别领域,CNN已经成为一种高效的识别方法
CNN的应用广泛,包括图像分类,目标检测,目标识别,目标跟踪,文本检测和识别以及位置估计等。
CNN的基本概念:
局部感受野(local receptive fields):图像的空间联系是局部的,就像人通过局部的感受野去感受外界图像一样,每个神经元只感受局部的图像区域,然后在更高层,将这些感受不同局部的神经元综合起来就可以得到全局的信息了。
CNN中相邻层之间是部分连接,即某个神经单元的感知区域来自于上层的部分神经单元。这个与MLP多层感知机不同,MLP是全连接,某个神经单元的感知区域来自于上层的所有神经单元。
共享权重(shared weights):共享权重即参数共享,隐藏层的参数个数和隐藏层的神经元个数无关,只和滤波器的大小和滤波器种类的多少有关。也就是说,对于一个特征图,它的每一部分的卷积核的参数都是一样的。
左图是没有参数共享的情况,右图进行了参数共享
如果要提取不同的特征,就需要多个滤波器。每种滤波器的参数不一样,表示它提出输入图像的不同特征。这样每种滤波器进行卷积图像就得到对图像的不同特征的反映,我们称之为Feature Map;100种卷积核就有100个Feature Map,这100个Feature Map就组成了一层神经元。
池化(pooling)原理:根据图像局部相关的原理,图像某个邻域内只需要一个像素点就能表达整个区域的信息, 池化也称为混合、下采样,目的是减少参数量。分为最大池化,最小池化,平均池化。
CNN的网络结构如下:
光栅化(Rasterization):为了与传统的MLP(多层感知机)全连接,把上一层的所有Feature Map的每个像素依次展开,排成一列。
图像经过下采样后,得到的是一系列的特征图,而多层感知器接受的输入是一个向量,所以需要将这些特征图中的像素依次取出,排列成一个向量。
1990年,LeCun发表了一篇奠定现在CNN结构的重要文章,他们构建了一个叫做LeNet-5的多层前馈神经网络,并将其用于手写体识别。就像其他前馈神经网络,它也可以使用反向传播算法来训练。它之所以有效,是因为它能从原始图像学习到有效的特征,几乎不用对图像进行预处理。
LeNet5的第一层是卷积层
32*32
5*5
(作者定义)6
(作者定义)6
。(32-5+1)*(32-5+1)=28*28
(5*5+1)*6
或(5*5)*6+6
(5*5+1)*6*28*28
(5*5+1)
,总共6*28*28
个像素,所以总的计算量是(5*5+1)*6*28*28
(28*28)*6)
(5*5+1)*6*28*28
(28*28)*6
2*2
(作者定义)6
14*14
(1+1)*6
(1+1)*6
(2*2+1)*14*14*6
2*2
,要找到最大的采样点,需要比较3次,也就是3次计算,才能找到最大值(如果采样区域是3*3
,那么需要比较8次才能找到最大值),然后这个最大值再乘以权重w,加上偏置b,因此得到一个像素点的计算量是3+1+1=(2*2-1+1+1)=(2*2+1)
,总共14*14*6
个像素点,可得总共的计算量为(2*2+1)*14*14*6
(14*14)*6
(2*2+1)*[(14*14)*6]
(2*2+1)
,总共(14*14)*6
个像素点,故总的连接数为(2*2+1)*[(14*14)*6]
,可以看到,池化中计算量和连接数也是一样的(14*14)*6
5*5
(作者定义)16
(作者定义)16
(14-5+1)*(14-5+1)=10*10
(6*5*5+1)*16
6*5*5
的卷积做运算,输出通道是16,因此要有16个这样的卷积核,每个卷积核再加上一个偏置bias,所以参数量是(6*5*5+1)*16
,可参考神经网络之多维卷积的那些事(6*5*5+1)*16*10*10
(6*5*5+1)
,总的输出像素个数为16*10*10
,故总的计算量为(6*5*5+1)*16*10*10
,这里要注意:多维卷积计算后需要把每个卷积的计算结果相加,这个相加的结果才是一个像素点,这个计算就忽略不计了10*10*16
(6*5*5+1)*16*10*10
LeNet5的第四层是池化层(池化核大小2*2
,步长为2)
(10*10)*16
2*2
(作者定义)16
5*5
(1+1)*16
(2*2+1)*5*5*16
5*5*16
(2*2+1)*5*5*16
(5*5)*16
5*5
(作者定义)120
(作者定义)120
(5-5+1)*(5-5+1)=1*1
(16*5*5+1)*120
(16*5*5+1)*1*1*120
1*1*120
(16*5*5+1)*1*1*120
(1*1)*120
1*1
(作者定义)84
(作者定义)84
1*1
(120+1)*84
(全连接层)(120+1)*84
84
(120+1)*84
(全连接层)LeNet5的第7层是输出层
卷积层:设卷积核大小为k*k
,步长为1,输入特征图(输入图片)大小为n*n
,输入通道是a
,输出通道是b
(输出通道就是卷积核的种类数)
(n-k+1)*(n-k+1)=m*m
(a*k*k+1)*b
(a*k*k+1)*b*m*m
m*m*b
(a*k*k+1)*b*m*m
卷积步长为n的输出特征图大小计算方式可参考神经网络之多维卷积的那些事
池化层:设池化核大小为k*k
,步长是stride,输入特征图(输入图片)大小为n*n
,输入通道是a
,输出通道是a
(池化层输入通道和输出通道是样的)
(1+1)*a
(k*k+1)*(m*m*a)
m*m*a
(k*k+1)*(m*m*a)
import torch
from torch import nn,optim
import torchvision
import torch.nn.functional as F
#没参数可以用nn,也可以用F,有参数的只能用nn
class LeNet5(nn.Module):
def __init__(self):
super().__init__()
#定义卷定义层卷积层,1个输入通道,6个输出通道,5*5的filter,28+2+2=32
#左右、上下填充padding
#MNIST图像大小28,LeNet大小是32
self.conv1 = nn.Conv2d(1,6,5,padding=2)
#定义第二层卷积层
self.conv2 = nn.Conv2d(6,16,5)
#定义3个全连接层
self.fc1 = nn.Linear(16*5*5,120)
self.fc2 = nn.Linear(120,84)
self.fc3 = nn.Linear(84,10)
#前向传播
def forward(self,x):
#先卷积,再调用relue激活函数,然后再最大化池化
x=F.max_pool2d(F.relu(self.conv1(x)),(2,2))
x=F.max_pool2d(F.relu(self.conv2(x)),(2,2))
#num_flat_features=16*5*5
x=x.view(-1,self.num_flat_features(x))
#第一个全连接
x=F.relu(self.fc1(x))
x=F.relu(self.fc2(x))
x=self.fc3(x)
return x
def num_flat_features(self,x):
size=x.size()[1:]
num_features=1
for s in size:
num_features=num_features*s
return num_features
import torchvision.datasets as datasets
#import torchvision.transforms as transforms
from torchvision import transforms
from torch.utils.data import DataLoader
#超参数定义
#人为定义的参数是超参数,训练的是参数
EPOCH = 10 # 训练epoch次数
BATCH_SIZE = 64 # 批训练的数量
LR = 0.001 # 学习率
#首次执行download=True,下载数据集
#mnist存在的路径一定不要出现 -.?等非法字符
train_data=datasets.MNIST(root='./dataset',train=True,transform=transforms.ToTensor(),download=False)
test_data=datasets.MNIST(root='./dataset',train=False,transform=transforms.ToTensor(),download=False)
import matplotlib.pyplot as plt
%matplotlib inline
print('训练集大小',train_data.train_data.size())
print('训练集标签个数',train_data.train_labels.size())
plt.imshow(train_data.train_data[0].numpy(),cmap='gray')
plt.show()
输出:
训练集大小 torch.Size([60000, 28, 28])
训练集标签个数 torch.Size([60000])
#如果有dataloader 的话一般都是在dataset里,dataloader和dataset 这俩一般一起用的
#使用DataLoader进行分批
#shuffle=True是设置随机数种子
train_loader=DataLoader(dataset=train_data,batch_size=BATCH_SIZE,shuffle=True)
test_loader=DataLoader(dataset=test_data,batch_size=BATCH_SIZE,shuffle=True)
#创建model
model=LeNet5()
#定义损失函数
criterion=nn.CrossEntropyLoss()
#定义优化器
optimizer=optim.Adam(model.parameters(),lr=1e-3)
#device cuda:0是指使用第一个gpu
device=torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
# device=torch.device('cpu')
print(device,type(device))
model.to(device)
# 训练
for epoch in range(EPOCH):
for i,data in enumerate(train_loader):
inputs,labels=data
#可能会使用GPU,注意掉就不会在gpu里运行了
inputs,labels=inputs.to(device),labels.to(device)
# print(type(inputs),inputs.size(),'\n',inputs)
#前向传播
outpus=model(inputs)
#计算损失函数
loss=criterion(outpus,labels)
#清空上一轮梯度
optimizer.zero_grad()
#反向传播
loss.backward()
#参数更新
optimizer.step()
print('epoch {} loss:{:.4f}'.format(epoch+1,loss.item()))
输出:
epoch 1 loss:0.1051
epoch 2 loss:0.0102
epoch 3 loss:0.1055
epoch 4 loss:0.0070
epoch 5 loss:0.2846
epoch 6 loss:0.1184
epoch 7 loss:0.0104
epoch 8 loss:0.0014
epoch 9 loss:0.0141
epoch 10 loss:0.0126
#保存训练模型
torch.save(model,'dataset/mnist_lenet.pt')
model=torch.load('dataset/mnist_lenet.pt')
#测试
"""
训练完train_datasets之后,model要来测试样本了。
在model(test_datasets)之前,需要加上model.eval().
否则的话,有输入数据,即使不训练,它也会改变权值。
"""
model.eval()
correct=0
total=0
for data in test_loader:
images,labels=data
images,labels=images.to(device),labels.to(device)
#前向传播 model(images) 和 model.forward(x)一样的
out=model(images)
"""
首先得到每行最大值所在的索引(比图第7个分类是最大值,则索引是7)
然后,与真实结果比较(如果一个样本是图片7,那么该labels是7),如果相等,则加和
"""
_,predicted=torch.max(out.data,1)
total=total+labels.size(0)
correct=correct+(predicted==labels).sum().item()
#输出测试的准确率
print('10000张测试图像 准确率:{:.4f}%'.format(100*correct/total))
输出:
10000张测试图像 准确率:98.9400%
#记住,输入源一定要转化为torch.FloatTensor,否则无法预测,并且一定要加model.eval(),否则会更新权重
def predict_Result(img):
"""
预测结果,返回预测的值
img,numpy类型,二值图像
"""
model.eval()
img=torch.from_numpy(img).type(torch.FloatTensor).unsqueeze(0).unsqueeze(1)
img=img.to(device)
out=model(img)
_,predicted=torch.max(out.data,1)
return predicted.item()
img2=train_data.train_data[167].numpy()
plt.imshow(img2,cmap='gray')
print('预测结果:',predict_Result(img2))
输出: