8.3 TensorFlow BP神经网络构建与超参数的选取

前言

之前的8.1 构建回归模型的重点在于计算图概念,8.2则介绍一些在整个流程中更靠后的部分:损失函数,优化函数,以及一些其他常用的函数.而本片中的重点在于构建计算图,与模型的训练与测试BP

代码与讲解

设置数据

之所以对第一次生成的随机数据进行存储主要是为了能够进行后面的超参数的选取

# 生成与加载数据
# 构造满足一元二次方程的函数
def Build_Data():

    Path_x_data = \'/home/fonttian/Data/tf_basis/8_2_df_x_data.npy\'
    Path_y_data = \'/home/fonttian/Data/tf_basis/8_2_df_y_data.npy\'

    if os.path.exists(Path_x_data) and os.path.exists(Path_y_data):
        x_data = np.load(Path_x_data)
        y_data = np.load(Path_y_data)
    else:
        x_data = np.linspace(-1, 1, 300)[:, np.newaxis]
        # 为了使点更密一些,我们构建了300个点,分布在-1到1 的区间,直接曹永np生成等差数列的方式,并将结果为300个点的一维函数,转换为300 * 1 的二维函数
        noise = np.random.normal(0, 0.05, x_data.shape)
        # 加入一些噪声点,使它与x_data的维度一致,并且拟合为均值为0,方差为0.05的正态分布
        y_data = np.square(x_data) - 0.5 + noise
        # y = x^2 - 0.5 + 噪声

        if not os.path.exists("/home/fonttian/Data/tf_basis"):
            os.mkdir("/home/fonttian/Data/tf_basis")
        # 创建文件夹

        np.save(Path_x_data,x_data)
        np.save(Path_y_data,y_data)
        # 读取数据

    return (x_data,y_data)

添加神经元

本处添加的神经元隐藏层数为二,隐藏神经元为二十,由我们之前的神经网络知识所知道,输入层到隐藏层与隐藏层到输入层有相当多的代码一致,所以我们将相同代码提取出来,设计一个add_layer()函数,根据该代码,同样的,当你需要设置隐藏层到隐藏层时

def add_layer(inputs, in_size, out_size, activation_function=None):
    # 构建权重 : in_size * out)_sieze 大小的矩阵
    weights = tf.Variable(tf.zeros([in_size, out_size]))
    # 构建偏置 : 1 * out_size 的矩阵
    biases = tf.Variable(tf.zeros([1, out_size]) + 0.1)
    # 矩阵相乘
    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  # 得到输出数据


hidden_layers = 50
# 构建输入层到隐藏层,假设隐藏层有 hidden_layers 个神经元
h1 = add_layer(xs, 1, hidden_layers, activation_function=tf.nn.sigmoid)
# 构建隐藏层到隐藏层
# h2 = add_layer(h1, hidden_layers, hidden_layers, activation_function=tf.nn.sigmoid)
# 构建隐藏层到隐藏层
# h3 = add_layer(h2, hidden_layers, hidden_layers, activation_function=tf.nn.sigmoid)
# 构建隐藏层到输出层
prediction = add_layer(h1, hidden_layers, 1, activation_function=None)

然后是构建损失函数与优化函数,并存储计算图

# 接下来构建损失函数: 计算输出层的预测值和真是值间的误差,对于两者差的平方求和,再取平均,得到损失函数.运用梯度下降法,以0.1的效率最小化损失
loss = tf.reduce_mean(tf.reduce_sum(tf.square(ys - prediction), reduction_indices=[1]))
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss) # 优化算法选取SGD,随机梯度下降

print(\'将计算图写入事件文件,在TensorBoard里查看\')
writer = tf.summary.FileWriter(logdir=\'logs/8_2_BP\', graph=tf.get_default_graph())
writer.close()

训练模型并测试

# 训练模型
(x_data,y_data) = Build_Data()
# 我们让TensorFlow训练1000次,每50次输出训练的损失值:
with tf.Session() as sess:
    tf.global_variables_initializer().run()  # 初始化所有变量

    for i in range(100000):
        sess.run(train_step, feed_dict={xs: x_data, ys: y_data})
        if i % 50 == 0:
            print(\'第%d次的损失值为\'%i,sess.run(loss, feed_dict={xs: x_data, ys: y_data}))

选取超参数

可以参考的文章

关于随机初始化,建议阅读这篇文章:http://blog.csdn.net/fontthrone/article/details/77970533

关于激活函数的一些介绍以及TensorFlow中的局部响应归一化(之前(谷歌的论文)?中提到:一般提升1~2%):http://blog.csdn.net/fontthrone/article/details/77970486,仅以参考这一篇文章.

该例子我做了什么

选择激活函数与隐藏层数

本例子中我只对隐藏层数与隐藏节点数与激活函数种类进行少量测试,

首先一般而言,BP神经网络的隐藏层数不能太多,不然一般梯度会消失,而三层的隐藏层理论上足以拟合任何结构,那么首先我们选取最常用的激活函数sigmid函数进行测试.但是结果似乎并没有说明什么,然后我继续使用relu函数与tanh函数进行测试,测试次数为10000次,隐藏节点数为20

# 第9950次的损失值为 0.0913932 h1 sigmoid
# 第9950次的损失值为 0.0914038 20 h2 sigmoid
# 第9950次的损失值为 0.0914038 20 h3 sigmoid

# 第99950次的损失值为 0.0913863 20 h1 relu
# 第9950次的损失值为 0.0914038 20 h2 relu
# 第9950次的损失值为 0.0914038 20 h3 relu

# 第9950次的损失值为 0.0913932 20 h1 tanh
# 第9950次的损失值为 0.0914038 20 h2 tanh
# 第9950次的损失值为 0.0914038 20 h3 tanh

此时仅仅从最后的结果中我们就可以看出,在本例子(一元二次方程)中,一个隐藏层的BP神经网络为最佳

而且,通过变化率我们还知道了另外一个极其重要的信息,那就是tanh函数与sigmoid函数其最后都没用长时间停滞在某个值,无法再继续优化loss函数.此时待定两个激活函数:tanh函数与sigmoid函数

但是因为测试次数较少,我们还不能确定其是否真的陷入了局部最优解,所以我又进行了100000(十万)次的测试,结果显示,虽然部分超参数时还会有微小的变化,但是loss已经几乎不变了,这个时候我们可以继续进行更长时间的测试,但是因为时间关系我直接进行了超参数的下一步的调参优化,选择合适的隐藏节点数

最终选择激活函数与隐藏节点数

一般而言隐藏节点数应当在测试样本的2~10倍,之前我们选取的20似乎有点不合适?那么现在我们还是选取合适的隐藏节点数进行测试吧.

但是因为时间与篇幅关系,我并没有进行过于详细的测试,如果在比赛或者工程中还是进行比较详细的测试比较好

测试结果如下:

# 第99950次的损失值为 0.0913843 20 h1 sigmoid
# 第99950次的损失值为 0.0913146 50 h1 sigmoid 最佳结果,但是loss降低率已经大幅下降
# 第99950次的损失值为 0.0913899 40 h1 sigmoid
# 第99900次的损失值为 0.0913846 30 h1 sigmoid

# 而tanh除了50与40外,其他节点似乎都陷入了局部最优
# 第99950次的损失值为 0.091384 50 h1 tanh 没有陷入局部最优,loss的改变速度任然较快
# 第99950次的损失值为 0.0913966 40 h2 tanh 没有陷入局部最优,loss的改变速度任然较快

而此时,我们可以选取 50 sigmoid为最后的超参数,也可以选择进行更多次数的测试,然后再选择最后的超参数,本例子不再做更多演示,请自行测试.

超参数测试总结

  1. 首先要明确一个观念,TensorFlow只不过是深度学习的一个工具而已,学习深度学习的关键任然在于理论,先有理论再有代码,反其道而行之则是行不通的
  2. 其实在实际的应用中超参数的选取要比该例子中的复杂的多,调参虽然有一定的理论指导,但是很多时候还是有点玄学的,平时多读paper多写代码才能提高调参时的洪荒之力

code_all

# -*- coding: UTF-8 -*-
# 作者:田丰(FontTian)
# 创建时间:\'2017/8/7\'
# 邮箱:fonttian@Gmaill.com
# CSDN:http://blog.csdn.net/fontthrone
#
import tensorflow as tf
import os
import numpy as np
import pandas as pd
import sys

os.environ[\'TF_CPP_MIN_LOG_LEVEL\'] = \'2\'


# 生成与加载数据
# 构造满足一元二次方程的函数
def Build_Data():

    Path_x_data = \'/home/fonttian/Data/tf_basis/8_2_df_x_data.npy\'
    Path_y_data = \'/home/fonttian/Data/tf_basis/8_2_df_y_data.npy\'

    if os.path.exists(Path_x_data) and os.path.exists(Path_y_data):
        x_data = np.load(Path_x_data)
        y_data = np.load(Path_y_data)
    else:
        x_data = np.linspace(-1, 1, 300)[:, np.newaxis]
        # 为了使点更密一些,我们构建了300个点,分布在-1到1 的区间,直接曹永np生成等差数列的方式,并将结果为300个点的一维函数,转换为300 * 1 的二维函数
        noise = np.random.normal(0, 0.05, x_data.shape)
        # 加入一些噪声点,使它与x_data的维度一致,并且拟合为均值为0,方差为0.05的正态分布
        y_data = np.square(x_data) - 0.5 + noise
        # y = x^2 - 0.5 + 噪声

        if not os.path.exists("/home/fonttian/Data/tf_basis"):
            os.mkdir("/home/fonttian/Data/tf_basis")
        # 创建文件夹

        np.save(Path_x_data,x_data)
        np.save(Path_y_data,y_data)
        # 读取数据

    return (x_data,y_data)


xs = tf.placeholder(tf.float32, [None, 1])
ys = tf.placeholder(tf.float32, [None, 1])

# 构建网络模型

def add_layer(inputs, in_size, out_size, activation_function=None):
    # 构建权重 : in_size * out)_sieze 大小的矩阵
    weights = tf.Variable(tf.zeros([in_size, out_size]))
    # 构建偏置 : 1 * out_size 的矩阵
    biases = tf.Variable(tf.zeros([1, out_size]) + 0.1)
    # 矩阵相乘
    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  # 得到输出数据

hidden_layers = 20
# 构建输入层到隐藏层,假设隐藏层有 hidden_layers 个神经元
h1 = add_layer(xs, 1, hidden_layers, activation_function=tf.nn.sigmoid)
# 构建隐藏层到隐藏层
h2 = add_layer(h1, hidden_layers, hidden_layers, activation_function=tf.nn.sigmoid)
# 构建隐藏层到隐藏层
h3 = add_layer(h2, hidden_layers, hidden_layers, activation_function=tf.nn.sigmoid)
# 构建隐藏层到输出层
prediction = add_layer(h3, hidden_layers, 1, activation_function=None)

# 接下来构建损失函数: 计算输出层的预测值和真是值间的误差,对于两者差的平方求和,再取平均,得到损失函数.运用梯度下降法,以0.1的效率最小化损失
loss = tf.reduce_mean(tf.reduce_sum(tf.square(ys - prediction), reduction_indices=[1]))
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss) # 优化算法选取SGD,随机梯度下降

print(\'将计算图写入事件文件,在TensorBoard里查看\')
writer = tf.summary.FileWriter(logdir=\'logs/8_2_BP\', graph=tf.get_default_graph())
writer.close()

# 训练模型
(x_data,y_data) = Build_Data()
# 我们让TensorFlow训练1000次,每50次输出训练的损失值:
with tf.Session() as sess:
    tf.global_variables_initializer().run()  # 初始化所有变量

    for i in range(10000):
        sess.run(train_step, feed_dict={xs: x_data, ys: y_data})
        if i % 50 == 0:
            print(\'第%d次的损失值为\'%i,sess.run(loss, feed_dict={xs: x_data, ys: y_data}))