.. _sec_sequence:
Modelos Sequenciais
===================
Imagine que você está assistindo a filmes na Netflix. Como um bom
usuário do Netflix, você decide avaliar cada um dos filmes
religiosamente. Afinal, um bom filme é um bom filme, e você quer
assistir mais deles, certo? Acontece que as coisas não são tão simples.
As opiniões das pessoas sobre os filmes podem mudar significativamente
com o tempo. Na verdade, os psicólogos até têm nomes para alguns dos
efeitos:
- Há *ancoragem*, com base na opinião de outra pessoa. Por exemplo,
após a premiação do Oscar, as avaliações do filme correspondente
aumentam, embora ainda seja o mesmo filme. Este efeito persiste por
alguns meses até que o prêmio seja esquecido. Foi demonstrado que o
efeito eleva a classificação em mais de meio ponto
:cite:`Wu.Ahmed.Beutel.ea.2017`.
- Existe a *adaptação hedônica*, onde os humanos se adaptam rapidamente
para aceitar uma situação melhorada ou piorada como o novo normal.
Por exemplo, depois de assistir a muitos filmes bons, as expectativas
de que o próximo filme seja igualmente bom ou melhor são altas.
Consequentemente, mesmo um filme comum pode ser considerado ruim
depois que muitos filmes incríveis são assistidos.
- Existe *sazonalidade*. Muito poucos espectadores gostam de assistir a
um filme do Papai Noel em agosto.
- Em alguns casos, os filmes se tornam impopulares devido ao mau
comportamento de diretores ou atores na produção.
- Alguns filmes se tornam filmes de culto, porque eram quase
comicamente ruins. *Plan 9 from Outer Space* e *Troll 2* alcançaram
um alto grau de notoriedade por este motivo.
Resumindo, as avaliações dos filmes são tudo menos estacionárias. Assim,
usando a dinâmica temporal levou a recomendações de filmes mais precisas
:cite:`Koren.2009`. É claro que os dados da sequência não se referem
apenas às classificações dos filmes. O seguinte fornece mais
ilustrações.
- Muitos usuários têm um comportamento altamente específico quando se
trata do momento em que abrem aplicativos. Por exemplo, os
aplicativos de mídia social são muito mais populares entre os alunos
depois da escola. Os aplicativos de negociação do mercado de ações
são mais comumente usados quando os mercados estão abertos.
- É muito mais difícil prever os preços das ações de amanhã do que
preencher as lacunas de um preço das ações que perdemos ontem, embora
ambos sejam apenas uma questão de estimar um número. Afinal, a
previsão é muito mais difícil do que a retrospectiva. Em estatística,
a primeira (previsão além das observações conhecidas) é chamada de
*extrapolação*, enquanto a última (estimativa entre as observações
existentes) é chamada de *interpolação*.
- Música, fala, texto e vídeos são todos sequenciais por natureza. Se
fôssemos permutá-los, eles fariam pouco sentido. O título *cachorro
morde homem* é muito menos surpreendente do que *homem morde
cachorro*, embora as palavras sejam idênticas.
- Terremotos estão fortemente correlacionados, ou seja, após um grande
terremoto, é muito provável que ocorram vários tremores menores,
muito mais do que sem o forte terremoto. Na verdade, os terremotos
são correlacionados espaço-temporalmente, ou seja, os tremores
secundários ocorrem normalmente em um curto espaço de tempo e nas
proximidades.
- Os humanos interagem uns com os outros em uma natureza sequencial,
como pode ser visto nas lutas do Twitter, padrões de dança e debates.
Ferramentas Estatísticas
------------------------
Precisamos de ferramentas estatísticas e novas arquiteturas de rede
neural profunda para lidar com dados de sequência. Para manter as coisas
simples, usamos o preço das ações (índice FTSE 100) ilustrado em
:numref:`fig_ftse100 como exemplo.
.. _fig_ftse100:
.. figure:: ../img/ftse100.png
:width: 400px
Índice FTSE 100 ao longo de cerca de 30 anos.
Vamos denotar os preços por :math:`x_t`, ou seja, no *passo de
tempo*\ :math:`t \in \mathbb{Z}^+` observamos o preço :math:`x_t`.
Observe que para sequências neste texto, :math:`t` normalmente será
discreto e variará em números inteiros ou em seu subconjunto. Suponha
que um trader que deseja ter um bom desempenho no mercado de ações no
dia :math:`t` prevê :math:`x_t` via
.. math:: x_t \sim P(x_t \mid x_{t-1}, \ldots, x_1).
Modelos Autorregressivos
~~~~~~~~~~~~~~~~~~~~~~~~
Para conseguir isso, nosso trader pode usar um modelo de regressão como
o que treinamosem :numref:`sec_linear_concise`. Existe apenas um
grande problema: o número de entradas, :math:`x_{t-1}, \ldots, x_1`
varia, dependendo de :math:`t`. Ou seja, o número aumenta com a
quantidade de dados que encontramos e precisaremos de uma aproximação
para tornar isso computacionalmente tratável. Muito do que se segue
neste capítulo girará em torno de como estimar
:math:`P(x_t \mid x_{t-1}, \ldots, x_1)` de forma eficiente. Em poucas
palavras, ele se resume a duas estratégias como segue.
Primeiro, assuma que a sequência potencialmente bastante longa $
:math:`x_{t-1}, \ldots, x_1` não é realmente necessária. Neste caso,
podemos nos contentar com algum intervalo de tempo de comprimento
:math:`\tau` e usar apenas observações
:math:`x_{t-1}, \ldots, x_{t-\tau}`. O benefício imediato é que agora o
número de argumentos é sempre o mesmo, pelo menos para :math:`t > \tau`.
Isso nos permite treinar uma rede profunda, conforme indicado acima.
Esses modelos serão chamados de *modelos autorregressivos*, pois eles
literalmente realizam regressão em si mesmos.
A segunda estratégia, mostrada em :numref:`fig_sequence-model`, é
manter algum resumo :math:`h_t` das observações anteriores, e ao mesmo
tempo atualizar :math:`h_t` além da previsão :math:`\hat{x}_t`. Isso
leva a modelos que estimam :math:`x_t` com
:math:`\hat{x}_t = P(x_t \mid h_{t})` e, além disso, atualizações da
forma :math:`h_t = g(h_{t-1}, x_{t-1})`. Como :math:`h_t` nunca é
observado, esses modelos também são chamados de *modelos
autorregressivos latentes*.
.. _fig_sequence-model:
.. figure:: ../img/sequence-model.svg
Um modelo autorregressivo latente.
Ambos os casos levantam a questão óbvia de como gerar dados de
treinamento. Normalmente, usa-se observações históricas para prever a
próxima observação, dadas as até agora. Obviamente, não esperamos que o
tempo pare. No entanto, uma suposição comum é que, embora os valores
específicos de :math:`x_t` possam mudar, pelo menos a dinâmica da
própria sequência não mudará. Isso é razoável, uma vez que novas
dinâmicas são apenas isso, novas e, portanto, não previsíveis usando os
dados que temos até agora. Os estatísticos chamam a dinâmica que não
muda de *estacionária*. Independentemente do que fizermos, obteremos uma
estimativa de toda a sequência por meio de
.. math:: P(x_1, \ldots, x_T) = \prod_{t=1}^T P(x_t \mid x_{t-1}, \ldots, x_1).
Observe que as considerações acima ainda valem se lidarmos com objetos
discretos, como palavras, em vez de números contínuos. A única diferença
é que, em tal situação, precisamos usar um classificador em vez de um
modelo de regressão para estimar
:math:`P(x_t \mid x_{t-1}, \ldots, x_1)`.
Modelos de Markov
~~~~~~~~~~~~~~~~~
Lembre-se da aproximação de que em um modelo autoregressivo usamos
apenas :math:`x_{t-1}, \ldots, x_{t-\tau}` em vez de
:math:`x_{t-1}, \ldots, x_1` para estimar :math:`x_t` . Sempre que essa
aproximação for precisa, dizemos que a sequência satisfaz uma *condição
de Markov*. Em particular, se :math:`\tau = 1`, temos um *modelo de
Markov de primeira ordem* e :math:`P(x)` é dado por
.. math:: P(x_1, \ldots, x_T) = \prod_{t=1}^T P(x_t \mid x_{t-1}) \text{ onde } P(x_1 \mid x_0) = P(x_1).
Esses modelos são particularmente bons sempre que :math:`x_t` assume
apenas um valor discreto, uma vez que, neste caso, a programação
dinâmica pode ser usada para calcular valores exatamente ao longo da
cadeia. Por exemplo, podemos calcular :math:`P(x_{t+1} \mid x_{t-1})` de
forma eficiente:
.. math::
\begin{aligned}
P(x_{t+1} \mid x_{t-1})
&= \frac{\sum_{x_t} P(x_{t+1}, x_t, x_{t-1})}{P(x_{t-1})}\\
&= \frac{\sum_{x_t} P(x_{t+1} \mid x_t, x_{t-1}) P(x_t, x_{t-1})}{P(x_{t-1})}\\
&= \sum_{x_t} P(x_{t+1} \mid x_t) P(x_t \mid x_{t-1})
\end{aligned}
usando o fato de que só precisamos levar em consideração um histórico
muito curto de observações anteriores:
:math:`P(x_{t+1} \mid x_t, x_{t-1}) = P(x_{t+1} \mid x_t)`. Entraremos
em detalhes da programação dinâmica está além do escopo desta seção.
Algoritmos de aprendizagem de controle e reforço usam essas ferramentas
extensivamente.
Causalidade
~~~~~~~~~~~
Em princípio, não há nada de errado em desdobrar
:math:`P(x_1, \ldots, x_T)` na ordem inversa. Afinal, por
condicionamento, podemos sempre escrevê-la via
.. math:: P(x_1, \ldots, x_T) = \prod_{t=T}^1 P(x_t \mid x_{t+1}, \ldots, x_T).
Na verdade, se tivermos um modelo de Markov, também podemos obter uma
distribuição de probabilidade condicional reversa. Em muitos casos, no
entanto, existe uma direção natural para os dados, a saber, avançar no
tempo. É claro que eventos futuros não podem influenciar o passado.
Portanto, se mudarmos :math:`x_t`, podemos influenciar o que acontece
com :math:`x_{t+1}` daqui para frente, mas não o contrário. Ou seja, se
mudarmos :math:`x_t`, a distribuição sobre os eventos anteriores não
mudará. Consequentemente, deveria ser mais fácil explicar
:math:`P(x_{t+1} \mid x_t)` em vez de :math:`P(x_t \mid x_{t+1})`. Por
exemplo, foi mostrado que em alguns casos podemos encontrar
:math:`x_{t+1} = f(x_t) + \epsilon` para algum ruído aditivo
:math:`\epsilon`, enquanto o inverso não é verdadeiro
:cite:`Hoyer.Janzing.Mooij.ea.2009`. Esta é uma ótima notícia, pois
normalmente é a direção para a frente que estamos interessados em
estimar. O livro de Peters et al. explicou mais sobre este tópico
:cite:`Peters.Janzing.Scholkopf.2017`. Estamos apenas arranhando a
superfície disso.
Trainamento
-----------
Depois de revisar tantas ferramentas estatísticas, vamos tentar isso na
prática. Começamos gerando alguns dados. Para manter as coisas simples,
geramos nossos dados de sequência usando uma função seno com algum ruído
aditivo para etapas de tempo :math:`1, 2, \ldots, 1000`.
.. raw:: html
.. raw:: html
.. code:: python
%matplotlib inline
from mxnet import autograd, gluon, init, np, npx
from mxnet.gluon import nn
from d2l import mxnet as d2l
npx.set_np()
T = 1000 # Generate a total of 1000 points
time = np.arange(1, T + 1, dtype=np.float32)
x = np.sin(0.01 * time) + np.random.normal(0, 0.2, (T,))
d2l.plot(time, [x], 'time', 'x', xlim=[1, 1000], figsize=(6, 3))
.. figure:: output_sequence_ce248f_3_0.svg
.. raw:: html
.. raw:: html
.. code:: python
%matplotlib inline
import torch
from torch import nn
from d2l import torch as d2l
T = 1000 # Generate a total of 1000 points
time = torch.arange(1, T + 1, dtype=torch.float32)
x = torch.sin(0.01 * time) + torch.normal(0, 0.2, (T,))
d2l.plot(time, [x], 'time', 'x', xlim=[1, 1000], figsize=(6, 3))
.. figure:: output_sequence_ce248f_6_0.svg
.. raw:: html
.. raw:: html
.. code:: python
%matplotlib inline
import tensorflow as tf
from d2l import tensorflow as d2l
T = 1000 # Generate a total of 1000 points
time = tf.range(1, T + 1, dtype=tf.float32)
x = tf.sin(0.01 * time) + tf.random.normal([T], 0, 0.2)
d2l.plot(time, [x], 'time', 'x', xlim=[1, 1000], figsize=(6, 3))
.. figure:: output_sequence_ce248f_9_0.svg
.. raw:: html
.. raw:: html
Em seguida, precisamos transformar essa sequência em recursos e rótulos
nos quais nosso modelo pode treinar. Com base na dimensão de
incorporação :math:`\tau`, mapeamos os dados em pares :math:`y_t = x_t`
e :math:`\mathbf{x}_t = [x_{t-\tau}, \ldots, x_{t-1}]`. O leitor astuto
deve ter notado que isso nos dá :math:`\tau` menos exemplos de dados,
uma vez que não temos histórico suficiente para os primeiros
:math:`\tau` deles. Uma solução simples, em particular se a sequência
for longa, é descartar esses poucos termos. Como alternativa, podemos
preencher a sequência com zeros. Aqui, usamos apenas os primeiros 600
pares de rótulos de recursos para treinamento.
.. raw:: html
.. raw:: html
.. code:: python
tau = 4
features = np.zeros((T - tau, tau))
for i in range(tau):
features[:, i] = x[i: T - tau + i]
labels = x[tau:].reshape((-1, 1))
batch_size, n_train = 16, 600
# Only the first `n_train` examples are used for training
train_iter = d2l.load_array((features[:n_train], labels[:n_train]),
batch_size, is_train=True)
.. raw:: html
.. raw:: html
.. code:: python
tau = 4
features = torch.zeros((T - tau, tau))
for i in range(tau):
features[:, i] = x[i: T - tau + i]
labels = x[tau:].reshape((-1, 1))
batch_size, n_train = 16, 600
# Only the first `n_train` examples are used for training
train_iter = d2l.load_array((features[:n_train], labels[:n_train]),
batch_size, is_train=True)
.. raw:: html
.. raw:: html
.. code:: python
tau = 4
features = tf.Variable(tf.zeros((T - tau, tau)))
for i in range(tau):
features[:, i].assign(x[i: T - tau + i])
labels = tf.reshape(x[tau:], (-1, 1))
batch_size, n_train = 16, 600
# Only the first `n_train` examples are used for training
train_iter = d2l.load_array((features[:n_train], labels[:n_train]),
batch_size, is_train=True)
.. raw:: html
.. raw:: html
Aqui, mantemos a arquitetura bastante simples: apenas um MLP com duas
camadas totalmente conectadas, ativação ReLU e perda quadrática.
.. raw:: html
.. raw:: html
.. code:: python
# A simple MLP
def get_net():
net = nn.Sequential()
net.add(nn.Dense(10, activation='relu'),
nn.Dense(1))
net.initialize(init.Xavier())
return net
# Square loss
loss = gluon.loss.L2Loss()
.. raw:: html
.. raw:: html
.. code:: python
# Function for initializing the weights of the network
def init_weights(m):
if type(m) == nn.Linear:
nn.init.xavier_uniform_(m.weight)
# A simple MLP
def get_net():
net = nn.Sequential(nn.Linear(4, 10),
nn.ReLU(),
nn.Linear(10, 1))
net.apply(init_weights)
return net
# Square loss
loss = nn.MSELoss()
.. raw:: html
.. raw:: html
.. code:: python
# Vanilla MLP architecture
def get_net():
net = tf.keras.Sequential([tf.keras.layers.Dense(10, activation='relu'),
tf.keras.layers.Dense(1)])
return net
# Least mean squares loss
# Note: L2 Loss = 1/2 * MSE Loss. TensorFlow has MSE Loss that is slightly
# different from MXNet's L2Loss by a factor of 2. Hence we halve the loss
# value to get L2Loss in TF
loss = tf.keras.losses.MeanSquaredError()
.. raw:: html
.. raw:: html
Agora estamos prontos para treinar o modelo. O código abaixo é
essencialmente idêntico ao loop de treinamento nas seções anteriores,
como :numref:`sec_linear_concise`. Portanto, não iremos nos aprofundar
em muitos detalhes.
.. raw:: html
.. raw:: html
.. code:: python
def train(net, train_iter, loss, epochs, lr):
trainer = gluon.Trainer(net.collect_params(), 'adam',
{'learning_rate': lr})
for epoch in range(epochs):
for X, y in train_iter:
with autograd.record():
l = loss(net(X), y)
l.backward()
trainer.step(batch_size)
print(f'epoch {epoch + 1}, '
f'loss: {d2l.evaluate_loss(net, train_iter, loss):f}')
net = get_net()
train(net, train_iter, loss, 5, 0.01)
.. parsed-literal::
:class: output
[03:54:56] src/base.cc:49: GPU context requested, but no GPUs found.
epoch 1, loss: 0.040162
epoch 2, loss: 0.032239
epoch 3, loss: 0.028291
epoch 4, loss: 0.027804
epoch 5, loss: 0.026603
.. raw:: html
.. raw:: html
.. code:: python
def train(net, train_iter, loss, epochs, lr):
trainer = torch.optim.Adam(net.parameters(), lr)
for epoch in range(epochs):
for X, y in train_iter:
trainer.zero_grad()
l = loss(net(X), y)
l.backward()
trainer.step()
print(f'epoch {epoch + 1}, '
f'loss: {d2l.evaluate_loss(net, train_iter, loss):f}')
net = get_net()
train(net, train_iter, loss, 5, 0.01)
.. parsed-literal::
:class: output
epoch 1, loss: 0.059361
epoch 2, loss: 0.055103
epoch 3, loss: 0.052086
epoch 4, loss: 0.054857
epoch 5, loss: 0.050476
.. raw:: html
.. raw:: html
.. code:: python
def train(net, train_iter, loss, epochs, lr):
trainer = tf.keras.optimizers.Adam()
for epoch in range(epochs):
for X, y in train_iter:
with tf.GradientTape() as g:
out = net(X)
l = loss(y, out) / 2
params = net.trainable_variables
grads = g.gradient(l, params)
trainer.apply_gradients(zip(grads, params))
print(f'epoch {epoch + 1}, '
f'loss: {d2l.evaluate_loss(net, train_iter, loss):f}')
net = get_net()
train(net, train_iter, loss, 5, 0.01)
.. parsed-literal::
:class: output
epoch 1, loss: 0.066722
epoch 2, loss: 0.052959
epoch 3, loss: 0.052756
epoch 4, loss: 0.052145
epoch 5, loss: 0.052316
.. raw:: html
.. raw:: html
Predição
--------
Como a perda de treinamento é pequena, esperamos que nosso modelo
funcione bem. Vamos ver o que isso significa na prática. A primeira
coisa a verificar é o quão bem o modelo é capaz de prever o que acontece
na próxima etapa, ou seja, a *previsão um passo à frente*.
.. raw:: html
.. raw:: html
.. code:: python
onestep_preds = net(features)
d2l.plot([time, time[tau:]], [x.asnumpy(), onestep_preds.asnumpy()], 'time',
'x', legend=['data', '1-step preds'], xlim=[1, 1000], figsize=(6, 3))
.. figure:: output_sequence_ce248f_51_0.svg
.. raw:: html
.. raw:: html
.. code:: python
onestep_preds = net(features)
d2l.plot([time, time[tau:]], [x.detach().numpy(), onestep_preds.detach().numpy()], 'time',
'x', legend=['data', '1-step preds'], xlim=[1, 1000], figsize=(6, 3))
.. figure:: output_sequence_ce248f_54_0.svg
.. raw:: html
.. raw:: html
.. code:: python
onestep_preds = net(features)
d2l.plot([time, time[tau:]], [x.numpy(), onestep_preds.numpy()], 'time',
'x', legend=['data', '1-step preds'], xlim=[1, 1000], figsize=(6, 3))
.. figure:: output_sequence_ce248f_57_0.svg
.. raw:: html
.. raw:: html
As previsões de um passo à frente parecem boas, exatamente como
esperávamos. Mesmo além de 604 (``n_train + tau``) observações, as
previsões ainda parecem confiáveis. No entanto, há apenas um pequeno
problema para isso: se observarmos os dados de sequência apenas até a
etapa de tempo 604, não podemos esperar receber as entradas para todas
as previsões futuras de um passo à frente. Em vez disso, precisamos
trabalhar nosso caminho adiante, um passo de cada vez:
.. math::
\hat{x}_{605} = f(x_{601}, x_{602}, x_{603}, x_{604}), \\
\hat{x}_{606} = f(x_{602}, x_{603}, x_{604}, \hat{x}_{605}), \\
\hat{x}_{607} = f(x_{603}, x_{604}, \hat{x}_{605}, \hat{x}_{606}),\\
\hat{x}_{608} = f(x_{604}, \hat{x}_{605}, \hat{x}_{606}, \hat{x}_{607}),\\
\hat{x}_{609} = f(\hat{x}_{605}, \hat{x}_{606}, \hat{x}_{607}, \hat{x}_{608}),\\
\ldots
Geralmente, para uma sequência observada até :math:`x_t`, sua saída
prevista :math:`\hat{x}_{t+k}` no passo de tempo :math:`t+k` é chamada
de :math:`k` *- previsão passo à frente*. Como observamos até
:math:`x_{604}`, sua previsão de :math:`k`-step-forward é
:math:`\hat{x}_{604+k}`. Em outras palavras, teremos que usar nossas
próprias previsões para fazer previsões em várias etapas. Vamos ver como
isso vai bem.
.. raw:: html
.. raw:: html
.. code:: python
multistep_preds = np.zeros(T)
multistep_preds[: n_train + tau] = x[: n_train + tau]
for i in range(n_train + tau, T):
multistep_preds[i] = net(
multistep_preds[i - tau:i].reshape((1, -1)))
d2l.plot([time, time[tau:], time[n_train + tau:]],
[x.asnumpy(), onestep_preds.asnumpy(),
multistep_preds[n_train + tau:].asnumpy()], 'time',
'x', legend=['data', '1-step preds', 'multistep preds'],
xlim=[1, 1000], figsize=(6, 3))
.. figure:: output_sequence_ce248f_63_0.svg
.. raw:: html
.. raw:: html
.. code:: python
multistep_preds = torch.zeros(T)
multistep_preds[: n_train + tau] = x[: n_train + tau]
for i in range(n_train + tau, T):
multistep_preds[i] = net(
multistep_preds[i - tau:i].reshape((1, -1)))
d2l.plot([time, time[tau:], time[n_train + tau:]],
[x.detach().numpy(), onestep_preds.detach().numpy(),
multistep_preds[n_train + tau:].detach().numpy()], 'time',
'x', legend=['data', '1-step preds', 'multistep preds'],
xlim=[1, 1000], figsize=(6, 3))
.. figure:: output_sequence_ce248f_66_0.svg
.. raw:: html
.. raw:: html
.. code:: python
multistep_preds = tf.Variable(tf.zeros(T))
multistep_preds[:n_train + tau].assign(x[:n_train + tau])
for i in range(n_train + tau, T):
multistep_preds[i].assign(tf.reshape(net(
tf.reshape(multistep_preds[i - tau: i], (1, -1))), ()))
d2l.plot([time, time[tau:], time[n_train + tau:]],
[x.numpy(), onestep_preds.numpy(),
multistep_preds[n_train + tau:].numpy()], 'time',
'x', legend=['data', '1-step preds', 'multistep preds'],
xlim=[1, 1000], figsize=(6, 3))
.. figure:: output_sequence_ce248f_69_0.svg
.. raw:: html
.. raw:: html
Como mostra o exemplo acima, este é um fracasso espetacular. As
previsões decaem para uma constante muito rapidamente após algumas
etapas de previsão. Por que o algoritmo funcionou tão mal? Em última
análise, isso se deve ao fato de que os erros se acumulam. Digamos que
após o passo 1 tenhamos algum erro :math:`\epsilon_1 = \bar\epsilon`.
Agora a *entrada* para a etapa 2 é perturbada por :math:`\epsilon_1`,
portanto, sofremos algum erro na ordem de
:math:`\epsilon_2 = \bar\epsilon + c \epsilon_1` para alguma constante
:math:`c`, e assim por diante. O erro pode divergir rapidamente das
observações verdadeiras. Este é um fenômeno comum. Por exemplo, as
previsões do tempo para as próximas 24 horas tendem a ser bastante
precisas, mas, além disso, a precisão diminui rapidamente. Discutiremos
métodos para melhorar isso ao longo deste capítulo e além.
Vamos dar uma olhada mais de perto nas dificuldades nas previsões de
:math:`k`-step-ahead calculando previsões em toda a sequência para
:math:`k = 1, 4, 16, 64`.
.. raw:: html
.. raw:: html
.. code:: python
max_steps = 64
features = np.zeros((T - tau - max_steps + 1, tau + max_steps))
# Column `i` (`i` < `tau`) are observations from `x` for time steps from
# `i + 1` to `i + T - tau - max_steps + 1`
for i in range(tau):
features[:, i] = x[i: i + T - tau - max_steps + 1]
# Column `i` (`i` >= `tau`) are the (`i - tau + 1`)-step-ahead predictions for
# time steps from `i + 1` to `i + T - tau - max_steps + 1`
for i in range(tau, tau + max_steps):
features[:, i] = net(features[:, i - tau:i]).reshape(-1)
steps = (1, 4, 16, 64)
d2l.plot([time[tau + i - 1: T - max_steps + i] for i in steps],
[features[:, (tau + i - 1)].asnumpy() for i in steps], 'time', 'x',
legend=[f'{i}-step preds' for i in steps], xlim=[5, 1000],
figsize=(6, 3))
.. figure:: output_sequence_ce248f_75_0.svg
.. raw:: html
.. raw:: html
.. code:: python
max_steps = 64
features = torch.zeros((T - tau - max_steps + 1, tau + max_steps))
# Column `i` (`i` < `tau`) are observations from `x` for time steps from
# `i + 1` to `i + T - tau - max_steps + 1`
for i in range(tau):
features[:, i] = x[i: i + T - tau - max_steps + 1]
# Column `i` (`i` >= `tau`) are the (`i - tau + 1`)-step-ahead predictions for
# time steps from `i + 1` to `i + T - tau - max_steps + 1`
for i in range(tau, tau + max_steps):
features[:, i] = net(features[:, i - tau:i]).reshape(-1)
steps = (1, 4, 16, 64)
d2l.plot([time[tau + i - 1: T - max_steps + i] for i in steps],
[features[:, (tau + i - 1)].detach().numpy() for i in steps], 'time', 'x',
legend=[f'{i}-step preds' for i in steps], xlim=[5, 1000],
figsize=(6, 3))
.. figure:: output_sequence_ce248f_78_0.svg
.. raw:: html
.. raw:: html
.. code:: python
max_steps = 64
features = tf.Variable(tf.zeros((T - tau - max_steps + 1, tau + max_steps)))
# Column `i` (`i` < `tau`) are observations from `x` for time steps from
# `i + 1` to `i + T - tau - max_steps + 1`
for i in range(tau):
features[:, i].assign(x[i: i + T - tau - max_steps + 1].numpy())
# Column `i` (`i` >= `tau`) are the (`i - tau + 1`)-step-ahead predictions for
# time steps from `i + 1` to `i + T - tau - max_steps + 1`
for i in range(tau, tau + max_steps):
features[:, i].assign(tf.reshape(net((features[:, i - tau: i])), -1))
steps = (1, 4, 16, 64)
d2l.plot([time[tau + i - 1: T - max_steps + i] for i in steps],
[features[:, (tau + i - 1)].numpy() for i in steps], 'time', 'x',
legend=[f'{i}-step preds' for i in steps], xlim=[5, 1000],
figsize=(6, 3))
.. figure:: output_sequence_ce248f_81_0.svg
.. raw:: html
.. raw:: html
This clearly illustrates how the quality of the prediction changes as we
try to predict further into the future. While the 4-step-ahead
predictions still look good, anything beyond that is almost useless.
Resumo
------
- Existe uma grande diferença de dificuldade entre interpolação e
extrapolação. Consequentemente, se você tiver uma sequência, sempre
respeite a ordem temporal dos dados ao treinar, ou seja, nunca treine
com dados futuros.
- Os modelos de sequência requerem ferramentas estatísticas
especializadas para estimativa. Duas escolhas populares são os
modelos autorregressivos e os modelos autorregressivos de variáveis
latentes.
- Para modelos causais (por exemplo, o tempo indo para frente), estimar
a direção para frente é normalmente muito mais fácil do que a direção
reversa.
- Para uma sequência observada até o passo de tempo :math:`t`, sua
saída prevista no passo de tempo :math:`t+k` is the
:math:`k`\ *-previsão de avanço*. Como prevemos mais no tempo
aumentando :math:`k`, os erros se acumulam e a qualidade da previsão
se degrada, muitas vezes de forma dramática.
Exercícios
----------
1. Melhore o modelo no experimento desta seção.
1. Incorpora mais do que as últimas 4 observações? De quantas você
realmente precisa?
2. Quantas observações anteriores você precisaria se não houvesse
ruído? Dica: você pode escrever :math:`\sin` e :math:`\cos` como
uma equação diferencial.
3. Você pode incorporar observações mais antigas enquanto mantém
constante o número total de recursos? Isso melhora a precisão? Por
quê?
4. Altere a arquitetura da rede neural e avalie o desempenho.
2. Um investidor deseja encontrar um bom título para comprar. Ele olha
para os retornos anteriores para decidir qual deles provavelmente
terá um bom desempenho. O que poderia dar errado com essa estratégia?
3. A causalidade também se aplica ao texto? Até que ponto?
4. Dê um exemplo de quando um modelo autoregressivo latente pode ser
necessário para capturar a dinâmica dos dados.
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
.. raw:: html