[src,scripts,egs] Xvectors: DNN Embeddings for Speaker Recognition (#1896)
authordavid-ryan-snyder <david.ryan.snyder@gmail.com>
Tue, 3 Oct 2017 22:00:45 +0000 (18:00 -0400)
committerDaniel Povey <dpovey@gmail.com>
Tue, 3 Oct 2017 22:00:45 +0000 (18:00 -0400)
52 files changed:
egs/lre07/v1/run.sh
egs/sre08/v1/sid/nnet3/xvector/allocate_egs.py [new file with mode: 0755]
egs/sre08/v1/sid/nnet3/xvector/extract_xvectors.sh [new file with mode: 0755]
egs/sre08/v1/sid/nnet3/xvector/get_egs.sh [new file with mode: 0755]
egs/sre16/README.txt [new file with mode: 0644]
egs/sre16/v1/README.txt [new file with mode: 0644]
egs/sre16/v1/cmd.sh [new file with mode: 0755]
egs/sre16/v1/conf/mfcc.conf [new file with mode: 0644]
egs/sre16/v1/conf/vad.conf [new file with mode: 0644]
egs/sre16/v1/local/make_musan.py [new file with mode: 0755]
egs/sre16/v1/local/make_musan.sh [new file with mode: 0755]
egs/sre16/v1/local/make_mx6.sh [new file with mode: 0755]
egs/sre16/v1/local/make_mx6_calls.pl [new file with mode: 0755]
egs/sre16/v1/local/make_mx6_mic.pl [new file with mode: 0755]
egs/sre16/v1/local/make_sre.pl [new file with mode: 0755]
egs/sre16/v1/local/make_sre.sh [new file with mode: 0755]
egs/sre16/v1/local/make_sre08.pl [new file with mode: 0755]
egs/sre16/v1/local/make_sre10.pl [new file with mode: 0755]
egs/sre16/v1/local/make_sre16_eval.pl [new file with mode: 0755]
egs/sre16/v1/local/make_sre16_unlabeled.pl [new file with mode: 0755]
egs/sre16/v1/local/make_swbd2_phase1.pl [new file with mode: 0755]
egs/sre16/v1/local/make_swbd2_phase2.pl [new file with mode: 0755]
egs/sre16/v1/local/make_swbd2_phase3.pl [new file with mode: 0755]
egs/sre16/v1/local/make_swbd_cellular1.pl [new file with mode: 0755]
egs/sre16/v1/local/make_swbd_cellular2.pl [new file with mode: 0755]
egs/sre16/v1/local/nnet3/xvector/prepare_feats_for_egs.sh [new file with mode: 0755]
egs/sre16/v1/local/nnet3/xvector/run_xvector.sh [new symlink]
egs/sre16/v1/local/nnet3/xvector/tuning/run_xvector_1a.sh [new file with mode: 0755]
egs/sre16/v1/path.sh [new file with mode: 0755]
egs/sre16/v1/run.sh [new file with mode: 0755]
egs/sre16/v1/sid [new symlink]
egs/sre16/v1/steps [new symlink]
egs/sre16/v1/utils [new symlink]
egs/sre16/v2/README.txt [new file with mode: 0644]
egs/sre16/v2/cmd.sh [new file with mode: 0755]
egs/sre16/v2/conf/mfcc.conf [new file with mode: 0644]
egs/sre16/v2/conf/vad.conf [new file with mode: 0644]
egs/sre16/v2/local [new symlink]
egs/sre16/v2/path.sh [new file with mode: 0755]
egs/sre16/v2/run.sh [new file with mode: 0755]
egs/sre16/v2/sid [new symlink]
egs/sre16/v2/steps [new symlink]
egs/sre16/v2/utils [new symlink]
egs/wsj/s5/steps/data/augment_data_dir.py [new file with mode: 0755]
egs/wsj/s5/steps/data/reverberate_data_dir.py
egs/wsj/s5/steps/libs/nnet3/xconfig/stats_layer.py
egs/wsj/s5/utils/combine_data.sh
egs/wsj/s5/utils/copy_data_dir.sh
src/makefiles/default_rules.mk
src/nnet3bin/Makefile
src/nnet3bin/nnet3-xvector-compute.cc [new file with mode: 0644]
src/nnet3bin/nnet3-xvector-get-egs.cc [new file with mode: 0644]

index 8664494e55836673cde406e625bd96ea775a1038..87f518e6357dda894fbf921e20f0306dfa6224ac 100755 (executable)
@@ -13,8 +13,8 @@ set -e
 mfccdir=`pwd`/mfcc
 vaddir=`pwd`/mfcc
 languages=local/general_lr_closed_set_langs.txt
-
 data_root=/export/corpora/LDC
+
 # Training data sources
 local/make_sre_2008_train.pl $data_root/LDC2011S05 data
 local/make_callfriend.pl $data_root/LDC96S60 vietnamese data
diff --git a/egs/sre08/v1/sid/nnet3/xvector/allocate_egs.py b/egs/sre08/v1/sid/nnet3/xvector/allocate_egs.py
new file mode 100755 (executable)
index 0000000..72a4572
--- /dev/null
@@ -0,0 +1,325 @@
+#!/usr/bin/env python3
+
+# Copyright      2017 Johns Hopkins University (Author: Daniel Povey)
+#                2017 Johns Hopkins University (Author: Daniel Garcia-Romero)
+#                2017 David Snyder
+# Apache 2.0
+
+# This script, which is used in getting training examples, decides
+# which examples will come from which recordings, and at what point
+# during the training.
+
+# You call it as (e.g.)
+#
+#  allocate_egs.py --min-frames-per-chunk=50 --max-frames-per-chunk=200 \
+#   --frames-per-iter=1000000 --num-repeats=60 --num-archives=169 \
+#   --num-jobs=24  exp/xvector_a/egs/temp/utt2len.train exp/xvector_a/egs
+#
+# The program outputs certain things to the temp directory (e.g.,
+# exp/xvector_a/egs/temp) that will enable you to dump the chunks for xvector
+# training.  What we'll eventually be doing is invoking the following program
+# with something like the following args:
+#
+#  nnet3-xvector-get-egs [options] exp/xvector_a/temp/ranges.1 \
+#    scp:data/train/feats.scp ark:exp/xvector_a/egs/egs_temp.1.ark \
+#    ark:exp/xvector_a/egs/egs_temp.2.ark ark:exp/xvector_a/egs/egs_temp.3.ark
+#
+# where exp/xvector_a/temp/ranges.1 contains something like the following:
+#
+#   utt1  0  1  0   65 0
+#   utt1  6  7  160 50 0
+#   utt2  ...
+#
+# where each line is interpreted as follows:
+#  <source-utterance> <relative-archive-index> <absolute-archive-index> \
+#    <start-frame-index> <num-frames> <spkr-label>
+#
+# Note: <relative-archive-index> is the zero-based offset of the archive-index
+# within the subset of archives that a particular ranges file corresponds to;
+# and <absolute-archive-index> is the 1-based numeric index of the destination
+# archive among the entire list of archives, which will form part of the
+# archive's filename (e.g. egs/egs.<absolute-archive-index>.ark);
+# <absolute-archive-index> is only kept for debug purposes so you can see which
+# archive each line corresponds to.
+#
+# For each line of the ranges file, we specify an eg containing a chunk of data
+# from a given utterane, the corresponding speaker label, and the output
+# archive.  The list of archives corresponding to ranges.n will be written to
+# output.n, so in exp/xvector_a/temp/outputs.1 we'd have:
+#
+#  ark:exp/xvector_a/egs/egs_temp.1.ark ark:exp/xvector_a/egs/egs_temp.2.ark \
+#    ark:exp/xvector_a/egs/egs_temp.3.ark
+#
+# The number of these files will equal 'num-jobs'.  If you add up the
+# word-counts of all the outputs.* files you'll get 'num-archives'.  The number
+# of frames in each archive will be about the --frames-per-iter.
+#
+# This program will also output to the temp directory a file called
+# archive_chunk_length which tells you the frame-length associated with
+# each archive, e.g.,
+# 1   60
+# 2   120
+# the format is:  <archive-index> <num-frames>.  The <num-frames> will always
+# be in the range [min-frames-per-chunk, max-frames-per-chunk].
+
+
+# We're using python 3.x style print but want it to work in python 2.x.
+from __future__ import print_function
+import re, os, argparse, sys, math, warnings, random
+
+def get_args():
+    parser = argparse.ArgumentParser(description="Writes ranges.*, outputs.* and archive_chunk_lengths files "
+                                 "in preparation for dumping egs for xvector training.",
+                                 epilog="Called by sid/nnet3/xvector/get_egs.sh")
+    parser.add_argument("--prefix", type=str, default="",
+                   help="Adds a prefix to the output files. This is used to distinguish between the train "
+                   "and diagnostic files.")
+    parser.add_argument("--num-repeats", type=int, default=10, help="Number of times each speaker repeats within an archive.")
+    parser.add_argument("--min-frames-per-chunk", type=int, default=50,
+                    help="Minimum number of frames-per-chunk used for any archive")
+    parser.add_argument("--max-frames-per-chunk", type=int, default=300,
+                    help="Maximum number of frames-per-chunk used for any archive")
+    parser.add_argument("--randomize-chunk-length", type=str,
+                    help="If true, randomly pick a chunk length in [min-frames-per-chunk, max-frames-per-chunk]."
+                    "If false, the chunk length varies from min-frames-per-chunk to max-frames-per-chunk"
+                    "according to a geometric sequence.",
+                    default="true", choices = ["false", "true"])
+    parser.add_argument("--frames-per-iter", type=int, default=1000000,
+                    help="Target number of frames for each archive")
+    parser.add_argument("--num-archives", type=int, default=-1,
+                    help="Number of archives to write");
+    parser.add_argument("--num-jobs", type=int, default=-1,
+                    help="Number of jobs we're going to use to write the archives; the ranges.* "
+                    "and outputs.* files are indexed by job.  Must be <= the --num-archives option.");
+    parser.add_argument("--seed", type=int, default=123,
+                    help="Seed for random number generator")
+    parser.add_argument("--num-pdfs", type=int, default=-1,
+                    help="Num pdfs")
+
+    # now the positional arguments
+    parser.add_argument("--utt2len-filename", type=str, required=True,
+                    help="utt2len file of the features to be used as input (format is: "
+                    "<utterance-id> <num-frames>)");
+    parser.add_argument("--utt2int-filename", type=str, required=True,
+                    help="utt2int file of the features to be used as input (format is: "
+                    "<utterance-id> <id>)");
+    parser.add_argument("--egs-dir", type=str, required=True,
+                    help="Name of egs directory, e.g. exp/xvector_a/egs");
+
+    print(' '.join(sys.argv), file=sys.stderr)
+    print(sys.argv, file=sys.stderr)
+    args = parser.parse_args()
+    args = process_args(args)
+    return args
+
+def process_args(args):
+    if args.num_repeats < 1:
+        raise Exception("--num-repeats should have a minimum value of 1")
+    if not os.path.exists(args.utt2int_filename):
+        raise Exception("This script expects --utt2int-filename to exist")
+    if not os.path.exists(args.utt2len_filename):
+        raise Exception("This script expects --utt2len-filename to exist")
+    if args.min_frames_per_chunk <= 1:
+        raise Exception("--min-frames-per-chunk is invalid.")
+    if args.max_frames_per_chunk < args.min_frames_per_chunk:
+        raise Exception("--max-frames-per-chunk is invalid.")
+    if args.frames_per_iter < 1000:
+        raise Exception("--frames-per-iter is invalid.")
+    if args.num_archives < 1:
+        raise Exception("--num-archives is invalid")
+    if args.num_jobs > args.num_archives:
+        raise Exception("--num-jobs is invalid (must not exceed num-archives)")
+    return args
+
+# Create utt2len
+def get_utt2len(utt2len_filename):
+    utt2len = {}
+    f = open(utt2len_filename, "r")
+    if f is None:
+        sys.exit("Error opening utt2len file " + str(utt2len_filename))
+    utt_ids = []
+    lengths = []
+    for line in f:
+        tokens = line.split()
+        if len(tokens) != 2:
+            sys.exit("bad line in utt2len file " + line)
+        utt2len[tokens[0]] = int(tokens[1])
+    f.close()
+    return utt2len
+    # Done utt2len
+
+# Handle utt2int, create spk2utt, spks
+def get_labels(utt2int_filename):
+    f = open(utt2int_filename, "r")
+    if f is None:
+        sys.exit("Error opening utt2int file " + str(utt2int_filename))
+    spk2utt = {}
+    utt2spk = {}
+    for line in f:
+        tokens = line.split()
+        if len(tokens) != 2:
+            sys.exit("bad line in utt2int file " + line)
+        spk = int(tokens[1])
+        utt = tokens[0]
+        utt2spk[utt] = spk
+        if spk not in spk2utt:
+            spk2utt[spk] = [utt]
+        else:
+            spk2utt[spk].append(utt)
+    spks = spk2utt.keys()
+    f.close()
+    return spks, spk2utt, utt2spk
+    # Done utt2int
+
+
+# this function returns a random integer utterance index, limited to utterances
+# above a minimum length in frames, with probability proportional to its length.
+def get_random_utt(spkr, spk2utt, min_length):
+    this_utts = spk2utt[spkr]
+    this_num_utts = len(this_utts)
+    i = random.randint(0, this_num_utts-1)
+    utt = this_utts[i]
+    return utt
+
+def random_chunk_length(min_frames_per_chunk, max_frames_per_chunk):
+    ans = random.randint(min_frames_per_chunk, max_frames_per_chunk)
+    return ans
+
+# This function returns an integer in the range
+# [min-frames-per-chunk, max-frames-per-chunk] according to a geometric
+# sequence. For example, suppose min-frames-per-chunk is 50,
+# max-frames-per-chunk is 200, and args.num_archives is 3. Then the
+# lengths for archives 0, 1, and 2 will be 50, 100, and 200.
+def deterministic_chunk_length(archive_id, num_archives, min_frames_per_chunk, max_frames_per_chunk):
+  if max_frames_per_chunk == min_frames_per_chunk:
+    return max_frames_per_chunk
+  elif num_archives == 1:
+    return int(max_frames_per_chunk);
+  else:
+    return int(math.pow(float(max_frames_per_chunk) /
+                     min_frames_per_chunk, float(archive_id) /
+                     (num_archives-1)) * min_frames_per_chunk + 0.5)
+
+
+
+# given an utterance length utt_length (in frames) and two desired chunk lengths
+# (length1 and length2) whose sum is <= utt_length,
+# this function randomly picks the starting points of the chunks for you.
+# the chunks may appear randomly in either order.
+def get_random_offset(utt_length, length):
+    if length > utt_length:
+        sys.exit("code error: length > utt-length")
+    free_length = utt_length - length
+
+    offset = random.randint(0, free_length)
+    return offset
+
+
+def main():
+    args = get_args()
+    if not os.path.exists(args.egs_dir + "/temp"):
+        os.makedirs(args.egs_dir + "/temp")
+    random.seed(args.seed)
+    utt2len = get_utt2len(args.utt2len_filename)
+    spks, spk2utt, utt2spk = get_labels(args.utt2int_filename)
+    if args.num_pdfs == -1:
+        args.num_pdfs = max(spks) + 1
+
+    # archive_chunk_lengths is an mapping from archive id to the number of
+    # frames in examples of that archive.
+    archive_chunk_lengths = []
+    # all_egs contains 2-tuples of the form (utt-id, offset)
+    all_egs= []
+
+    prefix = ""
+    if args.prefix != "":
+        prefix = args.prefix + "_"
+
+    info_f = open(args.egs_dir + "/temp/" + prefix + "archive_chunk_lengths", "w")
+    if info_f is None:
+        sys.exit(str("Error opening file {0}/temp/" + prefix + "archive_chunk_lengths").format(args.egs_dir));
+    for archive_index in range(args.num_archives):
+        print("Processing archive {0}".format(archive_index + 1))
+        if args.randomize_chunk_length == "true":
+            # don't constrain the lengths to be the same
+            length = random_chunk_length(args.min_frames_per_chunk, args.max_frames_per_chunk)
+        else:
+            length = deterministic_chunk_length(archive_index, args.num_archives, args.min_frames_per_chunk, args.max_frames_per_chunk);
+        print("{0} {1}".format(archive_index + 1, length), file=info_f)
+        archive_chunk_lengths.append(length)
+        this_num_egs = int((args.frames_per_iter / length) + 1)
+        this_egs = [ ] # A 2-tuple of the form (utt-id, start-frame)
+        spkrs = args.num_repeats * list(spk2utt.keys())
+        random.shuffle(spkrs)
+        for n in range(this_num_egs):
+            if len(spkrs) == 0:
+                print("Ran out of speakers for archive {0}".format(archive_index + 1))
+                break
+            spkr = spkrs.pop()
+            utt = get_random_utt(spkr, spk2utt, length)
+            utt_len = utt2len[utt]
+            offset = get_random_offset(utt_len, length)
+            this_egs.append( (utt, offset) )
+        all_egs.append(this_egs)
+    info_f.close()
+
+    # work out how many archives we assign to each job in an equitable way.
+    num_archives_per_job = [ 0 ] * args.num_jobs
+    for i in range(0, args.num_archives):
+        num_archives_per_job[i % args.num_jobs]  = num_archives_per_job[i % args.num_jobs] + 1
+
+    pdf2num = {}
+    cur_archive = 0
+    for job in range(args.num_jobs):
+        this_ranges = []
+        this_archives_for_job = []
+        this_num_archives = num_archives_per_job[job]
+
+        for i in range(0, this_num_archives):
+            this_archives_for_job.append(cur_archive)
+            for (utterance_index, offset) in all_egs[cur_archive]:
+                this_ranges.append( (utterance_index, i, offset) )
+            cur_archive = cur_archive + 1
+
+        f = open(args.egs_dir + "/temp/" + prefix + "ranges." + str(job + 1), "w")
+        if f is None:
+            sys.exit("Error opening file " + args.egs_dir + "/temp/" + prefix + "ranges." + str(job + 1))
+        for (utterance_index, i, offset) in sorted(this_ranges):
+            archive_index = this_archives_for_job[i]
+            print("{0} {1} {2} {3} {4} {5}".format(utterance_index,
+                                           i,
+                                           archive_index + 1,
+                                           offset,
+                                           archive_chunk_lengths[archive_index],
+                                           utt2spk[utterance_index]),
+              file=f)
+            if utt2spk[utterance_index] in pdf2num:
+                 pdf2num[utt2spk[utterance_index]] += 1
+            else:
+                pdf2num[utt2spk[utterance_index]] = 1
+        f.close()
+
+
+        f = open(args.egs_dir + "/temp/" + prefix + "outputs." + str(job + 1), "w")
+        if f is None:
+            sys.exit("Error opening file " + args.egs_dir + "/temp/" + prefix + "outputs." + str(job + 1))
+        print( " ".join([ str("{0}/" + prefix + "egs_temp.{1}.ark").format(args.egs_dir, n + 1) for n in this_archives_for_job ]),
+           file=f)
+        f.close()
+
+    f = open(args.egs_dir + "/" + prefix + "pdf2num", "w")
+    nums = []
+    for k in range(0, args.num_pdfs):
+        if k in pdf2num:
+          nums.append(pdf2num[k])
+        else:
+          nums.append(0)
+
+    print(" ".join(map(str, nums)), file=f)
+    f.close()
+
+    print("allocate_egs.py: finished generating " + prefix + "ranges.* and " + prefix + "outputs.* files")
+
+if __name__ == "__main__":
+    main()
+
diff --git a/egs/sre08/v1/sid/nnet3/xvector/extract_xvectors.sh b/egs/sre08/v1/sid/nnet3/xvector/extract_xvectors.sh
new file mode 100755 (executable)
index 0000000..5b8a32b
--- /dev/null
@@ -0,0 +1,102 @@
+#!/bin/bash
+
+# Copyright     2017  David Snyder
+#               2017  Johns Hopkins University (Author: Daniel Povey)
+#               2017  Johns Hopkins University (Author: Daniel Garcia Romero)
+# Apache 2.0.
+
+# This script extracts embeddings (called "xvectors" here) from a set of
+# utterances, given features and a trained DNN.  The purpose of this script
+# is analogous to sid/extract_ivectors.sh: it creates archives of
+# vectors that are used in speaker recognition.  Like ivectors, xvectors can
+# be used in PLDA or a similar backend for scoring.
+
+# Begin configuration section.
+nj=30
+cmd="run.pl"
+chunk_size=-1 # The chunk size over which the embedding is extracted.
+              # If left unspecified, it uses the max_chunk_size in the nnet
+              # directory.
+use_gpu=false
+stage=0
+
+echo "$0 $@"  # Print the command line for logging
+
+if [ -f path.sh ]; then . ./path.sh; fi
+. parse_options.sh || exit 1;
+
+if [ $# != 3 ]; then
+  echo "Usage: $0 <nnet-dir> <data> <xvector-dir>"
+  echo " e.g.: $0 exp/xvector_nnet data/train exp/xvectors_train"
+  echo "main options (for others, see top of script file)"
+  echo "  --config <config-file>                           # config containing options"
+  echo "  --cmd (utils/run.pl|utils/queue.pl <queue opts>) # how to run jobs."
+  echo "  --use-gpu <bool|false>                           # If true, use GPU."
+  echo "  --nj <n|30>                                      # Number of jobs"
+  echo "  --stage <stage|0>                                # To control partial reruns"
+  echo "  --chunk-size <n|-1>                              # If provided, extracts embeddings with specified"
+  echo "                                                   # chunk size, and averages to produce final embedding"
+fi
+
+srcdir=$1
+data=$2
+dir=$3
+
+for f in $srcdir/final.raw $srcdir/min_chunk_size $srcdir/max_chunk_size $data/feats.scp $data/vad.scp ; do
+  [ ! -f $f ] && echo "No such file $f" && exit 1;
+done
+
+min_chunk_size=`cat $srcdir/min_chunk_size 2>/dev/null`
+max_chunk_size=`cat $srcdir/max_chunk_size 2>/dev/null`
+
+nnet=$srcdir/final.raw
+if [ -f $srcdir/extract.config ] ; then
+  echo "$0: using $srcdir/extract.config to extract xvectors"
+  nnet="nnet3-copy --nnet-config=$srcdir/extract.config $srcdir/final.raw - |"
+fi
+
+if [ $chunk_size -le 0 ]; then
+  chunk_size=$max_chunk_size
+fi
+
+if [ $max_chunk_size -lt $chunk_size ]; then
+  echo "$0: specified chunk size of $chunk_size is larger than the maximum chunk size, $max_chunk_size" && exit 1;
+fi
+
+mkdir -p $dir/log
+
+utils/split_data.sh $data $nj
+echo "$0: extracting xvectors for $data"
+sdata=$data/split$nj/JOB
+
+# Set up the features
+feat="ark:apply-cmvn-sliding --norm-vars=false --center=true --cmn-window=300 scp:${sdata}/feats.scp ark:- | select-voiced-frames ark:- scp,s,cs:${sdata}/vad.scp ark:- |"
+
+if [ $stage -le 0 ]; then
+  echo "$0: extracting xvectors from nnet"
+  if $use_gpu; then
+    for g in $(seq $nj); do
+      $cmd --gpu 1 ${dir}/log/extract.$g.log \
+        nnet3-xvector-compute --use-gpu=yes --min-chunk-size=$min_chunk_size --chunk-size=$chunk_size \
+        "$nnet" "`echo $feat | sed s/JOB/$g/g`" ark,scp:${dir}/xvector.$g.ark,${dir}/xvector.$g.scp || exit 1 &
+    done
+    wait
+  else
+    $cmd JOB=1:$nj ${dir}/log/extract.JOB.log \
+      nnet3-xvector-compute --use-gpu=no --min-chunk-size=$min_chunk_size --chunk-size=$chunk_size \
+      "$nnet" "$feat" ark,scp:${dir}/xvector.JOB.ark,${dir}/xvector.JOB.scp || exit 1;
+  fi
+fi
+
+if [ $stage -le 1 ]; then
+  echo "$0: combining xvectors across jobs"
+  for j in $(seq $nj); do cat $dir/xvector.$j.scp; done >$dir/xvector.scp || exit 1;
+fi
+
+if [ $stage -le 2 ]; then
+  # Average the utterance-level xvectors to get speaker-level xvectors.
+  echo "$0: computing mean of xvectors for each speaker"
+  $cmd $dir/log/speaker_mean.log \
+    ivector-mean ark:$data/spk2utt scp:$dir/xvector.scp \
+    ark,scp:$dir/spk_xvector.ark,$dir/spk_xvector.scp ark,t:$dir/num_utts.ark || exit 1;
+fi
diff --git a/egs/sre08/v1/sid/nnet3/xvector/get_egs.sh b/egs/sre08/v1/sid/nnet3/xvector/get_egs.sh
new file mode 100755 (executable)
index 0000000..3f2200c
--- /dev/null
@@ -0,0 +1,247 @@
+#!/bin/bash
+
+# Copyright      2017 Johns Hopkins University (Author: Daniel Povey)
+#                2017 Johns Hopkins University (Author: Daniel Garcia-Romero)
+#                2017 David Snyder
+# Apache 2.0
+#
+# This script dumps training examples (egs) for multiclass xvector training.
+# These egs consist of a data chunk and a zero-based speaker label.
+# Each archive of egs has, in general, a different input chunk-size.
+# We don't mix together different lengths in the same archive, because it
+# would require us to repeatedly run the compilation process within the same
+# training job.
+#
+# This script, which will generally be called from other neural net training
+# scripts, extracts the training examples used to train the neural net (and
+# also the validation examples used for diagnostics), and puts them in
+# separate archives.
+
+
+# Begin configuration section.
+cmd=run.pl
+# each archive has data-chunks off length randomly chosen between
+# $min_frames_per_eg and $max_frames_per_eg.
+min_frames_per_chunk=50
+max_frames_per_chunk=300
+frames_per_iter=10000000 # target number of frames per archive.
+
+frames_per_iter_diagnostic=100000 # have this many frames per archive for
+                                   # the archives used for diagnostics.
+
+num_diagnostic_archives=3  # we want to test the training likelihoods
+                           # on a range of utterance lengths, and this number controls
+                           # how many archives we evaluate on.
+
+
+compress=true   # set this to false to disable compression (e.g. if you want to see whether
+                # results are affected).
+
+num_heldout_utts=100     # number of utterances held out for training subset
+
+num_repeats=1 # number of times each speaker repeats per archive
+
+stage=0
+nj=6         # This should be set to the maximum number of jobs you are
+             # comfortable to run in parallel; you can increase it if your disk
+             # speed is greater and you have more machines.
+
+echo "$0 $@"  # Print the command line for logging
+
+if [ -f path.sh ]; then . ./path.sh; fi
+. parse_options.sh || exit 1;
+
+if [ $# != 2 ]; then
+  echo "Usage: $0 [opts] <data> <egs-dir>"
+  echo " e.g.: $0 data/train exp/xvector_a/egs"
+  echo ""
+  echo "Main options (for others, see top of script file)"
+  echo "  --config <config-file>                           # config file containing options"
+  echo "  --nj <nj>                                        # The maximum number of jobs you want to run in"
+  echo "                                                   # parallel (increase this only if you have good disk and"
+  echo "                                                   # network speed).  default=6"
+  echo "  --cmd (utils/run.pl;utils/queue.pl <queue opts>) # how to run jobs."
+  echo "  --min-frames-per-eg <#frames;50>                 # The minimum number of frames per chunk that we dump"
+  echo "  --max-frames-per-eg <#frames;200>                # The maximum number of frames per chunk that we dump"
+  echo "  --num-repeats <#repeats;1>                       # The (approximate) number of times the training"
+  echo "                                                   # data is repeated in the egs"
+  echo "  --frames-per-iter <#samples;1000000>             # Target number of frames per archive"
+  echo "  --num-diagnostic-archives <#archives;3>          # Option that controls how many different versions of"
+  echo "                                                   # the train and validation archives we create (e.g."
+  echo "                                                   # train_subset.{1,2,3}.egs and valid.{1,2,3}.egs by default;"
+  echo "                                                   # they contain different utterance lengths."
+  echo "  --frames-per-iter-diagnostic <#samples;100000>   # Target number of frames for the diagnostic archives"
+  echo "                                                   # {train_subset,valid}.*.egs"
+  echo "  --stage <stage|0>                                # Used to run a partially-completed training process from somewhere in"
+  echo "                                                   # the middle."
+
+  exit 1;
+fi
+
+data=$1
+dir=$2
+
+for f in $data/utt2num_frames $data/feats.scp ; do
+  [ ! -f $f ] && echo "$0: expected file $f" && exit 1;
+done
+
+feat_dim=$(feat-to-dim scp:$data/feats.scp -) || exit 1
+
+mkdir -p $dir/info $dir/info $dir/temp
+temp=$dir/temp
+
+echo $feat_dim > $dir/info/feat_dim
+echo '0' > $dir/info/left_context
+# The examples have at least min_frames_per_chunk right context.
+echo $min_frames_per_chunk > $dir/info/right_context
+echo '1' > $dir/info/frames_per_eg
+cp $data/utt2num_frames $dir/temp/utt2num_frames
+
+if [ $stage -le 0 ]; then
+  echo "$0: Preparing train and validation lists"
+  # Pick a list of heldout utterances for validation egs
+  awk '{print $1}' $data/utt2spk | utils/shuffle_list.pl | head -$num_heldout_utts > $temp/valid_uttlist || exit 1;
+  # The remaining utterances are used for training egs
+  utils/filter_scp.pl --exclude $temp/valid_uttlist $temp/utt2num_frames > $temp/utt2num_frames.train
+  utils/filter_scp.pl $temp/valid_uttlist $temp/utt2num_frames > $temp/utt2num_frames.valid
+  # Pick a subset of the training list for diagnostics
+  awk '{print $1}' $temp/utt2num_frames.train | utils/shuffle_list.pl | head -$num_heldout_utts > $temp/train_subset_uttlist || exit 1;
+  utils/filter_scp.pl $temp/train_subset_uttlist <$temp/utt2num_frames.train > $temp/utt2num_frames.train_subset
+  # Create a mapping from utterance to speaker ID (an integer)
+  awk -v id=0 '{print $1, id++}' $data/spk2utt > $temp/spk2int
+  utils/sym2int.pl -f 2 $temp/spk2int $data/utt2spk > $temp/utt2int
+  utils/filter_scp.pl $temp/utt2num_frames.train $temp/utt2int > $temp/utt2int.train
+  utils/filter_scp.pl $temp/utt2num_frames.valid $temp/utt2int > $temp/utt2int.valid
+  utils/filter_scp.pl $temp/utt2num_frames.train_subset $temp/utt2int > $temp/utt2int.train_subset
+fi
+
+num_pdfs=$(awk '{print $2}' $temp/utt2int | sort | uniq -c | wc -l)
+# The script assumes you've prepared the features ahead of time.
+feats="scp,s,cs:utils/filter_scp.pl $temp/ranges.JOB $data/feats.scp |"
+train_subset_feats="scp,s,cs:utils/filter_scp.pl $temp/train_subset_ranges.1 $data/feats.scp |"
+valid_feats="scp,s,cs:utils/filter_scp.pl $temp/valid_ranges.1 $data/feats.scp |"
+
+# first for the training data... work out how many archives.
+num_train_frames=$(awk '{n += $2} END{print n}' <$temp/utt2num_frames.train)
+num_train_subset_frames=$(awk '{n += $2} END{print n}' <$temp/utt2num_frames.train_subset)
+
+echo $num_train_frames >$dir/info/num_frames
+num_train_archives=$[($num_train_frames*$num_repeats)/$frames_per_iter + 1]
+echo "$0: Producing $num_train_archives archives for training"
+echo $num_train_archives > $dir/info/num_archives
+echo $num_diagnostic_archives > $dir/info/num_diagnostic_archives
+
+if [ $nj -gt $num_train_archives ]; then
+  echo "$0: Reducing num-jobs $nj to number of training archives $num_train_archives"
+  nj=$num_train_archives
+fi
+
+if [ $stage -le 1 ]; then
+  if [ -e $dir/storage ]; then
+    # Make soft links to storage directories, if distributing this way..  See
+    # utils/create_split_dir.pl.
+    echo "$0: creating data links"
+    utils/create_data_link.pl $(for x in $(seq $num_train_archives); do echo $dir/egs.$x.ark; done)
+    utils/create_data_link.pl $(for x in $(seq $num_train_archives); do echo $dir/egs_temp.$x.ark; done)
+  fi
+fi
+
+if [ $stage -le 2 ]; then
+  echo "$0: Allocating training examples"
+  $cmd $dir/log/allocate_examples_train.log \
+    sid/nnet3/xvector/allocate_egs.py \
+      --num-repeats=$num_repeats \
+      --min-frames-per-chunk=$min_frames_per_chunk \
+      --max-frames-per-chunk=$max_frames_per_chunk \
+      --frames-per-iter=$frames_per_iter \
+      --num-archives=$num_train_archives --num-jobs=$nj \
+      --utt2len-filename=$dir/temp/utt2num_frames.train \
+      --utt2int-filename=$dir/temp/utt2int.train --egs-dir=$dir  || exit 1
+
+  echo "$0: Allocating training subset examples"
+  $cmd $dir/log/allocate_examples_train_subset.log \
+    sid/nnet3/xvector/allocate_egs.py \
+      --prefix train_subset \
+      --num-repeats=1 \
+      --min-frames-per-chunk=$min_frames_per_chunk \
+      --max-frames-per-chunk=$max_frames_per_chunk \
+      --randomize-chunk-length false \
+      --frames-per-iter=$frames_per_iter_diagnostic \
+      --num-archives=$num_diagnostic_archives --num-jobs=1 \
+      --utt2len-filename=$dir/temp/utt2num_frames.train_subset \
+      --utt2int-filename=$dir/temp/utt2int.train_subset --egs-dir=$dir  || exit 1
+
+  echo "$0: Allocating validation examples"
+  $cmd $dir/log/allocate_examples_valid.log \
+    sid/nnet3/xvector/allocate_egs.py \
+      --prefix valid \
+      --num-repeats=1 \
+      --min-frames-per-chunk=$min_frames_per_chunk \
+      --max-frames-per-chunk=$max_frames_per_chunk \
+      --randomize-chunk-length false \
+      --frames-per-iter=$frames_per_iter_diagnostic \
+      --num-archives=$num_diagnostic_archives --num-jobs=1 \
+      --utt2len-filename=$dir/temp/utt2num_frames.valid \
+      --utt2int-filename=$dir/temp/utt2int.valid --egs-dir=$dir  || exit 1
+fi
+
+# At this stage we'll have created the ranges files that define how many egs
+# there are and where they come from.  If this is your first time running this
+# script, you might decide to put an exit 1 command here, and inspect the
+# contents of exp/$dir/temp/ranges.* before proceeding to the next stage.
+if [ $stage -le 3 ]; then
+  echo "$0: Generating training examples on disk"
+  rm $dir/.error 2>/dev/null
+  for g in $(seq $nj); do
+    outputs=$(awk '{for(i=1;i<=NF;i++)printf("ark:%s ",$i);}' $temp/outputs.$g)
+    $cmd $dir/log/train_create_examples.$g.log \
+      nnet3-xvector-get-egs --compress=$compress --num-pdfs=$num_pdfs $temp/ranges.$g \
+      "`echo $feats | sed s/JOB/$g/g`" $outputs || touch $dir/.error &
+  done
+  train_subset_outputs=$(awk '{for(i=1;i<=NF;i++)printf("ark:%s ",$i);}' $temp/train_subset_outputs.1)
+  echo "$0: Generating training subset examples on disk"
+  $cmd $dir/log/train_subset_create_examples.1.log \
+    nnet3-xvector-get-egs --compress=$compress --num-pdfs=$num_pdfs $temp/train_subset_ranges.1 \
+    "$train_subset_feats" $train_subset_outputs || touch $dir/.error &
+  wait
+  valid_outputs=$(awk '{for(i=1;i<=NF;i++)printf("ark:%s ",$i);}' $temp/valid_outputs.1)
+  echo "$0: Generating validation examples on disk"
+  $cmd $dir/log/valid_create_examples.1.log \
+    nnet3-xvector-get-egs --compress=$compress --num-pdfs=$num_pdfs $temp/valid_ranges.1 \
+    "$valid_feats" $valid_outputs || touch $dir/.error &
+  wait
+  if [ -f $dir/.error ]; then
+    echo "$0: Problem detected while dumping examples"
+    exit 1
+  fi
+fi
+
+if [ $stage -le 4 ]; then
+  echo "$0: Shuffling order of archives on disk"
+  $cmd --max-jobs-run $nj JOB=1:$num_train_archives $dir/log/shuffle.JOB.log \
+    nnet3-shuffle-egs --srand=JOB ark:$dir/egs_temp.JOB.ark \
+    ark,scp:$dir/egs.JOB.ark,$dir/egs.JOB.scp || exit 1;
+  $cmd --max-jobs-run $nj JOB=1:$num_diagnostic_archives $dir/log/train_subset_shuffle.JOB.log \
+    nnet3-shuffle-egs --srand=JOB ark:$dir/train_subset_egs_temp.JOB.ark \
+    ark,scp:$dir/train_diagnostic_egs.JOB.ark,$dir/train_diagnostic_egs.JOB.scp || exit 1;
+  $cmd --max-jobs-run $nj JOB=1:$num_diagnostic_archives $dir/log/valid_shuffle.JOB.log \
+    nnet3-shuffle-egs --srand=JOB ark:$dir/valid_egs_temp.JOB.ark \
+    ark,scp:$dir/valid_egs.JOB.ark,$dir/valid_egs.JOB.scp || exit 1;
+fi
+
+if [ $stage -le 5 ]; then
+  for file in $(for x in $(seq $num_diagnostic_archives); do echo $dir/train_subset_egs_temp.$x.ark; done) \
+    $(for x in $(seq $num_diagnostic_archives); do echo $dir/valid_egs_temp.$x.ark; done) \
+    $(for x in $(seq $num_train_archives); do echo $dir/egs_temp.$x.ark; done); do
+    [ -L $file ] && rm $(readlink -f $file)
+    rm $file
+  done
+  rm -rf $dir/valid_diagnostic.scp $dir/train_diagnostic.scp
+  for x in $(seq $num_diagnostic_archives); do
+    cat $dir/train_diagnostic_egs.$x.scp >> $dir/train_diagnostic.scp
+    cat $dir/valid_egs.$x.scp >> $dir/valid_diagnostic.scp
+  done
+  ln -sf train_diagnostic.scp $dir/combine.scp
+fi
+
+echo "$0: Finished preparing training examples"
diff --git a/egs/sre16/README.txt b/egs/sre16/README.txt
new file mode 100644 (file)
index 0000000..24eb4a5
--- /dev/null
@@ -0,0 +1,20 @@
+
+ This directory (sre16) contains example scripts for the NIST SRE 2016
+ speaker recognition evaluation. The following corpora are required to
+ perform the evaluation:
+    
+   NIST SRE 2016 enroll set
+   NIST SRE 2016 test set
+ More details on NIST SRE 2016 can be found at the url
+ https://www.nist.gov/itl/iad/mig/speaker-recognition-evaluation-2016.
+
+ Additional data sources (mostly past NIST SREs, Switchboard, etc) are
+ required to train the systems in the subdirectories. See the
+ corresponding README.txt files in the subdirectories for more details.
+
+ The subdirectories "v1" and so on are different speaker recognition
+ recipes. The recipe in v1 demonstrates a standard approach using a
+ full-covariance GMM-UBM, iVectors, and a PLDA backend.  The example 
+ in v2 demonstrates DNN speaker embeddings with a PLDA backend.
+
diff --git a/egs/sre16/v1/README.txt b/egs/sre16/v1/README.txt
new file mode 100644 (file)
index 0000000..41c0042
--- /dev/null
@@ -0,0 +1,29 @@
+ This example demonstrates a traditional iVector system evaluated on NIST
+ SRE 2016.  It is based on the recipe in ../../sre10/v1/.  In addition to the
+ standard features of the SRE10 recipe, it also demonstrates the use of data
+ augmentation for PLDA training.
+
+ The recipe uses the following data for system development.  This is in
+ addition to the NIST SRE 2016 dataset used for evaluation (see ../README.txt).
+     Corpus              LDC Catalog No.
+     SWBD2 Phase 1       LDC98S75
+     SWBD2 Phase 2       LDC99S79
+     SWBD2 Phase 3       LDC2002S06
+     SWBD Cellular 1     LDC2001S13
+     SWBD Cellular 2     LDC2004S07
+     SRE2004             LDC2006S44
+     SRE2005 Train       LDC2011S01
+     SRE2005 Test        LDC2011S04
+     SRE2006 Train       LDC2011S09
+     SRE2006 Test 1      LDC2011S10
+     SRE2006 Test 2      LDC2012S01
+     SRE2008 Train       LDC2011S05
+     SRE2008 Test        LDC2011S08
+     SRE2010 Eval        LDC2017S06
+     Mixer 6             LDC2013S03
+
+ The following datasets are used in data augmentation.
+
+     MUSAN               http://www.openslr.org/17
+     RIR_NOISES          http://www.openslr.org/28
diff --git a/egs/sre16/v1/cmd.sh b/egs/sre16/v1/cmd.sh
new file mode 100755 (executable)
index 0000000..d1ca1a6
--- /dev/null
@@ -0,0 +1,15 @@
+# you can change cmd.sh depending on what type of queue you are using.
+# If you have no queueing system and want to run on a local machine, you
+# can change all instances 'queue.pl' to run.pl (but be careful and run
+# commands one by one: most recipes will exhaust the memory on your
+# machine).  queue.pl works with GridEngine (qsub).  slurm.pl works
+# with slurm.  Different queues are configured differently, with different
+# queue names and different ways of specifying things like memory;
+# to account for these differences you can create and edit the file
+# conf/queue.conf to match your queue's configuration.  Search for
+# conf/queue.conf in http://kaldi-asr.org/doc/queue.html for more information,
+# or search for the string 'default_config' in utils/queue.pl or utils/slurm.pl.
+
+export train_cmd="queue.pl --mem 4G"
+
+
diff --git a/egs/sre16/v1/conf/mfcc.conf b/egs/sre16/v1/conf/mfcc.conf
new file mode 100644 (file)
index 0000000..e09ee93
--- /dev/null
@@ -0,0 +1,6 @@
+--sample-frequency=8000 
+--frame-length=25 # the default is 25
+--low-freq=20 # the default.
+--high-freq=3700 # the default is zero meaning use the Nyquist (4k in this case).
+--num-ceps=20 # higher than the default which is 12.
+--snip-edges=false
diff --git a/egs/sre16/v1/conf/vad.conf b/egs/sre16/v1/conf/vad.conf
new file mode 100644 (file)
index 0000000..a0ca244
--- /dev/null
@@ -0,0 +1,2 @@
+--vad-energy-threshold=5.5
+--vad-energy-mean-scale=0.5
diff --git a/egs/sre16/v1/local/make_musan.py b/egs/sre16/v1/local/make_musan.py
new file mode 100755 (executable)
index 0000000..b0bb362
--- /dev/null
@@ -0,0 +1,95 @@
+#!/usr/bin/env python3
+# Copyright 2015   David Snyder
+# Apache 2.0.
+#
+# This file is meant to be invoked by make_musan.sh.
+
+import os, sys
+
+def process_music_annotations(path):
+  utt2spk = {}
+  utt2vocals = {}
+  lines = open(path, 'r').readlines()
+  for line in lines:
+    utt, genres, vocals, musician = line.rstrip().split()[:4]
+    # For this application, the musican ID isn't important
+    utt2spk[utt] = utt
+    utt2vocals[utt] = vocals == "Y"
+  return utt2spk, utt2vocals
+
+def prepare_music(root_dir, use_vocals):
+  utt2vocals = {}
+  utt2spk = {}
+  utt2wav = {}
+  music_dir = os.path.join(root_dir, "music")
+  for root, dirs, files in os.walk(music_dir):
+    for file in files:
+      file_path = os.path.join(root, file)
+      if file.endswith(".wav"):
+        utt = str(file).replace(".wav", "")
+        utt2wav[utt] = file_path
+      elif str(file) == "ANNOTATIONS":
+        utt2spk_part, utt2vocals_part = process_music_annotations(file_path)
+        utt2spk.update(utt2spk_part)
+        utt2vocals.update(utt2vocals_part)
+  utt2spk_str = ""
+  utt2wav_str = ""
+  for utt in utt2vocals:
+    if use_vocals or not utt2vocals[utt]:
+      utt2spk_str = utt2spk_str + utt + " " + utt2spk[utt] + "\n"
+      utt2wav_str = utt2wav_str + utt + " sox -t wav " + utt2wav[utt] + " -r 8k -t wav - |\n"
+  return utt2spk_str, utt2wav_str
+
+def prepare_speech(root_dir):
+  utt2spk = {}
+  utt2wav = {}
+  speech_dir = os.path.join(root_dir, "speech")
+  for root, dirs, files in os.walk(speech_dir):
+    for file in files:
+      file_path = os.path.join(root, file)
+      if file.endswith(".wav"):
+        utt = str(file).replace(".wav", "")
+        utt2wav[utt] = file_path
+        utt2spk[utt] = utt
+  utt2spk_str = ""
+  utt2wav_str = ""
+  for utt in utt2spk:
+    utt2spk_str = utt2spk_str + utt + " " + utt2spk[utt] + "\n"
+    utt2wav_str = utt2wav_str + utt + " sox -t wav " + utt2wav[utt] + " -r 8k -t wav - |\n"
+  return utt2spk_str, utt2wav_str
+
+def prepare_noise(root_dir):
+  utt2spk = {}
+  utt2wav = {}
+  speech_dir = os.path.join(root_dir, "noise")
+  for root, dirs, files in os.walk(speech_dir):
+    for file in files:
+      file_path = os.path.join(root, file)
+      if file.endswith(".wav"):
+        utt = str(file).replace(".wav", "")
+        utt2wav[utt] = file_path
+        utt2spk[utt] = utt
+  utt2spk_str = ""
+  utt2wav_str = ""
+  for utt in utt2spk:
+    utt2spk_str = utt2spk_str + utt + " " + utt2spk[utt] + "\n"
+    utt2wav_str = utt2wav_str + utt + " sox -t wav " + utt2wav[utt] + " -r 8k -t wav - |\n"
+  return utt2spk_str, utt2wav_str
+
+def main():
+  in_dir = sys.argv[1]
+  out_dir = sys.argv[2]
+  use_vocals = sys.argv[3] == "Y"
+  utt2spk_music, utt2wav_music = prepare_music(in_dir, use_vocals)
+  utt2spk_speech, utt2wav_speech = prepare_speech(in_dir)
+  utt2spk_noise, utt2wav_noise = prepare_noise(in_dir)
+  utt2spk = utt2spk_speech + utt2spk_music + utt2spk_noise
+  utt2wav = utt2wav_speech + utt2wav_music + utt2wav_noise
+  wav_fi = open(os.path.join(out_dir, "wav.scp"), 'w')
+  wav_fi.write(utt2wav)
+  utt2spk_fi = open(os.path.join(out_dir, "utt2spk"), 'w')
+  utt2spk_fi.write(utt2spk)
+
+
+if __name__=="__main__":
+  main()
diff --git a/egs/sre16/v1/local/make_musan.sh b/egs/sre16/v1/local/make_musan.sh
new file mode 100755 (executable)
index 0000000..1faac0e
--- /dev/null
@@ -0,0 +1,37 @@
+#!/bin/bash
+# Copyright 2015   David Snyder
+# Apache 2.0.
+#
+# This script, called by ../run.sh, creates the MUSAN
+# data directory. The required dataset is freely available at
+#   http://www.openslr.org/17/
+
+set -e
+in_dir=$1
+data_dir=$2
+use_vocals='Y'
+
+rm -rf local/musan.tmp
+mkdir local/musan.tmp
+
+echo "Preparing ${data_dir}/musan..."
+mkdir -p ${data_dir}/musan
+local/make_musan.py ${in_dir} ${data_dir}/musan ${use_vocals}
+utils/fix_data_dir.sh ${data_dir}/musan
+
+grep "music" ${data_dir}/musan/utt2spk > local/musan.tmp/utt2spk_music
+grep "speech" ${data_dir}/musan/utt2spk > local/musan.tmp/utt2spk_speech
+grep "noise" ${data_dir}/musan/utt2spk > local/musan.tmp/utt2spk_noise
+utils/subset_data_dir.sh --utt-list local/musan.tmp/utt2spk_music \
+  ${data_dir}/musan ${data_dir}/musan_music
+utils/subset_data_dir.sh --utt-list local/musan.tmp/utt2spk_speech \
+  ${data_dir}/musan ${data_dir}/musan_speech
+utils/subset_data_dir.sh --utt-list local/musan.tmp/utt2spk_noise \
+  ${data_dir}/musan ${data_dir}/musan_noise
+
+utils/fix_data_dir.sh ${data_dir}/musan_music
+utils/fix_data_dir.sh ${data_dir}/musan_speech
+utils/fix_data_dir.sh ${data_dir}/musan_noise
+
+rm -rf local/musan.tmp
+
diff --git a/egs/sre16/v1/local/make_mx6.sh b/egs/sre16/v1/local/make_mx6.sh
new file mode 100755 (executable)
index 0000000..4e0df13
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/bash
+# Copyright 2017   David Snyder
+# Apache 2.0.
+#
+# This script prepares both the microphone and telephone portions of the
+# Mixer 6 corpus.
+if [ $# -ne 2 ]; then
+  echo "Usage: $0 <mixer6-speech> <out-dir>"
+  echo "e.g.: $0 /export/corpora/LDC/LDC2013S03 data/"
+  exit 1;
+fi
+
+set -e
+in_dir=$1
+out_dir=$2
+
+# Mic 01 is the lapel mic for the interviewer, so we don't use it.  Mic 02 is
+# the lapel mic for the interviewee.  All other mics are placed throughout the
+# room.  In addition to mic 01, we omit mics 03 and 14 as they are often
+# silent.
+echo "$0: preparing mic speech (excluding 01, 03, and 14)"
+
+for mic in 02 04 05 06 07 08 09 10 11 12 13; do
+  local/make_mx6_mic.pl $in_dir $mic $out_dir
+done
+
+utils/combine_data.sh $out_dir/mx6_mic_04_to_13 $out_dir/mx6_mic_{04,05,06,07,08,09,10,11,12,13}
+
+# Mics 02-13 contain the same content, but recorded from different microphones.
+# To get some channel diversity, but not be overwhelmed with duplicated data
+# we take a 2k subset from mics 04-13 and combine it with all of mic 02.
+echo "$0: selecting a 2k subset of mics 04 through 13 and combining it with mic 02"
+utils/subset_data_dir.sh $out_dir/mx6_mic_04_to_13 2000 $out_dir/mx6_mic_04_to_13_2k
+utils/combine_data.sh $out_dir/mx6_mic $out_dir/mx6_mic_02 $out_dir/mx6_mic_04_to_13_2k
+
+echo "$0: preparing telephone portion"
+local/make_mx6_calls.pl $in_dir $out_dir
+
+echo "$0 combining mic and telephone speech in data/mx6"
+utils/combine_data.sh $out_dir/mx6 $out_dir/mx6_mic $out_dir/mx6_calls
+utils/fix_data_dir.sh $out_dir/mx6
diff --git a/egs/sre16/v1/local/make_mx6_calls.pl b/egs/sre16/v1/local/make_mx6_calls.pl
new file mode 100755 (executable)
index 0000000..ed9d637
--- /dev/null
@@ -0,0 +1,105 @@
+#!/usr/bin/perl
+use warnings; #sed replacement for -w perl parameter
+# Copyright 2017   David Snyder
+# Apache 2.0
+#
+# Prepares the telephone portion of Mixer 6 (LDC2013S03).
+
+if (@ARGV != 2) {
+  print STDERR "Usage: $0 <path-to-LDC2013S03> <path-to-output>\n";
+  print STDERR "e.g. $0 /export/corpora5/LDC/LDC2013S03 data/\n";
+  exit(1);
+}
+($db_base, $out_dir) = @ARGV;
+
+if (! -d "$db_base/mx6_speech/data/ulaw_sphere/") {
+  print STDERR "Directory $db_base/mx6_speech/data/ulaw_sphere/ doesn't exist\n";
+  exit(1);
+}
+
+$out_dir = "$out_dir/mx6_calls";
+
+$tmp_dir = "$out_dir/tmp";
+if (system("mkdir -p $tmp_dir") != 0) {
+  die "Error making directory $tmp_dir";
+}
+
+if (system("mkdir -p $out_dir") != 0) {
+  print STDERR "Error making directory $out_dir\n";
+  exit(1);
+}
+
+%call2sph = ();
+open(SUBJECTS, "<$db_base/mx6_speech/docs/mx6_subjs.csv") || die "cannot open $$db_base/mx6_speech/docs/mx6_subjs.csv";
+open(SPKR, ">$out_dir/utt2spk") || die "Could not open the output file $out_dir/utt2spk";
+open(GNDR, ">$out_dir/spk2gender") || die "Could not open the output file $out_dir/spk2gender";
+open(WAV, ">$out_dir/wav.scp") || die "Could not open the output file $out_dir/wav.scp";
+open(META, "<$db_base/mx6_speech/docs/mx6_calls.csv") || die "cannot open $db_base/mx6_speech/docs/mx6_calls.csv";
+
+if (system("find $db_base/mx6_speech/data/ulaw_sphere/ -name '*.sph' > $tmp_dir/sph.list") != 0) {
+  die "Error getting list of sph files";
+}
+open(SPHLIST, "<$tmp_dir/sph.list") or die "cannot open wav list";
+
+while(<SPHLIST>) {
+  chomp;
+  $sph = $_;
+  @toks = split("/",$sph);
+  $sph_id = (split("[./]",$toks[$#toks]))[0];
+  $call_id = (split("_", $sph_id))[2];
+  $call2sph[$call_id] = $sph;
+}
+
+while (<SUBJECTS>) {
+  chomp;
+  $line = $_;
+  @toks = split(",", $line);
+  $spk = $toks[0];
+  $gender = lc $toks[1];
+  if ($gender eq "f" or $gender eq "m") {
+    print GNDR "$spk $gender\n";
+  }
+}
+
+$num_good_files = 0;
+$num_bad_files = 0;
+while (<META>) {
+  chomp;
+  $line = $_;
+  @toks = split(",", $line);
+  $call_id = $toks[0];
+  ($call_date, $call_time) = split(/_/, $toks[1]);
+  $sid_A = $toks[4];
+  $sid_B = $toks[12];
+  if (-f $call2sph[$call_id]) {
+    $utt_A = "${sid_A}_MX6_${call_id}_A";
+    $utt_B = "${sid_B}_MX6_${call_id}_B";
+    print SPKR "${utt_A} $sid_A\n";
+    print SPKR "${utt_B} $sid_B\n";
+    print WAV "${utt_A} sph2pipe -f wav -p -c 1 $call2sph[$call_id] |\n";
+    print WAV "${utt_B} sph2pipe -f wav -p -c 2 $call2sph[$call_id] |\n";
+    $num_good_files++;
+  } else {
+    print STDERR "Sphere file for $call_id doesn't exist\n";
+    $num_bad_files++;
+  }
+}
+
+print STDERR "Processed $num_good_files utterances; $num_bad_files had missing sphere data.\n";
+
+close(SPHLIST) || die;
+close(SUBJECTS) || die;
+close(GNDR) || die;
+close(SPKR) || die;
+close(WAV) || die;
+close(META) || die;
+
+if (system(
+  "utils/utt2spk_to_spk2utt.pl $out_dir/utt2spk >$out_dir/spk2utt") != 0) {
+  die "Error creating spk2utt file in directory $out_dir";
+}
+
+system("utils/fix_data_dir.sh $out_dir");
+if (system("utils/validate_data_dir.sh --no-text --no-feats $out_dir") != 0) {
+  die "Error validating directory $out_dir";
+}
diff --git a/egs/sre16/v1/local/make_mx6_mic.pl b/egs/sre16/v1/local/make_mx6_mic.pl
new file mode 100755 (executable)
index 0000000..7e1b404
--- /dev/null
@@ -0,0 +1,92 @@
+#!/usr/bin/perl
+use warnings; #sed replacement for -w perl parameter
+# Copyright 2017   David Snyder
+# Apache 2.0
+# Prepares Mixer 6 (LDC2013S03) speech from a specified microphone and
+# downsamples it to 8k.
+
+if (@ARGV != 3) {
+  print STDERR "Usage: $0 <path-to-LDC2013S03> <channel> <path-to-output>\n";
+  print STDERR "e.g. $0 /export/corpora5/LDC/LDC2013S03 02 data/\n";
+  exit(1);
+}
+($db_base, $ch, $out_dir) = @ARGV;
+
+@bad_channels = ("01", "03", "14");
+if (/$ch/i ~~ @bad_channels) {
+  print STDERR "Bad channel $ch\n";
+  exit(1);
+}
+
+if (! -d "$db_base/mx6_speech/data/pcm_flac/CH$ch/") {
+  print STDERR "Directory $db_base/mx6_speech/data/pcm_flac/CH$ch/ doesn't exist\n";
+  exit(1);
+}
+
+$out_dir = "$out_dir/mx6_mic_$ch";
+if (system("mkdir -p $out_dir")) {
+  print STDERR "Error making directory $out_dir\n";
+  exit(1);
+}
+
+if (system("mkdir -p $out_dir") != 0) {
+  print STDERR "Error making directory $out_dir\n";
+  exit(1);
+}
+
+open(SUBJECTS, "<$db_base/mx6_speech/docs/mx6_subjs.csv") || die "cannot open $$db_base/mx6_speech/docs/mx6_subjs.csv";
+open(SPKR, ">$out_dir/utt2spk") || die "Could not open the output file $out_dir/utt2spk";
+open(GNDR, ">$out_dir/spk2gender") || die "Could not open the output file $out_dir/spk2gender";
+open(WAV, ">$out_dir/wav.scp") || die "Could not open the output file $out_dir/wav.scp";
+open(META, "<$db_base/mx6_speech/docs/mx6_ivcomponents.csv") || die "cannot open $db_base/mx6_speech/docs/mx6_ivcomponents.csv";
+
+while (<SUBJECTS>) {
+  chomp;
+  $line = $_;
+  @toks = split(",", $line);
+  $spk = $toks[0];
+  $gender = lc $toks[1];
+  if ($gender eq "f" or $gender eq "m") {
+    print GNDR "$spk $gender\n";
+  }
+}
+
+$num_good_files = 0;
+$num_bad_files = 0;
+while (<META>) {
+  chomp;
+  $line = $_;
+  @toks = split(",", $line);
+  $flac = "$db_base/mx6_speech/data/pcm_flac/CH$ch/$toks[0]_CH$ch.flac";
+  $t1 = $toks[7];
+  $t2 = $toks[8];
+  @toks2 = split(/_/, $toks[0]);
+  $spk = $toks2[3];
+  $utt = "${spk}_MX6_$toks2[0]_$toks2[1]_$ch";
+  if (-f $flac) {
+    print SPKR "${utt} $spk\n";
+    print WAV "${utt} sox -t flac $flac -r 8k -t wav - trim $t1 =$t2 |\n";
+    $num_good_files++;
+  } else {
+    print STDERR "File $flac doesn't exist\n";
+    $num_bad_files++;
+  }
+}
+
+print STDERR "Processed $num_good_files utterances; $num_bad_files had missing flac data.\n";
+
+close(SUBJECTS) || die;
+close(GNDR) || die;
+close(SPKR) || die;
+close(WAV) || die;
+close(META) || die;
+
+if (system(
+  "utils/utt2spk_to_spk2utt.pl $out_dir/utt2spk >$out_dir/spk2utt") != 0) {
+  die "Error creating spk2utt file in directory $out_dir";
+}
+
+system("utils/fix_data_dir.sh $out_dir");
+if (system("utils/validate_data_dir.sh --no-text --no-feats $out_dir") != 0) {
+  die "Error validating directory $out_dir";
+}
diff --git a/egs/sre16/v1/local/make_sre.pl b/egs/sre16/v1/local/make_sre.pl
new file mode 100755 (executable)
index 0000000..d6e1abf
--- /dev/null
@@ -0,0 +1,75 @@
+#!/usr/bin/perl
+use warnings; #sed replacement for -w perl parameter
+#
+# Copyright 2015   David Snyder
+# Apache 2.0.
+# Usage: make_sre.pl <path-to-data> <name-of-source> <sre-ref> <output-dir>
+
+if (@ARGV != 4) {
+  print STDERR "Usage: $0 <path-to-data> <name-of-source> <sre-ref> <output-dir>\n";
+  print STDERR "e.g. $0 /export/corpora5/LDC/LDC2006S44 sre2004 sre_ref data/sre2004\n";
+  exit(1);
+}
+
+($db_base, $sre_year, $sre_ref_filename, $out_dir) = @ARGV;
+%utt2sph = ();
+%spk2gender = ();
+
+$tmp_dir = "$out_dir/tmp";
+if (system("mkdir -p $tmp_dir") != 0) {
+  die "Error making directory $tmp_dir";
+}
+
+if (system("find -L $db_base -name '*.sph' > $tmp_dir/sph.list") != 0) {
+  die "Error getting list of sph files";
+}
+open(WAVLIST, "<$tmp_dir/sph.list") or die "cannot open wav list";
+
+while(<WAVLIST>) {
+  chomp;
+  $sph = $_;
+  @A1 = split("/",$sph);
+  @A2 = split("[./]",$A1[$#A1]);
+  $uttId=$A2[0];
+  $utt2sph{$uttId} = $sph;
+}
+
+open(GNDR,">", "$out_dir/spk2gender") or die "Could not open the output file $out_dir/spk2gender";
+open(SPKR,">", "$out_dir/utt2spk") or die "Could not open the output file $out_dir/utt2spk";
+open(WAV,">", "$out_dir/wav.scp") or die "Could not open the output file $out_dir/wav.scp";
+open(SRE_REF, "<$sre_ref_filename") or die "Cannot open SRE reference.";
+while (<SRE_REF>) {
+  chomp;
+  ($speaker, $gender, $other_sre_year, $utt_id, $channel) = split(" ", $_);
+  $channel_num = "1";
+  if ($channel eq "A") {
+    $channel_num = "1";
+  } else {
+    $channel_num = "2";
+  }
+  $channel = lc $channel;
+  if (($other_sre_year eq "sre20$sre_year") and (exists $utt2sph{$utt_id})) {
+    $full_utt_id = "$speaker-sre$sre_year-$utt_id-$channel";
+    $spk2gender{"$speaker"} = $gender;
+    print WAV "$full_utt_id"," sph2pipe -f wav -p -c $channel_num $utt2sph{$utt_id} |\n";
+    print SPKR "$full_utt_id $speaker","\n";
+  }
+}
+foreach $speaker (keys %spk2gender) {
+  print GNDR "$speaker $spk2gender{$speaker}\n";
+}
+
+close(GNDR) || die;
+close(SPKR) || die;
+close(WAV) || die;
+close(SRE_REF) || die;
+
+if (system(
+  "utils/utt2spk_to_spk2utt.pl $out_dir/utt2spk >$out_dir/spk2utt") != 0) {
+  die "Error creating spk2utt file in directory $out_dir";
+}
+
+system("utils/fix_data_dir.sh $out_dir");
+if (system("utils/validate_data_dir.sh --no-text --no-feats $out_dir") != 0) {
+  die "Error validating directory $out_dir";
+}
diff --git a/egs/sre16/v1/local/make_sre.sh b/egs/sre16/v1/local/make_sre.sh
new file mode 100755 (executable)
index 0000000..45e75ac
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/bash
+# Copyright 2017   David Snyder
+# Apache 2.0.
+#
+# See README.txt for more info on data required.
+
+set -e
+
+data_root=$1
+data_dir=$2
+
+wget -P data/local/ http://www.openslr.org/resources/15/speaker_list.tgz
+tar -C data/local/ -xvf data/local/speaker_list.tgz
+sre_ref=data/local/speaker_list
+
+local/make_sre.pl $data_root/LDC2006S44/ \
+   04 $sre_ref $data_dir/sre2004
+
+local/make_sre.pl $data_root/LDC2011S01 \
+  05 $sre_ref $data_dir/sre2005_train
+
+local/make_sre.pl $data_root/LDC2011S04 \
+  05 $sre_ref $data_dir/sre2005_test
+
+local/make_sre.pl $data_root/LDC2011S09 \
+  06 $sre_ref $data_dir/sre2006_train
+
+local/make_sre.pl $data_root/LDC2011S10 \
+  06 $sre_ref $data_dir/sre2006_test_1
+
+local/make_sre.pl $data_root/LDC2012S01 \
+  06 $sre_ref $data_dir/sre2006_test_2
+
+rm data/local/speaker_list.*
diff --git a/egs/sre16/v1/local/make_sre08.pl b/egs/sre16/v1/local/make_sre08.pl
new file mode 100755 (executable)
index 0000000..e68cc49
--- /dev/null
@@ -0,0 +1,131 @@
+#!/usr/bin/perl
+use warnings; #sed replacement for -w perl parameter
+# Copyright 2017   David Snyder
+# Apache 2.0
+#
+# This script prepares SRE08 test (LDC2011S08) and SRE08 enroll (LDC2011S05)
+# simultaneously in a single data directory.
+
+if (@ARGV != 3) {
+  print STDERR "Usage: $0 <path-to-SRE08-test> <path-to-SRE08-train> <path-to-output>\n";
+  print STDERR "e.g. $0 /export/corpora/LDC/LDC2011S08 /export/corpora/LDC/LDC2011S05 data/\n";
+  exit(1);
+}
+($db_base_test, $db_base_train, $out_dir) = @ARGV;
+
+if (! -d "$db_base_test/data/") {
+  print STDERR "Directory $db_base_test/data/ doesn't exist\n";
+  exit(1);
+}
+
+if (! -d "$db_base_train/data/") {
+  print STDERR "Directory $db_base_train/data/ doesn't exist\n";
+  exit(1);
+}
+
+$out_dir = "$out_dir/sre08";
+$tmp_dir = "$out_dir/tmp";
+if (system("mkdir -p $tmp_dir") != 0) {
+  die "Error making directory $tmp_dir";
+}
+
+if (system("mkdir -p $out_dir") != 0) {
+  print STDERR "Error making directory $out_dir\n";
+  exit(1);
+}
+
+%seg2sph = ();
+open(TRIALS, "<$db_base_test/data/keys/NIST_SRE08_KEYS.v0.1/trial-keys/NIST_SRE08_short2-short3.trial.key") || die "Could not open $db_base_test/data/keys/NIST_SRE08_KEYS.v0.1/trial-keys/NIST_SRE08_short2-short3.trial.key";
+open(MODELS, "<$db_base_test/data/keys/NIST_SRE08_KEYS.v0.1/model-keys/NIST_SRE08_short2.model.key") || die "Could not open $db_base_test/data/keys/NIST_SRE08_KEYS.v0.1/model-keys/NIST_SRE08_short2.model.key";
+open(SPKR, ">$out_dir/utt2spk") || die "Could not open the output file $out_dir/utt2spk";
+open(GNDR, ">$out_dir/spk2gender") || die "Could not open the output file $out_dir/spk2gender";
+open(WAV, ">$out_dir/wav.scp") || die "Could not open the output file $out_dir/wav.scp";
+
+if (system("find $db_base_test/data/ -name '*.sph' > $tmp_dir/sph.list") != 0) {
+  die "Error getting list of sph files $db_base_test";
+}
+if (system("find $db_base_train/data/ -name '*.sph' >> $tmp_dir/sph.list") != 0) {
+  die "Error getting list of sph files for $db_base_train";
+}
+
+open(SPHLIST, "<$tmp_dir/sph.list") or die "cannot open wav list";
+while(<SPHLIST>) {
+  chomp;
+  $sph = $_;
+  @toks = split("/",$sph);
+  $sph_id = (split("[./]",$toks[$#toks]))[0];
+  $seg2sph{$sph_id} = $sph;
+}
+
+%model2sid = ();
+while (<MODELS>) {
+  chomp;
+  $line = $_;
+  @toks = split(",", $line);
+  $model = $toks[0];
+  $gender = $toks[1];
+  ($seg, $ch) = split("[:]", $toks[2]);
+  $sid = $toks[3];
+  $model2sid{$model} = $sid;
+  print GNDR "$sid $gender\n";
+  if (exists $seg2sph{$seg} and -f $seg2sph{$seg}) {
+    $sph = $seg2sph{$seg};
+    if ($ch eq "a") {
+      $utt = "${sid}_SRE08_${seg}_A";
+      print WAV "$utt"," sph2pipe -f wav -p -c 1 $sph |\n";
+      print SPKR "$utt $sid\n";
+    } elsif($ch eq "b") {
+      $utt = "${sid}_SRE08_${seg}_B";
+      print WAV "$utt"," sph2pipe -f wav -p -c 2 $sph |\n";
+      print SPKR "$utt $sid\n";
+    } else {
+      print STDERR "Malformed trials file\n";
+      exit(1);
+    }
+  }
+}
+
+while (<TRIALS>) {
+  chomp;
+  $line = $_;
+  @toks = split(",", $line);
+  $model = $toks[0];
+  $seg = $toks[1];
+  $ch = $toks[2];
+  $target = $toks[3];
+  if (exists $seg2sph{$seg} and -f $seg2sph{$seg}) {
+    $sph = $seg2sph{$seg};
+    if ($target eq "target" and exists $model2sid{$model}) {
+      $sid = $model2sid{$model};
+      if ($ch eq "a") {
+        $utt = "${sid}_SRE08_${seg}_A";
+        print WAV "$utt"," sph2pipe -f wav -p -c 1 $sph |\n";
+        print SPKR "$utt $sid\n";
+      } elsif($ch eq "b") {
+        $utt = "${sid}_SRE08_${seg}_B";
+        print WAV "$utt"," sph2pipe -f wav -p -c 2 $sph |\n";
+        print SPKR "$utt $sid\n";
+      } else {
+        print STDERR "Malformed trials file\n";
+        exit(1);
+      }
+    }
+  }
+}
+
+close(TRIALS) || die;
+close(MODELS) || die;
+close(GNDR) || die;
+close(SPKR) || die;
+close(WAV) || die;
+
+if (system(
+  "utils/utt2spk_to_spk2utt.pl $out_dir/utt2spk >$out_dir/spk2utt") != 0) {
+  die "Error creating spk2utt file in directory $out_dir";
+}
+
+system("utils/fix_data_dir.sh $out_dir");
+if (system("utils/validate_data_dir.sh --no-text --no-feats $out_dir") != 0) {
+  die "Error validating directory $out_dir";
+}
+
diff --git a/egs/sre16/v1/local/make_sre10.pl b/egs/sre16/v1/local/make_sre10.pl
new file mode 100755 (executable)
index 0000000..eba9f69
--- /dev/null
@@ -0,0 +1,133 @@
+#!/usr/bin/perl
+use warnings; #sed replacement for -w perl parameter
+# Copyright 2017   David Snyder
+# Apache 2.0
+#
+# Prepares NIST SRE10 enroll and test data in a single directory.
+if (@ARGV != 2) {
+  print STDERR "Usage: $0 <path-to-SRE10-eval> <path-to-output>\n";
+  print STDERR "e.g. $0 /export/corpora5/SRE/SRE2010/eval/ data/\n";
+  exit(1);
+}
+($db_base, $out_dir) = @ARGV;
+
+if (! -d "$db_base/data/") {
+  print STDERR "Directory $db_base/data/ doesn't exist\n";
+  exit(1);
+}
+$out_dir = "$out_dir/sre10";
+$tmp_dir = "$out_dir/tmp";
+if (system("mkdir -p $tmp_dir") != 0) {
+  die "Error making directory $tmp_dir";
+}
+
+if (system("mkdir -p $out_dir") != 0) {
+  print STDERR "Error making directory $out_dir\n";
+  exit(1);
+}
+
+%seg2sph = ();
+open(TRIALS, "<$db_base/keys/coreext-coreext.trialkey.csv") || die "Could not open $db_base/keys/coreext-coreext.trialkey.csv";
+open(TRAIN, "<$db_base/train/coreext.trn") || die "Could not open $db_base/train/coreext.trn";
+open(MODELS, "<$db_base/keys/coreext.modelkey.csv") || die "Could not open $db_base/keys/coreext.modelkey.csv";
+open(SPKR, ">$out_dir/utt2spk") || die "Could not open the output file $out_dir/utt2spk";
+open(GNDR, ">$out_dir/spk2gender") || die "Could not open the output file $out_dir/spk2gender";
+open(WAV, ">$out_dir/wav.scp") || die "Could not open the output file $out_dir/wav.scp";
+
+if (system("find $db_base/data/ -name '*.sph' > $tmp_dir/sph.list") != 0) {
+  die "Error getting list of sph files";
+}
+open(SPHLIST, "<$tmp_dir/sph.list") or die "cannot open wav list";
+while(<SPHLIST>) {
+  chomp;
+  $sph = $_;
+  @toks = split("/",$sph);
+  $sph_id = (split("[./]",$toks[$#toks]))[0];
+  $seg2sph{$sph_id} = $sph;
+}
+
+%model2sid = ();
+while (<MODELS>) {
+  chomp;
+  $line = $_;
+  ($model, $sid) = split(",", $line);
+  if (not $sid eq "NOT_SCORED") {
+    $model2sid{$model} = $sid;
+  }
+}
+
+while (<TRAIN>) {
+  chomp;
+  $line = $_;
+  @toks = split(" ", $line);
+  $model = $toks[0];
+  $gender = $toks[1];
+  @toks2 = split("/", $toks[2]);
+  ($sph, $ch) = split("[:]", $toks2[$#toks2]);
+  $seg = (split("[./]", $sph))[0];
+  if (exists $seg2sph{$seg}) {
+    $sph = $seg2sph{$seg};
+    if (exists $model2sid{$model}) {
+      $sid = $model2sid{$model};
+      print GNDR "$sid $gender\n";
+      if ($ch eq "A") {
+        $utt = "${sid}_SRE10_${seg}_A";
+        print WAV "$utt"," sph2pipe -f wav -p -c 1 $sph |\n";
+        print SPKR "$utt $sid\n";
+      } elsif($ch eq "B") {
+        $utt = "${sid}_SRE10_${seg}_B";
+        print WAV "$utt"," sph2pipe -f wav -p -c 2 $sph |\n";
+        print SPKR "$utt $sid\n";
+      } else {
+        print STDERR "Malformed train file\n";
+        exit(1);
+      }
+    }
+  }
+}
+
+while (<TRIALS>) {
+  chomp;
+  $line = $_;
+  @toks = split(",", $line);
+  $model = $toks[0];
+  $seg = $toks[1];
+  $ch = $toks[2];
+  $target = $toks[3];
+  if (exists $seg2sph{$seg} and -f $seg2sph{$seg}) {
+    $sph = $seg2sph{$seg};
+    if ($target eq "target" and exists $model2sid{$model}) {
+      $sid = $model2sid{$model};
+      if ($ch eq "a") {
+        $utt = "${sid}_SRE10_${seg}_A";
+        print WAV "$utt"," sph2pipe -f wav -p -c 1 $sph |\n";
+        print SPKR "$utt $sid\n";
+      } elsif($ch eq "b") {
+        $utt = "${sid}_SRE10_${seg}_B";
+        print WAV "$utt"," sph2pipe -f wav -p -c 2 $sph |\n";
+        print SPKR "$utt $sid\n";
+      } else {
+        print STDERR "Malformed trials file\n";
+        exit(1);
+      }
+    }
+  }
+}
+
+close(TRIALS) || die;
+close(TRAIN) || die;
+close(MODELS) || die;
+close(GNDR) || die;
+close(SPKR) || die;
+close(WAV) || die;
+
+if (system(
+  "utils/utt2spk_to_spk2utt.pl $out_dir/utt2spk >$out_dir/spk2utt") != 0) {
+  die "Error creating spk2utt file in directory $out_dir";
+}
+
+system("utils/fix_data_dir.sh $out_dir");
+if (system("utils/validate_data_dir.sh --no-text --no-feats $out_dir") != 0) {
+  die "Error validating directory $out_dir";
+}
+
diff --git a/egs/sre16/v1/local/make_sre16_eval.pl b/egs/sre16/v1/local/make_sre16_eval.pl
new file mode 100755 (executable)
index 0000000..9817a85
--- /dev/null
@@ -0,0 +1,154 @@
+#!/usr/bin/perl
+use warnings; #sed replacement for -w perl parameter
+# Copyright 2017   David Snyder
+# Apache 2.0
+#
+
+if (@ARGV != 2) {
+  print STDERR "Usage: $0 <path-to-SRE16-eval> <path-to-output>\n";
+  print STDERR "e.g. $0 /export/corpora/SRE/R149_0_1 data/\n";
+  exit(1);
+}
+
+($db_base, $out_dir) = @ARGV;
+
+# Handle enroll
+$out_dir_enroll = "$out_dir/sre16_eval_enroll";
+if (system("mkdir -p $out_dir_enroll")) {
+  die "Error making directory $out_dir_enroll";
+}
+
+$tmp_dir_enroll = "$out_dir_enroll/tmp";
+if (system("mkdir -p $tmp_dir_enroll") != 0) {
+  die "Error making directory $tmp_dir_enroll";
+}
+
+open(SPKR, ">$out_dir_enroll/utt2spk") || die "Could not open the output file $out_dir_enroll/utt2spk";
+open(WAV, ">$out_dir_enroll/wav.scp") || die "Could not open the output file $out_dir_enroll/wav.scp";
+open(META, "<$db_base/docs/sre16_eval_enrollment.tsv") or die "cannot open wav list";
+%utt2fixedutt = ();
+while (<META>) {
+  $line = $_;
+  @toks = split(" ", $line);
+  $spk = $toks[0];
+  $utt = $toks[1];
+  if ($utt ne "segment") {
+    print SPKR "${spk}-${utt} $spk\n";
+    $utt2fixedutt{$utt} = "${spk}-${utt}";
+  }
+}
+
+if (system("find $db_base/data/enrollment/ -name '*.sph' > $tmp_dir_enroll/sph.list") != 0) {
+  die "Error getting list of sph files";
+}
+
+open(WAVLIST, "<$tmp_dir_enroll/sph.list") or die "cannot open wav list";
+
+while(<WAVLIST>) {
+  chomp;
+  $sph = $_;
+  @t = split("/",$sph);
+  @t1 = split("[./]",$t[$#t]);
+  $utt=$utt2fixedutt{$t1[0]};
+  print WAV "$utt"," sph2pipe -f wav -p -c 1 $sph |\n";
+}
+close(WAV) || die;
+close(SPKR) || die;
+
+# Handle test
+$out_dir_test= "$out_dir/sre16_eval_test";
+if (system("mkdir -p $out_dir_test")) {
+  die "Error making directory $out_dir_test";
+}
+
+$tmp_dir_test = "$out_dir_test/tmp";
+if (system("mkdir -p $tmp_dir_test") != 0) {
+  die "Error making directory $tmp_dir_test";
+}
+
+open(SPKR, ">$out_dir_test/utt2spk") || die "Could not open the output file $out_dir_test/utt2spk";
+open(WAV, ">$out_dir_test/wav.scp") || die "Could not open the output file $out_dir_test/wav.scp";
+open(TRIALS, ">$out_dir_test/trials") || die "Could not open the output file $out_dir_test/trials";
+open(TGL_TRIALS, ">$out_dir_test/trials_tgl") || die "Could not open the output file $out_dir_test/trials_tgl";
+open(YUE_TRIALS, ">$out_dir_test/trials_yue") || die "Could not open the output file $out_dir_test/trials_yue";
+
+if (system("find $db_base/data/test/ -name '*.sph' > $tmp_dir_test/sph.list") != 0) {
+  die "Error getting list of sph files";
+}
+
+open(KEY, "<$db_base/docs/sre16_eval_trial_key.tsv") || die "Could not open trials file $db_base/docs/sre16_eval_trial_key.tsv.  It might be located somewhere else in your distribution.";
+open(SEG_KEY, "<$db_base/docs/sre16_eval_segment_key.tsv") || die "Could not open trials file $db_base/docs/sre16_eval_segment_key.tsv.  It might be located somewhere else in your distribution.";
+open(LANG_KEY, "<$db_base/metadata/calls.tsv") || die "Could not open trials file $db_base/metadata/calls.tsv.  It might be located somewhere else in your distribution.";
+open(WAVLIST, "<$tmp_dir_test/sph.list") or die "cannot open wav list";
+
+%utt2call = ();
+while(<SEG_KEY>) {
+  chomp;
+  $line = $_;
+  @toks = split(" ", $line);
+  $utt = $toks[0];
+  $call = $toks[1];
+  if ($utt ne "segment") {
+    $utt2call{$utt} = $call;
+  }
+}
+close(SEG_KEY) || die;
+
+%call2lang = ();
+while(<LANG_KEY>) {
+  chomp;
+  $line = $_;
+  @toks = split(" ", $line);
+  $call = $toks[0];
+  $lang = $toks[1];
+  $call2lang{$call} = $lang;
+}
+close(LANG_KEY) || die;
+
+while(<WAVLIST>) {
+  chomp;
+  $sph = $_;
+  @t = split("/",$sph);
+  @t1 = split("[./]",$t[$#t]);
+  $utt=$t1[0];
+  print WAV "$utt"," sph2pipe -f wav -p -c 1 $sph |\n";
+  print SPKR "$utt $utt\n";
+}
+close(WAV) || die;
+close(SPKR) || die;
+
+while (<KEY>) {
+  $line = $_;
+  @toks = split(" ", $line);
+  $spk = $toks[0];
+  $utt = $toks[1];
+  $call = $utt2call{$utt};
+  $target_type = $toks[3];
+  if ($utt ne "segment") {
+    print TRIALS "${spk} ${utt} ${target_type}\n";
+    if ($call2lang{$call} eq "tgl") {
+      print TGL_TRIALS "${spk} ${utt} ${target_type}\n";
+    } elsif ($call2lang{$call} eq "yue") {
+      print YUE_TRIALS "${spk} ${utt} ${target_type}\n";
+    } else {
+      die "Unexpected language $call2lang{$call} for utterance $utt.";
+    }
+  }
+}
+
+close(TRIALS) || die;
+close(TGL_TRIALS) || die;
+close(YUE_TRIALS) || die;
+
+if (system("utils/utt2spk_to_spk2utt.pl $out_dir_enroll/utt2spk >$out_dir_enroll/spk2utt") != 0) {
+  die "Error creating spk2utt file in directory $out_dir_enroll";
+}
+if (system("utils/utt2spk_to_spk2utt.pl $out_dir_test/utt2spk >$out_dir_test/spk2utt") != 0) {
+  die "Error creating spk2utt file in directory $out_dir_test";
+}
+if (system("utils/fix_data_dir.sh $out_dir_enroll") != 0) {
+  die "Error fixing data dir $out_dir_enroll";
+}
+if (system("utils/fix_data_dir.sh $out_dir_test") != 0) {
+  die "Error fixing data dir $out_dir_test";
+}
diff --git a/egs/sre16/v1/local/make_sre16_unlabeled.pl b/egs/sre16/v1/local/make_sre16_unlabeled.pl
new file mode 100755 (executable)
index 0000000..2de9d14
--- /dev/null
@@ -0,0 +1,90 @@
+#!/usr/bin/perl
+use warnings; #sed replacement for -w perl parameter
+# Copyright   2017   David Snyder
+# Apache 2.0
+
+if (@ARGV != 2) {
+  print STDERR "Usage: $0 <path-to-call-my-net-training-data> <path-to-output>\n";
+  print STDERR "e.g. $0 /export/corpora/SRE/LDC2016E46_SRE16_Call_My_Net_Training_Data data/\n";
+  exit(1);
+}
+
+($db_base, $out_dir) = @ARGV;
+
+# Handle major subset.
+$out_dir_major = "$out_dir/sre16_major";
+if (system("mkdir -p $out_dir_major")) {
+  die "Error making directory $out_dir_major";
+}
+
+$tmp_dir_major = "$out_dir_major/tmp";
+if (system("mkdir -p $tmp_dir_major") != 0) {
+  die "Error making directory $tmp_dir_major";
+}
+
+open(SPKR, ">$out_dir_major/utt2spk") || die "Could not open the output file $out_dir_major/utt2spk";
+open(WAV, ">$out_dir_major/wav.scp") || die "Could not open the output file $out_dir_major/wav.scp";
+
+if (system("find $db_base/data/unlabeled/major/ -name '*.sph' > $tmp_dir_major/sph.list") != 0) {
+  die "Error getting list of sph files";
+}
+
+open(WAVLIST, "<$tmp_dir_major/sph.list") or die "cannot open wav list";
+
+while(<WAVLIST>) {
+  chomp;
+  $sph = $_;
+  @t = split("/",$sph);
+  @t1 = split("[./]",$t[$#t]);
+  $utt=$t1[0];
+  print WAV "$utt"," sph2pipe -f wav -p -c 1 $sph |\n";
+  print SPKR "$utt $utt\n";
+}
+
+close(WAV) || die;
+close(SPKR) || die;
+
+# Handle minor subset.
+$out_dir_minor= "$out_dir/sre16_minor";
+if (system("mkdir -p $out_dir_minor")) {
+  die "Error making directory $out_dir_minor";
+}
+
+$tmp_dir_minor = "$out_dir_minor/tmp";
+if (system("mkdir -p $tmp_dir_minor") != 0) {
+  die "Error making directory $tmp_dir_minor";
+}
+
+open(SPKR, ">$out_dir_minor/utt2spk") || die "Could not open the output file $out_dir_minor/utt2spk";
+open(WAV, ">$out_dir_minor/wav.scp") || die "Could not open the output file $out_dir_minor/wav.scp";
+
+if (system("find $db_base/data/unlabeled/minor/ -name '*.sph' > $tmp_dir_minor/sph.list") != 0) {
+  die "Error getting list of sph files";
+}
+
+open(WAVLIST, "<$tmp_dir_minor/sph.list") or die "cannot open wav list";
+
+while(<WAVLIST>) {
+  chomp;
+  $sph = $_;
+  @t = split("/",$sph);
+  @t1 = split("[./]",$t[$#t]);
+  $utt=$t1[0];
+  print WAV "$utt"," sph2pipe -f wav -p -c 1 $sph |\n";
+  print SPKR "$utt $utt\n";
+}
+close(WAV) || die;
+close(SPKR) || die;
+
+if (system("utils/utt2spk_to_spk2utt.pl $out_dir_major/utt2spk >$out_dir_major/spk2utt") != 0) {
+  die "Error creating spk2utt file in directory $out_dir_major";
+}
+if (system("utils/utt2spk_to_spk2utt.pl $out_dir_minor/utt2spk >$out_dir_minor/spk2utt") != 0) {
+  die "Error creating spk2utt file in directory $out_dir_minor";
+}
+if (system("utils/fix_data_dir.sh $out_dir_major") != 0) {
+  die "Error fixing data dir $out_dir_major";
+}
+if (system("utils/fix_data_dir.sh $out_dir_minor") != 0) {
+  die "Error fixing data dir $out_dir_minor";
+}
diff --git a/egs/sre16/v1/local/make_swbd2_phase1.pl b/egs/sre16/v1/local/make_swbd2_phase1.pl
new file mode 100755 (executable)
index 0000000..71b26b5
--- /dev/null
@@ -0,0 +1,106 @@
+#!/usr/bin/perl
+use warnings; #sed replacement for -w perl parameter
+#
+# Copyright   2017   David Snyder
+# Apache 2.0
+
+if (@ARGV != 2) {
+  print STDERR "Usage: $0 <path-to-LDC98S75> <path-to-output>\n";
+  print STDERR "e.g. $0 /export/corpora3/LDC/LDC98S75 data/swbd2_phase1_train\n";
+  exit(1);
+}
+($db_base, $out_dir) = @ARGV;
+
+if (system("mkdir -p $out_dir")) {
+  die "Error making directory $out_dir";
+}
+
+open(CS, "<$db_base/doc/callstat.tbl") || die  "Could not open $db_base/doc/callstat.tbl";
+open(GNDR, ">$out_dir/spk2gender") || die "Could not open the output file $out_dir/spk2gender";
+open(SPKR, ">$out_dir/utt2spk") || die "Could not open the output file $out_dir/utt2spk";
+open(WAV, ">$out_dir/wav.scp") || die "Could not open the output file $out_dir/wav.scp";
+
+@badAudio = ("3", "4");
+
+$tmp_dir = "$out_dir/tmp";
+if (system("mkdir -p $tmp_dir") != 0) {
+  die "Error making directory $tmp_dir";
+}
+
+if (system("find $db_base -name '*.sph' > $tmp_dir/sph.list") != 0) {
+  die "Error getting list of sph files";
+}
+
+open(WAVLIST, "<$tmp_dir/sph.list") or die "cannot open wav list";
+
+%wavs = ();
+while(<WAVLIST>) {
+  chomp;
+  $sph = $_;
+  @t = split("/",$sph);
+  @t1 = split("[./]",$t[$#t]);
+  $uttId = $t1[0];
+  $wavs{$uttId} = $sph;
+}
+
+while (<CS>) {
+  $line = $_ ;
+  @A = split(",", $line);
+  @A1 = split("[./]",$A[0]);
+  $wav = $A1[0];
+  if (/$wav/i ~~ @badAudio) {
+    # do nothing
+    print "Bad Audio = $wav";
+  } else {
+    $spkr1= "sw_" . $A[2];
+    $spkr2= "sw_" . $A[3];
+    $gender1 = $A[5];
+    $gender2 = $A[6];
+    if ($gender1 eq "M") {
+      $gender1 = "m";
+    } elsif ($gender1 eq "F") {
+      $gender1 = "f";
+    } else {
+      die "Unknown Gender in $line";
+    }
+    if ($gender2 eq "M") {
+      $gender2 = "m";
+    } elsif ($gender2 eq "F") {
+      $gender2 = "f";
+    } else {
+      die "Unknown Gender in $line";
+    }
+    if (-e "$wavs{$wav}") {
+      $uttId = $spkr1 ."_" . $wav ."_1";
+      if (!$spk2gender{$spkr1}) {
+        $spk2gender{$spkr1} = $gender1;
+        print GNDR "$spkr1"," $gender1\n";
+      }
+      print WAV "$uttId"," sph2pipe -f wav -p -c 1 $wavs{$wav} |\n";
+      print SPKR "$uttId"," $spkr1","\n";
+
+      $uttId = $spkr2 . "_" . $wav ."_2";
+      if (!$spk2gender{$spkr2}) {
+        $spk2gender{$spkr2} = $gender2;
+        print GNDR "$spkr2"," $gender2\n";
+      }
+      print WAV "$uttId"," sph2pipe -f wav -p -c 2 $wavs{$wav} |\n";
+      print SPKR "$uttId"," $spkr2","\n";
+    } else {
+      print STDERR "Missing $wavs{$wav} for $wav\n";
+    }
+  }
+}
+
+close(WAV) || die;
+close(SPKR) || die;
+close(GNDR) || die;
+if (system("utils/utt2spk_to_spk2utt.pl $out_dir/utt2spk >$out_dir/spk2utt") != 0) {
+  die "Error creating spk2utt file in directory $out_dir";
+}
+if (system("utils/fix_data_dir.sh $out_dir") != 0) {
+  die "Error fixing data dir $out_dir";
+}
+if (system("utils/validate_data_dir.sh --no-text --no-feats $out_dir") != 0) {
+  die "Error validating directory $out_dir";
+}
diff --git a/egs/sre16/v1/local/make_swbd2_phase2.pl b/egs/sre16/v1/local/make_swbd2_phase2.pl
new file mode 100755 (executable)
index 0000000..05b2b1f
--- /dev/null
@@ -0,0 +1,107 @@
+#!/usr/bin/perl
+use warnings; #sed replacement for -w perl parameter
+#
+# Copyright   2013   Daniel Povey
+# Apache 2.0
+
+if (@ARGV != 2) {
+  print STDERR "Usage: $0 <path-to-LDC99S79> <path-to-output>\n";
+  print STDERR "e.g. $0 /export/corpora5/LDC/LDC99S79 data/swbd2_phase2_train\n";
+  exit(1);
+}
+($db_base, $out_dir) = @ARGV;
+
+if (system("mkdir -p $out_dir")) {
+  die "Error making directory $out_dir";
+}
+
+open(CS, "<$db_base/DISC1/doc/callstat.tbl") || die  "Could not open $db_base/DISC1/doc/callstat.tbl";
+open(CI, "<$db_base/DISC1/doc/callinfo.tbl") || die  "Could not open $db_base/DISC1/doc/callinfo.tbl";
+open(GNDR, ">$out_dir/spk2gender") || die "Could not open the output file $out_dir/spk2gender";
+open(SPKR, ">$out_dir/utt2spk") || die "Could not open the output file $out_dir/utt2spk";
+open(WAV, ">$out_dir/wav.scp") || die "Could not open the output file $out_dir/wav.scp";
+
+@badAudio = ("3", "4");
+
+$tmp_dir = "$out_base/tmp";
+if (system("mkdir -p $tmp_dir") != 0) {
+  die "Error making directory $tmp_dir";
+}
+
+if (system("find $db_base -name '*.sph' > $tmp_dir/sph.list") != 0) {
+  die "Error getting list of sph files";
+}
+
+open(WAVLIST, "<$tmp_dir/sph.list") or die "cannot open wav list";
+
+while(<WAVLIST>) {
+  chomp;
+  $sph = $_;
+  @t = split("/",$sph);
+  @t1 = split("[./]",$t[$#t]);
+  $uttId=$t1[0];
+  $wav{$uttId} = $sph;
+}
+
+while (<CS>) {
+  $line = $_ ;
+  $ci = <CI>;
+  $ci = <CI>;
+  @ci = split(",",$ci);
+  $wav = $ci[0];
+  @A = split(",", $line);
+  if (/$wav/i ~~ @badAudio) {
+    # do nothing
+  } else {
+    $spkr1= "sw_" . $A[2];
+    $spkr2= "sw_" . $A[3];
+    $gender1 = $A[4];
+    $gender2 = $A[5];
+    if ($gender1 eq "M") {
+      $gender1 = "m";
+    } elsif ($gender1 eq "F") {
+      $gender1 = "f";
+    } else {
+      die "Unknown Gender in $line";
+    }
+    if ($gender2 eq "M") {
+      $gender2 = "m";
+    } elsif ($gender2 eq "F") {
+      $gender2 = "f";
+    } else {
+      die "Unknown Gender in $line";
+    }
+    if (-e "$wav{$wav}") {
+      $uttId = $spkr1 ."_" . $wav ."_1";
+      if (!$spk2gender{$spkr1}) {
+        $spk2gender{$spkr1} = $gender1;
+        print GNDR "$spkr1"," $gender1\n";
+      }
+      print WAV "$uttId"," sph2pipe -f wav -p -c 1 $wav{$wav} |\n";
+      print SPKR "$uttId"," $spkr1","\n";
+
+      $uttId = $spkr2 . "_" . $wav ."_2";
+      if (!$spk2gender{$spkr2}) {
+        $spk2gender{$spkr2} = $gender2;
+        print GNDR "$spkr2"," $gender2\n";
+      }
+      print WAV "$uttId"," sph2pipe -f wav -p -c 2 $wav{$wav} |\n";
+      print SPKR "$uttId"," $spkr2","\n";
+    } else {
+      print STDERR "Missing $wav{$wav} for $wav\n";
+    }
+  }
+}
+
+close(WAV) || die;
+close(SPKR) || die;
+close(GNDR) || die;
+if (system("utils/utt2spk_to_spk2utt.pl $out_dir/utt2spk >$out_dir/spk2utt") != 0) {
+  die "Error creating spk2utt file in directory $out_dir";
+}
+if (system("utils/fix_data_dir.sh $out_dir") != 0) {
+  die "Error fixing data dir $out_dir";
+}
+if (system("utils/validate_data_dir.sh --no-text --no-feats $out_dir") != 0) {
+  die "Error validating directory $out_dir";
+}
diff --git a/egs/sre16/v1/local/make_swbd2_phase3.pl b/egs/sre16/v1/local/make_swbd2_phase3.pl
new file mode 100755 (executable)
index 0000000..ca70df3
--- /dev/null
@@ -0,0 +1,102 @@
+#!/usr/bin/perl
+use warnings; #sed replacement for -w perl parameter
+#
+# Copyright   2013   Daniel Povey
+# Apache 2.0
+
+if (@ARGV != 2) {
+  print STDERR "Usage: $0 <path-to-LDC2002S06> <path-to-output>\n";
+  print STDERR "e.g. $0 /export/corpora5/LDC/LDC2002S06 data/swbd2_phase3_train\n";
+  exit(1);
+}
+($db_base, $out_dir) = @ARGV;
+
+if (system("mkdir -p $out_dir")) {
+  die "Error making directory $out_dir";
+}
+
+open(CS, "<$db_base/DISC1/docs/callstat.tbl") || die  "Could not open $db_base/DISC1/docs/callstat.tbl";
+open(GNDR, ">$out_dir/spk2gender") || die "Could not open the output file $out_dir/spk2gender";
+open(SPKR, ">$out_dir/utt2spk") || die "Could not open the output file $out_dir/utt2spk";
+open(WAV, ">$out_dir/wav.scp") || die "Could not open the output file $out_dir/wav.scp";
+
+@badAudio = ("3", "4");
+
+$tmp_dir = "$out_base/tmp";
+if (system("mkdir -p $tmp_dir") != 0) {
+  die "Error making directory $tmp_dir";
+}
+
+if (system("find $db_base -name '*.sph' > $tmp_dir/sph.list") != 0) {
+  die "Error getting list of sph files";
+}
+
+open(WAVLIST, "<$tmp_dir/sph.list") or die "cannot open wav list";
+while(<WAVLIST>) {
+  chomp;
+  $sph = $_;
+  @t = split("/",$sph);
+  @t1 = split("[./]",$t[$#t]);
+  $uttId=$t1[0];
+  $wav{$uttId} = $sph;
+}
+
+while (<CS>) {
+  $line = $_ ;
+  @A = split(",", $line);
+  $wav = "sw_" . $A[0] ;
+  if (/$wav/i ~~ @badAudio) {
+    # do nothing
+  } else {
+    $spkr1= "sw_" . $A[3];
+    $spkr2= "sw_" . $A[4];
+    $gender1 = $A[5];
+    $gender2 = $A[6];
+    if ($gender1 eq "M") {
+      $gender1 = "m";
+    } elsif ($gender1 eq "F") {
+      $gender1 = "f";
+    } else {
+      die "Unknown Gender in $line";
+    }
+    if ($gender2 eq "M") {
+      $gender2 = "m";
+    } elsif ($gender2 eq "F") {
+      $gender2 = "f";
+    } else {
+      die "Unknown Gender in $line";
+    }
+    if (-e "$wav{$wav}") {
+      $uttId = $spkr1 ."_" . $wav ."_1";
+      if (!$spk2gender{$spkr1}) {
+        $spk2gender{$spkr1} = $gender1;
+        print GNDR "$spkr1"," $gender1\n";
+      }
+      print WAV "$uttId"," sph2pipe -f wav -p -c 1 $wav{$wav} |\n";
+      print SPKR "$uttId"," $spkr1","\n";
+
+      $uttId = $spkr2 . "_" . $wav ."_2";
+      if (!$spk2gender{$spkr2}) {
+        $spk2gender{$spkr2} = $gender2;
+        print GNDR "$spkr2"," $gender2\n";
+      }
+      print WAV "$uttId"," sph2pipe -f wav -p -c 2 $wav{$wav} |\n";
+      print SPKR "$uttId"," $spkr2","\n";
+    } else {
+      print STDERR "Missing $wav{$wav} for $wav\n";
+    }
+  }
+}
+
+close(WAV) || die;
+close(SPKR) || die;
+close(GNDR) || die;
+if (system("utils/utt2spk_to_spk2utt.pl $out_dir/utt2spk >$out_dir/spk2utt") != 0) {
+  die "Error creating spk2utt file in directory $out_dir";
+}
+if (system("utils/fix_data_dir.sh $out_dir") != 0) {
+  die "Error fixing data dir $out_dir";
+}
+if (system("utils/validate_data_dir.sh --no-text --no-feats $out_dir") != 0) {
+  die "Error validating directory $out_dir";
+}
diff --git a/egs/sre16/v1/local/make_swbd_cellular1.pl b/egs/sre16/v1/local/make_swbd_cellular1.pl
new file mode 100755 (executable)
index 0000000..e30c710
--- /dev/null
@@ -0,0 +1,83 @@
+#!/usr/bin/perl
+use warnings; #sed replacement for -w perl parameter
+#
+# Copyright   2013   Daniel Povey
+# Apache 2.0
+
+if (@ARGV != 2) {
+  print STDERR "Usage: $0 <path-to-LDC2001S13> <path-to-output>\n";
+  print STDERR "e.g. $0 /export/corpora5/LDC/LDC2001S13 data/swbd_cellular1_train\n";
+  exit(1);
+}
+($db_base, $out_dir) = @ARGV;
+
+if (system("mkdir -p $out_dir")) {
+  die "Error making directory $out_dir";
+}
+
+open(CS, "<$db_base/doc/swb_callstats.tbl") || die  "Could not open $db_base/doc/swb_callstats.tbl";
+open(GNDR, ">$out_dir/spk2gender") || die "Could not open the output file $out_dir/spk2gender";
+open(SPKR, ">$out_dir/utt2spk") || die "Could not open the output file $out_dir/utt2spk";
+open(WAV, ">$out_dir/wav.scp") || die "Could not open the output file $out_dir/wav.scp";
+
+@badAudio = ("40019", "45024", "40022");
+
+while (<CS>) {
+  $line = $_ ;
+  @A = split(",", $line);
+  if (/$A[0]/i ~~ @badAudio) {
+    # do nothing
+  } else {
+    $wav = "sw_" . $A[0];
+    $spkr1= "sw_" . $A[1];
+    $spkr2= "sw_" . $A[2];
+    $gender1 = $A[3];
+    $gender2 = $A[4];
+    if ($A[3] eq "M") {
+      $gender1 = "m";
+    } elsif ($A[3] eq "F") {
+      $gender1 = "f";
+    } else {
+      die "Unknown Gender in $line";
+    }
+    if ($A[4] eq "M") {
+      $gender2 = "m";
+    } elsif ($A[4] eq "F") {
+      $gender2 = "f";
+    } else {
+      die "Unknown Gender in $line";
+    }
+    if (-e "$db_base/$wav.sph") {
+      $uttId = $spkr1 . "-swbdc_" . $wav ."_1";
+      if (!$spk2gender{$spkr1}) {
+        $spk2gender{$spkr1} = $gender1;
+        print GNDR "$spkr1"," $gender1\n";
+      }
+      print WAV "$uttId"," sph2pipe -f wav -p -c 1 $db_base/$wav.sph |\n";
+      print SPKR "$uttId"," $spkr1","\n";
+
+      $uttId = $spkr2 . "-swbdc_" . $wav ."_2";
+      if (!$spk2gender{$spkr2}) {
+        $spk2gender{$spkr2} = $gender2;
+        print GNDR "$spkr2"," $gender2\n";
+      }
+      print WAV "$uttId"," sph2pipe -f wav -p -c 2 $db_base/$wav.sph |\n";
+      print SPKR "$uttId"," $spkr2","\n";
+    } else {
+      print STDERR "Missing $db_base/$wav.sph\n";
+    }
+  }
+}
+
+close(WAV) || die;
+close(SPKR) || die;
+close(GNDR) || die;
+if (system("utils/utt2spk_to_spk2utt.pl $out_dir/utt2spk >$out_dir/spk2utt") != 0) {
+  die "Error creating spk2utt file in directory $out_dir";
+}
+if (system("utils/fix_data_dir.sh $out_dir") != 0) {
+  die "Error fixing data dir $out_dir";
+}
+if (system("utils/validate_data_dir.sh --no-text --no-feats $out_dir") != 0) {
+  die "Error validating directory $out_dir";
+}
diff --git a/egs/sre16/v1/local/make_swbd_cellular2.pl b/egs/sre16/v1/local/make_swbd_cellular2.pl
new file mode 100755 (executable)
index 0000000..4de954c
--- /dev/null
@@ -0,0 +1,83 @@
+#!/usr/bin/perl
+use warnings; #sed replacement for -w perl parameter
+#
+# Copyright   2013   Daniel Povey
+# Apache 2.0
+
+if (@ARGV != 2) {
+  print STDERR "Usage: $0 <path-to-LDC2004S07> <path-to-output>\n";
+  print STDERR "e.g. $0 /export/corpora5/LDC/LDC2004S07 data/swbd_cellular2_train\n";
+  exit(1);
+}
+($db_base, $out_dir) = @ARGV;
+
+if (system("mkdir -p $out_dir")) {
+  die "Error making directory $out_dir";
+}
+
+open(CS, "<$db_base/docs/swb_callstats.tbl") || die  "Could not open $db_base/docs/swb_callstats.tbl";
+open(GNDR, ">$out_dir/spk2gender") || die "Could not open the output file $out_dir/spk2gender";
+open(SPKR, ">$out_dir/utt2spk") || die "Could not open the output file $out_dir/utt2spk";
+open(WAV, ">$out_dir/wav.scp") || die "Could not open the output file $out_dir/wav.scp";
+
+@badAudio=("45024", "40022");
+
+while (<CS>) {
+  $line = $_ ;
+  @A = split(",", $line);
+  if (/$A[0]/i ~~ @badAudio) {
+    # do nothing
+  } else {
+    $wav = "sw_" . $A[0];
+    $spkr1= "sw_" . $A[1];
+    $spkr2= "sw_" . $A[2];
+    $gender1 = $A[3];
+    $gender2 = $A[4];
+    if ($A[3] eq "M") {
+      $gender1 = "m";
+    } elsif ($A[3] eq "F") {
+      $gender1 = "f";
+    } else {
+      die "Unknown Gender in $line";
+    }
+    if ($A[4] eq "M") {
+      $gender2 = "m";
+    } elsif ($A[4] eq "F") {
+      $gender2 = "f";
+    } else {
+      die "Unknown Gender in $line";
+    }
+    if (-e "$db_base/data/$wav.sph") {
+      $uttId = $spkr1 . "-swbdc_" . $wav ."_1";
+      if (!$spk2gender{$spkr1}) {
+        $spk2gender{$spkr1} = $gender1;
+        print GNDR "$spkr1"," $gender1\n";
+      }
+      print WAV "$uttId"," sph2pipe -f wav -p -c 1 $db_base/data/$wav.sph |\n";
+      print SPKR "$uttId"," $spkr1","\n";
+
+      $uttId = $spkr2 . "-swbdc_" . $wav ."_2";
+      if (!$spk2gender{$spkr2}) {
+        $spk2gender{$spkr2} = $gender2;
+        print GNDR "$spkr2"," $gender2\n";
+      }
+      print WAV "$uttId"," sph2pipe -f wav -p -c 2 $db_base/data/$wav.sph |\n";
+      print SPKR "$uttId"," $spkr2","\n";
+    } else {
+      print STDERR "Missing $db_base/data/$wav.sph\n";
+    }
+  }
+}
+
+close(WAV) || die;
+close(SPKR) || die;
+close(GNDR) || die;
+if (system("utils/utt2spk_to_spk2utt.pl $out_dir/utt2spk >$out_dir/spk2utt") != 0) {
+  die "Error creating spk2utt file in directory $out_dir";
+}
+if (system("utils/fix_data_dir.sh $out_dir") != 0) {
+  die "Error fixing data dir $out_dir";
+}
+if (system("utils/validate_data_dir.sh --no-text --no-feats $out_dir") != 0) {
+  die "Error validating directory $out_dir";
+}
diff --git a/egs/sre16/v1/local/nnet3/xvector/prepare_feats_for_egs.sh b/egs/sre16/v1/local/nnet3/xvector/prepare_feats_for_egs.sh
new file mode 100755 (executable)
index 0000000..9f132bd
--- /dev/null
@@ -0,0 +1,70 @@
+#!/bin/bash
+#
+# Apache 2.0.
+
+# This script applies sliding window cmvn and removes silence frames.  This
+# is performed on the raw features prior to generating examples for training
+# the xvector system.
+
+nj=40
+cmd="run.pl"
+stage=0
+norm_vars=false
+center=true
+compress=true
+cmn_window=300
+
+echo "$0 $@"  # Print the command line for logging
+
+if [ -f path.sh ]; then . ./path.sh; fi
+. parse_options.sh || exit 1;
+if [ $# != 3 ]; then
+  echo "Usage: $0 <in-data-dir> <out-data-dir> <feat-dir>"
+  echo "e.g.: $0 data/train data/train_no_sil exp/make_xvector_features"
+  echo "Options: "
+  echo "  --nj <nj>                                        # number of parallel jobs"
+  echo "  --cmd (utils/run.pl|utils/queue.pl <queue opts>) # how to run jobs."
+  echo "  --norm-vars <true|false>                         # If true, normalize variances in the sliding window cmvn"
+  exit 1;
+fi
+
+data_in=$1
+data_out=$2
+dir=$3
+
+name=`basename $data_in`
+
+for f in $data_in/feats.scp $data_in/vad.scp ; do
+  [ ! -f $f ] && echo "$0: No such file $f" && exit 1;
+done
+
+# Set various variables.
+mkdir -p $dir/log
+mkdir -p $data_out
+featdir=${PWD}/$dir
+
+cp $data_in/utt2spk $data_out/utt2spk
+cp $data_in/spk2utt $data_out/spk2utt
+cp $data_in/wav.scp $data_out/wav.scp
+
+for n in $(seq $nj); do
+  # the next command does nothing unless $featdir/storage/ exists, see
+  # utils/create_data_link.pl for more info.
+  utils/create_data_link.pl $featdir/xvector_feats_${name}.$n.ark
+done
+
+sdata_in=$data_in/split$nj;
+utils/split_data.sh $data_in $nj || exit 1;
+
+$cmd JOB=1:$nj $dir/log/create_xvector_feats_${name}.JOB.log \
+  apply-cmvn-sliding --norm-vars=false --center=true --cmn-window=$cmn_window \
+  scp:${sdata_in}/JOB/feats.scp ark:- \| \
+  select-voiced-frames ark:- scp,s,cs:${sdata_in}/JOB/vad.scp ark:- \| \
+  copy-feats --compress=$compress ark:- \
+  ark,scp:$featdir/xvector_feats_${name}.JOB.ark,$featdir/xvector_feats_${name}.JOB.scp || exit 1;
+
+for n in $(seq $nj); do
+  cat $featdir/xvector_feats_${name}.$n.scp || exit 1;
+done > ${data_out}/feats.scp || exit 1
+
+echo "$0: Succeeded creating xvector features for $name"
diff --git a/egs/sre16/v1/local/nnet3/xvector/run_xvector.sh b/egs/sre16/v1/local/nnet3/xvector/run_xvector.sh
new file mode 120000 (symlink)
index 0000000..585b63f
--- /dev/null
@@ -0,0 +1 @@
+tuning/run_xvector_1a.sh
\ No newline at end of file
diff --git a/egs/sre16/v1/local/nnet3/xvector/tuning/run_xvector_1a.sh b/egs/sre16/v1/local/nnet3/xvector/tuning/run_xvector_1a.sh
new file mode 100755 (executable)
index 0000000..6e87b30
--- /dev/null
@@ -0,0 +1,152 @@
+#!/bin/bash
+# Copyright      2017   David Snyder
+#                2017   Johns Hopkins University (Author: Daniel Garcia-Romero)
+#                2017   Johns Hopkins University (Author: Daniel Povey)
+# Apache 2.0.
+
+# This script trains a DNN similar to the recipe described in
+# http://www.danielpovey.com/files/2017_interspeech_embeddings.pdf .
+
+. ./cmd.sh
+set -e
+
+stage=1
+train_stage=0
+use_gpu=true
+remove_egs=false
+
+data=data/train
+nnet_dir=exp/xvector_nnet_1a/
+egs_dir=exp/xvector_nnet_1a/egs
+
+. ./path.sh
+. ./cmd.sh
+. ./utils/parse_options.sh
+
+num_pdfs=$(awk '{print $2}' $data/utt2spk | sort | uniq -c | wc -l)
+
+# Now we create the nnet examples using sid/nnet3/xvector/get_egs.sh.
+# The argument --num-repeats is related to the number of times a speaker
+# repeats per archive.  If it seems like you're getting too many archives
+# (e.g., more than 200) try increasing the --frames-per-iter option.  The
+# arguments --min-frames-per-chunk and --max-frames-per-chunk specify the
+# minimum and maximum length (in terms of number of frames) of the features
+# in the examples.
+#
+# To make sense of the egs script, it may be necessary to put an "exit 1"
+# command immediately after stage 3.  Then, inspect
+# exp/<your-dir>/egs/temp/ranges.* . The ranges files specify the examples that
+# will be created, and which archives they will be stored in.  Each line of
+# ranges.* has the following form:
+#    <utt-id> <local-ark-indx> <global-ark-indx> <start-frame> <end-frame> <spk-id>
+# For example:
+#    100304-f-sre2006-kacg-A 1 2 4079 881 23
+
+# If you're satisfied with the number of archives (e.g., 50-150 archives is
+# reasonable) and with the number of examples per speaker (e.g., 1000-5000
+# is reasonable) then you can let the script continue to the later stages.
+# Otherwise, try increasing or decreasing the --num-repeats option.  You might
+# need to fiddle with --frames-per-iter.  Increasing this value decreases the
+# the number of archives and increases the number of examples per archive.
+# Decreasing this value increases the number of archives, while decreasing the
+# number of examples per archive.
+if [ $stage -le 4 ]; then
+  echo "$0: Getting neural network training egs";
+  # dump egs.
+  if [[ $(hostname -f) == *.clsp.jhu.edu ]] && [ ! -d $dir/egs/storage ]; then
+    utils/create_split_dir.pl \
+     /export/b{03,04,05,06}/$USER/kaldi-data/egs/sre16/v2/xvector-$(date +'%m_%d_%H_%M')/$egs_dir/storage $egs_dir/storage
+  fi
+  sid/nnet3/xvector/get_egs.sh --cmd "$train_cmd" \
+    --nj 8 \
+    --stage 0 \
+    --frames-per-iter 1000000000 \
+    --frames-per-iter-diagnostic 100000 \
+    --min-frames-per-chunk 200 \
+    --max-frames-per-chunk 400 \
+    --num-diagnostic-archives 3 \
+    --num-repeats 35 \
+    "$data" $egs_dir
+fi
+
+if [ $stage -le 5 ]; then
+  echo "$0: creating neural net configs using the xconfig parser";
+  num_targets=$(wc -w $egs_dir/pdf2num | awk '{print $1}')
+  feat_dim=$(cat $egs_dir/info/feat_dim)
+
+  # This chunk-size corresponds to the maximum number of frames the
+  # stats layer is able to pool over.  In this script, it corresponds
+  # to 100 seconds.  If the input recording is greater than 100 seconds,
+  # we will compute multiple xvectors from the same recording and average
+  # to produce the final xvector.
+  max_chunk_size=10000
+
+  # The smallest number of frames we're comfortable computing an xvector from.
+  # Note that the hard minimum is given by the left and right context of the
+  # frame-level layers.
+  min_chunk_size=25
+  mkdir -p $nnet_dir/configs
+  cat <<EOF > $nnet_dir/configs/network.xconfig
+  # please note that it is important to have input layer with the name=input
+
+  # The frame-level layers
+  input dim=${feat_dim} name=input
+  relu-batchnorm-layer name=tdnn1 input=Append(-2,-1,0,1,2) dim=512
+  relu-batchnorm-layer name=tdnn2 input=Append(-2,0,2) dim=512
+  relu-batchnorm-layer name=tdnn3 input=Append(-3,0,3) dim=512
+  relu-batchnorm-layer name=tdnn4 dim=512
+  relu-batchnorm-layer name=tdnn5 dim=1500
+
+  # The stats pooling layer. Layers after this are segment-level.
+  # In the config below, the first and last argument (0, and ${max_chunk_size})
+  # means that we pool over an input segment starting at frame 0
+  # and ending at frame ${max_chunk_size} or earlier.  The other arguments (1:1)
+  # mean that no subsampling is performed.
+  stats-layer name=stats config=mean+stddev(0:1:1:${max_chunk_size})
+
+  # This is where we usually extract the embedding (aka xvector) from.
+  relu-batchnorm-layer name=tdnn6 dim=512 input=stats
+
+  # This is where another layer the embedding could be extracted
+  # from, but usually the previous one works better.
+  relu-batchnorm-layer name=tdnn7 dim=512
+  output-layer name=output include-log-softmax=true dim=${num_targets}
+EOF
+
+  steps/nnet3/xconfig_to_configs.py \
+      --xconfig-file $nnet_dir/configs/network.xconfig \
+      --config-dir $nnet_dir/configs/
+  cp $nnet_dir/configs/final.config $nnet_dir/nnet.config
+
+  # These three files will be used by sid/nnet3/xvector/extract_xvectors.sh
+  echo "output-node name=output input=tdnn6.affine" > $nnet_dir/extract.config
+  echo "$max_chunk_size" > $nnet_dir/max_chunk_size
+  echo "$min_chunk_size" > $nnet_dir/min_chunk_size
+fi
+
+dropout_schedule='0,0@0.20,0.1@0.50,0'
+srand=123
+if [ $stage -le 6 ]; then
+  steps/nnet3/train_raw_dnn.py --stage=$train_stage \
+    --cmd="$train_cmd" \
+    --trainer.optimization.proportional-shrink 10 \
+    --trainer.optimization.momentum=0.5 \
+    --trainer.optimization.num-jobs-initial=3 \
+    --trainer.optimization.num-jobs-final=8 \
+    --trainer.optimization.initial-effective-lrate=0.001 \
+    --trainer.optimization.final-effective-lrate=0.0001 \
+    --trainer.optimization.minibatch-size=64 \
+    --trainer.srand=$srand \
+    --trainer.max-param-change=2 \
+    --trainer.num-epochs=3 \
+    --trainer.dropout-schedule="$dropout_schedule" \
+    --trainer.shuffle-buffer-size=1000 \
+    --egs.frames-per-eg=1 \
+    --egs.dir="$egs_dir" \
+    --cleanup.remove-egs $remove_egs \
+    --cleanup.preserve-model-interval=10 \
+    --use-gpu=true \
+    --dir=$nnet_dir  || exit 1;
+fi
+
+exit 0;
diff --git a/egs/sre16/v1/path.sh b/egs/sre16/v1/path.sh
new file mode 100755 (executable)
index 0000000..e50f57c
--- /dev/null
@@ -0,0 +1,5 @@
+export KALDI_ROOT=`pwd`/../../..
+export PATH=$PWD/utils/:$KALDI_ROOT/tools/openfst/bin:$KALDI_ROOT/tools/sph2pipe_v2.5:$PWD:$PATH
+[ ! -f $KALDI_ROOT/tools/config/common_path.sh ] && echo >&2 "The standard file $KALDI_ROOT/tools/config/common_path.sh is not present -> Exit!" && exit 1
+. $KALDI_ROOT/tools/config/common_path.sh
+export LC_ALL=C
diff --git a/egs/sre16/v1/run.sh b/egs/sre16/v1/run.sh
new file mode 100755 (executable)
index 0000000..3ab81d2
--- /dev/null
@@ -0,0 +1,289 @@
+#!/bin/bash
+# Copyright      2017   David Snyder
+#                2017   Johns Hopkins University (Author: Daniel Garcia-Romero)
+#                2017   Johns Hopkins University (Author: Daniel Povey)
+# Apache 2.0.
+#
+# See README.txt for more info on data required.
+# Results (mostly EERs) are inline in comments below.
+#
+# This example demonstrates a "bare bones" NIST SRE 2016 recipe using ivectors.
+# In the future, we will add score-normalization and a more effective form of
+# PLDA domain adaptation.
+
+. cmd.sh
+. path.sh
+set -e
+mfccdir=`pwd`/mfcc
+vaddir=`pwd`/mfcc
+
+# SRE16 trials
+sre16_trials=data/sre16_eval_test/trials
+sre16_trials_tgl=data/sre16_eval_test/trials_tgl
+sre16_trials_yue=data/sre16_eval_test/trials_yue
+
+stage=0
+if [ $stage -le 0 ]; then
+  # Path to some, but not all of the training corpora
+  data_root=/export/corpora/LDC
+
+  # Prepare telephone and microphone speech from Mixer6.
+  local/make_mx6.sh $data_root/LDC2013S03 data/
+
+  # Prepare SRE10 test and enroll. Includes microphone interview speech.
+  # NOTE: This corpus is now available through the LDC as LDC2017S06.
+  local/make_sre10.pl /export/corpora5/SRE/SRE2010/eval/ data/
+
+  # Prepare SRE08 test and enroll. Includes some microphone speech.
+  local/make_sre08.pl $data_root/LDC2011S08 $data_root/LDC2011S05 data/
+
+  # This prepares the older NIST SREs from 2004-2006.
+  local/make_sre.sh $data_root data/
+
+  # Combine all SREs prior to 2016 and Mixer6 into one dataset
+  utils/combine_data.sh data/sre \
+    data/sre2004 data/sre2005_train \
+    data/sre2005_test data/sre2006_train \
+    data/sre2006_test_1 data/sre2006_test_2 \
+    data/sre08 data/mx6 data/sre10
+  utils/validate_data_dir.sh --no-text --no-feats data/sre
+  utils/fix_data_dir.sh data/sre
+
+  # Prepare SWBD corpora.
+  local/make_swbd_cellular1.pl $data_root/LDC2001S13 \
+    data/swbd_cellular1_train
+  local/make_swbd_cellular2.pl /export/corpora5/LDC/LDC2004S07 \
+    data/swbd_cellular2_train
+  local/make_swbd2_phase1.pl $data_root/LDC98S75 \
+    data/swbd2_phase1_train
+  local/make_swbd2_phase2.pl /export/corpora5/LDC/LDC99S79 \
+    data/swbd2_phase2_train
+  local/make_swbd2_phase3.pl /export/corpora5/LDC/LDC2002S06 \
+    data/swbd2_phase3_train
+
+  # Combine all SWB corpora into one dataset.
+  utils/combine_data.sh data/swbd \
+    data/swbd_cellular1_train data/swbd_cellular2_train \
+    data/swbd2_phase1_train data/swbd2_phase2_train data/swbd2_phase3_train
+
+  # Prepare NIST SRE 2016 evaluation data.
+  local/make_sre16_eval.pl /export/corpora5/SRE/R149_0_1 data
+
+  # Prepare unlabeled Cantonese and Tagalog development data. This dataset
+  # was distributed to SRE participants.
+  local/make_sre16_unlabeled.pl /export/corpora5/SRE/LDC2016E46_SRE16_Call_My_Net_Training_Data data
+fi
+
+if [ $stage -le 1 ]; then
+  # Make MFCCs and compute the energy-based VAD for each dataset
+  for name in sre swbd sre16_eval_enroll sre16_eval_test sre16_major; do
+    steps/make_mfcc.sh --mfcc-config conf/mfcc.conf --nj 40 --cmd "$train_cmd" \
+      data/${name} exp/make_mfcc $mfccdir
+    utils/fix_data_dir.sh data/${name}
+    sid/compute_vad_decision.sh --nj 40 --cmd "$train_cmd" \
+      data/${name} exp/make_vad $vaddir
+    utils/fix_data_dir.sh data/${name}
+  done
+fi
+
+if [ $stage -le 2 ]; then
+  # Train the UBM.
+  sid/train_diag_ubm.sh --cmd "$train_cmd --mem 20G" \
+    --nj 40 --num-threads 8  --subsample 1 \
+    data/sre16_major 2048 \
+    exp/diag_ubm
+
+  sid/train_full_ubm.sh --cmd "$train_cmd --mem 25G" \
+    --nj 40 --remove-low-count-gaussians false --subsample 1 \
+    data/sre16_major \
+    exp/diag_ubm exp/full_ubm
+fi
+
+if [ $stage -le 3 ]; then
+  # Train the i-vector extractor.
+  utils/combine_data.sh data/swbd_sre data/swbd data/sre
+  sid/train_ivector_extractor.sh --cmd "$train_cmd --mem 35G" \
+    --ivector-dim 600 \
+    --num-iters 5 \
+    exp/full_ubm/final.ubm data/swbd_sre \
+    exp/extractor
+fi
+
+# In this section, we augment the SRE data with reverberation,
+# noise, music, and babble, and combined it with the clean SRE
+# data.  The combined list will be used to train the PLDA model.
+if [ $stage -le 4 ]; then
+  utils/data/get_utt2num_frames.sh --nj 40 --cmd "$train_cmd" data/sre
+  frame_shift=0.01
+  awk -v frame_shift=$frame_shift '{print $1, $2*frame_shift;}' data/sre/utt2num_frames > data/sre/reco2dur
+
+  if [ ! -d "RIRS_NOISES" ]; then
+    # Download the package that includes the real RIRs, simulated RIRs, isotropic noises and point-source noises
+    wget --no-check-certificate http://www.openslr.org/resources/28/rirs_noises.zip
+    unzip rirs_noises.zip
+  fi
+
+  # Make a version with reverberated speech
+  rvb_opts=()
+  rvb_opts+=(--rir-set-parameters "0.5, RIRS_NOISES/simulated_rirs/smallroom/rir_list")
+  rvb_opts+=(--rir-set-parameters "0.5, RIRS_NOISES/simulated_rirs/mediumroom/rir_list")
+
+  # Make a reverberated version of the SRE list.  Note that we don't add any
+  # additive noise here.
+  python steps/data/reverberate_data_dir.py \
+    "${rvb_opts[@]}" \
+    --speech-rvb-probability 1 \
+    --pointsource-noise-addition-probability 0 \
+    --isotropic-noise-addition-probability 0 \
+    --num-replications 1 \
+    --source-sampling-rate 8000 \
+    data/sre data/sre_reverb
+  cp data/sre/vad.scp data/sre_reverb/
+  utils/copy_data_dir.sh --utt-suffix "-reverb" data/sre_reverb data/sre_reverb.new
+  rm -rf data/sre_reverb
+  mv data/sre_reverb.new data/sre_reverb
+
+  # Prepare the MUSAN corpus, which consists of music, speech, and noise
+  # suitable for augmentation.
+  local/make_musan.sh /export/corpora/JHU/musan data
+
+  # Get the duration of the MUSAN recordings.  This will be used by the
+  # script augment_data_dir.py.
+  for name in speech noise music; do
+    utils/data/get_utt2dur.sh data/musan_${name}
+    mv data/musan_${name}/utt2dur data/musan_${name}/reco2dur
+  done
+
+  # Augment with musan_noise
+  python steps/data/augment_data_dir.py --utt-suffix "noise" --fg-interval 1 --fg-snrs "15:10:5:0" --fg-noise-dir "data/musan_noise" data/sre data/sre_noise
+  # Augment with musan_music
+  python steps/data/augment_data_dir.py --utt-suffix "music" --bg-snrs "15:10:8:5" --num-bg-noises "1" --bg-noise-dir "data/musan_music" data/sre data/sre_music
+  # Augment with musan_speech
+  python steps/data/augment_data_dir.py --utt-suffix "babble" --bg-snrs "20:17:15:13" --num-bg-noises "3:4:5:6:7" --bg-noise-dir "data/musan_speech" data/sre data/sre_babble
+
+  # Combine reverb, noise, music, and babble into one directory.
+  utils/combine_data.sh data/sre_aug data/sre_reverb data/sre_noise data/sre_music data/sre_babble
+
+  # Take a random subset of the augmentations (64k is roughly the size of the SRE dataset)
+  utils/subset_data_dir.sh data/sre_aug 64000 data/sre_aug_64k
+  utils/fix_data_dir.sh data/sre_aug_64k
+
+  # Make MFCCs for the augmented data.  Note that we want we should alreay have the vad.scp
+  # from the clean version at this point, which is identical to the clean version!
+  steps/make_mfcc.sh --mfcc-config conf/mfcc.conf --nj 40 --cmd "$train_cmd" \
+    data/sre_aug_64k exp/make_mfcc $mfccdir
+
+  # Combine the clean and augmented SRE list.  This is now roughly
+  # double the size of the original clean list.
+  utils/combine_data.sh data/sre_combined data/sre_aug_64k data/sre
+fi
+
+if [ $stage -le 5 ]; then
+  # Extract i-vectors for SRE data (includes Mixer 6). We'll use this for
+  # things like LDA or PLDA.
+  sid/extract_ivectors.sh --cmd "$train_cmd --mem 6G" --nj 40 \
+    exp/extractor data/sre_combined \
+    exp/ivectors_sre_combined
+
+  # The SRE16 major is an unlabeled dataset consisting of Cantonese and
+  # and Tagalog.  This is useful for things like centering, whitening and
+  # score normalization.
+  sid/extract_ivectors.sh --cmd "$train_cmd --mem 6G" --nj 40 \
+    exp/extractor data/sre16_major \
+    exp/ivectors_sre16_major
+
+  # The SRE16 test data
+  sid/extract_ivectors.sh --cmd "$train_cmd --mem 6G" --nj 40 \
+    exp/extractor data/sre16_eval_test \
+    exp/ivectors_sre16_eval_test
+
+  # The SRE16 enroll data
+  sid/extract_ivectors.sh --cmd "$train_cmd --mem 6G" --nj 40 \
+    exp/extractor data/sre16_eval_enroll \
+    exp/ivectors_sre16_eval_enroll
+fi
+
+if [ $stage -le 6 ]; then
+  # Compute the mean vector for centering the evaluation i-vectors.
+  $train_cmd exp/ivectors_sre16_major/log/compute_mean.log \
+    ivector-mean scp:exp/ivectors_sre16_major/ivector.scp \
+    exp/ivectors_sre16_major/mean.vec || exit 1;
+
+  # This script uses LDA to decrease the dimensionality prior to PLDA.
+  lda_dim=200
+  $train_cmd exp/ivectors_sre_combined/log/lda.log \
+    ivector-compute-lda --total-covariance-factor=0.0 --dim=$lda_dim \
+    "ark:ivector-subtract-global-mean scp:exp/ivectors_sre_combined/ivector.scp ark:- |" \
+    ark:data/sre_combined/utt2spk exp/ivectors_sre_combined/transform.mat || exit 1;
+
+  #  Train the PLDA model.
+  $train_cmd exp/ivectors_sre_combined/log/plda.log \
+    ivector-compute-plda ark:data/sre_combined/spk2utt \
+    "ark:ivector-subtract-global-mean scp:exp/ivectors_sre_combined/ivector.scp ark:- | transform-vec exp/ivectors_sre_combined/transform.mat ark:- ark:- | ivector-normalize-length ark:-  ark:- |" \
+    exp/ivectors_sre_combined/plda || exit 1;
+
+  # Here we adapt the out-of-domain PLDA model to SRE16 major, a pile
+  # of unlabeled in-domain data.  In the future, we will include a clustering
+  # based approach for domain adaptation.
+  $train_cmd exp/ivectors_sre16_major/log/plda_adapt.log \
+    ivector-adapt-plda --within-covar-scale=0.75 --between-covar-scale=0.25 \
+    exp/ivectors_sre_combined/plda \
+    "ark:ivector-subtract-global-mean scp:exp/ivectors_sre16_major/ivector.scp ark:- | transform-vec exp/ivectors_sre_combined/transform.mat ark:- ark:- | ivector-normalize-length ark:- ark:- |" \
+    exp/ivectors_sre16_major/plda_adapt || exit 1;
+fi
+
+if [ $stage -le 7 ]; then
+  # Get results using the out-of-domain PLDA model
+  $train_cmd exp/scores/log/sre16_eval_scoring.log \
+    ivector-plda-scoring --normalize-length=true \
+    --num-utts=ark:exp/ivectors_sre16_eval_enroll/num_utts.ark \
+    "ivector-copy-plda --smoothing=0.0 exp/ivectors_sre_combined/plda - |" \
+    "ark:ivector-mean ark:data/sre16_eval_enroll/spk2utt scp:exp/ivectors_sre16_eval_enroll/ivector.scp ark:- | ivector-subtract-global-mean exp/ivectors_sre16_major/mean.vec ark:- ark:- | transform-vec exp/ivectors_sre_combined/transform.mat ark:- ark:- | ivector-normalize-length ark:- ark:- |" \
+    "ark:ivector-subtract-global-mean exp/ivectors_sre16_major/mean.vec scp:exp/ivectors_sre16_eval_test/ivector.scp ark:- | transform-vec exp/ivectors_sre_combined/transform.mat ark:- ark:- | ivector-normalize-length ark:- ark:- |" \
+    "cat '$sre16_trials' | cut -d\  --fields=1,2 |" exp/scores/sre16_eval_scores || exit 1;
+
+  utils/filter_scp.pl $sre16_trials_tgl exp/scores/sre16_eval_scores > exp/scores/sre16_eval_tgl_scores
+  utils/filter_scp.pl $sre16_trials_yue exp/scores/sre16_eval_scores > exp/scores/sre16_eval_yue_scores
+  pooled_eer=$(paste $sre16_trials exp/scores/sre16_eval_scores | awk '{print $6, $3}' | compute-eer - 2>/dev/null)
+  tgl_eer=$(paste $sre16_trials_tgl exp/scores/sre16_eval_tgl_scores | awk '{print $6, $3}' | compute-eer - 2>/dev/null)
+  yue_eer=$(paste $sre16_trials_yue exp/scores/sre16_eval_yue_scores | awk '{print $6, $3}' | compute-eer - 2>/dev/null)
+  echo "Using Out-of-Domain PLDA, EER: Pooled ${pooled_eer}%, Tagalog ${tgl_eer}%, Cantonese ${yue_eer}%"
+  # EER: Pooled 13.65%, Tagalog 17.73%, Cantonese 9.612%
+fi
+
+if [ $stage -le 8 ]; then
+  # Get results using an adapted PLDA model. In the future we'll replace
+  # this (or add to this) with a clustering based approach to PLDA adaptation.
+  $train_cmd exp/scores/log/sre16_eval_scoring_adapt.log \
+    ivector-plda-scoring --normalize-length=true \
+    --num-utts=ark:exp/ivectors_sre16_eval_enroll/num_utts.ark \
+    "ivector-copy-plda --smoothing=0.0 exp/ivectors_sre16_major/plda_adapt - |" \
+    "ark:ivector-mean ark:data/sre16_eval_enroll/spk2utt scp:exp/ivectors_sre16_eval_enroll/ivector.scp ark:- | ivector-subtract-global-mean exp/ivectors_sre16_major/mean.vec ark:- ark:- | transform-vec exp/ivectors_sre_combined/transform.mat ark:- ark:- | ivector-normalize-length ark:- ark:- |" \
+    "ark:ivector-subtract-global-mean exp/ivectors_sre16_major/mean.vec scp:exp/ivectors_sre16_eval_test/ivector.scp ark:- | transform-vec exp/ivectors_sre_combined/transform.mat ark:- ark:- | ivector-normalize-length ark:- ark:- |" \
+    "cat '$sre16_trials' | cut -d\  --fields=1,2 |" exp/scores/sre16_eval_scores_adapt || exit 1;
+
+  utils/filter_scp.pl $sre16_trials_tgl exp/scores/sre16_eval_scores_adapt > exp/scores/sre16_eval_tgl_scores_adapt
+  utils/filter_scp.pl $sre16_trials_yue exp/scores/sre16_eval_scores_adapt > exp/scores/sre16_eval_yue_scores_adapt
+  pooled_eer=$(paste $sre16_trials exp/scores/sre16_eval_scores_adapt | awk '{print $6, $3}' | compute-eer - 2>/dev/null)
+  tgl_eer=$(paste $sre16_trials_tgl exp/scores/sre16_eval_tgl_scores_adapt | awk '{print $6, $3}' | compute-eer - 2>/dev/null)
+  yue_eer=$(paste $sre16_trials_yue exp/scores/sre16_eval_yue_scores_adapt | awk '{print $6, $3}' | compute-eer - 2>/dev/null)
+  echo "Using Adapted PLDA, EER: Pooled ${pooled_eer}%, Tagalog ${tgl_eer}%, Cantonese ${yue_eer}%"
+  # EER: Pooled 12.98%, Tagalog 17.8%, Cantonese 8.35%
+  #
+  # Using the official SRE16 scoring software, we obtain the following equalized results:
+  #
+  # -- Pooled --
+  # EER:         13.08
+  # min_Cprimary: 0.72
+  # act_Cprimary: 0.73
+
+  # -- Cantonese --
+  # EER:          8.23
+  # min_Cprimary: 0.59
+  # act_Cprimary: 0.59
+
+  # -- Tagalog --
+  # EER:         17.87
+  # min_Cprimary: 0.84
+  # act_Cprimary: 0.87
+fi
diff --git a/egs/sre16/v1/sid b/egs/sre16/v1/sid
new file mode 120000 (symlink)
index 0000000..5cb0274
--- /dev/null
@@ -0,0 +1 @@
+../../sre08/v1/sid/
\ No newline at end of file
diff --git a/egs/sre16/v1/steps b/egs/sre16/v1/steps
new file mode 120000 (symlink)
index 0000000..6e99bf5
--- /dev/null
@@ -0,0 +1 @@
+../../wsj/s5/steps
\ No newline at end of file
diff --git a/egs/sre16/v1/utils b/egs/sre16/v1/utils
new file mode 120000 (symlink)
index 0000000..b240885
--- /dev/null
@@ -0,0 +1 @@
+../../wsj/s5/utils
\ No newline at end of file
diff --git a/egs/sre16/v2/README.txt b/egs/sre16/v2/README.txt
new file mode 100644 (file)
index 0000000..0c9cc0d
--- /dev/null
@@ -0,0 +1,30 @@
+ This recipe replaces iVectors used in the v1 recipe with embeddings extracted
+ from a deep neural network.  In the scripts, we refer to these embeddings as
+ "xvectors."  The recipe is based on 
+ http://www.danielpovey.com/files/2017_interspeech_embeddings.pdf but with
+ improvements due to augmentation in the DNN training data.
+
+ The recipe uses the following data for system development.  This is in
+ addition to the NIST SRE 2016 dataset used for evaluation (see ../README.txt).
+     Corpus              LDC Catalog No.
+     SWBD2 Phase 1       LDC98S75
+     SWBD2 Phase 2       LDC99S79
+     SWBD2 Phase 3       LDC2002S06
+     SWBD Cellular 1     LDC2001S13
+     SWBD Cellular 2     LDC2004S07
+     SRE2004             LDC2006S44
+     SRE2005 Train       LDC2011S01
+     SRE2005 Test        LDC2011S04
+     SRE2006 Train       LDC2011S09
+     SRE2006 Test 1      LDC2011S10
+     SRE2006 Test 2      LDC2012S01
+     SRE2008 Train       LDC2011S05
+     SRE2008 Test        LDC2011S08
+     SRE2010 Eval        LDC2017S06
+     Mixer 6             LDC2013S03
+
+ The following datasets are used in data augmentation.
+
+     MUSAN               http://www.openslr.org/17
+     RIR_NOISES          http://www.openslr.org/28
diff --git a/egs/sre16/v2/cmd.sh b/egs/sre16/v2/cmd.sh
new file mode 100755 (executable)
index 0000000..d1ca1a6
--- /dev/null
@@ -0,0 +1,15 @@
+# you can change cmd.sh depending on what type of queue you are using.
+# If you have no queueing system and want to run on a local machine, you
+# can change all instances 'queue.pl' to run.pl (but be careful and run
+# commands one by one: most recipes will exhaust the memory on your
+# machine).  queue.pl works with GridEngine (qsub).  slurm.pl works
+# with slurm.  Different queues are configured differently, with different
+# queue names and different ways of specifying things like memory;
+# to account for these differences you can create and edit the file
+# conf/queue.conf to match your queue's configuration.  Search for
+# conf/queue.conf in http://kaldi-asr.org/doc/queue.html for more information,
+# or search for the string 'default_config' in utils/queue.pl or utils/slurm.pl.
+
+export train_cmd="queue.pl --mem 4G"
+
+
diff --git a/egs/sre16/v2/conf/mfcc.conf b/egs/sre16/v2/conf/mfcc.conf
new file mode 100644 (file)
index 0000000..d32a221
--- /dev/null
@@ -0,0 +1,6 @@
+--sample-frequency=8000 
+--frame-length=25 # the default is 25
+--low-freq=20 # the default.
+--high-freq=3700 # the default is zero meaning use the Nyquist (4k in this case).
+--num-ceps=23 # higher than the default which is 12.
+--snip-edges=false
diff --git a/egs/sre16/v2/conf/vad.conf b/egs/sre16/v2/conf/vad.conf
new file mode 100644 (file)
index 0000000..c9f5e8b
--- /dev/null
@@ -0,0 +1,4 @@
+--vad-energy-threshold=5.5
+--vad-energy-mean-scale=0.5
+--vad-proportion-threshold=0.12
+--vad-frames-context=2
diff --git a/egs/sre16/v2/local b/egs/sre16/v2/local
new file mode 120000 (symlink)
index 0000000..740b697
--- /dev/null
@@ -0,0 +1 @@
+../v1/local/
\ No newline at end of file
diff --git a/egs/sre16/v2/path.sh b/egs/sre16/v2/path.sh
new file mode 100755 (executable)
index 0000000..e50f57c
--- /dev/null
@@ -0,0 +1,5 @@
+export KALDI_ROOT=`pwd`/../../..
+export PATH=$PWD/utils/:$KALDI_ROOT/tools/openfst/bin:$KALDI_ROOT/tools/sph2pipe_v2.5:$PWD:$PATH
+[ ! -f $KALDI_ROOT/tools/config/common_path.sh ] && echo >&2 "The standard file $KALDI_ROOT/tools/config/common_path.sh is not present -> Exit!" && exit 1
+. $KALDI_ROOT/tools/config/common_path.sh
+export LC_ALL=C
diff --git a/egs/sre16/v2/run.sh b/egs/sre16/v2/run.sh
new file mode 100755 (executable)
index 0000000..d18118b
--- /dev/null
@@ -0,0 +1,320 @@
+#!/bin/bash
+# Copyright      2017   David Snyder
+#                2017   Johns Hopkins University (Author: Daniel Garcia-Romero)
+#                2017   Johns Hopkins University (Author: Daniel Povey)
+# Apache 2.0.
+#
+# See README.txt for more info on data required.
+# Results (mostly EERs) are inline in comments below.
+#
+# This example demonstrates a "bare bones" NIST SRE 2016 recipe using xvectors.
+# In the future, we will add score-normalization and a more effective form of
+# PLDA domain adaptation.
+
+. cmd.sh
+. path.sh
+set -e
+mfccdir=`pwd`/mfcc
+vaddir=`pwd`/mfcc
+
+# SRE16 trials
+sre16_trials=data/sre16_eval_test/trials
+sre16_trials_tgl=data/sre16_eval_test/trials_tgl
+sre16_trials_yue=data/sre16_eval_test/trials_yue
+nnet_dir=exp/xvector_nnet_1a
+
+stage=0
+if [ $stage -le 0 ]; then
+  # Path to some, but not all of the training corpora
+  data_root=/export/corpora/LDC
+
+  # Prepare telephone and microphone speech from Mixer6.
+  local/make_mx6.sh $data_root/LDC2013S03 data/
+
+  # Prepare SRE10 test and enroll. Includes microphone interview speech.
+  # NOTE: This corpus is now available through the LDC as LDC2017S06.
+  local/make_sre10.pl /export/corpora5/SRE/SRE2010/eval/ data/
+
+  # Prepare SRE08 test and enroll. Includes some microphone speech.
+  local/make_sre08.pl $data_root/LDC2011S08 $data_root/LDC2011S05 data/
+
+  # This prepares the older NIST SREs from 2004-2006.
+  local/make_sre.sh $data_root data/
+
+  # Combine all SREs prior to 2016 and Mixer6 into one dataset
+  utils/combine_data.sh data/sre \
+    data/sre2004 data/sre2005_train \
+    data/sre2005_test data/sre2006_train \
+    data/sre2006_test_1 data/sre2006_test_2 \
+    data/sre08 data/mx6 data/sre10
+  utils/validate_data_dir.sh --no-text --no-feats data/sre
+  utils/fix_data_dir.sh data/sre
+
+  # Prepare SWBD corpora.
+  local/make_swbd_cellular1.pl $data_root/LDC2001S13 \
+    data/swbd_cellular1_train
+  local/make_swbd_cellular2.pl /export/corpora5/LDC/LDC2004S07 \
+    data/swbd_cellular2_train
+  local/make_swbd2_phase1.pl $data_root/LDC98S75 \
+    data/swbd2_phase1_train
+  local/make_swbd2_phase2.pl /export/corpora5/LDC/LDC99S79 \
+    data/swbd2_phase2_train
+  local/make_swbd2_phase3.pl /export/corpora5/LDC/LDC2002S06 \
+    data/swbd2_phase3_train
+
+  # Combine all SWB corpora into one dataset.
+  utils/combine_data.sh data/swbd \
+    data/swbd_cellular1_train data/swbd_cellular2_train \
+    data/swbd2_phase1_train data/swbd2_phase2_train data/swbd2_phase3_train
+
+  # Prepare NIST SRE 2016 evaluation data.
+  local/make_sre16_eval.pl /export/corpora5/SRE/R149_0_1 data
+
+  # Prepare unlabeled Cantonese and Tagalog development data. This dataset
+  # was distributed to SRE participants.
+  local/make_sre16_unlabeled.pl /export/corpora5/SRE/LDC2016E46_SRE16_Call_My_Net_Training_Data data
+fi
+
+if [ $stage -le 1 ]; then
+  # Make filterbanks and compute the energy-based VAD for each dataset
+  for name in sre swbd sre16_eval_enroll sre16_eval_test sre16_major; do
+    steps/make_mfcc.sh --mfcc-config conf/mfcc.conf --nj 40 --cmd "$train_cmd" \
+      data/${name} exp/make_mfcc $mfccdir
+    utils/fix_data_dir.sh data/${name}
+    sid/compute_vad_decision.sh --nj 40 --cmd "$train_cmd" \
+      data/${name} exp/make_vad $vaddir
+    utils/fix_data_dir.sh data/${name}
+  done
+  utils/combine_data.sh data/swbd_sre data/swbd data/sre
+  utils/fix_data_dir.sh data/swbd_sre
+fi
+
+# In this section, we augment the SWBD and SRE data with reverberation,
+# noise, music, and babble, and combined it with the clean data.
+# The combined list will be used to train the xvector DNN.  The SRE
+# subset will be used to train the PLDA model.
+if [ $stage -le 2 ]; then
+  utils/data/get_utt2num_frames.sh --nj 40 --cmd "$train_cmd" data/swbd_sre
+  frame_shift=0.01
+  awk -v frame_shift=$frame_shift '{print $1, $2*frame_shift;}' data/swbd_sre/utt2num_frames > data/swbd_sre/reco2dur
+
+  if [ ! -d "RIRS_NOISES" ]; then
+    # Download the package that includes the real RIRs, simulated RIRs, isotropic noises and point-source noises
+    wget --no-check-certificate http://www.openslr.org/resources/28/rirs_noises.zip
+    unzip rirs_noises.zip
+  fi
+
+  # Make a version with reverberated speech
+  rvb_opts=()
+  rvb_opts+=(--rir-set-parameters "0.5, RIRS_NOISES/simulated_rirs/smallroom/rir_list")
+  rvb_opts+=(--rir-set-parameters "0.5, RIRS_NOISES/simulated_rirs/mediumroom/rir_list")
+
+  # Make a reverberated version of the SWBD+SRE list.  Note that we don't add any
+  # additive noise here.
+  python steps/data/reverberate_data_dir.py \
+    "${rvb_opts[@]}" \
+    --speech-rvb-probability 1 \
+    --pointsource-noise-addition-probability 0 \
+    --isotropic-noise-addition-probability 0 \
+    --num-replications 1 \
+    --source-sampling-rate 8000 \
+    data/swbd_sre data/swbd_sre_reverb
+  cp data/swbd_sre/vad.scp data/swbd_sre_reverb/
+  utils/copy_data_dir.sh --utt-suffix "-reverb" data/swbd_sre_reverb data/swbd_sre_reverb.new
+  rm -rf data/swbd_sre_reverb
+  mv data/swbd_sre_reverb.new data/swbd_sre_reverb
+
+  # Prepare the MUSAN corpus, which consists of music, speech, and noise
+  # suitable for augmentation.
+  local/make_musan.sh /export/corpora/JHU/musan data
+
+  # Get the duration of the MUSAN recordings.  This will be used by the
+  # script augment_data_dir.py.
+  for name in speech noise music; do
+    utils/data/get_utt2dur.sh data/musan_${name}
+    mv data/musan_${name}/utt2dur data/musan_${name}/reco2dur
+  done
+
+  # Augment with musan_noise
+  python steps/data/augment_data_dir.py --utt-suffix "noise" --fg-interval 1 --fg-snrs "15:10:5:0" --fg-noise-dir "data/musan_noise" data/swbd_sre data/swbd_sre_noise
+  # Augment with musan_music
+  python steps/data/augment_data_dir.py --utt-suffix "music" --bg-snrs "15:10:8:5" --num-bg-noises "1" --bg-noise-dir "data/musan_music" data/swbd_sre data/swbd_sre_music
+  # Augment with musan_speech
+  python steps/data/augment_data_dir.py --utt-suffix "babble" --bg-snrs "20:17:15:13" --num-bg-noises "3:4:5:6:7" --bg-noise-dir "data/musan_speech" data/swbd_sre data/swbd_sre_babble
+
+  # Combine reverb, noise, music, and babble into one directory.
+  utils/combine_data.sh data/swbd_sre_aug data/swbd_sre_reverb data/swbd_sre_noise data/swbd_sre_music data/swbd_sre_babble
+
+  # Take a random subset of the augmentations (128k is somewhat larger than twice
+  # the size of the SWBD+SRE list)
+  utils/subset_data_dir.sh data/swbd_sre_aug 128000 data/swbd_sre_aug_128k
+  utils/fix_data_dir.sh data/swbd_sre_aug_128k
+
+  # Make filterbanks for the augmented data.  Note that we do not compute a new
+  # vad.scp file here.  Instead, we use the vad.scp from the clean version of
+  # the list.
+  steps/make_mfcc.sh --mfcc-config conf/mfcc.conf --nj 40 --cmd "$train_cmd" \
+    data/swbd_sre_aug_128k exp/make_mfcc $mfccdir
+
+  # Combine the clean and augmented SWBD+SRE list.  This is now roughly
+  # double the size of the original clean list.
+  utils/combine_data.sh data/swbd_sre_combined data/swbd_sre_aug_128k data/swbd_sre
+
+  # Filter out the clean + augmented portion of the SRE list.  This will be used to
+  # train the PLDA model later in the script.
+  utils/copy_data_dir.sh data/swbd_sre_combined data/sre_combined
+  utils/filter_scp.pl data/sre/spk2utt data/swbd_sre_combined/spk2utt | utils/spk2utt_to_utt2spk.pl > data/sre_combined/utt2spk
+  utils/fix_data_dir.sh data/sre_combined
+fi
+
+# Now we prepare the features to generate examples for xvector training.
+if [ $stage -le 3 ]; then
+  # This script applies CMVN and removes nonspeech frames.  Note that this is somewhat
+  # wasteful, as it roughly doubles the amount of training data on disk.  After
+  # creating training examples, this can be removed.
+  local/nnet3/xvector/prepare_feats_for_egs.sh --nj 40 --cmd "$train_cmd" \
+    data/swbd_sre_combined data/swbd_sre_combined_no_sil exp/swbd_sre_combined_no_sil
+  utils/fix_data_dir.sh data/swbd_sre_combined_no_sil
+  utils/data/get_utt2num_frames.sh --nj 40 --cmd "$train_cmd" data/swbd_sre_combined_no_sil
+  utils/fix_data_dir.sh data/swbd_sre_combined_no_sil
+
+  # Now, we need to remove features that are too short after removing silence
+  # frames.  We want atleast 5s (500 frames) per utterance.
+  min_len=500
+  mv data/swbd_sre_combined_no_sil/utt2num_frames data/swbd_sre_combined_no_sil/utt2num_frames.bak
+  awk -v min_len=${min_len} '$2 > min_len {print $1, $2}' data/swbd_sre_combined_no_sil/utt2num_frames.bak > data/swbd_sre_combined_no_sil/utt2num_frames
+  utils/filter_scp.pl data/swbd_sre_combined_no_sil/utt2num_frames data/swbd_sre_combined_no_sil/utt2spk > data/swbd_sre_combined_no_sil/utt2spk.new
+  mv data/swbd_sre_combined_no_sil/utt2spk.new data/swbd_sre_combined_no_sil/utt2spk
+  utils/fix_data_dir.sh data/swbd_sre_combined_no_sil
+
+  # We also want several utterances per speaker. Now we'll throw out speakers
+  # with fewer than 8 utterances.
+  min_num_utts=8
+  awk '{print $1, NF-1}' data/swbd_sre_combined_no_sil/spk2utt > data/swbd_sre_combined_no_sil/spk2num
+  awk -v min_num_utts=${min_num_utts} '$2 >= min_num_utts {print $1, $2}' data/swbd_sre_combined_no_sil/spk2num | utils/filter_scp.pl - data/swbd_sre_combined_no_sil/spk2utt > data/swbd_sre_combined_no_sil/spk2utt.new
+  mv data/swbd_sre_combined_no_sil/spk2utt.new data/swbd_sre_combined_no_sil/spk2utt
+  utils/spk2utt_to_utt2spk.pl data/swbd_sre_combined_no_sil/spk2utt > data/swbd_sre_combined_no_sil/utt2spk
+
+  utils/filter_scp.pl data/swbd_sre_combined_no_sil/utt2spk data/swbd_sre_combined_no_sil/utt2num_frames > data/swbd_sre_combined_no_sil/utt2num_frames.new
+  mv data/swbd_sre_combined_no_sil/utt2num_frames.new data/swbd_sre_combined_no_sil/utt2num_frames
+
+  # Now we're reaady to create training examples.
+  utils/fix_data_dir.sh data/swbd_sre_combined_no_sil
+fi
+
+local/nnet3/xvector/run_xvector.sh --stage $stage --train-stage -1 \
+  --data data/swbd_sre_combined_no_sil --nnet-dir $nnet_dir \
+  --egs-dir $nnet_dir/egs
+
+if [ $stage -le 7 ]; then
+  # The SRE16 major is an unlabeled dataset consisting of Cantonese and
+  # and Tagalog.  This is useful for things like centering, whitening and
+  # score normalization.
+  sid/nnet3/xvector/extract_xvectors.sh --cmd "$train_cmd --mem 6G" --nj 40 \
+    $nnet_dir data/sre16_major \
+    exp/xvectors_sre16_major
+
+  # Extract xvectors for SRE data (includes Mixer 6). We'll use this for
+  # things like LDA or PLDA.
+  sid/nnet3/xvector/extract_xvectors.sh --cmd "$train_cmd --mem 12G" --nj 40 \
+    $nnet_dir data/sre_combined \
+    exp/xvectors_sre_combined
+
+  # The SRE16 test data
+  sid/nnet3/xvector/extract_xvectors.sh --cmd "$train_cmd --mem 6G" --nj 40 \
+    $nnet_dir data/sre16_eval_test \
+    exp/xvectors_sre16_eval_test
+
+  # The SRE16 enroll data
+  sid/nnet3/xvector/extract_xvectors.sh --cmd "$train_cmd --mem 6G" --nj 40 \
+    $nnet_dir data/sre16_eval_enroll \
+    exp/xvectors_sre16_eval_enroll
+fi
+
+if [ $stage -le 8 ]; then
+  # Compute the mean vector for centering the evaluation xvectors.
+  $train_cmd exp/xvectors_sre16_major/log/compute_mean.log \
+    ivector-mean scp:exp/xvectors_sre16_major/xvector.scp \
+    exp/xvectors_sre16_major/mean.vec || exit 1;
+
+  # This script uses LDA to decrease the dimensionality prior to PLDA.
+  lda_dim=150
+  $train_cmd exp/xvectors_sre_combined/log/lda.log \
+    ivector-compute-lda --total-covariance-factor=0.0 --dim=$lda_dim \
+    "ark:ivector-subtract-global-mean scp:exp/xvectors_sre_combined/xvector.scp ark:- |" \
+    ark:data/sre_combined/utt2spk exp/xvectors_sre_combined/transform.mat || exit 1;
+
+  # Train an out-of-domain PLDA model.
+  $train_cmd exp/xvectors_sre_combined/log/plda.log \
+    ivector-compute-plda ark:data/sre_combined/spk2utt \
+    "ark:ivector-subtract-global-mean scp:exp/xvectors_sre_combined/xvector.scp ark:- | transform-vec exp/xvectors_sre_combined/transform.mat ark:- ark:- | ivector-normalize-length ark:-  ark:- |" \
+    exp/xvectors_sre_combined/plda || exit 1;
+
+  # Here we adapt the out-of-domain PLDA model to SRE16 major, a pile
+  # of unlabeled in-domain data.  In the future, we will include a clustering
+  # based approach for domain adaptation, which tends to work better.
+  $train_cmd exp/xvectors_sre16_major/log/plda_adapt.log \
+    ivector-adapt-plda --within-covar-scale=0.75 --between-covar-scale=0.25 \
+    exp/xvectors_sre_combined/plda \
+    "ark:ivector-subtract-global-mean scp:exp/xvectors_sre16_major/xvector.scp ark:- | transform-vec exp/xvectors_sre_combined/transform.mat ark:- ark:- | ivector-normalize-length ark:- ark:- |" \
+    exp/xvectors_sre16_major/plda_adapt || exit 1;
+fi
+
+if [ $stage -le 9 ]; then
+  # Get results using the out-of-domain PLDA model.
+  $train_cmd exp/scores/log/sre16_eval_scoring.log \
+    ivector-plda-scoring --normalize-length=true \
+    --num-utts=ark:exp/xvectors_sre16_eval_enroll/num_utts.ark \
+    "ivector-copy-plda --smoothing=0.0 exp/xvectors_sre_combined/plda - |" \
+    "ark:ivector-mean ark:data/sre16_eval_enroll/spk2utt scp:exp/xvectors_sre16_eval_enroll/xvector.scp ark:- | ivector-subtract-global-mean exp/xvectors_sre16_major/mean.vec ark:- ark:- | transform-vec exp/xvectors_sre_combined/transform.mat ark:- ark:- | ivector-normalize-length ark:- ark:- |" \
+    "ark:ivector-subtract-global-mean exp/xvectors_sre16_major/mean.vec scp:exp/xvectors_sre16_eval_test/xvector.scp ark:- | transform-vec exp/xvectors_sre_combined/transform.mat ark:- ark:- | ivector-normalize-length ark:- ark:- |" \
+    "cat '$sre16_trials' | cut -d\  --fields=1,2 |" exp/scores/sre16_eval_scores || exit 1;
+
+  utils/filter_scp.pl $sre16_trials_tgl exp/scores/sre16_eval_scores > exp/scores/sre16_eval_tgl_scores
+  utils/filter_scp.pl $sre16_trials_yue exp/scores/sre16_eval_scores > exp/scores/sre16_eval_yue_scores
+  pooled_eer=$(paste $sre16_trials exp/scores/sre16_eval_scores | awk '{print $6, $3}' | compute-eer - 2>/dev/null)
+  tgl_eer=$(paste $sre16_trials_tgl exp/scores/sre16_eval_tgl_scores | awk '{print $6, $3}' | compute-eer - 2>/dev/null)
+  yue_eer=$(paste $sre16_trials_yue exp/scores/sre16_eval_yue_scores | awk '{print $6, $3}' | compute-eer - 2>/dev/null)
+  echo "Using Out-of-Domain PLDA, EER: Pooled ${pooled_eer}%, Tagalog ${tgl_eer}%, Cantonese ${yue_eer}%"
+  # EER: Pooled 11.73%, Tagalog 15.96%, Cantonese 7.52%
+  # For reference, here's the ivector system from ../v1:
+  # EER: Pooled 13.65%, Tagalog 17.73%, Cantonese 9.61%
+fi
+
+if [ $stage -le 10 ]; then
+  # Get results using the adapted PLDA model.
+  $train_cmd exp/scores/log/sre16_eval_scoring_adapt.log \
+    ivector-plda-scoring --normalize-length=true \
+    --num-utts=ark:exp/xvectors_sre16_eval_enroll/num_utts.ark \
+    "ivector-copy-plda --smoothing=0.0 exp/xvectors_sre16_major/plda_adapt - |" \
+    "ark:ivector-mean ark:data/sre16_eval_enroll/spk2utt scp:exp/xvectors_sre16_eval_enroll/xvector.scp ark:- | ivector-subtract-global-mean exp/xvectors_sre16_major/mean.vec ark:- ark:- | transform-vec exp/xvectors_sre_combined/transform.mat ark:- ark:- | ivector-normalize-length ark:- ark:- |" \
+    "ark:ivector-subtract-global-mean exp/xvectors_sre16_major/mean.vec scp:exp/xvectors_sre16_eval_test/xvector.scp ark:- | transform-vec exp/xvectors_sre_combined/transform.mat ark:- ark:- | ivector-normalize-length ark:- ark:- |" \
+    "cat '$sre16_trials' | cut -d\  --fields=1,2 |" exp/scores/sre16_eval_scores_adapt || exit 1;
+
+  utils/filter_scp.pl $sre16_trials_tgl exp/scores/sre16_eval_scores_adapt > exp/scores/sre16_eval_tgl_scores_adapt
+  utils/filter_scp.pl $sre16_trials_yue exp/scores/sre16_eval_scores_adapt > exp/scores/sre16_eval_yue_scores_adapt
+  pooled_eer=$(paste $sre16_trials exp/scores/sre16_eval_scores_adapt | awk '{print $6, $3}' | compute-eer - 2>/dev/null)
+  tgl_eer=$(paste $sre16_trials_tgl exp/scores/sre16_eval_tgl_scores_adapt | awk '{print $6, $3}' | compute-eer - 2>/dev/null)
+  yue_eer=$(paste $sre16_trials_yue exp/scores/sre16_eval_yue_scores_adapt | awk '{print $6, $3}' | compute-eer - 2>/dev/null)
+  echo "Using Adapted PLDA, EER: Pooled ${pooled_eer}%, Tagalog ${tgl_eer}%, Cantonese ${yue_eer}%"
+  # EER: Pooled 8.57%, Tagalog 12.29%, Cantonese 4.89%
+  # For reference, here's the ivector system from ../v1:
+  # EER: Pooled 12.98%, Tagalog 17.8%, Cantonese 8.35%
+  #
+  # Using the official SRE16 scoring software, we obtain the following equalized results:
+  #
+  # -- Pooled --
+  #  EER:          8.66
+  #  min_Cprimary: 0.61
+  #  act_Cprimary: 0.62
+  #
+  # -- Cantonese --
+  # EER:           4.69
+  # min_Cprimary:  0.42
+  # act_Cprimary:  0.43
+  #
+  # -- Tagalog --
+  # EER:          12.63
+  # min_Cprimary:  0.76
+  # act_Cprimary:  0.81
+fi
diff --git a/egs/sre16/v2/sid b/egs/sre16/v2/sid
new file mode 120000 (symlink)
index 0000000..5cb0274
--- /dev/null
@@ -0,0 +1 @@
+../../sre08/v1/sid/
\ No newline at end of file
diff --git a/egs/sre16/v2/steps b/egs/sre16/v2/steps
new file mode 120000 (symlink)
index 0000000..1b18677
--- /dev/null
@@ -0,0 +1 @@
+../../wsj/s5/steps/
\ No newline at end of file
diff --git a/egs/sre16/v2/utils b/egs/sre16/v2/utils
new file mode 120000 (symlink)
index 0000000..b240885
--- /dev/null
@@ -0,0 +1 @@
+../../wsj/s5/utils
\ No newline at end of file
diff --git a/egs/wsj/s5/steps/data/augment_data_dir.py b/egs/wsj/s5/steps/data/augment_data_dir.py
new file mode 100755 (executable)
index 0000000..520e7b5
--- /dev/null
@@ -0,0 +1,194 @@
+#!/usr/bin/env python3
+# Copyright 2017  David Snyder
+# Apache 2.0
+#
+# This script generates augmented data.  It is based on
+# steps/data/reverberate_data_dir.py but doesn't handle reverberation.
+# It is designed to be somewhat simpler and more flexible for augmenting with
+# additive noise.
+from __future__ import print_function
+import sys, random, argparse, os, imp
+sys.path.append("steps/data/")
+from reverberate_data_dir import ParseFileToDict
+from reverberate_data_dir import WriteDictToFile
+data_lib = imp.load_source('dml', 'steps/data/data_dir_manipulation_lib.py')
+
+def GetArgs():
+    parser = argparse.ArgumentParser(description="Augment the data directory with additive noises. "
+        "Noises are separated into background and foreground noises which are added together or "
+        "separately.  Background noises are added to the entire recording, and repeated as necessary "
+        "to cover the full length.  Multiple overlapping background noises can be added, to simulate "
+        "babble, for example.  Foreground noises are added sequentially, according to a specified "
+        "interval.  See also steps/data/reverberate_data_dir.py "
+        "Usage: augment_data_dir.py [options...] <in-data-dir> <out-data-dir> "
+        "E.g., steps/data/augment_data_dir.py --utt-suffix aug --fg-snrs 20:10:5:0 --bg-snrs 20:15:10 "
+        "--num-bg-noise 1:2:3 --fg-interval 3 --fg-noise-dir data/musan_noise --bg-noise-dir "
+        "data/musan_music data/train data/train_aug", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+    parser.add_argument('--fg-snrs', type=str, dest = "fg_snr_str", default = '20:10:0',
+                        help='When foreground noises are being added, the script will iterate through these SNRs.')
+    parser.add_argument('--bg-snrs', type=str, dest = "bg_snr_str", default = '20:10:0',
+                        help='When background noises are being added, the script will iterate through these SNRs.')
+    parser.add_argument('--num-bg-noises', type=str, dest = "num_bg_noises", default = '1',
+                        help='Number of overlapping background noises that we iterate over. For example, if the input is "1:2:3" then the output wavs will have either 1, 2, or 3 randomly chosen background noises overlapping the entire recording')
+    parser.add_argument('--fg-interval', type=int, dest = "fg_interval", default = 0,
+                        help='Number of seconds between the end of one foreground noise and the beginning of the next.')
+    parser.add_argument('--utt-suffix', type=str, dest = "utt_suffix", default = "aug", help='Suffix added to utterance IDs.')
+    parser.add_argument('--random-seed', type=int, dest = "random_seed", default = 123, help='Random seed.')
+
+    parser.add_argument("--bg-noise-dir", type=str, dest="bg_noise_dir",
+                        help="Background noise data directory")
+    parser.add_argument("--fg-noise-dir", type=str, dest="fg_noise_dir",
+                        help="Foreground noise data directory")
+    parser.add_argument("input_dir", help="Input data directory")
+    parser.add_argument("output_dir", help="Output data directory")
+
+    print(' '.join(sys.argv))
+    args = parser.parse_args()
+    args = CheckArgs(args)
+    return args
+
+def CheckArgs(args):
+    if not os.path.exists(args.output_dir):
+        os.makedirs(args.output_dir)
+    if not args.fg_interval >= 0:
+        raise Exception("--fg-interval must be 0 or greater")
+    if args.bg_noise_dir is None and args.fg_noise_dir is None:
+        raise Exception("Either --fg-noise-dir or --bg-noise-dir must be specified")
+    return args
+
+def GetNoiseList(noise_wav_scp_filename):
+    noise_wav_scp_file = open(noise_wav_scp_filename, 'r').readlines()
+    noise_wavs = {}
+    noise_utts = []
+    for line in noise_wav_scp_file:
+        toks=line.split(" ")
+        wav = " ".join(toks[1:])
+        noise_utts.append(toks[0])
+        noise_wavs[toks[0]] = wav.rstrip()
+    return noise_utts, noise_wavs
+
+def AugmentWav(utt, wav, dur, fg_snr_opts, bg_snr_opts, fg_noise_utts, \
+    bg_noise_utts, noise_wavs, noise2dur, interval, num_opts):
+    # This section is common to both foreground and background noises
+    new_wav = ""
+    dur_str = str(dur)
+    noise_dur = 0
+    tot_noise_dur = 0
+    snrs=[]
+    noises=[]
+    start_times=[]
+
+    # Now handle the background noises
+    if len(bg_noise_utts) > 0:
+        num = random.choice(num_opts)
+        for i in range(0, num):
+            noise_utt = random.choice(bg_noise_utts)
+            noise = noise_wavs[noise_utt] + " wav-reverberate --duration=" \
+            + dur_str + " - - |"
+            snr = random.choice(bg_snr_opts)
+            snrs.append(snr)
+            start_times.append(0)
+            noises.append(noise)
+
+    # Now handle the foreground noises
+    if len(fg_noise_utts) > 0:
+        while tot_noise_dur < dur:
+            noise_utt = random.choice(fg_noise_utts)
+            noise = noise_wavs[noise_utt]
+            snr = random.choice(fg_snr_opts)
+            snrs.append(snr)
+            noise_dur = noise2dur[noise_utt]
+            start_times.append(tot_noise_dur)
+            tot_noise_dur += noise_dur + interval
+            noises.append(noise)
+
+    start_times_str = "--start-times='" + ",".join(map(str,start_times)) + "'"
+    snrs_str = "--snrs='" + ",".join(map(str,snrs)) + "'"
+    noises_str = "--additive-signals='" + ",".join(noises) + "'"
+
+    # If the wav is just a file
+    if len(wav.split()) == 1:
+        new_wav = "wav-reverberate --shift-output=true " + noises_str + " " \
+            + start_times_str + " " + snrs_str + " " + wav + " - |"
+    # Else if the wav is in a pipe
+    else:
+        new_wav = wav + "wav-reverberate --shift-output=true " + noises_str + " " \
+            + start_times_str + " " + snrs_str + " - - |"
+    return new_wav
+
+def CopyFileIfExists(utt_suffix, filename, input_dir, output_dir):
+    if os.path.isfile(input_dir + "/" + filename):
+        dict = ParseFileToDict(input_dir + "/" + filename,
+            value_processor = lambda x: " ".join(x))
+        if len(utt_suffix) > 0:
+            new_dict = {}
+            for key in dict.keys():
+                new_dict[key + "-" + utt_suffix] = dict[key]
+            dict = new_dict
+        WriteDictToFile(dict, output_dir + "/" + filename)
+
+def main():
+    args = GetArgs()
+    fg_snrs = map(int, args.fg_snr_str.split(":"))
+    bg_snrs = map(int, args.bg_snr_str.split(":"))
+    input_dir = args.input_dir
+    output_dir = args.output_dir
+    num_bg_noises = map(int, args.num_bg_noises.split(":"))
+    reco2dur = ParseFileToDict(input_dir + "/reco2dur",
+        value_processor = lambda x: float(x[0]))
+    wav_scp_file = open(input_dir + "/wav.scp", 'r').readlines()
+
+    noise_wavs = {}
+    noise_reco2dur = {}
+    bg_noise_utts = []
+    fg_noise_utts = []
+
+    # Load background noises
+    if args.bg_noise_dir:
+        bg_noise_wav_filename = args.bg_noise_dir + "/wav.scp"
+        bg_noise_utts, bg_noise_wavs = GetNoiseList(bg_noise_wav_filename)
+        bg_noise_reco2dur = ParseFileToDict(args.bg_noise_dir + "/reco2dur",
+            value_processor = lambda x: float(x[0]))
+        noise_wavs.update(bg_noise_wavs)
+        noise_reco2dur.update(bg_noise_reco2dur)
+
+    # Load background noises
+    if args.fg_noise_dir:
+        fg_noise_wav_filename = args.fg_noise_dir + "/wav.scp"
+        fg_noise_reco2dur_filename = args.fg_noise_dir + "/reco2dur"
+        fg_noise_utts, fg_noise_wavs = GetNoiseList(fg_noise_wav_filename)
+        fg_noise_reco2dur = ParseFileToDict(args.fg_noise_dir + "/reco2dur",
+            value_processor = lambda x: float(x[0]))
+        noise_wavs.update(fg_noise_wavs)
+        noise_reco2dur.update(fg_noise_reco2dur)
+
+    random.seed(args.random_seed)
+    new_utt2wav = {}
+    new_utt2spk = {}
+
+    # Augment each line in the wav file
+    for line in wav_scp_file:
+        toks = line.rstrip().split(" ")
+        utt = toks[0]
+        wav = " ".join(toks[1:])
+        dur = reco2dur[utt]
+        new_wav = AugmentWav(utt, wav, dur, fg_snrs, bg_snrs, fg_noise_utts,
+            bg_noise_utts, noise_wavs, noise_reco2dur, args.fg_interval,
+            num_bg_noises)
+        new_utt = utt + "-" + args.utt_suffix
+        new_utt2wav[new_utt] = new_wav
+
+    if not os.path.exists(output_dir):
+        os.makedirs(output_dir)
+
+    WriteDictToFile(new_utt2wav, output_dir + "/wav.scp")
+    CopyFileIfExists(args.utt_suffix, "utt2spk", input_dir, output_dir)
+    CopyFileIfExists(args.utt_suffix, "utt2lang", input_dir, output_dir)
+    CopyFileIfExists(args.utt_suffix, "text", input_dir, output_dir)
+    CopyFileIfExists(args.utt_suffix, "utt2spk", input_dir, output_dir)
+    CopyFileIfExists(args.utt_suffix, "vad.scp", input_dir, output_dir)
+    CopyFileIfExists("", "spk2gender", input_dir, output_dir)
+    data_lib.RunKaldiCommand("utils/fix_data_dir.sh {output_dir}".format(output_dir = output_dir))
+
+if __name__ == "__main__":
+    main()
index 0083efa4939a423a828ee966b943289ca8ee8d84..71e64d9e680ff5e8927e50934fbc7d8e513a03c9 100755 (executable)
@@ -20,7 +20,7 @@ def GetArgs():
                                                  "--random-seed 1 data/train data/train_rvb",
                                      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
 
-    parser.add_argument("--rir-set-parameters", type=str, action='append', required = True, dest = "rir_set_para_array", 
+    parser.add_argument("--rir-set-parameters", type=str, action='append', required = True, dest = "rir_set_para_array",
                         help="Specifies the parameters of an RIR set. "
                         "Supports the specification of  mixture_weight and rir_list_file_name. The mixture weight is optional. "
                         "The default mixture weight is the probability mass remaining after adding the mixture weights "
@@ -104,7 +104,7 @@ def CheckArgs(args):
 
     if args.isotropic_noise_addition_probability < 0 or args.isotropic_noise_addition_probability > 1:
         raise Exception("--isotropic-noise-addition-probability must be between 0 and 1")
-    
+
     if args.rir_smoothing_weight < 0 or args.rir_smoothing_weight > 1:
         raise Exception("--rir-smoothing-weight must be between 0 and 1")
 
@@ -113,7 +113,7 @@ def CheckArgs(args):
 
     if args.max_noises_per_minute < 0:
         raise Exception("--max-noises-per-minute cannot be negative")
-    
+
     if args.source_sampling_rate is not None and args.source_sampling_rate <= 0:
         raise Exception("--source-sampling-rate cannot be non-positive")
 
@@ -133,7 +133,7 @@ class list_cyclic_iterator:
 
 
 # This functions picks an item from the collection according to the associated probability distribution.
-# The probability estimate of each item in the collection is stored in the "probability" field of 
+# The probability estimate of each item in the collection is stored in the "probability" field of
 # the particular item. x : a collection (list or dictionary) where the values contain a field called probability
 def PickItemWithProbability(x):
    if isinstance(x, dict):
@@ -155,7 +155,6 @@ def PickItemWithProbability(x):
 def ParseFileToDict(file, assert2fields = False, value_processor = None):
     if value_processor is None:
         value_processor = lambda x: x[0]
-
     dict = {}
     for line in open(file, 'r'):
         parts = line.split()
@@ -236,7 +235,7 @@ def AddPointSourceNoise(noise_addition_descriptor,  # descriptor to store the in
 
 
 # This function randomly decides whether to reverberate, and sample a RIR if it does
-# It also decides whether to add the appropriate noises 
+# It also decides whether to add the appropriate noises
 # This function return the string of options to the binary wav-reverberate
 def GenerateReverberationOpts(room_dict,  # the room dictionary, please refer to MakeRoomDict() for the format
                               pointsource_noise_list, # the point source noise list
@@ -306,15 +305,15 @@ def GetNewId(id, prefix=None, copy=0):
         new_id = id
 
     return new_id
-    
+
 
 # This is the main function to generate pipeline command for the corruption
 # The generic command of wav-reverberate will be like:
-# wav-reverberate --duration=t --impulse-response=rir.wav 
+# wav-reverberate --duration=t --impulse-response=rir.wav
 # --additive-signals='noise1.wav,noise2.wav' --snrs='snr1,snr2' --start-times='s1,s2' input.wav output.wav
 def GenerateReverberatedWavScp(wav_scp,  # a dictionary whose values are the Kaldi-IO strings of the speech recordings
                                durations, # a dictionary whose values are the duration (in sec) of the speech recordings
-                               output_dir, # output directory to write the corrupted wav.scp 
+                               output_dir, # output directory to write the corrupted wav.scp
                                room_dict,  # the room dictionary, please refer to MakeRoomDict() for the format
                                pointsource_noise_list, # the point source noise list
                                iso_noise_dict, # the isotropic noise dictionary
@@ -358,11 +357,11 @@ def GenerateReverberatedWavScp(wav_scp,  # a dictionary whose values are the Kal
                                                          pointsource_noise_addition_probability, # Probability of adding point-source noises
                                                          speech_dur,  # duration of the recording
                                                          max_noises_recording  # Maximum number of point-source noises that can be added
-                                                         )       
+                                                         )
 
             # prefix using index 0 is reserved for original data e.g. rvb0_swb0035 corresponds to the swb0035 recording in original data
             if reverberate_opts == "" or i == 0:
-                wav_corrupted_pipe = "{0}".format(wav_original_pipe) 
+                wav_corrupted_pipe = "{0}".format(wav_original_pipe)
             else:
                 wav_corrupted_pipe = "{0} wav-reverberate --shift-output={1} {2} - - |".format(wav_original_pipe, shift_output, reverberate_opts)
 
@@ -380,7 +379,7 @@ def AddPrefixToFields(input_file, output_file, num_replicas, include_original, p
         start_index = 0
     else:
         start_index = 1
-    
+
     for i in range(start_index, num_replicas+1):
         for line in list:
             if len(line) > 0 and line[0] != ';':
@@ -410,7 +409,7 @@ def CreateReverberatedCopy(input_dir,
                            pointsource_noise_addition_probability, # Probability of adding point-source noises
                            max_noises_per_minute  # maximum number of point-source noises that can be added to a recording according to its duration
                            ):
-    
+
     wav_scp = ParseFileToDict(input_dir + "/wav.scp", value_processor = lambda x: " ".join(x))
     if not os.path.isfile(input_dir + "/reco2dur"):
         print("Getting the duration of the recordings...");
@@ -426,8 +425,8 @@ def CreateReverberatedCopy(input_dir,
     background_snr_array = map(lambda x: float(x), background_snr_string.split(':'))
 
     GenerateReverberatedWavScp(wav_scp, durations, output_dir, room_dict, pointsource_noise_list, iso_noise_dict,
-               foreground_snr_array, background_snr_array, num_replicas, include_original, prefix, 
-               speech_rvb_probability, shift_output, isotropic_noise_addition_probability, 
+               foreground_snr_array, background_snr_array, num_replicas, include_original, prefix,
+               speech_rvb_probability, shift_output, isotropic_noise_addition_probability,
                pointsource_noise_addition_probability, max_noises_per_minute)
 
     AddPrefixToFields(input_dir + "/utt2spk", output_dir + "/utt2spk", num_replicas, include_original, prefix, field = [0,1])
@@ -447,7 +446,7 @@ def CreateReverberatedCopy(input_dir,
     if os.path.isfile(input_dir + "/reco2file_and_channel"):
         AddPrefixToFields(input_dir + "/reco2file_and_channel", output_dir + "/reco2file_and_channel", num_replicas, include_original, prefix, field = [0,1])
 
-    data_lib.RunKaldiCommand("utils/validate_data_dir.sh --no-feats {output_dir}"
+    data_lib.RunKaldiCommand("utils/validate_data_dir.sh --no-feats --no-text {output_dir}"
                     .format(output_dir = output_dir))
 
 
@@ -507,7 +506,7 @@ def ParseSetParameterStrings(set_para_array):
     return SmoothProbabilityDistribution(set_list)
 
 
-# This function creates the RIR list 
+# This function creates the RIR list
 # Each rir object in the list contains the following attributes:
 # rir_id, room_id, receiver_position_id, source_position_id, rt60, drr, probability
 # Please refer to the help messages in the parser for the meaning of these attributes
@@ -521,7 +520,7 @@ def ParseRirList(rir_set_para_array, smoothing_weight, sampling_rate = None):
     rir_parser.add_argument('--drr', type=float, default=None, help='Direct-to-reverberant-ratio of the impulse response.')
     rir_parser.add_argument('--cte', type=float, default=None, help='Early-to-late index of the impulse response.')
     rir_parser.add_argument('--probability', type=float, default=None, help='probability of the impulse response.')
-    rir_parser.add_argument('rir_rspecifier', type=str, help="""rir rspecifier, it can be either a filename or a piped command. 
+    rir_parser.add_argument('rir_rspecifier', type=str, help="""rir rspecifier, it can be either a filename or a piped command.
                             E.g. data/impulses/Room001-00001.wav or "sox data/impulses/Room001-00001.wav -t wav - |" """)
 
     set_list = ParseSetParameterStrings(rir_set_para_array)
@@ -569,7 +568,7 @@ def MakeRoomDict(rir_list):
     return room_dict
 
 
-# This function creates the point-source noise list 
+# This function creates the point-source noise list
 # and the isotropic noise dictionary from the noise information file
 # The isotropic noise dictionary is indexed by the room
 # and its value is the corrresponding isotropic noise list
@@ -596,7 +595,7 @@ def ParseNoiseList(noise_set_para_array, smoothing_weight, sampling_rate = None)
         current_noise_list = map(lambda x: noise_parser.parse_args(shlex.split(x.strip())),open(noise_set.filename))
         current_pointsource_noise_list = []
         for noise in current_noise_list:
-            if sampling_rate is not None:                
+            if sampling_rate is not None:
                 # check if the rspecifier is a pipe or not
                 if len(noise.noise_rspecifier.split()) == 1:
                     noise.noise_rspecifier = "sox {0} -r {1} -t wav - |".format(noise.noise_rspecifier, sampling_rate)
@@ -615,11 +614,11 @@ def ParseNoiseList(noise_set_para_array, smoothing_weight, sampling_rate = None)
 
         pointsource_noise_list += SmoothProbabilityDistribution(current_pointsource_noise_list, smoothing_weight, noise_set.probability)
 
-    # ensure the point-source noise probabilities sum to 1 
+    # ensure the point-source noise probabilities sum to 1
     pointsource_noise_list = SmoothProbabilityDistribution(pointsource_noise_list, smoothing_weight, 1.0)
     if len(pointsource_noise_list) > 0:
         assert almost_equal(sum(noise.probability for noise in pointsource_noise_list), 1.0)
-    
+
     # ensure the isotropic noise source probabilities for a given room sum to 1
     for key in iso_noise_dict.keys():
         iso_noise_dict[key] = SmoothProbabilityDistribution(iso_noise_dict[key])
index c114833d42ef1e5ddc8f2a3690ed626fef08cfac..77e7bbb33bf20a49178da2e58d0ddc218827eb5c 100644 (file)
@@ -64,9 +64,12 @@ class XconfigStatsLayer(XconfigLayerBase):
         self._stats_period = int(m.group(4))
         self._right_context = int(m.group(5))
 
-        output_dim = (self.descriptors['input']['dim']
-                      * (2 if self._output_stddev else 1)
-                      + 1 if self._output_log_counts else 0)
+        if self._output_stddev:
+          output_dim = 2 * self.descriptors['input']['dim']
+        else:
+          output_dim = self.descriptors['input']['dim']
+        if self._output_log_counts:
+          output_dim = output_dim + 1
 
         if self.config['dim'] > 0 and self.config['dim'] != output_dim:
             raise RuntimeError(
@@ -76,7 +79,7 @@ class XconfigStatsLayer(XconfigLayerBase):
         self.config['dim'] = output_dim
 
     def check_configs(self):
-        if not (self._left_context > 0 and self._right_context > 0
+        if not (self._left_context >= 0 and self._right_context >= 0
                 and self._input_period > 0 and self._stats_period > 0
                 and self._left_context % self._stats_period == 0
                 and self._right_context % self._stats_period == 0
index d0c754f71b5eb6a2c0e82f70921e4a692efd411b..1dc3da6b742dc0fb2b7130792d35c5ea72d9283e 100755 (executable)
@@ -94,7 +94,7 @@ else
   echo "$0 [info]: not combining segments as it does not exist"
 fi
 
-for file in utt2spk utt2lang utt2dur feats.scp text cmvn.scp reco2file_and_channel wav.scp spk2gender $extra_files; do
+for file in utt2spk utt2lang utt2dur feats.scp text cmvn.scp vad.scp reco2file_and_channel wav.scp spk2gender $extra_files; do
   exists_somewhere=false
   absent_somewhere=false
   for d in $*; do
index 222bc7085270b7306b9c78d16ebe623b31c2d9c3..5b0b3946d254167bfb36356a58653dd4a2331245 100755 (executable)
@@ -7,6 +7,7 @@
 # that contains some subset of the following files:
 #  feats.scp
 #  wav.scp
+#  vad.scp
 #  spk2utt
 #  utt2spk
 #  text
@@ -79,6 +80,9 @@ if [ -f $srcdir/feats.scp ]; then
   utils/apply_map.pl -f 1 $destdir/utt_map <$srcdir/feats.scp >$destdir/feats.scp
 fi
 
+if [ -f $srcdir/vad.scp ]; then
+  utils/apply_map.pl -f 1 $destdir/utt_map <$srcdir/vad.scp >$destdir/vad.scp
+fi
 
 if [ -f $srcdir/segments ]; then
   utils/apply_map.pl -f 1 $destdir/utt_map <$srcdir/segments >$destdir/segments
index 510c9bca90590f1f083d239cf57b9a100b2b62ed..1b55b478d0c7c6cc1686002d7d7752ccb6bd02b2 100644 (file)
@@ -33,11 +33,11 @@ $(LIBFILE): $(OBJFILES)
 ifeq ($(KALDI_FLAVOR), dynamic)
   ifeq ($(shell uname), Darwin)
        $(CXX) -dynamiclib -o $@ -install_name @rpath/$@ $(LDFLAGS) $(OBJFILES) $(LDLIBS)
-       rm -f $(KALDILIBDIR)/$@; ln -s $(shell pwd)/$@ $(KALDILIBDIR)/$@
+       ln -sf $(shell pwd)/$@ $(KALDILIBDIR)/$@
   else ifeq ($(shell uname), Linux)
         # Building shared library from static (static was compiled with -fPIC)
        $(CXX) -shared -o $@ -Wl,--no-undefined -Wl,--as-needed  -Wl,-soname=$@,--whole-archive $(LIBNAME).a -Wl,--no-whole-archive $(LDFLAGS) $(LDLIBS)
-       rm -f $(KALDILIBDIR)/$@; ln -s $(shell pwd)/$@ $(KALDILIBDIR)/$@
+       ln -sf $(shell pwd)/$@ $(KALDILIBDIR)/$@
   else  # Platform not supported
        $(error Dynamic libraries not supported on this platform. Run configure with --static flag.)
   endif
index 42931acaaf825af29217605ebff42cde0602f8b6..3f39c361c65c75e17db50b687573051a92758fb8 100644 (file)
@@ -18,7 +18,7 @@ BINFILES = nnet3-init nnet3-info nnet3-get-egs nnet3-copy-egs nnet3-subset-egs \
    nnet3-discriminative-compute-objf nnet3-discriminative-train \
    nnet3-discriminative-subset-egs nnet3-get-egs-simple \
    nnet3-discriminative-compute-from-egs nnet3-latgen-faster-looped \
-   nnet3-egs-augment-image
+   nnet3-egs-augment-image nnet3-xvector-get-egs nnet3-xvector-compute
 
 OBJFILES =
 
diff --git a/src/nnet3bin/nnet3-xvector-compute.cc b/src/nnet3bin/nnet3-xvector-compute.cc
new file mode 100644 (file)
index 0000000..e649dc4
--- /dev/null
@@ -0,0 +1,211 @@
+// nnet3bin/nnet3-xvector-compute.cc
+
+// Copyright 2017   Johns Hopkins University (author: Daniel Povey)
+//           2017   Johns Hopkins University (author: Daniel Garcia-Romero)
+//           2017   David Snyder
+
+// See ../../COPYING for clarification regarding multiple authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
+// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
+// MERCHANTABLITY OR NON-INFRINGEMENT.
+// See the Apache 2 License for the specific language governing permissions and
+// limitations under the License.
+
+
+#include "base/kaldi-common.h"
+#include "util/common-utils.h"
+#include "nnet3/nnet-am-decodable-simple.h"
+#include "base/timer.h"
+#include "nnet3/nnet-utils.h"
+
+namespace kaldi {
+namespace nnet3 {
+
+// Computes an xvector from a chunk of speech features.
+static void RunNnetComputation(const MatrixBase<BaseFloat> &features,
+    const Nnet &nnet, CachingOptimizingCompiler *compiler,
+    Vector<BaseFloat> *xvector) {
+  ComputationRequest request;
+  request.need_model_derivative = false;
+  request.store_component_stats = false;
+  request.inputs.push_back(
+    IoSpecification("input", 0, features.NumRows()));
+  IoSpecification output_spec;
+  output_spec.name = "output";
+  output_spec.has_deriv = false;
+  output_spec.indexes.resize(1);
+  request.outputs.resize(1);
+  request.outputs[0].Swap(&output_spec);
+  const NnetComputation *computation = compiler->Compile(request);
+  Nnet *nnet_to_update = NULL;  // we're not doing any update.
+  NnetComputer computer(NnetComputeOptions(), *computation,
+                  nnet, nnet_to_update);
+  CuMatrix<BaseFloat> input_feats_cu(features);
+  computer.AcceptInput("input", &input_feats_cu);
+  computer.Run();
+  CuMatrix<BaseFloat> cu_output;
+  computer.GetOutputDestructive("output", &cu_output);
+  xvector->Resize(cu_output.NumCols());
+  xvector->CopyFromVec(cu_output.Row(0));
+}
+
+} // namespace nnet3
+} // namespace kaldi
+
+int main(int argc, char *argv[]) {
+  try {
+    using namespace kaldi;
+    using namespace kaldi::nnet3;
+    typedef kaldi::int32 int32;
+    typedef kaldi::int64 int64;
+
+    const char *usage =
+        "Propagate features through an xvector neural network model and write\n"
+        "the output vectors.  \"Xvector\" is our term for a vector or\n"
+        "embedding which is the output of a particular type of neural network\n"
+        "architecture found in speaker recognition.  This architecture\n"
+        "consists of several layers that operate on frames, a statistics\n"
+        "pooling layer that aggregates over the frame-level representations\n"
+        "and possibly additional layers that operate on segment-level\n"
+        "representations.  The xvectors are generally extracted from an\n"
+        "output layer after the statistics pooling layer.  By default, one\n"
+        "xvector is extracted directly from the set of features for each\n"
+        "utterance.  Optionally, xvectors are extracted from chunks of input\n"
+        "features and averaged, to produce a single vector.\n"
+        "\n"
+        "Usage: nnet3-xvector-compute [options] <raw-nnet-in> "
+        "<features-rspecifier> <vector-wspecifier>\n"
+        "e.g.: nnet3-xvector-compute final.raw scp:feats.scp "
+        "ark:nnet_prediction.ark\n"
+        "See also: nnet3-compute\n";
+
+    ParseOptions po(usage);
+    Timer timer;
+
+    NnetSimpleComputationOptions opts;
+    opts.acoustic_scale = 1.0; // by default do no scaling in this recipe.
+
+    std::string use_gpu = "no";
+    int32 chunk_size = -1,
+      min_chunk_size = 100;
+
+    opts.Register(&po);
+    po.Register("use-gpu", &use_gpu,
+      "yes|no|optional|wait, only has effect if compiled with CUDA");
+    po.Register("chunk-size", &chunk_size,
+      "If set, extracts xectors from specified chunk-size, and averages.  "
+      "If not set, extracts an xvector from all available features.");
+    po.Register("min-chunk-size", &min_chunk_size,
+      "Minimum chunk-size allowed when extracting xvectors.");
+
+    po.Read(argc, argv);
+
+    if (po.NumArgs() != 3) {
+      po.PrintUsage();
+      exit(1);
+    }
+
+#if HAVE_CUDA==1
+    CuDevice::Instantiate().SelectGpuId(use_gpu);
+#endif
+
+    std::string nnet_rxfilename = po.GetArg(1),
+                feature_rspecifier = po.GetArg(2),
+                vector_wspecifier = po.GetArg(3);
+
+    Nnet nnet;
+    ReadKaldiObject(nnet_rxfilename, &nnet);
+    SetBatchnormTestMode(true, &nnet);
+    SetDropoutTestMode(true, &nnet);
+    CollapseModel(CollapseModelConfig(), &nnet);
+
+    CachingOptimizingCompiler compiler(nnet, opts.optimize_config);
+
+    BaseFloatVectorWriter vector_writer(vector_wspecifier);
+
+    int32 num_success = 0, num_fail = 0;
+    int64 frame_count = 0;
+    int32 xvector_dim = nnet.OutputDim("output");
+
+    SequentialBaseFloatMatrixReader feature_reader(feature_rspecifier);
+
+    for (; !feature_reader.Done(); feature_reader.Next()) {
+      std::string utt = feature_reader.Key();
+      const Matrix<BaseFloat> &features (feature_reader.Value());
+      if (features.NumRows() == 0) {
+        KALDI_WARN << "Zero-length utterance: " << utt;
+        num_fail++;
+        continue;
+      }
+      int32 num_rows = features.NumRows(),
+            feat_dim = features.NumCols(),
+            this_chunk_size = chunk_size;
+
+      if (num_rows < min_chunk_size) {
+        KALDI_WARN << "Minimum chunk size of " << min_chunk_size
+                   << " is greater than the number of rows "
+                   << "in utterance: " << utt;
+        num_fail++;
+        continue;
+      } else if (num_rows < chunk_size) {
+        KALDI_LOG << "Chunk size of " << chunk_size << " is greater than "
+                  << "the number of rows in utterance: " << utt
+                  << ", using chunk size  of " << num_rows;
+        this_chunk_size = num_rows;
+      } else if (chunk_size == -1) {
+        this_chunk_size = num_rows;
+      }
+
+      int32 num_chunks = ceil(
+        num_rows / static_cast<BaseFloat>(this_chunk_size));
+      Vector<BaseFloat> xvector_avg(xvector_dim, kSetZero);
+      BaseFloat tot_weight = 0.0;
+
+      // Iterate over the feature chunks.
+      for (int32 chunk_indx = 0; chunk_indx < num_chunks; chunk_indx++) {
+        // If we're nearing the end of the input, we may need to shift the
+        // offset back so that we can get this_chunk_size frames of input to
+        // the nnet.
+        int32 offset = std::min(
+          this_chunk_size, num_rows - chunk_indx * this_chunk_size);
+        if (offset < min_chunk_size)
+          continue;
+        SubMatrix<BaseFloat> sub_features(
+          features, chunk_indx * this_chunk_size, offset, 0, feat_dim);
+        Vector<BaseFloat> xvector;
+        tot_weight += offset;
+        RunNnetComputation(sub_features, nnet, &compiler, &xvector);
+        xvector_avg.AddVec(offset, xvector);
+      }
+      xvector_avg.Scale(1.0 / tot_weight);
+      vector_writer.Write(utt, xvector_avg);
+
+      frame_count += features.NumRows();
+      num_success++;
+    }
+
+#if HAVE_CUDA==1
+    CuDevice::Instantiate().PrintProfile();
+#endif
+    double elapsed = timer.Elapsed();
+    KALDI_LOG << "Time taken "<< elapsed
+              << "s: real-time factor assuming 100 frames/sec is "
+              << (elapsed*100.0/frame_count);
+    KALDI_LOG << "Done " << num_success << " utterances, failed for "
+              << num_fail;
+
+    if (num_success != 0) return 0;
+    else return 1;
+  } catch(const std::exception &e) {
+    std::cerr << e.what();
+    return -1;
+  }
+}
diff --git a/src/nnet3bin/nnet3-xvector-get-egs.cc b/src/nnet3bin/nnet3-xvector-get-egs.cc
new file mode 100644 (file)
index 0000000..10d29ee
--- /dev/null
@@ -0,0 +1,229 @@
+// nnet3bin/nnet3-xvector-get-egs.cc
+
+// Copyright 2016-2017  Johns Hopkins University (author:  Daniel Povey)
+//           2016-2017  Johns Hopkins University (author:  Daniel Garcia-Romero)
+//           2016-2017  David Snyder
+
+// See ../../COPYING for clarification regarding multiple authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
+// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
+// MERCHANTABLITY OR NON-INFRINGEMENT.
+// See the Apache 2 License for the specific language governing permissions and
+// limitations under the License.
+
+#include <sstream>
+#include "util/common-utils.h"
+#include "nnet3/nnet-example.h"
+
+namespace kaldi {
+namespace nnet3 {
+
+// A struct for holding information about the position and
+// duration of each chunk.
+struct ChunkInfo {
+  std::string name;
+  int32 output_archive_id;
+  int32 start_frame;
+  int32 num_frames;
+  int32 label;
+};
+
+// Process the range input file and store it as a map from utterance
+// name to vector of ChunkInfo structs.
+static void ProcessRangeFile(const std::string &range_rxfilename,
+    unordered_map<std::string, std::vector<ChunkInfo *> > *utt_to_chunks) {
+  Input range_input(range_rxfilename);
+  if (!range_rxfilename.empty()) {
+    std::string line;
+    while (std::getline(range_input.Stream(), line)) {
+      ChunkInfo *chunk_info = new ChunkInfo();
+      std::vector<std::string> fields;
+      SplitStringToVector(line, " \t\n\r", true, &fields);
+      if (fields.size() != 6)
+        KALDI_ERR << "Expected 6 fields in line of range file, got "
+                  << fields.size() << " instead.";
+
+      std::string utt = fields[0],
+                  start_frame_str = fields[3],
+                  num_frames_str = fields[4],
+                  label_str = fields[5];
+
+      if (!ConvertStringToInteger(fields[1], &(chunk_info->output_archive_id))
+        || !ConvertStringToInteger(start_frame_str, &(chunk_info->start_frame))
+        || !ConvertStringToInteger(num_frames_str, &(chunk_info->num_frames))
+        || !ConvertStringToInteger(label_str, &(chunk_info->label)))
+        KALDI_ERR << "Expected integer for output archive in range file.";
+
+      chunk_info->name = utt + "-" + start_frame_str + "-" + num_frames_str
+        + "-" + label_str;
+      unordered_map<std::string, std::vector<ChunkInfo*> >::iterator
+        got = utt_to_chunks->find(utt);
+
+      if (got == utt_to_chunks->end()) {
+        std::vector<ChunkInfo* > chunk_infos;
+        chunk_infos.push_back(chunk_info);
+        utt_to_chunks->insert(std::pair<std::string,
+          std::vector<ChunkInfo* > > (utt, chunk_infos));
+      } else {
+        got->second.push_back(chunk_info);
+      }
+    }
+  }
+}
+
+static void WriteExamples(const MatrixBase<BaseFloat> &feats,
+    const std::vector<ChunkInfo *> &chunks, const std::string &utt,
+    bool compress, int32 num_pdfs, int32 *num_egs_written,
+    std::vector<NnetExampleWriter *> *example_writers) {
+  for (std::vector<ChunkInfo *>::const_iterator it = chunks.begin();
+      it != chunks.end(); ++it) {
+    ChunkInfo *chunk = *it;
+    NnetExample eg;
+    int32 num_rows = feats.NumRows(),
+          feat_dim = feats.NumCols();
+    if (num_rows < chunk->num_frames) {
+      KALDI_WARN << "Unable to create examples for utterance " << utt
+                 << ". Requested chunk size of "
+                 << chunk->num_frames
+                 << " but utterance has only " << num_rows << " frames.";
+    } else {
+      // The requested chunk positions are approximate. It's possible
+      // that they slightly exceed the number of frames in the utterance.
+      // If that occurs, we can shift the chunks location back slightly.
+      int32 shift = std::min(0, num_rows - chunk->start_frame
+                                 - chunk->num_frames);
+      SubMatrix<BaseFloat> chunk_mat(feats, chunk->start_frame + shift,
+                                  chunk->num_frames, 0, feat_dim);
+      NnetIo nnet_input = NnetIo("input", 0, chunk_mat);
+      for (std::vector<Index>::iterator indx_it = nnet_input.indexes.begin();
+          indx_it != nnet_input.indexes.end(); ++indx_it)
+        indx_it->n = 0;
+
+      Posterior label;
+      std::vector<std::pair<int32, BaseFloat> > post;
+      post.push_back(std::pair<int32, BaseFloat>(chunk->label, 1.0));
+      label.push_back(post);
+      NnetExample eg;
+      eg.io.push_back(nnet_input);
+      eg.io.push_back(NnetIo("output", num_pdfs, 0, label));
+      if (compress)
+        eg.Compress();
+
+      if (chunk->output_archive_id >= example_writers->size())
+        KALDI_ERR << "Requested output index exceeds number of specified "
+                  << "output files.";
+      (*example_writers)[chunk->output_archive_id]->Write(
+                         chunk->name, eg);
+      (*num_egs_written) += 1;
+    }
+  }
+}
+
+} // namespace nnet3
+} // namespace kaldi
+
+int main(int argc, char *argv[]) {
+  try {
+    using namespace kaldi;
+    using namespace kaldi::nnet3;
+    typedef kaldi::int32 int32;
+
+    const char *usage =
+        "Get examples for training an nnet3 neural network for the xvector\n"
+        "system.  Each output example contains a chunk of features from some\n"
+        "utterance along with a speaker label.  The location and length of\n"
+        "the feature chunks are specified in the 'ranges' file.  Each line\n"
+        "is interpreted as follows:\n"
+        "  <source-utterance> <relative-output-archive-index> "
+        "<absolute-archive-index> <start-frame-index> <num-frames> "
+        "<speaker-label>\n"
+        "where <relative-output-archive-index> is interpreted as a zero-based\n"
+        "index into the wspecifiers provided on the command line (<egs-0-out>\n"
+        "and so on), and <absolute-archive-index> is ignored by this program.\n"
+        "For example:\n"
+        "  utt1  3  13  65  300  3\n"
+        "  utt1  0  10  50  400  3\n"
+        "  utt2  ...\n"
+        "\n"
+        "Usage:  nnet3-xvector-get-egs [options] <ranges-filename> "
+        "<features-rspecifier> <egs-0-out> <egs-1-out> ... <egs-N-1-out>\n"
+        "\n"
+        "For example:\n"
+        "nnet3-xvector-get-egs ranges.1 \"$feats\" ark:egs_temp.1.ark"
+        "  ark:egs_temp.2.ark ark:egs_temp.3.ark\n";
+
+    bool compress = true;
+    int32 num_pdfs = -1;
+
+    ParseOptions po(usage);
+    po.Register("compress", &compress, "If true, write egs in "
+                "compressed format.");
+    po.Register("num-pdfs", &num_pdfs, "Number of speakers in the training "
+                "list.");
+
+    po.Read(argc, argv);
+
+    if (po.NumArgs() < 3) {
+      po.PrintUsage();
+      exit(1);
+    }
+
+    std::string  range_rspecifier = po.GetArg(1),
+        feature_rspecifier = po.GetArg(2);
+    std::vector<NnetExampleWriter *> example_writers;
+
+    for (int32 i = 3; i <= po.NumArgs(); i++)
+      example_writers.push_back(new NnetExampleWriter(po.GetArg(i)));
+
+    unordered_map<std::string, std::vector<ChunkInfo *> > utt_to_chunks;
+    ProcessRangeFile(range_rspecifier, &utt_to_chunks);
+    SequentialBaseFloatMatrixReader feat_reader(feature_rspecifier);
+
+    int32 num_done = 0,
+          num_err = 0,
+          num_egs_written = 0;
+
+    for (; !feat_reader.Done(); feat_reader.Next()) {
+      std::string key = feat_reader.Key();
+      const Matrix<BaseFloat> &feats = feat_reader.Value();
+      unordered_map<std::string, std::vector<ChunkInfo*> >::iterator
+        got = utt_to_chunks.find(key);
+      if (got == utt_to_chunks.end()) {
+        KALDI_WARN << "Could not create examples from utterance "
+                   << key << " because it has no entry in the ranges "
+                  <<  "input file.";
+        num_err++;
+      } else {
+        std::vector<ChunkInfo *> chunks = got->second;
+        WriteExamples(feats, chunks, key, compress, num_pdfs,
+                      &num_egs_written, &example_writers);
+        num_done++;
+      }
+    }
+
+    // Free memory
+    for (unordered_map<std::string, std::vector<ChunkInfo*> >::iterator
+        map_it = utt_to_chunks.begin();
+        map_it != utt_to_chunks.end(); ++map_it) {
+      DeletePointers(&map_it->second);
+    }
+    DeletePointers(&example_writers);
+
+    KALDI_LOG << "Finished generating examples, "
+              << "successfully processed " << num_done
+              << " feature files, wrote " << num_egs_written << " examples; "
+              << num_err << " files had errors.";
+    return (num_egs_written == 0 || num_err > num_done ? 1 : 0);
+  } catch(const std::exception &e) {
+    std::cerr << e.what() << '\n';
+    return -1;
+  }
+}