这段代码的核心功能是基于 BERT 构建一个自回归语言模型(Autoregressive Language Model),用于文本生成任务。它的核心思路是通过训练模型 “预测下一个字符”,从而让模型学会生成连贯的文本。
#coding:utf8 import torch import torch.nn as nn import numpy as np import random import os from transformers import BertTokenizer, BertModel class LanguageModel(nn.Module): def __init__(self, hidden_size, vocab_size, pretrain_model_path): super(LanguageModel, self).__init__() self.bert = BertModel.from_pretrained(pretrain_model_path, return_dict=False, attn_implementation='eager') self.classify = nn.Linear(hidden_size, vocab_size) self.loss = nn.functional.cross_entropy #当输入真实标签,返回loss值;无真实标签,返回预测值 def forward(self, x, y=None): if y is not None: #训练时,构建一个下三角的mask矩阵,让上下文之间没有交互 mask = torch.tril(torch.ones((x.shape[0], x.shape[1], x.shape[1]))) if torch.cuda.is_available(): mask = mask.cuda() x, _ = self.bert(x, attention_mask=mask) y_pred = self.classify(x) #output shape:(batch_size, vocab_size) return self.loss(y_pred.view(-1, y_pred.shape[-1]), y.view(-1)) else: #预测时,可以不使用mask x, _ = self.bert(x) y_pred = self.classify(x) #output shape:(batch_size, vocab_size) return torch.softmax(y_pred, dim=-1) #加载语料 def load_corpus(path): corpus = "" with open(path, encoding="gbk") as f: for line in f: corpus += line.strip() return corpus #随机生成一个样本 #从文本中截取随机窗口,前n个字作为输入,最后一个字作为输出 def build_sample(tokenizer, window_size, corpus): start = random.randint(0, len(corpus) - 1 - window_size) end = start + window_size window = corpus[start:end] target = corpus[start + 1:end + 1] #输入输出错开一位 x = tokenizer.encode(window, add_special_tokens=False, padding='max_length', truncation=True, max_length=10) #将字转换成序号 y = tokenizer.encode(target, add_special_tokens=False, padding='max_length', truncation=True, max_length=10) return x, y #建立数据集 #sample_length 输入需要的样本数量。需要多少生成多少 #vocab 词表 #window_size 样本长度 #corpus 语料字符串 def build_dataset(sample_length, tokenizer, window_size, corpus): dataset_x = [] dataset_y = [] for i in range(sample_length): x, y = build_sample(tokenizer, window_size, corpus) dataset_x.append(x) dataset_y.append(y) return torch.LongTensor(dataset_x), torch.LongTensor(dataset_y) #建立模型 def build_model(vocab, char_dim, pretrain_model_path): model = LanguageModel(768, 21128, pretrain_model_path) return model #文本生成测试代码 def generate_sentence(openings, model, tokenizer, window_size): # reverse_vocab = dict((y, x) for x, y in vocab.items()) model.eval() with torch.no_grad(): pred_char = "" #生成了换行符,或生成文本超过30字则终止迭代 while pred_char != "\n" and len(openings) <= 30: openings += pred_char x = tokenizer.encode(openings, add_special_tokens=False) x = torch.LongTensor([x]) if torch.cuda.is_available(): x = x.cuda() y = model(x)[0][-1] index = sampling_strategy(y) pred_char = ''.join(tokenizer.decode(index)) return openings def sampling_strategy(prob_distribution): if random.random() > 0.1: strategy = "greedy" else: strategy = "sampling" if strategy == "greedy": return int(torch.argmax(prob_distribution)) elif strategy == "sampling": prob_distribution = prob_distribution.cpu().numpy() return np.random.choice(list(range(len(prob_distribution))), p=prob_distribution) def train(corpus_path, save_weight=True): epoch_num = 20 #训练轮数 batch_size = 128 #每次训练样本个数 train_sample = 10000 #每轮训练总共训练的样本总数 char_dim = 768 #每个字的维度 window_size = 10 #样本文本长度 vocab_size = 21128 #字表大小 learning_rate = 0.001 #学习率 pretrain_model_path = r'F:\Desktop\work_space\pretrain_models\bert-base-chinese' tokenizer = BertTokenizer.from_pretrained(pretrain_model_path) corpus = load_corpus(corpus_path) #加载语料 model = build_model(vocab_size, char_dim, pretrain_model_path) #建立模型 if torch.cuda.is_available(): model = model.cuda() optim = torch.optim.Adam(model.parameters(), lr=learning_rate) #建立优化器 print("文本词表模型加载完毕,开始训练") for epoch in range(epoch_num): model.train() watch_loss = [] for batch in range(int(train_sample / batch_size)): x, y = build_dataset(batch_size, tokenizer, window_size, corpus) #构建一组训练样本 if torch.cuda.is_available(): x, y = x.cuda(), y.cuda() optim.zero_grad() #梯度归零 loss = model(x, y) #计算loss loss.backward() #计算梯度 optim.step() #更新权重 watch_loss.append(loss.item()) print("=========\n第%d轮平均loss:%f" % (epoch + 1, np.mean(watch_loss))) print(generate_sentence("让他在半年之前,就不能做出", model, tokenizer, window_size)) print(generate_sentence("李慕站在山路上,深深的呼吸", model, tokenizer, window_size)) if not save_weight: return else: base_name = os.path.basename(corpus_path).replace("txt", "pth") model_path = os.path.join("model", base_name) torch.save(model.state_dict(), model_path) return if __name__ == "__main__": # build_vocab_from_corpus("corpus/all.txt") train(r"F:\python codes\week10\corpus.txt", False) 解读下代码 是干什么 这个思路跟gpt2啥关系
这段代码的核心功能是基于 BERT 构建一个自回归语言模型(Autoregressive Language Model),用于文本生成任务。它的核心思路是通过训练模型 “预测下一个字符”,从而让模型学会生成连贯的文本。
代码核心逻辑解读
-
模型结构
代码定义了LanguageModel类,本质是一个 “BERT + 线性层” 的组合:- 底层使用预训练的 BERT 模型(
BertModel)作为特征提取器,获取文本的上下文表示。 - 顶层接一个线性层(
nn.Linear),将 BERT 的输出映射到词汇表大小(vocab_size),用于预测下一个字符的概率。
- 底层使用预训练的 BERT 模型(
-
训练逻辑
训练的核心任务是 “给定前 n 个字符,预测第 n+1 个字符”,具体做法:- 从语料中截取窗口(
window_size)大小的文本,输入是[c1, c2, ..., cn],标签是[c2, c3, ..., cn+1](输入和标签错开一位)。 - 关键:训练时使用下三角掩码(
torch.tril),确保每个位置的字符只能 “看到” 它前面的字符(无法看到后面的字符),模拟 “只能根据历史预测未来” 的自回归过程。 - 损失函数使用交叉熵(
cross_entropy),优化模型对下一个字符的预测能力。
- 从语料中截取窗口(
-
文本生成逻辑
生成时采用 “自回归生成” 策略:- 从一个初始文本(
openings)开始,模型预测下一个最可能的字符。 - 将预测的字符追加到输入中,重复预测过程,直到生成换行符或达到长度限制(30 字)。
- 采样策略结合了贪婪搜索(90% 概率选概率最高的字符)和随机采样(10% 概率按概率分布随机选),平衡生成的确定性和多样性。
- 从一个初始文本(
与 GPT-2 的关系:思路一致,架构不同
这段代码的核心思路与 GPT-2 高度相似,但实现细节有差异,具体关系如下:
共同点(核心思路一致)
-
自回归语言模型
两者的核心目标都是 “通过预测下一个 token(字符 / 词)训练模型”,生成时都采用 “逐 token 生成,将生成结果作为新输入” 的自回归方式。 -
因果掩码(Causal Mask)
训练时都使用下三角掩码(GPT-2 称为 “因果掩码”),强制模型只能利用 “历史信息”(前面的 token)预测 “未来信息”(后面的 token),避免模型 “偷看” 后面的内容。 -
文本生成逻辑
生成时都基于已生成的文本,通过模型预测下一个 token 的概率分布,再通过采样策略(贪婪、随机等)选择 token,逐步扩展文本。
差异(架构与基础模型不同)
-
基础模型不同
- GPT-2 基于Transformer 解码器(仅包含自注意力和前馈网络,无编码器),天生适合自回归任务(解码器的掩码机制就是下三角的)。
- 这段代码基于BERT(Transformer 编码器),而 BERT 原本是为 “双向理解”(如完形填空)设计的(使用双向注意力)。代码通过手动添加下三角掩码,强行将 BERT 的双向注意力改为 “只能看前面” 的单向注意力,适配自回归任务。
-
预训练目标差异
- GPT-2 的预训练目标就是纯自回归的 “下一个 token 预测”,与下游生成任务目标一致。
- BERT 的预训练目标是 “掩码语言模型(MLM)+ 下一句预测(NSP)”,更侧重双向语义理解,与自回归生成目标不完全匹配。因此,这段代码需要在 BERT 基础上进一步微调 “下一个 token 预测” 任务。
总结
这段代码本质是 “用 BERT 改造出自回归语言模型”,核心思路(自回归、因果掩码、逐 token 生成)与 GPT-2 完全一致,都是为了实现文本生成。但由于基础模型(BERT 编码器 vs GPT-2 解码器)的差异,实现方式上需要对 BERT 做额外的掩码处理,以适配自回归逻辑。

浙公网安备 33010602011771号