| Title: | Detect and Audit Train/Test Leakage from Near-Duplicate Images |
|---|---|
| Description: | Detects near-duplicate images across dataset splits using perceptual hashing, reports the resulting train/validation/test contamination, and produces a corrected, leak-free split assignment. Intended for machine learning researchers who need to verify that image classification splits do not share near-duplicate samples across partitions before reporting model metrics. |
| Authors: | Kartik Patel [aut, cre], Samruddhi Amol Shah [aut], BitandByte [cph] |
| Maintainer: | Kartik Patel <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.0 |
| Built: | 2026-10-01 07:59:24 UTC |
| Source: | https://github.com/anakincodex/leakaudit |
Resolves duplicate groups that span multiple splits by assigning every
member of a leaked group to a single split, chosen by priority. This
mirrors the common convention of keeping duplicates in train (so no
information is discarded, only potential evaluation leakage) but any
priority order can be supplied.
clean_splits(grouped_df, priority = c("train", "val", "test"))clean_splits(grouped_df, priority = c("train", "val", "test"))
grouped_df |
A data frame with columns |
priority |
Character vector giving split precedence, highest
priority first. Defaults to |
grouped_df with split replaced by the corrected,
leak-free assignment, plus a logical column reassigned flagging
which rows were changed.
groups <- data.frame( path = c("a.jpg", "b.jpg", "c.jpg"), split = c("train", "test", "val"), group_id = c(1, 1, 2) ) clean_splits(groups)groups <- data.frame( path = c("a.jpg", "b.jpg", "c.jpg"), split = c("train", "test", "val"), group_id = c(1, 1, 2) ) clean_splits(groups)
Computes a 64-bit difference hash (dHash) for each image path. dHash is robust to resizing, mild compression, and small crops, which makes it well suited to catching near-duplicate images that differ only in resolution, format, or minor edits – the kind of duplication that commonly leaks across train/validation/test splits.
compute_hashes(paths, split = NULL)compute_hashes(paths, split = NULL)
paths |
Character vector of file paths to image files. Any format
readable by |
split |
A character vector the same length as |
A data frame with columns path, split, and hash (a 16
character hex string encoding the 64-bit dHash). Any path that fails
to load produces NA in hash with a warning, rather than stopping
the whole run.
## Not run: hashes <- compute_hashes( paths = list.files("images/", full.names = TRUE), split = rep(c("train", "test"), length.out = 10) ) ## End(Not run)## Not run: hashes <- compute_hashes( paths = list.files("images/", full.names = TRUE), split = rep(c("train", "test"), length.out = 10) ) ## End(Not run)
Given duplicate groups (from find_duplicate_groups()) and each
image's split assignment, reports how many groups span more than one
split – i.e. how many near-duplicate images "leak" across the
train/validation/test boundary – and what fraction of each split is
affected.
dhash_audit(grouped_df)dhash_audit(grouped_df)
grouped_df |
A data frame with columns |
An object of class leakage_report (a list) with:
data frame of group_ids that span >1 split, with the splits involved and member count
total images sitting in a leaked group
named numeric vector, % of each split's images that belong to a leaked group
total images audited
groups <- data.frame( path = c("a.jpg", "b.jpg", "c.jpg", "d.jpg"), split = c("train", "test", "train", "val"), group_id = c(1, 1, 2, 3) ) dhash_audit(groups)groups <- data.frame( path = c("a.jpg", "b.jpg", "c.jpg", "d.jpg"), split = c("train", "test", "train", "val"), group_id = c(1, 1, 2, 3) ) dhash_audit(groups)
Given a table of image hashes (as produced by compute_hashes()),
clusters images into duplicate groups: any two images within
threshold Hamming distance of each other are placed in the same
group, and group membership propagates transitively (A~B~C all end
up in one group even if A and C aren't directly close).
find_duplicate_groups(hash_df, threshold = 5)find_duplicate_groups(hash_df, threshold = 5)
hash_df |
A data frame with at least columns |
threshold |
Maximum Hamming distance (in bits, out of 64) for two
images to be considered near-duplicates. Defaults to |
hash_df with an added integer column group_id. Images with
no near-duplicates get their own unique group_id. Row order is
preserved.
hashes <- data.frame( path = c("a.jpg", "b.jpg", "c.jpg", "d.jpg"), hash = c("0000000000000000", "0000000000000001", "ffffffffffffffff", "1234567890abcdef") ) find_duplicate_groups(hashes, threshold = 5)hashes <- data.frame( path = c("a.jpg", "b.jpg", "c.jpg", "d.jpg"), hash = c("0000000000000000", "0000000000000001", "ffffffffffffffff", "1234567890abcdef") ) find_duplicate_groups(hashes, threshold = 5)
Computes the number of differing bits between two perceptual hashes
encoded as hexadecimal strings (as produced by compute_hashes()).
hamming_distance(hash1, hash2)hamming_distance(hash1, hash2)
hash1 |
A character scalar or vector of hex-encoded hashes. |
hash2 |
A character scalar or vector of hex-encoded hashes, the
same length as |
An integer vector of Hamming distances (number of differing bits).
hamming_distance("ff00", "ff01") hamming_distance(c("ffff", "0000"), "0000")hamming_distance("ff00", "ff01") hamming_distance(c("ffff", "0000"), "0000")