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


Основные понятия BrainScript

BrainScript — Walk-Through

В этом разделе представлены основные понятия языка BrainScript. Новый язык? Не беспокойтесь, & читать дальше, это очень прямо.

В CNTK пользовательские сети определяются с помощью BrainScriptNetworkBuilder и описаны в языке описания сети CNTK "BrainScript". Аналогичным образом описания сетей называются сценариями мозга.

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

Окей, давайте смочим ноги с полным примером BrainScript!

Полный пример определения сети BrainScript

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

BrainScriptNetworkBuilder = {   # (we are inside the train section of the CNTK config file)

    SDim = 28*28 # feature dimension
    HDim = 256   # hidden dimension
    LDim = 10    # number of classes

    # define the model function. We choose to name it 'model()'.
    model (features) = {
        # model parameters
        W0 = ParameterTensor {(HDim:SDim)} ; b0 = ParameterTensor {HDim}
        W1 = ParameterTensor {(LDim:HDim)} ; b1 = ParameterTensor {LDim}

        # model formula
        r = RectifiedLinear (W0 * features + b0) # hidden layer
        z = W1 * r + b1                          # unnormalized softmax
    }.z

    # define inputs
    features = Input {SDim}
    labels   = Input {LDim} 

    # apply model to features
    z = model (features)

    # define criteria and output(s)
    ce   = CrossEntropyWithSoftmax (labels, z)  # criterion (loss)
    errs = ErrorPrediction         (labels, z)  # additional metric
    P    = Softmax (z)     # actual model usage uses this

    # connect to the system. These five variables must be named exactly like this.
    featureNodes    = (features)
    inputNodes      = (labels)
    criterionNodes  = (ce)
    evaluationNodes = (errs)
    outputNodes     = (P)
}

Основные сведения о синтаксисе BrainScript

Прежде чем разобраться, ознакомьтесь с некоторыми общими заметками о синтаксисе BrainScript.

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

Softplus (x) = Log (1 + Exp (x))
h = Softplus (W * v + b)

Строки, примечания, включение

Хотя назначение обычно записывается в одной строке, выражения могут охватывать несколько строк. Однако чтобы поместить несколько назначений в одну строку, их необходимо разделить точкой с запятой. Пример:

SDim = 28*28 ; HDim = 256 ; LDim = 10    # feature, hidden, and label dimension

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

BrainScript распознает комментарии конца строки, используя как стиль # Python, так и стиль //C++. Встроенные комментарии используют синтаксис C (/* this is a comment*/), но в отличие от C, они могут не охватывать несколько строк.

Для BrainScript, внедренного в файлы конфигурации CNTK (в отличие от BrainScript, считываемого из отдельного файла с помощью include директивы), из-за взаимодействия с анализатором конфигурации существует (несколько странное) дополнительное ограничение на то, что любые круглые скобки, фигурные скобки или квадратные скобки должны быть сбалансированы внутри комментариев стиля C/C++ и строковых литералов. Следовательно, нет смайликов в комментариях в стиле C/C++!

Директива include "PATH" может использоваться в любом месте для вставки содержимого файла в точку инструкции . PATH Здесь может быть абсолютным или относительным относительным путем (с подкаталогами или без нее). Если это относительный путь, поиск в следующих расположениях выполняется по порядку: текущий рабочий каталог; каталоги, содержащие внешние файлы, включая файлы, если таковые есть; каталоги, содержащие файлы конфигурации; и, наконец, каталог, содержащий исполняемый файл CNTK. Все встроенные функции BrainScript включаются таким образом из файла с именем CNTK.core.bs , который находится рядом с исполняемым файлом CNTK.

Выражения

Далее необходимо знать о выражениях BrainScript — формулах, описывающих вашу сеть. Выражения BrainScript написаны в виде математических выражений в синтаксисе, аналогичном популярным языкам программирования. Простейшими выражениями являются литералы, например числа и строки. Математическим примером является W1 * r + b, где * ссылается на скалярное, матричное или тензорное произведение в зависимости от типа переменных. Другим распространенным типом выражения является вызов функции, например RectifiedLinear (.).

BrainScript — это динамически типизированный язык. Важным типом выражения является запись, определенная с помощью синтаксиса {...} и доступ к ней осуществляется с помощью синтаксиса точки. Например, r = { x = 13 ; y = 42 } назначает запись с двумя элементами r, где первый член может быть доступ как r.x.

В дополнение к обычным математическим операторам BrainScript имеет условное выражение (if c then t else f), выражение массива и простые лямбда-выражения. Наконец, для взаимодействия с кодом CNTK C++ BrainScript может напрямую создавать экземпляры ограниченного набора предопределенных объектов C++, преимущественно ComputationNode из которых состоят вычислительные сети. Дополнительные сведения см. в разделе Выражения.

Выражения BrainScript оцениваются при первом использовании. Основной целью BrainScript является описание сети, поэтому значение выражения часто является не конечным значением, а узлом в графе вычислений для отложенных вычислений (как в W1 * r + b). На момент анализа BrainScript "вычисляются" только выражения BrainScript скаляров (например 28*28, ). Выражения, которые никогда не используются (например, из-за условия), никогда не вычисляются.

Примечание. В устаревшей NDLNetworkBuilder версии поддерживается только синтаксис вызова функции, например, необходимо написать Plus (Times (W1, r), b1).

Переменные

Переменные могут содержать значение любого выражения BrainScript (число, строка, запись, массив, лямбда-выражение, CNTK C++ объект) и подставляться при использовании в выражении. Переменные неизменяемы, т. е. назначаются только один раз. Например, приведенное выше определение сети начинается с:

SDim = 28*28  
HDim = 256
LDim = 10

Здесь переменным присваиваются скалярные числовые значения, которые используются в качестве параметров в последующих выражениях. Эти значения являются измерениями выборок данных, скрытых слоев и меток, используемых в обучении. Этот конкретный пример настройки предназначен для набора данных MNIST, который представляет собой коллекцию изображений пикселей [28 x 28]. Каждое изображение является рукописной цифрой (0–9), поэтому к каждому изображению можно применить 10 возможных меток. Скрытое измерение HDim активации является выбором пользователя.

Большинство переменных являются элементами записи (внешний блок BrainScript неявно является записью). Кроме того, переменные могут быть аргументами функции или храниться как элементы массивов.

Все готово к просмотру определения модели.

Определение сети

Сеть в основном описывается формулами вычисления выходных данных сети на основе входных данных. Мы называем это функцией модели, которая часто определяется как фактическая функция в BrainScript. В рамках функции модели пользователь должен объявить параметры модели. Наконец, необходимо определить входные данные сети, а также критерии и выходные данные. Все они определены как переменные. Затем входные и критерии/выходные переменные должны быть переданы в систему.

Функция модели сети и параметры модели

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

r = RectifiedLinear (W0 * features + b0)
z = W1 * r + b1 

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

W0 = ParameterTensor {(HDim:SDim)}
b0 = ParameterTensor {(HDim)}

В этом случае — это матрица веса, W0 а b0 — вектор смещения. ParameterTensor{} обозначает специальный примитив CNTK, который создает экземпляр вектора, матрицы или тензора произвольного ранга и принимает параметры измерения в виде массива BrainScript (числа, сцепленные двоеточием :). Измерение вектора является одним числом, в то время как матричное измерение должно быть указано как (numRows:numCols). По умолчанию параметры инициализируются с одинаковыми случайными числами при непосредственном создании экземпляра и heNormal при использовании через слои, но существуют другие параметры (см. здесь) для полного списка. В отличие от обычных функций, ParameterTensor{} принимает аргументы в фигурных скобках вместо круглых скобок. Фигурные скобки — это соглашение BrainScript для функций, создающих параметры или объекты, в отличие от функций.

Затем все это упаковывается в функцию BrainScript. Функции BrainScript объявляются в формате f(x) = an expression of x. Например, Sqr (x) = x * x является допустимым объявлением функции BrainScript. Не может быть гораздо проще и прямо, не так ли?

Теперь фактическая функция модели в приведенном выше примере немного сложнее:

model (features) = {
    # model parameters
    W0 = ParameterTensor {(HDim:SDim)} ; b0 = ParameterTensor {HDim}  
    W1 = ParameterTensor {(LDim:HDim)} ; b1 = ParameterTensor {LDim}

    # model formula
    r = RectifiedLinear (W0 * features + b0) # hidden layer
    z = W1 * r + b1                          # unnormalized softmax
}.z

Внешний { ... } и, что финал .z заслуживает некоторого объяснения. Внешние фигурки { ... } и их содержимое фактически определяют запись с 6 элементами записи (W0, , b0W1, b1, rи z). Однако значение функции модели равно просто z; все остальные являются внутренними для функции. Таким образом, мы используем для .z выбора элемента записи, который нам нужно вернуть. Это просто синтаксис точки для доступа к членам записи. Таким образом, другие элементы записей недоступны извне. Но они по-прежнему существуют как часть выражения для вычисления z. Шаблон { ... ; x = ... }.x — это способ использования локальных переменных.

Обратите внимание, что синтаксис записи не требуется. Кроме того, model(features) можно было бы объявить без объезда через запись в виде одного выражения:

model (features) = ParameterTensor {(LDim:HDim)} * (RectifiedLinear (ParameterTensor {(HDim:SDim)}
                   * features + ParameterTensor {HDim})) + ParameterTensor {LDim}

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

Входные данные

Входные данные в сеть определяются примерами данных и метками, связанными с примерами:

features = Input {SDim}
labels   = Input {LDim}

Input{} — это второй специальный примитив CNTK, необходимый для определения модели (первый — Parameter{}). Он создает переменную, которая получает входные данные извне сети: от средства чтения. Аргументом Input{} является измерение данных. В этом примере features входные данные будут иметь измерения образца данных (которые мы определили в переменной SDimlabels ), а входные данные будут иметь размеры меток. Имена переменных входных данных должны соответствовать соответствующим записям в определении средства чтения.

Критерии обучения и сетевые выходные данные

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

  • определить критерий обучения,
  • измерение точности и
  • вычисление вероятности по сравнению с выходными классами, заданными входными данными, чтобы основывать решение о классификации (обратите внимание, что ненормализованный журнал плаката часто можно использовать для классификации z напрямую).

В примере сети используются метки категорий, которые представлены в виде однократных векторов. В примере MNIST они будут отображаться в виде массива из 10 значений с плавающей запятой, все из которых равны нулю, за исключением соответствующей категории метки 1,0. Задачи классификации, такие как наша, обычно используют функцию SoftMax() для получения вероятностей для каждой метки. Затем сеть оптимизируется, чтобы максимально увеличить вероятность журналов правильного класса (перекрестная энтропия) и свести к минимуму все остальные классы. Это наш критерий обучения, или функция потерь. В CNTK эти два действия обычно объединяются в одну функцию для повышения эффективности:

ce = CrossEntropyWithSoftmax (labels, z)

CrossEntropyWithSoftmax() функция принимает входные данные, вычисляет функцию SoftMax() , вычисляет ошибку на основе фактического значения, используя перекрестную энтропию, и этот сигнал ошибки используется для обновления параметров в сети путем обратного распространения. Следовательно, в приведенном выше примере нормализованное Softmax() значение, вычисленное как P, не используется во время обучения. Однако она потребуется для использования сети (опять же обратите внимание, z что во многих случаях часто достаточно для классификации; в этом случае z выходными данными будет сама по себе).

CNTK использует стохастический градиентный спуск (SGD) в качестве алгоритма обучения. SGD необходимо вычислить градиент целевой функции по отношению ко всем параметрам модели. Важно отметить, что CNTK не требует от пользователей указывать эти градиенты. Скорее, каждая встроенная функция в CNTK также имеет производный аналог функции, и система автоматически выполняет обновление параметров сети обратного распространения. Это невидимо для пользователя. Пользователям не нужно беспокоиться о градиентах. Никогда не.

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

errs = ClassificationError (labels, z)

Вероятности, создаваемые сетью, сравниваются с фактической меткой и вычисляется частота ошибок. Обычно это отображается системой. Хотя это полезно, использовать не обязательно ClassificationError().

Передача входных данных, выходных данных и условий в систему

Теперь, когда все переменные определены, мы должны сообщить системе, какие из переменных следует рассматривать как входные, выходные данные и критерии. Для этого необходимо определить 5 специальных переменных, которые должны иметь именно следующие имена:

featureNodes    = (features)
labelNodes      = (labels)
criterionNodes  = (ce)
evaluationNodes = (errs)
outputNodes     = (z:P)

Значения являются массивами, где значения должны быть разделены двоеточием (двоеточие : — это оператор BrainScript, который формирует массив путем объединения двух значений или массивов). Это показано выше для outputNodes, который объявляет как выходные данные, так z и P .

(Примечание. Вместо нерекомендуемых NDLNetworkBuilder элементов массива необходимо разделять запятыми.)

Сводка по специальным именам

Как мы видели выше, есть 7 специальных имен, которые имеют "магические" свойства:

  • ParameterTensor{}: объявляет и инициализирует обучаемый параметр.
  • Input{}: объявляет переменную, которая подключается и отправляется из средства чтения данных.
  • featureNodes, labelNodes, criterionNodes, и evaluationNodesoutputNodes: объявляет системе, какие из наших переменных следует использовать в качестве входных, выходных данных и критериев.

Кроме того, есть еще 3 специальные функции со встроенной "магией" в CNTK, которые обсуждаются в другом месте:

  • Constant(): объявляет константу.
  • PastValue() и FutureValue(): доступ к переменной в сетевой функции на другом шаге времени для повторяющихся циклов формы.

Любое другое предопределенное имя представляет собой встроенную примитивную функцию, например Sigmoid() или Convolution() с реализацией C++, предопределенную функцию библиотеки, реализованную в BrainScript, например BS.RNNs.LSTMP(), или запись, которая выступает в качестве пространства имен для функций библиотеки (например BS.RNNs, ). Полный список см. в статье BrainScript-Full-Function-Reference .

Далее: Выражения BrainScript