<jupyter_start><jupyter_text>Lab 06: Recurrent Neural Network (RNN)Trong bài thực hành này:- Cài đặt 1 mạng RNN cơ bản LSTM- Sử dụng Word Embedding GLOVE của Stanford- Chạy trên data spam detectionReference:- Glove: https://github.com/stanfordnlp/GloVe- LSTM: Long Short-Term Memory layer - Hochreiter 1997.Đọc thêm:- LSTM: https://colah.github.io/posts/2015-08-Understanding-LSTMs/  Tiền xử lý dữ liệuChúng ta cần tách câu thành từng từ trước.<jupyter_code>import pandas as pd
import numpy as np
import nltk

df = pd.read_csv("spam_detection.csv")
df.head()
texts = df["Text"].to_list()
texts = [text.lower() for text in texts]            # chuyển các đoạn text thành chữ thường (word embedding chỉ cho chữ thường)
tokenized_texts = [nltk.tokenize.word_tokenize(text) for text in texts]    # tách câu thành một list các từ

print(tokenized_texts[0])<jupyter_output>['go', 'until', 'jurong', 'point', ',', 'crazy', '..', 'available', 'only', 'in', 'bugis', 'n', 'great', 'world', 'la', 'e', 'buffet', '...', 'cine', 'there', 'got', 'amore', 'wat', '...']<jupyter_text>Load embedding từ filePretrained Embeddings từ Glove-Stanford đã được rút gọn cho bài tập này và lưu thành file glove_embedding.txt.<jupyter_code>## không cần hiểu đống này lắm đâu
import io
import numpy as np
def load_word_embeddings(fname):
    fin = io.open(fname, 'r', encoding='utf-8', newline='\n', errors='ignore')
    vocab, matrix = [], []
    i=0
    for line in fin:
        tokens = line.rstrip().split(' ')
        vocab.append(tokens[0])
        matrix.append(list(map(float, tokens[1:])))
    return vocab, np.asarray(matrix)
vocab, matrix = load_word_embeddings("glove_embedding.txt")<jupyter_output><empty_output><jupyter_text>Sau khi đọc xong thì- vocab: một danh sách các từ vực có trong embedding- matrix: một ma trận, mỗi dòng là một embedding cho từ tương ứng trong vocab (xếp đúng thứ tự)  Số hóa dataĐể số hóa 1 từ (word) trong ngôn ngữ tự nhiên, người ta sẽ biểu diễn từ đó thành một vector (gọi là embedding). 2 bước trước ta đã tách các câu trong data thành từ riêng biệt, và load một bộ embedding có sẵn. Bây giờ ta chuyển từng từ trong data thành một mã số biểu thị vị trí của từ đó trong ma trận embedding.Tuy nhiên, ta cần có vài mã số đặc biệt để giải quyết các vấn đề như: - từ không có trong embedding- Độ dài các câu không giống nhau. Cơ bản, các thư viện deep learning tính toán nhanh dựa trên các kĩ thuật tính toán ma trận (tensor), nên để tính các câu có độ dài ngắn khác nhau, các câu ngắn cần được nối thêm bởi các mã đặc biệt để có cùng kích thước.<jupyter_code>## Gán các mã
__PADDED_INDEX__ = 0    # mã dùng cho các vị trí chỉ có tính nối dài cho cùng kích thước
__UNKNOWN_WORD__ = 1    # mã cho những từ không có trong embedding
# Tạo một dictionary, có nhiệm vụ là một ánh xạ từ ảnh sang mã số, mã số được bắt đầu từ 2 vì số 0 và 1 được dành cho trường hợp đặc biệt
word_to_index = {word: index+2 for index, word in enumerate(vocab)}

# Do do mã số được bắt đầu từ 2, nên cần thêm 2 vector vào đàu ma trận
embedding_matrix = np.pad(matrix, [[2,0],[0,0]], mode='constant', constant_values =0.0)
print(embedding_matrix)

# Khi đó, __PADDED_INDEX__ dùng dòng đầu tiên của  embedding_matrix
# __UNKNOWN_WORD__ dùng dòng thứ hai của embedding_matrix 
## Bây giờ ta sẽ chuyển data spam dection thành các mã số
import tensorflow as tf

X = []
for text in tokenized_texts:
    cur_text_indices = []
    for word in text:
        if word in word_to_index:
            cur_text_indices.append(word_to_index[word])    ## map từ word sang index
        else:
            cur_text_indices.append(__UNKNOWN_WORD__)       ## gán unknown cho từ không có trong bộ glove
    X.append(cur_text_indices)

## pad data cho có cùng chiều dài
X = tf.keras.preprocessing.sequence.pad_sequences(sequences=X,       # sequences: list các câu có độ dài không bằng nhau
                                                  padding='post')    # vị trí pad là 'pre' (trước) hoặc 'post' (sau)

y = df['y'].values   ## Label của bài toán, 0 là không phải spam, 1 là spam
## Chia data
from sklearn.model_selection import train_test_split
X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size= 0.2, random_state =0)<jupyter_output><empty_output><jupyter_text>RNN trong tensorflow<jupyter_code>from tensorflow.keras.layers import Input, Embedding, LSTM, Bidirectional, Dense
from tensorflow.keras.models import Model

inputs = Input(shape=(None,))                   ## None biểu thị kích thước không xác định của câu

embed = Embedding(input_dim=embedding_matrix.shape[0],   ## Khai báo kích thước của vocab
                 output_dim=embedding_matrix.shape[1],   ## Khai báo kích thước của embedding
                  embeddings_initializer = tf.keras.initializers.Constant(value=embedding_matrix),  ## Khởi tạo cho embedding bằng ma trận có sẵn
                  trainable=False,                       ## Không cần thiết train embedding
                 mask_zero=True)(inputs)                 ## zero_mask: những vị trí có giá trị 0 không được tính toán, vì đó là giá trị thêm vào cho đủ độ dài mà thôi
                                                         ##  (__PADDED_INDEX__ gán bằng 0)

lstm = LSTM(units=100,                          ## units: kích thước của hidden_state trong LSTM
            return_sequences=False)(embed)      ## return_sequences: LSTM trả về toàn bộ  hay là trả về hidden_state cuối cùng

dense = Dense(units=2, activation='softmax')(lstm)
model = Model(inputs=inputs,
              outputs=dense)

model.compile(optimizer='adam',
             loss='sparse_categorical_crossentropy',
             metrics=['accuracy'])
model.summary()
# Checkpoint Callback
mc = tf.keras.callbacks.ModelCheckpoint(filepath="lstm_spam.h5", 
                                     monitor='val_loss',
                                     mode='min', 
                                     verbose=0, 
                                     save_best_only=True)
# Train
model.fit(X_train, y_train, 
          validation_data=(X_valid, y_valid),
         epochs=10,
         callbacks=[mc])

model.load_weights("lstm_spam.h5")
_, val_acc = model.evaluate(X_valid, y_valid)
print("Accuracy on valid: ", val_acc)<jupyter_output>Train on 4459 samples, validate on 1115 samples
Epoch 1/10
  32/4459 [..............................] - ETA: 10:54WARNING:tensorflow:Can save best model only with val_loss available, skipping.