《暗时间》读书笔记(三)
第三篇——跟波利亚学解题
波利亚,数学家。数学问题中运用的各种方法,又何尝不是我们解各种学术or生活中问题的思维方式呢?
波利亚在书中提到的一些启发式思考方法:
-
时刻不忘未知量(即时刻别忘记你到底想要求什么,问题是什么)
-
用特例启发思考(泛化的问题往往无从下手,可以通过特例寻求一般性解答)
-
倒推法(利用结论蕴含的知识,智能搜索解空间)
-
试错
-
调整题目的条件(通过增删、改变条件,来发现条件与结论如何联系)
-
求解一个类似题目(抽象出来)
-
列出所有可能跟问题有关的定理或性质
-
考察反面,考察其他所有的情况
-
将问题泛化,并求解泛化后的问题
-
意识孵化法(要求吃透问题,激发潜意识对其不停思考)
-
烫手山芋法
知识是一把双刃剑。如何在获取知识优势的同时,防止被知识束缚住?抽象。在吸收知识的时候抽象,对问题也是。对某一个对象,去枝减叶,从不同的抽象层面去掌握其知识信息,是启发思维和吸收知识的一大重要点。
知识!知识!
知识就是力量,这句话不是空穴来风。没有根基,任何探索都无法进行下去。作者提出了一个解题的好习惯,就是把解题的思考过程写在纸上。一步步的过程的记录,有利于记忆的缓存,更方便回溯和查错。因为每一个关键的一步或许都有另一种可能性,“一条路走到黑”是可以避免和修正的。这样,解题结束后,更有利于反思和总结收获。
练习!练习!
如果你手里有一把锤子,所有东西看上去都像钉子。
如果你想钉一个钉子,所有东西看上去都像是锤子。
鱼是最后一个看到水的
人倾向于在既有框架下去解决问题,在这个过程中,还很难察觉到框架约束的存在。
think out of the box
知其所以然
凡事多问几个为什么,探索背后的原因。
思考问题的时候,我们的大脑中其实有一棵搜索树来寻求解答,当问题很复杂时它会是很庞大的。这个探索的过程即时所谓的思维过程。“如果说问题求解是一部侦探小说,那么算法只是结局而已,而思考过程才是情节。” 揣摩如何得到这样的解答的,可以理解到更深刻的东西,透析问题本质,把握解题精髓。
知道怎么做是从正确(高效)解法得到的,而知道为什么必须得那样则往往是从错误(低效)的解法中得到的。
Y Combinator
这部分好好看了几遍,梳理一下如下:
首先,关于lambda的基本语法:
定义lambda函数:
lambda x y. x + y
或可以起一个名字:
let Add = (lambda x y. x + y)
调用该函数:
(Add 2 3)
不过在下面为了方便起见,还是会写成Add(2, 3)这种形式
在lambda中可以递归吗?
理想想法:
lambda n. If_Else n==0 1 n*<self>(n-1)
<self>是指函数本身,然而lambda算子系统里面的lambda表达式(函数)是没有名字的
第一次尝试:
let F = lambda n. If_Else n==0 1 n*F(n-1)
但是let F只是一个语法糖的作用(使对象便于阅读和表达),在它所代表的lambda表达式还没有完全定义出来之前是不可以使用F这个名字的
第二次尝试:
引入软件工程里面的一条黄金定律:“任何问题都可以通过增加一个间接层来解决。”
既然不能直接填入<self>,可以增加一个参数:
lambda self n. If_Else n==0 1 n*self(n-1)
为简单起见,用let语句给该语句起个别名:
let P = lambda self n. If_Else n==0 1 n*self(n-1)
然后调用:P(P, 3)
但是,这个函数包含两个参数,所以在If处需要修改需要修改:
let P = lambda self n. If_Else n==0 1 n*self(self, n-1)
调用时:P(P,3),即把自己作为P的第一个参数(此时P已经定义完毕了),这样self处就等于P本身了,也就实现了递归
不动点原理
虽然上面的例子成功了,但是很冗余。回顾那个失败的做法:
let P = lambda self n. If_Else n==0 1 n*self(n-1) (*)
调用P(P, n)时,里面的self(n-1)会展开为P(n-1)。不能使用。
下一步,假设一个“真正”的递归阶乘函数:power(n),这样可以把power传给P:
P(power, 3)
考虑一下power和P之间的关系,直接把power交给P
P(power)
What's this?It's called partial evaluation(函数的部分求值)。得到的是“还剩一个参数待给的一个新的函数”,可以发现P(power)即为power本身的定义。
P(power) = power
以上,对于函数P来说,power就是一个不动点。power此时是不存在的,如何找到它?
我们看到,对于伪递归的P,存在一个power,满足P(power) = power;注意,这里的伪递归是指(*)。对任一伪递归F(伪递归如何得到——是为了解决lambda函数不能引用自身的问题,于是给理想的f加一个self参数从而得到的),必存在一个理想f(F就是从这个理想f演变而来的),满足F(f) = f.
现在,问题归结为如何针对F找到对应的f。假设有一个神奇的函数Y,有
Y(F) = f
结合上面的F(f) = f,得到:
Y(F) = f = F(f) = F(Y(F))
不过如何构造这个Y又成了难题。
再次回顾伪递归的求阶乘函数:
let P = lambda self n. If_Else n==0 1 n*self(n-1) (*)
又根据不动点原理:
power = P(power)
如何实现一个能够自己调用自己的power?上面提到的方法,增加一个间接层:
let power_gen = lambda self. P(self(self))
递归调用:
power_gen(power_gen) ==> P(power_gen(power_gen))
现在,把power_gen(power_gen)当成整体看,不妨令为power:
power = P(power)
总结:对于给定的P,只要构造出一个相应的power_gen如下:
let power_gen = lambda self. P(self(self))
power_gen(power_gen)即为我们寻找的不动点
铸造Y Combinator
Y Combinator只要生成一个形如power_gen的lambda函数,然后把它应用到自身:
let Y = lambda F.
let f_gen = lambda self. F(self(self))
return f_gen(f_gen)
解释:Y是一个lambda函数,它接受一个伪递归F,在内部生成一个f_gen,然后把f_gen应用到它自身,得到的f_gen(f_gen)就是F的不动点了。而根据不动点的性质,F的不动点也就是那个对应于F的真正的递归函数。
哥德尔的不完备性定理
哥德尔证明出,任何足够强到蕴涵了皮亚诺算术系统(PA)的一致(即无矛盾)的系统都是不完备的,所谓不完备,也就是说在系统内存在一个为真但无法在系统内推导出的命题。
需要构造一个为真但无法在T内推倒(证明)的命题。
N(n) is unprovable in T
其中n是自由变量,为一个自然数,一旦给定,公式就变成一个明确的命题。N是一个公式(哥德尔的证明第一部分就是把公式编码)。“is unprovable in T”是可以用形式语言表达的:一个形式系统中的符号数目时有限的,它们构成这个形式系统的符号表;依次枚举所有串,列举所有良构的公式(well formed formula,简称wff);这样,枚举出所有可由T推导出的定理;本质上,该句即为“不存在一个自然数S,它所解码出来的wff序列以X为终结”。
哥德尔的公式变成了:
UnPr(N(n))
首先我们把这个公式简记为G(n)——由于G内有一个自由变量n,所以G不是命题,谈不上真假:
G(n): UnPr(N(n))
又由于G也是wff,所以它也有自己的编码g,当然g是一个自然数。现在我们把g作为G的参数,也就是把自由变量n替换为g,我们得到一个真正的命题:
G(g): UnPr(G(g))
这就证明了哥德尔的第一不完备性定理。还有一个推论,称为第二不完备性定理:
任一个系统T内无法证明这个系统本身的一致性。
从哥德尔公式到Y Combinator
回顾哥德尔命题的构造:
G(n): UnPr(N(n))
其中UnPr可以泛化为一个一般的谓词P:
G(n): P(N(n))
也就是说,对于任意一个单参的谓词P,都存在上面这个哥德尔公式。然后算出这个哥德尔公式的自然数编码g并把它扔给G,就得到:
G(g): P(G(g))
这个形式也就是Y Combinator的构造,精妙绝伦!
对角线方法——停机问题的深刻含义
通过对角线方法证明图灵机无法判断所有的停机行为:
从反证开始,假设存在这样一个图灵机,能判断任何程序在任何输入上是否停机。由于所有图灵机构成的集合是一个可列集,所以可以自然列出下表,表示每个图灵机分别在每一个可能的输入下的输出,N表示无法停机:
1 2 3 4 ...
M1 N 1 N N ...
M2 2 0 N 0 ...
M3 0 1 2 0 ...
...
Mi表示编码为i 的图灵机,第一行代表的是输入数据。我们刚才假设存在这样一饿图灵机H,它能够判断任何程序在任何输入上能否停机,换句话说,H(i,j)能够给出“Mi(j)”是N还是给出一个具体结果。
现在运用康托尔德对角线方法,构造一个新的图灵机P,P在输入i上的输出根Mi(i)不一样。构造出的图灵机P如下:
P(i):
if (H(i, i) == 1) then // Mi(i) halts
return 1 + Mi(i)
else // if H(i, i) == 0 (M(i) doesn't halt)
return 0
现在,我们注意到P本身就是一个图灵机,而我们上面已经列出了所有的图灵机,所以必然存在一个k,使得Mk = P。而两个图灵机相等当且仅当它们对于所有的输入都相等,也就是说对于任取的n,有Mk(n) = P(n),现在令n=k,得到Mk(k) = P(k),根据上面给出的P的定义,这实际上就是:
Mk(k) = P(k) =
1+Mk(k) if Mk(k) halts
0 if Mk(k) doesn't halt
这个式子显然矛盾。于是我们得出,不存在那样的H。
这个对角线方法实际上说明了,无论多聪明的H,总存在一个图灵机的停机行为是它无法判断的。这跟哥德尔定理“无论多‘完备’的形式化公理系统,都存在一个‘哥德尔命题‘是无法在系统内推导出来的”从本质上其实是一模一样的。
数学之美番外篇
快排为什么那样快
不论是猜数字,还是称球,我们选取策略的本质都可以概括为“让未知世界无机可乘”。它是没有弱点的,答案的任何一个分支都是等概率的。
称球问题有一个更本质的思路。
回顾猜数字游戏,为保证任何情况下以最少次数猜中,我们的策略是每次都排除恰好一半的可能性。类比到称球问题上:坏球可能是12个球中的任意一个,这就是12种可能性;每种可能性下坏球可能轻也可能重。于是答案有24种可能性。用天平称球,就等同于对这24种可能性发问,由于天平的输出结果有三种“平衡、左倾、右倾”,这就相当于可以讲所有的可能性切成三份。如此,理论上三次是完全可以称出来的。
而关于快排为什么那么快。可以设想有n个数字,它们的排列方法为n!种。基于比较算法的话,每次比较得出两种大小关系,最理想的情况是把解空间去掉一半。所以它的复杂度为log(2)n!,即nlog(2)n。
信息论
关于信息论,重要的是一种看问题的本质视角:看排序和猜数字问题,都是通过问问题来缩小/排除(narrow down)结果的可能性空间,这样一来就会发现,“最好的问题”就是那些能够均分所有可能性的问题,因为那样不管答案如何,都能排除掉k-1/k种可能性。
平凡而又神奇的贝叶斯方法
贝叶斯方法是由“逆概”引出的。背后的深刻原因在于,现实世界本身是不确定的,人类的观察能力是有局限性的。这个时候,我们就需要提供一个假设。1、算出各种不同猜测的可能性大小;2、算出最靠谱的猜测是什么。
公式:
P(B|A) = P(AB) / P(A)
关于拼写纠正
对于一个词库中不存在的单词D,如何判断用户想打的是什么单词(可能是h1,h2,,,)?
我们需要计算 P(h|D), 运用贝叶斯公式:P(h|D) = P(h) * P(D|h) / P(D)
由于P(D)是一样的,可以忽略。所以 P(h|D) 正比于 P(h) 和 P(D|h) 。抽象含义是:对于给定观测数据,一个猜测(假设)是好是坏,取决于“这个猜测本身独立的可能性大小(先验概率,Prior)”和“这个猜测生成我们观测到的数据的可能性大小(似然,Likelihood)”的乘积。
模型比较与奥卡姆剃刀
为什么不能只看最大似然?第一,不能提供决策的全部信息;第二,猜测本身的可能性也许就非常低。
观测数据总是有各种误差,所以如果过分寻求能够完美解释观测数据的模型,就会落入所谓的数据过配(overfitting)的境地,一个过配的模型试图连误差(噪音)都去解释(实际上噪音又是不需要解释的),显然就过犹不及了。
所谓奥卡姆剃刀的精神就是说:如果两个理论具有相似的解释力度,那么优先选择那个更简单的(往往也正是更平凡的,更少繁复的,更常见的)。
过分匹配的另一个原因在于当观测的结果并不是因为误差而显得“不精确”,而是因为真实世界中对数据的结果产生贡献的因素太多,不是你的模型(仅抽取了几个重要因素)所能解释的
无处不在的贝叶斯
- 中文分词
- 统计机器翻译
- 贝叶斯图像识别
- EM算法与基于模型的聚类
聚类是一种无指导的机器学习问题,问题描述:给你一堆数据点,让你将它们最靠谱地分成一堆一堆的。例如书中举的例子,就是根据给出来的那些点,算出这两个正态分布的核心在什么位置,以及分布的参数是多少。但是怎么分?EM算法就是,先随机整一个值出来,再根据变化调整,不断迭代相互推导,最终收敛到一个解。
- 最大似然与最小二乘