5.1 卷积神经网络简介
5.1.1 卷积运算 5.1.2 最大池化运算
5.2 在小型数据集从头开始训练一个卷积神经网络
5.2.1 深度学习与小数据问题的相关性 5.2.2 下载数据
完整代码
5.2.3 构建网络 5.2.4 数据预处理 5.2.5 使用数据增强
5.3 使用与训练的卷积神经网络
5.3.1 特征提取 5.3.2 微调模型 5.3.3 小结
5.4 卷积神经网络的可视化
5.4.1 可视化中间激活 5.4.2 可视化卷积神经网络的过滤器 5.4.3 可视化类激活的热力图
1-4章主要是介绍深度学习,以及其工作原理,5-9章将通过实践培养出如何用深度学习解决实际问题。
深入介绍神经网络原理,以及在计算机视觉任务上为何如此成功。
下面是Conv2D层和MaxPooling2D层的堆叠。
# 5-1 实例化一个小型的神经网络
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()
可以看到,每个Conv2D层和MaxPooling2D层的输出都是一个形状为(heights, width, channels)的3D张量。
# 5-2 在卷积神经网络上添加分类器
model.add(layers.Flatten())
model.add(layers.Dense(64, activation = 'relu'))
model.add(layers.Dense(10, activation = 'softmax'))
model.summary()
# 5-3 在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)
print(test_acc)
# 0.99129999999999996
密集连接层:从输入特征空间学习到的是全局模型
卷积层:学到的是局部模式
两个空间轴(高度和宽度)和一个深度轴(channel)的3D张量,其卷积叫做特征图(feature map)。
在每次MaxPoolingD后,特征图的尺寸会减半。最大池化通常使用2x2的窗口或者步幅2。而卷积通常使用3x3的窗口或者步幅1。
model_no_max_pool = models.Sequential()
model_no_max_pool.add(layers.Conv2D(32, (3, 3), activation = 'relu', input_shape = (28, 28, 1)))
model_no_max_pool.add(layers.Conv2D(64, (3, 3), activation = 'relu'))
model_no_max_pool.add(layers.Conv2D(64, (3, 3), activation = 'relu'))
model_no_max_pool.summary()
注意,最大池化不是实现这种下采样的唯一方法。你已经知道,还可以在前一个卷积层中使用步幅来实现。此外,你还可以使用平均池化来代替最大池化,其方法是将每个局部输人图块变换为取该图块各通道的平均值,而不是最大值。但最大池化的效果往往比这些替代方法更好。简而言之,原因在于特征中往往编码了某种模式或概念在特征图的不同位置是否存在(因此得名特征图),而观察不同特征的最大值而不是平均值能够给出更多的信息。因此,最合理的子采样策略是首先生成密集的特征图(通过无步进的卷积),然后观察特征每个小图块上的最大激活,而不是查看输入的稀疏窗口(通过步进卷积)或对输人图块取平均,因为后两种方法可能导致错过或淡化特征是否存在的信息
通常来说,深度学习需要大量数据。但是有时候如果模型很小,并做了很好的正则化,同时任务十分简单,可能几百个样本就足够了。
此外,神经网络还有高度的可复用性。特别是在计算机视觉领域,许多预训练模型都能公开下载到。
本章数据在kaggle上https://www.kaggle.com/c/dogs-vs-cats/data 这个数据集包含25000张猫狗图片,大小为543MB,我们创建三个小的子集:每个类别各1000个样本的训练集,每个类别各500个样本验证集和每个类别各500个样本的测试集.
# 5-4 将图像复制到训练、验证和测试的目录
import os, shutil
original_dataset_dir = "D:/kaggle_data/kaggle_original_data/train"
#生成保存小数据集的目录
base_dir = 'D:/kaggle_data/cats_and_dogs_small'
os.mkdir(base_dir)
os.mkdir() —— 创建文件夹
os.path.join() —— 路径拼接
会从第一个以”/”开头的参数开始拼接,之前的参数全部丢弃。
以上一种情况为先。在上一种情况确保情况下,若出现”./”开头的参数,会从”./”开头的参数的上一个参数开始拼接。
# 对应划分后的序列、验证和测试的目录
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)
# 设置猫和狗的训练、验证、测试目录
train_cats_dir = os.path.join(train_dir, 'cats')
os.mkdir(train_cats_dir)
train_dogs_dir = os.path.join(train_dir, 'dogs')
os.mkdir(train_dogs_dir)
validation_cats_dir = os.path.join(validation_dir , 'cats')
os.mkdir(validation_cats_dir)
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
os.mkdir(validation_dogs_dir)
test_cats_dir = os.path.join(test_dir, 'cats')
os.mkdir(test_cats_dir)
test_dogs_dir = os.path.join(test_dir, 'dogs')
os.mkdir(test_dogs_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)
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)
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)
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)
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)
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)
print('total training cat images:',len(os.listdir(train_cats_dir)))
print('total training dog images:',len(os.listdir(train_dogs_dir)))
print('total validation cat images:',len(os.listdir(validation_cats_dir)))
print('total validation dog images:',len(os.listdir(validation_dogs_dir)))
print('total test cat images:',len(os.listdir(test_cats_dir)))
print('total test dog images:',len(os.listdir(test_dogs_dir)))
'''
total training cat images: 1000
total training dog images: 1000
total validation cat images: 500
total validation dog images: 500
total test cat images: 500
total test dog images: 500
'''
# 5-4 将图像复制到训练、验证和测试的目录
import os, shutil
original_dataset_dir = "D:/kaggle_data/kaggle_original_data/train"
base_dir = 'D:/kaggle_data/cats_and_dogs_small'
# 1.创建对应划分后的序列、验证和测试的目录
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)
# 2.创建猫和狗的训练、验证、测试目录
train_cats_dir = os.path.join(train_dir, 'cats')
#os.mkdir(train_cats_dir)
train_dogs_dir = os.path.join(train_dir, 'dogs')
#os.mkdir(train_dogs_dir)
validation_cats_dir = os.path.join(validation_dir , 'cats')
#os.mkdir(validation_cats_dir)
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
#os.mkdir(validation_dogs_dir)
test_cats_dir = os.path.join(test_dir, 'cats')
#os.mkdir(test_cats_dir)
test_dogs_dir = os.path.join(test_dir, 'dogs')
#os.mkdir(test_dogs_dir)
# 3.将图片复制到目录
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)
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)
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)
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)
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)
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)
# 5-5 将猫狗分类的小型卷积神经网络实例化
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(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.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'))
model.summary()
编译这一步,使用RMSprop优化器。因为网络最后一层是单一sigmoid单元,使用二元交叉熵作为损失函数。
# 5-6 配置模型用于训练
from keras import optimizers
model.compile(loss = 'binary_crossentropy',
optimizer = optimizers.RMSprop(lr = 1e-4),
metrics = ['acc'])
(1)读取图像文件
(2)将JEPG文件解码为RGB像素网络
(3)将这些像素网络转换为浮点数张量
(4)将像素值缩放到[0,1]区间
keras.preprocessing.image能快速创建python生成器。
ImageDataGenerator(rescale = 1./255) —— 缩放图片
keras.preprocessing.image.ImageDataGenerator.flow_from_directory( ) —— 读取文件夹下的所有图片
flow_from_directory(self, directory,
target_size=(256, 256), color_mode='rgb',
classes=None, class_mode='categorical',
batch_size=32, shuffle=True, seed=None,
save_to_dir=None,
save_prefix='',
save_format='jpeg',
follow_links=False)
# 5-7 使用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,
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')
Found 2000 images belonging to 2 classes. Found 1000 images belonging to 2 classes.
观察生成器的输出
for data_batch, labels_batch in train_generator:
print('data batch shape:', data_batch.shape)
print('labels batch shape:', labels_batch.shape)
break
# data batch shape: (20, 150, 150, 3)
# labels batch shape: (20,)
train_generator相当于一个迭代器,这里只用fit_generator来拟合数据
# 5-8 利用批量生成器拟合数据
history = model.fit_generator(
train_generator,
steps_per_epoch = 100,
epochs = 30,
validation_data = validation_generator,
validation_steps = 50)
# 5-9 保存模型
model.save('cats_and_dogs_small_1.h5')
# 5-10 绘制过程中的损失曲线和精度曲线
import matpltlib.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(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label = 'Training acc')
plt.plot(epochs, val_acc, 'b', label = 'Validation acc')
plt.title('Training and validation loss')
plt.legend()
plt.show()
训练精度随时间线性增加,直到接近100%,而验证精度停留在70%左右,显然已经过拟合了。这里我们使用视觉领域的新方法,数据增强(data augmentation)
过拟合的原因是学习样本太少,这里我们生成可信图像。
# 5-11 利用ImageDatagenerator来设置数据增强
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_modee = 'nearest')
# 5-12 显示几个随机增强后的训练图像
from keras.preprocessing import image
fnames = [os.path.join(train_cats_dir, fname) for fname in os.listdir(train_cats_dir)]
img_path = fname[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层
# 5-13 定义一个包含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'))
mdoel.compile(loss = 'binary_crossentropy',
optimizer = optimizers.RMSprop(lr = 1e-4),
metrics = ['acc'])
# 5-14 利用数据增强生成器训练卷积神经网络
train_datagen = ImageDataGenerator(
rescale = 1./255,
rotation_range = 40,
width_shift_rantge = 0.2,
height_shift_range = 0.2,
shear_range = 0.2,
zoom_range = 0.2,
horiontal_flip = Ture)
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)
# 5-15 保存模型
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()
使用VGG16架构:特征提取(feature extraction)和微调模型(fine-tuning)
重复使用卷积基(convolutional base),卷积层提取的通用性取决于该层在模型中的深度。
keras.applications中的部分模型Xception、Inception V3、Resnet50、VGG16、VGG19、MobileNet
# 5-16 将VGG16卷积基实例化
from keras.applications import VGG16
conv_base = VGG16(weights = 'imagenet',
include_top = False,
input_shape = (150, 150, 3))
# weights指定模型初始化的权重
# include_top指定是否包含密集连接分类器
# input_shape是输入到网络中的图像张量
conv_base.summary()
特征图现在为(4, 4, 512),在这个特征上添加一个密集连接分类器
你的数据集上运行卷积基,将输出的数组作为输入,输入到独立的密集连接分类器中。不允许使用数据增强。
在顶部添加Dense层来扩展已有模型。
# 5-17 使用预训练的卷积基提取特征
import os
import numpy as np
from keras.preprocessing.image import ImageDataGenerator
base_dir = 'D:/kaggle_data/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:
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)
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))
# Found 2000 images belonging to 2 classes.
# Found 1000 images belonging to 2 classes.
# Found 1000 images belonging to 2 classes.
# 5-18 定义并训练密集连接分类器
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))
# 5-19 绘制结果
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(1, len(acc)+1)
plt.plot(epochs, acc, 'bo', label = 'Training acc')
plt.plot(epochs, val_acc, 'b', label = 'Validtion acc')
plt.title('Training and validaiton accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label = 'Training loss')
plt.plot(epochs, val_loss, 'b', label = 'Validtion loss')
plt.title('Training and validaiton loss')
plt.legend()
plt.show()
验证精度达到了90%,但是模型几乎一开始就过拟合了。
# 5-20 在卷积基上添加一个密集连接分类器
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()
print('This is the number of trainable weights '
'before freezing the conv base:', len(model.trainable_weights))
# This is the number of trainable weights before freezing the conv base: 30
conv_base.trainable = False
print('This is the number of trainable weights '
'after freezing the conv base:', len(model.trainable_weights))
# This is the number of trainable weights after freezing the conv base: 4
# 5-21 利用冻结的卷积基端到端地训练模型
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
train_datagen = ImageDataGenerator(
rescale = 1./255,
rotation_range = 40,
width_shift_range = 0.2,
height_shift_range = 0.2,
shear_range = 0.2,
horizontal_flip = True,
fill_mode = 'nearest')
test_datagen = ImageDataGenerator(rescale = 1./255)
train_generator = train_datagen.flow_from_directory(
train_dir,
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)
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()
对于提取的冻结的模型基,微调让顶层的几层解冻。
conv_base.summary()
# 5-22 冻结直到某一层的所有层
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
# 5-23 微调模型
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()
# 5-24 使曲线变得平滑
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()
# 测试结果
test_generator = test_datagen.flow_from_directory(
test_dir,
target_size=(150, 150),
batch_size=20,
class_mode='binary')
test_loss, test_acc = model.evaluate_generator(test_generator, steps=50)
print('test acc:', test_acc)
# Found 1000 images belonging to 2 classes.
# test acc: 0.967999992371
可视化神经网络的中间输出
可视化神经网络的过滤器
可视化图像中类激活的热力图
1. 对给定输入,激活函数的输出。首先加载5.2保存的模型。
from keras.models import load_model
model = load_model('cats_and_dogs_small_2.h5')
model.summary()
2.将单个图像预处理为一个4D张量 from keras.preprocessing import image,并且显示图像。
# 5-25 预处理单张图像
img_path = 'D:/kaggle_data/cats_and_dogs_small/test/cats/cat.1700.jpg'
from keras.preprocessing import image
import numpy as np
img = image.load_img(img_path, target_size = (150, 150))
img_tensor = img.img_to_array(img)
img_tensor = np.expand_dims(img_tensor, axis = 0)
img_tensor /= 255.
print(img_tensor.shape)
# (1, 150, 150, 3)
# 5-26 显示测试图像
import matplotlib.pyplot as plt
plt.imshow(img_tensor[0])
plt.show()
3. 用一个输出和输入张量将模型实例化
# 5-27 用一个输入张量和一个输出张量列表将模型实例化
from keras import models
# 提取前8层的输出
layer_outputs = [layer.output for layer in model.layers[:8]]
# print(layer_outputs.value)
for i in layer_outputs:
print(i)
# 创建一个模型,给定模型输入,可以返回这些输出
activation_model = models.Model(inputs = model.input, outputs = layer_outputs)
activation_model.summary()
Tensor("conv2d_5_1/Relu:0", shape=(?, 148, 148, 32), dtype=float32)
Tensor("max_pooling2d_5_1/MaxPool:0", shape=(?, 74, 74, 32), dtype=float32)
Tensor("conv2d_6_1/Relu:0", shape=(?, 72, 72, 64), dtype=float32)
Tensor("max_pooling2d_6_1/MaxPool:0", shape=(?, 36, 36, 64), dtype=float32)
Tensor("conv2d_7_1/Relu:0", shape=(?, 34, 34, 128), dtype=float32)
Tensor("max_pooling2d_7_1/MaxPool:0", shape=(?, 17, 17, 128), dtype=float32)
Tensor("conv2d_8_1/Relu:0", shape=(?, 15, 15, 128), dtype=float32)
Tensor("max_pooling2d_8_1/MaxPool:0", shape=(?, 7, 7, 128), dtype=float32)
4. 对于每一次的输出激活对应一个数组
# 5-28 以预测模式运行模型
activations = activation_model.predict(img_tensor)
# 对于输入的猫图像第一个卷积层的激活如下
first_layer_activation = activations[0]
print(first_layer_activation.shape)
# (1, 148, 148, 32)
# 5-29 将第4个通道可视化
import matplotlib.pyplot as plt
plt.matshow(first_layer_activation[0,:,:,4], cmap = 'viridis')
plt.show()
# 5-30 将第7个通道可视化
plt.matshow(first_layer_activation[0, :, :, 7], cmap = 'viridis')
plt.show()
# 5-31 将每个中间激活的所有通道可视化
layer_names = []
# 每一个层的名字
for layer in model.layers[:8]:
layer_name.append(layer.name)
image_per_row = 16
# 显示特征图
for layer_name, layer_activation in zip(layer_names, activations):
n_features = layer_activation.shape[-1] # 特征图的特征个数
size = layer_activation.shape[1] # 特征图的形状
n_cols = n_features
display_grid = np.zeros((size * n_cols, image_per_row * size))
for col in range(n_cols):
for row in range(image_per_row):
channel_image = layer_activation[0, : , : , col * images_per_row + row]
channel_image -= channel_image.mean()
channel_image /= channel_iamge.std()
channel_image *= 64
channel_image += 128
channel_image = np.clip(channel_image, 0, 225).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 = 'virids')
for layer_name, layer_activation in zip(layer_names, activations):
print(layer_name)
print(layer_activation.shape)
conv2d_5
(1, 148, 148, 32)
max_pooling2d_5
(1, 74, 74, 32)
conv2d_6
(1, 72, 72, 64)
max_pooling2d_6
(1, 36, 36, 64)
conv2d_7
(1, 34, 34, 128)
max_pooling2d_7
(1, 17, 17, 128)
conv2d_8
(1, 15, 15, 128)
max_pooling2d_8
(1, 7, 7, 128)
有助于精确理解卷积神经网络中,每个过滤器容易接受的视觉模式或视觉概念
# 5-32 为过滤器的可视化定义损失张量
from keras.applications import VGG16
from keras import backend as K
model = VGG16(weights = 'imagenet',
include_top = False)
layer_name = 'block3_conv1'
filter_index = 0
layer_output = model.get_layer(layer_name).output
loss = K.mean(layer_output[:, :, :, filter_index])
# 5-33 获取损失相对于输入的梯度
grads = K.gradients(loss, model.input)[0]
# 5-34 梯度标准化技巧
grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)
# 5-35 给定Numpy输入值,得到Numpy输出值
iterate = K.function([model.input], [loss, grads])
import numpy as np
loss_value, grads_value = iterate([np.zeros((1, 150, 150, 3))])
定义一个循环来进行随机梯度下降
# 5-36 通过随机梯度下降让损失最大化
input_img_data = np.random.random((1, 150, 150, 3)) * 20 + 128.
step = 1.
for i in range(40):
loss_value, grads_value = iterate([input_img_data])
input_img_data += grads_value * step
# 5-37 将张量转换为有效图像的实用函数
def deprocess_image(x):
x -= x.mean()
x /= (x.std() + 1e-5)
x *= 0.1
x += 0.5
x = np.clip(x, 0, 1)
x *= 255
x = np.clip(x, 0 , 255).astype('uint8')
return x
# 5-38 生成过滤器可视化的函数
def generate_pattern(layer_name, filter_index, size = 150):
layer_output = model.get_layer(layer_name).output
loss = K.mean(layer_output[:, :, :, filter_index])
grads = K.gradients(loss, model.input)[0]
grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)
iterate = K.function([model.input], [loss, grads])
input_img_data = np.random.random((1, size, size, 3)) * 20 + 128.
step = 1.
for i in range(40):
loss_value, grads_value = iterate([input_img_data])
input_img_data += grads_value * step
img = input_img_data[0]
return deprocess_image(img)
plt.imshow(generate_pattern('block3_conv1', 0))
# 5-39 生成某一层中所有过滤器相应模型组成的网络
layer_name = 'block1_conv1'
size = 64
margin = 5
results = np.zeros((8 * size + 7 * margin, 8 * size + 7 * margin, 3))
for i in range(8):
for j in range(8):
filter_img = generate_pattern(layer_name, i + (j * 8), size = size)
horizontal_start = i * size + i * margin
horizontal_end = horizontal_start + size
vertical_start = j * size + j * margin
vertical_end = vertical_start + size
results[horizontal_start: horizontal_end, vertical_start: vertical_end, :] = filter_img
plt.figure(figsize = (20, 20))
plt.imshow(results)
Grad-CAM
# 5-40 加载带有预训练权重的VGG16网络
from keras.applications.vgg16 import VGG16
model = VGG16(weights = 'imagenet')
# 5-41 为VGG16模型预处理一张输入图像
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input, decode_predictions
import numpy as np
img_path = '' # 目标图像的本地路径
img = img.load_img(img_path, target_size = (224, 224)) # 大小为224x224的图形库图像
x = image.img_to_array(img) # 大小为224,224,3的numpy数组
x = np.enpand_dims(x, axis = 0) # 添加一个维度 转化为1,224,224,3
x = preprocess_input(x) # 对批量预处理
preds = model.predict(x)
print('Predicted:', decode_predictions(preds, top = 3)[0])
Predicted: [('n02504458', 'African_elephant', 0.90942144), ('n01871265', 'tusker', 0.08618243), ('n02504013', 'Indian_elephant', 0.0043545929)]
为了展示图像中的哪些部分最像非洲象,使用Grad-CAM算法
# 5-42 应用Grad-CAM算法
african_elephant_output = model.output[:, 386]
last_conv_layer = model.get_layer('block5_conv3')
grads = K.gradient(african_elephant_output, last_conv_layer.output)[0]
pooled_grads = K.mean(grads, axis = (0, 1, 2))
iterate = K.function([model.input], [pooled_grads, last_conv_layer.output[0]])
pooled_grads_value, conv_layer_output_value = iterate([x])
for i in range(512):
conv_layer_output_value[:, :, i] *= pooled_grads_value[i]
heatmap = np.mean(conv_layer_output_value, axis = -1)
# 5-43 热力图后处理
heatmap = np.maximum(heatmap, 0)
heatmap /= np.max(heatmap)
plt.matshow(heatmap)
plt.show()
# 5-44 将热力图与原始图像叠加
import cv2
img = cv2.imread(img_path)
heatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))
heatmap = np.uint8(255 * heatmap)
heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
superimposed_img = heatmap * 0.4 + img
cv2.imwrite('', superimposed_img)