CS224N(1.24)Language Models and RNNs

今天要介绍一个新的NLP任务——语言模型(Language Modeling, LM),以及用来训练语言模型的一类新的神经网络——循环神经网络(Recurrent Neural Networks, RNNs)。 语言模型就是预测一个句子中下一个词的概率分布。如下图所示,假设给定一个句子前缀是the students opened their,语言模型预测这个句子片段下一个词是books、laptops、exams、minds或者其他任意一个词的概率。形式化表示就是计算概率 $$\begin{eqnarray}P(x^{(t+1)}|x^{(t)},…,x^{(1)})\tag{1}\end{eqnarray}$$\(x^{(t+1)}\)表示第\(t+1\)个位置(时刻)的词是\(x\),\(x\)可以是词典\(V\)中的任意一个词。 既然语言模型在给定前t个词之后可以预测第t+1个词的概率,那么预测到第t+1个词之后,又可以递归的根据前t+1个词预测第t+2个词,如此不断的进行下去,就可以预测一整个句子的概率了。所以,也可以把语言模型看做一个可以计算一个句子出现的概率的系统,形式化表示就是如果一个句子是\(x^{(1)},…,x^{(T)}\) ,那么语言模型可以计算句子概率 $$\begin{eqnarray}P(x^{(1)},…,x^{(T)})& = & P(x^{(1)})\times P(x^{(2)}|x^{(1)})\times…\times P(x^{(T)}|x^{(T-1)},…,x^{(1)}) \tag{2}\\& = & \prod_{t=1}^T P(x^{(t)}|x^{(t-1)},…,x^{(1)})\tag{3}\end{eqnarray}$$可以看到(3)式连乘的项就是(1)式,所以这两个定义的内涵是一样的。 那么语言模型有什么用呢?它的用处可大了,比如现在的输入法会根据前一个输入的词预测下一个将要输入的词,此所谓智能输入法;比如在百度或谷歌搜索时,输入前几个关键词,搜索引擎会自动预测接下来可能的几个词;比如网上有很多智能AI会自动生成新闻、诗歌;还比如用在语音识别、机器翻译、问答系统等等。可以说语言模型是很多NLP任务的基础模块,具有非常重要的作用。 在前-深度学习时代,人们使用n-gram方法来学习语言模型。对于一个句子,n-gram表示句子中连续的n个词,比如还是上图的例子,n-gram对于n=1,2,3,4的结果是: 1-grams (unigrams): “the”, “students”, “opened”, “their” 2-grams (bigrams): “the students”, “students opened”, “opened their” 3-grams (trigrams): “the students opened”, “students opened their” 4-grams: “the students opened their” n-gram方法有一个前提假设,即假设每个词出现的概率只和前n-1个词有关,比如2-gram对于每个词出现的概率只和前面一个词有关,和更前面的词以及后面的词都没有关系,所以我们有如下图的assumption。又这是一个条件概率,展开之后得到如下除法的形式。n-gram的计算方法就是,统计语料库中出现\(x^{(t)},…,x^{(t-n+2)}\)的次数(分母),以及在这个基础上再接一个词\(x^{(t+1)}\)的次数\(x^{(t+1)},x^{(t)},…,x^{(t-n+2)}\)(分子),用后者除以前者来近视这个条件概率。 举个例子,假设完整的句子是as the proctor started the clock, the students opened their,需要预测下一个词的概率分布。对于4-gram方法,则只有students opened their对下一个词有影响,前面的词都没有影响。然后我们统计训练集语料库中发现,分母students opened their出现1000次,其后接books即students opened their books出现了400次,所以P(books|students opened their)=400/1000=0.4。类似的,可以算出下一个词为exams的概率是0.1。所以4-gram方法认为下一个词是books的概率更大。 ...

July 31, 2019 · 2 min

CS224N(1.15 & 1.17)Backpropagation

这篇博客把1.15和1.17两次课内容合并到一起,因为两次课的内容都是BP及公式推导,和之前的Neural Networks and Deep Learning(二)BP网络内容基本相同,这里不再赘述。下面主要列一些需要注意的知识点。 使用神经网络进行表示学习,不用输入的x直接预测输出,而是加一个中间层(图中橙色神经元),让中间层对输入层做一定的变换,然后中间层负责预测输出是什么。那么中间层能学到输入层的特征,相当于表示学习,自动学习特征。对于word2vec,中间层就是词向量。 命名实体识别(Named Entity Recognition, NER),任务是把一个句子中的一些实体词识别出来,比如下图中识别出地点LOC、机构ORG和人名PER等。通常采用的方法是把需要判断类型的词及其周围的几个词的词向量拼接起来,输入到神经网络进行分类。但是由于一个句子中真正的实体词较少,而很多其他词Others会很多,导致样本不均衡,此时需要进行采样,具体方法可以搜索怎样处理NER样本不均衡的问题。 我们知道词向量其实是NLP实际任务中的副产品,任何一个NLP任务都可以得到词向量。这就存在一个问题,当我们在实现一个具体的NLP任务时,是使用预训练的词向量,还是根据实际任务现场训练一个词向量呢?建议是,如果有可用的预训练词向量,则最好使用预训练词向量。因为预训练的词向量通常在很大规模的数据集上进行过训练,词向量的质量还不错,而某个具体的NLP任务的样本数可能不太多,导致训练得到的词向量还没人家预训练的好。所以,如果实际任务的数据量较小,则用预训练的词向量;否则,可以尝试一下根据实际任务fine tune词向量。 这节课的核心就是不断使用链式法则对BP算法求导,然后反向传播。在反向传播的过程中,可以利用上游计算好的梯度,增量式的更新下游的梯度,如下图所示,就是公式[downstream gradient] = [upstream gradient] × [local gradient]。这个在之前介绍BP网络的时候也提到过,其实就是那篇博客的(BP2)公式,误差对\(w\)的偏导可以通过误差对\(b\)的偏导乘以神经元输出得到。 当有多个输入的时候,也是一样,只不过local gradients变为了多个分支。 很有意思的是老师总结到:加法相当于把上游的梯度分发给下游;max相当于路由;乘法相当于开关。听老师讲下面的实例会有切身的体会。 当然,现在的流行的神经网络框架都帮我们完成了自动求导,我们只需要把local gradient定义好,框架会自动帮我们进行反向传播。需要定义的就两点,一个是正向经过该神经元,output=forward(intput);另一个是反向经过该神经元时,input_gradient=backward(output_gradient)。下面第二个图是一个很简单的例子,定义了forward和backward两个操作。 最后,简要介绍了6个注意事项: 使用正则化避免过拟合 使用python的向量和矩阵运行,而不是for循环,前者相比于后者有~10x加速 目前流行的非线性激活函数是ReLU,Sigmoid和tanh比较少用了 参数(权重)初始化,初始值最好是随机的很小的值,有一些专门的策略,如Xavier sgd优化器效果还不错,不过目前流行的优化器是Adam 学习率最好是10的倍数,而且可以成10倍的放大或缩小;一些fancy的优化器会对设定的学习率进行逐步缩减(比如Adam),所以对于这些优化器,一开始的学习率可以设大一点,比如0.1 最后是本周作业,包含两部分内容,一部分是手动求导,编辑公式太麻烦了,我就写在了纸上,大家可以参考这位仁兄的解答。另一部分是根据手动推导的梯度公式,补充word2vec算法中的求解梯度的算法以及sgd更新公式,如果是第一次接触这方面内容,可以参考这位仁兄的实现。 但是,根据上一篇博客的介绍,word2vec除了可以根据作业中的极大似然的方法求解,还可以用3层全连接网络来实现,相比于极大似然更简洁也更容易理解,具体可以参考这篇博客以及这篇具体实现。

July 29, 2019 · 1 min

Neural Networks and Deep Learning(二)BP网络

这一讲介绍误差反向传播(backpropagation)网络,简称BP网络。 以上一讲介绍的MNIST手写数字图片分类问题为研究对象,首先明确输入输出:输入就是一张28×28的手写数字图片,展开后可以表示成一个长度为784的向量;输出可以表示为一个长度为10的one-hot向量,比如输入是一张“3”的图片,则输出向量为(0,0,0,1,0,0,0,0,0,0,0)。 然后构造一个如下的三层全连接网络。第一层为输入层,包含784个神经元,正好对应输入的一张28×28的图片。第二层为隐藏层,假设隐藏层有15个神经元。第三层为输出层,正好10个神经元,对应该图片的one-hot结果。 全连接网络表示上一层的每个神经元都和下一层的每个神经元有连接,即每个神经元的输入来自上一层所有神经元的输出,每个神经元的输出连接到下一层的所有神经元。每条连边上都有一个权重w。 每个神经元执行的操作非常简单,就是把跟它连接的每个输入乘以边上的权重,然后累加起来。 比如上面的一个神经元,它的输出就是: $$\begin{eqnarray}\mbox{output} = \left\{ \begin{array}{ll}0 & \mbox{if} \sum_j w_j x_j \leq \mbox{ threshold} \\1 & \mbox{if} \sum_j w_j x_j > \mbox{threshold}\end{array}\right.\tag{1}\end{eqnarray}$$其中的threshold就是该神经元激活的阈值,如果累加值超过threshold,则该神经元被激活,输出为1,否则为0。这就是最原始的感知机网络。感知机网络也可以写成如下的向量形式,用激活阈值b代替threshold,然后移到左边。神经网络中,每条边具有权重w,每个神经元具有激活阈值b。 $$\begin{eqnarray}\mbox{output} = \left\{ \begin{array}{ll} 0 & \mbox{if } w\cdot x + b \leq 0 \\1 & \mbox{if } w\cdot x + b > 0\end{array}\right.\tag{2}\end{eqnarray}$$ 但是感知机网络的这种激活方式不够灵活,它在threshold左右有一个突变,如果输入或者某个边上的权重稍微有一点变化,输出结果可能就千差万别了。于是后来人们提出了用sigmoid函数来当激活函数,它在0附近的斜率较大,在两边的斜率较小,能达到和阶梯函数类似的效果,而且函数光滑可导。sigmoid的函数形式如下,其中\(z\equiv w \cdot x + b\)为神经元激活之前的值。 $$\begin{eqnarray} \sigma(z) \equiv \frac{1}{1+e^{-z}}\tag{3}\end{eqnarray}$$sigmmoid函数还有一个优点就是它的导数很好计算,可以用它本身来表示: $$\begin{eqnarray}\sigma'(z)=\sigma(z)(1-\sigma(z))\tag{4}\end{eqnarray}$$BP网络的参数就是所有连线上的权重w和所有神经元中的激活阈值b,如果知道这些参数,给定一个输入x,则可以很容易的通过正向传播(feedforward)的方法计算到输出,即不断的执行\(w \cdot x + b\)操作,然后用sigmoid激活,再把上一层的输出传递给下一层作为输入,直到最后一层。 1 2 3 4 5 def feedforward(self, a): """Return the output of the network if ``a`` is input.""" for b, w in zip(self.biases, self.weights): a = sigmoid(np.dot(w, a)+b) return a 同时,网络的误差可以用均方误差(mean squared error, MSE)表示,即网络在最后一层的激活值(即网络的输出值)\(a\)和对应训练集输入\(x\)的正确答案\(y(x)\)的差的平方。有\(n\)个输入则误差取平均,\(\dfrac{1}{2}\)是为了后续求导方便。 ...

December 14, 2018 · 2 min