Trong bài tutorial này, chúng ta sẽ tiến hành xây dựng và huấn luyện một Text Generation Model với Recurrent Neural Network và sử dụng thư viện Keras của TensorFlow. Đối với bài toán Text generation thì thường có hai hướng tiếp cận chính là Character-level text generation (xây dựng mô hình học máy dựa trên từng ký tự) và Word-level text generation (xây dựng mô hình học máy dựa trên từng từ). Thông thường Word-level cho kết quả với độ chính xác các hơn với thời gian training ngắn hơn và Model ít phức tạp hơn (do Character-level có ít input và output hơn nên cần phải xây dựng một mô hình phức tạp để có thể biểu diễn hết các mối quan hệ). Tuy nhiên, đối với một số ngôn ngữ có cấu trúc từ phức tạp (rich morphology languages) như tiếng Nga, Phần Lan, Thổ Nhĩ Kỳ… thì Character-level lại tỏ ra hiệu quả hơn. Ngoài ra Character-level còn có thể khắc phục nhược điểm Out-of-Vocabulary (OOV) trong Word-level (Word-level không thể tạo ra các từ không có trong phần training)
Về cơ bản thì cả Character-level và Word-level text generation đều có các bước tương tự nhau trong việc xây dựng và huấn luyện Model. Ở đây, chúng ta sẽ sử dụng Word-level text generation với dataset là cuốn truyện “Battles with the Sea” (các bạn có thể tham khảo về Character-level text generation tại đây). Sau khi download text của cuốn truyện trên, ta tiến hành loại bỏ các phần không cần thiết cho việc huấn luyện mô hình Text generation như phần giới thiệu ở trước ‘CHAPTER ONE’, xóa bỏ tiêu đề của các Chapter và phần sau ‘THE END’.
Ta tiến hành đọc dữ liệu và hiển thị 500 ký tự đầu tiên như sau (các bạn tham khảo cách sử dụng Google Colab trong bài tutorial Lập trình TensorFlow với Google Colab):
1 2 3 4 5 |
# Read text from file text=open('Data/Battles-with-the-Sea.txt').read() # Display the first 500 character print(text[:500]) |
Tiếp theo, chúng ta thực hiện việc tách từng từ trong đoạn dữ liệu ở trên và hiển thị các tokens này như sau (Lưu ý: các từ trong tiếng Anh thường được phân tách bằng space nên ở đây ta có thể sử dụng hàm .split(). Tuy nhiên đối với một số ngôn ngữ khác, bao gồm cả tiếng Việt, thì chúng ta cần phải sử dụng Tokenizer do các từ không được phân tách bằng space như trong tiếng Anh)
1 2 3 4 5 6 |
# Split the text by space ' ' tokens=text.split() # Print the tokens for i in range(int(len(tokens)/10)): print(tokens[i*10:(i+1)*10]) |
Các tokens ở trên đều là các tokens ở dạng thô, do đó để có một training data tốt hơn, chúng ta cần phải thực hiện một số bước Data cleaning như sau:
– Thay dấu ‘-’ và ‘–’ trong các từ như manne–only, we–especially, land–are, sea-coast, … bằng space để tách các từ này thành các từ độc lập.
– Loại bỏ các dấu câu có trong các từ như: ‘wanting.’,’brave,’,’sea.’,..
– Chỉ giữ lại các từ mà tất cả các ký tự đều là alphabets
– Chuyển tất cả các từ về dạng lowercase
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import string # Replace '--' and '-' with a space ' ' text=text.replace("--"," ") text=text.replace("-"," ") # Split text into words words=list() for word in tokens: # Remove punctuation from each word word=word.translate(str.maketrans('','',string.punctuation)) # Remove words containing non-alphabetic characters if(word.isalpha()): # Convert word to lowercase word=word.lower() words.append(word) print("Total number of words: ",len(words)) print("Total number of unique words: ", len(set(words))) # Print the words for i in range(int(len(words)/10)): print(words[i*10:(i+1)*10]) |
Như vậy, chúng ta thu được một dataset với tổng 23.662 từ và vocab-size= 3885 (trong thực tế, chúng ta cần một dataset với số lượng từ và vocab-size lớn hơn nhiều để xây dựng một Model với độ chính xác cao). Tuy nhiên, chúng ta không thể sử dụng các words là đầu vào trực tiếp để huấn luyện Model mà cần phải chuyển các words này thành dạng số (như đã đề cập trong bài Word Embedding). Công việc này được thực hiện bằng cách sử dụng 2 từ điển:
– word2int: được sử dụng ở phần input để chuyển dữ liệu đầu vào từ dạng word thành int. Ở đây, chúng ta đánh chỉ số cho các từ dựa trên Word frequency theo thứ tự giảm dần. Tức là từ nào xuất hiện càng nhiều thì sẽ có chỉ số càng thấp. Ví dụ như từ ‘the’ tương ứng với giá trị 0 hoặc từ ‘and’ tương ứng với giá trị là 2 như kết quả bên dưới.
– int2word: được sử dụng ở phần output để chuyển dữ liệu đầu ra của Model từ dạng int thành word. Ví dụ: nếu kết quả output là 1, tức là Model dự đoán từ được tạo ra là ‘of’.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import collections # Arrage the words from the most common to the least word_counter = collections.Counter(words).most_common() # Map each word to an integer word2int=dict() int2word=dict() for word,_ in word_counter: index=len(word2int) word2int[word]=index int2word[index]=word # The size of vocab vocab_size=len(word2int) print('The top 5 popular words: ',word_counter[:5]) print('The size of Vocab: ',vocab_size) print('The first five words in word2int: ') for i in range(5): print("\t",word_counter[i][0]," : ", word2int[word_counter[i][0]]) print('The first five words in int2word: ') for i in range(5): print("\t",i," : ", int2word[i]) |
Với việc sử dụng word2int, chúng ta có thể chuyển dữ liệu từ dạng word thành dữ liệu dưới dạng int với dòng code sau:
1 2 3 4 5 |
# Convert sequence of words to sequence of int word2int_seq=[word2int[word] for word in words] print('The first ten words: ', words[:10]) print('The corresponding int sequence: ', word2int_seq[:10]) |
Bước tiếp theo chúng ta cần thực hiện là xây dựng dữ liệu đầu vào và tartget ouput cho việc huấn luyên Model. Ở đây chúng ta sử dụng RNN với seq-length là 50. Tức là sử dụng 50 từ cho trước để dự đoán từ tiếp theo. Ví dụ như khi ta sử dụng seq-length = 5 trong câu: ‘this is an example of text generation using rnn’. Nếu input là ‘ this is an example of’, thì target sẽ là ‘text’. Tương tự, nếu input là ‘ is an example of text’, thì target sẽ là ‘generation’,… Việc xây dựng input và output data được thực hiện như sau (ở đây chúng ta chuyển output về dạng one-hot encoding để Model có thể dự đoán xác xuất của mỗi từ trong Vocab rồi chọn từ có xác xuất lớn nhất là Output):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import numpy as np from tensorflow import keras # Set the sequence length seq_length=50 # Convert the input data to a list of sequences inputSequences=list()ulti outputs=list() for i in range(seq_length, len(word2int_seq)): seq=word2int_seq[i-seq_length:i] inputSequences.append(seq) outputs.append(word2int_seq[i]) # Convert inputSequences to numpy array inputSequences= np.array(inputSequences) # One hot encode the output one_hot_outputs=keras.utils.to_categorical(outputs,num_classes=vocab_size) print('Shape of inputSequences: ', inputSequences.shape) print('Shape of outputs: ', one_hot_outputs.shape) print('\nThe first input : ') print('Text format: ',words[:seq_length]) print('Int format: ',inputSequences[0]) print('\nThe Expected output : ') print('One hot encoding format: ',one_hot_outputs[0]) print('Int format: ',outputs[0]) print('Text format: ',int2word[outputs[0]]) |
Như vậy, ta đã tạo ra một training set với 23612 dữ liệu training và mỗi dữ liệu training là một dãy 50 số tự nhiên tương ứng với 50 từ trong đoạn văn bản. Tiếp theo ta tiến hành xây dựng một Model với 5 layers như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# Define the dimension of embedding embedding_dim=50 lstm1_unit=64 lstm2_unit=128 hiden1=128 # Build a model using keras.Sequential() model=keras.Sequential() # Add an Embedding layer model.add(keras.layers.Embedding(input_dim=vocab_size,output_dim=embedding_dim,input_length=seq_length)) # Add a LSTM layer using CuDNNLSTM for GPU training # We have to set return_sequences=True for the second LSTM layer model.add(keras.layers.CuDNNLSTM(units=lstm1_unit,return_sequences=True)) # Add the second LSTM layer model.add(keras.layers.CuDNNLSTM(units=lstm2_unit)) # Add a Dense layer model.add(keras.layers.Dense(hiden1,activation='relu')) # Add the output layer model.add(keras.layers.Dense(vocab_size,activation='softmax')) # Display model summary print(model.summary()) |
Ở đây, chúng ta xây dựng mô hình với 2 layers của LSTM (xem thêm phần Multi-layer RNN network trong bài Các biến thể của RNN). Với LSTM Layer1, chúng ta phải thiết lập tham số ‘return_sequences=True’ để đảm bảo rằng LSTM Layer2 sẽ nhận được đúng dữ liệu đầu vào. Ngoài ra, chúng ta sử dụng Embedding Layer là Layer đầu tiên của Model để tạo Word Embedding(chúng ta có thể sử dụng pre-trained Word Embedding như hướng dẫn tại đây) .
Lưu ý:
– Tham số units trong keras.layers.CuDNNLSTM chính là dimension của hidden state của một LSTM cell (tương tự như việc chúng ta cho dữ liệu với 5 features vào một mạng Feed Forword Network (1 Layer) với 10 nodes thì ta được output với 10 dimensions)
– Mặc định thì ‘return_sequences=False’ tức là LSTM chỉ trả về kết quả là hidden state của cell cuối cùng (many to one RNN) và dimension của từng hidden state là giá trị units được thiết lập. Do đó, Output Shape của cu_dnnlstm_3 ở hình trên là là (None,128) vì mỗi một sequence chỉ cho kết quả là 1 output với 128 dimensions.
– Khi ta thiết lập ‘return_sequences=True’ tức là LSTM sẽ trả về kết quả là hidden state của từng cell với số lượng cell chính là độ dài của một sequence(many to many RNN). Do đó, Output Shape của cu_dnnlstm_2 ở hình trên là là (None, 50, 64) vì mỗi một sequence cho ra kết quả là outputs của 50 cells và output của từng cell có 64 dimensions.
Sau khi xây dựng Model xong, chúng ta tiến hành việc huấn luyện Model như sau:
1 2 3 4 5 |
# Compile the model model.compile(optimizer=keras.optimizers.Adam(lr=0.01), loss='categorical_crossentropy', metrics=['accuracy']) # Start training model.fit(inputSequences, one_hot_outputs, batch_size=128, epochs=150) |
Sau khi huấn luyên Model xong, chúng ta thử sử dụng Model này để tạo một đoạn văn từ một chuỗi đầu vào như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# Input sequence for testing input_test=inputSequences[0] # Using Model to generate next 50 words predicted_words=list() for i in range(50): # Get the prediction of the next word (integer number) pred = model.predict_classes(np.reshape(input_test,(1,seq_length)), verbose=0) # Get the next input sequence by appending the predicted word and remove the first word input_test= np.append(input_test,pred[0]) input_test=input_test[1:] # Convert integer value to word and append it to the list predicted_words.append(int2word[pred[0]]) print('The input sequence: ') print(' '.join(words[:50])) print('\nThe original words sequence: ') print(' '.join(words[50:100])) print('\nThe words sequence generated by the Model: ') print(' '.join(predicted_words)) |
Lưu ý: tutorial này chỉ là một ví dụ để giúp các bạn hiểu rõ hơn về RNN và cách xây dựng một mô hình multi-layer RNN, do đó chúng ta chỉ sử dụng một Dataset nhỏ và không sử dụng Testset hoặc Validation set.
Các bạn có thể tham khảo Notebook của tutorial này tại đây.