Vansh Chugh commited on
Commit
bdb2428
·
1 Parent(s): c79d26b

cleanup (~25k lines) + torchlibrosa in requirements.txt

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. inference/validator_FxProcessor.py +0 -436
  2. requirements.txt +1 -0
  3. utils/MSS_loss.py +0 -260
  4. utils/collators.py +0 -262
  5. utils/common_audioeffects.py +0 -1729
  6. utils/dnnlib/__init__.py +0 -8
  7. utils/dnnlib/util.py +0 -491
  8. utils/evaluation/KAD_evaluate.py +0 -307
  9. utils/evaluation/dist_metrics.py +0 -971
  10. utils/evaluation/dist_metrics_multitrack.py +0 -619
  11. utils/evaluation/pairwise_metrics.py +0 -958
  12. utils/feature_extractors/audio_features.py +0 -76
  13. utils/feature_extractors/load_features.py +0 -163
  14. utils/feature_extractors/networks/pytorch_utils.py +0 -256
  15. utils/fx_normalization/fxnorm.py +0 -154
  16. utils/laion_clap/clap_module/__init__.py +0 -2
  17. utils/laion_clap/clap_module/bert.py +0 -32
  18. utils/laion_clap/clap_module/bpe_simple_vocab_16e6.txt.gz +0 -3
  19. utils/laion_clap/clap_module/linear_probe.py +0 -63
  20. utils/laion_clap/clap_module/loss.py +0 -307
  21. utils/laion_clap/clap_module/tokenizer.py +0 -180
  22. utils/laion_clap/clap_module/utils.py +0 -296
  23. utils/laion_clap/clap_module/version.py +0 -1
  24. utils/laion_clap/evaluate/__init__.py +0 -0
  25. utils/laion_clap/evaluate/eval_dcase.py +0 -150
  26. utils/laion_clap/evaluate/eval_linear_probe.py +0 -515
  27. utils/laion_clap/evaluate/eval_retrieval.py +0 -192
  28. utils/laion_clap/evaluate/eval_retrieval_main.py +0 -257
  29. utils/laion_clap/evaluate/eval_zeroshot_classification.py +0 -261
  30. utils/laion_clap/training/data.py +3 -755
  31. utils/laion_clap/training/distributed.py +0 -139
  32. utils/laion_clap/training/imagenet_zeroshot_data.py +0 -254
  33. utils/laion_clap/training/infer_demo.py +0 -92
  34. utils/laion_clap/training/logger.py +0 -26
  35. utils/laion_clap/training/lp_main.py +0 -643
  36. utils/laion_clap/training/lp_train.py +0 -292
  37. utils/laion_clap/training/main.py +0 -597
  38. utils/laion_clap/training/params.py +0 -567
  39. utils/laion_clap/training/scheduler.py +0 -23
  40. utils/laion_clap/training/train.py +0 -781
  41. utils/laion_clap/training/zero_shot.py +0 -90
  42. utils/laion_clap/unit_test.py +0 -75
  43. utils/log.py +0 -976
  44. utils/st_ito/__init__.py +0 -0
  45. utils/st_ito/callbacks.py +0 -164
  46. utils/st_ito/config.yaml +0 -151
  47. utils/st_ito/dataset/dataset_param.py +0 -595
  48. utils/st_ito/dataset/dataset_sim.py +0 -320
  49. utils/st_ito/dataset/dataset_style.py +0 -351
  50. utils/st_ito/dataset/utils.py +0 -25
inference/validator_FxProcessor.py DELETED
@@ -1,436 +0,0 @@
1
- # Copyright (c) 2025 Sony Research
2
- # Licensed under CC BY-NC-SA 4.0
3
- # See LICENSE file for details
4
-
5
- from datetime import date
6
- import math
7
- import io
8
- import matplotlib.pyplot as plt
9
- from functools import partial
10
- import re
11
- import torch
12
- import os
13
- import wandb
14
- import copy
15
- from glob import glob
16
- from tqdm import tqdm
17
- import omegaconf
18
- import hydra
19
- import utils.log as utils_logging
20
- import utils.training_utils as tr_utils
21
-
22
- import soundfile as sf
23
- import numpy as np
24
- import torchaudio
25
-
26
- from utils.data_utils import apply_RMS_normalization
27
-
28
-
29
- class ValidatorFxProcessor:
30
- def __init__(
31
- self,
32
- args,
33
- network,
34
- test_set_dict=None,
35
- device=None,
36
- in_training=False,
37
- ):
38
- self.args = args
39
- self.network = network
40
- self.device = device
41
- self.test_set_dict = test_set_dict
42
-
43
- self.use_wandb = False # hardcoded for now
44
- self.in_training = in_training
45
-
46
- if in_training:
47
- self.use_wandb = True
48
- # Will inherit wandb_run from Trainer
49
- else: # If we use the tester in training, we will log in WandB in the Trainer() class, no need to create all these paths
50
- torch.backends.cudnn.benchmark = True
51
- if self.device is None:
52
- self.device = torch.device(
53
- "cuda" if torch.cuda.is_available() else "cpu"
54
- )
55
-
56
- self.setup_wandb()
57
-
58
- if self.args.tester.compute_metrics:
59
- self.metrics_dict = self.prepare_metrics(self.args.tester.metrics)
60
- else:
61
- self.metrics_dict = {}
62
-
63
-
64
- self.RMS_norm = (
65
- self.args.exp.RMS_norm
66
- ) # Use fixed RMS for evaluation, hardcoded for now
67
-
68
- if self.args.exp.style_encoder_type == "FxEncoder++_DynamicFeatures":
69
-
70
- Fxencoder_kwargs = self.args.exp.fx_encoder_plusplus_args
71
-
72
- from utils.feature_extractors.load_features import (
73
- load_fx_encoder_plusplus_2048,
74
- )
75
-
76
- feat_extractor = load_fx_encoder_plusplus_2048(
77
- Fxencoder_kwargs, self.device
78
- )
79
-
80
- from utils.feature_extractors.AF_features_embedding import (
81
- AF_fourier_embedding,
82
- )
83
-
84
- AFembedding = AF_fourier_embedding(device=self.device)
85
-
86
- def fxencode_fn(x):
87
- """
88
- x: tensor of shape [B, C, L] where B is the batch size, C is the number of channels and L is the length of the audio
89
- """
90
- z = feat_extractor(x)
91
- z = torch.nn.functional.normalize(
92
- z, dim=-1, p=2
93
- ) # normalize to unit variance
94
- z = z * math.sqrt(z.shape[-1]) # rescale to keep the same scale
95
-
96
- z_af, _ = AFembedding.encode(x)
97
- # embedding is l2 normalized, normalize to unit variance
98
- z_af = z_af * math.sqrt(
99
- z_af.shape[-1]
100
- ) # rescale to keep the same scale
101
-
102
- # concatenate z and z_af (rescaling with sqrt(dim) to keep the same scale)
103
- z_all = torch.cat([z, z_af], dim=-1)
104
-
105
- norm_z = z_all / math.sqrt(
106
- z_all.shape[-1]
107
- ) # normalize by dividing by sqrt(dim) to keep the same scale
108
-
109
- return norm_z
110
-
111
- self.style_encode = fxencode_fn
112
- else:
113
- raise NotImplementedError(
114
- "Only FxEncoder++_DynamicFeatures is implemented for now"
115
- )
116
-
117
- if self.args.exp.apply_fxnorm:
118
- self.fx_normalizer = hydra.utils.instantiate(self.args.exp.fxnorm)
119
-
120
- def setup_wandb(self):
121
- """
122
- Configure wandb, open a new run and log the configuration.
123
- """
124
- config = omegaconf.OmegaConf.to_container(
125
- self.args, resolve=True, throw_on_missing=True
126
- )
127
- self.wandb_run = wandb.init(
128
- project="testing" + self.args.tester.wandb.project,
129
- entity=self.args.tester.wandb.entity,
130
- config=config,
131
- tags=self.args.tester.wandb.tags,
132
- )
133
- # wandb.watch(self.network,
134
- # log_freq=self.args.logging.heavy_log_interval)
135
-
136
- self.wandb_run.name = self.args.tester.wandb.run_name
137
- self.use_wandb = True
138
-
139
- def setup_wandb_run(self, run):
140
- # get the wandb run object from outside (in trainer.py or somewhere else)
141
- self.wandb_run = run
142
- self.use_wandb = True
143
-
144
- def load_latest_checkpoint(self):
145
- # load the latest checkpoint from self.args.model_dir
146
- try:
147
- # find latest checkpoint_id
148
- save_basename = f"{self.args.exp.exp_name}-*.pt"
149
- save_name = f"{self.args.model_dir}/{save_basename}"
150
- list_weights = glob(save_name)
151
- id_regex = re.compile(f"{self.args.exp.exp_name}-(\d*)\.pt")
152
- list_ids = [
153
- int(id_regex.search(weight_path).groups()[0])
154
- for weight_path in list_weights
155
- ]
156
- checkpoint_id = max(list_ids)
157
-
158
- state_dict = torch.load(
159
- f"{self.args.model_dir}/{self.args.exp.exp_name}-{checkpoint_id}.pt",
160
- map_location=self.device,
161
- )
162
- try:
163
- self.network.load_state_dict(state_dict["network"])
164
- except Exception as e:
165
- print(e)
166
- print("Failed to load in strict mode, trying again without strict mode")
167
- self.network.load_state_dict(state_dict["model"], strict=False)
168
-
169
- print(f"Loaded checkpoint {checkpoint_id}")
170
- return True
171
- except (FileNotFoundError, ValueError):
172
- raise ValueError("No checkpoint found")
173
-
174
- def load_checkpoint(self, path):
175
- state_dict = torch.load(path, map_location=self.device, weights_only=False)
176
- print("state_dict keys:", state_dict.keys())
177
- try:
178
- self.it = state_dict["it"]
179
- except:
180
- self.it = 0
181
-
182
- print(f"loading checkpoint {self.it}")
183
- return tr_utils.load_state_dict(state_dict, network=self.network)
184
-
185
- def log_figure(self, fig, name: str, step=None):
186
- # Save the figure to a buffer
187
-
188
- self.wandb_run.log(
189
- {name: wandb.Image(fig)}, step=step if step is not None else self.it
190
- )
191
-
192
- def log_metric(self, value, name: str, step=None):
193
- # print("logging metric it:", self.it, "name:", name)
194
- self.wandb_run.log({name: value}, step=step if step is not None else self.it)
195
-
196
- def log_audio(self, pred, name: str, it=None):
197
- if it is None:
198
- it = self.it
199
- if self.use_wandb:
200
- pred = pred.permute(1, 0)
201
- self.wandb_run.log(
202
- {
203
- name: wandb.Audio(
204
- pred.detach().cpu().numpy(),
205
- sample_rate=self.args.exp.sample_rate,
206
- )
207
- },
208
- step=it,
209
- )
210
-
211
- # ------------- UNCONDITIONAL SAMPLING ---------------#
212
-
213
- ##############################
214
- ### UNCONDITIONAL SAMPLING ###
215
- ##############################
216
-
217
- def prepare_metrics(self, metrics):
218
- metrics_dict = {}
219
- for metric in metrics:
220
- print(f"Preparing metric {metric}")
221
- if "pairwise" in metric:
222
- from utils.evaluation.pairwise_metrics import metric_factory
223
-
224
- metrics_dict[metric] = metric_factory(
225
- metric, self.args.exp.sample_rate, **self.args.tester
226
- )
227
-
228
- return metrics_dict
229
-
230
- def test_paired(self, mode, exp_description=""):
231
-
232
- # self.it = 0
233
- for k, test_set in self.test_set_dict.items():
234
-
235
- print(f"Testing on {k} set", k)
236
-
237
- assert len(test_set) != 0, "No samples found in test set"
238
-
239
- dict_y = {}
240
- dict_x = {}
241
- dict_y_hat = {}
242
-
243
- i = 0
244
-
245
- for x, y in tqdm(test_set):
246
-
247
- B, C, T = y.shape
248
-
249
- x = x.to(self.device).float()
250
- if x.shape[-1] > self.args.exp.audio_len:
251
- x = x[:, :, : self.args.exp.audio_len]
252
- elif x.shape[-1] < self.args.exp.audio_len:
253
- raise ValueError(
254
- f"Sample length {x.shape[-1]} is less than expected {self.args.exp.audio_len}"
255
- )
256
-
257
- if mode == "paired":
258
- y = y.to(self.device).float()
259
-
260
- if y.shape[-1] > self.args.exp.audio_len:
261
- y = y[:, :, : self.args.exp.audio_len]
262
- elif y.shape[-1] < self.args.exp.audio_len:
263
- raise ValueError(
264
- f"Sample length {y.shape[-1]} is less than expected {self.args.exp.audio_len}"
265
- )
266
-
267
- if x.shape[1] == 2:
268
- x = x.mean(
269
- dim=1, keepdim=True
270
- ) # expand to [B*N, 1, L] to keep the shape consistent
271
-
272
- # RMS normalization of x and y
273
-
274
- if self.args.exp.apply_fxnorm:
275
- x = self.fx_normalizer(
276
- x,
277
- RMS=self.args.exp.RMS_norm,
278
- use_gate=self.args.exp.use_gated_RMSnorm,
279
- )
280
- else:
281
- x = apply_RMS_normalization(
282
- x,
283
- self.args.exp.RMS_norm,
284
- use_gate=self.args.exp.use_gated_RMSnorm,
285
- )
286
-
287
- if "baseline" in mode:
288
- if mode == "baseline_dry":
289
- preds = x # Just return the dry vocals as baseline
290
- elif mode == "baseline_autoencoder":
291
- preds = self.autoencoder_reconstruction(
292
- y
293
- ) # Just return the dry vocals as baseline
294
- elif mode == "baseline_random":
295
- raise NotImplementedError(
296
- "Baseline random sampling not implemented yet"
297
- )
298
- pass
299
- else:
300
- with torch.no_grad():
301
- z = self.style_encode(y)
302
- try:
303
- preds = self.network(
304
- x, z
305
- ) # Get the predictions from the network
306
- except Exception as e:
307
- print(f"Error during inference: {e}")
308
- continue
309
- print(
310
- "y_pred",
311
- preds.shape,
312
- preds.std(),
313
- preds.mean(),
314
- preds.min(),
315
- preds.max(),
316
- )
317
-
318
- is_nan = torch.isnan(preds).any()
319
- if is_nan:
320
- num_nan = torch.sum(torch.isnan(x)).item()
321
- print(
322
- f"Number of NaN values in sample_x: {num_nan} of {x.numel()}"
323
- )
324
-
325
- y = apply_RMS_normalization(
326
- y, self.args.exp.RMS_norm, use_gate=self.args.exp.use_gated_RMSnorm
327
- )
328
-
329
- for b in range(B):
330
- if self.use_wandb:
331
- if (
332
- i < self.args.tester.wandb.num_examples_to_log
333
- ): # Log only first 10 samples
334
- self.log_audio(
335
- preds[b], f"pred_wet_{k}_{mode}_{i}", it=self.it
336
- ) # Just log first sample
337
- self.log_audio(
338
- y[b], f"original_wet_{k}_{mode}_{i}", it=self.it
339
- ) # Just log first sample
340
- self.log_audio(
341
- x[b], f"original_dry_{k}_{mode}_{i}", it=self.it
342
- ) # Just log first sample
343
-
344
- dict_y[i] = y[b].detach().cpu().numpy()
345
- dict_x[i] = x[b].detach().cpu().numpy()
346
- dict_y_hat[i] = preds[b].detach().cpu().numpy()
347
-
348
- i += 1
349
-
350
- if self.args.tester.compute_metrics:
351
- for metric in self.metrics_dict.keys():
352
- try:
353
- print(f"Computing metric {metric}")
354
- result, result_dict = self.metrics_dict[metric].compute(
355
- dict_y, dict_y_hat, dict_x
356
- )
357
-
358
- if self.use_wandb:
359
- if result is not None:
360
- self.log_metric(
361
- result, metric + "_" + k + "_" + mode, step=self.it
362
- )
363
-
364
- for key, value in result_dict.items():
365
- if "figure" in key:
366
- # log figure as an image
367
- self.log_figure(
368
- value, key + "_" + k + "_" + mode, step=self.it
369
- )
370
- else:
371
- self.log_metric(
372
- value, key + "_" + k + "_" + mode, step=self.it
373
- )
374
-
375
- except Exception as e:
376
- print(f"Error computing metric {metric}: {e}")
377
- continue
378
-
379
- def prepare_directories(self, mode, unconditional=False, string=None):
380
-
381
- today = date.today()
382
- self.paths = {}
383
- if (
384
- "overriden_name" in self.args.tester.keys()
385
- and self.args.tester.overriden_name is not None
386
- ):
387
- self.path_sampling = os.path.join(
388
- self.args.model_dir, self.args.tester.overriden_name
389
- )
390
- else:
391
- self.path_sampling = os.path.join(
392
- self.args.model_dir, "test" + today.strftime("%d_%m_%Y")
393
- )
394
- if not os.path.exists(self.path_sampling):
395
- os.makedirs(self.path_sampling)
396
-
397
- self.paths[mode] = os.path.join(
398
- self.path_sampling, mode, self.args.exp.exp_name
399
- )
400
-
401
- if not os.path.exists(self.paths[mode]):
402
- os.makedirs(self.paths[mode])
403
- if string is None:
404
- string = ""
405
-
406
- self.paths[mode + "wet_original"] = os.path.join(
407
- self.paths[mode], string + "wet_original"
408
- )
409
- if not os.path.exists(self.paths[mode + "wet_original"]):
410
- os.makedirs(self.paths[mode + "wet_original"])
411
- self.paths[mode + "dry"] = os.path.join(self.paths[mode], string + "dry")
412
- if not os.path.exists(self.paths[mode + "dry"]):
413
- os.makedirs(self.paths[mode + "dry"])
414
- self.paths[mode + "wet_estimate"] = os.path.join(
415
- self.paths[mode], string + "wet_estimate"
416
- )
417
- if not os.path.exists(self.paths[mode + "wet_estimate"]):
418
- os.makedirs(self.paths[mode + "wet_estimate"])
419
-
420
- def save_experiment_args(self, mode):
421
- with open(
422
- os.path.join(self.paths[mode], ".argv"), "w"
423
- ) as f: # Keep track of the arguments we used for this experiment
424
- omegaconf.OmegaConf.save(config=self.args, f=f.name)
425
-
426
- def do_test(self, it=0):
427
-
428
- self.it = it
429
- for m in self.args.tester.modes:
430
- if m == "paired":
431
- if not self.in_training:
432
- self.prepare_directories(m, unconditional=True)
433
- self.save_experiment_args(m)
434
- self.test_paired(m)
435
- else:
436
- print("Warning: unknown mode: ", m)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -11,6 +11,7 @@ pyloudnorm
11
  einops
12
  packaging
13
  librosa
 
14
  transformers
15
  wget
16
  huggingface-hub
 
11
  einops
12
  packaging
13
  librosa
14
+ torchlibrosa
15
  transformers
16
  wget
17
  huggingface-hub
utils/MSS_loss.py DELETED
@@ -1,260 +0,0 @@
1
- """
2
- Implementation of objective functions used in the task 'ITO-Master'
3
- https://github.com/SonyResearch/ITO-Master/blob/master/ito_master/modules/loss.py
4
- """
5
-
6
- import numpy as np
7
- import math
8
- import torch
9
- import torch.nn.functional as F
10
- import torch.nn as nn
11
- import auraloss
12
- import torchaudio
13
- import warnings
14
-
15
-
16
- import os
17
- import sys
18
-
19
- currentdir = os.path.dirname(os.path.realpath(__file__))
20
- sys.path.append(os.path.dirname(currentdir))
21
-
22
- class FrontEnd(nn.Module):
23
- def __init__(self, channel='stereo', \
24
- n_fft=2048, \
25
- n_mels=128, \
26
- sample_rate=44100, \
27
- hop_length=None, \
28
- win_length=None, \
29
- window="hann", \
30
- eps=1e-7, \
31
- device=torch.device("cpu")):
32
- super(FrontEnd, self).__init__()
33
- self.channel = channel
34
- self.n_fft = n_fft
35
- self.n_mels = n_mels
36
- self.sample_rate = sample_rate
37
- self.hop_length = n_fft//4 if hop_length==None else hop_length
38
- self.win_length = n_fft if win_length==None else win_length
39
- self.eps = eps
40
- if window=="hann":
41
- self.window = torch.hann_window(window_length=self.win_length, periodic=True).to(device)
42
- elif window=="hamming":
43
- self.window = torch.hamming_window(window_length=self.win_length, periodic=True).to(device)
44
- self.melscale_transform = torchaudio.transforms.MelScale(n_mels=self.n_mels, \
45
- sample_rate=self.sample_rate, \
46
- n_stft=self.n_fft//2+1).to(device)
47
-
48
-
49
- def forward(self, input, mode):
50
- # front-end function which channel-wise combines all demanded features
51
- # input shape : batch x channel x raw waveform
52
- # output shape : batch x channel x frequency x time
53
- phase_output = None
54
-
55
- front_output_list = []
56
- for cur_mode in mode:
57
- # Real & Imaginary
58
- if cur_mode=="cplx":
59
- if self.channel=="mono":
60
- output = torch.stft(input, n_fft=self.n_fft, hop_length=self.hop_length, win_length=self.win_length, window=self.window)
61
- elif self.channel=="stereo":
62
- output_l = torch.stft(input[:,0], n_fft=self.n_fft, hop_length=self.hop_length, win_length=self.win_length, window=self.window)
63
- output_r = torch.stft(input[:,1], n_fft=self.n_fft, hop_length=self.hop_length, win_length=self.win_length, window=self.window)
64
- output = torch.cat((output_l, output_r), axis=-1)
65
- if input.shape[-1] % round(self.n_fft/4) == 0:
66
- output = output[:, :, :-1]
67
- if self.n_fft % 2 == 0:
68
- output = output[:, :-1]
69
- front_output_list.append(output.permute(0, 3, 1, 2))
70
- # Magnitude & Phase or Mel
71
- elif "mag" in cur_mode or "mel" in cur_mode:
72
- if self.channel=="mono":
73
- cur_cplx = torch.stft(input, n_fft=self.n_fft, hop_length=self.hop_length, win_length=self.win_length, window=self.window, return_complex=True)
74
- output = self.mag(cur_cplx).unsqueeze(-1)[..., 0:1]
75
- if "mag_phase" in cur_mode:
76
- phase = self.phase(cur_cplx)
77
- if "mel" in cur_mode:
78
- output = self.melscale_transform(output.squeeze(-1)).unsqueeze(-1)
79
- elif self.channel=="stereo":
80
- cplx_l = torch.stft(input[:,0], n_fft=self.n_fft, hop_length=self.hop_length, win_length=self.win_length, window=self.window, return_complex=True)
81
- cplx_r = torch.stft(input[:,1], n_fft=self.n_fft, hop_length=self.hop_length, win_length=self.win_length, window=self.window, return_complex=True)
82
- mag_l = self.mag(cplx_l).unsqueeze(-1)
83
- mag_r = self.mag(cplx_r).unsqueeze(-1)
84
- output = torch.cat((mag_l, mag_r), axis=-1)
85
- if "mag_phase" in cur_mode:
86
- phase_l = self.phase(cplx_l).unsqueeze(-1)
87
- phase_r = self.phase(cplx_r).unsqueeze(-1)
88
- output = torch.cat((mag_l, phase_l, mag_r, phase_r), axis=-1)
89
- if "mel" in cur_mode:
90
- output = torch.cat((self.melscale_transform(mag_l.squeeze(-1)).unsqueeze(-1), self.melscale_transform(mag_r.squeeze(-1)).unsqueeze(-1)), axis=-1)
91
-
92
- if "log" in cur_mode:
93
- output = torch.log(output+self.eps)
94
-
95
- if input.shape[-1] % round(self.n_fft/4) == 0:
96
- output = output[:, :, :-1]
97
- if cur_mode!="mel" and self.n_fft % 2 == 0: # discard highest frequency
98
- output = output[:, 1:]
99
- front_output_list.append(output.permute(0, 3, 1, 2))
100
-
101
- # combine all demanded features
102
- if not front_output_list:
103
- raise NameError("NameError at FrontEnd: check using features for front-end")
104
- elif len(mode)!=1:
105
- for i, cur_output in enumerate(front_output_list):
106
- if i==0:
107
- front_output = cur_output
108
- else:
109
- front_output = torch.cat((front_output, cur_output), axis=1)
110
- else:
111
- front_output = front_output_list[0]
112
-
113
- return front_output
114
-
115
-
116
- def mag(self, cplx_input, eps=1e-07):
117
- # mag_summed = cplx_input.pow(2.).sum(-1) + eps
118
- mag_summed = cplx_input.real.pow(2.) + cplx_input.imag.pow(2.) + eps
119
- return mag_summed.pow(0.5)
120
-
121
-
122
- def phase(self, cplx_input, ):
123
- return torch.atan2(cplx_input.imag, cplx_input.real)
124
- # return torch.angle(cplx_input)
125
-
126
-
127
-
128
- # Multi-Scale Spectral Loss proposed at the paper "DDSP: DIFFERENTIABLE DIGITAL SIGNAL PROCESSING" (https://arxiv.org/abs/2001.04643)
129
- # we extend this loss by applying it to mid/side channels
130
- class MultiScale_Spectral_Loss_MidSide_DDSP(nn.Module):
131
- def __init__(self, mode='midside', \
132
- reduce=True, \
133
- n_filters=None, \
134
- windows_size=None, \
135
- hops_size=None, \
136
- window="hann", \
137
- eps=1e-7, \
138
- device=torch.device("cpu")):
139
- super(MultiScale_Spectral_Loss_MidSide_DDSP, self).__init__()
140
- self.mode = mode
141
- self.eps = eps
142
- self.mid_weight = 0.5 # value in the range of 0.0 ~ 1.0
143
- self.logmag_weight = 0.1
144
-
145
- if n_filters is None:
146
- n_filters = [4096, 2048, 1024, 512]
147
- if windows_size is None:
148
- windows_size = [4096, 2048, 1024, 512]
149
- if hops_size is None:
150
- hops_size = [1024, 512, 256, 128]
151
-
152
- self.multiscales = []
153
- for i in range(len(windows_size)):
154
- cur_scale = {'window_size' : float(windows_size[i])}
155
- if self.mode=='midside':
156
- cur_scale['front_end'] = FrontEnd(channel='mono', \
157
- n_fft=n_filters[i], \
158
- hop_length=hops_size[i], \
159
- win_length=windows_size[i], \
160
- window=window, \
161
- device=device)
162
- elif self.mode=='ori':
163
- cur_scale['front_end'] = FrontEnd(channel='stereo', \
164
- n_fft=n_filters[i], \
165
- hop_length=hops_size[i], \
166
- win_length=windows_size[i], \
167
- window=window, \
168
- device=device)
169
- self.multiscales.append(cur_scale)
170
-
171
- self.reduce=reduce
172
- self.objective_l1 = nn.L1Loss(reduce=reduce)
173
- self.objective_l2 = nn.MSELoss(reduce=reduce)
174
-
175
-
176
- def forward(self, est_targets, targets):
177
- if self.mode=='midside':
178
- return self.forward_midside(est_targets, targets)
179
- elif self.mode=='ori':
180
- return self.forward_ori(est_targets, targets)
181
-
182
-
183
- def forward_ori(self, est_targets, targets):
184
- if self.reduce:
185
- total_mag_loss = 0.0
186
- total_logmag_loss = 0.0
187
- else:
188
- total_mag_loss=torch.zeros(est_targets.shape[0], 1).to(est_targets.device)
189
- total_logmag_loss=torch.zeros(est_targets.shape[0], 1).to(est_targets.device)
190
- for cur_scale in self.multiscales:
191
- est_mag = cur_scale['front_end'](est_targets, mode=["mag"])
192
- tgt_mag = cur_scale['front_end'](targets, mode=["mag"])
193
-
194
- mag_loss = self.magnitude_loss(est_mag, tgt_mag)
195
- logmag_loss = self.log_magnitude_loss(est_mag, tgt_mag)
196
- if self.reduce:
197
- total_mag_loss += mag_loss
198
- total_logmag_loss += logmag_loss
199
- else:
200
- total_logmag_loss += logmag_loss.mean((1, 2, 3)).unsqueeze(-1)
201
- total_mag_loss += mag_loss.mean((1, 2, 3)).unsqueeze(-1)
202
- # return total_loss
203
- return (1-self.logmag_weight)*total_mag_loss + \
204
- (self.logmag_weight)*total_logmag_loss
205
-
206
-
207
- def forward_midside(self, est_targets, targets):
208
- est_mid, est_side = self.to_mid_side(est_targets)
209
- tgt_mid, tgt_side = self.to_mid_side(targets)
210
- if self.reduce:
211
- total_mag_loss = 0.0
212
- total_logmag_loss = 0.0
213
- else:
214
- total_logmag_loss=torch.zeros(est_targets.shape[0], 1).to(est_targets.device)
215
- total_mag_loss=torch.zeros(est_targets.shape[0], 1).to(est_targets.device)
216
-
217
- for cur_scale in self.multiscales:
218
- est_mid_mag = cur_scale['front_end'](est_mid, mode=["mag"])
219
- est_side_mag = cur_scale['front_end'](est_side, mode=["mag"])
220
- tgt_mid_mag = cur_scale['front_end'](tgt_mid, mode=["mag"])
221
- tgt_side_mag = cur_scale['front_end'](tgt_side, mode=["mag"])
222
-
223
- mag_loss = self.mid_weight*self.magnitude_loss(est_mid_mag, tgt_mid_mag) + \
224
- (1-self.mid_weight)*self.magnitude_loss(est_side_mag, tgt_side_mag)
225
- logmag_loss = self.mid_weight*self.log_magnitude_loss(est_mid_mag, tgt_mid_mag) + \
226
- (1-self.mid_weight)*self.log_magnitude_loss(est_side_mag, tgt_side_mag)
227
-
228
- #take mean over all dimensions except batch
229
- if self.reduce:
230
- total_mag_loss += mag_loss
231
- total_logmag_loss += logmag_loss
232
- else:
233
- total_mag_loss += mag_loss.mean((1, 2, 3)).unsqueeze(-1)
234
- #mean over dims 1, 2, 3
235
- total_logmag_loss += logmag_loss.mean((1, 2, 3)).unsqueeze(-1)
236
- # return total_loss
237
- return (1-self.logmag_weight)*total_mag_loss + \
238
- (self.logmag_weight)*total_logmag_loss
239
-
240
-
241
- def to_mid_side(self, stereo_in):
242
- mid = stereo_in[:,0] + stereo_in[:,1]
243
- side = stereo_in[:,0] - stereo_in[:,1]
244
- return mid, side
245
-
246
-
247
- def magnitude_loss(self, est_mag_spec, tgt_mag_spec):
248
- if self.reduce:
249
- return torch.norm(self.objective_l1(est_mag_spec, tgt_mag_spec))
250
- else:
251
- return self.objective_l1(est_mag_spec, tgt_mag_spec)
252
-
253
-
254
- def log_magnitude_loss(self, est_mag_spec, tgt_mag_spec):
255
- est_log_mag_spec = torch.log10(est_mag_spec+self.eps)
256
- tgt_log_mag_spec = torch.log10(tgt_mag_spec+self.eps)
257
- return self.objective_l2(est_log_mag_spec, tgt_log_mag_spec)
258
-
259
-
260
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/collators.py DELETED
@@ -1,262 +0,0 @@
1
-
2
-
3
- import torch
4
- import torch.nn.functional as F
5
- import torchaudio
6
-
7
- def collate_multitrack_sim(batch, max_tracks=None):
8
-
9
- x= [ data_i[0] for data_i in batch ] # x is a list of tensors, each tensor is a track
10
- clusters=[ torch.tensor(data_i[1]) for data_i in batch ] # cluster is a list of tensors, each tensor is a cluster
11
- taxonomies=[ data_i[2] for data_i in batch ] # taxonomy is a
12
-
13
- if max_tracks is None:
14
- max_tracks = max(track.shape[0] for track in x) # Find the maximum number of tracks in the batch
15
- else:
16
- if max_tracks > max(track.shape[0] for track in x):
17
- print(f"Warning: max_tracks is set to {max_tracks}, but the maximum number of tracks in the batch is {max(track.shape[0] for track in x)}. I dont know what will happen, consider increasing max_tracks," )
18
-
19
- # Pad each examples with zeros to the maximum length (dimension 0)
20
-
21
- padded_x = []
22
- padded_taxonomies = []
23
- masks = torch.zeros((len(x), max_tracks), dtype=torch.bool) # Create a mask tensor
24
-
25
- for i in range(len(x)):
26
- current_x = x[i]
27
- current_taxonomies = taxonomies[i]
28
-
29
- # Get current number of tracks
30
- current_tracks = current_x.shape[0]
31
-
32
- masks[i, :current_tracks] = 1 # Set mask for current tracks
33
-
34
- if current_tracks < max_tracks:
35
- # Pad dimension N (first dimension)
36
- # For a tensor of shape [N, C, L], we need to pad the first dimension
37
- # F.pad expects padding as (pad_left, pad_right, pad_top, pad_bottom, pad_front, pad_back)
38
- # To pad the first dimension, we need to specify padding for the third-from-last dimension
39
- pad_size = (0, 0, # last dim (L): no padding
40
- 0, 0, # second-to-last dim (C): no padding
41
- 0, max_tracks - current_tracks) # third-to-last dim (N): pad at the end
42
-
43
- padded_x.append(F.pad(current_x, pad_size))
44
-
45
- # Pad taxonomies
46
- padded_taxonomies.append(current_taxonomies + [None] * (max_tracks - len(current_taxonomies)))
47
- elif current_tracks > max_tracks :
48
- raise ValueError(f"Number of tracks {current_tracks} exceeds maximum allowed {max_tracks}. that is impossible")
49
- else:
50
- padded_x.append(current_x)
51
- padded_taxonomies.append(current_taxonomies[:max_tracks])
52
-
53
-
54
-
55
- x_stacked = torch.stack(padded_x, dim=0) # Shape: [B, max_tracks, C, L]
56
-
57
- clusters_stacked = torch.stack(clusters, dim=0) # Shape: [B, max_tracks]
58
-
59
- return {
60
- 'x': x_stacked,
61
- 'clusters': clusters_stacked,
62
- 'taxonomies': padded_taxonomies,
63
- "masks": masks
64
- }
65
-
66
- def collate_multitrack(batch, max_tracks=None, sample_rate=None, segment_length=None, device=None):
67
-
68
- x= [ data_i[0] for data_i in batch ] # x is a list of tensors, each tensor is a track
69
-
70
- paths=[ data_i[1] for data_i in batch ] # paths is a list of paths to the audio files, each path is a string
71
- fs= [ data_i[2] for data_i in batch ] # fs is a list of sample rates, each sample rate is an integer
72
-
73
- if max_tracks is None:
74
- max_tracks = max(track.shape[0] for track in x) # Find the maximum number of tracks in the batch
75
- else:
76
- if max_tracks < max(track.shape[0] for track in x):
77
- print(f"Warning: max_tracks is set to {max_tracks}, but the maximum number of tracks in the batch is {max(track.shape[0] for track in x)}. I will crop the last tracks. I hope the order is random..." )
78
-
79
- for i in range(len(x)):
80
- if x[i].shape[0] > max_tracks:
81
- print("cropping x[i] to max_tracks")
82
- x[i] = x[i][:max_tracks]
83
-
84
- is_x_none= any(x_i is None for x_i in x)
85
-
86
- assert not (is_x_none ), "Either x or y should be None, but not both. This is a bug in the collator"
87
-
88
- #now resample audio to 44100 Hz and cut to segment length
89
- for i in range(len(x)):
90
- x[i] = x[i].to(device) # Move to device
91
-
92
- if fs[i] != sample_rate:
93
- if fs[i] == 48000 and sample_rate == 44100:
94
- #print(f"Resampling audio from {fs[i]} Hz to {sample_rate} Hz")
95
- x[i]=torchaudio.functional.resample(x[i], orig_freq=160, new_freq=147)
96
- else:
97
- #print(f"Resampling audio from {fs[i]} Hz to {sample_rate} Hz")
98
- x[i]=torchaudio.functional.resample(x[i], fs[i], sample_rate)
99
-
100
- assert segment_length is not None, "segment_length should be set to the length of the audio segment in samples"
101
- if x[i].shape[-1] > segment_length:
102
- x[i] = x[i][..., :segment_length]
103
- elif x[i].shape[-1] < segment_length:
104
- raise ValueError(f"Audio length {x[i].shape[-1]} is less than segment length {segment_length}. Please check your data.")
105
-
106
- # Pad each examples with zeros to the maximum length (dimension 0)
107
- padded_x = []
108
- masks = torch.zeros((len(x), max_tracks), dtype=torch.bool) # Create a mask tensor
109
-
110
-
111
- for i in range(len(x)):
112
- if not is_x_none:
113
- current_x = x[i]
114
-
115
- # Get current number of tracks
116
- current_tracks = current_x.shape[0]
117
-
118
- masks[i, :current_tracks] = 1 # Set mask for current tracks
119
-
120
- if current_tracks < max_tracks:
121
- # Pad dimension N (first dimension)
122
- # For a tensor of shape [N, C, L], we need to pad the first dimension
123
- # F.pad expects padding as (pad_left, pad_right, pad_top, pad_bottom, pad_front, pad_back)
124
- # To pad the first dimension, we need to specify padding for the third-from-last dimension
125
- pad_size = (0, 0, # last dim (L): no padding
126
- 0, 0, # second-to-last dim (C): no padding
127
- 0, max_tracks - current_tracks) # third-to-last dim (N): pad at the end
128
-
129
-
130
- if not is_x_none:
131
- padded_x.append(F.pad(current_x, pad_size))
132
-
133
- elif current_tracks > max_tracks :
134
- raise ValueError(f"Number of tracks {current_tracks} exceeds maximum allowed {max_tracks}. that is impossible")
135
- else:
136
- if not is_x_none:
137
- padded_x.append(current_x)
138
-
139
-
140
- if not is_x_none:
141
- x_stacked = torch.stack(padded_x, dim=0) # Shape: [B, max_tracks, C, L]
142
-
143
- return {
144
- 'y': x_stacked.to(device), # Shape: [B, max_tracks, C, L]
145
- "masks": masks.to(device), # Shape: [B, max_tracks]
146
- "paths": paths,
147
- "fs": fs
148
- }
149
-
150
- def collate_multitrack_paired(batch, max_tracks=None, sample_rate=None, segment_length=None, device=None):
151
-
152
- x= [ data_i[0] for data_i in batch ] # x is a list of tensors, each tensor is a track
153
- y= [ data_i[1] for data_i in batch ] # x is a list of tensors, each tensor is a track
154
-
155
- taxonomies=[ data_i[2] for data_i in batch ] # taxonomy is a
156
-
157
- paths=[ data_i[3] for data_i in batch ] # paths is a list of paths to the audio files, each path is a string
158
-
159
- if max_tracks is None:
160
- max_tracks = max(track.shape[0] for track in x) # Find the maximum number of tracks in the batch
161
- else:
162
- if max_tracks < max(track.shape[0] for track in x):
163
- print(f"Warning: max_tracks is set to {max_tracks}, but the maximum number of tracks in the batch is {max(track.shape[0] for track in x)}. I will crop the last tracks. I hope the order is random..." )
164
-
165
- for i in range(len(x)):
166
- if x[i].shape[0] > max_tracks:
167
- print("cropping x[i] to max_tracks")
168
- x[i] = x[i][:max_tracks]
169
- if y[i].shape[0] > max_tracks:
170
- print("cropping y[i] to max_tracks")
171
- y[i] = y[i][:max_tracks]
172
- if len(taxonomies[i]) > max_tracks:
173
- print("cropping taxonomies[i] to max_tracks")
174
- taxonomies[i] = taxonomies[i][:max_tracks]
175
-
176
-
177
-
178
- is_x_none= any(x_i is None for x_i in x)
179
- is_y_none= any(y_i is None for y_i in y)
180
-
181
- assert not (is_x_none and is_y_none), "Either x or y should be None, but not both. This is a bug in the collator"
182
-
183
- # Pad each examples with zeros to the maximum length (dimension 0)
184
- padded_x = []
185
- padded_y = []
186
- padded_taxonomies = []
187
- masks = torch.zeros((len(x), max_tracks), dtype=torch.bool) # Create a mask tensor
188
-
189
- for i in range(len(x)):
190
-
191
- if not is_x_none:
192
- current_x = x[i]
193
- if not is_y_none:
194
- current_y = y[i]
195
- current_taxonomies = taxonomies[i]
196
-
197
- # Get current number of tracks
198
- if is_x_none:
199
- current_tracks = current_y.shape[0]
200
- else:
201
- current_tracks = current_x.shape[0]
202
- if not is_y_none:
203
- assert current_tracks == current_y.shape[0], f"Number of tracks in x ({current_tracks}) does not match number of tracks in y ({current_y.shape[0]})"
204
-
205
- masks[i, :current_tracks] = 1 # Set mask for current tracks
206
-
207
- if current_tracks < max_tracks:
208
- # Pad dimension N (first dimension)
209
- # For a tensor of shape [N, C, L], we need to pad the first dimension
210
- # F.pad expects padding as (pad_left, pad_right, pad_top, pad_bottom, pad_front, pad_back)
211
- # To pad the first dimension, we need to specify padding for the third-from-last dimension
212
- pad_size = (0, 0, # last dim (L): no padding
213
- 0, 0, # second-to-last dim (C): no padding
214
- 0, max_tracks - current_tracks) # third-to-last dim (N): pad at the end
215
-
216
-
217
- if not is_x_none:
218
- padded_x.append(F.pad(current_x, pad_size))
219
- if not is_y_none:
220
- padded_y.append(F.pad(current_y, pad_size))
221
-
222
- # Pad taxonomies
223
- padded_taxonomies.append(current_taxonomies + [None] * (max_tracks - len(current_taxonomies)))
224
- elif current_tracks > max_tracks :
225
- raise ValueError(f"Number of tracks {current_tracks} exceeds maximum allowed {max_tracks}. that is impossible")
226
- else:
227
- if not is_x_none:
228
- padded_x.append(current_x)
229
- if not is_y_none:
230
- padded_y.append(current_y)
231
-
232
- padded_taxonomies.append(current_taxonomies[:max_tracks])
233
-
234
-
235
- if not is_x_none:
236
- x_stacked = torch.stack(padded_x, dim=0) # Shape: [B, max_tracks, C, L]
237
-
238
- if not is_y_none:
239
- y_stacked = torch.stack(padded_y, dim=0) # Shape: [B, max_tracks, C, L]
240
-
241
- if is_x_none:
242
- return {
243
- 'y': y_stacked,
244
- 'taxonomies': padded_taxonomies,
245
- "masks": masks,
246
- "paths": paths
247
- }
248
- if is_y_none:
249
- return {
250
- 'x': x_stacked,
251
- 'taxonomies': padded_taxonomies,
252
- "masks": masks,
253
- "paths": paths
254
- }
255
-
256
- return {
257
- 'x': x_stacked,
258
- 'y': y_stacked,
259
- 'taxonomies': padded_taxonomies,
260
- "masks": masks,
261
- "paths": paths
262
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/common_audioeffects.py DELETED
@@ -1,1729 +0,0 @@
1
- """
2
- Audio effects for data augmentation.
3
-
4
- Several audio effects can be combined into an augmentation chain.
5
-
6
- Important note: We assume that the parallelization during training is done using
7
- multi-processing and not multi-threading. Hence, we do not need the
8
- `@sox.sox_context()` decorators as discussed in this
9
- [thread](https://github.com/pseeth/soxbindings/issues/4).
10
-
11
- Section 2, TL21
12
- AI Speech and Sound Group, SL1
13
- """
14
-
15
- from itertools import permutations
16
- import os
17
- import io
18
- import functools
19
- import lameenc
20
- import logging
21
- import numpy as np
22
- import pymixconsole as pymc
23
- from pymixconsole.parameter import Parameter
24
- from pymixconsole.parameter_list import ParameterList
25
- from pymixconsole.processor import Processor
26
- from random import shuffle
27
- from scipy.signal import oaconvolve
28
- import soundfile as sf
29
- #import soxbindings as sox
30
- from typing import List, Optional, Tuple, Union, Dict
31
- from numba import jit
32
-
33
- #from common_dataprocessing import sample_data
34
-
35
- # prevent pysox from logging warnings regarding non-opimal timestretch factors
36
- logging.getLogger('sox').setLevel(logging.ERROR)
37
-
38
- # set maximum peak value if we pass a signal through SOX
39
- MAX_SOX_PROCESSING_PEAK = 0.707
40
-
41
-
42
- def convert_audio2data(x):
43
- """
44
- Convert audio data from the format it was stored in to float32.
45
-
46
- Args:
47
- x (Numpy array): input with `x.dtype` either `np.int16`, `np.int32`, `np.float32` or `np.float64`.
48
-
49
- Returns:
50
- Numpy array: output with values in [-1., 1.) where `dtype` is `np.float32`.
51
- """
52
- if x.dtype in [np.float32, np.float64]:
53
- return x.astype(dtype=np.float32)
54
- else:
55
- return (x.astype(dtype=np.float64) / (1. + np.iinfo(x.dtype).max)).astype(np.float32)
56
-
57
-
58
-
59
- def sample_data(data: Tuple[int, Union[np.ndarray, functools.partial]],
60
- start: int = 0, length: Optional[int] = None) -> np.ndarray:
61
- """
62
- Load one stem specified by `data`.
63
-
64
- Returns the audio beginning from `start` either up to the end (if `length` is None)
65
- or until the provided `length`. For the case that `start + length > n_samples`, we do a wrap-around and
66
- load the remaining samples from the beginning of `data`.
67
-
68
- Args:
69
- data: Data with shape (n_samples, data).
70
- start: Start index.
71
- length: Length of sample. If `length` is not None, `length` samples are returned (possibly with a wrap-around).
72
- Otherwise, everything until the end of `data` is returned.
73
-
74
- Returns:
75
- samples: data with shape `n_samples x n_channels`
76
- """
77
- n_samples, audio = data
78
-
79
- # determine whether we have to load the audio or whether it was already loaded
80
- is_loaded = True if type(audio) is np.ndarray else False
81
-
82
- # if `length` is not None, then only select subset
83
- do_wrap_around = False
84
- if length is not None:
85
- if start + length > n_samples:
86
- # we need to wrap around and concatenate `start:` and `:stop`
87
- do_wrap_around = True
88
- stop = length - (n_samples - start)
89
- else:
90
- # no wrap around as it is inside the array/file boundaries
91
- stop = start + length
92
- else:
93
- stop = None
94
-
95
- if is_loaded:
96
- if not do_wrap_around:
97
- samples = convert_audio2data(audio[start:stop])
98
- else:
99
- samples = np.vstack((convert_audio2data(audio[start:]),
100
- convert_audio2data(audio[:stop])))
101
- else:
102
- if not do_wrap_around:
103
- samples = convert_audio2data(audio(start=start, stop=stop)[0])
104
- else:
105
- samples = np.vstack((convert_audio2data(audio(start=start)[0]),
106
- convert_audio2data(audio(stop=stop)[0])))
107
-
108
- return samples
109
-
110
-
111
- # Monkey-Patch `Processor` for convenience
112
- # (a) Allow `None` as blocksize if processor can work on variable-length audio
113
- def new_init(self, name, parameters, block_size, sample_rate=None, normalize=None, dtype='float32'):
114
- """
115
- Initialize processor.
116
-
117
- Args:
118
- self: Reference to object
119
- name (str): Name of processor.
120
- parameters (parameter_list): Parameters for this processor.
121
- block_size (int): Size of blocks for blockwise processing.
122
- Can also be `None` if full audio can be processed at once.
123
- sample_rate (int): Sample rate of input audio. Use `None` if effect is independent of this value.
124
- normalize (str): Defines, whether the processed signal is normalized.
125
- Possible values are `'rms'`, `'max'` and `None`.
126
- dtype (str): data type of samples
127
-
128
- Raises:
129
- ValueError: If `normalize` is not equal to `'rms'`, `'max'` or `False`.
130
- """
131
- self.name = name
132
- self.parameters = parameters
133
- self.block_size = block_size
134
- self.sample_rate = sample_rate
135
- self.dtype = dtype
136
- if normalize not in [None, 'rms', 'max']:
137
- raise ValueError(f'Unknown value {normalize} for `normalize`. Must be either `rms`, `max` or `None`')
138
- self.normalize = normalize
139
-
140
-
141
- # (b) make code simpler
142
- def new_update(self, parameter_name):
143
- """
144
- Update processor after randomization of parameters.
145
-
146
- Args:
147
- self: Reference to object.
148
- parameter_name (str): Parameter whose value has changed.
149
- """
150
- pass
151
-
152
-
153
- # (c) representation for nice print
154
- def new_repr(self):
155
- """
156
- Create human-readable representation.
157
-
158
- Args:
159
- self: Reference to object.
160
-
161
- Returns:
162
- string representation of object.
163
- """
164
- return f'Processor(name={self.name!r}, parameters={self.parameters!r}'
165
-
166
-
167
- Processor.__init__ = new_init
168
- Processor.__repr__ = new_repr
169
- Processor.update = new_update
170
-
171
-
172
- class AugmentationChain:
173
- """Basic audio Fx chain which is used for data augmentation."""
174
-
175
- def __init__(self,
176
- fxs: Optional[List[Tuple[Union[Processor, 'AugmentationChain'], float]]] = [],
177
- shuffle: Optional[bool] = False,
178
- apply_to: Optional[str] = 'both'):
179
- """
180
- Create augmentation chain from the dictionary `fxs`.
181
-
182
- Args:
183
- fxs (list of tuples): Each tuple has three elements:
184
- First tuple element is an instance of `pymc.processor` or `AugmentationChain` that
185
- we want to use for data augmentation.
186
- Second element gives probability that effect should be applied.
187
- shuffle (bool): If `True` then order of Fx are changed whenever chain is applied.
188
- apply_to (str): Apply the chain to both input and target or one of them only.
189
- Possible values are `'both'`, `'input'` and `target`.
190
-
191
- Raises:
192
- ValueError: If `apply_to` is not equal to `both`, `input` or `target`.
193
- """
194
- self.fxs = fxs
195
- self.shuffle = shuffle
196
- if apply_to not in ['both', 'input', 'target']:
197
- raise ValueError(f'Unknown value {apply_to} for `apply_to`. Must be `both`, `input` or `target`')
198
- else:
199
- self.apply_to = apply_to
200
-
201
- def apply_processor(self, x, processor: Processor):
202
- """
203
- Pass audio in `x` through `processor` and output the respective processed audio.
204
-
205
- Args:
206
- x (Numpy array): Input audio of shape `n_samples` x `n_channels`.
207
- processor (Processor): Audio effect that we want to apply.
208
-
209
- Returns:
210
- Numpy array: Processed audio of shape `n_samples` x `n_channels` (same size as `x')
211
- """
212
- n_samples_input = x.shape[0]
213
-
214
- if processor.block_size is None:
215
- y = processor.process(x)
216
- else:
217
- # make sure that n_samples is a multiple of `processor.block_size`
218
- if x.shape[0] % processor.block_size != 0:
219
- n_pad = processor.block_size - x.shape[0] % processor.block_size
220
- x = np.pad(x, ((0, n_pad), (0, 0)), mode='reflective')
221
-
222
- y = np.zeros_like(x)
223
- for idx in range(0, x.shape[0], processor.block_size):
224
- y[idx:idx+processor.block_size, :] = processor.process(x[idx:idx+processor.block_size, :])
225
-
226
- if processor.normalize is not None:
227
- if processor.normalize == 'rms': # normalize output energy such that it is the same as the input energy
228
- scale = np.sqrt(np.mean(np.square(x)) / np.maximum(1e-7, np.mean(np.square(y))))
229
- elif processor.normalize == 'max': # normalize output signal by its max. amplitude
230
- scale = (1 + 1e-7)/(np.max(np.abs(y)) + 1e-7)
231
- y *= scale
232
-
233
- # return audio of same length as x
234
- return y[:n_samples_input, :]
235
-
236
- def __call__(self, input_x, target_x):
237
- """
238
- Apply augmentation chain to audio in `input_x` and `target_x`.
239
-
240
- Args:
241
- input_x (Numpy array): Audio samples of shape `n_samples` x `n_channels`.
242
- target_x (Numpy array): Audio samples of shape `n_samples` x `n_channels`.
243
-
244
- Returns:
245
- input_y (Numpy array): Processed audio of same shape as `input_x` where effects have been applied.
246
- target_y (Numpy array): Processed audio of same shape as `target_x` where effects have been applied.
247
- """
248
- # randomly shuffle effect order if `self.shuffle` is True
249
- if self.shuffle:
250
- shuffle(self.fxs)
251
-
252
- input_y = input_x
253
- target_y = target_x
254
-
255
- # check whether we only need to process once later
256
- if self.apply_to == 'both' and np.allclose(input_y, target_y):
257
- is_input_equal_target = True
258
- else:
259
- is_input_equal_target = False
260
-
261
- # apply effects with probabilities given in `self.fxs`
262
- for fx, p in self.fxs:
263
- if np.random.rand() < p:
264
- if isinstance(fx, Processor):
265
- # randomize all effect parameters (also calls `update()` for each processor)
266
- fx.randomize()
267
- # apply processor dependent on `apply_to`
268
- if self.apply_to == 'both':
269
- input_y = self.apply_processor(input_y, fx)
270
- if not is_input_equal_target:
271
- target_y = self.apply_processor(target_y, fx)
272
- elif self.apply_to == 'input':
273
- input_y = self.apply_processor(input_y, fx)
274
- elif self.apply_to == 'target':
275
- target_y = self.apply_processor(target_y, fx)
276
- else:
277
- # apply effect chain
278
- if is_input_equal_target:
279
- target_y = input_y
280
- input_y, target_y = fx(input_y, target_y)
281
- # check whether input and target are still the same
282
- if not fx.apply_to == 'both':
283
- is_input_equal_target = False
284
-
285
- if is_input_equal_target:
286
- target_y = input_y
287
-
288
- return input_y, target_y
289
-
290
- def __repr__(self):
291
- """
292
- Human-readable representation.
293
-
294
- Returns:
295
- string representation of object.
296
- """
297
- return f'AugmentationChain(fxs={self.fxs!r}, shuffle={self.shuffle!r})'
298
-
299
-
300
- # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% DISTORTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
301
- def hard_clip(x, threshold_dB, drive):
302
- """
303
- Hard clip distortion.
304
-
305
- Args:
306
- x: input audio
307
- threshold_dB: threshold
308
- drive: drive
309
-
310
- Returns:
311
- (Numpy array): distorted audio
312
- """
313
- drive_linear = np.power(10., drive / 20.).astype(np.float32)
314
- threshold_linear = 10. ** (threshold_dB / 20.)
315
- return np.clip(x * drive_linear, -threshold_linear, threshold_linear)
316
-
317
-
318
- def overdrive(x, drive, colour, sample_rate):
319
- """
320
- Overdrive distortion.
321
-
322
- Args:
323
- x: input audio
324
- drive: Controls the amount of distortion (dB).
325
- colour: Controls the amount of even harmonic content in the output(dB)
326
- sample_rate: sampling rate
327
-
328
- Returns:
329
- (Numpy array): distorted audio
330
- """
331
- scale = np.max(np.abs(x))
332
- if scale > MAX_SOX_PROCESSING_PEAK:
333
- clips = True
334
- x = x * (MAX_SOX_PROCESSING_PEAK / scale)
335
- else:
336
- clips = False
337
-
338
- tfm = sox.Transformer()
339
- tfm.overdrive(gain_db=drive, colour=colour)
340
- y = tfm.build_array(input_array=x, sample_rate_in=sample_rate).astype(np.float32)
341
-
342
- if clips:
343
- y *= scale / MAX_SOX_PROCESSING_PEAK # rescale output to original scale
344
- return y
345
-
346
-
347
- def hyperbolic_tangent(x, drive):
348
- """
349
- Hyperbolic Tanh distortion.
350
-
351
- Args:
352
- x: input audio
353
- drive: drive
354
-
355
- Returns:
356
- (Numpy array): distorted audio
357
- """
358
- drive_linear = np.power(10., drive / 20.).astype(np.float32)
359
- return np.tanh(2. * x * drive_linear)
360
-
361
-
362
- def soft_sine(x, drive):
363
- """
364
- Soft sine distortion.
365
-
366
- Args:
367
- x: input audio
368
- drive: drive
369
-
370
- Returns:
371
- (Numpy array): distorted audio
372
- """
373
- drive_linear = np.power(10., drive / 20.).astype(np.float32)
374
- y = np.clip(x * drive_linear, -np.pi/4.0, np.pi/4.0)
375
- return np.sin(2. * y)
376
-
377
-
378
- def bit_crusher(x, bits):
379
- """
380
- Bit crusher distortion.
381
-
382
- Args:
383
- x: input audio
384
- bits: bits
385
-
386
- Returns:
387
- (Numpy array): distorted audio
388
- """
389
- return np.rint(x * (2 ** bits)) / (2 ** bits)
390
-
391
-
392
- class Distortion(Processor):
393
- """
394
- Distortion processor.
395
-
396
- Processor parameters:
397
- sample_rate (int): Current (assumed) sample rate of the audio.
398
- mode (str): Currently supports the following five modes: hard_clip, waveshaper, soft_sine, tanh, bit_crusher.
399
- Each mode has different parameters such as threshold, factor, or bits.
400
- threshold (float): threshold
401
- drive (float): drive
402
- factor (float): factor
403
- limit_range (float): limit range
404
- bits (int): bits
405
- """
406
-
407
- def __init__(self, sample_rates, name='Distortion', parameters=None, **kwargs):
408
- """
409
- Initialize processor.
410
-
411
- Args:
412
- sample_rates (list of ints): sample rates of audio.
413
- name (str): Name of processor.
414
- parameters (parameter_list): Parameters for this processor.
415
- **kwargs (dict): Keyword arguments that are passed to the Processor class.
416
- """
417
- super().__init__(name, None, block_size=None, **kwargs)
418
- if not parameters:
419
- self.parameters = ParameterList()
420
- self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
421
- self.parameters.add(Parameter('mode', 'hard_clip', 'string',
422
- options=['hard_clip',
423
- 'overdrive',
424
- 'soft_sine',
425
- 'tanh',
426
- 'bit_crusher']))
427
- self.parameters.add(Parameter('threshold', 0.0, 'float',
428
- units='dB', maximum=0.0, minimum=-20.0))
429
- self.parameters.add(Parameter('drive', 0.0, 'float',
430
- units='dB', maximum=20.0, minimum=0.0))
431
- self.parameters.add(Parameter('colour', 20.0, 'float',
432
- maximum=100.0, minimum=0.0))
433
- self.parameters.add(Parameter('bits', 12, 'int',
434
- maximum=12, minimum=8))
435
- else:
436
- self.parameters = parameters
437
-
438
- def process(self, x):
439
- """
440
- Process audio.
441
-
442
- Args:
443
- x (Numpy array): input audio of size `n_samples x n_channels`.
444
-
445
- Returns:
446
- (Numpy array): distorted audio of size `n_samples x n_channels`.
447
- """
448
- if self.parameters.mode.value == 'hard_clip':
449
- y = hard_clip(x, self.parameters.threshold.value, self.parameters.drive.value)
450
- elif self.parameters.mode.value == 'overdrive':
451
- y = overdrive(x, self.parameters.drive.value,
452
- self.parameters.colour.value, self.parameters.sample_rate.value)
453
- elif self.parameters.mode.value == 'soft_sine':
454
- y = soft_sine(x, self.parameters.drive.value)
455
- elif self.parameters.mode.value == 'tanh':
456
- y = hyperbolic_tangent(x, self.parameters.drive.value)
457
- elif self.parameters.mode.value == 'bit_crusher':
458
- y = bit_crusher(x, self.parameters.bits.value)
459
-
460
- return y
461
-
462
-
463
- # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% EQUALISER %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
464
- class Equaliser(Processor):
465
- """
466
- Five band parametric equaliser (two shelves and three central bands).
467
-
468
- All gains are set in dB values and range from `MIN_GAIN` dB to `MAX_GAIN` dB.
469
- This processor is implemented as cascade of five biquad IIR filters
470
- that are implemented using the infamous cookbook formulae from RBJ.
471
-
472
- Processor parameters:
473
- sample_rate (int): Current (assumed) sample rate of the audio.
474
- low_shelf_gain (float), low_shelf_freq (float)
475
- first_band_gain (float), first_band_freq (float), first_band_q (float)
476
- second_band_gain (float), second_band_freq (float), second_band_q (float)
477
- third_band_gain (float), third_band_freq (float), third_band_q (float)
478
-
479
- original from https://github.com/csteinmetz1/pymixconsole/blob/master/pymixconsole/processors/equaliser.py
480
- """
481
-
482
- def __init__(self, n_channels, sample_rates, gain_range=(-15.0, 15.0), q_range=(0.1, 2.0), hard_clip=False,
483
- name='Equaliser', parameters=None, **kwargs):
484
- """
485
- Initialize processor.
486
-
487
- Args:
488
- n_channels (int): Number of audio channels.
489
- sample_rates (list of ints): Sample rates of audio.
490
- gain_range (tuple of floats): minimum and maximum gain that can be used.
491
- q_range (tuple of floats): minimum and maximum q value.
492
- hard_clip (bool): Whether we clip to [-1, 1.] after processing.
493
- name (str): Name of processor.
494
- parameters (parameter_list): Parameters for this processor.
495
- **kwargs (dict): Keyword arguments that are passed to the Processor class.
496
- """
497
- super().__init__(name, parameters=parameters, block_size=None, **kwargs)
498
-
499
- self.n_channels = n_channels
500
-
501
- MIN_GAIN, MAX_GAIN = gain_range
502
- MIN_Q, MAX_Q = q_range
503
-
504
- if not parameters:
505
- self.parameters = ParameterList()
506
- self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
507
- # low shelf parameters -------
508
- self.parameters.add(Parameter('low_shelf_gain', 0.0, 'float', minimum=MIN_GAIN, maximum=MAX_GAIN))
509
- self.parameters.add(Parameter('low_shelf_freq', 80.0, 'float', minimum=30.0, maximum=200.0))
510
- # first band parameters ------
511
- self.parameters.add(Parameter('first_band_gain', 0.0, 'float', minimum=MIN_GAIN, maximum=MAX_GAIN))
512
- self.parameters.add(Parameter('first_band_freq', 400.0, 'float', minimum=200.0, maximum=1000.0))
513
- self.parameters.add(Parameter('first_band_q', 0.7, 'float', minimum=MIN_Q, maximum=MAX_Q))
514
- # second band parameters -----
515
- self.parameters.add(Parameter('second_band_gain', 0.0, 'float', minimum=MIN_GAIN, maximum=MAX_GAIN))
516
- self.parameters.add(Parameter('second_band_freq', 2000.0, 'float', minimum=1000.0, maximum=3000.0))
517
- self.parameters.add(Parameter('second_band_q', 0.7, 'float', minimum=MIN_Q, maximum=MAX_Q))
518
- # third band parameters ------
519
- self.parameters.add(Parameter('third_band_gain', 0.0, 'float', minimum=MIN_GAIN, maximum=MAX_GAIN))
520
- self.parameters.add(Parameter('third_band_freq', 4000.0, 'float', minimum=3000.0, maximum=8000.0))
521
- self.parameters.add(Parameter('third_band_q', 0.7, 'float', minimum=MIN_Q, maximum=MAX_Q))
522
- # high shelf parameters ------
523
- self.parameters.add(Parameter('high_shelf_gain', 0.0, 'float', minimum=MIN_GAIN, maximum=MAX_GAIN))
524
- self.parameters.add(Parameter('high_shelf_freq', 8000.0, 'float', minimum=5000.0, maximum=10000.0))
525
- else:
526
- self.parameters = parameters
527
-
528
- self.bands = ['low_shelf', 'first_band', 'second_band', 'third_band', 'high_shelf']
529
- self.filters = self.setup_filters()
530
- self.hard_clip = hard_clip
531
-
532
- def setup_filters(self):
533
- """
534
- Create IIR filters.
535
-
536
- Returns:
537
- IIR filters
538
- """
539
- filters = {}
540
-
541
- for band in self.bands:
542
-
543
- G = getattr(self.parameters, band + '_gain').value
544
- fc = getattr(self.parameters, band + '_freq').value
545
- rate = self.parameters.sample_rate.value
546
-
547
- if band in ['low_shelf', 'high_shelf']:
548
- Q = 0.707
549
- filter_type = band
550
- else:
551
- Q = getattr(self.parameters, band + '_q').value
552
- filter_type = 'peaking'
553
-
554
- filters[band] = pymc.components.iirfilter.IIRfilter(G, Q, fc, rate, filter_type, n_channels=self.n_channels)
555
-
556
- return filters
557
-
558
- def update_filter(self, band):
559
- """
560
- Update filters.
561
-
562
- Args:
563
- band (str): Band that should be updated.
564
- """
565
- self.filters[band].G = getattr(self.parameters, band + '_gain').value
566
- self.filters[band].fc = getattr(self.parameters, band + '_freq').value
567
- self.filters[band].rate = self.parameters.sample_rate.value
568
-
569
- if band in ['first_band', 'second_band', 'third_band']:
570
- self.filters[band].Q = getattr(self.parameters, band + '_q').value
571
-
572
- def update(self, parameter_name=None):
573
- """
574
- Update processor after randomization of parameters.
575
-
576
- Args:
577
- parameter_name (str): Parameter whose value has changed.
578
- """
579
- if parameter_name is not None:
580
- bands = ['_'.join(parameter_name.split('_')[:2])]
581
- else:
582
- bands = self.bands
583
-
584
- for band in bands:
585
- self.update_filter(band)
586
-
587
- def process(self, x):
588
- """
589
- Process audio.
590
-
591
- Args:
592
- x (Numpy array): input audio of size `n_samples x n_channels`.
593
-
594
- Returns:
595
- (Numpy array): equalized audio of size `n_samples x n_channels`.
596
- """
597
- for _band, iirfilter in self.filters.items():
598
- iirfilter.reset_state()
599
- x = iirfilter.apply_filter(x)
600
-
601
- if self.hard_clip:
602
- x = np.clip(x, -1.0, 1.0)
603
-
604
- # make sure that we have float32 as IIR filtering returns float64
605
- x = x.astype(np.float32)
606
-
607
- # make sure that we have two dimensions (if `n_channels == 1`)
608
- if x.ndim == 1:
609
- x = x[:, np.newaxis]
610
-
611
- return x
612
-
613
-
614
- # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% COMPRESSOR %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
615
- @jit(nopython=True)
616
- def compressor_process(x, threshold, attack_time, release_time, ratio, makeup_gain, sample_rate):
617
- """
618
- Apply compressor.
619
-
620
- Args:
621
- x (Numpy array): audio data.
622
- threshold: threshold in dB.
623
- attack_time: attack_time in ms.
624
- release_time: release_time in ms.
625
- ratio: ratio.
626
- makeup_gain: makeup_gain.
627
- sample_rate: sample rate.
628
-
629
- Returns:
630
- compressed audio.
631
- """
632
- M = x.shape[0]
633
- x_g = np.zeros(M)
634
- x_l = np.zeros(M)
635
- y_g = np.zeros(M)
636
- y_l = np.zeros(M)
637
- c = np.zeros(M)
638
- yL_prev = 0.
639
-
640
- alpha_attack = np.exp(-1/(0.001 * sample_rate * attack_time))
641
- alpha_release = np.exp(-1/(0.001 * sample_rate * release_time))
642
-
643
- for i in np.arange(M):
644
- if np.abs(x[i]) < 0.000001:
645
- x_g[i] = -120.0
646
- else:
647
- x_g[i] = 20 * np.log10(np.abs(x[i]))
648
-
649
- if x_g[i] >= threshold:
650
- y_g[i] = threshold + (x_g[i] - threshold) / ratio
651
- else:
652
- y_g[i] = x_g[i]
653
-
654
- x_l[i] = x_g[i] - y_g[i]
655
-
656
- if x_l[i] > yL_prev:
657
- y_l[i] = alpha_attack * yL_prev + (1 - alpha_attack) * x_l[i]
658
- else:
659
- y_l[i] = alpha_release * yL_prev + (1 - alpha_release) * x_l[i]
660
-
661
- c[i] = np.power(10.0, (makeup_gain - y_l[i]) / 20.0)
662
- yL_prev = y_l[i]
663
-
664
- y = x * c
665
-
666
- return y
667
-
668
-
669
- class Compressor(Processor):
670
- """
671
- Single band stereo dynamic range compressor.
672
-
673
- Processor parameters:
674
- sample_rate (int): Current (assumed) sample rate of the audio.
675
- threshold (float)
676
- attack_time (float)
677
- release_time (float)
678
- ratio (float)
679
- makeup_gain (float)
680
- """
681
-
682
- def __init__(self, sample_rates, name='Compressor', parameters=None, **kwargs):
683
- """
684
- Initialize processor.
685
-
686
- Args:
687
- sample_rates (list of ints): Sample rates of input audio.
688
- name (str): Name of processor.
689
- parameters (parameter_list): Parameters for this processor.
690
- **kwargs (dict): Keyword arguments that are passed to the Processor class.
691
- """
692
- super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
693
-
694
- if not parameters:
695
- self.parameters = ParameterList()
696
- self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
697
- self.parameters.add(Parameter('threshold', 0.0, 'float', units='dB', minimum=-40.0, maximum=0.0))
698
- self.parameters.add(Parameter('attack_time', 2.0, 'float', units='ms', minimum=0.03, maximum=30.0))
699
- self.parameters.add(Parameter('release_time', 50.0, 'float', units='ms', minimum=50.0, maximum=100.0))
700
- self.parameters.add(Parameter('ratio', 2.0, 'float', minimum=2.0, maximum=10.0))
701
- self.parameters.add(Parameter('makeup_gain', 0.0, 'float', units='dB', minimum=-3.0, maximum=6.0))
702
- else:
703
- self.parameters = parameters
704
-
705
- def process(self, x):
706
- """
707
- Process audio.
708
-
709
- Args:
710
- x (Numpy array): input audio of size `n_samples x n_channels`.
711
-
712
- Returns:
713
- (Numpy array): compressed audio of size `n_samples x n_channels`.
714
- """
715
- if not self.parameters.threshold.value == 0.0:
716
- y = np.zeros_like(x)
717
-
718
- for ch in range(x.shape[1]):
719
- y[:, ch] = compressor_process(x[:, ch],
720
- self.parameters.threshold.value,
721
- self.parameters.attack_time.value,
722
- self.parameters.release_time.value,
723
- self.parameters.ratio.value,
724
- self.parameters.makeup_gain.value,
725
- self.parameters.sample_rate.value)
726
- else:
727
- y = x
728
-
729
- return y
730
-
731
-
732
- # %%%%%%%%%%%%%%%%%%%%%%%%%% CONVOLUTIONAL REVERB %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
733
- class ConvolutionalReverb(Processor):
734
- """
735
- Convolutional Reverb.
736
-
737
- Important: Due to convolving the audio sequence with some impulse response, we should ignore the
738
- first/last samples of the augmented audio sequence using `config['AUGMENTER_PADDING']`.
739
-
740
- Processor parameters:
741
- sample_rate (int): Current (assumed) sample rate of the audio.
742
- wet_dry (float): Wet/dry ratio.
743
- decay (float): Applies a fade out to the impulse response.
744
- pre_delay (float): Value in ms. Shifts the IR in time and allows.
745
- A positive value produces a traditional delay between the dry signal and the wet.
746
- A negative delay is, in reality, zero delay, but effectively trims off the start of IR,
747
- so the reverb response begins at a point further in.
748
- """
749
-
750
- def __init__(self, impulse_responses, sample_rates, name='ConvolutionalReverb', parameters=None, **kwargs):
751
- """
752
- Initialize processor.
753
-
754
- Args:
755
- impulse_responses (list): List with impulse responses created by `common_dataprocessing.create_dataset`
756
- sample_rates (list of ints): Sample rates that we should assume (used for fade-out computation)
757
- name (str): Name of processor.
758
- parameters (parameter_list): Parameters for this processor.
759
- **kwargs (dict): Keyword arguments that are passed to the Processor class.
760
-
761
- Raises:
762
- ValueError: if no impulse responses are provided.
763
- """
764
- super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
765
-
766
- if impulse_responses is None:
767
- raise ValueError('List of impulse responses must be provided for ConvolutionalReverb processor.')
768
- self.impulse_responses = impulse_responses
769
-
770
- if not parameters:
771
- self.parameters = ParameterList()
772
- self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
773
- self.parameters.add(Parameter('index', 0, 'int', minimum=0, maximum=len(impulse_responses)))
774
- self.parameters.add(Parameter('wet_dry', 1.0, 'float', minimum=0.1, maximum=1.0))
775
- self.parameters.add(Parameter('decay', 1.0, 'float', minimum=0.1, maximum=1.0))
776
- self.parameters.add(Parameter('pre_delay', 0, 'int', units='ms', minimum=-100, maximum=100))
777
- else:
778
- self.parameters = parameters
779
-
780
- def update(self, parameter_name=None):
781
- """
782
- Update processor after randomization of parameters.
783
-
784
- Args:
785
- parameter_name (str): Parameter whose value has changed.
786
- """
787
- # copy IR from current index (to avoid modifying it in-place)
788
- self.h = np.copy(sample_data(self.impulse_responses.loc[self.parameters.index.value]['impulse_response']))
789
-
790
- # fade out the impulse based on the decay setting (starting from peak value) - constant 20ms fade-out
791
- if self.parameters.decay.value < 1.:
792
- idx_peak = np.argmax(np.max(np.abs(self.h), axis=1), axis=0)
793
- fstart = np.minimum(self.h.shape[0],
794
- idx_peak + int(self.parameters.decay.value * (self.h.shape[0] - idx_peak)))
795
- fstop = np.minimum(self.h.shape[0], fstart + int(0.020*self.parameters.sample_rate.value))
796
- flen = fstop - fstart
797
-
798
- fade = np.arange(1, flen+1, dtype=self.dtype)/flen
799
- fade = np.power(0.1, fade * 5)
800
- self.h[fstart:fstop, :] *= fade[:, np.newaxis]
801
- self.h = self.h[:fstop]
802
-
803
- def process(self, x):
804
- """
805
- Process audio.
806
-
807
- Args:
808
- x (Numpy array): input audio of size `n_samples x n_channels`.
809
-
810
- Returns:
811
- (Numpy array): reverbed audio of size `n_samples x n_channels`.
812
- """
813
- # reshape IR to the correct size
814
- n_channels = x.shape[1]
815
- if self.h.shape[1] == 1 and n_channels > 1:
816
- self.h = np.hstack([self.h] * n_channels) # repeat mono IR for multi-channel input
817
- if self.h.shape[1] > 1 and n_channels == 1:
818
- self.h = self.h[:, np.random.randint(self.h.shape[1]), np.newaxis] # randomly choose one IR channel
819
-
820
- if self.parameters.wet_dry.value == 0.0:
821
- return x
822
- else:
823
- # perform convolution to get wet signal
824
- y = oaconvolve(x, self.h, mode='full', axes=0)
825
-
826
- # cut out wet signal (compensating for the delay that the IR is introducing + predelay)
827
- idx = np.argmax(np.max(np.abs(self.h), axis=1), axis=0)
828
- idx += int(0.001 * self.parameters.pre_delay.value * self.parameters.sample_rate.value)
829
-
830
- idx = np.clip(idx, 0, self.h.shape[0]-1)
831
-
832
- y = y[idx:idx+x.shape[0], :]
833
-
834
- # return weighted sum of dry and wet signal
835
- return (1.0 - self.parameters.wet_dry.value) * x + self.parameters.wet_dry.value * y
836
-
837
-
838
- # %%%%%%%%%%%%%%%%%%%%%%%%%%%%% HAAS EFFECT %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
839
- def haas_process(x, delay, feedback, wet_channel):
840
- """
841
- Add Haas effect to audio.
842
-
843
- Args:
844
- x (Numpy array): input audio.
845
- delay: Delay that we apply to one of the channels (in samples).
846
- feedback: Feedback value.
847
- wet_channel: Which channel we process (`left` or `right`).
848
-
849
- Returns:
850
- (Numpy array): Audio with Haas effect.
851
- """
852
- y = np.copy(x)
853
- if wet_channel == 'left':
854
- y[:, 0] += feedback * np.roll(x[:, 0], delay)
855
- elif wet_channel == 'right':
856
- y[:, 1] += feedback * np.roll(x[:, 1], delay)
857
-
858
- return y
859
-
860
-
861
- class Haas(Processor):
862
- """
863
- Haas Effect Processor.
864
-
865
- Randomly selects one channel and applies a short delay to it.
866
-
867
- Important: This audio effect uses `np.roll` to perform the shift of one channel. Hence, you should use
868
- `config['AUGMENTER_PADDING']` to ignore the first samples of the augmented audio sequence.
869
-
870
- Processor parameters:
871
- sample_rate (int): Current (assumed) sample rate of the audio.
872
- delay (float)
873
- feedback (float)
874
- wet_channel (string)
875
- """
876
-
877
- def __init__(self, sample_rates, delay_range=(-0.040, 0.040), name='Haas', parameters=None, **kwargs):
878
- """
879
- Initialize processor.
880
-
881
- Args:
882
- sample_rates (list of ints): Sample rates of input audio.
883
- delay_range (tuple of floats): minimum/maximum delay in milliseconds for Haas effect.
884
- name (str): Name of processor.
885
- parameters (parameter_list): Parameters for this processor.
886
- **kwargs (dict): Keyword arguments that are passed to the Processor class.
887
- """
888
- super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
889
-
890
- if not parameters:
891
- self.parameters = ParameterList()
892
- self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
893
- self.parameters.add(Parameter('delay', delay_range[1], 'float', units='ms',
894
- minimum=delay_range[0], maximum=delay_range[1]))
895
- self.parameters.add(Parameter('feedback', 0.35, 'float', minimum=0.33, maximum=0.66))
896
- self.parameters.add(Parameter('wet_channel', 'left', 'string', options=['left', 'right']))
897
- else:
898
- self.parameters = parameters
899
-
900
- def process(self, x):
901
- """
902
- Process audio.
903
-
904
- Args:
905
- x (Numpy array): input audio of size `n_samples x n_channels`.
906
-
907
- Returns:
908
- (Numpy array): audio with Haas effect of size `n_samples x n_channels`.
909
- """
910
- assert x.shape[1] == 1 or x.shape[1] == 2, 'Haas effect only works with monaural or stereo audio.'
911
-
912
- if x.shape[1] < 2:
913
- x = np.repeat(x, 2, axis=1)
914
-
915
- y = haas_process(x, int(self.parameters.delay.value * self.parameters.sample_rate.value),
916
- self.parameters.feedback.value, self.parameters.wet_channel.value)
917
-
918
- return y
919
-
920
-
921
- # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PANNER %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
922
- class Panner(Processor):
923
- """
924
- Simple stereo panner (adjusting amplitude and delay).
925
-
926
- If input is mono, output is stereo.
927
- Original edited from https://github.com/csteinmetz1/pymixconsole/blob/master/pymixconsole/processors/panner.py
928
-
929
- Important: This audio effect uses `np.roll` to perform the shift of one channel. Hence, you should use
930
- `config['AUGMENTER_PADDING']` to ignore the first samples of the augmented audio sequence.
931
-
932
- Processor parameters:
933
- sample_rate (int): Current (assumed) sample rate of the audio.
934
- pan (float): Panning angle. Can take values in [0, 1] where `0` corresponds to fully panned to left
935
- and `1` corresponds to fully panned to the right.
936
- pan_law (str): Pan law to be used for amplitude panning. Can be '-4.5dB', 'linear' or 'constant_power'.
937
- pan_mode (str): Scheme that is used for panning. Can be 'amplitude', 'delay' or 'both'.
938
- pan_maxdelay (float): Maximum delay if we pan a source fully to the left/right.
939
- For example `2. / 343.` is the maximum delay if the microphones are 2 meters apart.
940
- """
941
-
942
- def __init__(self, sample_rates, maxdelay_range=(0, 2. / 343.0), name='Panner', parameters=None, **kwargs):
943
- """
944
- Initialize processor.
945
-
946
- Args:
947
- sample_rates (list of ints): Sample rates of input audio.
948
- maxdelay_range (tuple of floats): minimum/maximum delay for panning effect. `2. / 343.` corresponds to
949
- the maximum delay if we have a microphone array where the microphones are 2 meters apart.
950
- name (str): Name of processor.
951
- parameters (parameter_list): Parameters for this processor.
952
- **kwargs (dict): Keyword arguments that are passed to the Processor class.
953
- """
954
- # default processor class constructor
955
- super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
956
-
957
- if not parameters:
958
- self.parameters = ParameterList()
959
- self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
960
- self.parameters.add(Parameter('pan', 0.5, 'float', minimum=0.1, maximum=0.9))
961
- self.parameters.add(Parameter('pan_law', '-4.5dB', 'string',
962
- options=['-4.5dB', 'linear', 'constant_power']))
963
- self.parameters.add(Parameter('pan_mode', 'amplitude', 'string',
964
- options=['amplitude', 'delay', 'both']))
965
- self.parameters.add(Parameter('pan_maxdelay', (maxdelay_range[0] + maxdelay_range[1]) / 2., 'float',
966
- units='ms', minimum=maxdelay_range[0], maximum=maxdelay_range[1]))
967
- else:
968
- self.parameters = parameters
969
-
970
- # setup the coefficents based on default params
971
- self.update()
972
-
973
- def _calculate_pan_coefficents(self):
974
- """
975
- Calculate panning coefficients from the chosen pan law.
976
-
977
- Based on the set pan law determine the gain value
978
- to apply for the left and right channel to achieve panning effect.
979
- This operates on the assumption that the input channel is mono.
980
- The output data will be stereo at the moment, but could be expanded
981
- to a higher channel count format.
982
- The panning value is in the range [0, 1], where
983
- 0 means the signal is panned completely to the left, and
984
- 1 means the signal is apanned copletely to the right.
985
-
986
- Raises:
987
- ValueError: `self.parameters.pan_law` is not supported.
988
- """
989
- self.gains = np.zeros(2, dtype=self.dtype)
990
-
991
- # first scale the linear [0, 1] to [0, pi/2]
992
- theta = self.parameters.pan.value * (np.pi/2)
993
-
994
- if self.parameters.pan_law.value == 'linear':
995
- self.gains[0] = ((np.pi/2) - theta) * (2/np.pi)
996
- self.gains[1] = theta * (2/np.pi)
997
- elif self.parameters.pan_law.value == 'constant_power':
998
- self.gains[0] = np.cos(theta)
999
- self.gains[1] = np.sin(theta)
1000
- elif self.parameters.pan_law.value == '-4.5dB':
1001
- self.gains[0] = np.sqrt(((np.pi/2) - theta) * (2/np.pi) * np.cos(theta))
1002
- self.gains[1] = np.sqrt(theta * (2/np.pi) * np.sin(theta))
1003
- else:
1004
- raise ValueError(f'Invalid pan_law {self.parameters.pan_law.value}.')
1005
-
1006
- def _calculate_pan_delay(self):
1007
- """Calculate delay for the chosen pan angle."""
1008
- self.shifts = np.zeros(2, dtype=np.int32)
1009
-
1010
- # compute overall shift that we need between the two channels
1011
- pan_maxdelay_samples = self.parameters.pan_maxdelay.value * self.parameters.sample_rate.value
1012
- shift = 2 * int(pan_maxdelay_samples * np.abs(self.parameters.pan.value - 0.5))
1013
-
1014
- if self.parameters.pan.value < 0.5:
1015
- # panning to the left
1016
- self.shifts[0] = -shift // 2
1017
- self.shifts[1] = shift - shift // 2
1018
- else:
1019
- self.shifts[0] = shift - shift // 2
1020
- self.shifts[1] = -shift // 2
1021
-
1022
- def process(self, x):
1023
- """
1024
- Process audio.
1025
-
1026
- Args:
1027
- x (Numpy array): input audio of size `n_samples x n_channels`.
1028
-
1029
- Returns:
1030
- (Numpy array): panned audio of size `n_samples x n_channels`.
1031
- """
1032
- assert x.shape[1] == 1 or x.shape[1] == 2, 'Panner only works with monaural or stereo audio.'
1033
-
1034
- # convert to stereo if signal is monaural
1035
- if x.shape[1] < 2:
1036
- x = np.repeat(x, 2, axis=1)
1037
-
1038
- y = np.copy(x)
1039
- if self.parameters.pan_mode.value in ['delay', 'both']:
1040
- y[:, 0] = np.roll(y[:, 0], self.shifts[0])
1041
- y[:, 1] = np.roll(y[:, 1], self.shifts[1])
1042
-
1043
- if self.parameters.pan_mode.value in ['amplitude', 'both']:
1044
- y *= self.gains
1045
-
1046
- return y
1047
-
1048
- def update(self, parameter_name=None):
1049
- """
1050
- Update processor after randomization of parameters.
1051
-
1052
- Args:
1053
- parameter_name (str): Parameter whose value has changed.
1054
- """
1055
- self._calculate_pan_coefficents()
1056
- self._calculate_pan_delay()
1057
-
1058
-
1059
- # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% GAIN %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1060
- class Gain(Processor):
1061
- """
1062
- Gain Processor.
1063
-
1064
- Applies gain in dB and optionally inverts polarity.
1065
-
1066
- Processor parameters:
1067
- gain (float): Gain that should be applied (dB scale).
1068
- invert (bool): If True, then we also invert the waveform (all channels jointly).
1069
- """
1070
-
1071
- def __init__(self, name='Gain', parameters=None, **kwargs):
1072
- """
1073
- Initialize processor.
1074
-
1075
- Args:
1076
- name (str): Name of processor.
1077
- parameters (parameter_list): Parameters for this processor.
1078
- **kwargs (dict): Keyword arguments that are passed to the Processor class.
1079
- """
1080
- super().__init__(name, parameters=parameters, block_size=None, **kwargs)
1081
-
1082
- if not parameters:
1083
- self.parameters = ParameterList()
1084
- self.parameters.add(Parameter('gain', 1.0, 'float', units='dB', minimum=-6.0, maximum=6.0))
1085
- self.parameters.add(Parameter('invert', False, 'bool'))
1086
- else:
1087
- self.parameters = parameters
1088
-
1089
- def process(self, x):
1090
- """
1091
- Process audio.
1092
-
1093
- Args:
1094
- x (Numpy array): input audio of size `n_samples x n_channels`.
1095
-
1096
- Returns:
1097
- (Numpy array): gain-augmented audio of size `n_samples x n_channels`.
1098
- """
1099
- gain = 10 ** (self.parameters.gain.value / 20.)
1100
- if self.parameters.invert.value:
1101
- gain = -gain
1102
- return gain * x
1103
-
1104
-
1105
- # %%%%%%%%%%%%%%%%%%%%%%% SIMPLE CHANNEL SWAP %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1106
- class SwapChannels(Processor):
1107
- """
1108
- Swap channels in multi-channel audio.
1109
-
1110
- Processor parameters:
1111
- index (int) Selects the permutation that we are using.
1112
- Please note that "no permutation" is one of the permutations in `self.permutations` at index `0`.
1113
- Hence, this effect should be applied with probability "1.".
1114
- """
1115
-
1116
- def __init__(self, n_channels, name='SwapChannels', parameters=None, **kwargs):
1117
- """
1118
- Initialize processor.
1119
-
1120
- Args:
1121
- n_channels (int): Number of channels in audio that we want to process.
1122
- name (str): Name of processor.
1123
- parameters (parameter_list): Parameters for this processor.
1124
- **kwargs (dict): Keyword arguments that are passed to the Processor class.
1125
- """
1126
- super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
1127
-
1128
- self.permutations = tuple(permutations(range(n_channels), n_channels))
1129
-
1130
- if not parameters:
1131
- self.parameters = ParameterList()
1132
- self.parameters.add(Parameter('index', 0, 'int', minimum=0, maximum=len(self.permutations)))
1133
- else:
1134
- self.parameters = parameters
1135
-
1136
- def process(self, x):
1137
- """
1138
- Process audio.
1139
-
1140
- Args:
1141
- x (Numpy array): input audio of size `n_samples x n_channels`.
1142
-
1143
- Returns:
1144
- (Numpy array): channel-swapped audio of size `n_samples x n_channels`.
1145
- """
1146
- return x[:, self.permutations[self.parameters.index.value]]
1147
-
1148
-
1149
- # %%%%%%%%%%%%%%%%%%%%%%% Monauralize %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1150
- class Monauralize(Processor):
1151
- """
1152
- Monauralizes audio (i.e., removes spatial information).
1153
-
1154
- Process parameters:
1155
- seed_channel (int): channel that we use for overwriting the others.
1156
- `-1` refers to using the mean over all channels.
1157
- """
1158
-
1159
- def __init__(self, n_channels, name='Monauralize', parameters=None, **kwargs):
1160
- """
1161
- Initialize processor.
1162
-
1163
- Args:
1164
- n_channels (int): Number of channels in audio that we want to process.
1165
- name (str): Name of processor.
1166
- parameters (parameter_list): Parameters for this processor.
1167
- **kwargs (dict): Keyword arguments that are passed to the Processor class.
1168
- """
1169
- super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
1170
-
1171
- if not parameters:
1172
- self.parameters = ParameterList()
1173
- self.parameters.add(Parameter('seed_channel', 0, 'int', minimum=-1, maximum=n_channels))
1174
- else:
1175
- self.parameters = parameters
1176
-
1177
- def process(self, x):
1178
- """
1179
- Process audio.
1180
-
1181
- Args:
1182
- x (Numpy array): input audio of size `n_samples x n_channels`.
1183
-
1184
- Returns:
1185
- (Numpy array): monauralized audio of size `n_samples x n_channels`.
1186
- """
1187
- tile_reps = (1, x.shape[1])
1188
- if self.parameters.seed_channel.value == -1:
1189
- # use average as seed
1190
- return np.tile(np.mean(x, axis=1, keepdims=True), tile_reps)
1191
- else:
1192
- # use one channel as seed
1193
- return np.tile(x[:, [self.parameters.seed_channel.value]], tile_reps)
1194
-
1195
-
1196
- # %%%%%%%%%%%%%%%%%%%%%%% ChunkShuffle %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1197
- class ChunkShuffle(Processor):
1198
- """
1199
- Split audio into chunks and randomly re-arrange it.
1200
-
1201
- Process parameters:
1202
- n_chunks (int): number of chunks into which we split
1203
- """
1204
-
1205
- def __init__(self, name='ChunkShuffle', parameters=None, **kwargs):
1206
- """
1207
- Initialize processor.
1208
-
1209
- Args:
1210
- name (str): Name of processor.
1211
- parameters (parameter_list): Parameters for this processor.
1212
- **kwargs (dict): Keyword arguments that are passed to the Processor class.
1213
- """
1214
- super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
1215
-
1216
- if not parameters:
1217
- self.parameters = ParameterList()
1218
- self.parameters.add(Parameter('n_chunks', 2, 'int', minimum=2, maximum=6))
1219
- else:
1220
- self.parameters = parameters
1221
-
1222
- def process(self, x):
1223
- """
1224
- Process audio.
1225
-
1226
- Args:
1227
- x (Numpy array): input audio of size `n_samples x n_channels`.
1228
-
1229
- Returns:
1230
- (Numpy array): chunk-shuffled audio of size `n_samples x n_channels`.
1231
- """
1232
- # determine random chunk boundaries
1233
- boundaries = np.sort(np.random.randint(x.shape[0], size=self.parameters.n_chunks.value-1))
1234
-
1235
- # create chunks
1236
- chunks = np.split(x, boundaries)
1237
-
1238
- # shuffle them
1239
- np.random.shuffle(chunks)
1240
-
1241
- # return chunks in new random order
1242
- return np.concatenate(chunks)
1243
-
1244
-
1245
- # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PITCH SHIFT %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1246
- class PitchShift(Processor):
1247
- """
1248
- Simple pitch shifter using SoX and soxbindings (https://github.com/pseeth/soxbindings).
1249
-
1250
- Processor parameters:
1251
- sample_rate (int): Current (assumed) sample rate of the audio.
1252
- steps (float): Pitch shift as positive/negative semitones
1253
- quick (bool): If True, this effect will run faster but with lower sound quality.
1254
- """
1255
-
1256
- def __init__(self, sample_rates, fix_length=True, name='PitchShift', parameters=None, **kwargs):
1257
- """
1258
- Initialize processor.
1259
-
1260
- Args:
1261
- sample_rates (list of ints): Sample rates of input audio.
1262
- fix_length (bool): If True, then output has same length as input.
1263
- name (str): Name of processor.
1264
- parameters (parameter_list): Parameters for this processor.
1265
- **kwargs (dict): Keyword arguments that are passed to the Processor class.
1266
- """
1267
- super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
1268
-
1269
- if not parameters:
1270
- self.parameters = ParameterList()
1271
- self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
1272
- self.parameters.add(Parameter('steps', 0.0, 'float', minimum=-6., maximum=6.))
1273
- self.parameters.add(Parameter('quick', False, 'bool'))
1274
- else:
1275
- self.parameters = parameters
1276
-
1277
- self.fix_length = fix_length
1278
- self.clips = False
1279
-
1280
- def process(self, x):
1281
- """
1282
- Process audio.
1283
-
1284
- Args:
1285
- x (Numpy array): input audio of size `n_samples x n_channels`.
1286
-
1287
- Returns:
1288
- (Numpy array): pitch-shifted audio of size `n_samples x n_channels`.
1289
- """
1290
- if self.parameters.steps.value == 0.0:
1291
- y = x
1292
- else:
1293
- scale = np.max(np.abs(x))
1294
- if scale > MAX_SOX_PROCESSING_PEAK:
1295
- clips = True
1296
- x = x * (MAX_SOX_PROCESSING_PEAK / scale)
1297
- else:
1298
- clips = False
1299
-
1300
- tfm = sox.Transformer()
1301
- tfm.pitch(self.parameters.steps.value, quick=bool(self.parameters.quick.value))
1302
- y = tfm.build_array(input_array=x, sample_rate_in=self.parameters.sample_rate.value).astype(np.float32)
1303
-
1304
- if clips:
1305
- y *= scale / MAX_SOX_PROCESSING_PEAK # rescale output to original scale
1306
-
1307
- if self.fix_length:
1308
- n_samples_input = x.shape[0]
1309
- n_samples_output = y.shape[0]
1310
- if n_samples_input < n_samples_output:
1311
- idx1 = (n_samples_output - n_samples_input) // 2
1312
- idx2 = idx1 + n_samples_input
1313
- y = y[idx1:idx2]
1314
- elif n_samples_input > n_samples_output:
1315
- n_pad = n_samples_input - n_samples_output
1316
- y = np.pad(y, ((n_pad//2, n_pad - n_pad//2), (0, 0)))
1317
-
1318
- return y
1319
-
1320
-
1321
- # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% TIME STRETCH %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1322
- class TimeStretch(Processor):
1323
- """
1324
- Simple time stretcher using SoX and soxbindings (https://github.com/pseeth/soxbindings).
1325
-
1326
- Processor parameters:
1327
- sample_rate (int): Current (assumed) sample rate of the audio.
1328
- factor (float): Time stretch factor.
1329
- quick (bool): If True, this effect will run faster but with lower sound quality.
1330
- stretch_type (str): Algorithm used for stretching (`tempo` or `stretch`).
1331
- audio_type (str): Sets which time segments are most optmial when finding
1332
- the best overlapping points for time stretching.
1333
- """
1334
-
1335
- def __init__(self, sample_rates, fix_length=True, name='TimeStretch', parameters=None, **kwargs):
1336
- """
1337
- Initialize processor.
1338
-
1339
- Args:
1340
- sample_rates (list of ints): Sample rates of input audio.
1341
- fix_length (bool): If True, then output has same length as input.
1342
- name (str): Name of processor.
1343
- parameters (parameter_list): Parameters for this processor.
1344
- **kwargs (dict): Keyword arguments that are passed to the Processor class.
1345
- """
1346
- super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
1347
-
1348
- if not parameters:
1349
- self.parameters = ParameterList()
1350
- self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
1351
- self.parameters.add(Parameter('factor', 1.0, 'float', minimum=1/1.33, maximum=1.33))
1352
- self.parameters.add(Parameter('quick', False, 'bool'))
1353
- self.parameters.add(Parameter('stretch_type', 'tempo', 'string', options=['tempo', 'stretch']))
1354
- self.parameters.add(Parameter('audio_type', 'l', 'string', options=['m', 's', 'l']))
1355
- else:
1356
- self.parameters = parameters
1357
-
1358
- self.fix_length = fix_length
1359
-
1360
- def process(self, x):
1361
- """
1362
- Process audio.
1363
-
1364
- Args:
1365
- x (Numpy array): input audio of size `n_samples x n_channels`.
1366
-
1367
- Returns:
1368
- (Numpy array): time-stretched audio of size `n_samples x n_channels`.
1369
- """
1370
- if self.parameters.factor.value == 1.0:
1371
- y = x
1372
- else:
1373
- scale = np.max(np.abs(x))
1374
- if scale > MAX_SOX_PROCESSING_PEAK:
1375
- clips = True
1376
- x = x * (MAX_SOX_PROCESSING_PEAK / scale)
1377
- else:
1378
- clips = False
1379
-
1380
- tfm = sox.Transformer()
1381
- if self.parameters.stretch_type.value == 'stretch':
1382
- tfm.stretch(self.parameters.factor.value)
1383
- elif self.parameters.stretch_type.value == 'tempo':
1384
- tfm.tempo(1/self.parameters.factor.value,
1385
- audio_type=self.parameters.audio_type.value,
1386
- quick=bool(self.parameters.quick.value))
1387
- y = tfm.build_array(input_array=x, sample_rate_in=self.parameters.sample_rate.value).astype(np.float32)
1388
-
1389
- if clips:
1390
- y *= scale / MAX_SOX_PROCESSING_PEAK # rescale output to original scale
1391
-
1392
- if self.fix_length:
1393
- n_samples_input = x.shape[0]
1394
- n_samples_output = y.shape[0]
1395
- if n_samples_input < n_samples_output:
1396
- idx1 = (n_samples_output - n_samples_input) // 2
1397
- idx2 = idx1 + n_samples_input
1398
- y = y[idx1:idx2]
1399
- elif n_samples_input > n_samples_output:
1400
- n_pad = n_samples_input - n_samples_output
1401
- y = np.pad(y, ((n_pad//2, n_pad - n_pad//2), (0, 0)))
1402
-
1403
- return y
1404
-
1405
-
1406
- # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PLAYBACK SPEED %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1407
- class PlaybackSpeed(Processor):
1408
- """
1409
- Simple playback speed effect using SoX and soxbindings (https://github.com/pseeth/soxbindings).
1410
-
1411
- Processor parameters:
1412
- sample_rate (int): Current (assumed) sample rate of the audio.
1413
- factor (float): Playback speed factor.
1414
- """
1415
-
1416
- def __init__(self, sample_rates, fix_length=True, name='PlaybackSpeed', parameters=None, **kwargs):
1417
- """
1418
- Initialize processor.
1419
-
1420
- Args:
1421
- sample_rates (list of ints): Sample rates of input audio.
1422
- fix_length (bool): If True, then output has same length as input.
1423
- name (str): Name of processor.
1424
- parameters (parameter_list): Parameters for this processor.
1425
- **kwargs (dict): Keyword arguments that are passed to the Processor class.
1426
- """
1427
- super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
1428
-
1429
- if not parameters:
1430
- self.parameters = ParameterList()
1431
- self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
1432
- self.parameters.add(Parameter('factor', 1.0, 'float', minimum=1./1.33, maximum=1.33))
1433
- else:
1434
- self.parameters = parameters
1435
-
1436
- self.fix_length = fix_length
1437
-
1438
- def process(self, x):
1439
- """
1440
- Process audio.
1441
-
1442
- Args:
1443
- x (Numpy array): input audio of size `n_samples x n_channels`.
1444
-
1445
- Returns:
1446
- (Numpy array): resampled audio of size `n_samples x n_channels`.
1447
- """
1448
- if self.parameters.factor.value == 1.0:
1449
- y = x
1450
- else:
1451
- scale = np.max(np.abs(x))
1452
- if scale > MAX_SOX_PROCESSING_PEAK:
1453
- clips = True
1454
- x = x * (MAX_SOX_PROCESSING_PEAK / scale)
1455
- else:
1456
- clips = False
1457
-
1458
- tfm = sox.Transformer()
1459
- tfm.speed(self.parameters.factor.value)
1460
- y = tfm.build_array(input_array=x, sample_rate_in=self.parameters.sample_rate.value).astype(np.float32)
1461
-
1462
- if clips:
1463
- y *= scale / MAX_SOX_PROCESSING_PEAK # rescale output to original scale
1464
-
1465
- if self.fix_length:
1466
- n_samples_input = x.shape[0]
1467
- n_samples_output = y.shape[0]
1468
- if n_samples_input < n_samples_output:
1469
- idx1 = (n_samples_output - n_samples_input) // 2
1470
- idx2 = idx1 + n_samples_input
1471
- y = y[idx1:idx2]
1472
- elif n_samples_input > n_samples_output:
1473
- n_pad = n_samples_input - n_samples_output
1474
- y = np.pad(y, ((n_pad//2, n_pad - n_pad//2), (0, 0)))
1475
-
1476
- return y
1477
-
1478
-
1479
- # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% BEND %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1480
- class Bend(Processor):
1481
- """
1482
- Simple bend effect using SoX and soxbindings (https://github.com/pseeth/soxbindings).
1483
-
1484
- Processor parameters:
1485
- sample_rate (int): Current (assumed) sample rate of the audio.
1486
- n_bends (int): Number of segments or intervals to pitch shift
1487
- """
1488
-
1489
- def __init__(self, sample_rates, pitch_range=(-600, 600), fix_length=True, name='Bend', parameters=None, **kwargs):
1490
- """
1491
- Initialize processor.
1492
-
1493
- Args:
1494
- sample_rates (list of ints): Sample rates of input audio.
1495
- pitch_range (tuple of ints): min and max pitch bending ranges in cents
1496
- fix_length (bool): If True, then output has same length as input.
1497
- name (str): Name of processor.
1498
- parameters (parameter_list): Parameters for this processor.
1499
- **kwargs (dict): Keyword arguments that are passed to the Processor class.
1500
- """
1501
- super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
1502
-
1503
- if not parameters:
1504
- self.parameters = ParameterList()
1505
- self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
1506
- self.parameters.add(Parameter('n_bends', 2, 'int', minimum=2, maximum=10))
1507
- else:
1508
- self.parameters = parameters
1509
- self.pitch_range_min, self.pitch_range_max = pitch_range
1510
-
1511
- def process(self, x):
1512
- """
1513
- Process audio.
1514
-
1515
- Args:
1516
- x (Numpy array): input audio of size `n_samples x n_channels`.
1517
-
1518
- Returns:
1519
- (Numpy array): pitch-bended audio of size `n_samples x n_channels`.
1520
- """
1521
- n_bends = self.parameters.n_bends.value
1522
- max_length = x.shape[0] / self.parameters.sample_rate.value
1523
-
1524
- # Generates random non-overlapping segments
1525
- delta = 1. / self.parameters.sample_rate.value
1526
- boundaries = np.sort(delta + np.random.rand(n_bends-1) * (max_length - delta))
1527
-
1528
- start, end = np.zeros(n_bends), np.zeros(n_bends)
1529
- start[0] = delta
1530
- for i, b in enumerate(boundaries):
1531
- end[i] = b
1532
- start[i+1] = b
1533
- end[-1] = max_length
1534
-
1535
- # randomly sample pitch-shifts in cents
1536
- cents = np.random.randint(self.pitch_range_min, self.pitch_range_max+1, n_bends)
1537
-
1538
- # remove segment if cent value is zero or start == end (as SoX does not allow such values)
1539
- idx_keep = np.logical_and(cents != 0, start != end)
1540
- n_bends, start, end, cents = sum(idx_keep), start[idx_keep], end[idx_keep], cents[idx_keep]
1541
-
1542
- scale = np.max(np.abs(x))
1543
- if scale > MAX_SOX_PROCESSING_PEAK:
1544
- clips = True
1545
- x = x * (MAX_SOX_PROCESSING_PEAK / scale)
1546
- else:
1547
- clips = False
1548
-
1549
- tfm = sox.Transformer()
1550
- tfm.bend(n_bends=int(n_bends), start_times=list(start), end_times=list(end), cents=list(cents))
1551
- y = tfm.build_array(input_array=x, sample_rate_in=self.parameters.sample_rate.value).astype(np.float32)
1552
-
1553
- if clips:
1554
- y *= scale / MAX_SOX_PROCESSING_PEAK # rescale output to original scale
1555
-
1556
- return y
1557
-
1558
-
1559
- # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% MASTERING %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1560
- class Mastering(Processor):
1561
- """
1562
- Mastering Processor.
1563
-
1564
- Models different mastering effects, i.e., ways how we deal with sample values outside [-1, 1].
1565
-
1566
- Processor parameters:
1567
- method (str): Method that should be applied. Can take the following values:
1568
- `none`: Do nothing. E.g., useful if training a network for VST plugin where DAW can also handle peaks
1569
- larger/smaller than `+1`/`-1`.
1570
- `scale`: Scale to max-abs peak to avoid clipping.
1571
- `hardclip`: Apply hardclipping to [-1, 1].
1572
- `softclip`: Apply softclipping to [-1, 1] using tanh.
1573
- """
1574
-
1575
- def __init__(self, name='Mastering', parameters=None, maximum_amplitude=1.0, **kwargs):
1576
- """
1577
- Initialize processor.
1578
-
1579
- Args:
1580
- name (str): Name of processor.
1581
- parameters (parameter_list): Parameters for this processor.
1582
- maximum_amplitude (float): Maximum amplitude after applying the mastering processor.
1583
- **kwargs (dict): Keyword arguments that are passed to the Processor class.
1584
- """
1585
- super().__init__(name, parameters=parameters, block_size=None, **kwargs)
1586
-
1587
- if not parameters:
1588
- self.parameters = ParameterList()
1589
- self.parameters.add(Parameter('method', 'none', 'string',
1590
- options=['none', 'scale', 'hardclip', 'softclip']))
1591
- else:
1592
- self.parameters = parameters
1593
-
1594
- self.maximum_amplitude = maximum_amplitude
1595
-
1596
- def process(self, x):
1597
- """
1598
- Process audio.
1599
-
1600
- Args:
1601
- x (Numpy array): input audio of size `n_samples x n_channels`.
1602
-
1603
- Returns:
1604
- (Numpy array): mastered audio of size `n_samples x n_channels`.
1605
- """
1606
- y = np.copy(x)
1607
- if self.parameters.method.value == 'none':
1608
- return y
1609
- elif self.parameters.method.value == 'scale':
1610
- maxabs = np.maximum(self.maximum_amplitude, np.max(np.abs(y)))
1611
- return y / (maxabs / self.maximum_amplitude)
1612
- elif self.parameters.method.value == 'hardclip':
1613
- return np.clip(y, -self.maximum_amplitude, self.maximum_amplitude)
1614
- elif self.parameters.method.value == 'softclip':
1615
- return self.maximum_amplitude * np.tanh(y)
1616
-
1617
-
1618
- # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% MP3 COMPRESSION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1619
- class MP3Compression(Processor):
1620
- """
1621
- Models the effect of an mp3 compression using LAME.
1622
-
1623
- LAME adds some delay at the beginning - we therefore truncate the first samples
1624
-
1625
- Processor parameters:
1626
- sample_rate (int): Current (assumed) sample rate of the audio.
1627
- quality (int): in the range from 2 ... 7 (2 = highest, 7 = fastest)
1628
- bitrate (int): supported bitrates are
1629
- [8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 192, 224, 256, 320]
1630
- """
1631
-
1632
- def __init__(self, sample_rates, fix_length=True, name='MP3Compression', parameters=None, **kwargs):
1633
- """
1634
- Initialize processor.
1635
-
1636
- Args:
1637
- sample_rates (list of ints): Sample rates of input audio.
1638
- fix_length (bool): If True, then output has same length as input.
1639
- name (str): Name of processor.
1640
- parameters (parameter_list): Parameters for this processor.
1641
- **kwargs (dict): Keyword arguments that are passed to the Processor class.
1642
- """
1643
- super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
1644
-
1645
- if not parameters:
1646
- supported_bitrates = [8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 192, 224, 256, 320]
1647
- self.parameters = ParameterList()
1648
- self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
1649
- self.parameters.add(Parameter('quality', 2, 'string', options=[2, 3, 4, 5, 6, 7]))
1650
- self.parameters.add(Parameter('bitrate', 96, 'string', options=supported_bitrates))
1651
- else:
1652
- self.parameters = parameters
1653
-
1654
- self.fix_length = fix_length
1655
-
1656
- def process(self, x):
1657
- """
1658
- Process audio.
1659
-
1660
- Args:
1661
- x (Numpy array): input audio of size `n_samples x n_channels`.
1662
-
1663
- Returns:
1664
- (Numpy array): MP3 compressed audio of size `n_samples x n_channels`.
1665
- """
1666
- # scale if max-abs peak is larger than `MAX_SOX_PROCESSING_PEAK`
1667
- scale = np.max(np.abs(x))
1668
- if scale > MAX_SOX_PROCESSING_PEAK:
1669
- clips = True
1670
- x = x * (MAX_SOX_PROCESSING_PEAK / scale)
1671
- else:
1672
- clips = False
1673
-
1674
- # convert to int16
1675
- x_int = (x * np.iinfo(np.int16).max).astype(np.int16)
1676
-
1677
- encoder = lameenc.Encoder()
1678
- encoder.set_bit_rate(self.parameters.bitrate.value)
1679
- encoder.set_in_sample_rate(self.parameters.sample_rate.value)
1680
- encoder.set_channels(x.shape[1])
1681
- encoder.set_quality(self.parameters.quality.value)
1682
- encoder.silence()
1683
-
1684
- mp3_data = encoder.encode(x_int.tobytes())
1685
- mp3_data += encoder.flush()
1686
-
1687
- memory_file = io.BytesIO(initial_bytes=mp3_data)
1688
- y, fs = sf.read(memory_file, always_2d=True, dtype=np.float32)
1689
-
1690
- # resample to original sampling rate if LAME changed it
1691
- if fs != self.parameters.sample_rate.value:
1692
- tfm = sox.Transformer()
1693
- tfm.speed(fs / self.parameters.sample_rate.value)
1694
- y = tfm.build_array(input_array=y, sample_rate_in=fs).astype(np.float32)
1695
-
1696
- if clips:
1697
- y *= scale / MAX_SOX_PROCESSING_PEAK # rescale output to original scale
1698
-
1699
- if self.fix_length:
1700
- # LAME adds some samples at the beginning - remove them here
1701
- n_samples_input = x.shape[0]
1702
- n_samples_output = y.shape[0]
1703
- if n_samples_input < n_samples_output:
1704
- y = y[-n_samples_input:, :]
1705
-
1706
- return y
1707
-
1708
-
1709
- def __main__():
1710
- """
1711
- Main function for testing the audio effects.
1712
-
1713
- examples:
1714
-
1715
- config['AUGMENTER_CHAIN'] = AugmentationChain([(ConvolutionalReverb(impulse_responses=impulse_responses,
1716
- sample_rates=config['ACCEPTED_SAMPLING_RATES']), 0.5),
1717
- (Gain(), 0.5),
1718
- (Haas(sample_rates=config['ACCEPTED_SAMPLING_RATES']), 0.5),
1719
- (Panner(sample_rates=config['ACCEPTED_SAMPLING_RATES']), 0.5),
1720
- (SwapChannels(n_channels=config['N_CHANNELS']), 1.),
1721
- (Monauralize(n_channels=config['N_CHANNELS']), 0.5)],
1722
- shuffle=True)
1723
-
1724
- config['AUGMENTER_CHAIN'] = AugmentationChain([(Gain(), 1.),
1725
- (SwapChannels(n_channels=config['N_CHANNELS']), 1.),
1726
- (Monauralize(n_channels=config['N_CHANNELS']), 0.5)], shuffle=True)
1727
- """
1728
- pass
1729
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/dnnlib/__init__.py DELETED
@@ -1,8 +0,0 @@
1
- # Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
- #
3
- # This work is licensed under a Creative Commons
4
- # Attribution-NonCommercial-ShareAlike 4.0 International License.
5
- # You should have received a copy of the license along with this
6
- # work. If not, see http://creativecommons.org/licenses/by-nc-sa/4.0/
7
-
8
- from .util import EasyDict, make_cache_dir_path, call_func_by_name
 
 
 
 
 
 
 
 
 
utils/dnnlib/util.py DELETED
@@ -1,491 +0,0 @@
1
- # Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
- #
3
- # This work is licensed under a Creative Commons
4
- # Attribution-NonCommercial-ShareAlike 4.0 International License.
5
- # You should have received a copy of the license along with this
6
- # work. If not, see http://creativecommons.org/licenses/by-nc-sa/4.0/
7
-
8
- """Miscellaneous utility classes and functions."""
9
-
10
- import ctypes
11
- import fnmatch
12
- import importlib
13
- import inspect
14
- import numpy as np
15
- import os
16
- import shutil
17
- import sys
18
- import types
19
- import io
20
- import pickle
21
- import re
22
- import requests
23
- import html
24
- import hashlib
25
- import glob
26
- import tempfile
27
- import urllib
28
- import urllib.request
29
- import uuid
30
-
31
- from distutils.util import strtobool
32
- from typing import Any, List, Tuple, Union, Optional
33
-
34
-
35
- # Util classes
36
- # ------------------------------------------------------------------------------------------
37
-
38
-
39
- class EasyDict(dict):
40
- """Convenience class that behaves like a dict but allows access with the attribute syntax."""
41
-
42
- def __getattr__(self, name: str) -> Any:
43
- try:
44
- return self[name]
45
- except KeyError:
46
- raise AttributeError(name)
47
-
48
- def __setattr__(self, name: str, value: Any) -> None:
49
- self[name] = value
50
-
51
- def __delattr__(self, name: str) -> None:
52
- del self[name]
53
-
54
-
55
- class Logger(object):
56
- """Redirect stderr to stdout, optionally print stdout to a file, and optionally force flushing on both stdout and the file."""
57
-
58
- def __init__(self, file_name: Optional[str] = None, file_mode: str = "w", should_flush: bool = True):
59
- self.file = None
60
-
61
- if file_name is not None:
62
- self.file = open(file_name, file_mode)
63
-
64
- self.should_flush = should_flush
65
- self.stdout = sys.stdout
66
- self.stderr = sys.stderr
67
-
68
- sys.stdout = self
69
- sys.stderr = self
70
-
71
- def __enter__(self) -> "Logger":
72
- return self
73
-
74
- def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
75
- self.close()
76
-
77
- def write(self, text: Union[str, bytes]) -> None:
78
- """Write text to stdout (and a file) and optionally flush."""
79
- if isinstance(text, bytes):
80
- text = text.decode()
81
- if len(text) == 0: # workaround for a bug in VSCode debugger: sys.stdout.write(''); sys.stdout.flush() => crash
82
- return
83
-
84
- if self.file is not None:
85
- self.file.write(text)
86
-
87
- self.stdout.write(text)
88
-
89
- if self.should_flush:
90
- self.flush()
91
-
92
- def flush(self) -> None:
93
- """Flush written text to both stdout and a file, if open."""
94
- if self.file is not None:
95
- self.file.flush()
96
-
97
- self.stdout.flush()
98
-
99
- def close(self) -> None:
100
- """Flush, close possible files, and remove stdout/stderr mirroring."""
101
- self.flush()
102
-
103
- # if using multiple loggers, prevent closing in wrong order
104
- if sys.stdout is self:
105
- sys.stdout = self.stdout
106
- if sys.stderr is self:
107
- sys.stderr = self.stderr
108
-
109
- if self.file is not None:
110
- self.file.close()
111
- self.file = None
112
-
113
-
114
- # Cache directories
115
- # ------------------------------------------------------------------------------------------
116
-
117
- _dnnlib_cache_dir = None
118
-
119
- def set_cache_dir(path: str) -> None:
120
- global _dnnlib_cache_dir
121
- _dnnlib_cache_dir = path
122
-
123
- def make_cache_dir_path(*paths: str) -> str:
124
- if _dnnlib_cache_dir is not None:
125
- return os.path.join(_dnnlib_cache_dir, *paths)
126
- if 'DNNLIB_CACHE_DIR' in os.environ:
127
- return os.path.join(os.environ['DNNLIB_CACHE_DIR'], *paths)
128
- if 'HOME' in os.environ:
129
- return os.path.join(os.environ['HOME'], '.cache', 'dnnlib', *paths)
130
- if 'USERPROFILE' in os.environ:
131
- return os.path.join(os.environ['USERPROFILE'], '.cache', 'dnnlib', *paths)
132
- return os.path.join(tempfile.gettempdir(), '.cache', 'dnnlib', *paths)
133
-
134
- # Small util functions
135
- # ------------------------------------------------------------------------------------------
136
-
137
-
138
- def format_time(seconds: Union[int, float]) -> str:
139
- """Convert the seconds to human readable string with days, hours, minutes and seconds."""
140
- s = int(np.rint(seconds))
141
-
142
- if s < 60:
143
- return "{0}s".format(s)
144
- elif s < 60 * 60:
145
- return "{0}m {1:02}s".format(s // 60, s % 60)
146
- elif s < 24 * 60 * 60:
147
- return "{0}h {1:02}m {2:02}s".format(s // (60 * 60), (s // 60) % 60, s % 60)
148
- else:
149
- return "{0}d {1:02}h {2:02}m".format(s // (24 * 60 * 60), (s // (60 * 60)) % 24, (s // 60) % 60)
150
-
151
-
152
- def format_time_brief(seconds: Union[int, float]) -> str:
153
- """Convert the seconds to human readable string with days, hours, minutes and seconds."""
154
- s = int(np.rint(seconds))
155
-
156
- if s < 60:
157
- return "{0}s".format(s)
158
- elif s < 60 * 60:
159
- return "{0}m {1:02}s".format(s // 60, s % 60)
160
- elif s < 24 * 60 * 60:
161
- return "{0}h {1:02}m".format(s // (60 * 60), (s // 60) % 60)
162
- else:
163
- return "{0}d {1:02}h".format(s // (24 * 60 * 60), (s // (60 * 60)) % 24)
164
-
165
-
166
- def ask_yes_no(question: str) -> bool:
167
- """Ask the user the question until the user inputs a valid answer."""
168
- while True:
169
- try:
170
- print("{0} [y/n]".format(question))
171
- return strtobool(input().lower())
172
- except ValueError:
173
- pass
174
-
175
-
176
- def tuple_product(t: Tuple) -> Any:
177
- """Calculate the product of the tuple elements."""
178
- result = 1
179
-
180
- for v in t:
181
- result *= v
182
-
183
- return result
184
-
185
-
186
- _str_to_ctype = {
187
- "uint8": ctypes.c_ubyte,
188
- "uint16": ctypes.c_uint16,
189
- "uint32": ctypes.c_uint32,
190
- "uint64": ctypes.c_uint64,
191
- "int8": ctypes.c_byte,
192
- "int16": ctypes.c_int16,
193
- "int32": ctypes.c_int32,
194
- "int64": ctypes.c_int64,
195
- "float32": ctypes.c_float,
196
- "float64": ctypes.c_double
197
- }
198
-
199
-
200
- def get_dtype_and_ctype(type_obj: Any) -> Tuple[np.dtype, Any]:
201
- """Given a type name string (or an object having a __name__ attribute), return matching Numpy and ctypes types that have the same size in bytes."""
202
- type_str = None
203
-
204
- if isinstance(type_obj, str):
205
- type_str = type_obj
206
- elif hasattr(type_obj, "__name__"):
207
- type_str = type_obj.__name__
208
- elif hasattr(type_obj, "name"):
209
- type_str = type_obj.name
210
- else:
211
- raise RuntimeError("Cannot infer type name from input")
212
-
213
- assert type_str in _str_to_ctype.keys()
214
-
215
- my_dtype = np.dtype(type_str)
216
- my_ctype = _str_to_ctype[type_str]
217
-
218
- assert my_dtype.itemsize == ctypes.sizeof(my_ctype)
219
-
220
- return my_dtype, my_ctype
221
-
222
-
223
- def is_pickleable(obj: Any) -> bool:
224
- try:
225
- with io.BytesIO() as stream:
226
- pickle.dump(obj, stream)
227
- return True
228
- except:
229
- return False
230
-
231
-
232
- # Functionality to import modules/objects by name, and call functions by name
233
- # ------------------------------------------------------------------------------------------
234
-
235
- def get_module_from_obj_name(obj_name: str) -> Tuple[types.ModuleType, str]:
236
- """Searches for the underlying module behind the name to some python object.
237
- Returns the module and the object name (original name with module part removed)."""
238
-
239
- # allow convenience shorthands, substitute them by full names
240
- obj_name = re.sub("^np.", "numpy.", obj_name)
241
- obj_name = re.sub("^tf.", "tensorflow.", obj_name)
242
-
243
- # list alternatives for (module_name, local_obj_name)
244
- parts = obj_name.split(".")
245
- name_pairs = [(".".join(parts[:i]), ".".join(parts[i:])) for i in range(len(parts), 0, -1)]
246
-
247
- # try each alternative in turn
248
- for module_name, local_obj_name in name_pairs:
249
- try:
250
- module = importlib.import_module(module_name) # may raise ImportError
251
- get_obj_from_module(module, local_obj_name) # may raise AttributeError
252
- return module, local_obj_name
253
- except:
254
- pass
255
-
256
- # maybe some of the modules themselves contain errors?
257
- for module_name, _local_obj_name in name_pairs:
258
- try:
259
- importlib.import_module(module_name) # may raise ImportError
260
- except ImportError:
261
- if not str(sys.exc_info()[1]).startswith("No module named '" + module_name + "'"):
262
- raise
263
-
264
- # maybe the requested attribute is missing?
265
- for module_name, local_obj_name in name_pairs:
266
- try:
267
- module = importlib.import_module(module_name) # may raise ImportError
268
- get_obj_from_module(module, local_obj_name) # may raise AttributeError
269
- except ImportError:
270
- pass
271
-
272
- # we are out of luck, but we have no idea why
273
- raise ImportError(obj_name)
274
-
275
-
276
- def get_obj_from_module(module: types.ModuleType, obj_name: str) -> Any:
277
- """Traverses the object name and returns the last (rightmost) python object."""
278
- if obj_name == '':
279
- return module
280
- obj = module
281
- for part in obj_name.split("."):
282
- obj = getattr(obj, part)
283
- return obj
284
-
285
-
286
- def get_obj_by_name(name: str) -> Any:
287
- """Finds the python object with the given name."""
288
- module, obj_name = get_module_from_obj_name(name)
289
- return get_obj_from_module(module, obj_name)
290
-
291
-
292
- def call_func_by_name(*args, func_name: str = None, **kwargs) -> Any:
293
- """Finds the python object with the given name and calls it as a function."""
294
- assert func_name is not None
295
- func_obj = get_obj_by_name(func_name)
296
- assert callable(func_obj)
297
- return func_obj(*args, **kwargs)
298
-
299
-
300
- def construct_class_by_name(*args, class_name: str = None, **kwargs) -> Any:
301
- """Finds the python class with the given name and constructs it with the given arguments."""
302
- return call_func_by_name(*args, func_name=class_name, **kwargs)
303
-
304
-
305
- def get_module_dir_by_obj_name(obj_name: str) -> str:
306
- """Get the directory path of the module containing the given object name."""
307
- module, _ = get_module_from_obj_name(obj_name)
308
- return os.path.dirname(inspect.getfile(module))
309
-
310
-
311
- def is_top_level_function(obj: Any) -> bool:
312
- """Determine whether the given object is a top-level function, i.e., defined at module scope using 'def'."""
313
- return callable(obj) and obj.__name__ in sys.modules[obj.__module__].__dict__
314
-
315
-
316
- def get_top_level_function_name(obj: Any) -> str:
317
- """Return the fully-qualified name of a top-level function."""
318
- assert is_top_level_function(obj)
319
- module = obj.__module__
320
- if module == '__main__':
321
- module = os.path.splitext(os.path.basename(sys.modules[module].__file__))[0]
322
- return module + "." + obj.__name__
323
-
324
-
325
- # File system helpers
326
- # ------------------------------------------------------------------------------------------
327
-
328
- def list_dir_recursively_with_ignore(dir_path: str, ignores: List[str] = None, add_base_to_relative: bool = False) -> List[Tuple[str, str]]:
329
- """List all files recursively in a given directory while ignoring given file and directory names.
330
- Returns list of tuples containing both absolute and relative paths."""
331
- assert os.path.isdir(dir_path)
332
- base_name = os.path.basename(os.path.normpath(dir_path))
333
-
334
- if ignores is None:
335
- ignores = []
336
-
337
- result = []
338
-
339
- for root, dirs, files in os.walk(dir_path, topdown=True):
340
- for ignore_ in ignores:
341
- dirs_to_remove = [d for d in dirs if fnmatch.fnmatch(d, ignore_)]
342
-
343
- # dirs need to be edited in-place
344
- for d in dirs_to_remove:
345
- dirs.remove(d)
346
-
347
- files = [f for f in files if not fnmatch.fnmatch(f, ignore_)]
348
-
349
- absolute_paths = [os.path.join(root, f) for f in files]
350
- relative_paths = [os.path.relpath(p, dir_path) for p in absolute_paths]
351
-
352
- if add_base_to_relative:
353
- relative_paths = [os.path.join(base_name, p) for p in relative_paths]
354
-
355
- assert len(absolute_paths) == len(relative_paths)
356
- result += zip(absolute_paths, relative_paths)
357
-
358
- return result
359
-
360
-
361
- def copy_files_and_create_dirs(files: List[Tuple[str, str]]) -> None:
362
- """Takes in a list of tuples of (src, dst) paths and copies files.
363
- Will create all necessary directories."""
364
- for file in files:
365
- target_dir_name = os.path.dirname(file[1])
366
-
367
- # will create all intermediate-level directories
368
- if not os.path.exists(target_dir_name):
369
- os.makedirs(target_dir_name)
370
-
371
- shutil.copyfile(file[0], file[1])
372
-
373
-
374
- # URL helpers
375
- # ------------------------------------------------------------------------------------------
376
-
377
- def is_url(obj: Any, allow_file_urls: bool = False) -> bool:
378
- """Determine whether the given object is a valid URL string."""
379
- if not isinstance(obj, str) or not "://" in obj:
380
- return False
381
- if allow_file_urls and obj.startswith('file://'):
382
- return True
383
- try:
384
- res = requests.compat.urlparse(obj)
385
- if not res.scheme or not res.netloc or not "." in res.netloc:
386
- return False
387
- res = requests.compat.urlparse(requests.compat.urljoin(obj, "/"))
388
- if not res.scheme or not res.netloc or not "." in res.netloc:
389
- return False
390
- except:
391
- return False
392
- return True
393
-
394
-
395
- def open_url(url: str, cache_dir: str = None, num_attempts: int = 10, verbose: bool = True, return_filename: bool = False, cache: bool = True) -> Any:
396
- """Download the given URL and return a binary-mode file object to access the data."""
397
- assert num_attempts >= 1
398
- assert not (return_filename and (not cache))
399
-
400
- # Doesn't look like an URL scheme so interpret it as a local filename.
401
- if not re.match('^[a-z]+://', url):
402
- return url if return_filename else open(url, "rb")
403
-
404
- # Handle file URLs. This code handles unusual file:// patterns that
405
- # arise on Windows:
406
- #
407
- # file:///c:/foo.txt
408
- #
409
- # which would translate to a local '/c:/foo.txt' filename that's
410
- # invalid. Drop the forward slash for such pathnames.
411
- #
412
- # If you touch this code path, you should test it on both Linux and
413
- # Windows.
414
- #
415
- # Some internet resources suggest using urllib.request.url2pathname() but
416
- # but that converts forward slashes to backslashes and this causes
417
- # its own set of problems.
418
- if url.startswith('file://'):
419
- filename = urllib.parse.urlparse(url).path
420
- if re.match(r'^/[a-zA-Z]:', filename):
421
- filename = filename[1:]
422
- return filename if return_filename else open(filename, "rb")
423
-
424
- assert is_url(url)
425
-
426
- # Lookup from cache.
427
- if cache_dir is None:
428
- cache_dir = make_cache_dir_path('downloads')
429
-
430
- url_md5 = hashlib.md5(url.encode("utf-8")).hexdigest()
431
- if cache:
432
- cache_files = glob.glob(os.path.join(cache_dir, url_md5 + "_*"))
433
- if len(cache_files) == 1:
434
- filename = cache_files[0]
435
- return filename if return_filename else open(filename, "rb")
436
-
437
- # Download.
438
- url_name = None
439
- url_data = None
440
- with requests.Session() as session:
441
- if verbose:
442
- print("Downloading %s ..." % url, end="", flush=True)
443
- for attempts_left in reversed(range(num_attempts)):
444
- try:
445
- with session.get(url) as res:
446
- res.raise_for_status()
447
- if len(res.content) == 0:
448
- raise IOError("No data received")
449
-
450
- if len(res.content) < 8192:
451
- content_str = res.content.decode("utf-8")
452
- if "download_warning" in res.headers.get("Set-Cookie", ""):
453
- links = [html.unescape(link) for link in content_str.split('"') if "export=download" in link]
454
- if len(links) == 1:
455
- url = requests.compat.urljoin(url, links[0])
456
- raise IOError("Google Drive virus checker nag")
457
- if "Google Drive - Quota exceeded" in content_str:
458
- raise IOError("Google Drive download quota exceeded -- please try again later")
459
-
460
- match = re.search(r'filename="([^"]*)"', res.headers.get("Content-Disposition", ""))
461
- url_name = match[1] if match else url
462
- url_data = res.content
463
- if verbose:
464
- print(" done")
465
- break
466
- except KeyboardInterrupt:
467
- raise
468
- except:
469
- if not attempts_left:
470
- if verbose:
471
- print(" failed")
472
- raise
473
- if verbose:
474
- print(".", end="", flush=True)
475
-
476
- # Save to cache.
477
- if cache:
478
- safe_name = re.sub(r"[^0-9a-zA-Z-._]", "_", url_name)
479
- safe_name = safe_name[:min(len(safe_name), 128)]
480
- cache_file = os.path.join(cache_dir, url_md5 + "_" + safe_name)
481
- temp_file = os.path.join(cache_dir, "tmp_" + uuid.uuid4().hex + "_" + url_md5 + "_" + safe_name)
482
- os.makedirs(cache_dir, exist_ok=True)
483
- with open(temp_file, "wb") as f:
484
- f.write(url_data)
485
- os.replace(temp_file, cache_file) # atomic
486
- if return_filename:
487
- return cache_file
488
-
489
- # Return data as file object.
490
- assert not return_filename
491
- return io.BytesIO(url_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/evaluation/KAD_evaluate.py DELETED
@@ -1,307 +0,0 @@
1
-
2
- import pandas as pd
3
-
4
- import torch
5
- import os
6
- import omegaconf
7
- import numpy as np
8
- import glob
9
- import pyloudnorm as pyln
10
- import sys
11
- #move to the parent directory to import datasets
12
- sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
13
-
14
- from datasets.eval_benchmark import load_audio
15
- from utils.evaluation.dist_metrics import KADFeatures
16
-
17
- # see device 1
18
- os.environ["CUDA_VISIBLE_DEVICES"] = "0"
19
-
20
- path_test_set="/scratch/elec/t412-asp/automix/MDX_TM_benchmark"
21
-
22
- set="MDX_TM"
23
-
24
- features=[
25
- "AFxRep",
26
- "FxEncoder",
27
- "FxEncoder++",
28
- "CLAP",
29
- ]
30
-
31
-
32
- KAD_metrics={}
33
-
34
- KAD_args = {
35
- "do_PCA_figure": False,
36
- "do_TSNE_figure": False,
37
- "kernel": "gaussian", #kernel to use for the KAD metric
38
- }
39
-
40
- KAD_args = omegaconf.OmegaConf.create(KAD_args)
41
-
42
- for feature in features:
43
- if feature=="AFxRep":
44
- AFxRep_args = {
45
- "distance_type": "cosine", # not used
46
- "ckpt_path": "/scratch/work/molinee2/projects/project_mfm_eloi/src/tmp/afx-rep.ckpt"
47
- }
48
- AFxRep_args = omegaconf.OmegaConf.create(AFxRep_args)
49
- KAD_metrics[feature] = KADFeatures(type="AFxRep", sample_rate=44100, AFxRep_args=AFxRep_args, KAD_args=KAD_args)
50
- elif feature=="FxEncoder":
51
- fx_encoder_args = {
52
- "distance_type": "cosine", # not used
53
- "ckpt_path": "/scratch/work/molinee2/projects/project_mfm_eloi/src/utils/feature_extractors/ckpt/fxenc_default.pt"
54
- }
55
- fx_encoder_args = omegaconf.OmegaConf.create(fx_encoder_args)
56
- KAD_metrics[feature] = KADFeatures(type="fx_encoder", sample_rate=44100, fx_encoder_args=fx_encoder_args, KAD_args=KAD_args)
57
- elif feature=="FxEncoder++":
58
- fx_encoder_plusplus_args = {
59
- "distance_type": "cosine", # not used
60
- "ckpt_path": "/scratch/work/molinee2/projects/project_mfm_eloi/src_clean/checkpoints/fxenc_plusplus_default.pt"
61
- }
62
- fx_encoder_plusplus_args = omegaconf.OmegaConf.create(fx_encoder_plusplus_args)
63
- KAD_metrics[feature] = KADFeatures(type="fx_encoder_++", sample_rate=44100, fx_encoder_plusplus_args=fx_encoder_plusplus_args, KAD_args=KAD_args)
64
-
65
- elif feature=="CLAP":
66
- clap_args = {
67
- "ckpt_path": "/scratch/work/molinee2/projects/project_mfm_eloi/src_clean/checkpoints/music_audioset_epoch_15_esc_90.14.patched.pt",
68
- "distance_type": "cosine",
69
- "normalize": True, # if True, the features will be normalized
70
- "use_adaptor": False, # if True, the features will be adapted to the CLAP space
71
- "adaptor_checkpoint":None,
72
- "adaptor_type":None,
73
- "add_noise": False, # if True, the features will be augmented with orthogonal noise
74
- "noise_sigma": 0 # sigma of the orthogonal noise to
75
- }
76
-
77
- clap_args = omegaconf.OmegaConf.create(clap_args)
78
- KAD_metrics[feature] = KADFeatures(type="CLAP", sample_rate=44100, CLAP_args=clap_args, KAD_args=KAD_args)
79
-
80
- elif feature=="bark":
81
- bark_args = {
82
- "distance_type": "cosine", # not used
83
- "normalize": True, # if True, the features will be normalized
84
- }
85
- bark_args = omegaconf.OmegaConf.create(bark_args)
86
- KAD_metrics[feature] = KADFeatures(type="bark", sample_rate=44100, bark_args=bark_args, KAD_args=KAD_args, normalize=True)
87
-
88
- else:
89
- raise ValueError(f"Unknown feature: {feature}")
90
-
91
-
92
- def get_ref_file_path(dir):
93
- ref_mixture = os.path.join(dir,"mix", "mix.wav")
94
- return ref_mixture
95
-
96
- pylnmeter = pyln.Meter(44100) # Create a meter for 44100 Hz sampling rate
97
-
98
- def loudness_normalize(audio, target_loudness=-23.0):
99
- """
100
- Normalize the loudness of the audio to a target level.
101
- """
102
- audio= np.array(audio, dtype=np.float32).T
103
- loudness = pylnmeter.integrated_loudness(audio)
104
-
105
- # loudness normalize audio to -12 dB LUFS
106
- loudness_normalized_audio = pyln.normalize.loudness(audio, loudness, -14.0)
107
-
108
- return torch.tensor(loudness_normalized_audio.T, dtype=torch.float32)
109
-
110
- song_dirs= sorted(glob.glob(os.path.join(path_test_set, "*")))
111
-
112
- reference_dict = {}
113
-
114
- for song_dir in song_dirs:
115
-
116
- song_name = os.path.basename(song_dir)
117
- segment_subdirs = sorted(glob.glob(os.path.join(song_dir, "*")))
118
-
119
- song_id=os.path.basename(song_dir)
120
-
121
- for segment_dir in segment_subdirs:
122
-
123
- segment= os.path.basename(segment_dir)
124
- id= f"{song_id}_{segment}"
125
-
126
- ref_file_path= get_ref_file_path(segment_dir)
127
- mix_ref, fs=load_audio(str(ref_file_path), stereo=True)
128
- assert fs==44100, "Expected sampling rate of 44100 Hz"
129
- mix_ref = loudness_normalize(mix_ref)
130
- reference_dict[id] = mix_ref
131
-
132
-
133
- # create dataframe colums are features, rows are methods
134
- dataframe= pd.DataFrame(columns=["method"] +list(KAD_metrics.keys()))
135
-
136
- filename_results="results_eval/results_KAD"+f"_{set}_test.csv"
137
-
138
- #methods=[ "fxnorm_automix_S_Lb", "fxnorm_automix_L_Lb",]
139
- #methods=[ "proposed_random_churn","S3_random_churn", "S4_random_churn", "only_rms_S4_random_churn", "only_rms_random_churn" ]
140
- #methods=[ "proposed_public", "only_rms_public" ]j
141
- #methods=[ "S1internal_S2public"]
142
- #methods=[ "S1internal_S2publicv3", "S1publicv3_S2publicv3", "S1publicv3_S2internal"]
143
- #methods=[ "S1public_S2publicv3"]
144
- #methods=[ "S1public_S2publicv3", "S1public_S2publicv3_oracle", "S1public_S2publicv3_rms"]
145
- #methods=["mst_oracle", "equal_loudness", "diff_baseline", "proposed_oracle", "proposed_random", "only_rms_random" , "proposed_centroid_close", "proposed_centroid_far", "only_rms_centroid_close", "only_rms_centroid_far"]
146
- #methods=["S4_random"]
147
- #methods=["onlyrms_3108"]
148
- #methods=["publicv3_2"]
149
- #methods=["mst_oracle", "equal_loudness", "diff_baseline", "proposed_oracle", "proposed_random_3108_v2", "prop_random_indep_3108", "S4_random", "S3_random", "WUN_4instr", "publicv3", "DMC_14instr", "fxnorm_automix_S_Lb", "fxnorm_automix_L_Lb"]
150
- methods=[ "tencydb_test", "publicv3_test"]
151
- #methods=["prop_random_indep", "only_rms_indep"]
152
- #methods=["WUN_4instr"]
153
- #methods=["diff_baseline"]
154
- #methods=["internal_2708", "equal_loudness", "internal_2708_oracle"]
155
- #methods=["WUN_4instr", "WUN_14instr", "DMC_14instr"]
156
-
157
-
158
- for method in methods:
159
-
160
- if method=="mst_oracle":
161
- def get_pred_file_path(dir):
162
- pred_mixture= os.path.join(dir,"mst_oracle", "mixture_output.wav")
163
- return pred_mixture
164
- elif method=="mst_oracle_multi":
165
- def get_pred_file_path(dir):
166
- pred_mixture= os.path.join(dir,"mst_oracle_multi", "mixture_output.wav")
167
- return pred_mixture
168
- elif method=="equal_loudness":
169
- def get_pred_file_path(dir):
170
- pred_mixture= os.path.join(dir,"anchor_equal_loudness", "mix.wav")
171
- return pred_mixture
172
- elif method=="diff_baseline":
173
- def get_pred_file_path(dir):
174
- pred_mixture= os.path.join(dir,"diff_baseline", "pred_mixture.wav")
175
- return pred_mixture
176
- elif method=="fxnorm_automix_S_Lb":
177
- def get_pred_file_path(dir):
178
- pred_mixture= os.path.join(dir,"fxnorm_automix_S_Lb_v2", "mixture_output.wav")
179
- return pred_mixture
180
- elif method=="fxnorm_automix_L_Lb":
181
- def get_pred_file_path(dir):
182
- pred_mixture= os.path.join(dir,"fxnorm_automix_L_Lb_v2", "mixture_output.wav")
183
- return pred_mixture
184
- elif method=="proposed_random":
185
- def get_pred_file_path(dir):
186
- pred_mixture= os.path.join(dir,"stylediffpipeline_S9v6_MF3wetv6_MDX_TM_benchmark_cfg_1_T30", "random.wav")
187
- return pred_mixture
188
- elif method=="proposed_random_3108_v2":
189
- def get_pred_file_path(dir):
190
- pred_mixture= os.path.join(dir,"stylediffpipeline_S9v6_MF3wetv6_MDX_TM_benchmark_cfg_1_T30_3108_v2", "random.wav")
191
- return pred_mixture
192
- elif method=="onlyrms_3108":
193
- def get_pred_file_path(dir):
194
- pred_mixture= os.path.join(dir,"stylediffpipeline_S9v6_MF3wetv6_MDX_TM_benchmark_cfg_1_T30_3108_v2", "only_rms_random.wav")
195
- return pred_mixture
196
- elif method=="proposed_random_3108_v2_oracle":
197
- def get_pred_file_path(dir):
198
- pred_mixture= os.path.join(dir,"stylediffpipeline_S9v6_MF3wetv6_MDX_TM_benchmark_cfg_1_T30_3108_v2_oracle", "random.wav")
199
- return pred_mixture
200
- elif method=="only_rms_random":
201
- def get_pred_file_path(dir):
202
- pred_mixture= os.path.join(dir,"stylediffpipeline_S9v6_MF3wetv6_MDX_TM_benchmark_cfg_1_T30", "only_rms_random.wav")
203
- return pred_mixture
204
- elif method=="S3_random":
205
- def get_pred_file_path(dir):
206
- pred_mixture= os.path.join(dir,"stylediffpipeline_S3v6_MF3wetv6_MDX_TM_benchmark_cfg_1_T30", "random.wav")
207
- return pred_mixture
208
- elif method=="S4_3108":
209
- def get_pred_file_path(dir):
210
- pred_mixture= os.path.join(dir,"stylediffpipeline_S4v6_MF3wetv6_MDX_TM_benchmark_cfg_1_T30_3108_v2", "random.wav")
211
- return pred_mixture
212
- elif method=="publicv3":
213
- def get_pred_file_path(dir):
214
- pred_mixture= os.path.join(dir,"stylediffpipeline_publicv3_publicv3_MDX_TM_benchmark_cfg_1_T30_publicv3", "random.wav")
215
- return pred_mixture
216
- elif method=="publicv3_test":
217
- def get_pred_file_path(dir):
218
- pred_mixture= os.path.join(dir,"public_public_MDX_TM_benchmark_public_test_Sep29", "random.wav")
219
- return pred_mixture
220
- elif method=="tencydb_test":
221
- def get_pred_file_path(dir):
222
- pred_mixture= os.path.join(dir,"internal_TencyDB_internal_TencyMastering_MDX_TM_benchmark_internal_test_Sep29", "random.wav")
223
- return pred_mixture
224
- elif method=="prop_random_indep_3108":
225
- def get_pred_file_path(dir):
226
- pred_mixture= os.path.join(dir,"stylediffpipeline_S9v6_MF3wetv6_MDX_TM_benchmark_cfg_1_T30_3108_v2_independent", "random.wav")
227
- return pred_mixture
228
- elif method=="WUN_4instr":
229
- def get_pred_file_path(dir):
230
- pred_mixture= os.path.join(dir,"WUN_4instr", "pred_mixture.wav")
231
- return pred_mixture
232
- elif method=="DMC_14instr":
233
- def get_pred_file_path(dir):
234
- pred_mixture= os.path.join(dir,"DMC_14instr", "pred_mixture.wav")
235
- return pred_mixture
236
- elif method=="only_rms_public":
237
- def get_pred_file_path(dir):
238
- pred_mixture= os.path.join(dir,"ours_public", "mixture_processed_onlyrms.wav")
239
- return pred_mixture
240
- elif method=="proposed_oracle":
241
- def get_pred_file_path(dir):
242
- pred_mixture= os.path.join(dir,"oracle_proposed", "mix.wav")
243
- return pred_mixture
244
- elif method=="publicv3_oracle":
245
- def get_pred_file_path(dir):
246
- pred_mixture= os.path.join(dir,"oracle_proposed_public", "mix.wav")
247
- return pred_mixture
248
- else:
249
- raise ValueError(f"Unknown method: {method}")
250
-
251
-
252
- method_dict = {}
253
-
254
- for song_dir in song_dirs:
255
-
256
- song_name = os.path.basename(song_dir)
257
- segment_subdirs = sorted(glob.glob(os.path.join(song_dir, "*")))
258
-
259
- song_id=os.path.basename(song_dir)
260
-
261
- for segment_dir in segment_subdirs:
262
-
263
- segment= os.path.basename(segment_dir)
264
- id= f"{song_id}_{segment}"
265
-
266
- pred_file_path= get_pred_file_path(segment_dir)
267
- pred_mixture, fs=load_audio(str(pred_file_path), stereo=True)
268
- assert fs==44100, "Expected sampling rate of 44100 Hz"
269
- pred_mixture = loudness_normalize(pred_mixture)
270
- assert not np.isnan(pred_mixture).any(), f"NaN values found in predicted mixture for {id}"
271
- method_dict[id] = pred_mixture
272
-
273
-
274
- #create new empty row with key as "method"
275
- dataframe.loc[method] = [None] * (len(KAD_metrics)+1)
276
-
277
- for feature, metric in KAD_metrics.items():
278
-
279
- KAD_distance, dict_output=metric.compute(reference_dict, method_dict, None)
280
-
281
- print(f"KAD distance for method {method} and feature {feature}: {KAD_distance}")
282
-
283
- #write the KAD distance to the dataframe
284
- dataframe.at[method, "method"] = method
285
- dataframe.at[method, feature] = KAD_distance
286
-
287
- for output_key, output_value in dict_output.items():
288
- if "figure" in output_key:
289
- output_value.savefig(f"results_eval/{output_key}_{set}_{method}_{feature}.png")
290
- print(f"Saved figure: {output_key}_{set}_{method}_{feature}.png")
291
-
292
-
293
-
294
- dataframe.to_csv(filename_results, index=False)
295
- print(f"Results saved to {filename_results}")
296
-
297
- #except Exception as e:
298
-
299
- # print(f"Error processing method {method}: {e}")
300
-
301
- # dataframe.to_csv(filename_results, index=False)
302
- # print(f"Probably incompleted Results saved to {filename_results}")
303
- # continue
304
-
305
-
306
-
307
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/evaluation/dist_metrics.py DELETED
@@ -1,971 +0,0 @@
1
-
2
- from numpy.lib.scimath import sqrt as scisqrt
3
- from scipy import linalg
4
- import numpy as np
5
- import torchaudio
6
- import os
7
- from importlib import import_module
8
- import torch
9
-
10
-
11
- from utils.feature_extractors.load_features import load_AFxRep, load_fx_encoder, load_fx_encoder_plusplus, load_CLAP
12
-
13
- from utils.log import make_PCA_figure
14
-
15
- SCALE_FACTOR = 100
16
-
17
- def calc_kernel_audio_distance(
18
- x: torch.Tensor,
19
- y: torch.Tensor,
20
- device: str,
21
- bandwidth=None,
22
- kernel='gaussian',
23
- precision=torch.float32,
24
- eps=1e-8
25
- ) -> torch.Tensor:
26
- """
27
- Compute the Kernel Audio Distance (KAD) between two samples using PyTorch.
28
-
29
- Args:
30
- x: The first set of embeddings of shape (m, embedding_dim).
31
- y: The second set of embeddings of shape (n, embedding_dim).
32
- cache_dirs: Directories to cache kernel statistics.
33
- bandwidth: The bandwidth value for the Gaussian RBF kernel.
34
- kernel: Kernel function to use ('gaussian', 'iq', 'imq').
35
- precision: Type setting for matrix calculation precision.
36
- eps: Small value to prevent division by zero.
37
-
38
- Returns:
39
- The KAD between x and y embedding sets.
40
- """
41
- # Ensure x and y are of the correct precision
42
- x = x.to(dtype=precision, device=device)
43
- y = y.to(dtype=precision, device=device)
44
-
45
- assert bandwidth is not None, "Bandwidth must be provided for KAD calculation"
46
-
47
- m, n = x.shape[0], y.shape[0]
48
-
49
- # Define kernel functions
50
- gamma = 1 / (2 * bandwidth**2 + eps)
51
- if kernel == 'gaussian': # Gaussian Kernel
52
- kernel = lambda a: torch.exp(-gamma * a)
53
- elif kernel == 'iq': # Inverse Quadratic Kernel
54
- kernel = lambda a: 1 / (1 + gamma * a)
55
- elif kernel == 'imq': # Inverse Multiquadric Kernel
56
- kernel = lambda a: 1 / torch.sqrt(1 + gamma * a)
57
- else:
58
- raise ValueError("Invalid kernel type. Valid kernels: 'gaussian', 'iq', 'imq'")
59
-
60
- # Load x kernel statistics
61
- xx = x @ x.T
62
- x_sqnorms = torch.diagonal(xx)
63
- d2_xx = x_sqnorms.unsqueeze(1) + x_sqnorms.unsqueeze(0) - 2 * xx # shape (m, m)
64
-
65
- k_xx = kernel(d2_xx)
66
- k_xx = k_xx - torch.diag(torch.diagonal(k_xx))
67
- k_xx_mean = k_xx.sum() / (m * (m - 1))
68
-
69
- # Load y kernel statistics
70
- yy = y @ y.T
71
- y_sqnorms = torch.diagonal(yy)
72
- d2_yy = y_sqnorms.unsqueeze(1) + y_sqnorms.unsqueeze(0) - 2 * yy # shape (n, n)
73
-
74
- k_yy = kernel(d2_yy)
75
- k_yy = k_yy - torch.diag(torch.diagonal(k_yy))
76
- k_yy_mean = k_yy.sum() / (n * (n - 1))
77
-
78
- # Compute kernel statistics for xy
79
- xy = x @ y.T
80
- d2_xy = x_sqnorms.unsqueeze(1) + y_sqnorms.unsqueeze(0) - 2 * xy # shape (m, n)
81
- k_xy = kernel(d2_xy)
82
- k_xy_mean = k_xy.mean()
83
-
84
- # Compute MMD
85
- result = k_xx_mean + k_yy_mean - 2 * k_xy_mean
86
-
87
- return result * SCALE_FACTOR
88
-
89
-
90
- def median_pairwise_distance(x, subsample=None):
91
- """
92
- Compute the median pairwise distance of an embedding set.
93
-
94
- Args:
95
- x: torch.Tensor of shape (n_samples, embedding_dim)
96
- subsample: int, number of random pairs to consider (optional)
97
-
98
- Returns:
99
- The median pairwise distance between points in x.
100
- """
101
- x = torch.tensor(x, dtype=torch.float32)
102
- n_samples = x.shape[0]
103
-
104
- if subsample is not None and subsample < n_samples * (n_samples - 1) / 2:
105
- # Randomly select pairs of indices
106
- idx1 = torch.randint(0, n_samples, (subsample,))
107
- idx2 = torch.randint(0, n_samples, (subsample,))
108
-
109
- # Ensure idx1 != idx2
110
- mask = idx1 == idx2
111
- idx2[mask] = (idx2[mask] + 1) % n_samples
112
-
113
- # Compute distances for selected pairs
114
- distances = torch.sqrt(torch.sum((x[idx1] - x[idx2])**2, dim=1))
115
- else:
116
- # Compute all pairwise distances
117
- distances = torch.pdist(x)
118
-
119
- return torch.median(distances).item()
120
-
121
- class DistMetric:
122
- """
123
- Base class for pairwise metrics.
124
-
125
- This class should be subclassed to implement specific pairwise metrics.
126
- """
127
- def __init__(self,type, sample_rate,*args, **kwargs):
128
- """
129
- Initialize the PairwiseMetric instance.
130
-
131
- Args:
132
- *args: Variable length argument list.
133
- **kwargs: Arbitrary keyword arguments.
134
- """
135
- self.type=type
136
- self.sample_rate=sample_rate
137
-
138
- if self.type == "fx_encoder":
139
- self.model_args= kwargs.get("fx_encoder_args", None)
140
-
141
- assert self.model_args is not None, "model_args must be provided for fx_encoder type"
142
-
143
- self.distance_type=self.model_args.distance_type
144
-
145
- self.feat_extractor = load_fx_encoder(self.model_args, self.device)
146
-
147
- #self.feat_extractor = load_effects_encoder(ckpt_path=ckpt_path).to(self.device)
148
-
149
- elif self.type == "fx_encoder_++":
150
- self.model_args= kwargs.get("fx_encoder_plusplus_args", None)
151
-
152
- assert self.model_args is not None, "model_args must be provided for fx_encoder_plusplus type"
153
-
154
- print(self.model_args)
155
-
156
- self.distance_type=self.model_args.distance_type
157
-
158
- self.feat_extractor = load_fx_encoder_plusplus(self.model_args, self.device)
159
-
160
- #self.feat_extractor = load_effects_encoder(ckpt_path=ckpt_path).to(self.device)
161
-
162
- elif self.type== "AFxRep-mid" or self.type== "AFxRep-side" or self.type== "AFxRep":
163
-
164
- self.model_args= kwargs.get("AFxRep_args", None)
165
-
166
- assert self.model_args is not None, "model_args must be provided for AFxRep type"
167
-
168
- self.distance_type=self.model_args.distance_type
169
-
170
- feat_extractor = load_AFxRep(self.model_args, self.device, sample_rate=self.sample_rate)
171
-
172
- if self.type == "AFxRep-mid":
173
- def feat_extractor_mid(x):
174
-
175
- features= feat_extractor(x)
176
-
177
- #print("features shape:", features.shape)
178
- #divide by 2 to get mid and side features
179
-
180
- feat_mid, feat_side = features.chunk(2, dim=-1)
181
-
182
- return feat_mid
183
-
184
- self.feat_extractor = feat_extractor_mid
185
-
186
- elif self.type == "AFxRep-side":
187
- def feat_extractor_side(x):
188
-
189
- features= feat_extractor(x)
190
-
191
- #divide by 2 to get mid and side features
192
-
193
- feat_mid, feat_side = features.chunk(2, dim=-1)
194
-
195
- return feat_side
196
-
197
- self.feat_extractor = feat_extractor_side
198
- else:
199
- self.feat_extractor = feat_extractor
200
-
201
- elif self.type == "CLAP":
202
- self.model_args= kwargs.get("CLAP_args", None)
203
- assert self.model_args is not None, "model_args must be provided for CLAP type"
204
-
205
- CLAP_fn = load_CLAP(self.model_args, self.device)
206
- self.feat_extractor=lambda x: CLAP_fn(x, None)
207
-
208
- else:
209
- raise ValueError(f"Unknown type: {self.type}. Supported types: fx_encoder, fx_encoder_plusplus, AFxRep-mid, AFxRep-side, AFxRep")
210
-
211
-
212
- def compute(self, *args, **kwargs):
213
- raise NotImplementedError("Subclasses should implement this method.")
214
-
215
- def extract_features(self, y, y_hat, x=None):
216
-
217
- y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
218
- y_hat=torch.tensor(y_hat).permute(1,0).unsqueeze(0).to(self.device)
219
-
220
- with torch.no_grad():
221
- feat_y= self.feat_extractor(y)
222
- feat_y_hat= self.feat_extractor(y_hat)
223
-
224
- if x is not None:
225
- x=torch.tensor(x).permute(1,0).unsqueeze(0).to(self.device)
226
- with torch.no_grad():
227
- feat_x= self.feat_extractor(x)
228
- else:
229
- feat_x=None
230
-
231
-
232
- return feat_y, feat_y_hat, feat_x
233
-
234
-
235
- def do_TSNE_figure(self, dict_features_y, dict_features_y_hat, dict_features_x=None, fit_mode="target", dict_cluster=None):
236
- """
237
- Perform PCA on the features and create a figure.
238
-
239
- Args:
240
- dict_features_y (dict): Dictionary containing features for the first set.
241
- dict_features_y_hat (dict): Dictionary containing features for the second set.
242
-
243
- Returns:
244
- fig: The created figure.
245
- """
246
- import matplotlib.pyplot as plt
247
- from sklearn.decomposition import PCA
248
- from sklearn.manifold import TSNE
249
-
250
- y_values = list(dict_features_y.values())
251
- y_values = torch.cat(y_values, dim=0)
252
-
253
- y_hat_values = list(dict_features_y_hat.values())
254
- y_hat_values = torch.cat(y_hat_values, dim=0)
255
-
256
-
257
- if dict_cluster is not None:
258
- clusters= list(dict_cluster.values())
259
- clusters = [c.unsqueeze(0) if c.dim() == 0 else c for c in clusters]
260
- clusters = torch.cat(clusters, dim=0)
261
-
262
- #check different clusters (0,1,2,3...)
263
- assert len(torch.unique(clusters)) == 2, "Only two clusters are supported for PCA visualization"
264
- C0= clusters == 0
265
- C1= clusters == 1
266
-
267
- #manage Nans in y_values
268
- y_values=torch.nan_to_num(y_values, nan=0)
269
- y_hat_values=torch.nan_to_num(y_hat_values, nan=0)
270
-
271
- #if self.pca is None:
272
- self.tsne =TSNE(n_components=2, perplexity=30)
273
-
274
- combined_data = torch.cat([y_values, y_hat_values], dim=0).cpu().numpy()
275
- combined_result = self.tsne.fit_transform(combined_data)
276
-
277
- # Split the results back
278
- n_y = y_values.shape[0]
279
- tsne_result = combined_result[:n_y]
280
- tsne_result_hat = combined_result[n_y:]
281
-
282
-
283
- if dict_cluster is not None:
284
- data_dict = {
285
- "y_C0": tsne_result[C0],
286
- "y_hat_C0": tsne_result_hat[C0],
287
- "y_C1": tsne_result[C1],
288
- "y_hat_C1": tsne_result_hat[C1]
289
- }
290
- else:
291
- data_dict = {
292
- "y": tsne_result,
293
- "y_hat": tsne_result_hat
294
- }
295
-
296
-
297
-
298
- fig= make_PCA_figure(data_dict, title=self.type + " TSNE")
299
-
300
-
301
- return fig
302
-
303
-
304
- def do_PCA_figure(self, dict_features_y, dict_features_y_hat, dict_features_x=None, fit_mode="target", dict_cluster=None):
305
- """
306
- Perform PCA on the features and create a figure.
307
-
308
- Args:
309
- dict_features_y (dict): Dictionary containing features for the first set.
310
- dict_features_y_hat (dict): Dictionary containing features for the second set.
311
-
312
- Returns:
313
- fig: The created figure.
314
- """
315
- import matplotlib.pyplot as plt
316
- from sklearn.decomposition import PCA
317
-
318
- y_values = list(dict_features_y.values())
319
- y_values = torch.cat(y_values, dim=0)
320
-
321
- y_hat_values = list(dict_features_y_hat.values())
322
- y_hat_values = torch.cat(y_hat_values, dim=0)
323
-
324
-
325
- if dict_cluster is not None:
326
- clusters= list(dict_cluster.values())
327
- clusters = [c.unsqueeze(0) if c.dim() == 0 else c for c in clusters]
328
- clusters = torch.cat(clusters, dim=0)
329
-
330
- #check different clusters (0,1,2,3...)
331
- assert len(torch.unique(clusters)) == 2, "Only two clusters are supported for PCA visualization"
332
- C0= clusters == 0
333
- C1= clusters == 1
334
-
335
- #manage Nans in y_values
336
- y_values=torch.nan_to_num(y_values, nan=0)
337
- y_hat_values=torch.nan_to_num(y_hat_values, nan=0)
338
-
339
- if self.pca is None:
340
- self.pca = PCA(n_components=2)
341
- if fit_mode == "target":
342
- pca_result = self.pca.fit_transform(y_values.cpu().numpy())
343
- elif fit_mode == "all":
344
- self.pca = self.pca.fit(torch.cat([y_values,y_hat_values], dim=0).cpu().numpy())
345
- pca_result= self.pca.transform(y_values.cpu().numpy())
346
- else:
347
- pca_result = self.pca.transform(y_values.cpu().numpy())
348
-
349
-
350
- #project y_hat values into the same PCA space
351
- pca_result_hat = self.pca.transform(y_hat_values.cpu().numpy())
352
-
353
- if dict_cluster is not None:
354
- data_dict = {
355
- "y_C0": pca_result[C0],
356
- "y_hat_C0": pca_result_hat[C0],
357
- "y_C1": pca_result[C1],
358
- "y_hat_C1": pca_result_hat[C1]
359
- }
360
- else:
361
- data_dict = {
362
- "y": pca_result,
363
- "y_hat": pca_result_hat
364
- }
365
-
366
- if dict_features_x is not None:
367
- x_values = list(dict_features_x.values())
368
- x_values = torch.cat(x_values, dim=0)
369
-
370
- pca_result_x = self.pca.transform(x_values.cpu().numpy())
371
-
372
- data_dict["x"] = pca_result_x
373
-
374
-
375
-
376
- fig= make_PCA_figure(data_dict, title=self.type + " PCA")
377
-
378
-
379
- return fig
380
-
381
-
382
-
383
-
384
- class KADFeatures(DistMetric):
385
- """
386
- Class for computing the pairwise spectral metric.
387
-
388
- This class inherits from PairwiseMetric and implements the compute method
389
- to calculate the pairwise spectral metric.
390
- """
391
- def __init__(self,
392
- type=None,
393
- sample_rate=44100,
394
- KAD_args=None,
395
- classwise=False,
396
- *args, **kwargs):
397
- """
398
- Initialize the PairwiseSpectral instance.
399
-
400
- Args:
401
- *args: Variable length argument list.
402
- **kwargs: Arbitrary keyword arguments.
403
- """
404
- self.type = type
405
-
406
- self.classwise = classwise
407
-
408
- self.device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
409
-
410
-
411
- self.KAD_args = KAD_args
412
-
413
- assert self.KAD_args is not None, "FAD_args must be provided"
414
-
415
- if self.KAD_args.do_PCA_figure:
416
- self.pca = None
417
-
418
- self.bandwidth = None
419
-
420
- super().__init__(self.type, sample_rate, *args, **kwargs)
421
-
422
- def calculate_KAD_distance_classwise(self, dict_features_y, dict_features_y_hat, dict_cluster):
423
-
424
- if dict_cluster is not None:
425
- clusters= list(dict_cluster.values())
426
- clusters = [c.unsqueeze(0) if c.dim() == 0 else c for c in clusters]
427
- clusters = torch.cat(clusters, dim=0)
428
-
429
- #check different clusters (0,1,2,3...)
430
- assert len(torch.unique(clusters)) == 2, "Only two clusters are supported for PCA visualization"
431
- C0= clusters == 0
432
- C1= clusters == 1
433
-
434
- y= torch.cat(list(dict_features_y.values()), dim=0)
435
- y_hat= torch.cat(list(dict_features_y_hat.values()), dim=0)
436
-
437
- y_C0= y[C0]
438
- y_hat_C0= y_hat[C0]
439
-
440
- with torch.no_grad():
441
- KAD_C0=calc_kernel_audio_distance(y_C0, y_hat_C0, device=self.device, bandwidth=self.bandwidth, kernel=self.KAD_args.kernel, precision=torch.float32)
442
-
443
- y_C1= y[C1]
444
- y_hat_C1= y_hat[C1]
445
- with torch.no_grad():
446
- KAD_C1=calc_kernel_audio_distance(y_C1, y_hat_C1, device=self.device, bandwidth=self.bandwidth, kernel=self.KAD_args.kernel, precision=torch.float32)
447
-
448
- KAD= (KAD_C0 + KAD_C1) / 2
449
-
450
- return KAD.cpu().item()
451
- def calculate_KAD_distance(self, dict_features_y, dict_features_y_hat):
452
- y= torch.cat(list(dict_features_y.values()), dim=0)
453
- y_hat= torch.cat(list(dict_features_y_hat.values()), dim=0)
454
- with torch.no_grad():
455
- KAD=calc_kernel_audio_distance(y, y_hat, device=self.device, bandwidth=self.bandwidth, kernel=self.KAD_args.kernel, precision=torch.float32)
456
- return KAD.cpu().item()
457
-
458
- def calculate_bandwidth(self, dict_features_y):
459
- print("Calculating bandwidth...")
460
- y= torch.cat(list(dict_features_y.values()), dim=0)
461
- print("y shape:", y.shape)
462
- self.bandwidth = median_pairwise_distance(y)
463
- print("Bandwidth:", self.bandwidth)
464
-
465
-
466
-
467
- def compute(self, dict_y, dict_y_hat, dict_x, dict_p_hat=None, dict_cluster=None, normalize=False, *args, **kwargs):
468
- """
469
- Compute the pairwise spectral metric.
470
-
471
- Args:
472
- *args: Variable length argument list.
473
- **kwargs: Arbitrary keyword arguments.
474
-
475
- Returns:
476
- The computed pairwise spectral metric.
477
- """
478
-
479
- print("Computing KAD distance...")
480
-
481
-
482
- if self.classwise:
483
- assert dict_cluster is not None, "dict_cluster must be provided if classwise is True"
484
-
485
- dict_features_y={}
486
- dict_features_y_hat={}
487
- #dict_features_x={}
488
-
489
- if dict_y_hat is None:
490
- print("computing KAD with style embeddings")
491
- assert dict_p_hat is not None, "dict_p_hat must be provided if dict_y_hat is None"
492
-
493
- for key in dict_y.keys():
494
- y= dict_y[key]
495
- #x= dict_x[key]
496
-
497
- embed= dict_p_hat[key]
498
- embed=torch.tensor(embed).to(self.device).unsqueeze(0)
499
-
500
- embed_mid, embed_side = torch.chunk(embed, 2, dim=-1)
501
-
502
- if self.type== "AFxRep-mid":
503
- p_hat= embed_mid
504
- elif self.type== "AFxRep-side":
505
- p_hat= embed_side
506
- elif self.type== "AFxRep":
507
- p_hat= embed
508
-
509
- #if x.shape[-2] == 1:
510
- # x = x.repeat( 2, 1)
511
-
512
- #assert x.shape == y.shape, f"Shape mismatch for key {key}: {x.shape} vs {y.shape}"
513
-
514
- c, d=y.shape
515
- #assert b==1, f"Expected batch size of 1, got {b} for key {key}"
516
-
517
- assert c==2, f"Expected 2 channels, got {c} for key {key}"
518
-
519
- y=y.T
520
- #x=x.T
521
-
522
- y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
523
- #x=torch.tensor(x).permute(1,0).unsqueeze(0).to(self.device)
524
-
525
- with torch.no_grad():
526
- feat_y= self.feat_extractor(y)
527
- #feat_x= self.feat_extractor(x)
528
-
529
- assert p_hat.shape == feat_y.shape, f"Shape mismatch for key {key}: {p_hat.shape} vs {feat_y.shape}"
530
- #assert p_hat.shape == feat_x.shape, f"Shape mismatch for key {key}: {p_hat.shape} vs {feat_x.shape}"
531
-
532
- dict_features_y[key] = feat_y
533
- #dict_features_x[key] = feat_x
534
- dict_features_y_hat[key] = p_hat
535
-
536
- else:
537
- for key in dict_y.keys():
538
- y= dict_y[key]
539
- #x= dict_x[key]
540
- y_hat= dict_y_hat[key]
541
-
542
- #if x.shape[-2] == 1:
543
- # x = x.repeat( 2, 1)
544
-
545
- assert y.shape == y_hat.shape, f"Shape mismatch for key {key}: {y.shape} vs {y_hat.shape}"
546
- #assert x.shape == y.shape, f"Shape mismatch for key {key}: {x.shape} vs {y.shape}"
547
-
548
- c, d=y.shape
549
- #assert b==1, f"Expected batch size of 1, got {b} for key {key}"
550
-
551
- assert c==2, f"Expected 2 channels, got {c} for key {key}"
552
-
553
- y=y.T
554
- y_hat=y_hat.T
555
- #x=x.T
556
-
557
- y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
558
- y_hat=torch.tensor(y_hat).permute(1,0).unsqueeze(0).to(self.device)
559
- #x=torch.tensor(x).permute(1,0).unsqueeze(0).to(self.device)
560
-
561
- with torch.no_grad():
562
- #try:
563
- feat_y= self.feat_extractor(y)
564
- feat_y_hat= self.feat_extractor(y_hat)
565
- #except Exception as e:
566
- #print(f"Error extracting features for key {key}: {e}")
567
- # continue
568
- #feat_x= self.feat_extractor(x)
569
-
570
- dict_features_y[key] = feat_y
571
- dict_features_y_hat[key] = feat_y_hat
572
- #dict_features_x[key] = feat_x
573
-
574
- if normalize:
575
- #compute normalization statistics wrt to reference features
576
- y_values = list(dict_features_y.values())
577
- y_values = torch.cat(y_values, dim=0)
578
- mean= y_values.mean(dim=0, keepdim=True)
579
- std= y_values.std(dim=0, keepdim=True)
580
-
581
- #normalize features
582
- for key in dict_features_y.keys():
583
- dict_features_y[key] = (dict_features_y[key] - mean) / (std + 1e-8)
584
- dict_features_y_hat[key] = (dict_features_y_hat[key] - mean) / (std + 1e-8)
585
-
586
-
587
- dict_output = {}
588
- if self.KAD_args.do_PCA_figure:
589
- fig=self.do_PCA_figure(dict_features_y, dict_features_y_hat, fit_mode=self.KAD_args.PCA_fit_mode, dict_cluster=dict_cluster)
590
- key= self.type+ "_PCA_figure"
591
- dict_output = {key: fig}
592
-
593
- if self.KAD_args.do_TSNE_figure:
594
- fig=self.do_TSNE_figure(dict_features_y, dict_features_y_hat, fit_mode="all", dict_cluster=dict_cluster)
595
- key= self.type+ "_TSNE_figure"
596
- dict_output[key] = fig
597
-
598
-
599
- # Compute mean features
600
-
601
-
602
-
603
- # Compute the distance between the mean features
604
- #FAD_distance = self.calculate_FAD_distance(y_mean, y_cov, y_hat_mean, y_hat_cov)
605
- if self.bandwidth is None:
606
- self.calculate_bandwidth(dict_features_y)
607
-
608
- if self.classwise:
609
- #calculate KAD distance for each class
610
- KAD_distance = self.calculate_KAD_distance_classwise(dict_features_y, dict_features_y_hat, dict_cluster)
611
- else:
612
-
613
- KAD_distance = self.calculate_KAD_distance(dict_features_y, dict_features_y_hat)
614
-
615
-
616
- return KAD_distance, dict_output
617
-
618
- class FADFeatures(DistMetric):
619
- """
620
- Class for computing the pairwise spectral metric.
621
-
622
- This class inherits from PairwiseMetric and implements the compute method
623
- to calculate the pairwise spectral metric.
624
- """
625
- def __init__(self,
626
- type=None,
627
- sample_rate=44100,
628
- FAD_args=None,
629
- *args, **kwargs):
630
- """
631
- Initialize the PairwiseSpectral instance.
632
-
633
- Args:
634
- *args: Variable length argument list.
635
- **kwargs: Arbitrary keyword arguments.
636
- """
637
- self.type = type
638
-
639
- self.device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
640
-
641
-
642
- self.FAD_args = FAD_args
643
-
644
- assert self.FAD_args is not None, "FAD_args must be provided"
645
-
646
- if self.FAD_args.do_PCA_figure:
647
- self.pca = None
648
-
649
- super().__init__(self.type, sample_rate, *args, **kwargs)
650
-
651
- def calculate_FAD_distance(self, mu1, sigma1, mu2, sigma2, eps=1e-6):
652
-
653
- """Numpy implementation of the Frechet Distance.
654
- The Frechet distance between two multivariate Gaussians X_1 ~ N(mu_1, C_1)
655
- and X_2 ~ N(mu_2, C_2) is
656
- d^2 = ||mu_1 - mu_2||^2 + Tr(C_1 + C_2 - 2*sqrt(C_1*C_2)).
657
-
658
- Stable version by Dougal J. Sutherland.
659
-
660
- Params:
661
- -- mu1 : Numpy array containing the activations of a layer of the
662
- inception net (like returned by the function 'get_predictions')
663
- for generated samples.
664
- -- mu2 : The sample mean over activations, precalculated on an
665
- representative data set.
666
- -- sigma1: The covariance matrix over activations for generated samples.
667
- -- sigma2: The covariance matrix over activations, precalculated on an
668
- representative data set.
669
-
670
- Returns:
671
- -- : The Frechet Distance.
672
- """
673
- mu1=mu1.detach().cpu().numpy()
674
- mu2=mu2.detach().cpu().numpy()
675
-
676
- sigma1=sigma1.detach().cpu().numpy()
677
- sigma2=sigma2.detach().cpu().numpy()
678
-
679
-
680
- mu1 = np.atleast_1d(mu1)
681
- mu2 = np.atleast_1d(mu2)
682
-
683
- sigma1 = np.atleast_2d(sigma1)
684
- sigma2 = np.atleast_2d(sigma2)
685
-
686
- assert (
687
- mu1.shape == mu2.shape
688
- ), "Training and test mean vectors have different lengths"
689
- assert (
690
- sigma1.shape == sigma2.shape
691
- ), "Training and test covariances have different dimensions"
692
-
693
- diff = mu1 - mu2
694
-
695
- # Product might be almost singular
696
- covmean, _ = linalg.sqrtm(sigma1.dot(sigma2), disp=False)
697
- if not np.isfinite(covmean).all():
698
- msg = (
699
- "fid calculation produces singular product; "
700
- "adding %s to diagonal of cov estimates"
701
- ) % eps
702
- offset = np.eye(sigma1.shape[0]) * eps
703
- covmean = linalg.sqrtm((sigma1 + offset).dot(sigma2 + offset))
704
-
705
- # Numerical error might give slight imaginary component
706
- if np.iscomplexobj(covmean):
707
- if not np.allclose(np.diagonal(covmean).imag, 0, atol=1e-3):
708
- m = np.max(np.abs(covmean.imag))
709
- raise ValueError("Imaginary component {}".format(m))
710
- covmean = covmean.real
711
-
712
- tr_covmean = np.trace(covmean)
713
-
714
- return diff.dot(diff) + np.trace(sigma1) + np.trace(sigma2) - 2 * tr_covmean
715
-
716
-
717
- def calculate_emb_statistics(self, features_dicto):
718
- """
719
- Calculate the mean and standard deviation of the features.
720
-
721
- Args:
722
- features_dicto (dict): Dictionary containing features for each key.
723
-
724
- Returns:
725
- mean_features (torch.Tensor): Mean of the features.
726
- std_features (torch.Tensor): Standard deviation of the features.
727
- """
728
-
729
- all_features = torch.cat(list(features_dicto.values()), dim=0)
730
-
731
-
732
- #mean
733
- #mean_features = all_features.mean(dim=0)
734
-
735
- #cov
736
- #cov_features = torch.cov(all_features.T)
737
- mean_features = np.mean(all_features.cpu().numpy(), axis=0)
738
-
739
- cov_features= np.cov(all_features.cpu().numpy(), rowvar=False)
740
-
741
-
742
-
743
- return torch.tensor(mean_features), torch.tensor(cov_features)
744
-
745
-
746
- def do_PCA_figure(self, dict_features_y, dict_features_y_hat, dict_features_x=None):
747
- """
748
- Perform PCA on the features and create a figure.
749
-
750
- Args:
751
- dict_features_y (dict): Dictionary containing features for the first set.
752
- dict_features_y_hat (dict): Dictionary containing features for the second set.
753
-
754
- Returns:
755
- fig: The created figure.
756
- """
757
- import matplotlib.pyplot as plt
758
- from sklearn.decomposition import PCA
759
-
760
- y_values = list(dict_features_y.values())
761
- y_values = torch.cat(y_values, dim=0)
762
-
763
-
764
-
765
- if self.pca is None:
766
- self.pca = PCA(n_components=2)
767
- pca_result = self.pca.fit_transform(y_values.cpu().numpy())
768
- else:
769
- pca_result = self.pca.transform(y_values.cpu().numpy())
770
-
771
-
772
- y_hat_values = list(dict_features_y_hat.values())
773
- y_hat_values = torch.cat(y_hat_values, dim=0)
774
-
775
- #project y_hat values into the same PCA space
776
- pca_result_hat = self.pca.transform(y_hat_values.cpu().numpy())
777
-
778
- data_dict = {
779
- "y": pca_result,
780
- "y_hat": pca_result_hat
781
- }
782
-
783
- if dict_features_x is not None:
784
- x_values = list(dict_features_x.values())
785
- x_values = torch.cat(x_values, dim=0)
786
-
787
- pca_result_x = self.pca.transform(x_values.cpu().numpy())
788
-
789
- data_dict["x"] = pca_result_x
790
-
791
-
792
-
793
- fig= make_PCA_figure(data_dict, title=self.type + " PCA")
794
-
795
-
796
- return fig
797
-
798
-
799
-
800
-
801
-
802
- def compute(self, dict_y, dict_y_hat, dict_x, dict_p_hat=None, dict_cluster=None, *args, **kwargs):
803
- """
804
- Compute the pairwise spectral metric.
805
-
806
- Args:
807
- *args: Variable length argument list.
808
- **kwargs: Arbitrary keyword arguments.
809
-
810
- Returns:
811
- The computed pairwise spectral metric.
812
- """
813
-
814
- print("Computing FAD distance...")
815
-
816
-
817
-
818
- dict_features_y={}
819
- dict_features_y_hat={}
820
- #dict_features_x={}
821
-
822
- if dict_y_hat is None:
823
- print("computing FAD with style embeddings")
824
- assert dict_p_hat is not None, "dict_p_hat must be provided if dict_y_hat is None"
825
-
826
- for key in dict_y.keys():
827
- y= dict_y[key]
828
- #x= dict_x[key]
829
-
830
- embed= dict_p_hat[key]
831
- embed=torch.tensor(embed).to(self.device).unsqueeze(0)
832
-
833
- print("embed shape:", embed.shape)
834
- embed_mid, embed_side = torch.chunk(embed, 2, dim=-1)
835
-
836
- if self.type== "AFxRep-mid":
837
- p_hat= embed_mid
838
- elif self.type== "AFxRep-side":
839
- p_hat= embed_side
840
- elif self.type== "AFxRep":
841
- p_hat= embed
842
-
843
- #if x.shape[-2] == 1:
844
- # x = x.repeat( 2, 1)
845
-
846
- #assert x.shape == y.shape, f"Shape mismatch for key {key}: {x.shape} vs {y.shape}"
847
-
848
- c, d=y.shape
849
- #assert b==1, f"Expected batch size of 1, got {b} for key {key}"
850
-
851
- assert c==2, f"Expected 2 channels, got {c} for key {key}"
852
-
853
- y=y.T
854
- #x=x.T
855
-
856
- y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
857
- #x=torch.tensor(x).permute(1,0).unsqueeze(0).to(self.device)
858
-
859
- with torch.no_grad():
860
- feat_y= self.feat_extractor(y)
861
- #feat_x= self.feat_extractor(x)
862
-
863
- assert p_hat.shape == feat_y.shape, f"Shape mismatch for key {key}: {p_hat.shape} vs {feat_y.shape}"
864
- #assert p_hat.shape == feat_x.shape, f"Shape mismatch for key {key}: {p_hat.shape} vs {feat_x.shape}"
865
-
866
- dict_features_y[key] = feat_y
867
- #dict_features_x[key] = feat_x
868
- dict_features_y_hat[key] = p_hat
869
-
870
- else:
871
- for key in dict_y.keys():
872
- y= dict_y[key]
873
- #x= dict_x[key]
874
- y_hat= dict_y_hat[key]
875
-
876
- #if x.shape[-2] == 1:
877
- # x = x.repeat( 2, 1)
878
-
879
- assert y.shape == y_hat.shape, f"Shape mismatch for key {key}: {y.shape} vs {y_hat.shape}"
880
- #assert x.shape == y.shape, f"Shape mismatch for key {key}: {x.shape} vs {y.shape}"
881
-
882
- c, d=y.shape
883
- #assert b==1, f"Expected batch size of 1, got {b} for key {key}"
884
-
885
- assert c==2, f"Expected 2 channels, got {c} for key {key}"
886
-
887
- y=y.T
888
- y_hat=y_hat.T
889
- #x=x.T
890
-
891
- y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
892
- y_hat=torch.tensor(y_hat).permute(1,0).unsqueeze(0).to(self.device)
893
- #x=torch.tensor(x).permute(1,0).unsqueeze(0).to(self.device)
894
-
895
- with torch.no_grad():
896
- feat_y= self.feat_extractor(y)
897
- feat_y_hat= self.feat_extractor(y_hat)
898
- #feat_x= self.feat_extractor(x)
899
-
900
- dict_features_y[key] = feat_y
901
- dict_features_y_hat[key] = feat_y_hat
902
- #dict_features_x[key] = feat_x
903
-
904
- dict_output = {}
905
- if self.FAD_args.do_PCA_figure:
906
- fig=self.do_PCA_figure(dict_features_y, dict_features_y_hat, fit_mode="target")
907
- key= self.type+ "_PCA_figure"
908
- dict_output[key] = fig
909
-
910
- if self.FAD_args.do_TSNE_figure:
911
- fig=self.do_TSNE_figure(dict_features_y, dict_features_y_hat, fit_mode="target")
912
- key= self.type+ "_TSNE_figure"
913
- dict_output[key] = fig
914
-
915
- # Compute mean features
916
-
917
- y_mean, y_cov = self.calculate_emb_statistics(dict_features_y)
918
-
919
- y_hat_mean, y_hat_cov = self.calculate_emb_statistics(dict_features_y_hat)
920
-
921
-
922
- # Compute the distance between the mean features
923
- #FAD_distance = self.calculate_FAD_distance(y_mean, y_cov, y_hat_mean, y_hat_cov)
924
- FAD_distance = self.calculate_FAD_distance(y_mean, y_cov, y_hat_mean, y_hat_cov)
925
-
926
-
927
- return FAD_distance, dict_output
928
-
929
-
930
- def metric_factory(metric_name, sample_rate, *args, **kwargs):
931
- """
932
- Factory function to create a metric function based on the metric name.
933
-
934
- Args:
935
- metric_name (str): The name of the metric to create.
936
- *args: Variable length argument list.
937
- **kwargs: Arbitrary keyword arguments.
938
-
939
- Returns:
940
- An instance of a class that implements the metric function.
941
- """
942
- if metric_name == "fad-fx_encoder":
943
- return FADFeatures(*args, **kwargs, type="fx_encoder", sample_rate=sample_rate )
944
- elif metric_name == "fad-AFxRep":
945
- return FADFeatures(*args, **kwargs, type="AFxRep", sample_rate=sample_rate)
946
- elif metric_name == "fad-AFxRep-mid":
947
- return FADFeatures(*args, **kwargs, type="AFxRep-side", sample_rate=sample_rate)
948
- elif metric_name == "fad-AFxRep-side":
949
- return FADFeatures(*args, **kwargs, type="AFxRep-mid", sample_rate=sample_rate)
950
- elif metric_name == "kad-fx_encoder":
951
- return KADFeatures(*args, **kwargs, type="fx_encoder", sample_rate=sample_rate)
952
- elif metric_name == "kad-AFxRep":
953
- return KADFeatures(*args, **kwargs, type="AFxRep", sample_rate=sample_rate)
954
- elif metric_name == "kad-AFxRep-mid":
955
- return KADFeatures(*args, **kwargs, type="AFxRep-side", sample_rate=sample_rate)
956
- elif metric_name == "kad-AFxRep-side":
957
- return KADFeatures(*args, **kwargs, type="AFxRep-mid", sample_rate=sample_rate)
958
- elif metric_name == "kad-class-fx_encoder":
959
- return KADFeatures(*args, **kwargs, type="fx_encoder", sample_rate=sample_rate, classwise=True)
960
- elif metric_name == "kad-class-AFxRep":
961
- return KADFeatures(*args, **kwargs, type="AFxRep", sample_rate=sample_rate, classwise=True)
962
- elif metric_name == "kad-class-AFxRep-mid":
963
- return KADFeatures(*args, **kwargs, type="AFxRep-side", sample_rate=sample_rate, classwise=True)
964
- elif metric_name == "kad-class-AFxRep-side":
965
- return KADFeatures(*args, **kwargs, type="AFxRep-mid", sample_rate=sample_rate, classwise=True)
966
- else:
967
- raise ValueError(f"Unknown metric: {metric_name}")
968
-
969
- # Example usage:
970
- #metric_instance = metric_factory("pairwise-spectral")
971
- #```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/evaluation/dist_metrics_multitrack.py DELETED
@@ -1,619 +0,0 @@
1
-
2
- import math
3
- from numpy.lib.scimath import sqrt as scisqrt
4
- from scipy import linalg
5
- import numpy as np
6
- import torchaudio
7
- import os
8
- from importlib import import_module
9
- import torch
10
-
11
-
12
- from utils.feature_extractors.load_features import load_AFxRep, load_fx_encoder, load_fx_encoder_plusplus, load_MERT, load_CLAP, load_fx_encoder_plusplus_2048
13
-
14
- from utils.log import make_PCA_figure
15
-
16
- SCALE_FACTOR = 100
17
-
18
- def calc_kernel_audio_distance(
19
- x: torch.Tensor,
20
- y: torch.Tensor,
21
- device: str,
22
- bandwidth=None,
23
- kernel='gaussian',
24
- precision=torch.float32,
25
- eps=1e-8
26
- ) -> torch.Tensor:
27
- """
28
- Compute the Kernel Audio Distance (KAD) between two samples using PyTorch.
29
-
30
- Args:
31
- x: The first set of embeddings of shape (m, embedding_dim).
32
- y: The second set of embeddings of shape (n, embedding_dim).
33
- cache_dirs: Directories to cache kernel statistics.
34
- bandwidth: The bandwidth value for the Gaussian RBF kernel.
35
- kernel: Kernel function to use ('gaussian', 'iq', 'imq').
36
- precision: Type setting for matrix calculation precision.
37
- eps: Small value to prevent division by zero.
38
-
39
- Returns:
40
- The KAD between x and y embedding sets.
41
- """
42
- # Ensure x and y are of the correct precision
43
- x = x.to(dtype=precision, device=device)
44
- y = y.to(dtype=precision, device=device)
45
-
46
- assert bandwidth is not None, "Bandwidth must be provided for KAD calculation"
47
-
48
- m, n = x.shape[0], y.shape[0]
49
-
50
- # Define kernel functions
51
- gamma = 1 / (2 * bandwidth**2 + eps)
52
- if kernel == 'gaussian': # Gaussian Kernel
53
- kernel = lambda a: torch.exp(-gamma * a)
54
- elif kernel == 'iq': # Inverse Quadratic Kernel
55
- kernel = lambda a: 1 / (1 + gamma * a)
56
- elif kernel == 'imq': # Inverse Multiquadric Kernel
57
- kernel = lambda a: 1 / torch.sqrt(1 + gamma * a)
58
- else:
59
- raise ValueError("Invalid kernel type. Valid kernels: 'gaussian', 'iq', 'imq'")
60
-
61
- # Load x kernel statistics
62
- xx = x @ x.T
63
- x_sqnorms = torch.diagonal(xx)
64
- d2_xx = x_sqnorms.unsqueeze(1) + x_sqnorms.unsqueeze(0) - 2 * xx # shape (m, m)
65
-
66
- k_xx = kernel(d2_xx)
67
- k_xx = k_xx - torch.diag(torch.diagonal(k_xx))
68
- k_xx_mean = k_xx.sum() / (m * (m - 1))
69
-
70
- # Load y kernel statistics
71
- yy = y @ y.T
72
- y_sqnorms = torch.diagonal(yy)
73
- d2_yy = y_sqnorms.unsqueeze(1) + y_sqnorms.unsqueeze(0) - 2 * yy # shape (n, n)
74
-
75
- k_yy = kernel(d2_yy)
76
- k_yy = k_yy - torch.diag(torch.diagonal(k_yy))
77
- k_yy_mean = k_yy.sum() / (n * (n - 1))
78
-
79
- # Compute kernel statistics for xy
80
- xy = x @ y.T
81
- d2_xy = x_sqnorms.unsqueeze(1) + y_sqnorms.unsqueeze(0) - 2 * xy # shape (m, n)
82
- k_xy = kernel(d2_xy)
83
- k_xy_mean = k_xy.mean()
84
-
85
- # Compute MMD
86
- result = k_xx_mean + k_yy_mean - 2 * k_xy_mean
87
-
88
- return result * SCALE_FACTOR
89
-
90
-
91
- def median_pairwise_distance(x, subsample=None):
92
- """
93
- Compute the median pairwise distance of an embedding set.
94
-
95
- Args:
96
- x: torch.Tensor of shape (n_samples, embedding_dim)
97
- subsample: int, number of random pairs to consider (optional)
98
-
99
- Returns:
100
- The median pairwise distance between points in x.
101
- """
102
- x = torch.tensor(x, dtype=torch.float32)
103
- n_samples = x.shape[0]
104
-
105
- if subsample is not None and subsample < n_samples * (n_samples - 1) / 2:
106
- # Randomly select pairs of indices
107
- idx1 = torch.randint(0, n_samples, (subsample,))
108
- idx2 = torch.randint(0, n_samples, (subsample,))
109
-
110
- # Ensure idx1 != idx2
111
- mask = idx1 == idx2
112
- idx2[mask] = (idx2[mask] + 1) % n_samples
113
-
114
- # Compute distances for selected pairs
115
- distances = torch.sqrt(torch.sum((x[idx1] - x[idx2])**2, dim=1))
116
- else:
117
- # Compute all pairwise distances
118
- distances = torch.pdist(x)
119
-
120
- return torch.median(distances).item()
121
-
122
- class DistMetric:
123
- """
124
- Base class for pairwise metrics.
125
-
126
- This class should be subclassed to implement specific pairwise metrics.
127
- """
128
- def __init__(self,type, sample_rate,*args, **kwargs):
129
- """
130
- Initialize the PairwiseMetric instance.
131
-
132
- Args:
133
- *args: Variable length argument list.
134
- **kwargs: Arbitrary keyword arguments.
135
- """
136
- self.type=type
137
- self.sample_rate=sample_rate
138
-
139
- self.taxonomy_ref= kwargs.get("taxonomy_ref", None)
140
-
141
- if self.type == "DynamicFeatures":
142
- from utils.feature_extractors.AF_features_embedding import AF_fourier_embedding
143
- AFembedding= AF_fourier_embedding(device=self.device)
144
-
145
- def feat_extracfor_fn(x):
146
- z, _ = AFembedding.encode(x)
147
- return z
148
-
149
- self.feat_extractor = feat_extracfor_fn
150
- elif self.type == "FxEncoder++":
151
- self.model_args= kwargs.get("fx_encoder_plusplus_args", None)
152
- assert self.model_args is not None, "model_args must be provided for fxencAFv2-fxenc++ type"
153
-
154
- self.distance_type=self.model_args.distance_type
155
- fxencoder = load_fx_encoder_plusplus_2048(self.model_args, self.device)
156
-
157
- def feat_extractor_fn(x):
158
- z= fxencoder(x)
159
- z=torch.nn.functional.normalize(z, dim=-1, p=2) # normalize to unit variance
160
- return z
161
-
162
- self.feat_extractor = feat_extractor_fn
163
-
164
- else:
165
- raise ValueError(f"Unknown type: {self.type}. Supported types: fx_encoder, fx_encoder_plusplus, AFxRep-mid, AFxRep-side, AFxRep")
166
-
167
-
168
- def compute(self, *args, **kwargs):
169
- raise NotImplementedError("Subclasses should implement this method.")
170
-
171
- def extract_features(self, y, y_hat, x=None):
172
-
173
- y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
174
- y_hat=torch.tensor(y_hat).permute(1,0).unsqueeze(0).to(self.device)
175
-
176
- with torch.no_grad():
177
- feat_y= self.feat_extractor(y)
178
- feat_y_hat= self.feat_extractor(y_hat)
179
-
180
- if x is not None:
181
- x=torch.tensor(x).permute(1,0).unsqueeze(0).to(self.device)
182
- with torch.no_grad():
183
- feat_x= self.feat_extractor(x)
184
- else:
185
- feat_x=None
186
-
187
-
188
- return feat_y, feat_y_hat, feat_x
189
-
190
-
191
- def do_TSNE_figure(self, dict_features_y, dict_features_y_hat, dict_taxonomy=None):
192
- """
193
- Perform PCA on the features and create a figure.
194
-
195
- Args:
196
- dict_features_y (dict): Dictionary containing features for the first set.
197
- dict_features_y_hat (dict): Dictionary containing features for the second set.
198
-
199
- Returns:
200
- fig: The created figure.
201
- """
202
- import matplotlib.pyplot as plt
203
- from sklearn.manifold import TSNE
204
-
205
-
206
- y_values = {}
207
- y_hat_values = {}
208
- for key, track_name in self.taxonomy_ref.items():
209
-
210
-
211
- y_values[track_name] = []
212
- for k in range(len(dict_features_y.keys())):
213
- index = [i for i, tax_id in enumerate(dict_taxonomy[k]) if tax_id == key]
214
- print("taxonomy",k, dict_taxonomy[k])
215
- if index: # If any matches were found
216
- # Select the corresponding rows from dict_features_y[k]
217
- selected_features = dict_features_y[k][index]
218
- y_values[track_name].append(selected_features)
219
-
220
-
221
- if not y_values[track_name]:
222
- print(f"Warning: No features found for track {track_name} with key {key}. Skipping this track.")
223
- #remove the track from the dictionaries
224
- y_values.pop(track_name, None)
225
- continue
226
-
227
- y_values[track_name]=torch.cat(y_values[track_name], dim=0)
228
- y_values[track_name]=torch.nan_to_num(y_values[track_name], nan=0)
229
-
230
- #print(clusters[track_name])
231
-
232
- y_hat_values[track_name] = []
233
-
234
- for k in range(len(dict_features_y_hat.keys())):
235
- index = [i for i, tax_id in enumerate(dict_taxonomy[k]) if tax_id == key]
236
- if index: # If any matches were found
237
- # Select the corresponding rows from dict_features_y[k]
238
- selected_features = dict_features_y_hat[k][index]
239
- y_hat_values[track_name].append(selected_features)
240
- #else:
241
- # y_hat_values[track_name].append(None) # Append zeros if no match found
242
-
243
-
244
- y_hat_values[track_name]=torch.cat(y_hat_values[track_name], dim=0)
245
- y_hat_values[track_name]=torch.nan_to_num(y_hat_values[track_name], nan=0)
246
-
247
- #print(clusters_hat[track_name])
248
-
249
- if self.pca is None:
250
- self.pca= {}
251
-
252
-
253
- tsne_result = {}
254
- tsne_result_hat = {}
255
- data_dict = {}
256
- figs= {}
257
-
258
- for k in y_values.keys():
259
- print("Processing track:", k)
260
- self.tsne =TSNE(n_components=2, perplexity=30)
261
- combined_data = torch.cat([y_values[k], y_hat_values[k]], dim=0).cpu().numpy()
262
- combined_result = self.tsne.fit_transform(combined_data)
263
-
264
- n_y = y_values[k].shape[0]
265
- tsne_result[k] = combined_result[:n_y]
266
- tsne_result_hat[k] = combined_result[n_y:]
267
-
268
-
269
- data_dict[k] = {
270
- "y": tsne_result[k],
271
- "y_hat": tsne_result_hat[k] }
272
-
273
- figs[k]= make_PCA_figure(data_dict[k], title=self.type + " TSNE; track: "+k)
274
-
275
-
276
- return figs
277
-
278
- def do_PCA_figure(self, dict_features_y, dict_features_y_hat, fit_mode="target", dict_cluster=None, dict_taxonomy=None):
279
- """
280
- Perform PCA on the features and create a figure.
281
-
282
- Args:
283
- dict_features_y (dict): Dictionary containing features for the first set.
284
- dict_features_y_hat (dict): Dictionary containing features for the second set.
285
-
286
- Returns:
287
- fig: The created figure.
288
- """
289
- import matplotlib.pyplot as plt
290
- from sklearn.decomposition import PCA
291
-
292
-
293
- y_values = {}
294
- y_hat_values = {}
295
- if dict_cluster is not None:
296
- clusters={}
297
- clusters_hat= {}
298
-
299
- for key, track_name in self.taxonomy_ref.items():
300
-
301
-
302
- y_values[track_name] = []
303
- if dict_cluster is not None:
304
- clusters[track_name] = []
305
- for k in range(len(dict_features_y.keys())):
306
- print("taxonomy",k, dict_taxonomy[k])
307
- index = [i for i, tax_id in enumerate(dict_taxonomy[k]) if tax_id == key]
308
- if index: # If any matches were found
309
- # Select the corresponding rows from dict_features_y[k]
310
- selected_features = dict_features_y[k][index]
311
- y_values[track_name].append(selected_features)
312
-
313
- if dict_cluster is not None:
314
- clusters[track_name].append(dict_cluster[k].unsqueeze(0))
315
-
316
- #check if y_values[track_name] is empty
317
- if not y_values[track_name]:
318
- print(f"Warning: No features found for track {track_name} with key {key}. Skipping this track.")
319
- #remove the track from the dictionaries
320
- y_values.pop(track_name, None)
321
- continue
322
-
323
- y_values[track_name]=torch.cat(y_values[track_name], dim=0)
324
- y_values[track_name]=torch.nan_to_num(y_values[track_name], nan=0)
325
-
326
- if dict_cluster is not None:
327
- clusters[track_name]=torch.cat(clusters[track_name], dim=0)
328
-
329
- y_hat_values[track_name] = []
330
- if dict_cluster is not None:
331
- clusters_hat[track_name] = []
332
-
333
- for k in range(len(dict_features_y_hat.keys())):
334
- index = [i for i, tax_id in enumerate(dict_taxonomy[k]) if tax_id == key]
335
- if index: # If any matches were found
336
- # Select the corresponding rows from dict_features_y[k]
337
- selected_features = dict_features_y_hat[k][index]
338
- y_hat_values[track_name].append(selected_features)
339
- if dict_cluster is not None:
340
- clusters_hat[track_name].append(dict_cluster[k].unsqueeze(0))
341
-
342
- y_hat_values[track_name]=torch.cat(y_hat_values[track_name], dim=0)
343
- y_hat_values[track_name]=torch.nan_to_num(y_hat_values[track_name], nan=0)
344
-
345
- if dict_cluster is not None:
346
- clusters_hat[track_name]=torch.cat(clusters_hat[track_name], dim=0)
347
-
348
-
349
- if self.pca is None:
350
- self.pca= {}
351
-
352
- pca_result = {}
353
- pca_result_hat = {}
354
- data_dict = {}
355
- figs= {}
356
-
357
- for k in y_values.keys():
358
- print("Processing track:", k)
359
- #check if pca already exists for this key
360
- if k not in self.pca.keys():
361
- if fit_mode == "target":
362
- print("Fitting PCA for target values only track:", k)
363
- self.pca[k] = PCA(n_components=2).fit(y_values[k].cpu().numpy())
364
- elif fit_mode == "all":
365
- print("Fitting PCA for all values track:", k)
366
- self.pca[k] = PCA(n_components=2).fit(torch.cat([y_values[k], y_hat_values[k]], dim=0).cpu().numpy())
367
- else:
368
- raise ValueError(f"Unknown fit_mode: {fit_mode}. Supported modes: target, all")
369
-
370
- pca_result[k]=self.pca[k].transform(y_values[k].cpu().numpy())
371
- pca_result_hat[k] = self.pca[k].transform(y_hat_values[k].cpu().numpy())
372
-
373
-
374
- if dict_cluster is not None:
375
- data_dict[k] = {
376
- "y_C0": pca_result[k][clusters[k] == 0],
377
- "y_hat_C0": pca_result_hat[k][clusters_hat[k] == 0],
378
- "y_C1": pca_result[k][clusters[k] == 1],
379
- "y_hat_C1": pca_result_hat[k][clusters_hat[k] == 1]
380
- }
381
- else:
382
- data_dict[k] = {
383
- "y": pca_result[k],
384
- "y_hat": pca_result_hat[k]
385
- }
386
-
387
-
388
- figs[k]= make_PCA_figure(data_dict[k], title=self.type + " PCA; track: "+k)
389
-
390
-
391
- return figs
392
-
393
-
394
- class KADFeatures(DistMetric):
395
- """
396
- Class for computing the pairwise spectral metric.
397
-
398
- This class inherits from PairwiseMetric and implements the compute method
399
- to calculate the pairwise spectral metric.
400
- """
401
- def __init__(self,
402
- type=None,
403
- sample_rate=44100,
404
- KAD_args=None,
405
- *args, **kwargs):
406
- """
407
- Initialize the PairwiseSpectral instance.
408
-
409
- Args:
410
- *args: Variable length argument list.
411
- **kwargs: Arbitrary keyword arguments.
412
- """
413
- self.type = type
414
-
415
- self.device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
416
-
417
- self.KAD_args = KAD_args
418
-
419
- assert self.KAD_args is not None, "FAD_args must be provided"
420
-
421
- if self.KAD_args.do_PCA_figure:
422
- self.pca = None
423
-
424
- self.bandwidth = None
425
-
426
- super().__init__(self.type, sample_rate, *args, **kwargs)
427
-
428
- def calculate_KAD_distance(self, dict_features_y, dict_features_y_hat, dict_taxonomy=None):
429
-
430
- y_values = {}
431
- y_hat_values = {}
432
- for key, track_name in self.taxonomy_ref.items():
433
- y_values[track_name] = []
434
- for k in range(len(dict_features_y.keys())):
435
- index = [i for i, tax_id in enumerate(dict_taxonomy[k]) if tax_id == key]
436
- if index: # If any matches were found
437
- # Select the corresponding rows from dict_features_y[k]
438
- selected_features = dict_features_y[k][index]
439
- y_values[track_name].append(selected_features)
440
- #else:
441
- # y_values[track_name].append(None) # Append zeros if no match found
442
- if not y_values[track_name]:
443
- print(f"Warning: No features found for track {track_name} with key {key}. Skipping this track.")
444
- #remove the track from the dictionaries
445
- y_values.pop(track_name, None)
446
- continue
447
-
448
- y_values[track_name]=torch.cat(y_values[track_name], dim=0)
449
- y_values[track_name]=torch.nan_to_num(y_values[track_name], nan=0)
450
-
451
- y_hat_values[track_name] = []
452
- for k in range(len(dict_features_y_hat.keys())):
453
- index = [i for i, tax_id in enumerate(dict_taxonomy[k]) if tax_id == key]
454
- if index: # If any matches were found
455
- # Select the corresponding rows from dict_features_y[k]
456
- selected_features = dict_features_y_hat[k][index]
457
- y_hat_values[track_name].append(selected_features)
458
- #else:
459
- # y_hat_values[track_name].append(None) # Append zeros if no match found
460
-
461
- y_hat_values[track_name]=torch.cat(y_hat_values[track_name], dim=0)
462
- y_hat_values[track_name]=torch.nan_to_num(y_hat_values[track_name], nan=0)
463
-
464
-
465
- KAD={}
466
- weight={}
467
- total_examples= 0
468
- for key in y_values.keys():
469
- y= y_values[key]
470
- y_hat= y_hat_values[key]
471
- with torch.no_grad():
472
- KAD[key]=calc_kernel_audio_distance(y, y_hat, device=self.device, bandwidth=self.bandwidth, kernel=self.KAD_args.kernel, precision=torch.float32)
473
-
474
- weight[key]= y.shape[0]
475
- total_examples += weight[key]
476
-
477
- KAD= sum([KAD[k] * weight[k] for k in KAD.keys()]) / total_examples
478
-
479
- return KAD.cpu().item()
480
-
481
- def calculate_bandwidth(self, dict_features_y):
482
-
483
- print("Calculating bandwidth...")
484
- y= torch.cat(list(dict_features_y.values()), dim=0)
485
- print("y shape:", y.shape)
486
- self.bandwidth = median_pairwise_distance(y)
487
- print("Bandwidth:", self.bandwidth)
488
-
489
-
490
-
491
- def compute(self, dict_y, dict_y_hat, dict_taxonomy=None, dict_p_hat=None, *args, **kwargs):
492
- """
493
- Compute the pairwise spectral metric.
494
-
495
- Args:
496
- *args: Variable length argument list.
497
- **kwargs: Arbitrary keyword arguments.
498
-
499
- Returns:
500
- The computed pairwise spectral metric.
501
- """
502
-
503
- print("Computing KAD distance...")
504
-
505
- dict_features_y={}
506
- dict_features_y_hat={}
507
-
508
- if dict_y_hat is None and dict_p_hat is not None:
509
-
510
- for key in dict_y.keys():
511
- y= dict_y[key]
512
-
513
- embed= dict_p_hat[key]
514
- embed=torch.tensor(embed).to(self.device)
515
-
516
- embed=embed*math.sqrt(embed.shape[-1]) # Scale the embedding
517
-
518
- embed_fxenc=embed[...,:2048]/ math.sqrt(2048) # Scale the first 128 dimensions
519
- embed_AF=embed[...,2048:]/ math.sqrt(64) # Scale the last 128 dimensions
520
-
521
-
522
- if self.type == "FxEncoder++":
523
- p_hat= embed_fxenc
524
-
525
- elif self.type == "DynamicFeatures":
526
- p_hat= embed_AF
527
-
528
- n, c, d=y.shape
529
-
530
- assert c==2, f"Expected 2 channels, got {c} for key {key}"
531
-
532
- y=torch.tensor(y).to(self.device)
533
-
534
- with torch.no_grad():
535
- feat_y= self.feat_extractor(y)
536
-
537
- assert p_hat.shape == feat_y.shape, f"Shape mismatch for key {key}: {p_hat.shape} vs {feat_y.shape}"
538
-
539
- dict_features_y[key] = feat_y
540
- dict_features_y_hat[key] = p_hat
541
-
542
- else:
543
- for key in dict_y.keys():
544
- y= dict_y[key]
545
- y_hat= dict_y_hat[key]
546
-
547
- assert y.shape == y_hat.shape, f"Shape mismatch for key {key}: {y.shape} vs {y_hat.shape}"
548
-
549
- n, c, d=y.shape
550
- if dict_taxonomy is not None:
551
- taxonomy= dict_taxonomy[key]
552
- assert len(taxonomy) == n, f"Taxonomy length mismatch for key {key}: {len(taxonomy)} vs {n}"
553
-
554
- assert c==2, f"Expected 2 channels, got {c} for key {key}"
555
-
556
- y=torch.tensor(y).to(self.device)
557
- y_hat=torch.tensor(y_hat).to(self.device)
558
-
559
- with torch.no_grad():
560
- feat_y= self.feat_extractor(y)
561
- feat_y_hat= self.feat_extractor(y_hat)
562
-
563
- dict_features_y[key] = feat_y
564
- dict_features_y_hat[key] = feat_y_hat
565
-
566
- dict_output = {}
567
- if self.KAD_args.do_PCA_figure:
568
- dict_figs=self.do_PCA_figure(dict_features_y, dict_features_y_hat, dict_taxonomy=dict_taxonomy, fit_mode=self.KAD_args.PCA_fit_mode)
569
- #returns a dictionary with figures for each taxonomy key
570
- #modify the key to include the type
571
-
572
- for k, v in dict_figs.items():
573
- #modify the key to include the type
574
- key= self.type+ "_PCA_figure_" + k
575
- dict_output[key] = v
576
-
577
- if self.KAD_args.do_TSNE_figure:
578
- dict_figs=self.do_TSNE_figure(dict_features_y, dict_features_y_hat, dict_taxonomy=dict_taxonomy)
579
- for k, fig in dict_figs.items():
580
- key= self.type+ "_TSNE_figure"+k
581
- dict_output[key] = fig
582
-
583
-
584
-
585
- # Compute the distance between the mean features
586
- #FAD_distance = self.calculate_FAD_distance(y_mean, y_cov, y_hat_mean, y_hat_cov)
587
- if self.bandwidth is None:
588
- self.calculate_bandwidth(dict_features_y)
589
-
590
-
591
- KAD_distance = self.calculate_KAD_distance(dict_features_y, dict_features_y_hat, dict_taxonomy)
592
-
593
-
594
- return KAD_distance, dict_output
595
-
596
-
597
-
598
- def metric_factory(metric_name, sample_rate, *args, **kwargs):
599
- """
600
- Factory function to create a metric function based on the metric name.
601
-
602
- Args:
603
- metric_name (str): The name of the metric to create.
604
- *args: Variable length argument list.
605
- **kwargs: Arbitrary keyword arguments.
606
-
607
- Returns:
608
- An instance of a class that implements the metric function.
609
- """
610
- if metric_name == "kad-FxEncoder++":
611
- return KADFeatures(*args, **kwargs, type="FxEncoder++", sample_rate=sample_rate)
612
- elif metric_name == "kad-DynamicFeatures":
613
- return KADFeatures(*args, **kwargs, type="DynamicFeatures", sample_rate=sample_rate)
614
- else:
615
- raise ValueError(f"Unknown metric: {metric_name}")
616
-
617
- # Example usage:
618
- #metric_instance = metric_factory("pairwise-spectral")
619
- #```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/evaluation/pairwise_metrics.py DELETED
@@ -1,958 +0,0 @@
1
-
2
- import math
3
- import torchaudio
4
- import os
5
- from importlib import import_module
6
- import yaml
7
- import torch
8
- from utils.feature_extractors.load_features import load_AFxRep, load_fx_encoder, load_fx_encoder_plusplus, load_fx_encoder_plusplus_2048
9
- from utils.MSS_loss import MultiScale_Spectral_Loss_MidSide_DDSP
10
-
11
- class PairwiseMetric:
12
- """
13
- Base class for pairwise metrics.
14
-
15
- This class should be subclassed to implement specific pairwise metrics.
16
- """
17
- def __init__(self, *args, **kwargs):
18
- """
19
- Initialize the PairwiseMetric instance.
20
-
21
- Args:
22
- *args: Variable length argument list.
23
- **kwargs: Arbitrary keyword arguments.
24
- """
25
- pass
26
-
27
- def compute(self, *args, **kwargs):
28
- raise NotImplementedError("Subclasses should implement this method.")
29
-
30
- class PairwiseFeatures(PairwiseMetric):
31
- """
32
- Class for computing the pairwise spectral metric.
33
-
34
- This class inherits from PairwiseMetric and implements the compute method
35
- to calculate the pairwise spectral metric.
36
- """
37
- def __init__(self,
38
- type=None,
39
- sample_rate=44100,
40
- *args, **kwargs):
41
- """
42
- Initialize the PairwiseSpectral instance.
43
-
44
- Args:
45
- *args: Variable length argument list.
46
- **kwargs: Arbitrary keyword arguments.
47
- """
48
- self.type = type
49
- self.sample_rate = sample_rate
50
-
51
- self.device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
52
-
53
- if self.type == "fx_encoder":
54
- self.model_args= kwargs.get("fx_encoder_args", None)
55
-
56
- assert self.model_args is not None, "model_args must be provided for fx_encoder type"
57
-
58
- self.distance_type=self.model_args.distance_type
59
-
60
- self.feat_extractor = load_fx_encoder(self.model_args, self.device)
61
-
62
-
63
- #self.feat_extractor = load_effects_encoder(ckpt_path=ckpt_path).to(self.device)
64
- elif self.type == "fxenc++":
65
- self.model_args= kwargs.get("fx_encoder_plusplus_args", None)
66
-
67
- assert self.model_args is not None, "model_args must be provided for fx_encoder type"
68
-
69
- self.distance_type=self.model_args.distance_type
70
-
71
- self.feat_extractor = load_fx_encoder_plusplus(self.model_args, self.device)
72
-
73
-
74
- #self.feat_extractor = load_effects_encoder(ckpt_path=ckpt_path).to(self.device)
75
-
76
- elif self.type == "logrms":
77
- self.model_args= kwargs.get("logrms_args", None)
78
-
79
- assert self.model_args is not None, "model_args must be provided for logrms type"
80
- self.distance_type=self.model_args.distance_type
81
-
82
- from utils.feature_extractors.dsp_features import compute_log_rms
83
-
84
- self.feat_extractor = lambda x: compute_log_rms(x)
85
-
86
- elif self.type == "crestfactor":
87
- self.model_args= kwargs.get("crestfactor_args", None)
88
- assert self.model_args is not None, "model_args must be provided for crestfactor type"
89
-
90
- self.distance_type=self.model_args.distance_type
91
-
92
- from utils.feature_extractors.dsp_features import compute_crest_factor
93
-
94
- self.feat_extractor = lambda x: compute_crest_factor(x)
95
- elif self.type == "logspread":
96
- self.model_args= kwargs.get("logspread_args", None)
97
- assert self.model_args is not None, "model_args must be provided for logspread type"
98
-
99
- self.distance_type=self.model_args.distance_type
100
-
101
- from utils.feature_extractors.dsp_features import compute_log_spread
102
-
103
- self.feat_extractor = lambda x: compute_log_spread(x).view(-1, 1)
104
- elif self.type == "stereowidth":
105
- self.model_args= kwargs.get("stereowidth_args", None)
106
- assert self.model_args is not None, "model_args must be provided for stereowidth type"
107
-
108
- self.distance_type=self.model_args.distance_type
109
-
110
- from utils.feature_extractors.dsp_features import compute_stereo_width
111
- self.feat_extractor = lambda x: compute_stereo_width(x).view(-1, 1)
112
-
113
- elif self.type == "stereoimbalance":
114
- self.model_args= kwargs.get("stereoimbalance_args", None)
115
- assert self.model_args is not None, "model_args must be provided for stereoimbalance type"
116
- self.distance_type=self.model_args.distance_type
117
- from utils.feature_extractors.dsp_features import compute_stereo_imbalance
118
- self.feat_extractor = lambda x: compute_stereo_imbalance(x).view(-1, 1)
119
-
120
- elif self.type== "AFxRep-mid" or self.type== "AFxRep-side" or self.type== "AFxRep":
121
-
122
- self.model_args= kwargs.get("AFxRep_args", None)
123
-
124
- assert self.model_args is not None, "model_args must be provided for AFxRep type"
125
-
126
- self.distance_type=self.model_args.distance_type
127
-
128
- feat_extractor = load_AFxRep(self.model_args, self.device)
129
-
130
- if self.type == "AFxRep-mid":
131
- def feat_extractor_mid(x):
132
-
133
- features= feat_extractor(x)
134
-
135
- #divide by 2 to get mid and side features
136
-
137
- feat_mid, feat_side = features.chunk(2, dim=-1)
138
-
139
- return feat_mid
140
-
141
- self.feat_extractor = feat_extractor_mid
142
-
143
- elif self.type == "AFxRep-side":
144
- def feat_extractor_side(x):
145
-
146
- features= feat_extractor(x)
147
-
148
- #divide by 2 to get mid and side features
149
-
150
- feat_mid, feat_side = features.chunk(2, dim=-1)
151
-
152
- return feat_side
153
-
154
- self.feat_extractor = feat_extractor_side
155
- else:
156
- self.feat_extractor = feat_extractor
157
-
158
-
159
- super().__init__(*args, **kwargs)
160
-
161
- def compute_feature_distance(self, y, y_hat, sample_rate, type):
162
-
163
- y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
164
- y_hat=torch.tensor(y_hat).permute(1,0).unsqueeze(0).to(self.device)
165
-
166
- with torch.no_grad():
167
- feat_y= self.feat_extractor(y)
168
- feat_y_hat= self.feat_extractor(y_hat)
169
-
170
- if self.distance_type == "cosine":
171
- cos_dist= 1- torch.cosine_similarity(feat_y_hat, feat_y, dim=1)
172
-
173
-
174
- return {"distance": cos_dist.mean().item()}
175
- elif self.distance_type == "l1":
176
- l1_dist = torch.abs(feat_y_hat - feat_y).mean(dim=1)
177
-
178
- return {"distance": l1_dist.mean().item()}
179
-
180
-
181
- def compute(self, dict_y, dict_y_hat, dict_x, *args, **kwargs):
182
- """
183
- Compute the pairwise spectral metric.
184
-
185
- Args:
186
- *args: Variable length argument list.
187
- **kwargs: Arbitrary keyword arguments.
188
-
189
- Returns:
190
- The computed pairwise spectral metric.
191
- """
192
-
193
-
194
-
195
- dict_features={}
196
-
197
- for key in dict_y.keys():
198
- y= dict_y[key]
199
- y_hat= dict_y_hat[key]
200
-
201
- assert y.shape == y_hat.shape, f"Shape mismatch for key {key}: {y.shape} vs {y_hat.shape}"
202
-
203
- c, d=y.shape
204
- #assert b==1, f"Expected batch size of 1, got {b} for key {key}"
205
-
206
- assert c==2, f"Expected 2 channels, got {c} for key {key}"
207
-
208
- y=y.T
209
- y_hat=y_hat.T
210
-
211
-
212
- if self.type == "spectral":
213
- from evaluation.automix_evaluation import compute_spectral_features
214
- dict_features_out= compute_spectral_features(y_hat, y ,self.sample_rate)
215
- dict_features[key] = dict_features_out['mean_mape_spectral']
216
- elif self.type=="panning":
217
- from evaluation.automix_evaluation import compute_panning_features
218
- dict_features_out = compute_panning_features(y_hat, y, self.sample_rate)
219
- dict_features[key] = dict_features_out['mean_mape_panning']
220
- elif self.type=="loudness":
221
- from evaluation.automix_evaluation import compute_loudness_features
222
- dict_features_out = compute_loudness_features(y_hat, y, self.sample_rate)
223
- dict_features[key] = dict_features_out['mean_mape_loudness']
224
- elif self.type=="dynamic":
225
- from evaluation.automix_evaluation import compute_dynamic_features
226
- dict_features_out = compute_dynamic_features(y_hat, y, self.sample_rate)
227
- dict_features[key] = dict_features_out['mean_mape_dynamic']
228
- elif self.type=="fx_encoder":
229
- dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
230
- dict_features[key] = dict_features_out["distance"]
231
- elif self.type=="AFxRep":
232
- dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
233
- dict_features[key] = dict_features_out["distance"]
234
- elif self.type=="AFxRep-mid":
235
- dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
236
- dict_features[key] = dict_features_out["distance"]
237
- elif self.type=="AFxRep-side":
238
- dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
239
- dict_features[key] = dict_features_out["distance"]
240
- elif self.type=="fxenc++":
241
- dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
242
- dict_features[key] = dict_features_out["distance"]
243
- elif self.type=="logrms":
244
- dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
245
- dict_features[key] = dict_features_out["distance"]
246
- elif self.type=="crestfactor":
247
- dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
248
- dict_features[key] = dict_features_out["distance"]
249
- elif self.type=="logspread":
250
- dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
251
- dict_features[key] = dict_features_out["distance"]
252
- elif self.type=="stereowidth":
253
- dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
254
- dict_features[key] = dict_features_out["distance"]
255
- elif self.type=="stereoimbalance":
256
- dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
257
- dict_features[key] = dict_features_out["distance"]
258
-
259
-
260
- # Compute the mean of the features across all keys
261
-
262
- mean_features = sum(dict_features.values()) / len(dict_features)
263
-
264
- return mean_features, {}
265
-
266
- class PairwiseStyleMultitrackFeatures(PairwiseMetric):
267
- """
268
- Class for computing the pairwise spectral metric.
269
-
270
- This class inherits from PairwiseMetric and implements the compute method
271
- to calculate the pairwise spectral metric.
272
- """
273
- def __init__(self,
274
- type=None,
275
- sample_rate=44100,
276
- *args, **kwargs):
277
- """
278
- Initialize the PairwiseSpectral instance.
279
-
280
- Args:
281
- *args: Variable length argument list.
282
- **kwargs: Arbitrary keyword arguments.
283
- """
284
- self.type = type
285
- self.sample_rate = sample_rate
286
-
287
- self.device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
288
-
289
- if self.type == "fx_encoder":
290
- raise NotImplementedError("Style features for fx_encoder not implemented yet")
291
- self.model_args= kwargs.get("fx_encoder_args", None)
292
-
293
- assert self.model_args is not None, "model_args must be provided for fx_encoder type"
294
-
295
- self.distance_type=self.model_args.distance_type
296
-
297
- self.feat_extractor = load_fx_encoder(self.model_args, self.device)
298
-
299
-
300
- #self.feat_extractor = load_effects_encoder(ckpt_path=ckpt_path).to(self.device)
301
-
302
- elif self.type== "AFxRep-mid" or self.type== "AFxRep-side" or self.type== "AFxRep":
303
-
304
- self.model_args= kwargs.get("AFxRep_args", None)
305
-
306
- assert self.model_args is not None, "model_args must be provided for AFxRep type"
307
-
308
- self.distance_type=self.model_args.distance_type
309
-
310
- feat_extractor = load_AFxRep(self.model_args, self.device)
311
-
312
- if self.type == "AFxRep-mid":
313
- def feat_extractor_mid(x):
314
-
315
- features= feat_extractor(x)
316
-
317
- #divide by 2 to get mid and side features
318
-
319
- feat_mid, feat_side = features.chunk(2, dim=-1)
320
-
321
- return feat_mid
322
-
323
- self.feat_extractor = feat_extractor_mid
324
-
325
- elif self.type == "AFxRep-side":
326
- def feat_extractor_side(x):
327
-
328
- features= feat_extractor(x)
329
-
330
- #divide by 2 to get mid and side features
331
-
332
- feat_mid, feat_side = features.chunk(2, dim=-1)
333
-
334
- return feat_side
335
-
336
- self.feat_extractor = feat_extractor_side
337
- else:
338
- self.feat_extractor = feat_extractor
339
-
340
- elif self.type == "fxenc2048AFv3-fxenc++":
341
-
342
- self.model_args= kwargs.get("fx_encoder_plusplus_args", None)
343
- assert self.model_args is not None, "model_args must be provided for fxencAFv2-fxenc++ type"
344
-
345
- self.distance_type= self.model_args.distance_type
346
-
347
- fxencoder = load_fx_encoder_plusplus_2048(self.model_args, self.device)
348
-
349
- def feat_extractor_fn(x):
350
- z= fxencoder(x)
351
- z=torch.nn.functional.normalize(z, dim=-1, p=2) # normalize to unit variance
352
- return z
353
-
354
- self.feat_extractor = feat_extractor_fn
355
-
356
- elif self.type == "fxenc2048AFv3-AF":
357
-
358
- from utils.AF_features_embedding_v2 import AF_fourier_embedding
359
- AFembedding= AF_fourier_embedding(device=self.device)
360
-
361
- self.distance_type="cosine"
362
-
363
- def feat_extracfor_fn(x):
364
- z, _ = AFembedding.encode(x)
365
- return z
366
-
367
- self.feat_extractor = feat_extracfor_fn
368
-
369
-
370
- super().__init__(*args, **kwargs)
371
-
372
- def compute_feature_distance(self, y, p_hat, sample_rate, type):
373
-
374
- y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
375
- #y_hat=torch.tensor(y_hat).permute(1,0).unsqueeze(0).to(self.device)
376
-
377
- with torch.no_grad():
378
- feat_y= self.feat_extractor(y)
379
- #feat_y_hat= self.feat_extractor(y_hat)
380
-
381
- assert p_hat.shape == feat_y.shape, f"Shape mismatch: p_hat {p_hat.shape} vs feat_y {feat_y.shape}"
382
-
383
- if self.distance_type == "cosine":
384
- cos_dist= 1- torch.cosine_similarity(p_hat, feat_y, dim=1)
385
-
386
- return {"distance": cos_dist.mean().item()}
387
-
388
-
389
-
390
- def compute(self, dict_y, dict_y_hat, dict_x, dict_p_hat=None, *args, **kwargs):
391
- """
392
- Compute the pairwise spectral metric.
393
-
394
- Args:
395
- *args: Variable length argument list.
396
- **kwargs: Arbitrary keyword arguments.
397
-
398
- Returns:
399
- The computed pairwise spectral metric.
400
- """
401
-
402
- dict_features={}
403
-
404
- index=0
405
-
406
- for key in dict_y.keys():
407
- y= dict_y[key]
408
-
409
- embed= dict_p_hat[key]
410
- embed=torch.tensor(embed).to(self.device).unsqueeze(0)
411
-
412
- print(f"Processing key: {key}, embed shape: {embed.shape}")
413
- if "AFxRep" in self.type:
414
- embed_mid, embed_side = torch.chunk(embed, 2, dim=-1)
415
-
416
- if self.type== "AFxRep-mid":
417
- p_hat= embed_mid
418
- elif self.type== "AFxRep-side":
419
- p_hat= embed_side
420
- elif self.type== "AFxRep":
421
- p_hat= embed
422
- elif "fxenc2048AFv3" in self.type:
423
-
424
- embed=embed*math.sqrt(embed.shape[-1]) # Scale the embedding
425
- embed_fxenc=embed[...,:2048]/ math.sqrt(2048) # Scale the first 128 dimensions
426
- embed_AF=embed[...,2048:]/ math.sqrt(64) # Scale the last 128 dimensions
427
-
428
- if self.type == "fxenc2048AFv3-fxenc++":
429
- p_hat= embed_fxenc
430
- elif self.type == "fxenc2048AFv3-AF":
431
- p_hat= embed_AF
432
-
433
-
434
- n, c, d=y.shape
435
-
436
- #assert b==1, f"Expected batch size of 1, got {b} for key {key}"
437
-
438
- assert c==2, f"Expected 2 channels, got {c} for key {key}"
439
-
440
- for i in range(n):
441
- y_i=y[i].T
442
-
443
- if self.type=="fx_encoder":
444
- raise NotImplementedError("Style features for fx_encoder not implemented yet")
445
- dict_features_out = self.compute_feature_distance(y, p_hat, self.sample_rate, type=self.type)
446
- dict_features[key] = dict_features_out["distance"]
447
- elif self.type=="AFxRep":
448
- dict_features_out = self.compute_feature_distance(y_i, p_hat[i], self.sample_rate, type=self.type)
449
- dict_features[index] = dict_features_out["distance"]
450
- elif self.type=="AFxRep-mid":
451
- dict_features_out = self.compute_feature_distance(y_i, p_hat[i], self.sample_rate, type=self.type)
452
- dict_features[index] = dict_features_out["distance"]
453
- elif self.type=="AFxRep-side":
454
- dict_features_out = self.compute_feature_distance(y_i, p_hat[i], self.sample_rate, type=self.type)
455
- dict_features[index] = dict_features_out["distance"]
456
- elif self.type=="fxenc2048AFv3-fxenc++":
457
- dict_features_out = self.compute_feature_distance(y_i, p_hat[i], self.sample_rate, type=self.type)
458
- dict_features[index] = dict_features_out["distance"]
459
- elif self.type=="fxenc2048AFv3-AF":
460
- dict_features_out = self.compute_feature_distance(y_i, p_hat, self.sample_rate, type=self.type)
461
- dict_features[index] = dict_features_out["distance"]
462
-
463
-
464
- mean_features = sum(dict_features.values()) / len(dict_features)
465
-
466
- return mean_features, {}
467
- class PairwiseStyleFeatures(PairwiseMetric):
468
- """
469
- Class for computing the pairwise spectral metric.
470
-
471
- This class inherits from PairwiseMetric and implements the compute method
472
- to calculate the pairwise spectral metric.
473
- """
474
- def __init__(self,
475
- type=None,
476
- sample_rate=44100,
477
- *args, **kwargs):
478
- """
479
- Initialize the PairwiseSpectral instance.
480
-
481
- Args:
482
- *args: Variable length argument list.
483
- **kwargs: Arbitrary keyword arguments.
484
- """
485
- self.type = type
486
- self.sample_rate = sample_rate
487
-
488
- self.device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
489
-
490
- if self.type == "fx_encoder":
491
- raise NotImplementedError("Style features for fx_encoder not implemented yet")
492
- self.model_args= kwargs.get("fx_encoder_args", None)
493
-
494
- assert self.model_args is not None, "model_args must be provided for fx_encoder type"
495
-
496
- self.distance_type=self.model_args.distance_type
497
-
498
- self.feat_extractor = load_fx_encoder(self.model_args, self.device)
499
-
500
-
501
- #self.feat_extractor = load_effects_encoder(ckpt_path=ckpt_path).to(self.device)
502
-
503
- elif self.type== "AFxRep-mid" or self.type== "AFxRep-side" or self.type== "AFxRep":
504
-
505
- self.model_args= kwargs.get("AFxRep_args", None)
506
-
507
- assert self.model_args is not None, "model_args must be provided for AFxRep type"
508
-
509
- self.distance_type=self.model_args.distance_type
510
-
511
- feat_extractor = load_AFxRep(self.model_args, self.device)
512
-
513
- if self.type == "AFxRep-mid":
514
- def feat_extractor_mid(x):
515
-
516
- features= feat_extractor(x)
517
-
518
- #divide by 2 to get mid and side features
519
-
520
- feat_mid, feat_side = features.chunk(2, dim=-1)
521
-
522
- return feat_mid
523
-
524
- self.feat_extractor = feat_extractor_mid
525
-
526
- elif self.type == "AFxRep-side":
527
- def feat_extractor_side(x):
528
-
529
- features= feat_extractor(x)
530
-
531
- #divide by 2 to get mid and side features
532
-
533
- feat_mid, feat_side = features.chunk(2, dim=-1)
534
-
535
- return feat_side
536
-
537
- self.feat_extractor = feat_extractor_side
538
- else:
539
- self.feat_extractor = feat_extractor
540
-
541
- elif self.type == "fxenc2048AFv3-fxenc++":
542
-
543
- self.model_args= kwargs.get("fx_encoder_plusplus_args", None)
544
- assert self.model_args is not None, "model_args must be provided for fxencAFv2-fxenc++ type"
545
-
546
- self.distance_type= self.model_args.distance_type
547
-
548
- fxencoder = load_fx_encoder_plusplus_2048(self.model_args, self.device)
549
-
550
- def feat_extractor_fn(x):
551
- z= fxencoder(x)
552
- z=torch.nn.functional.normalize(z, dim=-1, p=2) # normalize to unit variance
553
- return z
554
-
555
- self.feat_extractor = feat_extractor_fn
556
-
557
- elif self.type == "fxenc2048AFv3-AF":
558
-
559
- from utils.AF_features_embedding_v2 import AF_fourier_embedding
560
- AFembedding= AF_fourier_embedding(device=self.device)
561
-
562
- self.distance_type="cosine"
563
-
564
- def feat_extracfor_fn(x):
565
- z, _ = AFembedding.encode(x)
566
- return z
567
-
568
- self.feat_extractor = feat_extracfor_fn
569
-
570
-
571
- super().__init__(*args, **kwargs)
572
-
573
- def compute_feature_distance(self, y, p_hat, sample_rate, type):
574
-
575
- y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
576
- #y_hat=torch.tensor(y_hat).permute(1,0).unsqueeze(0).to(self.device)
577
-
578
- with torch.no_grad():
579
- feat_y= self.feat_extractor(y)
580
- #feat_y_hat= self.feat_extractor(y_hat)
581
-
582
- assert p_hat.shape == feat_y.shape, f"Shape mismatch: p_hat {p_hat.shape} vs feat_y {feat_y.shape}"
583
-
584
- if self.distance_type == "cosine":
585
- cos_dist= 1- torch.cosine_similarity(p_hat, feat_y, dim=1)
586
-
587
- return {"distance": cos_dist.mean().item()}
588
-
589
-
590
-
591
- def compute(self, dict_y, dict_y_hat, dict_x, dict_p_hat=None, *args, **kwargs):
592
- """
593
- Compute the pairwise spectral metric.
594
-
595
- Args:
596
- *args: Variable length argument list.
597
- **kwargs: Arbitrary keyword arguments.
598
-
599
- Returns:
600
- The computed pairwise spectral metric.
601
- """
602
-
603
- dict_features={}
604
-
605
- for key in dict_y.keys():
606
- y= dict_y[key]
607
-
608
- embed= dict_p_hat[key]
609
- embed=torch.tensor(embed).to(self.device).unsqueeze(0)
610
-
611
- print(f"Processing key: {key}, embed shape: {embed.shape}")
612
- if "AFxRep" in self.type:
613
- embed_mid, embed_side = torch.chunk(embed, 2, dim=-1)
614
-
615
- if self.type== "AFxRep-mid":
616
- p_hat= embed_mid
617
- elif self.type== "AFxRep-side":
618
- p_hat= embed_side
619
- elif self.type== "AFxRep":
620
- p_hat= embed
621
- elif "fxenc2048AFv3" in self.type:
622
-
623
- embed=embed*math.sqrt(embed.shape[-1]) # Scale the embedding
624
- embed_fxenc=embed[...,:2048]/ math.sqrt(2048) # Scale the first 128 dimensions
625
- embed_AF=embed[...,2048:]/ math.sqrt(64) # Scale the last 128 dimensions
626
-
627
- if self.type == "fxenc2048AFv3-fxenc++":
628
- p_hat= embed_fxenc
629
- elif self.type == "fxenc2048AFv3-AF":
630
- p_hat= embed_AF
631
-
632
-
633
- c, d=y.shape
634
- #assert b==1, f"Expected batch size of 1, got {b} for key {key}"
635
-
636
- assert c==2, f"Expected 2 channels, got {c} for key {key}"
637
-
638
- y=y.T
639
- #y_hat=y_hat.T
640
-
641
- if self.type=="fx_encoder":
642
- raise NotImplementedError("Style features for fx_encoder not implemented yet")
643
- dict_features_out = self.compute_feature_distance(y, p_hat, self.sample_rate, type=self.type)
644
- dict_features[key] = dict_features_out["distance"]
645
- elif self.type=="AFxRep":
646
- dict_features_out = self.compute_feature_distance(y, p_hat, self.sample_rate, type=self.type)
647
- dict_features[key] = dict_features_out["distance"]
648
- elif self.type=="AFxRep-mid":
649
- dict_features_out = self.compute_feature_distance(y, p_hat, self.sample_rate, type=self.type)
650
- dict_features[key] = dict_features_out["distance"]
651
- elif self.type=="AFxRep-side":
652
- dict_features_out = self.compute_feature_distance(y, p_hat, self.sample_rate, type=self.type)
653
- dict_features[key] = dict_features_out["distance"]
654
- elif self.type=="fxenc2048AFv3-fxenc++":
655
- dict_features_out = self.compute_feature_distance(y, p_hat, self.sample_rate, type=self.type)
656
- dict_features[key] = dict_features_out["distance"]
657
- elif self.type=="fxenc2048AFv3-AF":
658
- dict_features_out = self.compute_feature_distance(y, p_hat, self.sample_rate, type=self.type)
659
- dict_features[key] = dict_features_out["distance"]
660
-
661
-
662
-
663
- # Compute the mean of the features across all keys
664
-
665
- mean_features = sum(dict_features.values()) / len(dict_features)
666
-
667
- return mean_features, {}
668
-
669
- class PairwiseIMMSS(PairwiseMetric):
670
- """
671
- Class for computing the pairwise LDR metric.
672
-
673
- This class inherits from PairwiseMetric and implements the compute method
674
- to calculate the pairwise LDR metric.
675
- """
676
- def __init__(self, mode=None, *args, **kwargs):
677
- """
678
- Initialize the PairwiseLDR instance.
679
-
680
- Args:
681
- *args: Variable length argument list.
682
- **kwargs: Arbitrary keyword arguments.
683
- """
684
- super().__init__(*args, **kwargs)
685
-
686
- assert mode is not None, "Mode must be specified for PairwiseLDR"
687
-
688
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
689
- if mode == "mss-lr":
690
- multi_scale_spectral_ori = MultiScale_Spectral_Loss_MidSide_DDSP(mode='ori', eps=1e-6, device=device)
691
- self.metric= lambda y_hat, y: multi_scale_spectral_ori(y_hat, y)
692
- elif mode == "mss-ms":
693
- multi_scale_spectral_midside = MultiScale_Spectral_Loss_MidSide_DDSP(mode='midside', eps=1e-6, device=device)
694
- self.metric= lambda y_hat, y: multi_scale_spectral_midside(y_hat, y)
695
-
696
- def compute(self, dict_y, dict_y_hat, dict_x, *args, **kwargs):
697
- """
698
- Compute the pairwise spectral metric.
699
-
700
- Args:
701
- *args: Variable length argument list.
702
- **kwargs: Arbitrary keyword arguments.
703
-
704
- Returns:
705
- The computed pairwise spectral metric.
706
- """
707
-
708
- dict_metrics={}
709
-
710
- for key in dict_y.keys():
711
- y= dict_y[key]
712
- y_hat= dict_y_hat[key]
713
-
714
- assert y.shape == y_hat.shape, f"Shape mismatch for key {key}: {y.shape} vs {y_hat.shape}"
715
-
716
- c, d=y.shape
717
- #assert b==1, f"Expected batch size of 1, got {b} for key {key}"
718
-
719
- assert c==2, f"Expected 2 channels, got {c} for key {key}"
720
-
721
- y=torch.from_numpy(y).cuda().unsqueeze(0)
722
- y_hat=torch.from_numpy(y_hat).cuda().unsqueeze(0)
723
-
724
- metric=self.metric(y_hat, y)
725
-
726
- dict_metrics[key] = metric.item()
727
-
728
- mean_features = sum(dict_metrics.values()) / len(dict_metrics)
729
-
730
- return mean_features, {}
731
- class PairwiseLDR(PairwiseMetric):
732
- """
733
- Class for computing the pairwise LDR metric.
734
-
735
- This class inherits from PairwiseMetric and implements the compute method
736
- to calculate the pairwise LDR metric.
737
- """
738
- def __init__(self, mode=None, *args, **kwargs):
739
- """
740
- Initialize the PairwiseLDR instance.
741
-
742
- Args:
743
- *args: Variable length argument list.
744
- **kwargs: Arbitrary keyword arguments.
745
- """
746
- super().__init__(*args, **kwargs)
747
-
748
- assert mode is not None, "Mode must be specified for PairwiseLDR"
749
-
750
- if mode == "mldr-lr":
751
- from evaluation.ldr import MLDRLoss
752
- raw_metric= MLDRLoss(
753
- sr=44100,
754
- s_taus=[50, 100],
755
- l_taus=[1000, 2000],
756
- ).cuda()
757
- self.metric=lambda y_hat, y: 0.5*raw_metric(y_hat, y)
758
- elif mode == "mldr-ms":
759
- from evaluation.ldr import MLDRLoss
760
- raw_metric= MLDRLoss(
761
- sr=44100,
762
- s_taus=[50, 100],
763
- l_taus=[1000, 2000],
764
- mid_side=True
765
- ).cuda()
766
- self.metric=lambda y_hat, y: 0.25*raw_metric(y_hat, y)
767
-
768
- def compute(self, dict_y, dict_y_hat, dict_x, *args, **kwargs):
769
- """
770
- Compute the pairwise spectral metric.
771
-
772
- Args:
773
- *args: Variable length argument list.
774
- **kwargs: Arbitrary keyword arguments.
775
-
776
- Returns:
777
- The computed pairwise spectral metric.
778
- """
779
-
780
- dict_metrics={}
781
-
782
- for key in dict_y.keys():
783
- y= dict_y[key]
784
- y_hat= dict_y_hat[key]
785
-
786
- assert y.shape == y_hat.shape, f"Shape mismatch for key {key}: {y.shape} vs {y_hat.shape}"
787
-
788
- c, d=y.shape
789
- #assert b==1, f"Expected batch size of 1, got {b} for key {key}"
790
-
791
- assert c==2, f"Expected 2 channels, got {c} for key {key}"
792
-
793
- y=y.T
794
- y_hat=y_hat.T
795
-
796
- y=torch.from_numpy(y).cuda().unsqueeze(0)
797
- y_hat=torch.from_numpy(y_hat).cuda().unsqueeze(0)
798
-
799
- metric=self.metric(y_hat, y)
800
-
801
- dict_metrics[key] = metric.item()
802
-
803
- mean_features = sum(dict_metrics.values()) / len(dict_metrics)
804
-
805
- return mean_features, {}
806
-
807
-
808
- class PairwiseAuraloss(PairwiseMetric):
809
- """
810
- Class for computing the pairwise MSS metric.
811
-
812
- This class inherits from PairwiseMetric and implements the compute method
813
- to calculate the pairwise MSS metric.
814
- """
815
- def __init__(self, mode=None, *args, **kwargs):
816
- """
817
- Initialize the PairwiseMSS instance.
818
-
819
- Args:
820
- *args: Variable length argument list.
821
- **kwargs: Arbitrary keyword arguments.
822
- """
823
- super().__init__(*args, **kwargs)
824
-
825
- assert mode is not None, "Mode must be specified for PairwiseMSS"
826
-
827
- if mode == "mss-lr":
828
- from auraloss.freq import MultiResolutionSTFTLoss
829
- raw_metric=MultiResolutionSTFTLoss(
830
- [128, 512, 2048],
831
- [32, 128, 512],
832
- [128, 512, 2048],
833
- sample_rate=44100,
834
- perceptual_weighting=True,
835
- ).cuda()
836
- self.metric=lambda y_hat, y: raw_metric(y_hat, y)
837
- elif mode == "mss-ms":
838
- from auraloss.freq import SumAndDifferenceSTFTLoss
839
- raw_metric=SumAndDifferenceSTFTLoss(
840
- [128, 512, 2048],
841
- [32, 128, 512],
842
- [128, 512, 2048],
843
- sample_rate=44100,
844
- perceptual_weighting=True,
845
- ).cuda()
846
- self.metric=lambda y_hat, y: 0.5*raw_metric(y_hat, y)
847
-
848
- def compute(self, dict_y, dict_y_hat, dict_x, *args, **kwargs):
849
- """
850
- Compute the pairwise spectral metric.
851
-
852
- Args:
853
- *args: Variable length argument list.
854
- **kwargs: Arbitrary keyword arguments.
855
-
856
- Returns:
857
- The computed pairwise spectral metric.
858
- """
859
-
860
- dict_metrics={}
861
-
862
- for key in dict_y.keys():
863
- y= dict_y[key]
864
- y_hat= dict_y_hat[key]
865
-
866
- assert y.shape == y_hat.shape, f"Shape mismatch for key {key}: {y.shape} vs {y_hat.shape}"
867
-
868
- c, d=y.shape
869
- #assert b==1, f"Expected batch size of 1, got {b} for key {key}"
870
-
871
- assert c==2, f"Expected 2 channels, got {c} for key {key}"
872
-
873
- #y=y.T
874
- #y_hat=y_hat.T
875
-
876
- y=torch.from_numpy(y).cuda().unsqueeze(0)
877
- y_hat=torch.from_numpy(y_hat).cuda().unsqueeze(0)
878
-
879
- metric=self.metric(y_hat, y)
880
-
881
- dict_metrics[key] = metric.item()
882
-
883
- mean_features = sum(dict_metrics.values()) / len(dict_metrics)
884
-
885
- return mean_features, {}
886
-
887
- def metric_factory(metric_name, sample_rate, *args, **kwargs):
888
- """
889
- Factory function to create a metric function based on the metric name.
890
-
891
- Args:
892
- metric_name (str): The name of the metric to create.
893
- *args: Variable length argument list.
894
- **kwargs: Arbitrary keyword arguments.
895
-
896
- Returns:
897
- An instance of a class that implements the metric function.
898
- """
899
- if metric_name == "pairwise-spectral":
900
- return PairwiseFeatures(*args, **kwargs, type="spectral", sample_rate=sample_rate)
901
- elif metric_name == "pairwise-panning":
902
- return PairwiseFeatures(*args, **kwargs, type="panning", sample_rate=sample_rate)
903
- elif metric_name == "pairwise-loudness":
904
- return PairwiseFeatures(*args, **kwargs, type="loudness", sample_rate=sample_rate)
905
- elif metric_name == "pairwise-dynamic":
906
- return PairwiseFeatures(*args, **kwargs, type="dynamic", sample_rate=sample_rate)
907
- elif metric_name == "pairwise-mss-lr":
908
- return PairwiseAuraloss(mode="mss-lr",*args, **kwargs)
909
- elif metric_name == "pairwise-mss-ms":
910
- return PairwiseAuraloss(mode="mss-ms",*args, **kwargs)
911
- elif metric_name == "pairwise-IM-mss-lr":
912
- return PairwiseIMMSS(mode="mss-lr",*args, **kwargs)
913
- elif metric_name == "pairwise-IM-mss-ms":
914
- return PairwiseIMMSS(mode="mss-ms",*args, **kwargs)
915
- elif metric_name == "pairwise-mldr-lr":
916
- return PairwiseLDR(mode="mldr-lr",*args, **kwargs)
917
- elif metric_name == "pairwise-mldr-ms":
918
- return PairwiseLDR(mode="mldr-ms",*args, **kwargs)
919
- elif metric_name == "pairwise-fx_encoder":
920
- return PairwiseFeatures(*args, **kwargs, type="fx_encoder", sample_rate=sample_rate, model_args=kwargs.get('fx_encoder_args', None))
921
- elif metric_name == "pairwise-AFxRep":
922
- return PairwiseFeatures(*args, **kwargs, type="AFxRep", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
923
- elif metric_name == "pairwise-AFxRep-mid":
924
- return PairwiseFeatures(*args, **kwargs, type="AFxRep-mid", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
925
- elif metric_name == "pairwise-AFxRep-side":
926
- return PairwiseFeatures(*args, **kwargs, type="AFxRep-side", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
927
- elif metric_name == "pairwise-style-AFxRep":
928
- return PairwiseStyleFeatures(*args, **kwargs, type="AFxRep", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
929
- elif metric_name == "pairwise-style-AFxRep-mid":
930
- return PairwiseStyleFeatures(*args, **kwargs, type="AFxRep-mid", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
931
- elif metric_name == "pairwise-style-AFxRep-side":
932
- return PairwiseStyleFeatures(*args, **kwargs, type="AFxRep-side", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
933
- elif metric_name == "pairwise-style-fxenc2048AFv3-fxenc++":
934
- return PairwiseStyleFeatures(*args, **kwargs, type="fxenc2048AFv3-fxenc++", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
935
- elif metric_name == "pairwise-style-fxenc2048AFv3-AF":
936
- return PairwiseStyleFeatures(*args, **kwargs, type="fxenc2048AFv3-AF", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
937
- elif metric_name == "pairwise-style-multitrack-fxenc2048AFv3-fxenc++":
938
- return PairwiseStyleMultitrackFeatures(*args, **kwargs, type="fxenc2048AFv3-fxenc++", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
939
- elif metric_name == "pairwise-style-multitrack-fxenc2048AFv3-AF":
940
- return PairwiseStyleMultitrackFeatures(*args, **kwargs, type="fxenc2048AFv3-AF", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
941
- elif metric_name == "pairwise-fxenc++":
942
- return PairwiseFeatures(*args, **kwargs, type="fxenc++", sample_rate=sample_rate, model_args=kwargs.get('fx_encoder_plusplus_args', None))
943
- elif metric_name == "pairwise-logrms":
944
- return PairwiseFeatures(*args, **kwargs, type="logrms", sample_rate=sample_rate, model_args=kwargs.get('logrms_args', None))
945
- elif metric_name == "pairwise-crestfactor":
946
- return PairwiseFeatures(*args, **kwargs, type="crestfactor", sample_rate=sample_rate, model_args=kwargs.get('crestfactor_args', None))
947
- elif metric_name == "pairwise-logspread":
948
- return PairwiseFeatures(*args, **kwargs, type="logspread", sample_rate=sample_rate, model_args=kwargs.get('logspread_args', None))
949
- elif metric_name == "pairwise-stereowidth":
950
- return PairwiseFeatures(*args, **kwargs, type="stereowidth", sample_rate=sample_rate, model_args=kwargs.get('stereowidth_args', None))
951
- elif metric_name == "pairwise-stereoimbalance":
952
- return PairwiseFeatures(*args, **kwargs, type="stereoimbalance", sample_rate=sample_rate, model_args=kwargs.get('stereoimbalance_args', None))
953
- else:
954
- raise ValueError(f"Unknown metric: {metric_name}")
955
-
956
- # Example usage:
957
- #metric_instance = metric_factory("pairwise-spectral")
958
- #```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/feature_extractors/audio_features.py DELETED
@@ -1,76 +0,0 @@
1
- import numpy as np
2
- import librosa
3
-
4
- def compute_spectrogram(file_path, set_limit=False, limit_seconds=10):
5
- """Compute the spectrogram of an audio file."""
6
- y, sr = librosa.load(file_path, sr=None)
7
- if set_limit:
8
- y = y[:int(sr*limit_seconds)]
9
- S = librosa.stft(y)
10
- S_db = librosa.amplitude_to_db(np.abs(S), ref=np.max)
11
- return S_db, sr, y
12
-
13
- def smooth_curve(data, window_size=30):
14
- """Smooth a curve using a moving average."""
15
- return np.convolve(data, np.ones(window_size)/window_size, mode='same')
16
-
17
- def compute_crest_factor(y, sr, frame_length=2048, hop_length=512):
18
- # compute crest factor by windowing
19
- crest_factor_total = []
20
- for i in range(0, len(y), hop_length):
21
- frame = y[i:i+frame_length]
22
- peak = np.max(np.abs(frame))
23
- rms = np.sqrt(np.mean(frame**2))
24
- cur_crest_factor = peak / (rms + 1e-6)
25
- crest_factor_total.append(cur_crest_factor)
26
- crest_factor = np.asarray(crest_factor_total)
27
- return crest_factor
28
-
29
- def compute_audio_features(y, sr, smooth_window_size=1, average_time=True):
30
- # features to compute: RMS energy, crest factor, dynamic spread, spectral centroid, spectral contrast, spectral flatness, spectral balance, and spectral bandwidth
31
- y_mono = y.mean(axis=0)
32
- spectral_centroid = smooth_curve(librosa.feature.spectral_centroid(y=y_mono, sr=sr)[0], window_size=smooth_window_size)
33
- spectral_contrast = smooth_curve(np.mean(librosa.feature.spectral_contrast(y=y_mono, sr=sr), axis=0), window_size=smooth_window_size)
34
- spectral_flatness = smooth_curve(librosa.feature.spectral_flatness(y=y_mono)[0], window_size=smooth_window_size)
35
- rms_energy = smooth_curve(librosa.feature.rms(y=y_mono)[0], window_size=smooth_window_size)
36
- # Crest Factor: Peak-to-RMS ratio
37
- crest_factor = smooth_curve(compute_crest_factor(y_mono, sr, frame_length=2048, hop_length=512), window_size=smooth_window_size)
38
- # Dynamic Spread
39
- dynamic_spread = np.std(rms_energy)
40
- # Spectral Balance (Low, Mid, High Frequency Energy Ratios)
41
- spec = np.abs(librosa.stft(y_mono))**2
42
- freqs = librosa.fft_frequencies(sr=sr)
43
- low_energy = np.sum(spec[freqs < 200])
44
- mid_energy = np.sum(spec[(freqs >= 200) & (freqs < 2000)])
45
- high_energy = np.sum(spec[freqs >= 2000])
46
- total_energy = low_energy + mid_energy + high_energy
47
- spectral_balance = np.array([low_energy / total_energy, mid_energy / total_energy, high_energy / total_energy])
48
- spectral_bandwidth = smooth_curve(librosa.feature.spectral_bandwidth(y=y_mono, sr=sr)[0], window_size=smooth_window_size)
49
- # Stereo Width + Mid-Side Ratio
50
- y_mid = (y[:len(y)//2] + y[len(y)//2:]) / 2
51
- y_side = (y[:len(y)//2] - y[len(y)//2:]) / 2
52
- stereo_width = np.mean(np.abs(y_side)) / (np.mean(np.abs(y_mid)) + 1e-6)
53
- mid_side_ratio = np.mean(np.abs(y_mid)) / (np.mean(np.abs(y_side)) + 1e-6)
54
- # average across time
55
- if average_time:
56
- spectral_centroid = np.mean(spectral_centroid)
57
- spectral_flatness = np.mean(spectral_flatness)
58
- spectral_contrast = np.mean(spectral_contrast)
59
- spectral_flatness = np.mean(spectral_flatness)
60
- rms_energy = np.mean(rms_energy)
61
- crest_factor = np.mean(crest_factor)
62
- spectral_bandwidth = np.mean(spectral_bandwidth)
63
-
64
- # return as a dictionary
65
- return {'spectral_centroid': spectral_centroid,
66
- 'spectral_contrast': spectral_contrast,
67
- 'spectral_flatness': spectral_flatness,
68
- 'rms_energy': rms_energy,
69
- 'crest_factor': crest_factor,
70
- 'dynamic_spread': dynamic_spread,
71
- 'spectral_balance': spectral_balance,
72
- 'spectral_bandwidth': spectral_bandwidth,
73
- 'stereo_width': stereo_width,
74
- 'mid_side_ratio': mid_side_ratio}
75
-
76
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/feature_extractors/load_features.py CHANGED
@@ -1,37 +1,8 @@
1
- import os
2
- import math
3
  import sys
4
  import torchaudio
5
- from importlib import import_module
6
  import torch
7
- import yaml
8
  import torch.nn.functional as F
9
 
10
- def load_fx_encoder_plusplus(model_args, device, *args, **kwargs):
11
- from utils.feature_extractors.fx_encoder_plus_plus import load_model
12
-
13
- assert model_args is not None, "model_args must be provided for fx_encoder type"
14
-
15
- ckpt_path=model_args.ckpt_path
16
-
17
- model=load_model(
18
- model_path=ckpt_path,
19
- device=device,
20
- )
21
-
22
- def effects_encoder_fn(x):
23
- assert x.ndim == 3, f"Input tensor x must be 2D, got {x.ndim}D"
24
- assert x.shape[1] == 2, f"Input tensor x must have 2 channels, got {x.shape[1]} channels"
25
-
26
- emb=model.get_fx_embedding(x)
27
- #l2 normalize the embeddings
28
- emb = torch.nn.functional.normalize(emb, p=2, dim=-1)
29
-
30
- return emb
31
-
32
-
33
- return lambda x: effects_encoder_fn(x)
34
-
35
  def load_fx_encoder_plusplus_2048(model_args, device, *args, **kwargs):
36
  from utils.feature_extractors.fx_encoder_plus_plus import load_model
37
 
@@ -124,137 +95,3 @@ def load_CLAP(model_args, device, *args, **kwargs):
124
 
125
  return lambda x, type: clap_fn(x, type=type)
126
 
127
-
128
-
129
- def load_fx_encoder(model_args, device, *args, **kwargs):
130
- """
131
- Load the FX Encoder model.
132
-
133
- Args:
134
- model_args: Arguments for the FX Encoder model.
135
- device: Device to load the model on (CPU or GPU).
136
-
137
- Returns:
138
- a function that extracts features from audio.
139
- """
140
- assert model_args is not None, "model_args must be provided for fx_encoder type"
141
-
142
- ckpt_path=model_args.ckpt_path
143
-
144
- #from utils.feature_extractors.fx_encoder import load_effects_encoder
145
- from utils.feature_extractors.networks import Effects_Encoder
146
-
147
- def reload_weights(model, ckpt_path, device):
148
- checkpoint = torch.load(ckpt_path, map_location=device)
149
-
150
- from collections import OrderedDict
151
- new_state_dict = OrderedDict()
152
- for k, v in checkpoint["model"].items():
153
- name = k[7:] # remove `module.`
154
- new_state_dict[name] = v
155
- model.load_state_dict(new_state_dict, strict=False)
156
-
157
-
158
- try:
159
- with open(os.path.join('.','utils','feature_extractors', 'networks', 'configs.yaml'), 'r') as f:
160
- configs = yaml.full_load(f)
161
- except:
162
- with open(model_args.config_file, 'r') as f:
163
- configs = yaml.full_load(f)
164
-
165
- cfg_enc = configs['Effects_Encoder']['default']
166
-
167
- effects_encoder = Effects_Encoder(cfg_enc)
168
- reload_weights(effects_encoder, ckpt_path, device)
169
- effects_encoder.to(device)
170
- effects_encoder.eval()
171
-
172
- def effects_encoder_fn(x):
173
- emb=effects_encoder(x)
174
- #l2 normalize the embeddings
175
- emb = torch.nn.functional.normalize(emb, p=2, dim=-1)
176
- return emb
177
-
178
-
179
- return lambda x, *args: effects_encoder_fn(x)
180
-
181
- def load_AFxRep(model_args, device, sample_rate=44100, peak_scaling=True, *args, **kwargs):
182
-
183
- assert model_args is not None, "model_args must be provided for AFxRep type"
184
-
185
- ckpt_path=model_args.ckpt_path
186
-
187
- config_path = os.path.join(os.path.dirname(ckpt_path), "config.yaml")
188
-
189
- with open(config_path) as f:
190
- config = yaml.safe_load(f)
191
-
192
- encoder_configs = config["model"]["init_args"]["encoder"]
193
-
194
- module_path, class_name = encoder_configs["class_path"].rsplit(".", 1)
195
- module_path = module_path.replace("lcap", "utils.st_ito")
196
-
197
- module = import_module(module_path)
198
-
199
- model = getattr(module, class_name)(**encoder_configs["init_args"])
200
-
201
- checkpoint = torch.load(ckpt_path, map_location="cpu")
202
-
203
- # load state dicts
204
- state_dict = {}
205
- for k, v in checkpoint["state_dict"].items():
206
- if k.startswith("encoder"):
207
- state_dict[k.replace("encoder.", "", 1)] = v
208
-
209
- model.load_state_dict(state_dict)
210
-
211
- model.eval()
212
-
213
- model.to(device)
214
-
215
- def wrapper_fn(x, sample_rate):
216
-
217
- x=x.to(device)
218
-
219
- #x=torch.transpose(x,-1,-2)
220
-
221
- if sample_rate != 48000:
222
- x=torchaudio.functional.resample(x, sample_rate, 48000)
223
-
224
- bs= x.shape[0]
225
- #peak normalization. I do it because this is what ST-ITO get_param_embeds does. Not sure if it is good that this representation is invariant to gain
226
- if peak_scaling:
227
- x_max=[]
228
- for batch_idx in range(bs):
229
- #x[batch_idx, ...] /= x[batch_idx, ...].abs().max().clamp(1e-8)
230
- x_max.append( x[batch_idx, ...].abs().max().clamp(1e-8) )
231
-
232
- if x.ndim == 3:
233
- x_max=torch.stack(x_max, dim=0).view(bs, 1, 1)
234
- elif x.ndim == 2:
235
- x_max=torch.stack(x_max, dim=0).view(bs, 1)
236
-
237
- x=x/ x_max
238
-
239
- mid_embeddings, side_embeddings = model(x)
240
-
241
- # check for nan
242
- if torch.isnan(mid_embeddings).any():
243
- print("Warning: NaNs found in mid_embeddings")
244
- mid_embeddings = torch.nan_to_num(mid_embeddings)
245
- elif torch.isnan(side_embeddings).any():
246
- print("Warning: NaNs found in side_embeddings")
247
- side_embeddings = torch.nan_to_num(side_embeddings)
248
-
249
- mid_embeddings = torch.nn.functional.normalize(mid_embeddings, p=2, dim=-1)
250
- side_embeddings = torch.nn.functional.normalize(side_embeddings, p=2, dim=-1)
251
-
252
- embeddings_all= torch.cat([mid_embeddings, side_embeddings], dim=-1)
253
-
254
- return embeddings_all
255
-
256
- feat_extractor = lambda x, *args: wrapper_fn(x, sample_rate=sample_rate)
257
-
258
- return feat_extractor
259
-
260
-
 
 
 
1
  import sys
2
  import torchaudio
 
3
  import torch
 
4
  import torch.nn.functional as F
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  def load_fx_encoder_plusplus_2048(model_args, device, *args, **kwargs):
7
  from utils.feature_extractors.fx_encoder_plus_plus import load_model
8
 
 
95
 
96
  return lambda x, type: clap_fn(x, type=type)
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/feature_extractors/networks/pytorch_utils.py DELETED
@@ -1,256 +0,0 @@
1
- """
2
- From: https://github.com/qiuqiangkong/audioset_tagging_cnn/blob/master/pytorch/pytorch_utils.py
3
-
4
- Copyright (c) 2018-2020 Qiuqiang Kong
5
- """
6
- import numpy as np
7
- import time
8
- import torch
9
- import torch.nn as nn
10
-
11
-
12
- def move_data_to_device(x, device):
13
- if 'float' in str(x.dtype):
14
- x = torch.Tensor(x)
15
- elif 'int' in str(x.dtype):
16
- x = torch.LongTensor(x)
17
- else:
18
- return x
19
-
20
- return x.to(device)
21
-
22
-
23
- def do_mixup(x, mixup_lambda):
24
- """Mixup x of even indexes (0, 2, 4, ...) with x of odd indexes
25
- (1, 3, 5, ...).
26
-
27
- Args:
28
- x: (batch_size * 2, ...)
29
- mixup_lambda: (batch_size * 2,)
30
-
31
- Returns:
32
- out: (batch_size, ...)
33
- """
34
- out = (x[0 :: 2].transpose(0, -1) * mixup_lambda[0 :: 2] + \
35
- x[1 :: 2].transpose(0, -1) * mixup_lambda[1 :: 2]).transpose(0, -1)
36
- return out
37
-
38
-
39
- def append_to_dict(dict, key, value):
40
- if key in dict.keys():
41
- dict[key].append(value)
42
- else:
43
- dict[key] = [value]
44
-
45
-
46
- def forward(model, generator, return_input=False,
47
- return_target=False):
48
- """Forward data to a model.
49
-
50
- Args:
51
- model: object
52
- generator: object
53
- return_input: bool
54
- return_target: bool
55
-
56
- Returns:
57
- audio_name: (audios_num,)
58
- clipwise_output: (audios_num, classes_num)
59
- (ifexist) segmentwise_output: (audios_num, segments_num, classes_num)
60
- (ifexist) framewise_output: (audios_num, frames_num, classes_num)
61
- (optional) return_input: (audios_num, segment_samples)
62
- (optional) return_target: (audios_num, classes_num)
63
- """
64
- output_dict = {}
65
- device = next(model.parameters()).device
66
- time1 = time.time()
67
-
68
- # Forward data to a model in mini-batches
69
- for n, batch_data_dict in enumerate(generator):
70
- print(n)
71
- batch_waveform = move_data_to_device(batch_data_dict['waveform'], device)
72
-
73
- with torch.no_grad():
74
- model.eval()
75
- batch_output = model(batch_waveform)
76
-
77
- append_to_dict(output_dict, 'audio_name', batch_data_dict['audio_name'])
78
-
79
- append_to_dict(output_dict, 'clipwise_output',
80
- batch_output['clipwise_output'].data.cpu().numpy())
81
-
82
- if 'segmentwise_output' in batch_output.keys():
83
- append_to_dict(output_dict, 'segmentwise_output',
84
- batch_output['segmentwise_output'].data.cpu().numpy())
85
-
86
- if 'framewise_output' in batch_output.keys():
87
- append_to_dict(output_dict, 'framewise_output',
88
- batch_output['framewise_output'].data.cpu().numpy())
89
-
90
- if return_input:
91
- append_to_dict(output_dict, 'waveform', batch_data_dict['waveform'])
92
-
93
- if return_target:
94
- if 'target' in batch_data_dict.keys():
95
- append_to_dict(output_dict, 'target', batch_data_dict['target'])
96
-
97
- if n % 10 == 0:
98
- print(' --- Inference time: {:.3f} s / 10 iterations ---'.format(
99
- time.time() - time1))
100
- time1 = time.time()
101
-
102
- for key in output_dict.keys():
103
- output_dict[key] = np.concatenate(output_dict[key], axis=0)
104
-
105
- return output_dict
106
-
107
-
108
- def interpolate(x, ratio):
109
- """Interpolate data in time domain. This is used to compensate the
110
- resolution reduction in downsampling of a CNN.
111
-
112
- Args:
113
- x: (batch_size, time_steps, classes_num)
114
- ratio: int, ratio to interpolate
115
-
116
- Returns:
117
- upsampled: (batch_size, time_steps * ratio, classes_num)
118
- """
119
- (batch_size, time_steps, classes_num) = x.shape
120
- upsampled = x[:, :, None, :].repeat(1, 1, ratio, 1)
121
- upsampled = upsampled.reshape(batch_size, time_steps * ratio, classes_num)
122
- return upsampled
123
-
124
-
125
- def pad_framewise_output(framewise_output, frames_num):
126
- """Pad framewise_output to the same length as input frames. The pad value
127
- is the same as the value of the last frame.
128
-
129
- Args:
130
- framewise_output: (batch_size, frames_num, classes_num)
131
- frames_num: int, number of frames to pad
132
-
133
- Outputs:
134
- output: (batch_size, frames_num, classes_num)
135
- """
136
- pad = framewise_output[:, -1 :, :].repeat(1, frames_num - framewise_output.shape[1], 1)
137
- """tensor for padding"""
138
-
139
- output = torch.cat((framewise_output, pad), dim=1)
140
- """(batch_size, frames_num, classes_num)"""
141
-
142
- return output
143
-
144
-
145
- def count_parameters(model):
146
- return sum(p.numel() for p in model.parameters() if p.requires_grad)
147
-
148
-
149
- def count_flops(model, audio_length):
150
- """Count flops. Code modified from others' implementation.
151
- """
152
- multiply_adds = True
153
- list_conv2d=[]
154
- def conv2d_hook(self, input, output):
155
- batch_size, input_channels, input_height, input_width = input[0].size()
156
- output_channels, output_height, output_width = output[0].size()
157
-
158
- kernel_ops = self.kernel_size[0] * self.kernel_size[1] * (self.in_channels / self.groups) * (2 if multiply_adds else 1)
159
- bias_ops = 1 if self.bias is not None else 0
160
-
161
- params = output_channels * (kernel_ops + bias_ops)
162
- flops = batch_size * params * output_height * output_width
163
-
164
- list_conv2d.append(flops)
165
-
166
- list_conv1d=[]
167
- def conv1d_hook(self, input, output):
168
- batch_size, input_channels, input_length = input[0].size()
169
- output_channels, output_length = output[0].size()
170
-
171
- kernel_ops = self.kernel_size[0] * (self.in_channels / self.groups) * (2 if multiply_adds else 1)
172
- bias_ops = 1 if self.bias is not None else 0
173
-
174
- params = output_channels * (kernel_ops + bias_ops)
175
- flops = batch_size * params * output_length
176
-
177
- list_conv1d.append(flops)
178
-
179
- list_linear=[]
180
- def linear_hook(self, input, output):
181
- batch_size = input[0].size(0) if input[0].dim() == 2 else 1
182
-
183
- weight_ops = self.weight.nelement() * (2 if multiply_adds else 1)
184
- bias_ops = self.bias.nelement()
185
-
186
- flops = batch_size * (weight_ops + bias_ops)
187
- list_linear.append(flops)
188
-
189
- list_bn=[]
190
- def bn_hook(self, input, output):
191
- list_bn.append(input[0].nelement() * 2)
192
-
193
- list_relu=[]
194
- def relu_hook(self, input, output):
195
- list_relu.append(input[0].nelement() * 2)
196
-
197
- list_pooling2d=[]
198
- def pooling2d_hook(self, input, output):
199
- batch_size, input_channels, input_height, input_width = input[0].size()
200
- output_channels, output_height, output_width = output[0].size()
201
-
202
- kernel_ops = self.kernel_size * self.kernel_size
203
- bias_ops = 0
204
- params = output_channels * (kernel_ops + bias_ops)
205
- flops = batch_size * params * output_height * output_width
206
-
207
- list_pooling2d.append(flops)
208
-
209
- list_pooling1d=[]
210
- def pooling1d_hook(self, input, output):
211
- batch_size, input_channels, input_length = input[0].size()
212
- output_channels, output_length = output[0].size()
213
-
214
- kernel_ops = self.kernel_size[0]
215
- bias_ops = 0
216
-
217
- params = output_channels * (kernel_ops + bias_ops)
218
- flops = batch_size * params * output_length
219
-
220
- list_pooling2d.append(flops)
221
-
222
- def foo(net):
223
- childrens = list(net.children())
224
- if not childrens:
225
- if isinstance(net, nn.Conv2d):
226
- net.register_forward_hook(conv2d_hook)
227
- elif isinstance(net, nn.Conv1d):
228
- net.register_forward_hook(conv1d_hook)
229
- elif isinstance(net, nn.Linear):
230
- net.register_forward_hook(linear_hook)
231
- elif isinstance(net, nn.BatchNorm2d) or isinstance(net, nn.BatchNorm1d):
232
- net.register_forward_hook(bn_hook)
233
- elif isinstance(net, nn.ReLU):
234
- net.register_forward_hook(relu_hook)
235
- elif isinstance(net, nn.AvgPool2d) or isinstance(net, nn.MaxPool2d):
236
- net.register_forward_hook(pooling2d_hook)
237
- elif isinstance(net, nn.AvgPool1d) or isinstance(net, nn.MaxPool1d):
238
- net.register_forward_hook(pooling1d_hook)
239
- else:
240
- print('Warning: flop of module {} is not counted!'.format(net))
241
- return
242
- for c in childrens:
243
- foo(c)
244
-
245
- # Register hook
246
- foo(model)
247
-
248
- device = device = next(model.parameters()).device
249
- input = torch.rand(1, audio_length).to(device)
250
-
251
- out = model(input)
252
-
253
- total_flops = sum(list_conv2d) + sum(list_conv1d) + sum(list_linear) + \
254
- sum(list_bn) + sum(list_relu) + sum(list_pooling2d) + sum(list_pooling1d)
255
-
256
- return total_flops
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/fx_normalization/fxnorm.py DELETED
@@ -1,154 +0,0 @@
1
- import torch
2
- import numpy as np
3
-
4
- from utils.training_utils import Gauss_smooth_vectorized, prepare_smooth_filter
5
-
6
- def T602logmag(t60, sample_rate=44100, hop_length=512):
7
- return 6.908 / (t60 * (sample_rate / hop_length)) # Convert T60 to delta log magnitude
8
-
9
- from utils.data_utils import apply_RMS_normalization
10
-
11
-
12
- class FxNormAug:
13
-
14
- def __init__(self,
15
- sample_rate=44100, # Sample rate of the audio
16
- device="cuda" if torch.cuda.is_available() else "cpu",
17
- mode="train", # Mode can be "train" or "eval"
18
- seed=42,
19
- features_path="features_tency1_4instr_v4.npy", # Path to the features file
20
- ):
21
-
22
- torch.random.manual_seed(seed)
23
-
24
- #the path is in the same directory as this file
25
- self.features_path = features_path
26
-
27
- self.sample_rate = sample_rate
28
- self.device = device
29
- self.train_setup()
30
-
31
- self.EQ_normalize_setup() # Initialize EQ normalization function
32
-
33
-
34
- def train_setup(self):
35
- #hardcoded setuo used for training
36
-
37
- self.RMS_norm=-25 # Target RMS level in dB, used for normalization
38
-
39
- def EQ_normalize_setup(self ):
40
-
41
- features_mean = np.load(self.features_path, allow_pickle='TRUE')[()]
42
-
43
- target_cuves_original= {
44
- "vocals": torch.tensor(features_mean["eq"]["vocals"]).to(torch.float32).to(self.device),
45
- "drums": torch.tensor(features_mean["eq"]["drums"]).to(torch.float32).to(self.device),
46
- "bass": torch.tensor(features_mean["eq"]["bass"]).to(torch.float32).to(self.device),
47
- "other": torch.tensor(features_mean["eq"]["other"]).to(torch.float32).to(self.device),
48
- }
49
-
50
-
51
- nfft=4096 # FFT size hardcoded
52
- nfft_orig = 65536 # FFT size for the smoothing filter
53
-
54
- win_length=2048 # Window length hardcoded
55
- hop_length=1024 # Hop length hardcoded
56
-
57
- window = torch.sqrt(torch.hann_window(win_length, device=self.device))
58
- window_energy = window.pow(2).sum().sqrt() # Energy of the window
59
-
60
- freqs = torch.fft.rfftfreq(nfft, d=1.0).to(self.device)
61
- freqs_Hz=torch.fft.rfftfreq(nfft, d=1.0).to(self.device) * self.sample_rate
62
-
63
- smooth_filter = prepare_smooth_filter(freqs_Hz, Noct=3).to(self.device) # Prepare the smoothing filter
64
-
65
- freqs_Hz_orig=torch.fft.rfftfreq(nfft_orig, d=1.0).to(self.device) * self.sample_rate
66
- smooth_filter_orig = prepare_smooth_filter(freqs_Hz_orig, Noct=3).to(self.device) # Prepare the smoothing filter
67
-
68
- def downsample_curve(x):
69
- return torch.nn.functional.interpolate(
70
- x.unsqueeze(0).unsqueeze(0),
71
- size=(nfft // 2 + 1,),
72
- mode='linear',
73
- align_corners=False
74
- ).squeeze(0).squeeze(0)
75
-
76
- target_curves = {
77
- "vocals": downsample_curve(Gauss_smooth_vectorized(target_cuves_original["vocals"], freqs_Hz_orig, Noct=3, smooth_filter=smooth_filter_orig)),
78
- "drums": downsample_curve(Gauss_smooth_vectorized(target_cuves_original["drums"], freqs_Hz_orig, Noct=3, smooth_filter=smooth_filter_orig)),
79
- "bass": downsample_curve(Gauss_smooth_vectorized(target_cuves_original["bass"], freqs_Hz_orig, Noct=3, smooth_filter=smooth_filter_orig)),
80
- "other": downsample_curve(Gauss_smooth_vectorized(target_cuves_original["other"], freqs_Hz_orig, Noct=3, smooth_filter=smooth_filter_orig)),
81
- }
82
-
83
-
84
- def EQ_normalize_fn(x):
85
-
86
- shape= x.shape
87
-
88
- target_curves_tensor=torch.zeros((shape[0], nfft // 2 + 1), device=self.device, dtype=torch.float32)
89
-
90
- for i in range(shape[0]):
91
- track_class = "other"
92
-
93
- assert track_class in target_curves, f"track_class {track_class} not found in target_curves"
94
- target_curves_tensor[i] = target_curves[track_class]
95
-
96
- x=x.view(-1, shape[-1])
97
- #ensure x.shape[-1] is divisible by hop_length
98
- if x.shape[-1] % hop_length != 0:
99
- # Pad the input to make it divisible by hop_length
100
- pad_length = hop_length - (x.shape[-1] % hop_length)
101
- x = torch.nn.functional.pad(x, (0, pad_length), mode='constant', value=0)
102
- X=torch.stft(x, n_fft=nfft, hop_length=hop_length, win_length=win_length, window=window, return_complex=True)/ window_energy
103
- X_pow=X.abs().pow(2)
104
- X_mean= torch.sqrt(X_pow.mean(dim=-1, keepdim=False)) # Mean power spectrum
105
-
106
-
107
-
108
- ratio= target_curves_tensor / (X_mean + 1e-6)
109
-
110
- ratio = torch.clamp(ratio, max=10.0**(20.0/20.0))
111
-
112
-
113
- ratio_smooth = Gauss_smooth_vectorized(ratio, freqs_Hz, Noct=3, smooth_filter=smooth_filter)
114
-
115
-
116
-
117
- X= X * ratio_smooth.unsqueeze(-1)
118
-
119
- X_unnormalized=X* window_energy
120
-
121
- x_reconstructed = torch.istft(X_unnormalized,
122
- n_fft=nfft,
123
- hop_length=hop_length,
124
- win_length=win_length,
125
- window=window,
126
- return_complex=False) # Set to True if you want complex outpu
127
-
128
- #remove the padding if it was added
129
- if x_reconstructed.shape[-1] > shape[-1]:
130
- x_reconstructed = x_reconstructed[..., :shape[-1]]
131
- x_reconstructed = x_reconstructed.view(shape)
132
- return x_reconstructed
133
-
134
- self.EQ_normalize = EQ_normalize_fn
135
-
136
-
137
- def __call__(self, x, use_gate=False, RMS=None):
138
-
139
- B, C, T = x.shape
140
- if C > 1:
141
- x = x.mean(dim=1, keepdim=True)
142
-
143
-
144
- x= apply_RMS_normalization(x, self.RMS_norm , use_gate=use_gate) # Apply RMS normalization to the input
145
-
146
- x=self.EQ_normalize(x)
147
-
148
-
149
-
150
- x= apply_RMS_normalization(x, self.RMS_norm, use_gate=use_gate)
151
- assert not torch.isnan(x).any(), "NaN detected in x after EQ normalization"
152
-
153
-
154
- return x
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/clap_module/__init__.py CHANGED
@@ -1,8 +1,6 @@
1
  from .factory import list_models, create_model, create_model_and_transforms, add_model_config
2
- from .loss import ClipLoss, gather_features, LPLoss, lp_gather_features, LPMetrics
3
  from .model import CLAP, CLAPTextCfg, CLAPVisionCfg, CLAPAudioCfp, convert_weights_to_fp16, trace_model
4
  from .openai import load_openai_model, list_openai_models
5
  from .pretrained import list_pretrained, list_pretrained_tag_models, list_pretrained_model_tags,\
6
  get_pretrained_url, download_pretrained
7
- from .tokenizer import SimpleTokenizer, tokenize
8
  from .transform import image_transform
 
1
  from .factory import list_models, create_model, create_model_and_transforms, add_model_config
 
2
  from .model import CLAP, CLAPTextCfg, CLAPVisionCfg, CLAPAudioCfp, convert_weights_to_fp16, trace_model
3
  from .openai import load_openai_model, list_openai_models
4
  from .pretrained import list_pretrained, list_pretrained_tag_models, list_pretrained_model_tags,\
5
  get_pretrained_url, download_pretrained
 
6
  from .transform import image_transform
utils/laion_clap/clap_module/bert.py DELETED
@@ -1,32 +0,0 @@
1
- from transformers import BertTokenizer, BertModel
2
- tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
3
- model = BertModel.from_pretrained("bert-base-uncased")
4
- text = "Replace me by any text you'd like."
5
-
6
- def bert_embeddings(text):
7
- # text = "Replace me by any text you'd like."
8
- encoded_input = tokenizer(text, return_tensors='pt')
9
- output = model(**encoded_input)
10
- return output
11
-
12
- from transformers import RobertaTokenizer, RobertaModel
13
-
14
- tokenizer = RobertaTokenizer.from_pretrained('roberta-base')
15
- model = RobertaModel.from_pretrained('roberta-base')
16
- text = "Replace me by any text you'd like."
17
- def Roberta_embeddings(text):
18
- # text = "Replace me by any text you'd like."
19
- encoded_input = tokenizer(text, return_tensors='pt')
20
- output = model(**encoded_input)
21
- return output
22
-
23
- from transformers import BartTokenizer, BartModel
24
-
25
- tokenizer = BartTokenizer.from_pretrained('facebook/bart-base')
26
- model = BartModel.from_pretrained('facebook/bart-base')
27
- text = "Replace me by any text you'd like."
28
- def bart_embeddings(text):
29
- # text = "Replace me by any text you'd like."
30
- encoded_input = tokenizer(text, return_tensors='pt')
31
- output = model(**encoded_input)
32
- return output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/clap_module/bpe_simple_vocab_16e6.txt.gz DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:924691ac288e54409236115652ad4aa250f48203de50a9e4722a6ecd48d6804a
3
- size 1356917
 
 
 
 
utils/laion_clap/clap_module/linear_probe.py DELETED
@@ -1,63 +0,0 @@
1
- import numpy as np
2
- import torch.nn.functional as F
3
- from torch import nn
4
- from .model import MLPLayers
5
-
6
-
7
- class LinearProbe(nn.Module):
8
- def __init__(self, model, mlp, freeze, in_ch, out_ch, act=None):
9
- """
10
- Args:
11
- model: nn.Module
12
- mlp: bool, if True, then use the MLP layer as the linear probe module
13
- freeze: bool, if Ture, then freeze all the CLAP model's layers when training the linear probe
14
- in_ch: int, the output channel from CLAP model
15
- out_ch: int, the output channel from linear probe (class_num)
16
- act: torch.nn.functional, the activation function before the loss function
17
- """
18
- super().__init__()
19
- in_ch = 512
20
- self.clap_model = model
21
- self.clap_model.text_branch = None # to save memory
22
- self.freeze = freeze
23
- if mlp:
24
- self.lp_layer = MLPLayers(units=[in_ch, in_ch * 2, out_ch])
25
- else:
26
- self.lp_layer = nn.Linear(in_ch, out_ch)
27
-
28
- if self.freeze:
29
- for param in self.clap_model.parameters():
30
- param.requires_grad = False
31
-
32
- if act == 'None':
33
- self.act = None
34
- elif act == 'relu':
35
- self.act = nn.ReLU()
36
- elif act == 'elu':
37
- self.act = nn.ELU()
38
- elif act == 'prelu':
39
- self.act = nn.PReLU(num_parameters=in_ch)
40
- elif act == 'softmax':
41
- self.act = nn.Softmax(dim=-1)
42
- elif act == 'sigmoid':
43
- self.act = nn.Sigmoid()
44
-
45
- def forward(self, x, mix_lambda=None, device=None):
46
- """
47
- Args:
48
- x: waveform, torch.tensor [batch, t_samples] / batch of mel_spec and longer list
49
- mix_lambda: torch.tensor [batch], the mixup lambda
50
- Returns:
51
- class_prob: torch.tensor [batch, class_num]
52
-
53
- """
54
- # batchnorm cancel grandient
55
- if self.freeze:
56
- self.clap_model.eval()
57
-
58
- x = self.clap_model.audio_projection(
59
- self.clap_model.audio_branch(x, mixup_lambda=mix_lambda, device=device)["embedding"])
60
- out = self.lp_layer(x)
61
- if self.act is not None:
62
- out = self.act(out)
63
- return out
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/clap_module/loss.py DELETED
@@ -1,307 +0,0 @@
1
- from multiprocessing.sharedctypes import Value
2
- import torch
3
- import torch.distributed.nn
4
- from torch import distributed as dist, nn as nn
5
- from torch.nn import functional as F
6
- import numpy as np
7
- from sklearn.metrics import average_precision_score, roc_auc_score, accuracy_score
8
-
9
- try:
10
- import horovod.torch as hvd
11
- except ImportError:
12
- hvd = None
13
-
14
-
15
- def gather_features(
16
- audio_features,
17
- text_features,
18
- audio_features_mlp=None,
19
- text_features_mlp=None,
20
- local_loss=False,
21
- gather_with_grad=False,
22
- rank=0,
23
- world_size=1,
24
- use_horovod=False,
25
- mlp_loss=False
26
- ):
27
- if use_horovod:
28
- assert hvd is not None, 'Please install horovod'
29
- if gather_with_grad:
30
- all_audio_features = hvd.allgather(audio_features)
31
- all_text_features = hvd.allgather(text_features)
32
- if mlp_loss:
33
- all_audio_features_mlp = hvd.allgather(audio_features_mlp)
34
- all_text_features_mlp = hvd.allgather(text_features_mlp)
35
- else:
36
- with torch.no_grad():
37
- all_audio_features = hvd.allgather(audio_features)
38
- all_text_features = hvd.allgather(text_features)
39
- if mlp_loss:
40
- all_audio_features_mlp = hvd.allgather(audio_features_mlp)
41
- all_text_features_mlp = hvd.allgather(text_features_mlp)
42
- if not local_loss:
43
- # ensure grads for local rank when all_* features don't have a gradient
44
- gathered_audio_features = list(all_audio_features.chunk(world_size, dim=0))
45
- gathered_text_features = list(all_text_features.chunk(world_size, dim=0))
46
- gathered_audio_features[rank] = audio_features
47
- gathered_text_features[rank] = text_features
48
- all_audio_features = torch.cat(gathered_audio_features, dim=0)
49
- all_text_features = torch.cat(gathered_text_features, dim=0)
50
- if mlp_loss:
51
- gathered_audio_features_mlp = list(all_audio_features_mlp.chunk(world_size, dim=0))
52
- gathered_text_features_mlp = list(all_text_features_mlp.chunk(world_size, dim=0))
53
- gathered_audio_features_mlp[rank] = audio_features_mlp
54
- gathered_text_features_mlp[rank] = text_features_mlp
55
- all_audio_features_mlp = torch.cat(gathered_audio_features_mlp, dim=0)
56
- all_text_features_mlp = torch.cat(gathered_text_features_mlp, dim=0)
57
- else:
58
- # We gather tensors from all gpus
59
- if gather_with_grad:
60
- all_audio_features = torch.cat(torch.distributed.nn.all_gather(audio_features), dim=0)
61
- all_text_features = torch.cat(torch.distributed.nn.all_gather(text_features), dim=0)
62
- if mlp_loss:
63
- all_audio_features_mlp = torch.cat(torch.distributed.nn.all_gather(audio_features_mlp), dim=0)
64
- all_text_features_mlp = torch.cat(torch.distributed.nn.all_gather(text_features_mlp), dim=0)
65
- else:
66
- gathered_audio_features = [torch.zeros_like(audio_features) for _ in range(world_size)]
67
- gathered_text_features = [torch.zeros_like(text_features) for _ in range(world_size)]
68
- dist.all_gather(gathered_audio_features, audio_features)
69
- dist.all_gather(gathered_text_features, text_features)
70
- if mlp_loss:
71
- gathered_audio_features_mlp = [torch.zeros_like(audio_features_mlp) for _ in range(world_size)]
72
- gathered_text_features_mlp = [torch.zeros_like(text_features_mlp) for _ in range(world_size)]
73
- dist.all_gather(gathered_audio_features_mlp, audio_features_mlp)
74
- dist.all_gather(gathered_text_features_mlp, text_features_mlp)
75
- if not local_loss:
76
- # ensure grads for local rank when all_* features don't have a gradient
77
- gathered_audio_features[rank] = audio_features
78
- gathered_text_features[rank] = text_features
79
- if mlp_loss:
80
- gathered_audio_features_mlp[rank] = audio_features_mlp
81
- gathered_text_features_mlp[rank] = text_features_mlp
82
-
83
- all_audio_features = torch.cat(gathered_audio_features, dim=0)
84
- all_text_features = torch.cat(gathered_text_features, dim=0)
85
- if mlp_loss:
86
- all_audio_features_mlp = torch.cat(gathered_audio_features_mlp, dim=0)
87
- all_text_features_mlp = torch.cat(gathered_text_features_mlp, dim=0)
88
- if mlp_loss:
89
- return all_audio_features, all_text_features, all_audio_features_mlp, all_text_features_mlp
90
- else:
91
- return all_audio_features, all_text_features
92
-
93
- class ClipLoss(nn.Module):
94
-
95
- def __init__(
96
- self,
97
- local_loss=False,
98
- gather_with_grad=False,
99
- cache_labels=False,
100
- rank=0,
101
- world_size=1,
102
- use_horovod=False,
103
- mlp_loss=False,
104
- weight_loss_kappa=0,
105
- ):
106
- super().__init__()
107
- self.local_loss = local_loss
108
- self.gather_with_grad = gather_with_grad
109
- self.cache_labels = cache_labels
110
- self.rank = rank
111
- self.world_size = world_size
112
- self.use_horovod = use_horovod
113
- self.mlp_loss = mlp_loss
114
- self.weighted_loss = bool(weight_loss_kappa!=0)
115
- self.weight_loss_kappa = weight_loss_kappa
116
- # cache state
117
- self.prev_num_logits = 0
118
- self.labels = {}
119
-
120
- def forward(self, audio_features, text_features, logit_scale_a, logit_scale_t=None, audio_features_mlp=None, text_features_mlp=None):
121
- device = audio_features.device
122
- if self.mlp_loss:
123
- if self.world_size > 1:
124
- all_audio_features, all_text_features, all_audio_features_mlp, all_text_features_mlp = gather_features(
125
- audio_features=audio_features,text_features=text_features,
126
- audio_features_mlp=audio_features_mlp,text_features_mlp=text_features_mlp,
127
- local_loss=self.local_loss,gather_with_grad=self.gather_with_grad,
128
- rank=self.rank,world_size=self.world_size,use_horovod=self.use_horovod,
129
- mlp_loss=self.mlp_loss
130
- )
131
- if self.local_loss:
132
- a_logits_per_audio = logit_scale_a * audio_features @ all_text_features_mlp.T
133
- a_logits_per_text = logit_scale_a * text_features_mlp @ all_audio_features.T
134
- t_logits_per_audio = logit_scale_t * audio_features_mlp @ all_text_features.T
135
- t_logits_per_text = logit_scale_t * text_features @ all_audio_features_mlp.T
136
- else:
137
- a_logits_per_audio = logit_scale_a * all_audio_features @ all_text_features_mlp.T
138
- a_logits_per_text = a_logits_per_audio.T
139
- t_logits_per_audio = logit_scale_t * all_audio_features_mlp @ all_text_features.T
140
- t_logits_per_text = t_logits_per_audio.T
141
- else:
142
- a_logits_per_audio = logit_scale_a * audio_features @ text_features_mlp.T
143
- a_logits_per_text = logit_scale_a * text_features_mlp @ audio_features.T
144
- t_logits_per_audio = logit_scale_t * audio_features_mlp @ text_features.T
145
- t_logits_per_text = logit_scale_t * text_features @ audio_features_mlp.T
146
-
147
- # calculated ground-truth and cache if enabled
148
- num_logits = a_logits_per_audio.shape[0]
149
- if self.prev_num_logits != num_logits or device not in self.labels:
150
- labels = torch.arange(num_logits, device=device, dtype=torch.long)
151
- if self.world_size > 1 and self.local_loss:
152
- labels = labels + num_logits * self.rank
153
- if self.cache_labels:
154
- self.labels[device] = labels
155
- self.prev_num_logits = num_logits
156
- else:
157
- labels = self.labels[device]
158
-
159
- if not self.weighted_loss:
160
- total_loss = (
161
- F.cross_entropy(a_logits_per_audio, labels) +
162
- F.cross_entropy(a_logits_per_text, labels) +
163
- F.cross_entropy(t_logits_per_audio, labels) +
164
- F.cross_entropy(t_logits_per_text, labels)
165
- ) / 4
166
- else:
167
- audio_weight = (audio_features@audio_features.T).detach()
168
- audio_weight = (torch.exp(torch.sum(audio_weight, axis=1)/(self.weight_loss_kappa*len(audio_weight)))).detach()
169
- text_weight = (text_features@text_features.T).detach()
170
- text_weight = (torch.exp(torch.sum(text_weight, axis=1)/(self.weight_loss_kappa*len(text_features)))).detach()
171
- total_loss = (
172
- F.cross_entropy(a_logits_per_audio, labels, weight=audio_weight) +
173
- F.cross_entropy(a_logits_per_text, labels, weight=audio_weight) +
174
- F.cross_entropy(t_logits_per_audio, labels, weight=text_weight) +
175
- F.cross_entropy(t_logits_per_text, labels, weight=text_weight)
176
- ) / 4
177
- else:
178
- if self.world_size > 1:
179
- all_audio_features, all_text_features = gather_features(
180
- audio_features=audio_features,text_features=text_features,
181
- local_loss=self.local_loss,gather_with_grad=self.gather_with_grad,
182
- rank=self.rank,world_size=self.world_size,use_horovod=self.use_horovod,
183
- mlp_loss=self.mlp_loss
184
- )
185
-
186
- if self.local_loss:
187
- logits_per_audio = logit_scale_a * audio_features @ all_text_features.T
188
- logits_per_text = logit_scale_a * text_features @ all_audio_features.T
189
- else:
190
- logits_per_audio = logit_scale_a * all_audio_features @ all_text_features.T
191
- logits_per_text = logits_per_audio.T
192
- else:
193
- logits_per_audio = logit_scale_a * audio_features @ text_features.T
194
- logits_per_text = logit_scale_a * text_features @ audio_features.T
195
-
196
- # calculated ground-truth and cache if enabled
197
- num_logits = logits_per_audio.shape[0]
198
- if self.prev_num_logits != num_logits or device not in self.labels:
199
- labels = torch.arange(num_logits, device=device, dtype=torch.long)
200
- if self.world_size > 1 and self.local_loss:
201
- labels = labels + num_logits * self.rank
202
- if self.cache_labels:
203
- self.labels[device] = labels
204
- self.prev_num_logits = num_logits
205
- else:
206
- labels = self.labels[device]
207
- if not self.weighted_loss:
208
- total_loss = (
209
- F.cross_entropy(logits_per_audio, labels) +
210
- F.cross_entropy(logits_per_text, labels)
211
- ) / 2
212
- else:
213
- audio_weight = (all_audio_features@all_audio_features.T).detach()
214
- audio_weight = (torch.exp(torch.sum(audio_weight, axis=1)/(self.weight_loss_kappa*len(all_audio_features)))).detach()
215
- text_weight = (all_text_features@all_text_features.T).detach()
216
- text_weight = (torch.exp(torch.sum(text_weight, axis=1)/(self.weight_loss_kappa*len(all_text_features)))).detach()
217
- total_loss = (
218
- F.cross_entropy(logits_per_audio, labels, weight=text_weight) +
219
- F.cross_entropy(logits_per_text, labels, weight=audio_weight)
220
- ) / 2
221
- return total_loss
222
-
223
- def lp_gather_features(
224
- pred,
225
- target,
226
- world_size=1,
227
- use_horovod=False
228
- ):
229
- if use_horovod:
230
- assert hvd is not None, 'Please install horovod'
231
- with torch.no_grad():
232
- all_preds = hvd.allgather(pred)
233
- all_targets = hvd.allgath(target)
234
- else:
235
- gathered_preds = [torch.zeros_like(pred) for _ in range(world_size)]
236
- gathered_targets = [torch.zeros_like(target) for _ in range(world_size)]
237
-
238
- dist.all_gather(gathered_preds, pred)
239
- dist.all_gather(gathered_targets, target)
240
- all_preds = torch.cat(gathered_preds, dim=0)
241
- all_targets = torch.cat(gathered_targets, dim=0)
242
-
243
- return all_preds, all_targets
244
-
245
-
246
- def get_map(pred, target):
247
- pred = torch.sigmoid(pred).numpy()
248
- target = target.numpy()
249
- return np.mean(average_precision_score(target, pred, average=None))
250
-
251
- def get_acc(pred, target):
252
- pred = torch.argmax(pred,1).numpy()
253
- target = torch.argmax(target,1).numpy()
254
- return accuracy_score(target, pred)
255
-
256
- def get_mauc(pred, target):
257
- pred = torch.sigmoid(pred).numpy()
258
- target = target.numpy()
259
- return np.mean(roc_auc_score(target, pred, average=None))
260
-
261
-
262
- class LPMetrics(object):
263
- def __init__(self, metric_names = ['map','acc','mauc']):
264
- self.metrics = []
265
- for name in metric_names:
266
- self.metrics.append(self.get_metric(name))
267
- self.metric_names = metric_names
268
-
269
- def get_metric(self,name):
270
- if name == 'map':
271
- return get_map
272
- elif name == 'acc':
273
- return get_acc
274
- elif name == 'mauc':
275
- return get_mauc
276
- else:
277
- raise ValueError(f'the metric should be at least one of [map, acc, mauc]')
278
-
279
- def evaluate_mertics(self, pred, target):
280
- metric_dict = {}
281
- for i in range(len(self.metric_names)):
282
- metric_dict[self.metric_names[i]] = self.metrics[i](pred, target)
283
- return metric_dict
284
-
285
-
286
- def calc_celoss(pred, target):
287
- target = torch.argmax(target, 1).long()
288
- return nn.CrossEntropyLoss()(pred, target)
289
-
290
-
291
- class LPLoss(nn.Module):
292
-
293
- def __init__(self, loss_name):
294
- super().__init__()
295
- if loss_name == 'bce':
296
- self.loss_func = nn.BCEWithLogitsLoss()
297
- elif loss_name == 'ce':
298
- self.loss_func = calc_celoss
299
- elif loss_name == 'mse':
300
- self.loss_func = nn.MSELoss()
301
- else:
302
- raise ValueError(f'the loss func should be at least one of [bce, ce, mse]')
303
-
304
- def forward(self, pred, target):
305
- loss = self.loss_func(pred, target)
306
- return loss
307
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/clap_module/tokenizer.py DELETED
@@ -1,180 +0,0 @@
1
- """ CLIP tokenizer
2
-
3
- Copied from https://github.com/openai/CLIP. Originally MIT License, Copyright (c) 2021 OpenAI.
4
- """
5
- import gzip
6
- import html
7
- import os
8
- from functools import lru_cache
9
- from typing import Union, List
10
-
11
- import ftfy
12
- import regex as re
13
- import torch
14
-
15
-
16
- @lru_cache()
17
- def default_bpe():
18
- return os.path.join(os.path.dirname(os.path.abspath(__file__)), "bpe_simple_vocab_16e6.txt.gz")
19
-
20
-
21
- @lru_cache()
22
- def bytes_to_unicode():
23
- """
24
- Returns list of utf-8 byte and a corresponding list of unicode strings.
25
- The reversible bpe codes work on unicode strings.
26
- This means you need a large # of unicode characters in your vocab if you want to avoid UNKs.
27
- When you're at something like a 10B token dataset you end up needing around 5K for decent coverage.
28
- This is a signficant percentage of your normal, say, 32K bpe vocab.
29
- To avoid that, we want lookup tables between utf-8 bytes and unicode strings.
30
- And avoids mapping to whitespace/control characters the bpe code barfs on.
31
- """
32
- bs = list(range(ord("!"), ord("~")+1))+list(range(ord("¡"), ord("¬")+1))+list(range(ord("®"), ord("ÿ")+1))
33
- cs = bs[:]
34
- n = 0
35
- for b in range(2**8):
36
- if b not in bs:
37
- bs.append(b)
38
- cs.append(2**8+n)
39
- n += 1
40
- cs = [chr(n) for n in cs]
41
- return dict(zip(bs, cs))
42
-
43
-
44
- def get_pairs(word):
45
- """Return set of symbol pairs in a word.
46
- Word is represented as tuple of symbols (symbols being variable-length strings).
47
- """
48
- pairs = set()
49
- prev_char = word[0]
50
- for char in word[1:]:
51
- pairs.add((prev_char, char))
52
- prev_char = char
53
- return pairs
54
-
55
-
56
- def basic_clean(text):
57
- text = ftfy.fix_text(text)
58
- text = html.unescape(html.unescape(text))
59
- return text.strip()
60
-
61
-
62
- def whitespace_clean(text):
63
- text = re.sub(r'\s+', ' ', text)
64
- text = text.strip()
65
- return text
66
-
67
-
68
- class SimpleTokenizer(object):
69
- def __init__(self, bpe_path: str = default_bpe(), special_tokens=None):
70
- self.byte_encoder = bytes_to_unicode()
71
- self.byte_decoder = {v: k for k, v in self.byte_encoder.items()}
72
- merges = gzip.open(bpe_path).read().decode("utf-8").split('\n')
73
- merges = merges[1:49152-256-2+1]
74
- merges = [tuple(merge.split()) for merge in merges]
75
- vocab = list(bytes_to_unicode().values())
76
- vocab = vocab + [v+'</w>' for v in vocab]
77
- for merge in merges:
78
- vocab.append(''.join(merge))
79
- if not special_tokens:
80
- special_tokens = ['<start_of_text>', '<end_of_text>']
81
- else:
82
- special_tokens = ['<start_of_text>', '<end_of_text>'] + special_tokens
83
- vocab.extend(special_tokens)
84
- self.encoder = dict(zip(vocab, range(len(vocab))))
85
- self.decoder = {v: k for k, v in self.encoder.items()}
86
- self.bpe_ranks = dict(zip(merges, range(len(merges))))
87
- self.cache = {t:t for t in special_tokens}
88
- special = "|".join(special_tokens)
89
- self.pat = re.compile(special + r"""|'s|'t|'re|'ve|'m|'ll|'d|[\p{L}]+|[\p{N}]|[^\s\p{L}\p{N}]+""", re.IGNORECASE)
90
-
91
- self.vocab_size = len(self.encoder)
92
- self.all_special_ids = [self.encoder[t] for t in special_tokens]
93
-
94
- def bpe(self, token):
95
- if token in self.cache:
96
- return self.cache[token]
97
- word = tuple(token[:-1]) + ( token[-1] + '</w>',)
98
- pairs = get_pairs(word)
99
-
100
- if not pairs:
101
- return token+'</w>'
102
-
103
- while True:
104
- bigram = min(pairs, key = lambda pair: self.bpe_ranks.get(pair, float('inf')))
105
- if bigram not in self.bpe_ranks:
106
- break
107
- first, second = bigram
108
- new_word = []
109
- i = 0
110
- while i < len(word):
111
- try:
112
- j = word.index(first, i)
113
- new_word.extend(word[i:j])
114
- i = j
115
- except:
116
- new_word.extend(word[i:])
117
- break
118
-
119
- if word[i] == first and i < len(word)-1 and word[i+1] == second:
120
- new_word.append(first+second)
121
- i += 2
122
- else:
123
- new_word.append(word[i])
124
- i += 1
125
- new_word = tuple(new_word)
126
- word = new_word
127
- if len(word) == 1:
128
- break
129
- else:
130
- pairs = get_pairs(word)
131
- word = ' '.join(word)
132
- self.cache[token] = word
133
- return word
134
-
135
- def encode(self, text):
136
- bpe_tokens = []
137
- text = whitespace_clean(basic_clean(text)).lower()
138
- for token in re.findall(self.pat, text):
139
- token = ''.join(self.byte_encoder[b] for b in token.encode('utf-8'))
140
- bpe_tokens.extend(self.encoder[bpe_token] for bpe_token in self.bpe(token).split(' '))
141
- return bpe_tokens
142
-
143
- def decode(self, tokens):
144
- text = ''.join([self.decoder[token] for token in tokens])
145
- text = bytearray([self.byte_decoder[c] for c in text]).decode('utf-8', errors="replace").replace('</w>', ' ')
146
- return text
147
-
148
-
149
- _tokenizer = SimpleTokenizer()
150
-
151
-
152
- def tokenize(texts: Union[str, List[str]], context_length: int = 77) -> torch.LongTensor:
153
- """
154
- Returns the tokenized representation of given input string(s)
155
-
156
- Parameters
157
- ----------
158
- texts : Union[str, List[str]]
159
- An input string or a list of input strings to tokenize
160
- context_length : int
161
- The context length to use; all CLIP models use 77 as the context length
162
-
163
- Returns
164
- -------
165
- A two-dimensional tensor containing the resulting tokens, shape = [number of input strings, context_length]
166
- """
167
- if isinstance(texts, str):
168
- texts = [texts]
169
-
170
- sot_token = _tokenizer.encoder["<start_of_text>"]
171
- eot_token = _tokenizer.encoder["<end_of_text>"]
172
- all_tokens = [[sot_token] + _tokenizer.encode(text) + [eot_token] for text in texts]
173
- result = torch.zeros(len(all_tokens), context_length, dtype=torch.long)
174
-
175
- for i, tokens in enumerate(all_tokens):
176
- if len(tokens) > context_length:
177
- tokens = tokens[:context_length] # Truncate
178
- result[i, :len(tokens)] = torch.tensor(tokens)
179
-
180
- return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/clap_module/utils.py CHANGED
@@ -1,62 +1,6 @@
1
- import numpy as np
2
  import torch
3
  from torch import nn as nn
4
  from torchvision.ops.misc import FrozenBatchNorm2d
5
- import logging
6
- import h5py
7
- from tqdm import tqdm
8
- import random
9
- import json
10
- import os
11
- import pathlib
12
-
13
- # TODO: (yusong) this not a good place to store those information and does not scale. Need to be fixed later.
14
- dataset_split = {
15
- "audiocaps": ["train", "valid", "test"],
16
- "audioset": ["balanced_train", "unbalanced_train", "eval"],
17
- "BBCSoundEffects": ["train", "test"],
18
- "Clotho": ["train", "test", "valid"],
19
- "free_to_use_sounds": ["train", "test"],
20
- "paramount_motion": ["train", "test"],
21
- "sonniss_game_effects": ["train", "test"],
22
- "wesoundeffects": ["train", "test"],
23
- "MACS": ["train", "test"],
24
- "freesound": ["train", "test"],
25
- "FSD50K": ["train", "test", "valid"],
26
- "fsd50k_class_label": ["train", "test", "valid"],
27
- "esc50": ["train", "test"],
28
- "ESC50_1": ["train", "test"],
29
- "ESC50_2": ["train", "test"],
30
- "ESC50_3": ["train", "test"],
31
- "ESC50_4": ["train", "test"],
32
- "ESC50_5": ["train", "test"],
33
- "audiostock": ["train", "test"],
34
- "freesound_no_overlap_noesc50": ["train", "test"],
35
- "epidemic_sound_effects": ["train", "test"],
36
- "VGGSound": ["train", "test"],
37
- "urbansound8k_class_label": ["train", "test"],
38
- "audioset_t5": ["balanced_train", "unbalanced_train", "eval"],
39
- "audioset_t5_debiased": ["balanced_train", "unbalanced_train", "eval"],
40
- "epidemic_sound_effects_t5": ["train", "test"],
41
- "epidemic_sound_effects_t5_debiased": ["train", "test"],
42
- "WavText5K": ["train", "test"],
43
- "esc50_no_overlap": ["train", "test"],
44
- "usd8k_no_overlap": ["train", "test"],
45
- "fsd50k_200_class_label": ["train", "test", "valid"],
46
- "fma_full": ["train", "test"],
47
- "Genius": ["train", "test"],
48
- "Jamendo": ["train", "test"],
49
- "juno": ["train", "test"],
50
- "CMU_Arctic": ["train", "test"],
51
- "ravdess": ["train", "test"],
52
- "Europarl-st": ["train", "test"],
53
- "common_voice": ["train", "test"],
54
- "Jamendo_16bit": ["train", "test"],
55
- "genius_16bit_128": ["train", "test"],
56
- "juno_16bit": ["train", "test"],
57
- "fma_full_16bit_128": ["train", "test"],
58
- "GTZAN": ["train", "test"],
59
- }
60
 
61
 
62
  def freeze_batch_norm_2d(module, module_match={}, name=""):
@@ -100,99 +44,6 @@ def freeze_batch_norm_2d(module, module_match={}, name=""):
100
  return res
101
 
102
 
103
- def exist(dataset_name, dataset_type):
104
- """
105
- Check if dataset exists
106
- """
107
- if dataset_type in dataset_split[dataset_name]:
108
- return True
109
- else:
110
- return False
111
-
112
-
113
- def get_tar_path_from_dataset_name(
114
- dataset_names,
115
- dataset_types,
116
- islocal,
117
- dataset_path,
118
- proportion=1,
119
- full_dataset=None
120
- ):
121
- """
122
- Get tar path from dataset name and type
123
- """
124
- output = []
125
- for n in dataset_names:
126
- if full_dataset is not None and n in full_dataset:
127
- current_dataset_types = dataset_split[n]
128
- else:
129
- current_dataset_types = dataset_types
130
- for s in current_dataset_types:
131
- tmp = []
132
- if islocal:
133
- sizefilepath_ = f"{dataset_path}/{n}/{s}/sizes.json"
134
- if not os.path.exists(sizefilepath_):
135
- sizefilepath_ = f"./json_files/{n}/{s}/sizes.json"
136
- else:
137
- sizefilepath_ = f"./json_files/{n}/{s}/sizes.json"
138
- if not os.path.exists(sizefilepath_):
139
- continue
140
- sizes = json.load(open(sizefilepath_, "r"))
141
- for k in sizes.keys():
142
- if islocal:
143
- tmp.append(f"{dataset_path}/{n}/{s}/{k}")
144
- else:
145
- tmp.append(
146
- f"pipe:aws s3 --cli-connect-timeout 0 cp s3://s-laion-audio/webdataset_tar/{n}/{s}/{k} -"
147
- )
148
- if proportion != 1:
149
- tmp = random.sample(tmp, int(proportion * len(tmp)))
150
- output.append(tmp)
151
- return sum(output, [])
152
-
153
-
154
- def get_tar_path_from_txts(txt_path, islocal, proportion=1):
155
- """
156
- Get tar path from txt path
157
- """
158
- if isinstance(txt_path, (list, tuple)):
159
- return sum(
160
- [
161
- get_tar_path_from_txts(
162
- txt_path[i], islocal=islocal, proportion=proportion
163
- )
164
- for i in range(len(txt_path))
165
- ],
166
- [],
167
- )
168
- if isinstance(txt_path, str):
169
- with open(txt_path) as f:
170
- lines = f.readlines()
171
- if islocal:
172
- lines = [
173
- lines[i]
174
- .split("\n")[0]
175
- .replace("pipe:aws s3 cp s3://s-laion-audio/", "/mnt/audio_clip/")
176
- for i in range(len(lines))
177
- ]
178
- else:
179
- lines = [
180
- lines[i].split("\n")[0].replace(".tar", ".tar -")
181
- for i in range(len(lines))
182
- ]
183
- if proportion != 1:
184
- print("Sampling tars with proportion of {}".format(proportion))
185
- lines = random.sample(lines, int(proportion * len(lines)))
186
- return lines
187
-
188
-
189
- def get_mix_lambda(mixup_alpha, batch_size):
190
- mixup_lambdas = [
191
- np.random.beta(mixup_alpha, mixup_alpha, 1)[0] for _ in range(batch_size)
192
- ]
193
- return np.array(mixup_lambdas).astype(np.float32)
194
-
195
-
196
  def do_mixup(x, mixup_lambda):
197
  """
198
  Args:
@@ -240,150 +91,3 @@ def pad_framewise_output(framewise_output, frames_num):
240
 
241
  output = torch.cat((framewise_output, pad), dim=1)
242
  """(batch_size, frames_num, classes_num)"""
243
-
244
-
245
- def process_ipc(index_path, classes_num, filename):
246
- # load data
247
- logging.info("Load Data...............")
248
- ipc = [[] for _ in range(classes_num)]
249
- with h5py.File(index_path, "r") as f:
250
- for i in tqdm(range(len(f["target"]))):
251
- t_class = np.where(f["target"][i])[0]
252
- for t in t_class:
253
- ipc[t].append(i)
254
- print(ipc)
255
- np.save(filename, ipc)
256
- logging.info("Load Data Succeed...............")
257
-
258
-
259
- def save_to_dict(s, o_={}):
260
- sp = s.split(": ")
261
- o_.update({sp[0]: float(sp[1])})
262
- return o_
263
-
264
-
265
- def get_data_from_log(txt_path):
266
- """
267
- Output dictionary from out.txt log file
268
- """
269
- with open(txt_path) as f:
270
- lines = f.readlines()
271
- val_data = {}
272
- train_data = {}
273
- train_losses = []
274
- train_losses_epoch = []
275
- for i in range(len(lines)):
276
- if "| INFO |" in lines[i]:
277
- if "Eval Epoch" in lines[i]:
278
- if "val_loss" in lines[i]:
279
- # float(regex.sub("", lines[310].split(" ")[-1]).replace(" ", ""))
280
- line = lines[i].split("Eval Epoch: ")[-1]
281
- num_epoch = int(line.split(" ")[0].split(" ")[0])
282
- d = {
283
- line.split(" ")[0]
284
- .split(" ")[1]
285
- .replace(":", ""): float(line.split(" ")[0].split(" ")[-1])
286
- }
287
- for i in range(1, len(line.split(" "))):
288
- d = save_to_dict(line.split(" ")[i], d)
289
- val_data[num_epoch] = d
290
- elif "Train Epoch" in lines[i]:
291
- num_epoch = int(lines[i].split("Train Epoch: ")[1][0])
292
- loss = float(lines[i].split("Loss: ")[-1].split(" (")[0])
293
- train_losses.append(loss)
294
- train_losses_epoch.append(num_epoch)
295
- for i in range(len(train_losses)):
296
- train_data[i] = {
297
- "num_epoch": train_losses_epoch[i],
298
- "train_loss": train_losses[i],
299
- }
300
- return train_data, val_data
301
-
302
-
303
- def save_p(obj, filename):
304
- import pickle
305
-
306
- try:
307
- from deepdiff import DeepDiff
308
- except:
309
- os.system("pip install deepdiff")
310
- from deepdiff import DeepDiff
311
- with open(filename, "wb") as file:
312
- pickle.dump(obj, file, protocol=pickle.HIGHEST_PROTOCOL) # highest protocol
313
- with open(filename, "rb") as file:
314
- z = pickle.load(file)
315
- assert (
316
- DeepDiff(obj, z, ignore_string_case=True) == {}
317
- ), "there is something wrong with the saving process"
318
- return
319
-
320
-
321
- def load_p(filename):
322
- import pickle
323
-
324
- with open(filename, "rb") as file:
325
- z = pickle.load(file)
326
- return z
327
-
328
-
329
- def save_json(data, name="data.json"):
330
- import json
331
- with open(name, 'w') as fp:
332
- json.dump(data, fp)
333
- return
334
-
335
-
336
- def load_json(name):
337
- import json
338
- with open(name, 'r') as fp:
339
- data = json.load(fp)
340
- return data
341
-
342
-
343
- from multiprocessing import Process, Manager
344
- from multiprocessing import Process, Value, Array
345
- from ctypes import c_wchar
346
-
347
-
348
- def load_class_label(path):
349
- # https://stackoverflow.com/questions/48004243/how-to-share-large-read-only-dictionary-list-across-processes-in-multiprocessing
350
- # https://stackoverflow.com/questions/45693949/storing-strings-in-a-multiprocessing-sharedctypes-array
351
- out = None
352
- if path is not None:
353
- if pathlib.Path(path).suffix in [".pkl", ".pickle"]:
354
- out = load_p(path)
355
- elif pathlib.Path(path).suffix in [".json", ".txt"]:
356
- out = load_json(path)
357
- elif pathlib.Path(path).suffix in [".npy", ".npz"]:
358
- out = np.load(path)
359
- elif pathlib.Path(path).suffix in [".csv"]:
360
- import pandas as pd
361
- out = pd.read_csv(path)
362
- return out
363
- # if out is None:
364
- # return None
365
- # else:
366
- # key = Array(c_wchar, '\n'.join(list(out.keys())), lock=False)
367
- # val = Array('i', out.values(), lock=False)
368
- # return (key, val)
369
-
370
-
371
- from torch import optim
372
-
373
-
374
- def get_optimizer(params, lr, betas, eps, momentum, optimizer_name):
375
- if optimizer_name.lower() == "adamw":
376
- optimizer = optim.AdamW(
377
- params, lr=lr, betas=betas, eps=eps
378
- )
379
- elif optimizer_name.lower() == "sgd":
380
- optimizer = optim.SGD(
381
- params, lr=lr, momentum=momentum
382
- )
383
- elif optimizer_name.lower() == "adam":
384
- optimizer = optim.Adam(
385
- params, lr=lr, betas=betas, eps=eps
386
- )
387
- else:
388
- raise ValueError("optimizer name is not correct")
389
- return optimizer
 
 
1
  import torch
2
  from torch import nn as nn
3
  from torchvision.ops.misc import FrozenBatchNorm2d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
 
6
  def freeze_batch_norm_2d(module, module_match={}, name=""):
 
44
  return res
45
 
46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  def do_mixup(x, mixup_lambda):
48
  """
49
  Args:
 
91
 
92
  output = torch.cat((framewise_output, pad), dim=1)
93
  """(batch_size, frames_num, classes_num)"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/clap_module/version.py DELETED
@@ -1 +0,0 @@
1
- __version__ = '0.2.1'
 
 
utils/laion_clap/evaluate/__init__.py DELETED
File without changes
utils/laion_clap/evaluate/eval_dcase.py DELETED
@@ -1,150 +0,0 @@
1
- import torch
2
- import torch.nn.functional as F
3
- import torch.backends.cudnn as cudnn
4
- from open_clip import create_model
5
- from open_clip import tokenize
6
- import glob
7
- import json
8
- import librosa
9
- from tqdm import tqdm
10
- import numpy as np
11
- import os
12
- from laion_clap.training.params import parse_args
13
-
14
-
15
- def get_output_from_single_audio(audio, text, model, device):
16
-
17
- # audio_embedding = model.audio_infer(audio, hopsize=5 * 48000, key="embedding", device=device)['embedding']
18
- # if audio_embedding.ndim > 1:
19
- # audio_embedding = audio_embedding.mean(dim=0, keepdim=True)
20
- # else:
21
- # audio_embedding = audio_embedding.unsqueeze(0)
22
- audio_features = model(audio, None, device)
23
- audio_features = F.normalize(audio_features, dim=-1)
24
- text_features = model(None, text, device=device)
25
- text_features = F.normalize(text_features, dim=-1)
26
-
27
- # CHANGE: before normalize or after
28
- audio_features_mlp = model.audio_transform(audio_features)
29
- text_features_mlp = model.text_transform(text_features)
30
- return audio_features, text_features, audio_features_mlp, text_features_mlp, model.logit_scale_a.exp(), model.logit_scale_t.exp()
31
-
32
-
33
- def get_metrics(text_to_audio_logits):
34
- metrics = {}
35
-
36
- # repeat ground truth 5 times because Clotho has 5 text for 1 audio
37
- ground_truth = torch.repeat_interleave(torch.arange(len(text_features) // 5), 5).view(-1, 1)
38
-
39
- ranking = torch.argsort(text_to_audio_logits, descending=True)
40
- preds = torch.where(ranking == ground_truth)[1] # (yusong) this line is slow because it uses single thread
41
- preds = preds.detach().cpu().numpy()
42
- metrics[f"mean_rank"] = preds.mean() + 1
43
- metrics[f"median_rank"] = np.floor(np.median(preds)) + 1
44
- for k in [1, 5, 10]:
45
- metrics[f"R@{k}"] = np.mean(preds < k)
46
- # map@10
47
- metrics[f"mAP@10"] = np.mean(np.where(preds < 10, 1 / (preds + 1), 0.0))
48
- return metrics
49
-
50
-
51
- if __name__ == '__main__':
52
- args = parse_args()
53
-
54
- model_path = args.pretrained
55
-
56
- clotho_test_preprocessed_dir = "/fsx/yusong/clotho_test_set/test"
57
-
58
- cudnn.benchmark = True
59
- cudnn.deterministic = False
60
-
61
- audio_features_ensemble_all = []
62
- text_features_ensemble_all = []
63
- audio_features_mlp_ensemble_all = []
64
- text_features_mlp_ensemble_all = []
65
- logit_scale_a_ensemble_all = []
66
- logit_scale_t_ensemble_all = []
67
-
68
-
69
- device = torch.device('cuda')
70
- model, clap_model_cfg = create_model(
71
- args.amodel,
72
- args.tmodel,
73
- args.pretrained,
74
- precision=args.precision,
75
- device=device,
76
- jit=args.torchscript,
77
- force_quick_gelu=args.force_quick_gelu,
78
- openai_model_cache_dir=os.path.expanduser(args.openai_model_cache_dir),
79
- skip_params=False
80
- )
81
-
82
- # load model
83
- checkpoint = torch.load(model_path, map_location=device)
84
- if "epoch" in checkpoint:
85
- # resuming a train checkpoint w/ epoch and optimizer state
86
- start_epoch = checkpoint["epoch"]
87
- sd = checkpoint["state_dict"]
88
- if next(iter(sd.items()))[0].startswith(
89
- "module"
90
- ):
91
- sd = {k[len("module."):]: v for k, v in sd.items()}
92
- model.load_state_dict(sd)
93
- else:
94
- # loading a bare (model only) checkpoint for fine-tune or evaluation
95
- model.load_state_dict(checkpoint)
96
-
97
- model.to(device)
98
- model.eval()
99
- for param in model.parameters():
100
- param.requires_grad = False
101
-
102
- # take every 5th file because clotho has 5 texts for 1 audio
103
- test_file_list = sorted(glob.glob(f"{clotho_test_preprocessed_dir}/*.flac"))
104
-
105
- audio_features_all = []
106
- text_features_all = []
107
- audio_features_mlp_all = []
108
- text_features_mlp_all = []
109
- logit_scale_a_all = []
110
- logit_scale_t_all = []
111
-
112
- with torch.no_grad():
113
- for file_path in tqdm(test_file_list):
114
- json_path = file_path.replace(".flac", ".json")
115
- with open(json_path, "r") as f:
116
- json_data = json.load(f)
117
- audio, sr = librosa.load(file_path, sr=48000, mono=True)
118
- audio = torch.from_numpy(audio).to(device)
119
- audio = {'waveform': audio.unsqueeze(0), 'sample_rate': sr}
120
- text = json_data["text"]
121
-
122
- if args.tmodel == "transformer":
123
- from open_clip import tokenize
124
- text = tokenize(text)
125
- else:
126
- from laion_clap.training.data import tokenizer
127
- text = tokenizer(text, tmodel=args.tmodel) # 5 texts for each audio
128
-
129
- audio_features, text_features, audio_features_mlp, text_features_mlp, logit_scale_a, logit_scale_t = \
130
- get_output_from_single_audio(audio, text, model, device)
131
-
132
- audio_features_all.append(audio_features.detach().cpu())
133
- text_features_all.append(text_features.detach().cpu())
134
- audio_features_mlp_all.append(audio_features_mlp.detach().cpu())
135
- text_features_mlp_all.append(text_features_mlp.detach().cpu())
136
- logit_scale_a_all.append(logit_scale_a.detach().cpu())
137
- logit_scale_t_all.append(logit_scale_t.detach().cpu())
138
-
139
- audio_features = torch.cat(audio_features_all)
140
- text_features = torch.cat(text_features_all)
141
- logit_scale_a = logit_scale_a_all[0]
142
-
143
- logits_per_audio = (logit_scale_a * audio_features @ text_features.t()).detach().cpu()
144
- logits_per_text = logits_per_audio.t().detach().cpu()
145
-
146
- metrics = get_metrics(
147
- logits_per_text
148
- )
149
-
150
- print(metrics)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/evaluate/eval_linear_probe.py DELETED
@@ -1,515 +0,0 @@
1
- '''
2
- Evalute the linear probe performance on different checkpoints
3
- '''
4
- import logging
5
- import os
6
- import random
7
- from datetime import datetime
8
- import copy
9
- import numpy as np
10
- import torch
11
- import torch.backends.cudnn as cudnn
12
- from torch.cuda.amp import GradScaler
13
- import glob
14
-
15
- try:
16
- import wandb
17
- except ImportError:
18
- wandb = None
19
-
20
- try:
21
- import torch.utils.tensorboard as tensorboard
22
- except ImportError:
23
- tensorboard = None
24
-
25
- try:
26
- import horovod.torch as hvd
27
- except ImportError:
28
- hvd = None
29
-
30
- from clap_module import create_model_and_transforms, trace_model, create_model
31
- from training.data import get_data
32
- from training.params import parse_args
33
- from training.distributed import is_master, init_distributed_device, world_info_from_env
34
- from training.logger import setup_logging
35
- from training.scheduler import cosine_lr
36
- from training.lp_main import config_lp_optimizer
37
- from training.lp_train import train_one_epoch, evaluate
38
- from clap_module.utils import get_tar_path_from_dataset_name, dataset_split
39
- from clap_module.utils import load_p, load_class_label
40
- from clap_module.linear_probe import LinearProbe
41
-
42
- def maintain_ckpts(args, startidx, all_idx_len):
43
- for i in reversed(range(startidx, all_idx_len)):
44
- if os.path.exists(os.path.join(args.checkpoint_path, f"epoch_top_{i}.pt")):
45
- os.rename(
46
- os.path.join(args.checkpoint_path, f"epoch_top_{i}.pt"),
47
- os.path.join(args.checkpoint_path, f"epoch_top_{i+1}.pt"),
48
- )
49
- if os.path.exists(
50
- os.path.join(args.checkpoint_path, f"epoch_top_{all_idx_len}.pt")
51
- ):
52
- os.remove(os.path.join(args.checkpoint_path, f"epoch_top_{all_idx_len}.pt"))
53
- return
54
-
55
-
56
- def update_top_k_performance(
57
- new_metrics_inputs, current_top_k_ckpt_metrics, args, ckpt, bignumbetter=True, pretrain_epoch=0
58
- ):
59
- """
60
- Record the top-k performance of the current epoch.
61
- current_top_k_metrics is a dictionary of the form: {1: top_1_ckpt_measure, 2: top_2_ckpt_measure, ...}
62
- """
63
- if isinstance(new_metrics_inputs, (list, tuple)):
64
- new_metrics_inputs = np.mean(new_metrics_inputs)
65
- return update_top_k_performance(
66
- new_metrics_inputs,
67
- current_top_k_ckpt_metrics,
68
- args=args,
69
- ckpt=ckpt,
70
- bignumbetter=bignumbetter,
71
- pretrain_epoch=pretrain_epoch
72
- )
73
- elif isinstance(new_metrics_inputs, dict):
74
- new_metrics_inputs = np.mean(list(new_metrics_inputs.values()))
75
- return update_top_k_performance(
76
- new_metrics_inputs,
77
- current_top_k_ckpt_metrics,
78
- args=args,
79
- ckpt=ckpt,
80
- bignumbetter=bignumbetter,
81
- pretrain_epoch=pretrain_epoch
82
- )
83
- elif isinstance(new_metrics_inputs, (float, int)):
84
- update_flag = {k: False for k in current_top_k_ckpt_metrics.keys()}
85
- sorted_keys = sorted(current_top_k_ckpt_metrics.keys())
86
- sorted_values = sorted(
87
- current_top_k_ckpt_metrics.values(), reverse=bignumbetter
88
- )
89
- sorted_values_ = copy.deepcopy(sorted_values)
90
- sorted_values.append(new_metrics_inputs)
91
- sorted_values = sorted(sorted_values, reverse=bignumbetter)
92
- sorted_values = sorted_values[:-1]
93
-
94
- if sorted_values == sorted_values_:
95
- return current_top_k_ckpt_metrics, new_metrics_inputs
96
- else:
97
- for i in range(len(sorted_keys)):
98
- if current_top_k_ckpt_metrics[sorted_keys[i]] != sorted_values[i]:
99
- current_top_k_ckpt_metrics[sorted_keys[i]] = sorted_values[i]
100
- update_flag[sorted_keys[i]] = True
101
- for i in range(len(update_flag)):
102
- if update_flag[i]:
103
- maintain_ckpts(args, i, len(sorted_keys))
104
- torch.save(
105
- ckpt,
106
- os.path.join(args.checkpoint_path, f"pretrain_epoch_{pretrain_epoch}_lp_epoch_top_{i}.pt"),
107
- )
108
- break
109
- return current_top_k_ckpt_metrics, new_metrics_inputs
110
-
111
-
112
- # def updateifNone(a, b):
113
- # a = b if None else a
114
- # return a
115
-
116
-
117
- def is_pretrained_params(n):
118
- return (
119
- n.startswith("clap_model.transformer")
120
- or n in ["clap_model.positional_embedding", "clap_model.text_projection"]
121
- or n.startswith("clap_model.token_embedding")
122
- or n.startswith("clap_model.ln_final")
123
- or n.startswith("clap_model.logit_scale_t")
124
- )
125
-
126
-
127
- def random_seed(seed=42, rank=0):
128
- torch.manual_seed(seed + rank)
129
- np.random.seed(seed + rank)
130
- random.seed(seed + rank)
131
-
132
- def main():
133
- args = parse_args()
134
- # sanitize model name for filesystem / uri use, easier if we don't use / in name as a rule?
135
- args.amodel = args.amodel.replace("/", "-")
136
-
137
- pretrained_ckpts = sorted(glob.glob(os.path.join(args.pretrained, "*.pt")), key=os.path.getmtime)
138
-
139
- if args.name is None:
140
- args.name = "-".join(
141
- [
142
- datetime.now().strftime("%Y_%m_%d-%H_%M_%S"),
143
- f"linear_probe"
144
- f"model_{args.amodel}",
145
- f"lr_{args.lr}",
146
- f"b_{args.batch_size}",
147
- f"j_{args.workers}",
148
- f"p_{args.precision}",
149
- ]
150
- )
151
-
152
- # discover initial world args early so we can log properly
153
- args.distributed = False
154
- args.local_rank, args.rank, args.world_size = world_info_from_env()
155
-
156
- if args.remotedata and is_master(args):
157
- for dataset_name in args.datasetnames:
158
- for split in dataset_split[dataset_name]:
159
- if not os.path.exists(f"./json_files/{dataset_name}/{split}"):
160
- os.makedirs(f"./json_files/{dataset_name}/{split}")
161
- os.system(
162
- f"aws s3 cp s3://s-laion-audio/webdataset_tar/{dataset_name}/{split}/sizes.json ./json_files/{dataset_name}/{split}/sizes.json"
163
- )
164
- args.log_path = None
165
- if is_master(args, local=args.log_local):
166
- log_base_path = os.path.join(args.logs, args.name)
167
- os.makedirs(log_base_path, exist_ok=True)
168
- log_filename = f"out-{args.rank}" if args.log_local else "out.log"
169
- args.log_path = os.path.join(log_base_path, log_filename)
170
-
171
- # avoid log dir in same name:
172
- postfix = 0
173
- while os.path.exists(args.log_path):
174
- postfix += 1
175
- log_base_path_new = log_base_path+'-'+str(postfix)
176
- os.makedirs(log_base_path_new, exist_ok=True)
177
- log_filename = f"out-{args.rank}" if args.log_local else "out.log"
178
- args.log_path = os.path.join(log_base_path_new, log_filename)
179
- # print(
180
- # "Error. Experiment already exists. Use --name {} to specify a new experiment."
181
- # )
182
- # return -1
183
-
184
- # Set logger
185
- args.log_level = logging.DEBUG if args.debug else logging.INFO
186
- setup_logging(args.log_path, args.log_level)
187
-
188
- # fully initialize distributed device environment
189
- device = init_distributed_device(args)
190
-
191
- args.wandb = "wandb" in args.report_to or "all" in args.report_to
192
- args.tensorboard = "tensorboard" in args.report_to or "all" in args.report_to
193
- if is_master(args):
194
- args.tensorboard_path = (
195
- os.path.join(args.logs, args.name, "tensorboard")
196
- if args.tensorboard
197
- else ""
198
- )
199
- args.checkpoint_path = os.path.join(args.logs, args.name, "checkpoints")
200
- for dirname in [args.tensorboard_path, args.checkpoint_path]:
201
- if dirname:
202
- os.makedirs(dirname, exist_ok=True)
203
- else:
204
- args.tensorboard_path = ""
205
- args.checkpoint_path = ""
206
-
207
- if args.copy_codebase:
208
- copy_codebase(args)
209
-
210
- assert args.precision in ["amp", "fp16", "fp32"]
211
- if args.precision == "fp16":
212
- logging.warning(
213
- "It is recommended to use AMP mixed-precision instead of FP16. "
214
- "FP16 support needs further verification and tuning, especially for train."
215
- )
216
-
217
- if args.horovod:
218
- logging.info(
219
- f"Running in horovod mode with multiple processes / nodes. Device: {args.device}."
220
- f"Process (global: {args.rank}, local {args.local_rank}), total {args.world_size}."
221
- )
222
- elif args.distributed:
223
- logging.info(
224
- f"Running in distributed mode with multiple processes. Device: {args.device}."
225
- f"Process (global: {args.rank}, local {args.local_rank}), total {args.world_size}."
226
- )
227
- else:
228
- logging.info(f"Running with a single process. Device {args.device}.")
229
-
230
- logging.info(f'openai cache dir: {os.path.expanduser(args.openai_model_cache_dir)}')
231
-
232
- # determine if this worker should save logs and checkpoints. only do so if it is rank == 0
233
- args.save_logs = args.logs and args.logs.lower() != "none" and is_master(args)
234
- writer = None
235
- if args.save_logs and args.tensorboard:
236
- assert tensorboard is not None, "Please install tensorboard."
237
- writer = tensorboard.SummaryWriter(args.tensorboard_path)
238
-
239
- if args.wandb and is_master(args):
240
- assert wandb is not None, "Please install wandb."
241
- logging.debug("Starting wandb.")
242
- # you will have to configure this for your project!
243
- wandb.init(
244
- project="clap",
245
- notes=args.wandb_notes,
246
- name=args.wandb_notes,
247
- tags=[],
248
- config=vars(args),
249
- )
250
- logging.debug("Finished loading wandb.")
251
-
252
- for idx, f in enumerate(pretrained_ckpts):
253
- logging.info(f"pretrained on {f}")
254
- args.pretrained = f
255
- ckpt = torch.load(f, map_location='cpu')
256
- pretrain_epoch = 0
257
- if 'epoch' in ckpt:
258
- pretrain_epoch = ckpt['epoch']
259
- # train
260
- best_metrics = lp_main(args, device, writer, pretrain_epoch, idx)
261
-
262
- if args.wandb and is_master(args):
263
- assert wandb is not None, "Please install wandb."
264
- for name, val in best_metrics.items():
265
- wandb.log({f"val/summary/{name}": val, "epoch": pretrain_epoch})
266
-
267
- if args.wandb and is_master(args):
268
- wandb.finish()
269
-
270
- def update_metric(best_metric, new_metric):
271
- for key in new_metric:
272
- if key not in best_metric:
273
- best_metric[key] = new_metric[key]
274
- else:
275
- best_metric[key] = max(best_metric[key], new_metric[key])
276
- return best_metric
277
-
278
- def lp_main(args, device, writer, pretrain_epoch, idx):
279
-
280
- random.seed(args.seed)
281
- torch.manual_seed(args.seed)
282
- torch.cuda.manual_seed(args.seed)
283
- torch.cuda.manual_seed_all(args.seed)
284
- np.random.seed(args.seed)
285
- args.class_index_dict = load_class_label(args.class_label_path)
286
-
287
-
288
- # Create CLAP model
289
- clap_model, clap_model_cfg = create_model(
290
- args.amodel,
291
- args.tmodel,
292
- args.pretrained,
293
- precision=args.precision,
294
- device=device,
295
- jit=args.torchscript,
296
- force_quick_gelu=args.force_quick_gelu,
297
- openai_model_cache_dir=os.path.expanduser(args.openai_model_cache_dir),
298
- skip_params=False,
299
- enable_fusion=args.enable_fusion,
300
- fusion_type=args.fusion_type
301
- )
302
-
303
- args.lp_out_ch = len(list(args.class_index_dict.keys()))
304
- # Linear Probe
305
- if idx == 0:
306
- logging.info(f"linear probe using mlp: {args.lp_mlp}")
307
- logging.info(f"linear probe using freeze: {args.lp_freeze}")
308
- logging.info(f"linear probe act layer: {args.lp_act}")
309
- logging.info(f"linear probe out ch: {args.lp_out_ch}")
310
- logging.info(f"linear probe learning rate (if applicable): {args.lp_lr}")
311
- logging.info(f"linear probe loss func: {args.lp_loss}")
312
- logging.info(f"linear probe lp_metrics: {args.lp_metrics}")
313
-
314
- model = LinearProbe(
315
- clap_model,
316
- mlp=args.lp_mlp, freeze=args.lp_freeze,
317
- in_ch=512, out_ch=args.lp_out_ch,
318
- act=args.lp_act
319
- ) # in_ch is fixed (i.e., 512)
320
- model = model.to(device)
321
-
322
- if args.horovod:
323
- with torch.no_grad():
324
- for param in model.parameters():
325
- param.set_(param.contiguous())
326
-
327
- if args.trace:
328
- model = trace_model(model, batch_size=args.batch_size, device=device)
329
-
330
- if is_master(args) and idx == 0:
331
- logging.info("Linear Probe CLAP Model:")
332
- logging.info(f"{str(clap_model)}")
333
- logging.info("Params:")
334
- params_file = os.path.join(args.logs, args.name, "params.txt")
335
- with open(params_file, "w") as f:
336
- for name in sorted(vars(args)):
337
- val = getattr(args, name)
338
- logging.info(f" {name}: {val}")
339
- f.write(f"{name}: {val}\n")
340
-
341
-
342
- if args.distributed and not args.horovod:
343
- if args.use_bn_sync:
344
- model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model)
345
- ddp_args = {}
346
- if args.ddp_static_graph:
347
- # this doesn't exist in older PyTorch, arg only added if enabled
348
- ddp_args["static_graph"] = True
349
- model = torch.nn.parallel.DistributedDataParallel(
350
- model, device_ids=[device], find_unused_parameters=True, **ddp_args
351
- )
352
-
353
- data = get_data(args, clap_model_cfg)
354
- assert len(data), "At least one train or eval dataset must be specified."
355
- if args.trace:
356
- assert "train" not in data, "Cannot train with traced model"
357
-
358
- optimizer, scheduler, text_freeze_parameters = config_lp_optimizer(model, data, args)
359
-
360
- scaler = GradScaler() if args.precision == "amp" else None
361
-
362
- # optionally resume from a checkpoint
363
- start_epoch = 0
364
- if args.resume is not None:
365
- if os.path.isfile(args.resume):
366
- checkpoint = torch.load(args.resume, map_location=device)
367
- if "epoch" in checkpoint:
368
- # resuming a train checkpoint w/ epoch and optimizer state
369
- start_epoch = checkpoint["epoch"]
370
- sd = checkpoint["state_dict"]
371
- if not args.distributed and next(iter(sd.items()))[0].startswith(
372
- "module"
373
- ):
374
- sd = {k[len("module.") :]: v for k, v in sd.items()}
375
- model.load_state_dict(sd)
376
- if args.split_opt:
377
- if optimizer is not None:
378
- for k, o_ in optimizer.items():
379
- o_.load_state_dict(checkpoint[k + "_" + "optimizer"])
380
- if optimizer is not None:
381
- optimizer.load_state_dict(checkpoint["optimizer"])
382
- if scaler is not None and "scaler" in checkpoint:
383
- scaler.load_state_dict(checkpoint["scaler"])
384
- logging.info(
385
- f"=> resuming checkpoint '{args.resume}' (epoch {start_epoch})"
386
- )
387
- else:
388
- # loading a bare (model only) checkpoint for fine-tune or evaluation
389
- model.load_state_dict(checkpoint)
390
- logging.info(
391
- f"=> loaded checkpoint '{args.resume}' (epoch {start_epoch})"
392
- )
393
- if args.freeze_text:
394
- print("Freeze Text!!!!")
395
- for k in text_freeze_parameters:
396
- k.requires_grad = False
397
- else:
398
- logging.info("=> no checkpoint found at '{}'".format(args.resume))
399
-
400
- cudnn.benchmark = True
401
- cudnn.deterministic = False
402
-
403
- if args.wandb and is_master(args):
404
- args.train_sz = data["train"].dataloader.num_samples
405
- if args.val_data is not None:
406
- args.val_sz = data["val"].dataloader.num_samples
407
- if args.debug:
408
- wandb.watch(model, log="all")
409
- if idx == 0:
410
- wandb.save(params_file)
411
-
412
- best_metrics = {}
413
-
414
- if "train" not in data:
415
- metric = evaluate(model, data, start_epoch, args, writer, extra_suffix="_pe@" + str(pretrain_epoch))
416
- if is_master(args):
417
- best_metrics = update_metric(best_metrics, metric)
418
- return
419
- elif start_epoch == 0 and "val" in data and not args.no_eval:
420
- metric = evaluate(model, data, 0, args, writer, extra_suffix="_pe@" + str(pretrain_epoch))
421
- if is_master(args):
422
- best_metrics = update_metric(best_metrics, metric)
423
- if args.save_top_performance:
424
- current_top_k_ckpt_metrics = {
425
- i: 0 for i in range(args.save_top_performance)
426
- } # initialize the top-k metric for ckpts to 0
427
-
428
- for epoch in range(start_epoch, args.epochs):
429
- # freeze the text param after (include) args.freeze_text_after, this is -1 by default
430
- if epoch == args.freeze_text_after:
431
- print("Text pretrained parameters are freezed since this epoch.")
432
- for k in text_freeze_parameters:
433
- k.requires_grad = False
434
- if is_master(args):
435
- logging.info(f"Start epoch {epoch}")
436
-
437
- train_one_epoch(model, data, epoch, optimizer, scaler, scheduler, args, writer, extra_suffix="_pe@" + str(pretrain_epoch))
438
- completed_epoch = epoch + 1
439
-
440
- if any(v in data for v in ("val", "imagenet-val", "imagenet-v2")) and not args.no_eval:
441
- metric = evaluate(model, data, completed_epoch, args, writer, extra_suffix="_pe@" + str(pretrain_epoch))
442
- if is_master(args):
443
- best_metrics = update_metric(best_metrics, metric)
444
- if args.save_top_performance:
445
- top_k_dataset = args.top_k_checkpoint_select_dataset
446
- top_k_metric = args.top_k_checkpoint_select_metric
447
- filtered_metrics = [
448
- v
449
- for k, v in metric.items()
450
- if top_k_metric in k and top_k_dataset in k
451
- ] # check all R@10 metrics (all dataset) and use it to update the ckpt
452
- # Saving checkpoints.
453
- if args.save_logs:
454
- opt_dict = {
455
- k + "_" + "optimizer": v.state_dict() for k, v in optimizer.items()
456
- }
457
- checkpoint_dict = {
458
- "epoch": completed_epoch,
459
- "pretrain_epoch": pretrain_epoch,
460
- "name": args.name,
461
- "state_dict": model.state_dict(),
462
- }
463
- checkpoint_dict.update(opt_dict)
464
- if scaler is not None:
465
- checkpoint_dict["scaler"] = scaler.state_dict()
466
-
467
- if completed_epoch == args.epochs or (
468
- args.save_frequency > 0 and (completed_epoch % args.save_frequency) == 0
469
- ):
470
- torch.save(
471
- checkpoint_dict,
472
- os.path.join(args.checkpoint_path, f"pretrain_epoch_{pretrain_epoch}_lp_epoch_{completed_epoch}.pt"),
473
- )
474
- if args.save_most_recent:
475
- torch.save(
476
- checkpoint_dict,
477
- os.path.join(args.checkpoint_path, f"pretrain_epoch_{pretrain_epoch}_lp_epoch_latest.pt"),
478
- )
479
- if args.save_top_performance and not args.no_eval:
480
- update_top_k_performance(
481
- filtered_metrics,
482
- current_top_k_ckpt_metrics,
483
- args,
484
- checkpoint_dict,
485
- bignumbetter=True,
486
- pretrain_epoch=pretrain_epoch
487
- )
488
- del clap_model
489
- return best_metrics
490
-
491
-
492
- def copy_codebase(args):
493
- from shutil import copytree, ignore_patterns
494
-
495
- new_code_path = os.path.join(args.logs, args.name, "code")
496
- if os.path.exists(new_code_path):
497
- print(
498
- f"Error. Experiment already exists at {new_code_path}. Use --name to specify a new experiment."
499
- )
500
- return -1
501
- print(f"Copying codebase to {new_code_path}")
502
- current_code_path = os.path.realpath(__file__)
503
- for _ in range(3):
504
- current_code_path = os.path.dirname(current_code_path)
505
- copytree(
506
- current_code_path, new_code_path, ignore=ignore_patterns("log", "logs", "wandb")
507
- )
508
- print("Done copying code.")
509
- return 1
510
-
511
-
512
- if __name__ == "__main__":
513
- main()
514
-
515
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/evaluate/eval_retrieval.py DELETED
@@ -1,192 +0,0 @@
1
- import os.path
2
- import glob
3
- import random
4
- import numpy as np
5
- import logging
6
- import wandb
7
- import torch
8
- import torch.backends.cudnn as cudnn
9
- from laion_clap import create_model
10
- from laion_clap.training.logger import setup_logging
11
- from laion_clap.training.data import get_data
12
- from laion_clap.training.train import evaluate
13
- from laion_clap.utils import get_tar_path_from_dataset_name, dataset_split
14
- from laion_clap.training.params import parse_args
15
-
16
-
17
- def find_params_value(file, key):
18
- # find value of params in params_file
19
- with open(file, 'r') as f:
20
- for line in f:
21
- if key + ': ' in line:
22
- return line.split(': ')[1].strip()
23
- return None
24
-
25
-
26
- if __name__ == '__main__':
27
- # (yusong) repeated run might have different metric results.
28
- # This is because we randomly select crop 10s for each audio.
29
- args = parse_args()
30
-
31
- if os.path.isdir(args.pretrained):
32
- log_dir = os.path.dirname(args.pretrained)
33
- else:
34
- log_dir = os.path.dirname(os.path.dirname(args.pretrained))
35
-
36
- args.log_level = logging.DEBUG if args.debug else logging.INFO
37
- log_path = os.path.join(log_dir, 'out.log')
38
- setup_logging(log_path, args.log_level)
39
- params_file = os.path.join(log_dir, 'params.txt')
40
-
41
- seed = 3407
42
- random.seed(seed)
43
- torch.manual_seed(seed)
44
- torch.cuda.manual_seed(seed)
45
- torch.cuda.manual_seed_all(seed)
46
- np.random.seed(seed)
47
-
48
- cudnn.benchmark = True
49
- cudnn.deterministic = False
50
- pretrained = 'openai'
51
- amodel = find_params_value(params_file, 'amodel')
52
- tmodel = find_params_value(params_file, 'tmodel')
53
-
54
- if amodel is None or tmodel is None:
55
- raise ValueError('model type not found in params file')
56
-
57
- # set up dummy values for args
58
- args.parallel_eval = False
59
- args.rank = 0
60
- args.local_rank = 0
61
- args.world_size = 1
62
- args.val_frequency = 1
63
- args.epochs = 1
64
- args.precision = 'fp32'
65
- args.save_logs = True
66
- args.wandb = True
67
- args.class_index_dict = None
68
-
69
- device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
70
- args.device = device
71
-
72
- if args.remotedata:
73
- for dataset_name in args.datasetnames:
74
- for split in dataset_split[dataset_name]:
75
- if not os.path.exists(f"./json_files/{dataset_name}/{split}"):
76
- os.makedirs(f"./json_files/{dataset_name}/{split}")
77
- os.system(
78
- f"aws s3 cp s3://s-laion-audio/webdataset_tar/{dataset_name}/{split}/sizes.json ./json_files/{dataset_name}/{split}/sizes.json"
79
- )
80
-
81
- if args.datasetinfos is None:
82
- args.datasetinfos = ["train", "unbalanced_train", "balanced_train"]
83
- if args.dataset_type == "webdataset":
84
- args.train_data = get_tar_path_from_dataset_name(
85
- args.datasetnames,
86
- args.datasetinfos,
87
- islocal=not args.remotedata,
88
- proportion=args.dataset_proportion,
89
- dataset_path=args.datasetpath,
90
- )
91
- args.val_data = get_tar_path_from_dataset_name(
92
- args.datasetnames,
93
- ["valid", "test", "eval"],
94
- islocal=not args.remotedata,
95
- proportion=1,
96
- dataset_path=args.datasetpath,
97
- )
98
- model, model_cfg = create_model(
99
- amodel,
100
- tmodel,
101
- pretrained,
102
- precision='fp32',
103
- device=device,
104
- jit=False,
105
- force_quick_gelu=False,
106
- openai_model_cache_dir=os.path.expanduser(args.openai_model_cache_dir),
107
- skip_params=False,
108
- enable_fusion=args.enable_fusion,
109
- fusion_type=args.fusion_type
110
- ) # a hack to get model_cfg
111
-
112
- data = get_data(args, model_cfg=model_cfg) # (yusong): hack: no model_cfg needed to get data
113
-
114
- writer = None # if use tensorboard, initalize writer here
115
-
116
- if args.wandb:
117
- assert wandb is not None, "Please install wandb."
118
-
119
- # # find the line with "wandb_notes" and get the value
120
- # wandb_notes = find_params_value(params_file, 'wandb_notes')
121
- # if wandb_notes is None:
122
- # print(f'wandb_notes not found in params file: {params_file}, set to timestamp.')
123
- # wandb_notes = f'experiment_{time.strftime("%Y%m%d-%H%M%S")}'
124
- # wandb_notes = wandb_notes + '-eval-retrieval'
125
- wandb_notes = args.wandb_notes
126
-
127
- logging.debug("Starting wandb.")
128
- args.train_sz = data["train"].dataloader.num_samples
129
- if args.val_data is not None:
130
- args.val_sz = data["val"].dataloader.num_samples
131
- # you will have to configure this for your project!
132
- if args.wandb_id is not None:
133
- wandb.init(
134
- project="clap",
135
- id=args.wandb_id,
136
- resume=True
137
- )
138
- else:
139
- wandb.init(
140
- project="clap",
141
- notes=wandb_notes,
142
- name=wandb_notes,
143
- tags=[],
144
- config=vars(args),
145
- )
146
- logging.debug("Finished loading wandb.")
147
-
148
- if os.path.isdir(args.pretrained):
149
- all_model_checkpoints = sorted(glob.glob(os.path.join(log_dir, 'checkpoints', '*.pt')), key=os.path.getmtime)
150
- else:
151
- all_model_checkpoints = [args.pretrained]
152
- for model_path in all_model_checkpoints:
153
- args.checkpoint_path = os.path.dirname(model_path)
154
- model, model_cfg = create_model(
155
- amodel,
156
- tmodel,
157
- pretrained,
158
- precision='fp32',
159
- device=device,
160
- jit=False,
161
- force_quick_gelu=False,
162
- openai_model_cache_dir=os.path.expanduser(args.openai_model_cache_dir),
163
- skip_params=False,
164
- enable_fusion=args.enable_fusion,
165
- fusion_type=args.fusion_type
166
- )
167
-
168
- # load model
169
- checkpoint = torch.load(model_path, map_location=device)
170
- if "epoch" in checkpoint:
171
- # resuming a train checkpoint w/ epoch and optimizer state
172
- start_epoch = checkpoint["epoch"]
173
- sd = checkpoint["state_dict"]
174
- if next(iter(sd.items()))[0].startswith(
175
- "module"
176
- ):
177
- sd = {k[len("module."):]: v for k, v in sd.items()}
178
- model.load_state_dict(sd)
179
- logging.info(
180
- f"=> resuming checkpoint '{model_path}' (epoch {start_epoch})"
181
- )
182
- else:
183
- # loading a bare (model only) checkpoint for fine-tune or evaluation
184
- model.load_state_dict(checkpoint)
185
- start_epoch = 0
186
-
187
- model.to(device)
188
- model.eval()
189
- for param in model.parameters():
190
- param.requires_grad = False
191
-
192
- evaluate(model, data, start_epoch, args, writer)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/evaluate/eval_retrieval_main.py DELETED
@@ -1,257 +0,0 @@
1
- import os.path
2
- import glob
3
- import random
4
- import numpy as np
5
- import logging
6
- import wandb
7
- import torch
8
- import torch.nn.functional as F
9
- import torch.backends.cudnn as cudnn
10
- from clap_module import create_model
11
- from clap_module import tokenize
12
- from training.logger import setup_logging
13
- from training.data import get_data
14
- from training.train import evaluate
15
- from clap_module.utils import get_tar_path_from_dataset_name, dataset_split
16
- from training.params import parse_args
17
-
18
-
19
- def find_params_value(file, key):
20
- # find value of params in params_file
21
- with open(file, 'r') as f:
22
- for line in f:
23
- if key + ': ' in line:
24
- return line.split(': ')[1].strip()
25
- return None
26
-
27
-
28
- def evaluate_zeroshot(model, data, start_epoch, args, writer):
29
- dataloader = data["val"].dataloader
30
- metrics = {}
31
- device = torch.device(args.device)
32
- model.eval()
33
- metrics.update({"epoch": start_epoch})
34
-
35
- all_audio_features = []
36
- all_class_labels = []
37
- with torch.no_grad():
38
- for i, batch in enumerate(dataloader):
39
- audios = batch # contains mel_spec, wavform, and longer list
40
- audio_features = model(audios, None, device)
41
- audio_features = F.normalize(audio_features, dim=-1)
42
- all_audio_features.append(audio_features.detach().cpu())
43
- all_class_labels.append(torch.argmax(batch["class_label"], 1).long())
44
- all_audio_features = torch.cat(all_audio_features, dim=0)
45
- all_class_labels = torch.cat(all_class_labels, dim=0)
46
- metrics["num_samples"] = all_audio_features.shape[0]
47
-
48
- # get text features
49
- all_texts = ["This is a sound of " + t for t in args.class_index_dict.keys()]
50
- # (yusong): a hack, can make it better
51
- if args.tmodel == "transformer":
52
- from clap_module.tokenizer import tokenize
53
- all_texts = tokenize(all_texts)
54
- else:
55
- from training.data import tokenizer
56
- all_texts = tokenizer(all_texts)
57
- all_text_features = model(None, all_texts, device)
58
- all_text_features = F.normalize(all_text_features, dim=-1).detach().cpu()
59
-
60
- # compute similarity
61
- logit_scale_a, logit_scale_t = model(None, None, device)
62
- logit_scale_a = logit_scale_a.cpu()
63
-
64
- logits_per_audio = (logit_scale_a * all_audio_features @ all_text_features.t()).detach().cpu()
65
- logits_per_text = logits_per_audio.t().detach().cpu()
66
-
67
- ground_truth = all_class_labels.view(-1, 1)
68
- logit = logits_per_audio
69
-
70
- ranking = torch.argsort(logit, descending=True)
71
- preds = torch.where(ranking == ground_truth)[1] # (yusong) this line is slow because it uses single thread
72
- preds = preds.detach().cpu().numpy()
73
- metrics[f"{args.datasetnames[0]}_mean_rank"] = preds.mean() + 1
74
- metrics[f"{args.datasetnames[0]}_median_rank"] = np.floor(np.median(preds)) + 1
75
- for k in [1, 5, 10]:
76
- metrics[f"{args.datasetnames[0]}_R@{k}"] = np.mean(preds < k)
77
- # map@10
78
- metrics[f"{args.datasetnames[0]}_mAP@10"] = np.mean(np.where(preds < 10, 1 / (preds + 1), 0.0))
79
-
80
- logging.info(
81
- f"Eval Epoch: {start_epoch} "
82
- + "\t".join([f"{k}: {round(v, 4):.4f}" for k, v in metrics.items()])
83
- )
84
-
85
- if args.wandb:
86
- assert wandb is not None, "Please install wandb."
87
- for name, val in metrics.items():
88
- wandb.log({f"val/{name}": val, "epoch": start_epoch})
89
-
90
-
91
- if __name__ == '__main__':
92
- # (yusong) repeated run might have different metric results.
93
- # This is because we randomly select crop 10s for each audio.
94
- args = parse_args()
95
-
96
- if os.path.isdir(args.pretrained):
97
- log_dir = os.path.dirname(args.pretrained)
98
- else:
99
- log_dir = os.path.dirname(os.path.dirname(args.pretrained))
100
-
101
- args.log_level = logging.DEBUG if args.debug else logging.INFO
102
- log_path = os.path.join(log_dir, 'out.log')
103
- setup_logging(log_path, args.log_level)
104
- params_file = os.path.join(log_dir, 'params.txt')
105
-
106
- seed = 3407
107
- random.seed(seed)
108
- torch.manual_seed(seed)
109
- torch.cuda.manual_seed(seed)
110
- torch.cuda.manual_seed_all(seed)
111
- np.random.seed(seed)
112
-
113
- cudnn.benchmark = True
114
- cudnn.deterministic = False
115
- pretrained = 'openai'
116
- amodel = find_params_value(params_file, 'amodel')
117
- tmodel = find_params_value(params_file, 'tmodel')
118
-
119
- if amodel is None or tmodel is None:
120
- raise ValueError('model type not found in params file')
121
-
122
- # set up dummy values for args
123
- args.parallel_eval = False
124
- args.rank = 0
125
- args.local_rank = 0
126
- args.world_size = 1
127
- args.val_frequency = 1
128
- args.epochs = 1
129
- args.precision = 'fp32'
130
- args.save_logs = True
131
- args.wandb = args.report_to == 'wandb'
132
- args.class_index_dict = None
133
-
134
- device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
135
- args.device = device
136
-
137
- if args.remotedata:
138
- for dataset_name in args.datasetnames:
139
- for split in dataset_split[dataset_name]:
140
- if not os.path.exists(f"./json_files/{dataset_name}/{split}"):
141
- os.makedirs(f"./json_files/{dataset_name}/{split}")
142
- os.system(
143
- f"aws s3 cp s3://s-laion-audio/webdataset_tar/{dataset_name}/{split}/sizes.json ./json_files/{dataset_name}/{split}/sizes.json"
144
- )
145
-
146
- if args.datasetinfos is None:
147
- args.datasetinfos = ["train", "unbalanced_train", "balanced_train"]
148
- if args.dataset_type == "webdataset":
149
- args.train_data = get_tar_path_from_dataset_name(
150
- args.datasetnames,
151
- args.datasetinfos,
152
- islocal=not args.remotedata,
153
- proportion=args.dataset_proportion,
154
- dataset_path=args.datasetpath,
155
- )
156
- args.val_data = get_tar_path_from_dataset_name(
157
- args.datasetnames,
158
- ["valid", "test", "eval"],
159
- islocal=not args.remotedata,
160
- proportion=1,
161
- dataset_path=args.datasetpath,
162
- )
163
- model, model_cfg = create_model(
164
- amodel,
165
- tmodel,
166
- pretrained,
167
- precision='fp32',
168
- device=device,
169
- jit=False,
170
- force_quick_gelu=False,
171
- openai_model_cache_dir=os.path.expanduser(args.openai_model_cache_dir),
172
- skip_params=False,
173
- enable_fusion=args.enable_fusion,
174
- fusion_type=args.fusion_type
175
- ) # a hack to get model_cfg
176
-
177
- data = get_data(args, model_cfg=model_cfg) # (yusong): hack: no model_cfg needed to get data
178
-
179
- writer = None # if use tensorboard, initalize writer here
180
-
181
- if args.wandb:
182
- assert wandb is not None, "Please install wandb."
183
-
184
- # # find the line with "wandb_notes" and get the value
185
- # wandb_notes = find_params_value(params_file, 'wandb_notes')
186
- # if wandb_notes is None:
187
- # print(f'wandb_notes not found in params file: {params_file}, set to timestamp.')
188
- # wandb_notes = f'experiment_{time.strftime("%Y%m%d-%H%M%S")}'
189
- # wandb_notes = wandb_notes + '-eval-retrieval'
190
- wandb_notes = args.wandb_notes
191
-
192
- logging.debug("Starting wandb.")
193
- args.train_sz = data["train"].dataloader.num_samples
194
- if args.val_data is not None:
195
- args.val_sz = data["val"].dataloader.num_samples
196
- # you will have to configure this for your project!
197
- if args.wandb_id is not None:
198
- wandb.init(
199
- project="clap",
200
- id=args.wandb_id,
201
- resume=True
202
- )
203
- else:
204
- wandb.init(
205
- project="clap",
206
- notes=wandb_notes,
207
- name=wandb_notes,
208
- tags=[],
209
- config=vars(args),
210
- )
211
- logging.debug("Finished loading wandb.")
212
-
213
- if os.path.isdir(args.pretrained):
214
- all_model_checkpoints = sorted(glob.glob(os.path.join(log_dir, 'checkpoints', '*.pt')), key=os.path.getmtime)
215
- else:
216
- all_model_checkpoints = [args.pretrained]
217
- for model_path in all_model_checkpoints:
218
- args.checkpoint_path = os.path.dirname(model_path)
219
- model, model_cfg = create_model(
220
- amodel,
221
- tmodel,
222
- pretrained,
223
- precision='fp32',
224
- device=device,
225
- jit=False,
226
- force_quick_gelu=False,
227
- openai_model_cache_dir=os.path.expanduser(args.openai_model_cache_dir),
228
- skip_params=False,
229
- enable_fusion=args.enable_fusion,
230
- fusion_type=args.fusion_type
231
- )
232
-
233
- # load model
234
- checkpoint = torch.load(model_path, map_location=device)
235
- if "epoch" in checkpoint:
236
- # resuming a train checkpoint w/ epoch and optimizer state
237
- start_epoch = checkpoint["epoch"]
238
- sd = checkpoint["state_dict"]
239
- if next(iter(sd.items()))[0].startswith(
240
- "module"
241
- ):
242
- sd = {k[len("module."):]: v for k, v in sd.items()}
243
- model.load_state_dict(sd)
244
- logging.info(
245
- f"=> resuming checkpoint '{model_path}' (epoch {start_epoch})"
246
- )
247
- else:
248
- # loading a bare (model only) checkpoint for fine-tune or evaluation
249
- model.load_state_dict(checkpoint)
250
- start_epoch = 0
251
-
252
- model.to(device)
253
- model.eval()
254
- for param in model.parameters():
255
- param.requires_grad = False
256
-
257
- evaluate_zeroshot(model, data, start_epoch, args, writer)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/evaluate/eval_zeroshot_classification.py DELETED
@@ -1,261 +0,0 @@
1
- import os.path
2
- import glob
3
- import random
4
- import numpy as np
5
- import logging
6
- import wandb
7
- import torch
8
- import torch.nn.functional as F
9
- import torch.backends.cudnn as cudnn
10
- from clap_module import create_model
11
- from clap_module import tokenize
12
- from training.logger import setup_logging
13
- from training.data import get_data
14
- from training.train import evaluate
15
- from clap_module.utils import get_tar_path_from_dataset_name, dataset_split
16
- from training.params import parse_args
17
-
18
-
19
- def find_params_value(file, key):
20
- # find value of params in params_file
21
- with open(file, 'r') as f:
22
- for line in f:
23
- if key + ': ' in line:
24
- return line.split(': ')[1].strip()
25
- return None
26
-
27
-
28
- def evaluate_zeroshot(model, data, start_epoch, args, writer):
29
- dataloader = data["val"].dataloader
30
- metrics = {}
31
- device = torch.device(args.device)
32
- model.eval()
33
- metrics.update({"epoch": start_epoch})
34
-
35
- all_audio_features = []
36
- all_class_labels = []
37
- with torch.no_grad():
38
- for i, batch in enumerate(dataloader):
39
- audios = batch # contains mel_spec, wavform, and longer list
40
- audio_features = model(audios, None, device)
41
- audio_features = F.normalize(audio_features, dim=-1)
42
- all_audio_features.append(audio_features.detach().cpu())
43
- all_class_labels.append(torch.argmax(batch["class_label"], 1).long())
44
- all_audio_features = torch.cat(all_audio_features, dim=0)
45
- all_class_labels = torch.cat(all_class_labels, dim=0)
46
- metrics["num_samples"] = all_audio_features.shape[0]
47
-
48
- # get text features
49
- if args.val_dataset_names == ['GTZAN']:
50
- all_texts = [f"This is a {t} song." for t in args.class_index_dict.keys()]
51
- else:
52
- all_texts = [f"This is a sound of {t}." for t in args.class_index_dict.keys()]
53
- logging.info(f'class label prompts: {all_texts}')
54
- # (yusong): a hack, can make it better
55
- if args.tmodel == "transformer":
56
- from clap_module.tokenizer import tokenize
57
- all_texts = tokenize(all_texts)
58
- else:
59
- from training.data import tokenizer
60
- all_texts = tokenizer(all_texts)
61
- all_text_features = model(None, all_texts, device)
62
- all_text_features = F.normalize(all_text_features, dim=-1).detach().cpu()
63
-
64
- # compute similarity
65
- logit_scale_a, logit_scale_t = model(None, None, device)
66
- logit_scale_a = logit_scale_a.cpu()
67
-
68
- logits_per_audio = (logit_scale_a * all_audio_features @ all_text_features.t()).detach().cpu()
69
- logits_per_text = logits_per_audio.t().detach().cpu()
70
-
71
- ground_truth = all_class_labels.view(-1, 1)
72
- logit = logits_per_audio
73
-
74
- ranking = torch.argsort(logit, descending=True)
75
- preds = torch.where(ranking == ground_truth)[1] # (yusong) this line is slow because it uses single thread
76
- preds = preds.detach().cpu().numpy()
77
- metrics[f"{args.datasetnames[0]}_mean_rank"] = preds.mean() + 1
78
- metrics[f"{args.datasetnames[0]}_median_rank"] = np.floor(np.median(preds)) + 1
79
- for k in [1, 5, 10]:
80
- metrics[f"{args.datasetnames[0]}_R@{k}"] = np.mean(preds < k)
81
- # map@10
82
- metrics[f"{args.datasetnames[0]}_mAP@10"] = np.mean(np.where(preds < 10, 1 / (preds + 1), 0.0))
83
-
84
- logging.info(
85
- f"Eval Epoch: {start_epoch} "
86
- + "\t".join([f"{k}: {round(v, 4):.4f}" for k, v in metrics.items()])
87
- )
88
-
89
- if args.wandb:
90
- assert wandb is not None, "Please install wandb."
91
- for name, val in metrics.items():
92
- wandb.log({f"val/{name}": val, "epoch": start_epoch})
93
-
94
-
95
- if __name__ == '__main__':
96
- # (yusong) repeated run might have different metric results.
97
- # This is because we randomly select crop 10s for each audio.
98
- args = parse_args()
99
-
100
- if os.path.isdir(args.pretrained):
101
- log_dir = os.path.dirname(args.pretrained)
102
- else:
103
- log_dir = os.path.dirname(os.path.dirname(args.pretrained))
104
-
105
- args.log_level = logging.DEBUG if args.debug else logging.INFO
106
- log_path = os.path.join(log_dir, 'out.log')
107
- setup_logging(log_path, args.log_level)
108
- params_file = os.path.join(log_dir, 'params.txt')
109
-
110
- seed = 3407
111
- random.seed(seed)
112
- torch.manual_seed(seed)
113
- torch.cuda.manual_seed(seed)
114
- torch.cuda.manual_seed_all(seed)
115
- np.random.seed(seed)
116
-
117
- cudnn.benchmark = True
118
- cudnn.deterministic = False
119
- pretrained = 'openai'
120
- amodel = find_params_value(params_file, 'amodel')
121
- tmodel = find_params_value(params_file, 'tmodel')
122
-
123
- if amodel is None or tmodel is None:
124
- raise ValueError('model type not found in params file')
125
-
126
- # set up dummy values for args
127
- args.parallel_eval = False
128
- args.rank = 0
129
- args.local_rank = 0
130
- args.world_size = 1
131
- args.val_frequency = 1
132
- args.epochs = 1
133
- args.precision = 'fp32'
134
- args.save_logs = True
135
- args.wandb = args.report_to == 'wandb'
136
- args.class_index_dict = None
137
-
138
- device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
139
- args.device = device
140
-
141
- if args.remotedata:
142
- for dataset_name in args.datasetnames:
143
- for split in dataset_split[dataset_name]:
144
- if not os.path.exists(f"./json_files/{dataset_name}/{split}"):
145
- os.makedirs(f"./json_files/{dataset_name}/{split}")
146
- os.system(
147
- f"aws s3 cp s3://s-laion-audio/webdataset_tar/{dataset_name}/{split}/sizes.json ./json_files/{dataset_name}/{split}/sizes.json"
148
- )
149
-
150
- if args.datasetinfos is None:
151
- args.datasetinfos = ["train", "unbalanced_train", "balanced_train"]
152
- if args.dataset_type == "webdataset":
153
- args.train_data = get_tar_path_from_dataset_name(
154
- args.datasetnames,
155
- args.datasetinfos,
156
- islocal=not args.remotedata,
157
- proportion=args.dataset_proportion,
158
- dataset_path=args.datasetpath,
159
- )
160
- args.val_data = get_tar_path_from_dataset_name(
161
- args.datasetnames,
162
- ["valid", "test", "eval"],
163
- islocal=not args.remotedata,
164
- proportion=1,
165
- dataset_path=args.datasetpath,
166
- )
167
- model, model_cfg = create_model(
168
- amodel,
169
- tmodel,
170
- pretrained,
171
- precision='fp32',
172
- device=device,
173
- jit=False,
174
- force_quick_gelu=False,
175
- openai_model_cache_dir=os.path.expanduser(args.openai_model_cache_dir),
176
- skip_params=False,
177
- enable_fusion=args.enable_fusion,
178
- fusion_type=args.fusion_type
179
- ) # a hack to get model_cfg
180
-
181
- data = get_data(args, model_cfg=model_cfg) # (yusong): hack: no model_cfg needed to get data
182
-
183
- writer = None # if use tensorboard, initalize writer here
184
-
185
- if args.wandb:
186
- assert wandb is not None, "Please install wandb."
187
-
188
- # # find the line with "wandb_notes" and get the value
189
- # wandb_notes = find_params_value(params_file, 'wandb_notes')
190
- # if wandb_notes is None:
191
- # print(f'wandb_notes not found in params file: {params_file}, set to timestamp.')
192
- # wandb_notes = f'experiment_{time.strftime("%Y%m%d-%H%M%S")}'
193
- # wandb_notes = wandb_notes + '-eval-retrieval'
194
- wandb_notes = args.wandb_notes
195
-
196
- logging.debug("Starting wandb.")
197
- args.train_sz = data["train"].dataloader.num_samples
198
- if args.val_data is not None:
199
- args.val_sz = data["val"].dataloader.num_samples
200
- # you will have to configure this for your project!
201
- if args.wandb_id is not None:
202
- wandb.init(
203
- project="clap",
204
- id=args.wandb_id,
205
- resume=True
206
- )
207
- else:
208
- wandb.init(
209
- project="clap",
210
- notes=wandb_notes,
211
- name=wandb_notes,
212
- tags=[],
213
- config=vars(args),
214
- )
215
- logging.debug("Finished loading wandb.")
216
-
217
- if os.path.isdir(args.pretrained):
218
- all_model_checkpoints = sorted(glob.glob(os.path.join(log_dir, 'checkpoints', '*.pt')), key=os.path.getmtime)
219
- else:
220
- all_model_checkpoints = [args.pretrained]
221
- for model_path in all_model_checkpoints:
222
- args.checkpoint_path = os.path.dirname(model_path)
223
- model, model_cfg = create_model(
224
- amodel,
225
- tmodel,
226
- pretrained,
227
- precision='fp32',
228
- device=device,
229
- jit=False,
230
- force_quick_gelu=False,
231
- openai_model_cache_dir=os.path.expanduser(args.openai_model_cache_dir),
232
- skip_params=False,
233
- enable_fusion=args.enable_fusion,
234
- fusion_type=args.fusion_type
235
- )
236
-
237
- # load model
238
- checkpoint = torch.load(model_path, map_location=device)
239
- if "epoch" in checkpoint:
240
- # resuming a train checkpoint w/ epoch and optimizer state
241
- start_epoch = checkpoint["epoch"]
242
- sd = checkpoint["state_dict"]
243
- if next(iter(sd.items()))[0].startswith(
244
- "module"
245
- ):
246
- sd = {k[len("module."):]: v for k, v in sd.items()}
247
- model.load_state_dict(sd)
248
- logging.info(
249
- f"=> resuming checkpoint '{model_path}' (epoch {start_epoch})"
250
- )
251
- else:
252
- # loading a bare (model only) checkpoint for fine-tune or evaluation
253
- model.load_state_dict(checkpoint)
254
- start_epoch = 0
255
-
256
- model.to(device)
257
- model.eval()
258
- for param in model.parameters():
259
- param.requires_grad = False
260
-
261
- evaluate_zeroshot(model, data, start_epoch, args, writer)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/training/data.py CHANGED
@@ -1,94 +1,15 @@
1
- import ast
2
- import json
3
- import logging
4
- import math
5
- import os
6
- import random
7
- import h5py
8
- from dataclasses import dataclass
9
- import braceexpand
10
  import numpy as np
11
- import pandas as pd
12
  import torch
13
  import torch.nn.functional as F
14
- import torchvision.datasets as datasets
15
  import torchvision.transforms
16
- import webdataset as wds
17
- from PIL import Image
18
- from torch.utils.data import Dataset, DataLoader, SubsetRandomSampler
19
- from torch.utils.data.distributed import DistributedSampler
20
- from functools import partial
21
- from pathlib import Path
22
- import wget
23
- import tempfile
24
- import copy
25
- from contextlib import suppress
26
-
27
- from utils.laion_clap.clap_module.utils import get_tar_path_from_dataset_name, dataset_split
28
- from utils.laion_clap.clap_module.utils import load_p, load_class_label
29
- from utils.laion_clap.clap_module import tokenize as clip_tokenizer
30
- from transformers import BertTokenizer
31
- from transformers import RobertaTokenizer
32
- from transformers import BartTokenizer
33
-
34
- try:
35
- import horovod.torch as hvd
36
- except ImportError:
37
- hvd = None
38
 
39
  try:
40
  import torchaudio
41
  except ImportError:
42
  torchaudio = None
43
 
44
- bert_tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
45
- roberta_tokenizer = RobertaTokenizer.from_pretrained("roberta-base")
46
- bart_tokenizer = BartTokenizer.from_pretrained("facebook/bart-base")
47
-
48
- def tokenizer(text, tmodel="roberta", max_length=77):
49
- """tokenizer for different models
50
- tmodel is default to roberta as it is the best model for our task
51
- max_length is default to 77 from the OpenAI CLIP parameters
52
- We assume text to be a single string, but it can also be a list of strings
53
- """
54
- if tmodel == "transformer":
55
- return clip_tokenizer(text).squeeze(0)
56
-
57
- elif tmodel == "bert":
58
- result = bert_tokenizer(
59
- text,
60
- padding="max_length",
61
- truncation=True,
62
- max_length=max_length,
63
- return_tensors="pt",
64
- )
65
- return {k: v.squeeze(0) for k, v in result.items()}
66
-
67
- elif tmodel == "roberta":
68
- result = roberta_tokenizer(
69
- text,
70
- padding="max_length",
71
- truncation=True,
72
- max_length=max_length,
73
- return_tensors="pt",
74
- )
75
- return {k: v.squeeze(0) for k, v in result.items()}
76
-
77
- elif tmodel == "bart":
78
- result = bart_tokenizer(
79
- text,
80
- padding="max_length",
81
- truncation=True,
82
- max_length=max_length,
83
- return_tensors="pt",
84
- )
85
- return {k: v.squeeze(0) for k, v in result.items()}
86
-
87
-
88
- # initizlied the audioset map
89
- _AUDIOSET_MAP_PATH = os.path.join(Path(__file__).parent, "audioset_textmap.npy")
90
- _AUDIOSET_MAP = np.load(_AUDIOSET_MAP_PATH, allow_pickle=True)
91
-
92
 
93
  def int16_to_float32(x):
94
  return (x / 32767.0).astype(np.float32)
@@ -99,267 +20,6 @@ def float32_to_int16(x):
99
  return (x * 32767.).astype(np.int16)
100
 
101
 
102
- def int16_to_float32_torch(x):
103
- return (x / 32767.0).type(torch.float32)
104
-
105
-
106
- def float32_to_int16_torch(x):
107
- x = torch.clamp(x, min=-1., max=1.)
108
- return (x * 32767.).type(torch.int16)
109
-
110
-
111
- # For Toy Dataset
112
- class ToyDataset(Dataset):
113
- def __init__(self, index_path, ipc, config, eval_mode=False):
114
- """Toy Dataset for testing the audioset input with text labels
115
- Parameters
116
- ----------
117
- index_path: str
118
- the link to the h5 file of each audio
119
- idc: str
120
- the link to the npy file, the number of samples in each class
121
- config: dict
122
- the audio cfg file
123
- eval_model (bool): to indicate if the dataset is a testing dataset
124
- """
125
- self.audio_cfg = config["audio_cfg"]
126
- self.text_cfg = config["text_cfg"]
127
- self.fp = h5py.File(index_path, "r")
128
- self.ipc = np.load(ipc, allow_pickle=True)
129
- self.total_size = len(self.fp["audio_name"])
130
- self.classes_num = self.audio_cfg["class_num"]
131
- self.eval_mode = eval_mode
132
-
133
- if not eval_mode:
134
- self.generate_queue()
135
- else:
136
- self.queue = []
137
- for i in range(self.total_size):
138
- target = self.fp["target"][i]
139
- if np.sum(target) > 0:
140
- self.queue.append(i)
141
- self.total_size = len(self.queue)
142
- logging.info("total dataset size: %d" % (self.total_size))
143
- logging.info("class num: %d" % (self.classes_num))
144
-
145
- def time_shifting(self, x):
146
- frame_num = len(x)
147
- shift_len = random.randint(0, frame_num - 1)
148
- new_sample = np.concatenate([x[shift_len:], x[:shift_len]], axis=0)
149
- return new_sample
150
-
151
- def generate_queue(self):
152
- self.queue = []
153
- while len(self.queue) < self.total_size:
154
- class_set = [*range(self.classes_num)]
155
- random.shuffle(class_set)
156
- self.queue += [
157
- self.ipc[d][random.randint(0, len(self.ipc[d]) - 1)] for d in class_set
158
- ]
159
- self.queue = self.queue[: self.total_size]
160
-
161
- logging.info("queue regenerated:%s" % (self.queue[-5:]))
162
-
163
- def crop_wav(self, x):
164
- crop_size = self.audio_cfg["crop_size"]
165
- crop_pos = random.randint(0, len(x) - crop_size - 1)
166
- return x[crop_pos: crop_pos + crop_size]
167
-
168
- def prompt_text(self, target):
169
- events = _AUDIOSET_MAP[np.where(target > 0)]
170
- event_text = "The sounds of " + ", ".join(events[:-1]) + " and " + events[-1]
171
- text = tokenizer(event_text)[0]
172
- return text
173
-
174
- def __getitem__(self, index):
175
- """Load waveform, text, and target of an audio clip
176
-
177
- Parameters
178
- ----------
179
- index: int
180
- the index number
181
- Return
182
- ------
183
- output: dict {
184
- "hdf5_path": str,
185
- "index_in_hdf5": int,
186
- "audio_name": str,
187
- "waveform": list (audio_length,),
188
- "target": list (class_num, ),
189
- "text": torch.tensor (context_length,)
190
- }
191
- the output dictionary
192
- """
193
- s_index = self.queue[index]
194
-
195
- audio_name = self.fp["audio_name"][s_index].decode()
196
- # Hardcode here CHANGE
197
- hdf5_path = (
198
- self.fp["hdf5_path"][s_index]
199
- .decode()
200
- .replace(
201
- "../workspace",
202
- "/home/la/kechen/Research/ke_zsasp/workspace",
203
- )
204
- )
205
- r_idx = self.fp["index_in_hdf5"][s_index]
206
- target = self.fp["target"][s_index].astype(np.float32)
207
- text = self.prompt_text(target)
208
- with h5py.File(hdf5_path, "r") as f:
209
- waveform = int16_to_float32(f["waveform"][r_idx])[
210
- : self.audio_cfg["clip_samples"]
211
- ]
212
- assert (
213
- len(waveform) == self.audio_cfg["clip_samples"]
214
- ), "The sample length is not match"
215
- # Time shift
216
- # if (self.config.enable_time_shift) and (not self.eval_mode):
217
- # waveform = self.time_shifting(waveform)
218
- # # Label Enhance
219
- # if (self.config.crop_size is not None) and (not self.eval_mode):
220
- # waveform = self.crop_wav(waveform)
221
- # # the label enhance rate is fixed 0.5
222
- # if (self.config.enable_label_enhance) and (not self.eval_mode) and random.random() < 0.5:
223
- # kidx = np.where(target)[0]
224
- # for k in kidx:
225
- # for add_key in self.class_map[k][1]:
226
- # target[add_key] = 1.0
227
- # if len(self.class_map[k][2]) > 0:
228
- # add_key = random.choice(self.class_map[k][2])
229
- # target[add_key] = 1.0
230
-
231
- # missing the text input
232
- mel_spec = get_mel(torch.from_numpy(waveform), self.audio_cfg)[None, :, :]
233
- mel_spec = torch.cat([mel_spec, mel_spec.clone(), mel_spec.clone(), mel_spec.clone()], dim=0).cpu().numpy()
234
- longer = random.choice([True, False])
235
- if longer == False:
236
- mel_spec[1:, :, :] = 0.0
237
- data_dict = {
238
- "hdf5_path": hdf5_path,
239
- "index_in_hdf5": r_idx,
240
- "audio_name": audio_name,
241
- "waveform": waveform,
242
- "class_label": target,
243
- "text": text,
244
- "longer": longer,
245
- "mel_fusion": mel_spec
246
- }
247
- return data_dict
248
-
249
- def __len__(self):
250
- return self.total_size
251
-
252
- @dataclass
253
- class DataInfo:
254
- dataloader: DataLoader
255
- sampler: DistributedSampler
256
-
257
-
258
- def get_dataset_size(shards, sizefilepath_=None, is_local=True):
259
- if isinstance(shards, list):
260
- size_list = []
261
- for s in shards:
262
- size_list.append(
263
- get_dataset_size(s, sizefilepath_=sizefilepath_, is_local=is_local)[0]
264
- )
265
- else:
266
- if not is_local:
267
- for n in dataset_split.keys():
268
- if n in shards.split("/"):
269
- break
270
- for s in dataset_split[n]:
271
- if s in shards.split("/"):
272
- break
273
- sizefilepath_ = f"./json_files/{n}/{s}/sizes.json"
274
- shards_list = list(braceexpand.braceexpand(shards))
275
- dir_path = os.path.dirname(shards)
276
- if sizefilepath_ is not None:
277
- sizes = json.load(open(sizefilepath_, "r"))
278
- total_size = sum(
279
- [
280
- int(sizes[os.path.basename(shard.replace(".tar -", ".tar"))])
281
- for shard in shards_list
282
- ]
283
- )
284
- else:
285
- sizes_filename = os.path.join(dir_path, "sizes.json")
286
- len_filename = os.path.join(dir_path, "__len__")
287
- if os.path.exists(sizes_filename):
288
- sizes = json.load(open(sizes_filename, "r"))
289
- total_size = sum(
290
- [int(sizes[os.path.basename(shard)]) for shard in shards_list]
291
- )
292
- elif os.path.exists(len_filename):
293
- # FIXME this used to be eval(open(...)) but that seemed rather unsafe
294
- total_size = ast.literal_eval(open(len_filename, "r").read())
295
- else:
296
- raise Exception(
297
- f"Cannot find sizes file for dataset {shards}. Please specify the path to the file."
298
- )
299
- # total_size = None # num samples undefined
300
- # some common dataset sizes (at time of authors last download)
301
- # cc3m-train: 2905954
302
- # cc12m: 10968539
303
- # LAION-400m: 407332084
304
- num_shards = len(shards_list)
305
- if isinstance(shards, list):
306
- return sum(size_list), len(shards)
307
- else:
308
- return total_size, num_shards
309
-
310
-
311
- def count_samples(dataloader):
312
- os.environ["WDS_EPOCH"] = "0"
313
- n_elements, n_batches = 0, 0
314
- for images, texts in dataloader:
315
- n_batches += 1
316
- n_elements += len(images)
317
- assert len(images) == len(texts)
318
- return n_elements, n_batches
319
-
320
-
321
- def log_and_continue(exn):
322
- """Call in an exception handler to ignore any exception, isssue a warning, and continue."""
323
- logging.warning(f"Handling webdataset error ({repr(exn)}). Ignoring.")
324
- return True
325
-
326
-
327
- _SHARD_SHUFFLE_SIZE = 2000
328
- _SHARD_SHUFFLE_INITIAL = 500
329
- _SAMPLE_SHUFFLE_SIZE = 5000
330
- _SAMPLE_SHUFFLE_INITIAL = 1000
331
-
332
-
333
- def sample_prop(sizefile, inputs, proportion, is_local=True):
334
- """
335
- Sample a proportion of the data.
336
- """
337
- file_path_dict = {
338
- os.path.split(inputs[i])[1]: os.path.split(inputs[i])[0]
339
- for i in range(len(inputs))
340
- }
341
- sampled_filepath_dict = {}
342
- sampled_size_dict = {}
343
- if not is_local:
344
- if os.path.exists("sizes.json"):
345
- os.remove("sizes.json")
346
- wget.download(sizefile, "sizes.json")
347
- sizefile = "sizes.json"
348
- with open(sizefile, "r", encoding="UTF-8") as f:
349
- load_dict = json.load(f)
350
- L = int(len(file_path_dict) * proportion)
351
- subkeys = random.sample(file_path_dict.keys(), L)
352
- for k in subkeys:
353
- sampled_size_dict[k] = load_dict[k]
354
- sampled_filepath_dict[k] = file_path_dict[k]
355
- return (
356
- sum(sampled_size_dict.values()),
357
- L,
358
- [os.path.join(v, k) for k, v in sampled_filepath_dict.items()],
359
- sampled_size_dict,
360
- )
361
-
362
-
363
  def get_mel(audio_data, audio_cfg):
364
  # mel shape: (n_mels, T)
365
  mel_tf = torchaudio.transforms.MelSpectrogram(
@@ -376,24 +36,8 @@ def get_mel(audio_data, audio_cfg):
376
  f_min=audio_cfg['fmin'],
377
  f_max=audio_cfg['fmax']
378
  ).to(audio_data.device)
379
-
380
  mel = mel_tf(audio_data)
381
- # Align to librosa:
382
- # librosa_melspec = librosa.feature.melspectrogram(
383
- # waveform,
384
- # sr=audio_cfg['sample_rate'],
385
- # n_fft=audio_cfg['window_size'],
386
- # hop_length=audio_cfg['hop_size'],
387
- # win_length=audio_cfg['window_size'],
388
- # center=True,
389
- # pad_mode="reflect",
390
- # power=2.0,
391
- # n_mels=audio_cfg['mel_bins'],
392
- # norm=None,
393
- # htk=True,
394
- # f_min=audio_cfg['fmin'],
395
- # f_max=audio_cfg['fmax']
396
- # )
397
  # we use log mel spectrogram as input
398
  mel = torchaudio.transforms.AmplitudeToDB(top_db=None)(mel)
399
  return mel.T # (T, n_mels)
@@ -431,10 +75,6 @@ def get_audio_features(sample, audio_data, max_len, data_truncating, data_fillin
431
  longer = torch.tensor([False])
432
  else:
433
  ranges = np.array_split(list(range(0, total_frames - chunk_frames + 1)), 3)
434
- # print('total_frames-chunk_frames:', total_frames-chunk_frames,
435
- # 'len(audio_data):', len(audio_data),
436
- # 'chunk_frames:', chunk_frames,
437
- # 'total_frames:', total_frames)
438
  if len(ranges[1]) == 0:
439
  # if the audio is too short, we just use the first chunk
440
  ranges[1] = [0]
@@ -452,7 +92,6 @@ def get_audio_features(sample, audio_data, max_len, data_truncating, data_fillin
452
 
453
  # shrink the mel
454
  mel_shrink = torchvision.transforms.Resize(size=[chunk_frames, audio_cfg['mel_bins']])(mel[None])[0]
455
- # logging.info(f"mel_shrink.shape: {mel_shrink.shape}")
456
 
457
  # stack
458
  mel_fusion = torch.stack([mel_shrink, mel_chunk_front, mel_chunk_middle, mel_chunk_back], dim=0)
@@ -472,8 +111,6 @@ def get_audio_features(sample, audio_data, max_len, data_truncating, data_fillin
472
  if data_filling == "repeatpad":
473
  n_repeat = int(max_len / len(audio_data))
474
  audio_data = audio_data.repeat(n_repeat)
475
- # audio_data = audio_data.unsqueeze(0).unsqueeze(0).unsqueeze(0)
476
- # audio_data = F.interpolate(audio_data,size=max_len,mode="bicubic")[0,0,0]
477
  audio_data = F.pad(
478
  audio_data,
479
  (0, max_len - len(audio_data)),
@@ -504,392 +141,3 @@ def get_audio_features(sample, audio_data, max_len, data_truncating, data_fillin
504
  sample["waveform"] = audio_data
505
 
506
  return sample
507
-
508
-
509
- def select_text(json_dict_raw, text_augment_selection):
510
- # For selecting augmented text from dataset
511
- if text_augment_selection is None or text_augment_selection == "none":
512
- texts = json_dict_raw["text"]
513
- elif text_augment_selection == "all":
514
- if "text_augment_all" in json_dict_raw.keys():
515
- texts = json_dict_raw["text_augment_all"]
516
- else:
517
- texts = json_dict_raw["text"]
518
- elif text_augment_selection == "augment_only":
519
- if "text_augment_all" in json_dict_raw.keys():
520
- if json_dict_raw["text_augment_t5"] is None:
521
- texts = json_dict_raw["text"]
522
- else:
523
- texts = json_dict_raw["text_augment_t5"]
524
- else:
525
- texts = json_dict_raw["text"]
526
- else:
527
- raise NotImplementedError(
528
- f"text_augment_selection {text_augment_selection} not implemented"
529
- )
530
- return texts
531
-
532
-
533
- def preprocess_single(
534
- sample,
535
- audio_ext,
536
- text_ext,
537
- max_len,
538
- audio_cfg,
539
- tmodel,
540
- class_index_dict,
541
- data_filling,
542
- data_truncating,
543
- text_augment_selection,
544
- ):
545
- """
546
- Preprocess a single sample for wdsdataloader.
547
- """
548
- audio_data, orig_sr = sample[audio_ext]
549
- audio_data = int16_to_float32_torch(float32_to_int16_torch(audio_data[0]))
550
-
551
- sample = get_audio_features(sample, audio_data, max_len, data_truncating, data_filling, audio_cfg)
552
- del sample[audio_ext]
553
-
554
- json_dict_raw = sample[text_ext]
555
-
556
- texts = select_text(json_dict_raw, text_augment_selection)
557
- sample["full_text"] = texts
558
-
559
- if isinstance(texts, list) and isinstance(texts[0], str) and len(texts) > 1:
560
- texts = random.choice(texts)
561
- sample["raw_text"] = texts
562
- sample["text"] = tokenizer(texts, tmodel=tmodel) # text shape: [num_token]
563
- if class_index_dict is not None:
564
- # https://stackoverflow.com/questions/48004243/how-to-share-large-read-only-dictionary-list-across-processes-in-multiprocessing
565
- # https://stackoverflow.com/questions/45693949/storing-strings-in-a-multiprocessing-sharedctypes-array
566
-
567
- # in case the re-written version is wrong, here is the old version:
568
- # sample["class_label"] = np.zeros(len(class_index_dict.keys()))
569
- # for x in json_dict_raw["tag"]:
570
- # sample["class_label"][class_index_dict[x]] = 1
571
- # sample["class_label"] = torch.tensor(sample["class_label"]).float()
572
-
573
- class_labels = np.zeros(len(class_index_dict))
574
- class_labels[np.in1d(list(class_index_dict.keys()), json_dict_raw["tag"])] = 1
575
- sample["class_label"] = torch.tensor(class_labels).float()
576
-
577
- del sample[text_ext]
578
- sample["audio_name"] = sample["__key__"].split("/")[-1] + "." + audio_ext
579
- sample["text_name"] = sample["__key__"].split("/")[-1] + "." + text_ext
580
- sample["audio_orig_sr"] = orig_sr
581
- return sample
582
-
583
-
584
- def collate_fn_with_preprocess(batch,
585
- audio_ext,
586
- text_ext,
587
- max_len,
588
- audio_cfg,
589
- args,
590
- ):
591
- """
592
- Collate function for wdsdataloader.
593
- batch: a list of dict, each dict is a sample
594
- """
595
-
596
- class_index_dict = copy.deepcopy(args.class_index_dict) # To avoid deadlock in multiprocessing
597
- data_filling = args.data_filling
598
- data_truncating = args.data_truncating
599
- text_augment_selection = args.text_augment_selection
600
- tmodel = args.tmodel
601
-
602
- # concatenate values in each dictionary. if it is a tensor, concatenate. if it is a list, extend.
603
- data_preprocessed = []
604
-
605
- for sample in batch:
606
- data_preprocessed.append(
607
- preprocess_single(sample, audio_ext, text_ext, max_len, audio_cfg, tmodel, class_index_dict, data_filling,
608
- data_truncating, text_augment_selection))
609
-
610
- batch_dict = {}
611
- for k in data_preprocessed[0].keys():
612
- if isinstance(data_preprocessed[0][k], dict): # dealwith bert tokenizer output
613
- batch_dict[k] = {}
614
- for kk in data_preprocessed[0][k].keys():
615
- tmp = []
616
- for i in range(len(data_preprocessed)):
617
- tmp.append(data_preprocessed[i][k][kk])
618
- batch_dict[k][kk] = torch.vstack(tmp)
619
- elif isinstance(data_preprocessed[0][k], torch.Tensor):
620
- batch_dict[k] = torch.stack([sample[k] for sample in data_preprocessed])
621
- elif isinstance(data_preprocessed[0][k], np.ndarray):
622
- batch_dict[k] = torch.tensor(np.stack([sample[k] for sample in data_preprocessed]))
623
- else:
624
- batch_dict[k] = [sample[k] for sample in data_preprocessed]
625
- del data_preprocessed
626
- return batch_dict
627
-
628
-
629
- def get_wds_dataset(
630
- args,
631
- model_cfg,
632
- is_train,
633
- audio_ext="flac",
634
- text_ext="json",
635
- max_len=480000,
636
- proportion=1.0,
637
- sizefilepath_=None,
638
- is_local=None,
639
- ):
640
- """
641
- Get a dataset for wdsdataloader.
642
- """
643
- if is_local is None and (not args.remotedata is None):
644
- is_local = not args.remotedata
645
-
646
- input_shards = args.train_data if is_train else args.val_data
647
- assert input_shards is not None
648
-
649
- if not sizefilepath_ is None:
650
- sizefilepath = sizefilepath_
651
- else:
652
- sizefilepath = os.path.join(os.path.dirname(input_shards[0]), "sizes.json")
653
-
654
- if proportion != 1.0:
655
- num_samples, num_shards, input_shards, _ = sample_prop(
656
- sizefilepath, input_shards, proportion, is_local=is_local
657
- )
658
- else:
659
- num_samples, num_shards = get_dataset_size(
660
- input_shards, sizefilepath_=sizefilepath_, is_local=is_local
661
- )
662
-
663
- if not num_samples:
664
- if is_train:
665
- num_samples = args.train_num_samples
666
- if not num_samples:
667
- raise RuntimeError(
668
- "Currently, number of dataset samples must be specified for training dataset. "
669
- "Please specify via `--train-num-samples` if no dataset length info present."
670
- )
671
- else:
672
- num_samples = (
673
- args.val_num_samples or 0
674
- ) # eval will just exhaust the iterator if not specified
675
-
676
- pipeline = [wds.SimpleShardList(input_shards)]
677
- # at this point we have an iterator over all the shards
678
- # TODO: (yusong): add a if statement of distributed. If not, we don't need to split_by_node
679
- if is_train or args.parallel_eval:
680
- pipeline.extend(
681
- [
682
- wds.detshuffle(
683
- bufsize=_SHARD_SHUFFLE_SIZE,
684
- initial=_SHARD_SHUFFLE_INITIAL,
685
- seed=args.seed,
686
- ),
687
- wds.split_by_node,
688
- wds.split_by_worker,
689
- # at this point, we have an iterator over the shards assigned to each worker at each node
690
- wds.tarfile_to_samples(handler=log_and_continue),
691
- wds.shuffle(
692
- bufsize=_SAMPLE_SHUFFLE_SIZE,
693
- initial=_SAMPLE_SHUFFLE_INITIAL,
694
- rng=random.Random(args.seed),
695
- ),
696
- # wds.repeatedly, # FIXME determine if this is beneficial
697
- ]
698
- )
699
- else:
700
- pipeline.extend(
701
- [
702
- wds.split_by_worker,
703
- # at this point, we have an iterator over the shards assigned to each worker
704
- wds.tarfile_to_samples(handler=log_and_continue),
705
- ]
706
- )
707
-
708
- pipeline.append(
709
- wds.decode(wds.torch_audio),
710
- )
711
-
712
- pipeline.append(
713
- wds.batched(
714
- args.batch_size,
715
- partial=not (is_train or args.parallel_eval),
716
- collation_fn=partial(collate_fn_with_preprocess,
717
- audio_ext=audio_ext,
718
- text_ext=text_ext,
719
- max_len=max_len,
720
- audio_cfg=model_cfg['audio_cfg'],
721
- args=args,
722
- ),
723
-
724
- )
725
- )
726
-
727
- dataset = wds.DataPipeline(*pipeline)
728
- if is_train or args.parallel_eval:
729
- # (yusong): Currently parallel evaluation will be not precise as we are repeat the last few samples.
730
- # (yusong): See comments below.
731
- # roll over and repeat a few samples to get same number of full batches on each node
732
- global_batch_size = args.batch_size * args.world_size
733
- num_batches = math.ceil(num_samples / global_batch_size)
734
- num_workers = max(1, args.workers)
735
- num_worker_batches = math.ceil(
736
- num_batches / num_workers
737
- ) # per dataloader worker
738
- num_batches = num_worker_batches * num_workers
739
- num_samples = num_batches * global_batch_size
740
- dataset = dataset.with_epoch(
741
- num_worker_batches
742
- ) # each worker is iterating over this
743
- else:
744
- # last batches are partial, eval is done on single (master) node
745
- num_batches = math.ceil(num_samples / args.batch_size)
746
-
747
- kwargs = {}
748
- if args.horovod: # multi-node training on summit
749
- kwargs["multiprocessing_context"] = "forkserver"
750
-
751
- if is_train:
752
- if args.prefetch_factor:
753
- prefetch_factor = args.prefetch_factor
754
- else:
755
- prefetch_factor = max(2, args.batch_size // args.workers)
756
- else:
757
- prefetch_factor = 2
758
-
759
- dataloader = wds.WebLoader(
760
- dataset,
761
- batch_size=None,
762
- shuffle=False,
763
- num_workers=args.workers,
764
- pin_memory=True,
765
- prefetch_factor=prefetch_factor,
766
- **kwargs
767
- )
768
-
769
- # FIXME not clear which approach is better, with_epoch before vs after dataloader?
770
- # hoping to resolve via https://github.com/webdataset/webdataset/issues/169
771
- # if is_train:
772
- # # roll over and repeat a few samples to get same number of full batches on each node
773
- # global_batch_size = args.batch_size * args.world_size
774
- # num_batches = math.ceil(num_samples / global_batch_size)
775
- # num_workers = max(1, args.workers)
776
- # num_batches = math.ceil(num_batches / num_workers) * num_workers
777
- # num_samples = num_batches * global_batch_size
778
- # dataloader = dataloader.with_epoch(num_batches)
779
- # else:
780
- # # last batches are partial, eval is done on single (master) node
781
- # num_batches = math.ceil(num_samples / args.batch_size)
782
-
783
- # add meta-data to dataloader instance for convenience
784
- dataloader.num_batches = num_batches
785
- dataloader.num_samples = num_samples
786
-
787
- return DataInfo(dataloader, None)
788
-
789
-
790
- def wds_batch_list2dict(
791
- batch,
792
- keys=[
793
- "__url__",
794
- "__key__",
795
- "waveform",
796
- "text",
797
- "raw_text",
798
- "audio_name",
799
- "text_name",
800
- "audio_orig_sr",
801
- ],
802
- ):
803
- """
804
- Return a dictionary of the batch, with keys as the names of the fields.
805
- """
806
- assert len(keys) == len(
807
- batch
808
- ), "batch must have same number of keys as keys argument"
809
- return {keys[i]: batch[i] for i in range(len(batch))}
810
-
811
-
812
-
813
- def get_toy_dataset(args, model_cfg, is_train):
814
- index_path = args.train_data if is_train else args.val_data
815
- ipc_path = args.train_ipc if is_train else args.val_ipc
816
- assert index_path and ipc_path
817
- eval_mode = not is_train
818
- dataset = ToyDataset(index_path, ipc_path, model_cfg, eval_mode=eval_mode)
819
-
820
- num_samples = len(dataset)
821
- sampler = (
822
- DistributedSampler(dataset, shuffle=False)
823
- if args.distributed and is_train
824
- else None
825
- )
826
-
827
- dataloader = DataLoader(
828
- dataset,
829
- batch_size=args.batch_size,
830
- shuffle=False,
831
- num_workers=args.workers,
832
- sampler=sampler,
833
- drop_last=is_train,
834
- )
835
- dataloader.num_samples = num_samples
836
- dataloader.num_batches = len(dataloader)
837
-
838
- return DataInfo(dataloader, sampler)
839
-
840
-
841
- def get_dataset_fn(dataset_type):
842
- if dataset_type == "webdataset":
843
- return get_wds_dataset
844
- elif dataset_type == "toy":
845
- return get_toy_dataset
846
- else:
847
- raise ValueError(f"Unsupported dataset type: {dataset_type}")
848
-
849
-
850
- def get_data(args, model_cfg):
851
- data = {}
852
-
853
- args.class_index_dict = load_class_label(args.class_label_path)
854
-
855
- if args.datasetinfos is None:
856
- args.datasetinfos = ["train", "unbalanced_train", "balanced_train"]
857
- if args.dataset_type == "webdataset":
858
- args.train_data = get_tar_path_from_dataset_name(
859
- args.datasetnames,
860
- args.datasetinfos,
861
- islocal=not args.remotedata,
862
- proportion=args.dataset_proportion,
863
- dataset_path=args.datasetpath,
864
- full_dataset=args.full_train_dataset,
865
- )
866
-
867
- if args.full_train_dataset is None:
868
- args.full_train_dataset = []
869
- if args.exclude_eval_dataset is None:
870
- args.exclude_eval_dataset = []
871
- excluded_eval_datasets = args.full_train_dataset + args.exclude_eval_dataset
872
-
873
- val_dataset_names = [n for n in args.datasetnames if n not in excluded_eval_datasets] \
874
- if excluded_eval_datasets else args.datasetnames
875
- args.val_dataset_names = val_dataset_names
876
- args.val_data = get_tar_path_from_dataset_name(
877
- val_dataset_names,
878
- ["valid", "test", "eval"],
879
- islocal=not args.remotedata,
880
- proportion=1,
881
- dataset_path=args.datasetpath,
882
- full_dataset=None,
883
- )
884
-
885
- if args.train_data:
886
- data["train"] = get_dataset_fn(args.dataset_type)(
887
- args, model_cfg, is_train=True
888
- )
889
-
890
- if args.val_data:
891
- data["val"] = get_dataset_fn(args.dataset_type)(
892
- args, model_cfg, is_train=False
893
- )
894
-
895
- return data
 
1
+ from contextlib import suppress
2
+
 
 
 
 
 
 
 
3
  import numpy as np
 
4
  import torch
5
  import torch.nn.functional as F
 
6
  import torchvision.transforms
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
  try:
9
  import torchaudio
10
  except ImportError:
11
  torchaudio = None
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  def int16_to_float32(x):
15
  return (x / 32767.0).astype(np.float32)
 
20
  return (x * 32767.).astype(np.int16)
21
 
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  def get_mel(audio_data, audio_cfg):
24
  # mel shape: (n_mels, T)
25
  mel_tf = torchaudio.transforms.MelSpectrogram(
 
36
  f_min=audio_cfg['fmin'],
37
  f_max=audio_cfg['fmax']
38
  ).to(audio_data.device)
39
+
40
  mel = mel_tf(audio_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  # we use log mel spectrogram as input
42
  mel = torchaudio.transforms.AmplitudeToDB(top_db=None)(mel)
43
  return mel.T # (T, n_mels)
 
75
  longer = torch.tensor([False])
76
  else:
77
  ranges = np.array_split(list(range(0, total_frames - chunk_frames + 1)), 3)
 
 
 
 
78
  if len(ranges[1]) == 0:
79
  # if the audio is too short, we just use the first chunk
80
  ranges[1] = [0]
 
92
 
93
  # shrink the mel
94
  mel_shrink = torchvision.transforms.Resize(size=[chunk_frames, audio_cfg['mel_bins']])(mel[None])[0]
 
95
 
96
  # stack
97
  mel_fusion = torch.stack([mel_shrink, mel_chunk_front, mel_chunk_middle, mel_chunk_back], dim=0)
 
111
  if data_filling == "repeatpad":
112
  n_repeat = int(max_len / len(audio_data))
113
  audio_data = audio_data.repeat(n_repeat)
 
 
114
  audio_data = F.pad(
115
  audio_data,
116
  (0, max_len - len(audio_data)),
 
141
  sample["waveform"] = audio_data
142
 
143
  return sample
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/training/distributed.py DELETED
@@ -1,139 +0,0 @@
1
- import os
2
-
3
- import torch
4
- import socket
5
-
6
- try:
7
- import horovod.torch as hvd
8
- except ImportError:
9
- hvd = None
10
-
11
-
12
- def is_global_master(args):
13
- return args.rank == 0
14
-
15
-
16
- def is_local_master(args):
17
- return args.local_rank == 0
18
-
19
-
20
- def is_master(args, local=False):
21
- return is_local_master(args) if local else is_global_master(args)
22
-
23
-
24
- def is_using_horovod():
25
- # NOTE w/ horovod run, OMPI vars should be set, but w/ SLURM PMI vars will be set
26
- # Differentiating between horovod and DDP use via SLURM may not be possible, so horovod arg still required...
27
- ompi_vars = ["OMPI_COMM_WORLD_RANK", "OMPI_COMM_WORLD_SIZE"]
28
- pmi_vars = ["PMI_RANK", "PMI_SIZE"]
29
- if all([var in os.environ for var in ompi_vars]) or all([var in os.environ for var in pmi_vars]):
30
- return True
31
- else:
32
- return False
33
-
34
-
35
- def is_using_distributed():
36
- if 'WORLD_SIZE' in os.environ:
37
- return int(os.environ['WORLD_SIZE']) > 1
38
- if 'SLURM_NTASKS' in os.environ:
39
- return int(os.environ['SLURM_NTASKS']) > 1
40
- return False
41
-
42
-
43
- def world_info_from_env():
44
- local_rank = 0
45
- for v in ('SLURM_LOCALID', 'MPI_LOCALRANKID', 'OMPI_COMM_WORLD_LOCAL_RANK', 'LOCAL_RANK'):
46
- if v in os.environ:
47
- local_rank = int(os.environ[v])
48
- break
49
- global_rank = 0
50
- for v in ('SLURM_PROCID', 'PMI_RANK', 'OMPI_COMM_WORLD_RANK', 'RANK'):
51
- if v in os.environ:
52
- global_rank = int(os.environ[v])
53
- break
54
- world_size = 1
55
- for v in ('SLURM_NTASKS', 'PMI_SIZE', 'OMPI_COMM_WORLD_SIZE', 'WORLD_SIZE'):
56
- if v in os.environ:
57
- world_size = int(os.environ[v])
58
- break
59
-
60
- return local_rank, global_rank, world_size
61
-
62
-
63
- def init_distributed_device(args):
64
- # Distributed training = training on more than one GPU.
65
- # Works in both single and multi-node scenarios.
66
- args.distributed = False
67
- args.world_size = 1
68
- args.rank = 0 # global rank
69
- args.local_rank = 0
70
- if args.horovod:
71
- assert hvd is not None, "Horovod is not installed"
72
- hvd.init()
73
- world_size = int(os.environ['OMPI_COMM_WORLD_SIZE'])
74
- world_rank = int(os.environ['OMPI_COMM_WORLD_RANK'])
75
- local_rank = int(os.environ['OMPI_COMM_WORLD_LOCAL_RANK'])
76
- args.local_rank = local_rank
77
- args.rank = world_rank
78
- args.world_size = world_size
79
- # args.local_rank = int(hvd.local_rank())
80
- # args.rank = hvd.rank()
81
- # args.world_size = hvd.size()
82
- args.distributed = True
83
- os.environ['LOCAL_RANK'] = str(args.local_rank)
84
- os.environ['RANK'] = str(args.rank)
85
- os.environ['WORLD_SIZE'] = str(args.world_size)
86
- print(f"Distributed training: local_rank={args.local_rank}, "
87
- f"rank={args.rank}, world_size={args.world_size}, "
88
- f"hostname={socket.gethostname()}, pid={os.getpid()}")
89
- elif is_using_distributed():
90
- if 'SLURM_PROCID' in os.environ:
91
- # DDP via SLURM
92
- args.local_rank, args.rank, args.world_size = world_info_from_env()
93
- # SLURM var -> torch.distributed vars in case needed
94
- os.environ['LOCAL_RANK'] = str(args.local_rank)
95
- os.environ['RANK'] = str(args.rank)
96
- os.environ['WORLD_SIZE'] = str(args.world_size)
97
- torch.distributed.init_process_group(
98
- backend=args.dist_backend,
99
- init_method=args.dist_url,
100
- world_size=args.world_size,
101
- rank=args.rank,
102
- )
103
- elif 'OMPI_COMM_WORLD_SIZE' in os.environ: # using Summit cluster
104
- world_size = int(os.environ['OMPI_COMM_WORLD_SIZE'])
105
- world_rank = int(os.environ['OMPI_COMM_WORLD_RANK'])
106
- local_rank = int(os.environ['OMPI_COMM_WORLD_LOCAL_RANK'])
107
- args.local_rank = local_rank
108
- args.rank = world_rank
109
- args.world_size = world_size
110
- torch.distributed.init_process_group(
111
- backend=args.dist_backend,
112
- init_method=args.dist_url,
113
- world_size=args.world_size,
114
- rank=args.rank,
115
- )
116
- else:
117
- # DDP via torchrun, torch.distributed.launch
118
- args.local_rank, _, _ = world_info_from_env()
119
- torch.distributed.init_process_group(
120
- backend=args.dist_backend,
121
- init_method=args.dist_url)
122
- args.world_size = torch.distributed.get_world_size()
123
- args.rank = torch.distributed.get_rank()
124
- args.distributed = True
125
- print(f"Distributed training: local_rank={args.local_rank}, "
126
- f"rank={args.rank}, world_size={args.world_size}, "
127
- f"hostname={socket.gethostname()}, pid={os.getpid()}")
128
-
129
- if torch.cuda.is_available():
130
- if args.distributed and not args.no_set_device_rank:
131
- device = 'cuda:%d' % args.local_rank
132
- else:
133
- device = 'cuda:0'
134
- torch.cuda.set_device(device)
135
- else:
136
- device = 'cpu'
137
- args.device = device
138
- device = torch.device(device)
139
- return device
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/training/imagenet_zeroshot_data.py DELETED
@@ -1,254 +0,0 @@
1
- # NOTE: This script is currently not supported for CLAP.
2
-
3
- imagenet_classnames = ["tench", "goldfish", "great white shark", "tiger shark", "hammerhead shark", "electric ray",
4
- "stingray", "rooster", "hen", "ostrich", "brambling", "goldfinch", "house finch", "junco",
5
- "indigo bunting", "American robin", "bulbul", "jay", "magpie", "chickadee", "American dipper",
6
- "kite (bird of prey)", "bald eagle", "vulture", "great grey owl", "fire salamander",
7
- "smooth newt", "newt", "spotted salamander", "axolotl", "American bullfrog", "tree frog",
8
- "tailed frog", "loggerhead sea turtle", "leatherback sea turtle", "mud turtle", "terrapin",
9
- "box turtle", "banded gecko", "green iguana", "Carolina anole",
10
- "desert grassland whiptail lizard", "agama", "frilled-necked lizard", "alligator lizard",
11
- "Gila monster", "European green lizard", "chameleon", "Komodo dragon", "Nile crocodile",
12
- "American alligator", "triceratops", "worm snake", "ring-necked snake",
13
- "eastern hog-nosed snake", "smooth green snake", "kingsnake", "garter snake", "water snake",
14
- "vine snake", "night snake", "boa constrictor", "African rock python", "Indian cobra",
15
- "green mamba", "sea snake", "Saharan horned viper", "eastern diamondback rattlesnake",
16
- "sidewinder rattlesnake", "trilobite", "harvestman", "scorpion", "yellow garden spider",
17
- "barn spider", "European garden spider", "southern black widow", "tarantula", "wolf spider",
18
- "tick", "centipede", "black grouse", "ptarmigan", "ruffed grouse", "prairie grouse", "peafowl",
19
- "quail", "partridge", "african grey parrot", "macaw", "sulphur-crested cockatoo", "lorikeet",
20
- "coucal", "bee eater", "hornbill", "hummingbird", "jacamar", "toucan", "duck",
21
- "red-breasted merganser", "goose", "black swan", "tusker", "echidna", "platypus", "wallaby",
22
- "koala", "wombat", "jellyfish", "sea anemone", "brain coral", "flatworm", "nematode", "conch",
23
- "snail", "slug", "sea slug", "chiton", "chambered nautilus", "Dungeness crab", "rock crab",
24
- "fiddler crab", "red king crab", "American lobster", "spiny lobster", "crayfish", "hermit crab",
25
- "isopod", "white stork", "black stork", "spoonbill", "flamingo", "little blue heron",
26
- "great egret", "bittern bird", "crane bird", "limpkin", "common gallinule", "American coot",
27
- "bustard", "ruddy turnstone", "dunlin", "common redshank", "dowitcher", "oystercatcher",
28
- "pelican", "king penguin", "albatross", "grey whale", "killer whale", "dugong", "sea lion",
29
- "Chihuahua", "Japanese Chin", "Maltese", "Pekingese", "Shih Tzu", "King Charles Spaniel",
30
- "Papillon", "toy terrier", "Rhodesian Ridgeback", "Afghan Hound", "Basset Hound", "Beagle",
31
- "Bloodhound", "Bluetick Coonhound", "Black and Tan Coonhound", "Treeing Walker Coonhound",
32
- "English foxhound", "Redbone Coonhound", "borzoi", "Irish Wolfhound", "Italian Greyhound",
33
- "Whippet", "Ibizan Hound", "Norwegian Elkhound", "Otterhound", "Saluki", "Scottish Deerhound",
34
- "Weimaraner", "Staffordshire Bull Terrier", "American Staffordshire Terrier",
35
- "Bedlington Terrier", "Border Terrier", "Kerry Blue Terrier", "Irish Terrier",
36
- "Norfolk Terrier", "Norwich Terrier", "Yorkshire Terrier", "Wire Fox Terrier",
37
- "Lakeland Terrier", "Sealyham Terrier", "Airedale Terrier", "Cairn Terrier",
38
- "Australian Terrier", "Dandie Dinmont Terrier", "Boston Terrier", "Miniature Schnauzer",
39
- "Giant Schnauzer", "Standard Schnauzer", "Scottish Terrier", "Tibetan Terrier",
40
- "Australian Silky Terrier", "Soft-coated Wheaten Terrier", "West Highland White Terrier",
41
- "Lhasa Apso", "Flat-Coated Retriever", "Curly-coated Retriever", "Golden Retriever",
42
- "Labrador Retriever", "Chesapeake Bay Retriever", "German Shorthaired Pointer", "Vizsla",
43
- "English Setter", "Irish Setter", "Gordon Setter", "Brittany dog", "Clumber Spaniel",
44
- "English Springer Spaniel", "Welsh Springer Spaniel", "Cocker Spaniel", "Sussex Spaniel",
45
- "Irish Water Spaniel", "Kuvasz", "Schipperke", "Groenendael dog", "Malinois", "Briard",
46
- "Australian Kelpie", "Komondor", "Old English Sheepdog", "Shetland Sheepdog", "collie",
47
- "Border Collie", "Bouvier des Flandres dog", "Rottweiler", "German Shepherd Dog", "Dobermann",
48
- "Miniature Pinscher", "Greater Swiss Mountain Dog", "Bernese Mountain Dog",
49
- "Appenzeller Sennenhund", "Entlebucher Sennenhund", "Boxer", "Bullmastiff", "Tibetan Mastiff",
50
- "French Bulldog", "Great Dane", "St. Bernard", "husky", "Alaskan Malamute", "Siberian Husky",
51
- "Dalmatian", "Affenpinscher", "Basenji", "pug", "Leonberger", "Newfoundland dog",
52
- "Great Pyrenees dog", "Samoyed", "Pomeranian", "Chow Chow", "Keeshond", "brussels griffon",
53
- "Pembroke Welsh Corgi", "Cardigan Welsh Corgi", "Toy Poodle", "Miniature Poodle",
54
- "Standard Poodle", "Mexican hairless dog (xoloitzcuintli)", "grey wolf", "Alaskan tundra wolf",
55
- "red wolf or maned wolf", "coyote", "dingo", "dhole", "African wild dog", "hyena", "red fox",
56
- "kit fox", "Arctic fox", "grey fox", "tabby cat", "tiger cat", "Persian cat", "Siamese cat",
57
- "Egyptian Mau", "cougar", "lynx", "leopard", "snow leopard", "jaguar", "lion", "tiger",
58
- "cheetah", "brown bear", "American black bear", "polar bear", "sloth bear", "mongoose",
59
- "meerkat", "tiger beetle", "ladybug", "ground beetle", "longhorn beetle", "leaf beetle",
60
- "dung beetle", "rhinoceros beetle", "weevil", "fly", "bee", "ant", "grasshopper",
61
- "cricket insect", "stick insect", "cockroach", "praying mantis", "cicada", "leafhopper",
62
- "lacewing", "dragonfly", "damselfly", "red admiral butterfly", "ringlet butterfly",
63
- "monarch butterfly", "small white butterfly", "sulphur butterfly", "gossamer-winged butterfly",
64
- "starfish", "sea urchin", "sea cucumber", "cottontail rabbit", "hare", "Angora rabbit",
65
- "hamster", "porcupine", "fox squirrel", "marmot", "beaver", "guinea pig", "common sorrel horse",
66
- "zebra", "pig", "wild boar", "warthog", "hippopotamus", "ox", "water buffalo", "bison",
67
- "ram (adult male sheep)", "bighorn sheep", "Alpine ibex", "hartebeest", "impala (antelope)",
68
- "gazelle", "arabian camel", "llama", "weasel", "mink", "European polecat",
69
- "black-footed ferret", "otter", "skunk", "badger", "armadillo", "three-toed sloth", "orangutan",
70
- "gorilla", "chimpanzee", "gibbon", "siamang", "guenon", "patas monkey", "baboon", "macaque",
71
- "langur", "black-and-white colobus", "proboscis monkey", "marmoset", "white-headed capuchin",
72
- "howler monkey", "titi monkey", "Geoffroy's spider monkey", "common squirrel monkey",
73
- "ring-tailed lemur", "indri", "Asian elephant", "African bush elephant", "red panda",
74
- "giant panda", "snoek fish", "eel", "silver salmon", "rock beauty fish", "clownfish",
75
- "sturgeon", "gar fish", "lionfish", "pufferfish", "abacus", "abaya", "academic gown",
76
- "accordion", "acoustic guitar", "aircraft carrier", "airliner", "airship", "altar", "ambulance",
77
- "amphibious vehicle", "analog clock", "apiary", "apron", "trash can", "assault rifle",
78
- "backpack", "bakery", "balance beam", "balloon", "ballpoint pen", "Band-Aid", "banjo",
79
- "baluster / handrail", "barbell", "barber chair", "barbershop", "barn", "barometer", "barrel",
80
- "wheelbarrow", "baseball", "basketball", "bassinet", "bassoon", "swimming cap", "bath towel",
81
- "bathtub", "station wagon", "lighthouse", "beaker", "military hat (bearskin or shako)",
82
- "beer bottle", "beer glass", "bell tower", "baby bib", "tandem bicycle", "bikini",
83
- "ring binder", "binoculars", "birdhouse", "boathouse", "bobsleigh", "bolo tie", "poke bonnet",
84
- "bookcase", "bookstore", "bottle cap", "hunting bow", "bow tie", "brass memorial plaque", "bra",
85
- "breakwater", "breastplate", "broom", "bucket", "buckle", "bulletproof vest",
86
- "high-speed train", "butcher shop", "taxicab", "cauldron", "candle", "cannon", "canoe",
87
- "can opener", "cardigan", "car mirror", "carousel", "tool kit", "cardboard box / carton",
88
- "car wheel", "automated teller machine", "cassette", "cassette player", "castle", "catamaran",
89
- "CD player", "cello", "mobile phone", "chain", "chain-link fence", "chain mail", "chainsaw",
90
- "storage chest", "chiffonier", "bell or wind chime", "china cabinet", "Christmas stocking",
91
- "church", "movie theater", "cleaver", "cliff dwelling", "cloak", "clogs", "cocktail shaker",
92
- "coffee mug", "coffeemaker", "spiral or coil", "combination lock", "computer keyboard",
93
- "candy store", "container ship", "convertible", "corkscrew", "cornet", "cowboy boot",
94
- "cowboy hat", "cradle", "construction crane", "crash helmet", "crate", "infant bed",
95
- "Crock Pot", "croquet ball", "crutch", "cuirass", "dam", "desk", "desktop computer",
96
- "rotary dial telephone", "diaper", "digital clock", "digital watch", "dining table",
97
- "dishcloth", "dishwasher", "disc brake", "dock", "dog sled", "dome", "doormat", "drilling rig",
98
- "drum", "drumstick", "dumbbell", "Dutch oven", "electric fan", "electric guitar",
99
- "electric locomotive", "entertainment center", "envelope", "espresso machine", "face powder",
100
- "feather boa", "filing cabinet", "fireboat", "fire truck", "fire screen", "flagpole", "flute",
101
- "folding chair", "football helmet", "forklift", "fountain", "fountain pen", "four-poster bed",
102
- "freight car", "French horn", "frying pan", "fur coat", "garbage truck",
103
- "gas mask or respirator", "gas pump", "goblet", "go-kart", "golf ball", "golf cart", "gondola",
104
- "gong", "gown", "grand piano", "greenhouse", "radiator grille", "grocery store", "guillotine",
105
- "hair clip", "hair spray", "half-track", "hammer", "hamper", "hair dryer", "hand-held computer",
106
- "handkerchief", "hard disk drive", "harmonica", "harp", "combine harvester", "hatchet",
107
- "holster", "home theater", "honeycomb", "hook", "hoop skirt", "gymnastic horizontal bar",
108
- "horse-drawn vehicle", "hourglass", "iPod", "clothes iron", "carved pumpkin", "jeans", "jeep",
109
- "T-shirt", "jigsaw puzzle", "rickshaw", "joystick", "kimono", "knee pad", "knot", "lab coat",
110
- "ladle", "lampshade", "laptop computer", "lawn mower", "lens cap", "letter opener", "library",
111
- "lifeboat", "lighter", "limousine", "ocean liner", "lipstick", "slip-on shoe", "lotion",
112
- "music speaker", "loupe magnifying glass", "sawmill", "magnetic compass", "messenger bag",
113
- "mailbox", "tights", "one-piece bathing suit", "manhole cover", "maraca", "marimba", "mask",
114
- "matchstick", "maypole", "maze", "measuring cup", "medicine cabinet", "megalith", "microphone",
115
- "microwave oven", "military uniform", "milk can", "minibus", "miniskirt", "minivan", "missile",
116
- "mitten", "mixing bowl", "mobile home", "ford model t", "modem", "monastery", "monitor",
117
- "moped", "mortar and pestle", "graduation cap", "mosque", "mosquito net", "vespa",
118
- "mountain bike", "tent", "computer mouse", "mousetrap", "moving van", "muzzle", "metal nail",
119
- "neck brace", "necklace", "baby pacifier", "notebook computer", "obelisk", "oboe", "ocarina",
120
- "odometer", "oil filter", "pipe organ", "oscilloscope", "overskirt", "bullock cart",
121
- "oxygen mask", "product packet / packaging", "paddle", "paddle wheel", "padlock", "paintbrush",
122
- "pajamas", "palace", "pan flute", "paper towel", "parachute", "parallel bars", "park bench",
123
- "parking meter", "railroad car", "patio", "payphone", "pedestal", "pencil case",
124
- "pencil sharpener", "perfume", "Petri dish", "photocopier", "plectrum", "Pickelhaube",
125
- "picket fence", "pickup truck", "pier", "piggy bank", "pill bottle", "pillow", "ping-pong ball",
126
- "pinwheel", "pirate ship", "drink pitcher", "block plane", "planetarium", "plastic bag",
127
- "plate rack", "farm plow", "plunger", "Polaroid camera", "pole", "police van", "poncho",
128
- "pool table", "soda bottle", "plant pot", "potter's wheel", "power drill", "prayer rug",
129
- "printer", "prison", "missile", "projector", "hockey puck", "punching bag", "purse", "quill",
130
- "quilt", "race car", "racket", "radiator", "radio", "radio telescope", "rain barrel",
131
- "recreational vehicle", "fishing casting reel", "reflex camera", "refrigerator",
132
- "remote control", "restaurant", "revolver", "rifle", "rocking chair", "rotisserie", "eraser",
133
- "rugby ball", "ruler measuring stick", "sneaker", "safe", "safety pin", "salt shaker", "sandal",
134
- "sarong", "saxophone", "scabbard", "weighing scale", "school bus", "schooner", "scoreboard",
135
- "CRT monitor", "screw", "screwdriver", "seat belt", "sewing machine", "shield", "shoe store",
136
- "shoji screen / room divider", "shopping basket", "shopping cart", "shovel", "shower cap",
137
- "shower curtain", "ski", "balaclava ski mask", "sleeping bag", "slide rule", "sliding door",
138
- "slot machine", "snorkel", "snowmobile", "snowplow", "soap dispenser", "soccer ball", "sock",
139
- "solar thermal collector", "sombrero", "soup bowl", "keyboard space bar", "space heater",
140
- "space shuttle", "spatula", "motorboat", "spider web", "spindle", "sports car", "spotlight",
141
- "stage", "steam locomotive", "through arch bridge", "steel drum", "stethoscope", "scarf",
142
- "stone wall", "stopwatch", "stove", "strainer", "tram", "stretcher", "couch", "stupa",
143
- "submarine", "suit", "sundial", "sunglasses", "sunglasses", "sunscreen", "suspension bridge",
144
- "mop", "sweatshirt", "swim trunks / shorts", "swing", "electrical switch", "syringe",
145
- "table lamp", "tank", "tape player", "teapot", "teddy bear", "television", "tennis ball",
146
- "thatched roof", "front curtain", "thimble", "threshing machine", "throne", "tile roof",
147
- "toaster", "tobacco shop", "toilet seat", "torch", "totem pole", "tow truck", "toy store",
148
- "tractor", "semi-trailer truck", "tray", "trench coat", "tricycle", "trimaran", "tripod",
149
- "triumphal arch", "trolleybus", "trombone", "hot tub", "turnstile", "typewriter keyboard",
150
- "umbrella", "unicycle", "upright piano", "vacuum cleaner", "vase", "vaulted or arched ceiling",
151
- "velvet fabric", "vending machine", "vestment", "viaduct", "violin", "volleyball",
152
- "waffle iron", "wall clock", "wallet", "wardrobe", "military aircraft", "sink",
153
- "washing machine", "water bottle", "water jug", "water tower", "whiskey jug", "whistle",
154
- "hair wig", "window screen", "window shade", "Windsor tie", "wine bottle", "airplane wing",
155
- "wok", "wooden spoon", "wool", "split-rail fence", "shipwreck", "sailboat", "yurt", "website",
156
- "comic book", "crossword", "traffic or street sign", "traffic light", "dust jacket", "menu",
157
- "plate", "guacamole", "consomme", "hot pot", "trifle", "ice cream", "popsicle", "baguette",
158
- "bagel", "pretzel", "cheeseburger", "hot dog", "mashed potatoes", "cabbage", "broccoli",
159
- "cauliflower", "zucchini", "spaghetti squash", "acorn squash", "butternut squash", "cucumber",
160
- "artichoke", "bell pepper", "cardoon", "mushroom", "Granny Smith apple", "strawberry", "orange",
161
- "lemon", "fig", "pineapple", "banana", "jackfruit", "cherimoya (custard apple)", "pomegranate",
162
- "hay", "carbonara", "chocolate syrup", "dough", "meatloaf", "pizza", "pot pie", "burrito",
163
- "red wine", "espresso", "tea cup", "eggnog", "mountain", "bubble", "cliff", "coral reef",
164
- "geyser", "lakeshore", "promontory", "sandbar", "beach", "valley", "volcano", "baseball player",
165
- "bridegroom", "scuba diver", "rapeseed", "daisy", "yellow lady's slipper", "corn", "acorn",
166
- "rose hip", "horse chestnut seed", "coral fungus", "agaric", "gyromitra", "stinkhorn mushroom",
167
- "earth star fungus", "hen of the woods mushroom", "bolete", "corn cob", "toilet paper"]
168
-
169
-
170
-
171
-
172
-
173
- openai_imagenet_template = [
174
- lambda c: f'a bad photo of a {c}.',
175
- lambda c: f'a photo of many {c}.',
176
- lambda c: f'a sculpture of a {c}.',
177
- lambda c: f'a photo of the hard to see {c}.',
178
- lambda c: f'a low resolution photo of the {c}.',
179
- lambda c: f'a rendering of a {c}.',
180
- lambda c: f'graffiti of a {c}.',
181
- lambda c: f'a bad photo of the {c}.',
182
- lambda c: f'a cropped photo of the {c}.',
183
- lambda c: f'a tattoo of a {c}.',
184
- lambda c: f'the embroidered {c}.',
185
- lambda c: f'a photo of a hard to see {c}.',
186
- lambda c: f'a bright photo of a {c}.',
187
- lambda c: f'a photo of a clean {c}.',
188
- lambda c: f'a photo of a dirty {c}.',
189
- lambda c: f'a dark photo of the {c}.',
190
- lambda c: f'a drawing of a {c}.',
191
- lambda c: f'a photo of my {c}.',
192
- lambda c: f'the plastic {c}.',
193
- lambda c: f'a photo of the cool {c}.',
194
- lambda c: f'a close-up photo of a {c}.',
195
- lambda c: f'a black and white photo of the {c}.',
196
- lambda c: f'a painting of the {c}.',
197
- lambda c: f'a painting of a {c}.',
198
- lambda c: f'a pixelated photo of the {c}.',
199
- lambda c: f'a sculpture of the {c}.',
200
- lambda c: f'a bright photo of the {c}.',
201
- lambda c: f'a cropped photo of a {c}.',
202
- lambda c: f'a plastic {c}.',
203
- lambda c: f'a photo of the dirty {c}.',
204
- lambda c: f'a jpeg corrupted photo of a {c}.',
205
- lambda c: f'a blurry photo of the {c}.',
206
- lambda c: f'a photo of the {c}.',
207
- lambda c: f'a good photo of the {c}.',
208
- lambda c: f'a rendering of the {c}.',
209
- lambda c: f'a {c} in a video game.',
210
- lambda c: f'a photo of one {c}.',
211
- lambda c: f'a doodle of a {c}.',
212
- lambda c: f'a close-up photo of the {c}.',
213
- lambda c: f'a photo of a {c}.',
214
- lambda c: f'the origami {c}.',
215
- lambda c: f'the {c} in a video game.',
216
- lambda c: f'a sketch of a {c}.',
217
- lambda c: f'a doodle of the {c}.',
218
- lambda c: f'a origami {c}.',
219
- lambda c: f'a low resolution photo of a {c}.',
220
- lambda c: f'the toy {c}.',
221
- lambda c: f'a rendition of the {c}.',
222
- lambda c: f'a photo of the clean {c}.',
223
- lambda c: f'a photo of a large {c}.',
224
- lambda c: f'a rendition of a {c}.',
225
- lambda c: f'a photo of a nice {c}.',
226
- lambda c: f'a photo of a weird {c}.',
227
- lambda c: f'a blurry photo of a {c}.',
228
- lambda c: f'a cartoon {c}.',
229
- lambda c: f'art of a {c}.',
230
- lambda c: f'a sketch of the {c}.',
231
- lambda c: f'a embroidered {c}.',
232
- lambda c: f'a pixelated photo of a {c}.',
233
- lambda c: f'itap of the {c}.',
234
- lambda c: f'a jpeg corrupted photo of the {c}.',
235
- lambda c: f'a good photo of a {c}.',
236
- lambda c: f'a plushie {c}.',
237
- lambda c: f'a photo of the nice {c}.',
238
- lambda c: f'a photo of the small {c}.',
239
- lambda c: f'a photo of the weird {c}.',
240
- lambda c: f'the cartoon {c}.',
241
- lambda c: f'art of the {c}.',
242
- lambda c: f'a drawing of the {c}.',
243
- lambda c: f'a photo of the large {c}.',
244
- lambda c: f'a black and white photo of a {c}.',
245
- lambda c: f'the plushie {c}.',
246
- lambda c: f'a dark photo of a {c}.',
247
- lambda c: f'itap of a {c}.',
248
- lambda c: f'graffiti of the {c}.',
249
- lambda c: f'a toy {c}.',
250
- lambda c: f'itap of my {c}.',
251
- lambda c: f'a photo of a cool {c}.',
252
- lambda c: f'a photo of a small {c}.',
253
- lambda c: f'a tattoo of the {c}.',
254
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/training/infer_demo.py DELETED
@@ -1,92 +0,0 @@
1
- import torch
2
- import librosa
3
- from clap_module import create_model
4
- from training.data import get_audio_features
5
- from training.data import int16_to_float32, float32_to_int16
6
- from transformers import RobertaTokenizer
7
-
8
- tokenize = RobertaTokenizer.from_pretrained('roberta-base')
9
- def tokenizer(text):
10
- result = tokenize(
11
- text,
12
- padding="max_length",
13
- truncation=True,
14
- max_length=77,
15
- return_tensors="pt",
16
- )
17
- return {k: v.squeeze(0) for k, v in result.items()}
18
-
19
- def infer_text():
20
- device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
21
- precision = 'fp32'
22
- amodel = 'HTSAT-tiny' # or 'PANN-14'
23
- tmodel = 'roberta' # the best text encoder in our training
24
- enable_fusion = True # False if you do not want to use the fusion model
25
- fusion_type = 'aff_2d'
26
- pretrained = "/home/la/kechen/Research/KE_CLAP/ckpt/fusion_best.pt" # the checkpoint name, the unfusion model can also be loaded.
27
-
28
- model, model_cfg = create_model(
29
- amodel,
30
- tmodel,
31
- pretrained,
32
- precision=precision,
33
- device=device,
34
- enable_fusion=enable_fusion,
35
- fusion_type=fusion_type
36
- )
37
- # load the text, can be a list (i.e. batch size)
38
- text_data = ["I love the contrastive learning", "I love the pretrain model"]
39
- # tokenize for roberta, if you want to tokenize for another text encoder, please refer to data.py#L43-90
40
- text_data = tokenizer(text_data)
41
- model.eval()
42
- text_embed = model.get_text_embedding(text_data)
43
- text_embed = text_embed.detach().cpu().numpy()
44
- print(text_embed)
45
- print(text_embed.shape)
46
-
47
- def infer_audio():
48
-
49
- device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
50
- precision = 'fp32'
51
- amodel = 'HTSAT-tiny' # or 'PANN-14'
52
- tmodel = 'roberta' # the best text encoder in our training
53
- enable_fusion = True # False if you do not want to use the fusion model
54
- fusion_type = 'aff_2d'
55
- pretrained = "/home/la/kechen/Research/KE_CLAP/ckpt/fusion_best.pt" # the checkpoint name, the unfusion model can also be loaded.
56
-
57
- model, model_cfg = create_model(
58
- amodel,
59
- tmodel,
60
- pretrained,
61
- precision=precision,
62
- device=device,
63
- enable_fusion=enable_fusion,
64
- fusion_type=fusion_type
65
- )
66
-
67
- # load the waveform of the shape (T,), should resample to 48000
68
- audio_waveform, sr = librosa.load('/home/la/kechen/Research/KE_CLAP/ckpt/test_clap_short.wav', sr=48000)
69
- # quantize
70
- audio_waveform = int16_to_float32(float32_to_int16(audio_waveform))
71
- audio_waveform = torch.from_numpy(audio_waveform).float()
72
- audio_dict = {}
73
-
74
- # the 'fusion' truncate mode can be changed to 'rand_trunc' if run in unfusion mode
75
- audio_dict = get_audio_features(
76
- audio_dict, audio_waveform, 480000,
77
- data_truncating='fusion',
78
- data_filling='repeatpad',
79
- audio_cfg=model_cfg['audio_cfg']
80
- )
81
- model.eval()
82
- # can send a list to the model, to process many audio tracks in one time (i.e. batch size)
83
- audio_embed = model.get_audio_embedding([audio_dict])
84
- audio_embed = audio_embed.detach().cpu().numpy()
85
- print(audio_embed)
86
- print(audio_embed.shape)
87
-
88
-
89
-
90
- if __name__ == "__main__":
91
- infer_text()
92
- # infer_audio()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/training/logger.py DELETED
@@ -1,26 +0,0 @@
1
- import logging
2
-
3
-
4
- def setup_logging(log_file, level, include_host=False):
5
- if include_host:
6
- import socket
7
- hostname = socket.gethostname()
8
- formatter = logging.Formatter(
9
- f'%(asctime)s | {hostname} | %(levelname)s | %(message)s', datefmt='%Y-%m-%d,%H:%M:%S')
10
- else:
11
- formatter = logging.Formatter('%(asctime)s | %(levelname)s | %(message)s', datefmt='%Y-%m-%d,%H:%M:%S')
12
-
13
- logging.root.setLevel(level)
14
- loggers = [logging.getLogger(name) for name in logging.root.manager.loggerDict]
15
- for logger in loggers:
16
- logger.setLevel(level)
17
-
18
- stream_handler = logging.StreamHandler()
19
- stream_handler.setFormatter(formatter)
20
- logging.root.addHandler(stream_handler)
21
-
22
- if log_file:
23
- file_handler = logging.FileHandler(filename=log_file)
24
- file_handler.setFormatter(formatter)
25
- logging.root.addHandler(file_handler)
26
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/training/lp_main.py DELETED
@@ -1,643 +0,0 @@
1
- import logging
2
- import os
3
- import random
4
- from datetime import datetime
5
- import copy
6
- import numpy as np
7
- import torch
8
- import torch.backends.cudnn as cudnn
9
- from torch.cuda.amp import GradScaler
10
- import time
11
-
12
- try:
13
- import wandb
14
- except ImportError:
15
- wandb = None
16
-
17
- try:
18
- import torch.utils.tensorboard as tensorboard
19
- except ImportError:
20
- tensorboard = None
21
-
22
- try:
23
- import horovod.torch as hvd
24
- except ImportError:
25
- hvd = None
26
-
27
- from clap_module import create_model_and_transforms, trace_model, create_model
28
- from training.data import get_data
29
- from training.params import parse_args
30
- from training.distributed import is_master, init_distributed_device, world_info_from_env
31
- from training.logger import setup_logging
32
- from training.scheduler import cosine_lr
33
- from training.lp_train import train_one_epoch, evaluate
34
- from clap_module.utils import get_tar_path_from_dataset_name, dataset_split, get_optimizer
35
- from clap_module.utils import load_p, load_class_label
36
- from clap_module.linear_probe import LinearProbe
37
-
38
-
39
- def maintain_ckpts(args, startidx, all_idx_len):
40
- for i in reversed(range(startidx, all_idx_len)):
41
- if os.path.exists(os.path.join(args.checkpoint_path, f"epoch_top_{i}.pt")):
42
- os.rename(
43
- os.path.join(args.checkpoint_path, f"epoch_top_{i}.pt"),
44
- os.path.join(args.checkpoint_path, f"epoch_top_{i+1}.pt"),
45
- )
46
- if os.path.exists(
47
- os.path.join(args.checkpoint_path, f"epoch_top_{all_idx_len}.pt")
48
- ):
49
- os.remove(os.path.join(args.checkpoint_path, f"epoch_top_{all_idx_len}.pt"))
50
- return
51
-
52
-
53
- def update_top_k_performance(
54
- new_metrics_inputs, current_top_k_ckpt_metrics, args, ckpt, bignumbetter=True
55
- ):
56
- """
57
- Record the top-k performance of the current epoch.
58
- current_top_k_metrics is a dictionary of the form: {1: top_1_ckpt_measure, 2: top_2_ckpt_measure, ...}
59
- """
60
- if isinstance(new_metrics_inputs, (list, tuple)):
61
- new_metrics_inputs = np.mean(new_metrics_inputs)
62
- return update_top_k_performance(
63
- new_metrics_inputs,
64
- current_top_k_ckpt_metrics,
65
- args=args,
66
- ckpt=ckpt,
67
- bignumbetter=bignumbetter,
68
- )
69
- elif isinstance(new_metrics_inputs, dict):
70
- new_metrics_inputs = np.mean(list(new_metrics_inputs.values()))
71
- return update_top_k_performance(
72
- new_metrics_inputs,
73
- current_top_k_ckpt_metrics,
74
- args=args,
75
- ckpt=ckpt,
76
- bignumbetter=bignumbetter,
77
- )
78
- elif isinstance(new_metrics_inputs, (float, int)):
79
- update_flag = {k: False for k in current_top_k_ckpt_metrics.keys()}
80
- sorted_keys = sorted(current_top_k_ckpt_metrics.keys())
81
- sorted_values = sorted(
82
- current_top_k_ckpt_metrics.values(), reverse=bignumbetter
83
- )
84
- sorted_values_ = copy.deepcopy(sorted_values)
85
- sorted_values.append(new_metrics_inputs)
86
- sorted_values = sorted(sorted_values, reverse=bignumbetter)
87
- sorted_values = sorted_values[:-1]
88
-
89
- if sorted_values == sorted_values_:
90
- return current_top_k_ckpt_metrics, new_metrics_inputs
91
- else:
92
- for i in range(len(sorted_keys)):
93
- if current_top_k_ckpt_metrics[sorted_keys[i]] != sorted_values[i]:
94
- current_top_k_ckpt_metrics[sorted_keys[i]] = sorted_values[i]
95
- update_flag[sorted_keys[i]] = True
96
- for i in range(len(update_flag)):
97
- if update_flag[i]:
98
- maintain_ckpts(args, i, len(sorted_keys))
99
- torch.save(
100
- ckpt,
101
- os.path.join(args.checkpoint_path, f"epoch_top_{i}.pt"),
102
- )
103
- break
104
- return current_top_k_ckpt_metrics, new_metrics_inputs
105
-
106
-
107
- # def updateifNone(a, b):
108
- # a = b if None else a
109
- # return a
110
-
111
-
112
- def is_pretrained_params(n):
113
- return (
114
- n.startswith("clap_model.transformer")
115
- or n in ["clap_model.positional_embedding", "clap_model.text_projection"]
116
- or n.startswith("clap_model.token_embedding")
117
- or n.startswith("clap_model.ln_final")
118
- or n.startswith("clap_model.logit_scale_t")
119
- )
120
-
121
-
122
- def random_seed(seed=42, rank=0):
123
- torch.manual_seed(seed + rank)
124
- np.random.seed(seed + rank)
125
- random.seed(seed + rank)
126
-
127
- def config_lp_optimizer(model, data, args):
128
- # set wd-related params to 0 if use adam optimizer
129
- if args.optimizer == "adam":
130
- args.wd = 0
131
- args.wd_pretrained = 0
132
- args.wd_new = 0
133
-
134
- in_clap = (
135
- lambda n, p: n.startswith("clap_model")
136
- )
137
-
138
- named_parameters = list(model.named_parameters())
139
-
140
- optimizer = {}
141
- scheduler = {}
142
-
143
- # freeze text encoder
144
- text_freeze_parameters = [
145
- p
146
- for n, p in named_parameters
147
- if n.startswith("clap_model.transformer")
148
- or n in ["clap_model.positional_embedding", "clap_model.text_projection"]
149
- or n.startswith("clap_model.token_embedding")
150
- or n.startswith("clap_model.ln_final")
151
- ]
152
-
153
- if args.freeze_text:
154
- logging.info("Freeze Text!!!!")
155
- for k in text_freeze_parameters:
156
- k.requires_grad = False
157
-
158
- if not args.lp_freeze:
159
- exclude = (
160
- lambda n, p: p.ndim < 2
161
- or "bn" in n
162
- or "ln" in n
163
- or "bias" in n
164
- or "logit_scale" in n
165
- )
166
- include = lambda n, p: not exclude(n, p)
167
-
168
- # (yusong): we do not split the learning rate anymore
169
- # p for n, p in named_parameters if in_clap(n,p) and exclude(n, p) and p.requires_grad
170
- gain_or_bias_params = [
171
- p for n, p in named_parameters if exclude(n, p) and p.requires_grad
172
- ]
173
- # rest_params = [p for n, p in named_parameters if in_clap(n,p) and include(n, p) and p.requires_grad]
174
- rest_params = [p for n, p in named_parameters if include(n, p) and p.requires_grad]
175
-
176
- if args.train_data is None:
177
- optimizer = None
178
- scheduler = None
179
- else:
180
- total_steps = data["train"].dataloader.num_batches * args.epochs
181
-
182
- if args.split_opt:
183
- for x in ["lr", "beta1", "beta2", "eps", "wd"]:
184
- for y in ["_new", "_pretrained"]:
185
- if getattr(args, x + y) is None:
186
- setattr(args, x + y, getattr(args, x))
187
-
188
- gain_or_bias_pretrained_params = [
189
- p
190
- for n, p in named_parameters
191
- if (exclude(n, p) and p.requires_grad) and is_pretrained_params(n)
192
- ]
193
- rest_pretrained_params = [
194
- p
195
- for n, p in named_parameters
196
- if (include(n, p) and p.requires_grad) and is_pretrained_params(n)
197
- ]
198
- gain_or_bias_new_params = [
199
- p
200
- for n, p in named_parameters
201
- if (exclude(n, p) and p.requires_grad) and (not is_pretrained_params(n))
202
- ]
203
- rest_new_params = [
204
- p
205
- for n, p in named_parameters
206
- if (include(n, p) and p.requires_grad) and (not is_pretrained_params(n))
207
- ]
208
-
209
- pretrained_params_optimizer = get_optimizer(
210
- [
211
- {"params": gain_or_bias_pretrained_params, "weight_decay": 0.0},
212
- {
213
- "params": rest_pretrained_params,
214
- "weight_decay": args.wd_pretrained,
215
- },
216
- ],
217
- lr=args.lr_pretrained,
218
- betas=(args.beta1_pretrained, args.beta2_pretrained),
219
- eps=args.eps_pretrained,
220
- momentum=args.momentum_pretrained,
221
- optimizer_name=args.optimizer,
222
- )
223
- pretrained_params_scheduler = cosine_lr(
224
- pretrained_params_optimizer,
225
- args.lr_pretrained,
226
- args.warmup,
227
- total_steps,
228
- )
229
-
230
- new_params_optimizer = get_optimizer(
231
- [
232
- {"params": gain_or_bias_new_params, "weight_decay": 0.0},
233
- {"params": rest_new_params, "weight_decay": args.wd_new},
234
- ],
235
- lr=args.lr_new,
236
- betas=(args.beta1_new, args.beta2_new),
237
- eps=args.eps_new,
238
- momentum=args.momentum_new,
239
- optimizer_name=args.optimizer,
240
- )
241
- new_params_scheduler = cosine_lr(
242
- new_params_optimizer, args.lr_new, args.warmup, total_steps
243
- )
244
-
245
- optimizer["text"] = pretrained_params_optimizer
246
- optimizer["audio"] = new_params_optimizer
247
- scheduler["text"] = pretrained_params_scheduler
248
- scheduler["audio"] = new_params_scheduler
249
-
250
- if args.horovod:
251
- pretrained_params_optimizer = hvd.DistributedOptimizer(
252
- pretrained_params_optimizer,
253
- named_parameters=model.named_parameters(),
254
- )
255
- new_params_optimizer = hvd.DistributedOptimizer(
256
- new_params_optimizer, named_parameters=model.named_parameters()
257
- )
258
- hvd.broadcast_parameters(model.state_dict(), root_rank=0)
259
- hvd.broadcast_optimizer_state(pretrained_params_optimizer, root_rank=0)
260
- hvd.broadcast_optimizer_state(new_params_optimizer, root_rank=0)
261
- else:
262
-
263
- optimizer["clap"] = get_optimizer(
264
- [
265
- {"params": gain_or_bias_params, "weight_decay": 0.0},
266
- {"params": rest_params, "weight_decay": args.wd},
267
- ],
268
- lr=args.lr,
269
- betas=(args.beta1, args.beta2),
270
- eps=args.eps,
271
- momentum=args.momentum,
272
- optimizer_name=args.optimizer,
273
- )
274
- scheduler["clap"] = cosine_lr(optimizer["clap"], args.lr, args.warmup, total_steps)
275
-
276
- if args.horovod:
277
- optimizer["clap"] = hvd.DistributedOptimizer(
278
- optimizer["clap"], named_parameters=model.named_parameters()
279
- )
280
- hvd.broadcast_parameters(model.state_dict(), root_rank=0)
281
- hvd.broadcast_optimizer_state(optimizer["clap"], root_rank=0)
282
-
283
- # linear probe optimizer
284
- else:
285
- lp_params = [p for n, p in named_parameters if (not in_clap(n, p)) and p.requires_grad]
286
- lp_optim = get_optimizer(lp_params, lr=args.lp_lr, betas=(args.beta1, args.beta2), eps=args.eps, momentum=0.9,
287
- optimizer_name=args.optimizer)
288
- optimizer["lp"] = lp_optim
289
-
290
- return optimizer, scheduler, text_freeze_parameters
291
-
292
-
293
- def main():
294
- args = parse_args()
295
-
296
- time.sleep(args.sleep)
297
-
298
- # sanitize model name for filesystem / uri use, easier if we don't use / in name as a rule?
299
- args.amodel = args.amodel.replace("/", "-")
300
- # download sizes.json file
301
-
302
- # (yusong): the below two lines are for debug
303
- # print("setting up faulthandler")
304
- # faulthandler.register(10)
305
-
306
- random.seed(args.seed)
307
- torch.manual_seed(args.seed)
308
- torch.cuda.manual_seed(args.seed)
309
- torch.cuda.manual_seed_all(args.seed)
310
- np.random.seed(args.seed)
311
- args.class_index_dict = load_class_label(args.class_label_path)
312
-
313
- # get the name of the experiments
314
- if args.name is None:
315
- args.name = "-".join(
316
- [
317
- datetime.now().strftime("%Y_%m_%d-%H_%M_%S"),
318
- f"linear_probe"
319
- f"model_{args.amodel}",
320
- f"lr_{args.lr}",
321
- f"b_{args.batch_size}",
322
- f"j_{args.workers}",
323
- f"p_{args.precision}",
324
- ]
325
- )
326
-
327
- # discover initial world args early so we can log properly
328
- args.distributed = False
329
- args.local_rank, args.rank, args.world_size = world_info_from_env()
330
-
331
- if args.remotedata and is_master(args):
332
- for dataset_name in args.datasetnames:
333
- for split in dataset_split[dataset_name]:
334
- if not os.path.exists(f"./json_files/{dataset_name}/{split}"):
335
- os.makedirs(f"./json_files/{dataset_name}/{split}")
336
- os.system(
337
- f"aws s3 cp s3://s-laion-audio/webdataset_tar/{dataset_name}/{split}/sizes.json ./json_files/{dataset_name}/{split}/sizes.json"
338
- )
339
-
340
- args.log_path = None
341
- if is_master(args, local=args.log_local):
342
- log_base_path = os.path.join(args.logs, args.name)
343
- os.makedirs(log_base_path, exist_ok=True)
344
- log_filename = f"out-{args.rank}" if args.log_local else "out.log"
345
- args.log_path = os.path.join(log_base_path, log_filename)
346
-
347
- # avoid log dir in same name:
348
- postfix = 0
349
- while os.path.exists(args.log_path):
350
- postfix += 1
351
- log_base_path_new = log_base_path+'-'+str(postfix)
352
- os.makedirs(log_base_path_new, exist_ok=True)
353
- log_filename = f"out-{args.rank}" if args.log_local else "out.log"
354
- args.log_path = os.path.join(log_base_path_new, log_filename)
355
- # print(
356
- # "Error. Experiment already exists. Use --name {} to specify a new experiment."
357
- # )
358
- # return -1
359
-
360
- # Set logger
361
- args.log_level = logging.DEBUG if args.debug else logging.INFO
362
- setup_logging(args.log_path, args.log_level)
363
-
364
- # fully initialize distributed device environment
365
- device = init_distributed_device(args)
366
-
367
- args.wandb = "wandb" in args.report_to or "all" in args.report_to
368
- args.tensorboard = "tensorboard" in args.report_to or "all" in args.report_to
369
- if is_master(args):
370
- args.tensorboard_path = (
371
- os.path.join(args.logs, args.name, "tensorboard")
372
- if args.tensorboard
373
- else ""
374
- )
375
- args.checkpoint_path = os.path.join(args.logs, args.name, "checkpoints")
376
- for dirname in [args.tensorboard_path, args.checkpoint_path]:
377
- if dirname:
378
- os.makedirs(dirname, exist_ok=True)
379
- else:
380
- args.tensorboard_path = ""
381
- args.checkpoint_path = ""
382
-
383
- if args.copy_codebase:
384
- copy_codebase(args)
385
-
386
- assert args.precision in ["amp", "fp16", "fp32"]
387
- if args.precision == "fp16":
388
- logging.warning(
389
- "It is recommended to use AMP mixed-precision instead of FP16. "
390
- "FP16 support needs further verification and tuning, especially for train."
391
- )
392
-
393
- if args.horovod:
394
- logging.info(
395
- f"Running in horovod mode with multiple processes / nodes. Device: {args.device}."
396
- f"Process (global: {args.rank}, local {args.local_rank}), total {args.world_size}."
397
- )
398
- elif args.distributed:
399
- logging.info(
400
- f"Running in distributed mode with multiple processes. Device: {args.device}."
401
- f"Process (global: {args.rank}, local {args.local_rank}), total {args.world_size}."
402
- )
403
- else:
404
- logging.info(f"Running with a single process. Device {args.device}.")
405
-
406
- logging.info(f'openai cache dir: {os.path.expanduser(args.openai_model_cache_dir)}')
407
-
408
- # Create CLAP model
409
- clap_model, clap_model_cfg = create_model(
410
- args.amodel,
411
- args.tmodel,
412
- args.pretrained,
413
- precision=args.precision,
414
- device=device,
415
- jit=args.torchscript,
416
- force_quick_gelu=args.force_quick_gelu,
417
- openai_model_cache_dir=os.path.expanduser(args.openai_model_cache_dir),
418
- skip_params=False,
419
- pretrained_audio=args.pretrained_audio,
420
- pretrained_text=args.pretrained_text,
421
- enable_fusion=args.enable_fusion,
422
- fusion_type=args.fusion_type
423
- )
424
-
425
- args.lp_out_ch = len(list(args.class_index_dict.keys()))
426
- # Linear Probe
427
- logging.info(f"linear probe using mlp: {args.lp_mlp}")
428
- logging.info(f"linear probe using freeze: {args.lp_freeze}")
429
- logging.info(f"linear probe act layer: {args.lp_act}")
430
- logging.info(f"linear probe out ch: {args.lp_out_ch}")
431
- logging.info(f"linear probe learning rate (if applicable): {args.lp_lr}")
432
- logging.info(f"linear probe loss func: {args.lp_loss}")
433
- logging.info(f"linear probe lp_metrics: {args.lp_metrics}")
434
-
435
- model = LinearProbe(
436
- clap_model,
437
- mlp=args.lp_mlp, freeze=args.lp_freeze,
438
- in_ch=512, out_ch=args.lp_out_ch,
439
- act=args.lp_act
440
- ) # in_ch is fixed (i.e., 512)
441
- model = model.to(device)
442
-
443
- if args.horovod:
444
- with torch.no_grad():
445
- for param in model.parameters():
446
- param.set_(param.contiguous())
447
-
448
- if args.trace:
449
- model = trace_model(model, batch_size=args.batch_size, device=device)
450
-
451
- if is_master(args):
452
- logging.info("Linear Probe CLAP Model:")
453
- logging.info(f"{str(clap_model)}")
454
- logging.info("Params:")
455
- params_file = os.path.join(args.logs, args.name, "params.txt")
456
- with open(params_file, "w") as f:
457
- for name in sorted(vars(args)):
458
- val = getattr(args, name)
459
- logging.info(f" {name}: {val}")
460
- f.write(f"{name}: {val}\n")
461
-
462
- if args.distributed and not args.horovod:
463
- if args.use_bn_sync:
464
- model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model)
465
- ddp_args = {}
466
- if args.ddp_static_graph:
467
- # this doesn't exist in older PyTorch, arg only added if enabled
468
- ddp_args["static_graph"] = True
469
- model = torch.nn.parallel.DistributedDataParallel(
470
- model, device_ids=[device], find_unused_parameters=True, **ddp_args
471
- )
472
-
473
- data = get_data(args, clap_model_cfg)
474
- assert len(data), "At least one train or eval dataset must be specified."
475
- if args.trace:
476
- assert "train" not in data, "Cannot train with traced model"
477
-
478
-
479
- optimizer, scheduler, text_freeze_parameters = config_lp_optimizer(model, data, args)
480
-
481
-
482
- scaler = GradScaler() if args.precision == "amp" else None
483
-
484
- # optionally resume from a checkpoint
485
- start_epoch = 0
486
- if args.resume is not None:
487
- if os.path.isfile(args.resume):
488
- checkpoint = torch.load(args.resume, map_location=device)
489
- if "epoch" in checkpoint:
490
- # resuming a train checkpoint w/ epoch and optimizer state
491
- start_epoch = checkpoint["epoch"]
492
- sd = checkpoint["state_dict"]
493
- if not args.distributed and next(iter(sd.items()))[0].startswith(
494
- "module"
495
- ):
496
- sd = {k[len("module.") :]: v for k, v in sd.items()}
497
- model.load_state_dict(sd)
498
- if args.split_opt:
499
- if optimizer is not None:
500
- for k, o_ in optimizer.items():
501
- o_.load_state_dict(checkpoint[k + "_" + "optimizer"])
502
- if optimizer is not None:
503
- optimizer.load_state_dict(checkpoint["optimizer"])
504
- if scaler is not None and "scaler" in checkpoint:
505
- scaler.load_state_dict(checkpoint["scaler"])
506
- logging.info(
507
- f"=> resuming checkpoint '{args.resume}' (epoch {start_epoch})"
508
- )
509
- else:
510
- # loading a bare (model only) checkpoint for fine-tune or evaluation
511
- model.load_state_dict(checkpoint)
512
- logging.info(
513
- f"=> loaded checkpoint '{args.resume}' (epoch {start_epoch})"
514
- )
515
- if args.freeze_text:
516
- print("Freeze Text!!!!")
517
- for k in text_freeze_parameters:
518
- k.requires_grad = False
519
- else:
520
- logging.info("=> no checkpoint found at '{}'".format(args.resume))
521
-
522
- cudnn.benchmark = True
523
- cudnn.deterministic = False
524
-
525
- # determine if this worker should save logs and checkpoints. only do so if it is rank == 0
526
- args.save_logs = args.logs and args.logs.lower() != "none" and is_master(args)
527
- writer = None
528
- if args.save_logs and args.tensorboard:
529
- assert tensorboard is not None, "Please install tensorboard."
530
- writer = tensorboard.SummaryWriter(args.tensorboard_path)
531
-
532
- if args.wandb and is_master(args):
533
- assert wandb is not None, "Please install wandb."
534
- logging.debug("Starting wandb.")
535
- args.train_sz = data["train"].dataloader.num_samples
536
- if args.val_data is not None:
537
- args.val_sz = data["val"].dataloader.num_samples
538
- # you will have to configure this for your project!
539
- wandb.init(
540
- project="clap",
541
- notes=args.wandb_notes,
542
- name=args.wandb_notes,
543
- tags=[],
544
- config=vars(args),
545
- )
546
- if args.debug:
547
- wandb.watch(model, log="all")
548
- wandb.save(params_file)
549
- logging.debug("Finished loading wandb.")
550
-
551
- if "train" not in data:
552
- evaluate(model, data, start_epoch, args, writer)
553
- return
554
- elif start_epoch == 0 and "val" in data and not args.no_eval:
555
- evaluate(model, data, 0, args, writer)
556
- if args.save_top_performance:
557
- current_top_k_ckpt_metrics = {
558
- i: 0 for i in range(args.save_top_performance)
559
- } # initialize the top-k metric for ckpts to 0
560
-
561
- for epoch in range(start_epoch, args.epochs):
562
- # freeze the text param after (include) args.freeze_text_after, this is -1 by default
563
- if epoch == args.freeze_text_after:
564
- print("Text pretrained parameters are freezed since this epoch.")
565
- for k in text_freeze_parameters:
566
- k.requires_grad = False
567
- if is_master(args):
568
- logging.info(f"Start epoch {epoch}")
569
-
570
- train_one_epoch(model, data, epoch, optimizer, scaler, scheduler, args, writer)
571
- completed_epoch = epoch + 1
572
-
573
- if any(v in data for v in ("val", "imagenet-val", "imagenet-v2")) and not args.no_eval:
574
- metrics = evaluate(model, data, completed_epoch, args, writer)
575
- if args.save_top_performance:
576
- top_k_dataset = args.top_k_checkpoint_select_dataset
577
- top_k_metric = args.top_k_checkpoint_select_metric
578
- filtered_metrics = [
579
- v
580
- for k, v in metrics.items()
581
- if top_k_metric in k and top_k_dataset in k
582
- ] # check all R@10 metrics (all dataset) and use it to update the ckpt
583
- # Saving checkpoints.
584
- if args.save_logs:
585
- opt_dict = {
586
- k + "_" + "optimizer": v.state_dict() for k, v in optimizer.items()
587
- }
588
- checkpoint_dict = {
589
- "epoch": completed_epoch,
590
- "name": args.name,
591
- "state_dict": model.state_dict(),
592
- }
593
- checkpoint_dict.update(opt_dict)
594
- if scaler is not None:
595
- checkpoint_dict["scaler"] = scaler.state_dict()
596
-
597
- if completed_epoch == args.epochs or (
598
- args.save_frequency > 0 and (completed_epoch % args.save_frequency) == 0
599
- ):
600
- torch.save(
601
- checkpoint_dict,
602
- os.path.join(args.checkpoint_path, f"epoch_{completed_epoch}.pt"),
603
- )
604
- if args.save_most_recent:
605
- torch.save(
606
- checkpoint_dict,
607
- os.path.join(args.checkpoint_path, f"epoch_latest.pt"),
608
- )
609
- if args.save_top_performance and not args.no_eval:
610
- update_top_k_performance(
611
- filtered_metrics,
612
- current_top_k_ckpt_metrics,
613
- args,
614
- checkpoint_dict,
615
- bignumbetter=True,
616
- )
617
-
618
- if args.wandb and is_master(args):
619
- wandb.finish()
620
-
621
-
622
- def copy_codebase(args):
623
- from shutil import copytree, ignore_patterns
624
-
625
- new_code_path = os.path.join(args.logs, args.name, "code")
626
- if os.path.exists(new_code_path):
627
- print(
628
- f"Error. Experiment already exists at {new_code_path}. Use --name to specify a new experiment."
629
- )
630
- return -1
631
- print(f"Copying codebase to {new_code_path}")
632
- current_code_path = os.path.realpath(__file__)
633
- for _ in range(3):
634
- current_code_path = os.path.dirname(current_code_path)
635
- copytree(
636
- current_code_path, new_code_path, ignore=ignore_patterns("log", "logs", "wandb")
637
- )
638
- print("Done copying code.")
639
- return 1
640
-
641
-
642
- if __name__ == "__main__":
643
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/training/lp_train.py DELETED
@@ -1,292 +0,0 @@
1
- import json
2
- import logging
3
- import math
4
- import os
5
- import time
6
- from contextlib import suppress
7
-
8
- import numpy as np
9
- import torch
10
- import torch.nn.functional as F
11
-
12
- try:
13
- import wandb
14
- except ImportError:
15
- wandb = None
16
-
17
- from clap_module import LPLoss, LPMetrics, lp_gather_features
18
- from clap_module.utils import do_mixup, get_mix_lambda
19
- from .distributed import is_master
20
- from .zero_shot import zero_shot_eval
21
-
22
-
23
- class AverageMeter(object):
24
- """Computes and stores the average and current value"""
25
-
26
- def __init__(self):
27
- self.reset()
28
-
29
- def reset(self):
30
- self.val = 0
31
- self.avg = 0
32
- self.sum = 0
33
- self.count = 0
34
-
35
- def update(self, val, n=1):
36
- self.val = val
37
- self.sum += val * n
38
- self.count += n
39
- self.avg = self.sum / self.count
40
-
41
-
42
- def unwrap_model(model):
43
- if hasattr(model, "module"):
44
- return model.module
45
- else:
46
- return model
47
-
48
-
49
- def train_one_epoch(
50
- model, data, epoch, optimizer, scaler, scheduler, args, tb_writer=None, extra_suffix=""
51
- ):
52
- device = torch.device(args.device)
53
- autocast = torch.cuda.amp.autocast if args.precision == "amp" else suppress
54
- model.train()
55
- loss = LPLoss(args.lp_loss)
56
-
57
- dataloader, sampler = data["train"].dataloader, data["train"].sampler
58
- if args.distributed and sampler is not None:
59
- sampler.set_epoch(epoch)
60
- num_batches_per_epoch = dataloader.num_batches
61
- sample_digits = math.ceil(math.log(dataloader.num_samples + 1, 10))
62
-
63
- # for toy dataset
64
- if args.dataset_type == "toy":
65
- dataloader.dataset.generate_queue()
66
-
67
- loss_m = AverageMeter()
68
- batch_time_m = AverageMeter()
69
- data_time_m = AverageMeter()
70
- end = time.time()
71
-
72
- for i, batch in enumerate(dataloader):
73
- step = num_batches_per_epoch * epoch + i
74
-
75
- if isinstance(scheduler, dict):
76
- for s in scheduler.values():
77
- s(step)
78
- else:
79
- scheduler(step)
80
-
81
- audio = batch # contains mel_spec, wavform, and longer list
82
- class_label = batch['class_label']
83
- # audio = audio.to(device=device, non_blocking=True)
84
- class_label = class_label.to(device=device, non_blocking=True)
85
-
86
- if args.mixup:
87
- # https://github.com/RetroCirce/HTS-Audio-Transformer/blob/main/utils.py#L146
88
- mix_lambda = torch.from_numpy(get_mix_lambda(0.5, len(audio["waveform"]))).to(device)
89
- class_label = do_mixup(class_label, mix_lambda)
90
- else:
91
- mix_lambda = None
92
-
93
- data_time_m.update(time.time() - end)
94
- if isinstance(optimizer, dict):
95
- for o_ in optimizer.values():
96
- o_.zero_grad()
97
- else:
98
- optimizer.zero_grad()
99
-
100
- with autocast():
101
- pred = model(audio, mix_lambda=mix_lambda, device=device)
102
- total_loss = loss(pred, class_label)
103
-
104
- if isinstance(optimizer, dict):
105
- if scaler is not None:
106
- scaler.scale(total_loss).backward()
107
- for o_ in optimizer.values():
108
- if args.horovod:
109
- o_.synchronize()
110
- scaler.unscale_(o_)
111
- with o_.skip_synchronize():
112
- scaler.step(o_)
113
- else:
114
- scaler.step(o_)
115
- scaler.update()
116
- else:
117
- total_loss.backward()
118
- for o_ in optimizer.values():
119
- o_.step()
120
- else:
121
- if scaler is not None:
122
- scaler.scale(total_loss).backward()
123
- if args.horovod:
124
- optimizer.synchronize()
125
- scaler.unscale_(optimizer)
126
- with optimizer.skip_synchronize():
127
- scaler.step(optimizer)
128
- else:
129
- scaler.step(optimizer)
130
- scaler.update()
131
- else:
132
- total_loss.backward()
133
- optimizer.step()
134
-
135
- # Note: we clamp to 4.6052 = ln(100), as in the original paper.
136
- with torch.no_grad():
137
- unwrap_model(model).clap_model.logit_scale_a.clamp_(0, math.log(100))
138
- unwrap_model(model).clap_model.logit_scale_t.clamp_(0, math.log(100))
139
-
140
- batch_time_m.update(time.time() - end)
141
- end = time.time()
142
- batch_count = i + 1
143
-
144
- if is_master(args) and (i % 100 == 0 or batch_count == num_batches_per_epoch):
145
- if isinstance(audio, dict):
146
- batch_size = len(audio["waveform"])
147
- else:
148
- batch_size = len(audio)
149
- num_samples = batch_count * batch_size * args.world_size
150
- samples_per_epoch = dataloader.num_samples
151
- percent_complete = 100.0 * batch_count / num_batches_per_epoch
152
-
153
- # NOTE loss is coarsely sampled, just master node and per log update
154
- loss_m.update(total_loss.item(), batch_size)
155
- if isinstance(optimizer, dict):
156
- logging.info(
157
- f"Train Epoch: {epoch} [{num_samples:>{sample_digits}}/{samples_per_epoch} ({percent_complete:.0f}%)] "
158
- f"Loss: {loss_m.val:#.5g} ({loss_m.avg:#.4g}) "
159
- f"Data (t): {data_time_m.avg:.3f} "
160
- f"Batch (t): {batch_time_m.avg:.3f} "
161
- f"LR: {[o_.param_groups[0]['lr'] for o_ in optimizer.values()]}"
162
- )
163
- log_data = {
164
- "loss": loss_m.val,
165
- "data_time": data_time_m.val,
166
- "batch_time": batch_time_m.val,
167
- "lr": [o_.param_groups[0]["lr"] for o_ in optimizer.values()],
168
- }
169
- else:
170
- logging.info(
171
- f"Train Epoch: {epoch} [{num_samples:>{sample_digits}}/{samples_per_epoch} ({percent_complete:.0f}%)] "
172
- f"Loss: {loss_m.val:#.5g} ({loss_m.avg:#.4g}) "
173
- f"Data (t): {data_time_m.avg:.3f} "
174
- f"Batch (t): {batch_time_m.avg:.3f} "
175
- f"LR: {optimizer.param_groups[0]['lr']:5f} "
176
- )
177
-
178
- # Save train loss / etc. Using non avg meter values as loggers have their own smoothing
179
- log_data = {
180
- "loss": loss_m.val,
181
- "data_time": data_time_m.val,
182
- "batch_time": batch_time_m.val,
183
- "lr": optimizer.param_groups[0]["lr"],
184
- }
185
- for name, val in log_data.items():
186
- name = f"train{extra_suffix}/{name}"
187
- if tb_writer is not None:
188
- tb_writer.add_scalar(name, val, step)
189
- if args.wandb:
190
- assert wandb is not None, "Please install wandb."
191
- wandb.log({name: val, "step": step})
192
-
193
- # resetting batch / data time meters per log window
194
- batch_time_m.reset()
195
- data_time_m.reset()
196
- # end for
197
-
198
- def evaluate(model, data, epoch, args, tb_writer=None, extra_suffix=""):
199
- metrics = {}
200
- if not args.parallel_eval:
201
- if not is_master(args):
202
- return metrics
203
- device = torch.device(args.device)
204
- model.eval()
205
-
206
- # CHANGE
207
- # zero_shot_metrics = zero_shot_eval(model, data, epoch, args)
208
- # metrics.update(zero_shot_metrics)
209
- if is_master(args):
210
- print('Evaluating...')
211
- metric_names = args.lp_metrics.split(',')
212
- eval_tool = LPMetrics(metric_names=metric_names)
213
-
214
- autocast = torch.cuda.amp.autocast if args.precision == "amp" else suppress
215
- if "val" in data and (
216
- args.val_frequency
217
- and ((epoch % args.val_frequency) == 0 or epoch == args.epochs)
218
- ):
219
- if args.parallel_eval:
220
- dataloader, sampler = data["val"].dataloader, data["val"].sampler
221
- if args.distributed and sampler is not None:
222
- sampler.set_epoch(epoch)
223
- samples_per_val = dataloader.num_samples
224
- else:
225
- dataloader = data["val"].dataloader
226
- num_samples = 0
227
- samples_per_val = dataloader.num_samples
228
-
229
- eval_info = {
230
- 'pred': [],
231
- 'target': []
232
- }
233
- with torch.no_grad():
234
- for i, batch in enumerate(dataloader):
235
- audio = batch # contains mel_spec, wavform, and longer list
236
- class_label = batch['class_label']
237
-
238
- # audio = audio.to(device=device, non_blocking=True)
239
- class_label = class_label.to(device=device, non_blocking=True)
240
-
241
- with autocast():
242
- pred = model(audio, device=device)
243
- if args.parallel_eval:
244
- pred, class_label = lp_gather_features(pred, class_label, args.world_size, args.horovod)
245
- eval_info['pred'].append(pred)
246
- eval_info['target'].append(class_label)
247
-
248
- num_samples += class_label.shape[0]
249
-
250
- if (i % 100) == 0: # and i != 0:
251
- logging.info(
252
- f"Eval Epoch: {epoch} [{num_samples} / {samples_per_val}]"
253
- )
254
-
255
- if is_master(args):
256
- eval_info['pred'] = torch.cat(eval_info['pred'], 0).cpu()
257
- eval_info['target'] = torch.cat(eval_info['target'], 0).cpu()
258
- metric_dict = eval_tool.evaluate_mertics(eval_info['pred'], eval_info['target'])
259
- metrics.update(metric_dict)
260
- if "epoch" not in metrics.keys():
261
- metrics.update({"epoch": epoch})
262
-
263
- if is_master(args):
264
- if not metrics:
265
- return metrics
266
-
267
- logging.info(
268
- f"Eval Epoch: {epoch} "
269
- + "\n".join(
270
- [
271
- "\t".join([f"{m}: {round(metrics[m], 4):.4f}" ])
272
- for m in metrics
273
- ]
274
- )
275
- )
276
- if args.save_logs:
277
- for name, val in metrics.items():
278
- if tb_writer is not None:
279
- tb_writer.add_scalar(f"val{extra_suffix}/{name}", val, epoch)
280
-
281
- with open(os.path.join(args.checkpoint_path, "results.jsonl"), "a+") as f:
282
- f.write(json.dumps(metrics))
283
- f.write("\n")
284
-
285
- if args.wandb:
286
- assert wandb is not None, "Please install wandb."
287
- for name, val in metrics.items():
288
- wandb.log({f"val{extra_suffix}/{name}": val, "epoch": epoch})
289
-
290
- return metrics
291
- else:
292
- return metrics
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/training/main.py DELETED
@@ -1,597 +0,0 @@
1
- import logging
2
- import os
3
- import random
4
- from datetime import datetime
5
- import copy
6
- import numpy as np
7
- import torch
8
- import torch.backends.cudnn as cudnn
9
- from torch.cuda.amp import GradScaler
10
-
11
- try:
12
- import wandb
13
- except ImportError:
14
- wandb = None
15
-
16
- try:
17
- import torch.utils.tensorboard as tensorboard
18
- except ImportError:
19
- tensorboard = None
20
-
21
- try:
22
- import horovod.torch as hvd
23
- except ImportError:
24
- hvd = None
25
-
26
- from clap_module import create_model_and_transforms, trace_model, create_model
27
- from training.data import get_data
28
- from training.distributed import is_master, init_distributed_device, world_info_from_env
29
- from training.logger import setup_logging
30
- from training.params import parse_args
31
- from training.scheduler import cosine_lr
32
- from training.train import train_one_epoch, evaluate
33
- from clap_module.utils import dataset_split, get_optimizer
34
-
35
-
36
- def maintain_ckpts(args, startidx, all_idx_len):
37
- for i in reversed(range(startidx, all_idx_len)):
38
- if os.path.exists(os.path.join(args.checkpoint_path, f"epoch_top_{i}.pt")):
39
- os.rename(
40
- os.path.join(args.checkpoint_path, f"epoch_top_{i}.pt"),
41
- os.path.join(args.checkpoint_path, f"epoch_top_{i+1}.pt"),
42
- )
43
- if os.path.exists(
44
- os.path.join(args.checkpoint_path, f"epoch_top_{all_idx_len}.pt")
45
- ):
46
- os.remove(os.path.join(args.checkpoint_path, f"epoch_top_{all_idx_len}.pt"))
47
- return
48
-
49
-
50
- def update_top_k_performance(
51
- new_metrics_inputs, current_top_k_ckpt_metrics, args, ckpt, bignumbetter=True
52
- ):
53
- """
54
- Record the top-k performance of the current epoch.
55
- current_top_k_metrics is a dictionary of the form: {1: top_1_ckpt_measure, 2: top_2_ckpt_measure, ...}
56
- """
57
- if isinstance(new_metrics_inputs, (list, tuple)):
58
- new_metrics_inputs = np.mean(new_metrics_inputs)
59
- return update_top_k_performance(
60
- new_metrics_inputs,
61
- current_top_k_ckpt_metrics,
62
- args=args,
63
- ckpt=ckpt,
64
- bignumbetter=bignumbetter,
65
- )
66
- elif isinstance(new_metrics_inputs, dict):
67
- new_metrics_inputs = np.mean(list(new_metrics_inputs.values()))
68
- return update_top_k_performance(
69
- new_metrics_inputs,
70
- current_top_k_ckpt_metrics,
71
- args=args,
72
- ckpt=ckpt,
73
- bignumbetter=bignumbetter,
74
- )
75
- elif isinstance(new_metrics_inputs, (float, int)):
76
- update_flag = {k: False for k in current_top_k_ckpt_metrics.keys()}
77
- sorted_keys = sorted(current_top_k_ckpt_metrics.keys())
78
- sorted_values = sorted(
79
- current_top_k_ckpt_metrics.values(), reverse=bignumbetter
80
- )
81
- sorted_values_ = copy.deepcopy(sorted_values)
82
- sorted_values.append(new_metrics_inputs)
83
- sorted_values = sorted(sorted_values, reverse=bignumbetter)
84
- sorted_values = sorted_values[:-1]
85
-
86
- if sorted_values == sorted_values_:
87
- return current_top_k_ckpt_metrics, new_metrics_inputs
88
- else:
89
- for i in range(len(sorted_keys)):
90
- if current_top_k_ckpt_metrics[sorted_keys[i]] != sorted_values[i]:
91
- current_top_k_ckpt_metrics[sorted_keys[i]] = sorted_values[i]
92
- update_flag[sorted_keys[i]] = True
93
- for i in range(len(update_flag)):
94
- if update_flag[i]:
95
- maintain_ckpts(args, i, len(sorted_keys))
96
- torch.save(
97
- ckpt,
98
- os.path.join(args.checkpoint_path, f"epoch_top_{i}.pt"),
99
- )
100
- break
101
- return current_top_k_ckpt_metrics, new_metrics_inputs
102
-
103
-
104
- # def updateifNone(a, b):
105
- # a = b if None else a
106
- # return a
107
-
108
-
109
- def is_pretrained_params(n):
110
- return (
111
- n.startswith("transformer")
112
- or n in ["positional_embedding", "text_projection"]
113
- or n.startswith("token_embedding")
114
- or n.startswith("ln_final")
115
- or n.startswith("logit_scale_t")
116
- )
117
-
118
-
119
- def random_seed(seed=42, rank=0):
120
- torch.manual_seed(seed + rank)
121
- np.random.seed(seed + rank)
122
- random.seed(seed + rank)
123
-
124
-
125
- def main():
126
- args = parse_args()
127
- # sanitize model name for filesystem / uri use, easier if we don't use / in name as a rule?
128
- args.amodel = args.amodel.replace("/", "-")
129
- # download sizes.json file
130
-
131
- # (yusong): the below two lines are for debug
132
- # print("setting up faulthandler")
133
- # faulthandler.register(10)
134
-
135
- random.seed(args.seed)
136
- torch.manual_seed(args.seed)
137
- torch.cuda.manual_seed(args.seed)
138
- torch.cuda.manual_seed_all(args.seed)
139
- np.random.seed(args.seed)
140
- if args.tmodel == "bert" or args.tmodel == "roberta" or args.tmodel == "bart":
141
- assert (
142
- args.pretrained == "" or args.pretrained is None
143
- ), "bert/roberta/bart text encoder does not support pretrained models."
144
-
145
- # get the name of the experiments
146
- if args.name is None:
147
- args.name = "-".join(
148
- [
149
- datetime.now().strftime("%Y_%m_%d-%H_%M_%S"),
150
- f"model_{args.amodel}",
151
- f"lr_{args.lr}",
152
- f"b_{args.batch_size}",
153
- f"j_{args.workers}",
154
- f"p_{args.precision}",
155
- ]
156
- )
157
-
158
- # discover initial world args early so we can log properly
159
- args.distributed = False
160
- args.local_rank, args.rank, args.world_size = world_info_from_env()
161
-
162
- if args.remotedata and is_master(args):
163
- for dataset_name in args.datasetnames:
164
- for split in dataset_split[dataset_name]:
165
- if not os.path.exists(f"./json_files/{dataset_name}/{split}"):
166
- os.makedirs(f"./json_files/{dataset_name}/{split}")
167
- os.system(
168
- f"aws s3 cp s3://s-laion-audio/webdataset_tar/{dataset_name}/{split}/sizes.json ./json_files/{dataset_name}/{split}/sizes.json"
169
- )
170
-
171
- args.log_path = None
172
- if is_master(args, local=args.log_local):
173
- log_base_path = os.path.join(args.logs, args.name)
174
- os.makedirs(log_base_path, exist_ok=True)
175
- log_filename = f"out-{args.rank}" if args.log_local else "out.log"
176
- args.log_path = os.path.join(log_base_path, log_filename)
177
- if os.path.exists(args.log_path):
178
- print(
179
- "Error. Experiment already exists. Use --name {} to specify a new experiment."
180
- )
181
- return -1
182
-
183
- # Set logger
184
- args.log_level = logging.DEBUG if args.debug else logging.INFO
185
- setup_logging(args.log_path, args.log_level)
186
-
187
- # fully initialize distributed device environment
188
- device = init_distributed_device(args)
189
-
190
- args.wandb = "wandb" in args.report_to or "all" in args.report_to
191
- args.tensorboard = "tensorboard" in args.report_to or "all" in args.report_to
192
- if is_master(args):
193
- args.tensorboard_path = (
194
- os.path.join(args.logs, args.name, "tensorboard")
195
- if args.tensorboard
196
- else ""
197
- )
198
- args.checkpoint_path = os.path.join(args.logs, args.name, "checkpoints")
199
- for dirname in [args.tensorboard_path, args.checkpoint_path]:
200
- if dirname:
201
- os.makedirs(dirname, exist_ok=True)
202
- else:
203
- args.tensorboard_path = ""
204
- args.checkpoint_path = ""
205
-
206
- if args.copy_codebase:
207
- copy_codebase(args)
208
-
209
- assert args.precision in ["amp", "fp16", "fp32"]
210
- if args.precision == "fp16":
211
- logging.warning(
212
- "It is recommended to use fp32 mixed-precision instead of FP16 and AMP in this model. "
213
- "They will cause NaN loss and NaN gradients. "
214
- "FP16 and AMP support needs further verification and tuning, especially for train."
215
- )
216
-
217
- if args.horovod:
218
- logging.info(
219
- f"Running in horovod mode with multiple processes / nodes. Device: {args.device}."
220
- f"Process (global: {args.rank}, local {args.local_rank}), total {args.world_size}."
221
- )
222
- elif args.distributed:
223
- logging.info(
224
- f"Running in distributed mode with multiple processes. Device: {args.device}."
225
- f"Process (global: {args.rank}, local {args.local_rank}), total {args.world_size}."
226
- )
227
- else:
228
- logging.info(f"Running with a single process. Device {args.device}.")
229
-
230
- logging.info(f"openai cache dir: {os.path.expanduser(args.openai_model_cache_dir)}")
231
-
232
- model, model_cfg = create_model(
233
- args.amodel,
234
- args.tmodel,
235
- args.pretrained,
236
- precision=args.precision,
237
- device=device,
238
- jit=args.torchscript,
239
- force_quick_gelu=args.force_quick_gelu,
240
- openai_model_cache_dir=os.path.expanduser(args.openai_model_cache_dir),
241
- skip_params=True,
242
- pretrained_audio=args.pretrained_audio,
243
- pretrained_text=args.pretrained_text,
244
- enable_fusion=args.enable_fusion,
245
- fusion_type=args.fusion_type
246
- )
247
-
248
- if args.horovod:
249
- with torch.no_grad():
250
- for param in model.parameters():
251
- param.set_(param.contiguous())
252
-
253
- if args.trace:
254
- model = trace_model(model, batch_size=args.batch_size, device=device)
255
-
256
- if is_master(args):
257
- logging.info("Model:")
258
- logging.info(f"{str(model)}")
259
- logging.info("Params:")
260
- params_file = os.path.join(args.logs, args.name, "params.txt")
261
- with open(params_file, "w") as f:
262
- for name in sorted(vars(args)):
263
- val = getattr(args, name)
264
- logging.info(f" {name}: {val}")
265
- f.write(f"{name}: {val}\n")
266
-
267
- if args.distributed and not args.horovod:
268
- if args.use_bn_sync:
269
- model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model)
270
- ddp_args = {}
271
- if args.ddp_static_graph:
272
- # this doesn't exist in older PyTorch, arg only added if enabled
273
- ddp_args["static_graph"] = True
274
- model = torch.nn.parallel.DistributedDataParallel(
275
- model, device_ids=[device], find_unused_parameters=True, **ddp_args
276
- )
277
-
278
- data = get_data(args, model_cfg)
279
- assert len(data), "At least one train or eval dataset must be specified."
280
- if args.trace:
281
- assert "train" not in data, "Cannot train with traced model"
282
-
283
- exclude = (
284
- lambda n, p: p.ndim < 2
285
- or "bn" in n
286
- or "ln" in n
287
- or "bias" in n
288
- or "logit_scale" in n
289
- )
290
- include = lambda n, p: not exclude(n, p)
291
-
292
- named_parameters = list(model.named_parameters())
293
-
294
- # freeze text encoder
295
- text_freeze_parameters = [
296
- p
297
- for n, p in named_parameters
298
- if 'text_branch' in n
299
- ]
300
-
301
- if args.freeze_text:
302
- print("Freeze Text!!!!")
303
- for k in text_freeze_parameters:
304
- k.requires_grad = False
305
-
306
- gain_or_bias_params = [
307
- p for n, p in named_parameters if exclude(n, p) and p.requires_grad
308
- ]
309
- rest_params = [p for n, p in named_parameters if include(n, p) and p.requires_grad]
310
-
311
- # set wd-related params to 0 if use adam optimizer
312
- if args.optimizer == "adam":
313
- args.wd = 0
314
- args.wd_pretrained = 0
315
- args.wd_new = 0
316
-
317
- if args.train_data is None:
318
- optimizer = None
319
- scheduler = None
320
- else:
321
- total_steps = data["train"].dataloader.num_batches * args.epochs
322
-
323
- if args.split_opt:
324
- for x in ["lr", "beta1", "beta2", "eps", "wd"]:
325
- for y in ["_new", "_pretrained"]:
326
- if getattr(args, x + y) is None:
327
- setattr(args, x + y, getattr(args, x))
328
-
329
- gain_or_bias_pretrained_params = [
330
- p
331
- for n, p in named_parameters
332
- if (exclude(n, p) and p.requires_grad) and is_pretrained_params(n)
333
- ]
334
- rest_pretrained_params = [
335
- p
336
- for n, p in named_parameters
337
- if (include(n, p) and p.requires_grad) and is_pretrained_params(n)
338
- ]
339
- gain_or_bias_new_params = [
340
- p
341
- for n, p in named_parameters
342
- if (exclude(n, p) and p.requires_grad) and (not is_pretrained_params(n))
343
- ]
344
- rest_new_params = [
345
- p
346
- for n, p in named_parameters
347
- if (include(n, p) and p.requires_grad) and (not is_pretrained_params(n))
348
- ]
349
- pretrained_params_optimizer = get_optimizer(
350
- [
351
- {"params": gain_or_bias_pretrained_params, "weight_decay": 0.0},
352
- {
353
- "params": rest_pretrained_params,
354
- "weight_decay": args.wd_pretrained,
355
- },
356
- ],
357
- lr=args.lr_pretrained,
358
- betas=(args.beta1_pretrained, args.beta2_pretrained),
359
- eps=args.eps_pretrained,
360
- momentum=args.momentum_pretrained,
361
- optimizer_name=args.optimizer,
362
- )
363
- pretrained_params_scheduler = cosine_lr(
364
- pretrained_params_optimizer,
365
- args.lr_pretrained,
366
- args.warmup,
367
- total_steps,
368
- )
369
- new_params_optimizer = get_optimizer(
370
- [
371
- {"params": gain_or_bias_new_params, "weight_decay": 0.0},
372
- {"params": rest_new_params, "weight_decay": args.wd_new},
373
- ],
374
- lr=args.lr_new,
375
- betas=(args.beta1_new, args.beta2_new),
376
- eps=args.eps_new,
377
- momentum=args.momentum_new,
378
- optimizer_name=args.optimizer,
379
- )
380
-
381
- new_params_scheduler = cosine_lr(
382
- new_params_optimizer, args.lr_new, args.warmup, total_steps
383
- )
384
-
385
- optimizer = {
386
- "pretrained": pretrained_params_optimizer,
387
- "new": new_params_optimizer,
388
- }
389
- scheduler = {
390
- "pretrained": pretrained_params_scheduler,
391
- "new": new_params_scheduler,
392
- }
393
-
394
- if args.horovod:
395
- pretrained_params_optimizer = hvd.DistributedOptimizer(
396
- pretrained_params_optimizer,
397
- named_parameters=model.named_parameters(),
398
- )
399
- new_params_optimizer = hvd.DistributedOptimizer(
400
- new_params_optimizer, named_parameters=model.named_parameters()
401
- )
402
- hvd.broadcast_parameters(model.state_dict(), root_rank=0)
403
- hvd.broadcast_optimizer_state(pretrained_params_optimizer, root_rank=0)
404
- hvd.broadcast_optimizer_state(new_params_optimizer, root_rank=0)
405
- else:
406
- optimizer = get_optimizer(
407
- [
408
- {"params": gain_or_bias_params, "weight_decay": 0.0},
409
- {"params": rest_params, "weight_decay": args.wd},
410
- ],
411
- lr=args.lr,
412
- betas=(args.beta1, args.beta2),
413
- eps=args.eps,
414
- momentum=args.momentum,
415
- optimizer_name=args.optimizer,
416
- )
417
-
418
- scheduler = cosine_lr(optimizer, args.lr, args.warmup, total_steps)
419
-
420
- if args.horovod:
421
- optimizer = hvd.DistributedOptimizer(
422
- optimizer, named_parameters=model.named_parameters()
423
- )
424
- hvd.broadcast_parameters(model.state_dict(), root_rank=0)
425
- hvd.broadcast_optimizer_state(optimizer, root_rank=0)
426
-
427
- scaler = GradScaler() if args.precision == "amp" else None
428
-
429
- # optionally resume from a checkpoint
430
- start_epoch = 0
431
- if args.resume is not None:
432
- if os.path.isfile(args.resume):
433
- checkpoint = torch.load(args.resume, map_location=device)
434
- if "epoch" in checkpoint:
435
- # resuming a train checkpoint w/ epoch and optimizer state
436
- start_epoch = checkpoint["epoch"]
437
- sd = checkpoint["state_dict"]
438
- if not args.distributed and next(iter(sd.items()))[0].startswith(
439
- "module"
440
- ):
441
- sd = {k[len("module.") :]: v for k, v in sd.items()}
442
- model.load_state_dict(sd)
443
- if args.split_opt:
444
- if optimizer is not None:
445
- for k, o_ in optimizer.items():
446
- o_.load_state_dict(checkpoint[k + "_" + "optimizer"])
447
- if optimizer is not None:
448
- optimizer.load_state_dict(checkpoint["optimizer"])
449
- if scaler is not None and "scaler" in checkpoint:
450
- scaler.load_state_dict(checkpoint["scaler"])
451
- logging.info(
452
- f"=> resuming checkpoint '{args.resume}' (epoch {start_epoch})"
453
- )
454
- else:
455
- # loading a bare (model only) checkpoint for fine-tune or evaluation
456
- model.load_state_dict(checkpoint)
457
- logging.info(
458
- f"=> loaded checkpoint '{args.resume}' (epoch {start_epoch})"
459
- )
460
- if args.freeze_text:
461
- print("Freeze Text!!!!")
462
- for k in text_freeze_parameters:
463
- k.requires_grad = False
464
- else:
465
- logging.info("=> no checkpoint found at '{}'".format(args.resume))
466
-
467
- cudnn.benchmark = True
468
- cudnn.deterministic = False
469
-
470
- # determine if this worker should save logs and checkpoints. only do so if it is rank == 0
471
- args.save_logs = args.logs and args.logs.lower() != "none" and is_master(args)
472
- writer = None
473
- if args.save_logs and args.tensorboard:
474
- assert tensorboard is not None, "Please install tensorboard."
475
- writer = tensorboard.SummaryWriter(args.tensorboard_path)
476
-
477
- if args.wandb and is_master(args):
478
- assert wandb is not None, "Please install wandb."
479
- logging.debug("Starting wandb.")
480
- args.train_sz = data["train"].dataloader.num_samples
481
- if args.val_data is not None:
482
- args.val_sz = data["val"].dataloader.num_samples
483
- # you will have to configure this for your project!
484
- wandb.init(
485
- entity="clap",
486
- project="clap",
487
- notes=args.wandb_notes,
488
- name=args.wandb_notes,
489
- tags=[],
490
- config=vars(args),
491
- )
492
- if args.debug:
493
- wandb.watch(model, log="all")
494
- wandb.save(params_file)
495
- logging.debug("Finished loading wandb.")
496
-
497
- if "train" not in data:
498
- evaluate(model, data, start_epoch, args, writer)
499
- return
500
- elif start_epoch == 0 and "val" in data and not args.no_eval:
501
- evaluate(model, data, 0, args, writer)
502
- # print(f'rank {args.rank}, Start First Evaluation')# (yusong): for debug
503
- if args.save_top_performance:
504
- current_top_k_ckpt_metrics = {
505
- i: 0 for i in range(args.save_top_performance)
506
- } # initialize the top-k metric for ckpts to 0
507
-
508
- # print(f'rank {args.rank}, Start Training') # (yusong): for debug
509
- for epoch in range(start_epoch, args.epochs):
510
- # freeze the text param after (include) args.freeze_text_after, this is -1 by default
511
- if epoch == args.freeze_text_after:
512
- print("Text pretrained parameters are freezed since this epoch.")
513
- for k in text_freeze_parameters:
514
- k.requires_grad = False
515
- if is_master(args):
516
- logging.info(f"Start epoch {epoch}")
517
-
518
- train_one_epoch(model, data, epoch, optimizer, scaler, scheduler, args, writer)
519
- completed_epoch = epoch + 1
520
-
521
- if (
522
- any(v in data for v in ("val", "imagenet-val", "imagenet-v2"))
523
- and not args.no_eval
524
- ):
525
- metrics = evaluate(model, data, completed_epoch, args, writer)
526
- if args.save_top_performance:
527
- top_k_dataset = args.top_k_checkpoint_select_dataset
528
- top_k_metric = args.top_k_checkpoint_select_metric
529
- filtered_metrics = [
530
- v
531
- for k, v in metrics.items()
532
- if top_k_metric in k and top_k_dataset in k
533
- ] # check all R@10 metrics (all dataset) and use it to update the ckpt
534
- # Saving checkpoints.
535
- if args.save_logs:
536
- if args.split_opt:
537
- opt_dict = {
538
- k + "_" + "optimizer": v.state_dict() for k, v in optimizer.items()
539
- }
540
- else:
541
- opt_dict = {"optimizer": optimizer.state_dict()}
542
- checkpoint_dict = {
543
- "epoch": completed_epoch,
544
- "name": args.name,
545
- "state_dict": model.state_dict(),
546
- }
547
- checkpoint_dict.update(opt_dict)
548
- if scaler is not None:
549
- checkpoint_dict["scaler"] = scaler.state_dict()
550
-
551
- if completed_epoch == args.epochs or (
552
- args.save_frequency > 0 and (completed_epoch % args.save_frequency) == 0
553
- ):
554
- torch.save(
555
- checkpoint_dict,
556
- os.path.join(args.checkpoint_path, f"epoch_{completed_epoch}.pt"),
557
- )
558
- if args.save_most_recent:
559
- torch.save(
560
- checkpoint_dict,
561
- os.path.join(args.checkpoint_path, f"epoch_latest.pt"),
562
- )
563
- if args.save_top_performance and not args.no_eval:
564
- update_top_k_performance(
565
- filtered_metrics,
566
- current_top_k_ckpt_metrics,
567
- args,
568
- checkpoint_dict,
569
- bignumbetter=True,
570
- )
571
-
572
- if args.wandb and is_master(args):
573
- wandb.finish()
574
-
575
-
576
- def copy_codebase(args):
577
- from shutil import copytree, ignore_patterns
578
-
579
- new_code_path = os.path.join(args.logs, args.name, "code")
580
- if os.path.exists(new_code_path):
581
- print(
582
- f"Error. Experiment already exists at {new_code_path}. Use --name to specify a new experiment."
583
- )
584
- return -1
585
- print(f"Copying codebase to {new_code_path}")
586
- current_code_path = os.path.realpath(__file__)
587
- for _ in range(3):
588
- current_code_path = os.path.dirname(current_code_path)
589
- copytree(
590
- current_code_path, new_code_path, ignore=ignore_patterns("log", "logs", "wandb")
591
- )
592
- print("Done copying code.")
593
- return 1
594
-
595
-
596
- if __name__ == "__main__":
597
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/training/params.py DELETED
@@ -1,567 +0,0 @@
1
- import argparse
2
-
3
-
4
- def get_default_params(model_name):
5
- # Params from paper (https://arxiv.org/pdf/2103.00020.pdf)
6
- model_name = model_name.lower()
7
- if "vit" in model_name:
8
- return {"lr": 5.0e-4, "beta1": 0.9, "beta2": 0.98, "eps": 1.0e-6}
9
- else:
10
- return {"lr": 5.0e-4, "beta1": 0.9, "beta2": 0.999, "eps": 1.0e-8}
11
-
12
-
13
- def parse_args():
14
- parser = argparse.ArgumentParser()
15
- parser.add_argument(
16
- "--train-data",
17
- type=str,
18
- default=None,
19
- help="Path to h5 filewith training data",
20
- )
21
- parser.add_argument(
22
- "--val-data",
23
- type=str,
24
- default=None,
25
- help="Path to h5 file with validation data",
26
- )
27
- parser.add_argument(
28
- "--freeze-text",
29
- default=False,
30
- action="store_true",
31
- help="if you need to freeze the text encoder, make this True",
32
- )
33
- parser.add_argument(
34
- "--freeze-text-after",
35
- type=int,
36
- default=-1,
37
- help="if you need to freeze the text encoder after (include) epoch x, set this param to x. Set -1 to disable it",
38
- )
39
- parser.add_argument(
40
- "--train-ipc",
41
- type=str,
42
- default=None,
43
- help="Path to npy file of the number of instance per class in training data",
44
- )
45
- parser.add_argument(
46
- "--val-ipc",
47
- type=str,
48
- default=None,
49
- help="Path to npy file of the number of instance per class in validation data",
50
- )
51
- parser.add_argument(
52
- "--train-num-samples",
53
- type=int,
54
- default=None,
55
- help="Number of samples in dataset. Required for webdataset if not available in info file.",
56
- )
57
- parser.add_argument(
58
- "--val-num-samples",
59
- type=int,
60
- default=None,
61
- help="Number of samples in dataset. Useful for webdataset if not available in info file.",
62
- )
63
- parser.add_argument(
64
- "--dataset-type",
65
- choices=["webdataset", "csv", "auto", "toy"],
66
- default="auto",
67
- help="Which type of dataset to process.",
68
- )
69
- parser.add_argument(
70
- "--csv-separator",
71
- type=str,
72
- default="\t",
73
- help="For csv-like datasets, which separator to use.",
74
- )
75
- parser.add_argument(
76
- "--csv-img-key",
77
- type=str,
78
- default="filepath",
79
- help="For csv-like datasets, the name of the key for the image paths.",
80
- )
81
- parser.add_argument(
82
- "--csv-caption-key",
83
- type=str,
84
- default="title",
85
- help="For csv-like datasets, the name of the key for the captions.",
86
- )
87
- parser.add_argument(
88
- "--imagenet-val",
89
- type=str,
90
- default=None,
91
- help="Path to imagenet val set for conducting zero shot evaluation.",
92
- )
93
- parser.add_argument(
94
- "--imagenet-v2",
95
- type=str,
96
- default=None,
97
- help="Path to imagenet v2 for conducting zero shot evaluation.",
98
- )
99
- parser.add_argument(
100
- "--datasetnames",
101
- nargs="+",
102
- default=None,
103
- help="If loading webdataset, spedify the dataset names to load. Can be some of these: Clotho, audioset, audiocaps, BBCSoundEffects",
104
- )
105
- parser.add_argument(
106
- "--full-train-dataset",
107
- nargs="+",
108
- default=None,
109
- help="Which dataset will be trained with all the subsets. (train+test)",
110
- )
111
- parser.add_argument(
112
- "--exclude-eval-dataset",
113
- nargs="+",
114
- default=None,
115
- help="Which dataset will be excluded with evaluation",
116
- )
117
- parser.add_argument(
118
- "--datasetinfos",
119
- nargs="+",
120
- default=None,
121
- help="If loading webdataset, spedify the dataset types to load. Can be some of these: train, test, valid, unbalanced_train, balanced_train, eval",
122
- )
123
- parser.add_argument(
124
- "--dataset-proportion",
125
- type=float,
126
- default=1.0,
127
- help="How much proportion of dataset we want to train.",
128
- )
129
- parser.add_argument(
130
- "--remotedata",
131
- default=False,
132
- action="store_true",
133
- help="if the dataset is remote, set this flag",
134
- )
135
- parser.add_argument(
136
- "--class-label-path",
137
- type=str,
138
- default=None,
139
- help="The path of the class label pickle or csv.",
140
- )
141
- parser.add_argument(
142
- "--datasetpath",
143
- type=str,
144
- default="/mnt/audio_clip/webdataset_tar",
145
- help="The path to the dataset",
146
- )
147
- parser.add_argument(
148
- "--logs",
149
- type=str,
150
- default="./logs/",
151
- help="Where to store tensorboard logs. Use None to avoid storing logs.",
152
- )
153
- parser.add_argument(
154
- "--log-local",
155
- action="store_true",
156
- default=False,
157
- help="log files on local master, otherwise global master only.",
158
- )
159
- parser.add_argument(
160
- "--name",
161
- type=str,
162
- default=None,
163
- help="Optional identifier for the experiment when storing logs. Otherwise use current time.",
164
- )
165
- parser.add_argument(
166
- "--workers", type=int, default=1, help="Number of workers per GPU."
167
- )
168
- parser.add_argument(
169
- "--batch-size", type=int, default=64, help="Batch size per GPU."
170
- )
171
- parser.add_argument(
172
- "--epochs", type=int, default=32, help="Number of epochs to train for."
173
- )
174
- parser.add_argument("--lr", type=float, default=None, help="Learning rate.")
175
- parser.add_argument("--beta1", type=float, default=None, help="Adam beta 1.")
176
- parser.add_argument("--beta2", type=float, default=None, help="Adam beta 2.")
177
- parser.add_argument("--eps", type=float, default=None, help="Adam epsilon.")
178
- parser.add_argument("--momentum", type=float, default=None, help="SGD epsilon.")
179
- parser.add_argument("--wd", type=float, default=0.2, help="Weight decay.")
180
-
181
- parser.add_argument(
182
- "--split-opt",
183
- action="store_true",
184
- default=False,
185
- help="Use this flag to skip the learning rate decay.",
186
- )
187
- parser.add_argument(
188
- "--lr-pretrained", type=float, default=None, help="Learning rate for text."
189
- )
190
- parser.add_argument(
191
- "--beta1-pretrained", type=float, default=None, help="Adam beta 1 for text."
192
- )
193
- parser.add_argument(
194
- "--beta2-pretrained", type=float, default=None, help="Adam beta 2 for text."
195
- )
196
- parser.add_argument(
197
- "--eps-pretrained", type=float, default=None, help="Adam epsilon for text."
198
- )
199
- parser.add_argument(
200
- "--wd-pretrained", type=float, default=0.2, help="Weight decay for text."
201
- )
202
- parser.add_argument(
203
- "--momentum-pretrained", type=float, default=0.9, help="Momentum for text."
204
- )
205
- parser.add_argument(
206
- "--lr-new", type=float, default=None, help="Learning rate for audio."
207
- )
208
- parser.add_argument(
209
- "--beta1-new", type=float, default=None, help="Adam beta 1 for audio."
210
- )
211
- parser.add_argument(
212
- "--beta2-new", type=float, default=None, help="Adam beta 2 for audio."
213
- )
214
- parser.add_argument(
215
- "--eps-new", type=float, default=None, help="Adam epsilon for audio."
216
- )
217
- parser.add_argument(
218
- "--wd-new", type=float, default=0.2, help="Weight decay for audio."
219
- )
220
- parser.add_argument(
221
- "--momentum-new", type=float, default=0.9, help="Momentum for audio."
222
- )
223
- parser.add_argument(
224
- "--warmup", type=int, default=10000, help="Number of steps to warmup for."
225
- )
226
- parser.add_argument(
227
- "--use-bn-sync",
228
- default=False,
229
- action="store_true",
230
- help="Whether to use batch norm sync.",
231
- )
232
- parser.add_argument(
233
- "--skip-scheduler",
234
- action="store_true",
235
- default=False,
236
- help="Use this flag to skip the learning rate decay.",
237
- )
238
- parser.add_argument(
239
- "--save-frequency", type=int, default=1, help="How often to save checkpoints."
240
- )
241
- parser.add_argument(
242
- "--save-top-performance",
243
- type=int,
244
- default=0,
245
- help="Save the top x performance weights if the value >0",
246
- )
247
- parser.add_argument(
248
- "--save-most-recent",
249
- action="store_true",
250
- default=False,
251
- help="Always save the most recent model trained to epoch_latest.pt.",
252
- )
253
- parser.add_argument(
254
- "--zeroshot-frequency", type=int, default=2, help="How often to run zero shot."
255
- )
256
- parser.add_argument(
257
- "--val-frequency",
258
- type=int,
259
- default=1,
260
- help="How often to run evaluation with val data.",
261
- )
262
- parser.add_argument(
263
- "--resume",
264
- default=None,
265
- type=str,
266
- help="path to latest checkpoint (default: none)",
267
- )
268
- parser.add_argument(
269
- "--precision",
270
- choices=["amp", "fp16", "fp32"],
271
- default="amp",
272
- help="Floating point precision.",
273
- )
274
- parser.add_argument(
275
- "--amodel",
276
- type=str,
277
- default="RN50",
278
- help="Name of the audio backbone to use.",
279
- )
280
- parser.add_argument(
281
- "--tmodel",
282
- type=str,
283
- default="transformer",
284
- help="Name of the text backbone to use. Can be [transformer, bert, roberta, bart]",
285
- )
286
- parser.add_argument(
287
- "--pretrained-audio",
288
- default="",
289
- type=str,
290
- help="Use a pretrained audio model weights for the audio encoder of CLAP",
291
- )
292
- parser.add_argument(
293
- "--pretrained-text",
294
- default="",
295
- type=str,
296
- help="Use a pretrained text model weights for the text encoder of CLAP",
297
- )
298
- parser.add_argument(
299
- "--pretrained",
300
- default="",
301
- type=str,
302
- help="Use a pretrained CLIP model weights with the specified tag or file path.",
303
- )
304
- parser.add_argument(
305
- "--pretrained-image",
306
- default=False,
307
- action="store_true",
308
- help="Load imagenet pretrained weights for image tower backbone if available.",
309
- )
310
- parser.add_argument(
311
- "--lock-image",
312
- default=False,
313
- action="store_true",
314
- help="Lock full image tower by disabling gradients.",
315
- )
316
- parser.add_argument(
317
- "--lock-image-unlocked-groups",
318
- type=int,
319
- default=0,
320
- help="Leave last n image tower layer groups unlocked.",
321
- )
322
- parser.add_argument(
323
- "--lock-image-freeze-bn-stats",
324
- default=False,
325
- action="store_true",
326
- help="Freeze BatchNorm running stats in image tower for any locked layers.",
327
- )
328
- parser.add_argument(
329
- "--local-loss",
330
- default=False,
331
- action="store_true",
332
- help="calculate loss w/ local features @ global (instead of realizing full global @ global matrix)",
333
- )
334
- parser.add_argument(
335
- "--gather-with-grad",
336
- default=False,
337
- action="store_true",
338
- help="enable full distributed gradient for feature gather",
339
- )
340
- parser.add_argument(
341
- "--force-quick-gelu",
342
- default=False,
343
- action="store_true",
344
- help="Force use of QuickGELU activation for non-OpenAI transformer models.",
345
- )
346
- parser.add_argument(
347
- "--torchscript",
348
- default=False,
349
- action="store_true",
350
- help="torch.jit.script the model, also uses jit version of OpenAI models if pretrained=='openai'",
351
- )
352
- parser.add_argument(
353
- "--trace",
354
- default=False,
355
- action="store_true",
356
- help="torch.jit.trace the model for inference / eval only",
357
- )
358
- # arguments for distributed training
359
- parser.add_argument(
360
- "--dist-url",
361
- default="env://",
362
- type=str,
363
- help="url used to set up distributed training",
364
- )
365
- parser.add_argument(
366
- "--dist-backend", default="nccl", type=str, help="distributed backend"
367
- )
368
- parser.add_argument(
369
- "--report-to",
370
- default="",
371
- type=str,
372
- help="Options are ['wandb', 'tensorboard', 'wandb,tensorboard']",
373
- )
374
- parser.add_argument(
375
- "--wandb-notes", default="", type=str, help="Notes if logging with wandb"
376
- )
377
- parser.add_argument(
378
- "--C", type=float, default=3.16, help="inverse regularizer for logistic reg."
379
- )
380
- parser.add_argument(
381
- "--debug",
382
- default=False,
383
- action="store_true",
384
- help="If true, more information is logged.",
385
- )
386
- parser.add_argument(
387
- "--copy-codebase",
388
- default=False,
389
- action="store_true",
390
- help="If true, we copy the entire base on the log diretory, and execute from there.",
391
- )
392
- parser.add_argument(
393
- "--horovod",
394
- default=False,
395
- action="store_true",
396
- help="Use horovod for distributed training.",
397
- )
398
- parser.add_argument(
399
- "--ddp-static-graph",
400
- default=False,
401
- action="store_true",
402
- help="Enable static graph optimization for DDP in PyTorch >= 1.11.",
403
- )
404
- parser.add_argument(
405
- "--no-set-device-rank",
406
- default=False,
407
- action="store_true",
408
- help="Don't set device index from local rank (when CUDA_VISIBLE_DEVICES restricted to one per proc).",
409
- )
410
- parser.add_argument("--seed", type=int, default=4242, help="Default random seed.")
411
-
412
- parser.add_argument(
413
- "--top-k-checkpoint-select-dataset",
414
- type=str,
415
- default="all",
416
- help="The dataset of selecting top-k checkpoint.",
417
- )
418
-
419
- # @R10, @R@5, @R1, mAP@10
420
- parser.add_argument(
421
- "--top-k-checkpoint-select-metric",
422
- type=str,
423
- default="_R@10",
424
- help="The metric for selecting top-k checkpoint.",
425
- )
426
- parser.add_argument(
427
- "--openai-model-cache-dir",
428
- type=str,
429
- default="~/.cache/clip",
430
- help="Directory to download OpenAI models.",
431
- )
432
- parser.add_argument(
433
- "--optimizer",
434
- type=str,
435
- default="adamw",
436
- help="can be AdamW or SGD",
437
- )
438
- parser.add_argument(
439
- "--parallel-eval",
440
- default=False,
441
- action="store_true",
442
- help="Eval in parallel (multi-GPU, multi-node).",
443
- )
444
-
445
- parser.add_argument(
446
- "--no-eval",
447
- default=False,
448
- action="store_true",
449
- help="Training without evaluation.",
450
- )
451
-
452
- parser.add_argument(
453
- "--lp-mlp",
454
- default=False,
455
- action="store_true",
456
- help="Linear Probe using MLP layer or not.",
457
- )
458
-
459
- parser.add_argument(
460
- "--lp-freeze",
461
- default=False,
462
- action="store_true",
463
- help="Linear Probe using Freeze CLAP or not",
464
- )
465
-
466
- parser.add_argument(
467
- "--lp-act",
468
- default="None",
469
- type=str,
470
- help="Options are ['relu','elu','prelu','softmax','sigmoid']",
471
- )
472
-
473
- parser.add_argument(
474
- "--lp-loss", type=str, default="bce", help="Loss func of Linear Probe."
475
- )
476
-
477
- parser.add_argument(
478
- "--lp-metrics",
479
- type=str,
480
- default="map,mauc,acc",
481
- help="Metrics of Linear Probe.",
482
- )
483
-
484
- parser.add_argument(
485
- "--lp-lr", type=float, default=1e-4, help="learning rate of linear probe"
486
- )
487
- parser.add_argument(
488
- "--kappa", type=float, default=0,
489
- help="the kappa in the weighted contrastive loss, default is to turn off the weighted contrastive loss"
490
- )
491
-
492
- parser.add_argument(
493
- "--data-filling",
494
- type=str,
495
- default="pad",
496
- help="type of data filling when the audio length is shorter than the max length."
497
- "Can be one of the following: repeat, repeatpad, pad",
498
- )
499
- parser.add_argument(
500
- "--data-truncating",
501
- type=str,
502
- default="rand_trunc",
503
- help="type of data truncation when the audio length is longer than the max length."
504
- "Can be one of the following: rand_trunc, fusion",
505
- )
506
-
507
- parser.add_argument(
508
- "--clap-mlploss",
509
- default=False,
510
- action="store_true",
511
- help="Using MLP loss for CLAP model or not",
512
- )
513
-
514
- parser.add_argument(
515
- "--wandb-id",
516
- type=str,
517
- default=None,
518
- help="the id of wandb experiment to restore.",
519
- )
520
-
521
- parser.add_argument(
522
- "--sleep", type=float, default=0, help="sleep n seconds before start training"
523
- )
524
-
525
- # variable length processing
526
- parser.add_argument(
527
- "--enable-fusion",
528
- default=False,
529
- action="store_true",
530
- help="Enable feature funsion for variable-length data",
531
- )
532
-
533
- parser.add_argument(
534
- "--fusion-type",
535
- type=str,
536
- default='None',
537
- help="Type is among ['channel_map', 'daf_1d','aff_1d','iaff_1d','daf_2d','aff_2d','iaff_2d']",
538
- )
539
-
540
- parser.add_argument(
541
- "--mixup",
542
- default=False,
543
- action="store_true",
544
- help="Enable mixup in finetuning training.",
545
- )
546
- parser.add_argument(
547
- "--text-augment-selection",
548
- type=str,
549
- default=None,
550
- help="For selecting levels of augmented text. Type is among ['all', 'augment_only', 'none']",
551
- )
552
- parser.add_argument(
553
- "--prefetch-factor",
554
- type=int,
555
- default=None,
556
- help="The prefetch factor for dataloader. Larger value will use more memory and CPU but faster.",
557
- )
558
-
559
- args = parser.parse_args()
560
-
561
- # If some params are not passed, we use the default values based on model name.
562
- default_params = get_default_params(args.amodel)
563
- for name, val in default_params.items():
564
- if getattr(args, name) is None:
565
- setattr(args, name, val)
566
-
567
- return args
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/training/scheduler.py DELETED
@@ -1,23 +0,0 @@
1
- import numpy as np
2
-
3
-
4
- def assign_learning_rate(optimizer, new_lr):
5
- for param_group in optimizer.param_groups:
6
- param_group["lr"] = new_lr
7
-
8
-
9
- def _warmup_lr(base_lr, warmup_length, step):
10
- return base_lr * (step + 1) / warmup_length
11
-
12
-
13
- def cosine_lr(optimizer, base_lr, warmup_length, steps):
14
- def _lr_adjuster(step):
15
- if step < warmup_length:
16
- lr = _warmup_lr(base_lr, warmup_length, step)
17
- else:
18
- e = step - warmup_length
19
- es = steps - warmup_length
20
- lr = 0.5 * (1 + np.cos(np.pi * e / es)) * base_lr
21
- assign_learning_rate(optimizer, lr)
22
- return lr
23
- return _lr_adjuster
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/training/train.py DELETED
@@ -1,781 +0,0 @@
1
- import json
2
- import logging
3
- import math
4
- import os
5
- import time
6
- from contextlib import suppress
7
-
8
- import numpy as np
9
- import torch
10
- import torch.nn.functional as F
11
-
12
- try:
13
- import wandb
14
- except ImportError:
15
- wandb = None
16
-
17
- from clap_module import ClipLoss, gather_features
18
- from .distributed import is_master
19
-
20
-
21
- class AverageMeter(object):
22
- """Computes and stores the average and current value"""
23
-
24
- def __init__(self):
25
- self.reset()
26
-
27
- def reset(self):
28
- self.val = 0
29
- self.avg = 0
30
- self.sum = 0
31
- self.count = 0
32
-
33
- def update(self, val, n=1):
34
- self.val = val
35
- self.sum += val * n
36
- self.count += n
37
- self.avg = self.sum / self.count
38
-
39
-
40
- def unwrap_model(model):
41
- if hasattr(model, "module"):
42
- return model.module
43
- else:
44
- return model
45
-
46
-
47
- def train_one_epoch(
48
- model, data, epoch, optimizer, scaler, scheduler, args, tb_writer=None
49
- ):
50
- device = torch.device(args.device)
51
- autocast = torch.cuda.amp.autocast if args.precision == "amp" else suppress
52
- model.train()
53
- loss = ClipLoss(
54
- local_loss=args.local_loss,
55
- gather_with_grad=args.gather_with_grad,
56
- cache_labels=True,
57
- rank=args.rank,
58
- world_size=args.world_size,
59
- use_horovod=args.horovod,
60
- mlp_loss=args.clap_mlploss,
61
- weight_loss_kappa=args.kappa,
62
- )
63
-
64
- dataloader, sampler = data["train"].dataloader, data["train"].sampler
65
- if args.distributed and sampler is not None:
66
- sampler.set_epoch(epoch)
67
- num_batches_per_epoch = dataloader.num_batches
68
- sample_digits = math.ceil(math.log(dataloader.num_samples + 1, 10))
69
-
70
- # for toy dataset
71
- if args.dataset_type == "toy":
72
- dataloader.dataset.generate_queue()
73
-
74
- loss_m = AverageMeter()
75
- batch_time_m = AverageMeter()
76
- data_time_m = AverageMeter()
77
- end = time.time()
78
-
79
- for i, batch in enumerate(dataloader):
80
- # logging.info(f"batch {i} of {num_batches_per_epoch}")
81
- step = num_batches_per_epoch * epoch + i
82
- if isinstance(scheduler, dict):
83
- for s in scheduler.values():
84
- s(step)
85
- else:
86
- scheduler(step)
87
- audios = batch # contains mel_spec, wavform, and longer list
88
- texts = batch['text']
89
- # audios = audios.to(device=device, non_blocking=True)
90
- # texts = texts.to(device=device, non_blocking=True)
91
-
92
- data_time_m.update(time.time() - end)
93
- if isinstance(optimizer, dict):
94
- for o_ in optimizer.values():
95
- o_.zero_grad()
96
- else:
97
- optimizer.zero_grad()
98
-
99
- with autocast():
100
- (
101
- audio_features,
102
- text_features,
103
- audio_features_mlp,
104
- text_features_mlp,
105
- logit_scale_a,
106
- logit_scale_t,
107
- ) = model(audios, texts, device)
108
-
109
- if args.clap_mlploss:
110
- total_loss = loss(
111
- audio_features=audio_features,
112
- text_features=text_features,
113
- logit_scale_a=logit_scale_a,
114
- logit_scale_t=logit_scale_t,
115
- audio_features_mlp=audio_features_mlp,
116
- text_features_mlp=text_features_mlp
117
- )
118
- else:
119
- total_loss = loss(
120
- audio_features=audio_features,
121
- text_features=text_features,
122
- logit_scale_a=logit_scale_a
123
- )
124
- if isinstance(optimizer, dict):
125
- if scaler is not None:
126
- scaler.scale(total_loss).backward()
127
- for o_ in optimizer.values():
128
- if args.horovod:
129
- o_.synchronize()
130
- scaler.unscale_(o_)
131
- with o_.skip_synchronize():
132
- scaler.step(o_)
133
- else:
134
- scaler.step(o_)
135
- scaler.update()
136
- else:
137
- total_loss.backward()
138
- for o_ in optimizer.values():
139
- o_.step()
140
- else:
141
- if scaler is not None:
142
- scaler.scale(total_loss).backward()
143
- if args.horovod:
144
- optimizer.synchronize()
145
- scaler.unscale_(optimizer)
146
- with optimizer.skip_synchronize():
147
- scaler.step(optimizer)
148
- else:
149
- scaler.step(optimizer)
150
- scaler.update()
151
- else:
152
- total_loss.backward()
153
- optimizer.step()
154
-
155
- # Note: we clamp to 4.6052 = ln(100), as in the original paper.
156
- with torch.no_grad():
157
- unwrap_model(model).logit_scale_a.clamp_(0, math.log(100))
158
- if args.clap_mlploss:
159
- unwrap_model(model).logit_scale_t.clamp_(0, math.log(100))
160
-
161
- batch_time_m.update(time.time() - end)
162
- end = time.time()
163
- batch_count = i + 1
164
- if is_master(args) and (i % 100 == 0 or batch_count == num_batches_per_epoch):
165
- if isinstance(audios, dict):
166
- batch_size = len(audios["waveform"])
167
- else:
168
- batch_size = len(audios)
169
- num_samples = batch_count * batch_size * args.world_size
170
- samples_per_epoch = dataloader.num_samples
171
- percent_complete = 100.0 * batch_count / num_batches_per_epoch
172
-
173
- # NOTE loss is coarsely sampled, just master node and per log update
174
- loss_m.update(total_loss.item(), batch_size)
175
- logit_scale_scalar_a = logit_scale_a.item()
176
- logit_scale_scalar_t = logit_scale_t.item()
177
- if isinstance(optimizer, dict):
178
- if args.clap_mlploss:
179
- logging.info(
180
- f"Train Epoch: {epoch} [{num_samples:>{sample_digits}}/{samples_per_epoch} ({percent_complete:.0f}%)] "
181
- f"Loss: {loss_m.val:#.5g} ({loss_m.avg:#.4g}) "
182
- f"Data (t): {data_time_m.avg:.3f} "
183
- f"Batch (t): {batch_time_m.avg:.3f} "
184
- f"LR: {[o_.param_groups[0]['lr'] for o_ in optimizer.values()]} "
185
- f"Logit Scale Audio: {logit_scale_scalar_a:.3f}"
186
- f"Logit Scale Text: {logit_scale_scalar_t:.3f}"
187
- )
188
- log_data = {
189
- "loss": loss_m.val,
190
- "data_time": data_time_m.val,
191
- "batch_time": batch_time_m.val,
192
- "scale_audio": logit_scale_scalar_a,
193
- "scale_text": logit_scale_scalar_t,
194
- "lr": [o_.param_groups[0]["lr"] for o_ in optimizer.values()],
195
- }
196
- else:
197
- logging.info(
198
- f"Train Epoch: {epoch} [{num_samples:>{sample_digits}}/{samples_per_epoch} ({percent_complete:.0f}%)] "
199
- f"Loss: {loss_m.val:#.5g} ({loss_m.avg:#.4g}) "
200
- f"Data (t): {data_time_m.avg:.3f} "
201
- f"Batch (t): {batch_time_m.avg:.3f} "
202
- f"LR: {[o_.param_groups[0]['lr'] for o_ in optimizer.values()]} "
203
- f"Logit Scale Audio: {logit_scale_scalar_a:.3f}"
204
- )
205
- log_data = {
206
- "loss": loss_m.val,
207
- "data_time": data_time_m.val,
208
- "batch_time": batch_time_m.val,
209
- "scale_audio": logit_scale_scalar_a,
210
- "lr": [o_.param_groups[0]["lr"] for o_ in optimizer.values()],
211
- }
212
-
213
- else:
214
- if args.clap_mlploss:
215
- logging.info(
216
- f"Train Epoch: {epoch} [{num_samples:>{sample_digits}}/{samples_per_epoch} ({percent_complete:.0f}%)] "
217
- f"Loss: {loss_m.val:#.5g} ({loss_m.avg:#.4g}) "
218
- f"Data (t): {data_time_m.avg:.3f} "
219
- f"Batch (t): {batch_time_m.avg:.3f} "
220
- f"LR: {optimizer.param_groups[0]['lr']:5f} "
221
- f"Logit Scale Audio: {logit_scale_scalar_a:.3f}"
222
- f"Logit Scale Text: {logit_scale_scalar_t:.3f}"
223
- )
224
-
225
- # Save train loss / etc. Using non avg meter values as loggers have their own smoothing
226
- log_data = {
227
- "loss": loss_m.val,
228
- "data_time": data_time_m.val,
229
- "batch_time": batch_time_m.val,
230
- "scale_audio": logit_scale_scalar_a,
231
- "scale_text": logit_scale_scalar_t,
232
- "lr": optimizer.param_groups[0]["lr"],
233
- }
234
- else:
235
- logging.info(
236
- f"Train Epoch: {epoch} [{num_samples:>{sample_digits}}/{samples_per_epoch} ({percent_complete:.0f}%)] "
237
- f"Loss: {loss_m.val:#.5g} ({loss_m.avg:#.4g}) "
238
- f"Data (t): {data_time_m.avg:.3f} "
239
- f"Batch (t): {batch_time_m.avg:.3f} "
240
- f"LR: {optimizer.param_groups[0]['lr']:5f} "
241
- f"Logit Scale Audio: {logit_scale_scalar_a:.3f}"
242
- )
243
-
244
- # Save train loss / etc. Using non avg meter values as loggers have their own smoothing
245
- log_data = {
246
- "loss": loss_m.val,
247
- "data_time": data_time_m.val,
248
- "batch_time": batch_time_m.val,
249
- "scale_audio": logit_scale_scalar_a,
250
- "lr": optimizer.param_groups[0]["lr"],
251
- }
252
- for name, val in log_data.items():
253
- name = "train/" + name
254
- if tb_writer is not None:
255
- tb_writer.add_scalar(name, val, step)
256
- if args.wandb:
257
- assert wandb is not None, "Please install wandb."
258
- wandb.log({name: val, "step": step})
259
-
260
- # resetting batch / data time meters per log window
261
- batch_time_m.reset()
262
- data_time_m.reset()
263
- # end for
264
-
265
-
266
- def evaluate(model, data, epoch, args, tb_writer=None):
267
- metrics = {}
268
- if not args.parallel_eval:
269
- if not is_master(args):
270
- return metrics
271
- device = torch.device(args.device)
272
- model.eval()
273
-
274
- # CHANGE
275
- # zero_shot_metrics = zero_shot_eval(model, data, epoch, args)
276
- # metrics.update(zero_shot_metrics)
277
- if is_master(args):
278
- print('Evaluating...')
279
- autocast = torch.cuda.amp.autocast if args.precision == "amp" else suppress
280
- if args.val_dataset_names == ['Clotho', 'audiocaps']:
281
- # if only clotho and audiocaps are used, then we will use a different evaluation function.
282
- # This is because in the Clotho and audiocaps valid and test set, there are 5 text for 1 audio.
283
- if args.parallel_eval:
284
- # (yusong): just a hack here. Don't use parallel eval when evaluating only clotho and audiocaps.
285
- raise NotImplementedError("Parallel evaluation not supported for eval only Clotho and audiocaps.")
286
- val_metrics_per_dataset = evaluate_clotho_audiocaps(model, data, epoch, args, autocast, device, tb_writer)
287
- for m in val_metrics_per_dataset.values():
288
- metrics.update(m)
289
- if "epoch" not in metrics.keys():
290
- metrics.update({"epoch": epoch})
291
- metrics = select_top_metric_clotho_audiocaps(metrics, val_metrics_per_dataset, args)
292
- elif "val" in data and (
293
- args.val_frequency
294
- and ((epoch % args.val_frequency) == 0 or epoch == args.epochs)
295
- ):
296
- dataloader = data["val"].dataloader
297
- num_samples = 0
298
- samples_per_val = dataloader.num_samples
299
-
300
- # FIXME this does not scale past small eval datasets
301
- # all_audio_features @ all_text_features will blow up memory and compute very quickly
302
- eval_info = {}
303
- if args.clap_mlploss:
304
- eval_info["all"] = {
305
- "cumulative_loss": 0.0,
306
- "num_samples": 0,
307
- "all_audio_features": [],
308
- "all_text_features": [],
309
- "all_audio_features_mlp": [],
310
- "all_text_features_mlp": []
311
- } # cumulative_loss = 0.0
312
- else:
313
- eval_info["all"] = {
314
- "cumulative_loss": 0.0,
315
- "num_samples": 0,
316
- "all_audio_features": [],
317
- "all_text_features": []
318
- } # cumu
319
- # all_audio_features, all_text_features, all_audio_features_mlp, all_text_features_mlp = [], [], [], []
320
- with torch.no_grad():
321
- for i, batch in enumerate(dataloader):
322
- audios = batch # contains mel_spec, wavform, and longer list
323
- texts = batch['text']
324
- # audios = audios.to(device=device, non_blocking=True)
325
-
326
- all_names = list(set(["-".join(b.split("/")[-3:-1]) for b in batch['__url__']]))
327
- for name in all_names:
328
- if name not in eval_info.keys():
329
- if args.clap_mlploss:
330
- eval_info[name] = {
331
- "cumulative_loss": 0.0,
332
- "num_samples": 0,
333
- "all_audio_features": [],
334
- "all_text_features": [],
335
- "all_audio_features_mlp": [],
336
- "all_text_features_mlp": [],
337
- }
338
- else:
339
- eval_info[name] = {
340
- "cumulative_loss": 0.0,
341
- "num_samples": 0,
342
- "all_audio_features": [],
343
- "all_text_features": []
344
- }
345
- with autocast():
346
- (
347
- audio_features,
348
- text_features,
349
- audio_features_mlp,
350
- text_features_mlp,
351
- logit_scale_a,
352
- logit_scale_t,
353
- ) = model(audios, texts, device)
354
-
355
- if args.parallel_eval:
356
- # multi-GPU eval
357
- if args.clap_mlploss:
358
- (
359
- audio_features,
360
- text_features,
361
- audio_features_mlp,
362
- text_features_mlp,
363
- ) = gather_features(
364
- audio_features=audio_features,
365
- text_features=text_features,
366
- audio_features_mlp=audio_features_mlp,
367
- text_features_mlp=text_features_mlp,
368
- local_loss=False,
369
- gather_with_grad=False,
370
- rank=args.rank,
371
- world_size=args.world_size,
372
- use_horovod=args.horovod,
373
- mlp_loss=args.clap_mlploss
374
- )
375
- else:
376
- (
377
- audio_features,
378
- text_features,
379
- ) = gather_features(
380
- audio_features=audio_features,
381
- text_features=text_features,
382
- local_loss=False,
383
- gather_with_grad=False,
384
- rank=args.rank,
385
- world_size=args.world_size,
386
- use_horovod=args.horovod,
387
- mlp_loss=args.clap_mlploss
388
- )
389
-
390
- if is_master(args):
391
- num_samples += audio_features.shape[0]
392
- for n in [*all_names, "all"]:
393
- if n == "all":
394
- eval_info[n]["all_audio_features"].append(
395
- audio_features.cpu()
396
- )
397
- eval_info[n]["all_text_features"].append(
398
- text_features.cpu()
399
- )
400
- if args.clap_mlploss:
401
- eval_info[n]["all_audio_features_mlp"].append(
402
- audio_features_mlp.cpu()
403
- )
404
- eval_info[n]["all_text_features_mlp"].append(
405
- text_features_mlp.cpu()
406
- )
407
- else:
408
- idx = np.where(
409
- np.array(
410
- ["-".join(b.split("/")[-3:-1]) for b in batch['__url__']]
411
- )
412
- == n
413
- )[0]
414
- eval_info[n]["all_audio_features"].append(
415
- audio_features.cpu().index_select(
416
- 0, torch.tensor(idx).long()
417
- )
418
- )
419
- eval_info[n]["all_text_features"].append(
420
- text_features.cpu().index_select(
421
- 0, torch.tensor(idx).long()
422
- )
423
- )
424
- if args.clap_mlploss:
425
- eval_info[n]["all_audio_features_mlp"].append(
426
- audio_features_mlp.cpu().index_select(
427
- 0, torch.tensor(idx).long()
428
- )
429
- )
430
- eval_info[n]["all_text_features_mlp"].append(
431
- text_features_mlp.cpu().index_select(
432
- 0, torch.tensor(idx).long()
433
- )
434
- )
435
- # print(f'eval step {i}') # (yusong): for debug
436
-
437
- # cumulative_loss += total_loss * batch_size
438
- # num_samples += batch_size
439
- if is_master(args) and (i % 100) == 0: # and i != 0:
440
- logging.info(
441
- f"Eval Epoch: {epoch} [{num_samples} / {samples_per_val}]"
442
- )
443
- if is_master(args):
444
- val_metrics_per_dataset = {}
445
- for n in eval_info.keys():
446
- if args.clap_mlploss:
447
- metrics_single_dataset = get_metrics(
448
- audio_features=torch.cat(eval_info[n]["all_audio_features"]),
449
- text_features=torch.cat(eval_info[n]["all_text_features"]),
450
- logit_scale_a=logit_scale_a.cpu(),
451
- audio_features_mlp=torch.cat(
452
- eval_info[n]["all_audio_features_mlp"]
453
- ),
454
- text_features_mlp=torch.cat(eval_info[n]["all_text_features_mlp"]),
455
- logit_scale_t=logit_scale_t.cpu(),
456
- mlp_loss=args.clap_mlploss
457
- )
458
- else:
459
- metrics_single_dataset = get_metrics(
460
- audio_features=torch.cat(eval_info[n]["all_audio_features"]),
461
- text_features=torch.cat(eval_info[n]["all_text_features"]),
462
- logit_scale_a=logit_scale_a.cpu(),
463
- mlp_loss=args.clap_mlploss
464
- )
465
- val_metrics_per_dataset[n] = {
466
- n + "/" + k: v for k, v in metrics_single_dataset.items()
467
- }
468
- metrics.update(val_metrics_per_dataset[n])
469
- if "epoch" not in metrics.keys():
470
- metrics.update({"epoch": epoch})
471
- if is_master(args):
472
- if not metrics:
473
- return metrics
474
-
475
- logging.info(
476
- f"Eval Epoch: {epoch} "
477
- + "\n".join(
478
- [
479
- "\t".join([f"{k}: {round(v, 4):.4f}" for k, v in m.items()])
480
- for m in val_metrics_per_dataset.values()
481
- ]
482
- )
483
- )
484
-
485
- if args.save_logs:
486
- for name, val in metrics.items():
487
- if tb_writer is not None:
488
- tb_writer.add_scalar(f"val/{name}", val, epoch)
489
-
490
- with open(os.path.join(args.checkpoint_path, "results.jsonl"), "a+") as f:
491
- f.write(json.dumps(metrics))
492
- f.write("\n")
493
-
494
- if args.wandb:
495
- assert wandb is not None, "Please install wandb."
496
- for name, val in metrics.items():
497
- wandb.log({f"val/{name}": val, "epoch": epoch})
498
-
499
- return metrics
500
- else:
501
- return metrics
502
-
503
-
504
- def get_metrics(
505
- audio_features,
506
- text_features,
507
- logit_scale_a,
508
- audio_features_mlp=None,
509
- text_features_mlp=None,
510
- logit_scale_t=None,
511
- mlp_loss=False
512
- ):
513
- metrics = {}
514
- if mlp_loss:
515
- # Set up audio to text & text to audio similary matrice
516
- a_logits_per_audio = (
517
- (logit_scale_a * audio_features @ text_features_mlp.t()).detach().cpu()
518
- )
519
- a_logits_per_text = a_logits_per_audio.t().detach().cpu()
520
- t_logits_per_audio = (
521
- (logit_scale_t * audio_features_mlp @ text_features.t()).detach().cpu()
522
- )
523
- t_logits_per_text = t_logits_per_audio.t().detach().cpu()
524
-
525
- labels = torch.arange(audio_features.shape[0]).long()
526
- # Change the loss from two terms into four terms with 2x2 combined CE loss
527
- total_loss = (
528
- F.cross_entropy(a_logits_per_audio, labels)
529
- + F.cross_entropy(a_logits_per_text, labels)
530
- + F.cross_entropy(t_logits_per_audio, labels)
531
- + F.cross_entropy(t_logits_per_text, labels)
532
- ) / 4
533
-
534
- metrics[f"cumulative_loss"] = total_loss.item()
535
- metrics[f"num_samples"] = audio_features.shape[0]
536
-
537
- logits = {
538
- "audio_to_text": (a_logits_per_audio + t_logits_per_audio) / 2,
539
- "text_to_audio": (a_logits_per_text + t_logits_per_text) / 2,
540
- }
541
- ground_truth = torch.arange(len(text_features)).view(-1, 1)
542
-
543
- else:
544
- # print("text_features", text_features)
545
- # print("text_features.shape", text_features.shape)
546
- logits_per_audio = (logit_scale_a * audio_features @ text_features.t()).detach().cpu()
547
- logits_per_text = logits_per_audio.t().detach().cpu()
548
-
549
- labels = torch.arange(audio_features.shape[0]).long()
550
- # Change the loss from two terms into four terms with 2x2 combined CE loss
551
- total_loss = (
552
- F.cross_entropy(logits_per_audio, labels)
553
- + F.cross_entropy(logits_per_text, labels)
554
- ) / 2
555
-
556
- metrics[f"cumulative_loss"] = total_loss.item()
557
- metrics[f"num_samples"] = audio_features.shape[0]
558
-
559
- logits = {"audio_to_text": logits_per_audio, "text_to_audio": logits_per_text}
560
-
561
- ground_truth = torch.arange(len(text_features)).view(-1, 1)
562
-
563
- for name, logit in logits.items():
564
- ranking = torch.argsort(logit, descending=True)
565
- preds = torch.where(ranking == ground_truth)[1] # (yusong) this line is slow because it uses single thread
566
- preds = preds.detach().cpu().numpy()
567
- metrics[f"{name}_mean_rank"] = preds.mean() + 1
568
- metrics[f"{name}_median_rank"] = np.floor(np.median(preds)) + 1
569
- for k in [1, 5, 10]:
570
- metrics[f"{name}_R@{k}"] = np.mean(preds < k)
571
- # map@10
572
- metrics[f"{name}_mAP@10"] = np.mean(np.where(preds < 10, 1 / (preds + 1), 0.0))
573
-
574
- return metrics
575
-
576
-
577
- def evaluate_clotho_audiocaps(
578
- model, data, epoch, args, autocast, device, tb_writer=None
579
- ):
580
- """
581
- Adapted from https://github.com/XinhaoMei/audio-text_retrieval/blob/main/tools/utils.py.
582
- 1. for text-to-audio retrieval, do 5 times and average the results
583
- 2. for R@1, R@5, R@10 in audio-to-text retrieval, take the best rank among 5 text
584
- 3. for map@10 in audio-to-text retrieval:
585
- 3.1: sort the rank of 5 text
586
- 3.2: exclude the rank >=10 (0-index)
587
- 3.3: compute the map regarding the remaining ranks: np.mean(np.arange(1, len(ranks)+1) / ranks).
588
- (3.3) That is, take the top ranks of 5 text that is < 10, and assign the descending number as ground truth.
589
- (3.3) E.g.: the ground truth of first rank of the 5 text should be 1, the second rank should be 2, etc.
590
- """
591
- # TODO: (yusong) only support single GPU evaluation and only support non-mlp case for now.
592
- dataloader = data["val"].dataloader
593
- with torch.no_grad():
594
- eval_info = {}
595
- for i, batch in enumerate(dataloader):
596
- audios = batch # contains mel_spec, wavform, and longer list
597
-
598
- # each item in the list has 5 texts
599
- if args.tmodel == "transformer":
600
- from clap_module import tokenize
601
- texts = [tokenize(t) for t in batch['full_text']]
602
- texts = torch.cat(texts)
603
- else:
604
- from .data import tokenizer
605
- texts = [tokenizer(t, tmodel=args.tmodel) for t in batch['full_text']] # 5 texts for each audio
606
- texts = {k: torch.cat([t[k] for t in texts]) for k in texts[0].keys()} # 5 x batch
607
-
608
- # audios = audios.to(device=device, non_blocking=True)
609
-
610
- # batch['__url__'] contains the path to the data tar this sample is from
611
- # So, b.split("/")[-3:-1] will get you '<dataset_name>-<dataset-split>'
612
- all_names = list(set(["-".join(b.split("/")[-3:-1]) for b in batch['__url__']]))
613
- for name in all_names:
614
- if name not in eval_info.keys():
615
- # we will not use mlp outputs even if args.clap_mlploss=True
616
- eval_info[name] = {
617
- "cumulative_loss": 0.0,
618
- "num_samples": 0,
619
- "all_audio_features": [],
620
- "all_text_features": []
621
- }
622
- with autocast():
623
- audio_features = model(audios, None, device)
624
- text_features = model(None, texts, device)
625
- audio_features = F.normalize(audio_features, dim=-1)
626
- text_features = F.normalize(text_features, dim=-1)
627
-
628
- all_names = list(set(["-".join(b.split("/")[-3:-1]) for b in batch['__url__']]))
629
- for n in all_names:
630
- idx = np.where(
631
- np.array(
632
- ["-".join(b.split("/")[-3:-1]) for b in batch['__url__']]
633
- )
634
- == n
635
- )[0]
636
- eval_info[n]["all_audio_features"].append(
637
- audio_features.cpu().index_select(
638
- 0, torch.tensor(idx).long()
639
- )
640
- )
641
- # (yusong) please double-check. This is for selecting 5 text features at once.
642
- # because idx is a list of indices in size of num_samples,
643
- # and text_features is a tensor of size (5*num_samples, dim)
644
- # so we need to select 5 consecutive indices at once for a single index in idx.
645
- eval_info[n]["all_text_features"].append(
646
- text_features.cpu().reshape([-1, 5, text_features.shape[1]]).index_select(
647
- 0, torch.tensor(idx).long()
648
- ).reshape([-1, text_features.shape[1]])
649
- )
650
-
651
- val_metrics_all = {}
652
-
653
- for n in eval_info.keys():
654
- logit_scale_a, logit_scale_t = model(None, None, device)
655
- logit_scale_a = logit_scale_a.cpu()
656
-
657
- audio_features = torch.cat(eval_info[n]["all_audio_features"], dim=0)
658
- text_features = torch.cat(eval_info[n]["all_text_features"], dim=0)
659
-
660
- logits_per_audio = (logit_scale_a * audio_features @ text_features.t()).detach().cpu()
661
- logits_per_text = logits_per_audio.t().detach().cpu()
662
-
663
- # logits_per_audio shape: [num_samples, num_samples*5]
664
- # logits_per_text shape: [num_samples*5, num_samples]
665
-
666
- logging.info(f"dataset {n}, logits_per_audio shape: {logits_per_audio.shape}, "
667
- f"logits_per_text shape: {logits_per_text.shape}")
668
-
669
- metrics = {}
670
- num_samples = audio_features.shape[0]
671
- metrics[f"num_samples"] = num_samples
672
-
673
- # (yusong) the following code is very important, please double-check:
674
- # logits_per_audio.reshape(num_samples, num_samples, 5)[:, :, d]
675
- # logits_per_text.reshape(num_samples, 5, num_samples)[:, d, :]
676
- # Those two are retrieving one of the 5 text for each audio.
677
- labels = torch.arange(audio_features.shape[0]).long()
678
- audio_to_text_loss = [
679
- F.cross_entropy(
680
- logits_per_audio.reshape(num_samples, num_samples, 5)[:, :, d], labels) for d in range(5)
681
- ]
682
- text_to_audio_loss = [
683
- F.cross_entropy(
684
- logits_per_text.reshape(num_samples, 5, num_samples)[:, d, :], labels) for d in range(5)
685
- ]
686
- total_loss = (
687
- np.mean(audio_to_text_loss) + np.mean(text_to_audio_loss)
688
- ) / 2
689
-
690
- metrics[f"cumulative_loss"] = total_loss.item()
691
-
692
- # text to audio: do 5 times
693
- pred_text = []
694
- for d in range(5):
695
- logit = logits_per_text.reshape(num_samples, 5, num_samples)[:, d, :]
696
- ground_truth = torch.arange(len(logit)).view(-1, 1)
697
- ranking = torch.argsort(logit, descending=True) # [num_samples, num_samples]
698
- preds = torch.where(ranking == ground_truth)[1]
699
- pred_text.append(preds.detach().cpu().numpy())
700
- pred_text_concat = np.concatenate(pred_text, axis=0) # [5*num_samples]
701
- metrics[f"text_to_audio_mean_rank"] = pred_text_concat.mean() + 1
702
- metrics[f"text_to_audio_median_rank"] = np.floor(np.median(pred_text_concat)) + 1
703
- for k in [1, 5, 10]:
704
- metrics[f"text_to_audio_R@{k}"] = np.mean(pred_text_concat < k)
705
- # map@10
706
- metrics[f"text_to_audio_mAP@10"] = np.mean(np.where(pred_text_concat < 10, 1 / (pred_text_concat + 1), 0.0))
707
-
708
- # audio to text: take the best result
709
- # for audio to text map 10, sort and assign descending ground truth.
710
- # see https://github.com/XinhaoMei/audio-text_retrieval/blob/main/tools/utils.py#L103
711
- # map@10
712
- map_all = []
713
- pred_audio_all = []
714
- for d in range(num_samples):
715
- # logits_per_audio: [num_samples, num_samples*5]
716
- logit_single = logits_per_audio[d, :] # [5*num_samples]
717
- # Ground-truth index: [d*5, d*5+1, d*5+2, d*5+3, d*5+4]
718
- ranking = torch.argsort(logit_single, descending=True) # [5*num_samples]
719
- # ranking: the index of first match, second match, ...
720
- ground_truth = torch.arange(d * 5, d * 5 + 5)[None]
721
- all_pred = torch.where(torch.stack([ranking] * 5) == ground_truth.view(-1, 1))[1]
722
- min_pred = torch.min(all_pred)
723
- pred_audio_all.append(min_pred.detach().cpu().numpy())
724
- all_pred_filter = all_pred[all_pred < 10].detach().cpu().numpy()
725
- # /5 because we have 5 text, so it means for the text rank >=10 we count as 0.
726
- map_single = np.sum((np.arange(1, len(all_pred_filter) + 1) / (all_pred_filter + 1))) / 5
727
- map_all.append(map_single)
728
- metrics[f"audio_to_text_mAP@10"] = np.mean(map_all)
729
- for k in [1, 5, 10]:
730
- metrics[f"audio_to_text_R@{k}"] = np.mean(np.array(pred_audio_all) < k)
731
-
732
- val_metrics_all[n] = {
733
- n + "/" + k: v for k, v in metrics.items()
734
- }
735
- return val_metrics_all
736
-
737
-
738
- def calculate_selection_performance_clotho_audiocaps(val_metrics_per_dataset):
739
- """
740
- Calculate performance for Clotho+AudioCaps for model selection.
741
- """
742
- selection_performance_all = []
743
- for n in val_metrics_per_dataset.keys():
744
- selection_performance = (val_metrics_per_dataset[n][f"{n}/audio_to_text_mAP@10"] +
745
- val_metrics_per_dataset[n][f"{n}/text_to_audio_mAP@10"]) / 2
746
- selection_performance_all.append(selection_performance)
747
- return np.mean(selection_performance_all)
748
-
749
-
750
- def select_top_metric_clotho_audiocaps(metrics, val_metrics_per_dataset, args):
751
- # val_metrics_per_dataset: dict, key: dataset name, value: dict, key: metric name, value: metric value
752
- # metrics: dict, key: metric name, value: metric value
753
- # Hack: use args to save the top performance
754
- if not hasattr(args, "top_selection_performance"):
755
- selection_performance = calculate_selection_performance_clotho_audiocaps(val_metrics_per_dataset)
756
- # TODO: write the if and else together
757
- metric_update = {}
758
- for n in val_metrics_per_dataset.keys():
759
- for k in val_metrics_per_dataset[n].keys():
760
- metric_update[k.split('/')[0] + '-top' + '/' + k.split('/')[1]] = val_metrics_per_dataset[n][k]
761
- metric_update['top_selection_performance'] = selection_performance
762
- metric_update['top-selection-epoch'] = metrics['epoch']
763
- metrics.update(metric_update)
764
- args.top_metric = metric_update
765
- args.top_selection_performance = selection_performance
766
- else:
767
- selection_performance_new = calculate_selection_performance_clotho_audiocaps(val_metrics_per_dataset)
768
- selection_performance_old = args.top_selection_performance
769
- if selection_performance_new > selection_performance_old:
770
- metric_update = {}
771
- for n in val_metrics_per_dataset.keys():
772
- for k in val_metrics_per_dataset[n].keys():
773
- metric_update[k.split('/')[0] + '-top' + '/' + k.split('/')[1]] = val_metrics_per_dataset[n][k]
774
- metric_update['top_selection_performance'] = selection_performance_new
775
- metric_update['top-selection-epoch'] = metrics['epoch']
776
- metrics.update(metric_update)
777
- args.top_metric = metric_update
778
- args.top_selection_performance = selection_performance_new
779
- else:
780
- metrics.update(args.top_metric)
781
- return metrics
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/training/zero_shot.py DELETED
@@ -1,90 +0,0 @@
1
- # NOTE: This script is currently not supported for CLAP.
2
- import logging
3
- from contextlib import suppress
4
-
5
- import torch
6
- import torch.nn.functional as F
7
- from tqdm import tqdm
8
-
9
- from clap_module import tokenize
10
- from .imagenet_zeroshot_data import imagenet_classnames, openai_imagenet_template
11
-
12
-
13
- def zero_shot_classifier(model, classnames, templates, args):
14
- with torch.no_grad():
15
- zeroshot_weights = []
16
- for classname in tqdm(classnames):
17
- texts = [template(classname) for template in templates] # format with class
18
- texts = tokenize(texts).to(args.device) # tokenize
19
- if args.distributed and not args.horovod:
20
- class_embeddings = model.module.encode_text(texts)
21
- else:
22
- class_embeddings = model.encode_text(texts)
23
- class_embedding = F.normalize(class_embeddings, dim=-1).mean(dim=0)
24
- class_embedding /= class_embedding.norm()
25
- zeroshot_weights.append(class_embedding)
26
- zeroshot_weights = torch.stack(zeroshot_weights, dim=1).to(args.device)
27
- return zeroshot_weights
28
-
29
-
30
- def accuracy(output, target, topk=(1,)):
31
- pred = output.topk(max(topk), 1, True, True)[1].t()
32
- correct = pred.eq(target.view(1, -1).expand_as(pred))
33
- return [float(correct[:k].reshape(-1).float().sum(0, keepdim=True).cpu().numpy()) for k in topk]
34
-
35
-
36
- def run(model, classifier, dataloader, args):
37
- autocast = torch.cuda.amp.autocast if args.precision == 'amp' else suppress
38
- with torch.no_grad():
39
- top1, top5, n = 0., 0., 0.
40
- for images, target in tqdm(dataloader, unit_scale=args.batch_size):
41
- images = images.to(args.device)
42
- target = target.to(args.device)
43
-
44
- with autocast():
45
- # predict
46
- if args.distributed and not args.horovod:
47
- image_features = model.module.encode_image(images)
48
- else:
49
- image_features = model.encode_image(images)
50
- image_features = F.normalize(image_features, dim=-1)
51
- logits = 100. * image_features @ classifier
52
-
53
- # measure accuracy
54
- acc1, acc5 = accuracy(logits, target, topk=(1, 5))
55
- top1 += acc1
56
- top5 += acc5
57
- n += images.size(0)
58
-
59
- top1 = (top1 / n)
60
- top5 = (top5 / n)
61
- return top1, top5
62
-
63
-
64
- def zero_shot_eval(model, data, epoch, args):
65
- if 'imagenet-val' not in data and 'imagenet-v2' not in data:
66
- return {}
67
- if args.zeroshot_frequency == 0:
68
- return {}
69
- if (epoch % args.zeroshot_frequency) != 0 and epoch != args.epochs:
70
- return {}
71
-
72
- logging.info('Starting zero-shot imagenet.')
73
-
74
- logging.info('Building zero-shot classifier')
75
- classifier = zero_shot_classifier(model, imagenet_classnames, openai_imagenet_template, args)
76
-
77
- logging.info('Using classifier')
78
- results = {}
79
- if 'imagenet-val' in data:
80
- top1, top5 = run(model, classifier, data['imagenet-val'].dataloader, args)
81
- results['imagenet-zeroshot-val-top1'] = top1
82
- results['imagenet-zeroshot-val-top5'] = top5
83
- if 'imagenet-v2' in data:
84
- top1, top5 = run(model, classifier, data['imagenet-v2'].dataloader, args)
85
- results['imagenetv2-zeroshot-val-top1'] = top1
86
- results['imagenetv2-zeroshot-val-top5'] = top5
87
-
88
- logging.info('Finished zero-shot imagenet.')
89
-
90
- return results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/laion_clap/unit_test.py DELETED
@@ -1,75 +0,0 @@
1
- """
2
- Contrastive Language-Audio Pretraining Model from LAION
3
- --------------------------------------------------------
4
- Paper: https://arxiv.org/abs/2211.06687
5
- Authors (equal contributions): Ke Chen, Yusong Wu, Tianyu Zhang, Yuchen Hui
6
- Support: LAION
7
- """
8
-
9
- import numpy as np
10
- import librosa
11
- import torch
12
- import laion_clap
13
-
14
- # quantization
15
- def int16_to_float32(x):
16
- return (x / 32767.0).astype(np.float32)
17
-
18
-
19
- def float32_to_int16(x):
20
- x = np.clip(x, a_min=-1., a_max=1.)
21
- return (x * 32767.).astype(np.int16)
22
-
23
- model = laion_clap.CLAP_Module(enable_fusion=False)
24
- model.load_ckpt()
25
-
26
- # Directly get audio embeddings from audio files
27
- audio_file = [
28
- '/home/la/kechen/Research/KE_CLAP/ckpt/test_clap_short.wav',
29
- '/home/la/kechen/Research/KE_CLAP/ckpt/test_clap_long.wav'
30
- ]
31
- audio_embed = model.get_audio_embedding_from_filelist(x = audio_file, use_tensor=False)
32
- print(audio_embed[:,-20:])
33
- print(audio_embed.shape)
34
-
35
- # Get audio embeddings from audio data
36
- audio_data, _ = librosa.load('/home/la/kechen/Research/KE_CLAP/ckpt/test_clap_short.wav', sr=48000) # sample rate should be 48000
37
- audio_data = audio_data.reshape(1, -1) # Make it (1,T) or (N,T)
38
- audio_embed = model.get_audio_embedding_from_data(x = audio_data, use_tensor=False)
39
- print(audio_embed[:,-20:])
40
- print(audio_embed.shape)
41
-
42
- # Directly get audio embeddings from audio files, but return torch tensor
43
- audio_file = [
44
- '/home/la/kechen/Research/KE_CLAP/ckpt/test_clap_short.wav',
45
- '/home/la/kechen/Research/KE_CLAP/ckpt/test_clap_long.wav'
46
- ]
47
- audio_embed = model.get_audio_embedding_from_filelist(x = audio_file, use_tensor=True)
48
- print(audio_embed[:,-20:])
49
- print(audio_embed.shape)
50
-
51
- # Get audio embeddings from audio data
52
- audio_data, _ = librosa.load('/home/la/kechen/Research/KE_CLAP/ckpt/test_clap_short.wav', sr=48000) # sample rate should be 48000
53
- audio_data = audio_data.reshape(1, -1) # Make it (1,T) or (N,T)
54
- audio_data = torch.from_numpy(int16_to_float32(float32_to_int16(audio_data))).float() # quantize before send it in to the model
55
- audio_embed = model.get_audio_embedding_from_data(x = audio_data, use_tensor=True)
56
- print(audio_embed[:,-20:])
57
- print(audio_embed.shape)
58
-
59
- # Get text embedings from texts:
60
- text_data = ["I love the contrastive learning", "I love the pretrain model"]
61
- text_embed = model.get_text_embedding(text_data)
62
- print(text_embed)
63
- print(text_embed.shape)
64
-
65
- # Get text embedings from texts, but return torch tensor:
66
- text_data = ["I love the contrastive learning", "I love the pretrain model"]
67
- text_embed = model.get_text_embedding(text_data, use_tensor=True)
68
- print(text_embed)
69
- print(text_embed.shape)
70
-
71
-
72
-
73
-
74
-
75
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/log.py DELETED
@@ -1,976 +0,0 @@
1
- import os
2
- import torch
3
- import time
4
- import numpy as np
5
- #import torchaudio
6
- import plotly.express as px
7
- import soundfile as sf
8
- #import plotly.graph_objects as go
9
- import pandas as pd
10
- import plotly
11
- import plotly.graph_objects as go
12
- import matplotlib.pyplot as plt
13
-
14
- #from notebooks.shared_log_filter import frequencies
15
-
16
- """
17
- Logging related functions that I wrote for my own use
18
- This is quite a mess, but I'm too lazy to clean it up
19
- """
20
- #from src.CQT_nsgt import CQT_cpx
21
-
22
- def do_stft(noisy, clean=None, win_size=2048, hop_size=512, device="cpu", DC=True):
23
- """
24
- applies the stft, this an ugly old function, but I'm using it for logging and I'm to lazy to modify it
25
- """
26
-
27
- #window_fn = tf.signal.hamming_window
28
-
29
- #win_size=args.stft.win_size
30
- #hop_size=args.stft.hop_size
31
- window=torch.hamming_window(window_length=win_size)
32
- window=window.to(noisy.device)
33
- noisy=torch.cat((noisy, torch.zeros(noisy.shape[0],win_size).to(noisy.device)),1)
34
- stft_signal_noisy=torch.stft(noisy, win_size, hop_length=hop_size,window=window,center=False,return_complex=False)
35
- stft_signal_noisy=stft_signal_noisy.permute(0,3,2,1)
36
- #stft_signal_noisy=tf.signal.stft(noisy,frame_length=win_size, window_fn=window_fn, frame_step=hop_size)
37
- #stft_noisy_stacked=tf.stack( values=[tf.math.real(stft_signal_noisy), tf.math.imag(stft_signal_noisy)], axis=-1)
38
-
39
- if clean!=None:
40
-
41
- # stft_signal_clean=tf.signal.stft(clean,frame_length=win_size, window_fn=window_fn, frame_step=hop_size)
42
- clean=torch.cat((clean, torch.zeros(clean.shape[0],win_size).to(device)),1)
43
- stft_signal_clean=torch.stft(clean, win_size, hop_length=hop_size,window=window, center=False,return_complex=False)
44
- stft_signal_clean=stft_signal_clean.permute(0,3,2,1)
45
- #stft_clean_stacked=tf.stack( values=[tf.math.real(stft_signal_clean), tf.math.imag(stft_signal_clean)], axis=-1)
46
-
47
-
48
- if DC:
49
- return stft_signal_noisy, stft_signal_clean
50
- else:
51
- return stft_signal_noisy[...,1:], stft_signal_clean[...,1:]
52
- else:
53
-
54
- if DC:
55
- return stft_signal_noisy
56
- else:
57
- return stft_signal_noisy[...,1:]
58
-
59
- def plot_norms(path, normsscores, normsguides, t, name):
60
- values=t.cpu().numpy()
61
-
62
- df=pd.DataFrame.from_dict(
63
- {"sigma": values[0:-1], "score": normsscores.cpu().numpy(), "guidance": normsguides.cpu().numpy()}
64
- )
65
- fig= px.line(df, x="sigma", y=["score", "guidance"],log_x=True, log_y=True, markers=True)
66
-
67
- path_to_plotly_html = path+"/"+name+".html"
68
-
69
- fig.write_html(path_to_plotly_html, auto_play = False)
70
- return fig
71
-
72
-
73
- def plot_spectral_analysis_sampling( avgspecNF,avgspecDEN, ts, fs=22050, nfft=1024):
74
- T,F=avgspecNF.shape
75
- f=torch.arange(0, F)*fs/nfft
76
- f=f.unsqueeze(1).repeat(1,T).view(-1)
77
- avgspecNF=avgspecNF.permute(1,0).reshape(-1)
78
- avgspecDEN=avgspecDEN.permute(1,0).reshape(-1)
79
- ts=ts.cpu().numpy().tolist()
80
- ts=513*ts
81
- #ts=np.repeat(ts,513)
82
- print(f.shape, avgspecDEN.shape, avgspecNF.shape, len(ts))
83
- df=pd.DataFrame.from_dict(
84
- {"f": f, "noisy": avgspecNF, "denoised": avgspecDEN, "sigma":ts
85
- }
86
- )
87
-
88
- fig= px.line(df, x="f", y=["noisy", "denoised"], animation_frame="sigma", log_x=False,log_y=False, markers=False)
89
- return fig
90
-
91
- def plot_spectral_analysis(avgspecY, avgspecNF,avgspecDEN, ts, fs=22050, nfft=1024):
92
- T,F=avgspecNF.shape
93
- f=torch.arange(0, F)*fs/nfft
94
- f=f.unsqueeze(1).repeat(1,T).view(-1)
95
- avgspecY=avgspecY.squeeze(0).unsqueeze(1).repeat(1,T).view(-1)
96
- avgspecNF=avgspecNF.permute(1,0).reshape(-1)
97
- avgspecDEN=avgspecDEN.permute(1,0).reshape(-1)
98
- ts=ts.cpu().numpy().tolist()
99
- ts=513*ts
100
- #ts=np.repeat(ts,513)
101
- print(f.shape, avgspecY.shape, avgspecNF.shape, len(ts))
102
- df=pd.DataFrame.from_dict(
103
- {"f": f, "y": avgspecY, "noisy": avgspecNF, "denoised": avgspecDEN, "sigma":ts
104
- }
105
- )
106
-
107
- fig= px.line(df, x="f", y=["y", "noisy", "denoised"], animation_frame="sigma", log_x=False,log_y=False, markers=False)
108
- return fig
109
-
110
- def plot_loss_by_sigma_test_snr(average_snr, average_snr_out, t):
111
- #write a fancy plot to log in wandb
112
- values=np.array(t)
113
- df=pd.DataFrame.from_dict(
114
- {"sigma": values, "SNR": average_snr, "SNR_denoised": average_snr_out
115
- }
116
- )
117
-
118
- fig= px.line(df, x="sigma", y=["SNR", "SNR_denoised"],log_x=True, markers=True)
119
- return fig
120
-
121
-
122
-
123
-
124
- # Create and style traces
125
-
126
- def plot_loss_by_sigma(sigma_means, sigma_stds, sigma_bins, log_scale=True):
127
- df=pd.DataFrame.from_dict(
128
- {"sigma": sigma_bins, "loss": sigma_means, "std": sigma_stds
129
- }
130
- )
131
-
132
- fig= error_line('bar', data_frame=df, x="sigma", y="loss", error_y="std", log_x=log_scale, markers=True, range_y=[0, 2])
133
-
134
- return fig
135
-
136
- def plot_loss_by_sigma_and_freq(sigma_freq_means, sigma_freq_stds, sigma_bins, freq_bins):
137
- df=pd.DataFrame.from_dict(
138
- {"sigma": np.tile(sigma_bins, len(freq_bins))})
139
- names=[]
140
- means=[]
141
- stds=[]
142
- for i in range(len(freq_bins)):
143
- name=[str(freq_bins[i])+"Hz" for j in range(len(sigma_bins))]
144
- names.extend(name)
145
- means.extend(sigma_freq_means[i])
146
- stds.extend(sigma_freq_stds[i])
147
- df['freq']=names
148
- df['means']=means
149
- df['stds']=stds
150
- print(df)
151
-
152
-
153
- #basically, we want to plot the loss as a function of sigma and freq. We do it by plotting a line for each freq.
154
- fig= error_line('bar', data_frame=df, x="sigma", y="means", error_y="stds", color="freq", log_x=True, markers=True, range_y=[0, 2])
155
-
156
- return fig
157
-
158
-
159
-
160
- def plot_melspectrogram(X, refr=1):
161
- X=X.squeeze(1) #??
162
- X=X.cpu().numpy()
163
- if refr==None:
164
- refr=np.max(np.abs(X))+1e-8
165
-
166
- S_db = 10*np.log10(np.abs(X)/refr)
167
-
168
- S_db=np.transpose(S_db, (0,2,1))
169
- S_db=np.flip(S_db, axis=1)
170
-
171
- for i in range(X.shape[0]): #iterate over batch size, shity way of ploting all the batched spectrograms
172
- o=S_db[i]
173
- if i==0:
174
- res=o
175
- else:
176
- res=np.concatenate((res,o), axis=1)
177
-
178
- fig=px.imshow( res, zmin=-40, zmax=20)
179
-
180
- fig.update_layout(coloraxis_showscale=False)
181
-
182
- return fig
183
-
184
- def plot_cpxspectrogram(X):
185
- X=X.squeeze(1)
186
- X=X.cpu().numpy()
187
- fig=px.imshow(X, facet_col=3, animation_frame=0)
188
- fig.update_layout(coloraxis_showscale=False)
189
- return fig
190
- def print_cuda_memory():
191
- t = torch.cuda.get_device_properties(0).total_memory
192
- r = torch.cuda.memory_reserved(0)
193
- a = torch.cuda.memory_allocated(0)
194
- f = r-a # free inside reservedk
195
- print("memrylog",t,r,a,f)
196
-
197
-
198
-
199
- def plot_mag_spectrogram(X, refr=None, path=None,name="spec"):
200
- X=X.cpu().numpy()
201
- if refr==None:
202
- refr=np.max(np.abs(X))+1e-8
203
-
204
- S_db = 10*np.log10(np.abs(X)/refr)
205
- S_db=np.flip(S_db, axis=1)
206
-
207
- for i in range(X.shape[0]): #iterate over batch size, shity way of ploting all the batched spectrograms
208
- o=S_db[i]
209
- if i==0:
210
- res=o
211
- else:
212
- res=np.concatenate((res,o), axis=1)
213
-
214
- fig = px.imshow(res, zmin=-40, zmax=20)
215
-
216
- fig.update_layout(coloraxis_showscale=False)
217
-
218
- path_to_plotly_png = path+"/"+name+".png"
219
- plotly.io.write_image(fig, path_to_plotly_png)
220
-
221
- return fig
222
-
223
-
224
- def plot_spectrogram(X, refr=None, cplx=True, freqs=None, time=None):
225
- X=X.squeeze(1)
226
- X=X.cpu().numpy()
227
- if cplx:
228
- X=np.sqrt(X[:,:,:,0]**2 + X[:,:,:,1]**2)
229
-
230
- if refr==None:
231
- refr=np.max(np.abs(X))+1e-8
232
-
233
- S_db = 10*np.log10(np.abs(X)/refr)
234
-
235
- S_db=np.transpose(S_db, (0,2,1))
236
- S_db=np.flip(S_db, axis=1)
237
-
238
- for i in range(X.shape[0]): #iterate over batch size, shity way of ploting all the batched spectrograms
239
- o=S_db[i]
240
- if i==0:
241
- res=o
242
- else:
243
- res=np.concatenate((res,o), axis=1)
244
- #freqs ia a tensor of shape (F,)
245
- #time is a tensor of shape (T,)
246
- #add the frequency and time axis correctly
247
-
248
-
249
- fig=px.imshow( res, zmin=-40, zmax=20)
250
- fig.update_layout(coloraxis_showscale=False)
251
-
252
- return fig
253
-
254
- def write_audio_file(x, sr, string: str, path='tmp', stereo=False, normalize=False):
255
- if normalize:
256
- x=x/torch.max(torch.abs(x))
257
- if not(os.path.exists(path)):
258
- os.makedirs(path)
259
-
260
- path=os.path.join(path,string+".wav")
261
- if stereo:
262
- '''
263
- x has shape (B,2,T)
264
- '''
265
- x=x.permute(0,2,1) #B,T,2
266
- x=x.flatten(0,1) #B*T,2
267
- x=x.cpu().numpy()
268
- #if np.abs(np.max(x))>=1:
269
- # #normalize to avoid clipping
270
- # x=x/np.abs(np.max(x))
271
- else:
272
- x=x.flatten()
273
- x=x.unsqueeze(1)
274
- x=x.cpu().numpy()
275
- #if np.abs(np.max(x))>=1:
276
- # #normalize to avoid clipping
277
- # x=x/np.abs(np.max(x))
278
- sf.write(path,x,sr)
279
- return path
280
-
281
- def plot_trajectories(x,sr, sigma_data):
282
- """
283
- x: (B,T) Batch of trajectories
284
- sr: sampling rate
285
- args: args object
286
- """
287
- x=x.cpu()
288
- times=np.arange(x.shape[1])/sr
289
-
290
- fig=plot_batch_of_lines(x/sigma_data, times, log_x=False)
291
- return fig
292
-
293
- def plot_trajectories_fft(x, sr, sigma_data):
294
- """
295
- x: (B,T) Batch of trajectories
296
- sr: sampling rate
297
- args: args object
298
-
299
- Apply fft to the trajectories and plot the magnitude of the fft
300
- """
301
- x=x.cpu()
302
-
303
- times=np.arange(x.shape[1])/sr
304
- x=x/sigma_data
305
- x_fft=np.fft.rfft(x, axis=1)
306
- x_fft=np.abs(x_fft)
307
- freqs=np.fft.rfftfreq(x.shape[1], d=1/sr)
308
- #now do it on db scale
309
- x_fft_db=10*np.log10(x_fft)
310
- fig=plot_batch_of_lines(x_fft_db, freqs, log_x=False)
311
- return fig
312
- return fig
313
-
314
-
315
- def plot_cpxCQT_from_raw_audio(x, args, refr=None ):
316
- #shape of input spectrogram: ( ,T,F, )
317
- fmax=args.sample_rate/2
318
- fmin=fmax/(2**args.cqt.numocts)
319
- fbins=int(args.cqt.binsoct*args.cqt.numocts)
320
- device=x.device
321
- CQTransform=CQT_cpx(fmin,fbins, args.sample_rate, args.audio_len, device=device, split_0_nyq=False)
322
-
323
- x=x
324
- X=CQTransform.fwd(x)
325
- return plot_cpxspectrogram(X)
326
-
327
- def plot_CQT_from_raw_audio(x, args, refr=None ):
328
- #shape of input spectrogram: ( ,T,F, )
329
- fmax=args.sample_rate/2
330
- fmin=fmax/(2**args.cqt.numocts)
331
- fbins=int(args.cqt.binsoct*args.cqt.numocts)
332
- device=x.device
333
- CQTransform=CQT_cpx(fmin,fbins, args.sample_rate, args.audio_len, device=device, split_0_nyq=False)
334
-
335
- refr=3
336
- x=x
337
- X=CQTransform.fwd(x)
338
- return plot_spectrogram(X, refr)
339
-
340
- def get_spectrogram_from_raw_audio(x, stft, refr=1):
341
- X=do_stft(x, win_size=stft.win_size, hop_size=stft.hop_size)
342
- X=X.permute(0,2,3,1)
343
-
344
- X=X.squeeze(1)
345
- #X=X.cpu().numpy()
346
- X=torch.sqrt(X[:,:,:,0]**2 + X[:,:,:,1]**2)
347
-
348
- #if refr==None:
349
- # refr=np.max(np.abs(X))+1e-8
350
-
351
- S_db = 10*torch.log10(torch.abs(X)/refr)
352
-
353
- S_db=S_db.permute(0,2,1)
354
- #np.transpose(S_db, (0,2,1))
355
- S_db=torch.flip(S_db, [1])
356
-
357
- for i in range(X.shape[0]): #iterate over batch size, shity way of ploting all the batched spectrograms
358
- o=S_db[i]
359
- if i==0:
360
- res=o
361
- else:
362
- res=torch.cat((res,o), 1)
363
- return res
364
-
365
-
366
-
367
- def diffusion_CQT_animation(path, x ,t, args, refr=1, name="animation_diffusion", resample_factor=1 ):
368
- """
369
- Utility for creating an animation of the cqt diffusion process
370
- """
371
- #shape of input spectrograms: (Nsteps,B,Time,Freq)
372
- #print(noisy.shape)
373
- Nsteps=x.shape[0]
374
- numsteps=10
375
- tt=torch.linspace(0, Nsteps-1, numsteps)
376
- i_s=[]
377
- allX=None
378
- device=x.device
379
-
380
- fmax=args.sample_rate/2
381
- fmin=fmax/(2**args.cqt.numocts)
382
- fbins=int(args.cqt.binsoct*args.cqt.numocts)
383
- CQTransform=CQT_cpx(fmin,fbins, args.sample_rate, args.audio_len, device=device, split_0_nyq=False)
384
-
385
- for i in tt:
386
- i=int(torch.floor(i))
387
- i_s.append(i)
388
- xx=x[i]
389
- X=CQTransform.fwd(xx)
390
-
391
- X=torch.sqrt(X[...,0]**2 + X[...,1]**2)
392
-
393
- S_db = 10*torch.log10(torch.abs(X)/refr)
394
- S_db = S_db[:,1:-1,1:-1]
395
-
396
- S_db=S_db.permute(0,2,1)
397
- #np.transpose(S_db, (0,2,1))
398
- S_db=torch.flip(S_db, [1])
399
- S_db=S_db.unsqueeze(0)
400
- S_db=torch.nn.functional.interpolate(S_db, size= (S_db.shape[2]//resample_factor, S_db.shape[3]//resample_factor), mode="bilinear")
401
- S_db=S_db.squeeze(0)
402
- S_db=S_db.cpu().numpy()
403
- #S_db=S_db.squeeze(0)
404
-
405
- if i==0:
406
- allX=S_db
407
- else:
408
- allX=np.concatenate((allX,S_db), 0)
409
-
410
- #fig=px.imshow(S_db, animation_frame=0, facet_col=3, binary_compression_level=0) #I need to incorporate t here!!!
411
- fig=px.imshow(allX, animation_frame=0, zmin=-40, zmax=10, binary_compression_level=0) #I need to incorporate t here!!!
412
-
413
- fig.update_layout(coloraxis_showscale=False)
414
- fig.update_xaxes(showticklabels=False)
415
- fig.update_yaxes(showticklabels=False)
416
-
417
- t=t[i_s].cpu().numpy()
418
-
419
- assert len(t)==len(fig.frames), print(len(t), len(fig.frames))
420
- #print(fig)
421
- for i, f in enumerate(fig.layout.sliders[0].steps):
422
- #hacky way of changing the axis values
423
- #f.args[0]=[str(t[i])]
424
- f.label=str(t[i])
425
-
426
- path_to_plotly_html = path+"/"+name+".html"
427
-
428
- fig.write_html(path_to_plotly_html, auto_play = False)
429
-
430
-
431
- return fig
432
-
433
- def diffusion_joint_filter_animation(path, score, grads , f, t, refr=1, name="animation_diffusion", NT=15 ):
434
- '''
435
- plot an animation of the reverse diffusion process of filters
436
- args:
437
- path: path to save the animation
438
- x: filters (Nsteps, F)
439
- f: frequencies (F)
440
- t: timesteps (sigma)
441
- name: name of the animation
442
- '''
443
- #shape of input spectrograms: (Nsteps,B,Time,Freq)
444
- #print(noisy.shape)
445
- Nsteps=score.shape[0]
446
- #numsteps=min(Nsteps,NT) #hardcoded, I'll probably need more!
447
-
448
- #tt=torch.linspace(0, Nsteps-1, numsteps)
449
- #i_s=[]
450
- #for i in tt:
451
- # i=int(torch.floor(i))
452
- # i_s.append(i)
453
-
454
- print(f.shape, score.shape, grads.shape, t.shape) #19, (100,19)
455
- #fig=px.line(x=f.unsqueeze(0).numpy(),y=x, animation_frame=0) #I need to incorporate t here!!!
456
- sigma=t
457
- s=score.squeeze(1)# (100,19)
458
- g=grads.squeeze(1)# (100,19)
459
- f=f# (19,)
460
- f=f.unsqueeze(0).expand(s.shape[0], -1).reshape(-1)
461
- sigma=sigma.unsqueeze(-1).expand(-1, s.shape[1]).reshape(-1)
462
- s=s.reshape(-1)
463
- g=g.reshape(-1)
464
- max_score=torch.max(s).item()
465
- df=pd.DataFrame(
466
- {
467
- "f": f.cpu().numpy(),
468
- "score": s.cpu().numpy(),
469
- "grads": g.cpu().numpy(),
470
- "sigma": sigma.cpu().numpy()
471
- }
472
- )
473
- fig=px.line(df, x="f",y=["score","grads"], animation_frame="sigma", log_x=True) #I need to incorporate t here!!!
474
- #set the range of th y axis
475
- fig.update_yaxes(range=[-max_score,max_score])
476
- #t=t[i_s].cpu().numpy()
477
- #t=t.cpu().numpy()
478
-
479
- #assert len(t)==len(fig.frames), print(len(t), len(fig.frames))
480
- #print(fig)
481
- #for i, f in enumerate(fig.layout.sliders[0].steps):
482
- #hacky way of changing the axis values
483
- #f.args[0]=[str(t[i])]
484
- # f.label=str(t[i])
485
-
486
- path_to_plotly_html = path+"/"+name+".html"
487
-
488
- fig.write_html(path_to_plotly_html, auto_play = False)
489
-
490
- return fig
491
-
492
- def diffusion_filter_animation(path, x , f, t, refr=1, name="animation_diffusion", NT=15 ):
493
- '''
494
- plot an animation of the reverse diffusion process of filters
495
- args:
496
- path: path to save the animation
497
- x: filters (Nsteps, F)
498
- f: frequencies (F)
499
- t: timesteps (sigma)
500
- name: name of the animation
501
- '''
502
- #shape of input spectrograms: (Nsteps,B,Time,Freq)
503
- #print(noisy.shape)
504
- Nsteps=x.shape[0]
505
- #numsteps=min(Nsteps,NT) #hardcoded, I'll probably need more!
506
-
507
- #tt=torch.linspace(0, Nsteps-1, numsteps)
508
- #i_s=[]
509
- #for i in tt:
510
- # i=int(torch.floor(i))
511
- # i_s.append(i)
512
-
513
- print(f.shape, x.shape, t.shape) #19, (100,19)
514
- #fig=px.line(x=f.unsqueeze(0).numpy(),y=x, animation_frame=0) #I need to incorporate t here!!!
515
- sigma=t
516
- x=x.squeeze(1)# (100,19)
517
- f=f# (19,)
518
- f=f.unsqueeze(0).expand(x.shape[0], -1).reshape(-1)
519
- sigma=sigma.unsqueeze(-1).expand(-1, x.shape[1]).reshape(-1)
520
- x=x.reshape(-1)
521
- df=pd.DataFrame(
522
- {
523
- "f": f.cpu().numpy(),
524
- "x": x.cpu().numpy(),
525
- "sigma": sigma.cpu().numpy()
526
- }
527
- )
528
- fig=px.line(df, x="f",y="x", animation_frame="sigma", log_x=True) #I need to incorporate t here!!!
529
- #t=t[i_s].cpu().numpy()
530
- #t=t.cpu().numpy()
531
-
532
- #assert len(t)==len(fig.frames), print(len(t), len(fig.frames))
533
- #print(fig)
534
- #for i, f in enumerate(fig.layout.sliders[0].steps):
535
- #hacky way of changing the axis values
536
- #f.args[0]=[str(t[i])]
537
- # f.label=str(t[i])
538
-
539
- path_to_plotly_html = path+"/"+name+".html"
540
-
541
- fig.write_html(path_to_plotly_html, auto_play = False)
542
-
543
- return fig
544
-
545
- def diffusion_spec_animation(path, x ,t, stft, refr=1, name="animation_diffusion",NT=15 ):
546
- '''
547
- plot an animation of the reverse diffusion process of filters
548
- args:
549
- path: path to save the animation
550
- x: input audio (N,T)
551
- t: timesteps (sigma)
552
- name: name of the animation
553
- '''
554
- #print(noisy.shape)
555
- Nsteps=x.shape[0]
556
- numsteps=min(Nsteps,NT) #hardcoded, I'll probably need more!
557
- tt=torch.linspace(0, Nsteps-1, numsteps)
558
- i_s=[]
559
- allX=None
560
- for i in tt:
561
- i=int(torch.floor(i))
562
- i_s.append(i)
563
- X=get_spectrogram_from_raw_audio(x[i],stft, refr)
564
- X=X.unsqueeze(0)
565
- if allX==None:
566
- allX=X
567
- else:
568
- allX=torch.cat((allX,X), 0)
569
-
570
-
571
- allX=allX.cpu().numpy()
572
- fig=px.imshow(allX, animation_frame=0, zmin=-40, zmax=20) #I need to incorporate t here!!!
573
-
574
- fig.update_layout(coloraxis_showscale=False)
575
-
576
- t=t[i_s].cpu().numpy()
577
-
578
- assert len(t)==len(fig.frames), print(len(t), len(fig.frames))
579
- #print(fig)
580
- for i, f in enumerate(fig.layout.sliders[0].steps):
581
- #hacky way of changing the axis values
582
- #f.args[0]=[str(t[i])]
583
- f.label=str(t[i])
584
-
585
- path_to_plotly_html = path+"/"+name+".html"
586
-
587
- fig.write_html(path_to_plotly_html, auto_play = False)
588
-
589
-
590
- return fig
591
-
592
- def plot_spectrogram_from_raw_audio(x, stft, refr=None ):
593
- #shape of input spectrogram: ( ,T,F, )
594
- refr=3
595
- x=x
596
- X=do_stft(x, win_size=stft.win_size, hop_size=stft.hop_size)
597
- X=X.permute(0,2,3,1)
598
- return plot_spectrogram(X, refr)
599
-
600
- def plot_spectrogram_from_cpxspec(X, refr=None):
601
- #shape of input spectrogram: ( ,T,F, )
602
- return plot_spectrogram(X, refr)
603
-
604
-
605
- def error_line(error_y_mode='band', **kwargs):
606
- print("hello error line", error_y_mode)
607
- """Extension of `plotly.express.line` to use error bands."""
608
- ERROR_MODES = {'bar','band','bars','bands',None}
609
- if error_y_mode not in ERROR_MODES:
610
- raise ValueError(f"'error_y_mode' must be one of {ERROR_MODES}, received {repr(error_y_mode)}.")
611
- if error_y_mode in {'bar','bars',None}:
612
- print("Using error bars", kwargs)
613
- fig = px.line(**kwargs)
614
- elif error_y_mode in {'band','bands'}:
615
- if 'error_y' not in kwargs:
616
- raise ValueError(f"If you provide argument 'error_y_mode' you must also provide 'error_y'.")
617
- figure_with_error_bars = px.line(**kwargs)
618
- fig = px.line(**{arg: val for arg,val in kwargs.items() if arg != 'error_y'})
619
- for data in figure_with_error_bars.data:
620
- x = list(data['x'])
621
- y_upper = list(data['y'] + data['error_y']['array'])
622
- y_lower = list(data['y'] - data['error_y']['array'] if data['error_y']['arrayminus'] is None else data['y'] - data['error_y']['arrayminus'])
623
- color = f"rgba({tuple(int(data['line']['color'].lstrip('#')[i:i+2], 16) for i in (0, 2, 4))},.3)".replace('((','(').replace('),',',').replace(' ','')
624
- fig.add_trace(
625
- go.Scatter(
626
- x = x+x[::-1],
627
- y = y_upper+y_lower[::-1],
628
- fill = 'toself',
629
- fillcolor = color,
630
- line = dict(
631
- color = 'rgba(255,255,255,0)'
632
- ),
633
- hoverinfo = "skip",
634
- showlegend = False,
635
- legendgroup = data['legendgroup'],
636
- xaxis = data['xaxis'],
637
- yaxis = data['yaxis'],
638
- )
639
- )
640
- # Reorder data as said here: https://stackoverflow.com/a/66854398/8849755
641
- reordered_data = []
642
- for i in range(int(len(fig.data)/2)):
643
- reordered_data.append(fig.data[i+int(len(fig.data)/2)])
644
- reordered_data.append(fig.data[i])
645
- fig.data = tuple(reordered_data)
646
- return fig
647
-
648
-
649
- def plot_filters( x, freqs):
650
- '''
651
- This function plots a batch of lines using plotly
652
- args:
653
- x: (B, F)
654
- '''
655
- fig = px.line(x=freqs, y=x[0,:], log_x=True)
656
-
657
- for i in range(1,x.shape[0]):
658
- fig.add_trace(go.Scatter(x=freqs, y=x[i,:]))
659
- return fig
660
-
661
- def plot_batch_of_lines( x, freqs, log_x=True):
662
- '''
663
- This function plots a batch of lines using plotly
664
- args:
665
- x: (B, F)
666
- '''
667
- fig = px.line(x=freqs, y=x[0,:], log_x=log_x)
668
-
669
- for i in range(1,x.shape[0]):
670
- fig.add_trace(go.Scatter(x=freqs, y=x[i,:]))
671
- return fig
672
-
673
-
674
-
675
-
676
- def plot_rir(rir, T_separation, fs, path=None, name=None, save=True):
677
- '''
678
- Plot the RIR and indicate the separation time between early and late reflections
679
- '''
680
- rir = rir.cpu()
681
- RIR = torch.stft(rir, n_fft=512, hop_length=128, window=torch.hann_window(512).to(rir.device), return_complex=True)
682
-
683
- fig, ax = plt.subplots(1, 2)
684
-
685
- ax[0].pcolormesh(
686
- torch.linspace(0, RIR.size(-1)/128, RIR.size(-1)),
687
- torch.linspace(0, fs/2, RIR.size(-2)),
688
- 20*torch.log10(RIR.abs() + 1e-10))
689
-
690
- ax[1].plot(
691
- torch.linspace(0, rir.size(-1)/fs, rir.size(-1)),
692
- rir)
693
-
694
- if save:
695
- plt.savefig(os.path.join(path, name+".png"))
696
- else:
697
- return fig
698
-
699
-
700
- def create_mag_phase_plot():
701
- # Create subplots
702
- from plotly.subplots import make_subplots
703
-
704
- # fig = make_subplots(rows=2, cols=1, vertical_spacing=0.1,
705
- # subplot_titles=('Magnitude Response', 'Phase Response'))
706
- fig = make_subplots(specs=[[{"secondary_y": True}]])
707
- # Update layout
708
- fig.update_layout(height=600, width=800, title_text="Magnitude and Phase of Filter")
709
- # fig.update_xaxes(type='log',title_text="Frequency [Hz])", row=2, col=1)
710
- fig.update_xaxes(type='log', title_text="Frequency [Hz])")
711
- # fig.update_yaxes(title_text="Magnitude [dB]", row=1, col=1)
712
- # fig.update_yaxes(title_text="Phase [rad]", row=2, col=1)
713
- # Set y-axes titles
714
- fig.update_yaxes(title_text="<b>Magnitude [dB]</b>", secondary_y=False)
715
- fig.update_yaxes(title_text="<b>Phase [rad]</b>", secondary_y=True)
716
- return fig
717
-
718
-
719
- def plot_STFT(x, operator, type='mag_dB', min_freq=100):
720
-
721
- # Ensure that shape is (B, F, T)
722
- fs = operator.sample_rate
723
- nfft = operator.n_fft
724
- hop_size = operator.hop_length
725
-
726
- print(x.device, operator.device)
727
- X = operator.apply_stft(x.to(operator.device))
728
-
729
- print(X.shape, x.shape, X.device)
730
-
731
- # , X.device)
732
- if type == 'mag':
733
- x_spec = X.abs()
734
- if type == 'mag_dB':
735
- x_spec = 20*torch.log10(X.abs()+1e-7)
736
- if type == 'phase':
737
- x_spec = X.angle()
738
- # if type == 'phase_unwrap':
739
- # x_spec = X.angle().grad()
740
-
741
-
742
- if "mag" in type:
743
- z_min=-100
744
- z_max= 10
745
- else:
746
- z_min=-np.pi
747
- z_max= np.pi
748
-
749
-
750
-
751
-
752
- frequencies = torch.fft.rfftfreq(nfft) * fs
753
- timestamps = torch.arange(X.shape[-1]) * (hop_size / fs)
754
- fig = go.Figure(data=go.Heatmap(
755
- z=x_spec.squeeze(0).cpu().numpy(),
756
- x=timestamps.numpy(),
757
- y=frequencies.numpy(),
758
- colorscale='Magma',
759
- zmax=z_max,
760
- zmin=z_min
761
- ))
762
-
763
- fig.update_layout(
764
- yaxis=dict(
765
- type='log',
766
- title='Frequency(Hz)',
767
- range= [np.log10(min_freq), np.log10(frequencies[-1].item())],
768
- ),
769
- xaxis_title='Time(s)',
770
- title='Magnitude'
771
- )
772
-
773
- #define min and max for frequency axis
774
-
775
- return fig
776
-
777
- def lineplot(x=None, dict=None, xaxis="linear", yaxis="log"):
778
- assert dict is not None
779
-
780
- y=dict.items()
781
- y=torch.stack([v for k,v in y])
782
-
783
- labels = [k for k,v in dict.items()]
784
-
785
-
786
-
787
- # Ensure y is a NumPy array for compatibility with Plotly
788
- if isinstance(y, torch.Tensor):
789
- y = y.detach().cpu().numpy()
790
-
791
- if x is None:
792
- x = np.arange(y.shape[-1]) # Ensure x is NumPy array too
793
- elif isinstance(x, torch.Tensor):
794
- x = x.detach().cpu().numpy()
795
-
796
- # Validate labels
797
- num_series = y.shape[0]
798
- if labels is None:
799
- labels = [f"Series {i}" for i in range(num_series)]
800
- elif len(labels) != num_series:
801
- raise ValueError(f"Expected {num_series} labels, but got {len(labels)}.")
802
-
803
- # Convert to DataFrame for Plotly
804
- df = pd.DataFrame(y.T, index=x, columns=labels) # Use labels as column names
805
- df.index.name = "x"
806
- df = df.reset_index()
807
-
808
- # Melt DataFrame for Plotly (long format)
809
- df_melted = df.melt(id_vars=["x"], var_name="series", value_name="y")
810
-
811
- fig = px.line(df_melted, x="x", y="y", color="series", labels={"y": "Value", "x": "t", "series": "Legend"})
812
-
813
- # Set y-axis to log scale
814
- if yaxis=="log":
815
- fig.update_yaxes(type="log")
816
- #fix range from 1e-4 to 10
817
- fig.update_yaxes(range=[-4, 1])
818
- if xaxis=="log":
819
- fig.update_xaxes(type="log")
820
-
821
-
822
- return fig
823
-
824
- def make_PCA_figure(data_dict, num_bins=20, title="PCA"):
825
-
826
- fig = plt.figure(figsize=(6, 5), dpi=200)
827
- gs = fig.add_gridspec(
828
- 2,
829
- 2,
830
- width_ratios=(4, 1),
831
- height_ratios=(1, 4),
832
- left=0.1,
833
- right=0.9,
834
- bottom=0.1,
835
- top=0.9,
836
- )
837
-
838
- # Create the Axes.
839
- ax = fig.add_subplot(gs[1, 0])
840
- ax_histx = fig.add_subplot(gs[0, 0], sharex=ax)
841
- ax_histy = fig.add_subplot(gs[1, 1], sharey=ax)
842
- # Draw the scatter plot and marginals.
843
- # no labels
844
- ax_histx.tick_params(axis="x", labelbottom=False)
845
- ax_histy.tick_params(axis="y", labelleft=False)
846
-
847
- xymax=0
848
- xymin=999999
849
- markers= ["o", "x", "D", "^", "v", "x", "p", "*", "h", "+", "s"]
850
- colors=["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b"]
851
-
852
-
853
- for i,(k, v) in enumerate(data_dict.items()):
854
- if v is None or len(v) == 0:
855
- continue # Skip empty or None values
856
-
857
- # the scatter plot:
858
- ax.scatter(
859
- v[:, 0],
860
- v[:, 1],
861
- marker=markers[i % len(markers)],
862
- c=colors[i % len(colors)],
863
- linewidths=0.8,
864
- s=24,
865
- label=k,
866
- alpha=0.7,
867
- )
868
-
869
- try:
870
- xymax=max(xymax, np.max(v[:, :2]))
871
- xymin=min(xymin, np.min(v[:, :2]))
872
- except Exception as e:
873
- print(f"Error processing data for {k}: {e}")
874
- print("data:",v)
875
- print(f"Data shape: {v.shape}")
876
- continue
877
-
878
- ax.legend(loc="upper left")
879
- ax.set_xlabel("PC 1")
880
- ax.set_ylabel("PC 2")
881
-
882
- # now determine nice limits by hand:
883
- data_range = xymax - xymin
884
- binwidth = data_range / num_bins # Calculate bin width based on the range and number of bins
885
-
886
- lim = (int(xymax / binwidth) + 1) * binwidth
887
- bins = np.arange(-lim, lim + binwidth, binwidth)
888
-
889
- style = { "edgecolor": "black", "linewidth": 0.8, "alpha": 0.5 }
890
-
891
-
892
-
893
- #linestyles=[":", "-", "--", "-.", ":", "-"]
894
- facecolors=["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b"]
895
- hatches = ['/', '\\', '.', '+', '*', 'o', 'O', '.', '-', '|', 'x']
896
-
897
- for i, (k, v) in enumerate(data_dict.items()):
898
-
899
- hist_style = style.copy()
900
- hist_style["hatch"] = hatches[i % len(hatches)]
901
-
902
- ax_histx.hist(v[:, 0], bins=bins, density=False, label=k, facecolor=facecolors[i], **hist_style)
903
-
904
- ax_histy.hist(
905
- v[:, 1],
906
- bins=bins,
907
- orientation="horizontal",
908
- density=False,
909
- facecolor=facecolors[i],
910
- **hist_style
911
- )
912
-
913
- ax_histx.set_ylabel("Count")
914
- ax_histy.set_xlabel("Count")
915
-
916
- #ax_histx.legend(loc="upper right")
917
-
918
- #add title
919
- fig.suptitle(title, fontsize=16, fontweight='bold')
920
-
921
- return fig
922
-
923
- def make_histogram_figure(data_dict, num_bins=20):
924
-
925
- fig = plt.figure(figsize=(5, 3), dpi=100)
926
-
927
- # Create the Axes.
928
- #ax = fig.add_subplot(gs[1, 0])
929
- #ax_histx = fig.add_subplot(gs[0, 0], sharex=ax)
930
- #ax_histy = fig.add_subplot(gs[1, 1], sharey=ax)
931
- # Draw the scatter plot and marginals.
932
- # no labels
933
-
934
- #get ax from the figure
935
- ax = fig.add_subplot(111)
936
-
937
- ax.tick_params(axis="x", labelbottom=True)
938
-
939
-
940
- xymax=0
941
- xymin=999999
942
-
943
- for i,(k, v) in enumerate(data_dict.items()):
944
-
945
- xymax=max(xymax, np.max(v))
946
- xymin=min(xymin, np.min(v))
947
-
948
-
949
- data_range = xymax - xymin
950
- binwidth = data_range / num_bins # Calculate bin width based on the range and number of bins
951
-
952
- lim_up = (int(xymax / binwidth) + 1) * binwidth+binwidth
953
- lim_down= (int(xymin / binwidth) - 1) * binwidth-binwidth
954
-
955
- bins = np.arange(lim_down, lim_up, binwidth)
956
-
957
- style = { "edgecolor": "black", "linewidth": 0.8, "alpha": 0.5 }
958
-
959
- #linestyles=[":", "-", "--", "-.", ":", "-"]
960
- facecolors=["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b"]
961
- hatches = ['/', '\\', '.', '+', '*', 'o', 'O', '.', '-', '|', 'x']
962
-
963
- for i, (k, v) in enumerate(data_dict.items()):
964
-
965
- hist_style = style.copy()
966
- hist_style["hatch"] = hatches[i % len(hatches)]
967
-
968
- ax.hist(v[:], bins=bins, density=False, label=k, facecolor=facecolors[i], **hist_style)
969
-
970
- ax.set_ylabel("Count")
971
-
972
- ax.legend(loc="upper right")
973
-
974
-
975
- return fig
976
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/st_ito/__init__.py DELETED
File without changes
utils/st_ito/callbacks.py DELETED
@@ -1,164 +0,0 @@
1
- import os
2
- import wandb
3
- import torch
4
- import shutil
5
- import numpy as np
6
- import pytorch_lightning as pl
7
- import matplotlib.pyplot as plt
8
-
9
- from typing import Any
10
- from sklearn.metrics import confusion_matrix
11
- from pytorch_lightning.utilities.types import STEP_OUTPUT
12
-
13
-
14
- # trainer.logger.experiment.dir: /import/c4dm-datasets-ext/wandb/run-20230826_192342-4zjz1inp/files
15
-
16
-
17
- class LogAudioCallback(pl.Callback):
18
- def __init__(self) -> None:
19
- super().__init__()
20
-
21
- def on_validation_batch_end(
22
- self,
23
- trainer: pl.Trainer,
24
- pl_module: pl.LightningModule,
25
- outputs: STEP_OUTPUT,
26
- batch: Any,
27
- batch_idx: int,
28
- dataloader_idx: int = 0,
29
- ) -> None:
30
- # Only log audio for the first batch
31
- if batch_idx > 0:
32
- return
33
- # Log audio to wandb
34
- else:
35
- data_dict = outputs
36
- silence_gap = 48000
37
-
38
- # check the batch size
39
- batch_size = data_dict["input_audio_A"].shape[0]
40
-
41
- for bidx in range(min(4, batch_size)):
42
- audio_tensors = []
43
- for key in ["input_audio_A", "target_audio_A", "output_audio_A"]:
44
- audio_segment = data_dict[key][bidx, ...]
45
- chs, seq_len = audio_segment.shape
46
-
47
- if chs == 1: # make it stereo
48
- audio_segment = audio_segment.repeat(2, 1)
49
-
50
- audio_tensors.append(audio_segment)
51
-
52
- # check the length of the audio tensors
53
- total_audio_length = sum(
54
- [audio_tensor.shape[-1] for audio_tensor in audio_tensors]
55
- )
56
-
57
- # create a single tensor to hold the audio
58
- output_tensor = torch.zeros((total_audio_length + 2 * silence_gap, 2))
59
-
60
- # copy the audio tensors into the single tensor
61
- start_idx = 0
62
- for audio_tensor in audio_tensors:
63
- end_idx = start_idx + audio_tensor.shape[-1]
64
- output_tensor[start_idx:end_idx, :] = audio_tensor.permute(1, 0)
65
- start_idx = end_idx + silence_gap
66
-
67
- trainer.logger.experiment.log(
68
- {
69
- f"{bidx}-audio": wandb.Audio(
70
- output_tensor.cpu().numpy(), sample_rate=48000
71
- ),
72
- }
73
- )
74
-
75
-
76
- class MoveConfigCallback(pl.Callback):
77
- def __init__(self, project_name: str) -> None:
78
- super().__init__()
79
- self.project_name = project_name
80
-
81
- def on_fit_start(self, trainer, pl_module):
82
- full_run_dir = trainer.logger.experiment.dir
83
- run_id = full_run_dir.split(os.sep)[-2].split("-")[-1]
84
- src_dir = os.path.join(trainer.log_dir, "config.yaml")
85
- dest_dir = os.path.join(
86
- trainer.log_dir, self.project_name, run_id, "checkpoints", "config.yaml"
87
- )
88
-
89
- run_dir = os.path.join(trainer.log_dir, self.project_name, run_id)
90
- ckpt_dir = os.path.join(run_dir, "checkpoints")
91
- os.makedirs(run_dir, exist_ok=True)
92
- os.makedirs(ckpt_dir, exist_ok=True)
93
-
94
- shutil.copyfile(src_dir, dest_dir)
95
-
96
-
97
- class ConfusionMatrixCallback(pl.Callback):
98
- def __init__(self) -> None:
99
- super().__init__()
100
- self.outputs = []
101
-
102
- def on_validation_batch_end(
103
- self,
104
- trainer: pl.Trainer,
105
- pl_module: pl.LightningModule,
106
- outputs: STEP_OUTPUT,
107
- batch: Any,
108
- batch_idx: int,
109
- dataloader_idx: int = 0,
110
- ) -> None:
111
- logits, labels = outputs
112
- self.outputs.append(outputs)
113
-
114
- def on_validation_epoch_end(
115
- self, trainer: pl.Trainer, pl_module: pl.LightningModule
116
- ) -> None:
117
- labels = list(trainer.datamodule.val_dataset.plugins.keys())
118
-
119
- # Concatenate logits and labels from all batches in the validation set
120
- all_logits = torch.cat([output[0] for output in self.outputs], dim=0)
121
- all_labels = torch.cat([output[1] for output in self.outputs], dim=0)
122
-
123
- # Convert logits to predicted labels
124
- predicted_labels = torch.argmax(all_logits, dim=-1)
125
-
126
- # Compute confusion matrix
127
- cm = confusion_matrix(all_labels.cpu().numpy(), predicted_labels.cpu().numpy())
128
-
129
- # Plot confusion matrix using matplotlib
130
- num_classes = cm.shape[0]
131
- class_labels = [str(i) for i in labels]
132
-
133
- if num_classes != len(class_labels):
134
- print(
135
- f"Number of classes ({num_classes}) does not match number of class labels ({len(class_labels)}). Skipping."
136
- )
137
- return
138
-
139
- fig, ax = plt.subplots(figsize=(num_classes // 2, num_classes // 2))
140
- plt.imshow(cm, interpolation="nearest", cmap=plt.cm.Blues)
141
- plt.title("Confusion Matrix")
142
- plt.colorbar()
143
-
144
- tick_marks = np.arange(num_classes)
145
- plt.xticks(tick_marks, class_labels, rotation=90)
146
- ax.xaxis.set_tick_params(top=True)
147
- plt.yticks(tick_marks, class_labels)
148
-
149
- plt.xlabel("Predicted")
150
- plt.ylabel("True")
151
- plt.tight_layout()
152
-
153
- for i in range(num_classes):
154
- for j in range(num_classes):
155
- plt.text(
156
- j, i, str(cm[i, j]), horizontalalignment="center", color="black"
157
- )
158
-
159
- # log confusion matrix to wandb
160
- trainer.logger.experiment.log({"confusion_matrix": plt})
161
- plt.close("all")
162
-
163
- # reset outputs
164
- self.outputs = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/st_ito/config.yaml DELETED
@@ -1,151 +0,0 @@
1
- # pytorch_lightning==2.1.2
2
- seed_everything: 4000152388
3
- trainer:
4
- accelerator: gpu
5
- strategy: ddp_find_unused_parameters_true
6
- devices: -1
7
- num_nodes: 1
8
- precision: 32
9
- logger:
10
- class_path: pytorch_lightning.loggers.WandbLogger
11
- init_args:
12
- name: null
13
- save_dir: /import/c4dm-datasets-ext/lcap/param
14
- version: null
15
- offline: false
16
- dir: null
17
- id: null
18
- anonymous: null
19
- project: lcap-param
20
- log_model: false
21
- experiment: null
22
- prefix: ''
23
- checkpoint_name: null
24
- job_type: null
25
- config: null
26
- entity: null
27
- reinit: null
28
- tags: null
29
- group: null
30
- notes: null
31
- magic: null
32
- config_exclude_keys: null
33
- config_include_keys: null
34
- mode: null
35
- allow_val_change: null
36
- resume: null
37
- force: null
38
- tensorboard: null
39
- sync_tensorboard: null
40
- monitor_gym: null
41
- save_code: null
42
- settings: null
43
- callbacks:
44
- - class_path: pytorch_lightning.callbacks.ModelCheckpoint
45
- init_args:
46
- dirpath: null
47
- filename: '{epoch}-{step}'
48
- monitor: val_loss
49
- verbose: false
50
- save_last: true
51
- save_top_k: 1
52
- save_weights_only: false
53
- mode: min
54
- auto_insert_metric_name: true
55
- every_n_train_steps: null
56
- train_time_interval: null
57
- every_n_epochs: null
58
- save_on_train_epoch_end: null
59
- enable_version_counter: true
60
- - class_path: pytorch_lightning.callbacks.ModelSummary
61
- init_args:
62
- max_depth: 2
63
- - class_path: pytorch_lightning.callbacks.LearningRateMonitor
64
- init_args:
65
- logging_interval: null
66
- log_momentum: false
67
- log_weight_decay: false
68
- - class_path: lcap.callbacks.MoveConfigCallback
69
- init_args:
70
- project_name: lcap-param
71
- - class_path: lcap.callbacks.ConfusionMatrixCallback
72
- fast_dev_run: false
73
- max_epochs: 250
74
- min_epochs: null
75
- max_steps: -1
76
- min_steps: null
77
- max_time: null
78
- limit_train_batches: null
79
- limit_val_batches: null
80
- limit_test_batches: null
81
- limit_predict_batches: null
82
- overfit_batches: 0.0
83
- val_check_interval: null
84
- check_val_every_n_epoch: 5
85
- num_sanity_val_steps: 32
86
- log_every_n_steps: 100
87
- enable_checkpointing: null
88
- enable_progress_bar: null
89
- enable_model_summary: null
90
- accumulate_grad_batches: 1
91
- gradient_clip_val: null
92
- gradient_clip_algorithm: null
93
- deterministic: null
94
- benchmark: true
95
- inference_mode: true
96
- use_distributed_sampler: true
97
- profiler: null
98
- detect_anomaly: false
99
- barebones: false
100
- plugins: null
101
- sync_batchnorm: true
102
- reload_dataloaders_every_n_epochs: 0
103
- default_root_dir: /import/c4dm-datasets-ext/lcap/param
104
- ckpt_path: null
105
- model:
106
- class_path: lcap.methods.param.ParameterEstimator
107
- init_args:
108
- encoder:
109
- class_path: lcap.models.panns.Cnn14
110
- init_args:
111
- embed_dim: 512
112
- sample_rate: 48000.0
113
- window_size: 2048
114
- hop_size: 1024
115
- mel_bins: 128
116
- fmin: 20.0
117
- fmax: 20000.0
118
- use_batchnorm: true
119
- input_norm: minmax
120
- lr: 0.0001
121
- num_instances: 63
122
- num_presets: 10
123
- weight_decay: 0.0001
124
- max_epochs: 250
125
- embed_mode: concat
126
- norm: L2
127
- data:
128
- class_path: lcap.dataset.dataset_param.PluginTarfileDataModule
129
- init_args:
130
- train_tarfiles:
131
- - /import/c4dm-datasets-ext/lcap-datasets/20k/musdb18-vst3-presets.tar
132
- - /import/c4dm-datasets-ext/lcap-datasets/20k/mtg-jamendo-vst3-presets.tar
133
- - /import/c4dm-datasets-ext/lcap-datasets/20k/ursing-vst3-presets.tar
134
- - /import/c4dm-datasets-ext/lcap-datasets/20k/enst-drums-vst3-presets.tar
135
- - /import/c4dm-datasets-ext/lcap-datasets/20k/fsd50k-vst3-presets.tar
136
- - /import/c4dm-datasets-ext/lcap-datasets/20k/librispeech-vst3-presets.tar
137
- - /import/c4dm-datasets-ext/lcap-datasets/20k/medley-solos-db-vst3-presets.tar
138
- - /import/c4dm-datasets-ext/lcap-datasets/20k/guitarset-vst3-presets.tar
139
- val_tarfiles:
140
- - /import/c4dm-datasets-ext/lcap-datasets/20k/guitarset-vst3-presets.tar
141
- plugin_json: plugins/valid_plugins.json
142
- batch_size: 64
143
- num_workers: 8
144
- length: 262144
145
- stereo: true
146
- test_tarfiles: null
147
- pin_memory: true
148
- persistent_workers: true
149
- num_train_examples: 140000
150
- num_val_examples: 2000
151
- num_test_examples: 2000
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/st_ito/dataset/dataset_param.py DELETED
@@ -1,595 +0,0 @@
1
- import os
2
- import io
3
- import sys
4
- import glob
5
- import json
6
- import math
7
- import torch
8
- import tarfile
9
- import torchaudio
10
- import numpy as np
11
- import pytorch_lightning as pl
12
-
13
- from tqdm import tqdm
14
- from typing import List
15
- from torch.utils.data import Dataset, IterableDataset
16
-
17
-
18
- def tarfile_worker_init_fn(worker_id):
19
- worker_info = torch.utils.data.get_worker_info()
20
- dataset = worker_info.dataset # the dataset copy in this worker process
21
-
22
- # if there is more than one tar file in the dataset, then we need to
23
- # select one tar file for this worker
24
- print(f"Worker {worker_id} creating new tar file handles.")
25
-
26
- # reopen the tarfile handles for this worker
27
- dataset.tar_handles = []
28
- for tar_file in dataset.tar_files:
29
- tar_handle = tarfile.open(tar_file, "r:")
30
- tar_handle.next() # skip the first root directory
31
- tar_handle.next() # skip the first example directory
32
- dataset.tar_handles.append(tar_handle)
33
-
34
-
35
- def torchaudio_decode(b):
36
- x, sr = torchaudio.load(b, format="FLAC")
37
- return x
38
-
39
-
40
- class PluginTarfileDataset(IterableDataset):
41
- def __init__(
42
- self,
43
- tar_files: List[str],
44
- plugin_json: str,
45
- length: int = 262144,
46
- stereo: bool = True,
47
- num_examples: int = 20000,
48
- label_files: List[str] = None,
49
- ) -> None:
50
- super().__init__()
51
- """ Interable dataset that returns a single input/output pair from a tar file.
52
-
53
- When multiple tar files are provided, we open a file handle to each tar file. Then on each
54
- iteration we select a tar file at random and then select a random example from that tar file.
55
- We repeat this until each tar file has been read once. Then we start over again.
56
-
57
- Args:
58
- tar_files (List[str]): List of tar files to read from.
59
- plugin_json (str): Path to json file containing plugin instance details.
60
- length (int): Length of audio to return. Defaults to 262144.
61
- stereo (bool): whether to return stereo audio. Defaults to True.
62
- When set to False, the mean of the two channels is returned.
63
- num_examples (int): Number of examples to return. Defaults to 20000.
64
- label_files (List[str]): List of label files to use for classification. Defaults to None.
65
- Note: they must be int he same order as the associated tar_files.
66
-
67
- """
68
- self.tar_files = tar_files
69
- self.length = length
70
- self.stereo = stereo
71
- self.num_examples = num_examples
72
- self.label_files = label_files
73
-
74
- # first open plugin info
75
- # this is used for indexing into the plugin list
76
- with open(plugin_json, "r") as f:
77
- self.plugins = json.load(f)
78
-
79
- # get the tar file handles
80
- self.tar_handles = []
81
- for tar_file in tar_files:
82
- tar_handle = tarfile.open(tar_file, "r:")
83
- tar_handle.next() # skip the first root directory
84
- tar_handle.next() # skip the first example directory
85
- self.tar_handles.append(tar_handle)
86
-
87
- # check if we have label files
88
- if label_files is not None:
89
- assert len(label_files) == len(tar_files) # must be the same length
90
- self.labels = []
91
- for label_file in label_files:
92
- results = torch.load(label_file)
93
- self.labels.append(results)
94
-
95
- def __len__(self):
96
- return self.num_examples
97
-
98
- def next_example(self):
99
- while True:
100
- # select tar file at random
101
- tar_index = np.random.choice(len(self.tar_handles))
102
-
103
- # get the next member (should be root directory of this example)
104
- members = {}
105
- done = False
106
- handle_reset = False
107
- while not done:
108
- # get next member
109
- member = self.tar_handles[tar_index].next()
110
-
111
- if member is None:
112
- # we have reached the end of this tar file
113
- # so, we will close it and remove open tar handle
114
-
115
- # close this tar handle
116
- self.tar_handles[tar_index].close()
117
-
118
- # reopen
119
- tar_handle = tarfile.open(self.tar_files[tar_index], "r:")
120
- tar_handle.next() # skip the first root directory
121
- tar_handle.next() # skip the first example directory
122
- self.tar_handles[tar_index] = tar_handle # replace handle
123
- print(f"Resetting tar handle {self.tar_files[tar_index]}.")
124
- handle_reset = True
125
- break
126
-
127
- if member.isdir():
128
- done = True
129
- break
130
-
131
- members[os.path.basename(member.name)] = member
132
-
133
- if handle_reset:
134
- continue
135
-
136
- # get input file
137
- input_file = self.tar_handles[tar_index].extractfile(members["input.flac"])
138
- input = torchaudio_decode(input_file)
139
-
140
- parent_dir = os.path.dirname(members["input.flac"].name).split("/")[-1]
141
-
142
- # select at random from the processed files
143
- processed_members = [
144
- x for x in members.keys() if x != "input.flac" and ".json" not in x
145
- ]
146
- processed_member = np.random.choice(processed_members)
147
- processed_fp = self.tar_handles[tar_index].extractfile(
148
- members[processed_member]
149
- )
150
- output = torchaudio_decode(processed_fp)
151
-
152
- # load the json file
153
- json_member = members[processed_member.replace(".flac", ".json")]
154
- json_fp = self.tar_handles[tar_index].extractfile(json_member)
155
- details = json.load(json_fp)
156
-
157
- # if using labels, get the classification info
158
- if self.label_files is not None:
159
- print(
160
- tar_index, members["input.flac"].name, self.label_files[tar_index]
161
- )
162
- label_info = self.labels[tar_index][parent_dir]
163
- predicted_class_id = label_info["predicted_class_ids"]
164
- predicted_label = label_info["predicted_label"]
165
- logits = label_info["logits"].squeeze()
166
- else:
167
- logits = torch.empty(0)
168
-
169
- # get the index of the plugin instance for classification
170
- instance_index = self.plugins[details["instance"]]
171
- instance_index -= 1 # zero index
172
- instance_index = torch.tensor(instance_index).long()
173
- preset_index = torch.tensor(details["preset"]).long()
174
-
175
- # fit the proper length
176
- if input.shape[-1] < self.length:
177
- input = torch.cat(
178
- [
179
- input,
180
- torch.zeros([input.shape[0], self.length - input.shape[-1]]),
181
- ],
182
- dim=-1,
183
- )
184
- if output.shape[-1] < self.length:
185
- output = torch.cat(
186
- [
187
- output,
188
- torch.zeros([output.shape[0], self.length - output.shape[-1]]),
189
- ],
190
- dim=-1,
191
- )
192
-
193
- # take a different random crop from each file
194
- if input.shape[-1] > self.length:
195
- start = torch.randint(0, (input.shape[-1] - self.length), [1])
196
- input = input[..., start : start + self.length]
197
-
198
- # take a random crop
199
- if output.shape[-1] > self.length:
200
- start = torch.randint(0, (output.shape[-1] - self.length), [1])
201
- output = output[..., start : start + self.length]
202
-
203
- # check if stereo
204
- if self.stereo:
205
- # convert mono to stereo
206
- if input.shape[0] == 1:
207
- input = input.repeat(2, 1)
208
- if output.shape[0] == 1:
209
- output = output.repeat(2, 1)
210
- else:
211
- # convert stereo to mono
212
- if input.shape[0] == 2:
213
- input = torch.mean(input, dim=0, keepdim=True)
214
- if output.shape[0] == 2:
215
- output = torch.mean(output, dim=0, keepdim=True)
216
-
217
- # randomize the gain of input and output
218
- input /= torch.max(torch.abs(input)).clamp(1e-8)
219
- output /= torch.max(torch.abs(output)).clamp(1e-8)
220
-
221
- # from 0 to -32 dB
222
- gain_db = torch.rand(1) * -32.0
223
- input *= 10.0 ** (gain_db / 20.0)
224
-
225
- # from 0 to -32 dB
226
- gain_db = torch.rand(1) * -32.0
227
- output *= 10.0 ** (gain_db / 20.0)
228
-
229
- # swap left and right channels at random
230
- if torch.rand(1) > 0.5:
231
- input = input.flip(0)
232
- output = output.flip(0)
233
-
234
- yield input, output, instance_index, preset_index, tar_index, logits
235
-
236
- def __iter__(self):
237
- return iter(self.next_example())
238
-
239
-
240
- class PluginTarfileDataModule(pl.LightningDataModule):
241
- def __init__(
242
- self,
243
- train_tarfiles: List[str],
244
- val_tarfiles: List[str],
245
- plugin_json: str,
246
- batch_size: int,
247
- num_workers: int,
248
- length: int = 262144,
249
- stereo: bool = True,
250
- test_tarfiles: List[str] = None,
251
- train_labels: List[str] = None,
252
- val_labels: List[str] = None,
253
- test_labels: List[str] = None,
254
- pin_memory: bool = False,
255
- persistent_workers: bool = True,
256
- num_train_examples: int = 20000,
257
- num_val_examples: int = 2000,
258
- num_test_examples: int = 2000,
259
- ) -> None:
260
- super().__init__()
261
- """ Pytorch Lightning DataModule for loading data from tar files.
262
-
263
- Args:
264
- train_tarfiles (List[str]): List of tar files to use for training.
265
- val_tarfiles (List[str]): List of tar files to use for validation.
266
- plugin_json (str): Path to json file containing plugin instance details.
267
- batch_size (int): Batch size.
268
- num_workers (int): Number of workers.
269
- length (int): Length of audio to return. Defaults to 262144.
270
- stereo (bool): whether to return stereo audio. Defaults to True.
271
- When set to False, the mean of the two channels is returned.
272
- test_tarfiles (List[str]): List of tar files to use for testing.
273
- train_labels (List[str]): List of label files to use for training. Defaults to None.
274
- val_labels (List[str]): List of label files to use for validation. Defaults to None.
275
- test_labels (List[str]): List of label files to use for testing. Defaults to None.
276
- pin_memory (bool): Whether to pin memory. Defaults to False.
277
- persistent_workers (bool): Whether to use persistent workers. Defaults to True.
278
- num_train_examples (int): Number of training examples to return. Defaults to 20000.
279
- num_val_examples (int): Number of validation examples to return. Defaults to 2000.
280
- num_test_examples (int): Number of testing examples to return. Defaults to 2000.
281
- """
282
- self.save_hyperparameters()
283
-
284
- def setup(self, stage=None):
285
- self.train_dataset = PluginTarfileDataset(
286
- self.hparams.train_tarfiles,
287
- self.hparams.plugin_json,
288
- self.hparams.length,
289
- self.hparams.stereo,
290
- self.hparams.num_train_examples,
291
- label_files=self.hparams.train_labels,
292
- )
293
-
294
- self.val_dataset = PluginTarfileDataset(
295
- self.hparams.val_tarfiles,
296
- self.hparams.plugin_json,
297
- self.hparams.length,
298
- self.hparams.stereo,
299
- self.hparams.num_val_examples,
300
- label_files=self.hparams.val_labels,
301
- )
302
-
303
- if self.hparams.test_tarfiles is not None:
304
- self.test_dataset = PluginTarfileDataset(
305
- self.hparams.test_tarfiles,
306
- self.hparams.plugin_json,
307
- self.hparams.length,
308
- self.hparams.stereo,
309
- self.hparams.num_test_examples,
310
- label_files=self.hparams.test_labels,
311
- )
312
-
313
- def train_dataloader(self):
314
- return torch.utils.data.DataLoader(
315
- self.train_dataset,
316
- batch_size=self.hparams.batch_size,
317
- num_workers=self.hparams.num_workers,
318
- drop_last=True,
319
- pin_memory=self.hparams.pin_memory,
320
- persistent_workers=self.hparams.persistent_workers,
321
- worker_init_fn=tarfile_worker_init_fn,
322
- )
323
-
324
- def val_dataloader(self):
325
- return torch.utils.data.DataLoader(
326
- self.val_dataset,
327
- batch_size=self.hparams.batch_size,
328
- num_workers=self.hparams.num_workers,
329
- pin_memory=self.hparams.pin_memory,
330
- persistent_workers=self.hparams.persistent_workers,
331
- worker_init_fn=tarfile_worker_init_fn,
332
- )
333
-
334
- def test_dataloader(self):
335
- return torch.utils.data.DataLoader(
336
- self.val_dataset,
337
- batch_size=self.hparams.batch_size,
338
- num_workers=self.hparams.num_workers,
339
- persistent_workers=self.hparams.persistent_workers,
340
- worker_init_fn=tarfile_worker_init_fn,
341
- )
342
-
343
-
344
- # ------------------------------ File based dataset --------------------------------
345
-
346
-
347
- class PluginAudioFileWebDataset(Dataset):
348
- def __init__(
349
- self,
350
- root_dirs: str,
351
- plugin_json: str,
352
- length: int = 524288,
353
- stereo: bool = True,
354
- ) -> None:
355
- """
356
-
357
- Args:
358
- root_dirs (List[str]): List of directories containing dataset files.
359
- plugin_json: Path to json file containing plugin instance details.
360
- length (int): Length of audio to return. Defaults to 524288.
361
- stereo (bool): whether to return stereo audio. Defaults to True.
362
- When set to False, the mean of the two channels is returned.
363
-
364
- """
365
- super().__init__()
366
- self.root_dirs = root_dirs
367
- self.stereo = stereo
368
- self.length = length
369
-
370
- self.examples = []
371
-
372
- # find all segment directories
373
- for root_dir in root_dirs:
374
- examples = []
375
- # first, check if there is an examples.json file
376
- examples_json = os.path.join(root_dir, "examples.json")
377
- if os.path.exists(examples_json):
378
- print(f"Found {examples_json}.")
379
- with open(examples_json, "r") as f:
380
- examples = json.load(f)
381
- else:
382
- print(
383
- f"No examples.json file found. Crawling to find all examples in {root_dir}..."
384
- )
385
- segment_dirs = glob.glob(os.path.join(root_dir, "*"))
386
- segment_dirs = sorted([x for x in segment_dirs if os.path.isdir(x)])
387
-
388
- for segment_dir in tqdm(segment_dirs):
389
- # find the input file
390
- input_file = os.path.join(segment_dir, "input.flac")
391
- if not os.path.exists(input_file):
392
- continue
393
-
394
- # find all processed audio files
395
- processed_files = glob.glob(os.path.join(segment_dir, "*.flac"))
396
-
397
- for processed_file in processed_files:
398
- if processed_file == input_file:
399
- continue
400
- # find the appropriate json file
401
- json_file = processed_file.replace(".flac", ".json")
402
-
403
- # check if json file exists
404
- if not os.path.exists(json_file):
405
- print(f"Could not find {json_file}.")
406
- continue
407
-
408
- # test open json file
409
- # try:
410
- # with open(json_file, "r") as f:
411
- # details = json.load(f)
412
- # except Exception:
413
- # print(f"Error opening {json_file}.")
414
- # continue
415
-
416
- example = (input_file, processed_file, json_file)
417
- examples.append(example)
418
-
419
- self.examples += examples
420
- print(
421
- f"Found {len(examples)} examples in {root_dir}. Total: {len(self.examples)}"
422
- )
423
-
424
- with open(plugin_json, "r") as f:
425
- self.plugins = json.load(f)
426
-
427
- # for testing, load one example into memory
428
- # input_file, processed_file, json_file = self.examples[0]
429
-
430
- # open the json file
431
- # with open(json_file, "r") as f:
432
- # details = json.load(f)
433
-
434
- # input, sr = torchaudio.load(input_file, backend="soundfile")
435
- # output, sr = torchaudio.load(processed_file, backend="soundfile")
436
-
437
- # self.input = input
438
- # self.output = output
439
- # self.details = details
440
-
441
- def __len__(self):
442
- return len(self.examples)
443
-
444
- def __getitem__(self, idx):
445
- """Get a single example from the dataset."""
446
-
447
- if True:
448
- input_file, processed_file, json_file = self.examples[idx]
449
-
450
- # open the json file
451
- with open(json_file, "r") as f:
452
- details = json.load(f)
453
-
454
- input, sr = torchaudio.load(input_file, backend="soundfile")
455
- output, sr = torchaudio.load(processed_file, backend="soundfile")
456
- else:
457
- input = self.input
458
- output = self.output
459
- details = self.details
460
-
461
- # check length of input and output
462
- # if input.shape[-1] != output.shape[-1]:
463
- # raise RuntimeError(
464
- # f"Input and output shapes do not match: {input.shape} vs {output.shape}"
465
- # )
466
-
467
- # fit the proper length
468
- if input.shape[-1] < self.length:
469
- input = torch.cat(
470
- [input, torch.zeros([input.shape[0], self.length - input.shape[-1]])],
471
- dim=-1,
472
- )
473
- if output.shape[-1] < self.length:
474
- output = torch.cat(
475
- [
476
- output,
477
- torch.zeros([output.shape[0], self.length - output.shape[-1]]),
478
- ],
479
- dim=-1,
480
- )
481
-
482
- # take a random crop
483
- start = torch.randint(
484
- 0, (min(input.shape[-1], output.shape[-1]) - self.length), [1]
485
- )
486
- input = input[..., start : start + self.length]
487
- output = output[..., start : start + self.length]
488
-
489
- # note: now we take a crop from the same position in both files
490
- # however, it might be a good augmentation to take a different
491
- # random crop from each file
492
-
493
- # get the index of the plugin instance for classification
494
- instance_index = self.plugins[details["instance"]]
495
- instance_index -= 1 # zero index
496
- instance_index = torch.tensor(instance_index).long()
497
- preset_index = torch.tensor(details["preset"]).long()
498
-
499
- # check if stereo
500
- if self.stereo:
501
- # convert mono to stereo
502
- if input.shape[0] == 1:
503
- input = input.repeat(2, 1)
504
- if output.shape[0] == 1:
505
- output = output.repeat(2, 1)
506
- else:
507
- # convert stereo to mono
508
- if input.shape[0] == 2:
509
- input = torch.mean(input, dim=0, keepdim=True)
510
- if output.shape[0] == 2:
511
- output = torch.mean(output, dim=0, keepdim=True)
512
-
513
- # randomize the gain of input and output
514
- input /= torch.max(torch.abs(input)).clamp(1e-8)
515
- output /= torch.max(torch.abs(output)).clamp(1e-8)
516
-
517
- # from 0 to -32 dB
518
- gain_db = torch.rand(1) * -32.0
519
- input *= 10.0 ** (gain_db / 20.0)
520
-
521
- # from 0 to -32 dB
522
- gain_db = torch.rand(1) * -32.0
523
- output *= 10.0 ** (gain_db / 20.0)
524
-
525
- return input, output, instance_index, preset_index
526
-
527
-
528
- class PluginAudioFileWebDataModule(pl.LightningDataModule):
529
- def __init__(
530
- self,
531
- train_root_dirs: List[str],
532
- val_root_dirs: List[str],
533
- plugin_json: str,
534
- batch_size: int,
535
- num_workers: int,
536
- length: int = 262144,
537
- test_root_dirs: List[str] = None,
538
- pin_memory: bool = False,
539
- persistent_workers: bool = False,
540
- use_tarfile: bool = False,
541
- ) -> None:
542
- super().__init__()
543
- self.save_hyperparameters()
544
-
545
- def setup(self, stage=None):
546
- if not self.hparams.use_tarfile:
547
- self.train_dataset = PluginAudioFileWebDataset(
548
- self.hparams.train_root_dirs,
549
- self.hparams.plugin_json,
550
- self.hparams.length,
551
- )
552
-
553
- self.val_dataset = PluginAudioFileWebDataset(
554
- self.hparams.val_root_dirs,
555
- self.hparams.plugin_json,
556
- self.hparams.length,
557
- )
558
-
559
- if self.hparams.test_root_dirs is not None:
560
- self.test_dataset = PluginAudioFileWebDataset(
561
- self.hparams.test_root_dirs,
562
- self.hparams.plugin_json,
563
- self.hparams.length,
564
- )
565
- else:
566
- raise NotImplementedError
567
-
568
- def train_dataloader(self):
569
- return torch.utils.data.DataLoader(
570
- self.train_dataset,
571
- batch_size=self.hparams.batch_size,
572
- shuffle=True,
573
- num_workers=self.hparams.num_workers,
574
- drop_last=True,
575
- pin_memory=self.hparams.pin_memory,
576
- persistent_workers=self.hparams.persistent_workers,
577
- prefetch_factor=8,
578
- )
579
-
580
- def val_dataloader(self):
581
- return torch.utils.data.DataLoader(
582
- self.val_dataset,
583
- batch_size=self.hparams.batch_size,
584
- num_workers=self.hparams.num_workers,
585
- pin_memory=self.hparams.pin_memory,
586
- persistent_workers=self.hparams.persistent_workers,
587
- )
588
-
589
- def test_dataloader(self):
590
- return torch.utils.data.DataLoader(
591
- self.val_dataset,
592
- batch_size=self.hparams.batch_size,
593
- num_workers=self.hparams.num_workers,
594
- persistent_workers=self.hparams.persistent_workers,
595
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/st_ito/dataset/dataset_sim.py DELETED
@@ -1,320 +0,0 @@
1
- import os
2
- import glob
3
- import torch
4
- import torchaudio
5
- import pedalboard
6
- import numpy as np
7
- import pytorch_lightning as pl
8
-
9
- from tqdm import tqdm
10
- from typing import List, Optional, Tuple, Iterable
11
- from st_ito.effects import (
12
- BasicChorus,
13
- BasicCompressor,
14
- BasicDelay,
15
- BasicDistortion,
16
- BasicReverb,
17
- )
18
-
19
-
20
- def worker_init_fn(worker_id):
21
- worker_info = torch.utils.data.get_worker_info()
22
- # re-init the vst_plugins for each worker
23
- vst_plugins = load_plugins(worker_info.dataset.vst_dir)
24
- worker_info.dataset.vst_plugins = vst_plugins
25
-
26
-
27
- def find_files_with_glob(
28
- directory: str,
29
- ):
30
- """Find all files in the given directory with specific pattern."""
31
- audio_filepaths = []
32
- for ext in ["*.wav", "*.mp3", "*.ogg", "*.flac"]:
33
- audio_filepaths += glob.glob(os.path.join(directory, "**", ext), recursive=True)
34
-
35
- return audio_filepaths
36
-
37
-
38
- def find_files_with_extensions(
39
- directory: str,
40
- extensions: Tuple[str] = (".wav", ".mp3", ".ogg", ".flac"),
41
- ):
42
- """Find all files in the given directory with specific extensions."""
43
- for entry in os.scandir(directory):
44
- if entry.is_file() and entry.name.lower().endswith(extensions):
45
- yield entry.path
46
- elif entry.is_dir():
47
- yield from find_files_with_extensions(entry.path, extensions)
48
-
49
-
50
- def find_files_lazy(
51
- directory: str,
52
- extensions: Tuple[str] = (".wav", ".mp3", ".ogg", ".flac"),
53
- ):
54
- """Lazily find files with specific extensions in the given directory."""
55
- for root, dirs, files in os.walk(directory):
56
- for file in files:
57
- if file.lower().endswith(extensions):
58
- yield os.path.join(root, file)
59
-
60
-
61
- def get_nonsilent_region(
62
- audio_filepath: str,
63
- total_num_frames: int,
64
- length: int,
65
- threshold: float = 1e-8,
66
- max_tires: int = 100,
67
- ):
68
- silent = True
69
- tries = 0
70
- while silent:
71
-
72
- if total_num_frames > length:
73
- # load a random segment of the audio file
74
- audio, sr = torchaudio.load(
75
- audio_filepath,
76
- num_frames=length,
77
- frame_offset=np.random.randint(0, total_num_frames - length),
78
- backend="soundfile",
79
- )
80
-
81
- else:
82
- # load the entire audio file
83
- audio, sr = torchaudio.load(
84
- audio_filepath,
85
- num_frames=total_num_frames,
86
- backend="soundfile",
87
- )
88
-
89
- # repeat the audio to reach the desired length
90
- audio = torch.cat([audio] * (length // total_num_frames + 1), axis=1)
91
- audio = audio[:, :length]
92
-
93
- # check for silence
94
- energy = torch.mean(audio**2)
95
- print(energy)
96
- if energy > threshold:
97
- silent = False
98
- else:
99
- silent = False # TODO: remove this line
100
-
101
- tries += 1
102
-
103
- if tries > max_tires:
104
- raise ValueError(
105
- f"Could not find a non-silent region in the audio: {audio_filepath}."
106
- )
107
-
108
- return audio, sr
109
-
110
-
111
- def random_gain(audio: torch.Tensor):
112
- # first peak normalize
113
- audio = audio / torch.max(torch.abs(audio))
114
- # then apply random gain
115
- gain_db = np.random.uniform(-18, 0)
116
- gain_lin = 10 ** (gain_db / 20)
117
- audio = audio * gain_lin
118
- return audio
119
-
120
-
121
- def conform_channels(audio_input: torch.Tensor, num_channels: int):
122
- if audio_input.shape[0] == 1 and num_channels == 2:
123
- audio_input = torch.cat((audio_input, audio_input), axis=0)
124
- if audio_input.shape[0] == 2 and num_channels == 1:
125
- audio_input = audio_input[0:1, :]
126
- return audio_input
127
-
128
-
129
- def load_plugins(vst_dir: str, sample_rate: int = 48000):
130
- # find all vst3 files
131
- vst_filepaths = []
132
- for ext in ["*.vst3"]:
133
- vst_filepaths += glob.glob(os.path.join(vst_dir, ext), recursive=True)
134
-
135
- # vst_filepaths = vst_filepaths[:2]
136
-
137
- vst_filepaths = sorted(vst_filepaths)
138
- print(f"Found {len(vst_filepaths)} VST3 files in {vst_dir}.")
139
-
140
- vst_plugins = {}
141
- print("Loading plugins...")
142
- for vst_filepath in tqdm(vst_filepaths):
143
- instance_name = os.path.basename(vst_filepath).replace(".vst3", "")
144
- print(f"Loading {instance_name}...")
145
- # try to load each plugin
146
- try:
147
- plugin = pedalboard.load_plugin(vst_filepath)
148
- except Exception:
149
- print(f"Error loading {vst_filepath}.")
150
-
151
- # try to pass audio through the plugin
152
- failed = True
153
- try:
154
- num_channels = 2
155
- audio_input = np.random.randn(2, sample_rate * 5)
156
- audio_output = plugin(audio_input, sample_rate)
157
- failed = False
158
- except Exception as e:
159
- num_channels = 1
160
- print(e)
161
- print(f"Error processing audio with 2 channels.")
162
-
163
- try:
164
- audio_input = np.random.randn(1, sample_rate * 5)
165
- audio_output = plugin(audio_input, sample_rate)
166
- failed = False
167
- except Exception as e:
168
- num_channels = 2
169
- print(e)
170
- print(f"Failed processing audio with 1 channel.")
171
-
172
- if failed:
173
- continue
174
-
175
- print(f"Loaded {os.path.basename(vst_filepath)} successfully.")
176
-
177
- vst_plugins[instance_name] = {
178
- "filepath": vst_filepath,
179
- "instance": plugin,
180
- "num_channels": num_channels,
181
- }
182
-
183
- for idx, (instance_name, vst_plugin) in enumerate(vst_plugins.items()):
184
- print(idx + 1, instance_name)
185
-
186
- return vst_plugins
187
-
188
-
189
- class PluginSimilarityDataset(torch.utils.data.Dataset):
190
- def __init__(
191
- self,
192
- audio_filepaths: List[str],
193
- vst_dir: str,
194
- length: int = 262144,
195
- num_examples_per_epoch: int = 10000,
196
- ):
197
- self.audio_filepaths = audio_filepaths
198
- self.vst_dir = vst_dir
199
- self.length = length
200
- self.num_examples_per_epoch = num_examples_per_epoch
201
- self.plugins = load_plugins(self.vst_dir)
202
-
203
- def __len__(self):
204
- return self.num_examples_per_epoch
205
-
206
- def __getitem__(self, idx: int):
207
- # sample the first filepath
208
- filepath_a = np.random.choice(self.audio_filepaths)
209
- # sample the second filepath
210
- filepath_b = np.random.choice(self.audio_filepaths)
211
-
212
- # load the audio files
213
- num_frames_a = torchaudio.info(filepath_a).num_frames
214
- audio_a, sr_a = get_nonsilent_region(filepath_a, num_frames_a, self.length)
215
-
216
- num_frames_b = torchaudio.info(filepath_b).num_frames
217
- audio_b, sr_b = get_nonsilent_region(filepath_b, num_frames_b, self.length)
218
-
219
- # sample randomly a plugin from the dictionary
220
- plugin_name = np.random.choice(list(self.plugins.keys()))
221
- plugin = self.plugins[plugin_name]
222
-
223
- # randomize the parameters of the plugin
224
- for name, parameter in plugin["instance"].parameters.items():
225
- if "bypass" in name.lower():
226
- parameter.raw_value = 0
227
- elif name in ["buffer_size_frames", "sample_rate_frames"]:
228
- pass
229
- else: # set the parameter to the preset value
230
- parameter.raw_value = np.random.rand()
231
-
232
- audio_a = conform_channels(audio_a, plugin["num_channels"])
233
- audio_b = conform_channels(audio_b, plugin["num_channels"])
234
-
235
- # apply the effect to the two audio files
236
- audio_a_out = torch.from_numpy(
237
- plugin["instance"].process(audio_a.numpy(), sr_a)
238
- )
239
- audio_b_out = torch.from_numpy(
240
- plugin["instance"].process(audio_b.numpy(), sr_b)
241
- )
242
-
243
- # peak normalize and then apply random gain
244
- audio_a = random_gain(audio_a)
245
- audio_b = random_gain(audio_b)
246
- audio_a_out = random_gain(audio_a_out)
247
- audio_b_out = random_gain(audio_b_out)
248
-
249
- # ensure all outputs are stereo
250
- audio_a = conform_channels(audio_a, 2)
251
- audio_b = conform_channels(audio_b, 2)
252
- audio_a_out = conform_channels(audio_a_out, 2)
253
- audio_b_out = conform_channels(audio_b_out, 2)
254
-
255
- return audio_a, audio_b, audio_a_out, audio_b_out
256
-
257
-
258
- class PluginSimilarityDataModule(pl.LightningDataModule):
259
- def __init__(
260
- self,
261
- audio_dirs: List[str],
262
- vst_dir: str,
263
- length: int = 262144,
264
- batch_size: int = 32,
265
- num_train_examples: int = 10000,
266
- num_val_examples: int = 1000,
267
- num_workers: int = 4,
268
- persistent_workers: bool = True,
269
- pin_memory: bool = True,
270
- ) -> None:
271
- super().__init__()
272
- self.save_hyperparameters()
273
-
274
- def prepare_data(self):
275
- # download, split, etc...
276
- pass
277
-
278
- def setup(self, stage: Optional[str] = None):
279
- # find all audio files in the input directory based on file extensions
280
- audio_filepaths = []
281
- for audio_dir in self.hparams.audio_dirs:
282
- audio_filepaths += find_files_with_glob(audio_dir)
283
- print("Found", len(audio_filepaths), "audio files in", audio_dir)
284
-
285
- # Assign train/val datasets for use in dataloaders
286
- if stage == "fit" or stage is None:
287
- self.plugin_similarity_train = PluginSimilarityDataset(
288
- audio_filepaths,
289
- self.hparams.vst_dir,
290
- self.hparams.length,
291
- self.hparams.num_train_examples,
292
- )
293
- self.plugin_similarity_val = PluginSimilarityDataset(
294
- audio_filepaths,
295
- self.hparams.vst_dir,
296
- self.hparams.length,
297
- self.hparams.num_val_examples,
298
- )
299
-
300
- def train_dataloader(self):
301
- return torch.utils.data.DataLoader(
302
- self.plugin_similarity_train,
303
- batch_size=self.hparams.batch_size,
304
- shuffle=True,
305
- num_workers=self.hparams.num_workers,
306
- persistent_workers=self.hparams.persistent_workers,
307
- pin_memory=self.hparams.pin_memory,
308
- # worker_init_fn=worker_init_fn,
309
- )
310
-
311
- def val_dataloader(self):
312
- return torch.utils.data.DataLoader(
313
- self.plugin_similarity_val,
314
- batch_size=self.hparams.batch_size,
315
- shuffle=False,
316
- num_workers=self.hparams.num_workers,
317
- persistent_workers=self.hparams.persistent_workers,
318
- pin_memory=self.hparams.pin_memory,
319
- # worker_init_fn=worker_init_fn,
320
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/st_ito/dataset/dataset_style.py DELETED
@@ -1,351 +0,0 @@
1
- import os
2
- import glob
3
- import json
4
- import torch
5
- import tarfile
6
- import torchaudio
7
- import numpy as np
8
- import pytorch_lightning as pl
9
-
10
- from typing import List, Tuple, Dict, Optional
11
- from st_ito.dataset.utils import tarfile_worker_init_fn, torchaudio_decode
12
-
13
-
14
- class StyleTransferTarfileDataset(torch.utils.data.IterableDataset):
15
- def __init__(
16
- self,
17
- tar_files: List[str],
18
- length: int,
19
- num_examples: int = 10000,
20
- audio_format: str = "wav",
21
- input_only: bool = False,
22
- ):
23
- super().__init__()
24
- self.tar_files = tar_files
25
- self.length = length
26
- self.num_examples = num_examples
27
- self.audio_format = audio_format
28
- self.input_only = input_only
29
-
30
- # get the tar file handles
31
- self.tar_handles = []
32
- for tar_file in tar_files:
33
- tar_handle = tarfile.open(tar_file, "r:")
34
- tar_handle.next() # skip the first root directory
35
- tar_handle.next() # skip the first example directory
36
- self.tar_handles.append(tar_handle)
37
-
38
- def __len__(self):
39
- return self.num_examples
40
-
41
- def next_example(self):
42
- while True:
43
- # select tar file at random
44
- tar_index = np.random.choice(len(self.tar_handles))
45
-
46
- # get the next member (should be root directory of this example)
47
- members = {}
48
- done = False
49
- handle_reset = False
50
- while not done:
51
- # get next member
52
- member = self.tar_handles[tar_index].next()
53
-
54
- if member is None:
55
- # we have reached the end of this tar file
56
- # so, we will close it and remove open tar handle
57
-
58
- # close this tar handle
59
- self.tar_handles[tar_index].close()
60
-
61
- # reopen
62
- tar_handle = tarfile.open(self.tar_files[tar_index], "r:")
63
- tar_handle.next() # skip the first root directory
64
- tar_handle.next() # skip the first example directory
65
- self.tar_handles[tar_index] = tar_handle # replace handle
66
- print(f"Resetting tar handle {self.tar_files[tar_index]}.")
67
- handle_reset = True
68
- break
69
-
70
- if member.isdir():
71
- done = True
72
- break
73
-
74
- members[os.path.basename(member.name)] = member
75
-
76
- if handle_reset:
77
- continue
78
-
79
- # get input file
80
- input_file = self.tar_handles[tar_index].extractfile(
81
- members[f"input.{self.audio_format}"]
82
- )
83
- audio_input = torchaudio_decode(input_file)
84
-
85
- if self.input_only:
86
-
87
- num_frames = audio_input.shape[1]
88
- start_frame = torch.randint(0, num_frames - self.length, (1,)).item()
89
- audio_input = audio_input[:, start_frame : start_frame + self.length]
90
-
91
- # we will generate the output in the system
92
- audio_output = torch.empty_like(audio_input)
93
- params = torch.empty(1)
94
-
95
- else:
96
- # get the output file
97
- output_file = self.tar_handles[tar_index].extractfile(
98
- members[f"output.{self.audio_format}"]
99
- )
100
- audio_output = torchaudio_decode(output_file)
101
-
102
- # get the params file
103
- params_file = self.tar_handles[tar_index].extractfile(
104
- members["params.json"]
105
- )
106
- params = json.load(params_file)
107
- # convert params to tensor
108
- params = torch.tensor(
109
- [val for val in params.values()], dtype=torch.float32
110
- )
111
-
112
- # crop the audio to the desired length
113
- num_frames = audio_input.shape[1]
114
- start_frame = torch.randint(0, num_frames - self.length, (1,)).item()
115
- audio_input = audio_input[:, start_frame : start_frame + self.length]
116
- audio_output = audio_output[:, start_frame : start_frame + self.length]
117
-
118
- # check for silence in the output
119
- input_energy = torch.mean(audio_input**2, dim=1)
120
- output_energy = torch.mean(audio_output**2, dim=1)
121
-
122
- if output_energy < 1e-6 or input_energy < 1e-6:
123
- # skip this example
124
- continue
125
-
126
- yield audio_input, audio_output, params
127
-
128
- def __iter__(self):
129
- return iter(self.next_example())
130
-
131
-
132
- class StyleTransferTarfileDataModule(pl.LightningDataModule):
133
- def __init__(
134
- self,
135
- train_tarfiles: List[str],
136
- val_tarfiles: List[str],
137
- batch_size: int,
138
- num_workers: int,
139
- input_only: bool = False,
140
- length: int = 262144,
141
- test_tarfiles: List[str] = None,
142
- pin_memory: bool = False,
143
- persistent_workers: bool = True,
144
- num_train_examples: int = 20000,
145
- num_val_examples: int = 2000,
146
- num_test_examples: int = 2000,
147
- audio_format: str = "wav",
148
- ) -> None:
149
- super().__init__()
150
- """ Pytorch Lightning DataModule for loading data from tar files.
151
-
152
- Args:
153
- train_tarfiles (List[str]): List of tar files to use for training.
154
- val_tarfiles (List[str]): List of tar files to use for validation.
155
- batch_size (int): Batch size.
156
- num_workers (int): Number of workers.
157
- input_only (bool): Whether to use only the input audio. Defaults to False.
158
- length (int): Length of audio to return. Defaults to 262144.
159
- test_tarfiles (List[str]): List of tar files to use for testing.
160
- pin_memory (bool): Whether to pin memory. Defaults to False.
161
- persistent_workers (bool): Whether to use persistent workers. Defaults to True.
162
- num_train_examples (int): Number of training examples to return. Defaults to 20000.
163
- num_val_examples (int): Number of validation examples to return. Defaults to 2000.
164
- num_test_examples (int): Number of testing examples to return. Defaults to 2000.
165
- audio_format (str): Audio format to use. Defaults to "wav".
166
- """
167
- self.save_hyperparameters()
168
-
169
- def setup(self, stage=None):
170
- self.train_dataset = StyleTransferTarfileDataset(
171
- self.hparams.train_tarfiles,
172
- self.hparams.length,
173
- self.hparams.num_train_examples,
174
- self.hparams.audio_format,
175
- self.hparams.input_only,
176
- )
177
-
178
- self.val_dataset = StyleTransferTarfileDataset(
179
- self.hparams.val_tarfiles,
180
- self.hparams.length,
181
- self.hparams.num_val_examples,
182
- self.hparams.audio_format,
183
- self.hparams.input_only,
184
- )
185
-
186
- if self.hparams.test_tarfiles is not None:
187
- self.test_dataset = StyleTransferTarfileDataset(
188
- self.hparams.test_tarfiles,
189
- self.hparams.length,
190
- self.hparams.num_test_examples,
191
- self.hparams.audio_format,
192
- self.hparams.input_only,
193
- )
194
-
195
- def train_dataloader(self):
196
- return torch.utils.data.DataLoader(
197
- self.train_dataset,
198
- batch_size=self.hparams.batch_size,
199
- num_workers=self.hparams.num_workers,
200
- drop_last=True,
201
- pin_memory=self.hparams.pin_memory,
202
- persistent_workers=self.hparams.persistent_workers,
203
- worker_init_fn=tarfile_worker_init_fn,
204
- )
205
-
206
- def val_dataloader(self):
207
- return torch.utils.data.DataLoader(
208
- self.val_dataset,
209
- batch_size=self.hparams.batch_size,
210
- num_workers=self.hparams.num_workers,
211
- pin_memory=self.hparams.pin_memory,
212
- persistent_workers=self.hparams.persistent_workers,
213
- worker_init_fn=tarfile_worker_init_fn,
214
- )
215
-
216
- def test_dataloader(self):
217
- return torch.utils.data.DataLoader(
218
- self.val_dataset,
219
- batch_size=self.hparams.batch_size,
220
- num_workers=self.hparams.num_workers,
221
- persistent_workers=self.hparams.persistent_workers,
222
- worker_init_fn=tarfile_worker_init_fn,
223
- )
224
-
225
-
226
- # ------------------------------ File based dataset --------------------------------
227
-
228
-
229
- class StyleTransferDataset(torch.utils.data.Dataset):
230
- def __init__(
231
- self,
232
- root_dirs: List[str],
233
- length: int,
234
- ):
235
- super().__init__()
236
- self.root_dirs = root_dirs
237
- self.length = length
238
-
239
- self.examples = []
240
- for root_dir in root_dirs:
241
- # get all examples in the root dir
242
- print("Searching for examples in", root_dir)
243
- examples = glob.glob(os.path.join(root_dir, "*"))
244
- self.examples.extend(examples)
245
-
246
- # for testing use only the first 100 examples
247
- # self.examples = self.examples[:100]
248
- # repeat those examples 10 times
249
- # self.examples = self.examples * 100
250
-
251
- print(f"Found {len(self.examples)} examples.")
252
-
253
- def __len__(self):
254
- return len(self.examples)
255
-
256
- def __getitem__(self, idx):
257
- example = self.examples[idx]
258
-
259
- input_filepath = os.path.join(example, "input.wav")
260
- output_filepath = os.path.join(example, "output.wav")
261
- params_filepath = os.path.join(example, "params.json")
262
-
263
- # check the length of the audio
264
- md = torchaudio.info(input_filepath, backend="soundfile")
265
- num_frames = md.num_frames
266
-
267
- # pick random start frame
268
- start_frame = torch.randint(0, num_frames - self.length, (1,)).item()
269
- # start_frame = 0 # for now, just use the first frame
270
-
271
- # load the audio
272
- input_audio, sr = torchaudio.load(
273
- input_filepath,
274
- frame_offset=start_frame,
275
- num_frames=self.length,
276
- backend="soundfile",
277
- )
278
- output_audio, sr = torchaudio.load(
279
- output_filepath,
280
- frame_offset=start_frame,
281
- num_frames=self.length,
282
- backend="soundfile",
283
- )
284
-
285
- # load the params
286
- with open(params_filepath, "r") as fp:
287
- params = json.load(fp)
288
-
289
- # convert params to tensor
290
- params = torch.tensor([val for val in params.values()], dtype=torch.float32)
291
-
292
- return input_audio, output_audio, params
293
-
294
-
295
- class StyleTransferDataModule(pl.LightningDataModule):
296
- def __init__(
297
- self,
298
- train_root_dirs: List[str],
299
- val_root_dirs: List[str],
300
- batch_size: int,
301
- num_workers: int,
302
- length: int = 262144,
303
- test_root_dirs: List[str] = None,
304
- pin_memory: bool = False,
305
- persistent_workers: bool = False,
306
- ) -> None:
307
- super().__init__()
308
- self.save_hyperparameters()
309
-
310
- def setup(self, stage=None):
311
- self.train_dataset = StyleTransferDataset(
312
- self.hparams.train_root_dirs,
313
- self.hparams.length,
314
- )
315
- self.val_dataset = StyleTransferDataset(
316
- self.hparams.val_root_dirs,
317
- self.hparams.length,
318
- )
319
- if self.hparams.test_root_dirs is not None:
320
- self.test_dataset = StyleTransferDataset(
321
- self.hparams.test_root_dirs,
322
- self.hparams.length,
323
- )
324
-
325
- def train_dataloader(self):
326
- return torch.utils.data.DataLoader(
327
- self.train_dataset,
328
- batch_size=self.hparams.batch_size,
329
- shuffle=True,
330
- num_workers=self.hparams.num_workers,
331
- drop_last=True,
332
- pin_memory=self.hparams.pin_memory,
333
- persistent_workers=self.hparams.persistent_workers,
334
- )
335
-
336
- def val_dataloader(self):
337
- return torch.utils.data.DataLoader(
338
- self.val_dataset,
339
- batch_size=self.hparams.batch_size,
340
- num_workers=self.hparams.num_workers,
341
- pin_memory=self.hparams.pin_memory,
342
- persistent_workers=self.hparams.persistent_workers,
343
- )
344
-
345
- def test_dataloader(self):
346
- return torch.utils.data.DataLoader(
347
- self.val_dataset,
348
- batch_size=self.hparams.batch_size,
349
- num_workers=self.hparams.num_workers,
350
- persistent_workers=self.hparams.persistent_workers,
351
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/st_ito/dataset/utils.py DELETED
@@ -1,25 +0,0 @@
1
- import torch
2
- import tarfile
3
- import torchaudio
4
-
5
-
6
- def tarfile_worker_init_fn(worker_id):
7
- worker_info = torch.utils.data.get_worker_info()
8
- dataset = worker_info.dataset # the dataset copy in this worker process
9
-
10
- # if there is more than one tar file in the dataset, then we need to
11
- # select one tar file for this worker
12
- print(f"Worker {worker_id} creating new tar file handles.")
13
-
14
- # reopen the tarfile handles for this worker
15
- dataset.tar_handles = []
16
- for tar_file in dataset.tar_files:
17
- tar_handle = tarfile.open(tar_file, "r:")
18
- tar_handle.next() # skip the first root directory
19
- tar_handle.next() # skip the first example directory
20
- dataset.tar_handles.append(tar_handle)
21
-
22
-
23
- def torchaudio_decode(b):
24
- x, sr = torchaudio.load(b, format="FLAC")
25
- return x