想想博客这种东西写点务虚的讨论应该也没关系,所以扯一点最近的想法,也许会比较可笑,看看就好。

从深度学习开始

作为模式识别和机器学习领域发展的最新浪潮,深度学习的这一波势头已经持续了好几年, 而目前看来还有更加猛烈之势,并未到达它的最高点,更远未开始走下坡路。 深度学习有形象的概念,容易上手的工具,大大降低了此行业的门槛,使得机器学习方向大放光彩,以摧枯拉朽之势, 力斩各行各业的疑难杂症,甚至也反过来促使更多决策者考虑是否直接采用这种办法去解决新出现的问题。

但是有的研究者对这样的现状是不满意的。早在多年之前吴军博士就曾经提到数据为王,特定情况下多一点数据带来 的提升远比算法改进带来的进步要大得多。如今情况则更加严峻,由于大数据的兴起、以及特定领域新的数据集的提出, 数据无论对于工业界还是学术界都相对容易获取,不同的工作之间有了可比性。这时谁的计算能力更强,谁就能 在单位时间内训练得到一个相对更复杂,但性能更好的模型。当然也有以三个多层感知机四两拨千斤,效果胜过了经营多年 复杂且参数空间巨大的模型的情形,但这更多属于机器学习学科内部的讨论,此处不展开。从线性判别模型开始, 人们的方法就变成了:提出模型——用数据验证性能——提出更好的模型。这一眼看上去没什么问题,但这种方法之所以 有效有一个前提,即随机变量真的具有与所提出模型相近的分布。虽然这个方法从哲学上讲没有问题,但是比拼计算力、 让模型复杂化,这样的工作很难称得上真正有启发性。

同时,模型复杂使得它无论成功还是失败都难以解释。

深度学习的函数式解释

既然深度学习如此成功,以及部分研究者对此抱有怀疑,于是人们很自然地就想知道深度学习为什么能够有这样的效果。 曾经有工作证明,三层的感知机就能逼近任意函数。这个结论理解起来其实是显然的,哪怕隐含层只是把每种输入的输出 缓存起来,就能以黑盒的形式逼近任意函数。深度学习三巨头之一 Bengio 的工作曾经提到,层数少一层,前面隐含层就 需要指数级增加节点数。这个解释与前面的理解是较吻合的。当然 Bengio 的目的是证明深度的有效性,在这里我们 可以简单地对深度学习放下心来,它的表现力是可以信任的,不会具有某种我们目前没有想到的局限。

那么,深度学习是什么?最直接的解释当然是人的大脑。人脑就是一个复杂的神经网络,而多层前向神经网络也是源自于它。 虽然几十年前人们就已经提出了这项成果,但是直到现在有了充足的计算力、大量的训练数据之后,这项工作才能继续展开。 现有很多深度学习的成果都源于脑科学和认知科学的类比。这种比喻被媒体如火如荼地宣传,也被大众广泛认知,但 LeCun 在接受采访时批评了这种比喻。现阶段的深度学习和人脑完全不是一回事,这样的宣传反而会让人们以为深度学习无所不能。 因为深度学习依然还是模式识别学科的一个进展,它的方法论与以前的感知机、逻辑斯特回归、支持向量机等等并无二致。

于是很自然地,深度学习也可以有第二种基于概率与统计的解释,即可以认为我们是在为输入和输出进行建模,以估计其 联合概率或条件概率。但是由于在输入和输出之间可能有很多隐藏变量的影响,因此训练网络的过程其实是在进行一个含有 隐变量的概率进行建模。而训练完成后,人们实际也就是在神经网络上进行概率推断。

然而正如加拿大天才少年 Chris Olah 提到的那样,其实神经网络还可以用函数式编程的方式来解释。普通的前馈神经网络 就是一个函数的组合,如果把每一层看成一个函数,下一层的函数就使用了前一层函数的输出。而现在比较常见的 CNN、RNN 等典型的网络结构,也都有对应的函数式结构。例如流行的 sequence-to-sequence 模型,包含 encoder 和 decoder 两部分,encoder 就可以对应 foldl / foldr,decoder 对应 unfold 。

函数重用性

当我们把网络类比为函数式语言之后,神经网络就具有了另一种视角下的样子。以逻辑斯特回归为例,输入一个增广形式 的 x 向量(包含额外的一个 1 以便统一偏置项)时,实际上调用了一个多元函数 f,其参数是 x 的各个坐标, 输出为 0 ~ 1 之间的某个概率值。在我们的训练过程中,不断通过训练样本迭代,寻找合适的权值, 其实就是在寻找一个函数可以对此种类型的输出产生相应的反应。当然,这个函数是一个对现实的逼近,我们看到的代码 是各种加法乘法和非线性变换,无法将其直观解释为我们所需功能的算法实现。

这样得到的一个函数,对新换一批相同分布的样本应该依然有效,比如其输入是一条鱼的长度和表面光泽,输出是鱼为 三文鱼的概率,它应该对从海里新捕捞的鱼依然有效,但是换到另一个场景,若我们想判断网友是男是女,它的输出就 不可预测了。

而不难推断,如果深度模型非常复杂,管线拉得很长,这个函数自然很难找到,哪怕找到了可迁移性也会大打折扣。然而, 要对每一步进行优化的话,需要很多中间数据用来监督学习,这个就很难得到了。这也是为什么端对端的网络如今这么流行, 通过限制模型的结构,让优化中间管道更为容易,同时也不必利用中间监督数据,只需要有最终输入和输出数据就够了。 显然,这样做会导致中间任何一部分结构都不能为其他代码所用。

目前重用性做得最好的,当属自然语言处理的各种工具包,分词、词性标注、句法解析、依存句法解析等等模块,粒度较小, 同时监督数据比较容易得到,因此也比较容易形成可以重用的模块。

类型理论

前面实际上已经暗示了,在普通的编程语言中,对于输入的变量是需要限制其使用场景的,也就是要有类型。给定一个 32 位 机器上一个字 0xFFFFFFFF,如果我们知道它是无符号整数,那么它自然就是 4294967295 这个数,而如果其实它是浮点型, 把这个数字传到需要整数的函数中,自然结果就会不符合预期。如果有一套类型系统,就能帮我们检测出类似的错误,也能 作为约束帮助我们限制函数搜索空间。甚至,给定一系列类型数据和函数,我们可以自动组合出所有符合语法的 token 串。 例如函数 (song -> artist) 我们可以传入 “忘情水”,得到 “刘德华”。

在例如 Freebase / Wikidata 这类真实的知识库中,我们可以拿到庞大的 type symbol,也可以得到繁多的实体之间的关系。 类型的约束可以帮助我们在很多实体关系的推理任务上进行改进。合理设计逆变、协变等规则,也可以有助于推广实体之间 的函数。因此,实体按照类型进行编码也是一个有趣的点。

表示学习和函数编码

在知识库上,现在有个较热门的表示学习的方向。其初衷是想改进现有的推理方式,例如问 “姚明的妻子的女儿的外公是谁”, 以往的办法可能就是从“姚明”出发、然后找一条表示“妻子”关系的边、再找一条“女儿”关系的边、再找“外公”关系的边,中间 可能做一些跳跃,而如果实体或者关系缺失,这个问题就无法回答了。

然而,通过把关系和实体放到一个函数空间进行编码,经过精心设计的优化目标,可以实现把实体 h 加上某个关系 r 得到一个 实体 t 。这样只要 r 的向量表示方向是大体正确的,即使知识库中缺少了“叶莉”或者相关的边,我们也能得到姚明的岳父这一 实体。这个方法被叫做 TransE 模型,Trans- 前缀表示 translation,即原作者是以翻译的想法做的,他把关系 r 当成是 实体 h 到实体 t 的一个翻译过程。(个人认为翻译应该是相同意义下的序列转换,而这个算不上是翻译了,不过业内统一这么称呼, 也无所谓)。

当然上面这个方法是有缺陷的,它只能实现一对一的关系,例如奥巴马有两个女儿,而从奥巴马实体 + 女儿关系得到的实体,究竟 应该是谁?基于类似的疑问,研究者又提出了非常多的 TransE 系列变种。不过这不是本文的讨论目标。

通过这个表示学习的例子,我们能够看到,人们已经开始尝试实体到实体之间变换函数的编码。h + r = t 这样的代数运算, 实现了 r(h) = t 这样的函数形式,就变相地把关系 r 编码到了相容的向量空间中。

同样,我们从函数式编程的经验出发可以知道,函数也是有类型的,前面的类型理论同样可以作为有益约束应用到函数编码上。

函数编码的多种思路

上面的函数编码是将 r(h) = t 变为 h + r = t,这样只需向量的维度相同,就能进行变换了。然而,疑问就随之而来。为什么 要是加法呢,可以是矩阵乘法吗,可以再加非线性变换吗?从 h 到 t 显然是相同实体空间的变换,我们自然可以实现一个线性 算子矩阵 A 实现 Ah = t,或者 f(Ah) = t 这样的固定变换。

用什么样的会比较好呢?我们回忆,向量可以看成是一组基下的坐标。自然,使用什么样的变换,也要考虑我们选择了什么样的向量、 多少维的向量作为实体空间的基,结合起来考虑。毕竟每个实体向量即坐标的值也都是通过优化方法训练得到的。

但是问题还没有完。函数就应该是这样的吗?什么样可以称为一个特定的函数?

上面的编码我想把它称为函数的黑盒编码,它利用的是函数的可重入性,给定一个输入,就应该产生一个特定输出(暂时不考虑内部状态 改变这样的不可重入函数),也就是说它建立了一个输入到输出的映射。而我们知道这样是可以实现的,前面说了三层感知机可以逼近 任意函数,同时我们也有原始的 hopfield 网络可以存储下既有模式,以及最近对 memory network 的很多应用,都可以看作是这方面的 一些探索。

但是当涉及到高阶函数的时候还是会遇到先有鸡还是先有蛋的问题。高阶函数接受函数的时候就需要函数的编码,而函数如果只是用存储 模式的方式构建的就难以编码出来并进行表示。这需要我们精心设计一下网络的结构,以便能训练出高阶函数。

另一种就是函数的白盒编码,即我们能知道函数中每一个语句应该是什么样,函数内部的函数是如何组合的。这方面的东西没有细看过, 能想到的就是落实到传统计算机的设计上,把语句拆解成 CPU 指令,拆分出运算器、寄存器、总线、存储器等结构,就能解开谜底。 这方面的研究可以看 DeepMind 的工作《Neural Turing Machine》。

概率编程

回到一个基本的问题,即我们的训练过程实际上是在寻找一种函数,但是我们都知道现在的工作由于种种客观条件限制,不大可能有 100% 的正确率,也就是说给定输出,我们能给出的结果可能有 95% 是正确的。而我们用来构建系统的软件中所有函数都是这样带有随机性质的, 把这点作为无法避免的事情考虑进来的话,我们可以得出概率编程这样的一个方向。

如果一个编程语言的全部基本元素都带有了这样随机性的眼光,那么产生的东西可能是另一个我们未曾想过的新世界。现在就有类似 Blog 这样的一系列工作了。 前阵子忘了在哪个微信公众账号上也看到有我国的留学生(好像是格灵深瞳的实习生?)在 IJCAI2016 上发表了对概率编程的一个编译器 改进工作:1606.09242.

会写代码的代码

最近看到一个好像知乎上较出名的人在推特发问调查,问大家是否有需求能让人描述一段话,AI 就能自动帮我们写好代码。这个问题涉及到 这个功能的适用范围,以及自然语言和形式语言的讨论。但我觉得至少有一件事是可以做的,即用机器学习的方式来实现代码质量的分析, 代码的审计等。实际工作中人们会依赖一些软件工程的办法,或者说工程的办法,以保证代码上线的质量,以及出现问题以后的处理方式。 可能有公司会使用一些静态检查的工具,去发现如 C/C++/Java 等语言写的代码里隐藏的问题。也有如 Haskell / Rust 这样的尝试,把 一些代码缺陷在编译阶段就消除。但是,机器学习仍然可以视为一个简单有效的做法。以往,从带科学哲学的味道说,我们想看一段程序是否 有问题,唯一的办法就是去执行它。而对于是否有 bug 这样的问题,机器学习可以帮助我们以很大概率、以很小的工作量去发现可能有 问题的代码,对它来说就是将可能出问题的代码模式保存下来,这样节省了我们执行代码去发现问题的时间。

或许会真的有一天,AI 能帮助我们自动写软件。每次自动化的变革都会带来社会关系的巨大变化,我想我们不必为是否会丢掉工作而担心。 过去全中国几乎都是农民,如今农民的比例大大降低,人们也依然没有饿死也没有闲着,反而生活得更富足,工作得更起劲。以美国为例就 更明显了,现在美国的农民比例更是低于中国,而美国社会的富足程度也是远高于我们。大可不必担心如果 AI 能帮我们写软件我们会怎么办, 也不必担心 AI 帮我们写 AI 以后我们该怎么办,到时候总会有一些别的办法的。