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_out
或 dense_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
。将这些放在一起,变量x
是model.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
,所以我需要的张量输出是<tf.Tensor 'predictions/BiasAdd:0' shape=(?, 1000) dtype=float32>
。
【讨论】:
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_model
和no_activation_model
一样使用copy_model
来访问预激活输出。实际上,您甚至可以修改代码以拆分一组自定义层而不是输出。
【讨论】:
以上是关于Keras 在激活函数之前检索节点的值的主要内容,如果未能解决你的问题,请参考以下文章