ba4be087d5
Create PR to main with cherry-pick from release / cherry-pick (push) Failing after 0s
CICD NeMo / pre-flight (push) Failing after 0s
CICD NeMo / configure (push) Has been skipped
Build, validate, and release Neural Modules / pre-flight (push) Failing after 1s
CICD NeMo / code-linting (push) Has been skipped
Build, validate, and release Neural Modules / release (push) Has been skipped
Build, validate, and release Neural Modules / release-summary (push) Has been cancelled
CICD NeMo / cicd-test-container-build (push) Has been cancelled
CICD NeMo / cicd-import-tests (push) Has been cancelled
CICD NeMo / L0_Setup_Test_Data_And_Models (push) Has been cancelled
CICD NeMo / cicd-main-unit-tests (push) Has been cancelled
CICD NeMo / cicd-main-speech (push) Has been cancelled
CICD NeMo / Nemo_CICD_Test (push) Has been cancelled
CICD NeMo / Coverage (e2e) (push) Has been cancelled
CICD NeMo / Coverage (unit-test) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
CICD NeMo / cicd-wait-in-queue (push) Has been cancelled
370 lines
14 KiB
Python
370 lines
14 KiB
Python
# Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
|
#
|
|
# This source code is licensed under the MIT license found in the
|
|
# LICENSE file in the root directory of this source tree.
|
|
|
|
import math
|
|
import random
|
|
from typing import Dict, List
|
|
|
|
import torch
|
|
from omegaconf import DictConfig
|
|
from omegaconf.dictconfig import DictConfig
|
|
from torch import nn
|
|
from torch.nn import functional as F
|
|
|
|
from nemo.collections.asr.modules.common.transformer.transformer_encoders_nlp import TransformerEncoder
|
|
from nemo.collections.common.parts import form_attention_mask, transformer_weights_init
|
|
from nemo.core.classes.module import NeuralModule
|
|
from nemo.core.neural_types import AcousticEncodedRepresentation, AudioSignal, LengthsType, NeuralType, SpectrogramType
|
|
|
|
|
|
class TransposeLast(torch.nn.Module):
|
|
"""
|
|
Transposes last dimension. Useful for adding to a sequential block.
|
|
"""
|
|
|
|
def forward(self, x):
|
|
return x.transpose(-2, -1)
|
|
|
|
|
|
class SamePad(torch.nn.Module):
|
|
def __init__(self, kernel_size):
|
|
super().__init__()
|
|
self.remove = kernel_size % 2 == 0
|
|
|
|
def forward(self, x):
|
|
if self.remove:
|
|
x = x[:, :, :-1]
|
|
return x
|
|
|
|
|
|
class ConvFeatureEncoder(NeuralModule):
|
|
"""
|
|
Encoder used to isolate features in raw audio for Wav2Vec style training.
|
|
Treated as preprocessor module in NeMo ASR training. Defaults values are
|
|
for base model found in Baeski et al (https://arxiv.org/abs/2006.11477),
|
|
save for use of layer normalization as default schema. (Chosen for stability.)
|
|
"""
|
|
|
|
@property
|
|
def input_types(self):
|
|
"""Returns definitions of module input ports.
|
|
input_signal:
|
|
0: AxisType(BatchTag)
|
|
1: AxisType(TimeTag)
|
|
input_signal_length:
|
|
0: AxisType(BatchTag)
|
|
Note: length is in number of samples, not seconds
|
|
"""
|
|
return {
|
|
"input_signal": NeuralType(('B', 'T'), AudioSignal(freq=self._sample_rate)),
|
|
"length": NeuralType(tuple('B'), LengthsType()),
|
|
}
|
|
|
|
@property
|
|
def output_types(self):
|
|
"""Returns definitions of module output ports.
|
|
For compatibility, processed features are treated as Spectrogram types
|
|
processed_signal:
|
|
0: AxisType(BatchTag)
|
|
1: AxisType(ChannelTag)
|
|
2: AxisType(ProcessedTimeTag)
|
|
processed_signal_length:
|
|
0: AxisType(BatchTag)
|
|
"""
|
|
return {
|
|
"processed_signal": NeuralType(('B', 'C', 'T'), SpectrogramType()),
|
|
"processed_signal_length": NeuralType(tuple('B'), LengthsType()),
|
|
}
|
|
|
|
def __init__(
|
|
self,
|
|
conv_layers: List[Dict[str, int]],
|
|
extractor_mode: str = "layer_norm",
|
|
conv_bias: bool = False,
|
|
feature_grad_mult=1.0,
|
|
normalize_audio=True,
|
|
embedding_dim=768,
|
|
):
|
|
super().__init__()
|
|
|
|
self.grad_mult = feature_grad_mult
|
|
self.normalize_input = normalize_audio
|
|
|
|
def block(
|
|
n_in,
|
|
n_out,
|
|
k,
|
|
stride,
|
|
is_layer_norm=False,
|
|
is_group_norm=False,
|
|
conv_bias=False,
|
|
):
|
|
def make_conv():
|
|
conv = nn.Conv1d(n_in, n_out, k, stride=stride, bias=conv_bias)
|
|
nn.init.kaiming_normal_(conv.weight)
|
|
return conv
|
|
|
|
assert (is_layer_norm and is_group_norm) is False, "layer norm and group norm are exclusive"
|
|
|
|
if is_layer_norm:
|
|
return nn.Sequential(
|
|
make_conv(),
|
|
nn.Sequential(TransposeLast(), nn.LayerNorm(dim, elementwise_affine=True), TransposeLast()),
|
|
nn.GELU(),
|
|
)
|
|
elif is_group_norm:
|
|
return nn.Sequential(
|
|
make_conv(),
|
|
nn.GroupNorm(dim, dim, affine=True),
|
|
nn.GELU(),
|
|
)
|
|
else:
|
|
return nn.Sequential(make_conv(), nn.GELU())
|
|
|
|
in_d = 1
|
|
self.layer_cfg = conv_layers
|
|
self.conv_layers = nn.ModuleList()
|
|
self.mode = extractor_mode
|
|
for i, cl in enumerate(conv_layers):
|
|
assert len(cl) == 3, "invalid conv definition: " + str(cl)
|
|
dim, k, stride = cl["emb_dim"], cl["kernel_size"], cl["stride"]
|
|
|
|
self.conv_layers.append(
|
|
block(
|
|
in_d,
|
|
dim,
|
|
k,
|
|
stride,
|
|
is_layer_norm=self.mode == "layer_norm",
|
|
is_group_norm=self.mode == "group_norm" and i == 0, # applied to first layer only
|
|
conv_bias=conv_bias,
|
|
)
|
|
)
|
|
in_d = dim
|
|
|
|
# Model Layers
|
|
final_conv_dim = self.layer_cfg[-1]["emb_dim"] # Select last conv output layer dimension
|
|
self.post_extract_proj = ( # To project feature encodings to transformer
|
|
nn.Linear(final_conv_dim, embedding_dim) if final_conv_dim != embedding_dim else None
|
|
)
|
|
self.layer_norm = nn.LayerNorm(embedding_dim)
|
|
|
|
def apply_layers(self, x):
|
|
for conv in self.conv_layers:
|
|
x = conv(x)
|
|
return x
|
|
|
|
def normalize(self, source, lengths):
|
|
with torch.no_grad(): # Normalizes audio source
|
|
for i in range(lengths.size(0)):
|
|
orig = source[i, : lengths[i]]
|
|
norm = F.layer_norm(orig, orig.shape)
|
|
source[i, : lengths[i]] = norm
|
|
return source
|
|
|
|
def forward(self, input_signal, length):
|
|
if self.normalize_input:
|
|
input_signal = self.normalize(input_signal, length)
|
|
|
|
# BxT -> BxCxT
|
|
processed_signal = input_signal.unsqueeze(1)
|
|
|
|
# Applies grad mult scaling
|
|
if self.grad_mult > 0:
|
|
processed_signal = self.apply_layers(processed_signal)
|
|
if self.grad_mult != 1.0:
|
|
processed_signal = GradMultiply.apply(processed_signal, self.grad_mult)
|
|
else:
|
|
with torch.no_grad(): # 0 indicates frozen feature encoder
|
|
processed_signal = self.apply_layers(processed_signal)
|
|
|
|
processed_signal = processed_signal.transpose(1, 2) # B,T,C
|
|
# Project to embedding
|
|
if self.post_extract_proj is not None:
|
|
processed_signal = self.post_extract_proj(processed_signal)
|
|
|
|
# Adding normalization for output
|
|
if self.mode == "layer_norm":
|
|
processed_signal = self.layer_norm(processed_signal)
|
|
|
|
processed_signal = processed_signal.transpose(1, 2) # B,C,T
|
|
|
|
# Feature lengths will have been changed through convolutions
|
|
processed_signal_length = self.get_lengths(audio_lengths=length)
|
|
|
|
return processed_signal, processed_signal_length
|
|
|
|
def get_lengths(self, audio_lengths):
|
|
# converts audio lengths to timestep lengths
|
|
for conv in self.layer_cfg:
|
|
kernel = conv["kernel_size"]
|
|
stride = conv["stride"]
|
|
audio_lengths = (
|
|
torch.div(audio_lengths - kernel, stride, rounding_mode='floor') + 1
|
|
) # from pytorch documentation
|
|
return audio_lengths
|
|
|
|
|
|
class Wav2VecTransformerEncoder(TransformerEncoder):
|
|
"""
|
|
Encoder module following Transformer encoder paradigm
|
|
as described in Vaswani et al. (https://arxiv.org/abs/1706.03762). Used for Wav2Vec
|
|
style encoding of context vectors as described by in Baeski et al (https://arxiv.org/abs/2006.11477).
|
|
Takes convolutional encodings of all time steps and adds to features before applying series
|
|
of self-attention layers.
|
|
|
|
Example configs may be found at: https://github.com/NVIDIA/NeMo/tree/main/examples/asr/conf/wav2vec
|
|
|
|
Args:
|
|
layer_drop: Floating point value specifying proportion of module for layer dropout (See Fan et al. https://arxiv.org/pdf/1909.11556.pdf).
|
|
If non-zero, each layer will draw from uniform probability to determine if applied in current forward call.
|
|
Occurs only during training step
|
|
pos_embed: Config specifying parameters for contextual embedding convolutions. Module configures convolutional padding
|
|
to maintain number of time steps
|
|
Must contain following:
|
|
embedding_dim: Depth/number of channels of each time step from feature encoding
|
|
conv_pos: Kernel size for convolution
|
|
conv_pos_groups: Number of groups for convolution
|
|
transformer: Config for transformer encoder. Uses self-attention layers found in: nemo.collections.nlp.modules.common.transformer
|
|
Must contain followign:
|
|
num_layers: Number of attention layers
|
|
hidden_size: Expected input depth (embedding size between model layers)
|
|
inner_size: Depth of embeddings within feed-forward sections of encoder layers
|
|
num_attention_heads: Number of attention heads
|
|
attn_score_dropout: Probability of dropout applied to attention scores
|
|
attn_layer_dropout: Probability of dropout applied to the output of the attention layers (prior to normalization)
|
|
ffn_dropout: Probability of dropout applied to feed-forward modules
|
|
hidden_act: Activation function for hidden layers
|
|
"""
|
|
|
|
def __init__(self, pos_embed: DictConfig, transformer: DictConfig, layer_drop: float = 0.0):
|
|
super().__init__(**transformer) # see nlp.collections
|
|
|
|
# positional convolutional embeddings
|
|
emb_dim = pos_embed.embedding_dim
|
|
self.pos_conv = nn.Conv1d(
|
|
emb_dim,
|
|
emb_dim,
|
|
kernel_size=pos_embed.conv_pos,
|
|
padding=pos_embed.conv_pos // 2, # Padding size preserves time step length
|
|
groups=pos_embed.conv_pos_groups,
|
|
)
|
|
|
|
self.layer_drop = layer_drop
|
|
|
|
self.dropout = transformer.attn_layer_dropout # He initialization
|
|
std = math.sqrt((4 * (1.0 - self.dropout)) / (pos_embed.conv_pos * pos_embed.embedding_dim))
|
|
nn.init.normal_(self.pos_conv.weight, mean=0, std=std)
|
|
nn.init.constant_(self.pos_conv.bias, 0)
|
|
|
|
self.pos_conv = nn.utils.weight_norm(self.pos_conv, name="weight", dim=2)
|
|
self.pos_conv = nn.Sequential(self.pos_conv, SamePad(pos_embed.conv_pos), nn.GELU())
|
|
|
|
self.layer_norm = nn.LayerNorm(emb_dim)
|
|
self.apply(lambda x: transformer_weights_init(x, xavier=False))
|
|
|
|
@property
|
|
def input_types(self):
|
|
"""Returns definitions of module output ports.
|
|
We treat features as SpectrogramType for Nemo compatibility
|
|
audio_signal:
|
|
0: AxisType(BatchTag)
|
|
1: AxisType(ChannelTag)
|
|
2: AxisType(ProcessedTimeTag)
|
|
length:
|
|
0: AxisType(BatchTag)
|
|
"""
|
|
return {
|
|
"audio_signal": NeuralType(('B', 'C', 'T'), SpectrogramType()),
|
|
"length": NeuralType(tuple('B'), LengthsType()),
|
|
}
|
|
|
|
@property
|
|
def output_types(self):
|
|
"""Returns definitions of module output ports.
|
|
We're using SpectrogramType for now to keep things Nemo safe
|
|
processed_signal:
|
|
0: AxisType(BatchTag)
|
|
1: AxisType(ChannelTag)
|
|
2: AxisType(ProcessedTimeTag)
|
|
processed_length:
|
|
0: AxisType(BatchTag)
|
|
"""
|
|
return {
|
|
"processed_signal": NeuralType(('B', 'C', 'T'), AcousticEncodedRepresentation()),
|
|
"processed_length": NeuralType(tuple('B'), LengthsType()),
|
|
}
|
|
|
|
def forward(self, audio_signal, length):
|
|
|
|
# Padding mask needed for transformer
|
|
padding_mask = self.create_padding_mask(length)
|
|
|
|
# Applying padding before convolution
|
|
for idx, len in enumerate(length):
|
|
audio_signal[idx, :, len:] = 0.0
|
|
|
|
signal_conv = self.pos_conv(audio_signal) # B, C, T
|
|
audio_signal = audio_signal + signal_conv
|
|
|
|
audio_signal = audio_signal.transpose(1, 2) # B, C, T -> B, T, C
|
|
audio_signal = self.layer_norm(audio_signal)
|
|
|
|
context_emb = self.apply_transformer(audio_signal, padding_mask=padding_mask)
|
|
|
|
context_emb = context_emb.transpose(1, 2) # B, T, C -> B, C, T
|
|
|
|
return context_emb, length # Returning length for NeMo compatibility
|
|
|
|
def apply_transformer(self, x, padding_mask=None):
|
|
encoder_attn_mask = form_attention_mask(padding_mask)
|
|
if (
|
|
self.layer_drop and self.training
|
|
): # Stochastic layer drop as in: Huang et al. https://arxiv.org/pdf/1603.09382.pdf
|
|
for _, layer in enumerate(self.layers):
|
|
p = random.random()
|
|
if p > self.layer_drop:
|
|
x = layer(x, encoder_attn_mask, x)
|
|
else:
|
|
for _, layer in enumerate(self.layers):
|
|
x = layer(x, encoder_attn_mask, x)
|
|
return x
|
|
|
|
def create_padding_mask(self, length):
|
|
# Broadcast to vectorize creating the padding mask
|
|
max_len = max(length)
|
|
padding_mask = torch.arange(max_len, device=length.device)
|
|
|
|
# Switch to binary for transformer, 1 for valid tokens, 0 for padding
|
|
padding_mask = (padding_mask.expand(len(length), max_len) < length.unsqueeze(1)).type(torch.uint8)
|
|
|
|
return padding_mask
|
|
|
|
|
|
class GradMultiply(torch.autograd.Function):
|
|
@staticmethod
|
|
def forward(ctx, x, scale):
|
|
ctx.scale = scale
|
|
res = x.new(x)
|
|
return res
|
|
|
|
@staticmethod
|
|
def backward(ctx, grad):
|
|
return grad * ctx.scale, None
|