TensorFlow-学习指南-一-
TensorFlow 学习指南(一)
译者:飞龙
一、基础
变量
TensorFlow 是一种表示计算的方式,直到请求时才实际执行。 从这个意义上讲,它是一种延迟计算形式,它能够极大改善代码的运行:
- 更快地计算复杂变量
- 跨多个系统的分布式计算,包括 GPU。
- 减少了某些计算中的冗余
我们来看看实际情况。 首先,一个非常基本的 python 脚本:
x = 35
y = x + 5
print(y)
这个脚本基本上只是“创建一个值为35
的变量x
,将新变量y
的值设置为它加上5
,当前为40
,并将其打印出来”。 运行此程序时将打印出值40
。 如果你不熟悉 python,请创建一个名为basic_script.py
的新文本文件,并将该代码复制到该文件中。将其保存在你的计算机上并运行它:
python basic_script.py
请注意,路径(即basic_script.py
)必须指向该文件,因此如果它位于Code
文件夹中,则使用:
python Code/basic_script.py
此外,请确保已激活 Anaconda 虚拟环境。 在 Linux 上,这将使你的提示符看起来像:
(tensorenv)username@computername:~$
如果起作用,让我们将其转换为 TensorFlow 等价形式。
import tensorflow as tf
x = tf.constant(35, name='x')
y = tf.Variable(x + 5, name='y')
print(y)
运行之后,你会得到一个非常有趣的输出,类似于<tensorflow.python.ops.variables.Variable object at 0x7f074bfd9ef0>
。 这显然不是40
的值。
原因在于,我们的程序实际上与前一个程序完全不同。 这里的代码执行以下操作:
- 导入
tensorflow
模块并将其命名为tf
- 创建一个名为
x
的常量值,并为其赋值35
- 创建一个名为
y
的变量,并将其定义为等式x + 5
- 打印
y
的等式对象
微妙的区别是,y
没有像我们之前的程序那样,给出x + 5
的当前值”。 相反,它实际上是一个等式,意思是“当计算这个变量时,取x
的值(就像那样)并将它加上5
”。 y
值的计算在上述程序中从未实际执行。
我们来解决这个问题:
import tensorflow as tf
x = tf.constant(35, name='x')
y = tf.Variable(x + 5, name='y')
model = tf.global_variables_initializer()
with tf.Session() as session:
session.run(model)
print(session.run(y))
我们删除了print(y)
语句,而是创建了一个会话,并实际计算了y
的值。这里有相当多的样板,但它的工作原理如下:
- 导入
tensorflow
模块并将其命名为tf
- 创建一个名为
x
的常量值,并为其赋值35
- 创建一个名为
y
的变量,并将其定义为等式x + 5
- 使用
tf.global_variables_initializer()
初始化变量(我们将在此详细介绍) - 创建用于计算值的会话
- 运行第四步中创建的模型
- 仅运行变量
y
并打印出其当前值
上面的第四步是一些魔术发生的地方。在此步骤中,将创建变量之间的依赖关系的图。在这种情况下,变量y
取决于变量x
,并且通过向其添加5
来转换它的值。请记住,直到第七步才计算该值,在此之前,仅计算等式和关系。
1)常量也可以是数组。预测此代码将执行的操作,然后运行它来确认:
import tensorflow as tf
x = tf.constant([35, 40, 45], name='x')
y = tf.Variable(x + 5, name='y')
model = tf.global_variables_initializer()
with tf.Session() as session:
session.run(model)
print(session.run(y))
生成包含 10,000 个随机数的 NumPy 数组(称为x
),并创建一个存储等式的变量。
你可以使用以下代码生成 NumPy 数组:
import numpy as np
data = np.random.randint(1000, size=10000)
然后可以使用data
变量代替上面问题 1 中的列表。 作为一般规则,NumPy 应该用于更大的列表/数字数组,因为它具有比列表更高的内存效率和更快的计算速度。 它还提供了大量的函数(例如计算均值),通常不可用于列表。
3)你还可以在循环更新的变量,稍后我们将这些变量用于机器学习。 看看这段代码,预测它会做什么(然后运行它来检查):
import tensorflow as tf
x = tf.Variable(0, name='x')
model = tf.global_variables_initializer()
with tf.Session() as session:
session.run(model)
for i in range(5):
x = x + 1
print(session.run(x))
4)使用上面(2)和(3)中的代码,创建一个程序,计算以下代码行的“滑动”平均值:np.random.randint(1000)
。 换句话说,保持循环,并在每个循环中,调用np.random.randint(1000)
一次,并将当前平均值存储在在每个循环中不断更新变量中。
5)使用 TensorBoard 可视化其中一些示例的图。 要运行 TensorBoard,请使用以下命令:tensorboard --logdir=path/to/log-directory
。
import tensorflow as tf
x = tf.constant(35, name='x')
print(x)
y = tf.Variable(x + 5, name='y')
with tf.Session() as session:
merged = tf.summary.merge_all()
writer = tf.summary.FileWriter("/tmp/basic", session.graph)
model = tf.global_variables_initializer()
session.run(model)
print(session.run(y))
要了解 Tensorboard 的更多信息,请访问我们的可视化课程。
数组
在本教程中,我们将处理图像,以便可视化数组的更改。 数组是强大的结构,我们在前面的教程中简要介绍了它。 生成有趣的数组可能很困难,但图像提供了很好的选择。
首先,下载此图像到你的计算机(右键单击,并寻找选项“图片另存为”)。
此图片来自维基共享的用户 Uoaei1。
要处理图像,我们需要matplotlib
。 我们还需要pillow
库,它会覆盖已弃用的 PIL 库来处理图像。 你可以使用 Anaconda 的安装方法在你的环境中安装它们:
conda install matplotlib pillow
要加载图像,我们使用matplotlib
的图像模块:
import matplotlib.image as mpimg
import os
# 首先加载图像
dir_path = os.path.dirname(os.path.realpath(__file__))
filename = dir_path + "/MarshOrchid.jpg"
# 加载图像
image = mpimg.imread(filename)
# 打印它的形状
print(image.shape)
上面的代码将图像作为 NumPy 数组读入,并打印出大小。 请注意,文件名必须是下载的图像文件的完整路径(绝对路径或相对路径)。
你会看到输出,即(5528, 3685, 3)
。 这意味着图像高 5528 像素,宽 3685 像素,3 种颜色“深”。
你可以使用pyplot
查看当前图像,如下所示:
import matplotlib.pyplot as plt
plt.imshow(image)
plt.show()
现在我们有了图像,让我们使用 TensorFlow 对它进行一些更改。
几何操作
我们将要执行的第一个转换是转置,将图像逆时针旋转 90 度。 完整的程序如下,其中大部分是你见过的。
import tensorflow as tf
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
import os
# 再次加载图像
dir_path = os.path.dirname(os.path.realpath(__file__))
filename = dir_path + "/MarshOrchid.jpg"
image = mpimg.imread(filename)
# 创建 TF 变量
x = tf.Variable(image, name='x')
model = tf.global_variables_initializer()
with tf.Session() as session:
x = tf.transpose(x, perm=[1, 0, 2])
session.run(model)
result = session.run(x)
plt.imshow(result)
plt.show()
转置操作的结果:
新东西是这一行:
x = tf.transpose(x, perm=[1, 0, 2])
该行使用 TensorFlow 的transpose
方法,使用perm
参数交换轴 0 和 1(轴 2 保持原样)。
我们将要做的下一个操作是(左右)翻转,将像素从一侧交换到另一侧。 TensorFlow 有一个称为reverse_sequence
的方法,但签名有点奇怪。 这是文档所说的内容(来自该页面):
tf.reverse_sequence( input, seq_lengths, seq_axis=None, batch_axis=None, name=None, seq_dim=None, batch_dim=None )
反转可变长度切片。
这个操作首先沿着维度
batch_axis
对input
却偏,并且对于每个切片i
,沿着维度seq_axis
反转第一个seq_lengths [i]
元素。
seq_lengths
的元素必须满足seq_lengths [i] <= input.dims [seq_dim]
,而seq_lengths
必须是长度为input.dims [batch_dim]
的向量。然后,输入切片
i
给出了沿维度batch_axis
的输出切片i
,其中第一个seq_lengths [i]
切片沿着维度seq_axis
被反转。
对于这个函数,最好将其视为:
- 根据
batch_dim
迭代数组。 设置batch_dim = 0
意味着我们遍历行(从上到下)。 - 对于迭代中的每个项目
- 对第二维切片,用
seq_dim
表示。 设置seq_dim = 1
意味着我们遍历列(从左到右)。 - 迭代中第
n
项的切片由seq_lengths
中的第n
项表示
- 对第二维切片,用
让我们实际看看它:
import numpy as np
import tensorflow as tf
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
import os
# First, load the image again
dir_path = os.path.dirname(os.path.realpath(__file__))
filename = dir_path + "/MarshOrchid.jpg"
image = mpimg.imread(filename)
height, width, depth = image.shape
# Create a TensorFlow Variable
x = tf.Variable(image, name='x')
model = tf.global_variables_initializer()
with tf.Session() as session:
x = tf.reverse_sequence(x, [width] * height, 1, batch_dim=0)
session.run(model)
result = session.run(x)
print(result.shape)
plt.imshow(result)
plt.show()
新东西是这一行:
x = tf.reverse_sequence(x, np.ones((height,)) * width, 1, batch_dim=0)
它从上到下(沿着它的高度)迭代图像,并从左到右(沿着它的宽度)切片。 从这里开始,它选取大小为width
的切片,其中width
是图像的宽度。
译者注:
还有两个函数用于实现切片操作。一个是
tf.reverse
,另一个是张量的下标和切片运算符(和 NumPy 用法一样)。
代码np.ones((height,)) * width
创建一个填充值width
的 NumPy 数组。 这不是很有效! 不幸的是,在编写本文时,似乎此函数不允许你仅指定单个值。
“翻转”操作的结果:
1)将转置与翻转代码组合来顺时针旋转。
2)目前,翻转代码(使用reverse_sequence
)需要预先计算宽度。 查看tf.shape
函数的文档,并使用它在会话中计算x
变量的宽度。
3)执行“翻转”,从上到下翻转图像。
4)计算“镜像”,复制图像的前半部分,(左右)翻转然后复制到后半部分。
占位符
到目前为止,我们已经使用Variables
来管理我们的数据,但是有一个更基本的结构,即占位符。 占位符只是一个变量,我们将在以后向它分配数据。 它允许我们创建我们的操作,并构建我们的计算图,而不需要数据。 在 TensorFlow 术语中,我们随后通过这些占位符,将数据提供给图。
import tensorflow as tf
x = tf.placeholder("float", None)
y = x * 2
with tf.Session() as session:
result = session.run(y, feed_dict={x: [1, 2, 3]})
print(result)
这个例子与我们之前的例子略有不同,让我们分解它。
首先,我们正常导入tensorflow
。然后我们创建一个名为x
的placeholder
,即我们稍后将存储值的内存中的位置。
然后,我们创建一个Tensor
,它是x
乘以 2 的运算。注意我们还没有为x
定义任何初始值。
我们现在定义了一个操作(y
),现在可以在会话中运行它。我们创建一个会话对象,然后只运行y
变量。请注意,这意味着,如果我们定义了更大的操作图,我们只能运行图的一小部分。这个子图求值实际上是 TensorFlow 的一个卖点,而且许多其他类似的东西都没有。
运行y
需要了解x
的值。我们在feed_dict
参数中定义这些来运行。我们在这里声明x
的值是[1,2,3]
。我们运行y
,给了我们结果[2,4,6]
。
占位符不需要静态大小。让我们更新我们的程序,让x
可以接受任何长度。将x
的定义更改为:
x = tf.placeholder("float", None)
现在,当我们在feed_dict
中定义x
的值时,我们可以有任意维度的值。 代码应该仍然有效,并给出相同的答案,但现在它也可以处理feed_dict
中的任意维度的值。
占位符也可以有多个维度,允许存储数组。 在下面的示例中,我们创建一个 3 乘 2 的矩阵,并在其中存储一些数字。 然后,我们使用与以前相同的操作,来逐元素加倍数字。
import tensorflow as tf
x = tf.placeholder("float", [None, 3])
y = x * 2
with tf.Session() as session:
x_data = [[1, 2, 3],
[4, 5, 6],]
result = session.run(y, feed_dict={x: x_data})
print(result)
占位符的第一个维度是None
,这意味着我们可以有任意数量的行。 第二个维度固定为 3,这意味着每行需要有三列数据。
我们可以扩展它来接受任意数量的None
维度。 在此示例中,我们加载来自上一课的图像,然后创建一个存储该图像切片的占位符。 切片是图像的 2D 片段,但每个“像素”具有三个分量(红色,绿色,蓝色)。 因此,对于前两个维度,我们需要None
,但是对于最后一个维度,需要 3(或None
也能用)。 然后,我们使用 TensorFlow 的切片方法从图像中取出一个子片段来操作。
import tensorflow as tf
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
import os
# First, load the image again
dir_path = os.path.dirname(os.path.realpath(__file__))
filename = dir_path + "/MarshOrchid.jpg"
raw_image_data = mpimg.imread(filename)
image = tf.placeholder("uint8", [None, None, 3])
slice = tf.slice(image, [1000, 0, 0], [3000, -1, -1])
with tf.Session() as session:
result = session.run(slice, feed_dict={image: raw_image_data})
print(result.shape)
plt.imshow(result)
plt.show()
译者注:使用下标和切片运算符也可以实现切片。
结果是图像的子片段:
1)在官方文档中查看 TensorFlow 中的其他数组函数。
2)将图像分成四个“角”,然后再将它拼在一起。
3)将图像转换为灰度。 一种方法是只采用一个颜色通道并显示。 另一种方法是将三个通道的平均值作为灰色。
交互式会话
现在我们有了一些例子,让我们更仔细地看看发生了什么。
正如我们之前已经确定的那样,TensorFlow 允许我们创建操作和变量图。这些变量称为张量,表示数据,无论是单个数字,字符串,矩阵还是其他内容。张量通过操作来组合,整个过程以图来建模。
首先,确保激活了tensorenv
虚拟环境,一旦激活,请输入conda install jupyter
来安装jupter books
。
然后,运行jupyter notebook
以启动 Jupyter Notebook(以前称为 IPython Notebook)的浏览器会话。 (如果你的浏览器没有打开,请打开它并在浏览器的地址栏中输入localhost:8888
。)
单击New
(新建),然后单击Notebooks
(笔记本)下的Python 3
(Python 3)。这将启动一个新的浏览器选项卡。通过单击顶部的Untitled
(无标题)为该笔记本命名,并为其命名(我使用Interactive TensorFlow
)。
如果你以前从未使用过 Jupyter 笔记本(或 IPython 笔记本),请查看此站点来获得简介。
接下来,和以前一样,让我们创建一个基本的 TensorFlow 程序。 一个主要的变化是使用InteractiveSession
,它允许我们运行变量,而不需要经常引用会话对象(减少输入!)。 下面的代码块分为不同的单元格。 如果你看到代码中断,则需要先运行上一个单元格。 此外,如果你不自信,请确保在运行之前将给定块中的所有代码键入单元格。
import tensorflow as tf
session = tf.InteractiveSession()
x = tf.constant(list(range(10)))
在这段代码中,我们创建了一个InteractiveSession
,然后定义一个常量值,就像一个占位符,但具有设置的值(不会改变)。 在下一个单元格中,我们可以求解此常量并打印结果。
print(x.eval())
下面我们关闭打开的会话。
session.close()
关闭会话非常重要,并且很容易忘记。 出于这个原因,我们在之前的教程中使用with
关键字来处理这个问题。 当with
块完成执行时,会话将被关闭(如果发生错误也会发生这种情况 - 会话仍然关闭)。
现在让我们来看更大的例子。 在这个例子中,我们将使用一个非常大的矩阵并对其进行计算,跟踪何时使用内存。 首先,让我们看看我们的 Python 会话当前使用了多少内存:
import resource
print("{} Kb".format(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss))
在我的系统上,运行上面的代码之后,使用了 78496 千字节。 现在,创建一个新会话,并定义两个矩阵:
import numpy as np
session = tf.InteractiveSession()
X = tf.constant(np.eye(10000))
Y = tf.constant(np.random.randn(10000, 300))
让我们再看一下我们的内存使用情况:
print("{} Kb".format(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss))
在我的系统上,内存使用率跃升至 885,220 Kb - 那些矩阵很大!
现在,让我们使用matmul
将这些矩阵相乘:
Z = tf.matmul(X, Y)
如果我们现在检查我们的内存使用情况,我们发现没有使用更多的内存 - 没有实际的Z
的计算。 只有当我们求解操作时,我们才真正计算。 对于交互式会话,你可以使用Z.eval()
,而不是运行session.run(Z)
。 请注意,你不能总是依赖.eval()
,因为这是使用“默认”会话的快捷方式,不一定是你要使用的会话。
如果你的计算机比较低级(例如,ram 低于 3Gb),那么不要运行此代码 - 相信我!
Z.eval()
你的计算机会考虑很长一段时间,因为现在它才实际执行这些矩阵相乘。 之后检查内存使用情况会发现此计算已经发生,因为它现在使用了接近 3Gb!
print("{} Kb".format(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss))
别忘了关闭你的会话!
session.close()
注意:我建议使用新的 Jupyter Notebook,因为上面的示例代码可能会被意外再次执行,可能导致计算机崩溃!
1)创建一个整数值的大矩阵(至少 10,000,000)(例如,使用 NumPy 的randint
函数)。 创建矩阵后检查内存使用情况。 然后,使用 TensorFlow 的to_float
函数将矩阵转换为浮点值。 再次检查内存使用情况,看到内存使用量增加超过两倍。 “加倍”是由创建矩阵的副本引起的,但是“额外增加”的原因是什么? 执行此实验后,你可以使用此代码显示图像。
from PIL import Image
from io import BytesIO
# 从字符串读取数据
im = Image.open(BytesIO(result))
im
提示:确保在每一步之后仔细测量内存使用情况,因为只是导入 TensorFlow 就会使用相当多的内存。
2)使用 TensorFlow 的图像函数将上一个教程中的图像(或其他图像)转换为 JPEG 并记录内存使用情况。
可视化
在本课中,我们将介绍如何使用 TensorBoard 创建和可视化图。 我们在第一课变量中简要地浏览了 TensorBoard
那么什么是 TensorBoard 以及我们为什么要使用它呢?
TensorBoard 是一套 Web 应用程序,用于检查和理解你的 TensorFlow 运行和图。 TensorBoard 目前支持五种可视化:标量,图像,音频,直方图和图。 你将在 TensorFlow 中的计算用于训练大型深度神经网络,可能相当复杂且令人困惑,TensorBoard 将使你更容易理解,调试和优化 TensorFlow 程序。
要实际查看 TensorBoard,请单击此处。
这就是 TensorBoard 图的样子:
基本的脚本
下面我们有了构建 TensorBoard 图的基本脚本。 现在,如果你在 python 解释器中运行它,会返回 63。
import tensorflow as tf
a = tf.add(1, 2,)
b = tf.multiply(a, 3)
c = tf.add(4, 5,)
d = tf.multiply(c, 6,)
e = tf.multiply(4, 5,)
f = tf.div(c, 6,)
g = tf.add(b, d)
h = tf.multiply(g, f)
with tf.Session() as sess:
print(sess.run(h))
现在我们在代码末尾添加一个SummaryWriter
,这将在给定目录中创建一个文件夹,其中包含 TensorBoard 用于构建图的信息。
with tf.Session() as sess:
writer = tf.summary.FileWriter("output", sess.graph)
print(sess.run(h))
writer.close()
如果你现在运行 TensorBoard,使用tensorboard --logdir=path/to/logs/directory
,你会看到在你给定的目录中,你得到一个名为output
的文件夹。 如果你在终端中访问 IP 地址,它将带你到 TensorBoard,然后如果你点击图,你将看到你的图。
在这一点上,图遍布各处,并且相当难以阅读。 因此,请命名一些部分来其更更加可读。
添加名称
在下面的代码中,我们只添加了parameter
几次。name=[something]
。 这个parameter
将接受所选区域并在图形上为其命名。
a = tf.add(1, 2, name="Add_these_numbers")
b = tf.multiply(a, 3)
c = tf.add(4, 5, name="And_These_ones")
d = tf.multiply(c, 6, name="Multiply_these_numbers")
e = tf.multiply(4, 5, name="B_add")
f = tf.div(c, 6, name="B_mul")
g = tf.add(b, d)
h = tf.multiply(g, f)
现在,如果你重新运行 python 文件,然后再次运行tensorboard --logdir=path/to/logs/directory
,你现在将看到,在你命名的特定部分上,你的图有了一些名称。 然而,它仍然非常混乱,如果这是一个巨大的神经网络,它几乎是不可读的。
创建作用域
如果我们通过键入tf.name_scope("MyOperationGroup"):
给图命名:并使用with tf.name_scope("Scope_A"):
给图这样的作用域,当你重新运行你的 TensorBoard 时,你会看到一些非常不同的东西。 图现在更容易阅读,你可以看到它都在图的标题下,这里是MyOperationGroup
,然后你有你的作用域A
和B
,其中有操作。
# 这里我们定义图的名称,作用域 A,B 和 C。
with tf.name_scope("MyOperationGroup"):
with tf.name_scope("Scope_A"):
a = tf.add(1, 2, name="Add_these_numbers")
b = tf.multiply(a, 3)
with tf.name_scope("Scope_B"):
c = tf.add(4, 5, name="And_These_ones")
d = tf.multiply(c, 6, name="Multiply_these_numbers")
with tf.name_scope("Scope_C"):
e = tf.multiply(4, 5, name="B_add")
f = tf.div(c, 6, name="B_mul")
g = tf.add(b, d)
h = tf.multiply(g, f)
如你所见,图现在更容易阅读。
TensorBoard 具有广泛的功能,其中一些我们将在未来的课程中介绍。 如果你想深入了解,请先观看 2017 年 TensorFlow 开发者大会的视频。
在本课中,我们研究了:
- TensorBoard 图的基本布局
- 添加摘要编写器来构建 TensorBoard
- 将名称添加到 TensorBoard 图
- 将名称和作用域添加到 TensorBoard
有一个很棒的第三方工具叫做 TensorDebugger(TDB),TBD 就像它所谓的调试器一样。 但是与 TensorBoard 中内置的标准调试器不同,TBD 直接与 TensorFlow 图的执行交互,并允许一次执行一个节点。 由于标准 TensorBoard 调试器不能在运行 TensorFlow 图时同时使用,因此必须先写日志文件。
- 从这里安装 TBD 并阅读材料(试试 Demo!)。
- 将 TBD 与此梯度下降代码一起使用,绘制一个图表,通过结果显示调试器的工作,并打印预测模型。 (注意:这仅仅与 2.7 兼容)
import tensorflow as tf
import numpy as np
# x 和 y 是我们的训练数据的占位符
x = tf.placeholder("float")
y = tf.placeholder("float")
# w 是存储我们的值的变量。 它使用“猜测”来初始化
# w[0] 是我们方程中的“a”,w[1] 是“b”
w = tf.Variable([1.0, 2.0], name="w")
# 我们的模型是 y = a*x + b
y_model = tf.multiply(x, w[0]) + w[1]
# 我们的误差定义为差异的平方
error = tf.square(y - y_model)
# GradientDescentOptimizer 完成繁重的工作
train_op = tf.train.GradientDescentOptimizer(0.01).minimize(error)
# TensorFlow 常规 - 初始化值,创建会话并运行模型
model = tf.global_variables_initializer()
with tf.Session() as session:
session.run(model)
for i in range(1000):
x_value = np.random.rand()
y_value = x_value * 2 + 6
session.run(train_op, feed_dict={x: x_value, y: y_value})
w_value = session.run(w)
print("Predicted model: {a:.3f}x + {b:.3f}".format(a=w_value[0], b=w_value[1]))
这些特殊图标用于常量和摘要节点。
读取文件
TensorFlow 支持读取更大的数据集,特别是这样,数据永远不能一次全部保存在内存中(如果有这个限制则不会非常有用)。 你可以使用一些函数和选项,从标准 Python 一直到特定的操作。
TensorFlow 还支持编写自定义数据处理程序,如果你有一个包含大量数据的非常大的项目,这是值得研究的。 编写自定义数据加载是前期的一点努力,但以后可以节省大量时间。 此主题的更多信息,请查看此处的官方文档。
在本课程中,我们将介绍使用 TensorFlow 读取 CSV 文件,以及在图中使用数据的基础知识。
占位符
读取数据的最基本方法是使用标准 python 代码读取它。 让我们来看一个基本的例子,从这个 2016 年奥运会奖牌统计数据中读取数据。
首先,我们创建我们的图,它接受一行数据,并累计总奖牌。
import tensorflow as tf
import os
dir_path = os.path.dirname(os.path.realpath(__file__))
filename = dir_path + "/olympics2016.csv"
features = tf.placeholder(tf.int32, shape=[3], name='features')
country = tf.placeholder(tf.string, name='country')
total = tf.reduce_sum(features, name='total')
接下来,我将介绍一个名为Print
的新操作,它打印出图形上某些节点的当前值。 它是一个单位元素,这意味着它将操作作为输入,只返回与输出相同的值。
printerop = tf.Print(total, [country, features, total], name='printer')
当你求解打印操作时会发生什么? 它基本上将当前值记录在第二个参数中(在本例中为列表[country, features, total]
)并返回第一个值(total
)。 但它被认为是一个变量,因此我们需要在启动会话时初始化所有变量。
接下来,我们启动会话,然后打开文件来读取。 请注意,文件读取完全是在 python 中完成的 - 我们只是在执行图形的同时读取它。
with tf.Session() as sess:
sess.run( tf.global_variables_initializer())
with open(filename) as inf:
# 跳过标题
next(inf)
for line in inf:
# 使用 python 将数据读入我们的特征
country_name, code, gold, silver, bronze, total = line.strip().split(",")
gold = int(gold)
silver = int(silver)
bronze = int(bronze)
# 运行打印操作
total = sess.run(printerop, feed_dict={features: [gold, silver, bronze], country:country_name})
print(country_name, total)
在循环的内部部分,我们读取文件的一行,用逗号分割,将值转换为整数,然后将数据作为占位符值提供给feed_dict
。 如果你不确定这里发生了什么,请查看之前的占位符教程。
当你运行它时,你会在每一行看到两个输出。 第一个输出将是打印操作的结果,看起来有点像这样:
I tensorflow/core/kernels/logging_ops.cc:79] [\"France\"][10 18 14][42]
下一个输出将是print(country_name, total)
行的结果,该行打印当前国家/地区名称(python 变量)和运行打印操作的结果。 由于打印操作是一个单位函数,因此调用它的结果只是求值total
的结果,这会将金,银和铜的数量相加。
它通常以类似的方式工作得很好。 创建占位符,将一些数据加载到内存中,计算它,然后循环使用新数据。 毕竟,这是占位符的用途。
读取 CSV
TensorFlow 支持将数据直接读入张量,但格式有点笨重。 我将通过一种方式逐步完成此操作,但我选择了一种特殊的通用方法,我希望你可以将它用于你自己的项目。
步骤是创建要读取的文件名的队列(列表),然后创建稍后将执行读取的读取器操作。 从这个阅读器操作中,创建在图执行阶段执行时用实际值替换的变量。
让我们来看看该过程的最后几个步骤:
def create_file_reader_ops(filename_queue):
reader = tf.TextLineReader(skip_header_lines=1)
_, csv_row = reader.read(filename_queue)
record_defaults = [[""], [""], [0], [0], [0], [0]]
country, code, gold, silver, bronze, total = tf.decode_csv(csv_row, record_defaults=record_defaults)
features = tf.pack([gold, silver, bronze])
return features, country
这里的读取器在技术上采用队列对象,而不是普通的 Python 列表,所以我们需要在将它传递给函数之前构建一个:
filename_queue = tf.train.string_input_producer(filenames, num_epochs=1, shuffle=False)
example, country = create_file_reader_ops(filename_queue)
由该函数调用产生的那些操作,稍后将表示来自我们的数据集的单个条目。 运行这些需要比平常更多的工作。 原因是队列本身不像正常操作那样位于图上,因此我们需要一个Coordinator
来管理队列中的运行。 每次求值示例和标签时,此协调器将在数据集中递增,因为它们有效地从文件中提取数据。
with tf.Session() as sess:
tf.global_variables_initializer().run()
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(coord=coord)
while True:
try:
example_data, country_name = sess.run([example, country])
print(example_data, country_name)
except tf.errors.OutOfRangeError:
break
内部while
循环保持循环,直到我们遇到OutOfRangeError
,表明没有更多数据要还原。
有了这段代码,我们现在从数据集中一次得到一行,直接加载到我们的图形中。 还有其他用于创建批量和打乱的功能 - 如果你想了解这些参数的更多信息,请查看tf.train.string_input_producer
和tf.train.shuffle_batch
中的一些参数。
在本课中,我们研究了:
- 在执行 TensorFlow 图时使用 Python 读取数据
tf.Print
操作- 将数据直接读入 TensorFlow 图/变量
- 队列对象
- 更新第二个示例的代码(直接将文件读入 TensorFlow),使用与 python-version 相同的方式输出总和(即打印出来并使用
tf.Print
) - 在
create_file_reader_ops
中解包特征操作,即不执行tf.pack
行。 更改代码的其余部分来满足一下情况,特征作为三个单独的特征返回,而不是单个打包的特征。 需要改变什么? - 将数据文件拆分为几个不同的文件(可以使用文本编辑器完成)并更新队列来全部读取它们。
- 使用
tf.train.shuffle_batch
将多行合成一个变量。 这对于较大的数据集比逐行读取更有用。
对于问题4,一个好的目标是在一个批量中加载尽可能多的数据,但不要太多以至于它会使计算机的 RAM 过载。 这对于这个数据集无关紧要,但以后请记住。
另外,使用批量时不会返回所有数据 - 如果批量未满,则不会返回。
迁移到 AWS
在很多情况下,运行代码可能非常耗时,特别是如果你正在运行机器学习或神经网络。除非你在计算机上花费了大量资金,否则转向基于云的服务可能是最好的方法。
在本教程中,我们将采用一些 Tensorflow 代码并将其移至 Amazon Web 服务(AWS)弹性计算云实例(EC2)。
亚马逊网络服务(AWS)是一个安全的云服务平台,提供计算能力,数据库存储,内容交付和其他功能,来帮助企业扩展和发展。此外,亚马逊弹性计算云(Amazon EC2)是一种 Web 服务,可在云中提供可调整大小的计算能力。它旨在使 Web 级云计算对开发人员更轻松。
这样做的好处是,亚马逊拥有大量基于云的服务器,其背后有很多功能。这将允许你在网络上运行代码的时间,只有你能够从本地计算机运行代码的一半。这也意味着如果它是一个需要 5-8 个小时才能完成的大型文件,你可以在 EC2 实例上运行它,并将其保留在后台而不使用你的整个计算机资源。
创建一个 EC2 环境会花费你的钱,但它是一个非常少,8 小时可能大约 4.00 美元。 一旦你停止使用它,将不会收取你的费用。请访问此链接来查看价格。
创建 EC2 实例
首先,访问 AWS 控制台。
使用你的亚马逊帐户登录。如果你没有,则会提示你创建一个,你需要执行此操作才能继续。
接下来,请访问 EC2 服务控制台。
单击Launch Instance
并在右上角的下拉菜单中选择你的地区(例如sydney, N california
)作为你的位置。
接下来转到社区 AMI 并搜索 Ubuntu x64 AMI 和 TensorFlow(GPU),它已准备好通过 GPU 运行代码,但它也足以在其上运行基本或大型 Tensorflow 脚本,而且优势是 Tensorflow 已安装。
此时,将向你收取费用,因此请务必在完成后关闭机器。 你可以转到 EC2 服务,选择机器并停止它。 你不需要为未运行的机器付费。
系统将提示你如何连接到实例的一些信息。 如果你之前未使用过 AWS,则可能需要创建一个新密钥对才能安全地连接到你的实例。 在这种情况下,为你的密钥对命名,下载 pemfile,并将其存储在安全的地方 - 如果丢失,你将无法再次连接到你的实例!
单击“连接”来获取使用 pem 文件连接到实例的信息。 最可能的情况是你将使用以下命令来使用ssh
:
ssh -i <certificante_name>.pem ubuntu@<server_ip_address>
将你的代码移动到 AWS EC2
我们将使用以下示例继续我们的 EC2 实例,这来自前面的章节:
import tensorflow as tf
import numpy as np
# x 和 y 是我们的训练数据的占位符
x = tf.placeholder("float")
y = tf.placeholder("float")
# w 是存储我们的值的变量。 它使用“猜测”来初始化
# w[0] 是我们方程中的“a”,w[1] 是“b”
w = tf.Variable([1.0, 2.0], name="w")
# 我们的模型是 y = a*x + b
y_model = tf.multiply(x, w[0]) + w[1]
# 我们的误差定义为差异的平方
error = tf.square(y - y_model)
# GradientDescentOptimizer 完成繁重的工作
train_op = tf.train.GradientDescentOptimizer(0.01).minimize(error)
# TensorFlow 常规 - 初始化值,创建会话并运行模型
model = tf.global_variables_initializer()
with tf.Session() as session:
session.run(model)
for i in range(1000):
x_value = np.random.rand()
y_value = x_value * 2 + 6
session.run(train_op, feed_dict={x: x_value, y: y_value})
w_value = session.run(w)
print("Predicted model: {a:.3f}x + {b:.3f}".format(a=w_value[0], b=w_value[1]))
有很多方法可以将此文件放到EC2实例上,但最简单的方法之一就是复制并粘贴内容。
首先,按Ctrl + A
高亮以上所有代码,然后使用Ctrl + C
复制所有代码
在 Amazon 虚拟机上,移动到主目录并使用新文件名打开nano
,我们将在此示例中调用basic.py
(以下是终端命令):
$ cd~/
$ nano <nameofscript>.py
nano
程序将打开,这是一个命令行文本编辑器。
打开此程序后,将剪贴板的内容粘贴到此文件中。 在某些系统上,你可能需要使用ssh
程序的文件选项,而不是按Ctrl + V
进行粘贴。 在nano
中,按Ctrl + O
将文件保存在磁盘上,我们将其命名为basic.py
,然后按Ctrl + X
退出程序。
一旦你退出nano
,输入python basic.py
就可以了!
你现在应该看到终端中弹出代码的结果,因为你很可能会发现,这可能是一种执行大型数据程序的更好方法。
Facenet 是一款利用 Tensorflow 的人脸识别程序,它提供了预先训练的模型,供你下载和运行来查看其工作原理。
1)访问此链接并下载预先训练的人脸识别模型
2)使用上面的教程,将代码上传到 EC2 实例并使其运行。
二、线性模型
广播
当我们操作不同维度的数组时,它们可以以不同的方式组合,无论是逐元素还是通过广播。
让我们从头开始,构建更复杂的例子。 在下面的示例中,我们有表示单个数字的 TensorFlow 常量。
import tensorflow as tf
a = tf.constant(3, name='a')
with tf.Session() as session:
print(session.run(a))
这里没什么惊喜! 我们也可以进行计算,例如将其加上另一个数字:
a = tf.constant(3, name='a')
b = tf.constant(4, name='b')
add_op = a + b
with tf.Session() as session:
print(session.run(add_op))
让我们将这个概念扩展到一个数字列表。 首先,让我们创建一个包含三个数字的列表,然后创建另一个数字列表:
a = tf.constant([1, 2, 3], name='a')
b = tf.constant([4, 5, 6], name='b')
add_op = a + b
with tf.Session() as session:
print(session.run(add_op))
这称为逐元素操作,其中依次考虑每个列表中的元素,将它们相加,然后合并结果。
如果我们将这个列表和仅仅一个数字相加,会发生什么?
a = tf.constant([1, 2, 3], name='a')
b = tf.constant(4, name='b')
add_op = a + b
with tf.Session() as session:
print(session.run(add_op))
这是你所期望的吗? 这被称为广播操作。 我们的主要对象引用是a
,它是一个数字列表,也称为数组或一维向量。 与单个数字(称为标量)相加会产生广播操作,其中标量将与列表的每个元素相加。
现在让我们看一个扩展,它是一个二维数组,也称为矩阵。 这个额外的维度可以被认为是“列表的列表”。 换句话说,列表是标量的组合,矩阵是列表的列表。
也就是说,矩阵上的操作如何工作?
a = tf.constant([[1, 2, 3], [4, 5, 6]], name='a')
b = tf.constant([[1, 2, 3], [4, 5, 6]], name='b')
add_op = a + b
with tf.Session() as session:
print(session.run(add_op))
这是逐元素的。 如果我们加上一个标量,结果是可以预测的:
a = tf.constant([[1, 2, 3], [4, 5, 6]], name='a')
b = tf.constant(100, name='b')
add_op = a + b
with tf.Session() as session:
print(session.run(add_op))
事情开始变得棘手。 如果我们将一维数组与二维矩阵相加会发生什么?
a = tf.constant([[1, 2, 3], [4, 5, 6]], name='a')
b = tf.constant([100, 101, 102], name='b')
add_op = a + b
with tf.Session() as session:
print(session.run(add_op))
在这种情况下,数组被广播为矩阵的形状,导致数组与矩阵的每一行相加。 使用此术语,矩阵是行的列表。
如果我们不想要这个,而是想将矩阵的列与b
相加呢?
a = tf.constant([[1, 2, 3], [4, 5, 6]], name='a')
b = tf.constant([100, 101,], name='b')
add_op = a + b
with tf.Session() as session:
print(session.run(add_op))
这不起作用,因为 TensorFlow 试图按照行广播。 它不能这样做,因为b
中的值的数量(2)与每行中的标量数量(3)不同。
我们可以通过从列表中创建一个新矩阵来执行此操作。
a = tf.constant([[1, 2, 3], [4, 5, 6]], name='a')
b = tf.constant([[100], [101]], name='b')
add_op = a + b
with tf.Session() as session:
print(session.run(add_op))
这里发生了什么? 要理解这一点,让我们看一下矩阵形状。
a.shape
TensorShape([Dimension(2), Dimension(3)])
b.shape
TensorShape([Dimension(2), Dimension(1)])
你可以从这两个示例中看到a
有两个维度,第一个大小为 2,第二个大小为 3。换句话说,它有两行,每行有三个标量。
我们的常数b
也有两个维度,两行,每行一个标量。如果有一行两个标量,这与列表不同,也与矩阵不同。
由于形状在第一维匹配,而第二维不匹配的事实,广播发生在列而不是行中。 广播规则的更多信息请参见此处。
创建一个三维矩阵。 如果将其与标量,数组或矩阵相加,会发生什么?
使用tf.shape
(这是一个操作)在图的操作期间获得常量的形状。
考虑更高维矩阵的用例。 换句话说,在哪里你可能需要 4D 矩阵,甚至是 5D 矩阵? 提示:考虑集合而不是单个对象。
随机性
机器学习模型是许多变量的复杂集合,但必须经过训练才能找到好的值。这也意味着必须将这些“权重”设置为初始值。一种选择是从所有权重为零开始。但是,这会在算法上引起问题 - 基本上,错误的梯度无法修复错误。相反,我们经常将这些权重设置为随机值。然后,模型学习并调整。
TensorFlow 有许多用于生成随机数的内置方法。这包括我们熟悉的分布,如“均匀”,以及你可能听说过的其他分布,如“正态”分布。均匀分布就像你掷骰子时得到的东西那样 - 有一组值,它们都是等可能的。正态分布是统计课程中教授的标准,其中数据具有更可能的平均值,以及围绕它的“钟形”曲线。我们将看到的,其他的也包括在内。
在本节中,我们将创建一个基本的辅助函数,它只运行一个 TensorFlow 变量。这个小函数非常有用!它创建一个会话,初始化变量并为我们运行它。它仅限于单个变量,因此对于较大的程序可能没有用。
import tensorflow as tf
def run_variable(variable):
tf.initialize_all_variables()
with tf.Session() as sess:
return sess.run(variable)
希望现在这对你来说都很熟悉。 如果没有,请再看看第一章,开始吧。
让我们从一个基本的分布开始,均匀分布。
my_distribution = tf.random_uniform((6, 4), seed=42)
uniform = run_variable(my_distribution)
这为我们提供了一个 6 乘 4 的张量(随机值的更多信息,请参阅上一节)。为了可视化,我们可以使用直方图:
from matplotlib import pyplot as plt
plt.hist(uniform.flatten())
plt.show()
请注意,如果你使用的是 Jupyter 笔记本,请使用%matplotlib inline
并删除plt.show()
行。
所得图像显示了图片,虽然还不是很清楚......
此直方图显示可能的值介于 0 和 1 之间。每个值应该是等可能的,但它看起来并不是那样。 原因是我们只选择了少量的值。 如果我们增加数组的大小,它会变得更加均匀。
large_normal = tf.random_uniform((600, 400), seed=42)
large_uniform = run_variable(large_normal)
plt.hist(large_uniform.flatten())
plt.show()
更均匀了!
如果你没有任何其他信息,对于在机器学习模型中初始化权重,均匀分布非常有用。 它也是一个“有界”分布,它具有设定的最小值和最大值,随机值不能超出该范围。 要更改范围,例如更改为 0 和 10,请乘以范围并添加最小值。 在课程结束时有一个练习。
另一种常用的分布是正态分布,在 TensorFlow 中实现为random_normal
函数:
distribution = tf.random_normal((600, 4), seed=42)
normal = run_variable(distribution)
plt.hist(normal.flatten())
plt.show()
默认情况下,此分布的平均值约为 0,标准差为 1。这些值不受限制,但越来越不可能偏离平均值,标准差设置了可能性减小的速率。 在实践中,大约 60% 的值落在距离平均值一个标准差的“半径”内,并且 99% 落在 4 个标准差内。
均值和标准差是random_normal
函数的参数。 例如,身高可近似建模为正态分布,平均值约为 170cm,标准差约为 15cm。
distribution = tf.random_normal((10000,), seed=42, mean=170, stddev=15)
normal = run_variable(distribution)
plt.hist(normal.flatten())
plt.show()
到目前为止,我们的直方图使用matplotlib
生成。 我们也可以使用 TensorFlow 来创建它们!histogram_fixed_width
函数接受值的列表(如我们的随机值),范围和要计算的桶数。 然后计算每个桶的范围内有多少个值,并将结果作为数组返回。
import numpy as np
bins = tf.histogram_fixed_width(normal, (normal.min(), normal.max()), nbins=20)
histogram_bins = run_variable(bins)
x_values = np.linspace(normal.min(), normal.max(), len(histogram_bins))
plt.bar(x_values, histogram_bins,)
在plt.bar
调用中,我们再次手动生成bin
值,然后使用条形图将这些值绘制为x
值,并使用histogram_bins
作为高度。
这是正确的,但看起来不对。 直方图的值在那里,但宽度非常窄(我们的箱桶仅由单个值表示)。 我们来解决这个问题:
bar_width = (normal.max() - normal.min()) / len(histogram_bins)
plt.bar(x_values, histogram_bins, width=bar_width)
- 使用均匀分布建模单次掷骰子。 绘制结果来确保其符合你的期望
- 使用单个图中的纯 TensorFlow 调用替换本课程的最后一个代码块。 换句话说,使用 TensorFlow 概念来替换
.min()
,.max()
和len
调用。 只有绘图在没有 TensorFlow 的情况下进行!
线性方程
通过tf.solve
函数,TensorFlow 可以求解线性方程组。 你可能会将这些视为连接的方程,如下所示:
这些类型的线性方程用于数学中的许多问题,从优化工厂输出到几何。 你可以使用多种方法解决这些方程,但在本课中,我们将了解如何使用tf.solve
为我们执行此操作。
我将专注于几何。 这是位于二维(x, y)
空间的两个点,p1
和p2
:
这是他们在图上的样子:
要在 TensorFlow 中执行此操作,我们首先设置线性方程组,我们的点位于中心。 首先,我们创建我们的点矩阵。 第一行对应于第一个点,第二行对应于第二个点。 同样,第一列是x
值,而第二列是y
值。
import tensorflow as tf
# 点 1
x1 = tf.constant(2, dtype=tf.float32)
y1 = tf.constant(9, dtype=tf.float32)
point1 = tf.stack([x1, y1])
# 点 2
x2 = tf.constant(-1, dtype=tf.float32)
y2 = tf.constant(3, dtype=tf.float32)
point2 = tf.stack([x2, y2])
# 将点组合为数组
X = tf.transpose(tf.stack([point1, point2]))
直线的方程是:
重新排列方程(5),使x
和y
在同一侧,我们得到以下结果:
我们的任务是在给定观测点的情况下,找到上面的方程中的a
和b
的值。 我们可以通过取点数组的逆并将其乘以一个矩阵,来轻易做到这一点。
使用矩阵(因为我们使用的是 TensorFlow),如果X
是我们观察点的矩阵,而A
是我们需要学习的参数,我们设置一个系统:
接下来要学习的参数就是:
矩阵B
很简单,适当广播的数字 1,它源于上面方程的右侧。
矩阵A
是上面方程 3 中的参数。
B = tf.ones((1, 2), dtype=tf.float32)
parameters = tf.matmul(B, tf.matrix_inverse(X))
with tf.Session() as session:
A = session.run(parameters)
最后一步是从上面的方程(5)中找到我们的a
和b
值,即从这些参数转换(符合方程(7))。
b = 1 / A[0][1]
a = -b * A[0][0]
print("Equation: y = {a}x + {b}".format(a=a, b=b))
这个解决方案很好地包含在tf.solve
函数中。 为了看到它,让我们看另一个例子。 这是一个圆圈:
以下是圆圈上的三个观察点:
圆的规范方程是:
为了求解参数d
,e
和f
,我们创建另一个点数组,并用 1 填充它来创建一个方阵。 我们正在寻找三个参数,因此我们的A
矩阵必须具有形状(3, 3)
。
由于这个方程的平方部分没有参数,当我们有x
和y
的观测值时,我们的方程变得有点不同:
因此,我们的A
矩阵由x
和y
值(以及另一列 1)组成,我们的B
矩阵是负的x
和y
的平方和。
import tensorflow as tf
points = tf.constant([[2, 1],
[0, 5],
[-1, 2]], dtype=tf.float64)
A = tf.constant([
[2, 1, 1],
[0, 5, 1],
[-1, 2, 1]
], dtype='float64')
B = -tf.constant([[5], [25], [5]])
然后我们使用tf.matrix_solve
来找到我们的X
数组,这是我们方程的参数。 在会话中运行它,我们得到三个值,即D
,E
和F
。
X = tf.matrix_solve(A, B)
with tf.Session() as session:
result = session.run(X)
D, E, F = result.flatten()
print("Equation: x**2 + y**2 + {D}x + {E}y + {F} = 0".format(**locals()))
1)求解包含以下三点的圆:P(2,1)
, Q(0,5)
, R(-1,2)
2)下面给出椭圆的一般形式。 解决以下几点(解决这个方程需要五点):
椭圆的一般形式:
观测点:
3D 中的 TensorFlow
TensorFlow 不仅仅是一个深度学习库 - 它是一个但数值操作库,因此它可以执行许多其他库可以执行的任务。 在本课中,我们将介绍如何使用 TensorFlow 对 3D 对象执行操作。
3D 对象可以被建模为三维空间中的一系列三角形,我们通常将其称为(x, y, z)
。 这些名称不是必需的,但通常使用。 从这些 3D 点中的三个创建三角形。 点本身可以表示为大小为(3,)
的向量。 这些数组是一个大小为(n, 3),
的矩阵,其中n
是我们拥有的点数。 让我们深入去看一个基本的立方体。 我们稍后将需要此功能,所以让我们创建一个绘制基本形状的函数:
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
from matplotlib import cm
import matplotlib.pyplot as plt
from scipy.spatial import Delaunay
def plot_basic_object(points):
"""绘制一个基本对象,假设它是凸的而不是太复杂"""
tri = Delaunay(points).convex_hull
fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(111, projection='3d')
S = ax.plot_trisurf(points[:,0], points[:,1], points[:,2],
triangles=tri,
shade=True, cmap=cm.Blues,lw=0.5)
ax.set_xlim3d(-5, 5)
ax.set_ylim3d(-5, 5)
ax.set_zlim3d(-5, 5)
plt.show()
如果你正在使用 Jupyter 笔记本,我建议运行这一行代码,它为你提供了一个非常棒的交互式 3D 绘图。 左键单击并拖动来左右移动,右键单击并拖动来放大或缩小。
%matplotlib notebook
现在让我们创建一个形状。 下面的函数将返回组成立方体的六个点。 如果你回到上一个函数,你将看到 Delaunay 线,它将这些点转换成三角形,以便我们可以渲染它们。
import numpy as np
def create_cube(bottom_lower=(0, 0, 0), side_length=5):
"""从给定的左下角点(最小的 x,y,z 值)开始创建一个立方体"""
bottom_lower = np.array(bottom_lower)
points = np.vstack([
bottom_lower,
bottom_lower + [0, side_length, 0],
bottom_lower + [side_length, side_length, 0],
bottom_lower + [side_length, 0, 0],
bottom_lower + [0, 0, side_length],
bottom_lower + [0, side_length, side_length],
bottom_lower + [side_length, side_length, side_length],
bottom_lower + [side_length, 0, side_length],
bottom_lower,
])
return points
现在让我们把这些碎片放在一起,看看它是什么样的:
cube_1 = create_cube(side_length=2)
plot_basic_object(cube_1)
我只是在这里显示一个图像,但是你可以看到立方体,它已被我们的代码变成三角形并且颜色不同(取决于z
值)。 这很好,但现在让我们使用 TensorFlow 对此进行一些操作。
平移
平移是一个简单的动作:向上/向下,向左/向右,向前/向后,或这些的某种组合。 它是通过简单地向每个点添加一个向量来创建的。 如果向所有点添加相同的向量,则整个对象将一致地移动。 查看我们关于广播的章节,了解当我们将大小为(3,)
的平移向量添加到大小(n, 3)
的点矩阵时会发生什么。
import tensorflow as tf
def translate(points, amount):
return tf.add(points, amount)
points = tf.constant(cube_1, dtype=tf.float32)
# 更新此处的值来移动多维数据集。
translation_amount = tf.constant([3, -3, 0], dtype=tf.float32)
translate_op = translate(points, translation_amount)
with tf.Session() as session:
translated_cube = session.run(translate_op)
plot_basic_object(translated_cube)
旋转
通过创建点积或旋转矩阵和原点来形成旋转。 旋转对象首先需要你确定要旋转的轴。 要围绕特定轴旋转,请将该轴的值设置为 0,相关轴中的值为 1。 你需要三个矩阵:
沿x
轴旋转
[[1, 0, 0],
[0, cos \theta, sin \theta],
[0, -sin \theta, cos \theta]]
沿y
轴旋转
[[cos \theta, 0, -sin \theta],
[0, 1, 0],
[sin \theta, 0, cos \theta]]
沿z
轴旋转
[[cos \theta, sin \theta, 0],
[-sin \theta, cos \theta, 0],
[0, 0, 1]]
def rotate_around_z(points, theta):
theta = float(theta)
rotation_matrix = tf.stack([[tf.cos(theta), tf.sin(theta), 0],
[-tf.sin(theta), tf.cos(theta), 0],
[0, 0, 1]])
return tf.matmul(tf.to_float(points), tf.to_float(rotation_matrix))
with tf.Session() as session:
result = session.run(rotate_around_z(cube_1, 75))
plot_basic_object(result)
通过这些简单,但是可以大规模组合的矩阵操作,你可以像这样为 3D 对象创建一系列的变换。 此概念可以用于实现剪切,缩放,交叉等。 GPU 非常擅长进行这些转换,这些转换恰好与数据分析工作(如深度学习)所需的相同类型的转换相关。 因此,TensorFlow 可以很好地配合 GPU,处理 3D 对象以及深度学习任务。
- 创建不同的对象,例如四棱锥或者六棱柱。 如果你不确定如何开始,请先从棱柱开始,然后先在2D中创建它。
- 围绕
x
轴和y
轴旋转对象。 - 你可以将旋转组合到单个变换矩阵中。 为此,只需计算旋转的点积。
- 对于问题 3,顺序是否重要?
- 剪切矩阵是具有非对角线值的单位矩阵。 一个例子如下。 创建剪切矩阵并测试不同的值。
[[1, 0.5, 0],
[0, 1, 0],
[0, 0, 1]]
线性模型的分类
在本课中,我们将了解使用 TensorFlow 进行机器学习。
我们将创建自己的线性分类器,并使用 TensorFlow 的内置优化算法来训练它。
首先,我们将查看数据以及我们要做的事情。 对于那些刚接触机器学习的人来说,我们尝试执行的任务称为监督机器学习或分类。
任务是尝试计算一些输入数据和输出值之间的关系。 实际上,输入数据可以是测量值,例如高度或重量,输出值可以是预期的预测值,例如“cat”或“dog”。
这里的课程扩展自我们的课程“收敛”,在后面的章节中。 我建议你先完成那个课程。
让我们创建并可视化一些数据:
from sklearn.datasets import make_blobs
import numpy as np
from sklearn.preprocessing import OneHotEncoder
X_values, y_flat = make_blobs(n_features=2, n_samples=800, centers=3, random_state=500)
y = OneHotEncoder().fit_transform(y_flat.reshape(-1, 1)).todense()
y = np.array(y)
%matplotlib inline
from matplotlib import pyplot as plt
# 可选的行:将默认数字大小设置得稍大。
plt.rcParams['figure.figsize'] = (24, 10)
plt.scatter(X_values[:,0], X_values[:,1], c=y_flat, alpha=0.4, s=150)
在这里,我们有三种数据,黄色,蓝色和紫色。 它们绘制在两个维度上,我们称之为x0x0
和x1x1
。
这些值存储在X
数组中。
当我们执行机器学习时,有必要将数据拆分为我们用于创建模型的训练集和用于评估它的测试集。 如果我们不这样做,那么我们可以简单地创建一个“作弊分类器”,只记得我们的训练数据。 通过拆分,我们的分类器必须学习输入(绘图上的位置)和输出之间的关系。
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test, y_train_flat, y_test_flat = train_test_split(X_values, y, y_flat)
X_test += np.random.randn(*X_test.shape) * 1.5
现在我们绘制测试数据。 从训练数据中学习位置和颜色之间的关系之后,将给予分类器以下几个点,并且将评估它对点着色的准确度。
#plt.scatter(X_train[:,0], X_train[:,1], c=y_train_flat, alpha=0.3, s=150)
plt.plot(X_test[:,0], X_test[:,1], 'rx', markersize=20)
创建模型
我们的模型将是一个简单的线性分类器。 这意味着它将在三种颜色之间绘制直线。 一条线上方的点被赋予一种颜色,而一条线下方的点被赋予另一种颜色。 我们将这些称为决策直线,尽管它们通常被称为决策边界,因为其他模型可以学习比线更复杂的形状。
为了在数学上表示我们的模型,我们使用以下等式:
Y = XW + b
我们的权重W
是(n_features, n_classes)
矩阵,表示我们模型中的学习权重。 它决定了决策直线的位置。 X
是(n_rows, n_features)
矩阵,并且是位置数据 - 给定点位于图上。 最后,b
是(1, n_classes)
向量,并且是偏差。 我们需要这样,以便我们的线不必经过点(0,0)
,使我们能够在图上的任何位置“绘制”直线。
X
中的点是固定的 - 这些是训练或测试数据,称为观测数据。 W
和b
的值是我们模型中的参数,我们可以控制这些值。 为这些值选择好的值,可以为我们提供良好的决策线。
在我们的模型中为参数选择好的值的过程,称为训练算法,并且是机器学习中的“学习”。
让我们从上面得到我们的数学模型,并将其转换为 TensorFlow 操作。
import tensorflow as tf
n_features = X_values.shape[1]
n_classes = len(set(y_flat))
weights_shape = (n_features, n_classes)
W = tf.Variable(dtype=tf.float32, initial_value=tf.random_normal(weights_shape)) # Weights of the model
X = tf.placeholder(dtype=tf.float32)
Y_true = tf.placeholder(dtype=tf.float32)
bias_shape = (1, n_classes)
b = tf.Variable(dtype=tf.float32, initial_value=tf.random_normal(bias_shape))
Y_pred = tf.matmul(X, W) + b
上面的Y_pred
张量代表我们的数学模型。通过传入观测数据(X
),我们可以得到预期值,在我们的例子中,是给定点的预期颜色。请注意偏差使用广播在所有预测中应用。
Y_pred
中的实际值由“似然”组成,模型将为给定点选择每个类的似然,生成(n_rows, n_classes)
大小的矩阵。它们不是真正的似然,但我们可以通过找到最大值,来找出我们的模型认为的最有可能的类。
接下来,我们需要定义一个函数来评估给定权重集的好坏程度。请注意,我们尚未学习权重,只是给出了随机值。 TensorFlow 具有内置的损失函数,可以接受预测的输出的(即模型产生的值)与实际值(我们首次创建测试集时创建的真实情况)。我们比较它们,并评估我们的模型表现如何。我们称之为损失函数,因为我们做得越差,值越高 - 我们试图将损失最小化。
loss_function = tf.losses.softmax_cross_entropy(Y_true, Y_pred)
最后一步是创建一个优化步骤,该步骤接受我们的损失函数,并找到给定变量的最小化损失的值。 请注意,loss
函数引用Y_true
,后者又引用W
和b
。 TensorFlow 选择此关系,并更改这些变量中的值来寻找良好的值。
learner = tf.train.GradientDescentOptimizer(0.1).minimize(loss_function)
现在开始训练了!
我们在循环中遍历学习器,来找到最佳权重。 每次循环中,前一循环的学习权重会在下一个循环中略有改善。 前一行代码中的0.1
是学习率。 如果增加该值,算法学得更快。 但是,较小的值通常会收敛到更好的值。 当你查看模型的其他方面时,值为0.1
是一个很好的起点。
每次循环中,我们通过占位符将我们的训练数据传递给学习器。 每隔 100 个循环,我们通过将测试数据直接传递给损失函数,来了解我们的模型是如何学习的。
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for i in range(5000):
result = sess.run(learner, {X: X_train, Y_true: y_train})
if i % 100 == 0:
print("Iteration {}:\tLoss={:.6f}".format(i, sess.run(loss_function, {X: X_test, Y_true: y_test})))
y_pred = sess.run(Y_pred, {X: X_test})
W_final, b_final = sess.run([W, b])
predicted_y_values = np.argmax(y_pred, axis=1)
predicted_y_values
h = 1
x_min, x_max = X_values[:, 0].min() - 2 * h, X_values[:, 0].max() + 2 * h
y_min, y_max = X_values[:, 1].min() - 2 * h, X_values[:, 1].max() + 2 * h
x_0, x_1 = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
decision_points = np.c_[x_0.ravel(), x_1.ravel()]
有点复杂,但我们正在有效地创建一个二维网格,涵盖x0
和x1
的可能值。
# 我们在 NumPy 中重建我们的模型
Z = np.argmax(decision_points @ W_final[[0,1]] + b_final, axis=1)
# 创建 x_0 和 x_1 值的等高线图
Z = Z.reshape(xx.shape)
plt.contourf(x_0, x_1, Z, alpha=0.1)
plt.scatter(X_train[:,0], X_train[:,1], c=y_train_flat, alpha=0.3)
plt.scatter(X_test[:,0], X_test[:,1], c=predicted_y_values, marker='x', s=200)
plt.xlim(x_0.min(), x_0.max())
plt.ylim(x_1.min(), x_1.max())
你就实现了它! 我们的模型会将黄色区域中的任何东西分类为黄色,依此类推。 如果覆盖实际测试值(存储在y_test_flat
中),则可以高亮任何差异。
- 绘制迭代和损失之间的关系。 出现什么样的形状,你认为它将如何继续?
- 使用 TensorBoard,将图写入文件,并查看 TensorBoard 中变量的值。 更多信息请参阅其余教程。
- 通过在传递到线性模型之前对
X
执行一些变换来创建非线性模型。 这可以通过多种方式完成,你的模型的准确性将根据你的选择而改变。 - 使用以下代码加载 64 维(称为数字)的数据集,并将其传递给分类器。 你得到了什么预测准确度?
from sklearn.datasets import load_digits
digits = load_digits()
X = digits.data
y = digits.target
三、学习
聚类和 KMeans
我们现在冒险进入我们的第一个应用,即使用 k-means 算法进行聚类。 聚类是一种数据挖掘练习,我们获取大量数据并找到彼此相似的点的分组。 K-means 是一种非常善于在许多类型的数据集中查找簇的算法。
对于簇和 k-means的 更多信息,请参阅 k-means 算法的 scikit-learn 文档或观看此视频。
生成样本
首先,我们需要生成一些样本。 我们可以随机生成样本,但这可能会给我们提供非常稀疏的点,或者只是一个大分组 - 对于聚类来说并不是非常令人兴奋。
相反,我们将从生成三个质心开始,然后在该点周围随机选择(具有正态分布)。 首先,这是一个执行此操作的方法
import tensorflow as tf
import numpy as np
def create_samples(n_clusters, n_samples_per_cluster, n_features, embiggen_factor, seed):
np.random.seed(seed)
slices = []
centroids = []
# 为每个簇创建样本
for i in range(n_clusters):
samples = tf.random_normal((n_samples_per_cluster, n_features),
mean=0.0, stddev=5.0, dtype=tf.float32, seed=seed, name="cluster_{}".format(i))
current_centroid = (np.random.random((1, n_features)) * embiggen_factor) - (embiggen_factor/2)
centroids.append(current_centroid)
samples += current_centroid
slices.append(samples)
# 创建一个大的“样本”数据集
samples = tf.concat(slices, 0, name='samples')
centroids = tf.concat(centroids, 0, name='centroids')
return centroids, samples
这种方法的工作方式是随机创建n_clusters
个不同的质心(使用np.random.random((1, n_features))
)并将它们用作tf.random_normal
的质心。 tf.random_normal
函数生成正态分布的随机值,然后我们将其添加到当前质心。 这会在该形心周围创建一些点。 然后我们记录质心(centroids.append
)和生成的样本(slices.append(samples)
)。 最后,我们使用tf.concat
创建“一个大样本列表”,并使用tf.concat
将质心转换为 TensorFlow 变量。
将create_samples
方法保存在名为functions.py
的文件中,允许我们为这个(以及下一个!)课程,将这些方法导入到我们的脚本中。 创建一个名为generate_samples.py
的新文件,其中包含以下代码:
import tensorflow as tf
import numpy as np
from functions import create_samples
n_features = 2
n_clusters = 3
n_samples_per_cluster = 500
seed = 700
embiggen_factor = 70
np.random.seed(seed)
centroids, samples = create_samples(n_clusters, n_samples_per_cluster, n_features, embiggen_factor, seed)
model = tf.global_variables_initializer()
with tf.Session() as session:
sample_values = session.run(samples)
centroid_values = session.run(centroids)
这只是设置了簇和特征的数量(我建议将特征的数量保持为 2,以便我们以后可以可视化它们),以及要生成的样本数。 增加embiggen_factor
将增加簇的“散度”或大小。 我在这里选择了一个提供良好学习机会的值,因为它可以生成视觉上可识别的集群。
为了使结果可视化,我们使用matplotlib
创建绘图函数。 将此代码添加到functions.py
:
def plot_clusters(all_samples, centroids, n_samples_per_cluster):
import matplotlib.pyplot as plt
# 绘制出不同的簇
# 为每个簇选择不同的颜色
colour = plt.cm.rainbow(np.linspace(0,1,len(centroids)))
for i, centroid in enumerate(centroids):
# 为给定簇抓取样本,并用新颜色绘制出来
samples = all_samples[i*n_samples_per_cluster:(i+1)*n_samples_per_cluster]
plt.scatter(samples[:,0], samples[:,1], c=colour[i])
# 还绘制质心
plt.plot(centroid[0], centroid[1], markersize=35, marker="x", color='k', mew=10)
plt.plot(centroid[0], centroid[1], markersize=30, marker="x", color='m', mew=5)
plt.show()
所有这些代码都是使用不同的颜色绘制每个簇的样本,并在质心位置创建一个大的红色X
。 质心提供为参数,稍后会很方便。
更新generate_samples.py
,通过将import plot_clusters
添加到文件顶部来导入此函数。 然后,将这行代码添加到底部:
plot_clusters(sample_values, centroid_values, n_samples_per_cluster)
运行generate_samples.py
现在应该提供以下绘图:
初始化
k-means 算法从初始质心的选择开始,初始质心只是数据中实际质心的随机猜测。 以下函数将从数据集中随机选择多个样本作为此初始猜测:
def choose_random_centroids(samples, n_clusters):
# 第 0 步:初始化:选择 n_clusters 个随机点
n_samples = tf.shape(samples)[0]
random_indices = tf.random_shuffle(tf.range(0, n_samples))
begin = [0,]
size = [n_clusters,]
size[0] = n_clusters
centroid_indices = tf.slice(random_indices, begin, size)
initial_centroids = tf.gather(samples, centroid_indices)
return initial_centroids
这段代码首先为每个样本创建一个索引(使用tf.range(0, n_samples)
,然后随机打乱它。从那里,我们使用tf.slice
选择固定数量(n_clusters
)的索引。这些索引与我们的初始质心相关,然后使用tf.gather
组合在一起形成我们的初始质心数组。
将这个新的choose_random_centorids
函数添加到functions.py
中,并创建一个新脚本(或更新前一个脚本),写入以下内容:
import tensorflow as tf
import numpy as np
from functions import create_samples, choose_random_centroids, plot_clusters
n_features = 2
n_clusters = 3
n_samples_per_cluster = 500
seed = 700
embiggen_factor = 70
centroids, samples = create_samples(n_clusters, n_samples_per_cluster, n_features, embiggen_factor, seed)
initial_centroids = choose_random_centroids(samples, n_clusters)
model = tf.global_variables_initializer()
with tf.Session() as session:
sample_values = session.run(samples)
updated_centroid_value = session.run(initial_centroids)
plot_clusters(sample_values, updated_centroid_value, n_samples_per_cluster)
这里的主要变化是我们为这些初始质心创建变量,并在会话中计算其值。 然后,我们将初始猜测绘制到plot_cluster
,而不是用于生成数据的实际质心。
运行此操作会将得到与上面类似的图像,但质心将处于随机位置。 尝试运行此脚本几次,注意质心移动了很多。
更新质心
在开始对质心位置进行一些猜测之后,然后 k-means 算法基于数据更新那些猜测。 该过程是为每个样本分配一个簇号,表示它最接近的质心。 之后,将质心更新为分配给该簇的所有样本的平均值。 以下代码处理分配到最近的簇的步骤:
def assign_to_nearest(samples, centroids):
# 为每个样本查找最近的质心
# START from http://esciencegroup.com/2016/01/05/an-encounter-with-googles-tensorflow/
expanded_vectors = tf.expand_dims(samples, 0)
expanded_centroids = tf.expand_dims(centroids, 1)
distances = tf.reduce_sum( tf.square(
tf.subtract(expanded_vectors, expanded_centroids)), 2)
mins = tf.argmin(distances, 0)
# END from http://esciencegroup.com/2016/01/05/an-encounter-with-googles-tensorflow/
nearest_indices = mins
return nearest_indices
请注意,我从这个页面借用了一些代码,这些代码具有不同类型的 k-means 算法,以及许多其他有用的信息。
这种方法的工作方式是计算每个样本和每个质心之间的距离,这通过distances =
那行来实现。 这里的距离计算是欧几里德距离。 这里重要的一点是tf.subtract
会自动扩展两个参数的大小。 这意味着将我们作为矩阵的样本,和作为列向量的质心将在它们之间产生成对减法。 为了实现,我们使用tf.expand_dims
为样本和质心创建一个额外的维度,强制tf.subtract
的这种行为。
下一步代码处理质心更新:
def update_centroids(samples, nearest_indices, n_clusters):
# 将质心更新为与其相关的所有样本的平均值。
nearest_indices = tf.to_int32(nearest_indices)
partitions = tf.dynamic_partition(samples, nearest_indices, n_clusters)
new_centroids = tf.concat([tf.expand_dims(tf.reduce_mean(partition, 0), 0) for partition in partitions], 0)
return new_centroids
此代码选取每个样本的最近索引,并使用tf.dynamic_partition
将这些索引分到单独的组中。 从这里开始,我们在一个组中使用tf.reduce_mean
来查找该组的平均值,从而形成新的质心。 我们只需将它们连接起来形成我们的新质心。
现在我们有了这个部分,我们可以将这些调用添加到我们的脚本中(或者创建一个新脚本):
import tensorflow as tf
import numpy as np
from functions import *
n_features = 2
n_clusters = 3
n_samples_per_cluster = 500
seed = 700
embiggen_factor = 70
data_centroids, samples = create_samples(n_clusters, n_samples_per_cluster, n_features, embiggen_factor, seed)
initial_centroids = choose_random_centroids(samples, n_clusters)
nearest_indices = assign_to_nearest(samples, initial_centroids)
updated_centroids = update_centroids(samples, nearest_indices, n_clusters)
model = tf.global_variables_initializer()
with tf.Session() as session:
sample_values = session.run(samples)
updated_centroid_value = session.run(updated_centroids)
print(updated_centroid_value)
plot_clusters(sample_values, updated_centroid_value, n_samples_per_cluster)
此代码将:
- 从初始质心生成样本
- 随机选择初始质心
- 关联每个样本和最近的质心
- 将每个质心更新为与关联的样本的平均值
这是 k-means 的单次迭代! 我鼓励你们练习一下,把它变成一个迭代版本。
1)传递给generate_samples
的种子选项可确保每次运行脚本时,“随机”生成的样本都是一致的。 我们没有将种子传递给choose_random_centroids
函数,这意味着每次运行脚本时这些初始质心都不同。 更新脚本来为随机质心包含新的种子。
2)迭代地执行 k 均值算法,其中来自之前迭代的更新的质心用于分配簇,然后用于更新质心,等等。 换句话说,算法交替调用assign_to_nearest
和update_centroids
。 在停止之前,更新代码来执行此迭代 10 次。 你会发现,随着 k-means 的更多迭代,得到的质心平均上更接近。 (对于那些对 k-means 有经验的人,未来的教程将研究收敛函数和其他停止标准。)
训练和收敛
大多数人工智能和机器学习的关键组成部分是循环,即系统在多次训练迭代中得到改善。 以这种方式训练的一种非常简单的方法,就是在for
循环中执行更新。 我们在第 2 课中看到了这种方式的一个例子:
import tensorflow as tf
x = tf.Variable(0, name='x')
model = tf.global_variables_initializer()
with tf.Session() as session:
for i in range(5):
session.run(model)
x = x + 1
print(session.run(x))
我们可以改变此工作流,使用变量来收敛循环,如下所示:
import tensorflow as tf
x = tf.Variable(0., name='x')
threshold = tf.constant(5.)
model = tf.global_variables_initializer()
with tf.Session() as session:
session.run(model)
while session.run(tf.less(x, threshold)):
x = x + 1
x_value = session.run(x)
print(x_value)
这里的主要变化是,循环现在是一个while
循环,测试(tf.less
用于小于测试)为真时继续循环。 在这里,我们测试x
是否小于给定阈值(存储在常量中),如果是,我们继续循环。
梯度下降
任何机器学习库都必须具有梯度下降算法。 我认为这是一个定律。 无论如何,Tensorflow 在主题上有一些变化,它们可以直接使用。
梯度下降是一种学习算法,尝试最小化某些误差。 你问哪个误差? 嗯,这取决于我们,虽然有一些常用的方法。
让我们从一个基本的例子开始:
import tensorflow as tf
import numpy as np
# x 和 y 是我们的训练数据的占位符
x = tf.placeholder("float")
y = tf.placeholder("float")
# w 是存储我们的值的变量。 它使用“猜测”来初始化
# w[0] 是我们方程中的“a”,w[1] 是“b”
w = tf.Variable([1.0, 2.0], name="w")
# 我们的模型是 y = a*x + b
y_model = tf.multiply(x, w[0]) + w[1]
# 我们的误差定义为差异的平方
error = tf.square(y - y_model)
# GradientDescentOptimizer 完成繁重的工作
train_op = tf.train.GradientDescentOptimizer(0.01).minimize(error)
# TensorFlow 常规 - 初始化值,创建会话并运行模型
model = tf.global_variables_initializer()
with tf.Session() as session:
session.run(model)
for i in range(1000):
x_value = np.random.rand()
y_value = x_value * 2 + 6
session.run(train_op, feed_dict={x: x_value, y: y_value})
w_value = session.run(w)
print("Predicted model: {a:.3f}x + {b:.3f}".format(a=w_value[0], b=w_value[1]))
这里的主要兴趣点是train_op = tf.train.GradientDescentOptimizer(0.01).minimize(error)
,其中定义了训练步长。 它旨在最小化误差变量的值,该变量先前被定义为差的平方(常见的误差函数)。 0.01 是尝试学习更好的值所需的步长。
其它优化器
TensorFlow 有一整套优化器,并且你也可以定义自己的优化器(如果你对这类事情感兴趣)。 如何使用它们的 API,请参阅此页面。 列表如下:
GradientDescentOptimizer
AdagradOptimizer
MomentumOptimizer
AdamOptimizer
FtrlOptimizer
RMSPropOptimizer
其他优化方法可能会出现在 TensorFlow 的未来版本或第三方代码中。 也就是说,上述优化对于大多数深度学习技术来说已经足够了。 如果你不确定要使用哪一个,请使用AdamOptimizer
,除非失败。
译者注:原文推荐随机梯度优化器,在所有优化器里是最烂的,已更改。
这里一个重要的注意事项是,我们只优化了一个值,但该值可以是一个数组。 这就是为什么我们使用w
作为变量,而不是两个单独的变量a
和b
。
绘制误差
这个代码是上面的一个小改动。 首先,我们创建一个列表来存储误差。然后,在循环内部,我们显式地计算train_op
和误差。 我们在一行中执行此操作,因此误差仅计算一次。 如果我们在单独的行中这样做,它将计算误差,然后是训练步骤,并且在这样做时,它将需要重新计算误差。
下面我把代码放在上一个程序的tf.global_variables_initializer()
行下面 - 这一行上面的所有内容都是一样的。
errors = []
with tf.Session() as session:
session.run(model)
for i in range(1000):
x_train = tf.random_normal((1,), mean=5, stddev=2.0)
y_train = x_train * 2 + 6
x_value, y_value = session.run([x_train, y_train])
_, error_value = session.run([train_op, error], feed_dict={x: x_value, y: y_value})
errors.append(error_value)
w_value = session.run(w)
print("Predicted model: {a:.3f}x + {b:.3f}".format(a=w_value[0], b=w_value[1]))
import matplotlib.pyplot as plt
plt.plot([np.mean(errors[i-50:i]) for i in range(len(errors))])
plt.show()
plt.savefig("errors.png")
你可能已经注意到我在这里采用窗口平均值 - 使用np.mean(errors[i-50:i])
而不是仅使用errors[i]
。 这样做的原因是我们只在循环中测试一次,所以虽然误差会减小,但它会反弹很多。 采用这个窗口平均值可以平滑一点,但正如你在上面所看到的,它仍然会跳跃。
1)创建第 6 课中的 k-means 示例的收敛函数,如果旧质心与新质心之间的距离小于给定的epsilon
值,则停止训练。
2)尝试从梯度下降示例(w
)中分离a
和b
值。
3)我们的例子一次只训练一个示例,这是低效的。 扩展它来一次使用多个(例如 50 个)训练样本来学习。
TFLearn
已更新到最新的 TFLearn API。
这些教程主要关注 TensorFlow 的机制,但真正的用例是机器学习。 TensorFlow 有许多用于构建机器学习模型的方法,其中许多可以在官方 API 页面上找到。 这些函数允许你从头开始构建模型,包括自定义层面,例如如何构建神经网络中的层。
在本教程中,我们将查看 TensorFlow Learn,它是名为skflow
的软件包的新名称。 TensorFlow Learn(以下简称:TFLearn)是一个机器学习包装器,基于 scikit-learn API,允许你轻松执行数据挖掘。 这意味着什么? 让我们一步一步地完成它:
机器学习
机器学习是一种概念,构建从数据中学习的算法,以便对新数据执行操作。 在这种情况下,这意味着我们有一些输入的训练数据和预期结果 - 训练目标。 我们将看看著名的数字数据集,这是一堆手绘数字的图像。 我们的输入训练数据是几千个这些图像,我们的训练目标是预期的数字。
任务是学习一个模型,可以回答“这是什么数字?”,对于这样的输入:
这是一个分类任务,是数据挖掘最常见的应用之一。 还有一些称为回归和聚类的变体(以及许多其他变体),但在本课中我们不会涉及它们。
如果你想了解数据挖掘的更多信息,请查看我的书“Python 数据挖掘”。
Scikit-Learn API
Scikit-learn 是一个用于数据挖掘和分析的 Python 包,它非常受欢迎。 这是因为它广泛支持不同的算法,令人惊叹的文档,以及庞大而活跃的社区。 其他一个因素是它的一致接口,它的 API,允许人们构建可以使用 scikit-learn 辅助函数训练的模型,并允许人们非常容易地测试不同的模型。
我们来看看 scikit-learn 的 API,但首先我们需要一些数据。 以下代码加载了一组可以使用matplotlib.pyplot
显示的数字图像:
from sklearn.datasets import load_digits
from matplotlib import pyplot as plt
digits = load_digits()
我们可以使用pyplot.imshow
显示其中一个图像。 在这里,我设置interpolation ='none'
来完全按原样查看数据,但是如果你删除这个属性,它会变得更清晰(也尝试减小数字大小)。
fig = plt.figure(figsize=(3, 3))
plt.imshow(digits['images'][66], cmap="gray", interpolation='none')
plt.show()
在 scikit-learn 中,我们可以构建一个简单的分类器,训练它,然后使用它来预测图像的数字,只需使用四行代码:
from sklearn import svm
classifier = svm.SVC(gamma=0.001)
classifier.fit(digits.data, digits.target)
predicted = classifier.predict(digits.data)
第一行只是导入支持向量机模型,这是一种流行的机器学习方法。
第二行构建“空白”分类器,gamma
设置为 0.001。
第三行使用数据来训练模型。 在这一行(这是该代码的大部分“工作”)中,调整 SVM 模型的内部状态来拟合训练数据。 我们还传递digits.data
,因为这是一个展开的数组,是该算法的可接受输入。
最后,最后一行使用这个训练好的分类器来预测某些数据的类,在这种情况下再次是原始数据集。
要了解这是多么准确,我们可以使用 NumPy 计算准确度:
import numpy as np
print(np.mean(digits.target == predicted))
结果非常令人印象深刻(近乎完美),但这些有点误导。 在数据挖掘中,你永远不应该在用于训练的相同数据上评估你的模型。 潜在的问题被称为“过拟合”,其中模型准确地学习了训练数据所需的内容,但是无法很好地预测新的没见过的数据。 为解决这个问题,我们需要拆分我们的训练和测试数据:
from sklearn.cross_validation import train_test_split
X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target)
结果仍然非常好,大约 98%,但这个数据集在数据挖掘中是众所周知的,其特征已有详细记录。 无论如何,我们现在知道我们要做什么,让我们在 TFLearn 中实现它!
TFLearn
TensorFlow Learn 接口距离 scikit-learn 的接口只有一小步之遥:
from tensorflow.contrib import learn
n_classes = len(set(y_train))
classifier = learn.LinearClassifier(feature_columns=[tf.contrib.layers.real_valued_column("", dimension=X_train.shape[1])],
n_classes=n_classes)
classifier.fit(X_train, y_train, steps=10)
y_pred = classifier.predict(X_test)
唯一真正的变化是import
语句和模型,它来自不同的可用算法列表。 一个区别是分类器需要知道它将预测多少个类,可以使用len(set(y_train))
找到,或者换句话说,训练数据中有多少个唯一值。
另一个区别是,需要告知分类器预期的特征类型。 对于这个例子,我们有真正重要的连续特征,所以我们可以简单地指定feature_columns
值(它需要在列表中)。 如果你使用类别特征,则需要单独说明。 这方面的更多信息,请查看 TFLearn 示例的文档。
可以像以前一样评估结果,来计算准确性,但 scikit-learn 有 classification_report,它提供了更深入的了解:
from sklearn import metrics
print(metrics.classification_report(y_true=y_test, y_pred=y_pred))
结果显示每个类的召回率和精度,以及总体值和 f 度量。这些分数比准确性更可靠,更多信息请参阅维基百科上的此页面。
这是 TFLearn 的高级概述。你可以定义自定义分类器,你将在练习 3 中看到它们,并将分类器组合到流水线中(对此的支持很小,但正在改进)。该软件包有可能成为工业和学术界广泛使用的数据挖掘软件包。
1)将分类器更改为DNNClassifier
并重新运行。随意告诉所有朋友你现在使用深度学习来做数据分析。
2)DNNClassifier
的默认参数是好的,但不完美。尝试更改参数来获得更高的分数。
3)从 TFLearn 的文档中查看此示例并下载 CIFAR 10 数据集。构建一个使用卷积神经网络预测图像的分类器。你可以使用此代码加载数据:
def load_cifar(file):
import pickle
import numpy as np
with open(file, 'rb') as inf:
cifar = pickle.load(inf, encoding='latin1')
data = cifar['data'].reshape((10000, 3, 32, 32))
data = np.rollaxis(data, 3, 1)
data = np.rollaxis(data, 3, 1)
y = np.array(cifar['labels'])
# 最开始只需 2 和 9
# 如果要构建大型模型,请删除这些行
mask = (y == 2) | (y == 9)
data = data[mask]
y = y[mask]
return data, y
四、分布式
自定义函数
Conway 的生命游戏是一个有趣的计算机科学模拟,它在地图上发生,有许多正方形的单元格,就像棋盘一样。 模拟以特定的时间步骤进行,并且板上的每个单元可以是 1(生存)或 0(死亡)。 经过特定的时间步骤后,每个单元格都处于生存状态或死亡状态:
- 如果细胞是活着的,但是有一个或零个邻居,它会由于“人口不足”而“死亡”。
- 如果细胞存活并且有两个或三个邻居,它就会活着。
- 如果细胞有三个以上的邻居,它就会因人口过多而死亡。
- 任何有三个邻居的死细胞都会再生。
虽然这些规则似乎非常病态,但实际的模拟非常简单,创造了非常有趣的模式。 我们将创建一个 TensorFlow 程序来管理 Conway 的生命游戏,并在此过程中了解自定义py_func
函数,并生成如下动画:
http://learningtensorflow.com/images/game.mp4
首先,让我们生成地图。 这是非常基本的,因为它只是一个 0 和 1 的矩阵。 我们随机生成初始地图,每次运行时都会提供不同的地图:
import tensorflow as tf
from matplotlib import pyplot as plt
shape = (50, 50)
initial_board = tf.random_uniform(shape, minval=0, maxval=2, dtype=tf.int32)
with tf.Session() as session:
X = session.run(initial_board)
fig = plt.figure()
plot = plt.imshow(X, cmap='Greys', interpolation='nearest')
plt.show()
我们生成一个随机选择的 0 和 1 的initial_board
,然后运行它来获取值。 然后我们使用matplotlib.pyplot
来显示它,使用imshow
函数,它基本上只根据一些cmap
颜色方案绘制矩阵中的值。 在这种情况下,使用'Greys'
会产生黑白矩阵,以及我们生命游戏的单个初始起点:
更新地图的状态
由于生命游戏的地图状态表示为矩阵,因此使用矩阵运算符更新它是有意义的。 这应该提供一种快速方法,更新给定时间点的状态。
非常有才华的 Jake VanderPlas 在使用 SciPy 和 NumPy 更新生命游戏中的特定状态方面做了一些出色的工作。 他的写作值得一读,可以在[这里]找到。 如果你对以下代码的工作原理感兴趣,我建议你阅读 Jake 的说明。 简而言之,convolve2d
那行标识每个单元有多少邻居(这是计算机视觉中的常见操作符)。 我稍微更新了代码以减少行数,请参阅下面的更新后的函数:
def update_board(X):
# Check out the details at: https://jakevdp.github.io/blog/2013/08/07/conways-game-of-life/
# Compute number of neighbours,
N = convolve2d(X, np.ones((3, 3)), mode='same', boundary='wrap') - X
# Apply rules of the game
X = (N == 3) | (X & (N == 2))
return X
update_board
函数是 NumPy 数组的函数。 它不适用于张量,迄今为止,在 TensorFlow 中没有一种好方法可以做到这一点(虽然你可以使用现有的工具自己编写它,它不是直截了当的)。
在 TensorFlow 的 0.7 版本中,添加了一个新函数py_func
,它接受 python 函数并将其转换为 TensorFlow 中的节点。
在撰写本文时(3 月 22 日),0.6 是正式版,并且它没有py_func
。 我建议按照 TensorFlow 的 Github 页面上的说明为你的系统安装每晚构建。 例如,对于 Ubuntu 用户,你下载相关的 wheel 文件(python 安装文件)并安装它:
python -m wheel install --force ~/Downloads/tensorflow-0.7.1-cp34-cp34m-linux_x86_64.whl
请记住,你需要正确激活 TensorFlow 源(如果你愿意的话)。
最终结果应该是你安装了 TensorFlow 的 0.7 或更高版本。 你可以通过在终端中运行此代码来检查:
python -c "import tensorflow as tf; print(tf.__version__)"
结果将是版本号,在编写时为 0.7.1。
在代码上:
board = tf.placeholder(tf.int32, shape=shape, name='board')
board_update = tf.py_func(update_board, [board], [tf.int32])
从这里开始,你可以像往常一样,对张量操作节点(即board_update
)运行初始地图。 要记住的一点是,运行board_update
的结果是一个矩阵列表,即使我们的函数只定义了一个返回值。 我们通过在行尾添加[0]
来获取第一个结果,我们更新的地图存储在X
中。
with tf.Session() as session:
initial_board_values = session.run(initial_board)
X = session.run(board_update, feed_dict={board: initial_board_values})[0]
所得值X
是初始配置之后更新的地图。 它看起来很像一个初始随机地图,但我们从未显示初始的(虽然你可以更新代码来绘制两个值)
循环
这是事情变得非常有趣的地方,尽管从 TensorFlow 的角度来看,我们已经为本节做了很多努力。 我们可以使用matplotlib
来显示和动画,因此显示时间步骤中的模拟状态,就像我们的原始 GIF 一样。 matplotlib
动画的复杂性有点棘手,但是你创建一个更新并返回绘图的函数,并使用该函数调用动画代码:
import matplotlib.animation as animation
def game_of_life(*args):
X = session.run(board_update, feed_dict={board: X})[0]
plot.set_array(X)
return plot,
ani = animation.FuncAnimation(fig, game_of_life, interval=200, blit=True)
plt.show()
提示:你需要从早期代码中删除
plt.show()
才能运行!
我将把拼图的各个部分作为练习留给读者,但最终结果将是一个窗口出现,游戏状态每 200 毫秒更新一次。
如果你实现了,请给我们发消息!
1)获取完整的代码示例,使用matplotlib
和 TensorFlow 生成游戏的动画
2)康威的生命游戏已被广泛研究,并有许多有趣的模式。 创建一个从文件加载模式的函数,并使用它们而不是随机地图。 我建议从 Gosper 的滑翔枪开始。
3)生命游戏的一个问题(特征?)是地图可以重复,导致循环永远不会停止。 编写一些跟踪之前游戏状态的代码,并在游戏状态重复时停止循环。
使用 GPU
GPU(图形处理单元)是大多数现代计算机的组件,旨在执行 3D 图形所需的计算。 它们最常见的用途是为视频游戏执行这些操作,计算多边形向用户显示游戏。 总的来说,GPU 基本上是一大批小型处理器,执行高度并行化的计算。 你现在基本上有了一个迷你超级计算机!
注意:不是真正的超级计算机,但在许多方面有些相似。
虽然 GPU 中的每个“CPU”都很慢,但它们中有很多并且它们专门用于数字处理。 这意味着 GPU 可以同时执行许多简单的数字处理任务。 幸运的是,这正是许多机器学习算法需要做的事情。
没有 GPU 吗?
大多数现代(最近10年)的计算机都有某种形式的 GPU,即使它内置在你的主板上。 出于本教程的目的,这就足够了。
你需要知道你有什么类型的显卡。 Windows 用户可以遵循这些说明,其他系统的用户需要查阅他们系统的文档。
非 N 卡用户
虽然其他显卡可能是受支持的,但本教程仅在最近的 NVidia 显卡上进行测试。 如果你的显卡属于不同类型,我建议你寻找 NVidia 显卡来学习,购买或者借用。 如果这对你来说真的很难,请联系你当地的大学或学校,看看他们是否可以提供帮助。 如果你仍然遇到问题,请随意阅读以及使用标准 CPU 进行操作。 你将能够在以后迁移所学的东西。
安装 GPU 版的 TensorFlow
如果你之前没有安装支持 GPU 的 TensorFlow,那么我们首先需要这样做。我们在第 1 课中没有说明,所以如果你没有按照你的方式启用 GPU 支持,那就是没有了。
我建议你为此创建一个新的 Anaconda 环境,而不是尝试更新以前的环境。
在你开始之前
前往 TensorFlow 官方安装说明,并遵循 Anaconda 安装说明。这与我们在第 1 课中所做的主要区别在于,你需要为你的系统启用支持 GPU 的 TensorFlow 版本。但是,在将 TensorFlow 安装到此环境之前,你需要使用 CUDA 和 CuDNN,将计算机设置为启用 GPU 的。TensorFlow 官方文档逐步概述了这一点,但如果你尝试设置最近的 Ubuntu 安装,我推荐本教程。主要原因是,在撰写本文时(2016 年 7 月),尚未为最新的 Ubuntu 版本构建 CUDA,这意味着该过程更加手动。
使用你的 GPU
真的很简单。 至少是字面上。 只需将这个:
# 起步操作
with tf.Session() as sess:
# 运行你的代码
改为这个:
with tf.device("/gpu:0"):
# 起步操作
with tf.Session() as sess:
# 运行你的代码
这个新行将创建一个新的上下文管理器,告诉 TensorFlow 在 GPU 上执行这些操作。
我们来看一个具体的例子。 下面的代码创建一个随机矩阵,其大小在命令行中提供。 我们可以使用命令行选项在 CPU 或 GPU 上运行代码:
import sys
import numpy as np
import tensorflow as tf
from datetime import datetime
device_name = sys.argv[1] # Choose device from cmd line. Options: gpu or cpu
shape = (int(sys.argv[2]), int(sys.argv[2]))
if device_name == "gpu":
device_name = "/gpu:0"
else:
device_name = "/cpu:0"
with tf.device(device_name):
random_matrix = tf.random_uniform(shape=shape, minval=0, maxval=1)
dot_operation = tf.matmul(random_matrix, tf.transpose(random_matrix))
sum_operation = tf.reduce_sum(dot_operation)
startTime = datetime.now()
with tf.Session(config=tf.ConfigProto(log_device_placement=True)) as session:
result = session.run(sum_operation)
print(result)
# 很难在终端上看到具有大量输出的结果 - 添加一些换行符以提高可读性。
print("\n" * 5)
print("Shape:", shape, "Device:", device_name)
print("Time taken:", datetime.now() - startTime)
print("\n" * 5)
你可以在命令行运行此命令:
python matmul.py gpu 1500
这将使用 GPU 和大小为 1500 平方的矩阵。 使用以下命令在 CPU 上执行相同的操作:
python matmul.py cpu 1500
与普通的 TensorFlow 脚本相比,在运行支持 GPU 的代码时,你会注意到的第一件事是输出大幅增加。 这是我的计算机在打印出任何操作结果之前打印出来的内容。
I tensorflow/stream_executor/dso_loader.cc:108] successfully opened CUDA library libcublas.so locally
I tensorflow/stream_executor/dso_loader.cc:108] successfully opened CUDA library libcudnn.so.5 locally
I tensorflow/stream_executor/dso_loader.cc:108] successfully opened CUDA library libcufft.so locally
I tensorflow/stream_executor/dso_loader.cc:108] successfully opened CUDA library libcuda.so.1 locally
I tensorflow/stream_executor/dso_loader.cc:108] successfully opened CUDA library libcurand.so locally
I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:925] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
I tensorflow/core/common_runtime/gpu/gpu_init.cc:102] Found device 0 with properties:
name: GeForce GTX 950M
major: 5 minor: 0 memoryClockRate (GHz) 1.124
pciBusID 0000:01:00.0
Total memory: 3.95GiB
Free memory: 3.50GiB
I tensorflow/core/common_runtime/gpu/gpu_init.cc:126] DMA: 0
I tensorflow/core/common_runtime/gpu/gpu_init.cc:136] 0: Y
I tensorflow/core/common_runtime/gpu/gpu_device.cc:838] Creating TensorFlow device (/gpu:0) -> (device: 0, name: GeForce GTX 950M, pci bus id: 0000:01:00.0)
如果你的代码没有产生与此类似的输出,那么你没有运行支持 GPU 的 Tensorflow。或者,如果你收到ImportError: libcudart.so.7.5: cannot open shared object file: No such file or directory
这样的错误,那么你还没有正确安装 CUDA 库。在这种情况下,你需要返回,遵循指南来在你的系统上安装 CUDA。
尝试在 CPU 和 GPU 上运行上面的代码,慢慢增加数量。从 1500 开始,然后尝试 3000,然后是 4500,依此类推。你会发现 CPU 开始需要相当长的时间,而 GPU 在这个操作中真的非常快!
如果你有多个 GPU,则可以使用其中任何一个。 GPU 是从零索引的 - 上面的代码访问第一个 GPU。将设备更改为gpu:1
使用第二个 GPU,依此类推。你还可以将部分计算发送到一个 GPU,然后是另一个 GPU。此外,你可以以类似的方式访问计算机的 CPU - 只需使用cpu:0
(或其他数字)。
我应该把什么样的操作发送给 GPU?
通常,如果该过程的步骤可以描述,例如“执行该数学运算数千次”,则将其发送到 GPU。 示例包括矩阵乘法和计算矩阵的逆。 实际上,许多基本矩阵运算是 GPU 的拿手好戏。 作为一个过于宽泛和简单的规则,应该在 CPU 上执行其他操作。
更换设备和使用 GPU 还需要付出代价。 GPU 无法直接访问你计算机的其余部分(当然,除了显示器)。 因此,如果你在 GPU 上运行命令,则需要先将所有数据复制到 GPU,然后执行操作,然后将结果复制回计算机的主存。 TensorFlow 在背后处理这个问题,因此代码很简单,但仍需要执行工作。
并非所有操作都可以在 GPU 上完成。 如果你收到以下错误,你正在尝试执行无法在 GPU 上执行的操作:
Cannot assign a device to node 'PyFunc': Could not satisfy explicit device specification '/device:GPU:1' because no devices matching that specification are registered in this process;
如果是这种情况,你可以手动将设备更改为 CPU 来执行此函数,或者设置 TensorFlow,以便在这种情况下自动更改设备。 为此,请在配置中设置allow_soft_placement
为True
,作为创建会话的一部分。 原型看起来像这样:
with tf.Session(config=tf.ConfigProto(allow_soft_placement=True)):
# 在这里运行你的图
我还建议在使用 GPU 时记录设备的放置,这样可以轻松调试与不同设备使用情况相关的问题。 这会将设备的使用情况打印到日志中,从而可以查看设备何时更改以及它对图的影响。
with tf.Session(config=tf.ConfigProto(allow_soft_placement=True, log_device_placement=True)):
# 在这里运行你的图
1)设置你的计算机,将 GPU 用于 TensorFlow(或者如果你最近没有 GPU,就借一台)。
2)尝试在 GPU 上运行以前的练习的解决方案。 哪些操作可以在 GPU 上执行,哪些不可以?
3)构建一个在 GPU 和 CPU 上都使用操作的程序。 使用我们在第 5 课中看到的性能分析代码,来估计向 GPU 发送数据和从 GPU 获取数据的影响。
4)把你的代码发给我! 我很乐意看到你的代码示例,如何使用 Tensorflow,以及你找到的任何技巧。
分布式计算
TensorFlow 支持分布式计算,允许在不同的进程上计算图的部分,这些进程可能位于完全不同的服务器上! 此外,这可用于将计算分发到具有强大 GPU 的服务器,并在具有更多内存的服务器上完成其他计算,依此类推。 虽然接口有点棘手,所以让我们从头开始构建。
这是我们的第一个脚本,我们将在单个进程上运行,然后转移到多个进程。
import tensorflow as tf
x = tf.constant(2)
y1 = x + 300
y2 = x - 66
y = y1 + y2
with tf.Session() as sess:
result = sess.run(y)
print(result)
到现在为止,这个脚本不应该特别吓到你。 我们有一个常数和三个基本方程。 结果(238)最后打印出来。
TensorFlow 有点像服务器 - 客户端模型。 这个想法是你创造了一大堆能够完成繁重任务的工作器。 然后,你可以在其中一个工作器上创建会话,它将计算图,可能将其中的一部分分发到服务器上的其他集群。
为此,主工作器,主机,需要了解其他工作器。 这是通过创建ClusterSpec
来完成的,你需要将其传递给所有工作器。 ClusterSpec
使用字典构建,其中键是“作业名称”,每个任务包含许多工作器。
下面是这个图表看上去的样子。
以下代码创建一个ClusterSpect
,其作业名称为local
,和两个工作器进程。
请注意,这些代码不会启动这些进程,只会创建一个将启动它们的引用。
import tensorflow as tf
cluster = tf.train.ClusterSpec({"local": ["localhost:2222", "localhost:2223"]})
接下来,我们启动进程。 为此,我们绘制其中一个工作器的图,并启动它:
server = tf.train.Server(cluster, job_name="local", task_index=1)
上面的代码在local
作业下启动localhost:2223
工作器。
下面是一个脚本,你可以从命令行运行来启动这两个进程。 将代码在你的计算机上保存为create_worker.py
并运行python create_worker.py 0
然后运行python create_worker.py 1
。你需要单独的终端来执行此操作,因为脚本不会自己停止(他们正在等待指令)。
# 从命令行获取任务编号
import sys
task_number = int(sys.argv[1])
import tensorflow as tf
cluster = tf.train.ClusterSpec({"local": ["localhost:2222", "localhost:2223"]})
server = tf.train.Server(cluster, job_name="local", task_index=task_number)
print("Starting server #{}".format(task_number))
server.start()
server.join()
执行此操作后,你将发现服务器运行在两个终端上。 我们准备分发!
“分发”作业的最简单方法是在其中一个进程上创建一个会话,然后在那里执行图。 只需将上面的session
行更改为:
with tf.Session("grpc://localhost:2222") as sess:
现在,这并没有真正分发,不足以将作业发送到该服务器。 TensorFlow 可以将进程分发到集群中的其他资源,但可能不会。 我们可以通过指定设备来强制执行此操作(就像我们在上一课中对 GPU 所做的那样):
import tensorflow as tf
cluster = tf.train.ClusterSpec({"local": ["localhost:2222", "localhost:2223"]})
x = tf.constant(2)
with tf.device("/job:local/task:1"):
y2 = x - 66
with tf.device("/job:local/task:0"):
y1 = x + 300
y = y1 + y2
with tf.Session("grpc://localhost:2222") as sess:
result = sess.run(y)
print(result)
现在我们正在分发! 这可以通过根据名称和任务编号,为工作器分配任务来实现。 格式为:
/job:JOB_NAME/task:TASK_NUMBER
通过多个作业(即识别具有大型 GPU 的计算机),我们可以以多种不同方式分发进程。
映射和归约
MapReduce 是执行大型操作的流行范式。 它由两个主要步骤组成(虽然在实践中还有一些步骤)。
第一步称为映射,意思是“获取列表,并将函数应用于每个元素”。 你可以在普通的 python 中执行这样的映射:
def myfunction(x):
return x + 5
map_result = map(myfunction, [1, 2, 3])
print(list(map_result))
第二步是归约,这意味着“获取列表,并使用函数将它们组合”。 常见的归约操作是求和 - 即“获取数字列表并通过将它们全部加起来组合它们”,这可以通过创建相加两个数字的函数来执行。 reduce
的原理是获取列表的前两个值,执行函数,获取结果,然后使用结果和下一个值执行函数。 总之,我们将前两个数字相加,取结果,加上下一个数字,依此类推,直到我们到达列表的末尾。 同样,reduce
是普通 python 的一部分(尽管它不是分布式的):
from functools import reduce
def add(a, b):
return a + b
print(reduce(add, [1, 2, 3]))
译者注:原作者这里的话并不值得推荐,比如
for
你更应该使用reduce
,因为它更安全。
回到分布式 TensorFlow,执行map
和reduce
操作是许多非平凡程序的关键构建块。 例如,集成学习可以将单独的机器学习模型发送给多个工作器,然后组合分类结果来形成最终结果。另一个例子是一个进程。
这是我们将分发的另一个基本脚本:
import numpy as np
import tensorflow as tf
x = tf.placeholder(tf.float32, 100)
mean = tf.reduce_mean(x)
with tf.Session() as sess:
result = sess.run(mean, feed_dict={x: np.random.random(100)})
print(result)
import numpy as np
import tensorflow as tf
x = tf.placeholder(tf.float32, 100)
mean = tf.reduce_mean(x)
with tf.Session() as sess:
result = sess.run(mean, feed_dict={x: np.random.random(100)})
print(result)
转换为分布式版本只是对先前转换的更改:
import numpy as np
import tensorflow as tf
cluster = tf.train.ClusterSpec({"local": ["localhost:2222", "localhost:2223"]})
x = tf.placeholder(tf.float32, 100)
with tf.device("/job:local/task:1"):
first_batch = tf.slice(x, [0], [50])
mean1 = tf.reduce_mean(first_batch)
with tf.device("/job:local/task:0"):
second_batch = tf.slice(x, [50], [-1])
mean2 = tf.reduce_mean(second_batch)
mean = (mean1 + mean2) / 2
with tf.Session("grpc://localhost:2222") as sess:
result = sess.run(mean, feed_dict={x: np.random.random(100)})
print(result)
如果你从映射和归约的角度来考虑它,你会发现分发计算更容易。 首先,“我怎样才能将这个问题分解成可以独立解决的子问题?” - 这就是你的映射。 第二,“我如何将答案结合起来来形成最终结果?” - 这就是你的归约。
在机器学习中,映射最常用的场景就是分割数据集。 线性模型和神经网络通常都非常合适,因为它们可以单独训练,然后再进行组合。
1)将ClusterSpec
中的local
更改为其他内容。 你还需要在脚本中进行哪些更改才能使其正常工作?
2)计算平均的脚本目前依赖于切片大小相同的事实。 尝试使用不同大小的切片并观察错误。 通过使用tf.size
和以下公式来组合切片的平均值来解决此问题:
overall_average = ((size_slice_1 * mean_slice_1) + (size_slice_2 * mean_slice_2) + ...) / total_size
3)你可以通过修改设备字符串来指定远程计算机上的设备。 例如,/job:local/task:0/gpu:0
会定位local
作业的 GPU。 创建一个使用远程 GPU 的作业。 如果你有备用的第二台计算机,请尝试通过网络执行此操作。