从头开始构建和训练 Transformer(下)

导 读

上一篇推文从头开始构建和训练 Transformer(上)icon-default.png?t=N7T8https://blog.csdn.net/weixin_46287760/article/details/136048418介绍了构建和训练Transformer的过程和构建每个组件的代码示例。本文将使用数据对该架构进行代码演示,验证其模型性能。

本期『数据+代码』已上传百度网盘。

有需要的朋友关注公众号【小Z的科研日常】,回复关键词[Transformer]获取

01、加载数据集

对于此任务,我们将使用🤗Hugging Face 上提供的OpusBooks 数据集。该数据集由两个特征组成,idtranslation。该translation功能包含不同语言的句子对,例如西班牙语和葡萄牙语、英语和法语等。

我首先尝试将句子从英语翻译成葡萄牙语,但是这对句子只有 1.4k个示例,因此在该模型的当前配置中结果并不令人满意。然后,我尝试使用英语-法语对,因为它的示例数量较多(127k),但使用当前配置进行训练需要很长时间。

我们首先定义get_all_sentences函数来迭代数据集并根据定义的语言对提取句子。

# 迭代数据集,提取原句及其译文
def get_all_sentences(ds, lang):
    for pair in ds:
        yield pair['translation'][lang]

get_ds函数定义为加载和准备数据集以进行训练和验证。在此函数中,我们构建或加载分词器、拆分数据集并创建 DataLoader,以便模型可以成功地批量迭代数据集。这些函数的结果是源语言和目标语言的标记器以及 DataLoader 对象。

def get_ds(config):
    
      # 语言对将在我们稍后创建的 "配置 "字典中定义。
    ds_raw = load_dataset('opus_books', f'{config["lang_src"]}-{config["lang_tgt"]}', split = 'train') 
    
    # 为源语言和目标语言构建或加载标记符
    tokenizer_src = build_tokenizer(config, ds_raw, config['lang_src'])
    tokenizer_tgt = build_tokenizer(config, ds_raw, config['lang_tgt'])
    
    # 分割数据集进行训练和验证
    train_ds_size = int(0.9 * len(ds_raw)) # 90% for training
    val_ds_size = len(ds_raw) - train_ds_size # 10% for validation
    train_ds_raw, val_ds_raw = random_split(ds_raw, [train_ds_size, val_ds_size]) # Randomly splitting the dataset
                                    
    # 使用双语数据集(BilingualDataset)类处理数据,我们将在下面定义该类
    train_ds = BilingualDataset(train_ds_raw, tokenizer_src, tokenizer_tgt, config['lang_src'], config['lang_tgt'], config['seq_len'])
    val_ds = BilingualDataset(val_ds_raw, tokenizer_src, tokenizer_tgt, config['lang_src'], config['lang_tgt'], config['seq_len'])
                                    
   # 对整个数据集进行迭代,并打印在源语言和目标语言句子中找到的最大长度
    max_len_src = 0
    max_len_tgt = 0
    for pair in ds_raw:
        src_ids = tokenizer_src.encode(pair['translation'][config['lang_src']]).ids
        tgt_ids = tokenizer_src.encode(pair['translation'][config['lang_tgt']]).ids
        max_len_src = max(max_len_src, len(src_ids))
        max_len_tgt = max(max_len_tgt, len(tgt_ids))
        
    print(f'Max length of source sentence: {max_len_src}')
    print(f'Max length of target sentence: {max_len_tgt}')
    
    # 为训练集和验证集创建数据加载器
    # 在训练和验证过程中,使用数据加载器分批迭代数据集
    train_dataloader = DataLoader(train_ds, batch_size = config['batch_size'], shuffle = True) # Batch size will be defined in the config dictionary
    val_dataloader = DataLoader(val_ds, batch_size = 1, shuffle = True)
    
    return train_dataloader, val_dataloader, tokenizer_src, tokenizer_tgt # Returning the DataLoader objects and tokenizers

我们定义casual_mask函数来为解码器的注意力机制创建掩码。此掩码可防止模型获得有关序列中未来元素的信息。

我们首先制作一个充满 1 的方形网格。我们用参数确定网格大小size。然后,我们将主对角线上方的所有数字更改为零。一侧的每个数字都变成零,而其余的仍然是1。然后该函数翻转所有这些值,将 1 变为 0,将 0 变为 1。这个过程对于预测序列中未来标记的模型至关重要。

02、验证循环

我们现在将为验证循环创建两个函数。验证循环对于评估模型从训练期间未见过的数据翻译句子的性能至关重要。

我们将定义两个函数。第一个函数 ,greedy_decode通过获取最可能的下一个标记为我们提供模型的输出。第二个函数run_validation负责运行验证过程,在该过程中我们解码模型的输出并将其与目标句子的参考文本进行比较。

class BilingualDataset(Dataset):
    def __init__(self, ds, tokenizer_src, tokenizer_tgt, src_lang, tgt_lang, seq_len) -> None:
        super().__init__()
        
        self.seq_len = seq_len
        self.ds = ds
        self.tokenizer_src = tokenizer_src
        self.tokenizer_tgt = tokenizer_tgt
        self.src_lang = src_lang
        self.tgt_lang = tgt_lang
        
        
        self.sos_token = torch.tensor([tokenizer_tgt.token_to_id("[SOS]")], dtype=torch.int64)
        self.eos_token = torch.tensor([tokenizer_tgt.token_to_id("[EOS]")], dtype=torch.int64)
        self.pad_token = torch.tensor([tokenizer_tgt.token_to_id("[PAD]")], dtype=torch.int64)

        
    
    def __len__(self):
        return len(self.ds)
    
    
    def __getitem__(self, index: Any) -> Any:
        src_target_pair = self.ds[index]
        src_text = src_target_pair['translation'][self.src_lang]
        tgt_text = src_target_pair['translation'][self.tgt_lang]
        
       
        enc_input_tokens = self.tokenizer_src.encode(src_text).ids
        dec_input_tokens = self.tokenizer_tgt.encode(tgt_text).ids
        
       
        enc_num_padding_tokens = self.seq_len - len(enc_input_tokens) - 2 # Subtracting the two '[EOS]' and '[SOS]' special tokens
       
        dec_num_padding_tokens = self.seq_len - len(dec_input_tokens) - 1 # Subtracting the '[SOS]' special token
        
        if enc_num_padding_tokens < 0 or dec_num_padding_tokens < 0:
            raise ValueError('Sentence is too long')
         
        encoder_input = torch.cat(
            [
            self.sos_token, # inserting the '[SOS]' token
            torch.tensor(enc_input_tokens, dtype = torch.int64), # Inserting the tokenized source text
            self.eos_token, # Inserting the '[EOS]' token
            torch.tensor([self.pad_token] * enc_num_padding_tokens, dtype = torch.int64) # Addind padding tokens
            ]
        )
        
        decoder_input = torch.cat(
            [
                self.sos_token, # inserting the '[SOS]' token 
                torch.tensor(dec_input_tokens, dtype = torch.int64), # Inserting the tokenized target text
                torch.tensor([self.pad_token] * dec_num_padding_tokens, dtype = torch.int64) # Addind padding tokens
            ]
        
        )

        label = torch.cat(
            [
                torch.tensor(dec_input_tokens, dtype = torch.int64), # Inserting the tokenized target text
                self.eos_token, # Inserting the '[EOS]' token 
                torch.tensor([self.pad_token] * dec_num_padding_tokens, dtype = torch.int64) # Adding padding tokens
                
            ]
        )

        assert encoder_input.size(0) == self.seq_len
        assert decoder_input.size(0) == self.seq_len
        assert label.size(0) == self.seq_len
        
        return {
            'encoder_input': encoder_input,
            'decoder_input': decoder_input, 
            'encoder_mask': (encoder_input != self.pad_token).unsqueeze(0).unsqueeze(0).int(),
            'decoder_mask': (decoder_input != self.pad_token).unsqueeze(0).unsqueeze(0).int() & casual_mask(decoder_input.size(0)), 
            'label': label,
            'src_text': src_text,
            'tgt_text': tgt_text
        }  

03、训练循环

我们已准备好在 OpusBook 数据集上训练 Transformer 模型,以执行英语到意大利语翻译任务。我们首先通过调用我们之前定义的get_model函数来定义加载模型的函数。build_transformer该函数使用config字典来设置一些参数。

def get_model(config, vocab_src_len, vocab_tgt_len):
    
    model = build_transformer(vocab_src_len, vocab_tgt_len, config['seq_len'], config['seq_len'], config['d_model'])
    return model 

下面我们将定义两个函数来配置我们的模型和训练过程。

get_config函数中,我们定义了训练过程的关键参数。batch_size一次迭代中使用的训练示例的数量、num_epochs整个数据集通过 Transformer 向前和向后传递的次数、lr优化器的学习率等。我们最终还将定义来自 OpusBook 数据集的对,'lang_src': 'en'用于选择英语作为源语言以及'lang_tgt': 'it'选择意大利语作为目标语言。

get_weights_file_path函数构建用于保存或加载任何特定时期的模型权重的文件路径。

def get_config():
    return{
        'batch_size': 8,
        'num_epochs': 20,
        'lr': 10**-4,
        'seq_len': 350,
        'd_model': 512, 
        'lang_src': 'en',
        'lang_tgt': 'it',
        'model_folder': 'weights',
        'model_basename': 'tmodel_',
        'preload': None,
        'tokenizer_file': 'tokenizer_{0}.json',
        'experiment_name': 'runs/tmodel'
    }

def get_weights_file_path(config, epoch: str):
    model_folder = config['model_folder'] 
    model_basename = config['model_basename'] 
    model_filename = f"{model_basename}{epoch}.pt" 
    return str(Path('.')/ model_folder/ model_filename)

我们最终定义了最后一个函数 ,train_model它将config参数作为输入。

在此函数中,我们将为训练设置一切。我们将模型及其必要组件加载到 GPU 上以加快训练速度,设置Adam优化器并配置CrossEntropyLoss函数来计算模型输出的翻译与数据集中的参考翻译之间的差异。

迭代训练批次、执行反向传播和计算梯度所需的每个循环都在此函数中。我们还将使用它来运行验证函数并保存模型的当前状态。

def train_model(config):
    # 设置设备在 GPU 上运行,以加快训练速度
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    print(f"Using device {device}")
    
    # 创建模型目录以存储权重
    Path(config['model_folder']).mkdir(parents=True, exist_ok=True)
    
    # 使用 "get_ds "函数检索源语言和目标语言的数据加载器和标记器
    train_dataloader, val_dataloader, tokenizer_src, tokenizer_tgt = get_ds(config)
    
    # 使用 "get_model "函数在 GPU 上初始化模型
    model = get_model(config,tokenizer_src.get_vocab_size(), tokenizer_tgt.get_vocab_size()).to(device)
    
    # Tensorboard
    writer = SummaryWriter(config['experiment_name'])
    
    # 使用'# config'字典中的指定学习率和ε值设置优化器
    optimizer = torch.optim.Adam(model.parameters(), lr=config['lr'], eps = 1e-9)
    
    # 初始化和全局步长变量
    initial_epoch = 0
    global_step = 0
    
    if config['preload']:
        model_filename = get_weights_file_path(config, config['preload'])
        print(f'Preloading model {model_filename}')
        state = torch.load(model_filename) # Loading model
        
        initial_epoch = state['epoch'] + 1
        optimizer.load_state_dict(state['optimizer_state_dict'])
        global_step = state['global_step']
        
    loss_fn = nn.CrossEntropyLoss(ignore_index = tokenizer_src.token_to_id('[PAD]'), label_smoothing = 0.1).to(device)

    for epoch in range(initial_epoch, config['num_epochs']):

        batch_iterator = tqdm(train_dataloader, desc = f'Processing epoch {epoch:02d}')

        for batch in batch_iterator:
            model.train() # Train the model
            encoder_input = batch['encoder_input'].to(device)
            decoder_input = batch['decoder_input'].to(device)
            encoder_mask = batch['encoder_mask'].to(device)
            decoder_mask = batch['decoder_mask'].to(device)
           
            encoder_output = model.encode(encoder_input, encoder_mask)
            decoder_output = model.decode(encoder_output, encoder_mask, decoder_input, decoder_mask)
            proj_output = model.project(decoder_output)

            label = batch['label'].to(device)

            loss = loss_fn(proj_output.view(-1, tokenizer_tgt.get_vocab_size()), label.view(-1))

            batch_iterator.set_postfix({f"loss": f"{loss.item():6.3f}"})
            
            writer.add_scalar('train loss', loss.item(), global_step)
            writer.flush()
            loss.backward()

            optimizer.step()

            optimizer.zero_grad()
            
            global_step += 1 
            
        run_validation(model, val_dataloader, tokenizer_src, tokenizer_tgt, config['seq_len'], device, lambda msg: batch_iterator.write(msg), global_step, writer)

        model_filename = get_weights_file_path(config, f'{epoch:02d}')
        torch.save({
            'epoch': epoch, # Current epoch
            'model_state_dict': model.state_dict(),# Current model state
            'optimizer_state_dict': optimizer.state_dict(), # Current optimizer state
            'global_step': global_step # Current global step 
        }, model_filename)

现在开始训练我们的模型!

if __name__ == '__main__':
    warnings.filterwarnings('ignore') # 忽略警告
    config = get_config() # 检索配置设置
    train_model(config) # 使用配置参数训练模型

结果如下:

Using device cuda
Downloading builder script:
6.08k/? [00:00<00:00, 391kB/s]
Downloading metadata:
161k/? [00:00<00:00, 11.0MB/s]
Downloading and preparing dataset opus_books/en-it (download: 3.14 MiB, generated: 8.58 MiB, post-processed: Unknown size, total: 11.72 MiB) to /root/.cache/huggingface/datasets/opus_books/en-it/1.0.0/e8f950a4f32dc39b7f9088908216cd2d7e21ac35f893d04d39eb594746af2daf...
Downloading data: 100%
3.30M/3.30M [00:00<00:00, 10.6MB/s]
Dataset opus_books downloaded and prepared to /root/.cache/huggingface/datasets/opus_books/en-it/1.0.0/e8f950a4f32dc39b7f9088908216cd2d7e21ac35f893d04d39eb594746af2daf. Subsequent calls will reuse this data.
Max length of source sentence: 309
Max length of target sentence: 274
....................................................................

04、结论

在本文中,我们深入探索了原始 Transformer 架构,如《Attention Is All You Need》研究论文中所述。我们使用 PyTorch 在语言翻译任务上逐步实现它,使用 OpusBook 数据集进行英语到意大利语的翻译。

Transformer 是向当今最先进模型(例如 OpenAI 的 GPT-4 模型)迈出的革命性一步。这就是为什么理解这种架构如何工作以及它可以实现什么如此重要。

参考论文:“Attention Is All You Need”

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/374420.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[office] Excel如何快速统一数字编号长度 #经验分享#其他

Excel如何快速统一数字编号长度 我们在办公室使用Excel统计数据的时候&#xff0c;经常会遇到第一列数据全部是数字编号&#xff0c;但是因为数字的位数不一样&#xff0c;长短不一的样子看起来不是很协调。那么如何快速统一数字编号长度呢&#xff1f;一起来了解一下吧 我们在…

STM32内部Flash

目录 一、内部Flash简介 二、内部Flash构成 1. 主存储器 2. 系统存储区 3. 选项字节 三、内部Flash写入过程 1. 解锁 2. 页擦除 3. 写入数据 四、工程空间分布 某工程的ROM存储器分布映像&#xff1a; 1. 程序ROM的加载与执行空间 2. ROM空间分布表 一、内部Flash…

【Java数据结构】单向 不带头 非循环 链表实现

模拟实现LinkedList&#xff1a;下一篇文章 LinkedList底层是双向、不带头结点、非循环的链表 /*** LinkedList的模拟实现*单向 不带头 非循环链表实现*/ class SingleLinkedList {class ListNode {public int val;public ListNode next;public ListNode(int val) {this.val …

Go语言每日一练——链表篇(五)

传送门 牛客面试笔试必刷101题 ----------------合并k个已排序的链表 题目以及解析 题目 解题代码及解析 解析 这一道题与昨天的合并链表题目类似&#xff0c;但是由于有K个且时间复杂度要求控制在O(nlogn)&#xff0c;这里主要有两种解法&#xff1a;一种是依旧使用归并来…

Python算法题集_相交链表

Python算法题集_相交链表 题41&#xff1a;相交链表1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【双层循环】2) 改进版一【双指针】3) 改进版二【哈希检索-集合】4) 改进版三【哈希检索-字典】 4. 最优算法 本文为Python算法题集之一的代码示…

游戏服务器购买多少钱1个月?买一年贵吗?

游戏服务器购买多少钱1个月&#xff1f;阿里云26元1个月、腾讯云32元1个月。买一年贵吗&#xff1f;不贵。 游戏服务器租用多少钱一年&#xff1f;1个月游戏服务器费用多少&#xff1f;阿里云游戏服务器26元1个月、腾讯云游戏服务器32元&#xff0c;游戏服务器配置从4核16G、4…

OSI七层模型

文章目录 定义各层功能定义在 OSI 模型中如何进行通信OSI 模型有哪些替代方案&#xff1a;TCP/IP 定义 OSI是一种开放系统互连参考模型 (Open System Interconnect 简称OSI&#xff09;&#xff0c;是国际标准化组织(ISO)和国际电报电话咨询委员会(CCITT)联合制定的开放系统互…

[职场] 集成电路IC设计工程师求职简历工作经历范文(精选4篇) #职场发展#其他

集成电路IC设计工程师求职简历工作经历范文&#xff08;精选4篇&#xff09; 集成电路IC设计工程师在找工作做简历的时候&#xff0c;经常不知道求职简历中的工作经历板块怎么写&#xff0c;下面是简历网小编整理的适合集成电路IC设计工程师在做简历时写的工作经历范文4篇&…

40000000人民币有多重

在日常生活中&#xff0c;我们经常看到大量现金的重量作为一个有趣的话题。那么&#xff0c;40000000人民币到底有多重呢&#xff1f;本文将详细介绍如何计算这个问题&#xff0c;并讨论与现金重量相关的因素。 首先&#xff0c;我们需要了解人民币纸币的重量。一张崭新的100元…

MySQL篇----第十篇

系列文章目录 文章目录 系列文章目录前言一、MyISAM Static 和 MyISAM Dynamic 有什么区别?二、如果一个表有一列定义为 TIMESTAMP,将发生什么?三、你怎么看到为表格定义的所有索引?四、LIKE 声明中的%和_是什么意思?五、列对比运算符是什么?前言 前些天发现了一个巨牛…

【力扣】整数反转,判断是否溢出的数学解法

整数反转原题地址 方法一&#xff1a;数学 反转整数 如何反转一个整数呢&#xff1f;考虑整数操作的3个技巧&#xff1a; xmod10可以取出x的最低位&#xff0c;如x123&#xff0c;xmod103。x/10可以去掉x的最低位&#xff0c;如x123&#xff0c;x/10&#xff0c;x12。xx*10…

26 使用 Samba 实现文件共享

Samba 文件共享服务 Samba 服务程序现在已经成为在 Linux 系统与Windows 系统之间共享文件的最佳选择 详细配置请转Samba服务 安装 [rootlocalhost ~]# yum install samba -ySamba 服务程序的主配置文件&#xff0c;只有 37 行。 第 5&#xff5e;8 行参数中所提到的 cups…

仰暮计划|“用心感悟使我获取了艺术真谛,自律如始让我获得了人生成功,我将继续在艺术道路上走下去”

口述人:郭敬东&#xff08;男&#xff09; 整理人:马静 口述人与整理人关系:姥爷与外孙女 口述人基本信息:现60岁&#xff0c;1963年出生于湖北省大悟县刘集镇金鼓村&#xff0c;1987年移居到河南省焦作市&#xff0c;现居河南省焦作市高新区。 引言:在得知要讲述自己的经历…

企业数字化转型面临什么挑战?

数字化转型是一个复杂且持续的过程&#xff0c;涉及将数字技术集成到组织的各个方面&#xff0c;从根本上改变组织的运营方式和为客户提供价值的方式。虽然具体的挑战可能因企业的性质和规模而异&#xff0c;但一些常见的挑战包括&#xff1a; 1.抵制变革&#xff1a; 文化阻…

STM32单片机的基本原理与应用(七)

超声波测距实验 基本原理 超声波测距实验是STM32单片机通过控制HC-SR04超声波模块&#xff0c;使其发送超声波&#xff0c;遇到物体反射回超声波来实现距离测量&#xff0c;其原理就是在发射超声波到接收超声波会有一段时间&#xff0c;而超声波在空气中传播的速度为声速&…

python打包exe,并发布windows服务实践

操作实践 1、编写python程序&#xff0c;按照自己的需求编写 以下是案例 # -*- coding:utf-8 -*- import win32serviceutil import win32service import win32event import win32timezone #不加导入&#xff0c;打包后运行会报错&#xff0c;原因未知&#xff0c;暂时不…

小白Linux学习笔记-Linux内核

Linux内核 文章目录 Linux内核WHEREWHATmoudules.dep 文件depmod 命令depmod 实验lsmod 命令modinfo 命令内核模块的观察实验 内核模块的加载与移除:insmod, modprobe, rmmodinsmod 命令modprobe 命令rmmod 命令内核模块的加载与移除实验 内核模块的额外参数设定:/etc/modprobe…

二道经典OJ题带你入门回溯剪枝算法

风起于青萍之末 浪成于微澜之间 &#x1f3a5;个人主页 &#x1f525;个人专栏 &#x1f3a5;前期回顾-环形链表 目录 回溯算法的简介 N皇后问题 思路 代码测试 N皇后 思路 判断一竖列是否有皇后 判断对角线是否有皇后 代码测试 回溯算法的简介 回溯是递归的副产品&#xff0…

计算机设计大赛 深度学习+python+opencv实现动物识别 - 图像识别

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络3.1卷积层3.2 池化层3.3 激活函数&#xff1a;3.4 全连接层3.5 使用tensorflow中keras模块实现卷积神经网络 4 inception_v3网络5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; *…

5-2、S曲线计算【51单片机+L298N步进电机系列教程】

↑↑↑点击上方【目录】&#xff0c;查看本系列全部文章 摘要&#xff1a;本节介绍S曲线的基本变换&#xff0c;将基本形式的S曲线变换成为任意过两点的S曲线&#xff0c;为后续步进电机S曲线运动提供理论支撑 一.计算目标 ①计算经过任意不同两点的S曲线方程 ②可调节曲线平…