227 lines
8.0 KiB
Python
227 lines
8.0 KiB
Python
|
import torch
|
||
|
import torch.nn.functional as F
|
||
|
from torch import nn
|
||
|
|
||
|
|
||
|
# adapted from https://github.com/cvqluu/GE2E-Loss
|
||
|
class GE2ELoss(nn.Module):
|
||
|
def __init__(self, init_w=10.0, init_b=-5.0, loss_method="softmax"):
|
||
|
"""
|
||
|
Implementation of the Generalized End-to-End loss defined in https://arxiv.org/abs/1710.10467 [1]
|
||
|
Accepts an input of size (N, M, D)
|
||
|
where N is the number of speakers in the batch,
|
||
|
M is the number of utterances per speaker,
|
||
|
and D is the dimensionality of the embedding vector (e.g. d-vector)
|
||
|
Args:
|
||
|
- init_w (float): defines the initial value of w in Equation (5) of [1]
|
||
|
- init_b (float): definies the initial value of b in Equation (5) of [1]
|
||
|
"""
|
||
|
super().__init__()
|
||
|
# pylint: disable=E1102
|
||
|
self.w = nn.Parameter(torch.tensor(init_w))
|
||
|
# pylint: disable=E1102
|
||
|
self.b = nn.Parameter(torch.tensor(init_b))
|
||
|
self.loss_method = loss_method
|
||
|
|
||
|
print(" > Initialized Generalized End-to-End loss")
|
||
|
|
||
|
assert self.loss_method in ["softmax", "contrast"]
|
||
|
|
||
|
if self.loss_method == "softmax":
|
||
|
self.embed_loss = self.embed_loss_softmax
|
||
|
if self.loss_method == "contrast":
|
||
|
self.embed_loss = self.embed_loss_contrast
|
||
|
|
||
|
# pylint: disable=R0201
|
||
|
def calc_new_centroids(self, dvecs, centroids, spkr, utt):
|
||
|
"""
|
||
|
Calculates the new centroids excluding the reference utterance
|
||
|
"""
|
||
|
excl = torch.cat((dvecs[spkr, :utt], dvecs[spkr, utt + 1 :]))
|
||
|
excl = torch.mean(excl, 0)
|
||
|
new_centroids = []
|
||
|
for i, centroid in enumerate(centroids):
|
||
|
if i == spkr:
|
||
|
new_centroids.append(excl)
|
||
|
else:
|
||
|
new_centroids.append(centroid)
|
||
|
return torch.stack(new_centroids)
|
||
|
|
||
|
def calc_cosine_sim(self, dvecs, centroids):
|
||
|
"""
|
||
|
Make the cosine similarity matrix with dims (N,M,N)
|
||
|
"""
|
||
|
cos_sim_matrix = []
|
||
|
for spkr_idx, speaker in enumerate(dvecs):
|
||
|
cs_row = []
|
||
|
for utt_idx, utterance in enumerate(speaker):
|
||
|
new_centroids = self.calc_new_centroids(dvecs, centroids, spkr_idx, utt_idx)
|
||
|
# vector based cosine similarity for speed
|
||
|
cs_row.append(
|
||
|
torch.clamp(
|
||
|
torch.mm(
|
||
|
utterance.unsqueeze(1).transpose(0, 1),
|
||
|
new_centroids.transpose(0, 1),
|
||
|
)
|
||
|
/ (torch.norm(utterance) * torch.norm(new_centroids, dim=1)),
|
||
|
1e-6,
|
||
|
)
|
||
|
)
|
||
|
cs_row = torch.cat(cs_row, dim=0)
|
||
|
cos_sim_matrix.append(cs_row)
|
||
|
return torch.stack(cos_sim_matrix)
|
||
|
|
||
|
# pylint: disable=R0201
|
||
|
def embed_loss_softmax(self, dvecs, cos_sim_matrix):
|
||
|
"""
|
||
|
Calculates the loss on each embedding $L(e_{ji})$ by taking softmax
|
||
|
"""
|
||
|
N, M, _ = dvecs.shape
|
||
|
L = []
|
||
|
for j in range(N):
|
||
|
L_row = []
|
||
|
for i in range(M):
|
||
|
L_row.append(-F.log_softmax(cos_sim_matrix[j, i], 0)[j])
|
||
|
L_row = torch.stack(L_row)
|
||
|
L.append(L_row)
|
||
|
return torch.stack(L)
|
||
|
|
||
|
# pylint: disable=R0201
|
||
|
def embed_loss_contrast(self, dvecs, cos_sim_matrix):
|
||
|
"""
|
||
|
Calculates the loss on each embedding $L(e_{ji})$ by contrast loss with closest centroid
|
||
|
"""
|
||
|
N, M, _ = dvecs.shape
|
||
|
L = []
|
||
|
for j in range(N):
|
||
|
L_row = []
|
||
|
for i in range(M):
|
||
|
centroids_sigmoids = torch.sigmoid(cos_sim_matrix[j, i])
|
||
|
excl_centroids_sigmoids = torch.cat((centroids_sigmoids[:j], centroids_sigmoids[j + 1 :]))
|
||
|
L_row.append(1.0 - torch.sigmoid(cos_sim_matrix[j, i, j]) + torch.max(excl_centroids_sigmoids))
|
||
|
L_row = torch.stack(L_row)
|
||
|
L.append(L_row)
|
||
|
return torch.stack(L)
|
||
|
|
||
|
def forward(self, x, _label=None):
|
||
|
"""
|
||
|
Calculates the GE2E loss for an input of dimensions (num_speakers, num_utts_per_speaker, dvec_feats)
|
||
|
"""
|
||
|
|
||
|
assert x.size()[1] >= 2
|
||
|
|
||
|
centroids = torch.mean(x, 1)
|
||
|
cos_sim_matrix = self.calc_cosine_sim(x, centroids)
|
||
|
torch.clamp(self.w, 1e-6)
|
||
|
cos_sim_matrix = self.w * cos_sim_matrix + self.b
|
||
|
L = self.embed_loss(x, cos_sim_matrix)
|
||
|
return L.mean()
|
||
|
|
||
|
|
||
|
# adapted from https://github.com/clovaai/voxceleb_trainer/blob/master/loss/angleproto.py
|
||
|
class AngleProtoLoss(nn.Module):
|
||
|
"""
|
||
|
Implementation of the Angular Prototypical loss defined in https://arxiv.org/abs/2003.11982
|
||
|
Accepts an input of size (N, M, D)
|
||
|
where N is the number of speakers in the batch,
|
||
|
M is the number of utterances per speaker,
|
||
|
and D is the dimensionality of the embedding vector
|
||
|
Args:
|
||
|
- init_w (float): defines the initial value of w
|
||
|
- init_b (float): definies the initial value of b
|
||
|
"""
|
||
|
|
||
|
def __init__(self, init_w=10.0, init_b=-5.0):
|
||
|
super().__init__()
|
||
|
# pylint: disable=E1102
|
||
|
self.w = nn.Parameter(torch.tensor(init_w))
|
||
|
# pylint: disable=E1102
|
||
|
self.b = nn.Parameter(torch.tensor(init_b))
|
||
|
self.criterion = torch.nn.CrossEntropyLoss()
|
||
|
|
||
|
print(" > Initialized Angular Prototypical loss")
|
||
|
|
||
|
def forward(self, x, _label=None):
|
||
|
"""
|
||
|
Calculates the AngleProto loss for an input of dimensions (num_speakers, num_utts_per_speaker, dvec_feats)
|
||
|
"""
|
||
|
|
||
|
assert x.size()[1] >= 2
|
||
|
|
||
|
out_anchor = torch.mean(x[:, 1:, :], 1)
|
||
|
out_positive = x[:, 0, :]
|
||
|
num_speakers = out_anchor.size()[0]
|
||
|
|
||
|
cos_sim_matrix = F.cosine_similarity(
|
||
|
out_positive.unsqueeze(-1).expand(-1, -1, num_speakers),
|
||
|
out_anchor.unsqueeze(-1).expand(-1, -1, num_speakers).transpose(0, 2),
|
||
|
)
|
||
|
torch.clamp(self.w, 1e-6)
|
||
|
cos_sim_matrix = cos_sim_matrix * self.w + self.b
|
||
|
label = torch.arange(num_speakers).to(cos_sim_matrix.device)
|
||
|
L = self.criterion(cos_sim_matrix, label)
|
||
|
return L
|
||
|
|
||
|
|
||
|
class SoftmaxLoss(nn.Module):
|
||
|
"""
|
||
|
Implementation of the Softmax loss as defined in https://arxiv.org/abs/2003.11982
|
||
|
Args:
|
||
|
- embedding_dim (float): speaker embedding dim
|
||
|
- n_speakers (float): number of speakers
|
||
|
"""
|
||
|
|
||
|
def __init__(self, embedding_dim, n_speakers):
|
||
|
super().__init__()
|
||
|
|
||
|
self.criterion = torch.nn.CrossEntropyLoss()
|
||
|
self.fc = nn.Linear(embedding_dim, n_speakers)
|
||
|
|
||
|
print("Initialised Softmax Loss")
|
||
|
|
||
|
def forward(self, x, label=None):
|
||
|
# reshape for compatibility
|
||
|
x = x.reshape(-1, x.size()[-1])
|
||
|
label = label.reshape(-1)
|
||
|
|
||
|
x = self.fc(x)
|
||
|
L = self.criterion(x, label)
|
||
|
|
||
|
return L
|
||
|
|
||
|
def inference(self, embedding):
|
||
|
x = self.fc(embedding)
|
||
|
activations = torch.nn.functional.softmax(x, dim=1).squeeze(0)
|
||
|
class_id = torch.argmax(activations)
|
||
|
return class_id
|
||
|
|
||
|
|
||
|
class SoftmaxAngleProtoLoss(nn.Module):
|
||
|
"""
|
||
|
Implementation of the Softmax AnglePrototypical loss as defined in https://arxiv.org/abs/2009.14153
|
||
|
Args:
|
||
|
- embedding_dim (float): speaker embedding dim
|
||
|
- n_speakers (float): number of speakers
|
||
|
- init_w (float): defines the initial value of w
|
||
|
- init_b (float): definies the initial value of b
|
||
|
"""
|
||
|
|
||
|
def __init__(self, embedding_dim, n_speakers, init_w=10.0, init_b=-5.0):
|
||
|
super().__init__()
|
||
|
|
||
|
self.softmax = SoftmaxLoss(embedding_dim, n_speakers)
|
||
|
self.angleproto = AngleProtoLoss(init_w, init_b)
|
||
|
|
||
|
print("Initialised SoftmaxAnglePrototypical Loss")
|
||
|
|
||
|
def forward(self, x, label=None):
|
||
|
"""
|
||
|
Calculates the SoftmaxAnglePrototypical loss for an input of dimensions (num_speakers, num_utts_per_speaker, dvec_feats)
|
||
|
"""
|
||
|
|
||
|
Lp = self.angleproto(x)
|
||
|
|
||
|
Ls = self.softmax(x, label)
|
||
|
|
||
|
return Ls + Lp
|