Theano - 快速指南



Theano - 简介

您是否使用 Python 开发过机器学习模型?那么,您显然知道开发这些模型的复杂性。开发过程通常是一个缓慢的过程,需要花费数小时甚至数天的时间进行计算。

机器学习模型的开发需要大量的数学计算。这些通常需要算术运算,尤其是多维的大型矩阵。如今,我们使用神经网络而不是传统的统计技术来开发机器学习应用程序。神经网络需要在海量数据上进行训练。训练是分批进行的,每批数据的大小合理。因此,学习过程是迭代的。因此,如果计算效率不高,训练网络可能需要数小时甚至数天。因此,高度期望优化可执行代码。而这正是 Theano 提供的。

Theano 是一个 Python 库,它允许您定义机器学习中使用的数学表达式,优化这些表达式并在关键区域果断地使用 GPU 来非常有效地评估这些表达式。在大多数情况下,它可以与典型的完整 C 实现相媲美。

Theano 在 LISA 实验室编写,旨在快速开发高效的机器学习算法。它是在 BSD 许可下发布的。

在本教程中,您将学习如何使用 Theano 库。

Theano - 安装

Theano 可以安装在 Windows、MacOS 和 Linux 上。在所有情况下,安装都非常简单。在安装 Theano 之前,您必须安装其依赖项。以下是依赖项列表:

  • Python
  • NumPy - 必需
  • SciPy - 仅当需要稀疏矩阵和特殊函数时才需要
  • BLAS - 提供执行基本向量和矩阵运算的标准构建块

您可以根据需要选择安装的可选软件包为:

  • nose:运行 Theano 的测试套件
  • Sphinx - 用于构建文档
  • Graphiz 和 pydot - 用于处理图形和图像
  • NVIDIA CUDA 驱动程序 - GPU 代码生成/执行所需
  • libgpuarray - 在 CUDA 和 OpenCL 设备上进行 GPU/CPU 代码生成所需

我们将讨论在 MacOS 上安装 Theano 的步骤。

MacOS 安装

要安装 Theano 及其依赖项,请使用以下命令行中的 **pip**。这些是在本教程中我们将需要的最小依赖项。

$ pip install Theano
$ pip install numpy
$ pip install scipy
$ pip install pydot

您还需要使用以下命令安装 OSx 命令行开发者工具:

$ xcode-select --install

您将看到以下屏幕。点击 **安装** 按钮安装工具。

Install Button

安装成功后,您将在控制台上看到成功消息。

测试安装

安装成功完成后,在 Anaconda Jupyter 中打开一个新的笔记本。在代码单元格中,输入以下 Python 脚本:

示例

import theano
from theano import tensor
a = tensor.dscalar()
b = tensor.dscalar()
c = a + b
f = theano.function([a,b], c)
d = f(1.5, 2.5)
print (d)

输出

执行脚本,您应该会看到以下输出:

4.0

为了方便您快速参考,下面显示了执行的屏幕截图:

Testing The Installation

如果您获得上述输出,则您的 Theano 安装成功。否则,请按照 Theano 下载页面上的调试说明解决问题。

什么是 Theano?

既然您已成功安装 Theano,让我们首先尝试了解什么是 Theano?Theano 是一个 Python 库。它允许您定义、优化和评估数学表达式,尤其是机器学习模型开发中使用的那些表达式。Theano 本身不包含任何预定义的 ML 模型;它只是促进了它的开发。在处理多维数组时,它特别有用。它与 NumPy 无缝集成,NumPy 是 Python 中用于科学计算的基本且广泛使用的软件包。

Theano 促进了 ML 开发中使用的数学表达式的定义。此类表达式通常涉及矩阵算术、微分、梯度计算等。

Theano 首先为您的模型构建整个计算图。然后,它通过对图应用多种优化技术将其编译成高效的代码。编译后的代码通过 Theano 中称为 **function** 的特殊操作注入到 Theano 运行时。我们重复执行此 **function** 来训练神经网络。与使用纯 Python 编码甚至完整的 C 实现相比,训练时间大大减少。

我们现在将了解 Theano 开发的过程。让我们从如何在 Theano 中定义数学表达式开始。

Theano - 一个简单的 Theano 表达式

让我们从在 Theano 中定义和评估一个简单的表达式开始我们的 Theano 之旅。考虑以下将两个标量相加的简单表达式:

c = a + b

其中 **a**、**b** 是变量,**c** 是表达式的输出。在 Theano 中,即使定义和评估这个简单的表达式也很棘手。

让我们了解评估上述表达式的步骤。

导入 Theano

首先,我们需要在程序中导入 Theano 库,我们使用以下语句:

from theano import *

在上面的语句中,我们使用 * 包含 Theano 库中的所有软件包,而不是导入各个软件包。

声明变量

接下来,我们将使用以下语句声明一个名为 **a** 的变量:

a = tensor.dscalar()

**dscalar** 方法声明一个十进制标量变量。执行上述语句将在您的程序代码中创建一个名为 **a** 的变量。同样,我们将使用以下语句创建变量 **b**:

b = tensor.dscalar()

定义表达式

接下来,我们将定义对这两个变量 **a** 和 **b** 进行操作的表达式。

c = a + b

在 Theano 中,执行上述语句不会执行这两个变量 **a** 和 **b** 的标量加法。

定义 Theano 函数

要评估上述表达式,我们需要在 Theano 中定义一个函数,如下所示:

f = theano.function([a,b], c)

函数 **function** 接受两个参数,第一个参数是函数的输入,第二个参数是函数的输出。上述声明指出第一个参数是包含两个元素 **a** 和 **b** 的数组类型。输出是一个称为 **c** 的标量单元。此函数将在我们以后的代码中使用变量名 **f** 进行引用。

调用 Theano 函数

对函数 f 的调用使用以下语句进行:

d = f(3.5, 5.5)

函数的输入是一个包含两个标量的数组:**3.5** 和 **5.5**。执行的输出分配给标量变量 **d**。要打印 **d** 的内容,我们将使用 **print** 语句:

print (d)

执行将导致 **d** 的值打印到控制台,在本例中为 9.0。

完整程序清单

为了方便您快速参考,这里给出了完整的程序清单:

from theano import *
a = tensor.dscalar()
b = tensor.dscalar()
c = a + b
f = theano.function([a,b], c)
d = f(3.5, 5.5)
print (d)

执行上述代码,您将看到输出为 9.0。此处显示了屏幕截图:

Full Program

现在,让我们讨论一个稍微复杂一点的示例,该示例计算两个矩阵的乘法。

Theano - 矩阵乘法的表达式

我们将计算两个矩阵的点积。第一个矩阵的维度为 2 x 3,第二个矩阵的维度为 3 x 2。我们用作输入的矩阵及其乘积在此处表示:

$$\begin{bmatrix}0 & -1 & 2\\4 & 11 & 2\end{bmatrix} \:\begin{bmatrix}3& -1 \\1 & 2 \\35 & 20 \end{bmatrix}=\begin{bmatrix}11 & 0 \\35 & 20 \end{bmatrix}$$

声明变量

要为上述内容编写 Theano 表达式,我们首先声明两个变量来表示我们的矩阵,如下所示:

a = tensor.dmatrix()
b = tensor.dmatrix()

dmatrix 是双精度矩阵的类型。请注意,我们在任何地方都没有指定矩阵大小。因此,这些变量可以表示任何维度的矩阵。

定义表达式

要计算点积,我们使用名为 **dot** 的内置函数,如下所示:

c = tensor.dot(a,b)

乘法的输出分配给一个名为 **c** 的矩阵变量。

定义 Theano 函数

接下来,我们像前面的示例一样定义一个函数来评估表达式。

f = theano.function([a,b], c)

请注意,函数的输入是两个变量 a 和 b,它们是矩阵类型。函数输出分配给变量 **c**,该变量将自动为矩阵类型。

调用 Theano 函数

我们现在使用以下语句调用函数:

d = f([[0, -1, 2], [4, 11, 2]], [[3, -1],[1,2], [6,1]])

上述语句中的两个变量是 NumPy 数组。您可以像此处显示的那样显式定义 NumPy 数组:

f(numpy.array([[0, -1, 2], [4, 11, 2]]),
numpy.array([[3, -1],[1,2], [6,1]]))

计算 **d** 后,我们打印其值:

print (d)

您将在输出上看到以下输出:

[[11. 0.]
[25. 20.]]

完整程序清单

The complete program listing is given here:
from theano import *
a = tensor.dmatrix()
b = tensor.dmatrix()
c = tensor.dot(a,b)
f = theano.function([a,b], c)
d = f([[0, -1, 2],[4, 11, 2]], [[3, -1],[1,2],[6,1]])
print (d)

程序执行的屏幕截图显示在此处:

Program Execution

Theano - 计算图

从以上两个示例中,您可能已经注意到,在 Theano 中,我们创建了一个表达式,该表达式最终使用 Theano **function** 进行评估。Theano 使用高级优化技术来优化表达式的执行。为了可视化计算图,Theano 在其库中提供了一个 **printing** 软件包。

标量加法的符号图

要查看标量加法程序的计算图,请使用以下方式使用 printing 库:

theano.printing.pydotprint(f, outfile="scalar_addition.png", var_with_name_simple=True)

当您执行此语句时,将在您的机器上创建一个名为 **scalar_addition.png** 的文件。此处显示了保存的计算图,以供您快速参考:

Scalar Addition

生成上述图像的完整程序清单如下所示:

from theano import *
a = tensor.dscalar()
b = tensor.dscalar()
c = a + b
f = theano.function([a,b], c)
theano.printing.pydotprint(f, outfile="scalar_addition.png", var_with_name_simple=True)

矩阵乘法的符号图

现在,尝试为我们的矩阵乘法器创建计算图。生成此图的完整清单如下所示:

from theano import *
a = tensor.dmatrix()
b = tensor.dmatrix()
c = tensor.dot(a,b)
f = theano.function([a,b], c)
theano.printing.pydotprint(f, outfile="matrix_dot_product.png", var_with_name_simple=True)

生成的图显示在此处:

Matrix Multiplier

复杂图形

在较大的表达式中,计算图可能非常复杂。此处显示了从 Theano 文档中获取的一个此类图形:

Complex Graphs

要了解 Theano 的工作原理,首先了解这些计算图的重要性非常重要。有了这种理解,我们将知道 Theano 的重要性。

为什么要使用 Theano?

通过查看计算图的复杂性,您现在将能够理解开发 Theano 的目的。一个典型的编译器会在程序中提供本地优化,因为它从未将整个计算视为一个单元。

Theano 实施了非常高级的优化技术来优化完整的计算图。它将代数的各个方面与优化编译器的各个方面相结合。图的一部分可以编译成 C 语言代码。对于重复计算,评估速度至关重要,Theano 通过生成非常高效的代码来满足此目的。

Theano - 数据类型

现在,您已经了解了 Theano 的基础知识,让我们开始了解可用于创建表达式的不同数据类型。下表为您提供了 Theano 中定义的数据类型的部分列表。

数据类型 Theano 类型
字节

bscalar、bvector、bmatrix、brow、bcol、btensor3、btensor4、btensor5、btensor6、btensor7

16 位整数

wscalar、wvector、wmatrix、wrow、wcol、wtensor3、wtensor4、wtensor5、wtensor6、wtensor7

32 位整数

iscalar、ivector、imatrix、irow、icol、itensor3、itensor4、itensor5、itensor6、itensor7

64 位整数

lscalar、lvector、lmatrix、lrow、lcol、ltensor3、ltensor4、ltensor5、ltensor6、ltensor7

浮点数

fscalar、fvector、fmatrix、frow、fcol、ftensor3、ftensor4、ftensor5、ftensor6、ftensor7

双精度浮点数

标量,向量,矩阵,行向量,列向量,三阶张量,四阶张量,五阶张量,六阶张量,七阶张量

复数

复数标量,复数向量,复数矩阵,复数行向量,复数列向量,复数三阶张量,复数四阶张量,复数五阶张量,复数六阶张量,复数七阶张量

以上列表并不完整,请参考张量创建文档以获取完整列表。

我现在将给出一些关于如何在 Theano 中创建各种数据类型的变量的例子。

标量

要构造一个标量变量,可以使用以下语法:

语法

x = theano.tensor.scalar ('x')
x = 5.0
print (x)

输出

5.0

一维数组

要创建一个一维数组,使用以下声明:

示例

f = theano.tensor.vector
f = (2.0, 5.0, 3.0)
print (f)f = theano.tensor.vector
f = (2.0, 5.0, 3.0)
print (f)
print (f[0])
print (f[2])

输出

(2.0, 5.0, 3.0)
2.0
3.0

如果执行 **f[3]**,则会生成一个索引超出范围的错误,如下所示:

print f([3])

输出

IndexError                          Traceback (most recent call last)
<ipython-input-13-2a9c2a643c3a> in <module>
   4 print (f[0])
   5 print (f[2])
----> 6 print (f[3])
IndexError: tuple index out of range

二维数组

要声明一个二维数组,可以使用以下代码片段:

示例

m = theano.tensor.matrix
m = ([2,3], [4,5], [2,4])
print (m[0])
print (m[1][0])

输出

[2, 3]
4

五维数组

要声明一个五维数组,使用以下语法:

示例

m5 = theano.tensor.tensor5
m5 = ([0,1,2,3,4], [5,6,7,8,9], [10,11,12,13,14])
print (m5[1])
print (m5[2][3])

输出

[5, 6, 7, 8, 9]
13

可以使用数据类型 **tensor3** 代替 **tensor5** 声明三维数组,使用数据类型 **tensor4** 声明四维数组,依此类推,直到 **tensor7**。

多元构造器

有时,您可能希望在一个声明中创建相同类型的多个变量。可以使用以下语法来实现:

语法

from theano.tensor import * x, y, z = dmatrices('x', 'y', 'z') 
x = ([1,2],[3,4],[5,6]) 
y = ([7,8],[9,10],[11,12]) 
z = ([13,14],[15,16],[17,18]) 
print (x[2]) 
print (y[1]) 
print (z[0])

输出

[5, 6] 
[9, 10] 
[13, 14]

Theano - 变量

在上一章讨论数据类型时,我们创建并使用了 Theano 变量。重申一下,可以使用以下语法在 Theano 中创建变量:

x = theano.tensor.fvector('x')

在此语句中,我们创建了一个名为 **x** 的变量,它是一个包含 32 位浮点数的向量。我们也将其命名为 **x**。名称通常用于调试。

要声明一个 32 位整数的向量,可以使用以下语法:

i32 = theano.tensor.ivector

这里,我们没有为变量指定名称。

要声明一个包含 64 位浮点数的三维向量,可以使用以下声明:

f64 = theano.tensor.dtensor3

下面表格列出了各种构造器及其数据类型:

构造器 数据类型 维度
fvector float32 1
ivector int32 1
fscalar float32 0
fmatrix float32 2
ftensor3 float32 3
dtensor3 float64 3

可以使用通用向量构造器并显式指定数据类型,如下所示:

x = theano.tensor.vector ('x', dtype=int32)

在下一章中,我们将学习如何创建共享变量。

Theano - 共享变量

很多时候,您需要创建在不同函数之间以及同一函数的多次调用之间共享的变量。例如,在训练神经网络时,您会创建一个权重向量,用于为所考虑的每个特征分配权重。在网络训练期间的每次迭代中都会修改此向量。因此,它必须在对同一函数的多次调用中全局可用。因此,我们为此目的创建了一个共享变量。通常,Theano 会将此类共享变量移动到 GPU(如果可用)。这可以加快计算速度。

语法

创建共享变量时,可以使用以下语法:

import numpy
W = theano.shared(numpy.asarray([0.1, 0.25, 0.15, 0.3]), 'W')

示例

这里创建了一个包含四个浮点数的 NumPy 数组。要设置/获取 **W** 的值,可以使用以下代码片段:

import numpy
W = theano.shared(numpy.asarray([0.1, 0.25, 0.15, 0.3]), 'W')
print ("Original: ", W.get_value())
print ("Setting new values (0.5, 0.2, 0.4, 0.2)")
W.set_value([0.5, 0.2, 0.4, 0.2])
print ("After modifications:", W.get_value())

输出

Original: [0.1 0.25 0.15 0.3 ]
Setting new values (0.5, 0.2, 0.4, 0.2)
After modifications: [0.5 0.2 0.4 0.2]

Theano - 函数

Theano **function** 充当与符号图交互的钩子。符号图被编译成高效的执行代码。它通过重构数学方程来提高速度。它将表达式的一些部分编译成 C 语言代码。它将一些张量移动到 GPU,等等。

现在将高效的编译代码作为输入提供给 Theano **function**。当执行 Theano **function** 时,它会将计算结果分配给我们指定的变量。优化类型可以指定为 FAST_COMPILE 或 FAST_RUN。这在环境变量 THEANO_FLAGS 中指定。

Theano **function** 使用以下语法声明:

f = theano.function ([x], y)

第一个参数 **[x]** 是输入变量列表,第二个参数 **y** 是输出变量列表。

现在已经了解了 Theano 的基础知识,让我们用一个简单的例子开始 Theano 编码。

Theano - 简单训练示例

Theano 在训练神经网络方面非常有用,在神经网络中,我们必须重复计算成本和梯度以达到最优。在大型数据集上,这会变得计算密集。由于 Theano 对我们之前看到的计算图进行了内部优化,因此可以高效地完成此操作。

问题陈述

现在我们将学习如何使用 Theano 库来训练网络。我们将采用一个简单的案例,从一个包含四个特征的数据集开始。在对每个特征应用一定的权重(重要性)后,我们计算这些特征的总和。

训练的目标是修改分配给每个特征的权重,以便总和达到目标值 100。

sum = f1 * w1 + f2 * w2 + f3 * w3 + f4 * w4

其中 **f1**、**f2** 等是特征值,**w1**、**w2** 等是权重。

为了更好地理解问题陈述,让我量化一下这个例子。我们将假设每个特征的初始值为 1.0,并将 w1 等于 **0.1**、**w2** 等于 **0.25**、**w3** 等于 **0.15** 以及 **w4** 等于 **0.3**。分配权重值没有明确的逻辑,这只是我们的直觉。因此,初始总和如下:

sum = 1.0 * 0.1 + 1.0 * 0.25 + 1.0 * 0.15 + 1.0 * 0.3

总和为 **0.8**。现在,我们将不断修改权重分配,使此总和接近 100。当前 **0.8** 的结果值距离我们期望的 100 的目标值相差甚远。在机器学习术语中,我们将 **成本** 定义为目标值减去当前输出值之差,通常将其平方以放大误差。我们通过计算梯度并更新权重向量来减少每次迭代中的此成本。

让我们看看如何在 Theano 中实现整个逻辑。

声明变量

我们首先声明我们的输入向量 x,如下所示:

x = tensor.fvector('x')

其中 **x** 是一个包含浮点值的单维数组。

我们定义一个标量 **target** 变量,如下所示:

target = tensor.fscalar('target')

接下来,我们创建一个权重张量 **W**,其初始值如上所述:

W = theano.shared(numpy.asarray([0.1, 0.25, 0.15, 0.3]), 'W')

定义 Theano 表达式

我们现在使用以下表达式计算输出:

y = (x * W).sum()

请注意,在上述语句中,**x** 和 **W** 是向量,而不是简单的标量变量。我们现在使用以下表达式计算误差(成本):

cost = tensor.sqr(target - y)

成本是目标值和当前输出值之差的平方。

要计算梯度(它告诉我们距离目标有多远),我们使用内置的 **grad** 方法,如下所示:

gradients = tensor.grad(cost, [W])

我们现在通过采用 **0.1** 的学习率来更新 **weights** 向量,如下所示:

W_updated = W - (0.1 * gradients[0])

接下来,我们需要使用上述值更新我们的权重向量。我们在以下语句中执行此操作:

updates = [(W, W_updated)]

定义/调用 Theano 函数

最后,我们在 Theano 中定义一个 **function** 来计算总和。

f = function([x, target], y, updates=updates)

要调用上述函数一定次数,我们创建一个 **for** 循环,如下所示:

for i in range(10):
output = f([1.0, 1.0, 1.0, 1.0], 100.0)

如前所述,函数的输入是一个包含四个特征的初始值的向量 - 我们将每个特征的值分配为 **1.0**,没有任何具体原因。您可以分配您选择的不同值并检查函数最终是否收敛。我们将在每次迭代中打印权重向量和相应输出的值。它在下面的代码中显示:

print ("iteration: ", i)
print ("Modified Weights: ", W.get_value())
print ("Output: ", output)

完整程序清单

为了方便您快速参考,这里提供了完整的程序清单:

from theano import *
import numpy

x = tensor.fvector('x')
target = tensor.fscalar('target')

W = theano.shared(numpy.asarray([0.1, 0.25, 0.15, 0.3]), 'W')
print ("Weights: ", W.get_value())

y = (x * W).sum()
cost = tensor.sqr(target - y)
gradients = tensor.grad(cost, [W])
W_updated = W - (0.1 * gradients[0])
updates = [(W, W_updated)]

f = function([x, target], y, updates=updates)
for i in range(10):
   output = f([1.0, 1.0, 1.0, 1.0], 100.0)
   print ("iteration: ", i)
   print ("Modified Weights: ", W.get_value())
   print ("Output: ", output)

运行程序时,您将看到以下输出:

Weights: [0.1 0.25 0.15 0.3 ]
iteration: 0
Modified Weights: [19.94 20.09 19.99 20.14]
Output: 0.8
iteration: 1
Modified Weights: [23.908 24.058 23.958 24.108]
Output: 80.16000000000001
iteration: 2
Modified Weights: [24.7016 24.8516 24.7516 24.9016]
Output: 96.03200000000001
iteration: 3
Modified Weights: [24.86032 25.01032 24.91032 25.06032]
Output: 99.2064
iteration: 4
Modified Weights: [24.892064 25.042064 24.942064 25.092064]
Output: 99.84128
iteration: 5
Modified Weights: [24.8984128 25.0484128 24.9484128 25.0984128]
Output: 99.968256
iteration: 6
Modified Weights: [24.89968256 25.04968256 24.94968256 25.09968256]
Output: 99.9936512
iteration: 7
Modified Weights: [24.89993651 25.04993651 24.94993651 25.09993651]
Output: 99.99873024
iteration: 8
Modified Weights: [24.8999873 25.0499873 24.9499873 25.0999873]
Output: 99.99974604799999
iteration: 9
Modified Weights: [24.89999746 25.04999746 24.94999746 25.09999746]
Output: 99.99994920960002

观察到经过四次迭代后,输出为 **99.96**,经过五次迭代后,输出为 **99.99**,这接近我们期望的 **100.0** 的目标。

根据所需的精度,您可以安全地得出结论,网络在 4 到 5 次迭代中得到训练。训练完成后,查找权重向量,经过 5 次迭代后,权重向量将采用以下值:

iteration: 5
Modified Weights: [24.8984128 25.0484128 24.9484128 25.0984128]

您现在可以在网络中使用这些值来部署模型。

Theano - 结论

机器学习模型构建涉及大量重复计算,这些计算涉及张量。这些需要大量的计算资源。由于常规编译器会在本地级别提供优化,因此通常不会生成快速的执行代码。

Theano 首先为整个计算构建一个计算图。由于整个计算图在编译期间作为一个整体可用,因此可以在预编译期间应用多种优化技术,而这正是 Theano 所做的。它重构计算图,部分将其转换为 C,将共享变量移动到 GPU,等等,以生成非常快速的执行代码。然后,编译后的代码由 Theano **function** 执行,该函数仅充当将编译后的代码注入运行时的钩子。Theano 已证明其能力,并在学术界和工业界得到广泛认可。

广告