Beispiel #1
0
    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,
        }
Beispiel #2
0
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)
Beispiel #3
0
 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)
Beispiel #4
0
    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)
Beispiel #5
0
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: