从零开始训练大模型—以RoBERTa为例


0 前言

本文讲述了如何从零开始训练一个大模型,这个从零开始值是指从源码层面自己处理数据、搭建模型。

1 数据预处理

  • 加载数据

  • 对过长的文本进行切分,设置max_len=128,也就是先按照标点符号把长句子切分为短句子,然后将短句子进行组合,但是不能超过最大长度,超过的部分组合成新的句子,所有数据处理完得到 Dataset。
    在这里插入图片描述

  • 将Dataset传给DataLoader,在DataLoader里面的collate_fn进行操作,得到模型的输入。
    collate_fn函数:
    1)对输入的文本进行编码:input_ids = tokenizer.encode(list(text))
    2)计算需要掩码的token数量:n_pred = min(max_pred, max(1, int(len(input_ids) * 0.15)))
    3)对input_ids进行处理得到待掩码的token:cand_maked_pos = [i for i, token in enumerate(input_ids) if token != word2idx[‘[CLS]’] and token != word2idx[‘[SEP]’]]
    4)打乱待掩码的token:shuffle(cand_maked_pos)
    5)进行掩码:

    for pos in cand_maked_pos[:n_pred]:
    	masked_pos.append(pos) 
    	masked_tokens.append(input_ids[pos])
    	if random() < 0.8: 
    		input_ids[pos] = word2idx['[MASK]']
    	elif random() > 0.9:
    		index = randint(0, vocab_size - 1) 
    		while index == 0 or index == 101 or index == 102 or index == 103: 
    			index = randint(0, vocab_size - 1)
    	input_ids[pos] = index
    

    6)对input_ids进行补零操作:
    n_pad = maxlen - len(input_ids)
    input_ids.extend([0] * n_pad)
    7)对掩码的token和对应的位置向量进行补零操作:
    n_pad = max_pred - n_pred
    masked_tokens.extend([0] * n_pad)
    masked_pos.extend([0] * n_pad)
    8)将得到的模型输入input_ids、masked_tokens、 masked_pos转换为torch tensor格式:
    torch.tensor(input_ids, dtype=torch.long)
    torch.tensor(masked_tokens, dtype=torch.long)
    torch.tensor(masked_pos, dtype=torch.long)

2 模型构建

2.1 模型介绍

  1. RoBERTa是基于BERT进行改进得到的, RoBERTa 相较于 BERT 最大的改进有三点:
    1)动态 Masking: BERT的masking是在预处理时进行的,导致这种Masking是静态的,每个epoch的masking结果一致。而RoBERTa中使用Dynamic Masking,只是在序列送入模型中的时候才去进行动态的masking,这样在更大的数据集上或者更多步数的训练上会表现更好
    2)取消 NSP (Next Sentence predict) 任务:为了探索NSP训练策略对模型结果的影响,论文中设置了4种训练方式进行对比,最后发现没有NSP任务模型的训练结果会更好,下游任务的效果也会更好
    3)扩大 batch_size:论文中通过实验,证明了更大的batch_size可以得到更好的结果
  2. RoBERTa的结构为:Embedding层;EncoderLayer层;全连接层
    1)Embedding层
    class Embedding(nn.Module):
    	def __init__(self):
    		super(Embedding, self).__init__()
    		self.tok_embed = nn.Embedding(vocab_size, hidden_size)
    		self.pos_embed = nn.Embedding(maxlen, hidden_size)
    		self.norm = nn.LayerNorm(hidden_size)
    		self.dropout = nn.Dropout(hidden_dropout_prob)
    		
    	def forward(self, input_ids):
    		seq_len = input_ids.size(1)
    		pos = torch.arange(seq_len, dtype=torch.long)
    		pos = pos.unsqueeze(0).expand_as(input_ids) # [seq_len] -> [batch_size, seq_len] 
    		embedding = self.tok_embed(input_ids) + self.pos_embed(pos) # [batch_size, seq_len, hidden_size] 
    		embedding = self.norm(embedding)
    		embedding = self.dropout(embedding)
    		return embedding
    
    2)EncoderLayer层
    EncoderLayer层的主体其实就是Transformer中的编码层,核心内容为自注意力机制,代码如下图所示。这部分内容较为复杂,如果想进一步了解可以参考笔者之前的一篇文章“基于模型结构与模型源码两个层面理解Transformer”。
    def forward(self, Q, K, V, attn_mask):
    	scores = torch.matmul(Q, K.transpose(-1, -2)) / np.sqrt(d_k)
    	scores.masked_fill_(attn_mask, -1e9)
    	attn = nn.Softmax(dim=-1)(scores)
    	context = torch.matmul(attn, V)
    	return context
    
    3)全连接层
    self.linear = nn.Linear(hidden_size, hidden_size)
    self.activ2 = gelu
    embed_weight = self.embedding.tok_embed.weight
    self.fc2 = nn.Linear(hidden_size, vocab_size)
    self.fc2.weight = embed_weight
    
    masked_pos = masked_pos[:, :, None].expand(-1, -1, d_model) # [batch_size, max_pred, hidden_size]
    h_masked = self.activ2(self.linear(h_masked)) # [batch_size, max_pred, hidden_size]
    logits_lm = self.fc2(h_masked) # [batch_size, max_pred, vocab_size]
    return logits_lm
    
  3. RoBERTa模型
    class RoBERTa(nn.Module):
    	def __init__(self):
    		super(RoBERTa, self).__init__()
    		self.embedding = Embedding()
    		self.layers = nn.ModuleList([EncoderLayer() for _ in range(n_layers)])
    		self.linear = nn.Linear(hidden_size, hidden_size)
    		self.activ2 = gelu
    		embed_weight = self.embedding.tok_embed.weight
    		self.fc2 = nn.Linear(hidden_size, vocab_size)
    		self.fc2.weight = embed_weight
    	def forward(self, input_ids, masked_pos):
    		output = self.embedding(input_ids) # [batch_size, seq_len, hidden_size]
            enc_self_attn_mask = get_attn_pad_mask(input_ids, input_ids) # [batch_size, maxlen, maxlen]
            for layer in self.layers:
            	output = layer(output, enc_self_attn_mask) # [batch_size, seq_len, hidden_size]
            masked_pos = masked_pos[:, :, None].expand(-1, -1, hidden_size)  # [batch_size, max_pred, hidden_size]
            h_masked = torch.gather(output, 1, masked_pos) # [batch_size, max_pred, hidden_size]
            h_masked = self.activ2(self.linear(h_masked)) # [batch_size, max_pred, hidden_size]
            logits_lm = self.fc2(h_masked) # [batch_size, max_pred, vocab_size]
            return logits_lm
    

3 模型训练及保存

model = RoBERTa()
criterion = nn.CrossEntropyLoss(ignore_index=0) 
optimizer = optim.Adam(model.parameters(), lr=0.0001)

for epoch in range(epochs):
	loss = 0
	pbar = tqdm.tqdm(loader, desc='Train', nrows=200, ncols=100)
	for input_ids, masked_tokens, masked_pos in pbar:
		logits_lm = model(input_ids, masked_pos) # [batch_size, max_pred, vocab_size]
		loss_lm = criterion(logits_lm.view(-1, vocab_size), masked_tokens.view(-1)) 
		loss += loss_lm
		optimizer.zero_grad()
		loss_lm.backward()
		optimizer.step()
	print('Epoch:', '%04d' % (epoch + 1), 'loss =', '{:.6f}'.format(loss))
	
    output_path = './outputs/'
    save_path = os.path.join(output_path, 'checkpoint_RoBERTa-' + str(epoch+1))
    torch.save(model, save_path)

4 模型测试

下图为模型测试和测试结果,从预测结果可以看出,预测的两段文本,分别掩码了13和2个token,但是两段文本都只预测对了一个token。这是因为当时只用了少量的训练数据,而且模型未得到充分训练。
模型推理过程:

model=torch.load(save_path) 
input_ids = input_ids.numpy().tolist()
masked_tokens = masked_tokens.numpy().tolist()
masked_pos = masked_pos.numpy().tolist()
print([idx2word[w] for w in input_ids[0] if idx2word[w] !=[PAD]])

logits_lm = model(torch.LongTensor([input_ids[0]]),torch.LongTensor([masked_pos[0]]))# logits_lm :[batch_size, max_pred, vocab_size]
logits_lm = logits_lm.data.max(2)[1][0].data.numpy()  # 预测出的掩码位置的token,长度为max_pred
print(‘masked tokens list :,[pos for pos in masked_tokens[0] if pos != 0]) print('predict masked tokens list : ',[pos for pos in logits_lm if pos != 0])

测试结果1:
在这里插入图片描述
测试结果2:
在这里插入图片描述

5 常见大模型简述

5.1 ALBERT模型简述

ALBERT 的结构和 BERT 基本一样,采用了 Transformer 以及 GELU 激活函数。具体的创新部分有三个:

  • embedding 层参数因式分解
  • 跨层参数共享
  • 将 NSP 任务改为 SOP 任务
  1. embedding 层参数因式分解
    原始的 BERT 模型以及各种依据 Transformer 的预训连语言模型都有一个共同特点,即 E=H,其中 E 指的是 Embedding Dimension,H 指的是 Hidden Dimension。这就会导致一个问题,当提升 Hidden Dimension 时,Embedding Dimension 也需要提升,最终会导致参数量呈平方级的增加。所以 ALBERT 的作者将 E 和 H 进行解绑,具体的操作就是在 Embedding 后面加入一个矩阵进行维度变换。E 的维度是不变的,如果 H 增大了,我们只需要在 E 后面进行一个升维操作即可。
    举个例子:
    V * H = 30000 * 768 = 23 040 000
    V * E + E * H = 30000 * 256 + 256 * 768 = 7 876 608
    当V为30000,H为768,E为256时,参数量从2300万降低到780万
  2. 跨层参数共享
    传统 Transformer 的每一层参数都是独立的,包括各层的 self-attention、全连接。这样就导致层数增加时,参数量也会明显上升。之前有工作试过单独将 self-attention 或者全连接层进行共享,都取得了一些效果。ALBERT 作者尝试将所有层的参数进行共享,相当于只学习第一层的参数,并在剩下的所有层中重用该层的参数,而不是每个层都学习不同的参数。
    作者通过实验发现了使用参数共享可以有效的提升模型稳定性,实验结果如下图:
    在这里插入图片描述
  3. 将NSP 任务改为 SOP 任务
    BERT 引入了一个叫做下一个句子预测的二分类问题。这是专门为提高使用句子对,如 “自然语言推理” 的下游任务的性能而创建的。但是在 RoBERTa 这样的论文中已经阐明了 NSP 的无效性,并且发现它对下游任务的影响是不可靠的。
    因此,ALBERT 提出了另一个任务 —— 句子顺序预测,关键思想是(1)从同一个文档中取两个连续的句子作为一个正样本;(2)交换这两个句子的顺序,并使用它作为一个负样本。SOP 提高了下游多种任务(SQUAD,MNLI,SST-2,RACE)的表现。
    在这里插入图片描述

5.2 ELECTRA模型简述

ELECTRA最主要的贡献是提出了新的预训练任务和框架,把生成式的Masked language model(MLM)预训练任务改成了判别式的Replaced token detection(RTD)任务,判断当前token是否被语言模型替换过。
在这里插入图片描述
具体而言:首先按照一定的比例对于原始输入序列X-ORI进行随机MASK操作得到新序列X-MASK;其次将X-MASK作为生成器模型(Generator)的输入,该生成器模型用于对序列中那些被MASK操作的tokens生成新的token(此时生成器是面向所有词表而言),以此来产生新的序列X-Generator;之后将X-Generator作为判别器模型的输入(Discriminator),该判别器模型用于判别序列中每一个token是否是原始token(和X-ORI进行对比而言)。

ELECTRA和BERT的区别:
在这里插入图片描述

5.3 ERNIE模型简述

ERNIE相比于BERT,做出了如下改进:

  • mask策略。BERT只使用了字级别的随机masking,但是ERNIE使用了字、短语、实体三个级别的masking,旨在使模型学习到更多高级的语义
  • 添加更多优质中文语料。加入了百度百科、百度新闻、百度贴吧等中文语料,使得在中文NLP任务上效果更好
  • 对话语言模型。对Dialog的角色,进行了Dialog embedding,从而加强模型在Dialog上的效果
  1. mask策略,使用了字、短语、实体三个级别的masking,模型可以学习到更大语义单元的知识
    在这里插入图片描述
  2. 添加更多优质中文语料。加入了百度百科、百度新闻、百度贴吧等中文语料,使得在中文NLP任务上效果更好。
  3. 对话语言模型,对Dialog的角色,进行了Dialog embedding,从而加强模型在Dialog上的效果。
    在这里插入图片描述
    DLM任务可帮助ERNIE学习对话中的隐式关系,这也增强了模型学习语义表示的能力。DLM任务的模型体系结构与MLM任务的模型体系结构兼容,因此可以通过MLM任务对其进行预训练。

总结

本文是对过去看过内容的一个复盘,只涉及到训练大模型的主要部分,部分细节无法逐一展现,需要源码的可以私我,如果疑问欢迎评论区交流。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
RoBERTa中文预训练模型 概述 中文预训练RoBERTa模型 RoBERTa是BERT的改进版,通过改进训练任务和数据生成方式、训练更久、使用更大批次、使用更多数据等获得了State of The Art的效果;可以用Bert直接加载。 本项目是用TensorFlow实现了在大规模中文上RoBERTa的预训练,也会提供PyTorch的预训练模型和加载方式。 中文预训练RoBERTa模型-下载 6层RoBERTa体验版 RoBERTa-zh-Layer6: Google Drive 或 百度网盘,TensorFlow版本,Bert 直接加载, 大小为200M 推荐 RoBERTa-zh-Large 通过验证 RoBERTa-zh-Large: Google Drive 或 百度网盘 ,TensorFlow版本,Bert 直接加载 RoBERTa-zh-Large: Google Drive 或 百度网盘 ,PyTorch版本,Bert的PyTorch版直接加载 RoBERTa 24/12层版训练数据:30G原始文本,近3亿个句子,100亿个中文字(token),产生了2.5亿个训练数据(instance);覆盖新闻、社区问答、多个百科数据等; 本项目与中文预训练24层XLNet模型 XLNet_zh项目,使用相同的训练数据。 RoBERTa_zh_L12: Google Drive 或 百度网盘 TensorFlow版本,Bert 直接加载 RoBERTa_zh_L12: Google Drive 或百度网盘 PyTorch版本,Bert的PyTorch版直接加载 Roberta_l24_zh_base TensorFlow版本,Bert 直接加载 24层base版训练数据:10G文本,包含新闻、社区问答、多个百科数据等 什么是RoBERTa: 一种强大的用于预训练自然语言处理(NLP)系统的优化方法,改进了Transformers或BERT的双向编码器表示形式,这是Google在2018年发布的自监督方法。 RoBERTa在广泛使用的NLP基准通用语言理解评估(GLUE)上产生最先进的结果。 该模型在MNLI,QNLI,RTE,STS-B和RACE任务上提供了最先进的性能,并在GLUE基准上提供了可观的性能改进。 RoBERTa得分88.5,在GLUE排行榜上排名第一,与之前的XLNet-Large的表现相当。 效果测试与对比 Performance 互联网新闻情感分析:CCF-Sentiment-Analysis 模型 线上F1 BERT 80.3 Bert-wwm-ext 80.5 XLNet 79.6 Roberta-mid 80.5 Roberta-large (max_seq_length=512, split_num=1) 81.25 注:数据来源于guoday的开源项目;数据集和任务介绍见:CCF互联网新闻情感分析 自然语言推断:XNLI 模型 开发集 测试集 BERT 77.8 (77.4) 77.8 (77.5) ERNIE 79.7 (79.4) 78.6 (78.2) BERT-wwm 79.0 (78.4) 78.2 (78.0) BERT-wwm-ext 79.4 (78.6) 78.7 (78.3) XLNet 79.2 78.7 RoBERTa-zh-base 79.8 78.8 RoBERTa-zh-Large 80.2 (80.0) 79.9 (79.5) 注:RoBERTa_l24_zh,只跑了两次,Performance可能还会提升; BERT-wwm-ext来自于这里;XLNet来自于这里; RoBERTa-zh-base,指12层RoBERTa中文模型 问题匹配语任务:LCQMC(Sentence Pair Matching) 模型 开发集(Dev) 测试集(Test) BERT 89.4(88.4) 86.9(86.4) ERNIE 89.8 (89.6) 87.2 (87.0) BERT-wwm 89.4 (89.2) 87.0 (86.8) BERT-wwm-ext - - RoBERTa-zh-base 88.7 87.0 RoBERTa-zh-Large 89.9(89.6) 87.2(86.7) RoBERTa-zh-Large(20w_steps) 89.7 87.0 注:RoBERTa_l24_zh,只跑了两次,Performance可能还会提升。保持训练轮次和论文一致: 阅读理解测试 目前阅读理解类问题bert和roberta最优参数均为epoch2, batch=32, lr=3e-5, warmup=0.1 cmrc20
RoBERTa是一个基于BERT模型的改进版本,它在预训练方面进行了进一步的探索和改进。与BERT相比,RoBERTa模型结构上没有太多创新,但它改进了BERT的预训练策略。研究结果表明,原始的BERT可能存在训练不足的问题,没有充分学习到训练数据中的语言知识。因此,RoBERTa通过从头开始训练分词器、字节级字节对编码以及重新创建标记器等方式,对BERT进行了改进和优化。RoBERTa模型具有8000万个参数,并且可以应用于下游任务,如Masked Language Modeling(MLM)。\[1\]\[2\] 另外,还有一个名为KantaiBERT的预训练模型,它是一个相对较小的模型,具有6层、12个头和84095008个参数。尽管参数数量较少,但这个小型模型可以使预训练过程更加流畅,可以实时查看每个步骤的结果,而无需等待数小时。\[3\] #### 引用[.reference_title] - *1* *3* [【NLP】第4章 从头开始预训练 RoBERTa 模型](https://blog.csdn.net/sikh_0529/article/details/127034879)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [【原理】预训练模型之自然语言理解--RoBERTa](https://blog.csdn.net/m0_63642362/article/details/121261531)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值