深度学习在机器视觉上的应用
文章目录
1.卷积神经网络
卷积神经网络也称convnet,多应用于图像分类问题
卷积神经网络接收形状为 (image_height, image_width, image_channels)
的输入张量(不包括批量维度)。
简单卷积神经网络构成
- Conv2D层
- MaxPooling2D层
- 每个 Conv2D 层和 MaxPooling2D 层的输出都是一个形状为 (height, width, channels) 的 3D 张量
import keras
keras.__version__
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.summary() #显示当前神经网络架构
高度和宽度两个维度的尺寸都会随着网络加深而变小。
添加分类器
我们将进行 10 类别分类,最后一层使用带 10 个输出的 softmax 激活。现在网络的架构如下:
#添加分类器
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
#再次显示网络架构
model.summary()
可以看出,在进入两个Dense层之前,形状(3,3,64)的输出被展平为(576,)的向量
1.1 eg:在MNIST图像上训练卷积神经网络
from keras.datasets import mnist
from keras.utils import to_categorical
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
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
1.2 卷积运算
密集连接层和卷积层的根本区别:
- Dense层从输入的特征空间学习到的是全局模式(eg:MNIST数字图像,全局涉及到的是所有像素的模式;
- 卷积层学到的是局部模式
卷积神经网络的特性:
- 卷积神经网络学到的模式具有平移不变性
- 卷积神经网络可以学到模式空间层次结构
一些概念:
MNIST 示例中,第一个卷积层接收一个大小为 (28, 28, 1) 的特征图,并输出一个大小为 (26, 26, 32) 的特征图,即它在输入上计算 32 个过滤器。对于这 32 个输出通道,每个通道都包含一个 26×26 的数值网格,它是过滤器对输入的响应图(response map),表示这个过滤器模式在输入中不同位置的响应。
这也是特征图这一术语的含义:深度轴的每个维度都是一个特征(或过滤器),而 2D 张量 output[:, :, n] 是这个过滤器在输入上的响应的二维空间图(map)。
卷积的参数定义
- 输入中提取图块的尺寸:一般为33获55。
- 输出特征图的深度:卷积所计算的过滤器的数量。
- Conv2D(output_depth, (window_height, window_width))。
卷积的工作原理
- 在 3D 输入特征图上滑动(slide)这些 3×3 或 5×5 的窗口,在每个可能
的位置停止并提取周围特征的 3D 图块; - 每个 3D 图块与学到的同一个权重矩阵[叫作卷积核(convolution kernel)]做张量积,转换成形状为 (output_depth,) 的 1D 向量
- 对所有这些向量进行空间重组,使其转换为形状为 (height, width, output_depth) 的 3D 输出特征图;
- 输出特征图中的每个空间位置都对应于输入特征图中的相同位置(比如输出的右下角包含了输入右下角的信息);
两个概念:
- 边界效应与填充:
如果你希望输出特征图的空间维度与输入相同,那么可以使用填充(padding)。填充是在输入特征图的每一边添加适当数目的行和列,使得每个输入方块都能作为卷积窗口的中心 - 步幅:
对卷积的描述都假设卷积窗口的中心方块都是相邻的。但两个连续窗口的距离是卷积的一个参数,叫作步幅,默认值为 1
1.3 最大池化运算
最大池化是什么?
最大池化就是从输入的特征图中提取窗口,并输出每个通道的最大值;
它的概念与卷积类似,但最大池化是使用硬编码的max张量运算对局部图进行变化,而不是使用学到的线性变换。
通常最大池化的目的是将特征图下采样2倍,通常使用22的窗口和2的步幅(卷积是33窗口和步幅1)
最大池化的作用:对特征图进行下采样;
在每个 MaxPooling2D 层之后,特征图的尺寸都会减半。例如,在第一个 MaxPooling2D 层之前,特征图的尺寸是 26×26,但最大池化运算将其减半为 13×13;
最大池化的前提(reasons):
减少需要处理的特征图元素个数;
通过让连续的卷积层观察窗口越来越大从而引入空间过滤层的结构;
最大池化补充学习:https://blog.csdn.net/m0_51311105/article/details/122766764
1.4 案例猫狗图像分类训练卷积神经网络
小数据问题处理
深度学习擅长处理大量数据,他的基本特性之一就是:能够独立地在训练数据中找到有趣的特性,无需人为的特征工程;
当仅有十几个样本时:如果规模很小,并做了很好的正则化,同时任务简单,也可以实现高效率学习;
数据集介绍
下载数据(需要挂梯子):https://www.kaggle.com/c/dogs-vs-cats/data
包含: 25000张猫狗图像(每个类别都有12500张),大小为543MB。
创建新数据集,其中包含三个子集:
- 每个类别各1000个样本的训练集;
- 每个类别各500个样本的验证集;
- 每个类别各500个样本的测试集;
import os, shutil
#原始数据集的解压目录
original_dataset_dir = 'C:/Users/mn/jupyter_py/kaggle_original_data'
#创建新的目录
base_dir = 'C:/Users/mn/jupyter_py/cats_and_dogs_small'
os.mkdir(base_dir)
#分别创建三个子集目录(训练、验证、测试)
train_dir = os.path.join(base_dir, 'train')
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, 'test')
os.mkdir(test_dir)
# 猫的训练图像目录(1000个样本)
train_cats_dir = os.path.join(train_dir, 'cats')
os.mkdir(train_cats_dir)
# 猫的验证图像目录(500个样本)
train_dogs_dir = os.path.join(train_dir, 'dogs')
os.mkdir(train_dogs_dir)
# 猫的测试图像目录(500个样本)
validation_cats_dir = os.path.join(validation_dir, 'cats')
os.mkdir(validation_cats_dir)
# 狗的训练图像目录(1000个样本)
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
os.mkdir(validation_dogs_dir)
# 狗的验证图像目录(500个样本)
test_cats_dir = os.path.join(test_dir, 'cats')
os.mkdir(test_cats_dir)
# 狗的测试图像目录(500个样本)
test_dogs_dir = os.path.join(test_dir, 'dogs')
os.mkdir(test_dogs_dir)
#将 1000 猫图像 复制到猫的训练图像集里 train_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_cats_dir, fname)
shutil.copyfile(src, dst)
#将 500 猫图像 复制到猫的验证图像集里 validation_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_cats_dir, fname)
shutil.copyfile(src, dst)
#将 500 猫图像 复制到猫的测试图像集里
fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_cats_dir, fname)
shutil.copyfile(src, dst)
#将 1000 狗图像 复制到狗的训练图像集里 train_cats_dir
fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_dogs_dir, fname)
shutil.copyfile(src, dst)
#将 500 狗图像 复制到狗的验证图像集里
fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_dogs_dir, fname)
shutil.copyfile(src, dst)
#将 500 狗图像 复制到狗的测试图像集里
fnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_dogs_dir, fname)
shutil.copyfile(src, dst)
检查一下每个分组(训练/测试/验证)分别包含多上张图像:
构建网络
网络结构 :采用Conv2D(relu激活)和MaxPooling2D层交替堆叠再连接Flatten层;
#将猫狗分类的小型卷积神经网络实例化
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'))
#配置模型用于训练
#这里采用RMSprop优化器
#网络最后一层是单一的sigmoid单元,采用二元交叉熵作为损失函数
from keras import optimizers
model.compile(loss='binary_crossentropy',
optimizer=optimizers.RMSprop(lr=1e-4),
metrics=['acc'])
数据预处理
在数据输入神经网络之前,应该将数据格式化为经过处理的浮点数张量,处理步骤如下:
- 读取图像文件;
- 将JPEG文件解码为RGB像素网格;
- 将这些像素网格转化成浮点张量;
- 将像素值(0~255范围内)缩放到[0,1]区间
Keras的图像处理辅助工具模块:keras.preprocessing.imge
包含ImageDataGenerator类:可以快速创建Python生成器,快速将硬盘上的图片文件自动转化为预处理好的张量批量;
#使用ImageDataGenerator从目录中读取图像
from keras.preprocessing.image import ImageDataGenerator
# 将所有图像除以255
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
# 目标目录
train_dir,
# 将所有图像大小调整为150*150,每个批量中包含20个样本;
target_size=(150, 150),
batch_size=20,
# 这里使用了 binary_crossentropy loss所以需要使用二进制标签;
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=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()
存在过拟合问题:
针对于计算机视觉领域的新方法,在用深度学习模型处理图像时几乎都会用到这种方法,它就是数据增强;
数据增强
过拟合的原因是学习样本太少,导致无法训练出能够泛化到新数据的模型;
数据增强是从现有的训练样本中生成更多的训练数据,其方法是利用多种能够生成可信图像的随机变换来增加(augment)样本。其目标是,模型在训练时不会两次查看完全相同的图像。这让模型能够观察到数据的更多内容,从而具有更好的泛化能力;
#利用ImageDataGenerator设置数据增强;
datagen = ImageDataGenerator(
rotation_range=40, #rotation_range 是角度值,表示图像随机旋转的角度范围
width_shift_range=0.2, #是图像在水平或垂直方向上平移的范围
height_shift_range=0.2,
shear_range=0.2, #是随机错切变换的角度
zoom_range=0.2, #是图像随机缩放的范围
horizontal_flip=True, #p 是随机将一半图像水平翻转
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))
# 将其转化为形状 (150, 150, 3)的数组
x = image.img_to_array(img)
# 改变数组形状(1, 150, 150, 3)
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,)
# 注意不能增强验证数据
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
# 目标目录
train_dir,
target_size=(150, 150),
batch_size=32,
class_mode='binary')
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(150, 150),
batch_size=32,
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()
1.5 使用预训练的卷积神经网络
是什么?
预训练网络是一个保存好的网络,之前已经在大型数据集上训练好的网络,作为视觉视觉的通用模型,可以再次应用在不同的计算机视觉问题上;
使用预训练网络的两种方法:
- 特征提取;
- 微调模型
常用的架构:
VGG16:常用是卷积神经网络架构;
ResNet、Inception、Inception-ResNet、Xception
特征提取
是什么?
就是使用之前网络学到的表示从新样本里提取出有趣的特征。然后将这些特征输入到一个新的分类器,从头开始训练;
用于图像分类的卷积神经网络两部分组成
- 卷积基: 一系列的池化层和卷积层;
- 密集连接分类器;
对于卷积神经网络而言,特征提取就是取出之前训练好的网络的卷积基,在上边运行数据,然后输出上面训练一个新分类器;
为什么卷积基一般来说可以重复使用,分类器不可以?
- 卷积基学到的表示更加通用,更适合重复使用,注意,卷积层提取的表示通用性(可复用性)取决于该层在模型的深度,模型更靠近底部的层,提取的越是局部的高度通用的,比如(视觉边缘、颜色和纹理),更靠近顶部的是更加抽象的概念(“猫耳朵”、“狗眼睛”),因此最好只使用模型的前几层来做特征提取,而不是整个卷积;
- 分类器学到的表示必然是针对模型训练的类别
VGG16卷积基实例化
VGG16模型内置在Keras中,可以从keras.applications模块导入
from keras.applications import VGG16
conv_base = VGG16(weights=''C:/Users/mn/jupyter_py/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5'',
include_top=False,
input_shape=(150, 150, 3))
#这里原本是 weight="imagenet",但是由于网页无法直接连接到python中,可以自行下载h5文件,将weight后的参数改为h5保存位置;
#查看网络架构
conv_base.summary()
最后的特征图形为(4,4,512),我们将在这个特征上添加一个分类器;
- 方法1: 在数据集上运行卷积基,将输出保存为numpy数组,然后用这个数组作为输入,输入到独立的密集连接分类器中;
特点:速度快,计算代价低;因为对于每个输入图像只需要运行一次卷积基,每次卷积基的运行代价都比较高,但这种方法不允许数据增强;- 方法2:在顶部添加Dense层来扩展已有模型(conv_base),在输入数据上端到端运行整个模型;
特点:可以使用数据增强,每个输入图片进入模型时都会经过卷积基。
方法1:不使用数据增强的卷积基特征提取
运行ImageDataGenerator实例,将图像提取成Numpy数组。
调用conv_base模型的predict方法对这些图像进行特征提取
import os
import numpy as np
from keras.preprocessing.image import ImageDataGenerator
base_dir = '/Users/mn/jupyter_py/cats_and_dogs_small'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
test_dir = os.path.join(base_dir, 'test')
datagen = ImageDataGenerator(rescale=1./255)
batch_size = 20
def extract_features(directory, sample_count):
features = np.zeros(shape=(sample_count, 4, 4, 512))
labels = np.zeros(shape=(sample_count))
generator = datagen.flow_from_directory(
directory,
target_size=(150, 150),
batch_size=batch_size,
class_mode='binary')
i = 0
for inputs_batch, labels_batch in generator:
features_batch = conv_base.predict(inputs_batch)
features[i * batch_size : (i + 1) * batch_size] = features_batch
labels[i * batch_size : (i + 1) * batch_size] = labels_batch
i += 1
if i * batch_size >= sample_count:
# Note that since generators yield data indefinitely in a loop,
# we must `break` after every image has been seen once.
break
return features, labels
train_features, train_labels = extract_features(train_dir, 2000)
validation_features, validation_labels = extract_features(validation_dir, 1000)
test_features, test_labels = extract_features(test_dir, 1000)
目前,提取特征的形状为(samples,4,4,512),我们要将输入到密集连接器的分类中,必须将其形状展品为(samples,8192)
train_features = np.reshape(train_features, (2000, 4 * 4 * 512))
validation_features = np.reshape(validation_features, (1000, 4 * 4 * 512))
test_features = np.reshape(test_features, (1000, 4 * 4 * 512))
定义训练密集连接的分类器
from keras import models
from keras import layers
from keras import optimizers
model = models.Sequential()
model.add(layers.Dense(256, activation='relu', input_dim=4 * 4 * 512))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(optimizer=optimizers.RMSprop(lr=2e-5),
loss='binary_crossentropy',
metrics=['acc'])
history = model.fit(train_features, train_labels,
epochs=30,
batch_size=20,
validation_data=(validation_features, validation_labels))
可视化展示:绘制结果
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()
从结果来看,验证精度达到了90%,比1.4案例的模型效果要好的多,但从图中看,模型几乎一开始就过拟合;
方法2:使用数据增强的特征提取
扩展的conv_base模型,在输入数据上端到端的运行模型;
这种模型的行为和层类似:可以向Sequential模型中添加一个模型(比如conv_base)就像添加一个层一样;
from keras import models
from keras import layers
model = models.Sequential()
model.add(conv_base)
model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.summary()
可以看到,VGG16上的卷积基有14714688个参数,在其上添加的分类器有200万个参数;
在编译和训练模型之前,先要冻结卷积基;方法:将其trainable属性设置为False;
(冻结:指的是一个或多个层在训练过程中保持其权重不变)
这样设置之后,只有添加两个Dense层的权重才会被训练,总共有4个权重张量,每层两个;
#利用冻结的卷积基端到端的训练模型
from keras.preprocessing.image import ImageDataGenerator
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,
fill_mode='nearest')
# 注意不能增强验证数据
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')
model.compile(loss='binary_crossentropy',
optimizer=optimizers.RMSprop(lr=2e-5),
metrics=['acc'])
history = model.fit_generator(
train_generator,
steps_per_epoch=100,
epochs=30,
validation_data=validation_generator,
validation_steps=50,
verbose=2)
可视化展示
model.save('cats_and_dogs_small_3.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()
模型微调
aim:
模型微调是为了使整个复用模型更加抽象表示,以便让其与手中的问题更加关联;
概念
模型微调与特征提取互为补充,对于特征提取的冻结的模型基,微调就是将顶层的解冻,将解冻的几层和新增的部分联合训练;
注意:
冻结 VGG16
的卷积基是为了能够在上面训练一个随机初始化的分类器。同理,只有上面的分类器已经训练好了,才能微调卷积基的顶部几层。如果分类器没有训练好,那么训练期间通过网络传播的误差信号会特别大,微调的几层之前学到的表示都会被破坏
微调的网络步骤
(1) 在已经训练好的基网络(base network)上添加自定义网络。
(2) 冻结基网络。
(3) 训练所添加的部分。
(4) 解冻基网络的一些层。
(5) 联合训练解冻的这些层和添加的部分。
卷积基的架构:
conv_base.summary()
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) (None, 150, 150, 3) 0
_________________________________________________________________
block1_conv1 (Conv2D) (None, 150, 150, 64) 1792
_________________________________________________________________
block1_conv2 (Conv2D) (None, 150, 150, 64) 36928
_________________________________________________________________
block1_pool (MaxPooling2D) (None, 75, 75, 64) 0
_________________________________________________________________
block2_conv1 (Conv2D) (None, 75, 75, 128) 73856
_________________________________________________________________
block2_conv2 (Conv2D) (None, 75, 75, 128) 147584
_________________________________________________________________
block2_pool (MaxPooling2D) (None, 37, 37, 128) 0
_________________________________________________________________
block3_conv1 (Conv2D) (None, 37, 37, 256) 295168
_________________________________________________________________
block3_conv2 (Conv2D) (None, 37, 37, 256) 590080
_________________________________________________________________
block3_conv3 (Conv2D) (None, 37, 37, 256) 590080
_________________________________________________________________
block3_pool (MaxPooling2D) (None, 18, 18, 256) 0
_________________________________________________________________
block4_conv1 (Conv2D) (None, 18, 18, 512) 1180160
_________________________________________________________________
block4_conv2 (Conv2D) (None, 18, 18, 512) 2359808
_________________________________________________________________
block4_conv3 (Conv2D) (None, 18, 18, 512) 2359808
_________________________________________________________________
block4_pool (MaxPooling2D) (None, 9, 9, 512) 0
_________________________________________________________________
block5_conv1 (Conv2D) (None, 9, 9, 512) 2359808
_________________________________________________________________
block5_conv2 (Conv2D) (None, 9, 9, 512) 2359808
_________________________________________________________________
block5_conv3 (Conv2D) (None, 9, 9, 512) 2359808
_________________________________________________________________
block5_pool (MaxPooling2D) (None, 4, 4, 512) 0
=================================================================
Total params: 14,714,688
Trainable params: 0
Non-trainable params: 14,714,688
_________________________________________________________________
先解冻 conv_base,然后冻结其中的部分层。
#冻结直到某一层的所有层
conv_base.trainable = True
set_trainable = False
for layer in conv_base.layers:
if layer.name == 'block5_conv1':
set_trainable = True
if set_trainable:
layer.trainable = True
else:
layer.trainable = False
#微调模型
model.compile(loss='binary_crossentropy',
optimizer=optimizers.RMSprop(lr=1e-5),
metrics=['acc'])
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_4.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()
使曲线变的更平滑
def smooth_curve(points, factor=0.8):
smoothed_points = []
for point in points:
if smoothed_points:
previous = smoothed_points[-1]
smoothed_points.append(previous * factor + point * (1 - factor))
else:
smoothed_points.append(point)
return smoothed_points
plt.plot(epochs,
smooth_curve(acc), 'bo', label='Smoothed training acc')
plt.plot(epochs,
smooth_curve(val_acc), 'b', label='Smoothed validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs,
smooth_curve(loss), 'bo', label='Smoothed training loss')
plt.plot(epochs,
smooth_curve(val_loss), 'b', label='Smoothed validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
2.卷积神经网络的可视化
深度学习模型是“黑盒”,即模型学到的表示很难用人类可以理解的方式来提取和呈现。虽然对于某些类型的深度学习模型来说,这种说法部分正确,但对卷积神经网络来说绝对不是这样。卷积神经网络学到的表示非常适合可视化,很大程度上是因为它们是视觉概念的表示。自 2013 年以来,人们开发了多种技术来对这些表示进行可视化和解释。
三种可视化表达
- 可视化卷积神经网络的中间输出(中间激活)
有助于理解卷积神经网络连续层如何对输入进行变化,了解卷积神经网络每个过滤器的含义; - 可视化卷积神经网络的过滤器
有助于精确理解卷积神经网络中每个过滤器容易接受的视觉模式或视觉概念 - 可视化图像类激活的热力图
有助于理解图像的哪个部分被识别为属于某个类别,从而可以定位图像中的物体。
2.1 可视化中间激活
可视化中间激活,是指对于给定输入,展示网络中各个卷积层和池化层输出的特征图(层的输出通常被称为该层的激活,即激活函数的输出)。这让我们可以看到输入如何被分解为网络学到的不同过滤器。
我们希望在三个维度对特征图进行可视化:宽度、高度和深度(通道)。每
个通道都对应相对独立的特征,所以将这些特征图可视化的正确方法是将每个通道的内容分别绘制成二维图像
模型查看
#查看模型
from keras.models import load_model
model = load_model('cats_and_dogs_small_2.h5')
model.summary() # 作为提醒
预处理单张图片并显示
#预处理单张图片
img_path = '/Users/fchollet/Downloads/cats_and_dogs_small/test/cats/cat.1700.jpg'
# W将图像预处理为一个4D张量
from keras.preprocessing import image
import numpy as np
img = image.load_img(img_path, target_size=(150, 150))
img_tensor = image.img_to_array(img)
img_tensor = np.expand_dims(img_tensor, axis=0)
#图像预处理
img_tensor /= 255.
# 输出其形状
print(img_tensor.shape) #(1, 150, 150, 3)
import matplotlib.pyplot as plt
plt.imshow(img_tensor[0])
plt.show()
提取特征图
需要先创建一个kreas模型,以图像批量作为输入,输出卷积层和池化层的激活
#用一个输入张量和一个输出张量列表将模型实例化
from keras import models
# 提取前八层的输出
layer_outputs = [layer.output for layer in model.layers[:8]]
# 创建一个模型,给定模型输入,可以返回这些输出
activation_model = models.Model(inputs=model.input, outputs=layer_outputs)
输入一张图像,这个模型将返回原始模型前 8 层的激活,一般每层对应一个输出
第一个卷积层可视化
#以预测模式运行模型
# T返回八个numpy数组组成的列表,每个层对于一个numpy数组
activations = activation_model.predict(img_tensor)
#第一个卷积层的激活如下所示
first_layer_activation = activations[0]
print(first_layer_activation.shape) #(1, 148, 148, 32)
#第3个通道可视化(边缘检测器)
import matplotlib.pyplot as plt
plt.matshow(first_layer_activation[0, :, :, 3], cmap='viridis')
plt.show()
#第7个通道可视化(猫眼显示)
plt.matshow(first_layer_activation[0, :, :, 7], cmap='viridis')
plt.show()
可视化所有中间激活的通道
#将中间层激活所有通道可视化
import keras
# 层的名称:可以将名称画在图中
layer_names = []
for layer in model.layers[:8]:
layer_names.append(layer.name)
images_per_row = 16
# 显示特征图
for layer_name, layer_activation in zip(layer_names, activations):
# 显示特征图的个数
n_features = layer_activation.shape[-1]
# 特征图的形状 (1, size, size, n_features)
size = layer_activation.shape[1]
# W在这个矩阵中将激活通道平铺
n_cols = n_features // images_per_row
display_grid = np.zeros((size * n_cols, images_per_row * size))
#将每个过滤器平铺到一个大小的水平网格中
for col in range(n_cols):
for row in range(images_per_row):
channel_image = layer_activation[0,
:, :,
col * images_per_row + row]
# 对特征图进行处理,使其美观
channel_image -= channel_image.mean()
channel_image /= channel_image.std()
channel_image *= 64
channel_image += 128
channel_image = np.clip(channel_image, 0, 255).astype('uint8')
display_grid[col * size : (col + 1) * size,
row * size : (row + 1) * size] = channel_image
# 显示网格
scale = 1. / size
plt.figure(figsize=(scale * display_grid.shape[1],
scale * display_grid.shape[0]))
plt.title(layer_name)
plt.grid(False)
plt.imshow(display_grid, aspect='auto', cmap='viridis')
plt.show()
小结