use crate::error::{NeuralError, Result};
use scirs2_core::ndarray::Array2;
pub fn count_parameters(weights: &[Array2<f32>]) -> usize {
weights.iter().map(|w| w.len()).sum()
}
pub fn count_flops_dense(input_size: usize, output_size: usize) -> usize {
2 * input_size * output_size
}
pub fn count_flops_conv(
input_h: usize,
input_w: usize,
in_channels: usize,
out_channels: usize,
kernel_h: usize,
kernel_w: usize,
) -> usize {
2 * input_h * input_w * in_channels * out_channels * kernel_h * kernel_w
}
pub fn memory_footprint_bytes(weights: &[Array2<f32>]) -> usize {
count_parameters(weights) * std::mem::size_of::<f32>()
}
pub fn compression_ratio(original: &[Array2<f32>], compressed: &[Array2<f32>]) -> Result<f64> {
let n_orig = count_parameters(original);
let n_comp = count_parameters(compressed);
if n_orig == 0 {
return Err(NeuralError::InvalidArchitecture(
"compression_ratio: original model has 0 parameters".into(),
));
}
if n_comp == 0 {
return Err(NeuralError::InvalidArchitecture(
"compression_ratio: compressed model has 0 parameters".into(),
));
}
Ok(n_orig as f64 / n_comp as f64)
}
#[derive(Debug, Clone)]
pub struct ModelSummary {
pub total_parameters: usize,
pub sparsity: f64,
pub memory_bytes: usize,
pub num_layers: usize,
pub layer_params: Vec<usize>,
}
impl ModelSummary {
pub fn from_weights(weights: &[Array2<f32>]) -> Self {
let layer_params: Vec<usize> = weights.iter().map(|w| w.len()).collect();
let total_parameters: usize = layer_params.iter().sum();
let total_zeros: usize = weights
.iter()
.flat_map(|w| w.iter())
.filter(|&&v| v.abs() < 1e-8)
.count();
let sparsity = if total_parameters == 0 {
0.0
} else {
total_zeros as f64 / total_parameters as f64
};
Self {
total_parameters,
sparsity,
memory_bytes: total_parameters * std::mem::size_of::<f32>(),
num_layers: weights.len(),
layer_params,
}
}
}
pub fn format_summary(summary: &ModelSummary) -> String {
let mut s = String::new();
s.push_str(&format!(
"Model Summary\n Layers : {}\n Parameters : {}\n Sparsity : {:.2}%\n Memory : {} KB\n",
summary.num_layers,
summary.total_parameters,
summary.sparsity * 100.0,
summary.memory_bytes / 1024,
));
for (i, &p) in summary.layer_params.iter().enumerate() {
s.push_str(&format!(" Layer {:3}: {:8} params\n", i, p));
}
s
}
#[cfg(test)]
mod tests {
use super::*;
fn make_layer(nrows: usize, ncols: usize) -> Array2<f32> {
Array2::from_elem((nrows, ncols), 1.0_f32)
}
#[test]
fn test_count_parameters() {
let w = vec![make_layer(3, 4), make_layer(4, 5)];
assert_eq!(count_parameters(&w), 3 * 4 + 4 * 5);
}
#[test]
fn test_count_parameters_empty() {
assert_eq!(count_parameters(&[]), 0);
}
#[test]
fn test_count_flops_dense() {
assert_eq!(count_flops_dense(784, 256), 401_408);
}
#[test]
fn test_count_flops_dense_single_neuron() {
assert_eq!(count_flops_dense(10, 1), 20);
}
#[test]
fn test_count_flops_conv() {
let expected = 2 * 28 * 28 * 1 * 32 * 3 * 3;
assert_eq!(count_flops_conv(28, 28, 1, 32, 3, 3), expected);
}
#[test]
fn test_memory_footprint_bytes() {
let w = vec![make_layer(10, 10)];
assert_eq!(memory_footprint_bytes(&w), 400);
}
#[test]
fn test_compression_ratio() {
let original = vec![make_layer(100, 100)];
let compressed = vec![make_layer(50, 50)];
let ratio = compression_ratio(&original, &compressed).expect("ratio failed");
assert!((ratio - 4.0).abs() < 1e-9);
}
#[test]
fn test_compression_ratio_equal() {
let w = vec![make_layer(10, 10)];
let ratio = compression_ratio(&w, &w).expect("ratio failed");
assert!((ratio - 1.0).abs() < 1e-9);
}
#[test]
fn test_compression_ratio_empty_original() {
let result = compression_ratio(&[], &[make_layer(1, 1)]);
assert!(result.is_err());
}
#[test]
fn test_compression_ratio_empty_compressed() {
let result = compression_ratio(&[make_layer(1, 1)], &[]);
assert!(result.is_err());
}
#[test]
fn test_model_summary() {
let w = vec![make_layer(10, 5), make_layer(5, 2)];
let summary = ModelSummary::from_weights(&w);
assert_eq!(summary.total_parameters, 60);
assert_eq!(summary.num_layers, 2);
assert_eq!(summary.memory_bytes, 60 * 4);
assert!((summary.sparsity - 0.0).abs() < 1e-9);
}
#[test]
fn test_model_summary_sparse() {
let flat: Vec<f32> = (0..20).map(|i| if i % 2 == 0 { 0.0 } else { 1.0 }).collect();
let w = Array2::from_shape_vec((4, 5), flat).expect("shape");
let summary = ModelSummary::from_weights(&[w]);
assert!((summary.sparsity - 0.5).abs() < 0.01);
}
#[test]
fn test_format_summary_contains_key_info() {
let w = vec![make_layer(8, 8)];
let summary = ModelSummary::from_weights(&w);
let text = format_summary(&summary);
assert!(text.contains("64"), "should mention param count 64");
assert!(text.contains("Layer"), "should mention layers");
}
}