seq2seq裡在chatbot的一些用法
轉處:https://blog.csdn.net/liuchonge/article/details/79021938
上篇文章我們使用tf.contrib.legacy_seq2seq下的API構建了一個簡單的chatbot對話系統,但是我們已經說過,這部分程式碼是1.0版本之前所提供的API,將來會被棄用,而且API介面並不靈活,在實際使用過程中還會存在版本不同導致的各種個樣的錯誤。所以我們有必要學習一下新版本的API,這裡先來說一下二者的不同:
新版本都是用dynamic_rnn來構造RNN模型,這樣就避免了資料長度不同所帶來的困擾,不需要再使用model_with_buckets這種方法來構建模型,使得我們資料處理和模型程式碼都簡潔很多
新版本將Attention、Decoder等幾個主要的功能都分別進行封裝,直接呼叫相應的Wapper函式進行封裝即可,呼叫起來更加靈活方便,而且只需要寫幾個簡單的函式既可以自定義的各個模組以滿足我們個性化的需求。
實現了beam_search功能,可直接呼叫
這次我們先來看如何直接使用新版本API構造對話系統,然後等下一篇文章在分析一些主要檔案和函式的原始碼實現。本文程式碼可以再我的github中找到:。歡迎fork和star~~
):
inference圖往往與train和eval結構存在較大差異(沒有decoder輸入和目標,需要使用貪婪或者beam_search進行decode,batch_size也不同等等),所以往往需要單獨進行構建
eval圖也會得到簡化,因為其不需要進行反向傳播,只需要得到一個loss和acc值
資料可以分別進行feed,簡化資料操作
變數重用變得簡單,因為train、eval存在一些公用變數和程式碼塊,就不需要我們重複定義,使程式碼簡化
只需要在train時不斷儲存模型引數,然後在eval和infer的時候restore引數即可
inference圖往往與train和eval結構存在較大差異(沒有decoder輸入和目標,需要使用貪婪或者beam_search進行decode,batch_size也不同等等),所以往往需要單獨進行構建
eval圖也會得到簡化,因為其不需要進行反向傳播,只需要得到一個loss和acc值
資料可以分別進行feed,簡化資料操作
變數重用變得簡單,因為train、eval存在一些公用變數和程式碼塊,就不需要我們重複定義,使程式碼簡化
只需要在train時不斷儲存模型引數,然後在eval和infer的時候restore引數即可
以上,所以我們構建了train、eval、infer三個函式來實現上面的功能。在看程式碼之前我們先來簡單說一下新版API幾個主要的模組以及相互之間的呼叫關係。tf.contrib.seq2seq資料夾下面主要有下面6個檔案,除了loss檔案和之前的sequence_loss函式沒有很大區別,這裡不介紹之外,其他幾個檔案都會簡單的說一下,這裡主要介紹函式和類的功能,原始碼會放在下篇文章中介紹。
decoder
basic_decoder
helper
attention_wrapper
beam_search_decoder
loss
BasicDecoder類和dynamic_decode
decoder和basic_decoder檔案可以放在一起看,decoder檔案中定義了Decoder抽象類和dynamic_decode函式,dynamic_decode可以視為整個解碼過程的入口,需要傳入的引數就是Decoder的一個例項,他會動態的呼叫Decoder的step函式按步執行decode,可以理解為Decoder類定義了單步解碼(根據輸入求出輸出,並將該輸出當做下一時刻輸入),而dynamic_decode則會呼叫control_flow_ops.while_loop這個函式來迴圈執行直到輸出結束編碼過程。而basic_decoder檔案定義了一個基本的Decoder類例項BasicDecoder,看一下其初始化函式:
def __init__(self, cell, helper, initial_state, output_layer=None):
1
2
需要傳入的引數就是cell型別、helper型別、初始化狀態(encoder的最後一個隱層狀態)、輸出層(輸出對映層,將rnn_size轉化為vocab_size維),需要注意的就是前面兩個,下面分別介紹:
cell型別(Attention型別)
cell型別就是RNNCell,也就是decode階段的神經元,可以使簡單的RNN、GRU、LSTM(也可以加上dropout、並使用MultiRNNCell進行堆疊成多層),也可以是加上了Attention功能之後的RNNcell。這就引入了attention_wrapper檔案中定義的幾種attention機制(BahdanauAttention、 LuongAttention、 BahdanauMonotonicAttention、 LuongMonotonicAttention)和將attention機制封裝到RNNCell上面的方法AttentionWrapper。其實很簡單,就跟dropoutwrapper、outputwrapper一樣,我們只需要在原本RNNCell的基礎上在封裝一層attention即可。程式碼如下所示:
# 分為三步,第一步是定義attention機制,第二步是定義要是用的基礎的RNNCell,第三步是使用AttentionWrapper進行封裝 #定義要使用的attention機制。 attention_mechanism = tf.contrib.seq2seq.BahdanauAttention(num_units=self.rnn_size, memory=encoder_outputs, memory_sequence_length=encoder_inputs_length) #attention_mechanism = tf.contrib.seq2seq.LuongAttention(num_units=self.rnn_size, memory=encoder_outputs, memory_sequence_length=encoder_inputs_length) # 定義decoder階段要是用的LSTMCell,然後為其封裝attention wrapper decoder_cell = self._create_rnn_cell() decoder_cell = tf.contrib.seq2seq.AttentionWrapper(cell=decoder_cell, attention_mechanism=attention_mechanism, attention_layer_size=self.rnn_size, name='Attention_Wrapper')
helper型別
helper其實就是decode階段如何根據預測結果得到下一時刻的輸入,比如訓練過程中應該直接使用上一時刻的真實值作為下一時刻輸入,預測過程中可以使用貪婪的方法選擇機率最大的那個值作為下一時刻等等。所以Helper也就可以大致分為訓練時helper和預測時helper兩種。官網給出了下面幾種Helper類:
“Helper”:最基本的抽象類
“TrainingHelper”:訓練過程中最常使用的Helper,下一時刻輸入就是上一時刻target的真實值
“GreedyEmbeddingHelper”:預測階段最常使用的Helper,下一時刻輸入是上一時刻機率最大的單詞透過embedding之後的向量
“SampleEmbeddingHelper”:預測時helper,繼承自GreedyEmbeddingHelper,下一時刻輸入是上一時刻透過某種機率分佈取樣而來在經過embedding之後的向量
“CustomHelper”:最簡單的helper,一般使用者自定義helper時會基於此,需要使用者自己定義如何根據輸出得到下一時刻輸入
“ScheduledEmbeddingTrainingHelper”:訓練時Helper,繼承自TrainingHelper,新增了廣義伯努利分佈,對id的embedding向量進行sampling
“ScheduledOutputTrainingHelper”:訓練時Helper,繼承自TrainingHelper,直接對輸出進行取樣
“InferenceHelper”:CustomHelper的特例,只用於預測的helper,也需要使用者自定義如何得到下一時刻輸入
所以瞭解cell和helper類之後我們就可以很輕鬆的構建decode階段的模型,以train階段為例:
#分為四步,第一步是定義cell型別,第二步是定義helper型別,第三步是定義BasicDecoder類例項,第四步是呼叫dynamic_decode函式進行解碼 cell = ***(上面的程式碼) training_helper = tf.contrib.seq2seq.TrainingHelper(inputs=decoder_inputs_embedded, sequence_length=self.decoder_targets_length, time_major=False, name='training_helper') training_decoder = tf.contrib.seq2seq.BasicDecoder(cell=decoder_cell, helper=training_helper, initial_state=decoder_initial_state, output_layer=output_layer) decoder_outputs, _, _ = tf.contrib.seq2seq.dynamic_decode(decoder=training_decoder, impute_finished=True, maximum_iterations=self.max_target_sequence_length)
beam search decoder類
到這,基本上就可以構建一個完整的seq2seq模型了,但是上面的檔案中還有beam_search_decoder.py檔案沒有介紹,也就是我們常用的beam_search方法,下面也簡單說一下。該檔案定義了BeamSearchDecoder類,其實是一個Decoder的例項,跟BasicDecoder在一個等級上,但是二者又存在著不同,因為BasicDecoder需要指定helper引數,也就是指定decode階段如何根據上一時刻輸出獲得下一時刻輸入。但是BeamSearchDecoder不需要,因為其在內部實現了beam_search的功能,也就包含了helper的效果,不需要再額外定義。所以BeamSearchDecoder的呼叫方法如下所示:
#分為三步,第一步是定義cell,第二步是定義BeamSearchDecoder,第三步是呼叫dynamic_decode函式進行解碼 cell = ***(上面程式碼) inference_decoder = tf.contrib.seq2seq.BeamSearchDecoder(cell=decoder_cell, embedding=embedding, start_tokens=start_tokens, end_token=end_token, initial_state=decoder_initial_state, beam_width=self.beam_size, output_layer=output_layer) decoder_outputs, _, _ = tf.contrib.seq2seq.dynamic_decode(decoder=inference_decoder, maximum_iterations=self.max_target_sequence_length)
OK,接下來切入正題,看一下model部分程式碼:
class Seq2SeqModel(): def __init__(self, rnn_size, num_layers, embedding_size, learning_rate, word_to_idx, mode, use_attention, beam_search, beam_size, max_gradient_norm=5.0): self.learing_rate = learning_rate self.embedding_size = embedding_size self.rnn_size = rnn_size self.num_layers = num_layers self.word_to_idx = word_to_idx self.vocab_size = len(self.word_to_idx) self.mode = mode self.use_attention = use_attention self.beam_search = beam_search self.beam_size = beam_size self.max_gradient_norm = max_gradient_norm #執行模型構建部分的程式碼 self.build_model() def _create_rnn_cell(self, single=False): def single_rnn_cell(): # 建立單個cell,這裡需要注意的是一定要使用一個single_rnn_cell的函式,不然直接把cell放在MultiRNNCell # 的列表中最終模型會發生錯誤 single_cell = tf.contrib.rnn.LSTMCell(self.rnn_size) #新增dropout cell = tf.contrib.rnn.DropoutWrapper(single_cell, output_keep_prob=self.keep_prob_placeholder) return cell #列表中每個元素都是呼叫single_rnn_cell函式 cell = tf.contrib.rnn.MultiRNNCell([single_rnn_cell() for _ in range(self.num_layers)]) return cell def build_model(self): print('building model... ...') #=================================1, 定義模型的placeholder self.encoder_inputs = tf.placeholder(tf.int32, [None, None], name='encoder_inputs') self.encoder_inputs_length = tf.placeholder(tf.int32, [None], name='encoder_inputs_length') self.batch_size = tf.placeholder(tf.int32, [], name='batch_size') self.keep_prob_placeholder = tf.placeholder(tf.float32, name='keep_prob_placeholder') self.decoder_targets = tf.placeholder(tf.int32, [None, None], name='decoder_targets') self.decoder_targets_length = tf.placeholder(tf.int32, [None], name='decoder_targets_length') # 根據目標序列長度,選出其中最大值,然後使用該值構建序列長度的mask標誌。用一個sequence_mask的例子來說明起作用 # tf.sequence_mask([1, 3, 2], 5) # [[True, False, False, False, False], # [True, True, True, False, False], # [True, True, False, False, False]] self.max_target_sequence_length = tf.reduce_max(self.decoder_targets_length, name='max_target_len') self.mask = tf.sequence_mask(self.decoder_targets_length, self.max_target_sequence_length, dtype=tf.float32, name='masks') #=================================2, 定義模型的encoder部分 with tf.variable_scope('encoder'): #建立LSTMCell,兩層+dropout encoder_cell = self._create_rnn_cell() #構建embedding矩陣,encoder和decoder公用該詞向量矩陣 embedding = tf.get_variable('embedding', [self.vocab_size, self.embedding_size]) encoder_inputs_embedded = tf.nn.embedding_lookup(embedding, self.encoder_inputs) # 使用dynamic_rnn構建LSTM模型,將輸入編碼成隱層向量。 # encoder_outputs用於attention,batch_size*encoder_inputs_length*rnn_size, # encoder_state用於decoder的初始化狀態,batch_size*rnn_szie encoder_outputs, encoder_state = tf.nn.dynamic_rnn(encoder_cell, encoder_inputs_embedded, sequence_length=self.encoder_inputs_length, dtype=tf.float32) # =================================3, 定義模型的decoder部分 with tf.variable_scope('decoder'): encoder_inputs_length = self.encoder_inputs_length if self.beam_search: # 如果使用beam_search,則需要將encoder的輸出進行tile_batch,其實就是複製beam_size份。 print("use beamsearch decoding..") encoder_outputs = tf.contrib.seq2seq.tile_batch(encoder_outputs, multiplier=self.beam_size) encoder_state = nest.map_structure(lambda s: tf.contrib.seq2seq.tile_batch(s, self.beam_size), encoder_state) encoder_inputs_length = tf.contrib.seq2seq.tile_batch(self.encoder_inputs_length, multiplier=self.beam_size) #定義要使用的attention機制。 attention_mechanism = tf.contrib.seq2seq.BahdanauAttention(num_units=self.rnn_size, memory=encoder_outputs, memory_sequence_length=encoder_inputs_length) #attention_mechanism = tf.contrib.seq2seq.LuongAttention(num_units=self.rnn_size, memory=encoder_outputs, memory_sequence_length=encoder_inputs_length) # 定義decoder階段要是用的LSTMCell,然後為其封裝attention wrapper decoder_cell = self._create_rnn_cell() decoder_cell = tf.contrib.seq2seq.AttentionWrapper(cell=decoder_cell, attention_mechanism=attention_mechanism, attention_layer_size=self.rnn_size, name='Attention_Wrapper') #如果使用beam_seach則batch_size = self.batch_size * self.beam_size。因為之前已經複製過一次 batch_size = self.batch_size if not self.beam_search else self.batch_size * self.beam_size #定義decoder階段的初始化狀態,直接使用encoder階段的最後一個隱層狀態進行賦值 decoder_initial_state = decoder_cell.zero_state(batch_size=batch_size, dtype=tf.float32).clone(cell_state=encoder_state) output_layer = tf.layers.Dense(self.vocab_size, kernel_initializer=tf.truncated_normal_initializer(mean=0.0, stddev=0.1)) if self.mode == 'train': # 定義decoder階段的輸入,其實就是在decoder的target開始處新增一個,並刪除結尾處的 ,並進行embedding。 # decoder_inputs_embedded的shape為[batch_size, decoder_targets_length, embedding_size] ending = tf.strided_slice(self.decoder_targets, [0, 0], [self.batch_size, -1], [1, 1]) decoder_input = tf.concat([tf.fill([self.batch_size, 1], self.word_to_idx[' ']), ending], 1) decoder_inputs_embedded = tf.nn.embedding_lookup(embedding, decoder_input) #訓練階段,使用TrainingHelper+BasicDecoder的組合,這一般是固定的,當然也可以自己定義Helper類,實現自己的功能 training_helper = tf.contrib.seq2seq.TrainingHelper(inputs=decoder_inputs_embedded, sequence_length=self.decoder_targets_length, time_major=False, name='training_helper') training_decoder = tf.contrib.seq2seq.BasicDecoder(cell=decoder_cell, helper=training_helper, initial_state=decoder_initial_state, output_layer=output_layer) #呼叫dynamic_decode進行解碼,decoder_outputs是一個namedtuple,裡面包含兩項(rnn_outputs, sample_id) # rnn_output: [batch_size, decoder_targets_length, vocab_size],儲存decode每個時刻每個單詞的機率,可以用來計算loss # sample_id: [batch_size], tf.int32,儲存最終的編碼結果。可以表示最後的答案 decoder_outputs, _, _ = tf.contrib.seq2seq.dynamic_decode(decoder=training_decoder, impute_finished=True, maximum_iterations=self.max_target_sequence_length) # 根據輸出計算loss和梯度,並定義進行更新的AdamOptimizer和train_op self.decoder_logits_train = tf.identity(decoder_outputs.rnn_output) self.decoder_predict_train = tf.argmax(self.decoder_logits_train, axis=-1, name='decoder_pred_train') # 使用sequence_loss計算loss,這裡需要傳入之前定義的mask標誌 self.loss = tf.contrib.seq2seq.sequence_loss(logits=self.decoder_logits_train, targets=self.decoder_targets, weights=self.mask) # Training summary for the current batch_loss tf.summary.scalar('loss', self.loss) self.summary_op = tf.summary.merge_all() optimizer = tf.train.AdamOptimizer(self.learing_rate) trainable_params = tf.trainable_variables() gradients = tf.gradients(self.loss, trainable_params) clip_gradients, _ = tf.clip_by_global_norm(gradients, self.max_gradient_norm) self.train_op = optimizer.apply_gradients(zip(clip_gradients, trainable_params)) elif self.mode == 'decode': start_tokens = tf.ones([self.batch_size, ], tf.int32) * self.word_to_idx[' '] end_token = self.word_to_idx[' '] # decoder階段根據是否使用beam_search決定不同的組合, # 如果使用則直接呼叫BeamSearchDecoder(裡面已經實現了helper類) # 如果不使用則呼叫GreedyEmbeddingHelper+BasicDecoder的組合進行貪婪式解碼 if self.beam_search: inference_decoder = tf.contrib.seq2seq.BeamSearchDecoder(cell=decoder_cell, embedding=embedding, start_tokens=start_tokens, end_token=end_token, initial_state=decoder_initial_state, beam_width=self.beam_size, output_layer=output_layer) else: decoding_helper = tf.contrib.seq2seq.GreedyEmbeddingHelper(embedding=embedding, start_tokens=start_tokens, end_token=end_token) inference_decoder = tf.contrib.seq2seq.BasicDecoder(cell=decoder_cell, helper=decoding_helper, initial_state=decoder_initial_state, output_layer=output_layer) decoder_outputs, _, _ = tf.contrib.seq2seq.dynamic_decode(decoder=inference_decoder, maximum_iterations=10) # 呼叫dynamic_decode進行解碼,decoder_outputs是一個namedtuple, # 對於不使用beam_search的時候,它裡面包含兩項(rnn_outputs, sample_id) # rnn_output: [batch_size, decoder_targets_length, vocab_size] # sample_id: [batch_size, decoder_targets_length], tf.int32 # 對於使用beam_search的時候,它裡面包含兩項(predicted_ids, beam_search_decoder_output) # predicted_ids: [batch_size, decoder_targets_length, beam_size],儲存輸出結果 # beam_search_decoder_output: BeamSearchDecoderOutput instance namedtuple(scores, predicted_ids, parent_ids) # 所以對應只需要返回predicted_ids或者sample_id即可翻譯成最終的結果 if self.beam_search: self.decoder_predict_decode = decoder_outputs.predicted_ids else: self.decoder_predict_decode = tf.expand_dims(decoder_outputs.sample_id, -1) # =================================4, 儲存模型 self.saver = tf.train.Saver(tf.all_variables()) def train(self, sess, batch): #對於訓練階段,需要執行self.train_op, self.loss, self.summary_op三個op,並傳入相應的資料 feed_dict = {self.encoder_inputs: batch.encoder_inputs, self.encoder_inputs_length: batch.encoder_inputs_length, self.decoder_targets: batch.decoder_targets, self.decoder_targets_length: batch.decoder_targets_length, self.keep_prob_placeholder: 0.5, self.batch_size: len(batch.encoder_inputs)} _, loss, summary = sess.run([self.train_op, self.loss, self.summary_op], feed_dict=feed_dict) return loss, summary def eval(self, sess, batch): # 對於eval階段,不需要反向傳播,所以只執行self.loss, self.summary_op兩個op,並傳入相應的資料 feed_dict = {self.encoder_inputs: batch.encoder_inputs, self.encoder_inputs_length: batch.encoder_inputs_length, self.decoder_targets: batch.decoder_targets, self.decoder_targets_length: batch.decoder_targets_length, self.keep_prob_placeholder: 1.0, self.batch_size: len(batch.encoder_inputs)} loss, summary = sess.run([self.loss, self.summary_op], feed_dict=feed_dict) return loss, summary def infer(self, sess, batch): #infer階段只需要執行最後的結果,不需要計算loss,所以feed_dict只需要傳入encoder_input相應的資料即可 feed_dict = {self.encoder_inputs: batch.encoder_inputs, self.encoder_inputs_length: batch.encoder_inputs_length, self.keep_prob_placeholder: 1.0, self.batch_size: len(batch.encoder_inputs)} predict = sess.run([self.decoder_predict_decode], feed_dict=feed_dict) return predict
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1600/viewspace-2801344/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Qdrant用法;Qdrant在langchain裡的用法LangChain
- MUI在Android與IOS上的一些小問題以及一些框架的用法UIAndroidiOS框架
- js裡document的用法JS
- MapStruct的一些常規用法Struct
- typedef的一些高階用法
- axios的一些常見用法iOS
- 關於angularJS的一些用法AngularJS
- js陣列的一些用法JS陣列
- 遨翔在知識的海洋裡----(css之vue中sass基本用法)CSSVue
- 模板字串的一些用法小記字串
- Google Analytics 的一些用法介紹Go
- JavaScript 裡三個點 ... 的用法JavaScript
- Java裡的Character類的基本用法Java
- CSS裡的BFC和IFC的用法CSS
- Kubernetes裡的secret最基本的用法
- 實驗三:在centos裡使用一些簡單的程式碼,CentOS
- Java8中Stream 的一些用法Java
- 關於with 臨時表 as的一些用法
- vue一些不為人知的用法Vue
- 在 ABAP 技術棧裡實施 Continuous Integration 的一些挑戰
- seq2seq 的 keras 實現Keras
- 雲端儲存的一些功能用法
- java裡的一些hash方法Java
- Chatbot正在掀起全球銀行的AI變革AI
- 深度學習的seq2seq模型深度學習模型
- 專案裡的一些小函式函式
- Seq2Seq詳解
- 一些關於react的keep-alive功能相關知識在這裡(下)ReactKeep-Alive
- 一些關於react的keep-alive功能相關知識在這裡(上)ReactKeep-Alive
- Bash 指令碼程式設計的一些高階用法指令碼程式設計
- scss 檔案裡的特殊符號 @ 和 @include 的用法CSS符號
- 對話機器人ChatBot綜述機器人
- Seq2Seq原理詳解
- TensorFlow實現seq2seq
- css3的一些新屬性及部分用法CSSS3
- 在滲透中curl的常見用法
- Redis 在現實世界的 5 個用法Redis
- 開源|ns4_chatbot通訊元件的工作原理元件