NLP word embedding汇总

Word Embedding 词嵌入,从字面意思理解其实不是很好懂,他本质是一个向量化Vectorization的过程,一个把文字文本转化成数字形式的方式。这样模型才能够处理和训练文本。

比如我有一句话I love you, 最简单的翻译成向量,那就是[0,1,2],因为我的词库里暂时只有这三个词。接下来我想说I hate you,其中hate是新词,其他两个是旧词,那就是[0,3,2], 而hate是词典里的新词,所以他的编号也是放在最后。

当这个词库越来越大,比如有1万个词了,就像字典一样,基本每一句话都能用数字表示,这就实现了基本的向量化。当然直接用10进制肯定是不太好的,计算机就应该用01表示,于是one hot vector就产生了(这里不是二进制,每一个词只有一个位置为1)

One hot vector

NLP word embedding汇总
对于这种表示方式,词典有多长,每个one hot vector就有多长,而其中1的位置就像个开关,在对应词的位置就表示1,其他位置则表示0。

再次回到最开始的例子,我现在的vocabulary只有四个词I, love, you, hate,那one hot vector就分别是1000,0100, 0010, 0001,
而I love you=[1000, 0100, 0010]。

但显而易见,这种方式肯定是不够的,他没有区分每个词之间的区别,也没有考虑相似度。比如car和bus都是交通工具,但是从vector的角度而言,他们就是两个没有任何关联的单独的值。

另一方面,效率也不行,假如字典有10000万个词,那每个词都是9999个0和1个1组成,过于稀疏,并且也非常浪费空间。

进化一点,我们就有了Bag of Words 词袋模型。

Bag of Words

NLP word embedding汇总
词袋模型是不考虑顺序的,只根据每个词在构建的vocab中的频率表示
比如I love you, but you hate me, 这里由于新加入了词语,词典变成了[I, love, you, hate, but, me] 一共六个词,根据对应的频率表示
原句为[1, 1, 2, 1, 1, 1], 正好对应每个词出现的次数,2就表示you这个词出现了两次。

但问题是由于他不考虑顺序,
I love you, but you hate me
I hate you, but you love me
这两句话用词袋表示是一样的,但显而易见他们的含义是有很大差异的。

于是方法需要继续改进。

另外每个词的重要性也是不同的,比如[I love the movie] 和 [I hate the movie], 这两句话,love和hate显然比其他词更重要,而the这种甚至是可有可无的。于是我们希望更好的向量化方法。

TF-IDF

于是有了TF-IDF,可以衡量词在文本中的重要性,他是通过计算两个值TF(Term Frequency, 词频), IDF(Inverse Document Frequency)是实现,具体的TF-IDF计算方式和代码实现可以看我的另一篇文章
NLP word embedding汇总
简而言之:
假如一篇文件的总词语数是100个,而词语“母牛”出现了3次,那么“母牛”一词在该文件中的词频就是3/100=0.03。而IDF的是以文件集的文件总数,除以出现“母牛”一词的文件数。所以,如果“母牛”一词在1,000份文件出现过,而文件总数是10,000,000份的话,其逆向文件频率就是lg(10,000,000 / 1,000)=4。最后的tf-idf的分数为0.03 * 4=0.12

有时候为了防止IDF中分母为0,会用1+df表示分母。

当然tfidf也是有缺点的,他只考虑频率,而没有词之间的相似度,比如car和bus,他会认为这是两个没有关联的词,并且当词典特别大时,表示也会变得特别稀疏。

Word2Vec, FastText

于是,进一步改进,有了word2vec, fasttext,他们就考虑了词之间的相似度,具体操作可以通过gensims调包实现,这里具体可以看我的另一篇文章

具体Word2vec中CBOW,skipgram怎么训练的,可以看知乎上这个人写的, 感觉还不错。

Word2Vec和Fasttext都有两种模式,分别是CBOW和Skip gram。
NLP word embedding汇总

训练过程如下,以CBOW为例:
每个输入的词都用one-hot vector表示,当我用CBOW是就是周围词预测中心词,那么中心词就成了target Y,而周围词就是那一堆input,每一个input都会经过一个参数矩阵W。看下图比较容易理解,其中window size=2,即每个中心词的前2个词后2个词都是周围词。

NLP word embedding汇总

分别有输入权重矩阵 W W W,和输出权重矩阵 W ′ W' W′,比如CBOW的时候每个周围词用one hot vector和 W W W相乘之后再相加,然后进入 W ′ W' W′,然后再和中心词的向量计算loss,一直迭代更新两个参数矩阵 W , W W,W W,W。
NLP word embedding汇总
这个W矩阵就把one-hot向量映射到了低维度表示,然后中间这一层,就要用每一个周围词映射之后的向量相加,来表示中心词
NLP word embedding汇总
中心词现在是 v ^ \hat{v} v^, 他是根据周围词构建的,然后用 W ′ W' W′把它反向转换成长度跟one-hot vector一样的向量,再计算softmax,再和中心词原本的one-hot计算loss。
NLP word embedding汇总
这个训练过程其实就是神经网络的训练过程,经过一次次迭代之后可以获得最终的矩阵表示。

理解了CBOW之后,Skipgram就是一个相反的过程,他同样有 W , W ′ W, W' W,W′两个参数矩阵,输入是中心词的one-hot,target就是几个周围词的one-hot。

当然Word2Vec也是有缺点的,他不能处理OOV(Out-of-Vocabulary)问题,即不在词典里的词他没法表示。

而FastText是可以处理这个的,FastText虽然也有CBOW和Skipgram,但是和Word2Vec不同的是他是基于n-gram训练的,n-grams的意思就是他会把每n个字母或者词当作一个单独整体,比如2-grams(bigrams)时,apple就可以写成ap,pp,pl,le,如果是3-grams(trigrams),就是app,ppl,ple。

最后生成的结果,会对每一个n-grams都生成一个word embedding。基于这种策略,OOV问题就可以解决,即使这个词没有出现在vocab里面,但是他可以由存在的n-grams拼接而成,比如我有ban,native两个词,当我遇到banana这个新词时,就可以用ban中的‘ba’和native中的’na 拼接而成。

但无论是FastText还是Word2Vec,都局限在中心词和周围词,也就是只利用了局部信息,而没有利用全局信息,于是就有了利用到全局信息的Glove

glove

glove会利用到全局的词共现矩阵
NLP word embedding汇总

可以直接调用别人训练好的glove模型,而不需要自己训练

import gensim.downloader as api

model = api.load("glove-twitter-25")  

如果你想查看某个词在这个导入的模型中的词向量,只需要使用.wv[]并带上相应的词语。

model.wv['good']

结果如下,你会发现这个向量的长度就是25,也就是’glove-twitter-25’中25的含义,还有其他类似的可导入的模型,比如’glove-twitter-50’, 'glove-twitter-100’等。
NLP word embedding汇总

通过这种方式,我们可以用别人训练好的模型,在我们自己的vocab上生成一个embedding matrix作为lookup table,用来给我们后续的模型进行初始化,而不是用随机参数。

这里的word_list就是一个包含了input中所有词的list

import numpy as np
emb_dim = model.vector_size #向量长度,这里是25

emb_table = []
for i, word in enumerate(word_list):
    if word in word_emb_model:
        emb_table.append(word_emb_model[word])
    else:
        emb_table.append([0]*emb_dim)
emb_table = np.array(emb_table)

根据lookup table初始化模型

class Model(nn.Module):
    def __init__(self, hidden_dim, attn):
        super(Model, self).__init__()
        self.we_emb = nn.Embedding(vocab_size, emb_dim)
        self.we_emb.weight.data.copy_(torch.from_numpy(emb_table))
        
        self.lstm = nn.LSTM(we_dim, hidden_dim//2, num_layers=2, bidirectional=True, dropout = 0.5) #only word_embedding(glove-twittwe-25), no pretrained embedding,  no pos tagging
        
        self.linear = nn.Linear(hidden_dim*2, n_class)

其他的Word Embedding 方法

预训练模型pre-trained

利用预训练模型,可以直接用别人训练好的模型进行word embedding
比如BERT, GPT-3等,实现方式也有很多种

比如用Flair包,下载:

!pip install flair

然后用flair生成embedding模型

from flair.embeddings import StackedEmbeddings,CharacterEmbeddings,TransformerWordEmbeddings,PooledFlairEmbeddings
flair_embedding = StackedEmbeddings(
    [
        TransformerWordEmbeddings('bert-base-uncased', layers='-6',subtoken_pooling="mean"), 
        TransformerWordEmbeddings('roberta-large', layers='-6',subtoken_pooling="mean"),  
        
    ]
).to(device)

具体有哪些模型可以使用的可以看这个链接
这些预训练模型可以单独使用,也可以stack到一起使用。
NLP word embedding汇总

当模型构建好之后,测试一下

from flair.data import Sentence
sents = [['I', 'am', 'always', 'good', 'for', 'you'],
        ['come', 'here', 'and', 'have', 'a', 'drink']]
for sent in sents:
    temp_sent = Sentence(" ".join(sent),use_tokenizer=False)
    flair_embedding.embed(temp_sent)
    temp_emb=[]
    for emb in temp_sent:
        temp_emb.append(emb.embedding.cpu().tolist())
    print(temp_emb)

结果就是这两句话的word embedding向量。

完成了word embedding,就可以进行NLP的后续任务了。

上一篇:周末小短文:Sql如何处理热点key


下一篇:leetcode hot 100- 84. 柱状图中最大的矩形