TensorFlow Specialization Course 1学习笔记 Week 4
本周我们将运用之前学习的知识创建一个用于真实图像的分类器。
首先下载马-人数据集。数据集包含马和人的图像。所以这是一个二分类问题。
!wget --no-check-certificate \
https://storage.googleapis.com/laurencemoroney-blog.appspot.com/horse-or-human.zip \
-O /tmp/horse-or-human.zip
!wget --no-check-certificate \
https://storage.googleapis.com/laurencemoroney-blog.appspot.com/validation-horse-or-human.zip \
-O /tmp/validation-horse-or-human.zip
在这个示例中需要注意的一件事是:我们没有明确地将这些图像标记为马或人。如果你还记得之前的手写体例子,每个图像都有一个标签来说明它是数字几。在这个示例我们使用另一种方法。数据集的结构为
Horse-or-human
└──horses
└──humans
我们将使用ImageGenerator——它从子目录中读取图像,并根据该子目录的名称自动标记它们。、
首先我们建立模型。这一次的任务,图像分辨率要比之前大得多。所以多添加了几层卷积和池化层。除此之外,这个模型和之前的模型几乎一样。
import tensorflow as tf
model = tf.keras.models.Sequential([
# 这一次我们使用分辨率为300x300的彩色图像,所以input_shape=(300, 300, 3)
# 我们数据集中的图像不一定是300x300分辨率的图像,但是我们会在下面使用ImageGenerator来处理图像
tf.keras.layers.Conv2D(16, (3,3), activation='relu', input_shape=(300, 300, 3)),
tf.keras.layers.MaxPooling2D(2, 2),
tf.keras.layers.Conv2D(32, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(512, activation='relu'),
# 因为我们训练的是一个二分类器,所以最终的输出为1个值。激活函数使用sigmoid,输出为0到1之间的数。
# 这个值大于0.5则为人类,小于0.5为马
tf.keras.layers.Dense(1, activation='sigmoid')
])
可以使用model.summary()
输出这个模型的详细信息。
下面编译模型,因为是二分类问题,我们使用binary_crossentropy损失函数。优化器选择RMSprop,将学习速率设为0.001。
from tensorflow.keras.optimizers import RMSprop
model.compile(loss='binary_crossentropy',
optimizer=RMSprop(lr=0.001),
metrics=['acc'])
下面进行数据预处理
在Keras中,这可以通过keras.preprocessing.image.ImageDataGenerator
类来完成。使用缩放参数正则化图像。使用方法.flow(data, labels)
(如果数据集包含标签文件使用此方法)或.flow_from_directory(directory)
来实例化数据生成器。然后可以将这些生成器与Keras模型方法一起使用,Keras模型方法接受数据生成器作为输入:fit_generator
、evaluate_generator
和predict_generator
。
from tensorflow.keras.preprocessing.image import ImageDataGenerator
# 使用ImageDataGenerator正则化图像
train_datagen = ImageDataGenerator(rescale=1/255)
validation_datagen = ImageDataGenerator(rescale=1/255)
# Flow training images in batches of 128 using train_datagen generator
train_generator = train_datagen.flow_from_directory(
'/tmp/horse-or-human/', # 训练集的根目录
target_size=(300, 300), # 所有图像的分辨率将被调整为300x300
batch_size=128, # 每次将128个图像
# 类别模式设为二分类
class_mode='binary')
# 对验证集做同样的操作
validation_generator = validation_datagen.flow_from_directory(
'/tmp/validation-horse-or-human/',
target_size=(300, 300),
batch_size=32,
class_mode='binary')
下面使用fit_generator
来接收数据生成器开始训练模型。我们的训练集有500个马的图像,527个人的图像。一共1027个图像,一次训练传入128个图像,所以每次迭代需要9次训练。(课程中将steps_per_epoch设为了8。)验证集batch_size=32,一共256个图像,所以validation_steps正好等于8。实验发现,steps_per_epoch和batch_size参数设为什么或注释掉不影响训练过程,猜测fit方法会自己计算每次迭代需要的训练次数。
history = model.fit_generator(
train_generator,
steps_per_epoch=9,
epochs=15,
# verbose: Integer. 0, 1, or 2. Verbosity mode. 0 = silent, 1 = progress bar, 2 = one line per epoch.
verbose=1,
validation_data = validation_generator,
validation_steps=8)
同样可视化卷积的输出结果
import numpy as np
import random
from tensorflow.keras.preprocessing.image import img_to_array, load_img
# 定义一个新的模型,它可以输入图像,并将之前的模型中除第一层和全连接层外的每一层的输出都展示出来
successive_outputs = [layer.output for layer in model.layers[1:]]
visualization_model = tf.keras.models.Model(inputs = model.input, outputs = successive_outputs)
# 从训练集中随机找出一个图像作为输入
horse_img_files = [os.path.join(train_horse_dir, f) for f in train_horse_names]
human_img_files = [os.path.join(train_human_dir, f) for f in train_human_names]
img_path = random.choice(horse_img_files + human_img_files)
img = load_img(img_path, target_size=(300, 300)) # 读入作为PIL图像
x = img_to_array(img) # Numpy array大小为(300, 300, 3)
x = x.reshape((1,) + x.shape) # reshape后大小为(1, 300, 300, 3),reshape的原因上一篇有讲到
# 正则化
x /= 255
# 使用模型预测新图像,并得到卷积层和池化层的输出
successive_feature_maps = visualization_model.predict(x)
# 每一层的名字,下面被注释的为课程原代码,有bug。之前跳过了第一个卷积层,所以名字也要跳过第一个卷积层
# layer_names = [layer.name for layer in model.layers
layer_names = [layer.name for layer in model.layers[1:]]
# 下面可视化卷积层和池化层的输出
for layer_name, feature_map in zip(layer_names, successive_feature_maps):
if len(feature_map.shape) == 4: # 只可视化卷积层或池化层
# 特征图的大小为(1, size, size, n_features)
n_features = feature_map.shape[-1]
size = feature_map.shape[1]
# 定义可视化的格子尺寸
display_grid = np.zeros((size, size * n_features))
for i in range(n_features):
# 因为之前图像正则化了,所以这里将特征图还原回去,以便可视化
x = feature_map[0, :, :, i]
x -= x.mean()
x /= x.std()
x *= 64
x += 128
# 下面clip让x中的数小于0的就等于0,大于255的就等于255
x = np.clip(x, 0, 255).astype('uint8')
# 将图像放入格子中
display_grid[:, i * size : (i + 1) * size] = x
# 可视化
scale = 20. / n_features
plt.figure(figsize=(scale * n_features, scale))
plt.title(layer_name)
plt.grid(False)
plt.imshow(display_grid, aspect='auto', cmap='viridis')
从可视化的结果可以看到,从原始像素的图像到越来越抽象和紧凑的特征图。深层的特征图开始突出网络所关注的内容,并且它们显示出越来越少特征的被“激活”;大多数设置为零。这就是所谓的“稀疏性”。表征稀疏性是深度学习的一个重要特征。
这些特征图所携带的关于图像原始像素的信息越来越少,但是关于图像的类的信息却越来越精细。可以将convnet(或一般意义上的深度网络)视为信息提取管道。
我们可以通过测试新的图像,来发现分类器的问题。例如,训练集中白色马比较多,分类器对白色马的分类效果就会比较好。
实验:
将输入图像的分辨率调低,如150x150,然后去掉两个卷积和池化层。
结果:训练速度加快,准确率降低。
详细内容请看:https://github.com/lmoroney/dlaicourse/blob/master/Course 1 - Part 8 - Lesson 3 - Notebook.ipynb
课程地址:https://www.coursera.org/learn/introduction-tensorflow/