1.1 数学与机器学习
用向量、函数和梯度建立理解深度学习的数学坐标系。
1.先聊聊深度学习到底是个啥
当你看到这篇文章的时候,你大概率已经在新闻里听过无数遍深度学习这个词了,什么AlphaGo下围棋干翻柯洁啦、某大学生又拿codex写了个什么小游戏啦,这些事背后基本都有深度学习的影子。那么在我看来,深度学习(我们所谓的Deep Learning)其实就是机器学习里最nb的一个分支,而机器学习又是人工智能里专门负责让机器自己从数据里学本事的那一块。
想要精通深度学习很困难,但是如果只是学会基本概念的话并没有那么复杂。我希望通过这个系列的文章,来和大家一起入门一下深度学习的理论知识。
我们先从一个现象来引入吧。以前我们让计算机干活,得手动去把规则一条一条写死,写一堆if指令。比如说我们要写个程序判断一封邮件是不是垃圾邮件,你得手动列出一堆判断,比方说只要正文里出现中奖两个字、再带上一个可疑链接,就判定它是rubbish。但是问题很明显,现实太复杂了,你能想到的规则永远写不全,垃圾邮件稍微变一下就能跑到你的邮箱里面来。
机器学习换了个完全不同的思路。你不再去写规则了,你直接给机器看一大堆例子,几千封提前标好正常或者垃圾的邮件甩给它,让它自己去总结规律。它总结出来的那些规律,就藏在模型那一大堆参数里头,家底贼厚。深度学习干的其实就是同一件事,只不过它用的模型更加大、更加复杂,结构上有点模仿人的大脑神经元网络,所以说我们管他叫神经网络。至于深度这个词,指的就是这个网络摞了很多很多层。
那这些层到底在干嘛?先不要着急,我们一层一层来说。
先回答一个更基本的问题:模型到底是个什么东西?
2.模型其实就是一个函数
模型这个词听起来挺唬人,呃其实说白了,它就是一个函数。你给它一个输入input,它吐给你一个输出output,就这么简单。
我们把输入记作 ( 就是输入,比如一个学生每天复习的小时数),把输出记作 ( 就是输出,比如模型预测出来的期末分数)。模型内部那一大堆可以反复调整的数字,统一记作 ,叫作参数。整个模型写成 ,意思就是在参数 的安排下,对输入 做的那一番计算。
那训练到底在干嘛呢?训练就是反反复复地微调这堆参数 ,让模型在面对见过的样本时,给出的预测越来越贴近真实情况。这里说的样本,由两部分拼成,一部分是输入 ,另一部分是它对应的真实目标值 ,下标 表示这是第几个样本(比如 是张三的数据, 是李四的数据)。模型对着 算出来的预测值,记作 ,头顶上那个小帽子 是专门用来把预测值 和真实值 区分开的。
预测值跟真实值一般来说不会一模一样,它俩之间差了多少,总得有个度量衡来量一量。我们管这把尺子叫损失函数,记作 。损失越大,说明模型这次错得越离谱。整个训练的全部目标,说穿了就是想方设法让损失往小了走。
事实上让机器靠调参数、从数据里找到规律这件事,最早可以追溯到上世纪五十年代的感知机。那会儿的研究者就琢磨,能不能让机器跟人一样靠着不断地试错来提高模型。
3.为什么需要向量和矩阵
光拿一个数去描述一个学生,肯定不够用。你想想看,你总不能只盯着复习时长就去预测分数吧,作业是自己写的还是g哥包哥做的、上课的时候在打农打火打铲还是在认真听、又或者最近一次小测考了多少,可能都有用。于是我们就得把好几个数组织到一块儿,排成一队,这就成了一个向量。向量就是有顺序的一排数,它的顺序很要紧,第一个位置永远放复习时长,第二个位置永远放作业完成度,谁也不能乱窜。向量天生就适合用来描述一个样本的方方面面。
等我们手头有一大堆学生要一起处理的时候,就把很多个向量上下摞起来,变成一张数表,这就是矩阵。矩阵是按行和列排好的数表,每一行是一个学生,每一列是一种特征。矩阵除了能装样本,还特别适合装一整层网络的全部参数(一层网络动不动就有成千上万个数字,用矩阵来管让人稍微放心一点)。
矩阵乘向量这个运算,后面的文章又会反反复复说好几次,所有我们争取现在就把他讲透。当矩阵 乘以向量 的时候,矩阵会把输入重新搅和一番,吐出一个新的向量。矩阵的每一行干的都是同一件事,把它和向量 对应位置的数相乘再加起来,得到新向量里的一个数。你可以把矩阵的每一行想成一份配方,规定了原来那些特征各取多少、怎么调配,从而榨出一种新的组合特征。
还是一样我们举一个小例子。假设一个学生用两个特征来描述,复习时长 ,作业完成度 。有一份配方(也就是矩阵里的某一行)写着,取 份复习时长、再取 份作业完成度,那调出来的新特征就是 。这个新特征说不上有什么直观含义,但它很可能比任何单个原始特征都更有利于预测。矩阵乘法干的就是这种把原始特征重新调配成更好用的新特征的活儿。
4.导数和梯度:模型靠它找方向
模型要调参数,总不能xjb调吧,得知道往哪个方向调损失才会降。提供这个方向感的,叫导数。导数描述的是这么一回事:输入稍微变那么一点点,输出会跟着变多少。
可问题是,模型的参数动不动就有千千万万个(一个稍微大点的网络就能有几百万个参数),光靠单独一个导数哪够用。这时候我们就得把损失对每一个参数的偏导数全都收集起来,凑成一个跟参数一样大的向量,这个向量就叫梯度,记作 。式子里的 表示损失,说得再具体点,是一批样本的平均损失(一次性拿来一起训练的那一小撮样本叫一个批次, 就是这一个批次里所有样本损失的平均值)。
梯度有一个特别好用、又有点反直觉的性质:在当前位置,它指向损失上升最快的方向。你可能要问了,指向上升那有什么用啊?你还真别说,nb就nb在这里,既然梯度指向上坡,那咱掉个头,顺着它的反方向走,损失通常就往下掉了。
每走一步迈多大,得有个数来管着,这个数叫学习率,记作 。把这些全拼到一块儿,参数更新的基本套路就是:
意思是把 减去学习率乘以梯度,得到新的 ,下一轮就拿这个新 接着往下算。
怎么说呢,这个过程你不妨想象成蒙着眼下山。你站在半山腰某个地方,两眼一抹黑啥也看不见,只能拿脚探一探,脚下哪个方向最陡地往上抬(这就是梯度),那你就冲着它的反方向迈一步(这就是负梯度方向)。每一步迈多大的步子,就是学习率。迈太大了吧,你可能一脚跨过谷底、直接冲到对面的坡上,迈太小了吧,你走到天黑也下不到谷底。理想的训练,就是找着那么一个合适的步子,稳稳当当地溜到谷底附近。
这个蒙眼下山的办法,数学上叫梯度下降法,最早能追到1847年数学家柯西提出来的想法。一百多年之后,它反倒成了训练神经网络的主力方法,属实是一个好用的数学思想能有多顶的活例子。梯度下降法是一大精髓,建议大家感兴趣的可以好好去琢磨一下。
5.把数据切成三份
当然我们的数据可不能一股脑全都扔去训练,一般来说呢我们会把数据分成三份,分别叫训练集、验证集和测试集。
训练集专门用来调参数,就好比学生平时做的练习题,模型就是一边做这些题一边慢慢学会做题的。验证集用来挑模型的结构和一些手动设的数(学习率取多大、网络搞几层这种需要人拍板、不靠训练学出来的数,叫超参数),这就好比考前的模拟卷,帮你拿主意到底用哪套复习策略。测试集只在最后露一次脸,用来估摸模型的真实水平,这就是真正的期末考了,考完可不能再回去补刷,否则那成绩就水分大去了。
因为我们关注的是模型在没见过的新数据上还能不能支棱起来,而不是它在练习题是当刷子能拿多少分。所以测试集必须严格保密,在训练的时候不能拿出来用。
有一种特别常见的翻车情况。模型在训练集上损失低得吓人,几乎每道练习题都拿满分、卷得飞起,结果一上测试集当场拉胯。这说明它压根没学到背后的规律,纯粹是把练习题给死记硬背下来了。这种只会死记硬背不会思考消化知识点、换套卷子立马露馅的毛病,就叫过拟合。我们后面会讲到如何解决过拟合的问题。
练习
Q1. 把模型 、参数 、预测值 、损失 这几个概念串起来说一遍,训练到底在干嘛?
模型说白了就是一个函数 ,里头那一大堆能反复调的数字叫参数 。给它一个输入 ,它吐出预测值 。预测值和真实值 差了多少,拿损失 这把尺子去量。训练就是反反复复微调这堆参数 ,让损失往小了走,使模型给出的预测越来越贴近真实情况。
Q2. 还是课文里那个学生,复习时长 、作业完成度 。要是矩阵某一行配方写成 ,这一行调配出来的新特征是多少?这一步对应神经网络一层在干啥?
算式是 ,新特征就是 。矩阵的每一行就像一份配方,规定了原始特征各取多少再相加,榨出一种新的组合特征。神经网络每一层干的就是这种"矩阵乘向量、把特征重新调配"的活儿,只不过矩阵更大、配方更多,本质一回事。
Q3. 梯度 指向的是损失上升还是下降最快的方向?那梯度下降为啥偏偏要顺着它的反方向走?
梯度指向损失上升最快的方向,这点有点反直觉。可正因为梯度假装指向上坡,咱掉个头顺着负梯度方向走,损失通常就往下掉了。参数更新的式子 里那个减号,就是在走梯度的反方向,学习率 管着每一步迈多大。