.. _sec_hybridize:
Compiladores e Interpretadores
==============================
Até agora, este livro se concentrou na programação imperativa, que faz
uso de instruções como ``print``,\ ``+``\ ou\ ``if`` para alterar o
estado de um programa. Considere o seguinte exemplo de um programa
imperativo simples.
.. raw:: html
.. raw:: html
.. code:: python
def add(a, b):
return a + b
def fancy_func(a, b, c, d):
e = add(a, b)
f = add(c, d)
g = add(e, f)
return g
print(fancy_func(1, 2, 3, 4))
.. parsed-literal::
:class: output
10
.. raw:: html
.. raw:: html
.. code:: python
def add(a, b):
return a + b
def fancy_func(a, b, c, d):
e = add(a, b)
f = add(c, d)
g = add(e, f)
return g
print(fancy_func(1, 2, 3, 4))
.. parsed-literal::
:class: output
10
.. raw:: html
.. raw:: html
.. code:: python
def add(a, b):
return a + b
def fancy_func(a, b, c, d):
e = add(a, b)
f = add(c, d)
g = add(e, f)
return g
print(fancy_func(1, 2, 3, 4))
.. parsed-literal::
:class: output
10
.. raw:: html
.. raw:: html
Python é uma linguagem interpretada. Ao avaliar ``fancy_func`` ele
realiza as operações que compõem o corpo da função *em sequência*. Ou
seja, ele avaliará ``e = add (a, b)`` e armazenará os resultados como a
variável ``e``, alterando assim o estado do programa. As próximas duas
instruções ``f = add (c, d)`` e ``g = add (e, f)`` serão executadas de
forma semelhante, realizando adições e armazenando os resultados como
variáveis. :numref:`fig_compute_graph` ilustra o fluxo de dados.
.. _fig_compute_graph:
.. figure:: ../img/computegraph.svg
Fluxo de dados em um programa imperativo.
Embora a programação imperativa seja conveniente, pode ser ineficiente.
Por um lado, mesmo se a função ``add`` for repetidamente chamada
em\ ``fancy_func``, Python executará as três chamadas de função
individualmente. Se elas forem executadas, digamos, em uma GPU (ou mesmo
em várias GPUs), a sobrecarga decorrente do interpretador Python pode se
tornar excessiva. Além disso, ele precisará salvar os valores das
variáveis ``e`` e\ ``f`` até que todas as instruções em ``fancy_func``
tenham sido executadas. Isso ocorre porque não sabemos se as variáveis
``e`` e\ ``f`` serão usadas por outras partes do programa após as
instruções ``e = add (a, b)`` e ``f = add (c, d)`` serem executadas.
Programação Simbólica
---------------------
Considere a alternativa de programação simbólica, em que a computação
geralmente é realizada apenas depois que o processo foi totalmente
definido. Essa estratégia é usada por vários frameworks de aprendizado
profundo, incluindo Theano, Keras e TensorFlow (os dois últimos
adquiriram extensões imperativas). Geralmente envolve as seguintes
etapas:
1. Definir as operações a serem executadas.
2. Compilar as operações em um programa executável.
3. Fornecer as entradas necessárias e chamar o programa compilado para
execução.
Isso permite uma quantidade significativa de otimização. Em primeiro
lugar, podemos pular o interpretador Python em muitos casos, removendo
assim um gargalo de desempenho que pode se tornar significativo em
várias GPUs rápidas emparelhadas com um único thread Python em uma CPU.
Em segundo lugar, um compilador pode otimizar e reescrever o código
acima em ``print ((1 + 2) + (3 + 4))`` ou mesmo ``print (10)``. Isso é
possível porque um compilador consegue ver o código completo antes de
transformá-lo em instruções de máquina. Por exemplo, ele pode liberar
memória (ou nunca alocá-la) sempre que uma variável não for mais
necessária. Ou pode transformar o código inteiramente em uma parte
equivalente. Para ter uma ideia melhor, considere a seguinte simulação
de programação imperativa (afinal, é Python) abaixo.
.. raw:: html
.. raw:: html
.. code:: python
def add_():
return '''
def add(a, b):
return a + b
'''
def fancy_func_():
return '''
def fancy_func(a, b, c, d):
e = add(a, b)
f = add(c, d)
g = add(e, f)
return g
'''
def evoke_():
return add_() + fancy_func_() + 'print(fancy_func(1, 2, 3, 4))'
prog = evoke_()
print(prog)
y = compile(prog, '', 'exec')
exec(y)
.. parsed-literal::
:class: output
def add(a, b):
return a + b
def fancy_func(a, b, c, d):
e = add(a, b)
f = add(c, d)
g = add(e, f)
return g
print(fancy_func(1, 2, 3, 4))
10
.. raw:: html
.. raw:: html
.. code:: python
def add_():
return '''
def add(a, b):
return a + b
'''
def fancy_func_():
return '''
def fancy_func(a, b, c, d):
e = add(a, b)
f = add(c, d)
g = add(e, f)
return g
'''
def evoke_():
return add_() + fancy_func_() + 'print(fancy_func(1, 2, 3, 4))'
prog = evoke_()
print(prog)
y = compile(prog, '', 'exec')
exec(y)
.. parsed-literal::
:class: output
def add(a, b):
return a + b
def fancy_func(a, b, c, d):
e = add(a, b)
f = add(c, d)
g = add(e, f)
return g
print(fancy_func(1, 2, 3, 4))
10
.. raw:: html
.. raw:: html
.. code:: python
def add_():
return '''
def add(a, b):
return a + b
'''
def fancy_func_():
return '''
def fancy_func(a, b, c, d):
e = add(a, b)
f = add(c, d)
g = add(e, f)
return g
'''
def evoke_():
return add_() + fancy_func_() + 'print(fancy_func(1, 2, 3, 4))'
prog = evoke_()
print(prog)
y = compile(prog, '', 'exec')
exec(y)
.. parsed-literal::
:class: output
def add(a, b):
return a + b
def fancy_func(a, b, c, d):
e = add(a, b)
f = add(c, d)
g = add(e, f)
return g
print(fancy_func(1, 2, 3, 4))
10
.. raw:: html
.. raw:: html
As diferenças entre a programação imperativa (interpretada) e a
programação simbólica são as seguintes:
- A programação imperativa é mais fácil. Quando a programação
imperativa é usada em Python, a maior parte do código é direta e
fácil de escrever. Também é mais fácil depurar o código de
programação imperativo. Isso ocorre porque é mais fácil obter e
imprimir todos os valores de variáveis intermediárias relevantes ou
usar as ferramentas de depuração integradas do Python.
- A programação simbólica é mais eficiente e fácil de portar. Isso
torna mais fácil otimizar o código durante a compilação, além de ter
a capacidade de portar o programa para um formato independente do
Python. Isso permite que o programa seja executado em um ambiente
não-Python, evitando, assim, quaisquer problemas de desempenho em
potencial relacionados ao interpretador Python.
Programação Híbrida
-------------------
Historicamente, a maioria das estruturas de aprendizagem profunda
escolhe entre uma abordagem imperativa ou simbólica. Por exemplo,
Theano, TensorFlow (inspirado no último), Keras e CNTK formulam modelos
simbolicamente. Por outro lado, Chainer e PyTorch adotam uma abordagem
imperativa. Um modo imperativo foi adicionado ao TensorFlow 2.0 (via
Eager) e Keras em revisões posteriores.
.. raw:: html
.. raw:: html
Ao projetar o Gluon, os desenvolvedores consideraram se seria possível
combinar os benefícios de ambos os modelos de programação. Isso levou a
um modelo híbrido que permite aos usuários desenvolver e depurar usando
programação imperativa pura, ao mesmo tempo em que têm a capacidade de
converter a maioria dos programas em programas simbólicos a serem
executados quando o desempenho e a implantação de computação em nível de
produto são necessários.
Na prática, isso significa que construímos modelos usando as classes
``HybridBlock`` ou\ ``HybridSequential`` e ``HybridConcurrent``. Por
padrão, eles são executados da mesma forma que as classes ``Block``
ou\ ``Sequential`` e ``Concurrent`` são executadas na programação
imperativa. ``HybridSequential`` é uma subclasse de\ ``HybridBlock``
(assim como ``Sequential`` é subclasse de ``Block``). Quando a função
``hybridize`` é chamada, o Gluon compila o modelo na forma usada na
programação simbólica. Isso permite otimizar os componentes de
computação intensiva sem sacrificar a maneira como um modelo é
implementado. Ilustraremos os benefícios abaixo, focalizando apenas
modelos sequenciais e blocos (a composição concorrente funciona de forma
análoga).
.. raw:: html
.. raw:: html
Como mencionado acima, PyTorch é baseado em programação imperativa e usa
gráficos de computação dinâmica. Em um esforço para alavancar a
portabilidade e eficiência da programação simbólica, os desenvolvedores
consideraram se seria possível combinar os benefícios de ambos os
modelos de programação. Isso levou a um *torchscript* que permite aos
usuários desenvolver e depurar usando programação imperativa pura, ao
mesmo tempo em que têm a capacidade de converter a maioria dos programas
em programas simbólicos para serem executados quando o desempenho e a
implantação de computação em nível de produto forem necessários.
.. raw:: html
.. raw:: html
O paradigma de programação imperativo agora é o padrão no Tensorflow 2,
uma mudança acolhedora para aqueles que são novos na linguagem. No
entanto, as mesmas técnicas de programação simbólica e gráficos
computacionais subsequentes ainda existem no TensorFlow e podem ser
acessados pelo decorador ``tf.function`` fácil de usar. Isso trouxe o
paradigma de programação imperativo para o TensorFlow, permitindo que os
usuários definissem funções mais intuitivas, depois as envolvessem e
compilassem em gráficos computacionais automaticamente usando um recurso
que a equipe do TensorFlow chama de
`autograph `__.
.. raw:: html
.. raw:: html
Híbrido-Sequencial
------------------
A maneira mais fácil de ter uma ideia de como a hibridização funciona é
considerar redes profundas com várias camadas. Convencionalmente, o
interpretador Python precisará executar o código para todas as camadas
para gerar uma instrução que pode então ser encaminhada para uma CPU ou
GPU. Para um único dispositivo de computação (rápido), isso não causa
grandes problemas. Por outro lado, se usarmos um servidor avançado de 8
GPUs, como uma instância AWS P3dn.24xlarge, o Python terá dificuldade
para manter todas as GPUs ocupadas. O interpretador Python de thread
único torna-se o gargalo aqui. Vamos ver como podemos resolver isso para
partes significativas do código, substituindo ``Sequential`` por
``HybridSequential``. Começamos definindo um MLP simples.
.. raw:: html
.. raw:: html
.. code:: python
from mxnet import np, npx
from mxnet.gluon import nn
from d2l import mxnet as d2l
npx.set_np()
# Factory for networks
def get_net():
net = nn.HybridSequential()
net.add(nn.Dense(256, activation='relu'),
nn.Dense(128, activation='relu'),
nn.Dense(2))
net.initialize()
return net
x = np.random.normal(size=(1, 512))
net = get_net()
net(x)
.. parsed-literal::
:class: output
array([[ 0.16526186, -0.14005628]])
Ao chamar a função ``hybridize``, podemos compilar e otimizar o cálculo
no MLP. O resultado do cálculo do modelo permanece inalterado.
.. raw:: html
.. raw:: html
.. code:: python
import torch
from torch import nn
from d2l import torch as d2l
# Factory for networks
def get_net():
net = nn.Sequential(nn.Linear(512, 256),
nn.ReLU(),
nn.Linear(256, 128),
nn.ReLU(),
nn.Linear(128, 2))
return net
x = torch.randn(size=(1, 512))
net = get_net()
net(x)
.. parsed-literal::
:class: output
tensor([[ 0.0200, -0.0186]], grad_fn=
)
.. raw:: html
.. raw:: html
.. code:: python
import tensorflow as tf
from tensorflow.keras.layers import Dense
from d2l import tensorflow as d2l
# Factory for networks
def get_net():
net = tf.keras.Sequential()
net.add(Dense(256, input_shape = (512,), activation = "relu"))
net.add(Dense(128, activation = "relu"))
net.add(Dense(2, activation = "linear"))
return net
x = tf.random.normal([1,512])
net = get_net()
net(x)
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Ao converter o modelo usando a função ``torch.jit.script``, podemos
compilar e otimizar a computação no MLP. O resultado do cálculo do
modelo permanece inalterado.
.. raw:: html
.. raw:: html
.. code:: python
net.hybridize()
net(x)
.. parsed-literal::
:class: output
array([[ 0.16526186, -0.14005628]])
Isso parece bom demais para ser verdade: simplesmente designe um bloco
como ``HybridSequential``, escreva o mesmo código de antes e invoque
``hybridize``. Assim que isso acontecer, a rede estará otimizada
(faremos um benchmark do desempenho abaixo). Infelizmente, isso não
funciona magicamente para todas as camadas. Dito isso, os blocos
fornecidos pelo Gluon são, por padrão, subclasses de ``HybridBlock`` e,
portanto, hibridizáveis. Uma camada não será otimizada se, em vez disso,
herdar do ``Bloco``.
.. raw:: html
.. raw:: html
.. code:: python
net = torch.jit.script(net)
net(x)
.. parsed-literal::
:class: output
tensor([[ 0.0200, -0.0186]], grad_fn=
)
Convertendo o modelo usando ``torch.jit.script`` Isso parece quase bom
demais para ser verdade: escreva o mesmo código de antes e simplesmente
converta o modelo usando\ ``torch.jit.script``. Assim que isso
acontecer, a rede estará otimizada (faremos um benchmark do desempenho
abaixo).
.. raw:: html
.. raw:: html
Anteriormente, todas as funções construídas no tensorflow eram
construídas como um gráfico computacional e, portanto, JIT compilado por
padrão. No entanto, com o lançamento do tensorflow 2.X e tensores
*eager*, este não é mais o comportamento padrão. Podemos reativar essa
funcionalidade com tf.function. tf.function é mais comumente usado como
um decorador de função, no entanto, é possível chamá-lo diretamente como
uma função Python normal, mostrada abaixo. O resultado do cálculo do
modelo permanece inalterado.
.. code:: python
net = tf.function(net)
net(x)
.. parsed-literal::
:class: output
Converter o modelo usando ``tf.function`` nos dá um poder incrível no
TensorFlow: escreva o mesmo código de antes e simplesmente converta o
modelo usando\ ``tf.function``. Quando isso acontece, a rede é
construída como um gráfico computacional na representação intermediária
MLIR do TensorFlow e é altamente otimizada no nível do compilador para
uma execução rápida (faremos o benchmark do desempenho abaixo).
Adicionar explicitamente a sinalização ``jit_compile = True`` à chamada
``tf.function()`` ativa a funcionalidade XLA (Álgebra Linear Acelerada)
no TensorFlow. O XLA pode otimizar ainda mais o código compilado JIT em
certas instâncias. A execução no modo gráfico é habilitada sem essa
definição explícita, no entanto, o XLA pode tornar certas operações de
álgebra linear grandes (na veia daquelas que vemos em aplicativos de
aprendizado profundo) muito mais rápidas, particularmente em uma
GPUenvironment.
.. raw:: html
.. raw:: html
Aceleração por Hibridização
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Para demonstrar a melhoria de desempenho obtida pela compilação,
comparamos o tempo necessário para avaliar ``net (x)`` antes e depois da
hibridização. Vamos definir uma função para medir esse tempo primeiro.
Será útil ao longo do capítulo à medida que nos propomos a medir (e
melhorar) o desempenho.
.. raw:: html
.. raw:: html
.. code:: python
#@save
class Benchmark:
def __init__(self, description='Done'):
self.description = description
def __enter__(self):
self.timer = d2l.Timer()
return self
def __exit__(self, *args):
print(f'{self.description}: {self.timer.stop():.4f} sec')
Agora podemos chamar a rede duas vezes, uma com e outra sem
hibridização.
.. code:: python
net = get_net()
with Benchmark('Without hybridization'):
for i in range(1000): net(x)
npx.waitall()
net.hybridize()
with Benchmark('With hybridization'):
for i in range(1000): net(x)
npx.waitall()
.. parsed-literal::
:class: output
Without hybridization: 0.7594 sec
With hybridization: 0.4405 sec
Como é observado nos resultados acima, depois que uma instância
HybridSequential chama a função ``hybridize``, o desempenho da
computação é melhorado por meio do uso de programação simbólica.
.. raw:: html
.. raw:: html
.. code:: python
#@save
class Benchmark:
def __init__(self, description='Done'):
self.description = description
def __enter__(self):
self.timer = d2l.Timer()
return self
def __exit__(self, *args):
print(f'{self.description}: {self.timer.stop():.4f} sec')
Agora podemos chamar a rede duas vezes, uma com e outra sem torchscript.
.. code:: python
net = get_net()
with Benchmark('Without torchscript'):
for i in range(1000): net(x)
net = torch.jit.script(net)
with Benchmark('With torchscript'):
for i in range(1000): net(x)
.. parsed-literal::
:class: output
Without torchscript: 1.9314 sec
With torchscript: 0.8800 sec
Conforme observado nos resultados acima, depois que uma instância
nn.Sequential é criada com o script da função ``torch.jit.script``, o
desempenho da computação é aprimorado com o uso de programação
simbólica.
.. raw:: html
.. raw:: html
.. code:: python
#@save
class Benchmark:
def __init__(self, description='Done'):
self.description = description
def __enter__(self):
self.timer = d2l.Timer()
return self
def __exit__(self, *args):
print(f'{self.description}: {self.timer.stop():.4f} sec')
Agora podemos invocar a rede três vezes, uma vez executada avidamente,
uma vez com execução em modo gráfico e novamente usando XLA compilado
por JIT.
.. code:: python
net = get_net()
with Benchmark('Eager Mode'):
for i in range(1000): net(x)
net = tf.function(net)
with Benchmark('Graph Mode'):
for i in range(1000): net(x)
.. parsed-literal::
:class: output
Eager Mode: 0.6319 sec
Graph Mode: 0.2663 sec
Como é observado nos resultados acima, depois que uma instância tf.keras
Sequential é scriptada usando a função ``tf.function``, o desempenho da
computação é melhorado por meio do uso de programação simbólica por meio
da execução em modo gráfico em tensorflow.
.. raw:: html
.. raw:: html
Serialização
~~~~~~~~~~~~
.. raw:: html
.. raw:: html
Um dos benefícios de compilar os modelos é que podemos serializar
(salvar) o modelo e seus parâmetros no disco. Isso nos permite armazenar
um modelo de maneira independente da linguagem de front-end de escolha.
Isso nos permite implantar modelos treinados em outros dispositivos e
usar facilmente outras linguagens de programação front-end. Ao mesmo
tempo, o código geralmente é mais rápido do que o que pode ser alcançado
na programação imperativa. Vamos ver o método ``export`` em ação.
.. code:: python
net.export('my_mlp')
!ls -lh my_mlp*
.. parsed-literal::
:class: output
-rw-r--r-- 1 jenkins jenkins 643K Dec 11 03:39 my_mlp-0000.params
-rw-r--r-- 1 jenkins jenkins 3.0K Dec 11 03:39 my_mlp-symbol.json
O modelo é decomposto em um arquivo de parâmetro (binário grande) e uma
descrição JSON do programa necessário para executar o cálculo do modelo.
Os arquivos podem ser lidos por outras linguagens de front-end
suportadas por Python ou MXNet, como C ++, R, Scala e Perl. Vamos dar
uma olhada na descrição do modelo.
.. code:: python
!head my_mlp-symbol.json
.. parsed-literal::
:class: output
{
"nodes": [
{
"op": "null",
"name": "data",
"inputs": []
},
{
"op": "null",
"name": "dense3_weight",
As coisas são um pouco mais complicadas quando se trata de modelos que
se assemelham mais ao código. Basicamente, a hibridização precisa lidar
com o fluxo de controle e a sobrecarga do Python de uma maneira muito
mais imediata. Além disso,
Ao contrário da instância Block, que precisa usar a função ``forward``,
para uma instância HybridBlock precisamos usar a
função\ ``hybrid_forward``.
Anteriormente, demonstramos que, após chamar a função ``hybridize`` , o
modelo é capaz de atingir desempenho de computação superior e
portabilidade. Observe, porém, que a hibridização pode afetar a
flexibilidade do modelo, em particular em termos de fluxo de controle.
Ilustraremos como projetar modelos mais gerais e também como a
compilação removerá elementos Python espúrios.
.. code:: python
class HybridNet(nn.HybridBlock):
def __init__(self, **kwargs):
super(HybridNet, self).__init__(**kwargs)
self.hidden = nn.Dense(4)
self.output = nn.Dense(2)
def hybrid_forward(self, F, x):
print('module F: ', F)
print('value x: ', x)
x = F.npx.relu(self.hidden(x))
print('result : ', x)
return self.output(x)
O código acima implementa uma rede simples com 4 unidades ocultas e 2
saídas. ``hybrid_forward`` recebe um argumento adicional - o
módulo\ ``F``. Isso é necessário porque, dependendo se o código foi
hibridizado ou não, ele usará uma biblioteca ligeiramente diferente
(``ndarray`` ou\ ``símbolo``) para processamento. Ambas as classes
executam funções muito semelhantes e o MXNet determina automaticamente o
argumento. Para entender o que está acontecendo, imprimimos os
argumentos como parte da invocação da função.
.. code:: python
net = HybridNet()
net.initialize()
x = np.random.normal(size=(1, 3))
net(x)
.. parsed-literal::
:class: output
module F:
value x: [[-0.6338663 0.40156594 0.46456942]]
result : [[0.01641375 0. 0. 0. ]]
.. parsed-literal::
:class: output
array([[0.00097611, 0.00019453]])
A repetição do cálculo progressivo levará à mesma saída (omitimos os
detalhes). Agora vamos ver o que acontece se invocarmos o método
``hybridize``.
.. code:: python
net.hybridize()
net(x)
.. parsed-literal::
:class: output
module F:
value x: <_Symbol data>
result : <_Symbol hybridnet0_relu0>
.. parsed-literal::
:class: output
array([[0.00097611, 0.00019453]])
Em vez de usar ``ndarray`` agora usamos o módulo\ ``symbol`` para ``F``.
Além disso, embora a entrada seja do tipo ``ndarray``, os dados que
fluem pela rede agora são convertidos para o tipo ``symbol`` como parte
do processo de compilação. Repetir a chamada de função leva a um
resultado surpreendente:
.. code:: python
net(x)
.. parsed-literal::
:class: output
array([[0.00097611, 0.00019453]])
Resumo
------
- A programação imperativa torna mais fácil projetar novos modelos,
pois é possível escrever código com fluxo de controle e a capacidade
de usar uma grande parte do ecossistema de *software* Python.
- A programação simbólica requer que especifiquemos o programa e o
compilemos antes de executá-lo. O benefício é um desempenho
aprimorado.
- MXNet é capaz de combinar as vantagens de ambas as abordagens
conforme necessário.
- Modelos construídos pelas classes ``HybridSequential``
e\ ``HybridBlock`` são capazes de converter programas imperativos em
programas simbólicos chamando o método ``hibridizar``.
Exercícios
----------
1. Projete uma rede usando a classe ``HybridConcurrent``. Como
alternativa, olhe em :ref:`sec_googlenet` para uma rede para
compor.
2. Adicione ``x.asnumpy ()`` à primeira linha da função
``hybrid_forward`` da classe HybridNet nesta seção. Execute o código
e observe os erros que encontrar. Por que eles acontecem?
3. O que acontece se adicionarmos o fluxo de controle, ou seja, as
instruções Python ``if`` e\ ``for`` na função ``hybrid_forward``?
4. Revise os modelos de seu interesse nos capítulos anteriores e use a
classe HybridBlock ou HybridSequential para implementá-los.
`Discussions `__
.. raw:: html
.. raw:: html
Um dos benefícios de compilar os modelos é que podemos serializar
(salvar) o modelo e seus parâmetros no disco. Isso nos permite armazenar
um modelo de maneira independente da linguagem de front-end de escolha.
Isso nos permite implantar modelos treinados em outros dispositivos e
usar facilmente outras linguagens de programação front-end. Ao mesmo
tempo, o código geralmente é mais rápido do que o que pode ser alcançado
na programação imperativa. Vamos ver o método ``save`` em ação.
.. code:: python
net.save('my_mlp')
!ls -lh my_mlp*
.. parsed-literal::
:class: output
-rw-r--r-- 1 jenkins jenkins 651K Dec 11 06:53 my_mlp
.. raw:: html
.. raw:: html
Um dos benefícios de compilar os modelos é que podemos serializar
(salvar) o modelo e seus parâmetros no disco. Isso nos permite armazenar
um modelo de maneira independente da linguagem de front-end de escolha.
Isso nos permite implantar modelos treinados em outros dispositivos e
usar facilmente outras linguagens de programação front-end ou executar
um modelo treinado em um servidor. Ao mesmo tempo, o código geralmente é
mais rápido do que o que pode ser alcançado na programação imperativa. A
API de baixo nível que nos permite salvar em tensorflow é
``tf.saved_model``. Vamos ver a instância ``saved_model`` em ação.
.. code:: python
net = get_net()
tf.saved_model.save(net, 'my_mlp')
!ls -lh my_mlp*
.. parsed-literal::
:class: output
WARNING:tensorflow:From /var/lib/jenkins/miniconda3/envs/d2l-pt-release-0/lib/python3.8/site-packages/tensorflow/python/training/tracking/tracking.py:111: Model.state_updates (from tensorflow.python.keras.engine.training) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.
WARNING:tensorflow:From /var/lib/jenkins/miniconda3/envs/d2l-pt-release-0/lib/python3.8/site-packages/tensorflow/python/training/tracking/tracking.py:111: Layer.updates (from tensorflow.python.keras.engine.base_layer) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.
INFO:tensorflow:Assets written to: my_mlp/assets
total 68K
drwxr-xr-x 2 jenkins jenkins 4.0K Dec 11 08:06 assets
-rw-r--r-- 1 jenkins jenkins 60K Dec 11 08:06 saved_model.pb
drwxr-xr-x 2 jenkins jenkins 4.0K Dec 11 08:06 variables
.. raw:: html
.. raw:: html
.. raw:: html