【词向量表示】Word2Vec原理及实现

Word2Vec

单词与单词之间的向量往往不在同一个向量空间,例如,传统的编码方式:one-hot编码,不同单词[1, 0, 0][0, 1, 0]之间的余弦相似度为0。因此,Word2Vec希望能够通过训练得到一个新的词向量表达方式,从而丰富向量的语义信息。主要目标如图所示,从一个稀疏的one-hot向量通过训练得到一个丰富稠密的新向量。

image

How achieve

word2vec通过神经网络模型训练新的词向量表达
image

  • 模型中参数的定义:

    one-hot:[1, 7] 表示一共有七个单词;

Embedding:表示输入层到隐藏层的权重矩阵,是从one-hot向量到Embedding向量的关键,[7, 3]表示训练完成的每一个embedding向量维度为3;

WeightLogits:表示隐藏层到输出层的权重矩阵,是模型损失计算的关键;

Logits:表示最后每个单词输出的概率,与目标标签做损失进行模型训练;

Lookup table

语料库十分巨大,每个单词都采用one-hot输入训练会大大增加存储和计算开销,因此,在输入的过程,仅仅输入单词的索引值,例如在上述例子中,直接采用索引4进行输入,同样也可以得到相同的词向量。

image

Coding

Word2Vec有两种模型结构:CBOW和Skip-gram,本质上的模型架构的不同:输入和输出一对多(Skip-gram)和多对一(CBOW)。

CBOW:通过前t个单词预测后一个单词

Skip-gram:通过周围的单词预测中间的单词 (一般来说,效果较好

Pre-dataing

  • 构建词汇表(Lookup-table)
def build_vocab(corpus):
    word_counts = Counter(chain(*corpus))
    vocab = {word: i for i, (word, _) in enumerate(word_counts.items())}
    return vocab
  • 构建不同模型的输入、输出
# Skip-gram 数据集生成
def generate_skipgram_data(corpus, vocab, window_size):
    data = []
    for sentence in corpus:
        for i in range(len(sentence)):
            target = sentence[i]
            context = [sentence[j] for j in range(max(0, i - window_size), min(len(sentence), i + window_size + 1)) if j != i]
            for context_word in context:
                data.append((vocab[target], vocab[context_word]))
    return data

# cbow 数据集生成
def generate_cbow_data(corpus, vocab, window_size):
    data = []
    for sentence in corpus:
        for i in range(len(sentence)):
            target = sentence[i]
            context = [sentence[j] for j in range(max(0, i - window_size), min(len(sentence), i + window_size + 1)) if j != i]
            data.append(([vocab[word] for word in context], vocab[target]))
    return data

Model

可以发现两个结构的代码只有输入和输出的大小不同,其他类似

  • CBOW
class CBOW(nn.Module):
    def __init__(self, vocab_size, embedding_dim):
        super(CBOW, self).__init__()
        self.embeddings = nn.Embedding(vocab_size, embedding_dim)
        self.linear = nn.Linear(embedding_dim, vocab_size)

    def forward(self, context_words):
        embedded = self.embeddings(context_words).mean(dim=1)  # 平均上下文词向量
        logits = self.linear(embedded)
        return logits
  • Skip-gram
class SkipGram(nn.Module):
    def __init__(self, vocab_size, embedding_dim):
        super(SkipGram, self).__init__()
        self.embeddings = nn.Embedding(vocab_size, embedding_dim)
        self.linear = nn.Linear(embedding_dim, vocab_size)

    def forward(self, target_word):
        embedded = self.embeddings(target_word)  # (Batch, embedding_dim)
        logits = self.linear(embedded)
        return logits 

Negative sameple

提出动机:每次模型训练都需要计算所有词向量的损失,通过只更新负样本的权重,避免整个词汇表的计算

​ Word2Vec模型本质是一个多分类问题,最后需要通过softmax激活函数判断哪一个单词的概率最大,因此需要计算所有单词的概率大小。而负采样优化是指将原来的任务退化为二分类问题,只对正负样本进行判断,在词汇表随机选取\(N_{neg}\)个样本作为负样本集合。

posted @ 2024-12-04 15:34  九年义务漏网鲨鱼  阅读(128)  评论(0)    收藏  举报