卷积神经网络模型(CNN)基本架构
1.提取图片特征
假如我们有一个6*6的像素图,
在这一步,我们需要使用卷积核也可以称为特征过滤器,提取图片特征。
提取特征的计算规则:
我们要按照顺序提取原始图片中3*3的像素区域,再将其每个像素单元依此和卷积核内相对应的像素单元相乘再求和。最后把结果记录在新的4*4的像素图上
流程如下:
。。。。。。。。。。。。。。。。。
计算完成后,我们可以得到一个4*4的特征图,我们可以根据数字的大小来设置颜色的深浅,可以看到数字7竖直部分的特征被很好提取出来,但是水平特征却被提取出来。
这是因为在特征提取计算过程中,像素图由原来的6*6被降维成了4*4,边缘的特征丢失了。因此,为了解决边缘特征丢失的问题,我们可以使用“Padding”的扩充方法。将原来的6*6图像,扩充成8*8,扩充部分的像素值均设置为0。
这是可以发现水平特征和垂直特征都被很好的提取。
2.最大池化(Max Pooling)
这个过程的目的是将图片中的数据进一步压缩,仅反映图片中最突出的数据。
例如,我们将6×6的特征图用2×2的网格分割成3×3的部分,然后提取每个部分中的最大值。最后放于最大值化后的3×3网格中。池化后的数据保留了原始图片中最精华的特征部分。
3.扁平化处理
我们把池化后的数据进行扁平化处理,把两个3×3的像素图叠加转化成一维的数据条。
数据条录入到后面的“全连接隐藏层”,最终产生输出结果。扁平化之后的流程和我们之前介绍的Ann模型是完全一致的。全连接隐藏层意味着这里的任一个神经元都与前后层的所有神经元相连接。这样就可以保证最终的输出值是基于图片整体信息的结果。而在输出阶段,我们可以使用学习笔记(1)里提到的sigmoid函数返回0到1的值,代表该图片是否是7的概率。也可以用Softmax函数返回它分别属于0到9的概率。
CNN流程可视化在线界面CNN Explainer (poloclub.github.io)
卷积神经网络应用:
1.检测任务
2.分类与检索
3.医学任务
4.超分辨率重构
5.医学任务
6.无人驾驶
7.人脸识别
卷积神经网络与传统网络的区别
卷积神经网络整体架构
卷积神经网络MINST手写体识别
前面我们已经用普通的全连接神经网络做过MINST手写体识别,这里用卷积神经网络再次训练,并对比。
from keras import layers
from keras import models
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model = models.Sequential()
建了一个序贯模型(Sequential model),这是一个线性堆叠的模型,可以通过.add()方法一层层添加网络层。
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
这行代码向模型中添加了一个卷积层(Conv2D),这是网络的第一层。参数解释如下:
32
:指定了32个3x3的卷积核。(3, 3)
:卷积核的尺寸是3x3。activation='relu'
:激活函数使用ReLU(Rectified Linear Unit)。input_shape=(28, 28, 1)
:输入数据的形状,对于MNIST数据集,图像的尺寸是28x28,颜色通道是1(灰度图像)。
model.add(layers.MaxPooling2D((2, 2)))
这行代码向模型中添加了一个最大池化层(MaxPooling2D),池化窗口的尺寸是2x2。这个层的作用是在卷积层输出的特征图上进行下采样,减少计算量和参数数量。
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
这行代码向模型中添加了另一个卷积层,这次使用了64个3x3的卷积核。这个层没有指定input_shape
,因为它会自动接收上一层输出的形状。
model.add(layers.MaxPooling2D((2, 2)))
这行代码添加了第二个最大池化层,同样使用2x2的池化窗口。
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
这行代码添加了第三个卷积层,同样使用64个3x3的卷积核。这个层会进一步提取特征,为后续的分类任务做准备。
model.summary()
的作用是打印出模型的摘要信息,它会显示模型的每一层以及每一层的输出形状和参数数量。以下是对输出内容的解释:
- 层名(Layer (type)):每一层的名称和类型,例如
conv2d_1
表示第一个卷积层。 - 输出形状(Output Shape):每一层的输出形状,例如
(None, 26, 26, 32)
表示第一个卷积层的输出是一个26x26的图像,有32个特征图(通道)。 - 参数数量(Parameters):每一层中的参数数量,例如第一个卷积层有320个参数(32个卷积核 * (3*3的卷积核尺寸 + 1个偏置项))。
- 连接数(Connected to):每一层连接到的上一层。
每个Conv2D和MaxPooling2D层的输出都是一个形状为(高度,宽度,通道数)的3D张量。随着我们在网络中深入,宽度和高度维度通常会减小。通道数由传递给Conv2D层的第一个参数控制(例如32或64)。
接下来的步骤是将我们最后一个输出张量(形状为(3, 3, 64))输入到一个您已经熟悉的密集连接分类器网络中:即一堆Dense层。这些分类器处理的是向量,也就是1D的,而我们的当前输出是一个3D张量。因此,首先,我们需要将我们的3D输出扁平化为1D,然后在顶部添加几个Dense层。
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
model.add(layers.Flatten())
这行代码向模型中添加了一个Flatten层。Flatten层的作用是将输入“压平”,即把多维的数据转换为一维的数据。对于卷积层输出的特征图,Flatten层会将它们转换为一个长的特征向量,这样就可以作为全连接层(Dense层)的输入。
model.add(layers.Dense(64, activation='relu'))
这行代码向模型中添加了一个全连接层(Dense层)。全连接层中的每个神经元都与前一层中的每个神经元相连。
64
:指定了该层中的神经元数量为64。activation='relu'
:激活函数使用ReLU(Rectified Linear Unit),它会在输入大于0时输出输入值,否则输出0。
model.add(layers.Dense(10, activation='softmax'))
这行代码添加了另一个全连接层,它通常是网络的最后一层,用于输出预测结果。
10
:指定了该层中的神经元数量为10,这通常对应于分类任务的类别数。在MNIST手写体识别任务中,有10个类别(数字0到9)。activation='softmax'
:激活函数使用softmax,它将输出转换为概率分布,每个神经元的输出代表对应类别的概率。
这三个层组合起来,完成了从卷积层提取的特征到最终分类预测的转换。Flatten层将特征图转换为一维向量,第一个Dense层进行特征的非线性组合,最后一个Dense层输出每个类别的概率。
我们的(3, 3, 64)输出被扁平化为形状为(576,)的向量,然后才通过两个Dense层。
现在,让我们在MNIST数字上训练我们的卷积网络。接下来将重用深度学习学习笔记(1)MNIST示例中已经涵盖的大部分代码。
from keras.datasets import mnist
from tensorflow.keras.utils import to_categorical
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
#最后一个维度1表示颜色通道(灰度图像只有一个通道)。
train_images = train_images.reshape((60000, 28, 28, 1))
train_images = train_images.astype('float32') / 255
test_images = test_images.reshape((10000, 28, 28, 1))
test_images = test_images.astype('float32') / 255
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
model.fit(train_images, train_labels, epochs=5, batch_size=64)
test_loss, test_acc = model.evaluate(test_images, test_labels)
test_acc
可以看到准确率为98.9%,比使用全连接更高。
卷积神经网络猫狗图像分类
模型训练
from keras import layers
from keras import models
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',
input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
from tensorflow.keras import optimizers
model.compile(loss='binary_crossentropy',
optimizer=optimizers.RMSprop(lr=1e-4),
metrics=['acc'])
-
loss='binary_crossentropy'
:指定了损失函数为二元交叉熵(binary crossentropy),这是一种适用于二分类问题的损失函数,它计算的是预测值(通常是一个概率值)和真实标签之间的交叉熵损失。 -
optimizer=optimizers.RMSprop(lr=1e-4)
:指定了优化器为RMSprop(Root Mean Square Propagation),lr=1e-4
设置了学习率(learning rate)的初始值为0.0001。学习率是一个超参数,它控制了模型权重更新的幅度。 -
metrics=['acc']
:指定了模型训练和评估过程中需要监控的指标,这里选择了准确率(accuracy)。
数据预处理:
- 读取图片文件。
- 将JPEG内容解码为RBG像素网格。
- 这些网格转换为浮点张量。
- 将像素值(介于0和255之间)重新缩放到[0, 1]区间。
keras拥有能自动完成这些步骤的工具,模块keras.preprocessing.image中的ImageDataGenerator类,可以快速创建Python生成器,能够将硬盘上的图像文件自动转换为预处理好的张量批量。ImageDataGenerator
类默认会对输入的图像进行解码和缩放。
from keras.preprocessing.image import ImageDataGenerator
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
#训练集目录
train_dir,
#将图像大小调整到150x150
target_size=(150, 150),
#指定了每个批次的图像数量。
batch_size=20,
#使用二进制标签
class_mode='binary')
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(150, 150),
batch_size=20,
class_mode='binary')
for data_batch, labels_batch in train_generator:
print('data batch shape:', data_batch.shape)
print('labels batch shape:', labels_batch.shape)
break
可以看一下其中一个生成器的输出:它产生150x150 RGB(形状为(20, 150, 150, 3))和二元标签(形状为(20,))组成的批量。每个批量中包含20个样本。
接下来利用生成器让模型对数据进行拟合,并保存模型。
history = model.fit_generator(
train_generator,
steps_per_epoch=100,#意味着在每个周期中,模型会遍历100批数据。
epochs=30,#指定了训练的周期数
validation_data=validation_generator,#指定了验证数据生成器
validation_steps=50)#指定了每个验证周期中执行的步骤数
model.save('cats_and_dogs_small_1.h5')
绘制训练过程中的损失曲线和精度曲线
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc))
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
这四行代码分别从history
对象中提取了训练和验证的准确率(acc
和val_acc
)以及损失(loss
和val_loss
)。history
是fit_generator
方法返回的对象,它包含了一系列训练过程中的统计信息。
这些图表是过拟合的特征。我们的训练准确率随着时间的推移线性增加,直到接近100%,而我们的验证准确率在70-72%处停滞不前。我们的验证损失在仅五个周期后达到最小值然后停滞,而训练损失继续线性下降直到接近0。因为我们只有相对较少的训练样本(2000个),过拟合将是我们首要关注的问题。您已经知道一些可以帮助缓解过拟合的技术,例如dropout和权重衰减(L2正则化)。
datagen = ImageDataGenerator(
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest')
使用数据增强
from keras.preprocessing import image
fnames = [os.path.join(train_cats_dir, fname) for fname in os.listdir(train_cats_dir)]
img_path = fnames[3]
img = image.load_img(img_path, target_size=(150, 150))
x = image.img_to_array(img)
x = x.reshape((1,) + x.shape)
i = 0
for batch in datagen.flow(x, batch_size=1):
plt.figure(i)
imgplot = plt.imshow(image.array_to_img(batch[0]))
i += 1
if i % 4 == 0:
break
plt.show()
为了进一步降低过拟合,添加dropout层。
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',
input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy',
optimizer=optimizers.RMSprop(lr=1e-4),
metrics=['acc'])
train_datagen = ImageDataGenerator(
rescale=1./255,
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,)
# Note that the validation data should not be augmented!
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
# This is the target directory
train_dir,
# All images will be resized to 150x150
target_size=(150, 150),
batch_size=20,
# Since we use binary_crossentropy loss, we need binary labels
class_mode='binary')
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(150, 150),
batch_size=20,
class_mode='binary')
history = model.fit_generator(
train_generator,
steps_per_epoch=100,
epochs=100,
validation_data=validation_generator,
validation_steps=50)
model.save('cats_and_dogs_small_2.h5')
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc))
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()