词向量源码解析:(4.3)hyperwords源码解析之counts2pmi等

来源:互联网 发布:微信 请检查网络设置 编辑:程序博客网 时间:2024/06/05 15:50

PPMI和SVD都是很传统的模型。在word2vec之前很早就出现了。PPMI这种高维稀疏的单词表示一般认为比word2vec要差,尤其是在analogy问题上面。word2vec引起人注意的一个很重要的点就在于在analogy上面远远超过了传统的词向量模型。实际上,这个工具包的作者发现,当我们吧word2vec中的一些超参数引进传统的方法中,并且使用新的公式去做analogy这个任务,PPMI,SVD这种传统的模型也会去得很好的效果。实际上,word2vec,GloVe这些词向量模型本质上就是在利用共现矩阵进行训练。下面介绍如何从counts中生成PPMI矩阵。PPMI全称是positive pointwise mutual information。PMI大家应该比较熟悉,之前word2phrase中找phrase也利用的是PMI。这个东西实际上就是对共现矩阵的一个加权。用python代码几行就写好了。所以其实我们不需要用word2vec,GloVe那样复杂的训练,调参什么的,就用传统经典的bag-of-contexts加权,就能得到很高质量的词向量。而且,这个词向量很容易解释,每一维度都有清楚的含义。counts2pmi生成的是PMI矩阵,PPMI矩阵就是把PMI中的负数变成0。我们先看看counts2pmi的main函数。实际上就两步,首先从文件中读入共现矩阵,然后加权得到PMI矩阵

def main():
    args = docopt("""
    Usage:
        counts2pmi.py [options] <counts> <output_path>
    
    Options:
        --cds NUM    Context distribution smoothing [default: 1.0]
    """)
    
    counts_path = args['<counts>']
    vectors_path = args['<output_path>']
    cds = float(args['--cds'])//从word2vec中借鉴的超参
    
    counts, iw, ic = read_counts_matrix(counts_path)//从文件中读取共现矩阵


    pmi = calc_pmi(counts, cds)//在共现矩阵上面加权,即计算PMI矩阵


    save_matrix(vectors_path, pmi)//存储计算的结果
    save_vocabulary(vectors_path + '.words.vocab', iw)
    save_vocabulary(vectors_path + '.contexts.vocab', ic)

我们下面再看看如何从文件中读取共现矩阵。这段代码的逻辑是先读入中心词,上下文词典,然后再读入之前文件中的counts,得到共现矩阵。这里的共现矩阵用稀疏矩阵去存。这里默认内存能够存下整个的稀疏矩阵。还有一点要说明的是,读入的counts由于不是完全排好序的,建立稀疏矩阵的代价还是比较大的。

def read_counts_matrix(counts_path):
    """
    Reads the counts into a sparse matrix (CSR) from the count-word-context textual format.
    """
    words = load_count_vocabulary(counts_path + '.words.vocab')//读入中心词词典
    contexts = load_count_vocabulary(counts_path + '.contexts.vocab')//读入上下文词典
    words = list(words.keys())
    contexts = list(contexts.keys())
    iw = sorted(words)//从id找到单词
    ic = sorted(contexts)
    wi = dict([(w, i) for i, w in enumerate(iw)])//从单词找id
    ci = dict([(c, i) for i, c in enumerate(ic)])
    
    counts = csr_matrix((len(wi), len(ci)), dtype=np.float32)//共现矩阵的维度是由中心词词典和上下文词典中单词的个数决定。
    tmp_counts = dok_matrix((len(wi), len(ci)), dtype=np.float32)//dok_matrix适合做更新
    update_threshold = 100000//在dok_matrix上面做更新,到了一定的阈值,更新到counts上面
    i = 0
    with open(counts_path) as f:
        for line in f:
            count, word, context = line.strip().split()
            if word in wi and context in ci:
                tmp_counts[wi[word], ci[context]] = int(count)//在tmp_counts上面更新
            i += 1
            if i == update_threshold:
                counts = counts + tmp_counts.tocsr()//到了一定的阈值就更新counts
                tmp_counts = dok_matrix((len(wi), len(ci)), dtype=np.float32)//初始化tmp_counts
                i = 0
    counts = counts + tmp_counts.tocsr()//最后把tmp_counts剩下的去更新
    
    return counts, iw, ic//返回共现矩阵以及两个词典

下面看如何通过共现矩阵得到PMI矩阵,就是根据PMI公式算

def calc_pmi(counts, cds):
    """
    Calculates e^PMI; PMI without the log().
    """
    sum_w = np.array(counts.sum(axis=1))[:, 0]
    sum_c = np.array(counts.sum(axis=0))[0, :]
    if cds != 1:
        sum_c = sum_c ** cds
    sum_total = sum_c.sum()
    sum_w = np.reciprocal(sum_w)
    sum_c = np.reciprocal(sum_c)
    
    pmi = csr_matrix(counts)
    pmi = multiply_by_rows(pmi, sum_w)
    pmi = multiply_by_columns(pmi, sum_c)
    pmi = pmi * sum_total
    return pmi

def multiply_by_rows(matrix, row_coefs):
    normalizer = dok_matrix((len(row_coefs), len(row_coefs)))
    normalizer.setdiag(row_coefs)
    return normalizer.tocsr().dot(matrix)

def multiply_by_columns(matrix, col_coefs):
    normalizer = dok_matrix((len(col_coefs), len(col_coefs)))
    normalizer.setdiag(col_coefs)
    return matrix.dot(normalizer.tocsr())

然后我们看看怎样通过pmi得到svd。同样很简单。直接调用包的接口就好,真正的代码不到十行

def main():
    args = docopt("""
    Usage:
        pmi2svd.py [options] <pmi_path> <output_path>
    
    Options:
        --dim NUM    Dimensionality of eigenvectors [default: 500]
        --neg NUM    Number of negative samples; subtracts its log from PMI [default: 1]
    """)
    
    pmi_path = args['<pmi_path>']
    output_path = args['<output_path>']
    dim = int(args['--dim'])
    neg = int(args['--neg'])
    
    explicit = PositiveExplicit(pmi_path, normalize=False, neg=neg)//得到PPMI矩阵,explicit是一个类,里面的m成员是共现矩阵


    ut, s, vt = sparsesvd(explicit.m.tocsc(), dim)//sparsesvd支持csc格式的共现矩阵,得到稠密的单词,上下文表示


    np.save(output_path + '.ut.npy', ut)
    np.save(output_path + '.s.npy', s)
    np.save(output_path + '.vt.npy', vt)
    save_vocabulary(output_path + '.words.vocab', explicit.iw)//和ppmi共享词典
    save_vocabulary(output_path + '.contexts.vocab', explicit.ic)

阅读全文
0 0
原创粉丝点击