QuizGenerator 0.4.2__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- QuizGenerator/contentast.py +809 -117
- QuizGenerator/generate.py +219 -11
- QuizGenerator/misc.py +0 -556
- QuizGenerator/mixins.py +50 -29
- QuizGenerator/premade_questions/basic.py +3 -3
- QuizGenerator/premade_questions/cst334/languages.py +183 -175
- QuizGenerator/premade_questions/cst334/math_questions.py +81 -70
- QuizGenerator/premade_questions/cst334/memory_questions.py +262 -165
- QuizGenerator/premade_questions/cst334/persistence_questions.py +83 -60
- QuizGenerator/premade_questions/cst334/process.py +558 -79
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +39 -13
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +61 -36
- QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +29 -10
- QuizGenerator/premade_questions/cst463/gradient_descent/misc.py +2 -2
- QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +60 -43
- QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +173 -326
- QuizGenerator/premade_questions/cst463/models/attention.py +29 -14
- QuizGenerator/premade_questions/cst463/models/cnns.py +32 -20
- QuizGenerator/premade_questions/cst463/models/rnns.py +28 -15
- QuizGenerator/premade_questions/cst463/models/text.py +29 -15
- QuizGenerator/premade_questions/cst463/models/weight_counting.py +38 -30
- QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +91 -111
- QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +128 -55
- QuizGenerator/question.py +114 -20
- QuizGenerator/quiz.py +81 -24
- QuizGenerator/regenerate.py +98 -29
- {quizgenerator-0.4.2.dist-info → quizgenerator-0.6.0.dist-info}/METADATA +1 -1
- {quizgenerator-0.4.2.dist-info → quizgenerator-0.6.0.dist-info}/RECORD +31 -33
- QuizGenerator/README.md +0 -5
- QuizGenerator/logging.yaml +0 -55
- {quizgenerator-0.4.2.dist-info → quizgenerator-0.6.0.dist-info}/WHEEL +0 -0
- {quizgenerator-0.4.2.dist-info → quizgenerator-0.6.0.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.4.2.dist-info → quizgenerator-0.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -8,8 +8,8 @@ import numpy as np
|
|
|
8
8
|
import sympy as sp
|
|
9
9
|
from typing import List, Tuple, Dict, Any
|
|
10
10
|
|
|
11
|
-
from QuizGenerator.contentast import ContentAST
|
|
12
|
-
from QuizGenerator.question import Question,
|
|
11
|
+
from QuizGenerator.contentast import ContentAST, AnswerTypes
|
|
12
|
+
from QuizGenerator.question import Question, QuestionRegistry
|
|
13
13
|
from QuizGenerator.mixins import TableQuestionMixin, BodyTemplatesMixin
|
|
14
14
|
|
|
15
15
|
# Import gradient descent utilities
|
|
@@ -85,16 +85,18 @@ class ParameterCountingQuestion(Question):
|
|
|
85
85
|
"""Create answer fields."""
|
|
86
86
|
self.answers = {}
|
|
87
87
|
|
|
88
|
-
self.answers["total_weights"] =
|
|
88
|
+
self.answers["total_weights"] = AnswerTypes.Int(self.total_weights, label="Total weights")
|
|
89
89
|
|
|
90
90
|
if self.include_biases:
|
|
91
|
-
self.answers["total_biases"] =
|
|
92
|
-
self.answers["total_params"] =
|
|
91
|
+
self.answers["total_biases"] = AnswerTypes.Int(self.total_biases, label="Total biases")
|
|
92
|
+
self.answers["total_params"] = AnswerTypes.Int(self.total_params, label="Total trainable parameters")
|
|
93
93
|
else:
|
|
94
|
-
self.answers["total_params"] =
|
|
94
|
+
self.answers["total_params"] = AnswerTypes.Int(self.total_params, label="Total trainable parameters")
|
|
95
95
|
|
|
96
|
-
def
|
|
96
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
97
|
+
"""Build question body and collect answers."""
|
|
97
98
|
body = ContentAST.Section()
|
|
99
|
+
answers = []
|
|
98
100
|
|
|
99
101
|
# Question description
|
|
100
102
|
body.add_element(ContentAST.Paragraph([
|
|
@@ -122,27 +124,36 @@ class ParameterCountingQuestion(Question):
|
|
|
122
124
|
table_data = []
|
|
123
125
|
table_data.append(["Parameter Type", "Count"])
|
|
124
126
|
|
|
127
|
+
answers.append(self.answers["total_weights"])
|
|
125
128
|
table_data.append([
|
|
126
129
|
"Total weights (connections between layers)",
|
|
127
|
-
|
|
130
|
+
self.answers["total_weights"]
|
|
128
131
|
])
|
|
129
132
|
|
|
130
133
|
if self.include_biases:
|
|
134
|
+
answers.append(self.answers["total_biases"])
|
|
131
135
|
table_data.append([
|
|
132
136
|
"Total biases",
|
|
133
|
-
|
|
137
|
+
self.answers["total_biases"]
|
|
134
138
|
])
|
|
135
139
|
|
|
140
|
+
answers.append(self.answers["total_params"])
|
|
136
141
|
table_data.append([
|
|
137
142
|
"Total trainable parameters",
|
|
138
|
-
|
|
143
|
+
self.answers["total_params"]
|
|
139
144
|
])
|
|
140
145
|
|
|
141
146
|
body.add_element(ContentAST.Table(data=table_data))
|
|
142
147
|
|
|
148
|
+
return body, answers
|
|
149
|
+
|
|
150
|
+
def get_body(self, **kwargs) -> ContentAST.Section:
|
|
151
|
+
"""Build question body (backward compatible interface)."""
|
|
152
|
+
body, _ = self._get_body(**kwargs)
|
|
143
153
|
return body
|
|
144
154
|
|
|
145
|
-
def
|
|
155
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
156
|
+
"""Build question explanation."""
|
|
146
157
|
explanation = ContentAST.Section()
|
|
147
158
|
|
|
148
159
|
explanation.add_element(ContentAST.Paragraph([
|
|
@@ -150,7 +161,7 @@ class ParameterCountingQuestion(Question):
|
|
|
150
161
|
]))
|
|
151
162
|
|
|
152
163
|
explanation.add_element(ContentAST.Paragraph([
|
|
153
|
-
"
|
|
164
|
+
ContentAST.Text("Weights calculation:", emphasis=True)
|
|
154
165
|
]))
|
|
155
166
|
|
|
156
167
|
for i in range(len(self.layer_sizes) - 1):
|
|
@@ -174,7 +185,7 @@ class ParameterCountingQuestion(Question):
|
|
|
174
185
|
|
|
175
186
|
if self.include_biases:
|
|
176
187
|
explanation.add_element(ContentAST.Paragraph([
|
|
177
|
-
"
|
|
188
|
+
ContentAST.Text("Biases calculation:", emphasis=True)
|
|
178
189
|
]))
|
|
179
190
|
|
|
180
191
|
for i in range(len(self.layer_sizes) - 1):
|
|
@@ -194,7 +205,7 @@ class ParameterCountingQuestion(Question):
|
|
|
194
205
|
]))
|
|
195
206
|
|
|
196
207
|
explanation.add_element(ContentAST.Paragraph([
|
|
197
|
-
"
|
|
208
|
+
ContentAST.Text("Total trainable parameters:", emphasis=True)
|
|
198
209
|
]))
|
|
199
210
|
|
|
200
211
|
if self.include_biases:
|
|
@@ -208,6 +219,11 @@ class ParameterCountingQuestion(Question):
|
|
|
208
219
|
inline=False
|
|
209
220
|
))
|
|
210
221
|
|
|
222
|
+
return explanation, []
|
|
223
|
+
|
|
224
|
+
def get_explanation(self, **kwargs) -> ContentAST.Section:
|
|
225
|
+
"""Build question explanation (backward compatible interface)."""
|
|
226
|
+
explanation, _ = self._get_explanation(**kwargs)
|
|
211
227
|
return explanation
|
|
212
228
|
|
|
213
229
|
|
|
@@ -315,15 +331,17 @@ class ActivationFunctionComputationQuestion(Question):
|
|
|
315
331
|
|
|
316
332
|
if self.activation == self.ACTIVATION_SOFTMAX:
|
|
317
333
|
# Softmax: single vector answer
|
|
318
|
-
self.answers["output"] =
|
|
334
|
+
self.answers["output"] = AnswerTypes.Vector(self.output_vector, label="Output vector")
|
|
319
335
|
else:
|
|
320
336
|
# Element-wise: individual answers
|
|
321
337
|
for i, output in enumerate(self.output_vector):
|
|
322
338
|
key = f"output_{i}"
|
|
323
|
-
self.answers[key] =
|
|
339
|
+
self.answers[key] = AnswerTypes.Float(float(output), label=f"Output for input {self.input_vector[i]:.1f}")
|
|
324
340
|
|
|
325
|
-
def
|
|
341
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
342
|
+
"""Build question body and collect answers."""
|
|
326
343
|
body = ContentAST.Section()
|
|
344
|
+
answers = []
|
|
327
345
|
|
|
328
346
|
# Question description
|
|
329
347
|
body.add_element(ContentAST.Paragraph([
|
|
@@ -349,9 +367,10 @@ class ActivationFunctionComputationQuestion(Question):
|
|
|
349
367
|
"Compute the output vector:"
|
|
350
368
|
]))
|
|
351
369
|
|
|
370
|
+
answers.append(self.answers["output"])
|
|
352
371
|
table_data = []
|
|
353
372
|
table_data.append(["Output Vector"])
|
|
354
|
-
table_data.append([
|
|
373
|
+
table_data.append([self.answers["output"]])
|
|
355
374
|
|
|
356
375
|
body.add_element(ContentAST.Table(data=table_data))
|
|
357
376
|
|
|
@@ -364,16 +383,24 @@ class ActivationFunctionComputationQuestion(Question):
|
|
|
364
383
|
table_data.append(["Input", "Output"])
|
|
365
384
|
|
|
366
385
|
for i, x in enumerate(self.input_vector):
|
|
386
|
+
answer = self.answers[f"output_{i}"]
|
|
387
|
+
answers.append(answer)
|
|
367
388
|
table_data.append([
|
|
368
389
|
ContentAST.Equation(f"{x:.1f}", inline=True),
|
|
369
|
-
|
|
390
|
+
answer
|
|
370
391
|
])
|
|
371
392
|
|
|
372
393
|
body.add_element(ContentAST.Table(data=table_data))
|
|
373
394
|
|
|
395
|
+
return body, answers
|
|
396
|
+
|
|
397
|
+
def get_body(self, **kwargs) -> ContentAST.Section:
|
|
398
|
+
"""Build question body (backward compatible interface)."""
|
|
399
|
+
body, _ = self._get_body(**kwargs)
|
|
374
400
|
return body
|
|
375
401
|
|
|
376
|
-
def
|
|
402
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
403
|
+
"""Build question explanation."""
|
|
377
404
|
explanation = ContentAST.Section()
|
|
378
405
|
|
|
379
406
|
explanation.add_element(ContentAST.Paragraph([
|
|
@@ -382,7 +409,7 @@ class ActivationFunctionComputationQuestion(Question):
|
|
|
382
409
|
|
|
383
410
|
if self.activation == self.ACTIVATION_SOFTMAX:
|
|
384
411
|
explanation.add_element(ContentAST.Paragraph([
|
|
385
|
-
"
|
|
412
|
+
ContentAST.Text("Softmax computation:", emphasis=True)
|
|
386
413
|
]))
|
|
387
414
|
|
|
388
415
|
# Show exponentials
|
|
@@ -419,7 +446,7 @@ class ActivationFunctionComputationQuestion(Question):
|
|
|
419
446
|
|
|
420
447
|
else:
|
|
421
448
|
explanation.add_element(ContentAST.Paragraph([
|
|
422
|
-
"
|
|
449
|
+
ContentAST.Text("Element-wise computation:", emphasis=True)
|
|
423
450
|
]))
|
|
424
451
|
|
|
425
452
|
for i, (x, y) in enumerate(zip(self.input_vector, self.output_vector)):
|
|
@@ -441,6 +468,11 @@ class ActivationFunctionComputationQuestion(Question):
|
|
|
441
468
|
inline=False
|
|
442
469
|
))
|
|
443
470
|
|
|
471
|
+
return explanation, []
|
|
472
|
+
|
|
473
|
+
def get_explanation(self, **kwargs) -> ContentAST.Section:
|
|
474
|
+
"""Build question explanation (backward compatible interface)."""
|
|
475
|
+
explanation, _ = self._get_explanation(**kwargs)
|
|
444
476
|
return explanation
|
|
445
477
|
|
|
446
478
|
|
|
@@ -513,14 +545,16 @@ class RegularizationCalculationQuestion(Question):
|
|
|
513
545
|
"""Create answer fields."""
|
|
514
546
|
self.answers = {}
|
|
515
547
|
|
|
516
|
-
self.answers["prediction"] =
|
|
517
|
-
self.answers["base_loss"] =
|
|
518
|
-
self.answers["l2_penalty"] =
|
|
519
|
-
self.answers["total_loss"] =
|
|
520
|
-
self.answers["grad_total_w0"] =
|
|
548
|
+
self.answers["prediction"] = AnswerTypes.Float(float(self.prediction), label="Prediction ŷ")
|
|
549
|
+
self.answers["base_loss"] = AnswerTypes.Float(float(self.base_loss), label="Base MSE loss")
|
|
550
|
+
self.answers["l2_penalty"] = AnswerTypes.Float(float(self.l2_penalty), label="L2 penalty")
|
|
551
|
+
self.answers["total_loss"] = AnswerTypes.Float(float(self.total_loss), label="Total loss")
|
|
552
|
+
self.answers["grad_total_w0"] = AnswerTypes.Float(float(self.grad_total_w0), label="Gradient ∂L/∂w₀")
|
|
521
553
|
|
|
522
|
-
def
|
|
554
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
555
|
+
"""Build question body and collect answers."""
|
|
523
556
|
body = ContentAST.Section()
|
|
557
|
+
answers = []
|
|
524
558
|
|
|
525
559
|
# Question description
|
|
526
560
|
body.add_element(ContentAST.Paragraph([
|
|
@@ -570,36 +604,47 @@ class RegularizationCalculationQuestion(Question):
|
|
|
570
604
|
table_data = []
|
|
571
605
|
table_data.append(["Calculation", "Value"])
|
|
572
606
|
|
|
607
|
+
answers.append(self.answers["prediction"])
|
|
573
608
|
table_data.append([
|
|
574
609
|
ContentAST.Paragraph(["Prediction ", ContentAST.Equation(r"\hat{y}", inline=True)]),
|
|
575
|
-
|
|
610
|
+
self.answers["prediction"]
|
|
576
611
|
])
|
|
577
612
|
|
|
613
|
+
answers.append(self.answers["base_loss"])
|
|
578
614
|
table_data.append([
|
|
579
615
|
ContentAST.Paragraph(["Base MSE loss: ", ContentAST.Equation(r"L_{base} = (1/2)(y - \hat{y})^2", inline=True)]),
|
|
580
|
-
|
|
616
|
+
self.answers["base_loss"]
|
|
581
617
|
])
|
|
582
618
|
|
|
619
|
+
answers.append(self.answers["l2_penalty"])
|
|
583
620
|
table_data.append([
|
|
584
621
|
ContentAST.Paragraph(["L2 penalty: ", ContentAST.Equation(r"L_{reg} = (\lambda/2)\sum w_i^2", inline=True)]),
|
|
585
|
-
|
|
622
|
+
self.answers["l2_penalty"]
|
|
586
623
|
])
|
|
587
624
|
|
|
625
|
+
answers.append(self.answers["total_loss"])
|
|
588
626
|
table_data.append([
|
|
589
627
|
ContentAST.Paragraph(["Total loss: ", ContentAST.Equation(r"L_{total} = L_{base} + L_{reg}", inline=True)]),
|
|
590
|
-
|
|
628
|
+
self.answers["total_loss"]
|
|
591
629
|
])
|
|
592
630
|
|
|
631
|
+
answers.append(self.answers["grad_total_w0"])
|
|
593
632
|
table_data.append([
|
|
594
633
|
ContentAST.Paragraph(["Gradient: ", ContentAST.Equation(r"\frac{\partial L_{total}}{\partial w_0}", inline=True)]),
|
|
595
|
-
|
|
634
|
+
self.answers["grad_total_w0"]
|
|
596
635
|
])
|
|
597
636
|
|
|
598
637
|
body.add_element(ContentAST.Table(data=table_data))
|
|
599
638
|
|
|
639
|
+
return body, answers
|
|
640
|
+
|
|
641
|
+
def get_body(self, **kwargs) -> ContentAST.Section:
|
|
642
|
+
"""Build question body (backward compatible interface)."""
|
|
643
|
+
body, _ = self._get_body(**kwargs)
|
|
600
644
|
return body
|
|
601
645
|
|
|
602
|
-
def
|
|
646
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
647
|
+
"""Build question explanation."""
|
|
603
648
|
explanation = ContentAST.Section()
|
|
604
649
|
|
|
605
650
|
explanation.add_element(ContentAST.Paragraph([
|
|
@@ -608,7 +653,7 @@ class RegularizationCalculationQuestion(Question):
|
|
|
608
653
|
|
|
609
654
|
# Step 1: Forward pass
|
|
610
655
|
explanation.add_element(ContentAST.Paragraph([
|
|
611
|
-
"
|
|
656
|
+
ContentAST.Text("Step 1: Compute prediction", emphasis=True)
|
|
612
657
|
]))
|
|
613
658
|
|
|
614
659
|
terms = []
|
|
@@ -626,7 +671,7 @@ class RegularizationCalculationQuestion(Question):
|
|
|
626
671
|
|
|
627
672
|
# Step 2: Base loss
|
|
628
673
|
explanation.add_element(ContentAST.Paragraph([
|
|
629
|
-
"
|
|
674
|
+
ContentAST.Text("Step 2: Compute base MSE loss", emphasis=True)
|
|
630
675
|
]))
|
|
631
676
|
|
|
632
677
|
explanation.add_element(ContentAST.Equation(
|
|
@@ -636,7 +681,7 @@ class RegularizationCalculationQuestion(Question):
|
|
|
636
681
|
|
|
637
682
|
# Step 3: L2 penalty
|
|
638
683
|
explanation.add_element(ContentAST.Paragraph([
|
|
639
|
-
"
|
|
684
|
+
ContentAST.Text("Step 3: Compute L2 penalty", emphasis=True)
|
|
640
685
|
]))
|
|
641
686
|
|
|
642
687
|
weight_squares = [f"{w:.1f}^2" for w in self.weights]
|
|
@@ -649,7 +694,7 @@ class RegularizationCalculationQuestion(Question):
|
|
|
649
694
|
|
|
650
695
|
# Step 4: Total loss
|
|
651
696
|
explanation.add_element(ContentAST.Paragraph([
|
|
652
|
-
"
|
|
697
|
+
ContentAST.Text("Step 4: Compute total loss", emphasis=True)
|
|
653
698
|
]))
|
|
654
699
|
|
|
655
700
|
explanation.add_element(ContentAST.Equation(
|
|
@@ -659,7 +704,7 @@ class RegularizationCalculationQuestion(Question):
|
|
|
659
704
|
|
|
660
705
|
# Step 5: Gradient with regularization
|
|
661
706
|
explanation.add_element(ContentAST.Paragraph([
|
|
662
|
-
"
|
|
707
|
+
ContentAST.Text("Step 5: Compute gradient with regularization", emphasis=True)
|
|
663
708
|
]))
|
|
664
709
|
|
|
665
710
|
explanation.add_element(ContentAST.Paragraph([
|
|
@@ -688,6 +733,11 @@ class RegularizationCalculationQuestion(Question):
|
|
|
688
733
|
" to the gradient, pushing the weight toward zero."
|
|
689
734
|
]))
|
|
690
735
|
|
|
736
|
+
return explanation, []
|
|
737
|
+
|
|
738
|
+
def get_explanation(self, **kwargs) -> ContentAST.Section:
|
|
739
|
+
"""Build question explanation (backward compatible interface)."""
|
|
740
|
+
explanation, _ = self._get_explanation(**kwargs)
|
|
691
741
|
return explanation
|
|
692
742
|
|
|
693
743
|
|
|
@@ -767,17 +817,19 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
|
|
|
767
817
|
self.answers = {}
|
|
768
818
|
|
|
769
819
|
# New velocity
|
|
770
|
-
self.answers["velocity"] =
|
|
820
|
+
self.answers["velocity"] = AnswerTypes.Vector(self.new_velocity, label="New velocity")
|
|
771
821
|
|
|
772
822
|
# New weights with momentum
|
|
773
|
-
self.answers["weights_momentum"] =
|
|
823
|
+
self.answers["weights_momentum"] = AnswerTypes.Vector(self.new_weights, label="Weights (momentum)")
|
|
774
824
|
|
|
775
825
|
# Vanilla SGD weights for comparison
|
|
776
826
|
if self.show_vanilla_sgd:
|
|
777
|
-
self.answers["weights_sgd"] =
|
|
827
|
+
self.answers["weights_sgd"] = AnswerTypes.Vector(self.sgd_weights, label="Weights (vanilla SGD)")
|
|
778
828
|
|
|
779
|
-
def
|
|
829
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
830
|
+
"""Build question body and collect answers."""
|
|
780
831
|
body = ContentAST.Section()
|
|
832
|
+
answers = []
|
|
781
833
|
|
|
782
834
|
# Question description
|
|
783
835
|
body.add_element(ContentAST.Paragraph([
|
|
@@ -800,14 +852,14 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
|
|
|
800
852
|
|
|
801
853
|
# Current state
|
|
802
854
|
body.add_element(ContentAST.Paragraph([
|
|
803
|
-
"
|
|
855
|
+
ContentAST.Text("Current optimization state:", emphasis=True)
|
|
804
856
|
]))
|
|
805
857
|
|
|
806
858
|
body.add_element(ContentAST.Paragraph([
|
|
807
859
|
"Current weights: ",
|
|
808
860
|
ContentAST.Equation(f"{format_vector(self.current_weights)}", inline=True)
|
|
809
861
|
]))
|
|
810
|
-
|
|
862
|
+
|
|
811
863
|
body.add_element(ContentAST.Paragraph([
|
|
812
864
|
"Previous velocity: ",
|
|
813
865
|
ContentAST.Equation(f"{format_vector(self.prev_velocity)}", inline=True)
|
|
@@ -815,7 +867,7 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
|
|
|
815
867
|
|
|
816
868
|
# Hyperparameters
|
|
817
869
|
body.add_element(ContentAST.Paragraph([
|
|
818
|
-
"
|
|
870
|
+
ContentAST.Text("Hyperparameters:", emphasis=True)
|
|
819
871
|
]))
|
|
820
872
|
|
|
821
873
|
body.add_element(ContentAST.Paragraph([
|
|
@@ -837,30 +889,39 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
|
|
|
837
889
|
table_data = []
|
|
838
890
|
table_data.append(["Update Type", "Formula", "Result"])
|
|
839
891
|
|
|
892
|
+
answers.append(self.answers["velocity"])
|
|
840
893
|
table_data.append([
|
|
841
894
|
"New velocity",
|
|
842
895
|
ContentAST.Equation(r"v' = \beta v + (1-\beta)\nabla f", inline=True),
|
|
843
|
-
|
|
896
|
+
self.answers["velocity"]
|
|
844
897
|
])
|
|
845
898
|
|
|
899
|
+
answers.append(self.answers["weights_momentum"])
|
|
846
900
|
table_data.append([
|
|
847
901
|
"Weights (momentum)",
|
|
848
902
|
ContentAST.Equation(r"w' = w - \alpha v'", inline=True),
|
|
849
|
-
|
|
903
|
+
self.answers["weights_momentum"]
|
|
850
904
|
])
|
|
851
905
|
|
|
852
906
|
if self.show_vanilla_sgd:
|
|
907
|
+
answers.append(self.answers["weights_sgd"])
|
|
853
908
|
table_data.append([
|
|
854
909
|
"Weights (vanilla SGD)",
|
|
855
910
|
ContentAST.Equation(r"w' = w - \alpha \nabla f", inline=True),
|
|
856
|
-
|
|
911
|
+
self.answers["weights_sgd"]
|
|
857
912
|
])
|
|
858
913
|
|
|
859
914
|
body.add_element(ContentAST.Table(data=table_data))
|
|
860
915
|
|
|
916
|
+
return body, answers
|
|
917
|
+
|
|
918
|
+
def get_body(self, **kwargs) -> ContentAST.Section:
|
|
919
|
+
"""Build question body (backward compatible interface)."""
|
|
920
|
+
body, _ = self._get_body(**kwargs)
|
|
861
921
|
return body
|
|
862
922
|
|
|
863
|
-
def
|
|
923
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
924
|
+
"""Build question explanation."""
|
|
864
925
|
explanation = ContentAST.Section()
|
|
865
926
|
|
|
866
927
|
explanation.add_element(ContentAST.Paragraph([
|
|
@@ -870,7 +931,7 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
|
|
|
870
931
|
|
|
871
932
|
# Step 1: Calculate new velocity
|
|
872
933
|
explanation.add_element(ContentAST.Paragraph([
|
|
873
|
-
"
|
|
934
|
+
ContentAST.Text("Step 1: Update velocity using momentum", emphasis=True)
|
|
874
935
|
]))
|
|
875
936
|
|
|
876
937
|
explanation.add_element(ContentAST.Paragraph([
|
|
@@ -883,17 +944,24 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
|
|
|
883
944
|
))
|
|
884
945
|
|
|
885
946
|
# Show calculation for each component
|
|
947
|
+
digits = ContentAST.Answer.DEFAULT_ROUNDING_DIGITS
|
|
886
948
|
for i in range(self.num_variables):
|
|
887
949
|
var_name = f"x_{i}"
|
|
950
|
+
# Round all intermediate values to avoid floating point precision issues
|
|
951
|
+
beta_times_v = round(self.momentum_beta * self.prev_velocity[i], digits)
|
|
952
|
+
one_minus_beta = round(1 - self.momentum_beta, digits)
|
|
953
|
+
one_minus_beta_times_grad = round((1 - self.momentum_beta) * self.gradients[i], digits)
|
|
954
|
+
|
|
888
955
|
explanation.add_element(ContentAST.Equation(
|
|
889
|
-
f"v'[{i}] = {self.momentum_beta} \\times {self.prev_velocity[i]:.
|
|
890
|
-
f"{
|
|
956
|
+
f"v'[{i}] = {self.momentum_beta} \\times {self.prev_velocity[i]:.{digits}f} + "
|
|
957
|
+
f"{one_minus_beta:.{digits}f} \\times {self.gradients[i]:.{digits}f} = "
|
|
958
|
+
f"{beta_times_v:.{digits}f} + {one_minus_beta_times_grad:.{digits}f} = {self.new_velocity[i]:.{digits}f}",
|
|
891
959
|
inline=False
|
|
892
960
|
))
|
|
893
961
|
|
|
894
962
|
# Step 2: Update weights with momentum
|
|
895
963
|
explanation.add_element(ContentAST.Paragraph([
|
|
896
|
-
"
|
|
964
|
+
ContentAST.Text("Step 2: Update weights using new velocity", emphasis=True)
|
|
897
965
|
]))
|
|
898
966
|
|
|
899
967
|
explanation.add_element(ContentAST.Equation(
|
|
@@ -910,7 +978,7 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
|
|
|
910
978
|
# Comparison with vanilla SGD
|
|
911
979
|
if self.show_vanilla_sgd:
|
|
912
980
|
explanation.add_element(ContentAST.Paragraph([
|
|
913
|
-
"
|
|
981
|
+
ContentAST.Text("Comparison with vanilla SGD:", emphasis=True)
|
|
914
982
|
]))
|
|
915
983
|
|
|
916
984
|
explanation.add_element(ContentAST.Paragraph([
|
|
@@ -933,4 +1001,9 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
|
|
|
933
1001
|
"which can help accelerate learning and smooth out noisy gradients."
|
|
934
1002
|
]))
|
|
935
1003
|
|
|
1004
|
+
return explanation, []
|
|
1005
|
+
|
|
1006
|
+
def get_explanation(self, **kwargs) -> ContentAST.Section:
|
|
1007
|
+
"""Build question explanation (backward compatible interface)."""
|
|
1008
|
+
explanation, _ = self._get_explanation(**kwargs)
|
|
936
1009
|
return explanation
|