《动手学深度学习》-69BERT预训练实现

发布时间:2026/5/17 20:54:10

《动手学深度学习》-69BERT预训练实现 一、BERT1. 核心概述全称Bidirectional Encoder Representations from Transformers提出者Google 团队2018年发表核心贡献引入了深度双向架构确立了“预训练 (Pre-training) 微调 (Fine-tuning)”的新范式在发布时刷新了11项 NLP 任务的 SOTAState of the Art记录。核心思想通过大规模无标注语料进行预训练学习到包含丰富上下文信息的词向量表示Contextualized Embeddings再用少量的标注数据针对特定下游任务进行微调。2. 模型架构骨干网络Transformer 的Encoder编码器部分。双向性与早期单向语言模型如 GPT-1 从左到右生成不同BERT 基于 Self-Attention 机制在处理每一个词时都能同时看到其左侧和右侧的所有上下文信息。经典版本BERT-Base12层 (L12)隐藏层维度768 (H768)注意力头数12 (A12)总参数量约 1.1亿。BERT-Large24层 (L24)隐藏层维度1024 (H1024)注意力头数16 (A16)总参数量约 3.4亿。3. 输入表示 (Input Representation)BERT的输入是由三种 Embedding直接相加构成的Token Embeddings词向量使用 WordPiece 算法将单词切分为子词Subword如 playing - play ##ing以缓解 OOV未登录词问题。Segment Embeddings句子向量用于区分输入的两个句子例如句子A标记为0句子B标记为1。Position Embeddings位置向量与 Transformer 原生的正弦函数绝对位置编码不同BERT 使用的是可学习的绝对位置编码。最大支持长度通常为 512。关键的特殊标记 (Special Tokens)[CLS]永远放在序列的第一个位置。其最后一层的输出向量被用来代表整个句子的语义常用于句子级别的分类任务。[SEP]用于分隔两个句子或放在单句的句末。[MASK]在预训练的 MLM 任务中用于遮蔽真实的单词。4. 两大预训练任务 (Pre-training)BERT 能够在无监督数据上大放异彩归功于它设计的两个预训练任务任务一Masked Language Model (MLM, 掩码语言模型)目的迫使模型通过上下文来预测被遮蔽的词从而学习双向语境。做法随机选择输入序列中15%的 Token 作为目标进行预测。细节为了缓解预训练和微调时的输入不一致问题因为微调时不会出现 [MASK]在这被选中的15%的 Token 中80%的概率替换为 [MASK]例如my dog is hairy - my dog is[MASK]。10%的概率替换为随机的一个词例如my dog is hairy - my dog is apple。10%的概率保持原词不变例如my dog is hairy - my dog is hairy但模型依然需要去预测它以验证其正确性。任务二Next Sentence Prediction (NSP, 下一句预测)目的让模型学习句子之间的逻辑关系对 QA问答和 NLI自然语言推理等任务有很大帮助。做法二分类任务。输入句子A和句子B判断B是否真的是A的下一句。50% 正样本B 确实在真实文本中紧跟在 A 后面IsNext。50% 负样本B 是从语料库中随机抽取的一个句子NotNext。5. 微调机制 (Fine-tuning)BERT 在处理下游任务时非常灵活只需要对输入和输出做少量修改并微调所有参数即可单句分类如情感分析直接取句首 [CLS] 标记对应的隐藏层向量接入一个全连接层进行分类。句子对分类如语义相似度/自然语言推理句子 A 和 B 通过 [SEP] 拼接同样取 [CLS] 的输出进行分类。序列标注如命名实体识别 NER取每一个 Token 对应的最终隐藏层向量接入分类器或 CRF 层预测每个词的标签。阅读理解问答如 SQuAD输入问题和段落预测答案在段落中的“起始位置”和“结束位置”。二、代码import torch from torch import nn import test_68transformer def get_tokens_and_segments(tokens_a,tokens_bNone): tokens[cls]tokens_a[sep] segments[0]*(len(tokens_a)2) if tokens_b is not None: tokenstokens_b[sep] segments[1]*(len(tokens_b)1) return tokens,segments class BERTEncoder(nn.Module): def __init__(self,vocab_size,num_hiddens,norm_shape,ffn_num_input,ffn_num_hiddens,num_heads,num_layers,dropout, max_len1000,key_size768,query_size768,value_size768,**kwargs): super(BERTEncoder, self).__init__(**kwargs) self.token_embeddingnn.Embedding(vocab_size,num_hiddens)#有多少需要编号的词*每个词多少维度 self.segment_embeddingnn.Embedding(2,num_hiddens)#两类01*每个label几个维度 self.blksnn.Sequential() for i in range(num_layers): self.blks.add_module(f{i},test_68transformer.EncoderBlock(key_size,query_size,value_size,num_hiddens,norm_shape, ffn_num_input,ffn_num_hiddens,num_heads,dropout,True)) self.position_embeddingnn.Parameter(torch.randn(1,max_len,num_hiddens))#1方便广播与batch_size对应max_len最长长度num_hiddens def forward(self,tokens,segments,valid_len): Xself.token_embedding(tokens)self.segment_embedding(segments) XXself.position_embedding.data[:,:X.shape[1],:] for blk in self.blks: Xblk(X,valid_len) return X # print(encoder_X.shape) class MaskLM(nn.Module): #从X中挑出mask然后对其做预测,即BERT的MLMMasked Language Model掩蔽语言模型 def __init__(self,vocab_size,num_hiddens,num_inputs768,**kwargs): super(MaskLM, self).__init__(**kwargs) self.mlpnn.Sequential( nn.Linear(num_inputs,num_hiddens), nn.ReLU(), nn.LayerNorm(num_hiddens), nn.Linear(num_hiddens,vocab_size)) def forward(self,X,pred_position): #X的形状(batch_size,seq_len,num_hiddens) #pred_position的形状(batch_size,num_pred_position) #输出的形状(batch_size,num_pred_position,vocab_size) num_pred_positionpred_position.shape[1] pred_positionpred_position.reshape(-1) batch_sizeX.shape[0] batch_idxtorch.arange(0,batch_size) batch_idxtorch.repeat_interleave(batch_idx,num_pred_position) masked_XX[batch_idx,pred_position] masked_Xmasked_X.reshape((batch_size,num_pred_position,-1)) mlm_Y_hatself.mlp(masked_X) return mlm_Y_hat vocab_size,num_hiddens,ffn_num_hiddens,num_heads10000,768,1024,4 norm_shape,ffn_num_input,num_layers,dropout[768],768,2,0.2 encoderBERTEncoder(vocab_size,num_hiddens,norm_shape,ffn_num_input,ffn_num_hiddens,num_heads,num_layers,dropout) tokenstorch.randint(0,vocab_size,(2,8)) segmentstorch.tensor([[0,0,0,0,1,1,1,1],[0,0,0,0,1,1,1,1]]) encoder_Xencoder(tokens,segments,None) mlmMaskLM(vocab_size,num_hiddens) mlm_positionstorch.tensor([[1,5,2],[6,1,5]]) mlm_Y_hatmlm(encoder_X,mlm_positions) # print(mlm_Y_hat.shape) mlm_Ytorch.tensor([[7,8,9],[10,20,30]]) lossnn.CrossEntropyLoss(reductionnone) mlm_lloss(mlm_Y_hat.reshape((-1,vocab_size)),mlm_Y.reshape(-1)) # print(mlm_l.shape) #预测下一个句子 class NextSentencePred(nn.Module): def __init__(self,num_inputs,**kwargs): super(NextSentencePred, self).__init__(**kwargs) self.outputnn.Linear(num_inputs,2) def forward(self, X): return self.output(X) encoder_Xtorch.flatten(encoder_X,start_dim1)#X变成(batch_size,seq_len*num_hiddens) nspNextSentencePred(encoder_X.shape[-1])#只对最后一层数值全连接操作 nsp_Y_hatnsp(encoder_X)#输出维度变为(batch_size,2) # print(nsp_Y_hat.shape) nsp_ytorch.tensor([0,1]) nsp_lloss(nsp_Y_hat,nsp_y) # print(nsp_l.shape) 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_len1000,key_size768,query_size768,value_size768,hid_in_feature768, mlm_in_feature768,nsp_in_feature768,**kwargs): super(BERTModel, self).__init__() self.encoderBERTEncoder(vocab_size,num_hiddens,norm_shape,ffn_num_input,ffn_num_hiddens,num_heads,num_layers,dropout, max_lenmax_len,key_sizekey_size,query_sizequery_size,value_sizevalue_size) self.hiddennn.Sequential(nn.Linear(hid_in_feature,num_hiddens),nn.Tanh()) self.mlmMaskLM(vocab_size,num_hiddens,mlm_in_feature) self.nspNextSentencePred(nsp_in_feature) def forward(self,tokens,segments,valid_lenNone,pred_positionNone): encoded_Xself.encoder(tokens,segments,valid_len) if pred_position is not None: mlm_Y_hatself.mlm(encoded_X,pred_position) else: mlm_Y_hatNone nsp_Y_hatself.nsp(self.hidden(encoded_X[:,0,:])) return encoded_X,mlm_Y_hat,nsp_Y_hat

相关新闻