在自然语言处理(NLP)任务中,模型学习的不是原始的文本字符串,而是这些字符串通过分词和索引化过程转换成的单词索引。实际学习的内容是这些单词索引对应的嵌入向量及其在模型中的权重。
原始文本到模型输入的过程
-
原始文本:
- 例如:“hello world”
-
分词(Tokenization):
- 将文本字符串分割成单词、子词或字符。例如:
["hello", "world"]
- 将文本字符串分割成单词、子词或字符。例如:
-
构建词汇表(Vocabulary Building):
- 为每个唯一的单词分配一个唯一的整数索引。例如:
{"hello": 0, "world": 1}
- 为每个唯一的单词分配一个唯一的整数索引。例如:
-
转换为索引序列(Indexing):
- 将分割后的单词列表转换为对应的整数索引序列。例如:
[0, 1]
- 将分割后的单词列表转换为对应的整数索引序列。例如:
嵌入层的作用
在模型中,嵌入层 (nn.Embedding
) 接受这些离散的单词索引,并将其转换为连续的高维向量表示。这个过程将离散的输入(单词索引)转换为可以进行梯度下降和学习的连续表示。
具体例子
假设我们有一个包含 10 个单词的词汇表,每个嵌入向量的维度为 3:
import torch
import torch.nn as nn# 定义词汇表大小和嵌入维度
vocab_size = 10
embedding_dim = 3# 定义嵌入层
embedding_layer = nn.Embedding(vocab_size, embedding_dim)# 假设 text_tensor 是单词索引序列
text_tensor = torch.tensor([0, 1]) # 例如,"hello world" 对应的索引序列# 将索引序列转换为嵌入向量
embedded_text = embedding_layer(text_tensor)
print("Embedded text:", embedded_text)
输出可能是这样的(初始化的权重是随机的):
Embedded text:
tensor([[ 0.0254, -0.4884, -0.2643],[-1.4475, -0.4748, -0.4428]], grad_fn=<EmbeddingBackward>)
训练过程中学习的内容
在训练过程中,模型通过反向传播学习以下内容:
-
嵌入层的权重:
- 嵌入层的权重矩阵大小为
[vocab_size, embedding_dim]
,其中每一行都是一个单词的嵌入向量。通过训练,模型调整这些嵌入向量,使其更好地表示单词的语义。
- 嵌入层的权重矩阵大小为
-
模型其他部分的权重:
- 除了嵌入层,模型的其他部分(如 Transformer 层、卷积层、全连接层等)也会根据任务调整它们的权重。
好的,让我们通过一个具体的例子来更详细地说明输入张量和嵌入向量的概念,以及它们在嵌入层中的转换过程。
示例:输入张量和嵌入向量
1. 定义词汇表和嵌入层
首先,我们定义一个简单的词汇表,并创建一个嵌入层:
import torch
import torch.nn as nn# 定义词汇表大小和嵌入向量的维度
vocab_size = 5 # 词汇表包含 5 个单词
embedding_dim = 3 # 每个嵌入向量的维度为 3# 创建嵌入层
embedding_layer = nn.Embedding(vocab_size, embedding_dim)
在这个例子中,我们的词汇表大小为 5,每个单词的嵌入向量维度为 3。
2. 创建输入张量
假设我们有一个句子 “hello world” 对应的单词索引是 [0, 1]
。我们将其表示为一个张量:
# 创建输入张量(单词索引序列)
text_tensor = torch.tensor([0, 1]) # 例如,"hello world" 对应的索引序列
这里 text_tensor
是一个包含单词索引的张量。我们可以将其扩展到批处理,例如:
# 扩展到批处理(假设我们有两个句子)
batch_text_tensor = torch.tensor([[0, 1], [2, 3]]) # 每个数字表示词汇表中的单词索引
在这个例子中,batch_text_tensor
是一个形状为 [2, 2]
的张量,其中有两个句子,每个句子包含两个单词的索引。
3. 转换为嵌入向量
将输入张量通过嵌入层转换为嵌入向量:
# 将索引序列转换为嵌入向量
embedded_text = embedding_layer(batch_text_tensor)
print("嵌入向量:")
print(embedded_text)
输出可能是这样的(初始化的权重是随机的):
嵌入向量:
tensor([[[ 0.0254, -0.4884, -0.2643],[-1.4475, -0.4748, -0.4428]],[[ 0.1867, 0.3578, 0.6783],[ 0.6351, 0.4872, -0.7632]]], grad_fn=<EmbeddingBackward>)
4. 详细解释
batch_text_tensor
的形状为[2, 2]
,表示有两个句子,每个句子包含两个单词的索引。embedding_layer(batch_text_tensor)
的输出形状为[2, 2, 3]
,表示每个单词索引被转换为一个 3 维的嵌入向量。- 输出中的每个向量是嵌入层的权重矩阵中的一行。
在 CLIP
模型中的具体应用
在 CLIP
模型中,文本输入的单词索引通过嵌入层转换为嵌入向量,然后进行进一步的处理:
class CLIP(nn.Module):def __init__(self, ...):...self.token_embedding = nn.Embedding(vocab_size, transformer_width)...def encode_text(self, text):x = self.token_embedding(text).type(self.dtype)...
这里的 text
是一个包含单词索引的张量。例如,假设 text
是 [batch_size, sequence_length]
形状的张量:
text = torch.tensor([[0, 1], [2, 3]]) # 一个示例输入,其中每个数字表示词汇表中的单词索引
通过嵌入层转换后的嵌入向量表示:
x = self.token_embedding(text)
这将得到一个形状为 [batch_size, sequence_length, embedding_dim]
的张量,其中每个单词索引被转换为一个嵌入向量。
详细总结
- 离散输入:原始文本被转换为单词索引序列,这些索引是离散的整数。
- 嵌入层:嵌入层将离散的单词索引转换为连续的高维向量。
- 模型输入:嵌入向量作为模型的输入,用于进一步的计算和训练。
- 学习内容:模型通过训练学习嵌入层的权重和其他网络参数,以更好地表示和处理输入数据。
通过这个过程,模型能够有效地将离散的文本表示转换为适合神经网络处理的连续表示,从而实现对文本数据的学习和推理。
总结
原始的文本字符串本身并不被直接学习。模型学习的是通过嵌入层转换后的单词索引的嵌入向量,以及这些嵌入向量在模型中的使用。通过训练,模型优化这些嵌入向量及其他权重,以更好地执行任务。这个过程将离散的文本表示转换为连续的高维向量表示,使得模型能够通过梯度下降来学习和优化。