Bạn đang phát triển một back-office cho phép chia sẻ thư từ sau khi đã che thông tin nhạy cảm. Trong phần xem trước, tên và địa chỉ bị phủ màu đen. Tệp có vẻ đã sẵn sàng để gửi. Nhưng điều gì xảy ra nếu người nhận chọn văn bản rồi sao chép vào trình soạn thảo?
Với một lập trình viên web, cái bẫy này gợi nhớ đến một phần tử bị ẩn trên màn hình trong khi giá trị của nó vẫn còn trong HTML. PDF không phải trang web, nhưng sự khác biệt là như nhau: không còn nhìn thấy một thông tin không có nghĩa là nó đã biến mất khỏi tệp. Một thanh đen thêm vào PDF có thể vẫn để lại văn bản và các đối tượng gốc bên dưới.
Chúng ta sẽ xây dựng một chuỗi xử lý riêng từng vấn đề: đề xuất các vùng nhạy cảm, chọn các vùng cần phê duyệt, sửa các pixel liên quan, rồi tạo một PDF mới từ ảnh thu được. Sau đó, chúng ta sẽ kiểm tra điều gì thực sự đã được che, thay vì tin vào vẻ ngoài của nó.
Mạch xuyên suốt của chúng ta là một lá thư hư cấu « Solstice Support ». Nó có một tên, một địa chỉ @example.org và một mã tham chiếu ticket. Chương trình phải tìm các đề cập ứng viên mà không đọc đáp án mong đợi, chỉ che các vùng đã được phê duyệt và không mang cấu trúc PDF nguồn sang bản sao được dựng lại.
Hướng dẫn này là một bản minh họa, không phải công cụ ẩn danh hóa sẵn sàng dùng. Nó không đưa ra tư vấn pháp lý hay bảo đảm nào cho tài liệu thực. Kết quả quan trọng nhất của nó chính là một thất bại có thể quan sát: trong bài kiểm tra, một số đề cập sẽ không được che hoàn toàn.
Ví dụ thực sự đo lường điều gì
Văn bản, pixel và tệp: ba thứ khác nhau
Chúng ta sẽ thao tác nhiều biểu diễn của cùng một lá thư. Văn bản trích xuất cho phép tìm một địa chỉ hoặc tên. Pixel tạo nên diện mạo của trang được kết xuất. Cấu trúc PDF chứa các thành phần dùng để biểu diễn tài liệu: nó không chỉ là ảnh chụp màn hình.
Sự khác biệt sau cùng này không chỉ liên quan đến văn bản hiển thị. Tài liệu Adobe về thông tin ẩn trong PDF đề cập đến siêu dữ liệu, nhận xét và lớp, trong số những thành phần khác. Chúng ta sẽ không cho rằng chúng đã biến mất chỉ vì một vùng trông có màu đen: dự án sẽ kiểm tra bản sao mà nó tạo ra.
Raster hóa một trang là tính ra một ảnh gồm các pixel. Điều đó không có nghĩa là nhận dạng văn bản của nó. Ngược lại, OCR cố gắng tìm lại các ký tự từ một ảnh. Ở đây, khâu phát hiện nhận văn bản; raster hóa dùng để tạo và kiểm soát bản sao trực quan. Lộ trình này không chứng minh việc nhận dạng tên trực tiếp trên bản quét.
AI đóng góp gì, và quy tắc đã làm được gì
Một thực thể được đặt tên là một phần văn bản mà chúng ta gán cho một loại. Trong dự án này, các loại cần tìm là người (PERSON), e-mail (EMAIL) và mã tham chiếu (REFERENCE). Từ đề cập chỉ một lần xuất hiện trong tài liệu: tìm một tên ở một chỗ không loại trừ việc tìm các lần xuất hiện khác của nó.
NER, viết tắt của Named Entity Recognition, là nhận dạng thực thể được đặt tên. spaCy đề xuất các đề cập đến người từ một mô hình đã được huấn luyện. Hai biểu thức chính quy, hay regex, lần lượt tìm các định dạng e-mail và mã tham chiếu được quy định trong bài học.
Vì sao kết hợp cả hai? Bởi vì một địa chỉ có định dạng dễ nhận ra và một tên nằm trong câu không phải cùng một vấn đề. Regex biểu đạt các mẫu do lập trình viên viết; NER dùng mô hình thống kê. Không công cụ nào trong hai công cụ này tự động phát hiện mọi thông tin nhạy cảm có thể có. Tài liệu spaCy trình bày cách hoạt động này và nhắc rằng các dự đoán của nó có thể sai.
Sau đó chương trình tạo hợp của chúng: nó tập hợp các ứng viên từ hai cơ chế. Nó không yêu cầu cả hai phải đồng ý để đề xuất một vùng. Chúng ta sẽ xem trong mã phần nào thực sự được gộp, và phần nào vẫn riêng biệt.
Lộ trình dùng spaCy 3.7.2 với en_core_web_sm 3.7.1, được nạp cục bộ. Nó không huấn luyện lại mô hình này trên các lá thư: chúng ta thực hiện suy luận, tức dùng mô hình sẵn có để có dự đoán. Trang thông tin của đúng phiên bản en_core_web_sm mô tả một pipeline tiếng Anh tối ưu cho CPU. Vì hướng dẫn này được viết bằng tiếng Pháp không khiến mô hình trở thành bộ phát hiện tiếng Pháp.
Giữ đáp án mong đợi ngoài bộ phát hiện
Bộ dữ liệu có 120 lá thư tổng hợp: 30 để phát triển và 90 để kiểm tra. Phân vùng kiểm tra có 360 đề cập mong đợi.
Các đề cập này được mô tả bằng chú thích tham chiếu, thường gọi là gold. Chúng đóng vai trò đáp án của một bài tập. Chúng dùng để đo những gì chương trình tìm được, chứ không chỉ cho chương trình biết nơi cần tìm.
Container suy luận không mount thư mục của chúng. Với lập trình viên quen Docker, ranh giới này rất cụ thể: tiến trình không chỉ được dặn không mở đáp án, mà còn không được có thư mục chứa chúng. Sự tách biệt này cho phép đánh giá một đề xuất, thay vì một chương trình âm thầm sao chép đáp án.
30 lá thư phát triển dùng để chuẩn bị lộ trình; 90 lá thư kiểm tra dùng để đo kết quả cuối. Những số này không mô tả một lần huấn luyện spaCy mới. Chúng cũng không đại diện cho sự đa dạng về tên, ngôn ngữ và bố cục của một bộ sưu tập thực tế.
Đặt đúng câu hỏi trước khi tính điểm
Đối với phát hiện, chúng ta đếm dương tính thật, tức các đề cập được đề xuất đúng, và mọi dự đoán được đưa ra. Độ chính xác trả lời « trong các đề xuất của tôi, bao nhiêu là đúng? »; độ bao phủ trả lời « trong các đề cập mong đợi, tôi đã tìm lại được bao nhiêu? ».
Các phép tính là precision = true positives / predictions và recall = true positives / gold mentions. Trong một ví dụ sư phạm, tìm đúng 8 đề cập trong 10 đề cập mong đợi, kèm thêm 2 đề xuất không cần thiết, cho độ chính xác 8/10 và độ bao phủ 8/10. Các giá trị bằng nhau, nhưng câu hỏi vẫn khác nhau.
Việc che thêm một phép đo khác: coverage = covered gold pixels / gold pixels. Một đề cập chỉ được che hoàn toàn nếu toàn bộ pixel tham chiếu của nó được phủ. Phát hiện tốt không đảm bảo hình chữ nhật được áp dụng đúng vị trí; độ phủ trung bình tốt không đảm bảo mọi đề cập đều hoàn chỉnh.
Vì vậy chúng ta sẽ theo dõi phát hiện, pixel, đề cập hoàn chỉnh và cấu trúc tệp. Không có ký tự nào trích xuất được không phải là điểm số cho việc thông tin hiển thị đã biến mất. Bài tập cuối sẽ cho thấy điều này rất trực tiếp.
Chuẩn bị một lần chạy CPU có thể tái lập
Bạn cần biết đọc Python, dùng terminal và chạy Docker Compose. Chúng ta sẽ giải thích tọa độ và các thao tác ảnh trong quá trình đọc mã. Không cần kinh nghiệm trước về OCR hay tính toán GPU.
Lộ trình r001 đã được xác thực trên CPU: không cần GPU NVIDIA. Hãy dự trù khoảng 16 Gio RAM và 15 Gio dung lượng đĩa trống cho ảnh, container và đầu ra. Đây là chỉ dẫn chuẩn bị của bài học, không phải số đo được bảo đảm cho mọi máy.
Giải nén ZIP và đặt terminal tại thư mục gốc của nó. Mọi lệnh trong bài viết đều chạy từ cùng thư mục này. Chúng ta giữ nguyên cấu hình và thứ tự của chúng để tìm lại các điều kiện tham chiếu.
Kiểm tra môi trường và xây dựng dịch vụ
sh scripts/preflight.sh
sh scripts/setup.sh
docker compose config --quiet
docker compose build acquisition tutorial inference
Preflight là bước kiểm tra trước: nó xác minh Docker, bộ nhớ và dung lượng đĩa, trong số các yếu tố khác. setup.sh chuẩn bị lộ trình. Sau đó Compose kiểm tra cấu hình rồi xây dựng các dịch vụ acquisition, tutorial và inference.
Đừng ngạc nhiên nếu docker compose config --quiet không tạo văn bản khi thành công: tùy chọn --quiet xác thực mà không hiển thị cấu hình. Điều quan trọng là không có lỗi và mã trả về bằng không.
Kiểm soát tài nguyên rồi đến các kiểm thử
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
Trong bản sửa đổi này, bước thu thập kiểm soát phiên bản và dấu vân tay của các tài nguyên đã khóa; bài học nêu rõ không có tài nguyên nào được tải xuống khi chạy hướng dẫn. Đây không phải lời mời để suy luận tự động lấy một mô hình khác. Việc xây dựng ảnh Docker vẫn là bước riêng với lần chạy này.
check_environment.py xác nhận tài nguyên có mặt. Sau đó các kiểm thử xác minh các hàm của bài học trước khi tạo PDF. Một kiểm thử thành công nghĩa là hành vi được kiểm tra được tuân thủ; không có nghĩa mọi đề cập trong một lá thư sẽ được nhận ra.
Nếu kiểm tra bộ nhớ hoặc đĩa thất bại, hãy giải phóng tài nguyên cần thiết và chạy lại preflight. Xóa kiểm tra không làm giảm nhu cầu của chương trình.
Chuẩn bị các lá thư và đề xuất vùng
Tạo đầu vào của thí nghiệm
docker compose run --rm tutorial python -m pdfredactai.cli prepare --config configs/minimum.yaml
prepare tạo các lá thư tổng hợp, bản kết xuất của chúng và các chú thích đo lường. Những chú thích này nằm ngoài volume mà suy luận dùng. Cơ chế bảo vệ của dự án nêu rõ ràng ràng buộc này: bộ phát hiện không được thấy data/generated/labels.
Ở giai đoạn này, chúng ta có các tài liệu cần xử lý và, riêng biệt, đáp án của chúng. Giờ đây chúng ta có thể yêu cầu chương trình đề xuất vùng chỉ từ các đầu vào quan sát được.
Liên kết một đoạn văn bản với vị trí trên trang
docker compose run --rm inference python -m pdfredactai.cli propose --config configs/minimum.yaml
propose nạp mô hình cục bộ và ghi các ứng viên vào outputs/proposals.json. Một đề xuất cần lưu nhiều hơn một từ: chúng ta cần loại của nó, vị trí trong văn bản và các hình chữ nhật của nó trong tài liệu.
Các vị trí văn bản này là offset ký tự. Chúng chỉ nơi một đoạn chuỗi bắt đầu và kết thúc. Trong tài liệu về Span, spaCy hiển thị chúng qua start_char và end_char. Đừng nhầm chúng với vị trí của token, tức các đơn vị văn bản mà pipeline của nó tách ra.
Hãy lấy một chuỗi được tạo ra chỉ để hiểu cách đánh chỉ mục: Contact: Alice Rowan.. Đoạn Alice Rowan bắt đầu ở chỉ số 9 và kết thúc ngay trước chỉ số 20. Vì vậy biểu thức Python text[9:20] chứa 11 ký tự của tên. Cận kết thúc bị loại trừ. Ví dụ này không phải đầu ra của mô hình cũng không phải lá thư bổ sung của corpus.
Các chỉ số này không phải tọa độ ảnh. Ký tự số 9 không nhất thiết nằm ở pixel ngang số 9. Cần tìm các hình chữ nhật gắn với đoạn này trong tài liệu, rồi chuyển đổi chúng sang hệ tọa độ của bản kết xuất. Đó là liên kết mà dự án duy trì với span_boxes, rồi với xử lý hình học của mặt nạ.
Đọc phép hợp NER + regex
Đây là phần cốt lõi chính xác của propose:
regex = regex_candidates(text)
ner = [
{
"start": e.start_char,
"end": e.end_char,
"kind": e.label_,
"source": "ner",
}
for e in result.ents
if e.label_ in config["entity_labels"]
]
candidates = {}
for candidate in regex + ner:
key = (candidate["start"], candidate["end"])
row = candidates.setdefault(key, {**candidate, "sources": []})
row["sources"].append(candidate["source"])
union = []
for index, ((a, b), candidate) in enumerate(sorted(candidates.items())):
union.append(
{
**candidate,
"id": f"z{index:03d}",
"text": text[a:b],
"boxes": span_boxes(document, a, b),
}
)
Phần đầu tạo các ứng viên NER. Với mỗi thực thể trong result.ents, mã giữ lại cận ký tự, loại label_ và nguồn ner. Bộ lọc config["entity_labels"] chọn các loại được cấu hình cho phép. Các ứng viên regex đã được tính riêng bằng regex_candidates(text).
Phần thứ hai tập hợp các lần xuất hiện ở cùng vị trí. regex + ner nối hai danh sách. Khóa từ điển là cặp (start, end), không phải văn bản được nhận ra. Vì thế, hai lần xuất hiện giống nhau ở hai chỗ khác nhau không bị lẫn chỉ vì chúng mang cùng một tên.
setdefault chèn một dòng nếu khóa chưa tồn tại; nếu không, nó trả về dòng đã có. Đây là hành vi được mô tả trong tài liệu Python về từ điển. Ký hiệu **candidate sao chép các trường của ứng viên, rồi chương trình thêm danh sách sources.
Nếu regex và NER đề xuất đúng cùng các cận, chỉ một dòng được giữ lại và cả hai nguồn được ghi nhận. Đây không phải hai phiếu bầu độc lập và không tạo ra xác suất đúng. Đoạn mã giữ nguồn gốc; nó không tính điểm tin cậy kết hợp.
Cũng có một giới hạn cần thấy trong vài dòng này. Nếu hai ứng viên có cùng cận nhưng khác kind, các trường của ứng viên đầu tiên được giữ lại: setdefault không phân xử chúng. Vì danh sách regex đứng trước danh sách NER, ứng viên regex được gặp trước. Ngược lại, hai ứng viên chồng lên nhau nhưng không có đúng cùng cận vẫn tách biệt. Phép hợp này không phải là sự gộp chung các hình chữ nhật.
Phần cuối chuẩn bị đầu ra. Các cặp được sắp theo vị trí bắt đầu, rồi vị trí kết thúc. enumerate gán cho chúng các định danh như z000, z001 và z002. text[a:b] lấy văn bản của mỗi lần xuất hiện, còn span_boxes(document, a, b) tìm các hộp tương ứng.
Vì vậy, một định danh như z003 là vị trí trong danh sách đề xuất của phiên bản này, không phải danh tính phổ quát của tên đã phát hiện. Nếu các đề xuất thay đổi, số này có thể chỉ một vùng khác. Đây là lý do chính đáng để gắn phê duyệt với đúng phiên bản của tài liệu và đề xuất.
Một phê duyệt có thể truy vết, nhưng chưa phải do con người thực hiện
docker compose run --rm inference python -m pdfredactai.cli review --config configs/minimum.yaml
Từ review có thể khiến bạn nghĩ đến một người vận hành đã đọc lá thư. Đó không phải điều lệnh này chứng minh. Trong POC, hay proof of concept, bước duyệt áp dụng chính sách đã cấu hình cho các đề xuất của corpus tổng hợp.
Quyết định được gắn với SHA-256 của PDF, digest của đề xuất và bản sửa đổi. Digest là dấu vân tay được tính từ nội dung. Tài liệu Python về hashlib mô tả SHA-256, trong số các nội dung khác. Ở đây, các dấu vân tay này dùng để kiểm tra rằng chúng ta làm việc trên các tệp mà quyết định đề cập đến.
Hãy nghĩ đến việc xác thực biểu mẫu phải luôn gắn với đúng phiên bản của một bản ghi. Không được âm thầm dùng lại phê duyệt của thư A sau khi thay thư đó bằng thư B. Tuy vậy, hash không chứng minh một người đã đọc thư hay đã chọn đúng vùng.
Một sản phẩm vẫn cần được thiết kế giao diện duyệt, danh tính người vận hành, quyền của họ và chính sách leo thang cho các trường hợp không chắc chắn. Hiện tại, hãy xem đề xuất và quyết định là đầu ra của thí nghiệm, không phải một xác thực con người đã đạt được.
Che pixel và dựng lại bản sao
Thay pixel thay vì thêm một đối tượng chồng lên trên
Bây giờ chúng ta có các vùng đã được phê duyệt. Chương trình không biến chúng thành các hình chữ nhật được thêm vào PDF ban đầu. Nó làm việc trên một bản sao của ảnh trang, thay các pixel liên quan bằng màu đen, sau đó mô-đun rebuild tạo một PDF mới từ ảnh đã sửa đổi này.
Hàm sau tạo ba đối tượng: ảnh bị làm đen, mặt nạ kiểm soát và danh sách hình chữ nhật đã áp dụng. Bản thân nó không tạo PDF; bước đó diễn ra sau.
def mask_pixels(image, boxes, scale, margin):
image = image.convert("RGB").copy()
mask = Image.new("L", image.size, 0)
paint = ImageDraw.Draw(image)
mark = ImageDraw.Draw(mask)
rectangles = []
for box in boxes:
x0, y0, x1, y1 = pixel_box(box, scale, image.size, margin)
rectangle = (x0, y0, x1 - 1, y1 - 1)
paint.rectangle(rectangle, fill=(0, 0, 0))
mark.rectangle(rectangle, fill=255)
rectangles.append([x0, y0, x1, y1])
return image, mask, rectangles
Vì sao cần hai ảnh?
image.convert("RGB").copy() chuẩn bị một bản sao ba kênh: đỏ, lục và lam. Màu đen được biểu diễn bằng (0, 0, 0). Đối tượng thứ hai, được tạo với Image.new("L", image.size, 0), chỉ có một kênh mức xám, ban đầu chứa đầy số không. Các chế độ này được mô tả trong các khái niệm của Pillow.
Hai ảnh không có cùng vai trò. Ảnh RGB là tài liệu trực quan đã sửa. Mặt nạ chỉ ra nơi chương trình đã can thiệp. Trong mặt nạ này, giá trị 255, màu trắng, đánh dấu các vùng đã xử lý; số không, màu đen, nghĩa là không có hình chữ nhật nào được áp dụng ở đó. Vì vậy màu trắng của mặt nạ không có nghĩa trang được xuất bản sẽ trắng ở vị trí ấy: ở đó nó bị làm đen.
paint vẽ trong bản sao tài liệu; mark vẽ trong mặt nạ. Mỗi hình chữ nhật nhận cùng một vị trí trong cả hai. Sự tương ứng này cho phép sau đó so sánh các vùng thực sự được xử lý với các vùng tham chiếu.
Bản sao giữ nguyên ảnh đầu vào. Điều này hữu ích để kiểm tra biến đổi, nhưng cũng nhắc rằng tạo một bản sao đã che không xóa bản gốc. Hàm không xóa PDF nguồn cũng không xóa các tệp trung gian khác.
Chuyển từ tọa độ tài liệu sang tọa độ kết xuất
pixel_box nhận hộp nguồn, tỷ lệ scale, kích thước ảnh và lề margin. Việc tính làm tròn và giới hạn trang nằm trong hàm này của dự án, không nằm trong đoạn trích trên. Cần giữ các quy ước của nó thay vì đoán quy ước của thư viện khác.
Để chỉ hiểu tỷ lệ, hãy lấy một ví dụ hình học độc lập với corpus. Với các gốc đã thẳng hàng và không có lề, một hộp (10, 20, 30, 25) được phóng lớn gấp hai thành (20, 40, 60, 50). Kích thước của nó đổi từ 20 × 5 thành 40 × 10; diện tích nhân bốn. Ví dụ này không mô tả cách làm tròn hoặc lề được cấu hình trong r001.
Một lề có thể che cạnh của ký tự hơi vượt ra ngoài hộp đã giữ, nhưng cũng làm đen nhiều vùng lân cận hơn. Nó không thay thế việc tìm một đề cập bị bỏ quên ở nơi khác trên trang. Cần xem độ phủ vùng nhạy cảm và việc che các vùng không nhạy cảm.
Hai dấu « trừ một » dùng để làm gì?
Dòng rectangle = (x0, y0, x1 - 1, y1 - 1) cần được dừng lại xem kỹ. Hàm Pillow rectangle bao gồm cả hai cận. Do đó mã chuyển một hộp có cận phải và cận dưới bị loại trừ sang phép vẽ có cận bao gồm.
Giả sử pixel_box trả về (10, 20, 30, 25). Chúng ta muốn phủ các cột từ 10 đến 29 và các hàng từ 20 đến 24: 20 × 5 = 100 pixel. Vì vậy hình chữ nhật truyền cho Pillow kết thúc ở (29, 24).
Nếu không trừ một, hình vẽ sẽ đi đến cột 30 và hàng 25, tức 21 × 6 = 126 pixel. Hình chữ nhật trông gần như giống hệt trên màn hình, nhưng diện tích và số đo sẽ khác. Ví dụ nhỏ này giải thích một quy ước tọa độ; nó không phải kết quả mới của corpus.
Cuối cùng, rectangles.append([x0, y0, x1, y1]) giữ các cận gốc cho dấu vết. Danh sách được lưu và hình chữ nhật được vẽ vì vậy dùng hai cách viết nhất quán của cùng một vùng.
Chạy biến đổi và đánh giá
docker compose run --rm inference python -m pdfredactai.cli redact --config configs/minimum.yaml
docker compose run --rm tutorial python -m pdfredactai.cli evaluate --config configs/minimum.yaml
redact kiểm tra phê duyệt và dấu vân tay SHA của nguồn trước và sau khi dựng lại. Nó cũng từ chối sự hiện diện của chú thích gold. Sau đó evaluate có thể truy cập đáp án để đo phần đã được phủ.
Mỗi dòng trong outputs/transformation.json mang trạng thái FIXTURE_COPY_NOT_A_SAFETY_CERTIFICATE. Một fixture là trường hợp kiểm thử đã biết, được chuẩn bị để kiểm tra một hành vi. Bạn có thể đọc trạng thái này là « bản sao của trường hợp kiểm thử, không phải chứng chỉ an toàn ».
Đánh giá kiểm tra pixel, sự vắng mặt của dấu hiệu tổng hợp được tìm và cấu trúc bản sao: một trang, một ảnh /Im0, không có văn bản hay đối tượng nguồn được mong đợi. /Im0 là tên tài nguyên ảnh trong tệp này. Sự hiện diện của nó không có nghĩa ảnh không còn thông tin nhạy cảm: chính các pixel là phần vẫn cần kiểm soát.
Vì vậy, các kiểm tra này trả lời hai câu hỏi khác biệt: chúng ta đã dựng lại kiểu tệp mong muốn chưa, và chúng ta đã thực sự che mọi vùng mong đợi chưa? Câu hỏi thứ nhất có thể thành công trong khi câu hỏi thứ hai thất bại.
Đọc kết quả trước khi khái quát hóa
Hình minh họa tách việc phát hiện đề cập khỏi việc áp dụng che. Hãy giữ sự tách biệt này khi đọc các con số của lộ trình tham chiếu.
Regex chính xác, nhưng không tìm mọi thứ
Trên 360 đề cập kiểm tra, chỉ regex tìm được 180 đề cập đúng: 90 e-mail và 90 mã tham chiếu. Chúng không tìm được đề cập nào trong 180 đề cập về người. Vì vậy độ chính xác của chúng là 1,00, nhưng độ bao phủ chỉ 0,50.
Độ chính xác hoàn hảo không có nghĩa nhiệm vụ đã hoàn thành. Ở đây, nó có nghĩa các đề xuất được đưa ra là đúng; một nửa số đề cập mong đợi vẫn còn thiếu.
Hợp NER + regex tìm lại 282 dương tính thật trên 411 đề xuất, tức độ chính xác 0,686 và độ bao phủ 0,783. Đây là các số đếm giúp hiểu sự khác biệt:
| Phương pháp | Đề cập đúng | Đề xuất | Độ chính xác | Độ bao phủ |
|---|---|---|---|---|
| Chỉ regex | 180 | 180 | 1,00 | 0,50 |
| Hợp NER + regex | 282 | 411 | 0,686 | 0,783 |
Vì vậy, việc thêm NER tìm được nhiều đề cập hơn, nhưng cũng thêm các vùng không mong đợi. Theo phép trừ, hợp có 129 đề xuất không được công nhận là đúng (411 - 282) và 78 đề cập mong đợi không được tìm lại như dương tính thật (360 - 282). Hai số này được tính từ các số đếm đã công bố, không phải từ một thử nghiệm mới.
Loại PERSON giải thích phần lớn khó khăn này: nó có 102 dương tính thật trên 231 đề xuất và 180 đề cập gold. Vì vậy chúng ta có một bộ phát hiện giúp tìm người, chứ không phải bộ phát hiện tìm hết mọi người hoặc luôn đề xuất đúng vùng.
Độ phủ pixel không phải tỷ lệ đề cập hoàn chỉnh
Biến đổi phủ 1 717 175 trong số 1 881 925 pixel gold, tức 0,91246, khoảng 91,25 %. Nó che hoàn toàn 320 đề cập trên 360.
Vì vậy còn 40 đề cập bị che không hoàn toàn. Điều này không có nghĩa cả 40 đề cập đều hiển thị trọn vẹn, cũng không nhất thiết là không đọc được: kiểm soát cho biết độ phủ của chúng không đầy đủ. Chúng ta không biến nhận định này thành thành công vì hình chữ nhật « có vẻ đủ lớn ».
Vì sao tỷ lệ pixel và đề cập có thể khác nhau? Trong một ví dụ hư cấu, giả sử hai đề cập có vùng tham chiếu chiếm 100 và 10 pixel. Nếu vùng thứ nhất được phủ hoàn toàn và vùng thứ hai hoàn toàn không được phủ, độ phủ chung là 100/110, tức khoảng 90,91 %, nhưng chỉ một trong hai đề cập được che hoàn toàn. Bề mặt lớn có trọng số lớn hơn trong phép tính đầu; mỗi đề cập có trọng số một đơn vị trong phép tính sau.
Lý luận tương tự giải thích vì sao không nên lẫn lộn 282 phát hiện đúng với 320 đề cập được che hoàn toàn. Các chỉ số không đếm cùng đối tượng. Ví dụ, một vùng quá lớn có thể phủ pixel nhạy cảm mà không được công nhận là đề xuất phát hiện tốt; nhiều mặt nạ cũng có thể chồng lên nhau.
Đây là các giải thích khả dĩ về cơ chế, không phải chẩn đoán các đề cập của bài kiểm tra này. Tổng số đã công bố không đủ để giải thích từng khác biệt: cần xem đề xuất, hình chữ nhật và các đối sánh đánh giá, từng đề cập một. Chúng ta giữ cả hai kết quả mà không thay thế cái này bằng cái kia.
Dấu hiệu vắng mặt không chứng minh mọi thứ đã biến mất
Trong 12 tài liệu có chứa dấu hiệu tổng hợp, báo cáo đếm không còn chuỗi dấu hiệu nào. Kiểm soát có mục tiêu này hữu ích: tín hiệu được tìm không được phát hiện bởi kiểm thử này. Nhưng nó không bù cho 40 lỗi độ phủ.
Ở cấp độ tài liệu, đầu ra cho thấy 55 trạng thái PASS_FIXTURE và 35 lỗi nội dung fixture, trên 90 lá thư kiểm tra. Vì vậy một lá thư có thể thất bại dù nhiều vùng trong đó đã được xử lý đúng.
Hãy giữ tên trạng thái trong đầu ra của bạn. PASS_FIXTURE nghĩa là trường hợp đáp ứng các kiểm soát của thí nghiệm này; nó không biến trạng thái chung FIXTURE_COPY_NOT_A_SAFETY_CERTIFICATE thành quyền phát hành tài liệu thực.
Lộ trình cũng báo cáo thời gian trung bình khoảng 0,096 giây mỗi tài liệu và kích thước trung bình 54 116 byte mỗi PDF. Các giá trị này mô tả corpus này và máy CPU này. Chúng không đo thời gian của người vận hành và không hứa hẹn hiệu năng tương tự trên bộ sưu tập khác.
Chi phí của việc che quá mức
Phần pixel không nhạy cảm bị che là 0,00871, tức khoảng 0,87 % vùng không nhạy cảm. Đây là chi phí trực quan của lề và dương tính giả: một phần nội dung muốn giữ lại bị làm đen.
Tỷ lệ này không phải tỷ lệ đề xuất sai. Mẫu số của nó là diện tích pixel, không phải số thực thể. Một hình chữ nhật lớn không cần thiết có thể nặng trong phép đo này; nhiều đề xuất nhỏ có thể nặng ít.
Cuối cùng, dựng PDF từ pixel có một chi phí chức năng: văn bản có thể chọn, liên kết hoạt động, biểu mẫu và chữ ký số gốc không được mang nguyên trạng vào bản sao này. Vẫn nhìn thấy hình dạng của một liên kết hay hình vẽ chữ ký không có nghĩa chức năng gốc của chúng còn tồn tại. Khả năng truy cập cần một thiết kế riêng, ngoài bản minh họa này.
Tổng kết trước bài tập
Chúng ta đã liên kết bốn bước không thể thay thế cho nhau: phát hiện một đề cập, phê duyệt một vùng, sửa pixel của nó và kiểm tra bản sao đã tạo.
Mô hình và regex có thể bỏ sót một đề cập. Chính sách có thể phê duyệt một vùng không đủ. Chuyển đổi tọa độ có thể để hở một cạnh. Cuối cùng, cấu trúc PDF đơn giản hóa có thể chứa một ảnh trong đó thông tin nhạy cảm vẫn còn hiển thị.
Lộ trình giúp chúng ta xác định những câu hỏi này thay vì rút chúng xuống thành « PDF đã bị làm đen ». Bài tập giờ sẽ tách riêng hai giả thuyết sai: rằng lớp phủ đồ họa là đủ, và rằng không có văn bản trích xuất được là đủ.
Bài tập: dự đoán trước khi kiểm tra
Trước khi chạy lệnh, hãy đưa ra câu trả lời của bạn cho giả thuyết đầu tiên: « Một lớp phủ đen thêm vào PDF là đủ, vì người đọc thấy một vùng đen. » Theo bạn, việc trích xuất văn bản sẽ trả về gì?
Sau đó đảo ngược lập luận. Nếu chúng ta biến trang thành ảnh và tạo một PDF mới mà không che các pixel, trình trích xuất còn tìm được văn bản không? Và một người sẽ thấy gì khi mở tệp?
Hãy ghi lại dự đoán, rồi chạy kịch bản có sẵn:
docker compose run --rm tutorial python -m pdfredactai.cli exercise --config configs/exercise.yaml
So sánh dự đoán với hai phản ví dụ
Lệnh tạo hai trường hợp hư cấu. Các kết quả dưới đây là những kết quả được báo cáo cho lộ trình tham chiếu:
| Trường hợp | Trích xuất văn bản | Thí nghiệm cho thấy gì |
|---|---|---|
| Lớp phủ đen thêm vào PDF | cover_text_characters: 361 |
Văn bản nguồn vẫn trích xuất được dù có lớp phủ hiển thị. |
| Dựng lại raster không che | Không có ký tự nào trích xuất được | Các pixel nhạy cảm gốc được giữ nguyên chính xác. |
Trong trường hợp đầu, hiển thị là không đủ: lớp đen không xóa văn bản bên dưới. Trong trường hợp thứ hai, trích xuất là không đủ: nó trả về số không vì không tìm được văn bản gốc, chứ không phải vì thông tin đã biến mất khỏi ảnh.
Hai kiểm thử âm tính bác bỏ hai bảo đảm tưởng tượng. Chúng không sửa được cho nhau. Cần kiểm tra các vùng pixel từng pixel và cấu trúc PDF mới, rồi xem mọi thiếu sót trong phát hiện là một thất bại cần xem lại.
Sau đó hãy quay lại 40 đề cập bị che không hoàn toàn của lộ trình chính. Phản ví dụ thứ hai giải thích vì sao một lần trích xuất rỗng không thể khiến chúng được đánh dấu đạt. Chính sự kết hợp giữa quan sát và phép đo này làm hướng dẫn hữu ích.
Tạo hình, kiểm soát các mốc và dọn dẹp
Tạo các đầu ra cuối trước khi xóa chúng
Các hình phải được tạo từ các phép đo hiện có. Chạy hai lệnh này từ thư mục gốc của dự án:
docker compose run --rm tutorial python lesson/figures/plot.py
docker compose run --rm tutorial python scripts/check_checkpoints.py
plot.py dùng đánh giá đã được tạo. check_checkpoints.py kiểm soát các mốc và báo một sự không nhất quán giữa bài học và mã. Việc có từ checkpoint trong tên không có nghĩa chúng ta vừa huấn luyện spaCy: lộ trình này vẫn dùng mô hình cục bộ đã định.
Trước khi dọn dẹp, hãy giữ các kết quả bạn muốn nghiên cứu. Để theo dõi một trường hợp, hãy đặt cạnh nhau outputs/proposals.json, quyết định duyệt, các hình chữ nhật đã áp dụng, ảnh đã che và outputs/transformation.json. Một trung bình tổng thể không cho biết vì sao một lá thư cụ thể thất bại.
Gỡ lỗi đúng chỗ
Thiếu tài nguyên hoặc dấu vân tay của nó khác? Hãy làm lại bước thu thập và kiểm soát môi trường với các tệp đã khóa của bài học. Thay một mô hình khác sẽ thay đổi thí nghiệm ngay trước đề xuất đầu tiên.
Một tên vẫn hiển thị? Trước hết hãy xem có đề xuất nào cho lần xuất hiện này không, rồi xem vùng của nó có được phê duyệt không. Nếu có, hãy kiểm tra phép chiếu từ văn bản sang hộp, tỷ lệ và các hình chữ nhật thực tế đã áp dụng. Mở rộng mọi vùng mà không chẩn đoán có thể che nhiều nội dung hữu ích hơn mà không giải quyết nguyên nhân.
Một phê duyệt không còn khớp tệp? Hãy so sánh nguồn, đề xuất và bản sửa đổi của chúng. Đừng vượt qua kiểm tra dấu vân tay để dùng lại một quyết định đã trở nên không nhất quán.
Trích xuất rỗng, nhưng lá thư vẫn đọc được? Điều này tương thích với một PDF ảnh. Hãy quay lại kiểm soát pixel: bạn vừa gặp chính sự khác biệt được nghiên cứu trong bài tập.
Chỉ dọn các đầu ra đã định
docker compose down
docker compose run --rm tutorial python scripts/clean.py --outputs-only
docker compose down
Lệnh down đầu tiên dừng dự án. Sau đó script dọn dẹp chạy trong một container tạm thời; lệnh down thứ hai để môi trường ở trạng thái dừng. Tùy chọn --outputs-only giữ nguồn và cấu hình, nhưng loại bỏ các tạo phẩm sinh ra có liên quan. Đừng chạy chuỗi này trước khi đã đọc hoặc lưu trữ các đầu ra hữu ích.
Việc dọn dẹp này không được trình bày như xóa an toàn mọi bản sao của một tài liệu. Không nên nhầm nó với chính sách lưu giữ, xóa bản sao lưu hay quản lý quyền.
Những gì cần thiết kế trước khi dùng thực tế
Đừng kết nối trực tiếp chuỗi này với các PDF thực cần phát hành. 40 đề cập bị che không hoàn toàn trong bài kiểm tra là một giới hạn đã đo, không phải một lưu ý thuần lý thuyết.
Để điều chỉnh, hãy bắt đầu bằng một đánh giá riêng, được cho phép và chú thích, đại diện cho các tài liệu liên quan. Xác định các loại cần che và những trường hợp cần leo thang. Giữ nhãn ngoài dịch vụ đề xuất, lập phiên bản chính sách vùng và làm cho các quyết định có thể xem lại. Những công việc này tạo thành một xác thực mới; chúng không tự động kéo dài xác thực của corpus Solstice.
Một lần duyệt thực sự cũng phải cho phép tìm âm tính giả, tức thông tin bị bỏ sót. Chỉ hiển thị các vùng đề xuất không đủ để kiểm tra phần còn lại của trang không có gì cần che. Hãy dự kiến kiểm tra tài liệu, lấy mẫu con người đối với các thiếu sót và các kiểm soát kết xuất độc lập.
Khả năng truy vết cũng phải được bảo vệ. Đoạn propose giữ rõ ràng "text": text[a:b]: tệp đề xuất vì thế có thể chứa các chuỗi bạn muốn che trong bản sao cuối. Đừng tự động phân phối toàn bộ thư mục đầu ra cùng PDF đã dựng lại. Một bản sao được biến đổi đúng không khiến nhật ký hoặc bản xem trước trung gian của nó có thể chia sẻ.
Cuối cùng, hãy xác định chiến lược về khả năng truy cập và kiểm soát mọi biểu diễn bổ sung của tài liệu. Chỉ ảnh không thay thế nội dung văn bản có thể truy cập, nhưng đưa lại văn bản gốc vào bản sao mà không kiểm soát sẽ làm mất mục tiêu che thông tin.
Bài học cần nhớ không phải « raster hóa làm PDF an toàn ». Đúng hơn là: một biến đổi phải được xem xét ở mọi cấp độ nơi thông tin có thể còn tồn tại, với các phép đo cũng làm các thất bại trở nên hiển thị.
Tài liệu tham khảo để tìm hiểu thêm
Các liên kết được tích hợp trong bài viết dẫn đến tài liệu chính thức: spaCy cho thực thể và vị trí của chúng, Python cho từ điển và dấu vân tay, Pillow cho chế độ ảnh và hình chữ nhật, Docker cho kiểm soát cấu hình, và Adobe cho sự khác biệt giữa thông tin hiển thị và ẩn.
Các tài liệu bổ sung này giải thích cơ chế; chúng không phải thử nghiệm mới của dự án. Để tái tạo r001, hãy giữ các phiên bản, tài nguyên đã khóa và lệnh trong kho lưu trữ của nó thay vì thay một phụ thuộc bằng phụ thuộc trong tài liệu hiện hành.
Tải toàn bộ kho lưu trữ của khóa học: ZIP r001.