当前位置: 首页 > news >正文

第六章、从transformer到nlp大模型:编码器-解码器模型 (Encoder-Decoder)

0 前言

transformer实际上催生了大语言的三种框架的大模型:

模型架构模型类型代表模型特点
纯编码器模型 (Encoder-Only)
只使用 Transformer 的编码器部分
双向自注意力【双向模型BERT双向的。像一个可以同时扫视整个句子的分析者。常用来解决理解任务(分类、提取)
纯解码器模型 (Decoder-Only)
只使用 Transformer 的解码器部分
掩码自注意力(单向)【自回归模型GPT, LLaMA单向的(通常是从左到右)。像一个只能从左往右读的阅读器。常用来解决生成任务(创作、对话)
编码器-解码器模型 (Encoder-Decoder)编码器双向注意力 + 解码器交叉注意力T5, BART序列到序列任务(如翻译、摘要)

其实最主要的区别就是前一章所说的,没有掩码的多头注意力机制和有掩码的多头注意力机制的区别。无掩码的时候多头注意力机制是双向的,也就是说每个单词实际上结合了上下文的信息,有掩码的时候是单向的,即每个单词只结合了前面的单词的信息,它看不到后面的单词。

接下来我们拆开Transformer来看每部分能解决什么问题。

1 编码器-解码器模型 (Encoder-Decoder)

上一章中我们详细介绍了transformer模型的框架,相信读者已经可以自己搭建一个model了,本章我们将详细解释如何利用transformer模型训练一个英文到中文的翻译器en-zh,并将整体代码和数据集放在github上,感兴趣的小伙伴可以clone一下走一下整个流程。

ps:求一个小星星~
https://github.com/Gaoxinyigithub/transformer

1.1 中英文数据集

我们有两个txt文件,文件中分别是英文句子,和中文句子。

1.2 数据预处理

首先先来看这部分的代码

src_file = 'data/en.txt' # 英文txt地址
trg_file = 'data/zh.txt' # 中文txt地址
src_lang = 'en_core_web_sm' 
# 'en_core_web_sm' 是spaCy库中的预训练的英语语言模型
# 具体解释放在下面
trg_lang = 'zh_core_web_sm'
# 'zh_core_web_sm' 是spaCy库中的预训练的中文语言模型
max_strlen = 80
batchsize = 1500
src_data, trg_data = read_data(src_file, trg_file)
EN_TEXT, FR_TEXT = create_fields(src_lang, trg_lang)
train_iter, src_pad, trg_pad = create_dataset(src_data, trg_data, EN_TEXT, FR_TEXT, max_strlen, batchsize)

en_core_web_smzh_core_web_sm模型可以通过下面的连接搜索下载,注以版本号的对应关系,选择合适的下载即可。
https://github.com/explosion/spacy-models/releases

  • spaCy 是一个流行的开源自然语言处理(NLP)库,用于处理和分析文本数据。它提供了:

    • 分词(Tokenization)
    • 词性标注(Part-of-speech tagging)
    • 命名实体识别(Named Entity Recognition)
    • 依存句法分析(Dependency parsing)
    • 文本分类等功能
  • en_core_web_sm 是一个预训练的英语语言模型名称,由三部分组成:

    • en:表示英语(English)
    • core:表示核心功能(包含基本的NLP功能)
    • web:表示模型是在网络文本上训练的
    • sm:表示小型(small)版本
模型大小包含内容用途
en_core_web_sm~12MB词汇、词向量、语法、实体快速处理,资源有限环境
en_core_web_md~40MB小型版+词向量平衡性能与资源
en_core_web_lg~560MB中型版+更大的词向量最高准确性,资源充足

中文模型也是类似的

Token分词

接下来我们来看一下spaCy库的用法,还是以代码案例来看

import spacy
import re
from torchtext.legacy import data# 设置要使用的模型
src_lang = 'en_core_web_sm'
# 加载模型
nlp = spacy.load(src_lang)
# 使用模型处理文本
doc = nlp("This is a sample English sentence.")
# 访问处理结果
for token in doc:print(token.text, token.pos_, token.dep_)

下图为上面这段代码的输出结果,实际上就是对句子进行分解,分解的标准就是我们常说的token,并且标明了词性。
在这里插入图片描述
接下来再来看中文

import spacy
import re
from torchtext.legacy import data# 设置要使用的模型
src_lang = 'zh_core_web_sm'
# 加载模型
nlp = spacy.load(src_lang)
# 使用模型处理文本
doc = nlp("今天是周一,我又要上班了。")
# 访问处理结果
for token in doc:print(token.text, token.pos_, token.dep_)

结果如下图所示,实际上每个token是文本的一个最小的有意义的单位。
在这里插入图片描述

对大量分词做处理形成一个字典

想像一下最早的活字印刷术,我们先要印刷一段文本,首先需要刻字,再按照文稿,将一个个泥活字捡出来,排在一块带有框的铁板上。在排好的字版上洒上松脂、蜡等粘合剂,加热铁板使其熔化,然后用一块平板将字面压平,冷却后字模就固定住了,成为一块完整的印版。

有没有觉得这个过程很熟悉,nlp 某种程度上像是一个电子板的活字印刷,为什么这么说?活字印刷本质上是人来一个个选块,那nlp呢?聪明的你一定想到了,实际上就是根据概率哪个token的概率高就选哪个,是不是很合理~

那么这里的字典实际上就相当与大堆大堆的泥活字。

接下来我们来利用data.TabularDataset(path, format, fields)函数来生成字典

data.TabularDataset(path, format, fields)
'''
path(str):数据文件的地址
format(str):数据文件的格式,可选【CSV,TSV,JSON】格式
fields((list(tuple(str,Field)))or dict[str: tuple(str, Field)):如果使用list,格式必须为 CSV 或 TSV,并且列表的值应为(name, field)的元组。
'''

接下来再来看一下field是什么,Field 类是深度学习框架(如 PyTorch)中用于定义如何预处理文本数据的一个“说明书”。将原始的人类可读的文本(比如一句话),转换成一个模型可读的、数值化的张量(Tensor)。

class Field(RawField):"""定义一种数据类型及将其转换为张量的相关指令。Field类模拟了常见的文本处理数据类型,这些类型可以用张量表示。它包含一个Vocab(词表)对象,该对象定义了字段元素的可能取值集合及其对应的数值表示。Field对象还包含其他与数据数值化方式相关的参数,例如分词方法以及应生成的张量类型。如果一个Field在数据集的两列之间共享(例如,QA数据集中的问题和答案),那么它们将共享一个词表。属性:sequential: 该数据类型是否代表序列数据。如果为False,则不进行分词。默认值: True。use_vocab: 是否使用Vocab对象。如果为False,则该字段中的数据应已完成数值化。默认值: True。init_token: 一个将被预加到使用此字段的每个样本开头的标记(token),如果不需要起始标记则为None。默认值: None。eos_token: 一个将被追加到使用此字段的每个样本末尾的标记(EOS,句子结束标记),如果不需要结束标记则为None。默认值: None。fix_length: 使用此字段的所有样本将被填充到的固定长度,如果为None则表示序列长度可变。默认值: None。dtype: 代表此类数据批量样本的torch.dtype类型。默认值: torch.long。preprocessing: 一个在分词之后、数值化之前应用于样本的预处理流水线(Pipeline)。许多数据集会使用自定义的预处理器替换此属性。默认值: None。postprocessing: 一个在数值化之后、数值被转换为张量之前应用于样本的后处理流水线(Pipeline)。该流水线函数将批量数据作为列表接收,同时接收该字段的Vocab。默认值: None。lower: 是否将该字段中的文本转换为小写。默认值: False。tokenize: 用于将此字段中的字符串分词为序列样本的函数。如果为"spacy",则使用SpaCy分词器。如果传入不可序列化的函数,则该字段将无法被序列化。默认值: string.split (使用字符串的split方法)。tokenizer_language: 要构建的分词器的语言。目前各种语言仅支持SpaCy。include_lengths: 是返回一个填充后的迷你批次和一个包含各样本长度的元组,还是仅返回填充后的迷你批次。默认值: False。batch_first: 是否生成批次维度在第一位的张量。默认值: False。pad_token: 用作填充(padding)的字符串标记。默认值: "<pad>"。unk_token: 用于表示超出词表词汇(OOV)的字符串标记。默认值: "<unk>"。pad_first: 在序列的开头进行填充。默认值: False。truncate_first: 在序列的开头进行截断。默认值: False。stop_words: 在预处理步骤中要丢弃的标记(停用词)。默认值: None。is_target: 此字段是否为目标变量(target variable)。会影响对批次的迭代。默认值: False。"""

具体生成字典代码

# 设置要使用的模型
src_lang = 'en_core_web_sm'
# 加载模型
nlp = spacy.load(src_lang)
# 首先我们将上面分词的代码写成一个函数分词器
def tokenizer(sentence):# 使用模型处理文本doc = nlp(sentence)tokens=[]# 访问处理结果for token in doc:tokens.append(token.text)return tokens
# 利用Feild定义如何分词
# lower=True:全部转成小写字符
# tokenize=tokenizer:加载分词器
feild = data.Field(lower=True, tokenize=tokenizer)
# list[(name, field)]
data_fields=[('en',feild )]
# 对en_1.csv文件进行分层(该csv文件具体内容在下面的图中展示)
en=data.TabularDataset('./en_1.csv', format='csv', fields=data_fields)
feild.build_vocab(en)
# 查看结果(也放在下面的图中)
feild.vocab.stoi
print(feild.vocab.stoi.values())
print(feild.vocab.stoi.keys())

en_1.csv内容如下
在这里插入图片描述
对应生成的字典,可以看到How已经从大写变成了小写how,同时多个相同的词也变成了单个。
在这里插入图片描述最后就是对数据做批处理,此处省略该部分的叙述,有机会给补上。

1.3 模型训练

模型训练实际上包含以下几个步骤:

  • 模型参数设定
  • 模型类创建
  • 模型设置为训练模式
  • 获取目标,模型推理,计算损失迭代参数
'''模型训练'''
# 模型参数定义
d_model = 512
heads = 8
N = 6
dropout = 0.1
src_vocab = len(EN_TEXT.vocab) # 英文的数据集中的token的个数
trg_vocab = len(ZH_TEXT.vocab) # 中文的数据集中的token的个数
# device = 'cuda' if torch.cuda.is_available() else 'cpu'
device = 'cpu' # 由于torchtext的版本需要的比较低,而我的cuda版本比较高,所以torch无法使用cuda如果你可以可以将这一行注释掉,并取消上一行的注释
model = Transformer(src_vocab, trg_vocab, d_model, N, heads, dropout,device=device)
optim = torch.optim.Adam(model.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9)for p in model.parameters():if p.dim() > 1:nn.init.xavier_uniform_(p)# 模型训练
def train_model(epochs, print_every=10):model.train() # 将模型设置为训练模式start = time.time() # 记录训练开始时间temp = start # 临时时间变量,用于计算间隔时间total_loss = 0 #累积损失for epoch in range(epochs): # 外层循环:遍历所有训练轮次for i, batch in enumerate(train_iter): # 内层循环:遍历训练数据中的所有批次src = batch.src.transpose(0, 1) # 转置源语言序列维度trg = batch.trg.transpose(0, 1) # 转置目标语言序列维度trg_input = trg[:, :-1] # 目标序列输入(去掉最后一个token)# 记录目标,方便后续求loss functiontargets = trg[:, 1:].contiguous().view(-1) # 目标序列标签(去掉第一个token)并展平# 使用掩码代码创建函数来制作掩码src_mask, trg_mask = create_masks(src, trg_input, src_pad, trg_pad)preds = model(src, trg_input, src_mask, trg_mask) # 向前传播预测结果optim.zero_grad() # 清零梯度loss = F.cross_entropy(preds.view(-1, preds.size(-1)),targets, ignore_index=trg_pad) # 计算交叉熵损失loss.backward() # 反向传播计算梯度optim.step() # 跟新模型参数total_loss += loss.item() # 累积损失if (i + 1) % print_every == 0: # 每隔一定迭代次数打印进度loss_avg = total_loss / print_everyprint("time = %dm, epoch %d, iter = %d, loss = %.3f, %ds per %d iters" %((time.time() - start) // 60, epoch + 1, i + 1, loss_avg,time.time() - temp, print_every))total_loss = 0temp = time.time()# 训练结束后保存最终模型final_checkpoint = {'model_state_dict': model.state_dict(),'optimizer_state_dict': optim.state_dict(),'epoch': epochs,'ZH_TEXT': ZH_TEXT,'EN_TEXT': EN_TEXT,'src_pad':src_pad,'trg_pad':trg_pad}torch.save(final_checkpoint, 'model_final.pth')print("最终模型已保存为: model_final.pth")train_model(200)

1.4 模型推理

  • 读取训练好的模型及其参数
  • 设置模型为推理模式
  • 基于编码器编码后,基于解码器依次生成token。
# 加载保存的检查点
checkpoint_path = 'transformer/model_final.pth'
checkpoint = torch.load(checkpoint_path, map_location=torch.device('cpu'))  # 使用CPU加载,或指定GPU# 恢复词汇表
ZH_TEXT = checkpoint['ZH_TEXT']
EN_TEXT = checkpoint['EN_TEXT']
# 恢复部分参数
src_pad=checkpoint['src_pad']
trg_pad=checkpoint['trg_pad']
# 模型参数定义
d_model = 512
heads = 8
N = 6
dropout = 0.1
src_vocab = len(EN_TEXT.vocab) # 英文的数据集中的token的个数
trg_vocab = len(ZH_TEXT.vocab) # 中文的数据集中的token的个数
# device = 'cuda' if torch.cuda.is_available() else 'cpu'
device = 'cpu' # 由于torchtext的版本需要的比较低,而我的cuda版本比较高,所以torch无法使用cuda如果你可以可以将这一行注释掉,并取消上一行的注释
model = Transformer(src_vocab, trg_vocab, d_model, N, heads, dropout,device=device)# 恢复模型参数
model.load_state_dict(checkpoint['model_state_dict'])
# 恢复优化器状态 继续训练的时候才用的上
# optim = torch.optim.Adam(model.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9)
# optim.load_state_dict(checkpoint['optimizer_state_dict'])
# 获取训练轮次信息
trained_epochs = checkpoint['epoch']print(f"模型已从 {checkpoint_path} 加载")
print(f"模型已训练 {trained_epochs} 个轮次")'''
基于编码器-解码器架构的神经机器翻译模型的推理过程,用于将源语言句子翻译成目标语言。
在该代码中想要确定模型的输入和输出
'''
def translate(src, max_len=80, custom_string=False):"""用于推理:param src: 输入源,可以是与处理好的张量或原始字符串:param max_len: 生成翻译的最大长度限制:param custom_string: 标志位,只是输入是否为原始字符串:return:"""model.eval() # 设置为推理模式if custom_string == True: # 输入是否为原始字符src = tokenize_en(src, EN_TEXT) # 从Let me see. 变成向量 [89,21,95,2]src = torch.LongTensor(src) # 从向量 [89,21,95,2] 变成torch张量 tensor([89, 21, 95,  2])src_mask = (src != src_pad).unsqueeze(-2) # 张量中都不是1,新的张量tensor([[True, True, True, True]])e_outputs = model.encoder(src.unsqueeze(0), src_mask) # 计算编码器的输出# 4个数分别展开成512的向量,而且实际上已经是融合上下文信息的向量结果。outputs = torch.zeros(max_len).type_as(src.data) # 这里实际上相当于初始化一个输出outputs[0] = torch.LongTensor([ZH_TEXT.vocab.stoi['<sos>']])# ZH_TEXT.vocab.stoi是一个字典,是字符和数字的对应为了形成向量# <unk>:未知单词(out-of-vocabulary words)# <pad>:填充标记(用于使序列长度一致)# <sos>:序列开始标记 <eos>:序列结束标记for i in range(1, max_len): # 循环从1开始到max_len-1,i表示当前已生成的序列长度trg_mask = np.triu(np.ones((1, i, i)).astype('uint8'))trg_mask = Variable(torch.from_numpy(trg_mask) == 0)out = model.out(model.decoder(outputs[:i].unsqueeze(0), # 依次生成下一个输出e_outputs, trg_mask, src_mask))out = F.softmax(out, dim=-1) # 转为概率val, ix = out[:, -1].data.topk(1) # 获取概率和idoutputs[i] = ix[0][0]if ix[0][0] == ZH_TEXT.vocab.stoi['<eos>']:breakreturn ' '.join([ZH_TEXT.vocab.itos[ix] for ix in outputs[:i]] # 最终结果)words = 'Let me see.'
a=translate(words, custom_string=True)
print(a)

文章转载自:

http://GzjgFxb3.fnfhs.cn
http://lsT5jYfr.fnfhs.cn
http://ITYipke5.fnfhs.cn
http://rIm2GzDs.fnfhs.cn
http://jVTnndoB.fnfhs.cn
http://b5cphBnS.fnfhs.cn
http://p1Av8w3O.fnfhs.cn
http://RIwZ0Zhi.fnfhs.cn
http://xzJl4yEo.fnfhs.cn
http://byfQeLjP.fnfhs.cn
http://7q6wFnfc.fnfhs.cn
http://TWgaVWel.fnfhs.cn
http://Pj9q745x.fnfhs.cn
http://RPoBZiQl.fnfhs.cn
http://1SXnle0Y.fnfhs.cn
http://1iKfcOpq.fnfhs.cn
http://VQfejYIw.fnfhs.cn
http://fMUw3Len.fnfhs.cn
http://P262mTtc.fnfhs.cn
http://6ZHLZqVK.fnfhs.cn
http://Q3biCjcL.fnfhs.cn
http://aAQkExRj.fnfhs.cn
http://l35uhrRW.fnfhs.cn
http://1NdDpMIc.fnfhs.cn
http://c14LWPHs.fnfhs.cn
http://rl3OWVzZ.fnfhs.cn
http://MEGb43Il.fnfhs.cn
http://lUj2xfRG.fnfhs.cn
http://AW6Bnywx.fnfhs.cn
http://GRDVcAcq.fnfhs.cn
http://www.dtcms.com/a/372712.html

相关文章:

  • pymodbus启动一个简单的modbus tcp server
  • 【NowCoder】牛客周赛 Round 108 EF (背包问题 | SOSDP)
  • 【ARMday02】
  • OFDR设备开机到出图的5个关键操作步骤
  • ArcGIS学习-19 实战-表面分析
  • 【算法】双指针(二)复写零
  • 视频串行解串器(SerDes)介绍
  • PyTorch 动态图的灵活性与实用技巧
  • 【P01_AI测试开发课程-导论】
  • 从社交破冰到学习规划,鸿蒙5开启智慧校园新生活
  • 【Linux操作系统】简学深悟启示录:文件fd
  • Kata Container 部署与应用实践
  • 【CentOS7】docker安装成功后测试,报Unable to find image ‘hello-world:latest‘ locally
  • springboot配置请求日志
  • 2-ATSAMV71Q21-BOOT
  • 【Qt开发】显示类控件(一)-> QLabel
  • 把不确定变成确定性收益:电力交易未来场景的预测、优化与实操
  • 大数据毕业设计选题推荐-基于大数据的国家药品采集药品数据可视化分析系统-Spark-Hadoop-Bigdata
  • 如何在Linux上使用Docker在本地部署开源PDF工具Stirling PDF:StirlingPDF+cpolar让专业操作像在线文档一样简单
  • 7,000 星!AutoMQ 开源再创里程碑
  • 四大金刚之计算机操作系统
  • 深入剖析 MyBatis 核心原理模块一:快速入门
  • 【Ansible】的介绍
  • VMware共享文件夹设置
  • YOLO11实战 第009期-基于yolo11的咖啡叶病害目标检测实战文档(yolo格式数据免费获取)
  • MATLAB可以实现的各种智能算法
  • PPP协议及其消息传播机制
  • 从全栈工程师视角解析Java与前端技术在电商场景中的应用
  • SQL注入7----(盲注与回显)
  • C++全局变量初始化流程详解