QuizGenerator 0.7.1__py3-none-any.whl → 0.8.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 +6 -6
- QuizGenerator/generate.py +2 -1
- QuizGenerator/mixins.py +14 -100
- QuizGenerator/premade_questions/basic.py +24 -29
- QuizGenerator/premade_questions/cst334/languages.py +100 -99
- QuizGenerator/premade_questions/cst334/math_questions.py +112 -122
- QuizGenerator/premade_questions/cst334/memory_questions.py +621 -621
- QuizGenerator/premade_questions/cst334/persistence_questions.py +137 -163
- QuizGenerator/premade_questions/cst334/process.py +312 -322
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +34 -35
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +41 -36
- QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +48 -41
- QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +285 -520
- QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +149 -126
- QuizGenerator/premade_questions/cst463/models/attention.py +44 -50
- QuizGenerator/premade_questions/cst463/models/cnns.py +43 -47
- QuizGenerator/premade_questions/cst463/models/matrices.py +61 -11
- QuizGenerator/premade_questions/cst463/models/rnns.py +48 -50
- QuizGenerator/premade_questions/cst463/models/text.py +65 -67
- QuizGenerator/premade_questions/cst463/models/weight_counting.py +47 -46
- QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +100 -156
- QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +93 -141
- QuizGenerator/question.py +273 -202
- QuizGenerator/quiz.py +8 -5
- QuizGenerator/regenerate.py +14 -6
- {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.0.dist-info}/METADATA +30 -2
- {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.0.dist-info}/RECORD +30 -30
- {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.0.dist-info}/WHEEL +0 -0
- {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.0.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
import logging
|
|
3
3
|
import math
|
|
4
|
+
import random
|
|
4
5
|
import keras
|
|
5
6
|
import numpy as np
|
|
6
7
|
from typing import List, Tuple
|
|
@@ -13,8 +14,9 @@ log = logging.getLogger(__name__)
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class WeightCounting(Question, abc.ABC):
|
|
17
|
+
@staticmethod
|
|
16
18
|
@abc.abstractmethod
|
|
17
|
-
def get_model(
|
|
19
|
+
def get_model(rng: random.Random) -> keras.Model:
|
|
18
20
|
pass
|
|
19
21
|
|
|
20
22
|
@staticmethod
|
|
@@ -71,25 +73,30 @@ class WeightCounting(Question, abc.ABC):
|
|
|
71
73
|
lines.append("])")
|
|
72
74
|
return "\n".join(lines)
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
@classmethod
|
|
77
|
+
def _build_context(cls, *, rng_seed=None, **kwargs):
|
|
78
|
+
rng = random.Random(rng_seed)
|
|
79
|
+
|
|
77
80
|
refresh_success = False
|
|
78
81
|
while not refresh_success:
|
|
79
82
|
try:
|
|
80
|
-
|
|
83
|
+
model, fields = cls.get_model(rng)
|
|
81
84
|
refresh_success = True
|
|
82
85
|
except ValueError as e:
|
|
83
86
|
log.error(e)
|
|
84
|
-
log.info(f"Regenerating {
|
|
87
|
+
log.info(f"Regenerating {cls.__name__} due to improper configuration")
|
|
85
88
|
continue
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
|
|
90
|
+
num_parameters = model.count_params()
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
"model": model,
|
|
94
|
+
"fields": fields,
|
|
95
|
+
"num_parameters": num_parameters,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def _build_body(cls, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
93
100
|
"""Build question body and collect answers."""
|
|
94
101
|
body = ca.Section()
|
|
95
102
|
answers = []
|
|
@@ -104,26 +111,23 @@ class WeightCounting(Question, abc.ABC):
|
|
|
104
111
|
|
|
105
112
|
body.add_element(
|
|
106
113
|
ca.Code(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
fields=
|
|
114
|
+
cls.model_to_python(
|
|
115
|
+
context["model"],
|
|
116
|
+
fields=context["fields"]
|
|
110
117
|
)
|
|
111
118
|
)
|
|
112
119
|
)
|
|
113
120
|
|
|
114
121
|
body.add_element(ca.LineBreak())
|
|
115
122
|
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
num_parameters_answer = ca.AnswerTypes.Int(context["num_parameters"], label="Number of Parameters")
|
|
124
|
+
answers.append(num_parameters_answer)
|
|
125
|
+
body.add_element(num_parameters_answer)
|
|
118
126
|
|
|
119
127
|
return body, answers
|
|
120
128
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
body, _ = self._get_body(**kwargs)
|
|
124
|
-
return body
|
|
125
|
-
|
|
126
|
-
def _get_explanation(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
129
|
+
@classmethod
|
|
130
|
+
def _build_explanation(cls, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
127
131
|
"""Build question explanation."""
|
|
128
132
|
explanation = ca.Section()
|
|
129
133
|
|
|
@@ -159,31 +163,27 @@ class WeightCounting(Question, abc.ABC):
|
|
|
159
163
|
|
|
160
164
|
|
|
161
165
|
summary_lines = []
|
|
162
|
-
|
|
166
|
+
context["model"].summary(print_fn=lambda x: summary_lines.append(x))
|
|
163
167
|
explanation.add_element(
|
|
164
168
|
# ca.Text('\n'.join(summary_lines))
|
|
165
|
-
markdown_summary(
|
|
169
|
+
markdown_summary(context["model"])
|
|
166
170
|
)
|
|
167
171
|
|
|
168
172
|
return explanation, []
|
|
169
173
|
|
|
170
|
-
def get_explanation(self, **kwargs) -> ca.Section:
|
|
171
|
-
"""Build question explanation (backward compatible interface)."""
|
|
172
|
-
explanation, _ = self._get_explanation(**kwargs)
|
|
173
|
-
return explanation
|
|
174
|
-
|
|
175
174
|
|
|
176
175
|
@QuestionRegistry.register("cst463.WeightCounting-CNN")
|
|
177
176
|
class WeightCounting_CNN(WeightCounting):
|
|
178
177
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
178
|
+
@staticmethod
|
|
179
|
+
def get_model(rng: random.Random) -> tuple[keras.Model, list[str]]:
|
|
180
|
+
input_size = rng.choice(np.arange(28, 32))
|
|
181
|
+
cnn_num_filters = rng.choice(2 ** np.arange(8))
|
|
182
|
+
cnn_kernel_size = rng.choice(1 + np.arange(10))
|
|
183
|
+
cnn_strides = rng.choice(1 + np.arange(10))
|
|
184
|
+
pool_size = rng.choice(1 + np.arange(10))
|
|
185
|
+
pool_strides = rng.choice(1 + np.arange(10))
|
|
186
|
+
num_output_size = rng.choice([1, 10, 32, 100])
|
|
187
187
|
|
|
188
188
|
# Let's just make a small model
|
|
189
189
|
model = keras.models.Sequential(
|
|
@@ -209,15 +209,16 @@ class WeightCounting_CNN(WeightCounting):
|
|
|
209
209
|
|
|
210
210
|
@QuestionRegistry.register("cst463.WeightCounting-RNN")
|
|
211
211
|
class WeightCounting_RNN(WeightCounting):
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
212
|
+
@staticmethod
|
|
213
|
+
def get_model(rng: random.Random) -> tuple[keras.Model, list[str]]:
|
|
214
|
+
timesteps = int(rng.choice(np.arange(20, 41)))
|
|
215
|
+
feature_size = int(rng.choice(np.arange(8, 65)))
|
|
215
216
|
|
|
216
|
-
rnn_units = int(
|
|
217
|
-
rnn_type =
|
|
218
|
-
return_sequences = bool(
|
|
217
|
+
rnn_units = int(rng.choice(2 ** np.arange(4, 9)))
|
|
218
|
+
rnn_type = rng.choice(["SimpleRNN"])
|
|
219
|
+
return_sequences = bool(rng.choice([True, False]))
|
|
219
220
|
|
|
220
|
-
num_output_size = int(
|
|
221
|
+
num_output_size = int(rng.choice([1, 10, 32, 100]))
|
|
221
222
|
|
|
222
223
|
RNNLayer = getattr(keras.layers, rnn_type)
|
|
223
224
|
|
|
@@ -7,7 +7,7 @@ import math
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
import uuid
|
|
9
9
|
import os
|
|
10
|
-
from typing import List, Tuple
|
|
10
|
+
from typing import List, Tuple
|
|
11
11
|
|
|
12
12
|
import matplotlib.pyplot as plt
|
|
13
13
|
import matplotlib.patches as mpatches
|
|
@@ -73,37 +73,60 @@ class SimpleNeuralNetworkBase(MatrixQuestion, abc.ABC):
|
|
|
73
73
|
self.da2_dz2 = None # Gradient of activation w.r.t. pre-activation
|
|
74
74
|
self.dL_dz2 = None # Gradient of loss w.r.t. output pre-activation
|
|
75
75
|
|
|
76
|
+
def _build_context(self, *, rng_seed=None, **kwargs):
|
|
77
|
+
if "num_inputs" in kwargs:
|
|
78
|
+
self.num_inputs = kwargs.get("num_inputs", self.num_inputs)
|
|
79
|
+
if "num_hidden" in kwargs:
|
|
80
|
+
self.num_hidden = kwargs.get("num_hidden", self.num_hidden)
|
|
81
|
+
if "num_outputs" in kwargs:
|
|
82
|
+
self.num_outputs = kwargs.get("num_outputs", self.num_outputs)
|
|
83
|
+
if "use_bias" in kwargs:
|
|
84
|
+
self.use_bias = kwargs.get("use_bias", self.use_bias)
|
|
85
|
+
if "param_digits" in kwargs:
|
|
86
|
+
self.param_digits = kwargs.get("param_digits", self.param_digits)
|
|
87
|
+
|
|
88
|
+
self.rng.seed(rng_seed)
|
|
89
|
+
self._np_rng = np.random.RandomState(rng_seed)
|
|
90
|
+
|
|
91
|
+
context = dict(kwargs)
|
|
92
|
+
context["rng_seed"] = rng_seed
|
|
93
|
+
return context
|
|
94
|
+
|
|
76
95
|
def _generate_network(self, weight_range=(-2, 2), input_range=(-3, 3)):
|
|
77
96
|
"""Generate random network parameters and input."""
|
|
78
97
|
# Generate weights using MatrixQuestion's rounded matrix method
|
|
79
98
|
# Use param_digits to match display precision in tables and explanations
|
|
80
99
|
self.W1 = self.get_rounded_matrix(
|
|
100
|
+
self._np_rng,
|
|
81
101
|
(self.num_hidden, self.num_inputs),
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
102
|
+
weight_range[0],
|
|
103
|
+
weight_range[1],
|
|
104
|
+
self.param_digits
|
|
85
105
|
)
|
|
86
106
|
|
|
87
107
|
self.W2 = self.get_rounded_matrix(
|
|
108
|
+
self._np_rng,
|
|
88
109
|
(self.num_outputs, self.num_hidden),
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
110
|
+
weight_range[0],
|
|
111
|
+
weight_range[1],
|
|
112
|
+
self.param_digits
|
|
92
113
|
)
|
|
93
114
|
|
|
94
115
|
# Generate biases
|
|
95
116
|
if self.use_bias:
|
|
96
117
|
self.b1 = self.get_rounded_matrix(
|
|
118
|
+
self._np_rng,
|
|
97
119
|
(self.num_hidden,),
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
120
|
+
weight_range[0],
|
|
121
|
+
weight_range[1],
|
|
122
|
+
self.param_digits
|
|
101
123
|
)
|
|
102
124
|
self.b2 = self.get_rounded_matrix(
|
|
125
|
+
self._np_rng,
|
|
103
126
|
(self.num_outputs,),
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
127
|
+
weight_range[0],
|
|
128
|
+
weight_range[1],
|
|
129
|
+
self.param_digits
|
|
107
130
|
)
|
|
108
131
|
else:
|
|
109
132
|
self.b1 = np.zeros(self.num_hidden)
|
|
@@ -111,10 +134,11 @@ class SimpleNeuralNetworkBase(MatrixQuestion, abc.ABC):
|
|
|
111
134
|
|
|
112
135
|
# Generate input values (keep as integers for simplicity)
|
|
113
136
|
self.X = self.get_rounded_matrix(
|
|
137
|
+
self._np_rng,
|
|
114
138
|
(self.num_inputs,),
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
139
|
+
input_range[0],
|
|
140
|
+
input_range[1],
|
|
141
|
+
0 # Round to integers
|
|
118
142
|
)
|
|
119
143
|
|
|
120
144
|
def _select_activation_function(self):
|
|
@@ -546,8 +570,8 @@ class ForwardPassQuestion(SimpleNeuralNetworkBase):
|
|
|
546
570
|
- Final output (ŷ)
|
|
547
571
|
"""
|
|
548
572
|
|
|
549
|
-
def
|
|
550
|
-
super().
|
|
573
|
+
def _build_context(self, *, rng_seed=None, **kwargs):
|
|
574
|
+
super()._build_context(rng_seed=rng_seed, **kwargs)
|
|
551
575
|
|
|
552
576
|
# Generate network
|
|
553
577
|
self._generate_network()
|
|
@@ -556,22 +580,11 @@ class ForwardPassQuestion(SimpleNeuralNetworkBase):
|
|
|
556
580
|
# Run forward pass to get correct answers
|
|
557
581
|
self._forward_pass()
|
|
558
582
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
def _create_answers(self):
|
|
563
|
-
"""Create answer fields for forward pass values."""
|
|
564
|
-
self.answers = {}
|
|
565
|
-
|
|
566
|
-
# Hidden layer activations
|
|
567
|
-
for i in range(self.num_hidden):
|
|
568
|
-
key = f"h{i+1}"
|
|
569
|
-
self.answers[key] = ca.AnswerTypes.Float(float(self.a1[i]), label=f"h_{i + 1}")
|
|
570
|
-
|
|
571
|
-
# Output
|
|
572
|
-
self.answers["y_pred"] = ca.AnswerTypes.Float(float(self.a2[0]), label="ŷ")
|
|
583
|
+
context = dict(kwargs)
|
|
584
|
+
context["rng_seed"] = rng_seed
|
|
585
|
+
return context
|
|
573
586
|
|
|
574
|
-
def
|
|
587
|
+
def _build_body(self, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
575
588
|
"""Build question body and collect answers."""
|
|
576
589
|
body = ca.Section()
|
|
577
590
|
answers = []
|
|
@@ -599,22 +612,16 @@ class ForwardPassQuestion(SimpleNeuralNetworkBase):
|
|
|
599
612
|
f"**Hidden layer activation:** {self._get_activation_name()}"
|
|
600
613
|
]))
|
|
601
614
|
|
|
602
|
-
# Collect answers
|
|
603
615
|
for i in range(self.num_hidden):
|
|
604
|
-
answers.append(self.
|
|
616
|
+
answers.append(ca.AnswerTypes.Float(float(self.a1[i]), label=f"h_{i + 1}"))
|
|
605
617
|
|
|
606
|
-
answers.append(self.
|
|
618
|
+
answers.append(ca.AnswerTypes.Float(float(self.a2[0]), label="ŷ"))
|
|
607
619
|
|
|
608
620
|
body.add_element(ca.AnswerBlock(answers))
|
|
609
621
|
|
|
610
622
|
return body, answers
|
|
611
623
|
|
|
612
|
-
def
|
|
613
|
-
"""Build question body (backward compatible interface)."""
|
|
614
|
-
body, _ = self._get_body(**kwargs)
|
|
615
|
-
return body
|
|
616
|
-
|
|
617
|
-
def _get_explanation(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
624
|
+
def _build_explanation(self, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
618
625
|
"""Build question explanation."""
|
|
619
626
|
explanation = ca.Section()
|
|
620
627
|
|
|
@@ -694,11 +701,6 @@ class ForwardPassQuestion(SimpleNeuralNetworkBase):
|
|
|
694
701
|
|
|
695
702
|
return explanation, []
|
|
696
703
|
|
|
697
|
-
def get_explanation(self, **kwargs) -> ca.Section:
|
|
698
|
-
"""Build question explanation (backward compatible interface)."""
|
|
699
|
-
explanation, _ = self._get_explanation(**kwargs)
|
|
700
|
-
return explanation
|
|
701
|
-
|
|
702
704
|
|
|
703
705
|
@QuestionRegistry.register()
|
|
704
706
|
class BackpropGradientQuestion(SimpleNeuralNetworkBase):
|
|
@@ -709,8 +711,8 @@ class BackpropGradientQuestion(SimpleNeuralNetworkBase):
|
|
|
709
711
|
- Gradients for multiple specific weights (∂L/∂w)
|
|
710
712
|
"""
|
|
711
713
|
|
|
712
|
-
def
|
|
713
|
-
super().
|
|
714
|
+
def _build_context(self, *, rng_seed=None, **kwargs):
|
|
715
|
+
super()._build_context(rng_seed=rng_seed, **kwargs)
|
|
714
716
|
|
|
715
717
|
# Generate network
|
|
716
718
|
self._generate_network()
|
|
@@ -730,27 +732,11 @@ class BackpropGradientQuestion(SimpleNeuralNetworkBase):
|
|
|
730
732
|
self.loss = round(self.loss, 4)
|
|
731
733
|
self._compute_output_gradient()
|
|
732
734
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
def _create_answers(self):
|
|
737
|
-
"""Create answer fields for weight gradients."""
|
|
738
|
-
self.answers = {}
|
|
739
|
-
|
|
740
|
-
# Ask for gradients of 2-3 weights
|
|
741
|
-
# Include at least one from each layer
|
|
735
|
+
context = dict(kwargs)
|
|
736
|
+
context["rng_seed"] = rng_seed
|
|
737
|
+
return context
|
|
742
738
|
|
|
743
|
-
|
|
744
|
-
for i in range(self.num_hidden):
|
|
745
|
-
key = f"dL_dw2_{i}"
|
|
746
|
-
self.answers[key] = ca.AnswerTypes.Float(self._compute_gradient_W2(i), label=f"∂L/∂w_{i + 3}")
|
|
747
|
-
|
|
748
|
-
# Gradient for W1 (input to hidden) - pick first hidden neuron
|
|
749
|
-
for j in range(self.num_inputs):
|
|
750
|
-
key = f"dL_dw1_0{j}"
|
|
751
|
-
self.answers[key] = ca.AnswerTypes.Float(self._compute_gradient_W1(0, j), label=f"∂L/∂w_1{j + 1}")
|
|
752
|
-
|
|
753
|
-
def _get_body(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
739
|
+
def _build_body(self, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
754
740
|
"""Build question body and collect answers."""
|
|
755
741
|
body = ca.Section()
|
|
756
742
|
answers = []
|
|
@@ -783,24 +769,23 @@ class BackpropGradientQuestion(SimpleNeuralNetworkBase):
|
|
|
783
769
|
"**Calculate the following gradients:**"
|
|
784
770
|
]))
|
|
785
771
|
|
|
786
|
-
# Collect W2 gradient answers
|
|
787
772
|
for i in range(self.num_hidden):
|
|
788
|
-
answers.append(
|
|
773
|
+
answers.append(ca.AnswerTypes.Float(
|
|
774
|
+
self._compute_gradient_W2(i),
|
|
775
|
+
label=f"∂L/∂w_{i + 3}"
|
|
776
|
+
))
|
|
789
777
|
|
|
790
|
-
# Collect W1 gradient answers (first hidden neuron)
|
|
791
778
|
for j in range(self.num_inputs):
|
|
792
|
-
answers.append(
|
|
779
|
+
answers.append(ca.AnswerTypes.Float(
|
|
780
|
+
self._compute_gradient_W1(0, j),
|
|
781
|
+
label=f"∂L/∂w_1{j + 1}"
|
|
782
|
+
))
|
|
793
783
|
|
|
794
784
|
body.add_element(ca.AnswerBlock(answers))
|
|
795
785
|
|
|
796
786
|
return body, answers
|
|
797
787
|
|
|
798
|
-
def
|
|
799
|
-
"""Build question body (backward compatible interface)."""
|
|
800
|
-
body, _ = self._get_body(**kwargs)
|
|
801
|
-
return body
|
|
802
|
-
|
|
803
|
-
def _get_explanation(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
788
|
+
def _build_explanation(self, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
804
789
|
"""Build question explanation."""
|
|
805
790
|
explanation = ca.Section()
|
|
806
791
|
|
|
@@ -874,11 +859,6 @@ class BackpropGradientQuestion(SimpleNeuralNetworkBase):
|
|
|
874
859
|
|
|
875
860
|
return explanation, []
|
|
876
861
|
|
|
877
|
-
def get_explanation(self, **kwargs) -> ca.Section:
|
|
878
|
-
"""Build question explanation (backward compatible interface)."""
|
|
879
|
-
explanation, _ = self._get_explanation(**kwargs)
|
|
880
|
-
return explanation
|
|
881
|
-
|
|
882
862
|
|
|
883
863
|
@QuestionRegistry.register()
|
|
884
864
|
class EnsembleAveragingQuestion(Question):
|
|
@@ -897,8 +877,11 @@ class EnsembleAveragingQuestion(Question):
|
|
|
897
877
|
self.num_models = kwargs.get("num_models", 5)
|
|
898
878
|
self.predictions = None
|
|
899
879
|
|
|
900
|
-
def
|
|
901
|
-
|
|
880
|
+
def _build_context(self, *, rng_seed=None, **kwargs):
|
|
881
|
+
if "num_models" in kwargs:
|
|
882
|
+
self.num_models = kwargs.get("num_models", self.num_models)
|
|
883
|
+
|
|
884
|
+
self.rng.seed(rng_seed)
|
|
902
885
|
|
|
903
886
|
# Generate predictions from multiple models
|
|
904
887
|
# Use a range that makes sense for typical regression problems
|
|
@@ -911,22 +894,11 @@ class EnsembleAveragingQuestion(Question):
|
|
|
911
894
|
# Round to make calculations easier
|
|
912
895
|
self.predictions = [round(p, 1) for p in self.predictions]
|
|
913
896
|
|
|
914
|
-
|
|
915
|
-
|
|
897
|
+
context = dict(kwargs)
|
|
898
|
+
context["rng_seed"] = rng_seed
|
|
899
|
+
return context
|
|
916
900
|
|
|
917
|
-
def
|
|
918
|
-
"""Create answer fields for ensemble statistics."""
|
|
919
|
-
self.answers = {}
|
|
920
|
-
|
|
921
|
-
# Mean prediction
|
|
922
|
-
mean_pred = np.mean(self.predictions)
|
|
923
|
-
self.answers["mean"] = ca.AnswerTypes.Float(float(mean_pred), label="Mean (average)")
|
|
924
|
-
|
|
925
|
-
# Median (optional, but useful)
|
|
926
|
-
median_pred = np.median(self.predictions)
|
|
927
|
-
self.answers["median"] = ca.AnswerTypes.Float(float(median_pred), label="Median")
|
|
928
|
-
|
|
929
|
-
def _get_body(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
901
|
+
def _build_body(self, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
930
902
|
"""Build question body and collect answers."""
|
|
931
903
|
body = ca.Section()
|
|
932
904
|
answers = []
|
|
@@ -948,20 +920,16 @@ class EnsembleAveragingQuestion(Question):
|
|
|
948
920
|
"To create an ensemble, calculate the combined prediction using the following methods:"
|
|
949
921
|
]))
|
|
950
922
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
answers.append(
|
|
923
|
+
mean_pred = np.mean(self.predictions)
|
|
924
|
+
median_pred = np.median(self.predictions)
|
|
925
|
+
answers.append(ca.AnswerTypes.Float(float(mean_pred), label="Mean (average)"))
|
|
926
|
+
answers.append(ca.AnswerTypes.Float(float(median_pred), label="Median"))
|
|
954
927
|
|
|
955
928
|
body.add_element(ca.AnswerBlock(answers))
|
|
956
929
|
|
|
957
930
|
return body, answers
|
|
958
931
|
|
|
959
|
-
def
|
|
960
|
-
"""Build question body (backward compatible interface)."""
|
|
961
|
-
body, _ = self._get_body(**kwargs)
|
|
962
|
-
return body
|
|
963
|
-
|
|
964
|
-
def _get_explanation(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
932
|
+
def _build_explanation(self, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
965
933
|
"""Build question explanation."""
|
|
966
934
|
explanation = ca.Section()
|
|
967
935
|
|
|
@@ -1010,11 +978,6 @@ class EnsembleAveragingQuestion(Question):
|
|
|
1010
978
|
|
|
1011
979
|
return explanation, []
|
|
1012
980
|
|
|
1013
|
-
def get_explanation(self, **kwargs) -> ca.Section:
|
|
1014
|
-
"""Build question explanation (backward compatible interface)."""
|
|
1015
|
-
explanation, _ = self._get_explanation(**kwargs)
|
|
1016
|
-
return explanation
|
|
1017
|
-
|
|
1018
981
|
|
|
1019
982
|
@QuestionRegistry.register()
|
|
1020
983
|
class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
|
|
@@ -1034,8 +997,8 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
|
|
|
1034
997
|
self.new_W1 = None
|
|
1035
998
|
self.new_W2 = None
|
|
1036
999
|
|
|
1037
|
-
def
|
|
1038
|
-
super().
|
|
1000
|
+
def _build_context(self, *, rng_seed=None, **kwargs):
|
|
1001
|
+
super()._build_context(rng_seed=rng_seed, **kwargs)
|
|
1039
1002
|
|
|
1040
1003
|
# Generate network
|
|
1041
1004
|
self._generate_network()
|
|
@@ -1061,8 +1024,9 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
|
|
|
1061
1024
|
# Compute updated weights
|
|
1062
1025
|
self._compute_weight_updates()
|
|
1063
1026
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1027
|
+
context = dict(kwargs)
|
|
1028
|
+
context["rng_seed"] = rng_seed
|
|
1029
|
+
return context
|
|
1066
1030
|
|
|
1067
1031
|
def _compute_weight_updates(self):
|
|
1068
1032
|
"""Compute new weights after gradient descent step."""
|
|
@@ -1078,25 +1042,7 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
|
|
|
1078
1042
|
grad = self._compute_gradient_W1(0, j)
|
|
1079
1043
|
self.new_W1[0, j] = self.W1[0, j] - self.learning_rate * grad
|
|
1080
1044
|
|
|
1081
|
-
def
|
|
1082
|
-
"""Create answer fields for all steps."""
|
|
1083
|
-
self.answers = {}
|
|
1084
|
-
|
|
1085
|
-
# Forward pass answers
|
|
1086
|
-
self.answers["y_pred"] = ca.AnswerTypes.Float(float(self.a2[0]), label="1. Forward Pass - Network output ŷ")
|
|
1087
|
-
|
|
1088
|
-
# Loss answer
|
|
1089
|
-
self.answers["loss"] = ca.AnswerTypes.Float(float(self.loss), label="2. Loss")
|
|
1090
|
-
|
|
1091
|
-
# Gradient answers (for key weights)
|
|
1092
|
-
self.answers["grad_w3"] = ca.AnswerTypes.Float(self._compute_gradient_W2(0), label="3. Gradient ∂L/∂w₃")
|
|
1093
|
-
self.answers["grad_w11"] = ca.AnswerTypes.Float(self._compute_gradient_W1(0, 0), label="4. Gradient ∂L/∂w₁₁")
|
|
1094
|
-
|
|
1095
|
-
# Updated weight answers
|
|
1096
|
-
self.answers["new_w3"] = ca.AnswerTypes.Float(float(self.new_W2[0, 0]), label="5. Updated w₃:")
|
|
1097
|
-
self.answers["new_w11"] = ca.AnswerTypes.Float(float(self.new_W1[0, 0]), label="6. Updated w₁₁:")
|
|
1098
|
-
|
|
1099
|
-
def _get_body(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
1045
|
+
def _build_body(self, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
1100
1046
|
"""Build question body and collect answers."""
|
|
1101
1047
|
body = ca.Section()
|
|
1102
1048
|
answers = []
|
|
@@ -1145,24 +1091,27 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
|
|
|
1145
1091
|
# Network parameters table
|
|
1146
1092
|
body.add_element(self._generate_parameter_table(include_activations=False))
|
|
1147
1093
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
answers.append(self.
|
|
1153
|
-
answers.append(
|
|
1154
|
-
|
|
1094
|
+
answers.append(ca.AnswerTypes.Float(
|
|
1095
|
+
float(self.a2[0]),
|
|
1096
|
+
label="1. Forward Pass - Network output ŷ"
|
|
1097
|
+
))
|
|
1098
|
+
answers.append(ca.AnswerTypes.Float(float(self.loss), label="2. Loss"))
|
|
1099
|
+
answers.append(ca.AnswerTypes.Float(
|
|
1100
|
+
self._compute_gradient_W2(0),
|
|
1101
|
+
label="3. Gradient ∂L/∂w₃"
|
|
1102
|
+
))
|
|
1103
|
+
answers.append(ca.AnswerTypes.Float(
|
|
1104
|
+
self._compute_gradient_W1(0, 0),
|
|
1105
|
+
label="4. Gradient ∂L/∂w₁₁"
|
|
1106
|
+
))
|
|
1107
|
+
answers.append(ca.AnswerTypes.Float(float(self.new_W2[0, 0]), label="5. Updated w₃:"))
|
|
1108
|
+
answers.append(ca.AnswerTypes.Float(float(self.new_W1[0, 0]), label="6. Updated w₁₁:"))
|
|
1155
1109
|
|
|
1156
1110
|
body.add_element(ca.AnswerBlock(answers))
|
|
1157
1111
|
|
|
1158
1112
|
return body, answers
|
|
1159
1113
|
|
|
1160
|
-
def
|
|
1161
|
-
"""Build question body (backward compatible interface)."""
|
|
1162
|
-
body, _ = self._get_body(**kwargs)
|
|
1163
|
-
return body
|
|
1164
|
-
|
|
1165
|
-
def _get_explanation(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
1114
|
+
def _build_explanation(self, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
1166
1115
|
"""Build question explanation."""
|
|
1167
1116
|
explanation = ca.Section()
|
|
1168
1117
|
|
|
@@ -1287,8 +1236,3 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
|
|
|
1287
1236
|
]))
|
|
1288
1237
|
|
|
1289
1238
|
return explanation, []
|
|
1290
|
-
|
|
1291
|
-
def get_explanation(self, **kwargs) -> ca.Section:
|
|
1292
|
-
"""Build question explanation (backward compatible interface)."""
|
|
1293
|
-
explanation, _ = self._get_explanation(**kwargs)
|
|
1294
|
-
return explanation
|