深度学习与自然语言处理(5)_斯坦福cs224d 大作业测验2与解答

作业内容翻译:@胡杨([email protected]) && @面包君 && Fantzy同学
校正与调整:寒小阳 && 龙心尘
时间:2016年7月
出处:http://blog.csdn.net/han_xiaoyang/article/details/51815683
http://blog.csdn.net/longxinchen_ml/article/details/51814343

说明:本文为斯坦福大学CS224d课程的中文版内容笔记,已得到斯坦福大学课程@Richard Socher教授的授权翻译

0. 前言

原本打算把作业和答案做个编排,一起发出来给大家看,无奈代码量有点大,贴上来以后文章篇幅过长,于是乎题目的代码解答放到了百度云盘,欢迎自行下载和运行或者调整。

1. Tensorflow 与 softmax(20分)

在本题中,我们会执行一个线性分类器,并会用到下面的交叉熵损失函数:

JsoftmaxCE(W)=CE(y,softmax(xW))

(请注意,这里行 x 的是行特征向量)。我们会通过Tensorflow 的自动微分功能来训练数据。

a.(4分)
在脚本q1_softmax.py中,通过TensorFlow来构造softmax 函数。softmax的公式如下:

softmax(x)i=exijexj

请注意,你不能使用Tensorflow内建的tf.nn.softmax函数或者其他相关内建函数。这道题目做好的标准是你能够通过运行python q1_softmax.py命令将这个函数跑起来(需要跑通一个测试用例。当然,不要求完全详尽的测试。)

b. (4分)
在脚本q1_softmax.py中,通过TensorFlow来构造交叉熵损失函数(Cross Entropy Loss)。交叉熵损失函数的公式长这个样子:

CE(y,ŷ )=i=1Ncyilog(ŷ i)

在这里 y5 是一个one-hot标签向量, Nc 是所有类目的数量。请注意,你不能使用Tensorflow内建的cross-entropy函数或者其他相关内建函数。这道题目做好的标准是你能够通过运行Python q1_softmax.py脚本将这个函数跑起来(需要写一个测试用例。当然,这同样不要求完全详尽的测试。)

c. (4分)
请仔细学习model.py脚本中的model类。并简要解释一下其中占位符变量 (place holder vaiables)和填充字典(feed dictionaries)函数的目的. 在q1_classifier.py中填充好add_palceholderscreat_feed_dict这两个函数.
提示: 请注意配置变量(configuration variables)在config类中已经储存好了,而在代码中你会用到它们。
答案:在Tensorflow 计算图中,占位符变量是作为其输入节点而存在的。而填充字典函数说明了在计算的时候怎么给这些占位符(或者其他)变量赋值。

d. (4分)
在脚本ql_classifier.py中的add_model函数里完成一个用softmax进行分类的基本流程。并在同一个脚本中的函数add_loss_op中补充好交叉熵损失函数。 请注意,使用本题之前提供的函数,而不是 Tensorflow 内建函数。

e. (4分)
在脚本ql_classifier.py中填充完成add_training_op 函数。 并解释为什么Tensorflow 的自动微分功能能省去我们直接求梯度的工作。你可以在我们提供的数据上顺利运行python ql_classifier.py命令,并且确保能够跑通测试用例。这样你才能保证你的模型是合适的。
提示:请一定要使用在config类中定义的学习率.
答案:只要正确定义好了计算图,Tensorflow就能自动应用反向传播算法来计算梯度。

2. 神经网络在命名实体识别中的应用(35分)

这一节中,我们会练习反向传播算法和训练深度神经网络,通过它们来实现命名实体识别。命名实体识别是指识别文本中具有特定意义的实体,主要包括人名、地名、机构名、专有名词等。
这其实就是一个5类分类问题(5选1的单选题)。这5类包括上面这4类和1个非实体类——也就是不代表任何实体的类(大部词语都属于这类)。
这个模型是一个单隐藏层神经网络,它有一个类似我们在word2vec中看到的表示层。 这里我们不需要求平均值或者抽样,而是明确地将上下文定义为一个“窗口”,这个“窗口”包含目标词和它左右紧邻的词,是一个3d维度的行向量:

x(t)=[xt1L,xtL,xt+1L]R3d

这里的 xt1,xt,xt+1 是one-hot行向量(|V|维),而 L|V|×d 是嵌入矩阵,它的每一行 Li 其实就代表一个特定的词 i 。我们然后做如下预测:
h=tanh(x(t)W+b1)

ŷ =softmax(hU+b2)

之后通过交叉熵损失函数来评估误差:
J(θ)=CE(y,ŷ )=i=15yilog(ŷ i)

为了计算整个训练集的损失,我们在对每个训练样本计算 J(θ) 之后将其求和(或者求平均值)。
对于本题,令 d=50 作为词向量的长度。3个向量组成了一个宽度为 3×50=150 的窗口。隐藏层有100个神经元,输出层有5个神经元.

(part a)
(5分)请计算损失函数 J(θ) 在下列模型各个参数上的梯度

JU Jb2 JW Jb1 JLi

,这里矩阵的维度分别是

UR100×5 b2R5 WR150×100 b1R100 LiR50

提示:为了能够清晰的表示反向传播的过程,我们给您推荐两个小trick:

  • 使用激活函数的值来代替激活函数的微分。就像作业1中的 sigmoid 函数一样,下面这个函数也可以起到相同的作用。
    tanh(z)=2sigmoid(2z)1
  • 使用“残差向量”的形式来表示某一层的梯度。这意味着我们的链式法则,现在可以写成用括号括起来的向量(矩阵)乘法的形式,这将使您的分析过程大大简化。

值得注意的是,损失函数在模型各参数上的梯度应该化简到可以使用矩阵运算的形式。(通过作业1,相信您已经掌握的很熟练了:))

答案:
由提示公式可得:

tanh(z)=4sigmoid(2z)(1sigmoid(2z))

可得偏微分如下:
JU=hT(yŷ )

Jb2=yŷ 

Jh=(yŷ )UT

JW=(x(t))T(Jhtanh(2(x(t)W+b1)))

Jb1=(Jhtanh(2(x(t)W+b1)))

Jx(t)=(Jhtanh(2(x(t)W+b1)))WT

我们令 i=xt,j=xt1,k=xt+1 ,可得:
Jx(t)=[JLi,JLj,JLk]

(part b)(5分)
神经网络的训练过程往往不是一帆风顺的,许多小问题都会影响模型的性能,例如神经网络中参数过多会导致训练太慢,神经元之间相关性太强会导致模型陷入局部最优。为了避免上述两种情况的发生,我们在(part a)损失函数的基础上增加一个高斯先验。可别小看这个高斯先验,它能使我们的模型参数/权重不会有那么夸张的幅度(更向0值靠拢),同时又能保证大部分参数都有取值(不会对起作用的特征产生太大影响),从而保证了模型在实际场景中的可用性(泛化能力强一些)。混合后的损失函数就长这样。

Jfull(θ)=J(θ)+Jreg(θ)

有了混合损失函数,下面就可以开始将似然函数求最大化了(就像上面我们对交叉熵求最大化那样),高斯先验在这个过程中就会变成一个平方项的形式( L2 正则化项)。

Jreg(θ)=λ2i,jW2ij+i,jW2ij

要对似然函数求极大,我们所采用的方法是梯度上升,所以本题的任务还是请您来求一下梯度。与(part a)中不同的是,我们这里的损失函数多了一个由高斯先验得来的正则化项。所以,聪明的你,给我个答案吧。
答案:

Jfull=J+Jreg

JregW=λW

JregU=λU

W V 的梯度代入即可。

(part c)(5分)
在part b中我们了解到,如果神经元之间的相关性太强,模型就容易陷入局部最优,从而降低了模型的泛化能力。对此,我们的解决方法是使用L2正则化项对模型参数加以限制。在本题中,我们提供另外一种方法,叫做“参数随机初始化”。在众多参数随机初始化的方法中,我们使用最多的是Xavier方法。
Xavier方法的原理是这样的:给定一个 m×n 的矩阵 A 和一个区间[ ϵ,ϵ ],从该范围中进行均匀采样作为 Aij ,其中

ϵ=6m+n

好了,根据算法原理,请你在 q2_initialization.pyxavier_weight_init中,用代码来实现一下吧。

(part d)(20分)
在q2_NER.py中,我们实现了一个命名实体窗口(NER Window)模型。模型的输入是sections(小编注:sections在特定情况下可以看作是一句查询,每个section由一些tokens组成,即分词),输出就是命名实体的标签。您可以看一下代码,您刚刚推导的反向传播过程在代码中已经被实现了,是不是很神奇!?
以下工作需要您来完成:

  • 您需要在q2_NER.py中实现命名实体窗口模型部分的代码,我们会根据算法正确性和程序是否可执行来进行打分。
  • 您需要说明模型最佳的参数的值,主要有正则化项、特征维度、学习速率和SGD批量的大小等。您还需要说明模型在交叉验证集上的误差,本课程要求模型在交叉验证集上的误差不高于0.2。
  • 您需要将测试集的预测结果保存在q2_test.predicted文件中,格式为一行一个label,我们会根据真实结果来评估您模型的泛化能力。
  • 提示1:在debug模式中,请将参数max_epchs设为1,并且将load_datadebug参数设为True
  • 提示2:请注意程序的时间复杂度,保证在GPU上不超过15分钟,CPU上不超过1小时。

3.递归神经网络:语言建模(45分)

在这一节,你将首次实现一个用于建立语言模型的递归神经网络。

语言模型的建立是NLP技术的核心部分,语言模型被广泛使用于语音识别,机器翻译以及其他各类系统。给出一串连续的词 x1,,xt ,关于预测其后面紧跟的词 xt+1 的建模方式是:

P(xt+1=υj|xt,,x1)

其中, υj 是词库中的某个词。

你的任务是实现一个递归神经网络,此网络利用隐层中的反馈信息对“历史记录” xt,xt1,,x1 进行建模。关于 t=1,,n1 形式化的表示如文献[3]所描述:

e(t)=x(t)L(10)

h(t)=sigmoid(h(t1)H+etI+b1)(11)

ŷ (t)=sigmoid(h(t)U+b2)(12)

P¯(xt+1=υj|xt,,x1)=yj^(t)(13)

其中, h(0)=h0Dh 是隐藏层的初始化向量, x(t)L 是以 x(t) 为one-hot行向量与嵌入矩阵 L 的乘积,这个one-hot行向量就是当前处理词汇的索引。具体的一些参数设置如下:
L|V|×dHDh×DhId×Dhb1DhUDh×|V|b2|V|(14)

其中, L 是词嵌入矩阵, I 是输入词表征矩阵, H 是隐藏转化矩阵,还有 U 是输出词表征矩阵。 b1,b2 是偏置值。 d 是词嵌入的维数, |V| 代表词库的规模,然后 Dh 是隐层的维数。

输出向量 ŷ (t)|V| 是面向整个词库的概率分布,我们需要最优化交叉熵(非正则化的)的损失率:

J(t)(θ)=CE(y(t),ŷ (t))=i=1|V|y(t)ilogŷ (t)(15)

其中, y(t) 是与目标词(这里表示为 xt+1 )对应的one-hot向量。正如注释[2]所描述的,它是一个对于单个样本点的损失率,然后我们对序列中全部样例的交叉熵损失值做求和(或求平均)计算,对数据集中的全部序列 [4]均采取上述操作以衡量模型的性能。

(a) (5分)
通常地,我们使用困惑度来评估语言模型的性能,其定义形式如下:

PP(t)(y(t),ŷ (t))=1P¯(xpredt+1=xt+1|xt,,x1)=1|V|j=1y(t)jŷ (t)(16)

即通过模型分布 P¯ 得到词预测正确的逆概率。给出你是如何从交叉熵的损失率中得到困惑度的( 提示:不要忘了向量 y(t) 是one-hot形式的!),因此如果要进行最小化操作(从算术上)意味着(从几何上讲)交叉熵损失率将被最小化,就得到了在训练集上的困惑度。 在这一部分要处理的是一个非常短小的问题-所以并不需要过大的困惑度!
对于一个词库 |V| 中的词汇,如果你的模型做出的预测是完全随机的,你将如何预测你的困惑度呢?计算词库规模为 |V|=2000 |V|=10000 时对应的交叉熵损失率,并且将其作为基准牢记于心。

解答:使用的样例 y(t) 为one-hot模型,且假定 y(t)i y(t) 中唯一的非零元素。于是,记:

CE(y(t),ŷ (t))=logŷ (t)i=log1ŷ (t)

PP(t)(y(t),ŷ (t))=1ŷ (t)i

因此,上式可以被联立为如下形状:
CE(y(t),ŷ (t))=logPP(t)(y(t),ŷ (t))

这条公式展示了交叉熵在数学意义上的最小化目标,同时表达了困惑度在几何意义上的最小值。当模型的预测值完全随机化时,会有 E[ŷ (t)i]=1|V| 。所给的向量 y(t) 是one-hot模型,所对应困惑度的期望值是 |V| 。由于困惑度的对数即为交叉熵,那么交叉熵的期望值在面对上述两个词库规模时分别应为 log20007.6 log100009.21

(b) (5分)
正如注释[2]所描述的操作,在时刻 t 关于单点全部模型参数的梯度计算如下:

J(t)UJ(t)b2J(t)Lx(t)J(t)I(t)J(t)H(t)J(t)b1(t)

其中, Lx(t) 是词嵌入矩阵 L 中对应到当前处理词汇 x(t) 的列,符号 |(t) 表示时刻 t 该参数的显式梯度。(同样地, h(t1) 的取值是固定的,而且你现在也不需要在早先的迭代时刻中实现反向传播算法——这是c小节的任务)。

此外,还要计算代表前趋隐层权值的导数:

J(t)h(t1)

解答:调用函数 ddzsigmoid(z)=sigmoid(z)(1sigmoid(z))

J(t)U=(h(t))T(yŷ )

J(t)h(t)=(yŷ )UT

J(t)b2=(yŷ )

J(t)b1(t)=(J(t)h(t)sigmoid(h(t1)H+e(t)I+b1))

J(t)Lx(t)=J(t)e(t)=J(t)b1(t)IT

J(t)I(t)=(e(t))TJ(t)b1(t)

J(t)H(t)=(h(t1))TJ(t)b1(t)

J(t)h(t1)=HTJ(t)b1(t)

(c) (5分)
下面为一步迭代的网络框图:

绘制下面三次迭代所“展开”的网络,并且计算迭代时后向传播的梯度:

J(t)Lx(t1)J(t)H(t1)J(t)I(t1)J(t)b1(t1)

这里 |(t1) 代表模型参数在 (t1) 时刻的显式梯度。由于这些参数在前馈计算中要被多次使用,我们需要在每次迭代时都计算一下它们的梯度。

最好参考讲义[5]所描述的后向传播原理去将这些梯度表达成残差的形式:

δ(t1)=J(t)h(t1)

(同样的表达方式在迭代时刻 t2,t3 以及更多的时候都同样适用)。

注意一个训练样本的实际梯度是需要我们将直到 t=0 时刻的整条后向路径都使用后向传播算法迭代完成后才能得到的。在练习中,我们通常只需截取固定数值\tau\approx3-5 τ35 的后向传播步骤即可。

解答:

还是延续前一节的条件
\left.\frac{\partial{J^{(t)}}}{\partial{b_1}}\right|_{(t-1)}=\delta^{(t-1)}\odot\mathrm{sigmoid}^{'}(h^{(t-2)}H+e^{(t-1)}I+b_1)

J(t)b1(t1)=δ(t1)sigmoid(h(t2)H+e(t1)I+b1)

\frac{\partial{J^{(t)}}}{\partial{L_{x^{(t-1)}}}}=\left.\frac{\partial{J^{(t)}}}{\partial{b_1}}\right|_{(t-1)}I^T
J(t)Lx(t1)=J(t)b1(t1)IT

\left.\frac{\partial{J^{(t)}}}{\partial{I}}\right|_{(t-1)}=(e^{(t-1)})^T\left.\frac{\partial{J^{(t)}}}{\partial{b_1}}\right|_{(t-1)}
J(t)I(t1)=(e(t1))TJ(t)b1(t1)

J(t)H(t1)=(h(t2))TJ(t)b1(t1)

(d) (3分)
对于给定的 h(t1) ,执行一轮前向传播计算 J(t)(θ) 需要多少操作?执行一轮后向传播又会如何呢?执行 τ 轮呢?对于维度参数组 d,Dh |V| ,使用符号“大写-O”来表示你的答案(如公式14)。是否该慎重考虑这一步?

回忆各个参数的设置: h(t1) 大小为 Dh e(t) 大小为 d ,还有 ŷ (t) 的大小为 |V| 。计算 e(t) 花费的时间 O(d) 视矩阵 L 而定。计算 h(t) 花费的时间 O(D2h+dDh) ,计算 ŷ (t) 花费时间 O(Dh|V|) 。后向传播和前向传播算法具有相同的时间复杂度,于是,计算 τ 轮的前向或后向传播复杂度仅通过 τ 与先前得到的复杂度值相乘即可。由于 |V| 比较大的原因,计算 ŷ (t) 的“较慢的一步”计算需花费时长 O(Dh|V|)

(e) (20分)
在代码q3_RNNLM.py中实现以上的模型。其中已经实现了数据加载器和其它的初始化功能代码。顺着已有代码的指引来补充缺失的代码。执行命令行python q3_RNNLM.py将运行该模型。注意,在运行过程中你不能用到tensorflow库的内置函数,如rnn_cell模块。

ptb-train数据集中训练该模型,其中包含Penn Treebank中WSJ语料集的前20节语料。正如在Q 2部分中所说的,你应该最大限度地提高该模型在实际生产中数据集上的泛化性能(即最小化交叉熵损失率)。关于模型的性能,我们将通过未知但类似的句子集来进行评估。

撰写实验报告的一些要求:

  • 在你实验报告中,应包括最优超参数(如训练框架、迭代次数、学习率、反馈频率)以及在ptb-dev数据集上的困惑度。正常情况应当保持困惑度值低于175。
  • 在报告中还应当包含最优模型的相关参数;方便我们测试你的模型。
  • 提示:在调试过程中把参数max_epochs的值设为1。在_init_方法中通过设置参数关键字debug=True来开启对load_data方法的调用。
  • 提示:该模型代码在GPU上运行很快(低于30分钟),而在CPU上将耗时多达4小时。

(f) (7分)
作业1和该作业中的q2部分所示的神经网络是判别模型:他们接收数据之后,对其做出预测。前面你实现的RNNLM模型是一个生成式模型,因为它建模了数据序列 x1,,xn 的分布状况。这就意味着我们不仅能用它去评估句子生成概率,而且还能通过它生成概率最优的语句!

训练完成后,在代码q3 RNNLM.py 中实现函数generate_text()。该函数首先运行RNN前向算法,在起始标记处建立索引,然后从每轮迭代的 ŷ (t) 分布对新词 xt+1 进行采样。然后把该词作为下一步的输入,重复该操作直至该模型遇到一个结束标志(结束标志记为)。

撰写实验报告的一些要求:

  • 在你的实验报告中至少包含2-3条的生成语句。看看你是否能产生一些妙趣横生的东西!

一些参考建议:
如果你想用语言模型继续实验,非常欢迎你上传自己的文档和对其训练成果——有时会是一个令人惊喜的实验结果!(可从网站http://kingjamesprogramming.tumblr.com/ 上获取以纪念版《圣经》和书籍《计算机程序设计与实现》为混合语料进行训练的伟大成果。)
[1]Optional (not graded): The interested reader should prove that this is indeed the maximum-likelihood objective when we let WijN(0,1/λ) for all i,j .
[2]This is also referred to as Glorot initialization and was initially described in http://jmlr.org/proceedings/papers/v9/glorot10a/glorot10a.pdf
[3]这个模型可以参考Toma Mikolov的论文, 发表于2010年: http://www.fit.vutbr.cz/research/groups/speech/publi/2010/mikolov_interspeech2010_IS100722.pdf
[4]我们使用Tensorflow库函数计算此处的损失率。
[5]http://cs224d.stanford.edu/lectures/CS224d-Lecture5.pdf

你可能感兴趣的:(深度学习与自然语言处理,深度学习与自然语言处理)