use optirs_core::{
optimizers::GroupedAdam,
parameter_groups::{GroupedOptimizer, ParameterGroupConfig},
};
use scirs2_core::ndarray::{Array1, Array2};
struct SimpleNetwork {
feature_layer1: Array2<f64>,
feature_layer2: Array2<f64>,
classifier_layer1: Array2<f64>,
classifier_layer2: Array2<f64>,
output_layer: Array1<f64>,
}
impl SimpleNetwork {
fn new() -> Self {
Self {
feature_layer1: Array2::from_shape_vec((10, 5), vec![0.1; 50]).expect("unwrap failed"),
feature_layer2: Array2::from_shape_vec((8, 10), vec![0.1; 80]).expect("unwrap failed"),
classifier_layer1: Array2::from_shape_vec((6, 8), vec![0.1; 48])
.expect("unwrap failed"),
classifier_layer2: Array2::from_shape_vec((4, 6), vec![0.1; 24])
.expect("unwrap failed"),
output_layer: Array1::from_vec(vec![0.1; 4]),
}
}
fn get_feature_params(&self) -> Vec<Array2<f64>> {
vec![self.feature_layer1.clone(), self.feature_layer2.clone()]
}
fn get_classifier_params(&self) -> Vec<Array2<f64>> {
vec![
self.classifier_layer1.clone(),
self.classifier_layer2.clone(),
]
}
fn get_output_params(&self) -> Vec<Array1<f64>> {
vec![self.output_layer.clone()]
}
}
#[allow(dead_code)]
fn compute_gradients_2d(params: &[Array2<f64>]) -> Vec<Array2<f64>> {
params
.iter()
.map(|p| Array2::from_shape_vec(p.dim(), vec![0.01; p.len()]).expect("unwrap failed"))
.collect()
}
#[allow(dead_code)]
fn compute_gradients_1d(params: &[Array1<f64>]) -> Vec<Array1<f64>> {
params
.iter()
.map(|p| Array1::from_vec(vec![0.01; p.len()]))
.collect()
}
#[allow(dead_code)]
fn main() {
println!("Parameter Groups Example");
println!("========================\n");
let network = SimpleNetwork::new();
let mut optimizer_2d = GroupedAdam::new(0.001); let mut optimizer_1d = GroupedAdam::new(0.001);
let feature_config = ParameterGroupConfig::new()
.with_learning_rate(0.0001) .with_weight_decay(0.0001);
let feature_params = network.get_feature_params();
let feature_group = optimizer_2d
.add_group(feature_params.clone(), feature_config)
.expect("unwrap failed");
let classifier_config = ParameterGroupConfig::new()
.with_learning_rate(0.001) .with_weight_decay(0.0);
let classifier_params = network.get_classifier_params();
let classifier_group = optimizer_2d
.add_group(classifier_params.clone(), classifier_config)
.expect("unwrap failed");
let output_config = ParameterGroupConfig::new()
.with_learning_rate(0.01) .with_weight_decay(0.0);
let output_params = network.get_output_params();
let output_group = optimizer_1d
.add_group(output_params.clone(), output_config)
.expect("unwrap failed");
println!("Created parameter groups:");
println!(
"- Feature extractor: {} parameters, LR = 0.0001",
feature_params.iter().map(|p| p.len()).sum::<usize>()
);
println!(
"- Classifier: {} parameters, LR = 0.001",
classifier_params.iter().map(|p| p.len()).sum::<usize>()
);
println!(
"- Output layer: {} parameters, LR = 0.01\n",
output_params.iter().map(|p| p.len()).sum::<usize>()
);
println!("Training simulation:");
for epoch in 0..5 {
println!("\nEpoch {}:", epoch);
let feature_grads = compute_gradients_2d(&feature_params);
let classifier_grads = compute_gradients_2d(&classifier_params);
let output_grads = compute_gradients_1d(&output_params);
let updated_features = optimizer_2d
.step_group(feature_group, &feature_grads)
.expect("unwrap failed");
let feature_change: f64 = updated_features
.iter()
.zip(feature_params.iter())
.map(|(new, old)| (new - old).mapv(|x| x.abs()).sum())
.sum();
let updated_classifier = optimizer_2d
.step_group(classifier_group, &classifier_grads)
.expect("unwrap failed");
let classifier_change: f64 = updated_classifier
.iter()
.zip(classifier_params.iter())
.map(|(new, old)| (new - old).mapv(|x| x.abs()).sum())
.sum();
let updated_output = optimizer_1d
.step_group(output_group, &output_grads)
.expect("unwrap failed");
let output_change: f64 = updated_output
.iter()
.zip(output_params.iter())
.map(|(new, old)| (new - old).mapv(|x| x.abs()).sum())
.sum();
println!(" Feature extractor change: {:.6}", feature_change);
println!(" Classifier change: {:.6}", classifier_change);
println!(" Output layer change: {:.6}", output_change);
if epoch > 0 {
println!(
" Ratio (classifier/feature): {:.1}x",
classifier_change / feature_change
);
println!(
" Ratio (output/classifier): {:.1}x",
output_change / classifier_change
);
}
}
println!("\n\nDynamic learning rate adjustment:");
optimizer_2d
.set_group_learning_rate(classifier_group, 0.0001)
.expect("unwrap failed");
println!("Reduced classifier learning rate to 0.0001");
let feature_grads = compute_gradients_2d(&feature_params);
let classifier_grads = compute_gradients_2d(&classifier_params);
let updated_features = optimizer_2d
.step_group(feature_group, &feature_grads)
.expect("unwrap failed");
let updated_classifier = optimizer_2d
.step_group(classifier_group, &classifier_grads)
.expect("unwrap failed");
let feature_change: f64 = updated_features
.iter()
.zip(feature_params.iter())
.map(|(new, old)| (new - old).mapv(|x| x.abs()).sum())
.sum();
let classifier_change: f64 = updated_classifier
.iter()
.zip(classifier_params.iter())
.map(|(new, old)| (new - old).mapv(|x| x.abs()).sum())
.sum();
println!("\nAfter adjustment:");
println!(" Feature extractor change: {:.6}", feature_change);
println!(" Classifier change: {:.6}", classifier_change);
println!(
" Now they should be similar: ratio = {:.2}x",
classifier_change / feature_change
);
println!("\n\nParameter group summary:");
for (i, group) in optimizer_2d.groups().iter().enumerate() {
println!(
"Group {}: {} parameters, LR = {:?}",
i,
group.num_params(),
group.config.learning_rate
);
}
}