Skip to content

Commit ed40437

Browse files
authored
Add after method for drop(n).next(); easier for non-Scala folks (#3)
1 parent 1be7693 commit ed40437

11 files changed

Lines changed: 39 additions & 42 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ Training the model reduces to a termination condition on this iterator; here aft
114114
A model checkpointer serializes the final train state object.
115115

116116
```scala
117-
val finalState = trainTrajectory.drop(numIterations).next()
117+
val finalState = trainTrajectory.after(numIterations)
118118

119119
TensorTreeCheckpointer.newIn(checkpointRoot).save(finalState, numIterations)
120120
```
@@ -151,7 +151,7 @@ The user code composes these core modules into custom architectures given the us
151151
| `deepwit.init` | Xavier/Glorot normal and uniform, for matrices and vectors |
152152
| `deepwit.regularization` | `Perturbation` — thinning (dropout) as a mutation of the weights that *read* a feature |
153153
| `deepwit.optimizer` | `LearningRateSchedule` (constant, linear warmup, cosine decay), `LearningRateScheduler`, `clipGlobalNorm` |
154-
| `deepwit.training` | `Monitor` (step, loss, throughput, learning rate), `tapEvery` |
154+
| `deepwit.training` | `Monitor` (step, loss, throughput, learning rate), `tapEvery`, `after` |
155155
| `deepwit.checkpointing` | `TensorTreeCheckpointer` — save and load any `TensorTree` by iteration |
156156

157157
## Relationship to DimWit

core/src/main/scala/deepwit/training/package.scala

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@ extension [T](it: Iterator[T])
1010
if id > 0 && id % n == 0 then f(t, id)
1111
.map(_._1)
1212

13-
extension [T](it: LazyList[T])
14-
15-
def tapEvery(n: Int)(f: (T, Int) => Unit): LazyList[T] =
16-
it
17-
.zipWithIndex
18-
.tapEach: (t, id) =>
19-
if id > 0 && id % n == 0 then f(t, id)
20-
.map(_._1)
13+
/** The state after n iterations: Advances the iterator n steps and returns the resulting element */
14+
def after(n: Int): T =
15+
require(n >= 0, s"A number of steps must not be negative, but was $n.")
16+
it.drop(n).next()

core/src/test/scala/deepwit/training/TapEverySuite.scala

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,21 @@ class TapEverySuite extends AnyFunSpec with Matchers:
2222
Iterator.from(0).tapEvery(1)((_, id) => seen += id).take(3).toList
2323
seen.toList shouldBe List(1, 2)
2424

25-
describe("LazyList.tapEvery"):
25+
describe("Iterator.after"):
2626

27-
it("fires at every n-th index but not at zero"):
28-
val seen = ListBuffer.empty[(String, Int)]
29-
LazyList.from(0).map(i => s"e$i").tapEvery(3)((t, id) => seen += ((t, id))).take(10).toList
30-
seen.toList shouldBe List(("e3", 3), ("e6", 6), ("e9", 9))
27+
it("counts from zero, so the first element is the state after no steps"):
28+
Iterator.from(0).after(0) shouldBe 0
3129

32-
it("stays lazy until the elements are forced"):
33-
val seen = ListBuffer.empty[Int]
34-
val tapped = LazyList.from(0).tapEvery(1)((_, id) => seen += id)
35-
seen.toList shouldBe empty
36-
tapped.take(3).toList shouldBe List(0, 1, 2)
30+
it("returns the element that many steps in"):
31+
Iterator.from(0).after(3) shouldBe 3
32+
33+
it("advances the iterator past what it returns"):
34+
val trajectory = Iterator.from(0)
35+
trajectory.after(3) shouldBe 3
36+
trajectory.next() shouldBe 4
37+
38+
it("throws when the iterator ends first"):
39+
a[NoSuchElementException] should be thrownBy Iterator(0, 1).after(5)
40+
41+
it("rejects a negative number of steps"):
42+
an[IllegalArgumentException] should be thrownBy Iterator.from(0).after(-1)

examples/src/main/scala/deepwit/examples/autoencoder/AutoEncoderTrain.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import dimwit.Conversions.given
66
import deepwit.examples.dataset.MNISTLoader
77
import MNISTLoader.TestSample
88

9-
import deepwit.training.{Monitor, tapEvery}
9+
import deepwit.training.{Monitor, after, tapEvery}
1010
import deepwit.checkpointing.TensorTreeCheckpointer
1111
import deepwit.loss.BinaryCrossEntropy
1212
import dimwit.optimizer.{Adam, AdamState}
@@ -83,7 +83,6 @@ def train(): Unit =
8383
case (state, step) =>
8484
checkpointer.save(state, step)
8585
println(s"Checkpoint saved at epoch $step")
86-
.drop(numIterations)
87-
.next()
86+
.after(numIterations)
8887

8988
println(s"Done. Wrote ${checkpointer.rootPath}.")

examples/src/main/scala/deepwit/examples/gpt/GPTTrain.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import deepwit.loss.CategoricalCrossEntropy
44

55
import dimwit.*
66
import dimwit.Conversions.given
7-
import deepwit.training.{Monitor, tapEvery}
7+
import deepwit.training.{Monitor, after, tapEvery}
88
import deepwit.optimizer.*
99
import dimwit.optimizer.{AdamW, Adam, AdamState}
1010
import dimwit.TreeOf.ops.*
@@ -186,5 +186,4 @@ import Config.*
186186
logger.save(state, step)
187187
println(s"Checkpoint saved")
188188
println("-" * 30)
189-
.drop(1_000_000_000)
190-
.next()
189+
.after(1_000_000_000)

examples/src/main/scala/deepwit/examples/mnistClassification/MNistCNNTrain.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import deepwit.loss.CategoricalCrossEntropy
88

99
import deepwit.examples.dataset.{MNISTLoader, MNISTBatchSample}
1010
import dimwit.optimizer.GradientDescentState
11-
import deepwit.training.{Monitor, tapEvery}
11+
import deepwit.training.{Monitor, after, tapEvery}
1212
import deepwit.checkpointing.TensorTreeCheckpointer
1313

1414
case class TrainState(
@@ -81,7 +81,6 @@ def train(): Unit =
8181
case (state, step) =>
8282
checkpointer.save(state, step)
8383
println(s"Checkpoint saved at epoch $step")
84-
.drop(numIterations)
85-
.next()
84+
.after(numIterations)
8685

8786
println(s"Done. Wrote ${checkpointer.rootPath}.")

examples/src/main/scala/deepwit/examples/neuralImage/NeuralImageTrain.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import dimwit.*
44
import dimwit.Conversions.given
55
import dimwit.optimizer.{Adam, AdamState}
66

7-
import deepwit.training.{Monitor, tapEvery}
7+
import deepwit.training.{Monitor, after, tapEvery}
88
import deepwit.checkpointing.TensorTreeCheckpointer
99
import deepwit.loss.SquaredError
1010

@@ -103,8 +103,7 @@ def train(): Unit =
103103
val finalState = trainTrajectory
104104
.tapEvery(100):
105105
case (state, step) => println(trainMonitor.report(step, state))
106-
.drop(numIterations)
107-
.next()
106+
.after(numIterations)
108107

109108
// -- Save final state --
110109

examples/src/main/scala/deepwit/examples/regression/Regression.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import io.circe.Json
99
import plotwit.*
1010
import plotwit.PlotTargets.desktopBrowser
1111

12+
import deepwit.training.after
1213
import deepwit.activation.gelu
1314
import deepwit.base.{AffineFormLayer, AffineLayer}
1415
import deepwit.checkpointing.TensorTreeCheckpointer
@@ -104,8 +105,7 @@ def train(): Unit =
104105
// -- Run train trajectory --
105106

106107
val finalState = trainTrajectory
107-
.drop(numIterations)
108-
.next()
108+
.after(numIterations)
109109

110110
// -- Save the fitted state --
111111

examples/src/main/scala/deepwit/examples/thinning/MoonsMLPTrain.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import dimwit.Conversions.given
88
import dimwit.optimizer.{Adam, AdamState}
99

1010
import deepwit.loss.CategoricalCrossEntropy
11-
import deepwit.training.{Monitor, tapEvery}
11+
import deepwit.training.{Monitor, after, tapEvery}
1212
import deepwit.checkpointing.TensorTreeCheckpointer
1313

1414
case class TrainState(
@@ -90,8 +90,7 @@ def train(): Unit =
9090
case (state, step) =>
9191
checkpointer.save(state, step)
9292
println(s"Checkpoint saved at step $step")
93-
.drop(numIterations)
94-
.next()
93+
.after(numIterations)
9594

9695
println(f"Final cost: ${finalState.lastCost.item}%.6f")
9796
println(s"Done. Wrote ${checkpointer.rootPath}.")

examples/src/main/scala/deepwit/examples/variationalAutoencoder/VariationalAutoencoderTrain.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import deepwit.examples.dataset.MNISTLoader
88

99
import deepwit.checkpointing.TensorTreeCheckpointer
1010
import deepwit.loss.BinaryCrossEntropy
11-
import deepwit.training.{Monitor, tapEvery}
11+
import deepwit.training.{Monitor, after, tapEvery}
1212

1313
case class TrainState(
1414
params: VariationalAutoencoder.Params,
@@ -101,8 +101,7 @@ def train(): Unit =
101101
case (state, step) =>
102102
checkpointer.save(state, step)
103103
println(s"Checkpoint saved at step $step")
104-
.drop(numIterations)
105-
.next()
104+
.after(numIterations)
106105

107106
println(f"Final cost: ${finalState.lastCost.item}%.6f")
108107
println(s"Done. Wrote ${checkpointer.rootPath}.")

0 commit comments

Comments
 (0)