Skip to content

Commit 4626cfd

Browse files
committed
added streak controller
1 parent 7abfbab commit 4626cfd

3 files changed

Lines changed: 263 additions & 0 deletions

File tree

‎al_outerloop/controllers/streak.py‎

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
from .base import OuterLoopController
2+
from random import choice
3+
import json
4+
import sys,os,inspect
5+
import colorama
6+
from colorama import Fore, Back, Style
7+
8+
colorama.init(autoreset=True)
9+
10+
11+
#######
12+
# This version of the BKT controller updates
13+
# mastery probabilities by treating every interface component
14+
# (in the Selection field) as a separate KC, and choosing a
15+
# problem with the most unmastered KCs. It must be used with
16+
# an action_space that specifies the KCs in the problem set.
17+
# Chooses a problem with the maximum number of unm
18+
#
19+
# Options specifiable via outerloop_args:
20+
# bkt_probs (required) : Specifies the BKT probabilities for each skill:
21+
# - known: the probability the skill is already learned before any practice attempts
22+
# - learn: the probability of learning the skill on a single practice attempt
23+
# - guess: the probability a learner will answer correctly despite not knowing a skill
24+
# - slip: the probability a learner will answer incorrectly despite knowing a skill
25+
# num_test_problems : number of problems to use for testing (not training);
26+
# these problems will be the final problems specified in the action_space.
27+
# interface_to_skill : a mapping between each interface component and skill
28+
# If not included, raw interface components are skills. If this maps everything
29+
# to an empty string, than include_problem_start_in_kc can be used to map
30+
# all steps in a problem to the same skill.
31+
# single_kc : all attempts use the same kc
32+
# include_problem_start_in_kc : in addition to the interface component
33+
# skills will include any part of the problem name that occurs before a space,
34+
# or if num_chars_problem_start is included as well, that number of characters
35+
# from the problem name
36+
#######
37+
38+
39+
class Streak(OuterLoopController):
40+
def __init__(self):
41+
super().__init__()
42+
43+
def new_student(self, student_id, action_space=None, outer_loop_args = None):
44+
super().new_student(student_id, action_space)
45+
46+
self.kcs = outer_loop_args['kcs']
47+
self.interface_to_kc = outer_loop_args['interface_to_kc']
48+
49+
self.correct_counts = {kc: 0 for kc in self.kcs}
50+
self.num_problems = 0
51+
52+
self.choose_max_unmastered = True
53+
if 'choose_max_unmastered' in outer_loop_args:
54+
self.choose_max_unmastered = outer_loop_args['choose_max_unmastered']
55+
56+
# default streak threshold
57+
self.streak_threshold = 3
58+
if 'streak_threshold' in outer_loop_args:
59+
self.streak_threshold = outer_loop_args['streak_threshold']
60+
61+
# default max problems of 150
62+
self.max_problems = 150
63+
if 'max_problems' in outer_loop_args:
64+
self.max_problems = outer_loop_args['max_problems']
65+
66+
# Whether we're in testing or training - we'll only start in test mode if
67+
# the probability known is higher than the threshold for all skills.
68+
# (This generally shouldn't happen.)
69+
self.test_mode = False # We start off in training mode
70+
71+
# Map each problem to a skill
72+
self.problems_by_skill = {}
73+
for problem in self.action_space:
74+
kcs = self.get_problem_kcs(problem)
75+
for kc in kcs:
76+
if kc not in self.problems_by_skill:
77+
self.problems_by_skill[kc] = []
78+
self.problems_by_skill[kc].append(problem)
79+
80+
# print("self.problems_by_skill")
81+
# print(self.problems_by_skill)
82+
83+
def resolve_kcs(self, step):
84+
kc_list = self.current_prob['kc_list']
85+
kc_list2 = self.interface_to_kc[step]
86+
return set(kc_list).intersection(set(kc_list2))
87+
88+
89+
def update(self, step, reward, action_type):
90+
# 0/1 for correct incorrect rather than a string to print
91+
correctness_numeric = 1 if reward > 0 and action_type == "ATTEMPT" else 0
92+
93+
if(action_type == "ATTEMPT"):
94+
correctness = Back.GREEN + "correct" if reward > 0 else Back.RED + "incorrect"
95+
else:
96+
correctness = Back.BLUE + "example"
97+
98+
# Print out information about performance
99+
print(Fore.CYAN + "Streak Controller Udpate:", step, reward, correctness)
100+
101+
# This controller updates the knowledge component based on the interface marked in the
102+
# Selection field - here, that corresponds to the step variable.
103+
kcs = self.resolve_kcs(step)
104+
105+
for skill in kcs:
106+
if skill not in self.correct_counts:
107+
print("ERROR:", skill, "not included in kc_list adding... (",
108+
problem_name, ", ", step, ")")
109+
self.correct_counts[skill] = 0
110+
111+
if correctness_numeric == 1:
112+
self.correct_counts[skill] += 1
113+
else:
114+
self.correct_counts[skill] = 0
115+
116+
print("Correctness count for %s after update: %i" % (skill, self.correct_count[skill]))
117+
118+
def get_problem_kcs(self, problem):
119+
if("kc_list" in problem):
120+
kcs = problem["kc_list"]
121+
else:
122+
raise ValueError("Problem objects must have attribute 'kc_list'")
123+
124+
return kcs
125+
126+
127+
128+
def all_skills_mastered(self):
129+
'''
130+
Returns true if all skills that we have problems for are above mastery threshold
131+
'''
132+
for skill in self.correct_counts:
133+
if (skill in self.problems_by_skill and
134+
self.correct_counts[skill] <= self.streak_threshold):
135+
return False
136+
return True
137+
138+
def next_problem(self,student=None):
139+
self.num_problems += 1
140+
141+
if self.all_skills_mastered() or len(self.rewards) > max_problems:
142+
# All skills have been mastered or we've asked as many
143+
# problems as allowed - stop training.
144+
self.test_mode = True;
145+
print("Mastery estimates when entering testing:", self.correct_counts)
146+
print("Skills mastered:", self.all_skills_mastered())
147+
148+
if self.test_mode:
149+
if len(self.test_set) > 0:
150+
nxt = self.test_set.pop(0)
151+
nxt["test_mode"] = True
152+
return nxt
153+
else:
154+
print("Problems in training ( total number =", len(self.problems_asked),")")
155+
print(self.problems_asked)
156+
return None # done training
157+
else:
158+
print("Asking for problem ", self.num_problems)
159+
print("Mastery_Probabilities: ",self.correct_count)
160+
# Choose a problem with the most unmastered skills
161+
162+
max_unmastered_kcs = 0
163+
problem_with_max_unmastered_kcs = []
164+
for problem in self.action_space:
165+
kcs = self.get_problem_kcs(problem)
166+
# if bkt_config.get("single_kc", False):
167+
# skills = problem["kc_list"]
168+
# else:
169+
# skills = ["single_kc"]
170+
171+
# Check how many skills that are used in this problem are unmastered
172+
unmastered_kcs = [kc for kc in kcs if self.correct_counts[kc] < self.streak_threshold]
173+
if self.choose_max_unmastered:
174+
if len(unmastered_kcs) > max_unmastered_kcs:
175+
max_unmastered_kcs = len(unmastered_kcs)
176+
problem_with_max_unmastered_kcs = [problem]
177+
elif len(unmastered_kcs) == max_unmastered_kcs: # We'll choose randomly among problems with the same number of unmastered skills
178+
problem_with_max_unmastered_kcs.append(problem)
179+
else:
180+
problem_with_max_unmastered_kcs.append(problem)
181+
182+
# print("Number of problems with", max_unmastered_kcs, "unmastered skills:", len(problem_with_max_unmastered_kcs))
183+
# Choose a random problem from problems with the maximum unmastered skills
184+
nxt = choice(problem_with_max_unmastered_kcs)
185+
self.problems_asked.append(nxt["question_file"])
186+
self.current_prob = nxt
187+
# print("N",nxt)
188+
return nxt
189+

‎al_outerloop/server.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
print(sys.path)
2020
from controllers.random import Random
2121
from controllers.bkt import BKT
22+
from controllers.bkt import Streak
23+
2224
def str_to_class(s):
2325
return getattr(sys.modules[__name__], s)
2426

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{
2+
"set_params" : {
3+
"agent" : {
4+
"agent_type": "ModularAgent",
5+
"stay_active": true,
6+
"dont_save": true,
7+
"args": {
8+
"planner" : "numba",
9+
"when_learner": "trestle",
10+
"where_learner": "FastMostSpecific",
11+
"state_variablization" : "whereappend",
12+
"strip_attrs" : ["to_left","to_right","above","below","type","id","offsetParent"],
13+
"when_args" : {
14+
"cross_rhs_inference" : "none"
15+
},
16+
"function_set" : ["RipFloatValue","Add", "Subtract","Numerator_Multiply", "Cross_Multiply", "Divide"],
17+
"feature_set" : ["Equals"]
18+
}
19+
},
20+
"HTML": "tutors/FractionArithmetic/fraction_arithmetic.html",
21+
"abs_qf_paths": true
22+
},
23+
"training_set1": [
24+
{
25+
"agent_name": "Streak Agent",
26+
"repetitions" : 100,
27+
"outer_loop_controller" : {
28+
"type" : "Streak",
29+
"args" : {
30+
"streak_threshold": 3,
31+
"max_problems": 150,
32+
"choose_max_unmastered": True,
33+
"kcs": ["AD check_convert","AD den3","AD num3","AD den4",
34+
"AD num4","AD den5","AD num5","AD done","AS den5",
35+
"AS num5","AS done","M den5","M num5","M done"],
36+
"interface_to_kcs" : {
37+
"check_convert" : ["AD check_convert"],
38+
"done" : ["AD done", "AS done", "M done"],
39+
"JCommTable4.R0C0" : ["AD num3", "AS num3", "M num3"],
40+
"JCommTable4.R1C0" : ["AD den3", "AS den3", "M den3"],
41+
"JCommTable5.R0C0" : ["AD num4", "AS num4", "M num4"],
42+
"JCommTable5.R1C0" : ["AD den4", "AS den4", "M den4"],
43+
"JCommTable6.R0C0" : ["AD num5", "AS num5", "M num5"],
44+
"JCommTable6.R1C0" : ["AD den5", "AS den5", "M den5"],
45+
}
46+
},
47+
"problem_set": {
48+
"concatenate" : [
49+
{"glob" :
50+
{"key" : "question_file",
51+
"pattern": "tutors/FractionArithmetic/mass_production_brds/AD*",
52+
"obj_template" : {"kc_list" : ["AD check_convert","AD den3","AD num3","AD den4","AD num4","AD den5","AD num5","AD done"]}
53+
}
54+
},
55+
{"glob" :
56+
{"key" : "question_file",
57+
"pattern": "tutors/FractionArithmetic/mass_production_brds/AS*",
58+
"obj_template" : {"kc_list" : ["AS den5","AS num5","AS done",]}
59+
}
60+
},
61+
{"glob" :
62+
{"key" : "question_file",
63+
"pattern": "tutors/FractionArithmetic/mass_production_brds/M*",
64+
"obj_template" : {"kc_list" : ["M den5","M num5","M done"]}
65+
}
66+
}
67+
]
68+
}
69+
},
70+
}
71+
]
72+
}

0 commit comments

Comments
 (0)