【詞向量表示】Word2Vec原理及實現

九年义务漏网鲨鱼發表於2024-12-04

目錄
  • Word2Vec
    • How achieve
    • Lookup table
    • Coding
    • Pre-dataing
    • Model
    • Negative sameple

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}\)個樣本作為負樣本集合。

相關文章