Шумопонижение в CMU Sphinx

    Можно смело заявить, что на сегодня CMU Sphinx стал лидером среди свободного софта для распознавания речи. Pocketsphinx поставляется вместе с Ubuntu, многообещающий проект Simon построен с широким его использованием, а структура корпуса Voxforge как бы намекает, что создан он для sphinxtrain.

    Несмотря на бурное развитие самого Sphinx'а и методов распознавания речи вообще, каждый, кто пытался использовать его на практике, знает, насколько сложно получить вменяемый результат даже для простых задач. А все потому, что нельзя просто подключить дефолтные модели и ожидать, что система вас поймет. Требуется адаптировать акустику, построить релевантную языковую модель, найти оптимальные параметры и конфигурацию движка — вобщем, потратить недели времени, кропотливо снижая ошибку процент за процентом. Как человек, потративший эти самые недели, могу заверить, что и в этом случае вам ничего не гарантировано. Особенно, если вы хотите распознавать речь, записанную не гарнитурой, а встроенным микрофоном ноутбука, как это часто бывает.

    Вообще, фундаментальная причина плохого распознавания — несовпадение обучающих и тестовых условий (немного кривая калька с conditions mismatch). Туда можно отнести все: незнакомых дикторов, несовпадающие характеристики каналов, неадекватную языковую модель, и даже проявление эмоций, которых мы не ожидали от пользователя. В случае с ноутбучным микрофоном имеем различные аддитивные шумы и эхо, которых не было в обучающей базе, и которые могут значительно уронить точность распознавания.

    Предыстория


    Реализация шумопонижения в CMU Sphinx началась ровно год назад вот с этого поста Николая Шмырева (низкий ему поклон за всё, кстати): Around noise-robust PNCC features. Через два месяца состоялся коммит, но первое упоминание в FAQ появилось только 10 июня 2014. До этого момента с шумами предлагали бороться с помощью адаптации к каналу (весьма дельный совет, кстати, который никто не отменял). Так что, для экспериментов вам понадобятся новейшая на сегодня версия 0.8.

    Описание самого алгоритма приведено в фундаментальной статье и в посте Николая. Вкратце, алгоритм весьма похож на MFCC, а модификации обусловлены исследованиями в области слуховой системы человека. Понижение/подавление шумов в системах распознавания речи — весьма обширная облать, в которую я не буду углубляться, поскольку не шарю. Расскажу только, как реализовать ее на практике. Данный пост является обобщением информации, найденной мной в статьях и на форумах. От вас потребуется знакомство со сфинксом. В противном случае, добро пожаловать в вики.

    Шумопонижение на практике


    Если PNCCs — это просто новые признаки такие, логично предположить, что их можно использовать, указав соответствующее значение для -feat. А вот и нет, ха-ха. В данном случае, реализация представляет собой модификацию уже существующего механизма feature extraction. И выглядит это немного по-разному для pocketsphinx и Sphinx4. Но давайте по порядку.

    Создание акустических моделей

    Итак, прежде чем приступить к распознаванию, нам нужны акустические модели. Существующие модели нам не подойдут, потому что получены они пока обычным способом, а значит попытка их использования в шумоустойчивой системе породит тот самый фундаментальный mismatch. Поэтому модели нужно будет натренировать заново. Для этого, соответственно, нужен корпус и установленные sphinxbase и sphinxtrain. В качестве корпуса рекомендую voxforge, который нужно будет слегка модифицировать.

    Вот здесь мы и подходим к самому главному. Как вы, наверное, знаете, sphinxtrain управляется общим конфигом (sphinx_train.cfg), который задает все параметры для обучения (и тестирования) моделей, и дополнительно feat.params, в котором указываются параметры feature extraction. Начиная с версии 0.8, некоторые утилиты сфинкса получили дополнительные параметры, отвечающие за шумопонижение. А именно -remove_noise и -lifter. Для -remove_noise нужно задать значение yes (впрочем, это его дефолтное значение), а обычное значение параметра -lifter — 22. Если задавать его в основном конфиге:
    $CFG_LIFTER = "22"; # Cepstrum lifter is smoothing to improve recognition
    

    то можно читать его оттуда:
    -lifter __CFG_LIFTER__
    


    Еще один важный для нас параметр — это -transform. Его дефолтное значение legacy, но нам нужно dct. Итак, чтобы натренировать шумоустойчивые модели, нам нужно задать в feat.params трио параметров:
    -transform dct
    -remove_noise yes
    -lifter 22
    


    Но все же, лучше перенести их в sphinx_train.cfg, как это делается для других параметров:

    $CFG_TRANSFORM = "dct"; # Previously legacy transform is used, but dct is more accurate
    $CFG_LIFTER = "22"; # Cepstrum lifter is smoothing to improve recognition
    

    feat.params:
    -transform __CFG_TRANSFORM__
    -remove_noise yes
    -lifter __CFG_LIFTER__
    


    Надо понимать, что sphinxtrain — это всего лишь скрипт-обертка для отдельных утилит, таких как fe, поэтому если вы вызываете их отдельно, нужно всегда задавать эти параметры (если они есть).

    Вот пример моих конфигов для voxforge-en:

    sphinx_train.cfg:
    # Configuration script for sphinx trainer                  -*-mode:Perl-*-
    
    $CFG_VERBOSE = 1;		# Determines how much goes to the screen.
    
    # These are filled in at configuration time
    $CFG_DB_NAME = "voxforge_en";
    # Experiment name, will be used to name model files and log files
    $CFG_EXPTNAME = "$CFG_DB_NAME";
    
    # Directory containing SphinxTrain binaries
    $CFG_BASE_DIR = "/home/speechdat/voxforge-en";
    $CFG_SPHINXTRAIN_DIR = "/usr/local/lib/sphinxtrain";
    $CFG_BIN_DIR = "/usr/local/libexec/sphinxtrain";
    $CFG_SCRIPT_DIR = "/usr/local/lib/sphinxtrain/scripts";
    
    
    # Audio waveform and feature file information
    $CFG_WAVFILES_DIR = "$CFG_BASE_DIR/wav";
    $CFG_WAVFILE_EXTENSION = 'wav';
    $CFG_WAVFILE_TYPE = 'mswav'; # one of nist, mswav, raw
    $CFG_FEATFILES_DIR = "$CFG_BASE_DIR/feat";
    $CFG_FEATFILE_EXTENSION = 'mfc';
    $CFG_VECTOR_LENGTH = 13;
    
    
    # Feature extraction parameters
    $CFG_WAVFILE_SRATE = 16000.0;
    $CFG_NUM_FILT = 40; # For wideband speech it's 40, for telephone 8khz reasonable value is 31
    $CFG_LO_FILT = 133.33334; # For telephone 8kHz speech value is 200
    $CFG_HI_FILT = 6855.4976; # For telephone 8kHz speech value is 3500
    $CFG_TRANSFORM = "dct"; # Previously legacy transform is used, but dct is more accurate
    $CFG_LIFTER = "22"; # Cepstrum lifter is smoothing to improve recognition
    
    $CFG_MIN_ITERATIONS = 1;  # BW Iterate at least this many times
    $CFG_MAX_ITERATIONS = 10; # BW Don't iterate more than this, somethings likely wrong.
    
    # (none/max) Type of AGC to apply to input files
    $CFG_AGC = 'none';
    # (current/none) Type of cepstral mean subtraction/normalization
    # to apply to input files
    $CFG_CMN = 'current';
    $CFG_CMNINIT = 10.0;
    # (yes/no) Normalize variance of input files to 1.0
    $CFG_VARNORM = 'no';
    # (yes/no) Train full covariance matrices
    $CFG_FULLVAR = 'no';
    # (yes/no) Use diagonals only of full covariance matrices for
    # Forward-Backward evaluation (recommended if CFG_FULLVAR is yes)
    $CFG_DIAGFULL = 'no';
    
    # (yes/no) Perform vocal tract length normalization in training.  This
    # will result in a "normalized" model which requires VTLN to be done
    # during decoding as well.
    $CFG_VTLN = 'no';
    # Starting warp factor for VTLN
    $CFG_VTLN_START = 0.80;
    # Ending warp factor for VTLN
    $CFG_VTLN_END = 1.40;
    # Step size of warping factors
    $CFG_VTLN_STEP = 0.05;
    
    # Directory to write queue manager logs to
    $CFG_QMGR_DIR = "$CFG_BASE_DIR/qmanager";
    # Directory to write training logs to
    $CFG_LOG_DIR = "$CFG_BASE_DIR/logdir";
    # Directory for re-estimation counts
    $CFG_BWACCUM_DIR = "$CFG_BASE_DIR/bwaccumdir";
    # Directory to write model parameter files to
    $CFG_MODEL_DIR = "$CFG_BASE_DIR/model_parameters";
    
    # Directory containing transcripts and control files for
    # speaker-adaptive training
    $CFG_LIST_DIR = "$CFG_BASE_DIR/etc";
    
    
    # Decoding variables for MMIE training
    $CFG_LANGUAGEWEIGHT = "11.5";
    $CFG_BEAMWIDTH      = "1e-100";
    $CFG_WORDBEAM       = "1e-80";
    $CFG_LANGUAGEMODEL  = "$CFG_LIST_DIR/${CFG_DB_NAME}_full.lm.DMP";
    $CFG_WORDPENALTY    = "0.2";
    
    # Lattice pruning variables
    $CFG_ABEAM              = "1e-50";
    $CFG_NBEAM              = "1e-10";
    $CFG_PRUNED_DENLAT_DIR  = "$CFG_BASE_DIR/pruned_denlat";
    
    # MMIE training related variables
    $CFG_MMIE = "no";
    $CFG_MMIE_MAX_ITERATIONS = 5;
    $CFG_LATTICE_DIR = "$CFG_BASE_DIR/lattice";
    $CFG_MMIE_TYPE   = "best"; # Valid values are "rand", "best" or "ci"
    $CFG_MMIE_CONSTE = "3.0";
    $CFG_NUMLAT_DIR  = "$CFG_BASE_DIR/numlat";
    $CFG_DENLAT_DIR  = "$CFG_BASE_DIR/denlat";
    
    
    # Variables used in main training of models
    $CFG_DICTIONARY     = "$CFG_LIST_DIR/$CFG_DB_NAME.dict";
    $CFG_RAWPHONEFILE   = "$CFG_LIST_DIR/$CFG_DB_NAME.phone";
    $CFG_FILLERDICT     = "$CFG_LIST_DIR/$CFG_DB_NAME.filler";
    $CFG_LISTOFFILES    = "$CFG_LIST_DIR/${CFG_DB_NAME}_full.fileids";
    $CFG_TRANSCRIPTFILE = "$CFG_LIST_DIR/${CFG_DB_NAME}_full.transcription";
    $CFG_FEATPARAMS     = "$CFG_LIST_DIR/feat.params";
    
    # Variables used in characterizing models
    
    $CFG_HMM_TYPE = '.cont.'; # Sphinx 4, PocketSphinx
    #$CFG_HMM_TYPE  = '.semi.'; # PocketSphinx
    #$CFG_HMM_TYPE  = '.ptm.'; # PocketSphinx (larger data sets)
    
    if (($CFG_HMM_TYPE ne ".semi.")
        and ($CFG_HMM_TYPE ne ".ptm.")
        and ($CFG_HMM_TYPE ne ".cont.")) {
      die "Please choose one CFG_HMM_TYPE out of '.cont.', '.ptm.', or '.semi.', " .
        "currently $CFG_HMM_TYPE\n";
    }
    
    # This configuration is fastest and best for most acoustic models in
    # PocketSphinx and Sphinx-III.  See below for Sphinx-II.
    $CFG_STATESPERHMM = 3;
    $CFG_SKIPSTATE = 'no';
    
    if ($CFG_HMM_TYPE eq '.semi.') {
      $CFG_DIRLABEL = 'semi';
    # Four stream features for PocketSphinx
      $CFG_FEATURE = "s2_4x";
      $CFG_NUM_STREAMS = 4;
      $CFG_INITIAL_NUM_DENSITIES = 256;
      $CFG_FINAL_NUM_DENSITIES = 256;
      die "For semi continuous models, the initial and final models have the same density" 
        if ($CFG_INITIAL_NUM_DENSITIES != $CFG_FINAL_NUM_DENSITIES);
    } elsif ($CFG_HMM_TYPE eq '.ptm.') {
      $CFG_DIRLABEL = 'ptm';
    # Four stream features for PocketSphinx
      $CFG_FEATURE = "s2_4x";
      $CFG_NUM_STREAMS = 4;
      $CFG_INITIAL_NUM_DENSITIES = 64;
      $CFG_FINAL_NUM_DENSITIES = 64;
      die "For phonetically tied models, the initial and final models have the same density" 
        if ($CFG_INITIAL_NUM_DENSITIES != $CFG_FINAL_NUM_DENSITIES);
    } elsif ($CFG_HMM_TYPE eq '.cont.') {
      $CFG_DIRLABEL = 'cont';
    # Single stream features - Sphinx 3
      $CFG_FEATURE = "1s_c_d_dd";
      $CFG_NUM_STREAMS = 1;
      $CFG_INITIAL_NUM_DENSITIES = 1;
      $CFG_FINAL_NUM_DENSITIES = 32;
      die "The initial has to be less than the final number of densities" 
        if ($CFG_INITIAL_NUM_DENSITIES > $CFG_FINAL_NUM_DENSITIES);
    }
    
    # Number of top gaussians to score a frame. A little bit less accurate computations
    # make training significantly faster. Uncomment to apply this during the training
    # For good accuracy make sure you are using the same setting in decoder
    # In theory this can be different for various training stages. For example 4 for
    # CI stage and 16 for CD stage
    # $CFG_CI_TOPN = 4;
    # $CFG_CD_TOPN = 16;
    
    # (yes/no) Train multiple-gaussian context-independent models (useful
    # for alignment, use 'no' otherwise) in the models created
    # specifically for forced alignment
    $CFG_FALIGN_CI_MGAU = 'no';
    # (yes/no) Train multiple-gaussian context-independent models (useful
    # for alignment, use 'no' otherwise)
    $CFG_CI_MGAU = 'no';
    # Number of tied states (senones) to create in decision-tree clustering
    $CFG_N_TIED_STATES = 3000;
    # How many parts to run Forward-Backward estimatinon in
    $CFG_NPART = 1;
    
    # (yes/no) Train a single decision tree for all phones (actually one
    # per state) (useful for grapheme-based models, use 'no' otherwise)
    $CFG_CROSS_PHONE_TREES = 'no';
    
    # Use force-aligned transcripts (if available) as input to training
    $CFG_FORCEDALIGN = 'no';
    
    # Use a specific set of models for force alignment.  If not defined,
    # context-independent models for the current experiment will be used.
    $CFG_FORCE_ALIGN_MDEF = "$CFG_BASE_DIR/model_architecture/$CFG_EXPTNAME.falign_ci.mdef";
    $CFG_FORCE_ALIGN_MODELDIR = "$CFG_MODEL_DIR/$CFG_EXPTNAME.falign_ci_$CFG_DIRLABEL";
    
    # Use a specific dictionary and filler dictionary for force alignment.
    # If these are not defined, a dictionary and filler dictionary will be
    # created from $CFG_DICTIONARY and $CFG_FILLERDICT, with noise words
    # removed from the filler dictionary and added to the dictionary (this
    # is because the force alignment is not very good at inserting them)
    
    # $CFG_FORCE_ALIGN_DICTIONARY = "$ST::CFG_BASE_DIR/falignout$ST::CFG_EXPTNAME.falign.dict";;
    # $CFG_FORCE_ALIGN_FILLERDICT = "$ST::CFG_BASE_DIR/falignout/$ST::CFG_EXPTNAME.falign.fdict";;
    
    # Use a particular beam width for force alignment.  The wider
    # (i.e. smaller numerically) the beam, the fewer sentences will be
    # rejected for bad alignment.
    $CFG_FORCE_ALIGN_BEAM = 1e-60;
    
    # Calculate an LDA/MLLT transform?
    $CFG_LDA_MLLT = 'yes';
    # Dimensionality of LDA/MLLT output
    $CFG_LDA_DIMENSION = 29;
    
    # This is actually just a difference in log space (it doesn't make
    # sense otherwise, because different feature parameters have very
    # different likelihoods)
    $CFG_CONVERGENCE_RATIO = 0.1;
    
    # Queue::POSIX for multiple CPUs on a local machine
    # Queue::PBS to use a PBS/TORQUE queue
    $CFG_QUEUE_TYPE = "Queue::POSIX";
    
    # Name of queue to use for PBS/TORQUE
    $CFG_QUEUE_NAME = "workq";
    
    # (yes/no) Build questions for decision tree clustering automatically
    $CFG_MAKE_QUESTS = "yes";
    # If CFG_MAKE_QUESTS is yes, questions are written to this file.
    # If CFG_MAKE_QUESTS is no, questions are read from this file.
    $CFG_QUESTION_SET = "${CFG_BASE_DIR}/model_architecture/${CFG_EXPTNAME}.tree_questions";
    #$CFG_QUESTION_SET = "${CFG_BASE_DIR}/linguistic_questions";
    
    $CFG_CP_OPERATION = "${CFG_BASE_DIR}/model_architecture/${CFG_EXPTNAME}.cpmeanvar";
    
    # This variable has to be defined, otherwise utils.pl will not load.
    $CFG_DONE = 1;
    
    return 1;
    



    feat.params:
    -alpha 0.97
    -dither yes
    -doublebw no
    -nfilt __CFG_NUM_FILT__
    -ncep __CFG_VECTOR_LENGTH__
    -lowerf __CFG_LO_FILT__
    -upperf __CFG_HI_FILT__
    -samprate __CFG_WAVFILE_SRATE__
    -nfft 512
    -wlen 0.0256
    -transform __CFG_TRANSFORM__
    -feat __CFG_FEATURE__
    -agc __CFG_AGC__
    -cmn __CFG_CMN__
    -varnorm __CFG_VARNORM__
    -remove_noise yes
    -lifter __CFG_LIFTER__
    



    Конечно, тренировка акустических моделей — это тот еще геморрой труд. Помимо специфических знаний, она требует установки sphinxbase и sphinxtrain и длится около суток. Поэтому я расшарил свои модели, натренированные на voxforge-en по вышеприведенному рецепту: dropbox.

    Использование акустических моделей

    Имея модели, мы можем, наконец, свободно вздохнуть и подключить их в свою систему. Здесь рецепты разнятся в зависимости от того, используете вы pocketsphinx или Sphinx4. С pocketsphinx все просто: нужно просто задать трио параметров -transform, -remove_noise и -lifter. А ежели мы хотим использовать Sphinx4, то нужно включить во фронтенд компонент Denoise и немного изменить сам фронтенд. Соответствующий конвеер будет выглядеть примерно так:

    1. AudioFileDataSource
    2. Dither
    3. Preemphasizer
    4. RaisedCosineWindower
    5. DiscreteFourierTransform
    6. MelFrequencyFilterBank
    7. Denoise
    8. DiscreteCosineTransform2
    9. Lifter
    10. BatchCMN
    11. DeltasFeatureExtractor
    12. FeatureTransform

    NB: featureTransform нужен, только если вы применяли LDA/MLLT в обучении моделей.

    Три компонента, выделенные жирным, и обеспечивают шумопонижение.

    В XML соответствующая часть конфига будет выглядеть так:

    config.xml
    <component name="mfcFrontEnd"
              type="edu.cmu.sphinx.frontend.FrontEnd">
            <propertylist name="pipeline">
                <item>audioFileDataSource</item>
                <item>dither</item>
                <item>preemphasizer</item>
                <item>windower</item>
                <item>fft</item>
                <item>melFilterBank</item>
                <item>denoise</item>
                <item>dct</item>
                <item>lifter</item>
                <item>batchCMN</item>
                <item>featureExtraction</item>
                <item>featureTransform</item>
            </propertylist>
        </component>    
    
        <component name="audioFileDataSource"
              type="edu.cmu.sphinx.frontend.util.AudioFileDataSource">
        </component>
    
        <component name="preemphasizer"
              type="edu.cmu.sphinx.frontend.filter.Preemphasizer">
        </component>
    
        <component name="dither"
              type="edu.cmu.sphinx.frontend.filter.Dither">
        </component>
    
        <component name="windower"
              type="edu.cmu.sphinx.frontend.window.RaisedCosineWindower">
        </component>
    
        <component name="fft"
              type="edu.cmu.sphinx.frontend.transform.DiscreteFourierTransform">
        </component>
    
        <component name="melFilterBank"
              type="edu.cmu.sphinx.frontend.frequencywarp.MelFrequencyFilterBank">
            <property name="numberFilters" value="40"/>
            <property name="minimumFrequency" value="133.33334"/>
            <property name="maximumFrequency" value="6855.4976"/>
        </component>
        
        <component name="denoise" 
    	  type="edu.cmu.sphinx.frontend.denoise.Denoise">
        </component>
    
        <component name="dct"
              type="edu.cmu.sphinx.frontend.transform.DiscreteCosineTransform2">
        </component>
        
        <component name="lifter" 
    	  type="edu.cmu.sphinx.frontend.transform.Lifter">
        </component>
    
        <component name="batchCMN"
              type="edu.cmu.sphinx.frontend.feature.BatchCMN">
        </component>
    
        <component name="featureExtraction"
              type="edu.cmu.sphinx.frontend.feature.DeltasFeatureExtractor">
        </component>
    
        <component name="featureTransform"
              type="edu.cmu.sphinx.frontend.feature.FeatureTransform">
            <property name="loader" value="modelLoader"/>
        </component>
    
    



    Это работает?


    Вполне. Для своей задачи я получил прирост в точности распознавания на 6.5%: с 74.65% до 81.38%. Но все равно, адаптацию к каналу стоит проводить. И будьте осторожны в применении этого механизма: на чистом аудио он может ухудшить результат.
    • +5
    • 14.7k
    • 2
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 2

      +1
      Сорри за оффтоп, но есть три вопроса:

      Первый по постскриптуму: нельзя ли детектировать зашумлённость канала каким-нибудь простым детектором, например просто считать энтропию распределения, или дисперсию? Не понизит ли это ошибку

      Второй: А как системы распознавания речи работают на голосовых синтезаторах? Скажем, Сфинкс хорошо распознаёт Фестиваль?

      Третий: В сравнении с коммерческими разработками, насколько хуже работает Сфинкс? Субъективно?
        0
        Какие интересные вопросы… Постараюсь ответить =)

        1. Я думаю, Signal-to-noise ratio и Signal-to-interference ratio должны подойти в качестве метрики.

        2. Никогда не слышал ни о чем подобном. Робот позвонил в хелпдеск, а нам том конце тоже робот? =) Могу предположить, что качество распознавания будет неплохим (лучше чем для человеческой речи), если натренировать модели на синтезированной речи.

        3. Для начала, невозможно провести такую черту, потому что сфинкс часто используется, и весьма успешно, в коммерческих разработках. В любом случае, алгоритмы в основе лежат те же. Поэтому сравнивать нужно не движки, а акустические и языковые модели, фронтенды отвечающие за шумопонижение, вобщем весь тот обвес, который и создает в конечном итоге систему распознавания. Сфинкс — как лего. Если собрать из него гоночную машину, она застрянет на огороде, где проедет проприетарный трактор. А конкретно не скажу, не сравнивал. Да и корректное сравнение очень тяжело провести. Например, моя система на основе сфинкса работает лучше Google Speech API для одной специфической задачи. Но только потому, что у гугла языковая модель «для всего» и огромный словарь, а моя система конкретно заточена под предметную область. Впрочем, в коммерческих системах активно внедряется акустическое моделирование на DNNs (глубоких нейронных сетях) вместо GMMs, и языковые модели тоже на нейронках. Сфинкс тут пока отстает, но я думаю в скором времени эти техники будут реализованы и в нем.

        Only users with full accounts can post comments. Log in, please.