Реализация пользовательского уровня Hebbian в Keras - затемнение ввода / вывода и соединения с боковыми узлами - PullRequest
0 голосов
/ 28 декабря 2018

Я пытаюсь реализовать неконтролируемый ANN, используя обновление Hebbian в Keras.Я нашел пользовательский слой Hebbian, созданный Дэном Сондерсом, здесь - https://github.com/djsaunde/rinns_python/blob/master/hebbian/hebbian.py (я надеюсь, что задавать вопросы о коде другого человека здесь неплохо)

В примерах, которые я нашел, используя этот слой врепо, этот слой используется в качестве промежуточного слоя между слоями Dense / Conv, но я хотел бы построить сеть, используя только слои Hebbian.

В этой реализации меня смущают две важные вещи:

  1. Кажется, что входные и выходные значения должны быть одинаковыми, чтобы этот слой работал.Почему это так, и что я могу сделать, чтобы они могли отличаться?

  2. Почему диагональ весовой матрицы установлена ​​на ноль?Это говорит о том, что это «гарантирует, что ни один нейрон не соединен в боковом направлении с самим собой», но я думал, что вес соединения был между предыдущим слоем и текущим слоем, а не текущим слоем и самим собой.

Вот код для реализации Hebbian Layer:

    from keras import backend as K
    from keras.engine.topology import Layer

    import numpy as np
    import tensorflow as tf

    np.set_printoptions(threshold=np.nan)

    sess = tf.Session()


    class Hebbian(Layer):


    def __init__(self, output_dim, lmbda=1.0, eta=0.0005, connectivity='random', connectivity_prob=0.25, **kwargs):
    '''
    Constructor for the Hebbian learning layer.

    args:
        output_dim - The shape of the output / activations computed by the layer.
        lambda - A floating-point valued parameter governing the strength of the Hebbian learning activation.
        eta - A floating-point valued parameter governing the Hebbian learning rate.
        connectivity - A string which determines the way in which the neurons in this layer are connected to
            the neurons in the previous layer.
    '''
    self.output_dim = output_dim
    self.lmbda = lmbda
    self.eta = eta
    self.connectivity = connectivity
    self.connectivity_prob = connectivity_prob

    if self.connectivity == 'random':
        self.B = np.random.random(self.output_dim) < self.connectivity_prob
    elif self.connectivity == 'zero':
        self.B = np.zeros(self.output_dim)

    super(Hebbian, self).__init__(**kwargs)


    def random_conn_init(self, shape, dtype=None):
    A = np.random.normal(0, 1, shape)
    A[self.B] = 0
    return tf.constant(A, dtype=tf.float32)


    def zero_init(self, shape, dtype=None):
    return np.zeros(shape)


    def build(self, input_shape):
    # create weight variable for this layer according to user-specified initialization
    if self.connectivity == 'all':
        self.kernel = self.add_weight(name='kernel', shape=(np.prod(input_shape[1:]), \
                            np.prod(self.output_dim)), initializer='uniform', trainable=False)
    elif self.connectivity == 'random':
        self.kernel = self.add_weight(name='kernel', shape=(np.prod(input_shape[1:]), \
                            np.prod(self.output_dim)), initializer=self.random_conn_init, trainable=False)
    elif self.connectivity == 'zero':
        self.kernel = self.add_weight(name='kernel', shape=(np.prod(input_shape[1:]), \
                            np.prod(self.output_dim)), initializer=self.zero_init, trainable=False)
    else:
        raise NotImplementedError

    # ensure that no neuron is laterally connected to itself
    self.kernel = self.kernel * tf.diag(tf.zeros(self.output_dim))

    # call superclass "build" function
    super(Hebbian, self).build(input_shape)


    def call(self, x):
    x_shape = tf.shape(x)
    batch_size = tf.shape(x)[0]

    # reshape to (batch_size, product of other dimensions) shape
    x = tf.reshape(x, (tf.reduce_prod(x_shape[1:]), batch_size))

    # compute activations using Hebbian-like update rule
    activations = x + self.lmbda * tf.matmul(self.kernel, x)

    # compute outer product of activations matrix with itself
    outer_product = tf.matmul(tf.expand_dims(x, 1), tf.expand_dims(x, 0))

    # update the weight matrix of this layer
    self.kernel = self.kernel + tf.multiply(self.eta, tf.reduce_mean(outer_product, axis=2))
    self.kernel = tf.multiply(self.kernel, self.B)
    self.kernel = self.kernel * tf.diag(tf.zeros(self.output_dim))

    return K.reshape(activations, x_shape)

При первой проверке я ожидал, что этот слой сможет принимать входные данные с предыдущего уровня, выполнитьпростой расчет активации (входной * вес), обновите веса в соответствии с обновлением Hebbian (что-то вроде - если активация высока b / t узлов, увеличьте вес), затем передайте активации на следующий уровень.

Я также ожидал, что он сможет справиться с уменьшением / увеличением количества узлов от одного уровня к следующему.

Вместо этого я не могу понять, почему входные и выходные яркости должны быть одинаковыми и почему диагонали весовой матрицы установлены на ноль.

Где в коде (неявно или явно) указывается, что уровни должны быть одинаковыми?

Где в коде (неявно или явно) указывается, что весовая матрица этого слоя соединяет текущий слой с самим собой?

Извините, если этот Q должен был быть разделен на 2, но кажется, что они могут быть связаны с e / o, поэтому я оставил их равными 1.

Рад предоставить больше подробностей, если это необходимо.

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

model = Sequential()
model.add(Hebbian(input_shape = (256,1), output_dim = 256))

Это компилируется без ошибки ^

model = Sequential()
model.add(Hebbian(input_shape = (256,1), output_dim = 24))

Этот ^ выдает ошибку: IndexError: логический индекс не соответствует индексированному массиву по измерению 0;размерность 256, но соответствующее логическое измерение 24

1 Ответ

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

Хорошо, я думаю, что, возможно, понял это, вроде.Было много мелких проблем, но самое главное, что мне нужно было добавить функцию compute_output_shape, которая позволяет слою изменять форму своего ввода, как описано здесь: https://keras.io/layers/writing-your-own-keras-layers/

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

class Hebbian(Layer):


def __init__(self, output_dim, lmbda=1.0, eta=0.0005, connectivity='random', connectivity_prob=0.25, **kwargs):
    '''
    Constructor for the Hebbian learning layer.

    args:
        output_dim - The shape of the output / activations computed by the layer.
        lambda - A floating-point valued parameter governing the strength of the Hebbian learning activation.
        eta - A floating-point valued parameter governing the Hebbian learning rate.
        connectivity - A string which determines the way in which the neurons in this layer are connected to
            the neurons in the previous layer.
    '''
    self.output_dim = output_dim
    self.lmbda = lmbda
    self.eta = eta
    self.connectivity = connectivity
    self.connectivity_prob = connectivity_prob

    super(Hebbian, self).__init__(**kwargs)



def random_conn_init(self, shape, dtype=None):
    A = np.random.normal(0, 1, shape)
    A[self.B] = 0
    return tf.constant(A, dtype=tf.float32)


def zero_init(self, shape, dtype=None):
    return np.zeros(shape)


def build(self, input_shape):
    # create weight variable for this layer according to user-specified initialization
    if self.connectivity == 'random':
        self.B = np.random.random(input_shape[0]) < self.connectivity_prob
    elif self.connectivity == 'zero':
        self.B = np.zeros(self.output_dim)

    if self.connectivity == 'all':
        self.kernel = self.add_weight(name='kernel', shape=(np.prod(input_shape[1:]), \
                    np.prod(self.output_dim)), initializer='uniform', trainable=False)
    elif self.connectivity == 'random':
        self.kernel = self.add_weight(name='kernel', shape=(np.prod(input_shape[1:]), \
                    np.prod(self.output_dim)), initializer=self.random_conn_init, trainable=False)
    elif self.connectivity == 'zero':
        self.kernel = self.add_weight(name='kernel', shape=(np.prod(input_shape[1:]), \
                    np.prod(self.output_dim)), initializer=self.zero_init, trainable=False)
    else:
        raise NotImplementedError


    # call superclass "build" function
    super(Hebbian, self).build(input_shape)


def call(self, x):  # x is the input to the network
    x_shape = tf.shape(x)
    batch_size = tf.shape(x)[0]

    # reshape to (batch_size, product of other dimensions) shape
    x = tf.reshape(x, (tf.reduce_prod(x_shape[1:]), batch_size))

    # compute activations using Hebbian-like update rule
    activations = x + self.lmbda * tf.matmul(self.kernel, x)  


    # compute outer product of activations matrix with itself
    outer_product = tf.matmul(tf.expand_dims(x, 1), tf.expand_dims(x, 0)) 

    # update the weight matrix of this layer
    self.kernel = self.kernel + tf.multiply(self.eta, tf.reduce_mean(outer_product, axis=2)) 
    self.kernel = tf.multiply(self.kernel, self.B)
    return K.reshape(activations, x_shape)

def compute_output_shape(self, input_shape):
    return (input_shape[0], self.output_dim)
...