学习自中国大学MOOC TensorFlow学习课程
卷积神经网络的的原理
简而言之,如果取一个二维数组(通常是3x3或5x5)并将其应用到图像上。通过根据该矩阵内的公式改变底层像素,就可以进行图像边缘检测等工作。例如一个3x3的矩阵,它是为边缘检测而定义的,扫描整个图像,对每个像素都这样做,最终会得到一张边缘被增强的新图像。
这种计算对于计算机视觉来说是非常理想的,因为通常情况下,能够像这样被突出显示的特征才是区分一个物品和另一个物品的关键。卷积使得所需要的信息量会少很多......因为只需要对突出显示的特征进行训练。
这就是卷积神经网络的概念。在全连接层之前,增加一些层来做卷积,那么输入全连接层的信息就会更加集中,也可能更加准确。
它的核心思路,即通过卷积操作缩小了图像的内容,将模型注意力集中在图像特定的、明显的特征上。
#通过一张二维灰度图像,来探索卷积的工作原理。可以从scipy库中获取名为"上升"的图像
import cv2
import numpy as np
from scipy import misc
i = misc.ascent()
import matplotlib.pyplot as plt
plt.grid(False)
plt.gray()
plt.axis('off')
plt.imshow(i)
plt.show()
output_3_0.png
#图像是以numpy数组的形式存储的,所以我们只需复制这个数组就可以对图像进行转换。我们还可以得到图像的尺寸,这样就可以用循环遍历它的数据。
i_transformed = np.copy(i)
size_x = i_transformed.shape[0]
size_y = i_transformed.shape[1]
i_transformed.shape
(512, 512)
接下来创建一个3x3的过滤器
# 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
# 如果过滤器中的所有数字加起来不是0或1,那么就应该设置权重,让过滤器的总和为0或1。
# 例如,如果过滤器是1,1,1,1,2,1,1,1,1。
# 它们加起来是10,所以可以设置0.1的权重,使它标准化。
weight = 1
接下来让我们创建一个卷积,扫描整个图像,留出1个像素的边框,并将当前像素的每个邻居乘以过滤器中定义的值。
即当前像素上方和左侧的邻域将乘以滤镜中左上方的项,等等。然后我们再将结果乘以权重,然后确保结果在0-255的范围内。
最后我们将把新的值加载到转换后的图像中。
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
现在,我们可以绘制图像,看看卷积的效果!
#
# 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()
output_7_0.png
这段代码将显示一个(2,2)池化后的效果。
它的想法是用2x2尺寸的矩阵在图像上扫描,
查看像素和它的右方、下方和右下方的近邻像素。取其中最大的一个,并将其加载到新图像中。
这样,新的图像将是旧图像的1/4大小--通过这个过程,X和Y上的尺寸减半。
你会发现,尽管进行了这样的压缩,图像特征还是得到了保留。
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()
output_8_0.png
卷积神经网络的代码实现
一、加载数据
import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
print(tf.__version__)
2.3.0
mnist = tf.keras.datasets.fashion_mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
plt.imshow(train_images[0])
<matplotlib.image.AxesImage at 0x19bfdc85e50>
output_13_1.png
二、数据归一化
training_images = train_images/255
testing_images = test_images/255
plt.imshow(training_images[0])
<matplotlib.image.AxesImage at 0x19bfdfc3400>
output_16_1.png
三、构建卷积神经网络
接下来是定义模型。首先要添加一个卷积层。参数是
- 你想要生成的卷积数(过滤器数量)。这个数值是任意的,但最好是从32开始的倍数。
- 卷积的大小(过滤器的大小),在本例中为3x3网格。这是最常用的尺寸。
- 要使用的激活函数 -- 在本例中,我们将使用relu,你可能还记得它相当于当x>0时返回x,否则返回0。
- 在第一层,设定输入数据的形状。
在卷积层之后加上一个MaxPooling层,用来压缩图像,同时保持卷积所强调的特征内容。通过为MaxPooling指定(2,2),效果是将图像的大小缩小四分之一。
它的想法是创建一个2x2的像素数组,然后选取最大的一个,从而将4个像素变成1个,在整个图像中重复这样做,这样做的结果是将水平像素的数量减半,垂直像素的数量减半,有效地将图像缩小25%。
可以调用model.summary()来查看网络的大小和形状,你会注意到,每一个MaxPooling(池化)层之后,图像的大小都会以这种方式减少为原来的1/4
model = keras.Sequential()
#卷积层64个3*3像素的filter过滤器;激活函数relu, 输入size, 28*28, 1个通道(1个通道的意思是?)
model.add(keras.layers.Conv2D(64, (3,3), activation='relu', input_shape=(28,28,1)))
#Max pooling
model.add(keras.layers.MaxPooling2D(2, 2))
#再加一块卷积和Max pooling
model.add(keras.layers.Conv2D(64, (3,3), activation='relu'))
model.add(keras.layers.MaxPooling2D(2, 2))
#原本全连接的模型
model.add(keras.layers.Flatten()) #不用再指定输入size
model.add(keras.layers.Dense(128, activation=tf.nn.relu))
model.add(keras.layers.Dense(10, activation=tf.nn.softmax))
指定优化器和损失函数进行编译
model.compile(optimizer=tf.optimizers.Adam(), loss=tf.losses.sparse_categorical_crossentropy, metrics=['accuracy'])
查看模型的结构
model.summary()
#第一层parameters:(3*3+1) * 64 = 640
#第二层: 没有调整参数 0
#第三层:由于一张图经过第一层过滤器变成64张,所以是:(3*3*64张图片 + 1) * 64 = 36928
#第四层:0
#第五层展平像素:0
#第六层:(1600+1) * 128 = 204928
#第七层:(128+1) * 10 = 1290
#经过过滤器图片的每条边少2个像素, 经过max pooling后,图片只有原本面积的1/4,故边长只有原本的一半
Model: "sequential_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_2 (Conv2D) (None, 26, 26, 64) 640
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 13, 13, 64) 0
_________________________________________________________________
conv2d_3 (Conv2D) (None, 11, 11, 64) 36928
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 5, 5, 64) 0
_________________________________________________________________
flatten_1 (Flatten) (None, 1600) 0
_________________________________________________________________
dense_2 (Dense) (None, 128) 204928
_________________________________________________________________
dense_3 (Dense) (None, 10) 1290
=================================================================
Total params: 243,786
Trainable params: 243,786
Non-trainable params: 0
_________________________________________________________________
四、训练模型
class myCallback(tf.keras.callbacks.Callback):
def on_epoch_end(self, epoch, logs={}):
if(logs.get('accuracy')>0.99):
print("\nReached 99% accuracy so cancelling training!")
self.model.stop_training = True
callbacks = myCallback()
训练数据需要改变维度(shape)。这是因为第一次卷积期望一个包含所有数据的单一张量,所以要把训练数据设置为60000x28x28x1的一个4D列表,测试图像也是如此处理。
如果不这样做,会在训练时得到一个错误,因为卷积操作将不能识别数据形状。
#直接运行会报错,需要的输入数据维度是4维,实际输入数据是3维
model.fit(training_images, train_labels, epochs=20, callbacks=[callbacks])
Epoch 1/20
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
ValueError: Input 0 of layer sequential is incompatible with the layer: : expected min_ndim=4, found ndim=3. Full shape received: [32, 28, 28]
将数据修改为4维,-1代表我们不去管这个行数,让程序自动生成,程序正常运行
model.fit(training_images.reshape(-1,28,28,1), train_labels, epochs=30, callbacks=[callbacks])
Epoch 1/30
1875/1875 [==============================] - 47s 25ms/step - loss: 0.4472 - accuracy: 0.8382
Epoch 2/30
……
Epoch 27/30
1875/1875 [==============================] - ETA: 0s - loss: 0.0279 - accuracy: 0.9902
Reached 99% accuracy so cancelling training!
1875/1875 [==============================] - 45s 24ms/step - loss: 0.0279 - accuracy: 0.9902
<tensorflow.python.keras.callbacks.History at 0x19bfe8cb9d0>
五、将卷积和池化的结果可视化(查看每一层做了什么)
import matplotlib.pyplot as plt
layer_outputs = [layer.output for layer in model.layers] #将model每一层提取出来
activation_model = tf.keras.models.Model(inputs=model.input, outputs=layer_outputs) #将model的输入和输出层放在一起,构建激活模型
#预测一张图片testing_images[0]
pred = activation_model.predict(testing_images[0].reshape(1,28,28,1))
len(pred) #七个层的输出
7
pred[0].shape #第一层的卷积层
(1, 26, 26, 64)
#提取第一层卷积层的其中一个过滤器
plt.imshow(pred[0][0,:,:,1]) #取第一个卷积层的所有行所有列,下标为1(第二个)的过滤器
<matplotlib.image.AxesImage at 0x19bfe8b78b0>
output_31_1.png
plt.imshow(pred[1][0,:,:,1]) #提取第二层maxpooling层
<matplotlib.image.AxesImage at 0x19b9d1720a0>
output_32_1.png
这段代码将以图形方式向我们展示卷积的结果。print(test_labels[:100])向我们展示了测试集中的前100个标签,你可以看到索引0、索引23和索引28的标签都是相同的值(9),它们都是鞋子。
让我们来看看对图像做卷积操作的结果,会看到它们之间的共同特征出现。之后,当DNN在该数据上进行训练时,模型训练的工作内容就少了很多,模型会在卷积/池化的基础上找到鞋子图像的共性。
print(test_labels[:100])
[9 2 1 1 6 1 4 6 5 7 4 5 7 3 4 1 2 4 8 0 2 5 7 9 1 4 6 0 9 3 8 8 3 3 8 0 7
5 7 9 6 1 3 7 6 7 2 1 2 2 4 4 5 8 2 2 8 4 8 0 7 7 8 5 1 1 2 3 9 8 7 0 2 6
2 3 1 2 8 4 1 8 5 9 5 0 3 2 0 6 5 3 6 7 1 8 0 1 4 2]
import matplotlib.pyplot as plt
f, axarr = plt.subplots(3,4)
FIRST_IMAGE=0
SECOND_IMAGE=7
THIRD_IMAGE=26
CONVOLUTION_NUMBER = 1
from tensorflow.keras import models
layer_outputs = [layer.output for layer in model.layers]
activation_model = tf.keras.models.Model(inputs = model.input, outputs = layer_outputs)
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)
output_35_1.png
完整代码展示:
#加载包
import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
print(tf.__version__)
#加载数据
mnist = tf.keras.datasets.fashion_mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
#数据归一化
training_images = train_images/255
testing_images = test_images/255
#构建卷积神经网络模型
model = keras.Sequential([
#卷积层64个3*3像素的filter过滤器;激活函数relu, 输入size, 28*28, 1个通道(1个通道的意思是?)
keras.layers.Conv2D(64, (3,3), activation='relu', input_shape=(28,28,1)),
#Max pooling
keras.layers.MaxPooling2D(2, 2),
#再加一块卷积和Max pooling
keras.layers.Conv2D(64, (3,3), activation='relu'),
keras.layers.MaxPooling2D(2, 2),
#原本全连接的模型
keras.layers.Flatten(), #不用再指定输入size
keras.layers.Dense(128, activation=tf.nn.relu),
keras.layers.Dense(10, activation=tf.nn.softmax)
])
#指定优化器和损失函数进行编译
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
#构建回调函数类
class myCallback(tf.keras.callbacks.Callback):
def on_epoch_end(self, epoch, logs={}):
if(logs.get('accuracy')>0.99):
print("\nReached 99% accuracy so cancelling training!")
self.model.stop_training = True
callbacks = myCallback() #实例化回调函数
#模型训练
model.fit(training_images.reshape(-1,28,28,1), train_labels, epochs=5, callbacks=[callbacks])
2.3.0
Epoch 1/5
1875/1875 [==============================] - 44s 23ms/step - loss: 0.4418 - accuracy: 0.8400
Epoch 2/5
1875/1875 [==============================] - 42s 23ms/step - loss: 0.2967 - accuracy: 0.8899
Epoch 3/5
1875/1875 [==============================] - 39s 21ms/step - loss: 0.2509 - accuracy: 0.9077
Epoch 4/5
1875/1875 [==============================] - 42s 22ms/step - loss: 0.2182 - accuracy: 0.9181
Epoch 5/5
1875/1875 [==============================] - 41s 22ms/step - loss: 0.1913 - accuracy: 0.9280
<tensorflow.python.keras.callbacks.History at 0x19b8efb7880>
思考题
练习
-
尝试修改卷积层参数。将32改为16或64。这对准确度和/或训练时间有什么影响?
-
删除最后的卷积层。这将对精度或训练时间产生什么影响?
-
增加更多的卷积层会有什么影响?实验一下吧。
-
除第一项外,删除所有的Convolutions。这样做会有什么影响?请完成实验。
-
在上一节课中,实现了通过一个回调函数来检查模型的损失,并在损失减小到一定量时取消训练。看看是否能在这里实现?
网友评论