Поделиться через


Практические лаборатории Распознавание речи с повторяющимися сетями

Обратите внимание, что для работы с этим руководством требуется последняя главная версия или предстоящий выпуск CNTK 1.7.1, который будет выпущен в ближайшее время.

В этой практической лаборатории показано, как реализовать повторяющуюся сеть для обработки текста для задач Air Travel Information Services (ATIS) классификации тегов и намерений слотов. Мы начнем с прямой внедрения, а затем повторяющийся LSTM. Затем мы расширим его, чтобы включить соседние слова и запустить двунаправленно. Наконец, мы превратим эту систему в классификатор намерений.

К методам, которые вы будете применять, относятся:

  • описание модели путем создания блоков слоев вместо написания формул
  • создание собственного блока слоя
  • переменные с разными длинами последовательности в одной сети
  • параллельное обучение

Мы предполагаем, что вы знакомы с основами глубокого обучения и этими конкретными понятиями:

Предварительные требования

Предполагается, что вы уже установили CNTK и можете выполнить команду CNTK. Это руководство было проведено в KDD 2016 и требует недавней сборки, см. здесь, чтобы узнать инструкции по настройке. Вы можете просто следовать инструкциям по скачиванию пакета двоичной установки с этой страницы.

Затем скачайте ZIP-архив (около 12 МБ): щелкните эту ссылку и нажмите кнопку "Скачать". Архив содержит файлы для этого руководства. Заархивируйте и задайте для рабочего каталога значение SLUHandsOn. Файлы, с которыми вы будете работать:

  • SLUHandsOn.cntk: файл конфигурации CNTK, с которым мы познакомимся ниже и будем работать.
  • slu.forward.nobn.cmfslu.forward.lookahead.cmf, slu.forward.cmfи slu.forward.backward.cmf: предварительно обученные модели, которые являются результатом соответствующих конфигураций, которые мы разрабатываем в рамках этого руководства.
  • atis.train.ctf и atis.test.ctf: обучающий и тестовый корпус, уже преобразованный в текстовый формат CNTK (CTF).

Наконец, настоятельно рекомендуется запустить его на компьютере с gpu, совместимым с CUDA. Глубокое обучение без GPU не весело.

Структура задач и моделей

Задача, которую мы хотим использовать в этом руководстве, — это добавление тегов слотов. Мы используем корпус ATIS. ATIS содержит запросы к компьютеру человека из домена служб Air Travel Information Services, и наша задача будет добавлять заметки (тег) каждое слово запроса независимо от того, принадлежит ли он конкретному элементу информации (слоту) и какой из них.

Данные в рабочей папке уже преобразованы в текстовый формат CNTK. Рассмотрим пример из файла atis.test.ctfтестового набора:

19  |S0 178:1 |# BOS      |S1 14:1 |# flight  |S2 128:1 |# O
19  |S0 770:1 |# show                         |S2 128:1 |# O
19  |S0 429:1 |# flights                      |S2 128:1 |# O
19  |S0 444:1 |# from                         |S2 128:1 |# O
19  |S0 272:1 |# burbank                      |S2 48:1  |# B-fromloc.city_name
19  |S0 851:1 |# to                           |S2 128:1 |# O
19  |S0 789:1 |# st.                          |S2 78:1  |# B-toloc.city_name
19  |S0 564:1 |# louis                        |S2 125:1 |# I-toloc.city_name
19  |S0 654:1 |# on                           |S2 128:1 |# O
19  |S0 601:1 |# monday                       |S2 26:1  |# B-depart_date.day_name
19  |S0 179:1 |# EOS                          |S2 128:1 |# O

Этот файл содержит 7 столбцов:

  • идентификатор последовательности (19). Этот идентификатор последовательности содержит 11 записей. Это означает, что последовательность 19 состоит из 11 токенов;
  • столбец S0, содержащий числовые индексы слов;
  • столбец примечаний, обозначаемый пользователем #, позволяет читателю узнать, что означает числовой индекс слова; Столбцы комментариев игнорируются системой. BOS и EOS являются специальными словами для обозначения начала и конца предложения соответственно;
  • столбец — это метка S1 намерения, которую мы будем использовать только в последней части руководства;
  • другой столбец комментариев, показывающий удобочитаемую метку числового индекса намерения;
  • столбец — это метка S2 слота, представленная в виде числового индекса; и
  • другой столбец примечаний, показывающий удобочитаемую метку индекса числовой метки.

Задача нейронной сети заключается в том, чтобы просмотреть запрос (столбец S0) и спрогнозировать метку слота (столбец S2). Как видите, каждому слову во входных данных присваивается пустая метка O или метка слота, начинающаяся с B- первого слова, и I- для любого дополнительного последовательных слов, принадлежащих одному слоту.

Используемая модель — это повторяющаяся модель, состоящая из слоя внедрения, повторяющейся ячейки LSTM и плотного слоя для вычисления вероятностей афишора:

slot label   "O"        "O"        "O"        "O"  "B-fromloc.city_name"
              ^          ^          ^          ^          ^
              |          |          |          |          |
          +-------+  +-------+  +-------+  +-------+  +-------+
          | Dense |  | Dense |  | Dense |  | Dense |  | Dense |  ...
          +-------+  +-------+  +-------+  +-------+  +-------+
              ^          ^          ^          ^          ^
              |          |          |          |          |
          +------+   +------+   +------+   +------+   +------+   
     0 -->| LSTM |-->| LSTM |-->| LSTM |-->| LSTM |-->| LSTM |-->...
          +------+   +------+   +------+   +------+   +------+   
              ^          ^          ^          ^          ^
              |          |          |          |          |
          +-------+  +-------+  +-------+  +-------+  +-------+
          | Embed |  | Embed |  | Embed |  | Embed |  | Embed |  ...
          +-------+  +-------+  +-------+  +-------+  +-------+
              ^          ^          ^          ^          ^
              |          |          |          |          |
w      ------>+--------->+--------->+--------->+--------->+------... 
             BOS      "show"    "flights"    "from"   "burbank"

Или как описание сети CNTK. Ознакомьтесь с кратким представлением и составьте его с описанием выше:

    model = Sequential (
        EmbeddingLayer {150} :
        RecurrentLSTMLayer {300} :
        DenseLayer {labelDim}
    )

Описания этих функций можно найти по адресу: Sequential(), , EmbeddingLayer{}RecurrentLSTMLayer{}иDenseLayer{}

Конфигурация CNTK

Файл конфигурации

Чтобы обучить и протестировать модель в CNTK, необходимо предоставить файл конфигурации, который сообщает CNTK, какие операции требуется выполнить (command переменная) и раздел параметров для каждой команды.

Для команды обучения необходимо указать CNTK:

  • чтение данных (reader раздел)
  • функция модели и ее входные и выходные данные в графе вычислений (BrainScriptNetworkBuilder раздел)
  • hyper-parameters для учащегося (SGD раздел)

Для выполнения команды оценки CNTK необходимо знать, как считывать тестовые данные (reader раздел).

Ниже приведен файл конфигурации, с который мы начнем. Как видите, файл конфигурации CNTK — это текстовый файл, состоящий из определений параметров, которые организованы в иерархии записей. Вы также можете увидеть, как CNTK поддерживает простую замену параметров с помощью синтаксиса $parameterName$ . Фактический файл содержит всего несколько параметров, чем упоминалось выше, но проверьте его и найдите только что упомянутые элементы конфигурации:

# CNTK Configuration File for creating a slot tagger and an intent tagger.

command = TrainTagger:TestTagger

makeMode = false ; traceLevel = 0 ; deviceId = "auto"

rootDir = "." ; dataDir  = "$rootDir$" ; modelDir = "$rootDir$/Models"

modelPath = "$modelDir$/slu.cmf"

vocabSize = 943 ; numLabels = 129 ; numIntents = 26    # number of words in vocab, slot labels, and intent labels

# The command to train the LSTM model
TrainTagger = {
    action = "train"
    BrainScriptNetworkBuilder = {
        inputDim = $vocabSize$
        labelDim = $numLabels$
        embDim = 150
        hiddenDim = 300

        model = Sequential (
            EmbeddingLayer {embDim} :                            # embedding
            RecurrentLSTMLayer {hiddenDim, goBackwards=false} :  # LSTM
            DenseLayer {labelDim}                                # output layer
        )

        # features
        query      = Input {inputDim}
        slotLabels = Input {labelDim}

        # model application
        z = model (query)

        # loss and metric
        ce   = CrossEntropyWithSoftmax (slotLabels, z)
        errs = ClassificationError     (slotLabels, z)

        featureNodes    = (query)
        labelNodes      = (slotLabels)
        criterionNodes  = (ce)
        evaluationNodes = (errs)
        outputNodes     = (z)
    }

    SGD = {
        maxEpochs = 8 ; epochSize = 36000

        minibatchSize = 70

        learningRatesPerSample = 0.003*2:0.0015*12:0.0003
        gradUpdateType = "fsAdaGrad"
        gradientClippingWithTruncation = true ; clippingThresholdPerSample = 15.0

        firstMBsToShowResult = 10 ; numMBsToShowResult = 100
    }

    reader = {
        readerType = "CNTKTextFormatReader"
        file = "$DataDir$/atis.train.ctf"
        randomize = true
        input = {
            query        = { alias = "S0" ; dim = $vocabSize$ ;  format = "sparse" }
            intentLabels = { alias = "S1" ; dim = $numIntents$ ; format = "sparse" }
            slotLabels   = { alias = "S2" ; dim = $numLabels$ ;  format = "sparse" }
        }
    }
}

# Test the model's accuracy (as an error count)
TestTagger = {
    action = "eval"
    modelPath = $modelPath$
    reader = {
        readerType = "CNTKTextFormatReader"
        file = "$DataDir$/atis.test.ctf"
        randomize = false
        input = {
            query        = { alias = "S0" ; dim = $vocabSize$ ;  format = "sparse" }
            intentLabels = { alias = "S1" ; dim = $numIntents$ ; format = "sparse" }
            slotLabels   = { alias = "S2" ; dim = $numLabels$ ;  format = "sparse" }
        }
    }
}

Краткий обзор чтения данных и данных

Мы уже рассмотрели данные. Но как создать этот формат? Для чтения текста в этом руководстве используется .CNTKTextFormatReader Ожидается, что входные данные будут иметь определенный формат, описанный здесь.

В этом руководстве мы создали corpora двумя шагами:

  • преобразуйте необработанные данные в обычный текстовый файл, содержащий столбцы, разделенные пробелами. Например:

    BOS show flights from burbank to st. louis on monday EOS (TAB) flight (TAB) O O O O B-fromloc.city_name O B-toloc.city_name I-toloc.city_name O B-depart_date.day_name O
    

    Это должно быть совместимо с выходными данными paste команды.

  • Преобразуйте его в текстовый формат CNTK (CTF) с помощью следующей команды:

    python Scripts/txt2ctf.py --map query.wl intent.wl slots.wl --annotated True --input atis.test.txt --output atis.test.ctf
    

    где три .wl файла предоставляют словарь как обычные текстовые файлы, по одной строке на слово.

В этих файлах CTFG наши столбцы помечены как S0и S1S2. Они подключены к фактическим сетевым входным данным с помощью соответствующих строк в определении средства чтения:

input = {
    query        = { alias = "S0" ; dim = $vocabSize$ ;  format = "sparse" }
    intentLabels = { alias = "S1" ; dim = $numIntents$ ; format = "sparse" }
    slotLabels   = { alias = "S2" ; dim = $numLabels$ ;  format = "sparse" }
}

Запуск

Приведенный выше файл конфигурации можно найти под именем SLUHandsOn.cntk в рабочей папке. Чтобы запустить его, выполните указанную выше конфигурацию с помощью следующей команды:

cntk  configFile=SLUHandsOn.cntk

Это приведет к выполнению нашей конфигурации, начиная с обучения модели, как определено в разделе, который мы назвали TrainTagger. После нескольких чатых начальных выходных данных журнала вы увидите следующее:

Training 721479 parameters in 6 parameter tensors.

затем следуют выходные данные, как показано ниже:

Finished Epoch[ 1 of 8]: [Training] ce = 0.77274927 * 36007; errs = 15.344% * 36007
Finished Epoch[ 2 of 8]: [Training] ce = 0.27009664 * 36001; errs = 5.883% * 36001
Finished Epoch[ 3 of 8]: [Training] ce = 0.16390425 * 36005; errs = 3.688% * 36005
Finished Epoch[ 4 of 8]: [Training] ce = 0.13121604 * 35997; errs = 2.761% * 35997
Finished Epoch[ 5 of 8]: [Training] ce = 0.09308497 * 36000; errs = 2.028% * 36000
Finished Epoch[ 6 of 8]: [Training] ce = 0.08537533 * 35999; errs = 1.917% * 35999
Finished Epoch[ 7 of 8]: [Training] ce = 0.07477648 * 35997; errs = 1.686% * 35997
Finished Epoch[ 8 of 8]: [Training] ce = 0.06114417 * 36018; errs = 1.380% * 36018

Это показывает, как обучение продолжается по эпохам (проходит через данные). Например, после двух эпох критерий перекрестной энтропии, который мы назвали ce в файле конфигурации, достиг 0,27, как измеряется в образцах 36001 этой эпохи, и что частота ошибок составляет 5,883 % для тех же образцов обучения 36016.

36001 происходит из того факта, что наша конфигурация определила размер эпохи как 36000. Размер эпохи — это количество выборок, которые считаются маркерами слов, а не предложениями, которые обрабатываются между контрольными точками модели. Так как предложения имеют различную длину и не обязательно суммируют до нескольких кратных слов ровно 36000 слов, вы увидите некоторые небольшие варианты.

После завершения обучения (чуть менее 2 минут на Титан-X или Surface Book), CNTK продолжит работу с действием EvalTagger

Final Results: Minibatch[1-1]: errs = 2.922% * 10984; ce = 0.14306181 * 10984; perplexity = 1.15380111

Т.е. на нашем тестовом наборе метки слотов были спрогнозированы с частотой ошибок в 2,9%. Не совсем плохо, для такой простой системы!

На компьютере, доступном только для ЦП, он может быть более медленным в 4 или более раз. Чтобы убедиться, что система выполняется раньше, можно включить трассировку, чтобы увидеть частичные результаты, которые должны отображаться достаточно быстро:

cntk  configFile=SLUHandsOn.cntk  traceLevel=1

Epoch[ 1 of 8]-Minibatch[   1-   1, 0.19%]: ce = 4.86535690 * 67; errs = 100.000% * 67 
Epoch[ 1 of 8]-Minibatch[   2-   2, 0.39%]: ce = 4.83886670 * 63; errs = 57.143% * 63
Epoch[ 1 of 8]-Minibatch[   3-   3, 0.58%]: ce = 4.78657442 * 68; errs = 36.765% * 68
...

Если вы не хотите ждать завершения этого действия, можно запустить промежуточную модель, например.

cntk  configFile=SLUHandsOn.cntk  command=TestTagger  modelPath=Models/slu.cmf.4
Final Results: Minibatch[1-1]: errs = 3.851% * 10984; ce = 0.18932937 * 10984; perplexity = 1.20843890

или протестируйте предварительно обученную модель, которую можно найти в рабочей папке:

cntk  configFile=SLUHandsOn.cntk  command=TestTagger  modelPath=slu.forward.nobn.cmf
Final Results: Minibatch[1-1]: errs = 2.922% * 10984; ce = 0.14306181 * 10984; perplexity = 1.15380111

Изменение модели

В следующем примере вам будут предоставлены задачи по изменению конфигураций CNTK. Решения приведены в конце этого документа... но пожалуйста, попробуйте без!

Слово о программе Sequential()

Прежде чем переходить к задачам, давайте снова рассмотрим модель, которую мы только что запустили. Модель описывается в том, что мы называем стилем композиции функций.

    model = Sequential (
        EmbeddingLayer {embDim} :                            # embedding
        RecurrentLSTMLayer {hiddenDim, goBackwards=false} :  # LSTM
        DenseLayer {labelDim, initValueScale=7}              # output layer
    )

где двоеточие (:) является синтаксисом BrainScript выражения массивов. Например, (F:G:H) массив с тремя элементами, FGи H.

Вы можете ознакомиться с "последовательной" нотацией из других наборов средств нейронной сети. Если нет, это мощная операция, которая, вкратце, Sequential() позволяет компактно выразить очень распространенную ситуацию в нейронных сетях, где входные данные обрабатываются путем распространения его через прогрессирование слоев. Sequential() принимает массив функций в качестве аргумента и возвращает новую функцию, которая вызывает эти функции по порядку, каждый раз при передаче выходных данных одного в следующий. Например,

FGH = Sequential (F:G:H)
y = FGH (x)

означает то же, что и .

y = H(G(F(x))) 

Это называется "композицией функций" и особенно удобно для выражения нейронных сетей, которые часто имеют такую форму:

     +-------+   +-------+   +-------+
x -->|   F   |-->|   G   |-->|   H   |--> y
     +-------+   +-------+   +-------+

Возвращаясь к нашей модели под рукой, выражение просто говорит, Sequential что наша модель имеет следующую форму:

     +-----------+   +----------------+   +------------+
x -->| Embedding |-->| Recurrent LSTM |-->| DenseLayer |--> y
     +-----------+   +----------------+   +------------+

Задача 1. Добавление нормализации пакетной службы

Теперь мы хотим добавить новые слои в модель, в частности пакетную нормализацию.

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

Поэтому задача будет вставлять слои пакетной нормализации до и после повторяющегося слоя LSTM. Если вы завершили практические занятия по обработке изображений, вы можете помнить, что уровень нормализации пакета имеет следующую форму:

BatchNormalizationLayer{}

Так что, пожалуйста, идите вперед и измените конфигурацию и посмотрите, что произойдет.

Если все прошло правильно, вы заметите не только улучшенную скорость конвергенции (ce и errs) по сравнению с предыдущей конфигурацией, но и лучшую частоту ошибок 2,0% (по сравнению с 2,9%):

Training 722379 parameters in 10 parameter tensors.

Finished Epoch[ 1 of 8]: [Training] ce = 0.29396894 * 36007; errs = 5.621% * 36007 
Finished Epoch[ 2 of 8]: [Training] ce = 0.10104186 * 36001; errs = 2.280% * 36001
Finished Epoch[ 3 of 8]: [Training] ce = 0.05012737 * 36005; errs = 1.258% * 36005
Finished Epoch[ 4 of 8]: [Training] ce = 0.04116407 * 35997; errs = 1.108% * 35997
Finished Epoch[ 5 of 8]: [Training] ce = 0.02602344 * 36000; errs = 0.756% * 36000
Finished Epoch[ 6 of 8]: [Training] ce = 0.02234042 * 35999; errs = 0.622% * 35999
Finished Epoch[ 7 of 8]: [Training] ce = 0.01931362 * 35997; errs = 0.667% * 35997
Finished Epoch[ 8 of 8]: [Training] ce = 0.01714253 * 36018; errs = 0.522% * 36018

Final Results: Minibatch[1-1]: errs = 2.039% * 10984; ce = 0.12888706 * 10984; perplexity = 1.13756164

(Если вы не хотите ждать завершения обучения, вы можете найти полученную модель под именем slu.forward.cmf.)

См . здесь решение.

Задача 2. Добавление lookahead

Наша повторяющаяся модель страдает от структурного дефицита: так как повторение выполняется слева направо, решение для метки слота не имеет информации о предстоящих словах. Модель немного неопрямая. Ваша задача — изменить модель таким образом, чтобы входные данные повторения состояли не только из текущего слова, но и следующего (lookahead).

Ваше решение должно находиться в стиле композиции функций. Таким образом, необходимо написать функцию BrainScript, которая выполняет следующие действия:

  • принять один входной аргумент;
  • вычислить немедленное "будущее значение" этих входных данных с помощью FutureValue() функции (используйте следующую форму: FutureValue (0, input, defaultHiddenActivation=0)); и
  • объединение двух векторов в два раза измерения внедрения с помощью Splice() (используйте следующую форму: Splice (x:y))

а затем вставьте эту функцию в Sequence() интервал между внедрением и повторяющимся слоем. Если все пойдет хорошо, вы увидите следующие выходные данные:

Training 902679 parameters in 10 parameter tensors.

Finished Epoch[ 1 of 8]: [Training] ce = 0.30500536 * 36007; errs = 5.904% * 36007
Finished Epoch[ 2 of 8]: [Training] ce = 0.09723847 * 36001; errs = 2.167% * 36001
Finished Epoch[ 3 of 8]: [Training] ce = 0.04082365 * 36005; errs = 1.047% * 36005
Finished Epoch[ 4 of 8]: [Training] ce = 0.03219930 * 35997; errs = 0.867% * 35997
Finished Epoch[ 5 of 8]: [Training] ce = 0.01524993 * 36000; errs = 0.414% * 36000
Finished Epoch[ 6 of 8]: [Training] ce = 0.01367533 * 35999; errs = 0.383% * 35999
Finished Epoch[ 7 of 8]: [Training] ce = 0.00937027 * 35997; errs = 0.278% * 35997
Finished Epoch[ 8 of 8]: [Training] ce = 0.00584430 * 36018; errs = 0.147% * 36018

Final Results: Minibatch[1-1]: errs = 1.839% * 10984; ce = 0.12023170 * 10984; perplexity = 1.12775812

Это сработает! Зная, что такое следующее слово, позволяет тегу слота снизить частоту ошибок с 2,0% до 1,84%.

(Если вы не хотите ждать завершения обучения, вы можете найти полученную модель под именем slu.forward.lookahead.cmf.)

См . здесь решение.

Задача 3. Двунаправленная повторяющаяся модель

Аха, знание будущих слов поможет. Так что вместо одного слова lookahead, почему бы не смотреть вперед до конца предложения, через обратное повторение? Давайте создадим двунаправленную модель!

Задача состоит в том, чтобы реализовать новый слой, который выполняет и вперед, и обратную рекурсию по данным, и объединяет выходные векторы.

Однако обратите внимание, что это отличается от предыдущей задачи в том, что двунаправленный слой содержит параметры модели, доступные для обучения. В стиле композиции функций шаблон для реализации слоя с параметрами модели заключается в создании фабрики функции , которая создает объект функции.

Объект-функция, также известный как functor, является объектом, который является как функцией, так и объектом. Это означает, что все еще не может быть вызвано ничего другого, что содержит данные, как если бы это была функция.

Например, это функция фабрики, LinearLayer{outDim} которая возвращает объект функции, содержащий матрицу Wвеса, смещение bи другую функцию для вычисления W * input + b. Например, говоряLinearLayer{1024}, будет создан этот объект функции, который затем можно использовать, как и любая другая функция, также немедленно: LinearLayer{1024}(x)

Путать? Рассмотрим пример: Давайте реализуем новый слой, объединяющий линейный слой с последующей нормализацией пакета. Чтобы разрешить композицию функций, слой должен быть реализован как фабричные функции, которые могут выглядеть следующим образом:

LinearLayerWithBN {outDim} = {
    F = LinearLayer {outDim}
    G = BatchNormalization {normalizationTimeConstant=2048}
    apply (x) = G(F(x))
}.apply

При вызове этой функции фабрики сначала будет создана запись (указанная ) с тремя элементами {...}: F, Gи apply. В этом примере F и G являются объектами-функциями и apply являются функцией, применяемой к данным. .apply Добавление к этому выражению означает, что .x всегда означает в BrainScript доступ к члену записи. Таким образом, например, вызов LinearLayerWithBN{1024} создаст объект, содержащий объект функции линейного слоя, который называется F, объект Gфункции пакетной нормализации, а apply именно функция, реализующая фактическую операцию этого слоя с помощью F и G. Затем он вернет apply. На внешней стороне apply() выглядит и ведет себя как функция. Однако под капотом будет держаться за запись, к которой она принадлежит, и, таким образом, apply() сохранит доступ к его конкретным экземплярам F и G.

Теперь вернуться к нашей задаче под рукой. Теперь вам потребуется создать функцию фабрики, очень похожую на приведенный выше пример. Необходимо создать функцию фабрики, которая создает два повторяющихся экземпляра слоя (один вперед, один назад), а затем определяет apply (x) функцию, которая применяет оба экземпляра слоя к одному и тому же x и объединяет два результата.

Хорошо, дайте ему попробовать! Чтобы узнать, как реализовать обратную рекурсию в CNTK, воспользуйтесь подсказкой о том, как выполняется рекурсия вперед. Кроме того, сделайте следующее:

  • удалите один слово lookahead, добавленный в предыдущей задаче, которую мы стремимся заменить; И
  • Измените hiddenDim параметр с 300 на 150, чтобы ограничить общее количество параметров модели.

Успешное выполнение этой модели приведет к получению следующих выходных данных:

Training 542379 parameters in 13 parameter tensors.

Finished Epoch[ 1 of 8]: [Training] ce = 0.27651655 * 36007; errs = 5.288% * 36007
Finished Epoch[ 2 of 8]: [Training] ce = 0.08179804 * 36001; errs = 1.869% * 36001
Finished Epoch[ 3 of 8]: [Training] ce = 0.03528780 * 36005; errs = 0.828% * 36005
Finished Epoch[ 4 of 8]: [Training] ce = 0.02602517 * 35997; errs = 0.675% * 35997
Finished Epoch[ 5 of 8]: [Training] ce = 0.01310307 * 36000; errs = 0.386% * 36000
Finished Epoch[ 6 of 8]: [Training] ce = 0.01310714 * 35999; errs = 0.358% * 35999
Finished Epoch[ 7 of 8]: [Training] ce = 0.00900459 * 35997; errs = 0.300% * 35997
Finished Epoch[ 8 of 8]: [Training] ce = 0.00589050 * 36018; errs = 0.161% * 36018

Final Results: Minibatch[1-1]: errs = 1.830% * 10984; ce = 0.11924878 * 10984; perplexity = 1.12665017

Работает как очарование! Эта модель достигает 1,83 %, немного лучше, чем модель lookahead выше. Двунаправленная модель имеет 40 % меньше параметров, чем lookahead. Однако если вы вернетесь и внимательно посмотрите на полные выходные данные журнала (не показанные на этой веб-странице), вы можете обнаружить, что lookahead один обучен примерно на 30 % быстрее. Это связано с тем, что модель lookahead имеет как менее горизонтальные зависимости (одна вместо двух повторений), так и более крупные матричные продукты, и, таким образом, может достичь более высокого параллелизма.

См . здесь решение.

Задача 4. Классификация намерений

Оказывается, что модель, которую мы создали до сих пор, можно легко превратить в классификатор намерений. Помните, что файл данных содержал этот дополнительный столбец S1. Этот столбец содержит одну метку для каждого предложения, указывающую намерение запроса на поиск сведений о таких темах, как airport или airfare.

Задача классификации всей последовательности в одну метку называется классификацией последовательностей. Наш классификатор последовательности будет реализован как повторяющийся LSTM (у нас уже есть), из которого мы принимаем его скрытое состояние последнего шага. Это дает нам один вектор для каждой последовательности. Затем этот вектор поступает в плотный слой для классификации softmax.

CNTK имеет операцию извлечения последнего состояния из последовательности, называемой BS.Sequences.Last(). Эта операция учитывает тот факт, что один и тот же мини-пакет может содержать последовательности очень разных длин, и что они упорядочены в памяти в упакованном формате. Аналогичным образом, для обратного рекурсии можно использовать BS.Sequences.First().

Задача состоит в том, чтобы изменить двунаправленную сеть из задачи 3 таким образом, чтобы последний кадр извлекается из прямой рекурсии, а первый кадр извлекается из обратной рекурсии, а два вектора объединяются. Затем объединенный вектор (иногда называемый вектором мысли) должен быть входом плотного слоя.

Кроме того, необходимо изменить метку с слота на метку намерения: просто переименуйте входную переменную (slotLabels), чтобы она соответствовала имени, используемому в разделе чтения для меток намерений, а также соответствовать измерению.

Попробуйте внести изменения. Если вы делаете это правильно, вы, однако, столкнелись с сообщением об ошибке вексирования и длинным в этом:

EXCEPTION occurred: Dynamic axis layout '*' is shared between inputs 'intentLabels'
and 'query', but layouts generated from the input data are incompatible on this axis.
Are you using different sequence lengths? Did you consider adding a DynamicAxis()
to the Input nodes?

"Вы используете разные длины последовательности?" О, да! Запрос и метка намерения — это только один маркер для каждого запроса. Это последовательность из 1 элемента! Так как это исправить?

CNTK позволяет разным переменным в сети иметь разные длины последовательности. Длину последовательности можно рассматривать как дополнительное символическое тензорное измерение. Переменные одной длины имеют одно и то же измерение символьной длины. Если две переменные имеют разные длины, это должно быть явно объявлено, в противном случае CNTK предполагает, что все переменные имеют одинаковую символьную длину.

Для этого создайте новый динамический объект оси и связав его с одним из входных данных следующим образом:

    n = DynamicAxis()
    query = Input {inputDim, dynamicAxis=n}

CNTK имеет ось по умолчанию. Как можно угадать из приведенного выше исключения, его имя — "*".
Следовательно, необходимо объявить только одну новую ось; другие входные данные (intentLabels) будут продолжать использовать ось по умолчанию.

Теперь мы должны быть хорошо работать и увидеть следующие выходные данные:

Training 511376 parameters in 13 parameter tensors.

Finished Epoch[ 1 of 8]: [Training] ce = 1.17365003 * 2702; errs = 21.318% * 2702
Finished Epoch[ 2 of 8]: [Training] ce = 0.40112341 * 2677; errs = 9.189% * 2677
Finished Epoch[ 3 of 8]: [Training] ce = 0.17041608 * 2688; errs = 4.167% * 2688
Finished Epoch[ 4 of 8]: [Training] ce = 0.09521124 * 2702; errs = 2.739% * 2702
Finished Epoch[ 5 of 8]: [Training] ce = 0.08287138 * 2697; errs = 2.262% * 2697
Finished Epoch[ 6 of 8]: [Training] ce = 0.07138554 * 2707; errs = 2.032% * 2707
Finished Epoch[ 7 of 8]: [Training] ce = 0.06220047 * 2677; errs = 1.419% * 2677
Finished Epoch[ 8 of 8]: [Training] ce = 0.05072431 * 2686; errs = 1.340% * 2686

Final Results: Minibatch[1-1]: errs = 4.143% * 893; ce = 0.27832144 * 893; perplexity = 1.32091072

Без больших усилий мы достигли скорости ошибок в 4,1%. Очень приятно для первого выстрела (хотя и не совсем состояние искусства по этой задаче, что составляет 3%).

Вы можете заметить одну вещь, хотя: количество выборок на эпоху в настоящее время составляет около 2700. Это связано с тем, что это количество выборок меток, из которых теперь у нас есть только одно предложение. В этой задаче мы значительно сократили количество сигналов контроля. Это должно побудить нас попытаться увеличить размер мини-бэтч. Давайте попробуем 256 вместо 70:

Finished Epoch[ 1 of 8]: [Training] ce = 1.11500325 * 2702; errs = 19.282% * 2702
Finished Epoch[ 2 of 8]: [Training] ce = 0.29961089 * 2677; errs = 6.052% * 2677
Finished Epoch[ 3 of 8]: [Training] ce = 0.09018802 * 2688; errs = 2.418% * 2688
Finished Epoch[ 4 of 8]: [Training] ce = 0.04838102 * 2702; errs = 1.258% * 2702
Finished Epoch[ 5 of 8]: [Training] ce = 0.02996789 * 2697; errs = 0.704% * 2697
Finished Epoch[ 6 of 8]: [Training] ce = 0.02142932 * 2707; errs = 0.517% * 2707
Finished Epoch[ 7 of 8]: [Training] ce = 0.01220149 * 2677; errs = 0.299% * 2677
Finished Epoch[ 8 of 8]: [Training] ce = 0.01312233 * 2686; errs = 0.186% * 2686

Эта система учится гораздо лучше! (Однако обратите внимание, что эта разница, скорее всего, является артефактом, вызванным нашей fsAdagrad схемой нормализации градиента, и, как правило, исчезнет в ближайшее время при использовании больших наборов данных.)

Результирующая частота ошибок выше, однако:

Final Results: Minibatch[1-1]: errs = 4.479% * 893; ce = 0.31638223 * 893; perplexity = 1.37215463

но это различие на самом деле соответствует 3 ошибкам, которые не являются значительными.

Ознакомьтесь с решением здесь.

Задача 5. Параллельное обучение

Наконец, если у вас несколько GPU, CNTK позволяет параллельно выполнять обучение с помощью MPI (интерфейс передачи сообщений). Эта модель слишком мала, чтобы ожидать ускорения; параллелизация такой небольшой модели значительно недоиспользует доступные GPU. Тем не менее, позвольте нам пройти через движения, чтобы вы знали, как это сделать, как только вы перейдете к реальным рабочим нагрузкам.

Добавьте в блок следующие строки SGD :

SGD = {
    ...
    parallelTrain = {
        parallelizationMethod = "DataParallelSGD"
        parallelizationStartEpoch = 1
        distributedMBReading = true
        dataParallelSGD = { gradientBits = 2 }
    }
}

а затем выполните следующую команду:

mpiexec -np 4 cntk  configFile=SLUHandsOn_Solution4.cntk  stderr=Models/log  parallelTrain=true  command=TrainTagger

В этом случае обучение будет выполняться на 4 GPU с использованием 1-разрядного алгоритма ШИФРОВАНИЯ (2-разрядное ЗНАЧЕНИЕ в данном случае). Его приближение не повредило точности: частота ошибок составляет 4,367 %, две ошибки больше (выполните TestTagger действие отдельно на одном GPU).

Заключение

В этом руководстве представлен стиль композиции функций как компактный способ представления сетей. Многие типы нейронных сетей подходят для их представления таким образом, что является более прямым и менее подверженным ошибкам перевод графа в описание сети.

В этом руководстве описано, как использовать существующую конфигурацию в стиле композиции функций и изменять ее определенными способами:

  • добавление слоя (из коллекции предопределенных слоев)
  • определение и использование функции
  • определение и использование функции фабрики слоев

В этом руководстве также рассматривается обработка нескольких измерений времени, и мы видели, как параллелизировать обучение.

Решения

Ниже приведены решения для описанных выше задач. Эй, нет обмана!

Решение 1. Добавление пакетной нормализации

Измененная функция модели имеет следующую форму:

    model = Sequential (
        EmbeddingLayer {embDim} :                            # embedding
        BatchNormalizationLayer {} :           ##### added
        RecurrentLSTMLayer {hiddenDim, goBackwards=false} :  # LSTM
        BatchNormalizationLayer {} :           ##### added
        DenseLayer {labelDim}                                # output layer
    )

Решение 2. Добавление Lookahead

Функция lookahead-function может быть определена следующим образом:

    OneWordLookahead (x) = Splice (x : DelayLayer {T=-1} (x))

и он будет вставлен в модель следующим образом:

    model = Sequential (
        EmbeddingLayer {embDim} :
        OneWordLookahead :                   ##### added
        BatchNormalizationLayer {} :
        RecurrentLSTMLayer {hiddenDim, goBackwards=false} :
        BatchNormalizationLayer {} :
        DenseLayer {labelDim}
    )

Решение 3. Двунаправленная повторяющаяся модель

Двунаправленный повторяющийся слой можно написать следующим образом:

    BiRecurrentLSTMLayer {outDim} = {
        F = RecurrentLSTMLayer {outDim, goBackwards=false}
        G = RecurrentLSTMLayer {outDim, goBackwards=true}
        apply (x) = Splice (F(x):G(x))
    }.apply

и затем используется следующим образом:

    hiddenDim = 150      ##### changed from 300 to 150

    model = Sequential (
        EmbeddingLayer {embDim} :
        ###OneWordLookahead :                   ##### removed
        BatchNormalizationLayer {} :
        BiRecurrentLSTMLayer {hiddenDim} :
        BatchNormalizationLayer {} :
        DenseLayer {labelDim}
    )

Решение 4. Классификация намерений

Уменьшите последовательности до последнего или первого скрытого повторяющегося слоя:

        apply (x) = Splice (BS.Sequences.Last(F(x)):BS.Sequences.First(G(x)))
        ##### added Last() and First() calls ^^^

Измените входные данные метки с слота на намерение:

    intentDim = $numIntents$    ###### name change
    ...
        DenseLayer {intentDim}                      ##### different dimension
    ...
    intentLabels = Input {intentDim}
    ...
    ce   = CrossEntropyWithSoftmax (intentLabels, z)
    errs = ErrorPrediction         (intentLabels, z)
    ...
    labelNodes      = (intentLabels)

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

    n = DynamicAxis()                               ##### added
    query        = Input {inputDim, dynamicAxis=n}  ##### use dynamic axis

Acknowledgment (Подтверждение)

Мы хотели бы поблагодарить Дерек Лю за подготовку основы этого руководства.