第 3 章 TensorFlow 入门

3.1 TensorFlow 计算模型——计算图

TensorFlow 是一个通过计算图的形式来表述计算的编程系统。TensorFlow 中的每一个计算都是计算图上的一个节点,而节点之间的边描述了计算之间的依赖关系。
TensorFlow 的程序一般可以分为两个阶段。在第一个阶段需要定义计算图中所有的计算。第二个阶段为执行计算。以下代码给出了计算定义阶段的样例:

import tensorflow as tf
a = tf.constant([1.0, 2.0], name="a")
b = tf.constant([2.0, 3.0], name="b")

在 TensorFlow 程序中,系统会自动维护一个默认的计算图,通过tf.get_default_graph函数可以获取当前默认的计算图。以下代码示意了如何获取默认计算图以及如何查看一个运算所属的计算图:
print(a.graph is tf.get_default_graph())


除了使用默认的计算图,TensorFlow 支持通过tf.Graph函数来生成新的计算图

import tensorflow as tf


g1 = tf.Graph()
with g1.as_default():
    v = tf.get_variable("v", initializer = tf.zeros_initializer()(shape = [1]))

g2 = tf.Graph()
with g2.as_default():
    v = tf.get_variable("v", initializer = tf.ones_initializer()(shape = [1]))

with tf.Session(graph = g1) as sess:
    tf.global_variables_initializer().run()
    with tf.variable_scope("", reuse = True):
        print(sess.run(tf.get_variable("v")))

with tf.Session(graph = g2) as sess:
    tf.global_variables_initializer().run()
    with tf.variable_scope("", reuse = True):
        print(sess.run(tf.get_variable("v")))

v的取值分别为0和1

以上代码产生了两个计算图,每个计算图中定义了一个名字为“v”的变量。在计算图 g1 和 g2 中,分别将 v 初始化为 0 和 1。可以看到在运行不同的计算图时,变量 v 的值也不一样。
TensorFlow 中的计算图不仅可以用来隔离张量和计算,还提供了管理张量和计算的机制。计算图可以通过 tf.Graph.device 函数来指定运行计算的设备。这为 TensorFlow 使用 GPU 提供了机制。以下程序可以将加法计算跑在 GPU 上:

import tensorflow as tf

a = tf.constant([1.0, 2.0], name="a")
b = tf.constant([2.0, 3.0], name="b")

g = tf.Graph()
with g.device('/gpu:0'):
    result = a + b

sess = tf.Session()
print(sess.run(result))


注意到警告信息 Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2,加入以下代码忽略此警告:

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

3.2 TensorFlow 数据模型——张量

在 TensorFlow 中,所有的数据都通过张量的形式来表示。从功能的角度上看,张量可以简单理解为多维数组。其中零阶张量表示标量(scalar),也就是一个数;第一阶张量为向量(vector),也就是一个一维数组;第 n 阶张量可以理解为一个 n 维数组。但张量在 TensorFlow 中的实现并不是直接采用数组的形式,它只是对 TensorFlow 中运算结果的引用。在张量中并没有真正保存数字。它保存的是如何得到这些数字的计算过程。

import tensorflow as tf
import os 

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

a = tf.constant([1.0, 2.0], name="a")
b = tf.constant([2.0, 3.0], name="b")

result = tf.add(a, b, name = "add")
print(result)


TensorFlow 计算的结果不是一个具体的数字,而是一个张量的结构。一个张量中主要保存了三个属性:名字(name)、维度(shape)和类型(type)。
张量的命名通过 node:src_output 的形式来给出。其中 node 为节点的名称,src_output 表示当前张量来自节点的第几个输出。比如上图中的 add:0 就说明 result 这个张量是计算节点 add 输出的第一个结果。
张量的维度描述了一个张量的维度信息,比如上图中 shape=(2,) 说明了张量 result 是一个长度为2的一维数组。
张量的第三个属性是类型,每一个张量有唯一的类型。TensorFlow 会对参与运算的所有张量进行类型的检查,当发现类型不匹配时会报错。
报错:类型不匹配

通过指定类型避免出错:a = tf.constant([1, 2], name="a", dtype=tf.float32)


TensorFlow 支持 14 种不同的类型,主要包括了实数(tf.float32、tf.float64)、整数(tf.int8、tf.int16、tf.int32、tf.int64、tf.uint8)、布尔型(tf.bool)和复数(tf.complex128)。
张量的第一类用途是对中间计算结果的引用。当一个计算包含很多中间结果时,使用张量可以大大提高代码的可读性。
张量的第二类用途是当计算图构造完成之后,张量可以通过会话来获得计算结果,也就是得到真实的数字。

3.3 TensorFlow 运行模型——会话

会话(session)拥有并管理 TensorFlow 程序运行时的所有资源。所有计算完成后需要关闭会话来帮助系统回收资源,否则可能出现资源泄漏。TensorFlow 中使用会话的模式一般有两种,第一种模式需要明确调用会话生成函数和关闭会话函数,这种模式的代码流程如下:

# 创建一个会话
sess = tf.Session()
# 使用该会话来得到运算结果
sess.run(...)
# 关闭会话,释放资源
sess.close()

当程序因为异常而退出时,关闭会话的函数可能不会被执行从而导致资源泄漏。为了解决异常退出时资源释放的问题,TensorFlow 可以通过 Python 的上下文管理器来使用会话:

with tf.Session() as sess:
    # 使用该会话来得到运算结果
    sess.run(...)
# 当上下文退出时会话关闭和资源释放自动完成

通过 Python 上下文管理机制,只要将所有的计算放在with的内部就可以解决因为异常退出时资源释放的问题和忘记调用 Session.close() 函数而产生的资源泄漏。
TensorFlow 不会自动生成默认的会话,而是需要手动指定。当默认的会话被指定之后可用通过tf.Tensor.eval函数来计算一个张量的取值,以下代码展示了通过设定默认会话计算张量的取值:

import tensorflow as tf
import os 

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

a = tf.constant([1.0, 2.0], name="a")
b = tf.constant([2.0, 3.0], name="b")
result = a + b

sess = tf.Session()
with sess.as_default():
    print(result.eval())


以下代码也可以完成相同的功能
print(sess.run(result))
print(result.eval(session=sess))
在交互式环境下,通过设置默认会话的方式来获取张量的取值更加方便。所以 TensorFlow 提供了在交互式环境下直接构建默认会话的函数 tf.InteractiveSession(),该函数会自动将生产的会话注册为默认会话。

import tensorflow as tf
import os 

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

a = tf.constant([1.0, 2.0], name="a")
b = tf.constant([2.0, 3.0], name="b")
result = a + b

sess = tf.InteractiveSession()
print(result.eval())
sess.close()

3.4 TensorFlow 实现神经网络

在 TensorFlow 中,变量(tf.Variable)的作用就是保存和更新神经网络中的参数。一般使用随机数给 TensorFlow 中的变量初始化,以下示例代码给出了声明一个 2×3 的矩阵变量的方法:
weights = tf.Variable(tf.random_normal([2, 3], stddev=2))
该矩阵中的元素是均值为 0,标准差为 2 的随机数。tf.random_normal 函数通过参数 mean 来指定平均值,默认为 0。通过满足正态分布的随机数来初始化神经网络中的参数是非常常用的方法。


TensorFlow 随机数生成函数

函数名称 随机数分布 主要参数
tf.random_normal 正态分布 平均值、标准差、取值类型
tf.truncated_normal 正态分布,如果随机值偏离平均值超过2个标准差,将重新随机 平均值、标准差、取值类型
tf.random_uniform 均匀分布 最小、最大取值,取值类型
tf.random_gamma Gamma分布 形状参数alpha、尺度参数beta、取值类型

TensorFlow 常数生成函数

函数名称 功能 样例
tf.zeros 产生全0的数组 tf.zeros([2, 3], tf.int32)
tf.ones 产生全1的数组 tf.ones([2, 3], tf.int32)
tf.fill 产生一个全部为给定数字的数组 tf.fill([2, 3], 9)
tf.constant 产生一个给定值的常量 tf.constant([1, 2, 3])

在神经网络中,偏置项(bias)通常会使用常数来设置初始值。以下代码生成了一个初始值全部为 0 且长度为 3 的变量:
biases = tf.Variable(tf.zeros([3]))
除了使用随机数或者常数, TensorFlow 也支持通过其他变量的初始值来初始化新的变量:

w2 = tf.Variable(weights.initialized_value())
w3 = tf.Variable(weights.initialized_value() * 2.0)

以下样例演示了如何通过变量实现神经网络的参数并实现前向传播的过程:

import tensorflow as tf
import os 

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

# 声明w1、w2两个变量,并通过seed参数设定随机种子
w1 = tf.Variable(tf.random_normal((2, 3), stddev=1, seed=1))
w2 = tf.Variable(tf.random_normal((3, 1), stddev=1, seed=1))

# 将输入的特征向量定义为常量
x = tf.constant([[0.7, 0.9]])

# 前向传播算法
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)

sess = tf.Session()

# 初始化w1、w2
sess.run(w1.initializer)
sess.run(w2.initializer)

print(sess.run(y))
sess.close()


虽然直接调用每个变量的初始化过程是可行的,但当变量数目增多或者变量之间存在依赖关系时,单个调用方案比较麻烦。TensorFlow 提供了 tf.global_variables_initializer() 函数实现初始化所有变量的过程。

init_op = tf.global_variables_initializer()
sess.run(init_op)

所有的变量都会被自动加入到 GraphKeys.VARIABLES 集合中。通过 tf.global_variables 函数可以拿到当前计算图上的所有变量,有助于持久化整个计算图的运行状态。当构建机器学习模型时,可以通过变量声明函数中的 trainable 参数来区分需要优化的参数(比如神经网络中的参数)和其他参数(比如迭代的轮数)。如果声明变量时参数 trainable 为 True,那么这个变量将会被加入到 GraphKeys.TRAINABLE_VARIABLES 集合。可以通过 tf.trainable_variables() 函数得到所有需要优化的参数。TensorFlow 中提供的神经网络优化算法会将GraphKeys.TRAINABLE_VARIABLES 集合中的变量作为默认的优化对象。


在神经网络优化算法中,最常用的方法是反向传播算法(backpropagation)。反向传播算法实现了一个迭代的过程。在每次迭代的开始,首先需要选取一小部分训练数据,这一小部分数据叫作一个 batch。该 batch 的样例通过前向传播算法得到神经网络模型的预测结果,计算预测结果与正确答案的差距。基于预测值与真实值之间的差距,反向传播算法会相应更新神经网络参数的取值,使得在这个 batch 上神经网络预测的结果与真实答案更接近。
通过 TensorFlow 实现反向传播算法的第一步是使用 TensorFlow 表达一个 batch 的数据:
x = tf.constant([[0.7, 0.9]])
如果每轮迭代选取的数据都通过常量来表示,那么计算图将会非常大,导致利用率很低。TensorFlow 提供了 placeholder 机制用于提供输入数据,以下代码给出了通过 placeholder 实现前向传播算法:

import tensorflow as tf
import os 

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

w1 = tf.Variable(tf.random_normal([2, 3], stddev=1, seed=1))
w2 = tf.Variable(tf.random_normal([3, 1], stddev=1, seed=1))

# 将输入的特征向量定义为常量
x = tf.placeholder(tf.float32, shape=(1, 2), name="input")

# 前向传播算法
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)

sess = tf.Session()

# 初始化
init_op = tf.global_variables_initializer()
sess.run(init_op)

print(sess.run(y, feed_dict={x: [[0.7, 0.9]]}))
sess.close()


计算前向传播结果时,需要提供一个 feed_dict 来指定 x 的取值。feed_dict 是一个字典(map),在字典中需要给出每个用到的 placeholder 的取值。
如果将输入的 1×2 矩阵改为 n×2 的矩阵,就可以得到 n 个样例的前向传播结果,以下代码示例:

import tensorflow as tf
import os 

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

w1 = tf.Variable(tf.random_normal([2, 3], stddev=1, seed=1))
w2 = tf.Variable(tf.random_normal([3, 1], stddev=1, seed=1))

# 将输入的特征向量定义为常量
x = tf.placeholder(tf.float32, shape=(3, 2), name="input")

# 前向传播算法
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)

sess = tf.Session()

# 初始化
init_op = tf.global_variables_initializer()
sess.run(init_op)

print(sess.run(y, feed_dict={x: [[0.7, 0.9], [0.1, 0.4], [0.5, 0.8]]}))
sess.close()

在得到一个 batch 的前向传播结果后,需要定义一个损失函数来刻画当前预测值与真实值之间的差距。然后通过反向传播算法来调整神经网络参数的取值使得差距可以被缩小。
以下是一个完整的在模拟数据集上训练神经网络解决二分类问题的样例程序:

import tensorflow as tf
from numpy.random import RandomState
import os 

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

# 定义训练数据batch的大小
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))

# 在shape的一个维度上使用None可以方便使用不同的batch大小
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)
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)))
train_step = tf.train.AdamOptimizer(0.001).minimize(cross_entropy)

# 通过随机数生成一个模拟数据集
rdm = RandomState(1)
dataset_size = 128
X = rdm.rand(dataset_size, 2)

# 定义规则来给出样本的标签
Y = [[int(x1+x2 < 1)] for (x1, x2) in X]

# 创建会话
with tf.Session() as sess:
    init_op = tf.global_variables_initializer()

    sess.run(init_op)

    print(sess.run(w1))
    print(sess.run(w2))

# 设定训练轮数
    STEPS = 5000
    for i in range(STEPS):
        # 每次选取batch_size个样本进行训练
        start = (i * batch_size) % dataset_size
        end = min(start+batch_size, dataset_size)

     # 通过选取的样本训练神经网络并更新参数
        sess.run(train_step, feed_dict={x: X[start: end], y_: Y[start: end]})

        if (i % 1000 == 0):
            # 每隔一段时间计算在所有数据上的交叉熵并输出
            total_cross_entropy = sess.run(cross_entropy, feed_dict={x: X, y_: Y})
            print("After %d training step(s), cross entropy on all data is %g" % (i, total_cross_entropy))

    print(sess.run(w1))
    print(sess.run(w2))

参考链接:
https://blog.csdn.net/wqqgo/article/details/75675323

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容