使人困惑的TensorFlow!

选自jacobbuckman.com,做者:Jacob Buckman,机器之心编译。node

虽然对于大多数人来讲 TensorFlow 的开发语言是 Python,但它并非一个标准的 Python 库。这个神经网络框架经过构建「计算图」来运行,对于不少新手来讲,在理解其逻辑时会遇到不少困难。本文中,来自谷歌大脑的工程师 Jacob Buckman 将试图帮你解决初遇 TensorFlow 时你会遇到的麻烦。

导论

这是什么?我是谁?python

我叫 Jacob,是 Google AI Resident 项目的研究学者。我是在 2017 年夏天加入该项目的,尽管已经拥有了丰富的编程经验,而且对机器学习的理解也很深入,但此前我从未使用过 TensorFlow。当时我以为凭个人能力应该很快就能上手。但让我没想到的是,学习曲线至关的陡峭,甚至在加入该项目几个月后,我还偶尔对如何使用 TensorFlow 代码来实现想法感到困惑。我把这篇博文看成瓶中信写给过去的本身:一篇我但愿在学习之初能被给予的入门介绍。我但愿这篇博文也能帮助到其余人。git


以往的教程缺乏了哪些内容?github

自 TensorFlow 发布的三年以来,其已然成为深度学习生态系统中的一块基石。然而对于初学者来讲,它可能并不直观,特别是与 PyTorch 或 DyNet 这样运行即定义的神经网络库相比。编程

市面上有许多 TensorFlow 的入门教程,包含从线性回归到 MNIST 分类和机器翻译的内容。这些具体实用的指南是使 TensorFlow 项目启动并运行的良好资源,同时能够做为相似项目的切入点。但对于有些应用开发人员而言,他们开发的应用并无好的教程,或对于那些想打破常规的人(在研究中很常见)而言,刚接触 TensorFlow 确定是让人沮丧的。数组

我试图经过这篇文章去填补这个空白。我没有专一于某个特定的任务,而是提出更通常的方法,并解析 TensorFlow 背后基础的抽象概念。掌握好这些概念以后,用 TensorFlow 进行深度学习就会变得直观易懂。浏览器


目标受众bash

本教程适用于那些在编程和机器学习方面有必定经验,并想要学习 TensorFlow 的人。例如:一位想在机器学习课程的最后一个项目中使用 TensorFlow 的计算机科学专业的学生;一位刚被分配到涉及深度学习项目的软件工程师;或是一位处于困惑中的新的 Google AI Resident 新手(向过去的 Jacob 大声打招呼)。若是你想进一步了解基础知识,请参阅如下资源:网络

咱们就开始吧!session

理解 TensorFlow

TensorFlow 不是一个标准的 Python 库

大多数 Python 库被编写为 Python 的天然扩展形式。当你导入一个库时,你获得的是一组变量、函数和类,他们扩展并补充了你的代码「工具箱」。当你使用它们时,你能预期到返回的结果是怎样的。在我看来,当谈及 TensorfFlow 时,应该把这种认知彻底抛弃。思考什么是 TensorFlow 及其如何与其余代码进行交互从根本上来讲就是错误的。

Python 和 TensorFlow 之间的关系能够类比 Javascript 和 HTML 之间的关系。Javascript 是一种全功能的编程语言,能够作各类美妙的事情。HTML 是用于表示某种类型的实用计算抽象(此处指可由 Web 浏览器呈现的内容)的框架。Javascript 在交互式网页中的做用是组装浏览器看到的 HTML 对象,而后在须要时经过将其更新为新的 HTML 来与其交互。

与 HTML 相似,TensorFlow 是用于表示某种类型的计算抽象(称为「计算图」)的框架。但咱们用 Python 操做 TensorFlow 时,咱们用 Pyhton 代码作的第一件事就是构建计算图。一旦完成,咱们作的第二件事就是与它进行交互(启动 TensorFlow 的「会话」)。但重要的是,要记住计算图不在变量内部;而是处在全局命名空间中。正如莎士比亚所说:「全部的 RAM 都是一个阶段,全部的变量都仅仅是指针」


第一个关键抽象:计算图

当你在浏览 TensorFlow 文档时,可能会发现对「图形」和「节点」的间接引用。若是你仔细阅读,你甚至可能已经发现了这个页面(www.tensorflow.org/programmers…),该页面涵盖了我将以更准确和技术化的方式去解释的内容。本节是一篇高级攻略,把握重要的直觉概念,同时忽略一些技术细节。

那么:什么是计算图?它本质上是一个全局数据结构:是一个有向图,用于捕获有关如何计算的指令。

让咱们来看看构建计算图的一个示例。在下图中,上半部分是咱们运行的代码及其输出,下半部分是生成的计算图。

import tensorflow as tf
复制代码


计算图:

可见,仅仅导入 TensorFlow 并不会给咱们生成一个有趣的计算图。而只是一个单独的,空白的全局变量。但当咱们调用一个 TensorFlow 操做时,会发生什么?


代码:

import tensorflow as tf
two_node = tf.constant(2)
print two_node
复制代码

输出:

Tensor("Const:0", shape=(), dtype=int32)
复制代码

计算图:

快看!咱们获得了一个节点。它包含常量 2。很惊讶吧,这来自于一个名为 tf.constant 的函数。当咱们打印这个变量时,咱们看到它返回一个 tf.Tensor 对象,它是一个指向咱们刚刚建立的节点的指针。为了强调这一点,如下是另一个示例:


代码:

import tensorflow as tf
two_node = tf.constant(2)
another_two_node = tf.constant(2)
two_node = tf.constant(2)
tf.constant(3)
复制代码


计算图:

每次咱们调用 tf.constant 时,咱们都会在图中建立一个新的节点。即便该节点的功能与现有节点相同,即便咱们将节点从新分配给同一个变量,或者即便咱们根本没有将其分配给一个变量,结果都是同样的。


代码:

import tensorflow as tf
two_node = tf.constant(2)
another_pointer_at_two_node = two_node
two_node = None
print two_node
print another_pointer_at_two_node
复制代码


输出:

None
Tensor("Const:0", shape=(), dtype=int32)
复制代码


计算图:

好啦,让咱们更进一步:

代码:

import tensorflow as tf
two_node = tf.constant(2)
three_node = tf.constant(3)
sum_node = two_node + three_node ## equivalent to tf.add(two_node, three_node)
复制代码


计算图:

如今咱们正谈论—这才是咱们真正想要的计算图!请注意,+ 操做在 TensorFlow 中过载,所以同时添加两个张量会在图中增长一个节点,尽管它表面上看起来不像是 TensorFlow 操做。

那好,因此 two_node 指向包含 2 的节点,three_node 指向包含 3 的节点,同时 sum_node 指向包含 ...+ 的节点?怎么回事?它不是应该包含 5 吗?

事实证实,并无。计算图只包含计算步骤;不包含结果。至少……如今尚未!


第二个关键抽象: 会话

若是错误地理解 TensorFlow 抽象概念也有个「疯狂三月」(NCAA 篮球锦标赛,大部分在三月进行),那么会话将成为每一年的一号种子选手。会话有着那样使人困惑的殊荣是由于其反直觉的命名却又广泛存在—几乎每一个 TensorFlow 呈现都至少一次明确地调用 tf.Session()。

会话的做用是处理内存分配和优化,使咱们可以实际执行由计算图指定的计算。你能够将计算图想象为咱们想要执行的计算的「模版」:它列出了全部步骤。为了使用计算图,咱们须要启动一个会话,它使咱们可以实际地完成任务;例如,遍历模版的全部节点来分配一堆用于存储计算输出的存储器。为了使用 TensorFlow 进行各类计算,你既须要计算图也须要会话。

会话包含一个指向全局图的指针,该指针经过指向全部节点的指针不断更新。这意味着在建立节点以前仍是以后建立会话都无所谓。

建立会话对象后,可使用 sess.run(node) 返回节点的值,而且 TensorFlow 将执行肯定该值所需的全部计算。


代码:

import tensorflow as tf
two_node = tf.constant(2)
three_node = tf.constant(3)
sum_node = two_node + three_node
sess = tf.Session()
print sess.run(sum_node)
复制代码

输出:

5
复制代码

计算图:

太好了!咱们也能够传递一个列表,sess.run([node1, node2, ...]),并让它返回多个输出:


代码:

import tensorflow as tf
two_node = tf.constant(2)
three_node = tf.constant(3)
sum_node = two_node + three_node
sess = tf.Session()
print sess.run([two_node, sum_node])
复制代码


输出:

[2, 5]
复制代码


计算图:

通常来讲,sess.run() 的调用每每是 TensorFlow 最大的瓶颈之一,所以调用它的次数越少越好。若是能够的话,在一个 sess.run() 的调用中返回多个项目,而不是进行多个调用。


占位符和 feed_dict

迄今为止,咱们所作的计算一直很乏味:没有机会得到输入,因此它们老是输出相同的东西。一个更有价值的应用可能涉及构建一个计算图,它接受输入,以某种(一致)方式处理它,并返回一个输出。

最直接的方法是使用占位符。占位符是一种用于接受外部输入的节点。


代码:

import tensorflow as tf
input_placeholder = tf.placeholder(tf.int32)
sess = tf.Session() 
print sess.run(input_placeholder)
复制代码


输出:

Traceback (most recent call last):
...
InvalidArgumentError (see above *for* traceback): You must feed a value *for* placeholder tensor 'Placeholder' *with* dtype int32
 [[Node: Placeholder = Placeholder[dtype=DT_INT32, shape=<unknown>, _device="/job:localhost/replica:0/task:0/device:CPU:0"]()]]
复制代码


计算图:

... 这是一个糟糕的例子,由于它引起了一个异常。占位符预计会被赋予一个值。但咱们没有提供一个值,因此 TensorFlow 崩溃了。

为了提供一个值,咱们使用 sess.run() 的 feed_dixt 属性。


代码:

import tensorflow as tf 
input_placeholder = tf.placeholder(tf.int32) 
sess = tf.Session() 
print sess.run(input_placeholder, feed_dict={input_placeholder: 2})
复制代码


输出:

2
复制代码


计算图:

这就好多了。注意传递给 feed_dict 的 dict 格式,其关键应该是与图中的占位符节点相对应的变量(如前所述,它实际上意味着指向图中占位符节点的指针)。相应的值是要分配给每一个占位符的数据元素——一般是标量或 Numpy 数组。


第三个关键抽象:计算路径

让咱们看看另外一个使用占位符的示例:

代码:

import tensorflow as tf
input_placeholder = tf.placeholder(tf.int32)
three_node = tf.constant(3)
sum_node = input_placeholder + three_node
sess = tf.Session()
print sess.run(three_node)
print sess.run(sum_node)
复制代码


输出:

3
Traceback (most recent call last):
...
InvalidArgumentError (see above for traceback): You must feed a value *for* placeholder tensor 'Placeholder_2' with dtype int32
 [[Node: Placeholder_2 = Placeholder[dtype=DT_INT32, shape=<unknown>, _device="/job:localhost/replica:0/task:0/device:CPU:0"]()]]
复制代码


计算图:

为何第二次调用 sess.run() 会失败?即便咱们没有评估 input_placeholder,为何仍会引起与 input_placeholder 相关的错误?答案在于最终的关键 TensorFlow 抽象:计算路径。幸运的是,这个抽象很是直观。

当咱们在依赖于图中其余节点的节点上调用 sess.run() 时,咱们也须要计算那些节点的值。若是这些节点具备依赖关系,那么咱们须要计算这些值(依此类推……),直到达到计算图的「顶端」,即节点没有父节点时。


sum_node 的计算路径:

全部三个节点都须要进行求值以计算 sum_node 的值。最重要的是,这包含了咱们未填充的占位符,并解释了异常!

如今来看 three_node 的计算路径:

根据图结构,咱们不须要计算全部节点才能评估咱们想要的节点!由于咱们在评估 three_node 时不须要评估 placehoolder_node,因此运行 sess.run(three_node) 不会引起异常。


TensorFlow 仅经过必需的节点自动进行计算这一事实是该框架的一个巨大优点。若是计算图很是大而且有许多没必要要的节点,那么它能够节省大量调用的运行时间。它容许咱们构建大型的「多用途」计算图,这些计算图使用单个共享的核心节点集合,并根据所采起的不一样计算路径去作不一样的事情。对于几乎全部应用而言,根据所采起的计算路径考虑 sess.run() 的调用是很重要的。


变量 & 反作用

至此,咱们已经看到两种类型的「无祖先」节点(no-ancestor node):每次运行都同样的 tf.constant 和每次运行都不同的 tf.placeholder。咱们经常要考虑第三种状况:一个一般在运行时保持值不变的节点也能够被更新为新值。

这时就须要引入变量。

变量对于使用 TensorFlow 进行深度学习是相当重要的,由于模型的参数就是变量。在训练期间,你但愿经过梯度降低在每一个步骤更新参数;但在评估时,你但愿保持参数不变,并将大量不一样的测试集输入模型。一般,模型全部可训练参数都是变量。

要建立变量,就须要使用 tf.get_variable()。tf.get_variable() 的前两个参数是必需的,其他参数是可选的。它们是 tf.get_variable(name,shape)。name 是一个惟一标识这个变量对象的字符串。它必须相对于全局图是惟一的,因此要明了你使用过的全部命名,确保没有重复。shape 是与张量形状对应的整数数组,它的语法很是直观:按顺序,每一个维度只有一个整数。例如,一个 3x8 矩阵形状是 [3, 8]。要建立一个标量,就须要使用形状为 [] 的空列表。


代码:

import tensorflow as tf
count_variable = tf.get_variable("count", [])
sess = tf.Session()
print sess.run(count_variable)
复制代码


输出:

Traceback (most recent call last):
...
tensorflow.python.framework.errors_impl.FailedPreconditionError: Attempting to use uninitialized value count
 [[Node: _retval_count_0_0 = _Retval[T=DT_FLOAT, index=0, _device="/job:localhost/replica:0/task:0/device:CPU:0"](count)]]
复制代码


计算图:

噫,另外一个异常。当首次建立变量节点时,它的值基本上为「null」,而且任何试图对它求值的操做都会引起这个异常。咱们只能在将值放入变量以后才能对其求值。主要有两种将值放入变量的方法:初始化器和 tf.assign()。咱们先看看 tf.assign():


代码:

import tensorflow as tf
count_variable = tf.get_variable("count", [])
zero_node = tf.constant(0.)
assign_node = tf.assign(count_variable, zero_node)
sess = tf.Session()
sess.run(assign_node)
print sess.run(count_variable)
复制代码


输出:

0
复制代码


计算图:

与咱们迄今为止见过的节点相比,tf.assign(target, value) 是具有一些独特属性:

  • 恒等运算。tf.assign(target, value) 不作任何有趣的运算,一般与 value 相等。
  • 反作用。当计算「流经」assign_node 时,反作用发生在图中的其余节点上。此时,反作用是用存储在 zero_node 中的值替换 count_variable 的值。
  • 非依赖边。即便 count_variable 节点和 assign_node 在图中是相连的,但它们彼此独立。这意味着计算任一节点时,计算不会经过边回流。然而,assign_node 依赖于 zero_node,它须要知道分配了什么。

「反作用」节点支撑着大部分 Tensorflow 深度学习工做流程,因此请确保本身真正理解了在该节点发生的事情。当咱们调用 sess.run(assign_node) 时,计算路径会经过 assign_node 和 zero_node。


计算图:

当计算流经图中的任何节点时,它还会执行由该节点控制的任何反作用,如图中绿色所示。因为 tf.assign 的特殊反作用,与 count_variable(以前为「null」)关联的内存如今被永久设置为 0。这意味着当咱们下一次调用 sess.run(count_variable) 时,不会引起任何异常。相反,咱们会获得 0 值。成功!

接下来,让咱们看看初始化器:

代码:

import tensorflow as tf
const_init_node = tf.constant_initializer(0.)
count_variable = tf.get_variable("count", [], initializer=const_init_node)
sess = tf.Session()
print sess.run([count_variable])
复制代码


输出:

Traceback (most recent call last):
...
tensorflow.python.framework.errors_impl.FailedPreconditionError: Attempting to use uninitialized value count
 [[Node: _retval_count_0_0 = _Retval[T=DT_FLOAT, index=0, _device="/job:localhost/replica:0/task:0/device:CPU:0"](count)]]
复制代码


计算图:

那好,这里发生了什么?为何初始化器不工做?

问题出如今会话和图之间的分离。咱们已将 get_variable 的 initializer 属性设置为指向 const_init_node,但它只是在图中的节点之间添加了一个新的链接。咱们尚未作任何解决异常根源的事:与变量节点(存储在会话中,而不是计算图中)相关联的内存仍然设置为「null」。咱们须要经过会话使 const_init_node 去更新变量。


代码:

import tensorflow as tf
const_init_node = tf.constant_initializer(0.)
count_variable = tf.get_variable("count", [], initializer=const_init_node)
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
print sess.run(count_variable)
复制代码


输出:

0
复制代码


计算图:

为此,咱们添加另外一个特殊的节点:init = tf.global_variables_initializer()。与 tf.assign() 相似,这是一个带有反作用的节点。与 tf.assign() 相反,实际上咱们不须要指定它的输入是什么!tf.global_variables_initializer() 将在其建立时查看全局图并自动将依赖关系添加到图中的每一个 tf.initializer。当咱们在以后使用 sess.run(init) 对它求值时,它会告诉每一个初始化程序执行变量初始化,并容许咱们运行 sess.run(count_variable) 而不出错。


变量共享

你可能会遇到带有变量共享的 Tensorflow 代码,其涉及建立做用域并设置「reuse = True」。我强烈建议不要在本身的代码中使用变量共享。若是你想在多个地方使用单个变量,只需以编程方式记录指向该变量节点的指针,并在须要时从新使用它。换言之,对于想要保存在内存中的每一个变量,你只须要调用一次 tf.get_variable()。


优化器

最后:进行真正的深度学习!若是你跟上个人节奏,那么其他概念对你来讲应该很是简单。

在深度学习中,典型的「内循环」训练以下:

1. 获取输入和 true_output

2. 根据输入和参数计算「推测」值

3. 根据推测与 true_output 之间的差别计算「损失」

4. 根据损失的梯度更新参数

让咱们把全部东西放在一个快速脚本里,解决简单的线性回归问题:


代码:

import tensorflow as tf
### build the graph## first set up the parameters
m = tf.get_variable("m", [], initializer=tf.constant_initializer(0.))
b = tf.get_variable("b", [], initializer=tf.constant_initializer(0.))
init = tf.global_variables_initializer()
## then set up the computations
input_placeholder = tf.placeholder(tf.float32)
output_placeholder = tf.placeholder(tf.float32)

x = input_placeholder
y = output_placeholder
y_guess = m * x + b

loss = tf.square(y - y_guess)
## finally, set up the optimizer and minimization node
optimizer = tf.train.GradientDescentOptimizer(1e-3)
train_op = optimizer.minimize(loss)
### start the session
sess = tf.Session()
sess.run(init)
### perform the training loop*import* random
## set up problem
true_m = random.random()
true_b = random.random()
*for* update_i *in* range(100000):
 ## (1) get the input and output
 input_data = random.random()
 output_data = true_m * input_data + true_b

 ## (2), (3), and (4) all take place within a single call to sess.run()!
 _loss, _ = sess.run([loss, train_op], feed_dict={input_placeholder: input_data, output_placeholder: output_data})
 *print* update_i, _loss
### finally, print out the values we learned for our two variables*print* "True parameters: m=%.4f, b=%.4f" % (true_m, true_b)*print* "Learned parameters: m=%.4f, b=%.4f" % tuple(sess.run([m, b]))
复制代码


输出:

0 2.32053831 0.57927422 1.552543 1.57332594 0.64356485 2.40612656 1.07462567 2.19987158 1.67751169 1.646242310 2.441034
...99990 2.9878322e-1299991 5.158629e-1199992 4.53646e-1199993 9.422685e-1299994 3.991829e-1199995 1.134115e-1199996 4.9467985e-1199997 1.3219648e-1199998 5.684342e-1499999 3.007017e-11*True* parameters: m=0.3519, b=0.3242
Learned parameters: m=0.3519, b=0.3242
复制代码

就像你看到的同样,损失基本上变为零,而且咱们对真实参数进行了很好的估计。我但愿你只对代码中的如下部分感到陌生:

## finally, set up the optimizer and minimization node
optimizer = tf.train.GradientDescentOptimizer(1e-3)
train_op = optimizer.minimize(loss)
复制代码

可是,既然你对 Tensorflow 的基本概念有了很好的理解,这段代码就很容易解释!第一行,optimizer = tf.train.GradientDescentOptimizer(1e-3) 不会向计算图中添加节点。它只是建立一个包含有用的帮助函数的 Python 对象。第二行,train_op = optimizer.minimize(loss) 将一个节点添加到图中,并将一个指针存储在变量 train_op 中。train_op 节点没有输出,可是有一个十分复杂的反作用:

train_op 回溯输入和损失的计算路径,寻找变量节点。对于它找到的每一个变量节点,计算该变量对于损失的梯度。而后计算该变量的新值:当前值减去梯度乘以学习率的积。最后,它执行赋值操做更新变量的值。

所以基本上,当咱们调用 sess.run(train_op) 时,它对咱们的全部变量作了一个梯度降低的步骤。固然,咱们也须要使用 feed_dict 填充输入和输出占位符,而且咱们还但愿打印损失的值,由于这样方便调试。


用 tf.Print 调试

当你用 Tensorflow 开始作更复杂的事情时,你须要进行调试。通常来讲,检查计算图中发生了什么是至关困难的。由于你永远没法访问你想打印的值—它们被锁定在 sess.run() 的调用中,因此你不能使用常规的 Python 打印语句。具体来讲,假设你是想检查一个计算的中间值。在调用 sess.run() 以前,中间值还不存在。可是,当你调用的 sess.run() 返回时,中间值又不见了!

让咱们看一个简单的示例。


代码:

import tensorflow as tf
two_node = tf.constant(2)
three_node = tf.constant(3)
sum_node = two_node + three_node
sess = tf.Session()
print sess.run(sum_node)
复制代码


输出:

5
复制代码

这让咱们看到了答案是 5。可是,若是咱们想要检查中间值,two_node 和 three_node,怎么办?检查中间值的一个方法是向 sess.run() 中添加一个返回参数,该参数指向要检查的每一个中间节点,而后在返回后,打印它的值。


代码:

import tensorflow as tf
two_node = tf.constant(2)
three_node = tf.constant(3)
sum_node = two_node + three_node
sess = tf.Session()
answer, inspection = sess.run([sum_node, [two_node, three_node]])
print inspection
print answer
复制代码


输出:

[2, 3]5
复制代码

这一般是有用的,但当代码变得愈来愈复杂时,这可能有点棘手。一个更方便的方法是使用 tf.Print 语句。使人困惑的是,tf.Print 其实是一种具备输出和反作用的 Tensorflow 节点!它有两个必需参数:要复制的节点和要打印的内容列表。「要复制的节点」能够是图中的任何节点;tf.Print 是一个与「要复制的节点」相关的恒等操做,意味着输出的是输入的副本。可是,它的反作用是打印出「打印列表」里的全部当前值。


代码:

import tensorflow as tf
two_node = tf.constant(2)
three_node = tf.constant(3)
sum_node = two_node + three_node
print_sum_node = tf.Print(sum_node, [two_node, three_node])
sess = tf.Session()
print sess.run(print_sum_node)
复制代码


输出:

[2][3]5
复制代码


计算图:

有关 tf.Print 一个重要且有点微妙的点:打印是一个反作用。像全部其余反作用同样,只要在计算流经 tf.Print 节点时才会进行打印。若是 tf.Print 节点不在计算路径上,则不会打印任何内容。特别的是,即便 tf.Print 节点正在复制的原始节点位于计算路径上,但 tf.Print 节点自己可能不在。请注意这个问题!当这种状况发生时(总会发生的),若是你没有明确地找到问题所在,它会让你感到十分沮丧。通常来讲,最好在建立要复制的节点后,当即建立你的 tf.Print 节点。


代码:

import tensorflow as tf
two_node = tf.constant(2)
three_node = tf.constant(3)
sum_node = two_node + three_node### this new copy of two_node is not on the computation path, so nothing prints!
print_two_node = tf.Print(two_node, [two_node, three_node, sum_node])
sess = tf.Session() 
print sess.run(sum_node)
复制代码


输出:

5
复制代码


计算图:

这里有一个很好的资源(wookayin.github.io/tensorflow-…),它提供了其余一些实用的调试建议。


结论

但愿这篇博文能够帮助你更好地理解什么是 Tensorflow,它是如何工做的以及怎么使用它。总而言之,本文介绍的概念对全部 Tensorflow 项目都很重要,但只是停留在表面。在你探索 Tensorflow 的旅程中,你可能会遇到其余各类你须要的有趣概念:条件、迭代、分布式 Tensorflow、变量做用域、保存和加载模型、多图、多会话和多核、数据加载器队列等等。我将在将来的博文中讨论这些主题。但若是你使用官方文档、一些代码示例和一点深度学习的魔力来巩固你在本文学到的思想,我相信你必定能够弄明白 Tensorflow!

原文连接:jacobbuckman.com/post/tensor…

相关文章
相关标签/搜索