TensorFlow-强大的预测性分析-全-

TensorFlow 强大的预测性分析(全)

原文:annas-archive.org/md5/44cb2596e963141e6bdb1da768747fa9

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

预测分析通过从结构化和非结构化数据中发现隐藏的模式,为商业智能中的自动决策提供支持。预测决策正在成为全球的大趋势,通过预测哪些决策最有可能带来最大结果,服务于各行各业。数据挖掘、统计学和机器学习使用户能够通过揭示模式和展示结构化与非结构化数据之间的关系来发现预测智能。

机器学习关注的是将原始数据转化为信息,然后转化为可操作的智能。正是因为这一点,机器学习非常适合用于预测分析。因此,如果没有机器学习,要跟上这些庞大的信息流几乎是不可能的。

我能获得什么?

地图对于你的旅程至关重要,尤其是当你在另一个大洲度假时。学习过程中,路线图能帮助你为实现目标提供明确的路径。因此,在你开始旅程之前,这里给你呈现了一张路线图。

本书精心设计和开发,旨在为您提供所有关于 TensorFlow 的正确和相关信息。我们为您创建了这条学习路径,包含四节课:

第 1 课,从数据到决策 - TensorFlow 入门,详细描述了在实际问题中使用 TensorFlow 的主要特性,随后详细讨论了 TensorFlow 的安装和配置。接着,它涵盖了计算图、数据和编程模型,然后开始介绍 TensorFlow。课程的最后部分包含了一个实现线性回归模型进行预测分析的示例。

第 2 课,组织数据 - 监督学习与预测分析,从理论和实践的角度介绍了一些基于 TensorFlow 的监督学习技术。特别是,线性回归模型将会在真实数据集上进行回归分析。接下来,将展示如何利用逻辑回归、随机森林和支持向量机(SVM)解决泰坦尼克号生存问题,从而实现预测分析。

第 3 课,数据聚类 - 无监督学习与预测分析,深入探讨了预测分析,并揭示了如何利用它对无监督观测数据集中的记录进行聚类,将它们归属于某个特定组或类别。然后,它将提供一些无监督学习的实际示例。特别是,使用 TensorFlow 进行聚类技术的讨论将伴随一些实际操作示例。

Lesson 4,利用强化学习进行预测分析,讨论了设计以批评和奖励驱动的机器学习系统。它将展示如何应用强化学习算法来开发基于实际数据集的预测模型的多个示例。

本书能带给我什么?

  • 在实际问题中学习 TensorFlow 的特性,详细介绍 TensorFlow 的安装和配置。

  • 探索计算图、数据和编程模型,还可以深入了解实现线性回归模型进行预测分析的示例。

  • 使用逻辑回归、随机森林和支持向量机解决泰坦尼克号生存问题,用于预测分析。

  • 深入了解预测分析,并发现如何利用它来为无监督观察数据集中的记录进行分组或分类。

  • 学习几个例子,如何在实际数据集上应用强化学习算法来开发预测模型。

先决条件

本书旨在面向数据分析师、数据科学家和机器学习从业者,他们希望利用 TensorFlow 的强大、稳健和准确的预测模型能力构建模型。在开始阅读本书之前,需要具备以下先决条件:

  • Python 的实际应用知识。

  • TensorFlow 的基础知识。

  • 数学和统计学的基础知识。

第一章:从数据到决策——开始使用 TensorFlow

尽管数据的巨大可用性和大量投资,许多商业组织仍然依赖直觉,因为他们既没有充分利用数据,也没有做出适当且有效的商业决策。另一方面,TensorFlow 可以帮助从这庞大的数据集合中做出商业决策。TensorFlow 是一款数学软件,是由 Google Brain 团队在 2011 年开发的开源机器智能软件库,它可以帮助我们分析数据并预测有效的商业结果。虽然 TensorFlow 的初衷是进行机器学习和深度神经网络的研究,但该系统足够通用,能够应用于各种其他领域。

牢记您的需求,并基于 TensorFlow 1.x 的所有最新且令人兴奋的特性,在本课程中,我们将描述 TensorFlow 的主要功能,这些功能大多通过使用数据的实际例子来激励。

本课程将涵盖以下主题:

  • 从数据到决策:泰坦尼克号例子

  • TensorFlow 概述

  • 安装与配置 TensorFlow

  • TensorFlow 计算图

  • TensorFlow 编程模型

  • TensorFlow 数据模型

  • 通过 TensorBoard 可视化

  • 开始使用 TensorFlow:线性回归及其应用

基于数据做决策——泰坦尼克号例子

对数据需求的日益增加是一个关键挑战。决策支持团队,如机构研究和商业智能,通常无法在大量数据中做出关于如何扩展业务和研究成果的正确决策。尽管数据在推动决策方面起着重要作用,但实际上,正确的决策是在正确的时间做出的目标。

换句话说,目标是决策支持,而非数据支持。通过高级的数据管理和分析方法可以实现这一目标。

数据价值链与决策制定

图 1 中的下图(来源:H. Gilbert Miller 和 Peter Mork,From Data to Decisions: A Value Chain for Big Data,IT Professional,2013 年 1 月 - 2 月,卷:15,期:1,DOI:10.1109/MITP.2013.11)展示了从数据到实际决策的链条——即目标。价值链从数据发现阶段开始,包含多个步骤,如数据收集和注释数据准备,然后按逻辑顺序组织它们,确保所需的数据流。接着是数据集成,用于建立数据的共同表示。由于目标是做出正确的决策,因此,未来参考时,拥有适当的数据来源(即数据的来源)非常重要:

数据价值链与决策制定

图 1:从数据到决策:大数据的价值链

现在,您的数据已经以某种方式集成到一个可呈现的格式中,是时候进入数据探索阶段了,该阶段包括多个步骤,如分析集成数据和可视化,之后根据解读结果采取相应的行动。

然而,在做出正确决策之前,这就足够了吗?可能不够!原因在于,它缺乏足够的分析,这最终有助于通过可操作的洞察力做出决策。此时,预测分析介入,填补了其中的空白。现在让我们在接下来的部分中看看一个例子。

从灾难到决策 – Titanic 生存案例

这是挑战,来自 Kaggle 的 Titanic–机器学习从灾难中(www.kaggle.com/c/titanic):

"RMS Titanic 的沉没是历史上最臭名昭著的船难之一。1912 年 4 月 15 日,在她的处女航行中,Titanic 与冰山相撞后沉没,造成 2224 名乘客和船员中 1502 人死亡。这个震惊国际社会的悲剧促使了更严格的船只安全规定。船难导致如此多人死亡的原因之一是船上没有足够的救生艇供乘客和船员使用。虽然幸存者中有些人是因为运气因素,但某些群体比其他人更可能生还,例如妇女、儿童和上层阶级。在这个挑战中,我们要求你完成对哪些人更可能幸存的分析。特别地,我们要求你应用机器学习工具来预测哪些乘客在这场悲剧中幸存。"

但深入研究这一点,我们需要了解在灾难中乘坐 Titanic 的乘客数据,以便我们可以开发一个可用于生存分析的预测模型。

数据集可以从前述网址下载。表 1 显示了 Titanic 生存数据集的元数据:

从灾难到决策 – Titanic 生存案例

数据集的快照如下所示:

从灾难到决策 – Titanic 生存案例

图 2:Titanic 生存数据集快照

使用该数据集的最终目标是预测哪些人幸存于 Titanic 灾难。然而,稍微进行一些数据集的探索性分析是必要的。首先,我们需要导入必要的软件包和库:

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

现在读取数据集并创建一个 Pandas 的 DataFrame:

df = pd.read_csv('/home/asif/titanic_data.csv')

在绘制数据集的分布之前,让我们先指定图表的参数:

fig = plt.figure(figsize=(18,6), dpi=1600)
alpha=alpha_scatterplot = 0.2
alpha_bar_chart = 0.55
fig = plt.figure()
ax = fig.add_subplot(111)

绘制一个柱状图,显示谁幸存了与谁没有幸存:

ax1 = plt.subplot2grid((2,3),(0,0))
ax1.set_xlim(-1, 2)            
df.Survived.value_counts().plot(kind='bar', alpha=alpha_bar_chart)
plt.title("Survival distribution: 1 = survived")

绘制一个显示年龄与生存情况的图表:

plt.subplot2grid((2,3),(0,1))
plt.scatter(df.Survived, df.Age, alpha=alpha_scatterplot)
plt.ylabel("Age")                      
plt.grid(b=True, which='major', axis='y') 
plt.title("Survival by Age: 1 = survived")

绘制一个显示乘客类别分布的图表:

ax3 = plt.subplot2grid((2,3),(0,2))
df.Pclass.value_counts().plot(kind="barh", alpha=alpha_bar_chart)
ax3.set_ylim(-1, len(df.Pclass.value_counts()))
plt.title("Class dist. of the passengers")

绘制 1 等舱乘客年龄子集的核密度估计图:

plt.subplot2grid((2,3),(1,0), colspan=2)
df.Age[df.Pclass == 1].plot(kind='kde')   
df.Age[df.Pclass == 2].plot(kind='kde')
df.Age[df.Pclass == 3].plot(kind='kde')
plt.xlabel("Age")    
plt.title("Age dist. within class")
plt.legend(('1st Class', '2nd Class','3rd Class'),loc='best')

绘制一个显示每个登船地点的乘客的图表:

ax5 = plt.subplot2grid((2,3),(1,2))
df.Embarked.value_counts().plot(kind='bar', alpha=alpha_bar_chart)
ax5.set_xlim(-1, len(df.Embarked.value_counts()))
plt.title("Passengers per boarding location")
Finally, we show all the subplots together:
plt.show()
>>>

图表显示了生存分布、年龄生存率、年龄分布以及每个登船地点的乘客数量:

从灾难到决策 – 泰坦尼克号生存案例

图 3: 泰坦尼克号生存数据在年龄、舱位和各舱位内年龄分布以及登船地点上的分布

然而,要执行上述代码,您需要安装几个包,例如 matplotlib、pandas 和 scipy。它们列在下面:

  • 安装 pandas: Pandas 是用于数据操作的 Python 包。可以按以下步骤安装:

    $ sudo pip3 install pandas 
    #For Python 2.7, use the following: 
    $ sudo pip install pandas
    
    
  • 安装 matplotlib: 在上述代码中,matplotlib 是用于数学对象绘图的库。可以按以下步骤安装:

    $ sudo apt-get install python-matplotlib   # for Python 2.7 
    $ sudo apt-get install python3-matplotlib # for Python 3.x
    
    
  • 安装 scipy: Scipy 是用于科学计算的 Python 包。安装blaslapackgfortran是其先决条件。现在只需在您的终端上执行以下命令:

    $ sudo apt-get install libblas-dev liblapack-dev $ sudo apt-get install gfortran $ sudo pip3 install scipy # for Python 3.x
    $ sudo pip install scipy # for Python 2.7 
    
    

对于 Mac,请使用以下命令安装上述模块:

$ sudo easy_install pip
$ sudo pip install matplotlib
$ sudo pip install libblas-dev liblapack-dev
$ sudo pip install gfortran
$ sudo pip install scipy

对于 Windows,我假设 Python 2.7 已经安装在 C:\Python27\。然后打开命令提示符并键入以下命令:

C:\Users\admin-karim>cd C:/Python27
C:\Python27> python -m pip install <package_name> # provide package name accordingly.

对于 Python3,请发出以下命令:

C:\Users\admin-karim>cd C:\Users\admin-karim\AppData\Local\Programs\Python\Python35\Scripts
C:\Users\admin-karim\AppData\Local\Programs\Python\Python35\Scripts>python3 -m pip install <package_name>

好了,我们已经看过数据了。现在轮到你对数据进行一些分析了。比如预测哪些人从那场灾难中幸存下来了。你不觉得我们已经有足够的乘客信息了吗?但是如何进行预测建模,以便我们可以从这些数据中得出一些相当直接的结论呢?

例如,女性、一等舱乘客和儿童在这次灾难中生存的可能性更高。

在一种蛮力方法中,例如使用 if/else 语句和某种加权评分系统,您可以编写一个程序,以预测给定乘客是否会在灾难中生存。然而,在 Python 中编写这样的程序是否有多大意义呢?自然地,这将非常繁琐,难以泛化,并且需要对每个变量和样本(即乘客)进行广泛的微调。

这就是预测分析与机器学习算法及新兴工具的应用,使您可以构建一个程序,从样本数据中学习,预测给定乘客是否会生存。在本书中,我们将看到 TensorFlow 能够成为实现预测模型卓越准确性的完美解决方案。我们将开始描述 TensorFlow 框架的概要,然后展示如何在 Linux、Mac OS 和 Windows 上安装和配置 TensorFlow。

TensorFlow 框架的概述

TensorFlow 是谷歌推出的一个开源框架,用于基于数据流图进行科学计算和数值计算,数据流图代表了 TensorFlow 的执行模型。TensorFlow 中使用的数据流图帮助机器学习专家在数据上进行更高级、更密集的训练,以开发深度学习和预测分析模型。2015 年,谷歌将 TensorFlow 及其所有参考实现开源,并将所有源代码以 Apache 2.0 许可证发布到 GitHub 上。从那时起,TensorFlow 得到了学术界、研究界和工业界的广泛采用,最近发布了稳定的 1.x 版本,并且具有统一的 API。

正如名称 TensorFlow 所暗示的那样,操作是由神经网络在多维数据数组上执行的(也就是张量流)。通过这种方式,TensorFlow 提供了一些广泛使用且稳健的线性模型和深度学习算法实现。

使用 TensorFlow 部署预测模型或通用模型是相当简单的。关键是,一旦你构建了神经网络模型并完成必要的特征工程,就可以通过交互式训练(使用绘图或 TensorBoard 进行)来进行训练(我们将在接下来的部分看到更多内容)。最后,在通过一些测试数据进行评估后,你可以将模型部署到实际环境中。

既然我们在谈论数据流图,流图中的节点对应于数学运算,如加法、乘法、矩阵分解等,而边则对应于张量,确保边与节点之间的通信,即数据流和控制流。

你可以在 CPU 上执行数值计算。然而,使用 TensorFlow,你也可以将训练分布到同一系统上的多个设备,并在这些设备上进行训练,特别是如果你的系统有多个 GPU,可以共享计算负载。但前提是 TensorFlow 能访问这些设备,它会通过贪婪的方式自动将计算分配到多个设备上。TensorFlow 也允许程序指定哪些操作会在哪些设备上执行,这通过命名作用域进行分配。

TensorFlow 1.x 中的 API 发生了变化,这些变化并非完全向后兼容。也就是说,之前在 TensorFlow 0.x 上运行的 TensorFlow 程序不一定能在 TensorFlow 1.x 上运行。

TensorFlow 最新版本提供的主要功能包括:

  • 更快的计算:TensorFlow 的最新版本极其快速。例如,Inception v3 在 8 个 GPU 上速度提高了 7.3 倍,分布式 Inception(在 64 个 GPU 上进行 v3 训练)的速度提升达 58 倍。

  • 灵活性:TensorFlow 不仅仅是一个深度学习库,它还提供了几乎所有你需要的功能,通过函数解决最困难的问题,进行强大的数学运算。TensorFlow 1.x 引入了一些高层次的 API,用于处理高维数组或张量,包含tf.layerstf.metricstf.lossestf.keras模块。这些功能使得 TensorFlow 非常适合高层次的神经网络计算。

  • 可移植性:TensorFlow 可以在 Windows、Linux 和 Mac 机器以及移动计算平台(即 Android)上运行。

  • 易于调试:TensorFlow 提供了 TensorBoard 工具,用于分析已开发的模型。

  • 统一 API:TensorFlow 提供了非常灵活的架构,使得您可以通过一个 API 将计算部署到桌面、服务器或移动设备上的一个或多个 CPU 或 GPU 上。

  • 透明 使用 GPU 计算:自动管理和优化相同的内存和数据。现在,您可以使用 NVIDIA cuDNN 和 CUDA 工具包,在机器上进行大规模和数据密集型的 GPU 计算。

  • 易于使用:TensorFlow 适合每个人,适合学生、研究人员、深度学习从业者,也适合本书的读者。

  • 生产就绪 并且 可扩展:它最近已经发展为一种用于机器翻译的神经网络,并且可以在生产规模下运行。TensorFlow 1.x 保证了 Python API 的稳定性,使得选择新功能变得更加容易,无需过多担心现有代码的破坏。

  • 可扩展性:TensorFlow 是一项相对较新的技术,仍在积极开发中。然而,它具有可扩展性,因为它以开源代码的形式发布在 GitHub 上(github.com/tensorflow/tensorflow)。

  • 支持:有一个庞大的开发者和用户社区在共同努力,使 TensorFlow 成为一个更好的产品,既通过提供反馈,也通过积极贡献源代码。

  • 广泛应用:众多科技巨头正在使用 TensorFlow 来提升他们的商业智能。例如,ARM、Google、Intel、eBay、Qualcomm、SAM、Dropbox、DeepMind、Airbnb、Twitter 等。

在接下来的课程中,我们将看到如何实现这些预测分析功能。

安装和配置 TensorFlow

您可以在多个平台上安装和使用 TensorFlow,例如 Linux、Mac OS 和 Windows。此外,您还可以从 TensorFlow 的最新 GitHub 源代码构建并安装 TensorFlow。此外,如果您使用的是 Windows 机器,可以通过原生 pip 或 Anaconda 安装 TensorFlow。需要注意的是,TensorFlow 在 Windows 上支持 Python 3.5.x 和 3.6.x。

此外,Python 3 附带了 pip3 包管理器,这是你将用来安装 TensorFlow 的程序。所以,如果你使用的是此版本的 Python,就无需单独安装 pip。为了简便起见,在本节中,我将展示如何使用原生 pip 安装 TensorFlow。现在,为了安装 TensorFlow,启动终端,然后在终端中输入相应的pip3 install命令。

若要安装仅支持 CPU 版本的 TensorFlow,请输入以下命令:

C:\> pip3 install --upgrade tensorflow

若要安装 TensorFlow 的 GPU 版本,请输入以下命令:

C:\> pip3 install --upgrade tensorflow-gpu

当谈到 Linux 时,TensorFlow 的 Python API 支持 Python 2.7 和 Python 3.3 以上版本,因此你需要安装 Python 来开始安装 TensorFlow。为了获得 GPU 支持,你必须安装 Cuda Toolkit 7.5 和 cuDNN v5.1 以上版本。在本节中,我们将向你展示如何安装并开始使用 TensorFlow。更多关于在 Linux 上安装 TensorFlow 的详细信息将在后续展示。

注意

在 Mac OS 上的安装与 Linux 类似。有关更多详情,请参阅www.tensorflow.org/install/install_mac。另一方面,Windows 用户应参阅www.tensorflow.org/install/install_windows

请注意,在本课程的这一部分及后续部分,我们将提供大多数源代码,这些代码兼容 Python 3.x。

在 Linux 上安装 TensorFlow

在本节中,我们将向你展示如何在 Ubuntu 14.04 或更高版本上安装 TensorFlow。这里提供的说明也可能适用于其他 Linux 发行版,只需进行一些小的调整。

然而,在正式开始之前,我们需要确定在你的平台上安装哪个版本的 TensorFlow。TensorFlow 的开发使得你可以在 GPU 和 CPU 上运行数据密集型的张量应用。因此,你应选择以下类型之一的 TensorFlow 进行安装:

  • 仅支持 CPU 的 TensorFlow:如果你的机器上没有安装类似 NVIDIA®的 GPU,你必须安装并使用此版本进行计算。这非常简单,你只需 5 到 10 分钟即可完成。

  • 支持 GPU 的 TensorFlow:正如你所知,深度学习应用通常需要非常高强度的计算资源。因此,TensorFlow 也不例外,但通常在 GPU 上进行数据计算和分析比在 CPU 上快得多。因此,如果你的机器上有 NVIDIA® GPU 硬件,你最终应该安装并使用此版本。

根据我们的经验,即使你的机器上集成了 NVIDIA GPU 硬件,首先安装并尝试仅支持 CPU 的版本也是值得的。如果你没有获得良好的性能,再切换到 GPU 支持版本。

TensorFlow 的 GPU 支持版本有几个要求,如 64 位 Linux、Python 2.7(或 Python 3 的 3.3 及更高版本)、NVIDIA CUDA® 7.5 或更高版本(Pascal GPU 需要 CUDA 8.0)、NVIDIA cuDNN v4.0(最低要求)或 v5.1(推荐)。更具体地说,TensorFlow 的当前开发版本仅支持使用 NVIDIA 工具包和软件进行 GPU 计算。因此,必须在 Linux 机器上安装以下软件,以便为预测分析应用程序提供 GPU 支持:

  • Python

  • NVIDIA 驱动程序

  • 计算能力 >= 3.0 的 CUDA

  • cuDNN

  • TensorFlow

安装 Python 和 nVidia 驱动程序

我们已经了解如何在不同平台上安装 Python,因此可以跳过这一步。同时,我假设您的机器上已经安装了 NVIDIA GPU。

要检查 GPU 是否安装正确并正常工作,可以在终端中输入以下命令:

$ lspci -nnk | grep -i nvidia
# Expected output (of course may vary for your case): 4b:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:1b80] (rev a1)4b:00.1 Audio device [0403]: NVIDIA Corporation Device [10de:10f0] (rev a1)

由于预测分析在很大程度上依赖于机器学习和深度学习算法,因此请确保在机器上安装了一些基本的软件包,如 GCC 和一些科学 Python 包。

只需在终端中输入以下命令即可:

$ sudo apt-get update
$ sudo apt-get install libglu1-mesa libxi-dev libxmu-dev -y
$ sudo apt-get — yes install build-essential
$ sudo apt-get install python-pip python-dev -y
$ sudo apt-get install python-numpy python-scipy –y

现在通过wget下载 NVIDIA 驱动程序(别忘了选择适合您机器的版本),并以静默模式运行脚本:

$ wget http://us.download.nvidia.com/XFree86/Linux-x86_64/367.44/NVIDIA-Linux-x86_64-367.44.run
$ sudo chmod +x NVIDIA-Linux-x86_64-367.35.run
$ ./NVIDIA-Linux-x86_64-367.35.run --silent

注意

一些 GPU 卡,如 NVIDIA GTX 1080,配有内置驱动程序。因此,如果您的机器使用的是不同于 GTX 1080 的 GPU,您需要下载该 GPU 的驱动程序。

要确认驱动程序是否正确安装,请在终端中输入以下命令:

$ nvidia-smi

命令的输出结果应如下所示:

安装 Python 和 NVIDIA 驱动

图 4:nvidia-smi 命令的输出结果

安装 NVIDIA CUDA

要使用 TensorFlow 与 NVIDIA GPU,必须安装 CUDA®工具包 8.0 和与 CUDA 工具包 8+相关联的 NVIDIA 驱动程序。CUDA 工具包包括:

  • GPU 加速库,如 cuFFT,用于快速 傅里叶 变换FFT

  • cuBLAS 用于基本 线性 代数 子程序BLAS

  • cuSPARSE 用于稀疏矩阵运算

  • cuSOLVER 用于稠密和稀疏直接求解器

  • cuRAND 用于随机数生成,NPP 用于图像和视频处理原语

  • nvGRAPH 用于 NVIDIA 图形 分析

  • Thrust 用于模板并行算法和数据结构以及专用的 CUDA 数学库

对于 Linux,下载并安装所需的软件包:

使用wget命令在 Ubuntu 上下载 developer.nvidia.com/cuda-downloads

$ wget https://developer.nvidia.com/compute/cuda/8.0/Prod2/local_installers/cuda_8.0.61_375.26_linux-run
$ sudo chmod +x cuda_8.0.61_375.26_linux.run
$ ./ cuda_8.0.61_375.26_linux.run --driver --silent
$ ./ cuda_8.0.61_375.26_linux.run --toolkit --silent
$ ./ cuda_8.0.61_375.26_linux.run --samples –silent

此外,确保已将 CUDA 安装路径添加到LD_LIBRARY_PATH环境变量中,如下所示:

$ echo 'export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/cuda/lib64:/usr/local/cuda/extras/CUPTI/lib64"' >> ~/.bashrc
$ echo 'export CUDA_HOME=/usr/local/cuda' >> ~/.bashrc
$ source ~/.bashrc

安装 NVIDIA cuDNN v5.1+

一旦 CUDA 工具包安装完成,您应该从 Linux 下载 cuDNN v5.1 库,并在下载后解压文件并将其复制到 CUDA 工具包目录(假定位于/usr/local/cuda/):

$ cd /usr/local
$sudo mkdir cuda
$ cd ~/Downloads/
$ wget http://developer2.download.nvidia.com/compute/machine-learning/cudnn/secure/v6/prod/8.0_20170427/cudnn-8.0-linux-x64-v6.0.tgz 
$ sudo tar –xvzf cudnn-8.0-linux-x64-v6.0.tgz
$ cp cuda/lib64/* /usr/local/cuda/lib64/
$ cp cuda/include/cudnn.h /usr/local/cuda/include/

请注意,要安装 cuDNN v5.1 库,您必须注册 加速计算开发者计划。现在,当您安装了 cuDNN v5.1 库后,请确保创建 CUDA_HOME 环境变量。

安装 libcupti-dev 库

最后,您需要在机器上安装 libcupti-dev 库。这是 NVIDIA CUDA 提供的高级性能分析支持。要安装此库,请执行以下命令:

$ sudo apt-get install libcupti-dev

安装 TensorFlow

请参阅以下部分,获取关于如何安装仅支持 CPU 和支持 NVIDIA cuDNN 和 CUDA 计算能力的最新版本 TensorFlow 的逐步指南。您可以通过多种方式在您的机器上安装 TensorFlow,例如使用 virtualenv、pip、Docker 和 Anaconda。然而,使用 Docker 和 Anaconda 有点复杂,因此我们决定使用 pip 和 virtualenv。

注意

有兴趣的读者可以尝试从 www.tensorflow.org/install/ 使用 Docker 和 Anaconda。

使用本地 pip 安装 TensorFlow

如果步骤 1 到 6 已完成,请通过以下命令之一安装 TensorFlow。对于 Python 2.7,并且仅支持 CPU:

$ pip install tensorflow
# For Python 3.x and of course with only CPU support:
$ pip3 install tensorflow 
# For Python 2.7 and of course with GPU support:
$ pip install tensorflow-gpu
# For Python 3.x and of course with GPU support: 
$ pip3 install tensorflow-gpu

如果步骤 3 失败,请手动执行命令安装最新版本的 TensorFlow:

$ sudo pip install --upgrade TF_PYTHON_URL
#For Python 3.x, use the following command: 
$ sudo pip3 install --upgrade TF_PYTHON_URL 

对于两种情况,TF_PYTHON_URL 表示位于 www.tensorflow.org/install/install_linux#the_url_of_the_tensorflow_python_package 的 TensorFlow Python 包的 URL。

例如,要安装仅支持 CPU 的最新版本(在撰写时为 v1.1.0),请使用以下命令:

$ sudo pip3 install --upgrade https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.1.0-cp34-cp34m-linux_x86_64.wh
l

使用 virtualenv 安装

我们假设您已经在系统上安装了 Python 2+(或 3+)和 pip(或 pip3)。如果是这样,请按照以下步骤安装 TensorFlow:

  1. 如下创建 virtualenv 环境:

    $ virtualenv --system-site-packages targetDirectory
    
    

    targetDirectory 表示 virtualenv 树的根目录。默认情况下,它是 ~/tensorflow(但您可以选择任何目录)。

  2. 如下激活 virtualenv 环境:

    $ source ~/tensorflow/bin/activate # bash, sh, ksh, or zsh
     $ source ~/tensorflow/bin/activate.csh  # csh or tcsh
    
    

    如果步骤 2 中的命令成功,则应在终端中看到以下内容:

      (tensorflow)$
    
  3. 安装 TensorFlow。

    按照以下命令之一,在激活的 virtualenv 环境中安装 TensorFlow。对于 Python 2.7,仅支持 CPU,请使用以下命令:

     (tensorflow)$ pip install --upgrade tensorflow 
    #For Python 3.x with CPU support, use the following command: 
    (tensorflow)$ pip3 install --upgrade tensorflow 
    #For Python 2.7 with GPU support, use the following command:
    (tensorflow)$ pip install --upgrade tensorflow-gpu
    #For Python 3.x with GPU support, use the following command: 
     (tensorflow)$ pip3 install --upgrade tensorflow-gpu
    
    

    如果前面的命令成功,跳过步骤 5。如果前面的命令失败,请执行步骤 5。此外,如果步骤 3 失败,请尝试通过执行以下格式的命令,在激活的 virtualenv 环境中安装 TensorFlow:

    #For python 2.7 (select appropriate URL with CPU or GPU support):
    (tensorflow)$ pip install --upgrade TF_PYTHON_URL 
    #For python 3.x (select appropriate URL with CPU or GPU support):
     (tensorflow)$ pip3 install --upgrade TF_PYTHON_URL
    
    
  4. 验证安装。

    要验证步骤 3 中的安装,必须激活虚拟环境。如果虚拟环境当前没有激活,请执行以下命令之一:

    $ source ~/tensorflow/bin/activate  # bash, sh, ksh, or zsh
    $ source ~/tensorflow/bin/activate.csh  # csh or tcsh
    
    
  5. 卸载 TensorFlow

    要卸载 TensorFlow,只需删除您创建的目录树。例如:

    $ rm -r targetDirectory
    
    

    最后,如果您想手动控制哪些设备对 TensorFlow 可见,您应该设置CUDA_VISIBLE_DEVICES。例如,以下命令可强制仅使用 GPU 0:

    $ CUDA_VISIBLE_DEVICES=0 python
    
    

从源代码安装 TensorFlow

使用 pip 安装可能会在使用 TensorBoard 时出现问题(稍后在本课程中讨论)。因此,我建议您直接从源代码构建 TensorFlow。步骤如下所述。

注意

请按照以下网址上的指示和准则,在您的平台上安装 Bazel:bazel.io/docs/install.html

首先,按以下方式克隆整个 TensorFlow 存储库:

$git clone --recurse-submodules https://github.com/tensorflow/tensorflow

然后是安装 Bazel 的时候了,这是一个自动化软件构建和测试的工具。此外,为了从源代码构建 TensorFlow,必须在您的机器上安装 Bazel 构建系统。为此,请执行以下命令:

$ sudo apt-get install software-properties-common swig
$ sudo add-apt-repository ppa:webupd8team/java
$ sudo apt-get update $ sudo apt-get install oracle-java8-installer
$ echo "deb http://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
$ curl https://storage.googleapis.com/bazel-apt/doc/apt-key.pub.gpg | sudo apt-key add -
$ sudo apt-get update
$ sudo apt-get install bazel

然后运行 Bazel 安装程序,执行以下命令:

$ chmod +x bazel-version-installer-os.sh
$ ./bazel-version-installer-os.sh –-user

此外,您可能需要一些 Python 依赖项,例如python-numpyswigpython-dev。现在,执行以下命令来安装它们:

$ sudo apt-get install python-numpy swig python-dev

现在是时候配置安装(GPU 或 CPU)了。通过执行以下命令来完成:

$ ./configure

然后使用bazel创建您的 TensorFlow 包:

$ bazel build -c opt //tensorflow/tools/pip_package:
$ build_pip_package

然而,要构建支持 GPU 的版本,请执行以下命令:

$ bazel build -c opt --config=cuda //tensorflow/tools/pip_package:build_pip_package

最后,安装 TensorFlow。这里我列出了 Python 版本的安装步骤:

  • 对于 Python 2.7:

    $ sudo pip install --upgrade /tmp/tensorflow_pkg/tensorflow-1.1.0-*.whl
    
    
  • 对于 Python 3.4:

    $ sudo pip3 install --upgrade /tmp/tensorflow_pkg/tensorflow-1.1.0-*.whl
    
    

测试您的 TensorFlow 安装

我们从流行的 TensorFlow 别名tf开始。打开一个 Python 终端(只需在终端上键入pythonpython3)并执行以下代码:

>>> import tensorflow as tf

如果您喜欢的 Python 解释器没有投诉,那么您已经准备好开始使用 TensorFlow 了!

>>> hello = tf.constant("Hello, TensorFlow!")
>>> sess=tf.Session()

现在验证您的安装,只需输入以下命令:

>>> print sess.run(hello)

如果安装成功,您将看到以下输出:

Hello, TensorFlow!

TensorFlow 计算图

在考虑执行 TensorFlow 程序时,我们应该熟悉图的创建和会话的执行。基本上,第一个是用于构建模型,第二个是用于提供数据并获取结果。有趣的是,TensorFlow 的每一点乘或加法都是在 C++引擎上执行的,这意味着甚至在 Python 上执行的操作也只是一个包装器。基本上,TensorFlow 的 C++引擎由以下两部分组成:

  • 对于操作如卷积、最大池、sigmoid 等的高效实现。

  • 前向模式操作的导数。

当我们/您使用 TensorFlow 执行一些稍复杂的操作时,例如训练线性回归,TensorFlow 内部使用数据流图来表示其计算。这个图称为计算图,它是由以下内容组成的有向图:

  • 一组节点,每个节点表示一个操作

  • 一组有向弧线,每一条代表操作执行时的数据。

TensorFlow 有两种类型的边:

  • 普通:它们在节点之间传递数据结构。一个节点的某个操作的输出,成为另一个操作的输入。连接两个节点的边承载着这些值。

  • 特殊:该边不传递值,仅表示两个节点之间的控制依赖关系,例如 X 和 Y。这意味着,只有当 X 中的操作已执行时,节点 Y 才会被执行,但在数据操作之间的关系之前。

TensorFlow 的实现定义了控制依赖关系,以强制执行原本独立的操作之间的顺序,从而控制峰值内存使用。

计算图基本上就像是一个数据流图。图 5 展示了一个简单计算的计算图,例如 z=d×c=(a+b) ×c

TensorFlow 计算图

图 5:一个非常简单的执行图,计算一个简单的方程。

在前面的图中,图中的圆圈表示操作,而矩形表示数据计算图。正如前面所述,TensorFlow 图包含以下内容:

  • 一组 tf.Operation 对象:用于表示要执行的计算单元。

  • 一个 tf.Tensor 对象:用于表示控制操作之间数据流的单元数据。

使用 TensorFlow,也可以执行延迟计算。简单来说,一旦你在计算图的构建阶段组成了一个高度可组合的表达式,你仍然可以在运行会话阶段评估它们。从技术上讲,TensorFlow 会调度任务并高效地按时执行。例如,图 6 展示了如何使用 GPU 并行执行独立的代码部分。

TensorFlow 计算图

图 6:TensorFlow 图中的边和节点将在设备(如 CPU 或 GPU)上通过会话执行。

创建计算图后,TensorFlow 需要一个活跃的会话,在多个 CPU(以及可用的 GPU)上以分布式方式执行。通常,你不需要明确指定使用 CPU 还是 GPU,因为 TensorFlow 会选择并使用其中之一。默认情况下,TensorFlow 会选择尽可能多的操作使用 GPU;如果没有 GPU,则使用 CPU。因此,从整体上看,TensorFlow 的主要组件如下:

  • 变量:用于存储在 TensorFlow 会话之间的权重和偏置值。

  • 张量:一组在节点之间传递的值。

  • 占位符:用于在程序和 TensorFlow 图之间传递数据。

  • 会话:当会话启动时,TensorFlow 会自动计算图中所有操作的梯度,并使用链式法则。实际上,当图要执行时,会调用一个会话。

不用太担心,前面提到的每个组件将在后续章节中详细讨论。从技术上讲,你所编写的程序可以被视为客户端。然后,客户端用于在 C/C++ 或 Python 中符号化地创建执行图,然后你的代码可以要求 TensorFlow 执行这个图。详细信息请见下图:

TensorFlow 计算图

图 7:使用客户端–主节点架构执行 TensorFlow 图

计算图有助于将工作负载分配到具有 CPU 或 GPU 的多个计算节点。这样,神经网络可以进一步等同于一个复合函数,其中每一层(输入层、隐藏层或输出层)都可以表示为一个函数。现在,为了理解在张量上执行的操作,了解有关 TensorFlow 编程模型的良好解决方案是必须的。下一节将解释计算图在实现神经网络中的作用。

TensorFlow 编程模型

TensorFlow 编程模型表示如何构建你的预测模型。一旦导入了 TensorFlow 库的相关资源,TensorFlow 程序通常会分为四个阶段:

  • 构建计算图,涉及对张量的一些操作(我们将很快了解什么是张量)

  • 创建一个会话

  • 运行一个会话,这将在图中定义的操作上执行

  • 数据收集和分析的计算

这些主要步骤定义了 TensorFlow 中的编程模型。考虑以下示例,我们要乘两个数字:

import tensorflow as tf
x = tf.constant(8)
y = tf.constant(9)
z = tf.multiply(x, y)
sess = tf.Session()
out_z = sess.run(z)
Finally, close the TensorFlow session when you're done:
sess.close()print('The multiplicaiton of x and y: %d' % out_z)

前面的代码段可以通过以下图示表示:

TensorFlow 编程模型

图 8:在客户端-主节点架构中执行并返回乘积的简单乘法

为了提高前面程序的效率,TensorFlow 还允许你通过占位符(稍后讨论)在图变量中交换数据。现在,假设以下代码段以更高效的方式执行相同的操作:

# Import tensorflow
import tensorflow as tf
# Build a graph and create session passing the graph:
with tf.Session() as sess:
 x = tf.placeholder(tf.float32, name="x")
 y = tf.placeholder(tf.float32, name="y")
 z = tf.multiply(x,y)
# Put the values 8,9 on the placeholders x,y and execute the graph
z_output = sess.run(z,feed_dict={x: 8, y:9})
# Finally, close the TensorFlow session when you're done:
sess.close()
print(z_output)

TensorFlow 不是为了乘两个数而必需的;此外,这个简单操作的代码行数也很多。然而,这个例子旨在阐明如何构建任何代码,从最简单的例子到最复杂的例子。此外,该例子还包含了一些基本的指令,这些指令会出现在本书其他例子中。

注意

本书中的大多数例子将以 Python 3.x 兼容的方式演示。然而,也有少数例子将使用 Python 2.7.x 来展示。

第一行中的这个单一导入帮助你导入 TensorFlow,之后可以使用 tf 来实例化它。然后,TensorFlow 运算符将通过 tf 和点符号 '.' 来表达,并通过运算符的名称来使用。接下来的行中,我们通过 tf.Session() 指令来构造对象 session

with tf.Session() as sess:

注意

会话对象(即 sess)封装了 TensorFlow 的环境,以便执行所有操作对象并评估 Tensor 对象。我们将在接下来的章节中看到它们。

该对象包含计算图,正如我们之前所说,它是需要执行的计算。

以下两行定义了变量 x 和 y,使用了占位符的概念。通过占位符,你可以定义输入(例如我们示例中的变量 x)和输出变量(例如变量 y):

x = tf.placeholder(tf.float32, name='x')
y = tf.placeholder(tf.float32, name='y')

占位符提供了图形元素与问题计算数据之间的接口,它允许我们创建操作并构建计算图,而不需要数据,只需要对其的引用。

要通过占位符函数定义数据或张量(稍后我将介绍张量的概念),需要三个参数:

  • 数据 类型:是输入张量中元素的类型。

  • 形状:占位符的形状——即要输入的张量的形状(可选)。如果未指定形状,可以输入任何形状的张量。

  • 名称:对调试和代码分析非常有用,但它是可选的。

注意

更多信息,请参阅www.tensorflow.org/api_docs/python/tf/Tensor

因此,我们可以介绍我们希望使用两个参数计算的模型,即先前定义的占位符和常量。接下来,我们定义计算模型。

以下语句在会话内部构建了xy的乘积数据结构,并将操作结果赋值给占位符z。接下来是这样的:

  z = tf.multiply(x, y)

现在,由于结果已经由占位符z持有,我们通过 sess.run语句执行计算图。这里我们提供两个值,将张量传入计算图节点。它暂时用张量值替换操作的输出(更多内容将在接下来的章节中介绍):

z_output = sess.run(z,feed_dict={x: 8, y:9})

然后当我们完成时,关闭 TensorFlow 会话:

sess.close()

在最后的指令中,我们打印出结果:

     print(z_output)

这本质上是打印输出 72.0。

TensorFlow 中的数据模型

TensorFlow 中的数据模型由张量表示。无需使用复杂的数学定义,我们可以说,张量(在 TensorFlow 中)标识了一个多维的数字数组。但我们将在下一小节中看到关于张量的更多细节。

张量

让我们看看 Wikipedia 上对张量的正式定义(en.wikipedia.org/wiki/Tensor)如下:

“张量是几何对象,用于描述几何向量、标量和其他张量之间的线性关系。此类关系的基本示例包括点积、叉积和线性映射。几何向量通常用于物理学和工程应用中,标量本身也是张量。”

这个数据结构由三个参数来描述:秩、形状和类型,如下图所示:

张量

图 9:张量不过是几何对象,具有形状、秩和类型,用于存储多维数组

因此,张量可以被看作是矩阵的推广,它通过任意数量的索引来指定一个元素。在实际使用时,张量的语法几乎与嵌套向量相似。

注意

张量仅定义这个值的类型,以及在会话期间如何计算这个值。因此,实质上,它们并不代表或保存任何由操作生成的值。

一些人喜欢比较 NumPy 与 TensorFlow;然而,实际上,TensorFlow 和 NumPy 在某种程度上非常相似,因为它们都是 N 维数组库!

诚然,NumPy 支持 n 维数组,但它并没有提供创建张量函数和自动计算导数的方法(+ 不支持 GPU)。下表可以看作是一个简短且一对一的比较,可以帮助我们理解这种比较:

张量

图 10:NumPy 与 TensorFlow

现在让我们看看另一种创建张量的方式,在它们可以被 TensorFlow 图形接收之前(我们稍后会看到其他接收机制):

>>> X = [[2.0, 4.0],
        [6.0, 8.0]]
>>> Y = np.array([[2.0, 4.0],
                 [6.0, 6.0]], dtype=np.float32)
>>> Z = tf.constant([[2.0, 4.0],
                    [6.0, 8.0]])

这里,X 是一个列表,Y 是来自 NumPy 库的 n 维数组,而 Z 本身是 TensorFlow 的张量对象。现在让我们来看它们的类型:

>>> print(type(X))
>>> print(type(Y))
>>> print(type(Z))
#Output
<class 'list'>
<class 'numpy.ndarray'>
<class 'tensorflow.python.framework.ops.Tensor'>

好的,它们的类型已经正确打印。然而,当我们正式处理张量时,相比其他类型,更方便的函数是 tf.convert_to_tensor(),如下所示:

t1 = tf.convert_to_tensor(X, dtype=tf.float32)t2 = tf.convert_to_tensor(Z, dtype=tf.float32)t3 = tf.convert_to_tensor(Z, dtype=tf.float32)

现在让我们通过以下几行代码来看它们的类型:

>>> print(type(t1))
>>> print(type(t2))
>>> print(type(t3))
#Output:
<class 'tensorflow.python.framework.ops.Tensor'>
<class 'tensorflow.python.framework.ops.Tensor'>
<class 'tensorflow.python.framework.ops.Tensor'>

太棒了!我认为到现在为止关于张量的讨论已经足够了,现在我们可以考虑由 这一术语所描述的结构。

每个张量由一个叫做秩的维度单位来描述。它标识了张量的维度数,因此,秩也称为张量的阶数或 n 维度。秩为零的张量是标量,秩为一的张量是向量,而秩为二的张量是矩阵。以下代码定义了一个 TensorFlow 标量,一个 向量,一个 矩阵,以及一个 立方矩阵,在下一个例子中,我们将展示秩是如何工作的:

import tensorflow as tf
scalar = tf.constant(100)
vector = tf.constant([1,2,3,4,5])
matrix = tf.constant([[1,2,3],[4,5,6]])
cube_matrix = tf.constant([[[1],[2],[3]],[[4],[5],[6]],[[7],[8],[9]]])
print(scalar.get_shape())
print(vector.get_shape())
print(matrix.get_shape())
print(cube_matrix.get_shape())

结果打印如下:

>>>
()
(5,)
(2, 3)
(3, 3, 1)
>>>

形状

张量的形状是它拥有的行数和列数。现在我们将看到如何将形状与张量的秩关联起来:

>>scalar1.get_shape()
TensorShape([])
>>vector1.get_shape()
TensorShape([Dimension(5)])
>>matrix1.get_shape()
TensorShape([Dimension(2), Dimension(3)])
>>cube1.get_shape()
TensorShape([Dimension(3), Dimension(3), Dimension(1)])

数据类型

除了秩和形状,张量还有数据类型。以下是数据类型的列表:

数据类型

我们认为前面的表格不言自明,因此没有详细讨论前述数据类型。现在,TensorFlow APIs 已经实现了管理数据 NumPy 数组的功能。因此,要构建具有常量值的张量,只需将 NumPy 数组传递给 tf.constant() 运算符,结果将是一个包含该值的 TensorFlow 张量:

import tensorflow as tf
import numpy as np
tensor_1d = np.array([1,2,3,4,5,6,7,8,9,10])
tensor_1d = tf.constant(tensor_1d)
with tf.Session() as sess:
    print (tensor_1d.get_shape())
    print sess.run(tensor_1d)
# Finally, close the TensorFlow session when you're done
sess.close()

运行示例后,我们得到:

>>>
 (10,)
 [ 1  2  3  4  5  6  7  8  9 10]

要构建具有变量值的张量,使用一个 NumPy 数组并将其传递给 tf.Variable 构造函数,结果将是一个具有该初始值的 TensorFlow 变量张量:

import tensorflow as tf
import numpy as np
tensor_2d = np.array([(1,2,3),(4,5,6),(7,8,9)])
tensor_2d = tf.Variable(tensor_2d)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print (tensor_2d.get_shape())
    print sess.run(tensor_2d)
# Finally, close the TensorFlow session when you're done
sess.close()

结果是:

>>>
 (3, 3)
 [[1 2 3]
 [4 5 6]
 [7 8 9]]

为了方便在交互式 Python 环境中使用,我们可以使用 InteractiveSession 类,然后用该会话执行所有 Tensor.eval()Operation.run() 调用:

import tensorflow as tf
import numpy as np

interactive_session = tf.InteractiveSession()
tensor = np.array([1,2,3,4,5])
tensor = tf.constant(tensor)
print(tensor.eval())

interactive_session.close()

注意

tf.InteractiveSession() 只是为了在 IPython 中保持默认会话而提供的一种便捷的语法糖。

结果是:

>>>
   [1 2 3 4 5]

在交互式环境中(如 shell 或 IPython notebook)使用时,这种方式会更加方便,因为在这种环境下将会话对象传递到处可能会显得繁琐。

注意

IPython Notebook 现在被称为 Jupyter Notebook。它是一个交互式计算环境,允许你结合代码执行、富文本、数学公式、绘图和富媒体。欲了解更多信息,感兴趣的读者可以参考 ipython.org/notebook.html 页面。

定义张量的另一种方法是使用 TensorFlow 语句 tf.convert_to_tensor

import tensorflow as tf
import numpy as np
tensor_3d = np.array([[[0, 1, 2], [3, 4, 5], [6, 7, 8]],
					[[9, 10, 11], [12, 13, 14], [15, 16, 17]],
					[[18, 19, 20], [21, 22, 23], [24, 25, 26]]])
tensor_3d = tf.convert_to_tensor(tensor_3d, dtype=tf.float64)
with tf.Session() as sess:
    print(tensor_3d.get_shape())
    print(sess.run(tensor_3d))
# Finally, close the TensorFlow session when you're done
sess.close()
>>>
(3, 3, 3)
[[[  0\.   1\.   2.]
  [  3\.   4\.   5.]
  [  6\.   7\.   8.]]
 [[  9\.  10\.  11.]
  [ 12\.  13\.  14.]
  [ 15\.  16\.  17.]]
 [[ 18\.  19\.  20.]
  [ 21\.  22\.  23.]
  [ 24\.  25\.  26.]]]

变量

变量是 TensorFlow 对象,用于保存和更新参数。变量必须初始化;你还可以保存并恢复它以便分析代码。变量是通过使用 tf.Variable() 语句创建的。在以下示例中,我们想要计算从 1 到 10 的数字,但我们先导入 TensorFlow:

import tensorflow as tf

我们创建了一个变量,并将其初始化为标量值 0

value = tf.Variable(0, name="value")

assign()add() 运算符只是计算图的节点,因此它们不会在会话运行之前执行赋值操作:

one = tf.constant(1)
new_value = tf.add(value, one)
update_value = tf.assign(value, new_value)
initialize_var = tf.global_variables_initializer()

我们可以实例化计算图:

with tf.Session() as sess:
    sess.run(initialize_var)
    print(sess.run(value))
    for _ in range(5):
        sess.run(update_value)
        print(sess.run(value))
# Finally, close the TensorFlow session when you're done:
sess.close()

让我们回顾一下,张量对象是对操作结果的符号句柄,但它并不实际保存操作输出的值:

>>>
0
1
2
3
4
5

获取

要获取操作的输出,通过在会话对象上调用 run() 并传入要检索的张量来执行图。除了获取单个张量节点外,你还可以获取多个张量。在以下示例中,我们一起获取了求和和乘法张量,使用 run() 调用:

import tensorflow as tf

constant_A = tf.constant([100.0])
constant_B = tf.constant([300.0])
constant_C = tf.constant([3.0])

sum_ = tf.add(constant_A,constant_B)
mul_ = tf.multiply(constant_A,constant_C)

with tf.Session() as sess:
    result = sess.run([sum_,mul_])
    print(result)

# Finally, close the TensorFlow session when you're done:
sess.close()

输出如下:

>>>
[array(400.],dtype=float32),array([ 300.],dtype=float32)]

所有生成所请求张量值的操作仅执行一次(而不是每个请求的张量一次)。

输入和占位符

有四种方法可以将数据导入 TensorFlow 程序(详情见 www.tensorflow.org/api_guides/python/reading_data):

  • 数据集 API:它使你能够从分布式文件系统中构建复杂的输入管道,并执行复杂的操作。处理大量数据和不同数据格式时,建议使用数据集 API。数据集 API 引入了两个新的抽象概念来为 TensorFlow 创建可馈送的数据集,分别是通过 tf.contrib.data.Dataset(通过创建源或应用转换操作)或使用 tf.contrib.data.Iterator

  • 馈送:允许我们将数据注入到计算图中的任何张量中。

  • 从文件中读取:我们可以在 TensorFlow 图的开头使用 Python 内建的机制来开发输入管道,从数据文件中读取数据。

  • 预加载数据:对于小型数据集,我们可以在 TensorFlow 图中使用常量或变量来保存所有数据。

在这一部分,我们只会看到馈送机制的示例。其他方法将在接下来的课程中看到。TensorFlow 提供了馈送机制,允许我们将数据注入到计算图中的任何张量中。你可以通过 feed_dict 参数将馈送数据提供给 run()eval() 调用来启动计算。

注意

使用 feed_dict 参数进行馈送是将数据馈送到 TensorFlow 执行图中的最低效方式,应该仅用于需要小型数据集的小型实验。它也可以用于调试。

我们还可以用馈送数据(即变量和常量)替换任何张量,最佳实践是使用 tf.placeholder() 调用来创建 TensorFlow 占位符节点。占位符仅用于作为馈送的目标。空占位符未初始化,因此不包含任何数据。因此,如果没有馈送它,它总是会生成错误,这样你就不会忘记给它馈送数据。

以下示例演示了如何馈送数据以构建一个随机的 2×3 矩阵:

import tensorflow as tf
import numpy as np

a = 3
b = 2
x = tf.placeholder(tf.float32,shape=(a,b))
y = tf.add(x,x)

data = np.random.rand(a,b)
sess = tf.Session()
print sess.run(y,feed_dict={x:data})

# Finally, close the TensorFlow session when you're done:
sess.close()

输出是:

>>>
[[ 1.78602004  1.64606333]
 [ 1.03966308  0.99269408]
 [ 0.98822606  1.50157797]]
>>>

TensorBoard

TensorFlow 包括用于调试和优化程序的函数,这些函数位于一个名为 TensorBoard 的可视化工具中。使用 TensorBoard,你可以观察与图中任何部分的参数和细节相关的不同统计信息。

此外,在使用复杂的深度神经网络进行预测建模时,图可能会变得复杂和混乱。为了更容易理解、调试和优化 TensorFlow 程序,你可以使用 TensorBoard 来可视化你的 TensorFlow 图,绘制有关图执行的定量指标,并展示经过图形处理的额外数据,如图像等。

因此,TensorBoard 可以看作是一个为分析和调试预测模型而设计的框架。TensorBoard 使用所谓的汇总(summaries)来查看模型的参数:一旦执行了 TensorFlow 代码,我们可以调用 TensorBoard 在图形界面中查看汇总。

TensorBoard 是如何工作的?

如前所述,TensorFlow 使用计算图来执行应用程序,其中每个节点表示一个操作,弧线表示操作之间的数据。

TensorBoard 的主要思想是将所谓的摘要与计算图的节点(操作)关联起来。运行代码时,摘要操作会将与节点相关联的数据序列化,并将数据输出到一个文件中,TensorBoard 可以读取该文件。然后,TensorBoard 可以运行并可视化这些摘要操作。使用 TensorBoard 的工作流程是:

  • 构建你的计算图/代码

  • 将摘要操作附加到你感兴趣的节点

  • 像平常一样开始运行你的计算图

  • 此外,运行摘要操作

  • 当代码运行完成后,运行 TensorBoard 以可视化摘要输出

如果你在终端中输入$ which tensorboard,如果你是通过pip安装的,它应该会显示:

asif@ubuntu:~$ which tensorboard
/usr/local/bin/tensorboard

你需要提供一个日志目录,所以确保你在运行计算图的目录下;你可以在终端使用类似以下命令来启动它:

tensorboard --logdir .

然后打开你喜欢的网页浏览器,输入localhost:6006进行连接。当 TensorBoard 完全配置好后,你可以通过执行以下命令来访问它:

$ tensorboard –logdir=<trace_file_name>

现在你只需从浏览器访问本地端口6006,地址为http://localhost:6006/。然后界面应该是这样的:

TensorBoard 如何工作?

图 11:在浏览器中使用 TensorBoard

这是不是有点太复杂了?别担心,在最后一部分,我们将结合之前解释的所有概念,构建一个单输入神经元模型,并用 TensorBoard 分析它。

开始使用 TensorFlow – 线性回归及其应用

在这个示例中,我们将更详细地了解 TensorFlow 和 TensorBoard 的主要概念,并尝试进行一些基本操作,帮助你入门。我们要实现的模型是模拟线性回归。

在线性回归的统计学和机器学习领域,线性回归是一种常用的技术,用于衡量变量之间的关系。这也是一个非常简单但有效的算法,可以用于预测建模。线性回归模拟了一个因变量yi、一个自变量xi和一个随机项b之间的关系。可以表示为:

开始使用 TensorFlow – 线性回归及其应用

现在,为了更好地理解前面的方程,我将编写一个简单的 Python 程序来创建二维空间中的数据。然后,我将使用 TensorFlow 寻找最适合这些数据点的直线:

# Import libraries (Numpy, matplotlib)
import numpy as np
import matplotlib.pyplot as plot

# Create 1000 points following a function y=0.1 * x + 0.4 (i.e. y \= W * x + b) with some normal random distribution:

num_points = 1000
vectors_set = []
for i in range(num_points):
    W = 0.1 # W
    b = 0.4 # b
    x1 = np.random.normal(0.0, 1.0)
    nd = np.random.normal(0.0, 0.05)
    y1 = W * x1 + b

 # Add some impurity with some normal distribution -i.e. nd:
    y1 = y1+nd

 # Append them and create a combined vector set:
    vectors_set.append([x1, y1])

# Separate the data point across axises:
x_data = [v[0] for v in vectors_set]
y_data = [v[1] for v in vectors_set]

# Plot and show the data points in a 2D space
plt.plot(x_data, y_data, 'r*', label='Original data')
plt.legend()
plt.show()

如果编译器没有报错,你应该能看到以下图形:

开始使用 TensorFlow – 线性回归及其应用

图 12:随机生成(但原始的)数据

到目前为止,我们只是创建了一些数据点,但没有任何可以通过 TensorFlow 执行的关联模型。那么,下一步是创建一个线性回归模型,以便能够从输入数据点(即x_data)中估计输出值y。在这个上下文中,我们只有两个相关参数,即Wb。现在的目标是创建一个图形,允许通过调整Wb的值,使其根据输入数据x_data来拟合y_data,即优化问题。

所以在我们的例子中,目标函数将如下所示:

开始使用 TensorFlow——线性回归及更多

如果你还记得,我们在创建二维空间中的数据点时定义了W = 0.1b = 0.4。现在 TensorFlow 需要优化这两个值,使得W趋近于 0.1,b趋近于 0.4,但如果没有任何优化函数,TensorFlow 甚至无法知道这些。

解决这种优化问题的标准方法是通过每个数据点的值进行迭代,并调整Wb的值,以便在每次迭代中获得更精确的答案。现在,为了检查这些值是否真正改进,我们需要定义一个代价函数来衡量某条直线的好坏。

在我们的例子中,代价函数是均方误差,它有助于基于每次迭代中真实数据点和估计数据点之间的距离函数,找到误差的平均值。我们首先导入 TensorFlow 库:

import tensorflow as tf
W = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
b = tf.Variable(tf.zeros([1]))
y = W * x_data + b

在前面的代码段中,我们使用不同的策略生成一个随机点,并将其存储在变量 W 中。现在我们来定义一个损失函数loss=mean [(y−y_data) 2],它返回一个标量值,表示我们数据与模型预测之间所有距离的均值。按照 TensorFlow 的约定,损失函数可以表示如下:

loss = tf.reduce_mean(tf.square(y - y_data))

不深入讨论,我们可以使用一些广泛使用的优化算法,例如梯度下降。在最基本的层面,梯度下降是一种算法,它在一组已有的参数上工作。它从一组初始参数值开始,通过迭代逐步朝着一组最小化函数的值前进,并采用另一个参数称为学习率。这个迭代最小化是通过沿着函数的负方向(称为梯度)进行步进来实现的。

optimizer = tf.train.GradientDescentOptimizer(0.6)
train = optimizer.minimize(loss)

在运行这个优化函数之前,我们需要初始化到目前为止所有的变量。让我们按照 TensorFlow 的约定进行初始化,如下所示:

init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)

由于我们已经创建了一个 TensorFlow 会话,我们已经准备好进行迭代过程,帮助我们找到Wb的最优值:

for i in range(16):
  sess.run(train)
  print(i, sess.run(W), sess.run(b), sess.run(loss))

你应该看到以下输出:

>>>
0 [ 0.18418592] [ 0.47198644] 0.0152888
1 [ 0.08373772] [ 0.38146532] 0.00311204
2 [ 0.10470386] [ 0.39876288] 0.00262051
3 [ 0.10031486] [ 0.39547175] 0.00260051
4 [ 0.10123629] [ 0.39609471] 0.00259969
5 [ 0.1010423] [ 0.39597753] 0.00259966
6 [ 0.10108326] [ 0.3959994] 0.00259966
7 [ 0.10107458] [ 0.39599535] 0.00259966

因此,你可以看到算法从W = 0.18418592 和 b = 0.47198644的初始值开始,此时损失较高。然后,算法通过最小化成本函数来迭代调整这些值。在第八次迭代时,所有值趋近于我们期望的值。

那么,如果我们能够绘制它们呢?让我们通过在for循环下方添加绘图代码来实现,如下所示:

开始使用 TensorFlow – 线性回归及其扩展

图 13:第八次迭代后的线性回归,优化了损失函数

现在,让我们将迭代次数增加到第 16 次:

>>>
0 [ 0.23306453] [ 0.47967502] 0.0259004
1 [ 0.08183448] [ 0.38200468] 0.00311023
2 [ 0.10253634] [ 0.40177572] 0.00254209
3 [ 0.09969243] [ 0.39778906] 0.0025257
4 [ 0.10008509] [ 0.39859086] 0.00252516
5 [ 0.10003048] [ 0.39842987] 0.00252514
6 [ 0.10003816] [ 0.39846218] 0.00252514
7 [ 0.10003706] [ 0.39845571] 0.00252514
8 [ 0.10003722] [ 0.39845699] 0.00252514
9 [ 0.10003719] [ 0.39845672] 0.00252514
10 [ 0.1000372] [ 0.39845678] 0.00252514
11 [ 0.1000372] [ 0.39845678] 0.00252514
12 [ 0.1000372] [ 0.39845678] 0.00252514
13 [ 0.1000372] [ 0.39845678] 0.00252514
14 [ 0.1000372] [ 0.39845678] 0.00252514
15 [ 0.1000372] [ 0.39845678] 0.00252514

好得多,我们离优化后的值更近了,对吧?那么,接下来我们如何进一步通过 TensorFlow 提高可视化分析,帮助我们更好地理解这些图表中发生的事情。TensorBoard 提供了一个网页,供你调试图形并检查所用的变量、节点、边以及它们的连接关系。

然而,为了获取前面回归分析的功能,你需要为前面的图形注释一些变量,如损失函数、Wby_datax_data等。然后,你需要通过调用函数tf.summary.merge_all()来生成所有的汇总信息。

现在,我们需要对前面的代码进行以下更改。不过,使用tf.name_scope()函数将相关节点分组是一种好习惯。因此,我们可以使用tf.name_scope()来组织 TensorBoard 图形视图中的内容,但我们为它起个更合适的名字:

with tf.name_scope("LinearRegression") as scope:
   W = tf.Variable(tf.random_uniform([1], -1.0, 1.0), name="Weights")
   b = tf.Variable(tf.zeros([1]))y = W * x_data + b

接着,我们以类似的方式注释损失函数,但要给它起个合适的名字,如LossFunction

with tf.name_scope("LossFunction") as scope:
  loss = tf.reduce_mean(tf.square(y - y_data))

让我们注释 TensorBoard 所需的损失、权重和偏差:

loss_summary = tf.summary.scalar("loss", loss)
w_ = tf.summary.histogram("W", W)
b_ = tf.summary.histogram("b", b)

好了,一旦你注释了图形,接下来就该通过合并它们来配置汇总:

merged_op = tf.summary.merge_all()

现在,在运行训练之前(初始化后),使用tf.summary.FileWriter() API 来编写汇总信息,如下所示:

writer_tensorboard = tf.summary.FileWriter('/home/asif/LR/', sess.graph_def)

然后按如下方式启动 TensorBoard:

$ tensorboard –logdir=<trace_file_name>

在我们的例子中,它可能是如下所示:

$ tensorboard --logdir=/home/asif/LR/

现在,让我们转到http://localhost:6006,点击GRAPHS标签后,你应该会看到如下图:

开始使用 TensorFlow – 线性回归及其扩展

图 14:TensorBoard 中的主图和辅助节点

线性回归的源代码

我们报告了之前描述的示例的完整源代码:

# Import libraries (Numpy, Tensorflow, matplotlib)
import numpy as np
import matplotlib.pyplot as plot

# Create 1000 points following a function y=0.1 * x + 0.4 (i.e. y = W * x + b) with some normal random distribution:
num_points = 1000
vectors_set = []
for i in range(num_points):
    W = 0.1  # W
    b = 0.4  # b
    x1 = np.random.normal(0.0, 1.0)
    nd = np.random.normal(0.0, 0.05)
    y1 = W * x1 + b

# Add some impurity with some normal distribution -i.e. nd:y1 = y1 + nd

# Append them and create a combined vector set:
    vectors_set.append([x1, y1])

# Separate the data point across axises
x_data = [v[0] for v in vectors_set]
y_data = [v[1] for v in vectors_set]

# Plot and show the data points in a 2D space
plot.plot(x_data, y_data, 'ro', label='Original data')
plot.legend()
plot.show()

import tensorflow as tf

#tf.name_scope organize things on the tensorboard graph view
with tf.name_scope("LinearRegression") as scope:
   W = tf.Variable(tf.random_uniform([1], -1.0, 1.0), name="Weights")
   b = tf.Variable(tf.zeros([1]))
   y = W * x_data + b

# Define a loss function that takes into account the distance between the prediction and our dataset
with tf.name_scope("LossFunction") as scope:
   loss = tf.reduce_mean(tf.square(y - y_data))

optimizer = tf.train.GradientDescentOptimizer(0.6)
train = optimizer.minimize(loss)

# Annotate loss, weights, and bias (Needed for tensorboard)
loss_summary = tf.summary.scalar("loss", loss)
w_ = tf.summary.histogram("W", W)
b_ = tf.summary.histogram("b", b)

# Merge all the summaries
merged_op = tf.summary.merge_all()

init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)

# Writer for TensorBoard  (replace with our preferred location
writer_tensorboard = tf.summary.FileWriter('/ LR/', sess.graph_def)

for i in range(16):
   sess.run(train)
   print(i, sess.run(W), sess.run(b), sess.run(loss))
   plot.plot(x_data, y_data, 'ro', label='Original data')
   plot.plot(x_data, sess.run(W)*x_data + sess.run(b))
   plot.xlabel('X')
   plot.xlim(-2, 2)
   plot.ylim(0.1, 0.6)
   plot.ylabel('Y')
   plot.legend()
   plot.show()
# Finally, close the TensorFlow session when you're done
sess.close()

Ubuntu 可能会提示你安装 python-tk 包。你可以通过在 Ubuntu 上执行以下命令来安装:

$ sudo apt-get install python-tk
# For Python 3.x, use the following
$ sudo apt-get install python3-tk

总结

TensorFlow 的设计目的是使机器学习和深度学习中的预测分析变得简单易懂,但使用它确实需要理解一些基本原理和算法。此外,TensorFlow 的最新版本带来了许多令人兴奋的功能。因此,我也尝试覆盖这些功能,帮助你更轻松地使用它们。我展示了如何在不同平台上安装 TensorFlow,包括 Linux、Windows 和 Mac OS。总之,以下是本课中解释的 TensorFlow 的关键概念的简要回顾:

  • :每个 TensorFlow 计算可以表示为一组数据流图,其中每个图都作为一组操作对象构建。图的核心数据结构有三种:

    1. tf.Graph

    2. tf.Operation

    3. tf.Tensor

  • 操作:图中的一个节点将张量作为输入,并且也产生一个张量作为输出。一个节点可以通过操作对象来表示,执行加法、乘法、除法、减法或更复杂操作等计算单位。

  • 张量:张量类似于高维数组对象。换句话说,它们可以表示为数据流图的边缘,但它们并不持有由操作产生的任何值。

  • 会话:会话对象是一个封装了操作对象执行环境的实体,用于在数据流图上运行计算。因此,张量对象在run()eval()调用中被评估。

在课程的后续部分,我们介绍了 TensorBoard,它是一个强大的工具,用于分析和调试神经网络模型,本课最后通过一个示例展示了如何实现一个简单的神经元模型,以及如何通过 TensorBoard 分析其学习过程。

预测模型通常在实时交易中进行计算,例如评估给定客户或交易的风险或机会,以指导决策。随着计算速度的提升,单个代理建模系统已经能够模拟人类的行为或对给定刺激或场景的反应。

在下一课中,我们将介绍用于回归、分类和聚类的线性模型,以及降维方法,并对一些性能度量进行一些洞察。

评估

  1. 每个张量由一个维度单位描述,称为 ____。

    1. 数据类型

    2. 排序

    3. 变量

    4. 获取

  2. 判断下列陈述是否正确:TensorFlow 使用计算图来执行应用程序,其中每个节点表示一个操作,弧线是操作之间的数据。

  3. 判断下列陈述是否正确:NumPy 支持 n 维数组,但不提供创建张量函数和自动计算导数的方法(+ 不支持 GPU)。

  4. 一个 TensorFlow 图包含哪些对象?

  5. 当你使用 TensorFlow 执行稍微复杂的操作时,例如训练线性回归,TensorFlow 会在内部使用数据流图表示其计算。这个图被称为?

    1. 数据流图

    2. 线性图

    3. 计算图

    4. 回归图

第二章:将数据整理到位——用于预测分析的监督学习

在本课程中,我们将从理论和实践的角度讨论监督学习。特别是,我们将重新审视第一章中讨论的线性回归模型,通过使用真实数据集进行回归分析。然后,我们将看到如何使用逻辑回归LR)、随机森林和支持向量机SVM)来开发泰坦尼克号生存预测模型。

简而言之,本课程将涵盖以下主题:

  • 用于预测分析的监督学习

  • 用于预测分析的线性回归:重新审视

  • 用于预测分析的逻辑回归

  • 用于预测分析的随机森林

  • 用于预测分析的 SVM

  • 比较分析

用于预测分析的监督学习

根据可用的学习反馈类型,机器学习过程通常被分为三大类:监督学习、无监督学习和强化学习——见图 1。基于监督学习算法的预测模型可以根据标记数据集做出预测,该数据集将输入与现实世界中的输出相匹配。

例如,垃圾邮件过滤的数据集通常包含垃圾邮件和非垃圾邮件。因此,我们可以知道训练集中哪些消息是垃圾邮件,哪些是正常邮件。然而,我们可能有机会利用这些信息来训练我们的模型,以便对新的未见消息进行分类:

监督学习用于预测分析

图 1:机器学习任务(仅包含少数几个算法)

下图展示了监督学习的示意图。当算法找到所需的模式后,这些模式可以用于对未标记的测试数据进行预测:

监督学习用于预测分析

图 2:监督学习的实际应用

示例包括分类和回归,用于解决监督学习问题,从而可以基于它们建立预测模型进行预测分析。我们将提供几种监督学习的示例,如线性回归、逻辑回归、随机森林、决策树、朴素贝叶斯、多层感知机等。

在本课程中,我们将主要关注用于预测分析的监督学习算法。让我们从非常简单的线性回归算法开始。

线性回归——重新审视

在第一课《从数据到决策——TensorFlow 入门》中,我们看到了一个线性回归的例子。我们观察了如何在随机生成的数据集(即假数据)上使用 TensorFlow。我们了解到,回归是一种监督学习,用于预测连续值输出。然而,在假数据上运行线性回归就像是买了一辆新车却从未开过它。这台令人惊叹的机器渴望在现实世界中发挥作用!

幸运的是,许多数据集可以在网上找到,用于测试你新学到的回归知识:

因此,在这一部分,通过定义一组模型,我们将看到如何减少可能函数的搜索空间。此外,TensorFlow 利用这些函数的可微分特性,运行高效的梯度下降优化器来学习参数。为了避免过拟合数据,我们通过对较大值的参数施加惩罚来正则化成本函数。

线性回归在第一课《从数据到决策——TensorFlow 入门》中展示,其中有一些张量仅包含一个标量值,但你当然可以对任意形状的数组执行计算。在 TensorFlow 中,诸如加法和乘法等操作需要两个输入,并生成一个输出。相反,常量和变量不需要任何输入。我们还将看到一个例子,展示 TensorFlow 如何操作 2D 数组来执行类似线性回归的操作。

问题陈述

在线电影评分和推荐已成为全球重要的商业之一。例如,好莱坞每年在美国票房的收入约为 100 亿美元。像 Rotten Tomatoes 这样的电影网站将电影评论汇总成一个综合评分,并报告糟糕的首映周末。尽管单一的电影评论家或负面评论不能决定一部电影的成败,但成千上万的评论和评论家却能。

Rotten Tomatoes、Metacritic 和 IMDb 有自己汇总电影评论和独特的评分系统。另一方面,Fandango,NBCUniversal 的子公司,使用五颗星的评分系统,按照 FiveThirtyEight 的分析,大多数电影至少获得三颗星。

对 Fandango 使用的数据集进行的探索性分析显示,在 510 部电影中,437 部电影至少获得了一条评论,令人发笑的是,98% 的电影评分为 3 星或更高,75% 的电影评分为 4 星或更高。这意味着,根据 Fandango 的标准,一部电影几乎不可能在票房上失败。因此,Fandango 的评分存在偏见和失真:

问题陈述

图 3:Fandango 评分曲线的失衡现象

(来源:fivethirtyeight.com/features/fandango-movies-ratings/)

由于 Fandango 的评分不可靠,我们将根据 IMDb 评分来预测我们自己的评分。更具体地说,这是一个多元回归问题,因为我们的预测模型将使用多个特征来进行评分预测,拥有多个预测变量。

幸运的是,数据集足够小,可以适应内存,因此普通的批量学习就能很好地完成任务。考虑到这些因素和需求,我们会发现线性回归能够满足我们的要求。然而,对于更强大的回归分析,你仍然可以使用基于深度神经网络的回归技术,比如深度置信网络回归器(Deep Belief Networks Regressor)。

使用线性回归进行电影评分预测

现在,第一步是从 GitHub 下载 Fandango 的评分数据集,网址为 github.com/fivethirtyeight/data/tree/master/fandango。该数据集包含了所有拥有 Rotten Tomatoes 评分、RT 用户评分、Metacritic 评分、Metacritic 用户评分、IMDb 评分,且在 Fandango 上至少有 30 条影迷评论的电影。

表 1:fandango_score_comparison.csv 中各列的说明

该数据集有 22 列,具体说明如下:

列名 定义
FILM 电影名称。
RottenTomatoes Rotten Tomatoes 为电影提供的对应番茄评分(Tomatometer score)。
RottenTomatoes_User Rotten Tomatoes 用户评分。
Metacritic Metacritic 影评人评分。
Metacritic_User Metacritic 用户评分。
IMDB IMDb 用户评分。
Fandango_Stars 电影在 Fandango 电影页面上的星级评分。
Fandango_Ratingvalue 从每个页面的 HTML 中提取出的 Fandango 评分值。即电影实际获得的平均分数。
RT_norm 电影的番茄评分(Tomatometer score)。它被标准化为 0 到 5 分制。
RT_user_norm Rotten Tomatoes 用户评分,已标准化为 0 到 5 分制。
Metacritic_norm 电影的 Metacritic 影评人评分,已标准化为 0 到 5 分制。
Metacritic_user_nom Metacritic 用户评分,已标准化为 0 到 5 分制。
IMDB_norm IMDb 用户评分,已标准化为 0 到 5 分制。
RT_norm_round 电影的烂番茄 Tomatometer 评分,已规范化为 0 到 5 分的系统,并四舍五入至最接近的半颗星。
RT_user_norm_round 电影的烂番茄用户评分,已规范化为 0 到 5 分的系统,并四舍五入至最接近的半颗星。
Metacritic_norm_round 电影的 Metacritic 评论员评分,已规范化为 0 到 5 分的系统,并四舍五入至最接近的半颗星。
Metacritic_user_norm_round 电影的 Metacritic 用户评分,已规范化为 0 到 5 分的系统,并四舍五入至最接近的半颗星。
IMDB_norm_round 电影的 IMDb 用户评分,已规范化为 0 到 5 分的系统,并四舍五入至最接近的半颗星。
Metacritic_user_vote_count 电影在 Metacritic 上的用户投票数量。
IMDB_user_vote_count 电影在 IMDb 上的用户投票数量。
Fandango_votes 电影在 Fandango 上的用户投票数量。
Fandango_Difference 显示的Fandango_Stars与实际Fandango_Ratingvalue之间的差异。

我们已经看到,使用 TensorFlow 的典型线性回归问题具有以下工作流程,它更新参数以最小化 Fandango 的偏斜评分曲线的给定成本函数:

使用线性回归进行电影评分预测

图 4:使用 TensorFlow 进行线性回归的学习算法

现在,让我们尝试遵循前面的图示,并重新制作相同的线性回归:

  1. 导入所需的库:

    import numpy as np
    import pandas as pd
    from scipy import stats
    import sklearn
    from sklearn.model_selection import train_test_split
    import tensorflow as tf
    import matplotlib
    import matplotlib.pyplot as plt
    import seaborn as sns
    
  2. 读取数据集并创建一个 Panda DataFrame:

    df = pd.read_csv('fandango_score_comparison.csv')
    print(df.head())
    

    输出结果如下:

    使用线性回归进行电影评分预测

    图 5:展示 Metacritic_user_nom 中的错别字的数据集快照

    因此,如果你仔细查看前面的 DataFrame,会发现一个可能导致灾难的错别字。从直觉来看,很明显Metacritic_user_nom应该是Metacritic_user_norm。我们将其重命名,以避免进一步的混淆:

    df.rename(columns={'Metacritic_user_nom':'Metacritic_user_norm'}, inplace=True)
    

    此外,根据fivethirtyeight.com/features/fandango-movies-ratings/上的统计分析,所有变量并非等同贡献;以下列出的列对电影排名的影响更大:

     'Fandango_Stars',
    'RT_user_norm',
    'RT_norm',
    'IMDB_norm',
    'Metacritic_user_norm',
    'Metacritic_norm'
    

    现在,我们可以在构建 LR 模型之前检查变量之间的相关系数。首先,让我们为此创建一个排名列表:

    rankings_lst = ['Fandango_Stars',
                    'RT_user_norm',
                    'RT_norm',
                    'IMDB_norm',
                    'Metacritic_user_norm',
                    'Metacritic_norm']
    

    以下函数计算Pearson相关系数并构建完整的相关矩阵:

    def my_heatmap(df):    
        import seaborn as sns    
        fig, axes = plt.subplots()
        sns.heatmap(df, annot=True)
        plt.show()
        plt.close()
    

    我们可以调用前述方法来绘制矩阵,如下所示:

    my_heatmap(df[rankings_lst].corr(method='pearson'))
    

    注意

    皮尔逊相关系数:衡量两个变量之间线性关系强度的指标。如果变量之间的关系不是线性的,则相关系数不能准确而充分地表示这两个变量之间关系的强度。通常,当对总体进行测量时,它用“ρ”表示,而对样本进行测量时用“r”表示。从统计学角度看,相关系数的范围是-1 到 1,其中-1 表示完全负线性关系,r 为 0 表示没有线性关系,r 为 1 表示完全正线性关系。

    以下相关矩阵显示了使用皮尔逊相关系数的特征之间的相关性:

    使用线性回归进行电影评分预测

    图 6:排名电影的相关矩阵

    所以,Fandango 与 Metacritic 之间的相关性依然是正相关。接下来,让我们通过只考虑 RT 至少给出 4 星评分的电影来进行另一个研究:

    RT_lst = df['RT_norm'] >= 4.
    my_heatmap(df[RT_lst][rankings_lst].corr(method='pearson'))
    >>>
    

    输出的是排名电影和 RT 评分至少为 4 的电影的相关矩阵,显示了使用皮尔逊相关系数的特征之间的相关性:

    使用线性回归进行电影评分预测

    图 7:排名电影和 RT 至少评分为 4 的电影的相关矩阵

    这次,我们发现 Fandango 与 Metacritic 之间存在反相关(即负相关),相关系数为-0.23。这意味着 Metacritic 在 Fandango 的评分上存在显著的偏向高分。

    因此,我们可以在不考虑 Fandango 评分的情况下训练我们的模型,但在此之前,我们先使用这个构建 LR 模型。之后我们将决定哪个选项最终会产生更好的结果。

  3. 准备训练集和测试集。

    通过选择两个数据框列来创建一个特征矩阵X

    feature_cols = ['Fandango_Stars', 'RT_user_norm', 'RT_norm', 'Metacritic_user_norm', 'Metacritic_norm']
    X = df.loc[:, feature_cols]
    

    在这里,我只使用了选定的列作为特征,现在我们需要创建一个响应向量y

    y = df['IMDB_norm']
    

    我们假设 IMDB 是最可靠的评分基准来源。我们的最终目标是预测每部电影的评分,并将预测评分与响应列IMDB_norm进行比较。

    现在我们已经有了特征列和响应列,接下来是将数据划分为训练集和测试集:

    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.50, random_state=43)
    

    如果你想更改random_state,它将帮助你生成伪随机数,用于随机抽样,从而得到不同的最终结果。

    注意

    随机状态:顾名思义,它可用于初始化内部随机数生成器,从而决定数据划分为训练集和测试集的方式。这也意味着每次运行时,如果不指定random_state,你将得到不同的结果,这是预期的行为。因此,我们可以有以下三种选择:

    • 如果random_state为 None(或np.random),将返回一个随机初始化的RandomState对象。

    • 如果random_state是一个整数,它将用于初始化一个新的RandomState对象。

    • 如果random_state是一个RandomState对象,它会被传递进去。

    现在,我们需要知道数据集的维度,以便将其传递给张量:

    dim = len(feature_cols)
    

    我们需要为独立系数添加一个额外的维度:

    dim += 1
    
    

    因此,我们还需要为训练集和测试特征集中的独立系数创建一个额外的列:

    X_train = X_train.assign( independent = pd.Series([1] * len(y_train), index=X_train.index))
    X_test = X_test.assign( independent = pd.Series([1] * len(y_train), index=X_test.index))
    

    到目前为止,我们已经使用并利用了 Pandas DataFrame,但将其转换为张量很麻烦,因此我们改为将它们转换为 NumPy 数组:

    P_train = X_train.as_matrix(columns=None)
    P_test = X_test.as_matrix(columns=None)
    
    q_train = np.array(y_train.values).reshape(-1,1)
    q_test = np.array(y_test.values).reshape(-1,1)
    
  4. 为 TensorFlow 创建占位符。

    现在我们已经有了所有的训练集和测试集,在初始化这些变量之前,我们需要为 TensorFlow 创建占位符,以便通过张量传递训练集:

    P = tf.placeholder(tf.float32,[None,dim])
    q = tf.placeholder(tf.float32,[None,1])
    T = tf.Variable(tf.ones([dim,1]))
    

    让我们添加一些偏差,以区分在两种类型量化的情况下的值,如下所示:

    bias = tf.Variable(tf.constant(1.0, shape = [n_dim]))
    q_ = tf.add(tf.matmul(P, T),bias)
    
  5. 创建优化器。

    让我们为目标函数创建一个优化器:

    cost = tf.reduce_mean(tf.square(q_ - q))
    learning_rate = 0.0001
    training_op = tf.train.GradientDescentOptimizer(learning_rate=learning_rate).minimize(cost)
    
  6. 初始化全局变量:

    init_op = tf.global_variables_initializer()
    cost_history = np.empty(shape=[1],dtype=float)
    
  7. 训练 LR 模型。

    在这里,我们将训练迭代 50,000 次,并跟踪多个参数,例如均方误差,它表示训练的好坏;我们保持成本历史记录,以便未来可视化,等等:

    training_epochs = 50000
    with tf.Session() as sess:
        sess.run(init_op)
        cost_history = np.empty(shape=[1], dtype=float)
        t_history = np.empty(shape=[dim, 1], dtype=float)
        for epoch in range(training_epochs):
            sess.run(training_op, feed_dict={P: P_train, q: q_train})
            cost_history = np.append(cost_history, sess.run(cost, feed_dict={P: P_train, q: q_train}))
            t_history = np.append(t_history, sess.run(T, feed_dict={P: P_train, q: q_train}), axis=1)
        q_pred = sess.run(q_, feed_dict={P: P_test})[:, 0]
        mse = tf.reduce_mean(tf.square(q_pred - q_test))
        mse_temp = mse.eval()
        sess.close()
    

    最后,我们评估mse,以便从测试集上的训练评估中得到标量值。现在,让我们计算msermse值,如下所示:

    print(mse_temp)
    RMSE = math.sqrt(mse_temp)
    print(RMSE)
    >>> 
    0.425983107542
    0.6526738140461913
    

    你也可以按如下方式更改特征列:

    feature_cols = ['RT_user_norm', 'RT_norm', 'Metacritic_user_norm', 'Metacritic_norm']
    

    现在我们不考虑 Fandango 的评分,我得到了msermse的以下结果:

    0.426362842426
    0.6529646563375979
    
  8. 观察整个迭代过程中的训练成本:

    fig, axes = plt.subplots()
    plt.plot(range(len(cost_history)), cost_history)
    axes.set_xlim(xmin=0.95)
    axes.set_ylim(ymin=1.e-2)
    axes.set_xscale("log", nonposx='clip')
    axes.set_yscale("log", nonposy='clip')
    axes.set_ylabel('Training cost')
    axes.set_xlabel('Iterations')
    axes.set_title('Learning rate = ' + str(learning_rate))
    plt.show()
    plt.close()
    >>>
    
    

    输出如下:

    使用线性回归进行电影评分预测

    图 8:训练和训练成本在 10000 次迭代后变得饱和

    上述图表显示训练成本在 10,000 次迭代后变得饱和。这也意味着,即使你将模型迭代超过 10,000 次,成本也不会出现显著下降。

  9. 评估模型:

    predictedDF = X_test.copy(deep=True)
    predictedDF.insert(loc=0, column='IMDB_norm_predicted', value=pd.Series(data=q_pred, index=predictedDF.index))
    predictedDF.insert(loc=0, column='IMDB_norm_actual', value=q_test)
    
    print('Predicted vs actual rating using LR with TensorFlow')
    print(predictedDF[['IMDB_norm_actual', 'IMDB_norm_predicted']].head())print(predictedDF[['IMDB_norm_actual', 'IMDB_norm_predicted']].tail())
    >>>
    

    以下显示了使用 LR 模型的预测与实际评分:

              IMDB_norm_actual  IMDB_norm_predicted
    45              3.30              3.232061
    50              3.35              3.381659
    98              3.05              2.869175
    119             3.60              3.796200
    133             2.15              2.521702
    140             4.30              4.033006
    143             3.70              3.816177
    42              4.10              3.996275
    90              3.05              3.226954
    40              3.45              3.509809
    

    我们可以看到,预测是一个连续的值。现在是时候看看 LR 模型如何推广并拟合回归线:

    How the LR fit with the predicted data points:
    plt.scatter(q_test, q_pred, color='blue', alpha=0.5)
    plt.plot([q_test.min(), q_test.max()], [q_test.min(), q_test.max()], '--', lw=1)
    plt.title('Predicted vs Actual')
    plt.xlabel('Actual')
    plt.ylabel('Predicted')
    plt.show()
    plt.show()
    
    >>>
    

    输出如下:

    使用线性回归进行电影评分预测

    图 9:LR 模型的预测结果

    图表没有告诉我们 LR 模型的预测是好是坏。但我们仍然可以通过使用像深度神经网络这样的层次结构来提高模型的性能。

    下一个例子是关于应用其他监督学习算法,如逻辑回归、支持向量机和随机森林进行预测分析。

从灾难到决策——重新审视泰坦尼克号示例

在第 1 课中,从数据到决策 - 使用 TensorFlow 入门,我们对泰坦尼克号数据集进行了最基本的数据分析。现在轮到我们基于数据做一些分析了。让我们来看看哪些人群在这场灾难中幸存了下来。

既然我们有足够的数据,如何进行预测建模,以便从这些数据中得出一些相对直接的结论呢?例如,女性、头等舱乘客和儿童都是提高乘客在灾难中生存几率的因素。

使用像 if-else 语句这样简单的强力法(结合某种加权评分系统),你可以编写一个程序来预测一个给定的乘客是否会在灾难中幸存。然而,用 Python 编写这样一个程序没有太多意义。显然,这样的程序会非常繁琐,难以推广,并且需要对每个变量和样本(即每个乘客)进行广泛的微调:

从灾难到决策 - 泰坦尼克号示例重访

图 10:回归算法的目的是产生连续的输出

在这一点上,你可能会对分类问题和回归问题之间的基本区别感到困惑。那么,回归算法的目的是产生连续的输出。输入可以是离散的,也可以是连续的。相比之下,分类算法的目的是从一组离散或连续值的输入中产生离散的输出。这一区别很重要,因为离散值的输出更适合由分类算法处理,接下来的章节将讨论这一点:

从灾难到决策 - 泰坦尼克号示例重访

图 11:分类算法的目的是产生离散的输出

在本节中,我们将看到如何为泰坦尼克号生存预测开发几种预测模型,并使用它们进行一些分析。特别地,我们将讨论逻辑回归、随机森林和线性支持向量机(SVM)。我们从逻辑回归开始。然后使用 SVM,因为特征数量并不多。最后,我们将看看如何利用随机森林提高性能。然而,在深入之前,我们需要对数据集进行一个简短的探索性分析。

泰坦尼克号数据集的探索性分析

我们将看到各个变量如何对生存率产生影响。首先,我们需要导入所需的包:

import os
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
import shutil

现在,让我们加载数据并检查可以使用的特征:

train = pd.read_csv(os.path.join('input', 'train.csv'))
test = pd.read_csv(os.path.join('input', 'test.csv'))
print("Information about the data")
print(train.info())
>>> 
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId    891 non-null int64
Survived       891 non-null int64
Pclass         891 non-null int64
Name           891 non-null object
Sex            891 non-null object
Age            714 non-null float64
SibSp          891 non-null int64
Parch          891 non-null int64
Ticket         891 non-null object
Fare           891 non-null float64
Cabin          204 non-null object
Embarked       889 non-null object

因此,训练数据集一共有12列和891行。此外,AgeCabinEmbarked列存在空值或缺失值。我们将在特征工程部分处理这些空值,但目前,让我们看看有多少数据是有效的:

print("How many have survived?")
print(train.Survived.value_counts(normalize=True))
count_plot = sns.countplot(train.Survived)
count_plot.get_figure().savefig("survived_count.png")
>>>

有多少人幸存了下来?

0    0.616162
1    0.383838

因此,约 61%的乘客死亡,只有 39%的乘客成功生还,如下图所示:

泰坦尼克号数据集的探索性分析

图 12:泰坦尼克号训练集中生还者与死亡者的对比

那么,乘客的舱位与生存率之间有什么关系呢?首先我们应该查看每个舱位的计数:

train['Name_Title'] = train['Name'].apply(lambda x: x.split(',')[1]).apply(lambda x: x.split()[0])
print('Title count')
print(train['Name_Title'].value_counts())
print('Survived by title')
print(train['Survived'].groupby(train['Name_Title']).mean())
>>> 	
Title      count
Mr.          517
Miss.        182
Mrs.         125
Master.       40
Dr.            7
Rev.           6
Mlle.          2
Col.           2
Major.         2
Sir.           1
Jonkheer.      1
Lady.          1
Capt.          1
the            1
Don.           1
Ms.            1
Mme.           1

正如你可能记得的那样(即电影《泰坦尼克号》1997 年版),来自较高阶层的人有更好的生还机会。所以,你可能会认为,乘客的头衔也可能是生还的重要因素。另一个有趣的现象是,名字较长的人生还的几率更高。这是因为大多数名字较长的人是已婚女士,可能得到了丈夫或家人帮助,从而增加了生还机会:

train['Name_Len'] = train['Name'].apply(lambda x: len(x))
print('Survived by name length')
print(train['Survived'].groupby(pd.qcut(train['Name_Len'],5)).mean())
>>>
Survived by name length 
(11.999, 19.0]    0.220588
(19.0, 23.0]      0.301282
(23.0, 27.0]      0.319797
(27.0, 32.0]      0.442424
(32.0, 82.0]      0.674556

妇女和儿童的生还几率更高,因为他们是最先撤离船难的乘客:

print('Survived by sex')
print(train['Survived'].groupby(train['Sex']).mean())
>>> 
Survived by sex
Sex
female    0.742038
male      0.188908

舱位字段包含最多的空值(近 700 个),但我们仍然可以从中提取信息,比如每个舱位的首字母。因此,我们可以看到大多数舱位字母与生存率相关:

train['Cabin_Letter'] = train['Cabin'].apply(lambda x: str(x)[0])
print('Survived by Cabin_Letter')
print(train['Survived'].groupby(train['Cabin_Letter']).mean())
>>>
Survived by Cabin_Letter
A    0.466667
B    0.744681
C    0.593220
D    0.757576
E    0.750000
F    0.615385
G    0.500000
T    0.000000
n    0.299854

最后,看起来在谢尔堡登船的人生还率比其他登船地点高出 20%。这很可能是因为那个地方的上层阶级乘客比例较高:

print('Survived by Embarked')
print(train['Survived'].groupby(train['Embarked']).mean())
count_plot = sns.countplot(train['Embarked'], hue=train['Pclass'])
count_plot.get_figure().savefig("survived_count_by_embarked.png")

>>> 
Survived by Embarked
C    0.553571
Q    0.389610
S    0.336957

从图形上看,前面的结果可以如下所示:

泰坦尼克号数据集的探索性分析

图 13:按登船情况分组的生还者

因此,有几个重要因素影响了人们的生存。这意味着我们在开发预测模型时需要考虑这些因素。

我们将训练几个二分类器,因为这是一个二分类问题,预测值为 0 或 1,使用训练集进行训练,并用测试集进行生存预测。

但是,在我们进行这一步之前,让我们先做一些特征工程,因为你已经看到有些数据缺失或为空。我们将通过填充这些空值或删除这些记录来处理训练集和测试集中的缺失值。此外,我们不能直接使用这些数据集,而需要对其进行处理,使其能够适应我们的机器学习模型。

特征工程

由于我们将乘客姓名的长度视为一个重要特征,最好将姓名本身去除,计算其对应的长度,并且提取出头衔:

def create_name_feat(train, test):

    for i in [train, test]:
        i['Name_Len'] = i['Name'].apply(lambda x: len(x))
        i['Name_Title'] = i['Name'].apply(lambda x: x.split(',')[1]).apply(lambda x: x.split()[0])
        del i['Name']
    return train, test

由于年龄字段有 177 个空值,而这些空值的生还率比非空值低 10%。因此,在填充空值之前,我们将添加一个 Age_null 标志,以确保我们能够考虑数据的这一特征:

def age_impute(train, test):
    for i in [train, test]:
        i['Age_Null_Flag'] = i['Age'].apply(lambda x: 1 if pd.isnull(x) else 0)
        data = train.groupby(['Name_Title', 'Pclass'])['Age']
        i['Age'] = data.transform(lambda x: x.fillna(x.mean()))
    return train, test

我们通过用该列的平均值填充空缺的年龄数据。这样会在数据集中加入一些额外的偏差。但为了提高我们的预测模型,我们不得不做出一些牺牲。

然后我们将SibSpParch列合并,创建家庭规模,并将其分为三个级别:

def fam_size(train, test):
    for i in [train, test]:
        i['Fam_Size'] = np.where((i['SibSp']+i['Parch']) == 0, 'One',
                                 np.where((i['SibSp']+i['Parch']) <= 3, 'Small', 'Big'))
        del i['SibSp']
        del i['Parch']
    return train, test
We are using the Ticket column to create Ticket_Letr, which indicates the first letter of each ticket and Ticket_Len, which indicates the length of the Ticket field:
def ticket_grouped(train, test):
    for i in [train, test]:
        i['Ticket_Letr'] = i['Ticket'].apply(lambda x: str(x)[0])
        i['Ticket_Letr'] = i['Ticket_Letr'].apply(lambda x: str(x))
        i['Ticket_Letr'] = np.where((i['Ticket_Letr']).isin(['1', '2', '3', 'S', 'P', 'C', 'A']),
                                    i['Ticket_Letr'],
                                    np.where((i['Ticket_Letr']).isin(['W', '4', '7', '6', 'L', '5', '8']),'Low_ticket', 'Other_ticket'))
        i['Ticket_Len'] = i['Ticket'].apply(lambda x: len(x))
        del i['Ticket']
    return train, test

我们还需要提取Cabin列的第一个字母:

def cabin(train, test):
    for i in [train, test]:
        i['Cabin_Letter'] = i['Cabin'].apply(lambda x: str(x)[0])
        del i['Cabin']
    return train, test

用最常见的值'S'填充Embarked列中的空值:

def embarked_impute(train, test):
    for i in [train, test]:
        i['Embarked'] = i['Embarked'].fillna('S')
    return train, test

现在我们需要转换我们的分类列。到目前为止,我们认为对我们将要创建的预测模型来说,字符串变量需要转换为数值。下面的dummies()函数对字符串变量进行一热编码:

def dummies(train, test,
            columns = ['Pclass', 'Sex', 'Embarked', 'Ticket_Letr', 'Cabin_Letter', 'Name_Title', 'Fam_Size']):
    for column in columns:
        train[column] = train[column].apply(lambda x: str(x))
        test[column] = test[column].apply(lambda x: str(x))
        good_cols = [column+'_'+i for i in train[column].unique() if i in test[column].unique()]
        train = pd.concat((train, pd.get_dummies(train[column], prefix=column)[good_cols]), axis=1)
        test = pd.concat((test, pd.get_dummies(test[column], prefix=column)[good_cols]), axis=1)
        del train[column]
        del test[column]
    return train, test

我们已经有了数值特征,最后,我们需要为预测值或目标创建一个单独的列:

def PrepareTarget(data):
    return np.array(data.Survived, dtype='int8').reshape(-1, 1)

我们已经看过数据及其特征,并进行了特征工程,构建了适合线性模型的最佳特征。接下来的任务是构建预测模型并在测试集上进行预测。让我们从逻辑回归开始。

生存预测的逻辑回归

逻辑回归是最广泛使用的二元响应预测分类器之一。它是一种线性机器学习方法,loss函数由逻辑损失公式给出:

Logistic Regression for Survival Prediction

对于逻辑回归模型,损失函数是逻辑损失。对于二元分类问题,该算法输出一个二元逻辑回归模型,给定一个新的数据点,记为x,模型通过应用逻辑函数来进行预测:

Logistic Regression for Survival Prediction

在前面的方程中,Logistic Regression for Survival Prediction 并且如果Logistic Regression for Survival Prediction,结果为正;否则,结果为负。注意,逻辑回归模型的原始输出f (z)具有概率解释。

好吧,如果现在将逻辑回归与其前身线性回归进行比较,前者提供了更高的分类准确率。此外,它是一种灵活的方式来对模型进行正则化,以便进行自定义调整,并且总的来说,模型的响应是概率的度量。最重要的是,尽管线性回归只能预测连续值,但逻辑回归可以广泛地推广到预测离散值。从现在起,我们将经常使用 TensorFlow contrib API。那么,让我们快速了解一下它。

使用 TensorFlow Contrib

contrib 是一个用于 TensorFlow 学习的高级 API。它支持以下估算器:

  • tf.contrib.learn.BaseEstimator

  • tf.contrib.learn.Estimator

  • tf.contrib.learn.Trainable

  • tf.contrib.learn.Evaluable

  • tf.contrib.learn.KMeansClustering

  • tf.contrib.learn.ModeKeys

  • tf.contrib.learn.ModelFnOps

  • tf.contrib.learn.MetricSpec

  • tf.contrib.learn.PredictionKey

  • tf.contrib.learn.DNNClassifier

  • tf.contrib.learn.DNNRegressor

  • tf.contrib.learn.DNNLinearCombinedRegressor

  • tf.contrib.learn.DNNLinearCombinedClassifier

  • tf.contrib.learn.LinearClassifier

  • tf.contrib.learn.LinearRegressor

  • tf.contrib.learn.LogisticRegressor

因此,我们无需从头开发逻辑回归模型,而是使用 TensorFlow contrib 包中的估计器。当我们从头创建自己的估计器时,构造函数仍然接受两个高层次的参数来配置模型:model_fnparams

nn = tf.contrib.learn.Estimator(model_fn=model_fn, params=model_params)

要实例化一个估计器,我们需要提供两个参数,如 model_fnmodel_params,如下所示:

nn = tf.contrib.learn.Estimator(model_fn=model_fn, params=model_params)

值得注意的是,model_fn() 函数包含了上述所有 TensorFlow 逻辑,以支持训练、评估和预测。因此,您只需要实现能高效使用它的功能。

现在,调用 main() 方法时,model_params 包含学习率,实例化估计器。您可以按如下方式定义 model_params

model_params = {"learning_rate": LEARNING_RATE}

注意

欲了解更多关于 TensorFlow contrib 的信息,感兴趣的读者可以访问此网址:www.tensorflow.org/extend/estimators

好的,到目前为止,我们已经掌握了足够的背景知识,可以用我们的数据集在 TensorFlow 中创建 LR 模型。是时候实现它了:

  1. 导入所需的包和模块:

    import os
    import shutil
    import random
    import pandas as pd
    import numpy as np
    import seaborn as sns
    import matplotlib.pyplot as plt
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import classification_report
    from sklearn.metrics import confusion_matrix
    from feature import *
    import tensorflow as tf
    from tensorflow.contrib.learn.python.learn.estimators import estimator
    from tensorflow.contrib import learn
    
  2. 加载和准备数据集。

    首先,我们加载这两个数据集:

    random.seed(12345) # For the reproducibility 
    train = pd.read_csv(os.path.join('input', 'train.csv'))
    test = pd.read_csv(os.path.join('input', 'test.csv'))
    

    让我们做一些特征工程。我们将调用在特征工程部分定义的函数,但该函数将作为名为 feature.py 的独立 Python 脚本提供:

    train, test = create_name_feat(train, test)
    train, test = age_impute(train, test)
    train, test = cabin(train, test)
    train, test = embarked_impute(train, test)
    train, test = fam_size(train, test)
    test['Fare'].fillna(train['Fare'].mean(), inplace=True)
    train, test = ticket_grouped(train, test)
    

    值得注意的是,上述调用的顺序对于确保训练集和测试集的一致性至关重要。现在,我们还需要使用 sklearn 的 dummies() 函数为分类变量创建数值:

    train, test = dummies(train, test, columns=['Pclass', 'Sex', 'Embarked', 'Ticket_Letr', 'Cabin_Letter', 'Name_Title', 'Fam_Size'])
    

    我们需要准备训练集和测试集:

    TEST = True
    if TEST:
        train, test = train_test_split(train, test_size=0.25, random_state=10)
        train = train.sort_values('PassengerId')
        test = test.sort_values('PassengerId')
    
    X_train = train.iloc[:, 1:]
    x_test = test.iloc[:, 1:]
    

    然后我们将训练集和测试集转换为 NumPy 数组,因为到目前为止我们一直将它们保存在 Pandas DataFrame 格式中:

    x_train = np.array(x_train.iloc[:, 1:], dtype='float32')
    if TEST:
     x_test = np.array(x_test.iloc[:, 1:], dtype='float32')
    else:
        x_test = np.array(x_test, dtype='float32')
    

    让我们准备目标列进行预测:

    y_train = PrepareTarget(train)
    

    我们还需要知道特征数量,以便构建 LR 估计器:

    feature_count = x_train.shape[1]
    
    
  3. 准备 LR 估计器。

    我们构建 LR 估计器。我们将使用 LinearClassfier 估计器进行构建。由于这是一个二分类问题,我们提供了两个类别:

    def build_lr_estimator(model_dir, feature_count):
        return estimator.SKCompat(learn.LinearClassifier(
            feature_columns=[tf.contrib.layers.real_valued_column("", dimension=feature_count)],
            n_classes=2, model_dir=model_dir))
    
  4. 训练模型。

    在这里,我们训练上述 LR 估计器10,000次迭代。fit() 方法完成了训练,而 predict() 方法计算训练集的预测结果,其中包含特征 X_train 和标签 y_train

    print("Training...")
    try:
        shutil.rmtree('lr/')
    except OSError:
        pass
    lr = build_lr_estimator('lr/', feature_count)
    lr.fit(x_train, y_train, steps=1000)
    lr_pred = lr.predict(x_test)
    lr_pred = lr_pred['classes']
    
  5. 模型评估。

    我们将评估模型,查看几个分类性能指标,如精度、召回率、F1 分数和混淆矩阵:

    if TEST:
     target_names = ['Not Survived', 'Survived']
     print("Logistic Regression Report")
     print(classification_report(test['Survived'], lr_pred, target_names=target_names))
     print("Logistic Regression Confusion Matrix")
    
    >>>
    Logistic Regression Report
     precision    recall  f1-score   support
    Not Survived       0.90         0.88      0.89       147
    Survived           0.78         0.80      0.79        76---------------------------------------------------------
     avg / total       0.86         0.86       0.86       223
    

    由于我们用 NumPy 数据训练了 LR 模型,现在我们需要将其转换回 Pandas DataFrame 格式,以便创建混淆矩阵:

    cm = confusion_matrix(test['Survived'], lr_pred)
        df_cm = pd.DataFrame(cm, index=[i for i in ['Not Survived', 'Survived']],
                             columns=[i for i in ['Not Survived', 'Survived']])
        print(df_cm)
    
    >>> 
    Logistic Regression Confusion Matrix
                  Not Survived  Survived
    Not Survived           130        17
    Survived               15         61
    

    现在,让我们看看计数:

    print("Predicted Counts")
    print(sol.Survived.value_counts())
    
    >>> 
    Predicted Counts
    0    145
    1     78
    

    由于以图形方式查看计数非常棒,我们来绘制一下它:

    sol = pd.DataFrame()
    sol['PassengerId'] = test['PassengerId']
    sol['Survived'] = pd.Series(lr_pred.reshape(-1)).map({True:1, False:0}).values
    sns.plt.suptitle("Predicted Survived LR")
    count_plot = sns.countplot(sol.Survived)
    count_plot.get_figure().savefig("survived_count_lr_prd.png")
    
    >>>
    

    输出结果如下:

    使用 TensorFlow Contrib

    图 14:使用 TensorFlow 进行逻辑回归的生存预测

所以,我们使用 LR 模型获得的准确率是 86%,这已经算不错了。但它仍然可以通过更好的预测模型进行改进。在下一部分,我们将尝试使用线性 SVM 来进行生存预测。

用于生存预测的线性 SVM

线性 SVM 是大规模分类任务中最广泛使用和标准的方法之一。多分类和二分类问题都可以通过使用 hinge loss 损失函数的 SVM 来解决:

Linear SVM for Survival Prediction

通常,线性 SVM 使用 L2 正则化进行训练。最终,线性 SVM 算法会输出一个可以用来预测未知数据标签的 SVM 模型。

假设你有一个未知的数据点,x,SVM 模型根据 Linear SVM for Survival Prediction 的值进行预测。结果可以是正的或负的。更具体地说,如果 Linear SVM for Survival Prediction,那么预测值为正;否则,预测值为负。

当前版本的 TensorFlow contrib 包仅支持线性 SVM。TensorFlow 使用 SDCAOptimizer 作为底层优化器。现在的问题是,如果你想要构建自己的 SVM 模型,你需要考虑性能和收敛调整的问题。幸运的是,你可以将 num_loss_partitions 参数传递给 SDCAOptimizer 函数。但你需要设置 X,使其在每个工作节点的并发训练操作中收敛。

如果你将 num_loss_partitions 设置为大于或等于这个值,收敛是有保证的,但随着 num_loss_partitions 增加,整体训练会变得更慢。另一方面,如果你将其值设置得较小,优化器在减少全局损失时会更具攻击性,但收敛性无法保证。

注意

想了解更多实现的 contrib 包,感兴趣的读者可以参考这个网址 github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/learn/python/learn/estimators

好的,到目前为止,我们已经掌握了创建 SVM 模型所需的足够背景知识,现在是时候实现它了:

  1. 导入所需的包和模块:

    import os
    import shutil
    import random
    import pandas as pd
    import seaborn as sns
    import matplotlib.pyplot as plt
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import classification_report
    from sklearn.metrics import confusion_matrix
    from feature import *
    import tensorflow as tf
    from tensorflow.contrib.learn.python.learn.estimators import svm
    
  2. 构建 SVM 模型的数据集准备:

    现在,构建 SVM 模型的数据准备工作与 LR 模型差不多,只是我们需要将 PassengerId 转换为字符串,因为 SVM 需要这个格式:

    train['PassengerId'] = train['PassengerId'].astype(str)
    test['PassengerId'] = test['PassengerId'].astype(str)
    
  3. 为连续特征列创建一个 SVM 字典。

    注意

    为了将数据传入 SVM 模型,我们需要进一步创建一个字典,将每个连续特征列的名称(k)映射到该列存储在常量张量中的值。有关此问题的更多信息,请参见 TensorFlow GitHub 仓库中的此问题 github.com/tensorflow/tensorflow/issues/9505

    我为特征和标签都编写了两个函数。让我们看看第一个函数是什么样的:

    def train_input_fn():
        continuous_cols = {k: tf.expand_dims(tf.constant(train[k].values), 1)
                           for k in list(train) if k not in ['Survived', 'PassengerId']}
        id_col = {'PassengerId' : tf.constant(train['PassengerId'].values)}
        feature_cols = continuous_cols.copy()
        feature_cols.update(id_col)
        label = tf.constant(train["Survived"].values)
        return feature_cols, label
    

    上述函数创建了一个字典,将每个连续特征列映射到一个字典,然后又为 passengerId 列创建了一个字典。接着我将它们合并为一个字典。由于我们希望以 'Survived' 列作为标签,我将标签列转换为常量张量。最后,通过这个函数,我返回了特征列和标签。

    现在,第二种方法几乎做了相同的操作,唯一的区别是它只返回特征列,如下所示:

    def predict_input_fn():
        continuous_cols = {k: tf.expand_dims(tf.constant(test[k].values), 1)
                           for k in list(test) if k not in ['Survived', 'PassengerId']}
        id_col = {'PassengerId' : tf.constant(test['PassengerId'].values)}
        feature_cols = continuous_cols.copy()
        feature_cols.update(id_col)
        return feature_cols
    
  4. 训练 SVM 模型。

    现在,我们将只对实值列进行 10,000 次迭代训练。最后,它会创建一个包含所有预测值的预测列表:

    svm_model = svm.SVM(example_id_column="PassengerId",
                        feature_columns=[tf.contrib.layers.real_valued_column(k) for k in list(train)
                                         if k not in ['Survived', 'PassengerId']], 
                        model_dir="svm/")
    svm_model.fit(input_fn=train_input_fn, steps=10000)
    svm_pred = list(svm_model.predict_classes(input_fn=predict_input_fn))
    
  5. 模型评估:

    target_names = ['Not Survived', 'Survived']
    print("SVM Report")
    print(classification_report(test['Survived'], svm_pred, target_names=target_names))
    >>>
    SVM Report
                           precision    recall  f1-score   support
    Not Survived       0.94        0.72      0.82       117
    Survived           0.63        0.92      0.75        62--------------------------------------------------------
     avg / total       0.84         0.79      0.79       179
    

    因此,使用 SVM 时,准确率仅为 79%,低于 LR 模型的准确率。与 LR 模型类似,绘制并观察混淆矩阵:

    print("SVM Confusion Matrix")
    cm = confusion_matrix(test['Survived'], svm_pred)
    df_cm = pd.DataFrame(cm, index=[i for i in ['Not Survived', 'Survived']],
                            columns=[i for i in ['Not Survived', 'Survived']])
    print(df_cm)
    >>> 
    SVM Confusion Matrix
                  Not Survived  Survived
    Not Survived            84        33
    Survived                    5         57
    

    然后,我们绘制计数图,直观地查看比例:

    sol = pd.DataFrame()
    sol['PassengerId'] = test['PassengerId']
    sol['Survived'] = pd.Series(svm_pred).values
    sns.plt.suptitle("Titanic Survival prediction using SVM with TensorFlow")
    count_plot = sns.countplot(sol.Survived)
    

    输出如下:

    线性 SVM 进行生存预测

    图 15:使用 TensorFlow 的线性 SVM 进行生存预测

    现在,计数:

    print("Predicted Counts")
    print(sol.Survived.value_counts())
    
    >>> 
    Predicted Counts
    1    90
    0    89
    

生存预测的集成方法 – 随机森林

最常用的机器学习技术之一是集成方法,它是通过构建一组分类器来进行学习的算法。然后可以通过对预测结果加权投票的方式来对新的数据点进行分类。在本节中,我们将主要关注通过组合数百棵决策树来构建的随机森林。

决策树DT)是一种在监督学习中用于解决分类和回归任务的技术。决策树模型通过利用树状图来展示行动步骤,学习从数据特征中推断出的简单决策规则。决策树的每个分支表示一个可能的决策、事件或反应,基于统计概率:

生存预测的集成方法 – 随机森林

图 16:使用 R 的 rattle 包对入学测试数据集进行的样本决策树

与 LR 或 SVM 相比,决策树(DT)是更为稳健的分类算法。树根据训练数据中可用特征的划分推断预测标签或类别,从而产生良好的泛化效果。最有趣的是,该算法可以处理二分类和多分类问题。

例如,图 16 中的决策树从入学数据中学习,通过一组if...else决策规则来逼近正弦曲线。数据集包含每个申请入学学生的记录,比如申请美国大学的记录。每条记录包含研究生入学考试成绩、CGPA 成绩以及列的排名。现在,我们需要基于这三项特征(变量)来预测谁是合格的。

决策树(DTs)可以在训练 DT 模型并修剪掉不需要的树枝后用于解决这种问题。通常,较深的树表示更复杂的决策规则和更合适的模型。因此,树越深,决策规则越复杂,模型拟合度越高。

注意

如果你想绘制上面的图形,只需使用我的 R 脚本并在 RStudio 中执行,输入入学数据集。脚本和数据集可以在我的 GitHub 仓库中找到,链接为:github.com/rezacsedu/AdmissionUsingDecisionTree

到目前为止,我们已经获得了足够的背景知识来创建一个随机森林(RF)模型,现在是时候实现它了。

  1. 导入所需的包和模块:

    import os
    import shutil
    import random
    import pandas as pd
    import numpy as np
    import seaborn as sns
    import matplotlib.pyplot as plt
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import classification_report
    from sklearn.metrics import confusion_matrix
    from feature import *
    import tensorflow as tf
    from tensorflow.contrib.learn.python.learn.estimators import estimator
    from tensorflow.contrib.tensor_forest.client import random_forest
    from tensorflow.contrib.tensor_forest.python import tensor_forest
    
  2. 构建 RF 模型的数据集准备。

    现在,构建 RF 模型的数据准备与 LR 模型大致相同。因此,请参考逻辑回归部分。

  3. 构建随机森林估计器。

    以下函数构建了一个随机森林估计器。它创建了 1,000 棵树,最多 1,000 个节点,并进行了 10 倍交叉验证。由于这是一个二分类问题,我将类别数设为 2:

    def build_rf_estimator(model_dir, feature_count):
        params = tensor_forest.ForestHParams(
            num_classes=2,
            num_features=feature_count,
            num_trees=1000,
            max_nodes=1000,
            min_split_samples=10)
        graph_builder_class = tensor_forest.RandomForestGraphs
        return estimator.SKCompat(random_forest.TensorForestEstimator(
            params, graph_builder_class=graph_builder_class,
            model_dir=model_dir))
    
  4. 训练 RF 模型。

    在这里,我们训练上述 RF 估计器。一旦fit()方法完成工作,predict()方法便会计算在包含特征x_train和标签y_train的训练集上的预测结果:

    rf = build_rf_estimator('rf/', feature_count)
    rf.fit(x_train, y_train, batch_size=100)
    rf_pred = rf.predict(x_test)
    rf_pred = rf_pred['classes']
    
  5. 评估模型。

    现在让我们评估 RF 模型的性能:

        target_names = ['Not Survived', 'Survived']
        print("RandomForest Report")
        print(classification_report(test['Survived'], rf_pred, target_names=target_names))
    
    >>>
    RandomForest Report
                             precision    recall  f1-score   support
    ------------------------------------------------------
    Not Survived       0.92         0.85       0.88            117
    Survived           0.76         0.85       0.80            62
    ------------------------------------------------------
    avg / total        0.86         0.85       0.86            179
    

    因此,使用 RF 时,准确率为 87%,高于 LR 和 SVM 模型。与 LR 和 SVM 模型类似,我们将绘制并观察混淆矩阵:

        print("Random Forest Confusion Matrix")
        cm = confusion_matrix(test['Survived'], rf_pred)
        df_cm = pd.DataFrame(cm, index=[i for i in ['Not Survived', 'Survived']],
                             columns=[i for i in ['Not Survived', 'Survived']])
        print(df_cm)
    >>> 
    Random Forest Confusion Matrix
                           Not Survived  Survived
    -----------------------------------------------------
    Not Survived            100             17
    Survived                 9              53
    

    然后,让我们绘制计数图,直观地查看比例:

    sol = pd.DataFrame()
    sol['PassengerId'] = test['PassengerId']
    sol['Survived'] = pd.Series(svm_pred).values
    sns.plt.suptitle("Titanic Survival prediction using RF with TensorFlow")
    count_plot = sns.countplot(sol.Survived)
    

    输出结果如下:

    生存预测的集成方法 – 随机森林

    图 17:使用 TensorFlow 的随机森林进行泰坦尼克号生存预测

    现在,分别统计每个的数量:

    print("Predicted Counts")
    print(sol.Survived.value_counts())
    >>>  Predicted Counts
    -------------------------
    0   109
    1    70
    

一项比较分析

从分类报告中,我们可以看到随机森林具有最佳的整体表现。其原因可能是随机森林在处理分类特征时,比其他两种方法表现更好。同时,由于它使用隐式特征选择,过拟合问题得到了显著减轻。使用逻辑回归是为观察结果提供方便的概率评分。然而,当特征空间过大时,它的表现不佳,即不能很好地处理大量的分类特征/变量。它还完全依赖于非线性特征的转换。

最后,使用 SVM 我们可以处理具有非线性特征交互的大规模特征空间,而不依赖于整个数据集。然而,它在大量观察数据的情况下表现不好。不过,有时找到合适的核函数可能会有些棘手。

总结

在本节课中,我们从理论和实践的角度讨论了监督学习。特别是,我们重新审视了用于回归分析的线性回归模型。我们了解了如何使用回归来预测连续值。随后,在本节课中,我们讨论了其他一些用于预测分析的监督学习算法。我们看到了如何使用逻辑回归、SVM 和随机森林在泰坦尼克号数据集上进行生存预测。最后,我们还做了这些分类器的对比分析。我们还发现,基于决策树集成的随机森林优于逻辑回归和线性 SVM 模型。

在第 3 课中,聚类你的数据——用于预测分析的无监督学习,我们将提供一些无监督学习的实际示例。特别地,我们将提供使用 TensorFlow 进行邻域聚类和音频特征聚类的聚类技术。

更具体地说,我们将提供数据集的探索性分析,然后我们将使用 K-means、K-NN 和二分 K-means 方法结合充分的性能指标(如聚类成本、准确性等)来开发邻域的聚类。在课程的第二部分,我们将学习如何进行音频特征聚类。最后,我们将提供聚类算法的对比分析。

评估

  1. 根据可用的学习反馈的性质,机器学习过程通常分为三大类。请列出它们。

  2. 判断以下陈述是对还是错:使用暴力法(如 if-else 语句与某种加权评分系统),你无法编写程序来预测给定乘客是否能够在灾难中幸存。

  3. 调用 main()方法时,包含学习率的 model_params 会实例化 Estimator。你如何定义 model_params?

  4. 判断以下陈述是对还是错:决策树的每个分支代表一个可能的决策、事件或反应,从统计概率的角度来看。

  5. 基于监督学习算法的预测模型可以根据一个带标签的 _______ 进行预测,该标签将输入映射到与现实世界对齐的输出。

    1. 数据流图

    2. 线性图

    3. 回归模型

    4. 数据集

第三章:聚类你的数据 – 用于预测分析的无监督学习

在本课中,我们将深入探讨预测分析,并了解如何利用它对无监督观察数据集中的记录进行分组,属于某个特定组或类别。我们将提供一些无监督学习的实际示例;特别是会讨论使用 TensorFlow 进行的聚类技术,并附带一些动手示例。

本课将涵盖以下主题:

  • 无监督学习与聚类

  • 使用 K-means 进行预测邻域

  • 使用 K-means 进行音频文件聚类

  • 使用无监督的k-最近邻kNN)进行预测最近邻

无监督学习与聚类

本节将简要介绍无监督机器学习ML)技术。无监督学习是一种机器学习算法,用于通过推断无标签数据集中的隐藏模式,将相关的数据对象进行分组,也就是一个由无标签的输入数据组成的训练集。

让我们来看一个现实生活中的例子。假设你有一个庞大的文件夹,其中包含大量完全合法的非盗版 MP3 文件,存放在你的硬盘中。现在,如果你能够建立一个预测模型,帮助自动将相似的歌曲分组,并将它们组织到你最喜欢的类别中,比如乡村、嘻哈和摇滚,这该多好?

这是一种将项目分配到某个组中的行为,以便在无监督的方式下将 MP3 添加到相应的播放列表中。在第 1 课,从数据到决策 – 使用 TensorFlow 入门,关于分类中,我们假设你有一个标注正确的训练数据集。不幸的是,当我们在现实世界中收集数据时,通常无法享有这样的奢侈。例如,假设我们想将大量音乐集合分成有趣的播放列表。如果我们无法直接访问它们的元数据,我们该如何将歌曲分组呢?一种可能的方法是结合多种机器学习技术,但聚类往往是解决方案的核心。

换句话说,无监督学习算法的主要目标是探索输入数据中未知/隐藏的模式,而这些数据没有标签。然而,无监督学习还包括其他技术,通过探索性方式解释数据的关键特征,以发现隐藏的模式。为了克服这一挑战,聚类技术被广泛用于根据某些相似度度量将无标签数据点分组,且这一过程是无监督的。

在聚类任务中,算法通过分析输入示例之间的相似性,将相关特征分组到不同类别中,其中相似的特征被聚集并用圆圈标记。

聚类的应用包括但不限于以下几点:

  • 在无监督方式下进行异常检测,以发现可疑模式

  • 文本分类,用于寻找自然语言处理中的有用模式

  • 社交网络分析,用于寻找一致的群体

  • 数据中心计算集群,用于将相关计算机组合在一起

  • 基于相似特征的房地产数据分析,用于识别社区

聚类分析是将数据样本或数据点划分并放入相应的同质类别或簇中的过程。因此,聚类的简单定义可以看作是将对象组织成组,组内成员在某些方面是相似的,如图 1 所示:

无监督学习与聚类

图 1:典型的聚类原始数据管道

因此,簇是指一组在某些方面彼此相似,并且与属于其他簇的对象不相似的对象集合。如果给定一组对象,聚类算法会根据相似性将这些对象分成不同的组。例如,像 K-means 这样的聚类算法会定位数据点组的质心。

然而,为了使聚类更准确和有效,算法会评估每个点到簇质心的距离。最终,聚类的目标是确定一组无标签数据中的内在分组。例如,K-means 算法会尝试将相关的数据点聚类到预定的 3 个簇(即 k = 3)中,如图 2 所示:

无监督学习与聚类

图 2:典型聚类算法的结果以及簇中心的表示

聚类是智能地对数据集中的项进行分类的过程。总体思路是,同一簇中的两个项比属于不同簇的项更接近。这是一个通用定义,具体的接近度解释可以灵活调整。例如,当接近度通过物种在生物分类(如科、属和种)的层次关系中的相似性来衡量时,猎豹和美洲豹可能属于同一簇,而大象则属于另一个簇。

使用 K-means 进行预测分析

K-means 是一种聚类算法,旨在将相关的数据点聚集在一起。然而,我们需要了解其工作原理和数学运算。

K-means 工作原理

假设我们有 n 个数据点,xii = 1...n,需要将它们划分成 k 个簇。现在目标是为每个数据点分配一个簇,K-means 的目标是找到簇的位置 μii=1...k,使得数据点到簇的距离最小。从数学角度来看,K-means 算法通过解决一个优化问题的方程来实现这一目标:

K-means 工作原理

在之前的方程中,ci 是一组数据点,当这些数据点被分配到簇 i 时,K-means 工作原理是需要计算的欧几里得距离(我们稍后会解释为何需要使用这种距离度量)。因此,我们可以看到,使用 K-means 进行整体聚类操作并非易事,而是一个 NP-难度的优化问题。这也意味着 K-means 算法不仅尝试找到全局最小值,而且常常会陷入不同的解中。

使用 K-means 算法进行聚类时,从初始化所有坐标为质心开始。每次算法执行时,根据某种距离度量(通常是前面提到的欧几里得距离),每个点都会被分配到其最近的质心。

注意

距离计算:当然,还有其他方法可以计算距离。例如,切比雪夫距离可以通过只考虑最显著的维度来测量距离。哈明距离算法可以识别两个字符串之间的差异。马氏距离可以用来规范化协方差矩阵。曼哈顿距离则是通过仅考虑轴对齐的方向来测量距离。哈弗辛距离用于测量球面上两点之间的大圆距离。

考虑到这些距离度量算法,可以明确看出,欧几里得距离算法是最适合用于解决我们在 K-means 算法中距离计算的目的的。然后,质心会更新为该迭代中分配给它的所有点的中心。这个过程会不断重复,直到质心的变化最小。简而言之,K-means 算法是一个迭代算法,并且分为两个步骤:

  1. 簇分配步骤:K-means 会遍历数据集中每一个 n 数据点,将其分配到最接近的 k 个质心的簇中,然后选择距离最小的一个。

  2. 更新步骤:对于每个簇,计算该簇内所有数据点的新质心。K-means 的整体工作流程可以通过以下流程图来解释:

K-means 工作原理

图 4:K-means 算法的流程图(肘部法则是一个可选但也是高级的选项)

使用 K-means 预测邻域

现在,为了展示使用 K-means 进行聚类的例子,我们将使用从 course1.winona.edu/bdeppa/Stat%20425/Datasets.html 下载的 Saratoga NY Homes 数据集,作为一种无监督学习技术。该数据集包含位于纽约市郊的房屋的若干特征;例如,价格、地块面积、水 front、年龄、土地价值、新建构、中央空调、燃料类型、供热类型、污水处理类型、生活面积、大学百分比、卧室、壁炉、浴室以及房间数。然而,表 1 中仅展示了其中的一些特征:

使用 K-means 预测邻近区域

表 1:来自萨拉托加纽约房屋数据集的示例数据

该聚类技术的目标是基于城市中每个房屋的特征进行探索性分析,目的是寻找位于相同区域的可能邻近房屋。在进行特征提取之前,我们需要加载并解析萨拉托加纽约房屋数据集。然而,为了更好地理解,我们将一步一步地查看这个示例的源代码:

  1. 加载所需的库和软件包。

    我们需要一些内置的 Python 库,例如 os、random、NumPy 和 Pandas,用于数据处理;PCA 用于降维;Matplotlib 用于绘图;当然,还有 TensorFlow:

    import os
    import random
    from random import choice, shuffle
    import pandas as pd
    import numpy as np
    import tensorflow as tf
    from sklearn.decomposition import PCA
    import matplotlib.pyplot as plt
    from mpl_toolkits.mplot3d import axes3d, Axes3D
    
  2. 加载、解析并准备训练集。

    这里的第一行用于确保结果的可重现性。第二行基本上是从你的位置读取数据集并将其转换为 Pandas 数据框:

    random.seed(12345)
    train = pd.read_csv(os.path.join('input', 'saratoga.csv'))
    x_train = np.array(train.iloc[:, 1:], dtype='float32')
    

    如果现在打印数据框(使用 print(train)),您应该会看到包含标题和数据的数据框,如图 3 所示:

    使用 K-means 预测邻近区域

    图 5:萨拉托加纽约房屋数据集的快照

    好的,我们已经成功准备了数据集。现在,下一步是概念化我们的 K-means 并为其编写一个函数/类。

  3. 实现 K-means。

    以下是 K-means 的源代码,这在 TensorFlow 中非常简单:

    def kmeans(x, n_features, n_clusters, n_max_steps=1000, early_stop=0.0):
        input_vec = tf.constant(x, dtype=tf.float32)
        centroids = tf.Variable(tf.slice(tf.random_shuffle(input_vec), [0, 0], [n_clusters, -1]), dtype=tf.float32)
        old_centroids = tf.Variable(tf.zeros([n_clusters, n_features]), dtype=tf.float32)
        centroid_distance = tf.Variable(tf.zeros([n_clusters, n_features]))
        expanded_vectors = tf.expand_dims(input_vec, 0)
        exanded_centroids = tf.expand_dims(centroids, 1)
        distances = tf.reduce_sum(tf.square(tf.subtract(expanded_vectors, expanded_centroids)), 2)
        assignments = tf.argmin(distances, 0)
        means = tf.concat([tf.reduce_mean(
            tf.gather(input_vec, tf.reshape(tf.where(tf.equal(assignments, c)), [1, -1])),
            reduction_indices=[1]) for c in range(n_clusters)], 0)
        save_old_centroids = tf.assign(old_centroids, centroids)
        update_centroids = tf.assign(centroids, means)
        init_op = tf.global_variables_initializer()
        performance = tf.assign(centroid_distance, tf.subtract(centroids, old_centroids))
        check_stop = tf.reduce_sum(tf.abs(performance))
        with tf.Session() as sess:
            sess.run(init_op)
            for step in range(n_max_steps):
                sess.run(save_old_centroids)
                _, centroid_values, assignment_values = sess.run(
                    [update_centroids, centroids, assignments])            
                sess.run(check_stop)
                current_stop_coeficient = check_stop.eval()
                if current_stop_coeficient <= early_stop:
                    break
        return centroid_values, assignment_values
    

    上述代码包含了开发 K-means 模型所需的所有步骤,包括基于距离的质心计算、质心更新和训练所需的参数。

  4. 对房屋进行聚类。

    现在可以用实际值调用前面的函数,例如,我们的房屋数据集。由于有许多房屋及其相应的特征,绘制所有房屋的聚类图会很困难。这就是我们在之前的课程中讨论的主成分分析PCA):

    centers, cluster_assignments = kmeans(x_train, len(x_train[0]), 10)
    pca_model = PCA(n_components=3)
    reduced_data = pca_model.fit_transform(x_train)
    reduced_centers = pca_model.transform(centers)
    

    好的,现在我们一切就绪。为了更好地可视化聚类,您可以参见图 6 所示。为此,我们将使用 mpl_toolkits.mplot3d 进行 3D 投影,如下所示:

    plt.subplot(212, projection='3d')
    plt.scatter(reduced_data[:, 0], reduced_data[:, 1], reduced_data[:, 2], c=cluster_assignments)
    plt.title("Clusters")
    plt.show()
    >>>
    

    使用 K-means 预测邻近区域

    图 6:聚类具有相似属性的房屋,例如价格

    在这里,我们可以看到大多数房屋的价格位于0100,000之间。第二多的房屋价格位于100,000200,000之间。然而,要区分它们是非常困难的。此外,我们使用的预定义聚类数量是 10,这可能不是最优的选择。因此,我们需要调整此参数。

  5. 微调并找到最优的聚类数目。

    选择正确的簇数通常取决于任务。例如,假设你正在为数百人(包括老年人和年轻人)策划一个活动。如果你的预算只允许提供两个娱乐选项,那么你可以使用 K 均值聚类,设置k = 2,将宾客分成两组按年龄分配。有时,k值的选择并不那么明显。自动确定k值则会复杂一些。

    如前所述,K 均值算法试图最小化距离的平方和(即欧几里得距离),以簇内平方和WCSS)为准。

    注意

    然而,如果你想手动或自动最小化每个集合中点之间的平方和,你将得到一个模型,其中每个簇都是其自己的簇中心;在这种情况下,这个度量值将为 0,但它几乎不会是一个通用的模型。

    因此,一旦你通过指定参数训练好模型,就可以使用 WCSS 来评估结果。从技术上讲,它与每个 K 簇中每个观测点的距离和相同。聚类算法,尤其是 K 均值算法的优点是,它能在具有无限特征的数据上进行聚类。当你拥有原始数据,并希望了解其中的模式时,它是一个非常有用的工具。

    然而,在进行实验之前决定簇的数量可能并不会成功,有时可能导致过拟合问题或欠拟合问题。此外,非正式地说,确定簇的数量是一个独立的优化问题需要解决。因此,基于此,我们可以重新设计我们的 K 均值算法,考虑到 WCSS 值的计算,如下所示:

    def kmeans(x, n_features, n_clusters, n_max_steps=1000, early_stop=0.0):
        input_vec = tf.constant(x, dtype=tf.float32)
        centroids = tf.Variable(tf.slice(tf.random_shuffle(input_vec), [0, 0], [n_clusters, -1]), dtype=tf.float32)
        old_centroids = tf.Variable(tf.zeros([n_clusters, n_features]), dtype=tf.float32)
        centroid_distance = tf.Variable(tf.zeros([n_clusters, n_features]))
        expanded_vectors = tf.expand_dims(input_vec, 0)
        expanded_centroids = tf.expand_dims(centroids, 1)
        distances = tf.reduce_sum(tf.square(tf.subtract(expanded_vectors, expanded_centroids)), 2)
        assignments = tf.argmin(distances, 0)
        means = tf.concat([tf.reduce_mean(        tf.gather(input_vec, tf.reshape(tf.where(tf.equal(assignments, c)), [1, -1])),
            reduction_indices=[1]) for c in range(n_clusters)], 0)
        save_old_centroids = tf.assign(old_centroids, centroids)
        update_centroids = tf.assign(centroids, means)
        init_op = tf.global_variables_initializer()
    
        performance = tf.assign(centroid_distance, tf.subtract(centroids, old_centroids))
        check_stop = tf.reduce_sum(tf.abs(performance))
        calc_wss = tf.reduce_sum(tf.reduce_min(distances, 0))
        with tf.Session() as sess:
            sess.run(init_op)
            for step in range(n_max_steps):
                sess.run(save_old_centroids)
                _, centroid_values, assignment_values = sess.run(
                    [update_centroids, centroids, assignments])            
                sess.run(calc_wss)
                sess.run(check_stop)
                current_stop_coeficient = check_stop.eval()
                wss = calc_wss.eval()
                print(step, current_stop_coeficient)
                if current_stop_coeficient <= early_stop:
                    break
        return centroid_values, assignment_values, wss
    

    为了优化聚类性能,我们可以使用一种启发式方法,称为肘部法则。我们从K = 2开始。然后,通过增加K值运行 K 均值算法,并观察使用 WCSS 的代价函数(CF)值。在某个时刻,我们应该会看到 CF 值出现大幅下降。然而,随着K值的增加,改进将变得微不足道。

    总结来说,我们可以在 WCSS 最后一次大幅下降之后选择K值作为最优值。K 均值包括多个参数,如簇内性和中心性,分析这些参数可以帮助我们了解 K 均值的性能:

    • 中心性:这是平方和的中心性,也称为簇内相似度

    • 簇内性:这是平方和的簇内性,也称为簇间相似度

    • 总簇内性:这是所有簇的簇内性之和,也称为总的簇内相似度

    请注意,一个健壮且准确的聚类模型将具有较低的内聚度值和较高的中介度值。然而,这些值依赖于选择的簇的数量K,这个值是在建立模型之前确定的。现在,基于此,我们将为不同的K值训练 K-means 模型,这些K值是预定义的簇的数量。我们将从K = 210开始,具体如下:

    wcss_list = []
    for i in range(2, 10):
        centers, cluster_assignments, wcss = kmeans(x_train, len(x_train[0]), i)
        wcss_list.append(wcss)
    

    现在,让我们讨论如何利用肘部法则来确定簇的数量。我们计算了 K-means 算法应用于房屋数据时,WCSS 随簇数量变化的成本函数,如下所示:

    plt.figure(figsize=(12, 24))
    plt.subplot(211)
    plt.plot(range(2, 10), wcss_list)
    plt.xlabel('No of Clusters')
    plt.ylabel('WCSS')
    plt.title("WCSS vs Clusters")
    >>>
    

    使用 K-means 预测邻里

    图 7:簇的数量与 WCSS 的关系

    我们将在接下来的示例中,继续使用 K-means 算法复用这个教训。现在可以观察到,当k = 5时,发生了一个大幅下降。因此,我们选择了5个簇,如图 7 所示。基本上,这是在最后一次大幅下降之后的结果。这意味着,在我们开始训练 K-means 模型之前,我们需要设置的最优簇数量是5

  6. 聚类分析。

    从图 8 可以看出,大多数房屋属于簇 34655 套房屋),其次是簇 43356 套房屋)。x 轴表示价格,y 轴表示每套房屋的地块大小。我们还可以观察到,簇 1只有少量房屋,且可能分布在较远的距离,但价格较高。因此,如果你购买的房屋属于这一簇,最有可能的是你无法找到一个更近的邻里互动。然而,如果你喜欢更多的人际互动且预算有限,可能应该考虑购买簇 2、3、4 或 5 中的房屋:

    使用 K-means 预测邻里

    图 8:邻里簇,即相同簇中的房屋属于同质群体

    为了进行分析,我们将输出数据导入到 RStudio 中,并生成了图 6 所示的簇。R 脚本可以在我的 GitHub 仓库中找到,链接为:github.com/rezacsedu/ScalaAndSparkForBigDataAnalytics。或者,您也可以编写自己的脚本并相应地进行可视化。

音频文件聚类的预测模型

对于使用音频数据进行聚类,数据点是音频文件的特征向量。如果两个点靠得很近,说明它们的音频特征相似。我们希望发现哪些音频文件属于同一个邻里,因为这些簇很可能是组织您的音乐文件的好方法:

  1. 使用 TensorFlow 和 Python 加载音频文件。

    ML 算法中的一些常见输入类型是音频和图像文件。这不应该令人惊讶,因为录音和照片是语义概念的原始、冗余并且常常是嘈杂的表示。ML 是一种帮助处理这些复杂情况的工具。这些数据文件有各种实现方式,例如,音频文件可以是 MP3 或 WAV。

    从磁盘读取文件并不完全是 ML 特有的能力。你可以使用多种 Python 库将文件加载到内存中,例如 Numpy 或 Scipy。一些开发者喜欢将数据预处理步骤与 ML 步骤分开。然而,我认为这也是整个分析过程的一部分。

    由于这是一本关于 TensorFlow 的书,我会尝试使用 TensorFlow 内置操作符中的 tf.train.match_filenames_once() 来列出目录中的文件。然后,我们可以将此信息传递给 tf.train.string_input_producer() 队列操作符。通过这种方式,我们可以一次访问一个文件,而不是一次性加载所有文件。下面是该方法的结构:

    match_filenames_once(pattern,name=None)
    

    这个方法接受两个参数:patternnamepattern 表示文件模式或文件模式的 1D 张量。name 用来表示操作的名称。然而,这个参数是可选的。一旦调用该方法,它会保存匹配模式的列表,顾名思义,它只会计算一次。

    最后,返回一个初始化为与模式匹配的文件列表的变量。完成读取元数据和音频文件后,我们可以解码文件,以从给定的文件名中检索可用的数据。现在,让我们开始。首先,我们需要导入必要的包和 Python 模块,如下所示:

    import 
    tensorflow as tf
    import numpy as np
    from bregman.suite import *
    from tensorflow.python.framework import ops
    import warnings
    import random
    

    现在我们可以开始从指定的目录读取音频文件。首先,我们需要存储与包含特定文件扩展名的模式匹配的文件名,例如 .mp3.wav 等。然后,我们需要设置一个管道,用于随机获取文件名。现在,代码本地读取 TensorFlow 中的文件。然后,我们运行读取器来提取文件数据。你可以使用以下代码来完成这项任务:

    filenames = tf.train.match_filenames_once('./audio_dataset/*.wav')
    count_num_files = tf.size(filenames)
    filename_queue = tf.train.string_input_producer(filenames)
    reader = tf.WholeFileReader()
    filename, file_contents = reader.read(filename_queue)
    chromo = tf.placeholder(tf.float32)
    max_freqs = tf.argmax(chromo, 0)
    

    一旦我们读取了所有音频文件的数据和元数据,接下来的直接任务就是捕捉音频特征,这些特征将用于 K-means 聚类。

  2. 提取特征并准备特征向量。

    ML 算法通常设计为使用特征向量作为输入;然而,声音文件是一个完全不同的格式。我们需要一种方法从声音文件中提取特征,以便创建特征向量。

    它有助于理解这些文件是如何表示的。如果你曾经看过黑胶唱片,你可能会注意到音频被表示为磁碟上的凹槽。我们的耳朵通过空气中的一系列振动来解读音频。通过记录振动特性,我们的算法能够将声音存储为数据格式。现实世界是连续的,但计算机将数据存储为离散值。

    声音通过模拟到数字转换器ADC)被数字化为离散表示。你可以把声音看作是随时间波动的波。然而,这些数据噪声很大,难以理解。表示波的等效方式是检查每个时间间隔内构成它的频率。这种视角被称为频域。

    使用一种叫做离散傅里叶变换的数学运算(通常称为快速傅里叶变换),可以轻松地在时域和频域之间转换。我们将使用这一技术从我们的声音中提取特征向量。

    一个声音可能产生 12 种音高。在音乐术语中,这 12 个音高分别是 C、C#、D、D#、E、F、F#、G、G#、A、A#和 B。图 9 显示了如何在 0.1 秒的间隔内获取每个音高的贡献,从而得到一个包含 12 行的矩阵。列数随着音频文件长度的增加而增加。具体而言,对于一个t秒的音频,将有10*t列。

    这个矩阵也叫做音频的色谱图。但首先,我们需要为 TensorFlow 提供一个占位符,用来保存音频的色谱图和最大频率:

    chromo = tf.placeholder(tf.float32) 
    max_freqs = tf.argmax(chromo, 0)
    

    接下来的任务是我们可以编写一个方法,提取音频文件的这些色谱图。它可能如下所示:

    def get_next_chromogram(sess):
        audio_file = sess.run(filename)
        F = Chromagram(audio_file, nfft=16384, wfft=8192, nhop=2205)
        return F.X, audio_file
    

    上述代码的工作流程如下:

    • 首先,传入文件名,并使用这些参数描述每 0.1 秒的 12 个音高。

    • 最后,以每秒 10 次的频率表示 12 维向量的值。

    我们使用前面的方法提取的色谱图输出将是一个矩阵,如图 10 所示。一个声音片段可以被读取为色谱图,而色谱图则是生成声音片段的配方。现在,我们有了将音频和矩阵相互转换的方法。如你所学,绝大多数机器学习算法接受特征向量作为有效的数据形式。也就是说,我们将要看的第一个机器学习算法是 K 均值聚类:

    用于音频文件聚类的预测模型

    图 9:色谱矩阵的可视化,其中 x 轴表示时间,y 轴表示音高类别。绿色标记表示在该时间点该音高的存在。

    为了在我们的色谱图上运行机器学习算法,我们首先需要决定如何表示特征向量。一个想法是通过只关注每个时间间隔内最显著的音高类别来简化音频,如图 10 所示:

    用于音频文件聚类的预测模型

    图 10:在每个时间间隔内,最有影响力的音高被突出显示。你可以把它看作是每个时间间隔内最响亮的音高

    现在,我们将统计每个音高在音频文件中出现的次数。图 11 显示了这些数据的直方图,形成了一个 12 维向量。如果我们将该向量归一化,使所有计数之和为1,那么我们就可以轻松比较不同长度的音频:

    用于聚类音频文件的预测模型

    图 11:我们统计了在每个时间间隔内听到的最响亮音高的频率,以生成这个直方图,作为我们的特征向量

    既然我们已经有了色谱图,我们需要利用它提取音频特征来构建特征向量。你可以使用以下方法来完成这个任务:

    def extract_feature_vector(sess, chromo_data):
        num_features, num_samples = np.shape(chromo_data)
        freq_vals = sess.run(max_freqs, feed_dict={chromo: chromo_data})
        hist, bins = np.histogram(freq_vals, bins=range(num_features + 
    ))
        normalized_hist = hist.astype(float) / num_samples
        return normalized_hist
    

    前述代码的工作流程如下:

    • 创建一个操作来识别贡献最大的音高。

    • 现在,将色谱图转换为特征向量。

    • 之后,我们将构建一个矩阵,其中每一行都是一个数据项。

    • 现在,如果你能听到音频片段,你可以想象并区分不同的音频文件。然而,这只是直觉。

    因此,我们不能仅依赖于此,而应该进行视觉检查。所以,我们将调用先前的方法,从每个音频文件中提取特征向量并绘制特征图。整个操作应如下所示:

    def get_dataset(sess):
        num_files = sess.run(count_num_files)
        coord = tf.train.Coordinator()
        threads = tf.train.start_queue_runners(coord=coord)
        xs = list()
        names = list()
        plt.figure()
        for _ in range(num_files):
            chromo_data, filename = get_next_chromogram(sess)
            plt.subplot(1, 2, 1)
            plt.imshow(chromo_data, cmap='Greys', interpolation='nearest')
            plt.title('Visualization of Sound Spectrum')
            plt.subplot(1, 2, 2)
            freq_vals = sess.run(max_freqs, feed_dict={chromo: chromo_data})
            plt.hist(freq_vals)
            plt.title('Histogram of Notes')
            plt.xlabel('Musical Note')
            plt.ylabel('Count')
            plt.savefig('{}.png'.format(filename))
            plt.clf()
            plt.clf()
            names.append(filename)
            x = extract_feature_vector(sess, chromo_data)
            xs.append(x)
        xs = np.asmatrix(xs)
        return xs, names
    

    之前的代码应该将每个音频文件的音频特征绘制成直方图,如下所示:

    用于聚类音频文件的预测模型

    图 12:踩镲音频文件显示了相似的直方图

    你可以看到一些音频文件的示例,我们正在根据它们的音频特征进行聚类。正如你所看到的,右侧的两个音频文件似乎有相似的直方图。左侧的两个音频文件也具有相似的声音频谱:

    用于聚类音频文件的预测模型

    图 13:撞钹音频文件显示了相似的直方图

    现在,目标是开发 K-means,使其能够准确地将这些声音聚类在一起。我们将查看咳嗽音频文件的高级视图,如下图所示:

    用于聚类音频文件的预测模型

    图 14:咳嗽音频文件显示了相似的直方图

    最后,我们得到了具有相似直方图和音频频谱的尖叫音频文件,但与其他音频文件相比,当然它们是不同的:

    用于聚类音频文件的预测模型

    图 15:尖叫音频文件显示了相似的直方图和音频频谱

    现在,我们可以想象我们的任务。我们已经准备好了特征来训练 K-means 模型。让我们开始进行训练。

  3. 训练 K-means 模型。

    现在特征向量已经准备好,是时候将其输入 K-means 模型进行聚类了,如图 10 所示。其核心思想是,聚类中所有点的中点称为质心。

    根据我们选择提取的音频特征,质心可以捕捉到一些概念,如大声的声音、高音调的声音或萨克斯管类似的声音。因此,需要注意的是,K-means 算法分配的是没有描述性标签的聚类,例如聚类 1、聚类 2 或聚类 3。首先,我们可以编写一个方法,计算初始的聚类质心,如下所示:

    def initial_cluster_centroids(X, k):
        return X[0:k, :]
    

    现在,接下来的任务是根据初始聚类分配随机地将聚类编号分配给每个数据点。这次我们可以使用另一种方法:

    def assign_cluster(X, centroids):
        expanded_vectors = tf.expand_dims(X, 0)
        expanded_centroids = tf.expand_dims(centroids, 1)
        distances = tf.reduce_sum(tf.square(tf.subtract(expanded_vectors, expanded_centroids)), 2)
        calc_wss = tf.reduce_sum(tf.reduce_min(distances, 0))
        mins = tf.argmin(distances, 0)
        return mins, calc_wss
    

    之前的方法计算了聚类评估中最小距离和 WCSS。然后,我们需要更新质心来检查并确保聚类分配中是否发生了任何变化:

    def recompute_centroids(X, Y):
        sums = tf.unsorted_segment_sum(X, Y, k)
        counts = tf.unsorted_segment_sum(tf.ones_like(X), Y, k)
        return sums / counts
    

    既然我们已经定义了许多变量,现在是时候使用 local_variable_initializer() 来初始化它们,如下所示:

    init_op = tf.local_variables_initializer()
    

    最后,我们可以执行训练。为此,audioClusterin() 方法接受初步聚类数 k 并将训练迭代直到最大迭代次数,如下所示:

    def audioClustering(k, max_iterations ): 
        with tf.Session() as sess:
            sess.run(init_op)
            X, names = get_dataset(sess)
            centroids = initial_cluster_centroids(X, k)
            i, converged = 0, False
            while not converged and i < max_iterations:
                i += 1.
                Y, wcss_updated = assign_cluster(X, centroids)        
                centroids = sess.run(recompute_centroids(X, Y))
            wcss = wcss_updated.eval()        
            print(zip(sess.run(Y)), names) 
        return wcss
    

    之前的方法返回了聚类成本、WCSS,并打印了每个音频文件的聚类编号。因此,我们已经完成了训练步骤。接下来的任务是评估 K-means 聚类质量。

  4. 评估模型。

    在这里,我们将从两个角度评估聚类质量。首先,我们将观察预测的聚类数量。其次,我们还将尝试找出 k 的最优值,它是 WCSS 的一个函数。因此,我们将对 K = 210 进行迭代训练,并观察聚类结果。然而,首先,让我们创建两个空列表来保存每一步中 K 和 WCSS 的值:

    wcss_list = []
    k_list = []
    

    现在,让我们使用 for 循环来迭代训练,如下所示:

    for k in range(2, 9):
        random.seed(12345)
        wcss = audioClustering(k, 100)
        wcss_list.append(wcss)
        k_list.append(k)
    

    这将打印出以下输出:

     ([(0,), (1,), (1,), (0,), (1,), (0,), (0,), (0,), (0,), (0,), (0,)],
    ['./audio_dataset/scream_1.wav', './audio_dataset/Crash-Cymbal-3.
    wav', './audio_dataset/Ride_Cymbal_1.wav', './audio_dataset/Ride_
    Cymbal_2.wav', './audio_dataset/Crash-Cymbal-2.wav', './audio_
    dataset/Ride_Cymbal_3.wav', './audio_dataset/scream_3.wav', './
    audio_dataset/scream_2.wav', './audio_dataset/cough_2.wav', './audio_
    dataset/cough_1.wav', './audio_dataset/Crash-Cymbal-1.wav'])
    
    ([(0,), (1,), (2,), (2,), (2,), (2,), (2,), (1,), (2,), (2,), (2,)],
    ['./audio_dataset/Ride_Cymbal_2.wav', './audio_dataset/Crash-
    Cymbal-3.wav', './audio_dataset/cough_1.wav', './audio_dataset/Crash-
    Cymbal-2.wav', './audio_dataset/scream_2.wav', './audio_dataset/
    Ride_Cymbal_3.wav', './audio_dataset/Crash-Cymbal-1.wav', './
    udio_
    dataset/Ride_Cymbal_1.wav', './audio_dataset/cough_2.wav', './audio_
    dataset/scream_1.wav', './audio_dataset/scream_3.wav'])
    
    ([(0,), (1,), (2,), (3,), (2,), (2,), (2,), (2,), (2,), (2,), (2,)],
    ['./audio_dataset/Ride_Cymbal_2.wav', './audio_dataset/Ride_Cymbal_3.
    wav', './audio_dataset/cough_1.wav', './audio_dataset/Crash-Cymbal-1.
    wav', './audio_dataset/scream_3.wav', './audio_dataset/cough_2.wav',
    './audio_dataset/Crash-Cymbal-2.wav', './audio_dataset/Ride_Cymbal_1.
    wav', './audio_dataset/Crash-Cymbal-3.wav', './audio_dataset/
    scream_1.wav', './audio_dataset/scream_2.wav'])
    
    ([(0,), (1,), (2,), (3,), (4,), (0,), (0,), (4,), (0,), (0,), (0,)],
    ['./audio_dataset/cough_1.wav', './audio_dataset/scream_1.wav', './
    audio_dataset/Crash-Cymbal-1.wav', './audio_dataset/Ride_Cymbal_2.
    wav', './audio_dataset/Crash-Cymbal-3.wav', './audio_dataset/
    scream_2.wav', './audio_dataset/cough_2.wav', './audio_dataset/
    Ride_Cymbal_1.wav', './audio_dataset/Crash-Cymbal-2.wav', './audio_
    dataset/Ride_Cymbal_3.wav', './audio_dataset/scream_3.wav'])
    
    ([(0,), (1,), (2,), (3,), (4,), (5,), (2,), (2,), (2,), (4,), (2,)],
    ['./audio_dataset/scream_3.wav', './audio_dataset/Ride_Cymbal_2.wav',
    './audio_dataset/cough_1.wav', './audio_dataset/Crash-Cymbal-2.wav',
    './audio_dataset/Crash-Cymbal-3.wav', './audio_dataset/scream_2.wav',
    './audio_dataset/Crash-Cymbal-1.wav', './audio_dataset/cough_2.wav',
    './audio_dataset/Ride_Cymbal_3.wav', './audio_dataset/Ride_Cymbal_1.
    wav', './audio_dataset/scream_1.wav'])
    
    ([(0,), (1,), (2,), (3,), (4,), (5,), (6,), (5,), (6,), (5,), (5,)],
    ['./audio_dataset/cough_2.wav', './audio_dataset/Ride_Cymbal_3.
    av',
    './audio_dataset/scream_1.wav', './audio_dataset/Ride_Cymbal_2.wav',
    './audio_dataset/Crash-Cymbal-1.wav', './audio_dataset/cough_1.wav',
    './audio_dataset/scream_2.wav', './audio_dataset/Crash-Cymbal-3.wav',
    './audio_dataset/scream_3.wav', './audio_dataset/Ride_Cymbal_1.wav',
    './audio_dataset/Crash-Cymbal-2.wav'])
    
    ([(0,), (1,), (2,), (3,), (4,), (5,), (6,), (7,), (6,), (6,), (1,)],
    ['./audio_dataset/Crash-Cymbal-1.wav', './audio_dataset/scream_3.
    wav', './audio_dataset/Ride_Cymbal_3.wav', './audio_dataset/
    Crash-Cymbal-3.wav', './audio_dataset/Crash-Cymbal-2.wav', './
    audio_dataset/cough_2.wav', './audio_dataset/cough_1.wav', './audio_
    dataset/Ride_Cymbal_1.wav', './audio_dataset/Ride_Cymbal_2.wav', './
    audio_dataset/scream_1.wav', './audio_dataset/scream_2.wav'])
    
    ([(0,), (1,), (2,), (3,), (4,), (5,), (6,), (7,), (8,), (1,), (7,)],
    ['./audio_dataset/scream_2.wav', './audio_dataset/Ride_Cymbal_1.wav',
    './audio_dataset/Crash-Cymbal-2.wav', './audio_dataset/Ride_Cymbal_3.
    wav', './audio_dataset/Ride_Cymbal_2.wav', './audio_dataset/scream_3.
    wav', './audio_dataset/Crash-Cymbal-1.wav', './audio_dataset/cough_1.
    wav', './audio_dataset/cough_2.wav', './audio_dataset/Crash-Cymbal-3.
    wav', './audio_dataset/scream_1.wav'])
    

    这些值表示每个音频文件已被聚类,并且聚类编号已分配(第一个括号是聚类编号,第二个括号内是文件名)。然而,从这个输出中很难判断准确性。一种天真的方法是将每个文件与图 12 至图 15 进行比较。或者,让我们采用一种更好的方法,那就是我们在第一个示例中使用的肘部法则。为此,我已经创建了一个字典,使用了两个列表 k_listwcss_list,它们是先前计算得出的,具体如下:

    dict_list = zip(k_list, wcss_list)
    my_dict = dict(dict_list)
    print(my_dict)
    

    之前的代码产生了以下输出:

    {2: 2.8408628007260428, 3: 2.3755930780867365, 4: 0.9031724736903582,
    5: 0.7849431270192495, 6: 0.872767581979385, 7: 0.62019339653673422,
    8: 0.70075249251166494, 9: 0.86645706880532057}
    

    从之前的输出可以看到,在 k = 4 时 WCSS 出现了急剧下降,这是在第三次迭代中生成的。因此,基于这个最小评估,我们可以做出关于以下聚类分配的决策:

    Ride_Cymbal_1.wav => 2
    Ride_Cymbal_2.wav => 0 
    cough_1.wav => 2 
    cough_2.wav =>2
    Crash-Cymbal-1.wav =>3
    Crash-Cymbal-2.wav => 2
    scream_1.wav => 2 
    scream_2.wav => 2  
    

    既然我们已经看过了使用 K-means 的两个完整示例,接下来我们将介绍另一个示例,叫做 kNN。它通常是一个监督学习算法。在下一节中,我们将看到如何以无监督的方式训练这个算法来进行回归任务。

使用 kNN 进行预测分析

kNN 是非参数化和基于实例的,广泛用于监督学习。它是一个强大且多功能的分类器,常用作复杂分类器(如神经网络(NNs)和支持向量机(SVMs))的基准。kNN 常用于经济预测、数据压缩和基因组学,尤其是基于基因表达分析的应用。

kNN 的工作原理

kNN 的思想是,从一组特征x中,我们尝试预测标签y。因此,kNN 属于监督学习算法的范畴。非正式地说,这意味着我们有一个标记的数据集,由训练观测值(xy)组成。现在,任务是建模xy之间的关系,使得函数f: X→Y从未见过的观测值x中学习。通过观察一组最近邻点,函数f(x)可以自信地预测点z的相应标签 y。

然而,实际的预测方法取决于我们是进行回归(连续)还是分类(离散)。对于离散分类目标,预测可以通过一个最大投票方案给出,该方案根据距离预测点的远近进行加权:

kNN 的工作原理

在这里,我们的预测f(z)是所有类别j的最大加权值,其中预测点到训练点的加权距离由φ(dij)给出,其中d表示两点之间的距离。另一方面,Iij是一个指示函数,表示点i是否属于类别j

对于连续回归目标,预测值由与预测点最近的所有k点的加权平均值给出:

kNN 的工作原理

从前面的两个方程可以看出,预测结果在很大程度上依赖于距离度量的选择d。有很多不同的距离度量规范,例如 L1 和 L2 度量可用于文本距离:

一种简单的加权方法是根据距离本身进行加权。距离预测点更远的点应该比离得更近的点影响小。最常见的加权方式是距离的归一化逆值。我们将在下一节中实现此方法。

实现基于 kNN 的预测模型

为了说明如何在 TensorFlow 中使用最近邻进行预测,我们将使用 1970 年代的波士顿住房数据集,该数据集可通过 UCI ML 仓库获取,网址为archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.data。下表显示了数据集的基本描述:

实现基于 kNN 的预测模型

在这里,我们将预测的是邻里住房中位数,即最后一个名为 MEDV 的值,作为若干特征的函数。由于我们将训练集视为训练好的模型,因此我们将找到预测点的 kNN,并对目标值进行加权平均。让我们开始吧:

  1. 加载所需的库和包。

    作为入口,我们导入必要的库和包,这些将用于使用 TensorFlow 进行 kNN 预测分析:

    import matplotlib.pyplot as plt
    import numpy as np 
    import random
    import os
    import tensorflow as tf
    import requests
    from tensorflow.python.framework import ops
    import warnings
    
  2. 重置默认图并禁用 TensorFlow 警告。

    我们需要使用 TensorFlow 的 reset_default_graph() 函数重置默认的 TensorFlow 图。你还必须禁用所有警告,因为你的设备没有 GPU:

    warnings.filterwarnings("ignore")
    os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
    ops.reset_default_graph()
    
  3. 加载和预处理数据集。

    首先,我们将使用 requests 包中的 get() 函数加载并解析数据集,如下所示:

    housing_url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.data'
    housing_header = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV']
    num_features = len(housing_header)
    housing_file = requests.get(housing_url)
    housing_data = [[float(x) for x in y.split(' ') if len(x)>=1] for y in housing_file.text.split('\n') if len(y)>=1]
    

    注释

    欲了解更多关于前面代码如何工作的详细信息,请参阅requests 包的文档

    接下来,我们将把特征(预测变量)和标签分开:

    y_vals = np.transpose([np.array([y[len(housing_header)-1] for y in housing_data])])
    x_vals = np.array([[x for i,x in enumerate(y) if housing_header[i] in housing_header] for y in housing_data])
    

    现在,为了了解特征和标签的大概情况,让我们按如下方式打印它们:

    print(y_vals)
    >>>
    [[ 24\. ]
    [ 21.6]
    [ 34.7]
    [ 33.4]
    [ 36.2]
    [ 28.7]
    [ 22.9]
    [ 27.1]
    [ 16.5]
    [ 18.9]
    [ 15\. ]
    …]
    

    所以,标签可以正常使用,而且它们也是连续值。现在,让我们看看特征:

    print(x_vals)
    >>>
    [[  6.32000000e-03   1.80000000e+01   2.31000000e+00 ...,   3.96900000e+02
        4.98000000e+00   2.40000000e+01]
     [  2.73100000e-02   0.00000000e+00   7.07000000e+00 ...,   3.96900000e+02
        9.14000000e+00   2.16000000e+01]
     [  2.72900000e-02   0.00000000e+00   7.07000000e+00 ...,   3.92830000e+02
        4.03000000e+00   3.47000000e+01]
     ..., 
     [  6.07600000e-02   0.00000000e+00   1.19300000e+01 ...,   3.96900000e+02
        5.64000000e+00   2.39000000e+01]
     [  1.09590000e-01   0.00000000e+00   1.19300000e+01 ...,   3.93450000e+02
    
        6.48000000e+00   2.20000000e+01]
     [  4.74100000e-02   0.00000000e+00   1.19300000e+01 ...,   3.96900000e+02
        7.88000000e+00   1.19000000e+01]]
    

    好吧,如果你看到这些值,它们并没有经过缩放,不能直接输入到预测模型中。因此,我们需要应用最小-最大缩放来使特征结构更加合理,以便估算器能单独对每个特征进行缩放和转换,并确保其在训练集中的给定范围内,即在零到一之间。由于特征在预测分析中非常重要,我们应该特别关注它们。以下代码行完成了最小-最大缩放:

    x_vals = (x_vals - x_vals.min(0)) / x_vals.ptp(0)
    

    现在让我们再次打印它们,以检查确保发生了什么变化:

    print(x_vals)
    >>>
    [[  0.00000000e+00   1.80000000e-01   6.78152493e-02 ...,   1.00000000e+008.96799117e-02   4.22222222e-01]
     [  2.35922539e-04   0.00000000e+00   2.42302053e-01 ...,   1.00000000e+002.04470199e-01   3.68888889e-01]
     [  2.35697744e-04   0.00000000e+00   2.42302053e-01 ...,   9.89737254e-016.34657837e-02   6.60000000e-01] ..., 
     [  6.11892474e-04   0.00000000e+00   4.20454545e-01 ...,   1.00000000e+001.07891832e-01   4.20000000e-01]
     [  1.16072990e-03   0.00000000e+00   4.20454545e-01 ...,   9.91300620e-01
        1.31070640e-01   3.77777778e-01]
     [  4.61841693e-04   0.00000000e+00   4.20454545e-01 ...,   1.00000000e+00
        1.69701987e-01   1.53333333e-01]]
    
  4. 准备训练集和测试集。

    由于我们的特征已经进行了缩放,现在是时候将数据分成训练集和测试集了。现在,我们将 x 和 y 值分别拆分成训练集和测试集。我们将通过随机选择约 75% 的行来创建训练集,剩余的 25% 用于测试集:

    train_indices = np.random.choice(len(x_vals), int(len(x_vals)*0.75), replace=False)
    test_indices = np.array(list(set(range(len(x_vals))) - set(train_indices)))
    x_vals_train = x_vals[train_indices]
    x_vals_test = x_vals[test_indices]
    y_vals_train = y_vals[train_indices]
    y_vals_test = y_vals[test_indices]
    
  5. 准备张量的占位符。

    首先,我们将声明批量大小。理想情况下,批量大小应该等于测试集中特征的大小:

    batch_size=len(x_vals_test)
    

    接下来,我们需要声明 TensorFlow 张量的占位符,如下所示:

    x_data_train = tf.placeholder(shape=[None, num_features], dtype=tf.float32)
    x_data_test = tf.placeholder(shape=[None, num_features], dtype=tf.float32)
    y_target_train = tf.placeholder(shape=[None, 1], dtype=tf.float32)
    y_target_test = tf.placeholder(shape=[None, 1], dtype=tf.float32)
    
  6. 定义距离度量。

    对于这个例子,我们将使用 L1 距离。原因是使用 L2 距离在我的案例中没有得到更好的结果:

    distance = tf.reduce_sum(tf.abs(tf.subtract(x_data_train, tf.expand_dims(x_data_test,1))), axis=2)
    
  7. 实现 kNN。

    现在,到了实现 kNN 的时候了。这将通过获取最小距离的索引来预测最近的邻居。kNN() 方法完成了这个任务。为此,我们需要按以下步骤操作:

    1. 获取最小距离的索引。

    2. 计算预测函数。为此,我们将使用top_k()函数,它返回张量中最大值的值和索引。由于我们需要的是最小距离的索引,所以我们将查找 k 个最大的负距离。由于我们预测的是连续值,也就是回归任务,我们还声明了预测值和均方误差MSE)的目标值。

    3. 计算训练数据的循环次数。

    4. 初始化全局变量。

    5. 根据步骤 3 计算的循环次数进行训练迭代。

    现在,这是 kNN 的函数。它接受初始邻居的数量并开始计算。请注意,尽管这是一个广泛使用的约定,但在这里我将其做成一个变量以便进行一些调优,如下所示:

    def kNN(k): 
        topK_X, topK_indices = tf.nn.top_k(tf.negative(distance), k=k)
        x_sums = tf.expand_dims(tf.reduce_sum(topK_X, 1), 1)
        x_sums_repeated = tf.matmul(x_sums,tf.ones([1, k], tf.float32))
        x_val_weights = tf.expand_dims(tf.div(topK_X, x_sums_repeated), 1)
        topK_Y = tf.gather(y_target_train, topK_indices)
        prediction = tf.squeeze(tf.matmul(x_val_weights,topK_Y), axis=[1])
       mse = tf.div(tf.reduce_sum(tf.square(tf.subtract(prediction, y_target_test))), batch_size)
        num_loops = int(np.ceil(len(x_vals_test)/batch_size))
        init_op = tf.global_variables_initializer()
        with tf.Session() as sess:
                sess.run(init_op) 
                for i in range(num_loops):
                    min_index = i*batch_size
                    max_index = min((i+1)*batch_size,len(x_vals_train))
                    x_batch = x_vals_test[min_index:max_index]
                    y_batch = y_vals_test[min_index:max_index]
                    predictions = sess.run(prediction, feed_dict={x_
    data_train: x_vals_train, x_data_test: x_batch, y_target_train: y_vals_train, y_target_test: y_batch})
                    batch_mse = sess.run(mse, feed_dict={x_data_train: x_vals_train, x_data_test: x_batch, y_target_train: y_vals_train, y_target_test: y_batch})           
        return batch_mse
    
  8. 评估分类/回归。

    注意,这个函数并没有返回最优的mse值,也就是最低的mse值,而是在不同的k值下变化,因此它是一个需要调优的超参数。一个可能的技术是将方法迭代从k = 211,并跟踪哪个最优的k值使得kNN()产生最低的mse值。首先,我们定义一个方法,迭代多次从211,并分别返回两个单独的列表,包含msek值:

    mse_list = []
    k_list = []
    def getOptimalMSE_K():
        mse = 0.0
        for k in range(2, 11):
            mse = kNN(k)  
            mse_list.append(mse)
            k_list.append(k)
        return k_list, mse_list 
    

    现在,是时候调用前面的那个方法,找到使得 kNN 产生最低mse值的最优k值了。在收到这两个列表后,我们创建一个字典,并使用min()方法来返回最优的k值,代码如下:

    k_list, mse_list  = getOptimalMSE_K()
    dict_list = zip(k_list, mse_list)
    my_dict = dict(dict_list)
    print(my_dict)
    optimal_k = min(my_dict, key=my_dict.get)
    >>>
    {2: 7.6624126, 3: 10.184645, 4: 8.9112329, 5: 11.29573, 6: 13.341181, 7: 14.406253, 8: 13.923589, 9: 14.915736, 10: 13.920851}
    

    现在,让我们打印出最优的 k 值,即能够获得最低mse值的k值:

    print("Optimal K value: ", optimal_k)
    mse = min(mse_list)
    print("Minimum mean square error: ", mse)
    >>>
    Optimal K value: 2 minimum mean square error: 7.66241
    
  9. 运行最佳 kNN。

    现在我们有了最优的k值,我们将进行最近邻计算。这次,我们会尝试返回预测标签和实际标签的矩阵:

    def bestKNN(k): 
        topK_X, topK_indices = tf.nn.top_k(tf.negative(distance), k=k)
        x_sums = tf.expand_dims(tf.reduce_sum(topK_X, 1), 1)
        x_sums_repeated = tf.matmul(x_sums,tf.ones([1, k], tf.float32))
        x_val_weights = tf.expand_dims(tf.div(topK_X, x_sums_repeated), 1)
        topK_Y = tf.gather(y_target_train, topK_indices)
        prediction = tf.squeeze(tf.matmul(x_val_weights,topK_Y), axis=[1])
        num_loops = int(np.ceil(len(x_vals_test)/batch_size))
        init_op = tf.global_variables_initializer()
        with tf.Session() as sess:
                sess.run(init_op) 
                for i in range(num_loops):
                    min_index = i*batch_size
                    max_index = min((i+1)*batch_size,len(x_vals_train))
                    x_batch = x_vals_test[min_index:max_index]
                    y_batch = y_vals_test[min_index:max_index]
    

    predictions = sess.run(prediction, feed_dict={x_data_train: x_vals_train, x_data_test: x_batch, y_target_train: y_vals_train, y_target_test: y_batch})

        return predictions, y_batch
    
  10. 评估最佳 kNN。

    现在,我们将调用bestKNN()方法,使用在前一步中计算出的最优k值,代码如下:

    predicted_labels, actual_labels = bestKNN(optimal_k)
    

    现在,我想要测量预测的准确性。你是不是在想为什么?我知道原因。你没错。实际上,计算准确率或精度并没有特别重要的理由,因为我们预测的是连续值,也就是标签。尽管如此,我还是想展示给你看是否有效:

    def getAccuracy(testSet, predictions):
     correct = 0
     for x in range(len(testSet)):
         if(np.round(testSet[x]) == np.round(predictions[x])):
                    correct += 1
     return (correct/float(len(testSet))) * 100.0
    accuracy = getAccuracy(actual_labels, predicted_labels)
    print('Accuracy: ' + repr(accuracy) + '%')
    >>>
    Accuracy: 17.322834645669293%
    

    前面的getAccuracy()方法计算了准确率,但结果很低。这是显而易见的,且没有什么困难。这也意味着前面的这个方法没有什么意义。然而,如果你即将预测的是离散值,这个方法显然会对你有所帮助。试试用适当的数据和前面代码的组合来进行测试。

    但不要失望;我们还有另一种方式来观察我们的预测模型表现。我们仍然可以绘制一个直方图,展示预测标签与实际标签之间的分布:

    bins = np.linspace(5, 50, 45)
    plt.hist(predicted_labels, bins, alpha=1.0, facecolor='red', label='Prediction')
    plt.hist(actual_labels, bins, alpha=1.0, facecolor='green', label='Actual')
    plt.title('predicted vs actual values')
    plt.xlabel('Median house price in $1,000s')
    plt.ylabel('count')
    plt.legend(loc='upper right')
    plt.show()
    >>>
    

    基于 kNN 的预测模型实现

    图 16:房屋的预测价格与实际中位数价格(单位:千美元)

总结

在本课中,我们从理论和实践的角度讨论了无监督学习。我们已经了解了如何利用预测分析,并发现如何利用它将属于某个组或类别的记录聚类,以便对无监督观测数据集进行分析。我们讨论了使用 K 均值进行无监督学习和聚类。此外,我们还了解了如何通过肘部法则来优化聚类,以提高预测准确性。我们还讨论了如何使用 K 均值预测邻近区域,接着,我们看到了另一个基于音频特征对音频片段进行聚类的例子。最后,我们看到了如何使用无监督 kNN 预测最近邻。

在下一课中,我们将讨论使用 TensorFlow 的文本分析这一精彩领域。文本分析是自然语言处理NLP)中的一个广泛领域,机器学习在许多应用场景中都非常有用,例如情感分析、聊天机器人、电子邮件垃圾邮件检测、文本挖掘和自然语言处理。你将学习如何使用 TensorFlow 进行文本分析,重点关注基于非结构化垃圾邮件预测和电影评论数据集的文本分类应用场景。在垃圾邮件过滤数据集的基础上,我们将使用 LR 算法和 TensorFlow 开发预测模型。

评估

  1. kNN 属于 ______ 学习算法家族。

  2. 判断以下陈述是对还是错:一个簇是具有相似性的对象集合,与属于其他簇的对象不相似。如果提供了一组对象,聚类算法会根据相似性将这些对象分组。

  3. 无监督学习的主要目标是什么?

  4. 判断以下陈述是对还是错:在聚类任务中,算法通过分析输入示例之间的相似性,将相关特征分为类别,其中相似的特征被聚类并用圆圈标记。

  5. 聚类分析是关于将数据样本或数据点划分并放入相应的 ______ 类别或簇中。

    1. 异质的

    2. 线性的

    3. 同质的

    4. 相似的。

第四章:使用强化学习进行预测分析

作为人类,我们从过去的经验中学习。我们并不是偶然变得如此迷人。多年的正面赞美以及负面批评帮助我们塑造了今天的自己。我们通过尝试不同的肌肉动作直到掌握了骑自行车的方法。当你执行某些动作时,某些时候会立刻得到奖励。这就是强化学习RL)。

本课程的核心是设计一个由批评和奖励驱动的机器学习系统。我们将看到如何将强化学习算法应用于实际数据集的预测模型。

简而言之,本课程将涵盖以下主题:

  • 强化学习

  • 用于预测分析的强化学习

  • 强化学习中的符号、策略和效用

  • 开发多臂赌博机的预测模型

  • 开发股票价格预测模型

强化学习

从技术角度来看,监督学习和无监督学习分别位于两端,而强化学习则处于中间。它不是监督学习,因为训练数据来自算法在探索与利用之间的选择。它也不是无监督学习,因为算法会从环境中接收反馈。只要你处于一个在某个状态下执行某个动作会产生奖励的情况,你就可以使用强化学习来发现一系列好的动作,以获得最大期望奖励。

强化学习智能体的目标是最大化它在长期内所获得的总奖励。第三个主要子元素是价值函数。

奖励决定了状态的即时可取性,而价值则表示了状态的长期可取性,考虑到可能随之而来的状态以及这些状态中的可用奖励。价值函数是根据所选策略来指定的。在学习阶段,智能体会尝试采取能够确定具有最高价值的状态的行动,因为这些行动在长期内将获得最佳奖励。

预测分析中的强化学习

图 1 显示了一个人做决策以到达目的地。此外,假设你从家到工作一直选择相同的路线。但有一天,你的好奇心占了上风,你决定尝试一条不同的路,希望能缩短通勤时间。这种尝试新路线还是坚持最熟悉路线的困境,就是探索与利用的例子:

预测分析中的强化学习

图 1:智能体总是试图通过路线到达目的地

强化学习技术正在被应用于许多领域。目前一个正在追求的总体目标是创建一个只需要任务描述的算法。一旦这种性能实现,它将几乎在所有地方得到应用。

强化学习中的符号、政策和效用

你可能会注意到,强化学习的术语涉及到将算法人格化,使其在特定情境下采取行动以获得奖励。事实上,算法通常被称为一个代理,它与环境互动。你可以将它看作是一个智能硬件代理,利用传感器感知并通过执行器与环境互动。

因此,强化学习理论被广泛应用于机器人技术并不奇怪。图 2 展示了状态、行动和奖励之间的相互作用。如果你从状态s1开始,你可以执行行动a1来获得奖励 r(s1a1)。行动由箭头表示,状态由圆圈表示:

强化学习中的符号、政策和效用

图 2:一个代理在某个状态上执行一个行动产生奖励

一个机器人通过执行行动在不同的状态之间转换。但它是如何决定采取哪种行动的呢?这完全依赖于使用不同的或具体的策略。

政策

在强化学习的术语中,我们称这种策略为策略。强化学习的目标是发现一种有效的策略。解决问题的最常见方法之一是观察每个状态下行动的长期后果。短期后果很容易计算:那就是奖励。虽然执行一个行动会立即获得奖励,但贪婪地选择最佳奖励的行动并不总是一个好主意。

这也是生活中的一课,因为最立即的最佳选择可能并不总是长期来看最令人满意的。最佳的策略被称为最优策略,它通常是强化学习的圣杯,如图 3 所示,显示了在任何状态下的最优行动:

政策

图 3:一个策略定义了在给定状态下要采取的行动

到目前为止,我们已经看到了一个策略的例子,其中代理总是选择立即获得最大奖励的行动,这被称为贪婪策略。另一个简单的策略例子是随机选择一个行动,这被称为随机策略。如果你为强化学习问题设计了一个策略,通常建议你再次检查所学到的策略是否优于随机策略和贪婪策略。

此外,我们还将看到如何开发另一个强健的策略,称为策略梯度,其中神经网络通过调整其权重来学习选择行动的策略,方法是通过梯度下降使用来自环境的反馈。我们将看到,尽管这两种方法都在使用,但策略梯度更直接且更为乐观。

效用

长期奖励称为效用。事实证明,如果我们知道在某个状态下执行某个行动的效用,那么强化学习就变得容易解决。例如,要决定采取哪个行动,我们只需选择产生最高效用的行动。然而,揭示这些效用值才是更难解决的部分。执行行动a在状态s下的效用写作一个函数Q(s, a),称为效用函数,它预测期望的即时奖励加上遵循最优策略的奖励,该策略根据状态-行动输入计算,如图 4 所示:

Utility

图 4:使用效用函数

大多数强化学习算法归结为三个主要步骤:推理、执行和学习。在第一步中,算法使用到目前为止所获得的知识,从给定的状态(s)中选择最佳行动(a)。接下来,它执行该行动以获得奖励(r)以及下一个状态(s')。然后,它使用新获得的知识(s, r, a, s')来改进对世界的理解。然而,这只是计算效用的一种简单方法,你也会同意这一点。

现在,问题是有什么更稳健的方式来计算它吗?这是我的一些看法。我们可以通过递归地考虑未来行动的效用,来计算特定状态-行动对(s, a)的效用。你当前行动的效用不仅受到即时奖励的影响,还受到下一个最佳行动的影响,如下公式所示:

Utility

在之前的公式中,s'表示下一个状态,a'表示下一个行动。采取行动a在状态s中的奖励表示为r(s, a)。这里,γ是一个超参数,你可以选择,称为折扣因子。如果γ0,那么代理将选择最大化即时奖励的行动。更高的γ值会让代理更重视考虑长期后果。

在实践中,我们需要考虑更多的超参数。例如,如果预期吸尘机器人能够快速学习解决任务,但不一定是最优的,我们可能希望设置一个更快的学习率。相反,如果允许机器人有更多时间来探索和利用,我们可能会降低学习率。我们将学习率称为α,并按以下方式修改我们的效用函数(注意,当α = 1时,这两个公式是相同的):

Utility

总结来说,如果我们知道这个Q(s, a)函数,就可以解决强化学习问题。这里出现了一个叫做神经网络的机器学习策略,神经网络是一种通过足够的训练数据来逼近函数的方式。此外,TensorFlow 是处理神经网络的完美工具,因为它包含了许多必要的算法。

在接下来的两个部分中,我们将看到两个使用 TensorFlow 实现的示例。第一个示例是开发一个多臂强盗智能体的简单方法,用于预测模型。接着,第二个示例使用神经网络实现来进行股票价格预测,稍微复杂一些。

开发多臂强盗的预测模型

最简单的强化学习问题之一被称为 n 臂强盗问题。问题在于,有 n 个老丨虎丨机,但每个老丨虎丨机的固定奖励概率不同。目标是通过总是选择奖励最好的机器来最大化利润。

如前所述,我们还将看到如何使用策略梯度来产生显式的输出。对于我们的多臂强盗问题,我们不需要在任何特定状态上正规化这些输出。为了简单起见,我们可以设计我们的网络,使其仅由一组权重组成,这些权重对应于强盗中每个可能的臂。然后,我们将表示智能体认为拉取每个臂以获得最大利润的好坏。一个简单的方法是将这些权重初始化为 1,这样智能体会对每个臂的潜在奖励持乐观态度。

为了更新网络,我们可以尝试选择一个具有贪婪策略的臂,这在前面已经讨论过。我们的策略是,智能体每次执行一个动作后,都会收到 1-1 的奖励。我知道这不是一个现实的设想,但大多数时候,智能体会选择一个随机的动作,对应于最大的期望值。

我们将逐步开发一个简单但有效的强盗智能体,以解决多臂强盗问题。最初,不会有状态,即我们将拥有一个无状态的智能体。然后,我们将看到,使用无状态的强盗智能体来解决复杂问题会出现偏差,以至于我们无法在实际生活中使用它。

然后,我们将通过添加或将样本强盗转换为上下文强盗来增加智能体的复杂度。上下文强盗将成为一个有状态的智能体,从而可以更高效地解决我们的预测问题。最后,我们将通过将文本强盗转换为完整的强化学习智能体,进一步增加智能体的复杂度,直到部署:

  1. 加载所需的库。

    加载所需的库和所需的包/模块:

    import tensorflow as tf
    import tensorflow.contrib.slim as slim
    import numpy as np
    
  2. 定义强盗。

    在这个示例中,我使用的是四臂强盗。getBandit 函数从正态分布中生成一个随机数,均值为 0。Bandit 数字越低,获得正奖励的概率越大。如前所述,这只是一种简单但贪婪的方式来训练智能体,使其学会选择一个不仅能产生正奖励,而且能产生最大奖励的强盗。这里,我列出了强盗的列表,其中 Bandit 4 最常提供正奖励:

    def getBandit(bandit):
        ''
        This function creates the reward to the bandits on the basis of randomly generated numbers. It then returns either a positive or negative reward.
        '' 
        random_number = np.random.randn(1)
        if random_number > bandit:   
            return 1
        else:
            return -1
    
  3. 为强盗问题开发一个智能体。

    以下代码创建了一个非常简单的神经代理,它包含一个为每个bandits设置的值。每个值根据bandits的回报值估算为 1。我们使用策略梯度方法通过将所选行动的值朝着获得的奖励方向更新代理。首先,我们需要重置图形,如下所示:

    tf.reset_default_graph()
    

    然后,接下来的两行代码实际完成选择操作,建立网络的前馈部分:

    weight_op = tf.Variable(tf.ones([num_bandits]))
    action_op = tf.argmax(weight_op,0)
    

    现在,在开始训练过程之前,我们需要启动训练过程本身。因为我们已经知道奖励,现在是时候将奖励输入并选择网络中的行动来计算损失,并用它来更新网络:

    reward_holder = tf.placeholder(shape=[1],dtype=tf.float32)
    action_holder = tf.placeholder(shape=[1],dtype=tf.int32)
    responsible_weight = tf.slice(weight_op,action_holder,[1])
    

    我们需要定义目标函数,即损失函数:

    loss = -(tf.log(responsible_weight)*reward_holder)
    

    然后让我们将训练过程放慢,以便更全面地利用学习率:

    LR = 0.001
    

    然后我们使用梯度下降optimizer并实例化training操作:

    optimizer = tf.train.GradientDescentOptimizer(learning_rate=LR)
    training_op = optimizer.minimize(loss)
    

    现在,是时候定义训练参数了,比如训练代理的总迭代次数、reward函数和一个random行动。这里的奖励将bandits的计分板设置为0,通过选择随机行动,我们设置采取随机行动的概率:

    total_episodes = 10000
    total_reward = np.zeros(num_bandits) 
    chance_of_random_action = 0.1 
    

    最后,我们初始化全局变量:

    init_op = tf.global_variables_initializer() 
    
  4. 训练代理。

    我们需要通过采取行动与环境互动并获得奖励来训练代理。我们从创建一个 TensorFlow 会话并启动 TensorFlow 图开始。接着,迭代训练过程直到达到总迭代次数。然后,我们选择一个随机行为或从网络中选择一个行为。接下来,我们计算选择一个bandits后的奖励。然后,我们使训练过程保持一致并更新网络。最后,我们更新计分板:

    with tf.Session() as sess:
        sess.run(init_op)
        i = 0
        while i < total_episodes:        
            if np.random.rand(1) < chance_of_random_action:
                action = np.random.randint(num_bandits)
            else:
                action = sess.run(action_op)
                    reward = getBandit(bandits[action])         
                _,resp,ww = sess.run([training_op,responsible_weight,weight_op], feed_dict={reward_holder:[reward],action_holder:[action]})
            total_reward[action] += reward
            if i % 50 == 0:
                print("Running reward for all the " + str(num_bandits) + " bandits: " + str(total_reward))
            i+=1
    

    现在让我们按如下方式评估上述模型:

    print("The agent thinks bandit " + str(np.argmax(ww)+1) + " would be the most efficient one.")
    if np.argmax(ww) == np.argmax(-np.array(bandits)):
        print(" and it was right at the end!")
    else:
        print(" and it was wrong at the end!")
    >>>
    

    第一次迭代生成以下输出:

    Running reward for all the 4 bandits: [-1\. 0\. 0\. 0.]
    Running reward for all the 4 bandits: [ -1\. -2\. 14\. 0.]
    …
    Running reward for all the 4 bandits: [ -15\. -7\. 340\. 21.]
    Running reward for all the 4 bandits: [ -15\. -10\. 364\. 22.]
    The agent thinks Bandit 3 would be the most efficient one and it was wrong at the end!
    

    第二次迭代生成一个不同的结果,如下所示:

    Running reward for all the 4 bandits: [ 1\. 0\. 0\. 0.]
    Running reward for all the 4 bandits: [ -1\. 11\. -3\. 0.]
    Running reward for all the 4 bandits: [ -2\. 1\. -2\. 20.]
    …
    Running reward for all the 4 bandits: [ -7\. -2\. 8\. 762.]
    Running reward for all the 4 bandits: [ -8\. -3\. 8\. 806.]
    The agent thinks Bandit 4 would be the most efficient one and it was right at the end!
    

    现在,如果你看到这个代理是一个无状态的代理,它随机预测选择哪个 bandits。在这种情况下,没有环境状态,代理必须简单地学习选择最好的行动。为了解决这个问题,我们可以考虑开发上下文 bandits。

    使用上下文 bandits,我们可以引入并合理利用状态。状态由代理可以用来做出更智能、更有信息的行动的环境说明组成。关键是,与其使用单个 bandit,我们可以将多个 bandits 串联在一起。那么,状态的作用是什么呢?环境的状态告诉代理从可用列表中选择一个 bandit。另一方面,代理的目标是学习对于任意数量的 bandits,最好的行动。

    这样,代理会遇到一个问题,因为每个强盗对每个手臂的奖励概率可能不同,而代理需要学会如何在环境状态下执行动作。否则,代理无法实现最大可能的奖励:

    开发多臂强盗的预测模型

    图 5:无状态与上下文强盗

    如前所述,为了解决这个问题,我们可以构建一个单层神经网络,使其能够接受状态并输出动作。现在,类似于随机强盗,我们也可以使用策略梯度更新方法,使得网络更新更容易采取动作以最大化奖励。这种简化的强化学习问题表示方式被称为上下文强盗。

  5. 开发上下文强盗。

    本例基于 Arthur Juliani 发表在 medium.com/ 的《简单强化学习与 TensorFlow 第 1.5 部分:上下文强盗》进行了改编和扩展。

    首先,让我们定义我们的上下文强盗。在这个例子中,我们将看到如何使用三个四臂强盗,也就是说,每个强盗有四个手臂可以拉动以执行动作。由于每个强盗都是上下文相关的并且有一个状态,因此它们的手臂有不同的成功概率。这需要执行不同的动作来获得最佳的预测结果。

    在这里,我们定义了一个名为 contextual_bandit() 的类,其中包含一个构造函数和两个用户定义的函数:getBandit()pullArm()getBandit() 函数从均值为 0 的正态分布中生成一个随机数。Bandit 数字越低,返回正奖励的可能性越大。我们希望我们的代理学会选择最常返回正奖励的强盗手臂。当然,这取决于呈现的强盗。该构造函数列出了我们所有的强盗。我们假设当前状态为武装 4231,这些是最优的选择。

    此外,如果你仔细观察,大多数强化学习算法遵循类似的实现模式。因此,创建一个包含相关方法的类是个好主意,以便以后引用,例如抽象类或接口:

    class contextualBandit():
        def __init__(self):
            self.state = 0        
            self.bandits = np.array([[0.2,0,-0.0,-5], [0.1,-5,1,0.25], [0.3,0.4,-5,0.5], [-5,5,5,5]])
            self.num_bandits = self.bandits.shape[0]
            self.num_actions = self.bandits.shape[1]
            def getBandit(self):        
            '''
            This function returns a random state for each episode.
            '''
            self.state = np.random.randint(0, len(self.bandits)) 
            return self.state
            def pullArm(self,action):        
            '''
            This function creates the reward to the bandits on the basis of randomly generated numbers. It then returns either a positive or negative reward that is action
            ''' 
            bandit = self.bandits[self.state, action]
            result = np.random.randn(1)
            if result > bandit:
                return 1
            else:
                return -1
    
  6. 开发基于策略的代理。

    以下类 ContextualAgent 帮助开发我们的简单但非常有效的神经和上下文代理。我们提供当前状态作为输入,代理会根据环境的状态返回一个动作。这是将无状态代理转变为有状态代理并能够解决完整强化学习问题的最重要一步。

    在这里,我尝试开发一个代理,使其在给定强盗的情况下使用一组权重来选择特定的手臂。使用策略梯度方法通过将特定动作的值朝最大奖励的方向移动来更新代理:

    class ContextualAgent():
        def __init__(self, lr, s_size,a_size):
            '''
            This function establishes the feed-forward part of the network. The agent takes a state and produces an action -that is. contextual agent
            ''' 
            self.state_in= tf.placeholder(shape=[1], dtype=tf.int32)
            state_in_OH = slim.one_hot_encoding(self.state_in, s_size)
            output = slim.fully_connected(state_in_OH, a_size,biases_initializer=None, activation_fn=tf.nn.sigmoid, weights_initializer=tf.ones_initializer())
            self.output = tf.reshape(output,[-1])
            self.chosen_action = tf.argmax(self.output,0)
            self.reward_holder = tf.placeholder(shape=[1], dtype=tf.float32)
            self.action_holder = tf.placeholder(shape=[1], dtype=tf.int32)
            self.responsible_weight = tf.slice(self.output, self.action_holder,[1])
            self.loss = -(tf.log(self.responsible_weight)*self.reward_holder)
            optimizer = tf.train.GradientDescentOptimizer(learning_rate=lr)
            self.update = optimizer.minimize(self.loss)
    
  7. 训练上下文强盗代理。

    首先,我们清除默认的 TensorFlow 图:

    tf.reset_default_graph()
    

    然后,我们定义一些参数,供训练代理时使用:

    lrarning_rate = 0.001 # learning rate 
    chance_of_random_action = 0.1 # Chance of a random action.
    max_iteration = 10000 #Max iteration to train the agent.
    

    现在,在开始训练之前,我们需要加载强盗,并接着加载我们的代理:

    contextualBandit = contextualBandit() #Load the bandits.
    contextualAgent = ContextualAgent(lr=lrarning_rate, s_size=contextualBandit.num_bandits, a_size=contextualBandit.num_actions) #Load the agent.
    

    现在,为了最大化目标函数对总回报的影响,我们使用weights来评估网络。我们还将bandits的记分板初始设置为0

    weights = tf.trainable_variables()[0] 
    total_reward = np.zeros([contextualBandit.num_bandits,contextualBandit.num_actions])
    

    然后,我们使用global_variables_initializer()函数初始化所有变量:

    init_op = tf.global_variables_initializer()
    

    最后,我们将开始训练。这个训练与我们在前面示例中进行的随机训练类似。然而,在这里,训练的主要目标是计算每个强盗的平均回报,以便我们稍后可以通过利用它们来评估代理的预测准确性:

    with tf.Session() as sess:
        sess.run(init_op)
        i = 0
        while i < max_iteration:
            s = contextualBandit.getBandit() #Get a state from the environment.
            #Choose a random action or one from our network.
            if np.random.rand(1) < chance_of_random_action:
                action = np.random.randint(contextualBandit.num_actions)
            else:
                action = sess.run(contextualAgent.chosen_action,feed_dict={contextualAgent.state_in:[s]})
            reward = contextualBandit.pullArm(action) #Get our reward for taking an action given a bandit.
            #Update the network.
            feed_dict={contextualAgent.reward_holder:[reward],contextualAgent.action_
    holder:[action],contextualAgent.state_in:[s]}
            _,ww = sess.run([contextualAgent.update,weights], feed_dict=feed_dict)        
            #Update our running tally of scores.
            total_reward[s,action] += reward
            if i % 500 == 0:
                print("Mean reward for each of the " + str(contextualBandit.num_bandits) + " bandits: " + str(np.mean(total_reward,axis=1)))
            i+=1
    >>>
    Mean reward for each of the 4 bandits: [ 0\. 0\. -0.25 0\. ]
    Mean reward for each of the 4 bandits: [ 25.75 28.25 25.5 28.75]
    …
    Mean reward for each of the 4 bandits: [ 488.25 489\. 473.5 440.5 ]
    Mean reward for each of the 4 bandits: [ 518.75 520\. 499.25 465.25]
    Mean reward for each of the 4 bandits: [ 546.5 547.75 525.25 490.75]
    
  8. 评估代理。

    现在,我们已经有了所有四个强盗的平均回报,是时候利用它们来预测一些有趣的事情,也就是哪个强盗的臂将最大化回报。首先,我们可以初始化一些变量来估计预测准确性:

    right_flag = 0
    wrong_flag = 0
    

    然后让我们开始评估代理的预测性能:

    for a in range(contextualBandit.num_bandits):
        print("The agent thinks action " + str(np.argmax(ww[a])+1) + " for bandit " + str(a+1) + " would be the most efficient one.")
        if np.argmax(ww[a]) == np.argmin(contextualBandit.bandits[a]):
            right_flag += 1
            print(" and it was right at the end!")
        else:	
            print(" and it was wrong at the end!")
            wrong_flag += 1
    >>>
    The agent thinks action 4 for Bandit 1 would be the most efficient one and it was right at the end!
    The agent thinks action 2 for Bandit 2 would be the most efficient one and it was right at the end!
    The agent thinks action 3 for Bandit 3 would be the most efficient 
    ne and it was right at the end!
    The agent thinks action 1 for Bandit 4 would be the most efficient one and it was right at the end!
    

    如你所见,所有的预测都是正确的预测。现在我们可以按如下方式计算准确率:

    prediction_accuracy = (right_flag/right_flag+wrong_flag)
    print("Prediction accuracy (%):", prediction_accuracy * 100)
    >>>
    Prediction accuracy (%): 100.0
    

太棒了,做得好!我们通过一个上下文代理成功设计并开发了一个更强大的强盗代理,能够准确预测哪个臂,即哪个行动的强盗能够帮助实现最大回报,也就是利润。

在接下来的章节中,我们将看到另一个非常有趣但又非常实用的股票价格预测应用,我们将展示如何从强化学习的框架中开发一个基于策略的 Q 学习代理。

开发股票价格预测模型

一个新兴的应用领域是股市交易,其中交易员像一个强化学习代理,因为买卖(即行动)特定股票会通过产生利润或亏损来改变交易员的状态,也就是回报。下图展示了 2017 年 7 月 15 日一些最活跃的股票(例如):

开发股票价格预测模型

图 6: finance.yahoo.com/

现在,我们希望开发一个智能代理,预测股票价格,以便交易员能够以低价买入并以高价卖出。然而,这种预测并不容易,它依赖于多个参数,比如当前的股票数量、最近的历史价格,以及最重要的,买卖时可用的投资预算。

在这种情况下,状态是一个包含当前预算、当前股票数量和最近的股价历史(最近 200 个股价)信息的向量。所以每个状态是一个 202 维的向量。为了简化,股市代理需要执行的动作只有三种:买入、卖出和持有。

所以,我们已经有了状态和动作,那还缺什么?策略,对吧?是的,我们应该有一个好的策略,这样基于该策略,才能在某个状态下执行动作。一个简单的策略可以包含以下规则:

  • 在当前股价(即状态)下购买(即动作)股票会减少预算,同时增加当前股票的数量

  • 卖出股票则按当前股价将股票换成现金

  • 持有既不减少预算也不增加股票数量,执行该动作只是等待一个特定的时间段,并且不会获得任何奖励

要查找股票价格,我们可以使用 Python 中的 yahoo_finance 库。你可能会遇到一个常见的警告:“HTTPError: HTTP Error 400: Bad Request”。但请继续尝试。

现在,让我们尝试熟悉这个模块:

>>> from yahoo_finance import Share
>>> msoft = Share('MSFT')
>>> print(msoft.get_open())
72.24=
>>> print(msoft.get_price())
72.78
>>> print(msoft.get_trade_datetime())
2017-07-14 20:00:00 UTC+0000
>>>

所以截至 2017 年 7 月 14 日,微软公司的股价从 72.24 上涨至 72.78,这意味着大约 7.5%的涨幅。然而,这一小段仅一天的数据并未给我们任何显著的信息。但至少我们了解了这只特定股票或工具的当前状态。

要安装 yahoo_finance,请执行以下命令:

$ sudo pip3 install yahoo_finance 

现在,值得查看一下历史数据。以下函数帮助我们获取微软公司的历史数据:

def get_prices(share_symbol, start_date, end_date, cache_filename):
    try:
        stock_prices = np.load(cache_filename)
    except IOError:
        share = Share(share_symbol)
        stock_hist = share.get_historical(start_date, end_date)
        stock_prices = [stock_price['Open'] for stock_price in stock_hist]
        np.save(cache_filename, stock_prices)
    return stock_prices

get_prices() 方法接受几个参数,如股市中某个工具的股票符号、开始日期和结束日期。你还可以指定并缓存历史数据,以避免重复下载。一旦下载了数据,就可以绘制图表以获得一些洞察。

以下函数帮助我们绘制价格:

def plot_prices(prices):
    plt.title('Opening stock prices')
    plt.xlabel('day')
    plt.ylabel('price ($)')
    plt.plot(prices)
    plt.savefig('prices.png')

现在我们可以通过指定实际参数来调用这两个函数,如下所示:

if __name__ == '__main__':
    prices = get_prices('MSFT', '2000-07-01', '2017-07-01', 'historical_stock_prices.npy')
    plot_prices(prices)

在这里,我选择了 17 年的广泛历史数据,以获得更好的洞察。现在,让我们来看看这些数据的输出:

开发股票价格预测模型

图 7:微软公司 2000 年至 2017 年的历史股价数据

目标是学习一种策略,从股票市场交易中获得最大净资产。那么,最终交易代理会实现什么目标呢?图 8 给了你一些线索:

开发股票价格预测模型

图 8:一些洞察和线索,显示根据当前价格,最多可以获得$160 的利润

好吧,图 8 显示,如果代理以$20 购买某个工具,并在峰值价格如$180 时卖出,它将能够获得$160 的奖励,也就是利润。

所以,使用强化学习算法实现这样一个智能代理是个不错的主意吗?

从之前的例子中,我们已经看到,要实现一个成功的强化学习代理,我们需要定义两个操作,分别如下:

  • 如何选择一个动作

  • 如何提高效用 Q 函数

更具体来说,给定一个状态,决策策略将计算出下一步的动作。另一方面,通过从执行某个动作中获得的新经验来改进 Q 函数。

此外,大多数强化学习算法归结为三个主要步骤:推理、执行和学习。在第一步中,算法根据当前状态(s)使用迄今为止所获得的知识选择最佳行动(a)。接下来,它执行该行动以找到奖励(r)以及下一个状态(s')。

然后,它使用新获得的知识(s, r, a, s')来改善对世界的理解,如下图所示:

开发股票价格预测模型

图 9:实现智能股票价格预测代理的步骤

现在,让我们开始实现决策策略,基于该策略采取购买、卖出或持有股票的行动。同样,我们将以增量方式进行。首先,我们将创建一个随机决策策略并评估代理的性能。

但在此之前,让我们创建一个抽象类,以便我们可以相应地实现它:

class DecisionPolicy:
    def select_action(self, current_state, step):
        pass
    def update_q(self, state, action, reward, next_state):
        pass

接下来的任务是从这个父类继承,实施一个随机决策策略:

class RandomDecisionPolicy(DecisionPolicy):
    def __init__(self, actions):
        self.actions = actions
    def select_action(self, current_state, step):
        action = self.actions[random.randint(0, len(self.actions) - 1)]
        return action

上一个类什么也没做,只定义了一个名为select_action()的函数,它将随机选择一个行动,甚至不查看状态。

现在,如果你想使用这个策略,可以将其应用于现实世界的股价数据。此功能在每个时间间隔处理探索与开发,正如下面的图所示,形成状态 S1、S2 和 S3。该策略建议采取某种行动,我们可以选择执行该行动或随机探索另一个行动。随着我们为执行某个行动而获得奖励,我们可以随着时间推移更新策略函数:

开发股票价格预测模型

图 10:一个滚动窗口以某种大小在股票价格上迭代

太棒了,现在我们有了策略,接下来就是利用这个策略做决策并返回性能指标。现在,假设一个实际场景——假设你在外汇或 ForTrade 平台上进行交易,那么你也需要计算投资组合和当前的利润或损失,即奖励。通常,这些可以按以下方式计算:

portfolio = budget + number of stocks * share value
reward = new_portfolio - current_portfolio

一开始,我们可以初始化依赖于计算投资组合净资产的值,其中状态是一个hist+2维向量。在我们的例子中,它将是 202 维的。然后我们定义调节范围的范围,直到:

用户查询所选价格的长度 - (历史 + 1),由于我们从 0 开始,因此应该减去 1。接下来,我们应该计算投资组合的更新值,并从投资组合中计算奖励值,即利润。

此外,我们已经定义了随机策略,因此可以从当前策略中选择一个动作。然后,我们在每次迭代中根据动作更新投资组合的值,并可以计算出采取该动作后的新投资组合值。接着,我们需要计算在某个状态下采取行动所获得的奖励。然而,我们还需要在经历新动作后更新策略。最后,我们计算最终的投资组合价值:

def run_simulation(policy, initial_budget, initial_num_stocks, prices, hist, debug=False):
    budget = initial_budget
    num_stocks = initial_num_stocks
    share_value = 0
    transitions = list()
    for i in range(len(prices) - hist - 1):
        if i % 100 == 0:
            print('progress {:.2f}%'.format(float(100*i) / (len(prices) - hist - 1)))
        current_state = np.asmatrix(np.hstack((prices[i:i+hist], budget, num_stocks)))
        current_portfolio = budget + num_stocks * share_value
        action = policy.select_action(current_state, i)
        share_value = float(prices[i + hist + 1])
        if action == 'Buy' and budget >= share_value:
            budget -= share_value
            num_stocks += 1
        elif action == 'Sell' and num_stocks > 0:
            budget += share_value
            num_stocks -= 1
        else:
            action = 'Hold'
        new_portfolio = budget + num_stocks * share_value
        reward = new_portfolio - current_portfolio
        next_state = np.asmatrix(np.hstack((prices[i+1:i+hist+1], budget, num_stocks)))
        transitions.append((current_state, action, reward, next_state))
        policy.update_q(current_state, action, reward, next_state)
    portfolio = budget + num_stocks * share_value
    if debug:
        print('${}\t{} shares'.format(budget, num_stocks))
    return portfolio

之前的仿真预测出了一个相对不错的结果;然而,它也经常产生随机结果。因此,为了获得更可靠的成功度量,让我们多次运行仿真并对结果进行平均。这样做可能需要一段时间,比如 100 次,但结果会更可靠:

def run_simulations(policy, budget, num_stocks, prices, hist):
    num_tries = 100
    final_portfolios = list()
    for i in range(num_tries):
        final_portfolio = run_simulation(policy, budget, num_stocks, prices, hist)
        final_portfolios.append(final_portfolio)
    avg, std = np.mean(final_portfolios), np.std(final_portfolios)
    return avg, std

之前的函数通过迭代先前的仿真函数 100 次来计算平均投资组合和标准差。现在,是时候评估之前的代理了。正如之前所说,股票交易代理将采取三种可能的行动,如买入、卖出和持有。我们有一个 202 维的状态向量和仅 $1000 的预算。接下来,评估过程如下:

actions = ['Buy', 'Sell', 'Hold']
    hist = 200
    policy = RandomDecisionPolicy(actions)
    budget = 1000.0
    num_stocks = 0
    avg,std=run_simulations(policy,budget,num_stocks,prices, hist)
    print(avg, std)
>>>
1512.87102405 682.427384814

第一个是均值,第二个是最终投资组合的标准差。所以,我们的股票预测代理预测作为交易者的你/我们可以赚取大约 $513。不错。然而,问题在于,由于我们使用了随机决策策略,结果并不是很可靠。更具体地说,第二次执行肯定会产生不同的结果:

>>> 
1518.12039077 603.15350649 

因此,我们应该开发一个更稳健的决策策略。这里就需要使用基于神经网络的 QLearning 来进行决策策略的改进。接下来,我们将看到一个新的超参数 epsilon,它可以防止在重复应用相同的动作时解决方案陷入困境。它的值越小,随机探索新动作的频率就越高:

开发股票价格预测模型

图 11:输入是具有三个输出的状态空间向量,每个输出对应一个 Q 值

接下来,我将编写一个包含其功能的类:

  • Constructor:该函数帮助从 Q 函数设置超参数。它还帮助设置神经网络中隐藏节点的数量。一旦我们有了这两个参数,它有助于定义输入和输出张量。然后,它定义了神经网络的结构。此外,它定义了计算效用的操作。接着,它使用优化器来更新模型参数,以最小化损失,并设置会话和初始化变量。

  • select_action:该函数以 1-epsilon 的概率利用最佳选择。

  • update_q:该函数通过更新模型参数来更新 Q 函数。

    class QLearningDecisionPolicy(DecisionPolicy):
        def __init__(self, actions, input_dim):
            self.epsilon = 0.9
            self.gamma = 0.001
            self.actions = actions
            output_dim = len(actions)
            h1_dim = 200
            self.x = tf.placeholder(tf.float32, [None, input_dim])
            self.y = tf.placeholder(tf.float32, [output_dim])
            W1 = tf.Variable(tf.random_normal([input_dim, h1_dim]))
            b1 = tf.Variable(tf.constant(0.1, shape=[h1_dim]))
            h1 = tf.nn.relu(tf.matmul(self.x, W1) + b1)
            W2 = tf.Variable(tf.random_normal([h1_dim, output_dim]))
            b2 = tf.Variable(tf.constant(0.1, shape=[output_dim]))
            self.q = tf.nn.relu(tf.matmul(h1, W2) + b2)
            loss = tf.square(self.y - self.q)
            self.train_op = tf.train.GradientDescentOptimizer(0.01).minimize(loss)
            self.sess = tf.Session()
            self.sess.run(tf.initialize_all_variables())
        def select_action(self, current_state, step):
            threshold = min(self.epsilon, step / 1000.)
            if random.random() < threshold:
                # Exploit best option with probability epsilon
                action_q_vals = self.sess.run(self.q, feed_dict={self.x: current_state})
                action_idx = np.argmax(action_q_vals)  
                action = self.actions[action_idx]
            else:
                # Random option with probability 1 - epsilon
                action = self.actions[random.randint(0, len(self.actions) - 1)]
            return action
        def update_q(self, state, action, reward, next_state):
            action_q_vals = self.sess.run(self.q, feed_dict={self.x: state})
            next_action_q_vals = self.sess.run(self.q, feed_dict={self.x: next_state})
            next_action_idx = np.argmax(next_action_q_vals)
            action_q_vals[0, next_action_idx] = reward + self.gamma * next_action_q_vals[0, next_action_idx]
    

请参考以下代码:

        action_q_vals = np.squeeze(np.asarray(action_q_vals))
        self.sess.run(self.train_op, feed_dict={self.x: state, self.y: action_q_vals})

总结

在本课中,我们讨论了一个叫做强化学习的机器学习领域,使用了 TensorFlow。我们从理论和实践的角度都进行了讨论。强化学习是解决当一个问题可以通过状态来框定,而这些状态因代理采取的行动而变化,并且这些行动会带来奖励的自然工具。实现该算法有三个主要步骤:从当前状态推断出最佳行动,执行该行动,并从结果中学习。

我们已经看到了如何通过了解actionstatepolicyutility函数来实现 RL 代理进行预测。我们已经看到了如何使用随机策略以及基于神经网络的 QLearning 策略来开发基于 RL 的代理。QLearning 是一种解决强化学习问题的方法,它通过开发一个算法来近似效用函数(Q-函数)。一旦找到足够好的近似值,就可以开始推断从每个状态应该采取的最佳行动。特别地,我们已经看到了两个逐步的例子,展示了我们如何开发一个多臂赌博机代理和一个股价预测代理,并且取得了很好的准确性。但请注意,实际的股市要复杂得多,本课中使用的技术适用于许多情况。

这大致是我们与 TensorFlow 的小旅程的结束。我希望你有一个顺利的旅程,并且学到了很多关于 TensorFlow 的知识。

祝你未来的项目一切顺利,继续学习和探索!

评估

  1. 在强化学习术语中,我们称 ______ 为策略。

  2. 判断以下陈述是对还是错:强化学习的目标是发现一种好的策略。解决这个问题的最常见方法之一是通过观察在每个状态下行动的长期后果。

  3. 我们需要通过让代理采取行动并从环境中接收 ______ 来训练代理。

  4. 判断以下陈述是对还是错:使用上下文赌博者,我们不能引入并正确利用状态。

  5. 要查找股票价格,我们可以在 Python 中使用 _______ 库。

    1. get_prices

    2. plot_prices

    3. yahoo_finance

    4. finance_yahoo

附录 A. 评估答案

第一课:从数据到决策——开始使用 TensorFlow

问题编号 答案
1 2
2 正确
3 正确
4
  • 一组 tf.Operation 对象:用于表示需要执行的计算单元

  • tf.Tensor 对象:用于表示在操作之间控制数据流的数据单元

|

5 3

第二课:数据整理——用于预测分析的监督学习

问题编号 答案
1 监督学习、无监督学习和强化学习
2 错误
3 model_params = {"learning_rate": LEARNING_RATE}"
4 正确
5 4

第三课:数据聚类——用于预测分析的无监督学习

问题编号 答案
1 监督学习
2 正确
3 无监督学习算法的主要目标是探索输入数据中未标记的未知/隐藏模式
4 正确
5 3

第四课:使用强化学习进行预测分析

问题编号 答案
1 策略
2 正确
3 奖励
4 错误
5 3
posted @ 2025-07-08 21:23  绝不原创的飞龙  阅读(5)  评论(0)    收藏  举报