AI教程

CS224N中文笔记(3)神经网络基础知识-上

本文为2019版CS224N中文笔记系列第三篇文章。本节中我们将会快速回顾神经网络的基本知识,并尝试用神经网络来解决NLP中一个经典的问题——命名实体识别。

CS224N中文笔记又是拖了两周才写出来。拖延症是该治一治了……我尽力早日完成该系列内容……

往期文章:

1. 机器学习问题分类

1.1 监督学习、非监督学习与强化学习

监督学习(Supervised Learning),指我们有一数据集,数据集中每条数据都有特征X(自变量)和标记Y(因变量)。我们想要使用某个算法让计算机学习到从X到Y之间的映射,学习完后,当计算机见到从未在训练集中出现的特征X时,其可以准确地预测出其对应的标记Y。

监督学习可运用到的场景如:给定一个人的X光照片,推断其是否骨折(特征X为照片数据,标记Y为 是/否);给定一个房子的房屋面积、地理坐标等信息,预期该房间的房价(特征X为房屋信息、标记Y为房屋价格)

机器学习可以分为监督学习、非监督学习和强化学习

非监督学习(Unsupervised Learning)则是指计算机自己探求无标记Y的数据之间的关系,如让计算机根据用户的观影记录(只有用户的观影记录,即只有特征X,没有标记Y)自动给用户分类,并向用户推荐他们可能会喜欢的电影。

另外,我们可以不提前准备数据,而是建立一个环境(Environment)。我们让一个智能体(Agent)在环境中不断执行各种各样的动作(Action),从而到达不同的状态(State)。环境会在智能体每次执行一个动作后,根据其所在的状态,给与智能体一个奖励(Reward)。智能体的目的是不断与环境交互,从而找到使其获得的奖励最大/最小的策略(π)。该方法即为强化学习(Reinforcement Learning)。大名鼎鼎的Alpha go就是用该方法实现的。

强化学习示意图

1.2 分类与回归

无论是神经网络,还是经典的机器学习算法如朴素贝叶斯、SVM、逻辑回归、k近邻算法,都属于监督学习(严格上来讲,神经网络也可以进行非监督学习)。

监督学习可以细分为分类问题和回归问题。

分类问题中,我们数据中的标记Y有有限个可能取值。比如根据预测一个人是否得病(二分类,是/否)又或者预测一段新闻属于什么类型(多分类,科技/政治/娱乐/体育……)

回归问题中,我们的标记Y的有无限个可能取值。如根据房屋信息预测房价,标记Y的取值范围在[0, +∞)。

1.3 分类算法举例——softmax

机器学习中有很多典型的分类算法,如k近邻、线性分类、SVM等等。

这里,我们会介绍一个我们以后常见的分类算法——softmax

softmax是一个多分类算法,即给定特征X,softmax可以告诉我们其对应的特征Y是多种可能取值中的哪一个。如给定一篇新闻(特征X),我们预期其对应的新闻题材(Y取值为体育、政治、娱乐、科技……有限种可能中的一个)。

假设标记Y的可能取值有C种,那么已知一个样本的特征x,该样本的标记是y的概率为P(y|x):

softmax公式

上图算法即为softmax公式。其中W是需要我们学习的参数。

我们得到的P(y|x)是我们通过算法预测到的概率,为了评价该预测的结果好坏,我们会计算其交叉熵(Cross entropy):

交叉熵

q(c)为算法预测样本的标记y是c的概率,取值在0-1之间。p(c)为样本的标记y客观上是否真的为c,取值为0(样本的标记不是c)或1(样本的标记取值是c)。

对训练集中所有样本的交叉熵取平均值,就得到了损失函数(loss function/cost function)

损失函数 – 交叉熵

如果我们的算法预测的效果很好,则损失函数的取值应该很小。通过梯度下降法可以不断更新我们算法中的参数W,从而使损失函数的取值达到一个极小点,此时,我们可以人为算法学习到了训练集中特征X到标记Y之间的关系(当然,还要考虑过拟合的问题)。

此处只是softmax的一个简短介绍,读者若对机器学习感兴趣,可看吴恩达的CS229课程。(另外,笔者也在更新机器学习算法详解系列文章,只不过更新速度比CS224N中文笔记还慢……)

2. 神经网络-基础

2.1 神经网络结构

这里仅为神经网络基础知识的快速总结。读者若未曾系统地学习神经网络,可学习吴恩达的DeepLearning课程

神经网络,故名思议,其结构上参考了人脑中神经连接的方式:

我们也类似如上图的结构,将前一层神经元们的信号经过处理后传到下一层,下一层神经元们的信号经过处理后再传到下下层

如下图,上一层神经元的信号X1、X2、X3经过处理后传到下一层橙色神经元处。

仅仅传递一层往往是不够的,我们可以构建一个传递多层的神经网络。

让传递的层数再多点:

2012年惊艳众人的图像识别网络AlexNet只有8层,后来的VGG网络有16层和19层两个版本,再之后残差网络ResNet有50、101和152层,再到现在上千层的神经网络也不在少数。(有时,我们也会将“层数”称为“深度”)

如今的神经网络的确是往bigger and lagger的方向上来走,(摩尔定律可能在硬件领域已经不再适用了,不过在神经网络大小这个方向上定律十分适用。另外,适用于摩尔定律的还有每年发布的深度学习paper的数量)不过过大的网络会带来梯度爆炸、梯度消失等问题,这些问题我们会在之后的内容中讲到。

值得注意的是,我们上面所说的神经网络的层数、每一层中神经元的个数都是人们根据经验给定的,没有数学原理作为支撑。

不过数学上可以证明的是,神经网络层数越深、每层上神经元的个数越多,该神经网络可以拟合更复杂的函数、应用于更复杂的领域。

2.2 神经网络数学原理

那神经元之间如何传递信息呢?我们先研究第一层中全部神经元信号传递到第二层中一个神经元的过程,即如下图

每个神经元都存储着一个浮点数,我们用向量X = [X1, X2, X3]来表示第一层的三个神经元。于是我们可以用矩阵运算的方式来计算X1, X2, X3三个神经元对第二层橙色神经元的影响:

我们先同样定义一个向量W,其长度和向量X相同。向量W的中数据的取值在(-∞, +∞),W中的第i位代表第一层神经元X中第i个神经元对橙色神经元的重要程度。Wi大,说明橙色神经元与Xi正相关;Wi为负,说明橙色神经元与Xi负相关;Wi接近0说明橙色神经元与Xi几乎无关。

因此我们用 W*X(由于符号显示不便,矩阵的转置符号此处省略) 表示第一层神经元们的线性加权,该值的绝对值越大,则说明第一层神经元们有一个很重要的信息想要传送给橙色神经元;该值绝对值接近零,则说明第一层神经元们没有什么重要信息需要传递给橙色神经元。

一般,我们会用W*X+b来表示第一层神经元们的线性加权,其中b是偏置(bias)有称阈值,可以理解为只有当W*X的取值远大于-b或远小于-b时才可以激活橙色神经元

W和X都是大小为L1*1的向量(L1为第一层神经元们的个数),b为一个标量。

有的时候为了便于矩阵计算,我们会将b并入W中,即W’ = [W1, W2, W3, b], X’ = [X1, X2, X3, +1] 这样W’*X’就等于之前的W*X+b了。这也是为什么有的神经元示意图中每层末尾都会有“+1”符号。

经过上述操作后W*X+b的值就可以直接传送给橙色神经元了吗?并不可以!我们还需要一个非线性的激活函数,原因可见2.3节。

激活函数的作用是将原始的数据进行非线性变换,一个典型的代表就是我们上一篇文章中讲过的sigmod函数:y=1/(1+e^(-x))

sigmod函数

其可以将(-∞, +∞)的数据变换到(0, 1)。

综上所述,前一层的所有神经元想要把信号传送给下一层神经元中的一个神经元需要先进行W*X+b的线性变换,之后再经过一个非线性的激活函数,如sigmod激活函数。由激活函数得出来的值即为下一层神经元的取值。

我们称该步骤中的W和b为模型的参数其的取值是需要算法自行学习的。激活函数不用学习,激活函数长什么样是我们人为选定的,没有可训练的参数。

上述我们所进行的操作仅仅是前一层所有神经元信息传递到下一层一个神经元的过程。若下一层有N个神经元,则上述的 x’ = sigmod( X*W + b ) 过程需要进行N遍(矩阵运算可以同时进行N遍这样的操作)。

且W和b的取值不是共享的:下一层神经元不同,其所对应的W和b参数也不同。(很好理解,如果W和b对每一个下一层神经元来说都一样,那么下一层神经元的取值则完全相同,每个神经元学习到的东西都一模一样,这是十分冗余的)

2.3 激活函数

2.3.1 为何使用激活函数?

2.2节中,我们讲到,上一层神经元的数据想要传递到下一层,需要经过两步:

第一步:对上一层数据进行线性加权,Z = W*X + b

第二步:对线性加权后的值进行非线性变换,Y = f(Z),其中函数f是一个非线性函数。

步骤示意图

为什么必须要使用激活函数?神经网络中每层神经元之间数据传递只用线性加权,不用进行非线性变换可不可以?

假设我们有一个如下图的神经网络(我们统计一个神经网络的层数时,不会考虑输入层。因此下图的神经网络为一个三层的神经网络):

由上图可知,输入层X0维度为3*1,隐藏层X1的维度为4*1,隐藏层X2的维度为4*1。输入层X0到隐藏层X1的线性加权参数W1维度为3*4,偏置b1的维度为4*1;隐藏层X1到隐藏层X2的线性加权参数W2维度为4*4 ,偏置b2的维度为4*1。(分析各个参数的维度有助于更好地理解神经网络)

下面的计算中,为了计算简便,我们忽略偏置b(有偏置b的结果相同)。

如果不使用激活函数(非线性变换)的话,隐藏层X1可以用X0表示为:

那么隐藏层X2可以用X1表示为:

经过推导后我们发现,X2竟然也可以用X0的线性变换来表示!这就意味着无论是多深的神经网络,只要不使用激活函数(非线性变换),其效果和层数为1的神经网络是一样的。

不使用激活函数的神经网络其本质是Y = W*X+b,是一个线性分类器。线性分类器的一个特点是用二维图形展示线性分类器的分类效果时,其分类边界是一条直线(softmax也属于线性分类器)

线性分类器可以进行简单的分类,但是无法处理像“异或”这样稍微高级一点的逻辑(线性不可分的问题)。

假设我们的特征X是一个二维数据,Y的取值有两种,绿色或红色。那么我们可以用下图直观地展示使用激活函数和不使用激活函数的效果:

线性分类器和使用激活函数的神经网络。

图中每个点为训练数据,而背景颜色为训练完后的神经网络预测的结果。

左侧的图即为不使用激活函数的网络,是线性分类器。可以看到,有很多绿色的点被误判为红色。线性分类器的学习能力有限,左侧的图已经是“用一条线划分红绿区域”情况下最佳的结果了。

而右侧的图即为使用激活函数后的神经网络预测的结果。此时,神经网络可以捕获训练数据集中更多有用的信息,学习出更复杂的逻辑,其分类边界也更复杂。

2.3.2 激活函数举例

激活函数的目的就是为了让神经网络中每层神经元之间传递的数据不是简单的线性关系。非线性关系的数据可以让神经网络学习到更复杂的逻辑。

常用的激活函数有很多。在神经网络刚刚提出时(上个实际八十年代),人们很喜欢用sigmoid函数(下图中左一),但是现在人们反而很少使用sigmoid了,除非是作为二分类神经网络的最后一层。

sigmoid,tanh和hard tanh激活函数

其原因为sigmoid函数在无穷大和无穷小处的梯度都极小,这就导致在我们用梯度下降法进行参数更新时,每轮中参数变化的幅度很小,损失函数收敛速度极慢

tanh在NLP中很常用,尤其是在LSTM和GRU中。不过通过计算可以发现,tanh其本质就是2*sigmoid-1 。

hard tanh激活函数是tanh的变体,当x的绝对值大于1时,函数的梯度为0;当x的绝对值小于1时,函数的梯度为1。这样便捷了反向传播时的计算。

ReLU是如今最受欢迎的激活函数,y = max(0, x)。其和hard tanh比较相似:当神经元上的数值小于0时,激活函数的梯度为0,不更新该神经元对应的参数W和b;而当神经元上的参数大于0时,激活函数的梯度为1,更新该神经元对应的参数W和b。

ReLU激活函数

使用ReLU,X小于零时,对应的参数不会被更新。有人认为,我们也可以更新那些X小于零的神经元对应的参数,不过更新的梯度要小一点,比如x>0梯度为1,x<0梯度为0.1(如果x<0时梯度也为1,那就和不使用激活函数一样了,最后得到的神经网络就是一个线性分类器)

于是,就有了LeakyReLU:y = max(0, x) + leak*min(0,x) 。

leakyReLU

除了leakyReLU,ReLU激活函数的变体还有很多,如ELU(下图绿线),其比ReLU更加的平滑。

实验证明,ReLU的效果很好。但ReLU缺少数学上合理的解释。GELU(上图蓝线)是一种高性能的神经网络激活函数,且有一定的数学原理。NLP领域中大名鼎鼎的BERT模型就是使用GELU作为激活函数的。

读者若是刚入门深度学习,激活函数使用ReLU就足够了。

3. 神经网络应用案例——命名实体识别

经过之前那么的铺垫,我们终于开始进行第一个NLP的任务:命名实体识别(Named Entity Recognition)

3.1 何为命名实体识别

命名实体识别(Named Entity Recognition)简称NER,目的是找到并分类文本中的实体。如“崔永元曾经是中央电视台的主持人”中的实体有“崔永元”、“中央电视台”和“主持人”,除了找出这些实体,我们还希望AI可以识别出“崔永元”是人名、“中央电视台”是组织机构名、“主持人”是职位。

命名实体识别是NLP中的一项基础任务,也是一项比较重要的任务。因为一句话中重要的语义信息往往就蕴含在实体之间的关系中,我们在进行问答时,答案也常常是实体名称。

命名实体识别

我们现在的任务是,给定任意一段话,如“我今天去了游乐园”,我们希望找到该语句中的实体,如“我”、“游乐园”。(更高级的任务还包括给实体分类,“我”属于代词,“游乐园”属于地名)

也许你会觉得,这个问题太简单了,完全不需要使用深度学习来实现,人为建一个字典就行了,这个字典中包含我们提前定义的实体名称,只要语句中的词出现在字典中,那这些词就肯定是实体:当我们在一句话中看到“你”、“他”、“她”,“我”这样的词时,这些词肯定是实体,属于代词;当文本中有“游乐园”、“餐厅”、“车站”时,这些词肯定也是实体,属于地名……

只要按照如上步骤,就可以快速地找到一句话中的实体,哪还需要用深度学习来实现啊!

但是,无论是什么语言,都会存在一词多义的情况:英语中”To sanction” 可以理解为”to permit”(允许)或者 “to punish”(惩罚);中文中的“可怜”一词,在“我好可怜”中,“可怜”是形容词,而在“你们可怜可怜我吧”中,“可怜”又成了动词……类似的例子数不胜数,我们往往需要依靠一个词的上下文才能明白该词的意思。

而且在“南京市长江大桥”这样的话中,不考虑上下文的实体识别可能会出现分成“南京”、“市长”、“江大桥”这样的笑话。

3.2 如何实现命名实体识别

注:CS224N中的命名实体识别(NER)的深度学习算法有些过于简化,以至于若真正使用该算法进行NER任务,效果可能并不好。这里,笔者将会给出课程原版的算法和笔者改进版算法。

3.1中,我们讲到我们最好通过上下文来理解一个词。因此,我们让神经网络判别一个词是否是实体时,我们不仅告诉神经网络这个词是什么,我们还要告诉神经网络这个词前后N个词是什么。

比如N取2,那么我们可以向神经网络输入X=[museums, in, Paris, are, amaing],让神经网络通过“Paris”的上下文来推断“Paris”是否为实体。

这里,每个单词都由一个D维词向量来表示。(关于词向量的知识,可见第一讲第二讲

3.2.1 网络结构

假设每个单词都是的词向量维度D为4(取4仅仅是为了绘图方便,正常情况下,词向量维度在100-300之间),那么输入的数据的长度为5*4。我们可以构建一个输入节点个数为5*4,输出节点个数为1的神经网络。如下图:

该神经网络示意图中,输入层长度为100,只有一层隐藏层,其长度为8,输出层长度为1。这里激活函数只在隐藏层中使用,在输出层不使用。

(笔者改进版)上图的神经网络仅仅是一个示意,结合我们第2节所讲的知识,你可以自行DIY你的神经网络。如输入9个单词,每个单词的词向量维度为100。那么输入层的长度为900,之后我们再设第一层隐藏层维度为300,第二层隐藏层维度为100,第三层隐藏层维度为50,最后的输出层维度为1。其中输出层的激活函数是sigmoid,所有隐藏层的激活函数均为ReLU。

3.2.2 目标函数

经过神经网络后,输出层最终会返回一个数值。那么如何通过输出层的数值来判断输入的文本是不是实体呢?又如何通过判断我们的算法预测效果的好坏呢?

我们取一对训练样本,如一个正样本[“我”,“去”,“图书馆”, “看”, “西游记”](词“图书馆”是实体)和一个负样本[“今天”,“天气”,“很”,“好”,“啊”](词“很”不是实体)。(这里假设中文文本已经提前分词完成,且不考虑“南京市长江大桥”被分词成“南京”,“市长”,“江大桥”这样的尴尬情况)

我们希望正样本经过神经网络后,神经网络的输出数据是一个比较大的数,而负样本经过神经网络后,神经网络的输出数据是一个比较小的数。

我们设每一对训练样本中,正样本经过神经网络后的输出数据为S,而负样本经过神经网络后的输出数据为Sc 。为了满足我们上述的希望,即S大,Sc小,我们设目标函数为 J = max(0, 1 – S + Sc), 最小化目标函数 J 的取值即可完成我们S取值大Sc取值小的愿望。

当S的取值比Sc大很多时(这里是S比Sc大1)1-S+Sc就会是负数,那么J = max(0, 1 – S + Sc) 就能取到最小值0。若S比Sc小,或者S并不比Sc大多少时,1-S+Sc就会是一个正数,目标函数J就是一个大于0的数,其还有继续优化的空间。

这里 1 – S + Sc中的 1 可以是2、3、100……只要大于零即可,其目的是让正样本的输出S和负样本的输出Sc拉开一定的差距。

知识补充:这里max(0, ∆ − S + Sc)也称为 Hinge Loss 或者 max-margin objective function, 是SVM中常见的一个目标函数。另外,训练人脸识别模型时,有时也会选其作为目标函数。

(笔者改进版)笔者还是习惯以交叉熵作为损失函数。接着我们3.2.1末尾介绍的自己DIY设计的神经网络模型。只要你输出层使用了sigmoid或者softmax函数让其取值压缩在0-1之间,你就可以使用交叉熵作为损失函数。(输出层只有一个节点时用sigmoid,输出层有多个节点时用softmax。读者可以自己推导一下,而二分类问题中,softmax就是sigmoid)

交叉熵

输出层用sigmoid/softmax + 交叉熵 的组合相较于输出不用激活函数+目标函数为Hinge Loss的组合而言有很多优点。其中最明显的一个是前者的输出层的输出值就是该模型预测样本为对应类别的概率。如输出层若有3个节点,其输出值分别为0.1, 0.7, 0.2 ,那么这意味着模型认为样本是第一个类别的概率为0.1,是第二个类别的概率为0.7,是第三个类别的概率为0.2。

3.2.3 更多改进方向

上述内容中我们只考虑了一个词是不是命名实体,没有给改实体进行分类。可以考虑将我们所讨论的模型从二分类问题改为多分类问题。输出层的维度不再是1,而是N。如N取4,考虑一个单词是属于[非实体, 人名, 地名, 组织机构名]中的哪一个。若属于“人名”,则输出层中对应“人名”的结点取值应该很大,其他结点取值很小。

再另外,对于未分词的中文文本,如输入不是[ “我”, “今天”, “去”, “游乐园” ] ,而是[ “我”, “今”, “天”, “去”, “游”, “乐”, “园”],此时我们当然可以还是把其当成一个二分类问题,即每个字是不是实体词的一部分,“我”, “游”, “乐”, “园”是实体词的一部分, 而“今”, “天”, “去”这三个字不是。我们也可以把其当成一个三分类问题:[实体词开头,实体词结尾,其他词]

3.2.4 是否更新词向量?

在上述讨论中,词向量都是提前预训练好的(可以使用word2vec,glove等方法,词向量预训练可见第二讲

预训练的词向量中词义相近的词其词向量很相近。TV、telly和television是同义词。

训练时不更新词向量:假设我们的神经网络在训练时只见过TV和telly,并将其分类为红色,由于television的词向量和TV、telly很接近,因此即使神经网络没有在训练集中遇到television这个词,但它还是成功地把television分类为红色。

训练时更新词向量:神经网络更新了TV和telly的词向量,但其没有在训练样本中见过television,所有也就没有更新television的词向量。最后telly和TV被正确地分为红色,但television虽然与TV、telly语义相同,但是被分为了绿色。

那么这样看来训练时更新词向量很危险,所有就不更新词向量,只用预训练的词向量?不是这样的。在训练样本巨大的时候,训练时更新词向量会带来更好的结果

那我们该如何使用词向量呢?

首先,在条件允许的情况下,我们应该使用预训练的词向量(Almost always)。预训练的词向量已经捕获了很多有用的语义和语法信息。使用预训练的词向量来训练神经网络,其收敛速度远远快于随机初始化词向量,再训练神经网络。

其次,当我们训练样本很小时,不要更新预训练的词向量。当我们的训练样本很大时更新预训练的词向量。(关于训练样本有多大才可以更新词向量,没有很明确的界限。笔者认为,起码上万句语料吧)

第三,更新词向量的方法叫fine-tune(迁移学习中也会用到),更新词向量的学习率α要很小,因为词向量已经捕获了语义信息,我们只需要对词向量进行微调即可。而且在神经网络训练刚开始时不更新词向量,等目标函数收敛到一定程度时才开始训练词向量。因为神经网络开始时是随机初始化的,此时要是更新词向量,只会降低词向量的表现效果。

更新词向量我们将会使用反向传播。关于反向传播的相关知识以及神经网络的更多基础知识,我们将会放在下一讲中一一讲述。

总结

  • 机器学习任务可分为监督学习、非监督学习和强化学习。其中监督学习包括分类任务和回归任务。
  • 神经网络参照了人脑的神经网络结构,随着数据量和计算能力的提升,深度学习逐渐成为了AI的热门领域。
  • 不使用激活函数的神经网络是线性分类器,效果很差。我们常用ReLU作为激活函数。
  • 执行NLP任务时,建议使用预训练词向量。当我们有大量训练文本时,可以更新词向量。

额外内容

【注:本文部分配图来自于网络】

发表评论