ATUALIZAÇÃO - 15/1/2020 : a melhor prática atual para lotes pequenos deve ser a entrada direta do modelo - isto é preds = model(x)
, e se as camadas se comportarem de maneira diferente no trem / inferência model(x, training=False)
,. Pela confirmação mais recente, isso agora está documentado .
Eu não os comparei, mas de acordo com a discussão do Git , também vale a pena tentar predict_on_batch()
- especialmente com as melhorias no TF 2.1.
ULTIMATE CULPADO : self._experimental_run_tf_function = True
. É experimental . Mas não é realmente ruim.
Para qualquer desenvolvedor de TensorFlow que esteja lendo: limpe seu código . É uma bagunça. E viola importantes práticas de codificação, como uma função faz uma coisa ; _process_inputs
faz muito mais do que "entradas de processo", o mesmo para _standardize_user_data
. "Eu não sou pago o suficiente" - mas você fazer pagamento, no tempo extra gasto compreender o seu próprio material, e em usuários de encher sua página Problemas com erros resolvidos mais fácil com um código mais claro.
RESUMO : é apenas um pouco mais lento com compile()
.
compile()
define um sinalizador interno ao qual atribui uma função de previsão diferente predict
. Essa função constrói um novo gráfico a cada chamada, diminuindo a velocidade em relação a não compilado. No entanto, a diferença é pronunciada apenas quando o tempo de trem é muito menor que o tempo de processamento de dados . Se aumentarmos o tamanho do modelo para pelo menos o tamanho médio, os dois se tornarão iguais. Veja o código na parte inferior.
Esse ligeiro aumento no tempo de processamento de dados é mais do que compensado pela capacidade de gráficos amplificados. Como é mais eficiente manter apenas um gráfico de modelo, o pré-compilado é descartado. No entanto : se o seu modelo for pequeno em relação aos dados, você estará melhor sem compile()
a inferência do modelo. Veja minha outra resposta para uma solução alternativa.
O QUE DEVO FAZER?
Compare o desempenho do modelo compilado versus descompilado como eu tenho no código na parte inferior.
- Compilado é mais rápido : execute
predict
em um modelo compilado.
- Compilado é mais lento : execute
predict
em um modelo não compilado.
Sim, ambos são possíveis e dependerão do (1) tamanho dos dados; (2) tamanho do modelo; (3) hardware. O código na parte inferior mostra o modelo compilado sendo mais rápido, mas 10 iterações são uma amostra pequena. Veja "soluções alternativas" na minha outra resposta para o "como fazer".
DETALHES :
Demorou um pouco para depurar, mas foi divertido. Abaixo, descrevo os principais culpados que descobri, cito alguma documentação relevante e mostro os resultados do criador de perfil que levaram ao gargalo final.
( FLAG == self.experimental_run_tf_function
, por questões de concisão)
Model
por padrão instancia com FLAG=False
. compile()
define para True
.
predict()
envolve a aquisição da função de previsão, func = self._select_training_loop(x)
- Sem nenhum kwargs especial passado para
predict
e compile
, todos os outros sinalizadores são tais que:
- (A)
FLAG==True
->func = training_v2.Loop()
- (B)
FLAG==False
->func = training_arrays.ArrayLikeTrainingLoop()
- A partir da documentação do código-fonte , (A) é fortemente dependente de gráficos, usa mais estratégia de distribuição e as operações são propensas a criar e destruir elementos gráficos, o que "pode" afetar o desempenho.
Verdadeiro culpado : _process_inputs()
, respondendo por 81% do tempo de execução . Seu principal componente? _create_graph_function()
, 72% do tempo de execução . Este método nem existe para (B) . Usar um modelo de tamanho médio, no entanto, _process_inputs
compreende menos de 1% do tempo de execução . Código na parte inferior e resultados de criação de perfil a seguir.
PROCESSADORES DE DADOS :
(A) :, <class 'tensorflow.python.keras.engine.data_adapter.TensorLikeDataAdapter'>
usado em _process_inputs()
. Código fonte relevante
(B) : numpy.ndarray
, devolvido por convert_eager_tensors_to_numpy
. Código fonte relevante e aqui
FUNÇÃO DE EXECUÇÃO DE MODELO (por exemplo, prever)
(A) : função de distribuição , e aqui
(B) : função de distribuição (diferente) , e aqui
PROFILER : resultados para o código na minha outra resposta, "modelo pequeno", e nesta resposta, "modelo médio":
Modelo minúsculo : 1000 iterações,compile()
Modelo minúsculo : 1000 iterações, não compile()
Modelo médio : 10 iterações
DOCUMENTAÇÃO (indiretamente) sobre os efeitos de compile()
: fonte
Diferentemente de outras operações do TensorFlow, não convertemos entradas numéricas python em tensores. Além disso, um novo gráfico é gerado para cada valor numérico python distinto , por exemplo, chamando g(2)
e g(3)
gerará dois novos gráficos
function
instancia um gráfico separado para cada conjunto exclusivo de formas e tipos de dados de entrada . Por exemplo, o seguinte trecho de código resultará em três gráficos distintos sendo rastreados, pois cada entrada tem uma forma diferente
Um único objeto tf.function pode precisar mapear para vários gráficos de computação sob o capô. Isso deve ser visível apenas como desempenho (os gráficos de rastreamento têm um custo computacional e de memória diferente de zero ), mas não devem afetar a correção do programa
COUNTEREXAMPLE :
from tensorflow.keras.layers import Input, Dense, LSTM, Bidirectional, Conv1D
from tensorflow.keras.layers import Flatten, Dropout
from tensorflow.keras.models import Model
import numpy as np
from time import time
def timeit(func, arg, iterations):
t0 = time()
for _ in range(iterations):
func(arg)
print("%.4f sec" % (time() - t0))
batch_size = 32
batch_shape = (batch_size, 400, 16)
ipt = Input(batch_shape=batch_shape)
x = Bidirectional(LSTM(512, activation='relu', return_sequences=True))(ipt)
x = LSTM(512, activation='relu', return_sequences=True)(ipt)
x = Conv1D(128, 400, 1, padding='same')(x)
x = Flatten()(x)
x = Dense(256, activation='relu')(x)
x = Dropout(0.5)(x)
x = Dense(128, activation='relu')(x)
x = Dense(64, activation='relu')(x)
out = Dense(1, activation='sigmoid')(x)
model = Model(ipt, out)
X = np.random.randn(*batch_shape)
timeit(model.predict, X, 10)
model.compile('adam', loss='binary_crossentropy')
timeit(model.predict, X, 10)
Saídas :
34.8542 sec
34.7435 sec