赞
踩
我们来看一个最简单的例子:“边界检测(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的原理。
从上面的引子中,我们可以知道,原图像在经过filter卷积之后,变小了,从(8,8)变成了(6,6)。假设我们再卷一次,那大小就变成了(4,4)了。
这样有啥问题呢?
我们把上面这种“让卷积之后的大小不变”的padding方式,称为 “Same”方式,
把不经过任何填白的,称为 “Valid”方式。这个是我们在使用一些框架的时候,需要设置的超参数。
前面我们所介绍的卷积,都是默认步长是1,但实际上,我们可以设置步长为其他的值。
比如,对于(8,8)的输入,我们用(3,3)的filter,
如果stride=1,则输出为(6,6);
如果stride=2,则输出为(3,3);(这里例子举得不大好,除不断就向下取整)
这个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的话,
上面我们已经知道了卷积(convolution)、池化(pooling)以及填白(padding)是怎么进行的,接下来我们就来看看CNN的整体结构,它包含了3种层(layer):
由滤波器filters和激活函数构成。
一般要设置的超参数包括filters的数量、大小、步长,以及padding是“valid”还是“same”。当然,还包括选择什么激活函数。
这里里面没有参数需要我们学习,因为这里里面的参数都是我们设置好了,要么是Maxpooling,要么是Averagepooling。
需要指定的超参数,包括是Max还是average,窗口大小以及步长。通常,我们使用的比较多的是Maxpooling,而且一般取大小为(2,2)步长为2的filter,这样,经过pooling之后,输入的长宽都会缩小2倍,channels不变。
神经网络中的那种最普通的层,就是一排神经元。因为这一层是每一个单元都和前一层的每一个单元相连接,所以称之为“全连接”。
这里要指定的超参数,无非就是神经元的数量,以及激活函数。
接下来,我们随便看一个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组不同的权值,如果只用了局部连接的方法,共需要34=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的一个卷积核负责一个通道,一个通道只被一个卷积核卷积
一张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 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的神经网络层数可以做的更深。
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)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。