Skip to content

Commit 61cf1a4

Browse files
Merge pull request #118 from hyperion-ml/tyche-cleanup
Tyche cleanup
2 parents 45a3764 + 3242b6c commit 61cf1a4

File tree

16 files changed

+251
-30
lines changed

16 files changed

+251
-30
lines changed

egs/sre19-av-v/v0.1/datapath.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
#paths to databases
77
if [ "$(hostname --domain)" == "clsp.jhu.edu" ];then
8-
ldc_root=/export/corpora/LDC
8+
ldc_root=/export/corpora5/LDC
99
sre19_dev_root=$ldc_root/LDC2019E56
1010
sre19_eval_root=$ldc_root/LDC2019E57
1111
janus_root=$ldc_root/LDC2019E55/Janus_Multimedia_Dataset

egs/sre19-av-v/v0.2/datapath.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
#paths to databases
77
if [ "$(hostname --domain)" == "clsp.jhu.edu" ];then
8-
ldc_root=/export/corpora/LDC
8+
ldc_root=/export/corpora5/LDC
99
sre19_dev_root=$ldc_root/LDC2019E56
1010
sre19_eval_root=$ldc_root/LDC2019E57
1111
janus_root=$ldc_root/LDC2019E55/Janus_Multimedia_Dataset

egs/sre19-av-v/v0.2/steps_insightface/extract-face-embed-from-bbox-plus-face-det-v4.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ def __init__(self):
180180
overlap_score = np.expand_dims(overlap_score, axis=0)
181181
d_score = np.expand_dims(d_score, axis=0)
182182

183-
x_f = extract_embed_in_frame_v4(
183+
x_f, q_f = extract_embed_in_frame_v4(
184184
embed_extractor,
185185
frame,
186186
landmarks,

egs/sre19-av-v/v0.2/steps_insightface/extract-face-embed-from-image.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def __init__(self):
128128
)
129129
continue
130130

131-
x = extract_embed_in_frame_v4(
131+
x, _ = extract_embed_in_frame_v4(
132132
embed_extractor,
133133
frame,
134134
landmarks,

egs/sre21-av-v/v0.1/datapath.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
#paths to databases
77
if [ "$(hostname --domain)" == "clsp.jhu.edu" ];then
8-
ldc_root=/export/corpora/LDC
8+
ldc_root=/export/corpora5/LDC
99
# sre19_dev_root=$ldc_root/LDC2019E56
1010
# sre19_eval_root=$ldc_root/LDC2019E57
1111
sre21_dev_root=$ldc_root/LDC2021E09

egs/sre21-av-v/v0.2/datapath.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
#paths to databases
77
if [ "$(hostname --domain)" == "clsp.jhu.edu" ];then
8-
ldc_root=/export/corpora/LDC
8+
ldc_root=/export/corpora5/LDC
99
# sre19_dev_root=$ldc_root/LDC2019E56
1010
# sre19_eval_root=$ldc_root/LDC2019E57
1111
sre21_dev_root=$ldc_root/LDC2021E09

hyperion/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@
1818

1919
# from . import generators
2020

21-
__version__ = "0.3.0"
21+
__version__ = "0.3.1"

hyperion/clustering/ahc.py

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@
1515

1616

1717
class AHC(HypModel):
18+
"""Agglomerative Hierarchical Clustering class.
19+
20+
Attributes:
21+
method: linkage method to calculate the distance between a new agglomerated
22+
cluster and the rest of clusters.
23+
This can be ["average", "single", "complete", "weighted", "centroid", "median", "ward"].
24+
See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.linkage.html
25+
metric: indicates the type of metric used to calculate the input scores.
26+
It can be: "llr" (log-likelihood ratios), "prob" (probabilities), "distance": (distance metric).
27+
"""
28+
1829
def __init__(self, method="average", metric="llr", **kwargs):
1930
super().__init__(**kwargs)
2031
self.method = method
@@ -23,6 +34,15 @@ def __init__(self, method="average", metric="llr", **kwargs):
2334
self.flat_clusters = None
2435

2536
def fit(self, x, mask=None):
37+
"""Performs the clustering.
38+
It stores the AHC tree in the Z property of the object.
39+
40+
Args:
41+
x: input score matrix (num_samples, num_samples).
42+
It will use the upper triangular matrix only.
43+
mask: boolean mask where False in position i,j means that
44+
nodes i and j should not be merged.
45+
"""
2646

2747
if mask is not None:
2848
x = copy(x)
@@ -44,12 +64,27 @@ def fit(self, x, mask=None):
4464
self.Z = linkage(scores, method=self.method, metric=self.metric)
4565

4666
def get_flat_clusters(self, t, criterion="threshold"):
67+
"""Computes the flat clusters from the AHC tree.
68+
69+
Args:
70+
t: threshold or number of clusters
71+
criterion: if "threshold" with llr/prob larger than threshold or
72+
distance lower than threshold.
73+
if "num_clusters" returns the clusters corresponding
74+
to selecting a given number of clusters.
75+
76+
Returns:
77+
Clusters assigments for x as numpy integer vector (num_samples,).
78+
"""
4779
if criterion == "threshold":
4880
return self.get_flat_clusters_from_thr(t)
4981
else:
5082
return self.get_flat_clusters_from_num_clusters(t)
5183

5284
def get_flat_clusters_from_num_clusters(self, num_clusters):
85+
"""Computes the flat clusters from the AHC tree using
86+
num_clusters criterion"
87+
"""
5388
N = self.Z.shape[0] + 1
5489
num_clusters = min(N, num_clusters)
5590
p_idx = N - num_clusters
@@ -67,14 +102,23 @@ def get_flat_clusters_from_num_clusters(self, num_clusters):
67102
return flat_clusters
68103

69104
def get_flat_clusters_from_thr(self, thr):
105+
"""Computes the flat clusters from the AHC tree using
106+
threshold criterion"
107+
"""
70108
if self.metric == "llr" or self.metric == "prob":
71109
idx = self.Z[:, 2] >= thr
72110
else:
73111
idx = self.Z[:, 2] <= thr
74112
num_clusters = self.Z.shape[0] + 1 - np.sum(idx)
75113
return self.get_flat_clusters_from_num_clusters(num_clusters)
76114

77-
def compute_flat_clusters():
115+
def compute_flat_clusters(self):
116+
"""Computes the flat clusters for all possible number of clusters
117+
118+
Returns:
119+
numpy matrix (num_samples, num_samples) where row i contains the
120+
clusters assignments for the case of choosing num_samples - i clusters.
121+
"""
78122
N = self.Z.shape[0] + 1
79123
flat_clusters = np.zeros((N, N), dtype=int)
80124
flat_clusters[0] = np.arange(N, dtype=int)
@@ -86,20 +130,29 @@ def compute_flat_clusters():
86130
flat_clusters[i + 1][segm_idx] = N + i
87131

88132
for i in range(1, N):
89-
_, flat_clusters[i] = np.unique(flat_clusters, return_inverse=True)
133+
_, flat_clusters[i] = np.unique(flat_clusters[i], return_inverse=True)
90134
self.flat_clusters = flat_clusters
91135

92-
def evaluate_impurity_det(self, labels_true):
136+
def evaluate_homogeneity_completeness_tradeoff(self, true_labels):
137+
"""Evaluates the curve homogeneity versus completeness where
138+
Homogeneity: each cluster contains only members of a single class. (cluster purity)
139+
Completeness: all members of a given class are assigned to the same cluster. (class purity)
140+
141+
Args:
142+
true_labels: true cluster labels
143+
144+
Returns:
145+
homogeneity vector (num_samples,)
146+
completenes vector (num_samples,)
147+
"""
93148
if self.flat_clusters is None:
94149
self.compute_flat_clusters()
95150

96-
# homogeneity: each cluster contains only members of a single class. (cluster purity)
97-
# completeness: all members of a given class are assigned to the same cluster. (class purity)
98151
N = self.flat_clusters.shape[0]
99152
h = np.zeros((N,), dtype=float_cpu())
100153
c = np.zeros((N,), dtype=float_cpu())
101154
for i in range(self.flat_clusters.shape[0]):
102-
h[i] = homogeneity_score(labels_true, self.flat_clusters[i])
103-
c[i] = completeness_score(labels_true, self.flat_clusters[i])
155+
h[i] = homogeneity_score(true_labels, self.flat_clusters[i])
156+
c[i] = completeness_score(true_labels, self.flat_clusters[i])
104157

105-
return 1 - h, 1 - c
158+
return h, c

hyperion/clustering/kmeans.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,31 @@
1313

1414

1515
class KMeans(HypModel):
16+
"""K-Means clustering class.
17+
18+
Attributes:
19+
num_clusters: number of clusters.
20+
mu: cluster centers.
21+
rtol: minimum delta in loss function used as stopping criterion.
22+
"""
23+
1624
def __init__(self, num_clusters, mu=None, rtol=0.001, **kwargs):
1725
super(KMeans, self).__init__(**kwargs)
1826
self.num_clusters = num_clusters
1927
self.mu = mu
2028
self.rtol = rtol
2129

2230
def fit(self, x, epochs=100):
31+
"""Performs the clustering.
32+
33+
Args:
34+
x: input data (num_samples, feat_dim).
35+
epochs: max. number of epochs.
36+
37+
Returns:
38+
loss: value of loss function (num_epochs,).
39+
cluster_index: clustering labels as int numpy array with shape=(num_samples,)
40+
"""
2341
loss = np.zeros((epochs,), dtype=float_cpu())
2442
self.mu = self._choose_seeds(x)
2543
cluster_index, err2 = self.predict(x)
@@ -36,6 +54,14 @@ def fit(self, x, epochs=100):
3654
return loss, cluster_index
3755

3856
def _choose_seeds(self, x):
57+
"""Chooses the initial seeds for the clustering.
58+
59+
Args:
60+
x: input data (num_samples, feat_dim).
61+
62+
Returns:
63+
Initial centers (num_clusters, feat_dim)
64+
"""
3965
mu = np.zeros((self.num_clusters, x.shape[-1]), dtype=float_cpu())
4066
mu[0] = x[0]
4167
for i in range(1, self.num_clusters):
@@ -47,6 +73,15 @@ def _choose_seeds(self, x):
4773
return mu
4874

4975
def _compute_centroids(self, x, index):
76+
"""Compute the centroids given cluster assigments.
77+
78+
Args:
79+
x: input data (num_samples, feat_dim)
80+
index: cluster assignments as integers with shape=(num_samples,)
81+
82+
Returns:
83+
Cluster centroids (num_clusters, feat_dim)
84+
"""
5085
mu = np.zeros((self.num_clusters, x.shape[-1]), dtype=float_cpu())
5186
for k in range(self.num_clusters):
5287
r = index == k
@@ -55,6 +90,15 @@ def _compute_centroids(self, x, index):
5590
return mu
5691

5792
def predict(self, x):
93+
"""Compute the cluster labels for new data.
94+
95+
Args:
96+
x: input data (num_samples, feat_dim)
97+
98+
Returns:
99+
Cluster assignments as integer array (num_samples,)
100+
Square distance of each element to the center of its cluster.
101+
"""
58102
err2 = np.zeros((x.shape[0], self.num_clusters), dtype=float_cpu())
59103
for k in range(self.num_clusters):
60104
err2[:, k] = np.sum(np.square(x - self.mu[k]), axis=-1)

hyperion/diarization/diar_ahc_plda.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,30 @@
1919

2020

2121
class DiarAHCPLDA(object):
22+
"""Class to perform diarization using
23+
Agglomerative clustering using scores computed by a PLDA model.
24+
25+
The steps are:
26+
- It applies a pre-processing transformation to the data, such as LDA and
27+
Length normalization (optional).
28+
- Trains PCA on the test data and reduces test data dimension. It also
29+
transforms the parameters of the PLDA model using the PCA projection matrix (optional).
30+
- Gets affinity matrix using PLDA scoring.
31+
- It applies unsupervised calibration to scores using GMM model (optional).
32+
- Performs AHC.
33+
34+
Attributes:
35+
plda_model: pre-trained PLDA model.
36+
preproc: preprocessing transformation class.
37+
If None, no transformation is applied.
38+
threshold: stopping threshold for AHC.
39+
pca_var_r: ratio of variance to keep when doing PCA on features after
40+
the preprocessing. If "pca_var_r=1", PCA is not applied.
41+
do_unsup_cal: applies unsupervised calibration to PLDA scores.
42+
use_bic: uses Bayesian Information Criterion to decide if there is 1 or 2 components
43+
in the GMM used for calibration.
44+
"""
45+
2246
def __init__(
2347
self,
2448
plda_model,
@@ -39,7 +63,7 @@ def __init__(
3963

4064
@staticmethod
4165
def _plot_score_hist(scores, output_file, thr=None, gmm=None):
42-
66+
"""Plots the score histograms and GMM."""
4367
output_dir = Path(output_file).parent
4468
output_dir.mkdir(parents=True, exist_ok=True)
4569

@@ -72,6 +96,7 @@ def _plot_score_hist(scores, output_file, thr=None, gmm=None):
7296

7397
@staticmethod
7498
def _unsup_gmm_calibration(scores):
99+
"""Performs unsupervised calibration on the scores by training a GMM."""
75100
mask = np.triu(np.ones(scores.shape, dtype=np.bool), 1)
76101
scores_r = scores[mask].ravel()[:, None] # N x 1
77102
gmm_1c = GMM(num_comp=1)
@@ -95,6 +120,15 @@ def _unsup_gmm_calibration(scores):
95120
return scores, bic, gmm_2c
96121

97122
def cluster(self, x, hist_file=None):
123+
"""Peforms the diarization clustering.
124+
125+
Args:
126+
x: input data (num_frames, feat_dim)
127+
hist_file: file to plot the score histogram (optional).
128+
129+
Returns:
130+
Cluster assigments as (num_frames,) integer array.
131+
"""
98132
x = self.preproc.predict(x)
99133
if self.pca_var_r < 1:
100134
pca = PCA(pca_var_r=self.pca_var_r, whiten=True)

0 commit comments

Comments
 (0)