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

Đưa đúng bộ phận lên đầu kết quả

Trong danh mục phụ tùng, một mã gần tương thích có thể đứng trước bộ phận đúng. Hãy học cách huấn luyện AI đọc lại các trang đã tìm thấy để xếp hạng tốt hơn, rồi kiểm tra kết quả.

Récupérer → Noter → Réordonner. Reranker listwise

Hãy hình dung thanh tìm kiếm của một cửa hàng phụ tùng. Khách hàng mô tả thiết bị, phiên bản và bộ phận cần tìm. Công cụ tìm đúng nhóm sản phẩm… nhưng lại đặt một bộ phận gần tương thích trước bộ phận đáp ứng chính xác yêu cầu.

Vấn đề không còn chỉ là tìm các trang thông tin. Còn cần sắp xếp chúng đúng thứ tự.

Đó là vai trò của reranker, hay mô hình xếp hạng lại: đọc yêu cầu cùng từng ứng viên đã được truy xuất, gán điểm rồi thay đổi thứ tự của chúng. Với một lập trình viên web, ranh giới này giống một hàm sắp xếp mảng do API trả về: hàm có thể di chuyển các phần tử, nhưng không thể tìm lại phần tử mà API chưa từng gửi.

Trong hướng dẫn này, chúng ta sẽ huấn luyện bước xếp hạng lại, hiểu cách tính của nó và đo giới hạn của nó. Một thứ tự xuất sắc không thể tự sửa một danh sách không có câu trả lời liên quan nào.

Tải xuống dự án hướng dẫn hoàn chỉnh

Những gì chúng ta xây dựng

Chúng ta xây dựng một danh mục tổng hợp, tức danh mục hư cấu được tạo cho thí nghiệm. Với mỗi yêu cầu, một phép tìm kiếm từ vựng, dựa trên các từ trong văn bản, lấy về tám trang ứng viên. Một mô hình BERT được dùng làm cross-encoder sau đó gán điểm cho mỗi cặp (requête, document).

Trong lúc huấn luyện, chúng ta gom tám điểm của cùng một truy vấn để học một phân phối ưu tiên các ứng viên liên quan. Đây là cách tiếp cận listwise: hàm học xét cả danh sách, không chỉ từng cặp riêng lẻ.

Ở đầu ra, các định danh ứng viên vẫn giữ nguyên. Vị trí của chúng thay đổi, không phải việc chúng thuộc danh sách nào. Chương trình không viết câu trả lời, không tạo trang sản phẩm và không chứng nhận tính tương thích của một bộ phận thực tế.

Bước truy xuất tạo ra tám ứng viên; cross-encoder tính điểm; một loss listwise thay đổi thứ tự của chúng mà không tạo ứng viên.

Chúng ta sẽ so sánh ba cách xếp hạng: tìm kiếm từ vựng, mô hình được huấn luyện pointwise, rồi mô hình được huấn luyện listwise. Chúng ta cũng sẽ kiểm tra việc tiếp tục sau gián đoạn và đọc kết quả trong outputs/ranking-metrics.json.

Bạn không cần biết BERT hay các hàm mất mát để bắt đầu. Danh sách, hàm, một vài kiến thức Python cơ bản và terminal là đủ để theo dõi phần giải thích. Phần thực hành có thể chạy cũng cần các điều kiện Docker và GPU nêu bên dưới.

Các khái niệm được giải thích

Truy xuất không phải là xếp hạng lại

Truy xuất trả lời câu hỏi: « Trong tất cả các trang, chúng ta sẽ xem những trang nào? » Sau đó, reranking trả lời: « Hiển thị các trang đã chọn theo thứ tự nào? »

Hãy lấy một ví dụ hư cấu, khác với corpus r1. Một người dùng tìm bộ lọc của thiết bị Atlas hư cấu, phiên bản 2024. Để phần giải thích gọn hơn, chúng ta chỉ hiển thị ba ứng viên, trong khi bài thực hành thật dùng tám ứng viên.

Ứng viên minh họa Cách đọc trang Grade được gán cho ví dụ này
A Đáp ứng hoàn toàn yêu cầu 2
B Chỉ đáp ứng một phần tiêu chí 1
C Không đáp ứng yêu cầu 0

Tìm kiếm có thể trả về thứ tự B, C, A. Chúng ta muốn có A, B, C. Tuy nhiên, nếu A không có trong danh sách nhận được thì không phép sắp xếp nào có thể thêm A vào đó.

Các grade là chú thích: những giá trị tham chiếu được xác định để nói điều gì là liên quan. Mô hình học từ các giá trị này; nó không tự tạo chúng để chấm câu trả lời của chính mình. Trong hướng dẫn này, một « dương tính » là ứng viên có grade 1 hoặc 2. Vì vậy, dương tính không nhất thiết có nghĩa là tương thích hoàn toàn.

BERT biến một cặp văn bản thành điểm như thế nào

Mô hình không tính trực tiếp trên chuỗi ký tự. Tokenizer cắt văn bản thành các đơn vị gọi là token, rồi thay chúng bằng các định danh số. Token có thể là một từ, một phần của từ hoặc một ký hiệu: đếm từ không đủ để biết độ dài mô hình sẽ nhận.

Có thể biểu diễn cặp này theo sơ đồ: [CLS] requête [SEP] fiche [SEP]. Đây là sơ đồ đọc, không phải đầu ra token hóa được ghi lại trong thí nghiệm. [SEP] ngăn cách hai văn bản; [CLS] là vị trí đặc biệt mà biểu diễn của nó sẽ được dùng ở đây để tính điểm. Trang BERT mô tả định dạng đầu vào này.

BERT tính các biểu diễn số có xét đến ngữ cảnh của token. Trong cross-encoder của chúng ta, truy vấn và trang được xử lý cùng nhau: vì vậy các từ của chúng có thể được liên hệ trong phép tính này. Sau đó, một đầu tuyến tính biến vector gắn với [CLS] thành một số duy nhất. Hãy hình dung một hàm cuối cùng kết hợp các tọa độ vector bằng các hệ số đã học.

Số này là một điểm, còn gọi là logit trước khi có thể được biến đổi thành phân phối. Nó dùng để xếp hạng các trang của cùng một yêu cầu. Điểm 3 không có nghĩa là « grade 3 », « tương thích gấp ba lần » hay « xác suất tương thích 300 % ».

Một bi-encoder sẽ làm khác: nó mã hóa riêng truy vấn và tài liệu, rồi so sánh vector của chúng. Ngược lại, cross-encoder đọc lại từng cặp cho mỗi ứng viên. Điều đó cho phép so sánh trực tiếp hai văn bản, đổi lại cần thêm chi phí tính toán. Tài liệu CrossEncoder của Sentence Transformers trình bày kiến trúc xếp hạng lại này.

« Listwise » thực sự thay đổi điều gì

Ở đây, BERT vẫn chấm từng cặp riêng. Chúng ta không gửi tám trang trong một văn bản lớn duy nhất. Hàm mất mát mới là phần nhóm các điểm theo truy vấn.

Một mất mát, hay loss, là một số mà quá trình huấn luyện cố giảm xuống. Trong cách tiếp cận pointwise, mỗi cặp đóng góp độc lập vào mục tiêu. Trong cách tiếp cận listwise, điểm của các ứng viên được chuẩn hóa cùng nhau: tăng phần dành cho một ứng viên sẽ thay đổi các phần tương đối của những ứng viên khác.

Mục tiêu phù hợp với nhu cầu của chúng ta: không chỉ nhận ra nhiều trang hữu ích; chúng ta muốn dành vị trí cao hơn cho câu trả lời đầy đủ so với câu trả lời một phần. Lựa chọn này vẫn là giả thuyết cần đánh giá, không phải bảo đảm listwise luôn thắng pointwise.

Tài liệu về các loss CrossEncoder giúp đặt họ phương pháp này vào bối cảnh. Tuy vậy, dự án dùng hàm riêng graded_listwise_loss, được trình bày chi tiết bên dưới. Tên của một họ phương pháp không đủ để biết chính xác công thức của một cách cài đặt.

Từ grade đến phân phối kỳ vọng

Biến đổi được dùng là gain = 2^grade − 1. Với A, B và C, nó cho gain [3, 1, 0]. Sau đó chúng ta chia mỗi gain cho tổng của chúng, là 4: mục tiêu trở thành [0.75, 0.25, 0].

Nói cách khác, phân phối kỳ vọng dành ba phần tư khối lượng cho A và một phần tư cho B. Từ khối lượng ở đây chỉ đơn giản là một phần của tổng bằng 1. Đây không phải xác suất nghiệp vụ: grade 2 của A được biết qua chú thích, không được ước tính như một sự kiện có 75 % khả năng xảy ra.

Mục tiêu không tương ứng với softmax áp dụng lên các grade. Phép tính đúng là: grades → gains → chia cho tổng các gains. Thay đổi biến đổi này sẽ thay đổi mục tiêu học.

Ngược lại, để chuyển điểm của mô hình thành phân phối có thể so sánh, chúng ta dùng softmax: p_i = exp(score_i) / somme_j exp(score_j). Các giá trị dương và tổng của chúng bằng 1. Với ba điểm bằng nhau, mỗi điểm nhận một phần ba. Với tám điểm bằng nhau, mỗi điểm nhận một phần tám.

Việc chuẩn hóa này mang tính tương đối với danh sách. Ngay cả tám trang không phù hợp cũng có thể nhận tám phần có tổng bằng 1. Vì vậy, chỉ softmax không phải là bộ phát hiện « không có bộ phận tương thích ».

Huấn luyện và sử dụng mô hình

Các trọng số là những hệ số số được mạng học. Trong huấn luyện, các điểm được so với mục tiêu; lan truyền ngược tính gradient, tức độ nhạy của loss với các hệ số này. Một bộ tối ưu hóa dùng các gradient để cập nhật tham số có thể huấn luyện.

Khi sử dụng, gọi là suy luận, mô hình tính điểm mà không nhận các grade kỳ vọng để khai thác. Chú thích sau đó dùng để đánh giá thứ hạng, không phải để mách mô hình câu trả lời đúng.

Điều kiện Docker và GPU

Bài thực hành r1 được xác định cho Linux x86_64, Docker Compose, NVIDIA Container Toolkit và RTX 3090 24 Gio. README quy định 16 Gio RAM và 16 Gio ổ đĩa. Tài liệu không mô tả một lộ trình CPU đã được xác thực.

RAM là bộ nhớ của máy; bộ nhớ card đồ họa phục vụ các phép tính GPU. Những mức cấp phát này thuộc dự án: chúng không chứng minh mức tối thiểu phổ quát cho mọi card hay cấu hình.

Docker cung cấp môi trường thực thi. NVIDIA Container Toolkit làm GPU có thể truy cập được từ môi trường này; CUDA là ngăn xếp dùng để thực hiện các phép tính ở đó. Một card hiện diện trong Linux là chưa đủ: kiểm tra CUDA phải thành công trong container trước khi huấn luyện.

Tệp locks/models.lock.json khóa google-bert/bert-base-uncased ở revision 86b5e0934494bd15c9632b12f734a8a67f723594. Tệp locks/environment.lock.json mô tả môi trường đã khóa. Với lập trình viên web, các khóa này giống tệp phụ thuộc được cố định: chúng tránh việc hai cài đặt âm thầm dùng phiên bản khác nhau.

Dịch vụ acquire tải tài sản công khai; dịch vụ gpu chạy mã cục bộ. Hãy giữ các tham chiếu của dự án để tái lập r1: thay mô hình hoặc phụ thuộc sẽ tạo ra thí nghiệm khác.

Dữ liệu và hàm trung tâm

Tách ví dụ trước khi học

Danh mục tổng hợp có 480 tài liệu và 480 truy vấn. Cách tách theo họ gồm 336 cho huấn luyện, 72 cho xác thực và 72 cho kiểm thử. Mỗi danh sách có tám ứng viên. Grade 0, 1 và 2 lần lượt biểu thị không tương thích, tương thích một phần và tương thích hoàn toàn.

Huấn luyện dùng để thay đổi trọng số. Xác thực dùng để xem xét lựa chọn học. Kiểm thử dùng cho phép đo cuối cùng. Việc tách theo họ nhằm tránh các ví dụ rất gần nhau được dùng cả để học lẫn để đánh giá kết quả. Nhãn huấn luyện chỉ có ở các ứng viên train.

Trong một ứng dụng web, có lẽ bạn đã dùng dữ liệu kiểm thử với câu trả lời kỳ vọng đã biết. Nguyên tắc tương tự, với một ràng buộc bổ sung: không huấn luyện mô hình trên những câu trả lời sẽ dùng cho bài kiểm tra cuối.

Đầu vào và đầu ra của hàm

Một tensor là mảng số, có thể có nhiều chiều. Ở đây, scores và grades có một hàng cho mỗi truy vấn và một cột cho mỗi ứng viên. Vì vậy, với một batch gồm B truy vấn của bài thực hành, hình dạng của chúng là B × 8.

mask là mảng boolean có tổ chức tương tự. Giá trị True chỉ vị trí cần tính đến; False chỉ vị trí bị bỏ qua. Bài thực hành công bố tám ứng viên mỗi danh sách: việc tham số này hiện diện trong hàm không có nghĩa dữ liệu của nó nhất thiết chứa vị trí đệm.

Đây là đoạn liên tục từ src/listwise_ranker/losses.py. Nó giả định PyTorch được import với tên torch trong module của nó.

def graded_listwise_loss(scores, grades, mask=None):
    mask = torch.ones_like(grades, dtype=torch.bool) if mask is None else mask
    gains = (torch.pow(2.0, grades) - 1) * mask
    included = gains.sum(-1) > 0
    n = int(included.sum())
    if not n:
        return scores.sum() * 0, {"included": 0, "excluded": scores.shape[0]}
    selected = scores[included].masked_fill(~mask[included], -torch.inf)
    target = gains[included] / gains[included].sum(-1, keepdim=True)
    logp = torch.log_softmax(selected, dim=-1).masked_fill(~mask[included], 0.0)
    return -(target * logp).sum(-1).mean(), {
        "included": n,
        "excluded": scores.shape[0] - n,
    }

Hàm này trả về một loss và các bộ đếm. Nó không token hóa văn bản nào, không gọi BERT, không sắp xếp định danh và không tự cập nhật trọng số. Nó là một bước trong chương trình huấn luyện, không phải toàn bộ chương trình đó.

Theo dõi phép tính, từng dòng một

Tạo mask và gains. Không có mask được cung cấp, torch.ones_like(..., dtype=torch.bool) tính mọi vị trí. Sau đó torch.pow(2.0, grades) - 1 biến grade thành gain. Nhân chúng với mask triệt tiêu phần đóng góp của các vị trí bị bỏ qua.

Chọn các danh sách có thể dùng. gains.sum(-1) cộng gain trên mỗi hàng: -1 chỉ chiều cuối, chiều của ứng viên. included giữ các hàng có tổng này dương nghiêm ngặt. Vì vậy bộ đếm n là số danh sách được đưa vào, không phải số tài liệu dương tính.

Danh sách có tám grade 0 không có phân phối mục tiêu để chuẩn hóa: tổng gain của nó bằng không. Nó bị loại khỏi loss. Điều đó không có nghĩa phải làm nó biến mất khỏi đánh giá đầu-cuối.

Loại các vị trí bị mask. Với danh sách được chọn, masked_fill thay điểm bị bỏ qua bằng -torch.inf, tức âm vô cùng. Trong softmax, số mũ của chúng bằng không: những vị trí này không nhận phần nào của phân phối.

Tạo mục tiêu. Gain của mỗi hàng được chia cho tổng của chúng. keepdim=True giữ một chiều rộng 1 để mỗi hàng được chia cho tổng riêng. Ví dụ [3, 1, 0] của chúng ta đúng là cho [0.75, 0.25, 0].

Tính logarit của các phần dự đoán. torch.log_softmax trực tiếp tạo logarit của softmax. Cách này tốt hơn việc tính softmax rồi logarit riêng để ổn định số, như tài liệu PyTorch giải thích.

masked_fill thứ hai thay các giá trị ở vị trí bị bỏ qua trong logp bằng 0. Tại sao phải mask lại? Vì phép nhân 0 × (−inf) không cho một số 0 dùng được: nó có thể tạo NaN, giá trị « không phải số ». Vị trí bị bỏ qua phải đóng góp đúng bằng không vào tích cuối.

Cộng rồi lấy trung bình. -(target * logp).sum(-1) tính một loss theo danh sách, ở đây gọi là entropy chéo. Trung bình cuối cùng chỉ áp dụng cho các danh sách được đưa vào. Các bộ đếm included và excluded cho phép theo dõi quần thể này.

Grade 0 không phải vị trí vắng mặt

Cần phân biệt hai tình huống. Ứng viên grade 0 với mask True vẫn là đối thủ trong softmax. Gain mục tiêu của nó bằng không, nhưng dành cho nó một phần lớn sẽ lấy phần đó khỏi các ứng viên dương và làm loss tăng. Vị trí có mask False bị rút khỏi phép so sánh.

Hãy lấy lại ba điểm bằng nhau và grade [2, 1, 0]. Không mask, xác suất là [1/3, 1/3, 1/3]. Nếu chỉ mask ứng viên thứ ba, hai phần còn lại thành [0.5, 0.5]. Loại ứng viên âm khỏi mẫu số không hề trung tính: điều đó thay đổi bài toán chúng ta yêu cầu mô hình thực hiện.

Cũng có một trường hợp biên: nếu mask loại mọi ứng viên dương trong một hàng, hàng đó bị loại khỏi loss, dù các grade ban đầu của nó có ứng viên dương.

Tính loss bằng tay

Trong ví dụ minh họa A, B, C, mục tiêu là [0.75, 0.25, 0]. Với điểm hữu hạn và vị trí hoạt động, công thức trở thành: L = −0.75 × ln(p_A) − 0.25 × ln(p_B). ln là logarit tự nhiên. Mục tiêu bằng không của C không cho C một hạng riêng, nhưng C vẫn nằm trong mẫu số softmax.

Các phân phối dưới đây được chọn để giải thích; chúng không phải dự đoán được ghi lại trong r1.

Phần dự đoán A, B, C Loss xấp xỉ Cách đọc
Một phần ba, một phần ba, một phần ba 1,098612 Ba ứng viên nhận cùng một phần
0,74 ; 0,25 ; 0,01 0,572402 Phân phối gần với mục tiêu
0,95 ; 0,04 ; 0,01 0,843189 A được ưu tiên, nhưng B nhận quá ít phần

Tuy vậy, hai phân phối cuối cho cùng một thứ tự: A, B, C. Vì thế loss không chỉ xem ai đứng đầu. Nó so sánh toàn bộ phân phối với phân phối được xác định bởi các grade.

Một hệ quả khác: một loss tốt không nhất thiết phải đạt về không. Với mục tiêu [0.75, 0.25, 0], cận dưới là −0.75 × ln(0.75) − 0.25 × ln(0.25) ≈ 0,562335. Với các điểm hữu hạn, phần của C vẫn dương nghiêm ngặt; cận này được tiến gần khi phần đó tiến về không. Tìm một loss bằng không ở đây sẽ là chờ đợi kết quả sai.

Tìm hiểu thêm: một ứng viên dương có thể nhận gradient làm giảm điểm

Với một danh sách được đưa vào duy nhất, gradient theo mỗi điểm bằng phần dự đoán − phần mục tiêu. Với ba điểm bằng nhau, do đó nó xấp xỉ [-0.416667, +0.083333, +0.333333].

Một bước hạ gradient thực hiện trực tiếp trên các điểm này sẽ tăng A và giảm B, C. B vẫn dương tính. Đây không phải lỗi chú thích: hiện tại B nhận một phần ba tổng, trong khi mục tiêu chỉ dành cho nó một phần tư. Phép tính tìm cách sửa sự đại diện quá mức này, không phải biến B thành ứng viên không tương thích. Sau đó, mạng thực tế chuyển các gradient này thành thay đổi trên các trọng số dùng chung; mỗi điểm không phải tham số độc lập.

Trường hợp không danh sách nào đóng góp

Khi n bằng không, scores.sum() * 0 trả về số không liên kết với phép tính điểm, với gradient bằng không khi các điểm và tổng của chúng hữu hạn. Các bộ đếm báo rằng mọi danh sách đã bị loại.

Giá trị trả về này không có nghĩa huấn luyện vừa thành công hay bộ tối ưu hóa phải tiến hành: quyết định tiếp tục hoặc bỏ qua cập nhật thuộc về vòng lặp gọi hàm. Đoạn trích không chứa optimizer.step().

Cuối cùng, hàm này không phải trình xác thực đầu vào tổng quát. Nó không kiểm tra rõ ràng mọi hình dạng, tính hữu hạn của điểm hay việc các grade thuộc {0, 1, 2}. Cần giữ các hợp đồng và kiểm tra của dự án xung quanh nó, thay vì cho rằng nó nhận bất kỳ mảng nào.

Cài đặt và chuẩn bị danh sách

Kiểm tra môi trường trước khi động đến trọng số

Tải ZIP, giải nén và đặt terminal ở thư mục gốc. Mọi lệnh sau đều bắt đầu từ cùng thư mục này. Chúng tuân theo lộ trình r1 trong README.md và lesson/tutorial.json.

sh scripts/setup.sh
docker compose config --quiet
docker compose build acquire gpu
docker compose run --rm acquire python scripts/acquire.py --config configs/minimum.yaml
docker compose run --rm gpu python scripts/check_environment.py --config configs/minimum.yaml
docker compose run --rm gpu python -m pytest -q

setup.sh chuẩn bị dự án. docker compose config --quiet kiểm tra cấu hình Compose, rồi build xây dựng ảnh của các dịch vụ acquire và gpu. Bước acquire tải tài sản công khai theo các khóa. Sau đó script check_environment.py kiểm tra quyền truy cập CUDA trong container.

Lệnh cuối chạy bộ test của dự án. Các test này kiểm tra cơ chế chương trình; chúng chưa có nghĩa một trang sẽ liên quan với yêu cầu thật của khách hàng. Ở giai đoạn này, mục tiêu là có môi trường phù hợp và các kiểm tra không lỗi trước khi huấn luyện.

Dạng lặp docker compose run --rm gpu … chạy một lệnh trong dịch vụ gpu, rồi xóa container tạm khi lệnh kết thúc. Điều đó không có nghĩa bạn phải tự vào container để gõ phần còn lại.

Chuẩn bị, kiểm tra và xác minh công thức

Bây giờ chúng ta có thể xây dựng danh sách, xem mô hình nhận gì và kiểm tra loss bằng những số đã biết.

docker compose run --rm gpu python -m listwise_ranker.cli prepare --config configs/minimum.yaml
docker compose run --rm gpu python -m listwise_ranker.cli inspect-pairs --config configs/minimum.yaml
docker compose run --rm gpu python -m listwise_ranker.cli check-loss --config configs/minimum.yaml

Sau prepare, hãy xem các partition và tám ứng viên của mỗi danh sách. Bạn phải có thể liên hệ một truy vấn, các định danh ứng viên và grade được dùng trong giao thức. Mối liên hệ này phải ổn định: nếu một cột đổi tài liệu mà grade không đi theo, chương trình sẽ học ưu tiên nhầm ứng viên.

inspect-pairs làm rõ cắt ngắn theo độ dài cố định cho mô hình. Cắt ngắn nghĩa là bỏ một phần token khi cặp quá dài. Giới hạn áp dụng cho đầu vào của cặp, không chỉ số từ trong trang. Giá trị dùng ở r1 phải được đọc trong cấu hình ZIP.

Trong ví dụ hư cấu, một câu cuối như « chỉ dành cho phiên bản 2023 » có thể quyết định. Nếu bị bỏ trước BERT, nó không góp vào điểm. Khi đó mô hình có thể chấm một đầu vào thiếu thông tin thiết yếu, dù trang gốc có chứa thông tin đó.

check-loss kiểm tra phép tính trên các tensor đã biết. Hãy liên hệ kết quả của nó với biến đổi grade và chuẩn hóa gain trước khi bắt đầu huấn luyện. Kiểm tra công thức và kiểm tra token hóa là hai kiểm tra bổ sung: công thức đúng không khôi phục văn bản đã bị cắt ngắn.

Huấn luyện, tiếp tục và đo lường

So sánh hai mục tiêu học

Lệnh đầu tiên huấn luyện biến thể listwise. Lệnh thứ hai chạy biến thể pointwise. Ba lệnh sau tạo một lần chạy bị gián đoạn, tiếp tục nó và so sánh việc tiếp tục.

docker compose run --rm gpu python -m listwise_ranker.cli train --config configs/minimum.yaml
docker compose run --rm gpu python -m listwise_ranker.cli train --config configs/minimum.yaml --variant pointwise
docker compose run --rm gpu python -m listwise_ranker.cli train --config configs/minimum.yaml --stop-after 20 --run-name interrupted
docker compose run --rm gpu python -m listwise_ranker.cli train --config configs/minimum.yaml --resume artifacts/checkpoints/interrupted/state.pt --run-name resumed
docker compose run --rm gpu python -m listwise_ranker.cli compare-resume --config configs/minimum.yaml

Hãy giữ thứ tự này: resumed phụ thuộc vào tệp artifacts/checkpoints/interrupted/state.pt do interrupted tạo. Tùy chọn --stop-after 20 ở đây đặt điểm gián đoạn của lần thử; tự nó không cho biết tổng số lần cập nhật của công thức.

Checkpoint là điểm lưu của quá trình huấn luyện. Một lần tiếp tục hoàn chỉnh không chỉ là nạp trọng số để dự đoán: nó phải khôi phục trạng thái cần thiết để tiếp tục phép tính. Bài thực hành dự kiến kiểm tra này bằng compare-resume. Hãy xem đầu ra của nó để biết kết quả lần chạy của bạn; các phép đo xếp hạng bên dưới không thay thế việc kiểm tra tiếp tục này.

Biến thể pointwise dùng để so sánh. Mã loss của nó không được đưa lại trong bài viết này: đừng suy ra một công thức chính xác chỉ từ từ « pointwise ». Để nghiên cứu khác biệt cài đặt này, hãy dùng mã trong kho lưu trữ, giữ cấu hình tham chiếu.

Đo lô dữ liệu, rồi giải thích một trường hợp

Khi huấn luyện và tiếp tục đã được kiểm tra, hãy đánh giá các thứ hạng trước khi dừng lâu ở một tìm kiếm đặc biệt thuyết phục.

docker compose run --rm gpu python -m listwise_ranker.cli evaluate --config configs/minimum.yaml
docker compose run --rm gpu python -m listwise_ranker.cli inspect-case --config configs/minimum.yaml

outputs/ranking-metrics.json chứa các tổng hợp, tức các phép đo tóm tắt nhiều truy vấn. inspect-case sau đó giúp đọc lại một trường hợp riêng lẻ.

Với trường hợp này, hãy đối chiếu yêu cầu, các trang, grade của chúng và vị trí trước, sau xếp hạng lại. Kiểm tra định danh vẫn giữ nguyên. Trường hợp giải thích một phần kết quả; nó không đủ để kết luận về trung bình của 72 truy vấn kiểm thử.

Các chỉ số trước các phép đo

Coverage@8: có ít nhất một ứng viên liên quan không?

Coverage@8 là tỷ lệ truy vấn mà tám ứng viên có ít nhất một ứng viên dương:

coverage@8 = số danh sách có ít nhất một grade dương / tổng số truy vấn.

Phép đo này chỉ trả lời câu hỏi về sự hiện diện. Danh sách mà ứng viên dương duy nhất đứng thứ tám vẫn được phủ. Danh sách chỉ chứa tương thích một phần grade 1 cũng vậy. Vì vậy coverage không có nghĩa là « bộ phận đúng đứng đầu », « đã tìm thấy mọi bộ phận liên quan » hay « tương thích hoàn toàn ».

Trong ví dụ minh họa có bốn yêu cầu, trong đó ba yêu cầu có ít nhất một ứng viên dương, coverage bằng 3 / 4 = 0,75. Sắp xếp lại các danh sách này không đổi con số đó.

NDCG@5: chất lượng ở các vị trí đầu là gì?

NDCG, viết tắt của normalized discounted cumulative gain, đo gain liên quan tích lũy, được giảm theo vị trí rồi chuẩn hóa. Đằng sau cái tên này có ba ý đơn giản: trang liên quan hơn mang lại nhiều hơn; trang đặt thấp hơn mang lại ít hơn; kết quả được so với một thứ hạng lý tưởng.

Hậu tố @5 cho biết chỉ năm vị trí đầu góp vào điểm quan sát. Với các grade không âm và một lý tưởng được xác định trên cùng tập tham chiếu, 1 tương ứng với lý tưởng và 0 với không gain trong năm vị trí đầu.

Hãy tính trên ví dụ minh họa của chúng ta. Chúng ta chọn gain 2^grade − 1 và giảm theo hạng 1 / log2(hạng + 1), một quy ước được ghi trong TensorFlow Ranking. Tham chiếu này giải thích chỉ số; nó không thêm phụ thuộc TensorFlow nào vào dự án.

Theo quy ước này, DCG@5 = tổng gain ở năm hạng đầu, mỗi gain chia cho log2(hạng + 1). log2 là logarit cơ số 2: ở hạng 1, mẫu số bằng 1; ở hạng 3, bằng 2. Vì vậy grade 2 mang lại 3 ở vị trí đầu, nhưng chỉ 1,5 ở vị trí thứ ba.

Với tám grade [2, 1, 0, 0, 0, 0, 0, 0], thứ tự lý tưởng là:

IDCG@5 = 3 / log2(2) + 1 / log2(3) ≈ 3,630930.

Sau đó NDCG là DCG / IDCG. Đây là vài hoán vị của cùng các grade, chỉ được tính để giải thích:

Grade theo thứ tự đề xuất Coverage@8 cho truy vấn này NDCG@5 minh họa
2, 1, 0, 0, 0, 0, 0, 0 1 1,000000
1, 2, 0, 0, 0, 0, 0, 0 1 0,796708
0, 1, 2, 0, 0, 0, 0, 0 1 0,586883
0, 0, 0, 0, 0, 2, 1, 0 1 0,000000

Dòng cuối rất quan trọng: các ứng viên liên quan hiện diện, nhưng quá thấp để góp vào top 5. Vì vậy reranker có cơ hội cải thiện, khác với trường hợp không ứng viên dương nào nằm trong các ứng viên của nó.

Phép tính đầy đủ của chỉ số dự án không có trong đoạn Python trước: đoạn đó tính loss huấn luyện. Để điều chỉnh đánh giá, cũng hãy kiểm tra lý tưởng được xây dựng trên những tài liệu nào. Sắp xếp lý tưởng các ứng viên có sẵn và so với mọi mục liên quan trong danh mục không phải lúc nào cũng cho cùng mẫu số. Các phép tính nhỏ bên trên dùng rõ ràng tám grade hiển thị làm tập tham chiếu.

Cuối cùng, hai thứ tự giống nhau có thể có cùng NDCG và các loss khác nhau. Đó là trường hợp của phân phối [0.74, 0.25, 0.01] và [0.95, 0.04, 0.01]: cả hai xếp A, B, C, nhưng không phân bổ điểm theo cùng cách. Loss dùng để học; chỉ số dùng để đánh giá kết quả theo một tiêu chí đã cho.

Có điều kiện và đầu-cuối: hai trung bình, hai mẫu số

NDCG@5 có điều kiện chỉ được tính trên danh sách được phủ, tức nơi reranker có ít nhất một ứng viên dương. Nó mô tả thứ hạng trong các tình huống có tài liệu liên quan sẵn có.

NDCG@5 đầu-cuối cũng giữ các danh sách không có ứng viên dương trong mẫu số. Theo giao thức trình bày, chúng đóng góp bằng không. Lựa chọn này làm rõ phần yêu cầu mà hệ thống không đưa ra câu trả lời liên quan nào.

Hãy lấy lại bốn yêu cầu hư cấu. Nếu ba danh sách được phủ đều đạt NDCG bằng 1, trung bình có điều kiện của chúng là 1. Nhưng trên bốn yêu cầu, trung bình là (1 + 1 + 1 + 0) / 4 = 0,75.

Với trung bình không trọng số và quy ước bằng không cho danh sách không được phủ, ta có:

NDCG đầu-cuối = coverage × NDCG có điều kiện.

Vì NDCG có điều kiện không vượt quá 1, coverage đặt một trần cho điểm đầu-cuối. Trần này là cận; nó không tự động đạt được với mọi mô hình hay mọi cách chọn thứ hạng lý tưởng.

Một truy vấn không có câu trả lời cần trạng thái rõ ràng

Cần tiếp tục phân biệt hai lý do có thể khiến danh sách không có ứng viên dương: trang đúng tồn tại trong danh mục nhưng không được truy xuất, hoặc không trang nào trong danh mục đáp ứng yêu cầu.

Trường hợp đầu, cải thiện truy xuất có thể giúp. Trường hợp hai, cũng cần biết cách báo không có câu trả lời. Giao thức được chọn giữ các trường hợp này trong điểm đầu-cuối; đây không phải quy ước phổ quát mà một mình nó có thể chẩn đoán nguyên nhân.

Trong 72 truy vấn, chín truy vấn không có ứng viên dương trong tám ứng viên: reranking tốt nhất đạt NDCG@5 đầu-cuối là 0,875.

Kết quả thực tế

Các phép đo r1

Kiểm thử gồm 72 truy vấn, trong đó 63 danh sách được phủ. Báo cáo mô tả 9 trường hợp còn lại là « không có ứng viên dương toàn cục ». Coverage@8 bằng 63 / 72 = 0,875.

Cách diễn đạt này quan trọng: đừng biến chín trường hợp đó thành chín bộ phận đúng chắc chắn hiện diện trong danh mục nhưng bị tìm kiếm bỏ lỡ. Số danh sách không được phủ không đủ để chứng minh chẩn đoán đó.

Đây là các phép đo đã công bố, giữ nguyên độ chính xác gốc:

Xếp hạng NDCG@5 có điều kiện NDCG@5 đầu-cuối
Từ vựng 0,9903969127585871 0,8665972986637638
Pointwise 0,8838329034231467 0,7733537904952533
Listwise 1,0 0,875

Coverage@8 vẫn là 0,875: các reranker làm việc trên cùng ứng viên. Mô hình listwise ở đây đạt thứ tự có điều kiện hoàn hảo theo chỉ số đã dùng. Tuy nhiên, trung bình đầu-cuối vẫn bị giới hạn: (63 × 1 + 9 × 0) / 72 = 0,875.

Giá trị của các phương pháp khác theo cùng quan hệ, sai khác chỉ do làm tròn số. Với pointwise, 0,8838329034231467 × 0,875 ≈ 0,7733537904952533. Không có mâu thuẫn giữa hai điểm khác nhau: chúng không chia cho cùng quần thể.

Kết quả hữu ích không phải là « AI thắng tìm kiếm »

Tìm kiếm từ vựng đã rất mạnh trên corpus này: NDCG có điều kiện của nó xấp xỉ 0,9904. Mô hình listwise cải thiện kết quả đó, nhưng biên độ còn lại nhỏ. Trừ các phép đo đã công bố cho thấy mức tăng xấp xỉ 0,009603 trên NDCG có điều kiện và 0,008403 trên điểm đầu-cuối. Đây là chênh lệch điểm được tính, không phải phép đo mới hay tỷ lệ phần trăm bộ phận tương thích.

Ngược lại, biến thể pointwise xếp thấp hơn tham chiếu từ vựng trong thí nghiệm này. Vì vậy, thêm và huấn luyện mô hình không phải cải thiện theo định nghĩa. Cần giữ một tham chiếu đơn giản để kiểm tra bước mới thực sự mang lại gì.

Bảng cũng không cho phép khẳng định listwise luôn cao hơn pointwise. Nó mô tả corpus tổng hợp này, mô hình này và công thức này. Nó không đưa phép đo độ trễ khi tải cao hay bằng chứng khái quát hóa sang danh mục khác.

Điểm hoàn hảo không bảo đảm điều gì

NDCG có điều kiện bằng 1 nghĩa là các vị trí đầu thỏa lý tưởng mà đánh giá chọn cho các danh sách được phủ. Nó không phải chứng nhận tương thích vật lý, không bảo đảm không có trường hợp không trả lời, cũng không chứng minh chú thích hoàn hảo.

Vì vậy bài học chính có hai mặt: học thứ tự có thể giúp, nhưng thứ tự vẫn bị giới hạn bởi các ứng viên và định nghĩa về tính liên quan. Truy xuất, học và đánh giá đều có vai trò; một con số tốt ở một bước không thay thế các bước khác.

Bài tập: dự đoán, rồi thực thi

Bỏ ứng viên tốt nhất

Hãy hình dung chúng ta loại A, tài liệu tốt nhất trong ví dụ, nhưng giữ định danh của các ứng viên khác. Không chạy chương trình, hãy đưa ra dự đoán: huấn luyện thêm hoặc điểm rất cao có thể làm A xuất hiện lại trong thứ hạng của danh sách này không?

Kịch bản được cung cấp thực hiện so sánh này mà không yêu cầu bạn sửa công cụ tham chiếu.

docker compose run --rm gpu python -m listwise_ranker.cli exercise --config configs/exercise.yaml

Đầu ra xác nhận reranker nhận và trả về cùng ứng viên: nó có thể sắp xếp lại, nhưng không bao giờ truy xuất định danh vắng mặt. Để kiểm tra đặc tính này, hãy so sánh định danh của danh sách sau khi loại bỏ với định danh của thứ hạng cuối. Đừng so chúng với danh sách ban đầu mà quên việc loại bỏ có chủ ý.

Hãy chú ý dự đoán thứ hai: loại tài liệu tốt nhất không nhất thiết làm danh sách hoàn toàn không liên quan. Nếu B grade 1 còn hiện diện, danh sách vẫn được phủ. Nếu ta đã loại ứng viên dương duy nhất, nó không còn được phủ. Trong cả hai trường hợp, tài liệu tốt nhất vắng mặt vẫn không thể tìm lại chỉ bằng xếp hạng lại.

Sự phân biệt này giải thích vì sao bài tập trước hết kiểm tra một tính chất của định danh. Một thứ tự tốt hơn của các ứng viên còn lại không chứng minh truy xuất đã được sửa.

Giữ các artefact trước khi kết thúc

Một artefact là tệp do bài thực hành tạo ra: hình, phép đo hay bản lưu huấn luyện. Hãy kết thúc bằng việc tạo hình, kiểm tra checkpoint, xem trước việc dọn dẹp và dừng dịch vụ, theo thứ tự đó.

docker compose run --rm gpu python lesson/figures/plot.py
docker compose run --rm gpu python scripts/check_checkpoints.py
docker compose run --rm gpu python scripts/clean.py --scope generated --dry-run
docker compose down --remove-orphans

Checkpoint được kiểm tra sau bài tập. --dry-run yêu cầu một bản xem trước: clean.py cho biết nó sẽ nhắm đến gì mà không xóa tệp. Lệnh docker compose down --remove-orphans kết thúc các dịch vụ dự án; các tệp bài thực hành giữ trên máy chủ vẫn sẵn có.

Thích nghi, giới hạn, khắc phục sự cố và dọn dẹp

Chuyển từ danh mục hư cấu sang các trang của bạn

Bước đầu tiên không phải đổi BERT. Đó là viết định nghĩa về tính liên quan đã được những người hiểu danh mục xem lại. « Tương thích một phần » chính xác nghĩa là gì? Có phải kết quả cần hiển thị kèm dè dặt, một lựa chọn thay thế hữu ích hay bộ phận phải loại khỏi mọi đề xuất mua? Cấp độ được chọn cho hướng dẫn không thay thế quyết định nghiệp vụ này.

Hãy phiên bản hóa các trang, truy vấn, chú thích và partition. Văn bản có thể thay đổi trong khi định danh vẫn ổn định; không có phiên bản danh mục, bạn có nguy cơ so sánh điểm cũ với trang mới. Cũng hãy giữ danh sách ứng viên để phân biệt thay đổi thứ hạng với thay đổi truy xuất.

Đo riêng coverage, hạng, cắt ngắn và chi phí reranking. Nếu tăng số ứng viên, bạn thay đổi cả những gì mô hình có thể truy cập lẫn số cặp cần xử lý. Kết quả mới phải được đánh giá như thí nghiệm mới, không thêm vào số liệu r1 như thể nó mô tả cùng giao thức.

Kiểm tra các ngôn ngữ và thông tin thực sự được đọc

Trang BERT base uncased nêu việc tiền huấn luyện bằng tiếng Anh và xử lý không phân biệt chữ hoa chữ thường. Vì vậy tên « BERT » không bảo đảm tự thích nghi với yêu cầu tiếng Pháp, tham chiếu kỹ thuật hay mọi quy ước của danh mục bạn. Ví dụ tiếng Pháp trong phần giải thích không phải kết quả đa ngôn ngữ đã đo.

max_length của cross-encoder cũng có thể bỏ đi phần quyết định của một trang. Văn bản hiện trong tệp nguồn không nhất thiết được mạng nhìn thấy hoàn toàn sau token hóa. Hãy kiểm tra các cặp trước khi quy lỗi chỉ cho việc học.

Không nhầm lẫn xếp hạng với quyết định

Cơ chế trả về một thứ tự, không phải quyền mua hay lắp đặt. Hãy kiểm soát các trường hợp rủi ro cao trước mọi tự động hóa. Một hệ thống thực tế đặc biệt phải xử lý yêu cầu không có câu trả lời, chú thích không chắc chắn và quy tắc cấm một số thay thế.

Mục tiêu listwise yêu cầu phân phối tương đối giữa các ứng viên; ở đây nó không học ngưỡng tương thích phổ quát. Thiết kế việc từ chối hoặc xác thực bởi con người cần dữ liệu và kiểm soát riêng, khác với chỉ cải thiện NDCG.

Khắc phục sự cố mà không âm thầm thay đổi thí nghiệm

GPU không hiển thị. Chạy lại kiểm tra môi trường trước huấn luyện, rồi kiểm tra tích hợp Docker bằng hướng dẫn NVIDIA Container Toolkit. Đừng âm thầm thay thực thi GPU bằng lộ trình CPU chưa được xác thực.

Acquisition thất bại. Kiểm tra quyền truy cập mạng của dịch vụ acquire, sửa nguyên nhân thất bại rồi chạy lại chính lệnh đó. Đổi revision mô hình để vượt qua tài sản thiếu sẽ làm mất tham chiếu của thí nghiệm.

Loss có vẻ thấp bất thường. Hãy đọc lại included và excluded trước khi kết luận. Batch hoàn toàn bị loại cho số không mà không cung cấp giám sát. Mask loại những ứng viên gây khó cũng có thể làm mục tiêu dễ giả tạo.

NDCG có điều kiện rất tốt, nhưng điểm tổng bị chặn. Hãy xem coverage và các yêu cầu không có ứng viên dương. Đừng yêu cầu reranker trả lại trang nó chưa nhận, hay tạo bộ phận vắng mặt trong danh mục.

Bạn muốn dọn các lần thử. Lệnh cuối bài thực hành đã có trong phần bài tập. clean.py --dry-run không xóa kết quả nào; docker compose down --remove-orphans không thay thế việc dọn các tệp cục bộ của bạn. Hãy xem xét và giữ các phép đo hữu ích trước mọi thao tác xóa riêng biệt.

Như vậy, chúng ta có một bài thực hành nối nhu cầu giao diện — trình bày kết quả tốt hơn — với công thức học có thể kiểm chứng. Kết luận vẫn rất cụ thể: một reranker tốt đặt đúng ứng viên vào đúng vị trí; nó không bao giờ miễn cho bạn việc kiểm tra những ứng viên nào đã sẵn có.

Nguồn chính thức

Các tham chiếu sau dùng để tìm hiểu sâu hơn các khái niệm. Để tái lập các phép đo r1, hãy giữ phiên bản và cấu hình của kho lưu trữ: tài liệu bên ngoài không thay thế chúng.

Chia sẻ bài viết