卷积神经网络以及深度可分离卷积

一、卷积神经网络

在这里插入图片描述

边界检测

我们来看一个最简单的例子:“边界检测(edge detection)”,假设我们有这样的一张图片,大小8×8:
在这里插入图片描述
图片中的数字代表该位置的像素值,我们知道,像素值越大,颜色越亮,所以为了示意,我们把右边小像素的地方画成深色。图的中间两个颜色的分界线就是我们要检测的边界。

怎么检测这个边界呢?我们可以设计这样的一个 滤波器(filter,也称为kernel),大小3×3:
在这里插入图片描述
然后,这个filter,往我们的图片上“盖”,覆盖一块跟filter一样大的区域之后,对应元素相乘,然后求和。计算一个区域之后,就向其他区域挪动,接着计算,直到把原图片的每一个角落都覆盖到了为止。这个过程就是 “卷积”。
(我们不用管卷积在数学上到底是指什么运算,我们只用知道在CNN中是怎么计算的。)
这里的“挪动”,就涉及到一个步长了,假如我们的步长是1,那么覆盖了一个地方之后,就挪一格,容易知道,总共可以覆盖6×6个不同的区域。

那么,我们将这6×6个区域的卷积结果,拼成一个矩阵:
在这里插入图片描述
诶?!发现了什么?
这个图片,中间颜色浅,两边颜色深,这说明咱们的原图片中间的边界,在这里被反映出来了!
从上面这个例子中,我们发现,我们可以通过设计特定的filter,让它去跟图片做卷积,就可以识别出图片中的某些特征,比如边界
上面的例子是检测竖直边界,我们也可以设计出检测水平边界的,只用把刚刚的filter旋转90°即可。对于其他的特征,理论上只要我们经过精细的设计,总是可以设计出合适的filter的。
CNN(convolutional neural network),主要就是通过一个个的filter,不断地提取特征,从局部的特征到总体的特征,从而进行图像识别等等功能。
那么问题来了,我们怎么可能去设计这么多各种各样的filter呀?首先,我们都不一定清楚对于一大推图片,我们需要识别哪些特征,其次,就算知道了有哪些特征,想真的去设计出对应的filter,恐怕也并非易事,要知道,特征的数量可能是成千上万的。
其实学过神经网络之后,我们就知道,这些filter,根本就不用我们去设计,每个filter中的各个数字,不就是参数吗,我们可以通过大量的数据,来让机器自己去“学习”这些参数嘛。这,就是CNN的原理。

基本概念

padding填白

从上面的引子中,我们可以知道,原图像在经过filter卷积之后,变小了,从(8,8)变成了(6,6)。假设我们再卷一次,那大小就变成了(4,4)了。
这样有啥问题呢?

  1. 每次卷积,图像都缩小,这样卷不了几次就没了;
  2. 相比于图片中间的点,图片边缘的点在卷积中被计算的次数很少。这样的话,边缘的信息就易于丢失。
    为了解决这个问题,我们可以采用padding的方法。我们每次卷积前,先给图片周围都补一圈空白,让卷积之后图片跟原来一样大,同时,原来的边缘也被计算了更多次
    在这里插入图片描述
    比如,我们把(8,8)的图片给补成(10,10),那么经过(3,3)的filter之后,就是(8,8),没有变。

我们把上面这种“让卷积之后的大小不变”的padding方式,称为 “Same”方式,
把不经过任何填白的,称为 “Valid”方式。这个是我们在使用一些框架的时候,需要设置的超参数。

stride步长

前面我们所介绍的卷积,都是默认步长是1,但实际上,我们可以设置步长为其他的值。
比如,对于(8,8)的输入,我们用(3,3)的filter,
如果stride=1,则输出为(6,6);
如果stride=2,则输出为(3,3);(这里例子举得不大好,除不断就向下取整

pooling池化

这个pooling,是为了提取一定区域的主要特征,并减少参数数量,防止模型过拟合
比如下面的MaxPooling,采用了一个2×2的窗口,并取stride=2:
在这里插入图片描述
除了MaxPooling,还有AveragePooling,顾名思义就是取那个区域的平均值。

多通道图片卷积

彩色图像,一般都是RGB三个通道(channel)的,因此输入数据的维度一般有三个:(长,宽,通道)。
比如一个28×28的RGB图片,维度就是(28,28,3)。
前面的引子中,输入图片是2维的(8,8),filter是(3,3),输出也是2维的(6,6)。
如果输入图片是三维的呢(即增多了一个channels),比如是(8,8,3),这个时候,我们的filter的维度就要变成(3,3,3)了,它的最后一维要跟输入的channel维度一致
这个时候的卷积,是三个channel的所有元素对应相乘后求和,也就是之前是9个乘积的和,现在是27个乘积的和。因此,输出的维度并不会变化。还是(6,6)。
但是,一般情况下,我们会 使用多了filters同时卷积,比如,如果我们同时使用4个filter的话,那么 输出的维度则会变为(6,6,4)
我特地画了下面这个图,来展示上面的过程:
在这里插入图片描述
图中的输入图像是(8,8,3),filter有4个,大小均为(3,3,3),得到的输出为(6,6,4)。
其实,如果套用我们前面学过的神经网络的符号来看待CNN的话,

  1. 我们的输入图片就是X,shape=(8,8,3)
  2. 4个filters其实就是第一层神金网络的参数W1,,shape=(3,3,3,4),这个4是指有4个filters
  3. 我们的输出,就是Z1,shape=(6,6,4)
  4. 后面其实还应该有一个激活函数,比如relu,经过激活后,Z1变为A1,shape=(6,6,4)
    所以,在前面的图中,我加一个激活函数,给对应的部分标上符号,就是这样的:
    在这里插入图片描述

结构

上面我们已经知道了卷积(convolution)、池化(pooling)以及填白(padding)是怎么进行的,接下来我们就来看看CNN的整体结构,它包含了3种层(layer):

Convolutional layer(卷积层–CONV)

滤波器filters和激活函数构成。
一般要设置的超参数包括filters的数量、大小、步长,以及padding是“valid”还是“same”。当然,还包括选择什么激活函数

Pooling layer (池化层–POOL)

这里里面没有参数需要我们学习,因为这里里面的参数都是我们设置好了,要么是Maxpooling,要么是Averagepooling
需要指定的超参数,包括是Max还是average,窗口大小以及步长。通常,我们使用的比较多的是Maxpooling,而且一般取大小为(2,2)步长为2的filter,这样,经过pooling之后,输入的长宽都会缩小2倍,channels不变。

Fully Connected layer(全连接层–FC)

神经网络中的那种最普通的层,就是一排神经元。因为这一层是每一个单元都和前一层的每一个单元相连接,所以称之为“全连接”。
这里要指定的超参数,无非就是神经元的数量,以及激活函数
接下来,我们随便看一个CNN的模样,来获取对CNN的一些感性认识:
在这里插入图片描述
上面这个CNN结构可以表示为:

X–>CONV(relu)–>MAXPOOL–>CONV(relu)–>FC(relu)–>FC(softmax)–>Y

这里需要说明的是,在经过数次卷积和池化之后,我们 最后会先将多维的数据进行“扁平化”,也就是把 (height,width,channel)的数据压缩成长度为 height × width × channel 的一维数组,然后再与 FC层连接,这之后就跟普通的神经网络无异了。
可以从图中看到,随着网络的深入,我们的图像(严格来说中间的那些不能叫图像了,但是为了方便,还是这样说吧)越来越小,但是channels却越来越大了。在图中的表示就是长方体面对我们的面积越来越小,但是长度却越来越长了。

卷积神经网络中的卷积层有两个核心的思想,网络局部连接和卷积核参数共享,二者都是为了减少参数的数量

局部连接

所谓局部连接,就是卷积层的节点仅仅和其前一层的部分节点相连接,只用来学习局部特征。在计算机视觉中,图像中的某一块区域 ,像素之间的相关性与像素之间的距离同样相关,距离较近的像素间相关性强,距离较远则相关性就比较弱,由此可见局部相关性理论也适用于计算机视觉的图像处理领域。下图展示了一个局部连接的神经网络,第n+1层的每个节点只与第n层的3个节点相连接,而非与前一层全部5个神经元节点相连,这样原本需要53=15个权值参数,现在只需要33=9个权值参数,这种局部连接的方式大幅减少了参数数量,加快了学习速率,同时也在一定程度上减少了过拟合的可能。
在这里插入图片描述

参数共享

如图是一个33大小的卷积核在进行特征提取,channel=1,在每个位置进行特征提取的时候都是共享一个卷积核,假设有k个channel,则参数总量为33k,注意不同channel的参数是不能共享的
在这里插入图片描述
假设现在不使用参数共享,则卷积核作用于矩阵上的每一个位置时其参数都是不一样的,则卷积核的参数数量就与像素矩阵的大小保持一致了,假设有k个channel,则参数数量为这对于尺寸较大的图片来说明显是不可取的
在这里插入图片描述
也可看下图,通过权值共享的方法,这里一共只有3组不同的权值,如果只用了局部连接的方法,共需要3
4=12个权值参数,而加上了权值共享的方法后,现在仅仅需要3个权值,更进一步地减少参数数量。
在这里插入图片描述

二、深度可分离卷积

对于一些轻量级的网络,如mobilenet中,会有深度可分离卷积depthwise separable convolution,由depthwise(DW)pointwise(PW)两个部分结合起来,用来提取特征feature map
相比常规的卷积操作,其
参数数量和运算成本比较低

常规卷积操作

对于一张5×5像素、三通道(shape为5×5×3),经过3×3卷积核的卷积层(假设输出通道数为4,则卷积核shape为3×3×3×4,最终输出4个Feature Map,如果有same padding则尺寸与输入层相同(5×5),如果没有则为尺寸变为3×3
在这里插入图片描述
卷积层共4个Filter,每个Filter包含了3个Kernel,每个Kernel的大小为3×3。因此卷积层的参数数量可以用如下公式来计算:

N_std = 4 × 3 × 3 × 3 = 108

深度可分离卷积

Depthwise Convolution—逐通道卷积

Depthwise Convolution的一个卷积核负责一个通道,一个通道只被一个卷积核卷积
一张5×5像素、三通道彩色输入图片(shape为5×5×3),Depthwise Convolution首先经过第一次卷积运算,DW完全是在二维平面内进行卷积核的数量与上一层的通道数相同(通道和卷积核一一对应)。所以一个三通道的图像经过运算后生成了3个Feature map(如果有same padding则尺寸与输入层相同为5×5),如下图所示。
在这里插入图片描述
其中一个Filter只包含一个大小为3×3的Kernel,卷积部分的参数个数计算如下:
N_depthwise = 3 × 3 × 3 = 27
Depthwise Convolution完成后的Feature map数量与输入层的通道数相同,无法扩展Feature map。而且这种运算对输入层的每个通道独立进行卷积运算,没有有效的利用不同通道在相同空间位置上的feature信息。因此需要Pointwise Convolution来将这些Feature map进行组合生成新的Feature map

pointwise—逐点卷积

Pointwise Convolution的运算与常规卷积运算非常相似,它的卷积核的尺寸为 1×1×M,M为上一层的通道数。所以这里的卷积运算会将上一步的map在深度方向上进行加权组合,生成新的Feature map。有几个卷积核就有几个输出Feature map
在这里插入图片描述
由于采用的是1×1卷积的方式,此步中卷积涉及到的参数个数可以计算为:

N_pointwise = 1 × 1 × 3 × 4 = 12

经过Pointwise Convolution之后,同样输出了4张Feature map,与常规卷积的输出维度相同

参数对比

回顾一下,常规卷积的参数个数为:

N_std = 4 × 3 × 3 × 3 = 108

Separable Convolution的参数由两部分相加得到:

N_depthwise = 3 × 3 × 3 = 27
N_pointwise = 1 × 1 × 3 × 4 = 12
N_separable = N_depthwise + N_pointwise = 39

相同的输入,同样是得到4张Feature map,Separable Convolution的参数个数是常规卷积的约1/3。因此,在参数量相同的前提下,采用Separable Convolution的神经网络层数可以做的更深

三、CNN实现Fashion MNIST分类

import tensorflow as tf
from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt
import gzip
import os
from tensorflow.python.keras.utils.data_utils import get_file
%matplotlib inline
from sklearn.model_selection import train_test_split 
from sklearn.preprocessing import StandardScaler
import pandas as pd

载入Fashion MNIST数据集,https://github.com/zalandoresearch/fashion-mnist 包含 70000 张灰度图像,涵盖 10 个类别,分辨率28x28 像素
使用 60000 张图像训练网络,并使用 10000 张图像评估经过学习的网络分类图像的准确率
1.通过keras直接载入,文件保存在C:\Users\Administrator\.keras\datasets

fashion_mnist = keras.datasets.fashion_mnist
(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()

2.直接下载至本地,包含4个gzip文件。

#读取本地gz文档,并转换为numpy矩阵的函数
def load_data():
    base = "F:/jupyter notebook workspace/data/Fashion MNIST/"#gz文件所在路径
    files = [
        'train-labels-idx1-ubyte.gz', 'train-images-idx3-ubyte.gz',
        't10k-labels-idx1-ubyte.gz', 't10k-images-idx3-ubyte.gz'
    ]

    paths = []#保存四个gz文件的绝对路径
    for fname in files:
        paths.append(get_file(fname, origin=None, cache_dir=base + fname, cache_subdir=base))
#     for fname in files:
#         paths.append(base + fname)
    '''
    numpy.frombuffer(buffer, dtype = float, count = -1, offset = 0)
    用于实现动态数组,接受 buffer 输入参数,以流的形式读入转化成 ndarray 对象
    buffer 可以是任意对象,会以流的形式读入。
    dtype 返回数组的数据类型,可选
    count 读取的数据数量,默认为-1,读取所有数据。
    offset 读取的起始位置,默认为0
    '''
    with gzip.open(paths[0], 'r') as lbpath:
        y_train = np.frombuffer(lbpath.read(), np.uint8, offset=8)#uint8	无符号整数(0 to 255)
        #y_train=gzip.GzipFile(mode="rb", fileobj=open(paths[0], 'rb'))
    with gzip.open(paths[1], 'rb') as imgpath:
        x_train = np.frombuffer(
            imgpath.read(), np.uint8, offset=16).reshape(len(y_train), 28, 28)

    with gzip.open(paths[2], 'rb') as lbpath:
        y_test = np.frombuffer(lbpath.read(), np.uint8, offset=8)

    with gzip.open(paths[3], 'rb') as imgpath:
        x_test = np.frombuffer(
            imgpath.read(), np.uint8, offset=16).reshape(len(y_test), 28, 28)

    return (x_train, y_train), (x_test, y_test)

划分数据集为训练集、验证集和测试集,将归一化后的数据转化为CNN可读取的数据格式。

(train_images, train_labels), (test_images, test_labels) = load_data()
train_images,val_images,train_labels,val_labels=train_test_split(train_images,train_labels,test_size=0.2)

scaler=StandardScaler()
train_images=scaler.fit_transform(
    train_images.astype(np.float32).reshape(-1,1)).reshape(-1,28,28,1)
val_images=scaler.fit_transform(
    val_images.astype(np.float32).reshape(-1,1)).reshape(-1,28,28,1)
test_images=scaler.fit_transform(
    test_images.astype(np.float32).reshape(-1,1)).reshape(-1,28,28,1)
训练集:(48000, 28, 28, 1)
验证集:(12000, 28, 28, 1)
测试集:(10000, 28, 28, 1)

构建CNN模型(若使用深度可分离卷积,除输入层,其余的卷积层Conv2D均改为SeparableConv2D)

model=keras.Sequential()
model.add(keras.layers.Conv2D(filters=32,
                              kernel_size=3,
                              padding='same',
                              activation='relu',
                              input_shape=(28,28,1)))
model.add(keras.layers.Conv2D(filters=32,
                             kernel_size=3,
                             padding='same',
                             activation='relu'))
model.add(keras.layers.MaxPool2D(pool_size=2))


model.add(keras.layers.Conv2D(filters=64,
                              kernel_size=3,
                              padding='same',
                              activation='relu'))
model.add(keras.layers.Conv2D(filters=64,
                             kernel_size=3,
                             padding='same',
                             activation='relu'))
model.add(keras.layers.MaxPool2D(pool_size=2))


model.add(keras.layers.Conv2D(filters=128,
                              kernel_size=3,
                              padding='same',
                              activation='relu'))
model.add(keras.layers.Conv2D(filters=128,
                             kernel_size=3,
                             padding='same',
                             activation='relu'))
model.add(keras.layers.MaxPool2D(pool_size=2))

model.add(keras.layers.Flatten())#转换成全连接神经网络可读取的数据格式
model.add(keras.layers.Dense(128,activation='relu'))
model.add(keras.layers.Dense(10,activation='softmax'))

编译模型
模型还需要再进行几项设置才可以开始训练。这些设置会添加到模型的编译步骤:
(1)、损失函数:衡量模型在训练期间的准确率。我们希望尽可能缩小该函数,以“引导”模型朝着正确的方向优化。
(2)、优化器:根据模型看到的数据及其损失函数更新模型的方式。
(3)、度量标准:用于监控训练和测试步骤。以下示例使用准确率,即图像被正确分类的比例。

model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])
model.summary()

训练模型

logdir='F:\\jupyter notebook workspace\\model\\cnn-relu-callbacks'
if not os.path.exists(logdir):
    os.mkdir(logdir)
output_model_file=os.path.join(logdir,'fashion_mnist_model.hs')
callbacks=[keras.callbacks.TensorBoard(logdir),
          keras.callbacks.ModelCheckpoint(output_model_file,save_best_only=True),
          keras.callbacks.EarlyStopping(patience=5,min_delta=1e-3)]

history=model.fit(train_images, train_labels, epochs=10,
                 validation_data=(val_images,val_labels),
                 callbacks=callbacks)

绘制学习曲线

def plot_learning_curves(history):
    pd.DataFrame(history.history).plot(figsize=(8,5))
    plt.grid(True)
    plt.gca().set_ylim(0,1)

plot_learning_curves(history)

在这里插入图片描述
原文:
Depthwise卷积与Pointwise卷积
卷积神经网络
卷积神经网络中的参数共享和局部连接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值