链载Ai

标题: 终于把 word2vec 的原理搞清楚了! [打印本页]

作者: 链载Ai    时间: 前天 10:31
标题: 终于把 word2vec 的原理搞清楚了!
大家好,我是小寒。
今天给大家分享自然语言处理中常用的一个知识点,word2vec

word2vec 是一种广泛用于自然语言处理的技术,主要目的是将单词转换为词向量(将单词表示为数字向量)。这些词向量能够反映不同词语的相似性,使得语义上或语法上相近的词语在向量空间中也相互接近。


如下图所示,从 “man” 到 “woman” 的向量与从 “king” 到 “queen” 的向量在向量空间中是平行的。这表明这两对词之间具有相似的性别变换关系,即从男性到女性的转变。

word2vec 是自然语言处理领域的一种基础技术,广泛应用于文本分析、机器翻译、情感分析等多种场景。

word2vec 的工作原理

word2vec 基于这样的理念:单词的含义由其上下文定义
对于 word2vec 模型,上下文表示为当前单词之前的 N 个单词和之后的 N 个单词。N 是一个超参数。使用更大的 N,我们可以创建更好的嵌入,但同时,这样的模型需要更多的计算资源。

如下图所示,其中 N 为 2,对于单词 word,它的上下文词为 machine、learning、 a 和 method。

word2vec 包括两种主要架构:

例如,CBOW 模型以 machine、learning、 a 和 method 作为输入,返回 “is” 作为输出。Skip-Gram 模型则相反。
下面的详细可视化可以让你一目了然。

下面,我们来看一下具体的模型架构。

可以看到,模型架构主要有输入层、隐藏层和输出层组成。

CBOW 和 Skip-Gram 模型的区别在于输入词的数量。CBOW 模型采用多个词,每个词经过相同的嵌入层,然后对词嵌入向量进行平均,然后进入线性层。而 Skip-Gram 模型则采用单个词。

最终,输入层与隐藏层之间的权重作为单词的词向量表示。

Skip-Gram 模型的前向传播过程详解

对中心词和外部词进行编码

要开始训练模型,必须使用标记化将训练数据拆分为单词(标记)。在此示例中,文本只是按空格拆分,并使用正则表达式转换为小写,并删除标点符号。
可以按字母顺序排列一组唯一标记,以形成独热编码模型的词汇表。
最后,对于文本中的每个中心词,可以找到外部词并将其与中心词一起转换为独热向量并存储在列表中。

此中心词和相应外部词将用于训练模型。

  1. importre
    importnumpyasnp

    WINDOW_SIZE=2

    defcreate_vocabulary(training_data):
    """通过标记训练数据返回排序后的单词列表。"""
    all_words=''.join(training_data).lower()
    all_words=all_words.replace('.','')
    all_words=all_words.split('')
    vocab=list(set(all_words))
    vocab.sort()
    returnvocab


    defone_hot(word,vocab,vocab_size):
    """返回单词的独热编码向量。"""
    one_hot=[0]*vocab_size
    pos=vocab.index(word)
    one_hot[pos]=1
    one_hot=np.array(one_hot)
    returnone_hot


    defcreate_vector_word_map(vocab,vocab_size):
    """返回一个词典映射,将独热向量转换回单词。"""
    vec_to_word={str(one_hot(word,vocab,vocab_size)):wordforwordinvocab}
    returnvec_to_word

    defencode_training_data(training_data,vocab_size,window_size):
    """Encodethecenterandoutsidewordsasone-hotvectors."""

    encoded_training_data=[]

    forsentenceintraining_data:

    #Tokenizethesentence
    tokens=re.sub(r'[^\w\s]','',sentence).lower().split('')

    #Encodeeachcenterwordanditssurroundingcontextwords
    forword_pos,wordinenumerate(tokens):
    center_word=one_hot(word,vocab,vocab_size)
    foroutside_posinrange(word_pos-window_size,
    word_pos+window_size+1):
    if(outside_pos>=0)and(outside_pos<len(tokens))\
    and(outside_pos!=word_pos):
    outside_word=one_hot(tokens[outside_pos],
    vocab,
    vocab_size)
    encoded_training_data.append([center_word,outside_word])
    returnencoded_training_data


    defprint_training_encodings(encoded_training_data,vocab,vec_to_word):
    """rinttheencodingsforeach(centerword-outsidewords)set."""

    max_len=len(max(vocab,key=len))

    fornum,(cw_vector,ow_vectors)inenumerate(encoded_training_data):

    cw=vec_to_word[str(cw_vector)]
    ow=vec_to_word[str(ow_vectors)]

    print(f'CenterWord#{num}:{cw}{cw_vector}')
    print(f'OutsideWords:{ow}{ow_vectors}')


    #Createtrainingdata
    training_data=['Thedogchasedthecataroundthegarden.']

    #Encodetrainingdata
    vocab=create_vocabulary(training_data)
    vocab_size=len(vocab)
    vec_to_word=create_vector_word_map(vocab,vocab_size)
    encoded_training_data=encode_training_data(training_data,
    vocab_size,
    window_size=WINDOW_SIZE)
    ##Printoutresults
    print_training_encodings(encoded_training_data,vocab,vec_to_word)

  2. 计算隐藏层向量
    一旦对中心词及其各自的外部词进行编码,就可以将中心词逐个输入网络以完成其前向传递。‍

    EMBEDDING_DIM=3
    #Calculatethehiddenlayervector
    x=encoded_training_data[0][0]
    w_center=np.random.rand(vocab_size,EMBEDDING_DIM)
    h=np.dot(x,w_center)

    #Printtheresults
    print(f'Centerword,w(t):{vec_to_word[str(x)]}\n')
    print(f'Inputvector,x:{x}\n')
    print(f'W_center:\n\n{w_center}\n')
    print(f'Hiddenlayer,h:{h}\n')
    计算网络输出
    前向传递的下一阶段是计算隐藏层向量 h 和外部词权重矩阵 W_outside 的点积。这将产生原始网络输出 u,即 V 维的 logits 向量。下一阶段,softmax 函数会将这些输出转换为“概率”值。

    #Calculatetherawnetworkoutputs
    w_outside=np.random.rand(EMBEDDING_DIM,vocab_size)
    u=np.dot(h,w_outside)


    #Printtheresults
    print(f'Hiddenlayer,h:{h}\n')
    print(f'W_outside:\n\n{w_outside}\n')
    print(f'Rawnetworkoutputs(logits),u:{u}\n')
计算预测值 y_pred
为了完成前向传递,原始输出向量 u 将使用 softmax 函数逐个元素进行转换,从而得到一组分数。

每个元素的值对应于一个单词属于给定中心词 x 的集合外部单词的“概率” 。

  1. defsoftmax(u):
    """Returnthesoftmaxvaluesforavectoru."""
    values=np.exp(u)/np.sum(np.exp(u))
    returnvalues


    deffind_outside_words(y_pred,vocab):
    #Getasortedlistofsoftmaxscores
    sorted_y_pred=y_pred.copy()
    sorted_y_pred=sorted_y_pred[::-1]
    top_score=sorted_y_pred[:1]
    index=np.where(y_pred==top_score)[0][0]
    print(index)
    word=vocab[index]
    returnword

    #Calculatethesoftmaxoutputs
    y_pred=softmax(u)
    outside_word=find_outside_words(y_pred,vocab)
    #Printtheresults
    print(f'Rawnetworkoutputs(logits),u:{u}\n')
    print(f'Softmaxoutputs,y_pred:{y_pred}\n')
    print(f'Outsidewords:{outside_word}')

综合比较

适用场景

CBOW 更适合于词汇丰富、重复频率高的语料库,而 Skip-gram 更适合于词汇多样化、包含大量罕见词的语料库。

性能

Skip-gram 在保留词义和处理复杂模式方面通常比 CBOW 更优,尤其是在词向量需要非常精细的语义关系时。

资源消耗

CBOW 在资源有限的情况下是一个更实用的选择,因为它的训练更快且对计算资源的需求较低。







欢迎光临 链载Ai (https://www.lianzai.com/) Powered by Discuz! X3.5