2.1 线性神经网络

1.从一条最简单的直线开始

上一章我们说到,模型本质上就是一个函数。这一章,我们就从这个最简单的函数讲起,它叫线性神经网络。说它简单,是因为简单到一眼就能看穿,结构透明得很,所以特别适合当作入门的第一个真家伙。

我们先回顾一下,它接收一个特征向量 xx,这里的 xRdx\in\mathbb{R}^d 表示 xx 是一个装了 dd 个数(dd 就是特征个数)的向量。模型的参数分两部分,一部分是权重向量 wRdw\in\mathbb{R}^d(权重 ww 跟输入 xx 一样长,每个位置都对应一个特征),另一部分是偏置 bb(偏置就是单独一个数)。

它做的事情直白得不能再直白:把权重和特征按对应位置相乘,全部加起来,最后再补上一个偏置,就结束了。写成公式就是:

y^=wTx+b\hat{y}=w^\mathsf{T}x+b

这里的 T\mathsf{T} 表示转置,作用是让权重 ww 横过来,跟输入 xx 对应位置相乘再求和。y^\hat{y} 就是模型给出的那个预测值。

还是举个例子。假设我们想用一个学生平时的复习时长 x1x_1 和作业完成度 x2x_2 来预测他的期末分数。模型学成之后,权重是 w1=8w_1=8w2=5w_2=5,偏置是 b=20b=20。那么一个复习了5小时、作业完成度0.8的学生,模型给出的预测分数就是 8×5+5×0.8+20=648\times5+5\times0.8+20=64 分。每一个权重 wjw_j(下标 jj 表示第几个特征)都在告诉你,这个特征对结果有多大影响、是抬分还是压分。这个例子里复习时长的权重是8,意思是每多复习一小时,预测分就涨8分。偏置 b=20b=20 呢,是所有特征都为零时的保底分,可以理解成哪怕一点不复习、作业全空着,模型也给的20分底子。

正因为每个参数到底在做什么都明明白白,线性模型才成了理解所有神经网络最好的起点,胜在一个透明好懂。

不过这个例子藏着一个新手特别容易踩的坑,我得先提个醒。你看那两个特征,复习时长动不动就是好几个小时,量级在几这一档,作业完成度却永远在0到1之间晃悠。两个特征量级差出一大截,训练的时候权重 ww 照应起来就特别费劲。复习时长随便抖一下就是好几个单位,它贡献出来的梯度也跟着放大,模型的注意力全被它一个人吸走了,作业完成度那点微小波动模型根本顾不上看,很像一间教室里嗓门最大的同学一开口,老师就顾不上听别人说什么了。

所以训练开始之前,我们通常会先把所有特征拉到差不多的量级上,这一步叫特征缩放,最常见的做法叫标准化。操作很简单,对每一个特征,把整列数据先减去它的均值 μ\mu,再除以它的标准差 σ\sigma,写成公式就是:

x=xμσx'=\frac{x-\mu}{\sigma}

这里 μ\mu 是这个特征在所有样本上的平均值,σ\sigma 是衡量这列数据分散程度的数(标准差),新算出来的值记作 xx'(带个撇号表示这是标准化之后的版本)。减均值是把数据的中心挪到零那个位置,除以标准差是让数据的胖瘦变得规整,处理完之后每个特征都大致围在零附近,量级也接近了。

标准化对线性模型的训练来说,好处十分明显。一来,所有特征站在同一条起跑线上,谁也没法仗着数值大就霸占梯度的更新方向。二来,损失曲面变得更圆更均匀,梯度下降每一步都走得踏实,收敛速度也快起来。三来,学习率 η\eta 也好定得多,不至于这个特征那边已经溢出、那个特征那边还纹丝不动。说到底,特征缩放就像考试前先把各科卷面分都换算成百分制,大家站在同一套标准下才好比个高低,这一步功夫在几乎所有深度学习训练里都省不掉。

2.用平方损失衡量错得多离谱

刚才那个例子里,模型预测64分,可这个学生实际考了多少我们还不知道。模型猜得准不准,总得有把尺子来量一量。预测一个连续的数(比如分数、房价、温度)这类任务,我们叫它回归任务,回归任务最常用的尺子叫平方损失。对第 ii 个样本,损失是这样算的:

i=12(y^iyi)2\ell_i=\frac{1}{2}(\hat{y}_i-y_i)^2

里面 yiy_i 是真实值,y^i\hat{y}_i 是预测值。括号外面套个平方有两个用处,一是让误差无论正负都变成正数(预测高5分和低5分,错的份量一样),二是把大的误差再放大一截。前面那个 12\frac{1}{2} 呢,纯粹是为了后面求导的时候式子干净,对结果往哪边走一点影响都没有。

平方损失有个挺突出的脾气,它专挑大误差下手,死盯着错得最狠的样本不放。当预测误差变成两倍的时候,平方损失会变成四倍。还拿刚才那个学生说,模型预测他64分,实际也真是64分,这一项损失就是零。要是模型预测成74分(高了10分),损失就是 12×102=50\frac{1}{2}\times10^2=50。要是错得更离谱,预测成94分(高了30分),损失一下蹿到 12×302=450\frac{1}{2}\times30^2=450。你看,误差变3倍,损失直接变9倍,惩罚力度拉满。

这脾气是好事也是坏事。好的一面是,模型会死死咬住那些错得离谱的样本,铆足了劲想把它们拽回来,学得很快。坏的一面是,万一数据里混进来一两个奇葩的异常值(比如某个学生的分被手抖录成999了),平方损失就会被这几个歪点子带跑,整个模型为了讨好它们,反倒把大多数普通样本给冷落了。所以用平方损失之前,先扫一眼数据里有没有明显跑偏的点,是个挺靠谱的习惯。

既然聊到这儿,顺手再介绍一个跟它长得特别像的尺子,叫平均绝对误差,简称MAE。它的算法是把预测值和真实值的差先取绝对值,再求个平均:

MAE=1ni=1ny^iyi\mathrm{MAE}=\frac{1}{n}\sum_{i=1}^{n}|\hat{y}_i-y_i|

绝对值这一下,意思是管你预测偏高还是偏低,只看差了多少,而且误差按原样算账,不像平方损失那样还把误差再放大一截。所以平方损失(也叫均方误差,简称MSE)和MAE最大的差别,就在于对待异常值的态度。MSE碰到异常值会反应剧烈,铆着劲往那几个歪点子凑,MAE却稳得很,错多少就记多少账,几个奇葩点带不跑它。数据里要是明显有噪声(比如学委手抖把分录错了那种),换用MAE模型会稳一些。当然天下没有白吃的午餐,MAE在误差为零那一点是没法求导的,数学上没MSE那么顺滑,这也是大家训练的时候更爱用MSE的原因。小张有一次做房价预测,就被几套标错天价的房源带偏过,后来换成MAE重新训练,模型明显稳多了。

3.从一堆损失到参数更新

训练集里有一大堆样本,假设一共 nn 个(nn 就是样本总数)。我们真正关心的,是模型在所有样本上的整体表现,单看某一个样本错多少没太大意义,所以把平均损失定义成:

L=1ni=1niL=\frac{1}{n}\sum_{i=1}^{n}\ell_i

那个求和符号 \sum 的意思是,把从第1个到第 nn 个样本各自的损失 i\ell_i 一个一个加起来,再除以 nn 求个平均。这个 LL 呢,正好就是上一章梯度 θL\nabla_\theta L 里面的那个损失。

接下来的套路,跟第一章一模一样。先算出损失 LL 对权重 ww 和偏置 bb 的梯度,然后顺着负梯度的方向走一小步,学习率 η\eta 管着这一步迈多大。线性模型有个很大的便宜可占,它的损失曲面长成一个漂漂亮亮的碗(数学上叫凸函数),碗底就是最好的那组参数。这就意味着,只要学习率别太离谱,梯度下降几乎一定能把你带到碗底,不会困在半山腰的某个坑里。

整个过程可以这么想象:一条直线一开始歪歪扭扭地摆在那儿,每更新一次参数,它就朝数据点最密集的方向挪一点。挪个几十步、几百步,最后稳稳当当落在最能代表数据整体趋势的那个位置上。

话说回来,我们费这么大劲一步步往下挪,你心里可能会犯嘀咕:线性回归这种结构透明的问题,难道就没有一步到位的公式,非得靠梯度下降一点点磨吗?还真有,这东西数学上叫正规方程,也叫最小二乘的闭式解,相当于直接抄答案那种级别的爽快。把所有样本的特征向量上下摞成一张设计矩阵 XXXX 的每一行是一个样本,每一列是一种特征),再把所有真实值 yiy_i 也摞成一个列向量 yy,最好的那组权重可以直接一步算出来:

w^=(XTX)1XTy\hat{w}=(X^\mathsf{T}X)^{-1}X^\mathsf{T}y

公式里 XTX^\mathsf{T} 是设计矩阵的转置,(XTX)1(X^\mathsf{T}X)^{-1} 的意思是先让 XX 的转置乘上 XX 得到一个方阵,再对这个方阵求逆(偏置 bb 一般是塞进 XX 里多加的一列1一起算出来的,省得单独折腾)。w^\hat{w} 头顶那个小帽子 ^\hat{}y^\hat{y} 一个意思,标记这是算出来的最优权重。整套公式干的事情,说白了就是对误差平方和这个目标,用一点线性代数的本事直接求出最小值对应的权重,连磨都不用磨。

那为什么后面还要费劲搞梯度下降呢,直接套公式不更省事吗?问题就出在那个矩阵求逆上。求逆这个操作的计算量跟特征个数 dd 的三次方挂钩(粗略写成 O(d3)O(d^3) 量级),特征少的时候套公式确实省事,可一旦特征上千上万,那矩阵求逆能把你的电脑算到冒烟。更别提后面深度学习动辄几百万个参数,求逆这件事想都别想。所以梯度下降这种慢一点但扛得住规模的做法,才是训练大模型的正解,闭式解更适合小数据集拿来直接出结果或者对照验证。这两种求法目标完全一样,都是要把那个碗底找出来,只是走的路不同罢了。

4.看模型到底行不行:决定系数

训练完了,光盯着损失值 LL 其实还不够直观,因为损失值跟数据本身的量纲绑得死死的。预测房价的时候损失可能是好几十万的平方和,预测GPA的时候损失可能就零点零几,你根本说不清这个数到底算好算差。这时候我们要请出一个更通用的指标,叫决定系数,记作 R2R^2,读作R方。它的公式长这样:

R2=1i=1n(yiy^i)2i=1n(yiyˉ)2R^2=1-\frac{\sum_{i=1}^{n}(y_i-\hat{y}_i)^2}{\sum_{i=1}^{n}(y_i-\bar{y})^2}

分子那块 i=1n(yiy^i)2\sum_{i=1}^{n}(y_i-\hat{y}_i)^2 是模型预测的残差(预测值跟真实值差多少)的总平方和,分母那块 i=1n(yiyˉ)2\sum_{i=1}^{n}(y_i-\bar{y})^2 里的 yˉ\bar{y} 是所有真实值的均值(头上那道横杠就是均值的意思),整个分母衡量的是真实值本身有多大的波动。整句话翻译一下,R2R^2 衡量的就是模型到底解释了多少数据里的波动。

这个数读起来特别有感觉。它最理想是1,越靠近1说明模型把数据的波动解释得越透,拟合得越漂亮。要是 R2R^2 等于0,意思是模型预测得跟直接拿均值 yˉ\bar{y} 瞎蒙一个样,什么规律也没学到,白练一场。更狠的是 R2R^2 还可能是负数,这说明模型预测得还不如直接报均值那种敷衍做法,这种时候你大概得回去看看特征是不是选错了、模型是不是压根没训起来。所以说 R2R^2 越接近1越好,一旦掉到零以下,就该认真查问题了。

5.线性模型能做什么,又做不了什么

线性模型再好使,也有一个绕不过去的硬伤:它只会画直线(说得准一点,只能表达线性关系)。数据要是本身就顺着一条直线散开,那线性模型的表现相当漂亮。可现实里的关系,十有八九是弯的。还拿复习时长那个例子,它跟分数的关系往往不是一条直线,复习太少分低,复习到一定时长分最高,再往死里复习反而累趴了、分往下掉。这种先升后降的关系,你拿一条直线怎么摆都摆不顺,线性模型在这里就力不从心了。

当数据的分界线是弯的,或者得把好几个特征凑在一起组合着看才分得开的时候,单个线性变换的本事就彻底见底了。从下一章开始要上场的那些更复杂的模型,它们忙活的核心其实就一件事:在还能好好训练的前提下,给网络多塞点本事,让它画得出弯的界线、抓得住复杂的组合。而线性模型呢,就是这一切往下盖的地基。

建议大家感兴趣的,可以自己拿几个数手算一下这个 wTx+bw^\mathsf{T}x+b,亲手算一遍比看十遍都管用。下一章见。

练习

Q1. 线性回归有正规方程 w^=(XTX)1XTy\hat{w}=(X^\mathsf{T}X)^{-1}X^\mathsf{T}y 这种一步到位的闭式解,为啥实际训练大模型时反而都用梯度下降?

根子在那个矩阵求逆上。求逆的计算量跟特征个数 dd 的三次方挂钩(O(d3)O(d^3) 量级),特征少的时候套公式确实省事,可特征一旦上千上万,求逆能把电脑算到冒烟。深度学习动辄几百万参数,求逆想都别想。所以梯度下降这种慢一点但扛得住规模的做法才是正解,闭式解更适合小数据集直接出结果或对照验证。

Q2. 权重 w=(8,5)w=(8,5)、偏置 b=20b=20,输入 x=(5,0.8)x=(5,0.8),算预测值。每个权重 wjw_j 在说什么?

y^=wTx+b=8×5+5×0.8+20=40+4+20=64\hat{y}=w^\mathsf{T}x+b=8\times5+5\times0.8+20=40+4+20=64。每个权重 wjw_j 告诉你这个特征对结果有多大影响、是抬分还是压分:复习时长的权重是 8,意思是每多复习一小时预测分涨 8 分。偏置 b=20b=20 是所有特征都为零时的保底分。

Q3. 为什么训练线性模型之前一定要做特征缩放(标准化)?要是算出来的 R2R^2 是负数,说明什么?

不同特征量级差太多时(比如复习时长在几这一档,作业完成度在 0 到 1),数值大的特征会霸占梯度的更新方向,模型顾不上别的特征,损失曲面也变得不均匀、收敛慢。标准化后大家站在同一起跑线,损失曲面变圆变均匀,学习率也好定。R2R^2 为负说明模型预测得还不如直接报均值 yˉ\bar{y} 那种敷衍做法,啥规律也没学到,得回头查特征和训练是不是出了毛病。

相关标签
深度学习线性模型回归