Bài 37.3: Document processing và chunking

Bài 37.3: Document processing và chunking
Trong kỷ nguyên của dữ liệu lớn và Trí tuệ Nhân tạo (AI) đang bùng nổ, đặc biệt là sự trỗi dậy mạnh mẽ của các Mô hình Ngôn ngữ Lớn (LLM) như GPT, Claude, Llama..., khả năng xử lý và hiểu được thông tin từ các tài liệu văn bản trở nên cực kỳ quan trọng. Dù bạn đang xây dựng một ứng dụng web cần phân tích nội dung người dùng tải lên, một hệ thống tìm kiếm thông minh, hay một chatbot có khả năng trả lời dựa trên kiến thức từ tài liệu nội bộ, bạn sẽ cần đến hai kỹ thuật cốt lõi: Document Processing (Xử lý Tài liệu) và Chunking (Phân đoạn/Chia nhỏ Tài liệu).
Document Processing: Biến dữ liệu thô thành thông tin có cấu trúc
Document Processing là quá trình trích xuất, chuyển đổi và làm sạch dữ liệu từ các nguồn tài liệu khác nhau. Mục tiêu là biến dữ liệu văn bản thô, thường không có cấu trúc rõ ràng, thành một định dạng sạch sẽ và có tổ chức hơn, sẵn sàng cho các bước phân tích hoặc xử lý tiếp theo.
Quá trình này có thể bao gồm nhiều bước phức tạp tùy thuộc vào định dạng và độ phức tạp của tài liệu (text files, PDF, Word, HTML, JSON, XML, v.v.). Tuy nhiên, các bước cơ bản thường gặp là:
- Parsing (Phân tích cú pháp): Đọc và hiểu cấu trúc cơ bản của tài liệu. Ví dụ, đọc nội dung từ file
.txt
, phân tích các thẻ HTML từ một trang web, hoặc trích xuất văn bản từ PDF. - Cleaning (Làm sạch): Loại bỏ những thành phần không mong muốn như ký tự đặc biệt, khoảng trắng thừa, mã HTML/XML không cần thiết, hoặc các yếu tố gây nhiễu khác.
- Structuring (Tạo cấu trúc): Xác định và nhóm các phần tử logic của tài liệu như tiêu đề, đoạn văn, danh sách, bảng biểu.
- Metadata Extraction (Trích xuất siêu dữ liệu): Lấy các thông tin đi kèm tài liệu như tác giả, ngày tạo, tiêu đề, v.v.
Tại sao bước này quan trọng? AI, đặc biệt là các mô hình xử lý ngôn ngữ tự nhiên (NLP), hoạt động hiệu quả nhất với dữ liệu sạch và có định dạng nhất quán. Dữ liệu nhiễu hoặc không có cấu trúc sẽ làm giảm đáng kể hiệu suất của mô hình.
Hãy xem một ví dụ đơn giản về làm sạch văn bản bằng JavaScript:
function cleanText(text) {
// Loại bỏ khoảng trắng ở đầu và cuối
let cleaned = text.trim();
// Thay thế nhiều khoảng trắng liên tiếp bằng một khoảng trắng duy nhất
cleaned = cleaned.replace(/\s+/g, ' ');
// (Tùy chọn) Loại bỏ các ký tự không phải chữ cái, số, khoảng trắng và dấu câu cơ bản
// cleaned = cleaned.replace(/[^a-zA-Z0-9 .,!?;:]/g, '');
return cleaned;
}
const rawText = " Đây là một đoạn văn bản có khoảng trắng thừa. ";
const processedText = cleanText(rawText);
console.log("Văn bản gốc:", `"${rawText}"`);
console.log("Văn bản đã xử lý:", `"${processedText}"`);
Giải thích code:
cleanText
là một hàm nhận vào chuỗitext
.text.trim()
loại bỏ tất cả khoảng trắng (dấu cách, tab, xuống dòng) ở đầu và cuối chuỗi.replace(/\s+/g, ' ')
sử dụng biểu thức chính quy (/\s+/g
) để tìm kiếm một hoặc nhiều ký tự khoảng trắng (\s+
) và thay thế tất cả các lần xuất hiện (g
- global) bằng một dấu cách duy nhất (' '
).- Hàm trả về chuỗi đã được làm sạch.
Ví dụ này chỉ là một phần nhỏ của quá trình xử lý tài liệu, vốn có thể phức tạp hơn nhiều khi làm việc với các định dạng file khác.
The Challenge of Scale: Tại sao cần Chunking?
Bạn đã xử lý tài liệu và có một chuỗi văn bản sạch sẽ, có cấu trúc. Tuy nhiên, nếu tài liệu đó là một cuốn sách, một báo cáo dài, hoặc hàng trăm trang nội dung web được cào về? Lúc này, một thách thức lớn xuất hiện: kích thước của tài liệu.
- Giới hạn Context Window của AI: Các LLM, dù mạnh mẽ đến đâu, đều có một giới hạn về lượng văn bản mà chúng có thể xử lý cùng lúc (gọi là context window). Bạn không thể đơn giản đưa toàn bộ nội dung của Chiến tranh và Hòa bình vào prompt của một LLM và yêu cầu tóm tắt hoặc trả lời câu hỏi chi tiết. Kích thước context window có thể dao động từ vài nghìn tokens đến hàng triệu tokens, nhưng vẫn có giới hạn.
- Hiệu suất và Chi phí: Xử lý các chuỗi văn bản rất dài tốn kém hơn về mặt tính toán và chi phí API khi làm việc với các mô hình AI trả phí.
- Độ chính xác của Tìm kiếm: Khi tìm kiếm thông tin trong một tài liệu rất dài, việc tìm kiếm trên toàn bộ tài liệu có thể kém hiệu quả và trả về kết quả không chính xác bằng việc tìm kiếm trong các phần nhỏ, có liên quan của tài liệu.
Đây là lúc Chunking phát huy tác dụng. Chunking là kỹ thuật chia một tài liệu lớn thành các đoạn nhỏ hơn (gọi là chunks hoặc segments), mỗi đoạn có kích thước phù hợp để xử lý tiếp theo, đặc biệt là để đưa vào các mô hình AI hoặc hệ thống tìm kiếm.
Mục tiêu của chunking không chỉ là cắt nhỏ ngẫu nhiên. Một chiến lược chunking tốt sẽ cố gắng:
- Giữ ngữ cảnh: Các chunk nên chứa đựng các ý hoặc câu liên quan đến nhau để duy trì ngữ nghĩa. Cắt ngang một câu hoặc một đoạn văn có thể làm mất mát thông tin quan trọng.
- Kích thước phù hợp: Mỗi chunk nên nằm trong giới hạn kích thước (tokens hoặc ký tự) yêu cầu của mô hình AI hoặc hệ thống đích.
- Có thể phục hồi nguồn gốc: Dễ dàng xác định mỗi chunk đến từ phần nào của tài liệu gốc (số trang, đoạn, tiêu đề).
Các Phương pháp Chunking Phổ biến
Có nhiều cách tiếp cận để chia nhỏ tài liệu, từ đơn giản đến phức tạp:
Fixed-size Chunking (Chia cố định theo kích thước): Chia tài liệu thành các đoạn có cùng số lượng ký tự hoặc tokens.
- Ưu điểm: Đơn giản, dễ triển khai.
- Nhược điểm: Dễ dàng cắt ngang câu, đoạn văn, làm mất ngữ cảnh.
Chunking by Separator (Chia theo dấu phân cách): Chia tài liệu dựa trên các ký tự hoặc chuỗi ký tự cụ thể như dấu xuống dòng (
\n
), hai dấu xuống dòng (\n\n
thường là ranh giới đoạn văn), dấu chấm câu (.
,!
,?
), hoặc thậm chí là các tiêu đề, section trong tài liệu có cấu trúc.- Ưu điểm: Tôn trọng cấu trúc tự nhiên của văn bản (câu, đoạn văn), giữ ngữ cảnh tốt hơn Fixed-size.
- Nhược điểm: Một đoạn văn hoặc câu có thể quá dài, vượt quá giới hạn kích thước mong muốn.
Recursive Chunking (Chia đệ quy): Kết hợp nhiều phương pháp chia theo dấu phân cách một cách đệ quy. Ví dụ: Đầu tiên chia theo section, nếu section quá lớn thì chia tiếp theo đoạn văn, nếu đoạn văn vẫn lớn thì chia tiếp theo câu, v.v., cho đến khi các chunk đạt kích thước mong muốn.
- Ưu điểm: Cố gắng giữ cấu trúc và ngữ cảnh ở mức cao nhất có thể trong khi vẫn đảm bảo kích thước chunk.
- Nhược điểm: Phức tạp hơn để triển khai.
Semantic Chunking (Chia theo ngữ nghĩa): Phương pháp nâng cao hơn, sử dụng các kỹ thuật NLP (như tạo vector embedding) để xác định các đoạn văn bản có ý nghĩa tương đồng và nhóm chúng lại thành các chunk, ngay cả khi chúng không liền kề nhau trong tài liệu gốc.
- Ưu điểm: Tạo ra các chunk rất mạch lạc về mặt ý nghĩa.
- Nhược điểm: Phức tạp nhất, đòi hỏi kiến thức về NLP và vector database.
Hãy xem các ví dụ đơn giản về chunking bằng JavaScript:
Ví dụ 1: Chia theo đoạn văn (sử dụng hai dấu xuống dòng làm dấu phân cách)
function chunkByParagraph(text) {
// Sử dụng biểu thức chính quy để tìm 2 hoặc nhiều dấu xuống dòng liên tiếp
// kèm theo khoảng trắng tùy chọn giữa chúng.
const paragraphs = text.split(/\n\s*\n/).filter(chunk => chunk.trim().length > 0);
return paragraphs;
}
const longText = "Đây là đoạn văn thứ nhất.\n\nĐây là đoạn văn thứ hai, có lẽ dài hơn một chút.\n\n\n Đoạn văn thứ ba với nhiều khoảng trắng thừa.";
const chunks = chunkByParagraph(longText);
console.log("Số lượng chunks:", chunks.length);
console.log("Các chunks:");
chunks.forEach((chunk, index) => {
console.log(`--- Chunk ${index + 1} ---`);
console.log(chunk);
});
Giải thích code:
- Hàm
chunkByParagraph
nhận chuỗitext
. text.split(/\n\s*\n/)
chia chuỗi dựa trên mẫu regex/\n\s*\n/
. Mẫu này tìm kiếm một dấu xuống dòng (\n
), theo sau là không hoặc nhiều khoảng trắng (\s*
), và kết thúc bằng một dấu xuống dòng khác (\n
). Điều này hiệu quả trong việc phân chia các đoạn văn được ngăn cách bằng một hoặc nhiều dòng trống..filter(chunk => chunk.trim().length > 0)
loại bỏ bất kỳ chunk rỗng nào có thể xuất hiện do cách chia.- Kết quả là một mảng các chuỗi, mỗi chuỗi là một đoạn văn.
Ví dụ 2: Chia cố định theo số ký tự (đơn giản, không tính đến từ/câu)
function chunkByCharCount(text, chunkSize) {
const chunks = [];
for (let i = 0; i < text.length; i += chunkSize) {
chunks.push(text.substring(i, i + chunkSize));
}
return chunks;
}
const article = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
const fixedSizeChunks = chunkByCharCount(article, 100); // Chia mỗi 100 ký tự
console.log("\n--- Fixed Size Chunks ---");
console.log("Số lượng chunks:", fixedSizeChunks.length);
fixedSizeChunks.forEach((chunk, index) => {
console.log(`Chunk ${index + 1} (${chunk.length} chars): "${chunk.substring(0, 50)}..."`); // In vài ký tự đầu
});
Giải thích code:
- Hàm
chunkByCharCount
nhận chuỗitext
và kích thướcchunkSize
. - Vòng lặp
for
duyệt qua chuỗi với bước nhảy bằngchunkSize
. text.substring(i, i + chunkSize)
trích xuất một phần của chuỗi từ vị tríi
đếni + chunkSize
.- Kết quả là một mảng các chuỗi có kích thước (gần như) bằng
chunkSize
. Lưu ý rằng cách này có thể cắt ngang từ, câu.
Ví dụ 3: Sử dụng thư viện (Khái niệm - thường dùng Python cho AI libs)
Trong thực tế, đặc biệt khi tích hợp với các pipeline AI phức tạp (như RAG), bạn sẽ thường sử dụng các thư viện chuyên dụng. Các thư viện này cung cấp các chiến lược chunking nâng cao hơn, bao gồm cả recursive và thêm tính năng như "chunk overlap" (các chunk liền kề có một phần nội dung trùng lặp để giữ ngữ cảnh tốt hơn ở ranh giới).
Đây là một ví dụ khái niệm về cách bạn có thể sử dụng một thư viện phổ biến như langchain
trong Python (một ngôn ngữ rất phổ biến cho các tác vụ AI/ML):
# Đây là code Python, chỉ mang tính minh họa cho việc sử dụng thư viện
# Bạn sẽ cần cài đặt thư viện như: pip install langchain
# from langchain.text_splitter import RecursiveCharacterTextSplitter
# def recursive_chunking_example(text):
# text_splitter = RecursiveCharacterTextSplitter(
# chunk_size=1000, # Kích thước tối đa của mỗi chunk
# chunk_overlap=200 # Số ký tự/tokens trùng lặp giữa các chunk
# )
# chunks = text_splitter.create_documents([text]) # create_documents cũng có thể xử lý metadata
# return chunks
# my_long_text = "..." # Văn bản rất dài của bạn
# intelligent_chunks = recursive_chunking_example(my_long_text)
# for i, chunk in enumerate(intelligent_chunks):
# print(f"Chunk {i+1}:")
# print(chunk.page_content[:100] + "...") # In vài ký tự đầu
# print("-" * 20)
# Giải thích: Thư viện như Langchain cung cấp các Text Splitter thông minh hơn
# có thể xử lý nhiều loại dấu phân cách và thêm overlap để bảo toàn ngữ cảnh tốt hơn.
# Bạn chỉ cần cấu hình kích thước và overlap mong muốn, thư viện sẽ lo phần chia.
Giải thích khái niệm:
- Thay vì tự viết logic chia, bạn sử dụng một lớp (ví dụ:
RecursiveCharacterTextSplitter
) từ thư viện. - Bạn cấu hình
chunk_size
(kích thước mong muốn) vàchunk_overlap
(lượng nội dung trùng lặp). - Thư viện tự động áp dụng chiến lược chia (thường là recursive, thử các dấu phân cách khác nhau) để tạo ra các chunk.
- Việc sử dụng thư viện giúp bạn tiết kiệm thời gian và tận dụng các phương pháp chunking đã được tối ưu hóa.
Ứng dụng của Chunking
Chunking là bước tiền xử lý thiết yếu trong nhiều ứng dụng hiện đại, đặc biệt là khi kết hợp với AI:
- Đưa vào LLM: Cung cấp các chunk cho LLM để tóm tắt, dịch, phân tích sentiment, hoặc trả lời câu hỏi về từng phần của tài liệu lớn.
- Hệ thống RAG (Retrieval Augmented Generation): Đây là một kiến trúc phổ biến để xây dựng chatbot hoặc trợ lý AI có thể trả lời câu hỏi dựa trên kiến thức cụ thể của bạn (không phải kiến thức chung của LLM). RAG hoạt động bằng cách:
- Xử lý và chunking tài liệu kiến thức của bạn.
- Tạo vector embedding cho từng chunk (biến văn bản thành các vector số).
- Lưu trữ các vector này trong Vector Database.
- Khi nhận câu hỏi của người dùng (query), tạo embedding cho câu hỏi đó.
- Tìm kiếm trong Vector Database để tìm các chunk có embedding tương tự với câu hỏi (nghĩa là các chunk có nội dung liên quan).
- Đưa câu hỏi gốc cùng với các chunk liên quan vào LLM để nó tạo ra câu trả lời dựa trên thông tin từ các chunk đó. Chunking là bước đầu tiên và quan trọng nhất để các chunk liên quan được tìm thấy và đưa vào LLM.
- Semantic Search (Tìm kiếm ngữ nghĩa): Thay vì tìm kiếm dựa trên từ khóa, Semantic Search tìm kiếm dựa trên ý nghĩa của văn bản. Chunking giúp bạn tạo ra các đơn vị nhỏ có ý nghĩa để tạo embedding và tìm kiếm chính xác hơn.
- Tóm tắt Tài liệu Lớn: Chia tài liệu thành các chunk, tóm tắt từng chunk bằng LLM, sau đó tổng hợp các bản tóm tắt nhỏ lại.
Comments