Trong bài tutorial này, chúng ta sẽ thực hành viết chương trình sử dụng thư viện Tensorflow để xây dựng mô hình Seq2seq với Attention Mechanism. Trước hết chúng ta tạo một class với tên là Seq2SeqModel với hàm __init__ () có 6 tham số đầu vào là:

– vocab_size: là số lượng từ trong từ điển. Ở đây chính là độ dài của int2word (xem thêm int2word tại bài tutorial Xây dựng mô hình Text generation với RNN)

– word_embedding: là Word Embedding tương ứng với từng từ trong int2word

– input_len: là chiều dài của input sequence. Ở bước xử lý dữ liệu, tất cả các input sequence đều được chuyển về một độ dài nhất định (max_len) bằng cách loại bỏ bớt các từ với input sequence có chiều dài lớn hơn max_len và thêm padding với input sequence có chiều dài nhỏ hơn max_len

– output_len: tương tự như input_len, output_len là chiều dài của ouput sequence

– train: là một Boolean value thể hiện Training mode hoặc Inference mode. Mặc định train có giá trị là True tức là Training mode

– params: là một python dict, dùng để thiết lập các hyper parameters cho Seq2seq model. Trong bài này chúng ta sẽ sử dụng 4 hyper-parameters là:

+ num_layers: là số lượng layer của Multi-layer RNN

+ num_hiddens: là số lượng hidden units (hidden nodes) của một RNN cell (xem thêm tham số units trong bài tutorial Xây dựng mô hình Text generation với RNN)

+ learning_rate: là giá trị learning rate sử dụng trong quá trình huấn luyện mô hình.

+ keep_prob: tương ứng với giá trị (1 – drop_out)sử dụng trong quá trình huấn luyện mô hình.

+ beam_width: là số lượng n-words với xác xuất cao nhất được giữ lại trong Beam Search.

Các tham số trên được sử dụng trong Seq2SeqModel như sau:

Tiếp theo ta sử dụng Placeholder để thiết lập các biến cần thiết trong Seq2Seq Model như sau (xem thêm về Placeholder tại bài tutorial Các khái niệm cơ bản trong TensorFlow):

Lưu ý:

– Ở đây chúng ta sử dụng TensorFlow LSTMCell. Tuy nhiên các bạn có thể sử dụng các RNN khác như GRUCell, BasicRNNCell

– self.inputSeq_len và self.decoder_len là các tensor với rank=1, hay còn gọi là Vector (xem thêm tại bài tutorial Các khái niệm cơ bản trong TensorFlow). Mỗi giá trị trong Vector này là độ dài thực tế của Encoder input sequence và Decoder input sequence (độ dài không tính padding). Mặc dù tất cả các input sequence và output sequence đều có độ dài như nhau là input_len và output_len. Tuy nhiên chúng ta sử dụng độ dài thực tế này để tiết kiệm thời gian tính toán và giảm thiểu sai số khi thực hiện tính toán với cả padding.

– Chúng ta sử dụng projection_layer là một Dense layer để chuyển RNN ouput của Decoder về dạng Logits Vector với số lượng Dimension tương ứng với Vocab size. Từ đó có thể xác định được ouput word tương ứng.

Để giúp việc viết code và đọc code một cách dễ dàng, chúng ta sẽ sử dụng tf.name_scope () nhằm phân chia Seq2SeqModel thành các phần nhỏ như sau (name_scope thường được sử dụng để nhóm các variables cùng thực hiện một công việc vào một nhóm):

1) Embedding layer: Layer này có nhiệm vụ chuyển các input sequence của Encoder và Decoder từ dạng int sang dạng Word Embedding bằng cách sử dụng tf.nn.embedding_lookup. Ngoài ra chúng ta cũng sử dụng tf.transpose để chuyển các input sequence từ dạng batch major (có kích thước là batch_size*seq_len*embedding) sang dạng time major (seq_len*batch_size*embedding) nhằm tiết kiệm thời gian cho quá trình huấn luyện (xem thêm trong phần time_major tại đây):

2) Encoder: Với Encoder chúng ta sẽ sử dụng Multi layer Bi-directional LSTM với số lượng layer và số lượng hidden units của LSTM cell được thiết lập trong params. Ngoài ra, DropoutWrapper được sử dụng để thiết lập giá trị Drop Out cho các LSTM cell:

Sau khi định nghĩa xong các cells , chúng ta sử dụng tf.contrib.rnn.stack_bidirectional_dynamic_rnn để kết hợp các cells này thành một mô hình Multi layer Bi-directional LSTM. Lưu ý: do phần trên chúng ta đã chuyển input sequence sang dang time major, nên chúng ta phải thiết lập tham số time_major=True (các bạn có thể tham khảo thêm về stack_bidirectional_dynamic_rnn tại đây):

stack_bidirectional_dynamic_rnn trả về 3 kết quả là:

– outputs: là Ouput Tensor, trong đó forward và backward output đã được kết hợp với nhau (concatenation). Nếu time_major=False thì Ouput Tensor này có shape là: [batch_size, max_time, cell_fw.output_size + cell_bw.output_size], còn nếu time_major=True thì Ouput Tensor có shape là:[max_time, batch_size, cell_fw.output_size+ cell_bw.output_size] (xem thêm trong code của stack_bidirectional_dynamic_rnn tại đây)

– encoder_state_fw: là một tuple của các LSTMStateTuple. Mỗi một LSTMStateTuple là final state tại mỗi forward layer, chứa giá trị của Cell State và Hidden State của layer đó ( Cell State và Hidden State này có kích thước là batch_size * cell_fw.output_size)

– encoder_state_bw: tương tự như encoder_state_fw, encoder_state_bw một tuple của các backward LSTMStateTuple.

Tiếp theo, ta sử dụng đoạn code sau để lấy final state của layer cuối cùng làm Final state của Encoder:

Như vậy, Encoder sẽ trả về hai giá trị là encoder_outputs với kích thước [max_time, batch_size, cell_fw.output_size+ cell_bw.output_size] (do ta sử dụng time_major=True) và encoder_final_state là LSTMStateTuple của layer cuối cùng với kích thước của Cell State và Hidden State là [batch_size, cell_fw.output_size+ cell_bw.output_size]

3) Decoder:

Trước hết chúng ta định nghĩa Decoder cell (do ta sẽ sử dụng final state của Encoder có kích thước là [batch_size, cell_fw.output_size+ cell_bw.output_size] làm initial state cho Decoder nên số lượng hidden units của Decoder phải bằng với kích thước của Encoder final state ( cell_fw.output_size+ cell_bw.output_size = num_hiddens * 2):

Đối với Decoder, chúng ta phải chia thành hai trường hợp riêng biệt là training (huấn luyện mô hình) và inference (tính prediction). Trong quá trình huấn luyện, chúng ta sẽ sử dụng TrainingHelper còn khi tính prediction, chúng ta sử dụng BasicDecoder + GreedyEmbeddingHelper hoặc BeamSearchDecoder. Nguyên nhân của việc này là do, khi chúng ta huấn luyện mô hình, chúng ta sử dụng giá trị output thực làm input cho Decoder. Tuy nhiên, khi tính prediction chúng ta không biết giá trị thực này, do đó ta phải sử dụng GreedySearch hoặc BeamSearch để tìm giá trị output hợp lý nhất. Chi tiết về Training và Inference được mô tả dưới đây:

a) Training:

Trong phần Training, chúng ta sử dụng Attention và TrainingHelper để huấn luyện mô hình. Với Attention thì thư viện seq2seq của TensorFlow có hỗ trợ một số loại Attention như BahdanauAttention , LuongAttention, LuongMonotonicAttention … Ở đây chúng ta sẽ sử dụng BahdanauAttention với tham số memory có giá trị là output của Encoder. Tham số này phải ở dạng [batch_size, max_time, …]. Do đó chúng ta phải sử dụng tf.transpose để chuyển Encoder output từ dạng time major sang dạng batch major:

Sau khi xây dựng Decoder với Attention xong, chúng ta cần thiết lập initial state cho Decoder bằng cách sử dụng final state của Encoder như sau:

Cuối cùng, chúng ta sử dụng TrainingHelper và để huấn luyện mô hình và tính giá trị logits như sau:

Lưu ý: các output sequences của Decoder có thể có độ dài khác nhau. Do đó chúng ta cần thêm giá trị padding=0 nhằm đảm bảo rằng training_logits có kích thước là: [batch_size, sequence_length, vocab_size]

b) Inference: Inference là giai đoạn sau khi chúng ta đã huấn luyện xong mô hình Seq2seq và sử dụng mô hình này để dự đoán kết quả output. Do chúng ta không biết kết quả output thực tế như trong quá trình Training, nên ta cần sử dụng các thuật toán Search để tìm ra kết quả phù hợp nhất (ở đây chúng ta sẽ sử dụng BeamSearch). Khi sử dụng BeamSearch, chúng ta cần phải cân nhắc tham số beam_width, do đó ta phải sử dụng tf.contrib.seq2seq.tile_batch để chuyển dữ liệu về dạng beam_width như sau:

Sau đó, chúng ta sử dụng BeamSearchDecoder và seq2seq.dynamic_decode để tính kết quả prediction. Kết quả outputs trả về là một FinalBeamSearchDecoderOutput với predicted_ids là kết quả prediction có kích thước là [seq_len, batch_size, beam_width] khi time_major=True. Trong đó Beams được sắp xếp theo thứ tự giảm dần từ kết quả tốt nhất:

3) Optimization: Optimization chỉ được sử dụng trong quá trình Training. Ở đây, chúng ta sẽ tính giá trị của loss function sử dụng tf.contrib.seq2seq.sequence_loss và dùng AdamOptimizer để update model. Ngoài ra chúng ta cũng áp dụng Gradient Clipping để giữ giá trị gradient trong khoảng [-5, 5]:

Như vậy chúng ta đã hoàn thành việc xây dựng mô hình Seq2seq sử dụng Attention Mechanism và Beam Search. Các bạn có thể tham khảo Colab notebook của tutorial này trên trang Github của ITechSeeker tại đây. Trong bài tutorial tiếp theo, chúng ta sẽ sử dụng mô hình đã xây dựng ở trên vào để giải quyết bài toán thực tế là tóm tắt và tạo tiêu đề cho văn bản.

 

Tài liệu tham khảo:

https://github.com/tensorflow/nmt

https://github.com/theamrzaki/text_summurization_abstractive_methods

https://gist.github.com/ilblackdragon/c92066d9d38b236a21d5a7b729a10f12

Tháng Năm 4, 2019
ITechSeeker