Recommender system specification

Jump to: navigation, search

This is a proposal for a recommender system in Moodle. A recommender system "seeks to predict the "rating" or "preference" a user would give to an item.", in other words, it tries to identify items that would be interesting for a user.

This code snippet below is an example using the proposed public API. This generates two recommended activities of type page for the user with id 111 in the course with id 222 based on the values in an hypothetical user_activity_rates table. The recommender system should only consider users whose city is Barcelona.

// The 'contexts' filter is used to restrict the recommender system to a specific set of contexts. It can be used to restrict a recommender
// system to the activities of a single course or to restrict a recommender system to the entries of a single glossary activity.
// The filters in 'dimensions' are applied to each of the dimensions used by the recommender system.
$coursecontext = \context_course::instance(222);
$filters = [
    'contexts' => [$coursecontext->id],
    'dimensions' => [
        'user' => ['city' => 'Barcelona'],
        'activity' => ['modulename' => 'page']
$dataset = new \core_course\analytics\recommender\dataset\activities($filters);
$recommender = \core_analytics\recommender($dataset);
$recommendations = $recommender->recommend(111, 2);

This is an overview of the classes involved.

The training data for a recommender system is usually a grid of values in a two-dimensional matrix, where one of the axis usually represent the user. The classes extending the base recommender_dataset class are responsible of instantiating their dimensions and to fill the two-dimensional matrix.

We could replace x and y for items and users if we can not find use cases that do not directly involve users.

Example of a recommender_dataset class.

namespace \core_course\analytics\recommender\dataset;
class activities implements \core_analytics\recommender_dataset {
    public function __construct(array $filters) {
        $this->filters = $filters;
        $this->x = new \core_course\analytics\recommender\dimension\activity();
        $this->y = new \core_user\analytics\recommender\dimension\user();
    public function get_training_data() {
        $courseids = $this->get_course_ids_from_context_filter();
        $activityrates = $DB->get_records("SELECT * FROM {user_activity_rates} where courseid IN $courseids");
        $xitems = $this->x->get_items($this->filters);
        $yitems = $this->y->get_items($this->filters);
        // Iterate through both $xitems and $yitems filling $trainingdata two-dimensional array with $activityrates values.
        return $trainingdata;

Classes like user' or activity that extend the base class recommender_dimension represent each of dimensions in the two-dimensional matrix. They basically return the list of records used by the implementation of the recommender_dataset class. They are separated from the recommender_dataset class for re-usability in different recommender systems.

We can remove this recommender_dataset - recommender_dimension separation if we don't find enough use cases that justify the separation.

These implementations serve as example of recommender_dimension classes.

namespace \core_course\analytics\recommender;
class activity extends \core_analytics\recommender_dimension {
    private $acceptedfilters = ['modulename', 'coursecategory'];
    public function get_items(array $filters) {
        // The context filtering would not make sense applied to context module if what we want is a list of activities.
        return $DB->get_recordset_sql("SELECT cm.*, c.* FROM {course_modules} cm
                                         JOIN {course} c on cm.course =
                                         JOIN {context} ctx ON ctx.contextlevel = CONTEXT_COURSE AND ctx.instanceid =
                                        WHERE IN $contexts AND modulename = $filters['modulename']");
namespace \core_user\analytics\recommender;
class user extends \core_analytics\recommender_dimension {
    public function get_items(array $filters) {
       // $contexts is ignored as users depend on the system context.
        return $DB->get_recordset_sql("SELECT * FROM {user}");

This is the recommender class skeleton.

namespace \core_analytics;
class recommender {
    public function __construct(\core_analytics\recommender_dataset $dataset) {
        $this->dataset = $dataset;
    public function recommend($yid, $nrecommendations = 1) {
        $trainingdata = $dataset->get_training_data();
        // Collaborative filtering or any other alternative. This is just an example.
        $model = $this->get_embeddings($trainingdata);
        $y = $trainingdata[$yid];
        return $model->recommend($y, $nrecommendations);