🧡💛💚TensorFlow2实战-系列教程 总目录
有任何问题欢迎在下面留言
本篇文章的代码运行界面均在Jupyter Notebook中进行
本篇文章配套的代码资源已经上传
4、训练脚本train.py解读------创建模型
python
def get_model():
model = resnet50.ResNet50()
if config.model == "resnet34":
model = resnet34.ResNet34()
if config.model == "resnet101":
model = resnet101.ResNet101()
if config.model == "resnet152":
model = resnet152.ResNet152()
model.build(input_shape=(None, config.image_height, config.image_width, config.channels))
model.summary()
tf.keras.utils.plot_model(model, to_file='model.png')
return model
# create model
model = get_model()
调用get_model()函数构建模型
get_model()函数:
- 通过resnet50.py调用ResNet50类,构建ResNet50模型
- 如果在配置参数中设置的是"resnet34"、"resnet101"、"resnet152",则会对应使用(resnet34.py调用ResNet34类,构建ResNet34模型)、(resnet101.py调用ResNet101类,构建ResNet101模型)、(resnet152.py调用ResNet152类,构建ResNet152模型)
- 准备模型以供训练或评估,
- 输出模型的概览
- 创建了模型的结构图,plot_model 函数从 Keras 工具包中生成模型的可视化表示,指定了保存路径
5、模型构建解析------models/resnet50.py
python
import tensorflow as tf
from models.residual_block import build_res_block_2
from config import NUM_CLASSES
class ResNet50(tf.keras.Model):
def __init__(self, num_classes=NUM_CLASSES):
super(ResNet50, self).__init__()
self.pre1 = tf.keras.layers.Conv2D(filters=64, kernel_size=(7, 7), strides=2, padding='same')
self.pre2 = tf.keras.layers.BatchNormalization()
self.pre3 = tf.keras.layers.Activation(tf.keras.activations.relu)
self.pre4 = tf.keras.layers.MaxPool2D(pool_size=(3, 3), strides=2)
self.layer1 = build_res_block_2(filter_num=64, blocks=3)
self.layer2 = build_res_block_2(filter_num=128, blocks=4, stride=2)
self.layer3 = build_res_block_2(filter_num=256, blocks=6, stride=2)
self.layer4 = build_res_block_2(filter_num=512, blocks=3, stride=2)
self.avgpool = tf.keras.layers.GlobalAveragePooling2D()
self.fc1 = tf.keras.layers.Dense(units=1000, activation=tf.keras.activations.relu)
self.drop_out = tf.keras.layers.Dropout(rate=0.5)
self.fc2 = tf.keras.layers.Dense(units=num_classes, activation=tf.keras.activations.softmax)
def call(self, inputs, training=None, mask=None):
pre1 = self.pre1(inputs)
pre2 = self.pre2(pre1, training=training)
pre3 = self.pre3(pre2)
pre4 = self.pre4(pre3)
l1 = self.layer1(pre4, training=training)
l2 = self.layer2(l1, training=training)
l3 = self.layer3(l2, training=training)
l4 = self.layer4(l3, training=training)
avgpool = self.avgpool(l4)
fc1 = self.fc1(avgpool)
drop = self.drop_out(fc1)
out = self.fc2(drop)
return out
class ResNet50(tf.keras.Model),这个类定义了ResNet50模型的结构,以及前向传播的方式、顺序
ResNet50类解析:
- 构造函数,传入了预测的类别数
- 初始化
- pre1 ,定义一个二维卷积,输出64个特征图,7x7的卷积,步长为2
- pre2 ,定义一个批归一化
- pre3,定义一个ReLU激活函数
- pre4,一个二维的最大池化
- 依次通过build_res_block_2()函数定义4个残差块
- 定义一个全局平均池化
- 定义一个全连接层,输出维度为1000
- 定义一个dropout
- 定义一个输出层的全连接层
- 前向传播函数,传入输入值
- 依次经过pre1、pre2、pre3、pre4,即卷积、批归一化、ReLU、最大池化
- 依次经过layer1 、layer2 、layer3 、layer4 等四个残差块
- 将layer4 的输出经过平局池化
- 依次经过两个全连接层
6、模型构建解析------models/residual_block.py
- BottleNeck类
- build_res_block_2()函数
- build_res_block_2()函数通过调用BottleNeck类构建残差块
python
class BottleNeck(tf.keras.layers.Layer):
def __init__(self, filter_num, stride=1,with_downsample=True):
super(BottleNeck, self).__init__()
self.with_downsample = with_downsample
self.conv1 = tf.keras.layers.Conv2D(filters=filter_num, kernel_size=(1, 1), strides=1, padding='same')
self.bn1 = tf.keras.layers.BatchNormalization()
self.conv2 = tf.keras.layers.Conv2D(filters=filter_num, kernel_size=(3, 3), strides=stride, padding='same')
self.bn2 = tf.keras.layers.BatchNormalization()
self.conv3 = tf.keras.layers.Conv2D(filters=filter_num * 4, kernel_size=(1, 1), strides=1, padding='same')
self.bn3 = tf.keras.layers.BatchNormalization()
self.downsample = tf.keras.Sequential()
self.downsample.add(tf.keras.layers.Conv2D(filters=filter_num * 4, kernel_size=(1, 1), strides=stride))
self.downsample.add(tf.keras.layers.BatchNormalization())
def call(self, inputs, training=None):
identity = self.downsample(inputs)
conv1 = self.conv1(inputs)
bn1 = self.bn1(conv1, training=training)
relu1 = tf.nn.relu(bn1)
conv2 = self.conv2(relu1)
bn2 = self.bn2(conv2, training=training)
relu2 = tf.nn.relu(bn2)
conv3 = self.conv3(relu2)
bn3 = self.bn3(conv3, training=training)
if self.with_downsample == True:
output = tf.nn.relu(tf.keras.layers.add([identity, bn3]))
else:
output = tf.nn.relu(tf.keras.layers.add([inputs, bn3]))
return output
BottleNeck类解析:
- 继承tf.keras.layers.Layer
- 构造函数,传入 特征图个数、步长、是否下采样等参数
- 初始化
- 是否进行下采样参数
- 定义一个1x1,步长为1的二维卷积conv1
- conv1 对应的批归一化
- 定义一个3x3,步长为1的二维卷积conv2
- conv2 对应的批归一化
- 定义一个3x3,步长为1的二维卷积conv2
- conv3 对应的批归一化
- 定义一个下采样层(
self.downsample
),这个层是一个包含卷积层和批量归一化的Sequential
模型,用于匹配输入和残差的维度 - call()函数为前向传播
- 应用下采样
- 应用三层卷积和批量归一化以及对应的ReLU
- with_downsample == True:
- 启用下采样,将下采样后的输入(
identity
)与最后一个卷积层的输出(bn3
)相加 - 没有启用下采样,将原始输入(
inputs
)与最后一个卷积层的输出(bn3
)相加
python
def build_res_block_2(filter_num, blocks, stride=1):
res_block = tf.keras.Sequential()
res_block.add(BottleNeck(filter_num, stride=stride))
for _ in range(1, blocks):
res_block.add(BottleNeck(filter_num, stride=1,with_downsample=False))
return res_block
build_res_block_2函数解析:
- 这个函数构建了一个包含多个BottleNeck层的残差块
- filter_num 是每个瓶颈层内卷积层的过滤器数量
- blocks 是要添加到顺序模型中的瓶颈层的数量
- stride 是卷积的步长,默认为 1
- 该函数初始化一个 Sequential 模型,并添加一个 BottleNeck 层作为第一层
- 然后,它迭代地添加额外的 BottleNeck 层,每个层的 stride=1 且
with_downsample=False(除第一个之外) - 此函数返回组装好的顺序模型,代表一个残差块