Phát hiện trùng lặp tương tự bằng độ tương đồng Jaccard và MinHash
(blog.nelhage.com)- Trong các bộ sưu tập tài liệu quy mô lớn, khi crawl web có thể lấy cùng một trang nhiều lần hoặc lẫn vào các bản chỉ chỉnh sửa nhỏ, vì vậy độ tương đồng Jaccard và MinHash là cách thực tế để tìm các tài liệu “gần như giống nhau”
- Độ tương đồng Jaccard chuyển tài liệu thành tập đặc trưng rồi tính kích thước giao / kích thước hợp, và xem các cặp vượt ngưỡng là trùng lặp tương tự, nhưng quan hệ này nhìn chung không có tính bắc cầu
- Nếu so sánh mọi cặp tài liệu thì chi phí sẽ là O(n²) theo kích thước corpus, nên MinHash tóm tắt mỗi tài liệu thành chữ ký kích thước cố định để xấp xỉ độ tương đồng theo xác suất
- Khi dùng
khàm băm, có thể ước lượng độ tương đồng bằng tỷ lệ các vị trí trùng khớp trong chữ ký của hai tài liệu, và các điều kiện như min-wise independence là quan trọng trong việc chọn hàm băm - Nếu dùng toàn bộ chữ ký hoặc một phần chữ ký làm khóa nhóm, có thể điều chỉnh xác suất tài liệu tương tự rơi vào cùng một bucket; cách chọn n-gram và tokenization sẽ quyết định độ nhạy phát hiện và chi phí
Khó khăn của việc phát hiện trùng lặp tương tự
- Mục tiêu là tìm trong một tập tài liệu lớn những tài liệu không hoàn toàn giống nhau nhưng gần như giống nhau
- Khi crawl web theo chu kỳ, có thể lấy cùng một trang nhiều lần nhưng metadata hơi khác nhau
- Cũng có thể tồn tại nhiều bản chỉnh sửa nhỏ của cùng một trang
- Cách tiếp cận cơ bản là định nghĩa một hàm độ tương đồng
S(A, B)giữa hai tài liệu, rồi coi các cặp có giá trị từ ngưỡngScrittrở lên là trùng lặp tương tự - “Gần như giống hệt” nói chung không phải là quan hệ bắc cầu
AvàB,BvàCcó thể tương tự vượt ngưỡng- Nhưng đồng thời
AvàCcó thể thấp hơn ngưỡng - Vì vậy, phát hiện trùng lặp tương tự ở quy mô lớn khó xử lý hơn phát hiện trùng lặp chính xác
Định nghĩa độ tương đồng Jaccard
-
Jaccard index biểu diễn độ tương đồng của hai tập hữu hạn bằng kích thước giao / kích thước hợp
[ J(A, B) = \frac{|A \cap B|}{|A \cup B|} ]
-
Nếu hai tập giống nhau, chúng sẽ chia sẻ phần lớn phần tử nên hợp chỉ tăng ít còn giao chỉ giảm ít
-
Nếu hai tập tách biệt hoàn toàn thì kích thước giao là 0, nên độ tương đồng Jaccard bằng 0
-
Nếu hai tập giống hệt nhau thì giao và hợp đều là cùng một tập, nên độ tương đồng Jaccard bằng 1
-
Tài liệu thực tế có dạng như chuỗi Unicode, nên trước hết cần chuyển tài liệu thành tập đặc trưng
Vấn đề mở rộng khi so sánh mọi cặp
- Sau khi chuyển tài liệu thành tập đặc trưng, định nghĩa tìm các cặp có độ tương đồng Jaccard cao tự nó khá đơn giản
- Nhưng nếu so sánh mọi cặp tài liệu thì chi phí tăng thành O(n²) theo kích thước corpus
- Với phát hiện trùng lặp chính xác, người ta băm tài liệu rồi gom các tài liệu vào cùng bucket băm để tránh chi phí này
- Phát hiện trùng lặp tương tự cũng cần một đường vòng tương tự; trong lĩnh vực này, nó được gọi là locality-sensitive hash
- Với độ tương đồng Jaccard, có một kỹ thuật phù hợp cho mục đích này, và cốt lõi của nó là MinHash
Xấp xỉ độ tương đồng Jaccard bằng MinHash
-
MinHash xấp xỉ độ tương đồng Jaccard chỉ bằng một chữ ký (signature) nhỏ được tính trước cho mỗi tài liệu, thay vì phải so sánh toàn bộ tập mỗi lần
-
Ý tưởng cơ bản là lấy ngẫu nhiên đồng đều một phần tử từ hợp và xem phần tử đó có thuộc giao hay không
-
Trên thực tế, thay vì hoán vị ngẫu nhiên, người ta dùng một hàm băm tốt
H(x)và lưu đặc trưng có giá trị băm nhỏ nhất trong mỗi tập[ a_{min} \leftarrow \min_{x \in A} H(x) ]
[ b_{min} \leftarrow \min_{x \in B} H(x) ]
-
Phép
mincó tính kết hợp, nên có thể tiền xử lý độc lập giá trị băm nhỏ nhất của từng tài liệu -
Xác suất giá trị băm nhỏ nhất của hai tập bằng nhau chính bằng độ tương đồng Jaccard của chúng
Nhiều hàm băm và vector chữ ký
-
Nếu chỉ dùng một hàm băm thì chỉ có thể ước lượng kiểu boolean “giống/không giống” cho hai tài liệu
-
Nếu dùng
khàm băm khác nhau, có thể tóm tắt mỗi tài liệu thành một vector gồmkgiá trị MinHash[ A_{sig} = (\min_{x \in A} H_1(x), \min_{x \in A} H_2(x), \dots, \min_{x \in A} H_k(x)) ]
-
Xấp xỉ độ tương đồng Jaccard bằng tỷ lệ các vị trí có cùng giá trị trong hai chữ ký
[ J(A, B) \approx \frac{1}{k} \sum_{i=1}^{k} (A_{sig}[i] = B_{sig}[i]) ]
-
Việc chọn họ hàm băm có những điểm tinh tế
- Mục tiêu là xấp xỉ một hoán vị ngẫu nhiên trên toàn bộ không gian đặc trưng
- Trên thực tế, họ hàm băm chỉ biểu diễn được một phần cực nhỏ trong mọi hoán vị khả dĩ
- Cần tránh các tương quan không phù hợp, và tính chất liên quan được gọi là min-wise independence
- Vấn đề này đã được nghiên cứu khá kỹ và trong tài liệu có các lời giải hiệu quả
Tìm các cặp ứng viên trong toàn bộ corpus
- Khi đã rút mỗi tài liệu xuống thành dấu vân tay gồm
kgiá trị băm, có thể xấp xỉ độ tương đồng Jaccard hiệu quả - Vấn đề còn lại là tìm các tài liệu có độ tương đồng cao trong toàn bộ corpus mà không phải xét mọi cặp tài liệu
- Chiến lược là nhóm tài liệu theo một khóa nào đó, rồi chỉ so sánh trong cùng nhóm
- Khóa nhóm phải được thiết kế sao cho tài liệu tương tự có xác suất cao được gom cùng nhau, còn tài liệu không tương tự thì cố tránh bị gom chung
-
Dùng toàn bộ chữ ký MinHash làm khóa
- Cách đơn giản nhất là dùng toàn bộ
kgiá trị MinHash làm một khóa nhóm duy nhất - Chỉ coi hai tài liệu là trùng lặp tương tự khi mọi giá trị MinHash đều trùng nhau
- Bài báo GPT-3 đã dùng loại bỏ trùng lặp tương tự trong pipeline chuẩn bị dataset, và theo cách diễn đạt được trích dẫn thì có thể hiểu là họ dùng triển khai
MinHashLSHcủa Spark cùng 10 hàm băm - Ưu điểm của cách này là đơn giản và hiệu quả
- Việc nhóm theo một chuỗi byte có cardinality cao như vậy rất dễ mở rộng theo chiều ngang
- Nó tương ứng với các primitive cơ bản của công cụ xử lý dữ liệu, như bước “shuffle” giữa map và reduce trong MapReduce
- Nếu độ tương đồng Jaccard của hai tài liệu là
J(A, B)và cần cảkgiá trị đều trùng nhau, thì xác suất va chạm cho một cặp đơn lẻ làJ(A, B)^k - Với
k = 10, các tài liệu có độ tương đồng khoảng 0.6 trở xuống gần như không va chạm - Xác suất trùng bắt đầu tăng mạnh quanh mức tương đồng khoảng 0.95
- Nếu mục tiêu là tìm các tài liệu cực kỳ gần nhau thì như vậy có thể đã đủ
- Phép tính
J^knày là dành cho một cặp tài liệu đơn lẻ - Nếu có nhiều tài liệu rất giống nhau thì xác suất theo từng cặp không còn độc lập
- Trên thực tế, các tài liệu rất giống nhau thường rơi vào chỉ hai hoặc ba bucket, và có thể tìm được gần như toàn bộ trùng lặp
- Cách đơn giản nhất là dùng toàn bộ
Phát hiện trùng lặp lỏng hơn
-
Nếu muốn tìm không chỉ các tài liệu có độ tương đồng gần 1 mà cả các tài liệu từ 0.8 hoặc 0.7 trở lên, thì dùng toàn bộ chữ ký làm khóa có thể quá nghiêm ngặt
-
Nếu chỉ dùng một phần trong
kgiá trị MinHash làm khóa nhóm thì xác suất va chạm ở mức tương đồng thấp hơn sẽ tăng lên- Ví dụ, có thể nhóm theo 4 giá trị MinHash đầu tiên, rồi trong cùng bucket dùng toàn bộ giá trị MinHash để ước lượng độ tương đồng thực sự
-
Việc giảm số lượng băm cũng có giới hạn
J^rluôn nhỏ hơnJ- Nếu
rquá nhỏ thì có thể phát sinh quá nhiều va chạm sai
-
Thay vào đó, có thể tạo nhiều khóa cho mỗi tài liệu để đưa nó vào nhiều bucket
- Ví dụ, tính
k = 20giá trị băm, chia vàob = 4bucket, và mỗi khóa gồmr = 5giá trị băm
- Ví dụ, tính
-
Xác suất hai tài liệu va chạm trong ít nhất một bucket là
[ p = 1 - (1 - J^r)^b ]
-
Trong ví dụ dùng
4nhóm và5giá trị băm mỗi nhóm, điểm mà xác suất va chạm đạt 50% dịch xuống gần khoảngJ = 0.7 -
Khi cả
rvàbđều lớn hơn 1, đường cong kết quả thường có dạng chữ S, tạo ra không gian điều chỉnh giữa độ nhạy, recall và chi phí hiệu năng
Liên hệ với HyperLogLog
- Mánh cốt lõi của MinHash có nét tương đồng với các thuật toán sketch như HyperLogLog
- HyperLogLog băm từng phần tử của dòng dữ liệu rồi lưu giá trị cực đại đang chạy của số lượng bit 0 đứng đầu trong giá trị băm
- Cả hai kỹ thuật đều ánh xạ phần tử đầu vào sang phân phối đồng đều bằng hàm băm, rồi tính cực trị đang chạy để ước lượng các tính chất phân phối chỉ bằng bản tóm tắt kích thước hằng số
- Nếu đảo cách nhìn về thứ tự bit, HyperLogLog có thể được xem là tính cực tiểu đang chạy của
log2(H(x)), còn MinHash dùng cực tiểu của chínhH(x) - Theo một nghĩa nào đó, hai cấu trúc này là đối ngẫu
- Kết hợp hai cấu trúc HyperLogLog có thể ước lượng kích thước hợp của hai tập
- So sánh hai cấu trúc MinHash có thể ước lượng kích thước tương đối của giao giữa hai tập
- Kết hợp hai cấu trúc này có thể tạo ra sketch xử lý các câu hỏi về giao và hợp của những tập bất kỳ
- Ý tưởng này đã được biết đến ít nhất từ năm 2013, và có tài liệu liên quan cùng nghiên cứu tiếp theo
Cách biểu diễn tài liệu dưới dạng tập
- Để dùng Jaccard và MinHash, trước hết phải chuyển tài liệu chuỗi thành tập đặc trưng
- Dù chọn cách nào, vẫn có thể chuẩn hóa tài liệu ở bước tiền xử lý
- Chuyển sang Unicode normalization form
- Gộp chữ hoa/chữ thường
- Rút gọn khoảng trắng liên tiếp
- Và các biến đổi tương tự
-
n-gram hoặc shingle
- Có thể biểu diễn tài liệu bằng tập всех
n-gram xuất hiện trong đó - Trong tài liệu xử lý văn bản quy mô lớn, thuật ngữ “shingle” cũng được dùng, nhưng ở đây nó đóng vai trò giống
n-gram - Việc chọn giá trị
ncó sự đánh đổi - Giá trị nhỏ so sánh tài liệu theo cách thô hơn
- Ví dụ, phần lớn văn bản tiếng Anh có thể trông khá giống nhau nếu nhìn theo bigram
- Giá trị lớn tạo ra các đặc trưng phân biệt hơn và tập lớn hơn
- Nếu quá lớn thì độ nhạy có thể giảm, nhưng trước đó có khả năng vấn đề hiệu năng đã xuất hiện
- Theo Mining of Massive Datasets §3.2.2, trong nhiều ứng dụng các giá trị từ
n = 5đến9là lựa chọn phổ biến
- Có thể biểu diễn tài liệu bằng tập всех
-
Tách từ hoặc token
- Cũng có thể chia đầu vào thành “từ” hoặc “token” rồi dùng chúng làm đặc trưng
- Đoạn trích từ bài báo GPT-3 nhắc đến tokenizer chuẩn của Spark, có vẻ là pyspark.ml.feature.Tokenizer, bộ này chuyển đầu vào sang chữ thường rồi tách theo khoảng trắng
- Cũng có thể dùng NLTK tokenizer tinh vi hơn
- Một cách lai cũng khả thi: token hóa trước rồi dùng n-gram của token
- Vì token riêng lẻ có entropy cao hơn byte hay ký tự, nên trong trường hợp này thường dùng giá trị
nnhỏ hơn
1 bình luận
Các ý kiến trên Hacker News
Nhiều người bỏ qua việc các chỉ số dựa trên tập hợp như độ tương đồng Jaccard (hệ số Tanimoto) hay điểm F1 (hệ số Dice) cũng có thể dùng tương tự cho tập mờ
Tuy nhiên cần chọn một cặp T-Norm / T-Conorm phù hợp để biểu diễn khái niệm giao và hợp của tập mờ, và có vô số loại như vậy
Ngược lại, việc có thể chọn cặp phù hợp với ngữ nghĩa mong muốn lại là một lợi thế
Trong kiểm chứng phân đoạn ảnh y khoa, tôi từng xử lý nội dung này khi kết quả phân đoạn và đáp án đúng không phải là mặt nạ nhị phân mà ở dạng xác suất/mờ: https://link.springer.com/chapter/10.1007/978-3-319-46723-8_..., https://ora.ox.ac.uk/objects/uuid:dc352697-c804-4257-8aec-08...
Thông thường người ta đặt ngưỡng 0,5 để tạo tập nhị phân rồi dùng biến thể nhị phân của Jaccard/Dice, nhưng làm vậy có vẻ khiến độ chính xác của toán tử kiểm chứng giảm khoảng hai chữ số thập phân
Tức là công bố rằng thuật toán tốt hơn kỹ thuật mới nhất 0,001, nhưng lại bỏ qua việc biên sai số của toán tử kiểm chứng thực ra là 0,1
Từng có một khách hàng tự tạo bản triển khai Python của kỹ thuật này để loại bỏ trùng lặp các mục công dân trong một cơ sở dữ liệu lớn của chính phủ Pháp, và nó hoạt động tốt
Nếu là hiện nay thì có lẽ tôi đã bảo họ dùng datasketch: https://pypi.org/project/datasketch/
Tìm thử thì thấy các công cụ mới về chủ đề này vẫn liên tục xuất hiện. Chẳng hạn https://pypi.org/project/rensa/ là một phiên bản chuyên biệt hơn và nhanh hơn so với MinHash của datasketch, được viết bằng Rust và phủ thêm một chút Python bên trên
Tôi xin nói rõ mình là tác giả chính
Tôi cũng đã viết một hướng dẫn tương tác giải thích cách nó hoạt động: https://github.com/moj-analytical-services/splink, https://www.robinlinacre.com/intro_to_probabilistic_linkage/
datasketch rất tuyệt, nhưng hiệu năng không đủ cho trường hợp sử dụng của tôi, còn gaoya đang được dùng trong một hệ thống vận hành phân cụm quy mô lớn: https://github.com/serega/gaoya
Thật là trùng hợp đáng kinh ngạc. Tôi vừa triển khai một hệ thống MinHash mà có thể sẽ có người thấy thú vị
Bài toán là tìm giả nghịch đảo của nhiều ma trận con phù hợp trong một ma trận vuông lớn
Dùng các đẳng thức ma trận như Woodbury, Banachiewicz có thể cập nhật nghịch đảo của các ma trận con “gần” nhau để tính nghịch đảo mới với chi phí thấp
Chỉ cần lưu các nghịch đảo đã tính với chỉ số hàng/cột làm khóa, rồi với mỗi ma trận con mới, tìm một nghịch đảo đã có gần nó để làm điểm xuất phát cập nhật
Tôi đã giải bài toán này bằng MinHash, xử lý hash giá trị nhỏ nhất cho các chỉ số để các ma trận gần nhau có xác suất cao có cùng hash
Trong bản triển khai của tôi, tôi dùng hash đa độ phân giải để có thể điều chỉnh độ chọn lọc tìm kiếm khi số nghịch đảo đã tính tăng lên
Bổ sung thêm một chút bối cảnh mà bài viết này còn thiếu: theo tôi biết, kỹ thuật này được tạo ra vào thời kỳ đầu của Google để loại bỏ trùng lặp trong tập dữ liệu crawl
Cũng thú vị là việc tạo LLM và việc lập chỉ mục văn bản web thông thường lại giống nhau đến mức đáng ngạc nhiên
Có thể đọc chi tiết trong cuốn sách miễn phí “Mining Massive Datasets” của Jeffrey Ullman, trong đó giải thích nhiều kỹ thuật ấn tượng và rất hay từng được dùng để xây dựng chỉ mục cho toàn bộ Internet thời đó
Có thể tìm tài liệu liên quan miễn phí bằng cách tìm “chapter 3 pdf mmds ullman”
Chỉnh sửa: hóa ra tôi đã nhầm, theo Wikipedia thì nó được phát minh ở DEC cho AltaVista: https://en.wikipedia.org/wiki/MinHash
Dù sao thì sách của Ullman cũng có phần giải thích tốt và cũng nói về cách nó được dùng ở Google
Khi cố hiểu MinHash và các biến thể của nó, tôi thấy khá khó ngấm, nên đang làm một công cụ trực quan hóa trực tuyến: https://websla.sh/tools/minhash
Nó vẫn chưa hoàn thiện và tôi cũng muốn hiển thị cả phần tính độ tương đồng Jaccard, nhưng hiện giờ bạn đã có thể nhập nhiều chuỗi để tự xem “minhash” thực sự là gì
Việc dùng hashing hoặc mạng nơ-ron nhỏ cùng với công cụ tìm kiếm vector và Tanimoto/Jaccard là một chiến lược rất phổ biến trong khử trùng lặp tập dữ liệu quy mô lớn
Cách này có thể khôn ngoan hơn so với dùng các tác vụ MapReduce có độ phức tạp tuyến tính
Có một dự án hay của Google dùng mô hình RETSim 500 nghìn tham số và engine USearch: https://github.com/google/unisim
Hiện trong PostgreSQL tôi đang gặp một vấn đề tương tự. Có 600.000
feed_itemsvà schema là(feed_item_id uuid, author varchar, content text, guid varchar, link varchar, title varchar, summary text, feed_id integer)Đặc biệt, các cột
contentvàsummarycủa một số mục tin tức rất giống nhau nhưng không hoàn toàn giốngKhi có hai mục tin tức như vậy, tôi muốn gộp chúng thành một; có cách nào hay không?
Quy trình đại khái như sau
Mục tiêu là với mỗi hàng, thu được một mảng số nguyên “minhashed” có cùng độ dài với các mảng ở bước 2. Nếu khai báo độ dài mảng toàn cục là 64 thì mảng MinHash của mỗi hàng cũng có độ dài 64
Nếu làm đúng, hãy trải mảng này ra làm “hàng nguồn”, rồi join dataset với chính nó theo từng giá trị MinHash đã bucket hóa, khi đó sẽ có thêm cột “hàng đích”
Nhóm theo các cột nguồn/đích và đếm số lần xuất hiện thì có thể ước lượng hai hàng giống nhau đến mức nào
Về bản chất, hai mục càng được hash vào các bucket tương tự nhau thì càng giống nhau; còn từ ngưỡng nào mới tính Jaccard hoặc cosine similarity theo từng cặp thực tế thì bạn tự quyết định
Điểm mấu chốt là ngân sách thời gian của bạn là bao nhiêu
Nếu xem hai mục là chia sẻ phần văn bản rất giống nhau thì đáng thử khoảng cách Levenshtein
Khi đó có thể dùng Jaccard similarity
Tôi thích bài viết này. Nhóm chúng tôi ở NVIDIA gần đây đã công bố phiên bản tăng tốc GPU của thuật toán khử trùng lặp mờ được mô tả, và có lẽ cộng đồng này cũng sẽ quan tâm
Repository ở đây: https://github.com/NVIDIA/NeMo-Curator/
Tài liệu script khử trùng lặp mờ ở đây: https://docs.nvidia.com/nemo-framework/user-guide/latest/dat...
Cũng có ví dụ Python: https://github.com/NVIDIA/NeMo-Curator/blob/main/examples/fu...
Rất muốn nghe phản hồi
Những kỹ thuật kiểu này đọc bằng bài viết thì không hiểu được, nhưng chỉ cần đưa dữ liệu của mình vào ví dụ code chạy được vài lần và xem bên trong diễn ra thế nào là hấp thụ ngay
Tôi học kỹ thuật này lần đầu từ Douglas Eck: https://research.google/people/douglas-eck/
Google đã dùng nó để clustering bài hát, và tôi nhớ ông ấy nói về hashing và vector ngẫu nhiên
Khi đó tôi thấy bối rối vì nghĩ rằng các tối ưu hóa ít tính ngẫu nhiên hơn sẽ hoạt động tốt hơn
Thêm banding và một chút xác suất vào đó, ta có thể xấp xỉ Jaccard similarity trên dataset khổng lồ theo cách rẻ và rất dễ song song hóa
Nếu xem đây là kỹ thuật clustering tài liệu hoặc khử trùng lặp dataset, thì cách “ném machine learning vào vấn đề” sẽ thế nào về chất lượng và hiệu năng so với phương pháp thuật toán rời rạc đơn giản hơn này?
Ví dụ tạo vector embedding tài liệu bằng một LLM encoder đã pre-train, đưa các vector đó vào vector DB rồi clustering bằng k-means
Muốn dùng k-means thì vẫn phải chọn một hàm khoảng cách như Jaccard, và k-means có lẽ không lý tưởng cho near-duplicate
Cũng có thể dùng MinHash làm bước tiền xử lý cho k-means để tăng tốc
Tôi không nghĩ vector DB giúp được nhiều
Nếu có hàng trăm triệu tài liệu thì có thể dùng nó để tăng tốc tra cứu MinHash sketch, nhưng nhìn chung có lẽ là lựa chọn quá tay
Mỗi khi embedding một tài liệu, trước khi thêm vào sẽ tìm approximate nearest neighbors, nên cũng là O(N) như MinHash
Các vector index như HNSW và PQ có đánh đổi hiệu năng/chất lượng tốt hơn SimHash LSH, vốn tương ứng với MinHash cho cosine distance
Chất lượng phụ thuộc vào việc định nghĩa near-duplicate là gì và dùng mô hình embedding nào
Các mô hình hiện đại hoạt động tốt, và nếu có dữ liệu gán nhãn thì fine-tuning có thể làm tốt hơn nữa
Nhược điểm chính là chi phí bổ sung để embedding toàn bộ tài liệu, đặc biệt nặng với tài liệu dài
Nhưng nhờ các mô hình nhỏ, tối ưu hóa tốt hơn và phần cứng nhanh hơn, chi phí này đã giảm rất nhanh