Evaluation methods¶
The harness in evaluation/ measures how well an aligner places phoneme boundaries on TIMIT and Buckeye, for the BFA
models and for the Montreal Forced Aligner (MFA), against the corpora's hand labels.
This page explains how the harness works. The current results and their analysis are on the evaluation page.
There are two steps, and one file configures both:
python evaluation/process_alignments.py # 1. align: every experiment in evaluate.yaml
python evaluation/compute_metrics.py # 2. score: one CSV per dataset, one row per experiment
Both can be run from any directory, and both can be run again at any time: an experiment that is already aligned is not aligned again, and one that is already in the CSV is not scored again. Adding a model or a setting to the yaml only costs the new runs.
Before the first run¶
- Corpora. Set
paths.audio_rootsinevaluate.yamlto where TIMIT and Buckeye are. - Ground truth. Each parser reads a corpus and writes the transcripts and reference
boundaries the other steps use (
tmp/data/<dataset>_data/andtmp/paths/<dataset>_paths_all.json):
python evaluation/dataset/timit_parser.py
python evaluation/dataset/buckeye_parser.py
python evaluation/dataset/l2arctic_parser.py --base /path/to/l2arctic_release_v5.0 # optional
L2-ARCTIC (non-native English) gives two datasets, l2arctic (3,599 read sentences) and
l2arctic_suitcase (22 spontaneous narratives). Only its hand-annotated clips are used, and
most of their boundaries are still the MFA 1.0 alignment the annotators started from: the
parser's docstring has the details.
Run them again only when a parser changes. Alignments made before that no longer match
the new segments and have to be redone (run.existing: redo).
3. MFA, if it is in your experiments, in its own conda environment:
The environment's name goes in mfa.conda_env. You do not activate it: the sweep runs in
your usual environment and calls conda run -n mfa mfa ... itself. The acoustic model and
dictionary are downloaded on first use.
evaluate.yaml¶
Every option is described in the file, with its possible values. The sections are:
| section | what it holds |
|---|---|
paths |
corpus audio, the local release directory, the results CSVs |
models |
every model an experiment may name: published BFA models, local checkpoints, MFA |
run |
what to do with an experiment that already exists, logging, threads |
bfa |
the value of every BFA setting, for experiments that do not set it themselves |
mfa |
conda environment, number of jobs, extra arguments |
experiments |
the list of runs |
scoring |
tolerances, offsets and the other scorer options |
Every key is required and an unknown key is an error, so the file is always the complete description of a run.
Experiments¶
experiments is a list. In each entry any key can be one value or a list, and the entry
stands for every combination; the entries run one after another.
experiments:
- name: released models and MFA
dataset: [timit, buckeye]
model: [CUPE-3-en, mbfa-latin, mfa-english_us_arpa] # 2 x 3 = 6 runs
- name: boundary bonus
dataset: timit
subset: 100 # 100 random clips, same for all
model: [CUPE-3-en, mbfa-latin]
boundary_bonus_weight: [0, 15, 30] # 2 x 3 = 6 runs
datasetandmodelare required.subsetis optional (-1, every clip).- Any key of the
bfasection can be added to an entry to override it or sweep it. enabled: falsekeeps an entry in the file without running it.- MFA takes none of the BFA settings, so in an entry that sweeps them it runs once.
- A run described twice, in one entry or two, is done once.
What a run is called¶
Each experiment gets a name made of the dataset, the model and its settings, and its alignments go to a directory of that name:
tmp/alignments/timit__CUPE-3-en__b30_d6_s10_grid/
tmp/alignments/buckeye__mbfa-latin__b0_d6_s10_grid_n10/
tmp/alignments/timit__mfa-english_us_arpa/
| part | meaning |
|---|---|
b30 |
boundary_bonus_weight 30 |
d6 |
min_phone_frames 6 |
s10 |
soft_phone_frames 10 |
grid / subframe |
sub_frame_onsets off / on |
n10 |
subset 10; absent for the full corpus |
dur, bs, cov, lang, bct, lcl, chunk, seg |
duration_max, batch_size, coverage_min, lang, bad_confidence_threshold, low_confidence_level, chunk_audio, segment_max_seconds. Each appears only when it differs from its usual value (30, 16, 0.6, en-us, 0.6, null, true, 120) |
The decoder values in the name are the ones that actually ran. null in the yaml means "the
value the model was released with", so boundary_bonus_weight: null and 30 give the same
name for a model released at 30, and that run is done once.
Inside the directory:
<clip>.json one alignment per audio file
_manifest.json the full configuration and the run's statistics
_log.txt the run's log
The name is the cache key. To throw a run away, delete its directory.
Step 1: aligning¶
python evaluation/process_alignments.py # everything enabled in the yaml
python evaluation/process_alignments.py --list # what would run, and what is cached
python evaluation/process_alignments.py --only 'timit__*' # only names matching the pattern
python evaluation/process_alignments.py --config other.yaml
python evaluation/process_alignments.py 4 0 # shell 1 of 4, see below
--list prints each experiment as new, partial (started, not finished) or done.
With run.existing: skip, a done experiment is skipped and a partial one continues where
it stopped. With redo, the directory is deleted and the experiment is aligned from scratch.
If one experiment fails, the rest still run; the failures are listed at the end and the exit code is 1.
In parallel. process_alignments.py N K runs every N-th experiment starting at the K-th,
so N shells with K = 0 … N-1 cover the list between them:
python evaluation/process_alignments.py 4 0 &
python evaluation/process_alignments.py 4 1 &
python evaluation/process_alignments.py 4 2 &
python evaluation/process_alignments.py 4 3 &
Do not let two shells run MFA at the same time; MFA keeps one shared database per user.
Cost. The harness calls BFA once per clip. A Buckeye clip is a whole interview of about 130
segments, so use subset for a quick look. On a CPU with the harness's 30 s padding, BFA runs at
about 0.1-0.12 × real time on Buckeye; with a GPU, about 0.01 ×. MFA has about a minute of fixed
start-up and setup per call, then runs fast. With 8 jobs it aligned all of Buckeye in 12-19 min.
Measured timings for both are on the evaluation page.
Step 2: scoring¶
python evaluation/compute_metrics.py # score what is not in the CSV yet
python evaluation/compute_metrics.py --list # what is on disk, and what is scored
python evaluation/compute_metrics.py --only '*__n100'
python evaluation/compute_metrics.py --rescore # recompute rows that already exist
python evaluation/compute_metrics.py --rescore --yes # ... without asking
The scorer does not read the experiments list. It looks at every finished run under
tmp/alignments/, reads the CSVs, and scores only what has no row yet. Each dataset has its
own file, evaluation/results/results_timit.csv and results_buckeye.csv
(paths.results_csv in the yaml).
A row is replaced in two cases, and in both the scorer asks first:
- the experiment was aligned again after its row was written;
- you passed
--rescore, which is what to do after changing thescoringsection or re-running a parser.
A copy of the CSV is kept beside it as .bak before any row is replaced. Without a terminal
to ask on (a batch job), rows are only replaced with --yes.
For each experiment scored it also prints the full breakdown and writes
tmp/plots/<experiment>.json (the detail, including all_distances: for every hand-labelled
boundary, the distance to the nearest predicted onset) and .png (the histogram of those
distances).
Step 3: the figures for the docs¶
The evaluation page shows the runs in evaluation/results/ and the plots in
evaluation/plots/. To refresh them after a new full-corpus run, copy that run's tmp/plots/<experiment>.png
and .json into evaluation/plots/, then redraw the two comparison figures:
It draws one bar group or line per method from the full-corpus rows (cfg_subset -1) of each CSV.
Only the .png files in evaluation/plots/ are committed. The JSONs are ~3 MB each and stay local.
Reading the CSV¶
One file per dataset, one row per experiment. The columns, in order:
| columns | meaning |
|---|---|
experiment |
the run's name, as above |
method, dataset, cfg_model, cfg_ckpt, phoneme_set |
BFA or MFA, the corpus, the model |
notes |
a short label; * marks a decoder value left to the model |
cfg_* |
the settings as they ran, and when the alignment finished |
f1_<t>ms, r_value_<t>ms, recall_<t>ms, precision_<t>ms |
boundary scores at each tolerance |
files, total_known, total_pred, detection_ratio |
how much was scored |
abs_err_*, signed_err_median, missed_pct, fakes_pct |
timing error of the matched boundaries, in ms |
ex_* |
the same scores with gross errors left out; not comparable to a published F1 |
mn_* |
mean error per manner class: filled for every method, so use these to compare models |
err_*, std_* |
mean error per phoneme group of the ph66 / std set |
The headline number is f1_25ms: the F1 of phoneme onsets within 25 ms of a hand-labelled
one. Only compare rows with the same files count; a 100-clip row
reads a few points away from a full-corpus one.
How boundaries are matched is described at the top of compute_metrics.py. How the corpus
labels become reference boundaries is in LABEL_MAPPING.md.
Files¶
All in evaluation/.
| file | role |
|---|---|
evaluate.yaml |
the configuration |
process_alignments.py |
step 1 |
align_with_mfa.py |
the MFA part of step 1; not run directly |
compute_metrics.py |
step 2 |
plot_summary.py |
step 3: the comparison figures on the evaluation page |
config.py |
reads and checks the yaml |
layout.py |
where files go |
dataset/ |
the corpus parsers (timit_parser.py, buckeye_parser.py, l2arctic_parser.py), the label tables (std_labels.py), and the training probe cache (probe_cache.py) |
LABEL_MAPPING.md |
how each corpus's labels become reference boundaries |
results/results_<dataset>.csv |
the scores |
plots/ |
the histograms and figures that the evaluation page shows |