QuizGenerator 0.1.4__py3-none-any.whl → 0.3.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 +415 -284
- QuizGenerator/misc.py +96 -8
- QuizGenerator/mixins.py +10 -1
- QuizGenerator/premade_questions/cst334/memory_questions.py +1 -1
- QuizGenerator/premade_questions/cst334/process.py +1 -2
- QuizGenerator/premade_questions/cst463/models/__init__.py +0 -0
- QuizGenerator/premade_questions/cst463/models/attention.py +192 -0
- QuizGenerator/premade_questions/cst463/models/cnns.py +186 -0
- QuizGenerator/premade_questions/cst463/models/matrices.py +24 -0
- QuizGenerator/premade_questions/cst463/models/rnns.py +202 -0
- QuizGenerator/premade_questions/cst463/models/text.py +201 -0
- QuizGenerator/premade_questions/cst463/models/weight_counting.py +227 -0
- QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +138 -94
- QuizGenerator/question.py +3 -2
- QuizGenerator/quiz.py +0 -1
- {quizgenerator-0.1.4.dist-info → quizgenerator-0.3.0.dist-info}/METADATA +3 -1
- {quizgenerator-0.1.4.dist-info → quizgenerator-0.3.0.dist-info}/RECORD +20 -13
- {quizgenerator-0.1.4.dist-info → quizgenerator-0.3.0.dist-info}/WHEEL +1 -1
- {quizgenerator-0.1.4.dist-info → quizgenerator-0.3.0.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.1.4.dist-info → quizgenerator-0.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -15,11 +15,12 @@ import matplotlib.patches as mpatches
|
|
|
15
15
|
from QuizGenerator.contentast import ContentAST
|
|
16
16
|
from QuizGenerator.question import Question, Answer, QuestionRegistry
|
|
17
17
|
from QuizGenerator.mixins import TableQuestionMixin, BodyTemplatesMixin
|
|
18
|
+
from ..models.matrices import MatrixQuestion
|
|
18
19
|
|
|
19
20
|
log = logging.getLogger(__name__)
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
class SimpleNeuralNetworkBase(
|
|
23
|
+
class SimpleNeuralNetworkBase(MatrixQuestion, abc.ABC):
|
|
23
24
|
"""
|
|
24
25
|
Base class for simple neural network questions.
|
|
25
26
|
|
|
@@ -73,45 +74,42 @@ class SimpleNeuralNetworkBase(Question, abc.ABC):
|
|
|
73
74
|
|
|
74
75
|
def _generate_network(self, weight_range=(-2, 2), input_range=(-3, 3)):
|
|
75
76
|
"""Generate random network parameters and input."""
|
|
76
|
-
# Generate weights
|
|
77
|
-
self.W1 =
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
self.W2 =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
77
|
+
# Generate weights using MatrixQuestion's rounded matrix method
|
|
78
|
+
self.W1 = self.get_rounded_matrix(
|
|
79
|
+
(self.num_hidden, self.num_inputs),
|
|
80
|
+
low=weight_range[0],
|
|
81
|
+
high=weight_range[1]
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
self.W2 = self.get_rounded_matrix(
|
|
85
|
+
(self.num_outputs, self.num_hidden),
|
|
86
|
+
low=weight_range[0],
|
|
87
|
+
high=weight_range[1]
|
|
88
|
+
)
|
|
88
89
|
|
|
89
90
|
# Generate biases
|
|
90
91
|
if self.use_bias:
|
|
91
|
-
self.b1 =
|
|
92
|
-
self.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
self.b1 = self.get_rounded_matrix(
|
|
93
|
+
(self.num_hidden,),
|
|
94
|
+
low=weight_range[0],
|
|
95
|
+
high=weight_range[1]
|
|
96
|
+
)
|
|
97
|
+
self.b2 = self.get_rounded_matrix(
|
|
98
|
+
(self.num_outputs,),
|
|
99
|
+
low=weight_range[0],
|
|
100
|
+
high=weight_range[1]
|
|
101
|
+
)
|
|
99
102
|
else:
|
|
100
103
|
self.b1 = np.zeros(self.num_hidden)
|
|
101
104
|
self.b2 = np.zeros(self.num_outputs)
|
|
102
105
|
|
|
103
|
-
#
|
|
104
|
-
self.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
self.X = np.array([
|
|
111
|
-
self.rng.uniform(input_range[0], input_range[1])
|
|
112
|
-
for _ in range(self.num_inputs)
|
|
113
|
-
])
|
|
114
|
-
self.X = np.round(self.X) # Use integer inputs for simplicity
|
|
106
|
+
# Generate input values (keep as integers for simplicity)
|
|
107
|
+
self.X = self.get_rounded_matrix(
|
|
108
|
+
(self.num_inputs,),
|
|
109
|
+
low=input_range[0],
|
|
110
|
+
high=input_range[1],
|
|
111
|
+
digits_to_round=0 # Round to integers
|
|
112
|
+
)
|
|
115
113
|
|
|
116
114
|
def _select_activation_function(self):
|
|
117
115
|
"""Randomly select an activation function."""
|
|
@@ -158,7 +156,7 @@ class SimpleNeuralNetworkBase(Question, abc.ABC):
|
|
|
158
156
|
|
|
159
157
|
# Output layer
|
|
160
158
|
self.z2 = self.W2 @ self.a1 + self.b2
|
|
161
|
-
self.a2 = self._apply_activation(self.z2, self.
|
|
159
|
+
self.a2 = self._apply_activation(self.z2, self.ACTIVATION_SIGMOID) # Sigmoid output for binary classification
|
|
162
160
|
|
|
163
161
|
# Round all computed values to display precision to ensure students can reproduce calculations
|
|
164
162
|
# We display z and a values with 4 decimal places
|
|
@@ -170,21 +168,34 @@ class SimpleNeuralNetworkBase(Question, abc.ABC):
|
|
|
170
168
|
return self.a2
|
|
171
169
|
|
|
172
170
|
def _compute_loss(self, y_target):
|
|
173
|
-
"""Compute
|
|
171
|
+
"""Compute binary cross-entropy loss."""
|
|
174
172
|
self.y_target = y_target
|
|
175
|
-
|
|
173
|
+
# BCE: L = -[y log(ŷ) + (1-y) log(1-ŷ)]
|
|
174
|
+
# Add small epsilon to prevent log(0)
|
|
175
|
+
epsilon = 1e-15
|
|
176
|
+
y_pred = np.clip(self.a2[0], epsilon, 1 - epsilon)
|
|
177
|
+
self.loss = -(y_target * np.log(y_pred) + (1 - y_target) * np.log(1 - y_pred))
|
|
176
178
|
return self.loss
|
|
177
179
|
|
|
178
180
|
def _compute_output_gradient(self):
|
|
179
181
|
"""Compute gradient of loss w.r.t. output."""
|
|
180
|
-
# For
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
#
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
#
|
|
187
|
-
|
|
182
|
+
# For BCE loss with sigmoid activation, the gradient simplifies beautifully:
|
|
183
|
+
# dL/dz2 = ŷ - y (this is the combined derivative of BCE loss and sigmoid activation)
|
|
184
|
+
#
|
|
185
|
+
# Derivation:
|
|
186
|
+
# BCE: L = -[y log(ŷ) + (1-y) log(1-ŷ)]
|
|
187
|
+
# dL/dŷ = -[y/ŷ - (1-y)/(1-ŷ)]
|
|
188
|
+
# Sigmoid: ŷ = σ(z), dŷ/dz = ŷ(1-ŷ)
|
|
189
|
+
# Chain rule: dL/dz = dL/dŷ * dŷ/dz = ŷ - y
|
|
190
|
+
|
|
191
|
+
self.dL_dz2 = self.a2[0] - self.y_target
|
|
192
|
+
|
|
193
|
+
# Store intermediate values for explanation purposes
|
|
194
|
+
# Clip to prevent division by zero (same epsilon as in loss calculation)
|
|
195
|
+
epsilon = 1e-15
|
|
196
|
+
y_pred_clipped = np.clip(self.a2[0], epsilon, 1 - epsilon)
|
|
197
|
+
self.dL_da2 = -(self.y_target / y_pred_clipped - (1 - self.y_target) / (1 - y_pred_clipped))
|
|
198
|
+
self.da2_dz2 = self.a2[0] * (1 - self.a2[0])
|
|
188
199
|
|
|
189
200
|
return self.dL_dz2
|
|
190
201
|
|
|
@@ -302,7 +313,7 @@ class SimpleNeuralNetworkBase(Question, abc.ABC):
|
|
|
302
313
|
if self.y_target is not None:
|
|
303
314
|
right_data.append([
|
|
304
315
|
ContentAST.Equation("y", inline=True),
|
|
305
|
-
f"{self.y_target
|
|
316
|
+
f"{int(self.y_target)}" # Binary target (0 or 1)
|
|
306
317
|
])
|
|
307
318
|
|
|
308
319
|
if self.loss is not None:
|
|
@@ -560,7 +571,7 @@ class ForwardPassQuestion(SimpleNeuralNetworkBase):
|
|
|
560
571
|
# Question description
|
|
561
572
|
body.add_element(ContentAST.Paragraph([
|
|
562
573
|
f"Given the neural network below with {self._get_activation_name()} activation "
|
|
563
|
-
f"in the hidden layer and
|
|
574
|
+
f"in the hidden layer and sigmoid activation in the output layer (for binary classification), "
|
|
564
575
|
f"calculate the forward pass for the given input values."
|
|
565
576
|
]))
|
|
566
577
|
|
|
@@ -577,7 +588,7 @@ class ForwardPassQuestion(SimpleNeuralNetworkBase):
|
|
|
577
588
|
|
|
578
589
|
# Activation function
|
|
579
590
|
body.add_element(ContentAST.Paragraph([
|
|
580
|
-
f"**
|
|
591
|
+
f"**Hidden layer activation:** {self._get_activation_name()}"
|
|
581
592
|
]))
|
|
582
593
|
|
|
583
594
|
# Create answer block
|
|
@@ -586,14 +597,14 @@ class ForwardPassQuestion(SimpleNeuralNetworkBase):
|
|
|
586
597
|
answers.append(
|
|
587
598
|
ContentAST.Answer(
|
|
588
599
|
answer=self.answers[f"h{i+1}"],
|
|
589
|
-
label=f"h_{i+1}
|
|
600
|
+
label=f"h_{i+1}"
|
|
590
601
|
)
|
|
591
602
|
)
|
|
592
603
|
|
|
593
604
|
answers.append(
|
|
594
605
|
ContentAST.Answer(
|
|
595
606
|
answer=self.answers["y_pred"],
|
|
596
|
-
label="ŷ
|
|
607
|
+
label="ŷ"
|
|
597
608
|
)
|
|
598
609
|
)
|
|
599
610
|
|
|
@@ -652,7 +663,7 @@ class ForwardPassQuestion(SimpleNeuralNetworkBase):
|
|
|
652
663
|
|
|
653
664
|
# Output layer
|
|
654
665
|
explanation.add_element(ContentAST.Paragraph([
|
|
655
|
-
"**Step 3: Calculate output (with
|
|
666
|
+
"**Step 3: Calculate output (with sigmoid activation)**"
|
|
656
667
|
]))
|
|
657
668
|
|
|
658
669
|
terms = []
|
|
@@ -669,12 +680,13 @@ class ForwardPassQuestion(SimpleNeuralNetworkBase):
|
|
|
669
680
|
))
|
|
670
681
|
|
|
671
682
|
explanation.add_element(ContentAST.Equation(
|
|
672
|
-
f"\\hat{{y}} =
|
|
683
|
+
f"\\hat{{y}} = \\sigma(z_{{out}}) = \\frac{{1}}{{1 + e^{{-{self.z2[0]:.4f}}}}} = {self.a2[0]:.4f}",
|
|
673
684
|
inline=False
|
|
674
685
|
))
|
|
675
686
|
|
|
676
687
|
explanation.add_element(ContentAST.Paragraph([
|
|
677
|
-
"(Note: The output layer uses
|
|
688
|
+
"(Note: The output layer uses sigmoid activation for binary classification, "
|
|
689
|
+
"so the output is between 0 and 1, representing the probability of class 1)"
|
|
678
690
|
]))
|
|
679
691
|
|
|
680
692
|
return explanation
|
|
@@ -699,11 +711,12 @@ class BackpropGradientQuestion(SimpleNeuralNetworkBase):
|
|
|
699
711
|
# Run forward pass
|
|
700
712
|
self._forward_pass()
|
|
701
713
|
|
|
702
|
-
# Generate target
|
|
703
|
-
#
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
714
|
+
# Generate binary target (0 or 1)
|
|
715
|
+
# Choose the opposite of what the network predicts to create meaningful gradients
|
|
716
|
+
if self.a2[0] > 0.5:
|
|
717
|
+
self.y_target = 0
|
|
718
|
+
else:
|
|
719
|
+
self.y_target = 1
|
|
707
720
|
self._compute_loss(self.y_target)
|
|
708
721
|
# Round loss to display precision (4 decimal places)
|
|
709
722
|
self.loss = round(self.loss, 4)
|
|
@@ -735,7 +748,8 @@ class BackpropGradientQuestion(SimpleNeuralNetworkBase):
|
|
|
735
748
|
# Question description
|
|
736
749
|
body.add_element(ContentAST.Paragraph([
|
|
737
750
|
f"Given the neural network below with {self._get_activation_name()} activation "
|
|
738
|
-
f"in the hidden layer
|
|
751
|
+
f"in the hidden layer and sigmoid activation in the output layer (for binary classification), "
|
|
752
|
+
f"a forward pass has been completed with the values shown. "
|
|
739
753
|
f"Calculate the gradients (∂L/∂w) for the specified weights using backpropagation."
|
|
740
754
|
]))
|
|
741
755
|
|
|
@@ -752,7 +766,7 @@ class BackpropGradientQuestion(SimpleNeuralNetworkBase):
|
|
|
752
766
|
|
|
753
767
|
# Activation function
|
|
754
768
|
body.add_element(ContentAST.Paragraph([
|
|
755
|
-
f"**
|
|
769
|
+
f"**Hidden layer activation:** {self._get_activation_name()}"
|
|
756
770
|
]))
|
|
757
771
|
|
|
758
772
|
body.add_element(ContentAST.Paragraph([
|
|
@@ -767,7 +781,7 @@ class BackpropGradientQuestion(SimpleNeuralNetworkBase):
|
|
|
767
781
|
answers.append(
|
|
768
782
|
ContentAST.Answer(
|
|
769
783
|
answer=self.answers[f"dL_dw2_{i}"],
|
|
770
|
-
label=f"∂L/∂w_{i+3}
|
|
784
|
+
label=f"∂L/∂w_{i+3}"
|
|
771
785
|
)
|
|
772
786
|
)
|
|
773
787
|
|
|
@@ -776,7 +790,7 @@ class BackpropGradientQuestion(SimpleNeuralNetworkBase):
|
|
|
776
790
|
answers.append(
|
|
777
791
|
ContentAST.Answer(
|
|
778
792
|
answer=self.answers[f"dL_dw1_0{j}"],
|
|
779
|
-
label=f"∂L/∂w_1{j+1}
|
|
793
|
+
label=f"∂L/∂w_1{j+1}"
|
|
780
794
|
)
|
|
781
795
|
)
|
|
782
796
|
|
|
@@ -797,14 +811,19 @@ class BackpropGradientQuestion(SimpleNeuralNetworkBase):
|
|
|
797
811
|
]))
|
|
798
812
|
|
|
799
813
|
explanation.add_element(ContentAST.Paragraph([
|
|
800
|
-
"For
|
|
814
|
+
"For binary cross-entropy loss with sigmoid output activation, "
|
|
815
|
+
"the gradient with respect to the pre-activation simplifies beautifully:"
|
|
801
816
|
]))
|
|
802
817
|
|
|
803
818
|
explanation.add_element(ContentAST.Equation(
|
|
804
|
-
f"\\frac{{\\partial L}}{{\\partial
|
|
819
|
+
f"\\frac{{\\partial L}}{{\\partial z_{{out}}}} = \\hat{{y}} - y = {self.a2[0]:.4f} - {int(self.y_target)} = {self.dL_dz2:.4f}",
|
|
805
820
|
inline=False
|
|
806
821
|
))
|
|
807
822
|
|
|
823
|
+
explanation.add_element(ContentAST.Paragraph([
|
|
824
|
+
"(This elegant result comes from combining the BCE loss derivative and sigmoid activation derivative)"
|
|
825
|
+
]))
|
|
826
|
+
|
|
808
827
|
# W2 gradients
|
|
809
828
|
explanation.add_element(ContentAST.Paragraph([
|
|
810
829
|
"**Step 2: Gradients for hidden-to-output weights**"
|
|
@@ -817,7 +836,7 @@ class BackpropGradientQuestion(SimpleNeuralNetworkBase):
|
|
|
817
836
|
for i in range(self.num_hidden):
|
|
818
837
|
grad = self._compute_gradient_W2(i)
|
|
819
838
|
explanation.add_element(ContentAST.Equation(
|
|
820
|
-
f"\\frac{{\\partial L}}{{\\partial w_{i+3}}} = \\frac{{\\partial L}}{{\\partial
|
|
839
|
+
f"\\frac{{\\partial L}}{{\\partial w_{i+3}}} = \\frac{{\\partial L}}{{\\partial z_{{out}}}} \\cdot \\frac{{\\partial z_{{out}}}}{{\\partial w_{i+3}}} = {self.dL_dz2:.4f} \\cdot {self.a1[i]:.4f} = {grad:.4f}",
|
|
821
840
|
inline=False
|
|
822
841
|
))
|
|
823
842
|
|
|
@@ -839,14 +858,14 @@ class BackpropGradientQuestion(SimpleNeuralNetworkBase):
|
|
|
839
858
|
grad = self._compute_gradient_W1(0, j)
|
|
840
859
|
|
|
841
860
|
if self.activation_function == self.ACTIVATION_SIGMOID:
|
|
842
|
-
act_deriv_str = f"\\sigma(z_1)(1
|
|
861
|
+
act_deriv_str = f"\\sigma'(z_1) = h_1(1-h_1) = {self.a1[0]:.4f}(1-{self.a1[0]:.4f}) = {da1_dz1:.4f}"
|
|
843
862
|
elif self.activation_function == self.ACTIVATION_RELU:
|
|
844
|
-
act_deriv_str = f"\\mathbb{{1}}(z_1 > 0) = {da1_dz1:.4f}"
|
|
863
|
+
act_deriv_str = f"\\text{{ReLU}}'(z_1) = \\mathbb{{1}}(z_1 > 0) = {da1_dz1:.4f}"
|
|
845
864
|
else:
|
|
846
865
|
act_deriv_str = f"1"
|
|
847
866
|
|
|
848
867
|
explanation.add_element(ContentAST.Equation(
|
|
849
|
-
f"\\frac{{\\partial L}}{{\\partial w_{{1{j+1}}}}} = \\frac{{\\partial L}}{{\\partial
|
|
868
|
+
f"\\frac{{\\partial L}}{{\\partial w_{{1{j+1}}}}} = \\frac{{\\partial L}}{{\\partial z_{{out}}}} \\cdot w_{3} \\cdot {act_deriv_str} \\cdot x_{j+1} = {self.dL_dz2:.4f} \\cdot {dz2_da1:.4f} \\cdot {da1_dz1:.4f} \\cdot {self.X[j]:.1f} = {grad:.4f}",
|
|
850
869
|
inline=False
|
|
851
870
|
))
|
|
852
871
|
|
|
@@ -1015,10 +1034,12 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
|
|
|
1015
1034
|
# Run forward pass
|
|
1016
1035
|
self._forward_pass()
|
|
1017
1036
|
|
|
1018
|
-
# Generate target
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1037
|
+
# Generate binary target (0 or 1)
|
|
1038
|
+
# Choose the opposite of what the network predicts to create meaningful gradients
|
|
1039
|
+
if self.a2[0] > 0.5:
|
|
1040
|
+
self.y_target = 0
|
|
1041
|
+
else:
|
|
1042
|
+
self.y_target = 1
|
|
1022
1043
|
self._compute_loss(self.y_target)
|
|
1023
1044
|
# Round loss to display precision (4 decimal places)
|
|
1024
1045
|
self.loss = round(self.loss, 4)
|
|
@@ -1070,15 +1091,16 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
|
|
|
1070
1091
|
|
|
1071
1092
|
# Question description
|
|
1072
1093
|
body.add_element(ContentAST.Paragraph([
|
|
1073
|
-
f"Given the neural network below
|
|
1074
|
-
f"
|
|
1094
|
+
f"Given the neural network below with {self._get_activation_name()} activation "
|
|
1095
|
+
f"in the hidden layer and sigmoid activation in the output layer (for binary classification), "
|
|
1096
|
+
f"perform one complete training step (forward pass, loss calculation, "
|
|
1097
|
+
f"backpropagation, and weight update) for the given input and target."
|
|
1075
1098
|
]))
|
|
1076
1099
|
|
|
1077
1100
|
# Network diagram
|
|
1078
1101
|
body.add_element(
|
|
1079
1102
|
ContentAST.Picture(
|
|
1080
|
-
img_data=self._generate_network_diagram(show_weights=True, show_activations=False)
|
|
1081
|
-
caption=f"Neural network (before training)"
|
|
1103
|
+
img_data=self._generate_network_diagram(show_weights=True, show_activations=False)
|
|
1082
1104
|
)
|
|
1083
1105
|
)
|
|
1084
1106
|
|
|
@@ -1096,7 +1118,7 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
|
|
|
1096
1118
|
|
|
1097
1119
|
body.add_element(ContentAST.Paragraph([
|
|
1098
1120
|
"Target: ",
|
|
1099
|
-
ContentAST.Equation(f"y = {self.y_target
|
|
1121
|
+
ContentAST.Equation(f"y = {int(self.y_target)}", inline=True)
|
|
1100
1122
|
]))
|
|
1101
1123
|
|
|
1102
1124
|
body.add_element(ContentAST.Paragraph([
|
|
@@ -1105,13 +1127,9 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
|
|
|
1105
1127
|
]))
|
|
1106
1128
|
|
|
1107
1129
|
body.add_element(ContentAST.Paragraph([
|
|
1108
|
-
f"**
|
|
1130
|
+
f"**Hidden layer activation:** {self._get_activation_name()}"
|
|
1109
1131
|
]))
|
|
1110
1132
|
|
|
1111
|
-
body.add_element(ContentAST.Paragraph([
|
|
1112
|
-
"**Complete the following training steps:**"
|
|
1113
|
-
]))
|
|
1114
|
-
|
|
1115
1133
|
# Network parameters table
|
|
1116
1134
|
body.add_element(self._generate_parameter_table(include_activations=False))
|
|
1117
1135
|
|
|
@@ -1128,35 +1146,35 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
|
|
|
1128
1146
|
answers.append(
|
|
1129
1147
|
ContentAST.Answer(
|
|
1130
1148
|
answer=self.answers["loss"],
|
|
1131
|
-
label="2. Loss
|
|
1149
|
+
label="2. Loss"
|
|
1132
1150
|
)
|
|
1133
1151
|
)
|
|
1134
1152
|
|
|
1135
1153
|
answers.append(
|
|
1136
1154
|
ContentAST.Answer(
|
|
1137
1155
|
answer=self.answers["grad_w3"],
|
|
1138
|
-
label="3. Gradient ∂L/∂w₃
|
|
1156
|
+
label="3. Gradient ∂L/∂w₃"
|
|
1139
1157
|
)
|
|
1140
1158
|
)
|
|
1141
1159
|
|
|
1142
1160
|
answers.append(
|
|
1143
1161
|
ContentAST.Answer(
|
|
1144
1162
|
answer=self.answers["grad_w11"],
|
|
1145
|
-
label="4. Gradient ∂L/∂w₁₁
|
|
1163
|
+
label="4. Gradient ∂L/∂w₁₁"
|
|
1146
1164
|
)
|
|
1147
1165
|
)
|
|
1148
1166
|
|
|
1149
1167
|
answers.append(
|
|
1150
1168
|
ContentAST.Answer(
|
|
1151
1169
|
answer=self.answers["new_w3"],
|
|
1152
|
-
label="5. Updated w₃:
|
|
1170
|
+
label="5. Updated w₃:"
|
|
1153
1171
|
)
|
|
1154
1172
|
)
|
|
1155
1173
|
|
|
1156
1174
|
answers.append(
|
|
1157
1175
|
ContentAST.Answer(
|
|
1158
1176
|
answer=self.answers["new_w11"],
|
|
1159
|
-
label="6. Updated w₁₁:
|
|
1177
|
+
label="6. Updated w₁₁:"
|
|
1160
1178
|
)
|
|
1161
1179
|
)
|
|
1162
1180
|
|
|
@@ -1194,40 +1212,59 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
|
|
|
1194
1212
|
inline=False
|
|
1195
1213
|
))
|
|
1196
1214
|
|
|
1197
|
-
# Output
|
|
1215
|
+
# Output (pre-activation)
|
|
1198
1216
|
z2 = self.W2[0, 0] * self.a1[0] + self.W2[0, 1] * self.a1[1] + self.b2[0]
|
|
1199
1217
|
explanation.add_element(ContentAST.Equation(
|
|
1200
|
-
f"
|
|
1218
|
+
f"z_{{out}} = w_3 h_1 + w_4 h_2 + b_2 = {self.W2[0,0]:.1f} \\cdot {self.a1[0]:.4f} + {self.W2[0,1]:.1f} \\cdot {self.a1[1]:.4f} + {self.b2[0]:.1f} = {self.z2[0]:.4f}",
|
|
1219
|
+
inline=False
|
|
1220
|
+
))
|
|
1221
|
+
|
|
1222
|
+
# Output (sigmoid activation)
|
|
1223
|
+
explanation.add_element(ContentAST.Equation(
|
|
1224
|
+
f"\\hat{{y}} = \\sigma(z_{{out}}) = \\frac{{1}}{{1 + e^{{-{self.z2[0]:.4f}}}}} = {self.a2[0]:.4f}",
|
|
1201
1225
|
inline=False
|
|
1202
1226
|
))
|
|
1203
1227
|
|
|
1204
1228
|
# Step 2: Loss
|
|
1205
1229
|
explanation.add_element(ContentAST.Paragraph([
|
|
1206
|
-
"**Step 2: Calculate Loss**"
|
|
1230
|
+
"**Step 2: Calculate Loss (Binary Cross-Entropy)**"
|
|
1207
1231
|
]))
|
|
1208
1232
|
|
|
1233
|
+
# Show the full BCE formula first
|
|
1209
1234
|
explanation.add_element(ContentAST.Equation(
|
|
1210
|
-
f"L =
|
|
1235
|
+
f"L = -[y \\log(\\hat{{y}}) + (1-y) \\log(1-\\hat{{y}})]",
|
|
1211
1236
|
inline=False
|
|
1212
1237
|
))
|
|
1213
1238
|
|
|
1239
|
+
# Then evaluate it
|
|
1240
|
+
if self.y_target == 1:
|
|
1241
|
+
explanation.add_element(ContentAST.Equation(
|
|
1242
|
+
f"L = -[1 \\cdot \\log({self.a2[0]:.4f}) + 0 \\cdot \\log(1-{self.a2[0]:.4f})] = -\\log({self.a2[0]:.4f}) = {self.loss:.4f}",
|
|
1243
|
+
inline=False
|
|
1244
|
+
))
|
|
1245
|
+
else:
|
|
1246
|
+
explanation.add_element(ContentAST.Equation(
|
|
1247
|
+
f"L = -[0 \\cdot \\log({self.a2[0]:.4f}) + 1 \\cdot \\log(1-{self.a2[0]:.4f})] = -\\log({1-self.a2[0]:.4f}) = {self.loss:.4f}",
|
|
1248
|
+
inline=False
|
|
1249
|
+
))
|
|
1250
|
+
|
|
1214
1251
|
# Step 3: Gradients
|
|
1215
1252
|
explanation.add_element(ContentAST.Paragraph([
|
|
1216
1253
|
"**Step 3: Compute Gradients**"
|
|
1217
1254
|
]))
|
|
1218
1255
|
|
|
1219
1256
|
explanation.add_element(ContentAST.Paragraph([
|
|
1220
|
-
"
|
|
1257
|
+
"For BCE with sigmoid, the output layer gradient simplifies to:"
|
|
1221
1258
|
]))
|
|
1222
1259
|
|
|
1223
1260
|
explanation.add_element(ContentAST.Equation(
|
|
1224
|
-
f"\\frac{{\\partial L}}{{\\partial
|
|
1261
|
+
f"\\frac{{\\partial L}}{{\\partial z_{{out}}}} = \\hat{{y}} - y = {self.a2[0]:.4f} - {int(self.y_target)} = {self.dL_dz2:.4f}",
|
|
1225
1262
|
inline=False
|
|
1226
1263
|
))
|
|
1227
1264
|
|
|
1228
1265
|
grad_w3 = self._compute_gradient_W2(0)
|
|
1229
1266
|
explanation.add_element(ContentAST.Equation(
|
|
1230
|
-
f"\\frac{{\\partial L}}{{\\partial w_3}} = \\frac{{\\partial L}}{{\\partial
|
|
1267
|
+
f"\\frac{{\\partial L}}{{\\partial w_3}} = \\frac{{\\partial L}}{{\\partial z_{{out}}}} \\cdot h_1 = {self.dL_dz2:.4f} \\cdot {self.a1[0]:.4f} = {grad_w3:.4f}",
|
|
1231
1268
|
inline=False
|
|
1232
1269
|
))
|
|
1233
1270
|
|
|
@@ -1235,8 +1272,15 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
|
|
|
1235
1272
|
dz2_da1 = self.W2[0, 0]
|
|
1236
1273
|
da1_dz1 = self._activation_derivative(self.z1[0])
|
|
1237
1274
|
|
|
1275
|
+
if self.activation_function == self.ACTIVATION_SIGMOID:
|
|
1276
|
+
act_deriv_str = f"h_1(1-h_1)"
|
|
1277
|
+
elif self.activation_function == self.ACTIVATION_RELU:
|
|
1278
|
+
act_deriv_str = f"\\text{{ReLU}}'(z_1)"
|
|
1279
|
+
else:
|
|
1280
|
+
act_deriv_str = f"1"
|
|
1281
|
+
|
|
1238
1282
|
explanation.add_element(ContentAST.Equation(
|
|
1239
|
-
f"\\frac{{\\partial L}}{{\\partial w_{{11}}}} = \\frac{{\\partial L}}{{\\partial
|
|
1283
|
+
f"\\frac{{\\partial L}}{{\\partial w_{{11}}}} = \\frac{{\\partial L}}{{\\partial z_{{out}}}} \\cdot w_3 \\cdot {act_deriv_str} \\cdot x_1 = {self.dL_dz2:.4f} \\cdot {dz2_da1:.4f} \\cdot {da1_dz1:.4f} \\cdot {self.X[0]:.1f} = {grad_w11:.4f}",
|
|
1240
1284
|
inline=False
|
|
1241
1285
|
))
|
|
1242
1286
|
|
QuizGenerator/question.py
CHANGED
|
@@ -21,7 +21,7 @@ import yaml
|
|
|
21
21
|
from typing import List, Dict, Any, Tuple, Optional
|
|
22
22
|
import canvasapi.course, canvasapi.quiz
|
|
23
23
|
|
|
24
|
-
from QuizGenerator.misc import
|
|
24
|
+
from QuizGenerator.misc import Answer
|
|
25
25
|
from QuizGenerator.contentast import ContentAST
|
|
26
26
|
from QuizGenerator.performance import timer, PerformanceTracker
|
|
27
27
|
|
|
@@ -500,7 +500,8 @@ class Question(abc.ABC):
|
|
|
500
500
|
while not is_interesting:
|
|
501
501
|
# Increment seed for each backoff attempt to maintain deterministic behavior
|
|
502
502
|
current_seed = None if base_seed is None else base_seed + backoff_counter
|
|
503
|
-
|
|
503
|
+
# Pass config_params to refresh so custom kwargs from YAML are available
|
|
504
|
+
self.refresh(rng_seed=current_seed, hard_refresh=(backoff_counter > 0), **self.config_params)
|
|
504
505
|
is_interesting = self.is_interesting()
|
|
505
506
|
backoff_counter += 1
|
|
506
507
|
|
QuizGenerator/quiz.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: QuizGenerator
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Generate randomized quiz questions for Canvas LMS and PDF exams
|
|
5
5
|
Project-URL: Homepage, https://github.com/OtterDen-Lab/QuizGenerator
|
|
6
6
|
Project-URL: Documentation, https://github.com/OtterDen-Lab/QuizGenerator/tree/main/documentation
|
|
@@ -21,6 +21,7 @@ Requires-Dist: canvasapi==3.2.0
|
|
|
21
21
|
Requires-Dist: cryptography>=41.0.0
|
|
22
22
|
Requires-Dist: graphviz>=0.21
|
|
23
23
|
Requires-Dist: jinja2==3.1.3
|
|
24
|
+
Requires-Dist: keras>=3.12.0
|
|
24
25
|
Requires-Dist: markdown>=3.9
|
|
25
26
|
Requires-Dist: matplotlib
|
|
26
27
|
Requires-Dist: pylatex>=1.4.2
|
|
@@ -31,6 +32,7 @@ Requires-Dist: pyyaml==6.0.1
|
|
|
31
32
|
Requires-Dist: requests==2.32.2
|
|
32
33
|
Requires-Dist: segno>=1.6.0
|
|
33
34
|
Requires-Dist: sympy>=1.14.0
|
|
35
|
+
Requires-Dist: tensorflow>=2.20.0
|
|
34
36
|
Provides-Extra: grading
|
|
35
37
|
Requires-Dist: pillow>=10.0.0; extra == 'grading'
|
|
36
38
|
Requires-Dist: pyzbar>=0.1.9; extra == 'grading'
|
|
@@ -2,15 +2,15 @@ QuizGenerator/README.md,sha256=4n16gKyhIAKRBX4VKlpfcK0pyUYJ6Ht08MUsnwgxrZo,145
|
|
|
2
2
|
QuizGenerator/__init__.py,sha256=8EV-k90A3PNC8Cm2-ZquwNyVyvnwW1gs6u-nGictyhs,840
|
|
3
3
|
QuizGenerator/__main__.py,sha256=Dd9w4R0Unm3RiXztvR4Y_g9-lkWp6FHg-4VN50JbKxU,151
|
|
4
4
|
QuizGenerator/constants.py,sha256=AO-UWwsWPLb1k2JW6KP8rl9fxTcdT0rW-6XC6zfnDOs,4386
|
|
5
|
-
QuizGenerator/contentast.py,sha256=
|
|
5
|
+
QuizGenerator/contentast.py,sha256=Em4cnA64Y8_07VruJk_MXwiWcJwqT4-YVf-Lw7uIvYY,68327
|
|
6
6
|
QuizGenerator/generate.py,sha256=o2XezoSE0u-qjxYu1_Ofm9Lpkza7M2Tg47C-ClMcPsE,7197
|
|
7
7
|
QuizGenerator/logging.yaml,sha256=VJCdh26D8e_PNUs4McvvP1ojz9EVjQNifJzfhEk1Mbo,1114
|
|
8
|
-
QuizGenerator/misc.py,sha256=
|
|
9
|
-
QuizGenerator/mixins.py,sha256=
|
|
8
|
+
QuizGenerator/misc.py,sha256=JYv-SUn3y33O6grpgvRWRBkJi4RhTuXcMgrhmBjRbZg,18710
|
|
9
|
+
QuizGenerator/mixins.py,sha256=zUKTkswq7aoDZ_nGPUdRuvnza8iH8ZCi6IH2Uw-kCvs,18492
|
|
10
10
|
QuizGenerator/performance.py,sha256=CM3zLarJXN5Hfrl4-6JRBqD03j4BU1B2QW699HAr1Ds,7002
|
|
11
11
|
QuizGenerator/qrcode_generator.py,sha256=S3mzZDk2UiHiw6ipSCpWPMhbKvSRR1P5ordZJUTo6ug,10776
|
|
12
|
-
QuizGenerator/question.py,sha256=
|
|
13
|
-
QuizGenerator/quiz.py,sha256=
|
|
12
|
+
QuizGenerator/question.py,sha256=uxDYyrq17JFXQ11S03Px5cyRuPYn4qKT3z7TZn9XSjg,26093
|
|
13
|
+
QuizGenerator/quiz.py,sha256=toPodXea2UYGgAf4jyor3Gz-gtXYN1YUJFJFQ5u70v4,18718
|
|
14
14
|
QuizGenerator/typst_utils.py,sha256=XtMEO1e4_Tg0G1zR9D1fmrYKlUfHenBPdGoCKR0DhZg,3154
|
|
15
15
|
QuizGenerator/canvas/__init__.py,sha256=TwFP_zgxPIlWtkvIqQ6mcvBNTL9swIH_rJl7DGKcvkQ,286
|
|
16
16
|
QuizGenerator/canvas/canvas_interface.py,sha256=wsEWh2lonUMgmbtXF-Zj59CAM_0NInoaERqsujlYMfc,24501
|
|
@@ -20,10 +20,10 @@ QuizGenerator/premade_questions/basic.py,sha256=wAvVZED6a7VToIvSCdAx6SrExmR0xVRo
|
|
|
20
20
|
QuizGenerator/premade_questions/cst334/__init__.py,sha256=BTz-Os1XbwIRKqAilf2UIva2NlY0DbA_XbSIggO2Tdk,36
|
|
21
21
|
QuizGenerator/premade_questions/cst334/languages.py,sha256=N5vcmZi-AFM_BZypnvNogHD7s--28-j-8tykYg6CBzs,14388
|
|
22
22
|
QuizGenerator/premade_questions/cst334/math_questions.py,sha256=za8lNqhM0RB8qefmPP-Ww0WB_SQn0iRcBKOrZgyHCQQ,9290
|
|
23
|
-
QuizGenerator/premade_questions/cst334/memory_questions.py,sha256=
|
|
23
|
+
QuizGenerator/premade_questions/cst334/memory_questions.py,sha256=B4hpnMliJY-x65hNbjwbf22m-jiTi3WEXmauKv_YA84,51598
|
|
24
24
|
QuizGenerator/premade_questions/cst334/ostep13_vsfs.py,sha256=d9jjrynEw44vupAH_wKl57UoHooCNEJXaC5DoNYualk,16163
|
|
25
25
|
QuizGenerator/premade_questions/cst334/persistence_questions.py,sha256=em-HzFRnaroDmHl5uA771HyFMI7dMvG-gxTgsB3ecaY,14458
|
|
26
|
-
QuizGenerator/premade_questions/cst334/process.py,sha256=
|
|
26
|
+
QuizGenerator/premade_questions/cst334/process.py,sha256=EB0iuT9Q8FfOnmlQoXL7gkfsPyVJP55cRFOe2mWfamc,23647
|
|
27
27
|
QuizGenerator/premade_questions/cst463/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
28
|
QuizGenerator/premade_questions/cst463/gradient_descent/__init__.py,sha256=sH2CUV6zK9FT3jWTn453ys6_JTrUKRtZnU8hK6RmImU,240
|
|
29
29
|
QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py,sha256=ssj6Xkpw6vDiL4qwVOiHUhly3TX50oX4KJtouj7qN6g,12809
|
|
@@ -33,12 +33,19 @@ QuizGenerator/premade_questions/cst463/gradient_descent/misc.py,sha256=iB3obG6-u
|
|
|
33
33
|
QuizGenerator/premade_questions/cst463/math_and_data/__init__.py,sha256=EbIaUrx7_aK9j3Gd8Mk08h9GocTq_0OoNu2trfNwaU8,202
|
|
34
34
|
QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py,sha256=sq27xv7X9aa0axFxomusZRwM-ICj9grbhD_Bv3n3gJg,28947
|
|
35
35
|
QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py,sha256=tNxfR6J1cZHsHG9GfwVyl6lxxN_TEnhKDmMq4aVLwow,20793
|
|
36
|
+
QuizGenerator/premade_questions/cst463/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
+
QuizGenerator/premade_questions/cst463/models/attention.py,sha256=i8h6DihzJTc_QFrdm1eaYhnuhlXKRUv_vIDg3jk_LZ8,5502
|
|
38
|
+
QuizGenerator/premade_questions/cst463/models/cnns.py,sha256=iNuQmHgd8kwAXaofTwidal6pZum31r21UP3XERspE0M,5774
|
|
39
|
+
QuizGenerator/premade_questions/cst463/models/matrices.py,sha256=H61_8cF1DGCt4Z4Ssoi4SMClf6tD5wHkOqY5bMdsSt4,699
|
|
40
|
+
QuizGenerator/premade_questions/cst463/models/rnns.py,sha256=-tXeGgqPkctBBUy4RvEPqhv2kfPqoyO2wk-lNJLNWmY,6697
|
|
41
|
+
QuizGenerator/premade_questions/cst463/models/text.py,sha256=T1cmaoxNcWK1UbytFQmweL_Nmkm5ONq1m6F4AIJdmdc,6284
|
|
42
|
+
QuizGenerator/premade_questions/cst463/models/weight_counting.py,sha256=A2ZVyodfibAWQg9-KraT1cCPTLXRWy5vNSqL0GWexXg,6784
|
|
36
43
|
QuizGenerator/premade_questions/cst463/neural-network-basics/__init__.py,sha256=pmyCezO-20AFEQC6MR7KnAsaU9TcgZYsGQOMVkRZ-U8,149
|
|
37
|
-
QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py,sha256=
|
|
44
|
+
QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py,sha256=pyTSvibCaLuT0LnYAZfjUoZlzywaPWWAaSZ5VepH04E,44148
|
|
38
45
|
QuizGenerator/premade_questions/cst463/tensorflow-intro/__init__.py,sha256=G1gEHtG4KakYgi8ZXSYYhX6bQRtnm2tZVGx36d63Nmo,173
|
|
39
46
|
QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py,sha256=dPn8Sj0yk4m02np62esMKZ7CvcljhYq3Tq51nY9aJnA,29781
|
|
40
|
-
quizgenerator-0.
|
|
41
|
-
quizgenerator-0.
|
|
42
|
-
quizgenerator-0.
|
|
43
|
-
quizgenerator-0.
|
|
44
|
-
quizgenerator-0.
|
|
47
|
+
quizgenerator-0.3.0.dist-info/METADATA,sha256=qLE6mR5zXwWdFv5HYskA4KLgnya7VVRh85a7AY8lDRg,7212
|
|
48
|
+
quizgenerator-0.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
49
|
+
quizgenerator-0.3.0.dist-info/entry_points.txt,sha256=iViWMzswXGe88WKoue_Ib-ODUSiT_j_6f1us28w9pkc,56
|
|
50
|
+
quizgenerator-0.3.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
51
|
+
quizgenerator-0.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|