Ứng Dụng Kiến Trúc Text-RNN Cho Phân Loại Ý Đoán Trong Hệ Thống Chatbot

Tổng Quan Về Mô Hình

Mô hình Text-RNN (Recurrent Neural Network) là một lựa chọn phổ biến trong bài toán phân lớp văn bản. Trong ngữ cảnh hệ thống hỏi đáp (Question Answering), nhiệm vụ chính là dự đoán ý định (intent) của người dùng dựa trên câu lệnh nhập vào. Sử dụng mạng lưới thần kinh hồi tiếp có khả năng nắm bắt được thông tin ngữ cảnh và thứ tự từ ngữ, giúp cải thiện độ chính xác so với các phương pháp đơn giản như Bag-of-Words.

Cấu Trúc Dữ Liệu Và Tiền Xử Lý

Quy trình khởi tạo dữ liệu bao gồm việc tải tập hợp câu hỏi từ file CSV, tách câu thành các từ riêng lẻ và xây dựng từ điển (vocabulary). Việc sử dụng các vector nhúng đã được huấn luyện trước (pre-trained embeddings) giúp mô hình hiểu sâu hơn về nghĩa của từ mà không cần học từ đầu.

# Cấu hình thiết bị và đường dẫn
import os, sys
sys.path.append(os.getcwd())
import torch
from torch.nn.utils.rnn import pad_sequence
from torchtext.data import Field, Example, Dataset, BucketIterator, Iterator

# Xác định vị trí GPU hoặc CPU
if torch.cuda.is_available():
    device_config = torch.device('cuda')
else:
    device_config = torch.device('cpu')

# Thiết lập các tham số tiền xử lý
def split_tokens(sentence):
    return sentence.strip().split(' ')

# Khai báo trường dữ liệu cho câu và nhãn
sentence_field = Field(
    tokenize=split_tokens,
    lower=True,
    sequential=True,
    batch_first=True,
    unk_token='<unk>',
    pad_token='<pad>')

category_field = Field(
    sequential=False,
    use_vocab=False)

# Hàm hỗ trợ tải tập dữ liệu
def load_corpus(data_df, is_eval_mode=False):
    fields = [('id', None), ('input_text', sentence_field), ('target_label', category_field)]
    data_examples = []
    
    if is_eval_mode:
        for txt in data_df['input_text']:
            example = Example.fromlist([None, txt, None], fields)
            data_examples.append(example)
    else:
        for txt, lbl in zip(data_df['input_text'], data_df['target_label']):
            example = Example.fromlist([None, txt, lbl], fields)
            data_examples.append(example)
            
    return data_examples, fields

# Đọc dữ liệu gốc
import pandas as pd
csv_file_path = './classification_data.csv'
raw_data = pd.read_csv(csv_file_path)
examples_set, current_fields = load_corpus(raw_data)
base_dataset = Dataset(examples_set, current_fields)

Xây Dựng Từ Điển và Nhúng

Sau khi dữ liệu được chuẩn hóa, bước tiếp theo là xây dựng từ vựng dựa trên tần suất xuất hiện tối thiểu. Các vector từ ngữ sẽ được tải từ nguồn bên ngoài (ví dụ: Sogou Word Vector) để khởi tạo trọng số của tầng Embedding.

# Tạo thư mục lưu trữ nếu chưa tồn tại
os.makedirs('output_models', exist_ok=True)

# Build vocab với từ khóa nhúng
sentence_field.build_vocab(base_dataset, min_freq=1, vectors='sgns.sogou.char')

# Lưu danh sách từ khóa
with open('words_list.pkl', 'wb') as f_out:
    pickle.dump(sentence_field.vocab, f_out)

# Thiết lập bộ lặp (Iterator) cho quá trình huấn luyện
BATCH_SIZE_SETTING = 163
train_loader = BucketIterator(
    dataset=base_dataset, 
    batch_size=BATCH_SIZE_SETTING, 
    train=True,
    sort_within_batch=False)

# Gán vector pre-trained vào mô hình (có thể Fine-tune)
emb_vectors = sentence_field.vocab.vectors
em_dim = emb_vectors.shape[1]

Định Nghĩa Lớp Mô Hình

Mô hình kế thừa từ `nn.Module`, bao gồm tầng nhúng từ, tầng LSTM hai chiều (Bidirectional) để trích xuất đặc trưng chuỗi, và tầng Dense để phân lớp đầu ra.

import torch.nn as nn
import torch.nn.functional as F

class IntentClassifier(nn.Module):
    def __init__(self, voc_size, embed_dim, hidden_dim, num_lay, out_class, dr_rate=0.5):
        super(IntentClassifier, self).__init__()
        
        # Tầng Embedding
        self.emb_layer = nn.Embedding(voc_size, embed_dim)
        # Tầng LSTM Bidirectional
        self.rnn_layer = nn.LSTM(
            input_size=embed_dim,
            hidden_size=hidden_dim,
            num_layers=num_lay,
            bidirectional=True,
            dropout=dr_rate,
            batch_first=True
        )
        # Tầng Fully Connected đầu ra
        # Hidden size nhân 2 do là Bidirectional
        self.out_fc = nn.Linear(hidden_dim * 2, out_class)
        
    def forward(self, x_input):
        # Kích thước đầu vào: (Batch, Seq_Len)
        # Chuyển sang (Batch, Seq_Len, Embed_Dim)
        vec_emb = self.emb_layer(x_input) 
        
        # LSTM trả về output, h_n, c_n
        _, (final_h, _) = self.rnn_layer(vec_emb)
        
        # Lấy trạng thái ẩn cuối cùng của cả hai chiều
        # concat last hidden states of all layers
        out_features = final_h[-2, :, :] + final_h[-1, :, :] # Tổng hợp state
        
        # Chuyền qua tuyến tính
        logits = self.out_fc(out_features)
        return logits

Vòng Lặp Huấn Luyện

Quá trình huấn luyện sử dụng thuật toán SGD kết hợp động lượng để cập nhật trọng số. TensorBoard được kích hoạt để giám sát chỉ số suy giảm mất mát (Loss) theo thời gian thực.

from torch.utils.tensorboard import SummaryWriter

# Khởi tạo writer để log
tb_logger = SummaryWriter('./logs/textrnn_run')

# Cài đặt siêu tham số huấn luyện
VOC_SIZE = len(sentence_field.vocab)
OUT_DIM = 16
HIDDEN_DIM = 128
NUM_LAYERS = 2
LEARNING_RATE = 0.1
TOTAL_EPOCHS = 600

# Khởi tạo model
model_instance = IntentClassifier(VOC_SIZE, em_dim, HIDDEN_DIM, NUM_LAYERS, OUT_DIM).to(device_config)

# Copy weights pre-trained vào embedding layer
model_instance.emb_layer.weight.data.copy_(emb_vectors)

# Tối ưu hóa hàm mất mát
optimizer_inst = torch.optim.SGD(model_instance.parameters(), lr=LEARNING_RATE, momentum=0.95, nesterov=True)
loss_func = nn.CrossEntropyLoss()

model_instance.train()

# Bắt đầu vòng lặp training
for epoch_idx in range(TOTAL_EPOCHS):
    for i, batch_item in enumerate(train_loader):
        # Di chuyển dữ liệu lên GPU nếu cần
        input_tensor = batch_item.input_text.to(device_config)
        label_tensor = batch_item.target_label.to(device_config)
        
        # Dự đoán và tính Loss
        prediction = model_instance(input_tensor)
        current_loss = loss_func(prediction, label_tensor.long())
        
        # Backpropagation
        optimizer_inst.zero_grad()
        current_loss.backward()
        optimizer_inst.step()
        
        # Log tiến trình mỗi 10 epoch
        if (epoch_idx + 1) % 10 == 0:
            tb_logger.add_scalar('training_loss', current_loss.item(), global_step=epoch_idx + 1)
            print(f"Epoch [{epoch_idx+1}/{TOTAL_EPOCHS}], Loss: {current_loss.item():.4f}")

tb_logger.flush()
tb_logger.close()

# Lưu trọng số mô hình
save_path = './output_models/classifier_model.pth'
torch.save(model_instance.state_dict(), save_path)
print("Hoàn tất lưu mô hình.")

Kết Quả Thử Nghiệm Ban Đầu

Dưới đây là biểu đồ suy giảm Loss trong suốt quá trình chạy thử, cho thấy mô hình dần hội tụ.

Epoch [10/600], Loss: 2.7375
Epoch [20/600], Loss: 2.7167
Epoch [50/600], Loss: 2.7152
Epoch [100/600], Loss: 2.3200
Epoch [200/600], Loss: 1.5200
Epoch [300/600], Loss: 0.3708
Epoch [400/600], Loss: 0.0592
Epoch [500/600], Loss: 0.0102
Epoch [600/600], Loss: 0.0020

Thẻ: PyTorch text-rnn intent-classification lstm NLP

Đăng vào ngày 7 tháng 7 lúc 16:46