Skip to content

Commit 1afe413

Browse files
Refactor diarization (#341)
* refactor diarization * remove sortformer * code review * update requirements * remove constraints * test fix for windows
1 parent 7348644 commit 1afe413

11 files changed

Lines changed: 319 additions & 529 deletions

constraints.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
huggingface_hub<0.24
21
numpy<2
2+
indic-numtowords @ git+https://github.com/AI4Bharat/indic-numtowords.git

diarization/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .msdd.msdd import MSDDDiarizer
2+
3+
__all__ = ["MSDDDiarizer"]

nemo_msdd_configs/diar_infer_telephonic.yaml renamed to diarization/msdd/diar_infer_telephonic.yaml

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# {"audio_filepath": "/path/to/audio_file", "offset": 0, "duration": null, "label": "infer", "text": "-", "num_speakers": null, "rttm_filepath": "/path/to/rttm/file", "uem_filepath": "/path/to/uem/file"}
77
name: &name "ClusterDiarizer"
88

9-
num_workers: 1
9+
num_workers: 0
1010
sample_rate: 16000
1111
batch_size: 64
1212
device: null # can specify a specific device, i.e: cuda:1 (default cuda if cuda available, else cpu)
@@ -65,30 +65,5 @@ diarizer:
6565
split_infer: True # If True, break the input audio clip to short sequences and calculate cluster average embeddings for inference.
6666
diar_window_length: 50 # The length of split short sequence when split_infer is True.
6767
overlap_infer_spk_limit: 5 # If the estimated number of speakers are larger than this number, overlap speech is not estimated.
68-
69-
asr:
70-
model_path: stt_en_conformer_ctc_large # Provide NGC cloud ASR model name. stt_en_conformer_ctc_* models are recommended for diarization purposes.
71-
parameters:
72-
asr_based_vad: False # if True, speech segmentation for diarization is based on word-timestamps from ASR inference.
73-
asr_based_vad_threshold: 1.0 # Threshold (in sec) that caps the gap between two words when generating VAD timestamps using ASR based VAD.
74-
asr_batch_size: null # Batch size can be dependent on each ASR model. Default batch sizes are applied if set to null.
75-
decoder_delay_in_sec: null # Native decoder delay. null is recommended to use the default values for each ASR model.
76-
word_ts_anchor_offset: null # Offset to set a reference point from the start of the word. Recommended range of values is [-0.05 0.2].
77-
word_ts_anchor_pos: "start" # Select which part of the word timestamp we want to use. The options are: 'start', 'end', 'mid'.
78-
fix_word_ts_with_VAD: False # Fix the word timestamp using VAD output. You must provide a VAD model to use this feature.
79-
colored_text: False # If True, use colored text to distinguish speakers in the output transcript.
80-
print_time: True # If True, the start and end time of each speaker turn is printed in the output transcript.
81-
break_lines: False # If True, the output transcript breaks the line to fix the line width (default is 90 chars)
82-
83-
ctc_decoder_parameters: # Optional beam search decoder (pyctcdecode)
84-
pretrained_language_model: null # KenLM model file: .arpa model file or .bin binary file.
85-
beam_width: 32
86-
alpha: 0.5
87-
beta: 2.5
8868

89-
realigning_lm_parameters: # Experimental feature
90-
arpa_language_model: null # Provide a KenLM language model in .arpa format.
91-
min_number_of_words: 3 # Min number of words for the left context.
92-
max_number_of_words: 10 # Max number of words for the right context.
93-
logprob_diff_threshold: 1.2 # The threshold for the difference between two log probability values from two hypotheses.
9469

diarization/msdd/msdd.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import json
2+
import os
3+
import tempfile
4+
5+
from typing import Union
6+
7+
import torch
8+
import torchaudio
9+
10+
from nemo.collections.asr.models.msdd_models import NeuralDiarizer
11+
from nemo.collections.asr.parts.utils.speaker_utils import rttm_to_labels
12+
from omegaconf import OmegaConf
13+
14+
15+
class MSDDDiarizer:
16+
def __init__(self, device: Union[str, torch.device]):
17+
self.model: NeuralDiarizer = NeuralDiarizer(cfg=create_config()).to(device)
18+
19+
def diarize(self, audio: torch.Tensor):
20+
with tempfile.TemporaryDirectory() as temp_path:
21+
torchaudio.save(
22+
os.path.join(temp_path, "mono_file.wav"),
23+
audio,
24+
16000,
25+
channels_first=True,
26+
)
27+
28+
manifest_path = os.path.join(temp_path, "manifest.json")
29+
meta = {
30+
"audio_filepath": os.path.join(temp_path, "mono_file.wav"),
31+
"offset": 0,
32+
"duration": None,
33+
"label": "infer",
34+
"text": "-",
35+
"rttm_filepath": None,
36+
"uem_filepath": None,
37+
}
38+
39+
with open(manifest_path, "w") as f:
40+
json.dump(meta, f)
41+
42+
self.model._initialize_configs(
43+
manifest_path=manifest_path,
44+
max_speakers=8,
45+
num_speakers=None,
46+
tmpdir=temp_path,
47+
batch_size=24,
48+
num_workers=0,
49+
verbose=True,
50+
)
51+
self.model.clustering_embedding.clus_diar_model._diarizer_params.out_dir = (
52+
temp_path
53+
)
54+
self.model.clustering_embedding.clus_diar_model._diarizer_params.manifest_filepath = (
55+
manifest_path
56+
)
57+
self.model.msdd_model.cfg.test_ds.manifest_filepath = manifest_path
58+
self.model.diarize()
59+
60+
pred_labels_clus = rttm_to_labels(
61+
os.path.join(temp_path, "pred_rttms", "mono_file.rttm")
62+
)
63+
64+
labels = []
65+
for label in pred_labels_clus:
66+
start, end, speaker = label.split()
67+
start, end = float(start), float(end)
68+
start, end = int(start * 1000), int(end * 1000)
69+
labels.append((start, end, int(speaker.split("_")[1])))
70+
71+
labels = sorted(labels, key=lambda x: x[0])
72+
73+
return labels
74+
75+
76+
def create_config():
77+
config = OmegaConf.load(
78+
os.path.join(os.path.dirname(__file__), "diar_infer_telephonic.yaml")
79+
)
80+
pretrained_vad = "vad_multilingual_marblenet"
81+
pretrained_speaker_model = "titanet_large"
82+
83+
config.diarizer.out_dir = None
84+
config.diarizer.manifest_filepath = None
85+
config.diarizer.speaker_embeddings.model_path = pretrained_speaker_model
86+
config.diarizer.oracle_vad = (
87+
False # compute VAD provided with model_path to vad config
88+
)
89+
config.diarizer.clustering.parameters.oracle_num_speakers = False
90+
91+
# Here, we use our in-house pretrained NeMo VAD model
92+
config.diarizer.vad.model_path = pretrained_vad
93+
config.diarizer.vad.parameters.onset = 0.8
94+
config.diarizer.vad.parameters.offset = 0.6
95+
config.diarizer.vad.parameters.pad_offset = -0.05
96+
config.diarizer.msdd_model.model_path = (
97+
"diar_msdd_telephonic" # Telephonic speaker diarization model
98+
)
99+
100+
return config

diarize.py

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import faster_whisper
77
import torch
8-
import torchaudio
98

109
from ctc_forced_aligner import (
1110
generate_emissions,
@@ -16,11 +15,9 @@
1615
preprocess_text,
1716
)
1817
from deepmultilingualpunctuation import PunctuationModel
19-
from nemo.collections.asr.models.msdd_models import NeuralDiarizer
2018

2119
from helpers import (
2220
cleanup,
23-
create_config,
2421
find_numeral_symbol_tokens,
2522
get_realigned_ws_mapping_with_punctuation,
2623
get_sentences_speaker_mapping,
@@ -37,6 +34,8 @@
3734

3835
pid = os.getpid()
3936
temp_outputs_dir = f"temp_outputs_{pid}"
37+
temp_path = os.path.join(os.getcwd(), "temp_outputs")
38+
os.makedirs(temp_path, exist_ok=True)
4039

4140
# Initialize parser
4241
parser = argparse.ArgumentParser()
@@ -92,6 +91,13 @@
9291
help="if you have a GPU use 'cuda', otherwise 'cpu'",
9392
)
9493

94+
parser.add_argument(
95+
"--diarizer",
96+
default="msdd",
97+
choices=["msdd"],
98+
help="Choose the diarization model to use",
99+
)
100+
95101
args = parser.parse_args()
96102
language = process_language_arg(args.language, args.model_name)
97103

@@ -186,38 +192,15 @@
186192

187193
word_timestamps = postprocess_results(text_starred, spans, stride, scores)
188194

195+
if args.diarizer == "msdd":
196+
from diarization import MSDDDiarizer
189197

190-
# convert audio to mono for NeMo combatibility
191-
ROOT = os.getcwd()
192-
temp_path = os.path.join(ROOT, temp_outputs_dir)
193-
os.makedirs(temp_path, exist_ok=True)
194-
torchaudio.save(
195-
os.path.join(temp_path, "mono_file.wav"),
196-
torch.from_numpy(audio_waveform).unsqueeze(0).float(),
197-
16000,
198-
channels_first=True,
199-
)
198+
diarizer_model = MSDDDiarizer(device=args.device)
200199

201-
202-
# Initialize NeMo MSDD diarization model
203-
msdd_model = NeuralDiarizer(cfg=create_config(temp_path)).to(args.device)
204-
msdd_model.diarize()
205-
206-
del msdd_model
200+
speaker_ts = diarizer_model.diarize(torch.from_numpy(audio_waveform).unsqueeze(0))
201+
del diarizer_model
207202
torch.cuda.empty_cache()
208203

209-
# Reading timestamps <> Speaker Labels mapping
210-
211-
212-
speaker_ts = []
213-
with open(os.path.join(temp_path, "pred_rttms", "mono_file.rttm"), "r") as f:
214-
lines = f.readlines()
215-
for line in lines:
216-
line_list = line.split(" ")
217-
s = int(float(line_list[5]) * 1000)
218-
e = s + int(float(line_list[8]) * 1000)
219-
speaker_ts.append([s, e, int(line_list[11].split("_")[-1])])
220-
221204
wsm = get_words_speaker_mapping(word_timestamps, speaker_ts, "start")
222205

223206
if info.language in punct_model_langs:

0 commit comments

Comments
 (0)