Keras获取激活函数前节点的值

25

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

[Dense]
    units = 612
    activation = softplus

[Dense]
    units = 1
    activation = sigmoid

网络的输出值为1,但我想知道Sigmoid函数的输入x是多少(必然是一个很大的数,因为sigm(x)在这里等于1)。

根据indraforyou的回答,我成功地检索到了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_out或者dense_1_weights的输出或权重出了问题吗?


1
难道不应该是 x = np.dot(dense_0_out, dense_1_weights) + dense_1_bias 吗? - Marcin Możejko
@MarcinMożejko 你说得对,我已经纠正了。由于偏差被训练为0.0,所以没有改变任何东西。 - johk95
@MarcinMożejko 你是指最后一层吗?它被馈送到sigmoid,是的。因此,如果该值为-11.7,则将其馈送到sigmoid并获得接近零的某些值。 layer_outs [-1] 却显示为1... - johk95
@MarcinMożejko 不是的,因为 dense_1_weights.shape = (1, 612)dense_0_out.shape = (612, 1)。为了确保,你可以执行 x = numpy.sum(dense_1_weights.flatten() * dense_0_out.flatten()),这会得到相同的结果。 - johk95
你可以打印出 model.summary() 吗? - Marcin Możejko
显示剩余3条评论
6个回答

11

假设你正在使用get_value(),我会假定你正在使用Theano后端。要获取sigmoid激活之前的节点值,你可以遍历计算图

可以从输出开始(某些计算的结果),通过所有者字段向下遍历到其输入。

在你的情况下,你想要的是sigmoid激活操作的输入x。 sigmoid操作的输出是model.output。将这些组合起来,变量xmodel.output.owner.inputs[0]

如果你打印出这个值,你会看到Elemwise{add,no_inplace}.0,它是一个逐元素加法操作。这可以从Dense.call()源代码中进行验证:

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]


谢谢你的回答!我明白你的方法更适合,但你能否简要评论一下我的原始代码...它是否计算错误?为什么? - johk95
你尝试过这种方法,但仍然得到了负数的 x 吗?我尝试了你的代码,得到了与这种方法完全相同的结果(一个 正数x,大约为 600),所以我不太确定你的代码出了什么问题。 - Yu-Yang
顺便说一句,我在我的程序中看到 dense_0_out.shape 等于 (1, 612)dense_1_weights.shape 等于 (612, 1),这与您发布的内容不同。您能提供您使用的 test_input 和 Keras 和 TF 的版本吗? - Yu-Yang
1
ขอโทษครับ ผมหมายถึงเวอร์ชันภาษาไทย ผมลองรันโค้ดเดียวกันบน Theano แล้วผลลัพธ์และรูปร่างยังคงเดิมเหมือนเดิมครับ คุณสามารถโพสต์โค้ดเพิ่มเติมได้ไหมครับ (เช่นการกำหนดและการฝึกโมเดล) บางครั้งข้อผิดพลาดอาจไม่เกิดขึ้นภายในบล็อกโค้ดที่คุณโพสต์ไว้ครับ - Yu-Yang

6
我可以看到一种简单的方法,只需稍微更改模型结构即可。(请参见最后如何使用现有模型并仅更改结尾)。
这种方法的优点是:
- 您不需要猜测是否进行了正确的计算 - 您不需要关心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)   

使用trainingModel进行正常训练。这两个模型共享权重,因此训练一个就是训练另一个。

仅使用checkingModel查看密集层的输出,使用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)

重要的是,在创建新的密集层之后,从旧的层获取权重:

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.predict(X)检查所需的值,并训练trainingModel

2

这篇文章是给谷歌同事们的,自从发布最佳答案以来,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>


在tf 2.4上引发了“TypeError:Keras符号输入/输出未实现'op'。” - Crispy13

1

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

我有同样的问题,重写模型配置不是一个选项。简单的方法是手动执行调用函数。这可以控制激活。

从Keras 源代码复制粘贴,将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

现在我们需要稍微修改一下主函数。首先,通过名称识别层。然后从上一层检索激活值。最后,从目标层计算输出。
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

1

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

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

0

我也遇到了同样的问题,但其他答案都不适用于我。我正在使用较新版本的Keras和Tensorflow,因此有些答案现在不起作用。而且模型的结构已经给定,所以我不能轻易地改变它。总体思路是创建一个原始模型的副本,该副本将与原始模型完全相同,但会将激活从输出层中分离出来。完成这一步骤后,我们可以轻松地访问应用激活之前的输出值。

首先,我们将创建一个原始模型的副本,但在输出层上没有激活。这将使用Keras的clone_model函数完成(请参阅文档)。

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来访问预激活输出。实际上,您甚至可以修改代码以拆分自定义层集而不是输出。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接