一、CNN卷积神经网络原理
卷积神经网络(Convolutional Neural Networks / CNNs / ConvNets)与普通神经网络非常相似,它们都由具有可学习的权重和偏置常量(biases)的神经元组成。每个神经元都接收一些输入,并做一些点积计算,输出是每个分类的分数。
一个卷积神经网络由很多层组成,它们的输入是三维的,输出也是三维的,有的层有参数,有的层不需要参数。

** CNN主要特点**:减少权值,局部连接,权值共享
1. CNN的一般结构
- 输入层:用于数据的输入
- 卷积层:卷积神经网路中每层卷积层由若干卷积单元组成,每个卷积单元的参数都是通过反向传播算法优化得到的。卷积运算的目的是提取输入的不同特征,第一层卷积层可能只能提取一些低级的特征如边缘、线条和角等层级,更多层的网络能从低级特征中迭代提取更复杂的特征。
- 激励层:由于卷积也是一种线性运算,因此需要增加非线性映射
- 池化层:压缩数据和参数的量,减少过拟合
- 全连接层:通常在CNN的尾部进行重新拟合,减少特征信息的损失
- 输出层:用于输出结果
(1)输入层
CNN输入层的输入格式保留了图片本身的结构。
卷积神经网络利用输入是图片的特点,把神经元设计成三个维度 : width, height, depth(注意这个depth不是神经网络的深度,而是用来描述神经元的) 。比如输入的图片大小是 32 × 32 × 3 (rgb),那么输入神经元就也具有 32×32×3 的维度。
(2)卷积层
用它来进行特征提取。这里需要特殊说明的是,对于卷积层而言,有一个重要的概念就是深度(depth),即为图像的通道数,如RGB的深度就是三层的。而卷积层的感受野(receptive,又称过滤器(filter),神经元(neuron),卷积核(kernel))的深度必须与输入图像的深度相同。但filter的个数是可以根据需要让程序员自己定义。
基本概念
- 感受视野:
即感受上一层的部分特征。在卷积神经网络中,隐藏层中的神经元的感受视野比较小,只能看到上一次的部分特征,上一层的其他特征可以通过平移感受视野来得到同一层的其他神经元。 - 卷积核
感受视野中的权重矩阵 - 步长
感受视野对输入的扫描间隔称为步长(stride) - 边界扩充
当步长比较大时(stride>1),为了扫描到边缘的一些特征,感受视野可能会“出界”,这时需要对边界扩充(pad) - 特征映射图(feature map)
通过一个带有卷积核的感受视野 扫描生成的下一层神经元矩阵

(3)激励层
激励函数:使用relu作为激活函数
函数图像

为什么不用sigmoid函数?
因为sigmoid的函数具有饱和性,当输入的x过大或过小时,函数的输出会非常接近+1与-1,在这里斜率会非常小,那么在训练时引用梯度下降时,其饱和性会使梯度非常小,严重降低了网络的训练速度。进行卷积操作后,会产生一个特征图(Feature Map),设其尺寸为O,filter尺寸为K,图像宽度为W,高度为H(图中的W用H替换即为计算后的高度,这里为了方便,简写为W),步幅为S,补零填充(Zero Padding)为P,(这里补零填充的意思为在原图像周围补几圈0,这是为了尽可能多地保留原始输入内容的信息。),则有:


(4)池化层
池化(pool)即下采样(downsamples),目的是为了减少特征图。池化操作对每个深度切片独立,规模一般为 2*2,相对于卷积层进行卷积运算,池化层进行的运算一般有以下几种:
- 最大池化(Max Pooling)。取4个点的最大值。这是最常用的池化方法。
- 均值池化(Mean Pooling)。取4个点的均值。
- 高斯池化。借鉴高斯模糊的方法。不常用。
- 可训练池化。训练函数 ff ,接受4个点为输入,出入1个点。不常用。
最常见的池化层是规模为2*2, 步幅为2,对输入的每个深度切片进行下采样。每个MAX操作对四个数进行,如下图所示:

池化操作将保存深度大小不变。
如果池化层的输入单元大小不是二的整数倍,一般采取边缘补零(zero-padding)的方式补成2的倍数,然后再池化。
(5)全连接层
连接所有的特征,将输出值送给分类器。
全连接层和卷积层可以相互转换:
- 对于任意一个卷积层,要把它变成全连接层只需要把权重变成一个巨大的矩阵,其中大部分都是0 除了一些特定区块(因为局部感知),而且好多区块的权值还相同(由于权重共享)。
- 相反地,对于任何一个全连接层也可以变为卷积层。我们把 filter size 正好设置为整个输入层大小即可
(6)输出层
输出目标结果
二、使用CNN模型对MINIST手写字符数据集进行分类
环境
Win10,python3.6,spyder,pytorch
代码结构
- 导入包
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms- 设备配置
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')- 定义设置超参数
其中num_epochs为训练迭代次数;batch_size为每次训练的批数目;learning_rating为学习率
num_epochs = 5
num_classes = 10
batch_size = 100
learning_rate = 0.001- 加载MNIST数据集
调用pytorch的torchversion.datasets,通过datasets.MNIST方式来下载调用这些数据集 - 定义两层卷积神经网络模型
Conv2d为二维卷积层,因为输入的图片是单通道,所以第一个卷积层的输入通道为1,输出通道为16,滤波器为5x5;第二个卷积层的输入通道为16,输出通道为32,滤波器同样是5x5。
# 定义两层卷积神经网络模型
class ConvNet(nn.Module):
def __init__(self, num_classes=10):
super(ConvNet, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(1, 16, kernel_size=5, stride=1, padding=2), #1表示通道数,16表示卷积个数
nn.BatchNorm2d(16), #归一化
nn.ReLU(), #非线性激活
nn.MaxPool2d(kernel_size=2, stride=2)) #池化
self.layer2 = nn.Sequential(
nn.Conv2d(16, 32, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2))
self.fc = nn.Linear(7*7*32, num_classes)
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = out.reshape(out.size(0), -1)
out = self.fc(out)
return out
model = ConvNet(num_classes).to(device)- 定义损失器和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)- 训练模型
total_step = len(train_loader)
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
images = images.to(device)
labels = (device)(1)前向传播
outputs = model(images)
loss = criterion(outputs, labels)(2)后向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()(3)打印每个Epoch的step和loss
...
print ('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'
.format(epoch+1, num_epochs, i+1, total_step, loss.item()))- 测试模型
model.eval() # 在eval模式中,pytorch会自动把BN和DropOut固定住,不会取平均,而是用训练好的值- 输出最后的accuracy
...
print('Test Accuracy of the model on the 10000 test images: {} %'.format(100 * correct / total))- 保存模型
torch.save(model.state_dict(), 'model.ckpt')运行结果

经过每个epoch的训练,平均损失函数会依次减小,精度会依次提高,精确度最终达到99.25%
三、使用CNN模型对CIFAR-10图像数据集进行分类
环境
Win10,python3.6,spyder,pytorch
代码实现
- tips: 官网数据集下载速度太慢,这里附上一个下载较快的资源网址http://www.cs.toronto.edu/~kriz/cifar.html
- 实现思路
- 加载包
- 设备配置
- 定义设置超参数
- 加载数据集,预处理
- 定义网络结构
- 定义损失函数和优化器
- 训练模型
- 测试模型得到精确度
导入包
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torch.utils.data as Data
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import os
import numpy as np
import time设备配置
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # the device used to calc定义设置超参数
EPOCH为训练迭代次数;BATCH_SIZE为为每次训练的批数目;LR为学习率
EPOCH = 10
BATCH_SIZE = 100
LR = 0.001加载数据集
- 预处理
Compose的意思是将多个transform组合在一起用,ToTensor 将像素转化为0-1的数字,Normalize将值正则化变为 -1 – 1
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]
)- 下载数据集,训练集:需要训练;测试集:不需要训练
由于我们已经下载了数据集,所以train_data和test_data中的download设为False
train_data = torchvision.datasets.CIFAR10(
root='./data',
train=True,
download=False,
transform=transform
)
train_loader = Data.DataLoader(
train_data,
batch_size=BATCH_SIZE,
shuffle=True,
# num_workers=2 # ready to be commented(windows)
)
test_data = torchvision.datasets.CIFAR10(
root='./data',
train=False,
download=False,
transform=transform,
)
test_loader = Data.DataLoader(
test_data,
batch_size=BATCH_SIZE,
shuffle=False,
# num_workers=2
)- 指定十个类别的标签,有的数据集很大的回加载相应的标签文件
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'truck', 'ship')定义CNN网络
class CNN(nn.Module):第一个卷积层的输入通道为3(RGB),输出通道为6,滤波器为5x5;第二个卷积层的输入通道为6,输出通道为16,滤波器同样是5x5
三层全连接层
def __init__(self):
super(CNN, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16*5*5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10前向传播
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 16*5*5) # reshape to 16*5*5 to fc
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
output = self.fc3(x)
return output定义损失函数和优化器
optimizer = optim.Adam(cnn.parameters())
loss_func = nn.CrossEntropyLoss()定义训练函数
train_loader 一次性加载了sample中全部的样本数据,每次以batch_size为一组循环
def train():
global losses
for epoch in range(EPOCH):
running_loss = 0.0
for step, (inputs, labels) in enumerate(train_loader, 0):
# inputs, labels = data[0].to(device), data[1].to(device)
inputs = (device) # 2
labels = (device)
optimizer.zero_grad()
output = cnn(inputs)
loss = loss_func(output, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
if step % 100 == 99:
loss_num = running_loss / 100
losses.append(loss_num)
print('Epoch: %d, Step: %d, Loss: %.3f' %(epoch+1, step+1, loss_num))
running_loss = 0.0
save_losses(losses)其中使用了一个自定义的save_loss函数用来保存loss并绘制相应loss图像
losses = []
def save_loss(losses):
t = np.arange(len(losses))
plt.plot(t, losses)
plt.savefig('loss.png')
plt.show()训练模型
训练并保存模型参数到指定目录
start = time.time()
model_save_path = './data/cifar-10-batches-py/model/'
model_path = model_save_path + 'model.pth'
...
train()
torch.save(state, model_path)训练结束输出Training Finished!;模型参数保存成功输出You hava saved the model successfully!;如果模型参数保存目录已存在则输出Loaded model parameters from disk successfully! 最后输出训练花费的时间
测试训练集和测试集的准确度
# test and get accuracy
correct_time = time.time()
classes_correct = [0 for i in range(10)]
classes_total = [0 for i in range(10)]
pass_total = 0
train_total = 50000
test_total = 10000
# test train_data
print("==================Test Train results====================")
with torch.no_grad():
for (images, labels) in train_loader:
# images, labels = data[0].to(device), data[1].to(device)
images = images.to(device) # 3
labels = (device)
output = cnn(images)
_, prediction = torch.max(output, 1)
c = (prediction == labels).squeeze()
for i in range(BATCH_SIZE):
label = labels[i]
classes_correct[label] += c[i].item()
classes_total[label] += 1
accNow = []
for i in range(len(classes)):
print("%3d of %4d passed ---> Accuracy of %5s is %2d %%" % (classes_correct[i], classes_total[i], classes[i],round(100*classes_correct[i]/classes_total[i])))
pass_total += classes_correct[i]
accNow.append(str(round(100*classes_correct[i]/classes_total[i])) + '%')
accNow.append(str(round(100*pass_total/train_total)) + '%')
print("%3d of %4d passed ---> Total accuracy is %2d %%" % (pass_total, train_total, round(100*pass_total/train_total)))
print()
#测试逻辑集同上
...输出测试时间
now = time.time()
print('The test takes:', now-correct_time,'s')运行结果


loss图像如下

完成训练,输出相应信息

测试精确度

张瑜函 中山大学人工智能作业
















