Tensorflow搭建神经网络进阶篇

写在前面

Tensorflow搭建神经网络基础篇中,我们已经介绍了基本的神经网络的组成结构和搭建方法。在本博文中我们将会在上篇博文基础之上展示一些进阶内容。本博文主要涉及以下内容:Tensorboard可视化、分类学习任务、过拟合问题的产生以及解决方法。

1、可视化:Tensorboard

(1)什么是Tensorboard

TensorBoard可以将TensorFlow程序的执行步骤都显示出来,非常直观。并且,我们可以对训练的参数(比如loss值)进行统计,用图的方式来查看变化的趋势。

(2)示例程序

导入Tensorflow模块、Numpy模块。

1
2
import tensorflow as tf
import numpy as np

定义添加层函数。tf.name_scope()函数在这里建立了一个变量空间,变量空间下包含几个变量权重(Weights)、偏差(biases)、预测值(Wx_plus_b)、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def add_layer(inputs, in_size, out_size, n_layer, activation_function=None):
layer_name = "layer%s"%n_layer
with tf.name_scope('weights'):
Weights = tf.Variable(tf.random_normal([in_size, out_size]))#变量中的值都是随机变量(random)的值
tf.summary.histogram(layer_name+'/weights', Weights)
with tf.name_scope('biases'):
biases = tf.Variable(tf.zeros([1, out_size]) + 0.1)
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)
tf.summary.histogram(layer_name + '/outputs', outputs)
return outputs

定义输入数据和输出值函数。

1
2
3
x_data = np.linspace(-1, 1, 300)[:, np.newaxis]
noise = np.random.normal(0, 0.05, x_data.shape)
y_data = np.square(x_data) - 0.5 + noise

在这里要注意x_data的输出值是经过变形得到的,主要是由于[:, np.newaxis]函数的原因,下面的例子将展示该函数的使用。

1
2
3
4
5
6
7
8
9
10
11
import tensorflow as tf
import numpy as np

a = np.arange(10)
b = a[:, np.newaxis]
c = a[np.newaxis, :]

sess = tf.Session()
print(a)
print(b)
print(c)

程序输出结果:

np.newaxis函数使用示例结果

将占位符xs和ys设置在一个图层中,并将图层命名为inputs。xs在图层中显示为x_input,ys在图层中显示为y_input。

1
2
3
with tf.name_scope('inputs'):
xs = tf.placeholder(tf.float32, [None, 1], name='x_input')
ys = tf.placeholder(tf.float32, [None, 1], name='y_input')

设置隐藏层。

1
2
with tf.name_scope('layer1'):
l1 = add_layer(xs, 1, 10, n_layer=1, activation_function=tf.nn.relu)

设置输出层。

1
2
with tf.name_scope('layer2'):
prediction = add_layer(l1, 10, 1, n_layer=2, activation_function=None)

设置损失函数。

1
2
3
with tf.name_scope('loss'):
loss = tf.reduce_mean(tf.reduce_sum(tf.square(ys - prediction), reduction_indices=[1]))
tf.summary.scalar('loss', loss)

设置训练优化函数。

1
2
with tf.name_scope('train'):
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)

开始运行训练会话。

1
2
3
4
5
6
7
8
sess = tf.Session()
merged = tf.summary.merge_all()#将summary打包
writer = tf.compat.v1.summary.FileWriter('log/.', sess.graph)
for i in range(1000):
sess.run(train_step, feed_dict={xs: x_data, ys: y_data})
if i % 50 == 0:
result = sess.run(merged, feed_dict={xs: x_data, ys: y_data})
writer.add_summary(result, i)

程序运行完毕后,在根目录下的log文件夹中包含一个envents文件。本次Tensorboard程序存储在Tensorboard文件夹中,文件结构如下:

1
2
3
Tensorboard
log->envents
Tensorboard.py

打开命令行窗口,激活tensorflow环境,执行tensorboard —logdir=”Tensorboard”语句,命令行将会出现一个链接,复制链接到Google Chrome浏览器,即可查看预训练有关的曲线、神经网络的流程图以及变量的统计图。

训练损失函数曲线:

Tensorboard损失函数曲线图

神经网络流程图:

Tensorboard神经网络的流程图

单层数据统计图:

Tensorboard单层数据统计图

2、分类学习任务:Classification

(1)什么是分类?

首先,需要思考以下什么是分类?很显然,分类是将数据集中的数据进行归纳整理的一个过程。现实中我们经常会遇到分类问题,例如何对书籍进行归纳和整理、计算机文件夹中的文件如何放置以及桌面物品的收纳整理等。在我们去分类的过程中,是否意识到一个问题,我们是依据什么标准去对杂乱的书籍、文件以及物品进行分类整理的呢?

答案其实隐藏在我们思考的过程中,只不过是我们没有意识到而已。当我们对这些东西进行分类整理的过程中,在脑海里都会有一个思考过程,就是这个东西和什么有关,要把它整理到哪个类别中呢,这个过程就是我们对文件和物品进行了一个打上标签印象的过程。在分类学习任务中,我们也需要对待分类的数据进行打标签的操作,之后通过神经网络进行分类操作。

神经网络分类的结果并不一定总是很优秀的,自然而然需要对分类结果进行评价。不同的要求对分类结果的评价也不尽相同。

(2)例程:MNIST手写体分类

数据集:MNIST手写体数据集,包括0-9十个数字的手写体。

MNIST数据集详细介绍:

文件名称 大小 内容
train-images-idx3-ubyte.gz 9,681 kb 55000张训练集,5000张验证集
train-labels-idx1-ubyte.gz 29 kb 训练集图片对应的标签
t10k-images-idx3-ubyte.gz 1,611 kb 10000张测试集
t10k-labels-idx1-ubyte.gz 5 kb 测试集图片对应的标签

导入Tensorflow模块。

1
import tensorflow as tf

读取MNIST数据集,数据集包含55000张训练集,每张图片的分辨率大小是28*28。

1
2
3
from tensorflow.examples.tutorials.mnist import input_data
#number 1 to 10 data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)#括号内是MNIST数据集存放的路径

定义添加层函数,添加层函数的定义参考上一篇博文“Tensorflow搭建神经网络基础篇”。

1
2
3
4
5
6
7
8
9
def add_layer(inputs, in_size, out_size, activation_function=None):
Weights = tf.Variable(tf.random_normal([in_size, 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

定义训练准确率函数。tf.argmax()函数是索引,tf.equal()是比较。tf.cast()将数据进行转换,由于tf.equal()返回的向量或矩阵的组成元素是True或者False,因此采用tf.cast()将True转换为1,False转换成0。tf.reduce_mean()求解均值。

1
2
3
4
5
6
7
def compute_accuracy(v_xs, v_ys):
global prediction #将prediction定义成全局变量
y_pre = sess.run(prediction, feed_dict={xs:v_xs}) #y的预测值
correct_prediction = tf.equal(tf.argmax(y_pre, 1), tf.argmax(v_ys, 1)) #比较预测值和实际值是否相等,假设有50个预测值,判断有多少个预测值和真实值是相等的
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) #指定方向的平均值
result = sess.run(accuracy, feed_dict={xs:v_xs, ys:v_ys})
return result

注:下面一段程序可以更加清晰的认识tf.cast()函数和tf.reduce_mean()函数。

1
2
3
4
5
6
7
8
9
10
11
12
import tensorflow as tf

x = [[True, False, True, False, True, False]]
xx = tf.cast(x, tf.float32)

init = tf.global_variables_initializer()

with tf.Session() as sess:
sess.run(init)
a = tf.reduce_mean(xx)
print(sess.run(xx))
print(sess.run(a))

定义输入和输出占位符。由于每张图片的分辨率大小是28*28,因此需要输入784个像素数据;输出类别为10,对应MNIST数据集的数据组成类别。

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

定义输出层。在这里并没有定义隐藏层,而是直接定义了一个输出层。通常神经层由输入层、隐藏层和输出层组成,在这里省略了隐藏层,这样构成的神经网络实际上是一个由输入层和输出层构成的二层的网络,数据的输入直接作为输入层。

1
prediction = add_layer(xs, 784, 10, activation_function=tf.nn.softmax)#激励函数采用了softmax函数

定义损失函数,在这里采用了交叉熵作为损失函数。

1
cross_entropy = tf.reduce_mean(-tf.reduce_sum(ys*tf.log(prediction), reduction_indices=[1]))

定义神经网络的训练优化器。TensorFlow优化器GradientDescentOptimizer是一个实现梯度下降算法的优化器。在这里取值是0.5表示以0.5的效率来最小化误差交叉熵(cross_entropy)。

1
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

初始化变量。

1
init = tf.global_variables_initializer()

初始化会话控制Session。

1
sess = tf.Session()

初始化变量。

1
sess.run(init)

开始训练神经网络。执行1000次训练,在这里采用了批量梯度下降算法,因此设置每一次一批批训练数据为100个。注意,batch_xs是训练值,其输出是prediction;batch_ys是真实值(也就是设置的标签),其主要作用是计算交叉熵(cross_entropy)。在这里每50次输出一次神经网络的准确率值。

1
2
3
4
5
6
for i in range(1000):
batch_size = 100
batch_xs, batch_ys = mnist.train.next_batch(batch_size) #从train的集合中选取batch_size个训练数据
sess.run(train_step, feed_dict={xs:batch_xs, ys:batch_ys})
if i % 50 == 0:
print(compute_accuracy(mnist.test.images, mnist.test.labels))

神经网络训练准确率输出结果:

分类例程训练准确率结果

可以看到随着训练次数的不断增加,这个两层的简单神经网络已经能够实现很不错的分类效果。当然,我们可以调整训练次数和batch_size来实现更加准确的训练,然而训练并不是无止境的。盲目的扩大训练参数很可能会使神经网络陷入过拟合,也就是过度学习的问题。接下来我们会针对过拟合问题进行概念上的介绍,并且介绍如何防止过拟合的解决方法。

3、过拟合:Overfitting

为了使训练数据与训练标签一致,而对模型过度训练,从而使得模型出现过拟合(over-fitting)现象。具体表现为,训练后的模型在训练集中正确率很高,但是在测试集中的变现与训练集相差悬殊,也可以叫做模型的泛化能力差。下图展示了分类模型中过拟合的现象。

过拟合概念图

也就是说神经网络只对训练集有很好的效果而对测试集效果很差,模型的泛化能力弱这样的现象被称为过拟合现象。

4、过拟合的解决方法:Dropout

Dropout是解决神经网络出现过拟合情况的一种常用的方法。Dropout方法是在一定的概率上(通常设置为0.5,原因是此时随机生成的网络结构最多)隐式的去除网络中的神经元,如下图:

Dropout

Tensorflow提供了Dropout方法来解决过拟合问题,在Tensorflow中可以很方便地使用。

示例程序:

导入Tensorflow模块以及从sklearn数据库导入数据。

1
2
3
4
import tensorflow as tf
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelBinarizer

定义添加层函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def add_layer(inputs, in_size, out_size, n_layer, activation_function=None):#在此激励函数(activation function)是None,代表线性关系
with tf.name_scope('layer'):
layer_name = 'layer%s'%n_layer
with tf.name_scope('weights'):
Weights = tf.Variable(tf.random_normal([in_size, out_size]))#变量中的值都是随机变量(random)的值
tf.summary.histogram(layer_name+'/weights', Weights)
with tf.name_scope('biases'):
biases = tf.Variable(tf.zeros([1, out_size]) + 0.1)
tf.summary.histogram(layer_name + '/biases', biases)
with tf.name_scope('Wx_plus_b'):
Wx_plus_b = tf.matmul(inputs, Weights) + biases#未激活的值存储在这里面
Wx_plus_b = tf.nn.dropout(Wx_plus_b, keep_prob)#Dropout:此部分存储的值一部分不用
if activation_function is None:
outputs = Wx_plus_b
else:
outputs = activation_function(Wx_plus_b)
tf.summary.histogram(layer_name + '/outputs', outputs)
return outputs

准备数据。数据最后被分为训练集和测试集。train_test_split()函数是用来随机划分样本数据为训练集和测试集,参数介绍如下。

train_X,test_X,train_y,test_y = train_test_split(train_data,train_target,test_size=0.3,random_state=5):

train_data:待划分样本数据;

train_target:待划分样本数据的结果(标签);

test_size:测试数据占样本数据的比例,若整数则样本数量;random_state:设置随机数种子,保证每次都是同一个随机数。若为0或不填,则每次得到数据都不一样。

1
2
3
4
5
digits = load_digits()#从sklearn.datasets导入的数据
X = digits.data
y = digits.target
y = LabelBinarizer().fit_transform(y)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.3)

定义占位符。

1
2
3
keep_prob = tf.placeholder(tf.float32)
xs = tf.placeholder(tf.float32, [None, 64])#8x8
ys = tf.placeholder(tf.float32, [None, 10])#有10个输出

定义隐藏层和输出层。注意输入数据的个数和维度是两个不同的定义。

1
2
3
#add output layer
l1 = add_layer(xs, 64, 50, 'l1', activation_function=tf.nn.tanh)#64个输入,隐藏层有100个神经元,激励函数是relu
prediction = add_layer(l1, 50, 10, 'l2', activation_function=tf.nn.softmax)#l1隐藏层有100个神经元,size=10,输出是10个

定义损失函数,在这里采用交叉熵进行计算,并且采用tf.summary.scalar()函数在Tensorboard中进行显示。

1
2
cross_entropy = tf.reduce_mean(-tf.reduce_sum(ys*tf.log(prediction), reduction_indices=[1]))#交叉熵,loss
tf.summary.scalar('loss', cross_entropy)

定义神经网络的训练优化器。TensorFlow优化器GradientDescentOptimizer是一个实现梯度下降算法的优化器。在这里取值是0.5表示以0.5的效率来最小化误差交叉熵(cross_entropy)。

1
train_step = tf.train.GradientDescentOptimizer(0.6).minimize(cross_entropy)

开启会话窗口。

1
2
3
4
5
sess = tf.Session()
merged = tf.summary.merge_all()#将summary打包
#summary writer goes in here
train_writer = tf.compat.v1.summary.FileWriter('logs/train', sess.graph)#将训练图线保存到指定位置
test_writer = tf.compat.v1.summary.FileWriter('logs/test', sess.graph)#将测试图线保存到指定位置

执行训练程序。在这里keep_prob指保留结果的概率,一般在大量数据训练时,为了防止过拟合,添加Dropout层,设置一个0~1之间的小数。在这段程序中,训练次数设置为500次。

1
2
3
4
5
6
7
8
9
10
11
12
sess.run(tf.initialize_all_variables())

for i in range(500):
sess.run(train_step, feed_dict={xs: X_train, ys: y_train, keep_prob: 0.5})
if i % 50 == 0:
#record loss
#train_summary = sess.run(merge_summary,feed_dict = {...})#调用sess.run运行图,生成一步的训练过程数据
train_result = sess.run(merged, feed_dict={xs:X_train, ys:y_train,keep_prob: 1})
test_result = sess.run(merged, feed_dict={xs:X_test, ys:y_test,keep_prob: 1})
#train_writer.add_summary(train_summary,step)#调用train_writer的add_summary方法将训练过程以及训练步数保存
train_writer.add_summary(train_result, i)
test_writer.add_summary(test_result, i)