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_out
或 dense_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 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
所以这是针对其他 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_model
和 no_activation_model
一样使用 copy_model
来访问预激活输出。实际上,您甚至可以修改代码以拆分一组自定义图层而不是输出。
想象一个全连接神经网络,其最后两层具有以下结构:
[Dense]
units = 612
activation = softplus
[Dense]
units = 1
activation = sigmoid
网络的输出值为 1,但我想知道 sigmoidal 函数的输入 x 是什么(必须是一些高数,因为这里的 sigm(x) 是 1)。
根据
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_out
或 dense_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 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
所以这是针对其他 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_model
和 no_activation_model
一样使用 copy_model
来访问预激活输出。实际上,您甚至可以修改代码以拆分一组自定义图层而不是输出。