機器翻譯 Transformer代碼筆記

(未完)
Transformer論文及框架分析:機器翻譯Transformer框架分析筆記 | Attention is all you need
本文代碼來源Github:kyubyong/transformer/tf1.2_legacy
作者已更新較新版本tensorflow對應的transformer代碼,本筆記基於老代碼
做筆記使用

代碼 介紹
hyperhparams.py 超參數設定
prepro.py 生成字典
data_load.py 格式化數據,生成batch
modules.py 網絡模型
train.py 訓練
eval.py 評估

代碼1:hyperparams.py 定義超參數文件

# -*- coding: utf-8 -*-
#/usr/bin/python2
'''
June 2017 by kyubyong park. 
[email protected].
https://www.github.com/kyubyong/transformer
'''
class Hyperparams: #超參數
    '''Hyperparameters'''
    # data 訓練集與測試集
    source_train = 'corpora/train.tags.de-en.de'
    target_train = 'corpora/train.tags.de-en.en'
    source_test = 'corpora/IWSLT16.TED.tst2014.de-en.de.xml'
    target_test = 'corpora/IWSLT16.TED.tst2014.de-en.en.xml'
    
    # training
    #batch_size調參重點
    #mini-batch gradient decent,小批的梯度下降,這種方法把數據分爲若干個批,按批來更新參數,這樣,一個批中的一組數據共同決定了本次梯度的方向,下降起來就不容易跑偏,減少了隨機性。
    batch_size = 32 # alias = N 在實際機翻訓練過程中batch_size一般設置從4000—8000不等,要具體情況具體分析
    lr = 0.0001 # learning rate. In paper, learning rate is adjusted to the global step.
    # 在實際訓練中,一般動態設置學習率,從大到小以達到細分精度找到“最優解”
    logdir = 'logdir' # log directory
    
    # model
    maxlen = 10 # alias = T. 單詞最大長度,實習訓練中忽略此項的限制
                # Feel free to increase this if you are ambitious.
    #min_cnt調參
    min_cnt = 20 # words whose occurred less than min_cnt are encoded as <UNK>.
    #調參重點
    hidden_units = 512 # alias = C 隱藏節點
    num_blocks = 6 # number of encoder/decoder blocks
    num_epochs = 20 #迭代20次所有樣本
    num_heads = 8 #多頭注意力機制中的層數H
    dropout_rate = 0.1 #殘差丟棄正則化,根據實際情況可繼續增大
    sinusoid = False # If True, use sinusoid. If false, positional embedding. 不使用正弦曲線

代碼2:prepro.py 生成詞彙表

# -*- coding: utf-8 -*-
#/usr/bin/python2
'''
June 2017 by kyubyong park. 
[email protected].
https://www.github.com/kyubyong/transformer
'''
from __future__ import print_function #本機python2,使用python3的print()函數
from hyperparams import Hyperparams as hp #超參數
import tensorflow as tf
import numpy as np
import codecs #使用codecs.open()讀寫文件,避免編碼不統一報錯
import os
import regex #正則表達式
from collections import Counter #計數器

def make_vocab(fpath, fname): #生成詞彙表
    '''Constructs vocabulary.
    
    Args:
      fpath: A string. Input file path. 輸入路徑,訓練集
      fname: A string. Output file name. 輸出路徑,詞彙表
    
    Writes vocabulary line by line to `preprocessed/fname`
    '''  
    text = codecs.open(fpath, 'r', 'utf-8').read() #用unicode編碼方式讀取
    text = regex.sub("[^\s\p{Latin}']", "", text) #正則表達式,只保留英文單詞
    words = text.split()
    word2cnt = Counter(words) #計數器,輸出詞典:key=單詞,value=個數
    if not os.path.exists('preprocessed'): os.mkdir('preprocessed') #輸出路徑
    #使用with語句:不用close(),同時避免異常
    #str.format()格式化函數,類似於print('',% )中的%
    with codecs.open('preprocessed/{}'.format(fname), 'w', 'utf-8') as fout:
        #先寫入四個特殊詞
        #<PAD>主要用來進行字符補全,編號0
        #<UNK>未登錄詞/低頻詞,編號1
        #<S>句子開始的標識,編號2
        # </S>句子結尾的標識,編號3
        fout.write("{}\t1000000000\n{}\t1000000000\n{}\t1000000000\n{}\t1000000000\n".format("<PAD>", "<UNK>", "<S>", "</S>"))
        #collections.Counter.most_common(N)按照頻次從大到小排列詞典,只顯示前N個單詞。
        for word, cnt in word2cnt.most_common(len(word2cnt)):
            fout.write(u"{}\t{}\n".format(word, cnt)) #按照 單詞\t頻次\n來寫入

if __name__ == '__main__':
    make_vocab(hp.source_train, "de.vocab.tsv")
    make_vocab(hp.target_train, "en.vocab.tsv") #兩個詞彙表
    print("Done")

代碼3:data_load.py 格式化數據,生成batch

# -*- coding: utf-8 -*-
#/usr/bin/python2
'''
June 2017 by kyubyong park. 
[email protected].
https://www.github.com/kyubyong/transformer
'''
from __future__ import print_function
from hyperparams import Hyperparams as hp
import tensorflow as tf
import numpy as np
import codecs
import regex

#詞彙錶轉化爲字典格式,並刪除頻次較低的
def load_de_vocab(): #德語
    # splitlines()按行切片(\n,\r,\r\n);line.split()[0]將遍歷好的每一行(一個單詞一個頻次)列表化,並取第0個元素/單詞;如果單詞的頻次大於我們在hyperhparams.py中設定的參數就保存該單詞到一個列表中
    vocab = [line.split()[0] for line in codecs.open('preprocessed/de.vocab.tsv', 'r', 'utf-8').read().splitlines() if int(line.split()[1])>=hp.min_cnt]
    word2idx = {word: idx for idx, word in enumerate(vocab)} #轉換爲字典的形式進行保存單詞,並給每個單詞進行編號
    idx2word = {idx: word for idx, word in enumerate(vocab)}
    return word2idx, idx2word

def load_en_vocab(): #英語
    vocab = [line.split()[0] for line in codecs.open('preprocessed/en.vocab.tsv', 'r', 'utf-8').read().splitlines() if int(line.split()[1])>=hp.min_cnt]
    word2idx = {word: idx for idx, word in enumerate(vocab)}
    idx2word = {idx: word for idx, word in enumerate(vocab)}
    return word2idx, idx2word
'''
舉例:
word2idx ={'<PAD>': 0, '<UNK>': 1, '<STR>': 2, '<EOS>': 3, '有': 4, 
'的': 5, '`': 6, '-': 7, '卦': 8, '八': 9, ..., '爬': 1642, 'U': 1643}
idx2word={{0: '<PAD>', 1: '<UNK>', 2: '<STR>', 3: '<EOS>', 4: '有', 
5: '的', 6: '`', 7: '-', 8: '卦', 9: '八', ..., 1642: '爬', 1643: 'U'}}
'''

#數據處理
def create_data(source_sents, target_sents): #source_sents存放源語言句子的列表, target_sents目標語言句子
    de2idx, idx2de = load_de_vocab()
    en2idx, idx2en = load_en_vocab()
    
    # Index 索引
    x_list, y_list, Sources, Targets = [], [], [], []
    for source_sent, target_sent in zip(source_sents, target_sents): #使用zip()函數同時遍歷兩個句子列表
        #x,y 一個新句子
        x = [de2idx.get(word, 1) for word in (source_sent + u" </S>").split()] # 1: OOV, </S>: End of Text
        y = [en2idx.get(word, 1) for word in (target_sent + u" </S>").split()] #給每一個句子的末尾加上</s>終止符,並遍歷句子中的每一個單詞,將已經存在於word2idx中的那個單詞對應的ID添加到新的列表中,如果這個單詞不存在於word2idx中,那麼就返回 ID‘1’到新列表中組成一個新的‘ID句子’(其中1代表<UNK>)
        if max(len(x), len(y)) <= hp.maxlen: #我們在hyperhparams.py中設置的最大句子長度
            x_list.append(np.array(x)) #源語言ID句子
            y_list.append(np.array(y)) #目標語言ID句子
            Sources.append(source_sent) #源語言句子
            Targets.append(target_sent) #目標語言句子
            #超過長度閾值的丟棄
    
    # Pad 填充 對應site特殊詞中的編號0:<PAD>
    X = np.zeros([len(x_list), hp.maxlen], np.int32) #二維0矩陣:句子個數*最大句長
    Y = np.zeros([len(y_list), hp.maxlen], np.int32)
    for i, (x, y) in enumerate(zip(x_list, y_list)):
        #保證每個ID句子的長度/元素個數都是相同的
        X[i] = np.lib.pad(x, [0, hp.maxlen-len(x)], 'constant', constant_values=(0, 0)) #對每一個ID句子做填充,左側填充0個0,右側填充hp.maxlen-len(x)個0,並且0也是四個特殊詞中的一個:<PAD>編號0
        Y[i] = np.lib.pad(y, [0, hp.maxlen-len(y)], 'constant', constant_values=(0, 0))
    
    return X, Y, Sources, Targets #X和Y的shape爲[len(x_list), hp.maxlen],Sources, Targets的shape爲[1, len(x_list)]

#加載訓練集,對訓練集做數據處理,返回定長ID句子
def load_train_data():
    de_sents = [regex.sub("[^\s\p{Latin}']", "", line) for line in codecs.open(hp.source_train, 'r', 'utf-8').read().split("\n") if line and line[0] != "<"] #不加載特殊詞
    en_sents = [regex.sub("[^\s\p{Latin}']", "", line) for line in codecs.open(hp.target_train, 'r', 'utf-8').read().split("\n") if line and line[0] != "<"]
    
    X, Y, Sources, Targets = create_data(de_sents, en_sents)
    return X, Y

#加載測試集,對測試集做數據處理,返回定長ID句子
def load_test_data():
    def _refine(line):
        line = regex.sub("<[^>]+>", "", line) #刪除所有非空的< >項
        line = regex.sub("[^\s\p{Latin}']", "", line) 
        return line.strip() #刪除字符串首尾的指定字符(默認爲空格)
    #讀取源語言與目標語言文本,切片並處理;將句子開頭前4個字符爲<seg的保存到新列表中
    de_sents = [_refine(line) for line in codecs.open(hp.source_test, 'r', 'utf-8').read().split("\n") if line and line[:4] == "<seg"]
    en_sents = [_refine(line) for line in codecs.open(hp.target_test, 'r', 'utf-8').read().split("\n") if line and line[:4] == "<seg"]
        
    X, Y, Sources, Targets = create_data(de_sents, en_sents) #數據處理爲定長ID句子
    return X, Sources, Targets # (1064, 150) 測試集不需要處理目標語言爲ID句子

#生成batch數據,每次得到一個batch
def get_batch_data():
    # Load data
    X, Y = load_train_data() #訓練集定長ID句子

    # calc total batch count
    num_batch = len(X) // hp.batch_size #需要幾個batch來表示總數據,len(X)表示行數/句子個數
    
    # Convert to tensor
    #將python的數據類型轉換成TensorFlow可用的tensor數據類型
    X = tf.convert_to_tensor(X, tf.int32)
    Y = tf.convert_to_tensor(Y, tf.int32)
    
    # Create Queues
    #創建文件名隊列
    #從輸入中每次取一個切片返回到一個輸入隊列裏,該隊列作爲之後tf.train.shuffle_batch的一個參數,用以生成!一個!batch的數據。
    input_queues = tf.train.slice_input_producer([X, Y])
            
    # create batch queues
    #隊頭出隊,隊尾補充數據入隊,打亂順序;num_threads多線程入隊,batch_size每次從隊列中出隊的數據數量,
    #capacity隊列中最大元素數量,min_after_dequeue隊列中最少存在的元素數量,allow_smaller_final_batch隊列中最後小於batch_size的樣本不進行出隊
    x, y = tf.train.shuffle_batch(input_queues,
                                num_threads=8,
                                batch_size=hp.batch_size, 
                                capacity=hp.batch_size*64,   
                                min_after_dequeue=hp.batch_size*32, 
                                allow_smaller_final_batch=False)
    #不過現在好像都改用tf.data下的函數進行數據處理了
    return x, y, num_batch # shape分別爲(N, T), (N, T);N爲batch_size的大小,T爲最大句子長度maxlen;一個batch
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章