DennyQi's Log

神经网络

下面我们要讨论称为“神经网络”(Neural Network)的非线性模型。

神经网络

简单起见,我们依然考虑一个二分类问题。例如,我们要通过四个feature——价格、运费、营销、材质——来判断一件商品是否会有好的销量。如果直接用逻辑回归的模型,那么我们会用样本集训练出一个四维的向量β\betaβ\beta每次和输入xix_i做内积,然后通过sigmoid函数映射到一个概率pip_i,由pip_i直接给出这件商品是否有好销量的判断。神经网络的做法与此非常类似,只是加上了一些修改,但却取得了非常好的效果。

我们说过,逻辑回归本质上只是找到了一个线性的分类面把样本点分为了两部分,因此在大部分复杂的情况下难以有良好的表现。这是因为,逻辑回归要求我们直接在给定的样本点的分布上划分分类面。如果我们能够对样本进行几轮的位置调整(位置到位置的映射),每一次都把样本整理得更整齐一些,使得最后确实只需要一个平面就能把所有样本准确地分类。这就是神经网络本质上在做的事。具体地,在商品的例子中,我们这样来建立模型:对于价格、运费、营销、材质这四个feature,我们可以由这四者利用逻辑回归生成一个称为“付不付得起”的概率预测,同样的也可以生成称为“大众知不知道”、“质量好不好”的概率预测。那么我们就由四个feature生成了三个新的feature。而我们认为相比于原来的四者,这三者在决定商品销量的好坏时有着更直接的(接近于线性的)联系。根据这三个新的feature再做一次逻辑回归,再得到的销量好坏的判断概率,我们期待这样的模型会比单单做一次逻辑回归有好得多的效果。这就是一个一层的神经网络模型,根据输入的四个feature,我们分别做了三次逻辑回归得到了3个新的feature,这三者构成神经网络的第一层(Layer),三个feature称为这一层的3个unit。而后这三个unit作为新的输入,构成了神经网络的第二层,在这个例子里也就是输出了。在更复杂的问题中,神经网络可以有两个中间层、三个中间层、甚至成千上万个中间层。人们最初构想出这个模型的动机就在于对人类大脑神经结构的模拟,神经元之间互相连接,输入进入第一层神经元给出了输出,这些输出又进入下一层神经元给出新的输出,通过这样的前向传播(Forward Propagation),解决复杂的拟合问题。

我们用数学来叙述以上神经网络的运作过程。对于输入xix_i,我们需要在第一层训练n1n_1个unit,第jj个unit包含一个参数向量βj[1]\beta_j^{[1]},这个unit给出输出11+eβj[1]xi\dfrac{1}{1+e^{-\beta_j^{[1]}\cdot x_i}}(记sigmoid函数为g(z)=11+ezg(z)=\dfrac{1}{1+e^{-z}},则输出记为g(βj[1]xi)g(\beta_j^{[1]}\cdot x_i)。所有的n1n_1g(βj[1]Xi)g(\beta_j^{[1]}\cdot X_i)组成向量a1a_1,作为第二层的输入,以此类推。在第ii层中,输入a[i1]a^{[i-1]}称为activation value(激活值),函数g(z)g(z)称为activation function(激活函数),在这里我们选取了sigmoid函数作为激活函数。

激活函数(Activation Functions)

在神经网络发展的过程中,人们发现用函数g(z)=max{0,z}g(z)=\max\{0,z\}来代替原本的激活函数sigmoid会取得更好的效果。这个函数称为修正线性单元(Rectified Linear Unit, ReLU),是目前应用最广泛的激活函数。 当然,如果面对的是二分类问题,在最后一层(输出层)我们还是依然使用sigmoid函数,这样我们能保证最后给出的值是一个[0,1][0,1]间的实数用来代表概率。与sigmoid函数相比,ReLU计算起来会更快,因为它不涉及指数运算和除法运算;同时,相比于sigmoid,ReLU的图像只有在小于等于0时出现一个平台,而sigmoid在两端都出现平台。由于激活函数中的平台很多时候也会出现在代价函数中,而一个代价函数的平台越多,梯度下降的效率也就越低。因此使用ReLU函数通常能让训练变得更快。

我们为什么必须要用一个激活函数呢?能不能完全不用激活函数,或者说令g(z)=zg(z)=z(线性函数)作为激活函数呢?显然,如果一个神经网络里完全只有线性函数,那么由于线性复合线性依然是线性的,最终我们只是给出一个线性拟合,而做线性拟合根本不需要神经网络。正是激活函数的非线性使得神经网络可以发挥复杂的功效。

所以我们看到,神经网络的核心在于激活函数让模型变得非线性了。这很像我们在支持向量机一节中提到的Kernel Method,在那里我们通过把βx\beta \cdot x替换为βϕ(x)\beta\cdot \phi(x)来让模型拥有非线性拟合的功能。我们需要选择优秀的kernel,对Kernel的选取决定了模型的表现。而神经网络实际上是在让算法自己去寻找一个合适的kernel,因为非线性在训练中已经被包含在了每一层的参数当中。

现代神经网络

上面描述的这种神经网络现在更多地被称为“多层感知机(multi-layer perceptron, MLP)”,现代神经网络通常要比多层感知机更复杂。

残差网络(Residual Networks)

我们用矩阵的形式来表示神经网络中每一层完成的工作。假设某一层的输入为zz,那么我们会根据当前层的mm个unit依次给出g(βiz)g(\beta_i\cdot z)组成下一层的输入(g(β1z),,g(βmz))(g(\beta_1\cdot z),\cdots,g(\beta_m\cdot z))。如果记W=[β1βm]W=\begin{bmatrix}\beta_1^\top\\\vdots\\\beta_m^\top\end{bmatrix},那么下一层的输入可以直接写作g(Wz)g(Wz)。那么一个多层感知机模型(不考虑最后一层的非线性)就可以表达为函数MLP(z)=Wr(g(Wr1(g(g(W1z)))))\text{MLP}(z)=W_r(g(W_{r-1}(g(\cdots g(W_1z)))))

一个称为残差连接(residual connection)的改进是,记Res(z)=z+g(W(g(Wz)))\text{Res}(z)=z+g(W(g(Wz))),称为一个残差块(residual block)。然后令ResNet-S(x)=W(Res(Res(Res(z))))\text{ResNet-S}(x)=W(\text{Res}(\text{Res}(\cdots\text{Res}(z))))。这样的神经网络称为一个残差网络。它把每两层矩阵乘法复合非线性看作一轮做两轮,然后累加上两轮以前的输入作为下一次的输入。

层归一化(Layer Normalization)

很多时候我们需要能够控制输入数据的大小,因此我们希望能在不改变输入的向量的相对分布的情况下控制其期望与方差。设输入向量为zz,那么其期望为μ=i=1mzim\mu=\dfrac{\sum\limits_{i=1}^{m}z_i}{m},标准差为σ=i=1m(ziμ)2m\sigma=\sqrt{\dfrac{\sum\limits_{i=1}^{m}(z_i-\mu)^2}{m}},那么可以对zz做normalize:令zi=ziμσz_i'=\dfrac{z_i-\mu}{\sigma},容易验证此时zz'变为了一个期望为0,方差(标准差)为1的向量了。此时令z=β+γzz''=\beta+\gamma z',就可以使得zz''的期望和方差取任何我们想要的值。

把归一化记为函数LN(z)\text{LN}(z),归一化的很大一个好处就在于向量的数乘将不会影响结果。由于αziαμασ=ziμσ=zi\dfrac{\alpha z_i-\alpha \mu}{\alpha \sigma}=\dfrac{z_i-\mu}{\sigma}=z_i',因此LN(αz)=LN(z)\text{LN}(\alpha z)=\text{LN}(z)。那么LN(αWz)=LN(Wz)\text{LN}(\alpha Wz)=\text{LN}(Wz)。因此给神经网络的某一层中所有参数同时乘一个常数是不会对结果有任何影响的。

卷积神经网络(Convolutional Neural Networks, CNN)

神经网络中每一层线性的部分可以看作矩阵乘法WzWz,现在考虑一种特殊的矩阵乘法:设定一个特定的向量wRkw\in \R^k(称为filter或kernel),其中kk通常远小于zz的维数nn。把向量zz看作一个长度为nn序列,让ww分别与该序列中所有长度为kk的连续子序列做内积(卷积),假如我们给序列的前后分别补上0,使得这样的子序列恰好有nn个,我们便得到了新的向量Conv1D-S(z)i=zi=j=1kwjzi+j+k121\text{Conv1D-S}(z)_i=z'_i=\sum\limits_{j=1}^{k}w_{j}z_{i+j+\frac{k-1}{2}-1}(这里假定kk是奇数)。这就是最简单的一个一维卷积层,我们可以看到这实际上是一种特殊的某个稀疏矩阵的矩阵乘法。它使得运算次数从n2n^2降低到了nknk

在实际中,我们会先把zz通过feature map映射到CC个向量z1,,zCz_1,\cdots,z_C,称为CC个channel。用CC个不同的kernel wjw_j分别与每个zjz_j做卷积,再把这些结果相加作为输出:Conv1D(z)i=j=1CConv1D-Si,j(zj)\text{Conv1D}(z)_i=\sum\limits_{j=1}^{C}\text{Conv1D-S}_{i,j}(z_j)。这样,我们通过k×Ck\times C个参数得到了一个nn维向量作为输出结果。取CC'组这样的参数,我们就能得到CC'nn维向量,每个向量称为一个output channel。

以上卷积操作可以拓展到高维。例如二维卷积就是用一个k×kk\times k的矩阵依次去覆盖n×nn\times n的输入矩阵的每个连续k×kk\times k子矩阵做卷积。同样的,二维卷积也可以有多个input channel和output channel。

反向传播

两层的神经网络的代价函数

下面我们来计算一个只有一个中间层的神经网络的代价函数。假设中间层用的是ReLU,最后用sigmoid输出概率,那么yiBernoulli(pi)y_i\sim \text{Bernoulli}(p_i)pi=sigmoid(hiβ)=11+ehiβp_i=\text{sigmoid}(h_i^\top \beta)=\dfrac{1}{1+e^{-h_i^\top\beta}}hik=ReLU(xiαk)=max(xiαk,0)h_{ik}=\text{ReLU}(x_i^\top \alpha_k)=\max(x_i^\top \alpha_k,0)。那么P=ipiyi(1pi)1yiP=\prod\limits_{i}p_i^{y_i}(1-p_i)^{1-y_i},所以L(β,α)=lnP=i[yilnpi+(1yi)ln(1pi)]\mathscr{L}(\beta,\alpha)=\ln P=\sum\limits_{i}[y_i\ln p_i+(1-y_i)\ln(1-p_i)] =i[yiln(1+ehiβ)+(1yi)(hiβln(1+ehiβ))]=\sum\limits_{i}[-y_i\ln(1+e^{-h_i^\top\beta})+(1-y_i)(-h_i^\top \beta-\ln(1+e^{-h_i^\top\beta}))] =i[(yi1)hiβln(1+ehiβ)]=\sum\limits_{i}[(y_i-1)h_i^\top\beta-\ln(1+e^{-h_i^\top\beta})]。如果用梯度下降来训练这个神经网络,计算可得 \partL\partβ=i\part\partβ[(yi1)hiβln(1+ehiβ)]\dfrac{\part \mathscr{L}}{\part \beta}=\sum\limits_{i}\dfrac{\part }{\part \beta}[(y_i-1)h_i^\top\beta-\ln(1+e^{-h_i^\top\beta})] =i[(yi1)hi+ehiβhi1+ehiβ]=\sum\limits_{i}[(y_i-1)h_i+\dfrac{e^{-h_i^\top\beta}h_i}{1+e^{-h_i^\top\beta}}],代入pi=11+ehiβp_i=\dfrac{1}{1+e^{-h_i^\top\beta}}化简得i(yipi)hi\sum\limits_{i}(y_i-p_i)h_i\partL\partαk=i\partL\parthik\parthik\partαk\dfrac{\part \mathscr{L}}{\part \alpha_k}=\sum\limits_{i}\dfrac{\part \mathscr{L}}{\part h_{ik}}\dfrac{\part h_{ik}}{\part \alpha_k},其中\partL\parthik=\part\parthik[(yi1)hiβln(1+ehiβ)]\dfrac{\part \mathscr{L}}{\part h_{ik}}=\dfrac{\part}{\part h_{ik}}[(y_i-1)h_i^\top\beta-\ln(1+e^{-h_i^\top\beta})] =(yi1)βk+ehiβ1+ehiββk=(yipi)βk=(y_i-1)\beta_k+\dfrac{e^{-h_i^\top\beta}}{1+e^{-h_i^\top\beta}}\beta_k=(y_i-p_i)\beta_k\parthik\partαk=\partmax(xiαk,0)\partαk=Xi1[xiαk>0]\dfrac{\part h_{ik}}{\part \alpha_k}=\dfrac{\part \max(x_i^\top\alpha_k,0)}{\part \alpha_k}=X_i\cdot \mathbb{1}[x_i^\top \alpha_k>0]。代入可得\partL\partαk=i(yipi)βkxi1[xiαk>0]\dfrac{\part \mathscr{L}}{\part \alpha_k}=\sum\limits_{i}(y_i-p_i)\beta_k x_i\cdot \mathbb{1}[x_i^\top \alpha_k>0]。我们看到,在对β,α\beta,\alpha的梯度中都出现了因子yipiy_i-p_i,只要这因子不为0我们就会继续训练,可见我们的算法正是从yiy_ipip_i的误差中学习的。随着训练的进行,这一误差会越来越小。这个过程通过最后一层的输出pip_i来反向修改前几层的参数β,α\beta,\alpha\cdots,称为“反向传播”(Back-propagation)。

Multiclass Classification(多分类问题)

如果要分类的数据不再是简单的分为两类而是多类,该如何建立模型呢?例如,在手写数字识别的问题中,对于给定的输入(图片),我们必须输出它是0~9这十类中的哪一类。

在二分类问题中,逻辑回归给出了对分类的一个概率。而由于是二分类,给出一个概率相当于给出了一个分布。在多分类问题中,我们依然希望它给出的是一个分布。为此,我们需要在神经网络的最后一层把逻辑回归替换为以下的Softmax回归:以手写数字识别为例,假设输入XiX_i通过神经网络的前向传播已经变成了1010元的输入向量(z1,z2,,z10)(z_1,z_2,\cdots,z_{10})。我们使用如下Softmax函数把这任意的1010个实数映射成一个概率分布:pk=ezki[n]ezip_k=\dfrac{e^{z_k}}{\sum\limits_{i\in[n]}e^{z_i}}。这可以看作是对sigmoid函数的一种推广。最后,我们选取最大的pip_i输出我们的判断y^=i\hat y=i

那么,代价函数是什么呢?在逻辑回归中,我们的代价函数最终写作了J(β)=i=1n[yiXiβlog(1+eXiβ)]J(\beta)=-\sum\limits_{i=1}^{n}\left[y_i^*X_i^\top \beta-\log(1+e^{X_i^\top \beta})\right]。我们只考虑其中一项(并忽略最前面的负号)yizlog(1+ez)y_i^*z-\log(1+e^z),当yi=0y_i=0时,它就是log(1+ez)=log11+ez=-\log(1+e^z)=\log\dfrac{1}{1+e^z}= log(111+ez)=log(1sigmoid(z))\log(1-\dfrac{1}{1+e^{-z}})=\log(1-\text{sigmoid}(z))。当yi=1y_i=1时,它就是zlog(1+ez)=logez1+ezz-\log(1+e^z)=\log\dfrac{e^z}{1+e^z} =log11+ez=log(sigmoid(z))=\log\dfrac{1}{1+e^{-z}}=\log(\text{sigmoid}(z))。而我们知道,(1sigmoid(z),sigmoid(z))(1-\text{sigmoid}(z),\text{sigmoid}(z))实际上就是模型预测的概率分布。所以逻辑回归的代价函数只是把真实值对应的概率取对数做了累加。仿照这样的方式,我们可以写出多分类问题的代价函数J(z1,,zn,yi)=i=1n1[i=yi]logezij[n]ezjJ(z_1,\cdots,z_{n},y_i^*)=-\sum\limits_{i=1}^{n}\mathbb{1}[i=y_i^*]\cdot \log\dfrac{e^{z_i}}{\sum\limits_{j\in[n]}e^{z_j}}

模型评估

我们知道一个在训练集上最优化代价函数的模型不一定有最好的表现,因为这样训练的结果可能是过拟合的。一个解决方法是,我们把训练数据集分为两部分,一部分称为training set(训练集),一部分称为test set(测试集)。我们只用训练集上的数据做训练,然后用测试集做测试。如果模型在训练集上误差很小,而在测试集上产生了很大偏差,那么说明我们的训练方法是会导致过拟合的。反之,如果在测试集上也表现良好,那么这就是一个相对优秀的模型。

然而事实上,这只是一个评估方法,而不能真正作为一个帮助我们调整模型的方法。如果我们根据测试集返回的结果调整我们的模型,例如调整多项式拟合的次数dd或神经网络的层数dd,那么相当于我们是在用测试集训练出了一个参数dd。也就是说,我们依然是在使用全部的数据做训练,这样最终依然是会导致过拟合的。为此,我们实际上要把数据分为三部分。训练集和测试集依然保留,再多划分一部分数据称为development set(开发集)或cross validation set(交叉验证测试集)。我们用训练集训练以后,根据dev set中误差的大小选定最好的模型,最后再用test set做测试。这样才是一个有效的选择模型的方法。