Add to_varstore() compatibility shims on DuelingQNetwork and DistributionalDuelingQNetwork so test/example code can rebuild a GpuVarStore snapshot when needed. Delete dead tests that referenced removed DQNAgent, PrioritizedReplayBuffer, and ReplayBufferType. Fix action index references (action_19/21 -> action_28/30) and type annotation issues (sin ambiguity, remainder operator). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
607 lines
19 KiB
Rust
607 lines
19 KiB
Rust
#![allow(
|
|
clippy::assertions_on_constants,
|
|
clippy::assertions_on_result_states,
|
|
clippy::clone_on_copy,
|
|
clippy::decimal_literal_representation,
|
|
clippy::doc_markdown,
|
|
clippy::empty_line_after_doc_comments,
|
|
clippy::field_reassign_with_default,
|
|
clippy::get_unwrap,
|
|
clippy::identity_op,
|
|
clippy::inconsistent_digit_grouping,
|
|
clippy::indexing_slicing,
|
|
clippy::integer_division,
|
|
clippy::len_zero,
|
|
clippy::let_underscore_must_use,
|
|
clippy::manual_div_ceil,
|
|
clippy::manual_let_else,
|
|
clippy::manual_range_contains,
|
|
clippy::modulo_arithmetic,
|
|
clippy::needless_range_loop,
|
|
clippy::non_ascii_literal,
|
|
clippy::redundant_clone,
|
|
clippy::shadow_reuse,
|
|
clippy::shadow_same,
|
|
clippy::shadow_unrelated,
|
|
clippy::single_match_else,
|
|
clippy::str_to_string,
|
|
clippy::string_slice,
|
|
clippy::tests_outside_test_module,
|
|
clippy::too_many_lines,
|
|
clippy::unnecessary_wraps,
|
|
clippy::unseparated_literal_suffix,
|
|
clippy::use_debug,
|
|
clippy::useless_vec,
|
|
clippy::wildcard_enum_match_arm,
|
|
clippy::else_if_without_else,
|
|
clippy::expect_used,
|
|
clippy::missing_const_for_fn,
|
|
clippy::similar_names,
|
|
clippy::type_complexity,
|
|
clippy::collapsible_else_if,
|
|
clippy::doc_lazy_continuation,
|
|
clippy::items_after_test_module,
|
|
clippy::map_clone,
|
|
clippy::multiple_unsafe_ops_per_block,
|
|
clippy::unwrap_or_default,
|
|
clippy::assign_op_pattern,
|
|
clippy::needless_borrow,
|
|
clippy::println_empty_string,
|
|
clippy::unnecessary_cast,
|
|
clippy::used_underscore_binding,
|
|
clippy::create_dir,
|
|
clippy::implicit_saturating_sub,
|
|
clippy::exit,
|
|
clippy::expect_fun_call,
|
|
clippy::too_many_arguments,
|
|
clippy::unnecessary_map_or,
|
|
clippy::unwrap_used,
|
|
dead_code,
|
|
unused_imports,
|
|
unused_variables,
|
|
clippy::cloned_ref_to_slice_refs,
|
|
clippy::neg_multiply,
|
|
clippy::while_let_loop,
|
|
clippy::bool_assert_comparison,
|
|
clippy::excessive_precision,
|
|
clippy::trivially_copy_pass_by_ref,
|
|
clippy::op_ref,
|
|
clippy::redundant_closure,
|
|
clippy::unnecessary_lazy_evaluations,
|
|
clippy::if_then_some_else_none,
|
|
clippy::unnecessary_to_owned,
|
|
clippy::single_component_path_imports,
|
|
)]
|
|
//! Comprehensive edge case tests for ML model training loops
|
|
//!
|
|
//! This module tests edge cases across DQN, PPO, and Liquid Neural Networks:
|
|
//! - Training iteration with NaN/Inf loss values
|
|
//! - Gradient explosion/vanishing scenarios
|
|
//! - Convergence check boundary conditions
|
|
//! - Batch size edge cases (size=1, size=max)
|
|
//! - Learning rate edge cases (zero, negative, very large)
|
|
//! - Training loop early stopping conditions
|
|
//! - Model state save/load during training
|
|
//!
|
|
//! Target: +20% coverage increase for ml crate
|
|
|
|
#![allow(unused_crate_dependencies)]
|
|
|
|
use common::trading::MarketRegime;
|
|
use ml_core::common::action::{ExposureLevel, FactoredAction, OrderType, Urgency};
|
|
use ml::liquid::network::{LiquidNetwork, OutputLayerConfig};
|
|
use ml::liquid::training::{LiquidTrainer, LiquidTrainingConfig, TrainingBatch, TrainingSample};
|
|
use ml::liquid::{ActivationType, FixedPoint, LiquidNetworkConfig, NetworkType, PRECISION};
|
|
use ml::ppo::ppo::{PPOConfig, PPO};
|
|
use ml::ppo::trajectories::{Trajectory, TrajectoryBatch, TrajectoryStep};
|
|
|
|
// ============================================================================
|
|
// PPO Training Edge Cases
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
async fn test_ppo_training_with_empty_trajectory_batch() -> Result<(), Box<dyn std::error::Error>> {
|
|
let config = PPOConfig::default();
|
|
let mut ppo = PPO::new(config)?;
|
|
|
|
let mut empty_batch = TrajectoryBatch::from_trajectories(vec![], vec![], vec![]);
|
|
|
|
// Training with empty batch should handle gracefully
|
|
let result = ppo.update(&mut empty_batch);
|
|
|
|
// Empty batch should either succeed with zero updates or fail gracefully
|
|
match result {
|
|
Ok((policy_loss, value_loss)) => {
|
|
assert!(policy_loss.is_finite());
|
|
assert!(value_loss.is_finite());
|
|
},
|
|
Err(_) => {
|
|
// Empty batch error is acceptable
|
|
},
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_ppo_training_with_single_step_trajectory() -> Result<(), Box<dyn std::error::Error>> {
|
|
let config = PPOConfig {
|
|
state_dim: 2,
|
|
mini_batch_size: 1,
|
|
batch_size: 1,
|
|
..Default::default()
|
|
};
|
|
let _ppo = PPO::new(config)?;
|
|
|
|
// Create trajectory with single step
|
|
let mut trajectory = Trajectory::new();
|
|
trajectory.add_step(TrajectoryStep::new(
|
|
vec![1.0, 2.0],
|
|
FactoredAction::new(ExposureLevel::LongFull, OrderType::Market, Urgency::Normal),
|
|
-0.5,
|
|
10.0,
|
|
1.0,
|
|
true,
|
|
));
|
|
|
|
assert_eq!(trajectory.steps.len(), 1);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_ppo_training_with_all_terminal_states() -> Result<(), Box<dyn std::error::Error>> {
|
|
let config = PPOConfig {
|
|
state_dim: 2,
|
|
..Default::default()
|
|
};
|
|
let _ppo = PPO::new(config)?;
|
|
|
|
// Create trajectory with all terminal states
|
|
let mut trajectory = Trajectory::new();
|
|
for i in 0..10 {
|
|
trajectory.add_step(TrajectoryStep::new(
|
|
vec![i as f32, i as f32 + 1.0],
|
|
FactoredAction::new(ExposureLevel::Flat, OrderType::Market, Urgency::Normal),
|
|
-0.5,
|
|
5.0,
|
|
0.0,
|
|
true, // All steps are terminal
|
|
));
|
|
}
|
|
|
|
// Compute returns with all terminal states
|
|
let returns = trajectory.compute_returns(0.99);
|
|
assert_eq!(returns.len(), 10);
|
|
|
|
// All returns should be zero since all states are terminal
|
|
for ret in returns {
|
|
assert_eq!(ret, 0.0);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_ppo_clip_epsilon_boundary() -> Result<(), Box<dyn std::error::Error>> {
|
|
// Test with very small epsilon (should still clip)
|
|
let config_small = PPOConfig {
|
|
clip_epsilon: 0.001,
|
|
..Default::default()
|
|
};
|
|
let _ppo_small = PPO::new(config_small)?;
|
|
|
|
// Test with large epsilon (less aggressive clipping)
|
|
let config_large = PPOConfig {
|
|
clip_epsilon: 0.9,
|
|
..Default::default()
|
|
};
|
|
let _ppo_large = PPO::new(config_large)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_ppo_zero_entropy_coefficient() -> Result<(), Box<dyn std::error::Error>> {
|
|
let config = PPOConfig {
|
|
entropy_coeff: 0.0,
|
|
..Default::default()
|
|
};
|
|
let _ppo = PPO::new(config)?;
|
|
|
|
// PPO with zero entropy should work (deterministic policy)
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_ppo_high_entropy_coefficient() -> Result<(), Box<dyn std::error::Error>> {
|
|
let config = PPOConfig {
|
|
entropy_coeff: 1.0, // Very high entropy = more exploration
|
|
..Default::default()
|
|
};
|
|
let _ppo = PPO::new(config)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_ppo_training_epochs_boundary() -> Result<(), Box<dyn std::error::Error>> {
|
|
// Test with 1 epoch
|
|
let config_single = PPOConfig {
|
|
num_epochs: 1,
|
|
..Default::default()
|
|
};
|
|
let _ppo_single = PPO::new(config_single)?;
|
|
|
|
// Test with many epochs
|
|
let config_many = PPOConfig {
|
|
num_epochs: 100,
|
|
..Default::default()
|
|
};
|
|
let _ppo_many = PPO::new(config_many)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// ============================================================================
|
|
// Liquid Neural Network Training Edge Cases
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
async fn test_liquid_training_with_nan_loss() -> Result<(), Box<dyn std::error::Error>> {
|
|
let network_config = LiquidNetworkConfig {
|
|
network_type: NetworkType::LTC,
|
|
input_size: 2,
|
|
output_size: 1,
|
|
layer_configs: vec![],
|
|
output_layer: OutputLayerConfig {
|
|
use_linear_output: true,
|
|
output_activation: Some(ActivationType::Linear),
|
|
dropout_rate: None,
|
|
},
|
|
default_dt: FixedPoint::from_f64(0.01),
|
|
market_regime_adaptation: false,
|
|
};
|
|
let mut network = LiquidNetwork::new(network_config)?;
|
|
|
|
let training_config = LiquidTrainingConfig {
|
|
batch_size: 2,
|
|
max_epochs: 5,
|
|
..Default::default()
|
|
};
|
|
let mut trainer = LiquidTrainer::new(training_config);
|
|
|
|
// Create batch with extreme values that could cause NaN
|
|
let inputs = vec![
|
|
vec![FixedPoint(i64::MAX / 2), FixedPoint(i64::MAX / 2)],
|
|
vec![FixedPoint(i64::MIN / 2), FixedPoint(i64::MIN / 2)],
|
|
];
|
|
let targets = vec![vec![FixedPoint(PRECISION)], vec![FixedPoint(PRECISION / 2)]];
|
|
|
|
let batch = TrainingBatch::from_arrays(&inputs, &targets)?;
|
|
let training_data = vec![batch];
|
|
|
|
// Training should handle extreme values gracefully
|
|
let result = trainer.train(&mut network, &training_data, None);
|
|
|
|
// Either succeeds or fails with TrainingError (not panic)
|
|
match result {
|
|
Ok(_) => {},
|
|
Err(ml::liquid::LiquidError::TrainingError(_)) => {},
|
|
Err(e) => panic!("Unexpected error type: {:?}", e),
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_liquid_training_with_zero_learning_rate() -> Result<(), Box<dyn std::error::Error>> {
|
|
let network_config = LiquidNetworkConfig {
|
|
network_type: NetworkType::LTC,
|
|
input_size: 2,
|
|
output_size: 1,
|
|
layer_configs: vec![],
|
|
output_layer: OutputLayerConfig {
|
|
use_linear_output: true,
|
|
output_activation: Some(ActivationType::Linear),
|
|
dropout_rate: None,
|
|
},
|
|
default_dt: FixedPoint::from_f64(0.01),
|
|
market_regime_adaptation: false,
|
|
};
|
|
let mut network = LiquidNetwork::new(network_config)?;
|
|
|
|
let training_config = LiquidTrainingConfig {
|
|
learning_rate: FixedPoint(0), // Zero learning rate
|
|
batch_size: 2,
|
|
max_epochs: 3,
|
|
..Default::default()
|
|
};
|
|
let mut trainer = LiquidTrainer::new(training_config);
|
|
|
|
let inputs = vec![
|
|
vec![FixedPoint(PRECISION / 2), FixedPoint(PRECISION / 4)],
|
|
vec![FixedPoint(PRECISION / 3), FixedPoint(PRECISION / 5)],
|
|
];
|
|
let targets = vec![vec![FixedPoint(PRECISION)], vec![FixedPoint(PRECISION / 2)]];
|
|
|
|
let batch = TrainingBatch::from_arrays(&inputs, &targets)?;
|
|
let training_data = vec![batch];
|
|
|
|
// Training with zero learning rate should succeed but not learn
|
|
trainer.train(&mut network, &training_data, None)?;
|
|
|
|
// Learning rate should remain zero
|
|
assert_eq!(trainer.get_current_learning_rate(), 0.0);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_liquid_training_early_stopping() -> Result<(), Box<dyn std::error::Error>> {
|
|
let network_config = LiquidNetworkConfig {
|
|
network_type: NetworkType::LTC,
|
|
input_size: 2,
|
|
output_size: 1,
|
|
layer_configs: vec![],
|
|
output_layer: OutputLayerConfig {
|
|
use_linear_output: true,
|
|
output_activation: Some(ActivationType::Linear),
|
|
dropout_rate: None,
|
|
},
|
|
default_dt: FixedPoint::from_f64(0.01),
|
|
market_regime_adaptation: false,
|
|
};
|
|
let mut network = LiquidNetwork::new(network_config)?;
|
|
|
|
let training_config = LiquidTrainingConfig {
|
|
batch_size: 2,
|
|
max_epochs: 100,
|
|
early_stopping_patience: 3, // Stop if no improvement for 3 epochs
|
|
..Default::default()
|
|
};
|
|
let mut trainer = LiquidTrainer::new(training_config);
|
|
|
|
// Training data
|
|
let train_inputs = vec![
|
|
vec![FixedPoint(PRECISION / 2), FixedPoint(PRECISION / 4)],
|
|
vec![FixedPoint(PRECISION / 3), FixedPoint(PRECISION / 5)],
|
|
];
|
|
let train_targets = vec![vec![FixedPoint(PRECISION)], vec![FixedPoint(PRECISION / 2)]];
|
|
let train_batch = TrainingBatch::from_arrays(&train_inputs, &train_targets)?;
|
|
|
|
// Validation data
|
|
let val_inputs = vec![vec![FixedPoint(PRECISION / 6), FixedPoint(PRECISION / 7)]];
|
|
let val_targets = vec![vec![FixedPoint(PRECISION / 3)]];
|
|
let val_batch = TrainingBatch::from_arrays(&val_inputs, &val_targets)?;
|
|
|
|
let training_data = vec![train_batch];
|
|
let validation_data = vec![val_batch];
|
|
|
|
trainer.train(&mut network, &training_data, Some(&validation_data))?;
|
|
|
|
// Training should have stopped early (less than max epochs)
|
|
let history = trainer.get_training_history();
|
|
assert!(
|
|
history.len() < 100,
|
|
"Early stopping should trigger before max epochs"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_liquid_training_batch_size_one() -> Result<(), Box<dyn std::error::Error>> {
|
|
let network_config = LiquidNetworkConfig {
|
|
network_type: NetworkType::LTC,
|
|
input_size: 2,
|
|
output_size: 1,
|
|
layer_configs: vec![],
|
|
output_layer: OutputLayerConfig {
|
|
use_linear_output: true,
|
|
output_activation: Some(ActivationType::Linear),
|
|
dropout_rate: None,
|
|
},
|
|
default_dt: FixedPoint::from_f64(0.01),
|
|
market_regime_adaptation: false,
|
|
};
|
|
let mut network = LiquidNetwork::new(network_config)?;
|
|
|
|
let training_config = LiquidTrainingConfig {
|
|
batch_size: 1, // Single sample per batch
|
|
max_epochs: 5,
|
|
..Default::default()
|
|
};
|
|
let mut trainer = LiquidTrainer::new(training_config);
|
|
|
|
let inputs = vec![vec![FixedPoint(PRECISION / 2), FixedPoint(PRECISION / 4)]];
|
|
let targets = vec![vec![FixedPoint(PRECISION)]];
|
|
|
|
let batch = TrainingBatch::from_arrays(&inputs, &targets)?;
|
|
let training_data = vec![batch];
|
|
|
|
// Training with batch size 1 should succeed
|
|
trainer.train(&mut network, &training_data, None)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_liquid_training_gradient_clipping() -> Result<(), Box<dyn std::error::Error>> {
|
|
let network_config = LiquidNetworkConfig {
|
|
network_type: NetworkType::LTC,
|
|
input_size: 2,
|
|
output_size: 1,
|
|
layer_configs: vec![],
|
|
output_layer: OutputLayerConfig {
|
|
use_linear_output: true,
|
|
output_activation: Some(ActivationType::Linear),
|
|
dropout_rate: None,
|
|
},
|
|
default_dt: FixedPoint::from_f64(0.01),
|
|
market_regime_adaptation: false,
|
|
};
|
|
let mut network = LiquidNetwork::new(network_config)?;
|
|
|
|
let training_config = LiquidTrainingConfig {
|
|
gradient_clip_threshold: FixedPoint(PRECISION / 10), // Small threshold = aggressive clipping
|
|
batch_size: 2,
|
|
max_epochs: 3,
|
|
..Default::default()
|
|
};
|
|
let mut trainer = LiquidTrainer::new(training_config);
|
|
|
|
let inputs = vec![
|
|
vec![FixedPoint(PRECISION), FixedPoint(PRECISION)],
|
|
vec![FixedPoint(-PRECISION), FixedPoint(-PRECISION)],
|
|
];
|
|
let targets = vec![vec![FixedPoint(PRECISION)], vec![FixedPoint(PRECISION / 2)]];
|
|
|
|
let batch = TrainingBatch::from_arrays(&inputs, &targets)?;
|
|
let training_data = vec![batch];
|
|
|
|
// Training should apply gradient clipping
|
|
trainer.train(&mut network, &training_data, None)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_liquid_training_adaptive_learning_rate() -> Result<(), Box<dyn std::error::Error>> {
|
|
let network_config = LiquidNetworkConfig {
|
|
network_type: NetworkType::LTC,
|
|
input_size: 2,
|
|
output_size: 1,
|
|
layer_configs: vec![],
|
|
output_layer: OutputLayerConfig {
|
|
use_linear_output: true,
|
|
output_activation: Some(ActivationType::Linear),
|
|
dropout_rate: None,
|
|
},
|
|
default_dt: FixedPoint::from_f64(0.01),
|
|
market_regime_adaptation: false,
|
|
};
|
|
let mut network = LiquidNetwork::new(network_config)?;
|
|
|
|
let training_config = LiquidTrainingConfig {
|
|
learning_rate: FixedPoint(PRECISION / 100), // 0.01
|
|
adaptive_learning_rate: true,
|
|
batch_size: 2,
|
|
max_epochs: 50,
|
|
..Default::default()
|
|
};
|
|
let mut trainer = LiquidTrainer::new(training_config);
|
|
|
|
let initial_lr = trainer.get_current_learning_rate();
|
|
|
|
let inputs = vec![
|
|
vec![FixedPoint(PRECISION / 2), FixedPoint(PRECISION / 4)],
|
|
vec![FixedPoint(PRECISION / 3), FixedPoint(PRECISION / 5)],
|
|
];
|
|
let targets = vec![vec![FixedPoint(PRECISION)], vec![FixedPoint(PRECISION / 2)]];
|
|
|
|
let batch = TrainingBatch::from_arrays(&inputs, &targets)?;
|
|
let training_data = vec![batch];
|
|
|
|
trainer.train(&mut network, &training_data, None)?;
|
|
|
|
let final_lr = trainer.get_current_learning_rate();
|
|
|
|
// Learning rate should have decayed
|
|
assert!(final_lr < initial_lr, "Adaptive learning rate should decay");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_liquid_training_market_regime_adaptation() -> Result<(), Box<dyn std::error::Error>> {
|
|
let network_config = LiquidNetworkConfig {
|
|
network_type: NetworkType::LTC,
|
|
input_size: 2,
|
|
output_size: 1,
|
|
layer_configs: vec![],
|
|
output_layer: OutputLayerConfig {
|
|
use_linear_output: true,
|
|
output_activation: Some(ActivationType::Linear),
|
|
dropout_rate: None,
|
|
},
|
|
default_dt: FixedPoint::from_f64(0.01),
|
|
market_regime_adaptation: false,
|
|
};
|
|
let mut network = LiquidNetwork::new(network_config)?;
|
|
|
|
let training_config = LiquidTrainingConfig {
|
|
market_regime_adaptation: true,
|
|
batch_size: 2,
|
|
max_epochs: 5,
|
|
..Default::default()
|
|
};
|
|
let mut trainer = LiquidTrainer::new(training_config);
|
|
|
|
// Create samples with different market regimes
|
|
let sample1 = TrainingSample {
|
|
input: vec![FixedPoint(PRECISION / 2), FixedPoint(PRECISION / 4)],
|
|
target: vec![FixedPoint(PRECISION)],
|
|
timestamp: Some(1000),
|
|
market_regime: Some(MarketRegime::Trending),
|
|
volatility: Some(FixedPoint(PRECISION * 2)), // High volatility
|
|
};
|
|
|
|
let sample2 = TrainingSample {
|
|
input: vec![FixedPoint(PRECISION / 3), FixedPoint(PRECISION / 5)],
|
|
target: vec![FixedPoint(PRECISION / 2)],
|
|
timestamp: Some(2000),
|
|
market_regime: Some(MarketRegime::Sideways),
|
|
volatility: Some(FixedPoint(PRECISION / 10)), // Low volatility
|
|
};
|
|
|
|
let batch = TrainingBatch::new(vec![sample1, sample2]);
|
|
let training_data = vec![batch];
|
|
|
|
// Training should adapt to market regimes
|
|
trainer.train(&mut network, &training_data, None)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_liquid_training_l2_regularization() -> Result<(), Box<dyn std::error::Error>> {
|
|
let network_config = LiquidNetworkConfig {
|
|
network_type: NetworkType::LTC,
|
|
input_size: 2,
|
|
output_size: 1,
|
|
layer_configs: vec![],
|
|
output_layer: OutputLayerConfig {
|
|
use_linear_output: true,
|
|
output_activation: Some(ActivationType::Linear),
|
|
dropout_rate: None,
|
|
},
|
|
default_dt: FixedPoint::from_f64(0.01),
|
|
market_regime_adaptation: false,
|
|
};
|
|
let mut network = LiquidNetwork::new(network_config)?;
|
|
|
|
let training_config = LiquidTrainingConfig {
|
|
l2_regularization: FixedPoint(PRECISION / 100), // 0.01 L2 penalty
|
|
batch_size: 2,
|
|
max_epochs: 5,
|
|
..Default::default()
|
|
};
|
|
let mut trainer = LiquidTrainer::new(training_config);
|
|
|
|
let inputs = vec![
|
|
vec![FixedPoint(PRECISION / 2), FixedPoint(PRECISION / 4)],
|
|
vec![FixedPoint(PRECISION / 3), FixedPoint(PRECISION / 5)],
|
|
];
|
|
let targets = vec![vec![FixedPoint(PRECISION)], vec![FixedPoint(PRECISION / 2)]];
|
|
|
|
let batch = TrainingBatch::from_arrays(&inputs, &targets)?;
|
|
let training_data = vec![batch];
|
|
|
|
// Training with L2 regularization should succeed
|
|
trainer.train(&mut network, &training_data, None)?;
|
|
|
|
Ok(())
|
|
}
|
|
|