AlignMark / models.py
2026anonymous
Initial commit
b9deace
Raw
History Blame Contribute Delete
10.1 kB
from typing import Any, Dict, Optional, Tuple
import torch
import torch.nn as nn
import torch.nn.functional as F
from speechtokenizer.modules.seanet import SEANetEncoder
from util import chunks_to_bits
class SkipGatedBlock(nn.Module):
def __init__(self, c_in, c_out, kernel_size, stride, padding):
super().__init__()
self.conv = nn.Conv2d(c_in, c_out, kernel_size=kernel_size, stride=stride, padding=padding, bias=True)
self.gate = nn.Conv2d(c_in, c_out, kernel_size=kernel_size, stride=stride, padding=padding, bias=True)
self.skip_connection = c_in == c_out and (stride == 1 if isinstance(stride, int) else all(s == 1 for s in stride))
self.has_skip_proj = (c_in == c_out) and not self.skip_connection
def forward(self, x):
conv_output = self.conv(x)
gated_output = torch.sigmoid(self.gate(x))
output = conv_output * gated_output
if self.skip_connection:
output.add_(x)
elif self.has_skip_proj:
skip_output = F.adaptive_avg_pool2d(x, output.shape[2:])
output.add_(skip_output)
return output
class WatermarkDecoder(nn.Module):
def __init__(self, input_channels, nbits, nchunk_size, hidden_dim, d_model):
super().__init__()
self.nchunk_size = nchunk_size
assert nbits % nchunk_size == 0
self.nbits = nbits
self.d_model = d_model
self.nchunks = nbits // nchunk_size
self.proj = nn.Linear(input_channels, d_model)
self.detect_encoder = nn.Sequential(
nn.Conv1d(d_model, d_model, kernel_size=3, padding=1), nn.GELU(),
nn.Conv1d(d_model, d_model, kernel_size=3, padding=1), nn.GELU(),
nn.Conv1d(d_model, d_model, kernel_size=3, padding=1), nn.GELU(),
nn.Conv1d(d_model, d_model, kernel_size=3, padding=1), nn.GELU(),
nn.Conv1d(d_model, 1, kernel_size=3, padding=1),
)
self.message_encoder = nn.ModuleList([
nn.Conv2d(1, 16, kernel_size=(5, 3), stride=(2, 1), padding=(0, 1)), nn.GELU(),
nn.Conv2d(16, 32, kernel_size=(5, 3), stride=(2, 1), padding=(0, 1)), nn.GELU(),
nn.Conv2d(32, 64, kernel_size=(5, 3), stride=(2, 1), padding=(0, 1)), nn.GELU(),
nn.Conv2d(64, 128, kernel_size=(5, 3), stride=(2, 1), padding=(0, 1)), nn.GELU(),
nn.Conv2d(128, 256, kernel_size=(5, 3), stride=(2, 1), padding=(0, 1)), nn.GELU(),
nn.Conv2d(256, 512, kernel_size=(5, 3), stride=(1, 1), padding=(0, 1)),
])
self.d_proj_16 = nn.Linear(126, 1)
self.d_proj_32 = nn.Linear(61, 1)
self.d_proj_64 = nn.Linear(29, 1)
self.d_proj_128 = nn.Linear(13, 1)
self.d_proj_256 = nn.Linear(5, 1)
self.d_projs = [self.d_proj_16, self.d_proj_32, self.d_proj_64, self.d_proj_128, self.d_proj_256]
self.message_head = nn.Sequential(
nn.Linear(512 + 256 + 128 + 64 + 32 + 16, 1024), nn.GELU(), nn.Linear(1024, 2**nchunk_size * self.nchunks),
)
def forward(self, x):
batch_size = x.shape[0]
x = self.proj(x.transpose(-1, -2)).transpose(-1, -2)
frame_logits = self.detect_encoder(x).squeeze(1)
temperature = 2.0
frame_weights = torch.sigmoid(frame_logits / temperature).clamp(min=0.01, max=1.0)
current_feat = x.unsqueeze(1)
multi_scale_features = []
d_proj_idx = 0
for layer in self.message_encoder:
current_feat = layer(current_feat)
if isinstance(layer, nn.Conv2d):
B, C, D, T = current_feat.shape
if D != 1:
feat_trans = current_feat.permute(0, 1, 3, 2)
feat_flat = feat_trans.reshape(-1, D)
d_proj = self.d_projs[d_proj_idx]
feat_proj = d_proj(feat_flat).reshape(B, C, T, 1).squeeze(-1)
else:
feat_proj = current_feat.squeeze(2)
multi_scale_features.append(feat_proj)
d_proj_idx += 1
combined_features = torch.cat(multi_scale_features, dim=1).permute(0, 2, 1)
time_logits = self.message_head(combined_features)
weighted_logits = time_logits * frame_weights.unsqueeze(-1)
weight_sum = frame_weights.sum(dim=1, keepdim=True).clamp(min=1e-6)
avg_logits = weighted_logits.sum(dim=1) / weight_sum
chunk_logits = avg_logits.reshape(batch_size, self.nchunks, 2 ** self.nchunk_size)
return frame_logits, chunk_logits
def detect_watermark(self, x):
frame_logits, chunk_logits = self.forward(x)
chunk_probs = F.softmax(chunk_logits, dim=-1)
chunk_indices = torch.argmax(chunk_probs, dim=-1)
chunk_values = [chunk_indices[:, i] for i in range(self.nchunks)]
binary_message = chunks_to_bits(chunk_values, self.nchunk_size)
return frame_logits, chunk_logits, binary_message
class WatermarkEmbedder(nn.Module):
def __init__(self, nbits, input_dim, hidden_dim, d_model):
super().__init__()
self.nbits = nbits
self.hidden_dim = hidden_dim
self.input_dim = input_dim
self.proj = nn.Linear(input_dim, d_model)
self.msg_embedding = nn.Embedding(2 * nbits, d_model)
self.conv_layers = nn.Sequential(
SkipGatedBlock(1 + 1, hidden_dim, kernel_size=3, stride=1, padding=1),
SkipGatedBlock(hidden_dim, hidden_dim, kernel_size=3, stride=1, padding=1),
SkipGatedBlock(hidden_dim, hidden_dim, kernel_size=3, stride=1, padding=1),
SkipGatedBlock(hidden_dim, hidden_dim, kernel_size=3, stride=1, padding=1),
SkipGatedBlock(hidden_dim, hidden_dim, kernel_size=3, stride=1, padding=1),
SkipGatedBlock(hidden_dim, 1, kernel_size=3, stride=1, padding=1),
)
self.out_proj = nn.Conv1d(d_model, input_dim, kernel_size=1)
def embed_bits(self, message):
idx = message + torch.arange(self.nbits, device=message.device) * 2
emb = self.msg_embedding(idx)
return emb.sum(dim=1).unsqueeze(1)
def forward(self, hidden, msg):
seq_len = hidden.shape[-1]
hidden_orig = hidden
hidden = self.proj(hidden.transpose(-1, -2)).transpose(-1, -2)
msg_emb = self.embed_bits(msg)
if hidden.dim() == 3:
hidden = hidden.unsqueeze(1)
combined_input = torch.cat([hidden, msg_emb.unsqueeze(-1).expand(-1, -1, -1, seq_len)], dim=1)
x = self.conv_layers(combined_input)
output = x.squeeze(1)
output = self.out_proj(output)
output = hidden_orig + output
return output
class WatermarkModel(nn.Module):
def __init__(self, config):
super().__init__()
self.nbits = config.nbits
self.nfft = config.wm_mb.nfft
self.sr = config.wm_mb.sr
self.latent_dim = 1024
self.encoder = SEANetEncoder(
n_filters=64,
dimension=self.latent_dim,
ratios=[8, 5, 4, 2],
lstm=2,
dilation_base=2,
residual_kernel_size=3,
n_residual_layers=1,
activation="ELU",
bidirectional=True,
)
self.embedder = WatermarkEmbedder(
nbits=config.nbits, input_dim=self.latent_dim, hidden_dim=32, d_model=256
)
self.detector = WatermarkDecoder(
self.latent_dim, config.nbits, nchunk_size=config.wm_mb.nchunk_size, hidden_dim=32, d_model=256
)
def decode_watermark(self, x: torch.Tensor) -> Tuple[Any, ...]:
embedding = self.encoder(x)
frame_logits, chunk_logits, binary_message = self.detector.detect_watermark(embedding)
return embedding, (frame_logits, chunk_logits), binary_message
def forward(self, feat: torch.Tensor, message: Optional[torch.Tensor]) -> Dict[str, torch.Tensor]:
feat_wm = self.embedder(feat, message)
return feat_wm
class AudioFusionModel(nn.Module):
def __init__(self, n_fft=256, hop_length=64, win_length=256, hidden_dim=64, nbits=16):
super().__init__()
self.n_fft = n_fft
self.hop_length = hop_length
self.win_length = win_length
self.hidden_dim = hidden_dim
self.weight_net = nn.Sequential(
nn.Conv2d(4, hidden_dim, kernel_size=(3, 3), padding=(1, 1)), nn.LeakyReLU(0.1),
nn.Conv2d(hidden_dim, hidden_dim, kernel_size=(3, 3), padding=(1, 1)), nn.LeakyReLU(0.1),
nn.Conv2d(hidden_dim, hidden_dim, kernel_size=(3, 3), padding=(1, 1)), nn.LeakyReLU(0.1),
nn.Conv2d(hidden_dim, 2, kernel_size=(3, 3), padding=(1, 1)), nn.Sigmoid(),
)
def forward(self, wav_orig, wav_wm):
wav_orig = wav_orig.detach()
stft_orig = torch.stft(
wav_orig.squeeze(1), self.n_fft, hop_length=self.hop_length, win_length=self.win_length,
window=torch.hann_window(self.win_length).to(wav_orig.device), return_complex=True
)
real_orig, imag_orig = stft_orig.real, stft_orig.imag
stft_wm = torch.stft(
wav_wm.squeeze(1), self.n_fft, hop_length=self.hop_length, win_length=self.win_length,
window=torch.hann_window(self.win_length).to(wav_wm.device), return_complex=True
)
real_wm, imag_wm = stft_wm.real, stft_wm.imag
complex_input = torch.stack([real_orig, imag_orig, real_wm, imag_wm], dim=1)
alpha_weights = self.weight_net(complex_input)
alpha_real = alpha_weights[:, 0, :, :]
alpha_imag = alpha_weights[:, 1, :, :]
real_fused = real_orig * alpha_real + real_wm * (1 - alpha_real)
imag_fused = imag_orig * alpha_imag + imag_wm * (1 - alpha_imag)
final_stft = torch.complex(real_fused, imag_fused)
wav_fused = torch.istft(
final_stft.to(wav_orig.device), n_fft=self.n_fft, hop_length=self.hop_length, win_length=self.win_length,
window=torch.hann_window(self.win_length).to(wav_orig.device), length=wav_orig.shape[-1]
).unsqueeze(1)
return wav_fused