QuizGenerator 0.5.1__py3-none-any.whl → 0.6.1__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 +1056 -1231
- QuizGenerator/generate.py +174 -2
- QuizGenerator/misc.py +0 -6
- QuizGenerator/mixins.py +7 -8
- QuizGenerator/premade_questions/basic.py +3 -3
- QuizGenerator/premade_questions/cst334/languages.py +45 -51
- QuizGenerator/premade_questions/cst334/math_questions.py +9 -10
- QuizGenerator/premade_questions/cst334/memory_questions.py +39 -56
- QuizGenerator/premade_questions/cst334/persistence_questions.py +12 -27
- QuizGenerator/premade_questions/cst334/process.py +11 -22
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +11 -11
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +7 -7
- QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +6 -6
- QuizGenerator/premade_questions/cst463/gradient_descent/misc.py +2 -2
- QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +15 -19
- QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +149 -442
- QuizGenerator/premade_questions/cst463/models/attention.py +7 -8
- QuizGenerator/premade_questions/cst463/models/cnns.py +6 -7
- QuizGenerator/premade_questions/cst463/models/rnns.py +6 -6
- QuizGenerator/premade_questions/cst463/models/text.py +7 -8
- QuizGenerator/premade_questions/cst463/models/weight_counting.py +5 -9
- QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +22 -22
- QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +25 -25
- QuizGenerator/question.py +13 -14
- {quizgenerator-0.5.1.dist-info → quizgenerator-0.6.1.dist-info}/METADATA +1 -1
- {quizgenerator-0.5.1.dist-info → quizgenerator-0.6.1.dist-info}/RECORD +29 -29
- {quizgenerator-0.5.1.dist-info → quizgenerator-0.6.1.dist-info}/WHEEL +0 -0
- {quizgenerator-0.5.1.dist-info → quizgenerator-0.6.1.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.5.1.dist-info → quizgenerator-0.6.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,9 +5,8 @@ import keras
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
from typing import List, Tuple
|
|
7
7
|
|
|
8
|
-
from QuizGenerator.
|
|
9
|
-
from QuizGenerator.
|
|
10
|
-
from QuizGenerator.contentast import ContentAST
|
|
8
|
+
from QuizGenerator.question import Question, QuestionRegistry
|
|
9
|
+
from QuizGenerator.contentast import ContentAST, AnswerTypes
|
|
11
10
|
from QuizGenerator.constants import MathRanges
|
|
12
11
|
from QuizGenerator.mixins import TableQuestionMixin
|
|
13
12
|
|
|
@@ -62,12 +61,12 @@ class AttentionForwardPass(MatrixQuestion, TableQuestionMixin):
|
|
|
62
61
|
## Answers:
|
|
63
62
|
# Q, K, V, output, weights
|
|
64
63
|
|
|
65
|
-
self.answers["weights"] =
|
|
66
|
-
self.answers["output"] =
|
|
64
|
+
self.answers["weights"] = AnswerTypes.Matrix(self.weights, label="Weights")
|
|
65
|
+
self.answers["output"] = AnswerTypes.Matrix(self.output, label="Output")
|
|
67
66
|
|
|
68
67
|
return True
|
|
69
68
|
|
|
70
|
-
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
69
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
71
70
|
"""Build question body and collect answers."""
|
|
72
71
|
body = ContentAST.Section()
|
|
73
72
|
answers = []
|
|
@@ -101,10 +100,10 @@ class AttentionForwardPass(MatrixQuestion, TableQuestionMixin):
|
|
|
101
100
|
body, _ = self._get_body(**kwargs)
|
|
102
101
|
return body
|
|
103
102
|
|
|
104
|
-
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
103
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
105
104
|
"""Build question explanation."""
|
|
106
105
|
explanation = ContentAST.Section()
|
|
107
|
-
digits = Answer.DEFAULT_ROUNDING_DIGITS
|
|
106
|
+
digits = ContentAST.Answer.DEFAULT_ROUNDING_DIGITS
|
|
108
107
|
|
|
109
108
|
explanation.add_element(
|
|
110
109
|
ContentAST.Paragraph([
|
|
@@ -5,9 +5,8 @@ import keras
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
from typing import List, Tuple
|
|
7
7
|
|
|
8
|
-
from QuizGenerator.question import Question, QuestionRegistry
|
|
9
|
-
from QuizGenerator.
|
|
10
|
-
from QuizGenerator.contentast import ContentAST
|
|
8
|
+
from QuizGenerator.question import Question, QuestionRegistry
|
|
9
|
+
from QuizGenerator.contentast import ContentAST, AnswerTypes
|
|
11
10
|
from QuizGenerator.constants import MathRanges
|
|
12
11
|
from .matrices import MatrixQuestion
|
|
13
12
|
|
|
@@ -66,13 +65,13 @@ class ConvolutionCalculation(MatrixQuestion):
|
|
|
66
65
|
self.result = self.conv2d_multi_channel(self.image, self.kernel, stride=self.stride, padding=self.padding)
|
|
67
66
|
|
|
68
67
|
self.answers = {
|
|
69
|
-
f"result_{i}" :
|
|
68
|
+
f"result_{i}" : AnswerTypes.Matrix(self.result[:,:,i], label=f"Result of filter {i}")
|
|
70
69
|
for i in range(self.result.shape[-1])
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
return True
|
|
74
73
|
|
|
75
|
-
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
74
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
76
75
|
"""Build question body and collect answers."""
|
|
77
76
|
body = ContentAST.Section()
|
|
78
77
|
answers = []
|
|
@@ -119,10 +118,10 @@ class ConvolutionCalculation(MatrixQuestion):
|
|
|
119
118
|
body, _ = self._get_body(**kwargs)
|
|
120
119
|
return body
|
|
121
120
|
|
|
122
|
-
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
121
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
123
122
|
"""Build question explanation."""
|
|
124
123
|
explanation = ContentAST.Section()
|
|
125
|
-
digits = Answer.DEFAULT_ROUNDING_DIGITS
|
|
124
|
+
digits = ContentAST.Answer.DEFAULT_ROUNDING_DIGITS
|
|
126
125
|
|
|
127
126
|
explanation.add_element(
|
|
128
127
|
ContentAST.Paragraph([
|
|
@@ -6,8 +6,8 @@ import numpy as np
|
|
|
6
6
|
from typing import List, Tuple
|
|
7
7
|
|
|
8
8
|
from .matrices import MatrixQuestion
|
|
9
|
-
from QuizGenerator.question import Question, QuestionRegistry
|
|
10
|
-
from QuizGenerator.contentast import ContentAST
|
|
9
|
+
from QuizGenerator.question import Question, QuestionRegistry
|
|
10
|
+
from QuizGenerator.contentast import ContentAST, AnswerTypes
|
|
11
11
|
from QuizGenerator.constants import MathRanges
|
|
12
12
|
from QuizGenerator.mixins import TableQuestionMixin
|
|
13
13
|
|
|
@@ -64,11 +64,11 @@ class RNNForwardPass(MatrixQuestion, TableQuestionMixin):
|
|
|
64
64
|
## Answers:
|
|
65
65
|
# x_seq, W_xh, W_hh, b_h, h_0, h_states
|
|
66
66
|
|
|
67
|
-
self.answers["output_sequence"] =
|
|
67
|
+
self.answers["output_sequence"] = AnswerTypes.Matrix(value=self.h_states, label="Hidden states")
|
|
68
68
|
|
|
69
69
|
return True
|
|
70
70
|
|
|
71
|
-
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
71
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
72
72
|
"""Build question body and collect answers."""
|
|
73
73
|
body = ContentAST.Section()
|
|
74
74
|
answers = []
|
|
@@ -103,10 +103,10 @@ class RNNForwardPass(MatrixQuestion, TableQuestionMixin):
|
|
|
103
103
|
body, _ = self._get_body(**kwargs)
|
|
104
104
|
return body
|
|
105
105
|
|
|
106
|
-
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
106
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
107
107
|
"""Build question explanation."""
|
|
108
108
|
explanation = ContentAST.Section()
|
|
109
|
-
digits = Answer.DEFAULT_ROUNDING_DIGITS
|
|
109
|
+
digits = ContentAST.Answer.DEFAULT_ROUNDING_DIGITS
|
|
110
110
|
|
|
111
111
|
explanation.add_element(
|
|
112
112
|
ContentAST.Paragraph([
|
|
@@ -5,10 +5,9 @@ import keras
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
from typing import List, Tuple
|
|
7
7
|
|
|
8
|
-
from QuizGenerator.misc import MatrixAnswer
|
|
9
8
|
from QuizGenerator.premade_questions.cst463.models.matrices import MatrixQuestion
|
|
10
|
-
from QuizGenerator.question import Question, QuestionRegistry
|
|
11
|
-
from QuizGenerator.contentast import ContentAST
|
|
9
|
+
from QuizGenerator.question import Question, QuestionRegistry
|
|
10
|
+
from QuizGenerator.contentast import ContentAST, AnswerTypes
|
|
12
11
|
from QuizGenerator.constants import MathRanges
|
|
13
12
|
from QuizGenerator.mixins import TableQuestionMixin
|
|
14
13
|
|
|
@@ -60,15 +59,15 @@ class word2vec__skipgram(MatrixQuestion, TableQuestionMixin):
|
|
|
60
59
|
|
|
61
60
|
## Answers:
|
|
62
61
|
# center_word, center_emb, context_words, context_embs, logits, probs
|
|
63
|
-
self.answers["logits"] =
|
|
62
|
+
self.answers["logits"] = AnswerTypes.Vector(self.logits, label="Logits")
|
|
64
63
|
most_likely_idx = np.argmax(self.probs)
|
|
65
64
|
most_likely_word = self.context_words[most_likely_idx]
|
|
66
|
-
self.answers["center_word"] =
|
|
65
|
+
self.answers["center_word"] = AnswerTypes.String(most_likely_word, label="Most likely context word")
|
|
67
66
|
|
|
68
67
|
|
|
69
68
|
return True
|
|
70
69
|
|
|
71
|
-
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
70
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
72
71
|
"""Build question body and collect answers."""
|
|
73
72
|
body = ContentAST.Section()
|
|
74
73
|
answers = []
|
|
@@ -101,10 +100,10 @@ class word2vec__skipgram(MatrixQuestion, TableQuestionMixin):
|
|
|
101
100
|
body, _ = self._get_body(**kwargs)
|
|
102
101
|
return body
|
|
103
102
|
|
|
104
|
-
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
103
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
105
104
|
"""Build question explanation."""
|
|
106
105
|
explanation = ContentAST.Section()
|
|
107
|
-
digits = Answer.DEFAULT_ROUNDING_DIGITS
|
|
106
|
+
digits = ContentAST.Answer.DEFAULT_ROUNDING_DIGITS
|
|
108
107
|
|
|
109
108
|
explanation.add_element(
|
|
110
109
|
ContentAST.Paragraph([
|
|
@@ -5,8 +5,8 @@ import keras
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
from typing import List, Tuple
|
|
7
7
|
|
|
8
|
-
from QuizGenerator.question import Question, QuestionRegistry
|
|
9
|
-
from QuizGenerator.contentast import ContentAST
|
|
8
|
+
from QuizGenerator.question import Question, QuestionRegistry
|
|
9
|
+
from QuizGenerator.contentast import ContentAST, AnswerTypes
|
|
10
10
|
from QuizGenerator.constants import MathRanges
|
|
11
11
|
|
|
12
12
|
log = logging.getLogger(__name__)
|
|
@@ -85,15 +85,11 @@ class WeightCounting(Question, abc.ABC):
|
|
|
85
85
|
continue
|
|
86
86
|
|
|
87
87
|
self.num_parameters = self.model.count_params()
|
|
88
|
-
self.answers["num_parameters"] =
|
|
89
|
-
"num_parameters",
|
|
90
|
-
self.num_parameters,
|
|
91
|
-
label="Number of Parameters"
|
|
92
|
-
)
|
|
88
|
+
self.answers["num_parameters"] = AnswerTypes.Int(self.num_parameters, label="Number of Parameters")
|
|
93
89
|
|
|
94
90
|
return True
|
|
95
91
|
|
|
96
|
-
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
92
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
97
93
|
"""Build question body and collect answers."""
|
|
98
94
|
body = ContentAST.Section()
|
|
99
95
|
answers = []
|
|
@@ -127,7 +123,7 @@ class WeightCounting(Question, abc.ABC):
|
|
|
127
123
|
body, _ = self._get_body(**kwargs)
|
|
128
124
|
return body
|
|
129
125
|
|
|
130
|
-
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
126
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
131
127
|
"""Build question explanation."""
|
|
132
128
|
explanation = ContentAST.Section()
|
|
133
129
|
|
|
@@ -12,8 +12,8 @@ from typing import List, Tuple, Dict, Any
|
|
|
12
12
|
import matplotlib.pyplot as plt
|
|
13
13
|
import matplotlib.patches as mpatches
|
|
14
14
|
|
|
15
|
-
from QuizGenerator.contentast import ContentAST
|
|
16
|
-
from QuizGenerator.question import Question,
|
|
15
|
+
from QuizGenerator.contentast import ContentAST, AnswerTypes
|
|
16
|
+
from QuizGenerator.question import Question, QuestionRegistry
|
|
17
17
|
from QuizGenerator.mixins import TableQuestionMixin, BodyTemplatesMixin
|
|
18
18
|
from ..models.matrices import MatrixQuestion
|
|
19
19
|
|
|
@@ -566,12 +566,12 @@ class ForwardPassQuestion(SimpleNeuralNetworkBase):
|
|
|
566
566
|
# Hidden layer activations
|
|
567
567
|
for i in range(self.num_hidden):
|
|
568
568
|
key = f"h{i+1}"
|
|
569
|
-
self.answers[key] =
|
|
569
|
+
self.answers[key] = AnswerTypes.Float(float(self.a1[i]), label=f"h_{i + 1}")
|
|
570
570
|
|
|
571
571
|
# Output
|
|
572
|
-
self.answers["y_pred"] =
|
|
572
|
+
self.answers["y_pred"] = AnswerTypes.Float(float(self.a2[0]), label="ŷ")
|
|
573
573
|
|
|
574
|
-
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
574
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
575
575
|
"""Build question body and collect answers."""
|
|
576
576
|
body = ContentAST.Section()
|
|
577
577
|
answers = []
|
|
@@ -614,7 +614,7 @@ class ForwardPassQuestion(SimpleNeuralNetworkBase):
|
|
|
614
614
|
body, _ = self._get_body(**kwargs)
|
|
615
615
|
return body
|
|
616
616
|
|
|
617
|
-
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
617
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
618
618
|
"""Build question explanation."""
|
|
619
619
|
explanation = ContentAST.Section()
|
|
620
620
|
|
|
@@ -743,14 +743,14 @@ class BackpropGradientQuestion(SimpleNeuralNetworkBase):
|
|
|
743
743
|
# Gradient for W2 (hidden to output)
|
|
744
744
|
for i in range(self.num_hidden):
|
|
745
745
|
key = f"dL_dw2_{i}"
|
|
746
|
-
self.answers[key] =
|
|
746
|
+
self.answers[key] = AnswerTypes.Float(self._compute_gradient_W2(i), label=f"∂L/∂w_{i + 3}")
|
|
747
747
|
|
|
748
748
|
# Gradient for W1 (input to hidden) - pick first hidden neuron
|
|
749
749
|
for j in range(self.num_inputs):
|
|
750
750
|
key = f"dL_dw1_0{j}"
|
|
751
|
-
self.answers[key] =
|
|
751
|
+
self.answers[key] = AnswerTypes.Float(self._compute_gradient_W1(0, j), label=f"∂L/∂w_1{j + 1}")
|
|
752
752
|
|
|
753
|
-
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
753
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
754
754
|
"""Build question body and collect answers."""
|
|
755
755
|
body = ContentAST.Section()
|
|
756
756
|
answers = []
|
|
@@ -800,7 +800,7 @@ class BackpropGradientQuestion(SimpleNeuralNetworkBase):
|
|
|
800
800
|
body, _ = self._get_body(**kwargs)
|
|
801
801
|
return body
|
|
802
802
|
|
|
803
|
-
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
803
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
804
804
|
"""Build question explanation."""
|
|
805
805
|
explanation = ContentAST.Section()
|
|
806
806
|
|
|
@@ -920,13 +920,13 @@ class EnsembleAveragingQuestion(Question):
|
|
|
920
920
|
|
|
921
921
|
# Mean prediction
|
|
922
922
|
mean_pred = np.mean(self.predictions)
|
|
923
|
-
self.answers["mean"] =
|
|
923
|
+
self.answers["mean"] = AnswerTypes.Float(float(mean_pred), label="Mean (average)")
|
|
924
924
|
|
|
925
925
|
# Median (optional, but useful)
|
|
926
926
|
median_pred = np.median(self.predictions)
|
|
927
|
-
self.answers["median"] =
|
|
927
|
+
self.answers["median"] = AnswerTypes.Float(float(median_pred), label="Median")
|
|
928
928
|
|
|
929
|
-
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
929
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
930
930
|
"""Build question body and collect answers."""
|
|
931
931
|
body = ContentAST.Section()
|
|
932
932
|
answers = []
|
|
@@ -961,7 +961,7 @@ class EnsembleAveragingQuestion(Question):
|
|
|
961
961
|
body, _ = self._get_body(**kwargs)
|
|
962
962
|
return body
|
|
963
963
|
|
|
964
|
-
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
964
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
965
965
|
"""Build question explanation."""
|
|
966
966
|
explanation = ContentAST.Section()
|
|
967
967
|
|
|
@@ -1083,20 +1083,20 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
|
|
|
1083
1083
|
self.answers = {}
|
|
1084
1084
|
|
|
1085
1085
|
# Forward pass answers
|
|
1086
|
-
self.answers["y_pred"] =
|
|
1086
|
+
self.answers["y_pred"] = AnswerTypes.Float(float(self.a2[0]), label="1. Forward Pass - Network output ŷ")
|
|
1087
1087
|
|
|
1088
1088
|
# Loss answer
|
|
1089
|
-
self.answers["loss"] =
|
|
1089
|
+
self.answers["loss"] = AnswerTypes.Float(float(self.loss), label="2. Loss")
|
|
1090
1090
|
|
|
1091
1091
|
# Gradient answers (for key weights)
|
|
1092
|
-
self.answers["grad_w3"] =
|
|
1093
|
-
self.answers["grad_w11"] =
|
|
1092
|
+
self.answers["grad_w3"] = AnswerTypes.Float(self._compute_gradient_W2(0), label="3. Gradient ∂L/∂w₃")
|
|
1093
|
+
self.answers["grad_w11"] = AnswerTypes.Float(self._compute_gradient_W1(0, 0), label="4. Gradient ∂L/∂w₁₁")
|
|
1094
1094
|
|
|
1095
1095
|
# Updated weight answers
|
|
1096
|
-
self.answers["new_w3"] =
|
|
1097
|
-
self.answers["new_w11"] =
|
|
1096
|
+
self.answers["new_w3"] = AnswerTypes.Float(float(self.new_W2[0, 0]), label="5. Updated w₃:")
|
|
1097
|
+
self.answers["new_w11"] = AnswerTypes.Float(float(self.new_W1[0, 0]), label="6. Updated w₁₁:")
|
|
1098
1098
|
|
|
1099
|
-
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
1099
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
1100
1100
|
"""Build question body and collect answers."""
|
|
1101
1101
|
body = ContentAST.Section()
|
|
1102
1102
|
answers = []
|
|
@@ -1162,7 +1162,7 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
|
|
|
1162
1162
|
body, _ = self._get_body(**kwargs)
|
|
1163
1163
|
return body
|
|
1164
1164
|
|
|
1165
|
-
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
1165
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
1166
1166
|
"""Build question explanation."""
|
|
1167
1167
|
explanation = ContentAST.Section()
|
|
1168
1168
|
|
|
@@ -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,15 +85,15 @@ 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 _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
96
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
97
97
|
"""Build question body and collect answers."""
|
|
98
98
|
body = ContentAST.Section()
|
|
99
99
|
answers = []
|
|
@@ -152,7 +152,7 @@ class ParameterCountingQuestion(Question):
|
|
|
152
152
|
body, _ = self._get_body(**kwargs)
|
|
153
153
|
return body
|
|
154
154
|
|
|
155
|
-
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
155
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
156
156
|
"""Build question explanation."""
|
|
157
157
|
explanation = ContentAST.Section()
|
|
158
158
|
|
|
@@ -331,14 +331,14 @@ class ActivationFunctionComputationQuestion(Question):
|
|
|
331
331
|
|
|
332
332
|
if self.activation == self.ACTIVATION_SOFTMAX:
|
|
333
333
|
# Softmax: single vector answer
|
|
334
|
-
self.answers["output"] =
|
|
334
|
+
self.answers["output"] = AnswerTypes.Vector(self.output_vector, label="Output vector")
|
|
335
335
|
else:
|
|
336
336
|
# Element-wise: individual answers
|
|
337
337
|
for i, output in enumerate(self.output_vector):
|
|
338
338
|
key = f"output_{i}"
|
|
339
|
-
self.answers[key] =
|
|
339
|
+
self.answers[key] = AnswerTypes.Float(float(output), label=f"Output for input {self.input_vector[i]:.1f}")
|
|
340
340
|
|
|
341
|
-
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
341
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
342
342
|
"""Build question body and collect answers."""
|
|
343
343
|
body = ContentAST.Section()
|
|
344
344
|
answers = []
|
|
@@ -399,7 +399,7 @@ class ActivationFunctionComputationQuestion(Question):
|
|
|
399
399
|
body, _ = self._get_body(**kwargs)
|
|
400
400
|
return body
|
|
401
401
|
|
|
402
|
-
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
402
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
403
403
|
"""Build question explanation."""
|
|
404
404
|
explanation = ContentAST.Section()
|
|
405
405
|
|
|
@@ -545,13 +545,13 @@ class RegularizationCalculationQuestion(Question):
|
|
|
545
545
|
"""Create answer fields."""
|
|
546
546
|
self.answers = {}
|
|
547
547
|
|
|
548
|
-
self.answers["prediction"] =
|
|
549
|
-
self.answers["base_loss"] =
|
|
550
|
-
self.answers["l2_penalty"] =
|
|
551
|
-
self.answers["total_loss"] =
|
|
552
|
-
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₀")
|
|
553
553
|
|
|
554
|
-
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
554
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
555
555
|
"""Build question body and collect answers."""
|
|
556
556
|
body = ContentAST.Section()
|
|
557
557
|
answers = []
|
|
@@ -643,7 +643,7 @@ class RegularizationCalculationQuestion(Question):
|
|
|
643
643
|
body, _ = self._get_body(**kwargs)
|
|
644
644
|
return body
|
|
645
645
|
|
|
646
|
-
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
646
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
647
647
|
"""Build question explanation."""
|
|
648
648
|
explanation = ContentAST.Section()
|
|
649
649
|
|
|
@@ -817,16 +817,16 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
|
|
|
817
817
|
self.answers = {}
|
|
818
818
|
|
|
819
819
|
# New velocity
|
|
820
|
-
self.answers["velocity"] =
|
|
820
|
+
self.answers["velocity"] = AnswerTypes.Vector(self.new_velocity, label="New velocity")
|
|
821
821
|
|
|
822
822
|
# New weights with momentum
|
|
823
|
-
self.answers["weights_momentum"] =
|
|
823
|
+
self.answers["weights_momentum"] = AnswerTypes.Vector(self.new_weights, label="Weights (momentum)")
|
|
824
824
|
|
|
825
825
|
# Vanilla SGD weights for comparison
|
|
826
826
|
if self.show_vanilla_sgd:
|
|
827
|
-
self.answers["weights_sgd"] =
|
|
827
|
+
self.answers["weights_sgd"] = AnswerTypes.Vector(self.sgd_weights, label="Weights (vanilla SGD)")
|
|
828
828
|
|
|
829
|
-
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
829
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
830
830
|
"""Build question body and collect answers."""
|
|
831
831
|
body = ContentAST.Section()
|
|
832
832
|
answers = []
|
|
@@ -920,7 +920,7 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
|
|
|
920
920
|
body, _ = self._get_body(**kwargs)
|
|
921
921
|
return body
|
|
922
922
|
|
|
923
|
-
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
923
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
|
|
924
924
|
"""Build question explanation."""
|
|
925
925
|
explanation = ContentAST.Section()
|
|
926
926
|
|
|
@@ -944,7 +944,7 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
|
|
|
944
944
|
))
|
|
945
945
|
|
|
946
946
|
# Show calculation for each component
|
|
947
|
-
digits = Answer.DEFAULT_ROUNDING_DIGITS
|
|
947
|
+
digits = ContentAST.Answer.DEFAULT_ROUNDING_DIGITS
|
|
948
948
|
for i in range(self.num_variables):
|
|
949
949
|
var_name = f"x_{i}"
|
|
950
950
|
# Round all intermediate values to avoid floating point precision issues
|
QuizGenerator/question.py
CHANGED
|
@@ -21,8 +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.
|
|
25
|
-
from QuizGenerator.contentast import ContentAST
|
|
24
|
+
from QuizGenerator.contentast import ContentAST, AnswerTypes
|
|
26
25
|
from QuizGenerator.performance import timer, PerformanceTracker
|
|
27
26
|
|
|
28
27
|
import logging
|
|
@@ -33,7 +32,7 @@ log = logging.getLogger(__name__)
|
|
|
33
32
|
class QuestionComponents:
|
|
34
33
|
"""Bundle of question parts generated during construction."""
|
|
35
34
|
body: ContentAST.Element
|
|
36
|
-
answers: List[Answer]
|
|
35
|
+
answers: List[ContentAST.Answer]
|
|
37
36
|
explanation: ContentAST.Element
|
|
38
37
|
|
|
39
38
|
|
|
@@ -352,8 +351,8 @@ class Question(abc.ABC):
|
|
|
352
351
|
ensures consistent rendering across PDF/LaTeX and Canvas/HTML formats.
|
|
353
352
|
|
|
354
353
|
Required Methods:
|
|
355
|
-
- _get_body(): Return Tuple[ContentAST.Section, List[Answer]] with body and answers
|
|
356
|
-
- _get_explanation(): Return Tuple[ContentAST.Section, List[Answer]] with explanation
|
|
354
|
+
- _get_body(): Return Tuple[ContentAST.Section, List[ContentAST.Answer]] with body and answers
|
|
355
|
+
- _get_explanation(): Return Tuple[ContentAST.Section, List[ContentAST.Answer]] with explanation
|
|
357
356
|
|
|
358
357
|
Note: get_body() and get_explanation() are provided for backward compatibility
|
|
359
358
|
and call the _get_* methods, returning just the first element of the tuple.
|
|
@@ -373,7 +372,7 @@ class Question(abc.ABC):
|
|
|
373
372
|
body.add_element(ContentAST.Matrix(data=matrix_data, bracket_type="b"))
|
|
374
373
|
|
|
375
374
|
# Answer extends ContentAST.Leaf - add directly to body
|
|
376
|
-
ans = Answer.integer("result", 42, label="Result")
|
|
375
|
+
ans = ContentAST.Answer.integer("result", 42, label="Result")
|
|
377
376
|
answers.append(ans)
|
|
378
377
|
body.add_element(ans)
|
|
379
378
|
return body, answers
|
|
@@ -474,7 +473,7 @@ class Question(abc.ABC):
|
|
|
474
473
|
self.points_value = points_value
|
|
475
474
|
self.topic = topic
|
|
476
475
|
self.spacing = parse_spacing(kwargs.get("spacing", 0))
|
|
477
|
-
self.answer_kind = Answer.
|
|
476
|
+
self.answer_kind = ContentAST.Answer.CanvasAnswerKind.BLANK
|
|
478
477
|
|
|
479
478
|
# Support for multi-part questions (defaults to 1 for normal questions)
|
|
480
479
|
self.num_subquestions = kwargs.get("num_subquestions", 1)
|
|
@@ -600,7 +599,7 @@ class Question(abc.ABC):
|
|
|
600
599
|
[ContentAST.Text("[Please reach out to your professor for clarification]")]
|
|
601
600
|
)
|
|
602
601
|
|
|
603
|
-
def _get_body(self) -> Tuple[ContentAST.Element, List[Answer]]:
|
|
602
|
+
def _get_body(self) -> Tuple[ContentAST.Element, List[ContentAST.Answer]]:
|
|
604
603
|
"""
|
|
605
604
|
Build question body and collect answers (new pattern).
|
|
606
605
|
Questions should override this to return (body, answers) tuple.
|
|
@@ -612,7 +611,7 @@ class Question(abc.ABC):
|
|
|
612
611
|
body = self.get_body()
|
|
613
612
|
return body, []
|
|
614
613
|
|
|
615
|
-
def _get_explanation(self) -> Tuple[ContentAST.Element, List[Answer]]:
|
|
614
|
+
def _get_explanation(self) -> Tuple[ContentAST.Element, List[ContentAST.Answer]]:
|
|
616
615
|
"""
|
|
617
616
|
Build question explanation and collect answers (new pattern).
|
|
618
617
|
Questions can override this to include answers in explanations.
|
|
@@ -646,7 +645,7 @@ class Question(abc.ABC):
|
|
|
646
645
|
explanation=explanation
|
|
647
646
|
)
|
|
648
647
|
|
|
649
|
-
def get_answers(self, *args, **kwargs) -> Tuple[Answer.
|
|
648
|
+
def get_answers(self, *args, **kwargs) -> Tuple[ContentAST.Answer.CanvasAnswerKind, List[Dict[str,Any]]]:
|
|
650
649
|
"""
|
|
651
650
|
Return answers from cached components (new pattern) or self.answers dict (old pattern).
|
|
652
651
|
"""
|
|
@@ -664,7 +663,7 @@ class Question(abc.ABC):
|
|
|
664
663
|
answers = self._components.answers
|
|
665
664
|
if self.can_be_numerical():
|
|
666
665
|
return (
|
|
667
|
-
Answer.
|
|
666
|
+
ContentAST.Answer.CanvasAnswerKind.NUMERICAL_QUESTION,
|
|
668
667
|
list(itertools.chain(*[a.get_for_canvas(single_answer=True) for a in answers]))
|
|
669
668
|
)
|
|
670
669
|
return (
|
|
@@ -676,7 +675,7 @@ class Question(abc.ABC):
|
|
|
676
675
|
if len(self.answers.values()) > 0:
|
|
677
676
|
if self.can_be_numerical():
|
|
678
677
|
return (
|
|
679
|
-
Answer.
|
|
678
|
+
ContentAST.Answer.CanvasAnswerKind.NUMERICAL_QUESTION,
|
|
680
679
|
list(itertools.chain(*[a.get_for_canvas(single_answer=True) for a in self.answers.values()]))
|
|
681
680
|
)
|
|
682
681
|
return (
|
|
@@ -684,7 +683,7 @@ class Question(abc.ABC):
|
|
|
684
683
|
list(itertools.chain(*[a.get_for_canvas() for a in self.answers.values()]))
|
|
685
684
|
)
|
|
686
685
|
|
|
687
|
-
return (Answer.
|
|
686
|
+
return (ContentAST.Answer.CanvasAnswerKind.ESSAY, [])
|
|
688
687
|
|
|
689
688
|
def refresh(self, rng_seed=None, *args, **kwargs):
|
|
690
689
|
"""If it is necessary to regenerate aspects between usages, this is the time to do it.
|
|
@@ -751,7 +750,7 @@ class Question(abc.ABC):
|
|
|
751
750
|
|
|
752
751
|
def can_be_numerical(self):
|
|
753
752
|
if (len(self.answers.values()) == 1
|
|
754
|
-
and list(self.answers.values())[0]
|
|
753
|
+
and isinstance(list(self.answers.values())[0], AnswerTypes.Float)
|
|
755
754
|
):
|
|
756
755
|
return True
|
|
757
756
|
return False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: QuizGenerator
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
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
|