Редактирование модели с помощью BrainScript
(Примечание. Более старые версии CNTK использовали "MEL" (язык редактирования модели) для этих целей. Мы по-прежнему в процессе преобразования примеров. Документацию по MEL см. в здесь.)
CNTK позволяет изменять модели после факта. Это делается путем создания новой модели во время клонирования (частей) существующей модели с примененными изменениями. Для этого CNTK предоставляет три основных функции:
-
BS.Network.Load()
для загрузки существующей модели -
BS.Network.CloneFunction()
извлечь раздел существующей модели для повторного использования -
BS.Network.Edit()
клонировать модель с примененными изменениями узла
Операция редактирования не является отдельным шагом. Скорее, команда, которая должна работать с измененной моделью, не будет указывать modelPath
для загрузки модели из, а вместо BrainScriptNetworkBuilder
раздела, который загружает модель внутри и создает новую модель от загруженной на лету модели.
Пример: дискриминационное предварительное обучение
Дискриминационное предварительное обучение — это методика создания глубокой сети путем обучения последовательности мелких сетей. Начните с 1-скрытой сети слоя, обучите его частичной конвергенции, а затем удалите выходной слой, добавьте новый скрытый слой и добавьте новый выходной слой. Повторяйте до тех пор, пока не достигнет нужного количества скрытых слоев.
Предположим, что с очень простой начальной моделью
BrainScriptNetworkBuilder = [
N = 40; M = 9000; H = 512
W1 = Parameter (H, N); b1 = Parameter (H)
Wout = Parameter (M, H); bout = Parameter (M)
x = Input (N, tag=‘feature’) ; labels = Input (M, tag=‘labels’)
h1 = Sigmoid (W1 * x + b1)
z = Wout * h1 + bout
ce = CrossEntropyWithSoftmax (labels, z, tag=‘criterion’)
]
Давайте обучим эту модель и сохраните ее в разделе "Model.1.dnn". Далее мы хотим обучить модель двумя скрытыми слоями, где первый скрытый слой инициализирован из значений, обученных выше. Для этого мы создадим отдельное действие обучения, которое создает новую модель, но повторно использует части предыдущего, как показано ниже.
BrainScriptNetworkBuilder = {
# STEP 1: load 1-hidden-layer model
inModel = BS.Network.Load ("model.1.dnn")
# get its h1 variable --and also recover its dimension
h1 = inModel.h1
H = h1.dim
# also recover the number of output classes
M = inModel.z.dim
# STEP 2: create the rest of the extended network as usual
W2 = Parameter (H, H); b2 = Parameter (H)
Wout = Parameter (M, H); bout = Parameter (M)
h2 = Sigmoid (W2 * h1 + b2)
z = Wout * h2 + bout
ce = CrossEntropyWithSoftmax (labels, z, tag=‘criterion’)
}
Во-первых, шаг 1 использует Load()
для загрузки сети в переменную BrainScript. Сеть ведет себя как запись BrainScript, где доступны все узлы верхнего уровня (все узлы, которые не содержат .
или [
в именах узлов) доступны с помощью синтаксиса записи. Новая сеть может ссылаться на любой узел в загруженной сети. В этом примере загруженная сеть содержит узел h1
, который является выходным результатом первого скрытого слоя, а узел z
, который является ненормализованной вероятностью заставки журнала выходных классов (входных данных в функцию Softmax). Доступ к обоим узлам можно получить из BrainScript с помощью синтаксиса точки, например inModel.h1
и inModel.z
.
Обратите внимание, что константы не хранятся в моделях, поэтому ни N
, ни M
не доступны в модели. Однако их можно восстановить из загруженной модели. Для этого вычислительные узлы также ведут себя как записи BrainScript и предоставляют свойство dim
.
Далее шаг 2 создает остальную часть новой сети с помощью обычного BrainScript. Обратите внимание, что этот новый раздел просто использует узел h1
из входной модели в качестве входных данных, как и любой другой узел. Ссылка на узел из входной сети автоматически сделает все узлы, которые этот узел зависит от части созданной сети. Например, входной узел x
автоматически станет частью новой сети.
Кроме того, обратите внимание, что выходной слой создается заново. Таким образом, его параметры модели будут созданы недавно. (Чтобы этого не сделать и вместо этого повторно использовать существующие параметры, можно использовать inModel.Wout
, но обратите внимание, что не имеет смысла из точки зрения сетевой структуры в этом конкретном примере.)
Пример. Использование предварительно обученной модели
Ниже приведен пример использования предварительно обученной модели (из файла "./featext.dnn"
) в качестве средства извлечения компонентов:
BrainScriptNetworkBuilder = {
# STEP 1: load existing model
featExtNetwork = BS.Network.Load ("./featext.dnn")
# STEP 2: extract a read-only section that is the feature extractor function
featExt = BS.Network.CloneFunction (
featExtNetwork.input, # input node that AE model read data from
featExtNetwork.feat, # output node in AE model that holds the desired features
parameters="constant") # says to freeze that part of the network
# STEP 3: define the part of your network that uses the feature extractor
# from the loaded model, which above we isolated into featExt().
# featExt() can be used like any old BrainScript function.
input = Input (...)
features = featExt (input) # this will instantiate a clone of the above network
# STEP 4: and add the remaining bits of the network in BrainScript, e.g.
h = Sigmoid (W_hid * features + b_hid) # whatever your hidden layer looks like
z = W_out * h + b_out
ce = CrossEntropyWithSoftmax (labels, z)
criterionNodes = (ce)
}
ШАГ 1 использует Load()
для загрузки сети в переменную BrainScript.
Шаг 2 использует CloneFunction()
для клонирования раздела, связанного с извлечением функций из загруженной сети, который является под графом, который подключает featExtNetwork.input
к featExtNetwork.feat
. Так как мы указали parameters="constant"
, все параметры, от которых featExtNetwork.feat
зависят, также клонируются и выполняются только для чтения.
На шаге 3 и 4 определена новая сеть. Это делается как любая другая модель BrainScript, только теперь мы можем использовать функцию featExt()
при этом.
Проблемы с именами узлов с .
[
и ]
Чтобы ссылаться на узел в сети, содержащей .
или [
или ]
, замените эти символы _
.
Например, если network
содержит узел с именем result.z
, network.result.z
завершится ошибкой; вместо этого можно сказать network.result_z
.
Пример. Изменение узлов существующей сети
Чтобы изменить внутренние части существующей сети, следует клонировать сеть, а изменения применяются как часть процесса клонирования. Это достигается BS.Network.Edit()
.
Edit()
будет выполнять итерацию по всем узлам сети и будет предлагать каждому узлу по одному лямбда-функции, передаваемые вызывающим. Затем эти лямбда-функции могут проверять узел и возвращать неизмененные узлы или возвращать новый узел на его месте.
Edit()
будет итерировать узлы в неопределенном порядке. Если узел замены ссылается на сетевой узел, который, в свою очередь, был заменен, Edit()
будет обновлять все такие ссылки на соответствующие замены (ака "сделать правильно").
TODO: Пример.
Далее: справочник по полной функции