How to Design a DL Algorithm
我看到很多人将现代DL的成就完全归功于cuda和GPU带来的算力提升,而完全忽略了现代DL在算法设计上的进步,我认为这是极为荒谬的。对于这类观点,一个明显的反例就是LSTM无法scaling,但是transformer可以。从现代深度学习模型发展的趋势(MLP->CNN/RNN->LSTM->transformer)来看,更好的模型往往是更加复杂的模型,因为这些更加复杂的模型可以允许我们做更多有趣的事情。
其实这是相当好理解的一个事情。请回忆依赖于比较的排序问题(也即通过比较元素之间的大小来进行排序的算法),这类算法的时间复杂度的下界是$O(n\log_2(n))$。这告诉我们,永远都不可能找到时间复杂度为$O(n)$的比较排序的算法(存在$O(n)$的非比较排序算法,如桶排序),但是我们可以轻易的找到$O(n^2)$解决方案(冒泡排序)。我们可以看到,对于一个给定的任务,不同的时间复杂度的算法都能成功的解决,区别主要在于效率。
这是确定性的算法设计中的思维定式。当我们用数学手段建模了问题之后,剩下的就是优化算法,使得算法尽可能的快(也许正是这种思维导致了很多人希望能找到更简单的DL算法)。我们很难触及到算法复杂度的下界,所以一般也不会考虑算法复杂度太低的情况。但是对于DL算法来说,我们不知道我们具体想让模型去做什么,我们不知道这些任务具体的时间复杂度需要多少,我们甚至不知道这些问题的解是否存在,我们只能通过SGD让模型自己在杂乱的函数空间中游荡,企图找到对应的解决方案。我的观点是:既然这样,如果模型不知道自己要找什么东西,为什么不把他们放到一个更大更复杂的空间当中呢?
大部分的人都害怕向模型加入复杂度,因为传统机器学习理论告诉我们,当我们的模型的复杂度和问题的规模相匹配的时候模型才能泛化。因此当我们试图加入复杂度的时候,试图将模型放到更大更复杂的空间中的时候,模型总是会过拟合。但是这种理论无法解释高复杂度的DL算法展现出来的强大泛化性能。NN总是知道哪些函数是更加有价值的。对此,Ard Louis在他关于神经网络泛化性由来的研究中给出了一个相当合理的解释。这份工作告诉我们:尽管NN是一个高复杂度的模型,但是其拥有相当强大的归纳偏置,在优化的过程中,NN总会倾向于寻找低复杂度的函数(需要注意这和SGD本身没太大关系,因为NN可以通过无梯度方法优化)。也就是说,尽管NN的复杂度较高,但是当我们将其优化好之后,NN内部拟合好的函数总是低复杂度的。
这实际上就是告诉我们,不需要担心神经网络过度的复杂化。当我们需要设计一个新的DL算法时,只要能同时做到两件事情就能做到泛化:
- 增加模型的复杂度。
- 加强或者至少保持模型内部对于低复杂度函数归纳偏置。
不过,这两点仅仅是看起来简单,实际上做起来非常的困难。因为我们很难确定什么样的结构可以加强模型对低复杂度函数的归纳偏置。但是通过对经验的总结和基于理论的思考,我认为在设计时至少可以从以下两点来进行考虑:
- 增强模型的稀疏性:模型的复杂程度与一般与模型的稀疏程度反相关,模型的稀疏性越高,其复杂度就有可能越低。典型的一个例子是MoE和Relu。Moe这种方法虽然加入了一个路由层来确定每次使用的专家块,但是每次选择的专家块较少,反而使得NN最后的激活更加稀疏。
- 使得模型内部包含更多的子网络:这种方法的目的是形成一种隐式的集成,典型的案例是Dropout和ResNet,这两种方法实际上使得NN在训练的过程中集成了多个小的子网络。
以上提到的两张方法实际上存在交叉关系,例如我们同样可以认为MoE是一种子网络的集成。
我想现在我的观点已经很清楚了。对于DL算法的设计来说,我们应当试图让模型变得更复杂,但是于此同时,我们要保证模型内部的归纳偏置不被破坏,因为我们需要使得模型找到合适复杂度的算法来解决问题。当你做好了这些,再把你的数据放心的交给住在GPU里的模型吧。