From b493ede47923f611dafe1721a2464b835ce8ddb5 Mon Sep 17 00:00:00 2001 From: FelixChan <2223485532@qq,com> Date: Tue, 21 Oct 2025 15:27:03 +0800 Subject: [PATCH] 1021 add flexable attr control --- Amadeus/evaluation_utils.py | 38 ++- Amadeus/model_zoo.py | 60 ++-- Amadeus/sampling_utils.py | 18 ++ Amadeus/sub_decoder_zoo.py | 266 +++--------------- Amadeus/symbolic_encoding/data_utils.py | 1 + Amadeus/symbolic_yamls/config-accelerate.yaml | 10 +- Amadeus/trainer_accelerate.py | 3 +- Amadeus/transformer_utils.py | 17 ++ .../step1_midi2corpus_fined.py | 2 +- data_representation/step2_corpus2event.py | 2 +- generate-batch.py | 138 ++++++--- len_tunes/Melody/len_nb8.png | Bin 0 -> 19427 bytes len_tunes/msmidi/len_nb8.png | Bin 0 -> 19800 bytes midi_sim.py | 134 +++++++++ ,idi_sim.py | 105 ------- 15 files changed, 400 insertions(+), 394 deletions(-) create mode 100644 len_tunes/Melody/len_nb8.png create mode 100644 len_tunes/msmidi/len_nb8.png create mode 100644 midi_sim.py delete mode 100644 ,idi_sim.py diff --git a/Amadeus/evaluation_utils.py b/Amadeus/evaluation_utils.py index 00aa52b..37f9917 100644 --- a/Amadeus/evaluation_utils.py +++ b/Amadeus/evaluation_utils.py @@ -67,8 +67,19 @@ def get_best_ckpt_path_and_config(wandb_dir, code): return last_ckpt_fn, config_path, metadata_path, vocab_path -def prepare_model_and_dataset_from_config(config: DictConfig, metadata_path:str, vocab_path:str): +def prepare_model_and_dataset_from_config(config: DictConfig, metadata_path:str, vocab_path:str, condition_dataset: str=None): + # if config is a path, load it + if isinstance(config, (str, Path)): + from omegaconf import OmegaConf + config = OmegaConf.load(config) + config = wandb_style_config_to_omega_config(config) + nn_params = config.nn_params + for_evaluation = True + if condition_dataset is not None: + print(f"Conditioned dataset {condition_dataset} is used instead of {config.dataset}") + config.dataset = condition_dataset + for_evaluation = False dataset_name = config.dataset vocab_path = Path(vocab_path) @@ -104,7 +115,7 @@ def prepare_model_and_dataset_from_config(config: DictConfig, metadata_path:str, input_length=config.train_params.input_length, first_pred_feature=config.data_params.first_pred_feature, caption_path=config.captions_path if hasattr(config, 'captions_path') else None, - for_evaluation=True, + for_evaluation=for_evaluation ) vocab_sizes = symbolic_dataset.vocab.get_vocab_size() @@ -114,7 +125,6 @@ def prepare_model_and_dataset_from_config(config: DictConfig, metadata_path:str, split_ratio = config.data_params.split_ratio # test_set = [] train_set, valid_set, test_set = symbolic_dataset.split_train_valid_test_set(dataset_name=config.dataset, ratio=split_ratio, seed=42, save_dir=None) - # get proper prediction order according to the encoding scheme and target feature in the config prediction_order = adjust_prediction_order(encoding_scheme, num_features, config.data_params.first_pred_feature, nn_params) @@ -480,6 +490,28 @@ class Evaluator: prompt = self.model.decoder._prepare_inference(self.model.decoder.net.start_token, 0, tuneidx, num_target_measures=8) decoder(prompt, output_path=str(save_dir / f"{tune_name}_prompt.mid")) + def generate_samples_with_attrCtl(self, save_dir, num_target_measures, tuneidx, tune_name, first_pred_feature, sampling_method=None, threshold=None, temperature=1.0,generation_length=3072, attr_list=None): + encoding_scheme = self.config.nn_params.encoding_scheme + + in_beat_resolution_dict = {'Pop1k7': 4, 'Pop909': 4, 'SOD': 12, 'LakhClean': 4} + try: + in_beat_resolution = in_beat_resolution_dict[self.config.dataset] + except KeyError: + in_beat_resolution = 4 # Default resolution if dataset is not found + + midi_decoder_dict = {'remi':'MidiDecoder4REMI', 'cp':'MidiDecoder4CP', 'nb':'MidiDecoder4NB'} + decoder_name = midi_decoder_dict[encoding_scheme] + decoder = getattr(decoding_utils, decoder_name)(vocab=self.vocab, in_beat_resolution=in_beat_resolution, dataset_name=self.config.dataset) + + tuneidx = tuneidx.cuda() + generated_sample = self.model.generate(0, generation_length, condition=tuneidx, num_target_measures=num_target_measures, sampling_method=sampling_method, threshold=threshold, temperature=temperature, attr_list=attr_list) + if encoding_scheme == 'nb': + generated_sample = reverse_shift_and_pad_for_tensor(generated_sample, first_pred_feature) + decoder(generated_sample, output_path=str(save_dir / f"{tune_name}.mid")) + + prompt = self.model.decoder._prepare_inference(self.model.decoder.net.start_token, 0, tuneidx, num_target_measures=8) + decoder(prompt, output_path=str(save_dir / f"{tune_name}_prompt.mid")) + def generate_samples_unconditioned(self, save_dir, num_samples, first_pred_feature, sampling_method, threshold, temperature, generation_length=3072,uid=1): encoding_scheme = self.config.nn_params.encoding_scheme diff --git a/Amadeus/model_zoo.py b/Amadeus/model_zoo.py index 492c8c9..7b2788d 100644 --- a/Amadeus/model_zoo.py +++ b/Amadeus/model_zoo.py @@ -102,7 +102,17 @@ class AmadeusModelAutoregressiveWrapper(nn.Module): ''' super().__init__() self.net = net - + # self.prediction_order = net.prediction_order + # self.attribute2idx = {key: idx for idx, key in enumerate(self.prediction_order)} + self.attribute2idx_after = {'pitch': 0, + 'duration': 1, + 'velocity': 2, + 'type': 3, + 'beat': 4, + 'chord': 5, + 'tempo': 6, + 'instrument': 7} + self.attribute2idx = {'type':0, 'beat':1, 'chord':2, 'tempo':3, 'instrument':4, 'pitch':5, 'duration':6, 'velocity':7} def forward(self, input_seq:torch.Tensor, target:torch.Tensor,context=None): return self.net(input_seq, target, context=context) @@ -164,7 +174,7 @@ class AmadeusModelAutoregressiveWrapper(nn.Module): total_out = torch.LongTensor(total_out).unsqueeze(0).to(self.net.device) return total_out - def _run_one_step(self, input_seq, cache=None, sampling_method=None, threshold=None, temperature=1, bos_hidden_vec=None,context=None): + def _run_one_step(self, input_seq, cache=None, sampling_method=None, threshold=None, temperature=1, bos_hidden_vec=None,context=None,condition_step=None): ''' Runs one step of autoregressive generation by taking the input sequence, embedding it, passing it through the main decoder, and generating logits and a sampled token. @@ -192,7 +202,7 @@ class AmadeusModelAutoregressiveWrapper(nn.Module): input_dict = {'hidden_vec': hidden_vec, 'input_seq': input_seq, 'target': None, 'bos_token_hidden': bos_hidden_vec} # Generate the next token - logits, sampled_token = self.net.sub_decoder(input_dict, sampling_method, threshold, temperature) + logits, sampled_token = self.net.sub_decoder(input_dict, sampling_method, threshold, temperature, condition_step=condition_step) return logits, sampled_token, intermidiates, hidden_vec def _update_total_out(self, total_out, sampled_token): @@ -225,7 +235,7 @@ class AmadeusModelAutoregressiveWrapper(nn.Module): return total_out, sampled_token @torch.inference_mode() - def generate(self, manual_seed, max_seq_len, condition=None, num_target_measures=4, sampling_method=None, threshold=None, temperature=1, batch_size=1, context=None): + def generate(self, manual_seed, max_seq_len, condition=None, num_target_measures=4, sampling_method=None, threshold=None, temperature=1, batch_size=1, context=None, attr_list=None): ''' Autoregressively generates a sequence of tokens by repeatedly sampling the next token until the desired maximum sequence length is reached or the end token is encountered. @@ -243,15 +253,19 @@ class AmadeusModelAutoregressiveWrapper(nn.Module): - total_out: The generated sequence of tokens as a tensor. ''' # Prepare the starting sequence for inference - total_out = self._prepare_inference(self.net.start_token, manual_seed, condition, num_target_measures) - - # If a condition is provided, run one initial step - if condition is not None: - _, _, cache = self._run_one_step(total_out[:, -self.net.input_length:], cache=LayerIntermediates(), sampling_method=sampling_method, threshold=threshold, temperature=temperature, context=context) + if attr_list is None: + total_out = self._prepare_inference(self.net.start_token, manual_seed, condition, num_target_measures) else: - cache = LayerIntermediates() - - # Continue generating tokens until the maximum sequence length is reached + total_out = self._prepare_inference(self.net.start_token, manual_seed, None, num_target_measures) + # for attribute-controlled generation, only keep the specified attributes in condition, others set to 126336 + condition_filtered = condition.clone().unsqueeze(0) + # print(self.attribute2idx) + for attr, idx in self.attribute2idx.items(): + if attr not in attr_list: + condition_filtered[:, :, idx] = 126336 + # rearange condition_filtered to match prediction order + + cache = LayerIntermediates() pbar = tqdm(total=max_seq_len, desc="Generating tokens", unit="token") bos_hidden_vec = None hidden_vec_list = [] @@ -261,7 +275,21 @@ class AmadeusModelAutoregressiveWrapper(nn.Module): input_tensor = total_out[:, -self.net.input_length:] # Generate the next token and update the cache time_start = time.time() - _, sampled_token, cache, hidden_vec = self._run_one_step(input_tensor, cache=cache, sampling_method=sampling_method, threshold=threshold, temperature=temperature,bos_hidden_vec=bos_hidden_vec, context=context) + # if attr_list is not None, get one token in condition_filtered each time step + if attr_list is not None: + condition_filtered = condition_filtered.to(self.net.device) + # print(condition_filtered[:,:20,:]) + # print(condition_filtered.shape) + condition_step = condition_filtered[:, total_out.shape[1]-1:total_out.shape[1], :] + # rearange order, 0 to 5, 1 to 6, 2 to 7, 3 to 0, 4 to 1, 5 to 2, 6 to 3, 7 to 4 + condition_step_rearranged = torch.zeros_like(condition_step) + for attr, idx in self.attribute2idx.items(): + new_idx = self.attribute2idx_after[attr] + condition_step_rearranged[:, :, new_idx] = condition_step[:, :, idx] + # print("condition_step shape:", condition_step.shape) + _, sampled_token, cache, hidden_vec = self._run_one_step(input_tensor, cache=cache, sampling_method=sampling_method, threshold=threshold, temperature=temperature,bos_hidden_vec=bos_hidden_vec, context=context, condition_step=condition_step_rearranged) + else: + _, sampled_token, cache, hidden_vec = self._run_one_step(input_tensor, cache=cache, sampling_method=sampling_method, threshold=threshold, temperature=temperature,bos_hidden_vec=bos_hidden_vec, context=context) time_end = time.time() token_time_list.append(time_end - time_start) if bos_hidden_vec is None: @@ -416,11 +444,11 @@ class AmadeusModel(nn.Module): return self.decoder(input_seq, target, context=context) @torch.inference_mode() - def generate(self, manual_seed, max_seq_len, condition=None, num_target_measures=4, sampling_method=None, threshold=None, temperature=1,batch_size=1,context=None): + def generate(self, manual_seed, max_seq_len, condition=None, num_target_measures=4, sampling_method=None, threshold=None, temperature=1,batch_size=1,context=None,attr_list=None): if batch_size == 1: - return self.decoder.generate(manual_seed, max_seq_len, condition, num_target_measures, sampling_method, threshold, temperature, context=context) + return self.decoder.generate(manual_seed, max_seq_len, condition, num_target_measures, sampling_method, threshold, temperature, context=context, attr_list=attr_list) else: - return self.decoder.generate_batch(manual_seed, max_seq_len, condition, num_target_measures, sampling_method, threshold, temperature, batch_size, context=context) + return self.decoder.generate_batch(manual_seed, max_seq_len, condition, num_target_measures, sampling_method, threshold, temperature, batch_size, context=context, attr_list=attr_list) class AmadeusModel4Encodec(AmadeusModel): def __init__( diff --git a/Amadeus/sampling_utils.py b/Amadeus/sampling_utils.py index c5742ca..d64d392 100644 --- a/Amadeus/sampling_utils.py +++ b/Amadeus/sampling_utils.py @@ -43,6 +43,22 @@ def typical_sampling(logits, thres=0.99): scores = logits.masked_fill(indices_to_remove, float("-inf")) return scores +def min_p_sampling(logits, alpha=0.05): + """ + logits: Tensor of shape [B, L, V] + alpha: float, relative probability threshold (e.g., 0.05) + """ + # 计算 softmax 概率 + probs = F.softmax(logits, dim=-1) + + # 找到每个位置的最大概率 + max_probs, _ = probs.max(dim=-1, keepdim=True) # [B, L, 1] + + # 保留概率 >= alpha * max_prob 的 token + mask = probs < (alpha * max_probs) # True 表示要屏蔽 + masked_logits = logits.masked_fill(mask, float('-inf')) + return masked_logits + def add_gumbel_noise(logits, temperature): ''' The Gumbel max is a method for sampling categorical distributions. @@ -91,6 +107,8 @@ def sample_with_prob(logits, sampling_method, threshold, temperature): modified_logits = typical_sampling(logits, thres=threshold) elif sampling_method == "eta": modified_logits = eta_sampling(logits, epsilon=threshold) + elif sampling_method == "min_p": + modified_logits = min_p_sampling(logits, alpha=threshold) else: modified_logits = logits # 其他情况直接使用原始logits diff --git a/Amadeus/sub_decoder_zoo.py b/Amadeus/sub_decoder_zoo.py index e0994d6..9179211 100644 --- a/Amadeus/sub_decoder_zoo.py +++ b/Amadeus/sub_decoder_zoo.py @@ -1,3 +1,4 @@ +from re import T from selectors import EpollSelector from turtle import st from numpy import indices @@ -6,7 +7,7 @@ import torch import torch.profiler import torch.nn as nn -from x_transformers import Decoder +from .custom_x_transformers import Decoder from .transformer_utils import MultiEmbedding, RVQMultiEmbedding from .sub_decoder_utils import * @@ -146,7 +147,7 @@ class FeedForward(SubDecoderClass): f"layer_{key}": nn.Linear(dim+dim, dim) for key, _ in vocab_sizes.items() }) - def forward(self, input_dict, sampling_method=None, threshold=None, temperature=None): + def forward(self, input_dict, sampling_method=None, threshold=None, temperature=None, condition_step=None): logits_dict = {} hidden_vec = input_dict['hidden_vec'] target = input_dict['target'] @@ -204,7 +205,7 @@ class Parallel(SubDecoderClass): ''' super().__init__(prediction_order, vocab, sub_decoder_depth, dim, heads, dropout, sub_decoder_enricher_use) - def forward(self, input_dict, sampling_method=None, threshold=None, temperature=None): + def forward(self, input_dict, sampling_method=None, threshold=None, temperature=None, condition_step=None): logits_dict = {} hidden_vec = input_dict['hidden_vec'] target = input_dict['target'] @@ -414,7 +415,7 @@ class SelfAttention(SubDecoderClass): memory_tensor = torch.cat(input_seq_list, dim=1) # (B*T) x (window_size + BOS + num_sub_tokens-1) x d_model return memory_tensor - def forward(self, input_dict, sampling_method=None, threshold=None, temperature=None): + def forward(self, input_dict, sampling_method=None, threshold=None, temperature=None, condition_step=None): logits_dict = {} hidden_vec = input_dict['hidden_vec'] # B x T x d_model target = input_dict['target'] # B x T x num_sub_tokens @@ -490,7 +491,7 @@ class SelfAttentionUniAudio(SelfAttention): memory_tensor = hidden_vec_reshape + feature_tensor return memory_tensor - def forward(self, input_dict, sampling_method=None, threshold=None, temperature=None): + def forward(self, input_dict, sampling_method=None, threshold=None, temperature=None, condition_step=None): logits_dict = {} hidden_vec = input_dict['hidden_vec'] # B x T x d_model target = input_dict['target'] # B x T x num_sub-tokens @@ -604,7 +605,7 @@ class CrossAttention(SubDecoderClass): memory_list.append(BOS_emb[-1:, :, :]) return memory_list - def forward(self, input_dict, sampling_method=None, threshold=None, temperature=None): + def forward(self, input_dict, sampling_method=None, threshold=None, temperature=None, condition_step=None): logits_dict = {} hidden_vec = input_dict['hidden_vec'] # B x T x d_model target = input_dict['target'] @@ -677,7 +678,7 @@ class Flatten4Encodec(SubDecoderClass): ): super().__init__(prediction_order, vocab, sub_decoder_depth, dim, heads, dropout, sub_decoder_enricher_use) - def forward(self, input_dict, sampling_method=None, threshold=None, temperature=None): + def forward(self, input_dict, sampling_method=None, threshold=None, temperature=None, condition_step=None): hidden_vec = input_dict['hidden_vec'] # ---- Training ---- # @@ -838,7 +839,7 @@ class DiffusionDecoder(SubDecoderClass): This function is designed to precompute the number of tokens that need to be transitioned at each step. ''' - mask_num = mask_index.sum(dim=1, keepdim=True) + mask_num = mask_index.sum(dim=1,keepdim=True) base = mask_num // steps remainder = mask_num % steps @@ -941,94 +942,7 @@ class DiffusionDecoder(SubDecoderClass): indices = torch.tensor([[step]], device=hidden_vec.device) return indices - - def forward_(self, input_dict, sampling_method=None, threshold=None, temperature=None, worst_case=False, validation=False): - logits_dict = {} - hidden_vec = input_dict['hidden_vec'] # B x T x d_model - target = input_dict['target'] #B x T x d_model - - - # apply window on hidden_vec for enricher - if self.sub_decoder_enricher_use: - window_applied_hidden_vec = self._apply_window_on_hidden_vec(hidden_vec) # (B*T) x window_size x d_model - hidden_vec_reshape = hidden_vec.reshape((hidden_vec.shape[0]*hidden_vec.shape[1], 1, -1)) # (B*T) x 1 x d_model - input_seq = hidden_vec_reshape.repeat(1, len(self.prediction_order), 1) # (B*T) x num_sub_tokens x d_model - input_seq_pos = input_seq - # input_seq_pos = self._apply_pos_enc(input_seq) # (B*T) x num_sub_tokens x d_model - # prepare memory - memory_list = self._prepare_memory_list(hidden_vec=hidden_vec, target=target, add_BOS=False) - # ---- Generate(Inference) ---- # - if target is None: - sampled_token_dict = {} - b,t,d = hidden_vec.shape # B x T x d_model - l = len(self.prediction_order) # num_sub_tokens - memory_tensor = self._get_noisy_tensor(target_shape=(b*t, l, d)) - all_noise_tensor = memory_tensor.clone() # (B*T) x num_sub_tokens x d_model - - # indicate the position of the mask token,1 means that the token hsa been masked - masked_history = torch.ones((b*t, l), device=hidden_vec.device, dtype=torch.int64).bool() - num_transfer_tokens = self._get_num_transfer_tokens(masked_history, self.denoising_steps) - # denoising c - stored_logits_dict = {} - stored_probs_dict = {} - for step in range(self.denoising_steps): - # nomalize the memory tensor - # memory_tensor = self.layer_norm(memory_tensor) # (B*T) x num_sub_tokens x d_model - if self.sub_decoder_enricher_use: - input_dict = {'input_seq': memory_tensor, 'memory': window_applied_hidden_vec} - input_dict = self.feature_enricher_layers(input_dict) - memory_tensor = input_dict['input_seq'] # (B*T) x num_sub_tokens x d_model - input_dict = {'input_seq': input_seq_pos, 'memory': memory_tensor, 'memory_mask': self.causal_ca_mask} - # input_dict = {'input_seq': memory_tensor, 'memory': input_seq_pos, 'memory_mask': self.causal_ca_mask} - input_dict = self.sub_decoder_layers(input_dict) - attn_output = input_dict['input_seq'] # (B*T) x num_sub_tokens x d_model - candidate_token_probs = {} - sampled_token_dict, logits_dict, candidate_token_probs, stacked_logits_probs, stacked_token_embeddings = self.sample_from_logits(attn_output, hidden_vec, sampling_method=sampling_method, threshold=threshold, temperature=temperature) - - # set prob of the changed tokens to -inf - stacked_logits_probs = torch.where(masked_history, stacked_logits_probs, -torch.inf) - # indices = self.choose_tokens(hidden_vec,step, "auto-regressive", stacked_logits_probs, num_transfer_tokens) - indices = self.choose_tokens(hidden_vec, step, self.method, stacked_logits_probs, num_transfer_tokens) - # breakpoint() - # undate the masked history - for i in range(b*t): - for j in range(l): - if j in indices[i]: - masked_history[i][j] = False - stored_logits_dict[self.prediction_order[j]] = logits_dict[self.prediction_order[j]].clone() - stored_probs_dict[self.prediction_order[j]] = candidate_token_probs[self.prediction_order[j]].clone() - expand_masked_history = masked_history.unsqueeze(-1).expand(-1, -1, memory_tensor.shape[-1]) # (B*T) x num_sub_tokens x d_model - memory_tensor = torch.where(expand_masked_history, all_noise_tensor, stacked_token_embeddings) - # breakpoint() - # print("stored_probs_dict", stored_probs_dict) - # print("sampled_token_dict", sampled_token_dict) - return stored_logits_dict, sampled_token_dict - - # ---- Training ---- # - _, masked_indices, p_mask = self._forward_process(target, mask_idx=self.MASK_idx) # (B*T) x (num_sub_tokens) x d_model - memory_tensor = self._prepare_embedding(memory_list, target) # (B*T) x (num_sub_tokens) x d_model - # apply layer norm - - extend_masked_indices = masked_indices.unsqueeze(-1).expand(-1, -1, memory_tensor.shape[-1]) # (B*T) x (num_sub_tokens) x d_model - if worst_case: # mask all ,turn into parallel - extend_masked_indices = torch.ones_like(extend_masked_indices).to(self.device) - memory_tensor = torch.where(extend_masked_indices, self.diffusion_mask_emb, memory_tensor) - if self.sub_decoder_enricher_use: - input_dict = {'input_seq': memory_tensor, 'memory': window_applied_hidden_vec} - input_dict = self.feature_enricher_layers(input_dict) - memory_tensor = input_dict['input_seq'] # (B*T) x num_sub_tokens x d_model - input_dict = {'input_seq': input_seq_pos, 'memory': memory_tensor, 'memory_mask': self.causal_ca_mask} - input_dict = self.sub_decoder_layers(input_dict) - attn_output = input_dict['input_seq'] # (B*T) x num_sub_tokens x d_model - # get prob - for idx, feature in enumerate(self.prediction_order): - feature_pos = self.feature_order_in_output[feature] - logit = self.hidden2logit[f"layer_{feature}"](attn_output[:, feature_pos, :]) - logit = logit.reshape((hidden_vec.shape[0], hidden_vec.shape[1], -1)) # B x T x vocab_size - logits_dict[feature] = logit - return logits_dict, (masked_indices, p_mask) - - def forward_old(self, input_dict, sampling_method=None, threshold=None, temperature=None, worst_case=False, validation=False): + def forward(self, input_dict, sampling_method=None, threshold=None, temperature=None, Force_decode=False, worst_case=False, condition_step=None): logits_dict = {} hidden_vec = input_dict['hidden_vec'] # B x T x d_model target = input_dict['target'] #B x T x d_model @@ -1070,139 +984,25 @@ class DiffusionDecoder(SubDecoderClass): # indicate the position of the mask token,1 means that the token hsa been masked masked_history = torch.ones((b*t, l), device=hidden_vec.device, dtype=torch.int64).bool() - num_transfer_tokens = self._get_num_transfer_tokens(masked_history, self.denoising_steps) - # denoising c + # add attribute control here stored_logits_dict = {} - stored_probs_dict = {} - for step in range(self.denoising_steps): - memory_tensor = self._apply_pos_enc(memory_tensor) # (B*T) x num_sub_tokens x d_model - # nomalize the memory tensor - # memory_tensor = self.layer_norm(memory_tensor) # (B*T) x num_sub_tokens x d_model - if self.sub_decoder_enricher_use: - input_dict = {'input_seq': memory_tensor, 'memory': window_applied_hidden_vec} - input_dict = self.feature_enricher_layers(input_dict) - memory_tensor = input_dict['input_seq'] # (B*T) x num_sub_tokens x d_model - # input_dict = {'input_seq': input_seq_pos, 'memory': memory_tensor, 'memory_mask': self.causal_ca_mask} - input_dict = {'input_seq': memory_tensor, 'memory': input_seq_pos, 'memory_mask': self.causal_ca_mask} - input_dict = self.sub_decoder_layers(input_dict) - attn_output = input_dict['input_seq'] # (B*T) x num_sub_tokens x d_model - candidate_token_probs = {} - candidate_token_embeddings = {} - for idx, feature in enumerate(self.prediction_order): - feature_pos = self.feature_order_in_output[feature] - logit = self.hidden2logit[f"layer_{feature}"](attn_output[:, feature_pos, :]) - logit = logit.reshape((hidden_vec.shape[0], hidden_vec.shape[1], -1)) # B x T x vocab_size - logits_dict[feature] = logit - sampled_token,probs = sample_with_prob(logit, sampling_method=sampling_method, threshold=threshold, temperature=temperature) - # print(idx,feature,sampled_token,probs) - sampled_token_dict[feature] = sampled_token - candidate_token_probs[feature] = probs - feature_emb = self.emb_layer.get_emb_by_key(feature, sampled_token) - feature_emb_reshape = feature_emb.reshape((1, 1, -1)) # (B*T) x 1 x emb_size - candidate_token_embeddings[feature] = feature_emb_reshape - - stacked_logits_probs = torch.stack(list(candidate_token_probs.values()), dim=0).reshape((b*t, l)) # (B*T) x num_sub_tokens x vocab_size - stacked_token_embeddings = torch.stack(list(candidate_token_embeddings.values()), dim=0).reshape((b*t, l, d)) - - # set prob of the changed tokens to -inf - stacked_logits_probs = torch.where(masked_history, stacked_logits_probs, -torch.inf) - - if self.method == 'low-confidence': - _, indices = torch.topk(stacked_logits_probs, k=int(num_transfer_tokens[:,step]), dim=-1) - elif self.method == 'random': - indices = torch.randint(0, stacked_logits_probs.shape[-1], (num_transfer_tokens[:, step],)).to(logit.device) - elif self.method == 'auto-regressive': - indices = torch.tensor([[step]], device=logit.device) - # undate the masked history + stored_token_embeddings = torch.zeros((b*t, l, d), device=hidden_vec.device) + if condition_step is not None: + # print("shape of condition_step", condition_step.shape) + condition_step = condition_step.reshape((b*t, l)) for i in range(b*t): for j in range(l): - if j in indices[i]: - masked_history[i][j] = False - stored_logits_dict[self.prediction_order[j]] = logits_dict[self.prediction_order[j]].clone() - stored_probs_dict[self.prediction_order[j]] = candidate_token_probs[self.prediction_order[j]].clone() - expand_masked_history = masked_history.unsqueeze(-1).expand(-1, -1, memory_tensor.shape[-1]) # (B*T) x num_sub_tokens x d_model - memory_tensor = torch.where(expand_masked_history, all_noise_tensor, stacked_token_embeddings) - return stored_logits_dict, sampled_token_dict - - # ---- Training ---- # - _, masked_indices, p_mask = self._forward_process(target, mask_idx=self.MASK_idx) # (B*T) x (num_sub_tokens) x d_model - memory_tensor = self._prepare_embedding(memory_list, target) # (B*T) x (num_sub_tokens) x d_model - # apply layer norm - - extend_masked_indices = masked_indices.unsqueeze(-1).expand(-1, -1, memory_tensor.shape[-1]) # (B*T) x (num_sub_tokens) x d_model - if worst_case: # mask all ,turn into parallel - extend_masked_indices = torch.ones_like(extend_masked_indices).to(self.device) - memory_tensor = torch.where(extend_masked_indices, self.diffusion_mask_emb, memory_tensor) - memory_tensor = self._apply_pos_enc(memory_tensor) # (B*T) x num_sub_tokens x d_model - # all is embedding - # memory_tensor = self.layer_norm(memory_tensor) - # apply feature enricher to memory - if self.sub_decoder_enricher_use: - input_dict = {'input_seq': memory_tensor, 'memory': window_applied_hidden_vec} - input_dict = self.feature_enricher_layers(input_dict) - memory_tensor = input_dict['input_seq'] # (B*T) x num_sub_tokens x d_model - # implement sub decoder cross attention - # input_dict = {'input_seq': input_seq_pos, 'memory': memory_tensor, 'memory_mask': self.causal_ca_mask} - # inter_input = torch.cat([input_seq_pos, memory_tensor], dim=1) - # inter_input = input_seq_pos + memory_tensor # (B*T) x num_sub_tokens x d_model - # input_dict = {'input_seq': input_seq_pos, 'memory': memory_tensor, 'memory_mask': self.causal_ca_mask} - input_dict = {'input_seq': memory_tensor, 'memory': input_seq_pos, 'memory_mask': self.causal_ca_mask} - input_dict = self.sub_decoder_layers(input_dict) - attn_output = input_dict['input_seq'] # (B*T) x num_sub_tokens x d_model - # get prob - for idx, feature in enumerate(self.prediction_order): - feature_pos = self.feature_order_in_output[feature] - logit = self.hidden2logit[f"layer_{feature}"](attn_output[:, feature_pos, :]) - logit = logit.reshape((hidden_vec.shape[0], hidden_vec.shape[1], -1)) # B x T x vocab_size - logits_dict[feature] = logit - return logits_dict, (masked_indices, p_mask) - - def forward(self, input_dict, sampling_method=None, threshold=None, temperature=None, Force_decode=False, worst_case=False, validation=False): - logits_dict = {} - hidden_vec = input_dict['hidden_vec'] # B x T x d_model - target = input_dict['target'] #B x T x d_model - bos_hidden_vec = input_dict['bos_token_hidden'] # B x 1 x d_model, used for the first token in the sub-decoder + token = condition_step[i][j] + if condition_step[i][j] != self.MASK_idx: - # apply window on hidden_vec for enricher - if self.sub_decoder_enricher_use: - window_applied_hidden_vec = self._apply_window_on_hidden_vec(hidden_vec) # (B*T) x window_size x d_model - hidden_vec_reshape = hidden_vec.reshape((hidden_vec.shape[0]*hidden_vec.shape[1], 1, -1)) # (B*T) x 1 x d_model - input_seq = hidden_vec_reshape.repeat(1, len(self.prediction_order), 1) # (B*T) x num_sub_tokens x d_model - input_seq_pos = self._apply_pos_enc(input_seq) # (B*T) x num_sub_tokens x d_model - - if bos_hidden_vec is None: # start of generation - if target is None: - bos_hidden_vec = input_seq_pos - else: - bos_hidden_vec =hidden_vec[:, 0, :].unsqueeze(1).repeat(1, hidden_vec.shape[1], 1) # B x T x d_model - bos_hidden_vec = bos_hidden_vec.reshape((hidden_vec.shape[0]*hidden_vec.shape[1], 1, -1)) - bos_hidden_vec = bos_hidden_vec.repeat(1, len(self.prediction_order), 1) - - else: - bos_hidden_vec = bos_hidden_vec.repeat(1, len(self.prediction_order), 1) # (B*T) x num_sub_tokens x d_model - - # input_seq_pos = input_seq - input_dict = {'input_seq': input_seq_pos, 'memory': bos_hidden_vec, 'memory_mask': self.causal_ca_mask} - boosted_input_dict = self.feature_boost_layers(input_dict) # (B*T) x num_sub_tokens x d_model - input_seq_pos = boosted_input_dict['input_seq'] # (B*T) x num_sub_tokens x d_model - # input_seq_pos = self.input_norm(input_seq_pos) # (B*T) x num_sub_tokens x d_model - # input_seq_pos = self._apply_pos_enc(input_seq) # (B*T) x num_sub_tokens x d_model - # prepare memory - memory_list = self._prepare_memory_list(hidden_vec=hidden_vec, target=target, add_BOS=False) - # ---- Generate(Inference) ---- # - if target is None: - sampled_token_dict = {} - b,t,d = hidden_vec.shape # B x T x d_model - l = len(self.prediction_order) # num_sub_tokens - memory_tensor = self._get_noisy_tensor(target_shape=(b*t, l, d)) - all_noise_tensor = memory_tensor.clone() # (B*T) x num_sub_tokens x d_model + # print(f"Conditioning on token {token} for feature {self.prediction_order[j]} at position {(i,j)}") + masked_history[i][j] = False + memory_tensor[i][j][:] = self.emb_layer.get_emb_by_key(self.prediction_order[j], condition_step[i][j]) + stored_token_embeddings[i][j][:] = memory_tensor[i][j][:] + # print(f"Embedded token for feature {self.prediction_order[j]} at position {(i,j)}") - # indicate the position of the mask token,1 means that the token hsa been masked - masked_history = torch.ones((b*t, l), device=hidden_vec.device, dtype=torch.int64).bool() num_transfer_tokens = self._get_num_transfer_tokens(masked_history, self.denoising_steps) # denoising c - stored_logits_dict = {} - stored_probs_dict = {} # with torch.profiler.profile( # activities=[ # torch.profiler.ProfilerActivity.CPU, @@ -1213,8 +1013,6 @@ class DiffusionDecoder(SubDecoderClass): # ) as prof: for step in range(self.denoising_steps): memory_tensor = self._apply_pos_enc(memory_tensor) # (B*T) x num_sub_tokens x d_model - # nomalize the memory tensor - # memory_tensor = self.layer_norm(memory_tensor) # (B*T) x num_sub_tokens x d_model if self.sub_decoder_enricher_use: input_dict = {'input_seq': memory_tensor, 'memory': window_applied_hidden_vec} input_dict = self.feature_enricher_layers(input_dict) @@ -1223,14 +1021,15 @@ class DiffusionDecoder(SubDecoderClass): input_dict = {'input_seq': memory_tensor, 'memory': input_seq_pos, 'memory_mask': self.causal_ca_mask} input_dict = self.sub_decoder_layers(input_dict) attn_output = input_dict['input_seq'] # (B*T) x num_sub_tokens x d_model - candidate_token_probs = {} - sampled_token_dict, logits_dict, candidate_token_probs, stacked_logits_probs, stacked_token_embeddings = self.sample_from_logits(attn_output, hidden_vec, sampling_method=sampling_method, threshold=threshold, temperature=temperature, force_decode=Force_decode, step=step) + # print("step", step) + # print("toknes", sampled_token_dict) # set prob of the changed tokens to -inf stacked_logits_probs = torch.where(masked_history, stacked_logits_probs, -torch.inf) + print("stacked_logits_probs", stacked_logits_probs.clone()) if self.method == 'low-confidence': _, indices = torch.topk(stacked_logits_probs, k=int(num_transfer_tokens[:,step]), dim=-1) @@ -1242,12 +1041,25 @@ class DiffusionDecoder(SubDecoderClass): for i in range(b*t): for j in range(l): if j in indices[i]: + # print(f"Step {step}: Updating token for feature {self.prediction_order[j]} at position {(i,j)}") masked_history[i][j] = False stored_logits_dict[self.prediction_order[j]] = logits_dict[self.prediction_order[j]].clone() + stored_token_embeddings[i][j][:] = stacked_token_embeddings[i][j][:] expand_masked_history = masked_history.unsqueeze(-1).expand(-1, -1, memory_tensor.shape[-1]) # (B*T) x num_sub_tokens x d_model - memory_tensor = torch.where(expand_masked_history, all_noise_tensor, stacked_token_embeddings) + memory_tensor = torch.where(expand_masked_history, all_noise_tensor, stored_token_embeddings) + # skip if all tokens are unmasked + if not expand_masked_history.any(): + # print("All tokens have been unmasked. Ending denoising process.") + break # print(prof.key_averages().table(sort_by="self_cpu_time_total", row_limit=10)) + # get final sampled tokens by embedding the unmasked tokens + sampled_token_dict = {} + for idx, feature in enumerate(self.prediction_order): + sampled_token = self.emb_layer.get_token_by_emb(feature, memory_tensor[:, idx, :]) + sampled_token_dict[feature] = sampled_token + # print("Final sampled tokens:") # print(sampled_token_dict) + # print(condition_step) return stored_logits_dict, sampled_token_dict # ---- Training ---- # diff --git a/Amadeus/symbolic_encoding/data_utils.py b/Amadeus/symbolic_encoding/data_utils.py index 23e4583..26231f2 100644 --- a/Amadeus/symbolic_encoding/data_utils.py +++ b/Amadeus/symbolic_encoding/data_utils.py @@ -510,6 +510,7 @@ class Melody(SymbolicMusicDataset): shuffled_tune_names = list(self.tune_in_idx.keys()) song_names_without_version = [re.sub(r"\.\d+$", "", song) for song in shuffled_tune_names] song_dict = {} + ratio = 0.8 for song, orig_song in zip(song_names_without_version, shuffled_tune_names): if song not in song_dict: song_dict[song] = [] diff --git a/Amadeus/symbolic_yamls/config-accelerate.yaml b/Amadeus/symbolic_yamls/config-accelerate.yaml index 5dc78b8..d472105 100644 --- a/Amadeus/symbolic_yamls/config-accelerate.yaml +++ b/Amadeus/symbolic_yamls/config-accelerate.yaml @@ -2,8 +2,8 @@ defaults: # - nn_params: nb8_embSum_NMT # - nn_params: remi8 # - nn_params: nb8_embSum_diff_t2m_150M_finetunning - # - nn_params: nb8_embSum_diff_t2m_600M_pretrainingv2 - - nn_params: nb8_embSum_diff_t2m_600M_finetunningv2 + - nn_params: nb8_embSum_diff_t2m_600M_pretrainingv2 + # - nn_params: nb8_embSum_diff_t2m_600M_finetunningv2 # - nn_params: nb8_embSum_subPararell # - nn_params: nb8_embSum_diff_t2m_150M @@ -15,7 +15,7 @@ defaults: # - nn_params: remi8_main12_head_16_dim512 # - nn_params: nb5_embSum_diff_main12head16dim768_sub3 -dataset: FinetuneDataset # Pop1k7, Pop909, SOD, LakhClean,PretrainingDataset FinetuneDataset +dataset: msmidi # Pop1k7, Pop909, SOD, LakhClean,PretrainingDataset FinetuneDataset captions_path: dataset/midicaps/train_set.json # dataset: SymphonyNet_Dataset # Pop1k7, Pop909, SOD, LakhClean @@ -44,7 +44,7 @@ train_params: focal_gamma: 0 # learning rate scheduler: 'cosinelr', 'cosineannealingwarmuprestarts', 'not-using', please check train_utils.py for more details scheduler : cosinelr - initial_lr: 0.0004 + initial_lr: 0.0003 decay_step_rate: 0.8 # means it will reach its lowest point at decay_step_rate * total_num_iter num_steps_per_cycle: 20000 # number of steps per cycle for 'cosineannealingwarmuprestarts' warmup_steps: 2000 #number of warmup steps @@ -59,7 +59,7 @@ inference_params: data_params: first_pred_feature: pitch # compound shifting for NB only, choose the target sub-token (remi and cp are not influenced by this argument) split_ratio: 0.998 # train-validation-test split ratio - aug_type: pitch # random, null | pitch and chord augmentation type + aug_type: null # random, null | pitch and chord augmentation type general: debug: False make_log: True # True, False | update the log file in wandb online to your designated project and entity diff --git a/Amadeus/trainer_accelerate.py b/Amadeus/trainer_accelerate.py index 3af0b6b..2e4224a 100644 --- a/Amadeus/trainer_accelerate.py +++ b/Amadeus/trainer_accelerate.py @@ -74,7 +74,8 @@ class LanguageModelTrainer: sampling_threshold: float, # Threshold for sampling decisions sampling_temperature: float, # Temperature for controlling sampling randomness config, # Configuration parameters (contains general, training, and inference settings) - model_checkpoint: Union[str, None] = None, # Path to a pre-trainmodl checkpoint (optional) + model_checkpoint="wandb/run-20251016_180043-70ihsi93/files/checkpoints/iter80999_loss0.0300.pt", # Path to a pre-trained model checkpoint (optional) + # model_checkpoint: Union[str, None] = None, # Path to a pre-trainmodl checkpoint (optional) ): # Save model, optimizer, and other configurations self.model = model diff --git a/Amadeus/transformer_utils.py b/Amadeus/transformer_utils.py index f47ee17..8b970c3 100644 --- a/Amadeus/transformer_utils.py +++ b/Amadeus/transformer_utils.py @@ -111,6 +111,23 @@ class MultiEmbedding(nn.Module): def get_emb_by_key(self, key, token): layer_idx = self.feature_list.index(key) return self.layers[layer_idx](token) + + def get_token_by_emb(self, key, token_emb): + ''' + token_emb: B x emb_size + ''' + layer_idx = self.feature_list.index(key) + embedding_layer = self.layers[layer_idx] # nn.Embedding + # compute cosine similarity between token_emb and embedding weights + emb_weights = embedding_layer.weight # vocab_size x emb_size + cos_sim = torch.nn.functional.cosine_similarity( + token_emb.unsqueeze(1), # B x 1 x emb_size + emb_weights.unsqueeze(0), # 1 x vocab_size x emb_size + dim=-1 + ) # B x vocab_size + # get the index of the most similar embedding + token_idx = torch.argmax(cos_sim, dim=-1) # B + return token_idx class SummationEmbedder(MultiEmbedding): def __init__( diff --git a/data_representation/step1_midi2corpus_fined.py b/data_representation/step1_midi2corpus_fined.py index 49c8f25..713caaf 100644 --- a/data_representation/step1_midi2corpus_fined.py +++ b/data_representation/step1_midi2corpus_fined.py @@ -168,7 +168,7 @@ class CorpusMaker(): print("length of midi list: ", len(self.midi_list)) # Use set for faster lookup (O(1) per check) processed_files_set = set(processed_files) - # self.midi_list = [x for x in self.midi_list if x.name not in processed_files_set] + self.midi_list = [x for x in self.midi_list if x.name not in processed_files_set] # reverse the list to process the latest files first self.midi_list.reverse() print(f"length of midi list after filtering: ", len(self.midi_list)) diff --git a/data_representation/step2_corpus2event.py b/data_representation/step2_corpus2event.py index 74b2b55..6322516 100644 --- a/data_representation/step2_corpus2event.py +++ b/data_representation/step2_corpus2event.py @@ -61,7 +61,7 @@ class Corpus2Event(): # remove the corpus files that are already in the out_dir # Use set for faster existence checks existing_files = set(f.name for f in self.out_dir.glob("*.pkl")) - # corpus_list = [corpus for corpus in corpus_list if corpus.name not in existing_files] + corpus_list = [corpus for corpus in corpus_list if corpus.name not in existing_files] for filepath_name, event in tqdm(map(self._load_single_corpus_and_make_event, corpus_list), total=len(corpus_list)): if event is None: broken_count += 1 diff --git a/generate-batch.py b/generate-batch.py index f371b3f..607f547 100644 --- a/generate-batch.py +++ b/generate-batch.py @@ -1,3 +1,4 @@ +from ast import arg import sys import os from pathlib import Path @@ -25,14 +26,25 @@ def get_argument_parser(): parser.add_argument( "-generation_type", type=str, - choices=('conditioned', 'unconditioned', 'text-conditioned'), + choices=('conditioned', 'unconditioned', 'text-conditioned', 'attr-conditioned'), default='unconditioned', help="generation type", ) + parser.add_argument( + "-attr_list", + type=str, + default="beat,duration", + help="attribute list for attribute-controlled generation", + ) + parser.add_argument( + "-dataset", + type=str, + help="dataset name, only for conditioned generation", + ) parser.add_argument( "-sampling_method", type=str, - choices=('top_p', 'top_k'), + choices=('top_p', 'top_k', 'min_p'), default='top_p', help="sampling method", ) @@ -74,7 +86,7 @@ def get_argument_parser(): parser.add_argument( "-num_processes", type=int, - default=4, + default=1, help="number of processes to use", ) parser.add_argument( @@ -97,7 +109,7 @@ def get_argument_parser(): ) return parser -def load_resources(wandb_exp_dir, device): +def load_resources(wandb_exp_dir, condition_dataset, device): """Load model and dataset resources for a process""" wandb_dir = Path('wandb') ckpt_path, config_path, metadata_path, vocab_path = get_best_ckpt_path_and_config(wandb_dir, wandb_exp_dir) @@ -107,7 +119,8 @@ def load_resources(wandb_exp_dir, device): # Load checkpoint to specified device print("Loading checkpoint from:", ckpt_path) ckpt = torch.load(ckpt_path, map_location=device) - model, test_set, vocab = prepare_model_and_dataset_from_config(config, metadata_path, vocab_path) + print(config) + model, test_set, vocab = prepare_model_and_dataset_from_config(config, metadata_path, vocab_path, condition_dataset) model.load_state_dict(ckpt['model'], strict=False) model.to(device) model.eval() @@ -123,20 +136,33 @@ def load_resources(wandb_exp_dir, device): return config, model, dataset_for_prompt, vocab -def conditioned_worker(process_idx, gpu_id, args, data_slice): +def conditioned_worker(process_idx, gpu_id, args): """Worker process for conditioned generation""" torch.cuda.set_device(gpu_id) device = torch.device(f'cuda:{gpu_id}') # Load resources with proper device - config, model, dataset_for_prompt, vocab = load_resources(args.wandb_exp_dir, device) + config, model, test_set, vocab = load_resources(args.wandb_exp_dir, args.dataset, device) + # print(test_set) + if args.choose_selected_tunes and test_set.dataset == 'SOD': + selected_tunes = ['Requiem_orch', 'magnificat_bwv-243_8_orch', + "Clarinet Concert in A Major: 2nd Movement, Adagio_orch"] + else: + selected_tunes = [name for _, name in test_set][:args.num_samples] + + # Split selected data across processes + selected_data = [d for d in test_set if d[1] in selected_tunes] + chunk_size = (len(selected_data) + args.num_processes - 1) // args.num_processes + start_idx = 1 + end_idx = min(chunk_size, len(selected_data)) + data_slice = selected_data[start_idx:end_idx] # Create output directory with process index base_path = Path('wandb') / args.wandb_exp_dir / \ f"cond_{args.num_target_measure}m_{args.sampling_method}_t{args.threshold}_temp{args.temperature}" base_path.mkdir(parents=True, exist_ok=True) - evaluator = Evaluator(config, model, dataset_for_prompt, vocab, device=device) + evaluator = Evaluator(config, model, data_slice, vocab, device=device) # Process assigned data slice for idx, (tune_in_idx, tune_name) in enumerate(data_slice): @@ -154,13 +180,62 @@ def conditioned_worker(process_idx, gpu_id, args, data_slice): generation_length=args.generate_length ) +def attr_conditioned_worker(process_idx, gpu_id, args): + """Worker process for conditioned generation""" + torch.cuda.set_device(gpu_id) + device = torch.device(f'cuda:{gpu_id}') + # attr_list = "position,duration" + attr_list = args.attr_list.split(',') + + # Load resources with proper device + config, model, test_set, vocab = load_resources(args.wandb_exp_dir, args.dataset, device) + # print(test_set) + if args.choose_selected_tunes and test_set.dataset == 'SOD': + selected_tunes = ['Requiem_orch', 'magnificat_bwv-243_8_orch', + "Clarinet Concert in A Major: 2nd Movement, Adagio_orch"] + else: + selected_tunes = [name for _, name in test_set][:args.num_samples] + + # Split selected data across processes + selected_data = [d for d in test_set if d[1] in selected_tunes] + # chunk_size = (len(selected_data) + args.num_processes - 1) // args.num_processes + # start_idx = 1 + # end_idx = min(chunk_size, len(selected_data)) + # data_slice = selected_data[start_idx:end_idx] + data_slice = selected_data + + # Create output directory with process index + base_path = Path('wandb') / args.wandb_exp_dir / \ + f"attrcond_{args.num_target_measure}m_{args.sampling_method}_t{args.threshold}_temp{args.temperature}_attrs{'-'.join(attr_list)}" + base_path.mkdir(parents=True, exist_ok=True) + + evaluator = Evaluator(config, model, data_slice, vocab, device=device) + + # Process assigned data slice + for idx, (tune_in_idx, tune_name) in enumerate(data_slice): + batch_dir = base_path + batch_dir.mkdir(parents=True, exist_ok=True) + evaluator.generate_samples_with_attrCtl( + batch_dir, + args.num_target_measure, + tune_in_idx, + tune_name, + config.data_params.first_pred_feature, + args.sampling_method, + args.threshold, + args.temperature, + generation_length=args.generate_length, + attr_list=attr_list + ) + + def unconditioned_worker(process_idx, gpu_id, args, num_samples): """Worker process for unconditioned generation""" torch.cuda.set_device(gpu_id) device = torch.device(f'cuda:{gpu_id}') # Load resources with proper device - config, model, dataset_for_prompt, vocab = load_resources(args.wandb_exp_dir, device) + config, model, dataset_for_prompt, vocab = load_resources(args.wandb_exp_dir, args.dataset, device) # Create output directory with process index base_path = Path('wandb') / args.wandb_exp_dir / \ @@ -187,7 +262,7 @@ def text_conditioned_worker(process_idx, gpu_id, args, num_samples, data_slice): device = torch.device(f'cuda:{gpu_id}') # Load resources with proper device - config, model, dataset_for_prompt, vocab = load_resources(args.wandb_exp_dir, device) + config, model, dataset_for_prompt, vocab = load_resources(args.wandb_exp_dir, args.dataset, device) # Create output directory with process index base_path = Path('wandb') / args.wandb_exp_dir / \ @@ -237,40 +312,33 @@ def main(): if not wandb_dir.exists(): raise FileNotFoundError(f"Experiment {args.wandb_exp_dir} not found") - # Load test set to get selected tunes (dummy load to get dataset info) - dummy_device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') - _, test_set, _ = prepare_model_and_dataset_from_config( - wandb_dir / "files" / "config.yaml", - wandb_dir / "files" / "metadata.json", - wandb_dir / "files" / "vocab.json" - ) - - if args.choose_selected_tunes and test_set.dataset == 'SOD': - selected_tunes = ['Requiem_orch', 'magnificat_bwv-243_8_orch', - "Clarinet Concert in A Major: 2nd Movement, Adagio_orch"] - else: - selected_tunes = [name for _, name in test_set.data_list][:args.num_samples] - - # Split selected data across processes - selected_data = [d for d in test_set.data_list if d[1] in selected_tunes] - chunk_size = (len(selected_data) + args.num_processes - 1) // args.num_processes for i in range(args.num_processes): - start_idx = i * chunk_size - end_idx = min((i+1)*chunk_size, len(selected_data)) - data_slice = selected_data[start_idx:end_idx] - - if not data_slice: - continue gpu_id = gpu_ids[i % len(gpu_ids)] p = Process( target=conditioned_worker, - args=(i, gpu_id, args, data_slice) + args=(i, gpu_id, args) ) processes.append(p) p.start() - + elif args.generation_type == 'attr-conditioned': + # Prepare selected tunes + wandb_dir = Path('wandb') / args.wandb_exp_dir + if not wandb_dir.exists(): + raise FileNotFoundError(f"Experiment {args.wandb_exp_dir} not found") + + + for i in range(args.num_processes): + + gpu_id = gpu_ids[i % len(gpu_ids)] + p = Process( + target=attr_conditioned_worker, + args=(i, gpu_id, args) + ) + processes.append(p) + p.start() + elif args.generation_type == 'unconditioned': samples_per_proc = args.num_samples // args.num_processes remainder = args.num_samples % args.num_processes diff --git a/len_tunes/Melody/len_nb8.png b/len_tunes/Melody/len_nb8.png new file mode 100644 index 0000000000000000000000000000000000000000..01ef6ae7c117570905ee60eac20c875cb4fe4296 GIT binary patch literal 19427 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 literal 0 HcmV?d00001 diff --git a/len_tunes/msmidi/len_nb8.png b/len_tunes/msmidi/len_nb8.png new file mode 100644 index 0000000000000000000000000000000000000000..1e58ef8f15b5fb4c178ffd1acdce45d5df1ea376 GIT binary patch literal 19800 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 literal 0 HcmV?d00001 diff --git a/midi_sim.py b/midi_sim.py new file mode 100644 index 0000000..44c5ba5 --- /dev/null +++ b/midi_sim.py @@ -0,0 +1,134 @@ +import os +from math import ceil +#CUDA_VISIBLE_DEVICES= "0" +import numpy as np +import pandas as pd +from symusic import Score +from concurrent.futures import ProcessPoolExecutor, as_completed +from tqdm import tqdm +semitone2degree = np.array([0, 2, 2, 3, 3, 4, 4.5, 4, 3, 3, 2, 2]) + +def hausdorff_dist(a: np.ndarray, b: np.ndarray, weight: tuple[float, float] = (0., 1.5)): + if(not a.shape[1] or not b.shape[1]): + return np.inf + a_onset, a_pitch = a + b_onset, b_pitch = b + a_onset = a_onset.astype(np.float32) + b_onset = b_onset.astype(np.float32) + a_pitch = a_pitch.astype(np.int16) + b_pitch = b_pitch.astype(np.int16) + + onset_dist_matrix = np.abs(a_onset.reshape(1, -1) - b_onset.reshape(-1, 1)) + a2b_idx = onset_dist_matrix.argmin(1) + b2a_idx = onset_dist_matrix.argmin(0) + + a_pitch -= (np.median(a_pitch) - np.median(b_pitch)).astype(np.int16) # Normalize pitch + a_pitch = a_pitch + np.arange(-7, 7).reshape(-1, 1) # Transpose invarient + + interval_diff = np.concatenate([ + a_pitch[:, a2b_idx] - b_pitch, + b_pitch[b2a_idx] - a_pitch], axis=1) + pitch_dist = np.abs(semitone2degree[interval_diff % 8] + np.abs(interval_diff) // 8 * np.sign(interval_diff)).mean(1).min() + onset_dist = np.abs(np.concatenate([ + a_onset[a2b_idx] - b_onset, + b_onset[b2a_idx] - a_onset], axis=0)).mean() + + return (weight[0] * onset_dist + weight[1] * pitch_dist) / sum(weight) + + +def midi_time_sliding_window(x: list[tuple[float, int]], window_size: float = 8., hop_size: float = 4.): + x = sorted(x) + trim_offset = (x[0][0] // hop_size) * hop_size + end_time = x[-1][0] + num_segment = ceil((end_time - window_size - trim_offset) / hop_size) + 1 + + time_matrix = (np.fromiter((time for time, _ in x), dtype=float) - trim_offset).reshape(1, -1).repeat(num_segment, axis=0) + seg_time_starts = np.arange(num_segment).reshape(-1, 1) * hop_size + + time_compare_matrix = np.where((time_matrix >= seg_time_starts) & (time_matrix <= seg_time_starts + window_size), 0, 1) + time_compare_matrix = np.diff(np.pad(time_compare_matrix, ((0, 0), (1, 1)), constant_values=1)) + start_idxs = sorted(np.where(time_compare_matrix == -1), key=lambda x: x[0])[1].tolist() + end_idxs = sorted(np.where(time_compare_matrix == 1), key=lambda x: x[0])[1].tolist() + + segments = [x[start:end] for start, end in zip(start_idxs, end_idxs)] + + return segments + + +def midi_dist(a: list[tuple[float, int]], b: list[tuple[float, int]], window_size: float = 16., hop_size: float = 4): + a = midi_time_sliding_window(a, window_size=window_size, hop_size=hop_size) + b = midi_time_sliding_window(b, window_size=window_size, hop_size=hop_size) + dist = np.inf + for x,i in enumerate(a): + for y,j in enumerate(b): + cur_dist = hausdorff_dist(np.array(i, dtype=np.float32).T, np.array(j, dtype=np.float32).T) + if cur_dist == 0: + print(x, y) + if(cur_dist < dist): + dist = cur_dist + return float(dist) + + +def extract_notes(filepath: str): + """读取MIDI并返回 (time, pitch) 列表""" + try: + s = Score(filepath).to("quarter") + notes = [] + # for t in s.tracks: + # notes.extend([(n.time, n.pitch) for n in t.notes]) + notes = [(n.time, n.pitch) for n in s.tracks[0].notes] # 仅使用第一个track + return notes + except Exception as e: + print(f"读取 {filepath} 出错: {e}") + return [] + +def compare_pair(file_a: str, file_b: str): + try: + notes_a = extract_notes(file_a) + notes_b = extract_notes(file_b) + if not notes_a or not notes_b: + return (file_a, file_b, np.inf) + dist = midi_dist(notes_a, notes_b) + return (file_a, file_b, dist) + except Exception as e: + import traceback + print(f"⚠️ compare_pair 出错: {file_a} vs {file_b}") + traceback.print_exc() + return (file_a, file_b, np.inf) + + +def batch_compare(dir_a: str, dir_b: str, out_csv: str = "midi_similarity.csv", max_workers: int = 8): + files_a = [os.path.join(dir_a, f) for f in os.listdir(dir_a) if f.endswith(".mid")] + files_a = files_a[:100] # 仅比较前100个文件以节省时间 + files_b = [os.path.join(dir_b, f) for f in os.listdir(dir_b) if f.endswith(".mid")] + + results = [] + pbar = tqdm(total=len(files_a) * len(files_b), desc="Comparing MIDI files") + with ProcessPoolExecutor(max_workers=max_workers) as executor: + futures = [executor.submit(compare_pair, fa, fb) for fa in files_a for fb in files_b] + for fut in as_completed(futures): + pbar.update(1) + try: + results.append(fut.result()) + except Exception as e: + print(fut.result()) + print(f"Error comparing pair: {e}") + # print(f"Compared: {results[-1][0]} vs {results[-1][1]}, Distance: {results[-1][2]:.4f}") + # with tqdm(total=len(files_a) * len(files_b)) as pbar: + # for fa in files_a: + # for fb in files_b: + # results.append(compare_pair(fa, fb)) + # pbar.update(1) + # # 排序 + results = sorted(results, key=lambda x: x[2]) + + # 保存 + df = pd.DataFrame(results, columns=["file_a", "file_b", "distance"]) + df.to_csv(out_csv, index=False) + print(f"已保存结果到 {out_csv}") + + +if __name__ == "__main__": + dir_a = "wandb/run-20251015_154556-f0pj3ys3/cond_4m_top_p_t0.99_temp1.25/process_2_batch_23" + dir_b = "dataset/Melody" + batch_compare(dir_a, dir_b, out_csv="midi_similarity_v2.csv", max_workers=6) \ No newline at end of file diff --git a/,idi_sim.py b/,idi_sim.py deleted file mode 100644 index dfe9488..0000000 --- a/,idi_sim.py +++ /dev/null @@ -1,105 +0,0 @@ -import os -import numpy as np -import pandas as pd -from symusic import Score -from concurrent.futures import ProcessPoolExecutor, as_completed - -semitone2degree = np.array([0, 2, 2, 3, 3, 4, 4.5, 4, 3, 3, 2, 2]) - -def hausdorff_dist(a: np.ndarray, b: np.ndarray, weight: tuple[float, float] = (2., 1.5), oti: bool = True): - if(not a.shape[1] or not b.shape[1]): - return np.inf - a_onset, a_pitch = a - b_onset, b_pitch = b - a_onset = a_onset.astype(np.float32) - b_onset = b_onset.astype(np.float32) - a_pitch = a_pitch.astype(np.uint8) - b_pitch = b_pitch.astype(np.uint8) - - onset_dist_matrix = np.abs(a_onset.reshape(1, -1) - b_onset.reshape(-1, 1)) - if(oti): - pitch_dist_matrix = semitone2degree[np.abs(a_pitch.reshape(1, 1, -1) + np.arange(12).reshape(-1, 1, 1) - b_pitch.reshape(-1, 1)) % 12] - dist_matrix = (weight[0] * np.expand_dims(onset_dist_matrix, 0) + weight[1] * pitch_dist_matrix) / sum(weight) - a2b = dist_matrix.min(2) - b2a = dist_matrix.min(1) - dist = np.concatenate([a2b, b2a], axis=1) - return dist.sum(axis=1).min() / len(dist) - else: - pitch_dist_matrix = semitone2degree[np.abs(a_pitch.reshape(1, -1) - b_pitch.reshape(-1, 1)) % 12] - dist_matrix = (weight[0] * onset_dist_matrix + weight[1] * pitch_dist_matrix) / sum(weight) - a2b = dist_matrix.min(1) - b2a = dist_matrix.min(0) - return float((a2b.sum() + b2a.sum()) / (a.shape[1] + b.shape[1])) - - -def midi_time_sliding_window(x: list[tuple[float, int]], window_size: float = 16., hop_size: float = 4.): - x = sorted(x) - end_time = x[-1][0] - out = [[] for _ in range(int(end_time // hop_size))] - for i in sorted(x): - segment = min(int(i[0] // hop_size), len(out) - 1) - while(i[0] >= segment * hop_size): - out[segment].append(i) - segment -= 1 - if(segment < 0): - break - return out - - -def midi_dist(a: list[tuple[float, int]], b: list[tuple[float, int]], window_size: float = 16., hop_size: float = 4): - a = midi_time_sliding_window(a) - b = midi_time_sliding_window(b) - dist = np.inf - for i in a: - for j in b: - cur_dist = hausdorff_dist(np.array(i, dtype=np.float32).T, np.array(j, dtype=np.float32).T) - if(cur_dist < dist): - dist = cur_dist - return dist - - -def extract_notes(filepath: str): - """读取MIDI并返回 (time, pitch) 列表""" - try: - s = Score(filepath).to("quarter") - notes = [] - for t in s.tracks: - notes.extend([(n.time, n.pitch) for n in t.notes]) - return notes - except Exception as e: - print(f"读取 {filepath} 出错: {e}") - return [] - - -def compare_pair(file_a: str, file_b: str): - notes_a = extract_notes(file_a) - notes_b = extract_notes(file_b) - if not notes_a or not notes_b: - return (file_a, file_b, np.inf) - dist = midi_dist(notes_a, notes_b) - return (file_a, file_b, dist) - - -def batch_compare(dir_a: str, dir_b: str, out_csv: str = "midi_similarity.csv", max_workers: int = 8): - files_a = [os.path.join(dir_a, f) for f in os.listdir(dir_a) if f.endswith(".mid")] - files_b = [os.path.join(dir_b, f) for f in os.listdir(dir_b) if f.endswith(".mid")] - - results = [] - with ProcessPoolExecutor(max_workers=max_workers) as executor: - futures = [executor.submit(compare_pair, fa, fb) for fa in files_a for fb in files_b] - for fut in as_completed(futures): - results.append(fut.result()) - - # 排序 - results = sorted(results, key=lambda x: x[2]) - - # 保存 - df = pd.DataFrame(results, columns=["file_a", "file_b", "distance"]) - df.to_csv(out_csv, index=False) - print(f"已保存结果到 {out_csv}") - - -if __name__ == "__main__": - dir_a = "folder_a" - dir_b = "folder_b" - batch_compare(dir_a, dir_b, out_csv="midi_similarity.csv", max_workers=8) \ No newline at end of file