TensorFlow之DNN(一):构建“裸机版”全连接神经网络

时间:2022-08-31 10:12:00

博客断更了一周,干啥去了?想做个聊天机器人出来,去看教程了,然后大受打击,哭着回来补TensorFlow和自然语言处理的基础了。本来如意算盘打得挺响,作为一个初学者,直接看项目(不是指MINIST手写数字识别这种),哪里不会补哪里,这样不仅能学习到TensorFlow和算法知识,还知道如何在具体项目中应用,学完后还能出来一个项目。是不是要为博主的想法双击666?图样!

现在明白了什么叫基础不牢地动山摇,明白了什么叫步子太大直接就放弃,明白了我是适合循序渐进的学习,暂时不适合对着项目直接干。

同时也明白了一点,那就是为什么很多TensoFlow教程都用MINIST数据集来展示如何构建各种模型,我之前还很鄙视一MINIST到底,觉得这就是个toy项目。现在明白了,用这个数据集是为了把精力集中在模型搭建和优化上,而不是浪费在数据预处理上。知道数据的输入格式,明白如何构建模型和优化模型,那么面对新的任务时,只要把数据处理成相同的输入格式,就能比较快的用TensorFlow和模型来完成任务最关键的部分。

所以打算好好学习一下用TensorFlow实现深度学习模型的基础知识,主要用的这本书:《Hands On Machine Learning with Scikit-Learn and TensorFlow》。这本书真是神书,可以说是学习TensorFlow最好的资料了,一方面这本书把各种深度学习基础都用代码实现了,从DNN到CNN、RNN、自编码器,从参数初始化、选择优化器、Batch Norm、调整学习率、网络预训练等加速技巧到dropout、早停等正则化技巧,代码丰富,讲解详尽;另一方面作者一直在Github上更新代码,根据TensorFlow语法的变化更改代码,我看到前几天还在更新,感动哭。建议电子书和Github一起看。

好,接下来首先整理如何用TensorFlow构建DNN网络,实现参数初始化、选择优化算法、Batch Norm、梯度截断和学习率衰减这些加速技巧,以及实现dropout、L1范数和L2范数等正则化技巧。

这一篇博客整理如何用TensorFlow构建一个DNN网络,后面再写博客整理如何使用加速训练技巧和正则化技巧来优化模型,这样做也是为了方便对比。

一、全连接神经网络的“裸机版”

我们先用TensorFlow搭建一个没有使用任何加速优化技巧的全连接神经网络, 可以看作是低配的“裸机版”全连接神经网络。数据集是经典的MINIST数据集,训练一个DNN用于分类,有两个隐藏层(一个有300个神经元,另一个有100个神经元)和一个带有10个神经元的softmax输出层,用小批量梯度下降算法(Mini-Batch Gradient Descent)来进行训练。我们一步步来构建,最后再给出一份完整的代码。

第一步:指定输入的维度(每个样本的特征维度),输出的维度(类别数),并设置每个隐藏层神经元的数量

MINIST数据集就是手写数字识别数据集,里面的数字图片是黑白的,所以通道数是1,那么特征维度就是长和高两维,为了方便输入到网络中,把2维矩阵拉平成1维的向量,就是28 * 28。而标签是0-9这10个数字,所以输出是10维。将第2个隐藏层的神经元设为100个,小于第1个隐藏层的神经元数,因为神经网络中越深的隐藏层,提取到的特征越高级,维度越小。

import tensorflow as tf
import numpy as np n_inputs = 28*28
n_hidden1 = 300
n_hidden2 = 100
n_outputs = 10

第二步:切分数据集,打乱顺序,并生成小批量样本

首先获取数据,推荐用tf.keras.datasets来获取这个内置的数据集,速度快。训练集和测试集都可以直接获取,再把训练集切分为用于训练的样本和用于验证的样本(5000个)。

然后用np.random.permutation这个函数打乱训练样本的索引列表,再用np.array_split这个函数把索引列表进行切分,用来获取小批量样本。

(X_train, y_train), (X_test, y_test) = tf.keras.datasets.mnist.load_data()

# 一开始是三维数组,(60000, 28, 28)
print(X_train.shape,y_train.shape) # 把数据重新组合为二维数组,(60000, 784)
X_train = X_train.astype(np.float32).reshape(-1, 28*28) / 255.0
X_test = X_test.astype(np.float32).reshape(-1, 28*28) / 255.0
y_train = y_train.astype(np.int32)
y_test = y_test.astype(np.int32)
print(X_train.shape)
X_valid, X_train = X_train[:5000],X_train[5000:]
y_valid, y_train = y_train[:5000],y_train[5000:] # 打乱数据,并生成batch
def shuffle_batch(X, y, batch_size):
# permutation不直接在原来的数组上进行操作,而是返回一个新的打乱顺序的数组,并不改变原来的数组。
rnd_idx = np.random.permutation(len(X))
n_batches = len(X) // batch_size
# 把rnd_idx这个一位数组进行切分
for batch_idx in np.array_split(rnd_idx, n_batches):
X_batch, y_batch = X[batch_idx], y[batch_idx]
yield X_batch, y_batch

第三步:定义占位符节点

为了使用小批量梯度下降算法,我们需要使用占位符节点,然后在每次迭代时用下一个小批量样本替换X和y。这些占位符节点在训练阶段才将小批量样本传入给TensorFlow,目前是构建图阶段,不执行运算。

注意到X和y的形状中有None,也就是只定义了一部分。为什么呢?

X是一个2D张量,第一个维度是样本数,第二个维度是特征。我们知道特征的数量是28*28,但是还不知道batch size是多少,所以第一个维度指定为None,也就是任意大小。

y是一个1D张量,维度是样本数,同样指定为None。

X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int32, shape=(None), name="y")

第四步:搭建网络层

注意最后一层并没有定义softmax函数来求出概率分布,而是得到通过softmax函数激活之前的输入值logtis。接下来会说明为什么这里不定义一个softmax激活函数。

当然输出的概率分布也可以计算出来,用tf.nn.softmax,但是这个值我们并不会用于下面的计算。

with tf.name_scope("dnn"):
hidden1 = tf.layers.dense(X, n_hidden1, name="hidden1",
activation=tf.nn.relu)
hidden2 = tf.layers.dense(hidden1, n_hidden2, name="hidden2",
activation=tf.nn.relu)
logits = tf.layers.dense(hidden2, n_outputs, name="outputs")
y_proba = tf.nn.softmax(logits)

第五步:定义损失函数和优化器

损失函数用交叉熵损失函数。tf.nn.sparse_softmax_cross_entropy_with_logits这个老长老长的函数,是用softmax激活之前的输入值直接计算交叉熵损失,为什么这么干呢?一方面是为了正确处理像log等于0的极端情况,这是为啥不先用softmax激活,另一方面是与样本标签的格式相匹配,我们说了样本的标签是1D张量,也就是1或者7这种整数,这是这个函数需要的格式,而不是[0, 1, 0, ..., 0]这种独热编码格式。独热编码格式的标签需要使用tf.nn.softmax_cross_entropy_with_logits这个函数。

然后定义一个GD优化器。

# 定义损失函数和计算损失
with tf.name_scope("loss"): xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y,
logits=logits)
loss = tf.reduce_mean(xentropy, name="loss")
# 定义优化器
learning_rate = 0.01
with tf.name_scope("train"):
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
training_op = optimizer.minimize(loss)

第六步:评估模型

使用准确性作为我们的模型评估指标。首先,对于每个样本,通过检查logits最大值的索引是否与标签y相等,来确定神经网络的预测是否正确。为此,可以使用in_top_k()函数,这会返回一个充满布尔值的1D张量。

然后我们需要用tf.cast这个函数将这些布尔值转换为浮点值,然后计算平均值。

# 评估模型,使用准确性作为我们的绩效指标
with tf.name_scope("eval"):
# logists最大值的索引在0-9之间,恰好就是被预测所属于的类,因此和y进行对比,相等就是True,否则为False
correct = tf.nn.in_top_k(logits, y, 1)
accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))

第七步:初始化所有变量,并保存模型

init = tf.global_variables_initializer()
saver = tf.train.Saver()

第八步:定义训练轮次和batch size,训练模型和保存模型,记录训练时间

训练40轮(epoch),把所有的样本都输入进去训练一次,叫做一轮。然后一个小批量是输入200个样本。

在训练阶段可以把小批量样本传入模型中开始训练了。

定义了一个记录训练时间的函数,因为训练时间的长短也是调参的关注点之一。

# 定义好训练轮次和batch-size
n_epochs = 40
batch_size = 200 # 获取计算所花费的时间
import time
from datetime import timedelta
def get_time_dif(start_time):
end_time = time.time()
time_dif = end_time - start_time #timedelta是用于对间隔进行规范化输出,间隔10秒的输出为:00:00:10
return timedelta(seconds=int(round(time_dif))) with tf.Session() as sess:
init.run()
start_time = time.time() for epoch in range(n_epochs):
for X_batch, y_batch in shuffle_batch(X_train, y_train, batch_size):
sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
if epoch % 5 == 0:
acc_batch = accuracy.eval(feed_dict={X: X_batch, y: y_batch})
acc_val = accuracy.eval(feed_dict={X: X_valid, y: y_valid})
print(epoch, "Batch accuracy:", acc_batch, "Val accuracy:", acc_val) time_dif = get_time_dif(start_time)
print("\nTime usage:", time_dif)
save_path = saver.save(sess, "./my_model_final.ckpt")

得到的输出结果如下。用时22秒,感觉效果贼一般啊,最后一轮的验证精度为96.3%。后面通过调整batch size,看是否能得到更好的结果。

0 Batch accuracy: 0.835 Val accuracy: 0.8132
5 Batch accuracy: 0.895 Val accuracy: 0.9178
10 Batch accuracy: 0.94 Val accuracy: 0.934
15 Batch accuracy: 0.96 Val accuracy: 0.9414
20 Batch accuracy: 0.955 Val accuracy: 0.948
25 Batch accuracy: 0.94 Val accuracy: 0.9534
30 Batch accuracy: 0.96 Val accuracy: 0.9576
35 Batch accuracy: 0.955 Val accuracy: 0.9608
39 Batch accuracy: 0.96 Val accuracy: 0.963 Time usage: 0:00:22

第九步:调用训练好的模型进行预测

把保存好的模型恢复,然后对测试集中的20个样本进行预测,发现全部预测正确。

再评估模型在全部测试集上的正确率,得到正确率为95.76%。

效果确实贼一般。

with tf.Session() as sess:
saver.restore(sess, "./my_model_final.ckpt") # or better, use save_path
X_test_20 = X_test[:20]
# 得到softmax之前的输出
Z = logits.eval(feed_dict={X: X_test_20})
# 得到每一行最大值的索引
y_pred = np.argmax(Z, axis=1)
print("Predicted classes:", y_pred)
print("Actual calsses: ", y_test[:20])
# 评估在测试集上的正确率
acc_test = accuracy.eval(feed_dict={X: X_test, y: y_test})
print("\nTest_accuracy:", acc_test)
INFO:tensorflow:Restoring parameters from ./my_model_final.ckpt
Predicted classes: [7 2 1 0 4 1 4 9 6 9 0 6 9 0 1 5 9 7 3 4]
Actual calsses: [7 2 1 0 4 1 4 9 5 9 0 6 9 0 1 5 9 7 3 4] Test_accuracy: 0.9576

二、对全连接神经网络进行微调

这里的微调是调整batch size,学习率和迭代轮次。調参还是有点花时间,我就以调整batch size为例,来看看是否可以得到更好的结果。

分别把batch size设置为200,100,50,10,训练好模型后,计算在测试集上的正确率,分别为:

batch_size = 200    Test_accuracy: 0.9576

batch_size = 100    Test_accuracy: 0.9599

batch_size = 50     Test_accuracy: 0.9781

batch_size = 10     Test_accuracy: 0.9812

发现规律没有,在这个数据集上,batch size越小,则测试精度越高。batch size为10的时候,测试精度达到了98.12%,还是很不错的。

然后我想,如果用SGD,每次只输入一个样本又会怎么样呢?测试精度还能不能提高我不知道,可是能确定的一点是,我点击运行之后,我可以去听几首邓紫棋的歌再回来了。然后我就去跑步了。

我跑步和听歌回来了,用SGD训练模型耗时26分30秒,测试精度为98.51%,取得了到目前为止最好的成绩。

三、完整代码整理

import tensorflow as tf
import numpy as np
import time
from datetime import timedelta # 记录训练花费的时间
def get_time_dif(start_time):
end_time = time.time()
time_dif = end_time - start_time
#timedelta是用于对间隔进行规范化输出,间隔10秒的输出为:00:00:10
return timedelta(seconds=int(round(time_dif))) n_inputs = 28*28
n_hidden1 = 300
n_hidden2 = 100
n_outputs = 10 (X_train, y_train), (X_test, y_test) = tf.keras.datasets.mnist.load_data() # 一开始是三维数组,(60000, 28, 28)
print(X_train.shape,y_train.shape) # 把数据重新组合为二维数组,(60000, 784)
X_train = X_train.astype(np.float32).reshape(-1, 28*28) / 255.0
X_test = X_test.astype(np.float32).reshape(-1, 28*28) / 255.0
y_train = y_train.astype(np.int32)
y_test = y_test.astype(np.int32)
print(X_train.shape)
X_valid, X_train = X_train[:5000],X_train[5000:]
y_valid, y_train = y_train[:5000],y_train[5000:] # 打乱数据,并生成batch
def shuffle_batch(X, y, batch_size):
# permutation不直接在原来的数组上进行操作,而是返回一个新的打乱顺序的数组,并不改变原来的数组。
rnd_idx = np.random.permutation(len(X))
n_batches = len(X) // batch_size
# 把rnd_idx这个一位数组进行切分
for batch_idx in np.array_split(rnd_idx, n_batches):
X_batch, y_batch = X[batch_idx], y[batch_idx]
yield X_batch, y_batch X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int32, shape=(None), name="y") with tf.name_scope("dnn"):
hidden1 = tf.layers.dense(X, n_hidden1, name="hidden1",
activation=tf.nn.relu)
hidden2 = tf.layers.dense(hidden1, n_hidden2, name="hidden2",
activation=tf.nn.relu)
logits = tf.layers.dense(hidden2, n_outputs, name="outputs")
y_proba = tf.nn.softmax(logits) # 定义损失函数和计算损失
with tf.name_scope("loss"): xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y,
logits=logits)
loss = tf.reduce_mean(xentropy, name="loss") # 定义优化器
learning_rate = 0.01
with tf.name_scope("train"):
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
training_op = optimizer.minimize(loss) # 评估模型,使用准确性作为我们的绩效指标
with tf.name_scope("eval"):
# logists最大值的索引在0-9之间,恰好就是被预测所属于的类,因此和y进行对比,相等就是True,否则为False
correct = tf.nn.in_top_k(logits, y, 1)
accuracy = tf.reduce_mean(tf.cast(correct, tf.float32)) init = tf.global_variables_initializer()
saver = tf.train.Saver() # 定义好训练轮次和batch-size
n_epochs = 40
batch_size = 200 with tf.Session() as sess:
init.run()
start_time = time.time() for epoch in range(n_epochs):
for X_batch, y_batch in shuffle_batch(X_train, y_train, batch_size):
sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
if epoch % 5 == 0 or epoch == 39:
acc_batch = accuracy.eval(feed_dict={X: X_batch, y: y_batch})
acc_val = accuracy.eval(feed_dict={X: X_valid, y: y_valid})
print(epoch, "Batch accuracy:", acc_batch, "Val accuracy:", acc_val) time_dif = get_time_dif(start_time)
print("\nTime usage:", time_dif)
save_path = saver.save(sess, "./my_model_final.ckpt")
with tf.Session() as sess:
saver.restore(sess, "./my_model_final.ckpt") # or better, use save_path
X_test_20 = X_test[:20]
# 得到softmax之前的输出
Z = logits.eval(feed_dict={X: X_test_20})
# 得到每一行最大值的索引
y_pred = np.argmax(Z, axis=1)
print("Predicted classes:", y_pred)
print("Actual calsses: ", y_test[:20])
# 评估在测试集上的正确率
acc_test = accuracy.eval(feed_dict={X: X_test, y: y_test})
print("\nTest_accuracy:", acc_test)

参考资料:

《Hands On Machine Learning with Scikit-Learn and TensorFlow》

TensorFlow之DNN(一):构建“裸机版”全连接神经网络的更多相关文章

  1. TensorFlow之DNN(二):全连接神经网络的加速技巧(Xavier初始化、Adam、Batch Norm、学习率衰减与梯度截断)

    在上一篇博客<TensorFlow之DNN(一):构建“裸机版”全连接神经网络>中,我整理了一个用TensorFlow实现的简单全连接神经网络模型,没有运用加速技巧(小批量梯度下降不算哦) ...

  2. tensorflow中使用mnist数据集训练全连接神经网络-学习笔记

    tensorflow中使用mnist数据集训练全连接神经网络 ——学习曹健老师“人工智能实践:tensorflow笔记”的学习笔记, 感谢曹老师 前期准备:mnist数据集下载,并存入data目录: ...

  3. 【TensorFlow&sol;简单网络】MNIST数据集-softmax、全连接神经网络,卷积神经网络模型

    初学tensorflow,参考了以下几篇博客: soft模型 tensorflow构建全连接神经网络 tensorflow构建卷积神经网络 tensorflow构建卷积神经网络 tensorflow构 ...

  4. Tensorflow 多层全连接神经网络

    本节涉及: 身份证问题 单层网络的模型 多层全连接神经网络 激活函数 tanh 身份证问题新模型的代码实现 模型的优化 一.身份证问题 身份证号码是18位的数字[此处暂不考虑字母的情况],身份证倒数第 ...

  5. 深度学习tensorflow实战笔记(1)全连接神经网络(FCN)训练自己的数据(从txt文件中读取)

    1.准备数据 把数据放进txt文件中(数据量大的话,就写一段程序自己把数据自动的写入txt文件中,任何语言都能实现),数据之间用逗号隔开,最后一列标注数据的标签(用于分类),比如0,1.每一行表示一个 ...

  6. 如何使用numpy实现一个全连接神经网络?(上)

    全连接神经网络的概念我就不介绍了,对这个不是很了解的朋友,可以移步其他博主的关于神经网络的文章,这里只介绍我使用基本工具实现全连接神经网络的方法. 所用工具: numpy == 1.16.4 matp ...

  7. MINIST深度学习识别:python全连接神经网络和pytorch LeNet CNN网络训练实现及比较(三)

    版权声明:本文为博主原创文章,欢迎转载,并请注明出处.联系方式:460356155@qq.com 在前两篇文章MINIST深度学习识别:python全连接神经网络和pytorch LeNet CNN网 ...

  8. 基于MNIST数据集使用TensorFlow训练一个包含一个隐含层的全连接神经网络

    包含一个隐含层的全连接神经网络结构如下: 包含一个隐含层的神经网络结构图 以MNIST数据集为例,以上结构的神经网络训练如下: #coding=utf-8 from tensorflow.exampl ...

  9. Keras入门——(1)全连接神经网络FCN

    Anaconda安装Keras: conda install keras 安装完成: 在Jupyter Notebook中新建并执行代码: import keras from keras.datase ...

随机推荐

  1. mysql dump

    mysqldump wifi3 > wifi3.sql mysqldump wifi3 < wifi3.sql

  2. Winform开发框架的重要特性总结

    从事Winform开发框架的研究和推广,也做了有几个年头了,从最初的项目雏形到目前各种重要特性的加入完善,是经过了很多项目的总结归纳和升华,有些则是根据客户需要或者应用前景的需要进行的完善,整个Win ...

  3. Keil &colon; Contents missmatch at&colon;08000E84H Verify Failed&excl;

    Keil 下载时出以下错误: Device: STM32F103VB VTarget = 3.300V State of Pins: TCK: 0, TDI: 0, TDO: 1, TMS: 0, T ...

  4. 详解Makefile 函数的语法与使用

    使用函数: 在Makefile中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能.make所支持的函数也不算很多,不过已经足够我们的操作了.函数调用后,函数的返回值可以当做变量来使 ...

  5. ASP&period;NET获取IP的6种方法 【转】

    ).ToString();//方法四(无视代理)HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR" ...

  6. 利用webpack构建vue项目

    快速搭建vue项目 一,确认自己有无搭建好node以及npm环境,这些是前提,具体安装方法可参考https://nodejs.org/en/. 二,开始构建项目. 第1步:新建一个文件夹,随意命名. ...

  7. HTML(七)HTML 表单&lpar;form元素介绍,input元素的常用type类型,input元素的常用属性&rpar;

    前言 表单是网页与用户的交互工具,由一个<form>元素作为容器构成,封装其他任何数量的表单控件,还有其他任何<body>元素里可用的标签 表单能够包含<input&gt ...

  8. &commat;Autowired用法详解

    @Autowired 注释,它可以对类成员变量.方法及构造函数进行标注,完成自动装配的工作. 通过 @Autowired的使用来消除 set ,get方法.在使用@Autowired之前,我们对一个b ...

  9. numpy二分查找

    a = np.array([1, 2, 2, 3]) print(np.searchsorted(a, 0)) # 0 print(np.searchsorted(a, 1)) # 0 print(n ...

  10. 【虫师Python】第二讲:元素定位

    一.六种定位方式 1.id 2.name 3.class name 4.tag name:定位标签 5.link text:定位一个链接,如果是中文,需要在代码文最前面加一句I话|:#coding=u ...