首页 > 其他分享 >BERT的代码实现

BERT的代码实现

时间:2024-09-22 22:20:12浏览次数:3  
标签:hiddens BERT nn 实现 代码 mlm num self size

目录

1.BERT的理论

2.代码实现 

 2.1构建输入数据格式

 2.2定义BERT编码器的类

 2.3BERT的两个任务

2.3.1任务一:Masked Language Modeling MLM掩蔽语言模型任务 

2.3.2 任务二:next sentence prediction

3.整合代码 

 4.知识点个人理解


 

1.BERT的理论

BERT全称叫做Bidirectional Encoder Representations from Transformers, 论文地址: [1810.04805] BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding (arxiv.org)

BERT是谷歌AI研究院在2018年10月提出的一种预训练模型. BERT本质上就是Transformer模型的encoder部分, 并且对encoder做了一些改进.

下图中编码器部分即BERT的基本结构.

  

2.代码实现 

import torch
from torch import nn
import dltools

 2.1构建输入数据格式

def get_tokens_and_segments(tokens_a, tokens_b=None):
    #classification 分类
    #BERT是两句话作为一对句子一同传入的,也可以单独传一句话,若序列长度长,可以补padding
    #假设先传一句话tokens_a
    tokens = ['<cls>'] + tokens_a + ['<sep>']  #tokens_embedding层的处理
    segments = [0] * (len(tokens_a) + 2)  #判断词元属于哪一句话,加标记,0属于第一句话
    if tokens_b is not None:
        tokens += tokens_b + ['sep']
        segments += [1] * (len(tokens_b) + 1)
    return tokens, segments


#测试上面的函数
get_tokens_and_segments([1, 2, 3], [4, 5, 6])

(['<cls>', 1, 2, 3, '<sep>', 4, 5, 6, 'sep'], [0, 0, 0, 0, 0, 1, 1, 1, 1])

 2.2定义BERT编码器的类

class BERTEncoder(nn.Module):
    #由于前馈网络的ffn_num_outputs = num_hiddens,没有初始化传入
    #__init__()里面的参数,是创建类的时候传入的参数
    def __init__(self, vocab_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, num_layers, dropout,
                max_len=1000, key_size=768, query_size=768, value_size=768, **kwargs):
        super().__init__(**kwargs)
        #token_embeddings层
        self.token_emdedding = nn.Embedding(vocab_size, num_hiddens)
        #segment_embedding层  (传入两个句子,所以第0维为2)
        self.segment_embedding = nn.Embedding(2, num_hiddens)
        #pos_embedding层  :位置嵌入层是可以学习的, 用nn.Parameter()定义可学习的参数
        self.pos_embedding = nn.Parameter(torch.randn(1, max_len, num_hiddens))
        
        #设置Encoder_block的数量
        self.blks = nn.Sequential()  #为使用的Encoder_block依次编号
        for i in range(num_layers):  #有几层网络循环几层
            self.blks.add_module(f'{i}', dltools.EncoderBlock(key_size, query_size, value_size, num_hiddens, norm_shape, 
                                                              ffn_num_input, ffn_num_hiddens, num_heads, dropout))
    
    #__init__()里面的参数,是创建类的时候传入的参数
    #foward里面的参数是创建完类对象之后,调用类方法时传入的参数
    def forward(self, tokens, segments, valid_lens):
        #X = token_embedding + segment_embedding + pos_embedding
        #传入的token_embedding,segment_embedding两者的shape相同,可以直接相加
        X = self.token_emdedding(tokens) + self.segment_embedding(segments)
        #pos_embedding与前两层的数据shape不相同,不能直接相加
        #切片让self.pos_embedding的第1维度的数据切片到token_embedding,segment_embedding相加之后的数
        X = X + self.pos_embedding.data[:, :X.shape[1], :]
        
        for blk in self.blks:
            X = blk(X, valid_lens)
        return X  
#测试上面代码


#创建BERTEncoder类对象
vocab_size, num_hiddens, ffn_num_hiddens, num_heads = 10000, 768, 1024, 4
norm_shape, ffn_num_input, num_layers, dropout = [768], 768, 2, 0.2
encoder = BERTEncoder(vocab_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, num_layers, dropout)


tokens = torch.randint(0, vocab_size, (2, 8)) #生成随机正整数
segments = torch.tensor([[0, 0, 0, 0, 1, 1, 1, 1], [0, 0, 0, 0, 1, 1, 1, 1]])
#调用类方法
encoded_X = encoder(tokens, segments, None)


encoded_X.shape
torch.Size([2, 8, 768])

#  nn.Sequential()是PyTorch中的一个类,它允许用户将多个计算层按照顺序组合成一个模型。在深度学习中,模型可以是由各种不同类型的层组成的,例如卷积层、池化层、全连接层等。nn.Sequential()方法可以将这些层组合在一起,形成一个整体模型。 

 2.3BERT的两个任务

2.3.1任务一:Masked Language Modeling MLM掩蔽语言模型任务 

class MaskLM(nn.Module):
    def __init__(self, vocab_size, num_inputs=768, **kwargs):
        super().__init__(**kwargs)
        self.mlp = nn.Sequential(nn.Linear(num_inputs, num_hiddens),  #全连接层
                                nn.ReLU(),  
                                nn.LayerNorm(num_hiddens), 
                                nn.Linear(num_hiddens, vocab_size))  #输出层
    
    #X表示随机(15%概率)将一些词元换成mask
    #pred_positions表示已经处理好的80%概率将选中的词换成mask>, 10%概率换成随机词元,10%概率保持原有词元
    #pred_position是二维数据
    def forward(self, X, pred_positions):  
        num_pred_positions = pred_positions.shape[1]  #索引出80%、10%、10%三个概率选出的需要转换的词位置数量
        pred_positions = pred_positions.reshape(-1)  #变成一维数据
        batch_size = X.shape[0]  #获取批次
        batch_idx = torch.arange(0, batch_size) #获取批次的编号
        #将批次编号与元素数量对应起来
        #例如:batch_size = [0, 1]   -->   [0, 0, 0, 1, 1, 1]
        batch_idx = torch.repeat_interleave(batch_idx, num_pred_positions)  #将batch_idx中每个元素重复num_pred_positions次
        #把要预测位置的数据取出来
        masked_X = X[batch_idx, pred_positions]
        masked_X = masked_X.reshape(batch_size, num_pred_positions, -1)  #还原维度
        mlm_Y_hat = self.mlp(masked_X)
        return mlm_Y_hat
#测试代码


mlm = MaskLM(vocab_size, num_hiddens)
mlm_positions = torch.tensor([[1, 5, 2], [6, 1, 5]])
mlm_Y_hat = mlm(encoded_X, mlm_positions)


mlm_Y_hat.shape    #2:2个批次,   3:三个需要转换词元的位置     10000:计算的概率数量(在最后会用softmax函数计算分类结果),vocab_size有10000个,
torch.Size([2, 3, 10000])
mlm_Y = torch.tensor([[7, 8, 9], [10, 20, 30]])  #假设真实值
loss = nn.CrossEntropyLoss(reduction='none')
mlm_l = loss(mlm_Y_hat.reshape(-1, vocab_size), mlm_Y.reshape(-1))  # mlm_Y_hat的shape=(6, 10000)     mlm_Y的shape=(6)
mlm_l.shape

torch.Size([6])

2.3.2 任务二:next sentence prediction

class NextSentencePred(nn.Module):
    def __init__(self, num_inputs, **kwargs):
        super().__init__(**kwargs)
        self.output = nn.Linear(num_inputs, 2)  #预测输入的句子是否为下一个句子,预测目标值为“是/否”二分类问题
        
    def forward(self, X):
        #X的形状(batch_size, num_hiddens)
        return self.output(X)
#测试代码


encoded_X = torch.flatten(encoded_X, start_dim=1)  #将数据展平,相当于reshape
nsp = NextSentencePred(encoded_X.shape[-1])
nsp_Y_hat = nsp(encoded_X)

nsp_Y_hat.shape

torch.Size([2, 2])
#计算损失
nsp_y = torch.tensor([0, 1])   #假设真实值
nsp_1 = loss(nsp_Y_hat, nsp_y)
nsp_1.shape

torch.Size([2])

3.整合代码 

class BERTModel(nn.Module):
    def __init__(self, vocab_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, num_layers, dropout,
                 max_len=1000, key_size=768, query_size=768, value_size=768,
                 hid_in_features=768, mlm_in_features=768, nsp_in_features=768, **kwargs):
        super().__init__(**kwargs)
        #初始化编码器对象
        self.encoder = BERTEncoder(vocab_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, num_layers, dropout,
                                   max_len=max_len, key_size=key_size, query_size=query_size, value_size=value_size)
        #掩蔽语言模型任务
        self.mlm = MaskLM(vocab_size, num_hiddens, mlm_in_features)
        #中间隐藏层的线性转换+激活函数
        self.hidden = nn.Sequential(nn.Linear(hid_in_features, num_hiddens), nn.Tanh())
        #预测出下一句
        self.nsp = NextSentencePred(nsp_in_features)
        
    def forward(self, tokens, seqments, valid_lens=None, pred_position=None):
        encoded_X = self.encoder(tokens, seqments, valid_lens)
        if pred_position is not None:
            mlm_Y_hat = self.mlm(encoded_X, pred_position)
        else:
            pred_position = None
            
        #0表示<cls>标记的索引
        nsp_Y_hat = self.nsp(self.hidden(encoded_X[:, 0, :]))
        return encoded_X, mlm_Y_hat, nsp_Y_hat

 4.知识点个人理解

 

标签:hiddens,BERT,nn,实现,代码,mlm,num,self,size
From: https://blog.csdn.net/Hiweir/article/details/142445118

相关文章