找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

260

积分

0

好友

19

主题
发表于 昨天 17:22 | 查看: 3| 回复: 0

自然语言处理(NLP)的核心挑战在于将人类语言转化为计算机可处理的数字形式。词嵌入(Word Embedding)和位置编码(Position Encoding)正是解决这一问题的两大关键技术。本文将通过实例和代码,深入浅出地讲解这两个概念的原理与应用。

一、词嵌入的必要性

计算机只能理解数字,当我们希望它分析"我喜欢吃苹果"这句话时,必须先将文字转换为数字表示。

最简单的方式是为每个词分配唯一编号:

  • "我" → 1
  • "喜欢" → 2
  • "吃" → 3
  • "苹果" → 4

这种方法称为独热编码(One-Hot Encoding),但存在明显缺陷:每个词都是孤立的,模型无法感知"苹果"与"香蕉"同属水果,"喜欢"与"热爱"语义相近。

词嵌入通过将每个词映射为高维向量来解决这个问题:

  • "苹果" → [0.2, 0.5, 0.1, ..., 0.3](100维向量)
  • "香蕉" → [0.3, 0.4, 0.2, ..., 0.1]

这些向量在多维空间中的距离(如余弦相似度)能够反映词义的相似程度,语义相近的词在向量空间中会彼此靠近。

二、PyTorch实现词嵌入

下面用PyTorch实现一个简单的词嵌入示例:

import torch
import torch.nn as nn

# 假设词典包含8个单词
vocab_size = 8  # 词典大小
embedding_dim = 4  # 每个词向量的维度

# 创建词嵌入层
embedding = nn.Embedding(vocab_size, embedding_dim)

# 用单词索引表示句子
sentence = torch.tensor([1, 3, 5, 2])  # 例如:[我, 喜欢, 吃, 苹果]

# 获取词嵌入向量
embedded_sentence = embedding(sentence)

print("原始句子索引:", sentence)
print("词嵌入后的结果形状:", embedded_sentence.shape)  # 输出: [4, 4]
print("嵌入后的向量:\n", embedded_sentence)

代码解析:

  1. nn.Embedding 创建了一个可训练的查找表
  2. 表的维度为 vocab_size × embedding_dim
  3. 输入词的索引,输出对应的向量表示

初始时这些向量是随机的,但随着训练的进行,模型会学习到有意义的表示,使得"苹果"和"香蕉"的向量逐渐接近。

三、序列建模中的位置信息问题

词嵌入解决了词义表示,但忽略了一个关键因素:词序

在自然语言中,词的顺序至关重要:

  • "狗咬人"与"人咬狗"意思截然不同
  • "我今天吃了苹果"与"我吃了苹果今天"虽表达同一事件,但语序差异明显

标准词嵌入模型会将所有词视为无序集合,为了让模型感知词序,需要引入位置编码

四、位置编码原理

位置编码的目标是为每个词添加位置信息。最直观的方法是简单编号:

  • 第1个词 → 1
  • 第2个词 → 2
  • ...

但这种线性编号难以让模型学习相对位置关系(如"第3个词"与"第4个词"相邻)。

Transformer提出了更优雅的正弦余弦位置编码方案:

  1. 为每个位置生成一个向量
  2. 向量的各维度使用不同频率的正弦和余弦函数
  3. 相邻位置的编码向量相似,距离较远的位置编码差异较大

代码实现如下:

import torch
import math

max_position = 10  # 最大位置数
embedding_dim = 4  # 向量维度

# 创建位置编码矩阵
position_encoding = torch.zeros(max_position, embedding_dim)

# 对每个位置
for pos in range(max_position):
    # 对每个维度
    for i in range(0, embedding_dim, 2):
        # 偶数维度使用正弦函数
        position_encoding[pos, i] = math.sin(pos / (10000 ** (i / embedding_dim)))
        # 奇数维度使用余弦函数
        if i + 1 < embedding_dim:
            position_encoding[pos, i + 1] = math.cos(pos / (10000 ** (i / embedding_dim)))

print("位置编码矩阵形状:", position_encoding.shape)  # [10, 4]
print("前5个位置的编码:\n", position_encoding[:5])

这种位置编码具有以下特性:

  1. 不同位置的编码向量各不相同
  2. 相邻位置的编码相似(可通过向量点积验证)
  3. 能够表示相对位置关系

五、词嵌入与位置编码的结合

实际应用中,我们将词嵌入和位置编码相加作为模型输入。以下是两个完整示例:

示例1:基础组合

import torch
import torch.nn as nn
import torch.nn.functional as F

# 参数设置
vocab_size = 10
embedding_dim = 16
max_seq_len = 5

# 创建词嵌入层
word_embedding = nn.Embedding(vocab_size, embedding_dim)

# 创建位置编码
position_encoding = torch.zeros(max_seq_len, embedding_dim)
for pos in range(max_seq_len):
    for i in range(0, embedding_dim, 2):
        position_encoding[pos, i] = math.sin(pos / (10000 ** (i / embedding_dim)))
        if i + 1 < embedding_dim:
            position_encoding[pos, i + 1] = math.cos(pos / (10000 ** (i / embedding_dim)))

# 将位置编码转为Embedding层(不参与训练)
position_embedding = nn.Embedding.from_pretrained(position_encoding, freeze=True)

# 假设有一个句子
sentence_indices = torch.tensor([2, 4, 6, 0, 0])  # 0表示padding
sentence_length = 3  # 实际有效长度

# 获取词嵌入
word_vectors = word_embedding(sentence_indices)

# 创建位置索引
positions = torch.arange(sentence_length, dtype=torch.long)
positions = positions.unsqueeze(0).expand(1, sentence_length)

# 获取位置嵌入
pos_vectors = position_embedding(positions)

# 组合词嵌入和位置嵌入
final_vectors = word_vectors[:sentence_length] + pos_vectors

print("词嵌入形状:", word_vectors.shape)  # [5, 16]
print("位置嵌入形状:", pos_vectors.shape)  # [1, 3, 16]
print("最终向量形状:", final_vectors.shape)  # [3, 16]

这个示例展示了:

  1. 创建词嵌入层和位置编码层
  2. 对句子(用索引表示)进行处理
  3. 获取词嵌入和位置嵌入
  4. 将两者相加得到最终输入表示

示例2:批次处理与序列对齐

import torch
import numpy
import torch.nn as nn
import torch.nn.functional as F

# 批次大小
batch_size = 2

# 单词表大小
max_num_src_words = 8
max_num_tgt_words = 8
model_dim = 16

# 序列大小
max_src_seq_len = 5
max_tgt_seq_len = 5
max_position_len = 5

# 序列长度
src_len = torch.Tensor([2, 4]).to(torch.int)
tgt_len = torch.Tensor([4, 3]).to(torch.int)

# 生成单词索引构成的句子
src_seq = torch.cat([torch.unsqueeze(F.pad(torch.randint(1, max_num_src_words, (L,)), (0, max_src_seq_len - L)), 0) for L in src_len])
tgt_seq = torch.stack([F.pad(torch.randint(1, max_num_tgt_words, (L,)), (0, max_tgt_seq_len - L)) for L in tgt_len], 0)

# 构造词嵌入
src_embedding_table = nn.Embedding(max_num_src_words+1, model_dim)
tgt_embedding_table = nn.Embedding(max_num_tgt_words+1, model_dim)
src_embedding = src_embedding_table(src_seq)
tgt_embedding = tgt_embedding_table(tgt_seq)

# 构造位置编码
pos_mat = torch.arange(max_position_len).reshape((-1, 1))
i_mat = torch.pow(10000, torch.arange(0, model_dim, 2).reshape((1, -1)) / model_dim)
pe_embedding_table = torch.zeros(max_position_len, model_dim)
pe_embedding_table[:, 0::2] = torch.sin(pos_mat / i_mat)
pe_embedding_table[:, 1::2] = torch.cos(pos_mat / i_mat)

pe_embedding = nn.Embedding(max_position_len, model_dim)
pe_embedding.weight = nn.Parameter(pe_embedding_table, requires_grad=False)

src_pos = torch.cat([torch.unsqueeze(torch.arange(max(src_len)), 0) for _ in src_len]).to(torch.int32)
tgt_pos = torch.cat([torch.unsqueeze(torch.arange(max(tgt_len)), 0) for _ in src_len]).to(torch.int32)

src_pe_embedding = pe_embedding(src_pos)
tgt_pe_embedding = pe_embedding(tgt_pos)

print(src_pos)
print(tgt_pos)
print(src_pe_embedding)
print(tgt_pe_embedding)
数据准备:序列与批次

代码首先定义了基本参数:

batch_size = 2  # 批次大小
max_num_src_words = 8  # 源语言词汇表大小
max_num_tgt_words = 8  # 目标语言词汇表大小
model_dim = 16  # 向量维度

max_src_seq_len = 5  # 源序列最大长度
max_tgt_seq_len = 5  # 目标序列最大长度
max_position_len = 5  # 最大位置数

src_len = torch.Tensor([2, 4]).to(torch.int)  # 源序列实际长度
tgt_len = torch.Tensor([4, 3]).to(torch.int)  # 目标序列实际长度

这里定义了:

  • 批次大小:一次处理2个序列对
  • 词汇表大小:源语言和目标语言各8个单词
  • 模型维度:每个词向量16维
  • 序列长度:源序列长度为2和4,目标序列长度为4和3
生成序列数据
src_seq = torch.cat([torch.unsqueeze(F.pad(torch.randint(1, max_num_src_words, (L,)), (0, max_src_seq_len - L)), 0) for L in src_len])
tgt_seq = torch.stack([F.pad(torch.randint(1, max_num_tgt_words, (L,)), (0, max_tgt_seq_len - L)) for L in tgt_len], 0)

这段代码完成三个步骤:

  1. 随机生成单词索引torch.randint 生成1到8之间的随机整数
  2. 填充序列F.pad 将短序列用0填充到固定长度
  3. 组合批次torch.cattorch.stack 将多个序列组合

生成的源序列示例:

tensor([[6, 5, 0, 0, 0],  # 第一个序列长度2,补3个0
        [1, 4, 3, 4, 0]]) # 第二个序列长度4,补1个0
词嵌入:索引到向量的映射
src_embedding_table = nn.Embedding(max_num_src_words+1, model_dim)
tgt_embedding_table = nn.Embedding(max_num_tgt_words+1, model_dim)
src_embedding = src_embedding_table(src_seq)
tgt_embedding = tgt_embedding_table(tgt_seq)

关键点:

  • 参数(词汇表大小, 向量维度)
  • +1的原因:0用作填充符号,所以需要9个位置(0-8)
  • 工作原理:可训练的查找表,将索引映射到向量
位置编码:添加位置信息
pos_mat = torch.arange(max_position_len).reshape((-1, 1))
i_mat = torch.pow(10000, torch.arange(0, model_dim, 2).reshape((1, -1)) / model_dim)
pe_embedding_table = torch.zeros(max_position_len, model_dim)

pe_embedding_table[:, 0::2] = torch.sin(pos_mat / i_mat)
pe_embedding_table[:, 1::2] = torch.cos(pos_mat / i_mat)

pe_embedding = nn.Embedding(max_position_len, model_dim)
pe_embedding.weight = nn.Parameter(pe_embedding_table, requires_grad=False)

实现了Transformer的正弦余弦位置编码:

  1. 位置矩阵[0, 1, 2, 3, 4] 表示词的位置
  2. 频率矩阵:控制正弦余弦函数的周期
  3. 编码表:偶数维用正弦,奇数维用余弦
  4. 固定参数requires_grad=False 训练时不更新
获取位置编码向量
src_pos = torch.cat([torch.unsqueeze(torch.arange(max(src_len)), 0) for _ in src_len]).to(torch.int32)
tgt_pos = torch.cat([torch.unsqueeze(torch.arange(max(tgt_len)), 0) for _ in src_len]).to(torch.int32)

src_pe_embedding = pe_embedding(src_pos)
tgt_pe_embedding = pe_embedding(tgt_pos)

这里:

  • src_postgt_pos 是位置索引矩阵
  • 对于源序列,有效长度最长为4,位置索引为 [0, 1, 2, 3]
  • 每个位置索引对应位置编码表中的一行向量

最终得到:

  • src_embedding:词嵌入向量
  • src_pe_embedding:位置编码向量
  • 将两者相加,得到同时包含词义和位置信息的输入表示
设计思想

代码完整实现了Transformer模型的两个基础组件:

  1. 词嵌入:将单词转换为连续向量空间表示
  2. 位置编码:通过正弦余弦函数为每个位置创建唯一编码

这种设计的优势:

  • 捕获单词间的语义关系
  • 保留序列中的位置信息
  • 编码固定,不依赖训练数据,具有通用性

六、技术价值与应用

词嵌入和位置编码是深度学习处理文本的基石,带来了以下突破:

  1. 语义表示:词嵌入让计算机能够"理解"词义,捕捉词语间的语义关系

  2. 上下文感知:位置编码使模型能够区分"狗咬人"与"人咬狗"

  3. 泛化能力:通过学习到的词向量,模型可以对未见过的句子进行推理

这些技术是Transformer、BERT、GPT等先进模型的基础,推动了自然语言处理领域的快速发展。

转自 CSDN 作者 山野蓝莓酸奶昔




上一篇:Transformer旋转位置编码RoPE原理与实战:从数学推导到LLaMA源码解析
下一篇:Transformer架构深度解析:自注意力机制与编码器-解码器实现原理
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-12 08:34 , Processed in 0.082756 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表