TensorFlow/[TensorFlow 学习笔记 ] 2 计算机视觉 1-CNN

TensorFlow/[TensorFlow 学习笔记 ] 2 计算机视觉 1-CNN

在上一篇文章中,你学习了如何创建一个神经网络,来学习你要处理的问题。但上一篇文章中的神经网络处理的是非常简单的问题 \(y=2 \times x-1\)。现在我们来看下更加实际,并且也更加困难的问题。在这个问题中,我们使用的数据包含 10 种不同类型的数据,我们要训练一个神经网络,来识别衣服的类型。

首先导入库

1
2
import tensorflow as tf
print(tf.__version__)

然后加载 Fashion MNIST 数据集,这个数据集包含在 tf.keras 的数据 API 中。

1
mnist = tf.keras.datasets.fashion_mnist

调用 load_data 方法,会下载数据,得到两个 list,分别是训练数据和测试数据,包括图片和对应的标签。

1
(training_images, training_labels), (test_images, test_labels) = mnist.load_data()

我们来看看这些数据图片和对应的标签,下面的代码是看索引为 0 的数据,你可以修改索引,查看不同的数据。

1
2
3
4
5
6
import numpy as np
np.set_printoptions(linewidth=200)
import matplotlib.pyplot as plt
plt.imshow(training_images[0])
print(training_labels[0])
print(training_images[0])

现在,我们的图片数据的像素值都在 [0, 255] 之间。对于神经网络来说,如果数据的范围在 [0, 1] 之间,网络会更加容易训练。把数据的范围,从 [0, 255],转换为 [0, 1], 称为标准化(normalizing),代码如下:

1
2
training_images  = training_images / 255.0
test_images = test_images / 255.0

下一步,我们开始定义模型的结构:

1
2
3
model = tf.keras.models.Sequential([tf.keras.layers.Flatten(), 
tf.keras.layers.Dense(128, activation=tf.nn.relu),
tf.keras.layers.Dense(10, activation=tf.nn.softmax)])

解释下上面代码中的概念:

  • Sequential:定义神经网络种的一系列的网络层。
  • Flatten:我们的图片数据是一个方形,Flatten 层就是把图片数据展开为 1 维。
  • Dense:全连接层,表示一层神经元。

每个网络层的神经元都需要激活函数(activation function),Relu 是一种激活函数。

Softmax 的作用是取出一个数据列表里的最大一个数,例如最后一层的输出是 [0.1, 0.1, 0.05, 0.1, 9.5, 0.1, 0.05, 0.05, 0.05],那么 Softmax 会直接输出 [0,0,0,0,1,0,0,0,0],减少了非常多的代码。

下一步,是像之前一样,编译模型,然后使用 model.fit 来训练模型,学习输入数据和标签之间的关系。

1
2
3
4
5
model.compile(optimizer = tf.keras.optimizers.Adam(),
loss = 'sparse_categorical_crossentropy',
metrics=['accuracy'])

model.fit(training_images, training_labels, epochs=5)

你会在最后一个 epoch 看到一个准确率,大概是 0.9098。 虽然不够高,但模型只训练了 5 个 epoch,这已经是很好的结果了。

然后,我们使用 model.evaluate 来在测试集上评价模型。

1
model.evaluate(test_images, test_labels)

在测试集上得到的准确率,大概是 0.8838,这个分数会比训练集的准确率低一点。后面,我们会学习如何提高测试集的准确率。

练习

练习 1

运行下面的代码,输出的 classifications[0] 是 10 个数,表示这个数据属于 10 个类别的概率。

1
2
3
classifications = model.predict(test_images)

print(classifications[0])

练习 2

把第一层神经元的个数改为 1024,训练时间变长了,但是准确率会有提升。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import tensorflow as tf
print(tf.__version__)

mnist = tf.keras.datasets.mnist

(training_images, training_labels) , (test_images, test_labels) = mnist.load_data()

training_images = training_images/255.0
test_images = test_images/255.0

model = tf.keras.models.Sequential([tf.keras.layers.Flatten(),
tf.keras.layers.Dense(1024, activation=tf.nn.relu),
tf.keras.layers.Dense(10, activation=tf.nn.softmax)])

model.compile(optimizer = 'adam',
loss = 'sparse_categorical_crossentropy')

model.fit(training_images, training_labels, epochs=5)

model.evaluate(test_images, test_labels)

classifications = model.predict(test_images)

print(classifications[0])
print(test_labels[0])

练习 3

Flatten() 层的作用是将 (28, 28) 的数据,展开为一维的 (784, 1) 的数据。

练习 4

最后一个网络层的神经元个数是 10,表示输出 10 个类别。

练习 5

你可以添加更多的网络层,但是不会提升太多的准确率,因为我们使用的数据很简单。对于一些负责的数据,添加更多的网络层可以提升准确率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import tensorflow as tf
print(tf.__version__)

mnist = tf.keras.datasets.mnist

(training_images, training_labels) , (test_images, test_labels) = mnist.load_data()

training_images = training_images/255.0
test_images = test_images/255.0

model = tf.keras.models.Sequential([tf.keras.layers.Flatten(),
tf.keras.layers.Dense(512, activation=tf.nn.relu),
tf.keras.layers.Dense(256, activation=tf.nn.relu),
tf.keras.layers.Dense(10, activation=tf.nn.softmax)])

model.compile(optimizer = 'adam',
loss = 'sparse_categorical_crossentropy')

model.fit(training_images, training_labels, epochs=5)

model.evaluate(test_images, test_labels)

classifications = model.predict(test_images)

print(classifications[0])
print(test_labels[0])

练习 6

如果把 epoches 改为 30,那么可能会遇到过拟合,也就是 loss 先下降,然后上升。

练习 7

在上面的代码中,开始训练前,对数据进行了归一化,把 [0, 255] 的数据转换为 [0,1]。如果不进行归一化,直接训练,准确率会下降。

练习 8

在前面,我们提到过拟合, loss 先下降,然后上升。如果你想在达到某个较低 loss 的时候,就直接停止训练,或者达到 95% 的准确率就停止训练,那么你可以使用 callback

下面的代码中,定义的 callback ,重写了 on_epoch_end 方法,当在 epoch 结束时 loss 低于 0.4 时,就停止训练。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import tensorflow as tf
print(tf.__version__)

class myCallback(tf.keras.callbacks.Callback):
def on_epoch_end(self, epoch, logs={}):
if(logs.get('loss')<0.4):
print("\nReached 60% accuracy so cancelling training!")
self.model.stop_training = True

callbacks = myCallback()
mnist = tf.keras.datasets.fashion_mnist
(training_images, training_labels), (test_images, test_labels) = mnist.load_data()
training_images=training_images/255.0
test_images=test_images/255.0
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(512, activation=tf.nn.relu),
tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy')
model.fit(training_images, training_labels, epochs=5, callbacks=[callbacks])

再举一个例子,下面的代码中,定义的 callback ,重写了 on_epoch_end 方法,当在 epoch 结束时准确率超过 60%,就停止训练。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import tensorflow as tf

class myCallback(tf.keras.callbacks.Callback):
def on_epoch_end(self, epoch, logs={}):
if(logs.get('accuracy')>0.6):
print("\nReached 60% accuracy so cancelling training!")
self.model.stop_training = True

mnist = tf.keras.datasets.fashion_mnist

(x_train, y_train),(x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

callbacks = myCallback()

model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(512, activation=tf.nn.relu),
tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])
model.compile(optimizer=tf.optimizers.Adam(),
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])

model.fit(x_train, y_train, epochs=10, callbacks=[callbacks])

如果不使用 callback,那么训练集和验证集的准确率分别是 89% 和 87%。我们可以使用卷积神经网络(CNN)来进一步提升准确率。

简单来说,卷积就是使用一个数组(通常是 \(3 \times 3\) 或者 \(5 \times 5\) ),在图片上滑动,并和对应像素计算加权和。通过卷积操作,你可以做检测边缘之类的特征。例如,一个边缘检测的卷积核如下所示:


中间是 8,其余都是 -1。对图片里每个 \(3 \times 3\) 的格子都做加权和,得到的结果图片会突出边缘的信息。

这对于计算机视觉来说很有用。因为通常是这种比较明显的特征,可以区分不同的物体,而去除了大部分冗余的信息。

这就是卷积层的作用。

使用 CNN 提升准确率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import tensorflow as tf
print(tf.__version__)
mnist = tf.keras.datasets.fashion_mnist
(training_images, training_labels), (test_images, test_labels) = mnist.load_data()
training_images=training_images.reshape(60000, 28, 28, 1)
training_images=training_images / 255.0
test_images = test_images.reshape(10000, 28, 28, 1)
test_images=test_images/255.0
model = tf.keras.models.Sequential([
tf.keras.layers.Conv2D(64, (3,3), activation='relu', input_shape=(28, 28, 1)),
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(128, activation='relu'),
tf.keras.layers.Dense(10, activation='softmax')
])
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
# model.summary() 可以查看网络结构和每层的输出形状
model.summary()
model.fit(training_images, training_labels, epochs=5)
test_loss = model.evaluate(test_images, test_labels)

输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2.0.0
Model: "sequential_3"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_6 (Conv2D) (None, 26, 26, 64) 640
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 13, 13, 64) 0
_________________________________________________________________
conv2d_7 (Conv2D) (None, 11, 11, 64) 36928
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 5, 5, 64) 0
_________________________________________________________________
flatten_3 (Flatten) (None, 1600) 0
_________________________________________________________________
dense_6 (Dense) (None, 128) 204928
_________________________________________________________________
dense_7 (Dense) (None, 10) 1290
=================================================================
Total params: 243,786
Trainable params: 243,786
Non-trainable params: 0
_________________________________________________________________
Epoch 1/5
60000/60000==============================] - 6s 95us/sample - loss: 0.4325 - acc: 0.8411
Epoch 2/5
60000/60000==============================] - 6s 92us/sample - loss: 0.2930 - acc: 0.8914
Epoch 3/5
60000/60000==============================] - 5s 91us/sample - loss: 0.2463 - acc: 0.9079
Epoch 4/5
60000/60000==============================] - 5s 90us/sample - loss: 0.2156 - acc: 0.9187
Epoch 5/5
60000/60000==============================] - 6s 92us/sample - loss: 0.1874 - acc: 0.9307
10000/10000==============================] - 0s 42us/sample - loss: 0.2589 - acc: 0.9089

可以看到训练集和验证集的准确率分别达到了 93% 和 91%。

你可以尝试训练更多的 epoch,比如 20 个 epoch,但这可能会过拟合,因为模型在训练集上学习得太好了。如果训练集里只有红色的鞋子,那么模型辨认红色鞋子的能力非常强,但是对其他颜色鞋子的辨认能力会非常弱。

下面,我们来一步一步看看这个网络的细节。

数据预处理

在数据输入网络之前,我们需要变换训练集数据的形状,把 6000 个 [28,28,1] 的图片 list,转换为一个 tensor,形状是 [6000, 28, 28, 1]。同理,测试集的图片也需要转换形状。

1
2
3
4
5
6
7
import tensorflow as tf
mnist = tf.keras.datasets.fashion_mnist
(training_images, training_labels), (test_images, test_labels) = mnist.load_data()
training_images=training_images.reshape(60000, 28, 28, 1)
training_images=training_images / 255.0
test_images = test_images.reshape(10000, 28, 28, 1)
test_images=test_images/255.0

定义模型

接下来就是定义模型。包括卷积层和池化层。

其中卷积层的参数含义如下:

  • 32 表示输出的 channel 数量是 32。
  • (3, 3) 表示卷积核大小是 \(3 \times 3\)
  • input_shape 表示输入图片的形状。
1
2
3
model = tf.keras.models.Sequential([
tf.keras.layers.Conv2D(32, (3,3), activation='relu', input_shape=(28, 28, 1)),
tf.keras.layers.MaxPooling2D(2, 2),

继续添加另一个卷积层和池化层:

1
2
tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2)

接着添加全连接层

1
2
3
4
  tf.keras.layers.Flatten(),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(10, activation='softmax')
])

最后编译模型,并开始训练。

可视化卷积和池化

测试集中,索引为 0、23、28 的图片的类别都是 9,它们都是鞋子。

1
print(test_labels[:100])

下面的代码是对每一层的卷积层输出进行可视化。

首先把模型每一层的取出来,作为一个输出的列表,然后封装一个新的模型,输出就是这个列表。

然后循环每个输出,首先把图片的形状,变换为 [1, 28, 28, 1],表示[batch_size, height, width, channel]

得到每层的输出 f1f2f3f4 ,形状是 [batch_size, height, width, channel],使用 f1[0, : , :, CONVOLUTION_NUMBER] 取出第一个图片的第 CONVOLUTION_NUMBER 个 channel,进行可视化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import matplotlib.pyplot as plt
f, axarr = plt.subplots(3,4)
FIRST_IMAGE=0
SECOND_IMAGE=23
THIRD_IMAGE=28
CONVOLUTION_NUMBER = 1
from tensorflow.keras import models
# 把 4 个输出作为一个列表
layer_outputs = [layer.output for layer in model.layers]
# 封装一个新的模型,输出就是一个列表
activation_model = tf.keras.models.Model(inputs = model.input, outputs = layer_outputs)
# 循环 4 个输出列表。
for x in range(0,4):
# 第一张图片
f1 = activation_model.predict(test_images[FIRST_IMAGE].reshape(1, 28, 28, 1))[x]
axarr[0,x].imshow(f1[0, : , :, CONVOLUTION_NUMBER], cmap='inferno')
axarr[0,x].grid(False)
f2 = activation_model.predict(test_images[SECOND_IMAGE].reshape(1, 28, 28, 1))[x]
axarr[1,x].imshow(f2[0, : , :, CONVOLUTION_NUMBER], cmap='inferno')
axarr[1,x].grid(False)
f3 = activation_model.predict(test_images[THIRD_IMAGE].reshape(1, 28, 28, 1))[x]
axarr[2,x].imshow(f3[0, : , :, CONVOLUTION_NUMBER], cmap='inferno')
axarr[2,x].grid(False)

输出如下:


# 手动实现卷积

下面,我们手动实现卷积。我们首先加载 scipyascent 图片。

1
2
3
4
import cv2
import numpy as np
from scipy import misc
i = misc.ascent()

然后显示图片

1
2
3
4
5
6
import matplotlib.pyplot as plt
plt.grid(False)
plt.gray()
plt.axis('off')
plt.imshow(i)
plt.show()

下面获取这张图片的宽和高

1
2
3
i_transformed = np.copy(i)
size_x = i_transformed.shape[0]
size_y = i_transformed.shape[1]

接着定义一个 \(3 \times 3\) 的卷积核。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# This filter detects edges nicely
# It creates a convolution that only passes through sharp edges and straight
# lines.

#Experiment with different values for fun effects.
#filter = [ [0, 1, 0], [1, -4, 1], [0, 1, 0]]

# A couple more filters to try for fun!
filter = [ [-1, -2, -1], [0, 0, 0], [1, 2, 1]]
#filter = [ [-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]

# If all the digits in the filter don't add up to 0 or 1, you
# should probably do a weight to get it to do so
# so, for example, if your weights are 1,1,1 1,2,1 1,1,1
# They add up to 10, so you would set a weight of .1 if you want to normalize them
weight = 1

然后开始计算卷积操作。下面使用循环计算卷积操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
for x in range(1,size_x-1):
for y in range(1,size_y-1):
convolution = 0.0
convolution = convolution + (i[x - 1, y-1] * filter[0][0])
convolution = convolution + (i[x, y-1] * filter[0][1])
convolution = convolution + (i[x + 1, y-1] * filter[0][2])
convolution = convolution + (i[x-1, y] * filter[1][0])
convolution = convolution + (i[x, y] * filter[1][1])
convolution = convolution + (i[x+1, y] * filter[1][2])
convolution = convolution + (i[x-1, y+1] * filter[2][0])
convolution = convolution + (i[x, y+1] * filter[2][1])
convolution = convolution + (i[x+1, y+1] * filter[2][2])
convolution = convolution * weight
if(convolution<0):
convolution=0
if(convolution>255):
convolution=255
i_transformed[x, y] = convolution

然后显示经过卷积的图片。

1
2
3
4
5
6
# Plot the image. Note the size of the axes -- they are 512 by 512
plt.gray()
plt.grid(False)
plt.imshow(i_transformed)
#plt.axis('off')
plt.show()

如下所示:


手动实现池化

下面的代码是手动实现池化操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
new_x = int(size_x/2)
new_y = int(size_y/2)
newImage = np.zeros((new_x, new_y))
for x in range(0, size_x, 2):
for y in range(0, size_y, 2):
pixels = []
pixels.append(i_transformed[x, y])
pixels.append(i_transformed[x+1, y])
pixels.append(i_transformed[x, y+1])
pixels.append(i_transformed[x+1, y+1])
newImage[int(x/2),int(y/2)] = max(pixels)

# Plot the image. Note the size of the axes -- now 256 pixels instead of 512
plt.gray()
plt.grid(False)
plt.imshow(newImage)
#plt.axis('off')
plt.show()

池化后的图片如下所示:


CNN 使用案例

在上面的例子中,我们使用了 CNN,但是标签是另外标注的。TensorFlow 中提供了一个方便的 ImageGenerator ,将文件夹的名字作为 label,自动标注。

例如,文件夹结构如下所示:其中 Training 表示训练集,里面的每个子文件夹存放一类图片,文件夹的名字就是类别。同理,Validation 表示验证集。


首先,我们下载训练集和验证集数据。

1
!wget --no-check-certificate https://storage.googleapis.com/laurencemoroney-blog.appspot.com/horse-or-human.zip -O horse-or-human.zip
1
!wget --no-check-certificate https://storage.googleapis.com/laurencemoroney-blog.appspot.com/validation-horse-or-human.zip -O validation-horse-or-human.zip

然后解压训练集和测试集。

1
2
3
4
5
6
7
8
9
10
11
12
import os
import zipfile

# 解压训练集
local_zip = 'horse-or-human.zip'
zip_ref = zipfile.ZipFile(local_zip, 'r')
zip_ref.extractall('horse-or-human')
# 解压验证集
local_zip = 'validation-horse-or-human.zip'
zip_ref = zipfile.ZipFile(local_zip, 'r')
zip_ref.extractall('validation-horse-or-human')
zip_ref.close()

定义每个类别的文件夹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Directory with our training horse pictures
train_horse_dir = os.path.join('horse-or-human/horses')

# Directory with our training human pictures
train_human_dir = os.path.join('horse-or-human/humans')

# Directory with our training horse pictures
validation_horse_dir = os.path.join('validation-horse-or-human/horses')

# Directory with our training human pictures
validation_human_dir = os.path.join('validation-horse-or-human/humans')

train_horse_names = os.listdir(train_horse_dir)
train_human_names = os.listdir(train_human_dir)

validation_horse_hames = os.listdir(validation_horse_dir)
validation_human_names = os.listdir(validation_human_dir)

定义模型,由于我们是 2 分类,因此最后一层使用 sigmoid 激活函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import tensorflow as tf
model = tf.keras.models.Sequential([
# Note the input shape is the desired size of the image 150x150 with 3 bytes color
# This is the first convolution
tf.keras.layers.Conv2D(16, (3,3), activation='relu', input_shape=(150, 150, 3)),
tf.keras.layers.MaxPooling2D(2, 2),
# The second convolution
tf.keras.layers.Conv2D(32, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
# The third convolution
tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
# The fourth convolution
#tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
#tf.keras.layers.MaxPooling2D(2,2),
# The fifth convolution
#tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
#tf.keras.layers.MaxPooling2D(2,2),
# Flatten the results to feed into a DNN
tf.keras.layers.Flatten(),
# 512 neuron hidden layer
tf.keras.layers.Dense(512, activation='relu'),
# Only 1 output neuron. It will contain a value from 0-1 where 0 for 1 class ('horses') and 1 for the other ('humans')
tf.keras.layers.Dense(1, activation='sigmoid')
])

打印模型的信息

1
model.summary()

由于是 2 分类,使用了 sigmoid,因此损失函数使用 binary_crossentropy

1
2
3
4
5
from tensorflow.keras.optimizers import RMSprop

model.compile(loss='binary_crossentropy',
optimizer=RMSprop(lr=0.001),
metrics=['accuracy'])

下面,我们不需要把图片的类别转换为数字,使用 ImageGenerator 可以自动帮我们完成这个转换过程。对于训练集和测试集,我们分别需要一个 ImageGenerator

rescale=1/255 表示把像素值乘以 1/255,归一化到 [0,1] 之间。

ImageGenerator 可以通过 .flow(data, labels) 方法或者 .flow_from_directory(directory) 加载图片。然后在模型中,可以调用 fitevaluate_generator,或者 predict_generator 来接收 ImageGenerator 参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from tensorflow.keras.preprocessing.image import ImageDataGenerator

# All images will be rescaled by 1./255
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(
'horse-or-human/', # This is the source directory for training images
target_size=(150, 150), # All images will be resized to 150x150
batch_size=128,
# Since we use binary_crossentropy loss, we need binary labels
class_mode='binary')

# Flow training images in batches of 128 using train_datagen generator
validation_generator = validation_datagen.flow_from_directory(
'validation-horse-or-human/', # This is the source directory for training images
target_size=(150, 150), # All images will be resized to 150x150
batch_size=32,
# Since we use binary_crossentropy loss, we need binary labels
class_mode='binary')

下面开始训练模型。

1
2
3
4
5
6
7
history = model.fit_generator(
train_generator,
steps_per_epoch=8,
epochs=15,
verbose=1,
validation_data = validation_generator,
validation_steps=8)

下面对模型进行测试,path 表示图片路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
from keras.preprocessing import image

path = 'validation-horse-or-human/horses/horse1-000.png'
img = image.load_img(path, target_size=(150, 150))
x = image.img_to_array(img)
# 扩充一个 batch 的维度。
x = np.expand_dims(x, axis=0)

classes = model.predict(images, batch_size=10)
print(classes[0])
if classes[0]>0.5:
print(path + " is a human")
else:
print(path + " is a horse")

接着,对卷积层和池化层的输出进行可视化。

  • 首先把所有层的输出作为一个 list,构建一个新模型 visualization_model
  • 然后随机选择一张图片。读取图片,并添加一个 batch 的维度,最后的形状是 (1, 150, 150, 3),表示 (batch_size, heiht, width, channel)
  • 将图片经过 visualization_model,得到输出的 list
  • 遍历输出的 list,判断 shape 是否为 4,表示卷积层和池化层的输出,排除全连接层的输出。得到该层的输出 feature_map
    • 遍历 feature_map,通过 feature_map[0, :, :, i] 得到每个 channel 的输出,形状为 (height,width),做一些后处理。赋值给 display_grid[:, i * size : (i + 1) * size]。最后进行可视化。display_grid 表示一个 feature_map 的多个 channel。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import matplotlib.pyplot as plt
import numpy as np
import random
from tensorflow.keras.preprocessing.image import img_to_array, load_img

# Let's define a new Model that will take an image as input, and will output
# intermediate representations for all layers in the previous model after
# the first.
successive_outputs = [layer.output for layer in model.layers[1:]]
#visualization_model = Model(img_input, successive_outputs)
# 把所有层的输出作为一个 list,构建一个新模型
visualization_model = tf.keras.models.Model(inputs = model.input, outputs = successive_outputs)
# Let's prepare a random input image from the training set.
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)
# 读取图片,并添加一个 batch 的维度
img = load_img(img_path, target_size=(150, 150)) # this is a PIL image
x = img_to_array(img) # Numpy array with shape (150, 150, 3)
x = x.reshape((1,) + x.shape) # Numpy array with shape (1, 150, 150, 3)

# Rescale by 1/255
x /= 255

# Let's run our image through our network, thus obtaining all
# intermediate representations for this image.
successive_feature_maps = visualization_model.predict(x)

# These are the names of the layers, so can have them as part of our plot
layer_names = [layer.name for layer in model.layers]

# Now let's display our representations
for layer_name, feature_map in zip(layer_names, successive_feature_maps):
# feature_map.shape == 4 表示 卷积层和池化层的输出,排除全连接层的输出
if len(feature_map.shape) == 4:
# Just do this for the conv / maxpool layers, not the fully-connected layers
n_features = feature_map.shape[-1] # number of features in feature map
# The feature map has shape (1, size, size, n_features)
size = feature_map.shape[1]
# We will tile our images in this matrix
display_grid = np.zeros((size, size * n_features))
for i in range(n_features):
# Postprocess the feature to make it visually palatable
x = feature_map[0, :, :, i]
x -= x.mean()
x /= x.std()
x *= 64
x += 128
x = np.clip(x, 0, 255).astype('uint8')
# display_grid 表示一个 feature_map 的多个 channel。
# We'll tile each filter into this big horizontal grid
display_grid[:, i * size : (i + 1) * size] = x
# Display the grid
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')

最后杀死进程,释放资源。

1
2
import os, signal
os.kill(os.getpid(), signal.SIGKILL)

评论