内容简介:采用tensorflow对ResNet模型进行复现,模型的代码如下:最终,对ResNet的优缺点总结如下:
1. 引言
前面我们介绍了VGGNet在图像识别中的应用,在VGGNet中,作者提出随着CNN网络层数的加深,模型的效果会进一步得到提升,但是在这篇论文提出来之后,有学者发现,随着层数的加深,当深度达到一定数值之后,模型的准确率不仅没有提升,反而下降了,如图所示:
最开始有学者以为是梯度消失或爆炸的问题导致的,但是这个问题可以被Batch Normalization解决,并且加了Batch Normalization后依然出现类似的问题,另外,该现象也不是因为过拟合,因为如果是过拟合的话,模型在训练集上的准确率应该不会下降才对,因此,这里面肯定是因为模型中其他潜在的原因导致的。这个问题在2015年时被何凯明大神发现了,并且提出了一种新型的模型结构——ResNet,ResNet的提出可以说是图像识别任务中的里程碑,下面我们将对该模型进行具体介绍,并用tensorflow来实现它。
2. ResNet模型介绍
2.1 ResNet block介绍
记 为网络中几个堆叠的层所要拟合的潜在映射,其中, 是这几层中第一层的输入,那么,在VGGNet等传统的网络中,几个堆叠的层所要拟合的目标其实就是 ,而ResNet则只要求这几个层拟合目标为 ,即拟合该层输入与输出的残差,因此ResNet也被称为 残差网络 。其结构如下图所示,其实就是在每个block的最后一层relu层之前将输出与输入进行相加,这种结构也被称为“shortcut connections”,用公式表达如下:
其中, 和 分别表示一个block的输入和输出, 表示当前block所要拟合的残差映射, ,其中 和 为当前block的参数矩阵, 表示relu激活函数,在该计算之后,则是残差连接,残差连接后再经过一层relu激活函数得到该block的输出。虽然最终block的输出与VGGNet等网络类似,但是却降低了模型拟合的难度。
不过,上面这种残差连接方式要求 与 之间的维度必须完全相同,当两者的维度不一致时,可以通过一个线性变换将 的维度转变为与 的维度一致,即:
另外,上面介绍的 函数其实表示的是全连接网络,那么在图像中,我们也可以将其替换为卷积神经网络,并且最终的残差连接是直接对每个feature map进行对应相加。在每个block里面,层数也是可以灵活设置的,可以是两层、三层甚至多层,作者在论文中主要采用的是两种block结构,如下图所示,右边的三层结构也被称为“bottleneck”,采用这种三层的结构可以有效地提高模型的计算速度。
2.2 ResNet模型结构介绍
为了与传统的深层卷积神经网络对比,作者选择了经典的VGGNet进行对比,但是作者做了一些细微的修改,比如将中间的部分max-pooling改为卷积层,只是步伐改为2,另外,第一层卷积层采用 的卷积核。对于网络的层数,作者尝试了多种,用2层的block搭建了18、34层的模型,用3层的block搭建了50、101、152层的模型。模型的结构如下图所示,最右侧是ResNet,模型侧边每个实线表示一个block,并且尺度不变,每个虚线也表示一个block,但是对应的feature_map尺度减半,通道翻倍,当尺度发生变化时,作者主要有两种措施实现残差连接,一种是直接采用zero-padding操作使得 和 尺度一致,另一种是采用 的卷积操作使得 的维度和 一致。
作者通过在多个数据上进行实验,发现ResNet确实解决了随着深度加深,模型准确率下降的问题,如下图所示:
3. ResNet模型的tensorflow实现
采用tensorflow对ResNet模型进行复现,模型的代码如下:
import os import config import random import numpy as np import tensorflow as tf from config import resnet_config from data_loader import DataLoader from eval.evaluate import accuracy class ResNet(object): def __init__(self, depth=resnet_config.depth, height=config.height, width=config.width, channel=config.channel, num_classes=config.num_classes, learning_rate=resnet_config.learning_rate, learning_decay_rate=resnet_config.learning_decay_rate, learning_decay_steps=resnet_config.learning_decay_steps, epoch=resnet_config.epoch, batch_size=resnet_config.batch_size, model_path=resnet_config.model_path, summary_path=resnet_config.summary_path): """ :param depth: """ self.depth = depth self.height = height self.width = width self.channel = channel self.learning_rate = learning_rate self.learning_decay_rate = learning_decay_rate self.learning_decay_steps = learning_decay_steps self.epoch = epoch self.batch_size = batch_size self.num_classes = num_classes self.model_path = model_path self.summary_path = summary_path self.num_block_dict = {18: [2, 2, 2, 2], 34: [3, 4, 6, 3], 50: [3, 4, 6, 3], 101: [3, 4, 23, 3]} self.bottleneck_dict = {18: False, 34: False, 50: True, 101: True} self.filter_out = [64, 128, 256, 512] self.filter_out_last_layer = [256, 512, 1024, 2048] self.conv_out_depth = self.filter_out[-1] if self.depth < 50 else self.filter_out_last_layer[-1] assert self.depth in self.num_block_dict, 'depth should be in [18,34,50,101]' self.num_block = self.num_block_dict[self.depth] self.bottleneck = self.bottleneck_dict[self.depth] self.input_x = tf.placeholder(tf.float32, shape=[None, self.height, self.width, self.channel], name='input_x') self.input_y = tf.placeholder(tf.float32, shape=[None, self.num_classes], name='input_y') self.prediction = None self.loss = None self.acc = None self.global_step = None self.data_loader = DataLoader() self.model() def model(self): # first convolution layers x = self.conv(x=self.input_x, k_size=7, filters_out=64, strides=2, activation=True, name='First_Conv') x = tf.layers.max_pooling2d(x, pool_size=[3, 3], strides=2, padding='same', name='max_pool') x = self.stack_block(x) x = tf.layers.average_pooling2d(x, pool_size=x.get_shape()[1:3], strides=1, name='average_pool') x = tf.reshape(x, [-1, 1 * 1 * self.conv_out_depth]) logits = tf.layers.dense(inputs=x, units=self.num_classes, kernel_initializer=tf.truncated_normal_initializer(stddev=0.1)) # 预测值 self.prediction = tf.argmax(logits) # 计算准确率 self.acc = accuracy(logits, self.input_y) # 损失值 self.loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=self.input_y)) # 全局步数 self.global_step = tf.train.get_or_create_global_step() # 递减学习率 learning_rate = tf.train.exponential_decay(learning_rate=self.learning_rate, global_step=self.global_step, decay_rate=self.learning_decay_rate, decay_steps=self.learning_decay_steps, staircase=True) self.optimize = tf.train.AdamOptimizer(learning_rate).minimize(self.loss) def stack_block(self, input_x): for stack in range(4): stack_strides = 1 if stack == 0 else 2 stack_name = 'stack_%s' % stack with tf.name_scope(stack_name): for block in range(self.num_block[stack]): shortcut = input_x block_strides = stack_strides if block == 0 else 1 block_name = stack_name + '_block_%s' % block with tf.name_scope(block_name): if self.bottleneck: for layer in range(3): with tf.name_scope(block_name + '_layer_%s' % layer): filters = self.filter_out[stack] if layer < 2 else self.filter_out_last_layer[stack] k_size = 3 if layer == 1 else 1 layer_strides = block_strides if layer < 1 else 1 activation = True if layer < 2 else False layer_name = block_name + '_conv_%s' % layer input_x = self.conv(x=input_x, filters_out=filters, k_size=k_size, strides=layer_strides, activation=activation, name=layer_name) else: for layer in range(2): with tf.name_scope(block_name + '_layer_%s' % layer): filters = self.filter_out[stack] k_size = 3 layer_strides = block_strides if layer < 1 else 1 activation = True if layer < 1 else False layer_name = block_name + '_conv_%s' % layer input_x = self.conv(x=input_x, filters_out=filters, k_size=k_size, strides=layer_strides, activation=activation, name=layer_name) shortcut_depth = shortcut.get_shape()[-1] input_x_depth = input_x.get_shape()[-1] with tf.name_scope('shortcut_connect'): if shortcut_depth != input_x_depth: connect_k_size = 1 connect_strides = block_strides connect_filter = filters shortcut_name = block_name + '_shortcut' shortcut = self.conv(x=shortcut, filters_out=connect_filter, k_size=connect_k_size, strides=connect_strides, activation=False, name=shortcut_name) input_x = tf.nn.relu(shortcut + input_x) return input_x def conv(self, x, k_size, filters_out, strides, activation, name): x = tf.layers.conv2d(x, filters=filters_out, kernel_size=k_size, strides=strides, padding='same', name=name) x = tf.layers.batch_normalization(x, name=name + '_BN') if activation: x = tf.nn.relu(x) return x def fit(self, train_id_list, valid_img, valid_label): """ training model :return: """ # 模型存储路径初始化 if not os.path.exists(self.model_path): os.makedirs(self.model_path) if not os.path.exists(self.summary_path): os.makedirs(self.summary_path) # train_steps初始化 train_steps = 0 best_valid_acc = 0.0 # summary初始化 tf.summary.scalar('loss', self.loss) merged = tf.summary.merge_all() # session初始化 sess = tf.Session() writer = tf.summary.FileWriter(self.summary_path, sess.graph) saver = tf.train.Saver(max_to_keep=10) sess.run(tf.global_variables_initializer()) for epoch in range(self.epoch): shuffle_id_list = random.sample(train_id_list.tolist(), len(train_id_list)) batch_num = int(np.ceil(len(shuffle_id_list) / self.batch_size)) train_id_batch = np.array_split(shuffle_id_list, batch_num) for i in range(batch_num): this_batch = train_id_batch[i] batch_img, batch_label = self.data_loader.get_batch_data(this_batch) train_steps += 1 feed_dict = {self.input_x: batch_img, self.input_y: batch_label} _, train_loss, train_acc = sess.run([self.optimize, self.loss, self.acc], feed_dict=feed_dict) if train_steps % 1 == 0: val_loss, val_acc = sess.run([self.loss, self.acc], feed_dict={self.input_x: valid_img, self.input_y: valid_label}) msg = 'epoch:%s | steps:%s | train_loss:%.4f | val_loss:%.4f | train_acc:%.4f | val_acc:%.4f' % ( epoch, train_steps, train_loss, val_loss, train_acc, val_acc) print(msg) summary = sess.run(merged, feed_dict={self.input_x: valid_img, self.input_y: valid_label}) writer.add_summary(summary, global_step=train_steps) if val_acc >= best_valid_acc: best_valid_acc = val_acc saver.save(sess, save_path=self.model_path, global_step=train_steps) sess.close() def predict(self, x): """ predicting :param x: :return: """ sess = tf.Session() sess.run(tf.global_variables_initializer()) saver = tf.train.Saver(tf.global_variables()) ckpt = tf.train.get_checkpoint_state(self.model_path) saver.restore(sess, ckpt.model_checkpoint_path) prediction = sess.run(self.prediction, feed_dict={self.input_x: x}) prediction = np.bincount(prediction) prediction = np.argmax(prediction) return prediction
4. 总结
最终,对ResNet的优缺点总结如下:
- ResNet解决了准确率随着深度加深而下降的问题,收敛速度比VGGNet更快。
- 与VGGNet相比,模型的深度可以更深,复杂度更低,参数量更少。
以上所述就是小编给大家介绍的《ResNet图像识别与tensorflow实现》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 原 荐 Qt封装百度人脸识别+图像识别
- 走进AI时代的文档识别技术 之表格图像识别
- 揭秘“图像识别”的工作原理
- REM图像识别市场分析
- 图像识别攻击还没完全解决,语音识别攻击又来了!
- 零基础小白快速打造图像识别模型
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。