科普篇
科普知识
神经元(Neuron)——就像形成我们大脑基本元素的神经元一样,神经元形成神经网络的基本结构。想象一下,当我们得到新信息时我们该怎么做。当我们获取信息时,我们一般会处理它,然后生成一个输出。类似地,在神经网络的情况下,神经元接收输入,处理它并产生输出,而这个输出被发送到其他神经元用于进一步处理,或者作为最终输出进行输出。
权重(Weights)——当输入进入神经元时,它会乘以一个权重。例如,如果一个神经元有两个输入,则每个输入将具有分配给它的一个关联权重。我们随机初始化权重,并在模型训练过程中更新这些权重。训练后的神经网络对其输入赋予较高的权重,这是它认为与不那么重要的输入相比更为重要的输入。为零的权重则表示特定的特征是微不足道的。
前言
上周我们学习了如何在一个深度学习实战项目中制作一个图像分类的数据集输入形式,也就是将原有的数据制作成神经网络能够接受的格式,总的来说就是标签和维度以及批数据生成的问题。今天我们继续来学习图像分类项目的下一阶段,模型搭建,接着往下看哈!
一、TensorFlow之图像分类模型搭建
开讲之前我们回顾上一节的内容,首先提问一下为什么原始的数据在制作成路径和标签的两个列表之后还需要制作批数据呢?制作批处理的数据的意思是,得到所有数据的路径和标签的列表后,我们不能直接全部放进神经网络,因为训练的时候通常是在显卡上进行,显卡的显存一般也就8G-16G,虽然本身数据也许只有几个G大小,但是当这些数据输入神经网络之后,所产生的参数量所需的运行内存就会变得很大,由此,我们的全部数据应该尽量按照一批一批的输入进去,以更好的支持神经网络的训练,实验证明 按照批次进行训练的效果也往往比较好。因此批数据训练也就成为了如今大多数深度学习的从业者所采用,当然如果你家里有矿,也可以将批数据设置大一点512,1024,2048等等,通常实验室显卡较小就会设置为32,64,128等等。
1.1 模型搭建
神经网络的模型搭建其实较为简单,重要包含,各个网络层的定义和连接方式,以及最后的输出形式(维度),中间包含网络的选择和激活函数选择,过拟合操作等等。
1.2 代码实践
模型构建部分中,神经网络是按照层来搭建的,一般包含,卷积层,池化层,全连接层。
# 定义卷积层
def Conv_layer(names, input, w_shape, b_shape, strid):
# 给每一个层一个空间 防止不同层之间的变量冲突
with tf.variable_scope(names) as scope:
# 卷积操作包含了权重w和偏执b,需要实现定义
# 权重w有自己的维度:[卷积核尺寸,卷积核尺寸,输入通道,输出通道]
# 输入通道与输入数据的通道一样,即上一层的输出通道
# 卷积的输出通道为该层卷积的特征图的输出通道数,也就是下一层网络的输入通道数
weights = tf.Variable(tf.truncated_normal(shape = w_shape, stddev = 1.0, dtype = tf.float32),
name = 'weights_{}'.format(names), dtype = tf.float32)
# 偏置b的维度与w的输出通道一致,即有多少个输出神经元就有多少个偏置
biases = tf.Variable(tf.constant(value = 0.1, dtype = tf.float32, shape = b_shape),
name='biases_{}'.format(names), dtype = tf.float32)
# 执行卷积操作
conv = tf.nn.conv2d(input, weights, strides = [1, strid[0], strid[1], 1], padding = 'SAME')
# print(strid)
# 卷积后的结果加上偏置
conv = tf.nn.bias_add(conv, biases)
# 卷积后的结果经过relu激活函数后输出(通常都会这样做)
conv_out = tf.nn.relu(conv, name = 'relu_{}'.format(names))
print("---------names:{}".format(conv_out))
return conv_out
# 定义最大池化层
def Max_pool_lrn(names, input, ksize, is_lrn):
with tf.variable_scope(names) as scope:
# 最大池化操作
Max_pool_out = tf.nn.max_pool(input, ksize = ksize, strides = [1, 2, 2, 1], padding = 'SAME', name = 'max_pool_{}'.format(names))
if is_lrn:
# 是否增加一个lrn操作,一般来说用增加非线性表达和抑制过拟合
Max_pool_out = tf.nn.lrn(Max_pool_out, depth_radius=4, bias=1.0, alpha=0.001 / 9.0, beta=0.75, name = 'lrn_{}'.format(names))
print("use lrn operation")
return Max_pool_out
# 定义全连接层 (FC)
def local_layer(names, input, w_shape, b_shape):
with tf.variable_scope(names) as scope:
# 全连接层包含w和b,但是唯独却没有卷积那么多,仅仅有两个,[batch,out]
weights = tf.Variable(tf.truncated_normal(shape = w_shape, stddev=0.005, dtype = tf.float32),
name = 'weights_{}'.format(names), dtype = tf.float32)
biases = tf.Variable(tf.constant(value = 0.1, dtype = tf.float32, shape = b_shape),
name='biases_{}'.format(names), dtype=tf.float32)
# 全连接层通常加一个relu函数 增强其非线性表达
local = tf.nn.relu(tf.matmul(input, weights) + biases, name ='local_{}'.format(names))
return local
1.3 模型构建整体代码
def inference(images, batch_size, n_classes,drop_rate):
# 一个简单的卷积神经网络,卷积+池化层x2,全连接层x2,最后一个softmax层做分类。
# 卷积层1
# 64个3x3的卷积核(3通道),padding=’SAME’,表示padding后卷积的图与原图尺寸一致,激活函数relu()
conv1 = Conv_layer(names = 'conv1_scope', input = images , w_shape = [2, 2, 3, 64], b_shape = [64], strid = [2, 2])
conv2 = Conv_layer(names = 'conv2_scope', input = conv1 , w_shape = [2, 2, 64, 64], b_shape = [64], strid = [1, 1])
pool_1 = Max_pool_lrn(names = 'pooling1_lrn', input = conv2 , ksize = [1, 2, 2, 1], is_lrn = True)
conv3 = Conv_layer(names = 'conv3_scope', input = pool_1 , w_shape = [3, 3, 64, 64], b_shape = [64], strid = [1, 1])
pool_2 = Max_pool_lrn(names = 'pooling2', input = conv3 , ksize = [1, 2, 2, 1], is_lrn = False)
conv4 = Conv_layer(names = 'conv4_scope', input = pool_2 , w_shape = [3, 3, 64, 128], b_shape = [128], strid = [1, 1])
conv5 = Conv_layer(names = 'conv5_scope', input = conv4 , w_shape = [3, 3, 128, 128], b_shape = [128], strid = [1, 1])
pool_3 = Max_pool_lrn(names = 'pooling3', input = conv5 , ksize = [1, 2, 2, 1], is_lrn = False)
# conv-->local dimension change
reshape = tf.reshape(pool_3, shape=[batch_size, -1])
# 因为上一层是卷积的输出是四个维度
# 但是全连接的维度只有两个,因此 卷积额输出会被提前reshape为
# 两个维度
dim = reshape.get_shape()[1].value
local_1 = local_layer(names = 'local1_scope', input = reshape , w_shape = [dim, 64], b_shape = [64])
local_2 = local_layer(names = 'local2_scope', input = local_1 , w_shape = [64, 100], b_shape = [100])
drop_out1 = Dropout_layer(names = 'dropout1_scope', input = local_2, drop_rate = drop_rate )
local_3 = local_layer(names = 'local3_scope', input = drop_out1 , w_shape = [100, 80], b_shape = [80])
drop_out2 = Dropout_layer(names = 'dropout2_scope', input = local_3, drop_rate = drop_rate )
#
# 将前面的FC层输出,再次做一个FC
with tf.variable_scope('softmax_linear') as scope:
weights = tf.Variable(tf.truncated_normal(shape=[80, n_classes], stddev=0.005, dtype=tf.float32),
name='softmax_linear', dtype=tf.float32)
biases = tf.Variable(tf.constant(value=0.1, dtype=tf.float32, shape=[n_classes]),
name='biases', dtype=tf.float32)
softmax_linear = tf.add(tf.matmul(drop_out2, weights), biases, name='softmax_linear')
print("---------softmax_linear:{}".format(softmax_linear))
return softmax_linear
至此,模型构建部分的代码完成了,或许大家还有不太理解的地方,没关系,我们先仔细看懂每一行代码的用途,然后看它的输出是什么,值得注意的是在卷积操作中有四个维度,如果之后要链接全连接层就需要提前reshape维度为两维度(这样才能将两个维度的输出送入到全连接层),因为我们最终的输出是每一个样本的预测值,每一批次有多少个样本就有多少个预测值,因此最后的输出维度是:[样本数,预测值],每一个样本对应一个预测值,每一个预测值包含了与类别数目一致的值,也就是如果是5分类,那么每一个样本的预测值其实是其属于五个类别的概率值,有大有小,最后实际测试中我们取概率最大的一个。另外 在网络层次中,严格的说只有拥有训练参数的层才称之为网络(卷积层,全连接层),其余的都为操作(比如池化,dropout等)。对于卷积操作等的权重维度或许大家不太了解,可以借助相关文档,每一个操作都有其api介绍,大家多去看看,不懂的可以在后台提问哦。
模型搭建已经成功,到目前位置还没有进入到训练过程,因此,下周我们将会介绍如何将数据传入网络,网络是如何训练,训练时如何进行反向传播的,训练过程中是如何评价网络的训练好坏。
结语
今天的文章虽然结束了,网络搭建部分不是很难,但是涉及到的维度信息比较多,大家一定好好理解,总结一句话,根据上一层的输出来决定下一层网络的权重参数,最后根据需要分类的数目来决定网络的最终输出维度,从总体上有一个脉络就可以弄清楚神经网络是怎么设计啦,当然,我们只是设计了一个简单的网络,后期我们会分享一些经典的网络,当然,我们先讲理论,然后再实战。
各位 周末愉快!
编辑:玥怡居士|审核:小圈圈居士