-
Notifications
You must be signed in to change notification settings - Fork 0
/
arithmetic_ls_v3_with_wbkt.py
302 lines (258 loc) · 18.2 KB
/
arithmetic_ls_v3_with_wbkt.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
import ArithmeticExerciseGenerator
import DifficultyMachineV2
import ArithmeticResponseV3WBKTValidator
import WBKT as wbkt
import Timer
#### Sistema de aprendizaje por dominio mediante la práctica de ejercicios aritméticos
# Versión 3
# Además de considerar si la respuesta dada por el usuario es correcta o no, también se considera el
# tiempo de respuesta correcta del usuario frente a un ejercicio (a partir del momento en que se muestra el ejercicio)
# y un número de intentos permitidos por ejercicio en cada nivel de dificultad.
# Se considerarán dos tiempos de referencia de respuesta correcta para ejercicios en cada uno de los niveles de
# dificultad definidos:
# - Un tiempo máximo de respuesta hasta donde el cual la respuesta correcta es considerada superior (inclusivo).
# - Un tiempo mínimo de respuesta a partir del cual la respuesta correcta es considerada inferior (no inclusivo).
# response_times_per_difficulty_references = [2.1, 5.2, 7.25, 9.3, 13.0, 6.8, 22.0]
base_cr_times_per_difficulty = [2.1, 5.2, 7.25, 9.3, 13.0, 6.8, 22.0] # cr: correct response
# https://www.geeksforgeeks.org/python-map-function/
# Tiempo estimado inicialmente * 0.95
# min_response_times_per_difficulty_references = list(map(lambda x: x * 0.95, response_times_per_difficulty_references))
max_superior_cr_times_per_difficulty = list(map(lambda x: x * 0.95, base_cr_times_per_difficulty))
# Tiempo estimado inicialmente * 1.1
# max_response_times_per_difficulty_references = list(map(lambda x: x * 1.1, response_times_per_difficulty_references))
min_inferior_cr_times_per_difficulty = list(map(lambda x: x * 1.1, base_cr_times_per_difficulty))
# COMENTARIO NO VIGENTE:
# Se considerarán unos valores de referencia de números de intentos esperados para responder CORRECTAMENTE a
# ejercicios en cada uno de los niveles de dificultad definidos.
# Los valores de referencia de números de intentos realizados antes de respuesta correcta están constituidos por un número
# de intentos mínimo, y uno máximo (siendo este número máximo de intentos mayor al mínimo), y los números de intentos que
# estén dentro de este rango constituyen los números de intentos esperados, antes de obtener la siguiente respuesta
# correcta, para los ejercicios del nivel de dificultad al cual fueron asignados estos valores.
# exercise_attempts_per_difficulty_references = [1, 1, 1, 2, 2, 2, 2]
# min_exercise_attempts_per_difficulty_references = [1, 1, 1, 2, 2, 2, 2]
# max_exercise_attempts_per_difficulty_references = [1, 1, 1, 2, 2, 2, 2]
# COMENTARIO VIGENTE:
# Se considerará un número máximo de intentos permitidos por ejercicio en cada uno de los niveles de dificultad definidos
max_exercise_attempts_per_difficulty = [1, 1, 1, 2, 2, 2, 2]
# COMENTARIO NO VIGENTE:
# Cada nivel de dificultad tiene asociado unos valores de referencia de tiempos de respuesta (CORRECTA O INCORRECTA) y
# unos números de intentos antes de obtener la siguiente respuesta CORRECTA, para ejercicios de ese nivel de dificultad.
# COMENTARIO VIGENTE:
# En fin, cada nivel de dificultad tiene asociado unos valores de referencia de correspondientes a un tiempo máximo de
# respuesta correcta hasta donde esta seguirá considerando superior, un tiempo mínimo de respuesta a partir del cual
# la respuesta correcta se considerará inferior, y un número máximo de intentos permitidos por ejercicio.
# Se considerarán 7 niveles de dificultad en el sistema
difficulty_levels = []
for i in range(0, 7):
difficulty_levels.append(DifficultyMachineV2.DifficultyV3.Difficulty(i + 1,
max_exercise_attempts_per_difficulty[i],
max_superior_cr_times_per_difficulty[i],
min_inferior_cr_times_per_difficulty[i]))
# El estado de la máquina de dificultades es la que determina el nivel de dificultad del siguiente ejercicio mostrado
# al estudiante o usuario.
dm = DifficultyMachineV2.DifficultyMachine(difficulty_levels)
# Variable que representa si se va a mostrar el mismo ejercicio al estudiante o no.
same_exercise_flag = False # Si este valor es False, significa que se debe generar otro ejercicio en la siguiente iteración.
# Variable que cuenta los intentos realizados en un mismo ejercicio.
# Este valor, junto con el número máximo de intentos por ejercicios en la dificultad actual, se usará para decidir
# cuándo se mostrará otro ejercicio al estudiante del mismo nivel de dificultad.
# exercise_attempts = 0
same_exercise_attempts = 0
# Cronógrafo para calcular el tiempo de respuesta del usuario frente a un ejercicio.
# Este tiempo se calcula desde el momento en que el usuario ve el ejercicio, hasta que el usuario responda correctamente el
# ejercicio, o en su defecto, el ejercicio o la dificultad cambie.
# Este tiempo se usará para determinar si una respuesta correcta es inferior, superior o esperada, y dependiendo de esto,
# modificará parámetros del modelo WBKT asociado al nivel de dificultad del ejercicio al cual se dio respuesta, por medio
# de los pesos del modelo WBKT.
timer = Timer.Timer()
#### Implementación del WBKT
# En este caso, cada nivel de dificultad se considerá una habilidad según el modelo BKT.
# Es decir, cada nivel de dificultad tendrá su propio modelo WBKT.
# Los valores de los parámetros p(L0), p(T), p(G), p(S) del modelo BKT deberían seleccionarse siguiendo una
# heurística razonable, a menos que existan datos de la realidad que puedan usarse para construir el modelo.
# Los valores de los pesos w(L0), w(T), w(G) y w(S) se inicializarán en 1.0, lo que hace que, inicialmente, el modelo
# WBKT funcione exactamente como un modelo BKT, pero, a medida que se vaya modificando dinámicamente estos pesos en la
# ejecución del programa, las estimaciones debido a los pesos que modifican los valores de p(L0), p(T), p(G) y p(S).
wbkt_models = []
for i in range(1, 8):
# bkt_models.append(wbkt.WBKT(p_l0=0.02, p_t=0.05, p_g=0.3, p_s=0.01, w_l0=1.0, w_t=1.0, w_g=1.0, w_s=1.0))
# Modelo WBKT para ejercicios de sumas
wbkt_models.append(wbkt.WBKT(p_l0=0.01, p_t=0.01, p_g=0.2, p_s=0.1, w_l0=1.0, w_t=1.0, w_g=1.0, w_s=1.0))
# Datos adicionales a almacenar para tomar decisiones frente a cambios a realizar en los pesos del modelo WBKT de la
# dificultad actual, dependiendo de ciertas características adicionales de respuestas del usuario
# (e.g., tiempo de respuesta correcta)
# NOTA: Estos datos se deben almacenar por cada modelo WBKT o por cada dificultad, por separado.
current_consecutive_superior_time_response_count_per_dl = []
current_consecutive_inferior_time_response_count_per_dl = []
for dl in difficulty_levels:
current_consecutive_superior_time_response_count_per_dl.append(0)
current_consecutive_inferior_time_response_count_per_dl.append(0)
# VARIABLES NO VIGENTES:
# # Número de intentos correctos consecutivos y los tiempos de respuesta de estos intentos.
# consecutive_correct_attempts_per_difficulty_level = []
# # Número de intentos incorrectos consecutivos y los tiempos de respuesta de estos intentos.
# consecutive_incorrect_attempts_per_difficulty_level = []
# # Si el último intento fue correcto o no.
# is_last_attempt_correct_flag_per_difficulty_level = []
# for dl in difficulty_levels:
# consecutive_correct_attempts_per_difficulty_level.append([])
# consecutive_incorrect_attempts_per_difficulty_level.append([])
# is_last_attempt_correct_flag_per_difficulty_level.append(None)
# Variables que almacenan cuál es el siguiente ejercicio a mostrar al estudiante, y su número dentro de todos los que
# se han mostrado desde un principio.
exercise = None
exercise_number = 0
# VARIABLE NO VIGENTE:
# Variable de control que dice si el formato de respuesta del usuario fue válido o no
# is_valid_response_flag = True
while (not dm.is_on_end_state()):
# print(dm.get_current_state_tag())
if (dm.is_on_start_state()):
dm.increase_difficulty()
continue
# Si el ejercicio a mostrar al estudiante NO es el mismo
if same_exercise_flag == False:
exercise = ArithmeticExerciseGenerator.ArithmeticExerciseGenerator().generate_exercise(
dm.get_current_state())
exercise_number += 1
if exercise_number == 1:
print('---- Ejercicios Aritméticos ----')
# exercise_attempts = 0
same_exercise_attempts = 0
print('-- Ejercicio # ' + str(exercise_number) + ' --')
print('Escriba la respuesta para el siguiente ejercicio: ')
print(exercise.get_description())
# En este punto, se asume que el usuario puede comenzar a identificar el ejercicio
# Si el ejercicio a mostrar al estudiante NO es el mismo
# COMENTARIO NO VIGENTE: Ya no se calculará el tiempo de respuesta correcta, sino el tiempo de respuesta
# COMENTARIO NO VIGENTE: Ahora se calculará el tiempo de respuesta de un ejercicio
# CÓDIGO NO VIGENTE
# if (is_valid_response_flag):
# # Reinicie el contador de tiempo de respuesta únicamente si el formato de respuesta del usuario es válido
# # (o si es el primer ejercicio que se muestra)
# timer.start_new_timer()
# Reinicie el contador de respuesta correcta para un ejercicio solamente cuando el ejercicio cambie.
if same_exercise_flag == False:
timer.start_new_timer()
response = input('>> ')
# En este punto, ya se ha recibido una respuesta al ejercicio por parte del usuario
exercise_response_time_in_seconds = timer.get_elapsed_time_in_seconds()
try:
response = int(response)
# Desde aquí se sabe que ya se ha enviado una respuesta válida por parte del usuario
# (si no se produce una excepción de tipo ValueError); registre un intento más para el ejercicio.
same_exercise_attempts += 1
# is_valid_response_flag = True
# exercise_attempts += 1
# Obtenga el modelo WBKT correcto a considerar (dependiendo del nivel de dificultad del ejercicio)
exercise_dl_index = exercise.get_difficulty().get_difficulty_level() - 1
dl_wbkt_model = wbkt_models[exercise_dl_index]
# NOTAS DE FUNCIONAMIENTO DEL MODELO WBKT:
# En la implementación de WBKT, al igual que en un modelo BKT, se cambian los criterios de toma de decisiones frente
# a cuándo se cambia la dificultad de los ejercicios a los estudiantes con respecto a los criterios usados en los
# programas de ejercicios sin BKT (o WBKT).
# No obstante, en un modelo WBKT, se añade un paso previo a la actualización de la estimación del dominio de la
# habilidad: Antes de esta actualización, se modifican los pesos de algunos parámetros (e.g., p(T), p(G) o p(S))
# según la calidad del rendimiento observado del estudiante. En este caso, según la calidad de las respuestas
# correctas, dependiendo de si estas fueron alcanzadas en un tiempo esperado, o en uno mayor o menor al esperado.
# La toma de decisiones frente a cuándo se cambia un ejercicio de una misma dificultad sigue siendo independiente
# del modelo BKT o WBKT, y es más bien dependiente de unos números de intento máximo para ellos, elegido de forma
# heurística o arbitraria.
# Recuerde que los modelos BKT se actualizan cada vez que se recibe un resultado de un intento o interacción.
# En este caso, después de actualizar los pesos del modelo WBKT, se debe actualizar ese modelo de la siguiente forma,
# tal como sucede con los modelos BKT:
# 1) Actualice el valor a priori p(L,t) con base en la evidencia observada.
# - i.e., EN ESTE CASO, la respuesta del usuario ante un ejercicio, si este es correcto o no -,
# 2) Obtenga el valor a posteriori p(L,t+1) con base en la actualización del valor anterior p(L,t)
# Una vez se tenga el valor a posteriori p(L,t+1), y dependiendo de si la respuesta fue correcta o no,
# el número de intentos realizados en ese ejercicio - incluyendo esa respuesta, y el nivel de dificultad actual,
# se toma una o dos decisiones, según sea el caso:
# 1) Cuál es el nivel de dificultad que debería tener el próximo ejercicio.
# 2) Si el nivel de dificultad es el mismo, cuál es el ejercicio que debería mostrarse al estudiante:
# ¿el mismo, u otro diferente?
# CÓDIGO NO VIGENTE
# # IMPORTANTE: Estos cambios en p(G) son inferencias realizadas con algunos datos obtenidos,
# # pero no son garantías.
# # Note que el índice que representa el nivel de dificultad actual ya ha sido seleccionado anteriormente por medio
# # de la variable exercise_difficulty_level_index
# # Aumentos/disminuciones acumuladas en los pesos (Estos se podrán ir actualizando conforme se vayan detectando eventos)
# delta_w_g = 0.0
# delta_w_s = 0.0
# # No obstante, es necesario saber primero si la respuesta actual fue correcta o no, antes de detectar eventos
# # Revisión de eventos
# # I) Al obtener un intento correcto
# # 1) El número de intentos realizados hasta obtener el próximo intento correcto es MENOR que el número de intentos
# # esperados para ello.
# # 2) El número de intentos realizados hasta obtener el próximo intento correcto es MAYOR que el número de intentos
# # esperados para ello.
# attempts_until_next_correct_response = None
# is_last_attempt_correct = is_last_attempt_correct_flag_per_difficulty_level[exercise_difficulty_level_index]
# if (is_last_attempt_correct == None or is_last_attempt_correct == True):
# attempts_until_next_correct_response = 1
# elif (is_last_attempt_correct == False):
# attempts_until_next_correct_response = consecutive_incorrect_attempts_per_difficulty_level
# [exercise_difficulty_level_index] + 1
ccstrc = current_consecutive_superior_time_response_count_per_dl[exercise_dl_index]
ccitrc = current_consecutive_inferior_time_response_count_per_dl[exercise_dl_index]
# Con base en lo anterior, es el momento de tomar los datos relevantes relacionados con la respuesta del usuario,
# y realizar el procedimiento anterior, hasta tomar las dos decisiones descritas anteriormente.
response_validator = ArithmeticResponseV3WBKTValidator.ArithmeticResponseValidator(
exercise, response, exercise_response_time_in_seconds, same_exercise_attempts,
dl_wbkt_model, ccstrc, ccitrc)
response_validator_decision = response_validator.validate_response(dm)
# Actualice los valores de:
# current_consecutive_superior_time_response_count_per_dl
# current_consecutive_inferior_time_response_count_per_dl
# dependiendo de la calidad de la respuesta alcanzada, para el nivel de dificultad actual.
if (response_validator_decision.get_correctness() == True):
print('Correcto')
if(response_validator_decision.get_correct_response_time() == response_validator_decision.
SUPERIOR_CORRECT_RESPONSE_TIME):
print('Tiempo de respuesta correcta: Superior al esperado (<=',
str(max_superior_cr_times_per_difficulty[exercise_dl_index]) + ')')
current_consecutive_superior_time_response_count_per_dl[exercise_dl_index] += 1
current_consecutive_inferior_time_response_count_per_dl[exercise_dl_index] = 0
elif(response_validator_decision.get_correct_response_time() == response_validator_decision.
INFERIOR_CORRECT_RESPONSE_TIME):
print('Tiempo de respuesta correcta: Inferior al esperado (>',
str(min_inferior_cr_times_per_difficulty[exercise_dl_index]) + ')')
current_consecutive_inferior_time_response_count_per_dl[exercise_dl_index] += 1
current_consecutive_superior_time_response_count_per_dl[exercise_dl_index] = 0
else:
print('Tiempo de respuesta correcta: Esperado (> ',
max_superior_cr_times_per_difficulty[exercise_dl_index], 'y <= ',
str(min_inferior_cr_times_per_difficulty[exercise_dl_index]) + ')')
current_consecutive_superior_time_response_count_per_dl[exercise_dl_index] = 0
current_consecutive_inferior_time_response_count_per_dl[exercise_dl_index] = 0
print('El tiempo de respuesta correcta del ejercicio fue de:', exercise_response_time_in_seconds, "segundos.")
print('El número total de intentos realizados en este ejercicio fue de:', same_exercise_attempts, "intento(s).")
else:
print('Incorrecto')
print('El número total de intentos realizados en este ejercicio es de:',
same_exercise_attempts, "intento(s).")
dm.set_current_state(response_validator_decision.get_next_difficulty())
same_exercise_flag = response_validator_decision.get_same_exercise()
# TESTING 1: Imprima los valores de los contadores de intentos consecutivos
print('-----------------')
print('TESTING 1:')
print('Número de respuestas correctas consecutivas de tiempo superior al esperado :'
, str(current_consecutive_superior_time_response_count_per_dl[exercise_dl_index]))
print('Número de respuestas correctas consecutivas de tiempo inferior al esperado :'
, str(current_consecutive_inferior_time_response_count_per_dl[exercise_dl_index]))
# TESTING 2: Imprima los valores del modelo WBKT:
print('-----------------')
print('TESTING 2:')
print('Estado actual del modelo WBKT de la dificultad ' + str(exercise_dl_index + 1) + ':')
print('t = ', dl_wbkt_model.get_t())
print('p(Lt) = ', dl_wbkt_model.get_p_lt(), ' | p(T) =', dl_wbkt_model.get_p_t(),
' | p(G) =', dl_wbkt_model.get_p_g(), ' | p(S) =', dl_wbkt_model.get_p_s())
# print('w(L0) =', dl_wbkt_model.get_w_l0())
# print('w(T) =', dl_wbkt_model.get_w_t())
print('w(G) =', dl_wbkt_model.get_w_g())
# print('w(S) =', dl_wbkt_model.get_w_s())
print()
except ValueError:
print('El formato de la respuesta no es el adecuado. Por favor ingrese un número para responder el ejercicio.')
print()
same_exercise_flag = True
# is_valid_response_flag = False