Tensorflow2 ResNet实现猫狗二分类
- 1 前言
- 2 ResNet架构简介
- 3 Tensorflow实现ResNet
1 前言
卷积神经网络(CNN)在计算机视觉领域扮演着重要的角色,而ResNet(Residual Network)的诞生则是CNN历史上重要的里程碑。理论上说网络的层数越深,提取的特征越好,检测结果越好,但实验证明网络层数较深的却不如网络层数较浅的模型,这是因为网络层数越深,越容易出现梯度消失和梯度爆炸,而ResNet很好的解决了这一问题。
2 ResNet架构简介
ResNet的思想就是在普通的卷积过程中加了一个恒等映射x。这个恒等映射保证了模型相对于较浅的网络效果不会差
理解恒等映射x:
G(x) = F(x) + x
我们假设上一层(输出为x)已经达到最优效果,如果再简单的往后添加层数,则模型的效果便会降低,但如果我们让后面的层加上前一层的输出x,即让模型记住上一层的输出,极限状态下只需让F(x)=0,模型输出G(x)仍是最优状态x,可保证模型效果不会差于较浅层。
3 Tensorflow实现ResNet
注意事项:
1 ResNet包括两个残差块,左边的两次卷积的步长均为1,右边的第一次卷积步长为2,第二次卷积步长为1,用于压缩长和宽。
2 ResNet的本质在于上一层的输出x和x经过卷积、标准化等进一步提取特征后的结果的对应元素之和作为新的输出(好像很绕),要求两个相加的特征输出的长宽和维度必须完全一致,在卷积的过程中采用padding的模式保证长宽不变,另外需要自己检查两者的维度是否一致。比如说 x 是 (32, 224, 224, 64),则F(x)也必须是(32, 224, 224, 64),当然可以通过广播的机制相加,不过要自己检查能否广播到同一维度。
代码实现:
1 创建图片数据输入管道
数据集使用的是小型猫狗数据集仅3000张,创建图片数据输入管道时使用了图片增强(随机翻转和gamma变换)。
图片数据集网盘地址:https://pan.baidu.com/s/1qdg1fEBHWWpetYGDjIqTjA
提取码:oe0j
import tensorflow as tf
import numpy as np
import glob
import cv2
all_image_path = glob.glob('./dc_2000/*/*/*.jpg')
all_image_label = [int(path.split('\\')[2] == 'cat') for path in all_image_path]
BATCH_SIZE = 50
def generate_iter_data(all_image_path, all_image_label):
all_data = tf.data.Dataset.from_tensor_slices((all_image_path, all_image_label))
all_data = all_data.shuffle(len(all_image_path)).batch(BATCH_SIZE).repeat().prefetch(tf.data.experimental.AUTOTUNE)
for batch_path, batch_label in all_data:
batch_label = batch_label.numpy()
batch_image_data = []
for path in batch_path.numpy():
path = path.decode()
image = cv2.imread(path, cv2.IMREAD_COLOR)
image = cv2.resize(image, (224, 224))
a = np.random.randint(-1, 2)
flip_img = cv2.flip(image, a)
gamma = np.random.choice([0.4, 0.5, 0.6, 0.7])
gamma_img = np.power(flip_img / 255., gamma)
batch_image_data.append(gamma_img)
batch_image_data_ = np.stack(batch_image_data, axis=0)
yield batch_image_data_, batch_label.reshape(-1, 1)
2 自己搭建小型ResNet
自定义层搭建残差块,strides=2时用于改变维度和压缩长宽,模型参数量为160w。
class Residual_Block(tf.keras.layers.Layer):
def __init__(self, filters, strides=1):
super(Residual_Block, self).__init__()
self.conv1 = tf.keras.layers.Conv2D(filters=filters,
kernel_size=(3, 3),
strides=strides,
padding='same')
self.bn1 = tf.keras.layers.BatchNormalization()
self.relu1 = tf.keras.layers.Activation(tf.nn.relu)
self.conv2 = tf.keras.layers.Conv2D(filters=filters,
kernel_size=(3, 3),
strides=1,
padding='same')
self.bn2 = tf.keras.layers.BatchNormalization()
if strides!= 1: #这个既可以降维又可以改变通道数
self.downSamples = tf.keras.Sequential()
self.downSamples.add(tf.keras.layers.Conv2D(filters=filters,
kernel_size=(1, 1),
strides=strides))
self.downSamples.add(tf.keras.layers.BatchNormalization())
else:
self.downSamples = lambda x:x
def call(self, inputs, training=None, **kwargs):
residual = self.downSamples(inputs)
x = self.conv1(inputs)
x = self.bn1(x)
x = self.relu1(x)
x = self.conv2(x)
x = self.bn2(x)
return tf.nn.relu(tf.keras.layers.add([x, residual])) #传进来的必须是一个列表 不能是元组
def mini_resnet():
inputs = tf.keras.layers.Input(shape=(224, 224, 3))
x = tf.keras.layers.Conv2D(filters=32, kernel_size=(3, 3), strides=2, padding='same')(inputs)
x = tf.keras.layers.BatchNormalization()(x)
x = tf.keras.layers.Activation(tf.nn.relu)(x)
x = Residual_Block(filters=32)(x)
x = Residual_Block(filters=64, strides=2)(x)
x = Residual_Block(filters=64)(x)
x = Residual_Block(filters=128, strides=2)(x)
x = Residual_Block(filters=128)(x)
x = Residual_Block(filters=256, strides=2)(x)
x = tf.keras.layers.GlobalAveragePooling2D()(x)
x = tf.keras.layers.Dense(units=10, activation='relu')(x)
x = tf.keras.layers.Dense(units=1, activation='sigmoid')(x)
model = tf.keras.models.Model(inputs, x)
return model
3 迁移学习ResNet50
采用在imagenet上训练的权重,去掉全连接层,并冻结ResNet50,训练速度快且准备率高。不过参数量多达两千多万,这个数据集对于ResNet50来说完全是大材小用。
def resnet50():
ResNet50 = tf.keras.applications.ResNet50(include_top=False,
weights='imagenet',
input_shape=(224, 224, 3))
ResNet50.trainable = False
model = tf.keras.Sequential()
model.add(ResNet50)
model.add(tf.keras.layers.GlobalAveragePooling2D())
model.add(tf.keras.layers.Dense(10, activation='relu'))
model.add(tf.keras.layers.Dense(1, activation='sigmoid'))
return model