本项目使用PyTorch构建BERT模型。BERT是一种预训练深度双向Transformer模型,用于自然语言处理任务。模型核心两个主要部分:BertModel类和Model类。BertModel类负责实现BERT模型的核心架构,而Model类则在此基础上添加了预训练任务的输出层和损失函数。
BertModel
类是BERT
模型的核心,它包含了BERT
的嵌入层、编码层和池化层。
在初始化过程中,BertModel
接收config
参数,字典config
中包含了隐藏层大小、前馈网络大小、注意力头数和层数。
-
嵌入层:将输入的索引转换为对应的嵌入表示,包括词嵌入、位置嵌入和段嵌入。
-
编码层:由多个Transformer编码器层组成,这些编码器层堆叠在一起,形成了BERT的主体结构。每个编码器层都包含自注意力机制和前馈网络,能够处理序列数据并捕捉长距离依赖关系。
-
池化层:对编码层的输出进行聚合,得到整个序列的表示,这通常用于分类任务。
如下代码所示,输入嵌入后,在进入编码层,最后进入池化层,将所得结果返回。
class BertModel(nn.Module):
def __init__(self, config):
super().__init__()
self.embedding = BertEmbedding(config)
self.bert_layer = nn.Sequential(
*[BertEncoder(config["hidden_size"], config["feed_num"], config["head_num"]) for i in
range(config["layer_num"])])
self.pool = BertPooler(config)
def forward(self, batch_idx, batch_seg_idx):
x = self.embedding(batch_idx, batch_seg_idx)
x = self.bert_layer(x)
bertout2 = self.pool(x)
return x, bertout2
以下是BERT嵌入层(BertEmbeddding
)的实现。
- 在初始化方法中,
BERT
定义了三个嵌入层(embeddings),这与Transfomer的嵌入方法一致:word_embeddings
:用于将词汇索引映射到嵌入向量。position_embeddings
:用于将位置索引映射到位置嵌入向量,以捕获单词在句子中的位置信息。token_type_embeddings
:用于将令牌类型索引映射到类型嵌入向量。
- 每个嵌入层都是一个
nn.Embedding
实例,其权重(weight
)被设置为可训练(requires_grad=True
),这里也可定义为不可训练。
forward
方法接收两个参数(batch_idx
和batch_seg_idx
),这两个变量分别代表句子的token
表示和句子的划分(是第一个句子还是后一个句子)。word_emb
通过word_embeddings
层获取词汇嵌入。pos_idx
生成一个位置索引张量,其大小与批量大小相匹配,并用于通过position_embeddings
层获取位置嵌入。这里使用torch.arange
和repeat
来生成位置索引。token_emb
通过token_type_embeddings
层获取令牌类型嵌入。- 将词汇嵌入、位置嵌入和令牌类型嵌入相加,得到最终的嵌入表示。
- 应用层归一化和丢弃操作,然后返回最终的嵌入向量。
代码如下所示:
class BertEmbedding(nn.Module):
def __init__(self, config):
super().__init__()
self.word_embeddings = nn.Embedding(config["vocab_size"], config["hidden_size"])
self.word_embeddings.weight.requires_grad = True
self.position_embeddings = nn.Embedding(config["max_position_embeddings"], config["hidden_size"])
self.position_embeddings.weight.requires_grad = True
self.token_type_embeddings = nn.Embedding(config["type_vocab_size"], config["hidden_size"])
self.token_type_embeddings.weight.requires_grad = True
self.LayerNorm = nn.LayerNorm(config["hidden_size"])
self.dropout = nn.Dropout(config["hidden_dropout_prob"])
def forward(self, batch_idx, batch_seg_idx):
word_emb = self.word_embeddings(batch_idx)
pos_idx = torch.arange(0, self.position_embeddings.weight.data.shape[0], device=batch_idx.device)
pos_idx = pos_idx.repeat(batch_idx.shape[0], 1)
pos_emb = self.position_embeddings(pos_idx)
token_emb = self.token_type_embeddings(batch_seg_idx)
emb = word_emb + pos_emb + token_emb
emb = self.LayerNorm(emb)
emb = self.dropout(emb)
return emb
BertEncoder
部分即为Transformer
的Encoder
部分。
class BertEncoder(nn.Module):
def __init__(self,hidden_size,feed_num,head_num):
super().__init__()
self.multi_head_att = Multi_Head_Att(hidden_size,head_num)
self.add_norm1 = Add_Norm(hidden_size)
self.feed_forward = Feed_Forward(hidden_size,feed_num)
self.add_norm2 = Add_Norm(hidden_size)
def forward(self,x):
multi_head_out = self.multi_head_att(x)
add_norm1_out = self.add_norm1(multi_head_out)
add_norm1_out = x + add_norm1_out
feed_forward_out = self.feed_forward(add_norm1_out)
add_norm2_out = self.add_norm2(feed_forward_out)
add_norm2_out = add_norm1_out + add_norm2_out
return add_norm2_out
Model
类在BertModel
的基础上添加了预训练任务的输出层和损失函数,用于执行BERT的两种主要预训练任务:“masked language model”(MLM)和“next sentence prediction”(NSP)。
Model
定义了两个线性层用于MLM
和NSP
任务的输出,以及对应的损失函数。MLM任务的损失函数会忽略填充索引,而NSP任务的损失函数则正常计算。
在前向传播过程中,Model
类首先调用BertModel
的前向传播方法,获取编码层的输出和池化层的输出。然后,这些输出分别通过MLM和NSP的线性层,得到预测结果。如果提供了真实值和标签,模型将计算两种任务的损失,并返回总损失。如果没有提供真实值和标签,模型将返回预测结果。
class Model(nn.Module):
def __init__(self, config):
super().__init__()
self.bert = BertModel(config)
self.cls_mask = nn.Linear(config["hidden_size"], config["vocab_size"])
self.cls_next = nn.Linear(config["hidden_size"], 2)
self.loss_fun_mask = nn.CrossEntropyLoss(ignore_index=0)
self.loss_fun_next = nn.CrossEntropyLoss()
def forward(self, batch_idx, batch_seg_idx, batch_mask_val=None, batch_label=None):
bertout1, bertout2 = self.bert(batch_idx, batch_seg_idx)
pre_mask = self.cls_mask(bertout1)
pre_next = self.cls_next(bertout2)
if batch_mask_val is not None and batch_label is not None:
loss_mask = self.loss_fun_mask(pre_mask.reshape(-1, pre_mask.shape[-1]), batch_mask_val.reshape(-1))
loss_next = self.loss_fun_next(pre_next, batch_label)
loss = loss_mask + loss_next
return loss
else:
return torch.argmax(pre_mask, dim=-1), torch.argmax(pre_next, dim=-1)
本章节介绍了BERT模型的构建过程,包括其核心组件和预训练任务的实现。BERT模型通过深度双向预训练,能够学习到丰富的语言特征,并在多种自然语言处理任务中取得优异的性能。通过BertModel
类和Model
类的实现,BERT模型可以有效地进行预训练,并适应不同的下游任务。