王辉的博客

是什么让我对未知世界始终充满热情?

当下还有什么比机器学习,深度学习,人工智能更火?除了教机器学习,不妨也教教自己。拥有一个高效的大脑,可以帮你学习一切知识。所以我上了Coursera上至今最火的一门课(顺利拿到证书):学习如何学习。下面分享几个非常有用的技巧。

本文略长,如果一次看不完,建议你回头再来,因为每个技巧都很重要。

为什么学习如何学习?

高中毕业,流行互赠留言。有人认为我是一个真心享受学习的同学。当时的快乐,来自好成绩和别人的好评。如今,三十多了,早已没有考试,却比以往任何时候都更爱学习,因为学习让我能挖掘藏的很深的美丽。你学习的动力是什么?

注重过程而非结果

这是一个容易激起辩论的话题:到底是过程重要,还是结果更重要?不同的情景,有不同的答案,在学习这个领域,过程更重要。

首先,专注于过程,可以减少心里打击,治愈拖延症。对于人脑来说,学习并不是一件让它感到舒服的事情,毕竟你在绞尽脑汁思考一些从来没有思考过的问题。万一你学习的新知识,很难,比如深度学习,你一时半会领会不了,就会有一种挫败感。一旦有了这种心理打击,你就很有可能患上拖延症,找各种借口逃避啃硬骨头。这都是注重结果的弊端。因为你过于关注是否学会,而忽略了微小进步。注重过程,忽略结果,就不会有这种烦恼,因为你压根也没期待着掌握些什么,只要把时间用够就行了。

其次,专注于过程,更有利于知识的积累,培养长远目标。俗话说的好,一口吃不成胖子。暗示事物的发展是循序渐进的。很多知识复杂度极高,从本质上来说,不可能一下子都被消化掉。所以在处理这种知识的时候,注重过程,可以鼓励我们建立长远目标,一点一滴的积累。道理,就不多说了,古文“劝学”讲的不能再清楚了。从实践上来讲,比如一年前,当我在法国最佳程序员比赛里获奖的时候,我就突然意识到积累的效果显现了

当下,我仍在积累,每天花一到两个小时做程序员面试金典书中的算法题。请注意,这是注重过程而非结果的核心。我没有要求自己每天要做出来一道,而是说花一到两个小时,做到哪是哪。时间用完了,有可能做出来了三道,也有可能一道都没做出来。如果你也喜欢算法,和我一块积累吧,来我的Github算法仓库

休息并非浪费时间

有一次,我练一个算法题(132模式),苦思冥想,不得其解。一气之下就去游泳了。在用泳池里吐气的时候,灵感突然上来了,游完回去直接给搞定了。

我所描述的这个经历,是“学习如何学习”中非常核心的一个概念。就是大脑的专注模式和分散模式。当你苦思冥想的时候,你使用的是大脑的专注模式,这种模式有利于高相关度的概念,但很容易让你陷入思维的孤城,无法调兵遣将。在大脑的分散模式下,你没有特殊的目的,悠闲的徜徉于你大脑的各种分区,灵感更容易在这种条件下闪现光芒。

更为神奇的是,大脑会在你睡觉的时候,充当整理员的角色。它会打包处理你白天学到的知识,归类,总结,清洗,甚至碰撞出新的火花。德国有机化学家,凯库勒梦见猴子,创造苯的环状结构,是科学史的一段佳话。

知识碎片

开发一个知识碎片,就好像是学习一个泳姿。当你学习自由泳的时候,老师会一步一步的教你,打腿,划水,换气。而当你成为自由泳能手的时候,所有动作一气呵成,你根本不会再去想细节。如果你很有天赋,成为了混合泳高手的时候,什么蛙泳,仰泳,蝶泳都成为了你的知识碎片。知识碎片,就是有这样的魔力,我们可以通过不断的练习,把复杂的技能,概念打包转换成你直接可以消费的小单元。小单元一旦信手拈来,你就可以它们为基础再创建新的更大的碎片。

谈到这里,我想拿个算法举例,要不大家以为我是游泳运动员呢。比如说你通过勤学苦练掌握了二叉堆(这是我目前最喜欢的数据结构),把它用的得心应手。当你在学习最小生成树的时候,二叉堆就成了你的一个知识碎片,稍加功夫,你就能学会Prim算法。当你把最小生成树也用的炉火纯青的时候,如果你遇到了一个分堆(clustering)的问题,最小生成树就成了你的知识碎片。所以这是为什么,作为程序员,你要投资数据结构,积累你的知识碎片。

“我学会了”的假象

这个假象,是人们自我安慰的一个体现。你看完了概率论的一本书,觉得知识都学会了。可没过多久,你就把贝叶斯条件概率的公式给忘了,你又回去重温了一遍,告诉自己这次是真的学会了。那现在给你个挑战,看看你能不能把下面的这个概率算出来。

说,你有一个袋子,里面装了两枚硬币。其中一个是正常的,一个不正常。你闭上眼从里面拿一个,它是正常硬币的概率是50%,不正常的也是50%。正常硬币,投掷一次,得到正面反面的概率各50%;不正常的,有40%的概率得到正面,60%的概率反面。现在,你闭上眼从中取了一个硬币,投了一次,拿到了正面,问这个硬币是正常硬币的概率。

如果这个题的解法,对你来说没那么明显,那么我强烈建议你使劲的回想你概率里学过的知识,而不是回头去翻公式。如果你能回想出来,那么说明你真的掌握了,否则你之前是给了自己一个学会了的假象。

真正的学会一个知识,不能只靠被动的去看,去听。而是要主动的去回想,去创造,或者看你能不能把你的知识传授给别人。有一次在知乎上一个人问有哪些优秀的Java开源项目最值得阅读?,我的第一反应,就是这种人很容易就陷入“我学会了”的假象,别人的代码读的再多,自己的大脑没有用力的思考挣扎过,那还是别人的东西。

总结

学习如何学习,是可以让你受用一生的技能,特别是对爱学习的你来讲。技巧先分享到这,记住千万不要给你“我学会了”的假象,实践出真知!

在看吴军老师的数学之美之前,我已经开始意识到数学的重要性,前面写了一篇计算机中的数学。迫不及待的看完之后,收获巨大,特别是最后明白了吴军老师写作目的的时候,备受冲击。吴军老师希望我们做软件不要再凑了,而是从数学中找简洁的方法。

书中有好几个问题都讲的特别精彩。比如搜索问题。吴军老师在讲解问题的时候,更注重技术中的“术”的部分,所谓“术”,就是认识问题的角度,解决问题的思路。而“技”强调的是在找到方案之后,如何把方案付诸于实践。拿搜索来讲,搜索面临两个问题,一个是页面和关键字的匹配问题,一个匹配成功的页面之间的页面排名问题。比如说,你搜数学之美,搜索引擎会首先筛选出和这个问题相关的页面,然后在这些筛选出来的页面里给你提供最有可能让你满意的页面。能把搜索问题,提炼成上述的两个小问题,就是“术”的部分,和“技”无关。

如果你的任务,是设计一个页面排名的方案,你会怎么设计?真的推荐你想一想,看看你是用凑的方式,还是会从数学中找到灵感。如果是凑的话,你会怎么凑。在我读这本书之前,我只知道凑。我的思路可能是设计一个打分函数,对每个页面进行打分,分越高越好。其中打分的部分包扩,页面字数的多少,字数多的,讲的就更深;还有文章的作者,越出名的作者,写的文章质量越高。

百度的李彦宏,谷歌的拉里佩奇都思考过这个问题,并且还写了论文,开了公司。这本书里吴军老师讲了拉里佩奇的PageRank算法,里面用到了图论,线性代数,概率论,收敛的证明,分布式计算MapReduce。

搜索问题,只是其中的一个例子,如果你想知道语音识别是怎么设计的,信息论在其中扮演了什么角色,如何摆脱凑的局面,那么这本书会给你带来很多启发。

如何找带环的单链表里的环入口点?这是一个非常有意思的问题,解完之后,会让你感觉算法很美,很享受。其实已经有很多人谈这个问题的解法了,这里我主要想结合Canvas动画,让你更生动形象的感受这个问题的美妙。

具体问题是,给你一个含有环的单链表,求环的入口点。比如我们有一个链表,从0一直链到16,然后16转过身来又链到了5。这个链表里,5到16组成了一个环,环的入口点是5。

我在解这个问题之前,知道如何判断单链表是否有环,利用的是龟兔赛跑法。所谓龟兔赛跑,指的是用两个指针,兔子(蓝色)每次移动两个节点,乌龟(红色)每次一个,如果有环的话,兔子和乌龟有朝一日都会进入这个环。由于兔子比乌龟跑的快,他们在环里,肯定会相遇。

知道了如何判断是否有环,离找到环入口点就不远了。其实环的入口点,和他们在环里的相遇点是有关系的。现在停下来想一下,他们会在哪里相遇?如果想不通的话,就多跑几下上面的Canvas动画。

下面是用Java解的这道题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
LinkedListNode detect(LinkedListNode head) {
LinkedListNode runner = head;
LinkedListNode walker = head;
while (runner != null && runner.next != null) {
runner = runner.next.next;
walker = walker.next;
if (runner == walker) break;
}
if (runner == null || runner.next == null) return null;

walker = head;
while (runner != walker) {
runner = runner.next;
walker = walker.next;
}
return runner;
}

我想结合两个概念来谈职业规划,一是服务精神,另一个是技术。我想说明的结论是,技术需要服务引领,服务需要技术驱动。一旦理解他们之间的联系,职业规划就很容易谈了。

为什么要结合两者谈?首先,作为一个热情洋溢的程序员,我工作已有六年,主攻低延迟,高性能的金融市场数据系统。对技术的追求是执着的,除了白天敲代码,业余还参加各种算法比赛,上公开课,写技术博客。因此,不可能避技术不谈。其次,我开始意识到服务精神的重要性,我体会到培养服务精神,有利于超越自我,有利于搭建反馈循环,让别人喜欢用你做的产品,给你带来快乐。

技术的困境

技术,对于一个程序员来说,是一件美好,令人着迷的东西。你看看知乎上,有人问,有没有一段代码,让你觉得人类的智慧也可以璀璨无比?, 还有人问你认为最优美的数据结构是什么?。就像欣赏一件艺术品一样,你可能深深的折服于某个拥有对数复杂度的数据结构,你可能会享受微妙级低延期给你带来的快感,你可能自豪于你所向无敌的智能战斗机器人。

技术是一个有魔力的东西,可以给你带来快乐,但它也有困境。当你倾注了你的青春开发出的产品没人用的时候;当你写了上万行代码却未得到一个赞美之词的时候;当你面对着层出不穷的技术怎么学也学不完的时候;你会无助,困惑,感叹什么时候是个头。

这是技术的无力的一面,技术的困境。为什么?因为我们过分的看重技术,以为事情的成败,取决于技术的优劣。以为只要我学的越多,我就能立于不败之地。

服务精神

深爱技术的人,往往特别单纯。为了技术信仰,可以挣的面红耳赤。我曾经就有过类似的经历,当时以为是不善沟通,并且还参加了一个非暴力沟通的培训,其实是忽略了一个更深层次的原因,就是缺乏服务精神。

所谓服务精神,在我看来是一种意识,一种指导思想。就是当你做事情的时候,是不是先想到你服务的对象,是不是想着如何让它们满意。在服务行业里,这很容易辨别,不管是在餐馆,理发厅,还是水果店,你都知道你服务的对象是谁,因为他们会出现在那里,并且你能从他们的是否有笑容中判断他们满意了吗。

而在软件行业里,很多程序员接触不到最终客户,因此往往易缺乏这些意识,更糟糕的是,有些程序员认为他们的服务对象是技术不是人,体现就是他们不善解人意。

技术和服务

最近对我感触比较大的一件事是云栖大会上宣布成立的达摩院。马云说,阿里巴巴要有社会责任感,要把阿里的技术,阿里发展带来的红利更好的服务于社会,服务于人民。在别人看来,好像在说大话。可这些话却深深的触动了我。对于一个在服务精神上开始觉醒的人,这无疑是一颗定心丸。当一个人,一个企业,都要富可敌国的时候,仍然想着要服务别人,对于普通的我呢。

从另外一个角度来看,达摩院的第一批员工有谁?他们都是清一色的技术人员,都是在各自领域出类拔萃的人才。为什么?因为他们要解决的问题,是全社会,全人类都关注的问题。我们服务的对象越高,所要求的技术水平也就越高。

在说一下第三点,就是达摩院的盈利模式。他们不是漫步目的的搞科研,他们要解决实际问题,解决实际问题,服务于他人,既能带来利润,又能带来快乐。所以,只有技术,没有服务,也是生存不了的。

这就是我开篇的时候所说的,技术需要服务引领,服务需要技术驱动。

职业规划

在我们理解好了技术和服务之间的关系之后,我们在职业规划上就不难做出正确的决定。

如果你是一个热衷于技术的程序员,那么不要纠结这条路能走多远,不要顾虑要不要做管理。因为如果你立志接解决当今社会所存在的问题,你必将找到你的用武之地,看看达摩院的那些技术流们。

如果你还在忧虑,好多新东西学不完,那么请把你的关注点放在如何提高你的服务质量上,他可以更好的知道你该如何学习,学什么。

二分法(BinarySearch),是一个简单却很容易写错的算法。据说,Jon Bentley(k-d tree的发明者)做过一次调查,90%的程序员,花上几个小时,也写不出一个正确的二分算法。我写了几个,发现成败在细节上。后来总结出了一个经验,就是把重点放在二分法那个擦肩而过的美好瞬间。下面通过几个例子来体会。

找寻插入位置

1
2
3
4
5
6
7
8
9
说,你有一个整数数组,已经排序完毕,升序,并且没有重复的元素。
问,给你一个整数,求它在数组中的插入位置。

拿[1, 4, 7, 8]来说,
0 -> 0,
2 -> 1,
5 -> 2,
7 -> 2,
9 -> 4

我写了如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int searchInsert(int[] nums, int target) {
int start = 0;
int end = nums.length - 1;
while(start <= end) {
int mid = start + (end - start) / 2;
if(nums[mid] == target) {
return mid;
} else if(target > nums[mid]) {
start = mid + 1;
} else {
end = mid - 1;
}
}
return start;
}
}

确保相遇

当start比end小或相等的时候,我们都继续找,而当start超过end,他们擦肩而过的时候,我们停止循环。为了避免他们停滞不前,每次我们要么增加start,要么减小end,确保他们擦肩而过。

擦肩瞬间

由于每次要么start增加,要么end减小,只有一方会发生变化,必定有一个瞬间,他们相遇落在同一个位置上。这次相遇是最令人心动的地方,也是该算法,最需要注意的地方。请问在上述代码中,从while循环出来的时候,为什么返回的是start,而非end?请认真思考一下,再看我的分析。

让我们把注意力放在他们擦肩相遇的瞬间,start和end站在了同一个位置,start看一下目标值和眼下的值,如果发现目标大,start往前一步,超越了end,新的start必定是插入的正确位置;如果发现目标小,start让end走一步,start留在了原地,正好占用当下的插入位置,因为要插的值正好比当下的小。

是不是这个瞬间很美好?

求平方根

1
2
3
求整数的平方根,结果要整数。比如:
sqrt(16) = 4,
sqrt(15) = 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int mySqrt(int x) {
if(x == 0) return 0;
if(x == 1) return 1;
int start = 1;
int end = x / 2;
while(start <= end) {
int mid = start + (end - start) / 2;
int square = mid * mid;
if(square == x) return mid;
if(square > x || (square / mid != mid) ) {
end = mid - 1;
} else {
start = mid + 1;
}
}
return end;
}
}

为什么出了while循环,我们返回的是end?擦肩而过的瞬间发生了什么?

求区间的头和尾

1
2
3
说,有一个整数数组,按升序排序完毕
求,目标值得首次和最后出现的位置,找不到返回[-1, -1]。时间复杂度必须控制在o(logn)
比如在[1, 2, 3, 3, 3, 4, 4]求3的区间,答案是[2, 4]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
public int[] searchRange(int[] nums, int target) {
return new int[]{
searchLow(nums, target), searchHigh(nums, target)};
}

private int searchLow(int[] nums, int target) {
int start = 0;
int end = nums.length - 1;
while(start <= end) {
int mid = start + (end - start) /2;
if(target <= nums[mid]) {
end = mid - 1;
} else {
start = mid + 1;
}
}
if(start > nums.length - 1) return -1;
if(nums[end + 1] == target) return end + 1; //回头看
return -1;
}

private int searchHigh(int[] nums, int target) {
int start = 0;
int end = nums.length - 1;
while(start <= end) {
int mid = start + (end - start) /2;
if(target >= nums[mid]) {
start = mid + 1;
} else {
end = mid - 1;
}
}
if(end < 0) return -1;
if(nums[start - 1] == target) return start - 1;
return -1;
}
}

你能体会到擦肩而过的美妙吗?在找区间头的过程中,end在相遇的位置上看了一下目标值和眼下值,如果发现目标值小或正好,end就会往前走一步,翻过start。最后end回头看,那么相遇时的位置,要么区间头找到了,要么目标值不存在。

总结

二分法,是有规律可寻的,首先就是要确保start和end总有一方前进,进而确保擦肩而过跳出while循环。其次就是根据需求,决定在相遇的瞬间,是start还是end动。掌握了这个规律,你定能写出来正确的二分法。

法国工程师学位的我,后台开发六年有余。如今深深爱上了数学,感慨以前学得不够。

首先,无论是哪方面的程序员,都要大量的投资算法和数据结构。因为这是你的基本技能,就像棋手需要知道每个棋子的走法一样。在算法学习的过程中,你绕不过的就是衡量算法的效率,也就不可避免的学习Big O的知识。Big O,需要你了解函数和极限的一些知识,比如说,f(n) = O(g(n))是这样定义的,当n足够大的时候,你总可以找到一个常数c,使得c * f(n) >g(n)。除了效率,还有如何证明算法的正确性。这就要求掌握一些证明方法,比如反正法,递推归纳法。

其次,如果你遇到了性能方面的问题,需要降低延迟,增加吞吐的时候,你很有可能需要去设计高性能的队列系统,这时排队论的知识就显得尤为重要了。排队论要学好,概率论是基石。队列在计算机系统中随处可见,操作系统中的进程调度问题,基于队列解藕生产者消费者的架构问题等等。

接下来如果你对离散优化问题感兴趣,如说背包问题,旅行者问题,当你需要优化一个目标函数的时候,你往往会用到梯度下降法,而理解梯度下降法,对微积分要有很好的理解。更不用说在人工智能,机器学习领域,到处都得优化目标函数,降低预测误差,想走的远,是必须要有数学基础的。

然后再说一下,线性代数,特别是在人工智能里面,很多基本的模型都是线性的,你会随处看到矩阵的用处,单纯的只知道矩阵如何相乘是远远不够的,要理解的跟深入,矩阵是一个函数,用来转换矢量,既然是函数,就会具有很多函数的特点。你应该懂得如何证明 (矩阵甲 x 矩阵乙)x 矩阵丙 = 矩阵甲 x (矩阵乙 x 矩阵丙)

最后,说点我也没有搞明白的,分布式系统中的鼻祖级论文,Lamport的时间,时钟,和事件的顺序据他所说是他从相对论中的时间和空间的关系中得到的灵感。由此可见,不仅是数学,物理也很重要。

总结一下吧,当时是学生的时候,学数学不知道怎么用,只知道考试,回想起来 ,如果应用场景更清晰的话,我会投入的更多。

数学学习资源

入春以来,鼻子痒,堵,流鼻涕,像感冒一样,其实是过敏。看了个耳鼻喉的专家,她的一番话,竟让我联想到了软件开发中的测试问题。就像Dijkstra说的,测试只能表明Bug的存在,而非它们的绝迹

过程是这样的,我做了一个抽血实验,指标说明我没有过敏。医生很耐心的跟我讲,抽血结果说你没过敏,并不代表你没过敏。因为抽血化验只针对一部分过敏原。结果是好的,只能说明你对这些物质没有过敏反应。并不能代表你对其他东西也不过敏。像什么狗毛了,猫毛了,花粉了什么的,并不在化验的范围内,所以你有可能对他们过敏。也没有必要做更多的测试,因为很可能你又测了十几种,还是没找到过敏原,也只能证明对这些测试的东西没反应,而说明不了全部。

话,绕来绕去,就是告诉我,我知道你对哪些东西不过敏,不知道你对什么过敏。这就像软件测试一样,你写了一个测试,我们只能知道,在这种情况下,软件会好用,但并不知道,在哪些情况下不好用。

我举个简单的例子,比如下面的冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BubbleSort implements Sorting {

@Override
public <T> void sort(T[] elems, Comparator<? super T> comp) {
for (int out = elems.length - 1; out > 1; out--) {
for (int in = 0; in < out; in++) {
if (lessThan(elems[in + 1], elems[in], comp)) {
swap(elems, in, in + 1);
}
}
}
}
}

针对下面的输入,都能排好

1
2
3
Arrays.asList(2, 3, 4, 8, 5, 7, 1, 6);
Arrays.asList(2, 8, 3, 4, 5, 7, 1, 6);
Arrays.asList(5, 8, 4, 3, 2, 7, 1, 6);

但有一种情况排不好,你能看出来是为什么吗?

1
Arrays.asList(2, 3, 4, 8, 5, 7, 6, 1);

医生说,没必要做所有的测试,因为成本太大,并且不一定找到好的结果。但对软件测试而言,有没有好的办法呢。我是一个热情洋溢但比较笨的工程师,所以用了一种比较笨的方法,就是随机产生很多测试,暴力解决问题,这里你可以看得到,后来才意识到,在这种情况下,大家都在用一种叫基于属性的测试方法:Property-Based Test。有没有更暴力的呢,还真有,There are Only Four Billion Floats–So Test Them All!

一个过敏测试,一个软件测试,看上去两个没有联系的东西,可认真比较后,却惊喜的找到了他们的共同点。类比之后,反而对Dijkstra的那句话有了更加深入的理解。

0%