1、深度学习网络概述
1.1 深度学习网络简单介绍
常见的深度学习网络由卷积层+池化层+全连接层等组成,通过构建多层卷积层和池化层,可以学习到一些有用的模型,一个简单的深度学习模型结构如下所示:
卷积层 -> 池化层 -> ... -> 卷积层 -> 池化层 -> 全连接层 -> 全连接层
对应的函数层如下所示:
Conv2D -> Pool -> .. -> Conv2D -> Pool -> Flatten -> Dense -> Dense
2、Python代码示例
2.1 准备数据阶段(读入图片转成数组)
2.1.1 代码示例
import tensorflow as tf;
import numpy as np;
from PIL import Image;
import matplotlib.pyplot as plt;imgObj = Image.open("D:/study/image/1.png");
plt.subplot(2,3,2);
plt.imshow(imgObj);
plt.title(label='layers 1: conv2d',loc='center');img = np.array(imgObj, dtype='float32');
# 一般彩色图片由RGB三色构成,所以读入图片后数组的原始维度是: HEIGHT * WIDTH * 3,其中3可以理解为图片的通道数
# conv2d函数需要4维的输入,我们需要把图片3维数组转成4维的数组
# 4维数组的维度依次是:BATCH_SIZE,HEIGHT,WIDTH,CHANNELS
x_train = img[tf.newaxis,...];
print("img.shape=", img.shape, ", x_train=", x_train.shape);
2.1.2 代码执行结果
因为图片本身宽和高是1200*675,又是彩色图片,所以数据维度输出如下:
BATCH_SIZE=1,图片只有一张;
HEIGHT=675,图片的高度;
WIDTH=1200,图片的宽度;
CHANNELS=3,彩色图片有RGB三色,所以通道数是3;
下图是运行输出日志:
2.2 第1层:卷积运算Conv2D
2.2.1 代码示例
conv2d_1 = tf.keras.layers.Conv2D(filters=32, kernel_size=3, strides=(1,1),
padding='same', data_format='channels_last')(x_train);
print("conv2d_1.shape=", conv2d_1.shape);
# 打印卷积运算结果前3个通道的图片
for i in range(3):
showImg = conv2d_1[0,:,:,i];
plt.subplot(2,3,4+i);
plt.imshow(showImg, 'gray');
plt.title(label="channel"+str((i+1)), loc='center');
plt.show();
2.2.2 代码执行结果
在我们查看输出结果之前,需要简单介绍一下Conv2D函数常用的参数:
1、filters=32,这是通道数。原始彩色图片的通道是3,经过卷积运算后,通道数变成32;
2、kernel_size=3,这是卷积运算的核数。等价于kernel_size=(3,3),卷积运算从图片左上角开始,每次取3*3大小位置的数字,乘以一个3*3大小的过滤器(按元素位置对应相乘求和),重复计算到图片的右下角。
补充:图示卷积运算的计算过程
原始图片左上角4个元素,按位置乘以kernel的4个元素,得到输出的左上角位置的值2;
原始图片上方居中的4个元素,按位置乘以kernel的4个元素,得到输出的上方居中位置的值0;
原始图片右上角的4个元素,按位置乘以kernel的4个元素,得到输出的右上角位置的值-4;
同理,依次从左往右,从上到下读取原始图片的2*2元素,乘以kernel,最终得到一个3*3的输出。
3、strides=(1,1),这是步长。步长是控制读取原始图片的移动步伐,strides=(1,1)的第一个值控制着从左往右的移动速度,1表示每次移动一格;第二个值则控制从上到下的移动速度,1表示每次移动一格;由此可以理解步长越大,移动速度越快,最终输出的尺寸就越小。
4、padding='same',这是控制上下左右是否填充0,取值same,表示要填充0,取值为valid,表示不填充0。下图是填充0的示意图,因为kernel=3,所以是上下左右各填充多一维0,如果kernel=5,那么上下左右就要各多填充二维0。填充维度计算公式:p = ⌊kernel / 2⌋,这样才能保持输出尺寸不变(前提条件是步长为1,否则无法保证输出尺寸大小不变化)。
5、data_format='channels_last',这是指明通道是哪一维。取值channels_last,表示原始数据的通道在高度和宽度之后,数组维度是(BATCH_SIZE,HEIGHT,WIDTH,CHANNELS);取值channels_first,表示原始数据的通道在高度和宽度之前,数组维度是(BATCH_SIZE,CHANNELS,HEIGHT,WIDTH)。
当然Conv2D还有其他参数,想了解更多知识,自己可以去查阅资料。
下图是运行输出日志:
下图是第1层—卷积运算层,卷积运算结果前3个通道的输出效果图:
2.3 第2层:最大池化运算MaxPool2D
2.3.1 代码示例
pool_1 = tf.keras.layers.MaxPool2D(pool_size=(2,2), strides=(1,1), padding='same')(conv2d_1);
print("pool_1.shape=", pool_1.shape);
plt.subplot(2,3,2);
plt.imshow(imgObj);
plt.title(label='layers 2: pool',loc='center');
# 打印最大池化结果前3通道的图片
for i in range(3):
showImg = pool_1[0,:,:,i];
plt.subplot(2,3,4+i);
plt.imshow(showImg, 'gray');
plt.title(label="channel"+str((i+1)), loc='center');
plt.show();
2.3.2 代码执行结果
在查看输出日志和效果图之前,先解释一些MaxPool2D函数的参数:
1、pool_size=(2,2),这是指定池化运算过滤器大小。下图是最大池化运算的示意图,从左往右,从上往下,依次取2*2的元素,取出当中的最大值,作为输出的内容。
2、strides=(1,1),这是步长。步长是控制读取原始图片的移动步伐,strides=(1,1)的第一个值控制着从左往右的移动速度,1表示每次移动一格;第二个值则控制从上到下的移动速度,1表示每次移动一格;由此可以理解步长越大,移动速度越快,最终输出的尺寸就越小。
注意:如果不设置步长,则步长默认取pool_size的值。
3、padding='same',这是控制上下左右是否填充0,取值same,表示要填充0,取值为valid,表示不填充0。pool_size=(2,2),上下左右各填充多一维0,如果pool_size=(4,4),那么上下左右就要各多填充二维0。填充维度计算公式:p = ⌊pool_size / 2⌋,这样才能保持输出尺寸不变(前提条件是步长为1,否则无法保证输出尺寸大小不变化)。
下图是填充0的示意图:
下图是运行输出日志:
下图是第2层—最大池化层,池化运算前3个通道的输出效果图:
2.4 第3层:卷积运算Conv2D
2.4.1 代码示例
conv2d_2 = tf.keras.layers.Conv2D(filters=16, kernel_size=3, strides=(1,1),
padding='same', data_format='channels_last')(pool_1);
print("conv2d_2.shape=", conv2d_2.shape);
plt.subplot(2,3,2);
plt.imshow(imgObj);
plt.title(label='layers 3: conv2d',loc='center');
# 打印卷积结果前3通道的图片
for i in range(3):
showImg = conv2d_2[0,:,:,i];
plt.subplot(2,3,4+i);
plt.imshow(showImg, 'gray');
plt.title(label="channel"+str((i+1)), loc='center');
plt.show();
2.4.2 代码执行结果
前面已经解释过函数的参数了,这里不再重复。
下图是运行输出日志:
下图是第3层—卷积运算层,卷积运算结果前3个通道的输出效果图:
2.5 第4层:最大池化运算MaxPool2D
2.5.1 代码示例
pool_2 = tf.keras.layers.MaxPool2D(pool_size=(2,2), strides=(1,1), padding='same')(conv2d_2);
print("pool_2.shape=", pool_2.shape);
plt.subplot(2,3,2);
plt.imshow(imgObj);
plt.title(label='layers 4: pool',loc='center');
# 打印最大池化结果前3通道的图片
for i in range(3):
showImg = pool_2[0,:,:,i];
plt.subplot(2,3,4+i);
plt.imshow(showImg, 'gray');
plt.title(label="channel"+str((i+1)), loc='center');
plt.show();
2.5.2 代码执行结果
下图是运行输出日志:
下图是第4层—最大池化运算层,最大池化运算结果前3个通道的输出效果图:
2.6 Flatten函数
2.6.1 代码示例
flatten = tf.keras.layers.Flatten(data_format='channels_last')(pool_2);
print("flatten.shape=", flatten.shape);
2.6.1 代码执行结果
在看输出结果之前,先解释一下Flatten函数的参数:
1、data_format='channels_last',这是指明通道是哪一维。取值channels_last,表示原始数据的通道在高度和宽度之后,数组维度是(BATCH_SIZE,HEIGHT,WIDTH,CHANNELS);取值channels_first,表示原始数据的通道在高度和宽度之前,数组维度是(BATCH_SIZE,CHANNELS,HEIGHT,WIDTH)。
下图是运行输出日志:
2.7 第5层:全连接运算Dense
2.7.1 代码示例
dense_1 = tf.keras.layers.Dense(units=16, activation='relu')(flatten);
print("dense_1.shape=", dense_1.shape);
2.7.2 代码执行结果
在看输出结果之前,先解释一下Dense函数的参数:
1、units=16, 这是表示输出的维度,就是输出多少个节点;
2、activation='relu',这是激活函数,常见的激活函数在tf.keras.activations里面定义,这里使用relu激活函数。
下图是运行输出日志:
2.8 第6层:全连接运算Dense
2.8.1 代码示例
y_pred = tf.keras.layers.Dense(units=1, activation='sigmoid')(dense_1);
print("y_pred.shape=", y_pred.shape, ", y_pred=", y_pred);
2.8.2 代码执行结果
Dense函数之前已经解释过了,这里不再重复;
因为这已经模型是最后一层,所以我们直接输出模型预测结果。
下图是运行输出日志:
2.9 计算误差
2.9.1 代码示例
# 标记图片是否是风景图
y_true = np.array([1]);
print("y_true.shape=", y_true.shape, ", y_true=", y_true);# 平均绝对误差
loss = tf.keras.losses.MAE(y_true, y_pred);
print("loss.shape=", loss.shape, ",loss=", loss);
2.9.2 代码运行结果
在看输出结果之前,我们先解释一下上面的代码:
1、y_true=[1],因为我们输入只有一张图片,所以只需要定义长度为1的数组,来标记图片的分类,我们约定值为1表示图片是风景图,那么值为0就不是风景图;
2、我们使用MAE(平均绝对误差),计算最终的误差值;常见的误差函数,定义在tf.keras.losses,大家可以阅读,选择合适的误差函数。
下图是运行输出日志:
3 总结
1、Conv2D卷积层,负责学习图片一些特征。靠前的卷积网络层,可以学习到简单的特征(边界,位置等),后面的卷积层则在简单特征上,学习更复杂的特征(线条,形状等),从而实现功能强大的系统;
2、MaxPool2D池化层,一是可以放大占主导位置的特征,二是能够有效减少运算,本文设置strides=(1,1),所以没有缩小输出尺寸,实际应用过程中可以设置strides=(2,2),这样输出尺寸直接缩小为原图片的一半,极大地减少运算量;
3、可以在深度学习网络中,添加多层卷积层和池化层,让网络学习到更复杂的特征;
4、Flatten函数,是把多维数组平铺成1维数组,为了后续的全连接层而准备;
5、Dense全连接层,构造合适的输出结果;
6、误差函数,在训练过程可以根据训练误差,调整我们的模型参数;在测试/验证过程,可以记录测试误差,从而重新调整我们的训练模型;
文中只用极少的一部分函数和功能,更多的用法需要大家自行摸索。