Keras извлекает значение node перед активацией
Представьте себе полностью связанную нейронную сеть с последними двумя слоями следующей структуры:
[Dense]
units = 612
activation = softplus
[Dense]
units = 1
activation = sigmoid
Выходное значение сети равно 1, но я хотел бы знать, что входной сигнал x для сигмоидальной функции (должно быть некоторое большое число, так как sigm (x) здесь 1).
Folllowing ответ 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 может быть отрицательным числом? В этом случае вывод последних слоев должен быть рядом ближе к 0.0, чем 1.0. Являются ли dense_0_out
или dense_1_weights
неправильными выходами или весами?
Ответы
Ответ 1
Поскольку вы используете get_value()
, я предполагаю, что вы используете бэкэнд Theano. Чтобы получить значение node до активации сигмоида, вы можете пересечь граф вычислений.
График может быть пройден, начиная с выходов (результат некоторых вычислений) вплоть до его входов, используя поле владельца.
В вашем случае вам нужен вход x
активации сигмоида op. Выход сигмоида op равен model.output
. Объединяя их, переменная x
равна model.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()
.
С небольшой модификацией вашего кода вы можете получить значение node перед активацией:
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]
.
Ответ 2
Я вижу простой способ немного изменить структуру модели. (См. В конце, как использовать существующую модель и изменить только окончание).
Преимущества этого метода заключаются в следующем:
- Вам не нужно угадывать, выполняете ли вы правильные вычисления
- Вам не нужно заботиться о слоях отсеивания и о том, как реализовать вычисление выпадения
- Это чистое решение 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
(TF backend) Решение для слоев Conv.
У меня был тот же вопрос, и переписать конфигурацию модели было невозможно. Простым хаком было бы выполнить функцию вызова вручную. Это дает контроль над активацией.
Скопируйте и вставьте из источника 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