《动手学深度学习》读书笔记—9.5机器翻译与数据集

本文记录了自己在阅读《动手学深度学习》时的一些思考,仅用来作为作者本人的学习笔记,不存在商业用途。
语言模型是自然语言处理的关键, 而机器翻译是语言模型最成功的基准测试。 因为机器翻译正是将输入序列转换成输出序列的 序列转换模型(sequence transduction)的核心问题。 序列转换模型在各类现代人工智能应用中发挥着至关重要的作用, 本节将介绍机器翻译问题及其后文需要使用的数据集。
机器翻译(machine translation)指的是 将序列从一种语言自动翻译成另一种语言。 事实上,这个研究领域可以追溯到数字计算机发明后不久的20世纪40年代, 特别是在第二次世界大战中使用计算机破解语言编码。 几十年来,在使用神经网络进行端到端学习的兴起之前, 统计学方法在这一领域一直占据主导地位 (Brown et al., 1990, Brown et al., 1988)。 因为统计机器翻译(statistical machine translation)涉及了 翻译模型和语言模型等组成部分的统计分析, 因此基于神经网络的方法通常被称为 神经机器翻译(neural machine translation), 用于将两种翻译模型区分开来。
本书的关注点是神经网络机器翻译方法,强调的是端到端的学习。 机器翻译的数据集是由源语言和目标语言的文本序列对组成的。 因此,我们需要一种完全不同的方法来预处理机器翻译数据集。

import os
import torch
from d2l import torch as d2l

9.5.1 下载和预处理数据集

首先,下载一个由Tatoeba项目的双语句子对组成的“英-法”数据集,数据集中的每一行都是制表符分隔的文本序列对, 序列对由英文文本序列和翻译后的法语文本序列组成。 请注意,每个文本序列可以是一个句子, 也可以是包含多个句子的一个段落。 在这个将英语翻译成法语的机器翻译问题中, 英语是源语言(source language), 法语是目标语言(target language)。

#@save
d2l.DATA_HUB['fra-eng'] = (d2l.DATA_URL + 'fra-eng.zip','94646ad1522d915e7b0f9296181140edcf86a4f5')#@save
def read_data_nmt():"""载入“英语-法语”数据集"""data_dir = d2l.download_extract('fra-eng')with open(os.path.join(data_dir, 'fra.txt'), 'r',encoding='utf-8') as f:return f.read()# 这个数据集返回是字符串, 里面带有换行符和其他特殊符号
raw_text = read_data_nmt()
# 打印前面75个字符(0到74)
print(raw_text[:75])

🏷运行结果

Go.	Va !
Hi.	Salut !
Run!	Cours !
Run!	Courez !
Who?	Qui ?
Wow!	Ça alors !

下载数据集后,原始文本数据需要经过几个预处理步骤。 例如,我们用空格代替不间断空格(non-breaking space), 使用小写字母替换大写字母,并在单词和标点符号之间插入空格。

#@save
def preprocess_nmt(text):"""预处理“英语-法语”数据集"""def no_space(char, prev_char):# 如果当前字符是特殊符号且上一个字符不是空格, 说明当前字符是单词后面跟着的特殊符号, 则该字符前应该添加空格# 返回布尔变量, 需要添加空格时则返回True, 否则返回Falsereturn char in set(',.!?') and prev_char != ' '# 使用空格替换不间断空格# text.replace('\u202f', ' ')使用标准空格替换Unicode编码中的窄空格'\u202f'# text.replace('\xa0', ' ')使用标准空格替换Unicode编码中的不换行空格'\xa0'# 使用小写字母替换大写字母text = text.replace('\u202f', ' ').replace('\xa0', ' ').lower()# 在单词和标点符号之间插入空格# for i, char in enumerate(text)返回字符串text中的每个字符以及其对应的索引# if i > 0 and no_space(char, text[i - 1])如果当前字符不是首字符并且当前字符是特殊字符(,.!?), 则说明当前字符是单词后面的特殊字符, 在中间插入空格# else如果当前字符是首字符或不满足上述条件, 则当前字符是单词内的字符, 直接返回即可out = [' ' + char if i > 0 and no_space(char, text[i - 1]) else charfor i, char in enumerate(text)]# out是数据集raw_text经过字符修正后的结果, 是一个列表, 列表中每个元素是修正后的数据集中的字符# ''.join(out)表示利用列表out中的元素组建字符串, 其中不使用任何额外字符填充# ''.join(["A","B","C"])	"ABC"# ' '.join(["A","B","C"])	"A B C"# '-'.join(["A","B","C"])	"A-B-C"return ''.join(out)# 处理数据集并打印前80个字符
text = preprocess_nmt(raw_text)
print(text[:80])

🏷预处理后的数据集如下

go .	va !
hi .	salut !
run !	cours !
run !	courez !
who ?	qui ?
wow !	ça alors !

9.5.2 词元化

在机器翻译中,我们更喜欢单词级词元化 (最先进的模型可能使用更高级的词元化技术)。 下面的tokenize_nmt函数对前num_examples个文本序列对进行词元, 其中每个词元要么是一个词,要么是一个标点符号。此函数返回两个词元列表:source和target: sourcei是源语言(这里是英语)第iii个文本序列的词元列表, targeti是目标语言(这里是法语)第iii个文本序列的词元列表。

#@save
def tokenize_nmt(text, num_examples=None):"""词元化“英语-法语”数据数据集"""# 创建两个空列表source和targetsource, target = [], []# text.split('\n')按换行符拆分修正后的数据集text,得到每个英语-法语单词对(一行是一个)# for i, line in enumerate(text.split('\n'))返回每行line及行索引ifor i, line in enumerate(text.split('\n')):# 如果num_examples存在值(正整数)并且当前处理的行超过了num_examples时则终止(当前处理的是第i个单词对, 但设置了只处理前面num_examples个单词对)if num_examples and i > num_examples:break# line.split('\t')按照空格拆分, parts[0]是英语部分, parts[1]是法语部分parts = line.split('\t')# 如果拆分不出来两个部分, 说明当前行不是英语-法语单词对if len(parts) == 2:# 按照空格拆分英语部分(单词直接有空格, 单词和特殊符号之间有空格)source.append(parts[0].split(' '))# 按照空格拆分法语部分(单词直接有空格, 单词和特殊符号之间有空格)target.append(parts[1].split(' '))# 返回英语列表source和法语列表targetreturn source, target# 打印前6个英语单词和对应的法语单词
source, target = tokenize_nmt(text)
source[:6], target[:6]

🏷运行结果如下

([['go', '.'],['hi', '.'],['run', '!'],['run', '!'],['who', '?'],['wow', '!']],[['va', '!'],['salut', '!'],['cours', '!'],['courez', '!'],['qui', '?'],['ça', 'alors', '!']])

绘制每个文本序列所包含的词元数量的直方图。 在这个简单的“英-法”数据集中,大多数文本序列的词元数量少于20个。

#@save
def show_list_len_pair_hist(legend, xlabel, ylabel, xlist, ylist):"""绘制列表长度对的直方图"""# 设置图表大小d2l.set_figsize()# for l in xlist, for l in ylist遍历source和target中的每个元素(就是原始数据text的每一行)# len(l)获得source和target中包含的词元数量, 比如英语的['go','.']是两个词元, 对应法语的['va','!']也是两个词元_, _, patches = d2l.plt.hist([[len(l) for l in xlist], [len(l) for l in ylist]])d2l.plt.xlabel(xlabel)d2l.plt.ylabel(ylabel)# 设置target的直方图样式为带斜线for patch in patches[1].patches:patch.set_hatch('/')d2l.plt.legend(legend)show_list_len_pair_hist(['source', 'target'], '# tokens per sequence','count', source, target);

统计图

9.5.3 词表

由于机器翻译数据集由语言对组成, 因此我们可以分别为源语言和目标语言构建两个词表。 使用单词级词元化时,词表大小将明显大于使用字符级词元化时的词表大小。 为了缓解这一问题,这里我们将出现次数少于2次的低频率词元 视为相同的未知(“”)词元。 除此之外,我们还指定了额外的特定词元, 例如在小批量时用于将序列填充到相同长度的填充词元(“”), 以及序列的开始词元(“”)和结束词元(“”)。 这些特殊词元在自然语言处理任务中比较常用。

# 具体可以参考8.2文本预处理中的代码
# 以['go', '.']为例, 在d2l.Vocab中调用collection.Counter时会分别统计'go'和'.'出现的频率
src_vocab = d2l.Vocab(source, min_freq=2,reserved_tokens=['<pad>', '<bos>', '<eos>'])
len(src_vocab)

🏷运行结果显示英语词表共包含10012个词元

10012

9.5.4 加载数据集

在机器翻译中,每个样本都是由源和目标组成的文本序列对, 其中的每个文本序列可能具有不同的长度。为了提高计算效率,我们仍然可以通过截断(truncation)和 填充(padding)方式实现一次只处理一个小批量的文本序列。 假设同一个小批量中的每个序列都应该具有相同的长度num_steps, 那么如果文本序列的词元数目少于num_steps时, 我们将继续在其末尾添加特定的“”词元, 直到其长度达到num_steps; 反之,我们将截断文本序列时,只取其前num_steps 个词元, 并且丢弃剩余的词元。这样,每个文本序列将具有相同的长度, 以便以相同形状的小批量进行加载。

#@save
def truncate_pad(line, num_steps, padding_token):"""截断或填充文本序列"""if len(line) > num_steps:return line[:num_steps]  # 截断return line + [padding_token] * (num_steps - len(line))  # 填充# 根据source[0], 即['go', '.'], 去词表src_vocab中查找对应的词元索引
# 由于source[0] = ['go','.']共包含两个词元, 而num_steps设置了10, 即每行应包括10个词元,
# 所以需要填充8个'<pad>', 去词表src_vocab中查找对应的词元索引后拼接成包含10个词元索引的向量
truncate_pad(src_vocab[source[0]], 10, src_vocab['<pad>'])

🏷运行结果如下所示

[47, 4, 1, 1, 1, 1, 1, 1, 1, 1]

现在我们定义一个函数,可以将文本序列转换成小批量数据集用于训练。 我们将特定的“”词元添加到所有序列的末尾, 用于表示序列的结束。 当模型通过一个词元接一个词元地生成序列进行预测时, 生成的“”词元说明完成了序列输出工作。 此外,我们还记录了每个文本序列的长度, 统计长度时排除了填充词元, 在稍后将要介绍的一些模型会需要这个长度信息。

#@save
def build_array_nmt(lines, vocab, num_steps):"""将机器翻译的文本序列转换成小批量"""# for l in lines, 从lines中遍历每行l# vocab[l]获得该行中的词元在词表中的索引# lines是一个列表, 每个元素也是列表, 包含该行中的所有词元在词表中索引, 比如[[47, 4], [12, 4]]lines = [vocab[l] for l in lines]# 遍历lines中的每个元素l, 向里面加入终止符'<eos>'在词表中对应的索引, 于是lines变成类似[[47, 4, 0], [12, 4, 0]]lines = [l + [vocab['<eos>']] for l in lines]# 遍历lines中的每个元素, 如果不满足num_steps则填充'<pad>'在词表中的索引# 转成torch.tensor返回# array类似tensor([[  9,   4,   3,  ...,   1,   1,   1],#                  [113,   4,   3,  ...,   1,   1,   1]])array = torch.tensor([truncate_pad(l, num_steps, vocab['<pad>']) for l in lines])# array的每个元素就是一行, array != vocab['<pad>']逐元素比较是否为填充词元'<pad>'的索引, 获得布尔值# sum(1)按照列求和, 于是可以得到一行中有多少不是填充词元'<pad>'的词元, 即获得一行的长度valid_len = (array != vocab['<pad>']).type(torch.int32).sum(1)# 返回填充后的每行索引以及每行的长度(包含的词元数)return array, valid_len

9.5.5 训练模型

定义load_data_nmt函数来返回数据迭代器以及源语言和目标语言的两种词表。

#@save
def load_data_nmt(batch_size, num_steps, num_examples=600):"""返回翻译数据集的迭代器和词表"""# 预处理"英语-法语"数据集(规范化空格并统一小写)text = preprocess_nmt(read_data_nmt())# 词元化source, target = tokenize_nmt(text, num_examples)# 根据英语获得英语词表src_vocab = d2l.Vocab(source, min_freq=2,reserved_tokens=['<pad>', '<bos>', '<eos>'])# 根据法语获得法语词表tgt_vocab = d2l.Vocab(target, min_freq=2,reserved_tokens=['<pad>', '<bos>', '<eos>'])# 获得填充后的英语及每行长度src_array, src_valid_len = build_array_nmt(source, src_vocab, num_steps)# 获得填充后的法语及每行长度tgt_array, tgt_valid_len = build_array_nmt(target, tgt_vocab, num_steps)# 将数据集打包成元组data_arrays = (src_array, src_valid_len, tgt_array, tgt_valid_len)# 返回数据集迭代器, d2l.load_array需要元组型数据和批量大小, 元组型数据格式是(数据特征, 标签)data_iter = d2l.load_array(data_arrays, batch_size)return data_iter, src_vocab, tgt_vocab

读取"英语-法语"数据集中的第一个小批量数据

# 批量大小为2, 时间长度为8(每行应该包括8个词元)
# 返回训练集迭代器, 英语词表, 法语词表
train_iter, src_vocab, tgt_vocab = load_data_nmt(batch_size=2, num_steps=8)
# 打印第一个批量的数据
for X, X_valid_len, Y, Y_valid_len in train_iter:print('X:', X.type(torch.int32))print('X的有效长度:', X_valid_len)print('Y:', Y.type(torch.int32))print('Y的有效长度:', Y_valid_len)break

🏷运行结果,注意每个批量使用了两行(batch_size = 2),每行需要8个词元(num_step = 8)

X: tensor([[ 7, 43,  4,  3,  1,  1,  1,  1],[44, 23,  4,  3,  1,  1,  1,  1]], dtype=torch.int32)
X的有效长度: tensor([4, 4])
Y: tensor([[ 6,  7, 40,  4,  3,  1,  1,  1],[ 0,  5,  3,  1,  1,  1,  1,  1]], dtype=torch.int32)
Y的有效长度: tensor([5, 3])

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mysql进行操作时锁的具体行为

场景一&#xff1a;单个事务更新一条存在的数据 假设有表 user (id PK, name, age)&#xff0c;数据&#xff1a;[id1, nameAlice, age25] 你的 SQL&#xff1a; UPDATE user SET age 26 WHERE id 1; 底层动作&#xff1a; 事务 A (主动方) 发起更新请求。Lock Manager 介入&…

人工智能领域、图欧科技、IMYAI智能助手2025年7月更新月报

IMYAI 平台 2025 年 7 月重要功能更新与优化汇总 2025年07月31日更新 细节优化&#xff1a; 修复了移动端提交后自动弹出侧边栏的BUG。优化对话高级配置界面&#xff0c;增加滚动条并固定高度&#xff0c;避免内容超出屏幕。音乐生成界面的人声选择新增“合唱”选项&#xff…

HTTP 与 HTTPS 的区别深度解析:从原理到实践

HTTP 和 HTTPS 是现代 Web 开发中不可或缺的协议&#xff0c;它们决定了浏览器与服务器之间数据传输的方式。HTTPS 作为 HTTP 的安全版本&#xff0c;在安全性、性能和用户体验上都有显著提升。本文将通过万字篇幅&#xff0c;结合图表和代码示例&#xff0c;详细剖析 HTTP 与 …

STM32F407VET6学习笔记11:smallmodbus_(多从机)创建新的slave从机

今日记录一些smallmodbus 创建新的slave 从机 的过程&#xff0c;以及使用的关键点. 目录 创建新的从机对应操作函数与buffer 创建新的从机线程与操作代码&#xff1a; slave使用的要点&#xff1a; 完整的slave代码&#xff1a; 能正常通信&#xff1a; 创建新的从机对应操作函…

【论文阅读】Transformer Feed-Forward Layers Are Key-Value Memories

Transformer Feed-Forward Layers Are Key-Value Memories 原文摘要 研究背景与问题&#xff1a; 前馈层占Transformer模型参数总量的2/3&#xff0c;但其功能机制尚未得到充分研究 核心发现&#xff1a;提出前馈层实质上是键值存储系统 键&#xff1a;这里的键与训练数据中出…

昇思+昇腾开发板:DeepSeek-R1-Distill-Qwen-1.5B 模型推理部署与 JIT 优化实践

目录 引言 模型推理部署 环境准备 安装 MindSpore 查看当前 mindspore 版本 安装 MindNLP 模型与分词器加载 导入必要的库 加载分词器 加载模型 对话功能实现 设置系统提示词 构建对话历史输入 推理函数实现 交互界面实现 推理JIT优化 基础环境安装 JIT 优化配置…

用phpstudy安装php8.2后报错:意思是找不到php_redis.dll拓展时

1.地址&#xff1a;https://pecl.php.net/package/redis/6.2.0/windows 2.下载3.解压后复制php_redis.dll到phpstudy_pro\Extensions\php\php8.2.9nts\ext目录 4.打开php.ini&#xff0c;加上 extension_dir “D:\software\phpstudy_pro\Extensions\php\php8.2.9nts\ext”

开源列式分布式数据库clickhouse

这里写自定义目录标题开源列式OLAP数据库clickhouseclickhouse使用 ClickHouse 的场景如何理解行式存储和列式存储clickhouse-go开源列式OLAP数据库clickhouse OLAP (分析型)&#xff1a;专为快速扫描、聚合、分析海量数据设计。OLTP (事务型)&#xff1a;专为处理大量短事务&…

Java Stream API 详解(Java 8+)

1. Stream 操作分类Stream 操作分为两类&#xff1a;中间操作&#xff08;Intermediate Operations&#xff09;返回新的 Stream&#xff0c;可以链式调用&#xff08;如 filter, map, sorted, distinct&#xff09;。惰性求值&#xff1a;只有遇到终止操作时才会执行。终止操作…

「源力觉醒 创作者计划」_文心大模型4.5系列开源模型, 从一行代码到一个生态:聊聊开源战略那些事儿,顺便扯扯文心大模型 4.5 的使用心得

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录从一行…

算法专题(二)回文链表

1、源代码class Solution {public boolean isPalindrome(ListNode head) {ListNode fasthead,slowhead; //快慢指针都在头结点//快指针走2步&#xff0c;慢指针走一步。//双数快指针最后是null&#xff0c;单数快指针下一位是nullwhile(fast!null && fast.next!null){f…

2025《艾诺提亚失落之歌》逆向工程解包尝试

前言 想开发一下光明之魂&#xff0c;看能不能解包《艾诺提亚失落之歌》的模型。 之前写了&#xff08;https://blog.csdn.net/weixin_42875245/article/details/148616547?spm1001.2014.3001.5501&#xff09; 沿用这个思路进行逆向工程解包。 文章目录请添加图片描述前言…

JVM 03 类加载机制

JVM 将字节码二进制流加载到内存称为类加载。 什么时候加载类 new 实例化对象。而对象所属类还没被加载。读取/设置类的静态非常量字段&#xff0c;常量字段在常量池。调用类的静态方法。类初始化&#xff0c;优先初始化父类。虚拟机启动时&#xff0c;先加载用户指定的主类。 …

STM32H7+FreeRTOS+LwIP移植EtherCAT开源主站SOEM

代码下载什么的就不多说了&#xff0c;直接看需要移植修改的代码。 1、osal.c修改 /******************************************************************************* * *** **** *** *** …

VijosOJ:中文信息学竞赛的二十年开源之路

VijosOJ&#xff1a;中文信息学竞赛领域的老牌开源在线判题系统 在中文编程教育与信息学竞赛的发展历程中&#xff0c;在线判题系统&#xff08;OJ&#xff09;扮演了至关重要的角色。它们不仅是选手训练的 “战场”&#xff0c;更是知识传递与社区交流的枢纽。VijosOJ&#x…

QPainter::CompositionMode解析

基本概念目标(Destination)&#xff1a;已经存在的像素。源(Source)&#xff1a;要绘制的新像素。组合模式&#xff1a;决定源和目标如何混合。总结SourceOver&#xff1a;源绘制在目标之上。DestinationOver&#xff1a;目标绘制在源之上。Clear&#xff1a;二者重叠区域被清空…

对接钉钉审批过程记录(C#版本)

钉钉开放平台&#xff1a;API总览 - 钉钉开放平台 按照开放平台操作指引&#xff0c;进入到钉钉开发者后台&#xff1a;开发者后台统一登录 - 钉钉统一身份认证&#xff0c;进行应用创建。 按照开放平台指引下载钉钉SDK&#xff08;新版&#xff09;。 在vs引入钉钉dll文件。 获…

AFSIM入门教程03.03:更新所有依赖库版本

系列索引&#xff1a;AFSIM入门教程索引 上一篇中更新了tiff库版本&#xff0c;本文将更新所有使用到的依赖库版本。 失败了 依赖库 首先获取哪些库被使用了。打开源码目录&#xff0c;搜索# Configure the 3rd_party&#xff0c;可以看到调用第三方库的代码。 官方提供的…

完美解决hive external表中csv字段内容含“,“逗号的问题

为解决hive表中csv字段内容含","逗号的问题&#xff0c;网上几乎都是说要用org.apache.hadoop.hive.serde2.OpenCSVSerde。 使用方法为&#xff1a; 1、mysql导出时&#xff0c;加一个ENCLOSED BY ‘"’&#xff0c; 示例&#xff1a; mysql -h 10.16.0.10 -P …

【Git】修改本地和远程的分支名称

其原理是&#xff1a; 对于本地&#xff1a;可直接修改分支名称&#xff1b;对于远程&#xff1a;不可直接重命名分支&#xff0c;所以应该将修改好名称的分支以新分支的形式推送上远程仓库&#xff0c;之后将新分支与远程新分支关联&#xff0c;之后可选择删除旧分支# 例子&am…