sentiment_analysis / evaluate.py
vojmahdal's picture
Upload 28 files
f390b04 verified
Raw
History Blame Contribute Delete
4.33 kB
"""
Evaluation scripts for the extraction system.
Provides entity-level evaluation of NER (precision / recall / F1 via seqeval)
and recall-oriented evaluation of the anonymizer. These scripts produce the
numbers used in the "Testing" chapter of the thesis.
Usage:
python evaluate.py ner # evaluate NER on a small annotated sample
python evaluate.py anonymize # evaluate PII recall (NER vs regex)
The samples here are tiny illustrative examples. For the thesis, replace them
with a real annotated dataset (e.g. a subset of CoNLL-2003 for NER, or
ai4privacy/pii-masking-200k for anonymization).
"""
from __future__ import annotations
import sys
# ---------------------------------------------------------------------------
# NER evaluation (entity level, seqeval)
# ---------------------------------------------------------------------------
def evaluate_ner():
from seqeval.metrics import classification_report, f1_score, precision_score, recall_score
from processors import ner
# Each example: (tokens, gold BIO tags). Illustrative only.
samples = [
(
["My", "name", "is", "John", "Smith", "from", "London"],
["O", "O", "O", "B-PER", "I-PER", "O", "B-LOC"],
),
(
["Sarah", "works", "at", "Google", "in", "Berlin"],
["B-PER", "O", "O", "B-ORG", "O", "B-LOC"],
),
]
y_true, y_pred = [], []
for tokens, gold in samples:
text = " ".join(tokens)
ents = ner.extract_entities(text)
# Build predicted BIO tags aligned to whitespace tokens.
pred = ["O"] * len(tokens)
# character offset of each token
offsets = []
pos = 0
for tok in tokens:
start = text.index(tok, pos)
offsets.append((start, start + len(tok)))
pos = start + len(tok)
for ent in ents:
etype = ent["type"]
first = True
for i, (s, e) in enumerate(offsets):
# token overlaps the entity span
if s >= ent["start"] and e <= ent["end"] + 1:
pred[i] = ("B-" if first else "I-") + etype
first = False
y_true.append(gold)
y_pred.append(pred)
print("=== NER evaluation (entity level) ===")
print(classification_report(y_true, y_pred))
print(f"Precision: {precision_score(y_true, y_pred):.4f}")
print(f"Recall: {recall_score(y_true, y_pred):.4f}")
print(f"F1: {f1_score(y_true, y_pred):.4f}")
# ---------------------------------------------------------------------------
# Anonymization evaluation (PII recall: NER+Presidio vs regex only)
# ---------------------------------------------------------------------------
def evaluate_anonymize():
from processors import anonymizer
import re
# (text, list of PII substrings that MUST be removed)
samples = [
("My name is John Smith, email john@example.com", ["John Smith", "john@example.com"]),
("Call Sarah at +1 202 555 0143", ["Sarah", "+1 202 555 0143"]),
("I live in Berlin and work at Google", ["Berlin", "Google"]),
]
def recall(anon_fn):
found, total = 0, 0
for text, pii_list in samples:
anon = anon_fn(text)
for pii in pii_list:
total += 1
# PII counts as removed if it no longer appears verbatim
if pii.lower() not in anon.lower():
found += 1
return found / total if total else 0.0
# regex-only baseline (the original approach)
def regex_only(text):
from processors.anonymizer import _regex_anonymize
return _regex_anonymize(text)
print("=== Anonymization evaluation (PII recall) ===")
print(f"Active backend: {anonymizer.backend_name()}")
print(f"Recall (regex only): {recall(regex_only):.2%}")
print(f"Recall (full system): {recall(anonymizer.anonymize_text):.2%}")
print("\nNote: full system requires Presidio + spaCy model for name/location recall.")
if __name__ == "__main__":
mode = sys.argv[1] if len(sys.argv) > 1 else "ner"
if mode == "ner":
evaluate_ner()
elif mode == "anonymize":
evaluate_anonymize()
else:
print("Usage: python evaluate.py [ner|anonymize]")