你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

使用 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.h1inModel.z

请注意,常量不存储在模型中,因此 NM 都不可用于模型。 但是,可以从加载的模型重新构造它们。 为此,计算节点的行为也类似于 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() 将循环访问网络的所有节点,并将每个节点逐个提供给调用方传递的 lambda 函数。 然后,这些 lambda 函数可以检查节点,并返回未修改的节点,或在其位置返回新节点。 Edit() 将按未指定的顺序循环访问节点。 如果替换节点引用又被替换的网络节点,Edit() 将作为最后一步更新对相应替换的所有此类引用(即“执行正确的操作”)。

TODO:示例。

下一步:完整函数引用