pub struct Sgd { /* private fields */ }Expand description
Stochastic gradient descent with optional momentum, dampening, Nesterov momentum and
weight decay, as MLX’s optimizers.SGD.
Implementations§
Source§impl Sgd
impl Sgd
Sourcepub fn new(learning_rate: impl Into<LearningRate>) -> Self
pub fn new(learning_rate: impl Into<LearningRate>) -> Self
Plain SGD.
Examples found in repository?
examples/mnist.rs (line 110)
83fn main() -> Result<()> {
84 let args: Vec<String> = std::env::args().skip(1).collect();
85 let dir = args
86 .iter()
87 .find(|a| !a.starts_with("--"))
88 .cloned()
89 .unwrap_or_else(|| "data/mnist".into());
90 let epochs: usize = args
91 .iter()
92 .position(|a| a == "--epochs")
93 .and_then(|i| args.get(i + 1))
94 .map_or(10, |v| v.parse().expect("--epochs takes a number"));
95 let asynchronous = args.iter().any(|a| a == "--async");
96 let compiled = args.iter().any(|a| a == "--compile");
97 if args.iter().any(|a| a == "--cpu") {
98 set_default_stream(Stream::default_for(Device::cpu()));
99 }
100 let (train_x, train_y, n) = load(&dir, "train").inspect_err(|_| {
101 eprintln!("cannot read MNIST from {dir}: run `just mnist` (it downloads the data)");
102 })?;
103 let (test_x, test_y, n_test) = load(&dir, "t10k")?;
104 let pixels = train_x.len() / n;
105 let test_x = Array::from_slice(&test_x, [n_test, pixels]);
106 let test_y = Array::from_slice(&test_y, [n_test]);
107
108 let (batch_size, lr) = (256, 0.1);
109 random::seed(0);
110 let mut state = (Mlp::new(&[pixels as i32, 32, 32, 10]), Sgd::new(lr));
111 state.1.init(&state.0); // the optimizer's state exists before the first step
112 nuvec::eval(&state)?;
113 // The training step, compiled with the model and the optimizer as its state.
114 let step = nuvec::compile_mut(|(model, opt): &mut (Mlp, Sgd), (x, y): &(Array, Array)| {
115 let loss_fn = |m: &Mlp| cross_entropy(&m.forward(x), y, Reduction::Mean);
116 let (loss, grads) = value_and_grad(loss_fn)(model)?;
117 opt.update(model, &grads)?;
118 Ok(loss)
119 });
120 let mut shuffle = Shuffle(0);
121 let device = nuvec::default_stream().device;
122 let mut times = Vec::new();
123 let mut accuracy = 0.0;
124 for epoch in 0..epochs {
125 let tic = Instant::now();
126 let perm = shuffle.permutation(n);
127 for ids in perm.chunks(batch_size) {
128 let mut xb = Vec::with_capacity(ids.len() * pixels);
129 for &i in ids {
130 xb.extend_from_slice(&train_x[i * pixels..(i + 1) * pixels]);
131 }
132 let yb: Vec<u32> = ids.iter().map(|&i| train_y[i]).collect();
133 let x = Array::from_slice(&xb, [ids.len(), pixels]);
134 let y = Array::from_slice(&yb, [ids.len()]);
135 let batch = (x, y);
136 if compiled {
137 let _loss = step(&mut state, &batch)?;
138 } else {
139 let (model, opt) = &mut state;
140 let loss_fn =
141 |m: &Mlp| cross_entropy(&m.forward(&batch.0), &batch.1, Reduction::Mean);
142 let (_, grads) = value_and_grad(loss_fn)(&*model)?;
143 opt.update(model, &grads)?;
144 }
145 if asynchronous {
146 nuvec::async_eval(&state.0)?;
147 } else {
148 nuvec::eval(&state.0)?;
149 }
150 }
151 let predictions = state.0.forward(&test_x).argmax(1);
152 let hits = predictions.equal(&test_y).astype(DType::F32);
153 accuracy = hits.mean(..).item::<f32>()?;
154 let seconds = tic.elapsed().as_secs_f64();
155 times.push(seconds);
156 println!("Epoch {epoch}: Test accuracy {accuracy:.3}, Time {seconds:.3} (s)");
157 }
158 let mut steady: Vec<f64> = times.iter().skip(1).copied().collect();
159 steady.sort_by(f64::total_cmp);
160 let median = steady.get(steady.len() / 2).copied().unwrap_or(times[0]);
161 println!(
162 "RESULT device={device} async={asynchronous} compiled={compiled} accuracy={accuracy:.4} epoch_seconds={median:.4}"
163 );
164 Ok(())
165}Sourcepub fn weight_decay(self, weight_decay: f64) -> Self
pub fn weight_decay(self, weight_decay: f64) -> Self
Sets the (L2) weight decay added to the gradient.
Trait Implementations§
Source§impl Optimizer for Sgd
impl Optimizer for Sgd
Source§fn apply(
&mut self,
params: &[(&str, &Array)],
grads: &[&Array],
) -> Result<Vec<Array>>
fn apply( &mut self, params: &[(&str, &Array)], grads: &[&Array], ) -> Result<Vec<Array>>
One step over named parameters (the path keys each parameter’s state) and their
gradients: the updated parameters, in order. Increments the step once.
Source§fn init_params(&mut self, params: &[(&str, &Array)])
fn init_params(&mut self, params: &[(&str, &Array)])
Creates zero state for
params that have none (paths with state are kept).Source§fn visit_state(&self, v: &mut Visit<'_>)
fn visit_state(&self, v: &mut Visit<'_>)
Visits the state (
step, learning_rate, slots) and the hyperparameters.Source§fn visit_state_mut(&mut self, v: &mut VisitMut<'_>)
fn visit_state_mut(&mut self, v: &mut VisitMut<'_>)
Visits the state mutably.
Source§fn learning_rate(&self) -> Array
fn learning_rate(&self) -> Array
The learning rate: the 0-D float32 array in the state.
Source§fn set_learning_rate(&mut self, lr: f32)
fn set_learning_rate(&mut self, lr: f32)
Sets the learning rate array (in a compiled step it is an input: no retrace).
Source§impl Tree for Sgdwhere
Self: Clone,
impl Tree for Sgdwhere
Self: Clone,
Source§fn visit_mut(&mut self, __v: &mut VisitMut<'_>)
fn visit_mut(&mut self, __v: &mut VisitMut<'_>)
Reports every leaf (and child tree and mode flag) to
v mutably, in the order of
Leaves::visit.Source§fn map(&self, f: impl FnMut(&str, &Array) -> Array) -> Self
fn map(&self, f: impl FnMut(&str, &Array) -> Array) -> Self
The tree with every leaf replaced by
f(path, leaf) (same structure; parameters keep
their frozen flag).Source§fn flatten(&self) -> Vec<(String, Array)>
fn flatten(&self) -> Vec<(String, Array)>
The leaves with their paths, in order (MLX’s
tree_flatten).Source§fn unflatten(
&self,
leaves: impl IntoIterator<Item = Array>,
) -> Result<Self, Error>
fn unflatten( &self, leaves: impl IntoIterator<Item = Array>, ) -> Result<Self, Error>
A tree of this structure (
self is the template) with leaves in order: exactly
Tree::len of them, else an error naming both counts.Source§fn load_flat(
&mut self,
named: impl IntoIterator<Item = (String, Array)>,
mode: LoadMode,
) -> Result<(), Error>
fn load_flat( &mut self, named: impl IntoIterator<Item = (String, Array)>, mode: LoadMode, ) -> Result<(), Error>
Replaces leaves by path.
LoadMode::Strict requires exactly the tree’s paths with
matching shapes; LoadMode::Partial assigns the matching names and ignores
unknown ones (shapes must still match). Loaded dtypes are kept. On any problem the
error lists every one (missing, unknown and mismatched names, sorted) and the tree is
unchanged. Duplicate paths (in the tree or in named) are an error.Auto Trait Implementations§
impl !RefUnwindSafe for Sgd
impl !UnwindSafe for Sgd
impl Freeze for Sgd
impl Send for Sgd
impl Sync for Sgd
impl Unpin for Sgd
impl UnsafeUnpin for Sgd
Blanket Implementations§
impl<T> AutoreleaseSafe for Twhere
T: ?Sized,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
Converts
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
Converts
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> IntoResult for Twhere
T: Tree,
impl<T> IntoResult for Twhere
T: Tree,
Source§impl<T> Module for Twhere
T: Tree,
impl<T> Module for Twhere
T: Tree,
Source§fn parameters(&self) -> Vec<(String, Array)>
fn parameters(&self) -> Vec<(String, Array)>
Every leaf (parameters, buffers and plain arrays) with its path, in order.
Source§fn trainable_parameters(&self) -> Vec<(String, Array)>
fn trainable_parameters(&self) -> Vec<(String, Array)>
The trainable leaves with their paths, in order.
Source§fn num_params(&self) -> usize
fn num_params(&self) -> usize
The number of elements over every leaf (MLX’s
tree_flatten total).Source§fn num_trainable(&self) -> usize
fn num_trainable(&self) -> usize
The number of elements over the trainable leaves.
Source§fn summary(&self) -> Summary
fn summary(&self) -> Summary
A table of the leaves (path, shape, dtype, trainable, bytes) with totals.
Source§fn display(&self) -> ModuleDisplay<'_, Self>
fn display(&self) -> ModuleDisplay<'_, Self>
The nested modules with their parameter counts, for printing:
println!("{}", model.display()).Source§fn freeze(&mut self, filter: impl Fn(&str) -> bool) -> Result<usize>
fn freeze(&mut self, filter: impl Fn(&str) -> bool) -> Result<usize>
Freezes the parameters whose path matches
filter, returning how many changed.
A matching plain Array leaf cannot be frozen (only Params can): that is an
error naming the paths, and nothing changes. Buffers are never trainable anyway.Source§fn unfreeze(&mut self, filter: impl Fn(&str) -> bool) -> Result<usize>
fn unfreeze(&mut self, filter: impl Fn(&str) -> bool) -> Result<usize>
Unfreezes the parameters whose path matches
filter, returning how many changed.
Buffers (quantized weights) and integer parameters stay non-trainable.Source§fn train(&mut self, on: bool)
fn train(&mut self, on: bool)
Sets every mode flag of the tree: training (
true, the default) or evaluation
(false, where dropout is the identity), as MLX’s Module.train(mode). (There is
no eval(): it would read like x.eval(), which evaluates.)Source§fn training(&self) -> bool
fn training(&self) -> bool
Whether the tree is in training mode (its first mode flag;
true without any).Source§fn load_weights(&mut self, src: impl WeightSource) -> Result<()>
fn load_weights(&mut self, src: impl WeightSource) -> Result<()>
Replaces leaves by name, strictly (MLX’s
load_weights(strict=True)): every leaf
must be given with its shape and nothing else (loaded dtypes are kept). On any
problem the error lists every missing, unknown and mismatched name, and the model
is unchanged. src is a safetensors path or named arrays.Source§fn load_weights_partial(&mut self, src: impl WeightSource) -> Result<()>
fn load_weights_partial(&mut self, src: impl WeightSource) -> Result<()>
Replaces the leaves given by name (MLX’s
load_weights(strict=False)): unknown
names are ignored, missing leaves keep their values, shapes must match.Source§fn save_weights(&self, path: impl AsRef<Path>) -> Result<()>
fn save_weights(&self, path: impl AsRef<Path>) -> Result<()>
Writes every leaf as safetensors with MLX’s names and dtypes (MLX’s
save_weights), evaluating them. Unloaded placeholders are an error naming them.Source§fn save_trainable(&self, path: impl AsRef<Path>) -> Result<()>
fn save_trainable(&self, path: impl AsRef<Path>) -> Result<()>
Writes only the trainable leaves (an adapter’s weights, say).
Source§fn map_modules<T: Tree>(
&mut self,
filter: impl Fn(&str) -> bool,
f: impl FnMut(&str, &mut T) -> Result<()>,
) -> Result<usize>
fn map_modules<T: Tree>( &mut self, filter: impl Fn(&str) -> bool, f: impl FnMut(&str, &mut T) -> Result<()>, ) -> Result<usize>
Calls
f(path, submodule) on every submodule of type T whose path matches
filter (f may replace it, as when applying LoRA), returning how many. It stops at
the first error from f; submodules changed before it keep their change. Matching
submodules are not searched for further matches.