Bạn thêm chức năng nhập hóa đơn vào một ứng dụng web. Việc tải tệp hoạt động, PDF mở được và trình trích xuất tìm thấy các con số trên trang. Chỉ còn việc điền các trường biểu mẫu… nhưng số nào trong số đó là tổng tiền?
Biểu thức chính quy có thể nhận ra 250.16. Nhưng riêng nó không cho biết đó là số tiền phải trả, thuế hay tổng phụ. Để biết, chúng ta đọc các nhãn quanh con số và xem vị trí của nó. Chương trình của chúng ta cũng sẽ khai thác văn bản và vị trí.
Chúng ta sẽ xây dựng một trình trích xuất trường bằng Python, Docker và LayoutLM. Ta theo dõi hóa đơn hư cấu AL-01-03: tổng phụ là 250.00, thuế là 0.16, tổng là 250.16. Các giá trị này là sợi chỉ xuyên suốt, không phải quy tắc kế toán để mô hình học. Ta muốn tìm các khoản được ghi trong tài liệu, không tính lại hay bịa ra trường bị thiếu.
Kết quả cần đạt gói trong một câu: tìm một giá trị, xác định vai trò của nó và chỉ ra nó đến từ đâu. Ta cũng so sánh mạng với các quy tắc đơn giản. Mục tiêu là hiểu mỗi phương pháp mang lại gì, không mặc định phương pháp dùng AI chắc chắn tốt hơn.
Tải xuống toàn bộ dự án của hướng dẫn
Bạn sẽ xây dựng và học gì
Chương trình nhận các PDF chứa văn bản gốc và trả về sáu trường: số hóa đơn, ngày, nhà cung cấp, tổng phụ, thuế và tổng tiền. Mỗi giá trị giữ lại trang, các từ và hình chữ nhật của chúng. Trường vắng mặt hoặc mơ hồ có thể để trống.
Ở đây, PDF gốc chứa các ký tự mà chương trình có thể trích xuất. Đây không cùng điểm xuất phát với bản quét chỉ chứa ảnh: bản đó trước hết cần nhận dạng ký tự quang học, hay OCR. Bước này không thuộc lộ trình đang trình bày.
Chuỗi xử lý như sau:
PDF → từ và hình chữ nhật → token con → LayoutLM → nhãn theo từ → các trường kèm nguồn gốc.
Hãy hình dung thao tác tô sáng: chương trình phải gán một số từ cho « tổng tiền », số khác cho « ngày », và để các từ không có vai trò trong sáu trường sang một bên. Nhãn mô tả vai trò được chọn; hình chữ nhật cho phép tìm lại từ trên trang.
LayoutLM liên kết các biểu diễn đã học của văn bản và vị trí. Trong hướng dẫn này, nó phân loại token; nó không viết câu trả lời và không đọc trực tiếp pixel của bản quét. Mã Python trích xuất dữ liệu, chuẩn bị đầu vào, gom dự đoán và kiểm tra kết quả.
Bạn sẽ học cách đưa tọa độ về cùng một thang đo, giữ liên hệ giữa từ và các mảnh của chúng, huấn luyện bộ phân loại và xử lý trang quá dài để nằm trong một cửa sổ. Sau đó ta sẽ đo chất lượng trích xuất mà không nhầm một giá trị đúng với một vị trí đúng.
Một khung định vị một từ; nhãn mô tả vai trò của từ đó. Nguồn gốc cho phép quay lại trang.
Chuẩn bị môi trường
Bạn cần biết dùng Python, terminal và Docker. Không cần biết sẵn LayoutLM. Ta sẽ giải thích các khái niệm mạng nơ-ron khi chúng xuất hiện: ở đây tensor là một mảng số, còn huấn luyện điều chỉnh các tham số mô hình từ những ví dụ đã gán nhãn.
Lộ trình đã được xác nhận trên Linux với RTX 3090 24 GiB. README nêu 32 GiB RAM và 24 GiB dung lượng đĩa: đây là chỉ dẫn quy mô, không phải mức tối thiểu đo trên mọi máy. RAM là bộ nhớ máy tính; 24 GiB của card đồ họa là bộ nhớ riêng của nó, VRAM.
Hãy chuẩn bị Docker Engine, Compose và GPU NVIDIA dùng được bởi các container. Giữ nguyên các phụ thuộc và cấu hình trong ZIP để tái lập lộ trình r002.
Kiểm tra Docker có thấy GPU của bạn không
GPU hiển thị trên máy chủ không đảm bảo một chương trình Docker sử dụng được nó. Script sau kiểm tra máy chủ và Docker; sau đó ta sẽ kiểm tra CUDA từ trong container. CUDA là môi trường tính toán dùng ở đây để thực hiện các phép toán của mạng trên GPU.
NVIDIA Container Toolkit cung cấp tích hợp giữa Docker Engine và trình điều khiển NVIDIA của máy chủ. Việc cài đặt tùy hệ thống của bạn: hãy dùng tài liệu chính thức của NVIDIA.
Giải nén ZIP, rồi mở terminal tại thư mục gốc, nơi chứa README.md và compose.yaml. Mọi lệnh sau đây đều chạy từ thư mục này. Bắt đầu bằng các kiểm tra sơ bộ.
sh scripts/preflight.sh
Hãy sửa mọi kiểm tra chặn trước khi tiếp tục. Script thiết lập sau đó tạo các thư mục cần thiết, Compose xác thực cấu hình rồi xây dựng ba dịch vụ.
sh scripts/setup.sh
docker compose config --quiet
docker compose build acquisition tutorial inference
Mỗi dịch vụ có một vai trò: acquisition tải tài nguyên, tutorial chuẩn bị dữ liệu và huấn luyện mô hình, inference dự đoán từ PDF và trọng số đã lưu. Trọng số là các tham số số mà mạng đã học.
Để định hướng trong các tệp, assets/ lưu mô hình và checkpoint, data/generated/ chứa kho dữ liệu, còn outputs/ nhận kết quả. Checkpoint là bản lưu trạng thái huấn luyện; ta cũng dùng nó để kiểm tra việc tiếp tục sau gián đoạn.
Suy luận có đầu vào và trọng số ở chế độ chỉ đọc, không có nhãn tham chiếu hay truy cập mạng. Tệp Compose đầy đủ trong ZIP mô tả các mount và hạn chế. Trong lộ trình này, sự tách biệt đó ngăn việc đưa đáp án mong đợi cho dịch vụ phải dự đoán chúng. Nó không thay thế kiểm soát truy cập của một ứng dụng thực.
Tải mô hình một lần
Ta dùng microsoft/layoutlm-base-uncased. Bản sửa đổi 30e3cdd39c11f09757b0fcf7598533d05052acef được cố định để tìm lại đúng các tệp. scripts/acquire.py kiểm tra kích thước và dấu vân tay nêu trong locks/models.lock.json, rồi lưu chúng vào assets/model/.
Bạn có thể so sánh việc khóa này với khóa phụ thuộc của một dự án web: chỉ giữ tên là chưa đủ, còn phải biết phiên bản và các tệp nào đã được dùng. Internet phục vụ lần tải này; sau đó mô hình được nạp từ tệp cục bộ.
docker compose run --rm acquisition python scripts/acquire.py --config configs/minimum.yaml
Giờ hãy kiểm tra môi trường từ Docker, rồi chạy các kiểm thử. Kiểm tra đầu tiên bổ sung cho kiểm tra máy chủ: điều quan trọng là chương trình container thực sự truy cập được GPU.
docker compose run --rm tutorial python scripts/check_environment.py
docker compose run --rm tutorial python -m pytest -q
Cần quan sát: lộ trình đã xác nhận đạt 26 kiểm thử thành công. Chúng kiểm tra, đặc biệt, tọa độ, căn chỉnh, cửa sổ và các trường hợp không hợp lệ. Chúng xác minh hành vi chương trình; không đảm bảo mô hình xử lý được hóa đơn của riêng bạn.
Hiểu dữ liệu trước khi dự đoán
Ba nhóm tài liệu, ba vai trò
Kho dữ liệu tổng hợp gồm 600 trang và 50 họ bố cục. « Tổng hợp » nghĩa là hóa đơn được tạo cho thí nghiệm thay vì thu thập từ khách hàng thật. Một họ tập hợp các tài liệu dùng chung khuôn bố cục.
Việc tách theo họ: 420 trang huấn luyện, 84 trang xác thực và 96 trang kiểm thử. Huấn luyện điều chỉnh tham số. Xác thực dùng để chọn checkpoint. Sau đó kiểm thử đo kết quả cuối cùng trên tài liệu không dùng cho các quyết định đó.
Vì sao không trộn ngẫu nhiên tất cả trang? Vì một phương pháp có thể thành công nhờ nhận ra khuôn từng thấy mà không xử lý tốt trình bày mới. Tách các họ làm giảm rủi ro này mà không tái hiện toàn bộ đa dạng của thế giới thật.
Đọc hóa đơn trước khi đọc tensor
Các hóa đơn Alder hư cấu chứa văn bản tiếng Anh gốc. pdfplumber trích xuất từ và hình chữ nhật của chúng; tài liệu về trích xuất văn bản mô tả các đối tượng này. Nhãn gắn từ với trường mong đợi: đó là đáp án tham chiếu để học và đo lường, không bao giờ dùng để dự đoán.
Hãy tạo dữ liệu và xem một lô, hay batch: một tập đầu vào được chuẩn bị cho mô hình.
docker compose run --rm tutorial python -m pdflayout.cli prepare --config configs/minimum.yaml
docker compose run --rm tutorial python -m pdflayout.cli inspect-batch --config configs/minimum.yaml
Quan sát mã định danh token, hình chữ nhật và nhãn. Với AL-01-03, ba từ đặc biệt đáng chú ý:
| Mã định danh từ | Văn bản trích xuất | Vai trò mong đợi |
|---|---|---|
55 |
250.00 |
Tổng phụ |
58 |
0.16 |
Thuế |
62 |
250.16 |
Tổng tiền |
Các tương ứng này đến từ trường hợp tham chiếu. Mô hình nhận văn bản và vị trí; trong quá trình huấn luyện, nhãn cho nó biết vai trò cần nhận ra.
Các mã định danh ổn định để phân biệt hai lần xuất hiện có cùng văn bản. Điều này giống hai dòng cơ sở dữ liệu có cùng giá trị nhưng khác khóa: tìm 250.16 ở đâu đó không có nghĩa đã tìm đúng từ.
Tám trang kiểm thử không có tổng tiền. Giá trị vắng mặt không phải số không, cũng không phải lời mời tính lại. Chương trình phải có thể không đưa ra kết quả. Ta sẽ kiểm tra sau xem sự kiềm chế này đo lường điều gì.
Xây dựng một mốc tham chiếu đơn giản
Trước khi huấn luyện mạng, hãy bắt đầu bằng phương pháp có thể viết mà không cần học: biểu thức chính quy kết hợp với độ gần của nhãn. Baseline, hay phương pháp tham chiếu, cho biết liệu các dấu hiệu trong kho dữ liệu đã đủ cho những quy tắc đơn giản chưa.
Ý tưởng không chỉ là nhận ra dạng của một khoản tiền. Còn phải gắn nó với nhãn phù hợp, như « TOTAL » hoặc « SUBTOTAL ». Mã trong ZIP định nghĩa các quy tắc thực sự được so sánh.
docker compose run --rm tutorial python -m pdflayout.cli baseline --config configs/minimum.yaml
Giữ lại kết quả này cho đánh giá cuối. Nó trả lời câu hỏi thực tế: mạng có làm tốt hơn một giải pháp đơn giản hơn trên những tài liệu ta quan tâm không? Một điểm số tốt riêng lẻ không cho biết điều đó.
Cung cấp cho mô hình từ và vị trí
Giờ ta có từ, hình chữ nhật và nhãn. Cần chuyển chúng thành các bảng mà mạng dùng được, mà không mất liên hệ với tài liệu.
Đưa mọi hình chữ nhật về cùng thang đo
Một hộp chứa x0, top, x1, bottom: các biên trái, trên, phải và dưới của từ. Nếu từng thao tác kích thước phần tử trong trang web, nguyên tắc hộp bao quanh sẽ quen thuộc. Chỉ cần làm việc trong hệ tọa độ của trang PDF đã chuẩn bị, không phải cửa sổ trình duyệt.
Ta biến đổi tọa độ của trang chuẩn, tức trang đưa về một hệ quy chiếu chung, thành số nguyên từ 0 đến 1000. Cần tính đến xoay và cắt trước bước này. Chia tỷ lệ một hộp sai hướng không sửa được hướng của nó.
Trong src/pdflayout/align.py, hàm này kiểm tra rồi chuẩn hóa từng hình chữ nhật.
def normalize_bbox(box, width, height):
if (
width <= 0
or height <= 0
or not (0 <= box[0] <= box[2] <= width and 0 <= box[1] <= box[3] <= height)
):
raise ValueError("Invalid canonical box")
return [
round(1000 * box[0] / width),
round(1000 * box[1] / height),
round(1000 * box[2] / width),
round(1000 * box[3] / height),
]
Hai tọa độ ngang được chia cho chiều rộng; hai tọa độ dọc chia cho chiều cao. Nhân 1000 cho thang đo mong đợi và round() làm tròn giá trị. Kiểm tra đầu từ chối kích thước trang không dương, hộp đảo ngược hoặc hộp nằm ngoài trang.
Hãy lấy một ví dụ số để minh họa, độc lập với tọa độ thật của AL-01-03. Trên trang rộng 600 và cao 800, hộp [300, 560, 360, 576] thành [500, 700, 600, 720]. Cạnh trái của nó ở 50% chiều rộng; cạnh trên ở 70% chiều cao.
Nếu nhân đôi trang và mọi tọa độ của hộp, ta nhận cùng một hình chữ nhật đã chuẩn hóa. Đó là lợi ích: biểu diễn vị trí tương đối, không chỉ khoảng cách phụ thuộc định dạng. Các con số này mô tả hình học, không phải xác suất. Tài liệu LayoutLM nêu chi tiết định dạng hộp được mong đợi.
Từ từ đến token con
Mô hình không xử lý trực tiếp chuỗi Python. Tokenizer biến văn bản thành các đơn vị thuộc từ vựng của nó, rồi thành mã số. Tokenizer của lộ trình này dùng WordPiece, có thể tách một từ thành nhiều token con. Khóa học Hugging Face về WordPiece giải thích cách tách này.
Vì vậy, mã token và mã từ không có cùng vai trò. Mã đầu chỉ một mục trong từ vựng mô hình. Mã thứ hai giúp quay lại một lần xuất hiện chính xác trong PDF.
Với mỗi token con, ta lặp lại hộp của từ gốc. Điều này không có nghĩa ta biết vị trí chính xác của từng mảnh trong từ: mọi mảnh vẫn gắn với cùng hình chữ nhật nguồn.
Cũng cần chuyển đổi nhãn. Ta dùng quy ước BIO:
| Nhãn | Ý nghĩa |
|---|---|
B-TOTAL |
Bắt đầu một thực thể kiểu TOTAL. |
I-TOTAL |
Phần tiếp theo của cùng thực thể. |
O |
Từ nằm ngoài các trường cần trích xuất. |
Ở đây, một thực thể là tập từ mang cùng vai trò, chẳng hạn giá trị tổng tiền. Giá trị trên một từ chỉ cần nhãn B. Với hai nhãn cho mỗi kiểu trường cộng O, sáu trường cho mười ba nhãn: 6 × 2 + 1.
Dự án chọn chỉ giám sát token con đầu tiên của mỗi từ: tại đó ta so sánh dự đoán với nhãn mong đợi. Các token con khác, token đặc biệt và phần đệm nhận -100, giá trị bị hàm mất mát bỏ qua.
Hàm sau chuyển nhãn từ sang biểu diễn này.
def align_word_labels(word_ids, first_flags, word_labels, label_to_id):
return [
label_to_id[word_labels[word]] if word is not None and first else -100
for word, first in zip(word_ids, first_flags)
]
word_ids tìm lại từ nguồn và first_flags nhận diện token con đầu tiên. word_labels chứa nhãn theo từ; label_to_id chuyển tên nhãn thành số. List comprehension duyệt các tương ứng này và trả về mã nhãn hoặc -100.
Để minh họa quy tắc, giả sử một từ mang nhãn B-TOTAL bị tách thành ba mảnh. Mảnh đầu nhận mã của B-TOTAL; hai mảnh còn lại nhận -100. Đây không phải dấu vết token hóa của AL-01-03, mà là ví dụ về cơ chế căn chỉnh.
O và -100 không thể hoán đổi. O là câu trả lời thật cần học: « từ này không thuộc trường nào ». -100 nghĩa là: « không tính mất mát phân loại ở vị trí này ». Các mảnh khác của từ vẫn là đầu vào văn bản của mạng, dù không có nhãn giám sát riêng. Tài liệu phân loại token trình bày nguyên tắc căn chỉnh này.
Quy tắc này tránh tăng trọng số của một từ trong giám sát chỉ vì tokenizer đã tách nó thành nhiều mảnh.
Nạp và huấn luyện LayoutLM
Ta không học toàn bộ mạng từ đầu. Ta nạp một bộ mã hóa đã tiền huấn luyện, rồi điều chỉnh theo nhãn của mình: đó là fine-tuning, hay tinh chỉnh một mô hình đã được huấn luyện. Trang mô hình LayoutLM giới thiệu mô hình khởi đầu.
Bộ mã hóa chuyển đầu vào thành các biểu diễn số. LayoutLM dùng kiến trúc Transformer để kết hợp văn bản và vị trí với ngữ cảnh của các token khác. Ý tưởng là một khoản tiền không chỉ được phân loại theo các chữ số mà còn theo những dấu hiệu xung quanh.
Tiếp đó, lớp phân loại, còn gọi là đầu phân loại, biến biểu diễn mỗi token thành mười ba điểm số, một cho mỗi nhãn BIO. Các điểm thô này là logit. Chúng không phải JSON cuối cùng hay giá trị hóa đơn: mã còn phải gom và giải mã chúng.
Hàm của dự án nạp bộ mã hóa và khởi tạo lớp phù hợp với các nhãn của ta.
def create_model(config):
names = label_names(config["fields"])
model, info = LayoutLMForTokenClassification.from_pretrained(
"assets/model",
num_labels=len(names),
id2label=dict(enumerate(names)),
label2id={name: i for i, name in enumerate(names)},
local_files_only=True,
use_safetensors=True,
output_loading_info=True,
)
assert all(key.startswith("classifier.") for key in info["missing_keys"]), info
assert not info["mismatched_keys"] and not info["error_msgs"], info
assert all(p.requires_grad for p in model.parameters())
return model.cuda(), info
num_labels đặt số lớp. id2label và label2id giữ tương ứng hai chiều giữa tên và mã của chúng. local_files_only=True bắt buộc dùng mô hình đã tải cục bộ, còn use_safetensors=True yêu cầu nạp trọng số ở định dạng Safetensors.
Thông tin nạp phục vụ các kiểm tra sau: tham số thiếu chỉ được chấp nhận ở lớp classifier mới, còn không nhất quán kích thước hay lỗi nạp bị từ chối. Vì vậy chương trình không âm thầm tiếp tục với một lần nạp không nhất quán.
Cuối cùng, requires_grad cho biết tham số có thể tham gia tính gradient; model.cuda() đặt mô hình trên GPU. Mọi tham số đều có thể huấn luyện: ta điều chỉnh cả bộ mã hóa, không chỉ lớp phân loại mới. Vòng huấn luyện sẽ thực hiện điều chỉnh này.
Để đọc các bảng, hãy hình dung một lô gồm hai cửa sổ 128 vị trí. Mã định danh có dạng [2, 128], các hộp [2, 128, 4] và logit [2, 128, 13]. Đây là kích thước minh họa: hai cửa sổ, 128 vị trí mỗi cửa sổ, mười ba điểm số mỗi vị trí. Đây không phải cấu hình bổ sung cần áp dụng cho dự án.
Một hàm mất mát để học, không chỉ điểm số để ghi nhận
Mô hình tiến bộ thế nào? Ta so sánh logit với nhãn mong đợi bằng hàm mất mát, ở đây là entropy chéo. Nó phạt, đặc biệt, việc ưu tiên nhãn sai. Sau đó huấn luyện tính gradient, cho biết thay đổi tham số ảnh hưởng thế nào đến mất mát, rồi bộ tối ưu điều chỉnh tham số. Hướng dẫn PyTorch về tối ưu hóa mô tả chu trình này.
Mã sau cộng lỗi ở các vị trí được giám sát và đếm các vị trí đó.
def supervised_sum(logits, labels):
count = int((labels != -100).sum())
if not count:
raise ValueError("No supervised tokens")
loss = torch.nn.functional.cross_entropy(
logits.reshape(-1, logits.shape[-1]),
labels.reshape(-1),
ignore_index=-100,
reduction="sum",
)
return loss, count
labels != -100 xác định các vị trí cần tính. Nếu không có vị trí nào được giám sát, hàm từ chối lô thay vì tiếp tục với tín hiệu học rỗng.
logits.reshape(-1, logits.shape[-1]) gom các vị trí thành hàng mà vẫn giữ mười ba lớp ở cột. labels.reshape(-1) căn đáp án mong đợi. ignore_index=-100 loại các đích bị bỏ qua và reduction="sum" cộng các mất mát. Tài liệu cross_entropy nêu các tham số này; hàm nhận trực tiếp logit, không chuyển trước thành xác suất trong đoạn trích.
Sau đó vòng lặp chia tổng này cho số token được giám sát của nhóm tích lũy: nhiều lô mà ta tích lũy đóng góp trước một lần cập nhật. Nhờ vậy ta tính trung bình theo vị trí giám sát, thay vì trung bình mỗi lô có trọng số tùy ý như nhau.
Một phép tính nhỏ minh họa khác biệt. Giả sử lô 10 vị trí có tổng mất mát 20, rồi lô 30 vị trí có tổng mất mát 30. Trung bình toàn cục là (20 + 30) / (10 + 30) = 1,25. Lấy trung bình của hai trung bình sẽ là (2 + 1) / 2 = 1,5. Ví dụ số này giải thích việc chuẩn hóa; nó không mô tả mất mát đo được trong lộ trình.
Xử lý trang dài mà không mất từ
Điều gì xảy ra nếu hóa đơn có quá nhiều văn bản? Mô hình chấp nhận cửa sổ 512 token, bao gồm token đặc biệt. Dự án dùng tối đa 510 token con văn bản mỗi cửa sổ, với phần chồng lấp 128. Do đó một phần văn bản xuất hiện trong hai cửa sổ kề nhau.
Giới hạn này áp dụng cho token, không phải từ: một từ tách thành ba mảnh chiếm ba vị trí. Chỉ cắt văn bản ở giới hạn có thể làm mất cuối hóa đơn, đúng nơi tổng tiền có thể nằm.
Cửa sổ hóa giải vấn đề độ dài nhưng tạo câu hỏi mới: giữ dự đoán nào khi cùng một từ xuất hiện trong nhiều cửa sổ?
Ta hợp nhất logit theo từ, tăng trọng số cho các vị trí xa biên. Cách chọn này ưu tiên vị trí được bao quanh bởi nhiều ngữ cảnh hơn trong cửa sổ; nó không chứng minh dự đoán của chúng luôn tốt hơn. Đây là toàn bộ hàm.
def merge_logits(windows, logits, word_count):
classes = logits[0].shape[-1]
total = torch.zeros(word_count, classes, dtype=torch.float64)
weights = torch.zeros(word_count, dtype=torch.float64)
contributions = torch.zeros(word_count, dtype=torch.long)
for window, values in zip(windows, logits):
length = len(window["input_ids"])
for token, (word, first) in enumerate(
zip(window["word_ids"], window["first_flags"])
):
if not first:
continue
weight = min(16, token, length - 1 - token)
assert weight > 0
total[word] += weight * values[token].double().cpu()
weights[word] += weight
contributions[word] += 1
if not bool((weights > 0).all()):
raise ValueError("A word was dropped by windowing")
return total / weights[:, None], contributions.tolist()
total chứa một vector điểm số theo mỗi từ; weights giữ tổng trọng số và contributions đếm số đóng góp nhận được. Vòng lặp chỉ giữ token con đầu tiên, như khi giám sát.
Biến weight, tính bằng min(16, token, length - 1 - token), đo khoảng cách đến hai biên và giới hạn trọng số ở 16. Vị trí gần biên có trọng số thấp hơn vị trí đủ sâu bên trong. assert weight > 0 kiểm tra đóng góp được giữ có trọng số dương.
Ta cộng các điểm số có trọng số, rồi chia mỗi vector cho tổng trọng số. Trong weights[:, None], chiều bổ sung cho phép áp cùng một số chia lên mọi lớp của một từ. Giá trị được tích lũy trên CPU bằng float64, như .double().cpu() và việc khởi tạo các bảng cho thấy.
Đây là ví dụ tính toán, không phải đầu ra quan sát: một từ nhận logit 2 cho cùng một lớp với trọng số 4, rồi logit 6 với trọng số 12. Điểm hợp nhất là (4 × 2 + 12 × 6) / (4 + 12) = 5. Phép này lặp cho từng lớp. Kết quả vẫn là logit, không phải xác suất.
Cuối cùng, kiểm tra weights > 0 yêu cầu mỗi từ có một đóng góp. Nếu một từ biến mất khi chia cửa sổ, hàm ném lỗi thay vì âm thầm tạo trích xuất không đầy đủ. contributions cho biết có bao nhiêu cửa sổ tham gia dự đoán mỗi từ.
Huấn luyện, so sánh và tiếp tục
Ta sẽ huấn luyện hai biến thể: layout, dùng hình chữ nhật thật, và neutral, dùng hình chữ nhật bằng không trong cả huấn luyện và suy luận. « Bằng không » ở đây là tọa độ bằng không, không phải từ bị xóa.
Biến thể trung tính cho phép đặt câu hỏi chính xác: mô hình có học được nhiệm vụ này mà không có khác biệt vị trí giữa các hộp không? Văn bản vẫn hiện diện. Nó cũng không xóa thứ tự token trong chuỗi. Ta bỏ một tín hiệu hình học biến đổi, không phải toàn bộ thông tin tài liệu.
Ta cũng kiểm tra việc tiếp tục huấn luyện. Các lệnh sau huấn luyện mô hình chính bị ngắt, biến thể trung tính, một tham chiếu liên tục, rồi tiếp tục mô hình bị ngắt trong tiến trình khác.
docker compose run --rm tutorial python -m pdflayout.cli train --config configs/minimum.yaml
docker compose run --rm tutorial python -m pdflayout.cli train-neutral --config configs/minimum.yaml
docker compose run --rm tutorial python -m pdflayout.cli train-continuous --config configs/minimum.yaml
docker compose run --rm tutorial python -m pdflayout.cli train-resume --config configs/minimum.yaml
Một epoch là một lượt qua các ví dụ huấn luyện. Lộ trình chính có năm epoch, ngắt sau hai để kiểm tra việc tiếp tục. Tham chiếu liên tục hoàn thành lộ trình không ngắt; việc tiếp tục nạp trạng thái bị ngắt để chạy ba epoch còn lại.
Vì sao không chỉ kiểm tra điểm cuối gần nhau? Vì hai mô hình khác nhau có thể đạt cùng điểm trên kho dữ liệu nhỏ. Lặp lại cùng thí nghiệm đòi hỏi khôi phục trạng thái tính toán, không chỉ đạt hiệu năng tương tự.
Phép so sánh sau kiểm tra, đặc biệt, tham số, bộ tối ưu và các trạng thái ngẫu nhiên.
docker compose run --rm tutorial python -m pdflayout.cli verify-resume --config configs/minimum.yaml
Kết quả của lộ trình đã xác nhận: tiếp tục chính xác, với sai khác tối đa bằng không giữa các tham số so sánh. Điều này kiểm soát quy trình và môi trường này. Đừng giả định cùng một nhận dạng số sau khi đổi phần cứng hay phụ thuộc.
Dự đoán mà không xem nhãn
Ta đến với suy luận: dùng mô hình đã huấn luyện để tạo dự đoán mà không điều chỉnh tham số bằng đáp án mong đợi.
Dịch vụ suy luận không mount thư mục nhãn. Nó nạp từ, hộp và trọng số, hợp nhất cửa sổ rồi giải mã nhãn. Tình huống phải giống một tài liệu ứng dụng của bạn nhận được: văn bản có sẵn, bản sửa đúng thì không.
docker compose run --rm inference python -m pdflayout.cli predict --config configs/minimum.yaml
Mở outputs/example.json. Với AL-01-03, biến thể layout tìm TOTAL = 250.16 tại từ 62, cùng hộp của nó trên trang. Nó cũng tìm tổng phụ và thuế mong đợi. Hãy theo các liên kết này đến các từ: tệp không chỉ thông báo số mà còn cho biết lần xuất hiện nào được chọn.
Từ nhãn đến trường
Bộ giải mã gom chuỗi BIO thành thực thể. Nếu một I không nhất quán, nó sửa thành đầu thực thể và đếm lần sửa. Chẳng hạn chuỗi minh họa O, I-TOTAL có thể thành O, B-TOTAL: tiếp diễn không có mở đầu thành một mở đầu mới. Sửa dạng chuỗi không chứng minh từ đã chọn là đúng.
Với mỗi trường, chương trình trả một giá trị khi có một ứng viên duy nhất. Không có ứng viên hoặc có nhiều ứng viên dẫn đến giá trị trống, nhưng vẫn giữ các mơ hồ. Vì vậy nó không tùy tiện chọn giữa hai tổng tiền có thể có; ứng dụng có thể yêu cầu con người kiểm tra.
Hãy chú ý ý nghĩa đầu ra trống: nó có thể là trường vắng mặt, nhưng cũng có thể là thất bại phát hiện hoặc mơ hồ. Nguồn gốc và ứng viên giúp phân tích kết quả; chúng không là bảo đảm tính đúng hay bảo đảm kế toán.
Hiểu chỉ số trước kết quả
Một chỉ số là phép đo hành vi của chương trình. Ta dùng nhiều chỉ số vì chúng không trả lời cùng một câu hỏi.
Ta đã tìm đúng thực thể chưa?
Một thực thể đúng khi kiểu và mã định danh từ của nó trùng khớp chính xác với tham chiếu. Trên một trang có cùng khoản tiền xuất hiện hai lần, lấy nhầm lần xuất hiện vẫn là lỗi, dù chuỗi ký tự giống nhau.
Độ chính xác đo tỷ lệ thực thể dự đoán là đúng. Độ bao phủ đo tỷ lệ thực thể mong đợi đã được tìm thấy.
Lấy ví dụ: ta dự đoán bốn thực thể, ba đúng, và có năm thực thể được mong đợi. Độ chính xác là 3 / 4 = 0,75, độ bao phủ là 3 / 5 = 0,60. F1 kết hợp hai giá trị: 2 × précision × rappel / (précision + rappel). Trong ví dụ này nó xấp xỉ 0,667. Đây không phải điểm của hướng dẫn, mà là minh họa công thức.
Micro-F1 gom số đếm của mọi thực thể trước phép tính. Macro-F1 tính F1 của từng kiểu trường rồi lấy trung bình. Cách đọc thứ hai giúp tránh che khuất một trường khó bởi các trường khác.
Hóa đơn có hoàn toàn chính xác không?
Độ chính xác theo tài liệu yêu cầu mọi trường đều đúng trên một hóa đơn. Trong ví dụ mười hóa đơn, chín hóa đơn hoàn toàn đúng cho 0,9. Nhiều trường riêng lẻ có thể đúng trong khi ít tài liệu hoàn toàn thành công: chỉ một trường sai là đủ làm tài liệu thất bại.
Tỷ lệ lỗi khoản tiền so sánh các giá trị số. Càng thấp càng tốt. Kiểm tra này không thay thế kiểm tra thực thể: tìm đúng số ở sai từ có thể giữ giá trị số nhưng mất nguồn gốc đúng.
Nguồn gốc và sự kiềm chế có phù hợp không?
Nguồn gốc hợp lệ nghĩa là tham chiếu trỏ đúng đến từ và hộp trên trang. Nó không bảo đảm các từ đó có vai trò đúng. Tham chiếu kỹ thuật hợp lệ có thể chỉ tổng phụ trong khi ta tìm tổng tiền.
Sự kiềm chế trên hóa đơn không có tổng tiền kiểm tra chương trình không gán cho chúng một tổng tiền bịa ra. Nhưng hệ thống không bao giờ trả gì có thể đạt riêng tiêu chí đó trong khi bỏ lỡ mọi trường hiện diện. Hãy giữ giới hạn này trong đầu trước khi đọc kết quả.
Hãy chạy đánh giá; nó có thể truy cập nhãn tách biệt với suy luận.
docker compose run --rm tutorial python -m pdflayout.cli evaluate --config configs/minimum.yaml
Kết quả lộ trình r002
Các kết quả sau áp dụng cho 96 trang kiểm thử, với 568 thực thể mong đợi. Chúng đến từ outputs/evaluation.json. Số lượng này phù hợp với sáu trường mỗi trang và tám tổng tiền vắng mặt: 96 × 6 − 8 = 568.
Trên thiết bị di động, hãy cuộn ngang bảng.
| Phương pháp | Micro-F1 | Macro-F1 theo trường | Độ chính xác tài liệu | Lỗi khoản tiền |
|---|---|---|---|---|
| Quy tắc đơn giản | 1,00 | 1,00 | 1,00 | 0,00 |
| LayoutLM, hình chữ nhật thật | 1,00 | 1,00 | 1,00 | 0,00 |
| LayoutLM, huấn luyện trung tính | 1,00 | 1,00 | 1,00 | 0,00 |
| Chỉ che hình chữ nhật khi suy luận | 0,00 | 0,00 | 0,00 | 1,00 |
Hai cách rất khác nhau để « bỏ vị trí »
Ba phương pháp đầu đạt cùng điểm. Ngược lại, dòng cuối là mô hình huấn luyện với hình chữ nhật thật, rồi thay bằng số không chỉ lúc dự đoán.
Đó không phải biến thể trung tính. neutral đã học ngay từ đầu với biểu diễn hằng này. Còn mô hình bị che đột ngột nhận vị trí khác với những vị trí nó đã học để làm việc.
So sánh với ứng dụng web có thể hữu ích: dự kiến ngay từ thiết kế rằng một trường luôn hằng không giống thay giá trị của nó thành không sau khi đã xây dựng các xử lý dựa vào nó. Với mạng, khác biệt nằm ở các ví dụ huấn luyện đã dẫn dắt tham số.
Che muộn đo độ nhạy với thay đổi đầu vào; nó không chứng minh nhiệm vụ không thể học nếu không có hình học. Thành công của neutral cho thấy điều ngược lại trên kho dữ liệu này.
Các điểm số này mô tả kho dữ liệu tổng hợp của hướng dẫn. Chúng không chứng lập lợi thế của mô hình hình học so với quy tắc đơn giản.
Ba phương pháp đầu cũng thành công ở các nhóm con gồm tám trang dài, tám trang có khoản tiền lặp lại và tám trang không có tổng tiền.
Biến thể bị che không dự đoán thực thể nào. Vì vậy sự kiềm chế của nó trên tổng tiền vắng mặt bằng 1, nhưng giá trị riêng lẻ này che thất bại trên các trường hiện diện. Tương tự, tỷ lệ nguồn gốc hợp lệ bằng 1 có thể đi cùng đầu ra trống: không tạo tham chiếu không hợp lệ nào, nhưng cũng không có trích xuất hữu ích nào.
Vì vậy kết luận không phải « hình học không bao giờ hữu ích ». Nó chính xác hơn: trên kho dữ liệu này, quy tắc đơn giản và mô hình trung tính đã đủ đạt cùng điểm với LayoutLM dùng hình chữ nhật thật. Ta đã hiểu và đo cơ chế, chưa chứng minh ưu thế của nó trên mọi hóa đơn.
Kiểm tra những gì phải thất bại
Một kiểm thử âm đưa vào chương trình tình huống phải bị từ chối. Các kiểm thử chạy lúc đầu kiểm tra, đặc biệt, hộp không hợp lệ, từ bị mất do chia cửa sổ và đầu vào không nhất quán.
Lợi ích rất cụ thể: kết quả có vẻ hợp lý rất khó chẩn đoán nếu một phần tài liệu đã biến mất trước dự đoán. Tốt hơn là báo đầu vào không hợp lệ thay vì che vấn đề bằng đầu ra trấn an.
Hãy xem kiểm thử để biết trường hợp chính xác trước khi điều chỉnh tài liệu của bạn. Vượt qua các trường hợp được bao phủ không có nghĩa mọi tình huống có thể đã được khám phá.
Những gì ta đã học
Ta đi từ một khoản tiền trên trang đến một trường liên kết với nguồn của nó. Văn bản cung cấp dấu hiệu từ vựng; hình chữ nhật cho vị trí. LayoutLM kết hợp biểu diễn của chúng, rồi lớp phân loại đề xuất vai trò cho token.
Căn chỉnh cho phép quay từ token con về từ. Chia cửa sổ xử lý trang dài, còn hợp nhất tránh nhiều quyết định độc lập cho cùng một lần xuất hiện. Sau đó bộ giải mã lắp ghép thực thể và giữ lại các mơ hồ.
Cuối cùng, khả năng truy vết, độ đúng và lợi ích của việc học là ba câu hỏi khác nhau. Hộp hợp lệ không đảm bảo trường đúng; điểm hoàn hảo trên kho dữ liệu tổng hợp không bảo đảm dịch vụ đáng tin; mạng phức tạp không nhất thiết đem lại lợi ích so với quy tắc đơn giản. Kết quả lộ trình minh họa các khác biệt này.
Bài tập: dự đoán trước khi di chuyển
Hãy trở lại vấn đề tổng tiền và tổng phụ. Ta sẽ đổi vị trí các khối TOTAL và SUBTOTAL mà không đổi giá trị. Trước khi chạy, hãy viết giả thuyết: dấu hiệu nào vẫn có sẵn cho quy tắc, mô hình hình học và mô hình trung tính? Việc di chuyển có nhất thiết làm đổi dự đoán không? Vì sao?
Dự án cung cấp thay đổi này trong configs/exercise.yaml. Hãy so sánh hai trang thí nghiệm và ghi lại những trường bạn cho là sẽ đổi. Sau đó chạy biến thể đã chuẩn bị.
docker compose run --rm tutorial python -m pdflayout.cli exercise --config configs/exercise.yaml
So sánh dự đoán của bạn với kết quả bài tập. Tham chiếu đã xác nhận tìm mười hai thực thể bằng quy tắc, layout và neutral; biến thể bị che không tìm được thực thể nào. Lời giải chi tiết nằm trong lesson/SOLUTIONS.md.
Vì sao di chuyển không đủ làm các phương pháp này khó khăn? Các nhãn ở đây vẫn đủ phân biệt. Đổi vị trí một khối không xóa mọi dấu hiệu khác. Vì thế chỉ thí nghiệm này không chứng minh ích lợi của hình học.
Để tự thí nghiệm, tiếp theo hãy chọn một thay đổi duy nhất trong bộ sinh, chẳng hạn làm một nhãn trở nên mơ hồ. Dự đoán tác động lên từng phương pháp trước khi tạo lại dữ liệu và chạy lại lộ trình. Giữ riêng kết quả của bạn với kết quả tham chiếu.
Thí nghiệm mới này không có kết quả công bố sẵn. Chính điều đáng quan tâm là đo điều thay đổi, rồi giải thích lỗi hay thành công, kể cả khi chúng trái với trực giác của bạn.
Quan sát hình và kiểm tra checkpoint
Tạo hình từ kết quả riêng của bạn, rồi kiểm tra các tệp mô hình mong đợi.
docker compose run --rm tutorial python lesson/figures/plot.py
docker compose run --rm tutorial python scripts/check_checkpoints.py
Các hình trong thư mục outputs/ cho phép xem hóa đơn, token con và kết quả. Hãy kiểm tra tương ứng giữa trường, từ và hộp của nó trước khi hài lòng với bảng tổng hợp.
Với AL-01-03, kiểm tra trực quan gồm việc tìm từ 62 mang 250.16, rồi xác minh trường TOTAL thực sự trỏ đến lần xuất hiện đó. Một giá trị trung bình không thay thế việc đọc một ví dụ.
Khi có vấn đề
Hãy bắt đầu bằng việc xác định bước thất bại: truy cập GPU, tải tài nguyên, chuẩn bị từ, tách hay dự đoán. Sửa mô hình không giải quyết được hộp sai hướng ngay từ trước khi vào mạng.
Trên thiết bị di động, hãy cuộn ngang bảng.
| Triệu chứng | Kiểm tra và khắc phục |
|---|---|
| GPU vắng mặt trong Docker | So sánh preflight trên máy chủ với kiểm tra container. Sửa tích hợp NVIDIA Container Toolkit trước huấn luyện. |
| Mô hình thiếu hoặc tải bị gián đoạn | Xem lỗi của scripts/acquire.py, kiểm tra dung lượng trống và chạy lại quy trình tải đã định; giữ mô hình và bản sửa đổi bị khóa. |
| Hộp bị từ chối | Kiểm tra kích thước, cắt và xoay của trang chuẩn. Hộp ngoài trang không được coi là hợp lệ bằng cách che lỗi. |
| Mất từ khi tách | Kiểm tra mã định danh và token con đầu tiên của các cửa sổ. Kiểm soát phải vẫn hoạt động. |
| Đầu vào không có văn bản khai thác được | Kiểm tra PDF chứa văn bản gốc. Bản quét cần thêm bước OCR, nằm ngoài lộ trình đã xác nhận ở đây. |
Dừng và dọn dẹp
Trước khi dọn, hãy giữ các kết quả và hình bạn muốn nghiên cứu. Lệnh đầu dừng dự án, script chỉ xóa kết quả trong outputs/, rồi lệnh dừng cuối kết thúc lộ trình.
docker compose down
docker compose run --rm tutorial python scripts/clean.py --outputs-only
docker compose down
Tài nguyên mô hình, checkpoint trong assets/ và dữ liệu đã tạo được giữ lại. Không cần xóa Docker toàn cục. Vì vậy việc dọn dẹp không có nghĩa phải tải lại hoặc tạo lại trọng số hay kho dữ liệu.
Từ hướng dẫn đến ứng dụng thực
Công việc đầu tiên không phải thêm endpoint cho script này: hãy kiểm tra nó hoạt động với tài liệu của ứng dụng bạn. Hóa đơn nghiệp vụ đòi hỏi tiếp nhận được kiểm soát, đa dạng ngôn ngữ và định dạng, chiến lược OCR cho bản quét và xử lý nhiều trang.
Hãy lập một kho dữ liệu có nhãn đại diện, tách theo nhà cung cấp hoặc khuôn, rồi phân tích lỗi theo trường và kiểu tài liệu. Một điểm toàn cục có thể che khó khăn trên ngày tháng của một nhà cung cấp hoặc tổng tiền của một định dạng. Những ví dụ này mô tả các phân tích cần chuẩn bị, không phải lỗi đã đo ở đây.
Tùy nhu cầu, thêm xác thực, quyền tài liệu, bảo vệ dữ liệu, giới hạn kích thước, hàng đợi xử lý, nhật ký và giám sát. Xác định trường hợp kiềm chế và xác nhận của con người cho giá trị mơ hồ. Nguồn gốc được giữ lại có thể hỗ trợ việc đọc lại này, nhưng giao diện và quy trình nghiệp vụ vẫn phải được xây dựng.
Hãy phiên bản hóa mô hình, cấu hình và dữ liệu đánh giá để phát hiện hồi quy. Công cụ học tập này không xác lập bất kỳ sự tuân thủ kế toán nào và tự nó không phải dịch vụ sản xuất.
Đi xa hơn
Hãy bắt đầu bằng kiểm thử hóa đơn có khuôn thực sự khác nhau với nhãn đã xác minh. So sánh lỗi của quy tắc và mô hình trên nhãn cố ý mơ hồ: đó là cách tìm tình huống việc học có thể đem lại lợi ích, rồi đo nó.
Một mở rộng khác là thêm chuỗi OCR để lấy từ và hộp từ bản quét. Nó có các lỗi riêng; cần đánh giá chuỗi mới này, không gán cho nó điểm của lộ trình trên văn bản gốc.
Cuối cùng, bạn có thể xây giao diện hiển thị từng giá trị với vùng nguồn và cho phép sửa thủ công. Phần nối dài này kết nối trực tiếp cơ chế của hướng dẫn với nhu cầu ứng dụng web: để người dùng kiểm tra một đề xuất thay vì đưa họ một con số không rõ nguồn.
Nguồn để tìm hiểu thêm
README, mã và bài học trong ZIP là lộ trình có thể tái lập được dùng ở đây. Các điểm công bố là của lộ trình r002; các ví dụ số được nêu trong bài chỉ để giải thích tính toán.
Tài liệu LayoutLM của Transformers, phiên bản 4.56.2 mô tả đầu vào văn bản và hình học cùng phân loại token. Trang mô hình LayoutLM giới thiệu mô hình tiền huấn luyện.
Để tìm hiểu các bước trung gian, xem tài liệu pdfplumber, khóa học về WordPiece và hướng dẫn phân loại token. Giải thích về học tập cũng dựa vào tài liệu entropy chéo PyTorch và hướng dẫn tối ưu tham số.
Các tài liệu bổ sung này không thay thế các tệp đã khóa trong ZIP. Tài liệu trực tuyến có thể thay đổi độc lập với môi trường đã xác nhận; hãy giữ phiên bản và lệnh của dự án để tái lập nó.