AI见闻
首发于AI见闻

【NLP】基础模型之词向量

越来越觉得基础太重要了,要成为一个合格的算法工程师而不是调包侠,一定要知道各个基础模型的HOW&WHY,毕竟那些模型都是当年的SOTA,他们的思想也对之后的NLP模型影响很大。最近找到了一个还不错的nlp-tutorial,准备抽时间过一遍基础模型,模型的大致思想以及数学公式可能就带过了,主要是实现上的细节。

1. NNLM

通过神经语言模型学习词向量,网络结构如图:

公式:

y = b+Wx+Utanh(d+Hx) \\

复杂度(训练一个样本的参数量):

N*D+N*D*H+H*V \\

其中N=n_step,D=embedding size,H=hidden size,V=vocab size

解决了统计语言模型(n-gram model)的以下问题:

  1. 维度灾难:高维下的数据稀疏会导致很多统计概率为0,本文提出了分布式词表示
  2. 长距离依赖:n-gram一般最多为3
  3. 词的相似关系:在本文中,词以向量的方式存在,通过LM训练后相似的词会具有相似的词向量

源码:

class NNLM(nn.Module):
    def __init__(self):
        super(NNLM, self).__init__()
        self.C = nn.Embedding(n_class, m)
        self.H = nn.Parameter(torch.randn(n_step * m, n_hidden).type(dtype))
        self.W = nn.Parameter(torch.randn(n_step * m, n_class).type(dtype))
        self.d = nn.Parameter(torch.randn(n_hidden).type(dtype))
        self.U = nn.Parameter(torch.randn(n_hidden, n_class).type(dtype))
        self.b = nn.Parameter(torch.randn(n_class).type(dtype))

    def forward(self, X):
        X = self.C(X)
        X = X.view(-1, n_step * m) # [batch_size, n_step * emb_dim]
        tanh = torch.tanh(self.d + torch.mm(X, self.H)) # [batch_size, n_hidden]
        output = self.b + torch.mm(X, self.W) + torch.mm(tanh, self.U) # [batch_size, n_class]
        return output

要注意的点:

  1. 模型输入x是所有词向量的拼接,而不是平均
  2. 模型有两个隐层:一个是线性层C,一个是非线性层tanh。W矩阵中的参数是有可能为0的
  3. 模型输出层embedding参数矩阵不共享

2. Word2Vec

Word2Vec看过一篇特别好的解析,直接看word2vec中的数学原理详解就好了,我这里简要总结。

Word2vec主要为了学习更高质量、且快速训练的word vector,分别有Skip-gram和CBOW两种结构:

提出上述简单结构主要受NNLM后一些研究的启发,作者发现不完全训练模型也可以得到词向量,因此直接去掉了隐层。

CBOW复杂度:

N*D+D*V \\

Skip-gram复杂度:

C*(D+D*V) \\

其中C=n_context。

去掉了隐层之后,作者又用了两种优化的方法:

Hierarchical Softmax:传统softmax需要对整个词表进行计算,而HS根据词频构造了一颗Huffman树,思想是把多分类转换成二分类,减少计算量,这样直接把复杂度V变成了最坏情况下 \log_2(V) 。每个树节点进行一次二分类,更新当前节点的二分类参数即可。

Negative Sampling:算是HS的更快速替代,且免去了Huffman树的构建,基本上常用的版本都是基于NS方法实现的。思想是让模型学会区分数据中的噪声,类似hinge loss,目标是最大化正例概率、最小化负例概率。论文中提到,数据少的话需要取5-20个负样本,多的话2-5个就可以了。实现可以直接用tensorflow的nce_loss:

inputs = tf.placeholder(tf.int32, shape=[batch_size])
labels = tf.placeholder(tf.int32, shape=[batch_size, 1]) # To use tf.nn.nce_loss, [batch_size, 1]

embeddings = tf.Variable(tf.random_uniform([voc_size, embedding_size], -1.0, 1.0))
selected_embed = tf.nn.embedding_lookup(embeddings, inputs)

nce_weights = tf.Variable(tf.random_uniform([voc_size, embedding_size], -1.0, 1.0))
nce_biases = tf.Variable(tf.zeros([voc_size]))

# Loss 
cost = tf.reduce_mean(tf.nn.nce_loss(nce_weights, nce_biases, labels, selected_embed, num_sampled, voc_size))

3. Fasttext

Fasttext最初是为分类设计的浅层模型,由于提出了n-gram subword的做法,后来Mikolov又提出了skip-gram的改进版,因为word2vec中单独表示每个词,并没有考虑到subword之间的关系。还有一个好处就是可以解决oov问题,所以现在词向量建议用fasttext+skip-gram/cbow的方式做。


(并没有看源码,太打脸了)

编辑于 2019-07-19

文章被以下专栏收录

    分享一些炼丹经验、在读的paper,主要是NLP相关,欢迎喜欢分享的同行投稿