CS224学习作业一

作业一

Posted by 王难 on June 7, 2018

第一部分主要介绍了word2vec的算法,算是比较基础的内容.作业一因此也相应的只涉及到一些基础的内容,包括:softmax函数,简单神经网络,正向反向传播等.本次作业的主要目的是为了能够让每个学生入门,入门包括算法理论方面的入门,即对nlp和深度学习有一些粗浅的了解以及如何解决一些简单的问题,具体的思路是什么;其次是要锻炼编程能力,在完成本次作业的过程中,接触到了一些比较高大上的编程技巧(高效或简洁),更重要的是完成了从理论到实践的转换,能够将想法实现到程序上对于算法本身的理解是有极大的帮助的.本文的涉及到的复杂证明推导都会在下面直接给出,而且是以两种形式:矩阵直接求导和元素分开求导.而代码已经上传到了github的代码仓库中.

1.Softmax

Softmax可以说是一种很特殊的分类器.特殊主要特殊在它是最简单的多分类器之一了.其主要思路就是针对不同的标签,计算出对应标签的各个得分,其实就可以了.但是后面还有对其进行了标准化(normalization),主要是为了能够构成一个概率分布.而且标准化采用的还是最最简单的套e(使得每一项都是正的),然后单项除以总和(使得各个标签对应的概率相加和是1).

a.常数不变性

image.png

  这个小题是sofmax函数的一个重要性质的证明,即常数不变性.证明过程非常简单,代入之后可直接分子分母约分.重要性在于它的应用,我们在计算softmax的概率的时候,如果不利用常数的不变性质,任由x的值为特别大,那么在计算机中表示这个数就需要付出很大的代价,更不用说是要进行下一步的计算了.常用的一个技巧是,在进行softmax计算的时候,首先将输入向量的所有维度的数值减去最大值.这样的做法就能够得到一个相对较小的输入向量,并且对输出的相对大小关系没有太大的影响.

b.Softmax代码 image.png   这个小题该开始做的时候是觉得有点奇怪的,因为第一问已经给除了softmax的标准写法,即softmax是针对向量的(但是千万注意不是逐元素函数).而这一小问中却给了一个矩阵的输入,在做到后面是神经网络的代码之后,才明白这里是将多个测试用例放在了一个矩阵中,每一个测试用例都是一个向量,综合起来用一个矩阵进行统一的表示.

2.Neural Network Basics

a. Sigmoid求导

image.png

  白送三分,最简单的求导,甚至都没有啥要注意的要点.就是求完导数之后记得用原函数表示结果就好.

b. Cross Entropy+Softmax 求导

image.png

  来吧,让我们一起愉快的推导吧!两种口味一起炫技! 写到这里,值得一提的是有个坑花了我很长时间:在使用矩阵直接求导的方式的时候注意要先代入再求导,因为交叉熵和softmax天然的可以结合在一起化简运算.如果要采用传统的连式法则一点点算,复杂度高不说,还容易踩另外一个坑:softmax不是逐元素函数!!!这是个大坑,看分母就知道明显不是个逐元素函数,并不使用逐元素函数的求导法则. 1661596464.jpg 997732982.jpg

c. 梯度计算 3

image.png

  这里我仍然采用两种求导方式解题,为的是练习求导的技巧,因为类似的题目并不多,机会难得. 1161443379.jpg

d.e.f.g 代码

image.png

  上面一堆编程题,在经历了一段惨痛的适应期之后终于守的云开见月明.在这里有几个血的教训需要记录一下

  1.sigmoid 的梯度函数并不是想象中(书面计算中)的那样以sigmoid函数的输入作为输入,以梯度作为输出,而是使用了简单的sigmoid的函数的输出结果作为输入,以梯度作为输出.所以在求导的过程中造成了很大的困扰.

  2.最后一个编程题,使用了多个测试用例一起输入,这个和我前面的计算是不一致的.因为前面的计算是单个测试样本输入,所以在这里我天真的只是把原来的输入向量只是简单的替换成了输入矩阵.其实险些就要成功了,只不过在偏置项b的计算上马失前蹄.因为维度需要扩充,从向量扩充撑一个每行都一样的矩阵.

  3.这里的用到的梯度检验的方法是很棒的,给任意一个函数,输入一堆参数,输出函数值和梯度值.对于这样的函数,都可以使用它提供的这种数值的检验方法.主要的检验思路是针对输入的一堆参数,每次改变一个参数,改变的幅度超级小(h=0.0001这么小),观察一下函数值的变化.这种方法奏效的主要原理是,如果更新很小幅度的话,用函数值的差比上自变量的差就可以近似的作为切线的方向,即梯度在这一维度的数值.具体用下面公式进行计算数值梯度(numerical gradient)

image.png

  这个是题目要求的center版本的数值梯度,还有forward和backward版本的数值梯度,这都不是重点在这里就不赘述了.求出数值梯度之后就可以和真正的计算出来的梯度进行比较,就可以知道你算出来的梯度公式是不是合理的了.

  4.numpy 暗搓搓的学了个小技巧啊,就是transpose可以直接采用.T;以及numpy.sum可以对数组进行按行求和啊,或者按照列求和啊什么的,其他的一些数值统计函数也是有类似的功能的.   5.在python中的向量基本都是行向量,这和我们在数学中习惯采用的列向量是不一致的.不过,大丈夫,萌大奈!!对于所有的计算出来的梯度结果直接求个转置就好咯.

3.Word2Vec的证明

a.b 参数求梯度

  证明采用矩阵求导的方式,简直不要太简单啊…..不说了 image.png image.png 1397229263.jpg 221967242.jpg

c Negative Sampling

image.png

1109448785.jpg

d Softmax-CE & Negative Sampling

image.png   这题从计算的角度上来讲是毫无难度的,但是却在编程的时候有着重要的辅助作用.在这一题中,我们通过将损失函数加分类器写成统一的F的标记方式,可以将导数也表示成相对统一的方式,进而可以在程序中对这两种全部不同的损失函数进行统一的封装和调用.

1466094001.jpg

e Coding for Skip-gram & CBOW

image.png

  代码部分全都在Github的仓库中,在这里说一说踩到的几个坑吧.

  1.主要逻辑.前文提到过,作业给的代码框架的导向是希望我们把Softmax-CE和Negative Sampling封装成两个即插即用的损失函数.这就意味着,Softmax-CE和Negative Sampling这两个函数接受的参数,对外展现的接口必须保持一致.这一点如果不理解,很容易进入第一个坑.因为Negative Sampling 和Softmax-CE有一个关键的不同点是在更新语境词向量(区别中心词词向量)矩阵的时候仅仅进行了局部更新,而不是全局更新.这样的话就很容易就将返回值写成被更新的几个向量,这对后续其它函数的调用是增加了很大的负担的.

  2.梯度检查的精度问题.这一步中仍然使用了前面任务中写好的简单的梯度检查的函数,之前都是能够完美的运行的.但是在这里却出现了精度的问题.我们没有办法在这里保证1e-5的精度,最多只能保证1e-3的精度.这是在逻辑上说的通的.当然,也有可能是代码的优化不够,有些地方的写法损失了精度.

  3.Python的行向量的问题.这么多年了,学的向量一直都是列向量,让我突然一下转变成行向量还是有点难以接受的.这里需要说明两点:

    a.如果不想做太多改变,可以在书写的时候仍然保持行向量,最后对结果按照求转置的方式交换矩阵顺序就行(只交换顺序不求转置)

    b.python中的向量其实并不是真正意义上的向量的概念,叫它矩阵更贴切(np.array()).这也是反常规的,但是也带来了一些好处,比如可以使用一些这个新概念的api来进行操作.Python代码中的两个向量可以直接进行np.dot()运算得出的结果就是两个向量的内积.还可以进行np.outerproduct()的运算,得到的是一个矩阵,猜一下就知道求得结果是a.T.dot(b).注意:a,b都是行向量

  4.CBOW的梯度更新想当然了吧.仍然是每次输入更新一个中心词(outputVectors),多个语境词(inputVectors).

Future Study

  下一步待看:N-grams;hierarchical softmax;Noise Contrastive Estimation;参考这篇博客.此外还需要了解单词向量嵌入的模型历史,上面这篇博客也可以作为重要的工具.