QuizGenerator 0.6.3__py3-none-any.whl → 0.7.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 +2191 -2193
- QuizGenerator/misc.py +1 -1
- QuizGenerator/mixins.py +64 -64
- QuizGenerator/premade_questions/basic.py +16 -16
- QuizGenerator/premade_questions/cst334/languages.py +26 -26
- QuizGenerator/premade_questions/cst334/math_questions.py +42 -42
- QuizGenerator/premade_questions/cst334/memory_questions.py +124 -124
- QuizGenerator/premade_questions/cst334/persistence_questions.py +48 -48
- QuizGenerator/premade_questions/cst334/process.py +38 -38
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +45 -45
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +34 -34
- QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +53 -53
- QuizGenerator/premade_questions/cst463/gradient_descent/misc.py +2 -2
- QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +65 -65
- QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +39 -39
- QuizGenerator/premade_questions/cst463/models/attention.py +36 -36
- QuizGenerator/premade_questions/cst463/models/cnns.py +26 -26
- QuizGenerator/premade_questions/cst463/models/rnns.py +36 -36
- QuizGenerator/premade_questions/cst463/models/text.py +32 -32
- QuizGenerator/premade_questions/cst463/models/weight_counting.py +15 -15
- QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +124 -124
- QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +161 -161
- QuizGenerator/question.py +41 -41
- QuizGenerator/quiz.py +7 -7
- QuizGenerator/typst_utils.py +2 -2
- {quizgenerator-0.6.3.dist-info → quizgenerator-0.7.0.dist-info}/METADATA +1 -1
- {quizgenerator-0.6.3.dist-info → quizgenerator-0.7.0.dist-info}/RECORD +30 -30
- {quizgenerator-0.6.3.dist-info → quizgenerator-0.7.0.dist-info}/WHEEL +0 -0
- {quizgenerator-0.6.3.dist-info → quizgenerator-0.7.0.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.6.3.dist-info → quizgenerator-0.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,7 +6,7 @@ import math
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
from typing import List, Tuple, Dict, Any
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
import QuizGenerator.contentast as ca
|
|
10
10
|
from QuizGenerator.question import Question, QuestionRegistry
|
|
11
11
|
from QuizGenerator.mixins import TableQuestionMixin, BodyTemplatesMixin
|
|
12
12
|
|
|
@@ -73,18 +73,18 @@ class LossQuestion(Question, TableQuestionMixin, BodyTemplatesMixin, abc.ABC):
|
|
|
73
73
|
|
|
74
74
|
# Individual loss answers
|
|
75
75
|
for i in range(self.num_samples):
|
|
76
|
-
self.answers[f"loss_{i}"] = AnswerTypes.Float(self.individual_losses[i], label=f"Sample {i + 1} loss")
|
|
76
|
+
self.answers[f"loss_{i}"] = ca.AnswerTypes.Float(self.individual_losses[i], label=f"Sample {i + 1} loss")
|
|
77
77
|
|
|
78
78
|
# Overall loss answer
|
|
79
|
-
self.answers["overall_loss"] = AnswerTypes.Float(self.overall_loss, label="Overall loss")
|
|
79
|
+
self.answers["overall_loss"] = ca.AnswerTypes.Float(self.overall_loss, label="Overall loss")
|
|
80
80
|
|
|
81
|
-
def _get_body(self, **kwargs) -> Tuple[
|
|
81
|
+
def _get_body(self, **kwargs) -> Tuple[ca.Element, List[ca.Answer]]:
|
|
82
82
|
"""Build question body and collect answers."""
|
|
83
|
-
body =
|
|
83
|
+
body = ca.Section()
|
|
84
84
|
answers = []
|
|
85
85
|
|
|
86
86
|
# Question description
|
|
87
|
-
body.add_element(
|
|
87
|
+
body.add_element(ca.Paragraph([
|
|
88
88
|
f"Given the dataset below, calculate the {self._get_loss_function_short_name()} for each sample "
|
|
89
89
|
f"and the overall {self._get_loss_function_short_name()}."
|
|
90
90
|
]))
|
|
@@ -97,7 +97,7 @@ class LossQuestion(Question, TableQuestionMixin, BodyTemplatesMixin, abc.ABC):
|
|
|
97
97
|
answers.append(self.answers[f"loss_{i}"])
|
|
98
98
|
|
|
99
99
|
# Overall loss question
|
|
100
|
-
body.add_element(
|
|
100
|
+
body.add_element(ca.Paragraph([
|
|
101
101
|
f"Overall {self._get_loss_function_short_name()}: "
|
|
102
102
|
]))
|
|
103
103
|
answers.append(self.answers["overall_loss"])
|
|
@@ -105,31 +105,31 @@ class LossQuestion(Question, TableQuestionMixin, BodyTemplatesMixin, abc.ABC):
|
|
|
105
105
|
|
|
106
106
|
return body, answers
|
|
107
107
|
|
|
108
|
-
def get_body(self, **kwargs) ->
|
|
108
|
+
def get_body(self, **kwargs) -> ca.Element:
|
|
109
109
|
"""Build question body (backward compatible interface)."""
|
|
110
110
|
body, _ = self._get_body(**kwargs)
|
|
111
111
|
return body
|
|
112
112
|
|
|
113
113
|
@abc.abstractmethod
|
|
114
|
-
def _create_data_table(self) ->
|
|
114
|
+
def _create_data_table(self) -> ca.Element:
|
|
115
115
|
"""Create the data table with answer fields."""
|
|
116
116
|
pass
|
|
117
117
|
|
|
118
|
-
def _get_explanation(self, **kwargs) -> Tuple[
|
|
118
|
+
def _get_explanation(self, **kwargs) -> Tuple[ca.Element, List[ca.Answer]]:
|
|
119
119
|
"""Build question explanation."""
|
|
120
|
-
explanation =
|
|
120
|
+
explanation = ca.Section()
|
|
121
121
|
|
|
122
|
-
explanation.add_element(
|
|
122
|
+
explanation.add_element(ca.Paragraph([
|
|
123
123
|
f"To calculate the {self._get_loss_function_name()}, we apply the formula to each sample:"
|
|
124
124
|
]))
|
|
125
125
|
|
|
126
|
-
explanation.add_element(
|
|
126
|
+
explanation.add_element(ca.Equation(self._get_loss_function_formula(), inline=False))
|
|
127
127
|
|
|
128
128
|
# Step-by-step calculations
|
|
129
129
|
explanation.add_element(self._create_calculation_steps())
|
|
130
130
|
|
|
131
131
|
# Completed table
|
|
132
|
-
explanation.add_element(
|
|
132
|
+
explanation.add_element(ca.Paragraph(["Completed table:"]))
|
|
133
133
|
explanation.add_element(self._create_completed_table())
|
|
134
134
|
|
|
135
135
|
# Overall loss calculation
|
|
@@ -137,23 +137,23 @@ class LossQuestion(Question, TableQuestionMixin, BodyTemplatesMixin, abc.ABC):
|
|
|
137
137
|
|
|
138
138
|
return explanation, []
|
|
139
139
|
|
|
140
|
-
def get_explanation(self, **kwargs) ->
|
|
140
|
+
def get_explanation(self, **kwargs) -> ca.Element:
|
|
141
141
|
"""Build question explanation (backward compatible interface)."""
|
|
142
142
|
explanation, _ = self._get_explanation(**kwargs)
|
|
143
143
|
return explanation
|
|
144
144
|
|
|
145
145
|
@abc.abstractmethod
|
|
146
|
-
def _create_calculation_steps(self) ->
|
|
146
|
+
def _create_calculation_steps(self) -> ca.Element:
|
|
147
147
|
"""Create step-by-step calculation explanations."""
|
|
148
148
|
pass
|
|
149
149
|
|
|
150
150
|
@abc.abstractmethod
|
|
151
|
-
def _create_completed_table(self) ->
|
|
151
|
+
def _create_completed_table(self) -> ca.Element:
|
|
152
152
|
"""Create the completed table with all values filled in."""
|
|
153
153
|
pass
|
|
154
154
|
|
|
155
155
|
@abc.abstractmethod
|
|
156
|
-
def _create_overall_loss_explanation(self) ->
|
|
156
|
+
def _create_overall_loss_explanation(self) -> ca.Element:
|
|
157
157
|
"""Create explanation for overall loss calculation."""
|
|
158
158
|
pass
|
|
159
159
|
|
|
@@ -225,7 +225,7 @@ class LossQuestion_Linear(LossQuestion):
|
|
|
225
225
|
else:
|
|
226
226
|
return r"L(\mathbf{y}, \mathbf{p}) = \sum_{i=1}^{k} (y_i - p_i)^2"
|
|
227
227
|
|
|
228
|
-
def _create_data_table(self) ->
|
|
228
|
+
def _create_data_table(self) -> ca.Element:
|
|
229
229
|
"""Create table with input features, true values, predictions, and loss fields."""
|
|
230
230
|
headers = ["x"]
|
|
231
231
|
|
|
@@ -268,12 +268,12 @@ class LossQuestion_Linear(LossQuestion):
|
|
|
268
268
|
|
|
269
269
|
return self.create_answer_table(headers, rows, answer_columns=["loss"])
|
|
270
270
|
|
|
271
|
-
def _create_calculation_steps(self) ->
|
|
271
|
+
def _create_calculation_steps(self) -> ca.Element:
|
|
272
272
|
"""Show step-by-step MSE calculations."""
|
|
273
|
-
steps =
|
|
273
|
+
steps = ca.Section()
|
|
274
274
|
|
|
275
275
|
for i, sample in enumerate(self.data):
|
|
276
|
-
steps.add_element(
|
|
276
|
+
steps.add_element(ca.Paragraph([f"Sample {i+1}:"]))
|
|
277
277
|
|
|
278
278
|
if self.num_output_vars == 1:
|
|
279
279
|
y = sample['true_values']
|
|
@@ -286,7 +286,7 @@ class LossQuestion_Linear(LossQuestion):
|
|
|
286
286
|
calculation = f"L = ({y:.2f} - {p:.2f})^2 = ({diff:.2f})^2 = {loss:.4f}"
|
|
287
287
|
else:
|
|
288
288
|
calculation = f"L = ({y:.2f} - ({p:.2f}))^2 = ({diff:.2f})^2 = {loss:.4f}"
|
|
289
|
-
steps.add_element(
|
|
289
|
+
steps.add_element(ca.Equation(calculation, inline=False))
|
|
290
290
|
else:
|
|
291
291
|
# Multi-output calculation
|
|
292
292
|
y_vals = sample['true_values']
|
|
@@ -302,11 +302,11 @@ class LossQuestion_Linear(LossQuestion):
|
|
|
302
302
|
terms.append(f"({y:.2f} - ({p:.2f}))^2")
|
|
303
303
|
|
|
304
304
|
calculation = f"L = {' + '.join(terms)} = {loss:.4f}"
|
|
305
|
-
steps.add_element(
|
|
305
|
+
steps.add_element(ca.Equation(calculation, inline=False))
|
|
306
306
|
|
|
307
307
|
return steps
|
|
308
308
|
|
|
309
|
-
def _create_completed_table(self) ->
|
|
309
|
+
def _create_completed_table(self) -> ca.Element:
|
|
310
310
|
"""Create table with all values including calculated losses."""
|
|
311
311
|
headers = ["x_0", "x_1"]
|
|
312
312
|
|
|
@@ -346,20 +346,20 @@ class LossQuestion_Linear(LossQuestion):
|
|
|
346
346
|
|
|
347
347
|
rows.append(row)
|
|
348
348
|
|
|
349
|
-
return
|
|
349
|
+
return ca.Table(headers=headers, data=rows)
|
|
350
350
|
|
|
351
|
-
def _create_overall_loss_explanation(self) ->
|
|
351
|
+
def _create_overall_loss_explanation(self) -> ca.Element:
|
|
352
352
|
"""Explain overall MSE calculation."""
|
|
353
|
-
explanation =
|
|
353
|
+
explanation = ca.Section()
|
|
354
354
|
|
|
355
|
-
explanation.add_element(
|
|
355
|
+
explanation.add_element(ca.Paragraph([
|
|
356
356
|
"The overall MSE is the average of individual losses:"
|
|
357
357
|
]))
|
|
358
358
|
|
|
359
359
|
losses_str = " + ".join([f"{loss:.4f}" for loss in self.individual_losses])
|
|
360
360
|
calculation = f"MSE = \\frac{{{losses_str}}}{{{self.num_samples}}} = {self.overall_loss:.4f}"
|
|
361
361
|
|
|
362
|
-
explanation.add_element(
|
|
362
|
+
explanation.add_element(ca.Equation(calculation, inline=False))
|
|
363
363
|
|
|
364
364
|
return explanation
|
|
365
365
|
|
|
@@ -416,7 +416,7 @@ class LossQuestion_Logistic(LossQuestion):
|
|
|
416
416
|
def _get_loss_function_formula(self) -> str:
|
|
417
417
|
return r"L(y, p) = -[y \ln(p) + (1-y) \ln(1-p)]"
|
|
418
418
|
|
|
419
|
-
def _create_data_table(self) ->
|
|
419
|
+
def _create_data_table(self) -> ca.Element:
|
|
420
420
|
"""Create table with features, true labels, predicted probabilities, and loss fields."""
|
|
421
421
|
headers = ["x", "y", "p", "loss"]
|
|
422
422
|
|
|
@@ -441,27 +441,27 @@ class LossQuestion_Logistic(LossQuestion):
|
|
|
441
441
|
|
|
442
442
|
return self.create_answer_table(headers, rows, answer_columns=["loss"])
|
|
443
443
|
|
|
444
|
-
def _create_calculation_steps(self) ->
|
|
444
|
+
def _create_calculation_steps(self) -> ca.Element:
|
|
445
445
|
"""Show step-by-step log-loss calculations."""
|
|
446
|
-
steps =
|
|
446
|
+
steps = ca.Section()
|
|
447
447
|
|
|
448
448
|
for i, sample in enumerate(self.data):
|
|
449
449
|
y = sample['true_values']
|
|
450
450
|
p = sample['predictions']
|
|
451
451
|
loss = self.individual_losses[i]
|
|
452
452
|
|
|
453
|
-
steps.add_element(
|
|
453
|
+
steps.add_element(ca.Paragraph([f"Sample {i+1}:"]))
|
|
454
454
|
|
|
455
455
|
if y == 1:
|
|
456
456
|
calculation = f"L = -[1 \\cdot \\ln({p:.3f}) + 0 \\cdot \\ln(1-{p:.3f})] = -\\ln({p:.3f}) = {loss:.4f}"
|
|
457
457
|
else:
|
|
458
458
|
calculation = f"L = -[0 \\cdot \\ln({p:.3f}) + 1 \\cdot \\ln(1-{p:.3f})] = -\\ln({1-p:.3f}) = {loss:.4f}"
|
|
459
459
|
|
|
460
|
-
steps.add_element(
|
|
460
|
+
steps.add_element(ca.Equation(calculation, inline=False))
|
|
461
461
|
|
|
462
462
|
return steps
|
|
463
463
|
|
|
464
|
-
def _create_completed_table(self) ->
|
|
464
|
+
def _create_completed_table(self) -> ca.Element:
|
|
465
465
|
"""Create table with all values including calculated losses."""
|
|
466
466
|
headers = ["x_0", "x_1", "y", "p", "loss"]
|
|
467
467
|
|
|
@@ -484,20 +484,20 @@ class LossQuestion_Logistic(LossQuestion):
|
|
|
484
484
|
|
|
485
485
|
rows.append(row)
|
|
486
486
|
|
|
487
|
-
return
|
|
487
|
+
return ca.Table(headers=headers, data=rows)
|
|
488
488
|
|
|
489
|
-
def _create_overall_loss_explanation(self) ->
|
|
489
|
+
def _create_overall_loss_explanation(self) -> ca.Element:
|
|
490
490
|
"""Explain overall log-loss calculation."""
|
|
491
|
-
explanation =
|
|
491
|
+
explanation = ca.Section()
|
|
492
492
|
|
|
493
|
-
explanation.add_element(
|
|
493
|
+
explanation.add_element(ca.Paragraph([
|
|
494
494
|
"The overall log-loss is the average of individual losses:"
|
|
495
495
|
]))
|
|
496
496
|
|
|
497
497
|
losses_str = " + ".join([f"{loss:.4f}" for loss in self.individual_losses])
|
|
498
498
|
calculation = f"\\text{{Log-Loss}} = \\frac{{{losses_str}}}{{{self.num_samples}}} = {self.overall_loss:.4f}"
|
|
499
499
|
|
|
500
|
-
explanation.add_element(
|
|
500
|
+
explanation.add_element(ca.Equation(calculation, inline=False))
|
|
501
501
|
|
|
502
502
|
return explanation
|
|
503
503
|
|
|
@@ -560,7 +560,7 @@ class LossQuestion_MulticlassLogistic(LossQuestion):
|
|
|
560
560
|
def _get_loss_function_formula(self) -> str:
|
|
561
561
|
return r"L(\mathbf{y}, \mathbf{p}) = -\sum_{i=1}^{K} y_i \ln(p_i)"
|
|
562
562
|
|
|
563
|
-
def _create_data_table(self) ->
|
|
563
|
+
def _create_data_table(self) -> ca.Element:
|
|
564
564
|
"""Create table with features, true class vectors, predicted probabilities, and loss fields."""
|
|
565
565
|
headers = ["x", "y", "p", "loss"]
|
|
566
566
|
|
|
@@ -587,22 +587,22 @@ class LossQuestion_MulticlassLogistic(LossQuestion):
|
|
|
587
587
|
|
|
588
588
|
return self.create_answer_table(headers, rows, answer_columns=["loss"])
|
|
589
589
|
|
|
590
|
-
def _create_calculation_steps(self) ->
|
|
590
|
+
def _create_calculation_steps(self) -> ca.Element:
|
|
591
591
|
"""Show step-by-step cross-entropy calculations."""
|
|
592
|
-
steps =
|
|
592
|
+
steps = ca.Section()
|
|
593
593
|
|
|
594
594
|
for i, sample in enumerate(self.data):
|
|
595
595
|
y_vec = sample['true_values']
|
|
596
596
|
p_vec = sample['predictions']
|
|
597
597
|
loss = self.individual_losses[i]
|
|
598
598
|
|
|
599
|
-
steps.add_element(
|
|
599
|
+
steps.add_element(ca.Paragraph([f"Sample {i+1}:"]))
|
|
600
600
|
|
|
601
601
|
# Show vector dot product calculation
|
|
602
602
|
y_str = "[" + ", ".join([str(y) for y in y_vec]) + "]"
|
|
603
603
|
p_str = "[" + ", ".join([f"{p:.3f}" for p in p_vec]) + "]"
|
|
604
604
|
|
|
605
|
-
steps.add_element(
|
|
605
|
+
steps.add_element(ca.Paragraph([f"\\mathbf{{y}} = {y_str}, \\mathbf{{p}} = {p_str}"]))
|
|
606
606
|
|
|
607
607
|
# Find the true class (where y_i = 1)
|
|
608
608
|
try:
|
|
@@ -622,11 +622,11 @@ class LossQuestion_MulticlassLogistic(LossQuestion):
|
|
|
622
622
|
# Fallback in case no class is set to 1 (shouldn't happen, but safety check)
|
|
623
623
|
calculation = f"L = -\\mathbf{{y}} \\cdot \\ln(\\mathbf{{p}}) = {loss:.4f}"
|
|
624
624
|
|
|
625
|
-
steps.add_element(
|
|
625
|
+
steps.add_element(ca.Equation(calculation, inline=False))
|
|
626
626
|
|
|
627
627
|
return steps
|
|
628
628
|
|
|
629
|
-
def _create_completed_table(self) ->
|
|
629
|
+
def _create_completed_table(self) -> ca.Element:
|
|
630
630
|
"""Create table with all values including calculated losses."""
|
|
631
631
|
headers = ["x_0", "x_1", "y", "p", "loss"]
|
|
632
632
|
|
|
@@ -651,19 +651,19 @@ class LossQuestion_MulticlassLogistic(LossQuestion):
|
|
|
651
651
|
|
|
652
652
|
rows.append(row)
|
|
653
653
|
|
|
654
|
-
return
|
|
654
|
+
return ca.Table(headers=headers, data=rows)
|
|
655
655
|
|
|
656
|
-
def _create_overall_loss_explanation(self) ->
|
|
656
|
+
def _create_overall_loss_explanation(self) -> ca.Element:
|
|
657
657
|
"""Explain overall cross-entropy loss calculation."""
|
|
658
|
-
explanation =
|
|
658
|
+
explanation = ca.Section()
|
|
659
659
|
|
|
660
|
-
explanation.add_element(
|
|
660
|
+
explanation.add_element(ca.Paragraph([
|
|
661
661
|
"The overall cross-entropy loss is the average of individual losses:"
|
|
662
662
|
]))
|
|
663
663
|
|
|
664
664
|
losses_str = " + ".join([f"{loss:.4f}" for loss in self.individual_losses])
|
|
665
665
|
calculation = f"\\text{{Cross-Entropy}} = \\frac{{{losses_str}}}{{{self.num_samples}}} = {self.overall_loss:.4f}"
|
|
666
666
|
|
|
667
|
-
explanation.add_element(
|
|
667
|
+
explanation.add_element(ca.Equation(calculation, inline=False))
|
|
668
668
|
|
|
669
669
|
return explanation
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
from typing import List, Tuple, Callable, Union, Any
|
|
3
3
|
import sympy as sp
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import QuizGenerator.contentast as ca
|
|
6
6
|
|
|
7
7
|
def generate_function(rng, num_variables: int, max_degree: int, use_quadratic: bool = True) -> tuple[Any, sp.Expr, sp.MutableDenseMatrix, sp.Equality]:
|
|
8
8
|
"""
|
|
@@ -61,7 +61,7 @@ def format_vector(vec: List[float]) -> str:
|
|
|
61
61
|
|
|
62
62
|
vector_string = ', '.join(
|
|
63
63
|
[
|
|
64
|
-
sorted(
|
|
64
|
+
sorted(ca.Answer.accepted_strings(v), key=lambda s: len(s))[0]
|
|
65
65
|
for v in vec
|
|
66
66
|
]
|
|
67
67
|
)
|