GitHub Action commited on
Commit
9982dba
·
1 Parent(s): 2fc779c

deploy from github actions

Browse files
btc_toolkit/inference.py CHANGED
@@ -84,15 +84,17 @@ class BTCChordRecognizer:
84
  state = checkpoint["state_dict"]
85
  elif isinstance(checkpoint, dict) and "model_state_dict" in checkpoint:
86
  state = checkpoint["model_state_dict"]
 
 
87
  else:
88
  state = checkpoint
89
 
90
- # Carga flexible: ignora claves que no coinciden (permite compatibilidad entre versiones)
91
  missing, unexpected = self._model.load_state_dict(state, strict=False)
92
  if missing:
93
- logger.warning(f"[BTC] Parámetros faltantes al cargar pesos: {missing}")
94
  if unexpected:
95
- logger.debug(f"[BTC] Parámetros inesperados ignorados: {unexpected}")
96
 
97
  self._model.eval()
98
  logger.info("[BTC] Modelo BTC cargado y listo para inferencia.")
 
84
  state = checkpoint["state_dict"]
85
  elif isinstance(checkpoint, dict) and "model_state_dict" in checkpoint:
86
  state = checkpoint["model_state_dict"]
87
+ elif isinstance(checkpoint, dict) and "model" in checkpoint:
88
+ state = checkpoint["model"]
89
  else:
90
  state = checkpoint
91
 
92
+ # Carga flexible: ignora claves que no coinciden
93
  missing, unexpected = self._model.load_state_dict(state, strict=False)
94
  if missing:
95
+ logger.warning(f"[BTC] Parámetros faltantes al cargar pesos: {len(missing)} parámetros faltantes")
96
  if unexpected:
97
+ logger.debug(f"[BTC] Parámetros inesperados ignorados: {len(unexpected)} parámetros inesperados")
98
 
99
  self._model.eval()
100
  logger.info("[BTC] Modelo BTC cargado y listo para inferencia.")
btc_toolkit/model.py CHANGED
@@ -1,25 +1,19 @@
1
  """
2
- BTC Toolkit - Arquitectura del Modelo
3
- Implementación del Bidirectional Transformer for Chord Recognition
4
- basada en el paper ISMIR 2019: "A Bi-Directional Transformer for Musical Chord Recognition"
5
- (https://github.com/jayg996/BTC-ISMIR19)
6
-
7
- Vocabulario soportado: 25 clases (12 mayores + 12 menores + N/silencio)
8
  """
9
 
 
 
10
  import torch
11
  import torch.nn as nn
12
- import numpy as np
13
- import math
14
-
15
 
16
  # ==========================================
17
  # VOCABULARIO DE ACORDES (25 clases)
18
  # ==========================================
19
- # 0 = Silencio/Ninguno (N)
20
- # 1-12 = Mayores: C, C#, D, D#, E, F, F#, G, G#, A, A#, B
21
- # 13-24 = Menores: Cm, C#m, Dm, D#m, Em, Fm, F#m, Gm, G#m, Am, A#m, Bm
22
-
23
  CHORD_VOCAB = ['N'] + [
24
  'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B',
25
  'Cm', 'C#m', 'Dm', 'D#m', 'Em', 'Fm', 'F#m', 'Gm', 'G#m', 'Am', 'A#m', 'Bm'
@@ -27,165 +21,340 @@ CHORD_VOCAB = ['N'] + [
27
  NUM_CHORDS = len(CHORD_VOCAB) # 25
28
 
29
 
30
- # ==========================================
31
- # POSITIONAL ENCODING
32
- # ==========================================
33
-
34
- class PositionalEncoding(nn.Module):
35
- """Codificación posicional estándar con seno/coseno."""
36
-
37
- def __init__(self, d_model: int, dropout: float = 0.1, max_len: int = 5000):
38
- super().__init__()
39
- self.dropout = nn.Dropout(p=dropout)
40
-
41
- pe = torch.zeros(max_len, d_model)
42
- position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
43
- div_term = torch.exp(
44
- torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)
45
- )
46
-
47
- pe[:, 0::2] = torch.sin(position * div_term)
48
- pe[:, 1::2] = torch.cos(position * div_term)
49
- pe = pe.unsqueeze(0) # (1, max_len, d_model)
50
- self.register_buffer('pe', pe)
51
-
52
- def forward(self, x: torch.Tensor) -> torch.Tensor:
53
- # x: (batch, seq_len, d_model)
54
- x = x + self.pe[:, :x.size(1), :]
55
- return self.dropout(x)
56
-
57
-
58
- # ==========================================
59
- # BLOQUE CONVOLUCIONAL POSICIONAL
60
- # ==========================================
61
-
62
- class PositionWiseConvBlock(nn.Module):
63
- """
64
- Reemplaza el FFN estándar por convoluciones 1D para capturar
65
- patrones locales en la dimensión temporal.
66
- """
67
-
68
- def __init__(self, d_model: int, d_ff: int, kernel_size: int = 9, dropout: float = 0.1):
69
- super().__init__()
70
- padding = (kernel_size - 1) // 2
71
- self.conv1 = nn.Conv1d(d_model, d_ff, kernel_size=kernel_size, padding=padding)
72
- self.conv2 = nn.Conv1d(d_ff, d_model, kernel_size=kernel_size, padding=padding)
73
- self.norm = nn.LayerNorm(d_model)
74
- self.dropout = nn.Dropout(p=dropout)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  self.relu = nn.ReLU()
76
-
77
- def forward(self, x: torch.Tensor) -> torch.Tensor:
78
- # x: (batch, seq_len, d_model)
79
- residual = x
80
- x = x.transpose(1, 2) # (batch, d_model, seq_len)
81
- x = self.relu(self.conv1(x))
82
- x = self.conv2(x)
83
- x = x.transpose(1, 2) # (batch, seq_len, d_model)
84
- x = self.dropout(x)
85
- return self.norm(x + residual)
86
-
87
-
88
- # ==========================================
89
- # BLOQUE DE ATENCIÓN BIDIRECCIONAL
90
- # ==========================================
91
-
92
- class BidirectionalAttentionBlock(nn.Module):
93
- """
94
- Bloque completo de Transformer:
95
- Multi-Head Self-Attention -> Norm -> PositionWiseConvBlock -> Norm
96
- """
97
-
98
- def __init__(
99
- self,
100
- d_model: int,
101
- num_heads: int,
102
- d_ff: int,
103
- kernel_size: int = 9,
104
- dropout: float = 0.1
105
- ):
106
- super().__init__()
107
- self.self_attn = nn.MultiheadAttention(
108
- embed_dim=d_model,
109
- num_heads=num_heads,
110
- dropout=dropout,
111
- batch_first=True
112
- )
113
- self.norm1 = nn.LayerNorm(d_model)
114
- self.conv_block = PositionWiseConvBlock(d_model, d_ff, kernel_size, dropout)
115
- self.dropout = nn.Dropout(p=dropout)
116
-
117
- def forward(self, x: torch.Tensor, src_key_padding_mask=None) -> torch.Tensor:
118
- # Self-Attention (sin máscara causal = BIDIRECCIONAL)
119
- attn_out, _ = self.self_attn(x, x, x, key_padding_mask=src_key_padding_mask)
120
- x = self.norm1(x + self.dropout(attn_out))
121
-
122
- # Bloque convolucional
123
- x = self.conv_block(x)
124
  return x
125
 
126
 
127
- # ==========================================
128
- # MODELO PRINCIPAL: BTC
129
- # ==========================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
  class BTCModel(nn.Module):
132
  """
133
- Bidirectional Transformer for Chord Recognition.
134
- Arquitectura basada en el paper ISMIR 2019.
135
-
136
- Parámetros:
137
- n_freq (int): Número de bins de frecuencia en la entrada (default: 144 CQT bins).
138
- d_model (int): Dimensión interna del Transformer.
139
- num_heads (int): Cabezas de atención.
140
- num_layers (int): Capas del Transformer.
141
- d_ff (int): Dimensión del bloque convolucional.
142
- num_chords (int): Clases de acordes en la salida.
143
- dropout (float): Tasa de dropout.
144
  """
145
-
146
- def __init__(
147
- self,
148
- n_freq: int = 144,
149
- d_model: int = 128,
150
- num_heads: int = 4,
151
- num_layers: int = 8,
152
- d_ff: int = 256,
153
- num_chords: int = NUM_CHORDS,
154
- dropout: float = 0.1,
155
- ):
156
  super().__init__()
157
-
158
- # Proyección de entrada: frecuencias -> d_model
159
- self.input_projection = nn.Sequential(
160
- nn.Linear(n_freq, d_model),
161
- nn.LayerNorm(d_model),
162
- nn.Dropout(p=dropout)
163
- )
164
-
165
- # Codificación posicional
166
- self.pos_encoding = PositionalEncoding(d_model, dropout=dropout)
167
-
168
- # Bloques de Transformer Bidireccional
169
- self.transformer_blocks = nn.ModuleList([
170
- BidirectionalAttentionBlock(d_model, num_heads, d_ff, dropout=dropout)
171
- for _ in range(num_layers)
172
- ])
173
-
174
- # Clasificador final
175
- self.classifier = nn.Linear(d_model, num_chords)
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
  def forward(self, x: torch.Tensor) -> torch.Tensor:
178
  """
 
179
  Args:
180
- x: Tensor (batch, seq_len, n_freq) - características CQT del audio.
181
  Returns:
182
- logits: Tensor (batch, seq_len, num_chords) - probabilidades por frame.
183
  """
184
- x = self.input_projection(x)
185
- x = self.pos_encoding(x)
186
-
187
- for block in self.transformer_blocks:
188
- x = block(x)
189
-
190
- logits = self.classifier(x)
191
  return logits
 
1
  """
2
+ BTC Toolkit - Arquitectura del Modelo Oficial (Versión Dinámica)
3
+ Implementación del Bidirectional Transformer for Musical Chord Recognition (BTC)
4
+ basada en la arquitectura del paper original (ISMIR 2019) y su checkpoint pre-entrenado.
5
+ Soporta longitud de secuencia dinámica para evitar desajustes en el tamaño de los tensores.
 
 
6
  """
7
 
8
+ import math
9
+ import numpy as np
10
  import torch
11
  import torch.nn as nn
12
+ import torch.nn.functional as F
 
 
13
 
14
  # ==========================================
15
  # VOCABULARIO DE ACORDES (25 clases)
16
  # ==========================================
 
 
 
 
17
  CHORD_VOCAB = ['N'] + [
18
  'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B',
19
  'Cm', 'C#m', 'Dm', 'D#m', 'Em', 'Fm', 'F#m', 'Gm', 'G#m', 'Am', 'A#m', 'Bm'
 
21
  NUM_CHORDS = len(CHORD_VOCAB) # 25
22
 
23
 
24
+ def _gen_bias_mask(max_length):
25
+ """Generates bias values (-Inf) to mask future timesteps during attention."""
26
+ np_mask = np.triu(np.full([max_length, max_length], -np.inf), 1)
27
+ torch_mask = torch.from_numpy(np_mask).type(torch.FloatTensor)
28
+ return torch_mask.unsqueeze(0).unsqueeze(1)
29
+
30
+
31
+ def _gen_timing_signal(length, channels, min_timescale=1.0, max_timescale=1.0e4):
32
+ """Generates a [1, length, channels] timing signal consisting of sinusoids."""
33
+ position = np.arange(length)
34
+ num_timescales = channels // 2
35
+ log_timescale_increment = (
36
+ math.log(float(max_timescale) / float(min_timescale)) /
37
+ (float(num_timescales) - 1))
38
+ inv_timescales = min_timescale * np.exp(
39
+ np.arange(num_timescales).astype(float) * -log_timescale_increment)
40
+ scaled_time = np.expand_dims(position, 1) * np.expand_dims(inv_timescales, 0)
41
+
42
+ signal = np.concatenate([np.sin(scaled_time), np.cos(scaled_time)], axis=1)
43
+ signal = np.pad(signal, [[0, 0], [0, channels % 2]],
44
+ 'constant', constant_values=[0.0, 0.0])
45
+ signal = signal.reshape([1, length, channels])
46
+
47
+ return torch.from_numpy(signal).type(torch.FloatTensor)
48
+
49
+
50
+ class LayerNorm(nn.Module):
51
+ def __init__(self, features, eps=1e-6):
52
+ super(LayerNorm, self).__init__()
53
+ self.gamma = nn.Parameter(torch.ones(features))
54
+ self.beta = nn.Parameter(torch.zeros(features))
55
+ self.eps = eps
56
+
57
+ def forward(self, x):
58
+ mean = x.mean(-1, keepdim=True)
59
+ std = x.std(-1, keepdim=True)
60
+ return self.gamma * (x - mean) / (std + self.eps) + self.beta
61
+
62
+
63
+ class OutputLayer(nn.Module):
64
+ def __init__(self, hidden_size, output_size, probs_out=False):
65
+ super(OutputLayer, self).__init__()
66
+ self.output_size = output_size
67
+ self.output_projection = nn.Linear(hidden_size, output_size)
68
+ self.probs_out = probs_out
69
+ self.lstm = nn.LSTM(input_size=hidden_size, hidden_size=int(hidden_size/2), batch_first=True, bidirectional=True)
70
+ self.hidden_size = hidden_size
71
+
72
+ def loss(self, hidden, labels):
73
+ raise NotImplementedError('Must implement {}.loss'.format(self.__class__.__name__))
74
+
75
+
76
+ class SoftmaxOutputLayer(OutputLayer):
77
+ def forward(self, hidden):
78
+ logits = self.output_projection(hidden)
79
+ probs = F.softmax(logits, -1)
80
+ topk, indices = torch.topk(probs, 2)
81
+ predictions = indices[:, :, 0]
82
+ second = indices[:, :, 1]
83
+ if self.probs_out is True:
84
+ return logits
85
+ return predictions, second
86
+
87
+ def loss(self, hidden, labels):
88
+ logits = self.output_projection(hidden)
89
+ log_probs = F.log_softmax(logits, -1)
90
+ return F.nll_loss(log_probs.view(-1, self.output_size), labels.view(-1))
91
+
92
+
93
+ class MultiHeadAttention(nn.Module):
94
+ def __init__(self, input_depth, total_key_depth, total_value_depth, output_depth,
95
+ num_heads, bias_mask=None, dropout=0.0, attention_map=False):
96
+ super(MultiHeadAttention, self).__init__()
97
+ if total_key_depth % num_heads != 0:
98
+ raise ValueError("Key depth (%d) must be divisible by the number of attention heads (%d)." % (total_key_depth, num_heads))
99
+ if total_value_depth % num_heads != 0:
100
+ raise ValueError("Value depth (%d) must be divisible by the number of attention heads (%d)." % (total_value_depth, num_heads))
101
+
102
+ self.attention_map = attention_map
103
+ self.num_heads = num_heads
104
+ self.query_scale = (total_key_depth // num_heads) ** -0.5
105
+ self.bias_mask = bias_mask
106
+
107
+ self.query_linear = nn.Linear(input_depth, total_key_depth, bias=False)
108
+ self.key_linear = nn.Linear(input_depth, total_key_depth, bias=False)
109
+ self.value_linear = nn.Linear(input_depth, total_value_depth, bias=False)
110
+ self.output_linear = nn.Linear(total_value_depth, output_depth, bias=False)
111
+
112
+ self.dropout = nn.Dropout(dropout)
113
+
114
+ def _split_heads(self, x):
115
+ if len(x.shape) != 3:
116
+ raise ValueError("x must have rank 3")
117
+ shape = x.shape
118
+ return x.view(shape[0], shape[1], self.num_heads, shape[2] // self.num_heads).permute(0, 2, 1, 3)
119
+
120
+ def _merge_heads(self, x):
121
+ if len(x.shape) != 4:
122
+ raise ValueError("x must have rank 4")
123
+ shape = x.shape
124
+ return x.permute(0, 2, 1, 3).contiguous().view(shape[0], shape[2], shape[3] * self.num_heads)
125
+
126
+ def forward(self, queries, keys, values, bias_mask=None):
127
+ queries = self.query_linear(queries)
128
+ keys = self.key_linear(keys)
129
+ values = self.value_linear(values)
130
+
131
+ queries = self._split_heads(queries)
132
+ keys = self._split_heads(keys)
133
+ values = self._split_heads(values)
134
+
135
+ queries *= self.query_scale
136
+
137
+ logits = torch.matmul(queries, keys.permute(0, 1, 3, 2))
138
+
139
+ # Utilizar la máscara dinámica si se provee, sino la estática
140
+ mask = bias_mask if bias_mask is not None else self.bias_mask
141
+ if mask is not None:
142
+ logits += mask[:, :, :logits.shape[-2], :logits.shape[-1]].type_as(logits.data)
143
+
144
+ weights = nn.functional.softmax(logits, dim=-1)
145
+ weights = self.dropout(weights)
146
+ contexts = torch.matmul(weights, values)
147
+ contexts = self._merge_heads(contexts)
148
+ outputs = self.output_linear(contexts)
149
+
150
+ if self.attention_map is True:
151
+ return outputs, weights
152
+
153
+ return outputs
154
+
155
+
156
+ class Conv(nn.Module):
157
+ def __init__(self, input_size, output_size, kernel_size, pad_type):
158
+ super(Conv, self).__init__()
159
+ padding = (kernel_size - 1, 0) if pad_type == 'left' else (kernel_size // 2, (kernel_size - 1) // 2)
160
+ self.pad = nn.ConstantPad1d(padding, 0)
161
+ self.conv = nn.Conv1d(input_size, output_size, kernel_size=kernel_size, padding=0)
162
+
163
+ def forward(self, inputs):
164
+ inputs = self.pad(inputs.permute(0, 2, 1))
165
+ outputs = self.conv(inputs).permute(0, 2, 1)
166
+ return outputs
167
+
168
+
169
+ class PositionwiseFeedForward(nn.Module):
170
+ def __init__(self, input_depth, filter_size, output_depth, layer_config='ll', padding='left', dropout=0.0):
171
+ super(PositionwiseFeedForward, self).__init__()
172
+ layers = []
173
+ sizes = ([(input_depth, filter_size)] +
174
+ [(filter_size, filter_size)] * (len(layer_config) - 2) +
175
+ [(filter_size, output_depth)])
176
+
177
+ for lc, s in zip(list(layer_config), sizes):
178
+ if lc == 'l':
179
+ layers.append(nn.Linear(*s))
180
+ elif lc == 'c':
181
+ layers.append(Conv(*s, kernel_size=3, pad_type=padding))
182
+ else:
183
+ raise ValueError("Unknown layer type {}".format(lc))
184
+
185
+ self.layers = nn.ModuleList(layers)
186
  self.relu = nn.ReLU()
187
+ self.dropout = nn.Dropout(dropout)
188
+
189
+ def forward(self, inputs):
190
+ x = inputs
191
+ for i, layer in enumerate(self.layers):
192
+ x = layer(x)
193
+ if i < len(self.layers):
194
+ x = self.relu(x)
195
+ x = self.dropout(x)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  return x
197
 
198
 
199
+ class self_attention_block(nn.Module):
200
+ def __init__(self, hidden_size, total_key_depth, total_value_depth, filter_size, num_heads,
201
+ bias_mask=None, layer_dropout=0.0, attention_dropout=0.0, relu_dropout=0.0, attention_map=False):
202
+ super(self_attention_block, self).__init__()
203
+ self.attention_map = attention_map
204
+ self.multi_head_attention = MultiHeadAttention(hidden_size, total_key_depth, total_value_depth, hidden_size, num_heads, bias_mask, attention_dropout, attention_map)
205
+ self.positionwise_convolution = PositionwiseFeedForward(hidden_size, filter_size, hidden_size, layer_config='cc', padding='both', dropout=relu_dropout)
206
+ self.dropout = nn.Dropout(layer_dropout)
207
+ self.layer_norm_mha = LayerNorm(hidden_size)
208
+ self.layer_norm_ffn = LayerNorm(hidden_size)
209
+
210
+ def forward(self, inputs, bias_mask=None):
211
+ x = inputs
212
+ x_norm = self.layer_norm_mha(x)
213
+ if self.attention_map is True:
214
+ y, weights = self.multi_head_attention(x_norm, x_norm, x_norm, bias_mask=bias_mask)
215
+ else:
216
+ y = self.multi_head_attention(x_norm, x_norm, x_norm, bias_mask=bias_mask)
217
+ x = self.dropout(x + y)
218
+ x_norm = self.layer_norm_ffn(x)
219
+ y = self.positionwise_convolution(x_norm)
220
+ y = self.dropout(x + y)
221
+ if self.attention_map is True:
222
+ return y, weights
223
+ return y
224
+
225
+
226
+ class bi_directional_self_attention(nn.Module):
227
+ def __init__(self, hidden_size, total_key_depth, total_value_depth, filter_size, num_heads, max_length,
228
+ layer_dropout=0.0, attention_dropout=0.0, relu_dropout=0.0):
229
+ super(bi_directional_self_attention, self).__init__()
230
+ self.weights_list = list()
231
+ params = (hidden_size,
232
+ total_key_depth or hidden_size,
233
+ total_value_depth or hidden_size,
234
+ filter_size,
235
+ num_heads,
236
+ None, # La máscara se generará dinámicamente en forward
237
+ layer_dropout,
238
+ attention_dropout,
239
+ relu_dropout,
240
+ True)
241
+ self.attn_block = self_attention_block(*params)
242
+
243
+ params = (hidden_size,
244
+ total_key_depth or hidden_size,
245
+ total_value_depth or hidden_size,
246
+ filter_size,
247
+ num_heads,
248
+ None, # La máscara se generará dinámicamente en forward
249
+ layer_dropout,
250
+ attention_dropout,
251
+ relu_dropout,
252
+ True)
253
+ self.backward_attn_block = self_attention_block(*params)
254
+ self.linear = nn.Linear(hidden_size*2, hidden_size)
255
+
256
+ def forward(self, inputs):
257
+ x, list_weights = inputs
258
+ L = x.shape[1]
259
+
260
+ # Generar máscaras dinámicas para la longitud de secuencia actual
261
+ forward_mask = _gen_bias_mask(L).type_as(x)
262
+ backward_mask = torch.transpose(forward_mask, dim0=2, dim1=3)
263
+
264
+ # Forward Self-attention Block
265
+ encoder_outputs, weights = self.attn_block(x, bias_mask=forward_mask)
266
+ # Backward Self-attention Block
267
+ reverse_outputs, reverse_weights = self.backward_attn_block(x, bias_mask=backward_mask)
268
+
269
+ outputs = torch.cat((encoder_outputs, reverse_outputs), dim=2)
270
+ y = self.linear(outputs)
271
+
272
+ self.weights_list = list_weights
273
+ self.weights_list.append(weights)
274
+ self.weights_list.append(reverse_weights)
275
+ return y, self.weights_list
276
+
277
+
278
+ class bi_directional_self_attention_layers(nn.Module):
279
+ def __init__(self, embedding_size, hidden_size, num_layers, num_heads, total_key_depth, total_value_depth,
280
+ filter_size, max_length=100, input_dropout=0.0, layer_dropout=0.0,
281
+ attention_dropout=0.0, relu_dropout=0.0):
282
+ super(bi_directional_self_attention_layers, self).__init__()
283
+ params = (hidden_size,
284
+ total_key_depth or hidden_size,
285
+ total_value_depth or hidden_size,
286
+ filter_size,
287
+ num_heads,
288
+ max_length,
289
+ layer_dropout,
290
+ attention_dropout,
291
+ relu_dropout)
292
+ self.embedding_proj = nn.Linear(embedding_size, hidden_size, bias=False)
293
+ self.self_attn_layers = nn.Sequential(*[bi_directional_self_attention(*params) for l in range(num_layers)])
294
+ self.layer_norm = LayerNorm(hidden_size)
295
+ self.input_dropout = nn.Dropout(input_dropout)
296
+
297
+ def forward(self, inputs):
298
+ x = self.input_dropout(inputs)
299
+ x = self.embedding_proj(x)
300
+
301
+ # Generar señal de tiempo (timing signal) dinámicamente para evitar desajuste de dimensiones
302
+ timing_signal = _gen_timing_signal(x.shape[1], x.shape[2]).type_as(x)
303
+ x += timing_signal
304
+
305
+ y, weights_list = self.self_attn_layers((x, []))
306
+ y = self.layer_norm(y)
307
+ return y, weights_list
308
+
309
 
310
  class BTCModel(nn.Module):
311
  """
312
+ Bidirectional Transformer for Chord Recognition (Official Architecture Wrapper).
 
 
 
 
 
 
 
 
 
 
313
  """
314
+ def __init__(self, n_freq: int = 144):
 
 
 
 
 
 
 
 
 
 
315
  super().__init__()
316
+ config = {
317
+ 'feature_size': n_freq,
318
+ 'hidden_size': 128,
319
+ 'num_layers': 8,
320
+ 'num_heads': 4,
321
+ 'total_key_depth': 128,
322
+ 'total_value_depth': 128,
323
+ 'filter_size': 128,
324
+ 'timestep': 108,
325
+ 'input_dropout': 0.0,
326
+ 'layer_dropout': 0.0,
327
+ 'attention_dropout': 0.0,
328
+ 'relu_dropout': 0.0,
329
+ 'num_chords': NUM_CHORDS,
330
+ 'probs_out': True
331
+ }
332
+
333
+ params = (config['feature_size'],
334
+ config['hidden_size'],
335
+ config['num_layers'],
336
+ config['num_heads'],
337
+ config['total_key_depth'],
338
+ config['total_value_depth'],
339
+ config['filter_size'],
340
+ config['timestep'],
341
+ config['input_dropout'],
342
+ config['layer_dropout'],
343
+ config['attention_dropout'],
344
+ config['relu_dropout'])
345
+
346
+ self.self_attn_layers = bi_directional_self_attention_layers(*params)
347
+ self.output_layer = SoftmaxOutputLayer(hidden_size=config['hidden_size'], output_size=config['num_chords'], probs_out=config['probs_out'])
348
 
349
  def forward(self, x: torch.Tensor) -> torch.Tensor:
350
  """
351
+ Inferencia de acordes (retorna los logits).
352
  Args:
353
+ x: Tensor de entrada (batch, seq_len, n_freq)
354
  Returns:
355
+ logits: Tensor (batch, seq_len, num_chords)
356
  """
357
+ # Output of Bi-directional Self-attention Layers
358
+ self_attn_output, _ = self.self_attn_layers(x)
359
+ logits = self.output_layer(self_attn_output)
 
 
 
 
360
  return logits
btc_toolkit/weights_manager.py CHANGED
@@ -21,16 +21,15 @@ logger = logging.getLogger(__name__)
21
  # Directorio donde se guardan los pesos (relativo a este archivo)
22
  _MODELS_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "models")
23
 
24
- # URL pública del checkpoint pre-entrenado.
25
- # Actualizado al repositorio oficial jayg996/BTC-ISMIR19 porque el anterior daba 404.
26
- BTC_WEIGHTS_URL = "https://github.com/jayg996/BTC-ISMIR19/raw/master/test/btc_model.pt"
27
 
28
  # Nombre local del archivo de pesos
29
  BTC_WEIGHTS_FILENAME = "btc_chord.pth"
30
 
31
  # Hash SHA256 del archivo esperado (para verificar integridad)
32
- # Si cambia el archivo, este valor se debe actualizar
33
- BTC_WEIGHTS_SHA256 = None # Se puede activar en el futuro con el hash real
34
 
35
 
36
  def get_weights_path() -> str:
 
21
  # Directorio donde se guardan los pesos (relativo a este archivo)
22
  _MODELS_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "models")
23
 
24
+ # URL pública del checkpoint pre-entrenado oficial.
25
+ # Apunta al checkpoint completo de 12.2 MB alojado en Hugging Face (amaai-lab/music2emo)
26
+ BTC_WEIGHTS_URL = "https://huggingface.co/amaai-lab/music2emo/resolve/main/inference/data/btc_model.pt"
27
 
28
  # Nombre local del archivo de pesos
29
  BTC_WEIGHTS_FILENAME = "btc_chord.pth"
30
 
31
  # Hash SHA256 del archivo esperado (para verificar integridad)
32
+ BTC_WEIGHTS_SHA256 = "753d07ad5f8f70e1d2a58a2c80cdd5f91c23e8d3a9bb084fb7228418546388ba"
 
33
 
34
 
35
  def get_weights_path() -> str: