返回 播客 学习 audios

TNO深度学习框架介绍:从基础概念到实际应用

本讲座详细介绍TNO(Theano)深度学习框架的核心概念、计算图构建、自动微分、GPU支持及实际应用示例,包括逻辑回归、卷积网络和LSTM。适合初学者了解TNO的基本用法和高级特性。

TNO深度学习计算图自动微分
成长分 / 100 71 综合收获、行动、留存与影响

为什么值得读本讲座详细介绍TNO(Theano)深度学习框架的核心概念、计算图构建、自动微分、GPU支持及实际应用示例,包括逻辑回归、卷积网络和LSTM

适合初学者了解TNO的基本用法和高级特性

关键洞察
  1. 这条内容的价值在于把外部信息转化为可执行的判断和行动。
转成行动

Lex Fridman · 中文播客

查看原视频 ↗

节目文字稿

中文播客全文

Podcast Script (zh)

[0.120-9.721] SPEAKER_01: 今天简单介绍TNO和它的用法。 以及库背后的基本原理。如果你昨天看了 TensorFlow演示,一些概念会很熟悉。

[9.801-18.067] SPEAKER_01: 如果你注意到Hugo La Rochelle的演讲, 也会看到类似的概念。 主要分四部分。首先,这些幻灯片

[18.147-28.387] SPEAKER_01: 介绍TNO概念。还有一个配套的 IPython笔记本在GitHub上。访问或克隆 那个仓库,里面有笔记本,

[28.467-34.330] SPEAKER_01: 包含所有代码片段,可以一起运行。 然后有个更实际的例子,把

[34.410-43.593] SPEAKER_01: 逻辑回归应用到MNIST数据集。如果有时间, 再快速介绍两个例子:ConvNet(LeNet架构), 以及一个LSTM代码。

[43.673-49.419] SPEAKER_01: 用于字符级文本生成。TNO是个数学 符号表达式编译器。意思是,

[49.499-55.524] SPEAKER_01: 可以用NumPy语法定义数学表达式, 使用方便。它支持各种基本

[55.604-63.568] SPEAKER_01: 数学运算,如最小、最大、加减等, 还有更大的模块如神经网络层或整个网络。

[63.648-75.025] SPEAKER_01: 或类似的事,能操作这些表达式。 图的替换、克隆这些操作,并且 让它更复杂,但方法很简单。而且 能遍历图,做像自动

[75.105-82.326] SPEAKER_01: 微分,其实是符号微分,或前向R算子 微分,优化以提升数值稳定性。

[82.406-88.895] SPEAKER_01: 再用优化后的图和TNO运行时计算 值,即输入对应的输出值。

[88.975-92.283] SPEAKER_01: 我们还有一些调试工具

[92.363-99.630] SPEAKER_01: 包括TNO和用户代码 并尽量检查和理解 用TNO时实际发生了什么。

[99.710-102.856] SPEAKER_01: TNO已有八年多历史。

[102.936-107.347] SPEAKER_01: 它起步很小,贡献者很少。 来自Mila前身

[107.427-118.061] SPEAKER_01: 当时叫Lizat,后来壮大了。 贡献者来自世界各地 用户来自世界各地 用来推动了很多研究论文 还有初创公司的工业应用原型 大公司也在用。

[118.141-129.820] SPEAKER_01: TNO一直是基础。 它是其他项目的基础。 比如Blocks、Keras、Lasagne。 都是机器学习库。 它们用TNO做后端,带界面。

[129.900-133.626] SPEAKER_01: 它们更高级,有层的概念。

[133.706-144.758] SPEAKER_01: 还有训练算法等。 TNO更像后端。 还有SKLearn TNO,很不错。 它有转换器,可加载CAFE模型。 从CAFE Zoo导入后在TNO用。 还可以做很多事。

[144.838-150.608] SPEAKER_01: PyMC3用TNO不是为机器学习, 而是做概率编程。

[150.688-165.052] SPEAKER_01: 另外还有两个库, Mila开发的Platoon, 还有圭尔夫开发的TNO MPI。 它们是TNO的上层。 用于多机器、多GPU训练。 还有一定模型并行性 以及数据并行

[165.132-169.973] SPEAKER_01: 那怎么用TNO 首先,处理符号表达式

[170.053-177.634] SPEAKER_01: 符号变量构成概念 计算图 那来看看怎么做 要定义符号表达式 先定义表达式 再编译一个函数

[177.714-188.348] SPEAKER_01: 然后用数值执行函数 定义表达式先定义输入 输入是带类型的符号变量 所以你必须事先定义 这个变量是向量还是矩阵

[188.428-194.058] SPEAKER_01: 它的数据类型是什么 浮点数或整数等

[194.138-202.857] SPEAKER_01: 所以维度这类信息 得事先知道 但形状不固定 内存布局不固定。 所以形状可以变。 比如批次之间。 或者函数调用不同。

[202.937-208.428] SPEAKER_01: X和Y只是符号变量。 我们稍后会赋值。 目前它们还是空的。

[208.508-216.426] SPEAKER_01: 还有另一种输入。 共享变量是符号的。 但它们也有值。 这个值在调用间持久。 TNO函数间共享。

[216.506-227.082] SPEAKER_01: 比如常用于, 存模型参数。 你要学习的。 这些值也能更新。 这里我们从值, 从值创建共享变量。 它有两个维度。

[227.162-235.625] SPEAKER_01: 因为初始值是二维。 这个只有一个。 基本就是权重矩阵和偏置。 通过赋值给变量命名。 设置名称属性。

[235.705-258.405] SPEAKER_01: 共享变量没有固定值。 所以,通过赋值名称属性来命名变量。 所以,通过赋值名称属性来命名变量。 大多数模型中,它们通常是固定的。 但不是必须。 然后从这些输入中, 我们可以定义表达式来构建新变量。 我们可以定义表达式来构建新变量。 中间变量, 这是计算的结果。

[258.485-262.954] SPEAKER_01: 比如这里我们可以定义, X和W的点积,

[263.034-275.085] SPEAKER_01: 加上偏置,再用sigmoid函数, 然后假设, 这是输出变量, 然后从输出变量和Y, 比方说,平方误差成本。 Y也一样,比方说,平方误差成本。

[275.165-289.006] SPEAKER_01: 这些新变量连到之前的变量。 通过我们定义的操作。 我们可以这样展示图结构。 比方说,用 pilot print,一个辅助函数。 变量就是那些方形线条。 变量就是那些方形线条。

[289.086-297.085] SPEAKER_01: 还有别的节点,叫应用节点。 代表连接的数学运算。 输入变量和共享变量 没有祖先节点。 也没有错误连接。

[297.165-301.274] SPEAKER_01: 但能看到中间结果和其他结果。

[301.354-317.610] SPEAKER_01: 可视化时,我们不一定关心。 可视化时,我们不一定关心。 所有中间变量。 除非有名字之类的。 所以这是同一张图的简化版。 我们隐藏没名字的中间变量 但你仍能看到所有操作

[317.690-321.637] SPEAKER_01: 其实边上就能看到类型

[321.717-326.326] SPEAKER_01: 所以一旦你定义好图 比如模型的前向计算

[326.406-335.426] SPEAKER_01: 我们希望 我们希望能用反向传播求梯度 这只是链式法则的基本概念 我们有一个标量成本 中间变量是向量

[335.506-339.058] SPEAKER_01: 从成本开始用链式法则

[339.138-343.004] SPEAKER_01: 所以整个导数,比如那个函数

[343.084-354.577] SPEAKER_01: g 实际上是一个 m 乘 n 的雅可比矩阵 如果中间变量是 n 和 m 维的向量 通常你不需要它 显式算出来通常不是好主意

[354.657-364.978] SPEAKER_01: 除非你因为别的原因需要它 你只需要一个表达式,给定任意向量 表示成本对输出的梯度 就能算出成本梯度 关于输入。

[365.058-375.332] SPEAKER_01: 所以,向量间的点积。 以及整个雅可比矩阵。 所以这有时叫L算子。 所以,Tiano中几乎所有操作都实现一个函数。

[375.412-386.966] SPEAKER_01: 这个函数返回结果。 而且它返回的不是数字, 它不是数值表达式,而是返回结果的函数。 所以,它返回一个函数, 但它返回一个符号表达式 代表这个计算。

[387.046-392.107] SPEAKER_01: 同样,通常不用显式表示 或定义整个雅可比矩阵。

[392.187-402.949] SPEAKER_01: 所以,你可以调用Tiano的dot_grad, 它会通过图反向传播, 从成本到你提供的输入。 过程中,它会调用那个梯度方法 每个正在反向传播的操作。

[403.029-411.515] SPEAKER_01: 我是说,从成本开始为1, 并反向传播通过整个图。 变量相同就累加 那些重复使用的

[411.595-417.655] SPEAKER_01: 再看这儿,dcdw 和 dcdb 它们都是符号表达式 就像手动

[417.735-429.333] SPEAKER_01: 用 Tiano 操作定义梯度表达式 这是个符号表达式,像手动用 Tiano 定义 符号表达式,类似手动用 Tiano 定义梯度 比如点积、sigmoid 等 之前见过的

[429.413-438.817] SPEAKER_01: 那时候有非数值值 它们是计算图的一部分 计算图扩展了 加入了这些变量 可以继续扩展图 比如从这些变量出发

[438.897-444.945] SPEAKER_01: 计算对应的更新表达式 用于梯度下降等 就像我们这里做的

[445.025-455.102] SPEAKER_01: 比如,这是扩展后的图 对梯度来说,看起来差不多。 你看,有许多小操作 被加进去了,你得到的输出, 其实是相对偏置的梯度,

[455.182-460.383] SPEAKER_01: 它既是输出又是中间结果, 能帮我们算出梯度 相对于权重的梯度。

[460.463-465.014] SPEAKER_01: 这就是计算图。 这是更新表达式的计算图。

[465.094-473.093] SPEAKER_01: 所以,有作为中间变量的梯度, 就是上一张幻灯片里的, 然后只是缩放版本, 常数0.1藏在某处。

[473.173-482.077] SPEAKER_01: 一旦定义了整个计算图, 我们真正关心的整个表达式, 从输入和初始权重到... 训练算法的权重更新。

[482.157-490.620] SPEAKER_01: 我们要编译一个函数,能 给定输入算出这些数值, 并执行权重更新。 所以计算值用 tno.function

[490.700-499.361] SPEAKER_01: 你给它输入变量 你需要的输入和输出变量 你想得到的变量 你不用提供值 所有你声明的输入 尤其你不想一路 走到图的末端

[499.441-510.354] SPEAKER_01: 你可以有函数只计算 图子集的子集表达式 比如这里有个预测函数 它只从 x 到 out 不需要 y 的值 不需要,所以梯度等 就不会被计算

[510.434-519.559] SPEAKER_01: 它只取图的一小部分 然后从中创建函数 你可以先编译它 取值,然后调用 所以必须给所有输入变量值 你定义的。

[519.639-529.414] SPEAKER_01: 共享变量不用赋值。 之前声明的 w 和 b。 它们是所有函数的隐含输入。 它们的值会自动 在需要时获取。

[529.494-537.690] SPEAKER_01: 可以声明其他函数,比如监控函数。 同时计算输出和成本。 所以你有两个输出。 还需要第二个输入 y。

[537.770-558.345] SPEAKER_01: 可以计算不从起点开始的函数。 从头开始。 比如,我要一个误差函数。 只计算预测值和实际目标的不匹配。 那么可以计算误差函数。 我可以定义一个只算预测值与实际目标不匹配的误差函数。 而且不用从输入开始。 只要从预测开始算成本。

[558.425-563.893] SPEAKER_01: 那么下一步是 更新共享变量来训练,这很必要。

[563.973-582.052] SPEAKER_01: 同样,更新能传给TNO函数, 就是一个更新列表。 更新就是一对共享变量, 和一个计算新值的符号表达式。 所以这里能看到update w和update b, 是函数的隐式输出,

[582.132-594.844] SPEAKER_01: 就像w和b是隐式输入。 update w和update b是隐式输出, 它们跟c一起计算。 等所有输出算完后, 更新才会生效。 然后值就更新了。

[594.924-605.001] SPEAKER_01: 这里如果我们打印值, 值更新后,再在这打印值, 值更新后,再在这打印值, 调用train前后b的值, 就能看到值变了。

[605.081-608.192] SPEAKER_01: 图形编译时还有一件事,

[608.272-615.400] SPEAKER_01: 就是我们选的子图。 是我们选的子图吗? 因为那个特定函数优化了。 就是说它会被 部分重写,有些表达式

[615.480-623.978] SPEAKER_01: 会被替换掉等等 目标也不同。 有些很简单,比如 同一计算定义了两次 我们只让它执行一次。

[624.058-633.322] SPEAKER_01: 如果表达式没必要 你根本不想算它们。 比如X除以X,你不想 而且x没别处用。 我们就换成1。 还有数值稳定性优化。

[633.402-639.984] SPEAKER_01: 例如一加x的对数 x很小可能下溢 变成零 应该接近x。

[640.064-655.774] SPEAKER_01: softmax对数会被优化 成更稳定的log softmax。 这也是就地操作的时候 然后插入破坏性操作。 比如,如果一个操作是最后一个 对某些数字做的, 它不用分配输出内存,

[655.854-659.499] SPEAKER_01: 直接在输入上做就地操作等。

[659.579-662.713] SPEAKER_01: 还有,图表达式的传输

[662.793-666.624] SPEAKER_01: 到GPU在优化阶段完成。

[666.704-677.025] SPEAKER_01: 所以默认情况下, TNO会尽量用大部分优化, 好让你得到运行时 几乎最快, 除了检查和断言。 但如果你在迭代,想快速反馈

[677.105-684.767] SPEAKER_01: 又不太关心运行速度, 那你有几种启用禁用方法 一些优化集,你可以这样操作 要么全局,要么逐个函数。

[684.847-693.786] SPEAKER_01: 所以比如,要看 图优化阶段发生了什么, 这是没优化的原始图 从x和w到预测输出的过程

[693.866-705.626] SPEAKER_01: 这图就是我们之前见过的 如果拿它跟函数比较 编译好的函数 从输入变量到输出out 这函数叫predict,拿到的就是这个 我不细讲里面发生了什么

[705.706-717.466] SPEAKER_01: 但这里有个gem view操作 它调用了一个优化过的glass程序 这程序能同时做乘法和累加 这两个操作同时进行 这里有个sigmoid,就地操作

[717.546-722.294] SPEAKER_01: 破坏性地处理输入 这里用红色箭头标出

[722.374-729.595] SPEAKER_01: 比如,你看这里 算更新后w和b的未优化图表达式 这是原图 优化后图小多了 它也有就地操作

[729.675-742.239] SPEAKER_01: 它有融合的LMWise操作,比如, 如果你有完整的张量, 然后做一个逐元素操作,比如加常数, 然后sigmoid,还有其他操作等等, 你只想遍历一次数组,

[742.319-750.701] SPEAKER_01: 对每个元素应用所有标量操作, 然后进入下一个,以此类推, 而不是每次应用都迭代 一个新的操作。 这很常见,

[750.781-763.401] SPEAKER_01: 当你用自动生成的梯度表达式时。 这里你看到共享变量更新, 这些是输入。 所以看到成本和隐含输出, 用于更新后的w和b,这里和这里。

[763.481-772.083] SPEAKER_01: 另一种图可视化工具 是DebugPrint,它打印基于文本的 图的树状结构, 分配任意ID并打印变量名, 等等。

[772.163-778.722] SPEAKER_01: 这里能看到更详细的结构。 还有 gem v 的输入。 以及缩放参数这些。

[778.802-782.447] SPEAKER_01: 当函数编译后, 我们就可以实际运行了。

[782.527-791.304] SPEAKER_01: TNO 函数是 Python 可调用对象。 我们可以调用它。 我们已经看过例子了。 比如这里, 我们调用了 train 等。 但实际发生的是,

[791.384-794.298] SPEAKER_01: 比如,为了得到优化运行时,

[794.378-803.561] SPEAKER_01: 不只是图优化, 我们还会生成 C++ 或 CUDA 代码。 比如,刚才说的逐元素循环融合, 我们无法事先知道, 哪个逐元素操作会出现

[803.641-814.821] SPEAKER_01: 在用户定义的图中顺序如何。 所以我们做即时代码生成。 生成 C++ 或 CUDA 写的 Python 模块。 编译后重新导入 这样我们就能在Python里用了。

[814.901-829.125] SPEAKER_01: 运行时环境, 然后就能在Python里用了。 所以就能在Python里用了。 环境按正确顺序调用 要执行的不同操作 从输入到输出 得到想要的结果。 有几种不同的,

[829.205-840.478] SPEAKER_01: 特别有一个是用C++写的 它避免了上下文切换 在Python解释器 和C++执行引擎之间。 另一件很关键的事 关键在于GPU。

[840.558-847.547] SPEAKER_01: 那怎么在TNO里用GPU? 我们通常希望它尽量简单。

[847.627-851.330] SPEAKER_01: 现在支持几种数据类型,

[851.410-859.455] SPEAKER_01: 支持float32和双精度 要是你真需要那个。 整数也一样。 而且现在交互更简单了。

[859.535-868.834] SPEAKER_01: 直接用Python和GPU数组交互。 所以可以直接用Python代码处理GPU数组。 你可以在TNO函数外使用。

[868.914-874.556] SPEAKER_01: 这些都会在未来的0.9版本里。 希望能很快发布。

[874.636-885.514] SPEAKER_01: 要使用它,先选设备。 你想要的主设备。 用一个配置标志就行。 比如用CUDA获取第一个可用的GPU。 或者指定某个GPU。

[885.594-892.083] SPEAKER_01: 如果你在配置里指定了, 那默认所有共享变量 都会在GPU内存里创建。

[892.163-901.799] SPEAKER_01: 还有把计算从CPU移到GPU的优化, 从CPU到GPU,替换掉CPU操作。 用GPU操作。 所以CPU操作会被应用。

[901.879-912.281] SPEAKER_01: 一般用 float32 就行 或用 float16 存储,实验性的 但大部分 GPU 双精度性能不佳 那么怎么设置这些配置标志?

[912.361-917.910] SPEAKER_01: 首先有 TNORC 配置文件 这只是个基本的 Python 配置文件

[917.990-935.105] SPEAKER_01: 有环境变量,在里面定义设置 环境变量会覆盖配置文件的设置 另外,也能直接从 Python 设置 但有些标志得在导入 TNO 前就知道 比如设备本身,得在配置文件中用标志设置

[935.185-942.290] SPEAKER_01: 下面快速介绍更高级的主题 如果你想了解更多 网上有其他教程 还有 deeplearning.net 的文档

[942.370-946.294] SPEAKER_01: 为了图中有循环,我们看到

[946.374-952.260] SPEAKER_01: 如果想了解更多关于那个 网上有其他教程 还有 deeplearning.net 的文档

[952.340-962.475] SPEAKER_01: 图中包含循环,我们已经看到 表达式图就是个有向无环图 而且不能有循环 如果提前知道迭代次数 就用for循环展开

[962.555-968.719] SPEAKER_01: 给每个时间步建好节点 比如你想 给循环设动态大小

[968.799-972.490] SPEAKER_01: 比如生成序列的模型 就可能出问题

[972.570-983.193] SPEAKER_01: TNO里有个scan 它封装了另一个TNO函数 一个完整的TNO函数 这个步进函数会计算 每个时间步要做的计算

[983.273-994.708] SPEAKER_01: 每个时间步都得算完 有个TNO函数做计算 负责一个时间步,scan节点 在循环里反复调用 管好索引和序列记录 在正确位置提供正确切片

[994.788-997.876] SPEAKER_01: 需要时就输出反馈。

[997.956-1013.283] SPEAKER_01: 并且这种结构也让 可以给这个节点定义梯度。 这基本是另一个扫描节点和循环。 它会向后运行,并按时间反向传播。 它也能转到GPU上。 这时内部函数会被

[1013.363-1016.996] SPEAKER_01: 传到GPU上重新编译。

[1017.076-1020.709] SPEAKER_01: 后面LSTM示例中有个scan例子。

[1020.789-1024.782] SPEAKER_01: 这只是一个很小的例子。 但我们真没时间讨论那个。

[1024.862-1037.540] SPEAKER_01: 还有可视化、调试和诊断工具。 它重要原因之一是,在TNO里, 像在TensorFlow中,函数定义独立于 它的执行,如果某些东西在 执行过程中不工作,

[1037.620-1044.527] SPEAKER_01: 如果遇到错误等等, 那就不容易把错误和 跟实际定义表达式的位置关联。

[1044.607-1051.665] SPEAKER_01: 所以我们努力提供详细错误信息。 我们有一些完成模式,能启用。 比如检查是否为NaN或大数值。

[1051.745-1063.761] SPEAKER_01: 你可以给符号变量分配测试值。 这样每次你新建一个符号。 中间变量,每次定义新表达式。 测试值就会自动计算。 这样就能在单条数据上评估。

[1063.841-1066.813] SPEAKER_01: 同时构建计算图。

[1066.893-1071.838] SPEAKER_01: 这有助于检测形状不匹配错误。 或者类似情况。

[1071.918-1081.414] SPEAKER_01: 有几种方式可以扩展TNO。 你可以只用Python创建操作。 通过调用Python封装器。 用于现有的高效库。 你可以写C或CUDA代码扩展TNO。

[1081.494-1089.493] SPEAKER_01: 你还可以添加优化。 比如为了提高数值稳定性。 或者让计算更高效。 或者用于引入。 你的新操作代替朴素版本。

[1089.573-1092.487] SPEAKER_01: 用户可能用过。

[1092.567-1099.173] SPEAKER_01: 我们有几项新功能 最近加到TNO里的。 我提到新的GPU后端

[1099.253-1106.590] SPEAKER_01: 支持多种数据类型 我们还有一些性能改进 特别是2D和3D卷积。 尤其在GPU上。

[1106.670-1111.012] SPEAKER_01: 时间上有些进展 图优化阶段的时间

[1111.092-1127.395] SPEAKER_01: 还引入了新方法 避免重复编译同一个图。 我们还有很有用的诊断工具 还有交互式可视化, 交互式图可视化工具 以及PDB断点,让你能 监控几个变量,条件满足时中断

[1127.475-1131.468] SPEAKER_01: 如果满足条件 而不是每次监控 每个数据。

[1131.548-1142.879] SPEAKER_01: 以后在GPU上写新操作时 还是想封装更多QDNN操作 来提升性能 尤其基础的RNN,几天内能搞定 希望能成

[1142.959-1148.926] SPEAKER_01: 最近有人在这方面做了很多 我们想更好支持3D卷积

[1149.006-1154.973] SPEAKER_01: 还在追求更快优化 还有更多数据并行的工作 所以,感谢各位同事

[1155.053-1168.465] SPEAKER_01: 还有TNO的主要开发者 以及所有以各种方式贡献的人 以及所有以各种方式贡献的人 还有为实验室和软件开发出力的人 当然还有研讨会组织者

[1168.545-1169.694] SPEAKER_01: 现在

[1169.774-1173.303] SPEAKER_01: 对,幻灯片已经上线

[1173.383-1182.160] SPEAKER_01: 我提过,还有配套笔记本 现在可以开始了 现在可以开始了 想了解更多?还有更多资源。 现在该开始了。 来看看实际示例。

[1182.240-1184.794] SPEAKER_01: 还没克隆仓库的话,

[1184.874-1190.655] SPEAKER_01: 运行这个命令行。 克隆过仓库的话, 运行一次 git pull,

[1190.735-1196.435] SPEAKER_01: 获取最新内容, 确保版本最新。 然后启动 Jupyter Notebook,

[1196.515-1199.800] SPEAKER_01: 就在仓库里, 运行它。

[1199.880-1203.049] SPEAKER_01: 来看三个示例:

[1203.129-1206.774] SPEAKER_01: 逻辑回归、卷积网络和 LSTM。

[1206.854-1216.037] SPEAKER_01: 我启动了 Jupyter Notebook。 现在从 intro TNO 笔记本开始。 里面没什么新内容, 就之前展示的代码。

[1216.117-1219.007] SPEAKER_01: 好。 那就用逻辑回归吧。

[1219.087-1222.720] SPEAKER_01: 够大吗? 还是得把字体调大?

[1222.800-1223.833] SPEAKER_01: 好的。

[1223.913-1227.523] SPEAKER_01: 文字部分我就跳过了, 反正你们应该懂这个模型。

[1227.603-1229.994] SPEAKER_01: 我们有一些,

[1230.074-1233.638] SPEAKER_01: 已经打包好的MNIST数据集,

[1233.718-1234.693] SPEAKER_01: 还有,

[1234.773-1239.660] SPEAKER_01: 在GitHub上和代码一起。 那我们来加载数据吧。

[1239.740-1245.080] SPEAKER_01: 现在看看怎么定义模型。 跟刚才幻灯片里差不多。 我们定义一下

[1245.160-1254.935] SPEAKER_01: 共享变量的大小。 再定义一个输入变量。 这里用矩阵,因为要处理多个批次。 初始化共享变量为零。 然后定义模型。

[1255.015-1257.604] SPEAKER_01: 这就是我们的预测器。

[1257.684-1261.573] SPEAKER_01: 所以,给定输入后这类的概率,

[1261.653-1267.457] SPEAKER_01: 我们会很好地用它,所以这里, 仿射模型,再加上softmax,

[1267.537-1270.532] SPEAKER_01: 还有预测。 要是你想要硬预测,

[1270.612-1280.770] SPEAKER_01: 就选概率最大的类别。 所以沿着那个轴取argmax, 因为我们还要一个预测 对小批量里的每个元素。 然后定义损失函数。

[1280.850-1288.094] SPEAKER_01: 这里就是给定输入下标签的对数似然 或者说交叉熵。 我们简单定义一下,

[1288.174-1299.331] SPEAKER_01: 不需要单独搞个交叉熵 或者对数似然操作。 可以从基本组件直接构建。 所以我们取概率的对数, 再取真实目标的索引,

[1299.411-1302.801] SPEAKER_01: 然后取它们的平均值 得到小批量的平均预测。

[1302.881-1305.934] SPEAKER_01: 接下来推导梯度。

[1306.014-1316.114] SPEAKER_01: 再来推导更新规则。 再强调一次,没有梯度下降这类东西。 或者其他类似的。 我们就构建任何想要的规则。

[1316.194-1325.180] SPEAKER_01: 所以,我们可以用动量。 通过定义其他共享变量, 来保存速度。 然后更新表达式, 更新速度和共享变量。

[1325.260-1334.176] SPEAKER_01: 然后编译一个训练函数, 从X和Y开始, 输出损失,更新W和B。 当代码在生成和编译时,

[1334.256-1344.194] SPEAKER_01: 图也在优化。 我们看看下一步。 我们不仅要监控对数似然, 还要监控错误分类率, 在验证集和测试集上。 所以这只是不同。

[1344.274-1354.455] SPEAKER_01: 比如元素之间有多少不同。 但在预测之间, 也就是argmax和实际目标的差异。 比率是小批量的平均值。 我们再创建另一个。

[1354.535-1357.820] SPEAKER_01: 我们编译另一个TNO函数。

[1357.900-1360.790] SPEAKER_01: 输出它,当然不更新。

[1360.870-1375.036] SPEAKER_01: 所以,为了训练模型, 我们先处理一下数据。 所以我们要输入模型 一次一个小批量。 这里有个简单的生成器, 我是说,不完全是Python生成器, 只是个辅助函数,给我们 第I个小批量。

[1375.116-1379.620] SPEAKER_01: 同一个函数,用于 训练集、验证集和测试集。

[1379.700-1383.786] SPEAKER_01: 我们定义了提前停止的参数

[1383.866-1388.428] SPEAKER_01: 在那个训练循环里。 不必须,只是

[1388.508-1393.558] SPEAKER_01: 知道何时停下,只用最佳方法。 优化中遇到的模型。

[1393.638-1403.715] SPEAKER_01: 那我们来定义一下。 这是主要训练循环。 它比想象中复杂一点, 但这因为我们用了提前停止, 我们只在确信时才验证

[1403.795-1410.041] SPEAKER_01: 训练误差已经够低了。 但最重要的部分是, 你遍历每个时期,除非

[1410.121-1419.455] SPEAKER_01: 除非遇到提前停止条件。 然后每个时期里, 你要遍历小批量数据 并调用训练模型。 每隔一段时间就验证一下,

[1419.535-1429.891] SPEAKER_01: 并打印验证误差结果。 这里在验证集上调测试模型。 我们为此设置。 然后跟踪当前最佳模型,

[1429.971-1433.535] SPEAKER_01: 同时获取测试误差。

[1433.615-1436.320] SPEAKER_01: 保留最好的那个。

[1436.400-1441.102] SPEAKER_01: 所以呢,要保存最好的模型, 通常我们只保存参数的值,

[1441.182-1445.884] SPEAKER_01: 这比给整个对象做pickle更可靠, Python对象。

[1445.964-1451.931] SPEAKER_01: 而且它更容易迁移到其他框架, 像可视化框架这些。

[1452.011-1454.901] SPEAKER_01: 那我们来试一下。

[1454.981-1460.867] SPEAKER_01: 当然,这个模型很简单。 数据不算大,所以应该, 不会花太长时间。

[1460.947-1481.185] SPEAKER_01: 你会看到开始时,嗯,几乎每次迭代, 你会看到开始时,嗯,几乎每次迭代, 你会看到开始时,嗯,几乎每次迭代, 你会看到开始时,嗯,几乎每次迭代, 在训练集上表现越来越好。 但过一会儿,进步就变慢了。

[1481.265-1482.518] SPEAKER_01: 而且,

[1482.598-1486.649] SPEAKER_01: 好的。 那我再等一会儿。 感觉越来越慢了。 还有,

[1486.729-1491.698] SPEAKER_01: 好的。 训练96轮后结束。 也是96轮后结束。 所以现在,

[1491.778-1496.747] SPEAKER_01: 想看看学到的滤波器, 或者模型训练完的样子,

[1496.827-1501.656] SPEAKER_01: 用一个辅助函数就行, 来显示滤波器。 这不重要。

[1501.736-1505.787] SPEAKER_01: 但这里调用权重的get value方法

[1505.867-1518.977] SPEAKER_01: 得到共享变量的内部值。 然后拿这个取权重。 然后用它取权重。 再用它取权重。 来画不同的滤波器。 看起来还算合理。 比如这是类别0的滤波器。

[1519.057-1524.687] SPEAKER_01: 有点像0和1。 对两边来说,关键的是 就是这里有个开口啥的。

[1524.767-1530.792] SPEAKER_01: 没错。 看看最终误差, 看看最终误差, 看看最终误差, 可以看到训练误差是,

[1530.872-1542.542] SPEAKER_01: 我们看到训练误差了吗? 没画它。 但验证误差和测试误差都很高。 而且人类水平的表现也很低。 其他模型的表现也差不多低。 这说明模型太简单了。

[1542.622-1545.454] SPEAKER_01: 得用更先进的方法。

[1545.534-1548.378] SPEAKER_01: 为了用更先进的,

[1548.458-1553.067] SPEAKER_01: 回到Jupyter Notebook主页, 你可以看看卷积网络。

[1553.147-1555.399] SPEAKER_01: 然后运行它。

[1555.479-1565.939] SPEAKER_01: 这个新例子还是同样的数据。 仍然是MNIST,因为它有优势。 旧笔记本上也能快速训练。 这次我们用卷积网络。 包含几个卷积层。

[1566.019-1569.769] SPEAKER_01: 然后是全连接层。 最后是分类器。

[1569.849-1573.239] SPEAKER_01: 所以确保x是float32类型。

[1573.319-1577.521] SPEAKER_01: 来看看怎么用TNO定义辅助类。

[1577.601-1587.121] SPEAKER_01: 这些层让用户更方便。 用户更容易。 让用户更方便。 想复现结果时组合这些层。 或者用经典架构。

[1587.201-1596.396] SPEAKER_01: 这通常在TNO框架中完成。 比如Keras、Blocks、Lasagne。 也有人开发自己的小框架。 带上自己版本的层等。

[1596.476-1601.027] SPEAKER_01: 他们觉得这些层好用又直观。

[1601.107-1605.971] SPEAKER_01: 那么, 逻辑回归层里有, 权重和偏置的参数。 接着计算,

[1606.051-1613.214] SPEAKER_01: 嗯,类别的条件概率 预测参数 以及负对数似然表达式 还有误差。

[1613.294-1619.563] SPEAKER_01: 所以, 如果你只用分类器,比如Softmax, 那本质上就是在做之前手动做的事 笔记本。

[1619.643-1629.581] SPEAKER_01: 同样,我们可以定义带卷积和池化的层。 初始化时,传入过滤器形状、图像形状、池化大小 等等。

[1629.661-1636.359] SPEAKER_01: 我们用Gloro和Bengio 2010的公式初始化权重,偏置从零开始。

[1636.439-1645.320] SPEAKER_01: 然后从输入,我们计算与滤波器的二维卷积。 然后计算最大池化,输出tanh(0)。 再加上偏置。

[1645.400-1655.756] SPEAKER_01: 这里每个通道只有一个偏置数,意味着你 不会对图像每个位置有不同的偏置。 所以,你可以把这种层用于不同大小的图像,无需初始化

[1655.836-1667.747] SPEAKER_01: 新参数或重新训练。 然后用同样方法定义头部。 好的。 隐藏层就是全连接层。 再次初始化权重、偏置,以及输入到输出的符号表达式。

[1667.827-1675.512] SPEAKER_01: 再次收集参数,清楚要训练什么。 然后这里有一个函数,它具有... 训练主循环。

[1675.592-1686.284] SPEAKER_01: 所以有个小批量生成器,代码和之前一样。 这里我们在构建整个图。 总是同样的过程。 定义输入、符号输入变量、矩阵和整数向量。

[1686.364-1695.303] SPEAKER_01: L向量是长整型,标签是索引。 好的。 而不是独热向量或掩码。 创建第一层,这是一个特定大小的层。

[1695.383-1701.652] SPEAKER_01: 希望下一个也具有... 所以图像大小变了。 主要是为了提高效率。 这些模型,你不必传递那个。

[1701.732-1709.232] SPEAKER_01: 那些特定模型,不必传递那个。 但你仍需要滤波器的形状。 反正你也有那些滤波器。

[1709.312-1718.332] SPEAKER_01: 然后保持尺寸仍有用,因为即使卷积层可以 处理任意尺寸图像后,我们希望将整个特征图

[1718.412-1729.905] SPEAKER_01: 并将特征图输入全连接层,再到预测层。 所以这个必须是…… 这个必须是固定的。 所以必须知道最后一层的维度。 然后我们开始。

[1729.985-1741.943] SPEAKER_01: 全连接层。 输出层就是逻辑回归类。 和之前一样。 我们希望最终代价是它的对数似然。 我们有…… 再次,…… 我们有…… 好的。

[1742.023-1751.206] SPEAKER_01: 我们有误差,即误分类率。 参数是所有层参数的串联。 一旦有了这些,就能构建梯度。 只需一次梯度计算。

[1751.286-1759.691] SPEAKER_01: 获取更新。 还是普通SGD,用类加动量。 梯度上加Δ。 任何你需要的。 编译函数。

[1759.771-1769.267] SPEAKER_01: 这里提前停止,主循环相同。 遍历所有周期到结束。 循环小批量,验证后停止。 先声明一下。 加载数据。

[1769.347-1775.349] SPEAKER_01: 和之前一样。 然后... 好的。 这里可以运行了。 这是之前的结果。 花了五分钟。

[1775.429-1782.093] SPEAKER_01: 可能没时间做。 这里你能看到大致情况。 午休时试试,欢迎。 玩玩它。

[1782.173-1787.803] SPEAKER_01: 之后,你还能可视化过滤器运行。 这是第一层的过滤器。

[1787.883-1792.492] SPEAKER_01: 而对于… 第一层对示例的激活例子。 所以…

[1792.572-1799.433] SPEAKER_01: 我们还有时间完成LSTM教程。 我是说例子。 回到Jupyter首页,进入LSTM…

[1799.513-1814.550] SPEAKER_01: 所以… 所以… 所以… 所以… 所以… 所以… 所以… 所以… 所以… 所以… 所以… 所以呢… 所以说啊… 因此嘛… 这样一来… 那么… 于是啊… 可见… 总之一句话… 然后呢…

[1814.630-1830.457] SPEAKER_01: 所以嘛… 因此呢… 这么一来… 说到底… 结果呢… 话说回来… 这样看来… 所以说呢… 因此啊… 于是呢… 所以喽… 所以... 所以... 所以... 所以... 所以... 所以... 所以... 所以...

[1830.537-1834.205] SPEAKER_01: 你需要不同的偏置 用于不同的门等等。

[1834.285-1836.932] SPEAKER_01: 所以参数很多。

[1837.012-1846.636] SPEAKER_01: 有时更高效的做法是 只定义一个变量,比如 它包含几个矩阵的拼接 这样你能做更高效的 更大矩阵的矩阵乘法。

[1846.716-1850.280] SPEAKER_01: 但这只是个简单实现。

[1850.360-1854.911] SPEAKER_01: 这是一个用scan循环的例子。 所以我们定义一个步函数

[1854.991-1858.682] SPEAKER_01: 它接受几个不同输入

[1858.762-1869.199] SPEAKER_01: 所以有不同激活函数之类的。 来自之前时间步。 有当前序列输入。 然后从它们得LSTM公式。 有点积和sigmoid或tanh。

[1869.279-1872.645] SPEAKER_01: 在细胞内部不同连接。

[1872.725-1877.786] SPEAKER_01: 最后有隐藏状态和门。 所以一旦有了步骤函数。

[1877.866-1881.000] SPEAKER_01: 传递到tno.scan。

[1881.080-1885.689] SPEAKER_01: 其中序列含掩码和输入。 掩码有用,因为用很多批次。

[1885.769-1896.763] SPEAKER_01: 的序列,同一批次所有序列。 长度不一定相同。 为提高效率,通常分组。 把两个长度相近示例分组。 但长度不一定完全相同。

[1896.843-1911.532] SPEAKER_01: 所以只填充到最长序列。 在mini批次里,非整个集合最长。 仅针对mini批次,但还需填充。 并记住长度。 不同的序列顺序,是为了让我们 能正确预测和反向传播。

[1911.612-1914.201] SPEAKER_01: 那我们来定义一下。

[1914.281-1923.046] SPEAKER_01: 这里我们定义了成本函数。 也就是序列的分类交叉熵。 这里又看到,用了掩码 这样我们就不考虑 序列结束后的预测。

[1923.126-1928.048] SPEAKER_01: 逻辑回归,和之前一样, 只是最终的成本。

[1928.128-1938.902] SPEAKER_01: 这里为了处理, 我们用数据,用Fuel, 这是另一个开发中的工具, 是Mila的几个学生开发的。 它很好,因为能从纯文本读取数据,

[1938.982-1946.667] SPEAKER_01: 动态做一些预处理, 包括我之前提到的那些, 比如按长度相近的序列分组,

[1946.747-1949.637] SPEAKER_01: 然后打乱、填充这些操作,

[1949.717-1960.015] SPEAKER_01: 然后它输出,就像个生成器。 然后把它输入主循环。 通过TNO函数。 所以预处理都在TNO外面做。 然后预处理的值输入 到TNO函数里。

[1960.095-1968.535] SPEAKER_01: 所以我们在这里构建最终的TNO图。 我们有符号输入,用来输入 掩码。 我们建一个LSTM层、加载图层、

[1968.615-1979.098] SPEAKER_01: 定义成本、参数或者逻辑回归参数、 再与循环层参数拼接。 计算成本对所有参数的梯度。 就像我说的,这要用随时间反向传播

[1979.178-1987.235] SPEAKER_01: 来获取扫描操作的梯度。 更新规则就是简单SGD,没动量,啥都没有。

[1987.315-1990.205] SPEAKER_01: 这是你可以加的东西,

[1990.285-1995.555] SPEAKER_01: 如果你想玩玩的话, 再编译函数评估模型。 所以主循环就是训练。

[1995.635-1999.129] SPEAKER_01: 所以主循环就是训练。

[1999.209-2009.309] SPEAKER_01: 还有另一个函数 每次生成一个字符 基于之前的字符 所以这里要声明输入 有一个说话函数 它拿概率预测再归一化 因为用float32

[2009.389-2015.728] SPEAKER_01: 有时除以总和再相加 加起来不等于一 所以这个操作要用更高精度

[2015.808-2018.722] SPEAKER_01: 然后尝试生成

[2018.802-2028.995] SPEAKER_01: 生成一个序列 每个序列都有 偶尔 强调一下这是之前运行的结果 我们设置种子为了监控 用生命的意义是作为种子预测

[2029.075-2031.071] SPEAKER_01: 然后让网络生成

[2031.151-2041.704] SPEAKER_01: 我现在运行的话会花很长时间 但这里有一些昨天生成的例子 之前运行的时候。 所以一开始并不多。 而且有些少见字符。 我是说,通常呢,

[2041.784-2049.388] SPEAKER_01: 很少见到像汉字这样的, 出现在单词中间。 还有标点在单词中间。 但后来呢,

[2049.468-2054.332] SPEAKER_01: 你会看到, 它越来越大,越来越大。 它越来越大了。 它慢慢变好,越来越好。

[2054.412-2056.443] SPEAKER_01: 生命的意义就那些。

[2056.523-2059.495] SPEAKER_01: 所以这当然不会给你

[2059.575-2065.020] SPEAKER_01: 真正的生命意义,但是啊, 一大堆火腿,为什么不呢?

[2065.100-2067.828] SPEAKER_01: 当然,这就是那个。

[2067.908-2071.692] SPEAKER_01: 所以我在某个时候中断了训练,

[2071.772-2079.968] SPEAKER_01: 但你可以稍微玩玩。 这里有些建议,你可以试试。 比如更好的训练算法 LSTM里不同的非线性 不同的权重初始化

[2080.048-2085.109] SPEAKER_01: 尝试生成生命意义之外的东西

[2085.189-2086.222] SPEAKER_01: 是的

[2086.302-2094.208] SPEAKER_01: 希望我能好好介绍一下 TNO是什么,能做什么 以及你能用它构建什么 如果之后有问题

[2094.288-2100.917] SPEAKER_01: 我们有TNO用户邮件列表 也在Stack Overflow上回答 很乐意在那回答

[2101.217-2105.849] SPEAKER_00: 很高兴收到反馈。有几个问题,比如这里的人

[2106.149-2110.920] SPEAKER_02: 到麦克风前,演示一个调试例子 在Theano里,故意破坏点东西展示

[2111.220-2117.024] SPEAKER_01: 会怎样,怎么找出问题。当然,我想我有一个 我们看个更简单的例子。我就

[2117.104-2121.213] SPEAKER_01: 看逻辑回归那个,比如初始化 一个

[2121.293-2124.439] SPEAKER_01: 初始化时形状不对,所以你还可以

[2124.519-2131.798] SPEAKER_01: 建好符号图,运行时报形状不匹配。 X有行列,但Y行数不匹配,点积节点出错。

[2131.878-2139.877] SPEAKER_01: 它说不清定义位置,去掉优化可能就行。 这么做,再回到训练操作定义处:训练模型。

[2139.957-2146.899] SPEAKER_01: TNO函数,设优化器为none。 哦抱歉,得把模式设成TNO点模式。

[2146.979-2152.667] SPEAKER_01: 优化器为none对吗?对,它在重编译函数。 让我们回顾一下。

[2152.747-2158.946] SPEAKER_01: 新错误信息显示节点创建时的回溯。 它在我内核里某行。

[2159.026-2163.577] SPEAKER_01: P Y given X等于那个,里面东西多,但...

[2163.657-2171.296] SPEAKER_01: 有个点积,很可能不匹配。 这是例子,还有其他技术可用。 可设断点,如前所述。现在没教程。

[2171.376-2178.295] SPEAKER_01: 但有在线资源可指给你看。 最后一个问题。 你好,我有模型想分发,无需他人安装。

[2178.375-2181.080] SPEAKER_01: 关于那个,我有线上资源给你看。

[2181.380-2183.516] SPEAKER_00: 最后一个问题。

[2183.816-2190.085] SPEAKER_03: 嗨,我有模型想分发,不需要安装。 Python和一堆编译器,有没有支持编译模型的功能?

[2190.385-2201.681] SPEAKER_01: 但当时我们和Python绑得很紧,因为 运行时的内存全靠Python管,我们用umpire ndarray 处理CPU上的中间值 GPU上也有类似结构,

[2201.761-2212.813] SPEAKER_01: 不过GPU那边可能更好改。 但我们所有C代码都要和Python打交道 做引用计数加减那些事 让Python来管理内存。 所以如果要分发,

[2212.893-2223.713] SPEAKER_01: 建议用Docker容器之类的, 类似的东西。 最近NVIDIA Docker在GPU上也很好用。 而且没有遇到以前那种 模型减速的问题。 这就不太理想了。

[2223.793-2229.656] SPEAKER_01: 如果有人有时间有精力 帮我们把TNO从Python运行时拆出来

[2229.736-2233.184] SPEAKER_01: 那就太好了,但工程太大了。

[2233.484-2235.678] SPEAKER_00: 好,再次感谢帕斯卡。

[2235.758-2239.542] SPEAKER_00: 55分钟后集合,下一场演讲。

[2239.622-2241.758] SPEAKER_00: 午餐愉快。