#[cfg(feature = "ml_train")]
use burn::train::MultiLabelClassificationOutput;
use burn::{
module::Module,
nn::{
self,
attention::{MhaInput, MultiHeadAttention, MultiHeadAttentionConfig},
},
tensor::{backend::Backend, Tensor},
};
use super::{INPUT_SPACE_SIZE, NUM_CLASSES};
#[cfg(all(feature = "ml_train", feature = "ml_target_folded_bass"))]
use super::{PITCH_CLASS_COUNT, TARGET_FOLDED_BASS_NOTE_OFFSET, TARGET_FOLDED_BASS_OFFSET};
#[cfg(feature = "ml_train")]
use crate::ml::train::data::KordBatch;
#[derive(Module, Debug)]
pub struct KordModel<B: Backend> {
mha: MultiHeadAttention<B>,
norm1: nn::LayerNorm<B>,
trunk_in: nn::Linear<B>,
trunk_dropout: nn::Dropout,
trunk_out: nn::Linear<B>,
norm2: nn::LayerNorm<B>,
output: nn::Linear<B>,
num_chunks: usize,
chunk_size: usize,
}
impl<B: Backend> KordModel<B> {
pub fn new(device: &B::Device, mha_heads: usize, dropout: f64, trunk_hidden_size: usize) -> Self {
assert!(
INPUT_SPACE_SIZE.is_multiple_of(mha_heads),
"INPUT_SPACE_SIZE ({}) must be divisible by mha_heads ({})",
INPUT_SPACE_SIZE,
mha_heads
);
let num_chunks = mha_heads;
let chunk_size = INPUT_SPACE_SIZE / mha_heads;
assert!(
chunk_size >= mha_heads && chunk_size.is_multiple_of(mha_heads),
"chunk_size ({}) must be >= mha_heads ({}) and divisible by it",
chunk_size,
mha_heads
);
let mha = MultiHeadAttentionConfig::new(chunk_size, mha_heads).with_dropout(dropout).init::<B>(device);
let norm1 = nn::LayerNormConfig::new(INPUT_SPACE_SIZE).init(device);
let trunk_in = nn::LinearConfig::new(INPUT_SPACE_SIZE, trunk_hidden_size).with_bias(true).init::<B>(device);
let trunk_dropout = nn::DropoutConfig::new(dropout).init();
let trunk_out = nn::LinearConfig::new(trunk_hidden_size, INPUT_SPACE_SIZE).with_bias(true).init::<B>(device);
let norm2 = nn::LayerNormConfig::new(INPUT_SPACE_SIZE).init(device);
let output = nn::LinearConfig::new(INPUT_SPACE_SIZE, NUM_CLASSES).with_bias(true).init::<B>(device);
Self {
mha,
norm1,
trunk_in,
trunk_dropout,
trunk_out,
norm2,
output,
num_chunks,
chunk_size,
}
}
pub fn forward(&self, input: Tensor<B, 2>) -> Tensor<B, 2> {
use burn::tensor::activation::gelu;
let [batch_size, input_size] = input.dims();
let x = input.clone().reshape([batch_size, self.num_chunks, self.chunk_size]);
let attn_output = self.mha.forward(MhaInput::new(x.clone(), x.clone(), x));
let attn_out = attn_output.context.reshape([batch_size, input_size]);
let x = self.norm1.forward(input + attn_out);
let trunk_in = self.trunk_in.forward(x.clone());
let trunk_in = gelu(trunk_in);
let trunk_in = self.trunk_dropout.forward(trunk_in);
let trunk_out = self.trunk_out.forward(trunk_in);
let x = self.norm2.forward(x + trunk_out);
self.output.forward(x)
}
#[cfg(all(feature = "ml_train", any(feature = "ml_target_full", feature = "ml_target_folded")))]
pub fn forward_classification(&self, item: KordBatch<B>) -> MultiLabelClassificationOutput<B> {
use burn::nn::loss::BinaryCrossEntropyLossConfig;
let logits = self.forward(item.samples);
let targets = item.targets;
let loss = BinaryCrossEntropyLossConfig::new().with_logits(true).init(&logits.device()).forward(logits.clone(), targets.clone());
MultiLabelClassificationOutput { loss, output: logits, targets }
}
#[cfg(all(feature = "ml_train", feature = "ml_target_folded_bass"))]
pub fn forward_classification(&self, item: KordBatch<B>) -> MultiLabelClassificationOutput<B> {
use burn::nn::loss::{BinaryCrossEntropyLossConfig, CrossEntropyLossConfig};
let logits = self.forward(item.samples);
let targets = item.targets;
let batch = logits.dims()[0];
let device = logits.device();
let bce_loss = BinaryCrossEntropyLossConfig::new().with_logits(true).init(&device);
let ce_loss = CrossEntropyLossConfig::new().init(&device);
let bass_start = TARGET_FOLDED_BASS_OFFSET;
let bass_end = bass_start + PITCH_CLASS_COUNT;
let note_start = TARGET_FOLDED_BASS_NOTE_OFFSET;
let note_end = note_start + PITCH_CLASS_COUNT;
let bass_logits = logits.clone().slice([0..batch, bass_start..bass_end]);
let note_logits = logits.clone().slice([0..batch, note_start..note_end]);
let note_targets = targets.clone().slice([0..batch, note_start..note_end]);
let bass_targets_hot = targets.clone().slice([0..batch, bass_start..bass_end]);
let bass_targets = bass_targets_hot.argmax(1).squeeze_dim::<1>(1);
let note_loss = bce_loss.forward(note_logits, note_targets);
let categorical_loss = ce_loss.forward(bass_logits, bass_targets);
let loss = note_loss + categorical_loss;
MultiLabelClassificationOutput { loss, output: logits, targets }
}
}