最近在学深度学习的编程实现,以博客的形式把每天学到的东西总结一下,相信会有所收获。
注:写这篇博客时参考了《TensorFlow实战Google深度学习框架》第二版,主要对其中的第三章“TensorFlow入门”中的内容进行总结。
目录
1 TensorFlow的计算模型——计算图
TensorFlow是一个通过计算图的形式来表述计算的编程系统,其中TensorFlow中的每一个计算都是计算图上的一个节点,而节点之间相连的边描述了计算之间的依赖关系。
TensorFlow程序一般可以大致分为两个阶段:
第一阶段:定义计算图中所有的计算;
第二阶段:执行计算
TensorFlow会自动将定义的计算转化为计算图上的节点,在TensorFlow程序中,系统会自动维护一个默认的计算图,同时我们也可以通过tf.Graph()函数来生成新的计算图。不同计算图上的张量和运算都不会共享。
TensorFlow中的计算图不仅可以用来隔离张量和计算,它还提供了管理张量和计算的机制,计算图可以通过tf.Graph.device()函数来指定运行计算的设备。有效地整理TensorFlow程序中的资源也是计算图的一个重要功能,在一个计算图中,可以通过集合collection来管理不同类型的资源,如tf.GraphKeys.TRAINABLE_VARIABLES集合中包含了模型中的所有可学习变量。
2 TensorFlow的数据模型——张量
在TensorFlow程序中,所有的数据都通过张量的形式来表示。从功能的角度上看,张量可以被简单为多维数组,其中零阶张量表示标量;一阶张量为向量,也就是一维数组;n阶张量可以理解为一个n维数组。但张量在TensorFlow中的实现并不是直接采用数组的形式,它只是对TensorFlow中运算结果的引用。即在张量中并没有真正保存数字,它保存的是如何得到这些数字的计算过程。
一个张量中主要保存了三个属性:名字、维度和类型。
名字不仅是一个张量的唯一标识符,也给出了这个张量是如何被计算出来的;
维度这个属性描述了一个张量的维度信息;
每一个张量会有一个唯一的类型,TensorFlow会对参与计算的所有张量进行类型的检查,当发现类型不匹配时会报错。
因为使用默认类型有可能会导致潜在的类型不匹配问题,所以一般建议通过指定dtype来明确指出变量或者常量的类型。
张量的第一类用途是对中间计算结果的引用,通过张量来存储中间结果;第二类情况是当计算图构造完成之后,张量可以用来获取计算结果。
3 TensorFlow的运行模型——会话
计算图和会话是TensorFlow中关于如何组织数据和计算的概念,TensorFlow使用会话(session)来执行定义好的运算。会话拥有并管理TensorFlow程序运行时的所有资源,当所有计算完成之后需要关闭会话来帮助系统回收资源,否则就可能出现资源泄露的问题。
TensorFlow中使用会话的模式一般有两种:
第一种模式需要明确调用会话生成函数和关闭会话函数;
第二章模式通过Python的上下文管理器来使用会话,当上下文管理器退出时会自动释放所有资源。
在交互式环境下,通过设置默认会话的方式来获取张量的取值更加方便,所以TensorFlow提供了一种在交互式环境下直接构建默认会话的函数tf.InteractiveSession()。
4 使用TensorFlow实现一个简单的神经网络
使用神经网络解决分类问题可以分为以下4个步骤:
1) 提取问题中的实体的特征向量作为神经网络的输入;
2) 定义神经网络的结构,并定义如何从神经网络的输入得到输出,即神经网络的前向传播算法;
3) 通过训练数据来调整神经网络中参数的取值,即神经网络的训练过程;
4) 使用训练好的神经网络来预测未知的数据。
神经网络中的变量:
在TensorFlow中,变量的作用就是保存和更新神经网络中的参数。TensorFlow中的变量需要指定初始值,在神经网络模型中,一般使用随机数给TensorFlow中的变量初始化。除了使用随机数或常数进行初始化,TensorFlow也支持通过其他变量的初始值来初始化新的变量。一个变量的值在被使用之前,这个变量的初始化过程需要被明确地调用,因为虽然在变量定义时给出了变量初始化的方法,但是这个方法并没有被真正执行。我们可以通过sess.run(w.initializer)来逐个初始化变量,也可以通过tf.global_variables_initializer()函数来实现所有变量的初始化。
在TensorFlow中,变量的声明函数tf.Variable()是一个运算,这个运算的输出结果是一个张量。
如前所述,在TensorFlow中可以通过tf.trainable_variables函数得到所有需要优化的参数,TensorFlow中提供的神经网络优化算法会将GraphKeys.TRAINABLE_VARIABLES集合中的变量作为默认的优化对象。
维度和类型是变量最重要的两个属性,其中变量的类型是不可改变的,但变量的维度在程序运行时是有可能改变的,尽管这种用法很少见。
占位符placeholder:
TensorFlow提供了placeholder机制用于提供输入数据。placeholder相当于定义了一个位置,这个位置中的数据在程序运行时再指定。这样在程序中就不需要生成大量常量来提供输入数据,而只需要将数据通过placeholder传入TensorFlow计算图。在placeholder定义时,这个位置上的数据的类型是需要指定的,且类型不可更改。
在程序运行时,通过提供一个feed_dict来指定输入的取值,feed_dict是一个字典,在字典中需要给出每个用到的plcaeholder的取值。
下面将以一个完整的二分类程序说明以上三个概念:
#-*-coding:utf-8-*-
"""
@author:taoshouzheng
@time:2018/9/28 19:15
@email:tsz1216@sina.com
"""
import tensorflow as tf
# 导入Numpy包中的伪随机数生成器
from numpy.random import RandomState
# 首先定义计算图中所有的计算
# 定义训练数据BATCH_SIZE的大小
BATCH_SIZE = 8
# 定义神经网络的参数,注意这里变量的声明,变量的初始值为使用正态分布生成的随机数
w1 = tf.Variable(tf.random_normal([2, 3], stddev=1, seed=1)) # 随机数种子,保证结果可复现
w2 = tf.Variable(tf.random_normal([3, 1], stddev=1, seed=1))
# 定义占位符placeholder作为存放输入数据的地方
# 在shape的第一个维度上使用None可以方便使用不同的BATCH大小
# 注意定义占位符placeholder时,这个位置上的数据类型是需要指定的,placeholder的类型也是不可以改变的
x_ = tf.placeholder(tf.float32, shape=(None, 2), name='x_input')
y_ = tf.placeholder(tf.float32, shape=(None, 1), name='y_input') # 真实标签
# 定义神经网络的前向传播过程
# 注意这里构建的神经网络比较简单,没有激活函数和偏置
a = tf.matmul(x_, w1)
y = tf.matmul(a, w2)
# 定义损失函数和反向传播算法
y = tf.sigmoid(y) # 使用sigmoid函数将y转换为0~1之间的数值,转换后y代表预测为正例的概率,1-y代表为负例的概率
# 定义二分类的交叉熵损失函数
# tf.reduce_mean()函数:用于计算张量tensor沿着指定数轴(tensor的某一维度)上的平均值,主要用作降维或者计算tensor的平均值
# tf.log():定义某元素的自然对数
# tf.clip_by_value(A, min, max):输入一个张量A,把A中每一个元素的值都压缩在min和max之间,小于min的让它等于min,大于max的元素的值等于max
cross_entropy = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0)) + (1 - y_) *
tf.log(tf.clip_by_value(1 - y, 1e-10, 1.0)))
# 定义Adam优化器
train_step = tf.train.AdamOptimizer(0.001).minimize(cross_entropy)
# 通过随机数生成一个模拟数据集
rdm = RandomState(1)
dataset_size = 128 # 数据集规模
# RandomState创建一个给定形状的数组,数组中的值使用[0, 1)上的均匀分布填充
X = rdm.rand(dataset_size, 2)
# 定义规则来给出样本的标签,这里x1 + x2 < 1的样例被认为是正样本,其他的被认为是负样本
# 0表示负样本,1表示正样本
Y = [[int(x1 + x2 < 1)] for (x1, x2) in X]
# 打印样本,看看其格式
print("打印第一条样本")
print(X[0], Y[0])
# 执行计算
# 创建一个会话来运行TensorFlow程序
with tf.Session() as sess: # 使用上下文管理器创建会话
# 使用tf.global_variables_initializer()初始化所有变量
init_op = tf.global_variables_initializer()
# 初始化变量
sess.run(init_op)
# 打印初始化后的权值
print("训练之前的权值")
print(sess.run(w1))
print(sess.run(w2))
# 设定训练的轮数
STEPS = 5000
for i in range(STEPS):
# 每次选取BATCH_SIZE个样本进行训练
# %在Python中用于取模,返回除法的余数
start = (i * BATCH_SIZE) % dataset_size # 批次中第一个样本的索引
end = min(start + BATCH_SIZE, dataset_size) # 批次中最后一个样本的索引
# 通过选取的样本训练神经网络并更新参数c
# 注意feed_dict是一个字典,字典中的键是占位符placeholder的名字,值是占位符的取值
sess.run(train_step, feed_dict={x_: X[start: end], y_: Y[start: end]})
if i % 500 == 0:
# 每个500轮计算神经网络模型在所有数据上的交叉熵并输出
total_cross_entropy = sess.run(cross_entropy, feed_dict={x_: X, y_: Y})
print("在训练了 %d 轮之后,在所有数据上的交叉熵为:% g" % (i, total_cross_entropy))
# 打印训练完之后的权值用于比较
print("训练之后的权值")
print(sess.run(w1))
print(sess.run(w2))
运行结果截图如下:
这里可以看到经过训练后,神经网络模型在全部数据上的交叉熵损失逐渐变小,这是因为在训练过程中优化器优化了神经网络的权值,使得神经网络能够更好地拟合训练数据。
总结一下训练神经网络的三个步骤:
1) 定义神经网络的结构和前向传播的输出结果;
2) 定义损失函数、选择反向传播优化的算法;
3) 生成会话,并在训练数据上反复运行反向传播优化算法。
谢谢!