Skip to content
ramsterrPublic

About

bloom filter before feeding the data into the ML pipeline to avoid uncessary training

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

BloomGate

Duplicate detection for ML pipelines. A Bloom filter gate for streams, plus the batch work that actually breaks training runs: duplicate rows, train/test leakage, and re-uploaded datasets.

In one measured run over 2,310 news articles it caught 534 duplicates, cut 23% of the model's training work, and held 2.5 KB of state instead of 3.4 MB -- with no loss of accuracy.

python3 -m pip install -e ".[dev]"
python3 -m bloomgate report

Not sure this is for you? Read docs/00-is-this-for-you.md first. It lists the symptoms, compares this against a set, a SQL UNIQUE constraint and an exact dedup library, and tells you which to use instead. Roughly speaking: if your data arrives in one batch, or a database already holds your history, use those -- a Bloom filter is for streams where storing history is not an option.

Already have data? Point it at your own file, no code required:

# streaming: what is in it, how big a filter, and what a gate would do
python3 -m bloomgate inspect headlines.jsonl --field headline
python3 -m bloomgate dedupe  headlines.jsonl --field headline

# ML datasets: the checks worth running before you train
python3 -m bloomgate prep     train.csv --fields text --dry-run   # duplicate rows
python3 -m bloomgate leakage  train=train.csv test=test.csv --fields text
python3 -m bloomgate fingerprint train.csv --rows --fields text  # re-uploaded?

Start here: what is a Bloom filter?

A set is the normal way to answer "have I seen this before?". It is exact, and its memory grows forever, because it has to hold every item you ever put in it.

A Bloom filter answers the same question using a fixed array of bits that you size once, up front:

to REMEMBER an article:   hash it 7 ways, set those 7 bits
to ASK about an article:  hash it 7 ways, are all 7 bits set?

That is the whole algorithm. Bits get shared between articles, so a new article can occasionally look like one you have seen. That is the only way it fails, and the odds are something you choose: 1% by default, less if you need.

python3 -m bloomgate demo     # watch it happen, with the bit positions printed

How it works

Why anyone would bother

News aggregators get articles faster than they can train on them, and some fraction are junk: bots reposting yesterday's headline, syndicated stories running twice, feed loops repeating themselves.

Training on a duplicate is not dangerous, it is wasteful. The model already learned that text. A second gradient step on identical input teaches nothing and costs full CPU time -- and at firehose volume that waste is the difference between a model that keeps up and one that falls behind.

The obvious fix, a set of everything seen, is exactly what does not scale.

Memory: exact dedup grows, Bloom stays flat

Same job, same results, about 1,100x less memory.

The results

One run of python3 -m bloomgate report: 20 Newsgroups, three topics, 30% of articles pasted back in as duplicates, one model learning from a live stream.

run offered trained on dropped drop rate memory accuracy
No filter (baseline) 2310 2310 0 0.0% 0 (stateless) 0.948
Exact set (perfect dedup) 2310 1777 533 23.1% 3,414 KB 0.951
Bloom filter (p=1%) 2310 1776 534 23.1% 2.5 KB 0.951

The measured comparison

Four things worth reading off that table:

  1. The Bloom row matches the exact-set row. Same articles dropped, same accuracy. It differs only in memory: 1,368x smaller.
  2. Accuracy went up, from 0.948 to 0.951. The baseline spends 534 updates re-learning identical text. Those updates were noise, not signal.
  3. Work dropped by 23%, and wall-clock time with it (25-35% faster; timings move per machine, the current run is in docs/06-measured-results.md).
  4. One article was wrongly dropped. The filter rejected 6 of 1,182 held-out articles it had never seen -- 0.51%, against the 1% it was sized for. That is the cost of the memory saving, and it is measured, not assumed.

The architecture

for article in incoming_articles:          # one at a time, like a real feed
    if gate.offer(article.headline):       # constant time, fixed memory
        classifier.observe(article.text)   # expensive maths, new articles only

That is the whole system: a gate in front, an online learner behind.

The pipeline

  • The gate (bloom.py) -- hashes the item k times and sets k bits. No stored copy of the item, ever.
  • The model (pipeline.py) -- HashingVectorizer (text to numbers with no vocabulary to learn, which is what makes it work on a stream) and SGDClassifier.partial_fit (learns one row at a time).

bloom.py imports nothing else from this project. The data structure reads in one sitting, and a bug in the ML cannot corrupt it.

The honest caveats

This is a small, well-understood data structure, and it has sharp edges. All of them are handled in docs/04-production-patterns.md, and all of them are implemented in examples/05_production_patterns.py.

Caveat What it means in practice
It never forgets. No remove exists A filter that sees everything eventually rejects everything. You must rotate it on a schedule.
It says "seen" about new items One real article lost per 100 at p=0.01. Fine for training data, unacceptable for an audit log -- there you add a confirmation store.
It matches bytes, not meaning Two articles with 95% of their words in common are not duplicates to it. Near-duplicate detection needs MinHash, SimHash or embeddings.
The error rate degrades with volume p=0.01 is the rate at capacity. Past capacity it is 17% at 2x and 68% at 4x. Size for expected volume with headroom.
The gate costs real time 4.7 µs/article on 1.9 KB documents, 3.0 µs on headlines, measured on an Apple M1. It hashes bytes, so cost tracks input length -- feed it a headline or a URL, not a book.

When not to use one

  • You need to delete. Use a set, or a filter with a time window.
  • You need exactness at small volume. A set is simpler and free of error.
  • You need near-duplicate matching. This compares bytes.
  • Your stream is unbounded and you cannot rotate.

Getting started

Requires Python 3.10+. Dependencies: scikit-learn, mmh3, bitarray, plus matplotlib for the charts.

git clone <repo-url> && cd bloomgate
python3 -m pip install -e ".[dev]"

python3 -m bloomgate demo       # 60 seconds, no dataset needed
python3 -m bloomgate report     # the full experiment (downloads ~14 MB once)

The dataset is cached in ~/scikit_learn_data after the first run. On macOS, if that download fails with a certificate error, see troubleshooting.

Use it on your own data

Formats accepted: .jsonl/.ndjson, .csv, .tsv, .txt (one per line).

$ python3 -m bloomgate inspect headlines.jsonl --field headline

/tmp/headlines.jsonl
  rows              4,000
  distinct values   2,825
  duplicates        1,175 (29.4%)
  average length    32 characters

  most repeated values:
         215 x Bond yields slip on jobs data
         210 x Striker hat-trick seals playoff spot
         ...

  Sizing suggestion (capacity = distinct x headroom):
      error rate  headroom   capacity         bits   k        size
    --------------------------------------------------------------
           1.00%      1.0x      2,825       27,077   7    3.306 KB
           1.00%      1.5x      4,237       40,611   7    4.958 KB
           0.10%      1.0x      2,825       40,616  10    4.958 KB

inspect tells you what your data contains and hands you the sizing. dedupe runs it through a gate, and always makes a second exact pass so it can report how many of its own drops were false positives -- a number you cannot get anywhere else:

$ python3 -m bloomgate dedupe headlines.jsonl --field headline

  dropped as duplicates   1,176
  kept                    2,824
  gate memory             4,062 bytes (3.97 KB)
  filter fullness         45.8%

  exact dedup would drop  1,175
  false positives         1 (0.09% of all drops)

$ python3 -m bloomgate dedupe headlines.jsonl --field headline --confirm
  ...false positives         0 (0.00% of all drops)

Useful flags: --write out.jsonl to save the unique values, --confirm for zero false positives, --headroom and --error-rate to tune.

How big should it be?

You pick two numbers -- how many items you expect, and how often you can afford to be wrong -- and the size follows from a formula. A million articles at a 1% error rate is 1.17 MB; ten million is 11.4 MB.

Filter size against capacity

python3 -m bloomgate size --error-rate 0.001 prints the table without running anything.

Using it in your own code

from bloomgate import BloomGate

gate = BloomGate(capacity=10_000_000, error_rate=0.01)   # 11.4 MB, chosen once

for article in stream:                       # millions of articles, one at a time
    if gate.offer(article.headline):         # ~3 us; state stays at 11.4 MB forever
        train_on(article)

if gate.filter.fill_ratio > 0.8:             # getting full: time to rotate
    gate = BloomGate(capacity=10_000_000, error_rate=0.01)

Swap BloomGate for ExactSetGate to get exact dedup, or NullGate to turn dedup off. Same interface, same call site. Full reference in docs/05-api-reference.md.

What's in this repo

src/bloomgate/     the library
  bloom.py           the Bloom filter -- the entire idea, 200 lines, no internal deps
  gate.py            NullGate / ExactSetGate / BloomGate behind one interface
  pipeline.py        HashingVectorizer + SGDClassifier, one row at a time
  data.py            builds the seeded fake news stream
  bench.py           throughput and memory measurement
  figures.py         every chart in docs/figures, generated from live runs
  cli.py             python -m bloomgate ...
examples/            five runnable scripts, in reading order
docs/                the documentation set (start at docs/README.md)
tests/               154 pytest tests

Read in this order

# Page What it answers
0 Is this for you? Start here. Symptoms, and what to use instead if this isn't it
9 ML pipelines Leakage, duplicate rows, embeddings, fingerprints, export
8 Recipes Copy-pasteable code: your own files, URLs, restarts, Kafka, rotation, monitoring
1 What a Bloom filter actually is The idea, the maths, and when not to use one
2 How the project is put together Every file, the data flow, the two streaming gotchas that bite
3 Sizing and error rates How big to make it, and the three ways people get that wrong
4 Production patterns Rotation, sharding, normalisation, confirmation
5 API reference Every public function and what it returns
6 Measured results A full run, regenerated by one command
7 Troubleshooting Every likely error, with its cause

Run the examples

Each one explains itself as it prints.

python3 examples/01_bloom_filter_by_hand.py        # the algorithm, no library, 30 lines
python3 examples/02_gate_the_stream.py             # one interface, three gates
python3 examples/03_streaming_text_classifier.py   # a real classifier on a real stream
python3 examples/04_measure_the_error_rate.py      # measuring the rate honestly
python3 examples/05_production_patterns.py         # rotation, sharding, confirmation

Commands

Command What it does
make demo 60-second tour of the data structure
make compare the three-way comparison on a live stream
make report the above, plus the results chart and docs/06-measured-results.md
make bench throughput and memory, without the model
make figures regenerate the dataset-free charts
make inspect FILE what is in your file, and how to size a filter
make dedupe FILE run your file through a gate, report the result
make doctor check this machine can run the project
make prep FILE dedupe rows from your dataset and export the clean set
make leakage SPECS find rows shared between splits (train=train.csv test=test.csv)
make test the test suite
make check tests + figures + report -- what CI would run

Or drive the CLI directly: python3 -m bloomgate demo | size | compare | report | bench | figures | inspect | dedupe | doctor. Useful flags: --duplicate-rate, --error-rate, --headroom, --seed.

How to check these claims yourself

Every number above came from a command, not from a benchmark someone ran once.

make check
  • Counts are deterministic. The stream is seeded, so articles offered (2,310), articles trained on (1,777) and duplicates dropped (534) reproduce exactly on any machine. Timings do not, which is why they are not quoted here.
  • The false-positive rate is measured, not inferred. It comes from offering 1,182 held-out test articles to a filter that had never seen them. Probing with articles the filter had seen would report 100%, which is the bug this project shipped and fixed -- see docs/02.
  • The implementation matches the theory. A test asserts the measured error rate sits on the closed-form Bloom curve within 4 binomial standard errors. That is the test a broken hash function fails.
  • 154 tests, including the ones that catch the mistakes this project actually made: bit-order corruption in saved filters, peek and offer disagreeing, and probes drawn from the wrong pool.

Measured error rate against theory

Licence

MIT -- see LICENSE.

About

bloom filter before feeding the data into the ML pipeline to avoid uncessary training

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages