1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! Rescaling layer that applies a fixed affine map to every element of its input
//!
//! The fixed scale and offset do not train. The backward pass multiplies the input gradient
//! by the scale and needs no forward cache.
use crateError;
use crateParamCounts;
use crateno_trainable_parameters_layer_functions;
use crate;
use crate;
use Zip;
/// Applies `y = x * scale + offset` to every element
///
/// The map is elementwise and it keeps the input shape at every rank. The layer holds no
/// parameter, so `scale` and `offset` stay at the values that the constructor got. Training
/// never moves them
///
/// The layer has no training mode and no inference mode. A training pass therefore returns
/// exactly what an inference pass returns. The backward pass multiplies the incoming gradient by
/// `scale`, because the derivative of the map is `scale` at every element. The `offset` is a
/// constant, so it has no part in the gradient
///
/// Its use is input normalization in front of a model. An 8-bit image scales into `[0, 1]`
/// with `Rescaling::new(1.0 / 255.0)`, and into `[-1, 1]` with
/// `Rescaling::new(1.0 / 127.5).with_offset(-1.0)`. A layer keeps that step inside the model,
/// so inference applies the same map as training
///
/// A `scale` of 0 and a negative `scale` are both legal, and the layer applies them like any
/// other value
///
/// # Notes
///
/// The layer parks no cache in the context, not even the shape of the last input. Nothing in
/// the forward pass or the backward pass needs one. `known_input_shapes` therefore reports
/// `None` and `output_shape` reads "Unknown", and `backward` runs correctly before any forward
/// pass.
/// `compute_output_shape` still answers for any shape a caller passes, because the layer
/// changes values and not extents
///
/// # Examples
///
/// ```rust
/// use rustyml::neural_network::Shape;
/// use rustyml::neural_network::sequential::SequentialBuilder;
/// use rustyml::neural_network::layers::*;
/// use rustyml::neural_network::optimizers::*;
/// use rustyml::neural_network::losses::*;
/// use ndarray::Array2;
///
/// // A rank-2 input: 2 samples of 3 features each.
/// let x = Array2::from_shape_vec((2, 3), vec![0.0, 51.0, 102.0, 153.0, 204.0, 255.0])
/// .unwrap()
/// .into_dyn();
///
/// let mut model = SequentialBuilder::new()
/// .add(Rescaling::new(1.0 / 255.0))
/// .build(&Shape::known(x.shape()))
/// .unwrap();
/// model.compile(SGD::new(0.01, 0.0, false, 0.0).unwrap(), MeanSquaredError::new());
///
/// // View model structure.
/// model.summary();
///
/// let scaled = model.predict(&x).unwrap();
///
/// // Every value lands in [0, 1].
/// assert!(scaled.iter().all(|v| (0.0..=1.0).contains(v)));
/// assert!((scaled[[0, 1]] - 0.2).abs() < 1e-6);
/// ```
/// Maps `f` over `t` into a new tensor that is in C order
///
/// Every layer here emits C order, so a consumer can read any layer output as 1 contiguous
/// slice. The destination comes from `Tensor::zeros`, which is always in C order, and the
/// source reaches it through a `Zip` that accepts any input layout