-
Notifications
You must be signed in to change notification settings - Fork 0
/
Eval.py
410 lines (340 loc) · 18.1 KB
/
Eval.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
"""
Authors: Massimiliano Todisco, Michele Panariello and chatGPT
Email: https://mailhide.io/e/Qk2FFM4a
Date: August 2024
"""
import torch
import yaml
from torch.nn.functional import cosine_similarity
import numpy as np
import pandas as pd
import speechbrain as sb
from speakerlab.models.campplus.DTDNN import CAMPPlus
from speakerlab.process.processor import FBank
from speakerlab.models.eres2net.ERes2Net import ERes2Net
from tqdm import tqdm
from concurrent.futures import ThreadPoolExecutor, as_completed
from data_loader import parse_protocol_a, load_audio
from AASIST import Model as AS
import configparser
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# Create a ConfigParser object
config = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
# Read the conf.txt file
config.read('conf.ini')
# Access the values
TARGET_SAMPLE_RATE = int(config.get('DEFAULT', 'TARGET_SAMPLE_RATE'))
NUM_LAYERS = int(config.get('DEFAULT', 'NUM_LAYERS'))
KERNEL_SIZE = int(config.get('DEFAULT', 'KERNEL_SIZE'))
AUDIO_FOLDER = config.get('DEFAULT', 'AUDIO_FOLDER')
PROTOCOL_A = config.get('DEFAULT', 'PROTOCOL_A')
PROTOCOL_B = config.get('DEFAULT', 'PROTOCOL_B')
AUDIO_FOLDER_MALAC = config.get('DEFAULT', 'AUDIO_FOLDER_MALAC')
SCORE_FILE = config.get('DEFAULT', 'SCORE_FILE')
RESULTS_FILE = config.get('DEFAULT', 'RESULTS_FILE')
print(f"Malacopula Evaluation | K={NUM_LAYERS} L={KERNEL_SIZE}")
def load_models(device):
model_ecapa = sb.inference.speaker.EncoderClassifier.from_hparams(source="speechbrain/spkrec-ecapa-voxceleb",
run_opts={"device": device})
model_ecapa.eval()
feature_extractor = FBank(80, sample_rate=16000, mean_nor=True)
model_path = 'pretrained_models/campplus_voxceleb.bin'
d = torch.load(model_path)
model_campp = CAMPPlus().to(device)
model_campp.load_state_dict(d)
model_campp.eval()
model_path = 'pretrained_models/pretrained_eres2net.ckpt'
d = torch.load(model_path)
model_ern = ERes2Net().to(device)
model_ern.load_state_dict(d)
model_ern.eval()
with open('pretrained_models/AASIST.conf') as config_file:
config = yaml.safe_load(config_file)
aasist = AS(config['model_config'])
aasist_weights = torch.load('pretrained_models/AASIST.pth')
missing_keys, unexpected_keys = aasist.load_state_dict(aasist_weights)
aasist.to(device)
aasist.eval()
return model_ecapa, model_campp, model_ern, feature_extractor, aasist
def parse_protocol_b0(file_path):
with open(file_path, 'r') as f:
lines = f.readlines()
protocol = []
for line in lines:
parts = line.strip().split()
protocol.append(parts)
return protocol
def compute_embedding(file, model_ecapa, model_campp, model_ern, feature_extractor, aasist, audio_folder, device):
audio_path = audio_folder + file + '.flac'
audio = load_audio(audio_path).to(device)
with torch.no_grad():
# Generate the embeddings
ecapa_embedding = model_ecapa.encode_batch(audio).squeeze(1)
mel = feature_extractor(audio).unsqueeze(0)
campp_embedding = model_campp(mel)
ern_embedding = model_ern(mel)
if aasist:
_, bf_logits = aasist(audio)
bf_score = torch.nn.functional.softmax(bf_logits, dim=1)[:,0]
# we take the 0th columns (i.e. the spoof score) because in the labeling of this script, 'spoof' == 1 and
# 'bonafide' == 0
return ecapa_embedding, campp_embedding, ern_embedding, bf_score.item()
return ecapa_embedding, campp_embedding, ern_embedding
def process_enrollment_files(enroll_files, model_ecapa, model_campp, model_ern, feature_extractor, audio_folder, device):
ecapa_embeddings = []
campp_embeddings = []
ern_embeddings = []
for file in enroll_files:
ecapa_embedding, campp_embedding, ern_embedding = compute_embedding(file, model_ecapa, model_campp, model_ern, feature_extractor,
None, audio_folder, device) # leaving aasist as none here
ecapa_embeddings.append(ecapa_embedding.cpu().numpy())
campp_embeddings.append(campp_embedding.cpu().numpy())
ern_embeddings.append(ern_embedding.cpu().numpy())
# Concatenate embeddings along the first dimension
enroll_ecapa_embedding = np.concatenate(ecapa_embeddings, axis=0)
enroll_campp_embedding = np.concatenate(campp_embeddings, axis=0)
enroll_ern_embedding = np.concatenate(ern_embeddings, axis=0)
# Convert concatenated embeddings back to torch tensors
enroll_ecapa_embedding = torch.tensor(enroll_ecapa_embedding).to(device)
enroll_campp_embedding = torch.tensor(enroll_campp_embedding).to(device)
enroll_ern_embedding = torch.tensor(enroll_ern_embedding).to(device)
# Calculate the mean of embeddings along the 0th dimension
enroll_ecapa_embedding = torch.mean(enroll_ecapa_embedding, dim=0).unsqueeze(0)
enroll_campp_embedding = torch.mean(enroll_campp_embedding, dim=0).unsqueeze(0)
enroll_ern_embedding = torch.mean(enroll_ern_embedding, dim=0).unsqueeze(0)
return enroll_ecapa_embedding, enroll_campp_embedding, enroll_ern_embedding
def calculate_eer_by_group(df, score_column, label_column):
results = {}
attacks = df['Attack'].unique()
# Calculate EER for pooled data
labels = df[label_column].values
scores = df[score_column].values
eer, threshold = calculate_eer(scores, labels)
results['pooled'] = {'EER': eer, 'Threshold': threshold}
# Calculate EER for each attack type
for attack in attacks:
if attack == 'bonafide':
continue
# subset = df[(df['Attack'] == attack) | (df['Label'] == 'target')]
subset = df[(df['Attack'] == attack) | (df['Label'].isin(['target', 'nontarget']))]
if not subset.empty:
labels = subset[label_column].values
scores = subset[score_column].values
if np.isnan(labels).any() or np.isnan(scores).any():
print(f"NaN values found in attack {attack}, skipping.")
continue
eer, threshold = calculate_eer(scores, labels)
results[attack] = {'EER': eer, 'Threshold': threshold}
return results
def calculate_eer(scores, labels):
# Separate scores based on labels
positive_scores = [score for label, score in zip(labels, scores) if label == 1]
negative_scores = [score for label, score in zip(labels, scores) if label == 0]
# Convert lists to torch tensors
positive_scores = torch.tensor(positive_scores)
negative_scores = torch.tensor(negative_scores)
# Calculate EER
eer, thr = sb.utils.metric_stats.EER(positive_scores, negative_scores)
return eer, thr
# Convert labels to binary format for 'spoof' vs 'target'
def label_to_binary_spoof_target(label):
if label == 'spoof':
return 1
elif label == 'target':
return 0
else:
return None
# Convert labels to binary format for 'nontarget' vs 'target'
def label_to_binary_nontarget_target(label):
if label == 'nontarget':
return 1
elif label == 'target':
return 0
else:
return None
def main():
protocol_a = parse_protocol_a(PROTOCOL_A)
protocol_b = parse_protocol_b0(PROTOCOL_B)
# New dictionaries to store the results
enroll_dict_ecapa = {}
enroll_dict_campp = {}
enroll_dict_ern = {}
trial_dict_ecapa = {}
trial_dict_campp = {}
trial_dict_ern = {}
trial_bf_score = {}
with torch.no_grad():
model_ecapa, model_campp, model_ern, feature_extractor, aasist = load_models(device)
# Parallelize enrollment embedding extraction
with ThreadPoolExecutor() as executor:
futures = []
for spkID, enroll_files in protocol_a.items():
futures.append(executor.submit(process_enrollment_files, enroll_files, model_ecapa, model_campp, model_ern,
feature_extractor, AUDIO_FOLDER, device))
for spkID, future in zip(tqdm(protocol_a.keys(), desc="Extracting enrol embeddings"), futures):
enroll_dict_ecapa[spkID], enroll_dict_campp[spkID], enroll_dict_ern[spkID] = future.result()
# Get unique file names from protocol_b
unique_files = set([parts[1] for parts in protocol_b])
# Parallelize trial embedding extraction
with ThreadPoolExecutor() as executor:
futures = {executor.submit(compute_embedding, file_name, model_ecapa, model_campp, model_ern, feature_extractor, aasist,
AUDIO_FOLDER_MALAC, device): file_name for file_name in unique_files}
for future in tqdm(as_completed(futures), total=len(unique_files), desc="Extracting trial embeddings"):
file_name = futures[future]
trial_dict_ecapa[file_name], trial_dict_campp[file_name], trial_dict_ern[file_name], trial_bf_score[file_name] = future.result()
# List to store the updated lines
updated_lines = []
for parts in tqdm(protocol_b, desc="Computing scores"):
spkID = parts[0]
file_name = parts[1]
# Extract precomputed enrollment embedding for spkID
if spkID in enroll_dict_ecapa:
precomputed_embedding_ecapa = enroll_dict_ecapa[spkID]
precomputed_embedding_campp = enroll_dict_campp[spkID]
precomputed_embedding_ern = enroll_dict_ern[spkID]
else:
print(f"Embedding for {spkID} not found")
continue
# Extract trial embedding for the file
ecapa_embedding = trial_dict_ecapa[file_name]
campp_embedding = trial_dict_campp[file_name]
ern_embedding = trial_dict_ern[file_name]
# Compute cosine distances
ecapa_scores = 1 - cosine_similarity(precomputed_embedding_ecapa.cpu().detach(),
ecapa_embedding.cpu().detach())
campp_scores = 1 - cosine_similarity(precomputed_embedding_campp.cpu().detach(),
campp_embedding.cpu().detach())
ern_scores = 1 - cosine_similarity(precomputed_embedding_ern.cpu().detach(),
ern_embedding.cpu().detach())
bf_score = trial_bf_score[file_name]
# Append the cosine distances to the line
updated_line = ' '.join(parts) + f' {ecapa_scores.item()} {campp_scores.item()} {ern_scores.item()} {bf_score}\n'
updated_lines.append(updated_line)
# Save the updated protocol to a new file
with open(SCORE_FILE, 'w') as f:
f.writelines(updated_lines)
print(f"Trial protocol with scores saved to {SCORE_FILE}")
print(f"Computing EERs")
# Read the file into a pandas DataFrame with unique column names
df = pd.read_csv(SCORE_FILE, sep=" ", header=None,
names=["spkID", "fileID", "Attack", "Label", "Score ECAPA", "Score CAM++", "Score ERes2Net",
"Score AASIST"])
# Convert scores to float
df['Score ECAPA'] = df['Score ECAPA'].astype(float)
df['Score CAM++'] = df['Score CAM++'].astype(float)
df['Score ERes2Net'] = df['Score ERes2Net'].astype(float)
df['Score AASIST'] = df['Score AASIST'].astype(float)
df['Score_EACAPA+AASIST'] = df['Score ECAPA'] + df['Score AASIST']
df['Score_CAM+++AASIST'] = df['Score CAM++'] + df['Score AASIST']
df['Score_ERes2Net+AASIST'] = df['Score ERes2Net'] + df['Score AASIST']
# Convert labels to a binary format for EER calculation
df['Label_binary'] = df['Label'].apply(lambda x: 1 if x in ['spoof', 'nontarget'] else 0)
results_ecapa_sasv = calculate_eer_by_group(df, 'Score ECAPA', 'Label_binary')
results_campp_sasv = calculate_eer_by_group(df, 'Score CAM++', 'Label_binary')
results_ern_sasv = calculate_eer_by_group(df, 'Score ERes2Net', 'Label_binary')
results_ecapa_aasist_sasv = calculate_eer_by_group(df, 'Score_EACAPA+AASIST', 'Label_binary')
results_campp_aasist_sasv = calculate_eer_by_group(df, 'Score_CAM+++AASIST', 'Label_binary')
results_ern_aasist_sasv = calculate_eer_by_group(df, 'Score_ERes2Net+AASIST', 'Label_binary')
df['Label_binary_spoof_target'] = df['Label'].apply(label_to_binary_spoof_target)
df['Label_binary_nontarget_target'] = df['Label'].apply(label_to_binary_nontarget_target)
# Filter out rows where 'Label_binary' is None
df_filtered_spoof_target = df.dropna(subset=['Label_binary_spoof_target'])
df_filtered_nontarget_target = df.dropna(subset=['Label_binary_nontarget_target'])
# Calculate EER for each ASV system and combine the results
results_ecapa_spf = calculate_eer_by_group(df_filtered_spoof_target, 'Score ECAPA', 'Label_binary_spoof_target')
results_campp_spf = calculate_eer_by_group(df_filtered_spoof_target, 'Score CAM++', 'Label_binary_spoof_target')
results_ern_spf = calculate_eer_by_group(df_filtered_spoof_target, 'Score ERes2Net', 'Label_binary_spoof_target')
results_ecapa_sv = calculate_eer_by_group(df_filtered_nontarget_target, 'Score ECAPA',
'Label_binary_nontarget_target')
results_campp_sv = calculate_eer_by_group(df_filtered_nontarget_target, 'Score CAM++',
'Label_binary_nontarget_target')
results_ern_sv = calculate_eer_by_group(df_filtered_nontarget_target, 'Score ERes2Net',
'Label_binary_nontarget_target')
results_ecapa_aasist_spf = calculate_eer_by_group(df_filtered_spoof_target, 'Score_EACAPA+AASIST',
'Label_binary_spoof_target')
results_campp_aasist_spf = calculate_eer_by_group(df_filtered_spoof_target, 'Score_CAM+++AASIST',
'Label_binary_spoof_target')
results_ern_aasist_spf = calculate_eer_by_group(df_filtered_spoof_target, 'Score_ERes2Net+AASIST',
'Label_binary_spoof_target')
results_ecapa_aasist_sv = calculate_eer_by_group(df_filtered_nontarget_target, 'Score_EACAPA+AASIST',
'Label_binary_nontarget_target')
results_campp_aasist_sv = calculate_eer_by_group(df_filtered_nontarget_target, 'Score_CAM+++AASIST',
'Label_binary_nontarget_target')
results_ern_aasist_sv = calculate_eer_by_group(df_filtered_nontarget_target, 'Score_ERes2Net+AASIST',
'Label_binary_nontarget_target')
# results aasist spf
results_aasist_spf = calculate_eer_by_group(df_filtered_spoof_target, 'Score AASIST',
'Label_binary_spoof_target')
# Convert results to DataFrames for display
results_ecapa_df_sasv = pd.DataFrame(results_ecapa_sasv).T
results_campp_df_sasv = pd.DataFrame(results_campp_sasv).T
results_ern_df_sasv = pd.DataFrame(results_ern_sasv).T
results_ecapa_aasist_df_sasv = pd.DataFrame(results_ecapa_aasist_sasv).T
results_campp_aasist_df_sasv = pd.DataFrame(results_campp_aasist_sasv).T
results_ern_aasist_df_sasv = pd.DataFrame(results_ern_aasist_sasv).T
results_ecapa_df_spf = pd.DataFrame(results_ecapa_spf).T
results_campp_df_spf = pd.DataFrame(results_campp_spf).T
results_ern_df_spf = pd.DataFrame(results_ern_spf).T
results_ecapa_aasist_df_spf = pd.DataFrame(results_ecapa_aasist_spf).T
results_campp_aasist_df_spf = pd.DataFrame(results_campp_aasist_spf).T
results_ern_aasist_df_spf = pd.DataFrame(results_ern_aasist_spf).T
results_ecapa_df_sv = pd.DataFrame(results_ecapa_sv).T
results_campp_df_sv = pd.DataFrame(results_campp_sv).T
results_ern_df_sv = pd.DataFrame(results_ern_sv).T
results_ecapa_aasist_df_sv = pd.DataFrame(results_ecapa_aasist_sv).T
results_campp_aasist_df_sv = pd.DataFrame(results_campp_aasist_sv).T
results_ern_aasist_df_sv = pd.DataFrame(results_ern_aasist_sv).T
results_aasist_df_spf = pd.DataFrame(results_aasist_spf).T
# Open a file to write the output
with open(RESULTS_FILE, 'w') as f:
# Redirect print statements to the file
def print_to_file(*args, **kwargs):
print(*args, **kwargs, file=f)
print_to_file("ECAPA")
print_to_file("SASV-EER")
print_to_file(results_ecapa_df_sasv)
print_to_file("SPF-EER")
print_to_file(results_ecapa_df_spf)
print_to_file("SV-EER")
print_to_file(results_ecapa_df_sv)
print_to_file("\nCAM++")
print_to_file("SASV-EER")
print_to_file(results_campp_df_sasv)
print_to_file("SPF-EER")
print_to_file(results_campp_df_spf)
print_to_file("SV-EER")
print_to_file(results_campp_df_sv)
print_to_file("\nERes2Net")
print_to_file("SASV-EER")
print_to_file(results_ern_df_sasv)
print_to_file("SPF-EER")
print_to_file(results_ern_df_spf)
print_to_file("SV-EER")
print_to_file(results_ern_df_sv)
print_to_file("\nAASIST")
print_to_file("SPF-EER")
print_to_file(results_aasist_df_spf)
print_to_file("\nECAPA + AASIST")
print_to_file("SASV-EER")
print_to_file(results_ecapa_aasist_df_sasv)
print_to_file("SPF-EER")
print_to_file(results_ecapa_aasist_df_spf)
print_to_file("SV-EER")
print_to_file(results_ecapa_aasist_df_sv)
print_to_file("\nCAM++ + AASIST")
print_to_file("SASV-EER")
print_to_file(results_campp_aasist_df_sasv)
print_to_file("SPF-EER")
print_to_file(results_campp_aasist_df_spf)
print_to_file("SV-EER")
print_to_file(results_campp_aasist_df_sv)
print_to_file("\nERes2Net + AASIST")
print_to_file("SASV-EER")
print_to_file(results_ern_aasist_df_sasv)
print_to_file("SPF-EER")
print_to_file(results_ern_aasist_df_spf)
print_to_file("SV-EER")
print_to_file(results_ern_aasist_df_sv)
print(f"Summary results saved to {RESULTS_FILE}")
if __name__ == "__main__":
main()