I am making a model to learn the Sine function in domain x = [0, 2 * pi]. Very simple, and I have done it using C++ frameworks as a test to see if I know how to use the framework.
My model always returns a straight line at y = 0 after training, and a straight line with y between -1 and 1 before training. The Model’s structure is:
Linear(1, 20) -> Sigmoid -> Linear(20, 50) -> Sigmoid -> Linear(50, 50) -> Sigmoid -> Linear(50, 1).
This structure has worked for me in different frameworks, so I know using Sigmoid isnt the problem, and neither is the number of parameters. I believe the problem is in my usage of autograd.
Here is the code for my Net:
class Net(nn.Module):
    def __init__(self):
      super(Net, self).__init__()
      self.model = nn.Sequential(
      nn.Linear(1, 20),
      nn.Sigmoid(),
      nn.Linear(20, 50),
      nn.Sigmoid(),
      nn.Linear(50, 50),
      nn.Sigmoid(),
      nn.Linear(50, 1)
      )
            
    def forward(self, x):
        x = self.model(x)
        return x

Here is my training loop:
def train(net, trainloader, valloader, learningrate, n_epochs):
  net = net.train()
  loss = nn.MSELoss()
  optimizer = torch.optim.SGD(net.parameters(), lr = learningrate)
  
  for epoch in range(n_epochs):
    
    for X, y in trainloader:
        X = X.reshape(-1, 1)
        optimizer.zero_grad()
      
        outputs = net.forward(X)
        error   = loss(outputs, y)
        error.backward()
        optimizer.step()
      
    total_loss = 0
    for X, y in valloader:
        X = X.reshape(-1, 1).float()
        outputs = net(X)
        error   = loss(outputs, y.float() )
        total_loss += error.data
      
    print('Val loss for epoch', epoch, 'is', total_loss / len(valloader) )

Where trainloader and valloader are the data loaders for the training and validation data.
Here is my data creation:
data = np.repeat( np.arange(0, 2 * np.pi, step = .001), 7)

from sklearn.model_selection import train_test_split
train_X, test_X, train_y, test_y = train_test_split(data, np.sin(data), test_size = 1/5, random_state = 9122019, shuffle = True)
from torch.utils.data import DataLoader

train_X, val_X, train_y, val_y = train_test_split(train_X, train_y, train_size = .8, shuffle = False)

train_data  = Dataset(train_X, train_y)
val_data    = Dataset(val_X, val_y)
test_data   = Dataset(test_X, test_y)

trainloader = DataLoader(train_data, batch_size = 64, shuffle = True)
valloader   = DataLoader(val_data, batch_size = 64, shuffle = True)
testloader  = DataLoader(test_data, batch_size = 64, shuffle = True)

The final result after a large amount of epochs is a line at y = 0. The final result overall is always a straight line at the mean of the output data, so if i set Y = X, the final result is a horizontal line at ( X.max() - X.min() )/ 2
Can someone tell me where my code is wrong? Thank you.
Edit: My train is called as such:
net = Net()
train(net, trainloader, valloader, .0001, n_epochs = 4)