L1 regulariser Pytorch acting opposite to what I expect
I'm trying to add an L1 penalty to a specific layer of a neural network, and I have the code below (in which I attempt to add l1 penalty to the first layer). If I run it for lambda = 0 (i.e. no penalty), the output gets very close to the expected weights those being [10, 12, 2, 11, -0.25]) and if I run for enough epochs or reduce batch size it will get it exactly, as in the output below:


  mlp.0.weight
  
  Parameter containing:
  
  tensor([[  9.8657, -11.8305,   2.0242,  10.8913,  -0.1978]],
         requires_grad=True)


Then, when I run it for a large lambda, say 1000, I would expect these weights to shrink towards zero as there is a large penalty being added to the loss that we are trying to minimise. However, the opposite happens and the weights explode, as in the output below (for lam = 1000)


  mlp.0.weight
  
  Parameter containing:
  
  tensor([[-13.9368,   9.9072,   2.2447, -11.6870,  26.7293]],
         requires_grad=True)


If anyone could help me, that'd be great. I'm new to pytorch (but not the idea of regularisation), so I'm guessing it's something in my code that is the problem. 

Thanks

import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
import numpy as np
from sklearn.linear_model import LinearRegression


class TrainDataset(Dataset):
    def __init__(self, data):
        self.data = data
    def __len__(self):
        return self.data.shape[0]
    def __getitem__(self, ind):
        x = self.data[ind][1:]
        y = self.data[ind][0]
        return x, y


class TestDataset(TrainDataset):
    def __getitem__(self, ind):
        x = self.data[ind]
        return x


torch.manual_seed(94)

x_train = np.random.rand(1000, 5)
y_train = x_train[:, 0] * 10 - x_train[:, 1] * 12 + x_train[:, 2] * 2 + x_train[:, 3] * 11 - x_train[:, 4] * 0.25
y_train = y_train.reshape(1000, 1)
x_train.shape
y_train.shape
train_data = np.concatenate((y_train, x_train), axis=1)



train_set = TrainDataset(train_data)




batch_size = 100
train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)


class MLP(nn.Module):
    def __init__(self):
        super(MLP, self).__init__()
        self.mlp = nn.Sequential(nn.Linear(5, 1, bias=False))

    def forward(self, x_mlp):
        out = self.mlp(x_mlp)
        return out


device = 'cpu'

model = MLP()

optimizer = torch.optim.SGD(model.parameters(), lr=0.02, momentum=0.82)

criterion = nn.MSELoss()

epochs = 5
lam = 0
model.train()

for epoch in range(epochs):
    losses = []
    for batch_num, input_data in enumerate(train_loader):
        optimizer.zero_grad()

        x, y = input_data
        x = x.to(device).float()
        y = y.reshape(batch_size, 1)
        y = y.to(device)

        output = model(x)
        for name, param in model.named_parameters():
            if name == 'mlp.0.weight':
                l1_norm = torch.norm(param, 1)

        loss = criterion(output, y) + lam * l1_norm
        loss.backward()

        optimizer.step()
        print('\tEpoch %d | Batch %d | Loss %6.2f' % (epoch, batch_num, loss.item()))


for name, param in model.named_parameters():
    if param.requires_grad:
        print(name)
        print(param)


I found that if I use Adagrad as the optimiser instead of SGD, it acts as expected. Will need to look into the difference of those now, but this can be considered answered.
