進(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()