Theano - 快速指南


Theano - 简介

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

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

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

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

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

Theano - 安装

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

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

您可以根据需要选择安装的可选软件包是 -

  • 鼻子:运行 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

您将看到以下屏幕。单击“安装”按钮安装该工具。

安装按钮

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

测试安装

安装成功完成后,在 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

下面显示了执行的屏幕截图,供您快速参考 -

测试安装

如果得到以上输出,则表明 Theano 安装成功。如果没有,请按照 Theano 下载页面上的调试说明来修复问题。

什么是 Theano?

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

Theano 有助于定义 ML 开发中使用的数学表达式。此类表达式一般涉及矩阵运算、微分、梯度计算等。

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

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

Theano - 一个简单的 Theano 表达式

让我们通过定义和评估 Theano 中的一个简单表达式来开始我们的 Theano 之旅。考虑以下添加两个标量的简单表达式 -

c = a + b

其中ab是变量,c是表达式输出。在 Theano 中,即使定义和评估这个微不足道的表达式也是很棘手的。

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

导入 Theano

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

from theano import *

我们没有导入单个包,而是在上面的语句中使用 * 来包含 Theano 库中的所有包。

声明变量

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

a = tensor.dscalar()

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

b = tensor.dscalar()

定义表达式

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

c = a + b

在 Theano 中,执行上述语句并不执行两个变量ab的标量加法。

定义 Theano 函数

为了计算上面的表达式,我们需要在 Theano 中定义一个函数,如下所示 -

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

函数function有两个参数,第一个参数是函数的输入,第二个参数是函数的输出。上面的声明指出第一个参数是由两个元素ab组成的数组类型。输出是一个称为c 的标量单位。在我们的后续代码中将使用变量名f引用该函数。

调用 Theano 函数

使用以下语句调用函数 f -

d = f(3.5, 5.5)

函数的输入是一个由两个标量组成的数组:3.55.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。屏幕截图如下所示 -

完整计划

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

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)

程序执行的屏幕截图如下所示 -

程序执行

Theano - 计算图

从上面的两个示例中,您可能已经注意到,在 Theano 中,我们创建了一个表达式,最终使用 Theano函数对其进行求值。Theano 使用先进的优化技术来优化表达式的执行。为了可视化计算图,Theano 在其库中提供了一个打印包。

标量加法的符号图

要查看标量加法程序的计算图,请使用打印库,如下所示 -

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

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

标量加法

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

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)

生成的图表如下所示 -

矩阵乘法器

复杂图

在较大的表达式中,计算图可能非常复杂。这里显示了一张取自 Theano 文档的图表 -

复杂图

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

为什么是西阿诺?

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

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

Theano - 数据类型

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

数据类型 西亚诺型
字节

b标量、b向量、b矩阵、眉毛、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 位整数

l标量、l向量、l矩阵、lrow、lcol、ltensor3、ltensor4、ltensor5、ltensor6、ltensor7

漂浮

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

双倍的

d标量、d向量、d矩阵、drow、dcol、dtensor3、dtensor4、dtensor5、dtensor6、dtensor7

复杂的

cscalar、cvector、cmatrix、crow、ccol、ctensor3、ctensor4、ctensor5、ctensor6、ctensor7

上面的列表并不详尽,读者可以参考张量创建文档来获取完整的列表。

现在我将给你几个例子来说明如何在 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

5维数组

要声明 5 维数组,请使用以下语法 -

例子

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来声明3维数组,使用数据类型tensor4声明4维数组,依此类推,直到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')

在此语句中,我们创建了一个包含 32 位浮点数的向量类型变量x 。我们还将其命名为x。这些名称通常对于调试很有用。

要声明 32 位整数向量,您可以使用以下语法 -

i32 = theano.tensor.ivector

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

要声明由 64 位浮点组成的三维向量,您可以使用以下声明 -

f64 = theano.tensor.dtensor3

下表列出了各种类型的构造函数及其数据类型 -

构造函数 数据类型 方面
向量 浮动32 1
向量 整型32 1
f标量 浮动32 0
矩阵 浮动32 2
张量3 浮动32 3
dtensor3 浮动64 3

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

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

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

Theano - 共享变量

很多时候,您需要创建在不同函数之间以及在同一函数的多次调用之间共享的变量。举个例子,在训练神经网络时,您创建权重向量,为所考虑的每个特征分配权重。在网络训练期间的每次迭代中都会修改该向量。因此,它必须可以通过对同一函数的多次调用进行全局访问。因此我们为此目的创建一个共享变量。通常,Theano 会将此类共享变量移至 GPU(只要有可用的 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函数就像一个与符号图交互的钩子。符号图被编译成高效的执行代码。它通过重构数学方程以使其更快来实现这一目标。它将表达式的某些部分编译为 C 语言代码。它将一些张量移动到 GPU,等等。

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

Theano函数使用以下语法声明 -

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.1w2等于0.25w3等于0.15w4等于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 = tensor.fscalar('target')

接下来,我们使用上面讨论的初始值创建一个权重张量W -

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

定义 Theano 表达式

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

y = (x * W).sum()

请注意,在上面的语句中,xW是向量而不是简单的标量变量。我们现在用以下表达式计算误差(成本) -

cost = tensor.sqr(target - y)

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

为了计算告诉我们距目标有多远的梯度,我们使用内置的grad方法,如下所示 -

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

现在,我们采用学习率为0.1来更新权向量,如下所示 -

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

接下来,我们需要使用上述值更新权重向量。我们在以下声明中做到这一点 -

updates = [(W, W_updated)]

定义/调用 Theano 函数

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

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函数执行,该函数充当将编译后的代码注入运行时的钩子。Theano 已经证明了自己的实力,并在学术界和工业界得到广泛接受。