(自用笔记)Word Embedding原理和Pytorch实现

参考:
(1)从Word Embedding到Bert模型—自然语言处理中的预训练技术发展史 - 知乎 (zhihu.com)
(2)吴恩达深度学习
(3)deep_thoughts老师的原理和代码讲解:https://space.bilibili.com/373596439越是底层的特征越是所有不论什么领域的图像都会具备的比如边角线弧线等底层基础特征,越往上抽取出的特征越与手头任务相关。正因为此,所以预训练好的网络参数,尤其是底层的网络参数抽取出特征跟具体任务越无关,越具备任务的通用性

Word representation 词汇表征

单词的语义表征

稀疏式
one-hot encoding(维度太大)
分布式(连续浮点型的向量,长度固定)
类似于word embedding,各种embedding
应用场景
word/character/phrase/sentence/paragraph embedding
speaker/user/item embedding

基于词的one-hot的缺点:每个词孤立的分开,导致模型对相关词的泛化能力不强

image

image

将词标识为特征向量

转换为二维表示:t-SNE

image

WE Word Embedding

image

Word Embedding的特征

一个简单的例子:

已知man-woman,给出king找到对应的词

image

eman - ewoman ≈ eking - equeen

这是因为在特征向量表示中,main,woman,king,queen的差异主要由Gender决定

词特征向量相似度

image

t-SNE转换为二维标识后很多无法再明显显示为平行四边形

常用的相似度函数:余弦相似度

image

embedding matrix

当使用模型学习词嵌入时,实际上是在学习一个词嵌入矩阵

image

one-hot大部分为0,所以矩阵相乘的的效率在实际应用中太低

  1. 词汇表大小(Vocabulary Size): 这是用于训练 word embedding 的语料库中唯一单词的数量。假设词汇表大小为 V

  2. 词向量维度(Embedding Dimension): 这是将每个单词映射到的连续向量空间的维度。假设词向量维度为 D

    词向量维度通常是作为一个超参数(hyperparameter)在训练 word embedding 模型时手动指定的。选择词向量维度的过程通常是基于经验和任务需求的。较小的词向量维度可能会捕捉到更少的语义信息,而较大的词向量维度可能会包含更丰富的语义信息,但同时也会增加模型的复杂度和计算成本。

    一般来说,通常将词向量维度设置在 100 到 300 之间。常见的选择包括 50、100、200 或 300 维。

WE的问题

word embedding无法区分多义词的不同语义

用语言模型训练的时候,不论什么上下文的句子经过word2vec,都是预测相同的单词,而同一个单词占的是同一行的参数空间,这导致两种不同的上下文信息都会编码到相同的word embedding空间里去

语言模型,语言建模

基于已有的人类组织的文本语料,来去无监督学习如何组织一句话并还能得到单词的语义表征

统计模型-n-gram

特点: 统计性、简单、泛化能力差、无法得到单词的语义信息,只能算出单词的概率不能得到单词的语义表征的和单词之间的关联
定义:n个相邻字符构成的序列:n-gram

  • 用途:基于n-gram的频数分析文本,如垃圾邮件分类
  • 对于word n-gram(基于单词的),特征维度随着语料词汇增大和n增大而指数增大 (curse of dimensionality,维度灾难)
  • 对于character n-gram(基于字母的),特征维度只随着n增大而增大

无监督学习-NNLM

NNLM(Bengjo提出)

学习任务是输入某个句中单词 Wt = bert 前面句子的t-1个单词,要求网络正确预测单词Bert

最大化:image

image

输入层:wt-n+1,wt-1,n个相邻的单词

并且是one hot的索引表示,传入投影层,根据索引查询embedding table,one -hot encoding vector和embedding table做矩阵相乘得到word embedding

隐藏层:

image

原始单词xi用one-hot编码为Wi作为原始输入(作为矩阵W)

C×W(即为图中的C(W)(为每个向量拼接)),上接隐层,然后接softmax去预测后面应该后续接哪个单词

到了每个词的词向量表示后,将其拼接在一起,即进行concat操作,形成了一个(n-1)*w的向量,我们用X来表示。然后将X送入隐藏层进行计算,即图中中间的一层神经元,使用tanh作为激活函数

训练刚开始用随机值初始化矩阵C,矩阵C有v行(词典大小),矩阵C也是参数,需要不断更新,每一行代表一个单词对应的Word embedding值

word embedding 是 NNLM的副产品

image

大规模无监督学习-word2vec,BERT

word2vec
改进1:抛弃了隐藏层

为了获得word embedding

(1)CBOW,核心思想是从一个句子里面把一个词抠掉,用这个词的上文和下文去预测被抠掉的这个词(与Bert中的随机mask一个单词,预测这个单词类似)。考虑了前后上下文,使用周围单词预测中间单词。

image

image

输入加起来到一个softmax,输出一个概率分布,目标函数即为希望得到的中间的单词的概率最大

(2)Skip-gram,和CBOW正好反过来,输入某个单词,要求网络预测它的上下文单词。

学习词嵌入的几种上下文:

image

将所有周围单词t周围单词的概率求和:

image

image

构建很多个input和context word的组合,每次输入一个input,得到embedding后映射到概率分布,使得预测到希望的单词概率最大

类似bert中的nsp任务,两个句子是否互为相邻

改进2:优化softmax

在分类任务中,softmax需要将隐含层输出映射到大小为序列中单词数目的概率分布上,所以计算量和单词表数目k线性相关

image

huffman树构造思路的softmax

Hierarchical Softmax 使用了多个连续的二分类来逼近整个 softmax 函数。具体来说,它将词汇表中的单词按照概率分布构建成一棵二叉树,其中每个单词对应树中的一个节点。在构建过程中,每个单词的概率被用来指导节点的安排,常见的做法是使用 Huffman 编码树构建。一旦树被构建完成,针对每个中心词,softmax 的计算路径就沿着从根节点到目标单词的叶子节点的路径进行,每次选择左子节点或右子节点,直到到达目标单词的叶子节点。在这个过程中,树的深度通常与词汇表的大小相关,而不是固定的。因此,对于一个具有 V 个单词的词汇表,计算整个 softmax 的时间复杂度由 O(V) 降低到了 O(log V)

image

左分类当作负样本,右分类当作正样本,则可以算出p(cat|context)

image

单词表达成Word Embedding后,很容易找出语义相近的其它词汇

训练好的WE×word_one-hot = new_word_E

Word Embedding 和 One-hot 编码的对比:

  1. 形式:
    • Word Embedding:是一个实数向量,通常具有固定的维度,可以捕捉单词之间的语义关系。
    • One-hot 编码:是一个稀疏的向量,维度等于词汇表的大小,其中只有一个元素为 1,其余元素为 0。
  2. 表示效率:
    • Word Embedding:提供了密集的向量表示,相比于稀疏的 One-hot 编码,能够更有效地表示单词。
    • One-hot 编码:是一种稀疏的表示方法,需要存储大量的零值,浪费了存储空间。
  3. 语义信息:
    • Word Embedding:能够捕捉单词之间的语义关系,使得在向量空间中相似的单词在几何距离上也是相近的。
    • One-hot 编码:每个单词之间的距离都是相等的,无法捕捉语义信息。
  4. 泛化能力:
    • Word Embedding:由于能够学习到单词之间的语义关系,具有一定的泛化能力,适用于许多自然语言处理任务。
    • One-hot 编码:仅表示单词的存在与否,缺乏语义信息,泛化能力较弱。
pytorch中的实现
Word Embeddings: Encoding Lexical Semantics — PyTorch Tutorials 2.2.0+cu121 documentation

在nlp中特征就是单词(nlp最基础的单元),但是在计算机中需要用数字来表示单词

最简单的就是one-hot encoding(之前说过)(缺点:维度爆炸,只能表示单词顺序)There is an enormous drawback to this representation, besides just how huge it is. It basically treats all words as independent entities with no relation to each other. What we really want is some notion of similarity between words.

获取语义之间的相似度和关联度十分重要

语言模型的作用:根据句法推断出语义的相似度和关联度

获得两个句子的dense WE后:通过余弦相似度[-1,1]来就算

给出WE可以知道两个单词的语义是否接近,但无法得知两个单词的语义为什么接近

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
torch.manual_seed(1)
#对单词表构造字典用于将单词映射到索引
word_to_ix = {'hello':0,'world':1}
#2个单词(词汇表大小为2)和每个单词的嵌入维度为5。这意味着每个单词都将被表示为一个长度为5的向量。
embeds = nn.Embedding(2, 5)
#包含单词索引的PyTorch张量,表示要查询嵌入的单词。在这里,我们使用索引0来表示单词"hello"
lookup_tensor = torch.tensor([word_to_ix["hello"]], dtype=torch.long)
print(lookup_tensor)#tensor([0])
hello_embed = embeds(lookup_tensor)#根据索引查找对应的词嵌入向量
print(hello_embed)
#tensor([[-0.8923, -0.0583, -0.1955, -0.9656,  0.4224]],
#        grad_fn=<EmbeddingBackward0>)
n-gram pytorch
CONTEXT_SIZE = 2#上下文2
EMBEDDING_DIM = 10#we维度
# We will use Shakespeare Sonnet 2
test_sentence = """When forty winters shall besiege thy brow,
And dig deep trenches in thy beauty's field,
Thy youth's proud livery so gazed on now,
Will be a totter'd weed of small worth held:
Then being asked, where all thy beauty lies,
Where all the treasure of thy lusty days;
To say, within thine own deep sunken eyes,
Were an all-eating shame, and thriftless praise.
How much more praise deserv'd thy beauty's use,
If thou couldst answer 'This fair child of mine
Shall sum my count, and make my old excuse,'
Proving his beauty by succession thine!
This were to be new made when thou art old,
And see thy blood warm when thou feel'st it cold.""".split()
#ngrams为一个元组列表,其中每个元素都是单词i和单词i的上文(n = 2)
ngrams = [
    (
        [test_sentence[i - j - 1] for j in range(CONTEXT_SIZE)],
        test_sentence[i]
    )
    for i in range(CONTEXT_SIZE, len(test_sentence))
]
print(ngrams[:3])

vocab = set(test_sentence)
#构造索引字典
word_to_ix = {word: i for i, word in enumerate(vocab)}
print(word_to_ix)

#构建NLM
class NGramLanguageModeler(nn.Module):

    def __init__(self, vocab_size, embedding_dim, context_size):
        super(NGramLanguageModeler, self).__init__()
        #投影层,构建WE表
        self.embeddings = nn.Embedding(vocab_size, embedding_dim)
        #上下文大小*we维度得到一个大的向量
#         self.linear1 的输入是通过词嵌入层得到的上文单词的词嵌入向量
# 这些单词的词嵌入向量会被展平为一个大的向量,然后输入到线性层 self.linear1 中进行线性变换
        self.linear1 = nn.Linear(context_size * embedding_dim, 128)
#     将隐藏表示空间映射到词汇表大小的向量,以便生成每个单词的概率分布
        self.linear2 = nn.Linear(128, vocab_size)

    def forward(self, inputs):
        embeds = self.embeddings(inputs).view((1, -1))
        out = F.relu(self.linear1(embeds))
        out = self.linear2(out)
#         对输出进行log_softmax操作,得到模型预测的每个单词的对数概率。
        log_probs = F.log_softmax(out, dim=1)
        return log_probs


losses = []#存储每个epoch的损失值
#负对数似然损失函数 nn.NLLLoss(),用于计算模型输出和目标值之间的损失。
loss_function = nn.NLLLoss()
model = NGramLanguageModeler(len(vocab), EMBEDDING_DIM, CONTEXT_SIZE)
optimizer = optim.SGD(model.parameters(), lr=0.001)

for epoch in range(10):
    total_loss = 0
    for context, target in ngrams:

#         将上下文单词转换为整数索引,并封装成张量 context_idxs
        context_idxs = torch.tensor([word_to_ix[w] for w in context], dtype=torch.long)
        model.zero_grad()
        log_probs = model(context_idxs)
#计算损失,将模型的输出和目标单词的索引作为输入,并使用负对数似然损失函数计算损失值。
        loss = loss_function(log_probs, torch.tensor([word_to_ix[target]], dtype=torch.long))
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    losses.append(total_loss)
print(losses) 
# 打印特定单词(如"beauty")对应的词嵌入向量。
print(model.embeddings.weight[word_to_ix["beauty"]])
def predict_next_word(model, word_to_ix, context_words):
    # 将上下文单词转换为整数索引,并封装成张量
    context_idxs = torch.tensor([word_to_ix[w] for w in context_words], dtype=torch.long)
    
    # 使用模型计算给定上下文的对数概率
    log_probs = model(context_idxs)
    
    # 从对数概率中选择概率最高的单词作为预测结果
    _, predicted_idx = torch.max(log_probs, dim=1)
    
    # 获取预测单词的整数索引,并找到对应的单词
    predicted_word = [k for k, v in word_to_ix.items() if v == predicted_idx.item()][0]
    
    return predicted_word
context_words = ['When',"youth's"]

test = predict_next_word(model,word_to_ix,context_words)
test
CBOW pytorch
import torch
import torch.nn as nn
import torch.optim as optim

CONTEXT_SIZE = 2  # 左右各2个单词作为上下文
raw_text = """We are about to study the idea of a computational process.
Computational processes are abstract beings that inhabit computers.
As they evolve, processes manipulate other abstract things called data.
The evolution of a process is directed by a pattern of rules
called a program. People create programs to direct processes. In effect,
we conjure the spirits of the computer with our spells.""".split()

vocab = set(raw_text)
vocab_size = len(vocab)
word_to_ix = {word: i for i, word in enumerate(vocab)}

data = []
for i in range(CONTEXT_SIZE, len(raw_text) - CONTEXT_SIZE):
    context = [raw_text[i - j - 1] for j in range(CONTEXT_SIZE)] + [raw_text[i + j + 1] for j in range(CONTEXT_SIZE)]
    target = raw_text[i]
    data.append((context, target))


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

    def forward(self, inputs):
        embeds = self.embeddings(inputs).view((1, -1))
#         print(embeds.shape)
        
        out = self.linear(embeds)
        return out


# 创建模型实例
EMBEDDING_DIM = 100
model = CBOW(vocab_size, EMBEDDING_DIM, CONTEXT_SIZE)

# 定义损失函数和优化器
loss_function = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 开始训练
for epoch in range(100):
    total_loss = 0
    for context, target in data:
        # 准备输入和目标张量
        context_idxs = torch.tensor([word_to_ix[w] for w in context], dtype=torch.long)
        target_idx = torch.tensor([word_to_ix[target]], dtype=torch.long)
        # 清除之前的梯度
        model.zero_grad()
        # 前向传播
        log_probs = model(context_idxs)
        # 计算损失
        loss = loss_function(log_probs, target_idx)
        # 反向传播和参数更新
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    # 打印损失
    print(f"Epoch {epoch + 1}, Loss: {total_loss}")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值