web-dev-qa-db-fra.com

Keras: masquage et aplatissement

J'ai du mal à construire un modèle simple qui traite des valeurs d'entrée masquées. Mes données d’entraînement sont constituées de listes de traces GPS de longueur variable, c’est-à-dire des listes où chaque élément contient la latitude et la longitude.

Il y a 70 exemples de formation 

 enter image description here

Comme ils ont des longueurs variables, je les remplis de zéros, dans le but de dire à Keras d'ignorer ces valeurs nulles.

train_data = keras.preprocessing.sequence.pad_sequences(train_data, maxlen=max_sequence_len, dtype='float32', 
                                           padding='pre', truncating='pre', value=0)

 enter image description here

Je construis ensuite un modèle très basique comme

model = Sequential()
model.add(Dense(16, activation='relu',input_shape=(max_sequence_len, 2)))
model.add(Flatten())
model.add(Dense(2, activation='sigmoid'))

Après quelques essais et erreurs, j'ai réalisé que j'avais besoin de la couche Flatten ou que l'ajustement du modèle jetterait l'erreur.

ValueError: Error when checking target: expected dense_87 to have 3 dimensions, but got array with shape (70, 2)

En incluant cette couche Flatten, cependant, je ne peux pas utiliser une couche Masking (pour ignorer les zéros remplis) ou Keras lève cette erreur

TypeError: Layer flatten_31 does not support masking, but was passed an input_mask: Tensor("masking_9/Any_1:0", shape=(?, 48278), dtype=bool)

J'ai longuement cherché, en lisant des publications sur GitHub et de nombreuses Q/A ici, mais je n'arrive pas à comprendre. 

6
Philip O'Brien

Le masquage semble déréglé. Mais ne vous inquiétez pas: les 0 ne vont pas aggraver votre modèle; tout au plus moins efficace.

Je recommanderais d'utiliser une approche convolutionnelle au lieu de pure Dense ou peut-être RNN. Je pense que cela fonctionnera très bien pour les données GPS.

S'il vous plaît essayez le code suivant:

from keras.preprocessing.sequence import pad_sequences
from keras import Sequential
from keras.layers import Dense, Flatten, Masking, LSTM, GRU, Conv1D, Dropout, MaxPooling1D
import numpy as np
import random

max_sequence_len = 70

n_samples = 100
num_coordinates = 2 # lat/long

data = [[[random.random() for _ in range(num_coordinates)]
         for y in range(min(x, max_sequence_len))]
        for x in range(n_samples)]

train_y = np.random.random((n_samples, 2))

train_data = pad_sequences(data, maxlen=max_sequence_len, dtype='float32',
                           padding='pre', truncating='pre', value=0)

model = Sequential()
model.add(Conv1D(32, (5, ), input_shape=(max_sequence_len, num_coordinates)))
model.add(Dropout(0.5))
model.add(MaxPooling1D())
model.add(Flatten())
model.add(Dense(2, activation='relu'))
model.compile(loss='mean_squared_error', optimizer="adam")
model.fit(train_data, train_y)
2
PascalVKooten

Au lieu d'utiliser une couche Flatten, vous pouvez utiliser une couche Global Pooling

Celles-ci sont adaptées pour réduire la dimension longueur/temps sans perdre la possibilité d'utiliser des longueurs variables. 

Ainsi, au lieu de Flatten(), vous pouvez essayer un GlobalAveragePooling1D ou GlobalMaxPooling1D.

Aucun d'entre eux n'utilise supports_masking dans leur code, ils doivent donc être utilisés avec précaution.

Le moyen considérera plus d'entrées que le maximum (donc les valeurs qui doivent être masquées). 

Le max prendra un seul de la longueur. Avec un peu de chance, si toutes vos valeurs utiles sont supérieures à celles de la position masquée, le masque sera indirectement conservé. Il faudra probablement encore plus de neurones d'entrée que les autres.

Cela dit, oui, essayez les méthodes Conv1D ou RNN (LSTM) suggérées. 


Création d'un calque de regroupement personnalisé avec masque

Vous pouvez également créer votre propre couche de mise en pool (nécessite un modèle d'API fonctionnel dans lequel vous transmettez les entrées du modèle et le tenseur que vous souhaitez mettre en pool).

Ci-dessous, un exemple de travail avec la mise en commun moyenne appliquant un masque basé sur les entrées: 

def customPooling(maskVal):
    def innerFunc(x):
        inputs = x[0]
        target = x[1]

        #getting the mask by observing the model's inputs
        mask = K.equal(inputs, maskVal)
        mask = K.all(mask, axis=-1, keepdims=True)

        #inverting the mask for getting the valid steps for each sample
        mask = 1 - K.cast(mask, K.floatx())

        #summing the valid steps for each sample
        stepsPerSample = K.sum(mask, axis=1, keepdims=False)

        #applying the mask to the target (to make sure you are summing zeros below)
        target = target * mask

        #calculating the mean of the steps (using our sum of valid steps as averager)
        means = K.sum(target, axis=1, keepdims=False) / stepsPerSample

        return means

    return innerFunc


x = np.ones((2,5,3))
x[0,3:] = 0.
x[1,1:] = 0.


print(x)

inputs = Input((5,3))
out = Lambda(lambda x: x*4)(inputs)
out = Lambda(customPooling(0))([inputs,out])

model = Model(inputs,out)
model.predict(x)
1
Daniel Möller