def get_recommend_params(self, learner, valid_activities, valid_kcs): """ Retrieve features/params needed for doing recommendation Calls data/param retrieval functions that may be implementation(prod vs. prototype)-specific Does shared calculations before passing derived features to subscore calculators TODO: consider QuerySet.select_related() for optimization https://docs.djangoproject.com/en/2.0/ref/models/querysets/#select-related :param learner: Learner model instance :param valid_activities: Queryset of Activity objects :param valid_kcs: Queryset of KnowledgeComponent objects :return: dictionary with following keys: guess: QxK np.array, guess parameter values for activities slip: QxK np.array, slip parameter values for activities difficulty: 1xQ np.array, difficulty values for activities prereqs: KxK np.array, prerequisite matrix r_star: float, Threshold for forgiving lower odds of mastering pre-requisite LOs. L_star: float, Threshold logarithmic odds. If mastery logarithmic odds are >= than L_star, the LO is considered mastered W_p: (float), weight on substrategy P W_r: (float), weight on substrategy R W_d: (float), weight on substrategy D W_c: (float), weight on substrategy C last_attempted_guess: 1xK vector of guess parameters for activity last_attempted_slip: 1xK vector of slip parameters for activity learner_mastery_odds: 1xK vector of learner mastery odds values """ # retrieve or calculate features guess = self.get_guess(valid_activities, valid_kcs) slip = self.get_slip(valid_activities, valid_kcs) relevance = calculate_relevance(guess, slip) last_attempted_relevance = self.get_last_attempted_relevance( learner, valid_kcs) learner_mastery_odds = odds( self.get_learner_mastery(learner, valid_kcs)) # fill missing values with 0.0 fillna(relevance) fillna(learner_mastery_odds) # last_attempted_relevance may be None if no prior score history if last_attempted_relevance is not None: fillna(last_attempted_relevance) # construct param dict return { 'relevance': relevance, 'difficulty': self.get_difficulty(valid_activities), 'prereqs': self.get_prereqs(valid_kcs), 'last_attempted_relevance': last_attempted_relevance, 'learner_mastery_odds': learner_mastery_odds, 'r_star': self.engine_settings.r_star, 'L_star': self.engine_settings.L_star, 'W_p': self.engine_settings.W_p, 'W_r': self.engine_settings.W_r, 'W_d': self.engine_settings.W_d, 'W_c': self.engine_settings.W_c, }
def get_engine(engine_settings=None): """ Get relevant engine for learner based on their experimental group :param engine_settings: EngineSettings model instance """ if engine_settings is None: engine_settings = EngineSettings( L_star=np.log(odds(0.9)), # TODO initialize from constant r_star=0.0, W_r=2.0, # demand W_c=1.0, # continuity W_p=2.0, # readiness W_d=0.5, # difficulty ) return AdaptiveEngine(engine_settings)
def get_learner_mastery(learner, knowledge_components=None): """ Constructs a 1 x (# LOs) vector of mastery odds values for learner Optionally, subset and order can be defined using knowledge_components argument If mastery value for a KC does not exist, populates the corresponding array element with the prior mastery value of the KC output vector represents mastery values of KCs in knowledge_components arg; defines the vector "axis" :param learner: Learner model instance :param knowledge_components: KnowledgeComponent model instance or queryset :return: 1 x (# LOs) np.array vector of mastery odds values """ matrix = Matrix(Mastery)[learner, knowledge_components] # fill unpopulated values with appropriate kc prior values, from mastery_prior field on KC object matrix_values = fill_nan_from_index_field(matrix, 'mastery_prior') # convert to odds return odds(matrix_values)
def update_from_score(self, learner, activity, score): """ Action to take when new score information is received Doesn't add anything new to base class method, except new Also expects different input types for some args; see docstring TODO verify correctness of implementation Assumes creation of score object in database is done outside this method (i.e. handled in api view) :param learner: Learner object instance :param activity: Activity object instance :param score: float :return: """ # subset to relevant knowledge components from activity knowledge_components = activity.knowledge_components.all().order_by( 'pk') # ensure that there are knowledge components for the activity, otherwise mastery update is not relevant if not knowledge_components.exists(): log.debug( "Skipping engine update from score; no tagged knowledge components found for activity." ) return # current mastery odds for learner mastery_odds = odds( self.get_learner_mastery(learner, knowledge_components)) # convert activity into queryset with single element activity_qset = Activity.objects.filter(pk=activity.pk) # flatten from ndarray to vector before passing to calculation methods guess = self.get_guess(activity_qset, knowledge_components).flatten() slip = self.get_slip(activity_qset, knowledge_components).flatten() transit = self.get_transit(activity_qset, knowledge_components).flatten() new_mastery_odds = calculate_mastery_update(mastery_odds, score, guess, slip, transit, EPSILON) # save new mastery values in mastery data store self.update_learner_mastery(learner, new_mastery_odds, knowledge_components)
import logging import random from django.db.models import Model import numpy as np from alosi.engine import BaseAlosiAdaptiveEngine, recommendation_score, odds, EPSILON, calculate_mastery_update from .data_structures import Matrix, Vector, pk_index_map, convert_pk_to_index from .models import * log = logging.getLogger(__name__) GUESS_DEFAULT = odds(0.1) SLIP_DEFAULT = odds(0.15) TRANSIT_DEFAULT = odds(0.1) def inverse_odds(x, epsilon=EPSILON): """ Calculate probability from odds, and regularize returned probability to range [epsilon, 1-epsilon] :param x: odds value :return: probability value """ p = x / (1 + x) p = np.minimum(np.maximum(p, epsilon), 1 - epsilon) return p def get_tagging_matrix(activities=None, knowledge_components=None): """ Create Q x K matrix, where element is 1 if item q is tagged with KC k, else 0 TODO could be revised - any way to utilize matrix/vector data structures? :param activities: