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

Keras retrieve value of node before activation function

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

[Dense]
    units = 612
    activation = softplus

[Dense]
    units = 1
    activation = sigmoid

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

根据 的回答,我设法检索了 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怎么可能是负数呢?在这种情况下,最后一层输出应该是一个更接近 0.0 而不是 1.0 的数字。 dense_0_outdense_1_weights 是错误的输出或权重吗?

我可以看到一种简单的方法,只需稍微更改模型结构即可。 (见文末如何利用已有模型,只改结尾)

这种方法的优点是:

  • 您不必猜测自己的计算是否正确
  • 你不需要关心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)

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

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

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

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

The graph can be traversed starting from outputs (the result of some computation) down to its inputs using the owner field.

在你的例子中,你想要的是 sigmoid 激活操作的输入 x。 sigmoid 运算的输出是 model.output。把这些放在一起,变量 x 就是 model.output.owner.inputs[0].

如果你打印出这个值,你会看到 Elemwise{add,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]

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

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

Copy-paste 来自 Keras sourceself 更改为 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

所以这是针对其他 google 员工的,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 的致密层,所以我需要的张量输出是 <tf.Tensor 'predictions/BiasAdd:0' shape=(?, 1000) dtype=float32>

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

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

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

首先,我们将创建原始模型的副本,但在输出层上没有激活。这将使用 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 来访问预激活输出。实际上,您甚至可以修改代码以拆分一组自定义图层而不是输出。