《動手學深度學習》讀書筆記—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
英文地址,請注明出處:http://en.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…