Bạn đang bổ sung chức năng nhập tài liệu cho một back-office. Người dùng tải lên một tệp PDF duy nhất, nhưng thực tế tệp này chứa nhiều hồ sơ cần được lưu riêng: một hóa đơn, rồi một hóa đơn khác, sau đó là một báo cáo. Cần cắt ở đâu?
Ý tưởng đầu tiên có vẻ đơn giản: phát hiện sự thay đổi loại tài liệu. Nhưng hai hóa đơn liền nhau vẫn là hai hóa đơn, kể cả khi chúng thuộc các hồ sơ khác nhau. Một ý tưởng khác là cắt khi số trang quay về 1. Đây là một dấu hiệu hữu ích, nhưng chúng ta sẽ đo lường vì sao nó không phải lúc nào cũng đủ.
Hướng dẫn này xây dựng một chuỗi ước lượng sự hiện diện của ranh giới giữa hai trang kề nhau, rồi tập hợp các quyết định đó thành các tài liệu hoàn chỉnh. Chúng ta làm việc với các tập hồ sơ "Atlas" hư cấu. Mục tiêu là hiểu và tái tạo cơ chế này, không phải cung cấp một dịch vụ phân loại tài liệu thật.
Với một tập hồ sơ chín trang, kết quả có thể là một dãy khoảng: [0, 3], [3, 5], [5, 9]. Mỗi khoảng sẽ tạo thành một PDF riêng. Lưu ý quy ước: điểm bắt đầu được tính, điểm kết thúc không được tính, như trong lát cắt mảng Python pages[start:end].
| Khoảng | Chỉ số trang được giữ | Trang người đọc thấy |
|---|---|---|
[0, 3] |
0, 1, 2 | 1 đến 3 |
[3, 5] |
3, 4 | 4 đến 5 |
[5, 9] |
5, 6, 7, 8 | 6 đến 9 |
Vì vậy, trang có chỉ số 3 không bị nhân đôi: nó chỉ thuộc về khoảng thứ hai. Ví dụ này minh họa định dạng đầu ra, không mô tả một lần chạy mới của tập dữ liệu.
Mục tiêu của chúng ta gồm hai yêu cầu riêng biệt: tìm đúng các điểm cắt, rồi xuất đúng các trang theo đúng thứ tự và không mất trang nào. Một lần xuất trung thực vẫn có thể chứa điểm cắt sai; một lần phân đoạn đúng không đảm bảo giữ nguyên mọi thuộc tính của PDF.
Những khái niệm hữu ích trước khi chạy mô hình
Một câu hỏi nhị phân nhỏ thay vì phân loại theo nghiệp vụ
Ở đây, một tập hồ sơ là tệp chứa nhiều tài liệu liên tiếp. Giữa hai trang, ta luôn đặt cùng một câu hỏi: “Trang tiếp theo có bắt đầu một tài liệu mới không?” Câu trả lời mong đợi là 1 cho một ranh giới và 0 cho sự tiếp nối. Đây là phân loại nhị phân: chỉ có hai khả năng.
Một tập hồ sơ có n trang sẽ có n - 1 vị trí cắt nội bộ. Vì vậy, bốn trang cho ba quyết định. Điểm đầu và cuối tệp đã biết: không cần hỏi mô hình liệu có trang nào trước trang đầu hay sau trang cuối hay không.
Bỏ sót một ranh giới thật gây ra gộp nhầm: hai tài liệu vẫn nằm trong cùng một PDF. Thêm nhầm ranh giới gây ra cắt quá mức: một tài liệu bị chia thành nhiều tệp. Hai lỗi này khác nhau và chúng ta sẽ đếm riêng.
Mô hình không học cách nói “đây là một hóa đơn hợp lệ”. Nó học cách đề xuất một điểm tách. Việc sau đó nhận diện loại, hồ sơ hay trạng thái của tài liệu là một nhiệm vụ khác.
Biến văn bản trên trang thành các con số
MiniLM ở đây đóng vai trò bộ mã hóa văn bản. Nó không soạn câu trả lời: nó biến văn bản thành các vector, tức các danh sách số mà bộ phân loại có thể sử dụng. Các biểu diễn này cũng được gọi là embedding.
Quy trình mã hóa riêng phần đầu, phần thân và phần chân của mỗi trang. Nhờ vậy, các vùng vẫn được phân biệt thay vì lập tức trộn thành một biểu diễn duy nhất. Sau đó, các phép đo hình học có thể quan sát bổ sung thông tin văn bản.
Trước khi mã hóa, tokenizer cắt văn bản thành các đơn vị gọi là token. Một token có thể là từ, một mảnh từ hoặc một ký hiệu; không nhất thiết là cả một từ. Các token đặc biệt cũng phân định chuỗi. MiniLM tạo một biểu diễn cho mỗi vị trí.
Còn một khó khăn thực tế: hai văn bản không nhất thiết có cùng số token. Để xử lý chúng cùng lúc, ta có thể bổ sung văn bản ngắn hơn bằng padding, hay phần đệm. Các vị trí này phục vụ tính toán theo lô, không dùng để mô tả trang.
Mặt nạ chú ý phân biệt các vị trí được giữ lại với phần đệm. Sau đó, pooling có mặt nạ lấy trung bình các vector được giữ để tạo biểu diễn có kích thước cố định. Tài liệu Sentence Transformers về pooling trình bày bước chuyển từ chuỗi có độ dài thay đổi sang vector kích thước cố định này.
Hãy xét một phép tính minh họa nhỏ, với các vector hai chiều không xuất phát từ MiniLM. Hai vị trí hợp lệ có giá trị [2, 4] và [4, 2]; một vị trí đệm có giá trị [100, 100]. Với mặt nạ [1, 1, 0], trung bình là [3, 3]. Không có mặt nạ, nó sẽ xấp xỉ [35,33 ; 35,33]: phần đệm sẽ làm méo hoàn toàn biểu diễn.
Do đó, cần loại phần đóng góp của nó và chia cho số vị trí hợp lệ, chứ không phải cho độ dài của mảng đã được đệm. Mã pooling có mặt nạ chính thức cho phép tìm lại hai thao tác này.
Phần nào học, phần nào giữ nguyên
Các trọng số của MiniLM được đóng băng: chúng không thay đổi trong quá trình học này. Bộ mã hóa vẫn phải tính toán để biểu diễn các trang; “đóng băng” không có nghĩa là “không tốn chi phí tính toán”.
Chỉ đầu MLP, viết tắt của perceptron nhiều lớp, được huấn luyện. Bạn có thể xem nó như một hàm số nhỏ có các tham số điều chỉnh được: nó nhận đặc trưng của hai trang và tạo ra một điểm số ranh giới. Các tham số của nó là các số được học, khác với đặc trưng là các số được cung cấp ở đầu vào.
Trong lúc huấn luyện, một hàm mất mát đo chênh lệch giữa đầu ra mô hình và câu trả lời mong đợi. BCE, hay entropy chéo nhị phân, phạt sự tự tin cao ở phía sai. Với một ranh giới thật, dự đoán xác suất 0,9 cho mức phạt -ln(0,9), khoảng 0,1054; dự đoán 0,1 cho khoảng 2,3026. Các giá trị này giải thích phép tính, không phải mất mát được đo trên Atlas.
Dự án dùng PyTorch và BCEWithLogitsLoss, nhận trực tiếp các điểm số thô và kết hợp các phép toán theo cách ổn định về số học. Chúng ta sẽ giải thích các điểm số này, hay logit, trước khi đọc bộ giải mã.
Ba phương pháp, ba vai trò để so sánh
Phương pháp đầu tiên là một baseline, một mốc so sánh đơn giản: cắt khi số trang hiển thị quay lại 1. Đây không phải bộ phân loại loại tài liệu.
Phương pháp thứ hai dùng MLP, rồi một ngưỡng: mỗi vị trí cắt được quyết định riêng từ xác suất của nó.
Phương pháp thứ ba dùng lại các điểm số của MLP, nhưng chọn một phân hoạch hoàn chỉnh dưới độ dài tài liệu tối đa. Nó sử dụng quy hoạch động, mà chúng ta sẽ đọc như việc tìm kiếm lời giải tái sử dụng các kết quả trung gian.
Các phương pháp được so sánh trên cùng một tập kiểm thử. Chú thích, còn gọi là label hoặc gold, là các câu trả lời tham chiếu. Tùy phần dữ liệu, chúng dùng để học hoặc để đo; chúng không phải đặc trưng quan sát được của trang. Label kiểm thử không bao giờ được đưa vào các container inference và learning.
Huấn luyện điều chỉnh các tham số, xác thực chọn ngưỡng, còn kiểm thử đo quy tắc thu được. Các ràng buộc độ dài cũng được khai báo trước khi kiểm thử. Điều chỉnh các lựa chọn này sau khi xem đáp án kiểm thử chẳng khác nào làm bài với đáp án mở sẵn.
Điều kiện tiên quyết và kiểm tra môi trường
Bạn cần biết đọc Python, dùng terminal và chạy Docker. PyTorch thực hiện việc học; NumPy được dùng, đặc biệt trong bộ giải mã. Các khái niệm về phân loại nhị phân vừa được giới thiệu ở trên.
Quy trình tham chiếu được thiết kế cho Linux có GPU. Khuyến nghị RTX 3090 với 24 Gio, 24 Gio RAM và 25 Gio dung lượng đĩa. VRAM là bộ nhớ của card đồ họa; RAM là bộ nhớ máy. Quy trình r002 thực sự đã dùng RTX 3090, nhưng các chỉ dẫn này không phải mức tối thiểu được đảm bảo cho mọi cấu hình. Kết quả ở đây chưa thiết lập một quy trình CPU tương đương.
Giải nén ZIP rồi mở terminal tại thư mục gốc của nó. Mọi lệnh sau đây được chạy từ thư mục này, theo đúng thứ tự. Các phiên bản và tệp đã khóa của dự án là tham chiếu cần tái tạo; tài liệu được liên kết giúp hiểu cơ chế, không thay thế các phụ thuộc của dự án.
GPU hiển thị qua nvidia-smi trên máy chủ không nhất thiết dùng được trong Docker. NVIDIA Container Toolkit cung cấp tích hợp cần thiết với công cụ container. Chẩn đoán của quy trình sẽ báo sự thiếu vắng của nó và hành động cần làm, nhưng không tự cài đặt thay bạn. Biến GPU_DEVICE=1 có thể chọn card khác trước khi chạy quy trình, tùy cấu hình được cung cấp.
Kiểm tra máy chủ và xây dựng các dịch vụ
sh scripts/preflight.sh
sh scripts/setup.sh
docker compose config --quiet
docker compose build acquisition tutorial inference learning
Preflight là bước kiểm tra trước: Docker, Compose, bộ nhớ, dung lượng đĩa và khả năng nhìn thấy GPU trên Linux. Sau đó chạy script chuẩn bị, rồi Compose kiểm tra cấu hình và xây dựng bốn dịch vụ.
docker compose config --quiet thành công mà không hiển thị cấu hình khi cấu hình hợp lệ; đó là hành vi được ghi trong tài liệu tùy chọn --quiet. Không có văn bản không có nghĩa là được bỏ qua mã trả về lỗi.
Tải MiniLM và thực sự tính toán trong Docker
docker compose run --rm acquisition python scripts/acquire.py --config configs/minimum.yaml
docker compose run --rm tutorial python scripts/check_environment.py
docker compose run --rm tutorial python -m pytest -q
Bước tải xuống lấy các tệp MiniLM, rồi so sánh kích thước và dấu vân tay SHA-256 của chúng với các giá trị mong đợi. Dấu vân tay này nhận diện nội dung tệp. Việc thay thế nguyên tử tránh cài một tệp tải dở dang thay cho tài nguyên được mong đợi. Không có trọng số nào trong ZIP.
check_environment.py đi xa hơn kiểm tra máy chủ: nó thực sự tải MiniLM trong Docker và tính toán trên GPU. Cuối cùng, các kiểm thử xác minh đặc biệt quy hoạch động, việc xuất và ràng buộc độ dài. Quy trình tham chiếu báo cáo 35 kiểm thử thành công; điều đó không có nghĩa mọi tập hồ sơ thực tế đều sẽ được tách đúng.
Tạo các quyết định ranh giới
Chuẩn bị tài liệu mà không đưa đáp án của chúng cho bộ mã hóa
docker compose run --rm tutorial python -m pdfbundles.cli prepare --config configs/minimum.yaml
docker compose run --rm inference python -m pdfbundles.cli features --config configs/minimum.yaml
prepare tạo các tập hồ sơ hư cấu và tách các chú thích của chúng. features xây dựng đặc trưng của các trang kề nhau trong container không nhìn thấy các label này. Sau đó, huấn luyện sẽ dùng giám sát được phép, còn các điểm cắt gold của kiểm thử vẫn chỉ dành cho đánh giá.
Với một cặp trang, báo cáo r002 cho biết vector gồm 3 480 giá trị được đưa vào MLP và 462 209 tham số có thể huấn luyện trong đầu mạng. Con số thứ nhất mô tả đầu vào; con số thứ hai mô tả hàm đã học. Đừng nhầm chúng với số trang hoặc số tài liệu.
Các chiều này thuộc cấu hình của ZIP. Chỉ cái tên “MiniLM” không đủ để tái tạo chính xác cách ghép đặc trưng: để sửa đổi, cần theo dõi quá trình xây dựng chúng trong dự án, không được tự thêm cột chỉ từ tổng số.
Huấn luyện, ngắt, tiếp tục
docker compose run --rm learning python -m pdfbundles.cli train --config configs/minimum.yaml --mode interrupt
docker compose run --rm learning python -m pdfbundles.cli train --mode continuous --config configs/minimum.yaml
docker compose run --rm learning python -m pdfbundles.cli train --mode resume --config configs/minimum.yaml
docker compose run --rm learning python -m pdfbundles.cli verify-resume --config configs/minimum.yaml
Một epoch là một lượt đi qua các ví dụ huấn luyện. Ngân sách tổng ở đây là mười lăm epoch. Chế độ interrupt tạo quy trình bị ngắt; continuous cung cấp mốc được huấn luyện không có gián đoạn đó; resume tiếp tục phép tính bị ngắt. Đây không phải việc huấn luyện liên tiếp cùng một mô hình ba lần để cộng dồn bốn mươi lăm epoch.
Một checkpoint là trạng thái đã lưu, cho phép tiếp tục hoặc kiểm soát phép tính. verify-resume so sánh các trạng thái thu được để xác minh rằng việc ngắt rồi tiếp tục khôi phục kết quả của quy trình liên tục.
Đây là kiểm tra khả năng tái lập, không phải thước đo chất lượng tài liệu. Hai lần chạy có thể tái tạo chính xác cùng các lỗi. Sự đồng nhất quan sát được cũng chỉ áp dụng cho môi trường này: tài liệu PyTorch về khả năng tái lập nhắc rằng thay đổi nền tảng hoặc phiên bản có thể làm thay đổi kết quả.
Chọn ngưỡng trên tập xác thực
docker compose run --rm learning python -m pdfbundles.cli calibrate --config configs/minimum.yaml
calibrate chọn ngưỡng bằng tập xác thực. Ngưỡng thấp hơn có thể tạo nhiều điểm cắt hơn; ngưỡng cao hơn có thể bỏ sót nhiều hơn. Đánh giá phải đo sự đánh đổi này thay vì cho rằng một lựa chọn luôn tốt hơn.
Quy trình chọn 0,5. Ở đây, tên lệnh calibrate mô tả việc chọn quy tắc quyết định này. Nó không chứng minh rằng mọi xác suất mô hình đưa ra tương ứng với tần suất lỗi trong một ứng dụng thực tế.
Từ logit cục bộ đến tài liệu hoàn chỉnh
Đọc một logit trước khi ra quyết định
Một logit là số thô, có thể âm hoặc dương. Hàm sigmoid biến nó thành xác suất ranh giới: p = 1 / (1 + exp(-logit)). Định nghĩa này có trong tài liệu PyTorch về Sigmoid.
| Logit minh họa | Xác suất xấp xỉ | Xu hướng ở ngưỡng 0,5 |
|---|---|---|
| -2 | 0,1192 | Tiếp tục tài liệu |
| 0 | 0,5000 | Trường hợp bằng nhau cần phân định theo quy ước |
| 3 | 0,9526 | Cắt |
Ở ngưỡng 0,5, một logit dương nghiêm ngặt vì vậy nghiêng về ranh giới và một logit âm nghiêm ngặt nghiêng về sự tiếp nối. Điểm số 3 không có nghĩa là “ba ranh giới” hay “ba trang”.
Ngưỡng độc lập xử lý từng vị trí riêng. Điều đó đủ để tạo các khoảng liên tiếp, nhưng không áp đặt độ dài tối đa. Quy hoạch động bổ sung chính ràng buộc này.
Chấm điểm toàn bộ một phân hoạch
Để đánh giá một cách cắt, chương trình chọn một đóng góp ở mỗi vị trí nội bộ: log(p) nếu cắt, log(1 - p) nếu giữ hai trang cùng nhau. Sau đó nó cộng các đóng góp này.
Vì sao dùng logarithm? Nhân nhiều xác suất có thể tạo ra các số rất nhỏ. Cộng logarithm của chúng giữ nguyên thứ hạng các tích và giúp tính toán dễ hơn. Điểm số thu được âm hoặc bằng không; lớn hơn là tốt hơn, ngay cả khi tất cả đều âm. Vì vậy điểm -0,5 tốt hơn -3.
Điểm số này là tiêu chí so sánh được xây dựng từ đầu ra của MLP. Nó không phải xác suất đã hiệu chỉnh rằng toàn bộ tập hồ sơ được tách hoàn hảo. Bộ giải mã không xem chú thích nào để tính nó.
Mã hoàn chỉnh của bộ giải mã
Hàm sau nhận các logit ở vị trí nội bộ và độ dài tối đa. np là NumPy; score_segments và boundaries_of là các hàm trợ giúp của dự án hoàn chỉnh. Bản thân khối này không phải trình đọc hay trình xuất PDF.
def decode(logits, maximum):
logits = np.asarray(logits, dtype=np.float64)
if (
logits.ndim != 1
or not np.isfinite(logits).all()
or not isinstance(maximum, int)
or maximum < 1
):
raise ValueError("Invalid logits or length constraint")
n = len(logits) + 1
boundary = -np.logaddexp(0, -logits)
within = -np.logaddexp(0, logits)
prefix = np.concatenate([[0.0], np.cumsum(within)])
best = np.full(n + 1, -np.inf)
previous = np.full(n + 1, -1, dtype=int)
best[0] = 0
for end in range(1, n + 1):
candidates = []
for start in range(max(0, end - maximum), end):
# Internal non-boundaries are start..end-2, then one boundary at
# end-1 unless this is the final segment of the whole bundle.
segment = (
prefix[end - 1]
- prefix[start]
+ (boundary[end - 1] if end < n else 0.0)
)
candidates.append((best[start] + segment, -start, start))
value, _, start = max(candidates)
best[end] = value
previous[end] = start
segments = []
end = n
while end:
start = int(previous[end])
segments.append([start, end])
end = start
segments.reverse()
direct = score_segments(logits, segments)
if abs(direct - float(best[n])) > 1e-8:
raise ValueError("Boundary counted more than once")
return {
"segments": segments,
"boundaries": boundaries_of(segments),
"score": direct,
"maximum": maximum,
}
Chúng ta sẽ theo dõi bốn thao tác quan trọng: kiểm tra đầu vào, chấm điểm một đoạn, ghi nhớ đường đi tốt nhất, rồi dựng lại các khoảng.
1. Kiểm tra đầu vào và đặt đúng chỉ số
np.asarray(..., dtype=np.float64) chuẩn bị một mảng số có độ chính xác kép. Hàm từ chối mảng không một chiều, các giá trị không hữu hạn như NaN hoặc vô cực, cũng như độ dài không thỏa kiểm tra số nguyên dương.
Python có một sắc thái: isinstance(True, int) trả về đúng, vì bool là kiểu con của int. Vì vậy đoạn mã chấp nhận maximum=True như giới hạn 1. Không nên trình bày kiểm tra này như bộ xác thực nghiêm ngặt cho tham số API; cần bổ sung xác thực đó vào giao diện ứng dụng thật, mà không âm thầm sửa quy trình tham chiếu.
Số trang được suy ra từ n = len(logits) + 1. Ba logit mô tả bốn trang. Một mảng logit rỗng, theo quy ước này, mô tả tập hồ sơ một trang; nó không cho hàm phân biệt một PDF thực sự rỗng. Việc xác thực tài liệu nguồn thuộc một bước khác.
Các khoảng phải tuân thủ ba bất biến, những tính chất luôn đúng: bắt đầu ở 0, kết thúc ở n và tiếp giáp nhau không có lỗ hổng hay chồng lấn. Chúng cũng không được rỗng và không vượt quá maximum.
2. Tính điểm mà không đếm một ranh giới hai lần
Các mảng boundary và within lần lượt chứa log(p) và log(1 - p). Chúng dùng np.logaddexp, hàm tính log(exp(a) + exp(b)) theo cách phù hợp cho các phép toán số này. Nhờ vậy, mã làm việc trực tiếp từ logit, không phải làm tròn sigmoid thành 0 hoặc 1 trước rồi mới lấy logarithm.
Với một đoạn [start, end], các trang đi từ start đến end - 1. Vì vậy các sự tiếp nối bên trong đoạn nằm tại chỉ số từ start đến end - 2.
Lấy [0, 3]: nó chứa các trang 0, 1 và 2. Ta giữ cùng nhau 0–1 và 1–2. Nếu còn trang theo sau, ta thêm một điểm cắt giữa 2 và 3. Nếu 2 là trang cuối của tệp, điểm cắt đầu ra này không tồn tại.
Đó chính là vai trò của boundary[end - 1] if end < n else 0.0. Cuối tệp không thêm một ranh giới nội bộ nhân tạo.
Mảng prefix chứa tổng tích lũy của within. Để cộng các sự tiếp nối của một đoạn, chỉ cần một phép trừ: prefix[end - 1] - prefix[start]. Đây là cùng nguyên lý tổng tích lũy cho phép tính một tổng con giữa hai vị trí mà không phải cộng lại toàn bộ.
Ranh giới trước một đoạn đã được đoạn trước đó tính rồi. Đoạn mới tính các sự tiếp nối của nó và, nếu có, ranh giới đầu ra của nó. Sự phân chia này tránh đếm hai lần.
3. Giữ kết quả tốt nhất cho mỗi điểm kết thúc có thể
Quy hoạch động ở đây dựa trên một câu hỏi: “Để kết thúc một đoạn tại trang này, điểm bắt đầu nào cho tổng điểm tốt nhất?”
best[end] ghi nhớ điểm tốt nhất cho end trang đầu tiên. Có một chi tiết quan trọng trong cách cài đặt này: khi end < n, điểm này đã bao gồm điểm cắt đầu ra sau các trang đó. previous[end] giữ điểm bắt đầu của đoạn cuối được chọn.
best[0] = 0 khởi tạo đường đi trước mọi trang. Các điểm khác bắt đầu ở âm vô cực, để trạng thái chưa đạt tới không thể thắng trong phép so sánh.
Với một điểm kết thúc end, vòng lặp khám phá mọi điểm bắt đầu từ max(0, end - maximum) đến end - 1. Điều này cho phép các đoạn từ 1 đến maximum trang. Mỗi ứng viên cộng điểm tốt nhất đã biết cho tiền tố và điểm của đoạn mới.
Vì sao chỉ một điểm tốt nhất cho mỗi vị trí là đủ? Trong mã này, điểm của đoạn tiếp theo chỉ phụ thuộc vào các biên của nó và các logit mà nó dùng. Nó không cần biết loại hay số lượng tài liệu trước đó. Vì vậy, tái sử dụng tiền tố tốt nhất không loại bỏ sự tiếp nối tốt hơn nào theo tiêu chí này.
Khi điểm số hoàn toàn bằng nhau, tuple (score, -start, start) phân định các ứng viên. Python so sánh tuple từng phần tử: vì thế max ưu tiên start nhỏ nhất, tức đoạn hiện tại dài nhất. Đây là quy ước xác định, không phải thông tin về bản chất tài liệu. Nó không có nghĩa mã chủ động tối thiểu hóa tổng số tài liệu.
4. Quay từ điểm số về các khoảng
Khi đã đến vị trí cuối, while end lần ngược các tiền nhiệm. Khoảng cuối được tìm trước, rồi đến khoảng áp chót, v.v. segments.reverse() đưa danh sách về thứ tự đọc.
Điểm của các khoảng được dựng lại sau đó được tính lại bằng score_segments. Chênh lệch lớn hơn 1e-8 so với điểm động sẽ kích hoạt lỗi. Kiểm tra này phát hiện sự không nhất quán trong tính toán; nó không xác minh rằng các điểm cắt khớp với tài liệu mong đợi.
Đầu ra chứa các khoảng segments, các ranh giới boundaries, score và giới hạn maximum. Hàm này không tự tạo tên thư mục đích hay một loại tài liệu nghiệp vụ nào.
Làm lại phép tính với bốn trang
Đây là ví dụ sư phạm độc lập với Atlas: ba logit là [-2, 3, -1] và maximum bằng 2. Mô hình ưu tiên đúng một điểm cắt, giữa trang thứ hai và thứ ba.
Các đóng góp hữu ích xấp xỉ -0,1269 để tiếp tục ở vị trí đầu, -0,0486 để cắt ở vị trí thứ hai, và -0,3133 để tiếp tục ở vị trí thứ ba. Do đó, phân hoạch [0, 2], [2, 4] có điểm -0,4888.
Diễn tiến đầy đủ của best như sau. Hãy nhớ rằng các trạng thái trung gian bao gồm điểm cắt đầu ra của chúng.
Kết thúc end |
Điểm bắt đầu giữ lại previous[end] |
Điểm best[end] |
|---|---|---|
| 1 | 0 | -2,1269 |
| 2 | 0 | -0,1755 |
| 3 | 2 | -1,4888 |
| 4 | 2 | -0,4888 |
Lần truy ngược bắt đầu từ 4, tìm điểm đầu 2, rồi quay lại từ 2 và tìm 0. Sau khi đảo danh sách, ta có hai khoảng mong đợi trong ví dụ này. Bốn trang đều xuất hiện đúng một lần.
Ràng buộc cũng có thể buộc phải thỏa hiệp. Với bốn trang, các logit minh họa [-3, -0.2, -1] không ưu tiên điểm cắt nào. Nhưng maximum=3 cấm giữ cả bốn trang cùng nhau. Cắt sau trang thứ hai thêm mức phạt yếu nhất, -0,2, so với -3 hoặc -1 ở các vị trí khác. Bộ giải mã ở đây chọn [0, 2], [2, 4], thay vì máy móc lấp đầy một tài liệu đầu tiên ba trang.
Các phép tính nhỏ này giải thích thuật toán. Chúng không phải kết quả mới của tập dữ liệu chính.
Ràng buộc bổ sung gì — và không bổ sung gì
Không có ràng buộc đang hoạt động, điểm toàn cục này khôi phục các quyết định độc lập ở ngưỡng 0,5, ngoài các khác biệt có thể có khi phân định trường hợp bằng nhau. Có thể thấy trực tiếp: thay một sự tiếp nối bằng một ranh giới làm điểm thay đổi log(p) - log(1 - p), tức logit. Logit dương cải thiện điểm khi cắt; logit âm làm nó xấu đi.
Vì vậy, quy hoạch động không mang lại một mô hình hiểu tài liệu mới. Nó tối ưu cùng các dấu hiệu cục bộ trong các phân hoạch được phép. Hơn nữa, đoạn mã không nhận ngưỡng đã hiệu chỉnh làm đối số: nó dùng trực tiếp log-xác suất và maximum. Không nên cho rằng nó sẽ tái tạo bất kỳ ngưỡng nào khác được chọn cho phương pháp độc lập.
Ràng buộc này là cứng: một đoạn quá dài bị cấm, ngay cả khi mô hình rất tự tin rằng cần giữ các trang của nó cùng nhau. Ngược lại, với mọi maximum dương được chấp nhận, luôn có lời giải gồm các tài liệu một trang. Vì vậy bộ giải mã không tự động từ chối khi một độ dài có vẻ không tương thích với nội dung. Việc rà soát sẽ cần một quy tắc bổ sung.
So sánh các phương pháp và xuất trang
docker compose run --rm inference python -m pdfbundles.cli baseline --config configs/minimum.yaml
docker compose run --rm tutorial python -m pdfbundles.cli evaluate --config configs/minimum.yaml
baseline tính mốc tham chiếu đơn giản; evaluate tạo đầu ra kiểm thử và xuất các khoảng vào outputs/separated/. Các lệnh này không huấn luyện lại MLP và không chọn ngưỡng mới. Bài tập sẽ đến sau.
600 trang kiểm thử được so sánh với nguồn bằng văn bản gốc và pixel được kết xuất theo tỷ lệ của khóa học, theo cùng thứ tự. Hãy xem các kiểm tra này như kiểm tra một lần xuất ứng dụng: các phần tử mong đợi có được sao chép mà không bỏ sót hay lặp lại không?
Điều đó không xác minh chúng được nhóm đúng. Xuất trung thực trang 1 đến 6 trong cùng một tệp vẫn là một lần gộp sai nếu chúng thuộc hai tài liệu ba trang.
Kiểm tra cũng không chứng minh một PDF bất kỳ sẽ giữ được chữ ký, biểu mẫu hay chú thích. Nó so sánh các thuộc tính thực sự được kiểm tra trên PDF được tạo, không phải mọi đối tượng và hành vi PDF có thể có.
Diễn giải kiểm thử mà không che giấu điểm yếu
Đếm các quyết định, rồi đến các tập hồ sơ hoàn chỉnh
Tập kiểm thử có 50 tập hồ sơ, 600 trang và 550 quyết định nội bộ. Phép đếm rất đơn giản: mỗi tập n trang đóng góp n - 1 quyết định; trên toàn bộ tập, 600 - 50 = 550. Trong các vị trí này, 180 là ranh giới thật.
Độ chính xác hỏi: trong các điểm cắt dự đoán, có bao nhiêu điểm đúng? Độ bao phủ hỏi: trong các điểm cắt mong đợi, chúng ta tìm được bao nhiêu? F1 là trung bình điều hòa của hai đại lượng này: 2 × précision × rappel / (précision + rappel).
Để đọc kết quả trực tiếp từ các số đếm, ta cũng có thể viết F1 = 2 × TP / (2 × TP + FP + FN). Ở đây, TP đếm ranh giới thật được tìm lại, FP là các lần cắt quá mức và FN là các lần gộp do bỏ sót ranh giới.
Cuối cùng, một tập hồ sơ chính xác đòi hỏi mọi khoảng của nó đều đúng. Chỉ một điểm cắt sai đủ để không đạt tiêu chí này cho cả tập, ngay cả khi các quyết định khác đúng. Vì vậy F1 ranh giới và tỷ lệ tập hồ sơ chính xác trả lời hai câu hỏi khác nhau.
Kết quả của quy trình r002
Bảng này lấy lại các số đếm đã công bố. Hai phương pháp đã học có cùng kết quả chất lượng trên kiểm thử này; vì vậy chúng dùng chung một cột, mà không bị nhầm là cùng một cách cài đặt.
| Chỉ số | Quy tắc đánh số | MLP + ngưỡng 0,5 / MLP + bộ giải mã động |
|---|---|---|
| Ranh giới thật tìm lại | 109/180 | 180/180 |
| Cắt quá mức | 22 | 0 |
| Gộp nhầm | 71 | 0 |
| F1 ranh giới | 0,700965 | 1,00 |
| Tập hồ sơ hoàn toàn chính xác | 30/50 | 50/50 |
| Ranh giới giữa tài liệu cùng loại tìm lại | 53/89 | 89/89 |
Hãy tính lại cho quy tắc đơn giản. Nó dự đoán 109 + 22 = 131 điểm cắt. Vì vậy độ chính xác là 109/131, khoảng 0,8321, và độ bao phủ là 109/180, khoảng 0,6056. F1 là 218/311, tức 0,700965 sau làm tròn. Hai giá trị đầu được tính từ số đếm công bố, không phải từ thí nghiệm mới.
Quy tắc cho 30 tập hồ sơ hoàn toàn chính xác trên 50, tức 60 %. Nó chỉ tìm được 53 trong 89 ranh giới giữa tài liệu cùng loại. Việc quay về 1 là dấu hiệu phân trang, không phải mã nhận dạng hồ sơ: đó là điểm yếu cụ thể mà so sánh này cho thấy.
MLP với ngưỡng chọn trên tập xác thực và bộ giải mã động đều đạt F1 = 1,00, không cắt quá mức cũng không gộp nhầm. Vì vậy quy hoạch động không tăng thêm chất lượng nào trên kiểm thử chính. Ở đây nó thêm chính sách về độ dài, không phải lợi ích chính xác đã được chứng minh.
Thời gian tính toán và phạm vi kết quả
Giải mã động mất khoảng 0,000060 giây mỗi tập hồ sơ, so với 0,000016 giây cho ngưỡng. Tương ứng khoảng 60 và 16 microgiây. Các thời gian này áp dụng cho giải mã, không phải đọc PDF, mã hóa MiniLM, huấn luyện hay xuất hoàn chỉnh.
Quy trình quan sát kéo dài 485,526 giây, với cache Docker công khai, trong đó 35 kiểm thử trong 8,78 giây. Đây không phải đo lường khi cache lạnh cũng không phải cam kết hiệu năng cho máy khác.
Tập dữ liệu tổng hợp chuẩn bị cho kiểm thử thường được gọi là một fixture. Ở đây, nội dung, tham chiếu và nhóm theo các mẫu rất đều đặn. MiniLM vẫn đóng băng; chỉ dùng một hạt ngẫu nhiên, hay seed. Không có OCR — nhận dạng văn bản từ ảnh — cũng không có rà soát thủ công trong các kết quả được trình bày.
Vì vậy điểm hoàn hảo mô tả fixture này. Nó không chứng minh thành công trên bản quét, độ bền với tài liệu của người dùng, hay tính hữu ích của cùng giới hạn độ dài trong bối cảnh khác.
Tổng kết trước bài tập
Chúng ta đã xây dựng ba tầng riêng biệt. Văn bản và hình học cung cấp các quan sát. MLP học một điểm cho mỗi vị trí cắt, sau đó ngưỡng hoặc quy hoạch động chọn các khoảng. Cuối cùng, đánh giá so sánh các điểm cắt với chú thích và kiểm tra riêng các trang đã xuất.
Điểm phân biệt quan trọng lúc này là: một lời giải có thể tối ưu cho thuật toán nhưng vẫn sai đối với tài liệu. Điều này đặc biệt xảy ra khi phân hoạch đúng bị cấm bởi một ràng buộc. Bài tập sẽ tách riêng tình huống này mà không thay đổi mô hình.
Bài tập: một ràng buộc có thể cấm đáp án đúng
Dự đoán trước khi chạy
Hãy tưởng tượng một tập hồ sơ mười hai trang gồm hai tài liệu, mỗi tài liệu sáu trang. Tham chiếu mong đợi các khoảng [0, 6] và [6, 12]. Mô hình cung cấp mười một logit thuận lợi cho cách tách này.
Nhưng cấu hình áp đặt maximum=5. Bộ giải mã vẫn có thể giữ một tài liệu sáu trang không? Sự tự tin rất cao có đủ để vượt qua giới hạn đó không? Hãy viết câu trả lời trước khi chạy lệnh, rồi dự đoán điều gì thay đổi nếu giới hạn thành 8.
docker compose run --rm tutorial python -m pdfbundles.cli exercise --config configs/exercise.yaml
Quan sát, rồi giải thích
Phân hoạch đúng là không thể với giới hạn 5: không đoạn sáu trang nào được chấp nhận. Đây không nhất thiết là lỗi của MLP. Quá trình tìm kiếm đơn giản là không được phép trả về hai tài liệu nguyên vẹn.
Trong bài tập được cung cấp, decode được chạy lại trên cùng mười một logit, với các giới hạn 5 rồi 8. Không mô hình nào được huấn luyện lại và kiểm thử chính không được dùng.
Với 5, kết quả giữ ranh giới thật nhưng thêm hai lần cắt quá mức. Điều này cho một điểm cắt đúng trên ba điểm được dự đoán: độ chính xác 1/3, độ bao phủ 1, rồi F1 ranh giới = 0,5. Bốn đoạn thay cho hai tài liệu mong đợi.
Vì sao bốn đoạn, trong khi ba đoạn đủ phủ mười hai trang với giới hạn năm? Bởi phủ hết trang không phải tiêu chí duy nhất. Để giữ ranh giới thật sau trang thứ sáu, mỗi tài liệu sáu trang phải tự bị cắt ít nhất một lần. Đó chính là điều xảy ra trong bài tập này; chỉ chia số trang tổng cho giới hạn không mô tả phân hoạch được chọn.
Với 8, các khoảng sáu trang lại được phép và ở đây được tìm thấy: F1 = 1. Giới hạn cho phép đáp án đó, chứ không ra lệnh tạo tài liệu tám trang.
Nới một ràng buộc mở rộng tập lời giải khả dĩ, nhưng không đảm bảo tìm lại tham chiếu trong mọi tập hồ sơ. Trong bài tập này, các điểm số ưu tiên lời giải đúng ngay khi nó được phép trở lại. Với tài liệu khác, các điểm số sai có thể ưu tiên một lần gộp sai.
Vì vậy bài học không phải là “chọn 8 cho mọi PDF”. Cần xác định chính sách tương thích với tài liệu mong đợi, rồi đo lường nó trên một tập riêng trước khi sử dụng.
Hình minh họa, mốc và dọn dẹp
Đọc đầu ra trước khi xóa chúng
Hãy xem outputs/evaluation.json để biết các chỉ số và outputs/exports.json để biết các kiểm tra xuất. Đồng thời mở các PDF trong outputs/separated/ và các PNG có sẵn. Hãy quay lại một tập hồ sơ cụ thể: các ranh giới có đúng không, và mỗi trang có được xuất đúng chỗ không?
Các lệnh sau tạo hình minh họa từ kết quả có sẵn, kiểm tra các mốc, rồi dọn đầu ra. Hãy giữ các tệp hữu ích trước khi chạy dọn dẹp, vì các PDF đã xuất cũng sẽ bị xóa.
docker compose run --rm tutorial python lesson/figures/plot.py
docker compose run --rm tutorial python scripts/check_checkpoints.py
docker compose down
docker compose run --rm tutorial python scripts/clean.py --outputs-only
docker compose down
Kiểm tra mốc so sánh các bản sao nguồn sư phạm. Nó không thay thế việc so sánh trạng thái huấn luyện đã được thực hiện bởi verify-resume.
Lệnh docker compose down đầu tiên được đặt có chủ ý trước clean trong quy trình r002. clean --outputs-only hiển thị rồi chỉ xóa outputs/; nguồn, tập dữ liệu và tài nguyên vẫn còn. Lệnh down cuối cùng kết thúc quy trình. Không có lý do gì để thực hiện một lần dọn sạch toàn bộ các dự án Docker khác của bạn.
Chẩn đoán ở đúng tầng
GPU hoạt động trên Linux nhưng không hoạt động trong container? Hãy xem lại chẩn đoán preflight và kiểm tra môi trường. Khi đó, khó khăn nằm ở việc Docker truy cập GPU, không phải ở quyết định ranh giới.
Bước tải xuống từ chối một tệp? Hãy kiểm tra dung lượng trống và các kiểm soát kích thước cùng dấu vân tay. Thay một mô hình khác sẽ thay đổi trải nghiệm thay vì giải quyết vấn đề tái tạo.
Lần tiếp tục khác lần chạy liên tục? Hãy giữ các trạng thái và thông báo kiểm tra trước mọi thao tác dọn dẹp. F1 tương tự không đủ để kết luận việc tiếp tục là giống hệt nhau.
Các bản xuất trung thực nhưng tài liệu bị nhóm sai? Hãy kiểm tra điểm số và giới hạn độ dài. Phần cần giải thích là cách cắt; việc sao chép trang có thể đã hoạt động hoàn hảo.
Từ khóa học đến ứng dụng
Hãy bắt đầu với các tập hồ sơ được phép và có tính đại diện, với chú thích đáng tin cậy và một tập kiểm thử để riêng. Xác định độ dài chấp nhận được cùng các nhóm hiểu rõ tài liệu. Một giới hạn chỉ được chọn để làm tính toán dễ hơn có thể cấm một tài liệu hoàn toàn bình thường.
Hãy dự kiến việc rà soát các trường hợp ít tự tin hoặc có độ dài dường như không tương thích. Trong giao diện back-office, điều này có thể là bản xem trước các trang ở hai bên điểm cắt, cùng khả năng xác nhận hoặc di chuyển nó. Giao diện này là một hướng thích nghi, không phải tính năng được chứng minh bởi quy trình r002.
Bổ sung các kiểm tra chuyên biệt khi tài liệu có chữ ký, biểu mẫu, chú thích, bản quét cần OCR hoặc yêu cầu khả năng truy cập. Kiểm tra văn bản và pixel của fixture không thay thế các xác minh đó.
Mạch chính vẫn đơn giản: mô hình cung cấp các dấu hiệu, bộ giải mã áp dụng một chính sách, và đánh giá kiểm tra riêng tính đúng đắn của cách cắt cùng độ trung thực của lần xuất.
Tải khóa học r002: ZIP r002.
Tài liệu tham khảo để tìm hiểu thêm
Các liên kết đến Sentence Transformers, PyTorch, NumPy, Python, Docker và NVIDIA làm rõ các thao tác được giải thích trong bài. Các ví dụ số nhỏ mang tính sư phạm; kết quả Atlas vẫn là của quy trình r002. Để tái tạo quy trình này, hãy giữ nguyên các lệnh, cấu hình và tệp đã khóa trong ZIP, không thay chúng bằng ví dụ cài đặt của tài liệu hiện hành.