Download model.py from eliya/forensics_0.3B_base_deepfake_classifier: direct link, hf CLI and curl.
- Browser
- Download file 7.76 kB
-
https://huggingface.co/eliya/forensics_0.3B_base_deepfake_classifier/resolve/main/model.py
- Command line
-
hf download hf://eliya/forensics_0.3B_base_deepfake_classifier/model.py
-
curl -L -o model.py https://huggingface.co/eliya/forensics_0.3B_base_deepfake_classifier/resolve/main/model.py
7.76 kB
| """WavLM-large + AASIST speech deepfake detector (inference only).""" | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from transformers import WavLMModel | |
| class _GraphAttentionLayer(nn.Module): | |
| def __init__(self, in_dim, out_dim, temperature): | |
| super().__init__() | |
| self.att_proj = nn.Linear(in_dim, out_dim) | |
| self.att_weight = nn.Parameter(torch.empty(out_dim, 1)) | |
| self.proj_with_att = nn.Linear(in_dim, out_dim) | |
| self.proj_without_att = nn.Linear(in_dim, out_dim) | |
| self.bn = nn.BatchNorm1d(out_dim) | |
| self.temp = temperature | |
| def forward(self, x): | |
| N = x.size(1) | |
| x_i = x.unsqueeze(2).expand(-1, -1, N, -1) | |
| att = torch.matmul(torch.tanh(self.att_proj(x_i * x_i.transpose(1, 2))), self.att_weight) / self.temp | |
| att = F.softmax(att, dim=-2) | |
| out = self.proj_with_att(torch.matmul(att.squeeze(-1), x)) + self.proj_without_att(x) | |
| s = out.shape | |
| return F.selu(self.bn(out.reshape(-1, s[-1])).reshape(s)) | |
| class _HtrgGraphAttentionLayer(nn.Module): | |
| def __init__(self, in_dim, out_dim, temperature): | |
| super().__init__() | |
| self.proj_type1 = nn.Linear(in_dim, in_dim) | |
| self.proj_type2 = nn.Linear(in_dim, in_dim) | |
| self.att_proj = nn.Linear(in_dim, out_dim) | |
| self.att_projM = nn.Linear(in_dim, out_dim) | |
| self.att_weight11 = nn.Parameter(torch.empty(out_dim, 1)) | |
| self.att_weight22 = nn.Parameter(torch.empty(out_dim, 1)) | |
| self.att_weight12 = nn.Parameter(torch.empty(out_dim, 1)) | |
| self.att_weightM = nn.Parameter(torch.empty(out_dim, 1)) | |
| self.proj_with_att = nn.Linear(in_dim, out_dim) | |
| self.proj_without_att = nn.Linear(in_dim, out_dim) | |
| self.proj_with_attM = nn.Linear(in_dim, out_dim) | |
| self.proj_without_attM = nn.Linear(in_dim, out_dim) | |
| self.bn = nn.BatchNorm1d(out_dim) | |
| self.temp = temperature | |
| def forward(self, x1, x2, master): | |
| n1 = x1.size(1) | |
| x = torch.cat([self.proj_type1(x1), self.proj_type2(x2)], dim=1) | |
| N = x.size(1) | |
| x_i = x.unsqueeze(2).expand(-1, -1, N, -1) | |
| att_map = torch.tanh(self.att_proj(x_i * x_i.transpose(1, 2))) | |
| board = torch.zeros_like(att_map[:, :, :, 0]).unsqueeze(-1) | |
| board[:, :n1, :n1] = torch.matmul(att_map[:, :n1, :n1], self.att_weight11) | |
| board[:, n1:, n1:] = torch.matmul(att_map[:, n1:, n1:], self.att_weight22) | |
| board[:, :n1, n1:] = torch.matmul(att_map[:, :n1, n1:], self.att_weight12) | |
| board[:, n1:, :n1] = torch.matmul(att_map[:, n1:, :n1], self.att_weight12) | |
| board = F.softmax(board / self.temp, dim=-2) | |
| out = self.proj_with_att(torch.matmul(board.squeeze(-1), x)) + self.proj_without_att(x) | |
| s = out.shape | |
| out = F.selu(self.bn(out.reshape(-1, s[-1])).reshape(s)) | |
| att_m = F.softmax(torch.matmul(torch.tanh(self.att_projM(x * master)), self.att_weightM) / self.temp, dim=-2) | |
| master = self.proj_with_attM(torch.matmul(att_m.squeeze(-1).unsqueeze(1), x)) + self.proj_without_attM(master) | |
| return out.narrow(1, 0, n1), out.narrow(1, n1, N - n1), master | |
| class _GraphPool(nn.Module): | |
| def __init__(self, k, in_dim): | |
| super().__init__() | |
| self.k = k | |
| self.proj = nn.Linear(in_dim, 1) | |
| def forward(self, h): | |
| scores = torch.sigmoid(self.proj(h)) | |
| n_keep = max(int(h.size(1) * self.k), 1) | |
| idx = torch.topk(scores, n_keep, dim=1)[1].expand(-1, -1, h.size(-1)) | |
| return torch.gather(h * scores, 1, idx) | |
| class AASISTPool(nn.Module): | |
| def __init__(self, feat_dim, num_layers, gat_dims=(128, 64), | |
| pool_ratios=(0.5, 0.7, 0.5, 0.5), temps=(2.0, 2.0, 100.0, 100.0)): | |
| super().__init__() | |
| g = list(gat_dims) | |
| self.proj = nn.Linear(feat_dim, g[0]) | |
| self.pos_S = nn.Parameter(torch.randn(1, num_layers, g[0])) | |
| self.master1 = nn.Parameter(torch.randn(1, 1, g[0])) | |
| self.master2 = nn.Parameter(torch.randn(1, 1, g[0])) | |
| self.GAT_S = _GraphAttentionLayer(g[0], g[0], temps[0]) | |
| self.GAT_T = _GraphAttentionLayer(g[0], g[0], temps[1]) | |
| self.HtrgGAT_11 = _HtrgGraphAttentionLayer(g[0], g[1], temps[2]) | |
| self.HtrgGAT_12 = _HtrgGraphAttentionLayer(g[1], g[1], temps[3]) | |
| self.HtrgGAT_21 = _HtrgGraphAttentionLayer(g[0], g[1], temps[2]) | |
| self.HtrgGAT_22 = _HtrgGraphAttentionLayer(g[1], g[1], temps[3]) | |
| self.pool_S = _GraphPool(pool_ratios[0], g[0]) | |
| self.pool_T = _GraphPool(pool_ratios[1], g[0]) | |
| self.pool_hS1 = _GraphPool(pool_ratios[2], g[1]) | |
| self.pool_hT1 = _GraphPool(pool_ratios[2], g[1]) | |
| self.pool_hS2 = _GraphPool(pool_ratios[2], g[1]) | |
| self.pool_hT2 = _GraphPool(pool_ratios[2], g[1]) | |
| self.output_dim = 5 * g[1] | |
| def forward(self, hidden_states): | |
| projected = self.proj(torch.stack(hidden_states, dim=1)) | |
| e_S = torch.max(torch.abs(projected), dim=2)[0] + self.pos_S | |
| e_T = torch.max(torch.abs(projected), dim=1)[0] | |
| out_S = self.pool_S(self.GAT_S(e_S)) | |
| out_T = self.pool_T(self.GAT_T(e_T)) | |
| out_T1, out_S1, m1 = self.HtrgGAT_11(out_T, out_S, self.master1) | |
| out_S1, out_T1 = self.pool_hS1(out_S1), self.pool_hT1(out_T1) | |
| a_T, a_S, a_m = self.HtrgGAT_12(out_T1, out_S1, m1) | |
| out_T1, out_S1, m1 = out_T1 + a_T, out_S1 + a_S, m1 + a_m | |
| out_T2, out_S2, m2 = self.HtrgGAT_21(out_T, out_S, self.master2) | |
| out_S2, out_T2 = self.pool_hS2(out_S2), self.pool_hT2(out_T2) | |
| a_T, a_S, a_m = self.HtrgGAT_22(out_T2, out_S2, m2) | |
| out_T2, out_S2, m2 = out_T2 + a_T, out_S2 + a_S, m2 + a_m | |
| out_T, out_S, master = torch.max(out_T1, out_T2), torch.max(out_S1, out_S2), torch.max(m1, m2) | |
| return torch.cat([torch.max(torch.abs(out_T), 1)[0], out_T.mean(1), | |
| torch.max(torch.abs(out_S), 1)[0], out_S.mean(1), master.squeeze(1)], dim=1) | |
| class MLPBlock(nn.Module): | |
| def __init__(self, dim): | |
| super().__init__() | |
| self.net = nn.Sequential(nn.Linear(dim, dim * 2), nn.GELU(), nn.Dropout(0.1), | |
| nn.Linear(dim * 2, dim), nn.GELU(), nn.Dropout(0.1)) | |
| def forward(self, x): | |
| return self.net(x) | |
| class Classifier(nn.Module): | |
| def __init__(self, input_dim, hidden_dim, num_layers, num_labels=1): | |
| super().__init__() | |
| self.input_proj = nn.Linear(input_dim, hidden_dim) if input_dim != hidden_dim else nn.Identity() | |
| self.mlp_blocks = nn.ModuleList([MLPBlock(hidden_dim) for _ in range(num_layers)]) | |
| self.layer_norms = nn.ModuleList([nn.LayerNorm(hidden_dim) for _ in range(num_layers)]) | |
| self.output_head = nn.Sequential(nn.Linear(hidden_dim, hidden_dim), nn.GELU(), | |
| nn.Dropout(0.1), nn.Linear(hidden_dim, num_labels)) | |
| def forward(self, x): | |
| x = self.input_proj(x) | |
| for mlp, ln in zip(self.mlp_blocks, self.layer_norms): | |
| x = x + mlp(ln(x)) | |
| return self.output_head(x) | |
| class DeepfakeDetector(nn.Module): | |
| def __init__(self, hidden_dim=320, classifier_layers=3, aasist_gat_dims=(128, 64)): | |
| super().__init__() | |
| self.wavlm = WavLMModel.from_pretrained("microsoft/wavlm-large") | |
| self.pool = AASISTPool(self.wavlm.config.hidden_size, self.wavlm.config.num_hidden_layers, aasist_gat_dims) | |
| self.classifier = Classifier(self.pool.output_dim, hidden_dim, classifier_layers) | |
| def forward(self, waveform): # (B, T) 16 kHz -> logit (B,); sigmoid(logit)=P(real) | |
| hs = list(self.wavlm(waveform, output_hidden_states=True).hidden_states[1:]) | |
| return self.classifier(self.pool(hs)).squeeze(-1) | |