赞
踩
GloVe代码包括了四个.c文件
如何存储共现矩阵?
一个很直接简单的方法就是用二维数组去存
这么做忽略了一个问题,就是共现矩阵太过稀疏,大多数的单词从来没有共现过,通过二维矩阵存非常浪费内存。实际上一般内存也根本存不下甚至中等规模大小的语料。所以用二维数据存储共现矩阵,在大规模语料上面是不可行的。
既然显示的存储共现矩阵不行,我们可以用稀疏矩阵的方式压缩去存储。扫描语料的过程中不断地更新稀疏矩阵,从而得到最终的共现矩阵。
所以现在我们的目标是,压缩存储稀疏矩阵!
常见的方式是行压缩和列压缩,数据结构用数组,但glove并不是!
glove的思路是:首先不断地从文件中读取单词对(pair),读到内存写满为止。对这些pair进行排序以及汇总,然后写出到文件。以此类推,我们就能得到很多文件,每个文件里面存的都是对部分的语料的共现矩阵。然后我们对这些共生矩阵再进行汇总。
这种方法对内存更友好(当然,这是一种大数据思想)
当然,最秒的还在后面:
在GloVe代码中,它使用了混合的数据结构去存储共现矩阵。当我们的单词按照它们的频数排好序以后,比如the的id是1,of的id是2然后以此类推。我们会发现共现矩阵的左上角是非常稠密的。当一个pair中两个单词的id都很小(它们的频数很大),他们很有可能共现。而共现矩阵的其它地方很稀疏。比如右下角,低频词不大可能共现。基于这个观察,GloVe中使用二维矩阵去显式的存储共现矩阵的左上角,剩下的部分用我上段中提到的方法建立共现矩阵。所以,共现矩阵的左上角是一直在内存中的,剩余部分会写出最后再汇总。这里再说一下什么是左上角,GloVe中认为word pair中两个单词的id的乘积小于某个阈值时算是在左上角( d越小,词频越高 )。阈值时100的时候,id为9和id为10的单词组成的pair就算在共现矩阵的左上角,因为9乘以10小于100.
一种"内存读取"的思路铺面而来。
最后,还有一个很秒的地方是:
共现矩阵左上角以外的部分使用一个三元组去存储的。分别是第一个单词的id第二个单词的id以及他们的共现次数。GloVe对共现次数的计算是随着单词之间距离递减的,比如两个单词距离为5,那么算他们共现五分之一。所以共现次数是浮点型。建立共现矩阵自然也需要词典,所以内存中会维护词典。词典中记录单词的字符串和id,不再记录频数,单词的id是根据频数来的,频数越高id越小
这简直就是:在有限的内存情况下,尽可能压榨代码的效率和空间使用情况。
代码内的hash是为了提高查找效率。
第一阶段:统计共现的策略就和上面说的一样:循环读取单词,按照索引存入“显存数组”还是三元组内,达到指定大小后,就把三元组写到文件里。
第一阶段完成,进入第二阶段,对临时文件进行汇总。利用的是最小堆思想:
将overflow_0000.bin、0001.bin、0002.bin整理到一起,保存到cooccurrence.bin中。整合的时候,使用priority queue(最小堆,以词在字典中的序号为键,从小到大)来实现,从每个bin文件中读取一条记录插入到堆中,即堆中只保存一个bin文件的一条记录。堆建成后,取出堆顶元素保存到变量old后调整堆,从old的来源的bin文件中再读取一条记录保存到new,并插入堆中。此后,开始迭代处理,比较堆顶元素与old是否标记了同样两个词,如果是,则将堆顶元素合并到old中,否则,将old写入到cooccurence.bin中,并将堆顶元素赋值给old,记录堆顶元素的来源文件i,然后删除堆顶元素并读入i文件的下一条记录,并加入到堆中;重复这样的循环过程,直到所有的bin文件都读完。再次注意:堆只保存了每个bin文件的一条记录。
构造loss function
[ 1 ]glove详解
[ 2 ]GloVe源码解析之cooccur
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。