卷积神经网络概念及使用 PyTorch 简单实现

卷积神经网络

  卷积神经网络(CNN)是深度学习的代表算法之一 。具有表征学习能力,能够按其阶层结构对输入信息进行平移不变分类,因此也被称为“平移不变人工神经网络”随着深度学习理论的提出和数值计算设备的改进,卷积神经网络得到了快速发展,并被应用于计算机视觉自然语言处理等领域

  卷积是通过两个函数 f,g 生成第三个函数的一种数学算子,表征函数 f 与 g 经过翻转和平移的重叠部分的面积。数学定义公式:

  事实上,在卷积网络上使用的离散卷积,也就是不连续的,它是一种运算方式,也就是按照卷积核,将输入对应位置的数据进行加权和运算,接下来结合卷积核的概念,就会很好理解了。

  卷积神经网络利用卷积结构减少需要学习的参数量,从而提高反向传播算法的训练效率。在卷积神经网络中,第一个卷积层会直接接受图像像素级的输入,每一个卷积操作只处理一小块图像,进行卷积操作后传递到后面的网络,每一层卷积都会提取数据中最有效的特征。这种方法可以提取到图像中最基础的特征,比如不同方向的拐角或者边,而后进行组合和抽象成更高阶的特征,因此卷积神经网络对图像缩放、平移和旋转具有不变性。

卷积神经网络的要点就是卷积层中的局部连接、权值共享、和池化层中降采样。局部连接、权值共享和降采样降低了参数量,使得训练复杂度大大降低,并减轻了过拟合的风险。同时还赋予了卷积神经网络对平移、形变、尺度的某种程度的不变性,提高了模型的泛化能力

 

 卷积神经网络最重要的两个知识点就是卷积核卷积神经网络的结构

  • 卷积核
    • 卷积核定义
    • 卷积操作
    • 深度
    • 步幅
    • 零填充
  • 卷积神经网络的结构
    • 输入层 INPUT
    • 卷积层 CONV
    • 激活函数层 RELU
    • 池化层 POOL
    • 全连接层 FC

 

卷积核

​  卷积核是整个网络的核心,训练 CNN 的过程就是不断更新卷积核参数直到最优的过程。

  卷积核的定义:对于输入图像中的一部分区域,进行加权平均的处理,其中这个过程的权重,由一个函数定义,这个函数就是卷积核。

如下图彩色图像有RGB三个色值通道,分别表示红、绿、蓝,每个通道内的像素可以用一个像下图右边的二维数组表示,数值代表0-255之间的像素值。假设一张900*600的彩色的图片,计算机里面可以用 (900*600*3)的数组表示。

卷积过程

  卷积过程是基于一个小矩阵,也就是卷积核,在上面所说的每层像素矩阵上不断按步长扫过去的,扫到数与卷积核对应位置的数相乘,然后求总和,每扫一次,得到一个值,全部扫完则生成一个新的矩阵。如下图

  卷积核如何设置可以参考卷积神经网络的卷积核大小、个数。卷积层数一般取(3,3)的小矩阵,卷积核里面每个值就是我们需要寻找(训练)的神经元参数(权重),开始会随机有个初始值,当训练网络时,网络会通过后向传播不断更新这些参数值,直到寻找到最佳的参数值。“最佳”、需要通过损失函数去评估。

  卷积操作相当于特征提取,卷积核相当于一个过滤器,提取我们需要的特征。

如下图卷积操作,从左上角扫到右下角,最终得到右边的特征图谱。

 

  图解:一个过滤器(红色边框)在输入图像上移动(卷积操作)以生成特征映射。在同一张图像上,另一个过滤器(绿色边框)的卷积生成了不同的特征图。需要注意到,卷积操作捕获原始图像中的局部依赖关系很重要。还要注意这两个不同的过滤器如何从同一张原始图像得到不同的特征图。请记住,以上图像和两个过滤器只是数值矩阵。
  实际上,卷积神经网络在训练过程中会自己学习这些过滤器的值(尽管在训练过程之前我们仍需要指定诸如过滤器数目、大小,网络框架等参数)。过滤器数目越多,提取的图像特征就越多,识别新图像时效果就会越好。
 
特征映射(卷积特征)的大小由执行卷积步骤之前需要决定的三个参数控制:
  • 深度:深度对应于我们用于卷积运算的过滤器数量。在图7所示的网络中,我们使用三个不同的过滤器对初始的船图像进行卷积,从而生成三个不同的特征图。可以将这三个特征地图视为堆叠的二维矩阵,因此,特征映射的“深度”为3。
  • 步幅:步幅是在输入矩阵上移动一次过滤器矩阵的像素数量。当步幅为1时,我们一次将过滤器移动1个像素。当步幅为2时,过滤器每次移动2个像素。步幅越大,生成的特征映射越小。有横行和纵向两个方向
  • 零填充:有时,将输入矩阵边界用零来填充会很方便,这样我们可以将过滤器应用于输入图像矩阵的边界元素。零填充一个很好的特性是它允许我们控制特征映射的大小。添加零填充也称为宽卷积,而不使用零填充是为窄卷积。

零填充(padding)

  卷积操作之后维度会变少,得到的矩阵会比原来矩阵小,这样不好计算,所以需要Padding,在每次卷积操作之前,在原矩阵外边补包一层0,可以只在横向补,或只在纵向补,或者四周都补0,从而使得卷积后输出的图像跟输入图像在尺寸上一致。

比如:需要做一个5*5的原始矩阵的卷积,用一个3*3卷积核来扫,扫出来结果的矩阵应该是:3*3的矩阵,变小了。

卷积前加 Padding 操作补一圈0,即300*300矩阵外面周围加一圈“0”,这样的300*300就变成了302*302的矩阵,再进行卷积出来就是300*300 ,尺寸和原图一样。

 

卷积神经网络结构

  CNN 一般来说分为五个部分:输入层卷积层激活函数层池化层全连接层

  不过注意需要注的点是,对于大部分卷积网络,都会交替地用到中间地四层结构,也就是呈现出一种 卷积层——激活函数层——池化层——卷积层——激活函数层——池化层…地交替结构,当然,对于一些新出现地卷积网络,连池化层都省去了,以上五层结构只是一般会出现的层次。

 

输入层

  整个网络的输入,一般是一张图象地像素矩阵,在上面的图中,可以看到输入是一个立体的结构,这是因为一般的图像都会有一个深度的概念,就像RGB的彩色图像,就是 a*b*c 的形式,其中前两维指定的是图像的长和宽,第三维则是深度,彩色RGB的深度是3,而黑白图像的深度是 1

 

卷积层

  这一层的主要部分就是进行卷积操作,前面已经介绍了卷积核的概念,卷积层实际上就是实现了这个卷积核的计算过程,在这一层中,可能会见到以下的几种关键词:

  • 滤波器 Filter:实现前面定义的卷积核的神经元。
  • 步长 Stride
  • 填充 Padding
  • 深度 Depth:这里的深度不是指图像,而是指某一层中神经元(滤波器)的个数,不同的 Filter 重点处理的特征是不同的我们想要得到不同的这些特征图,所以设置了多个 Filter ,这样每个 Filter 处理得到一张 Feature Map ,多个 Filter 就会得到多个Feature Map, 将这些Feature Map 叠在一起就是输出的立体,可以看到,Filter 与 Feature Map的数量是一样的,这个数量,就是 深度。

 在PyTorch中, 类nn.Conv2d()是卷积核模块。卷积核及其调用例子如下:

# 调用形式
nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0,dilation=1,groups=1, bias=True)
'''
  nn.Conv2d中参数含义:
  in_channels表示输入数据体的深度;
  out_channels表示输出数据体的深度;
  kernel_size 表示卷积核的大小;
  stride表示滑动的步长;
  padding表示边界0填充的个数;
  dilation表示输入数据体的空间间隔;
  groups 表示输入数据体和输出数据体在深度上的关联;
  bias 表示偏置。
'''

# With square kernels and equal stride
m = nn.Conv2d(16, 33, 3, stride=2)
# non-square kernels and unequal stride and with padding
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2))
# non-square kernels and unequal stride and with padding and dilation
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2), dilation=(3, 1))
nput = autograd.Variable(torch.randn(20, 16, 50, 100))
output = m(input)

 

激励函数层

  实际中,使用激活函数处理往往是与之前的卷积层绑定在一起,这个作用其实也就是是激活函数的作用,去线性化,在卷积网络中,一般使用的激励函数是 ReLu 函数,注意大多数情况下都不会使用 Sigmoid 函数处理。

 

池化层

  作用在 Feature Map 上,相当于对输入矩阵的尺寸进一步浓缩,也就是进一步提取特征。卷积操作后我们提取了很多特征信息,相邻区域有相似特征信息,可以相互替代的,如果全部保留这些特征信息就会有信息冗余,增加了计算难度,这时候池化就相当于降维操作。池化是在一个小矩阵区域内,取该区域的最大值或平均值来代替该区域,该小矩阵的大小可以在搭建网络的时候自己设置。小矩阵也是从左上角扫到右下角。如下图

 

池化层有以下几个功能:

  1. 对 Feature Map 又进行一次特征提取,这也是减小数据量的操作
  2. 获取更抽象的特征,防止过拟合,提高泛化性
  3. 经过这个处理,对输入的微小变化有更大的容忍,也就是说如果数据有一些噪音,那么经过这个特征提取的过程,就一定程度上减小了噪音的影响。

最后一点要注意的是,池化层并非是卷积网络所必需的。一些新的CNN网络设计时候并没有使用池化层。

在PyTorch中,池化层是包括在类nn.MaxPool2d和nn.AvgPoo2d。下面介绍一下nn.MaxPool2d及其调用例子。其调用如下

nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1,return_indices=False,ceil_mode=False)
'''
    nn.MaxPool2d中各个参数的含义:
    kernel_size, stride,padding, dilation在nn.Conv2d中已经解释过。
    return_indices表示是否返回最大值所处的下标;
    ceil_model表示使用方格代替层结构
'''

# pool of square window of size=3, stride=2
m = nn.MaxPool2d(3, stride=2)
# pool of non-square window
m = nn.MaxPool2d((3, 2), stride=(2, 1))
input = autograd.Variable(torch.randn(20, 16, 50, 32))
output = m(input)

 

全连接层

  在一开始的结构图中可以看出, CNN网络还有一个特点 : 可能有多轮 卷积层 和池化层的处理,这时候,模型已经将图像输入处理成了信息含量更高的特征,为了实现分类的任务(一般是分类,当然也会有其他的任务),需要使用全连接层来完成分类任务。

  对n-1层和n层而言,n-1层的任意一个节点,都和第n层所有节点有连接。即第n层的每个节点在进行计算的时候,激活函数的输入是n-1层所有节点的加权。像下面的中间层就是全连接方式。

 

 

使用PyTorch实现卷积神经网络

  使用PyTorch实现一个简单的卷积神经网络,使用的数据集是 MNIST,预期可以达到 97.05%左右的准确率。该神经网络由2个卷积层和3个全连接层构建,通过这个例子可以掌握设计卷积神经网络的特征以及参数的配置。

  PyTorch 已经实现了专门为神经网络设计的模块化接口 torch.nn,nn构建于 Autograd之上,可用来定义和运行神经网络。

# coding=utf-8
# 配置库
import torch
from torch import nn, optim
import torch.nn.functional as F
from torch.autograd import Variable
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision import datasets

# 配置参数
torch.manual_seed(1)  # 设置随机数种子,确保结果可重复  
batch_size = 128  # 批处理大小
learning_rate = 1e-2  # 学习率
num_epoches = 10  # 训练次数 

# ---------------------- 加载MINSIT数据 ----------------------
# 下载训练集 MNIST 手写数字训练集
train_dataset = datasets.MNIST(
    root='./data',  # 数据保持的位置
    train=True,  # 训练集 
    transform=transforms.ToTensor(),  # 一个取值范围是[0,255]的PIL.Image
    # 转化为取值范围是[0,1.0]的torch.FloadTensor
    download=True)  # 下载数据

test_dataset = datasets.MNIST(
    root='./data',
    train=False,  # 测试集
    transform=transforms.ToTensor())


# ---------------------- 数据的批处理 ----------------------
# 数据的批处理,尺寸大小为batch_size, 
# 在训练集中,shuffle 必须设置为True, 表示次序是随机的
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)


# ---------------------- 创建CNN模型 ----------------------
# 一个类来建立CNN模型.这个CNN整体流程是:
# 卷积(Conv2d) -> 激励函数(ReLU) -> 池化, 向下采样(MaxPooling) -> 再来一遍 -> 展开多维的卷积成的特征图 -> 接入全连接层(Linear) -> 输出。

# 定义卷积神经网络模型
class Cnn(nn.Module):
    def __init__(self, in_dim, n_class):  # 1x28x28
        super(Cnn, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(in_dim, 6, 3, stride=1, padding=1),  # 28 x 28 
            nn.ReLU(True),
            nn.MaxPool2d(2, 2),  # 14 x 14
            nn.Conv2d(6, 16, 5, stride=1, padding=0),  # 16 x 10 x 10
            nn.ReLU(True), nn.MaxPool2d(2, 2))  # 16x5x5

        self.fc = nn.Sequential(
            nn.Linear(400, 120),  # 400 = 16 x 5 x 5 
            nn.Linear(120, 84),
            nn.Linear(84, n_class))

    def forward(self, x):
        out = self.conv(x)
        out = out.view(out.size(0), 400)  # 400 = 16 x 5 x 5  
        out = self.fc(out)
        return out


model = Cnn(1, 10)  # 图片大小是28x28, 10是数据的种类
# 打印模型,呈现网络结构
print(model)


# ---------------------- 训练 ----------------------
# 将img, label都用Variable包起来, 然后放入model中计算out, 最后再计算less和正确率.

# 定义loss和optimizer
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)

# 开始训练
for epoch in range(num_epoches):
    running_loss = 0.0
    running_acc = 0.0
    for i, data in enumerate(train_loader, 1):  # 批处理
        img, label = data
        img = Variable(img)
        label = Variable(label)
        # 前向传播 
        out = model(img)
        loss = criterion(out, label)  # loss 
        running_loss += loss.data[0] * label.size(0)  # total loss , 由于loss 是batch 取均值的,需要把batch size 乘回去
        _, pred = torch.max(out, 1)  # 预测结果
        num_correct = (pred == label).sum()  # 正确结果的num
        # accuracy = (pred == label).float().mean() #正确率
        running_acc += num_correct.data[0]  # 正确结果的总数
        # 后向传播
        optimizer.zero_grad()  # 梯度清零,以免影响其他batch
        loss.backward()  # 后向传播,计算梯度
        optimizer.step()  # 利用梯度更新 W ,b参数

    # 打印一个循环后,训练集合上的loss 和正确率
    print('Train {} epoch, Loss: {:.6f}, Acc: {:.6f}'.format(
        epoch + 1, running_loss / (len(train_dataset)), running_acc / (len(
            train_dataset))))


# ---------------------- 在测试集测试识别率 ----------------------
# 模型测试, 
model.eval()  # 由于训练和测试 BatchNorm, Dropout配置不同,需要说明是否模型测试
eval_loss = 0
eval_acc = 0
for data in test_loader:  # test set 批处理
    img, label = data

    img = Variable(img, volatile=True)  # volatile 确定你是否不调用.backward(), 测试中不需要
    label = Variable(label, volatile=True)
    out = model(img)  # 前向算法 
    loss = criterion(out, label)  # 计算 loss
    eval_loss += loss.data[0] * label.size(0)  # total loss
    _, pred = torch.max(out, 1)  # 预测结果
    num_correct = (pred == label).sum()  # 正确结果
    eval_acc += num_correct.data[0]  # 正确结果总数

print('Test Loss: {:.6f}, Acc: {:.6f}'.format(eval_loss / (len(
    test_dataset)), eval_acc * 1.0 / (len(test_dataset))))

'''
最后的训练和测试上loss和识别率分别是:
Train 1 epoch, Loss: 2.226007, Acc: 0.320517
Train 2 epoch, Loss: 0.736581, Acc: 0.803717
Train 3 epoch, Loss: 0.329458, Acc: 0.901117
Train 4 epoch, Loss: 0.252310, Acc: 0.923550
Train 5 epoch, Loss: 0.201800, Acc: 0.939000
Train 6 epoch, Loss: 0.167249, Acc: 0.949550
Train 7 epoch, Loss: 0.145517, Acc: 0.955617
Train 8 epoch, Loss: 0.128391, Acc: 0.960817
Train 9 epoch, Loss: 0.117047, Acc: 0.964567
Train 10 epoch, Loss: 0.108246, Acc: 0.966550
Test
Loss: 0.094209, Acc: 0.970500
'''

 

 

 

 

参考:http://www.sohu.com/a/241338315_787107

参考:https://blog.csdn.net/sinat_34328764/article/details/84192303

参考:https://www.cnblogs.com/wmr95/articles/7814892.html

 

 

            

 

 

 

 

 

posted @ 2019-09-17 11:08  ''竹先森゜  阅读(4886)  评论(0编辑  收藏  举报