# -*- coding: utf-8 -*-
#
# This file is part of SIDEKIT.
#
# SIDEKIT is a python package for speaker verification.
# Home page: http://www-lium.univ-lemans.fr/sidekit/
#
# SIDEKIT is a python package for speaker verification.
# Home page: http://www-lium.univ-lemans.fr/sidekit/
#
# SIDEKIT is free software: you can redistribute it and/or modify
# it under the terms of the GNU LLesser General Public License as
# published by the Free Software Foundation, either version 3 of the License,
# or (at your option) any later version.
#
# SIDEKIT is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with SIDEKIT. If not, see <http://www.gnu.org/licenses/>.
__license__ = "LGPL"
__author__ = "Anthony Larcher"
__copyright__ = "Copyright 2014-2015 Anthony Larcher"
__license__ = "LGPL"
__version__ = "1.0"
__maintainer__ = "Anthony Larcher"
__email__ = "anthony.larcher@univ-lemans.fr"
__status__ = "Production"
__docformat__ = 'reStructuredText'
#import os
import numpy as np
import scipy as sp
from sidekit.bosaris import Ndx
from sidekit.bosaris import Scores
from sidekit.statserver import StatServer
import logging
[docs]def cosine_scoring(enroll, test, ndx, wccn = None):
"""Compute the cosine similarities between to sets of vectors. The list of
trials to perform is given in an Ndx object.
:param enroll: a StatServer in which stat1 are i-vectors
:param test: a StatServer in which stat1 are i-vectors
:param ndx: an Ndx object defining the list of trials to perform
:return: a score object
"""
assert isinstance(enroll, StatServer), \
'First parameter should be a StatServer'
assert isinstance(test, StatServer),\
'Second parameter should be a StatServer'
assert isinstance(ndx, Ndx),\
'Third parameter should be an Ndx'
# Remove missing models and test segments
clean_ndx = ndx.filter(enroll.modelset , test.segset, True)
# Align StatServers to match the clean_ndx
enroll.align_models(clean_ndx.modelset)
test.align_segments(clean_ndx.segset)
if wccn is not None:
enroll.rotate_stat1(wccn)
if enroll != test:
test.rotate_stat1(wccn)
# Cosine scoring
enroll.norm_stat1()
if enroll != test:
test.norm_stat1()
S = np.dot(enroll.stat1, test.stat1.transpose())
Score = Scores()
Score.scoremat = S
Score.modelset = clean_ndx.modelset
Score.segset = clean_ndx.segset
Score.scoremask = clean_ndx.trialmask
return Score
[docs]def mahalanobis_scoring(enroll, test, ndx, M):
"""Compute the mahalanobis distance between to sets of vectors. The list of
trials to perform is given in an Ndx object.
:param enroll: a StatServer in which stat1 are i-vectors
:parm test: a StatServer in which stat1 are i-vectors
:param ndx: an Ndx object defining the list of trials to perform
:param M: mahalanobis matrix as a ndarray
:return: a score object
"""
assert isinstance(enroll, StatServer), \
'First parameter should be a StatServer'
assert isinstance(test, StatServer),\
'Second parameter should be a StatServer'
assert isinstance(ndx, Ndx),\
'Third parameter should be an Ndx'
assert enroll.stat1.shape[1] == test.stat1.shape[1], \
'I-vectors dimension mismatch'
assert enroll.stat1.shape[1] == M.shape[0], \
'I-vectors and Mahalanobis matrix dimension mismatch'
# Remove missing models and test segments
clean_ndx = ndx.filter(enroll.modelset , test.segset, True)
# Align StatServers to match the clean_ndx
enroll.align_models(clean_ndx.modelset)
test.align_segments(clean_ndx.segset)
# Mahalanobis scoring
S = np.zeros((enroll.modelset.shape[0], test.segset.shape[0]))
for i in range(enroll.modelset.shape[0]):
diff = enroll.stat1[i, :] - test.stat1
S[i, :] = -0.5 * np.sum(np.dot(diff, M) * diff,axis=1)
score = Scores()
score.scoremat = S
score.modelset = clean_ndx.modelset
score.segset = clean_ndx.segset
score.scoremask = clean_ndx.trialmask
return score
[docs]def two_covariance_scoring(enroll, test, ndx, W, B):
"""Compute the 2-covariance scores between to sets of vectors. The list of
trials to perform is given in an Ndx object. Within and between class
co-variance matrices have to be pre-computed.
:param enroll: a StatServer in which stat1 are i-vectors
:param test: a StatServer in which stat1 are i-vectors
:param ndx: an Ndx object defining the list of trials to perform
:param W: the within-class co-variance matrix to consider
:param B: the between-class co-variance matrix to consider
:return: a score object
"""
assert isinstance(enroll, StatServer), \
'First parameter should be a directory'
assert isinstance(test, StatServer),\
'Second parameter should be a StatServer'
assert isinstance(ndx, Ndx),\
'Third parameter should be an Ndx'
assert enroll.stat1.shape[1] == test.stat1.shape[1], \
'I-vectors dimension mismatch'
assert enroll.stat1.shape[1] == W.shape[0], \
'I-vectors and co-variance matrix dimension mismatch'
assert enroll.stat1.shape[1] == B.shape[0], \
'I-vectors and co-variance matrix dimension mismatch'
# Remove missing models and test segments
clean_ndx = ndx.filter(enroll.modelset , test.segset, True)
# Align StatServers to match the clean_ndx
enroll.align_models(clean_ndx.modelset)
test.align_segments(clean_ndx.segset)
# Two covariance scoring scoring
S = np.zeros((enroll.modelset.shape[0], test.segset.shape[0]))
iW = sp.linalg.inv(W)
iB = sp.linalg.inv(B)
G = reduce(np.dot, [iW, sp.linalg.inv(iB + 2*iW), iW])
H = reduce(np.dot, [iW, sp.linalg.inv(iB + iW), iW])
s2 = np.sum(np.dot(enroll.stat1, H) * enroll.stat1 ,axis=1)
s3 = np.sum(np.dot(test.stat1, H) * test.stat1 , axis=1)
for ii in range(enroll.modelset.shape[0]):
A = enroll.stat1[ii, :] + test.stat1
s1 = np.sum(np.dot(A, G) * A ,axis=1)
S[ii, :] = s1 - s3 - s2[ii]
score = Scores()
score.scoremat = S
score.modelset = clean_ndx.modelset
score.segset = clean_ndx.segset
score.scoremask = clean_ndx.trialmask
return score
[docs]def PLDA_scoring(enroll, test, ndx, mu, F, G, Sigma):
"""Compute the PLDA scores between to sets of vectors. The list of
trials to perform is given in an Ndx object. PLDA matrices have to be
pre-computed. i-vectors are supposed to be whitened before.
:param enroll: a StatServer in which stat1 are i-vectors
:param test: a StatServer in which stat1 are i-vectors
:param ndx: an Ndx object defining the list of trials to perform
:param mu: the mean vector of the PLDA gaussian
:param F: the between-class co-variance matrix of the PLDA
:param G: the within-class co-variance matrix of the PLDA
:param Sigma: the residual covariance matrix
:return: a score object
"""
assert isinstance(enroll, StatServer), \
'First parameter should be a StatServer'
assert isinstance(test, StatServer),\
'Second parameter should be a StatServer'
assert isinstance(ndx, Ndx),\
'Third parameter should be an Ndx'
assert enroll.stat1.shape[1] == test.stat1.shape[1], \
'I-vectors dimension mismatch'
assert enroll.stat1.shape[1] == F.shape[0], \
'I-vectors and co-variance matrix dimension mismatch'
assert enroll.stat1.shape[1] == G.shape[0], \
'I-vectors and co-variance matrix dimension mismatch'
# Remove missing models and test segments
clean_ndx = ndx.filter(enroll.modelset , test.segset, True)
# Align StatServers to match the clean_ndx
enroll.align_models(clean_ndx.modelset)
test.align_segments(clean_ndx.segset)
# Center the i-vectors around the PLDA mean
enroll.center_stat1(mu)
test.center_stat1(mu)
# If models are not unique, compute the mean per model, display a warning
if not np.unique(enroll.modelset).shape == enroll.modelset.shape:
logging.warning("Enrollment models are not unique, average i-vectors")
enroll = enroll.mean_stat_per_model()
# Compute temporary matrices
invSigma = np.linalg.inv(Sigma)
I_iv = np.eye(mu.shape[0], dtype='float')
I_ch = np.eye(G.shape[1], dtype='float')
I_spk = np.eye(F.shape[1], dtype='float')
A = np.linalg.inv(G.T.dot(invSigma).dot(G) + I_ch)
B = F.T.dot(invSigma).dot(I_iv - G.dot(A).dot(G.T).dot(invSigma))
K = B.dot(F)
K1 = np.linalg.inv(K + I_spk)
K2 = np.linalg.inv(2*K + I_spk)
# Compute the Gaussian distribution constant
alpha1 = np.linalg.slogdet(K1)[1]
alpha2 = np.linalg.slogdet(K2)[1]
constant = alpha2 / 2.0 - alpha1;
# Compute verification scores
score = sidekit.Scores()
score.scoremat = np.zeros(ndx.trialmask.shape)
score.modelset = clean_ndx.modelset
score.segset = clean_ndx.segset
score.scoremask = ndx.trialmask
# Project data in the space that maximizes the speaker separability
test_tmp = B.dot(test.stat1.T)
enroll_tmp = B.dot(enroll.stat1.T)
# Loop on the models
for model_idx in range(enroll.modelset.shape[0]):
s2 = enroll_tmp[:, model_idx].dot(K1).dot(enroll_tmp[:, model_idx])
mod_plus_test_seg = test_tmp + np.atleast_2d(enroll_tmp[:, model_idx]).T
tmp1 = test_tmp.T.dot(K1)
tmp2 = mod_plus_test_seg.T.dot(K2)
for seg_idx in range(test.segset.shape[0]):
s1 = tmp1[seg_idx, :].dot(test_tmp[:, seg_idx])
s3 = tmp2[seg_idx, :].dot(mod_plus_test_seg[:, seg_idx])
score.scoremat[model_idx, seg_idx] = ( s3 - s1 - s2)/2. + constant
return score