Coding-With-Bashir commited on
Commit
dc37a2d
·
verified ·
1 Parent(s): e83cfa7

Upload .\src\evaluation\metrics.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. .//src//evaluation//metrics.py +289 -0
.//src//evaluation//metrics.py ADDED
@@ -0,0 +1,289 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Evaluation metrics for BwengeAi."""
2
+
3
+ import json
4
+ import logging
5
+ import math
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import numpy as np
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class BwengeEvaluator:
15
+ """Evaluation manager for BwengeAi."""
16
+
17
+ def __init__(self, config: dict[str, Any]):
18
+ self.config = config
19
+ self.eval_config = config.get("evaluation", {})
20
+ self.metrics = self.eval_config.get("metrics", ["perplexity", "rouge", "bleu", "accuracy"])
21
+
22
+ def compute_perplexity(
23
+ self,
24
+ model: Any,
25
+ tokenizer: Any,
26
+ texts: list[str],
27
+ max_length: int = 512,
28
+ ) -> dict[str, float]:
29
+ """Compute perplexity on a set of texts."""
30
+ import torch
31
+
32
+ model.eval()
33
+ total_loss = 0.0
34
+ total_tokens = 0
35
+
36
+ for text in texts:
37
+ inputs = tokenizer(
38
+ text,
39
+ return_tensors="pt",
40
+ truncation=True,
41
+ max_length=max_length,
42
+ padding=True,
43
+ ).to(model.device)
44
+
45
+ with torch.no_grad():
46
+ outputs = model(**inputs, labels=inputs["input_ids"])
47
+ loss = outputs.loss
48
+
49
+ num_tokens = inputs["input_ids"].numel()
50
+ total_loss += loss.item() * num_tokens
51
+ total_tokens += num_tokens
52
+
53
+ avg_loss = total_loss / total_tokens if total_tokens > 0 else float("inf")
54
+ perplexity = np.exp(avg_loss)
55
+
56
+ return {"perplexity": float(perplexity), "avg_loss": float(avg_loss)}
57
+
58
+ def compute_rouge(
59
+ self,
60
+ predictions: list[str],
61
+ references: list[str],
62
+ ) -> dict[str, float]:
63
+ """Compute ROUGE scores (simplified implementation)."""
64
+
65
+ def get_ngrams(text: str, n: int) -> list[tuple[str, ...]]:
66
+ words = text.lower().split()
67
+ return [tuple(words[i:i + n]) for i in range(len(words) - n + 1)]
68
+
69
+ def compute_rouge_n(pred: str, ref: str, n: int) -> dict[str, float]:
70
+ pred_ngrams = get_ngrams(pred, n)
71
+ ref_ngrams = get_ngrams(ref, n)
72
+
73
+ if not pred_ngrams or not ref_ngrams:
74
+ return {"precision": 0.0, "recall": 0.0, "fmeasure": 0.0}
75
+
76
+ pred_counts = {}
77
+ for ng in pred_ngrams:
78
+ pred_counts[ng] = pred_counts.get(ng, 0) + 1
79
+
80
+ ref_counts = {}
81
+ for ng in ref_ngrams:
82
+ ref_counts[ng] = ref_counts.get(ng, 0) + 1
83
+
84
+ overlap = 0
85
+ for ng, count in pred_counts.items():
86
+ overlap += min(count, ref_counts.get(ng, 0))
87
+
88
+ precision = overlap / len(pred_ngrams) if pred_ngrams else 0.0
89
+ recall = overlap / len(ref_ngrams) if ref_ngrams else 0.0
90
+ fmeasure = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0.0
91
+
92
+ return {"precision": precision, "recall": recall, "fmeasure": fmeasure}
93
+
94
+ scores = {
95
+ "rouge1": {"precision": [], "recall": [], "fmeasure": []},
96
+ "rouge2": {"precision": [], "recall": [], "fmeasure": []},
97
+ "rougeL": {"precision": [], "recall": [], "fmeasure": []},
98
+ }
99
+
100
+ for pred, ref in zip(predictions, references):
101
+ result1 = compute_rouge_n(pred, ref, 1)
102
+ result2 = compute_rouge_n(pred, ref, 2)
103
+
104
+ words_pred = pred.lower().split()
105
+ words_ref = ref.lower().split()
106
+ lcs_len = self._lcs_length(words_pred, words_ref)
107
+ precision_lcs = lcs_len / len(words_pred) if words_pred else 0.0
108
+ recall_lcs = lcs_len / len(words_ref) if words_ref else 0.0
109
+ fmeasure_lcs = 2 * precision_lcs * recall_lcs / (precision_lcs + recall_lcs) if (precision_lcs + recall_lcs) > 0 else 0.0
110
+ resultL = {"precision": precision_lcs, "recall": recall_lcs, "fmeasure": fmeasure_lcs}
111
+
112
+ for metric, result in [("rouge1", result1), ("rouge2", result2), ("rougeL", resultL)]:
113
+ scores[metric]["precision"].append(result["precision"])
114
+ scores[metric]["recall"].append(result["recall"])
115
+ scores[metric]["fmeasure"].append(result["fmeasure"])
116
+
117
+ averaged = {}
118
+ for metric in scores:
119
+ averaged[f"{metric}_precision"] = float(np.mean(scores[metric]["precision"]))
120
+ averaged[f"{metric}_recall"] = float(np.mean(scores[metric]["recall"]))
121
+ averaged[f"{metric}_fmeasure"] = float(np.mean(scores[metric]["fmeasure"]))
122
+
123
+ return averaged
124
+
125
+ def _lcs_length(self, x: list[str], y: list[str]) -> int:
126
+ """Compute length of longest common subsequence."""
127
+ m, n = len(x), len(y)
128
+ dp = [[0] * (n + 1) for _ in range(m + 1)]
129
+ for i in range(1, m + 1):
130
+ for j in range(1, n + 1):
131
+ if x[i - 1] == y[j - 1]:
132
+ dp[i][j] = dp[i - 1][j - 1] + 1
133
+ else:
134
+ dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
135
+ return dp[m][n]
136
+
137
+ def compute_bleu(
138
+ self,
139
+ predictions: list[str],
140
+ references: list[str],
141
+ ) -> dict[str, float]:
142
+ """Compute BLEU scores (simplified implementation)."""
143
+
144
+ def compute_bleu_single(pred: list[str], refs: list[list[str]], max_order: int = 4) -> float:
145
+ clipped_counts = [0] * max_order
146
+ total_counts = [0] * max_order
147
+
148
+ for order in range(1, max_order + 1):
149
+ pred_ngrams = {}
150
+ for i in range(len(pred) - order + 1):
151
+ ngram = tuple(pred[i:i + order])
152
+ pred_ngrams[ngram] = pred_ngrams.get(ngram, 0) + 1
153
+
154
+ max_ref_count = 0
155
+ for ref in refs:
156
+ ref_ngrams = {}
157
+ for i in range(len(ref) - order + 1):
158
+ ngram = tuple(ref[i:i + order])
159
+ ref_ngrams[ngram] = ref_ngrams.get(ngram, 0) + 1
160
+
161
+ for ngram, count in pred_ngrams.items():
162
+ max_ref_count = max(max_ref_count, min(count, ref_ngrams.get(ngram, 0)))
163
+
164
+ clipped_counts[order - 1] += max_ref_count
165
+ total_counts[order - 1] += max(1, len(pred) - order + 1)
166
+
167
+ p_scores = []
168
+ for i in range(max_order):
169
+ if total_counts[i] > 0:
170
+ p_scores.append(clipped_counts[i] / total_counts[i])
171
+ else:
172
+ p_scores.append(0.0)
173
+
174
+ if min(p_scores) > 0:
175
+ p_avg = sum(p_scores) / len(p_scores)
176
+ penalty = math.exp(1 - max_order / max(1, len(pred))) if len(pred) < max_order else 1.0
177
+ return penalty * p_avg
178
+ else:
179
+ return 0.0
180
+
181
+ scores = {"bleu_1": [], "bleu_2": [], "bleu_3": [], "bleu_4": []}
182
+
183
+ for pred, ref in zip(predictions, references):
184
+ pred_tokens = pred.split()
185
+ ref_tokens = [ref.split()]
186
+
187
+ for order in range(1, 5):
188
+ bleu = compute_bleu_single(pred_tokens, ref_tokens, max_order=order)
189
+ scores[f"bleu_{order}"].append(bleu)
190
+
191
+ averaged = {}
192
+ for metric in scores:
193
+ averaged[metric] = float(np.mean(scores[metric])) if scores[metric] else 0.0
194
+
195
+ return averaged
196
+
197
+ def compute_accuracy(
198
+ self,
199
+ predictions: list[str],
200
+ references: list[str],
201
+ ) -> dict[str, float]:
202
+ """Compute accuracy metrics."""
203
+ exact_matches = sum(1 for p, r in zip(predictions, references) if p.strip() == r.strip())
204
+ accuracy = exact_matches / len(predictions) if predictions else 0.0
205
+
206
+ return {"accuracy": accuracy, "exact_matches": exact_matches}
207
+
208
+ def evaluate_generation(
209
+ self,
210
+ model: Any,
211
+ tokenizer: Any,
212
+ test_data: list[dict[str, str]],
213
+ max_new_tokens: int = 256,
214
+ ) -> dict[str, Any]:
215
+ """Evaluate model generation quality."""
216
+ predictions = []
217
+ references = []
218
+
219
+ for item in test_data:
220
+ prompt = item.get("instruction", item.get("prompt", item.get("question", "")))
221
+ reference = item.get("output", item.get("response", item.get("answer", "")))
222
+
223
+ if not prompt or not reference:
224
+ continue
225
+
226
+ inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
227
+
228
+ import torch
229
+ with torch.no_grad():
230
+ outputs = model.generate(
231
+ **inputs,
232
+ max_new_tokens=max_new_tokens,
233
+ temperature=0.7,
234
+ top_p=0.9,
235
+ do_sample=True,
236
+ pad_token_id=tokenizer.eos_token_id,
237
+ )
238
+
239
+ generated = tokenizer.decode(outputs[0], skip_special_tokens=True)
240
+ prediction = generated[len(prompt):]
241
+
242
+ predictions.append(prediction)
243
+ references.append(reference)
244
+
245
+ results = {}
246
+
247
+ if "rouge" in self.metrics:
248
+ results.update(self.compute_rouge(predictions, references))
249
+
250
+ if "bleu" in self.metrics:
251
+ results.update(self.compute_bleu(predictions, references))
252
+
253
+ if "accuracy" in self.metrics:
254
+ results.update(self.compute_accuracy(predictions, references))
255
+
256
+ results["num_samples"] = len(predictions)
257
+
258
+ return results
259
+
260
+ def evaluate_model(
261
+ self,
262
+ model: Any,
263
+ tokenizer: Any,
264
+ eval_data_path: str,
265
+ output_dir: str | None = None,
266
+ ) -> dict[str, Any]:
267
+ """Run full evaluation."""
268
+ logger.info("Starting evaluation...")
269
+
270
+ eval_data = []
271
+ with open(eval_data_path, "r", encoding="utf-8") as f:
272
+ for line in f:
273
+ line = line.strip()
274
+ if line:
275
+ eval_data.append(json.loads(line))
276
+
277
+ max_samples = self.eval_config.get("max_eval_samples", 1000)
278
+ eval_data = eval_data[:max_samples]
279
+
280
+ results = self.evaluate_generation(model, tokenizer, eval_data)
281
+
282
+ if output_dir:
283
+ output_path = Path(output_dir) / "eval_results.json"
284
+ with open(output_path, "w", encoding="utf-8") as f:
285
+ json.dump(results, f, indent=2, ensure_ascii=False)
286
+ logger.info(f"Results saved to {output_path}")
287
+
288
+ logger.info(f"Evaluation results: {results}")
289
+ return results