Keras 在激活函数之前检索节点的值

Posted

技术标签:

【中文标题】Keras 在激活函数之前检索节点的值【英文标题】:Keras retrieve value of node before activation function 【发布时间】:2018-01-11 12:53:50 【问题描述】:

想象一个全连接的神经网络,其最后两层的结构如下:

[Dense]
    units = 612
    activation = softplus

[Dense]
    units = 1
    activation = sigmoid

net 的输出值为 1,但我想知道 sigmoidal 函数的输入 x 是什么(必须是一些大数字,因为这里 sigm(x) 是 1)。

按照indraforyou's 的回答,我设法检索了 Keras 层的输出和权重:

outputs = [layer.output for layer in model.layers[-2:]]
functors = [K.function( [model.input]+[K.learning_phase()], [out] ) for out in outputs]

test_input = np.array(...)
layer_outs = [func([test_input, 0.]) for func in functors]

print layer_outs[-1][0]  # -> array([[ 1.]])

dense_0_out = layer_outs[-2][0]                           # shape (612, 1)
dense_1_weights = model.layers[-1].weights[0].get_value() # shape (1, 612)
dense_1_bias = model.layers[-1].weights[1].get_value()

x = np.dot(dense_0_out, dense_1_weights) + dense_1_bias
print x # -> -11.7

x 怎么可能是负数?在这种情况下,最后一层输出应该是一个比 1.0 更接近 0.0 的数字。 dense_0_outdense_1_weights 是错误的输出或权重吗?

【问题讨论】:

不应该是x = np.dot(dense_0_out, dense_1_weights) + dense_1_bias吗? @MarcinMożejko 你是对的,我更正了。自从偏差被训练到 0.0 后没有改变任何东西。 但是该层的输出被馈送到softmax - 然后你获得的值被压缩到[0, 1]间隔。 @MarcinMożejko 你是说最后一层吗?它被喂给 sigmoid,是的。因此,如果该值为 -11.7,则将其输入 sigmoid 并获得一些接近零的值。 layer_outs[-1] 改为 1... 啊——不应该是x = np.dot(dense_1_weights, dense_0_out.transpose())吗? 【参考方案1】:

由于您使用的是get_value(),我假设您使用的是 Theano 后端。要获取sigmoid激活前节点的值,可以traverse the computation graph。

可以使用 owner 字段从输出(某些计算的结果)到输入进行遍历。

在您的情况下,您想要的是 sigmoid 激活操作的输入 x。 sigmoid 运算的输出是model.output。将这些放在一起,变量xmodel.output.owner.inputs[0]

如果你打印出这个值,你会看到Elemwiseadd,no_inplace.0,这是一个元素相加操作。可以从Dense.call()的source code验证:

def call(self, inputs):
    output = K.dot(inputs, self.kernel)
    if self.use_bias:
        output = K.bias_add(output, self.bias)
    if self.activation is not None:
        output = self.activation(output)
    return output

激活函数的输入是K.bias_add()的输出。

对你的代码稍加修改,就可以得到激活前节点的值:

x = model.output.owner.inputs[0]
func = K.function([model.input] + [K.learning_phase()], [x])
print func([test_input, 0.])

对于使用 TensorFlow 后端的任何人:请改用 x = model.output.op.inputs[0]

【讨论】:

感谢您的回答!我很清楚你的方法更适合,但你能简单地评论一下我的原始代码吗......这计算有什么问题吗?为什么? 您是否尝试过这种方法,但它仍然给您一个否定的x?我尝试了你的代码,它给出的结果与这种方法完全相同(positive x,大约 600),所以我不太确定问题出在你的代码中。 顺便说一句,我在我的程序中看到dense_0_out.shape 等于(1, 612)dense_1_weights.shape 等于(612, 1),这与您发布的不同。能否提供您使用的test_input以及Keras和TF的版本? 糟糕,我的意思是 TH 版本。我在 Theano 上尝试了相同的代码,结果和形状仍然相同。您是否可以发布更多代码(例如模型定义和拟合)?可能错误不会出现在您发布的代码块中。【参考方案2】:

我可以看到一个简单的方法,只需稍微改变模型结构。 (见最后如何使用现有模型,只更改结尾)。

这种方法的优点是:

您不必猜测您的计算是否正确 您无需关心 dropout 层以及如何实现 dropout 计算 这是一个纯 Keras 解决方案(适用于任何后端,无论是 Theano 还是 Tensorflow)。

以下有两种可能的解决方案:

选项 1 - 从建议的结构开始创建新模型 选项 2 - 重用现有模型,仅更改其结尾

模型结构

你可以把最后一个密集的最后分成两层:

[Dense]
    units = 612
    activation = softplus

[Dense]
    units = 1
    #no activation

[Activation]
    activation = sigmoid

然后你只需得到最后一个密集层的输出。

我想说你应该创建两个模型,一个用于训练,另一个用于检查这个值。

选项 1 - 从头开始​​构建模型:

from keras.models import Model

#build the initial part of the model the same way you would
#add the Dense layer without an activation:

#if using the functional Model API
    denseOut = Dense(1)(outputFromThePreviousLayer)    
    sigmoidOut = Activation('sigmoid')(denseOut)    

#if using the sequential model - will need the functional API
    model.add(Dense(1))
    sigmoidOut = Activation('sigmoid')(model.output)

从中创建两个模型,一个用于训练,一个用于检查密集输出:

#if using the functional API
    checkingModel = Model(yourInputs, denseOut)

#if using the sequential model:
    checkingModel = model   

trainingModel = Model(checkingModel.inputs, sigmoidOut)   

使用trianingModel 进行正常训练。这两个模型共享权重,所以训练一个就是训练另一个。

使用checkingModel 只是为了查看Dense 层的输出,使用checkingModel.predict(X)

选项 2 - 从现有模型构建:

from keras.models import Model

#find the softplus dense layer and get its output:
softplusOut = oldModel.layers[indexForSoftplusLayer].output
    #or should this be the output from the dropout? Whichever comes immediately after the last Dense(1)

#recreate the dense layer
outDense = Dense(1, name='newDense', ...)(softPlusOut)

#create the new model
checkingModel = Model(oldModel.inputs,outDense)

重要的是,因为您创建了一个新的 Dense 层,所以要从旧层中获取权重:

wgts = oldModel.layers[indexForDense].get_weights()
checkingModel.get_layer('newDense').set_weights(wgts)

在这种情况下,训练旧模型不会更新新模型中的最后一个密集层,所以,让我们创建一个 trainingModel:

outSigmoid = Activation('sigmoid')(checkingModel.output)
trainingModel = Model(checkingModel.inputs,outSigmoid)

使用checkingModel 检查您想要的值和checkingModel.predict(X)。并训练trainingModel

【讨论】:

【参考方案3】:

所以这是针对谷歌同事的,自从发布接受的答案以来,keras API 的工作已经发生了显着变化。激活前提取层输出的工作代码(用于 tensorflow 后端)是:

model = Your_Keras_Model()
the_tensor_you_need = model.output.op.inputs[0] #<- this is indexable, if there are multiple inputs to this node then you can find it with indexing.

在我的例子中,最后一层是一个密集层,激活softmax,所以我需要的张量输出是&lt;tf.Tensor 'predictions/BiasAdd:0' shape=(?, 1000) dtype=float32&gt;

【讨论】:

TypeError: Keras symbolic inputs/outputs do not implement 'op'. 在 tf 2.4 提出【参考方案4】:

(TF 后端) Conv 层的解决方案。

我有同样的问题,重写模型的配置不是一个选项。 简单的技巧是手动执行调用功能。它可以控制激活。

从 Keras 复制粘贴 source,将 self 更改为 layer。您可以对任何其他层执行相同操作。

def conv_no_activation(layer, inputs, activation=False):

    if layer.rank == 1:
        outputs = K.conv1d(
            inputs,
            layer.kernel,
            strides=layer.strides[0],
            padding=layer.padding,
            data_format=layer.data_format,
            dilation_rate=layer.dilation_rate[0])
    if layer.rank == 2:
        outputs = K.conv2d(
            inputs,
            layer.kernel,
            strides=layer.strides,
            padding=layer.padding,
            data_format=layer.data_format,
            dilation_rate=layer.dilation_rate)
    if layer.rank == 3:
        outputs = K.conv3d(
            inputs,
            layer.kernel,
            strides=layer.strides,
            padding=layer.padding,
            data_format=layer.data_format,
            dilation_rate=layer.dilation_rate)

    if layer.use_bias:
        outputs = K.bias_add(
            outputs,
            layer.bias,
            data_format=layer.data_format)

    if activation and layer.activation is not None:
        outputs = layer.activation(outputs)

    return outputs

现在我们需要稍微修改一下 main 函数。首先,通过名称识别图层。然后从上一层检索激活。最后,计算目标层的输出。

def get_output_activation_control(model, images, layername, activation=False):
    """Get activations for the input from specified layer"""

    inp = model.input

    layer_id, layer = [(n, l) for n, l in enumerate(model.layers) if l.name == layername][0]
    prev_layer = model.layers[layer_id - 1]
    conv_out = conv_no_activation(layer, prev_layer.output, activation=activation)
    functor = K.function([inp] + [K.learning_phase()], [conv_out]) 

    return functor([images]) 

这是一个小测试。我使用的是 VGG16 模型。

a_relu = get_output_activation_control(vgg_model, img, 'block4_conv1', activation=True)[0]
a_no_relu = get_output_activation_control(vgg_model, img, 'block4_conv1', activation=False)[0]

print(np.sum(a_no_relu < 0))
> 245293

将所有负数设置为零以与嵌入 VGG16 ReLu 操作后检索到的结果进行比较。

a_no_relu[a_no_relu < 0] = 0
print(np.allclose(a_relu, a_no_relu))
> True

【讨论】:

【参考方案5】:

用新的激活函数定义新层的简单方法:

def change_layer_activation(layer):

    if isinstance(layer, keras.layers.Conv2D):

        config = layer.get_config()
        config["activation"] = "linear"
        new = keras.layers.Conv2D.from_config(config)

    elif isinstance(layer, keras.layers.Dense):

        config = layer.get_config()
        config["activation"] = "linear"
        new = keras.layers.Dense.from_config(config)

    weights = [x.numpy() for x in layer.weights]

    return new, weights

【讨论】:

【参考方案6】:

我遇到了同样的问题,但没有其他答案对我有用。我使用更新版本的 Keras 和 Tensorflow,所以一些答案现在不起作用。还给出了模型的结构,所以我不能轻易改变它。总体思路是创建原始模型的副本,该副本将与原始模型完全相同,但将激活与输出层分开。完成此操作后,我们可以在应用激活之前轻松访问输出值。

首先,我们将创建原始模型的副本,但不激活输出层。这将使用 Keras clone_model 函数 (See Docs) 完成。

from tensorflow.keras.models import clone_model
from tensorflow.keras.layers import Activation

original_model = get_model()

def f(layer):
  config = layer.get_config()
  if not isinstance(layer, Activation) and layer.name in original_model.output_names:
    config.pop('activation', None)
  layer_copy = layer.__class__.from_config(config)
  return layer_copy

copy_model = clone_model(model, clone_function=f)  

仅此一项只会生成具有新权重的克隆,因此我们必须将 original_model 权重复制到新权重:

copy_model.build(original_model.input_shape)
copy_model.set_weights(original_model.get_weights())

现在我们将添加激活层:

from tensorflow.keras.models import Model

old_outputs = [ original_model.get_layer(name=name) for name in copy_model.output_names ]
new_outputs = [ Activation(old_output.activation)(output) if old_output.activation else output 
                for output, old_output in zip(copy_model.outputs, old_outputs) ]
copy_model = Model(copy_model.inputs, new_outputs)

最后我们可以创建一个新模型,其评估将是未应用激活的输出:

no_activation_outputs = [ copy_model.get_layer(name=name).output for name in original_model.output_names ]
no_activation_model = Model(copy.inputs, no_activation_outputs)

现在我们可以像original_modelno_activation_model 一样使用copy_model 来访问预激活输出。实际上,您甚至可以修改代码以拆分一组自定义层而不是输出。

【讨论】:

以上是关于Keras 在激活函数之前检索节点的值的主要内容,如果未能解决你的问题,请参考以下文章

Keras 二元分类 - Sigmoid 激活函数

Keras深度学习实战——深度学习中常用激活函数和损失函数详解

深度学习Keras框架笔记之激活函数详解

如何在一个 Keras 层中使用不同的激活函数?

Keras官方中文文档:激活函数Activation

如何使用 Keras 创建自定义激活函数?