File size: 4,095 Bytes
3a023fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# 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))
        }