Mở đầu

Các Mô hình Ngôn ngữ Lớn (LLM) đã trở nên vô cùng phổ biến sau khi OpenAI phát hành ChatGPT vào tháng 11 năm 2022. Kể từ đó, việc sử dụng các mô hình ngôn ngữ này đã bùng nổ, một phần nhờ vào các thư viện như thư viện Transformer của Hugging Face và PyTorch.

Tuy nhiên, với tất cả các công cụ có sẵn này, người dùng rất dễ dàng bỏ qua những gì đang diễn ra ở cấp độ cơ bản. Do đó, nhiều hướng dẫn trực tuyến sẽ cung cấp cho bạn phần "cái gì" mà không phải phần "tại sao" khi bạn tự xây dựng mô hình của mình. Đó là những gì mà loạt bài viết này hướng đến để khắc phục.

"LLM từ đầu" mổ xẻ các thành phần cấu thành nên các mô hình ngôn ngữ lớn và giải thích cách chúng hoạt động bên trong. Mục tiêu của việc này là: Xây dựng sự hiểu biết cơ bản về cách LLM hoạt động từ các nguyên tắc cơ bản nhất, bao gồm cả sự hiểu biết trực quan về toán học.

  • Chứng minh cách từng thành phần hoạt động, thể hiện các triển khai từ đầu bằng Python.

  • Sử dụng càng ít thư viện càng tốt để giảm thiểu mọi trừu tượng hóa không cần thiết.

Với tất cả những điều đó, chúng ta hãy bắt đầu.

Phần 1: Tokenization là gì?

Các bài toán xử lý ngôn ngữ tự nhiên sử dụng dữ liệu văn bản, mà máy móc không thể hiểu được ngay lập tức. Để máy tính xử lý ngôn ngữ, trước tiên chúng cần chuyển đổi văn bản thành dạng số. Quá trình này được thực hiện qua hai giai đoạn chính bởi một mô hình gọi là tokenizer (bộ tách từ).

Bước 1: Chia văn bản đầu vào thành các Token

Tokenizer đầu tiên lấy văn bản và chia nó thành các phần nhỏ hơn, có thể là các từ, các phần của từ hoặc các ký tự riêng lẻ. Các phần văn bản nhỏ hơn này được gọi là token. Stanford NLP Group [2] định nghĩa token một cách chặt chẽ hơn như sau:

Một thể hiện của một chuỗi các ký tự trong một tài liệu cụ thể được nhóm lại với nhau như một đơn vị ngữ nghĩa hữu ích cho việc xử lý.

Bước 2: Gán ID cho mỗi Token

Khi tokenizer đã chia văn bản thành các token, mỗi token có thể được gán một số nguyên gọi là token ID. Ví dụ: từ "cat" có thể được gán giá trị 15, và do đó, mọi token "cat" trong văn bản đầu vào được biểu diễn bằng số 15. Quá trình thay thế các token văn bản bằng một biểu diễn số được gọi là mã hóa (encoding).

Tương tự, quá trình chuyển đổi các token đã mã hóa trở lại thành văn bản được gọi là giải mã (decoding).

Việc sử dụng một số duy nhất để biểu diễn một token có những hạn chế của nó, và do đó, các mã hóa này được xử lý thêm để tạo ra word embeddings (nhúng từ), là chủ đề của bài viết tiếp theo trong loạt bài này.

Các phương pháp Tokenization

Có ba phương pháp chính để chia văn bản thành các token:

  • Dựa trên từ (Word-based)
  • Dựa trên ký tự (Character-based)
  • Dựa trên đơn vị từ con (Subword-based)

Tokenizers dựa trên từ:

Tokenization dựa trên từ có lẽ là phương pháp đơn giản nhất trong ba phương pháp tokenization. Ở đây, tokenizer sẽ chia các câu thành các từ bằng cách tách trên mỗi ký tự khoảng trắng (đôi khi được gọi là 'tokenization dựa trên khoảng trắng'), hoặc bằng một tập các quy tắc tương tự (chẳng hạn như tokenization dựa trên dấu câu, tokenization treebank, v.v.) [12].

Ví dụ, câu: "Cats are great, but dogs are better!" có thể được tách trên các ký tự khoảng trắng để cho ra: ['Cats', 'are', 'great,', 'but', 'dogs', 'are', 'better!'] hoặc bằng cách tách trên cả dấu câu và khoảng trắng để cho ra: ['Cats', 'are', 'great', ',', 'but', 'dogs', 'are', 'better', '!']

Từ ví dụ đơn giản này, hy vọng rõ ràng là các quy tắc được sử dụng để xác định việc tách là rất quan trọng. Cách tiếp cận khoảng trắng cho ra token "better!" có khả năng hiếm gặp, trong khi cách tách thứ hai cho ra hai token ít hiếm gặp hơn là "better" và "!". Cần cẩn thận để không loại bỏ hoàn toàn các dấu câu, vì chúng có thể mang những ý nghĩa rất cụ thể. Một ví dụ về điều này là dấu nháy đơn, có thể phân biệt giữa dạng số nhiều và dạng sở hữu của các từ. Ví dụ: "book's" đề cập đến một số thuộc tính của một cuốn sách, như trong "the book's spine is damaged" (gáy sách bị hỏng), và "books" đề cập đến nhiều cuốn sách, như trong "the books are damaged" (những cuốn sách bị hỏng).

Khi các token đã được tạo, mỗi token có thể được gán một số. Lần tới khi một token được tạo mà tokenizer đã thấy, token đó chỉ cần được gán số được chỉ định cho từ đó. Ví dụ: nếu token "great" được gán giá trị 1 trong câu trên, tất cả các lần xuất hiện tiếp theo của từ "great" cũng sẽ được gán giá trị 1 [3].

Ưu và nhược điểm của Tokenizers dựa trên từ:

Các token được tạo ra trong các phương pháp dựa trên từ chứa một lượng lớn thông tin, vì mỗi token chứa thông tin ngữ nghĩa và ngữ cảnh. Tuy nhiên, một trong những nhược điểm lớn nhất của phương pháp này là các từ rất giống nhau được coi là các token hoàn toàn riêng biệt. Ví dụ: mối liên hệ giữa "cat" và "cats" sẽ không tồn tại, và do đó, chúng sẽ được coi là các từ riêng biệt. Điều này trở thành một vấn đề trong các ứng dụng quy mô lớn có chứa nhiều từ, vì số lượng token có thể có trong từ vựng của mô hình (tổng tập hợp các token mà mô hình đã thấy) có thể tăng lên rất lớn. Tiếng Anh có khoảng 170.000 từ, và do đó, việc bao gồm các dạng ngữ pháp khác nhau như số nhiều và thì quá khứ cho mỗi từ có thể dẫn đến cái được gọi là vấn đề bùng nổ từ vựng. Một ví dụ về điều này là tokenizer TransformerXL sử dụng cách tách dựa trên khoảng trắng. Điều này dẫn đến kích thước từ vựng hơn 250.000 [4].

Một cách để chống lại điều này là bằng cách thực thi giới hạn cứng đối với số lượng token mà mô hình có thể học (ví dụ: 10.000). Điều này sẽ phân loại bất kỳ từ nào nằm ngoài 10.000 token thường xuyên nhất là ngoài từ vựng (out-of-vocabulary - OOV) và sẽ gán giá trị token là UNKNOWN thay vì một giá trị số (thường được viết tắt là UNK). Điều này làm cho hiệu suất bị ảnh hưởng trong các trường hợp có nhiều từ không xác định, nhưng có thể là một sự thỏa hiệp phù hợp nếu dữ liệu chủ yếu chứa các từ thông dụng. [5]

Tóm tắt ưu điểm:

  • Phương pháp đơn giản
  • Mức độ thông tin cao được lưu trữ trong mỗi token
  • Có thể giới hạn kích thước từ vựng, hoạt động tốt với các bộ dữ liệu chủ yếu chứa các từ thông dụng

Tóm tắt nhược điểm:

  • Các token riêng biệt được tạo cho các từ tương tự (ví dụ: "cat" và "cats")
  • Có thể dẫn đến từ vựng rất lớn
  • Giới hạn từ vựng có thể làm giảm đáng kể hiệu suất trên các bộ dữ liệu có nhiều từ không phổ biến

Tokenizers dựa trên ký tự:

Tokenization dựa trên ký tự chia văn bản trên mỗi ký tự, bao gồm: chữ cái, số và các ký tự đặc biệt như dấu câu. Điều này làm giảm đáng kể kích thước từ vựng, đến mức ngôn ngữ tiếng Anh có thể được biểu diễn bằng một từ vựng khoảng 256 token, thay vì hơn 170.000 từ như cách tiếp cận dựa trên từ [5].

Ngay cả các ngôn ngữ Đông Á như tiếng Trung và tiếng Nhật cũng có thể thấy sự giảm đáng kể về kích thước từ vựng, mặc dù chứa hàng nghìn ký tự riêng biệt trong hệ thống chữ viết của chúng.

Trong một tokenizer dựa trên ký tự, câu sau:
"Cats are great, but dogs are better!"
sẽ được chuyển đổi thành:
['C', 'a', 't', 's', ' ', 'a', 'r', 'e', ' ', 'g', 'r', 'e', 'a', 't', ',', ' ', 'b', 'u', 't', ' ', 'd', 'o', 'g', 's', ' ', 'a', 'r', 'e', ' ', 'b', 'e', 't', 't', 'e', 'r', ' !']


Ưu và nhược điểm của Tokenizers dựa trên ký tự:

Các phương pháp dựa trên ký tự tạo ra kích thước từ vựng nhỏ hơn nhiều so với các phương pháp dựa trên từ, và cũng tạo ra ít token ngoài từ vựng hơn nhiều. Điều này thậm chí cho phép các từ bị viết sai chính tả được token hóa (mặc dù khác với dạng đúng của từ), thay vì bị loại bỏ ngay lập tức do giới hạn từ vựng dựa trên tần suất.
Tuy nhiên, có một số nhược điểm với phương pháp này.



Đầu tiên, thông tin được lưu trữ trong một token duy nhất được tạo ra bằng phương pháp dựa trên ký tự là rất thấp. Điều này là do không giống như các token trong phương pháp dựa trên từ, không có ý nghĩa ngữ nghĩa hoặc ngữ cảnh nào được nắm bắt (đặc biệt là trong các ngôn ngữ có hệ thống chữ viết dựa trên bảng chữ cái như tiếng Anh). Cuối cùng, kích thước của đầu vào được token hóa có thể được đưa vào một mô hình ngôn ngữ bị giới hạn với phương pháp này, vì cần nhiều số để mã hóa văn bản đầu vào.

Tóm tắt ưu điểm:

  • Kích thước từ vựng nhỏ hơn
  • Không loại bỏ các từ bị viết sai chính tả

Tóm tắt nhược điểm:

  • Thông tin thấp được lưu trữ trong mỗi token, ít hoặc không có ý nghĩa ngữ cảnh hoặc ngữ nghĩa (đặc biệt là với các hệ thống chữ viết dựa trên bảng chữ cái)
  • Kích thước đầu vào cho các mô hình ngôn ngữ bị giới hạn vì đầu ra của tokenizer yêu cầu nhiều số hơn để token hóa văn bản (so với phương pháp dựa trên từ)

Tokenizers dựa trên đơn vị từ con:

Tokenization dựa trên đơn vị từ con nhằm đạt được những lợi ích của cả phương pháp dựa trên từ và dựa trên ký tự, đồng thời giảm thiểu nhược điểm của chúng. Các phương pháp dựa trên đơn vị từ con đạt được sự cân bằng bằng cách chia văn bản trong các từ để cố gắng tạo ra các token có ý nghĩa ngữ nghĩa, ngay cả khi chúng không phải là toàn bộ từ. Ví dụ: các token "ing" và "ed" mang ý nghĩa ngữ pháp, mặc dù bản thân chúng không phải là từ. Phương pháp này tạo ra kích thước từ vựng nhỏ hơn so với các phương pháp dựa trên từ, nhưng lớn hơn so với các phương pháp dựa trên ký tự. Điều tương tự cũng đúng với lượng thông tin được lưu trữ trong mỗi token, cũng nằm giữa các token được tạo bởi hai phương pháp trước đó.

Cách tiếp cận đơn vị từ con sử dụng hai nguyên tắc sau:

  • Các từ được sử dụng thường xuyên không nên được chia thành các đơn vị từ con, mà nên được lưu trữ dưới dạng toàn bộ token.
  • Các từ ít được sử dụng nên được chia thành các đơn vị từ con.

Việc chỉ tách các từ ít được sử dụng tạo cơ hội cho các biến thể, dạng số nhiều, v.v. được phân tách thành các thành phần cấu thành của chúng, đồng thời bảo toàn mối quan hệ giữa các token. Ví dụ: "cat" có thể là một từ rất phổ biến trong bộ dữ liệu, nhưng "cats" có thể ít phổ biến hơn. Vì lý do này, "cats" sẽ được tách thành "cat" và "s", trong đó "cat" hiện được gán cùng giá trị với mọi token "cat" khác và "s" được gán một giá trị khác, có thể mã hóa ý nghĩa của số nhiều. Một ví dụ khác sẽ là từ "tokenization", có thể được tách thành từ gốc "token" và hậu tố "ization". Do đó, phương pháp này có thể bảo toàn tính tương đồng cú pháp và ngữ nghĩa [6]. Vì những lý do này, tokenizers dựa trên đơn vị từ con ngày nay được sử dụng rất phổ biến trong các mô hình NLP.

Chuẩn hóa và tiền Tokenization

Quá trình tokenization yêu cầu một số bước tiền xử lý và hậu xử lý, tất cả tạo nên quy trình tokenization. Điều này mô tả toàn bộ chuỗi các hành động được thực hiện để chuyển đổi văn bản thô thành các token. Các bước của quy trình này là:

  1. Chuẩn hóa
  2. Tiền Tokenization
  3. Mô hình
  4. Hậu xử lý

trong đó phương pháp tokenization (có thể là dựa trên đơn vị từ con, dựa trên ký tự, v.v.) diễn ra ở bước mô hình [7]. Phần này sẽ đề cập đến từng bước trong số này cho một tokenizer sử dụng phương pháp tokenization dựa trên đơn vị từ con.

LƯU Ý QUAN TRỌNG: Tất cả các bước của quy trình tokenization được xử lý tự động cho người dùng khi sử dụng tokenizer từ các thư viện như thư viện tokenizerstransformers của Hugging Face. Toàn bộ quy trình được thực hiện bởi một đối tượng duy nhất có tên là Tokenizer. Phần này đi sâu vào các hoạt động bên trong của mã mà hầu hết người dùng không cần xử lý thủ công khi làm việc với các tác vụ NLP. Sau đó, các bước để tùy chỉnh lớp tokenizer cơ sở trong thư viện tokenizers cũng được trình bày, để có thể xây dựng một tokenizer có mục đích cụ thể cho một tác vụ cụ thể nếu cần.

Các phương pháp chuẩn hóa

Chuẩn hóa là quá trình làm sạch văn bản trước khi nó được chia thành các token. Điều này bao gồm các bước như chuyển đổi từng ký tự thành chữ thường, loại bỏ dấu thanh khỏi các ký tự (ví dụ: "é" trở thành "e"), loại bỏ các khoảng trắng không cần thiết, v.v. Ví dụ: chuỗi "ThÍs is áN ExaMPlé sÉnteNCE" trở thành "this is an example sentence" sau khi chuẩn hóa. Các bộ chuẩn hóa khác nhau sẽ thực hiện các bước khác nhau, có thể hữu ích tùy thuộc vào trường hợp sử dụng. Ví dụ: trong một số tình huống, có thể cần giữ nguyên chữ hoa chữ thường hoặc dấu thanh. Tùy thuộc vào bộ chuẩn hóa được chọn, các hiệu ứng khác nhau có thể đạt được ở giai đoạn này.

Gói tokenizers.normalizers của Hugging Face chứa một số bộ chuẩn hóa cơ bản được sử dụng bởi các tokenizer khác nhau như một phần của các mô hình lớn hơn. Dưới đây cho thấy các bộ chuẩn hóa NFC unicode, Lowercase và BERT. Chúng cho thấy các hiệu ứng sau trên câu ví dụ:

  • NFC: Không chuyển đổi chữ hoa chữ thường hoặc loại bỏ dấu thanh
  • Lower: Chuyển đổi chữ hoa chữ thường nhưng không loại bỏ dấu thanh
  • BERT: Chuyển đổi chữ hoa chữ thường và loại bỏ dấu thanh

					from tokenizers.normalizers import NFC, Lowercase, BertNormalizer
# Text to normalize
text = 'ThÍs is  áN ExaMPlé     sÉnteNCE'
# Instantiate normalizer objects
NFCNorm = NFC()
LowercaseNorm = Lowercase()
BertNorm = BertNormalizer()
# Normalize the text
print(f'NFC:   {NFCNorm.normalize_str(text)}')
print(f'Lower: {LowercaseNorm.normalize_str(text)}')
print(f'BERT:  {BertNorm.normalize_str(text)}')