基于TensorFlow的FNN模型——MNIST手写数字识别器(三)之定义FNN【开源】

注意:这是一个完整的项目,建议您按照完整的博客顺序阅读。

文章目录

前言

一、定义全连接神经层

1、tf.Variable()或者tf.get_variable()的区别

2、定义网络的权重

(1)使用tf.Variable(initial_value,name)

(2)使用tf.get_Variable(name,shape,initializer,collections)

(3)随机数生成的常用函数

3、定义网络的偏置

(1)使用tf.Variable(initial_value,name)

(2)使用tf.get_Variable(name,shape,initializer,collections)

(3)张量生成的常用函数

4、网络的激活函数

5、定义全连接神经层

(1)使用tf.Variable自定义方式

(2)使用tf.get_Variable自定义方式

(3)使用tf.layers.dense()

(4)使用tf.layers.dense()

二、定义全连接网络

三、定义计算图

1、定义计算图的输入结构

2、定义计算图的输出变量

3、定义一个滑动平均模型

4、定义计算图的损失函数

5、定义计算图的准确率

6、定义计算图的学习率

7、定义计算图的优化器

提示

 


前言

全连接网络是深度学习中的最简单的神经网络,在定义该网络时有许多不同的方式,本文尽可能将使用TensorFlow定义FNN的常见方式总结下


一、定义全连接神经层

众所周知,一个现代神经元的基本元素是权重偏置激活函数,其中使用非线性激活函数可以将多层网络从线性模型转换为非线性模型,这也是目前深度学习技术可以处理复杂非线性问题的原因之一。

使用TensorFlow来创建网络的权重和偏置参数是学习神经网络的基础,在TensorFlow中一般使用tf.Variable()或者tf.get_variable()是创建模型的参数变量。

1、tf.Variable()或者tf.get_variable()的区别

tf.Variable()每次调用都在创建一个新对象,所以无法进行参数复用;tf.Variable如果检测到命名冲突,系统会自己处理。tf.Variable可以使用tf.variable_scope()和tf.name_scope()来定义命名空间。

tf.get_variable()每次调用时如果存在该名称的变量并且自身reuse=True,那么就将之前已经创建的对象返回,否则才创建一个新对象。tf.get_variable()如果检测到命名冲突系统会报错;tf.get_variable()要使用tf.variable_scope()来定义命名空间,

2、定义网络的权重

网络的权重一般使用随机初始化一个正态分布的二维数字矩阵,有以下两种方式:

(1)使用tf.Variable(initial_value,name)

with tf.name_scope('weights'):
   weights = tf.Variable(initial_value=tf.random_normal([in_size, out_size]), name='w')
   tf.summary.histogram('weights', weights) #在 tensorboard中检测变量

(2)使用tf.get_Variable(name,shape,initializer,collections)

with tf.variable_scope('biases'):
    biases = tf.get_variable(name='b',
                shape=[1, units],
                initializer=tf.constant_initializer(0.1),
                collections=c_names)

(3)随机数生成的常用函数

# 正态分布
tf.random_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=Noneshape,mean,stddev)
# 正态分布
tf.truncated_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)
# 均匀分布
tf.random_uniform(shape, minval=0.0, maxval=1.0, dtype=tf.float32, seed=None, name=None)
# Gamma分布
tf.random_gamma: 
tf.random_shuffle(value, seed=None, name=None)

3、定义网络的偏置

(1)使用tf.Variable(initial_value,name)

with tf.name_scope('biases'):
    biases = tf.Variable(tf.zeros([1, out_size]) + 0.1, name='b')# 推荐不为0,此处加0.1
    tf.summary.histogram('biases', biases)# 在 tensorboard中绘制图片

(2)使用tf.get_Variable(name,shape,initializer,collections)

with tf.variable_scope('biases'):
    biases = tf.get_variable(name='b',
                shape=[1, units],
                initializer=tf.constant_initializer(0.1),
                collections=c_names)

(3)张量生成的常用函数

tf.zeros(shape, dtype=tf.float32, name=None)
tf.zeros_like(tensor, dtype=None, name=None)
tf.constant(value, dtype=None, shape=None, name='Const')
tf.fill(dims, value, name=None)
tf.ones_like(tensor, dtype=None, name=None)
tf.ones(shape, dtype=tf.float32, name=None)

4、网络的激活函数

最常见的是以下三类:

tf.nn.relu() 
tf.nn.sigmoid() 
tf.nn.tanh() 

5、定义全连接神经层

(1)使用tf.Variable自定义方式

在定义激活函数时,我们在定义变量时要考虑顺便定义好其name,激活函数也要参数化,默认为空,因为有些神经层不需要经过激活函数处理

同时我们要为该神经层定义一个整体的命名空间,方便在TensorBoard中区分,这里我们以layer1、layer2的形式命名,则我们的定义可以如下处理:

def _define_layer(self,inputs, in_size, out_size, index_layer, activation_function=None):
        """ 定义一个全连接神经层"""
        layer_name = 'layer%s' % index_layer # 定义该神经层命名空间的名称
        with tf.name_scope(layer_name):
            with tf.name_scope('weights'):
                weights = tf.Variable(initial_value=tf.random_normal([in_size, out_size]), name='w')
            with tf.name_scope('biases'):
                biases = tf.Variable(tf.zeros([1, out_size]) + 0.1, name='b')
            with tf.name_scope('wx_plus_b'):
                # 神经元未激活的值,矩阵乘法
                wx_plus_b = tf.matmul(inputs, weights) + biases
            # 使用激活函数进行激活
            if activation_function is None:
                outputs = wx_plus_b
            else:
                outputs = activation_function(wx_plus_b)
            
        # 返回神经层的输出
        return outputs

注意在上述中我们使用的是tf.name_scope()来定义命名空间

接下来,为了让我们的神经层更通用,我们可以考虑将着正则化函数(regularizer__function)和是否在TensorBoard中监控参数(is_historgram)两个选项参数化。

若需要正则化,我们只需要将权重参数输入正则化参数中,再将返回值添加到TensorFlow的自定义集合(collection)中,这样当我们需要这个变量的时候就可以在指定地方(一般是定义损失函数)调用该集合。

若需要监控参数,我们则需要tf.summary.histogram()来标记该参数,

所以我们的定义方法修改如下: 

def _define_layer(self,inputs, in_size, out_size, index_layer, activation_function=None,regularizer__function=None,is_historgram=True):
        """ 定义一个全连接神经层"""
        layer_name = 'layer%s' % index_layer # 定义该神经层命名空间的名称
        with tf.name_scope(layer_name):
            with tf.name_scope('weights'):
                weights = tf.Variable(initial_value=tf.random_normal([in_size, out_size]), name='w')
                if regularizer__function != None: # 是否使用正则化项
                    tf.add_to_collection('losses', regularizer__function(weights))  # 将正则项添加到一个名为'losses'的列表中
                if is_historgram: # 是否记录该变量用于TensorBoard中显示
                    tf.summary.histogram(layer_name + '/weights', weights)#第一个参数是图表的名称,第二个参数是图表要记录的变量
            with tf.name_scope('biases'):
                biases = tf.Variable(tf.zeros([1, out_size]) + 0.1, name='b')
                if is_historgram:  # 是否记录该变量用于TensorBoard中显示
                    tf.summary.histogram(layer_name + '/biases', biases)
            with tf.name_scope('wx_plus_b'):
                # 神经元未激活的值,矩阵乘法
                wx_plus_b = tf.matmul(inputs, weights) + biases
            # 使用激活函数进行激活
            if activation_function is None:
                outputs = wx_plus_b
            else:
                outputs = activation_function(wx_plus_b)
            if is_historgram:  # 是否记录该变量用于TensorBoard中显示
                tf.summary.histogram(layer_name + '/outputs', outputs)
            # 返回神经层的输出
        return outputs

(2)使用tf.get_Variable自定义方式

使用tf.get_Variable和之前的定义方式差不多,唯一有区别的可能就是该API输入的参数不同使用tf.variable_scope()来定义命名空间

下面直接给出方法定义:

def _define_layer(self,inputs, in_size, out_size, index_layer, activation_function=None,regularizer__function=None,is_historgram=True):
        """ 定义一个全连接神经层"""
        layer_name = 'layer%s' % index_layer # 定义该神经层命名空间的名称
        with tf.variable_scope(layer_name,reuse=tf.AUTO_REUSE):
            with tf.variable_scope('weights'):
                weights = tf.get_variable('w', [in_size, out_size], initializer=tf.truncated_normal_initializer(stddev=0.1))
                if regularizer__function != None: # 是否使用正则化项
                    tf.add_to_collection('losses', regularizer__function(weights))  # 将正则项添加到一个名为'losses'的列表中
                if is_historgram: # 是否记录该变量用于TensorBoard中显示
                    tf.summary.histogram(layer_name + '/weights', weights)#第一个参数是图表的名称,第二个参数是图表要记录的变量
            with tf.variable_scope('biases'):
                biases = tf.get_variable('b', [1, out_size], initializer=tf.constant_initializer(0.0))
                if is_historgram:  # 是否记录该变量用于TensorBoard中显示
                    tf.summary.histogram(layer_name + '/biases', biases)
            with tf.variable_scope('wx_plus_b'):
                # 神经元未激活的值,矩阵乘法
                wx_plus_b = tf.matmul(inputs, weights) + biases
            # 使用激活函数进行激活
            if activation_function is None:
                outputs = wx_plus_b
            else:
                outputs = activation_function(wx_plus_b)
            if is_historgram:  # 是否记录该变量用于TensorBoard中显示
                tf.summary.histogram(layer_name + '/outputs', outputs)
        # 返回神经层的输出
        return outputs

当然除了自定义全连接神经层这种,TensorFlow本身也提供了封装好的方法来让我们直接定义全连接神经层,下面给出常见的两个API。

(3)使用tf.layers.dense()

tf.layers.dense(
    inputs,# 输入2维的Tensor,第1维表示数据数量
    units, # 该层的神经元数量
    activation=None, # 激活函数
    use_bias=True,# 是否使用偏置项
    kernel_initializer=None,# 运算核的初始化
    bias_initializer=tf.zeros_initializer(),# 运算核的初始化
    kernel_regularizer=None,# 运算核的正则化函数,可选
    bias_regularizer=None,# 运算核的正则化函数,可选
    activity_regularizer=None,# 输出的正则化函数
    trainable=True,# 该层参数是否参与训练
    name=None,# 层的名字
    reuse=None # 是否重复使用参数
)

(4)使用tf.layers.dense()

tf.contrib.layers.fully_connected(
    inputs,# 输入2维的Tensor
    units, # 该层的神经元数量
    activation=None, # 激活函数
    use_bias=True,# 是否使用偏置项
    kernel_initializer=None,# 运算核的初始化
    bias_initializer=tf.zeros_initializer(),# 运算核的初始化
    kernel_regularizer=None,# 运算核的正则化函数,可选
    bias_regularizer=None,# 运算核的正则化函数,可选
    activity_regularizer=None,# 输出的正则化函数
    trainable=True,# 该层参数是否参与训练
    name=None,# 层的名字
    reuse=None # 是否重复使用参数
)

二、定义全连接网络

在上述定义好了全连接层方法后,我们就可以很容易的搭建我们的全连接网络

为了让不想下载源码的同学方便复现,我们这里也给出定义全连接网络的方法:

    def _define_net(self, input,regularizer__function=None,is_historgram=True):
        """ 定义一个全连接神经网络"""
        # 定义layer1层
        layer1 = self._define_layer(input, # 输入张量
                                    self.n_input, # 输入维度
                                    self.n_layer_1, # 输出维度
                                    index_layer=1,  # 本神经层命名序号
                                    activation_function=tf.nn.relu,# 激活函数
                                    regularizer__function=regularizer__function,# 正则化函数
                                    is_historgram=is_historgram)  # 是否用TensorBoard可视化该变量
        # 定义layer2层
        output = self._define_layer(layer1,
                                    self.n_layer_1,
                                    self.n_output,
                                    index_layer=2,
                                    activation_function=None,
                                    regularizer__function=regularizer__function,
                                    is_historgram=is_historgram)  # 预测值
        return output

三、定义计算图

现在我们已经定义好了我们的全连接网络,但是想在TensorFlow中进行训练还是不行的,我们要继续定义一个计算图来包含该网络。

1、定义计算图的输入结构

计算图的输入结构就是定义输入数据集的样本标签的数据格式。

我们一般以tf.placeholder()是用来创建模型的输入格式,该方法可以帮助我们定义一个不需要立即初始化的占位节点。

上述tf.Variable()和tf.get_variable()是用来定义模型的参数变量,一般定义该参数节点要立即进行初始化。

     with tf.name_scope('input'):
            self.x_input = tf.placeholder(dtype=tf.float32, shape=[None, self.n_input], name='x-input')  # 网络输入格式
            self.y_input = tf.placeholder(dtype=tf.float32, shape=[None, self.n_output], name='y-input')  # 网络标签格式

2、定义计算图的输出变量

计算图的输出就是我们网络的输出,我们直接调用前面的_define_net()方法,注意我们这里定义了L2正则化参数,正则化操作是用来缓解过拟合现象

     with tf.name_scope('output'):
            regularizer = tf.contrib.layers.l2_regularizer(REGULARIZTION_RATE)  # L2正则化函数
            self.output = self._define_net(self.x_input,regularizer__function = regularizer,is_historgram=True)

3、定义一个滑动平均模型

滑动平均模型是深度学习中的一个常见技术,该技巧用于使神经网络更加健壮,在未知数据中表现更好。该技术的思路就是让网络的权重参数更新时以一定的速率来变化:

         # 定义一个不用于训练的step变量,用来更新衰减率
        self.global_step = tf.Variable(0, trainable=False,name='global_step')
        # 定义一个滑动平均模型,该技巧用于使神经网络更加健壮
        with tf.name_scope('moving_averages'):
            variable_averages = tf.train.ExponentialMovingAverage(self.moving_average_decay,self.global_step)  # 生成一个滑动平均的类:v/ExponentialMovingAverage:0
            variable_averages_op = variable_averages.apply(tf.trainable_variables())  # 定义一个更新变量滑动平均的操作

4、定义计算图的损失函数

我们的项目是一个分类器,所以在分类网络中经常使用交叉熵损失函数,因为我们之前使用了正则化操作,所以这里也需要添加一个正则化损失

        with tf.name_scope('loss'):
            #cross_entropy = tf.reduce_mean(-tf.reduce_sum(self.y_input * tf.log(self.output), reduction_indices=[1]))  # 使用交叉熵计算损失函数
            cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=self.output, labels=tf.argmax(self.y_input, 1))
            cross_entropy_mean = tf.reduce_mean(cross_entropy)
            self.loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))  # 交叉熵损失添加上正则化损失
            tf.summary.scalar("loss", self.loss) # 使用TensorBoard监测该变量

5、定义计算图的准确率

对比本批次输出和我们的标签,可以计算准确率

        with tf.name_scope('accuracy'):
            correct_prediction = tf.equal(tf.argmax(self.output, 1), tf.argmax(self.y_input, 1))  # 计算本次预测是否正确
            self.accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))  # 将正确值转化为浮点类型,再转化为概率
            tf.summary.scalar("accuracy", self.accuracy) # 使用TensorBoard监测该变量

6、定义计算图的学习率

学习率是一个超参数,在实际实验要经常调整其大小,其中最常用的就是指数衰减法

即让学习率按照一定的衰减率在指定回合内衰减到指定大小,我们直接调用tf的API:

 # 定义计算图的学习率
        with tf.name_scope('learning_rate'):
            # 学习率的变化
            learning_rate = tf.train.exponential_decay(self.learn_rate_base,
                                                       self.global_step,
                                                       mnist.train.num_examples / BATCH_SIZE,
                                                       self.learn_rate_decay)
            tf.summary.scalar("learning_rate", learning_rate)  # 使用TensorBoard监测该变量

7、定义计算图的优化器

优化器定义了我们进行反向传播的操作方式,是优化神经网络的重要操作节点。

因为前面我们使用了滑动平均模型,所以我们这里要将优化器滑动平均操作组合在一起,这样两个操作就可以同时执行:

 # 定义计算图的优化器
        with tf.name_scope('optimizer'):
            # 定义优化器
            train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(self.loss,global_step=self.global_step)
            self.optimizer = tf.group(train_step, variable_averages_op)

 


提示

如果本项目对您的学习有帮助,欢迎点赞支持!

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魔法攻城狮MRL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值