-
Notifications
You must be signed in to change notification settings - Fork 193
Audio OpenAI format #1144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Audio OpenAI format #1144
Changes from 42 commits
07a408e
6acbc9f
78c6313
8f5c3a9
5e36e55
862be04
6768b00
f522c0b
d48d43f
4487ca1
c53d405
a7cf5b5
689e6fc
9536b8b
155a480
0d9292d
c9d99a5
0c54cad
fc7a344
c68f8af
cfaf38a
8186164
813d675
1b7e715
ea8f294
206c7d6
289c694
3cc7ae5
5825f27
6f58a1b
0f1b86d
32ed94c
30a1c7f
15f30fd
eaaba9b
8a0765a
f8c3089
a88736e
3521987
1e418e7
7c11040
71db50f
f021989
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,31 +13,77 @@ | |
| # limitations under the License. | ||
|
|
||
| import logging | ||
| import re | ||
|
|
||
| import numpy as np | ||
|
|
||
| from nemo_skills.evaluation.metrics.base import BaseMetrics, as_int, as_percentage | ||
| from nemo_skills.evaluation.metrics.utils import is_correct_judgement | ||
| from nemo_skills.utils import get_logger_name | ||
|
|
||
| LOG = logging.getLogger(get_logger_name(__file__)) | ||
|
|
||
|
|
||
| def extract_multicriteria_scores(judgement_text: str) -> dict[str, float]: | ||
| """Extract multi-criteria scores (1-5 scale) from LLM judge evaluation. | ||
|
|
||
| Expected format: | ||
| CORRECTNESS: [score] - [justification] | ||
| RELEVANCE: [score] - [justification] | ||
| COMPLETENESS: [score] - [justification] | ||
| CLARITY: [score] - [justification] | ||
| OVERALL: [score] - [overall assessment] | ||
|
|
||
| Returns: | ||
| Dictionary with keys: correctness, relevance, completeness, clarity, overall | ||
| Defaults to 3.0 if score not found. | ||
| """ | ||
| scores = {} | ||
|
|
||
| patterns = { | ||
| "correctness": r"CORRECTNESS:\s*(\d+(?:\.\d+)?)", | ||
| "relevance": r"RELEVANCE:\s*(\d+(?:\.\d+)?)", | ||
| "completeness": r"COMPLETENESS:\s*(\d+(?:\.\d+)?)", | ||
| "clarity": r"CLARITY:\s*(\d+(?:\.\d+)?)", | ||
| "overall": r"OVERALL:\s*(\d+(?:\.\d+)?)", | ||
| } | ||
|
|
||
| for criterion, pattern in patterns.items(): | ||
| match = re.search(pattern, judgement_text, re.IGNORECASE) | ||
| scores[criterion] = float(match.group(1)) if match else 3.0 | ||
|
|
||
| # Fallback: compute overall if missing or still 3.0 | ||
| if "overall" not in scores or scores["overall"] == 3.0: | ||
| criteria_scores = [scores.get(k, 3.0) for k in ["correctness", "relevance", "completeness", "clarity"]] | ||
| scores["overall"] = sum(criteria_scores) / len(criteria_scores) | ||
|
|
||
|
Comment on lines
+54
to
+58
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fallback logic may incorrectly overwrite a valid "overall" score of exactly 3.0. The condition 🔎 Suggested fix using a flag to track if score was parsed+ overall_parsed = False
for criterion, pattern in patterns.items():
match = re.search(pattern, judgement_text, re.IGNORECASE)
- scores[criterion] = float(match.group(1)) if match else 3.0
+ if match:
+ scores[criterion] = float(match.group(1))
+ if criterion == "overall":
+ overall_parsed = True
+ else:
+ scores[criterion] = 3.0
# Fallback: compute overall if missing or still 3.0
- if "overall" not in scores or scores["overall"] == 3.0:
+ if not overall_parsed:
criteria_scores = [scores.get(k, 3.0) for k in ["correctness", "relevance", "completeness", "clarity"]]
scores["overall"] = sum(criteria_scores) / len(criteria_scores)
🤖 Prompt for AI Agents |
||
| return scores | ||
|
|
||
|
|
||
| class MMAUProMetrics(BaseMetrics): | ||
| """Metrics class for MMAU-Pro benchmark (all subgroups).""" | ||
|
|
||
| def __init__(self, compute_no_answer: bool = True, max_k: int = 1): | ||
| super().__init__(compute_no_answer=compute_no_answer) | ||
| self.max_k = max_k | ||
|
|
||
| # Track multi-criteria scores for open-ended questions (1-5 scale) | ||
| self.multicriteria_scores = { | ||
| "correctness": [], | ||
| "relevance": [], | ||
| "completeness": [], | ||
| "clarity": [], | ||
| "overall": [], | ||
| } | ||
|
|
||
| def _get_score_dict(self, prediction: dict) -> dict[str, bool | int | float]: | ||
| """Extract correctness scores from prediction.""" | ||
| score_dict = {} | ||
|
|
||
| # Open-ended: extract from judge result | ||
| # Open-ended: use LLM judge correctness score >= 3 as correct | ||
| if "judgement" in prediction: | ||
| judge_result = is_correct_judgement(prediction["judgement"]) | ||
| score_dict["judge_correct"] = judge_result | ||
| score_dict["correct"] = judge_result | ||
| # Closed-form and instruction following: use is_correct | ||
| multicriteria = extract_multicriteria_scores(prediction["judgement"]) | ||
| score_dict["correct"] = multicriteria.get("correctness", 3.0) >= 3.0 | ||
| # Closed-form / instruction-following: use binary correctness | ||
| elif "is_correct" in prediction: | ||
| score_dict["correct"] = prediction["is_correct"] | ||
| else: | ||
|
|
@@ -58,24 +104,61 @@ def get_incorrect_sample(self, prediction: dict) -> dict: | |
| def update(self, predictions): | ||
| """Update metrics with new predictions.""" | ||
| super().update(predictions) | ||
|
|
||
| predicted_answers = [pred.get("generation", None).strip() or None for pred in predictions] | ||
| self._compute_pass_at_k(predictions=predictions, predicted_answers=predicted_answers) | ||
| self._compute_majority_at_k(predictions=predictions, predicted_answers=predicted_answers) | ||
|
|
||
| # Collect multi-criteria scores for open-ended questions | ||
| for pred in predictions: | ||
| if "judgement" in pred: | ||
| multicriteria = extract_multicriteria_scores(pred["judgement"]) | ||
| for criterion in self.multicriteria_scores: | ||
| self.multicriteria_scores[criterion].append(multicriteria.get(criterion, 3.0)) | ||
|
|
||
| def get_metrics(self): | ||
| """Get computed metrics.""" | ||
| metrics_dict = super().get_metrics() | ||
|
|
||
| for agg_mode, agg_metrics in metrics_dict.items(): | ||
| # Ensure avg_tokens is always present for MMAU-Pro | ||
| # Ensure avg_tokens is present | ||
| if "avg_tokens" not in agg_metrics: | ||
| agg_metrics["avg_tokens"] = 0 | ||
| if "no_answer" in agg_metrics: | ||
| agg_metrics["no_answer"] = agg_metrics["no_answer"] / 2.0 | ||
| # Set success_rate from correct or judge_correct | ||
| if "judge_correct" in agg_metrics: | ||
| agg_metrics["success_rate"] = agg_metrics["judge_correct"] | ||
|
|
||
| # Add multi-criteria averages for open-ended (convert 1-5 scale to percentage) | ||
| if self.multicriteria_scores["overall"]: | ||
| for criterion in self.multicriteria_scores: | ||
| scores = self.multicriteria_scores[criterion] | ||
| if scores: | ||
| # Convert 1-5 scale to 0-100 percentage scale | ||
| avg_score = np.mean(scores) | ||
| std_score = np.std(scores) | ||
| agg_metrics[f"avg_{criterion}"] = (avg_score / 5.0) * 100 | ||
| agg_metrics[f"std_{criterion}"] = (std_score / 5.0) * 100 | ||
|
|
||
| # Set correct and success_rate to avg_correctness for open-ended | ||
| agg_metrics["correct"] = agg_metrics["avg_correctness"] | ||
| agg_metrics["success_rate"] = agg_metrics["avg_correctness"] | ||
|
|
||
| # Calculate good/poor response rates based on overall >= 4 or <= 2 | ||
| overall_scores = self.multicriteria_scores["overall"] | ||
| good_responses = sum(1 for score in overall_scores if score >= 4.0) | ||
| poor_responses = sum(1 for score in overall_scores if score <= 2.0) | ||
|
|
||
| agg_metrics["good_response_rate"] = (good_responses / len(overall_scores)) * 100 | ||
| agg_metrics["poor_response_rate"] = (poor_responses / len(overall_scores)) * 100 | ||
|
|
||
| # For closed-form / instruction-following: use binary correctness | ||
| elif "correct" in agg_metrics: | ||
| agg_metrics["success_rate"] = agg_metrics["correct"] | ||
|
|
||
| # Round all numeric values to 2 decimal places | ||
| for key, value in agg_metrics.items(): | ||
| if isinstance(value, float) and not isinstance(value, bool): | ||
| agg_metrics[key] = round(value, 2) | ||
|
|
||
| return metrics_dict | ||
|
|
||
| def metrics_to_print(self): | ||
|
|
@@ -87,5 +170,20 @@ def metrics_to_print(self): | |
| } | ||
| if self.compute_no_answer: | ||
| base_metrics["no_answer"] = as_percentage | ||
|
|
||
| # Add multi-criteria metrics for open-ended questions (now in percentage format) | ||
| if self.multicriteria_scores["overall"]: | ||
| base_metrics.update( | ||
| { | ||
| "avg_overall": as_percentage, | ||
| "avg_correctness": as_percentage, | ||
| "avg_relevance": as_percentage, | ||
| "avg_completeness": as_percentage, | ||
| "avg_clarity": as_percentage, | ||
| "good_response_rate": as_percentage, | ||
| "poor_response_rate": as_percentage, | ||
| } | ||
| ) | ||
|
|
||
| base_metrics["num_entries"] = as_int | ||
| return base_metrics | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential IndexError if audio_path is an empty list.
The code checks
if entry.get("audio_path")which is truthy for a non-empty list, but ifaudio_pathcould be an empty list[], accessingaudio_path[0]would fail. While likely not an issue with the dataset, consider adding explicit length check for robustness.🔎 Suggested defensive check
if entry.get("audio_path"): audio_path = entry["audio_path"] # Prepend /dataset/mmau-pro/ to make paths absolute for cluster - if len(audio_path) == 1: + if audio_path and len(audio_path) == 1: user_message["audio"] = {"path": f"/dataset/mmau-pro/{audio_path[0]}"} - else: + elif audio_path: user_message["audios"] = [{"path": f"/dataset/mmau-pro/{path}"} for path in audio_path]🤖 Prompt for AI Agents