QuizGenerator 0.7.1__py3-none-any.whl → 0.8.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 +48 -15
- 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 -328
- 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 -521
- 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 +310 -202
- QuizGenerator/quiz.py +8 -5
- QuizGenerator/regenerate.py +14 -6
- {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.1.dist-info}/METADATA +30 -2
- {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.1.dist-info}/RECORD +30 -30
- {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.1.dist-info}/WHEEL +0 -0
- {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.1.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -49,29 +49,33 @@ class ConvolutionCalculation(MatrixQuestion):
|
|
|
49
49
|
|
|
50
50
|
return output
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
@classmethod
|
|
53
|
+
def _build_context(cls, *, rng_seed=None, **kwargs):
|
|
54
|
+
rng = cls.get_rng(rng_seed)
|
|
55
|
+
digits = cls.get_digits_to_round(digits_to_round=kwargs.get("digits_to_round"))
|
|
54
56
|
|
|
55
|
-
# num_input_channels = 1
|
|
56
57
|
input_size = kwargs.get("input_size", 4)
|
|
57
58
|
num_filters = kwargs.get("num_filters", 1)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
stride = kwargs.get("stride", 1)
|
|
60
|
+
padding = kwargs.get("padding", 0)
|
|
61
|
+
|
|
62
|
+
image = cls.get_rounded_matrix(rng, (input_size, input_size), digits_to_round=digits)
|
|
63
|
+
kernel = cls.get_rounded_matrix(rng, (3, 3, num_filters), -1, 1, digits)
|
|
64
|
+
result = cls.conv2d_multi_channel(image, kernel, stride=stride, padding=padding)
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
"digits": digits,
|
|
68
|
+
"input_size": input_size,
|
|
69
|
+
"num_filters": num_filters,
|
|
70
|
+
"stride": stride,
|
|
71
|
+
"padding": padding,
|
|
72
|
+
"image": image,
|
|
73
|
+
"kernel": kernel,
|
|
74
|
+
"result": result,
|
|
70
75
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def _get_body(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def _build_body(cls, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
75
79
|
"""Build question body and collect answers."""
|
|
76
80
|
body = ca.Section()
|
|
77
81
|
answers = []
|
|
@@ -79,7 +83,7 @@ class ConvolutionCalculation(MatrixQuestion):
|
|
|
79
83
|
body.add_elements(
|
|
80
84
|
[
|
|
81
85
|
ca.Text("Given image represented as matrix: "),
|
|
82
|
-
ca.Matrix(
|
|
86
|
+
ca.Matrix(context["image"], name="image")
|
|
83
87
|
]
|
|
84
88
|
)
|
|
85
89
|
|
|
@@ -87,38 +91,35 @@ class ConvolutionCalculation(MatrixQuestion):
|
|
|
87
91
|
[
|
|
88
92
|
ca.Text("And convolution filters: "),
|
|
89
93
|
] + [
|
|
90
|
-
ca.Matrix(
|
|
91
|
-
for i in range(
|
|
94
|
+
ca.Matrix(context["kernel"][:, :, i], name=f"Filter {i}")
|
|
95
|
+
for i in range(context["kernel"].shape[-1])
|
|
92
96
|
]
|
|
93
97
|
)
|
|
94
98
|
|
|
95
99
|
body.add_element(
|
|
96
100
|
ca.Paragraph(
|
|
97
101
|
[
|
|
98
|
-
f"Calculate the output of the convolution operation. Assume stride = {
|
|
102
|
+
f"Calculate the output of the convolution operation. Assume stride = {context['stride']} and padding = {context['padding']}."
|
|
99
103
|
]
|
|
100
104
|
)
|
|
101
105
|
)
|
|
102
106
|
|
|
103
107
|
body.add_element(ca.LineBreak())
|
|
104
108
|
|
|
105
|
-
for i in range(
|
|
106
|
-
|
|
109
|
+
for i in range(context["result"].shape[-1]):
|
|
110
|
+
answer = ca.AnswerTypes.Matrix(context["result"][:, :, i], label=f"Result of filter {i}")
|
|
111
|
+
answers.append(answer)
|
|
107
112
|
body.add_elements([
|
|
108
113
|
ca.Container([
|
|
109
|
-
|
|
114
|
+
answer,
|
|
110
115
|
ca.LineBreak()
|
|
111
116
|
])
|
|
112
117
|
])
|
|
113
118
|
|
|
114
119
|
return body, answers
|
|
115
120
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
body, _ = self._get_body(**kwargs)
|
|
119
|
-
return body
|
|
120
|
-
|
|
121
|
-
def _get_explanation(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
121
|
+
@classmethod
|
|
122
|
+
def _build_explanation(cls, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
122
123
|
"""Build question explanation."""
|
|
123
124
|
explanation = ca.Section()
|
|
124
125
|
digits = ca.Answer.DEFAULT_ROUNDING_DIGITS
|
|
@@ -131,14 +132,14 @@ class ConvolutionCalculation(MatrixQuestion):
|
|
|
131
132
|
|
|
132
133
|
explanation.add_element(
|
|
133
134
|
ca.Paragraph([
|
|
134
|
-
f"With stride={
|
|
135
|
+
f"With stride={context['stride']} and padding={context['padding']}: ",
|
|
135
136
|
f"stride controls how many pixels the filter moves each step, ",
|
|
136
|
-
f"and padding adds zeros around the border {'(no border in this case)' if
|
|
137
|
+
f"and padding adds zeros around the border {'(no border in this case)' if context['padding'] == 0 else f'({context['padding']} pixels)'}."
|
|
137
138
|
])
|
|
138
139
|
)
|
|
139
140
|
|
|
140
141
|
# For each filter, show one detailed example computation
|
|
141
|
-
for f_idx in range(
|
|
142
|
+
for f_idx in range(context["kernel"].shape[-1]):
|
|
142
143
|
explanation.add_element(
|
|
143
144
|
ca.Paragraph([
|
|
144
145
|
ca.Text(f"Filter {f_idx}:", emphasis=True)
|
|
@@ -147,7 +148,7 @@ class ConvolutionCalculation(MatrixQuestion):
|
|
|
147
148
|
|
|
148
149
|
# Show the filter (rounded)
|
|
149
150
|
explanation.add_element(
|
|
150
|
-
ca.Matrix(np.round(
|
|
151
|
+
ca.Matrix(np.round(context["kernel"][:, :, f_idx], digits), name=f"Filter {f_idx}")
|
|
151
152
|
)
|
|
152
153
|
|
|
153
154
|
# Show ONE example computation (position 0,0)
|
|
@@ -158,23 +159,23 @@ class ConvolutionCalculation(MatrixQuestion):
|
|
|
158
159
|
)
|
|
159
160
|
|
|
160
161
|
# Account for padding when extracting receptive field
|
|
161
|
-
if
|
|
162
|
-
padded_image = np.pad(
|
|
162
|
+
if context["padding"] > 0:
|
|
163
|
+
padded_image = np.pad(context["image"], ((context["padding"], context["padding"]), (context["padding"], context["padding"])), mode='constant')
|
|
163
164
|
receptive_field = padded_image[0:3, 0:3]
|
|
164
165
|
else:
|
|
165
|
-
receptive_field =
|
|
166
|
+
receptive_field = context["image"][0:3, 0:3]
|
|
166
167
|
|
|
167
168
|
computation_steps = []
|
|
168
169
|
for r in range(3):
|
|
169
170
|
row_terms = []
|
|
170
171
|
for c in range(3):
|
|
171
172
|
img_val = receptive_field[r, c]
|
|
172
|
-
kernel_val =
|
|
173
|
+
kernel_val = context["kernel"][r, c, f_idx]
|
|
173
174
|
row_terms.append(f"({img_val:.2f} \\times {kernel_val:.2f})")
|
|
174
175
|
computation_steps.append(" + ".join(row_terms))
|
|
175
176
|
|
|
176
177
|
equation_str = " + ".join(computation_steps)
|
|
177
|
-
result_val =
|
|
178
|
+
result_val = context["result"][0, 0, f_idx]
|
|
178
179
|
|
|
179
180
|
explanation.add_element(
|
|
180
181
|
ca.Equation(f"{equation_str} = {result_val:.2f}")
|
|
@@ -187,12 +188,7 @@ class ConvolutionCalculation(MatrixQuestion):
|
|
|
187
188
|
])
|
|
188
189
|
)
|
|
189
190
|
explanation.add_element(
|
|
190
|
-
ca.Matrix(np.round(
|
|
191
|
+
ca.Matrix(np.round(context["result"][:, :, f_idx], digits))
|
|
191
192
|
)
|
|
192
193
|
|
|
193
194
|
return explanation, []
|
|
194
|
-
|
|
195
|
-
def get_explanation(self, **kwargs) -> ca.Section:
|
|
196
|
-
"""Build question explanation (backward compatible interface)."""
|
|
197
|
-
explanation, _ = self._get_explanation(**kwargs)
|
|
198
|
-
return explanation
|
|
@@ -7,18 +7,68 @@ from QuizGenerator.question import Question
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class MatrixQuestion(Question, abc.ABC):
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def get_rounded_matrix(self, shape, low=0, high=1, digits_to_round=None):
|
|
10
|
+
default_digits_to_round = 2
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def get_rng(rng_seed):
|
|
14
|
+
return np.random.RandomState(rng_seed)
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def get_digits_to_round(cls, *, digits_to_round=None):
|
|
19
18
|
if digits_to_round is None:
|
|
20
|
-
digits_to_round =
|
|
19
|
+
digits_to_round = cls.default_digits_to_round
|
|
20
|
+
return digits_to_round
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def get_rounded_matrix(*args, **kwargs):
|
|
24
|
+
# Supports (rng, shape, ...) or (self, shape, ...)
|
|
25
|
+
if not args:
|
|
26
|
+
raise ValueError("get_rounded_matrix requires at least a rng or self plus shape.")
|
|
27
|
+
|
|
28
|
+
if isinstance(args[0], MatrixQuestion):
|
|
29
|
+
self = args[0]
|
|
30
|
+
shape = args[1]
|
|
31
|
+
remaining = list(args[2:])
|
|
32
|
+
rng = kwargs.pop("rng", getattr(self, "_np_rng", np.random.RandomState()))
|
|
33
|
+
low = kwargs.pop("low", None)
|
|
34
|
+
high = kwargs.pop("high", None)
|
|
35
|
+
digits_to_round = kwargs.pop("digits_to_round", None)
|
|
36
|
+
|
|
37
|
+
if remaining:
|
|
38
|
+
low = remaining.pop(0)
|
|
39
|
+
if remaining:
|
|
40
|
+
high = remaining.pop(0)
|
|
41
|
+
if remaining:
|
|
42
|
+
digits_to_round = remaining.pop(0)
|
|
43
|
+
|
|
44
|
+
if low is None:
|
|
45
|
+
low = 0
|
|
46
|
+
if high is None:
|
|
47
|
+
high = 1
|
|
48
|
+
digits_to_round = self.get_digits_to_round(digits_to_round=digits_to_round)
|
|
49
|
+
else:
|
|
50
|
+
rng = args[0]
|
|
51
|
+
shape = args[1]
|
|
52
|
+
remaining = list(args[2:])
|
|
53
|
+
low = kwargs.pop("low", None)
|
|
54
|
+
high = kwargs.pop("high", None)
|
|
55
|
+
digits_to_round = kwargs.pop("digits_to_round", None)
|
|
56
|
+
|
|
57
|
+
if remaining:
|
|
58
|
+
low = remaining.pop(0)
|
|
59
|
+
if remaining:
|
|
60
|
+
high = remaining.pop(0)
|
|
61
|
+
if remaining:
|
|
62
|
+
digits_to_round = remaining.pop(0)
|
|
63
|
+
|
|
64
|
+
if low is None:
|
|
65
|
+
low = 0
|
|
66
|
+
if high is None:
|
|
67
|
+
high = 1
|
|
68
|
+
if digits_to_round is None:
|
|
69
|
+
digits_to_round = 2
|
|
70
|
+
|
|
21
71
|
return np.round(
|
|
22
|
-
(high - low) *
|
|
72
|
+
(high - low) * rng.rand(*shape) + low,
|
|
23
73
|
digits_to_round
|
|
24
74
|
)
|
|
@@ -44,31 +44,38 @@ class RNNForwardPass(MatrixQuestion, TableQuestionMixin):
|
|
|
44
44
|
|
|
45
45
|
return h_states
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
@classmethod
|
|
48
|
+
def _build_context(cls, *, rng_seed=None, **kwargs):
|
|
49
|
+
rng = cls.get_rng(rng_seed)
|
|
50
|
+
digits = cls.get_digits_to_round(digits_to_round=kwargs.get("digits_to_round"))
|
|
51
|
+
|
|
51
52
|
seq_len = kwargs.get("seq_len", 3)
|
|
52
|
-
input_dim =
|
|
53
|
+
input_dim = kwargs.get("input_dim", 1)
|
|
53
54
|
hidden_dim = kwargs.get("hidden_dim", 1)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
55
|
+
|
|
56
|
+
x_seq = cls.get_rounded_matrix(rng, (seq_len, input_dim), digits_to_round=digits)
|
|
57
|
+
W_xh = cls.get_rounded_matrix(rng, (input_dim, hidden_dim), -1, 2, digits)
|
|
58
|
+
W_hh = cls.get_rounded_matrix(rng, (hidden_dim, hidden_dim), -1, 2, digits)
|
|
59
|
+
b_h = cls.get_rounded_matrix(rng, (hidden_dim,), -1, 2, digits)
|
|
60
|
+
h_0 = np.zeros(hidden_dim)
|
|
61
|
+
|
|
62
|
+
h_states = cls.rnn_forward(x_seq, W_xh, W_hh, b_h, h_0)
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
"digits": digits,
|
|
66
|
+
"seq_len": seq_len,
|
|
67
|
+
"input_dim": input_dim,
|
|
68
|
+
"hidden_dim": hidden_dim,
|
|
69
|
+
"x_seq": x_seq,
|
|
70
|
+
"W_xh": W_xh,
|
|
71
|
+
"W_hh": W_hh,
|
|
72
|
+
"b_h": b_h,
|
|
73
|
+
"h_0": h_0,
|
|
74
|
+
"h_states": h_states,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def _build_body(cls, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
72
79
|
"""Build question body and collect answers."""
|
|
73
80
|
body = ca.Section()
|
|
74
81
|
answers = []
|
|
@@ -80,30 +87,27 @@ class RNNForwardPass(MatrixQuestion, TableQuestionMixin):
|
|
|
80
87
|
])
|
|
81
88
|
)
|
|
82
89
|
body.add_element(
|
|
83
|
-
|
|
90
|
+
cls.create_info_table(
|
|
84
91
|
{
|
|
85
|
-
ca.Container(["Input sequence, ", ca.Equation("x_{seq}", inline=True)]) : ca.Matrix(
|
|
86
|
-
ca.Container(["Input weights, ", ca.Equation("W_{xh}", inline=True)]) : ca.Matrix(
|
|
87
|
-
ca.Container(["Hidden weights, ", ca.Equation("W_{hh}", inline=True)]) : ca.Matrix(
|
|
88
|
-
ca.Container(["Bias, ", ca.Equation("b_{h}", inline=True)]) : ca.Matrix(
|
|
89
|
-
ca.Container(["Hidden states, ", ca.Equation("h_{0}", inline=True)]) : ca.Matrix(
|
|
92
|
+
ca.Container(["Input sequence, ", ca.Equation("x_{seq}", inline=True)]) : ca.Matrix(context["x_seq"]),
|
|
93
|
+
ca.Container(["Input weights, ", ca.Equation("W_{xh}", inline=True)]) : ca.Matrix(context["W_xh"]),
|
|
94
|
+
ca.Container(["Hidden weights, ", ca.Equation("W_{hh}", inline=True)]) : ca.Matrix(context["W_hh"]),
|
|
95
|
+
ca.Container(["Bias, ", ca.Equation("b_{h}", inline=True)]) : ca.Matrix(context["b_h"]),
|
|
96
|
+
ca.Container(["Hidden states, ", ca.Equation("h_{0}", inline=True)]) : ca.Matrix(context["h_0"]),
|
|
90
97
|
}
|
|
91
98
|
)
|
|
92
99
|
)
|
|
93
100
|
|
|
94
101
|
body.add_element(ca.LineBreak())
|
|
95
102
|
|
|
96
|
-
|
|
97
|
-
|
|
103
|
+
output_answer = ca.AnswerTypes.Matrix(value=context["h_states"], label="Hidden states")
|
|
104
|
+
answers.append(output_answer)
|
|
105
|
+
body.add_element(output_answer)
|
|
98
106
|
|
|
99
107
|
return body, answers
|
|
100
108
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
body, _ = self._get_body(**kwargs)
|
|
104
|
-
return body
|
|
105
|
-
|
|
106
|
-
def _get_explanation(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
109
|
+
@classmethod
|
|
110
|
+
def _build_explanation(cls, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
107
111
|
"""Build question explanation."""
|
|
108
112
|
explanation = ca.Section()
|
|
109
113
|
digits = ca.Answer.DEFAULT_ROUNDING_DIGITS
|
|
@@ -134,7 +138,7 @@ class RNNForwardPass(MatrixQuestion, TableQuestionMixin):
|
|
|
134
138
|
return "[" + ", ".join([f"{fix_negative_zero(x):.{digits}f}" for x in arr.flatten()]) + "]"
|
|
135
139
|
|
|
136
140
|
# Show detailed examples for first 2 timesteps (or just 1 if seq_len == 1)
|
|
137
|
-
seq_len = len(
|
|
141
|
+
seq_len = len(context["x_seq"])
|
|
138
142
|
num_examples = min(2, seq_len)
|
|
139
143
|
|
|
140
144
|
explanation.add_element(ca.Paragraph([""]))
|
|
@@ -147,18 +151,18 @@ class RNNForwardPass(MatrixQuestion, TableQuestionMixin):
|
|
|
147
151
|
)
|
|
148
152
|
|
|
149
153
|
# Compute step t
|
|
150
|
-
x_contribution =
|
|
154
|
+
x_contribution = context["x_seq"][t] @ context["W_xh"]
|
|
151
155
|
if t == 0:
|
|
152
|
-
h_prev =
|
|
156
|
+
h_prev = context["h_0"]
|
|
153
157
|
h_prev_label = 'h_{-1}'
|
|
154
158
|
h_prev_desc = " (initial state)"
|
|
155
159
|
else:
|
|
156
|
-
h_prev =
|
|
160
|
+
h_prev = context["h_states"][t-1]
|
|
157
161
|
h_prev_label = f'h_{{{t-1}}}'
|
|
158
162
|
h_prev_desc = ""
|
|
159
163
|
|
|
160
|
-
h_contribution = h_prev @
|
|
161
|
-
pre_activation = x_contribution + h_contribution +
|
|
164
|
+
h_contribution = h_prev @ context["W_hh"]
|
|
165
|
+
pre_activation = x_contribution + h_contribution + context["b_h"]
|
|
162
166
|
h_result = np.tanh(pre_activation)
|
|
163
167
|
|
|
164
168
|
explanation.add_element(
|
|
@@ -203,13 +207,7 @@ class RNNForwardPass(MatrixQuestion, TableQuestionMixin):
|
|
|
203
207
|
)
|
|
204
208
|
|
|
205
209
|
explanation.add_element(
|
|
206
|
-
ca.Matrix(np.round(
|
|
210
|
+
ca.Matrix(np.round(context["h_states"], digits))
|
|
207
211
|
)
|
|
208
212
|
|
|
209
213
|
return explanation, []
|
|
210
|
-
|
|
211
|
-
def get_explanation(self, **kwargs) -> ca.Section:
|
|
212
|
-
"""Build question explanation (backward compatible interface)."""
|
|
213
|
-
explanation, _ = self._get_explanation(**kwargs)
|
|
214
|
-
return explanation
|
|
215
|
-
|
|
@@ -34,73 +34,77 @@ class word2vec__skipgram(MatrixQuestion, TableQuestionMixin):
|
|
|
34
34
|
|
|
35
35
|
return logits, probs
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
@classmethod
|
|
38
|
+
def _build_context(cls, *, rng_seed=None, **kwargs):
|
|
39
|
+
rng = cls.get_rng(rng_seed)
|
|
40
|
+
digits = cls.get_digits_to_round(digits_to_round=kwargs.get("digits_to_round"))
|
|
41
|
+
|
|
41
42
|
embed_dim = kwargs.get("embed_dim", 3)
|
|
42
43
|
num_contexts = kwargs.get("num_contexts", 3)
|
|
43
|
-
|
|
44
|
-
# Vocabulary pool
|
|
45
|
-
vocab = ['cat', 'dog', 'run', 'jump', 'happy', 'sad', 'tree', 'house',
|
|
46
|
-
'walk', 'sleep', 'fast', 'slow', 'big', 'small']
|
|
47
|
-
|
|
48
|
-
# Sample words
|
|
49
|
-
self.selected_words = self.rng.choice(vocab, size=num_contexts + 1, replace=False)
|
|
50
|
-
self.center_word = self.selected_words[0]
|
|
51
|
-
self.context_words = self.selected_words[1:]
|
|
52
|
-
|
|
53
|
-
# Small integer embeddings
|
|
54
44
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
45
|
+
vocab = [
|
|
46
|
+
'cat', 'dog', 'run', 'jump', 'happy', 'sad', 'tree', 'house',
|
|
47
|
+
'walk', 'sleep', 'fast', 'slow', 'big', 'small'
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
selected_words = rng.choice(vocab, size=num_contexts + 1, replace=False)
|
|
51
|
+
center_word = selected_words[0]
|
|
52
|
+
context_words = selected_words[1:]
|
|
53
|
+
|
|
54
|
+
center_emb = cls.get_rounded_matrix(rng, (embed_dim,), -2, 3, digits)
|
|
55
|
+
context_embs = cls.get_rounded_matrix(rng, (num_contexts, embed_dim), -2, 3, digits)
|
|
56
|
+
|
|
57
|
+
logits, probs = cls.skipgram_predict(center_emb, context_embs)
|
|
58
|
+
|
|
59
|
+
most_likely_idx = np.argmax(probs)
|
|
60
|
+
most_likely_word = context_words[most_likely_idx]
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
"digits": digits,
|
|
64
|
+
"embed_dim": embed_dim,
|
|
65
|
+
"num_contexts": num_contexts,
|
|
66
|
+
"center_word": center_word,
|
|
67
|
+
"context_words": context_words,
|
|
68
|
+
"center_emb": center_emb,
|
|
69
|
+
"context_embs": context_embs,
|
|
70
|
+
"logits": logits,
|
|
71
|
+
"probs": probs,
|
|
72
|
+
"most_likely_word": most_likely_word,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def _build_body(cls, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
71
77
|
"""Build question body and collect answers."""
|
|
72
78
|
body = ca.Section()
|
|
73
79
|
answers = []
|
|
74
80
|
|
|
75
81
|
body.add_element(
|
|
76
82
|
ca.Paragraph([
|
|
77
|
-
f"Given center word: `{
|
|
83
|
+
f"Given center word: `{context['center_word']}` with embedding {context['center_emb']}, compute the skip-gram probabilities for each context word and identify the most likely one."
|
|
78
84
|
])
|
|
79
85
|
)
|
|
80
86
|
body.add_elements([
|
|
81
|
-
ca.Paragraph([ca.Text(f"`{w}` : "), str(e)]) for w, e in zip(
|
|
87
|
+
ca.Paragraph([ca.Text(f"`{w}` : "), str(e)]) for w, e in zip(context["context_words"], context["context_embs"])
|
|
82
88
|
])
|
|
83
89
|
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
logits_answer = ca.AnswerTypes.Vector(context["logits"], label="Logits")
|
|
91
|
+
center_word_answer = ca.AnswerTypes.String(context["most_likely_word"], label="Most likely context word")
|
|
92
|
+
answers.append(logits_answer)
|
|
93
|
+
answers.append(center_word_answer)
|
|
86
94
|
body.add_elements([
|
|
87
95
|
ca.LineBreak(),
|
|
88
|
-
|
|
96
|
+
logits_answer,
|
|
89
97
|
ca.LineBreak(),
|
|
90
|
-
|
|
98
|
+
center_word_answer
|
|
91
99
|
])
|
|
92
100
|
|
|
93
|
-
log.debug(f"output: {
|
|
94
|
-
log.debug(f"weights: {
|
|
101
|
+
log.debug(f"output: {context['logits']}")
|
|
102
|
+
log.debug(f"weights: {context['probs']}")
|
|
95
103
|
|
|
96
104
|
return body, answers
|
|
97
105
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
body, _ = self._get_body(**kwargs)
|
|
101
|
-
return body
|
|
102
|
-
|
|
103
|
-
def _get_explanation(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
106
|
+
@classmethod
|
|
107
|
+
def _build_explanation(cls, context) -> Tuple[ca.Section, List[ca.Answer]]:
|
|
104
108
|
"""Build question explanation."""
|
|
105
109
|
explanation = ca.Section()
|
|
106
110
|
digits = ca.Answer.DEFAULT_ROUNDING_DIGITS
|
|
@@ -119,10 +123,10 @@ class word2vec__skipgram(MatrixQuestion, TableQuestionMixin):
|
|
|
119
123
|
)
|
|
120
124
|
|
|
121
125
|
# Format center embedding
|
|
122
|
-
center_emb_str = "[" + ", ".join([f"{x:.{digits}f}" for x in
|
|
126
|
+
center_emb_str = "[" + ", ".join([f"{x:.{digits}f}" for x in context["center_emb"]]) + "]"
|
|
123
127
|
explanation.add_element(
|
|
124
128
|
ca.Paragraph([
|
|
125
|
-
f"Center word `{
|
|
129
|
+
f"Center word `{context['center_word']}`: {center_emb_str}"
|
|
126
130
|
])
|
|
127
131
|
)
|
|
128
132
|
|
|
@@ -132,7 +136,7 @@ class word2vec__skipgram(MatrixQuestion, TableQuestionMixin):
|
|
|
132
136
|
])
|
|
133
137
|
)
|
|
134
138
|
|
|
135
|
-
for i, (word, emb) in enumerate(zip(
|
|
139
|
+
for i, (word, emb) in enumerate(zip(context["context_words"], context["context_embs"])):
|
|
136
140
|
emb_str = "[" + ", ".join([f"{x:.2f}" for x in emb]) + "]"
|
|
137
141
|
explanation.add_element(
|
|
138
142
|
ca.Paragraph([
|
|
@@ -150,20 +154,20 @@ class word2vec__skipgram(MatrixQuestion, TableQuestionMixin):
|
|
|
150
154
|
# Show ONE example
|
|
151
155
|
explanation.add_element(
|
|
152
156
|
ca.Paragraph([
|
|
153
|
-
f"Example: Logit for `{
|
|
157
|
+
f"Example: Logit for `{context['context_words'][0]}`"
|
|
154
158
|
])
|
|
155
159
|
)
|
|
156
160
|
|
|
157
|
-
context_emb =
|
|
158
|
-
dot_product_terms = " + ".join([f"({
|
|
159
|
-
for j in range(len(
|
|
160
|
-
logit_val =
|
|
161
|
+
context_emb = context["context_embs"][0]
|
|
162
|
+
dot_product_terms = " + ".join([f"({context['center_emb'][j]:.2f} \\times {context_emb[j]:.2f})"
|
|
163
|
+
for j in range(len(context["center_emb"]))])
|
|
164
|
+
logit_val = context["logits"][0]
|
|
161
165
|
|
|
162
166
|
explanation.add_element(
|
|
163
167
|
ca.Equation(f"{dot_product_terms} = {logit_val:.2f}")
|
|
164
168
|
)
|
|
165
169
|
|
|
166
|
-
logits_str = "[" + ", ".join([f"{x:.2f}" for x in
|
|
170
|
+
logits_str = "[" + ", ".join([f"{x:.2f}" for x in context["logits"]]) + "]"
|
|
167
171
|
explanation.add_element(
|
|
168
172
|
ca.Paragraph([
|
|
169
173
|
f"All logits: {logits_str}"
|
|
@@ -177,10 +181,10 @@ class word2vec__skipgram(MatrixQuestion, TableQuestionMixin):
|
|
|
177
181
|
])
|
|
178
182
|
)
|
|
179
183
|
|
|
180
|
-
exp_logits = np.exp(
|
|
184
|
+
exp_logits = np.exp(context["logits"])
|
|
181
185
|
sum_exp = exp_logits.sum()
|
|
182
186
|
|
|
183
|
-
exp_terms = " + ".join([f"e^{{{l:.{digits}f}}}" for l in
|
|
187
|
+
exp_terms = " + ".join([f"e^{{{l:.{digits}f}}}" for l in context["logits"]])
|
|
184
188
|
|
|
185
189
|
explanation.add_element(
|
|
186
190
|
ca.Equation(f"\\text{{denominator}} = {exp_terms} = {sum_exp:.{digits}f}")
|
|
@@ -192,26 +196,20 @@ class word2vec__skipgram(MatrixQuestion, TableQuestionMixin):
|
|
|
192
196
|
])
|
|
193
197
|
)
|
|
194
198
|
|
|
195
|
-
for i, (word, prob) in enumerate(zip(
|
|
199
|
+
for i, (word, prob) in enumerate(zip(context["context_words"], context["probs"])):
|
|
196
200
|
explanation.add_element(
|
|
197
|
-
ca.Equation(f"P(\\text{{{word}}}) = \\frac{{e^{{{
|
|
201
|
+
ca.Equation(f"P(\\text{{{word}}}) = \\frac{{e^{{{context['logits'][i]:.{digits}f}}}}}{{{sum_exp:.{digits}f}}} = {prob:.{digits}f}")
|
|
198
202
|
)
|
|
199
203
|
|
|
200
204
|
# Step 4: Identify most likely
|
|
201
|
-
most_likely_idx = np.argmax(
|
|
202
|
-
most_likely_word =
|
|
205
|
+
most_likely_idx = np.argmax(context["probs"])
|
|
206
|
+
most_likely_word = context["context_words"][most_likely_idx]
|
|
203
207
|
|
|
204
208
|
explanation.add_element(
|
|
205
209
|
ca.Paragraph([
|
|
206
210
|
ca.Text("Conclusion:", emphasis=True),
|
|
207
|
-
f" The most likely context word is `{most_likely_word}` with probability {
|
|
211
|
+
f" The most likely context word is `{most_likely_word}` with probability {context['probs'][most_likely_idx]:.{digits}f}"
|
|
208
212
|
])
|
|
209
213
|
)
|
|
210
214
|
|
|
211
215
|
return explanation, []
|
|
212
|
-
|
|
213
|
-
def get_explanation(self, **kwargs) -> ca.Section:
|
|
214
|
-
"""Build question explanation (backward compatible interface)."""
|
|
215
|
-
explanation, _ = self._get_explanation(**kwargs)
|
|
216
|
-
return explanation
|
|
217
|
-
|