个人笔记
首先,文本中的词汇在输入到词嵌入层之前,已经经过了一个数字化的表示,也就是说,传入到词嵌入层的是数字,而文本嵌入层(即词嵌入层)目的就是将文本中词汇的数字表示转变为向量表示,这样可以在高维空间捕捉文本词汇之间的关系。
# 构建Embedding类来实现文本嵌入层
class Embeddings(nn.Module):
def __init__(self, d_model, vocab):
# d_model: 词嵌入的维度
# vocab: 词表的大小
super(Embeddings, self).__init__()
# 定义Embedding层
self.lut = nn.Embedding(vocab, d_model)
# 将参数传入类中
self.d_model = d_model
def forward(self, x):
# x: 代表输入进模型的文本通过词汇映射后的数字张量
return self.lut(x) * math.sqrt(self.d_model)
d_model = 512
vocab = 1000
x = torch.LongTensor([[100, 2, 421, 508], [491, 998, 1, 221]]) # 创建了一个包含两个样本的输入张量,每个样本包含四个词汇索引。
emb = Embeddings(d_model, vocab)
embr = emb(x)
print("embr:", embr)
print(embr.shape) # 打印嵌入后的张量和其形状。输出的形状应该是(2, 4, 512),表示有两个样本,每个样本有四个词汇,每个词汇映射到一个512维的向量
问:如何通俗理解这个词嵌入?
假设我们有一个非常简单的词汇表(vocab)和一些句子。词汇表如下:
vocab = {
"hello": 0,
"world": 1,
"this": 2,
"is": 3,
"a": 4,
"test": 5
}
我们想要将以下句子转换为嵌入向量:
sentences = [
"hello world this is a test",
"this is another test"
]
首先,我们需要将句子中的每个单词转换为词汇表中的索引。对于上面的句子,索引表示如下:
indexed_sentences = [
[0, 1, 2, 3, 4, 5],
[2, 3, 5]
]
现在,我们使用上面的代码来创建一个嵌入层,其中d_model
(嵌入向量的维度)设置为4,vocab
(词汇表的大小)设置为6(因为有6个单词)。
d_model = 4
vocab = 6
然后,我们创建一个嵌入层的实例,并将其应用于索引化的句子:
emb = Embeddings(d_model, vocab)
x = torch.LongTensor(indexed_sentences)
embr = emb(x)
在嵌入层内部,nn.Embedding
会为每个索引查找一个4维的向量(因为我们设置了d_model=4
)。例如,索引0(对应单词"hello")可能会映射到一个向量如[0.1, -0.2, 0.3, -0.4]
。这个过程对每个索引都会发生,从而将整个句子转换为一个嵌入矩阵。
应用嵌入层后,embr
将是一个形状为(2, 6, 4)
的张量,其中2是句子的数量,6是每个句子中单词的数量(包括填充),4是嵌入向量的维度。每个单词都被转换为一个4维向量,这些向量可以捕捉单词的语义信息。
最后,我们打印嵌入结果和形状:
print("embr:", embr)
print(embr.shape)
输出可能会是这样的(注意,实际的嵌入向量会由模型随机初始化,并且在训练过程中学习):
embr: tensor([[[ 0.1, -0.2, 0.3, -0.4],
[ 0.5, 0.6, -0.7, 0.8],
[ 0.9, -1.0, 1.1, -1.2],
[-0.1, 0.2, -0.3, 0.4],
[-0.5, 0.6, -0.7, 0.8],
[-0.9, 1.0, -1.1, 1.2]],
[[ 0.9, -1.0, 1.1, -1.2],
[-0.1, 0.2, -0.3, 0.4],
[-0.5, 0.6, -0.7, 0.8]]])
embr.shape
torch.Size([2, 6, 4])
这里,每个单词都被转换为一个4维的向量,这些向量可以作为神经网络的输入,用于各种NLP任务。
问:为什么another没有对应索引?
在上面的例子中,我提供了一个简化的词汇表(vocab)和句子,用于说明嵌入层的工作原理。词汇表中只包含了几个单词,而"another"这个单词并没有包含在词汇表中,因此它没有对应的数字索引。
在实际应用中,我们会有一个更全面、包含大量单词的词汇表。如果遇到词汇表中没有的单词,即所谓的"未知单词"(unknown words),有几种处理方法:
-
添加到词汇表:如果"another"是一个常见的单词,我们可能会选择将其添加到词汇表中,并为其分配一个新的索引。
-
使用特殊标记:在词汇表中,通常会有一个特殊的标记来表示未知单词,例如
<UNK>
。当遇到未知单词时,我们可以使用这个特殊标记的索引来代替。 -
子词处理:在一些高级的NLP模型中,如BERT,可能会使用子词(subword)处理方法。这种方法会将单词分解为更小的单元(子词),并为这些子词创建嵌入。这样,即使遇到未知单词,模型也可以通过识别和组合这些子词来理解单词的含义。
-
嵌入层的初始化:在创建嵌入层时,我们可以指定一个额外的嵌入向量用于未知单词。例如,在PyTorch中,
nn.Embedding
允许我们指定padding_idx
和unk_idx
参数,分别用于填充(padding)和未知单词。
让我们更新一下之前的例子,包括一个未知单词的处理:
# 假设词汇表中添加了<UNK>标记
vocab = {
"hello": 0,
"world": 1,
"this": 2,
"is": 3,
"a": 4,
"test": 5,
"<UNK>": 6 # 用于未知单词
}
# 索引化句子,遇到未知单词"another"使用<UNK>的索引
indexed_sentences = [
[0, 1, 2, 3, 4, 5],
[2, 3, 6, 5] # "another"用<UNK>的索引6代替
]
# 创建嵌入层实例
emb = Embeddings(d_model, vocab)
x = torch.LongTensor(indexed_sentences)
embr = emb(x)
print("embr:", embr)
print(embr.shape)
在这个更新后的例子中,如果"another"不在词汇表中,我们将其替换为<UNK>
标记的索引6,然后嵌入层会为这个索引生成一个嵌入向量。这样,即使遇到未知单词,模型也能够继续处理并生成嵌入表示。
引申:位置编码和位置嵌入
见博文:Transformer学习笔记
词嵌入与位置编码的结合:位置嵌入是将词嵌入与位置编码相加得到的。这种结合使得模型既能够捕捉单词的语义信息,又能够捕捉单词的位置信息。
位置编码的作用:位置编码为模型提供了序列中每个位置的相对或绝对位置信息,这是位置嵌入的基础。
位置嵌入的作用:位置嵌入是模型最终使用的输入表示,它结合了词嵌入的语义信息和位置编码的位置信息,使得模型能够同时处理单词的语义和顺序。