Spaces:
Running
Running
| # phone_metrics.py | |
| """ | |
| This module implements phone error metrics based on the work from ginic/phone_errors. | |
| Original implementation: https://huggingface.co/spaces/ginic/phone_errors | |
| Citation: | |
| @inproceedings{Mortensen-et-al:2016, | |
| author = {David R. Mortensen and | |
| Patrick Littell and | |
| Akash Bharadwaj and | |
| Kartik Goyal and | |
| Chris Dyer and | |
| Lori S. Levin}, | |
| title = {PanPhon: {A} Resource for Mapping {IPA} Segments to Articulatory Feature Vectors}, | |
| booktitle = {Proceedings of {COLING} 2016, the 26th International Conference on Computational Linguistics: Technical Papers}, | |
| pages = {3475--3484}, | |
| publisher = {{ACL}}, | |
| year = {2016} | |
| } | |
| """ | |
| import numpy as np | |
| import panphon.distance | |
| from typing import List, Dict | |
| class PhoneErrorMetrics: | |
| def __init__(self, feature_model: str = "segment"): | |
| """Initialize the phone error metrics calculator. | |
| Args: | |
| feature_model (str): panphon feature parsing model ("strict", "permissive", or "segment") | |
| """ | |
| self.distance_computer = panphon.distance.Distance(feature_model=feature_model) | |
| def _phone_error_rate(self, prediction: str, reference: str) -> float: | |
| """Compute phone error rate between prediction and reference. | |
| Args: | |
| prediction (str): Predicted IPA string | |
| reference (str): Reference IPA string | |
| Returns: | |
| float: Phone error rate | |
| """ | |
| if not reference: | |
| raise ValueError("Reference string cannot be empty") | |
| pred_phones = self.distance_computer.fm.ipa_segs(prediction) | |
| ref_phones = self.distance_computer.fm.ipa_segs(reference) | |
| phone_edits = self.distance_computer.min_edit_distance( | |
| lambda x: 1, # deletion cost | |
| lambda x: 1, # insertion cost | |
| lambda x, y: 0 if x == y else 1, # substitution cost | |
| [[]], | |
| pred_phones, | |
| ref_phones | |
| ) | |
| return phone_edits / len(ref_phones) | |
| def compute(self, | |
| predictions: List[str], | |
| references: List[str], | |
| is_normalize_pfer: bool = False) -> Dict: | |
| """Compute phone error metrics between predictions and references. | |
| Args: | |
| predictions (List[str]): List of predicted IPA strings | |
| references (List[str]): List of reference IPA strings | |
| is_normalize_pfer (bool): Whether to normalize phone feature error rates | |
| Returns: | |
| Dict containing: | |
| - phone_error_rates: List of PER for each pair | |
| - mean_phone_error_rate: Average PER | |
| - phone_feature_error_rates: List of PFER for each pair | |
| - mean_phone_feature_error_rate: Average PFER | |
| - feature_error_rates: List of FER for each pair | |
| - mean_feature_error_rate: Average FER | |
| """ | |
| phone_error_rates = [] | |
| feature_error_rates = [] | |
| hamming_distances = [] | |
| for pred, ref in zip(predictions, references): | |
| if is_normalize_pfer: | |
| hd = self.distance_computer.hamming_feature_edit_distance_div_maxlen(pred, ref) | |
| else: | |
| hd = self.distance_computer.hamming_feature_edit_distance(pred, ref) | |
| hamming_distances.append(hd) | |
| per = self._phone_error_rate(pred, ref) | |
| phone_error_rates.append(per) | |
| fer = self.distance_computer.feature_error_rate(pred, ref) | |
| feature_error_rates.append(fer) | |
| return { | |
| "phone_error_rates": phone_error_rates, | |
| "mean_phone_error_rate": float(np.mean(phone_error_rates)), | |
| "phone_feature_error_rates": hamming_distances, | |
| "mean_phone_feature_error_rate": float(np.mean(hamming_distances)), | |
| "feature_error_rates": feature_error_rates, | |
| "mean_feature_error_rate": float(np.mean(feature_error_rates)) | |
| } |