tau-eval 0.1.0__py3-none-any.whl
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/__init__.py +18 -0
- tau_eval/experiment.py +231 -0
- tau_eval/logger.py +28 -0
- tau_eval/metrics/bertscore.py +35 -0
- tau_eval/metrics/cola.py +89 -0
- tau_eval/metrics/luar.py +54 -0
- tau_eval/metrics/meteor.py +46 -0
- tau_eval/metrics/nli.py +78 -0
- tau_eval/metrics/perplexity.py +24 -0
- tau_eval/metrics/rouge.py +28 -0
- tau_eval/metrics/sbert.py +52 -0
- tau_eval/models/__init__.py +4 -0
- tau_eval/models/anonymizer.py +10 -0
- tau_eval/models/authorship.py +129 -0
- tau_eval/models/dummy.py +12 -0
- tau_eval/models/llm_api.py +107 -0
- tau_eval/models/presidio.py +340 -0
- tau_eval/models/styleremix.py +163 -0
- tau_eval/tasks/__init__.py +5 -0
- tau_eval/tasks/customtask.py +14 -0
- tau_eval/tasks/deidentification.py +113 -0
- tau_eval/utils.py +273 -0
- tau_eval/visualization.py +633 -0
- tau_eval-0.1.0.dist-info/METADATA +170 -0
- tau_eval-0.1.0.dist-info/RECORD +27 -0
- tau_eval-0.1.0.dist-info/WHEEL +5 -0
- tau_eval-0.1.0.dist-info/top_level.txt +1 -0
tau_eval/__init__.py
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"
|
tau_eval/experiment.py
ADDED
|
@@ -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()
|
tau_eval/logger.py
ADDED
|
@@ -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)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import evaluate
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Loading evaluation metric
|
|
5
|
+
bert = evaluate.load("bertscore")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def compute_bertscore(
|
|
9
|
+
input_texts: str | list[str],
|
|
10
|
+
output_texts: str | list[str],
|
|
11
|
+
model_id: str = "distilbert-base-uncased",
|
|
12
|
+
) -> dict[str, list[float]]:
|
|
13
|
+
"""
|
|
14
|
+
Computes BERTScore for a list of input and output text pairs.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
input_texts: A string or a list of input text strings.
|
|
18
|
+
output_texts: A string or a list of output text strings.
|
|
19
|
+
model_id: Bert specification, HuggingFace model to use.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
A dictionary containing BERTScore scores for each input-output pair.
|
|
23
|
+
The dictionary will contain keys "precision", "recall", and "f1".
|
|
24
|
+
"""
|
|
25
|
+
if not isinstance(input_texts, list):
|
|
26
|
+
input_texts = [input_texts]
|
|
27
|
+
if not isinstance(output_texts, list):
|
|
28
|
+
output_texts = [output_texts]
|
|
29
|
+
assert len(input_texts) == len(output_texts), "inputs are different lengths"
|
|
30
|
+
|
|
31
|
+
return bert.compute(
|
|
32
|
+
predictions=output_texts,
|
|
33
|
+
references=input_texts,
|
|
34
|
+
model_type=model_id,
|
|
35
|
+
)
|
tau_eval/metrics/cola.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import torch
|
|
3
|
+
from nltk.tokenize import sent_tokenize
|
|
4
|
+
|
|
5
|
+
from transformers import (
|
|
6
|
+
AutoModelForSequenceClassification,
|
|
7
|
+
AutoTokenizer,
|
|
8
|
+
PreTrainedModel,
|
|
9
|
+
PreTrainedTokenizer,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load_cola(
|
|
14
|
+
model_name: str = "textattack/roberta-base-CoLA", device: str = "cuda"
|
|
15
|
+
) -> tuple[PreTrainedModel, PreTrainedTokenizer]:
|
|
16
|
+
"""
|
|
17
|
+
Loads the CoLA (Corpus of Linguistic Acceptability) model and tokenizer.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
model_name: HuggingFace model to load
|
|
21
|
+
device: The device to load the model onto ("cuda" or "cpu").
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
A tuple containing the loaded model and tokenizer.
|
|
25
|
+
"""
|
|
26
|
+
cola_tokenizer: PreTrainedTokenizer = AutoTokenizer.from_pretrained(model_name, map_location=device)
|
|
27
|
+
cola_model: PreTrainedModel = AutoModelForSequenceClassification.from_pretrained(model_name).to(device)
|
|
28
|
+
return (cola_tokenizer, cola_model)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def cola_score(
|
|
32
|
+
text: str,
|
|
33
|
+
cola_tokenizer: PreTrainedTokenizer,
|
|
34
|
+
cola_model: PreTrainedModel,
|
|
35
|
+
device: str = "cuda",
|
|
36
|
+
) -> float:
|
|
37
|
+
"""
|
|
38
|
+
Calculates the CoLA score for a single piece of text.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
text: The text to score.
|
|
42
|
+
cola_tokenizer: The tokenizer for the CoLA model.
|
|
43
|
+
cola_model: The CoLA model.
|
|
44
|
+
device: The device to run the model on ("cuda" or "cpu").
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
The CoLA score (a float between 0 and 1).
|
|
48
|
+
"""
|
|
49
|
+
tokenize_input = cola_tokenizer.tokenize(text)
|
|
50
|
+
tensor_input = torch.tensor([cola_tokenizer.convert_tokens_to_ids(tokenize_input)]).to(device)
|
|
51
|
+
output = cola_model(tensor_input)
|
|
52
|
+
return output.logits.softmax(-1)[0][1].item()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def compute_cola(
|
|
56
|
+
output_texts: str | list[str],
|
|
57
|
+
cola_tokenizer: PreTrainedTokenizer,
|
|
58
|
+
cola_model: PreTrainedModel,
|
|
59
|
+
device: str = "cuda",
|
|
60
|
+
) -> dict[str, list[float]]:
|
|
61
|
+
"""
|
|
62
|
+
Computes CoLA scores for a list of input texts.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
output_texts: A list of text strings.
|
|
66
|
+
cola_tokenizer: The tokenizer for the CoLA model.
|
|
67
|
+
cola_model: The CoLA model.
|
|
68
|
+
device: The device to run the model on ("cuda" or "cpu").
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
A dictionary containing CoLA scores for each input text.
|
|
72
|
+
"""
|
|
73
|
+
if not isinstance(output_texts, list):
|
|
74
|
+
output_texts = [output_texts]
|
|
75
|
+
|
|
76
|
+
cola_ls: list[float] = []
|
|
77
|
+
for i, text in enumerate(output_texts):
|
|
78
|
+
if text == "":
|
|
79
|
+
continue
|
|
80
|
+
# if text is too big, break it up
|
|
81
|
+
sentences = sent_tokenize(text)
|
|
82
|
+
cola_score_list: list[float] = []
|
|
83
|
+
|
|
84
|
+
for sent in sentences:
|
|
85
|
+
cola_score_list.append(cola_score(sent, cola_tokenizer, cola_model, device))
|
|
86
|
+
|
|
87
|
+
cola_ls.append(np.mean(cola_score_list))
|
|
88
|
+
|
|
89
|
+
return {"cola": cola_ls}
|
tau_eval/metrics/luar.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Sentence transformers version of LUAR"""
|
|
2
|
+
import torch
|
|
3
|
+
from sentence_transformers import SentenceTransformer
|
|
4
|
+
from sentence_transformers.util import cos_sim
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def load_luar(
|
|
8
|
+
model_name: str = "gabrielloiseau/LUAR-MUD-sentence-transformers",
|
|
9
|
+
device: str = "cuda",
|
|
10
|
+
) -> SentenceTransformer:
|
|
11
|
+
"""
|
|
12
|
+
Loads the LUAR (Language Understanding and Reasoning) sentence transformer model.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
model_name: SentenceTransformers model to load.
|
|
16
|
+
device: The device to load the model onto ("cuda" or "cpu").
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
The loaded SentenceTransformer model.
|
|
20
|
+
"""
|
|
21
|
+
return SentenceTransformer(model_name, device=device)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def compute_luar(
|
|
25
|
+
input_texts: str | list[str],
|
|
26
|
+
output_texts: str | list[str],
|
|
27
|
+
sim_model: SentenceTransformer,
|
|
28
|
+
) -> dict[str, list[float]]:
|
|
29
|
+
"""
|
|
30
|
+
Computes LUAR scores based on cosine similarity between the embeddings of original and rewritten texts.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
original: A string or list of original texts.
|
|
34
|
+
rewrites: A string or list of rewritten texts.
|
|
35
|
+
sim_model: The loaded SentenceTransformer model.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
A dictionary containing the LUAR scores for each pair of input texts.
|
|
39
|
+
The dictionary has the key "luar" with a list of float values.
|
|
40
|
+
"""
|
|
41
|
+
if not isinstance(input_texts, list):
|
|
42
|
+
input_texts = [input_texts]
|
|
43
|
+
if not isinstance(output_texts, list):
|
|
44
|
+
output_texts = [output_texts]
|
|
45
|
+
assert len(input_texts) == len(output_texts), "inputs are different lengths"
|
|
46
|
+
|
|
47
|
+
outputs = []
|
|
48
|
+
embedding_orig: torch.Tensor = sim_model.encode(input_texts, convert_to_tensor=True, show_progress_bar=False)
|
|
49
|
+
embedding_rew: torch.Tensor = sim_model.encode(output_texts, convert_to_tensor=True, show_progress_bar=False)
|
|
50
|
+
|
|
51
|
+
for orig, new in zip(embedding_orig, embedding_rew):
|
|
52
|
+
outputs.append(cos_sim(orig, new).item())
|
|
53
|
+
|
|
54
|
+
return {"luar": outputs}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import evaluate
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Loading evaluation metric
|
|
5
|
+
meteor = evaluate.load("meteor")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def compute_meteor(
|
|
9
|
+
input_texts: str | list[str],
|
|
10
|
+
output_texts: str | list[str],
|
|
11
|
+
alpha: float = 0.9,
|
|
12
|
+
beta: float = 3,
|
|
13
|
+
gamma: float = 0.5,
|
|
14
|
+
) -> dict[str, list[float]]:
|
|
15
|
+
"""
|
|
16
|
+
Computes METEOR scores for a list of input and output text pairs.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
input_texts: A list of input text strings.
|
|
20
|
+
output_texts: A list of output text strings.
|
|
21
|
+
alpha: Parameter for controlling relative weights of precision and recall.
|
|
22
|
+
beta: Parameter for controlling shape of penalty function.
|
|
23
|
+
gamma: Relative weight of fragmentation penalty.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
A dictionary containing METEOR scores for each input-output pair.
|
|
27
|
+
"""
|
|
28
|
+
if not isinstance(input_texts, list):
|
|
29
|
+
input_texts = [input_texts] # type: ignore
|
|
30
|
+
if not isinstance(output_texts, list):
|
|
31
|
+
output_texts = [output_texts] # type: ignore
|
|
32
|
+
assert len(input_texts) == len(output_texts), "inputs are different lengths"
|
|
33
|
+
|
|
34
|
+
scores = []
|
|
35
|
+
for input_text, output_text in zip(input_texts, output_texts):
|
|
36
|
+
scores.append(
|
|
37
|
+
meteor.compute(
|
|
38
|
+
predictions=[output_text],
|
|
39
|
+
references=[input_text],
|
|
40
|
+
alpha=alpha,
|
|
41
|
+
beta=beta,
|
|
42
|
+
gamma=gamma,
|
|
43
|
+
)["meteor"]
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return {"meteor": scores}
|
tau_eval/metrics/nli.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
from transformers import (
|
|
4
|
+
AutoModelForSequenceClassification,
|
|
5
|
+
AutoTokenizer,
|
|
6
|
+
PreTrainedModel,
|
|
7
|
+
PreTrainedTokenizer,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_nli(
|
|
12
|
+
model_name: str = "alisawuffles/roberta-large-wanli", device: str = "cuda"
|
|
13
|
+
) -> tuple[PreTrainedTokenizer, PreTrainedModel]:
|
|
14
|
+
"""
|
|
15
|
+
Loads the NLI (Natural Language Inference) model and tokenizer.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
model_name: HuggingFace model to load
|
|
19
|
+
device: The device to load the model onto ("cuda" or "cpu").
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
A tuple containing the loaded tokenizer and model.
|
|
23
|
+
"""
|
|
24
|
+
# Download necessary models for evaluation
|
|
25
|
+
nli_tokenizer: PreTrainedTokenizer = AutoTokenizer.from_pretrained(model_name, map_location=device)
|
|
26
|
+
nli_model: PreTrainedModel = AutoModelForSequenceClassification.from_pretrained(model_name).to(device)
|
|
27
|
+
|
|
28
|
+
return (nli_tokenizer, nli_model)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def compute_nli(
|
|
32
|
+
input_texts: str | list[str],
|
|
33
|
+
output_texts: str | list[str],
|
|
34
|
+
nli_tokenizer: PreTrainedTokenizer,
|
|
35
|
+
nli_model: PreTrainedModel,
|
|
36
|
+
batch_size: int = 16,
|
|
37
|
+
device: str = "cuda",
|
|
38
|
+
max_length: int = 128,
|
|
39
|
+
) -> dict[str, list[float]]:
|
|
40
|
+
"""
|
|
41
|
+
Computes the probability of entailment between two texts using the NLI model.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
input_text: The premise text.
|
|
45
|
+
output_text: The hypothesis text.
|
|
46
|
+
nli_tokenizer: The tokenizer for the NLI model.
|
|
47
|
+
nli_model: The NLI model.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
A dictionary containing the probability of entailment.
|
|
51
|
+
The dictionary has the key "entailment" with a float value.
|
|
52
|
+
"""
|
|
53
|
+
if not isinstance(input_texts, list):
|
|
54
|
+
input_texts = [input_texts] # type: ignore
|
|
55
|
+
if not isinstance(output_texts, list):
|
|
56
|
+
output_texts = [output_texts] # type: ignore
|
|
57
|
+
assert len(input_texts) == len(output_texts), "inputs are different lengths"
|
|
58
|
+
|
|
59
|
+
entailment_prob = []
|
|
60
|
+
|
|
61
|
+
for start_index in range(0, len(output_texts), batch_size):
|
|
62
|
+
end_index = min(start_index + batch_size, len(output_texts))
|
|
63
|
+
input_batch = input_texts[start_index:end_index]
|
|
64
|
+
output_batch = output_texts[start_index:end_index]
|
|
65
|
+
|
|
66
|
+
x = nli_tokenizer(
|
|
67
|
+
input_batch,
|
|
68
|
+
output_batch,
|
|
69
|
+
return_tensors="pt",
|
|
70
|
+
max_length=max_length,
|
|
71
|
+
padding=True,
|
|
72
|
+
truncation=True,
|
|
73
|
+
).to(device)
|
|
74
|
+
with torch.no_grad():
|
|
75
|
+
logits = nli_model.to(device)(**x).logits
|
|
76
|
+
entailment_prob += logits.softmax(dim=1)[:, 1].tolist()
|
|
77
|
+
|
|
78
|
+
return {"entailment": entailment_prob}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import evaluate
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Loading evaluation metric
|
|
5
|
+
perplexity = evaluate.load("perplexity", module_type="metric")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def compute_perplexity(output_texts: str | list[str], model_id: str = "gpt2") -> dict[str, list[float]]:
|
|
9
|
+
"""
|
|
10
|
+
Computes perplexity scores for a list of output texts.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
output_texts: A string or list of output text strings.
|
|
14
|
+
model_id: HuggingFace model to use
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
A dictionary containing perplexity scores for each input text.
|
|
18
|
+
"""
|
|
19
|
+
if not isinstance(output_texts, list):
|
|
20
|
+
output_texts = [output_texts]
|
|
21
|
+
|
|
22
|
+
result = perplexity.compute(predictions=output_texts, model_id=model_id)
|
|
23
|
+
|
|
24
|
+
return result
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import evaluate
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Loading evaluation metric
|
|
5
|
+
rouge = evaluate.load("rouge")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def compute_rouge(
|
|
9
|
+
input_texts: str | list[str],
|
|
10
|
+
output_texts: str | list[str],
|
|
11
|
+
) -> dict[str, list[float]]:
|
|
12
|
+
"""
|
|
13
|
+
Computes ROUGE scores for a list of input and output text pairs.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
input_texts: A list of input text strings.
|
|
17
|
+
output_texts: A list of output text strings.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
A dictionary containing ROUGE scores for each input-output pair.
|
|
21
|
+
"""
|
|
22
|
+
if not isinstance(input_texts, list):
|
|
23
|
+
input_texts = [input_texts] # type: ignore
|
|
24
|
+
if not isinstance(output_texts, list):
|
|
25
|
+
output_texts = [output_texts] # type: ignore
|
|
26
|
+
assert len(input_texts) == len(output_texts), "inputs are different lengths"
|
|
27
|
+
|
|
28
|
+
return rouge.compute(predictions=output_texts, references=input_texts, use_aggregator=False)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from sentence_transformers import SentenceTransformer
|
|
3
|
+
from sentence_transformers.util import cos_sim
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def load_sbert(
|
|
7
|
+
model_name: str = "sentence-transformers/all-MiniLM-L6-v2", device: str = "cuda"
|
|
8
|
+
) -> SentenceTransformer:
|
|
9
|
+
"""
|
|
10
|
+
Loads the sentence similarity model.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
model_name: SentenceTransformers model to load.
|
|
14
|
+
device: The device to load the model onto ("cuda" or "cpu").
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
The loaded SentenceTransformer model.
|
|
18
|
+
"""
|
|
19
|
+
return SentenceTransformer(model_name, device=device)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def compute_sbert(
|
|
23
|
+
input_texts: str | list[str],
|
|
24
|
+
output_texts: str | list[str],
|
|
25
|
+
sim_model: SentenceTransformer,
|
|
26
|
+
) -> dict[str, list[float]]:
|
|
27
|
+
"""
|
|
28
|
+
Computes the cosine similarity between the embeddings of original and rewritten texts.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
original: A string or a list of original texts.
|
|
32
|
+
rewrites: A string or a list of rewritten texts.
|
|
33
|
+
sim_model: The loaded SentenceTransformer model.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
A dictionary containing the similarity scores for each input text pair.
|
|
37
|
+
The dictionary has the key "similarity" with a list of float values.
|
|
38
|
+
"""
|
|
39
|
+
if not isinstance(input_texts, list):
|
|
40
|
+
input_texts = [input_texts]
|
|
41
|
+
if not isinstance(output_texts, list):
|
|
42
|
+
output_texts = [output_texts]
|
|
43
|
+
assert len(input_texts) == len(output_texts), "inputs are different lengths"
|
|
44
|
+
|
|
45
|
+
outputs = []
|
|
46
|
+
embedding_orig: torch.Tensor = sim_model.encode(input_texts, convert_to_tensor=True, show_progress_bar=False)
|
|
47
|
+
embedding_rew: torch.Tensor = sim_model.encode(output_texts, convert_to_tensor=True, show_progress_bar=False)
|
|
48
|
+
|
|
49
|
+
for orig, new in zip(embedding_orig, embedding_rew):
|
|
50
|
+
outputs.append(cos_sim(orig, new).item())
|
|
51
|
+
|
|
52
|
+
return {"sbert": outputs}
|