Speed of L2 Regularization on Pytorch
I'm trying to manually implement L2 regularisation and a couple of its variations in a neural network. What I'm doing is the following:

for name, param in model.state_dict():
    if 'weight' in name:
        l2_reg += torch.sum(param**2)

loss = cross_entropy(outputs, labels) + 0.0001*l2_reg


Is this equivalent to adding 'weight_decay = 0.0001' inside my optimizer? i.e.:

torch.optim.SGD(model.parameters(), lr=learning_rate , momentum=0.9, weight_decay = 0.0001)


My problem is that I thought they were equivalent, but the manual procedure is about 100x slower than adding 'weight_decay = 0.0001'. Why is that? How can I fix it?

Note that I need to also implement my own variation of L2 regularization, so just adding 'weight_decay = 0.0001' won't help.

You can check PyTorch implementation of SGD to get some tips and base off of that code.

There are a few things going on which should speed up your custom regularization.
Below is a cleaned version (a little pseudo-code, refer to original) of the parts we are interested in:

for p in group['params']:
    if p.grad is None:
        continue
    d_p = p.grad.data
    if weight_decay != 0:
        d_p.add_(weight_decay, p.data)

    p.data.add_(-group['lr'], d_p)

return loss


BTW. It seems your implementation is mathematically sound (correct me if I missed anything) and equivalent to PyTorch but will be slow indeed.

Modify only gradient

Please notice you perform regularization explicitly during forward pass. This takes a lot of time, more or less because:


take parameters and iterate over them
take it to the power of 2
sum all of them 
add to variable containing all previous parameters (all this while creating graph dynamically and creating new nodes).


What pytorch does is it only focuses on backward pass as that's all is needed. This is pretty handy because:


parameters have to be loaded and iterated over once anyway during corrections performed by optimizer (in your case they are taken out twice)
no power of 2 because gradient of w**2 is simply 2*w (2 is further left out and L2 is often expressed as 1/2 * w **2 to make it simpler and a little faster)
no accumulation and creation of additional graph nodes


Essentially, this line:

d_p.add_(weight_decay, p.data)


Modifies the gradient adding p.data (weight) multiplied by weight_decay all done in-place (notice d_p.add_), which is all you have to do to perform L2 regularization.

Finally this line:

p.data.add_(-group['lr'], d_p)


Updates weights with gradient (modified by weight decay) using standard SGD formula (once again, in-place to be as fast as possible, at least on Python level).

Your own implementation

I would advise you to follow similar logic for your own regularization if you want to make it faster.

You can copy PyTorch implementation of SGD and only change this one relevant line. This would also gives you functionality of PyTorch optimizer in case you need it in your experiments.

For L1 regularization (|w| instead of w**2) you would have to calculate the derivative of it (which is 1 for positive case, -1 for negative and undefined for 0 (we can't have that so it should be zero)).

With that in mind we can write the weight_decay like this:

if weight_decay != 0:
    d_p.add_(weight_decay, torch.sign(p.data))


torch.sign returns 1 for positive values and -1 for negative and 0 for... yeah, 0.

Hope this helps, exact implementation is left for you (hit me up in the comments in case you have any questions or troubles).
