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)) | |
} |