各位同学好,今天和大家分享一下TensorFlow2.0中如何构建卷积神经网络ResNet-50,案例内容:现在收集了10位艺术大师的画作,采用卷积神经网络判断某一幅画是哪位大师画的。
数据集:https://pan.baidu.com/share/init?surl=cWMvsl37MOZdxnoOjSGoNQ
提取码: 2h5x
1. 数据加载
在文件夹中将图片按照训练集、验证集、测试集划分好之后,使用tf.keras.preprocessing.image_dataset_from_directory()从文件夹中读取数据。指定参数label_model,'int'代表目标值y是数值类型,即0, 1, 2, 3等;'categorical'代表onehot类型,对应索引的值为1,如图像属于第二类则表示为0,1,0,0,0;'binary'代表二分类。
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import Model, optimizers, layers
#(1)获取数据集
def get_data(height, width, batchsz):
filepath1 = 'C:/Users/admin/.spyder-py3/test/数据集/艺术作品/new_data/train'
train_ds = tf.keras.preprocessing.image_dataset_from_directory(
filepath1,
label_mode='categorical', # 做one-hot编码 ,正数形式"int", 多分类"categorical", 二分类"binary", or None
seed=123,
image_size=(height, width), # resize图片大小
batch_size=batchsz)
# 加载验证集数据
filepath2 = 'C:/Users/admin/.spyder-py3/test/数据集/艺术作品/new_data/val'
val_ds = tf.keras.preprocessing.image_dataset_from_directory(
filepath2,
label_mode='categorical',
seed=123,
image_size=(height, width),
batch_size=batchsz)
# 加载测试集数据
filepath3 = 'C:/Users/admin/.spyder-py3/test/数据集/艺术作品/new_data/test'
test_ds = tf.keras.preprocessing.image_dataset_from_directory(
filepath3,
label_mode='categorical',
seed=123,
image_size=(height, width),
batch_size=batchsz)
return(train_ds, val_ds, test_ds)
# 从文件夹中获取图像
train_ds, val_ds, test_ds = get_data(224, 224, 32) #指定读入图片的宽度高度(和网络输入层大小相同),每个batch的大小
# 类别名称
class_names = train_ds.class_names
print('类别有:',class_names)
# 类别有: ['Alfred_Sisley', 'Edgar_Degas', 'Francisco_Goya', 'Marc_Chagall', 'Pablo_Picasso', 'Paul_Gauguin', 'Peter_Paul_Rubens', 'Rembrandt', 'Titian', 'Vincent_van_Gogh']
2. 数据预处理
定义预先处理函数,将x的每个像素值从[0,255]映射到[-1,1],映射到[0,1]也没问题。使用.map()将数据集中的所有数据放入函数进行处理,对训练数据打乱顺序.shuffle(),但不改变x和y之间的对应关系。
#(2)数据预处理
def processing(x,y):
x = 2 * tf.cast(x, tf.float32)/255.0 - 1 # 将每个像素值从[0,255]映射到[-1,1]
y = tf.cast(y, tf.int32)
return(x,y)
# 构造数据集
train_ds = train_ds.map(processing).shuffle(10000) # 训练数据
val_ds = val_ds.map(processing) # 验证数据
test_ds = test_ds.map(processing) # 测试数据
# 查看数据是否处理正确
sample = next(iter(train_ds)) #构造迭代器,每次运行取出一个batch数据
print('x_batch.shape:', sample[0].shape, 'y_batch.shape', sample[1].shape)
# x_batch.shape: (32, 128, 128, 3) y_batch.shape (32, 10)
# 绘图展示
import matplotlib.pyplot as plt
for i in range(15):
plt.subplot(3,5,i+1)
plt.imshow(sample[0][i]) #sample存放的是一个batch的图像
plt.xticks([]) #不显示坐标刻度
plt.yticks([])
plt.show()
经过处理后的图像如下:
3. 网络构建
接下来到最重要的一步了,构建ResNet50网络,网络的结构图如下:resnet50结构图 ,可以根据这个结构图慢慢敲代码,我这里使用函数的方法构建ResNet50网络。ResNet的原理解释如下:六、ResNet网络详细解析(超详细哦)
#(3)构建RNN-RESNET
# conv_block部分
def conv_block(input_tensor, filters, stride):
# 分别接收卷积核的个数,即特征图的个数
filter1, filter2, filter3 = filters
# ==1== 正向传播部分
# 卷积层
x = layers.Conv2D(filter1, kernel_size=(1,1), strides=stride)(input_tensor)
# BN层
x = layers.BatchNormalization()(x)
# 激活层
x = layers.Activation('relu')(x)
# 卷积层
x = layers.Conv2D(filter2, kernel_size=(3,3), strides=(1,1), padding='same')(x)
# BN层
x = layers.BatchNormalization()(x)
# 激活函数
x = layers.Activation('relu')(x)
# 卷积层
x = layers.Conv2D(filter3, kernel_size=(1,1), strides=(1,1))(x)
# BN层
x = layers.BatchNormalization()(x)
# ==2== shotcut部分
# 卷积层
shotcut = layers.Conv2D(filter3, kernel_size=(1,1), strides=stride)(input_tensor)
# BN层
shotcut = layers.BatchNormalization()(shotcut)
# ==3== 两部分组合
x = layers.add([x, shotcut])
# 激活函数
x = layers.Activation('relu')(x)
# 返回结果
return x
# identity_block部分
def iden_block(input_tensor, filters):
# 接收卷积核的个数
filter1, filter2, filter3 = filters
# ==1== 正向传播
# 卷积层
x = layers.Conv2D(filter1, kernel_size=(1,1), strides=(1,1))(input_tensor)
# BN层
x = layers.BatchNormalization()(x)
# 激活函数
x = layers.Activation('relu')(x)
# 卷积层
x = layers.Conv2D(filter2, kernel_size=(3,3), strides=(1,1), padding='same')(x)
# BN层
x = layers.BatchNormalization()(x)
# 激活函数
x = layers.Activation('relu')(x)
# 卷积层
x = layers.Conv2D(filter3, kernel_size=(1,1), strides=(1,1))(x)
# BN层
x = layers.BatchNormalization()(x)
# ==2== 结合
x = layers.add([x, input_tensor])
# 激活函数
x = layers.Activation('relu')(x)
return x
# 本体
def resnet50(input_shape=[224,224,3], output_shape=10):
# 输入层
inputs = keras.Input(shape=input_shape) #[224,224,3]
# padding,上下左右各三层
x = layers.ZeroPadding2D((3,3))(inputs)
# 卷积层
x = layers.Conv2D(64, kernel_size=(7,7), strides=(2,2))(x) #[112,112,64]
# BN层
x = layers.BatchNormalization()(x) #[112,112,64]
# relu层
x = layers.Activation('relu')(x) #[112,112,64]
# 池化层
x = layers.MaxPool2D(pool_size=(3,3), strides=(2,2))(x) #[55,55,64]
# block1
x = conv_block(x, [64, 64, 256], stride=(1,1)) #[55,55,256]
x = iden_block(x, [64, 64, 256]) #[55,55,256]
x = iden_block(x, [64, 64, 256]) #[55,55,256]
# block2
x = conv_block(x, [128, 128, 256], stride=(2,2)) #[28,28,512]
x = iden_block(x, [128, 128, 256]) #[28,28,512]
x = iden_block(x, [128, 128, 256]) #[28,28,512]
x = iden_block(x, [128, 128, 256]) #[28,28,512]
# block3
x = conv_block(x, [256, 256, 1024], stride=(2,2)) #[14,14,1024]
x = iden_block(x, [256, 256, 1024]) #[14,14,1024]
x = iden_block(x, [256, 256, 1024]) #[14,14,1024]
x = iden_block(x, [256, 256, 1024]) #[14,14,1024]
x = iden_block(x, [256, 256, 1024]) #[14,14,1024]
x = iden_block(x, [256, 256, 1024]) #[14,14,1024]
# block4
x = conv_block(x, [512, 512, 2048], stride=(2,2)) #[7,7,2048]
x = iden_block(x, [512, 512, 2048]) #[7,7,2048]
x = iden_block(x, [512, 512, 2048]) #[7,7,2048]
# 激活函数
x = layers.Activation('relu')(x) #[7,7,2048]
# 平均池化层
x = layers.AveragePooling2D(pool_size=(7,7))(x) #[1,1,2048]
# Flatten层
x = layers.Flatten()(x) #[None,2048]
# 输出层,不做softmax
outputs = layers.Dense(output_shape)(x)
# 构建模型
model = Model(inputs=inputs, outputs=outputs)
# 返回模型
return model
# 创建restnet-50
model = resnet50()
# 查看网络结构
model.summary()
网络结构如下
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_1 (InputLayer) [(None, 224, 224, 3 0 []
)]
zero_padding2d (ZeroPadding2D) (None, 230, 230, 3) 0 ['input_1[0][0]']
conv2d (Conv2D) (None, 112, 112, 64 9472 ['zero_padding2d[0][0]']
)
batch_normalization (BatchNorm (None, 112, 112, 64 256 ['conv2d[0][0]']
alization) )
activation (Activation) (None, 112, 112, 64 0 ['batch_normalization[0][0]']
)
----------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------
省略N多层
----------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------
activation_48 (Activation) (None, 7, 7, 2048) 0 ['add_15[0][0]']
activation_49 (Activation) (None, 7, 7, 2048) 0 ['activation_48[0][0]']
average_pooling2d (AveragePool (None, 1, 1, 2048) 0 ['activation_49[0][0]']
ing2D)
flatten (Flatten) (None, 2048) 0 ['average_pooling2d[0][0]']
dense (Dense) (None, 10) 20490 ['flatten[0][0]']
==================================================================================================
Total params: 22,979,210
Trainable params: 22,928,650
Non-trainable params: 50,560
__________________________________________________________________________________________________
4. 网络配置
采用动态学习率的方法,指定学习率是指数曲线下降,使网络刚开始时能更快接近最优点,后续再慢慢向逼近最优点。由于在网络的输出层没有进行softmax将实数值转为概率值,因此,在编译时使用交叉熵损失函数计算预测值和真实值的差异时,需要指定参数from_logits=True,代表将logits层输出的实数经过softmax转换为概率之后再和真实值计算损失。这样能有效提高数据稳定性。
#(4)网络配置
# 设置动态学习率
exponential_decay = optimizers.schedules.ExponentialDecay(initial_learning_rate=0.0001, # 初始学习率
decay_steps=2, # 衰减步长
decay_rate=0.95) # 衰减率0.95
# 编译
model.compile(optimizer=optimizers.Adam(learning_rate=exponential_decay), #指定学习率
#需要对真实值y进行onehot,而sparse_categorical_crossentropy会自动进行onehot
loss = tf.losses.CategoricalCrossentropy(from_logits=True), # from_logits会自动将输出层的实数转为softmax后再计算交叉熵
metrics = ['accuracy']) # 指定模型评价指标
# 训练,给出训练集、验证集、循环10次、每轮循环开始之前重新洗牌
model.fit(train_ds, validation_data=val_ds, epochs=10, shuffle=True)
5. 模型评估
绘制训练集和测试集的准确率和损失的对比曲线,观察是否出现过拟合现象。
#(5)评估
# ==1== 准确率
train_acc = model.history['accuracy'] #训练集准确率
val_acc = model.history['val_accuracy'] #验证集准确率
# ==2== 损失
train_loss = model.history['loss'] #训练集损失
val_loss = model.history['val_loss'] #验证集损失
# ==3== 绘图
epochs_range = range(len(train_acc))
plt.figure(figsize=(10,5))
# 准确率
plt.subplot(1,2,1)
plt.plot(epochs_range, train_acc, label='train_acc')
plt.plot(epochs_range, val_acc, label='val_acc')
plt.legend()
# 损失曲线
plt.subplot(1,2,2)
plt.plot(epochs_range, train_loss, label='train_loss')
plt.plot(epochs_range, val_loss, label='val_loss')
plt.legend()