Legally validated evaluation framework for voice anonymization
Reference implementation of the Singling Out and Linkability metrics from:
N. Vauquier, B. M. L. Srivastava, S. A. Hosseini, E. Vincent. Legally validated evaluation framework for voice anonymization. Interspeech 2025. [paper]
The two metrics turn the singling out and linkability criteria of the Article 29 Working Party's Opinion 05/2014 on Anonymization Techniques, endorsed by the European Data Protection Board, into quantities you can measure on speech. The framework was formally validated by the French Data Protection Authority (CNIL).
The motivating finding: the equal error rate is close to blind to residual re-identification risk. Across attacker models and conversation lengths the EER barely moves, while Linkability and Singling Out move a great deal.
Install
pip install voice-anonymization-legal-eval # core
pip install "voice-anonymization-legal-eval[all]" # plus h5py and matplotlib
From a checkout, for development:
pip install -e ".[dev]"
Python 3.9 or newer. The core needs only NumPy and SciPy; h5py is needed to
read HDF5 x-vector files, matplotlib to draw figures. SciPy and h5py are
imported only where they are used, so most of the package works without them.
The scripts under examples/ and scripts/ also run straight from a clone
without installing anything, falling back to the repo root on sys.path. An
installed copy always takes priority, so this never shadows a real install.
Try it in 30 seconds
legal-eval demo --output-dir demo_output
That builds a synthetic corpus, simulates two degrees of anonymization, computes
all three metrics across population sizes and conversation lengths, and writes a
figure. No data, no GPU, no model. For the same thing as readable code, see
examples/01_quickstart.py.
To redraw the paper's published curves and check them against the values quoted in its text:
python examples/02_reproduce_paper_figures.py --output-dir figures
What this package does, and what it does not
This package evaluates speaker embeddings. It takes x-vectors (or any other speaker embeddings) and computes the re-identification risk they carry.
It does not anonymize speech and does not train embedding extractors. Those
are upstream steps, done with the toolkits the paper used, and re-implementing
them here would only produce a worse copy. docs/reproduction.md
pins the exact external systems and versions.
speech ──▶ anonymization ──▶ x-vector extraction ──▶ THIS PACKAGE
(VPC B1/B1.a) (Sidekit ECAPA-TDNN) Singling Out
external external Linkability
ROCCH-EER
To evaluate your own system: anonymize your audio, extract embeddings with your attacker's extractor, and point this package at them. The metrics do not care how the embeddings were produced.
The metrics
| Metric | Question it answers | Chance level |
|---|---|---|
Singling Out π_sing |
Can an attacker isolate exactly one speaker out of N? |
exp(-1) ≈ 37% |
Linkability π_link |
Can an attacker match a test recording to the right speaker among N'? |
1/N' |
| ROCCH-EER | The conventional speaker-verification baseline, plotted as 1 - EER. |
50% |
Both legal metrics are reported against the number of speakers the attacker must
search and against the conversation length L, the number of utterances averaged
per speaker. docs/metrics.md gives the definitions, the
calibration rule and the correspondence to the paper's equations.
Using it on your own data
Point it at Kaldi-style directories, each holding a spk2utt file and an
xvector.h5 with one dataset per utterance id:
# Linkability and EER: enrollment set A, test set B
legal-eval score-matrix --enroll-dir data/cv11-A --test-dir data/cv11-B \
--conversation-length 1 --attacker informed --output scores/informed_L1.npy
legal-eval linkability --score-matrix scores/informed_L1.npy \
--output results/linkability_informed_L1.json
legal-eval eer --score-matrix scores/informed_L1.npy \
--output results/eer_informed_L1.json
# Singling Out: the roles of A and B swap, per the paper
legal-eval singling-out --enroll-dir data/cv11-B --test-dir data/cv11-A \
--conversation-length 1 --attacker informed \
--output results/singling_out_informed_L1.json
legal-eval plot --results-dir results --output figures/paper_figure.pdf
Or from Python:
from legal_eval import (
build_speaker_embeddings, build_test_embeddings,
cosine_score_matrix, linkability_sweep,
)
enroll = build_speaker_embeddings(enroll_spk2utt, utt2xvector)
test, _ = build_test_embeddings(test_spk2utt, utt2xvector, conversation_length=3)
scores = cosine_score_matrix(enroll, test)
result = linkability_sweep(scores, speaker_counts=[20, 100, 1000, 10000])
print(result.mean())
VoicePrivacy Challenge
The metrics run directly on a VPC 2026 evaluation, reusing the speaker embeddings its ASV step already caches. No model, no GPU, no second pass over audio:
pip install "voice-anonymization-legal-eval[vpc]"
legal-eval vpc --results-dir exp/asv_anon_mcadams \
--distractors train-clean-360_mcadams --output results/legal_mcadams.csv
See docs/vpc-benchmark.md, which also covers the two
things to get right before publishing a comparison: VPC's evaluation sets hold
far fewer speakers than the paper's, and the VPC repo already uses the name
"linkability" for a different quantity.
Reproducing the paper
The full pipeline needs roughly 1,700 hours of anonymized Common Voice and three
trained x-vector extractors, so it is not something to re-run casually.
docs/reproduction.md sets out what is needed at each
stage and what this repository can verify on its own.
legal_eval/data/paper_results/ ships the values behind all nine panels of the paper's
Figure 1: three metrics, three conversation lengths, four attacker models.
python examples/02_reproduce_paper_figures.py redraws the figure from them and
checks it against the values quoted in the paper's text.
The twelve cosine score matrices those results come from are on Zenodo at 10.5281/zenodo.23142030 (5.2 GB, CC-BY-4.0), together with the Common Voice subset definitions. With them you can recompute every Linkability and EER curve from scratch rather than taking the published numbers on trust:
pip install zenodo_get && zenodo_get 10.5281/zenodo.23142030 -o release/
python examples/03_verify_against_score_matrices.py --release-dir release/
Read docs/data-release.md before redistributing them.
How we know it is right
The implementation was checked against the original cosine score matrices the paper's numbers came from. Every published Linkability point, across all four attacker models and all three conversation lengths, was recomputed:
| Published points recomputed | 2,640 |
| Mean absolute difference | 0.0007 |
| Worst difference at any point | 0.005 |
| Correlation with published | > 0.9987 |
The residual is Monte Carlo noise from averaging only five runs; it shrinks
further with --estimator exact. Reproduce it with
examples/03_verify_against_score_matrices.py
once you have the score matrices.
Each full condition takes 0.6 seconds, against hours for the original code.
The metrics are additionally pinned by independent oracles and theoretical
baselines rather than by their own past output; see
docs/provenance.md.
Differences from the original experiment code
The implementation here is faithful in its mathematics and corrects three
reproducibility defects in the internal scripts: an RNG-sharing bug across
parallel workers, a fold loop that silently reused one data split, and the use of
random.sample on a set, which has been an error since Python 3.11. Each is
documented, and the original behaviour remains reachable by a flag where it
affects results. See docs/differences.md.
Linkability also runs far faster here. Explicit candidate-set sampling is
replaced by an equivalent hypergeometric draw, which collapses the paper's full
sweep from hours to seconds without changing the sampling distribution. The
derivation is in docs/metrics.md and the equivalence is
asserted in the test suite.
Tests
pytest -q
The suite pins behaviour rather than just exercising it:
- Singling Out: an uninformative attacker isolates at
exp(-1), the PSO baseline. - Linkability: the fast estimator matches literal candidate-set sampling.
- ROCCH-EER: matches a brute-force search over randomised decision rules, and the analytic EER of separated Gaussians.
- Both legal metrics respond to conversation length while the EER stays flat.
Releasing
See docs/releasing.md. Versioning treats the numbers the
metrics produce as the public contract, not just the Python API: anything that
moves a value is a major version and gets an entry in
docs/differences.md.
Citation
@inproceedings{vauquier25_interspeech,
title = {Legally validated evaluation framework for voice anonymization},
author = {Nathalie Vauquier and Brij Mohan Lal Srivastava and
Seyed Ahmad Hosseini and Emmanuel Vincent},
booktitle = {Interspeech 2025},
year = {2025},
}
License
MIT, see LICENSE. The ROCCH-EER here is an independent implementation; the BOSARIS toolkit used in the original experiments is licensed for non-commercial use only and is deliberately not vendored.
Metadata
Release files for voice-anonymization-legal-eval 2.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| voice_anonymization_legal_eval-2.1.0.tar.gz | 149.8 kB | With provenance | Details for voice_anonymization_legal_eval-2.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| voice_anonymization_legal_eval-2.1.0-py3-none-any.whl | Python 3 | none | any | Details for voice_anonymization_legal_eval-2.1.0-py3-none-any.whl |
Total release size: 289.5 kB
Release files / voice_anonymization_legal_eval-2.1.0.tar.gz
| Download URL | voice_anonymization_legal_eval-2.1.0.tar.gz |
|---|---|
| Size | 149.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dab3d53ec004b9250d04cb53d3ea6b3146047a76cddc4100ce3bafb6d29d4d72
|
|
BLAKE2b-256 checksum How to use checksums |
0125c75337cf18e8cd780c1c7b3992bd635093570a2fa766998948fdcf032fb8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.
Transparency logRelease files / voice_anonymization_legal_eval-2.1.0-py3-none-any.whl
| Download URL | voice_anonymization_legal_eval-2.1.0-py3-none-any.whl |
|---|---|
| Size | 139.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ce9c2b131a9a1a6383b0e3d07bc4a921b95569796682a00772653010cc1fc103
|
|
BLAKE2b-256 checksum How to use checksums |
079e3c1f1d55280f55cedcfd927f7d0b544c5accd543511d78d89cf306eccc69
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.
Transparency logSecurity for voice-anonymization-legal-eval 2.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for voice_anonymization_legal_eval-2.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.