Tempo de previsão inconsistente de Keras

17

Tentei obter uma estimativa do tempo de previsão do meu modelo keras e percebi algo estranho. Além de ser bastante rápido normalmente, de vez em quando o modelo precisa de muito tempo para apresentar uma previsão. E não apenas isso, esses tempos também aumentam quanto mais tempo o modelo é executado. Adicionei um exemplo de trabalho mínimo para reproduzir o erro.

import time
import numpy as np
from sklearn.datasets import make_classification
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten

# Make a dummy classification problem
X, y = make_classification()

# Make a dummy model
model = Sequential()
model.add(Dense(10, activation='relu',name='input',input_shape=(X.shape[1],)))
model.add(Dense(2, activation='softmax',name='predictions'))
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])

model.fit(X, y, verbose=0, batch_size=20, epochs=100)

for i in range(1000):
    # Pick a random sample
    sample = np.expand_dims(X[np.random.randint(99), :], axis=0)
    # Record the prediction time 10x and then take the average
    start = time.time()
    for j in range(10):
        y_pred = model.predict_classes(sample)
    end = time.time()
    print('%d, %0.7f' % (i, (end-start)/10))

O tempo não depende da amostra (está sendo escolhida aleatoriamente). Se o teste for repetido, os índices no loop for, onde a previsão leva mais tempo, serão (quase) os mesmos novamente.

insira a descrição da imagem aqui

Estou a usar:

tensorflow 2.0.0
python 3.7.4

Para minha aplicação, preciso garantir a execução em um determinado período de tempo. No entanto, isso é impossível considerando esse comportamento. O que está acontecendo de errado? É um bug no Keras ou no backend tensorflow?

EDIT: predict_on_batchmostra o mesmo comportamento, no entanto, mais esparsos: insira a descrição da imagem aqui

y_pred = model(sample, training=False).numpy() mostra alguns outliers pesados ​​também, no entanto, eles não estão aumentando. insira a descrição da imagem aqui

EDIT 2: Fiz o downgrade para a versão mais recente do tensorflow 1 (1.15). Além de o problema não existir mais, o tempo de previsão "normal" melhorou significativamente! Não vejo os dois picos como problemáticos, pois não apareceram quando repeti o teste (pelo menos não nos mesmos índices e aumentando linearmente) e são percentuais não tão grandes quanto no primeiro gráfico. insira a descrição da imagem aqui

Assim, podemos concluir que esse parece ser um problema inerente ao tensorflow 2.0, que mostra um comportamento semelhante em outras situações, como menciona o @OverLordGoldDragon.

ga97dil
fonte
Esse comportamento parece previsível ... o aumento é meio linear. Se você incluir esse comportamento no seu cálculo de tempo, ele não funcionará? --- Eu não sei o que está acontecendo lá .... mas o que acontece se você tentar predict_on_batch?
Daniel Möller
Outra tentativa, o que acontece com y_pred = model(sample).numpy()e com y_pred = model(sample, training=False).numpy()?
Daniel Möller
Eu adicionei minhas descobertas. As versões numpy parecem não mostrar o comportamento.
ga97dil 17/02
Mas predict_classesainda é o mais rápido .... parece. Que tal apenas predict?
Daniel Möller
11
Presumo que isso possa ser algum tipo de limpeza de memória ...
Daniel Möller

Respostas:

10

O TF2 geralmente exibe um gerenciamento de memória ruim e semelhante a erros em vários casos que encontrei - breve descrição aqui e aqui . Com a previsão em particular, o método de alimentação com melhor desempenho é via model(x)diretamente - veja aqui e suas discussões vinculadas.

Em poucas palavras: model(x)actua através do seu seu __call__método (que herda base_layer.Layer), ao passo que predict(), predict_classes()etc. envolver uma função de circuito dedicado via _select_training_loop(); cada um utiliza diferentes métodos de pré e pós-processamento de dados, adequados para diferentes casos de uso, e model(x)no 2.1 foi projetado especificamente para obter o desempenho mais rápido de modelos pequenos / lotes pequenos (e talvez de qualquer tamanho) (e ainda mais rápido no 2.0).

Citando um desenvolvedor TensorFlow de discussões vinculadas:

Você pode prever a saída usando a chamada de modelo, e não a previsão do modelo, ou seja, a chamada model(x)tornaria isso muito mais rápido, porque não há parte de "conversão para conjunto de dados" e também está chamando diretamente um cache tf.function.

Nota : isso deve ser um problema menor no 2.1, e principalmente no 2.2 - mas teste cada método de qualquer maneira. Também sei que isso não responde diretamente à sua pergunta sobre os picos de tempo; Suspeito que esteja relacionado aos mecanismos de cache do Eager, mas a maneira mais segura de determinar é via TF Profiler, que é quebrada no 2.1.


Atualização : quanto ao aumento de picos, possível otimização da GPU; você fez ~ 1000 iters, tente 10.000 - eventualmente, o aumento deve parar. Como você observou em seus comentários, isso não ocorre com model(x); faz sentido, pois uma etapa a menos da GPU está envolvida ("conversão em conjunto de dados").

Update2 : você pode corrigir os desenvolvedores aqui sobre isso se enfrentar esse problema; é principalmente eu cantando lá

OverLordGoldDragon
fonte
Essa é uma boa resposta para o motivo de um método ser mais lento, mas não explica o aumento do tempo de execução em várias execuções.
LLSv2.0
11
@ LLSv2.0 Não tenho muita certeza de mim, mas resposta atualizada - Ainda estou aguardando uma resposta dos desenvolvedores quando levantei esse problema aqui aqui
OverLordGoldDragon
11
@ ga97dil Sim, estou sem explicações - tente perguntar no Github, embora você possa enfrentar longos tempos de resposta.
OverLordGoldDragon
11
@ ga97dil De fato, o TF1 pode ser muito mais rápido que o TF2 - embora o TF 2.1 valha a pena tentar para pequenos modelos e conjuntos de dados, pois é o mais rápido no treinamento em meus benchmarks (não fiz previsões). Mais importante, se você usa o TF2, sugiro fortemente que você teste a reprodutibilidade no Graph vs. Eager; os resultados podem diferir extremamente no TF 2.1.
OverLordGoldDragon
11
Adicionei sua postagem ao tópico Git e minha postagem TF2 vs. TF1. Obrigado por me informar que o problema desaparece no TF 1.
OverLordGoldDragon
2

Embora não possa explicar as inconsistências no tempo de execução, recomendo que você tente converter seu modelo em TensorFlow Lite para acelerar as previsões em registros de dados únicos ou em pequenos lotes.

Eu executei uma referência neste modelo:

model = tf.keras.models.Sequential([
    tf.keras.layers.Dense(384, activation='elu', input_shape=(256,)),
    tf.keras.layers.Dense(384, activation='elu'),
    tf.keras.layers.Dense(256, activation='elu'),
    tf.keras.layers.Dense(128, activation='elu'),
    tf.keras.layers.Dense(32, activation='tanh')
])

Os tempos de previsão para registros únicos foram:

  1. model.predict(input): 18ms
  2. model(input): 1.3ms
  3. Modelo convertido em TensorFlow Lite: 43us

O tempo para converter o modelo foi de 2 segundos.

A classe abaixo mostra como converter e usar o modelo e fornece um predictmétodo como o modelo Keras. Observe que precisaria ser modificado para uso com modelos que não possuem apenas uma única entrada 1-D e uma única saída 1-D.

class LiteModel:

    @classmethod
    def from_file(cls, model_path):
        return LiteModel(tf.lite.Interpreter(model_path=model_path))

    @classmethod
    def from_keras_model(cls, kmodel):
        converter = tf.lite.TFLiteConverter.from_keras_model(kmodel)
        tflite_model = converter.convert()
        return LiteModel(tf.lite.Interpreter(model_content=tflite_model))

    def __init__(self, interpreter):
        self.interpreter = interpreter
        self.interpreter.allocate_tensors()
        input_det = self.interpreter.get_input_details()[0]
        output_det = self.interpreter.get_output_details()[0]
        self.input_index = input_det["index"]
        self.output_index = output_det["index"]
        self.input_shape = input_det["shape"]
        self.output_shape = output_det["shape"]
        self.input_dtype = input_det["dtype"]
        self.output_dtype = output_det["dtype"]

    def predict(self, inp):
        inp = inp.astype(self.input_dtype)
        count = inp.shape[0]
        out = np.zeros((count, self.output_shape[1]), dtype=self.output_dtype)
        for i in range(count):
            self.interpreter.set_tensor(self.input_index, inp[i:i+1])
            self.interpreter.invoke()
            out[i] = self.interpreter.get_tensor(self.output_index)[0]
        return out

    def predict_single(self, inp):
        """ Like predict(), but only for a single record. The input data can be a Python list. """
        inp = np.array([inp], dtype=self.input_dtype)
        self.interpreter.set_tensor(self.input_index, inp)
        self.interpreter.invoke()
        out = self.interpreter.get_tensor(self.output_index)
        return out[0]

O código de referência completo e uma plotagem podem ser encontrados aqui: https://medium.com/@micwurm/using-tensorflow-lite-to-speed-up-predictions-a3954886eb98

Michael
fonte
Legal, nunca tentei antes, mas talvez valesse a pena tentar. Obrigado pela dica!
ga97dil 11/03