Initial commit: FunASR Speech Recognition Toolkit
Update API Documentation / build-api-docs (push) Has been cancelled

Add complete FunASR codebase including models, runtime, and documentation.
This commit is contained in:
freedakgmail
2026-07-09 22:38:58 +08:00
commit 6116b1f3c6
3683 changed files with 990984 additions and 0 deletions
View File
+245
View File
@@ -0,0 +1,245 @@
#!/usr/bin/env python3
# encoding: utf-8
# Copyright 2017 Johns Hopkins University (Shinji Watanabe)
# Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
"""Common functions for ASR."""
import json
import logging
import sys
from itertools import groupby
from rapidfuzz.distance import Levenshtein
import numpy as np
import six
def end_detect(ended_hyps, i, M=3, D_end=np.log(1 * np.exp(-10))):
"""End detection.
described in Eq. (50) of S. Watanabe et al
"Hybrid CTC/Attention Architecture for End-to-End Speech Recognition"
:param ended_hyps:
:param i:
:param M:
:param D_end:
:return:
"""
if len(ended_hyps) == 0:
return False
count = 0
best_hyp = sorted(ended_hyps, key=lambda x: x["score"], reverse=True)[0]
for m in six.moves.range(M):
# get ended_hyps with their length is i - m
hyp_length = i - m
hyps_same_length = [x for x in ended_hyps if len(x["yseq"]) == hyp_length]
if len(hyps_same_length) > 0:
best_hyp_same_length = sorted(hyps_same_length, key=lambda x: x["score"], reverse=True)[
0
]
if best_hyp_same_length["score"] - best_hyp["score"] < D_end:
count += 1
if count == M:
return True
else:
return False
# TODO(takaaki-hori): add different smoothing methods
def label_smoothing_dist(odim, lsm_type, transcript=None, blank=0):
"""Obtain label distribution for loss smoothing.
:param odim:
:param lsm_type:
:param blank:
:param transcript:
:return:
"""
if transcript is not None:
with open(transcript, "rb") as f:
trans_json = json.load(f)["utts"]
if lsm_type == "unigram":
assert transcript is not None, "transcript is required for %s label smoothing" % lsm_type
labelcount = np.zeros(odim)
for k, v in trans_json.items():
ids = np.array([int(n) for n in v["output"][0]["tokenid"].split()])
# to avoid an error when there is no text in an uttrance
if len(ids) > 0:
labelcount[ids] += 1
labelcount[odim - 1] = len(transcript) # count <eos>
labelcount[labelcount == 0] = 1 # flooring
labelcount[blank] = 0 # remove counts for blank
labeldist = labelcount.astype(np.float32) / np.sum(labelcount)
else:
logging.error("Error: unexpected label smoothing type: %s" % lsm_type)
sys.exit()
return labeldist
def get_vgg2l_odim(idim, in_channel=3, out_channel=128):
"""Return the output size of the VGG frontend.
:param in_channel: input channel size
:param out_channel: output channel size
:return: output size
:rtype int
"""
idim = idim / in_channel
idim = np.ceil(np.array(idim, dtype=np.float32) / 2) # 1st max pooling
idim = np.ceil(np.array(idim, dtype=np.float32) / 2) # 2nd max pooling
return int(idim) * out_channel # numer of channels
class ErrorCalculator(object):
"""Calculate CER and WER for E2E_ASR and CTC models during training.
:param y_hats: numpy array with predicted text
:param y_pads: numpy array with true (target) text
:param char_list:
:param sym_space:
:param sym_blank:
:return:
"""
def __init__(self, char_list, sym_space, sym_blank, report_cer=False, report_wer=False):
"""Construct an ErrorCalculator object."""
super(ErrorCalculator, self).__init__()
self.report_cer = report_cer
self.report_wer = report_wer
self.char_list = char_list
self.space = sym_space
self.blank = sym_blank
self.idx_blank = self.char_list.index(self.blank)
if self.space in self.char_list:
self.idx_space = self.char_list.index(self.space)
else:
self.idx_space = None
def __call__(self, ys_hat, ys_pad, is_ctc=False):
"""Calculate sentence-level WER/CER score.
:param torch.Tensor ys_hat: prediction (batch, seqlen)
:param torch.Tensor ys_pad: reference (batch, seqlen)
:param bool is_ctc: calculate CER score for CTC
:return: sentence-level WER score
:rtype float
:return: sentence-level CER score
:rtype float
"""
cer, wer = None, None
if is_ctc:
return self.calculate_cer_ctc(ys_hat, ys_pad)
elif not self.report_cer and not self.report_wer:
return cer, wer
seqs_hat, seqs_true = self.convert_to_char(ys_hat, ys_pad)
if self.report_cer:
cer = self.calculate_cer(seqs_hat, seqs_true)
if self.report_wer:
wer = self.calculate_wer(seqs_hat, seqs_true)
return cer, wer
def calculate_cer_ctc(self, ys_hat, ys_pad):
"""Calculate sentence-level CER score for CTC.
:param torch.Tensor ys_hat: prediction (batch, seqlen)
:param torch.Tensor ys_pad: reference (batch, seqlen)
:return: average sentence-level CER score
:rtype float
"""
cers, char_ref_lens = [], []
for i, y in enumerate(ys_hat):
y_hat = [x[0] for x in groupby(y)]
y_true = ys_pad[i]
seq_hat, seq_true = [], []
for idx in y_hat:
idx = int(idx)
if idx != -1 and idx != self.idx_blank and idx != self.idx_space:
seq_hat.append(self.char_list[int(idx)])
for idx in y_true:
idx = int(idx)
if idx != -1 and idx != self.idx_blank and idx != self.idx_space:
seq_true.append(self.char_list[int(idx)])
hyp_chars = "".join(seq_hat)
ref_chars = "".join(seq_true)
if len(ref_chars) > 0:
cers.append(Levenshtein.distance(hyp_chars, ref_chars))
char_ref_lens.append(len(ref_chars))
cer_ctc = float(sum(cers)) / sum(char_ref_lens) if cers else None
return cer_ctc
def convert_to_char(self, ys_hat, ys_pad):
"""Convert index to character.
:param torch.Tensor seqs_hat: prediction (batch, seqlen)
:param torch.Tensor seqs_true: reference (batch, seqlen)
:return: token list of prediction
:rtype list
:return: token list of reference
:rtype list
"""
seqs_hat, seqs_true = [], []
for i, y_hat in enumerate(ys_hat):
y_true = ys_pad[i]
eos_true = np.where(y_true == -1)[0]
ymax = eos_true[0] if len(eos_true) > 0 else len(y_true)
# NOTE: padding index (-1) in y_true is used to pad y_hat
seq_hat = [self.char_list[int(idx)] for idx in y_hat[:ymax]]
seq_true = [self.char_list[int(idx)] for idx in y_true if int(idx) != -1]
seq_hat_text = "".join(seq_hat).replace(self.space, " ")
seq_hat_text = seq_hat_text.replace(self.blank, "")
seq_true_text = "".join(seq_true).replace(self.space, " ")
seqs_hat.append(seq_hat_text)
seqs_true.append(seq_true_text)
return seqs_hat, seqs_true
def calculate_cer(self, seqs_hat, seqs_true):
"""Calculate sentence-level CER score.
:param list seqs_hat: prediction
:param list seqs_true: reference
:return: average sentence-level CER score
:rtype float
"""
char_eds, char_ref_lens = [], []
for i, seq_hat_text in enumerate(seqs_hat):
seq_true_text = seqs_true[i]
hyp_chars = seq_hat_text.replace(" ", "")
ref_chars = seq_true_text.replace(" ", "")
char_eds.append(Levenshtein.distance(hyp_chars, ref_chars))
char_ref_lens.append(len(ref_chars))
ref_len = sum(char_ref_lens)
return float(sum(char_eds)) / ref_len if ref_len > 0 else None
def calculate_wer(self, seqs_hat, seqs_true):
"""Calculate sentence-level WER score.
:param list seqs_hat: prediction
:param list seqs_true: reference
:return: average sentence-level WER score
:rtype float
"""
word_eds, word_ref_lens = [], []
for i, seq_hat_text in enumerate(seqs_hat):
seq_true_text = seqs_true[i]
hyp_words = seq_hat_text.split()
ref_words = seq_true_text.split()
word_eds.append(Levenshtein.distance(hyp_words, ref_words))
word_ref_lens.append(len(ref_words))
ref_len = sum(word_ref_lens)
return float(sum(word_eds)) / ref_len if ref_len > 0 else None
+40
View File
@@ -0,0 +1,40 @@
import torch
def th_accuracy(pad_outputs, pad_targets, ignore_label):
"""Calculate accuracy.
Args:
pad_outputs (Tensor): Prediction tensors (B * Lmax, D).
pad_targets (LongTensor): Target label tensors (B, Lmax, D).
ignore_label (int): Ignore label id.
Returns:
float: Accuracy value (0.0 - 1.0).
"""
pad_pred = pad_outputs.view(
pad_targets.size(0), pad_targets.size(1), pad_outputs.size(1)
).argmax(2)
mask = pad_targets != ignore_label
numerator = torch.sum(pad_pred.masked_select(mask) == pad_targets.masked_select(mask))
denominator = torch.sum(mask)
return float(numerator) / float(denominator)
def compute_accuracy(pad_outputs, pad_targets, ignore_label):
"""Calculate accuracy.
Args:
pad_outputs (LongTensor): Prediction tensors (B, Lmax).
pad_targets (LongTensor): Target label tensors (B, Lmax).
ignore_label (int): Ignore label id.
Returns:
float: Accuracy value (0.0 - 1.0).
"""
mask = pad_targets != ignore_label
numerator = torch.sum(pad_outputs.masked_select(mask) == pad_targets.masked_select(mask))
denominator = torch.sum(mask)
return numerator.float() / denominator.float() # (FIX:MZY):return torch.Tensor type
+66
View File
@@ -0,0 +1,66 @@
import numpy as np
from sklearn.metrics import roc_curve
import argparse
def _compute_eer(label, pred, positive_label=1):
"""
Python compute equal error rate (eer)
ONLY tested on binary classification
:param label: ground-truth label, should be a 1-d list or np.array, each element represents the ground-truth label of one sample
:param pred: model prediction, should be a 1-d list or np.array, each element represents the model prediction of one sample
:param positive_label: the class that is viewed as positive class when computing EER
:return: equal error rate (EER)
"""
# all fpr, tpr, fnr, fnr, threshold are lists (in the format of np.array)
fpr, tpr, threshold = roc_curve(label, pred, pos_label=positive_label)
fnr = 1 - tpr
# the threshold of fnr == fpr
eer_threshold = threshold[np.nanargmin(np.absolute((fnr - fpr)))]
# theoretically eer from fpr and eer from fnr should be identical but they can be slightly differ in reality
eer_1 = fpr[np.nanargmin(np.absolute((fnr - fpr)))]
eer_2 = fnr[np.nanargmin(np.absolute((fnr - fpr)))]
# return the mean of eer from fpr and from fnr
eer = (eer_1 + eer_2) / 2
return eer, eer_threshold
def compute_eer(trials_path, scores_path):
"""Compute eer.
Args:
trials_path: TODO.
scores_path: TODO.
"""
labels = []
for one_line in open(trials_path, "r"):
labels.append(one_line.strip().rsplit(" ", 1)[-1] == "target")
labels = np.array(labels, dtype=int)
scores = []
for one_line in open(scores_path, "r"):
scores.append(float(one_line.strip().rsplit(" ", 1)[-1]))
scores = np.array(scores, dtype=float)
eer, threshold = _compute_eer(labels, scores)
return eer, threshold
def main():
"""Main."""
parser = argparse.ArgumentParser()
parser.add_argument("trials", help="trial list")
parser.add_argument("scores", help="score file, normalized to [0, 1]")
args = parser.parse_args()
eer, threshold = compute_eer(args.trials, args.scores)
print("EER is {:.4f} at threshold {:.4f}".format(eer * 100.0, threshold))
if __name__ == "__main__":
main()
+208
View File
@@ -0,0 +1,208 @@
#!/usr/bin/env python3
# Copyright 2018 David Snyder
# Apache 2.0
# This script computes the minimum detection cost function, which is a common
# error metric used in speaker recognition. Compared to equal error-rate,
# which assigns equal weight to false negatives and false positives, this
# error-rate is usually used to assess performance in settings where achieving
# a low false positive rate is more important than achieving a low false
# negative rate. See the NIST 2016 Speaker Recognition Evaluation Plan at
# https://www.nist.gov/sites/default/files/documents/2016/10/07/sre16_eval_plan_v1.3.pdf
# for more details about the metric.
from __future__ import print_function
from operator import itemgetter
import sys, argparse, os
def GetArgs():
"""Getargs."""
parser = argparse.ArgumentParser(
description="Compute the minimum "
"detection cost function along with the threshold at which it occurs. "
"Usage: sid/compute_min_dcf.py [options...] <scores-file> "
"<trials-file> "
"E.g., sid/compute_min_dcf.py --p-target 0.01 --c-miss 1 --c-fa 1 "
"exp/scores/trials data/test/trials",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"--p-target",
type=float,
dest="p_target",
default=0.01,
help="The prior probability of the target speaker in a trial.",
)
parser.add_argument(
"--c-miss",
type=float,
dest="c_miss",
default=1,
help="Cost of a missed detection. This is usually not changed.",
)
parser.add_argument(
"--c-fa",
type=float,
dest="c_fa",
default=1,
help="Cost of a spurious detection. This is usually not changed.",
)
parser.add_argument(
"scores_filename",
help="Input scores file, with columns of the form " "<utt1> <utt2> <score>",
)
parser.add_argument(
"trials_filename",
help="Input trials file, with columns of the form " "<utt1> <utt2> <target/nontarget>",
)
sys.stderr.write(" ".join(sys.argv) + "\n")
args = parser.parse_args()
args = CheckArgs(args)
return args
def CheckArgs(args):
"""Checkargs.
Args:
args: TODO.
"""
if args.c_fa <= 0:
raise Exception("--c-fa must be greater than 0")
if args.c_miss <= 0:
raise Exception("--c-miss must be greater than 0")
if args.p_target <= 0 or args.p_target >= 1:
raise Exception("--p-target must be greater than 0 and less than 1")
return args
# Creates a list of false-negative rates, a list of false-positive rates
# and a list of decision thresholds that give those error-rates.
def ComputeErrorRates(scores, labels):
# Sort the scores from smallest to largest, and also get the corresponding
# indexes of the sorted scores. We will treat the sorted scores as the
# thresholds at which the the error-rates are evaluated.
"""Computeerrorrates.
Args:
scores: TODO.
labels: TODO.
"""
sorted_indexes, thresholds = zip(
*sorted([(index, threshold) for index, threshold in enumerate(scores)], key=itemgetter(1))
)
labels = [labels[i] for i in sorted_indexes]
fns = []
tns = []
# At the end of this loop, fns[i] is the number of errors made by
# incorrectly rejecting scores less than thresholds[i]. And, tns[i]
# is the total number of times that we have correctly rejected scores
# less than thresholds[i].
for i in range(0, len(labels)):
if i == 0:
fns.append(labels[i])
tns.append(1 - labels[i])
else:
fns.append(fns[i - 1] + labels[i])
tns.append(tns[i - 1] + 1 - labels[i])
positives = sum(labels)
negatives = len(labels) - positives
# Now divide the false negatives by the total number of
# positives to obtain the false negative rates across
# all thresholds
fnrs = [fn / float(positives) for fn in fns]
# Divide the true negatives by the total number of
# negatives to get the true negative rate. Subtract these
# quantities from 1 to get the false positive rates.
fprs = [1 - tn / float(negatives) for tn in tns]
return fnrs, fprs, thresholds
# Computes the minimum of the detection cost function. The comments refer to
# equations in Section 3 of the NIST 2016 Speaker Recognition Evaluation Plan.
def ComputeMinDcf(fnrs, fprs, thresholds, p_target, c_miss, c_fa):
"""Computemindcf.
Args:
fnrs: TODO.
fprs: TODO.
thresholds: TODO.
p_target: TODO.
c_miss: TODO.
c_fa: TODO.
"""
min_c_det = float("inf")
min_c_det_threshold = thresholds[0]
for i in range(0, len(fnrs)):
# See Equation (2). it is a weighted sum of false negative
# and false positive errors.
c_det = c_miss * fnrs[i] * p_target + c_fa * fprs[i] * (1 - p_target)
if c_det < min_c_det:
min_c_det = c_det
min_c_det_threshold = thresholds[i]
# See Equations (3) and (4). Now we normalize the cost.
c_def = min(c_miss * p_target, c_fa * (1 - p_target))
min_dcf = min_c_det / c_def
return min_dcf, min_c_det_threshold
def compute_min_dcf(scores_filename, trials_filename, c_miss=1, c_fa=1, p_target=0.01):
"""Compute min dcf.
Args:
scores_filename: TODO.
trials_filename: TODO.
c_miss: TODO.
c_fa: TODO.
p_target: TODO.
"""
scores_file = open(scores_filename, "r").readlines()
trials_file = open(trials_filename, "r").readlines()
c_miss = c_miss
c_fa = c_fa
p_target = p_target
scores = []
labels = []
trials = {}
for line in trials_file:
utt1, utt2, target = line.rstrip().split()
trial = utt1 + " " + utt2
trials[trial] = target
for line in scores_file:
utt1, utt2, score = line.rstrip().split()
trial = utt1 + " " + utt2
if trial in trials:
scores.append(float(score))
if trials[trial] == "target":
labels.append(1)
else:
labels.append(0)
else:
raise Exception("Missing entry for " + utt1 + " and " + utt2 + " " + scores_filename)
fnrs, fprs, thresholds = ComputeErrorRates(scores, labels)
mindcf, threshold = ComputeMinDcf(fnrs, fprs, thresholds, p_target, c_miss, c_fa)
return mindcf, threshold
def main():
"""Main."""
args = GetArgs()
mindcf, threshold = compute_min_dcf(
args.scores_filename, args.trials_filename, args.c_miss, args.c_fa, args.p_target
)
sys.stdout.write(
"minDCF is {0:.4f} at threshold {1:.4f} (p-target={2}, c-miss={3}, "
"c-fa={4})\n".format(mindcf, threshold, args.p_target, args.c_miss, args.c_fa)
)
if __name__ == "__main__":
main()
+249
View File
@@ -0,0 +1,249 @@
import os
import numpy as np
import sys
import hydra
from omegaconf import DictConfig, OmegaConf, ListConfig
def compute_wer(
ref_file,
hyp_file,
cer_file,
cn_postprocess=False,
):
"""Compute wer.
Args:
ref_file: TODO.
hyp_file: TODO.
cer_file: TODO.
cn_postprocess: TODO.
"""
rst = {
"Wrd": 0,
"Corr": 0,
"Ins": 0,
"Del": 0,
"Sub": 0,
"Snt": 0,
"Err": 0.0,
"S.Err": 0.0,
"wrong_words": 0,
"wrong_sentences": 0,
}
hyp_dict = {}
ref_dict = {}
with open(hyp_file, "r") as hyp_reader:
for line in hyp_reader:
key = line.strip().split()[0]
value = line.strip().split()[1:]
if cn_postprocess:
value = " ".join(value)
value = value.replace(" ", "")
# if value[0] == "请":
# value = value[1:]
value = [x for x in value]
hyp_dict[key] = value
with open(ref_file, "r") as ref_reader:
for line in ref_reader:
key = line.strip().split()[0]
value = line.strip().split()[1:]
if cn_postprocess:
value = " ".join(value)
value = value.replace(" ", "")
value = [x for x in value]
ref_dict[key] = value
cer_detail_writer = open(cer_file, "w")
for hyp_key in hyp_dict:
if hyp_key in ref_dict:
out_item = compute_wer_by_line(hyp_dict[hyp_key], ref_dict[hyp_key])
rst["Wrd"] += out_item["nwords"]
rst["Corr"] += out_item["cor"]
rst["wrong_words"] += out_item["wrong"]
rst["Ins"] += out_item["ins"]
rst["Del"] += out_item["del"]
rst["Sub"] += out_item["sub"]
rst["Snt"] += 1
if out_item["wrong"] > 0:
rst["wrong_sentences"] += 1
cer_detail_writer.write(hyp_key + print_cer_detail(out_item) + "\n")
cer_detail_writer.write(
"ref:" + "\t" + " ".join(list(map(lambda x: x.lower(), ref_dict[hyp_key]))) + "\n"
)
cer_detail_writer.write(
"hyp:" + "\t" + " ".join(list(map(lambda x: x.lower(), hyp_dict[hyp_key]))) + "\n"
)
cer_detail_writer.flush()
if rst["Wrd"] > 0:
rst["Err"] = round(rst["wrong_words"] * 100 / rst["Wrd"], 2)
if rst["Snt"] > 0:
rst["S.Err"] = round(rst["wrong_sentences"] * 100 / rst["Snt"], 2)
cer_detail_writer.write("\n")
cer_detail_writer.write(
"%WER "
+ str(rst["Err"])
+ " [ "
+ str(rst["wrong_words"])
+ " / "
+ str(rst["Wrd"])
+ ", "
+ str(rst["Ins"])
+ " ins, "
+ str(rst["Del"])
+ " del, "
+ str(rst["Sub"])
+ " sub ]"
+ "\n"
)
cer_detail_writer.write(
"%SER "
+ str(rst["S.Err"])
+ " [ "
+ str(rst["wrong_sentences"])
+ " / "
+ str(rst["Snt"])
+ " ]"
+ "\n"
)
cer_detail_writer.write(
"Scored "
+ str(len(hyp_dict))
+ " sentences, "
+ str(len(hyp_dict) - rst["Snt"])
+ " not present in hyp."
+ "\n"
)
cer_detail_writer.close()
def compute_wer_by_line(hyp, ref):
"""Compute wer by line.
Args:
hyp: TODO.
ref: TODO.
"""
hyp = list(map(lambda x: x.lower(), hyp))
ref = list(map(lambda x: x.lower(), ref))
len_hyp = len(hyp)
len_ref = len(ref)
cost_matrix = np.zeros((len_hyp + 1, len_ref + 1), dtype=np.int16)
ops_matrix = np.zeros((len_hyp + 1, len_ref + 1), dtype=np.int8)
for i in range(len_hyp + 1):
cost_matrix[i][0] = i
for j in range(len_ref + 1):
cost_matrix[0][j] = j
for i in range(1, len_hyp + 1):
for j in range(1, len_ref + 1):
if hyp[i - 1] == ref[j - 1]:
cost_matrix[i][j] = cost_matrix[i - 1][j - 1]
else:
substitution = cost_matrix[i - 1][j - 1] + 1
insertion = cost_matrix[i - 1][j] + 1
deletion = cost_matrix[i][j - 1] + 1
compare_val = [substitution, insertion, deletion]
min_val = min(compare_val)
operation_idx = compare_val.index(min_val) + 1
cost_matrix[i][j] = min_val
ops_matrix[i][j] = operation_idx
match_idx = []
i = len_hyp
j = len_ref
rst = {"nwords": len_ref, "cor": 0, "wrong": 0, "ins": 0, "del": 0, "sub": 0}
while i >= 0 or j >= 0:
i_idx = max(0, i)
j_idx = max(0, j)
if ops_matrix[i_idx][j_idx] == 0: # correct
if i - 1 >= 0 and j - 1 >= 0:
match_idx.append((j - 1, i - 1))
rst["cor"] += 1
i -= 1
j -= 1
elif ops_matrix[i_idx][j_idx] == 2: # insert
i -= 1
rst["ins"] += 1
elif ops_matrix[i_idx][j_idx] == 3: # delete
j -= 1
rst["del"] += 1
elif ops_matrix[i_idx][j_idx] == 1: # substitute
i -= 1
j -= 1
rst["sub"] += 1
if i < 0 and j >= 0:
rst["del"] += 1
elif j < 0 and i >= 0:
rst["ins"] += 1
match_idx.reverse()
wrong_cnt = cost_matrix[len_hyp][len_ref]
rst["wrong"] = wrong_cnt
return rst
def print_cer_detail(rst):
"""Print cer detail.
Args:
rst: TODO.
"""
return (
"("
+ "nwords="
+ str(rst["nwords"])
+ ",cor="
+ str(rst["cor"])
+ ",ins="
+ str(rst["ins"])
+ ",del="
+ str(rst["del"])
+ ",sub="
+ str(rst["sub"])
+ ") corr:"
+ "{:.2%}".format(rst["cor"] / rst["nwords"])
+ ",cer:"
+ "{:.2%}".format(rst["wrong"] / rst["nwords"])
)
@hydra.main(config_name=None, version_base=None)
def main_hydra(cfg: DictConfig):
"""Main hydra.
Args:
cfg: Configuration overrides.
"""
ref_file = cfg.get("ref_file", None)
hyp_file = cfg.get("hyp_file", None)
cer_file = cfg.get("cer_file", None)
cn_postprocess = cfg.get("cn_postprocess", False)
if ref_file is None or hyp_file is None or cer_file is None:
print(
"usage : python -m funasr.metrics.wer ++ref_file=test.ref ++hyp_file=test.hyp ++cer_file=test.wer ++cn_postprocess=false"
)
sys.exit(0)
compute_wer(ref_file, hyp_file, cer_file, cn_postprocess)
if __name__ == "__main__":
main_hydra()