卷积神经网络

上一篇我们详细讲解了人工神经网络以及DNN的原理。CNN主要应用在图像处理方面。这一讲我们将详细讲解卷积神经网络CNN的原理以及在深度学习框架pytorch上的实现。

在讲CNN之前我们需要了解这么几个问题? 
①人工神经网络能用到计算机视觉上吗? 
答:能 
②那么为什么还需要卷积神经网络? 
答:上一讲我们讲到人工神经网络中,每一层都是全连接,就是相邻的层之间,神经元必须两两之间彼此连接,而图像数据维度很高,这就导致神经网络中神经元个数很多,那么参数w的个数就会很多,神经网络的目的就是学习到参数w,这么多的w会很耗费计算机的运算资源,而且很容易产生过拟合。所以有时候参数越多并不一定是好事。 
③卷积神经网络和人工神经网络的差异在哪? 
答:上一讲我们说了人工神经网络的结果基本上是全连接,就是每两层之间都是wx+b然后在激活函数做个非线性变化。在卷积神经网络中,我们会多出一个卷积(convolution)层。

卷积神经网络的层级结构: 
保持了层级网络结构。 
不同层次有不同形式(运算)与功能 
这里写图片描述

主要是一下层次: 
数据输入层/ Input layer 
卷积计算层/ Conv layer 
Relu激活层/ Relu layer 
池化层/ Pooling layer 
全连接层/ FC layer 
下面详细讲下每一层内部原理

数据输入层:需要对数据进行预处理,常见有三种数据处理方式 
1)去均值:把输入数据各个维度都中心化到0 
2)归一化:幅度归一化到同一的范围 
3)PCA:用PCA降维

卷积计算层:这一层极大降低了人工神经网络中的参数个数 
这里写图片描述

上图左边是一个3个通道((RGB)的32*32的图像,假设我们取一个3*3的滑动窗口,就是每个滑动窗口中有9个像素点,我们可以做这样的比喻:右图有五个小孩子(神经元),每个孩子有一组不同的价值观(一组权重) ,每个孩子都以上面滑动窗口每次滑动2个步长来看这张图像,每个滑动窗口内都能根据他的权重得出这个滑动窗口的值,并且每次在滑动时,每个孩子的权重保持不变,这样这个五个孩子就把这张图片卷积成五张不同的图片信息。 
以上的说明中涉及到几个概念,我们得详细的解释下: 
深度/depth:就是上面说的孩子个数(有几个不同参数组数,然后卷积成几个不同的图像信息) 
步长/stride:滑动窗口在滑动过程中,每次滑动几个步长。 
填充值/zero-padding:在滑动过程中不一定能正好从左边滑到右边,滑到右边时,很有可能滑到外面,这样我们可以在外层再添加一圈0,使得正好滑到右边。 
这里写图片描述 
上面这张图很好的说明整个卷积过程,滑动串口大小取3*3,9个像素点,并且在原始的图像的进行了zero-padding,一组孩子的权重矩阵看图像做左上方的一个滑动窗口,计算出这个滑动窗口结果为-8.

参数共享机制:假设每个神经元连接数据窗口的权重是固定的。 
固定每个神经元连接权重,可以看做模板:每个神经元只关注一个特征。 
需要估算的权重个数大大减少:AlexNet 1亿=>3.5w 
一组固定的权重和不同的窗口做内积:卷积

激活层:将卷积层输出的结果做非线性映射。 
激活函数通常有这些:Sigmoid,Tanh(双曲正切) ,ReLu,Leaky Relu,ELU,Maxout 
这里写图片描述

激活层(实际经验): 
1)CNN尽量不用sigmoid函数!因为典型CNN神经网络都是一层一层叠加,这样在反向更新参数时容易产生梯度消失现象!双曲正切同样有这个问题。 
2)首先试试Relu,Relu函数在数据小于0时,梯度为0直接将数据过滤掉,只有在数据大于0时才能通过且由直线斜率可以看出可以学习的很快。 
3)但是有时候,在用Relu时,数据全小于0,这样就行不通了,这个时候可以用Leaky Relu或Maxout 
4)某些情况下tanh效果不错,但是很少。

池化层/Pooling layer 
夹在连续的卷积层中间 
压缩数据和参数数量,减小过拟合 
这里写图片描述 
通常有两种pooling方式 
Max Pooling:取窗口内最大值作为pooling的结果值 
average Pooling:取窗口内平均值作为Pooling的结果值 
这里写图片描述

全连接层: 
两层之间所有神经元都有权重连接 
通常全连接层在卷积神经网络尾部

综上我们可以知道一般CNN的结构依次是: 
Input–>[[Conv->Relu]*N->Pool?]*M–>[FC->Relu]*k–>FC 
*N或者*M,*K:表示有连续数个这样的结构 
?:表示可能有可能无这个结构

CNN训练算法: 
1)同一般的机器学习算法类似,定义loassFunction,衡量和实际结果之间的差距。 
2)找到最小化损失函数的w和b,CNN中用的是SGD 
3)SGD需要计算W和b的偏导 
4)BP算法用来计算偏导(链式求导法)

下面说说CNN的优缺点: 
优点:1)共享卷积核,对处理高维数据无压力。 
2)无需手动选取特征,训练好权重,既得特征 
3)深层次的网络抽取图像信息丰富,表达效果好。 
缺点:1)需要调参,需要大量样本,训练最好用GPU 
2)物理意义不明确

几个经典的CNN模型 
详细说说LeNet和resNet模型: 
LeNet神经网络: 
这里写图片描述

这里写图片描述 
我们知道神经网络越深越难训练,因为越深梯度消失的越严重。但是在resNet神经网络中,设计一种办法解决这个问题,使得神经网络的层数更深。 
这里写图片描述

在正向传播时每一层得出的结果为上一层累积结果加上前几层的输入数据x,因为是求和,这样在反向更新参数时,会有两条路径更新参数,减少了梯度消失的现象。

下面在pytorch上定义一个类似leNet神经网络

#coding=utf-8
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable

class Net(nn.Module):
    #定义Net的初始化函数,这个函数定义了该神经网络的基本结构
    def __init__(self):
        super(Net, self).__init__() #复制并使用Net的父类的初始化方法,即先运行nn.Module的初始化函数
        self.conv1 = nn.Conv2d(1, 6, 5) # 定义conv1函数的是图像卷积函数:输入为图像(1个频道,即灰度图),输出为 6张特征图, 卷积核为5x5正方形
        self.conv2 = nn.Conv2d(6, 16, 5)# 定义conv2函数的是图像卷积函数:输入为6张特征图,输出为16张特征图, 卷积核为5x5正方形
        self.fc1 = nn.Linear(16*5*5, 120) # 定义fc1(fullconnect)全连接函数1为线性函数:y = Wx + b,并将16*5*5个节点连接到120个节点上。
        self.fc2 = nn.Linear(120, 84)#定义fc2(fullconnect)全连接函数2为线性函数:y = Wx + b,并将120个节点连接到84个节点上。
        self.fc3 = nn.Linear(84, 10)#定义fc3(fullconnect)全连接函数3为线性函数:y = Wx + b,并将84个节点连接到10个节点上。

    #定义该神经网络的向前传播函数,该函数必须定义,一旦定义成功,向后传播函数也会自动生成(autograd)
    def forward(self, x):
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2)) #输入x经过卷积conv1之后,经过激活函数ReLU,使用2x2的窗口进行最大池化Max pooling,然后更新到x。
        x = F.max_pool2d(F.relu(self.conv2(x)), 2) #输入x经过卷积conv2之后,经过激活函数ReLU,使用2x2的窗口进行最大池化Max pooling,然后更新到x。
        x = x.view(-1, self.num_flat_features(x)) #view函数将张量x变形成一维的向量形式,总特征数并不改变,为接下来的全连接作准备。
        x = F.relu(self.fc1(x)) #输入x经过全连接1,再经过ReLU激活函数,然后更新x
        x = F.relu(self.fc2(x)) #输入x经过全连接2,再经过ReLU激活函数,然后更新x
        x = self.fc3(x) #输入x经过全连接3,然后更新x
        return x

    #使用num_flat_features函数计算张量x的总特征量(把每个数字都看出是一个特征,即特征总量),比如x是4*2*2的张量,那么它的特征总量就是16。
    def num_flat_features(self, x):
        size = x.size()[1:] # 这里为什么要使用[1:],是因为pytorch只接受批输入,也就是说一次性输入好几张图片,那么输入数据张量的维度自然上升到了4维。【1:】让我们把注意力放在后3维上面
        num_features = 1
        for s in size:
            num_features *= s
        return num_features


net = Net()
net

# 以下代码是为了看一下我们需要训练的参数的数量
print net
params = list(net.parameters())

k=0
for i in params:
    l =1
    print "该层的结构:"+str(list(i.size()))
    for j in i.size():
        l *= j
    print "参数和:"+str(l)
    k = k+l

print "总参数和:"+ str(k)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

下面是在pytorch上训练一个类似Lenet神经网络

#coding=utf-8
import torch
import torchvision
import torchvision.transforms as transforms

#ToTensor是指把PIL.Image(RGB) 或者numpy.ndarray(H x W x C) 从0到255的值映射到0到1的范围内,并转化成Tensor格式。
# torchvision输出的是PILImage,值的范围是[0, 1].
# 我们将其转化为tensor数据,并归一化为[-1, 1]。
#Normalize(mean,std)是通过下面公式实现数据归一化
####channel=(channel-mean)/std
transform=transforms.Compose([transforms.ToTensor(),
                              transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
                              ])

#训练集,将相对目录./data下的cifar-10-batches-py文件夹中的全部数据(50000张图片作为训练数据)加载到内存中,若download为True时,会自动从网上下载数据并解压
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)

#将训练集的50000张图片划分成12500份,每份4张图,用于mini-batch输入。shffule=True在表示不同批次的数据遍历时,打乱顺序。num_workers=2表示使用两个子进程来加载数据
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=False, num_workers=2)
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2)

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

# print len(trainset)
# print len(trainloader)
#
#
#
# #下面是代码只是为了给小伙伴们显示一个图片例子,让大家有个直觉感受。
# # functions to show an image
# import matplotlib.pyplot as plt
# import numpy as np
# #matplotlib inline
# def imshow(img):
#     img = img / 2 + 0.5 # unnormalize
#     npimg = img.numpy()
#     plt.imshow(np.transpose(npimg, (1,2,0)))
#     plt.show()
#
#
#
#
# # show some random training images
# dataiter = iter(trainloader)
# images, labels = dataiter.next()
#
# # print images
# imshow(torchvision.utils.make_grid(images))
# # print labels
# print(' '.join('%5s'%classes[labels[j]] for j in range(4)))

from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
    def __init__(self):
        super(Net,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)
        x=F.relu(self.fc1(x))
        x=F.relu(self.fc2(x))
        x=self.fc3(x)
        return x

net=Net()
net.cuda()
import torch.optim as optim
criterion=nn.CrossEntropyLoss()
optimizer=optim.SGD(net.parameters(),lr=0.001,momentum=0.9)

for epoch in range(10):
    running_loss=0.0
    for i,data in enumerate(trainloader,0):
        inputs,labels=data
        inputs,labels=Variable(inputs.cuda()),Variable(labels.cuda())
        optimizer.zero_grad()
        outputs=net(inputs)
        loss=criterion(outputs,labels)
        loss.backward()
        optimizer.step()

        running_loss+=loss.data[0]
        if i%2000==1999:
            print '[%d,%5d] loss:%.3f' %(epoch + 1,i+1,running_loss/2000)
            running_loss=0.0
print 'Finished Training'
correct = 0
total = 0
for data in testloader:
    images, labels = data
    inputs,labels=Variable(images.cuda()),Variable(labels.cuda())
    outputs = net(inputs)
    #print outputs.data
    _, predicted = torch.max(outputs.data, 1)  #outputs.data是一个4x10张量,将每一行的最大的那一列的值和序号各自组成一个一维张量返回,第一个是值的张量,第二个是序号的张量。
    total += labels.size(0)
    predicted=Variable(predicted.cuda())
    correct += (predicted == labels).sum()   #两个一维张量逐行对比,相同的行记为1,不同的行记为0,再利用sum(),求总和,得到相同的个数。

#print('Accuracy of the network on the 10000 test images: %d %%' % (100 * correct / total))
print 'Accuracy of the network on the 10000 test images:', (100 * correct / total)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值