Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

6.6 训练神经网络

前馈神经网络属于监督机器学习:每个观测 x\mathbf x 的正确输出 y\mathbf y 都已知。系统通过式 6.13 产生 y^\hat{\mathbf y},即对真实 y\mathbf y 的估计。训练目标是为每层 ii 学习参数 W[i]\mathbf W^{[i]}b[i]\mathbf b^{[i]},使每个训练观测的 y^\hat{\mathbf y} 尽可能接近真实 y\mathbf y

总体上,我们沿用第 4 章逻辑回归的方法,本节用通用简单网络讲解,而非专门为情感分析或语言建模设计的网络。

第一,需要一个表示系统输出与金标准输出距离的损失函数,通常使用逻辑回归中的交叉熵损失。

第二,使用第 4 章的梯度下降优化算法,寻找使损失最小的参数。

第三,梯度下降需要损失函数的梯度,即包含损失对每个参数偏导数的向量。逻辑回归可以为每个观测直接计算损失对单个 wwbb 的导数;神经网络却可能有许多层、数百万个参数,而损失只连接在很后面的层上,因此很难直接看出如何计算损失对第 1 层某个权重的偏导数。解决方法是误差反向传播(error backpropagation),也称反向微分(backward differentiation)。

6.6.1 损失函数

神经网络使用的交叉熵损失与逻辑回归相同。若网络用于二元分类,最终层使用 sigmoid,损失就是式 4.19:

LCE(y^,y)=logp(yx)=[ylogy^+(1y)log(1y^)](6.25)L_{CE}(\hat y,y)=-\log p(y\mid x)=-[y\log\hat y+(1-y)\log(1-\hat y)]\tag{6.25}

若网络分类为三类或更多,损失与第 4 章第 112 页的多项回归相同。此时 y\mathbf yy^\hat{\mathbf y} 都是向量。对只有一个正确类别的硬分类,真实标签 y\mathbf yKK 个元素;正确类别为 ccyc=1y_c=1,其余均为 0,即独热向量。分类器输出估计向量 y^\hat{\mathbf y},其中 y^k\hat y_k 表示估计概率 p(yk=1x)p(y_k=1\mid\mathbf x)

单个样本的损失,是 KK 个输出类别对数以 yky_k 加权后求和并取负:

LCE(y^,y)=k=1Kyklogy^k(6.26)L_{CE}(\hat{\mathbf y},\mathbf y)=-\sum_{k=1}^Ky_k\log\hat y_k\tag{6.26}

用条件成立时为 1、否则为 0 的指示函数 1{}\mathbb1\{\} 可写为:

LCE(y^,y)=k=1K1{yk=1}logy^kL_{CE}(\hat{\mathbf y},\mathbf y)=-\sum_{k=1}^K\mathbb1\{y_k=1\}\log\hat y_k

除真实类别对应项外,其余各项均为 0。因此交叉熵损失就是正确类别输出概率的负对数,也称负对数似然损失:

LCE(y^,y)=logy^c(c 为正确类别)(6.27)L_{CE}(\hat{\mathbf y},\mathbf y)=-\log\hat y_c\qquad(c\text{ 为正确类别})\tag{6.27}

代入式 6.9 的 softmax:

LCE(y^,y)=logexp(zc)j=1Kexp(zj)(c 为正确类别)(6.28)L_{CE}(\hat{\mathbf y},\mathbf y)=-\log\frac{\exp(z_c)}{\sum_{j=1}^K\exp(z_j)}\qquad(c\text{ 为正确类别})\tag{6.28}

完美分类器给正确类别概率 1、所有错误类别概率 0。正确类别概率越接近 1,分类器越好;越接近 0,分类器越差。负对数概率是很合适的损失:从 log1=0-\log1=0(无损失)到 log0=-\log0=\infty(无限损失)。它还确保正确答案概率最大化时,错误答案概率都被最小化,因为全部概率之和为 1。

输出向量的类别数 KK 可大可小。三分类情感分析中类别为正面、负面、中性;词性判断中,KK 是标注集中的词性数(第 18 章将定义的标注集有 17 类);语言建模中类别是全部词元,可能有 50,000 或 100,000 个。

6.6.2 计算梯度

计算损失梯度,需要求损失函数对每个参数的偏导数。只有一个权重层和 sigmoid 输出的网络就是逻辑回归,可直接使用式 6.29(推导见第 4.15 节):

LCE(y^,y)wj=(y^y)xj=(σ(wx+b)y)xj(6.29)\begin{array}{rcl}\frac{\partial L_{CE}(\hat y,y)}{\partial w_j}&=&(\hat y-y)x_j\\&=&(\sigma(\mathbf w\cdot\mathbf x+b)-y)x_j\end{array}\tag{6.29}

只有一个权重层和 softmax 输出的网络就是多项逻辑回归,可使用式 4.41。对权重 wk,iw_{k,i}

LCE(y^,y)wk,i=(yky^k)xi=(ykp(yk=1x))xi=(ykexp(wkx+bk)j=1Kexp(wjx+bj))xi(6.30)\begin{array}{ll}\frac{\partial L_{CE}(\hat{\mathbf y},\mathbf y)}{\partial w_{k,i}}&=-(y_k-\hat y_k)x_i\\&=-(y_k-p(y_k=1\mid\mathbf x))x_i\\&=-\left(y_k-\frac{\exp(\mathbf w_k\cdot\mathbf x+b_k)}{\sum_{j=1}^K\exp(\mathbf w_j\cdot\mathbf x+b_j)}\right)x_i\end{array}\tag{6.30}

但这些导数只适用于一个权重层,也就是最后一层。深层网络的损失在末端计算,而要对前面各层的权重参数求导,情况复杂得多。解决这一问题的算法称为误差反向传播backprop(Rumelhart et al., 1986)。它其实与更一般的反向微分过程相同,而反向微分依赖计算图(computation graph)。

6.6.3 计算图

计算图把数学表达式的计算过程拆成独立操作,每个操作表示为图中的一个节点。

以函数 L(a,b,c)=c(a+2b)L(a,b,c)=c(a+2b) 为例,显式写出加法、乘法并为中间输出命名:

d=2be=a+dL=ce\begin{array}{rl}d&=2b\\e&=a+d\\L&=ce\end{array}

可把它画成图:每个操作一个节点,有向边将某操作的输出作为下一操作的输入。图 6.15 设 a=3,b=1,c=2a=3,b=1,c=-2,展示前向传递得到 L(3,1,2)=10L(3,1,-2)=-10。计算图的前向传递(forward pass)从左到右应用各操作,把每步输出传给下一节点。

图 6.15 函数 L(a,b,c)=c(a+2b)L(a,b,c)=c(a+2b) 的计算图。输入 a=3,b=1,c=2a=3,b=1,c=-2,展示计算 LL 的前向传递。

6.6.4 计算图上的反向微分

计算图的重要性来自反向传递(backward pass),它用于计算权重更新所需的导数。这个例子要计算输出 LL 对每个输入变量的导数 La,Lb,Lc\frac{\partial L}{\partial a},\frac{\partial L}{\partial b},\frac{\partial L}{\partial c};例如 La\frac{\partial L}{\partial a} 表示 aa 的微小变化会使 LL 改变多少。

反向微分使用微积分的链式法则。若复合函数 f(x)=u(v(x))f(x)=u(v(x))

dfdx=dudvdvdx(6.31)\frac{df}{dx}=\frac{du}{dv}\cdot\frac{dv}{dx}\tag{6.31}

f(x)=u(v(w(x)))f(x)=u(v(w(x)))

dfdx=dudvdvdwdwdx(6.32)\frac{df}{dx}=\frac{du}{dv}\cdot\frac{dv}{dw}\cdot\frac{dw}{dx}\tag{6.32}

反向微分把梯度从最终节点传回图中所有节点。图 6.16 展示节点 ee 的一部分计算:节点接收右侧父节点传来的上游梯度(upstream gradient),为每个输入计算局部梯度(local gradient,即输出对该输入的梯度),再依据链式法则将二者相乘,得到传给更早节点的下游梯度(downstream gradient)。

由于计算图中 L=ceL=ce,可以直接得到:

Lc=e(6.33)\frac{\partial L}{\partial c}=e\tag{6.33}

另外两个导数使用链式法则:

La=LeeaLb=Leeddb(6.34)\begin{array}{l}\frac{\partial L}{\partial a}=\frac{\partial L}{\partial e}\frac{\partial e}{\partial a}\\\frac{\partial L}{\partial b}=\frac{\partial L}{\partial e}\frac{\partial e}{\partial d}\frac{\partial d}{\partial b}\end{array}\tag{6.34}

图 6.16 每个节点(如 ee)接收上游梯度,与局部梯度相乘,利用链式法则计算传给前一节点的下游梯度。多输入节点会有多个局部梯度。

所需中间导数为:

L=ce:Le=c,Lc=ee=a+d:ea=1,ed=1d=2b:db=2\begin{array}{cc}L=ce:&\frac{\partial L}{\partial e}=c,\quad\frac{\partial L}{\partial c}=e\\e=a+d:&\frac{\partial e}{\partial a}=1,\quad\frac{\partial e}{\partial d}=1\\d=2b:&\frac{\partial d}{\partial b}=2\end{array}

反向传递从右向左沿每条边计算偏导。先从 LL 节点计算 Le\frac{\partial L}{\partial e}Lc\frac{\partial L}{\partial c};到节点 ee 后,把上游梯度 Le\frac{\partial L}{\partial e} 与局部梯度 ed\frac{\partial e}{\partial d} 相乘,得到传回 ddLd\frac{\partial L}{\partial d},依此类推,直到所有输入变量都标注完毕。前向传递已计算并保存 d,ed,e 等中间变量的值,可以直接用于求这些导数。

图 6.17 函数 L(a,b,c)=c(a+2b)L(a,b,c)=c(a+2b) 的计算图,展示反向传递计算 La,Lb,Lc\frac{\partial L}{\partial a},\frac{\partial L}{\partial b},\frac{\partial L}{\partial c}

神经网络的反向微分

真实神经网络的计算图复杂得多。图 6.18 展示一个两层网络的样例计算图,其中 n0=2,n1=2,n2=1n_0=2,n_1=2,n_2=1。为简单起见,假设进行二元分类并使用 sigmoid 输出。计算函数为:

z[1]=W[1]x+b[1]a[1]=ReLU(z[1])z[2]=W[2]a[1]+b[2]a[2]=σ(z[2])y^=a[2](6.35)\begin{array}{rcl}\mathbf z^{[1]}&=&\mathbf W^{[1]}\mathbf x+\mathbf b^{[1]}\\\mathbf a^{[1]}&=&\operatorname{ReLU}(\mathbf z^{[1]})\\z^{[2]}&=&\mathbf W^{[2]}\mathbf a^{[1]}+b^{[2]}\\a^{[2]}&=&\sigma(z^{[2]})\\\hat y&=&a^{[2]}\end{array}\tag{6.35}

反向传递还需计算损失。二元 sigmoid 输出的损失为:

LCE(y^,y)=[ylogy^+(1y)log(1y^)](6.36)L_{CE}(\hat y,y)=-[y\log\hat y+(1-y)\log(1-\hat y)]\tag{6.36}

由于 y^=a[2]\hat y=a^{[2]}

LCE(a[2],y)=[yloga[2]+(1y)log(1a[2])](6.37)L_{CE}(a^{[2]},y)=-[y\log a^{[2]}+(1-y)\log(1-a^{[2]})]\tag{6.37}

图 6.18 一个简单两层神经网络的样例计算图,包含两个输入单元和两个隐藏单元。为避免节点中的方程过长,图中只标出所计算的函数和结果变量。例如,w11[1]w_{11}^{[1]} 右侧的乘号表示它要与 x1x_1 相乘,z[1]=+z^{[1]}=+ 节点表示对流入的两个乘积和偏置项求和。青色表示需要更新、因而需要损失偏导数的权重。

进行反向传递需要图中所有函数的导数。sigmoid 的导数为:

dσ(z)dz=σ(z)(1σ(z))(6.38)\frac{d\sigma(z)}{dz}=\sigma(z)(1-\sigma(z))\tag{6.38}

tanh 的导数为:

dtanh(z)dz=1tanh2(z)(6.39)\frac{d\tanh(z)}{dz}=1-\tanh^2(z)\tag{6.39}

ReLU 的导数为:

dReLU(z)dz={0z<01z0(6.40)\frac{d\operatorname{ReLU}(z)}{dz}=\left\{\begin{array}{ll}0&z<0\\1&z\ge0\end{array}\right.\tag{6.40}

下面开始计算损失 LLz=z[2]z=z^{[2]} 的导数,其余留作练习。由链式法则:

Lz=La[2]a[2]z(6.41)\frac{\partial L}{\partial z}=\frac{\partial L}{\partial a^{[2]}}\frac{\partial a^{[2]}}{\partial z}\tag{6.41}

先对式 6.37 关于 a[2]a^{[2]} 求导:

La[2]=[y1a[2]+(1y)11a[2](1)]=(ya[2]+y11a[2])(6.42)\begin{array}{rcl}\frac{\partial L}{\partial a^{[2]}}&=&-\left[y\frac1{a^{[2]}}+(1-y)\frac1{1-a^{[2]}}(-1)\right]\\&=&-\left(\frac y{a^{[2]}}+\frac{y-1}{1-a^{[2]}}\right)\end{array}\tag{6.42}

再由 sigmoid 的导数:

a[2]z=a[2](1a[2])\frac{\partial a^{[2]}}{\partial z}=a^{[2]}(1-a^{[2]})

最后应用链式法则:

Lz=La[2]a[2]z=(ya[2]+y11a[2])a[2](1a[2])=a[2]y(6.43)\begin{array}{rcl}\frac{\partial L}{\partial z}&=&\frac{\partial L}{\partial a^{[2]}}\frac{\partial a^{[2]}}{\partial z}\\&=&-\left(\frac y{a^{[2]}}+\frac{y-1}{1-a^{[2]}}\right)a^{[2]}(1-a^{[2]})\\&=&a^{[2]}-y\end{array}\tag{6.43}

继续把梯度依次传过 b1[2]b_1^{[2]}、两个乘积节点以及更早节点,求得所有青色节点的梯度,留作读者练习。

6.6.5 学习的更多细节

神经网络优化是非凸优化问题,比逻辑回归更复杂,因此成功训练涉及许多最佳实践。

逻辑回归可以把所有权重和偏置初始化为 0;神经网络则必须用较小随机数初始化权重。将输入值归一化为均值 0、方差 1 也有帮助。

防止过拟合会采用各种正则化,其中最重要的方法之一是 dropout:训练期间随机丢弃网络中的一些单元及其连接(Hinton et al., 2012;Srivastava et al., 2014)。每次参数更新时——使用小批量梯度下降时就是每个小批量——选择概率 pp,以概率 pp 把每个单元的输出替换为 0,并重新归一化该层其余输出。因此 pp 是算法的超参数。

超参数调节也很重要。神经网络参数是权重 W\mathbf W 和偏置 b\mathbf b,由梯度下降学习;超参数由算法设计者选择,在开发集上调优,而非通过训练集上的梯度下降学得。超参数包括学习率 η\eta、小批量大小、模型架构(层数、每层隐藏节点数、激活函数)、正则化方式等。梯度下降本身也有许多变体,如 Adam(Kingma and Ba, 2015)。

最后,现代神经网络大都基于计算图框架构建,以便自然地计算梯度,并在基于向量的 GPU(图形处理器)上并行化。PyTorch(Paszke et al., 2017)和 TensorFlow(Abadi et al., 2015)是最流行的两个框架。更多细节可参见神经网络教材,本章末尾列出了一些建议。