1#[cfg(feature = "ml_train")]
4use burn::train::MultiLabelClassificationOutput;
5use burn::{
6 module::Module,
7 nn::{
8 self,
9 attention::{MhaInput, MultiHeadAttention, MultiHeadAttentionConfig},
10 },
11 tensor::{backend::Backend, Tensor},
12};
13
14use super::{INPUT_SPACE_SIZE, NUM_CLASSES};
15
16#[cfg(all(feature = "ml_train", feature = "ml_target_folded_bass"))]
17use super::{PITCH_CLASS_COUNT, TARGET_FOLDED_BASS_NOTE_OFFSET, TARGET_FOLDED_BASS_OFFSET};
18
19#[cfg(feature = "ml_train")]
20use crate::ml::train::data::KordBatch;
21
22#[derive(Module, Debug)]
26pub struct KordModel<B: Backend> {
27 mha: MultiHeadAttention<B>,
28 norm1: nn::LayerNorm<B>,
29
30 trunk_in: nn::Linear<B>,
31 trunk_dropout: nn::Dropout,
32 trunk_out: nn::Linear<B>,
33 norm2: nn::LayerNorm<B>,
34
35 output: nn::Linear<B>,
36
37 num_chunks: usize,
39 chunk_size: usize,
40}
41
42impl<B: Backend> KordModel<B> {
43 pub fn new(device: &B::Device, mha_heads: usize, dropout: f64, trunk_hidden_size: usize) -> Self {
45 assert!(
48 INPUT_SPACE_SIZE.is_multiple_of(mha_heads),
49 "INPUT_SPACE_SIZE ({}) must be divisible by mha_heads ({})",
50 INPUT_SPACE_SIZE,
51 mha_heads
52 );
53 let num_chunks = mha_heads;
54 let chunk_size = INPUT_SPACE_SIZE / mha_heads;
55 assert!(
56 chunk_size >= mha_heads && chunk_size.is_multiple_of(mha_heads),
57 "chunk_size ({}) must be >= mha_heads ({}) and divisible by it",
58 chunk_size,
59 mha_heads
60 );
61
62 let mha = MultiHeadAttentionConfig::new(chunk_size, mha_heads).with_dropout(dropout).init::<B>(device);
63 let norm1 = nn::LayerNormConfig::new(INPUT_SPACE_SIZE).init(device);
64
65 let trunk_in = nn::LinearConfig::new(INPUT_SPACE_SIZE, trunk_hidden_size).with_bias(true).init::<B>(device);
66 let trunk_dropout = nn::DropoutConfig::new(dropout).init();
67 let trunk_out = nn::LinearConfig::new(trunk_hidden_size, INPUT_SPACE_SIZE).with_bias(true).init::<B>(device);
68 let norm2 = nn::LayerNormConfig::new(INPUT_SPACE_SIZE).init(device);
69
70 let output = nn::LinearConfig::new(INPUT_SPACE_SIZE, NUM_CLASSES).with_bias(true).init::<B>(device);
71
72 Self {
73 mha,
74 norm1,
75 trunk_in,
76 trunk_dropout,
77 trunk_out,
78 norm2,
79 output,
80 num_chunks,
81 chunk_size,
82 }
83 }
84
85 pub fn forward(&self, input: Tensor<B, 2>) -> Tensor<B, 2> {
87 use burn::tensor::activation::gelu;
88
89 let [batch_size, input_size] = input.dims();
90
91 let x = input.clone().reshape([batch_size, self.num_chunks, self.chunk_size]);
95
96 let attn_output = self.mha.forward(MhaInput::new(x.clone(), x.clone(), x));
98
99 let attn_out = attn_output.context.reshape([batch_size, input_size]);
101
102 let x = self.norm1.forward(input + attn_out);
104
105 let trunk_in = self.trunk_in.forward(x.clone());
107 let trunk_in = gelu(trunk_in);
108 let trunk_in = self.trunk_dropout.forward(trunk_in);
109 let trunk_out = self.trunk_out.forward(trunk_in);
110 let x = self.norm2.forward(x + trunk_out);
111
112 self.output.forward(x)
114 }
115
116 #[cfg(all(feature = "ml_train", any(feature = "ml_target_full", feature = "ml_target_folded")))]
118 pub fn forward_classification(&self, item: KordBatch<B>) -> MultiLabelClassificationOutput<B> {
119 use burn::nn::loss::BinaryCrossEntropyLossConfig;
120
121 let logits = self.forward(item.samples);
122 let targets = item.targets;
123
124 let loss = BinaryCrossEntropyLossConfig::new().with_logits(true).init(&logits.device()).forward(logits.clone(), targets.clone());
125
126 MultiLabelClassificationOutput { loss, output: logits, targets }
127 }
128
129 #[cfg(all(feature = "ml_train", feature = "ml_target_folded_bass"))]
131 pub fn forward_classification(&self, item: KordBatch<B>) -> MultiLabelClassificationOutput<B> {
132 use burn::nn::loss::{BinaryCrossEntropyLossConfig, CrossEntropyLossConfig};
133
134 let logits = self.forward(item.samples);
135 let targets = item.targets;
136 let batch = logits.dims()[0];
137
138 let device = logits.device();
139 let bce_loss = BinaryCrossEntropyLossConfig::new().with_logits(true).init(&device);
140 let ce_loss = CrossEntropyLossConfig::new().init(&device);
141
142 let bass_start = TARGET_FOLDED_BASS_OFFSET;
143 let bass_end = bass_start + PITCH_CLASS_COUNT;
144 let note_start = TARGET_FOLDED_BASS_NOTE_OFFSET;
145 let note_end = note_start + PITCH_CLASS_COUNT;
146
147 let bass_logits = logits.clone().slice([0..batch, bass_start..bass_end]);
148 let note_logits = logits.clone().slice([0..batch, note_start..note_end]);
149
150 let note_targets = targets.clone().slice([0..batch, note_start..note_end]);
151 let bass_targets_hot = targets.clone().slice([0..batch, bass_start..bass_end]);
152 let bass_targets = bass_targets_hot.argmax(1).squeeze_dim::<1>(1);
153
154 let note_loss = bce_loss.forward(note_logits, note_targets);
155 let categorical_loss = ce_loss.forward(bass_logits, bass_targets);
156 let loss = note_loss + categorical_loss;
157
158 MultiLabelClassificationOutput { loss, output: logits, targets }
159 }
160}