pub struct Tensor { /* private fields */ }Expand description
High-performance multi-dimensional tensor with automatic differentiation support
The core data structure for machine learning operations, designed for maximum performance with zero-cost abstractions. Supports arbitrary dimensionality, SIMD optimization, gradient tracking, device placement, and natural mathematical expressions through operator overloading.
§Key Features
- Raw Pointer Storage: Zero-overhead memory access for maximum performance
- SIMD Optimization: AVX2 alignment and vectorized operations
- Memory Efficiency: Optimized alignment strategies for different tensor sizes
- gradtrack Integration: Built-in gradient tracking and computation
- Device Support: CPU and future CUDA device placement
- View Tensors: Zero-copy tensor views with shared memory management
- Thread Safety: Send + Sync implementation for concurrent usage
- Operator Overloading: Natural mathematical expressions (+, -, *, /, +=, -=, *=, /=)
§Memory Layout
Tensors use row-major memory layout with size-dependent alignment:
- Small tensors (≤8 elements): 16-byte SSE alignment
- Medium tensors (8-1024 elements): 32-byte AVX2 alignment
- Large tensors (>1024 elements): 64-byte cache-line alignment
§Performance Characteristics
- Memory Overhead: ~64 bytes per tensor (excluding data)
- SIMD Ready: Properly aligned for vectorized operations
- Cache Friendly: Optimized memory layout for CPU cache hierarchies
- Zero-Cost Views: View tensors share memory without copying
- Thread Safe: Atomic ID generation and lock-free operations
- Operator Performance: Zero-cost operator overloading for mathematical expressions
§Safety
This struct uses unsafe code for performance. The following invariants must be maintained:
datamust be valid forshape.sizeelementsdatamust be properly aligned forf32datamust not be aliased while the tensor existsshape.sizemust match the actual allocated memoryallocation_ownermust be valid if present
§Examples
§Basic Tensor Operations
use train_station::Tensor;
// Create tensors with different configurations
let tensor = Tensor::new(vec![2, 3]);
let tensor_with_grad = Tensor::ones(vec![10, 10]).with_requires_grad();
// Access tensor properties
assert_eq!(tensor.size(), 6);
assert_eq!(tensor.shape().dims, vec![2, 3]);
assert!(tensor.is_contiguous());§Operator Overloading
use train_station::Tensor;
// Create tensors for operations
let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
// Tensor operations with operators
let result = a.clone() + b.clone(); // Tensor addition
let result = a.clone() * b.clone(); // Element-wise multiplication
let result = a.clone() - b.clone(); // Tensor subtraction
let result = a.clone() / b.clone(); // Element-wise division
// Scalar operations
let result = a.clone() + 5.0; // Tensor + scalar
let result = 5.0 + a.clone(); // Scalar + tensor
let result = a.clone() * 3.0; // Tensor * scalar
let result = 3.0 * a.clone(); // Scalar * tensor
// Compound expressions
let result = (a.clone() + b.clone()) * 2.0 - 1.0; // Complex mathematical expressions
// Assignment operators
let mut c = a.clone();
c += b.clone(); // In-place addition
c *= 2.0; // In-place scalar multiplication
// Negation
let result = -a; // Negate all elements§Thread Safety
This type is Send + Sync and can be safely shared between threads.
All operations are thread-safe through atomic ID generation and
thread-local gradtrack storage.
Implementations§
Source§impl Tensor
impl Tensor
Sourcepub fn new(shape_dims: Vec<usize>) -> Self
pub fn new(shape_dims: Vec<usize>) -> Self
Creates a new tensor with the specified shape and optimized memory layout
Allocates memory with size-dependent alignment for optimal performance:
- Small tensors (≤8 elements): 16-byte SSE alignment
- Medium tensors (8-1024 elements): 32-byte AVX2 alignment
- Large tensors (>1024 elements): 64-byte cache-line alignment
§Arguments
shape_dims- Vector of dimension sizes defining the tensor shape
§Returns
A new tensor with uninitialized data. The data must be initialized before use to avoid undefined behavior.
§Performance
- Memory Allocation: Single allocation with optimized alignment
- SIMD Ready: Properly aligned for vectorized operations
- Cache Friendly: Optimized for CPU cache hierarchies
- Thread Safe: Atomic ID generation for gradtrack tracking
§Safety
The returned tensor contains uninitialized memory. You must initialize the data before performing any operations that read from it.
§Examples
use train_station::Tensor;
// Create tensors of different sizes
let small_tensor = Tensor::new(vec![2, 3]); // 16-byte alignment
let medium_tensor = Tensor::new(vec![32, 32]); // 32-byte alignment
let large_tensor = Tensor::new(vec![1000, 1000]); // 64-byte alignment
// Initialize data before use
let mut tensor = Tensor::new(vec![2, 3]);
tensor.fill(0.0); // Initialize with zerosExamples found in repository?
42fn demonstrate_tensor_creation() {
43 println!("--- Tensor Creation ---");
44
45 // Create tensors with different initializations
46 let zeros = Tensor::zeros(vec![2, 3]);
47 println!(
48 "Zeros tensor: shape {:?}, data: {:?}",
49 zeros.shape().dims,
50 zeros.data()
51 );
52
53 let ones = Tensor::ones(vec![3, 2]);
54 println!(
55 "Ones tensor: shape {:?}, data: {:?}",
56 ones.shape().dims,
57 ones.data()
58 );
59
60 // Create tensor from slice
61 let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
62 let from_slice = Tensor::from_slice(&data, vec![2, 3]).unwrap();
63 println!(
64 "From slice: shape {:?}, data: {:?}",
65 from_slice.shape().dims,
66 from_slice.data()
67 );
68
69 // Create tensor with specific value
70 let mut filled = Tensor::new(vec![2, 2]);
71 {
72 let data = filled.data_mut();
73 for value in data.iter_mut() {
74 *value = 42.0;
75 }
76 }
77 println!("Filled with 42: {:?}", filled.data());
78
79 // Create tensor with random data
80 let random = Tensor::randn(vec![2, 2], Some(42));
81 println!(
82 "Random tensor: shape {:?}, data: {:?}",
83 random.shape().dims,
84 random.data()
85 );
86}Sourcepub fn shape(&self) -> &Shape
pub fn shape(&self) -> &Shape
Returns the shape and dimensional information of the tensor
Provides access to the tensor’s dimensions, size, strides, and memory layout information. This is used for shape validation, memory access calculations, and optimization decisions.
§Returns
Reference to the tensor’s shape information containing dimensions, size, strides, and memory layout type.
§Performance
- Time Complexity: O(1) - direct field access
- Memory: No allocation - returns reference to existing data
§Examples
use train_station::Tensor;
let tensor = Tensor::new(vec![2, 3, 4]);
let shape = tensor.shape();
assert_eq!(shape.dims, vec![2, 3, 4]);
assert_eq!(shape.size, 24);
assert_eq!(shape.rank(), 3);Examples found in repository?
147fn demonstrate_layer_creation() {
148 println!("--- Layer Creation ---");
149
150 let layer = LinearLayer::new(3, 2, Some(42));
151
152 println!("Created linear layer:");
153 println!(" Input size: {}", layer.input_size);
154 println!(" Output size: {}", layer.output_size);
155 println!(" Parameter count: {}", layer.parameter_count());
156 println!(" Weight shape: {:?}", layer.weight.shape().dims);
157 println!(" Bias shape: {:?}", layer.bias.shape().dims);
158 println!(" Weight requires grad: {}", layer.weight.requires_grad());
159 println!(" Bias requires grad: {}", layer.bias.requires_grad());
160}
161
162/// Demonstrate forward pass with gradient tracking
163fn demonstrate_forward_pass() {
164 println!("\n--- Forward Pass (with gradients) ---");
165
166 let layer = LinearLayer::new(3, 2, Some(43));
167
168 // Single input
169 let input = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![1, 3]).unwrap();
170 let output = layer.forward(&input);
171
172 println!("Single input:");
173 println!(" Input: {:?}", input.data());
174 println!(" Output: {:?}", output.data());
175 println!(" Output requires grad: {}", output.requires_grad());
176
177 // Batch input
178 let batch_input = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
179 let batch_output = layer.forward(&batch_input);
180
181 println!("Batch input:");
182 println!(" Input shape: {:?}", batch_input.shape().dims);
183 println!(" Output shape: {:?}", batch_output.shape().dims);
184 println!(" Output requires grad: {}", batch_output.requires_grad());
185}
186
187/// Demonstrate forward pass without gradient tracking
188fn demonstrate_forward_pass_no_grad() {
189 println!("\n--- Forward Pass (no gradients) ---");
190
191 let layer = LinearLayer::new(3, 2, Some(44));
192
193 // Single input
194 let input = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![1, 3]).unwrap();
195 let output = layer.forward_no_grad(&input);
196
197 println!("Single input (no grad):");
198 println!(" Input: {:?}", input.data());
199 println!(" Output: {:?}", output.data());
200 println!(" Output requires grad: {}", output.requires_grad());
201
202 // Compare with grad version
203 let output_with_grad = layer.forward(&input);
204 println!("Comparison:");
205 println!(
206 " Same values: {}",
207 output.data() == output_with_grad.data()
208 );
209 println!(" No grad requires grad: {}", output.requires_grad());
210 println!(
211 " With grad requires grad: {}",
212 output_with_grad.requires_grad()
213 );
214}
215
216/// Demonstrate complete training loop
217fn demonstrate_training_loop() -> Result<(), Box<dyn std::error::Error>> {
218 println!("\n--- Training Loop ---");
219
220 // Create layer and training data
221 let mut layer = LinearLayer::new(2, 1, Some(45));
222
223 // Simple regression task: y = 2*x1 + 3*x2 + 1
224 let x_data = Tensor::from_slice(
225 &[
226 1.0, 1.0, // x1=1, x2=1 -> y=6
227 2.0, 1.0, // x1=2, x2=1 -> y=8
228 1.0, 2.0, // x1=1, x2=2 -> y=9
229 2.0, 2.0, // x1=2, x2=2 -> y=11
230 ],
231 vec![4, 2],
232 )
233 .unwrap();
234
235 let y_true = Tensor::from_slice(&[6.0, 8.0, 9.0, 11.0], vec![4, 1]).unwrap();
236
237 println!("Training data:");
238 println!(" X shape: {:?}", x_data.shape().dims);
239 println!(" Y shape: {:?}", y_true.shape().dims);
240 println!(" Target function: y = 2*x1 + 3*x2 + 1");
241
242 // Create optimizer
243 let config = AdamConfig {
244 learning_rate: 0.01,
245 beta1: 0.9,
246 beta2: 0.999,
247 eps: 1e-8,
248 weight_decay: 0.0,
249 amsgrad: false,
250 };
251
252 let mut optimizer = Adam::with_config(config);
253 let params = layer.parameters();
254 for param in ¶ms {
255 optimizer.add_parameter(param);
256 }
257
258 println!("Optimizer setup complete. Starting training...");
259
260 // Training loop
261 let num_epochs = 100;
262 let mut losses = Vec::new();
263
264 for epoch in 0..num_epochs {
265 // Forward pass
266 let y_pred = layer.forward(&x_data);
267
268 // Compute loss: MSE
269 let diff = y_pred.sub_tensor(&y_true);
270 let mut loss = diff.pow_scalar(2.0).mean();
271
272 // Backward pass
273 loss.backward(None);
274
275 // Optimizer step
276 let mut params = layer.parameters();
277 optimizer.step(&mut params);
278 optimizer.zero_grad(&mut params);
279
280 losses.push(loss.value());
281
282 // Print progress
283 if epoch % 20 == 0 || epoch == num_epochs - 1 {
284 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
285 }
286 }
287
288 // Evaluate final model
289 let final_predictions = layer.forward_no_grad(&x_data);
290
291 println!("\nFinal model evaluation:");
292 println!(" Learned weights: {:?}", layer.weight.data());
293 println!(" Learned bias: {:?}", layer.bias.data());
294 println!(" Target weights: [2.0, 3.0]");
295 println!(" Target bias: [1.0]");
296
297 println!(" Predictions vs True:");
298 for i in 0..4 {
299 let pred = final_predictions.data()[i];
300 let true_val = y_true.data()[i];
301 println!(
302 " Sample {}: pred={:.3}, true={:.1}, error={:.3}",
303 i + 1,
304 pred,
305 true_val,
306 (pred - true_val).abs()
307 );
308 }
309
310 // Training analysis
311 let initial_loss = losses[0];
312 let final_loss = losses[losses.len() - 1];
313 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
314
315 println!("\nTraining Analysis:");
316 println!(" Initial loss: {:.6}", initial_loss);
317 println!(" Final loss: {:.6}", final_loss);
318 println!(" Loss reduction: {:.1}%", loss_reduction);
319
320 Ok(())
321}
322
323/// Demonstrate single vs batch inference
324fn demonstrate_single_vs_batch_inference() {
325 println!("\n--- Single vs Batch Inference ---");
326
327 let layer = LinearLayer::new(4, 3, Some(46));
328
329 // Single inference
330 println!("Single inference:");
331 let single_input = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![1, 4]).unwrap();
332 let single_output = layer.forward_no_grad(&single_input);
333 println!(" Input shape: {:?}", single_input.shape().dims);
334 println!(" Output shape: {:?}", single_output.shape().dims);
335 println!(" Output: {:?}", single_output.data());
336
337 // Batch inference
338 println!("Batch inference:");
339 let batch_input = Tensor::from_slice(
340 &[
341 1.0, 2.0, 3.0, 4.0, // Sample 1
342 5.0, 6.0, 7.0, 8.0, // Sample 2
343 9.0, 10.0, 11.0, 12.0, // Sample 3
344 ],
345 vec![3, 4],
346 )
347 .unwrap();
348 let batch_output = layer.forward_no_grad(&batch_input);
349 println!(" Input shape: {:?}", batch_input.shape().dims);
350 println!(" Output shape: {:?}", batch_output.shape().dims);
351
352 // Verify batch consistency - first sample should match single inference
353 let _first_batch_sample = batch_output.view(vec![3, 3]); // Reshape to access first sample
354 let first_sample_data = &batch_output.data()[0..3]; // First 3 elements
355 let single_sample_data = single_output.data();
356
357 println!("Consistency check:");
358 println!(" Single output: {:?}", single_sample_data);
359 println!(" First batch sample: {:?}", first_sample_data);
360 println!(
361 " Match: {}",
362 single_sample_data
363 .iter()
364 .zip(first_sample_data.iter())
365 .all(|(a, b)| (a - b).abs() < 1e-6)
366 );
367}More examples
77fn demonstrate_basic_iteration() -> Result<(), Box<dyn std::error::Error>> {
78 println!("\n--- Basic Element Iteration ---");
79
80 // Create a simple tensor for demonstration
81 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
82 println!("Original tensor: {:?}", tensor.data());
83
84 // Basic iteration with for loop
85 println!("\nBasic iteration with for loop:");
86 for (i, element) in tensor.iter().enumerate() {
87 println!(
88 " Element {}: value = {:.1}, shape = {:?}",
89 i,
90 element.value(),
91 element.shape().dims
92 );
93 }
94
95 // Element-wise transformation
96 println!("\nElement-wise transformation (2x + 1):");
97 let transformed: Tensor = tensor
98 .iter()
99 .map(|elem| elem.mul_scalar(2.0).add_scalar(1.0))
100 .collect();
101 println!(" Result: {:?}", transformed.data());
102
103 // Filtering elements
104 println!("\nFiltering elements (values > 3.0):");
105 let filtered: Tensor = tensor.iter().filter(|elem| elem.value() > 3.0).collect();
106 println!(" Filtered: {:?}", filtered.data());
107
108 Ok(())
109}158fn demonstrate_broadcasting() {
159 println!("\n--- Broadcasting ---");
160
161 // 2D tensor
162 let tensor_2d = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
163 println!(
164 "2D tensor: shape {:?}, data: {:?}",
165 tensor_2d.shape().dims,
166 tensor_2d.data()
167 );
168
169 // 1D tensor (will be broadcasted)
170 let tensor_1d = Tensor::from_slice(&[10.0, 20.0], vec![2]).unwrap();
171 println!(
172 "1D tensor: shape {:?}, data: {:?}",
173 tensor_1d.shape().dims,
174 tensor_1d.data()
175 );
176
177 // Broadcasting addition
178 let broadcast_sum = &tensor_2d + &tensor_1d;
179 println!(
180 "Broadcast sum: shape {:?}, data: {:?}",
181 broadcast_sum.shape().dims,
182 broadcast_sum.data()
183 );
184
185 // Broadcasting multiplication
186 let broadcast_mul = &tensor_2d * &tensor_1d;
187 println!(
188 "Broadcast multiplication: shape {:?}, data: {:?}",
189 broadcast_mul.shape().dims,
190 broadcast_mul.data()
191 );
192
193 // Broadcasting with scalar
194 let broadcast_scalar = &tensor_2d + 100.0;
195 println!(
196 "Broadcast scalar: shape {:?}, data: {:?}",
197 broadcast_scalar.shape().dims,
198 broadcast_scalar.data()
199 );
200}42fn demonstrate_tensor_creation() {
43 println!("--- Tensor Creation ---");
44
45 // Create tensors with different initializations
46 let zeros = Tensor::zeros(vec![2, 3]);
47 println!(
48 "Zeros tensor: shape {:?}, data: {:?}",
49 zeros.shape().dims,
50 zeros.data()
51 );
52
53 let ones = Tensor::ones(vec![3, 2]);
54 println!(
55 "Ones tensor: shape {:?}, data: {:?}",
56 ones.shape().dims,
57 ones.data()
58 );
59
60 // Create tensor from slice
61 let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
62 let from_slice = Tensor::from_slice(&data, vec![2, 3]).unwrap();
63 println!(
64 "From slice: shape {:?}, data: {:?}",
65 from_slice.shape().dims,
66 from_slice.data()
67 );
68
69 // Create tensor with specific value
70 let mut filled = Tensor::new(vec![2, 2]);
71 {
72 let data = filled.data_mut();
73 for value in data.iter_mut() {
74 *value = 42.0;
75 }
76 }
77 println!("Filled with 42: {:?}", filled.data());
78
79 // Create tensor with random data
80 let random = Tensor::randn(vec![2, 2], Some(42));
81 println!(
82 "Random tensor: shape {:?}, data: {:?}",
83 random.shape().dims,
84 random.data()
85 );
86}
87
88/// Demonstrate basic arithmetic operations
89fn demonstrate_basic_operations() {
90 println!("\n--- Basic Operations ---");
91
92 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
93 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
94
95 // Addition
96 let sum = a.add_tensor(&b);
97 println!("A + B: {:?}", sum.data());
98
99 // Subtraction
100 let diff = a.sub_tensor(&b);
101 println!("A - B: {:?}", diff.data());
102
103 // Multiplication
104 let product = a.mul_tensor(&b);
105 println!("A * B: {:?}", product.data());
106
107 // Division
108 let quotient = a.div_tensor(&b);
109 println!("A / B: {:?}", quotient.data());
110
111 // Scalar operations
112 let scalar_add = a.add_scalar(5.0);
113 println!("A + 5.0: {:?}", scalar_add.data());
114
115 let scalar_mul = a.mul_scalar(2.0);
116 println!("A * 2.0: {:?}", scalar_mul.data());
117}
118
119/// Demonstrate shape manipulation operations
120fn demonstrate_shape_operations() {
121 println!("\n--- Shape Operations ---");
122
123 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
124 println!(
125 "Original: shape {:?}, data: {:?}",
126 tensor.shape().dims,
127 tensor.data()
128 );
129
130 // Reshape (view)
131 let reshaped = tensor.view(vec![3, 2]);
132 println!(
133 "Reshaped to [3, 2]: shape {:?}, data: {:?}",
134 reshaped.shape().dims,
135 reshaped.data()
136 );
137
138 // Create a different shaped tensor for demonstration
139 let tensor_2d = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
140 println!(
141 "2D tensor: shape {:?}, data: {:?}",
142 tensor_2d.shape().dims,
143 tensor_2d.data()
144 );
145
146 // Create a 1D tensor
147 let tensor_1d = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4]).unwrap();
148 println!(
149 "1D tensor: shape {:?}, data: {:?}",
150 tensor_1d.shape().dims,
151 tensor_1d.data()
152 );
153}
154
155/// Demonstrate data access patterns
156fn demonstrate_data_access() {
157 println!("\n--- Data Access ---");
158
159 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
160
161 // Access individual elements
162 println!("Element [0, 0]: {}", tensor.get(&[0, 0]));
163 println!("Element [0, 1]: {}", tensor.get(&[0, 1]));
164 println!("Element [1, 0]: {}", tensor.get(&[1, 0]));
165 println!("Element [1, 1]: {}", tensor.get(&[1, 1]));
166
167 // Access data as slice
168 let data = tensor.data();
169 println!("Data as slice: {:?}", data);
170
171 // Iterate over elements
172 println!("Elements:");
173 for (i, &value) in data.iter().enumerate() {
174 println!(" [{}]: {}", i, value);
175 }
176}
177
178/// Demonstrate utility functions
179fn demonstrate_utility_functions() {
180 println!("\n--- Utility Functions ---");
181
182 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
183
184 // Basic properties
185 println!("Shape: {:?}", tensor.shape().dims);
186 println!("Size: {}", tensor.size());
187 println!("Is contiguous: {}", tensor.is_contiguous());
188 println!("Device: {:?}", tensor.device());
189
190 // Mathematical operations
191 let sum = tensor.sum();
192 println!("Sum: {}", sum.value());
193
194 let mean = tensor.mean();
195 println!("Mean: {}", mean.value());
196
197 let norm = tensor.norm();
198 println!("Norm: {}", norm.value());
199
200 // Device placement
201 let cpu_tensor = Tensor::zeros_on_device(vec![3, 3], train_station::Device::cpu());
202 println!(
203 "CPU tensor: shape {:?}, device: {:?}",
204 cpu_tensor.shape().dims,
205 cpu_tensor.device()
206 );
207}47fn demonstrate_basic_optimizer_setup() {
48 println!("--- Basic Optimizer Setup ---");
49
50 // Create parameters that require gradients
51 let weight = Tensor::randn(vec![3, 2], Some(42)).with_requires_grad();
52 let bias = Tensor::zeros(vec![2]).with_requires_grad();
53
54 println!("Created parameters:");
55 println!(
56 " Weight: shape {:?}, requires_grad: {}",
57 weight.shape().dims,
58 weight.requires_grad()
59 );
60 println!(
61 " Bias: shape {:?}, requires_grad: {}",
62 bias.shape().dims,
63 bias.requires_grad()
64 );
65
66 // Create Adam optimizer with default configuration
67 let mut optimizer = Adam::new();
68 println!(
69 "Created Adam optimizer with learning rate: {}",
70 optimizer.learning_rate()
71 );
72
73 // Add parameters to optimizer
74 optimizer.add_parameter(&weight);
75 optimizer.add_parameter(&bias);
76 println!(
77 "Added {} parameters to optimizer",
78 optimizer.parameter_count()
79 );
80
81 // Create optimizer with custom configuration
82 let config = AdamConfig {
83 learning_rate: 0.01,
84 beta1: 0.9,
85 beta2: 0.999,
86 eps: 1e-8,
87 weight_decay: 0.0,
88 amsgrad: false,
89 };
90
91 let mut custom_optimizer = Adam::with_config(config);
92 custom_optimizer.add_parameter(&weight);
93 custom_optimizer.add_parameter(&bias);
94
95 println!(
96 "Created custom optimizer with learning rate: {}",
97 custom_optimizer.learning_rate()
98 );
99
100 // Demonstrate parameter linking
101 println!("Parameter linking completed successfully");
102}339fn demonstrate_forward_pass() {
340 println!("\n--- Forward Pass ---");
341
342 let config = FeedForwardConfig {
343 input_size: 3,
344 hidden_sizes: vec![5, 3],
345 output_size: 2,
346 use_bias: true,
347 };
348 let network = FeedForwardNetwork::new(config, Some(43));
349
350 // Single input
351 let input = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![1, 3]).unwrap();
352 let output = network.forward(&input);
353
354 println!("Single input forward pass:");
355 println!(" Input shape: {:?}", input.shape().dims);
356 println!(" Output shape: {:?}", output.shape().dims);
357 println!(" Output: {:?}", output.data());
358 println!(" Output requires grad: {}", output.requires_grad());
359
360 // Batch input
361 let batch_input = Tensor::from_slice(
362 &[
363 1.0, 2.0, 3.0, // Sample 1
364 4.0, 5.0, 6.0, // Sample 2
365 7.0, 8.0, 9.0, // Sample 3
366 ],
367 vec![3, 3],
368 )
369 .unwrap();
370 let batch_output = network.forward(&batch_input);
371
372 println!("Batch input forward pass:");
373 println!(" Input shape: {:?}", batch_input.shape().dims);
374 println!(" Output shape: {:?}", batch_output.shape().dims);
375 println!(" Output requires grad: {}", batch_output.requires_grad());
376
377 // Compare with no-grad version
378 let output_no_grad = network.forward_no_grad(&input);
379 println!("No-grad comparison:");
380 println!(" Same values: {}", output.data() == output_no_grad.data());
381 println!(" With grad requires grad: {}", output.requires_grad());
382 println!(
383 " No grad requires grad: {}",
384 output_no_grad.requires_grad()
385 );
386}
387
388/// Demonstrate different configurable architectures
389fn demonstrate_configurable_architectures() {
390 println!("\n--- Configurable Architectures ---");
391
392 let architectures = vec![
393 ("Shallow", vec![8]),
394 ("Medium", vec![16, 8]),
395 ("Deep", vec![32, 16, 8, 4]),
396 ("Wide", vec![64, 32]),
397 ("Bottleneck", vec![16, 4, 16]),
398 ];
399
400 for (name, hidden_sizes) in architectures {
401 let config = FeedForwardConfig {
402 input_size: 10,
403 hidden_sizes,
404 output_size: 3,
405 use_bias: true,
406 };
407
408 let network = FeedForwardNetwork::new(config.clone(), Some(44));
409
410 // Test forward pass
411 let test_input = Tensor::randn(vec![5, 10], Some(45)); // Batch of 5
412 let output = network.forward_no_grad(&test_input);
413
414 println!("{} network:", name);
415 println!(" Architecture: 10 -> {:?} -> 3", config.hidden_sizes);
416 println!(" Parameters: {}", network.parameter_count());
417 println!(" Test output shape: {:?}", output.shape().dims);
418 println!(
419 " Output range: [{:.3}, {:.3}]",
420 output.data().iter().fold(f32::INFINITY, |a, &b| a.min(b)),
421 output
422 .data()
423 .iter()
424 .fold(f32::NEG_INFINITY, |a, &b| a.max(b))
425 );
426 }
427}
428
429/// Demonstrate basic training workflow
430fn demonstrate_training_workflow() -> Result<(), Box<dyn std::error::Error>> {
431 println!("\n--- Training Workflow ---");
432
433 // Create a simple classification network
434 let config = FeedForwardConfig {
435 input_size: 2,
436 hidden_sizes: vec![4, 3],
437 output_size: 1,
438 use_bias: true,
439 };
440 let mut network = FeedForwardNetwork::new(config, Some(46));
441
442 println!("Training network: 2 -> [4, 3] -> 1");
443
444 // Create simple binary classification data: XOR problem
445 let x_data = Tensor::from_slice(
446 &[
447 0.0, 0.0, // -> 0
448 0.0, 1.0, // -> 1
449 1.0, 0.0, // -> 1
450 1.0, 1.0, // -> 0
451 ],
452 vec![4, 2],
453 )
454 .unwrap();
455
456 let y_true = Tensor::from_slice(&[0.0, 1.0, 1.0, 0.0], vec![4, 1]).unwrap();
457
458 println!("Training on XOR problem:");
459 println!(" Input shape: {:?}", x_data.shape().dims);
460 println!(" Target shape: {:?}", y_true.shape().dims);
461
462 // Create optimizer
463 let mut optimizer = Adam::with_learning_rate(0.1);
464 let params = network.parameters();
465 for param in ¶ms {
466 optimizer.add_parameter(param);
467 }
468
469 // Training loop
470 let num_epochs = 50;
471 let mut losses = Vec::new();
472
473 for epoch in 0..num_epochs {
474 // Forward pass
475 let y_pred = network.forward(&x_data);
476
477 // Compute loss: MSE
478 let diff = y_pred.sub_tensor(&y_true);
479 let mut loss = diff.pow_scalar(2.0).mean();
480
481 // Backward pass
482 loss.backward(None);
483
484 // Optimizer step and zero grad
485 let mut params = network.parameters();
486 optimizer.step(&mut params);
487 optimizer.zero_grad(&mut params);
488
489 losses.push(loss.value());
490
491 // Print progress
492 if epoch % 10 == 0 || epoch == num_epochs - 1 {
493 println!("Epoch {:2}: Loss = {:.6}", epoch, loss.value());
494 }
495 }
496
497 // Test final model
498 let final_predictions = network.forward_no_grad(&x_data);
499 println!("\nFinal predictions vs targets:");
500 for i in 0..4 {
501 let pred = final_predictions.data()[i];
502 let target = y_true.data()[i];
503 let input_x = x_data.data()[i * 2];
504 let input_y = x_data.data()[i * 2 + 1];
505 println!(
506 " [{:.0}, {:.0}] -> pred: {:.3}, target: {:.0}, error: {:.3}",
507 input_x,
508 input_y,
509 pred,
510 target,
511 (pred - target).abs()
512 );
513 }
514
515 Ok(())
516}
517
518/// Demonstrate comprehensive training with 100+ steps
519fn demonstrate_comprehensive_training() -> Result<(), Box<dyn std::error::Error>> {
520 println!("\n--- Comprehensive Training (100+ Steps) ---");
521
522 // Create a regression network
523 let config = FeedForwardConfig {
524 input_size: 3,
525 hidden_sizes: vec![8, 6, 4],
526 output_size: 2,
527 use_bias: true,
528 };
529 let mut network = FeedForwardNetwork::new(config, Some(47));
530
531 println!("Network architecture: 3 -> [8, 6, 4] -> 2");
532 println!("Total parameters: {}", network.parameter_count());
533
534 // Create synthetic regression data
535 // Target function: [y1, y2] = [x1 + 2*x2 - x3, x1*x2 + x3]
536 let num_samples = 32;
537 let mut x_vec = Vec::new();
538 let mut y_vec = Vec::new();
539
540 for i in 0..num_samples {
541 let x1 = (i as f32 / num_samples as f32) * 2.0 - 1.0; // [-1, 1]
542 let x2 = ((i * 2) as f32 / num_samples as f32) * 2.0 - 1.0;
543 let x3 = ((i * 3) as f32 / num_samples as f32) * 2.0 - 1.0;
544
545 let y1 = x1 + 2.0 * x2 - x3;
546 let y2 = x1 * x2 + x3;
547
548 x_vec.extend_from_slice(&[x1, x2, x3]);
549 y_vec.extend_from_slice(&[y1, y2]);
550 }
551
552 let x_data = Tensor::from_slice(&x_vec, vec![num_samples, 3]).unwrap();
553 let y_true = Tensor::from_slice(&y_vec, vec![num_samples, 2]).unwrap();
554
555 println!("Training data:");
556 println!(" {} samples", num_samples);
557 println!(" Input shape: {:?}", x_data.shape().dims);
558 println!(" Target shape: {:?}", y_true.shape().dims);
559
560 // Create optimizer with learning rate scheduling
561 let mut optimizer = Adam::with_learning_rate(0.01);
562 let params = network.parameters();
563 for param in ¶ms {
564 optimizer.add_parameter(param);
565 }
566
567 // Comprehensive training loop (150 epochs)
568 let num_epochs = 150;
569 let mut losses = Vec::new();
570 let mut best_loss = f32::INFINITY;
571 let mut patience_counter = 0;
572 let patience = 20;
573
574 println!("Starting comprehensive training...");
575
576 for epoch in 0..num_epochs {
577 // Forward pass
578 let y_pred = network.forward(&x_data);
579
580 // Compute loss: MSE
581 let diff = y_pred.sub_tensor(&y_true);
582 let mut loss = diff.pow_scalar(2.0).mean();
583
584 // Backward pass
585 loss.backward(None);
586
587 // Optimizer step and zero grad
588 let mut params = network.parameters();
589 optimizer.step(&mut params);
590 optimizer.zero_grad(&mut params);
591
592 let current_loss = loss.value();
593 losses.push(current_loss);
594
595 // Learning rate scheduling
596 if epoch > 0 && epoch % 30 == 0 {
597 let new_lr = optimizer.learning_rate() * 0.8;
598 optimizer.set_learning_rate(new_lr);
599 println!(" Reduced learning rate to {:.4}", new_lr);
600 }
601
602 // Early stopping logic
603 if current_loss < best_loss {
604 best_loss = current_loss;
605 patience_counter = 0;
606 } else {
607 patience_counter += 1;
608 }
609
610 // Print progress
611 if epoch % 25 == 0 || epoch == num_epochs - 1 {
612 println!(
613 "Epoch {:3}: Loss = {:.6}, LR = {:.4}, Best = {:.6}",
614 epoch,
615 current_loss,
616 optimizer.learning_rate(),
617 best_loss
618 );
619 }
620
621 // Early stopping
622 if patience_counter >= patience && epoch > 50 {
623 println!("Early stopping at epoch {} (patience exceeded)", epoch);
624 break;
625 }
626 }
627
628 // Final evaluation
629 let final_predictions = network.forward_no_grad(&x_data);
630
631 // Compute final metrics
632 let final_loss = losses[losses.len() - 1];
633 let initial_loss = losses[0];
634 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
635
636 println!("\nTraining completed!");
637 println!(" Initial loss: {:.6}", initial_loss);
638 println!(" Final loss: {:.6}", final_loss);
639 println!(" Best loss: {:.6}", best_loss);
640 println!(" Loss reduction: {:.1}%", loss_reduction);
641 println!(" Final learning rate: {:.4}", optimizer.learning_rate());
642
643 // Sample predictions analysis
644 println!("\nSample predictions (first 5):");
645 for i in 0..5.min(num_samples) {
646 let pred1 = final_predictions.data()[i * 2];
647 let pred2 = final_predictions.data()[i * 2 + 1];
648 let true1 = y_true.data()[i * 2];
649 let true2 = y_true.data()[i * 2 + 1];
650
651 println!(
652 " Sample {}: pred=[{:.3}, {:.3}], true=[{:.3}, {:.3}], error=[{:.3}, {:.3}]",
653 i + 1,
654 pred1,
655 pred2,
656 true1,
657 true2,
658 (pred1 - true1).abs(),
659 (pred2 - true2).abs()
660 );
661 }
662
663 Ok(())
664}Sourcepub fn size(&self) -> usize
pub fn size(&self) -> usize
Returns the total number of elements in the tensor
Provides the total count of elements across all dimensions. This is used for memory allocation, iteration bounds, and performance optimization.
§Returns
Total number of elements as usize
§Performance
- Time Complexity: O(1) - direct field access
- Memory: No allocation - returns stored value
§Examples
use train_station::Tensor;
let tensor = Tensor::new(vec![2, 3, 4]);
assert_eq!(tensor.size(), 24); // 2 * 3 * 4
let scalar = Tensor::new(vec![1]);
assert_eq!(scalar.size(), 1);
let empty = Tensor::new(vec![0]);
assert_eq!(empty.size(), 0);Examples found in repository?
179fn demonstrate_utility_functions() {
180 println!("\n--- Utility Functions ---");
181
182 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
183
184 // Basic properties
185 println!("Shape: {:?}", tensor.shape().dims);
186 println!("Size: {}", tensor.size());
187 println!("Is contiguous: {}", tensor.is_contiguous());
188 println!("Device: {:?}", tensor.device());
189
190 // Mathematical operations
191 let sum = tensor.sum();
192 println!("Sum: {}", sum.value());
193
194 let mean = tensor.mean();
195 println!("Mean: {}", mean.value());
196
197 let norm = tensor.norm();
198 println!("Norm: {}", norm.value());
199
200 // Device placement
201 let cpu_tensor = Tensor::zeros_on_device(vec![3, 3], train_station::Device::cpu());
202 println!(
203 "CPU tensor: shape {:?}, device: {:?}",
204 cpu_tensor.shape().dims,
205 cpu_tensor.device()
206 );
207}More examples
135fn demonstrate_memory_optimization() -> Result<(), Box<dyn std::error::Error>> {
136 println!("\n--- Memory Optimization ---");
137
138 // Create a large tensor for memory testing
139 let size = 10000;
140 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
141 let tensor = Tensor::from_slice(&data, vec![size])?;
142
143 println!("Processing tensor of size: {}", size);
144
145 // Pattern 1: Streaming processing (process in chunks)
146 println!("\nPattern 1: Streaming Processing");
147 let chunk_size = 1000;
148 let start = Instant::now();
149
150 let mut streamed_result = Vec::new();
151 for chunk_start in (0..size).step_by(chunk_size) {
152 let chunk_end = (chunk_start + chunk_size).min(size);
153 let chunk: Tensor = tensor
154 .iter_range(chunk_start, chunk_end)
155 .map(|elem| elem.pow_scalar(2.0).sqrt())
156 .collect();
157 streamed_result.extend(chunk.data().iter().cloned());
158 }
159 let streamed_time = start.elapsed();
160
161 // Pattern 2: Full processing
162 let start = Instant::now();
163 let _full_result: Tensor = tensor
164 .iter()
165 .map(|elem| elem.pow_scalar(2.0).sqrt())
166 .collect();
167 let full_time = start.elapsed();
168
169 println!(" Streaming time: {:?}", streamed_time);
170 println!(" Full processing time: {:?}", full_time);
171 println!(
172 " Memory efficiency ratio: {:.2}x",
173 full_time.as_nanos() as f64 / streamed_time.as_nanos() as f64
174 );
175
176 // Pattern 3: Lazy evaluation with take
177 println!("\nPattern 2: Lazy Evaluation");
178 let start = Instant::now();
179 let lazy_result: Tensor = tensor
180 .iter()
181 .take(1000) // Only process first 1000 elements
182 .map(|elem| elem.pow_scalar(2.0).sqrt())
183 .collect();
184 let lazy_time = start.elapsed();
185
186 println!(" Lazy processing (1000 elements): {:?}", lazy_time);
187 println!(" Lazy result size: {}", lazy_result.size());
188
189 // Pattern 4: Memory-efficient filtering
190 println!("\nPattern 3: Memory-Efficient Filtering");
191 let start = Instant::now();
192 let filtered_result: Tensor = tensor
193 .iter()
194 .filter(|elem| elem.value() > size as f32 / 2.0) // Keep only large values
195 .map(|elem| elem.mul_scalar(2.0))
196 .collect();
197 let filtered_time = start.elapsed();
198
199 println!(" Filtered processing: {:?}", filtered_time);
200 println!(
201 " Filtered result size: {} (reduced from {})",
202 filtered_result.size(),
203 size
204 );
205
206 Ok(())
207}
208
209/// Demonstrate large-scale processing techniques
210///
211/// Shows how to efficiently process very large datasets using
212/// iterator patterns and optimization strategies.
213fn demonstrate_large_scale_processing() -> Result<(), Box<dyn std::error::Error>> {
214 println!("\n--- Large-Scale Processing ---");
215
216 // Simulate large dataset processing
217 let sizes = vec![10000, 50000, 100000];
218
219 for size in sizes {
220 println!("\nProcessing dataset of size: {}", size);
221
222 // Generate large dataset
223 let data: Vec<f32> = (0..size)
224 .map(|i| {
225 let x = i as f32 / size as f32;
226 x * x + 0.1 * (i % 10) as f32 // Quadratic with noise
227 })
228 .collect();
229
230 let tensor = Tensor::from_slice(&data, vec![size])?;
231
232 // Technique 1: Batch processing
233 let batch_size = 1000;
234 let start = Instant::now();
235
236 let mut batch_results = Vec::new();
237 for batch_start in (0..size).step_by(batch_size) {
238 let batch_end = (batch_start + batch_size).min(size);
239 let batch: Tensor = tensor
240 .iter_range(batch_start, batch_end)
241 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
242 .collect();
243 batch_results.push(batch);
244 }
245 let batch_time = start.elapsed();
246
247 // Technique 2: Parallel-like processing with stride
248 let start = Instant::now();
249 let stride = 4;
250 let strided_result: Tensor = tensor
251 .iter()
252 .enumerate()
253 .filter(|(i, _)| i % stride == 0)
254 .map(|(_, elem)| elem.pow_scalar(2.0).add_scalar(1.0))
255 .collect();
256 let strided_time = start.elapsed();
257
258 // Technique 3: Hierarchical processing
259 let start = Instant::now();
260 let coarse: Tensor = tensor
261 .iter()
262 .enumerate()
263 .filter(|(i, _)| i % 10 == 0) // Every 10th element
264 .map(|(_, elem)| elem.pow_scalar(2.0).add_scalar(1.0))
265 .collect();
266 let fine: Tensor = tensor
267 .iter()
268 .enumerate()
269 .filter(|(i, _)| i % 10 != 0) // Rest of elements
270 .map(|(_, elem)| elem.pow_scalar(1.5).add_scalar(0.5))
271 .collect();
272 let hierarchical_time = start.elapsed();
273
274 // Report performance
275 println!(" Batch processing: {:?}", batch_time);
276 println!(" Strided processing: {:?}", strided_time);
277 println!(" Hierarchical processing: {:?}", hierarchical_time);
278
279 // Memory usage analysis
280 let total_batches = (size + batch_size - 1) / batch_size;
281 println!(" Batch count: {}", total_batches);
282 println!(" Strided result size: {}", strided_result.size());
283 println!(
284 " Hierarchical: coarse={}, fine={}",
285 coarse.size(),
286 fine.size()
287 );
288 }
289
290 Ok(())
291}
292
293/// Demonstrate advanced optimization techniques
294///
295/// Shows sophisticated optimization strategies and techniques
296/// for maximizing performance in tensor iterator operations.
297fn demonstrate_optimization_techniques() -> Result<(), Box<dyn std::error::Error>> {
298 println!("\n--- Optimization Techniques ---");
299
300 let size = 50000;
301 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
302 let tensor = Tensor::from_slice(&data, vec![size])?;
303
304 println!("Optimizing processing for size: {}", size);
305
306 // Technique 1: Operation fusion
307 println!("\nTechnique 1: Operation Fusion");
308 let start = Instant::now();
309 let fused_result: Tensor = tensor
310 .iter()
311 .map(|elem| {
312 // Fuse multiple operations into single chain
313 elem.mul_scalar(2.0).add_scalar(1.0).pow_scalar(2.0).sqrt()
314 })
315 .collect();
316 let fused_time = start.elapsed();
317
318 // Technique 2: Conditional optimization
319 println!("\nTechnique 2: Conditional Optimization");
320 let start = Instant::now();
321 let conditional_result: Tensor = tensor
322 .iter()
323 .map(|elem| {
324 let val = elem.value();
325 if val < size as f32 / 2.0 {
326 elem.mul_scalar(2.0) // Simple operation for small values
327 } else {
328 elem.pow_scalar(2.0).sqrt() // Complex operation for large values
329 }
330 })
331 .collect();
332 let conditional_time = start.elapsed();
333
334 // Technique 3: Cache-friendly processing
335 println!("\nTechnique 3: Cache-Friendly Processing");
336 let start = Instant::now();
337 let cache_friendly_result: Tensor = tensor
338 .iter()
339 .take(1000) // Process in cache-friendly chunks
340 .map(|elem| elem.mul_scalar(2.0))
341 .collect();
342 let cache_friendly_time = start.elapsed();
343
344 // Technique 4: Memory pooling simulation
345 println!("\nTechnique 4: Memory Pooling Simulation");
346 let start = Instant::now();
347 let pooled_result: Tensor = tensor
348 .iter()
349 .enumerate()
350 .filter(|(i, _)| i % 100 == 0) // Process every 100th element
351 .map(|(_, elem)| elem.pow_scalar(2.0))
352 .collect();
353 let pooled_time = start.elapsed();
354
355 // Report optimization results
356 println!(" Fused operations: {:?}", fused_time);
357 println!(" Conditional optimization: {:?}", conditional_time);
358 println!(" Cache-friendly processing: {:?}", cache_friendly_time);
359 println!(" Memory pooling simulation: {:?}", pooled_time);
360
361 // Performance analysis
362 let fastest = fused_time
363 .min(conditional_time)
364 .min(cache_friendly_time)
365 .min(pooled_time);
366 println!(" Fastest technique: {:?}", fastest);
367
368 // Memory efficiency analysis
369 println!(" Fused result size: {}", fused_result.size());
370 println!(" Conditional result size: {}", conditional_result.size());
371 println!(
372 " Cache-friendly result size: {}",
373 cache_friendly_result.size()
374 );
375 println!(" Pooled result size: {}", pooled_result.size());
376
377 // Technique 5: Gradient optimization
378 println!("\nTechnique 5: Gradient Optimization");
379 let grad_tensor = tensor.with_requires_grad();
380 let start = Instant::now();
381
382 let grad_result: Tensor = grad_tensor
383 .iter()
384 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
385 .collect();
386
387 let mut loss = grad_result.sum();
388 loss.backward(None);
389 let grad_time = start.elapsed();
390
391 println!(" Gradient computation: {:?}", grad_time);
392 println!(
393 " Gradient tracking enabled: {}",
394 grad_result.requires_grad()
395 );
396
397 Ok(())
398}74fn demonstrate_data_pipeline() -> Result<(), Box<dyn std::error::Error>> {
75 println!("\n--- Data Processing Pipeline ---");
76
77 // Simulate raw sensor data with noise
78 let raw_data: Vec<f32> = (0..20)
79 .map(|i| {
80 let base = i as f32 * 0.5;
81 let noise = (i % 3) as f32 * 0.1;
82 base + noise
83 })
84 .collect();
85
86 let tensor = Tensor::from_slice(&raw_data, vec![20])?;
87 println!("Raw sensor data: {:?}", tensor.data());
88
89 // Multi-stage processing pipeline
90 println!("\nProcessing pipeline:");
91 println!("1. Normalize data (z-score)");
92 println!("2. Apply smoothing filter");
93 println!("3. Detect outliers");
94 println!("4. Apply feature scaling");
95
96 // Stage 1: Normalization
97 let mean = tensor.mean().value();
98 let std = tensor.std().value();
99 let normalized: Tensor = tensor
100 .iter()
101 .map(|elem| elem.sub_scalar(mean).div_scalar(std))
102 .collect();
103 println!(
104 " Normalized (mean={:.3}, std={:.3}): {:?}",
105 mean,
106 std,
107 normalized.data()
108 );
109
110 // Stage 2: Smoothing (simple moving average)
111 let smoothed: Tensor = normalized
112 .iter()
113 .enumerate()
114 .map(|(i, elem)| {
115 if i == 0 || i == normalized.size() - 1 {
116 elem.clone()
117 } else {
118 // Simple 3-point average
119 let prev = normalized.element_view(i - 1);
120 let next = normalized.element_view(i + 1);
121 elem.add_tensor(&prev).add_tensor(&next).div_scalar(3.0)
122 }
123 })
124 .collect();
125 println!(" Smoothed: {:?}", smoothed.data());
126
127 // Stage 3: Outlier detection and removal
128 let outlier_threshold = 2.0;
129 let cleaned: Tensor = smoothed
130 .iter()
131 .filter(|elem| elem.value().abs() < outlier_threshold)
132 .collect();
133 println!(
134 " Outliers removed (threshold={}): {:?}",
135 outlier_threshold,
136 cleaned.data()
137 );
138
139 // Stage 4: Feature scaling to [0, 1] range
140 let min_val = cleaned
141 .iter()
142 .map(|e| e.value())
143 .fold(f32::INFINITY, f32::min);
144 let max_val = cleaned
145 .iter()
146 .map(|e| e.value())
147 .fold(f32::NEG_INFINITY, f32::max);
148 let scaled: Tensor = cleaned
149 .iter()
150 .map(|elem| elem.sub_scalar(min_val).div_scalar(max_val - min_val))
151 .collect();
152 println!(" Scaled to [0,1]: {:?}", scaled.data());
153
154 Ok(())
155}
156
157/// Demonstrate conditional processing patterns
158///
159/// Shows how to implement dynamic filtering and transformation
160/// based on data characteristics and conditions.
161fn demonstrate_conditional_processing() -> Result<(), Box<dyn std::error::Error>> {
162 println!("\n--- Conditional Processing ---");
163
164 // Create data with mixed characteristics
165 let data = vec![1.0, -2.0, 3.0, -4.0, 5.0, -6.0, 7.0, -8.0, 9.0, -10.0];
166 let tensor = Tensor::from_slice(&data, vec![10])?;
167 println!("Input data: {:?}", tensor.data());
168
169 // Conditional transformation based on sign
170 println!("\nConditional transformation (positive/negative handling):");
171 let processed: Tensor = tensor
172 .iter()
173 .map(|elem| {
174 let val = elem.value();
175 if val > 0.0 {
176 elem.pow_scalar(2.0) // Square positive values
177 } else {
178 elem.mul_scalar(-1.0).sqrt() // Square root of absolute negative values
179 }
180 })
181 .collect();
182 println!(" Processed: {:?}", processed.data());
183
184 // Adaptive filtering based on local statistics
185 println!("\nAdaptive filtering (remove values > 2 std from local mean):");
186 let window_size = 3;
187 let adaptive_filtered: Tensor = tensor
188 .iter()
189 .enumerate()
190 .filter(|(i, elem)| {
191 let start = i.saturating_sub(window_size / 2);
192 let end = (i + window_size / 2 + 1).min(tensor.size());
193
194 // Calculate local mean and std
195 let local_values: Vec<f32> = (start..end)
196 .map(|j| tensor.element_view(j).value())
197 .collect();
198
199 let local_mean = local_values.iter().sum::<f32>() / local_values.len() as f32;
200 let local_variance = local_values
201 .iter()
202 .map(|v| (v - local_mean).powi(2))
203 .sum::<f32>()
204 / local_values.len() as f32;
205 let local_std = local_variance.sqrt();
206
207 let threshold = local_mean + 2.0 * local_std;
208 elem.value() <= threshold
209 })
210 .map(|(_, elem)| elem)
211 .collect();
212 println!(" Adaptive filtered: {:?}", adaptive_filtered.data());
213
214 // Multi-condition processing
215 println!("\nMulti-condition processing:");
216 let multi_processed: Tensor = tensor
217 .iter()
218 .map(|elem| {
219 let val = elem.value();
220 match () {
221 _ if val > 5.0 => elem.mul_scalar(2.0), // Double large values
222 _ if val < -5.0 => elem.div_scalar(2.0), // Halve small values
223 _ if val.abs() < 2.0 => elem.add_scalar(1.0), // Add 1 to small values
224 _ => elem.clone(), // Keep others unchanged
225 }
226 })
227 .collect();
228 println!(" Multi-condition: {:?}", multi_processed.data());
229
230 Ok(())
231}
232
233/// Demonstrate batch processing operations
234///
235/// Shows efficient processing of large datasets using iterator
236/// patterns and batch operations for performance optimization.
237fn demonstrate_batch_operations() -> Result<(), Box<dyn std::error::Error>> {
238 println!("\n--- Batch Operations ---");
239
240 // Create a larger dataset for batch processing
241 let size = 100;
242 let data: Vec<f32> = (0..size)
243 .map(|i| {
244 let x = i as f32 / size as f32;
245 x * x + 0.1 * (i % 7) as f32 // Quadratic with some noise
246 })
247 .collect();
248
249 let tensor = Tensor::from_slice(&data, vec![size])?;
250 println!("Dataset size: {}", tensor.size());
251
252 // Batch processing with windowing
253 println!("\nBatch processing with sliding windows:");
254 let batch_size = 10;
255 let batches: Vec<Tensor> = tensor
256 .iter()
257 .collect::<Vec<_>>()
258 .chunks(batch_size)
259 .map(|chunk| {
260 // Process each batch independently
261 chunk
262 .iter()
263 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
264 .collect()
265 })
266 .collect();
267
268 println!(
269 " Processed {} batches of size {}",
270 batches.len(),
271 batch_size
272 );
273 for (i, batch) in batches.iter().enumerate() {
274 println!(
275 " Batch {}: mean={:.3}, std={:.3}",
276 i,
277 batch.mean().value(),
278 batch.std().value()
279 );
280 }
281
282 // Parallel-like processing with stride
283 println!("\nStrided processing (every nth element):");
284 let stride = 5;
285 let strided: Tensor = tensor
286 .iter()
287 .enumerate()
288 .filter(|(i, _)| i % stride == 0)
289 .map(|(_, elem)| elem)
290 .collect();
291 println!(" Strided (every {}th): {:?}", stride, strided.data());
292
293 // Hierarchical processing
294 println!("\nHierarchical processing (coarse to fine):");
295 let coarse: Tensor = tensor
296 .iter()
297 .enumerate()
298 .filter(|(i, _)| i % 4 == 0) // Take every 4th element
299 .map(|(_, elem)| elem)
300 .collect();
301
302 let fine: Tensor = tensor
303 .iter()
304 .enumerate()
305 .filter(|(i, _)| i % 4 != 0) // Take the rest
306 .map(|(_, elem)| elem)
307 .collect();
308
309 println!(" Coarse (every 4th): {:?}", coarse.data());
310 println!(" Fine (rest): {:?}", fine.data());
311
312 // Combine coarse and fine with different processing
313 let combined: Tensor = coarse
314 .iter()
315 .map(|elem| elem.mul_scalar(2.0)) // Scale coarse
316 .chain(fine.iter().map(|elem| elem.div_scalar(2.0))) // Scale fine
317 .collect();
318 println!(" Combined: {:?}", combined.data());
319
320 Ok(())
321}
322
323/// Demonstrate real-world processing scenarios
324///
325/// Shows practical applications of iterator patterns for
326/// common data processing tasks in machine learning and analytics.
327fn demonstrate_real_world_scenarios() -> Result<(), Box<dyn std::error::Error>> {
328 println!("\n--- Real-world Scenarios ---");
329
330 // Scenario 1: Time series analysis
331 println!("\nScenario 1: Time Series Analysis");
332 let time_series: Vec<f32> = (0..24)
333 .map(|hour| {
334 let base = 20.0 + 10.0 * (hour as f32 * std::f32::consts::PI / 12.0).sin();
335 base + (hour % 3) as f32 * 2.0 // Add some noise
336 })
337 .collect();
338
339 let series = Tensor::from_slice(&time_series, vec![24])?;
340 println!(" Time series (24 hours): {:?}", series.data());
341
342 // Calculate moving average
343 let window_size = 3;
344 let moving_avg: Tensor = series
345 .iter()
346 .enumerate()
347 .map(|(i, _)| {
348 let start = i.saturating_sub(window_size / 2);
349 let end = (i + window_size / 2 + 1).min(series.size());
350 let window = series.iter_range(start, end);
351 window.fold(0.0, |acc, elem| acc + elem.value()) / (end - start) as f32
352 })
353 .map(|val| Tensor::from_slice(&[val], vec![1]).unwrap())
354 .collect();
355 println!(
356 " Moving average (window={}): {:?}",
357 window_size,
358 moving_avg.data()
359 );
360
361 // Scenario 2: Feature engineering
362 println!("\nScenario 2: Feature Engineering");
363 let features = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
364 println!(" Original features: {:?}", features.data());
365
366 // Create polynomial features
367 let poly_features: Tensor = features
368 .iter()
369 .flat_map(|elem| {
370 vec![
371 elem.clone(), // x^1
372 elem.pow_scalar(2.0), // x^2
373 elem.pow_scalar(3.0), // x^3
374 ]
375 })
376 .collect();
377 println!(
378 " Polynomial features (x, x^2, x^3): {:?}",
379 poly_features.data()
380 );
381
382 // Scenario 3: Data augmentation
383 println!("\nScenario 3: Data Augmentation");
384 let original = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?;
385 println!(" Original data: {:?}", original.data());
386
387 // Augment with noise and scaling
388 let augmented: Tensor = original
389 .iter()
390 .flat_map(|elem| {
391 vec![
392 elem.clone(), // Original
393 elem.add_scalar(0.1), // Add noise
394 elem.sub_scalar(0.1), // Subtract noise
395 elem.mul_scalar(1.1), // Scale up
396 elem.mul_scalar(0.9), // Scale down
397 ]
398 })
399 .collect();
400 println!(" Augmented data: {:?}", augmented.data());
401
402 // Scenario 4: Statistical analysis
403 println!("\nScenario 4: Statistical Analysis");
404 let sample_data = Tensor::from_slice(&[1.1, 2.3, 1.8, 2.1, 1.9, 2.0, 1.7, 2.2], vec![8])?;
405 println!(" Sample data: {:?}", sample_data.data());
406
407 // Calculate various statistics
408 let mean = sample_data.mean().value();
409 let std = sample_data.std().value();
410 let min = sample_data
411 .iter()
412 .map(|e| e.value())
413 .fold(f32::INFINITY, f32::min);
414 let max = sample_data
415 .iter()
416 .map(|e| e.value())
417 .fold(f32::NEG_INFINITY, f32::max);
418
419 // Z-score normalization
420 let z_scores: Tensor = sample_data
421 .iter()
422 .map(|elem| elem.sub_scalar(mean).div_scalar(std))
423 .collect();
424
425 println!(
426 " Statistics: mean={:.3}, std={:.3}, min={:.3}, max={:.3}",
427 mean, std, min, max
428 );
429 println!(" Z-scores: {:?}", z_scores.data());
430
431 Ok(())
432}Sourcepub fn device(&self) -> Device
pub fn device(&self) -> Device
Returns the device where this tensor is located
Provides the physical location of the tensor data (CPU/GPU). This determines which operations can be performed on the tensor and where computations will be executed.
§Returns
Device enum indicating the tensor’s physical location
§Performance
- Time Complexity: O(1) - direct field access
- Memory: No allocation - returns stored value
§Examples
use train_station::Tensor;
let tensor = Tensor::new(vec![2, 3]);
assert!(tensor.device().is_cpu());
assert!(!tensor.device().is_cuda());Examples found in repository?
179fn demonstrate_utility_functions() {
180 println!("\n--- Utility Functions ---");
181
182 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
183
184 // Basic properties
185 println!("Shape: {:?}", tensor.shape().dims);
186 println!("Size: {}", tensor.size());
187 println!("Is contiguous: {}", tensor.is_contiguous());
188 println!("Device: {:?}", tensor.device());
189
190 // Mathematical operations
191 let sum = tensor.sum();
192 println!("Sum: {}", sum.value());
193
194 let mean = tensor.mean();
195 println!("Mean: {}", mean.value());
196
197 let norm = tensor.norm();
198 println!("Norm: {}", norm.value());
199
200 // Device placement
201 let cpu_tensor = Tensor::zeros_on_device(vec![3, 3], train_station::Device::cpu());
202 println!(
203 "CPU tensor: shape {:?}, device: {:?}",
204 cpu_tensor.shape().dims,
205 cpu_tensor.device()
206 );
207}Sourcepub fn new_on_device(shape_dims: Vec<usize>, device: Device) -> Self
pub fn new_on_device(shape_dims: Vec<usize>, device: Device) -> Self
Creates a new tensor with the specified shape on a specific device
Allocates memory on the specified device with the same optimized alignment
strategy as new(). Currently supports CPU device with future CUDA support.
§Arguments
shape_dims- Vector of dimension sizes defining the tensor shapedevice- The device where the tensor should be allocated
§Returns
A new tensor with uninitialized data on the specified device
§Performance
- Memory Allocation: Device-specific allocation with optimized alignment
- SIMD Ready: Properly aligned for vectorized operations on target device
- Thread Safe: Atomic ID generation for gradtrack tracking
§Panics
Panics if the specified device is not supported (e.g., CUDA without feature flag)
§Examples
use train_station::Tensor;
let tensor = Tensor::new_on_device(vec![2, 3], train_station::Device::cpu());
assert!(tensor.device().is_cpu());
assert_eq!(tensor.size(), 6);§Arguments
shape_dims- Vector of dimension sizes defining the tensor shapedevice- Device where the tensor should be allocated
§Returns
A new tensor with uninitialized data on the specified device
§Panics
Panics if the device is not supported (currently only CPU is supported)
§Performance
- Memory Allocation: Single allocation with optimized alignment
- SIMD Ready: Properly aligned for vectorized operations
- Cache Friendly: Optimized for CPU cache hierarchies
- Thread Safe: Atomic ID generation for gradtrack tracking
§Examples
use train_station::{Tensor, Device};
// Create tensor on CPU device
let tensor = Tensor::new_on_device(vec![2, 3], Device::cpu());
assert_eq!(tensor.device(), Device::cpu());
assert_eq!(tensor.size(), 6);Sourcepub fn with_requires_grad(self) -> Self
pub fn with_requires_grad(self) -> Self
Enable gradient computation for this tensor
Builder method that enables automatic gradient tracking for this tensor. When enabled, all operations involving this tensor will be recorded in the computation graph for gradient computation during backward pass.
§Returns
self with gradient tracking enabled
§Performance
- Time Complexity: O(1) - simple field assignment
- Memory: No additional allocation
- Overhead: Minimal gradtrack tracking overhead when gradients computed
§Examples
use train_station::Tensor;
let tensor = Tensor::ones(vec![2, 3]).with_requires_grad();
assert!(tensor.requires_grad());Examples found in repository?
68 pub fn new(input_size: usize, output_size: usize, seed: Option<u64>) -> Self {
69 let scale = (1.0 / input_size as f32).sqrt();
70
71 let weight = Tensor::randn(vec![input_size, output_size], seed)
72 .mul_scalar(scale)
73 .with_requires_grad();
74 let bias = Tensor::zeros(vec![output_size]).with_requires_grad();
75
76 Self {
77 weight,
78 bias,
79 input_size,
80 output_size,
81 }
82 }
83
84 pub fn forward(&self, input: &Tensor) -> Tensor {
85 let output = input.matmul(&self.weight);
86 output.add_tensor(&self.bias)
87 }
88
89 pub fn forward_no_grad(&self, input: &Tensor) -> Tensor {
90 let _guard = NoGradTrack::new();
91 self.forward(input)
92 }
93
94 pub fn parameters(&mut self) -> Vec<&mut Tensor> {
95 vec![&mut self.weight, &mut self.bias]
96 }
97}
98
99/// Configuration for feed-forward network
100#[derive(Debug, Clone)]
101pub struct FeedForwardConfig {
102 pub input_size: usize,
103 pub hidden_sizes: Vec<usize>,
104 pub output_size: usize,
105 pub use_bias: bool,
106}
107
108impl Default for FeedForwardConfig {
109 fn default() -> Self {
110 Self {
111 input_size: 4,
112 hidden_sizes: vec![8, 4],
113 output_size: 2,
114 use_bias: true,
115 }
116 }
117}
118
119/// A configurable feed-forward neural network
120pub struct FeedForwardNetwork {
121 layers: Vec<LinearLayer>,
122 config: FeedForwardConfig,
123}
124
125impl FeedForwardNetwork {
126 /// Create a new feed-forward network with the given configuration
127 pub fn new(config: FeedForwardConfig, seed: Option<u64>) -> Self {
128 let mut layers = Vec::new();
129 let mut current_size = config.input_size;
130 let mut current_seed = seed;
131
132 // Create hidden layers
133 for &hidden_size in &config.hidden_sizes {
134 layers.push(LinearLayer::new(current_size, hidden_size, current_seed));
135 current_size = hidden_size;
136 current_seed = current_seed.map(|s| s + 1);
137 }
138
139 // Create output layer
140 layers.push(LinearLayer::new(
141 current_size,
142 config.output_size,
143 current_seed,
144 ));
145
146 Self { layers, config }
147 }
148
149 /// Forward pass through the entire network
150 pub fn forward(&self, input: &Tensor) -> Tensor {
151 let mut x = input.clone();
152
153 // Pass through all layers except the last one with ReLU activation
154 for layer in &self.layers[..self.layers.len() - 1] {
155 x = layer.forward(&x);
156 x = ReLU::forward(&x);
157 }
158
159 // Final layer without activation (raw logits)
160 if let Some(final_layer) = self.layers.last() {
161 x = final_layer.forward(&x);
162 }
163
164 x
165 }
166
167 /// Forward pass without gradients (for inference)
168 pub fn forward_no_grad(&self, input: &Tensor) -> Tensor {
169 let _guard = NoGradTrack::new();
170 self.forward(input)
171 }
172
173 /// Get all parameters for optimization
174 pub fn parameters(&mut self) -> Vec<&mut Tensor> {
175 let mut params = Vec::new();
176 for layer in &mut self.layers {
177 params.extend(layer.parameters());
178 }
179 params
180 }
181
182 /// Get the number of layers
183 pub fn num_layers(&self) -> usize {
184 self.layers.len()
185 }
186
187 /// Get the total number of parameters
188 pub fn parameter_count(&self) -> usize {
189 let mut count = 0;
190 let mut current_size = self.config.input_size;
191
192 for &hidden_size in &self.config.hidden_sizes {
193 count += current_size * hidden_size + hidden_size; // weights + bias
194 current_size = hidden_size;
195 }
196
197 // Output layer
198 count += current_size * self.config.output_size + self.config.output_size;
199
200 count
201 }
202
203 /// Save network parameters to JSON
204 pub fn save_json(&self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
205 if let Some(parent) = std::path::Path::new(path).parent() {
206 fs::create_dir_all(parent)?;
207 }
208
209 for (i, layer) in self.layers.iter().enumerate() {
210 let layer_path = format!("{}_layer_{}", path, i);
211 let weight_path = format!("{}_weight.json", layer_path);
212 let bias_path = format!("{}_bias.json", layer_path);
213
214 layer.weight.save_json(&weight_path)?;
215 layer.bias.save_json(&bias_path)?;
216 }
217
218 println!(
219 "Saved feed-forward network to {} ({} layers)",
220 path,
221 self.layers.len()
222 );
223 Ok(())
224 }
225
226 /// Load network parameters from JSON
227 pub fn load_json(
228 path: &str,
229 config: FeedForwardConfig,
230 ) -> Result<Self, Box<dyn std::error::Error>> {
231 let mut layers = Vec::new();
232 let mut current_size = config.input_size;
233 let mut layer_idx = 0;
234
235 // Load hidden layers
236 for &hidden_size in &config.hidden_sizes {
237 let layer_path = format!("{}_layer_{}", path, layer_idx);
238 let weight_path = format!("{}_weight.json", layer_path);
239 let bias_path = format!("{}_bias.json", layer_path);
240
241 let weight = Tensor::load_json(&weight_path)?.with_requires_grad();
242 let bias = Tensor::load_json(&bias_path)?.with_requires_grad();
243
244 layers.push(LinearLayer {
245 weight,
246 bias,
247 input_size: current_size,
248 output_size: hidden_size,
249 });
250
251 current_size = hidden_size;
252 layer_idx += 1;
253 }
254
255 // Load output layer
256 let layer_path = format!("{}_layer_{}", path, layer_idx);
257 let weight_path = format!("{}_weight.json", layer_path);
258 let bias_path = format!("{}_bias.json", layer_path);
259
260 let weight = Tensor::load_json(&weight_path)?.with_requires_grad();
261 let bias = Tensor::load_json(&bias_path)?.with_requires_grad();
262
263 layers.push(LinearLayer {
264 weight,
265 bias,
266 input_size: current_size,
267 output_size: config.output_size,
268 });
269
270 Ok(Self { layers, config })
271 }More examples
52 pub fn new(input_size: usize, output_size: usize, seed: Option<u64>) -> Self {
53 // Xavier/Glorot initialization: scale by sqrt(1/input_size)
54 let scale = (1.0 / input_size as f32).sqrt();
55
56 let weight = Tensor::randn(vec![input_size, output_size], seed)
57 .mul_scalar(scale)
58 .with_requires_grad();
59 let bias = Tensor::zeros(vec![output_size]).with_requires_grad();
60
61 Self {
62 weight,
63 bias,
64 input_size,
65 output_size,
66 }
67 }
68
69 /// Forward pass: output = input @ weight + bias
70 pub fn forward(&self, input: &Tensor) -> Tensor {
71 // Matrix multiplication: [batch_size, input_size] @ [input_size, output_size] = [batch_size, output_size]
72 let output = input.matmul(&self.weight);
73 // Add bias: [batch_size, output_size] + [output_size] = [batch_size, output_size]
74 output.add_tensor(&self.bias)
75 }
76
77 /// Forward pass without gradients (for inference)
78 pub fn forward_no_grad(&self, input: &Tensor) -> Tensor {
79 let _guard = NoGradTrack::new();
80 self.forward(input)
81 }
82
83 /// Get all parameters for optimization
84 pub fn parameters(&mut self) -> Vec<&mut Tensor> {
85 vec![&mut self.weight, &mut self.bias]
86 }
87
88 /// Save layer parameters to JSON
89 pub fn save_json(&self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
90 // Create directory if it doesn't exist
91 if let Some(parent) = std::path::Path::new(path).parent() {
92 fs::create_dir_all(parent)?;
93 }
94
95 let weight_path = format!("{}_weight.json", path);
96 let bias_path = format!("{}_bias.json", path);
97
98 self.weight.save_json(&weight_path)?;
99 self.bias.save_json(&bias_path)?;
100
101 println!("Saved linear layer to {} (weight and bias)", path);
102 Ok(())
103 }
104
105 /// Load layer parameters from JSON
106 pub fn load_json(
107 path: &str,
108 input_size: usize,
109 output_size: usize,
110 ) -> Result<Self, Box<dyn std::error::Error>> {
111 let weight_path = format!("{}_weight.json", path);
112 let bias_path = format!("{}_bias.json", path);
113
114 let weight = Tensor::load_json(&weight_path)?.with_requires_grad();
115 let bias = Tensor::load_json(&bias_path)?.with_requires_grad();
116
117 Ok(Self {
118 weight,
119 bias,
120 input_size,
121 output_size,
122 })
123 }159fn demonstrate_gradient_tracking() -> Result<(), Box<dyn std::error::Error>> {
160 println!("\n--- Gradient Tracking ---");
161
162 // Create a tensor with gradient tracking enabled
163 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?.with_requires_grad();
164 println!("Input tensor (requires_grad): {:?}", tensor.data());
165
166 // Perform element-wise operations through iteration
167 let result: Tensor = tensor
168 .iter()
169 .map(|elem| {
170 // Apply a complex transformation: (x^2 + 1) * 2
171 elem.pow_scalar(2.0).add_scalar(1.0).mul_scalar(2.0)
172 })
173 .collect();
174
175 println!("Result tensor: {:?}", result.data());
176 println!("Result requires_grad: {}", result.requires_grad());
177
178 // Compute gradients
179 let mut loss = result.sum();
180 loss.backward(None);
181
182 println!("Loss: {:.6}", loss.value());
183 println!("Input gradients: {:?}", tensor.grad().map(|g| g.data()));
184
185 Ok(())
186}47fn demonstrate_basic_optimizer_setup() {
48 println!("--- Basic Optimizer Setup ---");
49
50 // Create parameters that require gradients
51 let weight = Tensor::randn(vec![3, 2], Some(42)).with_requires_grad();
52 let bias = Tensor::zeros(vec![2]).with_requires_grad();
53
54 println!("Created parameters:");
55 println!(
56 " Weight: shape {:?}, requires_grad: {}",
57 weight.shape().dims,
58 weight.requires_grad()
59 );
60 println!(
61 " Bias: shape {:?}, requires_grad: {}",
62 bias.shape().dims,
63 bias.requires_grad()
64 );
65
66 // Create Adam optimizer with default configuration
67 let mut optimizer = Adam::new();
68 println!(
69 "Created Adam optimizer with learning rate: {}",
70 optimizer.learning_rate()
71 );
72
73 // Add parameters to optimizer
74 optimizer.add_parameter(&weight);
75 optimizer.add_parameter(&bias);
76 println!(
77 "Added {} parameters to optimizer",
78 optimizer.parameter_count()
79 );
80
81 // Create optimizer with custom configuration
82 let config = AdamConfig {
83 learning_rate: 0.01,
84 beta1: 0.9,
85 beta2: 0.999,
86 eps: 1e-8,
87 weight_decay: 0.0,
88 amsgrad: false,
89 };
90
91 let mut custom_optimizer = Adam::with_config(config);
92 custom_optimizer.add_parameter(&weight);
93 custom_optimizer.add_parameter(&bias);
94
95 println!(
96 "Created custom optimizer with learning rate: {}",
97 custom_optimizer.learning_rate()
98 );
99
100 // Demonstrate parameter linking
101 println!("Parameter linking completed successfully");
102}
103
104/// Demonstrate simple linear regression training
105fn demonstrate_linear_regression() -> Result<(), Box<dyn std::error::Error>> {
106 println!("\n--- Linear Regression Training ---");
107
108 // Create model parameters
109 let mut weight = Tensor::randn(vec![1, 1], Some(43)).with_requires_grad();
110 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
111
112 // Create optimizer
113 let mut optimizer = Adam::with_learning_rate(0.01);
114 optimizer.add_parameter(&weight);
115 optimizer.add_parameter(&bias);
116
117 // Create simple training data: y = 2*x + 1
118 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
119 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
120
121 println!("Training data:");
122 println!(" X: {:?}", x_data.data());
123 println!(" Y: {:?}", y_true.data());
124 println!(" Target: y = 2*x + 1");
125
126 // Training loop
127 let num_epochs = 100;
128 let mut losses = Vec::new();
129
130 for epoch in 0..num_epochs {
131 // Forward pass: y_pred = x * weight + bias
132 let y_pred = x_data.matmul(&weight) + &bias;
133
134 // Compute loss: MSE
135 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
136
137 // Backward pass
138 loss.backward(None);
139
140 // Optimizer step
141 optimizer.step(&mut [&mut weight, &mut bias]);
142 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
143
144 losses.push(loss.value());
145
146 // Print progress every 20 epochs
147 if epoch % 20 == 0 || epoch == num_epochs - 1 {
148 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
149 }
150 }
151
152 // Evaluate final model
153 let final_predictions = x_data.matmul(&weight) + &bias;
154 println!("\nFinal model evaluation:");
155 println!(" Learned weight: {:.6}", weight.value());
156 println!(" Learned bias: {:.6}", bias.value());
157 println!(" Predictions vs True:");
158
159 for i in 0..5 {
160 let x1 = x_data.data()[i];
161 let pred = final_predictions.data()[i];
162 let true_val = y_true.data()[i];
163 println!(
164 " x={:.1}: pred={:.3}, true={:.1}, error={:.3}",
165 x1,
166 pred,
167 true_val,
168 (pred - true_val).abs()
169 );
170 }
171
172 Ok(())
173}
174
175/// Demonstrate advanced training patterns
176fn demonstrate_advanced_training() -> Result<(), Box<dyn std::error::Error>> {
177 println!("\n--- Advanced Training Patterns ---");
178
179 // Create a more complex model
180 let mut weight = Tensor::randn(vec![1, 2], Some(44)).with_requires_grad();
181 let mut bias = Tensor::zeros(vec![2]).with_requires_grad();
182
183 // Create optimizer with different learning rate
184 let mut optimizer = Adam::with_learning_rate(0.005);
185 optimizer.add_parameter(&weight);
186 optimizer.add_parameter(&bias);
187
188 // Create training data: y = 2*x + [1, 3]
189 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
190 let y_true = Tensor::from_slice(
191 &[3.0, 5.0, 7.0, 9.0, 11.0, 6.0, 8.0, 10.0, 12.0, 14.0],
192 vec![5, 2],
193 )
194 .unwrap();
195
196 println!("Advanced training with monitoring:");
197 println!(" Initial learning rate: {}", optimizer.learning_rate());
198
199 // Training loop with monitoring
200 let num_epochs = 50;
201 let mut losses = Vec::new();
202 let mut weight_norms = Vec::new();
203 let mut gradient_norms = Vec::new();
204
205 for epoch in 0..num_epochs {
206 // Forward pass
207 let y_pred = x_data.matmul(&weight) + &bias;
208 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
209
210 // Backward pass
211 loss.backward(None);
212
213 // Compute gradient norm before optimizer step
214 let gradient_norm = weight.grad_by_value().unwrap().norm();
215
216 // Optimizer step
217 optimizer.step(&mut [&mut weight, &mut bias]);
218 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
219
220 // Learning rate scheduling: reduce every 10 epochs
221 if epoch > 0 && epoch % 10 == 0 {
222 let current_lr = optimizer.learning_rate();
223 let new_lr = current_lr * 0.5;
224 optimizer.set_learning_rate(new_lr);
225 println!(
226 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
227 epoch, current_lr, new_lr
228 );
229 }
230
231 // Record metrics
232 losses.push(loss.value());
233 weight_norms.push(weight.norm().value());
234 gradient_norms.push(gradient_norm.value());
235
236 // Print detailed progress
237 if epoch % 10 == 0 || epoch == num_epochs - 1 {
238 println!(
239 "Epoch {:2}: Loss = {:.6}, Weight Norm = {:.6}, Gradient Norm = {:.6}",
240 epoch,
241 loss.value(),
242 weight.norm().value(),
243 gradient_norm.value()
244 );
245 }
246 }
247
248 println!("Final learning rate: {}", optimizer.learning_rate());
249
250 // Analyze training progression
251 let initial_loss = losses[0];
252 let final_loss = losses[losses.len() - 1];
253 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
254
255 println!("\nTraining Analysis:");
256 println!(" Initial loss: {:.6}", initial_loss);
257 println!(" Final loss: {:.6}", final_loss);
258 println!(" Loss reduction: {:.1}%", loss_reduction);
259 println!(" Final weight norm: {:.6}", weight.norm().value());
260 println!(" Final bias: {:?}", bias.data());
261
262 Ok(())
263}
264
265/// Demonstrate learning rate scheduling
266fn demonstrate_learning_rate_scheduling() -> Result<(), Box<dyn std::error::Error>> {
267 println!("\n--- Learning Rate Scheduling ---");
268
269 // Create simple model
270 let mut weight = Tensor::randn(vec![1, 1], Some(45)).with_requires_grad();
271 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
272
273 // Create optimizer with high initial learning rate
274 let mut optimizer = Adam::with_learning_rate(0.1);
275 optimizer.add_parameter(&weight);
276 optimizer.add_parameter(&bias);
277
278 // Simple data
279 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3, 1]).unwrap();
280 let y_true = Tensor::from_slice(&[2.0, 4.0, 6.0], vec![3, 1]).unwrap();
281
282 println!("Initial learning rate: {}", optimizer.learning_rate());
283
284 // Training loop with learning rate scheduling
285 let num_epochs = 50;
286 let mut losses = Vec::new();
287
288 for epoch in 0..num_epochs {
289 // Forward pass
290 let y_pred = x_data.matmul(&weight) + &bias;
291 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
292
293 // Backward pass
294 loss.backward(None);
295
296 // Optimizer step
297 optimizer.step(&mut [&mut weight, &mut bias]);
298 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
299
300 // Learning rate scheduling: reduce every 10 epochs
301 if epoch > 0 && epoch % 10 == 0 {
302 let current_lr = optimizer.learning_rate();
303 let new_lr = current_lr * 0.5;
304 optimizer.set_learning_rate(new_lr);
305 println!(
306 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
307 epoch, current_lr, new_lr
308 );
309 }
310
311 losses.push(loss.value());
312
313 // Print progress
314 if epoch % 10 == 0 || epoch == num_epochs - 1 {
315 println!(
316 "Epoch {:2}: Loss = {:.6}, LR = {:.3}",
317 epoch,
318 loss.value(),
319 optimizer.learning_rate()
320 );
321 }
322 }
323
324 println!("Final learning rate: {}", optimizer.learning_rate());
325
326 Ok(())
327}
328
329/// Demonstrate training monitoring and analysis
330fn demonstrate_training_monitoring() -> Result<(), Box<dyn std::error::Error>> {
331 println!("\n--- Training Monitoring ---");
332
333 // Create model
334 let mut weight = Tensor::randn(vec![1, 1], Some(46)).with_requires_grad();
335 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
336
337 // Create optimizer
338 let mut optimizer = Adam::with_learning_rate(0.01);
339 optimizer.add_parameter(&weight);
340 optimizer.add_parameter(&bias);
341
342 // Training data
343 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4, 1]).unwrap();
344 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0], vec![4, 1]).unwrap();
345
346 // Training loop with comprehensive monitoring
347 let num_epochs = 30;
348 let mut losses = Vec::new();
349 let mut weight_history = Vec::new();
350 let mut bias_history = Vec::new();
351
352 for epoch in 0..num_epochs {
353 // Forward pass
354 let y_pred = x_data.matmul(&weight) + &bias;
355 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
356
357 // Backward pass
358 loss.backward(None);
359
360 // Optimizer step
361 optimizer.step(&mut [&mut weight, &mut bias]);
362 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
363
364 // Record history
365 losses.push(loss.value());
366 weight_history.push(weight.value());
367 bias_history.push(bias.value());
368
369 // Print detailed monitoring
370 if epoch % 5 == 0 || epoch == num_epochs - 1 {
371 println!(
372 "Epoch {:2}: Loss = {:.6}, Weight = {:.6}, Bias = {:.6}",
373 epoch,
374 loss.value(),
375 weight.value(),
376 bias.value()
377 );
378 }
379 }
380
381 // Analyze training progression
382 println!("\nTraining Analysis:");
383 println!(" Initial loss: {:.6}", losses[0]);
384 println!(" Final loss: {:.6}", losses[losses.len() - 1]);
385 println!(
386 " Loss reduction: {:.1}%",
387 (losses[0] - losses[losses.len() - 1]) / losses[0] * 100.0
388 );
389
390 // Compute statistics
391 let loss_mean = compute_mean(&losses);
392 let loss_std = compute_std(&losses);
393 let weight_change = (weight_history[weight_history.len() - 1] - weight_history[0]).abs();
394 let bias_change = (bias_history[bias_history.len() - 1] - bias_history[0]).abs();
395
396 println!(" Average loss: {:.6} ± {:.6}", loss_mean, loss_std);
397 println!(" Weight change: {:.6}", weight_change);
398 println!(" Bias change: {:.6}", bias_change);
399 println!(" Final weight norm: {:.6}", weight.norm().value());
400 println!(" Final bias: {:.6}", bias.value());
401
402 Ok(())
403}109fn demonstrate_optimizer_serialization() -> Result<(), Box<dyn std::error::Error>> {
110 println!("\n--- Optimizer Serialization ---");
111
112 // Create an optimizer with some parameters
113 let mut weight = Tensor::randn(vec![2, 2], Some(42)).with_requires_grad();
114 let mut bias = Tensor::randn(vec![2], Some(43)).with_requires_grad();
115
116 let config = AdamConfig {
117 learning_rate: 0.001,
118 beta1: 0.9,
119 beta2: 0.999,
120 eps: 1e-8,
121 weight_decay: 0.0,
122 amsgrad: false,
123 };
124
125 let mut optimizer = Adam::with_config(config);
126 optimizer.add_parameter(&weight);
127 optimizer.add_parameter(&bias);
128
129 println!(
130 "Created optimizer with {} parameters",
131 optimizer.parameter_count()
132 );
133 println!("Learning rate: {}", optimizer.learning_rate());
134
135 // Simulate some training steps
136 for _ in 0..3 {
137 let mut loss = weight.sum() + bias.sum();
138 loss.backward(None);
139 optimizer.step(&mut [&mut weight, &mut bias]);
140 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
141 }
142
143 // Save optimizer state
144 let optimizer_path = "temp_optimizer.json";
145 optimizer.save_json(optimizer_path)?;
146 println!("Saved optimizer to: {}", optimizer_path);
147
148 // Load optimizer state
149 let loaded_optimizer = Adam::load_json(optimizer_path)?;
150 println!(
151 "Loaded optimizer with {} parameters",
152 loaded_optimizer.parameter_count()
153 );
154 println!("Learning rate: {}", loaded_optimizer.learning_rate());
155
156 // Verify optimizer state
157 assert_eq!(
158 optimizer.parameter_count(),
159 loaded_optimizer.parameter_count()
160 );
161 assert_eq!(optimizer.learning_rate(), loaded_optimizer.learning_rate());
162 println!("Optimizer serialization verification: PASSED");
163
164 Ok(())
165}
166
167/// Demonstrate format comparison and performance characteristics
168fn demonstrate_format_comparison() -> Result<(), Box<dyn std::error::Error>> {
169 println!("\n--- Format Comparison ---");
170
171 // Create a larger tensor for comparison
172 let tensor = Tensor::randn(vec![10, 10], Some(44));
173
174 // Save in both formats
175 tensor.save_json("temp_comparison.json")?;
176 tensor.save_binary("temp_comparison.bin")?;
177
178 // Compare file sizes
179 let json_size = fs::metadata("temp_comparison.json")?.len();
180 let binary_size = fs::metadata("temp_comparison.bin")?.len();
181
182 println!("JSON file size: {} bytes", json_size);
183 println!("Binary file size: {} bytes", binary_size);
184 println!(
185 "Compression ratio: {:.2}x",
186 json_size as f64 / binary_size as f64
187 );
188
189 // Load and verify both formats
190 let json_tensor = Tensor::load_json("temp_comparison.json")?;
191 let binary_tensor = Tensor::load_binary("temp_comparison.bin")?;
192
193 assert_eq!(tensor.shape().dims, json_tensor.shape().dims);
194 assert_eq!(tensor.shape().dims, binary_tensor.shape().dims);
195 assert_eq!(tensor.data(), json_tensor.data());
196 assert_eq!(tensor.data(), binary_tensor.data());
197
198 println!("Format comparison verification: PASSED");
199
200 Ok(())
201}
202
203/// Demonstrate a basic model checkpointing workflow
204fn demonstrate_model_checkpointing() -> Result<(), Box<dyn std::error::Error>> {
205 println!("\n--- Model Checkpointing ---");
206
207 // Create a simple model (weights and bias)
208 let mut weights = Tensor::randn(vec![2, 1], Some(45)).with_requires_grad();
209 let mut bias = Tensor::randn(vec![1], Some(46)).with_requires_grad();
210
211 // Create optimizer
212 let mut optimizer = Adam::with_learning_rate(0.01);
213 optimizer.add_parameter(&weights);
214 optimizer.add_parameter(&bias);
215
216 println!("Initial weights: {:?}", weights.data());
217 println!("Initial bias: {:?}", bias.data());
218
219 // Simulate training
220 for epoch in 0..5 {
221 let mut loss = weights.sum() + bias.sum();
222 loss.backward(None);
223 optimizer.step(&mut [&mut weights, &mut bias]);
224 optimizer.zero_grad(&mut [&mut weights, &mut bias]);
225
226 if epoch % 2 == 0 {
227 // Save checkpoint
228 let checkpoint_dir = format!("checkpoint_epoch_{}", epoch);
229 fs::create_dir_all(&checkpoint_dir)?;
230
231 weights.save_json(format!("{}/weights.json", checkpoint_dir))?;
232 bias.save_json(format!("{}/bias.json", checkpoint_dir))?;
233 optimizer.save_json(format!("{}/optimizer.json", checkpoint_dir))?;
234
235 println!("Saved checkpoint for epoch {}", epoch);
236 }
237 }
238
239 // Load from checkpoint
240 let loaded_weights = Tensor::load_json("checkpoint_epoch_4/weights.json")?;
241 let loaded_bias = Tensor::load_json("checkpoint_epoch_4/bias.json")?;
242 let loaded_optimizer = Adam::load_json("checkpoint_epoch_4/optimizer.json")?;
243
244 println!("Loaded weights: {:?}", loaded_weights.data());
245 println!("Loaded bias: {:?}", loaded_bias.data());
246 println!(
247 "Loaded optimizer learning rate: {}",
248 loaded_optimizer.learning_rate()
249 );
250
251 // Verify checkpoint integrity
252 assert_eq!(weights.shape().dims, loaded_weights.shape().dims);
253 assert_eq!(bias.shape().dims, loaded_bias.shape().dims);
254 assert_eq!(optimizer.learning_rate(), loaded_optimizer.learning_rate());
255
256 println!("Checkpointing verification: PASSED");
257
258 Ok(())
259}84fn demonstrate_default_adam() -> Result<(), Box<dyn std::error::Error>> {
85 println!("--- Default Adam Configuration ---");
86
87 // Create a simple regression problem: y = 2*x + 1
88 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
89 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
90
91 // Create model parameters
92 let mut weight = Tensor::randn(vec![1, 1], Some(42)).with_requires_grad();
93 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
94
95 // Create Adam optimizer with default configuration
96 let mut optimizer = Adam::new();
97 optimizer.add_parameter(&weight);
98 optimizer.add_parameter(&bias);
99
100 println!("Default Adam configuration:");
101 println!(" Learning rate: {}", optimizer.learning_rate());
102 println!(" Initial weight: {:.6}", weight.value());
103 println!(" Initial bias: {:.6}", bias.value());
104
105 // Training loop
106 let num_epochs = 50;
107 let mut losses = Vec::new();
108
109 for epoch in 0..num_epochs {
110 // Forward pass
111 let y_pred = x_data.matmul(&weight) + &bias;
112 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
113
114 // Backward pass
115 loss.backward(None);
116
117 // Optimizer step
118 optimizer.step(&mut [&mut weight, &mut bias]);
119 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
120
121 losses.push(loss.value());
122
123 if epoch % 10 == 0 || epoch == num_epochs - 1 {
124 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
125 }
126 }
127
128 // Evaluate final model
129 let _final_predictions = x_data.matmul(&weight) + &bias;
130 println!("\nFinal model:");
131 println!(" Learned weight: {:.6} (target: 2.0)", weight.value());
132 println!(" Learned bias: {:.6} (target: 1.0)", bias.value());
133 println!(" Final loss: {:.6}", losses[losses.len() - 1]);
134
135 Ok(())
136}
137
138/// Demonstrate learning rate comparison
139fn demonstrate_learning_rate_comparison() -> Result<(), Box<dyn std::error::Error>> {
140 println!("\n--- Learning Rate Comparison ---");
141
142 let learning_rates = [0.001, 0.01, 0.1];
143 let mut results = Vec::new();
144
145 for &lr in &learning_rates {
146 println!("\nTesting learning rate: {}", lr);
147
148 let stats = train_with_config(TrainingConfig {
149 learning_rate: lr,
150 ..Default::default()
151 })?;
152
153 results.push((lr, stats.clone()));
154
155 println!(" Final loss: {:.6}", stats.final_loss);
156 println!(" Convergence epoch: {}", stats.convergence_epoch);
157 }
158
159 // Compare results
160 println!("\nLearning Rate Comparison Summary:");
161 for (lr, stats) in &results {
162 println!(
163 " LR={:6}: Loss={:.6}, Converged@{}",
164 lr, stats.final_loss, stats.convergence_epoch
165 );
166 }
167
168 Ok(())
169}
170
171/// Demonstrate weight decay comparison
172fn demonstrate_weight_decay_comparison() -> Result<(), Box<dyn std::error::Error>> {
173 println!("\n--- Weight Decay Comparison ---");
174
175 let weight_decays = [0.0, 0.001, 0.01];
176 let mut results = Vec::new();
177
178 for &wd in &weight_decays {
179 println!("\nTesting weight decay: {}", wd);
180
181 let stats = train_with_config(TrainingConfig {
182 weight_decay: wd,
183 ..Default::default()
184 })?;
185
186 results.push((wd, stats.clone()));
187
188 println!(" Final loss: {:.6}", stats.final_loss);
189 println!(" Final weight norm: {:.6}", stats.weight_norm);
190 }
191
192 // Compare results
193 println!("\nWeight Decay Comparison Summary:");
194 for (wd, stats) in &results {
195 println!(
196 " WD={:6}: Loss={:.6}, Weight Norm={:.6}",
197 wd, stats.final_loss, stats.weight_norm
198 );
199 }
200
201 Ok(())
202}
203
204/// Demonstrate beta parameter tuning
205fn demonstrate_beta_parameter_tuning() -> Result<(), Box<dyn std::error::Error>> {
206 println!("\n--- Beta Parameter Tuning ---");
207
208 let beta_configs = [
209 (0.9, 0.999), // Default
210 (0.8, 0.999), // More aggressive momentum
211 (0.95, 0.999), // Less aggressive momentum
212 (0.9, 0.99), // Faster second moment decay
213 ];
214
215 let mut results = Vec::new();
216
217 for (i, (beta1, beta2)) in beta_configs.iter().enumerate() {
218 println!(
219 "\nTesting beta configuration {}: beta1={}, beta2={}",
220 i + 1,
221 beta1,
222 beta2
223 );
224
225 let config = TrainingConfig {
226 beta1: *beta1,
227 beta2: *beta2,
228 ..Default::default()
229 };
230
231 let stats = train_with_config(config)?;
232 results.push(((*beta1, *beta2), stats.clone()));
233
234 println!(" Final loss: {:.6}", stats.final_loss);
235 println!(" Convergence epoch: {}", stats.convergence_epoch);
236 }
237
238 // Compare results
239 println!("\nBeta Parameter Comparison Summary:");
240 for ((beta1, beta2), stats) in &results {
241 println!(
242 " B1={:4}, B2={:5}: Loss={:.6}, Converged@{}",
243 beta1, beta2, stats.final_loss, stats.convergence_epoch
244 );
245 }
246
247 Ok(())
248}
249
250/// Demonstrate configuration benchmarking
251fn demonstrate_configuration_benchmarking() -> Result<(), Box<dyn std::error::Error>> {
252 println!("\n--- Configuration Benchmarking ---");
253
254 // Define configurations to benchmark
255 let configs = vec![
256 (
257 "Conservative",
258 TrainingConfig {
259 learning_rate: 0.001,
260 weight_decay: 0.001,
261 beta1: 0.95,
262 ..Default::default()
263 },
264 ),
265 (
266 "Balanced",
267 TrainingConfig {
268 learning_rate: 0.01,
269 weight_decay: 0.0,
270 beta1: 0.9,
271 ..Default::default()
272 },
273 ),
274 (
275 "Aggressive",
276 TrainingConfig {
277 learning_rate: 0.1,
278 weight_decay: 0.0,
279 beta1: 0.8,
280 ..Default::default()
281 },
282 ),
283 ];
284
285 let mut benchmark_results = Vec::new();
286
287 for (name, config) in configs {
288 println!("\nBenchmarking {} configuration:", name);
289
290 let start_time = std::time::Instant::now();
291 let stats = train_with_config(config.clone())?;
292 let elapsed = start_time.elapsed();
293
294 println!(" Training time: {:.2}ms", elapsed.as_millis());
295 println!(" Final loss: {:.6}", stats.final_loss);
296 println!(" Convergence: {} epochs", stats.convergence_epoch);
297
298 benchmark_results.push((name.to_string(), stats, elapsed));
299 }
300
301 // Summary
302 println!("\nBenchmarking Summary:");
303 for (name, stats, elapsed) in &benchmark_results {
304 println!(
305 " {:12}: Loss={:.6}, Time={:4}ms, Converged@{}",
306 name,
307 stats.final_loss,
308 elapsed.as_millis(),
309 stats.convergence_epoch
310 );
311 }
312
313 Ok(())
314}
315
316/// Helper function to train with specific configuration
317fn train_with_config(config: TrainingConfig) -> Result<TrainingStats, Box<dyn std::error::Error>> {
318 // Create training data
319 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
320 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
321
322 // Create model parameters
323 let mut weight = Tensor::randn(vec![1, 1], Some(123)).with_requires_grad();
324 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
325
326 // Create optimizer with custom configuration
327 let adam_config = AdamConfig {
328 learning_rate: config.learning_rate,
329 beta1: config.beta1,
330 beta2: config.beta2,
331 eps: 1e-8,
332 weight_decay: config.weight_decay,
333 amsgrad: false,
334 };
335
336 let mut optimizer = Adam::with_config(adam_config);
337 optimizer.add_parameter(&weight);
338 optimizer.add_parameter(&bias);
339
340 // Training loop
341 let mut losses = Vec::new();
342 let mut convergence_epoch = config.epochs;
343
344 for epoch in 0..config.epochs {
345 // Forward pass
346 let y_pred = x_data.matmul(&weight) + &bias;
347 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
348
349 // Backward pass
350 loss.backward(None);
351
352 // Optimizer step
353 optimizer.step(&mut [&mut weight, &mut bias]);
354 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
355
356 let loss_value = loss.value();
357 losses.push(loss_value);
358
359 // Check for convergence (loss < 0.01)
360 if loss_value < 0.01 && convergence_epoch == config.epochs {
361 convergence_epoch = epoch;
362 }
363 }
364
365 Ok(TrainingStats {
366 config,
367 final_loss: losses[losses.len() - 1],
368 loss_history: losses,
369 convergence_epoch,
370 weight_norm: weight.norm().value(),
371 })
372}Sourcepub fn set_requires_grad(&mut self, requires_grad: bool)
pub fn set_requires_grad(&mut self, requires_grad: bool)
Set gradient tracking for this tensor
Controls whether the gradtrack system tracks operations on this tensor and computes gradients during backward pass. When disabled, clears any existing gradients and gradient functions.
§Arguments
requires_grad- Whether to track gradients for this tensor
§Performance
- Time Complexity: O(1) - simple field assignment
- Memory: May free gradient storage when disabled
- Overhead: Zero overhead when gradients disabled
§Examples
use train_station::Tensor;
let mut tensor = Tensor::ones(vec![2, 3]);
tensor.set_requires_grad(true);
assert!(tensor.requires_grad());
// Disable gradient tracking
tensor.set_requires_grad(false);
assert!(!tensor.requires_grad());Sourcepub fn requires_grad(&self) -> bool
pub fn requires_grad(&self) -> bool
Check if this tensor requires gradients
§Returns
true if gradient tracking is enabled for this tensor
§Examples
use train_station::Tensor;
let tensor = Tensor::new(vec![2, 3]);
assert!(!tensor.requires_grad());
let grad_tensor = Tensor::ones(vec![2, 3]).with_requires_grad();
assert!(grad_tensor.requires_grad());Examples found in repository?
147fn demonstrate_layer_creation() {
148 println!("--- Layer Creation ---");
149
150 let layer = LinearLayer::new(3, 2, Some(42));
151
152 println!("Created linear layer:");
153 println!(" Input size: {}", layer.input_size);
154 println!(" Output size: {}", layer.output_size);
155 println!(" Parameter count: {}", layer.parameter_count());
156 println!(" Weight shape: {:?}", layer.weight.shape().dims);
157 println!(" Bias shape: {:?}", layer.bias.shape().dims);
158 println!(" Weight requires grad: {}", layer.weight.requires_grad());
159 println!(" Bias requires grad: {}", layer.bias.requires_grad());
160}
161
162/// Demonstrate forward pass with gradient tracking
163fn demonstrate_forward_pass() {
164 println!("\n--- Forward Pass (with gradients) ---");
165
166 let layer = LinearLayer::new(3, 2, Some(43));
167
168 // Single input
169 let input = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![1, 3]).unwrap();
170 let output = layer.forward(&input);
171
172 println!("Single input:");
173 println!(" Input: {:?}", input.data());
174 println!(" Output: {:?}", output.data());
175 println!(" Output requires grad: {}", output.requires_grad());
176
177 // Batch input
178 let batch_input = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
179 let batch_output = layer.forward(&batch_input);
180
181 println!("Batch input:");
182 println!(" Input shape: {:?}", batch_input.shape().dims);
183 println!(" Output shape: {:?}", batch_output.shape().dims);
184 println!(" Output requires grad: {}", batch_output.requires_grad());
185}
186
187/// Demonstrate forward pass without gradient tracking
188fn demonstrate_forward_pass_no_grad() {
189 println!("\n--- Forward Pass (no gradients) ---");
190
191 let layer = LinearLayer::new(3, 2, Some(44));
192
193 // Single input
194 let input = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![1, 3]).unwrap();
195 let output = layer.forward_no_grad(&input);
196
197 println!("Single input (no grad):");
198 println!(" Input: {:?}", input.data());
199 println!(" Output: {:?}", output.data());
200 println!(" Output requires grad: {}", output.requires_grad());
201
202 // Compare with grad version
203 let output_with_grad = layer.forward(&input);
204 println!("Comparison:");
205 println!(
206 " Same values: {}",
207 output.data() == output_with_grad.data()
208 );
209 println!(" No grad requires grad: {}", output.requires_grad());
210 println!(
211 " With grad requires grad: {}",
212 output_with_grad.requires_grad()
213 );
214}More examples
159fn demonstrate_gradient_tracking() -> Result<(), Box<dyn std::error::Error>> {
160 println!("\n--- Gradient Tracking ---");
161
162 // Create a tensor with gradient tracking enabled
163 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?.with_requires_grad();
164 println!("Input tensor (requires_grad): {:?}", tensor.data());
165
166 // Perform element-wise operations through iteration
167 let result: Tensor = tensor
168 .iter()
169 .map(|elem| {
170 // Apply a complex transformation: (x^2 + 1) * 2
171 elem.pow_scalar(2.0).add_scalar(1.0).mul_scalar(2.0)
172 })
173 .collect();
174
175 println!("Result tensor: {:?}", result.data());
176 println!("Result requires_grad: {}", result.requires_grad());
177
178 // Compute gradients
179 let mut loss = result.sum();
180 loss.backward(None);
181
182 println!("Loss: {:.6}", loss.value());
183 println!("Input gradients: {:?}", tensor.grad().map(|g| g.data()));
184
185 Ok(())
186}47fn demonstrate_basic_optimizer_setup() {
48 println!("--- Basic Optimizer Setup ---");
49
50 // Create parameters that require gradients
51 let weight = Tensor::randn(vec![3, 2], Some(42)).with_requires_grad();
52 let bias = Tensor::zeros(vec![2]).with_requires_grad();
53
54 println!("Created parameters:");
55 println!(
56 " Weight: shape {:?}, requires_grad: {}",
57 weight.shape().dims,
58 weight.requires_grad()
59 );
60 println!(
61 " Bias: shape {:?}, requires_grad: {}",
62 bias.shape().dims,
63 bias.requires_grad()
64 );
65
66 // Create Adam optimizer with default configuration
67 let mut optimizer = Adam::new();
68 println!(
69 "Created Adam optimizer with learning rate: {}",
70 optimizer.learning_rate()
71 );
72
73 // Add parameters to optimizer
74 optimizer.add_parameter(&weight);
75 optimizer.add_parameter(&bias);
76 println!(
77 "Added {} parameters to optimizer",
78 optimizer.parameter_count()
79 );
80
81 // Create optimizer with custom configuration
82 let config = AdamConfig {
83 learning_rate: 0.01,
84 beta1: 0.9,
85 beta2: 0.999,
86 eps: 1e-8,
87 weight_decay: 0.0,
88 amsgrad: false,
89 };
90
91 let mut custom_optimizer = Adam::with_config(config);
92 custom_optimizer.add_parameter(&weight);
93 custom_optimizer.add_parameter(&bias);
94
95 println!(
96 "Created custom optimizer with learning rate: {}",
97 custom_optimizer.learning_rate()
98 );
99
100 // Demonstrate parameter linking
101 println!("Parameter linking completed successfully");
102}339fn demonstrate_forward_pass() {
340 println!("\n--- Forward Pass ---");
341
342 let config = FeedForwardConfig {
343 input_size: 3,
344 hidden_sizes: vec![5, 3],
345 output_size: 2,
346 use_bias: true,
347 };
348 let network = FeedForwardNetwork::new(config, Some(43));
349
350 // Single input
351 let input = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![1, 3]).unwrap();
352 let output = network.forward(&input);
353
354 println!("Single input forward pass:");
355 println!(" Input shape: {:?}", input.shape().dims);
356 println!(" Output shape: {:?}", output.shape().dims);
357 println!(" Output: {:?}", output.data());
358 println!(" Output requires grad: {}", output.requires_grad());
359
360 // Batch input
361 let batch_input = Tensor::from_slice(
362 &[
363 1.0, 2.0, 3.0, // Sample 1
364 4.0, 5.0, 6.0, // Sample 2
365 7.0, 8.0, 9.0, // Sample 3
366 ],
367 vec![3, 3],
368 )
369 .unwrap();
370 let batch_output = network.forward(&batch_input);
371
372 println!("Batch input forward pass:");
373 println!(" Input shape: {:?}", batch_input.shape().dims);
374 println!(" Output shape: {:?}", batch_output.shape().dims);
375 println!(" Output requires grad: {}", batch_output.requires_grad());
376
377 // Compare with no-grad version
378 let output_no_grad = network.forward_no_grad(&input);
379 println!("No-grad comparison:");
380 println!(" Same values: {}", output.data() == output_no_grad.data());
381 println!(" With grad requires grad: {}", output.requires_grad());
382 println!(
383 " No grad requires grad: {}",
384 output_no_grad.requires_grad()
385 );
386}297fn demonstrate_optimization_techniques() -> Result<(), Box<dyn std::error::Error>> {
298 println!("\n--- Optimization Techniques ---");
299
300 let size = 50000;
301 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
302 let tensor = Tensor::from_slice(&data, vec![size])?;
303
304 println!("Optimizing processing for size: {}", size);
305
306 // Technique 1: Operation fusion
307 println!("\nTechnique 1: Operation Fusion");
308 let start = Instant::now();
309 let fused_result: Tensor = tensor
310 .iter()
311 .map(|elem| {
312 // Fuse multiple operations into single chain
313 elem.mul_scalar(2.0).add_scalar(1.0).pow_scalar(2.0).sqrt()
314 })
315 .collect();
316 let fused_time = start.elapsed();
317
318 // Technique 2: Conditional optimization
319 println!("\nTechnique 2: Conditional Optimization");
320 let start = Instant::now();
321 let conditional_result: Tensor = tensor
322 .iter()
323 .map(|elem| {
324 let val = elem.value();
325 if val < size as f32 / 2.0 {
326 elem.mul_scalar(2.0) // Simple operation for small values
327 } else {
328 elem.pow_scalar(2.0).sqrt() // Complex operation for large values
329 }
330 })
331 .collect();
332 let conditional_time = start.elapsed();
333
334 // Technique 3: Cache-friendly processing
335 println!("\nTechnique 3: Cache-Friendly Processing");
336 let start = Instant::now();
337 let cache_friendly_result: Tensor = tensor
338 .iter()
339 .take(1000) // Process in cache-friendly chunks
340 .map(|elem| elem.mul_scalar(2.0))
341 .collect();
342 let cache_friendly_time = start.elapsed();
343
344 // Technique 4: Memory pooling simulation
345 println!("\nTechnique 4: Memory Pooling Simulation");
346 let start = Instant::now();
347 let pooled_result: Tensor = tensor
348 .iter()
349 .enumerate()
350 .filter(|(i, _)| i % 100 == 0) // Process every 100th element
351 .map(|(_, elem)| elem.pow_scalar(2.0))
352 .collect();
353 let pooled_time = start.elapsed();
354
355 // Report optimization results
356 println!(" Fused operations: {:?}", fused_time);
357 println!(" Conditional optimization: {:?}", conditional_time);
358 println!(" Cache-friendly processing: {:?}", cache_friendly_time);
359 println!(" Memory pooling simulation: {:?}", pooled_time);
360
361 // Performance analysis
362 let fastest = fused_time
363 .min(conditional_time)
364 .min(cache_friendly_time)
365 .min(pooled_time);
366 println!(" Fastest technique: {:?}", fastest);
367
368 // Memory efficiency analysis
369 println!(" Fused result size: {}", fused_result.size());
370 println!(" Conditional result size: {}", conditional_result.size());
371 println!(
372 " Cache-friendly result size: {}",
373 cache_friendly_result.size()
374 );
375 println!(" Pooled result size: {}", pooled_result.size());
376
377 // Technique 5: Gradient optimization
378 println!("\nTechnique 5: Gradient Optimization");
379 let grad_tensor = tensor.with_requires_grad();
380 let start = Instant::now();
381
382 let grad_result: Tensor = grad_tensor
383 .iter()
384 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
385 .collect();
386
387 let mut loss = grad_result.sum();
388 loss.backward(None);
389 let grad_time = start.elapsed();
390
391 println!(" Gradient computation: {:?}", grad_time);
392 println!(
393 " Gradient tracking enabled: {}",
394 grad_result.requires_grad()
395 );
396
397 Ok(())
398}Sourcepub fn grad(&self) -> Option<&Tensor>
pub fn grad(&self) -> Option<&Tensor>
Get the accumulated gradients (if any)
Returns a reference to the gradient tensor if gradients have been computed and this tensor has gradient tracking enabled.
§Returns
Optional reference to the gradient tensor, or None if no gradients exist
§Examples
use train_station::Tensor;
let tensor = Tensor::ones(vec![2, 3]).with_requires_grad();
assert!(tensor.grad().is_none()); // No gradients computed yetExamples found in repository?
159fn demonstrate_gradient_tracking() -> Result<(), Box<dyn std::error::Error>> {
160 println!("\n--- Gradient Tracking ---");
161
162 // Create a tensor with gradient tracking enabled
163 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?.with_requires_grad();
164 println!("Input tensor (requires_grad): {:?}", tensor.data());
165
166 // Perform element-wise operations through iteration
167 let result: Tensor = tensor
168 .iter()
169 .map(|elem| {
170 // Apply a complex transformation: (x^2 + 1) * 2
171 elem.pow_scalar(2.0).add_scalar(1.0).mul_scalar(2.0)
172 })
173 .collect();
174
175 println!("Result tensor: {:?}", result.data());
176 println!("Result requires_grad: {}", result.requires_grad());
177
178 // Compute gradients
179 let mut loss = result.sum();
180 loss.backward(None);
181
182 println!("Loss: {:.6}", loss.value());
183 println!("Input gradients: {:?}", tensor.grad().map(|g| g.data()));
184
185 Ok(())
186}Sourcepub fn grad_by_value(&self) -> Option<Tensor>
pub fn grad_by_value(&self) -> Option<Tensor>
Get accumulated gradient by value (helper for testing)
Returns the gradient tensor by value, which is useful for testing and when you need to own the gradient data.
§Returns
Optional gradient tensor, or None if no gradients exist
§Examples
use train_station::Tensor;
let tensor = Tensor::ones(vec![2, 3]).with_requires_grad();
assert!(tensor.grad_by_value().is_none()); // No gradients computed yetExamples found in repository?
176fn demonstrate_advanced_training() -> Result<(), Box<dyn std::error::Error>> {
177 println!("\n--- Advanced Training Patterns ---");
178
179 // Create a more complex model
180 let mut weight = Tensor::randn(vec![1, 2], Some(44)).with_requires_grad();
181 let mut bias = Tensor::zeros(vec![2]).with_requires_grad();
182
183 // Create optimizer with different learning rate
184 let mut optimizer = Adam::with_learning_rate(0.005);
185 optimizer.add_parameter(&weight);
186 optimizer.add_parameter(&bias);
187
188 // Create training data: y = 2*x + [1, 3]
189 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
190 let y_true = Tensor::from_slice(
191 &[3.0, 5.0, 7.0, 9.0, 11.0, 6.0, 8.0, 10.0, 12.0, 14.0],
192 vec![5, 2],
193 )
194 .unwrap();
195
196 println!("Advanced training with monitoring:");
197 println!(" Initial learning rate: {}", optimizer.learning_rate());
198
199 // Training loop with monitoring
200 let num_epochs = 50;
201 let mut losses = Vec::new();
202 let mut weight_norms = Vec::new();
203 let mut gradient_norms = Vec::new();
204
205 for epoch in 0..num_epochs {
206 // Forward pass
207 let y_pred = x_data.matmul(&weight) + &bias;
208 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
209
210 // Backward pass
211 loss.backward(None);
212
213 // Compute gradient norm before optimizer step
214 let gradient_norm = weight.grad_by_value().unwrap().norm();
215
216 // Optimizer step
217 optimizer.step(&mut [&mut weight, &mut bias]);
218 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
219
220 // Learning rate scheduling: reduce every 10 epochs
221 if epoch > 0 && epoch % 10 == 0 {
222 let current_lr = optimizer.learning_rate();
223 let new_lr = current_lr * 0.5;
224 optimizer.set_learning_rate(new_lr);
225 println!(
226 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
227 epoch, current_lr, new_lr
228 );
229 }
230
231 // Record metrics
232 losses.push(loss.value());
233 weight_norms.push(weight.norm().value());
234 gradient_norms.push(gradient_norm.value());
235
236 // Print detailed progress
237 if epoch % 10 == 0 || epoch == num_epochs - 1 {
238 println!(
239 "Epoch {:2}: Loss = {:.6}, Weight Norm = {:.6}, Gradient Norm = {:.6}",
240 epoch,
241 loss.value(),
242 weight.norm().value(),
243 gradient_norm.value()
244 );
245 }
246 }
247
248 println!("Final learning rate: {}", optimizer.learning_rate());
249
250 // Analyze training progression
251 let initial_loss = losses[0];
252 let final_loss = losses[losses.len() - 1];
253 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
254
255 println!("\nTraining Analysis:");
256 println!(" Initial loss: {:.6}", initial_loss);
257 println!(" Final loss: {:.6}", final_loss);
258 println!(" Loss reduction: {:.1}%", loss_reduction);
259 println!(" Final weight norm: {:.6}", weight.norm().value());
260 println!(" Final bias: {:?}", bias.data());
261
262 Ok(())
263}Sourcepub fn id(&self) -> usize
pub fn id(&self) -> usize
Get the unique ID of this tensor
Returns the unique identifier assigned to this tensor during creation. This ID is used for gradtrack tracking and tensor identification.
§Returns
Unique tensor ID as usize
§Examples
use train_station::Tensor;
let tensor1 = Tensor::new(vec![2, 3]);
let tensor2 = Tensor::new(vec![2, 3]);
assert_ne!(tensor1.id(), tensor2.id()); // Each tensor has unique IDSourcepub fn detach(&self) -> Self
pub fn detach(&self) -> Self
Detach this tensor from the computation graph
Returns a new tensor with the same data but no gradient tracking. This is useful when you want to use a tensor in inference without affecting the computation graph.
§Returns
A new tensor with the same data but gradient tracking disabled
§Examples
use train_station::Tensor;
let tensor = Tensor::ones(vec![2, 3]).with_requires_grad();
let detached = tensor.detach();
assert!(!detached.requires_grad());
assert_eq!(tensor.size(), detached.size());Sourcepub fn detach_(&mut self)
pub fn detach_(&mut self)
Create a new tensor that doesn’t track gradients from this one
Similar to detach() but modifies this tensor in place. This is useful when you want to disable gradient tracking for the current tensor without creating a copy.
§Examples
use train_station::Tensor;
let mut tensor = Tensor::ones(vec![2, 3]).with_requires_grad();
assert!(tensor.requires_grad());
tensor.detach_();
assert!(!tensor.requires_grad());Sourcepub fn backward(&mut self, grad_output: Option<Tensor>)
pub fn backward(&mut self, grad_output: Option<Tensor>)
Entry point for backward pass on this tensor
Computes gradients for all tensors in the computation graph that have
requires_grad set to true. This is the main entry point for automatic
differentiation.
§Arguments
grad_output- Optional gradient tensor for the output. If None, assumes the tensor is a scalar (e.g., loss value) and uses a tensor of ones.
§Examples
use train_station::Tensor;
let mut tensor = Tensor::ones(vec![2, 3]).with_requires_grad();
let mut result = tensor.add_scalar(5.0);
result.backward(None);
// Note: Gradient computation depends on the gradtrack system implementationExamples found in repository?
159fn demonstrate_gradient_tracking() -> Result<(), Box<dyn std::error::Error>> {
160 println!("\n--- Gradient Tracking ---");
161
162 // Create a tensor with gradient tracking enabled
163 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?.with_requires_grad();
164 println!("Input tensor (requires_grad): {:?}", tensor.data());
165
166 // Perform element-wise operations through iteration
167 let result: Tensor = tensor
168 .iter()
169 .map(|elem| {
170 // Apply a complex transformation: (x^2 + 1) * 2
171 elem.pow_scalar(2.0).add_scalar(1.0).mul_scalar(2.0)
172 })
173 .collect();
174
175 println!("Result tensor: {:?}", result.data());
176 println!("Result requires_grad: {}", result.requires_grad());
177
178 // Compute gradients
179 let mut loss = result.sum();
180 loss.backward(None);
181
182 println!("Loss: {:.6}", loss.value());
183 println!("Input gradients: {:?}", tensor.grad().map(|g| g.data()));
184
185 Ok(())
186}More examples
109fn demonstrate_optimizer_serialization() -> Result<(), Box<dyn std::error::Error>> {
110 println!("\n--- Optimizer Serialization ---");
111
112 // Create an optimizer with some parameters
113 let mut weight = Tensor::randn(vec![2, 2], Some(42)).with_requires_grad();
114 let mut bias = Tensor::randn(vec![2], Some(43)).with_requires_grad();
115
116 let config = AdamConfig {
117 learning_rate: 0.001,
118 beta1: 0.9,
119 beta2: 0.999,
120 eps: 1e-8,
121 weight_decay: 0.0,
122 amsgrad: false,
123 };
124
125 let mut optimizer = Adam::with_config(config);
126 optimizer.add_parameter(&weight);
127 optimizer.add_parameter(&bias);
128
129 println!(
130 "Created optimizer with {} parameters",
131 optimizer.parameter_count()
132 );
133 println!("Learning rate: {}", optimizer.learning_rate());
134
135 // Simulate some training steps
136 for _ in 0..3 {
137 let mut loss = weight.sum() + bias.sum();
138 loss.backward(None);
139 optimizer.step(&mut [&mut weight, &mut bias]);
140 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
141 }
142
143 // Save optimizer state
144 let optimizer_path = "temp_optimizer.json";
145 optimizer.save_json(optimizer_path)?;
146 println!("Saved optimizer to: {}", optimizer_path);
147
148 // Load optimizer state
149 let loaded_optimizer = Adam::load_json(optimizer_path)?;
150 println!(
151 "Loaded optimizer with {} parameters",
152 loaded_optimizer.parameter_count()
153 );
154 println!("Learning rate: {}", loaded_optimizer.learning_rate());
155
156 // Verify optimizer state
157 assert_eq!(
158 optimizer.parameter_count(),
159 loaded_optimizer.parameter_count()
160 );
161 assert_eq!(optimizer.learning_rate(), loaded_optimizer.learning_rate());
162 println!("Optimizer serialization verification: PASSED");
163
164 Ok(())
165}
166
167/// Demonstrate format comparison and performance characteristics
168fn demonstrate_format_comparison() -> Result<(), Box<dyn std::error::Error>> {
169 println!("\n--- Format Comparison ---");
170
171 // Create a larger tensor for comparison
172 let tensor = Tensor::randn(vec![10, 10], Some(44));
173
174 // Save in both formats
175 tensor.save_json("temp_comparison.json")?;
176 tensor.save_binary("temp_comparison.bin")?;
177
178 // Compare file sizes
179 let json_size = fs::metadata("temp_comparison.json")?.len();
180 let binary_size = fs::metadata("temp_comparison.bin")?.len();
181
182 println!("JSON file size: {} bytes", json_size);
183 println!("Binary file size: {} bytes", binary_size);
184 println!(
185 "Compression ratio: {:.2}x",
186 json_size as f64 / binary_size as f64
187 );
188
189 // Load and verify both formats
190 let json_tensor = Tensor::load_json("temp_comparison.json")?;
191 let binary_tensor = Tensor::load_binary("temp_comparison.bin")?;
192
193 assert_eq!(tensor.shape().dims, json_tensor.shape().dims);
194 assert_eq!(tensor.shape().dims, binary_tensor.shape().dims);
195 assert_eq!(tensor.data(), json_tensor.data());
196 assert_eq!(tensor.data(), binary_tensor.data());
197
198 println!("Format comparison verification: PASSED");
199
200 Ok(())
201}
202
203/// Demonstrate a basic model checkpointing workflow
204fn demonstrate_model_checkpointing() -> Result<(), Box<dyn std::error::Error>> {
205 println!("\n--- Model Checkpointing ---");
206
207 // Create a simple model (weights and bias)
208 let mut weights = Tensor::randn(vec![2, 1], Some(45)).with_requires_grad();
209 let mut bias = Tensor::randn(vec![1], Some(46)).with_requires_grad();
210
211 // Create optimizer
212 let mut optimizer = Adam::with_learning_rate(0.01);
213 optimizer.add_parameter(&weights);
214 optimizer.add_parameter(&bias);
215
216 println!("Initial weights: {:?}", weights.data());
217 println!("Initial bias: {:?}", bias.data());
218
219 // Simulate training
220 for epoch in 0..5 {
221 let mut loss = weights.sum() + bias.sum();
222 loss.backward(None);
223 optimizer.step(&mut [&mut weights, &mut bias]);
224 optimizer.zero_grad(&mut [&mut weights, &mut bias]);
225
226 if epoch % 2 == 0 {
227 // Save checkpoint
228 let checkpoint_dir = format!("checkpoint_epoch_{}", epoch);
229 fs::create_dir_all(&checkpoint_dir)?;
230
231 weights.save_json(format!("{}/weights.json", checkpoint_dir))?;
232 bias.save_json(format!("{}/bias.json", checkpoint_dir))?;
233 optimizer.save_json(format!("{}/optimizer.json", checkpoint_dir))?;
234
235 println!("Saved checkpoint for epoch {}", epoch);
236 }
237 }
238
239 // Load from checkpoint
240 let loaded_weights = Tensor::load_json("checkpoint_epoch_4/weights.json")?;
241 let loaded_bias = Tensor::load_json("checkpoint_epoch_4/bias.json")?;
242 let loaded_optimizer = Adam::load_json("checkpoint_epoch_4/optimizer.json")?;
243
244 println!("Loaded weights: {:?}", loaded_weights.data());
245 println!("Loaded bias: {:?}", loaded_bias.data());
246 println!(
247 "Loaded optimizer learning rate: {}",
248 loaded_optimizer.learning_rate()
249 );
250
251 // Verify checkpoint integrity
252 assert_eq!(weights.shape().dims, loaded_weights.shape().dims);
253 assert_eq!(bias.shape().dims, loaded_bias.shape().dims);
254 assert_eq!(optimizer.learning_rate(), loaded_optimizer.learning_rate());
255
256 println!("Checkpointing verification: PASSED");
257
258 Ok(())
259}84fn demonstrate_default_adam() -> Result<(), Box<dyn std::error::Error>> {
85 println!("--- Default Adam Configuration ---");
86
87 // Create a simple regression problem: y = 2*x + 1
88 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
89 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
90
91 // Create model parameters
92 let mut weight = Tensor::randn(vec![1, 1], Some(42)).with_requires_grad();
93 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
94
95 // Create Adam optimizer with default configuration
96 let mut optimizer = Adam::new();
97 optimizer.add_parameter(&weight);
98 optimizer.add_parameter(&bias);
99
100 println!("Default Adam configuration:");
101 println!(" Learning rate: {}", optimizer.learning_rate());
102 println!(" Initial weight: {:.6}", weight.value());
103 println!(" Initial bias: {:.6}", bias.value());
104
105 // Training loop
106 let num_epochs = 50;
107 let mut losses = Vec::new();
108
109 for epoch in 0..num_epochs {
110 // Forward pass
111 let y_pred = x_data.matmul(&weight) + &bias;
112 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
113
114 // Backward pass
115 loss.backward(None);
116
117 // Optimizer step
118 optimizer.step(&mut [&mut weight, &mut bias]);
119 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
120
121 losses.push(loss.value());
122
123 if epoch % 10 == 0 || epoch == num_epochs - 1 {
124 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
125 }
126 }
127
128 // Evaluate final model
129 let _final_predictions = x_data.matmul(&weight) + &bias;
130 println!("\nFinal model:");
131 println!(" Learned weight: {:.6} (target: 2.0)", weight.value());
132 println!(" Learned bias: {:.6} (target: 1.0)", bias.value());
133 println!(" Final loss: {:.6}", losses[losses.len() - 1]);
134
135 Ok(())
136}
137
138/// Demonstrate learning rate comparison
139fn demonstrate_learning_rate_comparison() -> Result<(), Box<dyn std::error::Error>> {
140 println!("\n--- Learning Rate Comparison ---");
141
142 let learning_rates = [0.001, 0.01, 0.1];
143 let mut results = Vec::new();
144
145 for &lr in &learning_rates {
146 println!("\nTesting learning rate: {}", lr);
147
148 let stats = train_with_config(TrainingConfig {
149 learning_rate: lr,
150 ..Default::default()
151 })?;
152
153 results.push((lr, stats.clone()));
154
155 println!(" Final loss: {:.6}", stats.final_loss);
156 println!(" Convergence epoch: {}", stats.convergence_epoch);
157 }
158
159 // Compare results
160 println!("\nLearning Rate Comparison Summary:");
161 for (lr, stats) in &results {
162 println!(
163 " LR={:6}: Loss={:.6}, Converged@{}",
164 lr, stats.final_loss, stats.convergence_epoch
165 );
166 }
167
168 Ok(())
169}
170
171/// Demonstrate weight decay comparison
172fn demonstrate_weight_decay_comparison() -> Result<(), Box<dyn std::error::Error>> {
173 println!("\n--- Weight Decay Comparison ---");
174
175 let weight_decays = [0.0, 0.001, 0.01];
176 let mut results = Vec::new();
177
178 for &wd in &weight_decays {
179 println!("\nTesting weight decay: {}", wd);
180
181 let stats = train_with_config(TrainingConfig {
182 weight_decay: wd,
183 ..Default::default()
184 })?;
185
186 results.push((wd, stats.clone()));
187
188 println!(" Final loss: {:.6}", stats.final_loss);
189 println!(" Final weight norm: {:.6}", stats.weight_norm);
190 }
191
192 // Compare results
193 println!("\nWeight Decay Comparison Summary:");
194 for (wd, stats) in &results {
195 println!(
196 " WD={:6}: Loss={:.6}, Weight Norm={:.6}",
197 wd, stats.final_loss, stats.weight_norm
198 );
199 }
200
201 Ok(())
202}
203
204/// Demonstrate beta parameter tuning
205fn demonstrate_beta_parameter_tuning() -> Result<(), Box<dyn std::error::Error>> {
206 println!("\n--- Beta Parameter Tuning ---");
207
208 let beta_configs = [
209 (0.9, 0.999), // Default
210 (0.8, 0.999), // More aggressive momentum
211 (0.95, 0.999), // Less aggressive momentum
212 (0.9, 0.99), // Faster second moment decay
213 ];
214
215 let mut results = Vec::new();
216
217 for (i, (beta1, beta2)) in beta_configs.iter().enumerate() {
218 println!(
219 "\nTesting beta configuration {}: beta1={}, beta2={}",
220 i + 1,
221 beta1,
222 beta2
223 );
224
225 let config = TrainingConfig {
226 beta1: *beta1,
227 beta2: *beta2,
228 ..Default::default()
229 };
230
231 let stats = train_with_config(config)?;
232 results.push(((*beta1, *beta2), stats.clone()));
233
234 println!(" Final loss: {:.6}", stats.final_loss);
235 println!(" Convergence epoch: {}", stats.convergence_epoch);
236 }
237
238 // Compare results
239 println!("\nBeta Parameter Comparison Summary:");
240 for ((beta1, beta2), stats) in &results {
241 println!(
242 " B1={:4}, B2={:5}: Loss={:.6}, Converged@{}",
243 beta1, beta2, stats.final_loss, stats.convergence_epoch
244 );
245 }
246
247 Ok(())
248}
249
250/// Demonstrate configuration benchmarking
251fn demonstrate_configuration_benchmarking() -> Result<(), Box<dyn std::error::Error>> {
252 println!("\n--- Configuration Benchmarking ---");
253
254 // Define configurations to benchmark
255 let configs = vec![
256 (
257 "Conservative",
258 TrainingConfig {
259 learning_rate: 0.001,
260 weight_decay: 0.001,
261 beta1: 0.95,
262 ..Default::default()
263 },
264 ),
265 (
266 "Balanced",
267 TrainingConfig {
268 learning_rate: 0.01,
269 weight_decay: 0.0,
270 beta1: 0.9,
271 ..Default::default()
272 },
273 ),
274 (
275 "Aggressive",
276 TrainingConfig {
277 learning_rate: 0.1,
278 weight_decay: 0.0,
279 beta1: 0.8,
280 ..Default::default()
281 },
282 ),
283 ];
284
285 let mut benchmark_results = Vec::new();
286
287 for (name, config) in configs {
288 println!("\nBenchmarking {} configuration:", name);
289
290 let start_time = std::time::Instant::now();
291 let stats = train_with_config(config.clone())?;
292 let elapsed = start_time.elapsed();
293
294 println!(" Training time: {:.2}ms", elapsed.as_millis());
295 println!(" Final loss: {:.6}", stats.final_loss);
296 println!(" Convergence: {} epochs", stats.convergence_epoch);
297
298 benchmark_results.push((name.to_string(), stats, elapsed));
299 }
300
301 // Summary
302 println!("\nBenchmarking Summary:");
303 for (name, stats, elapsed) in &benchmark_results {
304 println!(
305 " {:12}: Loss={:.6}, Time={:4}ms, Converged@{}",
306 name,
307 stats.final_loss,
308 elapsed.as_millis(),
309 stats.convergence_epoch
310 );
311 }
312
313 Ok(())
314}
315
316/// Helper function to train with specific configuration
317fn train_with_config(config: TrainingConfig) -> Result<TrainingStats, Box<dyn std::error::Error>> {
318 // Create training data
319 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
320 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
321
322 // Create model parameters
323 let mut weight = Tensor::randn(vec![1, 1], Some(123)).with_requires_grad();
324 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
325
326 // Create optimizer with custom configuration
327 let adam_config = AdamConfig {
328 learning_rate: config.learning_rate,
329 beta1: config.beta1,
330 beta2: config.beta2,
331 eps: 1e-8,
332 weight_decay: config.weight_decay,
333 amsgrad: false,
334 };
335
336 let mut optimizer = Adam::with_config(adam_config);
337 optimizer.add_parameter(&weight);
338 optimizer.add_parameter(&bias);
339
340 // Training loop
341 let mut losses = Vec::new();
342 let mut convergence_epoch = config.epochs;
343
344 for epoch in 0..config.epochs {
345 // Forward pass
346 let y_pred = x_data.matmul(&weight) + &bias;
347 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
348
349 // Backward pass
350 loss.backward(None);
351
352 // Optimizer step
353 optimizer.step(&mut [&mut weight, &mut bias]);
354 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
355
356 let loss_value = loss.value();
357 losses.push(loss_value);
358
359 // Check for convergence (loss < 0.01)
360 if loss_value < 0.01 && convergence_epoch == config.epochs {
361 convergence_epoch = epoch;
362 }
363 }
364
365 Ok(TrainingStats {
366 config,
367 final_loss: losses[losses.len() - 1],
368 loss_history: losses,
369 convergence_epoch,
370 weight_norm: weight.norm().value(),
371 })
372}319fn train_with_scheduler(
320 scheduler: &mut dyn LearningRateScheduler,
321 num_epochs: usize,
322) -> Result<TrainingStats, Box<dyn std::error::Error>> {
323 // Create training data: y = 2*x + 1
324 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
325 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
326
327 // Create model parameters
328 let mut weight = Tensor::randn(vec![1, 1], Some(456)).with_requires_grad();
329 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
330
331 // Create optimizer with initial learning rate
332 let mut optimizer = Adam::with_learning_rate(0.05);
333 optimizer.add_parameter(&weight);
334 optimizer.add_parameter(&bias);
335
336 // Training loop
337 let mut losses = Vec::new();
338 let mut lr_history = Vec::new();
339 let mut convergence_epoch = num_epochs;
340
341 for epoch in 0..num_epochs {
342 // Forward pass
343 let y_pred = x_data.matmul(&weight) + &bias;
344 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
345
346 // Backward pass
347 loss.backward(None);
348
349 // Update learning rate using scheduler
350 let current_lr = optimizer.learning_rate();
351 let new_lr = scheduler.step(current_lr, epoch, loss.value());
352
353 if (new_lr - current_lr).abs() > 1e-8 {
354 optimizer.set_learning_rate(new_lr);
355 }
356
357 // Optimizer step
358 optimizer.step(&mut [&mut weight, &mut bias]);
359 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
360
361 let loss_value = loss.value();
362 losses.push(loss_value);
363 lr_history.push(new_lr);
364
365 // Check for convergence
366 if loss_value < 0.01 && convergence_epoch == num_epochs {
367 convergence_epoch = epoch;
368 }
369 }
370
371 Ok(TrainingStats {
372 scheduler_name: scheduler.name().to_string(),
373 final_loss: losses[losses.len() - 1],
374 lr_history,
375 loss_history: losses,
376 convergence_epoch,
377 })
378}105fn demonstrate_linear_regression() -> Result<(), Box<dyn std::error::Error>> {
106 println!("\n--- Linear Regression Training ---");
107
108 // Create model parameters
109 let mut weight = Tensor::randn(vec![1, 1], Some(43)).with_requires_grad();
110 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
111
112 // Create optimizer
113 let mut optimizer = Adam::with_learning_rate(0.01);
114 optimizer.add_parameter(&weight);
115 optimizer.add_parameter(&bias);
116
117 // Create simple training data: y = 2*x + 1
118 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
119 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
120
121 println!("Training data:");
122 println!(" X: {:?}", x_data.data());
123 println!(" Y: {:?}", y_true.data());
124 println!(" Target: y = 2*x + 1");
125
126 // Training loop
127 let num_epochs = 100;
128 let mut losses = Vec::new();
129
130 for epoch in 0..num_epochs {
131 // Forward pass: y_pred = x * weight + bias
132 let y_pred = x_data.matmul(&weight) + &bias;
133
134 // Compute loss: MSE
135 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
136
137 // Backward pass
138 loss.backward(None);
139
140 // Optimizer step
141 optimizer.step(&mut [&mut weight, &mut bias]);
142 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
143
144 losses.push(loss.value());
145
146 // Print progress every 20 epochs
147 if epoch % 20 == 0 || epoch == num_epochs - 1 {
148 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
149 }
150 }
151
152 // Evaluate final model
153 let final_predictions = x_data.matmul(&weight) + &bias;
154 println!("\nFinal model evaluation:");
155 println!(" Learned weight: {:.6}", weight.value());
156 println!(" Learned bias: {:.6}", bias.value());
157 println!(" Predictions vs True:");
158
159 for i in 0..5 {
160 let x1 = x_data.data()[i];
161 let pred = final_predictions.data()[i];
162 let true_val = y_true.data()[i];
163 println!(
164 " x={:.1}: pred={:.3}, true={:.1}, error={:.3}",
165 x1,
166 pred,
167 true_val,
168 (pred - true_val).abs()
169 );
170 }
171
172 Ok(())
173}
174
175/// Demonstrate advanced training patterns
176fn demonstrate_advanced_training() -> Result<(), Box<dyn std::error::Error>> {
177 println!("\n--- Advanced Training Patterns ---");
178
179 // Create a more complex model
180 let mut weight = Tensor::randn(vec![1, 2], Some(44)).with_requires_grad();
181 let mut bias = Tensor::zeros(vec![2]).with_requires_grad();
182
183 // Create optimizer with different learning rate
184 let mut optimizer = Adam::with_learning_rate(0.005);
185 optimizer.add_parameter(&weight);
186 optimizer.add_parameter(&bias);
187
188 // Create training data: y = 2*x + [1, 3]
189 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
190 let y_true = Tensor::from_slice(
191 &[3.0, 5.0, 7.0, 9.0, 11.0, 6.0, 8.0, 10.0, 12.0, 14.0],
192 vec![5, 2],
193 )
194 .unwrap();
195
196 println!("Advanced training with monitoring:");
197 println!(" Initial learning rate: {}", optimizer.learning_rate());
198
199 // Training loop with monitoring
200 let num_epochs = 50;
201 let mut losses = Vec::new();
202 let mut weight_norms = Vec::new();
203 let mut gradient_norms = Vec::new();
204
205 for epoch in 0..num_epochs {
206 // Forward pass
207 let y_pred = x_data.matmul(&weight) + &bias;
208 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
209
210 // Backward pass
211 loss.backward(None);
212
213 // Compute gradient norm before optimizer step
214 let gradient_norm = weight.grad_by_value().unwrap().norm();
215
216 // Optimizer step
217 optimizer.step(&mut [&mut weight, &mut bias]);
218 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
219
220 // Learning rate scheduling: reduce every 10 epochs
221 if epoch > 0 && epoch % 10 == 0 {
222 let current_lr = optimizer.learning_rate();
223 let new_lr = current_lr * 0.5;
224 optimizer.set_learning_rate(new_lr);
225 println!(
226 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
227 epoch, current_lr, new_lr
228 );
229 }
230
231 // Record metrics
232 losses.push(loss.value());
233 weight_norms.push(weight.norm().value());
234 gradient_norms.push(gradient_norm.value());
235
236 // Print detailed progress
237 if epoch % 10 == 0 || epoch == num_epochs - 1 {
238 println!(
239 "Epoch {:2}: Loss = {:.6}, Weight Norm = {:.6}, Gradient Norm = {:.6}",
240 epoch,
241 loss.value(),
242 weight.norm().value(),
243 gradient_norm.value()
244 );
245 }
246 }
247
248 println!("Final learning rate: {}", optimizer.learning_rate());
249
250 // Analyze training progression
251 let initial_loss = losses[0];
252 let final_loss = losses[losses.len() - 1];
253 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
254
255 println!("\nTraining Analysis:");
256 println!(" Initial loss: {:.6}", initial_loss);
257 println!(" Final loss: {:.6}", final_loss);
258 println!(" Loss reduction: {:.1}%", loss_reduction);
259 println!(" Final weight norm: {:.6}", weight.norm().value());
260 println!(" Final bias: {:?}", bias.data());
261
262 Ok(())
263}
264
265/// Demonstrate learning rate scheduling
266fn demonstrate_learning_rate_scheduling() -> Result<(), Box<dyn std::error::Error>> {
267 println!("\n--- Learning Rate Scheduling ---");
268
269 // Create simple model
270 let mut weight = Tensor::randn(vec![1, 1], Some(45)).with_requires_grad();
271 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
272
273 // Create optimizer with high initial learning rate
274 let mut optimizer = Adam::with_learning_rate(0.1);
275 optimizer.add_parameter(&weight);
276 optimizer.add_parameter(&bias);
277
278 // Simple data
279 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3, 1]).unwrap();
280 let y_true = Tensor::from_slice(&[2.0, 4.0, 6.0], vec![3, 1]).unwrap();
281
282 println!("Initial learning rate: {}", optimizer.learning_rate());
283
284 // Training loop with learning rate scheduling
285 let num_epochs = 50;
286 let mut losses = Vec::new();
287
288 for epoch in 0..num_epochs {
289 // Forward pass
290 let y_pred = x_data.matmul(&weight) + &bias;
291 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
292
293 // Backward pass
294 loss.backward(None);
295
296 // Optimizer step
297 optimizer.step(&mut [&mut weight, &mut bias]);
298 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
299
300 // Learning rate scheduling: reduce every 10 epochs
301 if epoch > 0 && epoch % 10 == 0 {
302 let current_lr = optimizer.learning_rate();
303 let new_lr = current_lr * 0.5;
304 optimizer.set_learning_rate(new_lr);
305 println!(
306 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
307 epoch, current_lr, new_lr
308 );
309 }
310
311 losses.push(loss.value());
312
313 // Print progress
314 if epoch % 10 == 0 || epoch == num_epochs - 1 {
315 println!(
316 "Epoch {:2}: Loss = {:.6}, LR = {:.3}",
317 epoch,
318 loss.value(),
319 optimizer.learning_rate()
320 );
321 }
322 }
323
324 println!("Final learning rate: {}", optimizer.learning_rate());
325
326 Ok(())
327}
328
329/// Demonstrate training monitoring and analysis
330fn demonstrate_training_monitoring() -> Result<(), Box<dyn std::error::Error>> {
331 println!("\n--- Training Monitoring ---");
332
333 // Create model
334 let mut weight = Tensor::randn(vec![1, 1], Some(46)).with_requires_grad();
335 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
336
337 // Create optimizer
338 let mut optimizer = Adam::with_learning_rate(0.01);
339 optimizer.add_parameter(&weight);
340 optimizer.add_parameter(&bias);
341
342 // Training data
343 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4, 1]).unwrap();
344 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0], vec![4, 1]).unwrap();
345
346 // Training loop with comprehensive monitoring
347 let num_epochs = 30;
348 let mut losses = Vec::new();
349 let mut weight_history = Vec::new();
350 let mut bias_history = Vec::new();
351
352 for epoch in 0..num_epochs {
353 // Forward pass
354 let y_pred = x_data.matmul(&weight) + &bias;
355 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
356
357 // Backward pass
358 loss.backward(None);
359
360 // Optimizer step
361 optimizer.step(&mut [&mut weight, &mut bias]);
362 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
363
364 // Record history
365 losses.push(loss.value());
366 weight_history.push(weight.value());
367 bias_history.push(bias.value());
368
369 // Print detailed monitoring
370 if epoch % 5 == 0 || epoch == num_epochs - 1 {
371 println!(
372 "Epoch {:2}: Loss = {:.6}, Weight = {:.6}, Bias = {:.6}",
373 epoch,
374 loss.value(),
375 weight.value(),
376 bias.value()
377 );
378 }
379 }
380
381 // Analyze training progression
382 println!("\nTraining Analysis:");
383 println!(" Initial loss: {:.6}", losses[0]);
384 println!(" Final loss: {:.6}", losses[losses.len() - 1]);
385 println!(
386 " Loss reduction: {:.1}%",
387 (losses[0] - losses[losses.len() - 1]) / losses[0] * 100.0
388 );
389
390 // Compute statistics
391 let loss_mean = compute_mean(&losses);
392 let loss_std = compute_std(&losses);
393 let weight_change = (weight_history[weight_history.len() - 1] - weight_history[0]).abs();
394 let bias_change = (bias_history[bias_history.len() - 1] - bias_history[0]).abs();
395
396 println!(" Average loss: {:.6} ± {:.6}", loss_mean, loss_std);
397 println!(" Weight change: {:.6}", weight_change);
398 println!(" Bias change: {:.6}", bias_change);
399 println!(" Final weight norm: {:.6}", weight.norm().value());
400 println!(" Final bias: {:.6}", bias.value());
401
402 Ok(())
403}430fn demonstrate_training_workflow() -> Result<(), Box<dyn std::error::Error>> {
431 println!("\n--- Training Workflow ---");
432
433 // Create a simple classification network
434 let config = FeedForwardConfig {
435 input_size: 2,
436 hidden_sizes: vec![4, 3],
437 output_size: 1,
438 use_bias: true,
439 };
440 let mut network = FeedForwardNetwork::new(config, Some(46));
441
442 println!("Training network: 2 -> [4, 3] -> 1");
443
444 // Create simple binary classification data: XOR problem
445 let x_data = Tensor::from_slice(
446 &[
447 0.0, 0.0, // -> 0
448 0.0, 1.0, // -> 1
449 1.0, 0.0, // -> 1
450 1.0, 1.0, // -> 0
451 ],
452 vec![4, 2],
453 )
454 .unwrap();
455
456 let y_true = Tensor::from_slice(&[0.0, 1.0, 1.0, 0.0], vec![4, 1]).unwrap();
457
458 println!("Training on XOR problem:");
459 println!(" Input shape: {:?}", x_data.shape().dims);
460 println!(" Target shape: {:?}", y_true.shape().dims);
461
462 // Create optimizer
463 let mut optimizer = Adam::with_learning_rate(0.1);
464 let params = network.parameters();
465 for param in ¶ms {
466 optimizer.add_parameter(param);
467 }
468
469 // Training loop
470 let num_epochs = 50;
471 let mut losses = Vec::new();
472
473 for epoch in 0..num_epochs {
474 // Forward pass
475 let y_pred = network.forward(&x_data);
476
477 // Compute loss: MSE
478 let diff = y_pred.sub_tensor(&y_true);
479 let mut loss = diff.pow_scalar(2.0).mean();
480
481 // Backward pass
482 loss.backward(None);
483
484 // Optimizer step and zero grad
485 let mut params = network.parameters();
486 optimizer.step(&mut params);
487 optimizer.zero_grad(&mut params);
488
489 losses.push(loss.value());
490
491 // Print progress
492 if epoch % 10 == 0 || epoch == num_epochs - 1 {
493 println!("Epoch {:2}: Loss = {:.6}", epoch, loss.value());
494 }
495 }
496
497 // Test final model
498 let final_predictions = network.forward_no_grad(&x_data);
499 println!("\nFinal predictions vs targets:");
500 for i in 0..4 {
501 let pred = final_predictions.data()[i];
502 let target = y_true.data()[i];
503 let input_x = x_data.data()[i * 2];
504 let input_y = x_data.data()[i * 2 + 1];
505 println!(
506 " [{:.0}, {:.0}] -> pred: {:.3}, target: {:.0}, error: {:.3}",
507 input_x,
508 input_y,
509 pred,
510 target,
511 (pred - target).abs()
512 );
513 }
514
515 Ok(())
516}
517
518/// Demonstrate comprehensive training with 100+ steps
519fn demonstrate_comprehensive_training() -> Result<(), Box<dyn std::error::Error>> {
520 println!("\n--- Comprehensive Training (100+ Steps) ---");
521
522 // Create a regression network
523 let config = FeedForwardConfig {
524 input_size: 3,
525 hidden_sizes: vec![8, 6, 4],
526 output_size: 2,
527 use_bias: true,
528 };
529 let mut network = FeedForwardNetwork::new(config, Some(47));
530
531 println!("Network architecture: 3 -> [8, 6, 4] -> 2");
532 println!("Total parameters: {}", network.parameter_count());
533
534 // Create synthetic regression data
535 // Target function: [y1, y2] = [x1 + 2*x2 - x3, x1*x2 + x3]
536 let num_samples = 32;
537 let mut x_vec = Vec::new();
538 let mut y_vec = Vec::new();
539
540 for i in 0..num_samples {
541 let x1 = (i as f32 / num_samples as f32) * 2.0 - 1.0; // [-1, 1]
542 let x2 = ((i * 2) as f32 / num_samples as f32) * 2.0 - 1.0;
543 let x3 = ((i * 3) as f32 / num_samples as f32) * 2.0 - 1.0;
544
545 let y1 = x1 + 2.0 * x2 - x3;
546 let y2 = x1 * x2 + x3;
547
548 x_vec.extend_from_slice(&[x1, x2, x3]);
549 y_vec.extend_from_slice(&[y1, y2]);
550 }
551
552 let x_data = Tensor::from_slice(&x_vec, vec![num_samples, 3]).unwrap();
553 let y_true = Tensor::from_slice(&y_vec, vec![num_samples, 2]).unwrap();
554
555 println!("Training data:");
556 println!(" {} samples", num_samples);
557 println!(" Input shape: {:?}", x_data.shape().dims);
558 println!(" Target shape: {:?}", y_true.shape().dims);
559
560 // Create optimizer with learning rate scheduling
561 let mut optimizer = Adam::with_learning_rate(0.01);
562 let params = network.parameters();
563 for param in ¶ms {
564 optimizer.add_parameter(param);
565 }
566
567 // Comprehensive training loop (150 epochs)
568 let num_epochs = 150;
569 let mut losses = Vec::new();
570 let mut best_loss = f32::INFINITY;
571 let mut patience_counter = 0;
572 let patience = 20;
573
574 println!("Starting comprehensive training...");
575
576 for epoch in 0..num_epochs {
577 // Forward pass
578 let y_pred = network.forward(&x_data);
579
580 // Compute loss: MSE
581 let diff = y_pred.sub_tensor(&y_true);
582 let mut loss = diff.pow_scalar(2.0).mean();
583
584 // Backward pass
585 loss.backward(None);
586
587 // Optimizer step and zero grad
588 let mut params = network.parameters();
589 optimizer.step(&mut params);
590 optimizer.zero_grad(&mut params);
591
592 let current_loss = loss.value();
593 losses.push(current_loss);
594
595 // Learning rate scheduling
596 if epoch > 0 && epoch % 30 == 0 {
597 let new_lr = optimizer.learning_rate() * 0.8;
598 optimizer.set_learning_rate(new_lr);
599 println!(" Reduced learning rate to {:.4}", new_lr);
600 }
601
602 // Early stopping logic
603 if current_loss < best_loss {
604 best_loss = current_loss;
605 patience_counter = 0;
606 } else {
607 patience_counter += 1;
608 }
609
610 // Print progress
611 if epoch % 25 == 0 || epoch == num_epochs - 1 {
612 println!(
613 "Epoch {:3}: Loss = {:.6}, LR = {:.4}, Best = {:.6}",
614 epoch,
615 current_loss,
616 optimizer.learning_rate(),
617 best_loss
618 );
619 }
620
621 // Early stopping
622 if patience_counter >= patience && epoch > 50 {
623 println!("Early stopping at epoch {} (patience exceeded)", epoch);
624 break;
625 }
626 }
627
628 // Final evaluation
629 let final_predictions = network.forward_no_grad(&x_data);
630
631 // Compute final metrics
632 let final_loss = losses[losses.len() - 1];
633 let initial_loss = losses[0];
634 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
635
636 println!("\nTraining completed!");
637 println!(" Initial loss: {:.6}", initial_loss);
638 println!(" Final loss: {:.6}", final_loss);
639 println!(" Best loss: {:.6}", best_loss);
640 println!(" Loss reduction: {:.1}%", loss_reduction);
641 println!(" Final learning rate: {:.4}", optimizer.learning_rate());
642
643 // Sample predictions analysis
644 println!("\nSample predictions (first 5):");
645 for i in 0..5.min(num_samples) {
646 let pred1 = final_predictions.data()[i * 2];
647 let pred2 = final_predictions.data()[i * 2 + 1];
648 let true1 = y_true.data()[i * 2];
649 let true2 = y_true.data()[i * 2 + 1];
650
651 println!(
652 " Sample {}: pred=[{:.3}, {:.3}], true=[{:.3}, {:.3}], error=[{:.3}, {:.3}]",
653 i + 1,
654 pred1,
655 pred2,
656 true1,
657 true2,
658 (pred1 - true1).abs(),
659 (pred2 - true2).abs()
660 );
661 }
662
663 Ok(())
664}
665
666/// Demonstrate network serialization
667fn demonstrate_network_serialization() -> Result<(), Box<dyn std::error::Error>> {
668 println!("\n--- Network Serialization ---");
669
670 // Create and train a network
671 let config = FeedForwardConfig {
672 input_size: 2,
673 hidden_sizes: vec![4, 2],
674 output_size: 1,
675 use_bias: true,
676 };
677 let mut original_network = FeedForwardNetwork::new(config.clone(), Some(48));
678
679 // Quick training
680 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
681 let y_true = Tensor::from_slice(&[5.0, 11.0], vec![2, 1]).unwrap();
682
683 let mut optimizer = Adam::with_learning_rate(0.01);
684 let params = original_network.parameters();
685 for param in ¶ms {
686 optimizer.add_parameter(param);
687 }
688
689 for _ in 0..20 {
690 let y_pred = original_network.forward(&x_data);
691 let mut loss = (y_pred.sub_tensor(&y_true)).pow_scalar(2.0).mean();
692 loss.backward(None);
693
694 let mut params = original_network.parameters();
695 optimizer.step(&mut params);
696 optimizer.zero_grad(&mut params);
697 }
698
699 // Test original network
700 let test_input = Tensor::from_slice(&[1.0, 1.0], vec![1, 2]).unwrap();
701 let original_output = original_network.forward_no_grad(&test_input);
702
703 println!("Original network output: {:?}", original_output.data());
704
705 // Save network
706 original_network.save_json("temp_feedforward_network")?;
707
708 // Load network
709 let loaded_network = FeedForwardNetwork::load_json("temp_feedforward_network", config)?;
710 let loaded_output = loaded_network.forward_no_grad(&test_input);
711
712 println!("Loaded network output: {:?}", loaded_output.data());
713
714 // Verify consistency
715 let match_check = original_output
716 .data()
717 .iter()
718 .zip(loaded_output.data().iter())
719 .all(|(a, b)| (a - b).abs() < 1e-6);
720
721 println!(
722 "Serialization verification: {}",
723 if match_check { "PASSED" } else { "FAILED" }
724 );
725
726 Ok(())
727}Sourcepub unsafe fn as_ptr(&self) -> *const f32
pub unsafe fn as_ptr(&self) -> *const f32
Returns a raw pointer to the tensor data for unsafe operations
§Safety
This is unsafe because it provides direct access to the underlying memory. The caller must ensure:
- The tensor is not dropped while the pointer is used
- No concurrent mutable access occurs
- Bounds are respected
Sourcepub unsafe fn as_mut_ptr(&mut self) -> *mut f32
pub unsafe fn as_mut_ptr(&mut self) -> *mut f32
Returns a mutable raw pointer to the tensor data for unsafe operations
§Safety
This is unsafe because it provides direct mutable access to the underlying memory. The caller must ensure:
- The tensor is not dropped while the pointer is used
- No concurrent access occurs
- Bounds are respected
Sourcepub fn grad_fn(&self) -> &GradFn
pub fn grad_fn(&self) -> &GradFn
Get a reference to the gradient function (for gradtrack)
Returns a reference to the gradient function associated with this tensor. This is used internally by the gradtrack system to compute gradients.
§Returns
Reference to the gradient function
§Implementation Details
This method is used by the gradtrack engine to access the gradient computation function during backward pass.
Sourcepub fn set_grad(&mut self, grad: Tensor)
pub fn set_grad(&mut self, grad: Tensor)
Set gradient from external source
Sets the gradient tensor for this tensor. This is used internally by the gradtrack system to set gradients during backward pass.
§Arguments
grad- The gradient tensor to set
§Implementation Details
This method is used internally by the gradtrack engine to set gradients during backward pass. It only sets the gradient if gradient tracking is enabled for this tensor.
Sourcepub fn zero_grad(&mut self)
pub fn zero_grad(&mut self)
Clear accumulated gradients for this tensor
This method is used by optimizers to zero gradients before each backward pass. It clears any accumulated gradients, allowing for fresh gradient computation.
§Examples
use train_station::Tensor;
let mut tensor = Tensor::ones(vec![2, 3]).with_requires_grad();
tensor.set_grad(Tensor::ones(vec![2, 3]));
assert!(tensor.grad().is_some());
tensor.zero_grad();
assert!(tensor.grad().is_none());Sourcepub fn is_contiguous(&self) -> bool
pub fn is_contiguous(&self) -> bool
Checks if the tensor data is stored contiguously in memory
§Returns
true if the tensor data is contiguous, enabling optimized SIMD operations
§Examples
use train_station::Tensor;
let tensor = Tensor::new(vec![2, 3, 4]);
assert!(tensor.is_contiguous());Examples found in repository?
179fn demonstrate_utility_functions() {
180 println!("\n--- Utility Functions ---");
181
182 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
183
184 // Basic properties
185 println!("Shape: {:?}", tensor.shape().dims);
186 println!("Size: {}", tensor.size());
187 println!("Is contiguous: {}", tensor.is_contiguous());
188 println!("Device: {:?}", tensor.device());
189
190 // Mathematical operations
191 let sum = tensor.sum();
192 println!("Sum: {}", sum.value());
193
194 let mean = tensor.mean();
195 println!("Mean: {}", mean.value());
196
197 let norm = tensor.norm();
198 println!("Norm: {}", norm.value());
199
200 // Device placement
201 let cpu_tensor = Tensor::zeros_on_device(vec![3, 3], train_station::Device::cpu());
202 println!(
203 "CPU tensor: shape {:?}, device: {:?}",
204 cpu_tensor.shape().dims,
205 cpu_tensor.device()
206 );
207}Sourcepub fn is_view(&self) -> bool
pub fn is_view(&self) -> bool
Checks if this tensor is a view of another tensor
§Returns
true if this tensor is a view (non-contiguous reference)
Sourcepub fn layout(&self) -> &MemoryLayout
pub fn layout(&self) -> &MemoryLayout
Gets the memory layout type for optimization decisions
§Returns
Reference to the memory layout information
Sourcepub fn memory_offset(&self, indices: &[usize]) -> usize
pub fn memory_offset(&self, indices: &[usize]) -> usize
Calculates the linear memory offset for given multi-dimensional indices
§Arguments
indices- Vector of indices for each dimension
§Returns
Linear memory offset for direct memory access
§Examples
use train_station::Tensor;
let tensor = Tensor::new(vec![2, 3, 4]);
let offset = tensor.memory_offset(&[1, 2, 3]);
// offset = 1*12 + 2*4 + 3*1 = 23Sourcepub fn broadcast_with(
&self,
other: &Tensor,
) -> Result<(Tensor, Tensor, Shape), BroadcastError>
pub fn broadcast_with( &self, other: &Tensor, ) -> Result<(Tensor, Tensor, Shape), BroadcastError>
Broadcast this tensor with another tensor for element-wise operations
Returns a tuple containing:
- Broadcasted view of self
- Broadcasted view of other
- Result shape for the operation
§Arguments
other- The tensor to broadcast with
§Returns
A tuple (broadcasted_self, broadcasted_other, result_shape)
§Examples
use train_station::Tensor;
let a = Tensor::ones(vec![2, 1, 4]);
let b = Tensor::ones(vec![3, 1]);
let result = a.broadcast_with(&b);
assert!(result.is_ok());Sourcepub fn is_simd_aligned(&self) -> bool
pub fn is_simd_aligned(&self) -> bool
Checks if the tensor data is properly aligned for SIMD operations
§Returns
true if the tensor data is aligned to 32-byte boundaries for AVX2
Sourcepub fn memory_alignment(&self) -> usize
pub fn memory_alignment(&self) -> usize
Gets the memory alignment of the tensor data
§Returns
The memory alignment in bytes (typically 32 for SIMD optimization)
Sourcepub fn is_broadcastable_with(&self, other: &Tensor) -> bool
pub fn is_broadcastable_with(&self, other: &Tensor) -> bool
Checks if this tensor is broadcastable with another tensor
§Arguments
other- The other tensor to check broadcasting compatibility
§Returns
true if the tensors are broadcastable according to NumPy broadcasting rules
§Examples
use train_station::Tensor;
let a = Tensor::new(vec![2, 3, 4]);
let b = Tensor::new(vec![1, 3, 4]);
assert!(a.is_broadcastable_with(&b));Sourcepub fn memory_footprint(&self) -> usize
pub fn memory_footprint(&self) -> usize
Sourcepub fn get(&self, indices: &[usize]) -> f32
pub fn get(&self, indices: &[usize]) -> f32
Get a single element from the tensor at the specified indices
§Arguments
indices- Multi-dimensional indices to access the element
§Returns
The value at the specified position
§Panics
Panics if indices are out of bounds or indices length doesn’t match tensor rank
§Examples
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let value = tensor.get(&[0, 1]);
assert_eq!(value, 2.0);Examples found in repository?
156fn demonstrate_data_access() {
157 println!("\n--- Data Access ---");
158
159 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
160
161 // Access individual elements
162 println!("Element [0, 0]: {}", tensor.get(&[0, 0]));
163 println!("Element [0, 1]: {}", tensor.get(&[0, 1]));
164 println!("Element [1, 0]: {}", tensor.get(&[1, 0]));
165 println!("Element [1, 1]: {}", tensor.get(&[1, 1]));
166
167 // Access data as slice
168 let data = tensor.data();
169 println!("Data as slice: {:?}", data);
170
171 // Iterate over elements
172 println!("Elements:");
173 for (i, &value) in data.iter().enumerate() {
174 println!(" [{}]: {}", i, value);
175 }
176}Sourcepub fn set(&mut self, indices: &[usize], value: f32)
pub fn set(&mut self, indices: &[usize], value: f32)
Set a single element in the tensor at the specified indices
§Arguments
indices- Multi-dimensional indices to set the elementvalue- The value to set
§Panics
Panics if indices are out of bounds or indices length doesn’t match tensor rank
§Examples
use train_station::Tensor;
let mut tensor = Tensor::new(vec![2, 2]);
tensor.set(&[0, 1], 42.0);
assert_eq!(tensor.get(&[0, 1]), 42.0);Sourcepub fn data(&self) -> &[f32]
pub fn data(&self) -> &[f32]
Returns a safe slice of the tensor’s underlying data
Provides safe access to the tensor’s data without requiring unsafe pointer operations. This is the preferred way to access tensor data for reading values, comparisons, and other operations that don’t require direct pointer manipulation.
§Returns
A slice containing all tensor elements in row-major order
§Performance
- Zero-Cost: Direct slice creation with no copying
- Cache-Friendly: Sequential memory access pattern
- Safe: No unsafe code required for basic data access
§Examples
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let data = tensor.data();
// Safe indexing and comparisons
assert_eq!(data[0], 1.0);
assert_eq!(data.len(), tensor.size());Examples found in repository?
46fn demonstrate_basic_operators() {
47 println!("--- Basic Tensor-Tensor Operators ---");
48
49 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
50 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
51
52 println!("Tensor A: {:?}", a.data());
53 println!("Tensor B: {:?}", b.data());
54
55 // Addition
56 let c = &a + &b;
57 println!("A + B: {:?}", c.data());
58
59 // Subtraction
60 let d = &a - &b;
61 println!("A - B: {:?}", d.data());
62
63 // Multiplication
64 let e = &a * &b;
65 println!("A * B: {:?}", e.data());
66
67 // Division
68 let f = &a / &b;
69 println!("A / B: {:?}", f.data());
70}
71
72/// Demonstrate tensor-scalar operators
73fn demonstrate_scalar_operators() {
74 println!("\n--- Tensor-Scalar Operators ---");
75
76 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
77 println!("Original tensor: {:?}", tensor.data());
78
79 // Tensor + scalar
80 let result1 = &tensor + 5.0;
81 println!("Tensor + 5.0: {:?}", result1.data());
82
83 // Scalar + tensor
84 let result2 = 5.0 + &tensor;
85 println!("5.0 + Tensor: {:?}", result2.data());
86
87 // Tensor - scalar
88 let result3 = &tensor - 2.0;
89 println!("Tensor - 2.0: {:?}", result3.data());
90
91 // Tensor * scalar
92 let result4 = &tensor * 3.0;
93 println!("Tensor * 3.0: {:?}", result4.data());
94
95 // Scalar * tensor
96 let result5 = 3.0 * &tensor;
97 println!("3.0 * Tensor: {:?}", result5.data());
98
99 // Tensor / scalar
100 let result6 = &tensor / 2.0;
101 println!("Tensor / 2.0: {:?}", result6.data());
102}
103
104/// Demonstrate assignment operators
105fn demonstrate_operator_assignment() {
106 println!("\n--- Assignment Operators ---");
107
108 let mut tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
109 println!("Original tensor: {:?}", tensor.data());
110
111 // In-place addition
112 tensor += 5.0;
113 println!("After += 5.0: {:?}", tensor.data());
114
115 // In-place subtraction
116 tensor -= 2.0;
117 println!("After -= 2.0: {:?}", tensor.data());
118
119 // In-place multiplication
120 tensor *= 3.0;
121 println!("After *= 3.0: {:?}", tensor.data());
122
123 // In-place division
124 tensor /= 2.0;
125 println!("After /= 2.0: {:?}", tensor.data());
126}
127
128/// Demonstrate operator chaining and complex expressions
129fn demonstrate_operator_chaining() {
130 println!("\n--- Operator Chaining ---");
131
132 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
133 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
134 let c = Tensor::from_slice(&[9.0, 10.0, 11.0, 12.0], vec![2, 2]).unwrap();
135
136 println!("Tensor A: {:?}", a.data());
137 println!("Tensor B: {:?}", b.data());
138 println!("Tensor C: {:?}", c.data());
139
140 // Complex expression: (A + B) * C - 5
141 let result = (&a + &b) * &c - 5.0;
142 println!("(A + B) * C - 5: {:?}", result.data());
143
144 // Another complex expression: A * 2 + B / 2
145 let result2 = &a * 2.0 + &b / 2.0;
146 println!("A * 2 + B / 2: {:?}", result2.data());
147
148 // Negation and addition: -A + B * C
149 let result3 = -&a + &b * &c;
150 println!("-A + B * C: {:?}", result3.data());
151
152 // Division with parentheses: (A + B) / (C - 1)
153 let result4 = (&a + &b) / (&c - 1.0);
154 println!("(A + B) / (C - 1): {:?}", result4.data());
155}
156
157/// Demonstrate broadcasting behavior
158fn demonstrate_broadcasting() {
159 println!("\n--- Broadcasting ---");
160
161 // 2D tensor
162 let tensor_2d = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
163 println!(
164 "2D tensor: shape {:?}, data: {:?}",
165 tensor_2d.shape().dims,
166 tensor_2d.data()
167 );
168
169 // 1D tensor (will be broadcasted)
170 let tensor_1d = Tensor::from_slice(&[10.0, 20.0], vec![2]).unwrap();
171 println!(
172 "1D tensor: shape {:?}, data: {:?}",
173 tensor_1d.shape().dims,
174 tensor_1d.data()
175 );
176
177 // Broadcasting addition
178 let broadcast_sum = &tensor_2d + &tensor_1d;
179 println!(
180 "Broadcast sum: shape {:?}, data: {:?}",
181 broadcast_sum.shape().dims,
182 broadcast_sum.data()
183 );
184
185 // Broadcasting multiplication
186 let broadcast_mul = &tensor_2d * &tensor_1d;
187 println!(
188 "Broadcast multiplication: shape {:?}, data: {:?}",
189 broadcast_mul.shape().dims,
190 broadcast_mul.data()
191 );
192
193 // Broadcasting with scalar
194 let broadcast_scalar = &tensor_2d + 100.0;
195 println!(
196 "Broadcast scalar: shape {:?}, data: {:?}",
197 broadcast_scalar.shape().dims,
198 broadcast_scalar.data()
199 );
200}
201
202/// Demonstrate equivalence between operators and method calls
203fn demonstrate_method_equivalence() {
204 println!("\n--- Operator vs Method Call Equivalence ---");
205
206 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
207 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
208
209 // Addition: operator vs method
210 let operator_result = &a + &b;
211 let method_result = a.add_tensor(&b);
212
213 println!("A + B (operator): {:?}", operator_result.data());
214 println!("A.add_tensor(B): {:?}", method_result.data());
215 println!(
216 "Results are equal: {}",
217 operator_result.data() == method_result.data()
218 );
219
220 // Multiplication: operator vs method
221 let operator_result = &a * &b;
222 let method_result = a.mul_tensor(&b);
223
224 println!("A * B (operator): {:?}", operator_result.data());
225 println!("A.mul_tensor(B): {:?}", method_result.data());
226 println!(
227 "Results are equal: {}",
228 operator_result.data() == method_result.data()
229 );
230
231 // Scalar addition: operator vs method
232 let operator_result = &a + 5.0;
233 let method_result = a.add_scalar(5.0);
234
235 println!("A + 5.0 (operator): {:?}", operator_result.data());
236 println!("A.add_scalar(5.0): {:?}", method_result.data());
237 println!(
238 "Results are equal: {}",
239 operator_result.data() == method_result.data()
240 );
241}More examples
163fn demonstrate_forward_pass() {
164 println!("\n--- Forward Pass (with gradients) ---");
165
166 let layer = LinearLayer::new(3, 2, Some(43));
167
168 // Single input
169 let input = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![1, 3]).unwrap();
170 let output = layer.forward(&input);
171
172 println!("Single input:");
173 println!(" Input: {:?}", input.data());
174 println!(" Output: {:?}", output.data());
175 println!(" Output requires grad: {}", output.requires_grad());
176
177 // Batch input
178 let batch_input = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
179 let batch_output = layer.forward(&batch_input);
180
181 println!("Batch input:");
182 println!(" Input shape: {:?}", batch_input.shape().dims);
183 println!(" Output shape: {:?}", batch_output.shape().dims);
184 println!(" Output requires grad: {}", batch_output.requires_grad());
185}
186
187/// Demonstrate forward pass without gradient tracking
188fn demonstrate_forward_pass_no_grad() {
189 println!("\n--- Forward Pass (no gradients) ---");
190
191 let layer = LinearLayer::new(3, 2, Some(44));
192
193 // Single input
194 let input = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![1, 3]).unwrap();
195 let output = layer.forward_no_grad(&input);
196
197 println!("Single input (no grad):");
198 println!(" Input: {:?}", input.data());
199 println!(" Output: {:?}", output.data());
200 println!(" Output requires grad: {}", output.requires_grad());
201
202 // Compare with grad version
203 let output_with_grad = layer.forward(&input);
204 println!("Comparison:");
205 println!(
206 " Same values: {}",
207 output.data() == output_with_grad.data()
208 );
209 println!(" No grad requires grad: {}", output.requires_grad());
210 println!(
211 " With grad requires grad: {}",
212 output_with_grad.requires_grad()
213 );
214}
215
216/// Demonstrate complete training loop
217fn demonstrate_training_loop() -> Result<(), Box<dyn std::error::Error>> {
218 println!("\n--- Training Loop ---");
219
220 // Create layer and training data
221 let mut layer = LinearLayer::new(2, 1, Some(45));
222
223 // Simple regression task: y = 2*x1 + 3*x2 + 1
224 let x_data = Tensor::from_slice(
225 &[
226 1.0, 1.0, // x1=1, x2=1 -> y=6
227 2.0, 1.0, // x1=2, x2=1 -> y=8
228 1.0, 2.0, // x1=1, x2=2 -> y=9
229 2.0, 2.0, // x1=2, x2=2 -> y=11
230 ],
231 vec![4, 2],
232 )
233 .unwrap();
234
235 let y_true = Tensor::from_slice(&[6.0, 8.0, 9.0, 11.0], vec![4, 1]).unwrap();
236
237 println!("Training data:");
238 println!(" X shape: {:?}", x_data.shape().dims);
239 println!(" Y shape: {:?}", y_true.shape().dims);
240 println!(" Target function: y = 2*x1 + 3*x2 + 1");
241
242 // Create optimizer
243 let config = AdamConfig {
244 learning_rate: 0.01,
245 beta1: 0.9,
246 beta2: 0.999,
247 eps: 1e-8,
248 weight_decay: 0.0,
249 amsgrad: false,
250 };
251
252 let mut optimizer = Adam::with_config(config);
253 let params = layer.parameters();
254 for param in ¶ms {
255 optimizer.add_parameter(param);
256 }
257
258 println!("Optimizer setup complete. Starting training...");
259
260 // Training loop
261 let num_epochs = 100;
262 let mut losses = Vec::new();
263
264 for epoch in 0..num_epochs {
265 // Forward pass
266 let y_pred = layer.forward(&x_data);
267
268 // Compute loss: MSE
269 let diff = y_pred.sub_tensor(&y_true);
270 let mut loss = diff.pow_scalar(2.0).mean();
271
272 // Backward pass
273 loss.backward(None);
274
275 // Optimizer step
276 let mut params = layer.parameters();
277 optimizer.step(&mut params);
278 optimizer.zero_grad(&mut params);
279
280 losses.push(loss.value());
281
282 // Print progress
283 if epoch % 20 == 0 || epoch == num_epochs - 1 {
284 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
285 }
286 }
287
288 // Evaluate final model
289 let final_predictions = layer.forward_no_grad(&x_data);
290
291 println!("\nFinal model evaluation:");
292 println!(" Learned weights: {:?}", layer.weight.data());
293 println!(" Learned bias: {:?}", layer.bias.data());
294 println!(" Target weights: [2.0, 3.0]");
295 println!(" Target bias: [1.0]");
296
297 println!(" Predictions vs True:");
298 for i in 0..4 {
299 let pred = final_predictions.data()[i];
300 let true_val = y_true.data()[i];
301 println!(
302 " Sample {}: pred={:.3}, true={:.1}, error={:.3}",
303 i + 1,
304 pred,
305 true_val,
306 (pred - true_val).abs()
307 );
308 }
309
310 // Training analysis
311 let initial_loss = losses[0];
312 let final_loss = losses[losses.len() - 1];
313 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
314
315 println!("\nTraining Analysis:");
316 println!(" Initial loss: {:.6}", initial_loss);
317 println!(" Final loss: {:.6}", final_loss);
318 println!(" Loss reduction: {:.1}%", loss_reduction);
319
320 Ok(())
321}
322
323/// Demonstrate single vs batch inference
324fn demonstrate_single_vs_batch_inference() {
325 println!("\n--- Single vs Batch Inference ---");
326
327 let layer = LinearLayer::new(4, 3, Some(46));
328
329 // Single inference
330 println!("Single inference:");
331 let single_input = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![1, 4]).unwrap();
332 let single_output = layer.forward_no_grad(&single_input);
333 println!(" Input shape: {:?}", single_input.shape().dims);
334 println!(" Output shape: {:?}", single_output.shape().dims);
335 println!(" Output: {:?}", single_output.data());
336
337 // Batch inference
338 println!("Batch inference:");
339 let batch_input = Tensor::from_slice(
340 &[
341 1.0, 2.0, 3.0, 4.0, // Sample 1
342 5.0, 6.0, 7.0, 8.0, // Sample 2
343 9.0, 10.0, 11.0, 12.0, // Sample 3
344 ],
345 vec![3, 4],
346 )
347 .unwrap();
348 let batch_output = layer.forward_no_grad(&batch_input);
349 println!(" Input shape: {:?}", batch_input.shape().dims);
350 println!(" Output shape: {:?}", batch_output.shape().dims);
351
352 // Verify batch consistency - first sample should match single inference
353 let _first_batch_sample = batch_output.view(vec![3, 3]); // Reshape to access first sample
354 let first_sample_data = &batch_output.data()[0..3]; // First 3 elements
355 let single_sample_data = single_output.data();
356
357 println!("Consistency check:");
358 println!(" Single output: {:?}", single_sample_data);
359 println!(" First batch sample: {:?}", first_sample_data);
360 println!(
361 " Match: {}",
362 single_sample_data
363 .iter()
364 .zip(first_sample_data.iter())
365 .all(|(a, b)| (a - b).abs() < 1e-6)
366 );
367}
368
369/// Demonstrate serialization and loading
370fn demonstrate_serialization() -> Result<(), Box<dyn std::error::Error>> {
371 println!("\n--- Serialization ---");
372
373 // Create and train a simple layer
374 let mut original_layer = LinearLayer::new(2, 1, Some(47));
375
376 // Simple training data
377 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
378 let y_true = Tensor::from_slice(&[5.0, 11.0], vec![2, 1]).unwrap();
379
380 let mut optimizer = Adam::with_learning_rate(0.01);
381 let params = original_layer.parameters();
382 for param in ¶ms {
383 optimizer.add_parameter(param);
384 }
385
386 // Train for a few epochs
387 for _ in 0..10 {
388 let y_pred = original_layer.forward(&x_data);
389 let mut loss = (y_pred.sub_tensor(&y_true)).pow_scalar(2.0).mean();
390 loss.backward(None);
391
392 let mut params = original_layer.parameters();
393 optimizer.step(&mut params);
394 optimizer.zero_grad(&mut params);
395 }
396
397 println!("Original layer trained");
398 println!(" Weight: {:?}", original_layer.weight.data());
399 println!(" Bias: {:?}", original_layer.bias.data());
400
401 // Save layer
402 original_layer.save_json("temp_linear_layer")?;
403
404 // Load layer
405 let loaded_layer = LinearLayer::load_json("temp_linear_layer", 2, 1)?;
406
407 println!("Loaded layer");
408 println!(" Weight: {:?}", loaded_layer.weight.data());
409 println!(" Bias: {:?}", loaded_layer.bias.data());
410
411 // Verify consistency
412 let test_input = Tensor::from_slice(&[1.0, 1.0], vec![1, 2]).unwrap();
413 let original_output = original_layer.forward_no_grad(&test_input);
414 let loaded_output = loaded_layer.forward_no_grad(&test_input);
415
416 println!("Consistency check:");
417 println!(" Original output: {:?}", original_output.data());
418 println!(" Loaded output: {:?}", loaded_output.data());
419 println!(
420 " Match: {}",
421 original_output
422 .data()
423 .iter()
424 .zip(loaded_output.data().iter())
425 .all(|(a, b)| (a - b).abs() < 1e-6)
426 );
427
428 println!("Serialization verification: PASSED");
429
430 Ok(())
431}77fn demonstrate_basic_iteration() -> Result<(), Box<dyn std::error::Error>> {
78 println!("\n--- Basic Element Iteration ---");
79
80 // Create a simple tensor for demonstration
81 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
82 println!("Original tensor: {:?}", tensor.data());
83
84 // Basic iteration with for loop
85 println!("\nBasic iteration with for loop:");
86 for (i, element) in tensor.iter().enumerate() {
87 println!(
88 " Element {}: value = {:.1}, shape = {:?}",
89 i,
90 element.value(),
91 element.shape().dims
92 );
93 }
94
95 // Element-wise transformation
96 println!("\nElement-wise transformation (2x + 1):");
97 let transformed: Tensor = tensor
98 .iter()
99 .map(|elem| elem.mul_scalar(2.0).add_scalar(1.0))
100 .collect();
101 println!(" Result: {:?}", transformed.data());
102
103 // Filtering elements
104 println!("\nFiltering elements (values > 3.0):");
105 let filtered: Tensor = tensor.iter().filter(|elem| elem.value() > 3.0).collect();
106 println!(" Filtered: {:?}", filtered.data());
107
108 Ok(())
109}
110
111/// Demonstrate standard iterator trait methods
112///
113/// Shows compatibility with Rust's standard library iterator methods
114/// and demonstrates various functional programming patterns.
115fn demonstrate_standard_methods() -> Result<(), Box<dyn std::error::Error>> {
116 println!("\n--- Standard Iterator Methods ---");
117
118 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
119
120 // Using map for transformations
121 println!("\nMap transformation (square each element):");
122 let squared: Tensor = tensor.iter().map(|elem| elem.pow_scalar(2.0)).collect();
123 println!(" Squared: {:?}", squared.data());
124
125 // Using enumerate for indexed operations
126 println!("\nEnumerate with indexed operations:");
127 let indexed: Tensor = tensor
128 .iter()
129 .enumerate()
130 .map(|(i, elem)| elem.add_scalar(i as f32))
131 .collect();
132 println!(" Indexed: {:?}", indexed.data());
133
134 // Using fold for reduction
135 println!("\nFold for sum calculation:");
136 let sum: f32 = tensor.iter().fold(0.0, |acc, elem| acc + elem.value());
137 println!(" Sum: {:.1}", sum);
138
139 // Using find for element search
140 println!("\nFind specific element:");
141 if let Some(found) = tensor.iter().find(|elem| elem.value() == 3.0) {
142 println!(" Found element with value 3.0: {:.1}", found.value());
143 }
144
145 // Using any/all for condition checking
146 println!("\nCondition checking:");
147 let all_positive = tensor.iter().all(|elem| elem.value() > 0.0);
148 let any_large = tensor.iter().any(|elem| elem.value() > 4.0);
149 println!(" All positive: {}", all_positive);
150 println!(" Any > 4.0: {}", any_large);
151
152 Ok(())
153}
154
155/// Demonstrate gradient tracking through element operations
156///
157/// Shows how gradient tracking works seamlessly through iterator
158/// operations, maintaining the computational graph for backpropagation.
159fn demonstrate_gradient_tracking() -> Result<(), Box<dyn std::error::Error>> {
160 println!("\n--- Gradient Tracking ---");
161
162 // Create a tensor with gradient tracking enabled
163 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?.with_requires_grad();
164 println!("Input tensor (requires_grad): {:?}", tensor.data());
165
166 // Perform element-wise operations through iteration
167 let result: Tensor = tensor
168 .iter()
169 .map(|elem| {
170 // Apply a complex transformation: (x^2 + 1) * 2
171 elem.pow_scalar(2.0).add_scalar(1.0).mul_scalar(2.0)
172 })
173 .collect();
174
175 println!("Result tensor: {:?}", result.data());
176 println!("Result requires_grad: {}", result.requires_grad());
177
178 // Compute gradients
179 let mut loss = result.sum();
180 loss.backward(None);
181
182 println!("Loss: {:.6}", loss.value());
183 println!("Input gradients: {:?}", tensor.grad().map(|g| g.data()));
184
185 Ok(())
186}
187
188/// Demonstrate advanced iterator patterns
189///
190/// Shows complex iterator chains and advanced functional programming
191/// patterns for sophisticated data processing workflows.
192fn demonstrate_advanced_patterns() -> Result<(), Box<dyn std::error::Error>> {
193 println!("\n--- Advanced Iterator Patterns ---");
194
195 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![6])?;
196 println!("Input tensor: {:?}", tensor.data());
197
198 // Complex chain: enumerate -> filter -> map -> collect
199 println!("\nComplex chain (even indices only, add index to value):");
200 let result: Tensor = tensor
201 .iter()
202 .enumerate()
203 .filter(|(i, _)| i % 2 == 0) // Take even indices
204 .map(|(i, elem)| elem.add_scalar(i as f32)) // Add index to value
205 .collect();
206 println!(" Result: {:?}", result.data());
207
208 // Using take and skip for windowing
209 println!("\nWindowing with take and skip:");
210 let window1: Tensor = tensor.iter().take(3).collect();
211 let window2: Tensor = tensor.iter().skip(2).take(3).collect();
212 println!(" Window 1 (first 3): {:?}", window1.data());
213 println!(" Window 2 (middle 3): {:?}", window2.data());
214
215 // Using rev() for reverse iteration
216 println!("\nReverse iteration:");
217 let reversed: Tensor = tensor.iter().rev().collect();
218 println!(" Reversed: {:?}", reversed.data());
219
220 // Chaining with mathematical operations
221 println!("\nMathematical operation chain:");
222 let math_result: Tensor = tensor
223 .iter()
224 .map(|elem| elem.exp()) // e^x
225 .filter(|elem| elem.value() < 50.0) // Filter large values
226 .map(|elem| elem.log()) // ln(x)
227 .collect();
228 println!(" Math chain result: {:?}", math_result.data());
229
230 // Using zip for element-wise combinations
231 println!("\nElement-wise combination with zip:");
232 let tensor2 = Tensor::from_slice(&[10.0, 20.0, 30.0, 40.0, 50.0, 60.0], vec![6])?;
233 let combined: Tensor = tensor
234 .iter()
235 .zip(tensor2.iter())
236 .map(|(a, b)| a.mul_tensor(&b)) // Element-wise multiplication
237 .collect();
238 println!(" Combined: {:?}", combined.data());
239
240 Ok(())
241}42fn demonstrate_tensor_creation() {
43 println!("--- Tensor Creation ---");
44
45 // Create tensors with different initializations
46 let zeros = Tensor::zeros(vec![2, 3]);
47 println!(
48 "Zeros tensor: shape {:?}, data: {:?}",
49 zeros.shape().dims,
50 zeros.data()
51 );
52
53 let ones = Tensor::ones(vec![3, 2]);
54 println!(
55 "Ones tensor: shape {:?}, data: {:?}",
56 ones.shape().dims,
57 ones.data()
58 );
59
60 // Create tensor from slice
61 let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
62 let from_slice = Tensor::from_slice(&data, vec![2, 3]).unwrap();
63 println!(
64 "From slice: shape {:?}, data: {:?}",
65 from_slice.shape().dims,
66 from_slice.data()
67 );
68
69 // Create tensor with specific value
70 let mut filled = Tensor::new(vec![2, 2]);
71 {
72 let data = filled.data_mut();
73 for value in data.iter_mut() {
74 *value = 42.0;
75 }
76 }
77 println!("Filled with 42: {:?}", filled.data());
78
79 // Create tensor with random data
80 let random = Tensor::randn(vec![2, 2], Some(42));
81 println!(
82 "Random tensor: shape {:?}, data: {:?}",
83 random.shape().dims,
84 random.data()
85 );
86}
87
88/// Demonstrate basic arithmetic operations
89fn demonstrate_basic_operations() {
90 println!("\n--- Basic Operations ---");
91
92 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
93 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
94
95 // Addition
96 let sum = a.add_tensor(&b);
97 println!("A + B: {:?}", sum.data());
98
99 // Subtraction
100 let diff = a.sub_tensor(&b);
101 println!("A - B: {:?}", diff.data());
102
103 // Multiplication
104 let product = a.mul_tensor(&b);
105 println!("A * B: {:?}", product.data());
106
107 // Division
108 let quotient = a.div_tensor(&b);
109 println!("A / B: {:?}", quotient.data());
110
111 // Scalar operations
112 let scalar_add = a.add_scalar(5.0);
113 println!("A + 5.0: {:?}", scalar_add.data());
114
115 let scalar_mul = a.mul_scalar(2.0);
116 println!("A * 2.0: {:?}", scalar_mul.data());
117}
118
119/// Demonstrate shape manipulation operations
120fn demonstrate_shape_operations() {
121 println!("\n--- Shape Operations ---");
122
123 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
124 println!(
125 "Original: shape {:?}, data: {:?}",
126 tensor.shape().dims,
127 tensor.data()
128 );
129
130 // Reshape (view)
131 let reshaped = tensor.view(vec![3, 2]);
132 println!(
133 "Reshaped to [3, 2]: shape {:?}, data: {:?}",
134 reshaped.shape().dims,
135 reshaped.data()
136 );
137
138 // Create a different shaped tensor for demonstration
139 let tensor_2d = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
140 println!(
141 "2D tensor: shape {:?}, data: {:?}",
142 tensor_2d.shape().dims,
143 tensor_2d.data()
144 );
145
146 // Create a 1D tensor
147 let tensor_1d = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4]).unwrap();
148 println!(
149 "1D tensor: shape {:?}, data: {:?}",
150 tensor_1d.shape().dims,
151 tensor_1d.data()
152 );
153}
154
155/// Demonstrate data access patterns
156fn demonstrate_data_access() {
157 println!("\n--- Data Access ---");
158
159 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
160
161 // Access individual elements
162 println!("Element [0, 0]: {}", tensor.get(&[0, 0]));
163 println!("Element [0, 1]: {}", tensor.get(&[0, 1]));
164 println!("Element [1, 0]: {}", tensor.get(&[1, 0]));
165 println!("Element [1, 1]: {}", tensor.get(&[1, 1]));
166
167 // Access data as slice
168 let data = tensor.data();
169 println!("Data as slice: {:?}", data);
170
171 // Iterate over elements
172 println!("Elements:");
173 for (i, &value) in data.iter().enumerate() {
174 println!(" [{}]: {}", i, value);
175 }
176}339fn demonstrate_forward_pass() {
340 println!("\n--- Forward Pass ---");
341
342 let config = FeedForwardConfig {
343 input_size: 3,
344 hidden_sizes: vec![5, 3],
345 output_size: 2,
346 use_bias: true,
347 };
348 let network = FeedForwardNetwork::new(config, Some(43));
349
350 // Single input
351 let input = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![1, 3]).unwrap();
352 let output = network.forward(&input);
353
354 println!("Single input forward pass:");
355 println!(" Input shape: {:?}", input.shape().dims);
356 println!(" Output shape: {:?}", output.shape().dims);
357 println!(" Output: {:?}", output.data());
358 println!(" Output requires grad: {}", output.requires_grad());
359
360 // Batch input
361 let batch_input = Tensor::from_slice(
362 &[
363 1.0, 2.0, 3.0, // Sample 1
364 4.0, 5.0, 6.0, // Sample 2
365 7.0, 8.0, 9.0, // Sample 3
366 ],
367 vec![3, 3],
368 )
369 .unwrap();
370 let batch_output = network.forward(&batch_input);
371
372 println!("Batch input forward pass:");
373 println!(" Input shape: {:?}", batch_input.shape().dims);
374 println!(" Output shape: {:?}", batch_output.shape().dims);
375 println!(" Output requires grad: {}", batch_output.requires_grad());
376
377 // Compare with no-grad version
378 let output_no_grad = network.forward_no_grad(&input);
379 println!("No-grad comparison:");
380 println!(" Same values: {}", output.data() == output_no_grad.data());
381 println!(" With grad requires grad: {}", output.requires_grad());
382 println!(
383 " No grad requires grad: {}",
384 output_no_grad.requires_grad()
385 );
386}
387
388/// Demonstrate different configurable architectures
389fn demonstrate_configurable_architectures() {
390 println!("\n--- Configurable Architectures ---");
391
392 let architectures = vec![
393 ("Shallow", vec![8]),
394 ("Medium", vec![16, 8]),
395 ("Deep", vec![32, 16, 8, 4]),
396 ("Wide", vec![64, 32]),
397 ("Bottleneck", vec![16, 4, 16]),
398 ];
399
400 for (name, hidden_sizes) in architectures {
401 let config = FeedForwardConfig {
402 input_size: 10,
403 hidden_sizes,
404 output_size: 3,
405 use_bias: true,
406 };
407
408 let network = FeedForwardNetwork::new(config.clone(), Some(44));
409
410 // Test forward pass
411 let test_input = Tensor::randn(vec![5, 10], Some(45)); // Batch of 5
412 let output = network.forward_no_grad(&test_input);
413
414 println!("{} network:", name);
415 println!(" Architecture: 10 -> {:?} -> 3", config.hidden_sizes);
416 println!(" Parameters: {}", network.parameter_count());
417 println!(" Test output shape: {:?}", output.shape().dims);
418 println!(
419 " Output range: [{:.3}, {:.3}]",
420 output.data().iter().fold(f32::INFINITY, |a, &b| a.min(b)),
421 output
422 .data()
423 .iter()
424 .fold(f32::NEG_INFINITY, |a, &b| a.max(b))
425 );
426 }
427}
428
429/// Demonstrate basic training workflow
430fn demonstrate_training_workflow() -> Result<(), Box<dyn std::error::Error>> {
431 println!("\n--- Training Workflow ---");
432
433 // Create a simple classification network
434 let config = FeedForwardConfig {
435 input_size: 2,
436 hidden_sizes: vec![4, 3],
437 output_size: 1,
438 use_bias: true,
439 };
440 let mut network = FeedForwardNetwork::new(config, Some(46));
441
442 println!("Training network: 2 -> [4, 3] -> 1");
443
444 // Create simple binary classification data: XOR problem
445 let x_data = Tensor::from_slice(
446 &[
447 0.0, 0.0, // -> 0
448 0.0, 1.0, // -> 1
449 1.0, 0.0, // -> 1
450 1.0, 1.0, // -> 0
451 ],
452 vec![4, 2],
453 )
454 .unwrap();
455
456 let y_true = Tensor::from_slice(&[0.0, 1.0, 1.0, 0.0], vec![4, 1]).unwrap();
457
458 println!("Training on XOR problem:");
459 println!(" Input shape: {:?}", x_data.shape().dims);
460 println!(" Target shape: {:?}", y_true.shape().dims);
461
462 // Create optimizer
463 let mut optimizer = Adam::with_learning_rate(0.1);
464 let params = network.parameters();
465 for param in ¶ms {
466 optimizer.add_parameter(param);
467 }
468
469 // Training loop
470 let num_epochs = 50;
471 let mut losses = Vec::new();
472
473 for epoch in 0..num_epochs {
474 // Forward pass
475 let y_pred = network.forward(&x_data);
476
477 // Compute loss: MSE
478 let diff = y_pred.sub_tensor(&y_true);
479 let mut loss = diff.pow_scalar(2.0).mean();
480
481 // Backward pass
482 loss.backward(None);
483
484 // Optimizer step and zero grad
485 let mut params = network.parameters();
486 optimizer.step(&mut params);
487 optimizer.zero_grad(&mut params);
488
489 losses.push(loss.value());
490
491 // Print progress
492 if epoch % 10 == 0 || epoch == num_epochs - 1 {
493 println!("Epoch {:2}: Loss = {:.6}", epoch, loss.value());
494 }
495 }
496
497 // Test final model
498 let final_predictions = network.forward_no_grad(&x_data);
499 println!("\nFinal predictions vs targets:");
500 for i in 0..4 {
501 let pred = final_predictions.data()[i];
502 let target = y_true.data()[i];
503 let input_x = x_data.data()[i * 2];
504 let input_y = x_data.data()[i * 2 + 1];
505 println!(
506 " [{:.0}, {:.0}] -> pred: {:.3}, target: {:.0}, error: {:.3}",
507 input_x,
508 input_y,
509 pred,
510 target,
511 (pred - target).abs()
512 );
513 }
514
515 Ok(())
516}
517
518/// Demonstrate comprehensive training with 100+ steps
519fn demonstrate_comprehensive_training() -> Result<(), Box<dyn std::error::Error>> {
520 println!("\n--- Comprehensive Training (100+ Steps) ---");
521
522 // Create a regression network
523 let config = FeedForwardConfig {
524 input_size: 3,
525 hidden_sizes: vec![8, 6, 4],
526 output_size: 2,
527 use_bias: true,
528 };
529 let mut network = FeedForwardNetwork::new(config, Some(47));
530
531 println!("Network architecture: 3 -> [8, 6, 4] -> 2");
532 println!("Total parameters: {}", network.parameter_count());
533
534 // Create synthetic regression data
535 // Target function: [y1, y2] = [x1 + 2*x2 - x3, x1*x2 + x3]
536 let num_samples = 32;
537 let mut x_vec = Vec::new();
538 let mut y_vec = Vec::new();
539
540 for i in 0..num_samples {
541 let x1 = (i as f32 / num_samples as f32) * 2.0 - 1.0; // [-1, 1]
542 let x2 = ((i * 2) as f32 / num_samples as f32) * 2.0 - 1.0;
543 let x3 = ((i * 3) as f32 / num_samples as f32) * 2.0 - 1.0;
544
545 let y1 = x1 + 2.0 * x2 - x3;
546 let y2 = x1 * x2 + x3;
547
548 x_vec.extend_from_slice(&[x1, x2, x3]);
549 y_vec.extend_from_slice(&[y1, y2]);
550 }
551
552 let x_data = Tensor::from_slice(&x_vec, vec![num_samples, 3]).unwrap();
553 let y_true = Tensor::from_slice(&y_vec, vec![num_samples, 2]).unwrap();
554
555 println!("Training data:");
556 println!(" {} samples", num_samples);
557 println!(" Input shape: {:?}", x_data.shape().dims);
558 println!(" Target shape: {:?}", y_true.shape().dims);
559
560 // Create optimizer with learning rate scheduling
561 let mut optimizer = Adam::with_learning_rate(0.01);
562 let params = network.parameters();
563 for param in ¶ms {
564 optimizer.add_parameter(param);
565 }
566
567 // Comprehensive training loop (150 epochs)
568 let num_epochs = 150;
569 let mut losses = Vec::new();
570 let mut best_loss = f32::INFINITY;
571 let mut patience_counter = 0;
572 let patience = 20;
573
574 println!("Starting comprehensive training...");
575
576 for epoch in 0..num_epochs {
577 // Forward pass
578 let y_pred = network.forward(&x_data);
579
580 // Compute loss: MSE
581 let diff = y_pred.sub_tensor(&y_true);
582 let mut loss = diff.pow_scalar(2.0).mean();
583
584 // Backward pass
585 loss.backward(None);
586
587 // Optimizer step and zero grad
588 let mut params = network.parameters();
589 optimizer.step(&mut params);
590 optimizer.zero_grad(&mut params);
591
592 let current_loss = loss.value();
593 losses.push(current_loss);
594
595 // Learning rate scheduling
596 if epoch > 0 && epoch % 30 == 0 {
597 let new_lr = optimizer.learning_rate() * 0.8;
598 optimizer.set_learning_rate(new_lr);
599 println!(" Reduced learning rate to {:.4}", new_lr);
600 }
601
602 // Early stopping logic
603 if current_loss < best_loss {
604 best_loss = current_loss;
605 patience_counter = 0;
606 } else {
607 patience_counter += 1;
608 }
609
610 // Print progress
611 if epoch % 25 == 0 || epoch == num_epochs - 1 {
612 println!(
613 "Epoch {:3}: Loss = {:.6}, LR = {:.4}, Best = {:.6}",
614 epoch,
615 current_loss,
616 optimizer.learning_rate(),
617 best_loss
618 );
619 }
620
621 // Early stopping
622 if patience_counter >= patience && epoch > 50 {
623 println!("Early stopping at epoch {} (patience exceeded)", epoch);
624 break;
625 }
626 }
627
628 // Final evaluation
629 let final_predictions = network.forward_no_grad(&x_data);
630
631 // Compute final metrics
632 let final_loss = losses[losses.len() - 1];
633 let initial_loss = losses[0];
634 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
635
636 println!("\nTraining completed!");
637 println!(" Initial loss: {:.6}", initial_loss);
638 println!(" Final loss: {:.6}", final_loss);
639 println!(" Best loss: {:.6}", best_loss);
640 println!(" Loss reduction: {:.1}%", loss_reduction);
641 println!(" Final learning rate: {:.4}", optimizer.learning_rate());
642
643 // Sample predictions analysis
644 println!("\nSample predictions (first 5):");
645 for i in 0..5.min(num_samples) {
646 let pred1 = final_predictions.data()[i * 2];
647 let pred2 = final_predictions.data()[i * 2 + 1];
648 let true1 = y_true.data()[i * 2];
649 let true2 = y_true.data()[i * 2 + 1];
650
651 println!(
652 " Sample {}: pred=[{:.3}, {:.3}], true=[{:.3}, {:.3}], error=[{:.3}, {:.3}]",
653 i + 1,
654 pred1,
655 pred2,
656 true1,
657 true2,
658 (pred1 - true1).abs(),
659 (pred2 - true2).abs()
660 );
661 }
662
663 Ok(())
664}
665
666/// Demonstrate network serialization
667fn demonstrate_network_serialization() -> Result<(), Box<dyn std::error::Error>> {
668 println!("\n--- Network Serialization ---");
669
670 // Create and train a network
671 let config = FeedForwardConfig {
672 input_size: 2,
673 hidden_sizes: vec![4, 2],
674 output_size: 1,
675 use_bias: true,
676 };
677 let mut original_network = FeedForwardNetwork::new(config.clone(), Some(48));
678
679 // Quick training
680 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
681 let y_true = Tensor::from_slice(&[5.0, 11.0], vec![2, 1]).unwrap();
682
683 let mut optimizer = Adam::with_learning_rate(0.01);
684 let params = original_network.parameters();
685 for param in ¶ms {
686 optimizer.add_parameter(param);
687 }
688
689 for _ in 0..20 {
690 let y_pred = original_network.forward(&x_data);
691 let mut loss = (y_pred.sub_tensor(&y_true)).pow_scalar(2.0).mean();
692 loss.backward(None);
693
694 let mut params = original_network.parameters();
695 optimizer.step(&mut params);
696 optimizer.zero_grad(&mut params);
697 }
698
699 // Test original network
700 let test_input = Tensor::from_slice(&[1.0, 1.0], vec![1, 2]).unwrap();
701 let original_output = original_network.forward_no_grad(&test_input);
702
703 println!("Original network output: {:?}", original_output.data());
704
705 // Save network
706 original_network.save_json("temp_feedforward_network")?;
707
708 // Load network
709 let loaded_network = FeedForwardNetwork::load_json("temp_feedforward_network", config)?;
710 let loaded_output = loaded_network.forward_no_grad(&test_input);
711
712 println!("Loaded network output: {:?}", loaded_output.data());
713
714 // Verify consistency
715 let match_check = original_output
716 .data()
717 .iter()
718 .zip(loaded_output.data().iter())
719 .all(|(a, b)| (a - b).abs() < 1e-6);
720
721 println!(
722 "Serialization verification: {}",
723 if match_check { "PASSED" } else { "FAILED" }
724 );
725
726 Ok(())
727}52fn demonstrate_tensor_serialization() -> Result<(), Box<dyn std::error::Error>> {
53 println!("--- Tensor Serialization ---");
54
55 // Create a tensor with some data
56 let original_tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
57 println!(
58 "Original tensor: shape {:?}, data: {:?}",
59 original_tensor.shape().dims,
60 original_tensor.data()
61 );
62
63 // Save tensor in JSON format
64 let json_path = "temp_tensor.json";
65 original_tensor.save_json(json_path)?;
66 println!("Saved tensor to JSON: {}", json_path);
67
68 // Load tensor from JSON
69 let loaded_tensor_json = Tensor::load_json(json_path)?;
70 println!(
71 "Loaded from JSON: shape {:?}, data: {:?}",
72 loaded_tensor_json.shape().dims,
73 loaded_tensor_json.data()
74 );
75
76 // Verify data integrity
77 assert_eq!(
78 original_tensor.shape().dims,
79 loaded_tensor_json.shape().dims
80 );
81 assert_eq!(original_tensor.data(), loaded_tensor_json.data());
82 println!("JSON serialization verification: PASSED");
83
84 // Save tensor in binary format
85 let binary_path = "temp_tensor.bin";
86 original_tensor.save_binary(binary_path)?;
87 println!("Saved tensor to binary: {}", binary_path);
88
89 // Load tensor from binary
90 let loaded_tensor_binary = Tensor::load_binary(binary_path)?;
91 println!(
92 "Loaded from binary: shape {:?}, data: {:?}",
93 loaded_tensor_binary.shape().dims,
94 loaded_tensor_binary.data()
95 );
96
97 // Verify data integrity
98 assert_eq!(
99 original_tensor.shape().dims,
100 loaded_tensor_binary.shape().dims
101 );
102 assert_eq!(original_tensor.data(), loaded_tensor_binary.data());
103 println!("Binary serialization verification: PASSED");
104
105 Ok(())
106}
107
108/// Demonstrate optimizer serialization and deserialization
109fn demonstrate_optimizer_serialization() -> Result<(), Box<dyn std::error::Error>> {
110 println!("\n--- Optimizer Serialization ---");
111
112 // Create an optimizer with some parameters
113 let mut weight = Tensor::randn(vec![2, 2], Some(42)).with_requires_grad();
114 let mut bias = Tensor::randn(vec![2], Some(43)).with_requires_grad();
115
116 let config = AdamConfig {
117 learning_rate: 0.001,
118 beta1: 0.9,
119 beta2: 0.999,
120 eps: 1e-8,
121 weight_decay: 0.0,
122 amsgrad: false,
123 };
124
125 let mut optimizer = Adam::with_config(config);
126 optimizer.add_parameter(&weight);
127 optimizer.add_parameter(&bias);
128
129 println!(
130 "Created optimizer with {} parameters",
131 optimizer.parameter_count()
132 );
133 println!("Learning rate: {}", optimizer.learning_rate());
134
135 // Simulate some training steps
136 for _ in 0..3 {
137 let mut loss = weight.sum() + bias.sum();
138 loss.backward(None);
139 optimizer.step(&mut [&mut weight, &mut bias]);
140 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
141 }
142
143 // Save optimizer state
144 let optimizer_path = "temp_optimizer.json";
145 optimizer.save_json(optimizer_path)?;
146 println!("Saved optimizer to: {}", optimizer_path);
147
148 // Load optimizer state
149 let loaded_optimizer = Adam::load_json(optimizer_path)?;
150 println!(
151 "Loaded optimizer with {} parameters",
152 loaded_optimizer.parameter_count()
153 );
154 println!("Learning rate: {}", loaded_optimizer.learning_rate());
155
156 // Verify optimizer state
157 assert_eq!(
158 optimizer.parameter_count(),
159 loaded_optimizer.parameter_count()
160 );
161 assert_eq!(optimizer.learning_rate(), loaded_optimizer.learning_rate());
162 println!("Optimizer serialization verification: PASSED");
163
164 Ok(())
165}
166
167/// Demonstrate format comparison and performance characteristics
168fn demonstrate_format_comparison() -> Result<(), Box<dyn std::error::Error>> {
169 println!("\n--- Format Comparison ---");
170
171 // Create a larger tensor for comparison
172 let tensor = Tensor::randn(vec![10, 10], Some(44));
173
174 // Save in both formats
175 tensor.save_json("temp_comparison.json")?;
176 tensor.save_binary("temp_comparison.bin")?;
177
178 // Compare file sizes
179 let json_size = fs::metadata("temp_comparison.json")?.len();
180 let binary_size = fs::metadata("temp_comparison.bin")?.len();
181
182 println!("JSON file size: {} bytes", json_size);
183 println!("Binary file size: {} bytes", binary_size);
184 println!(
185 "Compression ratio: {:.2}x",
186 json_size as f64 / binary_size as f64
187 );
188
189 // Load and verify both formats
190 let json_tensor = Tensor::load_json("temp_comparison.json")?;
191 let binary_tensor = Tensor::load_binary("temp_comparison.bin")?;
192
193 assert_eq!(tensor.shape().dims, json_tensor.shape().dims);
194 assert_eq!(tensor.shape().dims, binary_tensor.shape().dims);
195 assert_eq!(tensor.data(), json_tensor.data());
196 assert_eq!(tensor.data(), binary_tensor.data());
197
198 println!("Format comparison verification: PASSED");
199
200 Ok(())
201}
202
203/// Demonstrate a basic model checkpointing workflow
204fn demonstrate_model_checkpointing() -> Result<(), Box<dyn std::error::Error>> {
205 println!("\n--- Model Checkpointing ---");
206
207 // Create a simple model (weights and bias)
208 let mut weights = Tensor::randn(vec![2, 1], Some(45)).with_requires_grad();
209 let mut bias = Tensor::randn(vec![1], Some(46)).with_requires_grad();
210
211 // Create optimizer
212 let mut optimizer = Adam::with_learning_rate(0.01);
213 optimizer.add_parameter(&weights);
214 optimizer.add_parameter(&bias);
215
216 println!("Initial weights: {:?}", weights.data());
217 println!("Initial bias: {:?}", bias.data());
218
219 // Simulate training
220 for epoch in 0..5 {
221 let mut loss = weights.sum() + bias.sum();
222 loss.backward(None);
223 optimizer.step(&mut [&mut weights, &mut bias]);
224 optimizer.zero_grad(&mut [&mut weights, &mut bias]);
225
226 if epoch % 2 == 0 {
227 // Save checkpoint
228 let checkpoint_dir = format!("checkpoint_epoch_{}", epoch);
229 fs::create_dir_all(&checkpoint_dir)?;
230
231 weights.save_json(format!("{}/weights.json", checkpoint_dir))?;
232 bias.save_json(format!("{}/bias.json", checkpoint_dir))?;
233 optimizer.save_json(format!("{}/optimizer.json", checkpoint_dir))?;
234
235 println!("Saved checkpoint for epoch {}", epoch);
236 }
237 }
238
239 // Load from checkpoint
240 let loaded_weights = Tensor::load_json("checkpoint_epoch_4/weights.json")?;
241 let loaded_bias = Tensor::load_json("checkpoint_epoch_4/bias.json")?;
242 let loaded_optimizer = Adam::load_json("checkpoint_epoch_4/optimizer.json")?;
243
244 println!("Loaded weights: {:?}", loaded_weights.data());
245 println!("Loaded bias: {:?}", loaded_bias.data());
246 println!(
247 "Loaded optimizer learning rate: {}",
248 loaded_optimizer.learning_rate()
249 );
250
251 // Verify checkpoint integrity
252 assert_eq!(weights.shape().dims, loaded_weights.shape().dims);
253 assert_eq!(bias.shape().dims, loaded_bias.shape().dims);
254 assert_eq!(optimizer.learning_rate(), loaded_optimizer.learning_rate());
255
256 println!("Checkpointing verification: PASSED");
257
258 Ok(())
259}Sourcepub fn data_mut(&mut self) -> &mut [f32]
pub fn data_mut(&mut self) -> &mut [f32]
Returns a mutable slice of the tensor’s underlying data
Provides safe mutable access to the tensor’s data without requiring unsafe pointer operations. Use this for in-place modifications of tensor values.
§Returns
A mutable slice containing all tensor elements in row-major order
§Performance
- Zero-Cost: Direct slice creation with no copying
- Cache-Friendly: Sequential memory access pattern
- Safe: No unsafe code required for basic data modification
§Examples
use train_station::Tensor;
let mut tensor = Tensor::new(vec![2, 2]);
let data = tensor.data_mut();
// Safe indexing for modification
data[0] = 1.0;
data[1] = 2.0;
assert_eq!(tensor.get(&[0, 0]), 1.0);
assert_eq!(tensor.get(&[0, 1]), 2.0);Examples found in repository?
42fn demonstrate_tensor_creation() {
43 println!("--- Tensor Creation ---");
44
45 // Create tensors with different initializations
46 let zeros = Tensor::zeros(vec![2, 3]);
47 println!(
48 "Zeros tensor: shape {:?}, data: {:?}",
49 zeros.shape().dims,
50 zeros.data()
51 );
52
53 let ones = Tensor::ones(vec![3, 2]);
54 println!(
55 "Ones tensor: shape {:?}, data: {:?}",
56 ones.shape().dims,
57 ones.data()
58 );
59
60 // Create tensor from slice
61 let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
62 let from_slice = Tensor::from_slice(&data, vec![2, 3]).unwrap();
63 println!(
64 "From slice: shape {:?}, data: {:?}",
65 from_slice.shape().dims,
66 from_slice.data()
67 );
68
69 // Create tensor with specific value
70 let mut filled = Tensor::new(vec![2, 2]);
71 {
72 let data = filled.data_mut();
73 for value in data.iter_mut() {
74 *value = 42.0;
75 }
76 }
77 println!("Filled with 42: {:?}", filled.data());
78
79 // Create tensor with random data
80 let random = Tensor::randn(vec![2, 2], Some(42));
81 println!(
82 "Random tensor: shape {:?}, data: {:?}",
83 random.shape().dims,
84 random.data()
85 );
86}Sourcepub fn value(&self) -> f32
pub fn value(&self) -> f32
Extract scalar value from single-element tensor
This method provides a convenient way to extract the scalar value from tensors that contain exactly one element. This is commonly used with element iterator results and scalar tensor operations.
§Returns
The scalar value contained in this tensor
§Panics
Panics if the tensor does not contain exactly one element
§Examples
use train_station::Tensor;
// Single-element tensor
let scalar = Tensor::from_slice(&[42.0], vec![1]).unwrap();
assert_eq!(scalar.value(), 42.0);Examples found in repository?
179fn demonstrate_utility_functions() {
180 println!("\n--- Utility Functions ---");
181
182 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
183
184 // Basic properties
185 println!("Shape: {:?}", tensor.shape().dims);
186 println!("Size: {}", tensor.size());
187 println!("Is contiguous: {}", tensor.is_contiguous());
188 println!("Device: {:?}", tensor.device());
189
190 // Mathematical operations
191 let sum = tensor.sum();
192 println!("Sum: {}", sum.value());
193
194 let mean = tensor.mean();
195 println!("Mean: {}", mean.value());
196
197 let norm = tensor.norm();
198 println!("Norm: {}", norm.value());
199
200 // Device placement
201 let cpu_tensor = Tensor::zeros_on_device(vec![3, 3], train_station::Device::cpu());
202 println!(
203 "CPU tensor: shape {:?}, device: {:?}",
204 cpu_tensor.shape().dims,
205 cpu_tensor.device()
206 );
207}More examples
77fn demonstrate_basic_iteration() -> Result<(), Box<dyn std::error::Error>> {
78 println!("\n--- Basic Element Iteration ---");
79
80 // Create a simple tensor for demonstration
81 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
82 println!("Original tensor: {:?}", tensor.data());
83
84 // Basic iteration with for loop
85 println!("\nBasic iteration with for loop:");
86 for (i, element) in tensor.iter().enumerate() {
87 println!(
88 " Element {}: value = {:.1}, shape = {:?}",
89 i,
90 element.value(),
91 element.shape().dims
92 );
93 }
94
95 // Element-wise transformation
96 println!("\nElement-wise transformation (2x + 1):");
97 let transformed: Tensor = tensor
98 .iter()
99 .map(|elem| elem.mul_scalar(2.0).add_scalar(1.0))
100 .collect();
101 println!(" Result: {:?}", transformed.data());
102
103 // Filtering elements
104 println!("\nFiltering elements (values > 3.0):");
105 let filtered: Tensor = tensor.iter().filter(|elem| elem.value() > 3.0).collect();
106 println!(" Filtered: {:?}", filtered.data());
107
108 Ok(())
109}
110
111/// Demonstrate standard iterator trait methods
112///
113/// Shows compatibility with Rust's standard library iterator methods
114/// and demonstrates various functional programming patterns.
115fn demonstrate_standard_methods() -> Result<(), Box<dyn std::error::Error>> {
116 println!("\n--- Standard Iterator Methods ---");
117
118 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
119
120 // Using map for transformations
121 println!("\nMap transformation (square each element):");
122 let squared: Tensor = tensor.iter().map(|elem| elem.pow_scalar(2.0)).collect();
123 println!(" Squared: {:?}", squared.data());
124
125 // Using enumerate for indexed operations
126 println!("\nEnumerate with indexed operations:");
127 let indexed: Tensor = tensor
128 .iter()
129 .enumerate()
130 .map(|(i, elem)| elem.add_scalar(i as f32))
131 .collect();
132 println!(" Indexed: {:?}", indexed.data());
133
134 // Using fold for reduction
135 println!("\nFold for sum calculation:");
136 let sum: f32 = tensor.iter().fold(0.0, |acc, elem| acc + elem.value());
137 println!(" Sum: {:.1}", sum);
138
139 // Using find for element search
140 println!("\nFind specific element:");
141 if let Some(found) = tensor.iter().find(|elem| elem.value() == 3.0) {
142 println!(" Found element with value 3.0: {:.1}", found.value());
143 }
144
145 // Using any/all for condition checking
146 println!("\nCondition checking:");
147 let all_positive = tensor.iter().all(|elem| elem.value() > 0.0);
148 let any_large = tensor.iter().any(|elem| elem.value() > 4.0);
149 println!(" All positive: {}", all_positive);
150 println!(" Any > 4.0: {}", any_large);
151
152 Ok(())
153}
154
155/// Demonstrate gradient tracking through element operations
156///
157/// Shows how gradient tracking works seamlessly through iterator
158/// operations, maintaining the computational graph for backpropagation.
159fn demonstrate_gradient_tracking() -> Result<(), Box<dyn std::error::Error>> {
160 println!("\n--- Gradient Tracking ---");
161
162 // Create a tensor with gradient tracking enabled
163 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?.with_requires_grad();
164 println!("Input tensor (requires_grad): {:?}", tensor.data());
165
166 // Perform element-wise operations through iteration
167 let result: Tensor = tensor
168 .iter()
169 .map(|elem| {
170 // Apply a complex transformation: (x^2 + 1) * 2
171 elem.pow_scalar(2.0).add_scalar(1.0).mul_scalar(2.0)
172 })
173 .collect();
174
175 println!("Result tensor: {:?}", result.data());
176 println!("Result requires_grad: {}", result.requires_grad());
177
178 // Compute gradients
179 let mut loss = result.sum();
180 loss.backward(None);
181
182 println!("Loss: {:.6}", loss.value());
183 println!("Input gradients: {:?}", tensor.grad().map(|g| g.data()));
184
185 Ok(())
186}
187
188/// Demonstrate advanced iterator patterns
189///
190/// Shows complex iterator chains and advanced functional programming
191/// patterns for sophisticated data processing workflows.
192fn demonstrate_advanced_patterns() -> Result<(), Box<dyn std::error::Error>> {
193 println!("\n--- Advanced Iterator Patterns ---");
194
195 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![6])?;
196 println!("Input tensor: {:?}", tensor.data());
197
198 // Complex chain: enumerate -> filter -> map -> collect
199 println!("\nComplex chain (even indices only, add index to value):");
200 let result: Tensor = tensor
201 .iter()
202 .enumerate()
203 .filter(|(i, _)| i % 2 == 0) // Take even indices
204 .map(|(i, elem)| elem.add_scalar(i as f32)) // Add index to value
205 .collect();
206 println!(" Result: {:?}", result.data());
207
208 // Using take and skip for windowing
209 println!("\nWindowing with take and skip:");
210 let window1: Tensor = tensor.iter().take(3).collect();
211 let window2: Tensor = tensor.iter().skip(2).take(3).collect();
212 println!(" Window 1 (first 3): {:?}", window1.data());
213 println!(" Window 2 (middle 3): {:?}", window2.data());
214
215 // Using rev() for reverse iteration
216 println!("\nReverse iteration:");
217 let reversed: Tensor = tensor.iter().rev().collect();
218 println!(" Reversed: {:?}", reversed.data());
219
220 // Chaining with mathematical operations
221 println!("\nMathematical operation chain:");
222 let math_result: Tensor = tensor
223 .iter()
224 .map(|elem| elem.exp()) // e^x
225 .filter(|elem| elem.value() < 50.0) // Filter large values
226 .map(|elem| elem.log()) // ln(x)
227 .collect();
228 println!(" Math chain result: {:?}", math_result.data());
229
230 // Using zip for element-wise combinations
231 println!("\nElement-wise combination with zip:");
232 let tensor2 = Tensor::from_slice(&[10.0, 20.0, 30.0, 40.0, 50.0, 60.0], vec![6])?;
233 let combined: Tensor = tensor
234 .iter()
235 .zip(tensor2.iter())
236 .map(|(a, b)| a.mul_tensor(&b)) // Element-wise multiplication
237 .collect();
238 println!(" Combined: {:?}", combined.data());
239
240 Ok(())
241}84fn demonstrate_default_adam() -> Result<(), Box<dyn std::error::Error>> {
85 println!("--- Default Adam Configuration ---");
86
87 // Create a simple regression problem: y = 2*x + 1
88 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
89 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
90
91 // Create model parameters
92 let mut weight = Tensor::randn(vec![1, 1], Some(42)).with_requires_grad();
93 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
94
95 // Create Adam optimizer with default configuration
96 let mut optimizer = Adam::new();
97 optimizer.add_parameter(&weight);
98 optimizer.add_parameter(&bias);
99
100 println!("Default Adam configuration:");
101 println!(" Learning rate: {}", optimizer.learning_rate());
102 println!(" Initial weight: {:.6}", weight.value());
103 println!(" Initial bias: {:.6}", bias.value());
104
105 // Training loop
106 let num_epochs = 50;
107 let mut losses = Vec::new();
108
109 for epoch in 0..num_epochs {
110 // Forward pass
111 let y_pred = x_data.matmul(&weight) + &bias;
112 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
113
114 // Backward pass
115 loss.backward(None);
116
117 // Optimizer step
118 optimizer.step(&mut [&mut weight, &mut bias]);
119 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
120
121 losses.push(loss.value());
122
123 if epoch % 10 == 0 || epoch == num_epochs - 1 {
124 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
125 }
126 }
127
128 // Evaluate final model
129 let _final_predictions = x_data.matmul(&weight) + &bias;
130 println!("\nFinal model:");
131 println!(" Learned weight: {:.6} (target: 2.0)", weight.value());
132 println!(" Learned bias: {:.6} (target: 1.0)", bias.value());
133 println!(" Final loss: {:.6}", losses[losses.len() - 1]);
134
135 Ok(())
136}
137
138/// Demonstrate learning rate comparison
139fn demonstrate_learning_rate_comparison() -> Result<(), Box<dyn std::error::Error>> {
140 println!("\n--- Learning Rate Comparison ---");
141
142 let learning_rates = [0.001, 0.01, 0.1];
143 let mut results = Vec::new();
144
145 for &lr in &learning_rates {
146 println!("\nTesting learning rate: {}", lr);
147
148 let stats = train_with_config(TrainingConfig {
149 learning_rate: lr,
150 ..Default::default()
151 })?;
152
153 results.push((lr, stats.clone()));
154
155 println!(" Final loss: {:.6}", stats.final_loss);
156 println!(" Convergence epoch: {}", stats.convergence_epoch);
157 }
158
159 // Compare results
160 println!("\nLearning Rate Comparison Summary:");
161 for (lr, stats) in &results {
162 println!(
163 " LR={:6}: Loss={:.6}, Converged@{}",
164 lr, stats.final_loss, stats.convergence_epoch
165 );
166 }
167
168 Ok(())
169}
170
171/// Demonstrate weight decay comparison
172fn demonstrate_weight_decay_comparison() -> Result<(), Box<dyn std::error::Error>> {
173 println!("\n--- Weight Decay Comparison ---");
174
175 let weight_decays = [0.0, 0.001, 0.01];
176 let mut results = Vec::new();
177
178 for &wd in &weight_decays {
179 println!("\nTesting weight decay: {}", wd);
180
181 let stats = train_with_config(TrainingConfig {
182 weight_decay: wd,
183 ..Default::default()
184 })?;
185
186 results.push((wd, stats.clone()));
187
188 println!(" Final loss: {:.6}", stats.final_loss);
189 println!(" Final weight norm: {:.6}", stats.weight_norm);
190 }
191
192 // Compare results
193 println!("\nWeight Decay Comparison Summary:");
194 for (wd, stats) in &results {
195 println!(
196 " WD={:6}: Loss={:.6}, Weight Norm={:.6}",
197 wd, stats.final_loss, stats.weight_norm
198 );
199 }
200
201 Ok(())
202}
203
204/// Demonstrate beta parameter tuning
205fn demonstrate_beta_parameter_tuning() -> Result<(), Box<dyn std::error::Error>> {
206 println!("\n--- Beta Parameter Tuning ---");
207
208 let beta_configs = [
209 (0.9, 0.999), // Default
210 (0.8, 0.999), // More aggressive momentum
211 (0.95, 0.999), // Less aggressive momentum
212 (0.9, 0.99), // Faster second moment decay
213 ];
214
215 let mut results = Vec::new();
216
217 for (i, (beta1, beta2)) in beta_configs.iter().enumerate() {
218 println!(
219 "\nTesting beta configuration {}: beta1={}, beta2={}",
220 i + 1,
221 beta1,
222 beta2
223 );
224
225 let config = TrainingConfig {
226 beta1: *beta1,
227 beta2: *beta2,
228 ..Default::default()
229 };
230
231 let stats = train_with_config(config)?;
232 results.push(((*beta1, *beta2), stats.clone()));
233
234 println!(" Final loss: {:.6}", stats.final_loss);
235 println!(" Convergence epoch: {}", stats.convergence_epoch);
236 }
237
238 // Compare results
239 println!("\nBeta Parameter Comparison Summary:");
240 for ((beta1, beta2), stats) in &results {
241 println!(
242 " B1={:4}, B2={:5}: Loss={:.6}, Converged@{}",
243 beta1, beta2, stats.final_loss, stats.convergence_epoch
244 );
245 }
246
247 Ok(())
248}
249
250/// Demonstrate configuration benchmarking
251fn demonstrate_configuration_benchmarking() -> Result<(), Box<dyn std::error::Error>> {
252 println!("\n--- Configuration Benchmarking ---");
253
254 // Define configurations to benchmark
255 let configs = vec![
256 (
257 "Conservative",
258 TrainingConfig {
259 learning_rate: 0.001,
260 weight_decay: 0.001,
261 beta1: 0.95,
262 ..Default::default()
263 },
264 ),
265 (
266 "Balanced",
267 TrainingConfig {
268 learning_rate: 0.01,
269 weight_decay: 0.0,
270 beta1: 0.9,
271 ..Default::default()
272 },
273 ),
274 (
275 "Aggressive",
276 TrainingConfig {
277 learning_rate: 0.1,
278 weight_decay: 0.0,
279 beta1: 0.8,
280 ..Default::default()
281 },
282 ),
283 ];
284
285 let mut benchmark_results = Vec::new();
286
287 for (name, config) in configs {
288 println!("\nBenchmarking {} configuration:", name);
289
290 let start_time = std::time::Instant::now();
291 let stats = train_with_config(config.clone())?;
292 let elapsed = start_time.elapsed();
293
294 println!(" Training time: {:.2}ms", elapsed.as_millis());
295 println!(" Final loss: {:.6}", stats.final_loss);
296 println!(" Convergence: {} epochs", stats.convergence_epoch);
297
298 benchmark_results.push((name.to_string(), stats, elapsed));
299 }
300
301 // Summary
302 println!("\nBenchmarking Summary:");
303 for (name, stats, elapsed) in &benchmark_results {
304 println!(
305 " {:12}: Loss={:.6}, Time={:4}ms, Converged@{}",
306 name,
307 stats.final_loss,
308 elapsed.as_millis(),
309 stats.convergence_epoch
310 );
311 }
312
313 Ok(())
314}
315
316/// Helper function to train with specific configuration
317fn train_with_config(config: TrainingConfig) -> Result<TrainingStats, Box<dyn std::error::Error>> {
318 // Create training data
319 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
320 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
321
322 // Create model parameters
323 let mut weight = Tensor::randn(vec![1, 1], Some(123)).with_requires_grad();
324 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
325
326 // Create optimizer with custom configuration
327 let adam_config = AdamConfig {
328 learning_rate: config.learning_rate,
329 beta1: config.beta1,
330 beta2: config.beta2,
331 eps: 1e-8,
332 weight_decay: config.weight_decay,
333 amsgrad: false,
334 };
335
336 let mut optimizer = Adam::with_config(adam_config);
337 optimizer.add_parameter(&weight);
338 optimizer.add_parameter(&bias);
339
340 // Training loop
341 let mut losses = Vec::new();
342 let mut convergence_epoch = config.epochs;
343
344 for epoch in 0..config.epochs {
345 // Forward pass
346 let y_pred = x_data.matmul(&weight) + &bias;
347 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
348
349 // Backward pass
350 loss.backward(None);
351
352 // Optimizer step
353 optimizer.step(&mut [&mut weight, &mut bias]);
354 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
355
356 let loss_value = loss.value();
357 losses.push(loss_value);
358
359 // Check for convergence (loss < 0.01)
360 if loss_value < 0.01 && convergence_epoch == config.epochs {
361 convergence_epoch = epoch;
362 }
363 }
364
365 Ok(TrainingStats {
366 config,
367 final_loss: losses[losses.len() - 1],
368 loss_history: losses,
369 convergence_epoch,
370 weight_norm: weight.norm().value(),
371 })
372}75fn demonstrate_performance_benchmarking() -> Result<(), Box<dyn std::error::Error>> {
76 println!("\n--- Performance Benchmarking ---");
77
78 // Create test data of different sizes
79 let sizes = vec![100, 1000, 10000];
80
81 for size in sizes {
82 println!("\nBenchmarking with tensor size: {}", size);
83
84 // Generate test data
85 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
86 let tensor = Tensor::from_slice(&data, vec![size])?;
87
88 // Benchmark 1: Direct tensor operations
89 let start = Instant::now();
90 let direct_result = tensor.mul_scalar(2.0).add_scalar(1.0);
91 let direct_time = start.elapsed();
92
93 // Benchmark 2: Iterator-based operations
94 let start = Instant::now();
95 let iterator_result: Tensor = tensor
96 .iter()
97 .map(|elem| elem.mul_scalar(2.0).add_scalar(1.0))
98 .collect();
99 let iterator_time = start.elapsed();
100
101 // Benchmark 3: Chained iterator operations
102 let start = Instant::now();
103 let _chained_result: Tensor = tensor
104 .iter()
105 .map(|elem| elem.mul_scalar(2.0))
106 .filter(|elem| elem.value() > size as f32)
107 .map(|elem| elem.add_scalar(1.0))
108 .collect();
109 let chained_time = start.elapsed();
110
111 // Report results
112 println!(" Direct operations: {:?}", direct_time);
113 println!(" Iterator operations: {:?}", iterator_time);
114 println!(" Chained operations: {:?}", chained_time);
115
116 // Verify correctness
117 assert_eq!(direct_result.data(), iterator_result.data());
118 println!(
119 " Results match: {}",
120 direct_result.data() == iterator_result.data()
121 );
122
123 // Performance ratio
124 let ratio = iterator_time.as_nanos() as f64 / direct_time.as_nanos() as f64;
125 println!(" Iterator/Direct ratio: {:.2}x", ratio);
126 }
127
128 Ok(())
129}
130
131/// Demonstrate memory optimization patterns
132///
133/// Shows memory-efficient processing patterns and techniques
134/// for minimizing memory usage while maintaining performance.
135fn demonstrate_memory_optimization() -> Result<(), Box<dyn std::error::Error>> {
136 println!("\n--- Memory Optimization ---");
137
138 // Create a large tensor for memory testing
139 let size = 10000;
140 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
141 let tensor = Tensor::from_slice(&data, vec![size])?;
142
143 println!("Processing tensor of size: {}", size);
144
145 // Pattern 1: Streaming processing (process in chunks)
146 println!("\nPattern 1: Streaming Processing");
147 let chunk_size = 1000;
148 let start = Instant::now();
149
150 let mut streamed_result = Vec::new();
151 for chunk_start in (0..size).step_by(chunk_size) {
152 let chunk_end = (chunk_start + chunk_size).min(size);
153 let chunk: Tensor = tensor
154 .iter_range(chunk_start, chunk_end)
155 .map(|elem| elem.pow_scalar(2.0).sqrt())
156 .collect();
157 streamed_result.extend(chunk.data().iter().cloned());
158 }
159 let streamed_time = start.elapsed();
160
161 // Pattern 2: Full processing
162 let start = Instant::now();
163 let _full_result: Tensor = tensor
164 .iter()
165 .map(|elem| elem.pow_scalar(2.0).sqrt())
166 .collect();
167 let full_time = start.elapsed();
168
169 println!(" Streaming time: {:?}", streamed_time);
170 println!(" Full processing time: {:?}", full_time);
171 println!(
172 " Memory efficiency ratio: {:.2}x",
173 full_time.as_nanos() as f64 / streamed_time.as_nanos() as f64
174 );
175
176 // Pattern 3: Lazy evaluation with take
177 println!("\nPattern 2: Lazy Evaluation");
178 let start = Instant::now();
179 let lazy_result: Tensor = tensor
180 .iter()
181 .take(1000) // Only process first 1000 elements
182 .map(|elem| elem.pow_scalar(2.0).sqrt())
183 .collect();
184 let lazy_time = start.elapsed();
185
186 println!(" Lazy processing (1000 elements): {:?}", lazy_time);
187 println!(" Lazy result size: {}", lazy_result.size());
188
189 // Pattern 4: Memory-efficient filtering
190 println!("\nPattern 3: Memory-Efficient Filtering");
191 let start = Instant::now();
192 let filtered_result: Tensor = tensor
193 .iter()
194 .filter(|elem| elem.value() > size as f32 / 2.0) // Keep only large values
195 .map(|elem| elem.mul_scalar(2.0))
196 .collect();
197 let filtered_time = start.elapsed();
198
199 println!(" Filtered processing: {:?}", filtered_time);
200 println!(
201 " Filtered result size: {} (reduced from {})",
202 filtered_result.size(),
203 size
204 );
205
206 Ok(())
207}
208
209/// Demonstrate large-scale processing techniques
210///
211/// Shows how to efficiently process very large datasets using
212/// iterator patterns and optimization strategies.
213fn demonstrate_large_scale_processing() -> Result<(), Box<dyn std::error::Error>> {
214 println!("\n--- Large-Scale Processing ---");
215
216 // Simulate large dataset processing
217 let sizes = vec![10000, 50000, 100000];
218
219 for size in sizes {
220 println!("\nProcessing dataset of size: {}", size);
221
222 // Generate large dataset
223 let data: Vec<f32> = (0..size)
224 .map(|i| {
225 let x = i as f32 / size as f32;
226 x * x + 0.1 * (i % 10) as f32 // Quadratic with noise
227 })
228 .collect();
229
230 let tensor = Tensor::from_slice(&data, vec![size])?;
231
232 // Technique 1: Batch processing
233 let batch_size = 1000;
234 let start = Instant::now();
235
236 let mut batch_results = Vec::new();
237 for batch_start in (0..size).step_by(batch_size) {
238 let batch_end = (batch_start + batch_size).min(size);
239 let batch: Tensor = tensor
240 .iter_range(batch_start, batch_end)
241 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
242 .collect();
243 batch_results.push(batch);
244 }
245 let batch_time = start.elapsed();
246
247 // Technique 2: Parallel-like processing with stride
248 let start = Instant::now();
249 let stride = 4;
250 let strided_result: Tensor = tensor
251 .iter()
252 .enumerate()
253 .filter(|(i, _)| i % stride == 0)
254 .map(|(_, elem)| elem.pow_scalar(2.0).add_scalar(1.0))
255 .collect();
256 let strided_time = start.elapsed();
257
258 // Technique 3: Hierarchical processing
259 let start = Instant::now();
260 let coarse: Tensor = tensor
261 .iter()
262 .enumerate()
263 .filter(|(i, _)| i % 10 == 0) // Every 10th element
264 .map(|(_, elem)| elem.pow_scalar(2.0).add_scalar(1.0))
265 .collect();
266 let fine: Tensor = tensor
267 .iter()
268 .enumerate()
269 .filter(|(i, _)| i % 10 != 0) // Rest of elements
270 .map(|(_, elem)| elem.pow_scalar(1.5).add_scalar(0.5))
271 .collect();
272 let hierarchical_time = start.elapsed();
273
274 // Report performance
275 println!(" Batch processing: {:?}", batch_time);
276 println!(" Strided processing: {:?}", strided_time);
277 println!(" Hierarchical processing: {:?}", hierarchical_time);
278
279 // Memory usage analysis
280 let total_batches = (size + batch_size - 1) / batch_size;
281 println!(" Batch count: {}", total_batches);
282 println!(" Strided result size: {}", strided_result.size());
283 println!(
284 " Hierarchical: coarse={}, fine={}",
285 coarse.size(),
286 fine.size()
287 );
288 }
289
290 Ok(())
291}
292
293/// Demonstrate advanced optimization techniques
294///
295/// Shows sophisticated optimization strategies and techniques
296/// for maximizing performance in tensor iterator operations.
297fn demonstrate_optimization_techniques() -> Result<(), Box<dyn std::error::Error>> {
298 println!("\n--- Optimization Techniques ---");
299
300 let size = 50000;
301 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
302 let tensor = Tensor::from_slice(&data, vec![size])?;
303
304 println!("Optimizing processing for size: {}", size);
305
306 // Technique 1: Operation fusion
307 println!("\nTechnique 1: Operation Fusion");
308 let start = Instant::now();
309 let fused_result: Tensor = tensor
310 .iter()
311 .map(|elem| {
312 // Fuse multiple operations into single chain
313 elem.mul_scalar(2.0).add_scalar(1.0).pow_scalar(2.0).sqrt()
314 })
315 .collect();
316 let fused_time = start.elapsed();
317
318 // Technique 2: Conditional optimization
319 println!("\nTechnique 2: Conditional Optimization");
320 let start = Instant::now();
321 let conditional_result: Tensor = tensor
322 .iter()
323 .map(|elem| {
324 let val = elem.value();
325 if val < size as f32 / 2.0 {
326 elem.mul_scalar(2.0) // Simple operation for small values
327 } else {
328 elem.pow_scalar(2.0).sqrt() // Complex operation for large values
329 }
330 })
331 .collect();
332 let conditional_time = start.elapsed();
333
334 // Technique 3: Cache-friendly processing
335 println!("\nTechnique 3: Cache-Friendly Processing");
336 let start = Instant::now();
337 let cache_friendly_result: Tensor = tensor
338 .iter()
339 .take(1000) // Process in cache-friendly chunks
340 .map(|elem| elem.mul_scalar(2.0))
341 .collect();
342 let cache_friendly_time = start.elapsed();
343
344 // Technique 4: Memory pooling simulation
345 println!("\nTechnique 4: Memory Pooling Simulation");
346 let start = Instant::now();
347 let pooled_result: Tensor = tensor
348 .iter()
349 .enumerate()
350 .filter(|(i, _)| i % 100 == 0) // Process every 100th element
351 .map(|(_, elem)| elem.pow_scalar(2.0))
352 .collect();
353 let pooled_time = start.elapsed();
354
355 // Report optimization results
356 println!(" Fused operations: {:?}", fused_time);
357 println!(" Conditional optimization: {:?}", conditional_time);
358 println!(" Cache-friendly processing: {:?}", cache_friendly_time);
359 println!(" Memory pooling simulation: {:?}", pooled_time);
360
361 // Performance analysis
362 let fastest = fused_time
363 .min(conditional_time)
364 .min(cache_friendly_time)
365 .min(pooled_time);
366 println!(" Fastest technique: {:?}", fastest);
367
368 // Memory efficiency analysis
369 println!(" Fused result size: {}", fused_result.size());
370 println!(" Conditional result size: {}", conditional_result.size());
371 println!(
372 " Cache-friendly result size: {}",
373 cache_friendly_result.size()
374 );
375 println!(" Pooled result size: {}", pooled_result.size());
376
377 // Technique 5: Gradient optimization
378 println!("\nTechnique 5: Gradient Optimization");
379 let grad_tensor = tensor.with_requires_grad();
380 let start = Instant::now();
381
382 let grad_result: Tensor = grad_tensor
383 .iter()
384 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
385 .collect();
386
387 let mut loss = grad_result.sum();
388 loss.backward(None);
389 let grad_time = start.elapsed();
390
391 println!(" Gradient computation: {:?}", grad_time);
392 println!(
393 " Gradient tracking enabled: {}",
394 grad_result.requires_grad()
395 );
396
397 Ok(())
398}319fn train_with_scheduler(
320 scheduler: &mut dyn LearningRateScheduler,
321 num_epochs: usize,
322) -> Result<TrainingStats, Box<dyn std::error::Error>> {
323 // Create training data: y = 2*x + 1
324 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
325 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
326
327 // Create model parameters
328 let mut weight = Tensor::randn(vec![1, 1], Some(456)).with_requires_grad();
329 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
330
331 // Create optimizer with initial learning rate
332 let mut optimizer = Adam::with_learning_rate(0.05);
333 optimizer.add_parameter(&weight);
334 optimizer.add_parameter(&bias);
335
336 // Training loop
337 let mut losses = Vec::new();
338 let mut lr_history = Vec::new();
339 let mut convergence_epoch = num_epochs;
340
341 for epoch in 0..num_epochs {
342 // Forward pass
343 let y_pred = x_data.matmul(&weight) + &bias;
344 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
345
346 // Backward pass
347 loss.backward(None);
348
349 // Update learning rate using scheduler
350 let current_lr = optimizer.learning_rate();
351 let new_lr = scheduler.step(current_lr, epoch, loss.value());
352
353 if (new_lr - current_lr).abs() > 1e-8 {
354 optimizer.set_learning_rate(new_lr);
355 }
356
357 // Optimizer step
358 optimizer.step(&mut [&mut weight, &mut bias]);
359 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
360
361 let loss_value = loss.value();
362 losses.push(loss_value);
363 lr_history.push(new_lr);
364
365 // Check for convergence
366 if loss_value < 0.01 && convergence_epoch == num_epochs {
367 convergence_epoch = epoch;
368 }
369 }
370
371 Ok(TrainingStats {
372 scheduler_name: scheduler.name().to_string(),
373 final_loss: losses[losses.len() - 1],
374 lr_history,
375 loss_history: losses,
376 convergence_epoch,
377 })
378}105fn demonstrate_linear_regression() -> Result<(), Box<dyn std::error::Error>> {
106 println!("\n--- Linear Regression Training ---");
107
108 // Create model parameters
109 let mut weight = Tensor::randn(vec![1, 1], Some(43)).with_requires_grad();
110 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
111
112 // Create optimizer
113 let mut optimizer = Adam::with_learning_rate(0.01);
114 optimizer.add_parameter(&weight);
115 optimizer.add_parameter(&bias);
116
117 // Create simple training data: y = 2*x + 1
118 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
119 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
120
121 println!("Training data:");
122 println!(" X: {:?}", x_data.data());
123 println!(" Y: {:?}", y_true.data());
124 println!(" Target: y = 2*x + 1");
125
126 // Training loop
127 let num_epochs = 100;
128 let mut losses = Vec::new();
129
130 for epoch in 0..num_epochs {
131 // Forward pass: y_pred = x * weight + bias
132 let y_pred = x_data.matmul(&weight) + &bias;
133
134 // Compute loss: MSE
135 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
136
137 // Backward pass
138 loss.backward(None);
139
140 // Optimizer step
141 optimizer.step(&mut [&mut weight, &mut bias]);
142 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
143
144 losses.push(loss.value());
145
146 // Print progress every 20 epochs
147 if epoch % 20 == 0 || epoch == num_epochs - 1 {
148 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
149 }
150 }
151
152 // Evaluate final model
153 let final_predictions = x_data.matmul(&weight) + &bias;
154 println!("\nFinal model evaluation:");
155 println!(" Learned weight: {:.6}", weight.value());
156 println!(" Learned bias: {:.6}", bias.value());
157 println!(" Predictions vs True:");
158
159 for i in 0..5 {
160 let x1 = x_data.data()[i];
161 let pred = final_predictions.data()[i];
162 let true_val = y_true.data()[i];
163 println!(
164 " x={:.1}: pred={:.3}, true={:.1}, error={:.3}",
165 x1,
166 pred,
167 true_val,
168 (pred - true_val).abs()
169 );
170 }
171
172 Ok(())
173}
174
175/// Demonstrate advanced training patterns
176fn demonstrate_advanced_training() -> Result<(), Box<dyn std::error::Error>> {
177 println!("\n--- Advanced Training Patterns ---");
178
179 // Create a more complex model
180 let mut weight = Tensor::randn(vec![1, 2], Some(44)).with_requires_grad();
181 let mut bias = Tensor::zeros(vec![2]).with_requires_grad();
182
183 // Create optimizer with different learning rate
184 let mut optimizer = Adam::with_learning_rate(0.005);
185 optimizer.add_parameter(&weight);
186 optimizer.add_parameter(&bias);
187
188 // Create training data: y = 2*x + [1, 3]
189 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
190 let y_true = Tensor::from_slice(
191 &[3.0, 5.0, 7.0, 9.0, 11.0, 6.0, 8.0, 10.0, 12.0, 14.0],
192 vec![5, 2],
193 )
194 .unwrap();
195
196 println!("Advanced training with monitoring:");
197 println!(" Initial learning rate: {}", optimizer.learning_rate());
198
199 // Training loop with monitoring
200 let num_epochs = 50;
201 let mut losses = Vec::new();
202 let mut weight_norms = Vec::new();
203 let mut gradient_norms = Vec::new();
204
205 for epoch in 0..num_epochs {
206 // Forward pass
207 let y_pred = x_data.matmul(&weight) + &bias;
208 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
209
210 // Backward pass
211 loss.backward(None);
212
213 // Compute gradient norm before optimizer step
214 let gradient_norm = weight.grad_by_value().unwrap().norm();
215
216 // Optimizer step
217 optimizer.step(&mut [&mut weight, &mut bias]);
218 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
219
220 // Learning rate scheduling: reduce every 10 epochs
221 if epoch > 0 && epoch % 10 == 0 {
222 let current_lr = optimizer.learning_rate();
223 let new_lr = current_lr * 0.5;
224 optimizer.set_learning_rate(new_lr);
225 println!(
226 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
227 epoch, current_lr, new_lr
228 );
229 }
230
231 // Record metrics
232 losses.push(loss.value());
233 weight_norms.push(weight.norm().value());
234 gradient_norms.push(gradient_norm.value());
235
236 // Print detailed progress
237 if epoch % 10 == 0 || epoch == num_epochs - 1 {
238 println!(
239 "Epoch {:2}: Loss = {:.6}, Weight Norm = {:.6}, Gradient Norm = {:.6}",
240 epoch,
241 loss.value(),
242 weight.norm().value(),
243 gradient_norm.value()
244 );
245 }
246 }
247
248 println!("Final learning rate: {}", optimizer.learning_rate());
249
250 // Analyze training progression
251 let initial_loss = losses[0];
252 let final_loss = losses[losses.len() - 1];
253 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
254
255 println!("\nTraining Analysis:");
256 println!(" Initial loss: {:.6}", initial_loss);
257 println!(" Final loss: {:.6}", final_loss);
258 println!(" Loss reduction: {:.1}%", loss_reduction);
259 println!(" Final weight norm: {:.6}", weight.norm().value());
260 println!(" Final bias: {:?}", bias.data());
261
262 Ok(())
263}
264
265/// Demonstrate learning rate scheduling
266fn demonstrate_learning_rate_scheduling() -> Result<(), Box<dyn std::error::Error>> {
267 println!("\n--- Learning Rate Scheduling ---");
268
269 // Create simple model
270 let mut weight = Tensor::randn(vec![1, 1], Some(45)).with_requires_grad();
271 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
272
273 // Create optimizer with high initial learning rate
274 let mut optimizer = Adam::with_learning_rate(0.1);
275 optimizer.add_parameter(&weight);
276 optimizer.add_parameter(&bias);
277
278 // Simple data
279 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3, 1]).unwrap();
280 let y_true = Tensor::from_slice(&[2.0, 4.0, 6.0], vec![3, 1]).unwrap();
281
282 println!("Initial learning rate: {}", optimizer.learning_rate());
283
284 // Training loop with learning rate scheduling
285 let num_epochs = 50;
286 let mut losses = Vec::new();
287
288 for epoch in 0..num_epochs {
289 // Forward pass
290 let y_pred = x_data.matmul(&weight) + &bias;
291 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
292
293 // Backward pass
294 loss.backward(None);
295
296 // Optimizer step
297 optimizer.step(&mut [&mut weight, &mut bias]);
298 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
299
300 // Learning rate scheduling: reduce every 10 epochs
301 if epoch > 0 && epoch % 10 == 0 {
302 let current_lr = optimizer.learning_rate();
303 let new_lr = current_lr * 0.5;
304 optimizer.set_learning_rate(new_lr);
305 println!(
306 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
307 epoch, current_lr, new_lr
308 );
309 }
310
311 losses.push(loss.value());
312
313 // Print progress
314 if epoch % 10 == 0 || epoch == num_epochs - 1 {
315 println!(
316 "Epoch {:2}: Loss = {:.6}, LR = {:.3}",
317 epoch,
318 loss.value(),
319 optimizer.learning_rate()
320 );
321 }
322 }
323
324 println!("Final learning rate: {}", optimizer.learning_rate());
325
326 Ok(())
327}
328
329/// Demonstrate training monitoring and analysis
330fn demonstrate_training_monitoring() -> Result<(), Box<dyn std::error::Error>> {
331 println!("\n--- Training Monitoring ---");
332
333 // Create model
334 let mut weight = Tensor::randn(vec![1, 1], Some(46)).with_requires_grad();
335 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
336
337 // Create optimizer
338 let mut optimizer = Adam::with_learning_rate(0.01);
339 optimizer.add_parameter(&weight);
340 optimizer.add_parameter(&bias);
341
342 // Training data
343 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4, 1]).unwrap();
344 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0], vec![4, 1]).unwrap();
345
346 // Training loop with comprehensive monitoring
347 let num_epochs = 30;
348 let mut losses = Vec::new();
349 let mut weight_history = Vec::new();
350 let mut bias_history = Vec::new();
351
352 for epoch in 0..num_epochs {
353 // Forward pass
354 let y_pred = x_data.matmul(&weight) + &bias;
355 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
356
357 // Backward pass
358 loss.backward(None);
359
360 // Optimizer step
361 optimizer.step(&mut [&mut weight, &mut bias]);
362 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
363
364 // Record history
365 losses.push(loss.value());
366 weight_history.push(weight.value());
367 bias_history.push(bias.value());
368
369 // Print detailed monitoring
370 if epoch % 5 == 0 || epoch == num_epochs - 1 {
371 println!(
372 "Epoch {:2}: Loss = {:.6}, Weight = {:.6}, Bias = {:.6}",
373 epoch,
374 loss.value(),
375 weight.value(),
376 bias.value()
377 );
378 }
379 }
380
381 // Analyze training progression
382 println!("\nTraining Analysis:");
383 println!(" Initial loss: {:.6}", losses[0]);
384 println!(" Final loss: {:.6}", losses[losses.len() - 1]);
385 println!(
386 " Loss reduction: {:.1}%",
387 (losses[0] - losses[losses.len() - 1]) / losses[0] * 100.0
388 );
389
390 // Compute statistics
391 let loss_mean = compute_mean(&losses);
392 let loss_std = compute_std(&losses);
393 let weight_change = (weight_history[weight_history.len() - 1] - weight_history[0]).abs();
394 let bias_change = (bias_history[bias_history.len() - 1] - bias_history[0]).abs();
395
396 println!(" Average loss: {:.6} ± {:.6}", loss_mean, loss_std);
397 println!(" Weight change: {:.6}", weight_change);
398 println!(" Bias change: {:.6}", bias_change);
399 println!(" Final weight norm: {:.6}", weight.norm().value());
400 println!(" Final bias: {:.6}", bias.value());
401
402 Ok(())
403}Sourcepub fn view(&self, new_shape: Vec<i32>) -> Tensor
pub fn view(&self, new_shape: Vec<i32>) -> Tensor
Create a view with a new shape (requires contiguous memory)
Behaves like PyTorch view: tensor must be contiguous and the total
number of elements must remain the same. Supports -1 inference for one dimension.
§Arguments
new_shape- New shape for the tensor (can contain -1 for inference)
§Returns
A tensor viewing the same data with a new shape
§Examples
use train_station::Tensor;
let x = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4]).unwrap();
let y = x.view(vec![2, 2]);
assert_eq!(y.shape().dims, vec![2, 2]);Examples found in repository?
120fn demonstrate_shape_operations() {
121 println!("\n--- Shape Operations ---");
122
123 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
124 println!(
125 "Original: shape {:?}, data: {:?}",
126 tensor.shape().dims,
127 tensor.data()
128 );
129
130 // Reshape (view)
131 let reshaped = tensor.view(vec![3, 2]);
132 println!(
133 "Reshaped to [3, 2]: shape {:?}, data: {:?}",
134 reshaped.shape().dims,
135 reshaped.data()
136 );
137
138 // Create a different shaped tensor for demonstration
139 let tensor_2d = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
140 println!(
141 "2D tensor: shape {:?}, data: {:?}",
142 tensor_2d.shape().dims,
143 tensor_2d.data()
144 );
145
146 // Create a 1D tensor
147 let tensor_1d = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4]).unwrap();
148 println!(
149 "1D tensor: shape {:?}, data: {:?}",
150 tensor_1d.shape().dims,
151 tensor_1d.data()
152 );
153}More examples
324fn demonstrate_single_vs_batch_inference() {
325 println!("\n--- Single vs Batch Inference ---");
326
327 let layer = LinearLayer::new(4, 3, Some(46));
328
329 // Single inference
330 println!("Single inference:");
331 let single_input = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![1, 4]).unwrap();
332 let single_output = layer.forward_no_grad(&single_input);
333 println!(" Input shape: {:?}", single_input.shape().dims);
334 println!(" Output shape: {:?}", single_output.shape().dims);
335 println!(" Output: {:?}", single_output.data());
336
337 // Batch inference
338 println!("Batch inference:");
339 let batch_input = Tensor::from_slice(
340 &[
341 1.0, 2.0, 3.0, 4.0, // Sample 1
342 5.0, 6.0, 7.0, 8.0, // Sample 2
343 9.0, 10.0, 11.0, 12.0, // Sample 3
344 ],
345 vec![3, 4],
346 )
347 .unwrap();
348 let batch_output = layer.forward_no_grad(&batch_input);
349 println!(" Input shape: {:?}", batch_input.shape().dims);
350 println!(" Output shape: {:?}", batch_output.shape().dims);
351
352 // Verify batch consistency - first sample should match single inference
353 let _first_batch_sample = batch_output.view(vec![3, 3]); // Reshape to access first sample
354 let first_sample_data = &batch_output.data()[0..3]; // First 3 elements
355 let single_sample_data = single_output.data();
356
357 println!("Consistency check:");
358 println!(" Single output: {:?}", single_sample_data);
359 println!(" First batch sample: {:?}", first_sample_data);
360 println!(
361 " Match: {}",
362 single_sample_data
363 .iter()
364 .zip(first_sample_data.iter())
365 .all(|(a, b)| (a - b).abs() < 1e-6)
366 );
367}Sourcepub fn element_view(&self, index: usize) -> Tensor
pub fn element_view(&self, index: usize) -> Tensor
Create an element view for the specified index
Returns a scalar tensor (shape [1]) that views a single element of the source tensor. Maintains gradient tracking.
§Arguments
index- Linear index of the element to view
§Returns
A scalar tensor viewing the specified element
§Examples
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let element = tensor.element_view(1);
assert_eq!(element.value(), 2.0);Examples found in repository?
74fn demonstrate_data_pipeline() -> Result<(), Box<dyn std::error::Error>> {
75 println!("\n--- Data Processing Pipeline ---");
76
77 // Simulate raw sensor data with noise
78 let raw_data: Vec<f32> = (0..20)
79 .map(|i| {
80 let base = i as f32 * 0.5;
81 let noise = (i % 3) as f32 * 0.1;
82 base + noise
83 })
84 .collect();
85
86 let tensor = Tensor::from_slice(&raw_data, vec![20])?;
87 println!("Raw sensor data: {:?}", tensor.data());
88
89 // Multi-stage processing pipeline
90 println!("\nProcessing pipeline:");
91 println!("1. Normalize data (z-score)");
92 println!("2. Apply smoothing filter");
93 println!("3. Detect outliers");
94 println!("4. Apply feature scaling");
95
96 // Stage 1: Normalization
97 let mean = tensor.mean().value();
98 let std = tensor.std().value();
99 let normalized: Tensor = tensor
100 .iter()
101 .map(|elem| elem.sub_scalar(mean).div_scalar(std))
102 .collect();
103 println!(
104 " Normalized (mean={:.3}, std={:.3}): {:?}",
105 mean,
106 std,
107 normalized.data()
108 );
109
110 // Stage 2: Smoothing (simple moving average)
111 let smoothed: Tensor = normalized
112 .iter()
113 .enumerate()
114 .map(|(i, elem)| {
115 if i == 0 || i == normalized.size() - 1 {
116 elem.clone()
117 } else {
118 // Simple 3-point average
119 let prev = normalized.element_view(i - 1);
120 let next = normalized.element_view(i + 1);
121 elem.add_tensor(&prev).add_tensor(&next).div_scalar(3.0)
122 }
123 })
124 .collect();
125 println!(" Smoothed: {:?}", smoothed.data());
126
127 // Stage 3: Outlier detection and removal
128 let outlier_threshold = 2.0;
129 let cleaned: Tensor = smoothed
130 .iter()
131 .filter(|elem| elem.value().abs() < outlier_threshold)
132 .collect();
133 println!(
134 " Outliers removed (threshold={}): {:?}",
135 outlier_threshold,
136 cleaned.data()
137 );
138
139 // Stage 4: Feature scaling to [0, 1] range
140 let min_val = cleaned
141 .iter()
142 .map(|e| e.value())
143 .fold(f32::INFINITY, f32::min);
144 let max_val = cleaned
145 .iter()
146 .map(|e| e.value())
147 .fold(f32::NEG_INFINITY, f32::max);
148 let scaled: Tensor = cleaned
149 .iter()
150 .map(|elem| elem.sub_scalar(min_val).div_scalar(max_val - min_val))
151 .collect();
152 println!(" Scaled to [0,1]: {:?}", scaled.data());
153
154 Ok(())
155}
156
157/// Demonstrate conditional processing patterns
158///
159/// Shows how to implement dynamic filtering and transformation
160/// based on data characteristics and conditions.
161fn demonstrate_conditional_processing() -> Result<(), Box<dyn std::error::Error>> {
162 println!("\n--- Conditional Processing ---");
163
164 // Create data with mixed characteristics
165 let data = vec![1.0, -2.0, 3.0, -4.0, 5.0, -6.0, 7.0, -8.0, 9.0, -10.0];
166 let tensor = Tensor::from_slice(&data, vec![10])?;
167 println!("Input data: {:?}", tensor.data());
168
169 // Conditional transformation based on sign
170 println!("\nConditional transformation (positive/negative handling):");
171 let processed: Tensor = tensor
172 .iter()
173 .map(|elem| {
174 let val = elem.value();
175 if val > 0.0 {
176 elem.pow_scalar(2.0) // Square positive values
177 } else {
178 elem.mul_scalar(-1.0).sqrt() // Square root of absolute negative values
179 }
180 })
181 .collect();
182 println!(" Processed: {:?}", processed.data());
183
184 // Adaptive filtering based on local statistics
185 println!("\nAdaptive filtering (remove values > 2 std from local mean):");
186 let window_size = 3;
187 let adaptive_filtered: Tensor = tensor
188 .iter()
189 .enumerate()
190 .filter(|(i, elem)| {
191 let start = i.saturating_sub(window_size / 2);
192 let end = (i + window_size / 2 + 1).min(tensor.size());
193
194 // Calculate local mean and std
195 let local_values: Vec<f32> = (start..end)
196 .map(|j| tensor.element_view(j).value())
197 .collect();
198
199 let local_mean = local_values.iter().sum::<f32>() / local_values.len() as f32;
200 let local_variance = local_values
201 .iter()
202 .map(|v| (v - local_mean).powi(2))
203 .sum::<f32>()
204 / local_values.len() as f32;
205 let local_std = local_variance.sqrt();
206
207 let threshold = local_mean + 2.0 * local_std;
208 elem.value() <= threshold
209 })
210 .map(|(_, elem)| elem)
211 .collect();
212 println!(" Adaptive filtered: {:?}", adaptive_filtered.data());
213
214 // Multi-condition processing
215 println!("\nMulti-condition processing:");
216 let multi_processed: Tensor = tensor
217 .iter()
218 .map(|elem| {
219 let val = elem.value();
220 match () {
221 _ if val > 5.0 => elem.mul_scalar(2.0), // Double large values
222 _ if val < -5.0 => elem.div_scalar(2.0), // Halve small values
223 _ if val.abs() < 2.0 => elem.add_scalar(1.0), // Add 1 to small values
224 _ => elem.clone(), // Keep others unchanged
225 }
226 })
227 .collect();
228 println!(" Multi-condition: {:?}", multi_processed.data());
229
230 Ok(())
231}Sourcepub fn slice_view(&self, start: usize, step: usize, length: usize) -> Tensor
pub fn slice_view(&self, start: usize, step: usize, length: usize) -> Tensor
Create a slice view of the tensor
Returns a view of a contiguous or strided slice of the source tensor.
§Arguments
start- Starting indexstep- Step size (1 for contiguous)length- Number of elements
§Returns
A tensor viewing the specified slice
§Examples
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5]).unwrap();
let slice = tensor.slice_view(1, 2, 2); // [2.0, 4.0]
assert_eq!(slice.data(), &[2.0, 4.0]);Sourcepub fn allocation_owner(&self) -> Option<&Arc<Allocation>>
pub fn allocation_owner(&self) -> Option<&Arc<Allocation>>
Get the allocation owner for this tensor
Returns the shared allocation owner if this tensor is a view, or None if this tensor owns its memory directly.
§Returns
Optional reference to the allocation owner
§Implementation Details
This method is used internally to manage memory lifecycle for tensor views. It helps determine whether a tensor shares memory with another tensor.
Sourcepub fn new_uninitialized(shape_dims: Vec<usize>) -> Self
pub fn new_uninitialized(shape_dims: Vec<usize>) -> Self
Create a new tensor with uninitialized memory
This method allocates memory for a tensor without initializing it to any value. This is useful for performance-critical operations where the memory will be immediately overwritten, such as matrix multiplication results.
§Safety
The caller must ensure that all memory is written before reading from the tensor. Reading from uninitialized memory is undefined behavior.
§Arguments
shape_dims- The dimensions of the tensor
§Returns
A tensor with uninitialized memory
§Performance
- Zero Initialization: Skips memory initialization for maximum performance
- SIMD Ready: Properly aligned for vectorized operations
- Memory Efficient: Uses optimized alignment strategies
§Example
use train_station::Tensor;
// Create uninitialized tensor for matmul result
let mut result = Tensor::new_uninitialized(vec![100, 100]);
// Initialize the memory before use
for value in result.data_mut() {
*value = 0.0;
}Source§impl Tensor
impl Tensor
Sourcepub fn gather(
&self,
dim: usize,
indices: &[usize],
index_shape: &[usize],
) -> Tensor
pub fn gather( &self, dim: usize, indices: &[usize], index_shape: &[usize], ) -> Tensor
Gather values along a dimension using a tensor of indices
This operation extracts elements from the input tensor based on indices provided along a specified dimension. The output tensor has the same shape as the index tensor, with each element taken from the input tensor at the corresponding position with the index value substituted for the specified dimension.
The gather operation is commonly used in machine learning for operations like embedding lookups, attention mechanisms, and advanced indexing patterns.
§Arguments
dim- The dimension along which to gather values (must be < tensor rank)indices- Flattened indices buffer containing the positions to gather fromindex_shape- Shape of the indices tensor and output tensor
§Returns
A new tensor with shape index_shape containing the gathered values
§Examples
§Basic Gather Operation
use train_station::Tensor;
// Create a 2x3 tensor: [[0.0, 0.1, 0.2], [0.3, 0.4, 0.5]]
let tensor = Tensor::from_slice(&[0.0, 0.1, 0.2, 0.3, 0.4, 0.5], vec![2, 3]).unwrap();
// Gather along dimension 1 (columns) with indices [2, 0, 1, 1]
let indices = [2, 0, 1, 1];
let index_shape = [2, 2];
let result = tensor.gather(1, &indices, &index_shape);
// Result shape is [2, 2]
assert_eq!(result.shape().dims, vec![2, 2]);
// Row 0: indices [2, 0] -> [0.2, 0.0]
assert!((result.get(&[0, 0]) - 0.2).abs() < 1e-6);
assert!((result.get(&[0, 1]) - 0.0).abs() < 1e-6);
// Row 1: indices [1, 1] -> [0.4, 0.4]
assert!((result.get(&[1, 0]) - 0.4).abs() < 1e-6);
assert!((result.get(&[1, 1]) - 0.4).abs() < 1e-6);§Gather with Gradient Tracking
use train_station::Tensor;
let tensor = Tensor::from_slice(&[0.0, 0.1, 0.2, 0.3, 0.4, 0.5], vec![2, 3]).unwrap()
.with_requires_grad();
let indices = [1, 1, 0, 2];
let index_shape = [2, 2];
let mut result = tensor.gather(1, &indices, &index_shape);
// Compute gradients
result.backward(None);
let grad = tensor.grad_by_value().expect("gradient missing");
// Verify gradient accumulation for repeated indices
assert!((grad.get(&[0, 1]) - 2.0).abs() < 1e-6); // Index 1 used twice in row 0§Performance Characteristics
- Time Complexity: O(n) where n is the number of elements in the output
- Memory Usage: Creates a new tensor with the same size as the index tensor
- Optimization: Uses precomputed strides for efficient memory access
- GradTrack Overhead: Minimal overhead when gradient tracking is enabled
§Implementation Details
The gather operation works by:
- Validating input dimensions and index bounds
- Creating an output tensor with the specified index shape
- Iterating through all positions in the output tensor
- Computing source offsets using the input tensor’s strides
- Copying values from the input tensor to the output tensor
- Registering the operation for gradient computation if needed
§Safety
This function performs bounds checking to ensure:
- The specified dimension is within the tensor’s rank
- All indices are within bounds for the specified dimension
- The index shape is compatible with the input tensor shape
- The indices buffer length matches the product of index shape dimensions
§Panics
This function will panic if:
dimis greater than or equal to the tensor’s rank- Any index in
indicesis out of bounds for the specified dimension - The
index_shaperank doesn’t match the input tensor’s rank - The
index_shapedimensions don’t match the input tensor (except alongdim) - The
indiceslength doesn’t equal the product ofindex_shapedimensions
Source§impl Tensor
impl Tensor
Sourcepub fn index_select(&self, dim: usize, indices: &[usize]) -> Tensor
pub fn index_select(&self, dim: usize, indices: &[usize]) -> Tensor
Select elements along a dimension using a list of indices
This operation extracts elements from the input tensor along a specified dimension using the provided indices. The output tensor has the same shape as the input except along the specified dimension, where the size becomes the length of the indices array.
The index_select operation is commonly used for extracting specific rows, columns, or slices from tensors, and is particularly useful in machine learning for operations like embedding lookups and attention mechanisms.
§Arguments
dim- The dimension along which to select elements (must be < tensor rank)indices- Array of indices specifying which elements to select alongdim
§Returns
A new tensor with the same shape as the input except along dim, where the
size is indices.len()
§Examples
§Basic Index Selection
use train_station::Tensor;
// Create a 2x3 tensor: [[0.0, 1.0, 2.0], [3.0, 4.0, 5.0]]
let tensor = Tensor::from_slice(&[0.0, 1.0, 2.0, 3.0, 4.0, 5.0], vec![2, 3]).unwrap();
// Select columns 2 and 0 from dimension 1
let result = tensor.index_select(1, &[2, 0]);
// Result shape is [2, 2] (same as input except dim 1 is now 2)
assert_eq!(result.shape().dims, vec![2, 2]);
// Row 0: selected columns [2, 0] -> [2.0, 0.0]
assert_eq!(result.get(&[0, 0]), 2.0);
assert_eq!(result.get(&[0, 1]), 0.0);
// Row 1: selected columns [2, 0] -> [5.0, 3.0]
assert_eq!(result.get(&[1, 0]), 5.0);
assert_eq!(result.get(&[1, 1]), 3.0);§Index Selection with Gradient Tracking
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap()
.with_requires_grad();
// Select specific elements with gradient tracking enabled
let mut result = tensor.index_select(1, &[1, 2]);
result.backward(None);
// Verify gradients are computed correctly
let grad = tensor.grad_by_value().expect("gradient missing");
assert_eq!(grad.shape().dims, vec![2, 3]);§Selecting Rows from a Matrix
use train_station::Tensor;
// Create a 3x2 matrix
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![3, 2]).unwrap();
// Select rows 2 and 0 (dimension 0)
let result = tensor.index_select(0, &[2, 0]);
// Result shape is [2, 2]
assert_eq!(result.shape().dims, vec![2, 2]);
// Selected rows: row 2 [5.0, 6.0], row 0 [1.0, 2.0]
assert_eq!(result.get(&[0, 0]), 5.0); // First row of result (was row 2)
assert_eq!(result.get(&[0, 1]), 6.0);
assert_eq!(result.get(&[1, 0]), 1.0); // Second row of result (was row 0)
assert_eq!(result.get(&[1, 1]), 2.0);§Performance Characteristics
- Time Complexity: O(n) where n is the number of elements in the output tensor
- Memory Usage: Creates a new tensor with size equal to the output shape
- Optimization: Uses precomputed strides for efficient memory access
- GradTrack Overhead: Minimal overhead when gradient tracking is enabled
- Memory Layout: Output tensor is always contiguous for optimal performance
§Implementation Details
The index_select operation works by:
- Validating the dimension and index bounds
- Computing the output shape (same as input except along
dim) - Creating a new contiguous output tensor
- Iterating through all positions in the output tensor using nested loops:
- Outer loop: iterate over dimensions before
dim - Middle loop: iterate over the selected indices
- Inner loop: iterate over dimensions after
dim
- Outer loop: iterate over dimensions before
- Computing source offsets using the input tensor’s strides
- Copying values from input to output tensor
- Registering the operation for gradient computation if needed
§Safety
This function performs comprehensive bounds checking to ensure:
- The specified dimension is within the tensor’s rank
- All indices are within bounds for the specified dimension
- Memory access is safe through proper offset calculations
§Panics
This function will panic if:
dimis greater than or equal to the tensor’s rank- Any index in
indicesis out of bounds for the specified dimension
§Thread Safety
This function is thread-safe and can be called concurrently on different tensors. The operation does not modify the input tensor and creates a new output tensor.
Source§impl Tensor
impl Tensor
Sourcepub fn masked_fill(&self, mask: &[bool], value: f32) -> Tensor
pub fn masked_fill(&self, mask: &[bool], value: f32) -> Tensor
Fill masked elements with a specified value
This operation returns a copy of the input tensor where elements are replaced by the specified value wherever the corresponding boolean mask is true. Elements where the mask is false retain their original values from the input tensor.
The masked_fill operation is commonly used in machine learning for operations like masking attention weights, zeroing out specific elements, and implementing dropout-like functionality.
§Arguments
mask- Boolean array with the same length as the number of tensor elementsvalue- The value to fill masked positions with
§Returns
A new tensor with the same shape as the input, where masked elements are
replaced by value and unmasked elements retain their original values
§Examples
§Basic Masked Fill
use train_station::Tensor;
// Create a 2x3 tensor: [[0.0, 1.0, 2.0], [3.0, 4.0, 5.0]]
let tensor = Tensor::from_slice(&[0.0, 1.0, 2.0, 3.0, 4.0, 5.0], vec![2, 3]).unwrap();
// Create a mask: [false, true, false, true, false, true]
let mask = [false, true, false, true, false, true];
let result = tensor.masked_fill(&mask, -1.0);
// Result: [[0.0, -1.0, 2.0], [-1.0, 4.0, -1.0]]
assert_eq!(result.shape().dims, vec![2, 3]);
assert_eq!(result.get(&[0, 0]), 0.0); // Unmasked
assert_eq!(result.get(&[0, 1]), -1.0); // Masked
assert_eq!(result.get(&[0, 2]), 2.0); // Unmasked
assert_eq!(result.get(&[1, 0]), -1.0); // Masked
assert_eq!(result.get(&[1, 1]), 4.0); // Unmasked
assert_eq!(result.get(&[1, 2]), -1.0); // Masked§Masked Fill with Gradient Tracking
use train_station::Tensor;
let tensor = Tensor::from_slice(&[0.0, 0.1, 0.2, 0.3, 0.4, 0.5], vec![2, 3]).unwrap()
.with_requires_grad();
// Create a mask with some true values
let mask = [false, true, false, true, false, false];
let mut result = tensor.masked_fill(&mask, 5.0);
// Compute gradients
result.backward(None);
let grad = tensor.grad_by_value().expect("gradient missing");
// Gradients should be zero where mask is true, 1 elsewhere
assert_eq!(grad.shape().dims, vec![2, 3]);
assert!((grad.get(&[0, 0]) - 1.0).abs() < 1e-6); // Unmasked: gradient flows
assert!((grad.get(&[0, 1]) - 0.0).abs() < 1e-6); // Masked: no gradient
assert!((grad.get(&[0, 2]) - 1.0).abs() < 1e-6); // Unmasked: gradient flows§Zeroing Out Specific Elements
use train_station::Tensor;
// Create a tensor with some values
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
// Create a mask to zero out every other element
let mask = [true, false, true, false, true, false];
let result = tensor.masked_fill(&mask, 0.0);
// Result: [[0.0, 2.0, 0.0], [4.0, 0.0, 6.0]]
assert_eq!(result.get(&[0, 0]), 0.0); // Zeroed
assert_eq!(result.get(&[0, 1]), 2.0); // Kept
assert_eq!(result.get(&[0, 2]), 0.0); // Zeroed
assert_eq!(result.get(&[1, 0]), 4.0); // Kept
assert_eq!(result.get(&[1, 1]), 0.0); // Zeroed
assert_eq!(result.get(&[1, 2]), 6.0); // Kept§Performance Characteristics
- Time Complexity: O(n) where n is the number of elements in the tensor
- Memory Usage: Creates a new tensor with the same size as the input
- Optimization: Uses efficient stride-based iteration for non-contiguous tensors
- GradTrack Overhead: Minimal overhead when gradient tracking is enabled
- Memory Layout: Output tensor is always contiguous for optimal performance
§Implementation Details
The masked_fill operation works by:
- Validating that the mask length equals the number of tensor elements
- Creating a new contiguous output tensor with the same shape
- Iterating through all elements in logical order
- For each element, checking the corresponding mask value:
- If mask is true: use the fill value
- If mask is false: copy the original value from input tensor
- Computing source offsets using the input tensor’s shape for non-contiguous tensors
- Registering the operation for gradient computation if needed
§Safety
This function performs bounds checking to ensure:
- The mask length equals the number of tensor elements
- Memory access is safe through proper offset calculations
- The operation handles both contiguous and non-contiguous tensors correctly
§Panics
This function will panic if:
- The mask length does not equal the number of tensor elements
§Thread Safety
This function is thread-safe and can be called concurrently on different tensors. The operation does not modify the input tensor and creates a new output tensor.
§GradTrack Behavior
When gradient tracking is enabled:
- Gradients do not flow through masked positions (they are zeroed)
- Gradients flow normally through unmasked positions
- This behavior is useful for implementing operations like dropout
Source§impl Tensor
impl Tensor
Sourcepub fn select(&self, dim: usize, index: usize) -> Tensor
pub fn select(&self, dim: usize, index: usize) -> Tensor
Select a slice along a given dimension at a specific index
This operation extracts a slice from the input tensor by fixing a specific dimension at a given index. The result is a tensor with one fewer dimension than the input, containing the selected slice.
The select operation returns a view (zero-copy) when the base offset is zero, otherwise it creates a contiguous copy to ensure correctness. This operation is commonly used for extracting specific rows, columns, or slices from tensors.
§Arguments
dim- The dimension along which to select (must be < tensor rank)index- The index within the specified dimension to select (must be < dim size)
§Returns
A tensor with the selected slice. The result has the same shape as the input except with the specified dimension removed.
§Examples
§Basic Row Selection
use train_station::Tensor;
// Create a 2x3 tensor: [[0.0, 1.0, 2.0], [3.0, 4.0, 5.0]]
let tensor = Tensor::from_slice(&[0.0, 1.0, 2.0, 3.0, 4.0, 5.0], vec![2, 3]).unwrap();
// Select row 1 (dimension 0, index 1)
let result = tensor.select(0, 1);
// Result shape is [3] (dimension 0 removed)
assert_eq!(result.shape().dims, vec![3]);
assert_eq!(result.get(&[0]), 3.0); // First element of row 1
assert_eq!(result.get(&[1]), 4.0); // Second element of row 1
assert_eq!(result.get(&[2]), 5.0); // Third element of row 1§Column Selection
use train_station::Tensor;
// Create a 2x3 tensor: [[0.0, 1.0, 2.0], [3.0, 4.0, 5.0]]
let tensor = Tensor::from_slice(&[0.0, 1.0, 2.0, 3.0, 4.0, 5.0], vec![2, 3]).unwrap();
// Select column 1 (dimension 1, index 1)
let result = tensor.select(1, 1);
// Result shape is [2] (dimension 1 removed)
assert_eq!(result.shape().dims, vec![2]);
assert_eq!(result.get(&[0]), 1.0); // Column 1, row 0
assert_eq!(result.get(&[1]), 4.0); // Column 1, row 1§Select with Gradient Tracking
use train_station::Tensor;
let tensor = Tensor::from_slice(&[0.0, 1.0, 2.0, 3.0], vec![2, 2]).unwrap()
.with_requires_grad();
// Select row 1 with gradient tracking enabled
let mut result = tensor.select(0, 1);
result.backward(None);
// Verify gradients are computed correctly
let grad = tensor.grad_by_value().expect("gradient missing");
assert_eq!(grad.shape().dims, vec![2, 2]);
// Only row 1 receives gradients
assert_eq!(grad.get(&[0, 0]), 0.0); // Row 0: no gradient
assert_eq!(grad.get(&[0, 1]), 0.0); // Row 0: no gradient
assert_eq!(grad.get(&[1, 0]), 1.0); // Row 1: gradient flows
assert_eq!(grad.get(&[1, 1]), 1.0); // Row 1: gradient flows§Performance Characteristics
- Time Complexity: O(n) where n is the number of elements in the selected slice
- Memory Usage: Zero-copy view when base offset is zero, otherwise creates a copy
- Optimization: Uses efficient stride-based access for non-contiguous tensors
- GradTrack Overhead: Minimal overhead when gradient tracking is enabled
- Memory Layout: Result is contiguous when a copy is made, view otherwise
§Implementation Details
The select operation works by:
- Validating the dimension and index bounds
- Computing the new shape by removing the selected dimension
- Computing the new strides by removing the selected dimension’s stride
- Calculating the base offset for the selected slice
- If base offset is zero: creating a view with adjusted shape/strides
- If base offset is non-zero: creating a contiguous copy of the slice
- Registering the operation for gradient computation if needed
§Safety
This function performs comprehensive bounds checking to ensure:
- The tensor has non-zero rank
- The specified dimension is within the tensor’s rank
- The index is within bounds for the specified dimension
- Memory access is safe through proper offset calculations
§Panics
This function will panic if:
- The tensor has zero rank
dimis greater than or equal to the tensor’s rankindexis greater than or equal to the size of the specified dimension
§Thread Safety
This function is thread-safe and can be called concurrently on different tensors. The operation does not modify the input tensor and creates either a view or a new tensor.
§View vs Copy Behavior
- View (zero-copy): When the base offset is zero, returns a view that shares the same memory as the input tensor with adjusted shape and strides
- Copy: When the base offset is non-zero, creates a contiguous copy to ensure correctness across all operations
§GradTrack Behavior
When gradient tracking is enabled:
- Gradients are scattered back to the selected slice in the input tensor
- Other positions in the input tensor receive zero gradients
- This behavior ensures correct gradient flow for the selected elements
Source§impl Tensor
impl Tensor
Sourcepub fn zeros(shape_dims: Vec<usize>) -> Self
pub fn zeros(shape_dims: Vec<usize>) -> Self
Creates a new tensor filled with zeros
Convenience constructor that creates a tensor and initializes all elements to zero. Uses optimized SIMD operations for efficient zero initialization.
§Arguments
shape_dims- Vector of dimension sizes defining the tensor shape
§Returns
A new tensor with all elements initialized to zero
§Performance
- Memory Allocation: Single allocation with optimized alignment
- Initialization: SIMD-optimized zero filling for large tensors
- Thread Safe: Atomic ID generation for gradtrack tracking
§Examples
use train_station::Tensor;
let tensor = Tensor::zeros(vec![2, 3]);
assert_eq!(tensor.size(), 6);
assert_eq!(tensor.shape().dims, vec![2, 3]);
// Verify all elements are zero
assert_eq!(tensor.get(&[0, 0]), 0.0);
assert_eq!(tensor.get(&[1, 2]), 0.0);Examples found in repository?
68 pub fn new(input_size: usize, output_size: usize, seed: Option<u64>) -> Self {
69 let scale = (1.0 / input_size as f32).sqrt();
70
71 let weight = Tensor::randn(vec![input_size, output_size], seed)
72 .mul_scalar(scale)
73 .with_requires_grad();
74 let bias = Tensor::zeros(vec![output_size]).with_requires_grad();
75
76 Self {
77 weight,
78 bias,
79 input_size,
80 output_size,
81 }
82 }More examples
52 pub fn new(input_size: usize, output_size: usize, seed: Option<u64>) -> Self {
53 // Xavier/Glorot initialization: scale by sqrt(1/input_size)
54 let scale = (1.0 / input_size as f32).sqrt();
55
56 let weight = Tensor::randn(vec![input_size, output_size], seed)
57 .mul_scalar(scale)
58 .with_requires_grad();
59 let bias = Tensor::zeros(vec![output_size]).with_requires_grad();
60
61 Self {
62 weight,
63 bias,
64 input_size,
65 output_size,
66 }
67 }42fn demonstrate_tensor_creation() {
43 println!("--- Tensor Creation ---");
44
45 // Create tensors with different initializations
46 let zeros = Tensor::zeros(vec![2, 3]);
47 println!(
48 "Zeros tensor: shape {:?}, data: {:?}",
49 zeros.shape().dims,
50 zeros.data()
51 );
52
53 let ones = Tensor::ones(vec![3, 2]);
54 println!(
55 "Ones tensor: shape {:?}, data: {:?}",
56 ones.shape().dims,
57 ones.data()
58 );
59
60 // Create tensor from slice
61 let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
62 let from_slice = Tensor::from_slice(&data, vec![2, 3]).unwrap();
63 println!(
64 "From slice: shape {:?}, data: {:?}",
65 from_slice.shape().dims,
66 from_slice.data()
67 );
68
69 // Create tensor with specific value
70 let mut filled = Tensor::new(vec![2, 2]);
71 {
72 let data = filled.data_mut();
73 for value in data.iter_mut() {
74 *value = 42.0;
75 }
76 }
77 println!("Filled with 42: {:?}", filled.data());
78
79 // Create tensor with random data
80 let random = Tensor::randn(vec![2, 2], Some(42));
81 println!(
82 "Random tensor: shape {:?}, data: {:?}",
83 random.shape().dims,
84 random.data()
85 );
86}47fn demonstrate_basic_optimizer_setup() {
48 println!("--- Basic Optimizer Setup ---");
49
50 // Create parameters that require gradients
51 let weight = Tensor::randn(vec![3, 2], Some(42)).with_requires_grad();
52 let bias = Tensor::zeros(vec![2]).with_requires_grad();
53
54 println!("Created parameters:");
55 println!(
56 " Weight: shape {:?}, requires_grad: {}",
57 weight.shape().dims,
58 weight.requires_grad()
59 );
60 println!(
61 " Bias: shape {:?}, requires_grad: {}",
62 bias.shape().dims,
63 bias.requires_grad()
64 );
65
66 // Create Adam optimizer with default configuration
67 let mut optimizer = Adam::new();
68 println!(
69 "Created Adam optimizer with learning rate: {}",
70 optimizer.learning_rate()
71 );
72
73 // Add parameters to optimizer
74 optimizer.add_parameter(&weight);
75 optimizer.add_parameter(&bias);
76 println!(
77 "Added {} parameters to optimizer",
78 optimizer.parameter_count()
79 );
80
81 // Create optimizer with custom configuration
82 let config = AdamConfig {
83 learning_rate: 0.01,
84 beta1: 0.9,
85 beta2: 0.999,
86 eps: 1e-8,
87 weight_decay: 0.0,
88 amsgrad: false,
89 };
90
91 let mut custom_optimizer = Adam::with_config(config);
92 custom_optimizer.add_parameter(&weight);
93 custom_optimizer.add_parameter(&bias);
94
95 println!(
96 "Created custom optimizer with learning rate: {}",
97 custom_optimizer.learning_rate()
98 );
99
100 // Demonstrate parameter linking
101 println!("Parameter linking completed successfully");
102}
103
104/// Demonstrate simple linear regression training
105fn demonstrate_linear_regression() -> Result<(), Box<dyn std::error::Error>> {
106 println!("\n--- Linear Regression Training ---");
107
108 // Create model parameters
109 let mut weight = Tensor::randn(vec![1, 1], Some(43)).with_requires_grad();
110 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
111
112 // Create optimizer
113 let mut optimizer = Adam::with_learning_rate(0.01);
114 optimizer.add_parameter(&weight);
115 optimizer.add_parameter(&bias);
116
117 // Create simple training data: y = 2*x + 1
118 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
119 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
120
121 println!("Training data:");
122 println!(" X: {:?}", x_data.data());
123 println!(" Y: {:?}", y_true.data());
124 println!(" Target: y = 2*x + 1");
125
126 // Training loop
127 let num_epochs = 100;
128 let mut losses = Vec::new();
129
130 for epoch in 0..num_epochs {
131 // Forward pass: y_pred = x * weight + bias
132 let y_pred = x_data.matmul(&weight) + &bias;
133
134 // Compute loss: MSE
135 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
136
137 // Backward pass
138 loss.backward(None);
139
140 // Optimizer step
141 optimizer.step(&mut [&mut weight, &mut bias]);
142 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
143
144 losses.push(loss.value());
145
146 // Print progress every 20 epochs
147 if epoch % 20 == 0 || epoch == num_epochs - 1 {
148 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
149 }
150 }
151
152 // Evaluate final model
153 let final_predictions = x_data.matmul(&weight) + &bias;
154 println!("\nFinal model evaluation:");
155 println!(" Learned weight: {:.6}", weight.value());
156 println!(" Learned bias: {:.6}", bias.value());
157 println!(" Predictions vs True:");
158
159 for i in 0..5 {
160 let x1 = x_data.data()[i];
161 let pred = final_predictions.data()[i];
162 let true_val = y_true.data()[i];
163 println!(
164 " x={:.1}: pred={:.3}, true={:.1}, error={:.3}",
165 x1,
166 pred,
167 true_val,
168 (pred - true_val).abs()
169 );
170 }
171
172 Ok(())
173}
174
175/// Demonstrate advanced training patterns
176fn demonstrate_advanced_training() -> Result<(), Box<dyn std::error::Error>> {
177 println!("\n--- Advanced Training Patterns ---");
178
179 // Create a more complex model
180 let mut weight = Tensor::randn(vec![1, 2], Some(44)).with_requires_grad();
181 let mut bias = Tensor::zeros(vec![2]).with_requires_grad();
182
183 // Create optimizer with different learning rate
184 let mut optimizer = Adam::with_learning_rate(0.005);
185 optimizer.add_parameter(&weight);
186 optimizer.add_parameter(&bias);
187
188 // Create training data: y = 2*x + [1, 3]
189 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
190 let y_true = Tensor::from_slice(
191 &[3.0, 5.0, 7.0, 9.0, 11.0, 6.0, 8.0, 10.0, 12.0, 14.0],
192 vec![5, 2],
193 )
194 .unwrap();
195
196 println!("Advanced training with monitoring:");
197 println!(" Initial learning rate: {}", optimizer.learning_rate());
198
199 // Training loop with monitoring
200 let num_epochs = 50;
201 let mut losses = Vec::new();
202 let mut weight_norms = Vec::new();
203 let mut gradient_norms = Vec::new();
204
205 for epoch in 0..num_epochs {
206 // Forward pass
207 let y_pred = x_data.matmul(&weight) + &bias;
208 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
209
210 // Backward pass
211 loss.backward(None);
212
213 // Compute gradient norm before optimizer step
214 let gradient_norm = weight.grad_by_value().unwrap().norm();
215
216 // Optimizer step
217 optimizer.step(&mut [&mut weight, &mut bias]);
218 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
219
220 // Learning rate scheduling: reduce every 10 epochs
221 if epoch > 0 && epoch % 10 == 0 {
222 let current_lr = optimizer.learning_rate();
223 let new_lr = current_lr * 0.5;
224 optimizer.set_learning_rate(new_lr);
225 println!(
226 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
227 epoch, current_lr, new_lr
228 );
229 }
230
231 // Record metrics
232 losses.push(loss.value());
233 weight_norms.push(weight.norm().value());
234 gradient_norms.push(gradient_norm.value());
235
236 // Print detailed progress
237 if epoch % 10 == 0 || epoch == num_epochs - 1 {
238 println!(
239 "Epoch {:2}: Loss = {:.6}, Weight Norm = {:.6}, Gradient Norm = {:.6}",
240 epoch,
241 loss.value(),
242 weight.norm().value(),
243 gradient_norm.value()
244 );
245 }
246 }
247
248 println!("Final learning rate: {}", optimizer.learning_rate());
249
250 // Analyze training progression
251 let initial_loss = losses[0];
252 let final_loss = losses[losses.len() - 1];
253 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
254
255 println!("\nTraining Analysis:");
256 println!(" Initial loss: {:.6}", initial_loss);
257 println!(" Final loss: {:.6}", final_loss);
258 println!(" Loss reduction: {:.1}%", loss_reduction);
259 println!(" Final weight norm: {:.6}", weight.norm().value());
260 println!(" Final bias: {:?}", bias.data());
261
262 Ok(())
263}
264
265/// Demonstrate learning rate scheduling
266fn demonstrate_learning_rate_scheduling() -> Result<(), Box<dyn std::error::Error>> {
267 println!("\n--- Learning Rate Scheduling ---");
268
269 // Create simple model
270 let mut weight = Tensor::randn(vec![1, 1], Some(45)).with_requires_grad();
271 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
272
273 // Create optimizer with high initial learning rate
274 let mut optimizer = Adam::with_learning_rate(0.1);
275 optimizer.add_parameter(&weight);
276 optimizer.add_parameter(&bias);
277
278 // Simple data
279 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3, 1]).unwrap();
280 let y_true = Tensor::from_slice(&[2.0, 4.0, 6.0], vec![3, 1]).unwrap();
281
282 println!("Initial learning rate: {}", optimizer.learning_rate());
283
284 // Training loop with learning rate scheduling
285 let num_epochs = 50;
286 let mut losses = Vec::new();
287
288 for epoch in 0..num_epochs {
289 // Forward pass
290 let y_pred = x_data.matmul(&weight) + &bias;
291 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
292
293 // Backward pass
294 loss.backward(None);
295
296 // Optimizer step
297 optimizer.step(&mut [&mut weight, &mut bias]);
298 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
299
300 // Learning rate scheduling: reduce every 10 epochs
301 if epoch > 0 && epoch % 10 == 0 {
302 let current_lr = optimizer.learning_rate();
303 let new_lr = current_lr * 0.5;
304 optimizer.set_learning_rate(new_lr);
305 println!(
306 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
307 epoch, current_lr, new_lr
308 );
309 }
310
311 losses.push(loss.value());
312
313 // Print progress
314 if epoch % 10 == 0 || epoch == num_epochs - 1 {
315 println!(
316 "Epoch {:2}: Loss = {:.6}, LR = {:.3}",
317 epoch,
318 loss.value(),
319 optimizer.learning_rate()
320 );
321 }
322 }
323
324 println!("Final learning rate: {}", optimizer.learning_rate());
325
326 Ok(())
327}
328
329/// Demonstrate training monitoring and analysis
330fn demonstrate_training_monitoring() -> Result<(), Box<dyn std::error::Error>> {
331 println!("\n--- Training Monitoring ---");
332
333 // Create model
334 let mut weight = Tensor::randn(vec![1, 1], Some(46)).with_requires_grad();
335 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
336
337 // Create optimizer
338 let mut optimizer = Adam::with_learning_rate(0.01);
339 optimizer.add_parameter(&weight);
340 optimizer.add_parameter(&bias);
341
342 // Training data
343 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4, 1]).unwrap();
344 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0], vec![4, 1]).unwrap();
345
346 // Training loop with comprehensive monitoring
347 let num_epochs = 30;
348 let mut losses = Vec::new();
349 let mut weight_history = Vec::new();
350 let mut bias_history = Vec::new();
351
352 for epoch in 0..num_epochs {
353 // Forward pass
354 let y_pred = x_data.matmul(&weight) + &bias;
355 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
356
357 // Backward pass
358 loss.backward(None);
359
360 // Optimizer step
361 optimizer.step(&mut [&mut weight, &mut bias]);
362 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
363
364 // Record history
365 losses.push(loss.value());
366 weight_history.push(weight.value());
367 bias_history.push(bias.value());
368
369 // Print detailed monitoring
370 if epoch % 5 == 0 || epoch == num_epochs - 1 {
371 println!(
372 "Epoch {:2}: Loss = {:.6}, Weight = {:.6}, Bias = {:.6}",
373 epoch,
374 loss.value(),
375 weight.value(),
376 bias.value()
377 );
378 }
379 }
380
381 // Analyze training progression
382 println!("\nTraining Analysis:");
383 println!(" Initial loss: {:.6}", losses[0]);
384 println!(" Final loss: {:.6}", losses[losses.len() - 1]);
385 println!(
386 " Loss reduction: {:.1}%",
387 (losses[0] - losses[losses.len() - 1]) / losses[0] * 100.0
388 );
389
390 // Compute statistics
391 let loss_mean = compute_mean(&losses);
392 let loss_std = compute_std(&losses);
393 let weight_change = (weight_history[weight_history.len() - 1] - weight_history[0]).abs();
394 let bias_change = (bias_history[bias_history.len() - 1] - bias_history[0]).abs();
395
396 println!(" Average loss: {:.6} ± {:.6}", loss_mean, loss_std);
397 println!(" Weight change: {:.6}", weight_change);
398 println!(" Bias change: {:.6}", bias_change);
399 println!(" Final weight norm: {:.6}", weight.norm().value());
400 println!(" Final bias: {:.6}", bias.value());
401
402 Ok(())
403}84fn demonstrate_default_adam() -> Result<(), Box<dyn std::error::Error>> {
85 println!("--- Default Adam Configuration ---");
86
87 // Create a simple regression problem: y = 2*x + 1
88 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
89 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
90
91 // Create model parameters
92 let mut weight = Tensor::randn(vec![1, 1], Some(42)).with_requires_grad();
93 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
94
95 // Create Adam optimizer with default configuration
96 let mut optimizer = Adam::new();
97 optimizer.add_parameter(&weight);
98 optimizer.add_parameter(&bias);
99
100 println!("Default Adam configuration:");
101 println!(" Learning rate: {}", optimizer.learning_rate());
102 println!(" Initial weight: {:.6}", weight.value());
103 println!(" Initial bias: {:.6}", bias.value());
104
105 // Training loop
106 let num_epochs = 50;
107 let mut losses = Vec::new();
108
109 for epoch in 0..num_epochs {
110 // Forward pass
111 let y_pred = x_data.matmul(&weight) + &bias;
112 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
113
114 // Backward pass
115 loss.backward(None);
116
117 // Optimizer step
118 optimizer.step(&mut [&mut weight, &mut bias]);
119 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
120
121 losses.push(loss.value());
122
123 if epoch % 10 == 0 || epoch == num_epochs - 1 {
124 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
125 }
126 }
127
128 // Evaluate final model
129 let _final_predictions = x_data.matmul(&weight) + &bias;
130 println!("\nFinal model:");
131 println!(" Learned weight: {:.6} (target: 2.0)", weight.value());
132 println!(" Learned bias: {:.6} (target: 1.0)", bias.value());
133 println!(" Final loss: {:.6}", losses[losses.len() - 1]);
134
135 Ok(())
136}
137
138/// Demonstrate learning rate comparison
139fn demonstrate_learning_rate_comparison() -> Result<(), Box<dyn std::error::Error>> {
140 println!("\n--- Learning Rate Comparison ---");
141
142 let learning_rates = [0.001, 0.01, 0.1];
143 let mut results = Vec::new();
144
145 for &lr in &learning_rates {
146 println!("\nTesting learning rate: {}", lr);
147
148 let stats = train_with_config(TrainingConfig {
149 learning_rate: lr,
150 ..Default::default()
151 })?;
152
153 results.push((lr, stats.clone()));
154
155 println!(" Final loss: {:.6}", stats.final_loss);
156 println!(" Convergence epoch: {}", stats.convergence_epoch);
157 }
158
159 // Compare results
160 println!("\nLearning Rate Comparison Summary:");
161 for (lr, stats) in &results {
162 println!(
163 " LR={:6}: Loss={:.6}, Converged@{}",
164 lr, stats.final_loss, stats.convergence_epoch
165 );
166 }
167
168 Ok(())
169}
170
171/// Demonstrate weight decay comparison
172fn demonstrate_weight_decay_comparison() -> Result<(), Box<dyn std::error::Error>> {
173 println!("\n--- Weight Decay Comparison ---");
174
175 let weight_decays = [0.0, 0.001, 0.01];
176 let mut results = Vec::new();
177
178 for &wd in &weight_decays {
179 println!("\nTesting weight decay: {}", wd);
180
181 let stats = train_with_config(TrainingConfig {
182 weight_decay: wd,
183 ..Default::default()
184 })?;
185
186 results.push((wd, stats.clone()));
187
188 println!(" Final loss: {:.6}", stats.final_loss);
189 println!(" Final weight norm: {:.6}", stats.weight_norm);
190 }
191
192 // Compare results
193 println!("\nWeight Decay Comparison Summary:");
194 for (wd, stats) in &results {
195 println!(
196 " WD={:6}: Loss={:.6}, Weight Norm={:.6}",
197 wd, stats.final_loss, stats.weight_norm
198 );
199 }
200
201 Ok(())
202}
203
204/// Demonstrate beta parameter tuning
205fn demonstrate_beta_parameter_tuning() -> Result<(), Box<dyn std::error::Error>> {
206 println!("\n--- Beta Parameter Tuning ---");
207
208 let beta_configs = [
209 (0.9, 0.999), // Default
210 (0.8, 0.999), // More aggressive momentum
211 (0.95, 0.999), // Less aggressive momentum
212 (0.9, 0.99), // Faster second moment decay
213 ];
214
215 let mut results = Vec::new();
216
217 for (i, (beta1, beta2)) in beta_configs.iter().enumerate() {
218 println!(
219 "\nTesting beta configuration {}: beta1={}, beta2={}",
220 i + 1,
221 beta1,
222 beta2
223 );
224
225 let config = TrainingConfig {
226 beta1: *beta1,
227 beta2: *beta2,
228 ..Default::default()
229 };
230
231 let stats = train_with_config(config)?;
232 results.push(((*beta1, *beta2), stats.clone()));
233
234 println!(" Final loss: {:.6}", stats.final_loss);
235 println!(" Convergence epoch: {}", stats.convergence_epoch);
236 }
237
238 // Compare results
239 println!("\nBeta Parameter Comparison Summary:");
240 for ((beta1, beta2), stats) in &results {
241 println!(
242 " B1={:4}, B2={:5}: Loss={:.6}, Converged@{}",
243 beta1, beta2, stats.final_loss, stats.convergence_epoch
244 );
245 }
246
247 Ok(())
248}
249
250/// Demonstrate configuration benchmarking
251fn demonstrate_configuration_benchmarking() -> Result<(), Box<dyn std::error::Error>> {
252 println!("\n--- Configuration Benchmarking ---");
253
254 // Define configurations to benchmark
255 let configs = vec![
256 (
257 "Conservative",
258 TrainingConfig {
259 learning_rate: 0.001,
260 weight_decay: 0.001,
261 beta1: 0.95,
262 ..Default::default()
263 },
264 ),
265 (
266 "Balanced",
267 TrainingConfig {
268 learning_rate: 0.01,
269 weight_decay: 0.0,
270 beta1: 0.9,
271 ..Default::default()
272 },
273 ),
274 (
275 "Aggressive",
276 TrainingConfig {
277 learning_rate: 0.1,
278 weight_decay: 0.0,
279 beta1: 0.8,
280 ..Default::default()
281 },
282 ),
283 ];
284
285 let mut benchmark_results = Vec::new();
286
287 for (name, config) in configs {
288 println!("\nBenchmarking {} configuration:", name);
289
290 let start_time = std::time::Instant::now();
291 let stats = train_with_config(config.clone())?;
292 let elapsed = start_time.elapsed();
293
294 println!(" Training time: {:.2}ms", elapsed.as_millis());
295 println!(" Final loss: {:.6}", stats.final_loss);
296 println!(" Convergence: {} epochs", stats.convergence_epoch);
297
298 benchmark_results.push((name.to_string(), stats, elapsed));
299 }
300
301 // Summary
302 println!("\nBenchmarking Summary:");
303 for (name, stats, elapsed) in &benchmark_results {
304 println!(
305 " {:12}: Loss={:.6}, Time={:4}ms, Converged@{}",
306 name,
307 stats.final_loss,
308 elapsed.as_millis(),
309 stats.convergence_epoch
310 );
311 }
312
313 Ok(())
314}
315
316/// Helper function to train with specific configuration
317fn train_with_config(config: TrainingConfig) -> Result<TrainingStats, Box<dyn std::error::Error>> {
318 // Create training data
319 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
320 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
321
322 // Create model parameters
323 let mut weight = Tensor::randn(vec![1, 1], Some(123)).with_requires_grad();
324 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
325
326 // Create optimizer with custom configuration
327 let adam_config = AdamConfig {
328 learning_rate: config.learning_rate,
329 beta1: config.beta1,
330 beta2: config.beta2,
331 eps: 1e-8,
332 weight_decay: config.weight_decay,
333 amsgrad: false,
334 };
335
336 let mut optimizer = Adam::with_config(adam_config);
337 optimizer.add_parameter(&weight);
338 optimizer.add_parameter(&bias);
339
340 // Training loop
341 let mut losses = Vec::new();
342 let mut convergence_epoch = config.epochs;
343
344 for epoch in 0..config.epochs {
345 // Forward pass
346 let y_pred = x_data.matmul(&weight) + &bias;
347 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
348
349 // Backward pass
350 loss.backward(None);
351
352 // Optimizer step
353 optimizer.step(&mut [&mut weight, &mut bias]);
354 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
355
356 let loss_value = loss.value();
357 losses.push(loss_value);
358
359 // Check for convergence (loss < 0.01)
360 if loss_value < 0.01 && convergence_epoch == config.epochs {
361 convergence_epoch = epoch;
362 }
363 }
364
365 Ok(TrainingStats {
366 config,
367 final_loss: losses[losses.len() - 1],
368 loss_history: losses,
369 convergence_epoch,
370 weight_norm: weight.norm().value(),
371 })
372}319fn train_with_scheduler(
320 scheduler: &mut dyn LearningRateScheduler,
321 num_epochs: usize,
322) -> Result<TrainingStats, Box<dyn std::error::Error>> {
323 // Create training data: y = 2*x + 1
324 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
325 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
326
327 // Create model parameters
328 let mut weight = Tensor::randn(vec![1, 1], Some(456)).with_requires_grad();
329 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
330
331 // Create optimizer with initial learning rate
332 let mut optimizer = Adam::with_learning_rate(0.05);
333 optimizer.add_parameter(&weight);
334 optimizer.add_parameter(&bias);
335
336 // Training loop
337 let mut losses = Vec::new();
338 let mut lr_history = Vec::new();
339 let mut convergence_epoch = num_epochs;
340
341 for epoch in 0..num_epochs {
342 // Forward pass
343 let y_pred = x_data.matmul(&weight) + &bias;
344 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
345
346 // Backward pass
347 loss.backward(None);
348
349 // Update learning rate using scheduler
350 let current_lr = optimizer.learning_rate();
351 let new_lr = scheduler.step(current_lr, epoch, loss.value());
352
353 if (new_lr - current_lr).abs() > 1e-8 {
354 optimizer.set_learning_rate(new_lr);
355 }
356
357 // Optimizer step
358 optimizer.step(&mut [&mut weight, &mut bias]);
359 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
360
361 let loss_value = loss.value();
362 losses.push(loss_value);
363 lr_history.push(new_lr);
364
365 // Check for convergence
366 if loss_value < 0.01 && convergence_epoch == num_epochs {
367 convergence_epoch = epoch;
368 }
369 }
370
371 Ok(TrainingStats {
372 scheduler_name: scheduler.name().to_string(),
373 final_loss: losses[losses.len() - 1],
374 lr_history,
375 loss_history: losses,
376 convergence_epoch,
377 })
378}Sourcepub fn ones(shape_dims: Vec<usize>) -> Self
pub fn ones(shape_dims: Vec<usize>) -> Self
Creates a new tensor filled with ones
Convenience constructor that creates a tensor and initializes all elements to one. Uses optimized SIMD operations for efficient initialization.
§Arguments
shape_dims- Vector of dimension sizes defining the tensor shape
§Returns
A new tensor with all elements initialized to one
§Performance
- Memory Allocation: Single allocation with optimized alignment
- Initialization: SIMD-optimized one filling for large tensors
- Thread Safe: Atomic ID generation for gradtrack tracking
§Examples
use train_station::Tensor;
let tensor = Tensor::ones(vec![2, 3]);
assert_eq!(tensor.size(), 6);
assert_eq!(tensor.shape().dims, vec![2, 3]);
// Verify all elements are one
assert_eq!(tensor.get(&[0, 0]), 1.0);
assert_eq!(tensor.get(&[1, 2]), 1.0);Examples found in repository?
42fn demonstrate_tensor_creation() {
43 println!("--- Tensor Creation ---");
44
45 // Create tensors with different initializations
46 let zeros = Tensor::zeros(vec![2, 3]);
47 println!(
48 "Zeros tensor: shape {:?}, data: {:?}",
49 zeros.shape().dims,
50 zeros.data()
51 );
52
53 let ones = Tensor::ones(vec![3, 2]);
54 println!(
55 "Ones tensor: shape {:?}, data: {:?}",
56 ones.shape().dims,
57 ones.data()
58 );
59
60 // Create tensor from slice
61 let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
62 let from_slice = Tensor::from_slice(&data, vec![2, 3]).unwrap();
63 println!(
64 "From slice: shape {:?}, data: {:?}",
65 from_slice.shape().dims,
66 from_slice.data()
67 );
68
69 // Create tensor with specific value
70 let mut filled = Tensor::new(vec![2, 2]);
71 {
72 let data = filled.data_mut();
73 for value in data.iter_mut() {
74 *value = 42.0;
75 }
76 }
77 println!("Filled with 42: {:?}", filled.data());
78
79 // Create tensor with random data
80 let random = Tensor::randn(vec![2, 2], Some(42));
81 println!(
82 "Random tensor: shape {:?}, data: {:?}",
83 random.shape().dims,
84 random.data()
85 );
86}Sourcepub fn zeros_on_device(shape_dims: Vec<usize>, device: Device) -> Self
pub fn zeros_on_device(shape_dims: Vec<usize>, device: Device) -> Self
Creates a new tensor filled with zeros on a specific device
Convenience constructor that creates a tensor on the specified device and initializes all elements to zero. Uses optimized SIMD operations for efficient zero initialization.
§Arguments
shape_dims- Vector of dimension sizes defining the tensor shapedevice- The device where the tensor should be allocated
§Returns
A new tensor with all elements initialized to zero
§Performance
- Memory Allocation: Device-specific allocation with optimized alignment
- Initialization: SIMD-optimized zero filling for large tensors
- Thread Safe: Atomic ID generation for gradtrack tracking
§Examples
use train_station::Tensor;
use train_station::Device;
let tensor = Tensor::zeros_on_device(vec![2, 2], Device::cpu());
assert_eq!(tensor.device(), Device::cpu());
assert_eq!(tensor.size(), 4);
// Verify all elements are zero
assert_eq!(tensor.get(&[0, 0]), 0.0);
assert_eq!(tensor.get(&[1, 1]), 0.0);Examples found in repository?
179fn demonstrate_utility_functions() {
180 println!("\n--- Utility Functions ---");
181
182 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
183
184 // Basic properties
185 println!("Shape: {:?}", tensor.shape().dims);
186 println!("Size: {}", tensor.size());
187 println!("Is contiguous: {}", tensor.is_contiguous());
188 println!("Device: {:?}", tensor.device());
189
190 // Mathematical operations
191 let sum = tensor.sum();
192 println!("Sum: {}", sum.value());
193
194 let mean = tensor.mean();
195 println!("Mean: {}", mean.value());
196
197 let norm = tensor.norm();
198 println!("Norm: {}", norm.value());
199
200 // Device placement
201 let cpu_tensor = Tensor::zeros_on_device(vec![3, 3], train_station::Device::cpu());
202 println!(
203 "CPU tensor: shape {:?}, device: {:?}",
204 cpu_tensor.shape().dims,
205 cpu_tensor.device()
206 );
207}Sourcepub fn ones_on_device(shape_dims: Vec<usize>, device: Device) -> Self
pub fn ones_on_device(shape_dims: Vec<usize>, device: Device) -> Self
Creates a new tensor filled with ones on a specific device
Convenience constructor that creates a tensor on the specified device and initializes all elements to one. Uses optimized SIMD operations for efficient initialization.
§Arguments
shape_dims- Vector of dimension sizes defining the tensor shapedevice- The device where the tensor should be allocated
§Returns
A new tensor with all elements initialized to one
§Performance
- Memory Allocation: Device-specific allocation with optimized alignment
- Initialization: SIMD-optimized one filling for large tensors
- Thread Safe: Atomic ID generation for gradtrack tracking
§Examples
use train_station::Tensor;
use train_station::Device;
let tensor = Tensor::ones_on_device(vec![2, 2], Device::cpu());
assert_eq!(tensor.device(), Device::cpu());
assert_eq!(tensor.size(), 4);
// Verify all elements are one
assert_eq!(tensor.get(&[0, 0]), 1.0);
assert_eq!(tensor.get(&[1, 1]), 1.0);Sourcepub fn fill(&mut self, value: f32)
pub fn fill(&mut self, value: f32)
Fills the tensor with a constant value using SIMD optimization
Efficiently initializes all elements of the tensor to the specified value. Uses SIMD operations for large tensors to maximize performance.
§Arguments
value- The value to fill the tensor with
§Performance
- SIMD Optimization: Uses AVX2 for large tensors when available
- Unrolled Loops: 4x unrolling for better instruction throughput
- Memory Bandwidth: Optimized for maximum memory bandwidth utilization
§Examples
use train_station::Tensor;
let mut tensor = Tensor::new(vec![2, 3]);
tensor.fill(42.0);
// Verify all elements are 42.0
assert_eq!(tensor.get(&[0, 0]), 42.0);
assert_eq!(tensor.get(&[1, 2]), 42.0);§Zero-Sized Tensor Handling
use train_station::Tensor;
let mut empty_tensor = Tensor::new(vec![0]);
empty_tensor.fill(42.0); // Should not panic
assert_eq!(empty_tensor.size(), 0);Source§impl Tensor
impl Tensor
Sourcepub fn from_slice(data: &[f32], shape_dims: Vec<usize>) -> Result<Self, String>
pub fn from_slice(data: &[f32], shape_dims: Vec<usize>) -> Result<Self, String>
Creates a tensor from a slice of data
Creates a new tensor with the specified shape and copies data from the provided slice. Validates that the data size matches the tensor shape before performing the copy operation.
This method provides an efficient way to create tensors from existing data sources while ensuring data integrity and proper memory management.
§Arguments
data- Slice of f32 values to copy into the tensorshape_dims- Vector of dimension sizes defining the tensor shape
§Returns
Ok(Tensor)- Successfully created tensor with copied dataErr(String)- Error if data size doesn’t match shape
§Performance
- Memory Copy: Efficient non-overlapping copy using SIMD when possible
- Validation: Fast size validation before allocation
- Alignment: Proper memory alignment for optimal performance
- Large Data: Optimized handling of large datasets
§Examples
§Basic Usage
use train_station::Tensor;
let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let tensor = Tensor::from_slice(&data, vec![2, 3]).unwrap();
assert_eq!(tensor.size(), 6);
assert_eq!(tensor.get(&[0, 0]), 1.0);
assert_eq!(tensor.get(&[1, 2]), 6.0);§Multi-Dimensional Data
use train_station::Tensor;
// 1D tensor
let data_1d = [1.0, 2.0, 3.0];
let tensor_1d = Tensor::from_slice(&data_1d, vec![3]).unwrap();
assert_eq!(tensor_1d.shape().dims, vec![3]);
assert_eq!(tensor_1d.get(&[1]), 2.0);
// 3D tensor
let data_3d = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let tensor_3d = Tensor::from_slice(&data_3d, vec![2, 2, 2]).unwrap();
assert_eq!(tensor_3d.shape().dims, vec![2, 2, 2]);
assert_eq!(tensor_3d.get(&[0, 0, 0]), 1.0);
assert_eq!(tensor_3d.get(&[1, 1, 1]), 8.0);§Error Handling
use train_station::Tensor;
// Size mismatch error
let data = [1.0, 2.0, 3.0];
let result = Tensor::from_slice(&data, vec![2, 2]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.contains("Data size 3 doesn't match shape size 4"));§Zero-Sized Tensors
use train_station::Tensor;
// Handle empty tensors gracefully
let data: [f32; 0] = [];
let tensor = Tensor::from_slice(&data, vec![0]).unwrap();
assert_eq!(tensor.size(), 0);
assert_eq!(tensor.shape().dims, vec![0]);§Large Data Sets
use train_station::Tensor;
// Efficient handling of large datasets
let size = 1000;
let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
let tensor = Tensor::from_slice(&data, vec![size]).unwrap();
assert_eq!(tensor.size(), size);
assert_eq!(tensor.get(&[0]), 0.0);
assert_eq!(tensor.get(&[100]), 100.0);
assert_eq!(tensor.get(&[999]), 999.0);§Implementation Details
This method performs the following steps:
- Shape Validation: Creates a Shape object and validates dimensions
- Size Check: Ensures data length matches the calculated tensor size
- Memory Allocation: Allocates tensor memory with proper alignment
- Data Copy: Uses efficient non-overlapping memory copy operation
- Return: Returns the created tensor or descriptive error message
The memory copy operation uses std::ptr::copy_nonoverlapping for
maximum performance and safety, ensuring no data corruption occurs
during the copy process.
Examples found in repository?
46fn demonstrate_basic_operators() {
47 println!("--- Basic Tensor-Tensor Operators ---");
48
49 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
50 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
51
52 println!("Tensor A: {:?}", a.data());
53 println!("Tensor B: {:?}", b.data());
54
55 // Addition
56 let c = &a + &b;
57 println!("A + B: {:?}", c.data());
58
59 // Subtraction
60 let d = &a - &b;
61 println!("A - B: {:?}", d.data());
62
63 // Multiplication
64 let e = &a * &b;
65 println!("A * B: {:?}", e.data());
66
67 // Division
68 let f = &a / &b;
69 println!("A / B: {:?}", f.data());
70}
71
72/// Demonstrate tensor-scalar operators
73fn demonstrate_scalar_operators() {
74 println!("\n--- Tensor-Scalar Operators ---");
75
76 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
77 println!("Original tensor: {:?}", tensor.data());
78
79 // Tensor + scalar
80 let result1 = &tensor + 5.0;
81 println!("Tensor + 5.0: {:?}", result1.data());
82
83 // Scalar + tensor
84 let result2 = 5.0 + &tensor;
85 println!("5.0 + Tensor: {:?}", result2.data());
86
87 // Tensor - scalar
88 let result3 = &tensor - 2.0;
89 println!("Tensor - 2.0: {:?}", result3.data());
90
91 // Tensor * scalar
92 let result4 = &tensor * 3.0;
93 println!("Tensor * 3.0: {:?}", result4.data());
94
95 // Scalar * tensor
96 let result5 = 3.0 * &tensor;
97 println!("3.0 * Tensor: {:?}", result5.data());
98
99 // Tensor / scalar
100 let result6 = &tensor / 2.0;
101 println!("Tensor / 2.0: {:?}", result6.data());
102}
103
104/// Demonstrate assignment operators
105fn demonstrate_operator_assignment() {
106 println!("\n--- Assignment Operators ---");
107
108 let mut tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
109 println!("Original tensor: {:?}", tensor.data());
110
111 // In-place addition
112 tensor += 5.0;
113 println!("After += 5.0: {:?}", tensor.data());
114
115 // In-place subtraction
116 tensor -= 2.0;
117 println!("After -= 2.0: {:?}", tensor.data());
118
119 // In-place multiplication
120 tensor *= 3.0;
121 println!("After *= 3.0: {:?}", tensor.data());
122
123 // In-place division
124 tensor /= 2.0;
125 println!("After /= 2.0: {:?}", tensor.data());
126}
127
128/// Demonstrate operator chaining and complex expressions
129fn demonstrate_operator_chaining() {
130 println!("\n--- Operator Chaining ---");
131
132 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
133 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
134 let c = Tensor::from_slice(&[9.0, 10.0, 11.0, 12.0], vec![2, 2]).unwrap();
135
136 println!("Tensor A: {:?}", a.data());
137 println!("Tensor B: {:?}", b.data());
138 println!("Tensor C: {:?}", c.data());
139
140 // Complex expression: (A + B) * C - 5
141 let result = (&a + &b) * &c - 5.0;
142 println!("(A + B) * C - 5: {:?}", result.data());
143
144 // Another complex expression: A * 2 + B / 2
145 let result2 = &a * 2.0 + &b / 2.0;
146 println!("A * 2 + B / 2: {:?}", result2.data());
147
148 // Negation and addition: -A + B * C
149 let result3 = -&a + &b * &c;
150 println!("-A + B * C: {:?}", result3.data());
151
152 // Division with parentheses: (A + B) / (C - 1)
153 let result4 = (&a + &b) / (&c - 1.0);
154 println!("(A + B) / (C - 1): {:?}", result4.data());
155}
156
157/// Demonstrate broadcasting behavior
158fn demonstrate_broadcasting() {
159 println!("\n--- Broadcasting ---");
160
161 // 2D tensor
162 let tensor_2d = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
163 println!(
164 "2D tensor: shape {:?}, data: {:?}",
165 tensor_2d.shape().dims,
166 tensor_2d.data()
167 );
168
169 // 1D tensor (will be broadcasted)
170 let tensor_1d = Tensor::from_slice(&[10.0, 20.0], vec![2]).unwrap();
171 println!(
172 "1D tensor: shape {:?}, data: {:?}",
173 tensor_1d.shape().dims,
174 tensor_1d.data()
175 );
176
177 // Broadcasting addition
178 let broadcast_sum = &tensor_2d + &tensor_1d;
179 println!(
180 "Broadcast sum: shape {:?}, data: {:?}",
181 broadcast_sum.shape().dims,
182 broadcast_sum.data()
183 );
184
185 // Broadcasting multiplication
186 let broadcast_mul = &tensor_2d * &tensor_1d;
187 println!(
188 "Broadcast multiplication: shape {:?}, data: {:?}",
189 broadcast_mul.shape().dims,
190 broadcast_mul.data()
191 );
192
193 // Broadcasting with scalar
194 let broadcast_scalar = &tensor_2d + 100.0;
195 println!(
196 "Broadcast scalar: shape {:?}, data: {:?}",
197 broadcast_scalar.shape().dims,
198 broadcast_scalar.data()
199 );
200}
201
202/// Demonstrate equivalence between operators and method calls
203fn demonstrate_method_equivalence() {
204 println!("\n--- Operator vs Method Call Equivalence ---");
205
206 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
207 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
208
209 // Addition: operator vs method
210 let operator_result = &a + &b;
211 let method_result = a.add_tensor(&b);
212
213 println!("A + B (operator): {:?}", operator_result.data());
214 println!("A.add_tensor(B): {:?}", method_result.data());
215 println!(
216 "Results are equal: {}",
217 operator_result.data() == method_result.data()
218 );
219
220 // Multiplication: operator vs method
221 let operator_result = &a * &b;
222 let method_result = a.mul_tensor(&b);
223
224 println!("A * B (operator): {:?}", operator_result.data());
225 println!("A.mul_tensor(B): {:?}", method_result.data());
226 println!(
227 "Results are equal: {}",
228 operator_result.data() == method_result.data()
229 );
230
231 // Scalar addition: operator vs method
232 let operator_result = &a + 5.0;
233 let method_result = a.add_scalar(5.0);
234
235 println!("A + 5.0 (operator): {:?}", operator_result.data());
236 println!("A.add_scalar(5.0): {:?}", method_result.data());
237 println!(
238 "Results are equal: {}",
239 operator_result.data() == method_result.data()
240 );
241}More examples
163fn demonstrate_forward_pass() {
164 println!("\n--- Forward Pass (with gradients) ---");
165
166 let layer = LinearLayer::new(3, 2, Some(43));
167
168 // Single input
169 let input = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![1, 3]).unwrap();
170 let output = layer.forward(&input);
171
172 println!("Single input:");
173 println!(" Input: {:?}", input.data());
174 println!(" Output: {:?}", output.data());
175 println!(" Output requires grad: {}", output.requires_grad());
176
177 // Batch input
178 let batch_input = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
179 let batch_output = layer.forward(&batch_input);
180
181 println!("Batch input:");
182 println!(" Input shape: {:?}", batch_input.shape().dims);
183 println!(" Output shape: {:?}", batch_output.shape().dims);
184 println!(" Output requires grad: {}", batch_output.requires_grad());
185}
186
187/// Demonstrate forward pass without gradient tracking
188fn demonstrate_forward_pass_no_grad() {
189 println!("\n--- Forward Pass (no gradients) ---");
190
191 let layer = LinearLayer::new(3, 2, Some(44));
192
193 // Single input
194 let input = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![1, 3]).unwrap();
195 let output = layer.forward_no_grad(&input);
196
197 println!("Single input (no grad):");
198 println!(" Input: {:?}", input.data());
199 println!(" Output: {:?}", output.data());
200 println!(" Output requires grad: {}", output.requires_grad());
201
202 // Compare with grad version
203 let output_with_grad = layer.forward(&input);
204 println!("Comparison:");
205 println!(
206 " Same values: {}",
207 output.data() == output_with_grad.data()
208 );
209 println!(" No grad requires grad: {}", output.requires_grad());
210 println!(
211 " With grad requires grad: {}",
212 output_with_grad.requires_grad()
213 );
214}
215
216/// Demonstrate complete training loop
217fn demonstrate_training_loop() -> Result<(), Box<dyn std::error::Error>> {
218 println!("\n--- Training Loop ---");
219
220 // Create layer and training data
221 let mut layer = LinearLayer::new(2, 1, Some(45));
222
223 // Simple regression task: y = 2*x1 + 3*x2 + 1
224 let x_data = Tensor::from_slice(
225 &[
226 1.0, 1.0, // x1=1, x2=1 -> y=6
227 2.0, 1.0, // x1=2, x2=1 -> y=8
228 1.0, 2.0, // x1=1, x2=2 -> y=9
229 2.0, 2.0, // x1=2, x2=2 -> y=11
230 ],
231 vec![4, 2],
232 )
233 .unwrap();
234
235 let y_true = Tensor::from_slice(&[6.0, 8.0, 9.0, 11.0], vec![4, 1]).unwrap();
236
237 println!("Training data:");
238 println!(" X shape: {:?}", x_data.shape().dims);
239 println!(" Y shape: {:?}", y_true.shape().dims);
240 println!(" Target function: y = 2*x1 + 3*x2 + 1");
241
242 // Create optimizer
243 let config = AdamConfig {
244 learning_rate: 0.01,
245 beta1: 0.9,
246 beta2: 0.999,
247 eps: 1e-8,
248 weight_decay: 0.0,
249 amsgrad: false,
250 };
251
252 let mut optimizer = Adam::with_config(config);
253 let params = layer.parameters();
254 for param in ¶ms {
255 optimizer.add_parameter(param);
256 }
257
258 println!("Optimizer setup complete. Starting training...");
259
260 // Training loop
261 let num_epochs = 100;
262 let mut losses = Vec::new();
263
264 for epoch in 0..num_epochs {
265 // Forward pass
266 let y_pred = layer.forward(&x_data);
267
268 // Compute loss: MSE
269 let diff = y_pred.sub_tensor(&y_true);
270 let mut loss = diff.pow_scalar(2.0).mean();
271
272 // Backward pass
273 loss.backward(None);
274
275 // Optimizer step
276 let mut params = layer.parameters();
277 optimizer.step(&mut params);
278 optimizer.zero_grad(&mut params);
279
280 losses.push(loss.value());
281
282 // Print progress
283 if epoch % 20 == 0 || epoch == num_epochs - 1 {
284 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
285 }
286 }
287
288 // Evaluate final model
289 let final_predictions = layer.forward_no_grad(&x_data);
290
291 println!("\nFinal model evaluation:");
292 println!(" Learned weights: {:?}", layer.weight.data());
293 println!(" Learned bias: {:?}", layer.bias.data());
294 println!(" Target weights: [2.0, 3.0]");
295 println!(" Target bias: [1.0]");
296
297 println!(" Predictions vs True:");
298 for i in 0..4 {
299 let pred = final_predictions.data()[i];
300 let true_val = y_true.data()[i];
301 println!(
302 " Sample {}: pred={:.3}, true={:.1}, error={:.3}",
303 i + 1,
304 pred,
305 true_val,
306 (pred - true_val).abs()
307 );
308 }
309
310 // Training analysis
311 let initial_loss = losses[0];
312 let final_loss = losses[losses.len() - 1];
313 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
314
315 println!("\nTraining Analysis:");
316 println!(" Initial loss: {:.6}", initial_loss);
317 println!(" Final loss: {:.6}", final_loss);
318 println!(" Loss reduction: {:.1}%", loss_reduction);
319
320 Ok(())
321}
322
323/// Demonstrate single vs batch inference
324fn demonstrate_single_vs_batch_inference() {
325 println!("\n--- Single vs Batch Inference ---");
326
327 let layer = LinearLayer::new(4, 3, Some(46));
328
329 // Single inference
330 println!("Single inference:");
331 let single_input = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![1, 4]).unwrap();
332 let single_output = layer.forward_no_grad(&single_input);
333 println!(" Input shape: {:?}", single_input.shape().dims);
334 println!(" Output shape: {:?}", single_output.shape().dims);
335 println!(" Output: {:?}", single_output.data());
336
337 // Batch inference
338 println!("Batch inference:");
339 let batch_input = Tensor::from_slice(
340 &[
341 1.0, 2.0, 3.0, 4.0, // Sample 1
342 5.0, 6.0, 7.0, 8.0, // Sample 2
343 9.0, 10.0, 11.0, 12.0, // Sample 3
344 ],
345 vec![3, 4],
346 )
347 .unwrap();
348 let batch_output = layer.forward_no_grad(&batch_input);
349 println!(" Input shape: {:?}", batch_input.shape().dims);
350 println!(" Output shape: {:?}", batch_output.shape().dims);
351
352 // Verify batch consistency - first sample should match single inference
353 let _first_batch_sample = batch_output.view(vec![3, 3]); // Reshape to access first sample
354 let first_sample_data = &batch_output.data()[0..3]; // First 3 elements
355 let single_sample_data = single_output.data();
356
357 println!("Consistency check:");
358 println!(" Single output: {:?}", single_sample_data);
359 println!(" First batch sample: {:?}", first_sample_data);
360 println!(
361 " Match: {}",
362 single_sample_data
363 .iter()
364 .zip(first_sample_data.iter())
365 .all(|(a, b)| (a - b).abs() < 1e-6)
366 );
367}
368
369/// Demonstrate serialization and loading
370fn demonstrate_serialization() -> Result<(), Box<dyn std::error::Error>> {
371 println!("\n--- Serialization ---");
372
373 // Create and train a simple layer
374 let mut original_layer = LinearLayer::new(2, 1, Some(47));
375
376 // Simple training data
377 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
378 let y_true = Tensor::from_slice(&[5.0, 11.0], vec![2, 1]).unwrap();
379
380 let mut optimizer = Adam::with_learning_rate(0.01);
381 let params = original_layer.parameters();
382 for param in ¶ms {
383 optimizer.add_parameter(param);
384 }
385
386 // Train for a few epochs
387 for _ in 0..10 {
388 let y_pred = original_layer.forward(&x_data);
389 let mut loss = (y_pred.sub_tensor(&y_true)).pow_scalar(2.0).mean();
390 loss.backward(None);
391
392 let mut params = original_layer.parameters();
393 optimizer.step(&mut params);
394 optimizer.zero_grad(&mut params);
395 }
396
397 println!("Original layer trained");
398 println!(" Weight: {:?}", original_layer.weight.data());
399 println!(" Bias: {:?}", original_layer.bias.data());
400
401 // Save layer
402 original_layer.save_json("temp_linear_layer")?;
403
404 // Load layer
405 let loaded_layer = LinearLayer::load_json("temp_linear_layer", 2, 1)?;
406
407 println!("Loaded layer");
408 println!(" Weight: {:?}", loaded_layer.weight.data());
409 println!(" Bias: {:?}", loaded_layer.bias.data());
410
411 // Verify consistency
412 let test_input = Tensor::from_slice(&[1.0, 1.0], vec![1, 2]).unwrap();
413 let original_output = original_layer.forward_no_grad(&test_input);
414 let loaded_output = loaded_layer.forward_no_grad(&test_input);
415
416 println!("Consistency check:");
417 println!(" Original output: {:?}", original_output.data());
418 println!(" Loaded output: {:?}", loaded_output.data());
419 println!(
420 " Match: {}",
421 original_output
422 .data()
423 .iter()
424 .zip(loaded_output.data().iter())
425 .all(|(a, b)| (a - b).abs() < 1e-6)
426 );
427
428 println!("Serialization verification: PASSED");
429
430 Ok(())
431}77fn demonstrate_basic_iteration() -> Result<(), Box<dyn std::error::Error>> {
78 println!("\n--- Basic Element Iteration ---");
79
80 // Create a simple tensor for demonstration
81 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
82 println!("Original tensor: {:?}", tensor.data());
83
84 // Basic iteration with for loop
85 println!("\nBasic iteration with for loop:");
86 for (i, element) in tensor.iter().enumerate() {
87 println!(
88 " Element {}: value = {:.1}, shape = {:?}",
89 i,
90 element.value(),
91 element.shape().dims
92 );
93 }
94
95 // Element-wise transformation
96 println!("\nElement-wise transformation (2x + 1):");
97 let transformed: Tensor = tensor
98 .iter()
99 .map(|elem| elem.mul_scalar(2.0).add_scalar(1.0))
100 .collect();
101 println!(" Result: {:?}", transformed.data());
102
103 // Filtering elements
104 println!("\nFiltering elements (values > 3.0):");
105 let filtered: Tensor = tensor.iter().filter(|elem| elem.value() > 3.0).collect();
106 println!(" Filtered: {:?}", filtered.data());
107
108 Ok(())
109}
110
111/// Demonstrate standard iterator trait methods
112///
113/// Shows compatibility with Rust's standard library iterator methods
114/// and demonstrates various functional programming patterns.
115fn demonstrate_standard_methods() -> Result<(), Box<dyn std::error::Error>> {
116 println!("\n--- Standard Iterator Methods ---");
117
118 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
119
120 // Using map for transformations
121 println!("\nMap transformation (square each element):");
122 let squared: Tensor = tensor.iter().map(|elem| elem.pow_scalar(2.0)).collect();
123 println!(" Squared: {:?}", squared.data());
124
125 // Using enumerate for indexed operations
126 println!("\nEnumerate with indexed operations:");
127 let indexed: Tensor = tensor
128 .iter()
129 .enumerate()
130 .map(|(i, elem)| elem.add_scalar(i as f32))
131 .collect();
132 println!(" Indexed: {:?}", indexed.data());
133
134 // Using fold for reduction
135 println!("\nFold for sum calculation:");
136 let sum: f32 = tensor.iter().fold(0.0, |acc, elem| acc + elem.value());
137 println!(" Sum: {:.1}", sum);
138
139 // Using find for element search
140 println!("\nFind specific element:");
141 if let Some(found) = tensor.iter().find(|elem| elem.value() == 3.0) {
142 println!(" Found element with value 3.0: {:.1}", found.value());
143 }
144
145 // Using any/all for condition checking
146 println!("\nCondition checking:");
147 let all_positive = tensor.iter().all(|elem| elem.value() > 0.0);
148 let any_large = tensor.iter().any(|elem| elem.value() > 4.0);
149 println!(" All positive: {}", all_positive);
150 println!(" Any > 4.0: {}", any_large);
151
152 Ok(())
153}
154
155/// Demonstrate gradient tracking through element operations
156///
157/// Shows how gradient tracking works seamlessly through iterator
158/// operations, maintaining the computational graph for backpropagation.
159fn demonstrate_gradient_tracking() -> Result<(), Box<dyn std::error::Error>> {
160 println!("\n--- Gradient Tracking ---");
161
162 // Create a tensor with gradient tracking enabled
163 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?.with_requires_grad();
164 println!("Input tensor (requires_grad): {:?}", tensor.data());
165
166 // Perform element-wise operations through iteration
167 let result: Tensor = tensor
168 .iter()
169 .map(|elem| {
170 // Apply a complex transformation: (x^2 + 1) * 2
171 elem.pow_scalar(2.0).add_scalar(1.0).mul_scalar(2.0)
172 })
173 .collect();
174
175 println!("Result tensor: {:?}", result.data());
176 println!("Result requires_grad: {}", result.requires_grad());
177
178 // Compute gradients
179 let mut loss = result.sum();
180 loss.backward(None);
181
182 println!("Loss: {:.6}", loss.value());
183 println!("Input gradients: {:?}", tensor.grad().map(|g| g.data()));
184
185 Ok(())
186}
187
188/// Demonstrate advanced iterator patterns
189///
190/// Shows complex iterator chains and advanced functional programming
191/// patterns for sophisticated data processing workflows.
192fn demonstrate_advanced_patterns() -> Result<(), Box<dyn std::error::Error>> {
193 println!("\n--- Advanced Iterator Patterns ---");
194
195 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![6])?;
196 println!("Input tensor: {:?}", tensor.data());
197
198 // Complex chain: enumerate -> filter -> map -> collect
199 println!("\nComplex chain (even indices only, add index to value):");
200 let result: Tensor = tensor
201 .iter()
202 .enumerate()
203 .filter(|(i, _)| i % 2 == 0) // Take even indices
204 .map(|(i, elem)| elem.add_scalar(i as f32)) // Add index to value
205 .collect();
206 println!(" Result: {:?}", result.data());
207
208 // Using take and skip for windowing
209 println!("\nWindowing with take and skip:");
210 let window1: Tensor = tensor.iter().take(3).collect();
211 let window2: Tensor = tensor.iter().skip(2).take(3).collect();
212 println!(" Window 1 (first 3): {:?}", window1.data());
213 println!(" Window 2 (middle 3): {:?}", window2.data());
214
215 // Using rev() for reverse iteration
216 println!("\nReverse iteration:");
217 let reversed: Tensor = tensor.iter().rev().collect();
218 println!(" Reversed: {:?}", reversed.data());
219
220 // Chaining with mathematical operations
221 println!("\nMathematical operation chain:");
222 let math_result: Tensor = tensor
223 .iter()
224 .map(|elem| elem.exp()) // e^x
225 .filter(|elem| elem.value() < 50.0) // Filter large values
226 .map(|elem| elem.log()) // ln(x)
227 .collect();
228 println!(" Math chain result: {:?}", math_result.data());
229
230 // Using zip for element-wise combinations
231 println!("\nElement-wise combination with zip:");
232 let tensor2 = Tensor::from_slice(&[10.0, 20.0, 30.0, 40.0, 50.0, 60.0], vec![6])?;
233 let combined: Tensor = tensor
234 .iter()
235 .zip(tensor2.iter())
236 .map(|(a, b)| a.mul_tensor(&b)) // Element-wise multiplication
237 .collect();
238 println!(" Combined: {:?}", combined.data());
239
240 Ok(())
241}42fn demonstrate_tensor_creation() {
43 println!("--- Tensor Creation ---");
44
45 // Create tensors with different initializations
46 let zeros = Tensor::zeros(vec![2, 3]);
47 println!(
48 "Zeros tensor: shape {:?}, data: {:?}",
49 zeros.shape().dims,
50 zeros.data()
51 );
52
53 let ones = Tensor::ones(vec![3, 2]);
54 println!(
55 "Ones tensor: shape {:?}, data: {:?}",
56 ones.shape().dims,
57 ones.data()
58 );
59
60 // Create tensor from slice
61 let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
62 let from_slice = Tensor::from_slice(&data, vec![2, 3]).unwrap();
63 println!(
64 "From slice: shape {:?}, data: {:?}",
65 from_slice.shape().dims,
66 from_slice.data()
67 );
68
69 // Create tensor with specific value
70 let mut filled = Tensor::new(vec![2, 2]);
71 {
72 let data = filled.data_mut();
73 for value in data.iter_mut() {
74 *value = 42.0;
75 }
76 }
77 println!("Filled with 42: {:?}", filled.data());
78
79 // Create tensor with random data
80 let random = Tensor::randn(vec![2, 2], Some(42));
81 println!(
82 "Random tensor: shape {:?}, data: {:?}",
83 random.shape().dims,
84 random.data()
85 );
86}
87
88/// Demonstrate basic arithmetic operations
89fn demonstrate_basic_operations() {
90 println!("\n--- Basic Operations ---");
91
92 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
93 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
94
95 // Addition
96 let sum = a.add_tensor(&b);
97 println!("A + B: {:?}", sum.data());
98
99 // Subtraction
100 let diff = a.sub_tensor(&b);
101 println!("A - B: {:?}", diff.data());
102
103 // Multiplication
104 let product = a.mul_tensor(&b);
105 println!("A * B: {:?}", product.data());
106
107 // Division
108 let quotient = a.div_tensor(&b);
109 println!("A / B: {:?}", quotient.data());
110
111 // Scalar operations
112 let scalar_add = a.add_scalar(5.0);
113 println!("A + 5.0: {:?}", scalar_add.data());
114
115 let scalar_mul = a.mul_scalar(2.0);
116 println!("A * 2.0: {:?}", scalar_mul.data());
117}
118
119/// Demonstrate shape manipulation operations
120fn demonstrate_shape_operations() {
121 println!("\n--- Shape Operations ---");
122
123 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
124 println!(
125 "Original: shape {:?}, data: {:?}",
126 tensor.shape().dims,
127 tensor.data()
128 );
129
130 // Reshape (view)
131 let reshaped = tensor.view(vec![3, 2]);
132 println!(
133 "Reshaped to [3, 2]: shape {:?}, data: {:?}",
134 reshaped.shape().dims,
135 reshaped.data()
136 );
137
138 // Create a different shaped tensor for demonstration
139 let tensor_2d = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
140 println!(
141 "2D tensor: shape {:?}, data: {:?}",
142 tensor_2d.shape().dims,
143 tensor_2d.data()
144 );
145
146 // Create a 1D tensor
147 let tensor_1d = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4]).unwrap();
148 println!(
149 "1D tensor: shape {:?}, data: {:?}",
150 tensor_1d.shape().dims,
151 tensor_1d.data()
152 );
153}
154
155/// Demonstrate data access patterns
156fn demonstrate_data_access() {
157 println!("\n--- Data Access ---");
158
159 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
160
161 // Access individual elements
162 println!("Element [0, 0]: {}", tensor.get(&[0, 0]));
163 println!("Element [0, 1]: {}", tensor.get(&[0, 1]));
164 println!("Element [1, 0]: {}", tensor.get(&[1, 0]));
165 println!("Element [1, 1]: {}", tensor.get(&[1, 1]));
166
167 // Access data as slice
168 let data = tensor.data();
169 println!("Data as slice: {:?}", data);
170
171 // Iterate over elements
172 println!("Elements:");
173 for (i, &value) in data.iter().enumerate() {
174 println!(" [{}]: {}", i, value);
175 }
176}
177
178/// Demonstrate utility functions
179fn demonstrate_utility_functions() {
180 println!("\n--- Utility Functions ---");
181
182 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
183
184 // Basic properties
185 println!("Shape: {:?}", tensor.shape().dims);
186 println!("Size: {}", tensor.size());
187 println!("Is contiguous: {}", tensor.is_contiguous());
188 println!("Device: {:?}", tensor.device());
189
190 // Mathematical operations
191 let sum = tensor.sum();
192 println!("Sum: {}", sum.value());
193
194 let mean = tensor.mean();
195 println!("Mean: {}", mean.value());
196
197 let norm = tensor.norm();
198 println!("Norm: {}", norm.value());
199
200 // Device placement
201 let cpu_tensor = Tensor::zeros_on_device(vec![3, 3], train_station::Device::cpu());
202 println!(
203 "CPU tensor: shape {:?}, device: {:?}",
204 cpu_tensor.shape().dims,
205 cpu_tensor.device()
206 );
207}339fn demonstrate_forward_pass() {
340 println!("\n--- Forward Pass ---");
341
342 let config = FeedForwardConfig {
343 input_size: 3,
344 hidden_sizes: vec![5, 3],
345 output_size: 2,
346 use_bias: true,
347 };
348 let network = FeedForwardNetwork::new(config, Some(43));
349
350 // Single input
351 let input = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![1, 3]).unwrap();
352 let output = network.forward(&input);
353
354 println!("Single input forward pass:");
355 println!(" Input shape: {:?}", input.shape().dims);
356 println!(" Output shape: {:?}", output.shape().dims);
357 println!(" Output: {:?}", output.data());
358 println!(" Output requires grad: {}", output.requires_grad());
359
360 // Batch input
361 let batch_input = Tensor::from_slice(
362 &[
363 1.0, 2.0, 3.0, // Sample 1
364 4.0, 5.0, 6.0, // Sample 2
365 7.0, 8.0, 9.0, // Sample 3
366 ],
367 vec![3, 3],
368 )
369 .unwrap();
370 let batch_output = network.forward(&batch_input);
371
372 println!("Batch input forward pass:");
373 println!(" Input shape: {:?}", batch_input.shape().dims);
374 println!(" Output shape: {:?}", batch_output.shape().dims);
375 println!(" Output requires grad: {}", batch_output.requires_grad());
376
377 // Compare with no-grad version
378 let output_no_grad = network.forward_no_grad(&input);
379 println!("No-grad comparison:");
380 println!(" Same values: {}", output.data() == output_no_grad.data());
381 println!(" With grad requires grad: {}", output.requires_grad());
382 println!(
383 " No grad requires grad: {}",
384 output_no_grad.requires_grad()
385 );
386}
387
388/// Demonstrate different configurable architectures
389fn demonstrate_configurable_architectures() {
390 println!("\n--- Configurable Architectures ---");
391
392 let architectures = vec![
393 ("Shallow", vec![8]),
394 ("Medium", vec![16, 8]),
395 ("Deep", vec![32, 16, 8, 4]),
396 ("Wide", vec![64, 32]),
397 ("Bottleneck", vec![16, 4, 16]),
398 ];
399
400 for (name, hidden_sizes) in architectures {
401 let config = FeedForwardConfig {
402 input_size: 10,
403 hidden_sizes,
404 output_size: 3,
405 use_bias: true,
406 };
407
408 let network = FeedForwardNetwork::new(config.clone(), Some(44));
409
410 // Test forward pass
411 let test_input = Tensor::randn(vec![5, 10], Some(45)); // Batch of 5
412 let output = network.forward_no_grad(&test_input);
413
414 println!("{} network:", name);
415 println!(" Architecture: 10 -> {:?} -> 3", config.hidden_sizes);
416 println!(" Parameters: {}", network.parameter_count());
417 println!(" Test output shape: {:?}", output.shape().dims);
418 println!(
419 " Output range: [{:.3}, {:.3}]",
420 output.data().iter().fold(f32::INFINITY, |a, &b| a.min(b)),
421 output
422 .data()
423 .iter()
424 .fold(f32::NEG_INFINITY, |a, &b| a.max(b))
425 );
426 }
427}
428
429/// Demonstrate basic training workflow
430fn demonstrate_training_workflow() -> Result<(), Box<dyn std::error::Error>> {
431 println!("\n--- Training Workflow ---");
432
433 // Create a simple classification network
434 let config = FeedForwardConfig {
435 input_size: 2,
436 hidden_sizes: vec![4, 3],
437 output_size: 1,
438 use_bias: true,
439 };
440 let mut network = FeedForwardNetwork::new(config, Some(46));
441
442 println!("Training network: 2 -> [4, 3] -> 1");
443
444 // Create simple binary classification data: XOR problem
445 let x_data = Tensor::from_slice(
446 &[
447 0.0, 0.0, // -> 0
448 0.0, 1.0, // -> 1
449 1.0, 0.0, // -> 1
450 1.0, 1.0, // -> 0
451 ],
452 vec![4, 2],
453 )
454 .unwrap();
455
456 let y_true = Tensor::from_slice(&[0.0, 1.0, 1.0, 0.0], vec![4, 1]).unwrap();
457
458 println!("Training on XOR problem:");
459 println!(" Input shape: {:?}", x_data.shape().dims);
460 println!(" Target shape: {:?}", y_true.shape().dims);
461
462 // Create optimizer
463 let mut optimizer = Adam::with_learning_rate(0.1);
464 let params = network.parameters();
465 for param in ¶ms {
466 optimizer.add_parameter(param);
467 }
468
469 // Training loop
470 let num_epochs = 50;
471 let mut losses = Vec::new();
472
473 for epoch in 0..num_epochs {
474 // Forward pass
475 let y_pred = network.forward(&x_data);
476
477 // Compute loss: MSE
478 let diff = y_pred.sub_tensor(&y_true);
479 let mut loss = diff.pow_scalar(2.0).mean();
480
481 // Backward pass
482 loss.backward(None);
483
484 // Optimizer step and zero grad
485 let mut params = network.parameters();
486 optimizer.step(&mut params);
487 optimizer.zero_grad(&mut params);
488
489 losses.push(loss.value());
490
491 // Print progress
492 if epoch % 10 == 0 || epoch == num_epochs - 1 {
493 println!("Epoch {:2}: Loss = {:.6}", epoch, loss.value());
494 }
495 }
496
497 // Test final model
498 let final_predictions = network.forward_no_grad(&x_data);
499 println!("\nFinal predictions vs targets:");
500 for i in 0..4 {
501 let pred = final_predictions.data()[i];
502 let target = y_true.data()[i];
503 let input_x = x_data.data()[i * 2];
504 let input_y = x_data.data()[i * 2 + 1];
505 println!(
506 " [{:.0}, {:.0}] -> pred: {:.3}, target: {:.0}, error: {:.3}",
507 input_x,
508 input_y,
509 pred,
510 target,
511 (pred - target).abs()
512 );
513 }
514
515 Ok(())
516}
517
518/// Demonstrate comprehensive training with 100+ steps
519fn demonstrate_comprehensive_training() -> Result<(), Box<dyn std::error::Error>> {
520 println!("\n--- Comprehensive Training (100+ Steps) ---");
521
522 // Create a regression network
523 let config = FeedForwardConfig {
524 input_size: 3,
525 hidden_sizes: vec![8, 6, 4],
526 output_size: 2,
527 use_bias: true,
528 };
529 let mut network = FeedForwardNetwork::new(config, Some(47));
530
531 println!("Network architecture: 3 -> [8, 6, 4] -> 2");
532 println!("Total parameters: {}", network.parameter_count());
533
534 // Create synthetic regression data
535 // Target function: [y1, y2] = [x1 + 2*x2 - x3, x1*x2 + x3]
536 let num_samples = 32;
537 let mut x_vec = Vec::new();
538 let mut y_vec = Vec::new();
539
540 for i in 0..num_samples {
541 let x1 = (i as f32 / num_samples as f32) * 2.0 - 1.0; // [-1, 1]
542 let x2 = ((i * 2) as f32 / num_samples as f32) * 2.0 - 1.0;
543 let x3 = ((i * 3) as f32 / num_samples as f32) * 2.0 - 1.0;
544
545 let y1 = x1 + 2.0 * x2 - x3;
546 let y2 = x1 * x2 + x3;
547
548 x_vec.extend_from_slice(&[x1, x2, x3]);
549 y_vec.extend_from_slice(&[y1, y2]);
550 }
551
552 let x_data = Tensor::from_slice(&x_vec, vec![num_samples, 3]).unwrap();
553 let y_true = Tensor::from_slice(&y_vec, vec![num_samples, 2]).unwrap();
554
555 println!("Training data:");
556 println!(" {} samples", num_samples);
557 println!(" Input shape: {:?}", x_data.shape().dims);
558 println!(" Target shape: {:?}", y_true.shape().dims);
559
560 // Create optimizer with learning rate scheduling
561 let mut optimizer = Adam::with_learning_rate(0.01);
562 let params = network.parameters();
563 for param in ¶ms {
564 optimizer.add_parameter(param);
565 }
566
567 // Comprehensive training loop (150 epochs)
568 let num_epochs = 150;
569 let mut losses = Vec::new();
570 let mut best_loss = f32::INFINITY;
571 let mut patience_counter = 0;
572 let patience = 20;
573
574 println!("Starting comprehensive training...");
575
576 for epoch in 0..num_epochs {
577 // Forward pass
578 let y_pred = network.forward(&x_data);
579
580 // Compute loss: MSE
581 let diff = y_pred.sub_tensor(&y_true);
582 let mut loss = diff.pow_scalar(2.0).mean();
583
584 // Backward pass
585 loss.backward(None);
586
587 // Optimizer step and zero grad
588 let mut params = network.parameters();
589 optimizer.step(&mut params);
590 optimizer.zero_grad(&mut params);
591
592 let current_loss = loss.value();
593 losses.push(current_loss);
594
595 // Learning rate scheduling
596 if epoch > 0 && epoch % 30 == 0 {
597 let new_lr = optimizer.learning_rate() * 0.8;
598 optimizer.set_learning_rate(new_lr);
599 println!(" Reduced learning rate to {:.4}", new_lr);
600 }
601
602 // Early stopping logic
603 if current_loss < best_loss {
604 best_loss = current_loss;
605 patience_counter = 0;
606 } else {
607 patience_counter += 1;
608 }
609
610 // Print progress
611 if epoch % 25 == 0 || epoch == num_epochs - 1 {
612 println!(
613 "Epoch {:3}: Loss = {:.6}, LR = {:.4}, Best = {:.6}",
614 epoch,
615 current_loss,
616 optimizer.learning_rate(),
617 best_loss
618 );
619 }
620
621 // Early stopping
622 if patience_counter >= patience && epoch > 50 {
623 println!("Early stopping at epoch {} (patience exceeded)", epoch);
624 break;
625 }
626 }
627
628 // Final evaluation
629 let final_predictions = network.forward_no_grad(&x_data);
630
631 // Compute final metrics
632 let final_loss = losses[losses.len() - 1];
633 let initial_loss = losses[0];
634 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
635
636 println!("\nTraining completed!");
637 println!(" Initial loss: {:.6}", initial_loss);
638 println!(" Final loss: {:.6}", final_loss);
639 println!(" Best loss: {:.6}", best_loss);
640 println!(" Loss reduction: {:.1}%", loss_reduction);
641 println!(" Final learning rate: {:.4}", optimizer.learning_rate());
642
643 // Sample predictions analysis
644 println!("\nSample predictions (first 5):");
645 for i in 0..5.min(num_samples) {
646 let pred1 = final_predictions.data()[i * 2];
647 let pred2 = final_predictions.data()[i * 2 + 1];
648 let true1 = y_true.data()[i * 2];
649 let true2 = y_true.data()[i * 2 + 1];
650
651 println!(
652 " Sample {}: pred=[{:.3}, {:.3}], true=[{:.3}, {:.3}], error=[{:.3}, {:.3}]",
653 i + 1,
654 pred1,
655 pred2,
656 true1,
657 true2,
658 (pred1 - true1).abs(),
659 (pred2 - true2).abs()
660 );
661 }
662
663 Ok(())
664}
665
666/// Demonstrate network serialization
667fn demonstrate_network_serialization() -> Result<(), Box<dyn std::error::Error>> {
668 println!("\n--- Network Serialization ---");
669
670 // Create and train a network
671 let config = FeedForwardConfig {
672 input_size: 2,
673 hidden_sizes: vec![4, 2],
674 output_size: 1,
675 use_bias: true,
676 };
677 let mut original_network = FeedForwardNetwork::new(config.clone(), Some(48));
678
679 // Quick training
680 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
681 let y_true = Tensor::from_slice(&[5.0, 11.0], vec![2, 1]).unwrap();
682
683 let mut optimizer = Adam::with_learning_rate(0.01);
684 let params = original_network.parameters();
685 for param in ¶ms {
686 optimizer.add_parameter(param);
687 }
688
689 for _ in 0..20 {
690 let y_pred = original_network.forward(&x_data);
691 let mut loss = (y_pred.sub_tensor(&y_true)).pow_scalar(2.0).mean();
692 loss.backward(None);
693
694 let mut params = original_network.parameters();
695 optimizer.step(&mut params);
696 optimizer.zero_grad(&mut params);
697 }
698
699 // Test original network
700 let test_input = Tensor::from_slice(&[1.0, 1.0], vec![1, 2]).unwrap();
701 let original_output = original_network.forward_no_grad(&test_input);
702
703 println!("Original network output: {:?}", original_output.data());
704
705 // Save network
706 original_network.save_json("temp_feedforward_network")?;
707
708 // Load network
709 let loaded_network = FeedForwardNetwork::load_json("temp_feedforward_network", config)?;
710 let loaded_output = loaded_network.forward_no_grad(&test_input);
711
712 println!("Loaded network output: {:?}", loaded_output.data());
713
714 // Verify consistency
715 let match_check = original_output
716 .data()
717 .iter()
718 .zip(loaded_output.data().iter())
719 .all(|(a, b)| (a - b).abs() < 1e-6);
720
721 println!(
722 "Serialization verification: {}",
723 if match_check { "PASSED" } else { "FAILED" }
724 );
725
726 Ok(())
727}52fn demonstrate_tensor_serialization() -> Result<(), Box<dyn std::error::Error>> {
53 println!("--- Tensor Serialization ---");
54
55 // Create a tensor with some data
56 let original_tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
57 println!(
58 "Original tensor: shape {:?}, data: {:?}",
59 original_tensor.shape().dims,
60 original_tensor.data()
61 );
62
63 // Save tensor in JSON format
64 let json_path = "temp_tensor.json";
65 original_tensor.save_json(json_path)?;
66 println!("Saved tensor to JSON: {}", json_path);
67
68 // Load tensor from JSON
69 let loaded_tensor_json = Tensor::load_json(json_path)?;
70 println!(
71 "Loaded from JSON: shape {:?}, data: {:?}",
72 loaded_tensor_json.shape().dims,
73 loaded_tensor_json.data()
74 );
75
76 // Verify data integrity
77 assert_eq!(
78 original_tensor.shape().dims,
79 loaded_tensor_json.shape().dims
80 );
81 assert_eq!(original_tensor.data(), loaded_tensor_json.data());
82 println!("JSON serialization verification: PASSED");
83
84 // Save tensor in binary format
85 let binary_path = "temp_tensor.bin";
86 original_tensor.save_binary(binary_path)?;
87 println!("Saved tensor to binary: {}", binary_path);
88
89 // Load tensor from binary
90 let loaded_tensor_binary = Tensor::load_binary(binary_path)?;
91 println!(
92 "Loaded from binary: shape {:?}, data: {:?}",
93 loaded_tensor_binary.shape().dims,
94 loaded_tensor_binary.data()
95 );
96
97 // Verify data integrity
98 assert_eq!(
99 original_tensor.shape().dims,
100 loaded_tensor_binary.shape().dims
101 );
102 assert_eq!(original_tensor.data(), loaded_tensor_binary.data());
103 println!("Binary serialization verification: PASSED");
104
105 Ok(())
106}Source§impl Tensor
impl Tensor
Sourcepub fn randn(shape_dims: Vec<usize>, seed: Option<u64>) -> Self
pub fn randn(shape_dims: Vec<usize>, seed: Option<u64>) -> Self
Creates a tensor with normally distributed random values (mean=0, std=1)
Similar to PyTorch’s torch.randn(), creates a tensor filled with random
values drawn from a standard normal distribution (mean=0, standard deviation=1).
Uses Box-Muller transform for efficient normal distribution generation.
This method provides high-quality random number generation with optional reproducibility through seed-based generation. The generated values follow a standard normal distribution suitable for machine learning applications.
§Arguments
shape_dims- Vector of dimension sizes defining the tensor shapeseed- Optional seed for reproducible random generation
§Returns
A new tensor with normally distributed random values
§Performance
- Box-Muller Transform: Efficient normal distribution generation
- SIMD Optimization: Vectorized operations for large tensors
- Memory Efficient: Single-pass generation with optimized allocation
- Thread Safe: Uses thread-local random state
§Examples
§Basic Usage
use train_station::Tensor;
// Create a 2x3 tensor with random normal values
let tensor = Tensor::randn(vec![2, 3], None);
assert_eq!(tensor.size(), 6);
assert_eq!(tensor.shape().dims, vec![2, 3]);
// Verify random values are generated
let first_value = tensor.get(&[0, 0]);
assert!(first_value != 0.0); // Should be random§Reproducible Generation
use train_station::Tensor;
// Create with fixed seed for reproducible results
let tensor1 = Tensor::randn(vec![100], Some(42));
let tensor2 = Tensor::randn(vec![100], Some(42));
// tensor1 and tensor2 will have identical values
for i in 0..tensor1.size() {
assert!((tensor1.get(&[i]) - tensor2.get(&[i])).abs() < 1e-6);
}§Statistical Properties
use train_station::Tensor;
// Generate large tensor for statistical analysis
let tensor = Tensor::randn(vec![1000], Some(42));
assert_eq!(tensor.size(), 1000);
// Check that values are reasonable (within 4 standard deviations)
let mut min_val = f32::INFINITY;
let mut max_val = f32::NEG_INFINITY;
let mut sum = 0.0;
for i in 0..tensor.size() {
let val = tensor.get(&[i]);
min_val = min_val.min(val);
max_val = max_val.max(val);
sum += val;
}
let mean = sum / tensor.size() as f32;
// Mean should be close to 0, values should be within reasonable bounds
assert!(mean.abs() < 0.1, "Mean should be close to 0, got {}", mean);
assert!(min_val > -4.0, "Values should not be too negative, min: {}", min_val);
assert!(max_val < 4.0, "Values should not be too positive, max: {}", max_val);§Zero-Sized Tensors
use train_station::Tensor;
// Handle empty tensors gracefully
let tensor = Tensor::randn(vec![0], Some(42));
assert_eq!(tensor.size(), 0);
assert_eq!(tensor.shape().dims, vec![0]);§Implementation Details
This method uses the Box-Muller transform to generate normally distributed random variables from uniform random variables. The process involves:
- Random Number Generation: Uses Xorshift algorithm for uniform random numbers
- Box-Muller Transform: Converts uniform random variables to normal distribution
- SIMD Optimization: Vectorized operations for large tensors when available
- Numerical Stability: Robust handling of edge cases and potential NaN values
The Box-Muller transform ensures that the generated values follow a true normal distribution with mean=0 and standard deviation=1, making it suitable for machine learning applications requiring normally distributed random values.
Examples found in repository?
68 pub fn new(input_size: usize, output_size: usize, seed: Option<u64>) -> Self {
69 let scale = (1.0 / input_size as f32).sqrt();
70
71 let weight = Tensor::randn(vec![input_size, output_size], seed)
72 .mul_scalar(scale)
73 .with_requires_grad();
74 let bias = Tensor::zeros(vec![output_size]).with_requires_grad();
75
76 Self {
77 weight,
78 bias,
79 input_size,
80 output_size,
81 }
82 }
83
84 pub fn forward(&self, input: &Tensor) -> Tensor {
85 let output = input.matmul(&self.weight);
86 output.add_tensor(&self.bias)
87 }
88
89 pub fn forward_no_grad(&self, input: &Tensor) -> Tensor {
90 let _guard = NoGradTrack::new();
91 self.forward(input)
92 }
93
94 pub fn parameters(&mut self) -> Vec<&mut Tensor> {
95 vec![&mut self.weight, &mut self.bias]
96 }
97}
98
99/// Configuration for feed-forward network
100#[derive(Debug, Clone)]
101pub struct FeedForwardConfig {
102 pub input_size: usize,
103 pub hidden_sizes: Vec<usize>,
104 pub output_size: usize,
105 pub use_bias: bool,
106}
107
108impl Default for FeedForwardConfig {
109 fn default() -> Self {
110 Self {
111 input_size: 4,
112 hidden_sizes: vec![8, 4],
113 output_size: 2,
114 use_bias: true,
115 }
116 }
117}
118
119/// A configurable feed-forward neural network
120pub struct FeedForwardNetwork {
121 layers: Vec<LinearLayer>,
122 config: FeedForwardConfig,
123}
124
125impl FeedForwardNetwork {
126 /// Create a new feed-forward network with the given configuration
127 pub fn new(config: FeedForwardConfig, seed: Option<u64>) -> Self {
128 let mut layers = Vec::new();
129 let mut current_size = config.input_size;
130 let mut current_seed = seed;
131
132 // Create hidden layers
133 for &hidden_size in &config.hidden_sizes {
134 layers.push(LinearLayer::new(current_size, hidden_size, current_seed));
135 current_size = hidden_size;
136 current_seed = current_seed.map(|s| s + 1);
137 }
138
139 // Create output layer
140 layers.push(LinearLayer::new(
141 current_size,
142 config.output_size,
143 current_seed,
144 ));
145
146 Self { layers, config }
147 }
148
149 /// Forward pass through the entire network
150 pub fn forward(&self, input: &Tensor) -> Tensor {
151 let mut x = input.clone();
152
153 // Pass through all layers except the last one with ReLU activation
154 for layer in &self.layers[..self.layers.len() - 1] {
155 x = layer.forward(&x);
156 x = ReLU::forward(&x);
157 }
158
159 // Final layer without activation (raw logits)
160 if let Some(final_layer) = self.layers.last() {
161 x = final_layer.forward(&x);
162 }
163
164 x
165 }
166
167 /// Forward pass without gradients (for inference)
168 pub fn forward_no_grad(&self, input: &Tensor) -> Tensor {
169 let _guard = NoGradTrack::new();
170 self.forward(input)
171 }
172
173 /// Get all parameters for optimization
174 pub fn parameters(&mut self) -> Vec<&mut Tensor> {
175 let mut params = Vec::new();
176 for layer in &mut self.layers {
177 params.extend(layer.parameters());
178 }
179 params
180 }
181
182 /// Get the number of layers
183 pub fn num_layers(&self) -> usize {
184 self.layers.len()
185 }
186
187 /// Get the total number of parameters
188 pub fn parameter_count(&self) -> usize {
189 let mut count = 0;
190 let mut current_size = self.config.input_size;
191
192 for &hidden_size in &self.config.hidden_sizes {
193 count += current_size * hidden_size + hidden_size; // weights + bias
194 current_size = hidden_size;
195 }
196
197 // Output layer
198 count += current_size * self.config.output_size + self.config.output_size;
199
200 count
201 }
202
203 /// Save network parameters to JSON
204 pub fn save_json(&self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
205 if let Some(parent) = std::path::Path::new(path).parent() {
206 fs::create_dir_all(parent)?;
207 }
208
209 for (i, layer) in self.layers.iter().enumerate() {
210 let layer_path = format!("{}_layer_{}", path, i);
211 let weight_path = format!("{}_weight.json", layer_path);
212 let bias_path = format!("{}_bias.json", layer_path);
213
214 layer.weight.save_json(&weight_path)?;
215 layer.bias.save_json(&bias_path)?;
216 }
217
218 println!(
219 "Saved feed-forward network to {} ({} layers)",
220 path,
221 self.layers.len()
222 );
223 Ok(())
224 }
225
226 /// Load network parameters from JSON
227 pub fn load_json(
228 path: &str,
229 config: FeedForwardConfig,
230 ) -> Result<Self, Box<dyn std::error::Error>> {
231 let mut layers = Vec::new();
232 let mut current_size = config.input_size;
233 let mut layer_idx = 0;
234
235 // Load hidden layers
236 for &hidden_size in &config.hidden_sizes {
237 let layer_path = format!("{}_layer_{}", path, layer_idx);
238 let weight_path = format!("{}_weight.json", layer_path);
239 let bias_path = format!("{}_bias.json", layer_path);
240
241 let weight = Tensor::load_json(&weight_path)?.with_requires_grad();
242 let bias = Tensor::load_json(&bias_path)?.with_requires_grad();
243
244 layers.push(LinearLayer {
245 weight,
246 bias,
247 input_size: current_size,
248 output_size: hidden_size,
249 });
250
251 current_size = hidden_size;
252 layer_idx += 1;
253 }
254
255 // Load output layer
256 let layer_path = format!("{}_layer_{}", path, layer_idx);
257 let weight_path = format!("{}_weight.json", layer_path);
258 let bias_path = format!("{}_bias.json", layer_path);
259
260 let weight = Tensor::load_json(&weight_path)?.with_requires_grad();
261 let bias = Tensor::load_json(&bias_path)?.with_requires_grad();
262
263 layers.push(LinearLayer {
264 weight,
265 bias,
266 input_size: current_size,
267 output_size: config.output_size,
268 });
269
270 Ok(Self { layers, config })
271 }
272}
273
274fn main() -> Result<(), Box<dyn std::error::Error>> {
275 println!("=== Feed-Forward Network Example ===\n");
276
277 demonstrate_network_creation();
278 demonstrate_forward_pass();
279 demonstrate_configurable_architectures();
280 demonstrate_training_workflow()?;
281 demonstrate_comprehensive_training()?;
282 demonstrate_network_serialization()?;
283 cleanup_temp_files()?;
284
285 println!("\n=== Example completed successfully! ===");
286 Ok(())
287}
288
289/// Demonstrate creating different network configurations
290fn demonstrate_network_creation() {
291 println!("--- Network Creation ---");
292
293 // Default configuration
294 let config = FeedForwardConfig::default();
295 let network = FeedForwardNetwork::new(config.clone(), Some(42));
296
297 println!("Default network configuration:");
298 println!(" Input size: {}", config.input_size);
299 println!(" Hidden sizes: {:?}", config.hidden_sizes);
300 println!(" Output size: {}", config.output_size);
301 println!(" Number of layers: {}", network.num_layers());
302 println!(" Total parameters: {}", network.parameter_count());
303
304 // Custom configurations
305 let configs = [
306 FeedForwardConfig {
307 input_size: 2,
308 hidden_sizes: vec![4],
309 output_size: 1,
310 use_bias: true,
311 },
312 FeedForwardConfig {
313 input_size: 8,
314 hidden_sizes: vec![16, 8, 4],
315 output_size: 3,
316 use_bias: true,
317 },
318 FeedForwardConfig {
319 input_size: 10,
320 hidden_sizes: vec![20, 15, 10, 5],
321 output_size: 2,
322 use_bias: true,
323 },
324 ];
325
326 for (i, config) in configs.iter().enumerate() {
327 let network = FeedForwardNetwork::new(config.clone(), Some(42 + i as u64));
328 println!("\nCustom network {}:", i + 1);
329 println!(
330 " Architecture: {} -> {:?} -> {}",
331 config.input_size, config.hidden_sizes, config.output_size
332 );
333 println!(" Layers: {}", network.num_layers());
334 println!(" Parameters: {}", network.parameter_count());
335 }
336}
337
338/// Demonstrate forward pass through the network
339fn demonstrate_forward_pass() {
340 println!("\n--- Forward Pass ---");
341
342 let config = FeedForwardConfig {
343 input_size: 3,
344 hidden_sizes: vec![5, 3],
345 output_size: 2,
346 use_bias: true,
347 };
348 let network = FeedForwardNetwork::new(config, Some(43));
349
350 // Single input
351 let input = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![1, 3]).unwrap();
352 let output = network.forward(&input);
353
354 println!("Single input forward pass:");
355 println!(" Input shape: {:?}", input.shape().dims);
356 println!(" Output shape: {:?}", output.shape().dims);
357 println!(" Output: {:?}", output.data());
358 println!(" Output requires grad: {}", output.requires_grad());
359
360 // Batch input
361 let batch_input = Tensor::from_slice(
362 &[
363 1.0, 2.0, 3.0, // Sample 1
364 4.0, 5.0, 6.0, // Sample 2
365 7.0, 8.0, 9.0, // Sample 3
366 ],
367 vec![3, 3],
368 )
369 .unwrap();
370 let batch_output = network.forward(&batch_input);
371
372 println!("Batch input forward pass:");
373 println!(" Input shape: {:?}", batch_input.shape().dims);
374 println!(" Output shape: {:?}", batch_output.shape().dims);
375 println!(" Output requires grad: {}", batch_output.requires_grad());
376
377 // Compare with no-grad version
378 let output_no_grad = network.forward_no_grad(&input);
379 println!("No-grad comparison:");
380 println!(" Same values: {}", output.data() == output_no_grad.data());
381 println!(" With grad requires grad: {}", output.requires_grad());
382 println!(
383 " No grad requires grad: {}",
384 output_no_grad.requires_grad()
385 );
386}
387
388/// Demonstrate different configurable architectures
389fn demonstrate_configurable_architectures() {
390 println!("\n--- Configurable Architectures ---");
391
392 let architectures = vec![
393 ("Shallow", vec![8]),
394 ("Medium", vec![16, 8]),
395 ("Deep", vec![32, 16, 8, 4]),
396 ("Wide", vec![64, 32]),
397 ("Bottleneck", vec![16, 4, 16]),
398 ];
399
400 for (name, hidden_sizes) in architectures {
401 let config = FeedForwardConfig {
402 input_size: 10,
403 hidden_sizes,
404 output_size: 3,
405 use_bias: true,
406 };
407
408 let network = FeedForwardNetwork::new(config.clone(), Some(44));
409
410 // Test forward pass
411 let test_input = Tensor::randn(vec![5, 10], Some(45)); // Batch of 5
412 let output = network.forward_no_grad(&test_input);
413
414 println!("{} network:", name);
415 println!(" Architecture: 10 -> {:?} -> 3", config.hidden_sizes);
416 println!(" Parameters: {}", network.parameter_count());
417 println!(" Test output shape: {:?}", output.shape().dims);
418 println!(
419 " Output range: [{:.3}, {:.3}]",
420 output.data().iter().fold(f32::INFINITY, |a, &b| a.min(b)),
421 output
422 .data()
423 .iter()
424 .fold(f32::NEG_INFINITY, |a, &b| a.max(b))
425 );
426 }
427}More examples
52 pub fn new(input_size: usize, output_size: usize, seed: Option<u64>) -> Self {
53 // Xavier/Glorot initialization: scale by sqrt(1/input_size)
54 let scale = (1.0 / input_size as f32).sqrt();
55
56 let weight = Tensor::randn(vec![input_size, output_size], seed)
57 .mul_scalar(scale)
58 .with_requires_grad();
59 let bias = Tensor::zeros(vec![output_size]).with_requires_grad();
60
61 Self {
62 weight,
63 bias,
64 input_size,
65 output_size,
66 }
67 }42fn demonstrate_tensor_creation() {
43 println!("--- Tensor Creation ---");
44
45 // Create tensors with different initializations
46 let zeros = Tensor::zeros(vec![2, 3]);
47 println!(
48 "Zeros tensor: shape {:?}, data: {:?}",
49 zeros.shape().dims,
50 zeros.data()
51 );
52
53 let ones = Tensor::ones(vec![3, 2]);
54 println!(
55 "Ones tensor: shape {:?}, data: {:?}",
56 ones.shape().dims,
57 ones.data()
58 );
59
60 // Create tensor from slice
61 let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
62 let from_slice = Tensor::from_slice(&data, vec![2, 3]).unwrap();
63 println!(
64 "From slice: shape {:?}, data: {:?}",
65 from_slice.shape().dims,
66 from_slice.data()
67 );
68
69 // Create tensor with specific value
70 let mut filled = Tensor::new(vec![2, 2]);
71 {
72 let data = filled.data_mut();
73 for value in data.iter_mut() {
74 *value = 42.0;
75 }
76 }
77 println!("Filled with 42: {:?}", filled.data());
78
79 // Create tensor with random data
80 let random = Tensor::randn(vec![2, 2], Some(42));
81 println!(
82 "Random tensor: shape {:?}, data: {:?}",
83 random.shape().dims,
84 random.data()
85 );
86}47fn demonstrate_basic_optimizer_setup() {
48 println!("--- Basic Optimizer Setup ---");
49
50 // Create parameters that require gradients
51 let weight = Tensor::randn(vec![3, 2], Some(42)).with_requires_grad();
52 let bias = Tensor::zeros(vec![2]).with_requires_grad();
53
54 println!("Created parameters:");
55 println!(
56 " Weight: shape {:?}, requires_grad: {}",
57 weight.shape().dims,
58 weight.requires_grad()
59 );
60 println!(
61 " Bias: shape {:?}, requires_grad: {}",
62 bias.shape().dims,
63 bias.requires_grad()
64 );
65
66 // Create Adam optimizer with default configuration
67 let mut optimizer = Adam::new();
68 println!(
69 "Created Adam optimizer with learning rate: {}",
70 optimizer.learning_rate()
71 );
72
73 // Add parameters to optimizer
74 optimizer.add_parameter(&weight);
75 optimizer.add_parameter(&bias);
76 println!(
77 "Added {} parameters to optimizer",
78 optimizer.parameter_count()
79 );
80
81 // Create optimizer with custom configuration
82 let config = AdamConfig {
83 learning_rate: 0.01,
84 beta1: 0.9,
85 beta2: 0.999,
86 eps: 1e-8,
87 weight_decay: 0.0,
88 amsgrad: false,
89 };
90
91 let mut custom_optimizer = Adam::with_config(config);
92 custom_optimizer.add_parameter(&weight);
93 custom_optimizer.add_parameter(&bias);
94
95 println!(
96 "Created custom optimizer with learning rate: {}",
97 custom_optimizer.learning_rate()
98 );
99
100 // Demonstrate parameter linking
101 println!("Parameter linking completed successfully");
102}
103
104/// Demonstrate simple linear regression training
105fn demonstrate_linear_regression() -> Result<(), Box<dyn std::error::Error>> {
106 println!("\n--- Linear Regression Training ---");
107
108 // Create model parameters
109 let mut weight = Tensor::randn(vec![1, 1], Some(43)).with_requires_grad();
110 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
111
112 // Create optimizer
113 let mut optimizer = Adam::with_learning_rate(0.01);
114 optimizer.add_parameter(&weight);
115 optimizer.add_parameter(&bias);
116
117 // Create simple training data: y = 2*x + 1
118 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
119 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
120
121 println!("Training data:");
122 println!(" X: {:?}", x_data.data());
123 println!(" Y: {:?}", y_true.data());
124 println!(" Target: y = 2*x + 1");
125
126 // Training loop
127 let num_epochs = 100;
128 let mut losses = Vec::new();
129
130 for epoch in 0..num_epochs {
131 // Forward pass: y_pred = x * weight + bias
132 let y_pred = x_data.matmul(&weight) + &bias;
133
134 // Compute loss: MSE
135 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
136
137 // Backward pass
138 loss.backward(None);
139
140 // Optimizer step
141 optimizer.step(&mut [&mut weight, &mut bias]);
142 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
143
144 losses.push(loss.value());
145
146 // Print progress every 20 epochs
147 if epoch % 20 == 0 || epoch == num_epochs - 1 {
148 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
149 }
150 }
151
152 // Evaluate final model
153 let final_predictions = x_data.matmul(&weight) + &bias;
154 println!("\nFinal model evaluation:");
155 println!(" Learned weight: {:.6}", weight.value());
156 println!(" Learned bias: {:.6}", bias.value());
157 println!(" Predictions vs True:");
158
159 for i in 0..5 {
160 let x1 = x_data.data()[i];
161 let pred = final_predictions.data()[i];
162 let true_val = y_true.data()[i];
163 println!(
164 " x={:.1}: pred={:.3}, true={:.1}, error={:.3}",
165 x1,
166 pred,
167 true_val,
168 (pred - true_val).abs()
169 );
170 }
171
172 Ok(())
173}
174
175/// Demonstrate advanced training patterns
176fn demonstrate_advanced_training() -> Result<(), Box<dyn std::error::Error>> {
177 println!("\n--- Advanced Training Patterns ---");
178
179 // Create a more complex model
180 let mut weight = Tensor::randn(vec![1, 2], Some(44)).with_requires_grad();
181 let mut bias = Tensor::zeros(vec![2]).with_requires_grad();
182
183 // Create optimizer with different learning rate
184 let mut optimizer = Adam::with_learning_rate(0.005);
185 optimizer.add_parameter(&weight);
186 optimizer.add_parameter(&bias);
187
188 // Create training data: y = 2*x + [1, 3]
189 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
190 let y_true = Tensor::from_slice(
191 &[3.0, 5.0, 7.0, 9.0, 11.0, 6.0, 8.0, 10.0, 12.0, 14.0],
192 vec![5, 2],
193 )
194 .unwrap();
195
196 println!("Advanced training with monitoring:");
197 println!(" Initial learning rate: {}", optimizer.learning_rate());
198
199 // Training loop with monitoring
200 let num_epochs = 50;
201 let mut losses = Vec::new();
202 let mut weight_norms = Vec::new();
203 let mut gradient_norms = Vec::new();
204
205 for epoch in 0..num_epochs {
206 // Forward pass
207 let y_pred = x_data.matmul(&weight) + &bias;
208 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
209
210 // Backward pass
211 loss.backward(None);
212
213 // Compute gradient norm before optimizer step
214 let gradient_norm = weight.grad_by_value().unwrap().norm();
215
216 // Optimizer step
217 optimizer.step(&mut [&mut weight, &mut bias]);
218 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
219
220 // Learning rate scheduling: reduce every 10 epochs
221 if epoch > 0 && epoch % 10 == 0 {
222 let current_lr = optimizer.learning_rate();
223 let new_lr = current_lr * 0.5;
224 optimizer.set_learning_rate(new_lr);
225 println!(
226 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
227 epoch, current_lr, new_lr
228 );
229 }
230
231 // Record metrics
232 losses.push(loss.value());
233 weight_norms.push(weight.norm().value());
234 gradient_norms.push(gradient_norm.value());
235
236 // Print detailed progress
237 if epoch % 10 == 0 || epoch == num_epochs - 1 {
238 println!(
239 "Epoch {:2}: Loss = {:.6}, Weight Norm = {:.6}, Gradient Norm = {:.6}",
240 epoch,
241 loss.value(),
242 weight.norm().value(),
243 gradient_norm.value()
244 );
245 }
246 }
247
248 println!("Final learning rate: {}", optimizer.learning_rate());
249
250 // Analyze training progression
251 let initial_loss = losses[0];
252 let final_loss = losses[losses.len() - 1];
253 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
254
255 println!("\nTraining Analysis:");
256 println!(" Initial loss: {:.6}", initial_loss);
257 println!(" Final loss: {:.6}", final_loss);
258 println!(" Loss reduction: {:.1}%", loss_reduction);
259 println!(" Final weight norm: {:.6}", weight.norm().value());
260 println!(" Final bias: {:?}", bias.data());
261
262 Ok(())
263}
264
265/// Demonstrate learning rate scheduling
266fn demonstrate_learning_rate_scheduling() -> Result<(), Box<dyn std::error::Error>> {
267 println!("\n--- Learning Rate Scheduling ---");
268
269 // Create simple model
270 let mut weight = Tensor::randn(vec![1, 1], Some(45)).with_requires_grad();
271 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
272
273 // Create optimizer with high initial learning rate
274 let mut optimizer = Adam::with_learning_rate(0.1);
275 optimizer.add_parameter(&weight);
276 optimizer.add_parameter(&bias);
277
278 // Simple data
279 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3, 1]).unwrap();
280 let y_true = Tensor::from_slice(&[2.0, 4.0, 6.0], vec![3, 1]).unwrap();
281
282 println!("Initial learning rate: {}", optimizer.learning_rate());
283
284 // Training loop with learning rate scheduling
285 let num_epochs = 50;
286 let mut losses = Vec::new();
287
288 for epoch in 0..num_epochs {
289 // Forward pass
290 let y_pred = x_data.matmul(&weight) + &bias;
291 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
292
293 // Backward pass
294 loss.backward(None);
295
296 // Optimizer step
297 optimizer.step(&mut [&mut weight, &mut bias]);
298 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
299
300 // Learning rate scheduling: reduce every 10 epochs
301 if epoch > 0 && epoch % 10 == 0 {
302 let current_lr = optimizer.learning_rate();
303 let new_lr = current_lr * 0.5;
304 optimizer.set_learning_rate(new_lr);
305 println!(
306 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
307 epoch, current_lr, new_lr
308 );
309 }
310
311 losses.push(loss.value());
312
313 // Print progress
314 if epoch % 10 == 0 || epoch == num_epochs - 1 {
315 println!(
316 "Epoch {:2}: Loss = {:.6}, LR = {:.3}",
317 epoch,
318 loss.value(),
319 optimizer.learning_rate()
320 );
321 }
322 }
323
324 println!("Final learning rate: {}", optimizer.learning_rate());
325
326 Ok(())
327}
328
329/// Demonstrate training monitoring and analysis
330fn demonstrate_training_monitoring() -> Result<(), Box<dyn std::error::Error>> {
331 println!("\n--- Training Monitoring ---");
332
333 // Create model
334 let mut weight = Tensor::randn(vec![1, 1], Some(46)).with_requires_grad();
335 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
336
337 // Create optimizer
338 let mut optimizer = Adam::with_learning_rate(0.01);
339 optimizer.add_parameter(&weight);
340 optimizer.add_parameter(&bias);
341
342 // Training data
343 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4, 1]).unwrap();
344 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0], vec![4, 1]).unwrap();
345
346 // Training loop with comprehensive monitoring
347 let num_epochs = 30;
348 let mut losses = Vec::new();
349 let mut weight_history = Vec::new();
350 let mut bias_history = Vec::new();
351
352 for epoch in 0..num_epochs {
353 // Forward pass
354 let y_pred = x_data.matmul(&weight) + &bias;
355 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
356
357 // Backward pass
358 loss.backward(None);
359
360 // Optimizer step
361 optimizer.step(&mut [&mut weight, &mut bias]);
362 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
363
364 // Record history
365 losses.push(loss.value());
366 weight_history.push(weight.value());
367 bias_history.push(bias.value());
368
369 // Print detailed monitoring
370 if epoch % 5 == 0 || epoch == num_epochs - 1 {
371 println!(
372 "Epoch {:2}: Loss = {:.6}, Weight = {:.6}, Bias = {:.6}",
373 epoch,
374 loss.value(),
375 weight.value(),
376 bias.value()
377 );
378 }
379 }
380
381 // Analyze training progression
382 println!("\nTraining Analysis:");
383 println!(" Initial loss: {:.6}", losses[0]);
384 println!(" Final loss: {:.6}", losses[losses.len() - 1]);
385 println!(
386 " Loss reduction: {:.1}%",
387 (losses[0] - losses[losses.len() - 1]) / losses[0] * 100.0
388 );
389
390 // Compute statistics
391 let loss_mean = compute_mean(&losses);
392 let loss_std = compute_std(&losses);
393 let weight_change = (weight_history[weight_history.len() - 1] - weight_history[0]).abs();
394 let bias_change = (bias_history[bias_history.len() - 1] - bias_history[0]).abs();
395
396 println!(" Average loss: {:.6} ± {:.6}", loss_mean, loss_std);
397 println!(" Weight change: {:.6}", weight_change);
398 println!(" Bias change: {:.6}", bias_change);
399 println!(" Final weight norm: {:.6}", weight.norm().value());
400 println!(" Final bias: {:.6}", bias.value());
401
402 Ok(())
403}109fn demonstrate_optimizer_serialization() -> Result<(), Box<dyn std::error::Error>> {
110 println!("\n--- Optimizer Serialization ---");
111
112 // Create an optimizer with some parameters
113 let mut weight = Tensor::randn(vec![2, 2], Some(42)).with_requires_grad();
114 let mut bias = Tensor::randn(vec![2], Some(43)).with_requires_grad();
115
116 let config = AdamConfig {
117 learning_rate: 0.001,
118 beta1: 0.9,
119 beta2: 0.999,
120 eps: 1e-8,
121 weight_decay: 0.0,
122 amsgrad: false,
123 };
124
125 let mut optimizer = Adam::with_config(config);
126 optimizer.add_parameter(&weight);
127 optimizer.add_parameter(&bias);
128
129 println!(
130 "Created optimizer with {} parameters",
131 optimizer.parameter_count()
132 );
133 println!("Learning rate: {}", optimizer.learning_rate());
134
135 // Simulate some training steps
136 for _ in 0..3 {
137 let mut loss = weight.sum() + bias.sum();
138 loss.backward(None);
139 optimizer.step(&mut [&mut weight, &mut bias]);
140 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
141 }
142
143 // Save optimizer state
144 let optimizer_path = "temp_optimizer.json";
145 optimizer.save_json(optimizer_path)?;
146 println!("Saved optimizer to: {}", optimizer_path);
147
148 // Load optimizer state
149 let loaded_optimizer = Adam::load_json(optimizer_path)?;
150 println!(
151 "Loaded optimizer with {} parameters",
152 loaded_optimizer.parameter_count()
153 );
154 println!("Learning rate: {}", loaded_optimizer.learning_rate());
155
156 // Verify optimizer state
157 assert_eq!(
158 optimizer.parameter_count(),
159 loaded_optimizer.parameter_count()
160 );
161 assert_eq!(optimizer.learning_rate(), loaded_optimizer.learning_rate());
162 println!("Optimizer serialization verification: PASSED");
163
164 Ok(())
165}
166
167/// Demonstrate format comparison and performance characteristics
168fn demonstrate_format_comparison() -> Result<(), Box<dyn std::error::Error>> {
169 println!("\n--- Format Comparison ---");
170
171 // Create a larger tensor for comparison
172 let tensor = Tensor::randn(vec![10, 10], Some(44));
173
174 // Save in both formats
175 tensor.save_json("temp_comparison.json")?;
176 tensor.save_binary("temp_comparison.bin")?;
177
178 // Compare file sizes
179 let json_size = fs::metadata("temp_comparison.json")?.len();
180 let binary_size = fs::metadata("temp_comparison.bin")?.len();
181
182 println!("JSON file size: {} bytes", json_size);
183 println!("Binary file size: {} bytes", binary_size);
184 println!(
185 "Compression ratio: {:.2}x",
186 json_size as f64 / binary_size as f64
187 );
188
189 // Load and verify both formats
190 let json_tensor = Tensor::load_json("temp_comparison.json")?;
191 let binary_tensor = Tensor::load_binary("temp_comparison.bin")?;
192
193 assert_eq!(tensor.shape().dims, json_tensor.shape().dims);
194 assert_eq!(tensor.shape().dims, binary_tensor.shape().dims);
195 assert_eq!(tensor.data(), json_tensor.data());
196 assert_eq!(tensor.data(), binary_tensor.data());
197
198 println!("Format comparison verification: PASSED");
199
200 Ok(())
201}
202
203/// Demonstrate a basic model checkpointing workflow
204fn demonstrate_model_checkpointing() -> Result<(), Box<dyn std::error::Error>> {
205 println!("\n--- Model Checkpointing ---");
206
207 // Create a simple model (weights and bias)
208 let mut weights = Tensor::randn(vec![2, 1], Some(45)).with_requires_grad();
209 let mut bias = Tensor::randn(vec![1], Some(46)).with_requires_grad();
210
211 // Create optimizer
212 let mut optimizer = Adam::with_learning_rate(0.01);
213 optimizer.add_parameter(&weights);
214 optimizer.add_parameter(&bias);
215
216 println!("Initial weights: {:?}", weights.data());
217 println!("Initial bias: {:?}", bias.data());
218
219 // Simulate training
220 for epoch in 0..5 {
221 let mut loss = weights.sum() + bias.sum();
222 loss.backward(None);
223 optimizer.step(&mut [&mut weights, &mut bias]);
224 optimizer.zero_grad(&mut [&mut weights, &mut bias]);
225
226 if epoch % 2 == 0 {
227 // Save checkpoint
228 let checkpoint_dir = format!("checkpoint_epoch_{}", epoch);
229 fs::create_dir_all(&checkpoint_dir)?;
230
231 weights.save_json(format!("{}/weights.json", checkpoint_dir))?;
232 bias.save_json(format!("{}/bias.json", checkpoint_dir))?;
233 optimizer.save_json(format!("{}/optimizer.json", checkpoint_dir))?;
234
235 println!("Saved checkpoint for epoch {}", epoch);
236 }
237 }
238
239 // Load from checkpoint
240 let loaded_weights = Tensor::load_json("checkpoint_epoch_4/weights.json")?;
241 let loaded_bias = Tensor::load_json("checkpoint_epoch_4/bias.json")?;
242 let loaded_optimizer = Adam::load_json("checkpoint_epoch_4/optimizer.json")?;
243
244 println!("Loaded weights: {:?}", loaded_weights.data());
245 println!("Loaded bias: {:?}", loaded_bias.data());
246 println!(
247 "Loaded optimizer learning rate: {}",
248 loaded_optimizer.learning_rate()
249 );
250
251 // Verify checkpoint integrity
252 assert_eq!(weights.shape().dims, loaded_weights.shape().dims);
253 assert_eq!(bias.shape().dims, loaded_bias.shape().dims);
254 assert_eq!(optimizer.learning_rate(), loaded_optimizer.learning_rate());
255
256 println!("Checkpointing verification: PASSED");
257
258 Ok(())
259}
260
261/// Demonstrate error handling for serialization operations
262fn demonstrate_error_handling() -> Result<(), Box<dyn std::error::Error>> {
263 println!("\n--- Error Handling ---");
264
265 // Test loading non-existent file
266 match Tensor::load_json("nonexistent_file.json") {
267 Ok(_) => println!("Unexpected: Successfully loaded non-existent file"),
268 Err(e) => println!("Expected error loading non-existent file: {}", e),
269 }
270
271 // Test loading with wrong format
272 let tensor = Tensor::randn(vec![2, 2], Some(47));
273 tensor.save_binary("temp_binary.bin")?;
274
275 match Tensor::load_json("temp_binary.bin") {
276 Ok(_) => println!("Unexpected: Successfully loaded binary as JSON"),
277 Err(e) => println!("Expected error loading binary as JSON: {}", e),
278 }
279
280 // Test loading corrupted file
281 fs::write("temp_invalid.json", "invalid json content")?;
282 match Tensor::load_json("temp_invalid.json") {
283 Ok(_) => println!("Unexpected: Successfully loaded invalid JSON"),
284 Err(e) => println!("Expected error loading invalid JSON: {}", e),
285 }
286
287 println!("Error handling verification: PASSED");
288
289 Ok(())
290}84fn demonstrate_default_adam() -> Result<(), Box<dyn std::error::Error>> {
85 println!("--- Default Adam Configuration ---");
86
87 // Create a simple regression problem: y = 2*x + 1
88 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
89 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
90
91 // Create model parameters
92 let mut weight = Tensor::randn(vec![1, 1], Some(42)).with_requires_grad();
93 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
94
95 // Create Adam optimizer with default configuration
96 let mut optimizer = Adam::new();
97 optimizer.add_parameter(&weight);
98 optimizer.add_parameter(&bias);
99
100 println!("Default Adam configuration:");
101 println!(" Learning rate: {}", optimizer.learning_rate());
102 println!(" Initial weight: {:.6}", weight.value());
103 println!(" Initial bias: {:.6}", bias.value());
104
105 // Training loop
106 let num_epochs = 50;
107 let mut losses = Vec::new();
108
109 for epoch in 0..num_epochs {
110 // Forward pass
111 let y_pred = x_data.matmul(&weight) + &bias;
112 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
113
114 // Backward pass
115 loss.backward(None);
116
117 // Optimizer step
118 optimizer.step(&mut [&mut weight, &mut bias]);
119 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
120
121 losses.push(loss.value());
122
123 if epoch % 10 == 0 || epoch == num_epochs - 1 {
124 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
125 }
126 }
127
128 // Evaluate final model
129 let _final_predictions = x_data.matmul(&weight) + &bias;
130 println!("\nFinal model:");
131 println!(" Learned weight: {:.6} (target: 2.0)", weight.value());
132 println!(" Learned bias: {:.6} (target: 1.0)", bias.value());
133 println!(" Final loss: {:.6}", losses[losses.len() - 1]);
134
135 Ok(())
136}
137
138/// Demonstrate learning rate comparison
139fn demonstrate_learning_rate_comparison() -> Result<(), Box<dyn std::error::Error>> {
140 println!("\n--- Learning Rate Comparison ---");
141
142 let learning_rates = [0.001, 0.01, 0.1];
143 let mut results = Vec::new();
144
145 for &lr in &learning_rates {
146 println!("\nTesting learning rate: {}", lr);
147
148 let stats = train_with_config(TrainingConfig {
149 learning_rate: lr,
150 ..Default::default()
151 })?;
152
153 results.push((lr, stats.clone()));
154
155 println!(" Final loss: {:.6}", stats.final_loss);
156 println!(" Convergence epoch: {}", stats.convergence_epoch);
157 }
158
159 // Compare results
160 println!("\nLearning Rate Comparison Summary:");
161 for (lr, stats) in &results {
162 println!(
163 " LR={:6}: Loss={:.6}, Converged@{}",
164 lr, stats.final_loss, stats.convergence_epoch
165 );
166 }
167
168 Ok(())
169}
170
171/// Demonstrate weight decay comparison
172fn demonstrate_weight_decay_comparison() -> Result<(), Box<dyn std::error::Error>> {
173 println!("\n--- Weight Decay Comparison ---");
174
175 let weight_decays = [0.0, 0.001, 0.01];
176 let mut results = Vec::new();
177
178 for &wd in &weight_decays {
179 println!("\nTesting weight decay: {}", wd);
180
181 let stats = train_with_config(TrainingConfig {
182 weight_decay: wd,
183 ..Default::default()
184 })?;
185
186 results.push((wd, stats.clone()));
187
188 println!(" Final loss: {:.6}", stats.final_loss);
189 println!(" Final weight norm: {:.6}", stats.weight_norm);
190 }
191
192 // Compare results
193 println!("\nWeight Decay Comparison Summary:");
194 for (wd, stats) in &results {
195 println!(
196 " WD={:6}: Loss={:.6}, Weight Norm={:.6}",
197 wd, stats.final_loss, stats.weight_norm
198 );
199 }
200
201 Ok(())
202}
203
204/// Demonstrate beta parameter tuning
205fn demonstrate_beta_parameter_tuning() -> Result<(), Box<dyn std::error::Error>> {
206 println!("\n--- Beta Parameter Tuning ---");
207
208 let beta_configs = [
209 (0.9, 0.999), // Default
210 (0.8, 0.999), // More aggressive momentum
211 (0.95, 0.999), // Less aggressive momentum
212 (0.9, 0.99), // Faster second moment decay
213 ];
214
215 let mut results = Vec::new();
216
217 for (i, (beta1, beta2)) in beta_configs.iter().enumerate() {
218 println!(
219 "\nTesting beta configuration {}: beta1={}, beta2={}",
220 i + 1,
221 beta1,
222 beta2
223 );
224
225 let config = TrainingConfig {
226 beta1: *beta1,
227 beta2: *beta2,
228 ..Default::default()
229 };
230
231 let stats = train_with_config(config)?;
232 results.push(((*beta1, *beta2), stats.clone()));
233
234 println!(" Final loss: {:.6}", stats.final_loss);
235 println!(" Convergence epoch: {}", stats.convergence_epoch);
236 }
237
238 // Compare results
239 println!("\nBeta Parameter Comparison Summary:");
240 for ((beta1, beta2), stats) in &results {
241 println!(
242 " B1={:4}, B2={:5}: Loss={:.6}, Converged@{}",
243 beta1, beta2, stats.final_loss, stats.convergence_epoch
244 );
245 }
246
247 Ok(())
248}
249
250/// Demonstrate configuration benchmarking
251fn demonstrate_configuration_benchmarking() -> Result<(), Box<dyn std::error::Error>> {
252 println!("\n--- Configuration Benchmarking ---");
253
254 // Define configurations to benchmark
255 let configs = vec![
256 (
257 "Conservative",
258 TrainingConfig {
259 learning_rate: 0.001,
260 weight_decay: 0.001,
261 beta1: 0.95,
262 ..Default::default()
263 },
264 ),
265 (
266 "Balanced",
267 TrainingConfig {
268 learning_rate: 0.01,
269 weight_decay: 0.0,
270 beta1: 0.9,
271 ..Default::default()
272 },
273 ),
274 (
275 "Aggressive",
276 TrainingConfig {
277 learning_rate: 0.1,
278 weight_decay: 0.0,
279 beta1: 0.8,
280 ..Default::default()
281 },
282 ),
283 ];
284
285 let mut benchmark_results = Vec::new();
286
287 for (name, config) in configs {
288 println!("\nBenchmarking {} configuration:", name);
289
290 let start_time = std::time::Instant::now();
291 let stats = train_with_config(config.clone())?;
292 let elapsed = start_time.elapsed();
293
294 println!(" Training time: {:.2}ms", elapsed.as_millis());
295 println!(" Final loss: {:.6}", stats.final_loss);
296 println!(" Convergence: {} epochs", stats.convergence_epoch);
297
298 benchmark_results.push((name.to_string(), stats, elapsed));
299 }
300
301 // Summary
302 println!("\nBenchmarking Summary:");
303 for (name, stats, elapsed) in &benchmark_results {
304 println!(
305 " {:12}: Loss={:.6}, Time={:4}ms, Converged@{}",
306 name,
307 stats.final_loss,
308 elapsed.as_millis(),
309 stats.convergence_epoch
310 );
311 }
312
313 Ok(())
314}
315
316/// Helper function to train with specific configuration
317fn train_with_config(config: TrainingConfig) -> Result<TrainingStats, Box<dyn std::error::Error>> {
318 // Create training data
319 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
320 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
321
322 // Create model parameters
323 let mut weight = Tensor::randn(vec![1, 1], Some(123)).with_requires_grad();
324 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
325
326 // Create optimizer with custom configuration
327 let adam_config = AdamConfig {
328 learning_rate: config.learning_rate,
329 beta1: config.beta1,
330 beta2: config.beta2,
331 eps: 1e-8,
332 weight_decay: config.weight_decay,
333 amsgrad: false,
334 };
335
336 let mut optimizer = Adam::with_config(adam_config);
337 optimizer.add_parameter(&weight);
338 optimizer.add_parameter(&bias);
339
340 // Training loop
341 let mut losses = Vec::new();
342 let mut convergence_epoch = config.epochs;
343
344 for epoch in 0..config.epochs {
345 // Forward pass
346 let y_pred = x_data.matmul(&weight) + &bias;
347 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
348
349 // Backward pass
350 loss.backward(None);
351
352 // Optimizer step
353 optimizer.step(&mut [&mut weight, &mut bias]);
354 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
355
356 let loss_value = loss.value();
357 losses.push(loss_value);
358
359 // Check for convergence (loss < 0.01)
360 if loss_value < 0.01 && convergence_epoch == config.epochs {
361 convergence_epoch = epoch;
362 }
363 }
364
365 Ok(TrainingStats {
366 config,
367 final_loss: losses[losses.len() - 1],
368 loss_history: losses,
369 convergence_epoch,
370 weight_norm: weight.norm().value(),
371 })
372}Sourcepub fn fill_randn(&mut self, seed: Option<u64>)
pub fn fill_randn(&mut self, seed: Option<u64>)
Fills the tensor with normally distributed random values
Internal method that fills an existing tensor with random values from a standard normal distribution. Uses Box-Muller transform for efficiency and provides SIMD optimization for large tensors.
This method is used internally by randn() and provides the core
random number generation functionality with optimized performance
characteristics.
§Arguments
seed- Optional seed for reproducible random generation
§Performance
- Box-Muller Transform: Generates pairs of normal random variables
- SIMD Optimization: Vectorized operations when possible
- Memory Efficient: Single-pass generation
- Unrolled Loops: 4x unrolling for better instruction throughput
§Implementation Details
The method performs the following steps:
- Zero-sized Check: Returns early for empty tensors
- RNG Initialization: Creates Xorshift RNG with seed or system time
- SIMD Detection: Checks for AVX2 availability for optimized path
- Generation: Uses SIMD or scalar path based on hardware support
- Completion: Fills all tensor elements with normal random values
The method automatically handles hardware capabilities and falls back to scalar operations when SIMD is not available, ensuring compatibility across different CPU architectures.
Source§impl Tensor
impl Tensor
Sourcepub fn iter(&self) -> TensorElementIterator<'_>
pub fn iter(&self) -> TensorElementIterator<'_>
Create an iterator over tensor elements as view tensors
Each element becomes a Tensor of shape [1] that supports all
tensor operations and gradient tracking. This is the main entry point
for element-wise iteration with full tensor operation support.
The iterator provides zero-copy access to tensor elements through view tensors, enabling efficient element-wise operations while maintaining full compatibility with Rust’s standard library iterator methods.
§Returns
An iterator that yields view tensors for each element
§Performance
- Zero-Copy Views: Each element is a view sharing memory with source
- O(1) Element Access: Constant-time view creation for each element
- Memory Efficient: ~64 bytes overhead per element view
- SIMD Compatible: All tensor operations use existing optimizations
- Gradient Tracking: Full gradtrack support through element operations
§Examples
§Basic Element Operations
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
// Use any std iterator method
let result: Tensor = tensor.iter()
.map(|elem| elem.mul_scalar(2.0).add_scalar(1.0)) // 2x + 1
.filter(|elem| elem.value() > 3.0) // Keep values > 3
.collect();
assert_eq!(result.data(), &[5.0, 7.0]);§Advanced Iterator Chains
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5]).unwrap();
// Chain with enumerate, zip, etc.
let indexed: Tensor = tensor.iter()
.enumerate()
.map(|(i, elem)| elem.add_scalar(i as f32))
.collect();
assert_eq!(indexed.data(), &[1.0, 3.0, 5.0, 7.0, 9.0]);§Double-Ended Iteration
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4]).unwrap();
// Use double-ended iterator
let reversed: Tensor = tensor.iter()
.rev()
.collect();
assert_eq!(reversed.data(), &[4.0, 3.0, 2.0, 1.0]);§Gradient Tracking
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0], vec![2])
.unwrap()
.with_requires_grad();
let result: Tensor = tensor.iter()
.map(|elem| elem.mul_scalar(2.0))
.collect();
assert!(result.requires_grad());
assert_eq!(result.data(), &[2.0, 4.0]);Examples found in repository?
77fn demonstrate_basic_iteration() -> Result<(), Box<dyn std::error::Error>> {
78 println!("\n--- Basic Element Iteration ---");
79
80 // Create a simple tensor for demonstration
81 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
82 println!("Original tensor: {:?}", tensor.data());
83
84 // Basic iteration with for loop
85 println!("\nBasic iteration with for loop:");
86 for (i, element) in tensor.iter().enumerate() {
87 println!(
88 " Element {}: value = {:.1}, shape = {:?}",
89 i,
90 element.value(),
91 element.shape().dims
92 );
93 }
94
95 // Element-wise transformation
96 println!("\nElement-wise transformation (2x + 1):");
97 let transformed: Tensor = tensor
98 .iter()
99 .map(|elem| elem.mul_scalar(2.0).add_scalar(1.0))
100 .collect();
101 println!(" Result: {:?}", transformed.data());
102
103 // Filtering elements
104 println!("\nFiltering elements (values > 3.0):");
105 let filtered: Tensor = tensor.iter().filter(|elem| elem.value() > 3.0).collect();
106 println!(" Filtered: {:?}", filtered.data());
107
108 Ok(())
109}
110
111/// Demonstrate standard iterator trait methods
112///
113/// Shows compatibility with Rust's standard library iterator methods
114/// and demonstrates various functional programming patterns.
115fn demonstrate_standard_methods() -> Result<(), Box<dyn std::error::Error>> {
116 println!("\n--- Standard Iterator Methods ---");
117
118 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
119
120 // Using map for transformations
121 println!("\nMap transformation (square each element):");
122 let squared: Tensor = tensor.iter().map(|elem| elem.pow_scalar(2.0)).collect();
123 println!(" Squared: {:?}", squared.data());
124
125 // Using enumerate for indexed operations
126 println!("\nEnumerate with indexed operations:");
127 let indexed: Tensor = tensor
128 .iter()
129 .enumerate()
130 .map(|(i, elem)| elem.add_scalar(i as f32))
131 .collect();
132 println!(" Indexed: {:?}", indexed.data());
133
134 // Using fold for reduction
135 println!("\nFold for sum calculation:");
136 let sum: f32 = tensor.iter().fold(0.0, |acc, elem| acc + elem.value());
137 println!(" Sum: {:.1}", sum);
138
139 // Using find for element search
140 println!("\nFind specific element:");
141 if let Some(found) = tensor.iter().find(|elem| elem.value() == 3.0) {
142 println!(" Found element with value 3.0: {:.1}", found.value());
143 }
144
145 // Using any/all for condition checking
146 println!("\nCondition checking:");
147 let all_positive = tensor.iter().all(|elem| elem.value() > 0.0);
148 let any_large = tensor.iter().any(|elem| elem.value() > 4.0);
149 println!(" All positive: {}", all_positive);
150 println!(" Any > 4.0: {}", any_large);
151
152 Ok(())
153}
154
155/// Demonstrate gradient tracking through element operations
156///
157/// Shows how gradient tracking works seamlessly through iterator
158/// operations, maintaining the computational graph for backpropagation.
159fn demonstrate_gradient_tracking() -> Result<(), Box<dyn std::error::Error>> {
160 println!("\n--- Gradient Tracking ---");
161
162 // Create a tensor with gradient tracking enabled
163 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?.with_requires_grad();
164 println!("Input tensor (requires_grad): {:?}", tensor.data());
165
166 // Perform element-wise operations through iteration
167 let result: Tensor = tensor
168 .iter()
169 .map(|elem| {
170 // Apply a complex transformation: (x^2 + 1) * 2
171 elem.pow_scalar(2.0).add_scalar(1.0).mul_scalar(2.0)
172 })
173 .collect();
174
175 println!("Result tensor: {:?}", result.data());
176 println!("Result requires_grad: {}", result.requires_grad());
177
178 // Compute gradients
179 let mut loss = result.sum();
180 loss.backward(None);
181
182 println!("Loss: {:.6}", loss.value());
183 println!("Input gradients: {:?}", tensor.grad().map(|g| g.data()));
184
185 Ok(())
186}
187
188/// Demonstrate advanced iterator patterns
189///
190/// Shows complex iterator chains and advanced functional programming
191/// patterns for sophisticated data processing workflows.
192fn demonstrate_advanced_patterns() -> Result<(), Box<dyn std::error::Error>> {
193 println!("\n--- Advanced Iterator Patterns ---");
194
195 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![6])?;
196 println!("Input tensor: {:?}", tensor.data());
197
198 // Complex chain: enumerate -> filter -> map -> collect
199 println!("\nComplex chain (even indices only, add index to value):");
200 let result: Tensor = tensor
201 .iter()
202 .enumerate()
203 .filter(|(i, _)| i % 2 == 0) // Take even indices
204 .map(|(i, elem)| elem.add_scalar(i as f32)) // Add index to value
205 .collect();
206 println!(" Result: {:?}", result.data());
207
208 // Using take and skip for windowing
209 println!("\nWindowing with take and skip:");
210 let window1: Tensor = tensor.iter().take(3).collect();
211 let window2: Tensor = tensor.iter().skip(2).take(3).collect();
212 println!(" Window 1 (first 3): {:?}", window1.data());
213 println!(" Window 2 (middle 3): {:?}", window2.data());
214
215 // Using rev() for reverse iteration
216 println!("\nReverse iteration:");
217 let reversed: Tensor = tensor.iter().rev().collect();
218 println!(" Reversed: {:?}", reversed.data());
219
220 // Chaining with mathematical operations
221 println!("\nMathematical operation chain:");
222 let math_result: Tensor = tensor
223 .iter()
224 .map(|elem| elem.exp()) // e^x
225 .filter(|elem| elem.value() < 50.0) // Filter large values
226 .map(|elem| elem.log()) // ln(x)
227 .collect();
228 println!(" Math chain result: {:?}", math_result.data());
229
230 // Using zip for element-wise combinations
231 println!("\nElement-wise combination with zip:");
232 let tensor2 = Tensor::from_slice(&[10.0, 20.0, 30.0, 40.0, 50.0, 60.0], vec![6])?;
233 let combined: Tensor = tensor
234 .iter()
235 .zip(tensor2.iter())
236 .map(|(a, b)| a.mul_tensor(&b)) // Element-wise multiplication
237 .collect();
238 println!(" Combined: {:?}", combined.data());
239
240 Ok(())
241}More examples
75fn demonstrate_performance_benchmarking() -> Result<(), Box<dyn std::error::Error>> {
76 println!("\n--- Performance Benchmarking ---");
77
78 // Create test data of different sizes
79 let sizes = vec![100, 1000, 10000];
80
81 for size in sizes {
82 println!("\nBenchmarking with tensor size: {}", size);
83
84 // Generate test data
85 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
86 let tensor = Tensor::from_slice(&data, vec![size])?;
87
88 // Benchmark 1: Direct tensor operations
89 let start = Instant::now();
90 let direct_result = tensor.mul_scalar(2.0).add_scalar(1.0);
91 let direct_time = start.elapsed();
92
93 // Benchmark 2: Iterator-based operations
94 let start = Instant::now();
95 let iterator_result: Tensor = tensor
96 .iter()
97 .map(|elem| elem.mul_scalar(2.0).add_scalar(1.0))
98 .collect();
99 let iterator_time = start.elapsed();
100
101 // Benchmark 3: Chained iterator operations
102 let start = Instant::now();
103 let _chained_result: Tensor = tensor
104 .iter()
105 .map(|elem| elem.mul_scalar(2.0))
106 .filter(|elem| elem.value() > size as f32)
107 .map(|elem| elem.add_scalar(1.0))
108 .collect();
109 let chained_time = start.elapsed();
110
111 // Report results
112 println!(" Direct operations: {:?}", direct_time);
113 println!(" Iterator operations: {:?}", iterator_time);
114 println!(" Chained operations: {:?}", chained_time);
115
116 // Verify correctness
117 assert_eq!(direct_result.data(), iterator_result.data());
118 println!(
119 " Results match: {}",
120 direct_result.data() == iterator_result.data()
121 );
122
123 // Performance ratio
124 let ratio = iterator_time.as_nanos() as f64 / direct_time.as_nanos() as f64;
125 println!(" Iterator/Direct ratio: {:.2}x", ratio);
126 }
127
128 Ok(())
129}
130
131/// Demonstrate memory optimization patterns
132///
133/// Shows memory-efficient processing patterns and techniques
134/// for minimizing memory usage while maintaining performance.
135fn demonstrate_memory_optimization() -> Result<(), Box<dyn std::error::Error>> {
136 println!("\n--- Memory Optimization ---");
137
138 // Create a large tensor for memory testing
139 let size = 10000;
140 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
141 let tensor = Tensor::from_slice(&data, vec![size])?;
142
143 println!("Processing tensor of size: {}", size);
144
145 // Pattern 1: Streaming processing (process in chunks)
146 println!("\nPattern 1: Streaming Processing");
147 let chunk_size = 1000;
148 let start = Instant::now();
149
150 let mut streamed_result = Vec::new();
151 for chunk_start in (0..size).step_by(chunk_size) {
152 let chunk_end = (chunk_start + chunk_size).min(size);
153 let chunk: Tensor = tensor
154 .iter_range(chunk_start, chunk_end)
155 .map(|elem| elem.pow_scalar(2.0).sqrt())
156 .collect();
157 streamed_result.extend(chunk.data().iter().cloned());
158 }
159 let streamed_time = start.elapsed();
160
161 // Pattern 2: Full processing
162 let start = Instant::now();
163 let _full_result: Tensor = tensor
164 .iter()
165 .map(|elem| elem.pow_scalar(2.0).sqrt())
166 .collect();
167 let full_time = start.elapsed();
168
169 println!(" Streaming time: {:?}", streamed_time);
170 println!(" Full processing time: {:?}", full_time);
171 println!(
172 " Memory efficiency ratio: {:.2}x",
173 full_time.as_nanos() as f64 / streamed_time.as_nanos() as f64
174 );
175
176 // Pattern 3: Lazy evaluation with take
177 println!("\nPattern 2: Lazy Evaluation");
178 let start = Instant::now();
179 let lazy_result: Tensor = tensor
180 .iter()
181 .take(1000) // Only process first 1000 elements
182 .map(|elem| elem.pow_scalar(2.0).sqrt())
183 .collect();
184 let lazy_time = start.elapsed();
185
186 println!(" Lazy processing (1000 elements): {:?}", lazy_time);
187 println!(" Lazy result size: {}", lazy_result.size());
188
189 // Pattern 4: Memory-efficient filtering
190 println!("\nPattern 3: Memory-Efficient Filtering");
191 let start = Instant::now();
192 let filtered_result: Tensor = tensor
193 .iter()
194 .filter(|elem| elem.value() > size as f32 / 2.0) // Keep only large values
195 .map(|elem| elem.mul_scalar(2.0))
196 .collect();
197 let filtered_time = start.elapsed();
198
199 println!(" Filtered processing: {:?}", filtered_time);
200 println!(
201 " Filtered result size: {} (reduced from {})",
202 filtered_result.size(),
203 size
204 );
205
206 Ok(())
207}
208
209/// Demonstrate large-scale processing techniques
210///
211/// Shows how to efficiently process very large datasets using
212/// iterator patterns and optimization strategies.
213fn demonstrate_large_scale_processing() -> Result<(), Box<dyn std::error::Error>> {
214 println!("\n--- Large-Scale Processing ---");
215
216 // Simulate large dataset processing
217 let sizes = vec![10000, 50000, 100000];
218
219 for size in sizes {
220 println!("\nProcessing dataset of size: {}", size);
221
222 // Generate large dataset
223 let data: Vec<f32> = (0..size)
224 .map(|i| {
225 let x = i as f32 / size as f32;
226 x * x + 0.1 * (i % 10) as f32 // Quadratic with noise
227 })
228 .collect();
229
230 let tensor = Tensor::from_slice(&data, vec![size])?;
231
232 // Technique 1: Batch processing
233 let batch_size = 1000;
234 let start = Instant::now();
235
236 let mut batch_results = Vec::new();
237 for batch_start in (0..size).step_by(batch_size) {
238 let batch_end = (batch_start + batch_size).min(size);
239 let batch: Tensor = tensor
240 .iter_range(batch_start, batch_end)
241 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
242 .collect();
243 batch_results.push(batch);
244 }
245 let batch_time = start.elapsed();
246
247 // Technique 2: Parallel-like processing with stride
248 let start = Instant::now();
249 let stride = 4;
250 let strided_result: Tensor = tensor
251 .iter()
252 .enumerate()
253 .filter(|(i, _)| i % stride == 0)
254 .map(|(_, elem)| elem.pow_scalar(2.0).add_scalar(1.0))
255 .collect();
256 let strided_time = start.elapsed();
257
258 // Technique 3: Hierarchical processing
259 let start = Instant::now();
260 let coarse: Tensor = tensor
261 .iter()
262 .enumerate()
263 .filter(|(i, _)| i % 10 == 0) // Every 10th element
264 .map(|(_, elem)| elem.pow_scalar(2.0).add_scalar(1.0))
265 .collect();
266 let fine: Tensor = tensor
267 .iter()
268 .enumerate()
269 .filter(|(i, _)| i % 10 != 0) // Rest of elements
270 .map(|(_, elem)| elem.pow_scalar(1.5).add_scalar(0.5))
271 .collect();
272 let hierarchical_time = start.elapsed();
273
274 // Report performance
275 println!(" Batch processing: {:?}", batch_time);
276 println!(" Strided processing: {:?}", strided_time);
277 println!(" Hierarchical processing: {:?}", hierarchical_time);
278
279 // Memory usage analysis
280 let total_batches = (size + batch_size - 1) / batch_size;
281 println!(" Batch count: {}", total_batches);
282 println!(" Strided result size: {}", strided_result.size());
283 println!(
284 " Hierarchical: coarse={}, fine={}",
285 coarse.size(),
286 fine.size()
287 );
288 }
289
290 Ok(())
291}
292
293/// Demonstrate advanced optimization techniques
294///
295/// Shows sophisticated optimization strategies and techniques
296/// for maximizing performance in tensor iterator operations.
297fn demonstrate_optimization_techniques() -> Result<(), Box<dyn std::error::Error>> {
298 println!("\n--- Optimization Techniques ---");
299
300 let size = 50000;
301 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
302 let tensor = Tensor::from_slice(&data, vec![size])?;
303
304 println!("Optimizing processing for size: {}", size);
305
306 // Technique 1: Operation fusion
307 println!("\nTechnique 1: Operation Fusion");
308 let start = Instant::now();
309 let fused_result: Tensor = tensor
310 .iter()
311 .map(|elem| {
312 // Fuse multiple operations into single chain
313 elem.mul_scalar(2.0).add_scalar(1.0).pow_scalar(2.0).sqrt()
314 })
315 .collect();
316 let fused_time = start.elapsed();
317
318 // Technique 2: Conditional optimization
319 println!("\nTechnique 2: Conditional Optimization");
320 let start = Instant::now();
321 let conditional_result: Tensor = tensor
322 .iter()
323 .map(|elem| {
324 let val = elem.value();
325 if val < size as f32 / 2.0 {
326 elem.mul_scalar(2.0) // Simple operation for small values
327 } else {
328 elem.pow_scalar(2.0).sqrt() // Complex operation for large values
329 }
330 })
331 .collect();
332 let conditional_time = start.elapsed();
333
334 // Technique 3: Cache-friendly processing
335 println!("\nTechnique 3: Cache-Friendly Processing");
336 let start = Instant::now();
337 let cache_friendly_result: Tensor = tensor
338 .iter()
339 .take(1000) // Process in cache-friendly chunks
340 .map(|elem| elem.mul_scalar(2.0))
341 .collect();
342 let cache_friendly_time = start.elapsed();
343
344 // Technique 4: Memory pooling simulation
345 println!("\nTechnique 4: Memory Pooling Simulation");
346 let start = Instant::now();
347 let pooled_result: Tensor = tensor
348 .iter()
349 .enumerate()
350 .filter(|(i, _)| i % 100 == 0) // Process every 100th element
351 .map(|(_, elem)| elem.pow_scalar(2.0))
352 .collect();
353 let pooled_time = start.elapsed();
354
355 // Report optimization results
356 println!(" Fused operations: {:?}", fused_time);
357 println!(" Conditional optimization: {:?}", conditional_time);
358 println!(" Cache-friendly processing: {:?}", cache_friendly_time);
359 println!(" Memory pooling simulation: {:?}", pooled_time);
360
361 // Performance analysis
362 let fastest = fused_time
363 .min(conditional_time)
364 .min(cache_friendly_time)
365 .min(pooled_time);
366 println!(" Fastest technique: {:?}", fastest);
367
368 // Memory efficiency analysis
369 println!(" Fused result size: {}", fused_result.size());
370 println!(" Conditional result size: {}", conditional_result.size());
371 println!(
372 " Cache-friendly result size: {}",
373 cache_friendly_result.size()
374 );
375 println!(" Pooled result size: {}", pooled_result.size());
376
377 // Technique 5: Gradient optimization
378 println!("\nTechnique 5: Gradient Optimization");
379 let grad_tensor = tensor.with_requires_grad();
380 let start = Instant::now();
381
382 let grad_result: Tensor = grad_tensor
383 .iter()
384 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
385 .collect();
386
387 let mut loss = grad_result.sum();
388 loss.backward(None);
389 let grad_time = start.elapsed();
390
391 println!(" Gradient computation: {:?}", grad_time);
392 println!(
393 " Gradient tracking enabled: {}",
394 grad_result.requires_grad()
395 );
396
397 Ok(())
398}74fn demonstrate_data_pipeline() -> Result<(), Box<dyn std::error::Error>> {
75 println!("\n--- Data Processing Pipeline ---");
76
77 // Simulate raw sensor data with noise
78 let raw_data: Vec<f32> = (0..20)
79 .map(|i| {
80 let base = i as f32 * 0.5;
81 let noise = (i % 3) as f32 * 0.1;
82 base + noise
83 })
84 .collect();
85
86 let tensor = Tensor::from_slice(&raw_data, vec![20])?;
87 println!("Raw sensor data: {:?}", tensor.data());
88
89 // Multi-stage processing pipeline
90 println!("\nProcessing pipeline:");
91 println!("1. Normalize data (z-score)");
92 println!("2. Apply smoothing filter");
93 println!("3. Detect outliers");
94 println!("4. Apply feature scaling");
95
96 // Stage 1: Normalization
97 let mean = tensor.mean().value();
98 let std = tensor.std().value();
99 let normalized: Tensor = tensor
100 .iter()
101 .map(|elem| elem.sub_scalar(mean).div_scalar(std))
102 .collect();
103 println!(
104 " Normalized (mean={:.3}, std={:.3}): {:?}",
105 mean,
106 std,
107 normalized.data()
108 );
109
110 // Stage 2: Smoothing (simple moving average)
111 let smoothed: Tensor = normalized
112 .iter()
113 .enumerate()
114 .map(|(i, elem)| {
115 if i == 0 || i == normalized.size() - 1 {
116 elem.clone()
117 } else {
118 // Simple 3-point average
119 let prev = normalized.element_view(i - 1);
120 let next = normalized.element_view(i + 1);
121 elem.add_tensor(&prev).add_tensor(&next).div_scalar(3.0)
122 }
123 })
124 .collect();
125 println!(" Smoothed: {:?}", smoothed.data());
126
127 // Stage 3: Outlier detection and removal
128 let outlier_threshold = 2.0;
129 let cleaned: Tensor = smoothed
130 .iter()
131 .filter(|elem| elem.value().abs() < outlier_threshold)
132 .collect();
133 println!(
134 " Outliers removed (threshold={}): {:?}",
135 outlier_threshold,
136 cleaned.data()
137 );
138
139 // Stage 4: Feature scaling to [0, 1] range
140 let min_val = cleaned
141 .iter()
142 .map(|e| e.value())
143 .fold(f32::INFINITY, f32::min);
144 let max_val = cleaned
145 .iter()
146 .map(|e| e.value())
147 .fold(f32::NEG_INFINITY, f32::max);
148 let scaled: Tensor = cleaned
149 .iter()
150 .map(|elem| elem.sub_scalar(min_val).div_scalar(max_val - min_val))
151 .collect();
152 println!(" Scaled to [0,1]: {:?}", scaled.data());
153
154 Ok(())
155}
156
157/// Demonstrate conditional processing patterns
158///
159/// Shows how to implement dynamic filtering and transformation
160/// based on data characteristics and conditions.
161fn demonstrate_conditional_processing() -> Result<(), Box<dyn std::error::Error>> {
162 println!("\n--- Conditional Processing ---");
163
164 // Create data with mixed characteristics
165 let data = vec![1.0, -2.0, 3.0, -4.0, 5.0, -6.0, 7.0, -8.0, 9.0, -10.0];
166 let tensor = Tensor::from_slice(&data, vec![10])?;
167 println!("Input data: {:?}", tensor.data());
168
169 // Conditional transformation based on sign
170 println!("\nConditional transformation (positive/negative handling):");
171 let processed: Tensor = tensor
172 .iter()
173 .map(|elem| {
174 let val = elem.value();
175 if val > 0.0 {
176 elem.pow_scalar(2.0) // Square positive values
177 } else {
178 elem.mul_scalar(-1.0).sqrt() // Square root of absolute negative values
179 }
180 })
181 .collect();
182 println!(" Processed: {:?}", processed.data());
183
184 // Adaptive filtering based on local statistics
185 println!("\nAdaptive filtering (remove values > 2 std from local mean):");
186 let window_size = 3;
187 let adaptive_filtered: Tensor = tensor
188 .iter()
189 .enumerate()
190 .filter(|(i, elem)| {
191 let start = i.saturating_sub(window_size / 2);
192 let end = (i + window_size / 2 + 1).min(tensor.size());
193
194 // Calculate local mean and std
195 let local_values: Vec<f32> = (start..end)
196 .map(|j| tensor.element_view(j).value())
197 .collect();
198
199 let local_mean = local_values.iter().sum::<f32>() / local_values.len() as f32;
200 let local_variance = local_values
201 .iter()
202 .map(|v| (v - local_mean).powi(2))
203 .sum::<f32>()
204 / local_values.len() as f32;
205 let local_std = local_variance.sqrt();
206
207 let threshold = local_mean + 2.0 * local_std;
208 elem.value() <= threshold
209 })
210 .map(|(_, elem)| elem)
211 .collect();
212 println!(" Adaptive filtered: {:?}", adaptive_filtered.data());
213
214 // Multi-condition processing
215 println!("\nMulti-condition processing:");
216 let multi_processed: Tensor = tensor
217 .iter()
218 .map(|elem| {
219 let val = elem.value();
220 match () {
221 _ if val > 5.0 => elem.mul_scalar(2.0), // Double large values
222 _ if val < -5.0 => elem.div_scalar(2.0), // Halve small values
223 _ if val.abs() < 2.0 => elem.add_scalar(1.0), // Add 1 to small values
224 _ => elem.clone(), // Keep others unchanged
225 }
226 })
227 .collect();
228 println!(" Multi-condition: {:?}", multi_processed.data());
229
230 Ok(())
231}
232
233/// Demonstrate batch processing operations
234///
235/// Shows efficient processing of large datasets using iterator
236/// patterns and batch operations for performance optimization.
237fn demonstrate_batch_operations() -> Result<(), Box<dyn std::error::Error>> {
238 println!("\n--- Batch Operations ---");
239
240 // Create a larger dataset for batch processing
241 let size = 100;
242 let data: Vec<f32> = (0..size)
243 .map(|i| {
244 let x = i as f32 / size as f32;
245 x * x + 0.1 * (i % 7) as f32 // Quadratic with some noise
246 })
247 .collect();
248
249 let tensor = Tensor::from_slice(&data, vec![size])?;
250 println!("Dataset size: {}", tensor.size());
251
252 // Batch processing with windowing
253 println!("\nBatch processing with sliding windows:");
254 let batch_size = 10;
255 let batches: Vec<Tensor> = tensor
256 .iter()
257 .collect::<Vec<_>>()
258 .chunks(batch_size)
259 .map(|chunk| {
260 // Process each batch independently
261 chunk
262 .iter()
263 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
264 .collect()
265 })
266 .collect();
267
268 println!(
269 " Processed {} batches of size {}",
270 batches.len(),
271 batch_size
272 );
273 for (i, batch) in batches.iter().enumerate() {
274 println!(
275 " Batch {}: mean={:.3}, std={:.3}",
276 i,
277 batch.mean().value(),
278 batch.std().value()
279 );
280 }
281
282 // Parallel-like processing with stride
283 println!("\nStrided processing (every nth element):");
284 let stride = 5;
285 let strided: Tensor = tensor
286 .iter()
287 .enumerate()
288 .filter(|(i, _)| i % stride == 0)
289 .map(|(_, elem)| elem)
290 .collect();
291 println!(" Strided (every {}th): {:?}", stride, strided.data());
292
293 // Hierarchical processing
294 println!("\nHierarchical processing (coarse to fine):");
295 let coarse: Tensor = tensor
296 .iter()
297 .enumerate()
298 .filter(|(i, _)| i % 4 == 0) // Take every 4th element
299 .map(|(_, elem)| elem)
300 .collect();
301
302 let fine: Tensor = tensor
303 .iter()
304 .enumerate()
305 .filter(|(i, _)| i % 4 != 0) // Take the rest
306 .map(|(_, elem)| elem)
307 .collect();
308
309 println!(" Coarse (every 4th): {:?}", coarse.data());
310 println!(" Fine (rest): {:?}", fine.data());
311
312 // Combine coarse and fine with different processing
313 let combined: Tensor = coarse
314 .iter()
315 .map(|elem| elem.mul_scalar(2.0)) // Scale coarse
316 .chain(fine.iter().map(|elem| elem.div_scalar(2.0))) // Scale fine
317 .collect();
318 println!(" Combined: {:?}", combined.data());
319
320 Ok(())
321}
322
323/// Demonstrate real-world processing scenarios
324///
325/// Shows practical applications of iterator patterns for
326/// common data processing tasks in machine learning and analytics.
327fn demonstrate_real_world_scenarios() -> Result<(), Box<dyn std::error::Error>> {
328 println!("\n--- Real-world Scenarios ---");
329
330 // Scenario 1: Time series analysis
331 println!("\nScenario 1: Time Series Analysis");
332 let time_series: Vec<f32> = (0..24)
333 .map(|hour| {
334 let base = 20.0 + 10.0 * (hour as f32 * std::f32::consts::PI / 12.0).sin();
335 base + (hour % 3) as f32 * 2.0 // Add some noise
336 })
337 .collect();
338
339 let series = Tensor::from_slice(&time_series, vec![24])?;
340 println!(" Time series (24 hours): {:?}", series.data());
341
342 // Calculate moving average
343 let window_size = 3;
344 let moving_avg: Tensor = series
345 .iter()
346 .enumerate()
347 .map(|(i, _)| {
348 let start = i.saturating_sub(window_size / 2);
349 let end = (i + window_size / 2 + 1).min(series.size());
350 let window = series.iter_range(start, end);
351 window.fold(0.0, |acc, elem| acc + elem.value()) / (end - start) as f32
352 })
353 .map(|val| Tensor::from_slice(&[val], vec![1]).unwrap())
354 .collect();
355 println!(
356 " Moving average (window={}): {:?}",
357 window_size,
358 moving_avg.data()
359 );
360
361 // Scenario 2: Feature engineering
362 println!("\nScenario 2: Feature Engineering");
363 let features = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
364 println!(" Original features: {:?}", features.data());
365
366 // Create polynomial features
367 let poly_features: Tensor = features
368 .iter()
369 .flat_map(|elem| {
370 vec![
371 elem.clone(), // x^1
372 elem.pow_scalar(2.0), // x^2
373 elem.pow_scalar(3.0), // x^3
374 ]
375 })
376 .collect();
377 println!(
378 " Polynomial features (x, x^2, x^3): {:?}",
379 poly_features.data()
380 );
381
382 // Scenario 3: Data augmentation
383 println!("\nScenario 3: Data Augmentation");
384 let original = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?;
385 println!(" Original data: {:?}", original.data());
386
387 // Augment with noise and scaling
388 let augmented: Tensor = original
389 .iter()
390 .flat_map(|elem| {
391 vec![
392 elem.clone(), // Original
393 elem.add_scalar(0.1), // Add noise
394 elem.sub_scalar(0.1), // Subtract noise
395 elem.mul_scalar(1.1), // Scale up
396 elem.mul_scalar(0.9), // Scale down
397 ]
398 })
399 .collect();
400 println!(" Augmented data: {:?}", augmented.data());
401
402 // Scenario 4: Statistical analysis
403 println!("\nScenario 4: Statistical Analysis");
404 let sample_data = Tensor::from_slice(&[1.1, 2.3, 1.8, 2.1, 1.9, 2.0, 1.7, 2.2], vec![8])?;
405 println!(" Sample data: {:?}", sample_data.data());
406
407 // Calculate various statistics
408 let mean = sample_data.mean().value();
409 let std = sample_data.std().value();
410 let min = sample_data
411 .iter()
412 .map(|e| e.value())
413 .fold(f32::INFINITY, f32::min);
414 let max = sample_data
415 .iter()
416 .map(|e| e.value())
417 .fold(f32::NEG_INFINITY, f32::max);
418
419 // Z-score normalization
420 let z_scores: Tensor = sample_data
421 .iter()
422 .map(|elem| elem.sub_scalar(mean).div_scalar(std))
423 .collect();
424
425 println!(
426 " Statistics: mean={:.3}, std={:.3}, min={:.3}, max={:.3}",
427 mean, std, min, max
428 );
429 println!(" Z-scores: {:?}", z_scores.data());
430
431 Ok(())
432}Sourcepub fn iter_range(&self, start: usize, end: usize) -> TensorElementIterator<'_>
pub fn iter_range(&self, start: usize, end: usize) -> TensorElementIterator<'_>
Create an iterator over a range of elements
Creates an iterator that yields view tensors for elements in the specified range. The range is automatically clamped to valid tensor bounds for safety.
§Arguments
start- Starting index (inclusive)end- Ending index (exclusive)
§Returns
An iterator that yields view tensors for elements in the specified range
§Safety
The range is automatically clamped to valid tensor bounds:
startis clamped to[0, tensor.size()]endis clamped to[start, tensor.size()]- Empty ranges (start >= end) are handled gracefully
§Performance
- O(1) Creation: Constant-time iterator initialization
- Bounds Checking: Automatic range validation and clamping
- Zero-Copy Views: Each element is a view sharing memory with source
- Memory Efficient: Minimal overhead for range iteration
§Examples
§Basic Range Iteration
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5]).unwrap();
let middle: Tensor = tensor.iter_range(1, 4)
.map(|elem| elem.mul_scalar(2.0))
.collect();
assert_eq!(middle.data(), &[4.0, 6.0, 8.0]);§Range with Operations
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5]).unwrap();
// Apply complex operations to range
let result: Tensor = tensor.iter_range(0, 3)
.enumerate()
.map(|(i, elem)| elem.add_scalar(i as f32))
.collect();
assert_eq!(result.data(), &[1.0, 3.0, 5.0]);§Out of Bounds Handling
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
// Out of bounds range is clamped
let empty: Tensor = tensor.iter_range(5, 10).collect();
assert_eq!(empty.size(), 0);
// Partial out of bounds
let partial: Tensor = tensor.iter_range(1, 10).collect();
assert_eq!(partial.data(), &[2.0, 3.0]);Examples found in repository?
135fn demonstrate_memory_optimization() -> Result<(), Box<dyn std::error::Error>> {
136 println!("\n--- Memory Optimization ---");
137
138 // Create a large tensor for memory testing
139 let size = 10000;
140 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
141 let tensor = Tensor::from_slice(&data, vec![size])?;
142
143 println!("Processing tensor of size: {}", size);
144
145 // Pattern 1: Streaming processing (process in chunks)
146 println!("\nPattern 1: Streaming Processing");
147 let chunk_size = 1000;
148 let start = Instant::now();
149
150 let mut streamed_result = Vec::new();
151 for chunk_start in (0..size).step_by(chunk_size) {
152 let chunk_end = (chunk_start + chunk_size).min(size);
153 let chunk: Tensor = tensor
154 .iter_range(chunk_start, chunk_end)
155 .map(|elem| elem.pow_scalar(2.0).sqrt())
156 .collect();
157 streamed_result.extend(chunk.data().iter().cloned());
158 }
159 let streamed_time = start.elapsed();
160
161 // Pattern 2: Full processing
162 let start = Instant::now();
163 let _full_result: Tensor = tensor
164 .iter()
165 .map(|elem| elem.pow_scalar(2.0).sqrt())
166 .collect();
167 let full_time = start.elapsed();
168
169 println!(" Streaming time: {:?}", streamed_time);
170 println!(" Full processing time: {:?}", full_time);
171 println!(
172 " Memory efficiency ratio: {:.2}x",
173 full_time.as_nanos() as f64 / streamed_time.as_nanos() as f64
174 );
175
176 // Pattern 3: Lazy evaluation with take
177 println!("\nPattern 2: Lazy Evaluation");
178 let start = Instant::now();
179 let lazy_result: Tensor = tensor
180 .iter()
181 .take(1000) // Only process first 1000 elements
182 .map(|elem| elem.pow_scalar(2.0).sqrt())
183 .collect();
184 let lazy_time = start.elapsed();
185
186 println!(" Lazy processing (1000 elements): {:?}", lazy_time);
187 println!(" Lazy result size: {}", lazy_result.size());
188
189 // Pattern 4: Memory-efficient filtering
190 println!("\nPattern 3: Memory-Efficient Filtering");
191 let start = Instant::now();
192 let filtered_result: Tensor = tensor
193 .iter()
194 .filter(|elem| elem.value() > size as f32 / 2.0) // Keep only large values
195 .map(|elem| elem.mul_scalar(2.0))
196 .collect();
197 let filtered_time = start.elapsed();
198
199 println!(" Filtered processing: {:?}", filtered_time);
200 println!(
201 " Filtered result size: {} (reduced from {})",
202 filtered_result.size(),
203 size
204 );
205
206 Ok(())
207}
208
209/// Demonstrate large-scale processing techniques
210///
211/// Shows how to efficiently process very large datasets using
212/// iterator patterns and optimization strategies.
213fn demonstrate_large_scale_processing() -> Result<(), Box<dyn std::error::Error>> {
214 println!("\n--- Large-Scale Processing ---");
215
216 // Simulate large dataset processing
217 let sizes = vec![10000, 50000, 100000];
218
219 for size in sizes {
220 println!("\nProcessing dataset of size: {}", size);
221
222 // Generate large dataset
223 let data: Vec<f32> = (0..size)
224 .map(|i| {
225 let x = i as f32 / size as f32;
226 x * x + 0.1 * (i % 10) as f32 // Quadratic with noise
227 })
228 .collect();
229
230 let tensor = Tensor::from_slice(&data, vec![size])?;
231
232 // Technique 1: Batch processing
233 let batch_size = 1000;
234 let start = Instant::now();
235
236 let mut batch_results = Vec::new();
237 for batch_start in (0..size).step_by(batch_size) {
238 let batch_end = (batch_start + batch_size).min(size);
239 let batch: Tensor = tensor
240 .iter_range(batch_start, batch_end)
241 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
242 .collect();
243 batch_results.push(batch);
244 }
245 let batch_time = start.elapsed();
246
247 // Technique 2: Parallel-like processing with stride
248 let start = Instant::now();
249 let stride = 4;
250 let strided_result: Tensor = tensor
251 .iter()
252 .enumerate()
253 .filter(|(i, _)| i % stride == 0)
254 .map(|(_, elem)| elem.pow_scalar(2.0).add_scalar(1.0))
255 .collect();
256 let strided_time = start.elapsed();
257
258 // Technique 3: Hierarchical processing
259 let start = Instant::now();
260 let coarse: Tensor = tensor
261 .iter()
262 .enumerate()
263 .filter(|(i, _)| i % 10 == 0) // Every 10th element
264 .map(|(_, elem)| elem.pow_scalar(2.0).add_scalar(1.0))
265 .collect();
266 let fine: Tensor = tensor
267 .iter()
268 .enumerate()
269 .filter(|(i, _)| i % 10 != 0) // Rest of elements
270 .map(|(_, elem)| elem.pow_scalar(1.5).add_scalar(0.5))
271 .collect();
272 let hierarchical_time = start.elapsed();
273
274 // Report performance
275 println!(" Batch processing: {:?}", batch_time);
276 println!(" Strided processing: {:?}", strided_time);
277 println!(" Hierarchical processing: {:?}", hierarchical_time);
278
279 // Memory usage analysis
280 let total_batches = (size + batch_size - 1) / batch_size;
281 println!(" Batch count: {}", total_batches);
282 println!(" Strided result size: {}", strided_result.size());
283 println!(
284 " Hierarchical: coarse={}, fine={}",
285 coarse.size(),
286 fine.size()
287 );
288 }
289
290 Ok(())
291}More examples
327fn demonstrate_real_world_scenarios() -> Result<(), Box<dyn std::error::Error>> {
328 println!("\n--- Real-world Scenarios ---");
329
330 // Scenario 1: Time series analysis
331 println!("\nScenario 1: Time Series Analysis");
332 let time_series: Vec<f32> = (0..24)
333 .map(|hour| {
334 let base = 20.0 + 10.0 * (hour as f32 * std::f32::consts::PI / 12.0).sin();
335 base + (hour % 3) as f32 * 2.0 // Add some noise
336 })
337 .collect();
338
339 let series = Tensor::from_slice(&time_series, vec![24])?;
340 println!(" Time series (24 hours): {:?}", series.data());
341
342 // Calculate moving average
343 let window_size = 3;
344 let moving_avg: Tensor = series
345 .iter()
346 .enumerate()
347 .map(|(i, _)| {
348 let start = i.saturating_sub(window_size / 2);
349 let end = (i + window_size / 2 + 1).min(series.size());
350 let window = series.iter_range(start, end);
351 window.fold(0.0, |acc, elem| acc + elem.value()) / (end - start) as f32
352 })
353 .map(|val| Tensor::from_slice(&[val], vec![1]).unwrap())
354 .collect();
355 println!(
356 " Moving average (window={}): {:?}",
357 window_size,
358 moving_avg.data()
359 );
360
361 // Scenario 2: Feature engineering
362 println!("\nScenario 2: Feature Engineering");
363 let features = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
364 println!(" Original features: {:?}", features.data());
365
366 // Create polynomial features
367 let poly_features: Tensor = features
368 .iter()
369 .flat_map(|elem| {
370 vec![
371 elem.clone(), // x^1
372 elem.pow_scalar(2.0), // x^2
373 elem.pow_scalar(3.0), // x^3
374 ]
375 })
376 .collect();
377 println!(
378 " Polynomial features (x, x^2, x^3): {:?}",
379 poly_features.data()
380 );
381
382 // Scenario 3: Data augmentation
383 println!("\nScenario 3: Data Augmentation");
384 let original = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?;
385 println!(" Original data: {:?}", original.data());
386
387 // Augment with noise and scaling
388 let augmented: Tensor = original
389 .iter()
390 .flat_map(|elem| {
391 vec![
392 elem.clone(), // Original
393 elem.add_scalar(0.1), // Add noise
394 elem.sub_scalar(0.1), // Subtract noise
395 elem.mul_scalar(1.1), // Scale up
396 elem.mul_scalar(0.9), // Scale down
397 ]
398 })
399 .collect();
400 println!(" Augmented data: {:?}", augmented.data());
401
402 // Scenario 4: Statistical analysis
403 println!("\nScenario 4: Statistical Analysis");
404 let sample_data = Tensor::from_slice(&[1.1, 2.3, 1.8, 2.1, 1.9, 2.0, 1.7, 2.2], vec![8])?;
405 println!(" Sample data: {:?}", sample_data.data());
406
407 // Calculate various statistics
408 let mean = sample_data.mean().value();
409 let std = sample_data.std().value();
410 let min = sample_data
411 .iter()
412 .map(|e| e.value())
413 .fold(f32::INFINITY, f32::min);
414 let max = sample_data
415 .iter()
416 .map(|e| e.value())
417 .fold(f32::NEG_INFINITY, f32::max);
418
419 // Z-score normalization
420 let z_scores: Tensor = sample_data
421 .iter()
422 .map(|elem| elem.sub_scalar(mean).div_scalar(std))
423 .collect();
424
425 println!(
426 " Statistics: mean={:.3}, std={:.3}, min={:.3}, max={:.3}",
427 mean, std, min, max
428 );
429 println!(" Z-scores: {:?}", z_scores.data());
430
431 Ok(())
432}Source§impl Tensor
impl Tensor
Sourcepub fn add_tensor(&self, other: &Tensor) -> Tensor
pub fn add_tensor(&self, other: &Tensor) -> Tensor
Element-wise addition with another tensor with broadcasting support.
Performs element-wise addition with automatic broadcasting: output[i] = self[i] + other[i]
Broadcasting enables addition between tensors of different but compatible shapes. Compatible shapes follow NumPy broadcasting rules:
- Dimensions are aligned from the rightmost dimension
- Dimensions are compatible if they are equal, or one of them is 1
- Missing dimensions are treated as 1
§Arguments
other- Tensor to add. Shapes must be broadcast-compatible.
§Returns
A new tensor containing the element-wise sum with broadcast result shape
§Examples
§Same Shape Addition
use train_station::Tensor;
let a = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let b = Tensor::from_slice(&[4.0, 5.0, 6.0], vec![3]).unwrap();
let c = a.add_tensor(&b);
assert_eq!(c.shape().dims, vec![3]);
assert_eq!(c.get(&[0]), 5.0);
assert_eq!(c.get(&[1]), 7.0);
assert_eq!(c.get(&[2]), 9.0);§Broadcasting Addition
use train_station::Tensor;
// Broadcasting: [2, 1] + [1, 3] -> [2, 3]
let a = Tensor::from_slice(&[1.0, 2.0], vec![2, 1]).unwrap();
let b = Tensor::from_slice(&[10.0, 20.0, 30.0], vec![1, 3]).unwrap();
let c = a.add_tensor(&b);
assert_eq!(c.shape().dims, vec![2, 3]);
assert_eq!(c.get(&[0, 0]), 11.0);
assert_eq!(c.get(&[0, 1]), 21.0);
assert_eq!(c.get(&[1, 0]), 12.0);
assert_eq!(c.get(&[1, 1]), 22.0);§Scalar Broadcasting
use train_station::Tensor;
// Scalar broadcasting: [2, 3] + scalar -> [2, 3]
let a = Tensor::ones(vec![2, 3]);
let b = Tensor::from_slice(&[5.0], vec![1]).unwrap();
let c = a.add_tensor(&b);
assert_eq!(c.shape().dims, vec![2, 3]);
assert_eq!(c.get(&[0, 0]), 6.0);
assert_eq!(c.get(&[1, 2]), 6.0);§Panics
Panics if tensor shapes are not broadcast-compatible
Examples found in repository?
More examples
89fn demonstrate_basic_operations() {
90 println!("\n--- Basic Operations ---");
91
92 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
93 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
94
95 // Addition
96 let sum = a.add_tensor(&b);
97 println!("A + B: {:?}", sum.data());
98
99 // Subtraction
100 let diff = a.sub_tensor(&b);
101 println!("A - B: {:?}", diff.data());
102
103 // Multiplication
104 let product = a.mul_tensor(&b);
105 println!("A * B: {:?}", product.data());
106
107 // Division
108 let quotient = a.div_tensor(&b);
109 println!("A / B: {:?}", quotient.data());
110
111 // Scalar operations
112 let scalar_add = a.add_scalar(5.0);
113 println!("A + 5.0: {:?}", scalar_add.data());
114
115 let scalar_mul = a.mul_scalar(2.0);
116 println!("A * 2.0: {:?}", scalar_mul.data());
117}203fn demonstrate_method_equivalence() {
204 println!("\n--- Operator vs Method Call Equivalence ---");
205
206 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
207 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
208
209 // Addition: operator vs method
210 let operator_result = &a + &b;
211 let method_result = a.add_tensor(&b);
212
213 println!("A + B (operator): {:?}", operator_result.data());
214 println!("A.add_tensor(B): {:?}", method_result.data());
215 println!(
216 "Results are equal: {}",
217 operator_result.data() == method_result.data()
218 );
219
220 // Multiplication: operator vs method
221 let operator_result = &a * &b;
222 let method_result = a.mul_tensor(&b);
223
224 println!("A * B (operator): {:?}", operator_result.data());
225 println!("A.mul_tensor(B): {:?}", method_result.data());
226 println!(
227 "Results are equal: {}",
228 operator_result.data() == method_result.data()
229 );
230
231 // Scalar addition: operator vs method
232 let operator_result = &a + 5.0;
233 let method_result = a.add_scalar(5.0);
234
235 println!("A + 5.0 (operator): {:?}", operator_result.data());
236 println!("A.add_scalar(5.0): {:?}", method_result.data());
237 println!(
238 "Results are equal: {}",
239 operator_result.data() == method_result.data()
240 );
241}74fn demonstrate_data_pipeline() -> Result<(), Box<dyn std::error::Error>> {
75 println!("\n--- Data Processing Pipeline ---");
76
77 // Simulate raw sensor data with noise
78 let raw_data: Vec<f32> = (0..20)
79 .map(|i| {
80 let base = i as f32 * 0.5;
81 let noise = (i % 3) as f32 * 0.1;
82 base + noise
83 })
84 .collect();
85
86 let tensor = Tensor::from_slice(&raw_data, vec![20])?;
87 println!("Raw sensor data: {:?}", tensor.data());
88
89 // Multi-stage processing pipeline
90 println!("\nProcessing pipeline:");
91 println!("1. Normalize data (z-score)");
92 println!("2. Apply smoothing filter");
93 println!("3. Detect outliers");
94 println!("4. Apply feature scaling");
95
96 // Stage 1: Normalization
97 let mean = tensor.mean().value();
98 let std = tensor.std().value();
99 let normalized: Tensor = tensor
100 .iter()
101 .map(|elem| elem.sub_scalar(mean).div_scalar(std))
102 .collect();
103 println!(
104 " Normalized (mean={:.3}, std={:.3}): {:?}",
105 mean,
106 std,
107 normalized.data()
108 );
109
110 // Stage 2: Smoothing (simple moving average)
111 let smoothed: Tensor = normalized
112 .iter()
113 .enumerate()
114 .map(|(i, elem)| {
115 if i == 0 || i == normalized.size() - 1 {
116 elem.clone()
117 } else {
118 // Simple 3-point average
119 let prev = normalized.element_view(i - 1);
120 let next = normalized.element_view(i + 1);
121 elem.add_tensor(&prev).add_tensor(&next).div_scalar(3.0)
122 }
123 })
124 .collect();
125 println!(" Smoothed: {:?}", smoothed.data());
126
127 // Stage 3: Outlier detection and removal
128 let outlier_threshold = 2.0;
129 let cleaned: Tensor = smoothed
130 .iter()
131 .filter(|elem| elem.value().abs() < outlier_threshold)
132 .collect();
133 println!(
134 " Outliers removed (threshold={}): {:?}",
135 outlier_threshold,
136 cleaned.data()
137 );
138
139 // Stage 4: Feature scaling to [0, 1] range
140 let min_val = cleaned
141 .iter()
142 .map(|e| e.value())
143 .fold(f32::INFINITY, f32::min);
144 let max_val = cleaned
145 .iter()
146 .map(|e| e.value())
147 .fold(f32::NEG_INFINITY, f32::max);
148 let scaled: Tensor = cleaned
149 .iter()
150 .map(|elem| elem.sub_scalar(min_val).div_scalar(max_val - min_val))
151 .collect();
152 println!(" Scaled to [0,1]: {:?}", scaled.data());
153
154 Ok(())
155}Sourcepub fn add_scalar(&self, scalar: f32) -> Tensor
pub fn add_scalar(&self, scalar: f32) -> Tensor
Broadcast addition with a scalar value.
Adds the scalar to every element: output[i] = self[i] + scalar
§Arguments
scalar- Value to add to each element
§Returns
A new tensor with the scalar added to each element
§Examples
§Basic Scalar Addition
use train_station::Tensor;
let a = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let b = a.add_scalar(10.0);
assert_eq!(b.shape().dims, vec![3]);
assert_eq!(b.get(&[0]), 11.0);
assert_eq!(b.get(&[1]), 12.0);
assert_eq!(b.get(&[2]), 13.0);§Multi-dimensional Scalar Addition
use train_station::Tensor;
let a = Tensor::ones(vec![2, 3]);
let b = a.add_scalar(5.0);
assert_eq!(b.shape().dims, vec![2, 3]);
assert_eq!(b.get(&[0, 0]), 6.0);
assert_eq!(b.get(&[1, 2]), 6.0);Examples found in repository?
89fn demonstrate_basic_operations() {
90 println!("\n--- Basic Operations ---");
91
92 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
93 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
94
95 // Addition
96 let sum = a.add_tensor(&b);
97 println!("A + B: {:?}", sum.data());
98
99 // Subtraction
100 let diff = a.sub_tensor(&b);
101 println!("A - B: {:?}", diff.data());
102
103 // Multiplication
104 let product = a.mul_tensor(&b);
105 println!("A * B: {:?}", product.data());
106
107 // Division
108 let quotient = a.div_tensor(&b);
109 println!("A / B: {:?}", quotient.data());
110
111 // Scalar operations
112 let scalar_add = a.add_scalar(5.0);
113 println!("A + 5.0: {:?}", scalar_add.data());
114
115 let scalar_mul = a.mul_scalar(2.0);
116 println!("A * 2.0: {:?}", scalar_mul.data());
117}More examples
77fn demonstrate_basic_iteration() -> Result<(), Box<dyn std::error::Error>> {
78 println!("\n--- Basic Element Iteration ---");
79
80 // Create a simple tensor for demonstration
81 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
82 println!("Original tensor: {:?}", tensor.data());
83
84 // Basic iteration with for loop
85 println!("\nBasic iteration with for loop:");
86 for (i, element) in tensor.iter().enumerate() {
87 println!(
88 " Element {}: value = {:.1}, shape = {:?}",
89 i,
90 element.value(),
91 element.shape().dims
92 );
93 }
94
95 // Element-wise transformation
96 println!("\nElement-wise transformation (2x + 1):");
97 let transformed: Tensor = tensor
98 .iter()
99 .map(|elem| elem.mul_scalar(2.0).add_scalar(1.0))
100 .collect();
101 println!(" Result: {:?}", transformed.data());
102
103 // Filtering elements
104 println!("\nFiltering elements (values > 3.0):");
105 let filtered: Tensor = tensor.iter().filter(|elem| elem.value() > 3.0).collect();
106 println!(" Filtered: {:?}", filtered.data());
107
108 Ok(())
109}
110
111/// Demonstrate standard iterator trait methods
112///
113/// Shows compatibility with Rust's standard library iterator methods
114/// and demonstrates various functional programming patterns.
115fn demonstrate_standard_methods() -> Result<(), Box<dyn std::error::Error>> {
116 println!("\n--- Standard Iterator Methods ---");
117
118 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
119
120 // Using map for transformations
121 println!("\nMap transformation (square each element):");
122 let squared: Tensor = tensor.iter().map(|elem| elem.pow_scalar(2.0)).collect();
123 println!(" Squared: {:?}", squared.data());
124
125 // Using enumerate for indexed operations
126 println!("\nEnumerate with indexed operations:");
127 let indexed: Tensor = tensor
128 .iter()
129 .enumerate()
130 .map(|(i, elem)| elem.add_scalar(i as f32))
131 .collect();
132 println!(" Indexed: {:?}", indexed.data());
133
134 // Using fold for reduction
135 println!("\nFold for sum calculation:");
136 let sum: f32 = tensor.iter().fold(0.0, |acc, elem| acc + elem.value());
137 println!(" Sum: {:.1}", sum);
138
139 // Using find for element search
140 println!("\nFind specific element:");
141 if let Some(found) = tensor.iter().find(|elem| elem.value() == 3.0) {
142 println!(" Found element with value 3.0: {:.1}", found.value());
143 }
144
145 // Using any/all for condition checking
146 println!("\nCondition checking:");
147 let all_positive = tensor.iter().all(|elem| elem.value() > 0.0);
148 let any_large = tensor.iter().any(|elem| elem.value() > 4.0);
149 println!(" All positive: {}", all_positive);
150 println!(" Any > 4.0: {}", any_large);
151
152 Ok(())
153}
154
155/// Demonstrate gradient tracking through element operations
156///
157/// Shows how gradient tracking works seamlessly through iterator
158/// operations, maintaining the computational graph for backpropagation.
159fn demonstrate_gradient_tracking() -> Result<(), Box<dyn std::error::Error>> {
160 println!("\n--- Gradient Tracking ---");
161
162 // Create a tensor with gradient tracking enabled
163 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?.with_requires_grad();
164 println!("Input tensor (requires_grad): {:?}", tensor.data());
165
166 // Perform element-wise operations through iteration
167 let result: Tensor = tensor
168 .iter()
169 .map(|elem| {
170 // Apply a complex transformation: (x^2 + 1) * 2
171 elem.pow_scalar(2.0).add_scalar(1.0).mul_scalar(2.0)
172 })
173 .collect();
174
175 println!("Result tensor: {:?}", result.data());
176 println!("Result requires_grad: {}", result.requires_grad());
177
178 // Compute gradients
179 let mut loss = result.sum();
180 loss.backward(None);
181
182 println!("Loss: {:.6}", loss.value());
183 println!("Input gradients: {:?}", tensor.grad().map(|g| g.data()));
184
185 Ok(())
186}
187
188/// Demonstrate advanced iterator patterns
189///
190/// Shows complex iterator chains and advanced functional programming
191/// patterns for sophisticated data processing workflows.
192fn demonstrate_advanced_patterns() -> Result<(), Box<dyn std::error::Error>> {
193 println!("\n--- Advanced Iterator Patterns ---");
194
195 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![6])?;
196 println!("Input tensor: {:?}", tensor.data());
197
198 // Complex chain: enumerate -> filter -> map -> collect
199 println!("\nComplex chain (even indices only, add index to value):");
200 let result: Tensor = tensor
201 .iter()
202 .enumerate()
203 .filter(|(i, _)| i % 2 == 0) // Take even indices
204 .map(|(i, elem)| elem.add_scalar(i as f32)) // Add index to value
205 .collect();
206 println!(" Result: {:?}", result.data());
207
208 // Using take and skip for windowing
209 println!("\nWindowing with take and skip:");
210 let window1: Tensor = tensor.iter().take(3).collect();
211 let window2: Tensor = tensor.iter().skip(2).take(3).collect();
212 println!(" Window 1 (first 3): {:?}", window1.data());
213 println!(" Window 2 (middle 3): {:?}", window2.data());
214
215 // Using rev() for reverse iteration
216 println!("\nReverse iteration:");
217 let reversed: Tensor = tensor.iter().rev().collect();
218 println!(" Reversed: {:?}", reversed.data());
219
220 // Chaining with mathematical operations
221 println!("\nMathematical operation chain:");
222 let math_result: Tensor = tensor
223 .iter()
224 .map(|elem| elem.exp()) // e^x
225 .filter(|elem| elem.value() < 50.0) // Filter large values
226 .map(|elem| elem.log()) // ln(x)
227 .collect();
228 println!(" Math chain result: {:?}", math_result.data());
229
230 // Using zip for element-wise combinations
231 println!("\nElement-wise combination with zip:");
232 let tensor2 = Tensor::from_slice(&[10.0, 20.0, 30.0, 40.0, 50.0, 60.0], vec![6])?;
233 let combined: Tensor = tensor
234 .iter()
235 .zip(tensor2.iter())
236 .map(|(a, b)| a.mul_tensor(&b)) // Element-wise multiplication
237 .collect();
238 println!(" Combined: {:?}", combined.data());
239
240 Ok(())
241}203fn demonstrate_method_equivalence() {
204 println!("\n--- Operator vs Method Call Equivalence ---");
205
206 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
207 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
208
209 // Addition: operator vs method
210 let operator_result = &a + &b;
211 let method_result = a.add_tensor(&b);
212
213 println!("A + B (operator): {:?}", operator_result.data());
214 println!("A.add_tensor(B): {:?}", method_result.data());
215 println!(
216 "Results are equal: {}",
217 operator_result.data() == method_result.data()
218 );
219
220 // Multiplication: operator vs method
221 let operator_result = &a * &b;
222 let method_result = a.mul_tensor(&b);
223
224 println!("A * B (operator): {:?}", operator_result.data());
225 println!("A.mul_tensor(B): {:?}", method_result.data());
226 println!(
227 "Results are equal: {}",
228 operator_result.data() == method_result.data()
229 );
230
231 // Scalar addition: operator vs method
232 let operator_result = &a + 5.0;
233 let method_result = a.add_scalar(5.0);
234
235 println!("A + 5.0 (operator): {:?}", operator_result.data());
236 println!("A.add_scalar(5.0): {:?}", method_result.data());
237 println!(
238 "Results are equal: {}",
239 operator_result.data() == method_result.data()
240 );
241}75fn demonstrate_performance_benchmarking() -> Result<(), Box<dyn std::error::Error>> {
76 println!("\n--- Performance Benchmarking ---");
77
78 // Create test data of different sizes
79 let sizes = vec![100, 1000, 10000];
80
81 for size in sizes {
82 println!("\nBenchmarking with tensor size: {}", size);
83
84 // Generate test data
85 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
86 let tensor = Tensor::from_slice(&data, vec![size])?;
87
88 // Benchmark 1: Direct tensor operations
89 let start = Instant::now();
90 let direct_result = tensor.mul_scalar(2.0).add_scalar(1.0);
91 let direct_time = start.elapsed();
92
93 // Benchmark 2: Iterator-based operations
94 let start = Instant::now();
95 let iterator_result: Tensor = tensor
96 .iter()
97 .map(|elem| elem.mul_scalar(2.0).add_scalar(1.0))
98 .collect();
99 let iterator_time = start.elapsed();
100
101 // Benchmark 3: Chained iterator operations
102 let start = Instant::now();
103 let _chained_result: Tensor = tensor
104 .iter()
105 .map(|elem| elem.mul_scalar(2.0))
106 .filter(|elem| elem.value() > size as f32)
107 .map(|elem| elem.add_scalar(1.0))
108 .collect();
109 let chained_time = start.elapsed();
110
111 // Report results
112 println!(" Direct operations: {:?}", direct_time);
113 println!(" Iterator operations: {:?}", iterator_time);
114 println!(" Chained operations: {:?}", chained_time);
115
116 // Verify correctness
117 assert_eq!(direct_result.data(), iterator_result.data());
118 println!(
119 " Results match: {}",
120 direct_result.data() == iterator_result.data()
121 );
122
123 // Performance ratio
124 let ratio = iterator_time.as_nanos() as f64 / direct_time.as_nanos() as f64;
125 println!(" Iterator/Direct ratio: {:.2}x", ratio);
126 }
127
128 Ok(())
129}
130
131/// Demonstrate memory optimization patterns
132///
133/// Shows memory-efficient processing patterns and techniques
134/// for minimizing memory usage while maintaining performance.
135fn demonstrate_memory_optimization() -> Result<(), Box<dyn std::error::Error>> {
136 println!("\n--- Memory Optimization ---");
137
138 // Create a large tensor for memory testing
139 let size = 10000;
140 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
141 let tensor = Tensor::from_slice(&data, vec![size])?;
142
143 println!("Processing tensor of size: {}", size);
144
145 // Pattern 1: Streaming processing (process in chunks)
146 println!("\nPattern 1: Streaming Processing");
147 let chunk_size = 1000;
148 let start = Instant::now();
149
150 let mut streamed_result = Vec::new();
151 for chunk_start in (0..size).step_by(chunk_size) {
152 let chunk_end = (chunk_start + chunk_size).min(size);
153 let chunk: Tensor = tensor
154 .iter_range(chunk_start, chunk_end)
155 .map(|elem| elem.pow_scalar(2.0).sqrt())
156 .collect();
157 streamed_result.extend(chunk.data().iter().cloned());
158 }
159 let streamed_time = start.elapsed();
160
161 // Pattern 2: Full processing
162 let start = Instant::now();
163 let _full_result: Tensor = tensor
164 .iter()
165 .map(|elem| elem.pow_scalar(2.0).sqrt())
166 .collect();
167 let full_time = start.elapsed();
168
169 println!(" Streaming time: {:?}", streamed_time);
170 println!(" Full processing time: {:?}", full_time);
171 println!(
172 " Memory efficiency ratio: {:.2}x",
173 full_time.as_nanos() as f64 / streamed_time.as_nanos() as f64
174 );
175
176 // Pattern 3: Lazy evaluation with take
177 println!("\nPattern 2: Lazy Evaluation");
178 let start = Instant::now();
179 let lazy_result: Tensor = tensor
180 .iter()
181 .take(1000) // Only process first 1000 elements
182 .map(|elem| elem.pow_scalar(2.0).sqrt())
183 .collect();
184 let lazy_time = start.elapsed();
185
186 println!(" Lazy processing (1000 elements): {:?}", lazy_time);
187 println!(" Lazy result size: {}", lazy_result.size());
188
189 // Pattern 4: Memory-efficient filtering
190 println!("\nPattern 3: Memory-Efficient Filtering");
191 let start = Instant::now();
192 let filtered_result: Tensor = tensor
193 .iter()
194 .filter(|elem| elem.value() > size as f32 / 2.0) // Keep only large values
195 .map(|elem| elem.mul_scalar(2.0))
196 .collect();
197 let filtered_time = start.elapsed();
198
199 println!(" Filtered processing: {:?}", filtered_time);
200 println!(
201 " Filtered result size: {} (reduced from {})",
202 filtered_result.size(),
203 size
204 );
205
206 Ok(())
207}
208
209/// Demonstrate large-scale processing techniques
210///
211/// Shows how to efficiently process very large datasets using
212/// iterator patterns and optimization strategies.
213fn demonstrate_large_scale_processing() -> Result<(), Box<dyn std::error::Error>> {
214 println!("\n--- Large-Scale Processing ---");
215
216 // Simulate large dataset processing
217 let sizes = vec![10000, 50000, 100000];
218
219 for size in sizes {
220 println!("\nProcessing dataset of size: {}", size);
221
222 // Generate large dataset
223 let data: Vec<f32> = (0..size)
224 .map(|i| {
225 let x = i as f32 / size as f32;
226 x * x + 0.1 * (i % 10) as f32 // Quadratic with noise
227 })
228 .collect();
229
230 let tensor = Tensor::from_slice(&data, vec![size])?;
231
232 // Technique 1: Batch processing
233 let batch_size = 1000;
234 let start = Instant::now();
235
236 let mut batch_results = Vec::new();
237 for batch_start in (0..size).step_by(batch_size) {
238 let batch_end = (batch_start + batch_size).min(size);
239 let batch: Tensor = tensor
240 .iter_range(batch_start, batch_end)
241 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
242 .collect();
243 batch_results.push(batch);
244 }
245 let batch_time = start.elapsed();
246
247 // Technique 2: Parallel-like processing with stride
248 let start = Instant::now();
249 let stride = 4;
250 let strided_result: Tensor = tensor
251 .iter()
252 .enumerate()
253 .filter(|(i, _)| i % stride == 0)
254 .map(|(_, elem)| elem.pow_scalar(2.0).add_scalar(1.0))
255 .collect();
256 let strided_time = start.elapsed();
257
258 // Technique 3: Hierarchical processing
259 let start = Instant::now();
260 let coarse: Tensor = tensor
261 .iter()
262 .enumerate()
263 .filter(|(i, _)| i % 10 == 0) // Every 10th element
264 .map(|(_, elem)| elem.pow_scalar(2.0).add_scalar(1.0))
265 .collect();
266 let fine: Tensor = tensor
267 .iter()
268 .enumerate()
269 .filter(|(i, _)| i % 10 != 0) // Rest of elements
270 .map(|(_, elem)| elem.pow_scalar(1.5).add_scalar(0.5))
271 .collect();
272 let hierarchical_time = start.elapsed();
273
274 // Report performance
275 println!(" Batch processing: {:?}", batch_time);
276 println!(" Strided processing: {:?}", strided_time);
277 println!(" Hierarchical processing: {:?}", hierarchical_time);
278
279 // Memory usage analysis
280 let total_batches = (size + batch_size - 1) / batch_size;
281 println!(" Batch count: {}", total_batches);
282 println!(" Strided result size: {}", strided_result.size());
283 println!(
284 " Hierarchical: coarse={}, fine={}",
285 coarse.size(),
286 fine.size()
287 );
288 }
289
290 Ok(())
291}
292
293/// Demonstrate advanced optimization techniques
294///
295/// Shows sophisticated optimization strategies and techniques
296/// for maximizing performance in tensor iterator operations.
297fn demonstrate_optimization_techniques() -> Result<(), Box<dyn std::error::Error>> {
298 println!("\n--- Optimization Techniques ---");
299
300 let size = 50000;
301 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
302 let tensor = Tensor::from_slice(&data, vec![size])?;
303
304 println!("Optimizing processing for size: {}", size);
305
306 // Technique 1: Operation fusion
307 println!("\nTechnique 1: Operation Fusion");
308 let start = Instant::now();
309 let fused_result: Tensor = tensor
310 .iter()
311 .map(|elem| {
312 // Fuse multiple operations into single chain
313 elem.mul_scalar(2.0).add_scalar(1.0).pow_scalar(2.0).sqrt()
314 })
315 .collect();
316 let fused_time = start.elapsed();
317
318 // Technique 2: Conditional optimization
319 println!("\nTechnique 2: Conditional Optimization");
320 let start = Instant::now();
321 let conditional_result: Tensor = tensor
322 .iter()
323 .map(|elem| {
324 let val = elem.value();
325 if val < size as f32 / 2.0 {
326 elem.mul_scalar(2.0) // Simple operation for small values
327 } else {
328 elem.pow_scalar(2.0).sqrt() // Complex operation for large values
329 }
330 })
331 .collect();
332 let conditional_time = start.elapsed();
333
334 // Technique 3: Cache-friendly processing
335 println!("\nTechnique 3: Cache-Friendly Processing");
336 let start = Instant::now();
337 let cache_friendly_result: Tensor = tensor
338 .iter()
339 .take(1000) // Process in cache-friendly chunks
340 .map(|elem| elem.mul_scalar(2.0))
341 .collect();
342 let cache_friendly_time = start.elapsed();
343
344 // Technique 4: Memory pooling simulation
345 println!("\nTechnique 4: Memory Pooling Simulation");
346 let start = Instant::now();
347 let pooled_result: Tensor = tensor
348 .iter()
349 .enumerate()
350 .filter(|(i, _)| i % 100 == 0) // Process every 100th element
351 .map(|(_, elem)| elem.pow_scalar(2.0))
352 .collect();
353 let pooled_time = start.elapsed();
354
355 // Report optimization results
356 println!(" Fused operations: {:?}", fused_time);
357 println!(" Conditional optimization: {:?}", conditional_time);
358 println!(" Cache-friendly processing: {:?}", cache_friendly_time);
359 println!(" Memory pooling simulation: {:?}", pooled_time);
360
361 // Performance analysis
362 let fastest = fused_time
363 .min(conditional_time)
364 .min(cache_friendly_time)
365 .min(pooled_time);
366 println!(" Fastest technique: {:?}", fastest);
367
368 // Memory efficiency analysis
369 println!(" Fused result size: {}", fused_result.size());
370 println!(" Conditional result size: {}", conditional_result.size());
371 println!(
372 " Cache-friendly result size: {}",
373 cache_friendly_result.size()
374 );
375 println!(" Pooled result size: {}", pooled_result.size());
376
377 // Technique 5: Gradient optimization
378 println!("\nTechnique 5: Gradient Optimization");
379 let grad_tensor = tensor.with_requires_grad();
380 let start = Instant::now();
381
382 let grad_result: Tensor = grad_tensor
383 .iter()
384 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
385 .collect();
386
387 let mut loss = grad_result.sum();
388 loss.backward(None);
389 let grad_time = start.elapsed();
390
391 println!(" Gradient computation: {:?}", grad_time);
392 println!(
393 " Gradient tracking enabled: {}",
394 grad_result.requires_grad()
395 );
396
397 Ok(())
398}161fn demonstrate_conditional_processing() -> Result<(), Box<dyn std::error::Error>> {
162 println!("\n--- Conditional Processing ---");
163
164 // Create data with mixed characteristics
165 let data = vec![1.0, -2.0, 3.0, -4.0, 5.0, -6.0, 7.0, -8.0, 9.0, -10.0];
166 let tensor = Tensor::from_slice(&data, vec![10])?;
167 println!("Input data: {:?}", tensor.data());
168
169 // Conditional transformation based on sign
170 println!("\nConditional transformation (positive/negative handling):");
171 let processed: Tensor = tensor
172 .iter()
173 .map(|elem| {
174 let val = elem.value();
175 if val > 0.0 {
176 elem.pow_scalar(2.0) // Square positive values
177 } else {
178 elem.mul_scalar(-1.0).sqrt() // Square root of absolute negative values
179 }
180 })
181 .collect();
182 println!(" Processed: {:?}", processed.data());
183
184 // Adaptive filtering based on local statistics
185 println!("\nAdaptive filtering (remove values > 2 std from local mean):");
186 let window_size = 3;
187 let adaptive_filtered: Tensor = tensor
188 .iter()
189 .enumerate()
190 .filter(|(i, elem)| {
191 let start = i.saturating_sub(window_size / 2);
192 let end = (i + window_size / 2 + 1).min(tensor.size());
193
194 // Calculate local mean and std
195 let local_values: Vec<f32> = (start..end)
196 .map(|j| tensor.element_view(j).value())
197 .collect();
198
199 let local_mean = local_values.iter().sum::<f32>() / local_values.len() as f32;
200 let local_variance = local_values
201 .iter()
202 .map(|v| (v - local_mean).powi(2))
203 .sum::<f32>()
204 / local_values.len() as f32;
205 let local_std = local_variance.sqrt();
206
207 let threshold = local_mean + 2.0 * local_std;
208 elem.value() <= threshold
209 })
210 .map(|(_, elem)| elem)
211 .collect();
212 println!(" Adaptive filtered: {:?}", adaptive_filtered.data());
213
214 // Multi-condition processing
215 println!("\nMulti-condition processing:");
216 let multi_processed: Tensor = tensor
217 .iter()
218 .map(|elem| {
219 let val = elem.value();
220 match () {
221 _ if val > 5.0 => elem.mul_scalar(2.0), // Double large values
222 _ if val < -5.0 => elem.div_scalar(2.0), // Halve small values
223 _ if val.abs() < 2.0 => elem.add_scalar(1.0), // Add 1 to small values
224 _ => elem.clone(), // Keep others unchanged
225 }
226 })
227 .collect();
228 println!(" Multi-condition: {:?}", multi_processed.data());
229
230 Ok(())
231}
232
233/// Demonstrate batch processing operations
234///
235/// Shows efficient processing of large datasets using iterator
236/// patterns and batch operations for performance optimization.
237fn demonstrate_batch_operations() -> Result<(), Box<dyn std::error::Error>> {
238 println!("\n--- Batch Operations ---");
239
240 // Create a larger dataset for batch processing
241 let size = 100;
242 let data: Vec<f32> = (0..size)
243 .map(|i| {
244 let x = i as f32 / size as f32;
245 x * x + 0.1 * (i % 7) as f32 // Quadratic with some noise
246 })
247 .collect();
248
249 let tensor = Tensor::from_slice(&data, vec![size])?;
250 println!("Dataset size: {}", tensor.size());
251
252 // Batch processing with windowing
253 println!("\nBatch processing with sliding windows:");
254 let batch_size = 10;
255 let batches: Vec<Tensor> = tensor
256 .iter()
257 .collect::<Vec<_>>()
258 .chunks(batch_size)
259 .map(|chunk| {
260 // Process each batch independently
261 chunk
262 .iter()
263 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
264 .collect()
265 })
266 .collect();
267
268 println!(
269 " Processed {} batches of size {}",
270 batches.len(),
271 batch_size
272 );
273 for (i, batch) in batches.iter().enumerate() {
274 println!(
275 " Batch {}: mean={:.3}, std={:.3}",
276 i,
277 batch.mean().value(),
278 batch.std().value()
279 );
280 }
281
282 // Parallel-like processing with stride
283 println!("\nStrided processing (every nth element):");
284 let stride = 5;
285 let strided: Tensor = tensor
286 .iter()
287 .enumerate()
288 .filter(|(i, _)| i % stride == 0)
289 .map(|(_, elem)| elem)
290 .collect();
291 println!(" Strided (every {}th): {:?}", stride, strided.data());
292
293 // Hierarchical processing
294 println!("\nHierarchical processing (coarse to fine):");
295 let coarse: Tensor = tensor
296 .iter()
297 .enumerate()
298 .filter(|(i, _)| i % 4 == 0) // Take every 4th element
299 .map(|(_, elem)| elem)
300 .collect();
301
302 let fine: Tensor = tensor
303 .iter()
304 .enumerate()
305 .filter(|(i, _)| i % 4 != 0) // Take the rest
306 .map(|(_, elem)| elem)
307 .collect();
308
309 println!(" Coarse (every 4th): {:?}", coarse.data());
310 println!(" Fine (rest): {:?}", fine.data());
311
312 // Combine coarse and fine with different processing
313 let combined: Tensor = coarse
314 .iter()
315 .map(|elem| elem.mul_scalar(2.0)) // Scale coarse
316 .chain(fine.iter().map(|elem| elem.div_scalar(2.0))) // Scale fine
317 .collect();
318 println!(" Combined: {:?}", combined.data());
319
320 Ok(())
321}
322
323/// Demonstrate real-world processing scenarios
324///
325/// Shows practical applications of iterator patterns for
326/// common data processing tasks in machine learning and analytics.
327fn demonstrate_real_world_scenarios() -> Result<(), Box<dyn std::error::Error>> {
328 println!("\n--- Real-world Scenarios ---");
329
330 // Scenario 1: Time series analysis
331 println!("\nScenario 1: Time Series Analysis");
332 let time_series: Vec<f32> = (0..24)
333 .map(|hour| {
334 let base = 20.0 + 10.0 * (hour as f32 * std::f32::consts::PI / 12.0).sin();
335 base + (hour % 3) as f32 * 2.0 // Add some noise
336 })
337 .collect();
338
339 let series = Tensor::from_slice(&time_series, vec![24])?;
340 println!(" Time series (24 hours): {:?}", series.data());
341
342 // Calculate moving average
343 let window_size = 3;
344 let moving_avg: Tensor = series
345 .iter()
346 .enumerate()
347 .map(|(i, _)| {
348 let start = i.saturating_sub(window_size / 2);
349 let end = (i + window_size / 2 + 1).min(series.size());
350 let window = series.iter_range(start, end);
351 window.fold(0.0, |acc, elem| acc + elem.value()) / (end - start) as f32
352 })
353 .map(|val| Tensor::from_slice(&[val], vec![1]).unwrap())
354 .collect();
355 println!(
356 " Moving average (window={}): {:?}",
357 window_size,
358 moving_avg.data()
359 );
360
361 // Scenario 2: Feature engineering
362 println!("\nScenario 2: Feature Engineering");
363 let features = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
364 println!(" Original features: {:?}", features.data());
365
366 // Create polynomial features
367 let poly_features: Tensor = features
368 .iter()
369 .flat_map(|elem| {
370 vec![
371 elem.clone(), // x^1
372 elem.pow_scalar(2.0), // x^2
373 elem.pow_scalar(3.0), // x^3
374 ]
375 })
376 .collect();
377 println!(
378 " Polynomial features (x, x^2, x^3): {:?}",
379 poly_features.data()
380 );
381
382 // Scenario 3: Data augmentation
383 println!("\nScenario 3: Data Augmentation");
384 let original = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?;
385 println!(" Original data: {:?}", original.data());
386
387 // Augment with noise and scaling
388 let augmented: Tensor = original
389 .iter()
390 .flat_map(|elem| {
391 vec![
392 elem.clone(), // Original
393 elem.add_scalar(0.1), // Add noise
394 elem.sub_scalar(0.1), // Subtract noise
395 elem.mul_scalar(1.1), // Scale up
396 elem.mul_scalar(0.9), // Scale down
397 ]
398 })
399 .collect();
400 println!(" Augmented data: {:?}", augmented.data());
401
402 // Scenario 4: Statistical analysis
403 println!("\nScenario 4: Statistical Analysis");
404 let sample_data = Tensor::from_slice(&[1.1, 2.3, 1.8, 2.1, 1.9, 2.0, 1.7, 2.2], vec![8])?;
405 println!(" Sample data: {:?}", sample_data.data());
406
407 // Calculate various statistics
408 let mean = sample_data.mean().value();
409 let std = sample_data.std().value();
410 let min = sample_data
411 .iter()
412 .map(|e| e.value())
413 .fold(f32::INFINITY, f32::min);
414 let max = sample_data
415 .iter()
416 .map(|e| e.value())
417 .fold(f32::NEG_INFINITY, f32::max);
418
419 // Z-score normalization
420 let z_scores: Tensor = sample_data
421 .iter()
422 .map(|elem| elem.sub_scalar(mean).div_scalar(std))
423 .collect();
424
425 println!(
426 " Statistics: mean={:.3}, std={:.3}, min={:.3}, max={:.3}",
427 mean, std, min, max
428 );
429 println!(" Z-scores: {:?}", z_scores.data());
430
431 Ok(())
432}Source§impl Tensor
impl Tensor
Sourcepub fn div_tensor(&self, other: &Tensor) -> Tensor
pub fn div_tensor(&self, other: &Tensor) -> Tensor
Element-wise division with another tensor with broadcasting support.
Performs element-wise division with automatic broadcasting: output[i] = self[i] / other[i]
Broadcasting enables division between tensors of different but compatible shapes. Compatible shapes follow NumPy broadcasting rules:
- Dimensions are aligned from the rightmost dimension
- Dimensions are compatible if they are equal, or one of them is 1
- Missing dimensions are treated as 1
§Arguments
other- Tensor to divide by. Shapes must be broadcast-compatible.
§Returns
A new tensor containing the element-wise quotient with broadcast result shape
§Examples
§Same Shape Division
use train_station::Tensor;
let a = Tensor::from_slice(&[10.0, 20.0, 30.0], vec![3]).unwrap();
let b = Tensor::from_slice(&[2.0, 4.0, 5.0], vec![3]).unwrap();
let c = a.div_tensor(&b);
assert_eq!(c.shape().dims, vec![3]);
assert_eq!(c.get(&[0]), 5.0);
assert_eq!(c.get(&[1]), 5.0);
assert_eq!(c.get(&[2]), 6.0);§Broadcasting Division
use train_station::Tensor;
// Broadcasting: [2, 1] / [1, 3] -> [2, 3]
let a = Tensor::from_slice(&[10.0, 20.0], vec![2, 1]).unwrap();
let b = Tensor::from_slice(&[1.0, 2.0, 5.0], vec![1, 3]).unwrap();
let c = a.div_tensor(&b);
assert_eq!(c.shape().dims, vec![2, 3]);
assert_eq!(c.get(&[0, 0]), 10.0);
assert_eq!(c.get(&[0, 1]), 5.0);
assert_eq!(c.get(&[1, 0]), 20.0);
assert_eq!(c.get(&[1, 1]), 10.0);§Scalar Division
use train_station::Tensor;
// Scalar division: [2, 3] / scalar -> [2, 3]
let a = Tensor::ones(vec![2, 3]);
let b = Tensor::from_slice(&[2.0], vec![1]).unwrap();
let c = a.div_tensor(&b);
assert_eq!(c.shape().dims, vec![2, 3]);
assert_eq!(c.get(&[0, 0]), 0.5);
assert_eq!(c.get(&[1, 2]), 0.5);§Panics
Panics if tensor shapes are not broadcast-compatible or division by zero
Examples found in repository?
89fn demonstrate_basic_operations() {
90 println!("\n--- Basic Operations ---");
91
92 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
93 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
94
95 // Addition
96 let sum = a.add_tensor(&b);
97 println!("A + B: {:?}", sum.data());
98
99 // Subtraction
100 let diff = a.sub_tensor(&b);
101 println!("A - B: {:?}", diff.data());
102
103 // Multiplication
104 let product = a.mul_tensor(&b);
105 println!("A * B: {:?}", product.data());
106
107 // Division
108 let quotient = a.div_tensor(&b);
109 println!("A / B: {:?}", quotient.data());
110
111 // Scalar operations
112 let scalar_add = a.add_scalar(5.0);
113 println!("A + 5.0: {:?}", scalar_add.data());
114
115 let scalar_mul = a.mul_scalar(2.0);
116 println!("A * 2.0: {:?}", scalar_mul.data());
117}Sourcepub fn div_scalar(&self, scalar: f32) -> Tensor
pub fn div_scalar(&self, scalar: f32) -> Tensor
Broadcast division with a scalar value.
Divides every element by the scalar: output[i] = self[i] / scalar
§Arguments
scalar- Value to divide each element by (must not be zero)
§Returns
A new tensor with each element divided by the scalar
§Examples
§Basic Scalar Division
use train_station::Tensor;
let a = Tensor::from_slice(&[10.0, 20.0, 30.0], vec![3]).unwrap();
let b = a.div_scalar(10.0);
assert_eq!(b.shape().dims, vec![3]);
assert_eq!(b.get(&[0]), 1.0);
assert_eq!(b.get(&[1]), 2.0);
assert_eq!(b.get(&[2]), 3.0);§Multi-dimensional Scalar Division
use train_station::Tensor;
let a = Tensor::ones(vec![2, 3]);
let b = a.div_scalar(2.0);
assert_eq!(b.shape().dims, vec![2, 3]);
assert_eq!(b.get(&[0, 0]), 0.5);
assert_eq!(b.get(&[1, 2]), 0.5);§Panics
Panics if scalar is zero
Examples found in repository?
74fn demonstrate_data_pipeline() -> Result<(), Box<dyn std::error::Error>> {
75 println!("\n--- Data Processing Pipeline ---");
76
77 // Simulate raw sensor data with noise
78 let raw_data: Vec<f32> = (0..20)
79 .map(|i| {
80 let base = i as f32 * 0.5;
81 let noise = (i % 3) as f32 * 0.1;
82 base + noise
83 })
84 .collect();
85
86 let tensor = Tensor::from_slice(&raw_data, vec![20])?;
87 println!("Raw sensor data: {:?}", tensor.data());
88
89 // Multi-stage processing pipeline
90 println!("\nProcessing pipeline:");
91 println!("1. Normalize data (z-score)");
92 println!("2. Apply smoothing filter");
93 println!("3. Detect outliers");
94 println!("4. Apply feature scaling");
95
96 // Stage 1: Normalization
97 let mean = tensor.mean().value();
98 let std = tensor.std().value();
99 let normalized: Tensor = tensor
100 .iter()
101 .map(|elem| elem.sub_scalar(mean).div_scalar(std))
102 .collect();
103 println!(
104 " Normalized (mean={:.3}, std={:.3}): {:?}",
105 mean,
106 std,
107 normalized.data()
108 );
109
110 // Stage 2: Smoothing (simple moving average)
111 let smoothed: Tensor = normalized
112 .iter()
113 .enumerate()
114 .map(|(i, elem)| {
115 if i == 0 || i == normalized.size() - 1 {
116 elem.clone()
117 } else {
118 // Simple 3-point average
119 let prev = normalized.element_view(i - 1);
120 let next = normalized.element_view(i + 1);
121 elem.add_tensor(&prev).add_tensor(&next).div_scalar(3.0)
122 }
123 })
124 .collect();
125 println!(" Smoothed: {:?}", smoothed.data());
126
127 // Stage 3: Outlier detection and removal
128 let outlier_threshold = 2.0;
129 let cleaned: Tensor = smoothed
130 .iter()
131 .filter(|elem| elem.value().abs() < outlier_threshold)
132 .collect();
133 println!(
134 " Outliers removed (threshold={}): {:?}",
135 outlier_threshold,
136 cleaned.data()
137 );
138
139 // Stage 4: Feature scaling to [0, 1] range
140 let min_val = cleaned
141 .iter()
142 .map(|e| e.value())
143 .fold(f32::INFINITY, f32::min);
144 let max_val = cleaned
145 .iter()
146 .map(|e| e.value())
147 .fold(f32::NEG_INFINITY, f32::max);
148 let scaled: Tensor = cleaned
149 .iter()
150 .map(|elem| elem.sub_scalar(min_val).div_scalar(max_val - min_val))
151 .collect();
152 println!(" Scaled to [0,1]: {:?}", scaled.data());
153
154 Ok(())
155}
156
157/// Demonstrate conditional processing patterns
158///
159/// Shows how to implement dynamic filtering and transformation
160/// based on data characteristics and conditions.
161fn demonstrate_conditional_processing() -> Result<(), Box<dyn std::error::Error>> {
162 println!("\n--- Conditional Processing ---");
163
164 // Create data with mixed characteristics
165 let data = vec![1.0, -2.0, 3.0, -4.0, 5.0, -6.0, 7.0, -8.0, 9.0, -10.0];
166 let tensor = Tensor::from_slice(&data, vec![10])?;
167 println!("Input data: {:?}", tensor.data());
168
169 // Conditional transformation based on sign
170 println!("\nConditional transformation (positive/negative handling):");
171 let processed: Tensor = tensor
172 .iter()
173 .map(|elem| {
174 let val = elem.value();
175 if val > 0.0 {
176 elem.pow_scalar(2.0) // Square positive values
177 } else {
178 elem.mul_scalar(-1.0).sqrt() // Square root of absolute negative values
179 }
180 })
181 .collect();
182 println!(" Processed: {:?}", processed.data());
183
184 // Adaptive filtering based on local statistics
185 println!("\nAdaptive filtering (remove values > 2 std from local mean):");
186 let window_size = 3;
187 let adaptive_filtered: Tensor = tensor
188 .iter()
189 .enumerate()
190 .filter(|(i, elem)| {
191 let start = i.saturating_sub(window_size / 2);
192 let end = (i + window_size / 2 + 1).min(tensor.size());
193
194 // Calculate local mean and std
195 let local_values: Vec<f32> = (start..end)
196 .map(|j| tensor.element_view(j).value())
197 .collect();
198
199 let local_mean = local_values.iter().sum::<f32>() / local_values.len() as f32;
200 let local_variance = local_values
201 .iter()
202 .map(|v| (v - local_mean).powi(2))
203 .sum::<f32>()
204 / local_values.len() as f32;
205 let local_std = local_variance.sqrt();
206
207 let threshold = local_mean + 2.0 * local_std;
208 elem.value() <= threshold
209 })
210 .map(|(_, elem)| elem)
211 .collect();
212 println!(" Adaptive filtered: {:?}", adaptive_filtered.data());
213
214 // Multi-condition processing
215 println!("\nMulti-condition processing:");
216 let multi_processed: Tensor = tensor
217 .iter()
218 .map(|elem| {
219 let val = elem.value();
220 match () {
221 _ if val > 5.0 => elem.mul_scalar(2.0), // Double large values
222 _ if val < -5.0 => elem.div_scalar(2.0), // Halve small values
223 _ if val.abs() < 2.0 => elem.add_scalar(1.0), // Add 1 to small values
224 _ => elem.clone(), // Keep others unchanged
225 }
226 })
227 .collect();
228 println!(" Multi-condition: {:?}", multi_processed.data());
229
230 Ok(())
231}
232
233/// Demonstrate batch processing operations
234///
235/// Shows efficient processing of large datasets using iterator
236/// patterns and batch operations for performance optimization.
237fn demonstrate_batch_operations() -> Result<(), Box<dyn std::error::Error>> {
238 println!("\n--- Batch Operations ---");
239
240 // Create a larger dataset for batch processing
241 let size = 100;
242 let data: Vec<f32> = (0..size)
243 .map(|i| {
244 let x = i as f32 / size as f32;
245 x * x + 0.1 * (i % 7) as f32 // Quadratic with some noise
246 })
247 .collect();
248
249 let tensor = Tensor::from_slice(&data, vec![size])?;
250 println!("Dataset size: {}", tensor.size());
251
252 // Batch processing with windowing
253 println!("\nBatch processing with sliding windows:");
254 let batch_size = 10;
255 let batches: Vec<Tensor> = tensor
256 .iter()
257 .collect::<Vec<_>>()
258 .chunks(batch_size)
259 .map(|chunk| {
260 // Process each batch independently
261 chunk
262 .iter()
263 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
264 .collect()
265 })
266 .collect();
267
268 println!(
269 " Processed {} batches of size {}",
270 batches.len(),
271 batch_size
272 );
273 for (i, batch) in batches.iter().enumerate() {
274 println!(
275 " Batch {}: mean={:.3}, std={:.3}",
276 i,
277 batch.mean().value(),
278 batch.std().value()
279 );
280 }
281
282 // Parallel-like processing with stride
283 println!("\nStrided processing (every nth element):");
284 let stride = 5;
285 let strided: Tensor = tensor
286 .iter()
287 .enumerate()
288 .filter(|(i, _)| i % stride == 0)
289 .map(|(_, elem)| elem)
290 .collect();
291 println!(" Strided (every {}th): {:?}", stride, strided.data());
292
293 // Hierarchical processing
294 println!("\nHierarchical processing (coarse to fine):");
295 let coarse: Tensor = tensor
296 .iter()
297 .enumerate()
298 .filter(|(i, _)| i % 4 == 0) // Take every 4th element
299 .map(|(_, elem)| elem)
300 .collect();
301
302 let fine: Tensor = tensor
303 .iter()
304 .enumerate()
305 .filter(|(i, _)| i % 4 != 0) // Take the rest
306 .map(|(_, elem)| elem)
307 .collect();
308
309 println!(" Coarse (every 4th): {:?}", coarse.data());
310 println!(" Fine (rest): {:?}", fine.data());
311
312 // Combine coarse and fine with different processing
313 let combined: Tensor = coarse
314 .iter()
315 .map(|elem| elem.mul_scalar(2.0)) // Scale coarse
316 .chain(fine.iter().map(|elem| elem.div_scalar(2.0))) // Scale fine
317 .collect();
318 println!(" Combined: {:?}", combined.data());
319
320 Ok(())
321}
322
323/// Demonstrate real-world processing scenarios
324///
325/// Shows practical applications of iterator patterns for
326/// common data processing tasks in machine learning and analytics.
327fn demonstrate_real_world_scenarios() -> Result<(), Box<dyn std::error::Error>> {
328 println!("\n--- Real-world Scenarios ---");
329
330 // Scenario 1: Time series analysis
331 println!("\nScenario 1: Time Series Analysis");
332 let time_series: Vec<f32> = (0..24)
333 .map(|hour| {
334 let base = 20.0 + 10.0 * (hour as f32 * std::f32::consts::PI / 12.0).sin();
335 base + (hour % 3) as f32 * 2.0 // Add some noise
336 })
337 .collect();
338
339 let series = Tensor::from_slice(&time_series, vec![24])?;
340 println!(" Time series (24 hours): {:?}", series.data());
341
342 // Calculate moving average
343 let window_size = 3;
344 let moving_avg: Tensor = series
345 .iter()
346 .enumerate()
347 .map(|(i, _)| {
348 let start = i.saturating_sub(window_size / 2);
349 let end = (i + window_size / 2 + 1).min(series.size());
350 let window = series.iter_range(start, end);
351 window.fold(0.0, |acc, elem| acc + elem.value()) / (end - start) as f32
352 })
353 .map(|val| Tensor::from_slice(&[val], vec![1]).unwrap())
354 .collect();
355 println!(
356 " Moving average (window={}): {:?}",
357 window_size,
358 moving_avg.data()
359 );
360
361 // Scenario 2: Feature engineering
362 println!("\nScenario 2: Feature Engineering");
363 let features = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
364 println!(" Original features: {:?}", features.data());
365
366 // Create polynomial features
367 let poly_features: Tensor = features
368 .iter()
369 .flat_map(|elem| {
370 vec![
371 elem.clone(), // x^1
372 elem.pow_scalar(2.0), // x^2
373 elem.pow_scalar(3.0), // x^3
374 ]
375 })
376 .collect();
377 println!(
378 " Polynomial features (x, x^2, x^3): {:?}",
379 poly_features.data()
380 );
381
382 // Scenario 3: Data augmentation
383 println!("\nScenario 3: Data Augmentation");
384 let original = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?;
385 println!(" Original data: {:?}", original.data());
386
387 // Augment with noise and scaling
388 let augmented: Tensor = original
389 .iter()
390 .flat_map(|elem| {
391 vec![
392 elem.clone(), // Original
393 elem.add_scalar(0.1), // Add noise
394 elem.sub_scalar(0.1), // Subtract noise
395 elem.mul_scalar(1.1), // Scale up
396 elem.mul_scalar(0.9), // Scale down
397 ]
398 })
399 .collect();
400 println!(" Augmented data: {:?}", augmented.data());
401
402 // Scenario 4: Statistical analysis
403 println!("\nScenario 4: Statistical Analysis");
404 let sample_data = Tensor::from_slice(&[1.1, 2.3, 1.8, 2.1, 1.9, 2.0, 1.7, 2.2], vec![8])?;
405 println!(" Sample data: {:?}", sample_data.data());
406
407 // Calculate various statistics
408 let mean = sample_data.mean().value();
409 let std = sample_data.std().value();
410 let min = sample_data
411 .iter()
412 .map(|e| e.value())
413 .fold(f32::INFINITY, f32::min);
414 let max = sample_data
415 .iter()
416 .map(|e| e.value())
417 .fold(f32::NEG_INFINITY, f32::max);
418
419 // Z-score normalization
420 let z_scores: Tensor = sample_data
421 .iter()
422 .map(|elem| elem.sub_scalar(mean).div_scalar(std))
423 .collect();
424
425 println!(
426 " Statistics: mean={:.3}, std={:.3}, min={:.3}, max={:.3}",
427 mean, std, min, max
428 );
429 println!(" Z-scores: {:?}", z_scores.data());
430
431 Ok(())
432}Source§impl Tensor
impl Tensor
Sourcepub fn exp(&self) -> Tensor
pub fn exp(&self) -> Tensor
Element-wise exponential function.
Computes e^x for each element: output[i] = e^(self[i])
§Returns
A new tensor with the exponential of each element
§Examples
§Basic Exponential
use train_station::Tensor;
let a = Tensor::from_slice(&[0.0, 1.0, 2.0], vec![3]).unwrap();
let b = a.exp();
assert_eq!(b.shape().dims, vec![3]);
assert_eq!(b.get(&[0]), 1.0); // e^0 = 1
assert!((b.get(&[1]) - 2.71828).abs() < 1e-5); // e^1 ≈ 2.71828
assert!((b.get(&[2]) - 7.38906).abs() < 1e-5); // e^2 ≈ 7.38906§Negative Values
use train_station::Tensor;
let a = Tensor::from_slice(&[-1.0, 0.0, 1.0], vec![3]).unwrap();
let b = a.exp();
assert_eq!(b.shape().dims, vec![3]);
assert!((b.get(&[0]) - 0.36788).abs() < 1e-5); // e^(-1) ≈ 0.36788
assert_eq!(b.get(&[1]), 1.0); // e^0 = 1
assert!((b.get(&[2]) - 2.71828).abs() < 1e-5); // e^1 ≈ 2.71828Examples found in repository?
192fn demonstrate_advanced_patterns() -> Result<(), Box<dyn std::error::Error>> {
193 println!("\n--- Advanced Iterator Patterns ---");
194
195 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![6])?;
196 println!("Input tensor: {:?}", tensor.data());
197
198 // Complex chain: enumerate -> filter -> map -> collect
199 println!("\nComplex chain (even indices only, add index to value):");
200 let result: Tensor = tensor
201 .iter()
202 .enumerate()
203 .filter(|(i, _)| i % 2 == 0) // Take even indices
204 .map(|(i, elem)| elem.add_scalar(i as f32)) // Add index to value
205 .collect();
206 println!(" Result: {:?}", result.data());
207
208 // Using take and skip for windowing
209 println!("\nWindowing with take and skip:");
210 let window1: Tensor = tensor.iter().take(3).collect();
211 let window2: Tensor = tensor.iter().skip(2).take(3).collect();
212 println!(" Window 1 (first 3): {:?}", window1.data());
213 println!(" Window 2 (middle 3): {:?}", window2.data());
214
215 // Using rev() for reverse iteration
216 println!("\nReverse iteration:");
217 let reversed: Tensor = tensor.iter().rev().collect();
218 println!(" Reversed: {:?}", reversed.data());
219
220 // Chaining with mathematical operations
221 println!("\nMathematical operation chain:");
222 let math_result: Tensor = tensor
223 .iter()
224 .map(|elem| elem.exp()) // e^x
225 .filter(|elem| elem.value() < 50.0) // Filter large values
226 .map(|elem| elem.log()) // ln(x)
227 .collect();
228 println!(" Math chain result: {:?}", math_result.data());
229
230 // Using zip for element-wise combinations
231 println!("\nElement-wise combination with zip:");
232 let tensor2 = Tensor::from_slice(&[10.0, 20.0, 30.0, 40.0, 50.0, 60.0], vec![6])?;
233 let combined: Tensor = tensor
234 .iter()
235 .zip(tensor2.iter())
236 .map(|(a, b)| a.mul_tensor(&b)) // Element-wise multiplication
237 .collect();
238 println!(" Combined: {:?}", combined.data());
239
240 Ok(())
241}Source§impl Tensor
impl Tensor
Sourcepub fn leaky_relu(&self, negative_slope: f32) -> Tensor
pub fn leaky_relu(&self, negative_slope: f32) -> Tensor
Element-wise Leaky ReLU activation.
Applies Leaky ReLU to each element: output[i] = max(0, x) + negative_slope * min(0, x)
Unlike standard ReLU, allows a small gradient when the unit is not active.
§Arguments
negative_slope- Slope for negative values (typically small, e.g., 0.01 or 0.1)
§Returns
A new tensor with Leaky ReLU applied to each element
§Examples
§Basic Leaky ReLU
use train_station::Tensor;
let a = Tensor::from_slice(&[-2.0, -1.0, 0.0, 1.0], vec![4]).unwrap();
let b = a.leaky_relu(0.1);
assert_eq!(b.shape().dims, vec![4]);
assert!((b.get(&[0]) - (-0.2)).abs() < 1e-6); // -2.0 * 0.1 = -0.2
assert!((b.get(&[1]) - (-0.1)).abs() < 1e-6); // -1.0 * 0.1 = -0.1
assert_eq!(b.get(&[2]), 0.0); // max(0, 0) = 0
assert_eq!(b.get(&[3]), 1.0); // max(0, 1) = 1§Different Negative Slopes
use train_station::Tensor;
let a = Tensor::from_slice(&[-1.0, 0.0, 1.0], vec![3]).unwrap();
let b = a.leaky_relu(0.01); // Smaller negative slope
assert_eq!(b.shape().dims, vec![3]);
assert!((b.get(&[0]) - (-0.01)).abs() < 1e-6); // -1.0 * 0.01 = -0.01
assert_eq!(b.get(&[1]), 0.0); // max(0, 0) = 0
assert_eq!(b.get(&[2]), 1.0); // max(0, 1) = 1Source§impl Tensor
impl Tensor
Sourcepub fn log(&self) -> Tensor
pub fn log(&self) -> Tensor
Element-wise natural logarithm.
Computes the natural logarithm for each element: output[i] = ln(self[i])
§Returns
A new tensor with the natural logarithm of each element
§Examples
§Basic Natural Logarithm
use train_station::Tensor;
let a = Tensor::from_slice(&[1.0, 2.71828, 7.38906], vec![3]).unwrap();
let b = a.log();
assert_eq!(b.shape().dims, vec![3]);
assert_eq!(b.get(&[0]), 0.0); // ln(1) = 0
assert!((b.get(&[1]) - 1.0).abs() < 1e-5); // ln(e) ≈ 1
assert!((b.get(&[2]) - 2.0).abs() < 1e-5); // ln(e^2) ≈ 2§Mathematical Properties
use train_station::Tensor;
let a = Tensor::from_slice(&[4.0, 8.0, 16.0], vec![3]).unwrap();
let b = a.log();
assert_eq!(b.shape().dims, vec![3]);
assert!((b.get(&[0]) - 1.38629).abs() < 1e-5); // ln(4) ≈ 1.38629
assert!((b.get(&[1]) - 2.07944).abs() < 1e-5); // ln(8) ≈ 2.07944
assert!((b.get(&[2]) - 2.77259).abs() < 1e-5); // ln(16) ≈ 2.77259§Panics
Panics if any element is non-positive (x <= 0)
Examples found in repository?
192fn demonstrate_advanced_patterns() -> Result<(), Box<dyn std::error::Error>> {
193 println!("\n--- Advanced Iterator Patterns ---");
194
195 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![6])?;
196 println!("Input tensor: {:?}", tensor.data());
197
198 // Complex chain: enumerate -> filter -> map -> collect
199 println!("\nComplex chain (even indices only, add index to value):");
200 let result: Tensor = tensor
201 .iter()
202 .enumerate()
203 .filter(|(i, _)| i % 2 == 0) // Take even indices
204 .map(|(i, elem)| elem.add_scalar(i as f32)) // Add index to value
205 .collect();
206 println!(" Result: {:?}", result.data());
207
208 // Using take and skip for windowing
209 println!("\nWindowing with take and skip:");
210 let window1: Tensor = tensor.iter().take(3).collect();
211 let window2: Tensor = tensor.iter().skip(2).take(3).collect();
212 println!(" Window 1 (first 3): {:?}", window1.data());
213 println!(" Window 2 (middle 3): {:?}", window2.data());
214
215 // Using rev() for reverse iteration
216 println!("\nReverse iteration:");
217 let reversed: Tensor = tensor.iter().rev().collect();
218 println!(" Reversed: {:?}", reversed.data());
219
220 // Chaining with mathematical operations
221 println!("\nMathematical operation chain:");
222 let math_result: Tensor = tensor
223 .iter()
224 .map(|elem| elem.exp()) // e^x
225 .filter(|elem| elem.value() < 50.0) // Filter large values
226 .map(|elem| elem.log()) // ln(x)
227 .collect();
228 println!(" Math chain result: {:?}", math_result.data());
229
230 // Using zip for element-wise combinations
231 println!("\nElement-wise combination with zip:");
232 let tensor2 = Tensor::from_slice(&[10.0, 20.0, 30.0, 40.0, 50.0, 60.0], vec![6])?;
233 let combined: Tensor = tensor
234 .iter()
235 .zip(tensor2.iter())
236 .map(|(a, b)| a.mul_tensor(&b)) // Element-wise multiplication
237 .collect();
238 println!(" Combined: {:?}", combined.data());
239
240 Ok(())
241}Source§impl Tensor
impl Tensor
Sourcepub fn matmul(&self, other: &Tensor) -> Tensor
pub fn matmul(&self, other: &Tensor) -> Tensor
Matrix multiplication operation following NumPy semantics
Performs matrix multiplication between this tensor and another tensor with intelligent kernel selection based on matrix dimensions and hardware capabilities. The operation follows broadcasting rules and supports all common matrix multiplication patterns found in machine learning workloads.
§Supported Operations
- 1D @ 1D: Dot product returning scalar tensor
- 1D @ 2D: Vector-matrix multiplication (v^T * M) returning 1D tensor
- 2D @ 1D: Matrix-vector multiplication (M * v) returning 1D tensor
- 2D @ 2D: Standard matrix multiplication with cache-optimized blocking
- ND @ ND: Batched matrix multiplication on last two dimensions with broadcasting
§Performance Characteristics
The implementation automatically selects optimal kernels based on matrix dimensions:
- Small matrices (<64 elements): Direct computation with minimal overhead
- Medium matrices (64-256 elements): Cache-optimized blocking for L1/L2 cache
- Large matrices (256+ elements): Memory bandwidth optimized with hierarchical blocking
- AVX2 acceleration: 8x SIMD operations for compatible hardware
- Scalar fallbacks: Optimized scalar implementations for non-SIMD platforms
§Automatic Differentiation
This operation supports automatic differentiation when either operand requires gradients.
Gradient computation follows PyTorch semantics with proper accumulation and chain rule
application through the gradtrack engine. Gradients are computed for both operands when
requires_grad is set.
§Arguments
other- The tensor to multiply with (must have compatible dimensions)
§Returns
A new tensor containing the matrix multiplication result with appropriate shape determined by broadcasting rules and matrix multiplication semantics
§Panics
Panics if the inner dimensions don’t match for matrix multiplication:
- For 2D @ 2D:
self.shape()[1] != other.shape()[0] - For 1D @ 2D:
self.shape()[0] != other.shape()[0] - For 2D @ 1D:
self.shape()[1] != other.shape()[0] - For ND @ ND: Last two dimensions must be compatible for matrix multiplication
§Examples
use train_station::Tensor;
// 2D matrix multiplication
let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
let result = a.matmul(&b); // [2, 2] @ [2, 2] -> [2, 2]
assert_eq!(result.shape().dims, vec![2, 2]);
assert_eq!(result.data(), &[19.0, 22.0, 43.0, 50.0]);§Vector-Matrix Multiplication
use train_station::Tensor;
let v = Tensor::from_slice(&[1.0, 2.0], vec![2]).unwrap();
let m = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let result = v.matmul(&m); // [2] @ [2, 2] -> [2]
assert_eq!(result.shape().dims, vec![2]);
assert_eq!(result.data(), &[7.0, 10.0]); // 1*1+2*3, 1*2+2*4§Gradient Tracking
use train_station::Tensor;
let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2])
.unwrap()
.with_requires_grad();
let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2])
.unwrap()
.with_requires_grad();
let result = a.matmul(&b);
assert!(result.requires_grad());
assert_eq!(result.shape().dims, vec![2, 2]);§Thread Safety
This operation is thread-safe and can be used concurrently across multiple threads. The implementation uses immutable tensor references and thread-local gradtrack state.
§Memory Safety
The implementation uses Tensor::new_uninitialized for performance-critical allocations
and handles memory initialization safely through the kernel system. All unsafe operations
are validated through comprehensive FFI testing against LibTorch reference implementation.
Examples found in repository?
More examples
84fn demonstrate_default_adam() -> Result<(), Box<dyn std::error::Error>> {
85 println!("--- Default Adam Configuration ---");
86
87 // Create a simple regression problem: y = 2*x + 1
88 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
89 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
90
91 // Create model parameters
92 let mut weight = Tensor::randn(vec![1, 1], Some(42)).with_requires_grad();
93 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
94
95 // Create Adam optimizer with default configuration
96 let mut optimizer = Adam::new();
97 optimizer.add_parameter(&weight);
98 optimizer.add_parameter(&bias);
99
100 println!("Default Adam configuration:");
101 println!(" Learning rate: {}", optimizer.learning_rate());
102 println!(" Initial weight: {:.6}", weight.value());
103 println!(" Initial bias: {:.6}", bias.value());
104
105 // Training loop
106 let num_epochs = 50;
107 let mut losses = Vec::new();
108
109 for epoch in 0..num_epochs {
110 // Forward pass
111 let y_pred = x_data.matmul(&weight) + &bias;
112 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
113
114 // Backward pass
115 loss.backward(None);
116
117 // Optimizer step
118 optimizer.step(&mut [&mut weight, &mut bias]);
119 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
120
121 losses.push(loss.value());
122
123 if epoch % 10 == 0 || epoch == num_epochs - 1 {
124 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
125 }
126 }
127
128 // Evaluate final model
129 let _final_predictions = x_data.matmul(&weight) + &bias;
130 println!("\nFinal model:");
131 println!(" Learned weight: {:.6} (target: 2.0)", weight.value());
132 println!(" Learned bias: {:.6} (target: 1.0)", bias.value());
133 println!(" Final loss: {:.6}", losses[losses.len() - 1]);
134
135 Ok(())
136}
137
138/// Demonstrate learning rate comparison
139fn demonstrate_learning_rate_comparison() -> Result<(), Box<dyn std::error::Error>> {
140 println!("\n--- Learning Rate Comparison ---");
141
142 let learning_rates = [0.001, 0.01, 0.1];
143 let mut results = Vec::new();
144
145 for &lr in &learning_rates {
146 println!("\nTesting learning rate: {}", lr);
147
148 let stats = train_with_config(TrainingConfig {
149 learning_rate: lr,
150 ..Default::default()
151 })?;
152
153 results.push((lr, stats.clone()));
154
155 println!(" Final loss: {:.6}", stats.final_loss);
156 println!(" Convergence epoch: {}", stats.convergence_epoch);
157 }
158
159 // Compare results
160 println!("\nLearning Rate Comparison Summary:");
161 for (lr, stats) in &results {
162 println!(
163 " LR={:6}: Loss={:.6}, Converged@{}",
164 lr, stats.final_loss, stats.convergence_epoch
165 );
166 }
167
168 Ok(())
169}
170
171/// Demonstrate weight decay comparison
172fn demonstrate_weight_decay_comparison() -> Result<(), Box<dyn std::error::Error>> {
173 println!("\n--- Weight Decay Comparison ---");
174
175 let weight_decays = [0.0, 0.001, 0.01];
176 let mut results = Vec::new();
177
178 for &wd in &weight_decays {
179 println!("\nTesting weight decay: {}", wd);
180
181 let stats = train_with_config(TrainingConfig {
182 weight_decay: wd,
183 ..Default::default()
184 })?;
185
186 results.push((wd, stats.clone()));
187
188 println!(" Final loss: {:.6}", stats.final_loss);
189 println!(" Final weight norm: {:.6}", stats.weight_norm);
190 }
191
192 // Compare results
193 println!("\nWeight Decay Comparison Summary:");
194 for (wd, stats) in &results {
195 println!(
196 " WD={:6}: Loss={:.6}, Weight Norm={:.6}",
197 wd, stats.final_loss, stats.weight_norm
198 );
199 }
200
201 Ok(())
202}
203
204/// Demonstrate beta parameter tuning
205fn demonstrate_beta_parameter_tuning() -> Result<(), Box<dyn std::error::Error>> {
206 println!("\n--- Beta Parameter Tuning ---");
207
208 let beta_configs = [
209 (0.9, 0.999), // Default
210 (0.8, 0.999), // More aggressive momentum
211 (0.95, 0.999), // Less aggressive momentum
212 (0.9, 0.99), // Faster second moment decay
213 ];
214
215 let mut results = Vec::new();
216
217 for (i, (beta1, beta2)) in beta_configs.iter().enumerate() {
218 println!(
219 "\nTesting beta configuration {}: beta1={}, beta2={}",
220 i + 1,
221 beta1,
222 beta2
223 );
224
225 let config = TrainingConfig {
226 beta1: *beta1,
227 beta2: *beta2,
228 ..Default::default()
229 };
230
231 let stats = train_with_config(config)?;
232 results.push(((*beta1, *beta2), stats.clone()));
233
234 println!(" Final loss: {:.6}", stats.final_loss);
235 println!(" Convergence epoch: {}", stats.convergence_epoch);
236 }
237
238 // Compare results
239 println!("\nBeta Parameter Comparison Summary:");
240 for ((beta1, beta2), stats) in &results {
241 println!(
242 " B1={:4}, B2={:5}: Loss={:.6}, Converged@{}",
243 beta1, beta2, stats.final_loss, stats.convergence_epoch
244 );
245 }
246
247 Ok(())
248}
249
250/// Demonstrate configuration benchmarking
251fn demonstrate_configuration_benchmarking() -> Result<(), Box<dyn std::error::Error>> {
252 println!("\n--- Configuration Benchmarking ---");
253
254 // Define configurations to benchmark
255 let configs = vec![
256 (
257 "Conservative",
258 TrainingConfig {
259 learning_rate: 0.001,
260 weight_decay: 0.001,
261 beta1: 0.95,
262 ..Default::default()
263 },
264 ),
265 (
266 "Balanced",
267 TrainingConfig {
268 learning_rate: 0.01,
269 weight_decay: 0.0,
270 beta1: 0.9,
271 ..Default::default()
272 },
273 ),
274 (
275 "Aggressive",
276 TrainingConfig {
277 learning_rate: 0.1,
278 weight_decay: 0.0,
279 beta1: 0.8,
280 ..Default::default()
281 },
282 ),
283 ];
284
285 let mut benchmark_results = Vec::new();
286
287 for (name, config) in configs {
288 println!("\nBenchmarking {} configuration:", name);
289
290 let start_time = std::time::Instant::now();
291 let stats = train_with_config(config.clone())?;
292 let elapsed = start_time.elapsed();
293
294 println!(" Training time: {:.2}ms", elapsed.as_millis());
295 println!(" Final loss: {:.6}", stats.final_loss);
296 println!(" Convergence: {} epochs", stats.convergence_epoch);
297
298 benchmark_results.push((name.to_string(), stats, elapsed));
299 }
300
301 // Summary
302 println!("\nBenchmarking Summary:");
303 for (name, stats, elapsed) in &benchmark_results {
304 println!(
305 " {:12}: Loss={:.6}, Time={:4}ms, Converged@{}",
306 name,
307 stats.final_loss,
308 elapsed.as_millis(),
309 stats.convergence_epoch
310 );
311 }
312
313 Ok(())
314}
315
316/// Helper function to train with specific configuration
317fn train_with_config(config: TrainingConfig) -> Result<TrainingStats, Box<dyn std::error::Error>> {
318 // Create training data
319 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
320 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
321
322 // Create model parameters
323 let mut weight = Tensor::randn(vec![1, 1], Some(123)).with_requires_grad();
324 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
325
326 // Create optimizer with custom configuration
327 let adam_config = AdamConfig {
328 learning_rate: config.learning_rate,
329 beta1: config.beta1,
330 beta2: config.beta2,
331 eps: 1e-8,
332 weight_decay: config.weight_decay,
333 amsgrad: false,
334 };
335
336 let mut optimizer = Adam::with_config(adam_config);
337 optimizer.add_parameter(&weight);
338 optimizer.add_parameter(&bias);
339
340 // Training loop
341 let mut losses = Vec::new();
342 let mut convergence_epoch = config.epochs;
343
344 for epoch in 0..config.epochs {
345 // Forward pass
346 let y_pred = x_data.matmul(&weight) + &bias;
347 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
348
349 // Backward pass
350 loss.backward(None);
351
352 // Optimizer step
353 optimizer.step(&mut [&mut weight, &mut bias]);
354 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
355
356 let loss_value = loss.value();
357 losses.push(loss_value);
358
359 // Check for convergence (loss < 0.01)
360 if loss_value < 0.01 && convergence_epoch == config.epochs {
361 convergence_epoch = epoch;
362 }
363 }
364
365 Ok(TrainingStats {
366 config,
367 final_loss: losses[losses.len() - 1],
368 loss_history: losses,
369 convergence_epoch,
370 weight_norm: weight.norm().value(),
371 })
372}319fn train_with_scheduler(
320 scheduler: &mut dyn LearningRateScheduler,
321 num_epochs: usize,
322) -> Result<TrainingStats, Box<dyn std::error::Error>> {
323 // Create training data: y = 2*x + 1
324 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
325 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
326
327 // Create model parameters
328 let mut weight = Tensor::randn(vec![1, 1], Some(456)).with_requires_grad();
329 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
330
331 // Create optimizer with initial learning rate
332 let mut optimizer = Adam::with_learning_rate(0.05);
333 optimizer.add_parameter(&weight);
334 optimizer.add_parameter(&bias);
335
336 // Training loop
337 let mut losses = Vec::new();
338 let mut lr_history = Vec::new();
339 let mut convergence_epoch = num_epochs;
340
341 for epoch in 0..num_epochs {
342 // Forward pass
343 let y_pred = x_data.matmul(&weight) + &bias;
344 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
345
346 // Backward pass
347 loss.backward(None);
348
349 // Update learning rate using scheduler
350 let current_lr = optimizer.learning_rate();
351 let new_lr = scheduler.step(current_lr, epoch, loss.value());
352
353 if (new_lr - current_lr).abs() > 1e-8 {
354 optimizer.set_learning_rate(new_lr);
355 }
356
357 // Optimizer step
358 optimizer.step(&mut [&mut weight, &mut bias]);
359 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
360
361 let loss_value = loss.value();
362 losses.push(loss_value);
363 lr_history.push(new_lr);
364
365 // Check for convergence
366 if loss_value < 0.01 && convergence_epoch == num_epochs {
367 convergence_epoch = epoch;
368 }
369 }
370
371 Ok(TrainingStats {
372 scheduler_name: scheduler.name().to_string(),
373 final_loss: losses[losses.len() - 1],
374 lr_history,
375 loss_history: losses,
376 convergence_epoch,
377 })
378}105fn demonstrate_linear_regression() -> Result<(), Box<dyn std::error::Error>> {
106 println!("\n--- Linear Regression Training ---");
107
108 // Create model parameters
109 let mut weight = Tensor::randn(vec![1, 1], Some(43)).with_requires_grad();
110 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
111
112 // Create optimizer
113 let mut optimizer = Adam::with_learning_rate(0.01);
114 optimizer.add_parameter(&weight);
115 optimizer.add_parameter(&bias);
116
117 // Create simple training data: y = 2*x + 1
118 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
119 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
120
121 println!("Training data:");
122 println!(" X: {:?}", x_data.data());
123 println!(" Y: {:?}", y_true.data());
124 println!(" Target: y = 2*x + 1");
125
126 // Training loop
127 let num_epochs = 100;
128 let mut losses = Vec::new();
129
130 for epoch in 0..num_epochs {
131 // Forward pass: y_pred = x * weight + bias
132 let y_pred = x_data.matmul(&weight) + &bias;
133
134 // Compute loss: MSE
135 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
136
137 // Backward pass
138 loss.backward(None);
139
140 // Optimizer step
141 optimizer.step(&mut [&mut weight, &mut bias]);
142 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
143
144 losses.push(loss.value());
145
146 // Print progress every 20 epochs
147 if epoch % 20 == 0 || epoch == num_epochs - 1 {
148 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
149 }
150 }
151
152 // Evaluate final model
153 let final_predictions = x_data.matmul(&weight) + &bias;
154 println!("\nFinal model evaluation:");
155 println!(" Learned weight: {:.6}", weight.value());
156 println!(" Learned bias: {:.6}", bias.value());
157 println!(" Predictions vs True:");
158
159 for i in 0..5 {
160 let x1 = x_data.data()[i];
161 let pred = final_predictions.data()[i];
162 let true_val = y_true.data()[i];
163 println!(
164 " x={:.1}: pred={:.3}, true={:.1}, error={:.3}",
165 x1,
166 pred,
167 true_val,
168 (pred - true_val).abs()
169 );
170 }
171
172 Ok(())
173}
174
175/// Demonstrate advanced training patterns
176fn demonstrate_advanced_training() -> Result<(), Box<dyn std::error::Error>> {
177 println!("\n--- Advanced Training Patterns ---");
178
179 // Create a more complex model
180 let mut weight = Tensor::randn(vec![1, 2], Some(44)).with_requires_grad();
181 let mut bias = Tensor::zeros(vec![2]).with_requires_grad();
182
183 // Create optimizer with different learning rate
184 let mut optimizer = Adam::with_learning_rate(0.005);
185 optimizer.add_parameter(&weight);
186 optimizer.add_parameter(&bias);
187
188 // Create training data: y = 2*x + [1, 3]
189 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
190 let y_true = Tensor::from_slice(
191 &[3.0, 5.0, 7.0, 9.0, 11.0, 6.0, 8.0, 10.0, 12.0, 14.0],
192 vec![5, 2],
193 )
194 .unwrap();
195
196 println!("Advanced training with monitoring:");
197 println!(" Initial learning rate: {}", optimizer.learning_rate());
198
199 // Training loop with monitoring
200 let num_epochs = 50;
201 let mut losses = Vec::new();
202 let mut weight_norms = Vec::new();
203 let mut gradient_norms = Vec::new();
204
205 for epoch in 0..num_epochs {
206 // Forward pass
207 let y_pred = x_data.matmul(&weight) + &bias;
208 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
209
210 // Backward pass
211 loss.backward(None);
212
213 // Compute gradient norm before optimizer step
214 let gradient_norm = weight.grad_by_value().unwrap().norm();
215
216 // Optimizer step
217 optimizer.step(&mut [&mut weight, &mut bias]);
218 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
219
220 // Learning rate scheduling: reduce every 10 epochs
221 if epoch > 0 && epoch % 10 == 0 {
222 let current_lr = optimizer.learning_rate();
223 let new_lr = current_lr * 0.5;
224 optimizer.set_learning_rate(new_lr);
225 println!(
226 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
227 epoch, current_lr, new_lr
228 );
229 }
230
231 // Record metrics
232 losses.push(loss.value());
233 weight_norms.push(weight.norm().value());
234 gradient_norms.push(gradient_norm.value());
235
236 // Print detailed progress
237 if epoch % 10 == 0 || epoch == num_epochs - 1 {
238 println!(
239 "Epoch {:2}: Loss = {:.6}, Weight Norm = {:.6}, Gradient Norm = {:.6}",
240 epoch,
241 loss.value(),
242 weight.norm().value(),
243 gradient_norm.value()
244 );
245 }
246 }
247
248 println!("Final learning rate: {}", optimizer.learning_rate());
249
250 // Analyze training progression
251 let initial_loss = losses[0];
252 let final_loss = losses[losses.len() - 1];
253 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
254
255 println!("\nTraining Analysis:");
256 println!(" Initial loss: {:.6}", initial_loss);
257 println!(" Final loss: {:.6}", final_loss);
258 println!(" Loss reduction: {:.1}%", loss_reduction);
259 println!(" Final weight norm: {:.6}", weight.norm().value());
260 println!(" Final bias: {:?}", bias.data());
261
262 Ok(())
263}
264
265/// Demonstrate learning rate scheduling
266fn demonstrate_learning_rate_scheduling() -> Result<(), Box<dyn std::error::Error>> {
267 println!("\n--- Learning Rate Scheduling ---");
268
269 // Create simple model
270 let mut weight = Tensor::randn(vec![1, 1], Some(45)).with_requires_grad();
271 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
272
273 // Create optimizer with high initial learning rate
274 let mut optimizer = Adam::with_learning_rate(0.1);
275 optimizer.add_parameter(&weight);
276 optimizer.add_parameter(&bias);
277
278 // Simple data
279 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3, 1]).unwrap();
280 let y_true = Tensor::from_slice(&[2.0, 4.0, 6.0], vec![3, 1]).unwrap();
281
282 println!("Initial learning rate: {}", optimizer.learning_rate());
283
284 // Training loop with learning rate scheduling
285 let num_epochs = 50;
286 let mut losses = Vec::new();
287
288 for epoch in 0..num_epochs {
289 // Forward pass
290 let y_pred = x_data.matmul(&weight) + &bias;
291 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
292
293 // Backward pass
294 loss.backward(None);
295
296 // Optimizer step
297 optimizer.step(&mut [&mut weight, &mut bias]);
298 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
299
300 // Learning rate scheduling: reduce every 10 epochs
301 if epoch > 0 && epoch % 10 == 0 {
302 let current_lr = optimizer.learning_rate();
303 let new_lr = current_lr * 0.5;
304 optimizer.set_learning_rate(new_lr);
305 println!(
306 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
307 epoch, current_lr, new_lr
308 );
309 }
310
311 losses.push(loss.value());
312
313 // Print progress
314 if epoch % 10 == 0 || epoch == num_epochs - 1 {
315 println!(
316 "Epoch {:2}: Loss = {:.6}, LR = {:.3}",
317 epoch,
318 loss.value(),
319 optimizer.learning_rate()
320 );
321 }
322 }
323
324 println!("Final learning rate: {}", optimizer.learning_rate());
325
326 Ok(())
327}
328
329/// Demonstrate training monitoring and analysis
330fn demonstrate_training_monitoring() -> Result<(), Box<dyn std::error::Error>> {
331 println!("\n--- Training Monitoring ---");
332
333 // Create model
334 let mut weight = Tensor::randn(vec![1, 1], Some(46)).with_requires_grad();
335 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
336
337 // Create optimizer
338 let mut optimizer = Adam::with_learning_rate(0.01);
339 optimizer.add_parameter(&weight);
340 optimizer.add_parameter(&bias);
341
342 // Training data
343 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4, 1]).unwrap();
344 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0], vec![4, 1]).unwrap();
345
346 // Training loop with comprehensive monitoring
347 let num_epochs = 30;
348 let mut losses = Vec::new();
349 let mut weight_history = Vec::new();
350 let mut bias_history = Vec::new();
351
352 for epoch in 0..num_epochs {
353 // Forward pass
354 let y_pred = x_data.matmul(&weight) + &bias;
355 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
356
357 // Backward pass
358 loss.backward(None);
359
360 // Optimizer step
361 optimizer.step(&mut [&mut weight, &mut bias]);
362 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
363
364 // Record history
365 losses.push(loss.value());
366 weight_history.push(weight.value());
367 bias_history.push(bias.value());
368
369 // Print detailed monitoring
370 if epoch % 5 == 0 || epoch == num_epochs - 1 {
371 println!(
372 "Epoch {:2}: Loss = {:.6}, Weight = {:.6}, Bias = {:.6}",
373 epoch,
374 loss.value(),
375 weight.value(),
376 bias.value()
377 );
378 }
379 }
380
381 // Analyze training progression
382 println!("\nTraining Analysis:");
383 println!(" Initial loss: {:.6}", losses[0]);
384 println!(" Final loss: {:.6}", losses[losses.len() - 1]);
385 println!(
386 " Loss reduction: {:.1}%",
387 (losses[0] - losses[losses.len() - 1]) / losses[0] * 100.0
388 );
389
390 // Compute statistics
391 let loss_mean = compute_mean(&losses);
392 let loss_std = compute_std(&losses);
393 let weight_change = (weight_history[weight_history.len() - 1] - weight_history[0]).abs();
394 let bias_change = (bias_history[bias_history.len() - 1] - bias_history[0]).abs();
395
396 println!(" Average loss: {:.6} ± {:.6}", loss_mean, loss_std);
397 println!(" Weight change: {:.6}", weight_change);
398 println!(" Bias change: {:.6}", bias_change);
399 println!(" Final weight norm: {:.6}", weight.norm().value());
400 println!(" Final bias: {:.6}", bias.value());
401
402 Ok(())
403}Source§impl Tensor
impl Tensor
Sourcepub fn mul_tensor(&self, other: &Tensor) -> Tensor
pub fn mul_tensor(&self, other: &Tensor) -> Tensor
Element-wise multiplication with another tensor with broadcasting support.
Performs element-wise multiplication with automatic broadcasting: output[i] = self[i] * other[i]
Broadcasting enables multiplication between tensors of different but compatible shapes. Compatible shapes follow NumPy broadcasting rules:
- Dimensions are aligned from the rightmost dimension
- Dimensions are compatible if they are equal, or one of them is 1
- Missing dimensions are treated as 1
§Arguments
other- Tensor to multiply. Shapes must be broadcast-compatible.
§Returns
A new tensor containing the element-wise product with broadcast result shape
§Examples
§Same Shape Multiplication
use train_station::Tensor;
let a = Tensor::from_slice(&[2.0, 3.0, 4.0], vec![3]).unwrap();
let b = Tensor::from_slice(&[5.0, 6.0, 7.0], vec![3]).unwrap();
let c = a.mul_tensor(&b);
assert_eq!(c.shape().dims, vec![3]);
assert_eq!(c.get(&[0]), 10.0); // 2.0 * 5.0
assert_eq!(c.get(&[1]), 18.0); // 3.0 * 6.0
assert_eq!(c.get(&[2]), 28.0); // 4.0 * 7.0§Broadcasting Multiplication
use train_station::Tensor;
let a = Tensor::from_slice(&[2.0, 3.0], vec![2, 1]).unwrap();
let b = Tensor::from_slice(&[10.0, 20.0, 30.0], vec![1, 3]).unwrap();
let c = a.mul_tensor(&b);
assert_eq!(c.shape().dims, vec![2, 3]);
// Result: [[20.0, 40.0, 60.0], [30.0, 60.0, 90.0]]
assert_eq!(c.get(&[0, 0]), 20.0); // 2.0 * 10.0
assert_eq!(c.get(&[0, 1]), 40.0); // 2.0 * 20.0
assert_eq!(c.get(&[1, 0]), 30.0); // 3.0 * 10.0§Panics
Panics if tensor shapes are not broadcast-compatible
Examples found in repository?
89fn demonstrate_basic_operations() {
90 println!("\n--- Basic Operations ---");
91
92 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
93 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
94
95 // Addition
96 let sum = a.add_tensor(&b);
97 println!("A + B: {:?}", sum.data());
98
99 // Subtraction
100 let diff = a.sub_tensor(&b);
101 println!("A - B: {:?}", diff.data());
102
103 // Multiplication
104 let product = a.mul_tensor(&b);
105 println!("A * B: {:?}", product.data());
106
107 // Division
108 let quotient = a.div_tensor(&b);
109 println!("A / B: {:?}", quotient.data());
110
111 // Scalar operations
112 let scalar_add = a.add_scalar(5.0);
113 println!("A + 5.0: {:?}", scalar_add.data());
114
115 let scalar_mul = a.mul_scalar(2.0);
116 println!("A * 2.0: {:?}", scalar_mul.data());
117}More examples
203fn demonstrate_method_equivalence() {
204 println!("\n--- Operator vs Method Call Equivalence ---");
205
206 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
207 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
208
209 // Addition: operator vs method
210 let operator_result = &a + &b;
211 let method_result = a.add_tensor(&b);
212
213 println!("A + B (operator): {:?}", operator_result.data());
214 println!("A.add_tensor(B): {:?}", method_result.data());
215 println!(
216 "Results are equal: {}",
217 operator_result.data() == method_result.data()
218 );
219
220 // Multiplication: operator vs method
221 let operator_result = &a * &b;
222 let method_result = a.mul_tensor(&b);
223
224 println!("A * B (operator): {:?}", operator_result.data());
225 println!("A.mul_tensor(B): {:?}", method_result.data());
226 println!(
227 "Results are equal: {}",
228 operator_result.data() == method_result.data()
229 );
230
231 // Scalar addition: operator vs method
232 let operator_result = &a + 5.0;
233 let method_result = a.add_scalar(5.0);
234
235 println!("A + 5.0 (operator): {:?}", operator_result.data());
236 println!("A.add_scalar(5.0): {:?}", method_result.data());
237 println!(
238 "Results are equal: {}",
239 operator_result.data() == method_result.data()
240 );
241}192fn demonstrate_advanced_patterns() -> Result<(), Box<dyn std::error::Error>> {
193 println!("\n--- Advanced Iterator Patterns ---");
194
195 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![6])?;
196 println!("Input tensor: {:?}", tensor.data());
197
198 // Complex chain: enumerate -> filter -> map -> collect
199 println!("\nComplex chain (even indices only, add index to value):");
200 let result: Tensor = tensor
201 .iter()
202 .enumerate()
203 .filter(|(i, _)| i % 2 == 0) // Take even indices
204 .map(|(i, elem)| elem.add_scalar(i as f32)) // Add index to value
205 .collect();
206 println!(" Result: {:?}", result.data());
207
208 // Using take and skip for windowing
209 println!("\nWindowing with take and skip:");
210 let window1: Tensor = tensor.iter().take(3).collect();
211 let window2: Tensor = tensor.iter().skip(2).take(3).collect();
212 println!(" Window 1 (first 3): {:?}", window1.data());
213 println!(" Window 2 (middle 3): {:?}", window2.data());
214
215 // Using rev() for reverse iteration
216 println!("\nReverse iteration:");
217 let reversed: Tensor = tensor.iter().rev().collect();
218 println!(" Reversed: {:?}", reversed.data());
219
220 // Chaining with mathematical operations
221 println!("\nMathematical operation chain:");
222 let math_result: Tensor = tensor
223 .iter()
224 .map(|elem| elem.exp()) // e^x
225 .filter(|elem| elem.value() < 50.0) // Filter large values
226 .map(|elem| elem.log()) // ln(x)
227 .collect();
228 println!(" Math chain result: {:?}", math_result.data());
229
230 // Using zip for element-wise combinations
231 println!("\nElement-wise combination with zip:");
232 let tensor2 = Tensor::from_slice(&[10.0, 20.0, 30.0, 40.0, 50.0, 60.0], vec![6])?;
233 let combined: Tensor = tensor
234 .iter()
235 .zip(tensor2.iter())
236 .map(|(a, b)| a.mul_tensor(&b)) // Element-wise multiplication
237 .collect();
238 println!(" Combined: {:?}", combined.data());
239
240 Ok(())
241}Sourcepub fn mul_scalar(&self, scalar: f32) -> Tensor
pub fn mul_scalar(&self, scalar: f32) -> Tensor
Broadcast multiplication with a scalar value.
Multiplies every element by the scalar: output[i] = self[i] * scalar
§Arguments
scalar- Value to multiply with each element
§Returns
A new tensor with each element multiplied by the scalar
§Examples
§Basic Scalar Multiplication
use train_station::Tensor;
let a = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let b = a.mul_scalar(10.0);
assert_eq!(b.shape().dims, vec![3]);
assert_eq!(b.get(&[0]), 10.0); // 1.0 * 10.0
assert_eq!(b.get(&[1]), 20.0); // 2.0 * 10.0
assert_eq!(b.get(&[2]), 30.0); // 3.0 * 10.0§Negative Scalar Multiplication
use train_station::Tensor;
let a = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let b = a.mul_scalar(-2.0);
assert_eq!(b.shape().dims, vec![3]);
assert_eq!(b.get(&[0]), -2.0); // 1.0 * -2.0
assert_eq!(b.get(&[1]), -4.0); // 2.0 * -2.0
assert_eq!(b.get(&[2]), -6.0); // 3.0 * -2.0Examples found in repository?
68 pub fn new(input_size: usize, output_size: usize, seed: Option<u64>) -> Self {
69 let scale = (1.0 / input_size as f32).sqrt();
70
71 let weight = Tensor::randn(vec![input_size, output_size], seed)
72 .mul_scalar(scale)
73 .with_requires_grad();
74 let bias = Tensor::zeros(vec![output_size]).with_requires_grad();
75
76 Self {
77 weight,
78 bias,
79 input_size,
80 output_size,
81 }
82 }More examples
52 pub fn new(input_size: usize, output_size: usize, seed: Option<u64>) -> Self {
53 // Xavier/Glorot initialization: scale by sqrt(1/input_size)
54 let scale = (1.0 / input_size as f32).sqrt();
55
56 let weight = Tensor::randn(vec![input_size, output_size], seed)
57 .mul_scalar(scale)
58 .with_requires_grad();
59 let bias = Tensor::zeros(vec![output_size]).with_requires_grad();
60
61 Self {
62 weight,
63 bias,
64 input_size,
65 output_size,
66 }
67 }89fn demonstrate_basic_operations() {
90 println!("\n--- Basic Operations ---");
91
92 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
93 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
94
95 // Addition
96 let sum = a.add_tensor(&b);
97 println!("A + B: {:?}", sum.data());
98
99 // Subtraction
100 let diff = a.sub_tensor(&b);
101 println!("A - B: {:?}", diff.data());
102
103 // Multiplication
104 let product = a.mul_tensor(&b);
105 println!("A * B: {:?}", product.data());
106
107 // Division
108 let quotient = a.div_tensor(&b);
109 println!("A / B: {:?}", quotient.data());
110
111 // Scalar operations
112 let scalar_add = a.add_scalar(5.0);
113 println!("A + 5.0: {:?}", scalar_add.data());
114
115 let scalar_mul = a.mul_scalar(2.0);
116 println!("A * 2.0: {:?}", scalar_mul.data());
117}77fn demonstrate_basic_iteration() -> Result<(), Box<dyn std::error::Error>> {
78 println!("\n--- Basic Element Iteration ---");
79
80 // Create a simple tensor for demonstration
81 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
82 println!("Original tensor: {:?}", tensor.data());
83
84 // Basic iteration with for loop
85 println!("\nBasic iteration with for loop:");
86 for (i, element) in tensor.iter().enumerate() {
87 println!(
88 " Element {}: value = {:.1}, shape = {:?}",
89 i,
90 element.value(),
91 element.shape().dims
92 );
93 }
94
95 // Element-wise transformation
96 println!("\nElement-wise transformation (2x + 1):");
97 let transformed: Tensor = tensor
98 .iter()
99 .map(|elem| elem.mul_scalar(2.0).add_scalar(1.0))
100 .collect();
101 println!(" Result: {:?}", transformed.data());
102
103 // Filtering elements
104 println!("\nFiltering elements (values > 3.0):");
105 let filtered: Tensor = tensor.iter().filter(|elem| elem.value() > 3.0).collect();
106 println!(" Filtered: {:?}", filtered.data());
107
108 Ok(())
109}
110
111/// Demonstrate standard iterator trait methods
112///
113/// Shows compatibility with Rust's standard library iterator methods
114/// and demonstrates various functional programming patterns.
115fn demonstrate_standard_methods() -> Result<(), Box<dyn std::error::Error>> {
116 println!("\n--- Standard Iterator Methods ---");
117
118 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
119
120 // Using map for transformations
121 println!("\nMap transformation (square each element):");
122 let squared: Tensor = tensor.iter().map(|elem| elem.pow_scalar(2.0)).collect();
123 println!(" Squared: {:?}", squared.data());
124
125 // Using enumerate for indexed operations
126 println!("\nEnumerate with indexed operations:");
127 let indexed: Tensor = tensor
128 .iter()
129 .enumerate()
130 .map(|(i, elem)| elem.add_scalar(i as f32))
131 .collect();
132 println!(" Indexed: {:?}", indexed.data());
133
134 // Using fold for reduction
135 println!("\nFold for sum calculation:");
136 let sum: f32 = tensor.iter().fold(0.0, |acc, elem| acc + elem.value());
137 println!(" Sum: {:.1}", sum);
138
139 // Using find for element search
140 println!("\nFind specific element:");
141 if let Some(found) = tensor.iter().find(|elem| elem.value() == 3.0) {
142 println!(" Found element with value 3.0: {:.1}", found.value());
143 }
144
145 // Using any/all for condition checking
146 println!("\nCondition checking:");
147 let all_positive = tensor.iter().all(|elem| elem.value() > 0.0);
148 let any_large = tensor.iter().any(|elem| elem.value() > 4.0);
149 println!(" All positive: {}", all_positive);
150 println!(" Any > 4.0: {}", any_large);
151
152 Ok(())
153}
154
155/// Demonstrate gradient tracking through element operations
156///
157/// Shows how gradient tracking works seamlessly through iterator
158/// operations, maintaining the computational graph for backpropagation.
159fn demonstrate_gradient_tracking() -> Result<(), Box<dyn std::error::Error>> {
160 println!("\n--- Gradient Tracking ---");
161
162 // Create a tensor with gradient tracking enabled
163 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?.with_requires_grad();
164 println!("Input tensor (requires_grad): {:?}", tensor.data());
165
166 // Perform element-wise operations through iteration
167 let result: Tensor = tensor
168 .iter()
169 .map(|elem| {
170 // Apply a complex transformation: (x^2 + 1) * 2
171 elem.pow_scalar(2.0).add_scalar(1.0).mul_scalar(2.0)
172 })
173 .collect();
174
175 println!("Result tensor: {:?}", result.data());
176 println!("Result requires_grad: {}", result.requires_grad());
177
178 // Compute gradients
179 let mut loss = result.sum();
180 loss.backward(None);
181
182 println!("Loss: {:.6}", loss.value());
183 println!("Input gradients: {:?}", tensor.grad().map(|g| g.data()));
184
185 Ok(())
186}75fn demonstrate_performance_benchmarking() -> Result<(), Box<dyn std::error::Error>> {
76 println!("\n--- Performance Benchmarking ---");
77
78 // Create test data of different sizes
79 let sizes = vec![100, 1000, 10000];
80
81 for size in sizes {
82 println!("\nBenchmarking with tensor size: {}", size);
83
84 // Generate test data
85 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
86 let tensor = Tensor::from_slice(&data, vec![size])?;
87
88 // Benchmark 1: Direct tensor operations
89 let start = Instant::now();
90 let direct_result = tensor.mul_scalar(2.0).add_scalar(1.0);
91 let direct_time = start.elapsed();
92
93 // Benchmark 2: Iterator-based operations
94 let start = Instant::now();
95 let iterator_result: Tensor = tensor
96 .iter()
97 .map(|elem| elem.mul_scalar(2.0).add_scalar(1.0))
98 .collect();
99 let iterator_time = start.elapsed();
100
101 // Benchmark 3: Chained iterator operations
102 let start = Instant::now();
103 let _chained_result: Tensor = tensor
104 .iter()
105 .map(|elem| elem.mul_scalar(2.0))
106 .filter(|elem| elem.value() > size as f32)
107 .map(|elem| elem.add_scalar(1.0))
108 .collect();
109 let chained_time = start.elapsed();
110
111 // Report results
112 println!(" Direct operations: {:?}", direct_time);
113 println!(" Iterator operations: {:?}", iterator_time);
114 println!(" Chained operations: {:?}", chained_time);
115
116 // Verify correctness
117 assert_eq!(direct_result.data(), iterator_result.data());
118 println!(
119 " Results match: {}",
120 direct_result.data() == iterator_result.data()
121 );
122
123 // Performance ratio
124 let ratio = iterator_time.as_nanos() as f64 / direct_time.as_nanos() as f64;
125 println!(" Iterator/Direct ratio: {:.2}x", ratio);
126 }
127
128 Ok(())
129}
130
131/// Demonstrate memory optimization patterns
132///
133/// Shows memory-efficient processing patterns and techniques
134/// for minimizing memory usage while maintaining performance.
135fn demonstrate_memory_optimization() -> Result<(), Box<dyn std::error::Error>> {
136 println!("\n--- Memory Optimization ---");
137
138 // Create a large tensor for memory testing
139 let size = 10000;
140 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
141 let tensor = Tensor::from_slice(&data, vec![size])?;
142
143 println!("Processing tensor of size: {}", size);
144
145 // Pattern 1: Streaming processing (process in chunks)
146 println!("\nPattern 1: Streaming Processing");
147 let chunk_size = 1000;
148 let start = Instant::now();
149
150 let mut streamed_result = Vec::new();
151 for chunk_start in (0..size).step_by(chunk_size) {
152 let chunk_end = (chunk_start + chunk_size).min(size);
153 let chunk: Tensor = tensor
154 .iter_range(chunk_start, chunk_end)
155 .map(|elem| elem.pow_scalar(2.0).sqrt())
156 .collect();
157 streamed_result.extend(chunk.data().iter().cloned());
158 }
159 let streamed_time = start.elapsed();
160
161 // Pattern 2: Full processing
162 let start = Instant::now();
163 let _full_result: Tensor = tensor
164 .iter()
165 .map(|elem| elem.pow_scalar(2.0).sqrt())
166 .collect();
167 let full_time = start.elapsed();
168
169 println!(" Streaming time: {:?}", streamed_time);
170 println!(" Full processing time: {:?}", full_time);
171 println!(
172 " Memory efficiency ratio: {:.2}x",
173 full_time.as_nanos() as f64 / streamed_time.as_nanos() as f64
174 );
175
176 // Pattern 3: Lazy evaluation with take
177 println!("\nPattern 2: Lazy Evaluation");
178 let start = Instant::now();
179 let lazy_result: Tensor = tensor
180 .iter()
181 .take(1000) // Only process first 1000 elements
182 .map(|elem| elem.pow_scalar(2.0).sqrt())
183 .collect();
184 let lazy_time = start.elapsed();
185
186 println!(" Lazy processing (1000 elements): {:?}", lazy_time);
187 println!(" Lazy result size: {}", lazy_result.size());
188
189 // Pattern 4: Memory-efficient filtering
190 println!("\nPattern 3: Memory-Efficient Filtering");
191 let start = Instant::now();
192 let filtered_result: Tensor = tensor
193 .iter()
194 .filter(|elem| elem.value() > size as f32 / 2.0) // Keep only large values
195 .map(|elem| elem.mul_scalar(2.0))
196 .collect();
197 let filtered_time = start.elapsed();
198
199 println!(" Filtered processing: {:?}", filtered_time);
200 println!(
201 " Filtered result size: {} (reduced from {})",
202 filtered_result.size(),
203 size
204 );
205
206 Ok(())
207}
208
209/// Demonstrate large-scale processing techniques
210///
211/// Shows how to efficiently process very large datasets using
212/// iterator patterns and optimization strategies.
213fn demonstrate_large_scale_processing() -> Result<(), Box<dyn std::error::Error>> {
214 println!("\n--- Large-Scale Processing ---");
215
216 // Simulate large dataset processing
217 let sizes = vec![10000, 50000, 100000];
218
219 for size in sizes {
220 println!("\nProcessing dataset of size: {}", size);
221
222 // Generate large dataset
223 let data: Vec<f32> = (0..size)
224 .map(|i| {
225 let x = i as f32 / size as f32;
226 x * x + 0.1 * (i % 10) as f32 // Quadratic with noise
227 })
228 .collect();
229
230 let tensor = Tensor::from_slice(&data, vec![size])?;
231
232 // Technique 1: Batch processing
233 let batch_size = 1000;
234 let start = Instant::now();
235
236 let mut batch_results = Vec::new();
237 for batch_start in (0..size).step_by(batch_size) {
238 let batch_end = (batch_start + batch_size).min(size);
239 let batch: Tensor = tensor
240 .iter_range(batch_start, batch_end)
241 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
242 .collect();
243 batch_results.push(batch);
244 }
245 let batch_time = start.elapsed();
246
247 // Technique 2: Parallel-like processing with stride
248 let start = Instant::now();
249 let stride = 4;
250 let strided_result: Tensor = tensor
251 .iter()
252 .enumerate()
253 .filter(|(i, _)| i % stride == 0)
254 .map(|(_, elem)| elem.pow_scalar(2.0).add_scalar(1.0))
255 .collect();
256 let strided_time = start.elapsed();
257
258 // Technique 3: Hierarchical processing
259 let start = Instant::now();
260 let coarse: Tensor = tensor
261 .iter()
262 .enumerate()
263 .filter(|(i, _)| i % 10 == 0) // Every 10th element
264 .map(|(_, elem)| elem.pow_scalar(2.0).add_scalar(1.0))
265 .collect();
266 let fine: Tensor = tensor
267 .iter()
268 .enumerate()
269 .filter(|(i, _)| i % 10 != 0) // Rest of elements
270 .map(|(_, elem)| elem.pow_scalar(1.5).add_scalar(0.5))
271 .collect();
272 let hierarchical_time = start.elapsed();
273
274 // Report performance
275 println!(" Batch processing: {:?}", batch_time);
276 println!(" Strided processing: {:?}", strided_time);
277 println!(" Hierarchical processing: {:?}", hierarchical_time);
278
279 // Memory usage analysis
280 let total_batches = (size + batch_size - 1) / batch_size;
281 println!(" Batch count: {}", total_batches);
282 println!(" Strided result size: {}", strided_result.size());
283 println!(
284 " Hierarchical: coarse={}, fine={}",
285 coarse.size(),
286 fine.size()
287 );
288 }
289
290 Ok(())
291}
292
293/// Demonstrate advanced optimization techniques
294///
295/// Shows sophisticated optimization strategies and techniques
296/// for maximizing performance in tensor iterator operations.
297fn demonstrate_optimization_techniques() -> Result<(), Box<dyn std::error::Error>> {
298 println!("\n--- Optimization Techniques ---");
299
300 let size = 50000;
301 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
302 let tensor = Tensor::from_slice(&data, vec![size])?;
303
304 println!("Optimizing processing for size: {}", size);
305
306 // Technique 1: Operation fusion
307 println!("\nTechnique 1: Operation Fusion");
308 let start = Instant::now();
309 let fused_result: Tensor = tensor
310 .iter()
311 .map(|elem| {
312 // Fuse multiple operations into single chain
313 elem.mul_scalar(2.0).add_scalar(1.0).pow_scalar(2.0).sqrt()
314 })
315 .collect();
316 let fused_time = start.elapsed();
317
318 // Technique 2: Conditional optimization
319 println!("\nTechnique 2: Conditional Optimization");
320 let start = Instant::now();
321 let conditional_result: Tensor = tensor
322 .iter()
323 .map(|elem| {
324 let val = elem.value();
325 if val < size as f32 / 2.0 {
326 elem.mul_scalar(2.0) // Simple operation for small values
327 } else {
328 elem.pow_scalar(2.0).sqrt() // Complex operation for large values
329 }
330 })
331 .collect();
332 let conditional_time = start.elapsed();
333
334 // Technique 3: Cache-friendly processing
335 println!("\nTechnique 3: Cache-Friendly Processing");
336 let start = Instant::now();
337 let cache_friendly_result: Tensor = tensor
338 .iter()
339 .take(1000) // Process in cache-friendly chunks
340 .map(|elem| elem.mul_scalar(2.0))
341 .collect();
342 let cache_friendly_time = start.elapsed();
343
344 // Technique 4: Memory pooling simulation
345 println!("\nTechnique 4: Memory Pooling Simulation");
346 let start = Instant::now();
347 let pooled_result: Tensor = tensor
348 .iter()
349 .enumerate()
350 .filter(|(i, _)| i % 100 == 0) // Process every 100th element
351 .map(|(_, elem)| elem.pow_scalar(2.0))
352 .collect();
353 let pooled_time = start.elapsed();
354
355 // Report optimization results
356 println!(" Fused operations: {:?}", fused_time);
357 println!(" Conditional optimization: {:?}", conditional_time);
358 println!(" Cache-friendly processing: {:?}", cache_friendly_time);
359 println!(" Memory pooling simulation: {:?}", pooled_time);
360
361 // Performance analysis
362 let fastest = fused_time
363 .min(conditional_time)
364 .min(cache_friendly_time)
365 .min(pooled_time);
366 println!(" Fastest technique: {:?}", fastest);
367
368 // Memory efficiency analysis
369 println!(" Fused result size: {}", fused_result.size());
370 println!(" Conditional result size: {}", conditional_result.size());
371 println!(
372 " Cache-friendly result size: {}",
373 cache_friendly_result.size()
374 );
375 println!(" Pooled result size: {}", pooled_result.size());
376
377 // Technique 5: Gradient optimization
378 println!("\nTechnique 5: Gradient Optimization");
379 let grad_tensor = tensor.with_requires_grad();
380 let start = Instant::now();
381
382 let grad_result: Tensor = grad_tensor
383 .iter()
384 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
385 .collect();
386
387 let mut loss = grad_result.sum();
388 loss.backward(None);
389 let grad_time = start.elapsed();
390
391 println!(" Gradient computation: {:?}", grad_time);
392 println!(
393 " Gradient tracking enabled: {}",
394 grad_result.requires_grad()
395 );
396
397 Ok(())
398}161fn demonstrate_conditional_processing() -> Result<(), Box<dyn std::error::Error>> {
162 println!("\n--- Conditional Processing ---");
163
164 // Create data with mixed characteristics
165 let data = vec![1.0, -2.0, 3.0, -4.0, 5.0, -6.0, 7.0, -8.0, 9.0, -10.0];
166 let tensor = Tensor::from_slice(&data, vec![10])?;
167 println!("Input data: {:?}", tensor.data());
168
169 // Conditional transformation based on sign
170 println!("\nConditional transformation (positive/negative handling):");
171 let processed: Tensor = tensor
172 .iter()
173 .map(|elem| {
174 let val = elem.value();
175 if val > 0.0 {
176 elem.pow_scalar(2.0) // Square positive values
177 } else {
178 elem.mul_scalar(-1.0).sqrt() // Square root of absolute negative values
179 }
180 })
181 .collect();
182 println!(" Processed: {:?}", processed.data());
183
184 // Adaptive filtering based on local statistics
185 println!("\nAdaptive filtering (remove values > 2 std from local mean):");
186 let window_size = 3;
187 let adaptive_filtered: Tensor = tensor
188 .iter()
189 .enumerate()
190 .filter(|(i, elem)| {
191 let start = i.saturating_sub(window_size / 2);
192 let end = (i + window_size / 2 + 1).min(tensor.size());
193
194 // Calculate local mean and std
195 let local_values: Vec<f32> = (start..end)
196 .map(|j| tensor.element_view(j).value())
197 .collect();
198
199 let local_mean = local_values.iter().sum::<f32>() / local_values.len() as f32;
200 let local_variance = local_values
201 .iter()
202 .map(|v| (v - local_mean).powi(2))
203 .sum::<f32>()
204 / local_values.len() as f32;
205 let local_std = local_variance.sqrt();
206
207 let threshold = local_mean + 2.0 * local_std;
208 elem.value() <= threshold
209 })
210 .map(|(_, elem)| elem)
211 .collect();
212 println!(" Adaptive filtered: {:?}", adaptive_filtered.data());
213
214 // Multi-condition processing
215 println!("\nMulti-condition processing:");
216 let multi_processed: Tensor = tensor
217 .iter()
218 .map(|elem| {
219 let val = elem.value();
220 match () {
221 _ if val > 5.0 => elem.mul_scalar(2.0), // Double large values
222 _ if val < -5.0 => elem.div_scalar(2.0), // Halve small values
223 _ if val.abs() < 2.0 => elem.add_scalar(1.0), // Add 1 to small values
224 _ => elem.clone(), // Keep others unchanged
225 }
226 })
227 .collect();
228 println!(" Multi-condition: {:?}", multi_processed.data());
229
230 Ok(())
231}
232
233/// Demonstrate batch processing operations
234///
235/// Shows efficient processing of large datasets using iterator
236/// patterns and batch operations for performance optimization.
237fn demonstrate_batch_operations() -> Result<(), Box<dyn std::error::Error>> {
238 println!("\n--- Batch Operations ---");
239
240 // Create a larger dataset for batch processing
241 let size = 100;
242 let data: Vec<f32> = (0..size)
243 .map(|i| {
244 let x = i as f32 / size as f32;
245 x * x + 0.1 * (i % 7) as f32 // Quadratic with some noise
246 })
247 .collect();
248
249 let tensor = Tensor::from_slice(&data, vec![size])?;
250 println!("Dataset size: {}", tensor.size());
251
252 // Batch processing with windowing
253 println!("\nBatch processing with sliding windows:");
254 let batch_size = 10;
255 let batches: Vec<Tensor> = tensor
256 .iter()
257 .collect::<Vec<_>>()
258 .chunks(batch_size)
259 .map(|chunk| {
260 // Process each batch independently
261 chunk
262 .iter()
263 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
264 .collect()
265 })
266 .collect();
267
268 println!(
269 " Processed {} batches of size {}",
270 batches.len(),
271 batch_size
272 );
273 for (i, batch) in batches.iter().enumerate() {
274 println!(
275 " Batch {}: mean={:.3}, std={:.3}",
276 i,
277 batch.mean().value(),
278 batch.std().value()
279 );
280 }
281
282 // Parallel-like processing with stride
283 println!("\nStrided processing (every nth element):");
284 let stride = 5;
285 let strided: Tensor = tensor
286 .iter()
287 .enumerate()
288 .filter(|(i, _)| i % stride == 0)
289 .map(|(_, elem)| elem)
290 .collect();
291 println!(" Strided (every {}th): {:?}", stride, strided.data());
292
293 // Hierarchical processing
294 println!("\nHierarchical processing (coarse to fine):");
295 let coarse: Tensor = tensor
296 .iter()
297 .enumerate()
298 .filter(|(i, _)| i % 4 == 0) // Take every 4th element
299 .map(|(_, elem)| elem)
300 .collect();
301
302 let fine: Tensor = tensor
303 .iter()
304 .enumerate()
305 .filter(|(i, _)| i % 4 != 0) // Take the rest
306 .map(|(_, elem)| elem)
307 .collect();
308
309 println!(" Coarse (every 4th): {:?}", coarse.data());
310 println!(" Fine (rest): {:?}", fine.data());
311
312 // Combine coarse and fine with different processing
313 let combined: Tensor = coarse
314 .iter()
315 .map(|elem| elem.mul_scalar(2.0)) // Scale coarse
316 .chain(fine.iter().map(|elem| elem.div_scalar(2.0))) // Scale fine
317 .collect();
318 println!(" Combined: {:?}", combined.data());
319
320 Ok(())
321}
322
323/// Demonstrate real-world processing scenarios
324///
325/// Shows practical applications of iterator patterns for
326/// common data processing tasks in machine learning and analytics.
327fn demonstrate_real_world_scenarios() -> Result<(), Box<dyn std::error::Error>> {
328 println!("\n--- Real-world Scenarios ---");
329
330 // Scenario 1: Time series analysis
331 println!("\nScenario 1: Time Series Analysis");
332 let time_series: Vec<f32> = (0..24)
333 .map(|hour| {
334 let base = 20.0 + 10.0 * (hour as f32 * std::f32::consts::PI / 12.0).sin();
335 base + (hour % 3) as f32 * 2.0 // Add some noise
336 })
337 .collect();
338
339 let series = Tensor::from_slice(&time_series, vec![24])?;
340 println!(" Time series (24 hours): {:?}", series.data());
341
342 // Calculate moving average
343 let window_size = 3;
344 let moving_avg: Tensor = series
345 .iter()
346 .enumerate()
347 .map(|(i, _)| {
348 let start = i.saturating_sub(window_size / 2);
349 let end = (i + window_size / 2 + 1).min(series.size());
350 let window = series.iter_range(start, end);
351 window.fold(0.0, |acc, elem| acc + elem.value()) / (end - start) as f32
352 })
353 .map(|val| Tensor::from_slice(&[val], vec![1]).unwrap())
354 .collect();
355 println!(
356 " Moving average (window={}): {:?}",
357 window_size,
358 moving_avg.data()
359 );
360
361 // Scenario 2: Feature engineering
362 println!("\nScenario 2: Feature Engineering");
363 let features = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
364 println!(" Original features: {:?}", features.data());
365
366 // Create polynomial features
367 let poly_features: Tensor = features
368 .iter()
369 .flat_map(|elem| {
370 vec![
371 elem.clone(), // x^1
372 elem.pow_scalar(2.0), // x^2
373 elem.pow_scalar(3.0), // x^3
374 ]
375 })
376 .collect();
377 println!(
378 " Polynomial features (x, x^2, x^3): {:?}",
379 poly_features.data()
380 );
381
382 // Scenario 3: Data augmentation
383 println!("\nScenario 3: Data Augmentation");
384 let original = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?;
385 println!(" Original data: {:?}", original.data());
386
387 // Augment with noise and scaling
388 let augmented: Tensor = original
389 .iter()
390 .flat_map(|elem| {
391 vec![
392 elem.clone(), // Original
393 elem.add_scalar(0.1), // Add noise
394 elem.sub_scalar(0.1), // Subtract noise
395 elem.mul_scalar(1.1), // Scale up
396 elem.mul_scalar(0.9), // Scale down
397 ]
398 })
399 .collect();
400 println!(" Augmented data: {:?}", augmented.data());
401
402 // Scenario 4: Statistical analysis
403 println!("\nScenario 4: Statistical Analysis");
404 let sample_data = Tensor::from_slice(&[1.1, 2.3, 1.8, 2.1, 1.9, 2.0, 1.7, 2.2], vec![8])?;
405 println!(" Sample data: {:?}", sample_data.data());
406
407 // Calculate various statistics
408 let mean = sample_data.mean().value();
409 let std = sample_data.std().value();
410 let min = sample_data
411 .iter()
412 .map(|e| e.value())
413 .fold(f32::INFINITY, f32::min);
414 let max = sample_data
415 .iter()
416 .map(|e| e.value())
417 .fold(f32::NEG_INFINITY, f32::max);
418
419 // Z-score normalization
420 let z_scores: Tensor = sample_data
421 .iter()
422 .map(|elem| elem.sub_scalar(mean).div_scalar(std))
423 .collect();
424
425 println!(
426 " Statistics: mean={:.3}, std={:.3}, min={:.3}, max={:.3}",
427 mean, std, min, max
428 );
429 println!(" Z-scores: {:?}", z_scores.data());
430
431 Ok(())
432}Source§impl Tensor
impl Tensor
Sourcepub fn pow_scalar(&self, exponent: f32) -> Tensor
pub fn pow_scalar(&self, exponent: f32) -> Tensor
Raises each element to a scalar power.
Computes element-wise power: output[i] = self[i]^exponent
§Arguments
exponent- The scalar exponent to raise each element to
§Returns
A new tensor with each element raised to the given power
§Examples
§Basic Scalar Power
use train_station::Tensor;
let a = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let b = a.pow_scalar(2.0);
assert_eq!(b.shape().dims, vec![3]);
assert_eq!(b.get(&[0]), 1.0); // 1.0^2 = 1.0
assert_eq!(b.get(&[1]), 4.0); // 2.0^2 = 4.0
assert_eq!(b.get(&[2]), 9.0); // 3.0^2 = 9.0§Square Root (Power 0.5)
use train_station::Tensor;
let a = Tensor::from_slice(&[1.0, 4.0, 9.0], vec![3]).unwrap();
let b = a.pow_scalar(0.5);
assert_eq!(b.shape().dims, vec![3]);
assert_eq!(b.get(&[0]), 1.0); // sqrt(1.0) = 1.0
assert_eq!(b.get(&[1]), 2.0); // sqrt(4.0) = 2.0
assert_eq!(b.get(&[2]), 3.0); // sqrt(9.0) = 3.0Examples found in repository?
115fn demonstrate_standard_methods() -> Result<(), Box<dyn std::error::Error>> {
116 println!("\n--- Standard Iterator Methods ---");
117
118 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
119
120 // Using map for transformations
121 println!("\nMap transformation (square each element):");
122 let squared: Tensor = tensor.iter().map(|elem| elem.pow_scalar(2.0)).collect();
123 println!(" Squared: {:?}", squared.data());
124
125 // Using enumerate for indexed operations
126 println!("\nEnumerate with indexed operations:");
127 let indexed: Tensor = tensor
128 .iter()
129 .enumerate()
130 .map(|(i, elem)| elem.add_scalar(i as f32))
131 .collect();
132 println!(" Indexed: {:?}", indexed.data());
133
134 // Using fold for reduction
135 println!("\nFold for sum calculation:");
136 let sum: f32 = tensor.iter().fold(0.0, |acc, elem| acc + elem.value());
137 println!(" Sum: {:.1}", sum);
138
139 // Using find for element search
140 println!("\nFind specific element:");
141 if let Some(found) = tensor.iter().find(|elem| elem.value() == 3.0) {
142 println!(" Found element with value 3.0: {:.1}", found.value());
143 }
144
145 // Using any/all for condition checking
146 println!("\nCondition checking:");
147 let all_positive = tensor.iter().all(|elem| elem.value() > 0.0);
148 let any_large = tensor.iter().any(|elem| elem.value() > 4.0);
149 println!(" All positive: {}", all_positive);
150 println!(" Any > 4.0: {}", any_large);
151
152 Ok(())
153}
154
155/// Demonstrate gradient tracking through element operations
156///
157/// Shows how gradient tracking works seamlessly through iterator
158/// operations, maintaining the computational graph for backpropagation.
159fn demonstrate_gradient_tracking() -> Result<(), Box<dyn std::error::Error>> {
160 println!("\n--- Gradient Tracking ---");
161
162 // Create a tensor with gradient tracking enabled
163 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?.with_requires_grad();
164 println!("Input tensor (requires_grad): {:?}", tensor.data());
165
166 // Perform element-wise operations through iteration
167 let result: Tensor = tensor
168 .iter()
169 .map(|elem| {
170 // Apply a complex transformation: (x^2 + 1) * 2
171 elem.pow_scalar(2.0).add_scalar(1.0).mul_scalar(2.0)
172 })
173 .collect();
174
175 println!("Result tensor: {:?}", result.data());
176 println!("Result requires_grad: {}", result.requires_grad());
177
178 // Compute gradients
179 let mut loss = result.sum();
180 loss.backward(None);
181
182 println!("Loss: {:.6}", loss.value());
183 println!("Input gradients: {:?}", tensor.grad().map(|g| g.data()));
184
185 Ok(())
186}More examples
84fn demonstrate_default_adam() -> Result<(), Box<dyn std::error::Error>> {
85 println!("--- Default Adam Configuration ---");
86
87 // Create a simple regression problem: y = 2*x + 1
88 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
89 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
90
91 // Create model parameters
92 let mut weight = Tensor::randn(vec![1, 1], Some(42)).with_requires_grad();
93 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
94
95 // Create Adam optimizer with default configuration
96 let mut optimizer = Adam::new();
97 optimizer.add_parameter(&weight);
98 optimizer.add_parameter(&bias);
99
100 println!("Default Adam configuration:");
101 println!(" Learning rate: {}", optimizer.learning_rate());
102 println!(" Initial weight: {:.6}", weight.value());
103 println!(" Initial bias: {:.6}", bias.value());
104
105 // Training loop
106 let num_epochs = 50;
107 let mut losses = Vec::new();
108
109 for epoch in 0..num_epochs {
110 // Forward pass
111 let y_pred = x_data.matmul(&weight) + &bias;
112 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
113
114 // Backward pass
115 loss.backward(None);
116
117 // Optimizer step
118 optimizer.step(&mut [&mut weight, &mut bias]);
119 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
120
121 losses.push(loss.value());
122
123 if epoch % 10 == 0 || epoch == num_epochs - 1 {
124 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
125 }
126 }
127
128 // Evaluate final model
129 let _final_predictions = x_data.matmul(&weight) + &bias;
130 println!("\nFinal model:");
131 println!(" Learned weight: {:.6} (target: 2.0)", weight.value());
132 println!(" Learned bias: {:.6} (target: 1.0)", bias.value());
133 println!(" Final loss: {:.6}", losses[losses.len() - 1]);
134
135 Ok(())
136}
137
138/// Demonstrate learning rate comparison
139fn demonstrate_learning_rate_comparison() -> Result<(), Box<dyn std::error::Error>> {
140 println!("\n--- Learning Rate Comparison ---");
141
142 let learning_rates = [0.001, 0.01, 0.1];
143 let mut results = Vec::new();
144
145 for &lr in &learning_rates {
146 println!("\nTesting learning rate: {}", lr);
147
148 let stats = train_with_config(TrainingConfig {
149 learning_rate: lr,
150 ..Default::default()
151 })?;
152
153 results.push((lr, stats.clone()));
154
155 println!(" Final loss: {:.6}", stats.final_loss);
156 println!(" Convergence epoch: {}", stats.convergence_epoch);
157 }
158
159 // Compare results
160 println!("\nLearning Rate Comparison Summary:");
161 for (lr, stats) in &results {
162 println!(
163 " LR={:6}: Loss={:.6}, Converged@{}",
164 lr, stats.final_loss, stats.convergence_epoch
165 );
166 }
167
168 Ok(())
169}
170
171/// Demonstrate weight decay comparison
172fn demonstrate_weight_decay_comparison() -> Result<(), Box<dyn std::error::Error>> {
173 println!("\n--- Weight Decay Comparison ---");
174
175 let weight_decays = [0.0, 0.001, 0.01];
176 let mut results = Vec::new();
177
178 for &wd in &weight_decays {
179 println!("\nTesting weight decay: {}", wd);
180
181 let stats = train_with_config(TrainingConfig {
182 weight_decay: wd,
183 ..Default::default()
184 })?;
185
186 results.push((wd, stats.clone()));
187
188 println!(" Final loss: {:.6}", stats.final_loss);
189 println!(" Final weight norm: {:.6}", stats.weight_norm);
190 }
191
192 // Compare results
193 println!("\nWeight Decay Comparison Summary:");
194 for (wd, stats) in &results {
195 println!(
196 " WD={:6}: Loss={:.6}, Weight Norm={:.6}",
197 wd, stats.final_loss, stats.weight_norm
198 );
199 }
200
201 Ok(())
202}
203
204/// Demonstrate beta parameter tuning
205fn demonstrate_beta_parameter_tuning() -> Result<(), Box<dyn std::error::Error>> {
206 println!("\n--- Beta Parameter Tuning ---");
207
208 let beta_configs = [
209 (0.9, 0.999), // Default
210 (0.8, 0.999), // More aggressive momentum
211 (0.95, 0.999), // Less aggressive momentum
212 (0.9, 0.99), // Faster second moment decay
213 ];
214
215 let mut results = Vec::new();
216
217 for (i, (beta1, beta2)) in beta_configs.iter().enumerate() {
218 println!(
219 "\nTesting beta configuration {}: beta1={}, beta2={}",
220 i + 1,
221 beta1,
222 beta2
223 );
224
225 let config = TrainingConfig {
226 beta1: *beta1,
227 beta2: *beta2,
228 ..Default::default()
229 };
230
231 let stats = train_with_config(config)?;
232 results.push(((*beta1, *beta2), stats.clone()));
233
234 println!(" Final loss: {:.6}", stats.final_loss);
235 println!(" Convergence epoch: {}", stats.convergence_epoch);
236 }
237
238 // Compare results
239 println!("\nBeta Parameter Comparison Summary:");
240 for ((beta1, beta2), stats) in &results {
241 println!(
242 " B1={:4}, B2={:5}: Loss={:.6}, Converged@{}",
243 beta1, beta2, stats.final_loss, stats.convergence_epoch
244 );
245 }
246
247 Ok(())
248}
249
250/// Demonstrate configuration benchmarking
251fn demonstrate_configuration_benchmarking() -> Result<(), Box<dyn std::error::Error>> {
252 println!("\n--- Configuration Benchmarking ---");
253
254 // Define configurations to benchmark
255 let configs = vec![
256 (
257 "Conservative",
258 TrainingConfig {
259 learning_rate: 0.001,
260 weight_decay: 0.001,
261 beta1: 0.95,
262 ..Default::default()
263 },
264 ),
265 (
266 "Balanced",
267 TrainingConfig {
268 learning_rate: 0.01,
269 weight_decay: 0.0,
270 beta1: 0.9,
271 ..Default::default()
272 },
273 ),
274 (
275 "Aggressive",
276 TrainingConfig {
277 learning_rate: 0.1,
278 weight_decay: 0.0,
279 beta1: 0.8,
280 ..Default::default()
281 },
282 ),
283 ];
284
285 let mut benchmark_results = Vec::new();
286
287 for (name, config) in configs {
288 println!("\nBenchmarking {} configuration:", name);
289
290 let start_time = std::time::Instant::now();
291 let stats = train_with_config(config.clone())?;
292 let elapsed = start_time.elapsed();
293
294 println!(" Training time: {:.2}ms", elapsed.as_millis());
295 println!(" Final loss: {:.6}", stats.final_loss);
296 println!(" Convergence: {} epochs", stats.convergence_epoch);
297
298 benchmark_results.push((name.to_string(), stats, elapsed));
299 }
300
301 // Summary
302 println!("\nBenchmarking Summary:");
303 for (name, stats, elapsed) in &benchmark_results {
304 println!(
305 " {:12}: Loss={:.6}, Time={:4}ms, Converged@{}",
306 name,
307 stats.final_loss,
308 elapsed.as_millis(),
309 stats.convergence_epoch
310 );
311 }
312
313 Ok(())
314}
315
316/// Helper function to train with specific configuration
317fn train_with_config(config: TrainingConfig) -> Result<TrainingStats, Box<dyn std::error::Error>> {
318 // Create training data
319 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
320 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
321
322 // Create model parameters
323 let mut weight = Tensor::randn(vec![1, 1], Some(123)).with_requires_grad();
324 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
325
326 // Create optimizer with custom configuration
327 let adam_config = AdamConfig {
328 learning_rate: config.learning_rate,
329 beta1: config.beta1,
330 beta2: config.beta2,
331 eps: 1e-8,
332 weight_decay: config.weight_decay,
333 amsgrad: false,
334 };
335
336 let mut optimizer = Adam::with_config(adam_config);
337 optimizer.add_parameter(&weight);
338 optimizer.add_parameter(&bias);
339
340 // Training loop
341 let mut losses = Vec::new();
342 let mut convergence_epoch = config.epochs;
343
344 for epoch in 0..config.epochs {
345 // Forward pass
346 let y_pred = x_data.matmul(&weight) + &bias;
347 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
348
349 // Backward pass
350 loss.backward(None);
351
352 // Optimizer step
353 optimizer.step(&mut [&mut weight, &mut bias]);
354 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
355
356 let loss_value = loss.value();
357 losses.push(loss_value);
358
359 // Check for convergence (loss < 0.01)
360 if loss_value < 0.01 && convergence_epoch == config.epochs {
361 convergence_epoch = epoch;
362 }
363 }
364
365 Ok(TrainingStats {
366 config,
367 final_loss: losses[losses.len() - 1],
368 loss_history: losses,
369 convergence_epoch,
370 weight_norm: weight.norm().value(),
371 })
372}319fn train_with_scheduler(
320 scheduler: &mut dyn LearningRateScheduler,
321 num_epochs: usize,
322) -> Result<TrainingStats, Box<dyn std::error::Error>> {
323 // Create training data: y = 2*x + 1
324 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
325 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
326
327 // Create model parameters
328 let mut weight = Tensor::randn(vec![1, 1], Some(456)).with_requires_grad();
329 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
330
331 // Create optimizer with initial learning rate
332 let mut optimizer = Adam::with_learning_rate(0.05);
333 optimizer.add_parameter(&weight);
334 optimizer.add_parameter(&bias);
335
336 // Training loop
337 let mut losses = Vec::new();
338 let mut lr_history = Vec::new();
339 let mut convergence_epoch = num_epochs;
340
341 for epoch in 0..num_epochs {
342 // Forward pass
343 let y_pred = x_data.matmul(&weight) + &bias;
344 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
345
346 // Backward pass
347 loss.backward(None);
348
349 // Update learning rate using scheduler
350 let current_lr = optimizer.learning_rate();
351 let new_lr = scheduler.step(current_lr, epoch, loss.value());
352
353 if (new_lr - current_lr).abs() > 1e-8 {
354 optimizer.set_learning_rate(new_lr);
355 }
356
357 // Optimizer step
358 optimizer.step(&mut [&mut weight, &mut bias]);
359 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
360
361 let loss_value = loss.value();
362 losses.push(loss_value);
363 lr_history.push(new_lr);
364
365 // Check for convergence
366 if loss_value < 0.01 && convergence_epoch == num_epochs {
367 convergence_epoch = epoch;
368 }
369 }
370
371 Ok(TrainingStats {
372 scheduler_name: scheduler.name().to_string(),
373 final_loss: losses[losses.len() - 1],
374 lr_history,
375 loss_history: losses,
376 convergence_epoch,
377 })
378}105fn demonstrate_linear_regression() -> Result<(), Box<dyn std::error::Error>> {
106 println!("\n--- Linear Regression Training ---");
107
108 // Create model parameters
109 let mut weight = Tensor::randn(vec![1, 1], Some(43)).with_requires_grad();
110 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
111
112 // Create optimizer
113 let mut optimizer = Adam::with_learning_rate(0.01);
114 optimizer.add_parameter(&weight);
115 optimizer.add_parameter(&bias);
116
117 // Create simple training data: y = 2*x + 1
118 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
119 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
120
121 println!("Training data:");
122 println!(" X: {:?}", x_data.data());
123 println!(" Y: {:?}", y_true.data());
124 println!(" Target: y = 2*x + 1");
125
126 // Training loop
127 let num_epochs = 100;
128 let mut losses = Vec::new();
129
130 for epoch in 0..num_epochs {
131 // Forward pass: y_pred = x * weight + bias
132 let y_pred = x_data.matmul(&weight) + &bias;
133
134 // Compute loss: MSE
135 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
136
137 // Backward pass
138 loss.backward(None);
139
140 // Optimizer step
141 optimizer.step(&mut [&mut weight, &mut bias]);
142 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
143
144 losses.push(loss.value());
145
146 // Print progress every 20 epochs
147 if epoch % 20 == 0 || epoch == num_epochs - 1 {
148 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
149 }
150 }
151
152 // Evaluate final model
153 let final_predictions = x_data.matmul(&weight) + &bias;
154 println!("\nFinal model evaluation:");
155 println!(" Learned weight: {:.6}", weight.value());
156 println!(" Learned bias: {:.6}", bias.value());
157 println!(" Predictions vs True:");
158
159 for i in 0..5 {
160 let x1 = x_data.data()[i];
161 let pred = final_predictions.data()[i];
162 let true_val = y_true.data()[i];
163 println!(
164 " x={:.1}: pred={:.3}, true={:.1}, error={:.3}",
165 x1,
166 pred,
167 true_val,
168 (pred - true_val).abs()
169 );
170 }
171
172 Ok(())
173}
174
175/// Demonstrate advanced training patterns
176fn demonstrate_advanced_training() -> Result<(), Box<dyn std::error::Error>> {
177 println!("\n--- Advanced Training Patterns ---");
178
179 // Create a more complex model
180 let mut weight = Tensor::randn(vec![1, 2], Some(44)).with_requires_grad();
181 let mut bias = Tensor::zeros(vec![2]).with_requires_grad();
182
183 // Create optimizer with different learning rate
184 let mut optimizer = Adam::with_learning_rate(0.005);
185 optimizer.add_parameter(&weight);
186 optimizer.add_parameter(&bias);
187
188 // Create training data: y = 2*x + [1, 3]
189 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
190 let y_true = Tensor::from_slice(
191 &[3.0, 5.0, 7.0, 9.0, 11.0, 6.0, 8.0, 10.0, 12.0, 14.0],
192 vec![5, 2],
193 )
194 .unwrap();
195
196 println!("Advanced training with monitoring:");
197 println!(" Initial learning rate: {}", optimizer.learning_rate());
198
199 // Training loop with monitoring
200 let num_epochs = 50;
201 let mut losses = Vec::new();
202 let mut weight_norms = Vec::new();
203 let mut gradient_norms = Vec::new();
204
205 for epoch in 0..num_epochs {
206 // Forward pass
207 let y_pred = x_data.matmul(&weight) + &bias;
208 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
209
210 // Backward pass
211 loss.backward(None);
212
213 // Compute gradient norm before optimizer step
214 let gradient_norm = weight.grad_by_value().unwrap().norm();
215
216 // Optimizer step
217 optimizer.step(&mut [&mut weight, &mut bias]);
218 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
219
220 // Learning rate scheduling: reduce every 10 epochs
221 if epoch > 0 && epoch % 10 == 0 {
222 let current_lr = optimizer.learning_rate();
223 let new_lr = current_lr * 0.5;
224 optimizer.set_learning_rate(new_lr);
225 println!(
226 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
227 epoch, current_lr, new_lr
228 );
229 }
230
231 // Record metrics
232 losses.push(loss.value());
233 weight_norms.push(weight.norm().value());
234 gradient_norms.push(gradient_norm.value());
235
236 // Print detailed progress
237 if epoch % 10 == 0 || epoch == num_epochs - 1 {
238 println!(
239 "Epoch {:2}: Loss = {:.6}, Weight Norm = {:.6}, Gradient Norm = {:.6}",
240 epoch,
241 loss.value(),
242 weight.norm().value(),
243 gradient_norm.value()
244 );
245 }
246 }
247
248 println!("Final learning rate: {}", optimizer.learning_rate());
249
250 // Analyze training progression
251 let initial_loss = losses[0];
252 let final_loss = losses[losses.len() - 1];
253 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
254
255 println!("\nTraining Analysis:");
256 println!(" Initial loss: {:.6}", initial_loss);
257 println!(" Final loss: {:.6}", final_loss);
258 println!(" Loss reduction: {:.1}%", loss_reduction);
259 println!(" Final weight norm: {:.6}", weight.norm().value());
260 println!(" Final bias: {:?}", bias.data());
261
262 Ok(())
263}
264
265/// Demonstrate learning rate scheduling
266fn demonstrate_learning_rate_scheduling() -> Result<(), Box<dyn std::error::Error>> {
267 println!("\n--- Learning Rate Scheduling ---");
268
269 // Create simple model
270 let mut weight = Tensor::randn(vec![1, 1], Some(45)).with_requires_grad();
271 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
272
273 // Create optimizer with high initial learning rate
274 let mut optimizer = Adam::with_learning_rate(0.1);
275 optimizer.add_parameter(&weight);
276 optimizer.add_parameter(&bias);
277
278 // Simple data
279 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3, 1]).unwrap();
280 let y_true = Tensor::from_slice(&[2.0, 4.0, 6.0], vec![3, 1]).unwrap();
281
282 println!("Initial learning rate: {}", optimizer.learning_rate());
283
284 // Training loop with learning rate scheduling
285 let num_epochs = 50;
286 let mut losses = Vec::new();
287
288 for epoch in 0..num_epochs {
289 // Forward pass
290 let y_pred = x_data.matmul(&weight) + &bias;
291 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
292
293 // Backward pass
294 loss.backward(None);
295
296 // Optimizer step
297 optimizer.step(&mut [&mut weight, &mut bias]);
298 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
299
300 // Learning rate scheduling: reduce every 10 epochs
301 if epoch > 0 && epoch % 10 == 0 {
302 let current_lr = optimizer.learning_rate();
303 let new_lr = current_lr * 0.5;
304 optimizer.set_learning_rate(new_lr);
305 println!(
306 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
307 epoch, current_lr, new_lr
308 );
309 }
310
311 losses.push(loss.value());
312
313 // Print progress
314 if epoch % 10 == 0 || epoch == num_epochs - 1 {
315 println!(
316 "Epoch {:2}: Loss = {:.6}, LR = {:.3}",
317 epoch,
318 loss.value(),
319 optimizer.learning_rate()
320 );
321 }
322 }
323
324 println!("Final learning rate: {}", optimizer.learning_rate());
325
326 Ok(())
327}
328
329/// Demonstrate training monitoring and analysis
330fn demonstrate_training_monitoring() -> Result<(), Box<dyn std::error::Error>> {
331 println!("\n--- Training Monitoring ---");
332
333 // Create model
334 let mut weight = Tensor::randn(vec![1, 1], Some(46)).with_requires_grad();
335 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
336
337 // Create optimizer
338 let mut optimizer = Adam::with_learning_rate(0.01);
339 optimizer.add_parameter(&weight);
340 optimizer.add_parameter(&bias);
341
342 // Training data
343 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4, 1]).unwrap();
344 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0], vec![4, 1]).unwrap();
345
346 // Training loop with comprehensive monitoring
347 let num_epochs = 30;
348 let mut losses = Vec::new();
349 let mut weight_history = Vec::new();
350 let mut bias_history = Vec::new();
351
352 for epoch in 0..num_epochs {
353 // Forward pass
354 let y_pred = x_data.matmul(&weight) + &bias;
355 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
356
357 // Backward pass
358 loss.backward(None);
359
360 // Optimizer step
361 optimizer.step(&mut [&mut weight, &mut bias]);
362 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
363
364 // Record history
365 losses.push(loss.value());
366 weight_history.push(weight.value());
367 bias_history.push(bias.value());
368
369 // Print detailed monitoring
370 if epoch % 5 == 0 || epoch == num_epochs - 1 {
371 println!(
372 "Epoch {:2}: Loss = {:.6}, Weight = {:.6}, Bias = {:.6}",
373 epoch,
374 loss.value(),
375 weight.value(),
376 bias.value()
377 );
378 }
379 }
380
381 // Analyze training progression
382 println!("\nTraining Analysis:");
383 println!(" Initial loss: {:.6}", losses[0]);
384 println!(" Final loss: {:.6}", losses[losses.len() - 1]);
385 println!(
386 " Loss reduction: {:.1}%",
387 (losses[0] - losses[losses.len() - 1]) / losses[0] * 100.0
388 );
389
390 // Compute statistics
391 let loss_mean = compute_mean(&losses);
392 let loss_std = compute_std(&losses);
393 let weight_change = (weight_history[weight_history.len() - 1] - weight_history[0]).abs();
394 let bias_change = (bias_history[bias_history.len() - 1] - bias_history[0]).abs();
395
396 println!(" Average loss: {:.6} ± {:.6}", loss_mean, loss_std);
397 println!(" Weight change: {:.6}", weight_change);
398 println!(" Bias change: {:.6}", bias_change);
399 println!(" Final weight norm: {:.6}", weight.norm().value());
400 println!(" Final bias: {:.6}", bias.value());
401
402 Ok(())
403}430fn demonstrate_training_workflow() -> Result<(), Box<dyn std::error::Error>> {
431 println!("\n--- Training Workflow ---");
432
433 // Create a simple classification network
434 let config = FeedForwardConfig {
435 input_size: 2,
436 hidden_sizes: vec![4, 3],
437 output_size: 1,
438 use_bias: true,
439 };
440 let mut network = FeedForwardNetwork::new(config, Some(46));
441
442 println!("Training network: 2 -> [4, 3] -> 1");
443
444 // Create simple binary classification data: XOR problem
445 let x_data = Tensor::from_slice(
446 &[
447 0.0, 0.0, // -> 0
448 0.0, 1.0, // -> 1
449 1.0, 0.0, // -> 1
450 1.0, 1.0, // -> 0
451 ],
452 vec![4, 2],
453 )
454 .unwrap();
455
456 let y_true = Tensor::from_slice(&[0.0, 1.0, 1.0, 0.0], vec![4, 1]).unwrap();
457
458 println!("Training on XOR problem:");
459 println!(" Input shape: {:?}", x_data.shape().dims);
460 println!(" Target shape: {:?}", y_true.shape().dims);
461
462 // Create optimizer
463 let mut optimizer = Adam::with_learning_rate(0.1);
464 let params = network.parameters();
465 for param in ¶ms {
466 optimizer.add_parameter(param);
467 }
468
469 // Training loop
470 let num_epochs = 50;
471 let mut losses = Vec::new();
472
473 for epoch in 0..num_epochs {
474 // Forward pass
475 let y_pred = network.forward(&x_data);
476
477 // Compute loss: MSE
478 let diff = y_pred.sub_tensor(&y_true);
479 let mut loss = diff.pow_scalar(2.0).mean();
480
481 // Backward pass
482 loss.backward(None);
483
484 // Optimizer step and zero grad
485 let mut params = network.parameters();
486 optimizer.step(&mut params);
487 optimizer.zero_grad(&mut params);
488
489 losses.push(loss.value());
490
491 // Print progress
492 if epoch % 10 == 0 || epoch == num_epochs - 1 {
493 println!("Epoch {:2}: Loss = {:.6}", epoch, loss.value());
494 }
495 }
496
497 // Test final model
498 let final_predictions = network.forward_no_grad(&x_data);
499 println!("\nFinal predictions vs targets:");
500 for i in 0..4 {
501 let pred = final_predictions.data()[i];
502 let target = y_true.data()[i];
503 let input_x = x_data.data()[i * 2];
504 let input_y = x_data.data()[i * 2 + 1];
505 println!(
506 " [{:.0}, {:.0}] -> pred: {:.3}, target: {:.0}, error: {:.3}",
507 input_x,
508 input_y,
509 pred,
510 target,
511 (pred - target).abs()
512 );
513 }
514
515 Ok(())
516}
517
518/// Demonstrate comprehensive training with 100+ steps
519fn demonstrate_comprehensive_training() -> Result<(), Box<dyn std::error::Error>> {
520 println!("\n--- Comprehensive Training (100+ Steps) ---");
521
522 // Create a regression network
523 let config = FeedForwardConfig {
524 input_size: 3,
525 hidden_sizes: vec![8, 6, 4],
526 output_size: 2,
527 use_bias: true,
528 };
529 let mut network = FeedForwardNetwork::new(config, Some(47));
530
531 println!("Network architecture: 3 -> [8, 6, 4] -> 2");
532 println!("Total parameters: {}", network.parameter_count());
533
534 // Create synthetic regression data
535 // Target function: [y1, y2] = [x1 + 2*x2 - x3, x1*x2 + x3]
536 let num_samples = 32;
537 let mut x_vec = Vec::new();
538 let mut y_vec = Vec::new();
539
540 for i in 0..num_samples {
541 let x1 = (i as f32 / num_samples as f32) * 2.0 - 1.0; // [-1, 1]
542 let x2 = ((i * 2) as f32 / num_samples as f32) * 2.0 - 1.0;
543 let x3 = ((i * 3) as f32 / num_samples as f32) * 2.0 - 1.0;
544
545 let y1 = x1 + 2.0 * x2 - x3;
546 let y2 = x1 * x2 + x3;
547
548 x_vec.extend_from_slice(&[x1, x2, x3]);
549 y_vec.extend_from_slice(&[y1, y2]);
550 }
551
552 let x_data = Tensor::from_slice(&x_vec, vec![num_samples, 3]).unwrap();
553 let y_true = Tensor::from_slice(&y_vec, vec![num_samples, 2]).unwrap();
554
555 println!("Training data:");
556 println!(" {} samples", num_samples);
557 println!(" Input shape: {:?}", x_data.shape().dims);
558 println!(" Target shape: {:?}", y_true.shape().dims);
559
560 // Create optimizer with learning rate scheduling
561 let mut optimizer = Adam::with_learning_rate(0.01);
562 let params = network.parameters();
563 for param in ¶ms {
564 optimizer.add_parameter(param);
565 }
566
567 // Comprehensive training loop (150 epochs)
568 let num_epochs = 150;
569 let mut losses = Vec::new();
570 let mut best_loss = f32::INFINITY;
571 let mut patience_counter = 0;
572 let patience = 20;
573
574 println!("Starting comprehensive training...");
575
576 for epoch in 0..num_epochs {
577 // Forward pass
578 let y_pred = network.forward(&x_data);
579
580 // Compute loss: MSE
581 let diff = y_pred.sub_tensor(&y_true);
582 let mut loss = diff.pow_scalar(2.0).mean();
583
584 // Backward pass
585 loss.backward(None);
586
587 // Optimizer step and zero grad
588 let mut params = network.parameters();
589 optimizer.step(&mut params);
590 optimizer.zero_grad(&mut params);
591
592 let current_loss = loss.value();
593 losses.push(current_loss);
594
595 // Learning rate scheduling
596 if epoch > 0 && epoch % 30 == 0 {
597 let new_lr = optimizer.learning_rate() * 0.8;
598 optimizer.set_learning_rate(new_lr);
599 println!(" Reduced learning rate to {:.4}", new_lr);
600 }
601
602 // Early stopping logic
603 if current_loss < best_loss {
604 best_loss = current_loss;
605 patience_counter = 0;
606 } else {
607 patience_counter += 1;
608 }
609
610 // Print progress
611 if epoch % 25 == 0 || epoch == num_epochs - 1 {
612 println!(
613 "Epoch {:3}: Loss = {:.6}, LR = {:.4}, Best = {:.6}",
614 epoch,
615 current_loss,
616 optimizer.learning_rate(),
617 best_loss
618 );
619 }
620
621 // Early stopping
622 if patience_counter >= patience && epoch > 50 {
623 println!("Early stopping at epoch {} (patience exceeded)", epoch);
624 break;
625 }
626 }
627
628 // Final evaluation
629 let final_predictions = network.forward_no_grad(&x_data);
630
631 // Compute final metrics
632 let final_loss = losses[losses.len() - 1];
633 let initial_loss = losses[0];
634 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
635
636 println!("\nTraining completed!");
637 println!(" Initial loss: {:.6}", initial_loss);
638 println!(" Final loss: {:.6}", final_loss);
639 println!(" Best loss: {:.6}", best_loss);
640 println!(" Loss reduction: {:.1}%", loss_reduction);
641 println!(" Final learning rate: {:.4}", optimizer.learning_rate());
642
643 // Sample predictions analysis
644 println!("\nSample predictions (first 5):");
645 for i in 0..5.min(num_samples) {
646 let pred1 = final_predictions.data()[i * 2];
647 let pred2 = final_predictions.data()[i * 2 + 1];
648 let true1 = y_true.data()[i * 2];
649 let true2 = y_true.data()[i * 2 + 1];
650
651 println!(
652 " Sample {}: pred=[{:.3}, {:.3}], true=[{:.3}, {:.3}], error=[{:.3}, {:.3}]",
653 i + 1,
654 pred1,
655 pred2,
656 true1,
657 true2,
658 (pred1 - true1).abs(),
659 (pred2 - true2).abs()
660 );
661 }
662
663 Ok(())
664}
665
666/// Demonstrate network serialization
667fn demonstrate_network_serialization() -> Result<(), Box<dyn std::error::Error>> {
668 println!("\n--- Network Serialization ---");
669
670 // Create and train a network
671 let config = FeedForwardConfig {
672 input_size: 2,
673 hidden_sizes: vec![4, 2],
674 output_size: 1,
675 use_bias: true,
676 };
677 let mut original_network = FeedForwardNetwork::new(config.clone(), Some(48));
678
679 // Quick training
680 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
681 let y_true = Tensor::from_slice(&[5.0, 11.0], vec![2, 1]).unwrap();
682
683 let mut optimizer = Adam::with_learning_rate(0.01);
684 let params = original_network.parameters();
685 for param in ¶ms {
686 optimizer.add_parameter(param);
687 }
688
689 for _ in 0..20 {
690 let y_pred = original_network.forward(&x_data);
691 let mut loss = (y_pred.sub_tensor(&y_true)).pow_scalar(2.0).mean();
692 loss.backward(None);
693
694 let mut params = original_network.parameters();
695 optimizer.step(&mut params);
696 optimizer.zero_grad(&mut params);
697 }
698
699 // Test original network
700 let test_input = Tensor::from_slice(&[1.0, 1.0], vec![1, 2]).unwrap();
701 let original_output = original_network.forward_no_grad(&test_input);
702
703 println!("Original network output: {:?}", original_output.data());
704
705 // Save network
706 original_network.save_json("temp_feedforward_network")?;
707
708 // Load network
709 let loaded_network = FeedForwardNetwork::load_json("temp_feedforward_network", config)?;
710 let loaded_output = loaded_network.forward_no_grad(&test_input);
711
712 println!("Loaded network output: {:?}", loaded_output.data());
713
714 // Verify consistency
715 let match_check = original_output
716 .data()
717 .iter()
718 .zip(loaded_output.data().iter())
719 .all(|(a, b)| (a - b).abs() < 1e-6);
720
721 println!(
722 "Serialization verification: {}",
723 if match_check { "PASSED" } else { "FAILED" }
724 );
725
726 Ok(())
727}135fn demonstrate_memory_optimization() -> Result<(), Box<dyn std::error::Error>> {
136 println!("\n--- Memory Optimization ---");
137
138 // Create a large tensor for memory testing
139 let size = 10000;
140 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
141 let tensor = Tensor::from_slice(&data, vec![size])?;
142
143 println!("Processing tensor of size: {}", size);
144
145 // Pattern 1: Streaming processing (process in chunks)
146 println!("\nPattern 1: Streaming Processing");
147 let chunk_size = 1000;
148 let start = Instant::now();
149
150 let mut streamed_result = Vec::new();
151 for chunk_start in (0..size).step_by(chunk_size) {
152 let chunk_end = (chunk_start + chunk_size).min(size);
153 let chunk: Tensor = tensor
154 .iter_range(chunk_start, chunk_end)
155 .map(|elem| elem.pow_scalar(2.0).sqrt())
156 .collect();
157 streamed_result.extend(chunk.data().iter().cloned());
158 }
159 let streamed_time = start.elapsed();
160
161 // Pattern 2: Full processing
162 let start = Instant::now();
163 let _full_result: Tensor = tensor
164 .iter()
165 .map(|elem| elem.pow_scalar(2.0).sqrt())
166 .collect();
167 let full_time = start.elapsed();
168
169 println!(" Streaming time: {:?}", streamed_time);
170 println!(" Full processing time: {:?}", full_time);
171 println!(
172 " Memory efficiency ratio: {:.2}x",
173 full_time.as_nanos() as f64 / streamed_time.as_nanos() as f64
174 );
175
176 // Pattern 3: Lazy evaluation with take
177 println!("\nPattern 2: Lazy Evaluation");
178 let start = Instant::now();
179 let lazy_result: Tensor = tensor
180 .iter()
181 .take(1000) // Only process first 1000 elements
182 .map(|elem| elem.pow_scalar(2.0).sqrt())
183 .collect();
184 let lazy_time = start.elapsed();
185
186 println!(" Lazy processing (1000 elements): {:?}", lazy_time);
187 println!(" Lazy result size: {}", lazy_result.size());
188
189 // Pattern 4: Memory-efficient filtering
190 println!("\nPattern 3: Memory-Efficient Filtering");
191 let start = Instant::now();
192 let filtered_result: Tensor = tensor
193 .iter()
194 .filter(|elem| elem.value() > size as f32 / 2.0) // Keep only large values
195 .map(|elem| elem.mul_scalar(2.0))
196 .collect();
197 let filtered_time = start.elapsed();
198
199 println!(" Filtered processing: {:?}", filtered_time);
200 println!(
201 " Filtered result size: {} (reduced from {})",
202 filtered_result.size(),
203 size
204 );
205
206 Ok(())
207}
208
209/// Demonstrate large-scale processing techniques
210///
211/// Shows how to efficiently process very large datasets using
212/// iterator patterns and optimization strategies.
213fn demonstrate_large_scale_processing() -> Result<(), Box<dyn std::error::Error>> {
214 println!("\n--- Large-Scale Processing ---");
215
216 // Simulate large dataset processing
217 let sizes = vec![10000, 50000, 100000];
218
219 for size in sizes {
220 println!("\nProcessing dataset of size: {}", size);
221
222 // Generate large dataset
223 let data: Vec<f32> = (0..size)
224 .map(|i| {
225 let x = i as f32 / size as f32;
226 x * x + 0.1 * (i % 10) as f32 // Quadratic with noise
227 })
228 .collect();
229
230 let tensor = Tensor::from_slice(&data, vec![size])?;
231
232 // Technique 1: Batch processing
233 let batch_size = 1000;
234 let start = Instant::now();
235
236 let mut batch_results = Vec::new();
237 for batch_start in (0..size).step_by(batch_size) {
238 let batch_end = (batch_start + batch_size).min(size);
239 let batch: Tensor = tensor
240 .iter_range(batch_start, batch_end)
241 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
242 .collect();
243 batch_results.push(batch);
244 }
245 let batch_time = start.elapsed();
246
247 // Technique 2: Parallel-like processing with stride
248 let start = Instant::now();
249 let stride = 4;
250 let strided_result: Tensor = tensor
251 .iter()
252 .enumerate()
253 .filter(|(i, _)| i % stride == 0)
254 .map(|(_, elem)| elem.pow_scalar(2.0).add_scalar(1.0))
255 .collect();
256 let strided_time = start.elapsed();
257
258 // Technique 3: Hierarchical processing
259 let start = Instant::now();
260 let coarse: Tensor = tensor
261 .iter()
262 .enumerate()
263 .filter(|(i, _)| i % 10 == 0) // Every 10th element
264 .map(|(_, elem)| elem.pow_scalar(2.0).add_scalar(1.0))
265 .collect();
266 let fine: Tensor = tensor
267 .iter()
268 .enumerate()
269 .filter(|(i, _)| i % 10 != 0) // Rest of elements
270 .map(|(_, elem)| elem.pow_scalar(1.5).add_scalar(0.5))
271 .collect();
272 let hierarchical_time = start.elapsed();
273
274 // Report performance
275 println!(" Batch processing: {:?}", batch_time);
276 println!(" Strided processing: {:?}", strided_time);
277 println!(" Hierarchical processing: {:?}", hierarchical_time);
278
279 // Memory usage analysis
280 let total_batches = (size + batch_size - 1) / batch_size;
281 println!(" Batch count: {}", total_batches);
282 println!(" Strided result size: {}", strided_result.size());
283 println!(
284 " Hierarchical: coarse={}, fine={}",
285 coarse.size(),
286 fine.size()
287 );
288 }
289
290 Ok(())
291}
292
293/// Demonstrate advanced optimization techniques
294///
295/// Shows sophisticated optimization strategies and techniques
296/// for maximizing performance in tensor iterator operations.
297fn demonstrate_optimization_techniques() -> Result<(), Box<dyn std::error::Error>> {
298 println!("\n--- Optimization Techniques ---");
299
300 let size = 50000;
301 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
302 let tensor = Tensor::from_slice(&data, vec![size])?;
303
304 println!("Optimizing processing for size: {}", size);
305
306 // Technique 1: Operation fusion
307 println!("\nTechnique 1: Operation Fusion");
308 let start = Instant::now();
309 let fused_result: Tensor = tensor
310 .iter()
311 .map(|elem| {
312 // Fuse multiple operations into single chain
313 elem.mul_scalar(2.0).add_scalar(1.0).pow_scalar(2.0).sqrt()
314 })
315 .collect();
316 let fused_time = start.elapsed();
317
318 // Technique 2: Conditional optimization
319 println!("\nTechnique 2: Conditional Optimization");
320 let start = Instant::now();
321 let conditional_result: Tensor = tensor
322 .iter()
323 .map(|elem| {
324 let val = elem.value();
325 if val < size as f32 / 2.0 {
326 elem.mul_scalar(2.0) // Simple operation for small values
327 } else {
328 elem.pow_scalar(2.0).sqrt() // Complex operation for large values
329 }
330 })
331 .collect();
332 let conditional_time = start.elapsed();
333
334 // Technique 3: Cache-friendly processing
335 println!("\nTechnique 3: Cache-Friendly Processing");
336 let start = Instant::now();
337 let cache_friendly_result: Tensor = tensor
338 .iter()
339 .take(1000) // Process in cache-friendly chunks
340 .map(|elem| elem.mul_scalar(2.0))
341 .collect();
342 let cache_friendly_time = start.elapsed();
343
344 // Technique 4: Memory pooling simulation
345 println!("\nTechnique 4: Memory Pooling Simulation");
346 let start = Instant::now();
347 let pooled_result: Tensor = tensor
348 .iter()
349 .enumerate()
350 .filter(|(i, _)| i % 100 == 0) // Process every 100th element
351 .map(|(_, elem)| elem.pow_scalar(2.0))
352 .collect();
353 let pooled_time = start.elapsed();
354
355 // Report optimization results
356 println!(" Fused operations: {:?}", fused_time);
357 println!(" Conditional optimization: {:?}", conditional_time);
358 println!(" Cache-friendly processing: {:?}", cache_friendly_time);
359 println!(" Memory pooling simulation: {:?}", pooled_time);
360
361 // Performance analysis
362 let fastest = fused_time
363 .min(conditional_time)
364 .min(cache_friendly_time)
365 .min(pooled_time);
366 println!(" Fastest technique: {:?}", fastest);
367
368 // Memory efficiency analysis
369 println!(" Fused result size: {}", fused_result.size());
370 println!(" Conditional result size: {}", conditional_result.size());
371 println!(
372 " Cache-friendly result size: {}",
373 cache_friendly_result.size()
374 );
375 println!(" Pooled result size: {}", pooled_result.size());
376
377 // Technique 5: Gradient optimization
378 println!("\nTechnique 5: Gradient Optimization");
379 let grad_tensor = tensor.with_requires_grad();
380 let start = Instant::now();
381
382 let grad_result: Tensor = grad_tensor
383 .iter()
384 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
385 .collect();
386
387 let mut loss = grad_result.sum();
388 loss.backward(None);
389 let grad_time = start.elapsed();
390
391 println!(" Gradient computation: {:?}", grad_time);
392 println!(
393 " Gradient tracking enabled: {}",
394 grad_result.requires_grad()
395 );
396
397 Ok(())
398}Sourcepub fn pow_tensor(&self, exponent: &Tensor) -> Tensor
pub fn pow_tensor(&self, exponent: &Tensor) -> Tensor
Element-wise power with tensor exponents.
Computes element-wise power: output[i] = self[i]^exponent[i]
§Arguments
exponent- Tensor of exponents, must have the same shape as self
§Returns
A new tensor with each element raised to the corresponding power
§Examples
§Basic Tensor Power
use train_station::Tensor;
let base = Tensor::from_slice(&[2.0, 3.0, 4.0], vec![3]).unwrap();
let exp = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let result = base.pow_tensor(&exp);
assert_eq!(result.shape().dims, vec![3]);
assert_eq!(result.get(&[0]), 2.0); // 2.0^1.0 = 2.0
assert_eq!(result.get(&[1]), 9.0); // 3.0^2.0 = 9.0
assert_eq!(result.get(&[2]), 64.0); // 4.0^3.0 = 64.0§Mixed Exponents
use train_station::Tensor;
let base = Tensor::from_slice(&[4.0, 9.0, 16.0], vec![3]).unwrap();
let exp = Tensor::from_slice(&[0.5, 1.0, 2.0], vec![3]).unwrap();
let result = base.pow_tensor(&exp);
assert_eq!(result.shape().dims, vec![3]);
assert_eq!(result.get(&[0]), 2.0); // sqrt(4.0) = 2.0
assert_eq!(result.get(&[1]), 9.0); // 9.0^1.0 = 9.0
assert_eq!(result.get(&[2]), 256.0); // 16.0^2.0 = 256.0§Panics
Panics if tensor shapes don’t match
Source§impl Tensor
impl Tensor
Sourcepub fn relu(&self) -> Tensor
pub fn relu(&self) -> Tensor
Element-wise ReLU (Rectified Linear Unit) activation.
Applies ReLU to each element: output[i] = max(0, self[i])
§Returns
A new tensor with ReLU applied to each element
§Examples
§Basic ReLU Activation
use train_station::Tensor;
let a = Tensor::from_slice(&[-1.0, 0.0, 2.5], vec![3]).unwrap();
let b = a.relu();
assert_eq!(b.shape().dims, vec![3]);
assert_eq!(b.get(&[0]), 0.0); // max(0, -1.0) = 0.0
assert_eq!(b.get(&[1]), 0.0); // max(0, 0.0) = 0.0
assert_eq!(b.get(&[2]), 2.5); // max(0, 2.5) = 2.5§Mixed Positive and Negative Values
use train_station::Tensor;
let a = Tensor::from_slice(&[-5.0, -0.1, 0.0, 0.1, 5.0], vec![5]).unwrap();
let b = a.relu();
assert_eq!(b.shape().dims, vec![5]);
assert_eq!(b.get(&[0]), 0.0); // max(0, -5.0) = 0.0
assert_eq!(b.get(&[1]), 0.0); // max(0, -0.1) = 0.0
assert_eq!(b.get(&[2]), 0.0); // max(0, 0.0) = 0.0
assert_eq!(b.get(&[3]), 0.1); // max(0, 0.1) = 0.1
assert_eq!(b.get(&[4]), 5.0); // max(0, 5.0) = 5.0Source§impl Tensor
impl Tensor
Sourcepub fn sigmoid(&self) -> Tensor
pub fn sigmoid(&self) -> Tensor
Element-wise sigmoid activation function
Computes the sigmoid function for each element: output[i] = 1 / (1 + e^(-self[i]))
Uses a numerically stable implementation that avoids overflow for large positive/negative values by using different computation paths for positive and negative inputs.
§Returns
A new tensor with sigmoid applied to each element, values in range (0, 1)
§Performance Characteristics
- Numerical Stability: Avoids overflow using stable implementation
- Scalar Implementation: Optimized scalar computation for mathematical accuracy
- Cache-friendly: Linear memory access patterns
- Mathematical Accuracy: High-precision exponential and division operations
- Gradient Tracking: Full gradtrack support with efficient gradient computation
§Implementation Details
Uses a numerically stable implementation:
- For x ≥ 0: computes 1 / (1 + e^(-x)) to avoid overflow in e^x for large positive x
- For x < 0: computes e^x / (1 + e^x) to avoid overflow in e^(-x) for large negative x This ensures the result is always in the range (0, 1) without numerical overflow.
§Examples
§Basic Sigmoid Activation
use train_station::Tensor;
let a = Tensor::from_slice(&[-1.0, 0.0, 1.0], vec![3]).unwrap();
let b = a.sigmoid();
assert_eq!(b.shape().dims, vec![3]);
assert!((b.get(&[0]) - 0.26894143).abs() < 1e-6); // sigmoid(-1.0)
assert!((b.get(&[1]) - 0.5).abs() < 1e-6); // sigmoid(0.0)
assert!((b.get(&[2]) - 0.7310586).abs() < 1e-6); // sigmoid(1.0)§Extreme Values
use train_station::Tensor;
let a = Tensor::from_slice(&[-10.0, 10.0], vec![2]).unwrap();
let b = a.sigmoid();
assert_eq!(b.shape().dims, vec![2]);
assert!(b.get(&[0]) < 1e-4); // sigmoid(-10.0) ≈ 0
assert!(b.get(&[1]) > 0.9999); // sigmoid(10.0) ≈ 1Source§impl Tensor
impl Tensor
Sourcepub fn softmax(&self, dim: usize) -> Tensor
pub fn softmax(&self, dim: usize) -> Tensor
Computes softmax activation along the specified dimension
Applies the softmax function along dimension dim, transforming values into
probabilities that sum to 1 along that dimension. Uses numerically stable
computation to avoid overflow: softmax(x_i) = exp(x_i - max(x)) / sum(exp(x_j - max(x)))
§Arguments
dim- Dimension along which to compute softmax (0-based indexing)
§Returns
A new tensor with softmax applied along the specified dimension.
Values are in range (0, 1) and sum to 1 along dim.
§Performance Characteristics
- Numerical Stability: Avoids overflow using max subtraction technique
- Scalar Implementation: Optimized scalar computation for mathematical accuracy
- Cache-friendly: Optimized memory access patterns for dimension operations
- Mathematical Accuracy: High-precision exponential and division operations
- GradTrack Support: Full automatic differentiation with efficient gradient computation
§Implementation Details
Uses a numerically stable three-pass algorithm:
- Max Computation: Find the maximum value along the specified dimension
- Exponential Sum: Compute exp(x - max) and sum the results
- Normalization: Divide each exp(x - max) by the sum to get probabilities
This approach prevents overflow by subtracting the maximum value before computing exponentials, ensuring numerical stability for any input range.
§Examples
§Basic Softmax Activation
use train_station::Tensor;
let a = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let b = a.softmax(0);
assert_eq!(b.shape().dims, vec![3]);
// Verify probabilities sum to 1
let sum = b.get(&[0]) + b.get(&[1]) + b.get(&[2]);
assert!((sum - 1.0).abs() < 1e-6);
// Verify relative ordering is preserved
assert!(b.get(&[0]) < b.get(&[1]));
assert!(b.get(&[1]) < b.get(&[2]));§2D Softmax Along Different Dimensions
use train_station::Tensor;
let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let b = a.softmax(0); // Softmax along first dimension
assert_eq!(b.shape().dims, vec![2, 2]);
// Each column should sum to 1
let col1_sum = b.get(&[0, 0]) + b.get(&[1, 0]);
let col2_sum = b.get(&[0, 1]) + b.get(&[1, 1]);
assert!((col1_sum - 1.0).abs() < 1e-6);
assert!((col2_sum - 1.0).abs() < 1e-6);§Panics
- Panics if
dimis out of bounds for the tensor’s rank - Panics if the dimension size is 0
Source§impl Tensor
impl Tensor
Sourcepub fn sqrt(&self) -> Tensor
pub fn sqrt(&self) -> Tensor
Element-wise square root
Computes the square root for each element: output[i] = sqrt(self[i])
Uses SIMD optimization when available for maximum performance, with automatic fallback to optimized scalar computation for non-SIMD hardware.
§Returns
A new tensor with the square root of each element
§Performance Characteristics
- SIMD Optimization: AVX2-optimized with 32-element blocks and 4x unrolling
- Scalar Fallback: 4x unrolled scalar implementation for non-SIMD hardware
- Cache-friendly: Linear memory access patterns
- Mathematical Accuracy: High-precision square root computation
- GradTrack Support: Full automatic differentiation with efficient gradient computation
§Implementation Details
Automatically selects between SIMD and scalar implementations based on hardware capabilities. SIMD implementation uses AVX2 vector square root operations for optimal performance. Scalar implementation uses 4x unrolling for better instruction-level parallelism.
§Examples
§Basic Square Root
use train_station::Tensor;
let a = Tensor::from_slice(&[1.0, 4.0, 9.0], vec![3]).unwrap();
let b = a.sqrt();
assert_eq!(b.shape().dims, vec![3]);
assert_eq!(b.get(&[0]), 1.0); // sqrt(1.0) = 1.0
assert_eq!(b.get(&[1]), 2.0); // sqrt(4.0) = 2.0
assert_eq!(b.get(&[2]), 3.0); // sqrt(9.0) = 3.0§Zero and Special Values
use train_station::Tensor;
let a = Tensor::from_slice(&[0.0, 1.0, 16.0], vec![3]).unwrap();
let b = a.sqrt();
assert_eq!(b.shape().dims, vec![3]);
assert_eq!(b.get(&[0]), 0.0); // sqrt(0.0) = 0.0
assert_eq!(b.get(&[1]), 1.0); // sqrt(1.0) = 1.0
assert_eq!(b.get(&[2]), 4.0); // sqrt(16.0) = 4.0§Note
Results are undefined for negative values (may produce NaN)
Examples found in repository?
135fn demonstrate_memory_optimization() -> Result<(), Box<dyn std::error::Error>> {
136 println!("\n--- Memory Optimization ---");
137
138 // Create a large tensor for memory testing
139 let size = 10000;
140 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
141 let tensor = Tensor::from_slice(&data, vec![size])?;
142
143 println!("Processing tensor of size: {}", size);
144
145 // Pattern 1: Streaming processing (process in chunks)
146 println!("\nPattern 1: Streaming Processing");
147 let chunk_size = 1000;
148 let start = Instant::now();
149
150 let mut streamed_result = Vec::new();
151 for chunk_start in (0..size).step_by(chunk_size) {
152 let chunk_end = (chunk_start + chunk_size).min(size);
153 let chunk: Tensor = tensor
154 .iter_range(chunk_start, chunk_end)
155 .map(|elem| elem.pow_scalar(2.0).sqrt())
156 .collect();
157 streamed_result.extend(chunk.data().iter().cloned());
158 }
159 let streamed_time = start.elapsed();
160
161 // Pattern 2: Full processing
162 let start = Instant::now();
163 let _full_result: Tensor = tensor
164 .iter()
165 .map(|elem| elem.pow_scalar(2.0).sqrt())
166 .collect();
167 let full_time = start.elapsed();
168
169 println!(" Streaming time: {:?}", streamed_time);
170 println!(" Full processing time: {:?}", full_time);
171 println!(
172 " Memory efficiency ratio: {:.2}x",
173 full_time.as_nanos() as f64 / streamed_time.as_nanos() as f64
174 );
175
176 // Pattern 3: Lazy evaluation with take
177 println!("\nPattern 2: Lazy Evaluation");
178 let start = Instant::now();
179 let lazy_result: Tensor = tensor
180 .iter()
181 .take(1000) // Only process first 1000 elements
182 .map(|elem| elem.pow_scalar(2.0).sqrt())
183 .collect();
184 let lazy_time = start.elapsed();
185
186 println!(" Lazy processing (1000 elements): {:?}", lazy_time);
187 println!(" Lazy result size: {}", lazy_result.size());
188
189 // Pattern 4: Memory-efficient filtering
190 println!("\nPattern 3: Memory-Efficient Filtering");
191 let start = Instant::now();
192 let filtered_result: Tensor = tensor
193 .iter()
194 .filter(|elem| elem.value() > size as f32 / 2.0) // Keep only large values
195 .map(|elem| elem.mul_scalar(2.0))
196 .collect();
197 let filtered_time = start.elapsed();
198
199 println!(" Filtered processing: {:?}", filtered_time);
200 println!(
201 " Filtered result size: {} (reduced from {})",
202 filtered_result.size(),
203 size
204 );
205
206 Ok(())
207}
208
209/// Demonstrate large-scale processing techniques
210///
211/// Shows how to efficiently process very large datasets using
212/// iterator patterns and optimization strategies.
213fn demonstrate_large_scale_processing() -> Result<(), Box<dyn std::error::Error>> {
214 println!("\n--- Large-Scale Processing ---");
215
216 // Simulate large dataset processing
217 let sizes = vec![10000, 50000, 100000];
218
219 for size in sizes {
220 println!("\nProcessing dataset of size: {}", size);
221
222 // Generate large dataset
223 let data: Vec<f32> = (0..size)
224 .map(|i| {
225 let x = i as f32 / size as f32;
226 x * x + 0.1 * (i % 10) as f32 // Quadratic with noise
227 })
228 .collect();
229
230 let tensor = Tensor::from_slice(&data, vec![size])?;
231
232 // Technique 1: Batch processing
233 let batch_size = 1000;
234 let start = Instant::now();
235
236 let mut batch_results = Vec::new();
237 for batch_start in (0..size).step_by(batch_size) {
238 let batch_end = (batch_start + batch_size).min(size);
239 let batch: Tensor = tensor
240 .iter_range(batch_start, batch_end)
241 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
242 .collect();
243 batch_results.push(batch);
244 }
245 let batch_time = start.elapsed();
246
247 // Technique 2: Parallel-like processing with stride
248 let start = Instant::now();
249 let stride = 4;
250 let strided_result: Tensor = tensor
251 .iter()
252 .enumerate()
253 .filter(|(i, _)| i % stride == 0)
254 .map(|(_, elem)| elem.pow_scalar(2.0).add_scalar(1.0))
255 .collect();
256 let strided_time = start.elapsed();
257
258 // Technique 3: Hierarchical processing
259 let start = Instant::now();
260 let coarse: Tensor = tensor
261 .iter()
262 .enumerate()
263 .filter(|(i, _)| i % 10 == 0) // Every 10th element
264 .map(|(_, elem)| elem.pow_scalar(2.0).add_scalar(1.0))
265 .collect();
266 let fine: Tensor = tensor
267 .iter()
268 .enumerate()
269 .filter(|(i, _)| i % 10 != 0) // Rest of elements
270 .map(|(_, elem)| elem.pow_scalar(1.5).add_scalar(0.5))
271 .collect();
272 let hierarchical_time = start.elapsed();
273
274 // Report performance
275 println!(" Batch processing: {:?}", batch_time);
276 println!(" Strided processing: {:?}", strided_time);
277 println!(" Hierarchical processing: {:?}", hierarchical_time);
278
279 // Memory usage analysis
280 let total_batches = (size + batch_size - 1) / batch_size;
281 println!(" Batch count: {}", total_batches);
282 println!(" Strided result size: {}", strided_result.size());
283 println!(
284 " Hierarchical: coarse={}, fine={}",
285 coarse.size(),
286 fine.size()
287 );
288 }
289
290 Ok(())
291}
292
293/// Demonstrate advanced optimization techniques
294///
295/// Shows sophisticated optimization strategies and techniques
296/// for maximizing performance in tensor iterator operations.
297fn demonstrate_optimization_techniques() -> Result<(), Box<dyn std::error::Error>> {
298 println!("\n--- Optimization Techniques ---");
299
300 let size = 50000;
301 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
302 let tensor = Tensor::from_slice(&data, vec![size])?;
303
304 println!("Optimizing processing for size: {}", size);
305
306 // Technique 1: Operation fusion
307 println!("\nTechnique 1: Operation Fusion");
308 let start = Instant::now();
309 let fused_result: Tensor = tensor
310 .iter()
311 .map(|elem| {
312 // Fuse multiple operations into single chain
313 elem.mul_scalar(2.0).add_scalar(1.0).pow_scalar(2.0).sqrt()
314 })
315 .collect();
316 let fused_time = start.elapsed();
317
318 // Technique 2: Conditional optimization
319 println!("\nTechnique 2: Conditional Optimization");
320 let start = Instant::now();
321 let conditional_result: Tensor = tensor
322 .iter()
323 .map(|elem| {
324 let val = elem.value();
325 if val < size as f32 / 2.0 {
326 elem.mul_scalar(2.0) // Simple operation for small values
327 } else {
328 elem.pow_scalar(2.0).sqrt() // Complex operation for large values
329 }
330 })
331 .collect();
332 let conditional_time = start.elapsed();
333
334 // Technique 3: Cache-friendly processing
335 println!("\nTechnique 3: Cache-Friendly Processing");
336 let start = Instant::now();
337 let cache_friendly_result: Tensor = tensor
338 .iter()
339 .take(1000) // Process in cache-friendly chunks
340 .map(|elem| elem.mul_scalar(2.0))
341 .collect();
342 let cache_friendly_time = start.elapsed();
343
344 // Technique 4: Memory pooling simulation
345 println!("\nTechnique 4: Memory Pooling Simulation");
346 let start = Instant::now();
347 let pooled_result: Tensor = tensor
348 .iter()
349 .enumerate()
350 .filter(|(i, _)| i % 100 == 0) // Process every 100th element
351 .map(|(_, elem)| elem.pow_scalar(2.0))
352 .collect();
353 let pooled_time = start.elapsed();
354
355 // Report optimization results
356 println!(" Fused operations: {:?}", fused_time);
357 println!(" Conditional optimization: {:?}", conditional_time);
358 println!(" Cache-friendly processing: {:?}", cache_friendly_time);
359 println!(" Memory pooling simulation: {:?}", pooled_time);
360
361 // Performance analysis
362 let fastest = fused_time
363 .min(conditional_time)
364 .min(cache_friendly_time)
365 .min(pooled_time);
366 println!(" Fastest technique: {:?}", fastest);
367
368 // Memory efficiency analysis
369 println!(" Fused result size: {}", fused_result.size());
370 println!(" Conditional result size: {}", conditional_result.size());
371 println!(
372 " Cache-friendly result size: {}",
373 cache_friendly_result.size()
374 );
375 println!(" Pooled result size: {}", pooled_result.size());
376
377 // Technique 5: Gradient optimization
378 println!("\nTechnique 5: Gradient Optimization");
379 let grad_tensor = tensor.with_requires_grad();
380 let start = Instant::now();
381
382 let grad_result: Tensor = grad_tensor
383 .iter()
384 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
385 .collect();
386
387 let mut loss = grad_result.sum();
388 loss.backward(None);
389 let grad_time = start.elapsed();
390
391 println!(" Gradient computation: {:?}", grad_time);
392 println!(
393 " Gradient tracking enabled: {}",
394 grad_result.requires_grad()
395 );
396
397 Ok(())
398}More examples
161fn demonstrate_conditional_processing() -> Result<(), Box<dyn std::error::Error>> {
162 println!("\n--- Conditional Processing ---");
163
164 // Create data with mixed characteristics
165 let data = vec![1.0, -2.0, 3.0, -4.0, 5.0, -6.0, 7.0, -8.0, 9.0, -10.0];
166 let tensor = Tensor::from_slice(&data, vec![10])?;
167 println!("Input data: {:?}", tensor.data());
168
169 // Conditional transformation based on sign
170 println!("\nConditional transformation (positive/negative handling):");
171 let processed: Tensor = tensor
172 .iter()
173 .map(|elem| {
174 let val = elem.value();
175 if val > 0.0 {
176 elem.pow_scalar(2.0) // Square positive values
177 } else {
178 elem.mul_scalar(-1.0).sqrt() // Square root of absolute negative values
179 }
180 })
181 .collect();
182 println!(" Processed: {:?}", processed.data());
183
184 // Adaptive filtering based on local statistics
185 println!("\nAdaptive filtering (remove values > 2 std from local mean):");
186 let window_size = 3;
187 let adaptive_filtered: Tensor = tensor
188 .iter()
189 .enumerate()
190 .filter(|(i, elem)| {
191 let start = i.saturating_sub(window_size / 2);
192 let end = (i + window_size / 2 + 1).min(tensor.size());
193
194 // Calculate local mean and std
195 let local_values: Vec<f32> = (start..end)
196 .map(|j| tensor.element_view(j).value())
197 .collect();
198
199 let local_mean = local_values.iter().sum::<f32>() / local_values.len() as f32;
200 let local_variance = local_values
201 .iter()
202 .map(|v| (v - local_mean).powi(2))
203 .sum::<f32>()
204 / local_values.len() as f32;
205 let local_std = local_variance.sqrt();
206
207 let threshold = local_mean + 2.0 * local_std;
208 elem.value() <= threshold
209 })
210 .map(|(_, elem)| elem)
211 .collect();
212 println!(" Adaptive filtered: {:?}", adaptive_filtered.data());
213
214 // Multi-condition processing
215 println!("\nMulti-condition processing:");
216 let multi_processed: Tensor = tensor
217 .iter()
218 .map(|elem| {
219 let val = elem.value();
220 match () {
221 _ if val > 5.0 => elem.mul_scalar(2.0), // Double large values
222 _ if val < -5.0 => elem.div_scalar(2.0), // Halve small values
223 _ if val.abs() < 2.0 => elem.add_scalar(1.0), // Add 1 to small values
224 _ => elem.clone(), // Keep others unchanged
225 }
226 })
227 .collect();
228 println!(" Multi-condition: {:?}", multi_processed.data());
229
230 Ok(())
231}Source§impl Tensor
impl Tensor
Sourcepub fn sub_tensor(&self, other: &Tensor) -> Tensor
pub fn sub_tensor(&self, other: &Tensor) -> Tensor
Element-wise subtraction with another tensor with broadcasting support
Performs element-wise subtraction with automatic broadcasting: output[i] = self[i] - other[i]
Broadcasting enables subtraction between tensors of different but compatible shapes. Compatible shapes follow NumPy broadcasting rules:
- Dimensions are aligned from the rightmost dimension
- Dimensions are compatible if they are equal, or one of them is 1
- Missing dimensions are treated as 1
§Arguments
other- Tensor to subtract. Shapes must be broadcast-compatible.
§Returns
A new tensor containing the element-wise difference with broadcast result shape
§Performance Characteristics
- Fast Path: Optimized for identical shapes to avoid broadcasting overhead
- SIMD Optimization: AVX2-optimized with 32-element blocks and 4x unrolling
- Broadcasting: Efficient broadcasting for compatible shapes
- Cache-friendly: Linear memory access patterns
- GradTrack Support: Full automatic differentiation with efficient gradient computation
§Implementation Details
Uses a fast path for identical shapes to avoid broadcasting overhead. For different shapes, performs broadcasting followed by optimized element-wise subtraction. Automatically selects between SIMD and scalar implementations based on hardware capabilities.
§Examples
§Same Shape Subtraction
use train_station::Tensor;
let a = Tensor::from_slice(&[5.0, 7.0, 9.0], vec![3]).unwrap();
let b = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let c = a.sub_tensor(&b);
assert_eq!(c.shape().dims, vec![3]);
assert_eq!(c.get(&[0]), 4.0); // 5.0 - 1.0
assert_eq!(c.get(&[1]), 5.0); // 7.0 - 2.0
assert_eq!(c.get(&[2]), 6.0); // 9.0 - 3.0§Broadcasting Subtraction
use train_station::Tensor;
let a = Tensor::from_slice(&[5.0, 10.0], vec![2, 1]).unwrap();
let b = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![1, 3]).unwrap();
let c = a.sub_tensor(&b);
assert_eq!(c.shape().dims, vec![2, 3]);
// Result: [[4.0, 3.0, 2.0], [9.0, 8.0, 7.0]]
assert_eq!(c.get(&[0, 0]), 4.0); // 5.0 - 1.0
assert_eq!(c.get(&[0, 1]), 3.0); // 5.0 - 2.0
assert_eq!(c.get(&[1, 0]), 9.0); // 10.0 - 1.0§Scalar Subtraction
use train_station::Tensor;
let a = Tensor::ones(vec![2, 3]);
let b = Tensor::from_slice(&[0.5], vec![1]).unwrap();
let c = a.sub_tensor(&b);
assert_eq!(c.shape().dims, vec![2, 3]);
assert_eq!(c.get(&[0, 0]), 0.5); // 1.0 - 0.5§Panics
Panics if tensor shapes are not broadcast-compatible
Examples found in repository?
89fn demonstrate_basic_operations() {
90 println!("\n--- Basic Operations ---");
91
92 let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
93 let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
94
95 // Addition
96 let sum = a.add_tensor(&b);
97 println!("A + B: {:?}", sum.data());
98
99 // Subtraction
100 let diff = a.sub_tensor(&b);
101 println!("A - B: {:?}", diff.data());
102
103 // Multiplication
104 let product = a.mul_tensor(&b);
105 println!("A * B: {:?}", product.data());
106
107 // Division
108 let quotient = a.div_tensor(&b);
109 println!("A / B: {:?}", quotient.data());
110
111 // Scalar operations
112 let scalar_add = a.add_scalar(5.0);
113 println!("A + 5.0: {:?}", scalar_add.data());
114
115 let scalar_mul = a.mul_scalar(2.0);
116 println!("A * 2.0: {:?}", scalar_mul.data());
117}More examples
430fn demonstrate_training_workflow() -> Result<(), Box<dyn std::error::Error>> {
431 println!("\n--- Training Workflow ---");
432
433 // Create a simple classification network
434 let config = FeedForwardConfig {
435 input_size: 2,
436 hidden_sizes: vec![4, 3],
437 output_size: 1,
438 use_bias: true,
439 };
440 let mut network = FeedForwardNetwork::new(config, Some(46));
441
442 println!("Training network: 2 -> [4, 3] -> 1");
443
444 // Create simple binary classification data: XOR problem
445 let x_data = Tensor::from_slice(
446 &[
447 0.0, 0.0, // -> 0
448 0.0, 1.0, // -> 1
449 1.0, 0.0, // -> 1
450 1.0, 1.0, // -> 0
451 ],
452 vec![4, 2],
453 )
454 .unwrap();
455
456 let y_true = Tensor::from_slice(&[0.0, 1.0, 1.0, 0.0], vec![4, 1]).unwrap();
457
458 println!("Training on XOR problem:");
459 println!(" Input shape: {:?}", x_data.shape().dims);
460 println!(" Target shape: {:?}", y_true.shape().dims);
461
462 // Create optimizer
463 let mut optimizer = Adam::with_learning_rate(0.1);
464 let params = network.parameters();
465 for param in ¶ms {
466 optimizer.add_parameter(param);
467 }
468
469 // Training loop
470 let num_epochs = 50;
471 let mut losses = Vec::new();
472
473 for epoch in 0..num_epochs {
474 // Forward pass
475 let y_pred = network.forward(&x_data);
476
477 // Compute loss: MSE
478 let diff = y_pred.sub_tensor(&y_true);
479 let mut loss = diff.pow_scalar(2.0).mean();
480
481 // Backward pass
482 loss.backward(None);
483
484 // Optimizer step and zero grad
485 let mut params = network.parameters();
486 optimizer.step(&mut params);
487 optimizer.zero_grad(&mut params);
488
489 losses.push(loss.value());
490
491 // Print progress
492 if epoch % 10 == 0 || epoch == num_epochs - 1 {
493 println!("Epoch {:2}: Loss = {:.6}", epoch, loss.value());
494 }
495 }
496
497 // Test final model
498 let final_predictions = network.forward_no_grad(&x_data);
499 println!("\nFinal predictions vs targets:");
500 for i in 0..4 {
501 let pred = final_predictions.data()[i];
502 let target = y_true.data()[i];
503 let input_x = x_data.data()[i * 2];
504 let input_y = x_data.data()[i * 2 + 1];
505 println!(
506 " [{:.0}, {:.0}] -> pred: {:.3}, target: {:.0}, error: {:.3}",
507 input_x,
508 input_y,
509 pred,
510 target,
511 (pred - target).abs()
512 );
513 }
514
515 Ok(())
516}
517
518/// Demonstrate comprehensive training with 100+ steps
519fn demonstrate_comprehensive_training() -> Result<(), Box<dyn std::error::Error>> {
520 println!("\n--- Comprehensive Training (100+ Steps) ---");
521
522 // Create a regression network
523 let config = FeedForwardConfig {
524 input_size: 3,
525 hidden_sizes: vec![8, 6, 4],
526 output_size: 2,
527 use_bias: true,
528 };
529 let mut network = FeedForwardNetwork::new(config, Some(47));
530
531 println!("Network architecture: 3 -> [8, 6, 4] -> 2");
532 println!("Total parameters: {}", network.parameter_count());
533
534 // Create synthetic regression data
535 // Target function: [y1, y2] = [x1 + 2*x2 - x3, x1*x2 + x3]
536 let num_samples = 32;
537 let mut x_vec = Vec::new();
538 let mut y_vec = Vec::new();
539
540 for i in 0..num_samples {
541 let x1 = (i as f32 / num_samples as f32) * 2.0 - 1.0; // [-1, 1]
542 let x2 = ((i * 2) as f32 / num_samples as f32) * 2.0 - 1.0;
543 let x3 = ((i * 3) as f32 / num_samples as f32) * 2.0 - 1.0;
544
545 let y1 = x1 + 2.0 * x2 - x3;
546 let y2 = x1 * x2 + x3;
547
548 x_vec.extend_from_slice(&[x1, x2, x3]);
549 y_vec.extend_from_slice(&[y1, y2]);
550 }
551
552 let x_data = Tensor::from_slice(&x_vec, vec![num_samples, 3]).unwrap();
553 let y_true = Tensor::from_slice(&y_vec, vec![num_samples, 2]).unwrap();
554
555 println!("Training data:");
556 println!(" {} samples", num_samples);
557 println!(" Input shape: {:?}", x_data.shape().dims);
558 println!(" Target shape: {:?}", y_true.shape().dims);
559
560 // Create optimizer with learning rate scheduling
561 let mut optimizer = Adam::with_learning_rate(0.01);
562 let params = network.parameters();
563 for param in ¶ms {
564 optimizer.add_parameter(param);
565 }
566
567 // Comprehensive training loop (150 epochs)
568 let num_epochs = 150;
569 let mut losses = Vec::new();
570 let mut best_loss = f32::INFINITY;
571 let mut patience_counter = 0;
572 let patience = 20;
573
574 println!("Starting comprehensive training...");
575
576 for epoch in 0..num_epochs {
577 // Forward pass
578 let y_pred = network.forward(&x_data);
579
580 // Compute loss: MSE
581 let diff = y_pred.sub_tensor(&y_true);
582 let mut loss = diff.pow_scalar(2.0).mean();
583
584 // Backward pass
585 loss.backward(None);
586
587 // Optimizer step and zero grad
588 let mut params = network.parameters();
589 optimizer.step(&mut params);
590 optimizer.zero_grad(&mut params);
591
592 let current_loss = loss.value();
593 losses.push(current_loss);
594
595 // Learning rate scheduling
596 if epoch > 0 && epoch % 30 == 0 {
597 let new_lr = optimizer.learning_rate() * 0.8;
598 optimizer.set_learning_rate(new_lr);
599 println!(" Reduced learning rate to {:.4}", new_lr);
600 }
601
602 // Early stopping logic
603 if current_loss < best_loss {
604 best_loss = current_loss;
605 patience_counter = 0;
606 } else {
607 patience_counter += 1;
608 }
609
610 // Print progress
611 if epoch % 25 == 0 || epoch == num_epochs - 1 {
612 println!(
613 "Epoch {:3}: Loss = {:.6}, LR = {:.4}, Best = {:.6}",
614 epoch,
615 current_loss,
616 optimizer.learning_rate(),
617 best_loss
618 );
619 }
620
621 // Early stopping
622 if patience_counter >= patience && epoch > 50 {
623 println!("Early stopping at epoch {} (patience exceeded)", epoch);
624 break;
625 }
626 }
627
628 // Final evaluation
629 let final_predictions = network.forward_no_grad(&x_data);
630
631 // Compute final metrics
632 let final_loss = losses[losses.len() - 1];
633 let initial_loss = losses[0];
634 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
635
636 println!("\nTraining completed!");
637 println!(" Initial loss: {:.6}", initial_loss);
638 println!(" Final loss: {:.6}", final_loss);
639 println!(" Best loss: {:.6}", best_loss);
640 println!(" Loss reduction: {:.1}%", loss_reduction);
641 println!(" Final learning rate: {:.4}", optimizer.learning_rate());
642
643 // Sample predictions analysis
644 println!("\nSample predictions (first 5):");
645 for i in 0..5.min(num_samples) {
646 let pred1 = final_predictions.data()[i * 2];
647 let pred2 = final_predictions.data()[i * 2 + 1];
648 let true1 = y_true.data()[i * 2];
649 let true2 = y_true.data()[i * 2 + 1];
650
651 println!(
652 " Sample {}: pred=[{:.3}, {:.3}], true=[{:.3}, {:.3}], error=[{:.3}, {:.3}]",
653 i + 1,
654 pred1,
655 pred2,
656 true1,
657 true2,
658 (pred1 - true1).abs(),
659 (pred2 - true2).abs()
660 );
661 }
662
663 Ok(())
664}
665
666/// Demonstrate network serialization
667fn demonstrate_network_serialization() -> Result<(), Box<dyn std::error::Error>> {
668 println!("\n--- Network Serialization ---");
669
670 // Create and train a network
671 let config = FeedForwardConfig {
672 input_size: 2,
673 hidden_sizes: vec![4, 2],
674 output_size: 1,
675 use_bias: true,
676 };
677 let mut original_network = FeedForwardNetwork::new(config.clone(), Some(48));
678
679 // Quick training
680 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
681 let y_true = Tensor::from_slice(&[5.0, 11.0], vec![2, 1]).unwrap();
682
683 let mut optimizer = Adam::with_learning_rate(0.01);
684 let params = original_network.parameters();
685 for param in ¶ms {
686 optimizer.add_parameter(param);
687 }
688
689 for _ in 0..20 {
690 let y_pred = original_network.forward(&x_data);
691 let mut loss = (y_pred.sub_tensor(&y_true)).pow_scalar(2.0).mean();
692 loss.backward(None);
693
694 let mut params = original_network.parameters();
695 optimizer.step(&mut params);
696 optimizer.zero_grad(&mut params);
697 }
698
699 // Test original network
700 let test_input = Tensor::from_slice(&[1.0, 1.0], vec![1, 2]).unwrap();
701 let original_output = original_network.forward_no_grad(&test_input);
702
703 println!("Original network output: {:?}", original_output.data());
704
705 // Save network
706 original_network.save_json("temp_feedforward_network")?;
707
708 // Load network
709 let loaded_network = FeedForwardNetwork::load_json("temp_feedforward_network", config)?;
710 let loaded_output = loaded_network.forward_no_grad(&test_input);
711
712 println!("Loaded network output: {:?}", loaded_output.data());
713
714 // Verify consistency
715 let match_check = original_output
716 .data()
717 .iter()
718 .zip(loaded_output.data().iter())
719 .all(|(a, b)| (a - b).abs() < 1e-6);
720
721 println!(
722 "Serialization verification: {}",
723 if match_check { "PASSED" } else { "FAILED" }
724 );
725
726 Ok(())
727}217fn demonstrate_training_loop() -> Result<(), Box<dyn std::error::Error>> {
218 println!("\n--- Training Loop ---");
219
220 // Create layer and training data
221 let mut layer = LinearLayer::new(2, 1, Some(45));
222
223 // Simple regression task: y = 2*x1 + 3*x2 + 1
224 let x_data = Tensor::from_slice(
225 &[
226 1.0, 1.0, // x1=1, x2=1 -> y=6
227 2.0, 1.0, // x1=2, x2=1 -> y=8
228 1.0, 2.0, // x1=1, x2=2 -> y=9
229 2.0, 2.0, // x1=2, x2=2 -> y=11
230 ],
231 vec![4, 2],
232 )
233 .unwrap();
234
235 let y_true = Tensor::from_slice(&[6.0, 8.0, 9.0, 11.0], vec![4, 1]).unwrap();
236
237 println!("Training data:");
238 println!(" X shape: {:?}", x_data.shape().dims);
239 println!(" Y shape: {:?}", y_true.shape().dims);
240 println!(" Target function: y = 2*x1 + 3*x2 + 1");
241
242 // Create optimizer
243 let config = AdamConfig {
244 learning_rate: 0.01,
245 beta1: 0.9,
246 beta2: 0.999,
247 eps: 1e-8,
248 weight_decay: 0.0,
249 amsgrad: false,
250 };
251
252 let mut optimizer = Adam::with_config(config);
253 let params = layer.parameters();
254 for param in ¶ms {
255 optimizer.add_parameter(param);
256 }
257
258 println!("Optimizer setup complete. Starting training...");
259
260 // Training loop
261 let num_epochs = 100;
262 let mut losses = Vec::new();
263
264 for epoch in 0..num_epochs {
265 // Forward pass
266 let y_pred = layer.forward(&x_data);
267
268 // Compute loss: MSE
269 let diff = y_pred.sub_tensor(&y_true);
270 let mut loss = diff.pow_scalar(2.0).mean();
271
272 // Backward pass
273 loss.backward(None);
274
275 // Optimizer step
276 let mut params = layer.parameters();
277 optimizer.step(&mut params);
278 optimizer.zero_grad(&mut params);
279
280 losses.push(loss.value());
281
282 // Print progress
283 if epoch % 20 == 0 || epoch == num_epochs - 1 {
284 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
285 }
286 }
287
288 // Evaluate final model
289 let final_predictions = layer.forward_no_grad(&x_data);
290
291 println!("\nFinal model evaluation:");
292 println!(" Learned weights: {:?}", layer.weight.data());
293 println!(" Learned bias: {:?}", layer.bias.data());
294 println!(" Target weights: [2.0, 3.0]");
295 println!(" Target bias: [1.0]");
296
297 println!(" Predictions vs True:");
298 for i in 0..4 {
299 let pred = final_predictions.data()[i];
300 let true_val = y_true.data()[i];
301 println!(
302 " Sample {}: pred={:.3}, true={:.1}, error={:.3}",
303 i + 1,
304 pred,
305 true_val,
306 (pred - true_val).abs()
307 );
308 }
309
310 // Training analysis
311 let initial_loss = losses[0];
312 let final_loss = losses[losses.len() - 1];
313 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
314
315 println!("\nTraining Analysis:");
316 println!(" Initial loss: {:.6}", initial_loss);
317 println!(" Final loss: {:.6}", final_loss);
318 println!(" Loss reduction: {:.1}%", loss_reduction);
319
320 Ok(())
321}
322
323/// Demonstrate single vs batch inference
324fn demonstrate_single_vs_batch_inference() {
325 println!("\n--- Single vs Batch Inference ---");
326
327 let layer = LinearLayer::new(4, 3, Some(46));
328
329 // Single inference
330 println!("Single inference:");
331 let single_input = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![1, 4]).unwrap();
332 let single_output = layer.forward_no_grad(&single_input);
333 println!(" Input shape: {:?}", single_input.shape().dims);
334 println!(" Output shape: {:?}", single_output.shape().dims);
335 println!(" Output: {:?}", single_output.data());
336
337 // Batch inference
338 println!("Batch inference:");
339 let batch_input = Tensor::from_slice(
340 &[
341 1.0, 2.0, 3.0, 4.0, // Sample 1
342 5.0, 6.0, 7.0, 8.0, // Sample 2
343 9.0, 10.0, 11.0, 12.0, // Sample 3
344 ],
345 vec![3, 4],
346 )
347 .unwrap();
348 let batch_output = layer.forward_no_grad(&batch_input);
349 println!(" Input shape: {:?}", batch_input.shape().dims);
350 println!(" Output shape: {:?}", batch_output.shape().dims);
351
352 // Verify batch consistency - first sample should match single inference
353 let _first_batch_sample = batch_output.view(vec![3, 3]); // Reshape to access first sample
354 let first_sample_data = &batch_output.data()[0..3]; // First 3 elements
355 let single_sample_data = single_output.data();
356
357 println!("Consistency check:");
358 println!(" Single output: {:?}", single_sample_data);
359 println!(" First batch sample: {:?}", first_sample_data);
360 println!(
361 " Match: {}",
362 single_sample_data
363 .iter()
364 .zip(first_sample_data.iter())
365 .all(|(a, b)| (a - b).abs() < 1e-6)
366 );
367}
368
369/// Demonstrate serialization and loading
370fn demonstrate_serialization() -> Result<(), Box<dyn std::error::Error>> {
371 println!("\n--- Serialization ---");
372
373 // Create and train a simple layer
374 let mut original_layer = LinearLayer::new(2, 1, Some(47));
375
376 // Simple training data
377 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
378 let y_true = Tensor::from_slice(&[5.0, 11.0], vec![2, 1]).unwrap();
379
380 let mut optimizer = Adam::with_learning_rate(0.01);
381 let params = original_layer.parameters();
382 for param in ¶ms {
383 optimizer.add_parameter(param);
384 }
385
386 // Train for a few epochs
387 for _ in 0..10 {
388 let y_pred = original_layer.forward(&x_data);
389 let mut loss = (y_pred.sub_tensor(&y_true)).pow_scalar(2.0).mean();
390 loss.backward(None);
391
392 let mut params = original_layer.parameters();
393 optimizer.step(&mut params);
394 optimizer.zero_grad(&mut params);
395 }
396
397 println!("Original layer trained");
398 println!(" Weight: {:?}", original_layer.weight.data());
399 println!(" Bias: {:?}", original_layer.bias.data());
400
401 // Save layer
402 original_layer.save_json("temp_linear_layer")?;
403
404 // Load layer
405 let loaded_layer = LinearLayer::load_json("temp_linear_layer", 2, 1)?;
406
407 println!("Loaded layer");
408 println!(" Weight: {:?}", loaded_layer.weight.data());
409 println!(" Bias: {:?}", loaded_layer.bias.data());
410
411 // Verify consistency
412 let test_input = Tensor::from_slice(&[1.0, 1.0], vec![1, 2]).unwrap();
413 let original_output = original_layer.forward_no_grad(&test_input);
414 let loaded_output = loaded_layer.forward_no_grad(&test_input);
415
416 println!("Consistency check:");
417 println!(" Original output: {:?}", original_output.data());
418 println!(" Loaded output: {:?}", loaded_output.data());
419 println!(
420 " Match: {}",
421 original_output
422 .data()
423 .iter()
424 .zip(loaded_output.data().iter())
425 .all(|(a, b)| (a - b).abs() < 1e-6)
426 );
427
428 println!("Serialization verification: PASSED");
429
430 Ok(())
431}Sourcepub fn sub_scalar(&self, scalar: f32) -> Tensor
pub fn sub_scalar(&self, scalar: f32) -> Tensor
Element-wise subtraction of a scalar from this tensor
Performs element-wise subtraction of a scalar value: output[i] = self[i] - scalar
§Arguments
scalar- The scalar value to subtract from each element
§Returns
A new tensor with the scalar subtracted from each element
§Performance Characteristics
- SIMD Optimization: AVX2-optimized with 32-element blocks and 4x unrolling
- Scalar Fallback: 4x unrolled scalar implementation for non-SIMD hardware
- Cache-friendly: Linear memory access patterns
- Mathematical Accuracy: High-precision subtraction computation
- GradTrack Support: Full automatic differentiation with efficient gradient computation
§Examples
§Basic Scalar Subtraction
use train_station::Tensor;
let a = Tensor::from_slice(&[5.0, 7.0, 9.0], vec![3]).unwrap();
let b = a.sub_scalar(2.0);
assert_eq!(b.shape().dims, vec![3]);
assert_eq!(b.get(&[0]), 3.0); // 5.0 - 2.0
assert_eq!(b.get(&[1]), 5.0); // 7.0 - 2.0
assert_eq!(b.get(&[2]), 7.0); // 9.0 - 2.0§Negative Scalar Subtraction
use train_station::Tensor;
let a = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let b = a.sub_scalar(-2.0); // Subtracting negative = adding
assert_eq!(b.shape().dims, vec![3]);
assert_eq!(b.get(&[0]), 3.0); // 1.0 - (-2.0) = 3.0
assert_eq!(b.get(&[1]), 4.0); // 2.0 - (-2.0) = 4.0
assert_eq!(b.get(&[2]), 5.0); // 3.0 - (-2.0) = 5.0Examples found in repository?
74fn demonstrate_data_pipeline() -> Result<(), Box<dyn std::error::Error>> {
75 println!("\n--- Data Processing Pipeline ---");
76
77 // Simulate raw sensor data with noise
78 let raw_data: Vec<f32> = (0..20)
79 .map(|i| {
80 let base = i as f32 * 0.5;
81 let noise = (i % 3) as f32 * 0.1;
82 base + noise
83 })
84 .collect();
85
86 let tensor = Tensor::from_slice(&raw_data, vec![20])?;
87 println!("Raw sensor data: {:?}", tensor.data());
88
89 // Multi-stage processing pipeline
90 println!("\nProcessing pipeline:");
91 println!("1. Normalize data (z-score)");
92 println!("2. Apply smoothing filter");
93 println!("3. Detect outliers");
94 println!("4. Apply feature scaling");
95
96 // Stage 1: Normalization
97 let mean = tensor.mean().value();
98 let std = tensor.std().value();
99 let normalized: Tensor = tensor
100 .iter()
101 .map(|elem| elem.sub_scalar(mean).div_scalar(std))
102 .collect();
103 println!(
104 " Normalized (mean={:.3}, std={:.3}): {:?}",
105 mean,
106 std,
107 normalized.data()
108 );
109
110 // Stage 2: Smoothing (simple moving average)
111 let smoothed: Tensor = normalized
112 .iter()
113 .enumerate()
114 .map(|(i, elem)| {
115 if i == 0 || i == normalized.size() - 1 {
116 elem.clone()
117 } else {
118 // Simple 3-point average
119 let prev = normalized.element_view(i - 1);
120 let next = normalized.element_view(i + 1);
121 elem.add_tensor(&prev).add_tensor(&next).div_scalar(3.0)
122 }
123 })
124 .collect();
125 println!(" Smoothed: {:?}", smoothed.data());
126
127 // Stage 3: Outlier detection and removal
128 let outlier_threshold = 2.0;
129 let cleaned: Tensor = smoothed
130 .iter()
131 .filter(|elem| elem.value().abs() < outlier_threshold)
132 .collect();
133 println!(
134 " Outliers removed (threshold={}): {:?}",
135 outlier_threshold,
136 cleaned.data()
137 );
138
139 // Stage 4: Feature scaling to [0, 1] range
140 let min_val = cleaned
141 .iter()
142 .map(|e| e.value())
143 .fold(f32::INFINITY, f32::min);
144 let max_val = cleaned
145 .iter()
146 .map(|e| e.value())
147 .fold(f32::NEG_INFINITY, f32::max);
148 let scaled: Tensor = cleaned
149 .iter()
150 .map(|elem| elem.sub_scalar(min_val).div_scalar(max_val - min_val))
151 .collect();
152 println!(" Scaled to [0,1]: {:?}", scaled.data());
153
154 Ok(())
155}
156
157/// Demonstrate conditional processing patterns
158///
159/// Shows how to implement dynamic filtering and transformation
160/// based on data characteristics and conditions.
161fn demonstrate_conditional_processing() -> Result<(), Box<dyn std::error::Error>> {
162 println!("\n--- Conditional Processing ---");
163
164 // Create data with mixed characteristics
165 let data = vec![1.0, -2.0, 3.0, -4.0, 5.0, -6.0, 7.0, -8.0, 9.0, -10.0];
166 let tensor = Tensor::from_slice(&data, vec![10])?;
167 println!("Input data: {:?}", tensor.data());
168
169 // Conditional transformation based on sign
170 println!("\nConditional transformation (positive/negative handling):");
171 let processed: Tensor = tensor
172 .iter()
173 .map(|elem| {
174 let val = elem.value();
175 if val > 0.0 {
176 elem.pow_scalar(2.0) // Square positive values
177 } else {
178 elem.mul_scalar(-1.0).sqrt() // Square root of absolute negative values
179 }
180 })
181 .collect();
182 println!(" Processed: {:?}", processed.data());
183
184 // Adaptive filtering based on local statistics
185 println!("\nAdaptive filtering (remove values > 2 std from local mean):");
186 let window_size = 3;
187 let adaptive_filtered: Tensor = tensor
188 .iter()
189 .enumerate()
190 .filter(|(i, elem)| {
191 let start = i.saturating_sub(window_size / 2);
192 let end = (i + window_size / 2 + 1).min(tensor.size());
193
194 // Calculate local mean and std
195 let local_values: Vec<f32> = (start..end)
196 .map(|j| tensor.element_view(j).value())
197 .collect();
198
199 let local_mean = local_values.iter().sum::<f32>() / local_values.len() as f32;
200 let local_variance = local_values
201 .iter()
202 .map(|v| (v - local_mean).powi(2))
203 .sum::<f32>()
204 / local_values.len() as f32;
205 let local_std = local_variance.sqrt();
206
207 let threshold = local_mean + 2.0 * local_std;
208 elem.value() <= threshold
209 })
210 .map(|(_, elem)| elem)
211 .collect();
212 println!(" Adaptive filtered: {:?}", adaptive_filtered.data());
213
214 // Multi-condition processing
215 println!("\nMulti-condition processing:");
216 let multi_processed: Tensor = tensor
217 .iter()
218 .map(|elem| {
219 let val = elem.value();
220 match () {
221 _ if val > 5.0 => elem.mul_scalar(2.0), // Double large values
222 _ if val < -5.0 => elem.div_scalar(2.0), // Halve small values
223 _ if val.abs() < 2.0 => elem.add_scalar(1.0), // Add 1 to small values
224 _ => elem.clone(), // Keep others unchanged
225 }
226 })
227 .collect();
228 println!(" Multi-condition: {:?}", multi_processed.data());
229
230 Ok(())
231}
232
233/// Demonstrate batch processing operations
234///
235/// Shows efficient processing of large datasets using iterator
236/// patterns and batch operations for performance optimization.
237fn demonstrate_batch_operations() -> Result<(), Box<dyn std::error::Error>> {
238 println!("\n--- Batch Operations ---");
239
240 // Create a larger dataset for batch processing
241 let size = 100;
242 let data: Vec<f32> = (0..size)
243 .map(|i| {
244 let x = i as f32 / size as f32;
245 x * x + 0.1 * (i % 7) as f32 // Quadratic with some noise
246 })
247 .collect();
248
249 let tensor = Tensor::from_slice(&data, vec![size])?;
250 println!("Dataset size: {}", tensor.size());
251
252 // Batch processing with windowing
253 println!("\nBatch processing with sliding windows:");
254 let batch_size = 10;
255 let batches: Vec<Tensor> = tensor
256 .iter()
257 .collect::<Vec<_>>()
258 .chunks(batch_size)
259 .map(|chunk| {
260 // Process each batch independently
261 chunk
262 .iter()
263 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
264 .collect()
265 })
266 .collect();
267
268 println!(
269 " Processed {} batches of size {}",
270 batches.len(),
271 batch_size
272 );
273 for (i, batch) in batches.iter().enumerate() {
274 println!(
275 " Batch {}: mean={:.3}, std={:.3}",
276 i,
277 batch.mean().value(),
278 batch.std().value()
279 );
280 }
281
282 // Parallel-like processing with stride
283 println!("\nStrided processing (every nth element):");
284 let stride = 5;
285 let strided: Tensor = tensor
286 .iter()
287 .enumerate()
288 .filter(|(i, _)| i % stride == 0)
289 .map(|(_, elem)| elem)
290 .collect();
291 println!(" Strided (every {}th): {:?}", stride, strided.data());
292
293 // Hierarchical processing
294 println!("\nHierarchical processing (coarse to fine):");
295 let coarse: Tensor = tensor
296 .iter()
297 .enumerate()
298 .filter(|(i, _)| i % 4 == 0) // Take every 4th element
299 .map(|(_, elem)| elem)
300 .collect();
301
302 let fine: Tensor = tensor
303 .iter()
304 .enumerate()
305 .filter(|(i, _)| i % 4 != 0) // Take the rest
306 .map(|(_, elem)| elem)
307 .collect();
308
309 println!(" Coarse (every 4th): {:?}", coarse.data());
310 println!(" Fine (rest): {:?}", fine.data());
311
312 // Combine coarse and fine with different processing
313 let combined: Tensor = coarse
314 .iter()
315 .map(|elem| elem.mul_scalar(2.0)) // Scale coarse
316 .chain(fine.iter().map(|elem| elem.div_scalar(2.0))) // Scale fine
317 .collect();
318 println!(" Combined: {:?}", combined.data());
319
320 Ok(())
321}
322
323/// Demonstrate real-world processing scenarios
324///
325/// Shows practical applications of iterator patterns for
326/// common data processing tasks in machine learning and analytics.
327fn demonstrate_real_world_scenarios() -> Result<(), Box<dyn std::error::Error>> {
328 println!("\n--- Real-world Scenarios ---");
329
330 // Scenario 1: Time series analysis
331 println!("\nScenario 1: Time Series Analysis");
332 let time_series: Vec<f32> = (0..24)
333 .map(|hour| {
334 let base = 20.0 + 10.0 * (hour as f32 * std::f32::consts::PI / 12.0).sin();
335 base + (hour % 3) as f32 * 2.0 // Add some noise
336 })
337 .collect();
338
339 let series = Tensor::from_slice(&time_series, vec![24])?;
340 println!(" Time series (24 hours): {:?}", series.data());
341
342 // Calculate moving average
343 let window_size = 3;
344 let moving_avg: Tensor = series
345 .iter()
346 .enumerate()
347 .map(|(i, _)| {
348 let start = i.saturating_sub(window_size / 2);
349 let end = (i + window_size / 2 + 1).min(series.size());
350 let window = series.iter_range(start, end);
351 window.fold(0.0, |acc, elem| acc + elem.value()) / (end - start) as f32
352 })
353 .map(|val| Tensor::from_slice(&[val], vec![1]).unwrap())
354 .collect();
355 println!(
356 " Moving average (window={}): {:?}",
357 window_size,
358 moving_avg.data()
359 );
360
361 // Scenario 2: Feature engineering
362 println!("\nScenario 2: Feature Engineering");
363 let features = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
364 println!(" Original features: {:?}", features.data());
365
366 // Create polynomial features
367 let poly_features: Tensor = features
368 .iter()
369 .flat_map(|elem| {
370 vec![
371 elem.clone(), // x^1
372 elem.pow_scalar(2.0), // x^2
373 elem.pow_scalar(3.0), // x^3
374 ]
375 })
376 .collect();
377 println!(
378 " Polynomial features (x, x^2, x^3): {:?}",
379 poly_features.data()
380 );
381
382 // Scenario 3: Data augmentation
383 println!("\nScenario 3: Data Augmentation");
384 let original = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?;
385 println!(" Original data: {:?}", original.data());
386
387 // Augment with noise and scaling
388 let augmented: Tensor = original
389 .iter()
390 .flat_map(|elem| {
391 vec![
392 elem.clone(), // Original
393 elem.add_scalar(0.1), // Add noise
394 elem.sub_scalar(0.1), // Subtract noise
395 elem.mul_scalar(1.1), // Scale up
396 elem.mul_scalar(0.9), // Scale down
397 ]
398 })
399 .collect();
400 println!(" Augmented data: {:?}", augmented.data());
401
402 // Scenario 4: Statistical analysis
403 println!("\nScenario 4: Statistical Analysis");
404 let sample_data = Tensor::from_slice(&[1.1, 2.3, 1.8, 2.1, 1.9, 2.0, 1.7, 2.2], vec![8])?;
405 println!(" Sample data: {:?}", sample_data.data());
406
407 // Calculate various statistics
408 let mean = sample_data.mean().value();
409 let std = sample_data.std().value();
410 let min = sample_data
411 .iter()
412 .map(|e| e.value())
413 .fold(f32::INFINITY, f32::min);
414 let max = sample_data
415 .iter()
416 .map(|e| e.value())
417 .fold(f32::NEG_INFINITY, f32::max);
418
419 // Z-score normalization
420 let z_scores: Tensor = sample_data
421 .iter()
422 .map(|elem| elem.sub_scalar(mean).div_scalar(std))
423 .collect();
424
425 println!(
426 " Statistics: mean={:.3}, std={:.3}, min={:.3}, max={:.3}",
427 mean, std, min, max
428 );
429 println!(" Z-scores: {:?}", z_scores.data());
430
431 Ok(())
432}Source§impl Tensor
impl Tensor
Sourcepub fn tanh(&self) -> Tensor
pub fn tanh(&self) -> Tensor
Element-wise hyperbolic tangent activation
Computes hyperbolic tangent for each element: output[i] = tanh(self[i])
The hyperbolic tangent function maps any real number to the range (-1, 1), making it useful as an activation function in neural networks.
§Returns
A new tensor with tanh applied to each element, values in range (-1, 1)
§Performance Characteristics
- High Precision: Accurate scalar implementation for mathematical validation
- 4x Unrolling: Optimized scalar operations with instruction-level parallelism
- Cache-friendly: Linear memory access patterns
- Numerical Stability: Robust handling of extreme input values
- GradTrack Support: Full automatic differentiation with efficient gradient computation
§Mathematical Properties
- Range: Output values are in the range (-1, 1)
- Symmetry: tanh(-x) = -tanh(x) (odd function)
- Zero: tanh(0) = 0
- Gradient: ∂tanh(x)/∂x = 1 - tanh²(x) = sech²(x)
§Examples
§Basic Hyperbolic Tangent
use train_station::Tensor;
let a = Tensor::from_slice(&[-1.0, 0.0, 1.0], vec![3]).unwrap();
let b = a.tanh();
assert_eq!(b.shape().dims, vec![3]);
assert!((b.get(&[0]) - (-0.7615942)).abs() < 1e-6); // tanh(-1.0)
assert!((b.get(&[1]) - 0.0).abs() < 1e-6); // tanh(0.0)
assert!((b.get(&[2]) - 0.7615942).abs() < 1e-6); // tanh(1.0)§Extreme Values
use train_station::Tensor;
let a = Tensor::from_slice(&[-10.0, 10.0], vec![2]).unwrap();
let b = a.tanh();
assert_eq!(b.shape().dims, vec![2]);
assert!((b.get(&[0]) - (-1.0)).abs() < 1e-6); // tanh(-10.0) ≈ -1
assert!((b.get(&[1]) - 1.0).abs() < 1e-6); // tanh(10.0) ≈ 1Source§impl Tensor
impl Tensor
Sourcepub fn argmax(&self) -> Tensor
pub fn argmax(&self) -> Tensor
Returns the index of the maximum value across all elements in the tensor
This operation finds the flat index (0-based) of the element with the highest value. If multiple elements have the same maximum value, the index of the first occurrence is returned. The output is a scalar tensor with shape [1] containing the index as a float.
This operation is non-differentiable and the output never requires gradients.
§Returns
A tensor with shape [1] containing the flat index of the maximum value
§Examples
use train_station::Tensor;
// 1D tensor
let tensor = Tensor::from_slice(&[1.0, 5.0, 3.0, 2.0], vec![4]).unwrap();
let max_idx = tensor.argmax();
assert_eq!(max_idx.shape().dims, vec![1]);
assert_eq!(max_idx.get(&[0]), 1.0); // Index 1 has value 5.0use train_station::Tensor;
// 2D tensor
let tensor = Tensor::from_slice(&[1.0, 3.0, 2.0, 4.0, 0.0, 5.0], vec![2, 3]).unwrap();
let max_idx = tensor.argmax();
assert_eq!(max_idx.get(&[0]), 5.0); // Flat index 5 has value 5.0use train_station::Tensor;
// Tied values return first occurrence
let tensor = Tensor::from_slice(&[3.0, 5.0, 5.0, 2.0], vec![4]).unwrap();
let max_idx = tensor.argmax();
assert_eq!(max_idx.get(&[0]), 1.0); // First occurrence of 5.0 at index 1Sourcepub fn argmax_dim(&self, dim: usize, keepdim: bool) -> Tensor
pub fn argmax_dim(&self, dim: usize, keepdim: bool) -> Tensor
Returns the indices of maximum values along a specified dimension
This operation finds the indices of maximum values along the specified dimension. For each slice along the dimension, it returns the index of the maximum value. If multiple elements have the same maximum value, the index of the first occurrence is returned.
The output shape depends on the keepdim parameter:
- If
keepdimistrue, the reduced dimension is kept with size 1 - If
keepdimisfalse, the reduced dimension is removed
This operation is non-differentiable and the output never requires gradients.
§Arguments
dim- The dimension along which to find argmax indices (0-based)keepdim- Whether to keep the reduced dimension with size 1
§Returns
A tensor containing the indices of maximum values along the specified dimension
§Panics
Panics if dim is out of bounds for the tensor’s rank or if the dimension size is 0.
§Examples
use train_station::Tensor;
// 2D tensor: [[1.0, 3.0, 2.0],
// [4.0, 0.0, 5.0]]
let tensor = Tensor::from_slice(&[1.0, 3.0, 2.0, 4.0, 0.0, 5.0], vec![2, 3]).unwrap();
// argmax along columns (dim=1)
let col_max_idx = tensor.argmax_dim(1, false);
assert_eq!(col_max_idx.shape().dims, vec![2]);
assert_eq!(col_max_idx.get(&[0]), 1.0); // Row 0: max at index 1 (value 3.0)
assert_eq!(col_max_idx.get(&[1]), 2.0); // Row 1: max at index 2 (value 5.0)use train_station::Tensor;
// argmax along rows (dim=0) with keepdim
let tensor = Tensor::from_slice(&[1.0, 3.0, 2.0, 4.0, 0.0, 5.0], vec![2, 3]).unwrap();
let row_max_idx = tensor.argmax_dim(0, true);
assert_eq!(row_max_idx.shape().dims, vec![1, 3]);
assert_eq!(row_max_idx.get(&[0, 0]), 1.0); // Col 0: max at index 1 (value 4.0)
assert_eq!(row_max_idx.get(&[0, 1]), 0.0); // Col 1: max at index 0 (value 3.0)
assert_eq!(row_max_idx.get(&[0, 2]), 1.0); // Col 2: max at index 1 (value 5.0)use train_station::Tensor;
// 1D tensor edge case
let tensor = Tensor::from_slice(&[5.0, 1.0, 8.0, 3.0], vec![4]).unwrap();
let max_idx = tensor.argmax_dim(0, false);
assert_eq!(max_idx.shape().dims, vec![1]); // Special case: becomes [1] not []
assert_eq!(max_idx.get(&[0]), 2.0); // Index 2 has maximum value 8.0Source§impl Tensor
impl Tensor
Sourcepub fn argmin(&self) -> Tensor
pub fn argmin(&self) -> Tensor
Returns the index of the minimum value in the tensor
This method finds the flat index of the minimum value across all elements in the tensor. The result is a scalar tensor containing the index as a floating-point value. This operation is non-differentiable and the output never requires gradient tracking.
§Returns
A tensor with shape [1] containing the flat index of the minimum value
as a f32. If the input tensor is empty, returns 0.0.
§Examples
use train_station::Tensor;
let tensor = Tensor::from_slice(&[3.0, -2.0, 5.0, -1.0], vec![4]).unwrap();
let min_index = tensor.argmin();
assert_eq!(min_index.get(&[0]), 1.0); // -2.0 is at index 1use train_station::Tensor;
// Empty tensor case
let empty_tensor = Tensor::new(vec![0]);
let min_index = empty_tensor.argmin();
assert_eq!(min_index.get(&[0]), 0.0);Sourcepub fn argmin_dim(&self, dim: usize, keepdim: bool) -> Tensor
pub fn argmin_dim(&self, dim: usize, keepdim: bool) -> Tensor
Returns the indices of minimum values along a specified dimension
This method finds the indices of minimum values along the specified dimension. The result contains the indices where the minimum values occur in that dimension. This operation is non-differentiable and the output never requires gradient tracking.
§Arguments
dim- The dimension along which to find minimum indices (0-based)keepdim- Whether to keep the reduced dimension in the output shape- If
true, the reduced dimension is kept with size 1 - If
false, the reduced dimension is removed from the output shape
- If
§Returns
A tensor containing the indices of minimum values along the specified dimension.
The output shape depends on keepdim:
- If
keepdimistrue, the reduced dimension has size 1 - If
keepdimisfalse, the reduced dimension is removed
§Panics
- If
dimis out of bounds for the tensor’s rank - If the dimension to reduce has size 0
§Examples
use train_station::Tensor;
let tensor = Tensor::from_slice(&[3.0, -2.0, 5.0, -1.0, 0.0, -3.0], vec![2, 3]).unwrap();
// Find minimum indices along dimension 1 (columns), keeping the dimension
let indices = tensor.argmin_dim(1, true);
assert_eq!(indices.shape().dims, vec![2, 1]);
assert_eq!(indices.get(&[0, 0]), 1.0); // -2.0 is at index 1 in first row
assert_eq!(indices.get(&[1, 0]), 2.0); // -3.0 is at index 2 in second rowuse train_station::Tensor;
let tensor = Tensor::from_slice(&[3.0, -2.0, 5.0, -1.0, 0.0, -3.0], vec![2, 3]).unwrap();
// Find minimum indices along dimension 1 (columns), removing the dimension
let indices = tensor.argmin_dim(1, false);
assert_eq!(indices.shape().dims, vec![2]);
assert_eq!(indices.get(&[0]), 1.0); // -2.0 is at index 1 in first row
assert_eq!(indices.get(&[1]), 2.0); // -3.0 is at index 2 in second rowuse train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
// Find minimum index in a 1D tensor
let index = tensor.argmin_dim(0, false);
assert_eq!(index.shape().dims, vec![1]);
assert_eq!(index.get(&[0]), 0.0); // 1.0 is at index 0Source§impl Tensor
impl Tensor
Sourcepub fn max(&self) -> Tensor
pub fn max(&self) -> Tensor
Computes the maximum value over all elements in the tensor
Returns a scalar tensor containing the maximum value. For empty tensors, returns negative infinity. This operation supports gradient tracking through the GradTrack system.
§Returns
A tensor with shape [1] containing the maximum value
§Examples
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 5.0, 3.0, 2.0], vec![2, 2]).unwrap();
let max_val = tensor.max();
assert_eq!(max_val.get(&[0]), 5.0);§GradTrack Support
When requires_grad is true, this operation is tracked for automatic
differentiation. The gradient computation uses the saved input and output
for efficient backward pass.
Sourcepub fn max_dims(&self, dims: &[usize], keepdim: bool) -> Tensor
pub fn max_dims(&self, dims: &[usize], keepdim: bool) -> Tensor
Computes the maximum value over specified dimensions
Reduces the tensor along the specified dimensions by computing the maximum
value in each reduction group. The keepdim parameter determines whether
reduced dimensions are kept with size 1 or removed entirely.
§Arguments
dims- Dimensions to reduce over (must be valid for the tensor’s rank)keepdim- If true, reduced dimensions are kept with size 1; if false, they are removed
§Returns
A tensor with the specified dimensions reduced
§Examples
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
// Max over columns (dim 1), keeping dimensions
let max_cols = tensor.max_dims(&[1], true);
assert_eq!(max_cols.shape().dims, vec![2, 1]);
assert_eq!(max_cols.get(&[0, 0]), 3.0);
assert_eq!(max_cols.get(&[1, 0]), 6.0);
// Max over rows (dim 0), removing dimensions
let max_rows = tensor.max_dims(&[0], false);
assert_eq!(max_rows.shape().dims, vec![3]);
assert_eq!(max_rows.get(&[0]), 4.0);
assert_eq!(max_rows.get(&[1]), 5.0);
assert_eq!(max_rows.get(&[2]), 6.0);§Panics
Panics if:
dimsis empty- Any dimension in
dimsis out of bounds for the tensor’s rank
§GradTrack Support
When requires_grad is true, this operation is tracked for automatic
differentiation. The gradient computation preserves the original input
shape and handles broadcasting correctly.
Source§impl Tensor
impl Tensor
Sourcepub fn mean(&self) -> Tensor
pub fn mean(&self) -> Tensor
Computes the arithmetic mean of all elements in the tensor
This method calculates the average value across all tensor elements by summing all values and dividing by the total number of elements. The result is a scalar tensor containing the mean value. This operation supports gradient tracking through the GradTrack system.
§Returns
A tensor with shape [1] containing the arithmetic mean of all elements.
For empty tensors, returns 0.0 as a safe default.
§Performance Characteristics
- Linear Time: O(n) complexity for computing the sum
- Memory Efficient: Single pass through tensor data with SIMD-optimized accumulation
- Numerical Stability: Uses direct accumulation for typical tensor sizes
- Edge Case Handling: Returns 0.0 for empty tensors
§Examples
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let mean_val = tensor.mean();
assert_eq!(mean_val.get(&[0]), 2.5); // (1+2+3+4)/4 = 2.5use train_station::Tensor;
// Empty tensor case
let empty_tensor = Tensor::new(vec![0]);
let mean_val = empty_tensor.mean();
assert_eq!(mean_val.get(&[0]), 0.0);§GradTrack Support
When requires_grad is true, this operation is tracked for automatic
differentiation. The gradient computation distributes the gradient equally
across all input elements.
Examples found in repository?
179fn demonstrate_utility_functions() {
180 println!("\n--- Utility Functions ---");
181
182 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
183
184 // Basic properties
185 println!("Shape: {:?}", tensor.shape().dims);
186 println!("Size: {}", tensor.size());
187 println!("Is contiguous: {}", tensor.is_contiguous());
188 println!("Device: {:?}", tensor.device());
189
190 // Mathematical operations
191 let sum = tensor.sum();
192 println!("Sum: {}", sum.value());
193
194 let mean = tensor.mean();
195 println!("Mean: {}", mean.value());
196
197 let norm = tensor.norm();
198 println!("Norm: {}", norm.value());
199
200 // Device placement
201 let cpu_tensor = Tensor::zeros_on_device(vec![3, 3], train_station::Device::cpu());
202 println!(
203 "CPU tensor: shape {:?}, device: {:?}",
204 cpu_tensor.shape().dims,
205 cpu_tensor.device()
206 );
207}More examples
84fn demonstrate_default_adam() -> Result<(), Box<dyn std::error::Error>> {
85 println!("--- Default Adam Configuration ---");
86
87 // Create a simple regression problem: y = 2*x + 1
88 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
89 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
90
91 // Create model parameters
92 let mut weight = Tensor::randn(vec![1, 1], Some(42)).with_requires_grad();
93 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
94
95 // Create Adam optimizer with default configuration
96 let mut optimizer = Adam::new();
97 optimizer.add_parameter(&weight);
98 optimizer.add_parameter(&bias);
99
100 println!("Default Adam configuration:");
101 println!(" Learning rate: {}", optimizer.learning_rate());
102 println!(" Initial weight: {:.6}", weight.value());
103 println!(" Initial bias: {:.6}", bias.value());
104
105 // Training loop
106 let num_epochs = 50;
107 let mut losses = Vec::new();
108
109 for epoch in 0..num_epochs {
110 // Forward pass
111 let y_pred = x_data.matmul(&weight) + &bias;
112 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
113
114 // Backward pass
115 loss.backward(None);
116
117 // Optimizer step
118 optimizer.step(&mut [&mut weight, &mut bias]);
119 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
120
121 losses.push(loss.value());
122
123 if epoch % 10 == 0 || epoch == num_epochs - 1 {
124 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
125 }
126 }
127
128 // Evaluate final model
129 let _final_predictions = x_data.matmul(&weight) + &bias;
130 println!("\nFinal model:");
131 println!(" Learned weight: {:.6} (target: 2.0)", weight.value());
132 println!(" Learned bias: {:.6} (target: 1.0)", bias.value());
133 println!(" Final loss: {:.6}", losses[losses.len() - 1]);
134
135 Ok(())
136}
137
138/// Demonstrate learning rate comparison
139fn demonstrate_learning_rate_comparison() -> Result<(), Box<dyn std::error::Error>> {
140 println!("\n--- Learning Rate Comparison ---");
141
142 let learning_rates = [0.001, 0.01, 0.1];
143 let mut results = Vec::new();
144
145 for &lr in &learning_rates {
146 println!("\nTesting learning rate: {}", lr);
147
148 let stats = train_with_config(TrainingConfig {
149 learning_rate: lr,
150 ..Default::default()
151 })?;
152
153 results.push((lr, stats.clone()));
154
155 println!(" Final loss: {:.6}", stats.final_loss);
156 println!(" Convergence epoch: {}", stats.convergence_epoch);
157 }
158
159 // Compare results
160 println!("\nLearning Rate Comparison Summary:");
161 for (lr, stats) in &results {
162 println!(
163 " LR={:6}: Loss={:.6}, Converged@{}",
164 lr, stats.final_loss, stats.convergence_epoch
165 );
166 }
167
168 Ok(())
169}
170
171/// Demonstrate weight decay comparison
172fn demonstrate_weight_decay_comparison() -> Result<(), Box<dyn std::error::Error>> {
173 println!("\n--- Weight Decay Comparison ---");
174
175 let weight_decays = [0.0, 0.001, 0.01];
176 let mut results = Vec::new();
177
178 for &wd in &weight_decays {
179 println!("\nTesting weight decay: {}", wd);
180
181 let stats = train_with_config(TrainingConfig {
182 weight_decay: wd,
183 ..Default::default()
184 })?;
185
186 results.push((wd, stats.clone()));
187
188 println!(" Final loss: {:.6}", stats.final_loss);
189 println!(" Final weight norm: {:.6}", stats.weight_norm);
190 }
191
192 // Compare results
193 println!("\nWeight Decay Comparison Summary:");
194 for (wd, stats) in &results {
195 println!(
196 " WD={:6}: Loss={:.6}, Weight Norm={:.6}",
197 wd, stats.final_loss, stats.weight_norm
198 );
199 }
200
201 Ok(())
202}
203
204/// Demonstrate beta parameter tuning
205fn demonstrate_beta_parameter_tuning() -> Result<(), Box<dyn std::error::Error>> {
206 println!("\n--- Beta Parameter Tuning ---");
207
208 let beta_configs = [
209 (0.9, 0.999), // Default
210 (0.8, 0.999), // More aggressive momentum
211 (0.95, 0.999), // Less aggressive momentum
212 (0.9, 0.99), // Faster second moment decay
213 ];
214
215 let mut results = Vec::new();
216
217 for (i, (beta1, beta2)) in beta_configs.iter().enumerate() {
218 println!(
219 "\nTesting beta configuration {}: beta1={}, beta2={}",
220 i + 1,
221 beta1,
222 beta2
223 );
224
225 let config = TrainingConfig {
226 beta1: *beta1,
227 beta2: *beta2,
228 ..Default::default()
229 };
230
231 let stats = train_with_config(config)?;
232 results.push(((*beta1, *beta2), stats.clone()));
233
234 println!(" Final loss: {:.6}", stats.final_loss);
235 println!(" Convergence epoch: {}", stats.convergence_epoch);
236 }
237
238 // Compare results
239 println!("\nBeta Parameter Comparison Summary:");
240 for ((beta1, beta2), stats) in &results {
241 println!(
242 " B1={:4}, B2={:5}: Loss={:.6}, Converged@{}",
243 beta1, beta2, stats.final_loss, stats.convergence_epoch
244 );
245 }
246
247 Ok(())
248}
249
250/// Demonstrate configuration benchmarking
251fn demonstrate_configuration_benchmarking() -> Result<(), Box<dyn std::error::Error>> {
252 println!("\n--- Configuration Benchmarking ---");
253
254 // Define configurations to benchmark
255 let configs = vec![
256 (
257 "Conservative",
258 TrainingConfig {
259 learning_rate: 0.001,
260 weight_decay: 0.001,
261 beta1: 0.95,
262 ..Default::default()
263 },
264 ),
265 (
266 "Balanced",
267 TrainingConfig {
268 learning_rate: 0.01,
269 weight_decay: 0.0,
270 beta1: 0.9,
271 ..Default::default()
272 },
273 ),
274 (
275 "Aggressive",
276 TrainingConfig {
277 learning_rate: 0.1,
278 weight_decay: 0.0,
279 beta1: 0.8,
280 ..Default::default()
281 },
282 ),
283 ];
284
285 let mut benchmark_results = Vec::new();
286
287 for (name, config) in configs {
288 println!("\nBenchmarking {} configuration:", name);
289
290 let start_time = std::time::Instant::now();
291 let stats = train_with_config(config.clone())?;
292 let elapsed = start_time.elapsed();
293
294 println!(" Training time: {:.2}ms", elapsed.as_millis());
295 println!(" Final loss: {:.6}", stats.final_loss);
296 println!(" Convergence: {} epochs", stats.convergence_epoch);
297
298 benchmark_results.push((name.to_string(), stats, elapsed));
299 }
300
301 // Summary
302 println!("\nBenchmarking Summary:");
303 for (name, stats, elapsed) in &benchmark_results {
304 println!(
305 " {:12}: Loss={:.6}, Time={:4}ms, Converged@{}",
306 name,
307 stats.final_loss,
308 elapsed.as_millis(),
309 stats.convergence_epoch
310 );
311 }
312
313 Ok(())
314}
315
316/// Helper function to train with specific configuration
317fn train_with_config(config: TrainingConfig) -> Result<TrainingStats, Box<dyn std::error::Error>> {
318 // Create training data
319 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
320 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
321
322 // Create model parameters
323 let mut weight = Tensor::randn(vec![1, 1], Some(123)).with_requires_grad();
324 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
325
326 // Create optimizer with custom configuration
327 let adam_config = AdamConfig {
328 learning_rate: config.learning_rate,
329 beta1: config.beta1,
330 beta2: config.beta2,
331 eps: 1e-8,
332 weight_decay: config.weight_decay,
333 amsgrad: false,
334 };
335
336 let mut optimizer = Adam::with_config(adam_config);
337 optimizer.add_parameter(&weight);
338 optimizer.add_parameter(&bias);
339
340 // Training loop
341 let mut losses = Vec::new();
342 let mut convergence_epoch = config.epochs;
343
344 for epoch in 0..config.epochs {
345 // Forward pass
346 let y_pred = x_data.matmul(&weight) + &bias;
347 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
348
349 // Backward pass
350 loss.backward(None);
351
352 // Optimizer step
353 optimizer.step(&mut [&mut weight, &mut bias]);
354 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
355
356 let loss_value = loss.value();
357 losses.push(loss_value);
358
359 // Check for convergence (loss < 0.01)
360 if loss_value < 0.01 && convergence_epoch == config.epochs {
361 convergence_epoch = epoch;
362 }
363 }
364
365 Ok(TrainingStats {
366 config,
367 final_loss: losses[losses.len() - 1],
368 loss_history: losses,
369 convergence_epoch,
370 weight_norm: weight.norm().value(),
371 })
372}319fn train_with_scheduler(
320 scheduler: &mut dyn LearningRateScheduler,
321 num_epochs: usize,
322) -> Result<TrainingStats, Box<dyn std::error::Error>> {
323 // Create training data: y = 2*x + 1
324 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
325 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
326
327 // Create model parameters
328 let mut weight = Tensor::randn(vec![1, 1], Some(456)).with_requires_grad();
329 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
330
331 // Create optimizer with initial learning rate
332 let mut optimizer = Adam::with_learning_rate(0.05);
333 optimizer.add_parameter(&weight);
334 optimizer.add_parameter(&bias);
335
336 // Training loop
337 let mut losses = Vec::new();
338 let mut lr_history = Vec::new();
339 let mut convergence_epoch = num_epochs;
340
341 for epoch in 0..num_epochs {
342 // Forward pass
343 let y_pred = x_data.matmul(&weight) + &bias;
344 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
345
346 // Backward pass
347 loss.backward(None);
348
349 // Update learning rate using scheduler
350 let current_lr = optimizer.learning_rate();
351 let new_lr = scheduler.step(current_lr, epoch, loss.value());
352
353 if (new_lr - current_lr).abs() > 1e-8 {
354 optimizer.set_learning_rate(new_lr);
355 }
356
357 // Optimizer step
358 optimizer.step(&mut [&mut weight, &mut bias]);
359 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
360
361 let loss_value = loss.value();
362 losses.push(loss_value);
363 lr_history.push(new_lr);
364
365 // Check for convergence
366 if loss_value < 0.01 && convergence_epoch == num_epochs {
367 convergence_epoch = epoch;
368 }
369 }
370
371 Ok(TrainingStats {
372 scheduler_name: scheduler.name().to_string(),
373 final_loss: losses[losses.len() - 1],
374 lr_history,
375 loss_history: losses,
376 convergence_epoch,
377 })
378}105fn demonstrate_linear_regression() -> Result<(), Box<dyn std::error::Error>> {
106 println!("\n--- Linear Regression Training ---");
107
108 // Create model parameters
109 let mut weight = Tensor::randn(vec![1, 1], Some(43)).with_requires_grad();
110 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
111
112 // Create optimizer
113 let mut optimizer = Adam::with_learning_rate(0.01);
114 optimizer.add_parameter(&weight);
115 optimizer.add_parameter(&bias);
116
117 // Create simple training data: y = 2*x + 1
118 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
119 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
120
121 println!("Training data:");
122 println!(" X: {:?}", x_data.data());
123 println!(" Y: {:?}", y_true.data());
124 println!(" Target: y = 2*x + 1");
125
126 // Training loop
127 let num_epochs = 100;
128 let mut losses = Vec::new();
129
130 for epoch in 0..num_epochs {
131 // Forward pass: y_pred = x * weight + bias
132 let y_pred = x_data.matmul(&weight) + &bias;
133
134 // Compute loss: MSE
135 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
136
137 // Backward pass
138 loss.backward(None);
139
140 // Optimizer step
141 optimizer.step(&mut [&mut weight, &mut bias]);
142 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
143
144 losses.push(loss.value());
145
146 // Print progress every 20 epochs
147 if epoch % 20 == 0 || epoch == num_epochs - 1 {
148 println!("Epoch {:3}: Loss = {:.6}", epoch, loss.value());
149 }
150 }
151
152 // Evaluate final model
153 let final_predictions = x_data.matmul(&weight) + &bias;
154 println!("\nFinal model evaluation:");
155 println!(" Learned weight: {:.6}", weight.value());
156 println!(" Learned bias: {:.6}", bias.value());
157 println!(" Predictions vs True:");
158
159 for i in 0..5 {
160 let x1 = x_data.data()[i];
161 let pred = final_predictions.data()[i];
162 let true_val = y_true.data()[i];
163 println!(
164 " x={:.1}: pred={:.3}, true={:.1}, error={:.3}",
165 x1,
166 pred,
167 true_val,
168 (pred - true_val).abs()
169 );
170 }
171
172 Ok(())
173}
174
175/// Demonstrate advanced training patterns
176fn demonstrate_advanced_training() -> Result<(), Box<dyn std::error::Error>> {
177 println!("\n--- Advanced Training Patterns ---");
178
179 // Create a more complex model
180 let mut weight = Tensor::randn(vec![1, 2], Some(44)).with_requires_grad();
181 let mut bias = Tensor::zeros(vec![2]).with_requires_grad();
182
183 // Create optimizer with different learning rate
184 let mut optimizer = Adam::with_learning_rate(0.005);
185 optimizer.add_parameter(&weight);
186 optimizer.add_parameter(&bias);
187
188 // Create training data: y = 2*x + [1, 3]
189 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
190 let y_true = Tensor::from_slice(
191 &[3.0, 5.0, 7.0, 9.0, 11.0, 6.0, 8.0, 10.0, 12.0, 14.0],
192 vec![5, 2],
193 )
194 .unwrap();
195
196 println!("Advanced training with monitoring:");
197 println!(" Initial learning rate: {}", optimizer.learning_rate());
198
199 // Training loop with monitoring
200 let num_epochs = 50;
201 let mut losses = Vec::new();
202 let mut weight_norms = Vec::new();
203 let mut gradient_norms = Vec::new();
204
205 for epoch in 0..num_epochs {
206 // Forward pass
207 let y_pred = x_data.matmul(&weight) + &bias;
208 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
209
210 // Backward pass
211 loss.backward(None);
212
213 // Compute gradient norm before optimizer step
214 let gradient_norm = weight.grad_by_value().unwrap().norm();
215
216 // Optimizer step
217 optimizer.step(&mut [&mut weight, &mut bias]);
218 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
219
220 // Learning rate scheduling: reduce every 10 epochs
221 if epoch > 0 && epoch % 10 == 0 {
222 let current_lr = optimizer.learning_rate();
223 let new_lr = current_lr * 0.5;
224 optimizer.set_learning_rate(new_lr);
225 println!(
226 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
227 epoch, current_lr, new_lr
228 );
229 }
230
231 // Record metrics
232 losses.push(loss.value());
233 weight_norms.push(weight.norm().value());
234 gradient_norms.push(gradient_norm.value());
235
236 // Print detailed progress
237 if epoch % 10 == 0 || epoch == num_epochs - 1 {
238 println!(
239 "Epoch {:2}: Loss = {:.6}, Weight Norm = {:.6}, Gradient Norm = {:.6}",
240 epoch,
241 loss.value(),
242 weight.norm().value(),
243 gradient_norm.value()
244 );
245 }
246 }
247
248 println!("Final learning rate: {}", optimizer.learning_rate());
249
250 // Analyze training progression
251 let initial_loss = losses[0];
252 let final_loss = losses[losses.len() - 1];
253 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
254
255 println!("\nTraining Analysis:");
256 println!(" Initial loss: {:.6}", initial_loss);
257 println!(" Final loss: {:.6}", final_loss);
258 println!(" Loss reduction: {:.1}%", loss_reduction);
259 println!(" Final weight norm: {:.6}", weight.norm().value());
260 println!(" Final bias: {:?}", bias.data());
261
262 Ok(())
263}
264
265/// Demonstrate learning rate scheduling
266fn demonstrate_learning_rate_scheduling() -> Result<(), Box<dyn std::error::Error>> {
267 println!("\n--- Learning Rate Scheduling ---");
268
269 // Create simple model
270 let mut weight = Tensor::randn(vec![1, 1], Some(45)).with_requires_grad();
271 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
272
273 // Create optimizer with high initial learning rate
274 let mut optimizer = Adam::with_learning_rate(0.1);
275 optimizer.add_parameter(&weight);
276 optimizer.add_parameter(&bias);
277
278 // Simple data
279 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3, 1]).unwrap();
280 let y_true = Tensor::from_slice(&[2.0, 4.0, 6.0], vec![3, 1]).unwrap();
281
282 println!("Initial learning rate: {}", optimizer.learning_rate());
283
284 // Training loop with learning rate scheduling
285 let num_epochs = 50;
286 let mut losses = Vec::new();
287
288 for epoch in 0..num_epochs {
289 // Forward pass
290 let y_pred = x_data.matmul(&weight) + &bias;
291 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
292
293 // Backward pass
294 loss.backward(None);
295
296 // Optimizer step
297 optimizer.step(&mut [&mut weight, &mut bias]);
298 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
299
300 // Learning rate scheduling: reduce every 10 epochs
301 if epoch > 0 && epoch % 10 == 0 {
302 let current_lr = optimizer.learning_rate();
303 let new_lr = current_lr * 0.5;
304 optimizer.set_learning_rate(new_lr);
305 println!(
306 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
307 epoch, current_lr, new_lr
308 );
309 }
310
311 losses.push(loss.value());
312
313 // Print progress
314 if epoch % 10 == 0 || epoch == num_epochs - 1 {
315 println!(
316 "Epoch {:2}: Loss = {:.6}, LR = {:.3}",
317 epoch,
318 loss.value(),
319 optimizer.learning_rate()
320 );
321 }
322 }
323
324 println!("Final learning rate: {}", optimizer.learning_rate());
325
326 Ok(())
327}
328
329/// Demonstrate training monitoring and analysis
330fn demonstrate_training_monitoring() -> Result<(), Box<dyn std::error::Error>> {
331 println!("\n--- Training Monitoring ---");
332
333 // Create model
334 let mut weight = Tensor::randn(vec![1, 1], Some(46)).with_requires_grad();
335 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
336
337 // Create optimizer
338 let mut optimizer = Adam::with_learning_rate(0.01);
339 optimizer.add_parameter(&weight);
340 optimizer.add_parameter(&bias);
341
342 // Training data
343 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4, 1]).unwrap();
344 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0], vec![4, 1]).unwrap();
345
346 // Training loop with comprehensive monitoring
347 let num_epochs = 30;
348 let mut losses = Vec::new();
349 let mut weight_history = Vec::new();
350 let mut bias_history = Vec::new();
351
352 for epoch in 0..num_epochs {
353 // Forward pass
354 let y_pred = x_data.matmul(&weight) + &bias;
355 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
356
357 // Backward pass
358 loss.backward(None);
359
360 // Optimizer step
361 optimizer.step(&mut [&mut weight, &mut bias]);
362 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
363
364 // Record history
365 losses.push(loss.value());
366 weight_history.push(weight.value());
367 bias_history.push(bias.value());
368
369 // Print detailed monitoring
370 if epoch % 5 == 0 || epoch == num_epochs - 1 {
371 println!(
372 "Epoch {:2}: Loss = {:.6}, Weight = {:.6}, Bias = {:.6}",
373 epoch,
374 loss.value(),
375 weight.value(),
376 bias.value()
377 );
378 }
379 }
380
381 // Analyze training progression
382 println!("\nTraining Analysis:");
383 println!(" Initial loss: {:.6}", losses[0]);
384 println!(" Final loss: {:.6}", losses[losses.len() - 1]);
385 println!(
386 " Loss reduction: {:.1}%",
387 (losses[0] - losses[losses.len() - 1]) / losses[0] * 100.0
388 );
389
390 // Compute statistics
391 let loss_mean = compute_mean(&losses);
392 let loss_std = compute_std(&losses);
393 let weight_change = (weight_history[weight_history.len() - 1] - weight_history[0]).abs();
394 let bias_change = (bias_history[bias_history.len() - 1] - bias_history[0]).abs();
395
396 println!(" Average loss: {:.6} ± {:.6}", loss_mean, loss_std);
397 println!(" Weight change: {:.6}", weight_change);
398 println!(" Bias change: {:.6}", bias_change);
399 println!(" Final weight norm: {:.6}", weight.norm().value());
400 println!(" Final bias: {:.6}", bias.value());
401
402 Ok(())
403}430fn demonstrate_training_workflow() -> Result<(), Box<dyn std::error::Error>> {
431 println!("\n--- Training Workflow ---");
432
433 // Create a simple classification network
434 let config = FeedForwardConfig {
435 input_size: 2,
436 hidden_sizes: vec![4, 3],
437 output_size: 1,
438 use_bias: true,
439 };
440 let mut network = FeedForwardNetwork::new(config, Some(46));
441
442 println!("Training network: 2 -> [4, 3] -> 1");
443
444 // Create simple binary classification data: XOR problem
445 let x_data = Tensor::from_slice(
446 &[
447 0.0, 0.0, // -> 0
448 0.0, 1.0, // -> 1
449 1.0, 0.0, // -> 1
450 1.0, 1.0, // -> 0
451 ],
452 vec![4, 2],
453 )
454 .unwrap();
455
456 let y_true = Tensor::from_slice(&[0.0, 1.0, 1.0, 0.0], vec![4, 1]).unwrap();
457
458 println!("Training on XOR problem:");
459 println!(" Input shape: {:?}", x_data.shape().dims);
460 println!(" Target shape: {:?}", y_true.shape().dims);
461
462 // Create optimizer
463 let mut optimizer = Adam::with_learning_rate(0.1);
464 let params = network.parameters();
465 for param in ¶ms {
466 optimizer.add_parameter(param);
467 }
468
469 // Training loop
470 let num_epochs = 50;
471 let mut losses = Vec::new();
472
473 for epoch in 0..num_epochs {
474 // Forward pass
475 let y_pred = network.forward(&x_data);
476
477 // Compute loss: MSE
478 let diff = y_pred.sub_tensor(&y_true);
479 let mut loss = diff.pow_scalar(2.0).mean();
480
481 // Backward pass
482 loss.backward(None);
483
484 // Optimizer step and zero grad
485 let mut params = network.parameters();
486 optimizer.step(&mut params);
487 optimizer.zero_grad(&mut params);
488
489 losses.push(loss.value());
490
491 // Print progress
492 if epoch % 10 == 0 || epoch == num_epochs - 1 {
493 println!("Epoch {:2}: Loss = {:.6}", epoch, loss.value());
494 }
495 }
496
497 // Test final model
498 let final_predictions = network.forward_no_grad(&x_data);
499 println!("\nFinal predictions vs targets:");
500 for i in 0..4 {
501 let pred = final_predictions.data()[i];
502 let target = y_true.data()[i];
503 let input_x = x_data.data()[i * 2];
504 let input_y = x_data.data()[i * 2 + 1];
505 println!(
506 " [{:.0}, {:.0}] -> pred: {:.3}, target: {:.0}, error: {:.3}",
507 input_x,
508 input_y,
509 pred,
510 target,
511 (pred - target).abs()
512 );
513 }
514
515 Ok(())
516}
517
518/// Demonstrate comprehensive training with 100+ steps
519fn demonstrate_comprehensive_training() -> Result<(), Box<dyn std::error::Error>> {
520 println!("\n--- Comprehensive Training (100+ Steps) ---");
521
522 // Create a regression network
523 let config = FeedForwardConfig {
524 input_size: 3,
525 hidden_sizes: vec![8, 6, 4],
526 output_size: 2,
527 use_bias: true,
528 };
529 let mut network = FeedForwardNetwork::new(config, Some(47));
530
531 println!("Network architecture: 3 -> [8, 6, 4] -> 2");
532 println!("Total parameters: {}", network.parameter_count());
533
534 // Create synthetic regression data
535 // Target function: [y1, y2] = [x1 + 2*x2 - x3, x1*x2 + x3]
536 let num_samples = 32;
537 let mut x_vec = Vec::new();
538 let mut y_vec = Vec::new();
539
540 for i in 0..num_samples {
541 let x1 = (i as f32 / num_samples as f32) * 2.0 - 1.0; // [-1, 1]
542 let x2 = ((i * 2) as f32 / num_samples as f32) * 2.0 - 1.0;
543 let x3 = ((i * 3) as f32 / num_samples as f32) * 2.0 - 1.0;
544
545 let y1 = x1 + 2.0 * x2 - x3;
546 let y2 = x1 * x2 + x3;
547
548 x_vec.extend_from_slice(&[x1, x2, x3]);
549 y_vec.extend_from_slice(&[y1, y2]);
550 }
551
552 let x_data = Tensor::from_slice(&x_vec, vec![num_samples, 3]).unwrap();
553 let y_true = Tensor::from_slice(&y_vec, vec![num_samples, 2]).unwrap();
554
555 println!("Training data:");
556 println!(" {} samples", num_samples);
557 println!(" Input shape: {:?}", x_data.shape().dims);
558 println!(" Target shape: {:?}", y_true.shape().dims);
559
560 // Create optimizer with learning rate scheduling
561 let mut optimizer = Adam::with_learning_rate(0.01);
562 let params = network.parameters();
563 for param in ¶ms {
564 optimizer.add_parameter(param);
565 }
566
567 // Comprehensive training loop (150 epochs)
568 let num_epochs = 150;
569 let mut losses = Vec::new();
570 let mut best_loss = f32::INFINITY;
571 let mut patience_counter = 0;
572 let patience = 20;
573
574 println!("Starting comprehensive training...");
575
576 for epoch in 0..num_epochs {
577 // Forward pass
578 let y_pred = network.forward(&x_data);
579
580 // Compute loss: MSE
581 let diff = y_pred.sub_tensor(&y_true);
582 let mut loss = diff.pow_scalar(2.0).mean();
583
584 // Backward pass
585 loss.backward(None);
586
587 // Optimizer step and zero grad
588 let mut params = network.parameters();
589 optimizer.step(&mut params);
590 optimizer.zero_grad(&mut params);
591
592 let current_loss = loss.value();
593 losses.push(current_loss);
594
595 // Learning rate scheduling
596 if epoch > 0 && epoch % 30 == 0 {
597 let new_lr = optimizer.learning_rate() * 0.8;
598 optimizer.set_learning_rate(new_lr);
599 println!(" Reduced learning rate to {:.4}", new_lr);
600 }
601
602 // Early stopping logic
603 if current_loss < best_loss {
604 best_loss = current_loss;
605 patience_counter = 0;
606 } else {
607 patience_counter += 1;
608 }
609
610 // Print progress
611 if epoch % 25 == 0 || epoch == num_epochs - 1 {
612 println!(
613 "Epoch {:3}: Loss = {:.6}, LR = {:.4}, Best = {:.6}",
614 epoch,
615 current_loss,
616 optimizer.learning_rate(),
617 best_loss
618 );
619 }
620
621 // Early stopping
622 if patience_counter >= patience && epoch > 50 {
623 println!("Early stopping at epoch {} (patience exceeded)", epoch);
624 break;
625 }
626 }
627
628 // Final evaluation
629 let final_predictions = network.forward_no_grad(&x_data);
630
631 // Compute final metrics
632 let final_loss = losses[losses.len() - 1];
633 let initial_loss = losses[0];
634 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
635
636 println!("\nTraining completed!");
637 println!(" Initial loss: {:.6}", initial_loss);
638 println!(" Final loss: {:.6}", final_loss);
639 println!(" Best loss: {:.6}", best_loss);
640 println!(" Loss reduction: {:.1}%", loss_reduction);
641 println!(" Final learning rate: {:.4}", optimizer.learning_rate());
642
643 // Sample predictions analysis
644 println!("\nSample predictions (first 5):");
645 for i in 0..5.min(num_samples) {
646 let pred1 = final_predictions.data()[i * 2];
647 let pred2 = final_predictions.data()[i * 2 + 1];
648 let true1 = y_true.data()[i * 2];
649 let true2 = y_true.data()[i * 2 + 1];
650
651 println!(
652 " Sample {}: pred=[{:.3}, {:.3}], true=[{:.3}, {:.3}], error=[{:.3}, {:.3}]",
653 i + 1,
654 pred1,
655 pred2,
656 true1,
657 true2,
658 (pred1 - true1).abs(),
659 (pred2 - true2).abs()
660 );
661 }
662
663 Ok(())
664}
665
666/// Demonstrate network serialization
667fn demonstrate_network_serialization() -> Result<(), Box<dyn std::error::Error>> {
668 println!("\n--- Network Serialization ---");
669
670 // Create and train a network
671 let config = FeedForwardConfig {
672 input_size: 2,
673 hidden_sizes: vec![4, 2],
674 output_size: 1,
675 use_bias: true,
676 };
677 let mut original_network = FeedForwardNetwork::new(config.clone(), Some(48));
678
679 // Quick training
680 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
681 let y_true = Tensor::from_slice(&[5.0, 11.0], vec![2, 1]).unwrap();
682
683 let mut optimizer = Adam::with_learning_rate(0.01);
684 let params = original_network.parameters();
685 for param in ¶ms {
686 optimizer.add_parameter(param);
687 }
688
689 for _ in 0..20 {
690 let y_pred = original_network.forward(&x_data);
691 let mut loss = (y_pred.sub_tensor(&y_true)).pow_scalar(2.0).mean();
692 loss.backward(None);
693
694 let mut params = original_network.parameters();
695 optimizer.step(&mut params);
696 optimizer.zero_grad(&mut params);
697 }
698
699 // Test original network
700 let test_input = Tensor::from_slice(&[1.0, 1.0], vec![1, 2]).unwrap();
701 let original_output = original_network.forward_no_grad(&test_input);
702
703 println!("Original network output: {:?}", original_output.data());
704
705 // Save network
706 original_network.save_json("temp_feedforward_network")?;
707
708 // Load network
709 let loaded_network = FeedForwardNetwork::load_json("temp_feedforward_network", config)?;
710 let loaded_output = loaded_network.forward_no_grad(&test_input);
711
712 println!("Loaded network output: {:?}", loaded_output.data());
713
714 // Verify consistency
715 let match_check = original_output
716 .data()
717 .iter()
718 .zip(loaded_output.data().iter())
719 .all(|(a, b)| (a - b).abs() < 1e-6);
720
721 println!(
722 "Serialization verification: {}",
723 if match_check { "PASSED" } else { "FAILED" }
724 );
725
726 Ok(())
727}74fn demonstrate_data_pipeline() -> Result<(), Box<dyn std::error::Error>> {
75 println!("\n--- Data Processing Pipeline ---");
76
77 // Simulate raw sensor data with noise
78 let raw_data: Vec<f32> = (0..20)
79 .map(|i| {
80 let base = i as f32 * 0.5;
81 let noise = (i % 3) as f32 * 0.1;
82 base + noise
83 })
84 .collect();
85
86 let tensor = Tensor::from_slice(&raw_data, vec![20])?;
87 println!("Raw sensor data: {:?}", tensor.data());
88
89 // Multi-stage processing pipeline
90 println!("\nProcessing pipeline:");
91 println!("1. Normalize data (z-score)");
92 println!("2. Apply smoothing filter");
93 println!("3. Detect outliers");
94 println!("4. Apply feature scaling");
95
96 // Stage 1: Normalization
97 let mean = tensor.mean().value();
98 let std = tensor.std().value();
99 let normalized: Tensor = tensor
100 .iter()
101 .map(|elem| elem.sub_scalar(mean).div_scalar(std))
102 .collect();
103 println!(
104 " Normalized (mean={:.3}, std={:.3}): {:?}",
105 mean,
106 std,
107 normalized.data()
108 );
109
110 // Stage 2: Smoothing (simple moving average)
111 let smoothed: Tensor = normalized
112 .iter()
113 .enumerate()
114 .map(|(i, elem)| {
115 if i == 0 || i == normalized.size() - 1 {
116 elem.clone()
117 } else {
118 // Simple 3-point average
119 let prev = normalized.element_view(i - 1);
120 let next = normalized.element_view(i + 1);
121 elem.add_tensor(&prev).add_tensor(&next).div_scalar(3.0)
122 }
123 })
124 .collect();
125 println!(" Smoothed: {:?}", smoothed.data());
126
127 // Stage 3: Outlier detection and removal
128 let outlier_threshold = 2.0;
129 let cleaned: Tensor = smoothed
130 .iter()
131 .filter(|elem| elem.value().abs() < outlier_threshold)
132 .collect();
133 println!(
134 " Outliers removed (threshold={}): {:?}",
135 outlier_threshold,
136 cleaned.data()
137 );
138
139 // Stage 4: Feature scaling to [0, 1] range
140 let min_val = cleaned
141 .iter()
142 .map(|e| e.value())
143 .fold(f32::INFINITY, f32::min);
144 let max_val = cleaned
145 .iter()
146 .map(|e| e.value())
147 .fold(f32::NEG_INFINITY, f32::max);
148 let scaled: Tensor = cleaned
149 .iter()
150 .map(|elem| elem.sub_scalar(min_val).div_scalar(max_val - min_val))
151 .collect();
152 println!(" Scaled to [0,1]: {:?}", scaled.data());
153
154 Ok(())
155}
156
157/// Demonstrate conditional processing patterns
158///
159/// Shows how to implement dynamic filtering and transformation
160/// based on data characteristics and conditions.
161fn demonstrate_conditional_processing() -> Result<(), Box<dyn std::error::Error>> {
162 println!("\n--- Conditional Processing ---");
163
164 // Create data with mixed characteristics
165 let data = vec![1.0, -2.0, 3.0, -4.0, 5.0, -6.0, 7.0, -8.0, 9.0, -10.0];
166 let tensor = Tensor::from_slice(&data, vec![10])?;
167 println!("Input data: {:?}", tensor.data());
168
169 // Conditional transformation based on sign
170 println!("\nConditional transformation (positive/negative handling):");
171 let processed: Tensor = tensor
172 .iter()
173 .map(|elem| {
174 let val = elem.value();
175 if val > 0.0 {
176 elem.pow_scalar(2.0) // Square positive values
177 } else {
178 elem.mul_scalar(-1.0).sqrt() // Square root of absolute negative values
179 }
180 })
181 .collect();
182 println!(" Processed: {:?}", processed.data());
183
184 // Adaptive filtering based on local statistics
185 println!("\nAdaptive filtering (remove values > 2 std from local mean):");
186 let window_size = 3;
187 let adaptive_filtered: Tensor = tensor
188 .iter()
189 .enumerate()
190 .filter(|(i, elem)| {
191 let start = i.saturating_sub(window_size / 2);
192 let end = (i + window_size / 2 + 1).min(tensor.size());
193
194 // Calculate local mean and std
195 let local_values: Vec<f32> = (start..end)
196 .map(|j| tensor.element_view(j).value())
197 .collect();
198
199 let local_mean = local_values.iter().sum::<f32>() / local_values.len() as f32;
200 let local_variance = local_values
201 .iter()
202 .map(|v| (v - local_mean).powi(2))
203 .sum::<f32>()
204 / local_values.len() as f32;
205 let local_std = local_variance.sqrt();
206
207 let threshold = local_mean + 2.0 * local_std;
208 elem.value() <= threshold
209 })
210 .map(|(_, elem)| elem)
211 .collect();
212 println!(" Adaptive filtered: {:?}", adaptive_filtered.data());
213
214 // Multi-condition processing
215 println!("\nMulti-condition processing:");
216 let multi_processed: Tensor = tensor
217 .iter()
218 .map(|elem| {
219 let val = elem.value();
220 match () {
221 _ if val > 5.0 => elem.mul_scalar(2.0), // Double large values
222 _ if val < -5.0 => elem.div_scalar(2.0), // Halve small values
223 _ if val.abs() < 2.0 => elem.add_scalar(1.0), // Add 1 to small values
224 _ => elem.clone(), // Keep others unchanged
225 }
226 })
227 .collect();
228 println!(" Multi-condition: {:?}", multi_processed.data());
229
230 Ok(())
231}
232
233/// Demonstrate batch processing operations
234///
235/// Shows efficient processing of large datasets using iterator
236/// patterns and batch operations for performance optimization.
237fn demonstrate_batch_operations() -> Result<(), Box<dyn std::error::Error>> {
238 println!("\n--- Batch Operations ---");
239
240 // Create a larger dataset for batch processing
241 let size = 100;
242 let data: Vec<f32> = (0..size)
243 .map(|i| {
244 let x = i as f32 / size as f32;
245 x * x + 0.1 * (i % 7) as f32 // Quadratic with some noise
246 })
247 .collect();
248
249 let tensor = Tensor::from_slice(&data, vec![size])?;
250 println!("Dataset size: {}", tensor.size());
251
252 // Batch processing with windowing
253 println!("\nBatch processing with sliding windows:");
254 let batch_size = 10;
255 let batches: Vec<Tensor> = tensor
256 .iter()
257 .collect::<Vec<_>>()
258 .chunks(batch_size)
259 .map(|chunk| {
260 // Process each batch independently
261 chunk
262 .iter()
263 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
264 .collect()
265 })
266 .collect();
267
268 println!(
269 " Processed {} batches of size {}",
270 batches.len(),
271 batch_size
272 );
273 for (i, batch) in batches.iter().enumerate() {
274 println!(
275 " Batch {}: mean={:.3}, std={:.3}",
276 i,
277 batch.mean().value(),
278 batch.std().value()
279 );
280 }
281
282 // Parallel-like processing with stride
283 println!("\nStrided processing (every nth element):");
284 let stride = 5;
285 let strided: Tensor = tensor
286 .iter()
287 .enumerate()
288 .filter(|(i, _)| i % stride == 0)
289 .map(|(_, elem)| elem)
290 .collect();
291 println!(" Strided (every {}th): {:?}", stride, strided.data());
292
293 // Hierarchical processing
294 println!("\nHierarchical processing (coarse to fine):");
295 let coarse: Tensor = tensor
296 .iter()
297 .enumerate()
298 .filter(|(i, _)| i % 4 == 0) // Take every 4th element
299 .map(|(_, elem)| elem)
300 .collect();
301
302 let fine: Tensor = tensor
303 .iter()
304 .enumerate()
305 .filter(|(i, _)| i % 4 != 0) // Take the rest
306 .map(|(_, elem)| elem)
307 .collect();
308
309 println!(" Coarse (every 4th): {:?}", coarse.data());
310 println!(" Fine (rest): {:?}", fine.data());
311
312 // Combine coarse and fine with different processing
313 let combined: Tensor = coarse
314 .iter()
315 .map(|elem| elem.mul_scalar(2.0)) // Scale coarse
316 .chain(fine.iter().map(|elem| elem.div_scalar(2.0))) // Scale fine
317 .collect();
318 println!(" Combined: {:?}", combined.data());
319
320 Ok(())
321}
322
323/// Demonstrate real-world processing scenarios
324///
325/// Shows practical applications of iterator patterns for
326/// common data processing tasks in machine learning and analytics.
327fn demonstrate_real_world_scenarios() -> Result<(), Box<dyn std::error::Error>> {
328 println!("\n--- Real-world Scenarios ---");
329
330 // Scenario 1: Time series analysis
331 println!("\nScenario 1: Time Series Analysis");
332 let time_series: Vec<f32> = (0..24)
333 .map(|hour| {
334 let base = 20.0 + 10.0 * (hour as f32 * std::f32::consts::PI / 12.0).sin();
335 base + (hour % 3) as f32 * 2.0 // Add some noise
336 })
337 .collect();
338
339 let series = Tensor::from_slice(&time_series, vec![24])?;
340 println!(" Time series (24 hours): {:?}", series.data());
341
342 // Calculate moving average
343 let window_size = 3;
344 let moving_avg: Tensor = series
345 .iter()
346 .enumerate()
347 .map(|(i, _)| {
348 let start = i.saturating_sub(window_size / 2);
349 let end = (i + window_size / 2 + 1).min(series.size());
350 let window = series.iter_range(start, end);
351 window.fold(0.0, |acc, elem| acc + elem.value()) / (end - start) as f32
352 })
353 .map(|val| Tensor::from_slice(&[val], vec![1]).unwrap())
354 .collect();
355 println!(
356 " Moving average (window={}): {:?}",
357 window_size,
358 moving_avg.data()
359 );
360
361 // Scenario 2: Feature engineering
362 println!("\nScenario 2: Feature Engineering");
363 let features = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
364 println!(" Original features: {:?}", features.data());
365
366 // Create polynomial features
367 let poly_features: Tensor = features
368 .iter()
369 .flat_map(|elem| {
370 vec![
371 elem.clone(), // x^1
372 elem.pow_scalar(2.0), // x^2
373 elem.pow_scalar(3.0), // x^3
374 ]
375 })
376 .collect();
377 println!(
378 " Polynomial features (x, x^2, x^3): {:?}",
379 poly_features.data()
380 );
381
382 // Scenario 3: Data augmentation
383 println!("\nScenario 3: Data Augmentation");
384 let original = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?;
385 println!(" Original data: {:?}", original.data());
386
387 // Augment with noise and scaling
388 let augmented: Tensor = original
389 .iter()
390 .flat_map(|elem| {
391 vec![
392 elem.clone(), // Original
393 elem.add_scalar(0.1), // Add noise
394 elem.sub_scalar(0.1), // Subtract noise
395 elem.mul_scalar(1.1), // Scale up
396 elem.mul_scalar(0.9), // Scale down
397 ]
398 })
399 .collect();
400 println!(" Augmented data: {:?}", augmented.data());
401
402 // Scenario 4: Statistical analysis
403 println!("\nScenario 4: Statistical Analysis");
404 let sample_data = Tensor::from_slice(&[1.1, 2.3, 1.8, 2.1, 1.9, 2.0, 1.7, 2.2], vec![8])?;
405 println!(" Sample data: {:?}", sample_data.data());
406
407 // Calculate various statistics
408 let mean = sample_data.mean().value();
409 let std = sample_data.std().value();
410 let min = sample_data
411 .iter()
412 .map(|e| e.value())
413 .fold(f32::INFINITY, f32::min);
414 let max = sample_data
415 .iter()
416 .map(|e| e.value())
417 .fold(f32::NEG_INFINITY, f32::max);
418
419 // Z-score normalization
420 let z_scores: Tensor = sample_data
421 .iter()
422 .map(|elem| elem.sub_scalar(mean).div_scalar(std))
423 .collect();
424
425 println!(
426 " Statistics: mean={:.3}, std={:.3}, min={:.3}, max={:.3}",
427 mean, std, min, max
428 );
429 println!(" Z-scores: {:?}", z_scores.data());
430
431 Ok(())
432}Sourcepub fn mean_dims(&self, dims: &[usize], keepdim: bool) -> Tensor
pub fn mean_dims(&self, dims: &[usize], keepdim: bool) -> Tensor
Computes the arithmetic mean over specified dimensions
This method calculates the mean value along the specified dimensions by first
computing the sum over those dimensions and then dividing by the product of
the reduced dimension sizes. The keepdim parameter determines whether
reduced dimensions are kept with size 1 or removed entirely.
§Arguments
dims- Dimensions to reduce over (must be valid for the tensor’s rank)keepdim- If true, reduced dimensions are kept with size 1; if false, they are removed
§Returns
A tensor with the specified dimensions reduced by computing the mean.
The output shape depends on keepdim:
- If
keepdimistrue, reduced dimensions have size 1 - If
keepdimisfalse, reduced dimensions are removed
§Performance Characteristics
- Efficient Implementation: Uses
sum_dimsfollowed by scalar multiplication - Memory Optimized: Leverages existing sum reduction for optimal performance
- Shape Computation: Fast output shape calculation with dimension preservation
- Numerical Stability: Maintains precision through direct computation
§Examples
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
// Mean over columns (dim 1), keeping dimensions
let mean_cols = tensor.mean_dims(&[1], true);
assert_eq!(mean_cols.shape().dims, vec![2, 1]);
assert_eq!(mean_cols.get(&[0, 0]), 2.0); // (1+2+3)/3 = 2.0
assert_eq!(mean_cols.get(&[1, 0]), 5.0); // (4+5+6)/3 = 5.0use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
// Mean over rows (dim 0), removing dimensions
let mean_rows = tensor.mean_dims(&[0], false);
assert_eq!(mean_rows.shape().dims, vec![3]);
assert_eq!(mean_rows.get(&[0]), 2.5); // (1+4)/2 = 2.5
assert_eq!(mean_rows.get(&[1]), 3.5); // (2+5)/2 = 3.5
assert_eq!(mean_rows.get(&[2]), 4.5); // (3+6)/2 = 4.5use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
// Mean over multiple dimensions
let mean_all = tensor.mean_dims(&[0, 1], false);
assert_eq!(mean_all.shape().dims, vec![1]);
assert_eq!(mean_all.get(&[0]), 2.5); // (1+2+3+4)/4 = 2.5§Panics
Panics if:
dimsis empty- Any dimension in
dimsis out of bounds for the tensor’s rank
§GradTrack Support
When requires_grad is true, this operation is tracked for automatic
differentiation. The gradient computation preserves the original input
shape and handles broadcasting correctly through the ReduceMeanDims gradient function.
Source§impl Tensor
impl Tensor
Sourcepub fn min(&self) -> Tensor
pub fn min(&self) -> Tensor
Computes the minimum value over all elements in the tensor
Returns a scalar tensor containing the minimum value. For empty tensors, returns positive infinity. This operation supports gradient tracking through the GradTrack system.
§Returns
A tensor with shape [1] containing the minimum value
§Examples
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 5.0, 3.0, 2.0], vec![2, 2]).unwrap();
let min_val = tensor.min();
assert_eq!(min_val.get(&[0]), 1.0);use train_station::Tensor;
// Empty tensor case
let empty_tensor = Tensor::new(vec![0]);
let min_val = empty_tensor.min();
assert_eq!(min_val.get(&[0]), f32::INFINITY);§GradTrack Support
When requires_grad is true, this operation is tracked for automatic
differentiation. The gradient computation uses the saved input and output
for efficient backward pass.
Sourcepub fn min_dims(&self, dims: &[usize], keepdim: bool) -> Tensor
pub fn min_dims(&self, dims: &[usize], keepdim: bool) -> Tensor
Computes the minimum value over specified dimensions
Reduces the tensor along the specified dimensions by computing the minimum
value in each reduction group. The keepdim parameter determines whether
reduced dimensions are kept with size 1 or removed entirely.
§Arguments
dims- Dimensions to reduce over (must be valid for the tensor’s rank)keepdim- If true, reduced dimensions are kept with size 1; if false, they are removed
§Returns
A tensor with the specified dimensions reduced
§Examples
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
// Min over columns (dim 1), keeping dimensions
let min_cols = tensor.min_dims(&[1], true);
assert_eq!(min_cols.shape().dims, vec![2, 1]);
assert_eq!(min_cols.get(&[0, 0]), 1.0);
assert_eq!(min_cols.get(&[1, 0]), 4.0);use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
// Min over rows (dim 0), removing dimensions
let min_rows = tensor.min_dims(&[0], false);
assert_eq!(min_rows.shape().dims, vec![3]);
assert_eq!(min_rows.get(&[0]), 1.0);
assert_eq!(min_rows.get(&[1]), 2.0);
assert_eq!(min_rows.get(&[2]), 3.0);use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
// Min over multiple dimensions
let min_all = tensor.min_dims(&[0, 1], false);
assert_eq!(min_all.shape().dims, vec![1]);
assert_eq!(min_all.get(&[0]), 1.0);§Panics
Panics if:
dimsis empty- Any dimension in
dimsis out of bounds for the tensor’s rank
§GradTrack Support
When requires_grad is true, this operation is tracked for automatic
differentiation. The gradient computation preserves the original input
shape and handles broadcasting correctly.
Source§impl Tensor
impl Tensor
Sourcepub fn norm(&self) -> Tensor
pub fn norm(&self) -> Tensor
Computes the L2 norm (Euclidean norm) over all elements
The L2 norm is calculated as sqrt(sum(x²)) where x represents each element in the tensor. This operation reduces the tensor to a scalar value [1].
§Returns
A scalar tensor containing the L2 norm value
§Examples
use train_station::Tensor;
// Basic L2 norm calculation
let tensor = Tensor::from_slice(&[3.0, 4.0], vec![2]).unwrap();
let norm = tensor.norm();
assert!((norm.get(&[0]) - 5.0).abs() < 1e-6); // sqrt(3² + 4²) = 5use train_station::Tensor;
// L2 norm of a larger tensor
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let tensor = Tensor::from_slice(&data, vec![2, 2, 2]).unwrap();
let norm = tensor.norm();
// sqrt(1² + 2² + 3² + 4² + 5² + 6² + 7² + 8²) = sqrt(204) ≈ 14.283
let expected = 204.0_f32.sqrt();
assert!((norm.get(&[0]) - expected).abs() < 1e-5);§Performance
Uses optimized contiguous tensor path with 4x loop unrolling for better performance. Non-contiguous tensors use stride-aware iteration.
Examples found in repository?
179fn demonstrate_utility_functions() {
180 println!("\n--- Utility Functions ---");
181
182 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
183
184 // Basic properties
185 println!("Shape: {:?}", tensor.shape().dims);
186 println!("Size: {}", tensor.size());
187 println!("Is contiguous: {}", tensor.is_contiguous());
188 println!("Device: {:?}", tensor.device());
189
190 // Mathematical operations
191 let sum = tensor.sum();
192 println!("Sum: {}", sum.value());
193
194 let mean = tensor.mean();
195 println!("Mean: {}", mean.value());
196
197 let norm = tensor.norm();
198 println!("Norm: {}", norm.value());
199
200 // Device placement
201 let cpu_tensor = Tensor::zeros_on_device(vec![3, 3], train_station::Device::cpu());
202 println!(
203 "CPU tensor: shape {:?}, device: {:?}",
204 cpu_tensor.shape().dims,
205 cpu_tensor.device()
206 );
207}More examples
317fn train_with_config(config: TrainingConfig) -> Result<TrainingStats, Box<dyn std::error::Error>> {
318 // Create training data
319 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
320 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0, 11.0], vec![5, 1]).unwrap();
321
322 // Create model parameters
323 let mut weight = Tensor::randn(vec![1, 1], Some(123)).with_requires_grad();
324 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
325
326 // Create optimizer with custom configuration
327 let adam_config = AdamConfig {
328 learning_rate: config.learning_rate,
329 beta1: config.beta1,
330 beta2: config.beta2,
331 eps: 1e-8,
332 weight_decay: config.weight_decay,
333 amsgrad: false,
334 };
335
336 let mut optimizer = Adam::with_config(adam_config);
337 optimizer.add_parameter(&weight);
338 optimizer.add_parameter(&bias);
339
340 // Training loop
341 let mut losses = Vec::new();
342 let mut convergence_epoch = config.epochs;
343
344 for epoch in 0..config.epochs {
345 // Forward pass
346 let y_pred = x_data.matmul(&weight) + &bias;
347 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
348
349 // Backward pass
350 loss.backward(None);
351
352 // Optimizer step
353 optimizer.step(&mut [&mut weight, &mut bias]);
354 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
355
356 let loss_value = loss.value();
357 losses.push(loss_value);
358
359 // Check for convergence (loss < 0.01)
360 if loss_value < 0.01 && convergence_epoch == config.epochs {
361 convergence_epoch = epoch;
362 }
363 }
364
365 Ok(TrainingStats {
366 config,
367 final_loss: losses[losses.len() - 1],
368 loss_history: losses,
369 convergence_epoch,
370 weight_norm: weight.norm().value(),
371 })
372}176fn demonstrate_advanced_training() -> Result<(), Box<dyn std::error::Error>> {
177 println!("\n--- Advanced Training Patterns ---");
178
179 // Create a more complex model
180 let mut weight = Tensor::randn(vec![1, 2], Some(44)).with_requires_grad();
181 let mut bias = Tensor::zeros(vec![2]).with_requires_grad();
182
183 // Create optimizer with different learning rate
184 let mut optimizer = Adam::with_learning_rate(0.005);
185 optimizer.add_parameter(&weight);
186 optimizer.add_parameter(&bias);
187
188 // Create training data: y = 2*x + [1, 3]
189 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5, 1]).unwrap();
190 let y_true = Tensor::from_slice(
191 &[3.0, 5.0, 7.0, 9.0, 11.0, 6.0, 8.0, 10.0, 12.0, 14.0],
192 vec![5, 2],
193 )
194 .unwrap();
195
196 println!("Advanced training with monitoring:");
197 println!(" Initial learning rate: {}", optimizer.learning_rate());
198
199 // Training loop with monitoring
200 let num_epochs = 50;
201 let mut losses = Vec::new();
202 let mut weight_norms = Vec::new();
203 let mut gradient_norms = Vec::new();
204
205 for epoch in 0..num_epochs {
206 // Forward pass
207 let y_pred = x_data.matmul(&weight) + &bias;
208 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
209
210 // Backward pass
211 loss.backward(None);
212
213 // Compute gradient norm before optimizer step
214 let gradient_norm = weight.grad_by_value().unwrap().norm();
215
216 // Optimizer step
217 optimizer.step(&mut [&mut weight, &mut bias]);
218 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
219
220 // Learning rate scheduling: reduce every 10 epochs
221 if epoch > 0 && epoch % 10 == 0 {
222 let current_lr = optimizer.learning_rate();
223 let new_lr = current_lr * 0.5;
224 optimizer.set_learning_rate(new_lr);
225 println!(
226 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
227 epoch, current_lr, new_lr
228 );
229 }
230
231 // Record metrics
232 losses.push(loss.value());
233 weight_norms.push(weight.norm().value());
234 gradient_norms.push(gradient_norm.value());
235
236 // Print detailed progress
237 if epoch % 10 == 0 || epoch == num_epochs - 1 {
238 println!(
239 "Epoch {:2}: Loss = {:.6}, Weight Norm = {:.6}, Gradient Norm = {:.6}",
240 epoch,
241 loss.value(),
242 weight.norm().value(),
243 gradient_norm.value()
244 );
245 }
246 }
247
248 println!("Final learning rate: {}", optimizer.learning_rate());
249
250 // Analyze training progression
251 let initial_loss = losses[0];
252 let final_loss = losses[losses.len() - 1];
253 let loss_reduction = (initial_loss - final_loss) / initial_loss * 100.0;
254
255 println!("\nTraining Analysis:");
256 println!(" Initial loss: {:.6}", initial_loss);
257 println!(" Final loss: {:.6}", final_loss);
258 println!(" Loss reduction: {:.1}%", loss_reduction);
259 println!(" Final weight norm: {:.6}", weight.norm().value());
260 println!(" Final bias: {:?}", bias.data());
261
262 Ok(())
263}
264
265/// Demonstrate learning rate scheduling
266fn demonstrate_learning_rate_scheduling() -> Result<(), Box<dyn std::error::Error>> {
267 println!("\n--- Learning Rate Scheduling ---");
268
269 // Create simple model
270 let mut weight = Tensor::randn(vec![1, 1], Some(45)).with_requires_grad();
271 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
272
273 // Create optimizer with high initial learning rate
274 let mut optimizer = Adam::with_learning_rate(0.1);
275 optimizer.add_parameter(&weight);
276 optimizer.add_parameter(&bias);
277
278 // Simple data
279 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3, 1]).unwrap();
280 let y_true = Tensor::from_slice(&[2.0, 4.0, 6.0], vec![3, 1]).unwrap();
281
282 println!("Initial learning rate: {}", optimizer.learning_rate());
283
284 // Training loop with learning rate scheduling
285 let num_epochs = 50;
286 let mut losses = Vec::new();
287
288 for epoch in 0..num_epochs {
289 // Forward pass
290 let y_pred = x_data.matmul(&weight) + &bias;
291 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
292
293 // Backward pass
294 loss.backward(None);
295
296 // Optimizer step
297 optimizer.step(&mut [&mut weight, &mut bias]);
298 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
299
300 // Learning rate scheduling: reduce every 10 epochs
301 if epoch > 0 && epoch % 10 == 0 {
302 let current_lr = optimizer.learning_rate();
303 let new_lr = current_lr * 0.5;
304 optimizer.set_learning_rate(new_lr);
305 println!(
306 "Epoch {:2}: Reduced learning rate from {:.3} to {:.3}",
307 epoch, current_lr, new_lr
308 );
309 }
310
311 losses.push(loss.value());
312
313 // Print progress
314 if epoch % 10 == 0 || epoch == num_epochs - 1 {
315 println!(
316 "Epoch {:2}: Loss = {:.6}, LR = {:.3}",
317 epoch,
318 loss.value(),
319 optimizer.learning_rate()
320 );
321 }
322 }
323
324 println!("Final learning rate: {}", optimizer.learning_rate());
325
326 Ok(())
327}
328
329/// Demonstrate training monitoring and analysis
330fn demonstrate_training_monitoring() -> Result<(), Box<dyn std::error::Error>> {
331 println!("\n--- Training Monitoring ---");
332
333 // Create model
334 let mut weight = Tensor::randn(vec![1, 1], Some(46)).with_requires_grad();
335 let mut bias = Tensor::zeros(vec![1]).with_requires_grad();
336
337 // Create optimizer
338 let mut optimizer = Adam::with_learning_rate(0.01);
339 optimizer.add_parameter(&weight);
340 optimizer.add_parameter(&bias);
341
342 // Training data
343 let x_data = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4, 1]).unwrap();
344 let y_true = Tensor::from_slice(&[3.0, 5.0, 7.0, 9.0], vec![4, 1]).unwrap();
345
346 // Training loop with comprehensive monitoring
347 let num_epochs = 30;
348 let mut losses = Vec::new();
349 let mut weight_history = Vec::new();
350 let mut bias_history = Vec::new();
351
352 for epoch in 0..num_epochs {
353 // Forward pass
354 let y_pred = x_data.matmul(&weight) + &bias;
355 let mut loss = (&y_pred - &y_true).pow_scalar(2.0).mean();
356
357 // Backward pass
358 loss.backward(None);
359
360 // Optimizer step
361 optimizer.step(&mut [&mut weight, &mut bias]);
362 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
363
364 // Record history
365 losses.push(loss.value());
366 weight_history.push(weight.value());
367 bias_history.push(bias.value());
368
369 // Print detailed monitoring
370 if epoch % 5 == 0 || epoch == num_epochs - 1 {
371 println!(
372 "Epoch {:2}: Loss = {:.6}, Weight = {:.6}, Bias = {:.6}",
373 epoch,
374 loss.value(),
375 weight.value(),
376 bias.value()
377 );
378 }
379 }
380
381 // Analyze training progression
382 println!("\nTraining Analysis:");
383 println!(" Initial loss: {:.6}", losses[0]);
384 println!(" Final loss: {:.6}", losses[losses.len() - 1]);
385 println!(
386 " Loss reduction: {:.1}%",
387 (losses[0] - losses[losses.len() - 1]) / losses[0] * 100.0
388 );
389
390 // Compute statistics
391 let loss_mean = compute_mean(&losses);
392 let loss_std = compute_std(&losses);
393 let weight_change = (weight_history[weight_history.len() - 1] - weight_history[0]).abs();
394 let bias_change = (bias_history[bias_history.len() - 1] - bias_history[0]).abs();
395
396 println!(" Average loss: {:.6} ± {:.6}", loss_mean, loss_std);
397 println!(" Weight change: {:.6}", weight_change);
398 println!(" Bias change: {:.6}", bias_change);
399 println!(" Final weight norm: {:.6}", weight.norm().value());
400 println!(" Final bias: {:.6}", bias.value());
401
402 Ok(())
403}Sourcepub fn norm_dims(&self, dims: &[usize], keepdim: bool) -> Tensor
pub fn norm_dims(&self, dims: &[usize], keepdim: bool) -> Tensor
Computes the L2 norm over specified dimensions
Reduces the tensor along the specified dimensions by computing the L2 norm of each slice. The result maintains the original tensor structure with reduced dimensions optionally preserved as size-1 dimensions.
§Arguments
dims- Vector of dimension indices to reduce over (must be valid for tensor rank)keepdim- Whether to keep reduced dimensions as size-1 dimensions
§Returns
A tensor with L2 norm computed over the specified dimensions
§Examples
use train_station::Tensor;
// Norm along rows (dimension 1) with keepdim=true
let matrix = Tensor::from_slice(&[3.0, 4.0, 0.0, 5.0], vec![2, 2]).unwrap();
let row_norms = matrix.norm_dims(&[1], true);
assert_eq!(row_norms.shape().dims, vec![2, 1]);
assert!((row_norms.get(&[0, 0]) - 5.0).abs() < 1e-6); // sqrt(3² + 4²)
assert!((row_norms.get(&[1, 0]) - 5.0).abs() < 1e-6); // sqrt(0² + 5²)use train_station::Tensor;
// Norm along columns (dimension 0) with keepdim=false
let matrix = Tensor::from_slice(&[3.0, 4.0, 0.0, 5.0], vec![2, 2]).unwrap();
let col_norms = matrix.norm_dims(&[0], false);
assert_eq!(col_norms.shape().dims, vec![2]);
assert!((col_norms.get(&[0]) - 3.0).abs() < 1e-6); // sqrt(3² + 0²)
assert!((col_norms.get(&[1]) - 6.403).abs() < 1e-3); // sqrt(4² + 5²)use train_station::Tensor;
// Norm over multiple dimensions
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let norm_all = tensor.norm_dims(&[0, 1], false);
assert_eq!(norm_all.shape().dims, vec![1]);
// sqrt(1² + 2² + 3² + 4²) = sqrt(30) ≈ 5.477
assert!((norm_all.get(&[0]) - 30.0_f32.sqrt()).abs() < 1e-5);§Panics
- If
dimsis empty - If any dimension index is out of bounds for the tensor rank
§Performance
Uses efficient coordinate-based iteration that works correctly with both contiguous and non-contiguous tensor layouts.
Source§impl Tensor
impl Tensor
Sourcepub fn std(&self) -> Tensor
pub fn std(&self) -> Tensor
Computes the standard deviation over all elements
The standard deviation is calculated as sqrt(variance) where variance is the mean of squared differences from the mean. This operation reduces the tensor to a scalar value [1].
The implementation uses population standard deviation (divides by n rather than n-1) to match PyTorch’s default behavior.
§Returns
A scalar tensor containing the standard deviation value
§Examples
use train_station::Tensor;
// Basic standard deviation calculation
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4]).unwrap();
let std_dev = tensor.std();
assert!((std_dev.get(&[0]) - 1.118_034).abs() < 1e-5);use train_station::Tensor;
// Standard deviation of a larger dataset
let data = vec![1.0, 3.0, 5.0, 7.0, 2.0, 4.0, 6.0, 8.0];
let tensor = Tensor::from_slice(&data, vec![2, 2, 2]).unwrap();
let std_dev = tensor.std();
// mean=4.5, var=5.25, std=sqrt(5.25)≈2.291
let expected = 5.25_f32.sqrt();
assert!((std_dev.get(&[0]) - expected).abs() < 1e-5);use train_station::Tensor;
// Standard deviation of constant values (should be 0)
let tensor = Tensor::from_slice(&[5.0, 5.0, 5.0, 5.0], vec![4]).unwrap();
let std_dev = tensor.std();
assert!((std_dev.get(&[0]) - 0.0).abs() < 1e-6);§Performance
Uses optimized contiguous tensor path with 4x loop unrolling for better performance. Non-contiguous tensors use stride-aware iteration. The algorithm performs two passes: first to compute the mean, then to compute the variance.
Examples found in repository?
74fn demonstrate_data_pipeline() -> Result<(), Box<dyn std::error::Error>> {
75 println!("\n--- Data Processing Pipeline ---");
76
77 // Simulate raw sensor data with noise
78 let raw_data: Vec<f32> = (0..20)
79 .map(|i| {
80 let base = i as f32 * 0.5;
81 let noise = (i % 3) as f32 * 0.1;
82 base + noise
83 })
84 .collect();
85
86 let tensor = Tensor::from_slice(&raw_data, vec![20])?;
87 println!("Raw sensor data: {:?}", tensor.data());
88
89 // Multi-stage processing pipeline
90 println!("\nProcessing pipeline:");
91 println!("1. Normalize data (z-score)");
92 println!("2. Apply smoothing filter");
93 println!("3. Detect outliers");
94 println!("4. Apply feature scaling");
95
96 // Stage 1: Normalization
97 let mean = tensor.mean().value();
98 let std = tensor.std().value();
99 let normalized: Tensor = tensor
100 .iter()
101 .map(|elem| elem.sub_scalar(mean).div_scalar(std))
102 .collect();
103 println!(
104 " Normalized (mean={:.3}, std={:.3}): {:?}",
105 mean,
106 std,
107 normalized.data()
108 );
109
110 // Stage 2: Smoothing (simple moving average)
111 let smoothed: Tensor = normalized
112 .iter()
113 .enumerate()
114 .map(|(i, elem)| {
115 if i == 0 || i == normalized.size() - 1 {
116 elem.clone()
117 } else {
118 // Simple 3-point average
119 let prev = normalized.element_view(i - 1);
120 let next = normalized.element_view(i + 1);
121 elem.add_tensor(&prev).add_tensor(&next).div_scalar(3.0)
122 }
123 })
124 .collect();
125 println!(" Smoothed: {:?}", smoothed.data());
126
127 // Stage 3: Outlier detection and removal
128 let outlier_threshold = 2.0;
129 let cleaned: Tensor = smoothed
130 .iter()
131 .filter(|elem| elem.value().abs() < outlier_threshold)
132 .collect();
133 println!(
134 " Outliers removed (threshold={}): {:?}",
135 outlier_threshold,
136 cleaned.data()
137 );
138
139 // Stage 4: Feature scaling to [0, 1] range
140 let min_val = cleaned
141 .iter()
142 .map(|e| e.value())
143 .fold(f32::INFINITY, f32::min);
144 let max_val = cleaned
145 .iter()
146 .map(|e| e.value())
147 .fold(f32::NEG_INFINITY, f32::max);
148 let scaled: Tensor = cleaned
149 .iter()
150 .map(|elem| elem.sub_scalar(min_val).div_scalar(max_val - min_val))
151 .collect();
152 println!(" Scaled to [0,1]: {:?}", scaled.data());
153
154 Ok(())
155}
156
157/// Demonstrate conditional processing patterns
158///
159/// Shows how to implement dynamic filtering and transformation
160/// based on data characteristics and conditions.
161fn demonstrate_conditional_processing() -> Result<(), Box<dyn std::error::Error>> {
162 println!("\n--- Conditional Processing ---");
163
164 // Create data with mixed characteristics
165 let data = vec![1.0, -2.0, 3.0, -4.0, 5.0, -6.0, 7.0, -8.0, 9.0, -10.0];
166 let tensor = Tensor::from_slice(&data, vec![10])?;
167 println!("Input data: {:?}", tensor.data());
168
169 // Conditional transformation based on sign
170 println!("\nConditional transformation (positive/negative handling):");
171 let processed: Tensor = tensor
172 .iter()
173 .map(|elem| {
174 let val = elem.value();
175 if val > 0.0 {
176 elem.pow_scalar(2.0) // Square positive values
177 } else {
178 elem.mul_scalar(-1.0).sqrt() // Square root of absolute negative values
179 }
180 })
181 .collect();
182 println!(" Processed: {:?}", processed.data());
183
184 // Adaptive filtering based on local statistics
185 println!("\nAdaptive filtering (remove values > 2 std from local mean):");
186 let window_size = 3;
187 let adaptive_filtered: Tensor = tensor
188 .iter()
189 .enumerate()
190 .filter(|(i, elem)| {
191 let start = i.saturating_sub(window_size / 2);
192 let end = (i + window_size / 2 + 1).min(tensor.size());
193
194 // Calculate local mean and std
195 let local_values: Vec<f32> = (start..end)
196 .map(|j| tensor.element_view(j).value())
197 .collect();
198
199 let local_mean = local_values.iter().sum::<f32>() / local_values.len() as f32;
200 let local_variance = local_values
201 .iter()
202 .map(|v| (v - local_mean).powi(2))
203 .sum::<f32>()
204 / local_values.len() as f32;
205 let local_std = local_variance.sqrt();
206
207 let threshold = local_mean + 2.0 * local_std;
208 elem.value() <= threshold
209 })
210 .map(|(_, elem)| elem)
211 .collect();
212 println!(" Adaptive filtered: {:?}", adaptive_filtered.data());
213
214 // Multi-condition processing
215 println!("\nMulti-condition processing:");
216 let multi_processed: Tensor = tensor
217 .iter()
218 .map(|elem| {
219 let val = elem.value();
220 match () {
221 _ if val > 5.0 => elem.mul_scalar(2.0), // Double large values
222 _ if val < -5.0 => elem.div_scalar(2.0), // Halve small values
223 _ if val.abs() < 2.0 => elem.add_scalar(1.0), // Add 1 to small values
224 _ => elem.clone(), // Keep others unchanged
225 }
226 })
227 .collect();
228 println!(" Multi-condition: {:?}", multi_processed.data());
229
230 Ok(())
231}
232
233/// Demonstrate batch processing operations
234///
235/// Shows efficient processing of large datasets using iterator
236/// patterns and batch operations for performance optimization.
237fn demonstrate_batch_operations() -> Result<(), Box<dyn std::error::Error>> {
238 println!("\n--- Batch Operations ---");
239
240 // Create a larger dataset for batch processing
241 let size = 100;
242 let data: Vec<f32> = (0..size)
243 .map(|i| {
244 let x = i as f32 / size as f32;
245 x * x + 0.1 * (i % 7) as f32 // Quadratic with some noise
246 })
247 .collect();
248
249 let tensor = Tensor::from_slice(&data, vec![size])?;
250 println!("Dataset size: {}", tensor.size());
251
252 // Batch processing with windowing
253 println!("\nBatch processing with sliding windows:");
254 let batch_size = 10;
255 let batches: Vec<Tensor> = tensor
256 .iter()
257 .collect::<Vec<_>>()
258 .chunks(batch_size)
259 .map(|chunk| {
260 // Process each batch independently
261 chunk
262 .iter()
263 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
264 .collect()
265 })
266 .collect();
267
268 println!(
269 " Processed {} batches of size {}",
270 batches.len(),
271 batch_size
272 );
273 for (i, batch) in batches.iter().enumerate() {
274 println!(
275 " Batch {}: mean={:.3}, std={:.3}",
276 i,
277 batch.mean().value(),
278 batch.std().value()
279 );
280 }
281
282 // Parallel-like processing with stride
283 println!("\nStrided processing (every nth element):");
284 let stride = 5;
285 let strided: Tensor = tensor
286 .iter()
287 .enumerate()
288 .filter(|(i, _)| i % stride == 0)
289 .map(|(_, elem)| elem)
290 .collect();
291 println!(" Strided (every {}th): {:?}", stride, strided.data());
292
293 // Hierarchical processing
294 println!("\nHierarchical processing (coarse to fine):");
295 let coarse: Tensor = tensor
296 .iter()
297 .enumerate()
298 .filter(|(i, _)| i % 4 == 0) // Take every 4th element
299 .map(|(_, elem)| elem)
300 .collect();
301
302 let fine: Tensor = tensor
303 .iter()
304 .enumerate()
305 .filter(|(i, _)| i % 4 != 0) // Take the rest
306 .map(|(_, elem)| elem)
307 .collect();
308
309 println!(" Coarse (every 4th): {:?}", coarse.data());
310 println!(" Fine (rest): {:?}", fine.data());
311
312 // Combine coarse and fine with different processing
313 let combined: Tensor = coarse
314 .iter()
315 .map(|elem| elem.mul_scalar(2.0)) // Scale coarse
316 .chain(fine.iter().map(|elem| elem.div_scalar(2.0))) // Scale fine
317 .collect();
318 println!(" Combined: {:?}", combined.data());
319
320 Ok(())
321}
322
323/// Demonstrate real-world processing scenarios
324///
325/// Shows practical applications of iterator patterns for
326/// common data processing tasks in machine learning and analytics.
327fn demonstrate_real_world_scenarios() -> Result<(), Box<dyn std::error::Error>> {
328 println!("\n--- Real-world Scenarios ---");
329
330 // Scenario 1: Time series analysis
331 println!("\nScenario 1: Time Series Analysis");
332 let time_series: Vec<f32> = (0..24)
333 .map(|hour| {
334 let base = 20.0 + 10.0 * (hour as f32 * std::f32::consts::PI / 12.0).sin();
335 base + (hour % 3) as f32 * 2.0 // Add some noise
336 })
337 .collect();
338
339 let series = Tensor::from_slice(&time_series, vec![24])?;
340 println!(" Time series (24 hours): {:?}", series.data());
341
342 // Calculate moving average
343 let window_size = 3;
344 let moving_avg: Tensor = series
345 .iter()
346 .enumerate()
347 .map(|(i, _)| {
348 let start = i.saturating_sub(window_size / 2);
349 let end = (i + window_size / 2 + 1).min(series.size());
350 let window = series.iter_range(start, end);
351 window.fold(0.0, |acc, elem| acc + elem.value()) / (end - start) as f32
352 })
353 .map(|val| Tensor::from_slice(&[val], vec![1]).unwrap())
354 .collect();
355 println!(
356 " Moving average (window={}): {:?}",
357 window_size,
358 moving_avg.data()
359 );
360
361 // Scenario 2: Feature engineering
362 println!("\nScenario 2: Feature Engineering");
363 let features = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![5])?;
364 println!(" Original features: {:?}", features.data());
365
366 // Create polynomial features
367 let poly_features: Tensor = features
368 .iter()
369 .flat_map(|elem| {
370 vec![
371 elem.clone(), // x^1
372 elem.pow_scalar(2.0), // x^2
373 elem.pow_scalar(3.0), // x^3
374 ]
375 })
376 .collect();
377 println!(
378 " Polynomial features (x, x^2, x^3): {:?}",
379 poly_features.data()
380 );
381
382 // Scenario 3: Data augmentation
383 println!("\nScenario 3: Data Augmentation");
384 let original = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?;
385 println!(" Original data: {:?}", original.data());
386
387 // Augment with noise and scaling
388 let augmented: Tensor = original
389 .iter()
390 .flat_map(|elem| {
391 vec![
392 elem.clone(), // Original
393 elem.add_scalar(0.1), // Add noise
394 elem.sub_scalar(0.1), // Subtract noise
395 elem.mul_scalar(1.1), // Scale up
396 elem.mul_scalar(0.9), // Scale down
397 ]
398 })
399 .collect();
400 println!(" Augmented data: {:?}", augmented.data());
401
402 // Scenario 4: Statistical analysis
403 println!("\nScenario 4: Statistical Analysis");
404 let sample_data = Tensor::from_slice(&[1.1, 2.3, 1.8, 2.1, 1.9, 2.0, 1.7, 2.2], vec![8])?;
405 println!(" Sample data: {:?}", sample_data.data());
406
407 // Calculate various statistics
408 let mean = sample_data.mean().value();
409 let std = sample_data.std().value();
410 let min = sample_data
411 .iter()
412 .map(|e| e.value())
413 .fold(f32::INFINITY, f32::min);
414 let max = sample_data
415 .iter()
416 .map(|e| e.value())
417 .fold(f32::NEG_INFINITY, f32::max);
418
419 // Z-score normalization
420 let z_scores: Tensor = sample_data
421 .iter()
422 .map(|elem| elem.sub_scalar(mean).div_scalar(std))
423 .collect();
424
425 println!(
426 " Statistics: mean={:.3}, std={:.3}, min={:.3}, max={:.3}",
427 mean, std, min, max
428 );
429 println!(" Z-scores: {:?}", z_scores.data());
430
431 Ok(())
432}Sourcepub fn std_dims(&self, dims: &[usize], keepdim: bool) -> Tensor
pub fn std_dims(&self, dims: &[usize], keepdim: bool) -> Tensor
Computes the standard deviation over specified dimensions
Reduces the tensor along the specified dimensions by computing the standard deviation of each slice. The result maintains the original tensor structure with reduced dimensions optionally preserved as size-1 dimensions.
Uses population standard deviation (divides by n rather than n-1) to match PyTorch’s default behavior.
§Arguments
dims- Vector of dimension indices to reduce over (must be valid for tensor rank)keepdim- Whether to keep reduced dimensions as size-1 dimensions
§Returns
A tensor with standard deviation computed over the specified dimensions
§Examples
use train_station::Tensor;
// Standard deviation along rows (dimension 1) with keepdim=true
let matrix = Tensor::from_slice(&[1.0, 3.0, 2.0, 2.0], vec![2, 2]).unwrap();
let row_stds = matrix.std_dims(&[1], true);
assert_eq!(row_stds.shape().dims, vec![2, 1]);
assert!((row_stds.get(&[0, 0]) - 1.0).abs() < 1e-6); // std([1, 3]) = 1.0
assert!((row_stds.get(&[1, 0]) - 0.0).abs() < 1e-6); // std([2, 2]) = 0.0use train_station::Tensor;
// Standard deviation along columns (dimension 0) with keepdim=false
let matrix = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let col_stds = matrix.std_dims(&[0], false);
assert_eq!(col_stds.shape().dims, vec![2]);
// std([1, 3]) = 1.0, std([2, 4]) = 1.0
assert!((col_stds.get(&[0]) - 1.0).abs() < 1e-6);
assert!((col_stds.get(&[1]) - 1.0).abs() < 1e-6);use train_station::Tensor;
// Standard deviation over multiple dimensions
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let std_all = tensor.std_dims(&[0, 1], false);
assert_eq!(std_all.shape().dims, vec![1]);
// std([1, 2, 3, 4]) = sqrt(1.25) ≈ 1.118
assert!((std_all.get(&[0]) - 1.25_f32.sqrt()).abs() < 1e-5);§Panics
- If
dimsis empty - If any dimension index is out of bounds for the tensor rank
- If the reduced size is 0 (invalid for standard deviation calculation)
§Performance
Uses efficient coordinate-based iteration that works correctly with both contiguous and non-contiguous tensor layouts. The algorithm performs two passes: first to compute means, then to compute variances.
Source§impl Tensor
impl Tensor
Sourcepub fn sum(&self) -> Tensor
pub fn sum(&self) -> Tensor
Returns the sum of all elements in the tensor
This operation computes the sum of all elements across all dimensions, reducing the tensor to a scalar value. The output is a tensor with shape [1] containing the sum as a float.
When requires_grad is enabled, this operation supports automatic gradient
tracking through the GradTrack system.
§Returns
A tensor with shape [1] containing the sum of all elements
§Examples
use train_station::Tensor;
// Basic sum calculation
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let total = tensor.sum();
assert_eq!(total.shape().dims, vec![1]);
assert_eq!(total.get(&[0]), 10.0); // 1 + 2 + 3 + 4 = 10use train_station::Tensor;
// Sum with gradient tracking
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])
.unwrap()
.with_requires_grad();
let mut total = tensor.sum();
total.backward(None);
let grad = tensor.grad_by_value().expect("gradient should exist");
// Gradient should be [1.0, 1.0, 1.0] for each element
assert_eq!(grad.get(&[0]), 1.0);
assert_eq!(grad.get(&[1]), 1.0);
assert_eq!(grad.get(&[2]), 1.0);use train_station::Tensor;
// Sum of empty tensor
let tensor = Tensor::new(vec![0]);
let total = tensor.sum();
assert_eq!(total.get(&[0]), 0.0); // Sum of empty tensor is 0§Performance
Uses optimized contiguous tensor path with 4x loop unrolling for better performance. Non-contiguous tensors use stride-aware iteration.
Examples found in repository?
179fn demonstrate_utility_functions() {
180 println!("\n--- Utility Functions ---");
181
182 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
183
184 // Basic properties
185 println!("Shape: {:?}", tensor.shape().dims);
186 println!("Size: {}", tensor.size());
187 println!("Is contiguous: {}", tensor.is_contiguous());
188 println!("Device: {:?}", tensor.device());
189
190 // Mathematical operations
191 let sum = tensor.sum();
192 println!("Sum: {}", sum.value());
193
194 let mean = tensor.mean();
195 println!("Mean: {}", mean.value());
196
197 let norm = tensor.norm();
198 println!("Norm: {}", norm.value());
199
200 // Device placement
201 let cpu_tensor = Tensor::zeros_on_device(vec![3, 3], train_station::Device::cpu());
202 println!(
203 "CPU tensor: shape {:?}, device: {:?}",
204 cpu_tensor.shape().dims,
205 cpu_tensor.device()
206 );
207}More examples
159fn demonstrate_gradient_tracking() -> Result<(), Box<dyn std::error::Error>> {
160 println!("\n--- Gradient Tracking ---");
161
162 // Create a tensor with gradient tracking enabled
163 let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3])?.with_requires_grad();
164 println!("Input tensor (requires_grad): {:?}", tensor.data());
165
166 // Perform element-wise operations through iteration
167 let result: Tensor = tensor
168 .iter()
169 .map(|elem| {
170 // Apply a complex transformation: (x^2 + 1) * 2
171 elem.pow_scalar(2.0).add_scalar(1.0).mul_scalar(2.0)
172 })
173 .collect();
174
175 println!("Result tensor: {:?}", result.data());
176 println!("Result requires_grad: {}", result.requires_grad());
177
178 // Compute gradients
179 let mut loss = result.sum();
180 loss.backward(None);
181
182 println!("Loss: {:.6}", loss.value());
183 println!("Input gradients: {:?}", tensor.grad().map(|g| g.data()));
184
185 Ok(())
186}109fn demonstrate_optimizer_serialization() -> Result<(), Box<dyn std::error::Error>> {
110 println!("\n--- Optimizer Serialization ---");
111
112 // Create an optimizer with some parameters
113 let mut weight = Tensor::randn(vec![2, 2], Some(42)).with_requires_grad();
114 let mut bias = Tensor::randn(vec![2], Some(43)).with_requires_grad();
115
116 let config = AdamConfig {
117 learning_rate: 0.001,
118 beta1: 0.9,
119 beta2: 0.999,
120 eps: 1e-8,
121 weight_decay: 0.0,
122 amsgrad: false,
123 };
124
125 let mut optimizer = Adam::with_config(config);
126 optimizer.add_parameter(&weight);
127 optimizer.add_parameter(&bias);
128
129 println!(
130 "Created optimizer with {} parameters",
131 optimizer.parameter_count()
132 );
133 println!("Learning rate: {}", optimizer.learning_rate());
134
135 // Simulate some training steps
136 for _ in 0..3 {
137 let mut loss = weight.sum() + bias.sum();
138 loss.backward(None);
139 optimizer.step(&mut [&mut weight, &mut bias]);
140 optimizer.zero_grad(&mut [&mut weight, &mut bias]);
141 }
142
143 // Save optimizer state
144 let optimizer_path = "temp_optimizer.json";
145 optimizer.save_json(optimizer_path)?;
146 println!("Saved optimizer to: {}", optimizer_path);
147
148 // Load optimizer state
149 let loaded_optimizer = Adam::load_json(optimizer_path)?;
150 println!(
151 "Loaded optimizer with {} parameters",
152 loaded_optimizer.parameter_count()
153 );
154 println!("Learning rate: {}", loaded_optimizer.learning_rate());
155
156 // Verify optimizer state
157 assert_eq!(
158 optimizer.parameter_count(),
159 loaded_optimizer.parameter_count()
160 );
161 assert_eq!(optimizer.learning_rate(), loaded_optimizer.learning_rate());
162 println!("Optimizer serialization verification: PASSED");
163
164 Ok(())
165}
166
167/// Demonstrate format comparison and performance characteristics
168fn demonstrate_format_comparison() -> Result<(), Box<dyn std::error::Error>> {
169 println!("\n--- Format Comparison ---");
170
171 // Create a larger tensor for comparison
172 let tensor = Tensor::randn(vec![10, 10], Some(44));
173
174 // Save in both formats
175 tensor.save_json("temp_comparison.json")?;
176 tensor.save_binary("temp_comparison.bin")?;
177
178 // Compare file sizes
179 let json_size = fs::metadata("temp_comparison.json")?.len();
180 let binary_size = fs::metadata("temp_comparison.bin")?.len();
181
182 println!("JSON file size: {} bytes", json_size);
183 println!("Binary file size: {} bytes", binary_size);
184 println!(
185 "Compression ratio: {:.2}x",
186 json_size as f64 / binary_size as f64
187 );
188
189 // Load and verify both formats
190 let json_tensor = Tensor::load_json("temp_comparison.json")?;
191 let binary_tensor = Tensor::load_binary("temp_comparison.bin")?;
192
193 assert_eq!(tensor.shape().dims, json_tensor.shape().dims);
194 assert_eq!(tensor.shape().dims, binary_tensor.shape().dims);
195 assert_eq!(tensor.data(), json_tensor.data());
196 assert_eq!(tensor.data(), binary_tensor.data());
197
198 println!("Format comparison verification: PASSED");
199
200 Ok(())
201}
202
203/// Demonstrate a basic model checkpointing workflow
204fn demonstrate_model_checkpointing() -> Result<(), Box<dyn std::error::Error>> {
205 println!("\n--- Model Checkpointing ---");
206
207 // Create a simple model (weights and bias)
208 let mut weights = Tensor::randn(vec![2, 1], Some(45)).with_requires_grad();
209 let mut bias = Tensor::randn(vec![1], Some(46)).with_requires_grad();
210
211 // Create optimizer
212 let mut optimizer = Adam::with_learning_rate(0.01);
213 optimizer.add_parameter(&weights);
214 optimizer.add_parameter(&bias);
215
216 println!("Initial weights: {:?}", weights.data());
217 println!("Initial bias: {:?}", bias.data());
218
219 // Simulate training
220 for epoch in 0..5 {
221 let mut loss = weights.sum() + bias.sum();
222 loss.backward(None);
223 optimizer.step(&mut [&mut weights, &mut bias]);
224 optimizer.zero_grad(&mut [&mut weights, &mut bias]);
225
226 if epoch % 2 == 0 {
227 // Save checkpoint
228 let checkpoint_dir = format!("checkpoint_epoch_{}", epoch);
229 fs::create_dir_all(&checkpoint_dir)?;
230
231 weights.save_json(format!("{}/weights.json", checkpoint_dir))?;
232 bias.save_json(format!("{}/bias.json", checkpoint_dir))?;
233 optimizer.save_json(format!("{}/optimizer.json", checkpoint_dir))?;
234
235 println!("Saved checkpoint for epoch {}", epoch);
236 }
237 }
238
239 // Load from checkpoint
240 let loaded_weights = Tensor::load_json("checkpoint_epoch_4/weights.json")?;
241 let loaded_bias = Tensor::load_json("checkpoint_epoch_4/bias.json")?;
242 let loaded_optimizer = Adam::load_json("checkpoint_epoch_4/optimizer.json")?;
243
244 println!("Loaded weights: {:?}", loaded_weights.data());
245 println!("Loaded bias: {:?}", loaded_bias.data());
246 println!(
247 "Loaded optimizer learning rate: {}",
248 loaded_optimizer.learning_rate()
249 );
250
251 // Verify checkpoint integrity
252 assert_eq!(weights.shape().dims, loaded_weights.shape().dims);
253 assert_eq!(bias.shape().dims, loaded_bias.shape().dims);
254 assert_eq!(optimizer.learning_rate(), loaded_optimizer.learning_rate());
255
256 println!("Checkpointing verification: PASSED");
257
258 Ok(())
259}297fn demonstrate_optimization_techniques() -> Result<(), Box<dyn std::error::Error>> {
298 println!("\n--- Optimization Techniques ---");
299
300 let size = 50000;
301 let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
302 let tensor = Tensor::from_slice(&data, vec![size])?;
303
304 println!("Optimizing processing for size: {}", size);
305
306 // Technique 1: Operation fusion
307 println!("\nTechnique 1: Operation Fusion");
308 let start = Instant::now();
309 let fused_result: Tensor = tensor
310 .iter()
311 .map(|elem| {
312 // Fuse multiple operations into single chain
313 elem.mul_scalar(2.0).add_scalar(1.0).pow_scalar(2.0).sqrt()
314 })
315 .collect();
316 let fused_time = start.elapsed();
317
318 // Technique 2: Conditional optimization
319 println!("\nTechnique 2: Conditional Optimization");
320 let start = Instant::now();
321 let conditional_result: Tensor = tensor
322 .iter()
323 .map(|elem| {
324 let val = elem.value();
325 if val < size as f32 / 2.0 {
326 elem.mul_scalar(2.0) // Simple operation for small values
327 } else {
328 elem.pow_scalar(2.0).sqrt() // Complex operation for large values
329 }
330 })
331 .collect();
332 let conditional_time = start.elapsed();
333
334 // Technique 3: Cache-friendly processing
335 println!("\nTechnique 3: Cache-Friendly Processing");
336 let start = Instant::now();
337 let cache_friendly_result: Tensor = tensor
338 .iter()
339 .take(1000) // Process in cache-friendly chunks
340 .map(|elem| elem.mul_scalar(2.0))
341 .collect();
342 let cache_friendly_time = start.elapsed();
343
344 // Technique 4: Memory pooling simulation
345 println!("\nTechnique 4: Memory Pooling Simulation");
346 let start = Instant::now();
347 let pooled_result: Tensor = tensor
348 .iter()
349 .enumerate()
350 .filter(|(i, _)| i % 100 == 0) // Process every 100th element
351 .map(|(_, elem)| elem.pow_scalar(2.0))
352 .collect();
353 let pooled_time = start.elapsed();
354
355 // Report optimization results
356 println!(" Fused operations: {:?}", fused_time);
357 println!(" Conditional optimization: {:?}", conditional_time);
358 println!(" Cache-friendly processing: {:?}", cache_friendly_time);
359 println!(" Memory pooling simulation: {:?}", pooled_time);
360
361 // Performance analysis
362 let fastest = fused_time
363 .min(conditional_time)
364 .min(cache_friendly_time)
365 .min(pooled_time);
366 println!(" Fastest technique: {:?}", fastest);
367
368 // Memory efficiency analysis
369 println!(" Fused result size: {}", fused_result.size());
370 println!(" Conditional result size: {}", conditional_result.size());
371 println!(
372 " Cache-friendly result size: {}",
373 cache_friendly_result.size()
374 );
375 println!(" Pooled result size: {}", pooled_result.size());
376
377 // Technique 5: Gradient optimization
378 println!("\nTechnique 5: Gradient Optimization");
379 let grad_tensor = tensor.with_requires_grad();
380 let start = Instant::now();
381
382 let grad_result: Tensor = grad_tensor
383 .iter()
384 .map(|elem| elem.pow_scalar(2.0).add_scalar(1.0))
385 .collect();
386
387 let mut loss = grad_result.sum();
388 loss.backward(None);
389 let grad_time = start.elapsed();
390
391 println!(" Gradient computation: {:?}", grad_time);
392 println!(
393 " Gradient tracking enabled: {}",
394 grad_result.requires_grad()
395 );
396
397 Ok(())
398}Sourcepub fn sum_dims(&self, dims: &[usize], keepdim: bool) -> Tensor
pub fn sum_dims(&self, dims: &[usize], keepdim: bool) -> Tensor
Returns the sum of elements along specified dimensions
This operation computes the sum of elements along the specified dimensions, reducing the tensor while optionally preserving the reduced dimensions as size-1 dimensions.
The output shape depends on the keepdim parameter:
- If
keepdimistrue, the reduced dimensions are kept with size 1 - If
keepdimisfalse, the reduced dimensions are removed
When requires_grad is enabled, this operation supports automatic gradient
tracking through the GradTrack system.
§Arguments
dims- Vector of dimension indices to sum over (must be valid for tensor rank)keepdim- Whether to keep reduced dimensions as size-1 dimensions
§Returns
A tensor with sum computed over the specified dimensions
§Panics
- If
dimsis empty - If any dimension index is out of bounds for the tensor rank
§Examples
use train_station::Tensor;
// Sum along rows (dimension 0) with keepdim=false
let matrix = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let row_sums = matrix.sum_dims(&[0], false);
assert_eq!(row_sums.shape().dims, vec![2]);
assert_eq!(row_sums.get(&[0]), 4.0); // 1 + 3 = 4
assert_eq!(row_sums.get(&[1]), 6.0); // 2 + 4 = 6use train_station::Tensor;
// Sum along columns (dimension 1) with keepdim=true
let matrix = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let col_sums = matrix.sum_dims(&[1], true);
assert_eq!(col_sums.shape().dims, vec![2, 1]);
assert_eq!(col_sums.get(&[0, 0]), 3.0); // 1 + 2 = 3
assert_eq!(col_sums.get(&[1, 0]), 7.0); // 3 + 4 = 7use train_station::Tensor;
// Sum over multiple dimensions
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let total = tensor.sum_dims(&[0, 1], false);
assert_eq!(total.shape().dims, vec![1]);
assert_eq!(total.get(&[0]), 10.0); // 1 + 2 + 3 + 4 = 10use train_station::Tensor;
// Sum with gradient tracking
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2])
.unwrap()
.with_requires_grad();
let mut row_sums = tensor.sum_dims(&[0], false);
row_sums.backward(None);
let grad = tensor.grad_by_value().expect("gradient should exist");
// Gradient should be [1.0, 1.0, 1.0, 1.0] for each element
assert_eq!(grad.get(&[0, 0]), 1.0);
assert_eq!(grad.get(&[0, 1]), 1.0);
assert_eq!(grad.get(&[1, 0]), 1.0);
assert_eq!(grad.get(&[1, 1]), 1.0);§Performance
Uses efficient coordinate-based iteration that works correctly with both contiguous and non-contiguous tensor layouts.
Source§impl Tensor
impl Tensor
Sourcepub fn var(&self) -> Tensor
pub fn var(&self) -> Tensor
Computes the variance over all elements
The variance is calculated as the mean of squared differences from the mean. This operation reduces the tensor to a scalar value [1].
The implementation uses population variance (divides by n rather than n-1) to match PyTorch’s default behavior.
§Returns
A scalar tensor containing the variance value
§Examples
use train_station::Tensor;
// Basic variance calculation
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4]).unwrap();
let variance = tensor.var();
assert!((variance.get(&[0]) - 1.25).abs() < 1e-5);use train_station::Tensor;
// Variance of a larger dataset
let data = vec![1.0, 3.0, 5.0, 7.0, 2.0, 4.0, 6.0, 8.0];
let tensor = Tensor::from_slice(&data, vec![2, 2, 2]).unwrap();
let variance = tensor.var();
// mean=4.5, var=mean([3.5², 1.5², 0.5², 2.5², 2.5², 0.5², 1.5², 3.5²]) = 5.25
assert!((variance.get(&[0]) - 5.25).abs() < 1e-5);use train_station::Tensor;
// Variance of constant values (should be 0)
let tensor = Tensor::from_slice(&[5.0, 5.0, 5.0, 5.0], vec![4]).unwrap();
let variance = tensor.var();
assert!((variance.get(&[0]) - 0.0).abs() < 1e-6);§Performance
Uses optimized contiguous tensor path with manual loop unrolling for better performance. Non-contiguous tensors use stride-aware iteration. The algorithm performs two passes: first to compute the mean, then to compute the variance.
Sourcepub fn var_dims(&self, dims: &[usize], keepdim: bool) -> Tensor
pub fn var_dims(&self, dims: &[usize], keepdim: bool) -> Tensor
Computes the variance over specified dimensions
Reduces the tensor along the specified dimensions by computing the variance of each slice. The result maintains the original tensor structure with reduced dimensions optionally preserved as size-1 dimensions.
Uses population variance (divides by n rather than n-1) to match PyTorch’s default behavior.
§Arguments
dims- Vector of dimension indices to reduce over (must be valid for tensor rank)keepdim- Whether to keep reduced dimensions as size-1 dimensions
§Returns
A tensor with variance computed over the specified dimensions
§Examples
use train_station::Tensor;
// Variance along rows (dimension 1) with keepdim=true
let matrix = Tensor::from_slice(&[1.0, 3.0, 2.0, 2.0], vec![2, 2]).unwrap();
let row_vars = matrix.var_dims(&[1], true);
assert_eq!(row_vars.shape().dims, vec![2, 1]);
assert!((row_vars.get(&[0, 0]) - 1.0).abs() < 1e-6); // var([1, 3]) = 1.0
assert!((row_vars.get(&[1, 0]) - 0.0).abs() < 1e-6); // var([2, 2]) = 0.0use train_station::Tensor;
// Variance along columns (dimension 0) with keepdim=false
let matrix = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let col_vars = matrix.var_dims(&[0], false);
assert_eq!(col_vars.shape().dims, vec![2]);
// var([1, 3]) = 1.0, var([2, 4]) = 1.0
assert!((col_vars.get(&[0]) - 1.0).abs() < 1e-6);
assert!((col_vars.get(&[1]) - 1.0).abs() < 1e-6);use train_station::Tensor;
// Variance over multiple dimensions
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let var_all = tensor.var_dims(&[0, 1], false);
assert_eq!(var_all.shape().dims, vec![1]);
// var([1, 2, 3, 4]) = 1.25
assert!((var_all.get(&[0]) - 1.25).abs() < 1e-5);§Panics
- If
dimsis empty - If any dimension index is out of bounds for the tensor rank
- If the reduced size is 0 (invalid for variance calculation)
§Performance
Uses efficient coordinate-based iteration that works correctly with both contiguous and non-contiguous tensor layouts. The algorithm performs two passes: first to compute means, then to compute variances.
Source§impl Tensor
impl Tensor
Sourcepub fn cat(tensors: &[Tensor], dim: usize) -> Tensor
pub fn cat(tensors: &[Tensor], dim: usize) -> Tensor
Concatenate tensors along a given dimension
Joins multiple tensors along the specified dimension, creating a new tensor with the combined data. All input tensors must have the same rank and matching dimensions except for the concatenation dimension.
§Arguments
tensors- Slice of tensors to concatenate (must not be empty)dim- Dimension along which to concatenate (must be < tensor rank)
§Returns
A new tensor containing the concatenated data with shape where the concatenation dimension is the sum of all input tensor sizes along that dimension.
§Panics
- If
tensorsis empty - If
dimis out of bounds for the tensor rank - If tensors have different ranks
- If tensors have mismatched dimensions (except along concatenation dimension)
§Examples
use train_station::Tensor;
// Concatenate 1D tensors
let a = Tensor::from_slice(&[1.0, 2.0], vec![2]).unwrap();
let b = Tensor::from_slice(&[3.0, 4.0], vec![2]).unwrap();
let result = Tensor::cat(&[a, b], 0);
assert_eq!(result.shape().dims, vec![4]);
assert_eq!(result.get(&[0]), 1.0);
assert_eq!(result.get(&[1]), 2.0);
assert_eq!(result.get(&[2]), 3.0);
assert_eq!(result.get(&[3]), 4.0);use train_station::Tensor;
// Concatenate 2D tensors along dimension 1
let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let b = Tensor::from_slice(&[5.0, 6.0], vec![2, 1]).unwrap();
let result = Tensor::cat(&[a, b], 1);
assert_eq!(result.shape().dims, vec![2, 3]);
assert_eq!(result.get(&[0, 0]), 1.0);
assert_eq!(result.get(&[0, 1]), 2.0);
assert_eq!(result.get(&[0, 2]), 5.0);use train_station::Tensor;
// Concatenate with gradient tracking
let mut a = Tensor::from_slice(&[1.0, 2.0], vec![2]).unwrap();
let mut b = Tensor::from_slice(&[3.0, 4.0], vec![2]).unwrap();
a.set_requires_grad(true);
b.set_requires_grad(true);
let result = Tensor::cat(&[a, b], 0);
assert!(result.requires_grad());Source§impl Tensor
impl Tensor
Sourcepub fn contiguous(&self) -> Tensor
pub fn contiguous(&self) -> Tensor
Creates a contiguous copy of the tensor
This operation ensures that the tensor data is stored in a linear, cache-friendly memory layout. If the tensor is already contiguous, this operation returns a clone. For non-contiguous tensors, it creates a new tensor with the same data but in contiguous memory layout.
The operation uses different optimization strategies based on tensor size:
- Small tensors (≤64 elements): Simple coordinate-based copy
- Medium tensors (65-1023 elements): Unrolled copy for better performance
- Large tensors (≥1024 elements): Blocked copy with cache optimization
§Returns
A new tensor with contiguous memory layout containing the same data
§Examples
use train_station::Tensor;
// Already contiguous tensor
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let contiguous = tensor.contiguous();
assert!(contiguous.is_contiguous());
assert_eq!(contiguous.shape().dims, vec![2, 2]);use train_station::Tensor;
// Non-contiguous tensor from transpose
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let transposed = tensor.transpose(0, 1);
assert!(!transposed.is_contiguous());
let contiguous = transposed.contiguous();
assert!(contiguous.is_contiguous());
assert_eq!(contiguous.get(&[0, 0]), 1.0);
assert_eq!(contiguous.get(&[0, 1]), 3.0);use train_station::Tensor;
// Preserves gradient tracking
let mut tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
tensor.set_requires_grad(true);
let contiguous = tensor.contiguous();
assert!(contiguous.requires_grad());§Performance
- Already contiguous: O(1) time complexity, returns a clone
- Non-contiguous: O(n) time complexity with size-dependent optimizations
- Memory usage: Creates a new tensor with the same size as the original
Source§impl Tensor
impl Tensor
Sourcepub fn flatten(&self) -> Tensor
pub fn flatten(&self) -> Tensor
Flatten the tensor into a 1D representation
Transforms a multi-dimensional tensor into a 1D tensor by reshaping
all dimensions into a single dimension. This is equivalent to
reshape(vec![-1]) where -1 automatically calculates the size
based on the total number of elements.
The flatten operation preserves the total number of elements while changing the tensor’s shape to have a single dimension. This is commonly used in neural networks to prepare tensor data for linear layers or feature extraction.
§Returns
A 1D tensor containing the same data as the original tensor, with
shape [total_elements] where total_elements is the product of
all original dimensions.
§Examples
use train_station::Tensor;
// Flatten a 2D tensor
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let flattened = tensor.flatten();
assert_eq!(flattened.shape().dims, vec![4]);
assert_eq!(flattened.get(&[0]), 1.0);
assert_eq!(flattened.get(&[1]), 2.0);
assert_eq!(flattened.get(&[2]), 3.0);
assert_eq!(flattened.get(&[3]), 4.0);use train_station::Tensor;
// Flatten a 3D tensor
let data: Vec<f32> = (0..12).map(|i| i as f32).collect();
let tensor = Tensor::from_slice(&data, vec![2, 2, 3]).unwrap();
let flattened = tensor.flatten();
assert_eq!(flattened.shape().dims, vec![12]);
assert_eq!(flattened.size(), 12);use train_station::Tensor;
// Flatten with gradient tracking
let mut tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
tensor.set_requires_grad(true);
let flattened = tensor.flatten();
assert!(flattened.requires_grad());
assert_eq!(flattened.shape().dims, vec![4]);use train_station::Tensor;
// Flatten an already 1D tensor (no change)
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let flattened = tensor.flatten();
assert_eq!(flattened.shape().dims, vec![3]);
assert_eq!(flattened.size(), 3);§Performance
- Time Complexity: O(1) - Returns a view when possible
- Memory Usage: No additional memory allocation for view operations
- Gradient Tracking: Preserves gradient requirements and tracking
§Relationship to Other Operations
This operation is equivalent to:
use train_station::Tensor;
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let flattened = tensor.reshape(vec![-1]);Where -1 is a special value that automatically calculates the
dimension size based on the total number of elements in the tensor.
Source§impl Tensor
impl Tensor
Sourcepub fn permute(&self, dims: Vec<usize>) -> Tensor
pub fn permute(&self, dims: Vec<usize>) -> Tensor
Permute tensor dimensions according to specified order
Rearranges the dimensions of the tensor according to the provided dimension order. This operation returns a view with reordered strides, avoiding data copying while changing the logical arrangement of the tensor’s dimensions.
The permutation is specified as a vector where each element represents
the new position of the corresponding dimension from the original tensor.
For example, permute(vec![1, 0]) swaps the first two dimensions.
§Arguments
dims- Vector specifying the new order of dimensions (must have length equal to tensor rank)
§Returns
A new tensor view with rearranged dimensions and correspondingly adjusted strides. The total number of elements remains unchanged.
§Panics
- If
dimslength does not equal the tensor rank - If any dimension index is out of bounds for the tensor rank
- If
dimscontains duplicate dimension indices
§Examples
use train_station::Tensor;
// Permute 2D tensor (swap dimensions)
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
let permuted = tensor.permute(vec![1, 0]);
assert_eq!(permuted.shape().dims, vec![3, 2]);
assert_eq!(permuted.get(&[0, 0]), 1.0);
assert_eq!(permuted.get(&[1, 0]), 2.0);
assert_eq!(permuted.get(&[2, 1]), 6.0);use train_station::Tensor;
// Permute 3D tensor (reorder dimensions)
let data: Vec<f32> = (0..24).map(|i| i as f32).collect();
let tensor = Tensor::from_slice(&data, vec![2, 3, 4]).unwrap();
let permuted = tensor.permute(vec![2, 0, 1]);
assert_eq!(permuted.shape().dims, vec![4, 2, 3]);
assert_eq!(permuted.size(), 24); // Total elements unchangeduse train_station::Tensor;
// Permute with gradient tracking
let mut tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
tensor.set_requires_grad(true);
let permuted = tensor.permute(vec![1, 0]);
assert!(permuted.requires_grad());
assert_eq!(permuted.shape().dims, vec![2, 2]);use train_station::Tensor;
// Identity permutation (no change)
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let permuted = tensor.permute(vec![0, 1]);
assert_eq!(permuted.shape().dims, vec![2, 2]);
assert_eq!(permuted.get(&[0, 0]), 1.0);
assert_eq!(permuted.get(&[1, 1]), 4.0);§Performance
- Time Complexity: O(1) - Returns a view with reordered strides
- Memory Usage: No additional memory allocation (view operation)
- Gradient Tracking: Preserves gradient requirements and tracking
§Relationship to Other Operations
This operation is similar to transpose() but more general:
transpose(dim0, dim1)is equivalent topermute()with a swap of two dimensionspermute()can handle arbitrary dimension reordering for tensors of any rank
§Memory Layout
The permuted tensor maintains the same underlying data but with reordered strides. This means the tensor becomes non-contiguous unless the permutation is the identity permutation.
Source§impl Tensor
impl Tensor
Sourcepub fn reshape(&self, new_shape: Vec<i32>) -> Tensor
pub fn reshape(&self, new_shape: Vec<i32>) -> Tensor
Reshape the tensor to the specified dimensions
Changes the shape of the tensor while preserving the total number of elements. This operation returns a view when the tensor is contiguous, avoiding data copying. For non-contiguous tensors, data is copied to ensure the reshape is valid.
The reshape operation supports automatic dimension inference using -1, which allows one dimension to be automatically calculated based on the total number of elements and the other specified dimensions.
§Arguments
new_shape- Target shape for the tensor. Use -1 for one dimension to have it automatically inferred from the total size.
§Returns
A new tensor with the specified shape containing the same data as the original tensor.
§Panics
- If more than one dimension is -1
- If the total number of elements doesn’t match the original tensor
- If any dimension size is 0 or less than -1
- If the inferred dimension size is not a whole number
§Examples
use train_station::Tensor;
// Basic reshape
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
let reshaped = tensor.reshape(vec![3, 2]);
assert_eq!(reshaped.shape().dims, vec![3, 2]);
assert_eq!(reshaped.get(&[0, 0]), 1.0);
assert_eq!(reshaped.get(&[2, 1]), 6.0);use train_station::Tensor;
// Using -1 for automatic dimension inference
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![4]).unwrap();
let reshaped = tensor.reshape(vec![2, -1]);
assert_eq!(reshaped.shape().dims, vec![2, 2]);
assert_eq!(reshaped.get(&[0, 0]), 1.0);
assert_eq!(reshaped.get(&[1, 1]), 4.0);use train_station::Tensor;
// Reshape with gradient tracking
let mut tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
tensor.set_requires_grad(true);
let reshaped = tensor.reshape(vec![4]);
assert!(reshaped.requires_grad());
assert_eq!(reshaped.shape().dims, vec![4]);use train_station::Tensor;
// Reshape 3D tensor
let data: Vec<f32> = (0..24).map(|i| i as f32).collect();
let tensor = Tensor::from_slice(&data, vec![2, 3, 4]).unwrap();
let reshaped = tensor.reshape(vec![6, 4]);
assert_eq!(reshaped.shape().dims, vec![6, 4]);
assert_eq!(reshaped.size(), 24);§Performance
- Contiguous tensors: O(1) time complexity, returns a view
- Non-contiguous tensors: O(n) time complexity with data copying
- Memory usage: No additional allocation for view operations
- Gradient tracking: Preserves gradient requirements and tracking
§Automatic Dimension Inference
When using -1 for a dimension, the size is automatically calculated:
use train_station::Tensor;
// For a tensor with 12 elements
let data: Vec<f32> = (0..12).map(|i| i as f32).collect();
let tensor = Tensor::from_slice(&data, vec![3, 4]).unwrap();
let reshaped1 = tensor.reshape(vec![3, -1]); // Results in shape [3, 4]
let reshaped2 = tensor.reshape(vec![-1, 6]); // Results in shape [2, 6]
let reshaped3 = tensor.reshape(vec![-1]); // Results in shape [12]Source§impl Tensor
impl Tensor
Sourcepub fn split(&self, split_size: usize, dim: usize) -> Vec<Tensor>
pub fn split(&self, split_size: usize, dim: usize) -> Vec<Tensor>
Split tensor into chunks of equal size along specified dimension
Divides the tensor into multiple smaller tensors along the specified dimension, where each chunk (except possibly the last) has the same size. The last chunk may be smaller if the dimension size is not evenly divisible by the split size.
This operation returns a vector of tensors, where each tensor is a view or copy of a portion of the original tensor. The first chunk is returned as a view when possible (zero-copy), while subsequent chunks may require data copying for non-zero base offsets.
§Arguments
split_size- Size of each chunk along the specified dimension (must be > 0)dim- Dimension along which to split the tensor (must be < tensor rank)
§Returns
A vector of tensors, each representing a chunk of the original tensor. The number of chunks depends on the dimension size and split size.
§Panics
- If tensor rank is 0 (scalar tensors cannot be split)
- If
dimis out of bounds for the tensor rank - If
split_sizeis 0
§Examples
use train_station::Tensor;
// Split 2D tensor into equal chunks along dimension 1
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
let parts = tensor.split(1, 1);
assert_eq!(parts.len(), 3);
assert_eq!(parts[0].shape().dims, vec![2, 1]);
assert_eq!(parts[1].shape().dims, vec![2, 1]);
assert_eq!(parts[2].shape().dims, vec![2, 1]);
assert_eq!(parts[0].get(&[0, 0]), 1.0);
assert_eq!(parts[1].get(&[0, 0]), 2.0);
assert_eq!(parts[2].get(&[1, 0]), 6.0);use train_station::Tensor;
// Split with uneven division (last chunk smaller)
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![1, 5]).unwrap();
let parts = tensor.split(2, 1);
assert_eq!(parts.len(), 3);
assert_eq!(parts[0].shape().dims, vec![1, 2]);
assert_eq!(parts[1].shape().dims, vec![1, 2]);
assert_eq!(parts[2].shape().dims, vec![1, 1]); // Last chunk smalleruse train_station::Tensor;
// Split with gradient tracking
let mut tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
tensor.set_requires_grad(true);
let parts = tensor.split(1, 1);
assert_eq!(parts.len(), 2);
assert!(parts[0].requires_grad());
assert!(parts[1].requires_grad());use train_station::Tensor;
// Split 1D tensor
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![6]).unwrap();
let parts = tensor.split(2, 0);
assert_eq!(parts.len(), 3);
assert_eq!(parts[0].shape().dims, vec![2]);
assert_eq!(parts[1].shape().dims, vec![2]);
assert_eq!(parts[2].shape().dims, vec![2]);§Performance
- First Chunk: O(1) - Returns a view when possible (zero-copy)
- Subsequent Chunks: O(n) - May require data copying for non-zero offsets
- Memory Usage: Minimal allocation for view operations, copying for non-zero offsets
- Gradient Tracking: Each chunk preserves gradient requirements and tracking
§Relationship to Other Operations
This operation is related to other tensor transformations:
split_with_sizes()- More general version with explicit chunk sizescat()- Inverse operation that concatenates tensors back togetherchunk()- Alternative splitting operation with different semantics
§Memory Layout
The first chunk maintains the same underlying data as a view when the base offset is zero. Subsequent chunks may require data copying to handle non-zero base offsets, ensuring proper memory layout.
Sourcepub fn split_with_sizes(&self, split_sizes: &[usize], dim: usize) -> Vec<Tensor>
pub fn split_with_sizes(&self, split_sizes: &[usize], dim: usize) -> Vec<Tensor>
Split tensor into chunks with explicit sizes along specified dimension
Divides the tensor into multiple smaller tensors along the specified
dimension according to the provided size specifications. Each chunk
has the exact size specified in the split_sizes array, and the sum
of all sizes must equal the size of the specified dimension.
This operation provides precise control over the size of each resulting
chunk, unlike split() which creates equal-sized chunks. The first
chunk is returned as a view when possible (zero-copy), while subsequent
chunks may require data copying for non-zero base offsets.
§Arguments
split_sizes- Array specifying the size of each chunk along the dimensiondim- Dimension along which to split the tensor (must be < tensor rank)
§Returns
A vector of tensors, each representing a chunk of the original tensor
with the specified size. The number of chunks equals the length of split_sizes.
§Panics
- If tensor rank is 0 (scalar tensors cannot be split)
- If
dimis out of bounds for the tensor rank - If sum of
split_sizesdoes not equal the size of the specified dimension
§Examples
use train_station::Tensor;
// Split with explicit sizes
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0], vec![1, 5]).unwrap();
let parts = tensor.split_with_sizes(&[2, 3], 1);
assert_eq!(parts.len(), 2);
assert_eq!(parts[0].shape().dims, vec![1, 2]);
assert_eq!(parts[1].shape().dims, vec![1, 3]);
assert_eq!(parts[0].get(&[0, 0]), 1.0);
assert_eq!(parts[0].get(&[0, 1]), 2.0);
assert_eq!(parts[1].get(&[0, 0]), 3.0);use train_station::Tensor;
// Split 2D tensor with different chunk sizes
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
let parts = tensor.split_with_sizes(&[1, 2], 1);
assert_eq!(parts.len(), 2);
assert_eq!(parts[0].shape().dims, vec![2, 1]);
assert_eq!(parts[1].shape().dims, vec![2, 2]);use train_station::Tensor;
// Split with gradient tracking
let mut tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
tensor.set_requires_grad(true);
let parts = tensor.split_with_sizes(&[1, 1], 1);
assert_eq!(parts.len(), 2);
assert!(parts[0].requires_grad());
assert!(parts[1].requires_grad());use train_station::Tensor;
// Split 1D tensor with explicit sizes
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![6]).unwrap();
let parts = tensor.split_with_sizes(&[2, 2, 2], 0);
assert_eq!(parts.len(), 3);
assert_eq!(parts[0].shape().dims, vec![2]);
assert_eq!(parts[1].shape().dims, vec![2]);
assert_eq!(parts[2].shape().dims, vec![2]);§Performance
- First Chunk: O(1) - Returns a view when possible (zero-copy)
- Subsequent Chunks: O(n) - May require data copying for non-zero offsets
- Memory Usage: Minimal allocation for view operations, copying for non-zero offsets
- Gradient Tracking: Each chunk preserves gradient requirements and tracking
§Relationship to Other Operations
This operation is related to other tensor transformations:
split()- Simplified version with equal-sized chunkscat()- Inverse operation that concatenates tensors back togetherchunk()- Alternative splitting operation with different semantics
§Memory Layout
The first chunk maintains the same underlying data as a view when the base offset is zero. Subsequent chunks may require data copying to handle non-zero base offsets, ensuring proper memory layout. Zero-sized chunks are handled by creating empty tensors with appropriate shapes.
Source§impl Tensor
impl Tensor
Sourcepub fn squeeze(&self, dim: Option<usize>) -> Tensor
pub fn squeeze(&self, dim: Option<usize>) -> Tensor
Remove dimensions of size 1 from the tensor
Removes singleton dimensions (dimensions with size 1) from the tensor, reducing its rank while preserving the total number of elements. This operation is useful for cleaning up tensor shapes and preparing data for operations that expect specific dimensionality.
The squeeze operation can remove either all size-1 dimensions or a
specific dimension if it has size 1. When all dimensions are size 1,
the result is a scalar tensor with shape [1] rather than an empty
tensor to maintain mathematical consistency.
§Arguments
dim- Optional specific dimension to squeeze. IfNone, all size-1 dimensions are removed. IfSome(d), only dimensiondis removed if it has size 1.
§Returns
A new tensor with size-1 dimensions removed. The total number of elements remains unchanged.
§Panics
- If
dimis specified but out of bounds for the tensor rank - If
dimis specified but the dimension does not have size 1
§Examples
use train_station::Tensor;
// Squeeze all size-1 dimensions
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![1, 3, 1]).unwrap();
let squeezed = tensor.squeeze(None);
assert_eq!(squeezed.shape().dims, vec![3]);
assert_eq!(squeezed.get(&[0]), 1.0);
assert_eq!(squeezed.get(&[1]), 2.0);
assert_eq!(squeezed.get(&[2]), 3.0);use train_station::Tensor;
// Squeeze specific dimension
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![1, 3, 1]).unwrap();
let squeezed = tensor.squeeze(Some(0));
assert_eq!(squeezed.shape().dims, vec![3, 1]);
assert_eq!(squeezed.get(&[0, 0]), 1.0);
assert_eq!(squeezed.get(&[1, 0]), 2.0);
assert_eq!(squeezed.get(&[2, 0]), 3.0);use train_station::Tensor;
// Squeeze preserves data integrity
let data = vec![1.0, 2.0, 3.0, 4.0];
let tensor = Tensor::from_slice(&data, vec![1, 2, 1, 2]).unwrap();
let squeezed = tensor.squeeze(None);
assert_eq!(squeezed.shape().dims, vec![2, 2]);
assert_eq!(squeezed.size(), 4);
assert_eq!(squeezed.get(&[0, 0]), data[0]);
assert_eq!(squeezed.get(&[0, 1]), data[1]);
assert_eq!(squeezed.get(&[1, 0]), data[2]);
assert_eq!(squeezed.get(&[1, 1]), data[3]);use train_station::Tensor;
// Handle edge case: all dimensions are size 1
let tensor = Tensor::from_slice(&[5.0], vec![1, 1, 1]).unwrap();
let squeezed = tensor.squeeze(None);
assert_eq!(squeezed.shape().dims, vec![1]); // Not empty!
assert_eq!(squeezed.get(&[0]), 5.0);use train_station::Tensor;
// Squeeze with gradient tracking
let mut tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![1, 3, 1]).unwrap();
tensor.set_requires_grad(true);
let squeezed = tensor.squeeze(None);
assert!(squeezed.requires_grad());
assert_eq!(squeezed.shape().dims, vec![3]);use train_station::Tensor;
// Squeeze and unsqueeze roundtrip
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let unsqueezed = tensor.unsqueeze(0);
assert_eq!(unsqueezed.shape().dims, vec![1, 3]);
let squeezed = unsqueezed.squeeze(Some(0));
assert_eq!(squeezed.shape().dims, vec![3]);
assert_eq!(squeezed.get(&[0]), 1.0);
assert_eq!(squeezed.get(&[2]), 3.0);§Performance
- Time Complexity: O(1) - Returns a view through reshape operation
- Memory Usage: No additional memory allocation (view operation)
- Gradient Tracking: Preserves gradient requirements and tracking
- Shape Transformation: Reduces tensor rank by removing singleton dimensions
§Relationship to Other Operations
This operation is related to other tensor transformations:
unsqueeze()- Inverse operation that adds size-1 dimensionsreshape()- More general shape transformation operationflatten()- Reduces tensor to 1D by combining all dimensions
§Memory Layout
The squeezed tensor maintains the same underlying data as the original tensor through the reshape operation. This ensures zero-copy behavior when the tensor is contiguous, with only the shape metadata being modified to reflect the reduced dimensionality.
§Edge Cases
- All size-1 dimensions: Returns a tensor with shape
[1]rather than an empty tensor to maintain mathematical consistency - No size-1 dimensions: Returns a tensor with the same shape as the input
- Mixed dimensions: Only removes dimensions with size 1, preserving others
Source§impl Tensor
impl Tensor
Sourcepub fn stack(tensors: &[Tensor], dim: usize) -> Tensor
pub fn stack(tensors: &[Tensor], dim: usize) -> Tensor
Stack a list of tensors along a new dimension
Combines multiple tensors by adding a new dimension at the specified
position. All input tensors must have identical shapes, and the output
tensor will have a new dimension of size equal to the number of input
tensors. This operation is similar to PyTorch’s torch.stack function.
The stacking operation creates a new axis in the output tensor, unlike concatenation which operates along existing dimensions. This makes stacking useful for creating batch dimensions, combining feature maps, and implementing operations that require adding new tensor axes.
§Arguments
tensors- Array of tensors to stack. All tensors must have identical shapes.dim- Index of the new axis in the output shape (0 <= dim <= rank)
§Returns
A new tensor with the stacked data. The output shape is the input shape
with a new dimension of size tensors.len() inserted at position dim.
§Panics
- If the tensor array is empty
- If any tensor has a different shape than the first tensor
- If
dimis out of bounds (dim > rank of input tensors)
§Examples
use train_station::Tensor;
// Stack two 1D tensors along dimension 0
let a = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let b = Tensor::from_slice(&[4.0, 5.0, 6.0], vec![3]).unwrap();
let stacked = Tensor::stack(&[a, b], 0);
assert_eq!(stacked.shape().dims, vec![2, 3]);
assert_eq!(stacked.get(&[0, 0]), 1.0);
assert_eq!(stacked.get(&[1, 2]), 6.0);use train_station::Tensor;
// Stack multiple 2D tensors along dimension 1
let a = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let b = Tensor::from_slice(&[5.0, 6.0, 7.0, 8.0], vec![2, 2]).unwrap();
let c = Tensor::from_slice(&[9.0, 10.0, 11.0, 12.0], vec![2, 2]).unwrap();
let stacked = Tensor::stack(&[a, b, c], 1);
assert_eq!(stacked.shape().dims, vec![2, 3, 2]);
assert_eq!(stacked.get(&[0, 0, 0]), 1.0);
assert_eq!(stacked.get(&[1, 2, 1]), 12.0);use train_station::Tensor;
// Stack with gradient tracking
let mut a = Tensor::from_slice(&[1.0, 2.0], vec![2]).unwrap();
let mut b = Tensor::from_slice(&[3.0, 4.0], vec![2]).unwrap();
a.set_requires_grad(true);
b.set_requires_grad(true);
let stacked = Tensor::stack(&[a, b], 0);
assert!(stacked.requires_grad());
assert_eq!(stacked.shape().dims, vec![2, 2]);use train_station::Tensor;
// Stack 3D tensors along the last dimension
let data1: Vec<f32> = (0..8).map(|i| i as f32).collect();
let data2: Vec<f32> = (8..16).map(|i| i as f32).collect();
let a = Tensor::from_slice(&data1, vec![2, 2, 2]).unwrap();
let b = Tensor::from_slice(&data2, vec![2, 2, 2]).unwrap();
let stacked = Tensor::stack(&[a, b], 3);
assert_eq!(stacked.shape().dims, vec![2, 2, 2, 2]);
assert_eq!(stacked.get(&[0, 0, 0, 0]), 0.0);
assert_eq!(stacked.get(&[1, 1, 1, 1]), 15.0);§Performance
- Time Complexity: O(n) where n is the total number of elements
- Memory Usage: Allocates new contiguous tensor for output
- SIMD Optimization: Uses AVX2 acceleration for large block copies
- Block-wise Copying: Optimized copying strategy for better cache performance
- Gradient Tracking: Preserves gradient requirements and tracking
§Relationship to Other Operations
This operation is related to other tensor transformations:
cat()- Concatenates tensors along existing dimensionsunsqueeze()- Adds a single dimension of size 1reshape()- Changes tensor shape without adding dimensions
§Memory Layout
The output tensor is always contiguous, with elements arranged so that the stacked dimension is the fastest-changing index. This ensures optimal performance for subsequent operations and maintains compatibility with SIMD optimizations.
§Gradient Computation
During backward passes, gradients are split along the stacked dimension and distributed back to the original input tensors. This is implemented using the same gradient function as concatenation, treating the stack operation as concatenation along a new axis.
Source§impl Tensor
impl Tensor
Sourcepub fn transpose(&self, dim0: usize, dim1: usize) -> Tensor
pub fn transpose(&self, dim0: usize, dim1: usize) -> Tensor
Transpose two dimensions of the tensor
Swaps two specified dimensions of the tensor, modifying the shape and memory access pattern. When possible, this operation returns a zero-copy view using stride manipulation. For complex cases or non-contiguous tensors, data is copied to ensure correct transposition.
The transpose operation is its own inverse - applying transpose twice with the same dimensions returns the original tensor.
§Arguments
dim0- First dimension to swap (must be < tensor rank)dim1- Second dimension to swap (must be < tensor rank)
§Returns
A new tensor with the specified dimensions transposed. The total number of elements remains unchanged.
§Panics
- If
dim0is out of bounds for the tensor rank - If
dim1is out of bounds for the tensor rank
§Examples
use train_station::Tensor;
// Basic 2D transpose
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]).unwrap();
let transposed = tensor.transpose(0, 1);
assert_eq!(transposed.shape().dims, vec![3, 2]);
assert_eq!(transposed.get(&[0, 0]), 1.0);
assert_eq!(transposed.get(&[0, 1]), 4.0);
assert_eq!(transposed.get(&[1, 0]), 2.0);
assert_eq!(transposed.get(&[1, 1]), 5.0);
assert_eq!(transposed.get(&[2, 0]), 3.0);
assert_eq!(transposed.get(&[2, 1]), 6.0);use train_station::Tensor;
// 3D tensor transpose
let data: Vec<f32> = (0..24).map(|i| i as f32).collect();
let tensor = Tensor::from_slice(&data, vec![2, 3, 4]).unwrap();
let transposed = tensor.transpose(0, 1);
assert_eq!(transposed.shape().dims, vec![3, 2, 4]);use train_station::Tensor;
// Transpose with gradient tracking
let mut tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
tensor.set_requires_grad(true);
let transposed = tensor.transpose(0, 1);
assert!(transposed.requires_grad());
assert_eq!(transposed.shape().dims, vec![2, 2]);use train_station::Tensor;
// Transpose same dimension (no change)
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let result = tensor.transpose(1, 1);
assert_eq!(result.shape().dims, tensor.shape().dims);
assert_eq!(result.get(&[0, 0]), tensor.get(&[0, 0]));use train_station::Tensor;
// Transpose is its own inverse
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let transposed = tensor.transpose(0, 1);
let double_transposed = transposed.transpose(0, 1);
assert_eq!(double_transposed.shape().dims, tensor.shape().dims);
assert_eq!(double_transposed.get(&[0, 0]), tensor.get(&[0, 0]));§Performance
- Contiguous tensors: O(1) time complexity, returns a view
- Non-contiguous tensors: O(n) time complexity with data copying
- Memory usage: No additional allocation for view operations
- Gradient tracking: Preserves gradient requirements and tracking
§Relationship to Other Operations
This operation is related to other tensor transformations:
t()- Convenience method for matrix transpose (last two dimensions)permute()- More general dimension reordering operationreshape()- Changes shape without changing dimension order
§Memory Layout
For contiguous tensors, transpose returns a view with modified strides, making the tensor non-contiguous. For non-contiguous tensors or complex cases, data is copied to ensure correct transposition.
Sourcepub fn t(&self) -> Tensor
pub fn t(&self) -> Tensor
Matrix transpose (transpose last two dimensions)
Convenience method for the common case of matrix transposition. For 2D tensors, this performs a standard matrix transpose. For higher-dimensional tensors, this transposes the last two dimensions, treating the tensor as a batch of matrices.
This method is equivalent to transpose(rank-2, rank-1) where
rank is the number of dimensions in the tensor.
§Returns
A new tensor with the last two dimensions transposed
§Panics
- If the tensor has less than 2 dimensions
§Examples
use train_station::Tensor;
// 2D matrix transpose
let matrix = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let transposed = matrix.t();
assert_eq!(transposed.shape().dims, vec![2, 2]);
assert_eq!(transposed.get(&[0, 0]), 1.0);
assert_eq!(transposed.get(&[0, 1]), 3.0);
assert_eq!(transposed.get(&[1, 0]), 2.0);
assert_eq!(transposed.get(&[1, 1]), 4.0);use train_station::Tensor;
// 3D tensor (batch of matrices)
let data: Vec<f32> = (0..12).map(|i| i as f32).collect();
let tensor = Tensor::from_slice(&data, vec![2, 2, 3]).unwrap();
let transposed = tensor.t();
assert_eq!(transposed.shape().dims, vec![2, 3, 2]);use train_station::Tensor;
// Matrix transpose with gradient tracking
let mut matrix = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
matrix.set_requires_grad(true);
let transposed = matrix.t();
assert!(transposed.requires_grad());
assert_eq!(transposed.shape().dims, vec![2, 2]);§Performance
- Time Complexity: Same as
transpose()- O(1) for views, O(n) for copies - Memory Usage: Same as
transpose()- no allocation for views - Gradient Tracking: Preserves gradient requirements and tracking
§Relationship to Other Operations
This operation is equivalent to:
use train_station::Tensor;
let tensor = Tensor::new(vec![2, 3, 4]);
let rank = tensor.shape().rank();
let transposed1 = tensor.t();
let transposed2 = tensor.transpose(rank - 2, rank - 1);
// transposed1 and transposed2 are identicalSource§impl Tensor
impl Tensor
Sourcepub fn unsqueeze(&self, dim: usize) -> Tensor
pub fn unsqueeze(&self, dim: usize) -> Tensor
Add a dimension of size 1 at the specified position
Inserts a new dimension of size 1 at the specified position in the tensor’s shape, increasing the rank by 1 while preserving the total number of elements. This operation is useful for preparing tensors for broadcasting, creating batch dimensions, and adapting tensor shapes for specific neural network operations.
The unsqueeze operation is the inverse of squeeze() - unsqueezing
a dimension and then squeezing it at the same position returns the
original tensor.
§Arguments
dim- Position to insert the new dimension (0 <= dim <= rank)
§Returns
A new tensor with an additional dimension of size 1 at the specified position. The total number of elements remains unchanged.
§Panics
- If
dimis out of bounds (dim > rank of the tensor)
§Examples
use train_station::Tensor;
// Add dimension at the beginning
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let unsqueezed = tensor.unsqueeze(0);
assert_eq!(unsqueezed.shape().dims, vec![1, 3]);
assert_eq!(unsqueezed.get(&[0, 0]), 1.0);
assert_eq!(unsqueezed.get(&[0, 1]), 2.0);
assert_eq!(unsqueezed.get(&[0, 2]), 3.0);use train_station::Tensor;
// Add dimension at the end
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let unsqueezed = tensor.unsqueeze(1);
assert_eq!(unsqueezed.shape().dims, vec![3, 1]);
assert_eq!(unsqueezed.get(&[0, 0]), 1.0);
assert_eq!(unsqueezed.get(&[1, 0]), 2.0);
assert_eq!(unsqueezed.get(&[2, 0]), 3.0);use train_station::Tensor;
// Add dimension in the middle of 2D tensor
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let unsqueezed = tensor.unsqueeze(1);
assert_eq!(unsqueezed.shape().dims, vec![2, 1, 2]);
assert_eq!(unsqueezed.get(&[0, 0, 0]), 1.0);
assert_eq!(unsqueezed.get(&[0, 0, 1]), 2.0);
assert_eq!(unsqueezed.get(&[1, 0, 0]), 3.0);
assert_eq!(unsqueezed.get(&[1, 0, 1]), 4.0);use train_station::Tensor;
// Unsqueeze preserves data integrity
let data = vec![1.0, 2.0, 3.0, 4.0];
let tensor = Tensor::from_slice(&data, vec![4]).unwrap();
let unsqueezed = tensor.unsqueeze(0);
assert_eq!(unsqueezed.shape().dims, vec![1, 4]);
assert_eq!(unsqueezed.size(), 4);
for (i, &d) in data.iter().enumerate() {
assert_eq!(unsqueezed.get(&[0, i]), d);
}use train_station::Tensor;
// Unsqueeze with gradient tracking
let mut tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
tensor.set_requires_grad(true);
let unsqueezed = tensor.unsqueeze(0);
assert!(unsqueezed.requires_grad());
assert_eq!(unsqueezed.shape().dims, vec![1, 3]);use train_station::Tensor;
// Unsqueeze and squeeze roundtrip
let tensor = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let unsqueezed = tensor.unsqueeze(0);
assert_eq!(unsqueezed.shape().dims, vec![1, 3]);
let squeezed = unsqueezed.squeeze(Some(0));
assert_eq!(squeezed.shape().dims, vec![3]);
assert_eq!(squeezed.get(&[0]), 1.0);
assert_eq!(squeezed.get(&[2]), 3.0);use train_station::Tensor;
// Multiple unsqueeze operations
let tensor = Tensor::from_slice(&[42.0], vec![1]).unwrap();
let unsqueezed1 = tensor.unsqueeze(0);
assert_eq!(unsqueezed1.shape().dims, vec![1, 1]);
let unsqueezed2 = unsqueezed1.unsqueeze(0);
assert_eq!(unsqueezed2.shape().dims, vec![1, 1, 1]);
assert_eq!(unsqueezed2.get(&[0, 0, 0]), 42.0);§Performance
- Time Complexity: O(1) - Returns a view through reshape operation
- Memory Usage: No additional memory allocation (view operation)
- Gradient Tracking: Preserves gradient requirements and tracking
- Shape Transformation: Increases tensor rank by adding singleton dimensions
§Relationship to Other Operations
This operation is related to other tensor transformations:
squeeze()- Inverse operation that removes size-1 dimensionsreshape()- More general shape transformation operationexpand()- Broadcasts dimensions to larger sizes
§Memory Layout
The unsqueezed tensor maintains the same underlying data as the original tensor through the reshape operation. This ensures zero-copy behavior when the tensor is contiguous, with only the shape metadata being modified to reflect the increased dimensionality.
§Broadcasting Applications
Unsqueeze is commonly used for broadcasting operations:
use train_station::Tensor;
// Prepare for broadcasting: [3] -> [1, 3] for row-wise operations
let vector = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let row_vector = vector.unsqueeze(0); // Shape: [1, 3]
// Prepare for broadcasting: [3] -> [3, 1] for column-wise operations
let column_vector = vector.unsqueeze(1); // Shape: [3, 1]§Neural Network Applications
Unsqueeze is essential for neural network operations:
use train_station::Tensor;
// Single sample -> batch dimension for neural network input
let sample = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let batch = sample.unsqueeze(0); // Shape: [1, 3] for batch processing
// Add channel dimension for convolutional operations
let feature_map = Tensor::from_slice(&[1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
let with_channels = feature_map.unsqueeze(0); // Shape: [1, 2, 2] for conv layersTrait Implementations§
Source§impl Add<Tensor> for f32
Scalar-tensor addition operator implementations
impl Add<Tensor> for f32
Scalar-tensor addition operator implementations
Provides addition operations between scalars and tensors.
All implementations delegate to the underlying add_scalar method.
Source§impl Add<f32> for Tensor
Tensor-scalar addition operator implementations
impl Add<f32> for Tensor
Tensor-scalar addition operator implementations
Provides addition operations between tensors and scalars.
All implementations delegate to the underlying add_scalar method.
Source§impl Add for Tensor
Tensor addition operator implementations
impl Add for Tensor
Tensor addition operator implementations
Provides addition operations between tensors with various reference combinations.
All implementations delegate to the underlying add_tensor method for optimal performance.
Source§impl AddAssign<&Tensor> for Tensor
impl AddAssign<&Tensor> for Tensor
Source§fn add_assign(&mut self, other: &Tensor)
fn add_assign(&mut self, other: &Tensor)
Adds another tensor reference to this tensor in-place
Source§impl AddAssign<f32> for Tensor
Tensor-scalar addition assignment operator implementations
impl AddAssign<f32> for Tensor
Tensor-scalar addition assignment operator implementations
Provides in-place addition operations between tensors and scalars.
Source§fn add_assign(&mut self, scalar: f32)
fn add_assign(&mut self, scalar: f32)
Adds a scalar to each element of this tensor in-place
Source§impl AddAssign for Tensor
Tensor addition assignment operator implementations
impl AddAssign for Tensor
Tensor addition assignment operator implementations
Provides in-place addition operations between tensors.
All implementations delegate to the underlying add_tensor method.
Source§fn add_assign(&mut self, other: Tensor)
fn add_assign(&mut self, other: Tensor)
Adds another tensor to this tensor in-place
Source§impl Clone for Tensor
Clone implementation for Tensor
impl Clone for Tensor
Clone implementation for Tensor
Creates a deep copy of the tensor data but resets gradtrack state (new tensor won’t track gradients unless explicitly set)
Source§impl Div<Tensor> for f32
Scalar-tensor division operator implementations
impl Div<Tensor> for f32
Scalar-tensor division operator implementations
Provides division operations between scalars and tensors.
Computes scalar / tensor by computing the reciprocal of the tensor and multiplying by the scalar.
Source§impl Div<f32> for Tensor
Tensor-scalar division operator implementations
impl Div<f32> for Tensor
Tensor-scalar division operator implementations
Provides division operations between tensors and scalars.
All implementations delegate to the underlying div_scalar method.
Source§impl Div for Tensor
Tensor division operator implementations
impl Div for Tensor
Tensor division operator implementations
Provides element-wise division operations between tensors with various reference combinations.
All implementations delegate to the underlying div_tensor method for optimal performance.
Source§impl DivAssign<&Tensor> for Tensor
impl DivAssign<&Tensor> for Tensor
Source§fn div_assign(&mut self, other: &Tensor)
fn div_assign(&mut self, other: &Tensor)
Divides this tensor by another tensor reference in-place
Source§impl DivAssign<f32> for Tensor
Tensor-scalar division assignment operator implementations
impl DivAssign<f32> for Tensor
Tensor-scalar division assignment operator implementations
Provides in-place division operations between tensors and scalars.
Source§fn div_assign(&mut self, scalar: f32)
fn div_assign(&mut self, scalar: f32)
Divides each element of this tensor by a scalar in-place
Source§impl DivAssign for Tensor
Tensor division assignment operator implementations
impl DivAssign for Tensor
Tensor division assignment operator implementations
Provides in-place division operations between tensors.
All implementations delegate to the underlying div_tensor method.
Source§fn div_assign(&mut self, other: Tensor)
fn div_assign(&mut self, other: Tensor)
Divides this tensor by another tensor in-place
Source§impl FromFieldValue for Tensor
impl FromFieldValue for Tensor
Source§fn from_field_value(
value: FieldValue,
field_name: &str,
) -> SerializationResult<Self>
fn from_field_value( value: FieldValue, field_name: &str, ) -> SerializationResult<Self>
Source§impl FromIterator<Tensor> for Tensor
impl FromIterator<Tensor> for Tensor
Source§fn from_iter<I: IntoIterator<Item = Tensor>>(iter: I) -> Self
fn from_iter<I: IntoIterator<Item = Tensor>>(iter: I) -> Self
Collect element view tensors back into a single tensor
This method reconstructs a tensor from an iterator of element view tensors. It includes optimizations for common patterns and maintains gradient tracking when appropriate.
The collection process automatically detects whether all elements are scalar
views (shape [1]) and uses optimized collection strategies accordingly.
Gradient tracking is preserved when any input element requires gradients.
§Performance
- Optimized Collection: Specialized paths for scalar and mixed views
- Memory Efficient: Direct memory copying without intermediate allocations
- Gradient Preservation: Maintains gradtrack functionality when enabled
- Shape Detection: Automatic detection of element shapes for optimization
§Implementation Details
The method performs the following steps:
- Element Collection: Gathers all element tensors from the iterator
- Shape Analysis: Determines if all elements are scalar views
- Optimized Path: Uses specialized collection for scalar views
- General Path: Handles mixed shapes by flattening into 1D tensor
- Gradient Setup: Preserves gradient tracking when appropriate
§Examples
§Basic Collection
use train_station::Tensor;
let original = Tensor::from_slice(&[1.0, 2.0, 3.0], vec![3]).unwrap();
let doubled: Tensor = original.iter()
.map(|elem| elem.mul_scalar(2.0))
.collect();
assert_eq!(doubled.data(), &[2.0, 4.0, 6.0]);§Collection with Gradient Tracking
use train_station::Tensor;
let original = Tensor::from_slice(&[1.0, 2.0], vec![2])
.unwrap()
.with_requires_grad();
let result: Tensor = original.iter()
.map(|elem| elem.mul_scalar(2.0))
.collect();
assert!(result.requires_grad());
assert_eq!(result.data(), &[2.0, 4.0]);§Empty Iterator Handling
use train_station::Tensor;
let empty: Tensor = Vec::<Tensor>::new().into_iter().collect();
assert_eq!(empty.size(), 0);
assert_eq!(empty.shape().dims, vec![0]);Source§impl<'a> IntoIterator for &'a Tensor
impl<'a> IntoIterator for &'a Tensor
Source§impl Mul<Tensor> for f32
Scalar-tensor multiplication operator implementations
impl Mul<Tensor> for f32
Scalar-tensor multiplication operator implementations
Provides multiplication operations between scalars and tensors.
All implementations delegate to the underlying mul_scalar method.
Source§impl Mul<f32> for Tensor
Tensor-scalar multiplication operator implementations
impl Mul<f32> for Tensor
Tensor-scalar multiplication operator implementations
Provides multiplication operations between tensors and scalars.
All implementations delegate to the underlying mul_scalar method.
Source§impl Mul for Tensor
Tensor multiplication operator implementations
impl Mul for Tensor
Tensor multiplication operator implementations
Provides element-wise multiplication operations between tensors with various reference combinations.
All implementations delegate to the underlying mul_tensor method for optimal performance.
Source§impl MulAssign<&Tensor> for Tensor
impl MulAssign<&Tensor> for Tensor
Source§fn mul_assign(&mut self, other: &Tensor)
fn mul_assign(&mut self, other: &Tensor)
Multiplies this tensor by another tensor reference in-place
Source§impl MulAssign<f32> for Tensor
Tensor-scalar multiplication assignment operator implementations
impl MulAssign<f32> for Tensor
Tensor-scalar multiplication assignment operator implementations
Provides in-place multiplication operations between tensors and scalars.
Source§fn mul_assign(&mut self, scalar: f32)
fn mul_assign(&mut self, scalar: f32)
Multiplies each element of this tensor by a scalar in-place
Source§impl MulAssign for Tensor
Tensor multiplication assignment operator implementations
impl MulAssign for Tensor
Tensor multiplication assignment operator implementations
Provides in-place multiplication operations between tensors.
All implementations delegate to the underlying mul_tensor method.
Source§fn mul_assign(&mut self, other: Tensor)
fn mul_assign(&mut self, other: Tensor)
Multiplies this tensor by another tensor in-place
Source§impl Neg for Tensor
Tensor negation operator implementations
impl Neg for Tensor
Tensor negation operator implementations
Provides unary negation operations for tensors.
All implementations delegate to the underlying mul_scalar method with -1.0.
Source§impl Serializable for Tensor
impl Serializable for Tensor
Source§fn to_json(&self) -> SerializationResult<String>
fn to_json(&self) -> SerializationResult<String>
Serialize the tensor to JSON format
This method converts the tensor into a human-readable JSON string representation that includes all tensor data, shape information, device placement, and gradtrack state. The JSON format is suitable for debugging, configuration files, and cross-language interoperability.
§Returns
JSON string representation of the tensor on success, or SerializationError on failure
§Examples
use train_station::Tensor;
use train_station::serialization::Serializable;
let mut tensor = Tensor::zeros(vec![2, 3]);
tensor.set(&[0, 0], 1.0);
tensor.set(&[1, 2], 5.0);
let json = tensor.to_json().unwrap();
assert!(!json.is_empty());
assert!(json.contains("data"));
assert!(json.contains("shape"));Source§fn from_json(json: &str) -> SerializationResult<Self>
fn from_json(json: &str) -> SerializationResult<Self>
Deserialize a tensor from JSON format
This method parses a JSON string and reconstructs a tensor with all its data, shape information, device placement, and gradtrack state. The JSON must contain all necessary fields in the expected format.
§Arguments
json- JSON string containing serialized tensor data
§Returns
The deserialized tensor on success, or SerializationError on failure
§Examples
use train_station::Tensor;
use train_station::serialization::Serializable;
let mut original = Tensor::ones(vec![2, 2]);
original.set(&[0, 1], 3.0);
original.set_requires_grad(true);
let json = original.to_json().unwrap();
let restored = Tensor::from_json(&json).unwrap();
assert_eq!(original.shape().dims, restored.shape().dims);
assert_eq!(original.get(&[0, 1]), restored.get(&[0, 1]));
assert_eq!(original.requires_grad(), restored.requires_grad());Source§fn to_binary(&self) -> SerializationResult<Vec<u8>>
fn to_binary(&self) -> SerializationResult<Vec<u8>>
Serialize the tensor to binary format
This method converts the tensor into a compact binary representation optimized for storage and transmission. The binary format provides maximum performance and minimal file sizes, making it ideal for large tensors and production use.
§Returns
Binary representation of the tensor on success, or SerializationError on failure
§Examples
use train_station::Tensor;
use train_station::serialization::Serializable;
let mut tensor = Tensor::zeros(vec![100, 100]);
for i in 0..10 {
tensor.set(&[i, i], i as f32);
}
let binary = tensor.to_binary().unwrap();
assert!(!binary.is_empty());
// Binary format is more compact than JSON for large tensorsSource§fn from_binary(data: &[u8]) -> SerializationResult<Self>
fn from_binary(data: &[u8]) -> SerializationResult<Self>
Deserialize a tensor from binary format
This method parses binary data and reconstructs a tensor with all its data, shape information, device placement, and gradtrack state. The binary data must contain complete serialized information in the expected format.
§Arguments
data- Binary data containing serialized tensor information
§Returns
The deserialized tensor on success, or SerializationError on failure
§Examples
use train_station::Tensor;
use train_station::serialization::Serializable;
let mut original = Tensor::ones(vec![3, 4]);
original.set(&[2, 3], 7.5);
original.set_requires_grad(true);
let binary = original.to_binary().unwrap();
let restored = Tensor::from_binary(&binary).unwrap();
assert_eq!(original.shape().dims, restored.shape().dims);
assert_eq!(original.get(&[2, 3]), restored.get(&[2, 3]));
assert_eq!(original.requires_grad(), restored.requires_grad());Source§fn save<P: AsRef<Path>>(
&self,
path: P,
format: Format,
) -> SerializationResult<()>
fn save<P: AsRef<Path>>( &self, path: P, format: Format, ) -> SerializationResult<()>
Source§fn save_to_writer<W: Write>(
&self,
writer: &mut W,
format: Format,
) -> SerializationResult<()>
fn save_to_writer<W: Write>( &self, writer: &mut W, format: Format, ) -> SerializationResult<()>
Source§fn load<P: AsRef<Path>>(path: P, format: Format) -> SerializationResult<Self>
fn load<P: AsRef<Path>>(path: P, format: Format) -> SerializationResult<Self>
Source§fn load_from_reader<R: Read>(
reader: &mut R,
format: Format,
) -> SerializationResult<Self>
fn load_from_reader<R: Read>( reader: &mut R, format: Format, ) -> SerializationResult<Self>
Source§impl StructSerializable for Tensor
impl StructSerializable for Tensor
Source§fn to_serializer(&self) -> StructSerializer
fn to_serializer(&self) -> StructSerializer
Convert Tensor to StructSerializer for serialization
Serializes tensor data, shape, device, and gradtrack state. Runtime state (id, grad, grad_fn, allocation_owner) is not serialized.
§Returns
StructSerializer containing all persistent tensor state
Source§fn from_deserializer(
deserializer: &mut StructDeserializer,
) -> SerializationResult<Self>
fn from_deserializer( deserializer: &mut StructDeserializer, ) -> SerializationResult<Self>
Create Tensor from StructDeserializer
Reconstructs tensor from serialized data, shape, device, and gradtrack state. Allocates new memory and generates new tensor ID.
§Arguments
deserializer- StructDeserializer containing tensor data
§Returns
Reconstructed Tensor instance or error if deserialization fails
Source§fn save_json<P: AsRef<Path>>(&self, path: P) -> SerializationResult<()>
fn save_json<P: AsRef<Path>>(&self, path: P) -> SerializationResult<()>
Source§fn save_binary<P: AsRef<Path>>(&self, path: P) -> SerializationResult<()>
fn save_binary<P: AsRef<Path>>(&self, path: P) -> SerializationResult<()>
Source§fn load_json<P: AsRef<Path>>(path: P) -> SerializationResult<Self>
fn load_json<P: AsRef<Path>>(path: P) -> SerializationResult<Self>
Source§fn load_binary<P: AsRef<Path>>(path: P) -> SerializationResult<Self>
fn load_binary<P: AsRef<Path>>(path: P) -> SerializationResult<Self>
Source§fn to_json(&self) -> SerializationResult<String>
fn to_json(&self) -> SerializationResult<String>
Source§fn to_binary(&self) -> SerializationResult<Vec<u8>>
fn to_binary(&self) -> SerializationResult<Vec<u8>>
Source§fn from_json(json: &str) -> SerializationResult<Self>
fn from_json(json: &str) -> SerializationResult<Self>
Source§fn from_binary(data: &[u8]) -> SerializationResult<Self>
fn from_binary(data: &[u8]) -> SerializationResult<Self>
Source§impl Sub<Tensor> for f32
Scalar-tensor subtraction operator implementations
impl Sub<Tensor> for f32
Scalar-tensor subtraction operator implementations
Provides subtraction operations between scalars and tensors.
Computes scalar - tensor by negating the tensor and adding the scalar.
Source§impl Sub<f32> for Tensor
Tensor-scalar subtraction operator implementations
impl Sub<f32> for Tensor
Tensor-scalar subtraction operator implementations
Provides subtraction operations between tensors and scalars.
All implementations delegate to the underlying sub_scalar method.
Source§impl Sub for Tensor
Tensor subtraction operator implementations
impl Sub for Tensor
Tensor subtraction operator implementations
Provides subtraction operations between tensors with various reference combinations.
All implementations delegate to the underlying sub_tensor method for optimal performance.
Source§impl SubAssign<&Tensor> for Tensor
impl SubAssign<&Tensor> for Tensor
Source§fn sub_assign(&mut self, other: &Tensor)
fn sub_assign(&mut self, other: &Tensor)
Subtracts another tensor reference from this tensor in-place
Source§impl SubAssign<f32> for Tensor
Tensor-scalar subtraction assignment operator implementations
impl SubAssign<f32> for Tensor
Tensor-scalar subtraction assignment operator implementations
Provides in-place subtraction operations between tensors and scalars.
Source§fn sub_assign(&mut self, scalar: f32)
fn sub_assign(&mut self, scalar: f32)
Subtracts a scalar from each element of this tensor in-place
Source§impl SubAssign for Tensor
Tensor subtraction assignment operator implementations
impl SubAssign for Tensor
Tensor subtraction assignment operator implementations
Provides in-place subtraction operations between tensors.
All implementations delegate to the underlying sub_tensor method.
Source§fn sub_assign(&mut self, other: Tensor)
fn sub_assign(&mut self, other: Tensor)
Subtracts another tensor from this tensor in-place