use crate::{Module, ModuleBase, Parameter};
use torsh_core::device::DeviceType;
use torsh_core::error::Result;
use torsh_tensor::{creation::*, Tensor};
use super::common::{utils, NormalizationConfig};
#[cfg(feature = "std")]
use std::collections::HashMap;
#[cfg(not(feature = "std"))]
use hashbrown::HashMap;
pub struct SwitchableNorm2d {
base: ModuleBase,
num_features: usize,
config: NormalizationConfig,
#[allow(dead_code)]
using_movavg: bool,
}
impl SwitchableNorm2d {
pub fn new(num_features: usize) -> Result<Self> {
Self::with_config(num_features, NormalizationConfig::default())
}
pub fn with_config(num_features: usize, config: NormalizationConfig) -> Result<Self> {
let mut base = ModuleBase::new();
let switch_weight = ones(&[3, num_features])?; base.register_parameter("switch_weight".to_string(), Parameter::new(switch_weight));
if config.affine {
let weight = ones(&[num_features])?;
let bias = zeros(&[num_features])?;
base.register_parameter("weight".to_string(), Parameter::new(weight));
base.register_parameter("bias".to_string(), Parameter::new(bias));
}
if config.track_running_stats {
let running_mean = zeros(&[num_features])?;
let running_var = ones(&[num_features])?;
base.register_buffer("running_mean".to_string(), running_mean);
base.register_buffer("running_var".to_string(), running_var);
base.register_buffer("num_batches_tracked".to_string(), zeros(&[1])?);
}
let using_movavg = config.track_running_stats;
Ok(Self {
base,
num_features,
config,
using_movavg,
})
}
pub fn num_features(&self) -> usize {
self.num_features
}
pub fn eps(&self) -> f32 {
self.config.eps
}
fn batch_norm_stats(&self, input: &Tensor) -> Result<(Tensor, Tensor)> {
let mean = input.mean(Some(&[0, 2, 3]), true)?;
let centered = input.sub(&mean)?;
let variance = centered.pow_scalar(2.0)?.mean(Some(&[0, 2, 3]), true)?;
Ok((mean, variance))
}
fn instance_norm_stats(&self, input: &Tensor) -> Result<(Tensor, Tensor)> {
let mean = input.mean(Some(&[2, 3]), true)?;
let centered = input.sub(&mean)?;
let variance = centered.pow_scalar(2.0)?.mean(Some(&[2, 3]), true)?;
Ok((mean, variance))
}
fn layer_norm_stats(&self, input: &Tensor) -> Result<(Tensor, Tensor)> {
let mean = input.mean(Some(&[1, 2, 3]), true)?;
let centered = input.sub(&mean)?;
let variance = centered.pow_scalar(2.0)?.mean(Some(&[1, 2, 3]), true)?;
Ok((mean, variance))
}
fn switch_probabilities(&self) -> Result<Tensor> {
let switch_weight = self.base.parameters.get("switch_weight").ok_or_else(|| {
torsh_core::error::TorshError::InvalidOperation(
"Switch weight parameter not found".to_string(),
)
})?;
let logits = switch_weight.tensor().read().clone();
let values = logits.to_vec()?;
let channels = self.num_features;
let mut maxima = vec![f32::NEG_INFINITY; channels];
for (index, value) in values.iter().enumerate() {
let channel = index % channels;
if *value > maxima[channel] {
maxima[channel] = *value;
}
}
let shift = Tensor::from_data(maxima, vec![1, channels], logits.device())?;
let exponentials = logits.sub(&shift)?.exp()?;
let total = exponentials.sum_dim(&[0], true)?;
exponentials.div(&total)
}
fn apply_switchable_norm(&self, input: &Tensor) -> Result<Tensor> {
let (bn_mean, bn_var) = self.batch_norm_stats(input)?;
let (in_mean, in_var) = self.instance_norm_stats(input)?;
let (ln_mean, ln_var) = self.layer_norm_stats(input)?;
let probabilities = self.switch_probabilities()?;
let channels = self.num_features as i32;
let weight_for = |row: i64| -> Result<Tensor> {
probabilities
.narrow(0, row, 1)?
.reshape(&[1, channels, 1, 1])
};
let bn_weight = weight_for(0)?;
let in_weight = weight_for(1)?;
let ln_weight = weight_for(2)?;
let combined_mean = bn_mean
.mul(&bn_weight)?
.add(&in_mean.mul(&in_weight)?)?
.add(&ln_mean.mul(&ln_weight)?)?;
let combined_var = bn_var
.mul(&bn_weight)?
.add(&in_var.mul(&in_weight)?)?
.add(&ln_var.mul(&ln_weight)?)?;
let std = combined_var.add_scalar(self.config.eps)?.sqrt()?;
let mut normalized = input.sub(&combined_mean)?.div(&std)?;
if self.config.affine {
let broadcast = utils::channel_broadcast_shape(4, self.num_features);
if let Some(w) = self.base.parameters.get("weight") {
let weight = w.tensor().read().reshape(&broadcast)?;
normalized = normalized.mul(&weight)?;
}
if let Some(b) = self.base.parameters.get("bias") {
let bias = b.tensor().read().reshape(&broadcast)?;
normalized = normalized.add(&bias)?;
}
}
Ok(normalized)
}
}
impl Module for SwitchableNorm2d {
fn forward(&self, input: &Tensor) -> Result<Tensor> {
let input_shape = input.shape();
let dims = input_shape.dims();
if dims.len() != 4 {
return Err(torsh_core::error::TorshError::InvalidShape(format!(
"SwitchableNorm2d expects 4D input (N, C, H, W), got shape {:?}",
dims
)));
}
if dims[1] != self.num_features {
return Err(torsh_core::error::TorshError::InvalidShape(format!(
"Expected {} features, got {}",
self.num_features, dims[1]
)));
}
self.apply_switchable_norm(input)
}
fn parameters(&self) -> HashMap<String, Parameter> {
self.base.named_parameters()
}
fn named_parameters(&self) -> HashMap<String, Parameter> {
self.base.named_parameters()
}
fn training(&self) -> bool {
self.base.training()
}
fn train(&mut self) {
self.base.set_training(true);
}
fn eval(&mut self) {
self.base.set_training(false);
}
fn to_device(&mut self, device: DeviceType) -> Result<()> {
self.base.to_device(device)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_switchable_norm_creation() {
let switchable_norm = SwitchableNorm2d::new(64).expect("Switchable Norm2d should succeed");
assert_eq!(switchable_norm.num_features(), 64);
assert_eq!(switchable_norm.eps(), 1e-5);
}
#[test]
fn test_switchable_norm_shape_validation() {
let switchable_norm = SwitchableNorm2d::new(3).expect("Switchable Norm2d should succeed");
let input = zeros(&[2, 3, 32, 32]).expect("zeros should succeed");
assert!(switchable_norm.forward(&input).is_ok());
let input_3d = zeros(&[2, 3, 32]).expect("zeros should succeed");
assert!(switchable_norm.forward(&input_3d).is_err());
let input_wrong_channels = zeros(&[2, 4, 32, 32]).expect("zeros should succeed");
assert!(switchable_norm.forward(&input_wrong_channels).is_err());
}
}