[tf]使用attention機(jī)制進(jìn)行NMT

進(jìn)行梯度裁剪

  • MAX_GRAD_NORM = 5裁剪梯度的計(jì)算值使其不要大于5,tf.trainable_variables返回的是需要訓(xùn)練的變量列表。 tf.all_variables返回的是所有變量的列表。
trainable_variables = tf.trainable_variables()
grads = tf.gradients(cost / tf.to_float(batch_size),
                     trainable_variables)
grads, _ = tf.clip_by_global_norm(grads, MAX_GRAD_NORM)
optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0)
train_op = optimizer.apply_gradients(
    zip(grads, trainable_variables))

tf.gradients(ys,xs)是求ys關(guān)于xs的導(dǎo)數(shù)。和tf.compute_gradients(ys)完全沒有區(qū)別

tf.gradients(ys, xs, 
             grad_ys=None, 
             name='gradients',
             colocate_gradients_with_ops=False,
             gate_gradients=False,
             aggregation_method=None,
             stop_gradients=None)
global_step = tf.Variable(0, name="global_step", trainable=False)
optim = tf.train.AdamOptimizer(learning_rate=FLAGS.learning_rate)
grads_and_vars = optim.compute_gradients(loss)
grads_and_vars_clip = [[tf.clip_by_value(g,-FLAGS.clip_grad,FLAGS.clip_grad), v] for g, v in grads_and_vars]
train_op = optim.apply_gradients(grads_and_vars_clip, global_step=global_step)

加載數(shù)據(jù)訓(xùn)練

  • 加載數(shù)據(jù)的方式也很優(yōu)秀。iterator.get_next()獲得一個(gè)epoch的數(shù)據(jù),但是每次sess.run的時(shí)候只能讀取一個(gè)batch的數(shù)據(jù),相當(dāng)于使用placeholder每次喂一個(gè)batch的數(shù)據(jù)。
# 定義輸入數(shù)據(jù)。
data = MakeSrcTrgDataset(SRC_TRAIN_DATA, TRG_TRAIN_DATA, BATCH_SIZE)
iterator = data.make_initializable_iterator()
(src, src_size), (trg_input, trg_label, trg_size) = iterator.get_next()

# 定義前向計(jì)算圖。輸入數(shù)據(jù)以張量形式提供給forward函數(shù)。
cost_op, train_op = train_model.forward(src, src_size, trg_input,
                                        trg_label, trg_size)

進(jìn)行訓(xùn)練

  • sess.run(iterator.initializer)的時(shí)候重新獲取一個(gè)epoch的數(shù)據(jù)。
  • 感覺這個(gè)方式很優(yōu)秀啊,不用定義什么亂七八糟的tf.Graph().as_default()tf.Sess().as_default()
saver = tf.train.Saver()
step = 0
with tf.Session() as sess:
    tf.global_variables_initializer().run()
    for i in range(NUM_EPOCH):
        print("In iteration: %d" % (i + 1))
        sess.run(iterator.initializer)
        step = run_epoch(sess, cost_op, train_op, saver, step)
  • 訓(xùn)練的話也是一個(gè)epoch一個(gè)epoch 的進(jìn)行訓(xùn)練,使用except tf.errors.OutOfRangeError。
def run_epoch(session, cost_op, train_op, saver, step):
    # 訓(xùn)練一個(gè)epoch。
    # 重復(fù)訓(xùn)練步驟直至遍歷完Dataset中所有數(shù)據(jù)。
    while True:
        try:
            # 運(yùn)行train_op并計(jì)算損失值。訓(xùn)練數(shù)據(jù)在main()函數(shù)中以Dataset方式提供。
            cost, _ = session.run([cost_op, train_op])
            if step % 10 == 0:
                print("After %d steps, per token cost is %.3f" % (step, cost))
            # 每200步保存一個(gè)checkpoint。
            if step % 200 == 0:
                saver.save(session, CHECKPOINT_PATH, global_step=step)
            step += 1
        except tf.errors.OutOfRangeError:
            break
    return step
  • model的運(yùn)行句柄和loss句柄放在外面,然后傳遞給run_epoch函數(shù)。
cost_op, train_op = train_model.forward(src, src_size, trg_input,trg_label, trg_size)

整個(gè)流程

import tensorflow as tf
# 假設(shè)輸入數(shù)據(jù)已經(jīng)用9.2.1小節(jié)中的方法轉(zhuǎn)換成了單詞編號的格式。
SRC_TRAIN_DATA = "./train.en"          # 源語言輸入文件。
TRG_TRAIN_DATA = "./train.zh"          # 目標(biāo)語言輸入文件。
CHECKPOINT_PATH = "./attention_ckpt"   # checkpoint保存路徑。  

HIDDEN_SIZE = 1024                     # LSTM的隱藏層規(guī)模。
DECODER_LAYERS = 2                     # 解碼器中LSTM結(jié)構(gòu)的層數(shù)。這個(gè)例子中編碼器固定使用單層的雙向LSTM。
SRC_VOCAB_SIZE = 10000                 # 源語言詞匯表大小。
TRG_VOCAB_SIZE = 4000                  # 目標(biāo)語言詞匯表大小。
BATCH_SIZE = 100                       # 訓(xùn)練數(shù)據(jù)batch的大小。
NUM_EPOCH = 5                          # 使用訓(xùn)練數(shù)據(jù)的輪數(shù)。
KEEP_PROB = 0.8                        # 節(jié)點(diǎn)不被dropout的概率。
MAX_GRAD_NORM = 5                      # 用于控制梯度膨脹的梯度大小上限。
SHARE_EMB_AND_SOFTMAX = True           # 在Softmax層和詞向量層之間共享參數(shù)。

MAX_LEN = 50   # 限定句子的最大單詞數(shù)量。
SOS_ID  = 1    # 目標(biāo)語言詞匯表中<sos>的ID。

# 使用Dataset從一個(gè)文件中讀取一個(gè)語言的數(shù)據(jù)。
# 數(shù)據(jù)的格式為每行一句話,單詞已經(jīng)轉(zhuǎn)化為單詞編號。
def MakeDataset(file_path):
    dataset = tf.data.TextLineDataset(file_path)
    # 根據(jù)空格將單詞編號切分開并放入一個(gè)一維向量。
    dataset = dataset.map(lambda string: tf.string_split([string]).values)
    # 將字符串形式的單詞編號轉(zhuǎn)化為整數(shù)。
    dataset = dataset.map(
        lambda string: tf.string_to_number(string, tf.int32))
    # 統(tǒng)計(jì)每個(gè)句子的單詞數(shù)量,并與句子內(nèi)容一起放入Dataset中。
    dataset = dataset.map(lambda x: (x, tf.size(x)))
    return dataset

# 從源語言文件src_path和目標(biāo)語言文件trg_path中分別讀取數(shù)據(jù),并進(jìn)行填充和
# batching操作。
def MakeSrcTrgDataset(src_path, trg_path, batch_size):
    # 首先分別讀取源語言數(shù)據(jù)和目標(biāo)語言數(shù)據(jù)。
    src_data = MakeDataset(src_path)
    trg_data = MakeDataset(trg_path)
    # 通過zip操作將兩個(gè)Dataset合并為一個(gè)Dataset。現(xiàn)在每個(gè)Dataset中每一項(xiàng)數(shù)據(jù)ds
    # 由4個(gè)張量組成:
    #   ds[0][0]是源句子
    #   ds[0][1]是源句子長度
    #   ds[1][0]是目標(biāo)句子
    #   ds[1][1]是目標(biāo)句子長度
    dataset = tf.data.Dataset.zip((src_data, trg_data))

    # 刪除內(nèi)容為空(只包含<EOS>)的句子和長度過長的句子。
    def FilterLength(src_tuple, trg_tuple):
        ((src_input, src_len), (trg_label, trg_len)) = (src_tuple, trg_tuple)
        src_len_ok = tf.logical_and(
            tf.greater(src_len, 1), tf.less_equal(src_len, MAX_LEN))
        trg_len_ok = tf.logical_and(
            tf.greater(trg_len, 1), tf.less_equal(trg_len, MAX_LEN))
        return tf.logical_and(src_len_ok, trg_len_ok)
    dataset = dataset.filter(FilterLength)
    
    # 從圖9-5可知,解碼器需要兩種格式的目標(biāo)句子:
    #   1.解碼器的輸入(trg_input),形式如同"<sos> X Y Z"
    #   2.解碼器的目標(biāo)輸出(trg_label),形式如同"X Y Z <eos>"
    # 上面從文件中讀到的目標(biāo)句子是"X Y Z <eos>"的形式,我們需要從中生成"<sos> X Y Z"
    # 形式并加入到Dataset中。
    def MakeTrgInput(src_tuple, trg_tuple):
        ((src_input, src_len), (trg_label, trg_len)) = (src_tuple, trg_tuple)
        trg_input = tf.concat([[SOS_ID], trg_label[:-1]], axis=0)
        return ((src_input, src_len), (trg_input, trg_label, trg_len))
    dataset = dataset.map(MakeTrgInput)

    # 隨機(jī)打亂訓(xùn)練數(shù)據(jù)。
    dataset = dataset.shuffle(10000)

    # 規(guī)定填充后輸出的數(shù)據(jù)維度。
    padded_shapes = (
        (tf.TensorShape([None]),      # 源句子是長度未知的向量
         tf.TensorShape([])),         # 源句子長度是單個(gè)數(shù)字
        (tf.TensorShape([None]),      # 目標(biāo)句子(解碼器輸入)是長度未知的向量
         tf.TensorShape([None]),      # 目標(biāo)句子(解碼器目標(biāo)輸出)是長度未知的向量
         tf.TensorShape([])))         # 目標(biāo)句子長度是單個(gè)數(shù)字
    # 調(diào)用padded_batch方法進(jìn)行batching操作。
    batched_dataset = dataset.padded_batch(batch_size, padded_shapes)
    return batched_dataset
# 定義NMTModel類來描述模型。
class NMTModel(object):
    # 在模型的初始化函數(shù)中定義模型要用到的變量。
    def __init__(self):
        # 定義編碼器和解碼器所使用的LSTM結(jié)構(gòu)。
        self.enc_cell_fw = tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE)
        self.enc_cell_bw = tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE)
        self.dec_cell = tf.nn.rnn_cell.MultiRNNCell(
          [tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE) 
           for _ in range(DECODER_LAYERS)])

        # 為源語言和目標(biāo)語言分別定義詞向量。   
        self.src_embedding = tf.get_variable(
            "src_emb", [SRC_VOCAB_SIZE, HIDDEN_SIZE])
        self.trg_embedding = tf.get_variable(
            "trg_emb", [TRG_VOCAB_SIZE, HIDDEN_SIZE])

        # 定義softmax層的變量
        if SHARE_EMB_AND_SOFTMAX:
           self.softmax_weight = tf.transpose(self.trg_embedding)
        else:
           self.softmax_weight = tf.get_variable(
               "weight", [HIDDEN_SIZE, TRG_VOCAB_SIZE])
        self.softmax_bias = tf.get_variable(
            "softmax_bias", [TRG_VOCAB_SIZE])

    # 在forward函數(shù)中定義模型的前向計(jì)算圖。
    # src_input, src_size, trg_input, trg_label, trg_size分別是上面
    # MakeSrcTrgDataset函數(shù)產(chǎn)生的五種張量。
    def forward(self, src_input, src_size, trg_input, trg_label, trg_size):
        batch_size = tf.shape(src_input)[0]
    
        # 將輸入和輸出單詞編號轉(zhuǎn)為詞向量。
        src_emb = tf.nn.embedding_lookup(self.src_embedding, src_input)
        trg_emb = tf.nn.embedding_lookup(self.trg_embedding, trg_input)
        
        # 在詞向量上進(jìn)行dropout。
        src_emb = tf.nn.dropout(src_emb, KEEP_PROB)
        trg_emb = tf.nn.dropout(trg_emb, KEEP_PROB)

        # 使用dynamic_rnn構(gòu)造編碼器。
        # 編碼器讀取源句子每個(gè)位置的詞向量,輸出最后一步的隱藏狀態(tài)enc_state。
        # 因?yàn)榫幋a器是一個(gè)雙層LSTM,因此enc_state是一個(gè)包含兩個(gè)LSTMStateTuple類
        # 張量的tuple,每個(gè)LSTMStateTuple對應(yīng)編碼器中的一層。
        # 張量的維度是 [batch_size, HIDDEN_SIZE]。
        # enc_outputs是頂層LSTM在每一步的輸出,它的維度是[batch_size, 
        # max_time, HIDDEN_SIZE]。Seq2Seq模型中不需要用到enc_outputs,而
        # 后面介紹的attention模型會用到它。
        # 下面的代碼取代了Seq2Seq樣例代碼中forward函數(shù)里的相應(yīng)部分。
        with tf.variable_scope("encoder"):
            # 構(gòu)造編碼器時(shí),使用bidirectional_dynamic_rnn構(gòu)造雙向循環(huán)網(wǎng)絡(luò)。
            # 雙向循環(huán)網(wǎng)絡(luò)的頂層輸出enc_outputs是一個(gè)包含兩個(gè)張量的tuple,每個(gè)張量的
            # 維度都是[batch_size, max_time, HIDDEN_SIZE],代表兩個(gè)LSTM在每一步的輸出。
            enc_outputs, enc_state = tf.nn.bidirectional_dynamic_rnn(
                self.enc_cell_fw, self.enc_cell_bw, src_emb, src_size, 
                dtype=tf.float32)
            # 將兩個(gè)LSTM的輸出拼接為一個(gè)張量。
            enc_outputs = tf.concat([enc_outputs[0], enc_outputs[1]], -1)     

        with tf.variable_scope("decoder"):
            # 選擇注意力權(quán)重的計(jì)算模型。BahdanauAttention是使用一個(gè)隱藏層的前饋神經(jīng)網(wǎng)絡(luò)。
            # memory_sequence_length是一個(gè)維度為[batch_size]的張量,代表batch
            # 中每個(gè)句子的長度,Attention需要根據(jù)這個(gè)信息把填充位置的注意力權(quán)重設(shè)置為0。
            attention_mechanism = tf.contrib.seq2seq.BahdanauAttention(
                HIDDEN_SIZE, enc_outputs,
                memory_sequence_length=src_size)

            # 將解碼器的循環(huán)神經(jīng)網(wǎng)絡(luò)self.dec_cell和注意力一起封裝成更高層的循環(huán)神經(jīng)網(wǎng)絡(luò)。
            attention_cell = tf.contrib.seq2seq.AttentionWrapper(
                self.dec_cell, attention_mechanism,
                attention_layer_size=HIDDEN_SIZE)

            # 使用attention_cell和dynamic_rnn構(gòu)造編碼器。
            # 這里沒有指定init_state,也就是沒有使用編碼器的輸出來初始化輸入,而完全依賴
            # 注意力作為信息來源。
            dec_outputs, _ = tf.nn.dynamic_rnn(
                attention_cell, trg_emb, trg_size, dtype=tf.float32)

        # 計(jì)算解碼器每一步的log perplexity。這一步與語言模型代碼相同。
        output = tf.reshape(dec_outputs, [-1, HIDDEN_SIZE])
        logits = tf.matmul(output, self.softmax_weight) + self.softmax_bias
        loss = tf.nn.sparse_softmax_cross_entropy_with_logits(
            labels=tf.reshape(trg_label, [-1]), logits=logits)

        # 在計(jì)算平均損失時(shí),需要將填充位置的權(quán)重設(shè)置為0,以避免無效位置的預(yù)測干擾
        # 模型的訓(xùn)練。
        label_weights = tf.sequence_mask(
            trg_size, maxlen=tf.shape(trg_label)[1], dtype=tf.float32)
        label_weights = tf.reshape(label_weights, [-1])
        cost = tf.reduce_sum(loss * label_weights)
        cost_per_token = cost / tf.reduce_sum(label_weights)
        
        # 定義反向傳播操作。反向操作的實(shí)現(xiàn)與語言模型代碼相同。
        trainable_variables = tf.trainable_variables()

        # 控制梯度大小,定義優(yōu)化方法和訓(xùn)練步驟。
        grads = tf.gradients(cost / tf.to_float(batch_size),
                             trainable_variables)
        grads, _ = tf.clip_by_global_norm(grads, MAX_GRAD_NORM)
        optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0)
        train_op = optimizer.apply_gradients(
            zip(grads, trainable_variables))
        return cost_per_token, train_op

# 使用給定的模型model上訓(xùn)練一個(gè)epoch,并返回全局步數(shù)。
# 每訓(xùn)練200步便保存一個(gè)checkpoint。
def run_epoch(session, cost_op, train_op, saver, step):
    # 訓(xùn)練一個(gè)epoch。
    # 重復(fù)訓(xùn)練步驟直至遍歷完Dataset中所有數(shù)據(jù)。
    while True:
        try:
            # 運(yùn)行train_op并計(jì)算損失值。訓(xùn)練數(shù)據(jù)在main()函數(shù)中以Dataset方式提供。
            cost, _ = session.run([cost_op, train_op])
            if step % 10 == 0:
                print("After %d steps, per token cost is %.3f" % (step, cost))
            # 每200步保存一個(gè)checkpoint。
            if step % 200 == 0:
                saver.save(session, CHECKPOINT_PATH, global_step=step)
            step += 1
        except tf.errors.OutOfRangeError:
            break
    return step

def main():
    # 定義初始化函數(shù)。
    initializer = tf.random_uniform_initializer(-0.05, 0.05)

    # 定義訓(xùn)練用的循環(huán)神經(jīng)網(wǎng)絡(luò)模型。
    with tf.variable_scope("nmt_model", reuse=None, 
                           initializer=initializer):
        train_model = NMTModel()
  
    # 定義輸入數(shù)據(jù)。
    data = MakeSrcTrgDataset(SRC_TRAIN_DATA, TRG_TRAIN_DATA, BATCH_SIZE)
    iterator = data.make_initializable_iterator()
    (src, src_size), (trg_input, trg_label, trg_size) = iterator.get_next()
 
    # 定義前向計(jì)算圖。輸入數(shù)據(jù)以張量形式提供給forward函數(shù)。
    cost_op, train_op = train_model.forward(src, src_size, trg_input,
                                            trg_label, trg_size)

    # 訓(xùn)練模型。
    saver = tf.train.Saver()
    step = 0
    with tf.Session() as sess:
        tf.global_variables_initializer().run()
        for i in range(NUM_EPOCH):
            print("In iteration: %d" % (i + 1))
            sess.run(iterator.initializer)
            step = run_epoch(sess, cost_op, train_op, saver, step)
if __name__ == "__main__":
    main()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 不同圖像灰度不同,邊界處一般會有明顯的邊緣,利用此特征可以分割圖像。需要說明的是:邊緣和物體間的邊界并不等同,邊緣...
    大川無敵閱讀 14,125評論 0 29
  • “衡量一件事情是不是有意義,主要是看我們投入時(shí)間之后是否有預(yù)期的產(chǎn)出。如果達(dá)到了預(yù)期效果,那么你投入的時(shí)間就是有意...
  • 記得在高中的時(shí)候,在不同作文里,結(jié)尾用的最多一句話,就是“關(guān)于怎樣的孤獨(dú)會永遠(yuǎn)記在泛黃的羊皮卷上,不會被時(shí)間...
    敬自由跟遠(yuǎn)方閱讀 543評論 0 0
  • 一堆的委屈說不出來 愛情里參雜了現(xiàn)實(shí)生活之后,你發(fā)現(xiàn)不僅僅只是柴米油鹽。更多關(guān)于錢的事兒并不是很少。而你自己沒有能...
    煮茶小妖閱讀 318評論 0 0

友情鏈接更多精彩內(nèi)容