跳至主要内容

TensorFlow 初学者教程

学习如何使用 TensorFlow 构建神经网络,以及如何对其进行训练、评估与优化
· 15分钟 阅读

使用 AI 探索

graphic

深度学习是机器学习的一个子领域,它是一组受大脑结构与功能启发而来的算法。

TensorFlow 是 Google 创建的第二个机器学习框架,被用于设计、构建和训练深度学习模型。您可以用 TensorFlow 库进行数值计算,这本身看起来并不特别,但这些计算是通过数据流图来完成的。在这些图中,节点代表数学运算,边代表数据,通常是多维数据数组或张量,在这些边之间传递。

明白了吗?“TensorFlow”的名字正来源于神经网络对多维数据数组(张量)执行的操作!它从字面意义上就是张量的流动。关于张量,您目前只需了解这些,接下来的章节会更深入!

今天这篇面向初学者的 TensorFlow 教程将以互动的方式带您开展深度学习:

下载本教程的 notebook 请点击这里。

您也可以对这门课程感兴趣:Deep Learning in Python,DataCamp 的Keras 教程或R 版 keras 教程。

走近张量(Tensors)

要深入理解张量,具备一定的线性代数与向量微积分基础会很有帮助。您在引言中已读到,张量在 TensorFlow 中作为多维数据数组实现,但为了完全把握张量及其在机器学习中的用途,仍需要更多铺垫。

平面向量

在谈论平面向量之前,先快速回顾一下“向量”这个概念;向量是特殊类型的矩阵,即数字的矩形数组。由于向量是有序的数字集合,常被视为列矩阵:只有一列,若干行。换言之,您也可将向量看作赋予了方向的标量大小。

请记住:标量的例子如“5 米”或“60 米/秒”,而向量的例子如“向北 5 米”或“向东 60 米/秒”。两者的区别显然在于向量具有方向。不过,迄今为止您看到的这些例子,似乎与在机器学习问题中遇到的向量相去甚远。这很正常;数学向量的长度是纯数值:是绝对量。另一方面,方向是相对量:相对于某个参考方向来测量,单位为弧度或角度。通常假设方向为正,并相对于参考方向逆时针旋转。

vector

在可视化上,您当然会像上图那样用箭头表示向量。这意味着您也可以把向量看作既有方向又有长度的箭头。箭头的尖端指示方向,而箭身长度表示大小。

那么,平面向量是什么呢?

平面向量是张量最直观的设定。它们与您上面见到的普通向量很相似,唯一的区别是它们处在一个向量空间中。为更好理解这一点,让我们从例子出发:假设您有一个 2 X 1 的向量。这意味着该向量属于成对出现的实数集合。换言之,它们属于二维空间。在这种情况下,您可以在坐标平面 (x,y) 上用箭头或射线表示向量。

在标准位置(向量的终点位于原点 (0,0))的坐标平面上,您可以通过查看向量的第一行得到 x 坐标,而第二行给出 y 坐标。当然,并不总需要保持这种标准位置:向量可以在平面内沿自身平行移动而不发生变化。

注意,类似地,对于大小为 3 X 1 的向量,您讨论的是三维空间。您可以将其表示为三维图形,箭头指向向量空间中的位置:它们绘制在标准的 x、y 和 z 轴上。

拥有这些向量并在坐标平面上表示它们固然很好,但本质上,您之所以需要向量,是为了对其进行运算。帮助您做到这一点的一种方法是将向量表示为基或单位向量。

单位向量是模为 1 的向量。您经常会看到小写字母上带“^”符号来表示单位向量。若要将一个二维或三维向量表示为两个或三个正交分量(如 x 轴和 y 轴,或再加上 z 轴)的和,单位向量将非常方便。

当您谈到将一个向量(例如)表示为分量之和时,实际上说的是分量向量,即两个或多个向量,其和等于给定向量。

提示:观看这个视频,它借助日常家居物品来解释张量!

张量

除了平面向量,共变向量与线性算子也是另外两种情形,这三者有一个共同点:它们都是张量的特例。您还记得上一节中如何将向量刻画为赋予了方向的标量大小。那么,张量就是对可能具有大小并有多个方向的物理实体的数学表示。

同样地,就像在三维空间中用一个数字表示标量,用三个数字的序列表示向量一样,张量可以在三维空间中用一个包含 3^R 个数字的数组来表示。

该表示中的“R”代表张量的秩(rank):这意味着在三维空间中,二阶张量可由 3 的 2 次方,即 9 个数字表示。在 N 维空间中,标量仍只需一个数字,向量需要 N 个数字,而张量需要 N^R 个数字。这也解释了为何常说标量是 0 阶张量:因为它们没有方向,可以用一个数字表示。

在此基础上,识别并区分标量、向量和张量就相对容易了:标量可用单个数字表示,向量由有序数字集合表示,张量由数字数组表示。

张量的独特之处在于其分量与基向量的组合:基向量在不同参考系之间以一种方式变换,而分量则以恰好相反的方式变换,从而保持分量与基向量的组合不变。

安装 TensorFlow

现在您对 TensorFlow 有了更多了解,是时候开始安装库了。需要说明的是,TensorFlow 为 Python、C++、Haskell、Java、Go、Rust 提供 API,另有 R 的第三方包 tensorflow。

提示:如果您想进一步了解 R 中的深度学习包,可查看 DataCamp 的keras:R 语言深度学习教程。

在本教程中,您将下载一个版本的 TensorFlow,以便用 Python 为深度学习项目编写代码。在 TensorFlow 安装页面上,您会看到使用 virtualenv、pip、Docker 安装 TensorFlow 的常见方法与最新说明,最后还列出了一些在个人电脑上安装 TensorFlow 的其它方式。

注意 如果您在 Windows 上工作,也可以通过 Conda 安装 TensorFlow。但由于该安装方式由社区支持,最好查阅官方安装指南。

完成安装流程后,是时候通过在工作区中以别名 tf 导入它,来再次确认 TensorFlow 是否正确安装:

import tensorflow as tf

注意,上述代码行所用的别名是一种“约定”——一方面有助于与其他在数据科学项目中使用 TensorFlow 的开发者保持一致,另一方面也与开源的 TensorFlow 项目保持一致性。

TensorFlow 入门:基础

通常您会把 TensorFlow 程序写好后“成块”运行;当您使用 Python 时,这乍看有点矛盾。不过,如果您愿意,也可以使用 TensorFlow 的交互式会话,这样就能以更互动的方式使用该库。这对于习惯使用 IPython 的人尤其方便。

在本教程中,您将专注于第二种方式:这将帮助您快速上手在 TensorFlow 中进行深度学习。但在进一步深入之前,先尝试一些小示例,再开始“重活”。

首先,像上一节所示那样,以别名 tf 导入 tensorflow 库。然后初始化两个实际上是常量的变量。向 constant() 函数传入一个包含四个数字的数组。

注意,您也可以传入一个整数,但更多时候您会使用数组。正如引言中所述,张量就是关于数组的!所以请确保传入一个数组 :) 接下来,使用 multiply() 来将两个变量相乘。将结果存储在 result 变量中。最后,通过 print() 函数打印 result。

# Import `tensorflow`
import tensorflow as tf

# Initialize two constants
x1 = tf.constant([1,2,3,4])
x2 = tf.constant([5,6,7,8])

# Multiply
result = tf.multiply(x1, x2)

# Print the result
print(result)

注意,在上面的 DataCamp Light 代码块中,您定义的是常量。不过,您还可以使用另外两种类型的值,即占位符,它们是未赋值的,将在您运行会话时由会话初始化。顾名思义,它仅是一个占位的张量,每次会话运行时都会被喂入;还有 变量,即可以改变的值。正如您可能已经明白的,常量则是不会改变的值。

这些代码行的结果是在计算图中的一个抽象张量。然而,与预期相反,result 实际上并没有被计算。它只是定义了模型,但没有执行任何过程来计算结果。您可以从打印输出看到:并没有出现您期望的结果(即 30)。这意味着 TensorFlow 采用惰性求值!

不过,如果您想看到结果,就需要在交互式会话中运行这段代码。可以通过几种方式实现,正如下方 DataCamp Light 代码块所示:

# Import `tensorflow` 
import tensorflow as tf

# Initialize two constants
x1 = tf.constant([1,2,3,4])
x2 = tf.constant([5,6,7,8])

# Multiply
result = tf.multiply(x1, x2)

# Intialize the Session
sess = tf.Session()

# Print the result
print(sess.run(result))

# Close the session
sess.close()

注意,您也可以使用以下代码来启动交互式会话,运行 result,并在打印 output 后自动关闭会话:

# Import `tensorflow`
import tensorflow as tf

# Initialize two constants
x1 = tf.constant([1,2,3,4])
x2 = tf.constant([5,6,7,8])

# Multiply
result = tf.multiply(x1, x2)

# Initialize Session and run `result`
with tf.Session() as sess:
  output = sess.run(result)
  print(output)

在以上代码块中,您只是定义了一个默认会话,但也要知道,您同样可以传入可选项。例如,您可以指定 config 参数,再使用 ConfigProto 协议缓冲区为会话添加配置项。

例如,如果您添加

config=tf.ConfigProto(log_device_placement=True)

到会话中,将确保记录分配给某个操作的 GPU 或 CPU 设备。您将获得该会话中每个操作使用了哪些设备的信息。例如,当您在设备放置上使用软约束时,也可以使用如下配置:

config=tf.ConfigProto(allow_soft_placement=True)

现在,您已经安装并在工作区中导入了 TensorFlow,也过了一遍使用该包的基础,是时候暂时把它放一边,把注意力转向数据。像往常一样,在开始构建神经网络之前,您会先花时间去探索和理解数据。

比利时交通标志:背景

尽管交通这个主题大家都很熟悉,但在开始之前,简要过一遍该数据集中包含的观测内容也无妨,以确认您理解无误。本节的本质是帮助您掌握继续学习本教程所需的领域知识。

当然,因为我是比利时人,我也会穿插一些趣闻轶事 :)

  • 比利时的交通标志通常使用荷兰语和法语。这一点需要了解,但对于您将要使用的数据集并不重要!
  • 比利时的交通标志分为六大类:警告标志、优先权标志、禁止标志、强制标志、与道路停车及临时停靠相关的标志,最后是指示性标志。
  • 截至 2017 年 1 月 1 日,比利时道路上移除了超过 30,000 个交通标志。这些都是与车速相关的禁止性标志。
  • 谈到移除标志,比利时(以及更广泛的整个欧盟)一直在讨论交通标志过度设置的问题。

加载并探索数据

现在您已经获取了更多背景信息,是时候在这里下载数据集了。您应当获取列在“BelgiumTS for Classification (cropped images)”旁的两个 zip 文件,名为“BelgiumTSC_Training”和“BelgiumTSC_Testing”。

提示:下载完成(或在完成本教程后下载)后,看看数据的文件夹结构!您会发现测试和训练数据文件夹各包含 61 个子文件夹,这些子文件夹对应本教程中用于分类的 62 种交通标志类型。此外,您会发现文件扩展名为 .ppm(Portable Pixmap Format)。您下载的是交通标志的图像!

让我们开始将数据导入工作区。先从用户自定义函数(UDF)load_data() 下方的代码行开始:

  • 首先设置 ROOT_PATH。这是您放置训练与测试数据目录的路径。
  • 接着,使用 join() 函数将具体路径拼接到 ROOT_PATH。将这两个具体路径分别存储到 train_data_directory 和 test_data_directory。
  • 之后,您就可以调用 load_data() 函数,并将 train_data_directory 传入。
  • 现在,load_data() 函数本身首先会收集 train_data_directory 中的所有子目录;它通过列表推导完成,这是构造列表的很自然方式——基本逻辑是:如果在 train_data_directory 中找到某样东西,就再检查它是否是目录,若是,则将其加入列表。请记住,每个子目录代表一个标签。
  • 接着,您需要遍历这些子目录。先初始化两个列表 labels 和 images。然后,收集这些子目录的路径以及存放于这些子目录中的图像文件名。随后,使用 append() 将数据加入这两个列表。
def load_data(data_directory):
    directories = [d for d in os.listdir(data_directory) 
                   if os.path.isdir(os.path.join(data_directory, d))]
    labels = []
    images = []
    for d in directories:
        label_directory = os.path.join(data_directory, d)
        file_names = [os.path.join(label_directory, f) 
                      for f in os.listdir(label_directory) 
                      if f.endswith(".ppm")]
        for f in file_names:
            images.append(skimage.data.imread(f))
            labels.append(int(d))
    return images, labels

ROOT_PATH = "/your/root/path"
train_data_directory = os.path.join(ROOT_PATH, "TrafficSigns/Training")
test_data_directory = os.path.join(ROOT_PATH, "TrafficSigns/Testing")

images, labels = load_data(train_data_directory)

注意,在上述代码中,训练与测试数据分别位于名为“Training”和“Testing”的文件夹中,二者均是另一个目录“TrafficSigns”的子目录。在本地机器上,可能是类似“/Users/Name/Downloads/TrafficSigns”的路径,下面再有“Training”和“Testing”两个子文件夹。

提示:复习 Python 中如何编写函数,请参阅 DataCamp 的Python 函数教程。

交通标志统计

数据加载完成后,就该做一些数据检查了!您可以先借助 images 数组的 ndim 与 size 属性来做一个很简单的分析:

注意 images 和 labels 变量是列表,因此在您自己的工作区中,可能需要用 np.array() 将其转换为数组。这里已经替您完成了!

# Print the `images` dimensions
print(images.ndim)

# Print the number of `images`'s elements
print(images.size)

# Print the first instance of `images`
images[0]

注意,您打印出来的 images[0] 实际上是一张由数组嵌套数组表示的单张图像!一开始这可能不太直观,但当您在机器学习或深度学习中处理图像时,会逐渐习惯。

接下来,您也可以简单看看 labels,不过此时应该不会有太多意外:

# Print the `labels` dimensions
print(labels.ndim)

# Print the number of `labels`'s elements
print(labels.size)

# Count the number of labels
print(len(set(labels)))

这些数字已经能让您对导入是否成功以及数据的具体规模有些认识。乍看之下,一切都如预期执行,同时考虑到这里是数组中嵌套数组,您也会发现该数组的规模不小。

提示 试着为数组添加以下属性,以获取更多信息:通过 flags、itemsize 和 nbytes 属性,分别查看内存布局、单个数组元素的字节长度,以及数组元素总共消耗的字节数。您可以在上方的 DataCamp Light 代码块中的 IPython 控制台测试!

接下来,您还可以看看交通标志的分布:

# Import the `pyplot` module
import matplotlib.pyplot as plt 

# Make a histogram with 62 bins of the `labels` data
plt.hist(labels, 62)

# Show the plot
plt.show()

干得好!现在让我们更仔细看看您绘制的直方图!

distribution of traffic sign labels

您可以清楚地看到,数据集中并非所有类型的交通标志都同样常见。这是您在开始构建神经网络之前对数据进行处理时需要考虑的问题。

乍看之下,可以发现某些标签在数据集中出现得更频繁:22、32、38 和 61 尤其显眼。此刻先记在心里,下一节会更深入探究!

可视化交通标志

前面的小分析或检查已经让您对所处理的数据有了些概念,但当数据主要由图像组成时,探索数据的首要步骤应是对其进行可视化。

让我们看看一些随机交通标志:

  • 首先,按惯例将 matplotlib 包的 pyplot 模块以别名 plt 导入。
  • 然后,生成一个包含 4 个随机数的列表。这些数将用于从您刚刚检查过的 images 数组中选择交通标志图像。本例使用 300、2250、3650 和 4000。
  • 接着,针对该列表的长度(从 0 到 4),创建不带坐标轴的子图(这样就不会抢走注意力,您的关注点能集中在图像本身!)。在这些子图中,您将展示与索引 i 对应的特定 images 数组中的图像。在第一次循环中,将 300 传给 images[],第二次为 2250,依此类推。最后,调整子图以保证它们之间有足够的间距。
  • 最后一步是通过 show() 函数显示您的图!

开始吧:

# Import the `pyplot` module of `matplotlib`
import matplotlib.pyplot as plt

# Determine the (random) indexes of the images that you want to see 
traffic_signs = [300, 2250, 3650, 4000]

# Fill out the subplots with the random images that you defined 
for i in range(len(traffic_signs)):
    plt.subplot(1, 4, i+1)
    plt.axis('off')
    plt.imshow(images[traffic_signs[i]])
    plt.subplots_adjust(wspace=0.5)

plt.show()

从该数据集包含的 62 个标签您已猜到,这些标志彼此不同。

但您还注意到了什么?再仔细看看下面的图像:

traffic signs

这四张图像的尺寸并不相同!

您当然可以修改 traffic_signs 列表中的数字,更深入地验证这一观察。但不管怎样,这是一个重要发现,等您进一步处理数据以便将其输入神经网络时,需要将此考虑在内。

让我们通过打印子图中这些特定图像的形状、最小值和最大值,来验证“尺寸不同”的假设。

下面的代码与上面绘制图像的代码很相似,但不同之处在于,这里您将交替输出尺寸和图像,而不是只把图像并排绘制出来:

# Import `matplotlib`
import matplotlib.pyplot as plt

# Determine the (random) indexes of the images
traffic_signs = [300, 2250, 3650, 4000]

# Fill out the subplots with the random images and add shape, min and max values
for i in range(len(traffic_signs)):
    plt.subplot(1, 4, i+1)
    plt.axis('off')
    plt.imshow(images[traffic_signs[i]])
    plt.subplots_adjust(wspace=0.5)
    plt.show()
    print("shape: {0}, min: {1}, max: {2}".format(images[traffic_signs[i]].shape, 
                                                  images[traffic_signs[i]].min(), 
                                                  images[traffic_signs[i]].max()))

注意,您在字符串 "shape: {0}, min: {1}, max: {2}" 上使用 format() 方法来填入您定义的参数 {0}、{1} 和 {2}。

traffic signs 2

在已查看单张图像之后,您或许还想回顾一下在数据探索最初步骤中打印的直方图;您可以很容易地通过绘制 62 个类别的概览图,并为每个类别展示一张对应的图像来实现:

# Import the `pyplot` module as `plt`
import matplotlib.pyplot as plt 

# Get the unique labels 
unique_labels = set(labels)

# Initialize the figure
plt.figure(figsize=(15, 15))

# Set a counter
i = 1

# For each unique label,
for label in unique_labels:
    # You pick the first image for each label
    image = images[labels.index(label)]
    # Define 64 subplots 
    plt.subplot(8, 8, i)
    # Don't include axes
    plt.axis('off')
    # Add a title to each subplot 
    plt.title("Label {0} ({1})".format(label, labels.count(label)))
    # Add 1 to the counter
    i += 1
    # And you plot this first image 
    plt.imshow(image)
    
# Show the plot
plt.show()

注意,虽然定义了 64 个子图,但并不会全部显示图像(因为只有 62 个标签!)。还要注意,这里同样没有显示坐标轴,以确保读者的注意力不偏离主题:交通标志!

traffic signs 3

正如您在上面的直方图中大致推测的那样,标签 22、32、38 和 61 的交通标志明显更多。这个假设在该图中得到证实:您可以看到标签 22 有 375 个实例,标签 32 有 316 个,标签 38 有 285 个,最后标签 61 有 282 个。

您此时可能会问的一个更有意思的问题是:这些实例之间是否存在联系——也许它们都是指示性标志?

仔细一看:标签 22 和 32 是禁止标志,而 38 和 61 分别是指示性标志和优先权标志。这意味着这四者之间并无直接联系,除了一个事实:在数据集中占比较高的标志中,有一半属于禁止类。

explore datacamp's python course library banner

特征提取

现在您已经充分探索了数据,是时候“撸起袖子”了!先简要回顾一下您发现的内容,以确保在处理时不遗漏任何步骤:

  • 图像尺寸不一致;
  • 共有 62 个标签或目标值(标签从 0 开始到 61 结束);
  • 交通标志的分布相当不均衡;在数据集中占比较高的标志之间并无明显联系。

既然明确了需要改进的点,就可以开始以一种适合输入神经网络或其它模型的方式来处理数据。先从提取一些特征开始——您将对图像进行缩放,并将 images 数组中的图像转换为灰度图。之所以进行颜色转换,主要是因为在当前这样的分类问题中,颜色影响较小。而在检测任务中,颜色作用很大!因此那种情况下不需要做此转换!

图像缩放

为了解决图像尺寸不一致,您要对图像进行缩放;借助 skimage(Scikit-Image)这一图像处理算法集合库即可轻松完成。

本例中将用到 transform 模块,它提供了 resize() 函数;您会再次看到我们使用列表推导,将每张图像缩放至 28×28 像素。您再次看到列表的构造方式:对于 images 数组中的每张图像,都执行来自 skimage 库的变换操作。最后,将结果存入 images28 变量:

# Import the `transform` module from `skimage`
from skimage import transform 

# Rescale the images in the `images` array
images28 = [transform.resize(image, (28, 28)) for image in images]

是不是很简单?

注意,图像现在是四维的:如果将 images28 转为数组,并连同 shape 属性一起打印,您会看到 images28 的维度为 (4575, 28, 28, 3)。这些图像是 784 维的(因为每张图像为 28×28 像素)。

您可以复用上文用 traffic_signs 变量绘制 4 张随机图像的代码来检查缩放结果。只是别忘了将所有对 images 的引用改为 images28。

看看结果:

4 random images

注意,由于进行了缩放,您的 min 与 max 值也发生了变化;它们现在似乎都落在相同的范围内,这很好,因为您就不一定需要再对数据进行归一化了!

图像灰度化

正如本节开头所说,在回答分类问题时,图像中的颜色影响较小。这就是为什么您还要进行图像灰度化。

注意,不过,您也可以自行测试,如果不执行这一步,模型的最终结果会如何变化。

就像进行缩放那样,您仍可依赖 Scikit-Image 来帮忙;这次需要使用的是 color 模块中的 rgb2gray() 函数。

这将会很轻松!

不过,别忘了先把 images28 转回数组,因为 rgb2gray() 需要数组作为参数。

# Import `rgb2gray` from `skimage.color`
from skimage.color import rgb2gray

# Convert `images28` to an array
images28 = np.array(images28)

# Convert `images28` to grayscale
images28 = rgb2gray(images28)

通过绘制部分图像再次检查灰度化的结果;这里您同样可以复用并稍作修改代码,来展示调整后的图像:

import matplotlib.pyplot as plt

traffic_signs = [300, 2250, 3650, 4000]

for i in range(len(traffic_signs)):
    plt.subplot(1, 4, i+1)
    plt.axis('off')
    plt.imshow(images28[traffic_signs[i]], cmap="gray")
    plt.subplots_adjust(wspace=0.5)
    
# Show the plot
plt.show()

注意,确实需要指定颜色映射 cmap 为 "gray" 才能以灰度显示图像。这是因为 imshow() 默认会使用类似热力图的颜色映射。更多信息请见这里。

traffic signs 4

提示:既然您在本教程中多次复用该功能,可以考虑将其封装成函数 :)

这两步都很基础;您还可以在数据上尝试其他操作,如数据增强(旋转、模糊、平移、改变亮度……)。如果愿意,您也可以搭建一整套数据处理流水线来依次处理图像。

用 TensorFlow 开展深度学习

现在您已探索并处理好数据,接下来就用 TensorFlow 包来构建神经网络架构吧!

构建神经网络模型

就像您在 Keras 中可能做过的一样,接下来要逐层构建神经网络。

若您尚未这样做,请将 tensorflow 以常用别名 tf 导入工作区。然后使用 Graph() 初始化计算图,用于定义计算。注意,仅有计算图时并不会进行任何计算,因为它不持有任何值。它只定义了您稍后要运行的操作。

在这里,您通过 as_default() 设置一个默认上下文,它返回一个上下文管理器,将该计算图设为默认图。如果您想在同一进程中创建多个图,可使用该方法:若不显式创建新图,所有操作都会被添加到这个全局默认图中。

接下来,您就可以向图中添加操作。回想一下使用 Keras 的经历,您会先构建模型,然后在编译时定义损失函数、优化器和指标。而在使用 TensorFlow 时,这些都会在一个步骤中完成:

    • 首先,为输入和标签定义占位符,因为暂时还不会放入“真实”数据。记住,占位符是未赋值的,将在运行会话时由会话初始化。所以当您最终运行会话时,这些占位符会从 run() 函数传入的数据集中获得值!
    • 然后,构建网络。先使用 flatten() 将输入展平,使其形状从 [None, 28, 28](灰度图形状)变为 [None, 784] 的数组。
    • 展平输入后,构建一个全连接层,生成形状为 [None, 62] 的 logits。logits 是对前一层未缩放输出进行线性理解的函数输入。
    • 多层感知机搭好后,定义损失函数。损失函数的选择取决于当前任务:本例使用
sparse_softmax_cross_entropy_with_logits()
  • 它计算 logits 与标签之间的稀疏 softmax 交叉熵。换言之,它度量在类别互斥的离散分类任务中的概率误差。这意味着每个样本只属于一个类别。这里,一块交通标志只能有一个标签。记住,回归用于预测连续值,而分类用于预测离散值或数据点的类别。您再用 reduce_mean() 封装该函数,对张量各维元素取均值。
  • 您还需要定义训练优化器;常用的优化算法有随机梯度下降(SGD)、ADAM 和 RMSprop。根据所选算法,您需要调节某些参数,如学习率或动量。本例选择 ADAM 优化器,学习率设为 0.001。
  • 最后,在开始训练前初始化要执行的操作。
# Import `tensorflow` 
import tensorflow as tf 

# Initialize placeholders 
x = tf.placeholder(dtype = tf.float32, shape = [None, 28, 28])
y = tf.placeholder(dtype = tf.int32, shape = [None])

# Flatten the input data
images_flat = tf.contrib.layers.flatten(x)

# Fully connected layer 
logits = tf.contrib.layers.fully_connected(images_flat, 62, tf.nn.relu)

# Define a loss function
loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(labels = y, 
                                                                    logits = logits))
# Define an optimizer 
train_op = tf.train.AdamOptimizer(learning_rate=0.001).minimize(loss)

# Convert logits to label indexes
correct_pred = tf.argmax(logits, 1)

# Define an accuracy metric
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

您已成功用 TensorFlow 创建了第一个神经网络!

如果愿意,您也可以打印(大部分)变量的值,以便快速回顾或检查刚刚编写的代码:

print("images_flat: ", images_flat)
print("logits: ", logits)
print("loss: ", loss)
print("predicted_labels: ", correct_pred)

提示:如果看到类似“module 'pandas' has no attribute 'computation'”的错误,请尝试在命令行运行 pip install --upgrade dask 升级 dask 包。更多信息请见这条 StackOverflow 帖子。

运行神经网络

既然您已逐层搭建好模型,接下来就真正运行它!为此,您首先需要使用 Session() 初始化会话,并将您在上一节定义的 graph 传入。然后使用 run() 运行会话,并将上一节中定义的已初始化操作(init 变量)作为参数传入。

接下来,使用这个已初始化的会话开始 epoch 或训练循环。本例中选择 201,因为您希望记录最后一个 loss_value;在循环中,您通过会话运行训练优化器和在上一节中定义的损失(或准确率)指标。您还传入 feed_dict 参数,以将数据馈送给模型。每 10 个 epoch,都会打印一条日志,为您提供有关模型损失或成本的更多信息。

正如在 TensorFlow 基础部分所见,无需手动关闭会话;这一步会为您完成。不过,如果您想尝试不同的设置,且像下面的代码块一样将会话定义为 sess,则可能需要通过 sess.close() 手动关闭:

tf.set_random_seed(1234)
sess = tf.Session()

sess.run(tf.global_variables_initializer())

for i in range(201):
        print('EPOCH', i)
        _, accuracy_val = sess.run([train_op, accuracy], feed_dict={x: images28, y: labels})
        if i % 10 == 0:
            print("Loss: ", loss)
        print('DONE WITH EPOCH')

请记住,您也可以运行下面这段代码,但它会在结束后立即关闭会话,就像本教程开头所展示的那样:

tf.set_random_seed(1234)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    for i in range(201):
        _, loss_value = sess.run([train_op, loss], feed_dict={x: images28, y: labels})
        if i % 10 == 0:
            print("Loss: ", loss)

注意,这里使用 global_variables_initializer(),因为 initialize_all_variables() 已被弃用。

您已经成功训练了模型!并不难,对吧?

评估您的神经网络

您还没完全结束;仍需评估神经网络。在本例中,您可以先随机选取 10 张图像,通过比较预测标签与真实标签,粗略了解模型表现。

您可以先打印出来,但为何不直接使用 matplotlib 绘制交通标志本身来进行可视化对比呢?

 # Import `matplotlib`
import matplotlib.pyplot as plt
import random

# Pick 10 random images
sample_indexes = random.sample(range(len(images28)), 10)
sample_images = [images28[i] for i in sample_indexes]
sample_labels = [labels[i] for i in sample_indexes]

# Run the "correct_pred" operation
predicted = sess.run([correct_pred], feed_dict={x: sample_images})[0]
                        
# Print the real and predicted labels
print(sample_labels)
print(predicted)

# Display the predictions and the ground truth visually.
fig = plt.figure(figsize=(10, 10))
for i in range(len(sample_images)):
    truth = sample_labels[i]
    prediction = predicted[i]
    plt.subplot(5, 2,1+i)
    plt.axis('off')
    color='green' if truth == prediction else 'red'
    plt.text(40, 10, "Truth:        {0}\nPrediction: {1}".format(truth, prediction), 
             fontsize=12, color=color)
    plt.imshow(sample_images[i],  cmap="gray")

plt.show()

traffic signs 6

然而,只看随机图像并不能让您深入了解模型实际表现。因此,接下来加载测试数据。

注意,这里复用了您在教程开头定义的 load_data() 函数。

# Import `skimage`
from skimage import transform

# Load the test data
test_images, test_labels = load_data(test_data_directory)

# Transform the images to 28 by 28 pixels
test_images28 = [transform.resize(image, (28, 28)) for image in test_images]

# Convert to grayscale
from skimage.color import rgb2gray
test_images28 = rgb2gray(np.array(test_images28))

# Run predictions against the full test set.
predicted = sess.run([correct_pred], feed_dict={x: test_images28})[0]

# Calculate correct matches 
match_count = sum([int(y == y_) for y, y_ in zip(test_labels, predicted)])

# Calculate the accuracy
accuracy = match_count / len(test_labels)

# Print the accuracy
print("Accuracy: {:.3f}".format(accuracy))

记得在未使用 with tf.Session() as sess: 启动 TensorFlow 会话的情况下,最后用 sess.close() 关闭会话。

接下来做什么?

如果您想继续使用本教程中的数据集和模型,可尝试以下事项:

  • 在将数据输入模型之前,先应用正则化 LDA。这是来自其中一篇原始论文(数据集研究者撰写)的建议。
  • 正如教程中所说,您也可以探索更多针对交通标志图像的数据增强操作。此外,您还可以进一步微调这个网络;当前构建的网络较为简单。
  • 早停(Early stopping):在训练神经网络时,同时跟踪训练误差与测试误差。当两者先下降后又突然上升时,停止训练——这表明神经网络开始对训练数据过拟合。
  • 尝试不同的优化器。

务必查看由 Nishant Shukla 撰写的Machine Learning With TensorFlow 一书。

提示 也可以体验一下 TensorFlow Playground 和 TensorBoard。

如果您想继续在图像方向深挖,强烈推荐 DataCamp 的scikit-learn 教程,它使用 PCA、K-Means 和支持向量机(SVM)来处理 MNIST 数据集。或查看其它教程,例如这篇同样使用比利时交通标志数据集的教程。

主题