Как использовать GRUCell при определении пользовательского слоя keras с дополнительными весами? - PullRequest
0 голосов
/ 07 декабря 2018

Я определяю пользовательский слой встраивания слов keras, который использует как cnn, так и rnn, я изменил код из GRU, чтобы использовать GRUCell и некоторые собственные веса для фильтров CNN.Важно, что весовые коэффициенты фильтра определяются в методе построения для уровня, а весовые коэффициенты GRUCell определяются путем вызова метода построения соты.По какой-то причине это приводит к тому, что веса только для одного из них регистрируются, в зависимости от того, наследую ли я от RNN или Layer.Если я наследую от RNN, он регистрирует веса только для RNN, а если я наследую от уровня, он регистрирует веса только для CNN.Под регистром я подразумеваю, когда я использую слой и проверяю количество параметров, они соответствуют только тем для CNN и RNN соответственно.Я предполагаю, что другие наборы весов не обучаются, поскольку обучение не работает.Я хотел бы получить некоторые идеи о том, что не так с моим кодом, нужно ли мне определять веса для RNN внутри метода сборки для слоя?

from keras.layers.recurrent import RNN, GRUCell
import keras.backend as K
class cnn_lstm_layer(RNN):
def __init__(self, num_vocab, embed_dim, units=10):
    self.num_vocab = num_vocab
    self.embed_dim = embed_dim
    self.units = units
    cell = GRUCell(units)
    super().__init__(cell)

def build(self, input_shape):

    f2_count = 3
    f3_count = 4
    f4_count = 5

    self.embeddings = self.add_weight(
        shape=[self.num_vocab, self.embed_dim],
        initializer='glorot_uniform',
        name='char_embeddings')
    self.kernel2 = self.add_weight(
        shape=[2, self.embed_dim, 1, f2_count],
        initializer='glorot_uniform',
        name='f2')
    self.kernel3 = self.add_weight(
        shape=[3, self.embed_dim, 1, f3_count],
        initializer='glorot_uniform',
        name='f3')
    self.kernel4 = self.add_weight(
        shape=[4, self.embed_dim, 1, f4_count],
        initializer='glorot_uniform',
        name='f4')
    self.conv_output_len = f2_count + f3_count + f4_count

    step_input_shape = [None, self.conv_output_len]
    self.cell.build(step_input_shape)

    self.built = True

def call(self, inputs):
    '''
    uses the following reshaping trick
    /10158700/raspredelennye-po-vremeni-svertochnye-sloi-v-tenzornom-potoke
    '''
    inputs = K.cast(inputs, tf.int32)
    encoded = K.gather(self.embeddings, inputs)
    #        encoded = K.print_tensor(encoded, message = 'encoded: ')
    #        encoded = K.tf.Print(encoded, data = [encoded],message='encoded: ',summarize = 1000)
    encoded = K.expand_dims(encoded, -1)
    input_shape = K.int_shape(encoded)
    _, s_words, s_char, s_embed_dim, _ = input_shape
    encoded = K.reshape(encoded, (-1, s_char, s_embed_dim, 1))
    paddings2 = [[0, 0], [1, 0], [0, 0], [0, 0]]
    paddings3 = [[0, 0], [2, 0], [0, 0], [0, 0]]
    paddings4 = [[0, 0], [3, 0], [0, 0], [0, 0]]

    c2 = K.conv2d(tf.pad(encoded, paddings2),
                  self.kernel2,
                  data_format='channels_last',
                  padding='valid')  # shape = (?,19,1,3)
    c3 = K.conv2d(tf.pad(encoded, paddings3),
                  self.kernel3,
                  data_format='channels_last',
                  padding='valid')
    c4 = K.conv2d(tf.pad(encoded, paddings4),
                  self.kernel4,
                  data_format='channels_last',
                  padding='valid')
    c = K.concatenate([c2, c3, c4], axis=3)  # shape = (?,19,1,12)
    c = K.squeeze(c, 2)  # shape = (?,19,12)


    initial_state = self.get_initial_state(c)
    last_output, outputs, states = K.rnn(self.cell.call,
                                            c,
                                            initial_state)
    output = last_output

    output = K.reshape(output,(-1,s_words,self.units))
    return output


def get_initial_state(self, inputs):
    # build an all-zero tensor of shape (samples, output_dim)
    initial_state = K.zeros_like(inputs)  # (samples, timesteps, input_dim)
    initial_state = K.sum(initial_state, axis=(1, 2))  # (samples,)
    initial_state = K.expand_dims(initial_state)  # (samples, 1)
    if hasattr(self.cell.state_size, '__len__'):
        return [K.tile(initial_state, [1, dim])
                for dim in self.cell.state_size]
    else:
        return [K.tile(initial_state, [1, self.cell.state_size])]



def compute_output_shape(self, input_shape):
    batch_size, s_words, s_char = input_shape
    output_shape = (batch_size, s_words,self.units)
    return output_shape

1 Ответ

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

Суть проблемы заключается в методе trainable_weights.В классе RNN это определяется как

def trainable_weights(self):
    if not self.trainable:
        return []
    if isinstance(self.cell, Layer):
        return self.cell.trainable_weights
    return []

, в то время как в классе Layer это определяется как

def trainable_weights(self):
    trainable = getattr(self, 'trainable', True)
    if trainable:
        return self._trainable_weights
    else:
        return []

Это причина, почему при наследовании от RNN и Layer дают веса от RNNили только CNN.Тогда решение состоит в том, чтобы переписать trainable_weights, чтобы учесть как весовые коэффициенты, определенные в методе построения снаружи, так и весовые коэффициенты, определенные в методе построения ячейки.т.е.

@property
def trainable_weights(self):
    return self._trainable_weights + self.cell.trainable_weights
...