somukandula commited on
Commit
83a6599
·
verified ·
1 Parent(s): 24335cb

Upload folder using huggingface_hub

Browse files
Files changed (6) hide show
  1. README.md +203 -14
  2. eval_results.json +19 -0
  3. inference.py +121 -0
  4. labels.json +23 -0
  5. model_metadata.json +49 -0
  6. requirements.txt +1 -0
README.md CHANGED
@@ -1,19 +1,208 @@
1
- # Maskara PII Token Perceptron
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- This directory contains the first CPU-trained Maskara span model.
4
 
5
- - Model type: averaged perceptron token classifier
6
- - Labels: BIO tags for synthetic Maskara PII entities
7
- - Training data: generated by `scripts/generate_synthetic_dataset.py`
8
- - Training command: `python scripts/train_token_perceptron.py --data-dir data/synthetic --output maskara/models/pii_token_perceptron.json --epochs 10`
9
 
10
- Validation from the local run:
 
 
 
11
 
12
- - Token accuracy: 0.9976
13
- - Span precision: 0.9712
14
- - Span recall: 0.9890
15
- - Span F1: 0.9800
16
 
17
- This is an MVP detector intended to augment deterministic rules. Structured
18
- PII such as cards, emails, SSNs, phone numbers, and secrets should still be
19
- handled by validators and regex detectors first.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ language:
4
+ - en
5
+ tags:
6
+ - privacy
7
+ - pii
8
+ - token-classification
9
+ - named-entity-recognition
10
+ - security
11
+ - synthetic-data
12
+ pipeline_tag: token-classification
13
+ library_name: maskara
14
+ datasets:
15
+ - somukandula/maskara-synthetic-pii
16
+ metrics:
17
+ - precision
18
+ - recall
19
+ - f1
20
+ model-index:
21
+ - name: maskara
22
+ results:
23
+ - task:
24
+ type: token-classification
25
+ name: PII span detection
26
+ dataset:
27
+ type: somukandula/maskara-synthetic-pii
28
+ name: Maskara Synthetic PII
29
+ split: validation
30
+ metrics:
31
+ - type: accuracy
32
+ value: 0.997638167217761
33
+ name: Token accuracy
34
+ - type: precision
35
+ value: 0.9712230215827338
36
+ name: Span precision
37
+ - type: recall
38
+ value: 0.989010989010989
39
+ name: Span recall
40
+ - type: f1
41
+ value: 0.9800362976406534
42
+ name: Span F1
43
+ ---
44
 
45
+ # Maskara
46
 
47
+ Maskara is a local PII span detector for privacy-preserving LLM middleware. It
48
+ finds sensitive values in prompts before they are sent to cloud model providers,
49
+ so a local SDK can replace them with stable fake twins and restore the original
50
+ values after the model responds.
51
 
52
+ This repository contains the first CPU-trained Maskara detector. It is a small
53
+ custom token classifier, not a Transformers checkpoint. It is designed to run
54
+ inside the `maskara` Python SDK alongside deterministic regex and validator
55
+ detectors.
56
 
57
+ ## What This Model Does
 
 
 
58
 
59
+ The model predicts BIO token tags for common sensitive entities:
60
+
61
+ - `PERSON_NAME`
62
+ - `EMAIL`
63
+ - `PHONE`
64
+ - `ADDRESS`
65
+ - `LOCATION`
66
+ - `CREDIT_CARD`
67
+ - `SSN`
68
+ - `API_KEY`
69
+ - `USERNAME`
70
+ - `PASSWORD`
71
+
72
+ Maskara uses this model for fuzzy spans such as names, locations, addresses,
73
+ and usernames. Structured and high-risk values such as credit cards, SSNs,
74
+ emails, phone numbers, API keys, and passwords should still be protected first
75
+ by deterministic rules and validators.
76
+
77
+ ## How It Helps
78
+
79
+ LLM applications often need cloud model quality, but prompts can contain names,
80
+ emails, phone numbers, addresses, payment test cards, credentials, or internal
81
+ identifiers. Maskara helps by keeping the privacy boundary local:
82
+
83
+ 1. Detect sensitive spans on the user's machine.
84
+ 2. Replace those spans with fake but plausible values.
85
+ 3. Send only the protected prompt to the LLM provider.
86
+ 4. Restore original values locally in the response.
87
+
88
+ The model improves coverage where pure regex is brittle, especially for names
89
+ and location-like text in normal prose.
90
+
91
+ ## Training Data
92
+
93
+ The model was trained on synthetic data generated by the Maskara repository:
94
+
95
+ - Dataset: [`somukandula/maskara-synthetic-pii`](https://huggingface.co/datasets/somukandula/maskara-synthetic-pii)
96
+ - Generator: `scripts/generate_synthetic_dataset.py`
97
+ - Train split: 800 examples
98
+ - Validation split: 120 examples
99
+ - Test split: 120 examples
100
+ - Locales: `en-US`, `en-IN`, `en-GB`
101
+ - Domains: chat prompts, email drafts, support tickets, delivery prompts,
102
+ finance prompts, JSON snippets, code/log snippets, calendar messages, and
103
+ hard negatives
104
+
105
+ The dataset uses fake identities, reserved example domains, and payment
106
+ processor test-card-style values. It does not contain real user vault data.
107
+
108
+ ## Training Procedure
109
+
110
+ The checked-in model was trained locally on CPU with a simple online perceptron
111
+ token classifier.
112
+
113
+ ```bash
114
+ python scripts/generate_synthetic_dataset.py --output-dir data/synthetic
115
+ python scripts/train_token_perceptron.py \
116
+ --data-dir data/synthetic \
117
+ --output maskara/models/pii_token_perceptron.json \
118
+ --epochs 10
119
+ ```
120
+
121
+ Training features include token text, lowercase form, prefixes, suffixes,
122
+ token shape, neighboring tokens, title-case flags, digit flags, `@`, and dash
123
+ signals.
124
+
125
+ ## Evaluation
126
+
127
+ Validation metrics from the local training run:
128
+
129
+ | Metric | Value |
130
+ |---|---:|
131
+ | Token accuracy | 0.9976 |
132
+ | Span precision | 0.9712 |
133
+ | Span recall | 0.9890 |
134
+ | Span F1 | 0.9800 |
135
+
136
+ End-to-end Maskara leakage evaluation on the synthetic test split:
137
+
138
+ | Metric | Value |
139
+ |---|---:|
140
+ | Examples | 120 |
141
+ | Cloud leakage rate | 0.0 |
142
+ | p95 protection latency | ~1.35 ms |
143
+
144
+ These numbers are from synthetic data and should not be interpreted as
145
+ production guarantees. The detector is an MVP baseline for local development.
146
+
147
+ ## Usage
148
+
149
+ Install the local SDK from the Maskara repository, then:
150
+
151
+ ```python
152
+ from maskara import Maskara
153
+
154
+ maskara = Maskara()
155
+
156
+ prompt = "Draft a reply to Maya Rao at maya.rao@example.org."
157
+ protected, ctx = maskara.protect(prompt)
158
+
159
+ # Send `protected` to your LLM provider.
160
+ # Then restore the provider response locally:
161
+ final = maskara.restore(protected, ctx)
162
+ ```
163
+
164
+ Direct model inference from this repo's artifact:
165
+
166
+ ```python
167
+ from maskara.ml import PerceptronPiiDetector
168
+
169
+ detector = PerceptronPiiDetector("pii_token_perceptron.json")
170
+ spans = detector.detect("Email Maya Rao at maya.rao@example.org.")
171
+ print(spans)
172
+ ```
173
+
174
+ ## Files
175
+
176
+ - `pii_token_perceptron.json`: trained perceptron weights and label list
177
+ - `labels.json`: BIO labels emitted by the model
178
+ - `model_metadata.json`: training, dataset, and evaluation metadata
179
+ - `eval_results.json`: validation and leakage evaluation metrics
180
+ - `inference.py`: minimal standalone inference helper for this artifact
181
+ - `requirements.txt`: minimal runtime requirements for standalone use
182
+
183
+ ## Limitations
184
+
185
+ - This is a custom lightweight model, not a general-purpose NER model.
186
+ - It was trained on synthetic data only.
187
+ - It currently focuses on English examples.
188
+ - It should be used together with deterministic detectors for structured PII.
189
+ - It may miss real-world names, addresses, and secrets outside the synthetic
190
+ distribution.
191
+ - It should not be trained on real local vault data.
192
+
193
+ ## Intended Use
194
+
195
+ Use this model as part of local privacy middleware for LLM prompts. It is
196
+ intended for local detection and pseudonymization workflows, not surveillance,
197
+ identity resolution, or user profiling.
198
+
199
+ ## Citation
200
+
201
+ ```bibtex
202
+ @software{maskara2026,
203
+ title = {Maskara: Local PII Detection for Privacy-Preserving LLM Middleware},
204
+ author = {Maskara Contributors},
205
+ year = {2026},
206
+ url = {https://huggingface.co/somukandula/maskara}
207
+ }
208
+ ```
eval_results.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "validation": {
3
+ "token_accuracy": 0.997638167217761,
4
+ "span_precision": 0.9712230215827338,
5
+ "span_recall": 0.989010989010989,
6
+ "span_f1": 0.9800362976406534
7
+ },
8
+ "synthetic_test_leakage": {
9
+ "examples": 120,
10
+ "leaked_examples": [],
11
+ "cloud_leakage_rate": 0.0,
12
+ "p95_protection_latency_ms": 1.3530830037780106
13
+ },
14
+ "notes": [
15
+ "Metrics are from synthetic data generated by the Maskara repo.",
16
+ "The model is intended to augment deterministic structured PII detectors.",
17
+ "These results are not production guarantees."
18
+ ]
19
+ }
inference.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import math
5
+ import re
6
+ from pathlib import Path
7
+
8
+
9
+ TOKEN_RE = re.compile(r"\w+|[^\w\s]", re.UNICODE)
10
+
11
+
12
+ class MaskaraDetector:
13
+ def __init__(self, model_path: str = "pii_token_perceptron.json") -> None:
14
+ payload = json.loads(Path(model_path).read_text(encoding="utf-8"))
15
+ self.labels = payload["labels"]
16
+ self.weights = payload["weights"]
17
+
18
+ def detect(self, text: str) -> list[dict[str, object]]:
19
+ tokens = [
20
+ {"text": match.group(0), "start": match.start(), "end": match.end()}
21
+ for match in TOKEN_RE.finditer(text)
22
+ ]
23
+ predictions = [self._predict(tokens, index) for index in range(len(tokens))]
24
+ return _spans_from_tags(text, tokens, predictions)
25
+
26
+ def _predict(self, tokens: list[dict[str, object]], index: int) -> tuple[str, float]:
27
+ feats = _features(tokens, index)
28
+ scores = {
29
+ label: sum(self.weights.get(label, {}).get(feat, 0.0) for feat in feats)
30
+ for label in self.labels
31
+ }
32
+ ranked = sorted(scores.items(), key=lambda item: item[1], reverse=True)
33
+ label, score = ranked[0]
34
+ runner_up = ranked[1][1] if len(ranked) > 1 else 0.0
35
+ confidence = 1.0 / (1.0 + math.exp(-min(8.0, score - runner_up)))
36
+ return label, confidence
37
+
38
+
39
+ def _spans_from_tags(
40
+ text: str, tokens: list[dict[str, object]], predictions: list[tuple[str, float]]
41
+ ) -> list[dict[str, object]]:
42
+ spans: list[dict[str, object]] = []
43
+ current_start = None
44
+ current_end = None
45
+ current_label = None
46
+ confidences: list[float] = []
47
+
48
+ for token, (tag, confidence) in zip(tokens, predictions):
49
+ if tag == "O":
50
+ if current_label is not None:
51
+ spans.append(_span(text, current_start, current_end, current_label, confidences))
52
+ current_start = current_end = current_label = None
53
+ confidences = []
54
+ continue
55
+ prefix, label = tag.split("-", 1)
56
+ if prefix == "B" or label != current_label:
57
+ if current_label is not None:
58
+ spans.append(_span(text, current_start, current_end, current_label, confidences))
59
+ current_start = int(token["start"])
60
+ current_label = label
61
+ confidences = []
62
+ current_end = int(token["end"])
63
+ confidences.append(confidence)
64
+
65
+ if current_label is not None:
66
+ spans.append(_span(text, current_start, current_end, current_label, confidences))
67
+ return [span for span in spans if span["confidence"] >= 0.60]
68
+
69
+
70
+ def _span(text: str, start: int, end: int, label: str, confidences: list[float]) -> dict[str, object]:
71
+ return {
72
+ "start": start,
73
+ "end": end,
74
+ "text": text[start:end],
75
+ "label": label,
76
+ "confidence": sum(confidences) / max(1, len(confidences)),
77
+ }
78
+
79
+
80
+ def _features(tokens: list[dict[str, object]], index: int) -> list[str]:
81
+ token = str(tokens[index]["text"])
82
+ lower = token.lower()
83
+ previous = str(tokens[index - 1]["text"]).lower() if index else "<BOS>"
84
+ next_token = str(tokens[index + 1]["text"]).lower() if index + 1 < len(tokens) else "<EOS>"
85
+ return [
86
+ "bias",
87
+ f"word={lower}",
88
+ f"prefix2={lower[:2]}",
89
+ f"prefix3={lower[:3]}",
90
+ f"suffix2={lower[-2:]}",
91
+ f"suffix3={lower[-3:]}",
92
+ f"shape={_shape(token)}",
93
+ f"prev={previous}",
94
+ f"next={next_token}",
95
+ f"prev+word={previous}|{lower}",
96
+ f"word+next={lower}|{next_token}",
97
+ f"is_title={token.istitle()}",
98
+ f"is_digit={token.isdigit()}",
99
+ f"has_digit={any(ch.isdigit() for ch in token)}",
100
+ f"has_at={'@' in token}",
101
+ f"has_dash={'-' in token}",
102
+ ]
103
+
104
+
105
+ def _shape(token: str) -> str:
106
+ output = []
107
+ for char in token:
108
+ if char.isupper():
109
+ output.append("X")
110
+ elif char.islower():
111
+ output.append("x")
112
+ elif char.isdigit():
113
+ output.append("d")
114
+ else:
115
+ output.append(char)
116
+ return "".join(output)
117
+
118
+
119
+ if __name__ == "__main__":
120
+ detector = MaskaraDetector()
121
+ print(detector.detect("Email Maya Rao at maya.rao@example.org."))
labels.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ "B-ADDRESS",
3
+ "B-API_KEY",
4
+ "B-CREDIT_CARD",
5
+ "B-EMAIL",
6
+ "B-LOCATION",
7
+ "B-PASSWORD",
8
+ "B-PERSON_NAME",
9
+ "B-PHONE",
10
+ "B-SSN",
11
+ "B-USERNAME",
12
+ "I-ADDRESS",
13
+ "I-API_KEY",
14
+ "I-CREDIT_CARD",
15
+ "I-EMAIL",
16
+ "I-LOCATION",
17
+ "I-PASSWORD",
18
+ "I-PERSON_NAME",
19
+ "I-PHONE",
20
+ "I-SSN",
21
+ "I-USERNAME",
22
+ "O"
23
+ ]
model_metadata.json ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_name": "maskara",
3
+ "artifact": "pii_token_perceptron.json",
4
+ "model_type": "custom_perceptron_token_classifier",
5
+ "task": "pii_span_detection",
6
+ "language": ["en"],
7
+ "labels": [
8
+ "PERSON_NAME",
9
+ "EMAIL",
10
+ "PHONE",
11
+ "ADDRESS",
12
+ "LOCATION",
13
+ "CREDIT_CARD",
14
+ "SSN",
15
+ "API_KEY",
16
+ "USERNAME",
17
+ "PASSWORD"
18
+ ],
19
+ "bio_labels_file": "labels.json",
20
+ "dataset": {
21
+ "repo_id": "somukandula/maskara-synthetic-pii",
22
+ "train_examples": 800,
23
+ "validation_examples": 120,
24
+ "test_examples": 120,
25
+ "generator": "scripts/generate_synthetic_dataset.py",
26
+ "data_policy": "synthetic_only_no_real_user_vault_data"
27
+ },
28
+ "training": {
29
+ "script": "scripts/train_token_perceptron.py",
30
+ "epochs": 10,
31
+ "hardware": "local_cpu",
32
+ "features": [
33
+ "token_text",
34
+ "lowercase_token",
35
+ "prefixes",
36
+ "suffixes",
37
+ "token_shape",
38
+ "previous_token",
39
+ "next_token",
40
+ "titlecase_flag",
41
+ "digit_flags",
42
+ "at_sign_flag",
43
+ "dash_flag"
44
+ ]
45
+ },
46
+ "intended_sdk_use": "Run locally inside Maskara with deterministic validators and regex detectors.",
47
+ "hub_model_url": "https://huggingface.co/somukandula/maskara",
48
+ "hub_dataset_url": "https://huggingface.co/datasets/somukandula/maskara-synthetic-pii"
49
+ }
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ maskara