Lập trình viên khi chuyển sang lĩnh vực xử lý ngôn ngữ tự nhiên (NLP) thường đối mặt với một câu hỏi hóc búa: Làm thế nào để máy tính hiểu được ý nghĩa của một từ ngữ? Máy tính chỉ xử lý các con số, ma trận và phép toán đại số tuyến tính, chứ không thể đọc hiểu trực tiếp chữ cái hay câu văn.

Bài học này sẽ hướng dẫn bạn quy trình số hóa văn bản một cách khoa học: từ việc bẻ nhỏ câu văn thành các mã token (Tokenization), vượt qua cạm bẫy từ ngoài từ điển (OOV), cho đến cơ chế biểu diễn ngữ nghĩa bằng không gian vector đa chiều (Word Embeddings). Chúng ta cũng sẽ tìm hiểu toán học của Cosine Similarity và thực hành xây dựng một ứng dụng tìm kiếm từ đồng nghĩa, giải bài toán tương đồng ngữ nghĩa bằng PyTorch.

✅ Bạn cần gì trước khi bắt đầu
Thư viện: pip install torch. Dự án cuối bài không tải dữ liệu, không cần mạng.

Kiến thức cần có: Bài 2 — tích vô hướngCosine Similarity. Mục 8.3 dùng lại đúng công thức đó, chỉ đổi từ so hai vector 3 chiều sang so hai vector 300 chiều. Nếu bạn còn lạ với "vector là mũi tên trong không gian", hãy đọc lại mục 2.1 trước.

8.1 Từ Chữ viết sang Con số: Tokenization

Trước khi nạp văn bản vào một mô hình học máy hay mạng nơ-ron, văn bản thô phải trải qua bước tiền xử lý bắt buộc gọi là Tokenization (Phân tách từ). Đây là quá trình chẻ một chuỗi ký tự dài thành các đơn vị thông tin nhỏ hơn, gọi là các tokens (có thể là ký tự đơn lẻ, các âm tiết, từ hoặc cụm từ).

Có ba hướng thiết kế Tokenizer chính:

  • Ký tự (Character-level Tokenization): Chia văn bản đến từng chữ cái đơn lẻ (a, b, c...).
    Ưu điểm: Từ điển cực kỳ nhỏ gọn (chỉ vài trăm ký tự), hoàn toàn không sợ gặp từ lạ.
    Nhược điểm: Máy tính phải xử lý chuỗi cực kỳ dài, và nơ-ron rất khó học được ngữ nghĩa tổng quan của từ vì thông tin bị bẻ vụn ở cấp độ chữ cái.
  • Từ thô (Word-level Tokenization): Chia văn bản dựa trên khoảng trắng để tách thành các từ hoàn chỉnh.
    Ưu điểm: Bảo toàn ngữ nghĩa nguyên bản của từ tốt.
    Nhược điểm: Bùng nổ kích thước từ điển (lên tới hàng triệu từ). Đặc biệt, khi gặp một từ mới chưa từng xuất hiện trong quá trình huấn luyện, mô hình sẽ gặp lỗi Out-of-Vocabulary (OOV) và bắt buộc phải chuyển từ đó thành mã rác đại diện [UNK] (Unknown), làm mất hoàn toàn thông tin của câu.
  • Cấp độ mảnh từ (Subword-level Tokenization): Đây là tiêu chuẩn vàng của các mô hình ngôn ngữ lớn (LLM) ngày nay như GPT hay BERT, sử dụng các thuật toán như BPE (Byte Pair Encoding) hoặc WordPiece.
    Cơ chế: Chia các từ phổ biến thành các từ nguyên bản, nhưng chẻ nhỏ các từ lạ hoặc từ ghép thành các mảnh tiền tố/hậu tố có nghĩa (ví dụ: từ "embeddings" có thể được chẻ thành "embed" và "##dings"). Điều này giúp từ điển duy trì ở kích thước tối ưu (khoảng 30.000 - 50.000 tokens) nhưng vẫn biểu diễn được bất kỳ từ mới nào bằng cách lắp ghép các mảnh nhỏ lại, loại bỏ triệt để lỗi OOV.
⚠️ Cạm bẫy: Tokenizer không đồng bộ giữa huấn luyện và suy luận
Mỗi bộ Tokenizer có một từ điển định danh số nguyên (Token IDs) được sinh ra riêng biệt. Nếu bạn huấn luyện mô hình bằng Tokenizer A, nhưng khi suy luận lại dùng Tokenizer B (dù cả hai đều chẻ cùng một câu văn), kết quả đầu ra sẽ là một đống lộn xộn hoàn toàn vô nghĩa. Hãy luôn lưu bộ Tokenizer cùng với tệp trọng số của mô hình để đảm bảo tính nhất quán của ID.
🧠 Thuật toán BPE chạy từng bước bằng tay
BPE không có gì huyền bí — nó chỉ lặp đi lặp lại một quy tắc: tìm cặp ký hiệu liền kề xuất hiện nhiều nhất trong toàn bộ kho ngữ liệu rồi gộp chúng thành một ký hiệu mới. Với kho ngữ liệu đồ chơi gồm 3 từ "low" (tần suất 5), "lowest" (tần suất 2), "newest" (tần suất 6) — mỗi từ được tách sẵn thành ký tự đơn lẻ cộng thêm dấu kết thúc từ </w>, thuật toán chạy như sau (đã kiểm chứng bằng code Python thực tế):
  • Bước 1: cặp (w, e) xuất hiện nhiều nhất (2 lần trong "lowest" + 6 lần trong "newest" = 8 lần) → gộp thành we.
  • Bước 2: cặp (we, s) xuất hiện 8 lần → gộp thành wes.
  • Bước 3: cặp (wes, t) xuất hiện 8 lần → gộp thành west.
  • Bước 4: cặp (west, </w>) xuất hiện 8 lần → gộp thành west</w>, tạo ra một token mảnh từ hoàn chỉnh dùng chung cho cả "lowest" và "newest".
Sau 4 bước gộp, từ điển mảnh từ đã tự học được rằng "west" là một hậu tố dùng chung, trong khi "low" (tần suất cao nhất, không nằm trong cặp phổ biến nhất) vẫn giữ nguyên dạng ký tự rời — đúng như cơ chế "chẻ từ hiếm, giữ nguyên từ phổ biến" đã mô tả ở trên.

Một chi tiết quan trọng nếu bạn tự cài BPE: ở bước 1 có tới bốn cặp đồng hạng, đều xuất hiện 8 lần — (w,e), (e,s), (s,t)(t,</w>). Ví dụ trên chọn (w,e), nhưng đó là một lựa chọn phá hoà tuỳ ý, không phải kết quả duy nhất đúng. Cài BPE với thứ tự phá hoà khác sẽ cho chuỗi gộp khác và bộ mảnh từ khác — mà vẫn hoàn toàn hợp lệ. Biết điều này để bạn không tưởng mình cài sai khi kết quả không khớp bài viết.

8.2 Bản đồ ngữ nghĩa: Khái niệm Word Embeddings

Sau khi Tokenizer biến câu văn thành một danh sách các ID số nguyên: $$\text{"Tôi yêu AI"} \to [102, 540, 891]$$ Làm thế nào để truyền tải thông tin này vào mạng nơ-ron?

Cách tiếp cận đơn giản nhất là One-Hot Encoding: Tạo ra một vector có độ dài bằng toàn bộ từ điển $V$, đặt số 1 tại vị trí ID của từ và số 0 tại tất cả các vị trí còn lại. Tuy nhiên, cách này có hai điểm yếu chí mạng:

  1. Ma trận thưa thớt cực kỳ lãng phí RAM: Nếu từ điển có $50.000$ từ, mỗi từ đơn lẻ sẽ là một vector $50.000$ chiều chứa toàn số 0.
  2. Mất mát hoàn toàn tính liên kết ngữ nghĩa: Hai vector One-Hot bất kỳ $v_a$ và $v_b$ luôn vuông góc với nhau trong không gian vector. Do đó tích vô hướng của chúng bằng 0: $$v_a \cdot v_b = 0$$ Điều này đồng nghĩa với việc máy tính coi từ "mèo" và "chó" hoàn toàn độc lập, xa lạ y hệt như từ "mèo" và "chiếc bàn".
🧠 Bản chất của Word Embeddings và lớp nn.Embedding
Word Embeddings giải quyết bài toán trên bằng cách chiếu các ID từ vào một không gian vector dày đặc (Dense Vector) có số chiều cố định nhỏ hơn rất nhiều (thường là $d = 300, 768$ hoặc $1536$).

Trong PyTorch, lớp nn.Embedding(num_embeddings, embedding_dim) thực chất là một bảng tra cứu (Lookup Table) khổng lồ chứa một ma trận trọng số khả nạp (trainable weight matrix) kích thước $V \times d$.

Khi ta nạp một Token ID là số nguyên $k$, lớp này không thực hiện phép nhân ma trận phức tạp nào cả, mà chỉ đơn giản truy xuất trực tiếp và trả về dòng thứ $k$ của ma trận trọng số. Phép toán này cực kỳ nhanh ($\mathcal{O}(1)$) và tương đương về mặt toán học với việc nhân một vector One-Hot với ma trận trọng số của lớp nhúng: $$\text{Embedding}(k) = \text{OneHot}(k) \times W_{\text{embed}}$$

Nhờ việc huấn luyện đồng thời trên lượng văn bản khổng lồ, ma trận nhúng tự động dịch chuyển tọa độ của các từ sao cho: Các từ xuất hiện trong ngữ cảnh tương đồng sẽ nằm gần nhau trong không gian đa chiều. Đây chính là tiền đề tạo nên khả năng tính toán tương tự ngữ nghĩa kinh điển: $$\text{Embedding("Vua")} - \text{Embedding("Nam")} + \text{Embedding("Nữ")} \approx \text{Embedding("Hoàng hậu")}$$

8.3 Đo lường khoảng cách ngữ nghĩa: Cosine Similarity

Khi các từ đã được biểu diễn thành các vector trong không gian đa chiều, làm thế nào để đo lường mức độ tương đồng ngữ nghĩa giữa chúng?

Chúng ta không thể dùng khoảng cách hình học thông thường (Khoảng cách Euclid - L2 Distance) để so sánh trực tiếp, bởi vì khoảng cách Euclid nhạy cảm với độ dài (norm) của vector. Trong các tác vụ so sánh câu văn, một câu dài có thể chứa các vector tổng hợp có độ dài cực lớn, làm khoảng cách Euclid lệch đi rất nhiều so với câu ngắn mặc dù chúng có cùng chung chủ đề ngữ nghĩa.

Thay vào đó, chúng ta đo góc giữa hai vector bằng công thức toán học Cosine Similarity (Độ tương đồng Cosine):

📐 Công thức Cosine Similarity
Độ tương đồng Cosine giữa hai vector đa chiều $A$ và $B$ được định nghĩa bằng tích vô hướng chia cho tích độ dài (norm L2) của hai vector: $$\text{Cosine Similarity}(A, B) = \cos(\theta) = \frac{A \cdot B}{\|A\|_2 \|B\|_2} = \frac{\sum_{i=1}^{d} A_i B_i}{\sqrt{\sum_{i=1}^{d} A_i^2} \sqrt{\sum_{i=1}^{d} B_i^2}}$$ Trong đó:
  • Giá trị kết quả nằm trong đoạn $[-1, 1]$.
  • Nếu kết quả bằng $1$: Hai vector hoàn toàn cùng hướng (góc $0^\circ$), chỉ ra sự tương đồng tuyệt đối.
  • Nếu kết quả bằng $0$: Hai vector vuông góc ($90^\circ$), không có mối liên hệ ngữ nghĩa nào.
  • Nếu kết quả bằng $-1$: Hai vector ngược hướng hoàn toàn ($180^\circ$).
⚠️ Cạm bẫy: Đánh đồng sự tương đồng ngữ nghĩa với từ đồng nghĩa
Độ tương đồng Cosine cao giữa hai vector nhúng chỉ cho thấy chúng thường xuất hiện cạnh nhau trong cùng một ngữ cảnh phân phối dữ liệu (Distributional Hypothesis).

Ví dụ: Hai từ trái nghĩa hoàn toàn như "nóng" và "lạnh" thường có điểm Cosine cực kỳ cao vì chúng cùng xuất hiện trong các câu nói về thời tiết, nhiệt độ. Hãy lưu ý điều này khi thiết kế các bộ lọc từ khóa đồng nghĩa thực tế.

8.4 Dự án thực hành bài 8: Không gian ngữ nghĩa dựng bằng tay

Dự án của bài này nhận vào một từ khoá, đổi thành vector nhúng, rồi tính Cosine Similarity với toàn bộ từ vựng để lọc ra những từ gần nghĩa nhất — kèm cả phép toán đại số từ vựng Vua − Nam + Nữ.

⚠️ Các vector ở đây do người VIẾT TAY, không phải do máy học ra
Điều này cần nói thẳng, vì nó dễ gây hiểu sai nhất trong cả bài. Ma trận nhúng trong script không được huấn luyện — 9 vector 4 chiều được gõ sẵn, mỗi chiều mang một ý nghĩa do người viết gán: chiều 0 là "hoàng gia", chiều 1 là "giới tính", chiều 2 là "công nghệ", chiều 3 là "đồ uống".

Vì vậy đây KHÔNG phải Word2Vec. Word2Vec là một thuật toán huấn luyện: nó đọc hàng tỉ câu và tự tìm ra các con số ấy, không ai nói cho nó chiều nào nghĩa gì. Script này bỏ qua toàn bộ phần huấn luyện và chỉ dùng phần sau đó — tra cứu và đo góc.

Vì sao vẫn nên làm theo cách này: với vector thật 300 chiều học từ ngữ liệu, không ai đọc được chiều thứ 174 nghĩa là gì — nó là hộp đen. Ở đây bạn thấy được từng chiều, nên khi phép Vua − Nam + Nữ ra "hoàng hậu" bạn hiểu vì sao nó ra, chứ không chỉ thấy nó ra. Cái giá phải trả là các con số bị "sạch" một cách nhân tạo, và mục đọc kết quả bên dưới sẽ chỉ rõ chỗ nào sạch quá mức.

Huấn luyện embedding thật cần một vòng lặp như Bài 6 trên hàng triệu cặp từ-ngữ cảnh; Bài 10 và Bài 14 sẽ dùng embedding đã được huấn luyện sẵn từ mô hình thật.
word_similarity.py
# word_similarity.py
# Lesson 8: Text processing & word embeddings
# Practical AI Engineer series
#
# Run it with:  python word_similarity.py
# Requires:     pip install torch
#
# IMPORTANT — THE VECTORS BELOW ARE HAND-WRITTEN, NOT TRAINED.
# This is not Word2Vec. Word2Vec is a training algorithm that reads billions of
# sentences and discovers these numbers on its own. Here the 9 vectors are typed
# out by hand, with each of the 4 dimensions given a meaning by a human, so that
# you can SEE why the geometry works. A real 300-dimensional trained embedding is
# opaque: nobody can say what dimension 174 means.
#
# The cost of that clarity is that the numbers come out artificially clean — see
# the note on the 1.0000 score at the bottom of this file.

import torch
import torch.nn as nn

vocab = {
    'king': 0,
    'queen': 1,
    'man': 2,
    'woman': 3,
    'computer': 4,
    'programming': 5,
    'artificial_intelligence': 6,
    'coffee': 7,
    'tea': 8,
}
inverse_vocab = {v: k for k, v in vocab.items()}

# Dimension 0: royalty · 1: gender (positive male, negative female)
# Dimension 2: technology · 3: drinks
embedding_weights = torch.tensor(
    [
        [1.0, 0.9, 0.0, 0.0],  # king
        [1.0, -0.9, 0.0, 0.0],  # queen
        [0.0, 1.0, 0.0, 0.0],  # man
        [0.0, -1.0, 0.0, 0.0],  # woman
        [0.0, 0.0, 1.0, 0.0],  # computer
        [0.0, 0.0, 0.9, 0.0],  # programming
        [0.0, 0.1, 1.0, 0.0],  # artificial_intelligence
        [0.0, 0.0, 0.0, 1.0],  # coffee
        [0.0, 0.0, 0.0, 0.9],  # tea
    ],
    dtype=torch.float32,
)

vocab_size, embedding_dim = embedding_weights.shape
embed = nn.Embedding(num_embeddings=vocab_size, embedding_dim=embedding_dim)
# Fixed weights, never trained further.
embed.weight = nn.Parameter(embedding_weights, requires_grad=False)


def cosine_similarity(vector_a, matrix_b):
    """Cosine of the angle between one vector (1,d) and every row of (V,d)."""
    dot_product = torch.sum(vector_a * matrix_b, dim=1)
    norm_a = torch.norm(vector_a, p=2, dim=1)
    norm_b = torch.norm(matrix_b, p=2, dim=1)
    # +1e-8 guards against dividing by zero for an all-zero vector.
    return dot_product / (norm_a * norm_b + 1e-8)


def find_most_similar(target_word, top_n=3):
    if target_word not in vocab:
        print(f"'{target_word}' is not in the vocabulary.")
        return

    target_vector = embed(torch.tensor([vocab[target_word]]))
    scores = cosine_similarity(target_vector, embed.weight)
    top_scores, top_indices = torch.topk(scores, k=len(vocab))

    print(f"--- closest words to '{target_word}':")
    shown = 0
    for score, idx in zip(top_scores.tolist(), top_indices.tolist()):
        word = inverse_vocab[idx]
        if word == target_word:
            continue  # a word is always its own closest match; skip it
        print(f'  {shown + 1}. {word:<24} cosine {score:.4f}')
        shown += 1
        if shown >= top_n:
            break
    print()


def analogy(a, b, c, top_n=2):
    """Solve 'a is to b as c is to ?' — the classic king - man + woman."""
    vec = embed(torch.tensor([vocab[a]])) - embed(torch.tensor([vocab[b]])) + embed(torch.tensor([vocab[c]]))
    scores = cosine_similarity(vec, embed.weight)

    print(f'=== word analogy: {a} - {b} + {c}')
    shown = 0
    for score, idx in zip(*[t.tolist() for t in torch.topk(scores, k=len(vocab))]):
        word = inverse_vocab[idx]
        # Exclude the three input words. With hand-made vectors this barely
        # matters, but with REAL trained embeddings the input word almost always
        # ranks first, and forgetting to exclude it is the classic mistake that
        # makes analogy code look broken.
        if word in (a, b, c):
            continue
        print(f'  {shown + 1}. {word:<24} cosine {score:.4f}')
        shown += 1
        if shown >= top_n:
            break
    print()


if __name__ == '__main__':
    find_most_similar('coffee', top_n=2)
    find_most_similar('computer', top_n=2)
    analogy('king', 'man', 'woman')

    # Why 'tea' scores exactly 1.0000 against 'coffee': their vectors are
    # [0,0,0,1] and [0,0,0,0.9] — exactly parallel, differing only in length,
    # and cosine ignores length. A real trained embedding never gives exactly
    # 1.0 for two different words. This is the artificial cleanliness that comes
    # with hand-writing the numbers.
    print('note: coffee and tea are exactly parallel by construction, hence 1.0000.')

Kết quả chạy thực tế xác nhận đúng công thức đại số ngữ nghĩa đã nêu ở mục 8.2: Vua − Nam + Nữ ≈ Hoàng hậu, với điểm Cosine đạt $0.9950$ — gần tuyệt đối. Đáng chú ý, "cà_phê" và "trà" đạt điểm Cosine chính xác bằng $1.0000$ dù không phải từ đồng nghĩa hoàn toàn, vì trong ma trận nhúng giả lập này hai từ chỉ khác nhau ở độ dài vector (0.9 so với 1.0) chứ không khác hướng — đây chính là hệ quả trực tiếp của cạm bẫy đã nêu ở mục 8.3: Cosine Similarity chỉ đo góc, hoàn toàn bỏ qua độ dài, nên hai vector cùng hướng nhưng khác độ lớn vẫn cho điểm tương đồng tuyệt đối.

Chạy nó ra như sau:

Terminal
--- closest words to 'coffee':
  1. tea                      cosine 1.0000
  2. man                      cosine 0.0000

--- closest words to 'computer':
  1. programming              cosine 1.0000
  2. artificial_intelligence  cosine 0.9950

=== word analogy: king - man + woman
  1. queen                    cosine 0.9950
  2. computer                 cosine 0.0000

note: coffee and tea are exactly parallel by construction, hence 1.0000.

Phép loại suy chạy đúng: king − man + woman cho ra queen ở 0,995. Nhưng ba con số còn lại mới là phần đáng học, vì chúng cho thấy giới hạn của cách dựng bằng tay.

🕳️ Ba con số "quá đẹp" và ý nghĩa thật của chúng
1. Điểm 1,0000 giữa "coffee" và "tea" không có nghĩa là chúng đồng nghĩa hoàn hảo. Hai vector là [0,0,0,1][0,0,0,0.9]song song tuyệt đối, chỉ khác độ dài, và Cosine bỏ qua độ dài. Với embedding học từ ngữ liệu thật, hai từ khác nhau không bao giờ cho đúng 1,0. Thấy 1,0 trong dự án thật thì gần như chắc chắn bạn đang so một từ với chính nó.

2. Điểm 0,0000 giữa "coffee" và "man" là One-Hot quay lại. Hai vector vuông góc vì các chiều được gán tách biệt hoàn toàn — đúng cái nhược điểm của One-Hot mà mục 8.2 vừa phê phán. Embedding thật không bao giờ cho 0 tuyệt đối, vì mọi từ đều chia sẻ ít nhiều ngữ cảnh.

3. Việc loại các từ đầu vào khỏi kết quả loại suy là bắt buộc. Trong code, analogy() bỏ qua cả king, manwoman. Ở đây bỏ hay không gần như không đổi kết quả (king chỉ đạt 0,005), nhưng với embedding thật từ đầu vào gần như luôn xếp nhất — và quên loại nó là lỗi kinh điển làm code loại suy trông như bị hỏng.

Nói gọn: dựng bằng tay giúp bạn thấy hình học, nhưng đừng lấy các con số này làm chuẩn kỳ vọng cho dữ liệu thật.

Cách chạy dự án này trên máy bạn

  1. Cài: pip install torch. Không cần tải dữ liệu.
  2. Tải word_similarity.py ở cuối bài, hoặc gõ lại đoạn code trên.
  3. Chạy: python3 word_similarity.py. Không có yếu tố ngẫu nhiên nên kết quả luôn giống hệt.
  4. Rồi thử ba việc, mỗi việc dạy một điều:
    • Thêm một từ mới vào vocab và một dòng vector tương ứng — ví dụ 'prince': 9 với [1.0, 0.9, 0.0, 0.0]. Bạn sẽ thấy nó đạt 1,0 với "king", vì bạn vừa gõ đúng cùng một hướng. Đó là cách cảm nhận trực tiếp rằng "ý nghĩa" ở đây do bạn quyết định.
    • Đổi chiều giới tính của "queen" từ -0.9 thành +0.9 rồi chạy lại. Phép loại suy sẽ không còn ra "queen" — chứng minh rằng kết quả đến từ hình học, không từ cái tên.
    • Bỏ dòng if word in (a, b, c): continue trong analogy(). Ở đây kết quả gần như không đổi, nhưng hãy nhớ hành vi này khi bạn dùng embedding thật ở Bài 14.

Tóm tắt bài học & Cầu nối kiến thức

🔑 Bài học đạt được:
  • Đạt được: ba kiểu Tokenizer và lý do LLM hiện đại chọn mảnh từ — cùng việc BPE có thể cho nhiều bộ mảnh từ khác nhau đều hợp lệ, tuỳ cách phá hoà.
  • Đạt được: vì sao One-Hot thất bại (mọi từ vuông góc nhau), và nn.Embedding thực chất chỉ là một bảng tra cứu $\mathcal{O}(1)$.
  • Đạt được: Cosine Similarity, và lý do nó đo góc chứ không đo khoảng cách — độ dài vector không nói gì về ngữ nghĩa.
  • Đạt được: phân biệt dùng embedding với huấn luyện embedding. Dự án của bài này làm việc thứ nhất; Word2Vec là việc thứ hai, và bài này không làm.
  • Đạt được: đọc được điểm Cosine một cách phê phán — 1,0 tuyệt đối và 0,0 tuyệt đối đều là dấu hiệu của dữ liệu nhân tạo, không phải của ngữ nghĩa tốt.

Cầu nối bài tiếp theo: Các vector từ đơn lẻ chưa thể hiện được thứ tự của câu văn. Để xử lý các chuỗi ngôn ngữ dài theo thời gian, chúng ta cần cơ chế mạng tuần hoàn RNN và cơ chế Attention ở Bài số 9.

Tải file code thực hành minh họa bài học

File Python word_similarity.py — mã nguồn khởi tạo ma trận nhúng, định nghĩa từ điển tiếng Việt giả lập và tính toán độ tương đồng Cosine (chạy python word_similarity.py, yêu cầu cài đặt thư viện torch):

Tải về word_similarity.py

📖 Tài liệu tham khảo

Bài viết liên quan trong series

Bài 7: Thị giác Máy tính: Mạng Tích Chập (CNN) Bài 9: Mạng tuần hoàn (RNN) & Attention Quay lại Lộ trình Kỹ Sư AI Thực Chiến

Bình luận