tau-eval 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tau_eval-0.1.0/PKG-INFO +170 -0
- tau_eval-0.1.0/README.md +127 -0
- tau_eval-0.1.0/pyproject.toml +109 -0
- tau_eval-0.1.0/setup.cfg +4 -0
- tau_eval-0.1.0/tau_eval/__init__.py +18 -0
- tau_eval-0.1.0/tau_eval/experiment.py +231 -0
- tau_eval-0.1.0/tau_eval/logger.py +28 -0
- tau_eval-0.1.0/tau_eval/metrics/bertscore.py +35 -0
- tau_eval-0.1.0/tau_eval/metrics/cola.py +89 -0
- tau_eval-0.1.0/tau_eval/metrics/luar.py +54 -0
- tau_eval-0.1.0/tau_eval/metrics/meteor.py +46 -0
- tau_eval-0.1.0/tau_eval/metrics/nli.py +78 -0
- tau_eval-0.1.0/tau_eval/metrics/perplexity.py +24 -0
- tau_eval-0.1.0/tau_eval/metrics/rouge.py +28 -0
- tau_eval-0.1.0/tau_eval/metrics/sbert.py +52 -0
- tau_eval-0.1.0/tau_eval/models/__init__.py +4 -0
- tau_eval-0.1.0/tau_eval/models/anonymizer.py +10 -0
- tau_eval-0.1.0/tau_eval/models/authorship.py +129 -0
- tau_eval-0.1.0/tau_eval/models/dummy.py +12 -0
- tau_eval-0.1.0/tau_eval/models/llm_api.py +107 -0
- tau_eval-0.1.0/tau_eval/models/presidio.py +340 -0
- tau_eval-0.1.0/tau_eval/models/styleremix.py +163 -0
- tau_eval-0.1.0/tau_eval/tasks/__init__.py +5 -0
- tau_eval-0.1.0/tau_eval/tasks/customtask.py +14 -0
- tau_eval-0.1.0/tau_eval/tasks/deidentification.py +113 -0
- tau_eval-0.1.0/tau_eval/utils.py +273 -0
- tau_eval-0.1.0/tau_eval/visualization.py +633 -0
- tau_eval-0.1.0/tau_eval.egg-info/PKG-INFO +170 -0
- tau_eval-0.1.0/tau_eval.egg-info/SOURCES.txt +31 -0
- tau_eval-0.1.0/tau_eval.egg-info/dependency_links.txt +1 -0
- tau_eval-0.1.0/tau_eval.egg-info/requires.txt +28 -0
- tau_eval-0.1.0/tau_eval.egg-info/top_level.txt +1 -0
- tau_eval-0.1.0/tests/test_metrics.py +0 -0
tau_eval-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tau-eval
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Text Anonymization Evaluation Library
|
|
5
|
+
Author-email: Gabriel Loiseau <gabriel.loiseau@hornetsecurity.com>
|
|
6
|
+
Maintainer-email: Gabriel Loiseau <gabriel.loiseau@hornetsecurity.com>
|
|
7
|
+
License-Expression: GPL-3.0
|
|
8
|
+
Keywords: Text anonymization,evaluation,NLP
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: transformers<5.0.0,>=4.48.0
|
|
18
|
+
Requires-Dist: sentence-transformers>=3.3.1
|
|
19
|
+
Requires-Dist: torch>=2.5.0
|
|
20
|
+
Requires-Dist: evaluate>=0.4.1
|
|
21
|
+
Requires-Dist: datasets>=2.14.4
|
|
22
|
+
Requires-Dist: huggingface-hub>=0.20.0
|
|
23
|
+
Requires-Dist: tasknet>=1.57.0
|
|
24
|
+
Requires-Dist: tasksource>=0.0.47
|
|
25
|
+
Requires-Dist: ipywidgets>=8.1.5
|
|
26
|
+
Requires-Dist: ipykernel>=6.29.5
|
|
27
|
+
Requires-Dist: rich>=14.0.0
|
|
28
|
+
Requires-Dist: accelerate>=1.6.0
|
|
29
|
+
Requires-Dist: bert-score>=0.3.13
|
|
30
|
+
Requires-Dist: nltk>=3.9.1
|
|
31
|
+
Requires-Dist: rouge-score>=0.1.2
|
|
32
|
+
Requires-Dist: faker>=37.1.0
|
|
33
|
+
Requires-Dist: presidio-analyzer>=2.2.358
|
|
34
|
+
Requires-Dist: presidio-anonymizer>=2.2.358
|
|
35
|
+
Requires-Dist: pip>=25.0.1
|
|
36
|
+
Requires-Dist: pytest>=8.3.5
|
|
37
|
+
Provides-Extra: tests
|
|
38
|
+
Requires-Dist: pytest; extra == "tests"
|
|
39
|
+
Requires-Dist: accelerate>=0.20.3; extra == "tests"
|
|
40
|
+
Provides-Extra: quality
|
|
41
|
+
Requires-Dist: ruff; extra == "quality"
|
|
42
|
+
Requires-Dist: pyyaml>=5.3.1; extra == "quality"
|
|
43
|
+
|
|
44
|
+
# 𝜏 Tau-Eval: A Unified Evaluation Framework for Useful and Private Text Anonymization
|
|
45
|
+
|
|
46
|
+
**Tau-Eval** is a user-friendly, modular, and customizable Python library designed to benchmark and evaluate text anonymization algorithms. It enables granular analysis of anonymization impacts from both privacy and utility perspectives. Tau-Eval seamlessly integrates with [LiteLLM](https://www.litellm.ai/) and [🤗 Hugging Face](https://huggingface.co/) to support a wide range of datasets, models, and evaluation metrics.
|
|
47
|
+
|
|
48
|
+
<div align="center">
|
|
49
|
+
|
|
50
|
+
[](LICENSE)
|
|
51
|
+
[](https://pypi.org/project/xxx/xxx/)
|
|
52
|
+
[](https://www.python.org/downloads/)
|
|
53
|
+
[](https://github.com/.../tree/main/examples)
|
|
54
|
+
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
### From PyPI
|
|
61
|
+
|
|
62
|
+
Install Tau-Eval via pip:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
pip install tau-eval
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### From source
|
|
69
|
+
|
|
70
|
+
To install from source:
|
|
71
|
+
|
|
72
|
+
1) Clone this repository on your own path:
|
|
73
|
+
```
|
|
74
|
+
git clone https://github.com/gabrielloiseau/tau-eval.git
|
|
75
|
+
cd tau-eval
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
2) Create an environment with your own preferred package manager. We used [python 3.9](https://www.python.org/downloads/release/python-390/) and dependencies listed in [`pyproject.toml`](pyproject.toml). If you use [conda](https://docs.conda.io/en/latest/), you can just run the following commands from the root of the project:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
conda create --name taueval python=3.9 # create the environment
|
|
82
|
+
conda activate taueval # activate the environment
|
|
83
|
+
pip install --user -r pyproject.toml # install the required packages
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
## Quickstart
|
|
88
|
+
|
|
89
|
+
Tau-Eval is designed for flexibility. With just a few lines of code, you can set up and run evaluations.
|
|
90
|
+
|
|
91
|
+
#### 1. Define Your Anonymization Model
|
|
92
|
+
|
|
93
|
+
Create a custom anonymization model by extending the Anonymizer interface:
|
|
94
|
+
```python
|
|
95
|
+
from tau_eval.models import Anonymizer
|
|
96
|
+
|
|
97
|
+
class TestModel(Anonymizer):
|
|
98
|
+
def __init__(self):
|
|
99
|
+
self.name = "Test Model"
|
|
100
|
+
|
|
101
|
+
def anonymize(self, text: str) -> str:
|
|
102
|
+
# Implement anonymization logic
|
|
103
|
+
return text
|
|
104
|
+
|
|
105
|
+
def anonymize_batch(self, texts: list[str]) -> list[str]:
|
|
106
|
+
# Batch processing
|
|
107
|
+
return texts
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
Or use prebuilt models from `tau_eval.models`.
|
|
111
|
+
|
|
112
|
+
#### 2. Configure Evaluation Metrics
|
|
113
|
+
Use built-in metrics from `tau_eval.metrics` or define your own following this signature:
|
|
114
|
+
```python
|
|
115
|
+
Callable[[str | list[str], str | list[str]], dict]
|
|
116
|
+
```
|
|
117
|
+
This allows complete control over what and how you evaluate.
|
|
118
|
+
|
|
119
|
+
#### 3. Instantiate Tasks
|
|
120
|
+
Tasks can be created using prebuilt options in `tau_eval.tasks`, or customized using `CustomTask`. Tau-Eval also supports [tasksource](https://github.com/sileod/tasksource) for dataset integration.
|
|
121
|
+
|
|
122
|
+
#### 4. Configure and Run Your Experiment
|
|
123
|
+
Define an experiment configuration:
|
|
124
|
+
```python
|
|
125
|
+
from tau_eval.config import ExperimentConfig
|
|
126
|
+
|
|
127
|
+
config = ExperimentConfig(
|
|
128
|
+
exp_name="test-experiment",
|
|
129
|
+
classifier_name="medicalai/ClinicalBERT",
|
|
130
|
+
train_task_models=True,
|
|
131
|
+
train_with_generations=False,
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
Run the experiment:
|
|
135
|
+
```python
|
|
136
|
+
from tau_eval.experiment import Experiment
|
|
137
|
+
|
|
138
|
+
Experiment(
|
|
139
|
+
models=[m1, m2, m3, m4, m5],
|
|
140
|
+
metrics=["bertscore"],
|
|
141
|
+
tasks=[mednli, pii],
|
|
142
|
+
config=config
|
|
143
|
+
).run()
|
|
144
|
+
```
|
|
145
|
+
#### 5. Visualize Results
|
|
146
|
+
|
|
147
|
+
Tau-Eval includes built-in visualization tools to compare model anonymization strategies and evaluation results. You can find them with `tau_eval.visualization`.
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
## Tutorials
|
|
151
|
+
|
|
152
|
+
You can find our tutorials to learn how to better leverage **Tau-Eval** in the [`examples/`]() folder.
|
|
153
|
+
|
|
154
|
+
There you can also find a set of interesting case studies using real-world datasets! :chart_with_upwards_trend:
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
## Contributors
|
|
159
|
+
|
|
160
|
+
- **[Gabriel Loiseau](https://gabrielloiseau.github.io)**, *Hornetsecurty, Inria Lille*
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
## Citation
|
|
164
|
+
|
|
165
|
+
If you use 𝜏 **Tau-Eval** in your work, please cite our paper as follows:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
```
|
tau_eval-0.1.0/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# 𝜏 Tau-Eval: A Unified Evaluation Framework for Useful and Private Text Anonymization
|
|
2
|
+
|
|
3
|
+
**Tau-Eval** is a user-friendly, modular, and customizable Python library designed to benchmark and evaluate text anonymization algorithms. It enables granular analysis of anonymization impacts from both privacy and utility perspectives. Tau-Eval seamlessly integrates with [LiteLLM](https://www.litellm.ai/) and [🤗 Hugging Face](https://huggingface.co/) to support a wide range of datasets, models, and evaluation metrics.
|
|
4
|
+
|
|
5
|
+
<div align="center">
|
|
6
|
+
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://pypi.org/project/xxx/xxx/)
|
|
9
|
+
[](https://www.python.org/downloads/)
|
|
10
|
+
[](https://github.com/.../tree/main/examples)
|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
### From PyPI
|
|
18
|
+
|
|
19
|
+
Install Tau-Eval via pip:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
pip install tau-eval
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### From source
|
|
26
|
+
|
|
27
|
+
To install from source:
|
|
28
|
+
|
|
29
|
+
1) Clone this repository on your own path:
|
|
30
|
+
```
|
|
31
|
+
git clone https://github.com/gabrielloiseau/tau-eval.git
|
|
32
|
+
cd tau-eval
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
2) Create an environment with your own preferred package manager. We used [python 3.9](https://www.python.org/downloads/release/python-390/) and dependencies listed in [`pyproject.toml`](pyproject.toml). If you use [conda](https://docs.conda.io/en/latest/), you can just run the following commands from the root of the project:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
conda create --name taueval python=3.9 # create the environment
|
|
39
|
+
conda activate taueval # activate the environment
|
|
40
|
+
pip install --user -r pyproject.toml # install the required packages
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
## Quickstart
|
|
45
|
+
|
|
46
|
+
Tau-Eval is designed for flexibility. With just a few lines of code, you can set up and run evaluations.
|
|
47
|
+
|
|
48
|
+
#### 1. Define Your Anonymization Model
|
|
49
|
+
|
|
50
|
+
Create a custom anonymization model by extending the Anonymizer interface:
|
|
51
|
+
```python
|
|
52
|
+
from tau_eval.models import Anonymizer
|
|
53
|
+
|
|
54
|
+
class TestModel(Anonymizer):
|
|
55
|
+
def __init__(self):
|
|
56
|
+
self.name = "Test Model"
|
|
57
|
+
|
|
58
|
+
def anonymize(self, text: str) -> str:
|
|
59
|
+
# Implement anonymization logic
|
|
60
|
+
return text
|
|
61
|
+
|
|
62
|
+
def anonymize_batch(self, texts: list[str]) -> list[str]:
|
|
63
|
+
# Batch processing
|
|
64
|
+
return texts
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
Or use prebuilt models from `tau_eval.models`.
|
|
68
|
+
|
|
69
|
+
#### 2. Configure Evaluation Metrics
|
|
70
|
+
Use built-in metrics from `tau_eval.metrics` or define your own following this signature:
|
|
71
|
+
```python
|
|
72
|
+
Callable[[str | list[str], str | list[str]], dict]
|
|
73
|
+
```
|
|
74
|
+
This allows complete control over what and how you evaluate.
|
|
75
|
+
|
|
76
|
+
#### 3. Instantiate Tasks
|
|
77
|
+
Tasks can be created using prebuilt options in `tau_eval.tasks`, or customized using `CustomTask`. Tau-Eval also supports [tasksource](https://github.com/sileod/tasksource) for dataset integration.
|
|
78
|
+
|
|
79
|
+
#### 4. Configure and Run Your Experiment
|
|
80
|
+
Define an experiment configuration:
|
|
81
|
+
```python
|
|
82
|
+
from tau_eval.config import ExperimentConfig
|
|
83
|
+
|
|
84
|
+
config = ExperimentConfig(
|
|
85
|
+
exp_name="test-experiment",
|
|
86
|
+
classifier_name="medicalai/ClinicalBERT",
|
|
87
|
+
train_task_models=True,
|
|
88
|
+
train_with_generations=False,
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
Run the experiment:
|
|
92
|
+
```python
|
|
93
|
+
from tau_eval.experiment import Experiment
|
|
94
|
+
|
|
95
|
+
Experiment(
|
|
96
|
+
models=[m1, m2, m3, m4, m5],
|
|
97
|
+
metrics=["bertscore"],
|
|
98
|
+
tasks=[mednli, pii],
|
|
99
|
+
config=config
|
|
100
|
+
).run()
|
|
101
|
+
```
|
|
102
|
+
#### 5. Visualize Results
|
|
103
|
+
|
|
104
|
+
Tau-Eval includes built-in visualization tools to compare model anonymization strategies and evaluation results. You can find them with `tau_eval.visualization`.
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
## Tutorials
|
|
108
|
+
|
|
109
|
+
You can find our tutorials to learn how to better leverage **Tau-Eval** in the [`examples/`]() folder.
|
|
110
|
+
|
|
111
|
+
There you can also find a set of interesting case studies using real-world datasets! :chart_with_upwards_trend:
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
## Contributors
|
|
116
|
+
|
|
117
|
+
- **[Gabriel Loiseau](https://gabrielloiseau.github.io)**, *Hornetsecurty, Inria Lille*
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
## Citation
|
|
121
|
+
|
|
122
|
+
If you use 𝜏 **Tau-Eval** in your work, please cite our paper as follows:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
```
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tau-eval"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Text Anonymization Evaluation Library"
|
|
5
|
+
license = "GPL-3.0"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Gabriel Loiseau", email = "gabriel.loiseau@hornetsecurity.com" },
|
|
9
|
+
]
|
|
10
|
+
maintainers = [
|
|
11
|
+
{ name = "Gabriel Loiseau", email = "gabriel.loiseau@hornetsecurity.com" },
|
|
12
|
+
]
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
keywords = [
|
|
15
|
+
"Text anonymization",
|
|
16
|
+
"evaluation",
|
|
17
|
+
"NLP",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Intended Audience :: Science/Research",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"transformers>=4.48.0,<5.0.0",
|
|
29
|
+
"sentence-transformers>=3.3.1",
|
|
30
|
+
"torch>=2.5.0",
|
|
31
|
+
"evaluate>=0.4.1",
|
|
32
|
+
"datasets>=2.14.4",
|
|
33
|
+
"huggingface-hub>=0.20.0",
|
|
34
|
+
"tasknet>=1.57.0",
|
|
35
|
+
"tasksource>=0.0.47",
|
|
36
|
+
"ipywidgets>=8.1.5",
|
|
37
|
+
"ipykernel>=6.29.5",
|
|
38
|
+
"rich>=14.0.0",
|
|
39
|
+
"accelerate>=1.6.0",
|
|
40
|
+
"bert-score>=0.3.13",
|
|
41
|
+
"nltk>=3.9.1",
|
|
42
|
+
"rouge-score>=0.1.2",
|
|
43
|
+
"faker>=37.1.0",
|
|
44
|
+
"presidio-analyzer>=2.2.358",
|
|
45
|
+
"presidio-anonymizer>=2.2.358",
|
|
46
|
+
"pip>=25.0.1",
|
|
47
|
+
"pytest>=8.3.5",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
[project.optional-dependencies]
|
|
52
|
+
tests = [
|
|
53
|
+
"pytest",
|
|
54
|
+
"accelerate>=0.20.3",
|
|
55
|
+
]
|
|
56
|
+
quality = [
|
|
57
|
+
"ruff",
|
|
58
|
+
"pyyaml>=5.3.1",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
[tool.ruff]
|
|
63
|
+
line-length = 119
|
|
64
|
+
fix = true
|
|
65
|
+
|
|
66
|
+
[tool.ruff.lint]
|
|
67
|
+
# Never enforce `E501` (line length violations).
|
|
68
|
+
ignore = ["C901", "E501", "E741", "F402", "F823", "C416" ]
|
|
69
|
+
select = ["C", "E", "F", "I", "W"]
|
|
70
|
+
|
|
71
|
+
[tool.ruff.lint.per-file-ignores]
|
|
72
|
+
"examples/**" = [
|
|
73
|
+
# Ignore `E402` (import violations) in all examples
|
|
74
|
+
"E402",
|
|
75
|
+
# Ignore missing required imports
|
|
76
|
+
"I002"
|
|
77
|
+
]
|
|
78
|
+
"docs/**" = [
|
|
79
|
+
# Ignore missing required imports
|
|
80
|
+
"I002"
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
[tool.ruff.lint.isort]
|
|
84
|
+
lines-after-imports = 2
|
|
85
|
+
known-first-party = ["transformers"]
|
|
86
|
+
|
|
87
|
+
[tool.ruff.format]
|
|
88
|
+
# Like Black, use double quotes for strings.
|
|
89
|
+
quote-style = "double"
|
|
90
|
+
|
|
91
|
+
# Like Black, indent with spaces, rather than tabs.
|
|
92
|
+
indent-style = "space"
|
|
93
|
+
|
|
94
|
+
# Like Black, respect magic trailing commas.
|
|
95
|
+
skip-magic-trailing-comma = false
|
|
96
|
+
|
|
97
|
+
# Like Black, automatically detect the appropriate line ending.
|
|
98
|
+
line-ending = "auto"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
[tool.pytest.ini_options]
|
|
102
|
+
testpaths = [
|
|
103
|
+
"tests"
|
|
104
|
+
]
|
|
105
|
+
addopts = "--strict-markers -m 'not slow'"
|
|
106
|
+
markers = [
|
|
107
|
+
"slow: marks tests as slow"
|
|
108
|
+
]
|
|
109
|
+
|
tau_eval-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Top-level package for tau-eval."""
|
|
2
|
+
|
|
3
|
+
from . import (
|
|
4
|
+
logger,
|
|
5
|
+
models,
|
|
6
|
+
utils,
|
|
7
|
+
)
|
|
8
|
+
from .experiment import Experiment, ExperimentConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"Experiment",
|
|
13
|
+
"ExperimentConfig",
|
|
14
|
+
"models",
|
|
15
|
+
"utils",
|
|
16
|
+
"logger",
|
|
17
|
+
]
|
|
18
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
from tasknet import Task
|
|
8
|
+
|
|
9
|
+
from tau_eval.tasks.customtask import CustomTask
|
|
10
|
+
|
|
11
|
+
from .logger import logger
|
|
12
|
+
from .models import Anonymizer
|
|
13
|
+
from .utils import run_models_on_custom_task, run_models_on_task
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def rich_display_dataframe(df, title="Dataframe") -> None:
|
|
17
|
+
"""Display dataframe as table using rich library.
|
|
18
|
+
Args:
|
|
19
|
+
df (pd.DataFrame): dataframe to display
|
|
20
|
+
title (str, optional): title of the table. Defaults to "Dataframe".
|
|
21
|
+
Raises:
|
|
22
|
+
NotRenderableError: if dataframe cannot be rendered
|
|
23
|
+
Returns:
|
|
24
|
+
rich.table.Table: rich table
|
|
25
|
+
"""
|
|
26
|
+
from rich import print
|
|
27
|
+
|
|
28
|
+
# ensure dataframe contains only string values
|
|
29
|
+
df = df.astype(str)
|
|
30
|
+
|
|
31
|
+
table = Table(title=title)
|
|
32
|
+
for col in df.columns:
|
|
33
|
+
table.add_column(col)
|
|
34
|
+
for row in df.values:
|
|
35
|
+
table.add_row(*row)
|
|
36
|
+
print(table)
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class ExperimentConfig:
|
|
40
|
+
"""Evaluation experiment config"""
|
|
41
|
+
|
|
42
|
+
exp_name: str = os.path.basename(__file__)[: -len(".py")]
|
|
43
|
+
classifier_name: str = "answerdotai/ModernBERT-base"
|
|
44
|
+
train_task_models: bool = False
|
|
45
|
+
train_with_generations: bool = False
|
|
46
|
+
device: str | None = "cuda"
|
|
47
|
+
classifier_args: dict = field(default_factory=dict)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Experiment:
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
models: list[Anonymizer],
|
|
54
|
+
metrics: list[str],
|
|
55
|
+
tasks: list[Task],
|
|
56
|
+
config: ExperimentConfig = ExperimentConfig(),
|
|
57
|
+
):
|
|
58
|
+
self.models = models
|
|
59
|
+
self.metrics = metrics
|
|
60
|
+
self.tasks = tasks
|
|
61
|
+
self.args = config
|
|
62
|
+
|
|
63
|
+
def run(self, output_dir="", device="cuda"):
|
|
64
|
+
self.output_dir = output_dir
|
|
65
|
+
logger.info("Running experiment...")
|
|
66
|
+
out = {}
|
|
67
|
+
|
|
68
|
+
for i, task in enumerate(self.tasks):
|
|
69
|
+
logger.info(f"Running task: {i}")
|
|
70
|
+
if isinstance(task, CustomTask):
|
|
71
|
+
results = run_models_on_custom_task(self.models, task, self.metrics)
|
|
72
|
+
else:
|
|
73
|
+
results = run_models_on_task(
|
|
74
|
+
self.models,
|
|
75
|
+
task,
|
|
76
|
+
self.metrics,
|
|
77
|
+
self.args.classifier_name,
|
|
78
|
+
self.args.train_task_models,
|
|
79
|
+
self.args.train_with_generations,
|
|
80
|
+
device,
|
|
81
|
+
)
|
|
82
|
+
if hasattr(task, "name"):
|
|
83
|
+
out[f"{task.name}_{i}"] = results
|
|
84
|
+
else:
|
|
85
|
+
out[f"{task.__class__.__name__}_{i}"] = results
|
|
86
|
+
|
|
87
|
+
self.results = out
|
|
88
|
+
with open(self.output_dir, "w") as f:
|
|
89
|
+
json.dump(out, f)
|
|
90
|
+
logger.info("Results saved")
|
|
91
|
+
|
|
92
|
+
def summary(self, output_dir=None, to_rich=False):
|
|
93
|
+
if output_dir is not None:
|
|
94
|
+
results = json.load(open(output_dir))
|
|
95
|
+
else:
|
|
96
|
+
results = json.load(open(self.output_dir))
|
|
97
|
+
|
|
98
|
+
task_dataframes = {}
|
|
99
|
+
|
|
100
|
+
for task_name, task in results.items():
|
|
101
|
+
# Prepare columns for DataFrame
|
|
102
|
+
if isinstance(task, CustomTask):
|
|
103
|
+
columns = ["Model Name"] + task["metrics"]
|
|
104
|
+
rows = []
|
|
105
|
+
|
|
106
|
+
# Add original metrics
|
|
107
|
+
original_metrics = task.get("original_metrics", {})
|
|
108
|
+
if original_metrics:
|
|
109
|
+
original_row = ["Original"]
|
|
110
|
+
for m in task["metrics"]:
|
|
111
|
+
if m == "cola":
|
|
112
|
+
original_row.append(round(original_metrics.get("cola", 0), 4))
|
|
113
|
+
else:
|
|
114
|
+
original_row.append("-")
|
|
115
|
+
rows.append(original_row)
|
|
116
|
+
# Add rewritten metrics
|
|
117
|
+
for model_name, model in task.items():
|
|
118
|
+
if model_name in ["original_metrics", "metrics"]:
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
row = [model_name]
|
|
122
|
+
|
|
123
|
+
for m in task["metrics"]:
|
|
124
|
+
row.append(round(model.get(m, 0), 4))
|
|
125
|
+
|
|
126
|
+
rows.append(row)
|
|
127
|
+
|
|
128
|
+
else:
|
|
129
|
+
columns = ["Model Name", "Accuracy", "F1"] + task["metrics"]
|
|
130
|
+
rows = []
|
|
131
|
+
|
|
132
|
+
# Add original metrics
|
|
133
|
+
original_metrics = task.get("original_metrics", {})
|
|
134
|
+
if original_metrics:
|
|
135
|
+
original_row = ["Original"]
|
|
136
|
+
original_row.append(round(original_metrics.get("test_accuracy", 0), 4))
|
|
137
|
+
original_row.append(round(original_metrics.get("test_f1", 0), 4))
|
|
138
|
+
for m in task["metrics"]:
|
|
139
|
+
if m == "cola":
|
|
140
|
+
original_row.append(round(original_metrics.get("cola", 0), 4))
|
|
141
|
+
else:
|
|
142
|
+
original_row.append("-")
|
|
143
|
+
rows.append(original_row)
|
|
144
|
+
|
|
145
|
+
# Add rewritten metrics
|
|
146
|
+
for model_name, model in task.items():
|
|
147
|
+
if model_name in ["original_metrics", "metrics"]:
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
row = [model_name]
|
|
151
|
+
row.append(model.get("test_accuracy", 0))
|
|
152
|
+
row.append(model.get("test_f1", 0))
|
|
153
|
+
|
|
154
|
+
for m in task["metrics"]:
|
|
155
|
+
row.append(round(model.get(m, 0), 4))
|
|
156
|
+
|
|
157
|
+
rows.append(row)
|
|
158
|
+
|
|
159
|
+
# Create DataFrame for the task
|
|
160
|
+
task_dataframe = pd.DataFrame(rows, columns=columns)
|
|
161
|
+
task_dataframes[task_name] = task_dataframe
|
|
162
|
+
if to_rich:
|
|
163
|
+
for t, value in task_dataframes.items():
|
|
164
|
+
rich_display_dataframe(value)
|
|
165
|
+
|
|
166
|
+
return task_dataframes
|
|
167
|
+
|
|
168
|
+
def plot_tradeoff(
|
|
169
|
+
self,
|
|
170
|
+
task_name1,
|
|
171
|
+
target1,
|
|
172
|
+
task_name2,
|
|
173
|
+
target2,
|
|
174
|
+
x_label="Metric 1",
|
|
175
|
+
y_label="Metric 2",
|
|
176
|
+
title="Tradeoff Between Metrics",
|
|
177
|
+
figsize=(4, 4),
|
|
178
|
+
):
|
|
179
|
+
from itertools import cycle
|
|
180
|
+
|
|
181
|
+
import matplotlib.pyplot as plt
|
|
182
|
+
|
|
183
|
+
task1 = self.results[task_name1]
|
|
184
|
+
task2 = self.results[task_name2]
|
|
185
|
+
model_names = []
|
|
186
|
+
x_vals = []
|
|
187
|
+
y_vals = []
|
|
188
|
+
|
|
189
|
+
for model_name, model in task1.items():
|
|
190
|
+
model_names.append(model_name)
|
|
191
|
+
if model_name in ["original_metrics", "metrics"]:
|
|
192
|
+
continue
|
|
193
|
+
else:
|
|
194
|
+
x_vals.append(model.get(target1))
|
|
195
|
+
|
|
196
|
+
for model_name, model in task2.items():
|
|
197
|
+
model_names.append(model_name)
|
|
198
|
+
if model_name in ["original_metrics", "metrics"]:
|
|
199
|
+
continue
|
|
200
|
+
else:
|
|
201
|
+
y_vals.append(model.get(target2))
|
|
202
|
+
|
|
203
|
+
markers = cycle(
|
|
204
|
+
["o", "s", "D", "^", "v", "<", ">", "p", "*", "h", "H"]
|
|
205
|
+
) # Cycle through different marker styles
|
|
206
|
+
plt.figure(figsize=figsize)
|
|
207
|
+
|
|
208
|
+
for i, (x_val, y_val) in enumerate(zip(x_vals, y_vals)):
|
|
209
|
+
marker = next(markers)
|
|
210
|
+
plt.scatter(
|
|
211
|
+
x_val,
|
|
212
|
+
y_val,
|
|
213
|
+
label=model_names[i] if model_names else f"Model {i + 1}",
|
|
214
|
+
marker=marker,
|
|
215
|
+
s=100,
|
|
216
|
+
alpha=0.7,
|
|
217
|
+
edgecolor="k",
|
|
218
|
+
)
|
|
219
|
+
# Annotate points with model names if provided
|
|
220
|
+
if model_names:
|
|
221
|
+
plt.text(x_val, y_val, model_names[i], fontsize=9, ha="right", va="bottom")
|
|
222
|
+
|
|
223
|
+
plt.axhline(0, color="gray", linewidth=0.5, linestyle="--")
|
|
224
|
+
plt.axvline(0, color="gray", linewidth=0.5, linestyle="--")
|
|
225
|
+
plt.grid(alpha=0.3)
|
|
226
|
+
|
|
227
|
+
plt.xlabel(x_label)
|
|
228
|
+
plt.ylabel(y_label)
|
|
229
|
+
plt.title(title)
|
|
230
|
+
plt.legend(loc="best", fontsize=9)
|
|
231
|
+
plt.tight_layout()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from rich.logging import RichHandler
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
# the handler determines where the logs go: stdout/file
|
|
9
|
+
shell_handler = RichHandler()
|
|
10
|
+
file_handler = logging.FileHandler("debug.log")
|
|
11
|
+
|
|
12
|
+
logger.setLevel(logging.DEBUG)
|
|
13
|
+
shell_handler.setLevel(logging.DEBUG)
|
|
14
|
+
file_handler.setLevel(logging.DEBUG)
|
|
15
|
+
|
|
16
|
+
# the formatter determines what our logs will look like
|
|
17
|
+
fmt_shell = "%(message)s"
|
|
18
|
+
fmt_file = "%(levelname)s %(asctime)s [%(filename)s:%(funcName)s:%(lineno)d] %(message)s"
|
|
19
|
+
|
|
20
|
+
shell_formatter = logging.Formatter(fmt_shell)
|
|
21
|
+
file_formatter = logging.Formatter(fmt_file)
|
|
22
|
+
|
|
23
|
+
# here we hook everything together
|
|
24
|
+
shell_handler.setFormatter(shell_formatter)
|
|
25
|
+
file_handler.setFormatter(file_formatter)
|
|
26
|
+
|
|
27
|
+
logger.addHandler(shell_handler)
|
|
28
|
+
logger.addHandler(file_handler)
|