Text Classification

Phân loại text theo chủ đề

Phân loại text theo chủ đề

Bài này mình xin phép đổi chủ đề một chút. Chúng ta sẽ thử làm bài toán phân loại text theo các chủ đề khác nhau. Đây là một trong những bài toán thuộc phạm vi của chủ đề xử lý ngôn ngữ tự nhiên (NLP).

Mình sẽ sử dụng bộ dữ liệu BBC news để thực hành. Bạn hãy download của 2 file Train.csv và Test.csv, sau đó gộp chung chúng lại thành 1 file để làm dữ liệu huấn luyện. Tổng số records là 2225, chia thành 6 chủ đề.

Đầu tiên, import các thư viện sẽ sử dụng:

import csv
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

Khai báo một số tham số:

vocab_size = 1000
embedding_dim = 16
max_length = 120
trunc_type = 'post'
padding_type = 'post'
oov_tok = '<OOV>'
training_portion = 0.8

sentences = []
labels = []
stopwords = [ "a", "about", "above", "after", "again", "against", "all", "am", "an", "and", "any", "are", "as", "at", "be", "because", "been", "before", "being", "below", "between", "both", "but", "by", "could", "did", "do", "does", "doing", "down", "during", "each", "few", "for", "from", "further", "had", "has", "have", "having", "he", "he'd", "he'll", "he's", "her", "here", "here's", "hers", "herself", "him", "himself", "his", "how", "how's", "i", "i'd", "i'll", "i'm", "i've", "if", "in", "into", "is", "it", "it's", "its", "itself", "let's", "me", "more", "most", "my", "myself", "nor", "of", "on", "once", "only", "or", "other", "ought", "our", "ours", "ourselves", "out", "over", "own", "same", "she", "she'd", "she'll", "she's", "should", "so", "some", "such", "than", "that", "that's", "the", "their", "theirs", "them", "themselves", "then", "there", "there's", "these", "they", "they'd", "they'll", "they're", "they've", "this", "those", "through", "to", "too", "under", "until", "up", "very", "was", "we", "we'd", "we'll", "we're", "we've", "were", "what", "what's", "when", "when's", "where", "where's", "which", "while", "who", "who's", "whom", "why", "why's", "with", "would", "you", "you'd", "you'll", "you're", "you've", "your", "yours", "yourself", "yourselves" ]

Chúng ta có một mảng chứa các stop words, tức là các từ thường hay xuất hiện trong câu nhưng lại không mang nhiều ý nghĩa. Chúng ta sẽ loại bỏ chúng đi trước khi huấn luyện model phân loại.

Bây giờ, ta sẽ đọc dataset và chuẩn bị dữ liệu training:

with open('bbc-text.csv', 'r') as csvfile:
    reader = csv.reader(csvfile, delimiter=',')
    next(reader)
    for row in reader:
        labels.append(row[0])
        sentence = row[1]
        # remove stop words
        for word in stopwords:
            token = ' ' + word + ' '
            sentence = sentence.replace(token, ' ')
            sentence = sentence.replace('  ', ' ')
        sentences.append(sentence)
print(len(sentences))

Chia dataset thành 2 phần: train và validation:

train_size = int(len(sentences) * training_portion)

train_sentences = sentences[:train_size]
train_labels = labels[:train_size]

validation_sentences = sentences[train_size:]
validation_labels = labels[train_size:]

Để model có thể hiểu được dataset, cần phải chuyển các câu dạng text sang dạng vector:

# chuyển text sang vector
tokenizer = Tokenizer(num_words=vocab_size, oov_token=oov_tok)
tokenizer.fit_on_texts(train_sentences)
word_index = tokenizer.word_index

label_tokenizer = Tokenizer()
label_tokenizer.fit_on_texts(labels)
training_label_seq = np.array(label_tokenizer.texts_to_sequences(train_labels))
validation_label_seq = np.array(label_tokenizer.texts_to_sequences(validation_labels))

# padding để các câu có cùng chiều dài
train_sequences = tokenizer.texts_to_sequences(train_sentences)
train_padded = pad_sequences(train_sequences, padding=padding_type, maxlen=max_length)
validation_sequences = tokenizer.texts_to_sequences(validation_sentences)
validation_padded = pad_sequences(validation_sequences, padding=padding_type, maxlen=max_length)

Mình sẽ đi chi tiết phần này trong 1 bài viết khác. Hôm nay các bạn chỉ cần hiểu ý tưởng của nó như vậy là được rồi.

Sau khi đã có dữ liệu huấn luyện, giờ là lúc chúng ta định nghĩa kiến trúc model.

model = tf.keras.Sequential([
    tf.keras.layers.Embedding(input_dim=vocab_size, output_dim=embedding_dim, input_length=max_length),
    tf.keras.layers.GlobalAveragePooling1D(),
    tf.keras.layers.Dense(24, activation='relu'),
    tf.keras.layers.Dense(6, activation='softmax')
])

model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['acc'])
model.summary()

Ở bài này, mình chỉ sử dụng một model đơn giản gồm các lớp Embedding, GlobalAveragePooling1D, và Dense.

Hàm plot để vẽ đồ thị quá trình training:

def plot_graph(history):
    acc = history.history['acc']
    val_acc = history.history['val_acc']
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    epochs = range(len(acc))
    
    plt.figure(figsize=(10,6))
    plt.plot(epochs, acc, 'r', label='Training Accuracy')
    plt.plot(epochs, val_acc, 'b', label='Validation Accuracy')
    plt.plot(epochs, loss, 'g', label='Training Loss')
    plt.plot(epochs, val_loss, 'y', label='Validation Loss')
    
    plt.title('Training & Validation, Accuracy & Loss')
    plt.legend(loc=0)
    plt.show()

Bước cuối cùng là chạy train model, ta sẽ train model với 30 epochs:

num_epochs = 30
history = model.fit(
    train_padded, 
    training_label_seq, 
    epochs=num_epochs, 
    validation_data=(validation_padded, validation_label_seq), 
    verbose=1
)

Training output:

Epoch 1/30
56/56 [==============================] - 0s 4ms/step - loss: 1.7737 - acc: 0.2180 - val_loss: 1.7481 - val_acc: 0.2270
Epoch 2/30
56/56 [==============================] - 0s 2ms/step - loss: 1.7163 - acc: 0.2303 - val_loss: 1.6755 - val_acc: 0.2270
Epoch 3/30
56/56 [==============================] - 0s 2ms/step - loss: 1.6299 - acc: 0.2371 - val_loss: 1.5792 - val_acc: 0.2539
....................
Epoch 27/30
56/56 [==============================] - 0s 2ms/step - loss: 0.0580 - acc: 0.9949 - val_loss: 0.2115 - val_acc: 0.9506
Epoch 28/30
56/56 [==============================] - 0s 2ms/step - loss: 0.0521 - acc: 0.9961 - val_loss: 0.2080 - val_acc: 0.9506
Epoch 29/30
56/56 [==============================] - 0s 2ms/step - loss: 0.0467 - acc: 0.9961 - val_loss: 0.2051 - val_acc: 0.9506
Epoch 30/30
56/56 [==============================] - 0s 2ms/step - loss: 0.0420 - acc: 0.9989 - val_loss: 0.2014 - val_acc: 0.9506

Quá trình train diễn ra khá nhanh, mất khoảng 2 phút trên máy tính của mình. Tại epochs cuối cùng, độ chính xác trên tập validation là 95,06%.

Toàn bộ quá trình này được thể hiện trên đồ thị như sau:

plot_graph(history)

Model hội tụ khá nhanh và cho kết quả tốt, không có hiện tượng overfitting. Có lẽ train thêm một số epochs nữa sẽ cho kết quả tốt hơn. Bạn có thể thử.

Source code của bài này, các bạn có thể tham khảo trên github cá nhân của mình tại đây.

Trong bài viết tiếp theo, mình sẽ vẫn thực hành bài toán phân loại văn bản nhưng sử dụng kỹ thuật Transfer Learning giống như bên Computer Vision. Mời các bạn đón đọc!

Tham khảo