From e16c84aab2d0d9331f5ba213359c8bcbb99a7c03 Mon Sep 17 00:00:00 2001 From: Mars <2213876981@qq.com> Date: Wed, 29 Oct 2025 17:14:33 +0800 Subject: [PATCH] 1029 add octuple --- Amadeus/evaluation_utils.py | 10 +- Amadeus/model_zoo.py | 26 +- Amadeus/sub_decoder_zoo.py | 335 ++++++++++++- Amadeus/symbolic_encoding/augmentor.py | 2 + Amadeus/symbolic_encoding/compile_utils.py | 4 +- Amadeus/symbolic_encoding/data_utils.py | 8 +- Amadeus/symbolic_encoding/decoding_utils.py | 63 ++- Amadeus/symbolic_yamls/config-accelerate.yaml | 10 +- ...t8_embSum_diff_t2m_150M_pretrainingv2.yaml | 19 + Amadeus/train_utils.py | 14 +- Amadeus/trainer_accelerate.py | 7 +- data_representation/octuple2tuneinidx.py | 135 ++++++ data_representation/test.py | 14 + data_representation/vocab_utils.py | 82 +++- generate-batch.py | 11 +- len_tunes/IrishMan/len_oct8.png | Bin 0 -> 21567 bytes len_tunes/Melody/len_nb8.png | Bin 19427 -> 13603 bytes len_tunes/Melody/len_oct8.png | Bin 0 -> 18941 bytes len_tunes/msmidi/len_oct8.png | Bin 0 -> 20366 bytes midi_sim.py | 2 +- train_accelerate.py | 13 +- vocab.json | 442 ++++++++++++++++++ 22 files changed, 1135 insertions(+), 62 deletions(-) create mode 100644 Amadeus/symbolic_yamls/nn_params/oct8_embSum_diff_t2m_150M_pretrainingv2.yaml create mode 100644 data_representation/octuple2tuneinidx.py create mode 100644 data_representation/test.py create mode 100644 len_tunes/IrishMan/len_oct8.png create mode 100644 len_tunes/Melody/len_oct8.png create mode 100644 len_tunes/msmidi/len_oct8.png create mode 100644 vocab.json diff --git a/Amadeus/evaluation_utils.py b/Amadeus/evaluation_utils.py index 37f9917..f5d7b13 100644 --- a/Amadeus/evaluation_utils.py +++ b/Amadeus/evaluation_utils.py @@ -96,7 +96,7 @@ def prepare_model_and_dataset_from_config(config: DictConfig, metadata_path:str, num_features = config.nn_params.num_features # get vocab - vocab_name = {'remi':'LangTokenVocab', 'cp':'MusicTokenVocabCP', 'nb':'MusicTokenVocabNB'} + vocab_name = {'remi':'LangTokenVocab', 'cp':'MusicTokenVocabCP', 'nb':'MusicTokenVocabNB', 'oct':'MusicTokenVocabOct'} selected_vocab_name = vocab_name[encoding_scheme] vocab = getattr(vocab_utils, selected_vocab_name)( @@ -477,7 +477,7 @@ class Evaluator: except KeyError: in_beat_resolution = 4 # Default resolution if dataset is not found - midi_decoder_dict = {'remi':'MidiDecoder4REMI', 'cp':'MidiDecoder4CP', 'nb':'MidiDecoder4NB'} + midi_decoder_dict = {'remi':'MidiDecoder4REMI', 'cp':'MidiDecoder4CP', 'nb':'MidiDecoder4NB', 'oct':'MidiDecoder4Octuple'} 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) @@ -499,7 +499,7 @@ class Evaluator: except KeyError: in_beat_resolution = 4 # Default resolution if dataset is not found - midi_decoder_dict = {'remi':'MidiDecoder4REMI', 'cp':'MidiDecoder4CP', 'nb':'MidiDecoder4NB'} + midi_decoder_dict = {'remi':'MidiDecoder4REMI', 'cp':'MidiDecoder4CP', 'nb':'MidiDecoder4NB', 'oct':'MidiDecoder4Octuple'} 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) @@ -509,7 +509,7 @@ class Evaluator: 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) + prompt = self.model.decoder._prepare_inference(self.model.decoder.net.start_token, 0, tuneidx, num_target_measures=num_target_measures) 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): @@ -520,7 +520,7 @@ class Evaluator: 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'} + midi_decoder_dict = {'remi':'MidiDecoder4REMI', 'cp':'MidiDecoder4CP', 'nb':'MidiDecoder4NB', 'oct':'MidiDecoder4Octuple'} 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) diff --git a/Amadeus/model_zoo.py b/Amadeus/model_zoo.py index 7b2788d..3c50b5c 100644 --- a/Amadeus/model_zoo.py +++ b/Amadeus/model_zoo.py @@ -103,7 +103,7 @@ 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, @@ -113,6 +113,17 @@ class AmadeusModelAutoregressiveWrapper(nn.Module): 'tempo': 6, 'instrument': 7} self.attribute2idx = {'type':0, 'beat':1, 'chord':2, 'tempo':3, 'instrument':4, 'pitch':5, 'duration':6, 'velocity':7} + # if using position attribute, change accordingly + if 'position' in self.net.vocab.feature_list: + self.attribute2idx_after = {'pitch': 0, + 'position': 1, + 'bar': 2, + 'velocity': 3, + 'duration': 4, + 'program': 5, + 'tempo': 6, + 'timesig': 7} + self.attribute2idx = {'pitch':0, 'position':1, 'bar':2, 'velocity':3, 'duration':4, 'program':5, 'tempo':6, 'timesig':7} def forward(self, input_seq:torch.Tensor, target:torch.Tensor,context=None): return self.net(input_seq, target, context=context) @@ -161,10 +172,12 @@ class AmadeusModelAutoregressiveWrapper(nn.Module): conditional_input_len = torch.where(measure_bool)[0][num_target_measures].item() # measure_bool = (condition[:,1] == 1) # measure tokens conditional_input_len = torch.where(measure_bool)[0][num_target_measures].item() - elif self.net.vocab.encoding_scheme == 'nb': + elif self.net.vocab.encoding_scheme == 'nb' or self.net.vocab.encoding_scheme == 'oct': measure_bool = (condition[:,0] == 2) | (condition[:,0] >= 5) # Empty measure or where new measure starts - conditional_input_len = torch.where(measure_bool)[0][num_target_measures].item() - + try: + conditional_input_len = torch.where(measure_bool)[0][num_target_measures].item() + except: + conditional_input_len = condition.shape[0] if conditional_input_len == 0: conditional_input_len = 50 @@ -262,7 +275,7 @@ class AmadeusModelAutoregressiveWrapper(nn.Module): # print(self.attribute2idx) for attr, idx in self.attribute2idx.items(): if attr not in attr_list: - condition_filtered[:, :, idx] = 126336 + condition_filtered[:, 1:, idx] = 126336 # rearange condition_filtered to match prediction order cache = LayerIntermediates() @@ -286,8 +299,9 @@ class AmadeusModelAutoregressiveWrapper(nn.Module): 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) + # print("condition_step shape:", condition_step) _, 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) + # print("sampled_token shape:", sampled_token) 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() diff --git a/Amadeus/sub_decoder_zoo.py b/Amadeus/sub_decoder_zoo.py index 9179211..db7ba31 100644 --- a/Amadeus/sub_decoder_zoo.py +++ b/Amadeus/sub_decoder_zoo.py @@ -713,7 +713,7 @@ class DiffusionDecoder(SubDecoderClass): dropout:float, sub_decoder_enricher_use:bool, MASK_IDX:int = 126336, - denoising_steps:int = 8, + denoising_steps:int = 6, eps:float = 1e-3, method:str = 'low-confidence', # or random or auto-regressive ): @@ -1029,7 +1029,338 @@ class DiffusionDecoder(SubDecoderClass): # 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()) + # 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) + 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 + 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, 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 ---- # + _, 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': 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) + +class DiffusionDecoder(SubDecoderClass): + def __init__( + self, + prediction_order:list, + vocab:LangTokenVocab, + sub_decoder_depth:int, + dim:int, + heads:int, + dropout:float, + sub_decoder_enricher_use:bool, + MASK_IDX:int = 126336, + denoising_steps:int = 6, + eps:float = 1e-3, + method:str = 'low-confidence', # or random or auto-regressive + ): + ''' + The power of Cross-attention and UniAudio style Self-attention lies in that using the output of the main decoder or hidden vec directly in the sub-decoder + As the output of the main decoder is the representation of the whole sequence, + it contains richer information which can even decode out sub-tokens in a parallel manner + So both architectures using the output of the main decoder in a direct way show better performance than the original self-attention sub-decoder + ''' + super().__init__(prediction_order, vocab, sub_decoder_depth, dim, heads, dropout, sub_decoder_enricher_use) + self.sub_decoder_enricher_use = sub_decoder_enricher_use + self.feature_order_in_output = {key: (idx-len(prediction_order)) for idx, key in enumerate(prediction_order)} + + self.pos_enc = nn.Embedding(len(self.prediction_order), dim) + nn.init.zeros_(self.pos_enc.weight) + + self.sub_decoder_BOS_emb = nn.Parameter(torch.zeros(dim), requires_grad=True) + self.diffusion_mask_emb = nn.Parameter(torch.empty(dim), requires_grad=True) # embedding of mask token,idx is 126336,which is not in vocab + nn.init.normal_(self.diffusion_mask_emb, mean=0.0, std=0.02) + self.MASK_idx = MASK_IDX + self.denoising_steps = denoising_steps + self.eps = eps + self.method = method + + self.input_norm = nn.LayerNorm(dim) + + self.feature_boost_layers = nn.Sequential(TransformerLayer(dim=dim, num_heads=heads, dropout=dropout)) + + if sub_decoder_enricher_use: + self.enricher_BOS_emb = nn.Parameter(torch.zeros(dim), requires_grad=True) + causal_mask = generate_SA_mask(len(prediction_order)) + causal_ca_mask = generate_none_causality_mask(len(prediction_order), len(prediction_order)).to(self.device) + self.register_buffer('causal_mask', causal_mask) + self.register_buffer('causal_ca_mask', causal_ca_mask) + + # get depth of the sub-decoder + if sub_decoder_depth > 1: + self.sub_decoder_layers = nn.Sequential(*[TransformerLayer(dim=dim, num_heads=heads, dropout=dropout) for _ in range(sub_decoder_depth)]) + else: + self.sub_decoder_layers = nn.Sequential(TransformerLayer(dim=dim, num_heads=heads, dropout=dropout)) + if sub_decoder_enricher_use: + self.feature_enricher_layers = nn.Sequential(FeatureEnricher(dim=dim, num_heads=heads, dropout=dropout)) + + + # simplified version of the forward process in diffusion model + def _forward_process(self, input_ids, eps=1e-3, mask_idx=None): + reshaped_input_ids = torch.reshape(input_ids, (-1, input_ids.shape[-1])) # B*T x num_sub_tokens + b, l = reshaped_input_ids.shape + t = torch.rand(b, device=input_ids.device) + p_mask = (1 - eps) * t + eps + p_mask = p_mask[:, None].repeat(1, l) + + masked_indices = torch.rand((b, l), device=input_ids.device) < p_mask + # 126336 is used for [MASK] token,attention that this token is not in the vocab + if mask_idx is not None: + noisy_batch = torch.where(masked_indices, mask_idx, reshaped_input_ids) + else: + noisy_batch = torch.where(masked_indices, 126336, reshaped_input_ids)# 126336 is used for [MASK] token in + return noisy_batch, masked_indices, p_mask + + + def _apply_window_on_hidden_vec(self, hidden_vec): + BOS_emb = self.enricher_BOS_emb.reshape(1,1,-1).repeat(hidden_vec.shape[0]*hidden_vec.shape[1], 1, 1) # (B*T) x 1 x d_model + # through our experiments, we found that the size of the window doesn't affect the performance of the model much + window_size = 1 + zero_vec = torch.zeros((hidden_vec.shape[0], window_size-1, hidden_vec.shape[2])).to(self.device) # B x (window_size-1) x d_model + cat_hidden_vec = torch.cat([zero_vec, hidden_vec], dim=1) # B x (window_size-1+T) x d_model + new_hidden_vec = cat_hidden_vec.unfold(1, window_size, 1).transpose(2, 3) # B x T x window_size x d_model + new_hidden_vec = new_hidden_vec.reshape((hidden_vec.shape[0]*hidden_vec.shape[1], window_size, -1)) # (B*T) x window_size x d_model + new_hidden_vec = torch.cat([BOS_emb, new_hidden_vec], dim=1) # (B*T) x (window_size+1) x d_model + return new_hidden_vec + + def _apply_pos_enc(self, tgt): + pos = torch.arange(tgt.shape[1]).to(tgt.device) # num_sub_tokens + pos = pos.unsqueeze(0).repeat(tgt.shape[0], 1) # (B*T) x num_sub_tokens + tgt_pos = tgt + self.pos_enc(pos.long()) # (B*T) x num_sub_tokens x d_model + return tgt_pos + + def _prepare_token_embedding_for_teacher_forcing(self, memory_list, target): + for _, feature in enumerate(self.prediction_order[:-1]): + feature_idx = self.vocab.feature_list.index(feature) + feature_emb = self.emb_layer.get_emb_by_key(feature, target[..., feature_idx]) # B x T x emb_size + feature_emb_reshape = feature_emb.reshape((feature_emb.shape[0]*feature_emb.shape[1], 1, -1)) # (B*T) x 1 x emb_size + memory_list.append(feature_emb_reshape) + memory_tensor = torch.cat(memory_list, dim=1) # (B*T) x (BOS + num_sub_tokens-1) x d_model + return memory_tensor + + # return a tensor + def _get_noisy_tensor(self, target_shape): + new_target = torch.zeros(target_shape).to(self.device) + # fill all the elements in the tensor with the embedding of the mask token + new_target[:, :, :] = self.diffusion_mask_emb + return new_target + + # prepare the embedding of the target, + def _prepare_embedding(self, memory_list, target): + for _, feature in enumerate(self.prediction_order): + feature_idx = self.vocab.feature_list.index(feature) + feature_emb = self.emb_layer.get_emb_by_key(feature, target[..., feature_idx]) # B x T x emb_size + feature_emb_reshape = feature_emb.reshape((feature_emb.shape[0]*feature_emb.shape[1], 1, -1)) # (B*T) x 1 x emb_size + memory_list.append(feature_emb_reshape) + memory_tensor = torch.cat(memory_list, dim=1) # (B*T) x (BOS + num_sub_tokens) x d_model + return memory_tensor + + + def _prepare_memory_list(self, hidden_vec, target=None, add_BOS=True): + memory_list = [] # used for key and value in cross attention + BOS_emb = self.sub_decoder_BOS_emb.reshape(1,1,-1).repeat(hidden_vec.shape[0]*hidden_vec.shape[1], 1, 1) # (B*T) x 1 x d_model + if add_BOS is true: + if target is not None: # training + memory_list.append(BOS_emb) + else: # inference + memory_list.append(BOS_emb[-1:, :, :]) + else: + pass + return memory_list + + def _get_num_transfer_tokens(self, mask_index, steps): + ''' + In the reverse process, the interval [0, 1] is uniformly discretized into steps intervals. + Furthermore, because LLaDA employs a linear noise schedule (as defined in Eq. (8)), + the expected number of tokens transitioned at each step should be consistent. + + 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) + base = mask_num // steps + remainder = mask_num % steps + + num_transfer_tokens = torch.zeros(mask_num.size(0), steps, device=mask_index.device, dtype=torch.int64) + base + + for i in range(mask_num.size(0)): + num_transfer_tokens[i, :remainder[i]] += 1 + + return num_transfer_tokens + + def sample_from_logits(self, attn_output, hidden_vec, sampling_method=None, threshold=None, temperature=None, force_decode=False,step=None): + sampled_token_dict = {} + logits_dict = {} + candidate_token_embeddings = {} + candidate_token_probs = {} + b,t,d = hidden_vec.shape # B x T x d_model + # print("*"*8) + logits_list = [] + 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_list.append(logit) + for idx, feature in enumerate(self.prediction_order): + logit = logits_list[idx] # B x T x vocab_siz + sampled_token, prob = sample_with_prob(logit, sampling_method=sampling_method, threshold=threshold, temperature=temperature) + if step==0 and force_decode: + if feature == 'velocity': + sampled_token = torch.tensor([2]).to(logit.device) + prob = torch.tensor([1.0]).to(logit.device) + else: + prob = torch.tensor([0.0]).to(logit.device) + # print(feature, sampled_token, prob) + sampled_token_dict[feature] = sampled_token + logits_dict[feature] = logit + candidate_token_probs[feature] = prob + 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, -1)) # (B*T) x num_sub_tokens x vocab_size + stacked_token_embeddings = torch.stack(list(candidate_token_embeddings.values()), dim=0).reshape((b*t, -1, d)) # (B*T) x num_sub_tokens x d_model + # print("sampled_token_dict", sampled_token_dict) + return sampled_token_dict, logits_dict, candidate_token_probs, stacked_logits_probs, stacked_token_embeddings + + 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 + bos_hidden_vec = input_dict['bos_token_hidden'] # B x 1 x d_model, used for the first token in the sub-decoder + + # 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 + + # 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() + # add attribute control here + stored_logits_dict = {} + 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): + token = condition_step[i][j] + if condition_step[i][j] != self.MASK_idx: + + # 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)}") + + num_transfer_tokens = self._get_num_transfer_tokens(masked_history, self.denoising_steps) + # denoising c + # with torch.profiler.profile( + # activities=[ + # torch.profiler.ProfilerActivity.CPU, + # torch.profiler.ProfilerActivity.CUDA], + # record_shapes=True, + # profile_memory=True, + # with_stack=True + # ) 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 + 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 + 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) diff --git a/Amadeus/symbolic_encoding/augmentor.py b/Amadeus/symbolic_encoding/augmentor.py index 40c1839..5ad5407 100644 --- a/Amadeus/symbolic_encoding/augmentor.py +++ b/Amadeus/symbolic_encoding/augmentor.py @@ -22,6 +22,8 @@ class Augmentor: self.chord_idx = self.feature_list.index('chord') def _get_shift(self, segment): + if self.encoding_scheme == 'oct': + return 0 # the pitch vocab has ignore token in 0 index if self.encoding_scheme == 'cp' or self.encoding_scheme == 'nb': pitch_mask = segment != 0 diff --git a/Amadeus/symbolic_encoding/compile_utils.py b/Amadeus/symbolic_encoding/compile_utils.py index 40eae76..e856e82 100644 --- a/Amadeus/symbolic_encoding/compile_utils.py +++ b/Amadeus/symbolic_encoding/compile_utils.py @@ -73,7 +73,7 @@ class VanillaTransformer_compiler(): for i in range(len(self.data_list)): tune_in_idx, tune_name = self.data_list[i] tune_in_idx = torch.LongTensor(tune_in_idx) - if self.encoding_scheme == 'remi' or self.encoding_scheme == 'cp': + if self.encoding_scheme == 'remi' or self.encoding_scheme == 'cp' or self.encoding_scheme == 'oct': eos_token = torch.LongTensor(self.eos_token) else: eos_token = torch.LongTensor(self.eos_token) @@ -148,7 +148,7 @@ class VanillaTransformer_compiler(): for i in range(len(self.data_list)): tune_in_idx, tune_name = self.data_list[i] tune_in_idx = torch.LongTensor(tune_in_idx) - if self.encoding_scheme == 'remi' or self.encoding_scheme == 'cp': + if self.encoding_scheme == 'remi' or self.encoding_scheme == 'cp' or self.encoding_scheme == 'oct': eos_token = torch.LongTensor(self.eos_token) else: eos_token = torch.LongTensor(self.eos_token) diff --git a/Amadeus/symbolic_encoding/data_utils.py b/Amadeus/symbolic_encoding/data_utils.py index 26231f2..af0ebca 100644 --- a/Amadeus/symbolic_encoding/data_utils.py +++ b/Amadeus/symbolic_encoding/data_utils.py @@ -95,11 +95,6 @@ class TuneCompiler(Dataset): print(f"Error encoding caption for tune {tune_name}: {e}") encoded_caption = self.t5_tokenizer("No caption available", return_tensors='pt', padding='max_length', truncation=True, max_length=128) return segment, tensor_mask, tune_name, encoded_caption - if self.data_type == 'train': - augmented_segment = self.augmentor(segment) - return augmented_segment, tensor_mask, tune_name, encoded_caption - else: - return segment, tensor_mask, tune_name, encoded_caption def get_segments_with_tune_idx(self, tune_name, seg_order): ''' @@ -135,6 +130,7 @@ class IterTuneCompiler(IterableDataset): self.data_type = data_type self.augmentor = augmentor self.eos_token = vocab.eos_token + self.vocab = vocab self.compile_function = VanillaTransformer_compiler( data_list=self.data_list, augmentor=self.augmentor, @@ -157,7 +153,7 @@ class IterTuneCompiler(IterableDataset): encoded_caption = self.t5_tokenizer(tune_name, return_tensors='pt', padding='max_length', truncation=True, max_length=128) except Exception as e: encoded_caption = self.t5_tokenizer("No caption available", return_tensors='pt', padding='max_length', truncation=True, max_length=128) - if self.data_type == 'train': + if self.data_type == 'train' and self.vocab.encoding_scheme != 'oct': segment = self.augmentor(segment) # use input_ids replace tune_name tune_name = encoded_caption['input_ids'][0] # Use the input_ids from the encoded caption diff --git a/Amadeus/symbolic_encoding/decoding_utils.py b/Amadeus/symbolic_encoding/decoding_utils.py index 99312a3..82efc32 100644 --- a/Amadeus/symbolic_encoding/decoding_utils.py +++ b/Amadeus/symbolic_encoding/decoding_utils.py @@ -1,13 +1,17 @@ +import re import os, sys from pathlib import Path import matplotlib.pyplot as plt from collections import defaultdict +import torch from music21 import converter import muspy import miditoolkit from miditoolkit.midi.containers import Marker, Instrument, TempoChange, Note, TimeSignature +from symusic import Score +from miditok import Octuple, TokenizerConfig from .midi2audio import FluidSynth from data_representation.constants import PROGRAM_INSTRUMENT_MAP @@ -400,5 +404,62 @@ class MidiDecoder4NB(MidiDecoder4REMI): music_path = os.path.join(music_path, output_path.split('/')[-1].replace('.mid', '.wav')) midi_obj.dump(output_path) # save_pianoroll_image_from_midi(output_path, output_path.replace('.mid', '.png')) - save_wav_from_midi_fluidsynth(output_path, music_path, gain=self.gain) + # save_wav_from_midi_fluidsynth(output_path, music_path, gain=self.gain) return midi_obj + +class MidiDecoder4Octuple(MidiDecoder4REMI): + def __init__(self, vocab, in_beat_resolution, dataset_name): + super().__init__(vocab, in_beat_resolution, dataset_name) + + + + def remove_rows_with_exact_0_1_2_3(self, t: torch.Tensor) -> torch.Tensor: + """ + 输入: + t: torch.Tensor, 形状 (1, N, M) + 功能: + 删除包含独立元素 0, 1, 2, 3 的子tensor行 + 返回: + torch.Tensor, 同样保持 batch 维度 (1, N_filtered, M) + """ + if t.dim() != 3: + raise ValueError("输入 tensor 必须是三维 (batch, seq_len, feature)") + + # 构造一个 mask,True 表示该行不包含 0,1,2,3 + exclude_vals = torch.tensor([0, 1, 2, 3], device=t.device) + + # 判断每一行是否含有这些值 + mask = ~((t[0][..., None] == exclude_vals).any(dim=(1, 2))) + + # 过滤行并保留 batch 维 + filtered_tensor = t[0][mask].unsqueeze(0) + + return filtered_tensor + + def __call__(self, generated_output, output_path=None): + config = TokenizerConfig( + use_time_signatures=True, + use_tempos=True, + use_velocities=True, + use_programs=True, + remove_duplicated_notes=True, + delete_equal_successive_tempo_changes=True, + ) + config.additional_params["max_bar_embedding"] = 512 + tokenizer = Octuple(config) + + output_path = Path(output_path) + output_path.parent.mkdir(parents=True, exist_ok=True) + + # generated_output = generated_output[:, 1:generated_output.shape[1]-1, :] # remove sos token + generated_output = self.remove_rows_with_exact_0_1_2_3(generated_output) + print(output_path) + try: + tok_seq = tokenizer.decode(generated_output.squeeze(0).tolist()) + tok_seq.dump_midi(output_path) + except Exception as e: + print(generated_output) + print(f" × 生成 MIDI 文件时出错:{output_path} -> {e}") + tok_seq = None + + return tok_seq diff --git a/Amadeus/symbolic_yamls/config-accelerate.yaml b/Amadeus/symbolic_yamls/config-accelerate.yaml index d472105..7fd217f 100644 --- a/Amadeus/symbolic_yamls/config-accelerate.yaml +++ b/Amadeus/symbolic_yamls/config-accelerate.yaml @@ -1,8 +1,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: oct8_embSum_diff_t2m_150M_pretrainingv2 + # - 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: msmidi # Pop1k7, Pop909, SOD, LakhClean,PretrainingDataset FinetuneDataset +dataset: Melody # Pop1k7, Pop909, SOD, LakhClean,PretrainingDataset FinetuneDataset captions_path: dataset/midicaps/train_set.json # dataset: SymphonyNet_Dataset # Pop1k7, Pop909, SOD, LakhClean @@ -31,7 +31,7 @@ tau: 0.5 train_params: device: cuda - batch_size: 5 + batch_size: 10 grad_clip: 1.0 num_iter: 300000 # total number of iterations num_cycles_for_inference: 10 # number of cycles for inference, iterations_per_validation_cycle * num_cycles_for_inference @@ -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.0003 + initial_lr: 0.00001 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 diff --git a/Amadeus/symbolic_yamls/nn_params/oct8_embSum_diff_t2m_150M_pretrainingv2.yaml b/Amadeus/symbolic_yamls/nn_params/oct8_embSum_diff_t2m_150M_pretrainingv2.yaml new file mode 100644 index 0000000..0e16086 --- /dev/null +++ b/Amadeus/symbolic_yamls/nn_params/oct8_embSum_diff_t2m_150M_pretrainingv2.yaml @@ -0,0 +1,19 @@ +encoding_scheme: oct +num_features: 8 +vocab_name: MusicTokenVocabOct +model_name: AmadeusModel +input_embedder_name: SummationEmbedder +main_decoder_name: XtransformerNewPretrainingDecoder +sub_decoder_name: DiffusionDecoder +model_dropout: 0.2 +input_embedder: + num_layer: 1 + num_head: 8 +main_decoder: + dim_model: 768 + num_layer: 16 + num_head: 12 +sub_decoder: + decout_window_size: 1 # 1 means no previous decoding output added + num_layer: 1 + feature_enricher_use: False \ No newline at end of file diff --git a/Amadeus/train_utils.py b/Amadeus/train_utils.py index a98ce59..57fe28b 100644 --- a/Amadeus/train_utils.py +++ b/Amadeus/train_utils.py @@ -29,8 +29,12 @@ def adjust_prediction_order(encoding_scheme, num_features, target_feature, nn_pa 7: ["type", "beat", "chord", "tempo", "pitch", "duration", "velocity"], 8: ["type", "beat", "chord", "tempo", "instrument", "pitch", "duration", "velocity"] } - - if encoding_scheme == 'remi': + oct_prediction_order = { + 7: ["pitch", "position", "bar", "duration", "program", "tempo", "timesig"], + 8: ["pitch", "position", "bar", "velocity", "duration", "program", "tempo", "timesig"]} + if encoding_scheme == 'oct': + prediction_order = oct_prediction_order[num_features] + elif encoding_scheme == 'remi': prediction_order = feature_prediction_order_dict[num_features] elif encoding_scheme == 'cp': if nn_params.get("partial_sequential_prediction", False): @@ -239,11 +243,11 @@ class DiffusionLoss4CompoundToken(): training_loss = self.get_nll_loss(logits_dict[key], shifted_tgt[..., idx], mask, mask_indices[..., idx], p_mask[..., idx]) train_loss_list.append(training_loss) if valid: - if key == 'type': + if key == 'type' or key == 'timesig': log_normal_loss = self.get_nll_loss_for_logging(logits_dict[key], shifted_tgt[..., idx], mask, ignore_token=None, conti_token=None, mask_indices=mask_indices[..., idx], p_mask=p_mask[..., idx]) - elif key == 'beat': + elif key == 'beat' or key == 'position' or key == 'bar': log_normal_loss = self.get_nll_loss_for_logging(logits_dict[key], shifted_tgt[..., idx], mask, ignore_token=0, conti_token=9999, mask_indices=mask_indices[..., idx], p_mask=p_mask[..., idx]) - elif key == 'chord' or key == 'tempo' or key == 'instrument': + elif key == 'chord' or key == 'tempo' or key == 'instrument' or key == 'program': log_normal_loss = self.get_nll_loss_for_logging(logits_dict[key], shifted_tgt[..., idx], mask, ignore_token=0, conti_token=9999, mask_indices=mask_indices[..., idx], p_mask=p_mask[..., idx]) else: log_normal_loss = self.get_nll_loss_for_logging(logits_dict[key], shifted_tgt[..., idx], mask, ignore_token=0, conti_token=None, mask_indices=mask_indices[..., idx], p_mask=p_mask[..., idx]) diff --git a/Amadeus/trainer_accelerate.py b/Amadeus/trainer_accelerate.py index 2e4224a..94054cf 100644 --- a/Amadeus/trainer_accelerate.py +++ b/Amadeus/trainer_accelerate.py @@ -74,7 +74,7 @@ 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="wandb/run-20251016_180043-70ihsi93/files/checkpoints/iter80999_loss0.0300.pt", # Path to a pre-trained model checkpoint (optional) + model_checkpoint="wandb/run-20251025_104202-kd5cf5b3/files/checkpoints/iter42612_loss-8.9870.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 @@ -104,7 +104,6 @@ class LanguageModelTrainer: checkpoint = torch.load(model_checkpoint, map_location='cpu') # print state dict keys print("Loading model checkpoint from", model_checkpoint) - print("Checkpoint keys:", checkpoint['model'].keys()) if isinstance(self.model, DDP): self.model.module.load_state_dict(checkpoint['model'], strict=False) else: @@ -902,9 +901,9 @@ class LanguageModelTrainer4CompoundToken(LanguageModelTrainer): correct_guess_by_feature = defaultdict(int) num_tokens_by_feature = defaultdict(int) for idx, key in enumerate(self.vocab.feature_list): - if key == 'type': + if key == 'type' or key == 'timesig' : num_correct_tokens, num_valid_tokens = self._get_num_valid_and_correct_tokens(probs_dict[key], target[..., idx], mask, ignore_token=None, conti_token=None) - elif key == 'chord' or key == 'tempo' or key == 'instrument': + elif key == 'chord' or key == 'tempo' or key == 'instrument' or key == 'program': num_correct_tokens, num_valid_tokens = self._get_num_valid_and_correct_tokens(probs_dict[key], target[..., idx], mask, ignore_token=0, conti_token=9999) elif key == 'beat': # NB's beat vocab has Ignore and CONTI token diff --git a/data_representation/octuple2tuneinidx.py b/data_representation/octuple2tuneinidx.py new file mode 100644 index 0000000..3b7f282 --- /dev/null +++ b/data_representation/octuple2tuneinidx.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +MIDI 预处理脚本(并行版) +功能: +1. 使用 miditok 的 Octuple 分词器。 +2. 限制 MIDI 文件时长在 8~2000 秒。 +3. 缺失 tempo 时默认 120 BPM;缺失 time signature 时默认 4/4。 +4. 保存 vocab.json。 +5. 使用多线程遍历目录下所有 MIDI 文件分词,每个文件单独保存为 {filename}.npz。 +""" + +import os +import glob +import struct +import numpy as np +from multiprocessing import RLock +from concurrent.futures import ProcessPoolExecutor, as_completed + +from tqdm import tqdm +from symusic import Score +from miditok import Octuple, TokenizerConfig + + +lock = RLock() + +def convert_event_dicts(dict_list): + """ + 将 event 词表列表按顺序转换为结构化输出 + 输入: list[dict] + 每个 dict 对应一个类别,按固定顺序排列: + 0: Pitch/PitchDrum + 1: Position + 2: Bar + (+ Optional) Velocity + (+ Optional) Duration + (+ Optional) Program + (+ Optional) Tempo + (+ Optional) TimeSignature + 输出示例: + { + "pitch": {"0": 0, "1": "Pitch_60", ...}, + "position": {...}, + ... + } + """ + keys_order = [ + "pitch", "position", "bar", + "velocity", "duration", "program", + "tempo", "timesig" + ] + + result = {} + for i, d in enumerate(dict_list): + if i >= len(keys_order): + break # 超出定义范围的忽略 + category = keys_order[i] + result[category] = {str(v): k for k, v in d.items()} + + return result + +def process_single_midi(midi_path: str, tokenizer: Octuple, output_dir: str): + try: + score = Score(midi_path, ttype="tick") + + if(not (8 <= (duration := score.to('second').end()) <= 2000)): + with lock: + print(f" × 时长不符合要求:{midi_path} -> {duration}s") + return + + # 分词 + tok_seq = tokenizer(score) + token_ids = tok_seq.ids + # add sos token at the beginning + vocab = tokenizer.vocab + sos_token = [vocab[0]['BOS_None']] + [0] * (len(vocab) - 1) + token_ids.insert(0, sos_token) + token_ids.sort(key=lambda x: (x[2], x[1])) # pos in 1, bar in 2 + + # 保存单个 npz 文件 + filename = os.path.splitext(os.path.basename(midi_path))[0] + save_path = os.path.join(output_dir, f"{filename}.npz") + np.savez_compressed(save_path, np.array(token_ids)) + except Exception as e: + with lock: + print(f" × 处理文件时出错:{midi_path} -> {e}") + + +def preprocess_midi_directory(midi_dir: str, output_dir: str = "dataset_npz", num_threads: int = int(os.cpu_count() // 2)): + # === 1. 初始化分词器并保存词表 === + print("初始化分词器 Octuple...") + config = TokenizerConfig( + use_time_signatures=True, + use_tempos=True, + use_velocities=True, + use_programs=True, + remove_duplicated_notes=True, + delete_equal_successive_tempo_changes=True, + ) + config.additional_params["max_bar_embedding"] = 512 + tokenizer = Octuple(config) + vocab = tokenizer.vocab + vocab_structured = convert_event_dicts(vocab) + with open( "vocab/oct_vocab.json", "w", encoding="utf-8") as f: + import json + json.dump(vocab_structured, f, ensure_ascii=False, indent=4) + # === 2. 创建输出目录 === + os.makedirs(output_dir, exist_ok=True) + + # === 3. 收集 MIDI 文件 === + midi_paths = glob.glob(os.path.join(midi_dir, "**", "*.mid"), recursive=True) + \ + glob.glob(os.path.join(midi_dir, "**", "*.midi"), recursive=True) + midi_paths = list(midi_paths) + print(f"共发现 {len(midi_paths)} 个 MIDI 文件,使用 {num_threads} 个线程处理。\n") + + # === 4. 并行处理 === + results = [] + with ProcessPoolExecutor(max_workers=num_threads) as executor: + futures = {executor.submit(process_single_midi, path, tokenizer, output_dir): path for path in midi_paths} + + for future in tqdm(as_completed(futures), total=len(futures)): + res = future.result() + if res: + results.append(res) + + # === 5. 汇总结果 === + print(f"\n处理完成:成功生成 {len(results)} 个 .npz 文件,保存在 {output_dir}/ 中。") + + +if __name__ == "__main__": + midi_directory = "dataset/Melody" # 修改为你的 MIDI 文件目录 + dataset_name = midi_directory.split("/")[-1] + tuneidx_prefix = f"dataset/represented_data/tuneidx/tuneidx_{dataset_name}/oct8" + output_dir = tuneidx_prefix + preprocess_midi_directory(midi_directory, output_dir) \ No newline at end of file diff --git a/data_representation/test.py b/data_representation/test.py new file mode 100644 index 0000000..08dafa7 --- /dev/null +++ b/data_representation/test.py @@ -0,0 +1,14 @@ +import numpy as np + +# 读取 npz 文件 +data = np.load("dataset/represented_data/tuneidx/tuneidx_Melody/octuple8/AIDemo-recuKqEwVxsfij.npz", allow_pickle=True) + +# 查看保存的键 +print(data.files) +# 输出:['filename', 'sequence'] + +# 访问数据 +sequence = data["arr_0"] + +print("token 序列长度:", len(sequence)) +print("前 20 个 token:", sequence[:20]) \ No newline at end of file diff --git a/data_representation/vocab_utils.py b/data_representation/vocab_utils.py index 999385b..70638e9 100644 --- a/data_representation/vocab_utils.py +++ b/data_representation/vocab_utils.py @@ -1,5 +1,6 @@ import pickle from pathlib import Path +from re import L from typing import Union from multiprocessing import Pool, cpu_count from collections import defaultdict @@ -58,8 +59,8 @@ class LangTokenVocab: if in_vocab_file_path is not None: with open(in_vocab_file_path, 'r') as f: idx2event_temp = json.load(f) - if self.encoding_scheme == 'cp' or self.encoding_scheme == 'nb': - for key in idx2event_temp.keys(): + if self.encoding_scheme == 'cp' or self.encoding_scheme == 'nb' or self.encoding_scheme == 'oct': + for key in idx2event_temp.keys(): idx2event_temp[key] = {int(idx):tok for idx, tok in idx2event_temp[key].items()} elif self.encoding_scheme == 'remi': idx2event_temp = {int(idx):tok for idx, tok in idx2event_temp.items()} @@ -71,13 +72,18 @@ class LangTokenVocab: # Extracts features depending on the number of features chosen (4, 5, 7, 8). def _get_features(self): - feature_args = { - 4: ["type", "beat", "pitch", "duration"], - 5: ["type", "beat", "instrument", "pitch", "duration"], - 7: ["type", "beat", "chord", "tempo", "pitch", "duration", "velocity"], - 8: ["type", "beat", "chord", "tempo", "instrument", "pitch", "duration", "velocity"]} - self.feature_list = feature_args[self.num_features] - + if self.encoding_scheme != 'oct': + feature_args = { + 4: ["type", "beat", "pitch", "duration"], + 5: ["type", "beat", "instrument", "pitch", "duration"], + 7: ["type", "beat", "chord", "tempo", "pitch", "duration", "velocity"], + 8: ["type", "beat", "chord", "tempo", "instrument", "pitch", "duration", "velocity"]} + self.feature_list = feature_args[self.num_features] + else: + feature_args = { + 7: ["pitch", "position", "bar", "duration", "program", "tempo", "timesig"], + 8: ["pitch", "position", "bar", "velocity", "duration", "program", "tempo", "timesig"]} + self.feature_list = feature_args[self.num_features] # Saves the current vocabulary to a specified JSON path. def save_vocab(self, json_path): with open(json_path, 'w') as f: @@ -93,13 +99,17 @@ class LangTokenVocab: self.sos_token = [self.event2idx['SOS_None']] self.eos_token = [[self.event2idx['EOS_None']]] else: - self.sos_token = [[self.event2idx['type']['SOS']] + [0] * (self.num_features - 1)] - self.eos_token = [[self.event2idx['type']['EOS']] + [0] * (self.num_features - 1)] - + if self.encoding_scheme == 'cp' or self.encoding_scheme == 'nb': + self.sos_token = [[self.event2idx['type']['SOS']] + [0] * (self.num_features - 1)] + self.eos_token = [[self.event2idx['type']['EOS']] + [0] * (self.num_features - 1)] + else: # oct + self.sos_token = [[self.event2idx['pitch']['BOS_None']] + [0] * (self.num_features - 1)] + self.eos_token = [[self.event2idx['pitch']['EOS_None']] + [0] * (self.num_features - 1)] + # Generates vocabularies by either loading from a file or creating them based on the event data. def _get_vocab(self, event_data, unique_vocabs=None): # make new vocab from given event_data - if event_data is not None: + if event_data is not None and self.encoding_scheme != 'oct': unique_char_list = list(set([f'{event["name"]}_{event["value"]}' for tune_path in event_data for event in pickle.load(open(tune_path, 'rb'))])) unique_vocabs = sorted(unique_char_list) unique_vocabs.remove('SOS_None') @@ -119,6 +129,7 @@ class LangTokenVocab: # load premade vocab else: idx2event = unique_vocabs + print(idx2event) event2idx = {tok : int(idx) for idx, tok in unique_vocabs.items()} return idx2event, event2idx @@ -392,4 +403,47 @@ class MusicTokenVocabNB(MusicTokenVocabCP): unique_vocabs.insert(3, 'SSS') unique_vocabs.insert(4, 'SSN') unique_vocabs.insert(5, 'SNN') - return unique_vocabs \ No newline at end of file + return unique_vocabs + + +class MusicTokenVocabOct(LangTokenVocab): + def __init__( + self, + in_vocab_file_path:Union[Path, None], + event_data: list, + encoding_scheme: str, + num_features: int + ): + super().__init__(in_vocab_file_path, event_data, encoding_scheme, num_features) + + def _get_vocab(self, event_data, unique_vocabs=None): + if event_data is not None: + # Create vocab mappings (event2idx, idx2event) from the provided event data + print('start to get unique vocab') + event2idx = {} + idx2event = {} + unique_vocabs = defaultdict(set) + # Use multiprocessing to extract unique vocabularies for each event + with Pool(16) as p: + results = p.starmap(self._mp_get_unique_vocab, tqdm([(tune, self.feature_list) for tune in event_data])) + # Combine results from different processes + for result in results: + for key in self.feature_list: + unique_vocabs[key].update(result[key]) + # Process each feature type + for key in self.feature_list: + unique_vocabs[key] = sorted(unique_vocabs[key], key=lambda x: (not isinstance(x, int), int(x.split('_')[-1] if isinstance(x, str) else x))) + # Create event2idx and idx2event mappings for each feature + event2idx[key] = {tok: int(idx) for idx, tok in enumerate(unique_vocabs[key])} + idx2event[key] = {int(idx): tok for idx, tok in enumerate(unique_vocabs[key])} + return idx2event, event2idx + else: + # If no event data, simply map unique vocab to indexes + event2idx = {} + for key in self.feature_list: + event2idx[key] = {tok: int(idx) for idx, tok in unique_vocabs[key].items()} + return unique_vocabs, event2idx + + def get_vocab_size(self): + # Return the size of the vocabulary for each feature + return {key: len(self.idx2event[key]) for key in self.feature_list} \ No newline at end of file diff --git a/generate-batch.py b/generate-batch.py index 607f547..e533482 100644 --- a/generate-batch.py +++ b/generate-batch.py @@ -33,7 +33,9 @@ def get_argument_parser(): parser.add_argument( "-attr_list", type=str, - default="beat,duration", + # default="beat,duration,,instrument,tempo", + default="pitch", + # default='bar,position,velocity,duration,program,tempo,timesig', help="attribute list for attribute-controlled generation", ) parser.add_argument( @@ -69,7 +71,7 @@ def get_argument_parser(): parser.add_argument( "-num_target_measure", type=int, - default=4, + default=128, help="number of target measures for conditioned generation", ) parser.add_argument( @@ -86,13 +88,13 @@ def get_argument_parser(): parser.add_argument( "-num_processes", type=int, - default=1, + default=4, help="number of processes to use", ) parser.add_argument( "-gpu_ids", type=str, - default="1,2,3,5", + default="0,1,2,3,5", help="comma-separated list of GPU IDs to use (e.g., '0,1,2,3')", ) parser.add_argument( @@ -203,6 +205,7 @@ def attr_conditioned_worker(process_idx, gpu_id, args): # end_idx = min(chunk_size, len(selected_data)) # data_slice = selected_data[start_idx:end_idx] data_slice = selected_data + print("data_slice length:", len(data_slice)) # Create output directory with process index base_path = Path('wandb') / args.wandb_exp_dir / \ diff --git a/len_tunes/IrishMan/len_oct8.png b/len_tunes/IrishMan/len_oct8.png new file mode 100644 index 0000000000000000000000000000000000000000..009e6859a50b0bd7c8a35308cfdd63353899a123 GIT binary patch literal 21567 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 literal 19427 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 diff --git a/len_tunes/Melody/len_oct8.png b/len_tunes/Melody/len_oct8.png new file mode 100644 index 0000000000000000000000000000000000000000..f83eb5cccdd4e863d0bd35e60a4774b9e6d54654 GIT binary patch literal 18941 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 literal 0 HcmV?d00001 diff --git a/len_tunes/msmidi/len_oct8.png b/len_tunes/msmidi/len_oct8.png new file mode 100644 index 0000000000000000000000000000000000000000..a0a4bdbc056c19db2dc5197f570887e4c31fa269 GIT binary patch literal 20366 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 literal 0 HcmV?d00001 diff --git a/midi_sim.py b/midi_sim.py index 44c5ba5..7ae08c2 100644 --- a/midi_sim.py +++ b/midi_sim.py @@ -129,6 +129,6 @@ def batch_compare(dir_a: str, dir_b: str, out_csv: str = "midi_similarity.csv", if __name__ == "__main__": - dir_a = "wandb/run-20251015_154556-f0pj3ys3/cond_4m_top_p_t0.99_temp1.25/process_2_batch_23" + dir_a = "wandb/run-20251027_161354-f9j1mwp2/uncond_min_p_t0.05_temp1.25_epochch8" 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/train_accelerate.py b/train_accelerate.py index 7279114..d286d9f 100644 --- a/train_accelerate.py +++ b/train_accelerate.py @@ -8,9 +8,6 @@ import torch import torch.multiprocessing as mp from torch.distributed import init_process_group, destroy_process_group -from accelerate import Accelerator -from accelerate.utils import set_seed - import wandb import hydra from hydra.core.hydra_config import HydraConfig @@ -20,6 +17,8 @@ from omegaconf import DictConfig, OmegaConf from accelerate import Accelerator from accelerate.utils import set_seed +from miditok import Octuple, TokenizerConfig + from Amadeus.symbolic_encoding import data_utils, decoding_utils from Amadeus.symbolic_encoding.data_utils import get_emb_total_size from Amadeus import model_zoo, trainer_accelerate as trainer @@ -99,7 +98,7 @@ def preapre_sybmolic(config: DictConfig, save_dir: str, rank: int) -> trainer.La out_vocab_path = Path(save_dir) / f'vocab_{dataset_name}_{encoding_scheme}{num_features}.json' # get vocab - vocab_name = {'remi':'LangTokenVocab', 'cp':'MusicTokenVocabCP', 'nb':'MusicTokenVocabNB'} + vocab_name = {'remi':'LangTokenVocab', 'cp':'MusicTokenVocabCP', 'nb':'MusicTokenVocabNB', 'oct':'MusicTokenVocabOct'} selected_vocab_name = vocab_name[encoding_scheme] vocab = getattr(vocab_utils, selected_vocab_name)( @@ -159,7 +158,7 @@ def preapre_sybmolic(config: DictConfig, save_dir: str, rank: int) -> trainer.La focal_gamma = config.train_params.focal_gamma if encoding_scheme == 'remi': loss_fn = NLLLoss4REMI(focal_alpha=focal_alpha, focal_gamma=focal_gamma) - elif encoding_scheme in ['cp', 'nb']: + elif encoding_scheme in ['cp', 'nb', 'oct']: if config.use_diff is False: loss_fn = NLLLoss4CompoundToken(feature_list=symbolic_dataset.vocab.feature_list, focal_alpha=focal_alpha, focal_gamma=focal_gamma) else: @@ -181,11 +180,11 @@ def preapre_sybmolic(config: DictConfig, save_dir: str, rank: int) -> trainer.La in_beat_resolution = in_beat_resolution_dict[dataset_name] except KeyError: in_beat_resolution = 4 - midi_decoder_dict = {'remi':'MidiDecoder4REMI', 'cp':'MidiDecoder4CP', 'nb':'MidiDecoder4NB'} + midi_decoder_dict = {'remi':'MidiDecoder4REMI', 'cp':'MidiDecoder4CP', 'nb':'MidiDecoder4NB', 'oct':'MidiDecoder4Octuple'} midi_decoder = getattr(decoding_utils, midi_decoder_dict[encoding_scheme])(vocab=symbolic_dataset.vocab, in_beat_resolution=in_beat_resolution, dataset_name=dataset_name) # Select trainer class based on encoding scheme - trainer_option_dict = {'remi': 'LanguageModelTrainer4REMI', 'cp': 'LanguageModelTrainer4CompoundToken', 'nb':'LanguageModelTrainer4CompoundToken'} + trainer_option_dict = {'remi': 'LanguageModelTrainer4REMI', 'cp': 'LanguageModelTrainer4CompoundToken', 'nb':'LanguageModelTrainer4CompoundToken', 'oct':'LanguageModelTrainer4CompoundToken'} trainer_option = trainer_option_dict[encoding_scheme] sampling_method = None sampling_threshold = 0.99 diff --git a/vocab.json b/vocab.json new file mode 100644 index 0000000..1a6e6ec --- /dev/null +++ b/vocab.json @@ -0,0 +1,442 @@ +{ + "config": { + "pitch_range": [ + 21, + 109 + ], + "beat_res": { + "0_4": 8, + "4_12": 4 + }, + "num_velocities": 32, + "remove_duplicated_notes": false, + "encode_ids_split": "bar", + "special_tokens": [ + "PAD_None", + "BOS_None", + "EOS_None", + "MASK_None" + ], + "use_velocities": true, + "use_note_duration_programs": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + -1 + ], + "use_chords": false, + "use_rests": false, + "use_tempos": true, + "use_time_signatures": true, + "use_sustain_pedals": false, + "use_pitch_bends": false, + "use_programs": false, + "use_pitch_intervals": false, + "use_pitchdrum_tokens": true, + "default_note_duration": 0.5, + "programs": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + -1 + ], + "one_token_stream_for_programs": false, + "program_changes": false, + "beat_res_rest": { + "0_1": 8, + "1_2": 4, + "2_12": 2 + }, + "chord_maps": { + "min": [ + 0, + 3, + 7 + ], + "maj": [ + 0, + 4, + 7 + ], + "dim": [ + 0, + 3, + 6 + ], + "aug": [ + 0, + 4, + 8 + ], + "sus2": [ + 0, + 2, + 7 + ], + "sus4": [ + 0, + 5, + 7 + ], + "7dom": [ + 0, + 4, + 7, + 10 + ], + "7min": [ + 0, + 3, + 7, + 10 + ], + "7maj": [ + 0, + 4, + 7, + 11 + ], + "7halfdim": [ + 0, + 3, + 6, + 10 + ], + "7dim": [ + 0, + 3, + 6, + 9 + ], + "7aug": [ + 0, + 4, + 8, + 11 + ], + "9maj": [ + 0, + 4, + 7, + 10, + 14 + ], + "9min": [ + 0, + 4, + 7, + 10, + 13 + ] + }, + "chord_tokens_with_root_note": false, + "chord_unknown": null, + "num_tempos": 32, + "tempo_range": [ + 40, + 250 + ], + "log_tempos": false, + "delete_equal_successive_tempo_changes": true, + "time_signature_range": { + "8": [ + 3, + 12, + 6 + ], + "4": [ + 5, + 6, + 3, + 2, + 1, + 4 + ] + }, + "delete_equal_successive_time_sig_changes": false, + "sustain_pedal_duration": false, + "pitch_bend_range": [ + -8192, + 8191, + 32 + ], + "max_pitch_interval": 16, + "pitch_intervals_max_time_dist": 1, + "drums_pitch_range": [ + 27, + 88 + ], + "ac_polyphony_track": false, + "ac_polyphony_bar": false, + "ac_polyphony_min": 1, + "ac_polyphony_max": 6, + "ac_pitch_class_bar": false, + "ac_note_density_track": false, + "ac_note_density_track_min": 0, + "ac_note_density_track_max": 18, + "ac_note_density_bar": false, + "ac_note_density_bar_max": 18, + "ac_note_duration_bar": false, + "ac_note_duration_track": false, + "ac_repetition_track": false, + "ac_repetition_track_num_bins": 10, + "ac_repetition_track_num_consec_bars": 4, + "additional_params": { + "max_bar_embedding": 60 + } + }, + "tokenization": "Octuple", + "miditok_version": "3.0.6.post1", + "symusic_version": "0.5.8", + "hf_tokenizers_version": "0.21.2" +} \ No newline at end of file