leakhound-ml 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- leakhound_ml-0.2.0/.gitignore +9 -0
- leakhound_ml-0.2.0/GETTING_STARTED.md +348 -0
- leakhound_ml-0.2.0/LICENSE +661 -0
- leakhound_ml-0.2.0/PKG-INFO +144 -0
- leakhound_ml-0.2.0/README.md +123 -0
- leakhound_ml-0.2.0/docs/report.png +0 -0
- leakhound_ml-0.2.0/docs/terminal.png +0 -0
- leakhound_ml-0.2.0/examples/demo.py +28 -0
- leakhound_ml-0.2.0/examples/real_world.py +50 -0
- leakhound_ml-0.2.0/examples/test_sample.csv +91 -0
- leakhound_ml-0.2.0/examples/train_sample.csv +169 -0
- leakhound_ml-0.2.0/pyproject.toml +34 -0
- leakhound_ml-0.2.0/src/leakhound/__init__.py +56 -0
- leakhound_ml-0.2.0/src/leakhound/autodetect.py +66 -0
- leakhound_ml-0.2.0/src/leakhound/checks/__init__.py +13 -0
- leakhound_ml-0.2.0/src/leakhound/checks/duplicates.py +70 -0
- leakhound_ml-0.2.0/src/leakhound/checks/group_split.py +34 -0
- leakhound_ml-0.2.0/src/leakhound/checks/target_encoding.py +68 -0
- leakhound_ml-0.2.0/src/leakhound/checks/temporal.py +36 -0
- leakhound_ml-0.2.0/src/leakhound/cli.py +64 -0
- leakhound_ml-0.2.0/src/leakhound/impact.py +112 -0
- leakhound_ml-0.2.0/src/leakhound/report.py +163 -0
- leakhound_ml-0.2.0/tests/test_features.py +62 -0
- leakhound_ml-0.2.0/tests/test_smoke.py +51 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# Getting Started with LeakHound
|
|
2
|
+
|
|
3
|
+
A complete, copy-paste guide — from zero to your first leakage report — on
|
|
4
|
+
**Windows, macOS and Linux**. No prior experience assumed. If you can open a
|
|
5
|
+
terminal and paste a line, you can run this.
|
|
6
|
+
|
|
7
|
+
> **What it does, in one sentence:** you give LeakHound your training and test
|
|
8
|
+
> files, and it tells you whether your model's score is real — and how much of it
|
|
9
|
+
> is fake — by finding the ways the test set was contaminated by the training data.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Table of contents
|
|
14
|
+
|
|
15
|
+
1. [Do you have Python?](#1-do-you-have-python)
|
|
16
|
+
2. [Install LeakHound](#2-install-leakhound)
|
|
17
|
+
3. [Run your first check (sample data included)](#3-run-your-first-check)
|
|
18
|
+
4. [Run it on your own data](#4-run-it-on-your-own-data)
|
|
19
|
+
5. [Read the report](#5-read-the-report)
|
|
20
|
+
6. [Fix each kind of leak](#6-fix-each-kind-of-leak)
|
|
21
|
+
7. [Measure the damage (`--measure-impact`)](#7-measure-the-damage)
|
|
22
|
+
8. [Auto mode (`--auto`)](#8-auto-mode)
|
|
23
|
+
9. [Shareable HTML report (`--html`)](#9-shareable-html-report)
|
|
24
|
+
10. [Use it from Python](#10-use-it-from-python)
|
|
25
|
+
11. [Use it in CI (fail a bad merge)](#11-use-it-in-ci)
|
|
26
|
+
12. [Troubleshooting](#12-troubleshooting)
|
|
27
|
+
13. [FAQ](#13-faq)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 1. Do you have Python?
|
|
32
|
+
|
|
33
|
+
LeakHound needs **Python 3.9 or newer**. Check it first.
|
|
34
|
+
|
|
35
|
+
**Windows** (Command Prompt or PowerShell):
|
|
36
|
+
```bat
|
|
37
|
+
python --version
|
|
38
|
+
```
|
|
39
|
+
**macOS / Linux** (Terminal):
|
|
40
|
+
```bash
|
|
41
|
+
python3 --version
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
If you see something like `Python 3.11.5`, you're set — skip to step 2.
|
|
45
|
+
If not, install Python:
|
|
46
|
+
|
|
47
|
+
- **Windows:** download from <https://www.python.org/downloads/> and, on the
|
|
48
|
+
first installer screen, **tick "Add python.exe to PATH"**. (Or run
|
|
49
|
+
`winget install Python.Python.3.12`.)
|
|
50
|
+
- **macOS:** `brew install python`, or download from python.org.
|
|
51
|
+
- **Linux (Debian/Ubuntu/Pop!_OS):** `sudo apt install python3 python3-pip`
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 2. Install LeakHound
|
|
56
|
+
|
|
57
|
+
> While LeakHound is pre-release, install it from source. Once it's published,
|
|
58
|
+
> `pip install leakhound` will be all you need.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
git clone https://github.com/happyhellpt/leakhound.git
|
|
62
|
+
cd leakhound
|
|
63
|
+
pip install -e '.[impact]'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`[impact]` also installs scikit-learn, which powers the `--measure-impact`
|
|
67
|
+
feature (step 7). Leave it out (`pip install -e .`) if you only want the
|
|
68
|
+
detectors.
|
|
69
|
+
|
|
70
|
+
On Windows, if `pip` isn't found, use `py -m pip install -e ".[impact]"`.
|
|
71
|
+
|
|
72
|
+
Check it works:
|
|
73
|
+
```bash
|
|
74
|
+
leakhound --version
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
> If `leakhound` is "not found" after install, you can always run it as
|
|
78
|
+
> `python -m leakhound.cli ...` — see [Troubleshooting](#12-troubleshooting).
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 3. Run your first check
|
|
83
|
+
|
|
84
|
+
The repo ships with example data that already contains planted leaks. From inside
|
|
85
|
+
the `leakhound` folder:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
leakhound --train examples/train_sample.csv --test examples/test_sample.csv --target label --time-col date --group-col patient_id
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
You'll see (trimmed):
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
LeakHound report
|
|
95
|
+
────────────────────────────────────────────────
|
|
96
|
+
✗ [high ] duplicates: 12 test rows (13.3%) also appear in training — the model has seen them
|
|
97
|
+
→ fix: Remove duplicates before splitting (df.drop_duplicates()) ...
|
|
98
|
+
✗ [high ] target_encoding: feature 'leaky_feature' correlates 1.000 with the target — it likely leaks it
|
|
99
|
+
→ fix: Drop this feature, or replace it with information available at prediction time.
|
|
100
|
+
✗ [high ] temporal: 168 training rows (100.0%) are dated at/after the earliest test row — the model trains on the future
|
|
101
|
+
→ fix: Split chronologically: sort by the time column and put later rows in test.
|
|
102
|
+
✗ [high ] group_split: 49 values of 'patient_id' (89.1% of test groups) are in both sets ...
|
|
103
|
+
→ fix: Use a group-aware splitter (GroupShuffleSplit / GroupKFold) ...
|
|
104
|
+
! [medium] near_duplicates: 5 test rows (5.6%) are near-identical to training rows ...
|
|
105
|
+
────────────────────────────────────────────────
|
|
106
|
+
5 likely leaks found. Your reported metric is probably optimistic.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
That's it — you just ran LeakHound. 🎉 Every finding tells you what it found **and
|
|
110
|
+
how to fix it**.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 4. Run it on your own data
|
|
115
|
+
|
|
116
|
+
You need **two CSV files**: your training set and your test (or validation) set,
|
|
117
|
+
each with a header row. Then:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
leakhound --train path/to/train.csv --test path/to/test.csv --target label
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Add the optional flags when your data has them — each switches on more:
|
|
124
|
+
|
|
125
|
+
| Flag | Give it | Turns on |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| `--train` (required) | your training CSV | — |
|
|
128
|
+
| `--test` | your test/validation CSV | duplicate + near-duplicate checks |
|
|
129
|
+
| `--target` | the column you're predicting | target-encoding check |
|
|
130
|
+
| `--time-col` | a date/timestamp column | look-ahead (temporal) check |
|
|
131
|
+
| `--group-col` | an id column (patient, user, device) | group-split check |
|
|
132
|
+
| `--measure-impact` | *(no value)* | quantify how much each leak inflates the score |
|
|
133
|
+
| `--auto` | *(no value)* | auto-detect the columns above / advise on one file |
|
|
134
|
+
| `--html PATH` | a file path | also write a shareable HTML report |
|
|
135
|
+
| `--ascii` | *(no value)* | plain-text output for old terminals |
|
|
136
|
+
|
|
137
|
+
Only the checks whose inputs you provide will run.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 5. Read the report
|
|
142
|
+
|
|
143
|
+
Each line is one finding, with a severity, the evidence, and a fix.
|
|
144
|
+
|
|
145
|
+
- **`✗ high`** — almost certainly real leakage. Fix before trusting any score.
|
|
146
|
+
- **`! medium`** — likely leakage; worth investigating.
|
|
147
|
+
- **`· low`** — informational (e.g. a check couldn't run).
|
|
148
|
+
- **`✓ ok`** — that specific check found nothing.
|
|
149
|
+
|
|
150
|
+
| Check | What it means |
|
|
151
|
+
|---|---|
|
|
152
|
+
| **duplicates** | Exact same rows in both train and test — tested on memorised data. |
|
|
153
|
+
| **near_duplicates** | Rows identical after tiny rounding — copies you didn't notice. |
|
|
154
|
+
| **temporal** | Training rows dated at/after your test rows — the model "saw the future". |
|
|
155
|
+
| **target_encoding** | A feature (often an ID) basically *is* the answer. |
|
|
156
|
+
| **group_split** | The same subject is in both sets — it recognises the subject, not the pattern. |
|
|
157
|
+
| **impact** | *How much* score each leak is inflating (see step 7). |
|
|
158
|
+
|
|
159
|
+
> ⚠️ A clean report is **not proof** your split is perfect. It means these
|
|
160
|
+
> common, high-impact leaks aren't present.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 6. Fix each kind of leak
|
|
165
|
+
|
|
166
|
+
**Duplicates / near-duplicates across train/test**
|
|
167
|
+
```python
|
|
168
|
+
df = df.drop_duplicates() # then split once, cleanly
|
|
169
|
+
```
|
|
170
|
+
Also check your pipeline for copied or augmented rows.
|
|
171
|
+
|
|
172
|
+
**Temporal (look-ahead) leakage** — split by time, not randomly:
|
|
173
|
+
```python
|
|
174
|
+
df = df.sort_values("date")
|
|
175
|
+
cut = int(len(df) * 0.8)
|
|
176
|
+
train, test = df.iloc[:cut], df.iloc[cut:]
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Target-encoding leakage** — drop the leaking feature (often an ID or a value
|
|
180
|
+
recorded *after* the outcome):
|
|
181
|
+
```python
|
|
182
|
+
train = train.drop(columns=["leaky_feature"])
|
|
183
|
+
test = test.drop(columns=["leaky_feature"])
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Group split leakage** — keep every group on one side:
|
|
187
|
+
```python
|
|
188
|
+
from sklearn.model_selection import GroupShuffleSplit
|
|
189
|
+
splitter = GroupShuffleSplit(test_size=0.2, random_state=0)
|
|
190
|
+
train_idx, test_idx = next(splitter.split(X, y, groups=df["patient_id"]))
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 7. Measure the damage
|
|
196
|
+
|
|
197
|
+
Detecting a leak is one thing; seeing what it's worth is another. Add
|
|
198
|
+
`--measure-impact` and LeakHound fits a quick baseline model, then reports your
|
|
199
|
+
honest score next to the inflated one:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
leakhound --train examples/train_sample.csv --test examples/test_sample.csv --target label --measure-impact
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
✗ [high ] impact: removing leaking feature(s) ['leaky_feature'] drops AUC from 1.000 to 0.669 — +0.331 of fake performance
|
|
207
|
+
AUC_with_leak: 1.0
|
|
208
|
+
AUC_without_leak: 0.669
|
|
209
|
+
inflation: 0.331
|
|
210
|
+
→ fix: Drop the leaking feature(s) and re-evaluate honestly.
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Your `1.000` was really `0.669`. It works for **binary** (0/1) and **continuous**
|
|
214
|
+
targets (AUC and R² respectively), using numeric features. Needs scikit-learn
|
|
215
|
+
(`pip install 'leakhound[impact]'`). Treat the number as a quick baseline
|
|
216
|
+
estimate, not gospel.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## 8. Auto mode
|
|
221
|
+
|
|
222
|
+
Not sure which columns are the target, the timestamp or the group id? Add
|
|
223
|
+
`--auto` and LeakHound guesses them:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
leakhound --train examples/train_sample.csv --auto
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
Auto-detected -> target='label', time_col='date', group_col='patient_id'
|
|
231
|
+
...
|
|
232
|
+
! [medium] temporal: column 'date' looks like a time column — a random split would let the model train on the future
|
|
233
|
+
! [medium] group_split: column 'patient_id' looks like a group id — a random split would put the same group on both sides
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Run on a **single file** (no `--test`), it becomes a *pre-split linter*: it warns
|
|
237
|
+
you which columns will leak **before** you split. Run it with a test set too and
|
|
238
|
+
it just fills in any columns you didn't name.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## 9. Shareable HTML report
|
|
243
|
+
|
|
244
|
+
Add `--html` to also write a self-contained report you can send to a colleague or
|
|
245
|
+
attach to a pull request (no internet or extra files needed to open it):
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
leakhound --train train.csv --test test.csv --target label --measure-impact --html leakhound_report.html
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Open `leakhound_report.html` in any browser. It adapts to light and dark mode.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## 10. Use it from Python
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
import pandas as pd
|
|
259
|
+
from leakhound import audit
|
|
260
|
+
|
|
261
|
+
train = pd.read_csv("train.csv")
|
|
262
|
+
test = pd.read_csv("test.csv")
|
|
263
|
+
|
|
264
|
+
report = audit(train, test, target="label", time_col="date",
|
|
265
|
+
group_col="patient_id", measure_impact=True)
|
|
266
|
+
print(report.render())
|
|
267
|
+
|
|
268
|
+
# Save the shareable report
|
|
269
|
+
with open("report.html", "w", encoding="utf-8") as f:
|
|
270
|
+
f.write(report.to_html())
|
|
271
|
+
|
|
272
|
+
if not report.clean:
|
|
273
|
+
raise SystemExit("Leakage detected — fix the split before training.")
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
`report.leaks` gives the findings as objects (`.check`, `.severity`, `.message`,
|
|
277
|
+
`.evidence`, `.fix`).
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## 11. Use it in CI
|
|
282
|
+
|
|
283
|
+
LeakHound exits non-zero when it finds leakage, so it can **fail a pull request**
|
|
284
|
+
that would ship a contaminated split (`.github/workflows/leakhound.yml`):
|
|
285
|
+
|
|
286
|
+
```yaml
|
|
287
|
+
name: data-leakage-check
|
|
288
|
+
on: [push, pull_request]
|
|
289
|
+
jobs:
|
|
290
|
+
leakhound:
|
|
291
|
+
runs-on: ubuntu-latest
|
|
292
|
+
steps:
|
|
293
|
+
- uses: actions/checkout@v4
|
|
294
|
+
- uses: actions/setup-python@v5
|
|
295
|
+
with:
|
|
296
|
+
python-version: "3.12"
|
|
297
|
+
- run: pip install 'leakhound[impact]'
|
|
298
|
+
- run: leakhound --train data/train.csv --test data/test.csv --target label --measure-impact
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
If a leak is found, the step fails and the merge is blocked.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## 12. Troubleshooting
|
|
306
|
+
|
|
307
|
+
**`leakhound: command not found`** — run it as a module instead:
|
|
308
|
+
```bash
|
|
309
|
+
python -m leakhound.cli --train examples/train_sample.csv --test examples/test_sample.csv --target label
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**`install scikit-learn to measure leak impact`** — `--measure-impact` needs it:
|
|
313
|
+
```bash
|
|
314
|
+
pip install 'leakhound[impact]'
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**`FileNotFoundError`** — wrong path. Use the full path, or `cd` into the folder
|
|
318
|
+
first. On Windows, paths use backslashes: `data\train.csv`.
|
|
319
|
+
|
|
320
|
+
**Strange symbols / `UnicodeEncodeError` on Windows** — old `cmd.exe` can't print
|
|
321
|
+
the report symbols. Add `--ascii` (LeakHound auto-detects this in most cases):
|
|
322
|
+
```bat
|
|
323
|
+
leakhound --train train.csv --test test.csv --target label --ascii
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**`UnicodeDecodeError` reading the CSV** — re-save your CSV as UTF-8.
|
|
327
|
+
|
|
328
|
+
**`target 'label' not found`** — the `--target` name must match a column header
|
|
329
|
+
exactly (case-sensitive). Or use `--auto` to let LeakHound guess it.
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## 13. FAQ
|
|
334
|
+
|
|
335
|
+
**Does LeakHound change my data?** No. It only reads your files and prints a
|
|
336
|
+
report. It never writes to them (except the `--html` file you ask for).
|
|
337
|
+
|
|
338
|
+
**Does a clean report guarantee my model is fine?** No. It rules out the most
|
|
339
|
+
common, highest-impact leaks — not every possible one.
|
|
340
|
+
|
|
341
|
+
**Which operating systems are supported?** Windows, macOS and Linux, identically
|
|
342
|
+
(bar the `python` vs `python3` detail in step 1).
|
|
343
|
+
|
|
344
|
+
**What file formats does it read?** CSV, for now. Data is loaded into memory with
|
|
345
|
+
pandas, so very large files need enough RAM.
|
|
346
|
+
|
|
347
|
+
**Can I run just one check?** Yes — only supply the inputs for the checks you
|
|
348
|
+
want.
|