跳轉至

基於術語字典幹預的機器翻譯

AI +X | Datawhale 學習指南:https://datawhaler.feishu.cn/wiki/X9AVwtmvyi87bIkYpi2cNGlIn3v

機器翻譯的發展歷程:基於規則的機器翻譯(1950s-1980s)->基於統計的機器翻譯(1990s-2000s)->**基於神經網路機器翻譯(2010s-present) **

資料劃分

在機器學習和深度學習專案中,資料集通常被分割為三個部分:訓練集(Training Set)、開發集(Development Set,也常被稱為驗證集,Validation Set )和測試集(Test Set)

task-1_terminology.ipynb解釋

類別 TranslationDataset

__init__ 方法

  1. 初始化資料和術語
  2. self.data = []:初始化一個空列表,用於儲存從檔案中讀取的資料。
  3. with open(filename, 'r', encoding='utf-8') as f::開啟檔案讀取數據,假設檔案中的每一行包含一對中英文翻譯,使用製表符(\ t)分隔。
  4. self.data.append((en, zh)):將讀取的中英文對加入 self.data 清單。
  5. self.terminology = terminology:儲存傳入的術語字典。
  6. 建立詞彙表
  7. self.en_tokenizer = get_tokenizer('basic_english'):使用 torchtext 提供的基本英文分詞器。
  8. self.zh_tokenizer = list:使用字元級分詞器來處理中文。
  9. 建構詞彙表
  10. en_vocab = Counter(self.terminology.keys()):初始化英文詞彙表,確保術語字典中的單字被包含。
  11. zh_vocab = Counter():初始化中文詞彙表。
  12. 更新詞彙表
  13. 遍歷資料中的每一對句子,更新英文和中文的詞彙表。
  14. 添加特殊符號和術語到詞彙表
  15. self.en_vocabself.zh_vocab:添加特殊符號(<pad><sos><eos>)和術語詞典中的詞,接著添加最常見的10000個詞。
  16. self.en_word2idxself.zh_word2idx:建立從單字到索引的映射字典。

__len__ 方法

  • 傳回資料集的大小。

__getitem__ 方法

  • 取得資料集中指定索引的中英文對,並將其轉換為張量(tensor)。
  • 將特殊符號 <eos> 加到每個句子的最後。
  • 使用詞彙表將每個單字轉換為索引,預設情況下,如果單字不在詞彙表中,使用 <sos> 索引。

collat​​e_fn 函數

  • 這個函式用於在 DataLoader 中進行批次處理。
  • 批次填充
  • en_batchzh_batch:分別儲存英文和中文句子的張量。
  • 使用 nn.utils.rnn.pad_sequence 對批次中的序列進行填充,確保每個序列的長度相同。填充值為0(即 <pad> 索引)。
# 定義資料集類
# 修改TranslationDataset類別以處理術語
class TranslationDataset(Dataset):
 def __init__(self, filename, terminology):
 self.data = []
 with open(filename, 'r', encoding='utf-8') as f:
 for line in f:
 en, zh = line.strip().split('\t')
 self.data.append((en, zh))

 self.terminology = terminology

 # 建立詞彙表,注意這裡需要確保術語字典中的單字也被包含在詞彙表中
 self.en_tokenizer = get_tokenizer('basic_english')
 self.zh_tokenizer = list # 使用字元級分詞

 en_vocab = Counter(self.terminology.keys()) # 確保術語在詞彙表中
 zh_vocab = Counter()

 for en, zh in self.data:
 en_vocab.update(self.en_tokenizer(en))
 zh_vocab.update(self.zh_tokenizer(zh))

 # 新增術語到詞彙表
 self.en_vocab = ['<pad>', '<sos>', '<eos>'] + list(self.terminology.keys()) + [word for word, _ in en_vocab.most_common(10000)]
 self.zh_vocab = ['<pad>', '<sos>', '<eos>'] + [word for word, _ in zh_vocab.most_common(10000)]

 self.en_word2idx = {word: idx for idx, word in enumerate(self.en_vocab)}
 self.zh_word2idx = {word: idx for idx, word in enumerate(self.zh_vocab)}


 def __len__(self):
 return len(self.data)

 def __getitem__(self, idx):
 en, zh = self.data[idx]
 en_tensor = torch.tensor([self.en_word2idx.get(word, self.en_word2idx['<sos>']) for word in self.en_tokenizer(en)] + [self.en_word2idx['<eos>']])
 zh_tensor = torch.tensor([self.zh_word2idx.get(word, self.zh_word2idx['<sos>']) for word in self.zh_tokenizer(zh)] + [self.zh_word2idx['<eos>'])
 return en_tensor, zh_tensor

def collat​​e_fn(batch):
 en_batch, zh_batch = [], []
 for en_item, zh_item in batch:
 en_batch.append(en_item)
 zh_batch.append(zh_item)

 # 將英文和中文序列分別進行填充
 en_batch = nn.utils.rnn.pad_sequence(en_batch, padding_value=0, batch_first=True)
 zh_batch = nn.utils.rnn.pad_sequence(zh_batch, padding_value=0, batch_first=True)

 return en_batch, zh_batch

Encoder

__init__ 方法

  • 初始化編碼器的各層和參數:
  • self.embedding:定義一個嵌入層,將輸入的索引轉換為對應的嵌入向量。
  • self.rnn:定義一個具有多層和 dropout 的 GRU(門控循環單元)層,用於處理嵌入向量。
  • self.dropout:定義一個 dropout 層,用於正規化以防止過度擬合。

forward 方法

  • 輸入 src(形狀為 [batch_size, src_len]),表示批次中的來源序列。
  • 經過嵌入層和 dropout 處理,得到嵌入向量 embedded(形狀為 [batch_size, src_len, emb_dim])。
  • 經過 GRU 層,得到 outputs(形狀為 [batch_size, src_len, hid_dim])和 hidden(形狀為 [n_layers, batch_size, hid_dim])。
  • 回傳 outputshidden

Decoder

__init__ 方法

  • 初始化解碼器的各個層和參數:
  • self.embedding:定義一個嵌入層,將輸入的索引轉換為對應的嵌入向量。
  • self.rnn:定義一個具有多層和 dropout 的 GRU 層,用於處理嵌入向量。
  • self.fc_out:定義一個全連接層,用於將 GRU 的輸出對應到目標詞彙表的大小。
  • self.dropout:定義一個 dropout 層,用於正規化。

forward 方法

  • 輸入 input(形狀為 [batch_size, 1]),表示批次中的目標序列的當前時間步的單字索引。
  • 輸入 hidden(形狀為 [n_layers, batch_size, hid_dim]),表示編碼器的隱藏狀態。
  • 經過嵌入層和 dropout 處理,得到嵌入向量 embedded(形狀為 [batch_size, 1, emb_dim])。
  • 經過 GRU 層,得到 output(形狀為 [batch_size, 1, hid_dim])和 hidden(形狀為 [n_layers, batch_size, hid_dim])。
  • 經過全連接層,得到預測 prediction(形狀為 [batch_size, output_dim])。
  • 回傳 predictionhidden

Seq2Seq

__init__ 方法

  • 初始化 seq2seq 模型的編碼器、解碼器和裝置(如 CPU 或 GPU)。

forward 方法

  • 輸入 src(形狀為 [batch_size, src_len])和 trg(形狀為 [batch_size, trg_len]),表示批次中的來源序列和目標序列。
  • 初始化 outputs 張量,用於儲存每個時間步的解碼器輸出(形狀為 [batch_size, trg_len, trg_vocab_size])。
  • 使用編碼器對來源序列進行編碼,得到 hidden(形狀為 [n_layers, batch_size, hid_dim])。
  • 初始化解碼器的輸入為目標序列的起始標記(trg[:, 0].unsqueeze(1))。
  • 對於目標序列的每個時間步,使用解碼器產生輸出和新的隱藏狀態:
  • 使用 teacher_forcing_ratio 決定是否使用真實的目標序列字作為下一時間步驟的輸入(教師強制),或使用解碼器的預測。
  • 傳回 outputs,即每個時間步的預測結果。
class Encoder(nn.Module):
 def __init__(self, input_dim, emb_dim, hid_dim, n_layers, dropout):
 super().__init__()
 self.embedding = nn.Embedding(input_dim, emb_dim)
 self.rnn = nn.GRU(emb_dim, hid_dim, n_layers, dropout=dropout, batch_first=True)
 self.dropout = nn.Dropout(dropout)

 def forward(self, src):
 # src shape: [batch_size, src_len]
 embedded = self.dropout(self.embedding(src))
 # embedded shape: [batch_size, src_len, emb_dim]
 outputs, hidden = self.rnn(embedded)
 # outputs shape: [batch_size, src_len, hid_dim]
 # hidden shape: [n_layers, batch_size, hid_dim]
 return outputs, hidden


class Decoder(nn.Module):
    def __init__(self, output_dim, emb_dim, hid_dim, n_layers, dropout):
        super().__init__()
        self.output_dim = output_dim
        self.embedding = nn.Embedding(output_dim, emb_dim)
        self.rnn = nn.GRU(emb_dim, hid_dim, n_layers, dropout=dropout, batch_first=True)
        self.fc_out = nn.Linear(hid_dim, output_dim)
        self.dropout = nn.Dropout(dropout)

    def forward(self, input, hidden):
        # input shape: [batch_size, 1]
        # hidden shape: [n_layers, batch_size, hid_dim]

        embedded = self.dropout(self.embedding(input))
        # embedded shape: [batch_size, 1, emb_dim]

        output, hidden = self.rnn(embedded, hidden)
        # output shape: [batch_size, 1, hid_dim]
        # hidden shape: [n_layers, batch_size, hid_dim]

        prediction = self.fc_out(output.squeeze(1))
        # prediction shape: [batch_size, output_dim]

        return prediction, hidden

class Seq2Seq(nn.Module):
    def __init__(self, encoder, decoder, device):
        super().__init__()
        self.encoder = encoder
        self.decoder = decoder
        self.device = device

    def forward(self, src, trg, teacher_forcing_ratio=0.5):
        # src shape: [batch_size, src_len]
        # trg shape: [batch_size, trg_len]

        batch_size = src.shape[0]
        trg_len = trg.shape[1]
        trg_vocab_size = self.decoder.output_dim

        outputs = torch.zeros(batch_size, trg_len, trg_vocab_size).to(self.device)

        _, hidden = self.encoder(src)

        input = trg[:, 0].unsqueeze(1)  # Start token

        for t in range(1, trg_len):
            output, hidden = self.decoder(input, hidden)
            outputs[:, t, :] = output
            teacher_force = random.random() < teacher_forcing_ratio
            top1 = output.argmax(1)
            input = trg[:, t].unsqueeze(1) if teacher_force else top1.unsqueeze(1)

        return outputs

load_terminology_dictionary

函數定義與參數

  • def load_terminology_dictionary(dict_file)::定義一個函數,接受一個參數 dict_file,表示包含術語字典的檔案路徑。

初始化術語詞典

  • terminology = {}:初始化一個空字典 terminology,用於儲存術語字典。

開啟檔案並讀取內容

  • with open(dict_file, 'r', encoding='utf-8') as f::使用 utf-8 編碼開啟檔案進行讀取操作。
  • for line in f::逐行讀取檔案內容。

解析並儲存術語對

  • en_term, ch_term = line.strip().split('\t'):移除每行首尾的空白字符,然後使用製表符(\t)將每行分割成英文術語和中文術語。
  • terminology[en_term] = ch_term:將英文術語作為鍵,中文術語作為值,儲存到字典 terminology 中。

傳回術語詞典

  • return terminology:傳回載入好的術語字典。

python # 新增術語字典載入部分 def load_terminology_dictionary(dict_file): terminology = {} with open(dict_file, 'r', encoding='utf-8') as f: for line in f: en_term, ch_term = line.strip().split('\t') terminology[en_term] = ch_term return terminology

主函數

這個函數透過批次資料訓練 seq2seq 模型,並傳回平均訓練損失。主要步驟包括前向傳播、損失計算、反向傳播、梯度裁剪和參數更新。可以在每個訓練輪次(epoch)中呼叫這個函數以訓練模型。

train

參數

  • model:要訓練的 seq2seq 模型。
  • iterator:資料載入器,用於迭代資料批次。
  • optimizer:優化器,用於更新模型參數。
  • criterion:損失函數,用於計算預測值和真實值之間的誤差。
  • clip:梯度裁切的閾值,用於防止梯度爆炸。

函數內容

  1. 設定模型為訓練模式
 model.train()
 ```
 這會啟用 dropout 和 batch normalization。

2. **初始化累積損失**:
```python
 epoch_loss = 0
 ```

3. **迭代資料批次**:
```python
 for i, (src, trg) in enumerate(iterator):
 ```
 使用資料載入器 `iterator` 取得每個批次的來源序列 `src` 和目標序列 `trg`。

4. **將資料移到裝置(CPU 或 GPU)**:
```python
 src, trg = src.to(device), trg.to(device)
 ```

5. **梯度清零**:
```python
 optimizer.zero_grad()
 ```

6. **前向傳播**:
```python
 output = model(src, trg)
 ```

7. **調整輸出和目標形狀**:
```python
 output_dim = output.shape[-1]
 output = output[:, 1:].contiguous().view(-1, output_dim)
 trg = trg[:, 1:].contiguous().view(-1)
 ```
 - `output[:, 1:]`:移除 `<sos>` token。
 - `contiguous().view(-1, output_dim)`:調整 `output` 的形狀以適應損失計算。
 - `trg[:, 1:]`:移除 `<sos>` token。
 - `contiguous().view(-1)`:調整 `trg` 的形狀以適應損失計算。

8. **計算損失**:
```python
 loss = criterion(output, trg)
 ```

9. **反向傳播**:
```python
 loss.backward()
 ```

10. **梯度裁剪**:
```python
 torch.nn.utils.clip_grad_norm_(model.parameters(), clip)
 ```
 這可以防止梯度爆炸。

11. **更新模型參數**:
```python
 optimizer.step()
 ```

12. **累積損失**:
```python
 epoch_loss += loss.item()
 ```

#### 傳回平均損失
『`python
return epoch_loss / len(iterator)

將累積損失除以迭代次數,傳回平均每批的損失。

Task02

image-20240717200826293

第二次訓練使用了第二份程式碼來運行,訓練了兩次但效果不佳哈哈哈,分數現在還沒有評出來。對於我這種只是大概知道要劃分訓練集測試集的崽來講,具體訓練的方法還是曉得哈哈哈,於是這份筆記補充了一些資料。

首先是需要額外安裝的幾個套件

torchtext

PyTorch 的一個擴充庫,專門用於自然語言處理 (NLP) 任務。提供了一組工具和資料處理管線,用於處理文字數據,建立詞彙表,載入預先訓練的字詞嵌入,以及建立和操作文字資料集。

資料載入和預處理

  • 提供了從常見的文字格式(如 CSV、JSON、TSV 等)載入資料的功能。
  • 支援文字資料的分詞、標記化、數位化等預處理操作。

詞彙表

  • 允許建立和管理詞彙表,包括計算詞頻、過濾低頻詞和創建詞到索引的映射。
  • 支援從預訓練的嵌入(如 GloVe, FastText)中載入詞向量。

嵌入表示

  • 方便地載入和使用預訓練的詞嵌入。
  • 支援自訂字詞嵌入的訓練和微調。

資料集與迭代器

  • 提供了常用 NLP 資料集的預定義載入器,如 IMDb, AG News, SQuAD 等。
  • 提供了一個靈活的資料迭代器,支援批次和動態填充,方便與 PyTorch 的資料載入器整合。

Jieba

是一個非常受歡迎的中文分詞工具庫,它能夠有效率地將中文文字切分成字詞。由於中文沒有明顯的詞邊界,分詞是自然語言處理 (NLP) 中的一個重要步驟。 Jieba 提供了以下主要功能:

  1. 精確模式:試圖將句子最精確地切開,適合文本分析。
  2. 全模式:把句子中所有可能的字詞都掃描出來,速度非常快,但是不能解決歧義。
  3. 搜尋引擎模式:在精確模式的基礎上,對長詞再次切分,提高召回率,適合用於搜尋引擎分詞。

SacreBLEU

是一個標準化且便利的 BLEU 分數計算工具,主要用於機器翻譯評估。

我們該次比賽的評分規則似乎是基於BLUE來的

隨後是資料的預處理,清洗工作,訓練驗證模型。這是之前照貓畫虎的一個影像辨識系統

image-20240717204005759

對於資料量較大的模型(樣本數量達到萬級以上),對訓練集,驗證集,測試集的劃分一般為為98:1:1,而萬級以下資料的訓練,最典型的劃分比例是6:2:2。劃分資料如下圖所示。 image-20240717204102240

循環神經網路、卷積神經網路、自註意力機制等都是模型常用的結構。

Task03

在task3的模型中,運用了Transformer ,Transformer基於循環卷積神經網路的序列到序列建模方法是現存機器翻譯任務中的經典方法,對於經典的捲積神經網絡

卷積神經網路演算法作為一種經典的深度學習演算法,又被稱為CNN演算法.,最初是用來針對手寫數字識別的,該是由多個層組成,主要包括輸入層、卷積層、池化層、全連接層和輸出層。

輸入層代表輸入的數據,它可以是一維也可以是多維;卷積層在整個網絡結構當中的位置處於最關鍵的位置,每個卷積層是由若干個卷積核組成,透過卷積運算對輸入層的特徵進行提取,並將其傳遞到下一層網路;卷積層後一般會加入池化層,池化層的作用是透過下採樣的操作,將特徵劃分為多個矩陣,並在每個矩陣中取相應的特徵值,來減少資料的維度而不改變資料的深度,從而達到減少參數數量提高運算速率的目的。具有平移不變性的特性;全連接層處於整個網路結構的尾端,在整個卷積神經網路模型當中扮演「分類器」的角色。全連接層透過對卷積層或池化層中具有類別區分性的局部資訊進行整合,將前一層的特徵資訊從高維空間轉到低維空間,並將前一層的特徵資訊轉換成輸出值,同時可以透過softmax邏輯迴歸進行對輸出值分類,該層也可稱為softmax 層。

img

而對於Transformer,一張十分景點的圖片展現了它的魅力:

cac07437-283a-4e79-a5a4-c2b40c2dc405

在調整過程中,最簡單的就是調參,將 epochs 調大一點,使用全部訓練集,以及調整模型的參數,如head、layers等。如果資料量允許,增加模型的深度(更多的編碼器/解碼器層)或寬度(更大的隱藏層尺寸),這通常可以提高模型的表達能力和翻譯質量,尤其是在處理複雜或專業內容時。