🕵️ Your First Anonymization¶
Detect sensitive entities and replace them with LLM-generated substitutes -- the simplest end-to-end example of Anonymizer.
📚 What you'll learn¶
- Load a CSV dataset and configure Anonymizer in a few lines
- Preview anonymized results on a small sample before committing to a full run
- Inspect entity detection and replacement with
display_record() - Process the full dataset with
run()
Tip: First time running notebooks? Start with setup instructions.
⚙️ Setup¶
- Check if your
NVIDIA_API_KEYfrom build.nvidia.com is registered for model access.- The default
build.nvidia.com(NVIDIA Build) setup is a convenient way to try Anonymizer and iterate on previews. Use of NVIDIA Build is subject to NVIDIA Build's own terms of service and privacy practices, which are separate from and independent of the NeMo Framework library. NVIDIA Build is intended for evaluation and testing purposes only and may not be used in production environments. Do not upload any confidential information or personal data when using NVIDIA Build. Your use of NVIDIA Build is logged for security purposes and to improve NVIDIA products and services. - Request and token rate limits on
build.nvidia.comvary by account and model access, and lower-volume development access can be slow for full-dataset runs. Start withpreview()on a small sample, then move to your own endpoint for production data and usage.
- The default
- Import the core Anonymizer classes:
Anonymizer,AnonymizerConfig,AnonymizerInput, andSubstitute. Anonymizer()initializes with the default model provider -- no extra config needed.configure_logging(LoggingConfig.default())keeps logs at INFO. Switch toLoggingConfig.debug()when troubleshooting.
In [2]:
Copied!
import getpass
import os
if not os.getenv("NVIDIA_API_KEY"):
key = getpass.getpass("Enter NVIDIA_API_KEY from build.nvidia.com: ").strip()
if not key:
raise RuntimeError("NVIDIA_API_KEY is required to run these notebooks.")
os.environ["NVIDIA_API_KEY"] = key
import getpass
import os
if not os.getenv("NVIDIA_API_KEY"):
key = getpass.getpass("Enter NVIDIA_API_KEY from build.nvidia.com: ").strip()
if not key:
raise RuntimeError("NVIDIA_API_KEY is required to run these notebooks.")
os.environ["NVIDIA_API_KEY"] = key
In [3]:
Copied!
from anonymizer import Anonymizer, AnonymizerConfig, AnonymizerInput, LoggingConfig, Substitute, configure_logging
configure_logging(LoggingConfig.default())
from anonymizer import Anonymizer, AnonymizerConfig, AnonymizerInput, LoggingConfig, Substitute, configure_logging
configure_logging(LoggingConfig.default())
In [4]:
Copied!
anonymizer = Anonymizer()
anonymizer = Anonymizer()
[10:59:16] [INFO] 🔧 Anonymizer initialized with 3 model configs
[10:59:16] [INFO] |-- 🔎 detector: gliner-pii-detector
[10:59:16] [INFO] |-- ✅ validator: gpt-oss-120b
[10:59:16] [INFO] |-- 🧩 augmenter: gpt-oss-120b
📦 Load data and configure¶
AnonymizerInputpoints to your CSV and names the text column.data_summarygives the LLM context about the kind of text it will process.- Records up to 2,000 tokens each work with the default model configs.
AnonymizerConfigwithSubstitute()tells Anonymizer to replace detected entities with LLM-generated synthetic values for names, cities, dates, etc.
In [5]:
Copied!
input_data = AnonymizerInput(
source="https://raw.githubusercontent.com/NVIDIA-NeMo/Anonymizer/refs/heads/main/docs/data/NVIDIA_synthetic_biographies.csv",
text_column="biography",
data_summary="Biographical profiles of individuals",
)
config = AnonymizerConfig(replace=Substitute())
input_data = AnonymizerInput(
source="https://raw.githubusercontent.com/NVIDIA-NeMo/Anonymizer/refs/heads/main/docs/data/NVIDIA_synthetic_biographies.csv",
text_column="biography",
data_summary="Biographical profiles of individuals",
)
config = AnonymizerConfig(replace=Substitute())
👁️ Preview¶
preview()runs on a small sample so you can iterate quickly.- Always preview before processing the full dataset -- it's the fastest way to catch prompt or config issues early.
In [6]:
Copied!
preview = anonymizer.preview(config=config, data=input_data, num_records=3)
preview = anonymizer.preview(config=config, data=input_data, num_records=3)
[10:59:17] [INFO] 👀 Preview mode: 📂 Loaded 3 records from https://raw.githubusercontent.com/NVIDIA-NeMo/Anonymizer/refs/heads/main/docs/data/NVIDIA_synthetic_biographies.csv (column: 'biography')
[10:59:17] [INFO] 🔍 Running entity detection on 3 records
[10:59:17] [INFO] detection labels in scope: (default: 65 labels; see anonymizer.DEFAULT_ENTITY_LABELS for list)
[11:00:13] [INFO] |-- 📋 Detection complete — 77 entities found across 3 records (0 failed) [56.4s]
[11:00:13] [INFO] |-- labels: first_name=22, organization_name=7, age=5, occupation=5, city=4, state=4, degree=4, university=4, field_of_study=4, political_view=4, last_name=3, race_ethnicity=3, language=2, street_address=2, place_name=1, date_of_birth=1, employment_status=1, religious_belief=1
[11:00:13] [INFO] 🔄 Running Substitute replacement
[11:00:54] [INFO] |-- 📋 Replacement complete (0 failed) [40.9s]
[11:00:54] [INFO] 🎉 Pipeline complete — 3 records processed, 0 total failures
🔍 Inspect¶
display_record()shows the original text with highlighted entities, the replacement map, and the anonymized output -- all in one view.- The result dataframe has original and substituted text side-by-side.
In [7]:
Copied!
preview.display_record(0)
preview.display_record(0)
In [8]:
Copied!
preview.display_record(1)
preview.display_record(1)
In [9]:
Copied!
preview.dataframe
preview.dataframe
Out[9]:
| biography | biography_with_spans | final_entities | biography_replaced | |
|---|---|---|---|---|
| 0 | Bobby Watford, a 40‑year‑old Mexican veterinar... | <first_name>Bobby</first_name> <last_name>Watf... | {'entities': [{'end_position': 5, 'id': 'first... | Ethan Kline, a 45‑year‑old Filipino zoological... |
| 1 | Idilio Bell is a 37‑year‑old astronomer living... | <first_name>Idilio</first_name> <last_name>Bel... | {'entities': [{'end_position': 6, 'id': 'first... | Dario Hawthorne is a 36‑year‑old geologist liv... |
| 2 | Jodi Allison, 36, lives at 204 Bluegrass in Cl... | <first_name>Jodi</first_name> <last_name>Allis... | {'entities': [{'end_position': 4, 'id': 'first... | Tara Harper, 42, lives at 317 Riverbend in Cha... |
🚀 Full run¶
run()processes the entire dataset with the same config you previewed.- Access the output via
result.dataframe.
In [10]:
Copied!
result = anonymizer.run(config=config, data=input_data)
print(result)
result = anonymizer.run(config=config, data=input_data)
print(result)
[11:00:55] [INFO] 📂 Loaded 25 records from https://raw.githubusercontent.com/NVIDIA-NeMo/Anonymizer/refs/heads/main/docs/data/NVIDIA_synthetic_biographies.csv (column: 'biography')
[11:00:55] [INFO] 🔍 Running entity detection on 25 records
[11:00:55] [INFO] detection labels in scope: (default: 65 labels; see anonymizer.DEFAULT_ENTITY_LABELS for list)
[11:03:15] [INFO] |-- 📋 Detection complete — 661 entities found across 25 records (0 failed) [139.8s]
[11:03:15] [INFO] |-- labels: first_name=152, organization_name=68, occupation=45, city=40, field_of_study=36, university=35, race_ethnicity=30, last_name=27, age=26, state=26, degree=26, political_view=25, religious_belief=25, street_address=23, language=18, place_name=12, employment_status=10, county=10, date_of_birth=9, education_level=6, date=5, company_name=3, landmark=1, country=1, gender=1, postcode=1
[11:03:15] [INFO] 🔄 Running Substitute replacement
[11:04:40] [INFO] |-- 📋 Replacement complete (0 failed) [85.0s]
[11:04:40] [INFO] 🎉 Pipeline complete — 25 records processed, 0 total failures
AnonymizerResult(rows=25, columns=4, trace_columns=22, failed_records=0)
In [11]:
Copied!
result.dataframe.head()
result.dataframe.head()
Out[11]:
| biography | biography_with_spans | final_entities | biography_replaced | |
|---|---|---|---|---|
| 0 | Bobby Watford, a 40‑year‑old Mexican veterinar... | <first_name>Bobby</first_name> <last_name>Watf... | {'entities': array([{'end_position': 5, 'id': ... | Ethan Hawkins, a 52‑year‑old Filipino zoologis... |
| 1 | Idilio Bell is a 37‑year‑old astronomer living... | <first_name>Idilio</first_name> <last_name>Bel... | {'entities': array([{'end_position': 6, 'id': ... | Ravi Khan is a 42‑year‑old geophysicist living... |
| 2 | Jodi Allison, 36, lives at 204 Bluegrass in Cl... | <first_name>Jodi</first_name> <last_name>Allis... | {'entities': array([{'end_position': 4, 'id': ... | Lena Keller, 42, lives at 317 Willowbrook in E... |
| 3 | James Mills is a 69‑year‑old paramedic who liv... | <first_name>James</first_name> <last_name>Mill... | {'entities': array([{'end_position': 5, 'id': ... | Robert Harper is a 74‑year‑old firefighter who... |
| 4 | Nancy Burton is a 21‑year‑old cashier who live... | <first_name>Nancy</first_name> <last_name>Burt... | {'entities': array([{'end_position': 5, 'id': ... | Maya Keller is a 23‑year‑old stock clerk who l... |
📊 (Optional) Evaluate replacement quality¶
evaluate()is a separate, opt-in step that scores the output with LLM-as-judge metrics.- For Substitute, all four metrics run: Detection Validity, Type Fidelity, Relational Consistency, Attribute Fidelity.
- Skip it for routine runs; call it when you want LLM-side confidence on the output. Costs LLM calls per record, so try it on
previewfirst.
In [12]:
Copied!
evaluated = anonymizer.evaluate(preview)
evaluated.display_record(0)
evaluated = anonymizer.evaluate(preview)
evaluated.display_record(0)
⏭️ Next steps¶
- 🔍 Inspecting Detected Entities -- dig into what the detection pipeline found and debug quality.
- 🎯 Choosing a Replacement Strategy -- compare Redact, Annotate, Hash, and Substitute side-by-side.
- ✏️ Rewriting Biographies -- generate privacy-safe paraphrases instead of token-level replacements.