Add causal person fusion and MuVAP stage-3 baselines
This commit is contained in:
+566
@@ -0,0 +1,566 @@
|
||||
import argparse
|
||||
from os import makedirs
|
||||
from os.path import dirname, exists, join
|
||||
from typing import List
|
||||
|
||||
import torch
|
||||
import math
|
||||
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from einops.layers.torch import Rearrange
|
||||
|
||||
"""
|
||||
Encoder should take a wave file, then return an embedding
|
||||
"""
|
||||
|
||||
|
||||
# 16 kHz / 160 in CPC's conv stack. `downsample` halves it once per block, so
|
||||
# the rate the model sees is a choice: 50 Hz is the original VAP's, 25 Hz halves
|
||||
# it again to meet Mimi on a shared grid.
|
||||
CPC_CONV_HZ = 100.0
|
||||
CPC_RATES = (100.0, 50.0, 25.0, 12.5)
|
||||
|
||||
|
||||
class CPC(nn.Module):
|
||||
def __init__(self, frame_hz=25.0):
|
||||
super().__init__()
|
||||
frame_hz = float(frame_hz)
|
||||
if frame_hz not in CPC_RATES:
|
||||
raise ValueError(
|
||||
f"cpc frame_hz must be one of {CPC_RATES} (the conv stack runs at "
|
||||
f"{CPC_CONV_HZ:g} Hz and `downsample` halves it), got {frame_hz:g}"
|
||||
)
|
||||
self.sample_rate = 16000
|
||||
self.frame_hz = frame_hz
|
||||
self.encoder = load_CPC(True)
|
||||
self.in_dim = self.encoder.gEncoder.conv4.out_channels
|
||||
self.out_dim = 256
|
||||
|
||||
halvings = round(math.log2(CPC_CONV_HZ / frame_hz))
|
||||
self.downsample = nn.Sequential(
|
||||
*[
|
||||
get_cnn_layer(
|
||||
in_dim=self.in_dim if block == 0 else self.out_dim,
|
||||
out_dim=self.out_dim,
|
||||
kernel=[3],
|
||||
stride=[2],
|
||||
dilation=[1],
|
||||
activation="GELU",
|
||||
)
|
||||
for block in range(halvings)
|
||||
]
|
||||
)
|
||||
if not halvings: # 100 Hz: still bridge the encoder width onto out_dim
|
||||
self.downsample = nn.Sequential(nn.Linear(self.in_dim, self.out_dim))
|
||||
|
||||
self.freeze()
|
||||
|
||||
def forward(self, waveform):
|
||||
if waveform.ndim < 3:
|
||||
waveform = waveform.unsqueeze(1)
|
||||
z = self.encoder.gEncoder(waveform)
|
||||
z = z.permute(0, 2, 1)
|
||||
z = self.encoder.gAR(z)
|
||||
z = self.downsample(z)
|
||||
return z
|
||||
|
||||
def freeze(self):
|
||||
for p in self.encoder.parameters():
|
||||
p.requires_grad_(False)
|
||||
|
||||
|
||||
"""
|
||||
|
||||
#############################################################
|
||||
#############################################################
|
||||
WARNING - ATTENTION - HEAR YEE HEAAAR YEEEE
|
||||
#############################################################
|
||||
#############################################################
|
||||
|
||||
Most of the code in this file are scaled down (and heavily copied) versions of
|
||||
|
||||
https://github.com/facebookresearch/CPC_audio
|
||||
|
||||
Please checkout their codebase if you are interested in CPC networks
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
torch.hub downloads to: `$HOME/.cache/torch/hub/checkpoints/`
|
||||
Explicit checkpoint path saved manually in "assets/" see CHECKPOINTS below.
|
||||
"""
|
||||
|
||||
|
||||
def repo_root():
|
||||
"""
|
||||
Returns the absolute path to the git repository
|
||||
"""
|
||||
root = dirname(__file__)
|
||||
root = dirname(root)
|
||||
return root
|
||||
|
||||
|
||||
CHECKPOINTS = {
|
||||
"cpc": join(repo_root(), "assets/checkpoints/cpc/60k_epoch4-d0f474de.pt")
|
||||
}
|
||||
NAMES = list(CHECKPOINTS.keys())
|
||||
|
||||
|
||||
class ChannelNorm(nn.Module):
|
||||
"""
|
||||
Most of the code in this file are scaled down (and heavily copied) versions of
|
||||
https://github.com/facebookresearch/CPC_audio
|
||||
"""
|
||||
|
||||
def __init__(self, numFeatures, epsilon=1e-05, affine=True):
|
||||
|
||||
super(ChannelNorm, self).__init__()
|
||||
if affine:
|
||||
self.weight = nn.parameter.Parameter(torch.Tensor(1, numFeatures, 1))
|
||||
self.bias = nn.parameter.Parameter(torch.Tensor(1, numFeatures, 1))
|
||||
else:
|
||||
self.weight = None
|
||||
self.bias = None
|
||||
self.epsilon = epsilon
|
||||
self.p = 0
|
||||
self.affine = affine
|
||||
self.reset_parameters()
|
||||
|
||||
def reset_parameters(self):
|
||||
if self.affine:
|
||||
torch.nn.init.ones_(self.weight)
|
||||
torch.nn.init.zeros_(self.bias)
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
cumMean = x.mean(dim=1, keepdim=True)
|
||||
cumVar = x.var(dim=1, keepdim=True)
|
||||
x = (x - cumMean) * torch.rsqrt(cumVar + self.epsilon)
|
||||
|
||||
if self.weight is not None:
|
||||
x = x * self.weight + self.bias
|
||||
return x
|
||||
|
||||
|
||||
class CPCEncoder(nn.Module):
|
||||
"""
|
||||
Most of the code in this file are scaled down (and heavily copied) versions of
|
||||
https://github.com/facebookresearch/CPC_audio
|
||||
"""
|
||||
|
||||
def __init__(self, sizeHidden=512, normMode="layerNorm"):
|
||||
super(CPCEncoder, self).__init__()
|
||||
normLayer = ChannelNorm
|
||||
self.dimEncoded = sizeHidden
|
||||
self.conv0 = nn.Conv1d(1, sizeHidden, 10, stride=5, padding=3)
|
||||
self.batchNorm0 = normLayer(sizeHidden)
|
||||
self.conv1 = nn.Conv1d(sizeHidden, sizeHidden, 8, stride=4, padding=2)
|
||||
self.batchNorm1 = normLayer(sizeHidden)
|
||||
self.conv2 = nn.Conv1d(sizeHidden, sizeHidden, 4, stride=2, padding=1)
|
||||
self.batchNorm2 = normLayer(sizeHidden)
|
||||
self.conv3 = nn.Conv1d(sizeHidden, sizeHidden, 4, stride=2, padding=1)
|
||||
self.batchNorm3 = normLayer(sizeHidden)
|
||||
self.conv4 = nn.Conv1d(sizeHidden, sizeHidden, 4, stride=2, padding=1)
|
||||
self.batchNorm4 = normLayer(sizeHidden)
|
||||
self.DOWNSAMPLING = 160
|
||||
|
||||
def getDimOutput(self):
|
||||
return self.conv4.out_channels
|
||||
|
||||
def forward(self, x):
|
||||
x = F.relu(self.batchNorm0(self.conv0(x)))
|
||||
x = F.relu(self.batchNorm1(self.conv1(x)))
|
||||
x = F.relu(self.batchNorm2(self.conv2(x)))
|
||||
x = F.relu(self.batchNorm3(self.conv3(x)))
|
||||
x = F.relu(self.batchNorm4(self.conv4(x)))
|
||||
return x
|
||||
|
||||
|
||||
class CPCAR(nn.Module):
|
||||
"""
|
||||
Most of the code in this file are scaled down (and heavily copied) versions of
|
||||
https://github.com/facebookresearch/CPC_audio
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, dimEncoded, dimOutput, keepHidden, nLevelsGRU, mode="GRU", reverse=False
|
||||
):
|
||||
|
||||
super(CPCAR, self).__init__()
|
||||
self.RESIDUAL_STD = 0.1
|
||||
|
||||
if mode == "LSTM":
|
||||
self.baseNet = nn.LSTM(
|
||||
dimEncoded, dimOutput, num_layers=nLevelsGRU, batch_first=True
|
||||
)
|
||||
elif mode == "RNN":
|
||||
self.baseNet = nn.RNN(
|
||||
dimEncoded, dimOutput, num_layers=nLevelsGRU, batch_first=True
|
||||
)
|
||||
else:
|
||||
self.baseNet = nn.GRU(
|
||||
dimEncoded, dimOutput, num_layers=nLevelsGRU, batch_first=True
|
||||
)
|
||||
|
||||
self.hidden = None
|
||||
self.keepHidden = keepHidden
|
||||
self.reverse = reverse
|
||||
|
||||
def getDimOutput(self):
|
||||
return self.baseNet.hidden_size
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
if self.reverse:
|
||||
x = torch.flip(x, [1])
|
||||
try:
|
||||
self.baseNet.flatten_parameters()
|
||||
except RuntimeError:
|
||||
pass
|
||||
x, h = self.baseNet(x, self.hidden)
|
||||
if self.keepHidden:
|
||||
if isinstance(h, tuple):
|
||||
self.hidden = tuple(x.detach() for x in h)
|
||||
else:
|
||||
self.hidden = h.detach()
|
||||
|
||||
# For better modularity, a sequence's order should be preserved
|
||||
# by each module
|
||||
if self.reverse:
|
||||
x = torch.flip(x, [1])
|
||||
return x
|
||||
|
||||
|
||||
class CPCModel(nn.Module):
|
||||
"""
|
||||
Most of the code in this file are scaled down (and heavily copied) versions of
|
||||
https://github.com/facebookresearch/CPC_audio
|
||||
"""
|
||||
|
||||
def __init__(self, encoder, AR):
|
||||
super(CPCModel, self).__init__()
|
||||
self.gEncoder = encoder
|
||||
self.gAR = AR
|
||||
|
||||
def forward(self, batchData, label):
|
||||
encodedData = self.gEncoder(batchData).permute(0, 2, 1)
|
||||
cFeature = self.gAR(encodedData)
|
||||
return cFeature, encodedData, label
|
||||
|
||||
|
||||
def load_CPC(load_state_dict=True):
|
||||
"""
|
||||
Contrast predictive learning model for audio data
|
||||
pretrained: if True, load a model trained on libri-light 60k
|
||||
(https://arxiv.org/abs/1912.07875)
|
||||
**kwargs : see cpc/cpc_default_config to get the list of possible arguments
|
||||
|
||||
Most of the code in this file are scaled down (and heavily copied) versions of
|
||||
https://github.com/facebookresearch/CPC_audio
|
||||
"""
|
||||
|
||||
def loadArgs(args, locArgs, forbiddenAttr=None):
|
||||
for k, v in vars(locArgs).items():
|
||||
if forbiddenAttr is not None:
|
||||
if k not in forbiddenAttr:
|
||||
setattr(args, k, v)
|
||||
else:
|
||||
setattr(args, k, v)
|
||||
|
||||
def get_default_cpc_config():
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
# Run parameters
|
||||
group = parser.add_argument_group(
|
||||
"Architecture configuration",
|
||||
description="The arguments defining the model's architecture.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--hiddenEncoder",
|
||||
type=int,
|
||||
default=256,
|
||||
help="Hidden dimension of the encoder network.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--hiddenGar",
|
||||
type=int,
|
||||
default=256,
|
||||
help="Hidden dimension of the auto-regressive network",
|
||||
)
|
||||
group.add_argument(
|
||||
"--nPredicts", type=int, default=12, help="Number of steps to predict."
|
||||
)
|
||||
group.add_argument(
|
||||
"--negativeSamplingExt",
|
||||
type=int,
|
||||
default=128,
|
||||
help="Number of negative samples to take.",
|
||||
)
|
||||
group.add_argument("--learningRate", type=float, default=2e-4)
|
||||
group.add_argument(
|
||||
"--schedulerStep",
|
||||
type=int,
|
||||
default=-1,
|
||||
help="Step of the learning rate scheduler: at each "
|
||||
"step the learning rate is divided by 2. Default: "
|
||||
"no scheduler.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--schedulerRamp",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Enable a warm up phase for the learning rate: "
|
||||
"adds a linear ramp of the given size.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--beta1",
|
||||
type=float,
|
||||
default=0.9,
|
||||
help="Value of beta1 for the Adam optimizer",
|
||||
)
|
||||
group.add_argument(
|
||||
"--beta2",
|
||||
type=float,
|
||||
default=0.999,
|
||||
help="Value of beta2 for the Adam optimizer",
|
||||
)
|
||||
group.add_argument(
|
||||
"--epsilon",
|
||||
type=float,
|
||||
default=1e-08,
|
||||
help="Value of epsilon for the Adam optimizer",
|
||||
)
|
||||
group.add_argument(
|
||||
"--sizeWindow",
|
||||
type=int,
|
||||
default=20480,
|
||||
help="Number of frames to consider at each batch.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--nEpoch", type=int, default=200, help="Number of epoch to run"
|
||||
)
|
||||
group.add_argument(
|
||||
"--samplingType",
|
||||
type=str,
|
||||
default="samespeaker",
|
||||
choices=["samespeaker", "uniform", "samesequence", "sequential"],
|
||||
help="How to sample the negative examples in the CPC loss.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--nLevelsPhone",
|
||||
type=int,
|
||||
default=1,
|
||||
help="(Supervised mode only). Number of layers in "
|
||||
"the phone classification network.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--cpc_mode",
|
||||
type=str,
|
||||
default=None,
|
||||
choices=["reverse", "none"],
|
||||
help="Some variations on CPC.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--encoder_type",
|
||||
type=str,
|
||||
choices=["cpc", "mfcc", "lfb"],
|
||||
default="cpc",
|
||||
help="Replace the encoder network by mfcc features or learned filter banks",
|
||||
)
|
||||
group.add_argument(
|
||||
"--normMode",
|
||||
type=str,
|
||||
default="layerNorm",
|
||||
choices=["instanceNorm", "ID", "layerNorm", "batchNorm"],
|
||||
help="Type of normalization to use in the encoder "
|
||||
"network (default is layerNorm).",
|
||||
)
|
||||
group.add_argument(
|
||||
"--onEncoder",
|
||||
action="store_true",
|
||||
help="(Supervised mode only) Perform the "
|
||||
"classification on the encoder's output.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--random_seed", type=int, default=None, help="Set a specific random seed."
|
||||
)
|
||||
group.add_argument(
|
||||
"--speakerEmbedding",
|
||||
type=int,
|
||||
default=0,
|
||||
help="(Depreciated) Feed the prediction network with "
|
||||
"speaker embeddings along with the usual sequence.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--arMode",
|
||||
default="LSTM",
|
||||
choices=["GRU", "LSTM", "RNN", "no_ar", "transformer"],
|
||||
help="Architecture to use for the auto-regressive "
|
||||
"network (default is lstm).",
|
||||
)
|
||||
group.add_argument(
|
||||
"--nLevelsGRU",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Number of layers in the autoregressive network.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--rnnMode",
|
||||
type=str,
|
||||
default="transformer",
|
||||
choices=[
|
||||
"transformer",
|
||||
"RNN",
|
||||
"LSTM",
|
||||
"linear",
|
||||
"ffd",
|
||||
"conv4",
|
||||
"conv8",
|
||||
"conv12",
|
||||
],
|
||||
help="Architecture to use for the prediction network",
|
||||
)
|
||||
group.add_argument(
|
||||
"--dropout",
|
||||
action="store_true",
|
||||
help="Add a dropout layer at the output of the prediction network.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--abspos",
|
||||
action="store_true",
|
||||
help="If the prediction network is a transformer, "
|
||||
"active to use absolute coordinates.",
|
||||
)
|
||||
return parser.parse_args([])
|
||||
|
||||
# from cpc.model import CPCModel as cpcmodel
|
||||
# from cpc.cpc_default_config import get_default_cpc_config
|
||||
# from cpc.feature_loader import getEncoder, getAR, loadArgs
|
||||
# from cpc.feature_loader import loadArgs
|
||||
|
||||
locArgs = get_default_cpc_config()
|
||||
if exists(CHECKPOINTS["cpc"]):
|
||||
checkpoint = torch.load(
|
||||
CHECKPOINTS["cpc"], map_location="cpu", weights_only=True
|
||||
)
|
||||
else:
|
||||
checkpoint_url = "https://dl.fbaipublicfiles.com/librilight/CPC_checkpoints/60k_epoch4-d0f474de.pt"
|
||||
checkpoint = torch.hub.load_state_dict_from_url(
|
||||
checkpoint_url, progress=False, map_location="cpu"
|
||||
)
|
||||
makedirs(dirname(CHECKPOINTS["cpc"]), exist_ok=True)
|
||||
torch.save(checkpoint, CHECKPOINTS["cpc"])
|
||||
loadArgs(locArgs, argparse.Namespace(**checkpoint["config"]))
|
||||
# encoderNet = getEncoder(locArgs)
|
||||
encoderNet = CPCEncoder(locArgs.hiddenEncoder, locArgs.normMode)
|
||||
# arNet = getAR(locArgs)
|
||||
arNet = CPCAR(
|
||||
locArgs.hiddenEncoder,
|
||||
locArgs.hiddenGar,
|
||||
locArgs.samplingType == "sequential",
|
||||
locArgs.nLevelsGRU,
|
||||
mode=locArgs.arMode,
|
||||
reverse=locArgs.cpc_mode == "reverse",
|
||||
)
|
||||
# model = cpcmodel(encoderNet, arNet)
|
||||
model = CPCModel(encoderNet, arNet)
|
||||
|
||||
# always load pretrained
|
||||
if load_state_dict:
|
||||
model.load_state_dict(checkpoint["weights"], strict=False)
|
||||
model.name = "cpc"
|
||||
return model
|
||||
|
||||
|
||||
class LayerNorm(nn.Module):
|
||||
def __init__(self, dim: int, rearrange_outputs: bool = True) -> None:
|
||||
super().__init__()
|
||||
self.ln = nn.LayerNorm(dim)
|
||||
self.in_rearrange = Rearrange("b d t -> b t d")
|
||||
if rearrange_outputs:
|
||||
self.out_rearrange = Rearrange("b t d -> b d t")
|
||||
else:
|
||||
self.out_rearrange = nn.Identity()
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.ln)
|
||||
|
||||
def forward(self, x):
|
||||
return self.out_rearrange(self.ln(self.in_rearrange(x)))
|
||||
|
||||
|
||||
class CConv1d(nn.Conv1d):
|
||||
"""source: https://github.com/pytorch/pytorch/issues/1333"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size,
|
||||
stride=1,
|
||||
dilation=1,
|
||||
groups=1,
|
||||
padding_value=0,
|
||||
bias=True,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
dilation=dilation,
|
||||
groups=groups,
|
||||
bias=bias,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
ks = kernel_size if isinstance(kernel_size, int) else kernel_size[0]
|
||||
pad_dim1_pre = ks - 1
|
||||
pad_dim1_post = 0
|
||||
if dilation > 0:
|
||||
pad_dim1_pre *= dilation
|
||||
pad = (pad_dim1_pre, pad_dim1_post)
|
||||
self.pad = nn.ConstantPad1d(padding=pad, value=padding_value)
|
||||
|
||||
def debug_weights(self, type="sum"):
|
||||
w = 1.0
|
||||
if type == "mean":
|
||||
w = 1.0 / self.kernel_size[0]
|
||||
|
||||
elif type == "range":
|
||||
k = self.kernel_size[0]
|
||||
w = torch.arange(1, k + 1).float().pow(2)
|
||||
w = w.repeat(self.out_channels, self.in_channels, 1)
|
||||
self.weight.data = w
|
||||
if self.bias:
|
||||
self.bias.data = self.bias.data.fill_(0.0)
|
||||
return None
|
||||
|
||||
self.weight.data = self.weight.data.fill_(w)
|
||||
if self.bias:
|
||||
self.bias.data = self.bias.data.fill_(0.0)
|
||||
|
||||
def forward(self, input):
|
||||
return super().forward(self.pad(input))
|
||||
|
||||
|
||||
def get_cnn_layer(
|
||||
in_dim: int,
|
||||
out_dim: int,
|
||||
kernel: List[int] = [3],
|
||||
stride: List[int] = [2],
|
||||
dilation: List[int] = [1],
|
||||
padding: List[int] = [0],
|
||||
activation: str = "GELU",
|
||||
):
|
||||
layers = []
|
||||
layers.append(Rearrange("b t d -> b d t"))
|
||||
for k, s, d, p in zip(kernel, stride, dilation, padding):
|
||||
layers.append(
|
||||
CConv1d(in_dim, out_dim, kernel_size=k, stride=s, dilation=d, padding=p)
|
||||
)
|
||||
layers.append(LayerNorm(out_dim))
|
||||
if activation is not None:
|
||||
layers.append(getattr(torch.nn, activation)())
|
||||
layers.append(Rearrange("b d t -> b t d"))
|
||||
return nn.Sequential(*layers)
|
||||
Reference in New Issue
Block a user