Spaces:
Runtime error
Runtime error
| import torch | |
| import torch.nn as nn | |
| def _make_pruned_conv(old_conv: nn.Conv2d, keep_out_idx: torch.Tensor) -> nn.Conv2d: | |
| """ | |
| Create a new Conv2d with fewer output channels (keep_out_idx), | |
| copying weights/bias from old_conv. | |
| """ | |
| device = old_conv.weight.device | |
| dtype = old_conv.weight.dtype | |
| new_out = keep_out_idx.numel() | |
| new_conv = nn.Conv2d( | |
| in_channels=old_conv.in_channels, | |
| out_channels=new_out, | |
| kernel_size=old_conv.kernel_size, | |
| stride=old_conv.stride, | |
| padding=old_conv.padding, | |
| dilation=old_conv.dilation, | |
| groups=old_conv.groups, | |
| bias=(old_conv.bias is not None), | |
| padding_mode=old_conv.padding_mode, | |
| ).to(device=device, dtype=dtype) | |
| with torch.no_grad(): | |
| new_conv.weight.copy_(old_conv.weight.data[keep_out_idx].contiguous()) | |
| if old_conv.bias is not None: | |
| new_conv.bias.copy_(old_conv.bias.data[keep_out_idx].contiguous()) | |
| return new_conv | |
| def _make_pruned_bn(old_bn: nn.BatchNorm2d, keep_idx: torch.Tensor) -> nn.BatchNorm2d: | |
| """ | |
| Create a new BatchNorm2d with fewer channels, copying params + running stats. | |
| """ | |
| device = old_bn.weight.device | |
| dtype = old_bn.weight.dtype | |
| new_nf = keep_idx.numel() | |
| new_bn = nn.BatchNorm2d( | |
| num_features=new_nf, | |
| eps=old_bn.eps, | |
| momentum=old_bn.momentum, | |
| affine=old_bn.affine, | |
| track_running_stats=old_bn.track_running_stats, | |
| ).to(device=device, dtype=dtype) | |
| with torch.no_grad(): | |
| if old_bn.affine: | |
| new_bn.weight.copy_(old_bn.weight.data[keep_idx].contiguous()) | |
| new_bn.bias.copy_(old_bn.bias.data[keep_idx].contiguous()) | |
| if old_bn.track_running_stats: | |
| new_bn.running_mean.copy_(old_bn.running_mean.data[keep_idx].contiguous()) | |
| new_bn.running_var.copy_(old_bn.running_var.data[keep_idx].contiguous()) | |
| new_bn.num_batches_tracked.copy_(old_bn.num_batches_tracked) | |
| return new_bn | |
| def prune_conv_bn_pair(conv: nn.Conv2d, bn: nn.BatchNorm2d, amount: float = 0.3): | |
| """ | |
| Structurally prune Conv2d output channels using L1 norm. | |
| Returns: (new_conv, new_bn, keep_out_idx) | |
| """ | |
| if not (0.0 <= amount < 1.0): | |
| raise ValueError("amount must be in [0, 1).") | |
| W = conv.weight.data # (out, in, kH, kW) | |
| out_ch = W.shape[0] | |
| num_prune = int(round(amount * out_ch)) | |
| if num_prune <= 0: | |
| keep_idx = torch.arange(out_ch, device=W.device) | |
| return conv, bn, keep_idx | |
| # L1 norm per output channel | |
| channel_l1 = W.abs().sum(dim=(1, 2, 3)) # (out,) | |
| # Keep the highest-L1 channels | |
| sorted_idx = torch.argsort(channel_l1, descending=True) | |
| keep_idx = sorted_idx[num_prune:] # (kept,) | |
| # Keep indices sorted for nicer determinism | |
| keep_idx, _ = torch.sort(keep_idx) | |
| new_conv = _make_pruned_conv(conv, keep_idx) | |
| new_bn = _make_pruned_bn(bn, keep_idx) | |
| return new_conv, new_bn, keep_idx | |
| def prune_conv_input_channels(conv: nn.Conv2d, keep_in_idx: torch.Tensor) -> nn.Conv2d: | |
| """ | |
| Prune Conv2d input channels by selecting keep_in_idx on dim=1 of weight. | |
| Returns a new conv with in_channels = len(keep_in_idx). | |
| """ | |
| device = conv.weight.device | |
| dtype = conv.weight.dtype | |
| new_in = keep_in_idx.numel() | |
| new_conv = nn.Conv2d( | |
| in_channels=new_in, | |
| out_channels=conv.out_channels, | |
| kernel_size=conv.kernel_size, | |
| stride=conv.stride, | |
| padding=conv.padding, | |
| dilation=conv.dilation, | |
| groups=conv.groups, # assumes groups-compatible; your model uses groups=1 | |
| bias=(conv.bias is not None), | |
| padding_mode=conv.padding_mode, | |
| ).to(device=device, dtype=dtype) | |
| with torch.no_grad(): | |
| # weight shape: (out, in, kH, kW) | |
| new_conv.weight.copy_(conv.weight.data[:, keep_in_idx].contiguous()) | |
| if conv.bias is not None: | |
| new_conv.bias.copy_(conv.bias.data.contiguous()) | |
| return new_conv | |
| def rebuild_fc_after_pruning(model: nn.Module, example_input: torch.Tensor) -> None: | |
| """ | |
| Rebuild model.fc input dim based on the current conv/ssrp path. | |
| Assumes model has attributes: conv1, conv2, conv3, ssrp_ms, flatten, fc. | |
| """ | |
| device = next(model.parameters()).device | |
| model.eval() | |
| with torch.no_grad(): | |
| x = example_input.to(device) | |
| feats = model.flatten(model.ssrp_ms(model.conv3(model.conv2(model.conv1(x))))) | |
| in_dim = feats.shape[1] | |
| old_out = model.fc.out_features | |
| model.fc = nn.Linear(in_dim, old_out).to(device) | |
| model.train() | |
| def apply_structural_pruning(model: nn.Module, amount: float = 0.8, example_input: torch.Tensor = torch.randn(1, 1, 40, 862)) -> nn.Module: | |
| """ | |
| Structural channel pruning for your CNN_PCAw_SSRPMS_KAN conv blocks. | |
| - Prunes conv1 out channels + bn1, updates conv2 input channels accordingly | |
| - Prunes conv2 out channels + bn2, updates conv3 input channels accordingly | |
| - Prunes conv3 out channels + bn3 | |
| - Optionally rebuilds fc using example_input | |
| example_input should be shaped like your model input, e.g. (1, 1, F, T) | |
| """ | |
| device = next(model.parameters()).device | |
| # ---- conv1 prune (conv1[1] is Conv2d, conv1[2] is BN) ---- | |
| conv1_old = model.conv1[1] | |
| bn1_old = model.conv1[2] | |
| conv1_new, bn1_new, keep1 = prune_conv_bn_pair(conv1_old, bn1_old, amount) | |
| model.conv1[1] = conv1_new | |
| model.conv1[2] = bn1_new | |
| # ---- conv2 input prune to match conv1 kept outputs ---- | |
| model.conv2[1] = prune_conv_input_channels(model.conv2[1], keep1) | |
| # ---- conv2 prune ---- | |
| conv2_old = model.conv2[1] | |
| bn2_old = model.conv2[2] | |
| conv2_new, bn2_new, keep2 = prune_conv_bn_pair(conv2_old, bn2_old, amount) | |
| model.conv2[1] = conv2_new | |
| model.conv2[2] = bn2_new | |
| # ---- conv3 input prune to match conv2 kept outputs ---- | |
| model.conv3[0] = prune_conv_input_channels(model.conv3[0], keep2) | |
| # ---- conv3 prune ---- | |
| conv3_old = model.conv3[0] | |
| bn3_old = model.conv3[1] | |
| conv3_new, bn3_new, keep3 = prune_conv_bn_pair(conv3_old, bn3_old, amount) | |
| model.conv3[0] = conv3_new | |
| model.conv3[1] = bn3_new | |
| # Ensure the whole model stays on the same device | |
| model.to(device) | |
| # ---- fc rebuild (needed because flatten dim changes) ---- | |
| if example_input is not None: | |
| rebuild_fc_after_pruning(model, example_input) | |
| return model | |