To implement PReLU with proper gradients, device/dtype awareness and practical notes, create a custom autograd.Function for the op and an nn.Module wrapper that registers alpha as a Parameter. Keep operations on the same device/dtype, avoid in-place ops if you need gradients, and expose state_dict for save/load. For JIT/ONNX, prefer torch.nn.PReLU (built-in) since custom Functions may not export cleanly; if you must, provide a torchscript-friendly forward or fallback.python
import torch
from torch import nn
class _PReLUFunc(torch.autograd.Function):
@staticmethod
def forward(ctx, input, alpha):
# ensure dtype/device alignment
alpha = alpha.to(input.dtype)
ctx.save_for_backward(input, alpha)
return torch.where(input >= 0, input, alpha * input)
@staticmethod
def backward(ctx, grad_output):
input, alpha = ctx.saved_tensors
grad_input = grad_alpha = None
# dL/dx = dL/dy * (1 if x>=0 else alpha)
mask_pos = (input >= 0).to(grad_output.dtype)
grad_input = grad_output * (mask_pos + (1 - mask_pos) * alpha)
# dL/dalpha = sum(dL/dy * x for x<0)
grad_alpha = (grad_output * input * (input < 0).to(grad_output.dtype)).sum()
# keep grad_alpha shape same as alpha param (broadcast)
grad_alpha = grad_alpha.view_as(alpha)
return grad_input, grad_alpha
class PReLU(nn.Module):
def __init__(self, num_parameters=1, init=0.25):
super().__init__()
# alpha shape: (num_parameters,) or scalar
self.num_parameters = num_parameters
alpha = torch.full((num_parameters,), float(init))
self.alpha = nn.Parameter(alpha)
def forward(self, x):
# broadcast alpha to input channels if needed (common pattern)
if self.num_parameters == 1:
a = self.alpha
else:
# assume channel-first: (N,C,...)
shape = [1, -1] + [1] * (x.dim() - 2)
a = self.alpha.view(*shape)
return _PReLUFunc.apply(x, a)
# saving/loading handled by state_dict automatically
Key points:- Initialization: small positive (0.01–0.25) or zero; choose based on model needs.- Regularization: apply L2 only if desired (e.g., weight decay on alpha or explicit penalty); consider constraining alpha to >=0 by parameterizing via softplus or clamping after update (clamp breaks gradients if applied in forward; prefer param transform: alpha = softplus(raw_alpha)).- Device/dtype: convert alpha to input.dtype in forward to avoid mismatches.- Inplace: avoid in-place ops to preserve saved tensors for backward.- JIT/ONNX: custom autograd Functions generally don't export; to export, either: - use torch.nn.PReLU (torch.ops that export), or - provide a scriptable Module replacing Function with pure torch ops in forward and annotate with @torch.jit.ignore for custom backward, or implement alpha transform in a scriptable way.- Saving/loading: standard state_dict() / load_state_dict() works. If parameterized (raw_alpha), save raw parameter and reconstruct transform on load.