Why is my neural network having trouble predicting the next value of a sin wave?

Alio: <p>Why is my neural network not able to predict the next number for a sin wave?</p>
<p>I don’t know if I need a better loss function or what the issue is. It seems to optimize for about 500 steps and then it just flounders with predictions that look nothing like a wave.</p>
<p>The input to the model is 120. That’s 120 of the previous numbers of the sin wave. The model is asked to predict where the next number will be and I store the sin wave values in a deque. I insert the next sin wave value to the end of its deque.</p>
<p>The targets and the predictions are arrays in a shape of (200,).<br>
Every index in the array below 100 represents a negative number and those above 100 represent a positive number.<br>
All values to the hundredth between <code>-1</code> and <code>0</code> are represented by indexes 0 to 100.<br>
Above 100, every hundredth between <code>0</code> to <code>1</code> are represented.<br>
In other words, 200 possible values in array form to show the neural network what to target and what the prediction is.</p>
<p>The code (which I had broken up in a Jupyter Notebook)…</p>
<pre><code>%matplotlib inline

import torch
import random
import numpy as np
from collections import deque
from matplotlib import pyplot as plt
from matplotlib.pyplot import figure
from torch import nn
import time
import math

plt.rcParams["figure.figsize"]=(12, 8)  

class Network(nn.Module):
    def __init__(self):
        super().__init__()
        
        self.net = nn.Sequential(
            nn.Linear(120, 136),
            nn.ReLU(),
            nn.Dropout(0.05),
            nn.Linear(136, 146),
            nn.ReLU(),
            nn.Dropout(0.05),
            nn.Linear(146, 156),
            nn.ReLU(),
            nn.Dropout(0.05),
            nn.Linear(156, 170),
            nn.ReLU(),
            nn.Linear(170, 188),
            nn.Dropout(0.05),
            nn.ReLU(),
            nn.Linear(188, 200))  

    def forward(self, x):
        return self.net(x)  

online_net = Network().cuda("cuda:2")

optimizer = torch.optim.Adam(online_net.parameters(), lr=1e-5)
device = torch.device("cuda:2" if torch.cuda.is_available() else "cpu")  

sensor_buffer = deque(maxlen=120)
action_buffer = deque(maxlen=120)

[sensor_buffer.append(np.array([random.random() for _ in range(4)]).mean()) for __ in range(121)]
[action_buffer.append(0) for _ in range(121)]  

def format_target(n):
    n = (n * 100)
    l = [-1 for o in range(200)]
    l[int(n)] = 1
    return l  

for steps in range(1400):
    
    adder += (random.random() * 0.2)
    sensor_buffer.append(adder)
    sensor_sinwave = np.sin(sensor_buffer[-1])

    outers = torch.tensor(np.sin(sensor_buffer), dtype=torch.float32).cuda("cuda:2").T

    outer = online_net.forward(outers)

    prediction = torch.argmax(outer).item()

    n_prediction = (prediction * 0.01) -1

    action_buffer.append(n_prediction)

    target = format_target(sensor_sinwave)
    target_t = torch.as_tensor(target, dtype=torch.float32, device=device)

    loss = nn.functional.smooth_l1_loss(outer, target_t)


    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if steps % 150 == 0:
        print(loss.item(), n_prediction, sensor_sinwave)
</code></pre>
<p>The sin wave I’m trying to predict<br>
<a href="https://i.stack.imgur.com/3pLQ7.png" rel="noopener nofollow ugc"><img src="https://i.stack.imgur.com/3pLQ7.png" alt="enter image description here" width="690" height="455"></a></p>
<p>My predictions<br>
<a href="https://i.stack.imgur.com/Qmhdj.png" rel="noopener nofollow ugc"><img src="https://i.stack.imgur.com/Qmhdj.png" alt="enter image description here" width="690" height="458"></a></p>

KFrank: <p>Hi Alio!</p>
<aside class="quote no-group quote-modified" data-username="Alio" data-post="1" data-topic="147511" data-full="true">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/user_avatar/discuss.pytorch.org/alio/48/8142_2.png" class="avatar"> Alio:</div>
<blockquote>
<p>The input to the model is 120. That’s 120 of the previous numbers of the sin wave. The model is asked to predict where the next number will be<br>
…<br>
The targets and the predictions are arrays in a shape of (200,).</p>
</blockquote>
</aside>
<p>I think I understand what you are doing (but I’m not entirely sure …).</p>
<p>Without commenting specifically on your code or proposed network<br>
architecture, let me say the following:</p>
<p>First, there are clearly better ways to predict the next number in a<br>
sine wave than using a neural network, so this is, in any event, a<br>
learning exercise.</p>
<p>However, unless you are purposely trying to use an atypical approach<br>
(as a learning exercise), it would be much more natural to proceed as<br>
follows:</p>
<p>You are trying to predict a single number – the next sine value.  So<br>
have the output of your network be a single number.  You would<br>
typically do this by having the final <code>Linear</code> layer in your model have<br>
<code>out_features = 1</code>.  Similarly, your <code>target</code> should also be a single<br>
number – the actual sine value you are trying to predict.  <code>MSELoss</code><br>
would likely be the most natural loss function to use.</p>
<p>What you are doing instead is, in essence, one-hot encoding which<br>
of 200 values ranging from -1 to 1 is closest to your actual sine value.<br>
(Note, the issue here isn’t really the discreteness – it’s the one-hot<br>
encoding.)  You then have your network predict this one-hot-encoded<br>
vector by outputting a predicted vector of length 200.</p>
<p>This has two conceptual problems:  First, you are burdening your network<br>
with predicting 200 values when, in point of fact, your problem is to<br>
predict just a single value.</p>
<p>Worse – in my mind – is that you penalize your network equally when<br>
it is almost right as when it is very wrong.  Here’s what I mean by this:</p>
<p>Let’s say the correct target value is <code>0.79</code>.  If you predict a single value<br>
of <code>0.78</code> (and use <code>MSELoss</code>), you’ll get a very low loss, which is good<br>
because you were almost right.  On the other hand, if you predict <code>-0.84</code>,<br>
you’ll get a significantly higher loss, which is also good, because your<br>
prediction was quite wrong.  Sure, you’d rather predict exactly <code>0.79</code>,<br>
and you will get a lower loss (if fact <code>0.0</code> for the loss) if you do, but<br>
predicting <code>0.78</code> or <code>0.80</code> is still very good, and your network will train<br>
much more efficiently if you provide it with this information by penalizing<br>
such almost-right predictions with only a small loss.</p>
<p>Let me now artificially simplify your scheme to make the main point:  Let’s<br>
say that your predictions (the length-200 vectors) always consist of 199<br>
<code>-1.0</code> values and one <code>+1.0</code> value.  The value <code>0.79</code> gets encoded in your<br>
target as having element 79 be set to <code>+1.0</code>.  So the correct prediction is<br>
the same.  But let’s say (in the context of my artificial simplification) that<br>
your network outputs a prediction with element 78 set to <code>+1.0</code> (and all of<br>
the others equal to <code>-1.0</code>).  This corresponds to predicting a value of <code>0.78</code>,<br>
which is almost right.  But, in your scheme, such a prediction is penalized<br>
<em>just as much</em> as a quite-wrong prediction of <code>-0.84</code>.</p>
<p>Now I’ve exaggerated the situation by assuming that your predictions<br>
have one <code>+1.0</code> value with all of the others equal to <code>-1.0</code>, but, still, this<br>
effect of the prediction having to be exactly correct to be considered<br>
“good” presents a much more difficult learning task for your network.</p>
<p>As an aside, if you do want to pursue your approach of using targets<br>
that are one-hot-encoded, you could think about your prediction task<br>
as a <em>classification</em> problem – each of your 200 discretized values for<br>
the next sine value is a class, and you want to predict that class.  If<br>
you choose to do this, you will find that using <code>CrossEntropyLoss</code> will<br>
lead to better training than <code>smooth_l1_loss()</code> (or the conceptually<br>
similar <code>MSELoss</code>).  The logarithmic divergence that <code>CrossEntropyLoss</code><br>
provides for bad predictions does seem to fit well with classification<br>
problems and leads to better training.</p>
<p>Good luck.</p>
<p>K. Frank</p>

tom: <p>You could also look for inspiration in the venerable time sequence prediction example:</p>
<aside class="onebox githubfolder" data-onebox-src="https://github.com/pytorch/examples/tree/main/time_sequence_prediction">
  <header class="source">
      <img src="https://github.githubassets.com/favicons/favicon.svg" class="site-icon" width="32" height="32">

      <a href="https://github.com/pytorch/examples/tree/main/time_sequence_prediction" target="_blank" rel="noopener nofollow ugc">github.com</a>
  </header>

  <article class="onebox-body">
    <h3><a href="https://github.com/pytorch/examples/tree/main/time_sequence_prediction" target="_blank" rel="noopener nofollow ugc">examples/time_sequence_prediction at main · pytorch/examples</a></h3>

  <p><a href="https://github.com/pytorch/examples/tree/main/time_sequence_prediction" target="_blank" rel="noopener nofollow ugc">main/time_sequence_prediction</a></p>

  <p><span class="label1">A set of examples around pytorch in Vision, Text, Reinforcement Learning, etc.</span></p>

  </article>

  <div class="onebox-metadata">
    
    
  </div>

  <div style="clear: both"></div>
</aside>

<p>Also, there are some discussions about adapting this on the forum.</p>
<p>Best regards</p>
<p>Thomas</p>