QuizGenerator 0.7.0__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 +128 -19
- {quizgenerator-0.7.0.dist-info → quizgenerator-0.8.0.dist-info}/METADATA +30 -2
- {quizgenerator-0.7.0.dist-info → quizgenerator-0.8.0.dist-info}/RECORD +30 -30
- {quizgenerator-0.7.0.dist-info → quizgenerator-0.8.0.dist-info}/WHEEL +0 -0
- {quizgenerator-0.7.0.dist-info → quizgenerator-0.8.0.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.7.0.dist-info → quizgenerator-0.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,26 +2,28 @@
|
|
|
2
2
|
import abc
|
|
3
3
|
import logging
|
|
4
4
|
import math
|
|
5
|
+
import random
|
|
5
6
|
from typing import List
|
|
6
7
|
|
|
7
8
|
from QuizGenerator.question import Question, QuestionRegistry
|
|
8
9
|
import QuizGenerator.contentast as ca
|
|
9
|
-
from QuizGenerator.mixins import MathOperationQuestion
|
|
10
10
|
|
|
11
11
|
log = logging.getLogger(__name__)
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class VectorMathQuestion(
|
|
14
|
+
class VectorMathQuestion(Question):
|
|
15
15
|
|
|
16
16
|
def __init__(self, *args, **kwargs):
|
|
17
17
|
kwargs["topic"] = kwargs.get("topic", Question.Topic.MATH)
|
|
18
18
|
super().__init__(*args, **kwargs)
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
@staticmethod
|
|
21
|
+
def _generate_vector(rng, dimension, min_val=-10, max_val=10):
|
|
21
22
|
"""Generate a vector with random integer values."""
|
|
22
|
-
return [
|
|
23
|
+
return [rng.randint(min_val, max_val) for _ in range(dimension)]
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
@staticmethod
|
|
26
|
+
def _format_vector(vector):
|
|
25
27
|
"""Return a ca.Matrix element for the vector (format-independent).
|
|
26
28
|
|
|
27
29
|
The Matrix element will render appropriately for each output format:
|
|
@@ -33,66 +35,42 @@ class VectorMathQuestion(MathOperationQuestion, Question):
|
|
|
33
35
|
matrix_data = [[v] for v in vector]
|
|
34
36
|
return ca.Matrix(data=matrix_data, bracket_type="b")
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
@staticmethod
|
|
39
|
+
def _format_vector_inline(vector):
|
|
37
40
|
"""Format vector for inline display."""
|
|
38
41
|
elements = [str(v) for v in vector]
|
|
39
42
|
return f"({', '.join(elements)})"
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# Vector-specific overrides
|
|
62
|
-
|
|
63
|
-
def refresh(self, *args, **kwargs):
|
|
64
|
-
# Generate vector dimension first
|
|
65
|
-
self.dimension = self.rng.randint(self.MIN_DIMENSION, self.MAX_DIMENSION)
|
|
66
|
-
|
|
67
|
-
# Call parent refresh which will use our generate_operands method
|
|
68
|
-
super().refresh(*args, **kwargs)
|
|
69
|
-
|
|
70
|
-
self.vector_a = self.operand_a
|
|
71
|
-
self.vector_b = self.operand_b
|
|
72
|
-
|
|
73
|
-
def generate_subquestion_data(self):
|
|
74
|
-
"""Generate LaTeX content for each subpart of the question.
|
|
75
|
-
Override to handle vector-specific keys in subquestion_data."""
|
|
76
|
-
subparts = []
|
|
77
|
-
for data in self.subquestion_data:
|
|
78
|
-
# Map generic operand names to vector names for compatibility
|
|
79
|
-
vector_a = data.get('vector_a', data['operand_a'])
|
|
80
|
-
vector_b = data.get('vector_b', data['operand_b'])
|
|
81
|
-
|
|
82
|
-
vector_a_latex = self._format_vector(vector_a)
|
|
83
|
-
vector_b_latex = self._format_vector(vector_b)
|
|
84
|
-
# Return as tuple of (matrix_a, operator, matrix_b)
|
|
85
|
-
subparts.append((vector_a_latex, self.get_operator(), vector_b_latex))
|
|
86
|
-
return subparts
|
|
44
|
+
@classmethod
|
|
45
|
+
def _build_context(cls, *, rng_seed=None, **kwargs):
|
|
46
|
+
rng = random.Random(rng_seed)
|
|
47
|
+
num_subquestions = kwargs.get("num_subquestions", 1)
|
|
48
|
+
if num_subquestions > 1:
|
|
49
|
+
raise NotImplementedError("Multipart not supported")
|
|
50
|
+
|
|
51
|
+
dimension = kwargs.get("dimension", rng.randint(cls.MIN_DIMENSION, cls.MAX_DIMENSION))
|
|
52
|
+
vector_a = cls._generate_vector(rng, dimension)
|
|
53
|
+
vector_b = cls._generate_vector(rng, dimension)
|
|
54
|
+
result = cls.calculate_single_result(vector_a, vector_b)
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
"dimension": dimension,
|
|
58
|
+
"vector_a": vector_a,
|
|
59
|
+
"vector_b": vector_b,
|
|
60
|
+
"result": result,
|
|
61
|
+
"num_subquestions": num_subquestions,
|
|
62
|
+
}
|
|
87
63
|
|
|
88
64
|
# Abstract methods that subclasses must still implement
|
|
65
|
+
@staticmethod
|
|
89
66
|
@abc.abstractmethod
|
|
90
|
-
def get_operator(
|
|
67
|
+
def get_operator():
|
|
91
68
|
"""Return the LaTeX operator for this operation."""
|
|
92
69
|
pass
|
|
93
70
|
|
|
71
|
+
@staticmethod
|
|
94
72
|
@abc.abstractmethod
|
|
95
|
-
def calculate_single_result(
|
|
73
|
+
def calculate_single_result(vector_a, vector_b):
|
|
96
74
|
"""Calculate the result for a single question with two vectors."""
|
|
97
75
|
pass
|
|
98
76
|
|
|
@@ -108,27 +86,32 @@ class VectorAddition(VectorMathQuestion):
|
|
|
108
86
|
MIN_DIMENSION = 2
|
|
109
87
|
MAX_DIMENSION = 4
|
|
110
88
|
|
|
111
|
-
|
|
89
|
+
@staticmethod
|
|
90
|
+
def get_operator():
|
|
112
91
|
return "+"
|
|
113
92
|
|
|
114
|
-
|
|
93
|
+
@staticmethod
|
|
94
|
+
def calculate_single_result(vector_a, vector_b):
|
|
115
95
|
return [vector_a[i] + vector_b[i] for i in range(len(vector_a))]
|
|
116
96
|
|
|
117
97
|
def create_subquestion_answers(self, subpart_index, result):
|
|
118
98
|
raise NotImplementedError("Multipart not supported")
|
|
119
99
|
|
|
120
100
|
def create_single_answers(self, result):
|
|
121
|
-
|
|
101
|
+
answer = ca.AnswerTypes.Vector(result)
|
|
102
|
+
self._single_answer = answer
|
|
103
|
+
return [answer]
|
|
122
104
|
|
|
123
|
-
|
|
105
|
+
@classmethod
|
|
106
|
+
def _build_body(cls, context):
|
|
124
107
|
"""Build question body and collect answers."""
|
|
125
108
|
body = ca.Section()
|
|
126
109
|
|
|
127
|
-
body.add_element(ca.Paragraph([
|
|
110
|
+
body.add_element(ca.Paragraph(["Calculate the following:"]))
|
|
128
111
|
|
|
129
112
|
# Equation display using MathExpression for format-independent rendering
|
|
130
|
-
vector_a_elem =
|
|
131
|
-
vector_b_elem =
|
|
113
|
+
vector_a_elem = cls._format_vector(context["vector_a"])
|
|
114
|
+
vector_b_elem = cls._format_vector(context["vector_b"])
|
|
132
115
|
body.add_element(ca.MathExpression([
|
|
133
116
|
vector_a_elem,
|
|
134
117
|
" + ",
|
|
@@ -136,24 +119,27 @@ class VectorAddition(VectorMathQuestion):
|
|
|
136
119
|
" = "
|
|
137
120
|
]))
|
|
138
121
|
|
|
139
|
-
|
|
140
|
-
answer = self.answers["result"]
|
|
122
|
+
answer = ca.AnswerTypes.Vector(context["result"])
|
|
141
123
|
body.add_element(ca.OnlyHtml([ca.Paragraph(["Enter your answer as a column vector:"])]))
|
|
142
124
|
body.add_element(ca.OnlyHtml([answer]))
|
|
143
125
|
|
|
144
|
-
return body,
|
|
126
|
+
return body, [answer]
|
|
145
127
|
|
|
146
|
-
|
|
128
|
+
@classmethod
|
|
129
|
+
def _build_explanation(cls, context):
|
|
147
130
|
"""Build question explanation."""
|
|
148
131
|
explanation = ca.Section()
|
|
149
132
|
|
|
150
133
|
explanation.add_element(ca.Paragraph(["To add vectors, we add corresponding components:"]))
|
|
151
134
|
|
|
152
135
|
# Use LaTeX syntax for make_block_equation__multiline_equals
|
|
153
|
-
vector_a_str = r" \\ ".join([str(v) for v in
|
|
154
|
-
vector_b_str = r" \\ ".join([str(v) for v in
|
|
155
|
-
result_str = r" \\ ".join([str(v) for v in
|
|
156
|
-
addition_str = r" \\ ".join([
|
|
136
|
+
vector_a_str = r" \\ ".join([str(v) for v in context["vector_a"]])
|
|
137
|
+
vector_b_str = r" \\ ".join([str(v) for v in context["vector_b"]])
|
|
138
|
+
result_str = r" \\ ".join([str(v) for v in context["result"]])
|
|
139
|
+
addition_str = r" \\ ".join([
|
|
140
|
+
f"{context['vector_a'][i]}+{context['vector_b'][i]}"
|
|
141
|
+
for i in range(context["dimension"])
|
|
142
|
+
])
|
|
157
143
|
|
|
158
144
|
explanation.add_element(
|
|
159
145
|
ca.Equation.make_block_equation__multiline_equals(
|
|
@@ -175,66 +161,87 @@ class VectorScalarMultiplication(VectorMathQuestion):
|
|
|
175
161
|
MIN_DIMENSION = 2
|
|
176
162
|
MAX_DIMENSION = 4
|
|
177
163
|
|
|
178
|
-
|
|
164
|
+
@classmethod
|
|
165
|
+
def _build_context(cls, *, rng_seed=None, **kwargs):
|
|
166
|
+
rng = random.Random(rng_seed)
|
|
167
|
+
num_subquestions = kwargs.get("num_subquestions", 1)
|
|
168
|
+
if num_subquestions > 1:
|
|
169
|
+
raise NotImplementedError("Multipart not supported")
|
|
170
|
+
|
|
171
|
+
dimension = kwargs.get("dimension", rng.randint(cls.MIN_DIMENSION, cls.MAX_DIMENSION))
|
|
172
|
+
vector_a = cls._generate_vector(rng, dimension)
|
|
173
|
+
scalar = cls._generate_scalar(rng)
|
|
174
|
+
result = cls.calculate_single_result(vector_a, scalar)
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
"dimension": dimension,
|
|
178
|
+
"vector_a": vector_a,
|
|
179
|
+
"scalar": scalar,
|
|
180
|
+
"result": result,
|
|
181
|
+
"num_subquestions": num_subquestions,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
def _generate_scalar(rng):
|
|
179
186
|
"""Generate a non-zero scalar for multiplication."""
|
|
180
|
-
scalar =
|
|
187
|
+
scalar = rng.randint(-5, 5)
|
|
181
188
|
while scalar == 0:
|
|
182
|
-
scalar =
|
|
189
|
+
scalar = rng.randint(-5, 5)
|
|
183
190
|
return scalar
|
|
184
191
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
super().refresh(*args, **kwargs)
|
|
189
|
-
|
|
190
|
-
def get_operator(self):
|
|
191
|
-
return f"{self.scalar} \\cdot"
|
|
192
|
+
@staticmethod
|
|
193
|
+
def get_operator():
|
|
194
|
+
return "\\cdot"
|
|
192
195
|
|
|
193
|
-
|
|
194
|
-
|
|
196
|
+
@staticmethod
|
|
197
|
+
def calculate_single_result(vector_a, vector_b):
|
|
198
|
+
return [vector_b * component for component in vector_a]
|
|
195
199
|
|
|
196
200
|
def create_subquestion_answers(self, subpart_index, result):
|
|
197
201
|
raise NotImplementedError("Multipart not supported")
|
|
198
202
|
|
|
199
203
|
def create_single_answers(self, result):
|
|
200
|
-
|
|
204
|
+
answer = ca.AnswerTypes.Vector(result)
|
|
205
|
+
self._single_answer = answer
|
|
206
|
+
return [answer]
|
|
201
207
|
|
|
202
|
-
|
|
208
|
+
@classmethod
|
|
209
|
+
def _build_body(cls, context):
|
|
203
210
|
"""Build question body and collect answers."""
|
|
204
211
|
body = ca.Section()
|
|
205
212
|
|
|
206
|
-
body.add_element(ca.Paragraph([
|
|
213
|
+
body.add_element(ca.Paragraph(["Calculate the following:"]))
|
|
207
214
|
|
|
208
215
|
# Equation display using MathExpression
|
|
209
|
-
vector_elem =
|
|
216
|
+
vector_elem = cls._format_vector(context["vector_a"])
|
|
210
217
|
body.add_element(ca.MathExpression([
|
|
211
|
-
f"{
|
|
218
|
+
f"{context['scalar']} \\cdot ",
|
|
212
219
|
vector_elem,
|
|
213
220
|
" = "
|
|
214
221
|
]))
|
|
215
222
|
|
|
216
|
-
|
|
217
|
-
answer = self.answers["result"]
|
|
223
|
+
answer = ca.AnswerTypes.Vector(context["result"])
|
|
218
224
|
body.add_element(ca.OnlyHtml([ca.Paragraph(["Enter your answer as a column vector:"])]))
|
|
219
225
|
body.add_element(ca.OnlyHtml([answer]))
|
|
220
226
|
|
|
221
|
-
return body,
|
|
227
|
+
return body, [answer]
|
|
222
228
|
|
|
223
|
-
|
|
229
|
+
@classmethod
|
|
230
|
+
def _build_explanation(cls, context):
|
|
224
231
|
"""Build question explanation."""
|
|
225
232
|
explanation = ca.Section()
|
|
226
233
|
|
|
227
234
|
explanation.add_element(ca.Paragraph(["To multiply a vector by a scalar, we multiply each component by the scalar:"]))
|
|
228
235
|
|
|
229
|
-
vector_str = r" \\ ".join([str(v) for v in
|
|
230
|
-
multiplication_str = r" \\ ".join([f"{
|
|
231
|
-
result_str = r" \\ ".join([str(v) for v in
|
|
236
|
+
vector_str = r" \\ ".join([str(v) for v in context["vector_a"]])
|
|
237
|
+
multiplication_str = r" \\ ".join([f"{context['scalar']} \\cdot {v}" for v in context["vector_a"]])
|
|
238
|
+
result_str = r" \\ ".join([str(v) for v in context["result"]])
|
|
232
239
|
|
|
233
240
|
explanation.add_element(
|
|
234
241
|
ca.Equation.make_block_equation__multiline_equals(
|
|
235
|
-
lhs=f"{
|
|
242
|
+
lhs=f"{context['scalar']} \\cdot \\vec{{v}}",
|
|
236
243
|
rhs=[
|
|
237
|
-
f"{
|
|
244
|
+
f"{context['scalar']} \\cdot \\begin{{bmatrix}} {vector_str} \\end{{bmatrix}}",
|
|
238
245
|
f"\\begin{{bmatrix}} {multiplication_str} \\end{{bmatrix}}",
|
|
239
246
|
f"\\begin{{bmatrix}} {result_str} \\end{{bmatrix}}"
|
|
240
247
|
]
|
|
@@ -250,27 +257,32 @@ class VectorDotProduct(VectorMathQuestion):
|
|
|
250
257
|
MIN_DIMENSION = 2
|
|
251
258
|
MAX_DIMENSION = 4
|
|
252
259
|
|
|
253
|
-
|
|
260
|
+
@staticmethod
|
|
261
|
+
def get_operator():
|
|
254
262
|
return "\\cdot"
|
|
255
263
|
|
|
256
|
-
|
|
264
|
+
@staticmethod
|
|
265
|
+
def calculate_single_result(vector_a, vector_b):
|
|
257
266
|
return sum(vector_a[i] * vector_b[i] for i in range(len(vector_a)))
|
|
258
267
|
|
|
259
268
|
def create_subquestion_answers(self, subpart_index, result):
|
|
260
269
|
raise NotImplementedError("Multipart not supported")
|
|
261
270
|
|
|
262
271
|
def create_single_answers(self, result):
|
|
263
|
-
|
|
272
|
+
answer = ca.AnswerTypes.Int(result)
|
|
273
|
+
self._single_answer = answer
|
|
274
|
+
return [answer]
|
|
264
275
|
|
|
265
|
-
|
|
276
|
+
@classmethod
|
|
277
|
+
def _build_body(cls, context):
|
|
266
278
|
"""Build question body and collect answers."""
|
|
267
279
|
body = ca.Section()
|
|
268
280
|
|
|
269
|
-
body.add_element(ca.Paragraph([
|
|
281
|
+
body.add_element(ca.Paragraph(["Calculate the following:"]))
|
|
270
282
|
|
|
271
283
|
# Equation display using MathExpression
|
|
272
|
-
vector_a_elem =
|
|
273
|
-
vector_b_elem =
|
|
284
|
+
vector_a_elem = cls._format_vector(context["vector_a"])
|
|
285
|
+
vector_b_elem = cls._format_vector(context["vector_b"])
|
|
274
286
|
body.add_element(ca.MathExpression([
|
|
275
287
|
vector_a_elem,
|
|
276
288
|
" \\cdot ",
|
|
@@ -278,22 +290,28 @@ class VectorDotProduct(VectorMathQuestion):
|
|
|
278
290
|
" = "
|
|
279
291
|
]))
|
|
280
292
|
|
|
281
|
-
|
|
282
|
-
answer = self.answers["dot_product"]
|
|
293
|
+
answer = ca.AnswerTypes.Int(context["result"])
|
|
283
294
|
body.add_element(ca.OnlyHtml([answer]))
|
|
284
295
|
|
|
285
|
-
return body,
|
|
296
|
+
return body, [answer]
|
|
286
297
|
|
|
287
|
-
|
|
298
|
+
@classmethod
|
|
299
|
+
def _build_explanation(cls, context):
|
|
288
300
|
"""Build question explanation."""
|
|
289
301
|
explanation = ca.Section()
|
|
290
302
|
|
|
291
303
|
explanation.add_element(ca.Paragraph(["The dot product is calculated by multiplying corresponding components and summing the results:"]))
|
|
292
304
|
|
|
293
|
-
vector_a_str = r" \\ ".join([str(v) for v in
|
|
294
|
-
vector_b_str = r" \\ ".join([str(v) for v in
|
|
295
|
-
products_str = " + ".join([
|
|
296
|
-
|
|
305
|
+
vector_a_str = r" \\ ".join([str(v) for v in context["vector_a"]])
|
|
306
|
+
vector_b_str = r" \\ ".join([str(v) for v in context["vector_b"]])
|
|
307
|
+
products_str = " + ".join([
|
|
308
|
+
f"({context['vector_a'][i]} \\cdot {context['vector_b'][i]})"
|
|
309
|
+
for i in range(context["dimension"])
|
|
310
|
+
])
|
|
311
|
+
calculation_str = " + ".join([
|
|
312
|
+
str(context["vector_a"][i] * context["vector_b"][i])
|
|
313
|
+
for i in range(context["dimension"])
|
|
314
|
+
])
|
|
297
315
|
|
|
298
316
|
explanation.add_element(
|
|
299
317
|
ca.Equation.make_block_equation__multiline_equals(
|
|
@@ -302,7 +320,7 @@ class VectorDotProduct(VectorMathQuestion):
|
|
|
302
320
|
f"\\begin{{bmatrix}} {vector_a_str} \\end{{bmatrix}} \\cdot \\begin{{bmatrix}} {vector_b_str} \\end{{bmatrix}}",
|
|
303
321
|
products_str,
|
|
304
322
|
calculation_str,
|
|
305
|
-
str(
|
|
323
|
+
str(context["result"])
|
|
306
324
|
]
|
|
307
325
|
)
|
|
308
326
|
)
|
|
@@ -316,10 +334,12 @@ class VectorMagnitude(VectorMathQuestion):
|
|
|
316
334
|
MIN_DIMENSION = 2
|
|
317
335
|
MAX_DIMENSION = 3
|
|
318
336
|
|
|
319
|
-
|
|
337
|
+
@staticmethod
|
|
338
|
+
def get_operator():
|
|
320
339
|
return "||"
|
|
321
340
|
|
|
322
|
-
|
|
341
|
+
@staticmethod
|
|
342
|
+
def calculate_single_result(vector_a, vector_b):
|
|
323
343
|
magnitude_squared = sum(component ** 2 for component in vector_a)
|
|
324
344
|
return math.sqrt(magnitude_squared)
|
|
325
345
|
|
|
@@ -327,29 +347,32 @@ class VectorMagnitude(VectorMathQuestion):
|
|
|
327
347
|
raise NotImplementedError("Multipart not supported")
|
|
328
348
|
|
|
329
349
|
def create_single_answers(self, result):
|
|
330
|
-
|
|
350
|
+
answer = ca.AnswerTypes.Float(result)
|
|
351
|
+
self._single_answer = answer
|
|
352
|
+
return [answer]
|
|
331
353
|
|
|
332
|
-
|
|
354
|
+
@classmethod
|
|
355
|
+
def _build_body(cls, context):
|
|
333
356
|
"""Build question body and collect answers."""
|
|
334
357
|
body = ca.Section()
|
|
335
358
|
|
|
336
|
-
body.add_element(ca.Paragraph([
|
|
359
|
+
body.add_element(ca.Paragraph(["Calculate the following:"]))
|
|
337
360
|
|
|
338
361
|
# Equation display using MathExpression
|
|
339
|
-
vector_elem =
|
|
362
|
+
vector_elem = cls._format_vector(context["vector_a"])
|
|
340
363
|
body.add_element(ca.MathExpression([
|
|
341
364
|
"||",
|
|
342
365
|
vector_elem,
|
|
343
366
|
"|| = "
|
|
344
367
|
]))
|
|
345
368
|
|
|
346
|
-
|
|
347
|
-
answer = self.answers["magnitude"]
|
|
369
|
+
answer = ca.AnswerTypes.Float(context["result"])
|
|
348
370
|
body.add_element(ca.OnlyHtml([answer]))
|
|
349
371
|
|
|
350
|
-
return body,
|
|
372
|
+
return body, [answer]
|
|
351
373
|
|
|
352
|
-
|
|
374
|
+
@classmethod
|
|
375
|
+
def _build_explanation(cls, context):
|
|
353
376
|
"""Build question explanation."""
|
|
354
377
|
explanation = ca.Section()
|
|
355
378
|
|
|
@@ -359,11 +382,11 @@ class VectorMagnitude(VectorMathQuestion):
|
|
|
359
382
|
))
|
|
360
383
|
|
|
361
384
|
# Use LaTeX syntax for make_block_equation__multiline_equals
|
|
362
|
-
vector_str = r" \\ ".join([str(v) for v in
|
|
363
|
-
squares_str = " + ".join([f"{v}^2" for v in
|
|
364
|
-
calculation_str = " + ".join([str(v**2) for v in
|
|
365
|
-
sum_of_squares = sum(component ** 2 for component in
|
|
366
|
-
result_formatted = sorted(ca.Answer.accepted_strings(
|
|
385
|
+
vector_str = r" \\ ".join([str(v) for v in context["vector_a"]])
|
|
386
|
+
squares_str = " + ".join([f"{v}^2" for v in context["vector_a"]])
|
|
387
|
+
calculation_str = " + ".join([str(v**2) for v in context["vector_a"]])
|
|
388
|
+
sum_of_squares = sum(component ** 2 for component in context["vector_a"])
|
|
389
|
+
result_formatted = sorted(ca.Answer.accepted_strings(context["result"]), key=lambda s: len(s))[0]
|
|
367
390
|
|
|
368
391
|
explanation.add_element(
|
|
369
392
|
ca.Equation.make_block_equation__multiline_equals(
|
|
@@ -40,33 +40,35 @@ class AttentionForwardPass(MatrixQuestion, TableQuestionMixin):
|
|
|
40
40
|
|
|
41
41
|
return output, attention_weights
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
d_k = kwargs.get("key_dimension", 1) # key/query dimension
|
|
48
|
-
d_v = kwargs.get("value_dimension", 1) # value dimension
|
|
49
|
-
|
|
50
|
-
# Small integer matrices
|
|
51
|
-
self.Q = self.rng.randint(0, 3, size=(seq_len, d_k))
|
|
52
|
-
self.K = self.rng.randint(0, 3, size=(seq_len, d_k))
|
|
53
|
-
self.V = self.rng.randint(0, 3, size=(seq_len, d_v))
|
|
54
|
-
|
|
55
|
-
self.Q = self.get_rounded_matrix((seq_len, d_k), 0, 3)
|
|
56
|
-
self.K = self.get_rounded_matrix((seq_len, d_k), 0, 3)
|
|
57
|
-
self.V = self.get_rounded_matrix((seq_len, d_v), 0, 3)
|
|
58
|
-
|
|
59
|
-
self.output, self.weights = self.simple_attention(self.Q, self.K, self.V)
|
|
60
|
-
|
|
61
|
-
## Answers:
|
|
62
|
-
# Q, K, V, output, weights
|
|
43
|
+
@classmethod
|
|
44
|
+
def _build_context(cls, *, rng_seed=None, **kwargs):
|
|
45
|
+
rng = cls.get_rng(rng_seed)
|
|
46
|
+
digits = cls.get_digits_to_round(digits_to_round=kwargs.get("digits_to_round"))
|
|
63
47
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
48
|
+
seq_len = kwargs.get("seq_len", 3)
|
|
49
|
+
d_k = kwargs.get("key_dimension", 1)
|
|
50
|
+
d_v = kwargs.get("value_dimension", 1)
|
|
51
|
+
|
|
52
|
+
Q = cls.get_rounded_matrix(rng, (seq_len, d_k), 0, 3, digits)
|
|
53
|
+
K = cls.get_rounded_matrix(rng, (seq_len, d_k), 0, 3, digits)
|
|
54
|
+
V = cls.get_rounded_matrix(rng, (seq_len, d_v), 0, 3, digits)
|
|
55
|
+
|
|
56
|
+
output, weights = cls.simple_attention(Q, K, V)
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
"digits": digits,
|
|
60
|
+
"seq_len": seq_len,
|
|
61
|
+
"d_k": d_k,
|
|
62
|
+
"d_v": d_v,
|
|
63
|
+
"Q": Q,
|
|
64
|
+
"K": K,
|
|
65
|
+
"V": V,
|
|
66
|
+
"output": output,
|
|
67
|
+
"weights": weights,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def _build_body(cls, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
70
72
|
"""Build question body and collect answers."""
|
|
71
73
|
body = ca.Section()
|
|
72
74
|
answers = []
|
|
@@ -75,32 +77,30 @@ class AttentionForwardPass(MatrixQuestion, TableQuestionMixin):
|
|
|
75
77
|
ca.Text("Given the below information about a self attention layer, please calculate the output sequence.")
|
|
76
78
|
)
|
|
77
79
|
body.add_element(
|
|
78
|
-
|
|
80
|
+
cls.create_info_table(
|
|
79
81
|
{
|
|
80
|
-
"Q": ca.Matrix(
|
|
81
|
-
"K": ca.Matrix(
|
|
82
|
-
"V": ca.Matrix(
|
|
82
|
+
"Q": ca.Matrix(context["Q"]),
|
|
83
|
+
"K": ca.Matrix(context["K"]),
|
|
84
|
+
"V": ca.Matrix(context["V"]),
|
|
83
85
|
}
|
|
84
86
|
)
|
|
85
87
|
)
|
|
86
88
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
weights_answer = ca.AnswerTypes.Matrix(context["weights"], label="Weights")
|
|
90
|
+
output_answer = ca.AnswerTypes.Matrix(context["output"], label="Output")
|
|
91
|
+
answers.append(weights_answer)
|
|
92
|
+
answers.append(output_answer)
|
|
89
93
|
body.add_elements([
|
|
90
94
|
ca.LineBreak(),
|
|
91
|
-
|
|
95
|
+
weights_answer,
|
|
92
96
|
ca.LineBreak(),
|
|
93
|
-
|
|
97
|
+
output_answer,
|
|
94
98
|
])
|
|
95
99
|
|
|
96
100
|
return body, answers
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
body, _ = self._get_body(**kwargs)
|
|
101
|
-
return body
|
|
102
|
-
|
|
103
|
-
def _get_explanation(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
102
|
+
@classmethod
|
|
103
|
+
def _build_explanation(cls, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
104
104
|
"""Build question explanation."""
|
|
105
105
|
explanation = ca.Section()
|
|
106
106
|
digits = ca.Answer.DEFAULT_ROUNDING_DIGITS
|
|
@@ -118,12 +118,12 @@ class AttentionForwardPass(MatrixQuestion, TableQuestionMixin):
|
|
|
118
118
|
])
|
|
119
119
|
)
|
|
120
120
|
|
|
121
|
-
d_k =
|
|
121
|
+
d_k = context["Q"].shape[1]
|
|
122
122
|
explanation.add_element(
|
|
123
123
|
ca.Equation(f"\\text{{scores}} = \\frac{{Q K^T}}{{\\sqrt{{d_k}}}} = \\frac{{Q K^T}}{{\\sqrt{{{d_k}}}}}")
|
|
124
124
|
)
|
|
125
125
|
|
|
126
|
-
scores =
|
|
126
|
+
scores = context["Q"] @ context["K"].T / np.sqrt(d_k)
|
|
127
127
|
|
|
128
128
|
explanation.add_element(
|
|
129
129
|
ca.Paragraph([
|
|
@@ -178,7 +178,7 @@ class AttentionForwardPass(MatrixQuestion, TableQuestionMixin):
|
|
|
178
178
|
"Complete attention weight matrix:"
|
|
179
179
|
])
|
|
180
180
|
)
|
|
181
|
-
explanation.add_element(ca.Matrix(np.round(
|
|
181
|
+
explanation.add_element(ca.Matrix(np.round(context["weights"], digits)))
|
|
182
182
|
|
|
183
183
|
# Step 3: Weighted sum of values
|
|
184
184
|
explanation.add_element(
|
|
@@ -196,12 +196,6 @@ class AttentionForwardPass(MatrixQuestion, TableQuestionMixin):
|
|
|
196
196
|
"Final output:"
|
|
197
197
|
])
|
|
198
198
|
)
|
|
199
|
-
explanation.add_element(ca.Matrix(np.round(
|
|
199
|
+
explanation.add_element(ca.Matrix(np.round(context["output"], digits)))
|
|
200
200
|
|
|
201
201
|
return explanation, []
|
|
202
|
-
|
|
203
|
-
def get_explanation(self, **kwargs) -> ca.Section:
|
|
204
|
-
"""Build question explanation (backward compatible interface)."""
|
|
205
|
-
explanation, _ = self._get_explanation(**kwargs)
|
|
206
|
-
return explanation
|
|
207
|
-
|