Не полностью связанный слой в тензорном потоке - PullRequest
0 голосов
/ 19 декабря 2018

Я хочу создать сеть, в которой на входном уровне узлы просто связаны с некоторыми узлами на следующем уровне.Вот небольшой пример:

enter image description here

Мое решение до сих пор состоит в том, чтобы я установил вес ребра от i1 до h1 на нольи после каждого шага оптимизации я умножаю весовые коэффициенты на матрицу (я называю эту матричную маску матрицы), в которой каждая запись равна 1, за исключением записи веса ребра между i1 и h1.(См. Код ниже)

Правильный ли это подход?Или это влияет на GradientDescent?Есть ли другой подход к созданию такой сети в TensorFlow?

import tensorflow as tf
import tensorflow.contrib.eager as tfe
import numpy as np

tf.enable_eager_execution()


model = tf.keras.Sequential([
  tf.keras.layers.Dense(2, activation=tf.sigmoid, input_shape=(2,)),  # input shape required
  tf.keras.layers.Dense(2, activation=tf.sigmoid)
])


#set the weights
weights=[np.array([[0, 0.25],[0.2,0.3]]),np.array([0.35,0.35]),np.array([[0.4,0.5],[0.45, 0.55]]),np.array([0.6,0.6])]

model.set_weights(weights)

model.get_weights()

features = tf.convert_to_tensor([[0.05,0.10 ]])
labels =  tf.convert_to_tensor([[0.01,0.99 ]])


mask =np.array([[0, 1],[1,1]])

#define the loss function
def loss(model, x, y):
  y_ = model(x)
  return tf.losses.mean_squared_error(labels=y, predictions=y_)

#define the gradient calculation
def grad(model, inputs, targets):
  with tf.GradientTape() as tape:
    loss_value = loss(model, inputs, targets)
  return loss_value, tape.gradient(loss_value, model.trainable_variables) 

#create optimizer an global Step
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
global_step = tf.train.get_or_create_global_step()


#optimization step
loss_value, grads = grad(model, features, labels)
optimizer.apply_gradients(zip(grads, model.variables),global_step)

#masking the optimized weights 
weights=(model.get_weights())[0]
masked_weights=tf.multiply(weights,mask)
model.set_weights([masked_weights])

Ответы [ 3 ]

0 голосов
/ 19 декабря 2018

У вас есть несколько вариантов здесь.

Во-первых, вы можете использовать подход динамического маскирования в вашем примере.Я полагаю, что это будет работать, как и ожидалось, поскольку градиенты с замаскированными параметрами будут равны нулю (выходной сигнал постоянен при изменении неиспользуемых параметров).Этот подход прост и его можно использовать, даже если ваша маска не постоянна во время тренировки.

Во-вторых, если вы заранее знаете, какие веса всегда будут равны нулю, вы можете составить свою матрицу весов, используя tf.get_variable дляполучить подматрицу, а затем объединить ее с тензором tf.constant, например:

weights_sub = tf.get_variable("w", [dim_in, dim_out - 1])
zeros = tf.zeros([dim_in, 1])
weights = tf.concat([weights_sub, zeros], axis=1)

. В этом примере один столбец вашей весовой матрицы будет всегда равен нулю.

Наконец, еслиВаша маска более сложна, вы можете использовать tf.get_variable для плоского вектора, а затем составить tf.SparseTensor со значениями переменных для используемых индексов:

weights_used = tf.get_variable("w", [num_used_vars])
indices = ...  # get your indices in a 2-D matrix of shape [num_used_vars, 2]
dense_shape = tf.constant([dim_in, dim_out])  # this is the final shape of the weight matrix
weights = tf.SparseTensor(indices, weights_used, dense_shape)

РЕДАКТИРОВАТЬ: Это, вероятно, не будет работать вкомбинация с методом set_weights Кераса, так как он ожидает массивы Numpy, а не Tensors.

0 голосов
/ 19 декабря 2018

Проблема с вашим решением и некоторыми другими, предложенными другими ответами в этом посте, заключается в том, что они не предотвращают тренировку этого веса.Они позволяют градиентному спуску тренировать несуществующий вес и затем перезаписывают его ретроспективно.Это приведет к тому, что сеть будет иметь ноль в этом месте по желанию, но отрицательно повлияет на ваш тренировочный процесс, так как вычисление обратного распространения не будет видеть шаг маскирования, так как он не является частью графика TensorFlow, и поэтому будет следовать градиентный спуск.путь, который включает в себя предположение, что этот вес оказывает влияние на результат (это не так).

Лучшим решением было бы включить шаг маскирования как часть вашего TensorFlowграфик, так что он может быть учтен в градиентном спуске.Поскольку шаг маскирования - это просто поэлементное умножение на ваш разреженный двоичный код martix mask, вы можете просто включить матрицу mask в качестве множителя элементарной матрицы в определении графа, используя tf.multiply.

К сожалению, это означает прощание с простыми в использовании керасами, методами наложения слоев и более понятным подходом к TensorFlow.Я не вижу очевидного способа сделать это с помощью API слоев.

См. Реализацию ниже, я постарался предоставить комментарии, объясняющие, что происходит на каждом этапе.

import tensorflow as tf

## Graph definition for model

# set up tf.placeholders for inputs x, and outputs y_
# these remain fixed during training and can have values fed to them during the session
with tf.name_scope("Placeholders"):
    x = tf.placeholder(tf.float32, shape=[None, 2], name="x")   # input layer
    y_ = tf.placeholder(tf.float32, shape=[None, 2], name="y_") # output layer

# set up tf.Variables for the weights at each layer from l1 to l3, and setup feeding of initial values
# also set up mask as a variable and set it to be un-trianable
with tf.name_scope("Variables"):
    w_l1_values = [[0, 0.25],[0.2,0.3]]
    w_l1 = tf.Variable(w_l1_values, name="w_l1")
    w_l2_values = [[0.4,0.5],[0.45, 0.55]]
    w_l2 = tf.Variable(w_l2_values, name="w_l2")

    mask_values = [[0., 1.], [1., 1.]]
    mask = tf.Variable(mask_values, trainable=False, name="mask")


# link each set of weights as matrix multiplications in the graph. Inlcude an elementwise multiplication by mask.
# Sequence takes us from inputs x to output final_out, which will be compared to labels fed to placeholder y_
l1_out = tf.nn.relu(tf.matmul(x, tf.multiply(w_l1, mask)), name="l1_out")
final_out = tf.nn.relu(tf.matmul(l1_out, w_l2), name="output")


## define loss function and training operation
with tf.name_scope("Loss"):
    # some loss defined as a function of graph output: final_out and labels: y_
    loss = tf.nn.sigmoid_cross_entropy_with_logits(logits=final_out, labels=y_, name="loss")

with tf.name_scope("Train"):
    # some optimisation strategy, arbitrary learning rate
    optimizer = tf.train.AdamOptimizer(learning_rate=0.001, name="optimizer_adam")
    train_op = optimizer.minimize(loss, name="train_op")


# create session, initialise variables and train according to inputs and corresponding labels
# This should show that the values of the first layer weights change, but the one set to 0 remains at 0
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    initial_l1_weights = sess.graph.get_tensor_by_name("Variables/w_l1:0")
    print(initial_l1_weights.eval())

    inputs = [[0.05, 0.10]]
    labels = [[0.01, 0.99]]
    ans = sess.run(train_op, feed_dict={"Placeholders/x:0": inputs, "Placeholders/y_:0": labels})

    train_steps = 1
    for i in range(train_steps):
        initial_l1_weights = sess.graph.get_tensor_by_name("Variables/w_l1:0")
    print(initial_l1_weights.eval())

Или используйте ответ, предоставленный сегодня для дружественного керасу варианта.

0 голосов
/ 19 декабря 2018

Если вы ищете решение для конкретного предоставленного вами примера, вы можете просто использовать tf.keras Функциональный API и определить два плотных слоя, где один связан с обоими нейронами в предыдущем слое, а другой - только содин из нейронов:

from tensorflow.keras.layer import Input, Lambda, Dense, concatenate
from tensorflow.keras.models import Model

inp = Input(shape=(2,))
inp2 = Lambda(lambda x: x[:,1:2])(inp)   # get the second neuron 

h1_out = Dense(1, activation='sigmoid')(inp2)  # only connected to the second neuron
h2_out = Dense(1, activation='sigmoid')(inp)  # connected to both neurons
h_out = concatenate([h1_out, h2_out])

out = Dense(2, activation='sigmoid')(h_out)

model = Model(inp, out)

# simply train it using `fit`
model.fit(...)
...