Download pooling.py from SpeechAntiSpoofingBenchmarks/SSL-Mamba: direct link, hf CLI and curl.
- Browser
- Download file 32.5 kB
-
https://huggingface.co/SpeechAntiSpoofingBenchmarks/SSL-Mamba/resolve/main/pooling.py
- Command line
-
hf download hf://SpeechAntiSpoofingBenchmarks/SSL-Mamba/pooling.py
-
curl -L -o pooling.py https://huggingface.co/SpeechAntiSpoofingBenchmarks/SSL-Mamba/resolve/main/pooling.py
32.5 kB
| import torch | |
| import torch.nn.functional as F | |
| import torch.nn as nn | |
| class TAP(nn.Module): | |
| """ | |
| Temporal average pooling, only first-order mean is considered | |
| """ | |
| def __init__(self, in_dim=0, **kwargs): | |
| super(TAP, self).__init__() | |
| self.in_dim = in_dim | |
| def forward(self, x): | |
| pooling_mean = x.mean(dim=-1) | |
| # To be compatable with 2D input | |
| pooling_mean = pooling_mean.flatten(start_dim=1) | |
| return pooling_mean | |
| def get_out_dim(self): | |
| self.out_dim = self.in_dim | |
| return self.out_dim | |
| class StatisticsPooling(torch.nn.Module): | |
| """ An usual mean [+ stddev] poolling layer""" | |
| def __init__(self, input_dim, stddev=True, unbiased=False, eps=1.0e-10): | |
| super(StatisticsPooling, self).__init__() | |
| self.stddev = stddev | |
| self.input_dim = input_dim | |
| if self.stddev : | |
| self.output_dim = 2 * input_dim | |
| else : | |
| self.output_dim = input_dim | |
| self.eps = eps | |
| # Used for unbiased estimate of stddev | |
| self.unbiased = unbiased | |
| def forward(self, inputs, lengths:torch.Tensor = torch.ones((0),dtype=torch.long)): | |
| """ | |
| @inputs: a 3-dimensional tensor (a batch), including [samples-index, frames-dim-index, frames-index] | |
| """ | |
| assert len(inputs.shape) == 3 | |
| assert inputs.shape[1] == self.input_dim | |
| if lengths.size(0) > 0: | |
| mean = [] | |
| std = [] | |
| for i in range(inputs.shape[0]): | |
| act_len = lengths[i] | |
| act_counts = act_len | |
| mean_i = torch.mean(inputs[i,:,:act_len],dim=1,keepdim=True) | |
| mean.append(mean_i) | |
| if self.stddev : | |
| if self.unbiased and act_len > 1: | |
| act_counts = act_len - 1 | |
| var = torch.sum((inputs[i,:,:act_len]-mean_i)**2, dim=1, keepdim=True)/act_counts | |
| std.append(torch.sqrt(var.clamp(min=self.eps))) | |
| mean = torch.stack(mean) | |
| out = mean | |
| if self.stddev : | |
| std_o = torch.stack(std) | |
| out = torch.cat((mean, std_o), dim=1) | |
| else: | |
| counts = inputs.shape[2] | |
| mean = inputs.mean(dim=2, keepdim=True) | |
| out = mean | |
| if self.stddev: | |
| if self.unbiased and counts > 1: | |
| counts = counts - 1 | |
| var = torch.sum((inputs - mean)**2, dim=2, keepdim=True) / counts | |
| std = torch.sqrt(var.clamp(min=self.eps)) | |
| out = torch.cat((mean, std), dim=1) | |
| return out | |
| class GlobalMultiHeadAttentionPooling(torch.nn.Module): | |
| """Implement global multi-head attention pooling based on AttentionAlphaComponent. | |
| Reference: Zhiming Wang, Kaisheng Yao, Xiaolong Li, Shuo Fang. "MULTI-RESOLUTION MULTI-HEAD | |
| ATTENTION IN DEEP SPEAKER EMBEDDING." ICASSP, 2020. | |
| It is not equivalent to multi-head attention pooling even when | |
| input_dim of global multi-head = 1/num_head * input_dim of multi-head. | |
| """ | |
| def __init__(self, input_dim, stddev=True, stddev_attention=True, num_head=4, share=True, affine_layers=2, **options): | |
| super(GlobalMultiHeadAttentionPooling, self).__init__() | |
| self.input_dim = input_dim | |
| self.num_head = num_head | |
| self.stddev = stddev | |
| self.stddev_attention = stddev_attention | |
| if self.stddev : | |
| self.output_dim = 2 * input_dim | |
| else : | |
| self.output_dim = input_dim | |
| if "split_input" in options.keys(): | |
| if options["split_input"]: | |
| raise ValueError("split_input==True is not valid for GlobalMultiHeadAttentionPooling.") | |
| options.pop("split_input") | |
| if "temperature" in options.keys(): | |
| if options["temperature"]: | |
| raise ValueError("temperature==True is not valid for GlobalMultiHeadAttentionPooling.") | |
| options.pop("temperature") | |
| # In this pooling, the special point is that all (global) features of inputs will be used. | |
| self.attention = AttentionAlphaComponent(input_dim, num_head=num_head, split_input=False, share=share, | |
| temperature=False, affine_layers=affine_layers, bias=True, **options) | |
| def forward(self, inputs): | |
| """ | |
| @inputs: a 3-dimensional tensor (a batch), including [samples-index, frames-dim-index, frames-index] | |
| """ | |
| assert len(inputs.shape) == 3 | |
| assert inputs.shape[1] == self.input_dim | |
| batch_size = inputs.shape[0] | |
| chunk_size = inputs.shape[2] # a.k.a total frames | |
| # alpha: [batch, weight, frames] | |
| # When using the conv1d to implement the multi-multiple of multi-head, we can get | |
| # the weight distribution of multi-head: [h11, h12, h13, h21, h22, h23, ..., hn1, hn2, ...] | |
| # So, just reshape it to split different heads. | |
| alpha = self.attention(inputs) | |
| # In sharing weight case, the shape of alpha is [batch, head, 1, frames] and [batch, head, all-features, frames] | |
| # for another case. | |
| # inputs: [batch, 1, all-features, frames] | |
| after_mul = alpha.reshape(batch_size, self.num_head, -1, chunk_size) * \ | |
| inputs.reshape(batch_size, 1, -1, chunk_size) | |
| # After multi-multipling alpha and inputs for multi-head case, the mean could be got by reshaping back. | |
| mean = torch.sum(after_mul.reshape(batch_size, -1, chunk_size), dim=2, keepdim=True) | |
| if self.stddev : | |
| if self.stddev_attention: | |
| after_mul_2 = alpha.reshape(batch_size, self.num_head, -1, chunk_size) * \ | |
| inputs.reshape(batch_size, 1, -1, chunk_size)**2 | |
| var = torch.sum(after_mul_2.reshape(batch_size, -1, chunk_size), dim=2, keepdim=True) - mean**2 | |
| std = torch.sqrt(var.clamp(min=1.0e-10)) | |
| else: | |
| var = torch.mean((inputs - mean)**2, dim=2, keepdim=True) | |
| std = torch.sqrt(var.clamp(min=1.0e-10)) | |
| return torch.cat((mean, std), dim=1) | |
| else : | |
| return mean | |
| def get_output_dim(self): | |
| return self.output_dim * self.num_head | |
| class MultiResolutionMultiHeadAttentionPooling(torch.nn.Module): | |
| """Implement multi-resolution global multi-head attention pooling based on AttentionAlphaComponent. | |
| Reference: Zhiming Wang, Kaisheng Yao, Xiaolong Li, Shuo Fang. "MULTI-RESOLUTION MULTI-HEAD | |
| ATTENTION IN DEEP SPEAKER EMBEDDING." ICASSP, 2020. | |
| """ | |
| def __init__(self, input_dim, stddev=True, stddev_attention=True, num_head=4, share=True, affine_layers=2, **options): | |
| super(MultiResolutionMultiHeadAttentionPooling, self).__init__() | |
| self.input_dim = input_dim | |
| self.num_head = num_head | |
| self.stddev = stddev | |
| self.stddev_attention = stddev_attention | |
| if self.stddev : | |
| self.output_dim = 2 * input_dim | |
| else : | |
| self.output_dim = input_dim | |
| if "split_input" in options.keys(): | |
| if options["split_input"]: | |
| raise ValueError("split_input==True is not valid for MultiResolutionMultiHeadAttentionPooling.") | |
| options.pop("split_input") | |
| if "temperature" in options.keys(): | |
| if not options["temperature"]: | |
| raise ValueError("temperature==False is not valid for MultiResolutionMultiHeadAttentionPooling.") | |
| options.pop("temperature") | |
| # In this pooling, the special point is that all (global) features of inputs will be used and | |
| # the temperature will be added. | |
| self.attention = AttentionAlphaComponent(input_dim, num_head=num_head, split_input=False, temperature=True, | |
| share=share, affine_layers=affine_layers, bias=True, **options) | |
| def forward(self, inputs): | |
| """ | |
| @inputs: a 3-dimensional tensor (a batch), including [samples-index, frames-dim-index, frames-index] | |
| """ | |
| assert len(inputs.shape) == 3 | |
| assert inputs.shape[1] == self.input_dim | |
| batch_size = inputs.shape[0] | |
| chunk_size = inputs.shape[2] # a.k.a total frames | |
| # alpha: [batch, weight, frames] | |
| # When using the conv1d to implement the multi-multiple of multi-head, we can get | |
| # the weight distribution of multi-head: [h11, h12, h13, h21, h22, h23, ..., hn1, hn2, ...] | |
| # So, just reshape it to split different heads. | |
| alpha = self.attention(inputs) | |
| # In sharing weight case, the shape of alpha is [batch, head, 1, frames] and [batch, head, all-features, frames] | |
| # for another case. | |
| # inputs: [batch, 1, all-features, frames] | |
| after_mul = alpha.reshape(batch_size, self.num_head, -1, chunk_size) * \ | |
| inputs.reshape(batch_size, 1, -1, chunk_size) | |
| # After multi-multipling alpha and inputs for multi-head case, the mean could be got by reshaping back. | |
| mean = torch.sum(after_mul.reshape(batch_size, -1, chunk_size), dim=2, keepdim=True) | |
| if self.stddev : | |
| if self.stddev_attention: | |
| after_mul_2 = alpha.reshape(batch_size, self.num_head, -1, chunk_size) * \ | |
| inputs.reshape(batch_size, 1, -1, chunk_size)**2 | |
| var = torch.sum(after_mul_2.reshape(batch_size, -1, chunk_size), dim=2, keepdim=True) - mean**2 | |
| std = torch.sqrt(var.clamp(min=1.0e-10)) | |
| else: | |
| var = torch.mean((inputs - mean)**2, dim=2, keepdim=True) | |
| std = torch.sqrt(var.clamp(min=1.0e-10)) | |
| return torch.cat((mean, std), dim=1) | |
| else : | |
| return mean | |
| def get_output_dim(self): | |
| return self.output_dim * self.num_head | |
| class MQMHASP(torch.nn.Module): | |
| """ | |
| Reference: | |
| Miao Zhao, Yufeng Ma, and Yiwei Ding et al. "Multi-query multi-head attention pooling and Inter-topK penalty for speaker verification". | |
| https://arxiv.org/pdf/2110.05042.pdf | |
| """ | |
| def __init__(self, in_dim, | |
| num_q = 2, | |
| num_head=4, | |
| hidden_size=128, | |
| stddev=True, | |
| share = True, | |
| affine_layers=2, | |
| time_attention=False, | |
| norm_type = 'batch_norm', | |
| **kargs | |
| ): | |
| super(MQMHASP,self).__init__() | |
| self.stddev = stddev | |
| # self.output_dim = in_dim*2 if self.stddev else in_dim | |
| self.num_head = max(1, num_head) | |
| self.num_q = max(1, num_q) | |
| self.time_attention = time_attention | |
| assert (in_dim % num_head) == 0 | |
| att_idim = in_dim //num_head | |
| if time_attention: | |
| att_idim = (in_dim * 3) //num_head if stddev else (in_dim * 2) //num_head | |
| att_odim = 1 if share else in_dim //num_head | |
| self.attention = self.build_attention(att_idim * num_head, att_odim * num_head * num_q, num_q, num_head, affine_layers, hidden_size, norm_type = norm_type) | |
| self.out_dim = in_dim * num_q * 2 if stddev else in_dim * num_q | |
| def forward(self, x, mask: torch.Tensor = torch.ones((0, 0, 0))): | |
| """ | |
| x: input feature [B, F, T] | |
| returns: pooling statiscs [B, F * qs, 1] | |
| """ | |
| B, C ,T = x.shape | |
| if mask.size(2) == 0 : | |
| mask = torch.ones((B, 1, T)).to(x.device) | |
| if self.time_attention: | |
| total = mask.sum(dim=2, keepdim=True) # [B, *, 1] | |
| mean, std = compute_statistics(x, mask / total,stddev = self.stddev) | |
| mean = (mean.repeat(1, 1, T)).view(B, self.num_head, -1, T) | |
| x_in = x.view(B, self.num_head, -1, T) | |
| if self.stddev: | |
| std = (std.repeat(1, 1, T)).view(B, self.num_head, -1, T) | |
| x_in = torch.cat([x_in, mean, std], dim = 2) | |
| else: | |
| x_in = torch.cat([x_in, mean], dim = 2) | |
| x_in = x_in.reshape(B, -1, T) | |
| else: | |
| x_in = x | |
| alpha = self.attention(x_in) # [B, head * att_dim, T] | |
| alpha = alpha.masked_fill(mask == 0, float("-inf")) | |
| alpha = F.softmax(alpha, dim=2) | |
| alpha = alpha.reshape(B, self.num_head, self.num_q, -1, T) | |
| mean, std = compute_statistics(x.reshape(B, self.num_head, 1, -1, T), alpha,stddev = self.stddev) # mean: [B, head, q, C/head, 1] | |
| mean = mean.reshape(B, -1, 1) | |
| if self.stddev: | |
| std = std.reshape(B, -1, 1) | |
| out = torch.cat([mean, std], dim=1) | |
| else: | |
| out = mean | |
| return out | |
| def get_output_dim(self): | |
| return self.out_dim | |
| def build_attention(self, | |
| idim, | |
| odim, | |
| num_q, | |
| num_head, | |
| affine_layers=1, | |
| hidden_size=128, | |
| norm_type = 'batch_norm', | |
| ): | |
| assert affine_layers in [1 ,2], "Expected 1 or 2 affine layers." | |
| assert (idim % num_head) == 0 | |
| assert (odim % (num_head * num_q)) == 0 | |
| if affine_layers == 2: | |
| if norm_type == 'batch_norm': | |
| norm = torch.nn.BatchNorm1d(hidden_size * num_head * num_q) | |
| elif norm_type == 'layer_norm': | |
| norm = torch.nn.GroupNorm(num_head * num_q, hidden_size * num_head * num_q) | |
| else: | |
| raise ValueError("Unsupport norm type:{}".format(norm_type)) | |
| att = torch.nn.Sequential( | |
| torch.nn.Conv1d(idim, hidden_size * num_head * num_q, kernel_size=1, groups=num_head), | |
| torch.nn.ReLU(), | |
| norm, | |
| torch.nn.Tanh(), | |
| torch.nn.Conv1d(hidden_size * num_head * num_q, odim, kernel_size=1, groups=num_head * num_q) | |
| ) | |
| elif affine_layers == 1 : | |
| att = torch.nn.Sequential( | |
| torch.nn.Conv1d(idim, odim, kernel_size=1, groups=num_head), | |
| ) | |
| else: | |
| raise ValueError("Expected 1 or 2 affine layers, but got {}.".format(affine_layers)) | |
| return att | |
| def extra_repr(self): | |
| return '(stddev={stddev}, num_head={num_head}, num_q={num_q}, out_dim={out_dim}) '.format(**self.__dict__) | |
| class MQMHASP_Linear(torch.nn.Module): | |
| """ Linear version of MQMHASP means computing querys one by one, which can save memory but cost more time. | |
| Reference: | |
| Miao Zhao, Yufeng Ma, and Yiwei Ding et al. "Multi-query multi-head attention pooling and Inter-topK penalty for speaker verification". | |
| https://arxiv.org/pdf/2110.05042.pdf | |
| """ | |
| def __init__(self, in_dim, | |
| num_q = 2, | |
| stddev=True, | |
| share = True, | |
| num_head=4, | |
| affine_layers=2, | |
| hidden_size=128, | |
| **kargs | |
| ): | |
| super(MQMHASP_Linear,self).__init__() | |
| num_q = max(1, num_q) | |
| self.num_head = max(1, num_head) | |
| self.num_q = max(1, num_q) | |
| self.stddev=stddev | |
| self.querys = torch.nn.ModuleList( | |
| [ | |
| MQMHASP(in_dim, | |
| num_q = 1, | |
| hidden_size=hidden_size, | |
| stddev=stddev, | |
| share=share, | |
| num_head=num_head, | |
| affine_layers=affine_layers, | |
| **kargs) for i in range(num_q) | |
| ] | |
| ) | |
| self.out_dim = in_dim * num_q * 2 if stddev else in_dim * num_q | |
| def forward(self, x, mask: torch.Tensor = torch.ones((0, 0, 0))): | |
| """ | |
| x: input feature [B, F, T] | |
| returns: pooling statiscs [B, F * qs, 1] | |
| """ | |
| out = [] | |
| for i, layer in enumerate(self.querys): | |
| out.append(layer(x, mask)) | |
| return torch.cat(out, dim=1) | |
| def get_output_dim(self): | |
| return self.out_dim | |
| def extra_repr(self): | |
| return '(stddev={stddev}, num_head={num_head}, num_q={num_q}, out_dim={out_dim}) '.format(**self.__dict__) | |
| class AttentiveStatisticsPooling(torch.nn.Module): | |
| """An attentive statistics pooling. | |
| Reference: Okabe, Koji, Takafumi Koshinaka, and Koichi Shinoda. 2018. "Attentive Statistics Pooling | |
| for Deep Speaker Embedding." ArXiv Preprint ArXiv:1803.10963. | |
| """ | |
| def __init__( | |
| self, | |
| input_dim, | |
| affine_layers=2, | |
| hidden_size=64, | |
| context=[0], | |
| stddev=True, | |
| stddev_attention=True, | |
| eps=1.0e-10, | |
| ): | |
| super(AttentiveStatisticsPooling, self).__init__() | |
| self.stddev = stddev | |
| self.input_dim = input_dim | |
| if self.stddev: | |
| self.output_dim = 2 * input_dim | |
| else: | |
| self.output_dim = input_dim | |
| self.eps = eps | |
| self.stddev_attention = stddev_attention | |
| self.attention = AttentionAlphaComponent( | |
| input_dim, | |
| num_head=1, | |
| share=True, | |
| affine_layers=affine_layers, | |
| hidden_size=hidden_size, | |
| context=context, | |
| ) | |
| def forward(self, inputs): | |
| """ | |
| @inputs: a 3-dimensional tensor (a batch), including [samples-index, frames-dim-index, frames-index] | |
| """ | |
| assert len(inputs.shape) == 3 | |
| assert inputs.shape[1] == self.input_dim | |
| alpha = self.attention(inputs) | |
| # Weight avarage | |
| mean = torch.sum(alpha * inputs, dim=2, keepdim=True) | |
| if self.stddev: | |
| if self.stddev_attention: | |
| var = torch.sum(alpha * inputs**2, dim=2, keepdim=True) - mean**2 | |
| std = torch.sqrt(var.clamp(min=self.eps)) | |
| else: | |
| var = torch.mean((inputs - mean) ** 2, dim=2, keepdim=True) | |
| std = torch.sqrt(var.clamp(min=self.eps)) | |
| return torch.cat((mean, std), dim=1) | |
| else: | |
| return mean | |
| def get_output_dim(self): | |
| return self.output_dim | |
| class MultiHeadAttentionPooling(torch.nn.Module): | |
| """Implement multi-head attention pooling based on AttentionAlphaComponent. | |
| Reference: Safari, Pooyan, and Javier Hernando. 2019. “Self Multi-Head Attention for Speaker | |
| Recognition.” ArXiv Preprint ArXiv:1906.09890. | |
| Note, in this paper, affine_layers is default to 1, and final_dim is 1 which means the weights are shared. | |
| """ | |
| def __init__( | |
| self, | |
| input_dim, | |
| stddev=True, | |
| stddev_attention=True, | |
| num_head=4, | |
| share=True, | |
| affine_layers=1, | |
| **options | |
| ): | |
| super(MultiHeadAttentionPooling, self).__init__() | |
| self.input_dim = input_dim | |
| self.stddev = stddev | |
| self.stddev_attention = stddev_attention | |
| self.num_head = num_head | |
| if self.stddev: | |
| self.output_dim = 2 * input_dim | |
| else: | |
| self.output_dim = input_dim | |
| if "split_input" in options.keys(): | |
| if not options["split_input"]: | |
| raise ValueError( | |
| "split_input==False is not valid for this MultiHeadAttentionPooling." | |
| ) | |
| options.pop("split_input") | |
| # In this pooling, the special point is that inputs will be splited. | |
| self.attention = AttentionAlphaComponent( | |
| input_dim, | |
| num_head=num_head, | |
| split_input=True, | |
| share=share, | |
| affine_layers=affine_layers, | |
| bias=False, | |
| **options | |
| ) | |
| def forward(self, inputs): | |
| """ | |
| @inputs: a 3-dimensional tensor (a batch), including [samples-index, frames-dim-index, frames-index] | |
| """ | |
| assert len(inputs.shape) == 3 | |
| assert inputs.shape[1] == self.input_dim | |
| batch_size = inputs.shape[0] | |
| chunk_size = inputs.shape[2] # a.k.a total frames | |
| # alpha: [batch, weight, frames] | |
| # When using the conv1d to implement the multi-multiple of multi-head, we can get | |
| # the weight distribution of multi-head: [h11, h12, h13, h21, h22, h23, ..., hn1, hn2, ...] | |
| # So, just reshape it to split different heads. | |
| alpha = self.attention(inputs) | |
| # In sharing weight case, the shape of alpha is [batch, head, 1, frames] and [batch, head, splited-features, frames] | |
| # for another case. | |
| # inputs: [batch, head, splited-features, frames] | |
| after_mul = alpha.reshape( | |
| batch_size, self.num_head, -1, chunk_size | |
| ) * inputs.reshape(batch_size, self.num_head, -1, chunk_size) | |
| # After multi-multipling alpha and inputs for multi-head case, the mean could be got by reshaping back. | |
| mean = torch.sum( | |
| after_mul.reshape(batch_size, -1, chunk_size), dim=2, keepdim=True | |
| ) | |
| if self.stddev: | |
| if self.stddev_attention: | |
| after_mul_2 = ( | |
| alpha.reshape(batch_size, self.num_head, -1, chunk_size) | |
| * inputs.reshape(batch_size, self.num_head, -1, chunk_size) ** 2 | |
| ) | |
| var = ( | |
| torch.sum( | |
| after_mul_2.reshape(batch_size, -1, chunk_size), | |
| dim=2, | |
| keepdim=True, | |
| ) | |
| - mean**2 | |
| ) | |
| std = torch.sqrt(var.clamp(min=1.0e-10)) | |
| else: | |
| var = torch.mean((inputs - mean) ** 2, dim=2, keepdim=True) | |
| std = torch.sqrt(var.clamp(min=1.0e-10)) | |
| return torch.cat((mean, std), dim=1) | |
| else: | |
| return mean | |
| def get_output_dim(self): | |
| return self.output_dim | |
| class AttentionAlphaComponent(torch.nn.Module): | |
| """Compute the alpha with attention module. | |
| alpha = softmax(v'·f(w·x + b) + k) or softmax(v'·x + k) | |
| where f is relu here and bias could be lost. | |
| Support: | |
| 1. Single or Multi-head attention | |
| 2. One affine or two affine | |
| 3. Share weight (last affine = vector) or un-shared weight (last affine = matrix) | |
| 4. Self-attention or time context attention (supported by context parameter of TdnnAffine) | |
| 5. Different temperatures for different heads. | |
| """ | |
| def __init__( | |
| self, | |
| input_dim, | |
| num_head=1, | |
| split_input=True, | |
| share=True, | |
| affine_layers=2, | |
| hidden_size=64, | |
| context=[0], | |
| bias=True, | |
| temperature=False, | |
| fixed=True, | |
| ): | |
| super(AttentionAlphaComponent, self).__init__() | |
| assert num_head >= 1 | |
| # Multi-head case. | |
| if num_head > 1: | |
| if split_input: | |
| # Make sure fatures/planes with input_dim dims could be splited to num_head parts. | |
| # print("input_dim:", input_dim) | |
| assert input_dim % num_head == 0 | |
| if temperature: | |
| if fixed: | |
| t_list = [] | |
| for i in range(num_head): | |
| t_list.append([[max(1, (i // 2) * 5)]]) | |
| # shape [1, num_head, 1, 1] | |
| self.register_buffer("t", torch.tensor([t_list])) | |
| else: | |
| # Different heads have different temperature. | |
| # Use 1 + self.t**2 in forward to make sure temperature >= 1. | |
| self.t = torch.nn.Parameter(torch.zeros(1, num_head, 1, 1)) | |
| self.input_dim = input_dim | |
| self.num_head = num_head | |
| self.split_input = split_input | |
| self.share = share | |
| self.temperature = temperature | |
| self.fixed = fixed | |
| if share: | |
| # weight: [input_dim, 1] or [input_dim, hidden_size] -> [hidden_size, 1] | |
| final_dim = 1 | |
| elif split_input: | |
| # weight: [input_dim, input_dim // num_head] or [input_dim, hidden_size] -> [hidden_size, input_dim // num_head] | |
| final_dim = input_dim // num_head | |
| else: | |
| # weight: [input_dim, input_dim] or [input_dim, hidden_size] -> [hidden_size, input_dim] | |
| final_dim = input_dim | |
| first_groups = 1 | |
| last_groups = 1 | |
| if affine_layers == 1: | |
| last_affine_input_dim = input_dim | |
| # (x, 1) for global case and (x, h) for split case. | |
| if num_head > 1 and split_input: | |
| last_groups = num_head | |
| self.relu_affine = False | |
| elif affine_layers == 2: | |
| last_affine_input_dim = hidden_size * num_head | |
| if num_head > 1: | |
| # (1, h) for global case and (h, h) for split case. | |
| last_groups = num_head | |
| if split_input: | |
| first_groups = num_head | |
| # Add a relu-affine with affine_layers=2. | |
| self.relu_affine = True | |
| self.first_affine = TdnnAffine( | |
| input_dim, | |
| last_affine_input_dim, | |
| context=context, | |
| bias=bias, | |
| groups=first_groups, | |
| ) | |
| self.relu = torch.nn.ReLU(inplace=True) | |
| else: | |
| raise ValueError( | |
| "Expected 1 or 2 affine layers, but got {}.", format(affine_layers) | |
| ) | |
| self.last_affine = TdnnAffine( | |
| last_affine_input_dim, | |
| final_dim * num_head, | |
| context=context, | |
| bias=bias, | |
| groups=last_groups, | |
| ) | |
| # Dim=2 means to apply softmax in different frames-index (batch is a 3-dim tensor in this case). | |
| self.softmax = torch.nn.Softmax(dim=2) | |
| def forward(self, inputs): | |
| """ | |
| @inputs: a 3-dimensional tensor (a batch), including [samples-index, frames-dim-index, frames-index] | |
| """ | |
| assert len(inputs.shape) == 3 | |
| assert inputs.shape[1] == self.input_dim | |
| if self.temperature: | |
| batch_size = inputs.shape[0] | |
| chunk_size = inputs.shape[2] | |
| x = inputs | |
| if self.relu_affine: | |
| x = self.relu(self.first_affine(x)) | |
| if self.num_head > 1 and self.temperature: | |
| if self.fixed: | |
| t = self.t | |
| else: | |
| t = 1 + self.t**2 | |
| x = ( | |
| self.last_affine(x).reshape(batch_size, self.num_head, -1, chunk_size) | |
| / t | |
| ) | |
| return self.softmax(x.reshape(batch_size, -1, chunk_size)) | |
| else: | |
| return self.softmax(self.last_affine(x)) | |
| class TdnnAffine(torch.nn.Module): | |
| """An implemented tdnn affine component by conv1d | |
| y = splice(w * x, context) + b | |
| @input_dim: number of dims of frame <=> inputs channels of conv | |
| @output_dim: number of layer nodes <=> outputs channels of conv | |
| @context: a list of context | |
| e.g. [-2,0,2] | |
| If context is [0], then the TdnnAffine is equal to linear layer. | |
| """ | |
| def __init__( | |
| self, | |
| input_dim, | |
| output_dim, | |
| context=[0], | |
| bias=True, | |
| pad=True, | |
| stride=1, | |
| groups=1, | |
| norm_w=False, | |
| norm_f=False, | |
| ): | |
| super(TdnnAffine, self).__init__() | |
| assert input_dim % groups == 0 | |
| # Check to make sure the context sorted and has no duplicated values | |
| for index in range(0, len(context) - 1): | |
| if context[index] >= context[index + 1]: | |
| raise ValueError( | |
| "Context tuple {} is invalid, such as the order.".format(context) | |
| ) | |
| self.input_dim = input_dim | |
| self.output_dim = output_dim | |
| self.context = context | |
| self.bool_bias = bias | |
| self.pad = pad | |
| self.groups = groups | |
| self.norm_w = norm_w | |
| self.norm_f = norm_f | |
| # It is used to subsample frames with this factor | |
| self.stride = stride | |
| self.left_context = context[0] if context[0] < 0 else 0 | |
| self.right_context = context[-1] if context[-1] > 0 else 0 | |
| self.tot_context = self.right_context - self.left_context + 1 | |
| # Do not support sphereConv now. | |
| if self.tot_context > 1 and self.norm_f: | |
| self.norm_f = False | |
| print("Warning: do not support sphereConv now and set norm_f=False.") | |
| kernel_size = (self.tot_context,) | |
| self.weight = torch.nn.Parameter( | |
| torch.randn(output_dim, input_dim // groups, *kernel_size) | |
| ) | |
| # For jit compiling. | |
| # 2021-07-08 Leo | |
| self.bias = ( | |
| torch.nn.Parameter(torch.randn(output_dim)) if self.bool_bias else None | |
| ) | |
| # if self.bool_bias: | |
| # self.bias = torch.nn.Parameter(torch.randn(output_dim)) | |
| # else: | |
| # self.register_parameter('bias', None) | |
| # init weight and bias. It is important | |
| self.init_weight() | |
| # Save GPU memory for no skiping case | |
| if len(context) != self.tot_context: | |
| # Used to skip some frames index according to context | |
| self.mask = torch.tensor( | |
| [ | |
| [ | |
| [ | |
| 1 if index in context else 0 | |
| for index in range( | |
| self.left_context, self.right_context + 1 | |
| ) | |
| ] | |
| ] | |
| ] | |
| ) | |
| else: | |
| self.mask = None | |
| ## Deprecated: the broadcast method could be used to save GPU memory, | |
| # self.mask = torch.randn(output_dim, input_dim, 0) | |
| # for index in range(self.left_context, self.right_context + 1): | |
| # if index in context: | |
| # fixed_value = torch.ones(output_dim, input_dim, 1) | |
| # else: | |
| # fixed_value = torch.zeros(output_dim, input_dim, 1) | |
| # self.mask=torch.cat((self.mask, fixed_value), dim = 2) | |
| # Save GPU memory of thi case. | |
| self.selected_device = False | |
| def init_weight(self): | |
| # Note, var should be small to avoid slow-shrinking | |
| torch.nn.init.normal_(self.weight, 0.0, 0.01) | |
| if self.bias is not None: | |
| torch.nn.init.constant_(self.bias, 0.0) | |
| def forward(self, inputs: torch.Tensor) -> torch.Tensor: | |
| """ | |
| @inputs: a 3-dimensional tensor (a batch), including [samples-index, frames-dim-index, frames-index] | |
| """ | |
| assert len(inputs.shape) == 3 | |
| assert inputs.shape[1] == self.input_dim | |
| # Do not use conv1d.padding for self.left_context + self.right_context != 0 case. | |
| if self.pad: | |
| inputs = F.pad( | |
| inputs, | |
| (-self.left_context, self.right_context), | |
| mode="constant", | |
| value=0.0, | |
| ) | |
| assert inputs.shape[2] >= self.tot_context | |
| # if not self.selected_device and self.mask is not None: | |
| # # To save the CPU -> GPU moving time | |
| # # Another simple case, for a temporary tensor, jus specify the device when creating it. | |
| # self.mask = to_device(self, self.mask) | |
| # | |
| # self.selected_device = True | |
| # filters = self.weight * self.mask if self.mask is not None else self.weight | |
| # For jit compiling. | |
| # 2021-07-08 Leo | |
| if self.mask is not None: | |
| self.mask = self.mask.to(inputs.device) | |
| filters = self.weight * self.mask | |
| else: | |
| filters = self.weight | |
| if self.norm_w: | |
| filters = F.normalize(filters, dim=1) | |
| if self.norm_f: | |
| inputs = F.normalize(inputs, dim=1) | |
| outputs = F.conv1d( | |
| inputs, | |
| filters, | |
| self.bias, | |
| stride=self.stride, | |
| padding=0, | |
| dilation=1, | |
| groups=self.groups, | |
| ) | |
| return outputs | |
| def compute_statistics(x, m, dim: int = 2, stddev: bool = True, eps: float = 1e-5): | |
| mean = (m * x).sum(dim) | |
| if stddev: | |
| # std = torch.sqrt( | |
| # (m * (x - mean.unsqueeze(dim)).pow(2)).sum(dim).clamp(eps) | |
| # ) | |
| std = torch.sqrt((torch.sum(m * (x**2), dim=dim) - mean**2).clamp(eps)) | |
| else: | |
| std = torch.empty(0) | |
| return mean, std |