Spaces:
Sleeping
Sleeping
| """ | |
| Machine Learning models for neural distinguishers. | |
| 1. MLP | |
| 2. CNN (1D ResNet like) | |
| 3. Siamese Network | |
| 4. MINE (Mutual Information Neural Estimator) | |
| """ | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| import math | |
| # ============================================================================== | |
| # 1. Multi-Layer Perceptron (MLP) | |
| # ============================================================================== | |
| class MLP(nn.Module): | |
| def __init__(self, input_dim, hidden_dim=256, num_layers=4): | |
| super(MLP, self).__init__() | |
| layers = [nn.Linear(input_dim, hidden_dim), nn.ReLU(), nn.BatchNorm1d(hidden_dim)] | |
| for _ in range(num_layers - 2): | |
| layers.extend([nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.BatchNorm1d(hidden_dim)]) | |
| layers.append(nn.Linear(hidden_dim, 1)) | |
| self.net = nn.Sequential(*layers) | |
| def forward(self, x): | |
| # Flatten arbitrary shapes | |
| if x.dim() > 2: | |
| x = x.view(x.size(0), -1) | |
| return self.net(x) | |
| # ============================================================================== | |
| # 2. Convolutional Neural Network (CNN) - ResNet inspired | |
| # ============================================================================== | |
| class ResidualBlock1D(nn.Module): | |
| def __init__(self, dim, k=3): | |
| super(ResidualBlock1D, self).__init__() | |
| self.conv1 = nn.Conv1d(dim, dim, k, padding=k//2) | |
| self.bn1 = nn.BatchNorm1d(dim) | |
| self.conv2 = nn.Conv1d(dim, dim, k, padding=k//2) | |
| self.bn2 = nn.BatchNorm1d(dim) | |
| def forward(self, x): | |
| out = F.relu(self.bn1(self.conv1(x))) | |
| out = self.bn2(self.conv2(out)) | |
| return F.relu(out + x) | |
| class CNN(nn.Module): | |
| def __init__(self, input_channels, seq_len=64, hidden_dim=64): | |
| super(CNN, self).__init__() | |
| self.conv_in = nn.Conv1d(input_channels, hidden_dim, 3, padding=1) | |
| self.res1 = ResidualBlock1D(hidden_dim) | |
| self.res2 = ResidualBlock1D(hidden_dim) | |
| self.pool = nn.AdaptiveAvgPool1d(1) | |
| self.fc = nn.Linear(hidden_dim, 1) | |
| def forward(self, x): | |
| # x shape: [B, channels, length] (e.g. rep 1 gives [B, 2, 64]) | |
| if x.dim() == 2: | |
| x = x.unsqueeze(1) # [B, 1, seq_len] | |
| elif x.dim() == 3: | |
| # If the current channel count doesn't match the weight but the length does, transpose. | |
| # This handles [B, L, C] inputs. | |
| if x.shape[1] != self.conv_in.in_channels and x.shape[2] == self.conv_in.in_channels: | |
| x = x.transpose(1, 2) | |
| out = F.relu(self.conv_in(x)) | |
| out = self.res1(out) | |
| out = self.res2(out) | |
| out = self.pool(out).squeeze(-1) | |
| return self.fc(out) | |
| # ============================================================================== | |
| # 3. Siamese Network | |
| # ============================================================================== | |
| class SiameseNet(nn.Module): | |
| def __init__(self, branch_dim, hidden_dim=64): | |
| super(SiameseNet, self).__init__() | |
| self.branch = nn.Sequential( | |
| nn.Linear(branch_dim, hidden_dim), | |
| nn.ReLU(), | |
| nn.BatchNorm1d(hidden_dim), | |
| nn.Linear(hidden_dim, hidden_dim), | |
| nn.ReLU() | |
| ) | |
| self.fc = nn.Linear(hidden_dim * 2, 1) | |
| def forward(self, x): | |
| # Expected shape [B, 2, branch_dim] | |
| # x1 is from ciphertext C, x2 is from C' | |
| x1 = x[:, 0, :] | |
| x2 = x[:, 1, :] | |
| feat1 = self.branch(x1) | |
| feat2 = self.branch(x2) | |
| combined = torch.cat([feat1, feat2], dim=1) | |
| return self.fc(combined) | |
| # ============================================================================== | |
| # 4. Mutual Information Neural Estimator (MINE) | |
| # ============================================================================== | |
| class MINE(nn.Module): | |
| """ | |
| Estimates MI between ciphertexts (C) and differences (C'). | |
| For distinguisher, we output scalar discrimination from joint stats. | |
| """ | |
| def __init__(self, x_dim, y_dim, hidden_size=64): | |
| super(MINE, self).__init__() | |
| self.net = nn.Sequential( | |
| nn.Linear(x_dim + y_dim, hidden_size), | |
| nn.ReLU(), | |
| nn.Linear(hidden_size, hidden_size), | |
| nn.ReLU(), | |
| nn.Linear(hidden_size, 1) | |
| ) | |
| def forward(self, x, y): | |
| # x is from joint distribution P(x,y), y is from marginals | |
| # Output is essentially T(x,y) for MINE objective | |
| joint = torch.cat((x, y), dim=1) | |
| return self.net(joint) | |
| def mine_loss(net, x, y): | |
| """ | |
| Computes MINE loss and mutual information estimate explicitly. | |
| In the context of distinguisher, we maximize MI on cipher data. | |
| """ | |
| # shuffle y to get marginals | |
| y_shuffled = y[torch.randperm(y.shape[0])] | |
| t = net(x, y) | |
| t_marg = net(x, y_shuffled) | |
| mi = torch.mean(t) - torch.log(torch.mean(torch.exp(t_marg))) | |
| return -mi # minimize negative MI | |