BƯỚC 01
Chuẩn bị và cách học
Học sau bài Word2Vec. Vector từ đã biểu diễn nội dung token, nhưng cần thêm cơ chế xử lý thứ tự.
Bag of Words không giữ thứ tự. RNN cập nhật một trạng thái khi đọc từng token, nên thứ tự có thể ảnh hưởng đầu ra. Bài này dùng trọng số cố định để xem phép tính bên trong, chưa huấn luyện mô hình hiểu câu.
Cần biết biến, hàm và vòng lặp Python. Tạo thư mục riêng, mở PowerShell tại đó rồi chạy các lệnh bên dưới. Ví dụ dùng dữ liệu tự tạo, chạy trên CPU.
py -m venv .venv
.\.venv\Scripts\python.exe -m pip install numpy matplotlibÝ nghĩa đoạn mã
venv tạo môi trường riêng. Lệnh thứ hai cài đúng thư viện vào môi trường đó. Chạy trực tiếp Python trong .venv nên không cần kích hoạt môi trường.
BƯỚC 02
Trạng thái ẩn là kết quả tính, không phải bản ghi nguyên câu
Một bước đơn giản có dạng h_t = tanh(W_x x_t + W_h h_(t-1) + b). x_t là vector token hiện tại, h_(t-1) là trạng thái trước. W_x và W_h biến đổi hai nguồn thông tin về cùng số chiều, sau đó tanh đưa mỗi thành phần vào khoảng -1 đến 1.
Trạng thái đầu thường đặt bằng 0. Mỗi câu cần trạng thái đầu riêng trong ví dụ này. Nếu vô tình giữ trạng thái câu trước, kết quả câu sau có thể bị ảnh hưởng ngoài ý muốn.
from pathlib import Path
import numpy as np
import matplotlib.pyplot as plt
output = Path("nlp_rnn_output")
output.mkdir(exist_ok=True)
vectors = {"chó": np.array([1., 0., 0.]),
"đuổi": np.array([0., 1., 0.]),
"mèo": np.array([0., 0., 1.])}
wx = np.array([[0.8, -0.4, 0.2], [0.1, 0.7, -0.6]])
wh = np.array([[0.5, 0.1], [-0.2, 0.4]])
bias = np.zeros(2)
def encode(words):
hidden = np.zeros(2)
history = [hidden.copy()]
for word in words:
x = vectors[word]
hidden = np.tanh(wx @ x + wh @ hidden + bias)
history.append(hidden.copy())
return np.stack(history)Ý nghĩa đoạn mã
Mỗi token có vector one-hot 3 chiều, trạng thái ẩn có 2 chiều. wx có kích thước (2, 3), wh có kích thước (2, 2). copy giữ lịch sử từng bước thay vì phụ thuộc vào mảng đang cập nhật.
BƯỚC 03
Đảo câu nhưng giữ nguyên các token
Hai câu có cùng token nhưng thứ tự khác nhau. Ta chạy cùng một phép cập nhật, chỉ thay thứ tự đầu vào. Nếu trạng thái cuối khác nhau, phép tính đã có khả năng phân biệt thứ tự.
Điều đó chưa chứng minh mạng hiểu ai đuổi ai. Trọng số trong bài do ta đặt, không học từ nhãn. Muốn làm nhiệm vụ thật cần dữ liệu, hàm mất mát, huấn luyện và đánh giá trên câu chưa gặp.
first = ["chó", "đuổi", "mèo"]
second = ["mèo", "đuổi", "chó"]
a = encode(first)
b = encode(second)
print("First final:", np.round(a[-1], 4))
print("Second final:", np.round(b[-1], 4))
assert a.shape == (4, 2)
assert not np.allclose(a[-1], b[-1])
assert np.all(np.abs(a) <= 1)
assert np.allclose(encode(first), a)Ý nghĩa đoạn mã
Lịch sử có 4 hàng vì gồm trạng thái ban đầu và 3 bước đọc. Lệnh cuối chạy lại từ đầu để kiểm tra không rò trạng thái giữa hai lần gọi.
BƯỚC 04
Quan sát bộ nhớ và hiểu giới hạn
Ta vẽ hai thành phần trạng thái qua các bước. Đây là các con số bên trong mạng, không phải xác suất lớp. Một đường tăng không có nghĩa mô hình ngày càng chắc chắn.
Với chuỗi dài, RNN cơ bản có thể khó giữ thông tin và khó truyền gradient qua nhiều bước. LSTM bổ sung các cổng điều khiển luồng thông tin. Bài tiếp theo giới thiệu attention, nơi một vị trí có thể kết hợp trực tiếp thông tin từ nhiều vị trí khác.
fig, axes = plt.subplots(1, 2, figsize=(10, 4), sharey=True)
for ax, values, title in zip(axes, [a, b], ["cho duoi meo", "meo duoi cho"]):
ax.plot(range(4), values[:, 0], "o-", label="h[0]")
ax.plot(range(4), values[:, 1], "s-", label="h[1]")
ax.set_xticks(range(4))
ax.set_xlabel("Buoc doc, 0 = ban dau")
ax.set_title(title)
ax.grid(alpha=0.25)
ax.legend()
axes[0].set_ylabel("Gia tri trang thai an")
fig.tight_layout()
fig.savefig(output / "rnn_states.png", dpi=150)
plt.close(fig)Ý nghĩa đoạn mã
Hai ô dùng cùng thang trục y để so sánh công bằng. Mỗi dấu trên đường là một hàng trong mảng lịch sử.
BƯỚC 05
Chạy và tự kiểm tra
Hai trạng thái cuối phải khác nhau. Mọi giá trị đều nằm trong [-1, 1]. Mở rnn_states.png để xem sự khác biệt xuất hiện ngay từ token đầu. Nếu thay trọng số khác, con số sẽ đổi, nên đối chiếu quy tắc cập nhật thay vì ghi nhớ số.

.\.venv\Scripts\python.exe nlp_sequence_memory.pyÝ nghĩa đoạn mã
Lưu mã đầy đủ vào tệp cùng tên rồi chạy tại thư mục chứa tệp. Các ảnh và số liệu được ghi trong thư mục output riêng.
ĐẾN LƯỢT BẠN
Thử một thay đổi nhỏ
- Đặt wh thành ma trận 0 rồi chạy lại.
- Giải thích vì sao trạng thái cuối khi đó chỉ phụ thuộc token cuối.
- Sau đó tạo hai chuỗi khác nhau nhưng có cùng token cuối để kiểm chứng.




