Trao đổi về dự án
Trí Tuệ Nhân Tạo

So sánh hai PDF phân trang lại bằng embedding và căn chỉnh động

Xây dựng công cụ so sánh điều khoản tách biệt thay đổi bố cục, căn chỉnh toàn cục, số đã sửa và nguồn gốc trong hai PDF.

Extraire les clauses → Aligner globalement → Vérifier les nombres. Comparer deux PDF

Vấn đề: một điều khoản bị chuyển chỗ và một thời hạn bị sửa đổi

Hãy hình dung một chức năng trong back-office: người dùng tải lên hai phiên bản của một hướng dẫn và muốn biết điều gì đã thay đổi. Bạn có thể trích xuất văn bản, chạy diff và hiển thị các dòng được thêm hoặc xóa. Nguyên tắc này giống việc so sánh tệp trong Git.

Nhưng đây là hai phiên bản của hướng dẫn giả định « Nova Service Policy ». Phiên bản thứ hai dùng phông chữ và cách phân trang khác. Một đoạn nay chiếm bốn dòng thay vì ba; đoạn sau chuyển sang trang khác. Văn bản chưa chắc đã thay đổi, nhưng các dòng và tọa độ của nó không còn khớp.

Một sửa đổi thực sự xuất hiện trong cách trình bày mới này: « Payment is due within 30 days » trở thành « Payment is due within 45 days ». Nói cách khác, thời hạn đổi từ 30 thành 45 ngày. Diff theo dòng có thể làm thay đổi này chìm trong các ngắt dòng. Ngược lại, mô hình tương đồng có thể thấy hai câu rất gần nhau: chúng vẫn nói cùng chủ đề, với gần như cùng từ ngữ.

Vì thế có hai câu hỏi khác nhau: cần so sánh những đoạn nào, rồi chúng khác nhau ở điểm nào? Tìm được một đoạn gần giống chưa đủ để tuyên bố nó không thay đổi.

Hướng dẫn này xây dựng một công cụ so sánh cục bộ cho PDF gốc, tức có văn bản khai thác được chứ không chỉ là ảnh quét. Lộ trình là: glyph → điều khoản → vector tương đồng → căn chỉnh toàn cục → kiểm tra tường minh → các đoạn cần đọc lại. Từ vựng sẽ được giải thích dần; bạn có thể bắt đầu dự án với kiến thức cơ bản về Python, bảng dữ liệu và Docker mà chưa từng huấn luyện mô hình.

Kết quả là danh sách các ứng viên cần đọc lại kèm nguồn gốc từ hai PDF. Đây không phải ý kiến pháp lý, cũng không đảm bảo rằng một cách diễn đạt lại giữ nguyên nghĩa. Trọng tâm là học cách để mô hình và các quy tắc phối hợp, thay vì yêu cầu mô hình tự quyết mọi thứ.

Hai phiên bản PDF, các điều khoản đã trích xuất, một ma trận chi phí và kiểm tra số được liên kết với các vùng nguồn.

Tải xuống dự án hoàn chỉnh.

Dữ liệu thực sự cho thấy điều gì

Ba cách thay đổi cùng một hướng dẫn

Corpus Nova gồm 40 họ và ba cặp cho mỗi họ, tức 120 cặp PDF gốc thực sự. Các tệp này được tạo cho thí nghiệm: « PDF thực sự » nghĩa là chúng ta thực sự xử lý định dạng này, không phải tài liệu đến từ khách hàng.

Trong mỗi họ, một cặp chỉ thay đổi cách trình bày. Cặp khác thay đổi thời hạn và dấu câu. Cặp thứ ba chèn một điều khoản, xóa một điều khoản và diễn đạt lại một liên hệ. Cấu trúc này giúp biết trước những khác biệt nào được kỳ vọng trước khi chạy công cụ so sánh.

Các họ 0 đến 9 tạo thành tập phát triển, tức 30 cặp; 30 họ còn lại tạo thành tập kiểm thử, tức 90 cặp. Không họ nào xuất hiện ở cả hai tập. Tập phát triển dùng để làm việc trên phương pháp; tập kiểm thử dùng để đo hành vi đã chọn mà không sửa nó sau khi xem kết quả.

Ở đây chúng ta không tách huấn luyện nơ-ron khỏi kiểm thử: mô hình tiền huấn luyện vẫn được đóng băng. Dù vậy, sự tách biệt này tránh thiết kế các quy tắc trên những ví dụ dùng để công bố kết quả. Ngữ pháp sinh vẫn chung cho các họ; vì vậy giao thức này không tự động đại diện cho sự đa dạng của tài liệu của bạn.

Tái tạo điều khoản, không chỉ lấy một chuỗi

Một glyph là hình dạng ký tự được vẽ trong PDF, đi kèm vị trí. Chương trình bắt đầu từ các yếu tố nhìn thấy này. Bộ phân đoạn của nó, mã chia tài liệu thành các đoạn, nhóm các dòng theo vị trí và nhận diện tiêu đề in đậm để tái tạo các điều khoản.

Ở đây, điều khoản là đơn vị đoạn mà chúng ta sẽ so sánh. Nó không phải một loại pháp lý được chương trình công nhận. Trước hết, bước trích xuất phải nhóm đúng các yếu tố của tài liệu.

Mỗi điều khoản giữ danh sách vùng nguồn: tài liệu, dấu vân tay SHA-256, chỉ mục trang, hộp và định danh glyph. Hộp đơn giản là hình chữ nhật định vị đoạn trên trang. Nếu một điều khoản bắt đầu ở trang này và kết thúc ở trang sau, nó giữ nhiều hộp: không thể tóm tắt trung thực nguồn gốc đó bằng chỉ một hình chữ nhật.

Với một lập trình viên web, nguồn gốc này giống liên kết giữa kết quả hiển thị và các bản ghi đã tạo ra nó. Nó giúp quay về đúng tài liệu và vùng đúng. Dấu vân tay SHA-256, tính từ tệp, giúp nhận diện phiên bản đã dùng; nó không chứng nhận tính đúng đắn của sự ghép nối được đề xuất.

Chuẩn hóa mà không mất đường về bản gốc

Để so sánh văn bản, dự án áp dụng NFKC, chuyển sang chữ thường và gộp khoảng trắng. NFKC là một dạng chuẩn hóa Unicode: đặc biệt, nó đưa một số cách biểu diễn ký tự khác nhau lại gần nhau. Tài liệu Python về unicodedata.normalize mô tả thao tác này.

Hãy lấy một ví dụ sư phạm, độc lập với các phép đo Nova. Chuỗi office có thể chứa một ký tự Unicode duy nhất cho ligature ffi. Sau NFKC, nó thành office: ba chữ f, f và i được tách ra. Đặc tả Unicode trình bày phép biến đổi này và những khác biệt mà chuẩn hóa tương thích có thể xóa đi.

Nếu glyph của ligature trong ví dụ có định danh G17, ba ký tự đã chuẩn hóa phải có thể quay về cùng glyph đó. Dự án chính xác giữ một bản đồ từ ký tự chuẩn hóa tới các glyph gốc. Nó không giả định rằng một ký tự chuẩn hóa luôn tương ứng với duy nhất một ký tự nguồn riêng biệt.

Tương tự, thống nhất Payment, PAYMENT và khoảng trắng giúp so sánh dễ hơn nhưng làm mất một số khác biệt về hình thức. Chỉ riêng văn bản chuẩn hóa không cho phép tái dựng bản gốc. Đó là lý do giữ cả biểu diễn làm việc lẫn nguồn gốc của nó. Lựa chọn chuẩn hóa này thuộc giao thức; cần xem lại tính phù hợp trước khi so sánh loại tài liệu khác.

Những gì trích xuất không sửa được

Đầu trang và chân trang bị loại trừ theo vùng khai báo: vị trí của chúng được cung cấp, không do mô hình tự phát hiện. Các từ bị ngắt ở cuối dòng không được tự sửa. Vì vậy, một từ bị cắt cuối dòng có thể cần xử lý thêm ở corpus khác.

Một lần trích xuất kém không trở nên đúng chỉ vì mô hình vector tốt. Nếu thiếu một đoạn hoặc hai điều khoản bị gộp nhầm, trước hết hãy kiểm tra bước này. Công cụ so sánh làm việc trên các điều khoản nó nhận được, không phải phiên bản lý tưởng của tài liệu.

Embedding: gần không có nghĩa là giống hệt

Biến một điều khoản thành các con số

Một embedding là vector, tức danh sách số biểu diễn văn bản trong không gian đã học. Mô hình của POC, hay proof of concept, là all-MiniLM-L6-v2. Thẻ chính thức của nó mô tả các vector 384 chiều và cách tạo chúng bằng pooling.

Ở đây mô hình đóng vai bộ mã hóa: nó biến đầu vào thành biểu diễn số. Nó không viết phân tích về các thay đổi. Tham số của nó vẫn đóng băng, tức không đổi trong lộ trình. Chúng ta thực hiện suy luận, dùng mô hình đã huấn luyện, chứ không fine-tuning trên Nova.

Tính toán dùng FP32 trên CPU: các số thực 32 bit do bộ xử lý xử lý. Hướng dẫn này không yêu cầu cũng không chạy GPU.

384 giá trị không tương ứng với 384 trường nghiệp vụ có tên. Chẳng hạn, chúng ta không có ô « thời hạn » nơi mô hình sẽ đặt đúng số ngày. Chính vì vậy sau đó chúng ta sẽ thêm kiểm tra số tường minh.

Token, cửa sổ và trung bình có trọng số

Một tokenizer chia văn bản thành đơn vị mà mô hình xử lý được. Ở đây là WordPiece: token có thể là từ hoặc mảnh từ, không nhất thiết là ký tự hay cả từ. Tài liệu tokenizers của Transformers giải thích cách cắt này.

Pooling gom các biểu diễn token để tạo vector cho một đoạn. Cần phân biệt thao tác này với phép trung bình thứ hai mà dự án dùng cho các điều khoản dài.

Thực tế tokenizer chia một điều khoản dài thành các cửa sổ 254 wordpiece nội dung. Lộ trình mã hóa mọi cửa sổ, lấy trung bình vector của chúng theo số token rồi chuẩn hóa vector thu được. Một điều khoản dài đi qua quy trình này trong một trên năm họ. Vì thế chúng ta không thay điều khoản dài bằng riêng phần đầu của nó.

Để hiểu trọng số, lấy hai cửa sổ giả định 254 và 46 token. Với vector v1 và v2, trung bình là (254 × v1 + 46 × v2) / 300, không phải (v1 + v2) / 2. Cửa sổ cuối nhỏ không có trọng lượng ngang cửa sổ lớn chỉ vì nó là một cửa sổ bổ sung.

Giảm vector xuống hai chiều để tính tay, giả sử v1 = (1 ; 0) và v2 = (0 ; 1). Trung bình xấp xỉ (0,8467 ; 0,1533). Chuẩn hóa theo độ dài cho xấp xỉ (0,9840 ; 0,1782). Các giá trị này chỉ giải thích phép tính; chúng không phải đầu ra của MiniLM.

Mã hóa mọi cửa sổ tránh bỏ qua một cửa sổ, nhưng không tạo một ngữ cảnh duy nhất nơi mỗi token tương tác với toàn bộ điều khoản. Phép trung bình cô đọng biểu diễn của chúng; nó không tự giữ lại mọi chi tiết hay quan hệ.

So sánh hướng, không chứng nhận tương đương

Độ tương đồng cosine so sánh hướng của hai vector. Với các vector khác không u và v, nó bằng tích_vô_hướng(u, v) / (độ_dài(u) × độ_dài(v)). Giá trị gần 1 biểu thị các hướng gần nhau. Khi hai vector được chuẩn hóa theo độ dài, tích vô hướng là đủ, như tài liệu Sentence Transformers giải thích.

Một ví dụ hai chiều cho thấy khác biệt với bình đẳng: (1 ; 0) và (2 ; 0) có cùng hướng, nên cosine bằng 1, mà không phải hai danh sách số giống hệt. Với văn bản, điểm cao cũng không phải chứng nhận tương đương.

Với n điều khoản trước và m điều khoản sau, chương trình tạo ma trận n × m: bảng có một hàng cho mỗi điều khoản cũ và một cột cho mỗi điều khoản mới. Ô [i, j] chứa độ gần của hai điều khoản này.

Quay lại 30 và 45 ngày. Dù độ tương đồng là 0,99, con số đó không có nghĩa « 99 % khả năng không có gì thay đổi ». Nó giúp nhận ra hai đoạn tương ứng. Khác biệt thời hạn phải còn thấy được ở bước tiếp theo. Gần ở đây nghĩa là “ứng viên tốt để so sánh”, không phải “có thể hoán đổi”.

Xây dựng đường đi toàn cục

Vì sao không lấy láng giềng tốt nhất cho mỗi điều khoản?

Giả sử hai điều khoản cũ đều giống cùng một điều khoản mới. Nếu mỗi điều khoản tự chọn láng giềng tốt nhất, công cụ có thể dùng lại đoạn ấy hai lần và bỏ qua đoạn khác.

Hàm align, trong src/pdfdiffai/align.py, thay vào đó chọn căn chỉnh toàn cục. Mỗi đoạn phải xuất hiện đúng một lần: trong một ghép cặp, hoặc riêng lẻ như chèn hay xóa. Ghép cặp là liên kết điều khoản của phiên bản trước với điều khoản của phiên bản sau; không có nghĩa tuyên bố chúng giống hệt.

Đường đi cũng đơn điệu: nó tiến theo thứ tự của hai tài liệu, không quay lại. Nếu điều khoản cũ A đứng trước B, các tương ứng của chúng không thể giao nhau trong phiên bản mới.

Ràng buộc này chấp nhận một điều khoản bị đẩy sang trang khác miễn là thứ tự các phần nhìn chung được giữ. Nó không cho phép hoán đổi tự do hai phần nguyên vẹn. Chúng ta sẽ đo giới hạn này trong bài tập.

Biến độ gần thành chi phí

Chúng ta tìm đường đi có chi phí tối thiểu. Chi phí thấp khiến một lựa chọn hấp dẫn; nó không phải xác suất sai. Chi phí ghép cặp kết hợp ba hạng, được cố định trước suy luận:

0,7 × (1 − cosine) + phạt tiêu đề + 0,1 × khoảng cách vị trí tương đối.

Phạt tiêu đề là 0,2 nếu các token tiêu đề khác nhau, nếu không là 0. Một lần chèn và một lần xóa đều có chi phí 0,45. Các giá trị này là lựa chọn của giao thức, không phải tham số học trong lộ trình.

Ví dụ, với cosine giả định 0,98, token tiêu đề khác nhau và khoảng cách tương đối 0,10, chi phí là 0,7 × 0,02 + 0,2 + 0,1 × 0,10 = 0,224. Độ gần văn bản mạnh, nhưng các dấu hiệu khác vẫn đóng góp.

Xóa một điều khoản rồi chèn điều khoản khác có chi phí 0,45 + 0,45 = 0,90. So sánh các chi phí này giúp hiểu các lựa chọn. Tuy nhiên đây không phải ngưỡng quyết định độc lập cho từng cặp: căn chỉnh còn phải tương thích với các điều khoản khác.

Lập trình động: ghi nhớ những phần đầu đường đi tốt nhất

Lập trình động tránh tính lại mọi đường đi có thể. Nó giữ lời giải tốt nhất cho mỗi cặp phần đầu tài liệu, rồi dựa vào các kết quả này để tiến tiếp.

distance[i, j] biểu diễn chi phí tối thiểu để xử lý i điều khoản đầu của phiên bản trước và j điều khoản đầu của phiên bản sau. Do đó ma trận có n + 1 hàng và m + 1 cột. Số 0 cho phép biểu diễn đầu tài liệu vẫn trống.

Ô distance[0, 0] bằng 0. Trên hàng đầu chỉ có thể chèn; trên cột đầu chỉ có thể xóa. Với chi phí 0,45, các biên vì thế tích lũy 0, 0,45, 0,90, v.v.

Đây là đoạn trung tâm của dự án. Nó can thiệp trong phép tính, sau khi chuẩn bị ma trận và chỉ mục; đây không phải chương trình tự chạy riêng lẻ.

value, rank, operation = min(
    (distance[i - 1, j - 1] + cost[i - 1, j - 1], 0, "M"),
    (distance[i - 1, j] + deletion, 1, "D"),
    (distance[i, j - 1] + insertion, 2, "I"),
)

Đường chéo, M, ghép hai điều khoản. Nó lấy chi phí tốt nhất cho các tiền tố trước đó và thêm chi phí ghép hiện tại. cost[i - 1, j - 1] dùng chỉ mục giảm một vì ma trận này không có các biên trống của distance.

Chuyển động dọc, D, xử lý một điều khoản cũ không có tương ứng. Nó thêm chi phí xóa. Chuyển động ngang, I, xử lý một điều khoản mới không có tương ứng. Nó thêm chi phí chèn. « Xử lý » ở đây là đưa vào kết quả căn chỉnh; không điều khoản nào bị xóa khỏi PDF nguồn.

Mỗi lựa chọn là tuple chứa chi phí, thứ hạng ưu tiên và tên thao tác. Python so sánh tuple từng phần tử. Khi chi phí bằng nhau chính xác, thứ hạng 0, 1, 2 ưu tiên ghép, rồi xóa, rồi chèn. Cách phân định này làm lựa chọn tái lập được; nó không tạo thêm bằng chứng về tính đúng.

Một căn chỉnh nhỏ để làm lại bằng tay

Lấy ví dụ sư phạm: phiên bản trước chứa A, B, phiên bản sau chứa A′, X, B′. Ta chọn các chi phí dưới đây để biểu diễn hai tương ứng hấp dẫn và một đoạn được chèn. Chúng không đến từ việc mã hóa Nova.

Chi phí ghép A′ X B′
A 0,10 1,20 1,20
B 1,20 1,20 0,10

Với chèn và xóa ở 0,45, ma trận chi phí tích lũy tốt nhất trở thành:

Tiền tố trước / sau Trống A′ A′, X A′, X, B′
Trống 0,00 0,45 0,90 1,35
A 0,45 0,10 0,55 1,00
A, B 0,90 0,55 1,00 0,65

Hãy nhìn ô cuối. Ghép B với B′ kéo dài chi phí 0,55 thêm 0,10, tức 0,65. Hai lựa chọn còn lại đều là 1,00 + 0,45 = 1,45. Vì thế đường đi tốt nhất ghép A với A′, chèn X, rồi ghép B với B′.

Còn phải tìm lại đường đi này, không chỉ chi phí của nó. Chương trình lưu các tiền nhiệm, tức lựa chọn được chọn trong mỗi ô. Nó đi ngược từ (n, m) đến (0, 0), sau đó đảo danh sách để đưa về thứ tự đọc.

Kiểm tra cuối xác nhận mỗi đoạn xuất hiện đúng một lần. Kết quả là đường đi xác định, nhưng có thể nhiều đường cùng chi phí trước khi phân định. Và quan trọng hơn, mức tối thiểu được tìm trong các đường đơn điệu được phép, không phải trong mọi cách tái tổ chức có thể tưởng tượng của tài liệu.

Báo hiệu khác biệt mà không nhầm lẫn các tín hiệu

Tìm đúng điều khoản chưa kết thúc công việc

Sau căn chỉnh, chúng ta biết các đoạn nào cần so sánh. Hàm classify, trong src/pdfdiffai/changes.py, sau đó xác định kiểu tín hiệu để trình bày.

numbers tìm số thập phân cùng từ hoặc đơn vị đi sau. Với thời hạn, thông tin cần so sánh về mặt khái niệm là « 30 với days » rồi « 45 với days ». Các mô tả này giải thích quy tắc; chúng không tuyên bố tái tạo định dạng JSON nội bộ của hàm.

Khác biệt giữa các phần tử này kích hoạt numeric, ngay cả khi cosine cao. Hãy xem thứ tự điều kiện trong mã được cung cấp.

def classify(left, right, similarity=None):
    # Similarity is explanatory metadata, never a gate on the numeric check.
    if left is None:
        return {"kind": "insertion", "review": True}
    if right is None:
        return {"kind": "deletion", "review": True}
    a = numbers(left)
    b = numbers(right)
    if a != b:
        return {
            "kind": "numeric",
            "review": True,
            "left_numbers": a,
            "right_numbers": b,
        }
    if lexical(left) == lexical(right):
        return {
            "kind": "unchanged" if left == right else "presentation",
            "review": False,
        }
    return {"kind": "rewording_candidate", "review": True}

Trước hết, các điều khoản thiếu ở một bên. left is None tạo insertion; right is None tạo deletion. None ở đây biểu thị không có đoạn đối tác, không phải chuỗi văn bản rỗng. Hai loại này cần đọc lại.

Tiếp theo, các số. Kết quả của numbers(left) và numbers(right) được so sánh. Nếu khác nhau, hàm lập tức trả về numeric, với giá trị quan sát ở trái và phải. Tham số similarity không xuất hiện trong điều kiện nào của đoạn mã: vì thế nó không thể hủy cảnh báo này.

Sau đó, dạng từ vựng. Nếu lexical(left) và lexical(right) giống nhau, chương trình không yêu cầu đọc lại. Tuy vậy nó phân biệt hai trường hợp: unchanged nếu chuỗi nhận được đã hoàn toàn bằng nhau; presentation nếu chúng khác nhưng trở nên bằng nhau sau xử lý từ vựng.

Sắc thái này quan trọng: hàm không trực tiếp so sánh phông chữ hay tọa độ. Vì vậy thay đổi phông chữ để hai chuỗi trích xuất vẫn giống nhau có thể dẫn đến unchanged, không nhất thiết là presentation. Điểm chung là review: False, theo quy tắc của lộ trình.

Cuối cùng, các khác biệt khác. Hàm trả về rewording_candidate, nghĩa đen là ứng viên diễn đạt lại, với review: True. Nó không kết luận sự thay đổi nghĩa chắc chắn hay sự tương đương chắc chắn.

Hiểu điều các loại không nói

Hàm chỉ trả về một loại. Nếu số và cách diễn đạt cùng thay đổi, kiểm tra số đến trước và cho numeric. Điều đó không có nghĩa số là khác biệt duy nhất trong đoạn. Phải giữ cả hai văn bản để đọc lại.

Tương tự, chèn hoặc xóa trước hết là kết luận của đường căn chỉnh: không đối tác nào được giữ tại vị trí đó. Đây không phải lúc nào cũng là bằng chứng nội dung thực sự mới hay bị xóa. Việc di chuyển các phần sẽ cho thấy chính xác trường hợp này.

Chẩn đoán số bao phủ ngữ pháp tiếng Anh của POC, không phải mọi ngày tháng, tiền tệ, đơn vị hay quy ước tài liệu. Không nên gán cho nó khả năng chuyển đổi đơn vị tổng quát, cũng không giả định nó nhận ra mọi số viết bằng chữ. Các trường hợp này đòi hỏi quy tắc và kiểm thử bổ sung.

Nhờ vậy ta giữ một phân biệt hữu ích: mô hình đưa lại gần, căn chỉnh liên kết, quy tắc báo hiệu, con người đọc lại.

Tái tạo lộ trình đã xác thực

Chuẩn bị môi trường CPU

Giải nén ZIP và mở terminal ở thư mục gốc. Chạy các lệnh chỉ một lần và theo thứ tự trình bày. Cấu hình đã xác thực là CPU: cần Docker Engine và Compose; khuyến nghị 16 GiB RAM và 12 GiB đĩa. Các khuyến nghị này không phải mức tối thiểu được đo trên mọi máy.

Ba dịch vụ có vai trò riêng. acquisition có mạng để tải và kiểm tra các tài sản đã khóa. tutorial chuẩn bị dữ liệu và thực hiện kiểm tra. inference thực hiện tính toán đã định mà không gắn labels hay gold operations, tức chú thích và thay đổi kỳ vọng dùng cho đánh giá. tutorial và inference không có mạng.

Sự tách biệt này giúp xác minh phép tính không âm thầm đọc các đáp án mong đợi. Nó không thay thế quyền truy cập của ứng dụng thực tế.

Hãy bắt đầu bằng kiểm tra trước, chuẩn bị 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

Preflight kiểm tra điều kiện tiên quyết trước khi chạy phần còn lại. Kiểm tra Compose phải chấp nhận cấu hình và việc xây dựng phải kết thúc không lỗi. Nếu thất bại, hãy sửa bước này trước khi tiếp tục với môi trường chưa hoàn chỉnh.

Tải mô hình, rồi kiểm tra chương trình

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

Quá trình tải dùng các tài sản đã khóa của dự án. Kiểm tra môi trường sau đó xác minh việc dùng chúng trong container; pytest chạy các kiểm thử chương trình. Ba lệnh có mục tiêu khác nhau: có tệp, dùng được chúng và kiểm tra hành vi mong đợi của mã.

Một kiểm thử thành công không chứng minh mọi ghép nối trên tài liệu chưa biết sẽ đúng. Giữ mô hình và phụ thuộc của ZIP để tái tạo lộ trình r001; các tài liệu liên kết trong bài là bổ sung để hiểu, không phải hướng dẫn nâng cấp.

Tạo tài liệu và kiểm tra đầu vào

docker compose run --rm tutorial python -m pdfdiffai.cli prepare --config configs/minimum.yaml

prepare ghi các PDF, tài liệu trích xuất và nguồn gốc của chúng. Trước khi quan tâm đến điểm số, hãy xem các đoạn tìm được: thời hạn có hiện diện không? Tiêu đề và thân bài có được nhóm như dự kiến không? Một điều khoản vắt qua hai trang có giữ các vùng nguồn không?

Tiếp theo là hai góc nhìn sư phạm của phép tính:

docker compose run --rm inference python -m pdfdiffai.cli inspect-similarity --config configs/minimum.yaml
docker compose run --rm inference python -m pdfdiffai.cli explain-alignment --config configs/minimum.yaml

inspect-similarity cho mã hóa và ma trận tương đồng. Mục tiêu là liên hệ một ô với hai đoạn cụ thể, không chỉ tìm giá trị lớn nhất.

explain-alignment trình bày một ma trận sư phạm nhỏ, được kiểm tra với liệt kê đầy đủ: với ví dụ thu gọn này, chương trình có thể xét mọi đường được phép và so sánh chi phí tốt nhất của chúng với lập trình động. Kiểm tra này thuộc cơ chế tính toán; nó không chứng minh mọi liên kết đều hợp lý trong tài liệu thực.

So sánh các phương án và tạo báo cáo

docker compose run --rm inference python -m pdfdiffai.cli baseline --config configs/minimum.yaml
docker compose run --rm tutorial python -m pdfdiffai.cli evaluate --config configs/minimum.yaml

baseline so sánh hai phương án của dự án trên cùng các điều khoản. Baseline là phương pháp tham chiếu: nó giúp đánh giá kết quả một phương pháp bằng điều gì đó khác với ấn tượng. Hãy xem đầu ra mà không gán cho các phương án những điểm không có trong kết quả công bố dưới đây.

evaluate tạo báo cáo song phương, với hai phiên bản đặt cạnh nhau, JSON các dòng và viewer cục bộ. Mở outputs/viewer.html, PNG và JSON. Với thời hạn, theo toàn bộ đường đi: đoạn cũ, đoạn mới, liên kết giữ lại, các số trích xuất, loại và vùng nguồn.

Chưa chạy dọn dẹp: nó sẽ xóa outputs/. Trước hết chúng ta sẽ diễn giải các kết quả này, rồi thực hiện bài tập.

Xác định thước đo trước các con số

Căn chỉnh có đúng không?

F1 căn chỉnh so sánh các cặp dự đoán với các cặp gold, tức tương ứng được kỳ vọng của corpus. Precision đo phần các cặp đề xuất là đúng; recall đo phần các cặp kỳ vọng đã tìm thấy. Kết hợp của chúng là F1 = 2 × precision × recall / (precision + recall) khi phép tính được xác định.

Lấy số đếm của bài tập sẽ đọc sau: tám cặp được đề xuất, tất cả đúng, trong khi có mười cặp được kỳ vọng. Precision là 8/8 = 1, nhưng recall chỉ là 8/10 = 0,8. F1 xấp xỉ 0,8889, không phải 0,8. Một phương pháp có thể chính xác trong các ghép cặp mà vẫn bỏ sót một số cặp.

Bao nhiêu đoạn có đối tác?

Độ bao phủ ghép cặp bằng 2 × số cặp đã ghép / tổng số điều khoản của hai phiên bản. Hệ số hai đến từ việc mỗi cặp chứa một điều khoản của mỗi phiên bản.

Thước đo này không nói các liên kết có đúng không. Ghép tùy ý mọi đoạn sẽ cho độ bao phủ cao mà không đảm bảo F1 tốt. Ngược lại, chèn hoặc xóa thật phải không có đối tác: độ bao phủ nhỏ hơn 1 vì thế không tự động là lỗi.

Các điều khoản cô lập vẫn ở trong mẫu số. Không thể làm các trường hợp khó biến mất khỏi tổng kết chỉ bằng cách chọn không ghép chúng.

Thay đổi số đã được tìm đúng chỗ chưa?

Recall số chỉ bằng 1 nếu mỗi thay đổi kỳ vọng nằm trong một điều khoản được ghép đúng và khác biệt số của nó được báo hiệu. Phát hiện số khác nhau trong hai điều khoản sai không phải thành công.

Precision của ứng viên được xem riêng cho chèn, xóa, thay đổi số và ứng viên diễn đạt lại. Điều này giúp phân biệt lỗi liên kết với quy tắc gửi quá nhiều đoạn đi đọc lại.

Với các cặp chỉ thay đổi phân trang, tỷ lệ thay đổi giả đo các đoạn bị gửi đi đọc lại không cần thiết. Cuối cùng, thời gian trung bình của giao thức chỉ bao phủ căn chỉnh: nó loại trừ tải, dựng PDF và mã hóa. Vì vậy đó không phải toàn bộ thời gian từ lúc tải tệp đến khi kết quả hiển thị trong back-office.

Các thước đo tách biệt ghép cặp, thay đổi số và giới hạn của việc di chuyển phần trong corpus Nova.

Kết quả kiểm thử Nova

Trên 90 cặp kiểm thử, công cụ so sánh động đạt các kết quả sau:

Thước đo Kết quả tham chiếu
Căn chỉnh 870 cặp gold được căn chỉnh; F1 = 1,00
Độ bao phủ ghép cặp 1 740 đoạn trên 1 800; 0,9667
Recall số 30 thay đổi kỳ vọng được báo hiệu; 1,00
Thay đổi giả khi chỉ phân trang lại Tỷ lệ 0 trên 30 cặp liên quan

Hãy kiểm tra số đếm: 2 × 870 = 1 740, rồi 1 740 / 1 800 ≈ 0,9667. 60 đoạn còn lại là cô lập trong căn chỉnh. Con số này phù hợp với 30 lần chèn và 30 lần xóa được kỳ vọng theo cấu trúc các cặp kiểm thử; nó không chỉ 60 tương ứng bị quên.

Các số này mô tả PDF Nova được kiểm soát. Chúng không xác thực công cụ so sánh pháp lý hay xử lý bố cục chưa biết. Đọc lại vẫn cần để diễn giải khác biệt, và điểm tốt trong phân phối này không dự đoán điểm ở corpus khác.

Kết quả âm: hoán đổi hai phần

Bài tập độc lập thay đổi thứ tự của hai phần nguyên vẹn. Nội dung được di chuyển vẫn có mặt, nhưng đường đơn điệu không thể tự do giữ các liên kết giao nhau.

Kết quả giảm xuống F1 = 0,8889, với tám ghép cặp gold trên mười và 16 đoạn ghép trên 20. Độ bao phủ tương ứng là 16/20 = 0,8. Hai lần chèn và hai lần xóa được đề xuất.

Bốn cảnh báo này thuộc hai loại có precision bằng 0 như ứng viên thay đổi nội dung trong bài tập: chúng biểu diễn sự di chuyển, không phải chèn hay xóa nội dung đã được chứng minh. Thuật toán có thể tuân thủ hoàn hảo hợp đồng thứ tự và thất bại khi tài liệu không còn tuân thủ giả định đó.

Tổng kết trước bài tập

Chúng ta đã tách ba đối tượng: dữ liệu trích xuất, quyết định của công cụ so sánh và đo lường so với tham chiếu. Nguồn gốc cho phép quay lại nguồn; nó không chứng minh chọn đối tác là đúng. Cosine giúp đưa lại gần; nó không vô hiệu khác biệt thời hạn. Đường toàn cục tránh tái sử dụng; nó cũng giới hạn những dịch chuyển có thể.

Bài tập sẽ kiểm chứng các phân biệt này. Mục đích là dự đoán cơ chế nào bị ảnh hưởng, rồi giải thích kết quả thay vì chỉ hài lòng với một điểm số.

Bài tập: dự đoán trước khi sửa

Viết giả thuyết trước khi chạy lệnh

Bắt đầu với tình huống này: Payment within 30 days và Payment within 45 days nhận độ tương đồng gần hoàn hảo. Chúng có nên được xếp là không thay đổi không? Hãy tìm trong classify điều kiện phải quyết định, không phát minh ngưỡng tương đồng mới.

Sau đó hãy tưởng tượng hai phần đổi hoàn toàn vị trí mà không đổi nội dung. Đường toàn cục có thể giữ mọi ghép cặp không? Một lần chèn được đề xuất khi đó có chứng minh văn bản mới xuất hiện không? Hãy phân biệt vấn đề xếp loại các đoạn với nội dung của chúng.

Ghi câu trả lời rồi chạy kịch bản được cung cấp:

docker compose run --rm tutorial python -m pdfdiffai.cli exercise --config configs/exercise.yaml

So sánh dự đoán của bạn với đầu ra

Với 30 và 45 ngày, so sánh số vẫn ưu tiên và phải yêu cầu đọc lại. Sửa giá trị tương đồng giải thích không làm thay đổi điều kiện thấy được trong classify: không phép kiểm nào trên giá trị này cho phép bỏ qua số.

Với hoán đổi, giả định thứ tự bị phá vỡ. Chương trình có thể ưu tiên chèn và xóa vì không có thao tác cho phép di chuyển tự do một phần. Không được đổi tên những cảnh báo này thành « diễn đạt lại chắc chắn ».

Hãy tìm trong đầu ra tám ghép cặp đúng, hai lần chèn và hai lần xóa, rồi quay lại các đoạn nguồn. Cách sửa là liên hệ những cảnh báo này với sự di chuyển mà không tuyên bố công cụ đã tự động giải quyết trường hợp đó. Nhận ra giới hạn là kết quả của bài tập; phát hiện di chuyển tổng quát không phải chức năng được thêm vào POC.

Tạo hình và dọn dẹp

Sau bài tập, tạo các hình và chạy kiểm tra tệp theo lộ trình:

docker compose run --rm tutorial python lesson/figures/plot.py
docker compose run --rm tutorial python scripts/check_checkpoints.py

Sự có mặt của lệnh mang tên check_checkpoints.py không có nghĩa vừa có huấn luyện diễn ra. MiniLM vẫn đóng băng; script thuộc các kiểm tra tệp của dự án.

Mở các hình, outputs/viewer.html và JSON trước khi tiếp tục. Giữ các kết quả hữu ích, đặc biệt các trường hợp tín hiệu chèn hoặc xóa thực ra tương ứng với một sự di chuyển.

Ba lệnh cuối dừng dự án, hiển thị rồi xóa chỉ outputs/, sau đó kết thúc lộ trình. Corpus và các tài sản vẫn được giữ.

docker compose down
docker compose run --rm tutorial python scripts/clean.py --outputs-only
docker compose down

Dọn dẹp không sửa lỗi căn chỉnh và không cải thiện thước đo: nó chỉ đặt lại đầu ra về 0. Không yêu cầu dọn Docker toàn cục.

Điều chỉnh, khắc phục sự cố và dọn dẹp

Chẩn đoán theo thứ tự xử lý

Môi trường hoặc quá trình tải thất bại? Bắt đầu bằng preflight và check_environment.py, rồi kiểm tra checksum tải về, các dấu vân tay tệp kỳ vọng. Không thay thế phiên bản đã khóa bằng tệp tìm nơi khác chỉ để làm lỗi biến mất.

Các đoạn thiếu, bị cắt hoặc nhóm sai? Kiểm tra glyph, vùng, tiêu đề đã nhận diện và vùng bị loại trừ. Đầu trang loại trừ sai hoặc từ ngắt không sửa được trước hết thuộc bước trích xuất, không phải MiniLM.

Ma trận hoặc báo cáo có vẻ không nhất quán? Theo các JSON mã hóa, căn chỉnh và đánh giá. Xem riêng đối tác được giữ và loại được gán. Hai đoạn có thể gần nhưng ghép sai; một ghép cặp có thể đúng mà cách diễn đạt vẫn cần đọc lại.

Cách diễn đạt đổi mà không có cảnh báo? Kiểm tra những khác biệt xử lý từ vựng đã xóa và điều numbers thực sự nhận ra. Chèn và xóa xuất hiện quanh các phần bị di chuyển? So sánh thứ tự điều khoản trước khi sửa chi phí: trọng số khác không xóa ràng buộc đơn điệu.

Xác định phạm vi mới trước khi mở rộng công cụ

Để xử lý tài liệu thực được phép, hãy lập các corpus riêng theo nguồn, ngôn ngữ và kiểu bố cục. Đo sự di chuyển, cũng như gộp và tách điều khoản, cột, bảng và bản quét, trước khi điều chỉnh ngưỡng.

Một ghép cặp cơ bản của lộ trình liên kết một điều khoản với một điều khoản. Chia một đoạn thành hai hoặc gộp nhiều đoạn vì thế là khó khăn khác với đổi vài từ. Tương tự, thêm OCR cho bản quét sẽ thay đổi đầu vào và lỗi có thể có: đó là mở rộng cần đánh giá, không phải khả năng đã xác thực ở đây.

Trong giao diện thực, hãy phân biệt ứng viên do công cụ so sánh tạo với kết luận của người đọc lại. Thêm trạng thái « từ chối / không chắc chắn », quyền tài liệu và đánh giá con người. Người dùng phải có thể tìm lại hai phiên bản, hiểu loại và thấy rằng đoạn không đối tác không nhất thiết là nội dung bị xóa.

Cũng cần dự kiến API bất đồng bộ, tức xử lý có kết quả được xem sau khi gửi, cùng nhật ký, sao lưu và khôi phục. Đây là công việc sản phẩm, không phải chức năng do hướng dẫn cung cấp.

Cuối cùng, POC không phải sandbox hoàn chỉnh cho trình phân tích PDF, cũng không phải chính sách lưu giữ. Tài liệu và chuỗi đã trích xuất là dữ liệu, không bao giờ là chỉ dẫn để thực thi. Cô lập xử lý và quyền trên tệp cần thiết kế phù hợp với dịch vụ triển khai.

Tài liệu tham khảo để tìm hiểu thêm

Các tham chiếu của lộ trình là thẻ MiniLM chính thức, cho mô hình và pooling, và tài liệu NumPy về mảng, cho kích thước và chỉ mục ma trận.

Các phần bổ sung theo mạch bài giải thích chuẩn hóa Unicode, token WordPiece, cosine và so sánh tuple Python. Các phép tính minh họa nhỏ không phải số đo Nova mới. Để tái tạo kết quả, phiên bản, thiết lập và lệnh tham chiếu vẫn là của ZIP r001.

Chia sẻ bài viết