| | |
| | | # [2] Speaker Overlap-aware Neural Diarization for Multi-party Meeting Analysis, EMNLP 2022 |
| | | # We recommend you run this script stage by stage. |
| | | |
| | | # This recipe includes: |
| | | # 1. downloading a pretrained model on the simulated data from switchboard and NIST, |
| | | # 2. finetuning the pretrained model on Callhome1. |
| | | # Finally, you will get a slightly better DER result 9.95% on Callhome2 than that in the paper 10.14%. |
| | | |
| | | # environment configuration |
| | | kaldi_root= |
| | | |
| | | if [ -z "${kaldi_root}" ]; then |
| | | echo "We need kaldi to prepare dataset, extract fbank features, please install kaldi first and set kaldi_root." |
| | | echo "Kaldi installation guide can be found at https://kaldi-asr.org/" |
| | | exit; |
| | | fi |
| | | |
| | | if [ ! -e local ]; then |
| | | ln -s ${kaldi_root}/egs/callhome_diarization/v2/local ./local |
| | | fi |
| | | |
| | | if [ ! -e utils ]; then |
| | | ln -s ../../../aishell/transformer/utils ./utils |
| | | ln -s ${kaldi_root}/egs/callhome_diarization/v2/utils ./utils |
| | | fi |
| | | |
| | | # callhome data root like path/to/NIST/LDC2001S97 |
| | | callhome_root= |
| | | if [ -z "${kaldi_root}" ]; then |
| | | echo "We need callhome corpus to prepare data." |
| | | exit; |
| | | fi |
| | | |
| | | # machines configuration |
| | | gpu_devices="0,1,2,3" |
| | | gpu_devices="0,1,2,3" # for V100-16G, need 4 gpus. |
| | | gpu_num=4 |
| | | count=1 |
| | | |
| | | # general configuration |
| | | stage=1 |
| | | stop_stage=1 |
| | | stage=0 |
| | | stop_stage=10 |
| | | # number of jobs for data process |
| | | nj=16 |
| | | sr=8000 |
| | | |
| | | # dataset related |
| | | data_root= |
| | | |
| | | # experiment configuration |
| | | lang=en |
| | |
| | | _ngpu=0 |
| | | fi |
| | | |
| | | # Download required resources |
| | | # Prepare datasets |
| | | if [ ${stage} -le 0 ] && [ ${stop_stage} -ge 0 ]; then |
| | | echo "Stage 0: Download required resources." |
| | | wget told_finetune_resources.zip |
| | | echo "Stage 0: Prepare callhome data." |
| | | local/make_callhome.sh ${callhome_root} ${datadir}/ |
| | | |
| | | # split ref.rttm |
| | | for dset in callhome1 callhome2; do |
| | | rm -rf ${datadir}/${dset}/ref.rttm |
| | | for name in `awk '{print $1}' ${datadir}/${dset}/wav.scp`; do |
| | | grep ${name} ${datadir}/callhome/fullref.rttm >> ${datadir}/${dset}/ref.rttm; |
| | | done |
| | | |
| | | # filter out records which don't have rttm labels. |
| | | awk '{print $2}' ${datadir}/${dset}/ref.rttm | sort | uniq > ${datadir}/${dset}/uttid |
| | | mv ${datadir}/${dset}/wav.scp ${datadir}/${dset}/wav.scp.bak |
| | | awk '{if (NR==FNR){a[$1]=1}else{if (a[$1]==1){print $0}}}' ${datadir}/${dset}/uttid ${datadir}/${dset}/wav.scp.bak > ${datadir}/${dset}/wav.scp |
| | | mkdir ${datadir}/${dset}/raw |
| | | mv ${datadir}/${dset}/{reco2num_spk,segments,spk2utt,utt2spk,uttid,wav.scp.bak} ${datadir}/${dset}/raw/ |
| | | awk '{print $1,$1}' ${datadir}/${dset}/wav.scp > ${datadir}/${dset}/utt2spk |
| | | done |
| | | fi |
| | | |
| | | # Finetune model on callhome1 |
| | | if [ ${stage} -le 1 ] && [ ${stop_stage} -ge 1 ]; then |
| | | echo "Stage 1: Finetune pretrained model on callhome1." |
| | | echo "Stage 1: Dump sph file to wav" |
| | | export PATH=${kaldi_root}/tools/sph2pipe/:${PATH} |
| | | if [ ! -f ${kaldi_root}/tools/sph2pipe/sph2pipe ]; then |
| | | echo "Can not find sph2pipe in ${kaldi_root}/tools/sph2pipe/," |
| | | echo "please install sph2pipe and put it in the right place." |
| | | exit; |
| | | fi |
| | | |
| | | for dset in callhome1 callhome2; do |
| | | echo "Stage 1: start to dump ${dset}." |
| | | mv ${datadir}/${dset}/wav.scp ${datadir}/${dset}/sph.scp |
| | | |
| | | mkdir -p ${dumpdir}/${dset}/wavs |
| | | python -Wignore script/dump_pipe_wav.py ${datadir}/${dset}/sph.scp ${dumpdir}/${dset}/wavs \ |
| | | --sr ${sr} --nj ${nj} --no_pbar |
| | | find `pwd`/${dumpdir}/${dset}/wavs -iname "*.wav" | sort | awk -F'[/.]' '{print $(NF-1),$0}' > ${datadir}/${dset}/wav.scp |
| | | done |
| | | fi |
| | | |
| | | if [ ${stage} -le 2 ] && [ ${stop_stage} -ge 2 ]; then |
| | | echo "Stage 2: Extract non-overlap segments from callhome dataset" |
| | | for dset in callhome1 callhome2; do |
| | | echo "Stage 2: Extracting non-overlap segments for "${dset} |
| | | mkdir -p ${dumpdir}/${dset}/nonoverlap_0s |
| | | python -Wignore script/extract_nonoverlap_segments.py \ |
| | | ${datadir}/${dset}/wav.scp ${datadir}/${dset}/ref.rttm ${dumpdir}/${dset}/nonoverlap_0s \ |
| | | --min_dur 0.1 --max_spk_num 8 --sr ${sr} --no_pbar --nj ${nj} |
| | | |
| | | mkdir -p ${datadir}/${dset}/nonoverlap_0s |
| | | find ${dumpdir}/${dset}/nonoverlap_0s/ -iname "*.wav" | sort | awk -F'[/.]' '{print $(NF-1),$0}' > ${datadir}/${dset}/nonoverlap_0s/wav.scp |
| | | awk -F'[/.]' '{print $(NF-1),$(NF-2)}' ${datadir}/${dset}/nonoverlap_0s/wav.scp > ${datadir}/${dset}/nonoverlap_0s/utt2spk |
| | | echo "Done." |
| | | done |
| | | fi |
| | | |
| | | if [ ${stage} -le 3 ] && [ ${stop_stage} -ge 3 ]; then |
| | | echo "Stage 3: Generate fbank features" |
| | | home_path=$(pwd) |
| | | cd ${kaldi_root}/egs/callhome_diarization/v2 || exit |
| | | |
| | | export train_cmd="run.pl" |
| | | export cmd="run.pl" |
| | | . ./path.sh |
| | | cd $home_path || exit |
| | | |
| | | ln -s ${kaldi_root}/egs/callhome_diarization/v2/steps ./ |
| | | for dset in callhome1 callhome2; do |
| | | utils/fix_data_dir.sh ${datadir}/${dset} |
| | | steps/make_fbank.sh --write-utt2num-frames true --fbank-config conf/fbank.conf --nj ${nj} --cmd "$train_cmd" \ |
| | | ${datadir}/${dset} ${expdir}/make_fbank/${dset} ${dumpdir}/${dset}/fbank |
| | | done |
| | | rm -f steps |
| | | |
| | | fi |
| | | |
| | | if [ ${stage} -le 4 ] && [ ${stop_stage} -ge 4 ]; then |
| | | echo "Stage 4: Extract speaker embeddings." |
| | | sv_exp_dir=exp/speech_xvector_sv-en-us-callhome-8k-spk6135-pytorch |
| | | |
| | | if [ ! -e ${sv_exp_dir} ]; then |
| | | echo "start to download sv models" |
| | | git lfs install |
| | | git clone https://www.modelscope.cn/damo/speech_xvector_sv-en-us-callhome-8k-spk6135-pytorch.git |
| | | mv speech_xvector_sv-en-us-callhome-8k-spk6135-pytorch ${expdir}/ |
| | | echo "Done." |
| | | fi |
| | | |
| | | for dset in callhome1/nonoverlap_0s callhome2/nonoverlap_0s; do |
| | | echo "Start to extract speaker embeddings for ${dset}" |
| | | key_file=${datadir}/${dset}/wav.scp |
| | | num_scp_file="$(<${key_file} wc -l)" |
| | | _nj=$([ $inference_nj -le $num_scp_file ] && echo "$inference_nj" || echo "$num_scp_file") |
| | | _logdir=${dumpdir}/${dset}/xvecs |
| | | mkdir -p ${_logdir} |
| | | split_scps= |
| | | for n in $(seq "${_nj}"); do |
| | | split_scps+=" ${_logdir}/keys.${n}.scp" |
| | | done |
| | | # shellcheck disable=SC2086 |
| | | utils/split_scp.pl "${key_file}" ${split_scps} |
| | | |
| | | ${infer_cmd} --gpu "${_ngpu}" --max-jobs-run "${_nj}" JOB=1:"${_nj}" "${_logdir}"/sv_inference.JOB.log \ |
| | | python -m funasr.bin.sv_inference_launch \ |
| | | --batch_size 1 \ |
| | | --njob ${njob} \ |
| | | --ngpu "${_ngpu}" \ |
| | | --gpuid_list ${gpuid_list} \ |
| | | --data_path_and_name_and_type "${key_file},speech,sound" \ |
| | | --key_file "${_logdir}"/keys.JOB.scp \ |
| | | --sv_train_config ${sv_exp_dir}/sv.yaml \ |
| | | --sv_model_file ${sv_exp_dir}/sv.pth \ |
| | | --output_dir "${_logdir}"/output.JOB |
| | | cat ${_logdir}/output.*/xvector.scp | sort > ${datadir}/${dset}/utt2xvec |
| | | |
| | | python script/calc_num_frames.py ${key_file} ${datadir}/${dset}/utt2num_frames |
| | | echo "Done." |
| | | done |
| | | |
| | | fi |
| | | |
| | | if [ ${stage} -le 5 ] && [ ${stop_stage} -ge 5 ]; then |
| | | echo "Stage 5: Generate label files." |
| | | |
| | | for dset in callhome1 callhome2; do |
| | | echo "Stage 5: Generate labels for ${dset}." |
| | | python -Wignore script/calc_real_meeting_frame_labels.py \ |
| | | ${datadir}/${dset} ${dumpdir}/${dset}/labels \ |
| | | --n_spk 8 --frame_shift 0.01 --nj 16 --sr 8000 |
| | | find `pwd`/${dumpdir}/${dset}/labels/ -iname "*.lbl.mat" | awk -F'[/.]' '{print $(NF-2),$0}' | sort > ${datadir}/${dset}/labels.scp |
| | | done |
| | | |
| | | fi |
| | | |
| | | if [ ${stage} -le 6 ] && [ ${stop_stage} -ge 6 ]; then |
| | | echo "Stage 6: Make training and evaluation files." |
| | | |
| | | # dump callhome1 data in training mode. |
| | | data_dir=${datadir}/callhome1/files_for_dump |
| | | mkdir ${data_dir} |
| | | # filter out zero duration segments |
| | | LC_ALL=C awk '{if ($5 > 0){print $0}}' ${datadir}/callhome1/ref.rttm > ${data_dir}/ref.rttm |
| | | cp ${datadir}/callhome1/{feats.scp,labels.scp} ${data_dir}/ |
| | | cp ${datadir}/callhome1/nonoverlap_0s/{utt2spk,utt2xvec,utt2num_frames} ${data_dir}/ |
| | | |
| | | echo "Stage 6: start to dump for callhome1." |
| | | python -Wignore script/dump_meeting_chunks.py --dir ${data_dir} \ |
| | | --out ${dumpdir}/callhome1/dumped_files/data --n_spk 16 --no_pbar --sr 8000 --mode train \ |
| | | --chunk_size 1600 --chunk_shift 400 --add_mid_to_speaker true |
| | | |
| | | mkdir -p ${datadir}/callhome1/dumped_files |
| | | cat ${dumpdir}/callhome1/dumped_files/data_parts*_feat.scp | sort > ${datadir}/callhome1/dumped_files/feats.scp |
| | | cat ${dumpdir}/callhome1/dumped_files/data_parts*_xvec.scp | sort > ${datadir}/callhome1/dumped_files/profile.scp |
| | | cat ${dumpdir}/callhome1/dumped_files/data_parts*_label.scp | sort > ${datadir}/callhome1/dumped_files/label.scp |
| | | mkdir -p ${expdir}/callhome1_states |
| | | awk '{print $1,"1600"}' ${datadir}/callhome1/dumped_files/feats.scp | shuf > ${expdir}/callhome1_states/speech_shape |
| | | python -Wignore script/convert_rttm_to_seg_file.py --rttm_scp ${data_dir}/ref.rttm --seg_file ${data_dir}/org_vad.txt |
| | | |
| | | # dump callhome2 data in test mode. |
| | | data_dir=${datadir}/callhome2/files_for_dump |
| | | mkdir ${data_dir} |
| | | # filter out zero duration segments |
| | | LC_ALL=C awk '{if ($5 > 0){print $0}}' ${datadir}/callhome2/ref.rttm > ${data_dir}/ref.rttm |
| | | cp ${datadir}/callhome2/{feats.scp,labels.scp} ${data_dir}/ |
| | | cp ${datadir}/callhome2/nonoverlap_0s/{utt2spk,utt2xvec,utt2num_frames} ${data_dir}/ |
| | | |
| | | echo "Stage 6: start to dump for callhome2." |
| | | python -Wignore script/dump_meeting_chunks.py --dir ${data_dir} \ |
| | | --out ${dumpdir}/callhome2/dumped_files/data --n_spk 16 --no_pbar --sr 8000 --mode test \ |
| | | --chunk_size 1600 --chunk_shift 400 --add_mid_to_speaker true |
| | | |
| | | mkdir -p ${datadir}/callhome2/dumped_files |
| | | cat ${dumpdir}/callhome2/dumped_files/data_parts*_feat.scp | sort > ${datadir}/callhome2/dumped_files/feats.scp |
| | | cat ${dumpdir}/callhome2/dumped_files/data_parts*_xvec.scp | sort > ${datadir}/callhome2/dumped_files/profile.scp |
| | | cat ${dumpdir}/callhome2/dumped_files/data_parts*_label.scp | sort > ${datadir}/callhome2/dumped_files/label.scp |
| | | mkdir -p ${expdir}/callhome2_states |
| | | awk '{print $1,"1600"}' ${datadir}/callhome2/dumped_files/feats.scp | shuf > ${expdir}/callhome2_states/speech_shape |
| | | python -Wignore script/convert_rttm_to_seg_file.py --rttm_scp ${data_dir}/ref.rttm --seg_file ${data_dir}/org_vad.txt |
| | | |
| | | fi |
| | | |
| | | # Finetune model on callhome1, this will take about 1.5 hours. |
| | | if [ ${stage} -le 7 ] && [ ${stop_stage} -ge 7 ]; then |
| | | echo "Stage 7: Finetune pretrained model on callhome1." |
| | | |
| | | if [ ! -e ${expdir}/speech_diarization_sond-en-us-swbd_sre-8k-n16k4-pytorch ]; then |
| | | echo "start to download pretrained models" |
| | | git lfs install |
| | | git clone https://www.modelscope.cn/damo/speech_diarization_sond-en-us-swbd_sre-8k-n16k4-pytorch.git |
| | | mv speech_diarization_sond-en-us-swbd_sre-8k-n16k4-pytorch ${expdir}/ |
| | | echo "Done." |
| | | fi |
| | | |
| | | world_size=$gpu_num # run on one machine |
| | | mkdir -p ${expdir}/${model_dir} |
| | | mkdir -p ${expdir}/${model_dir}/log |
| | |
| | | --valid_data_path_and_name_and_type ${datadir}/${valid_set}/dumped_files/profile.scp,profile,kaldi_ark \ |
| | | --valid_data_path_and_name_and_type ${datadir}/${valid_set}/dumped_files/label.scp,binary_labels,kaldi_ark \ |
| | | --valid_shape_file ${expdir}/${valid_set}_states/speech_shape \ |
| | | --init_param exp/pretrained_models/phase2.pth \ |
| | | --init_param ${expdir}/speech_diarization_sond-en-us-swbd_sre-8k-n16k4-pytorch/sond.pth \ |
| | | --unused_parameters true \ |
| | | ${init_opt} \ |
| | | ${freeze_opt} \ |
| | |
| | | |
| | | |
| | | # evaluate for finetuned model |
| | | if [ ${stage} -le 2 ] && [ ${stop_stage} -ge 2 ]; then |
| | | echo "stage 2: evaluation for finetuned model ${inference_model}." |
| | | if [ ${stage} -le 8 ] && [ ${stop_stage} -ge 8 ]; then |
| | | echo "stage 8: evaluation for finetuned model ${inference_model}." |
| | | for dset in ${test_sets}; do |
| | | echo "Processing for $dset" |
| | | exp_model_dir=${expdir}/${model_dir} |
| | |
| | | |
| | | # Scoring for finetuned model, you may get a DER like: |
| | | # oracle_vad | system_vad |
| | | # 7.28 | 8.06 |
| | | if [ ${stage} -le 3 ] && [ ${stop_stage} -ge 3 ]; then |
| | | echo "stage 3: Scoring finetuned models" |
| | | # 7.32 | 8.14 |
| | | if [ ${stage} -le 9 ] && [ ${stop_stage} -ge 9 ]; then |
| | | echo "stage 9: Scoring finetuned models" |
| | | if [ ! -e dscore ]; then |
| | | git clone https://github.com/nryant/dscore.git |
| | | pip install intervaltree |
| | | # add intervaltree to setup.py |
| | | fi |
| | | for dset in ${test_sets}; do |
| | | echo "stage 3: Scoring for ${dset}" |
| | | echo "stage 9: Scoring for ${dset}" |
| | | diar_exp=${expdir}/${model_dir} |
| | | _data="${datadir}/${dset}" |
| | | _inference_tag="$(basename "${inference_config}" .yaml)${inference_tag}" |
| | |
| | | # Then find the wav files to construct wav.scp and put it at data/callhome2/wav.scp. |
| | | # After iteratively perform SOAP, you will get DER results like: |
| | | # iters : oracle_vad | system_vad |
| | | # iter_0: 9.68 | 10.51 |
| | | # iter_1: 9.26 | 10.14 (reported in the paper) |
| | | # iter_2: 9.18 | 10.08 |
| | | # iter_3: 9.24 | 10.15 |
| | | # iter_4: 9.27 | 10.17 |
| | | if [ ${stage} -le 4 ] && [ ${stop_stage} -ge 4 ]; then |
| | | # iter_0: 9.58 | 10.46 |
| | | # iter_1: 9.22 | 10.15 |
| | | # iter_2: 9.21 | 10.14 |
| | | # iter_3: 9.30 | 10.24 |
| | | # iter_4: 9.29 | 10.23 |
| | | if [ ${stage} -le 10 ] && [ ${stop_stage} -ge 10 ]; then |
| | | if [ ! -e ${expdir}/speech_xvector_sv-en-us-callhome-8k-spk6135-pytorch ]; then |
| | | git lfs install |
| | | git clone https://www.modelscope.cn/damo/speech_xvector_sv-en-us-callhome-8k-spk6135-pytorch.git |
| | |
| | | fi |
| | | |
| | | for dset in ${test_sets}; do |
| | | echo "stage 4: Evaluating finetuned system on ${dset} set with medfilter_size=83 clustering=EEND-OLA" |
| | | echo "stage 10: Evaluating finetuned system on ${dset} set with medfilter_size=83 clustering=EEND-OLA" |
| | | sv_exp_dir=${expdir}/speech_xvector_sv-en-us-callhome-8k-spk6135-pytorch |
| | | diar_exp=${expdir}/${model_dir} |
| | | _data="${datadir}/${dset}/dumped_files" |