--- license: apache-2.0 language: - sl - hr - sr - cs - pl base_model: - facebook/w2v-bert-2.0 pipeline_tag: audio-classification metrics: - f1 - recall - precision --- # Frame classification for filled pauses This model classifies individual 20ms frames of audio based on presence of filled pauses ("eee", "errm", ...). # Training data The model was trained on human-annotated Slovenian speech corpus [ROG-Artur](http://hdl.handle.net/11356/1992). Recordings from the train split were segmented into at most 30s long chunks. # Evaluation Although the output of the model is a series 0 or 1, describing their 20ms frames, the evaluation was done on event level; spans of consecutive outputs 1 were bundled together into one event. When the true and predicted events partially overlap, this is counted as a true positive. We report precisions, recalls, and F1-scores of the positive class. ## Evaluation on ROG corpus | postprocessing | recall | precision | F1 | |------:|---------:|------------:|------:| |none| 0.981 | 0.955 | 0.968 | ## Evaluation on ParlaSpeech corpora For every language in the [ParlaSpeech collection](https://huggingface.co/collections/classla/parlaspeech-670923f23ab185f413d40795), 400 instances were sampled and annotated by human annotators. Since ParlaSpeech corpora are too big to be manually segmented as ROG is, we observed a few failure modes when inferring. It was discovered that post-processing can be used to improve results. False positives were observed to be caused by improper audio segmentation, which is why disabling predictions that start at the start of the audio or end at the end of the audio can be beneficial. Another failure mode is predicting very short events, which is why ignoring very short predictions can be safely discarded. With added postprocessing, the model achieves the following metrics: | lang | postprocessing | recall | precision | F1 | |:-------|:-----------------------|---------:|------------:|------:| | CZ | drop_short_initial_and_final | 0.889 | 0.859 | 0.874 | | HR | drop_short_initial_and_final | 0.94 | 0.887 | 0.913 | | PL | drop_short_initial_and_final | 0.903 | 0.947 | 0.924 | | RS | drop_short_initial_and_final | 0.966 | 0.915 | 0.94 | Fop details on postprocessing see function `frames_to_intervals` in the code snippet below. # Example use: ```python from transformers import AutoFeatureExtractor, Wav2Vec2BertForAudioFrameClassification from datasets import Dataset, Audio import torch import numpy as np from pathlib import Path device = torch.device("cuda") model_name = "classla/wav2vecbert2-filledPause" feature_extractor = AutoFeatureExtractor.from_pretrained(model_name) model = Wav2Vec2BertForAudioFrameClassification.from_pretrained(model_name).to(device) ds = Dataset.from_dict( { "audio": [ "/cache/peterr/mezzanine_resources/filled_pauses/data/dev/Iriss-J-Gvecg-P500001-avd_2082.293_2112.194.wav" ], } ).cast_column("audio", Audio(sampling_rate=16_000, mono=True)) def frames_to_intervals( frames: list[int], drop_short=True, drop_initial=True, drop_final=True, short_cutoff_s=0.08, ) -> list[tuple[float]]: """Transforms a list of ones or zeros, corresponding to annotations on frame levels, to a list of intervals ([start second, end second]). Allows for additional filtering on duration (false positives are often short) and start times (false positives starting at 0.0 are often an artifact of poor segmentation). :param list[int] frames: Input frame labels :param bool drop_short: Drop everything shorter than short_cutoff_s, defaults to True :param bool drop_initial: Drop predictions starting at 0.0, defaults to True :param bool drop_final: Drop predictions ending at audio end, defaults to True :param float short_cutoff_s: Duration in seconds of shortest allowable prediction, defaults to 0.08 :return list[tuple[float]]: List of intervals [start_s, end_s] """ from itertools import pairwise import pandas as pd results = [] ndf = pd.DataFrame( data={ "time_s": [0.020 * i for i in range(len(frames))], "frames": frames, } ) ndf = ndf.dropna() indices_of_change = ndf.frames.diff()[ndf.frames.diff() != 0].index.values for si, ei in pairwise(indices_of_change): if ndf.loc[si : ei - 1, "frames"].mode()[0] == 0: pass else: results.append( ( round(ndf.loc[si, "time_s"], 3), round(ndf.loc[ei, "time_s"], 3), ) ) if drop_short and (len(results) > 0): results = [i for i in results if (i[1] - i[0] >= short_cutoff_s)] if drop_initial and (len(results) > 0): results = [i for i in results if i[0] != 0.0] if drop_final and (len(results) > 0): results = [i for i in results if i[1] != 0.02 * len(frames)] return results def evaluator(chunks): sampling_rate = chunks["audio"][0]["sampling_rate"] with torch.no_grad(): inputs = feature_extractor( [i["array"] for i in chunks["audio"]], return_tensors="pt", sampling_rate=sampling_rate, ).to(device) logits = model(**inputs).logits y_pred = np.array(logits.cpu()).argmax(axis=-1) intervals = [frames_to_intervals(i) for i in y_pred] return {"y_pred": y_pred.tolist(), "intervals": intervals} ds = ds.map(evaluator, batched=True) print(ds["y_pred"][0]) # Prints a list of 20ms frames: [0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0....] # with 0 indicating no filled pause detected in that frame print(ds["intervals"][0]) # Prints the identified intervals as a list of [start_s, ends_s]: # [[0.08, 0.28 ], ...] ``` # Citation Coming soon.