Merge branch 'MDL-59661_master' of git://github.com/dmonllao/moodle

This commit is contained in:
Eloy Lafuente (stronk7)
2017-08-22 01:41:29 +02:00
8 changed files with 121 additions and 36 deletions
@@ -139,6 +139,7 @@ class models_list implements \renderable, \templatable {
$actionsmenu->add($icon);
}
// Get predictions.
if ($modeldata->enabled && !empty($modeldata->timesplitting)) {
$url = new \moodle_url('model.php', array('action' => 'getpredictions', 'id' => $model->get_id()));
$icon = new \action_menu_link_secondary($url, new \pix_icon('i/notifications',
@@ -154,6 +155,14 @@ class models_list implements \renderable, \templatable {
$actionsmenu->add($icon);
}
// Export training data.
if (!$model->is_static() && $model->is_trained()) {
$url = new \moodle_url('model.php', array('action' => 'export', 'id' => $model->get_id()));
$icon = new \action_menu_link_secondary($url, new \pix_icon('i/export',
get_string('exporttrainingdata', 'tool_analytics')), get_string('export', 'tool_analytics'));
$actionsmenu->add($icon);
}
$modeldata->actions = $actionsmenu->export_for_template($output);
$data->models[] = $modeldata;
@@ -36,13 +36,16 @@ $string['enabled'] = 'Enabled';
$string['errorcantenablenotimesplitting'] = 'You need to select a time splitting method before enabling the model';
$string['errornoenabledandtrainedmodels'] = 'There are not enabled and trained models to predict';
$string['errornoenabledmodels'] = 'There are not enabled models to train';
$string['errornoexport'] = 'Only trained models can be exported';
$string['errornostaticedit'] = 'Models based on assumptions can not be edited';
$string['errornostaticevaluated'] = 'Models based on assumptions can not be evaluated, they are always 100% correct according to how they were defined';
$string['errornostaticlog'] = 'Models based on assumptions can not be evaluated, there is no preformance log';
$string['errortrainingdataexport'] = 'The model training data could not be exported';
$string['evaluate'] = 'Evaluate';
$string['evaluatemodel'] = 'Evaluate model';
$string['evaluationinbatches'] = 'The site contents are calculated and stored in batches, during evaluation you can stop the process at any moment, the next time you run it it will continue from the point you stopped it.';
$string['trainandpredictmodel'] = 'Training model and calculating predictions';
$string['export'] = 'Export';
$string['exporttrainingdata'] = 'Export training data';
$string['getpredictionsresultscli'] = 'Results using {$a->name} (id: {$a->id}) course duration splitting';
$string['getpredictionsresults'] = 'Results using {$a->name} course duration splitting';
$string['extrainfo'] = 'Info';
@@ -68,6 +71,7 @@ $string['predictionprocessfinished'] = 'Prediction process finished';
$string['samestartdate'] = 'Current start date is good';
$string['sameenddate'] = 'Current end date is good';
$string['target'] = 'Target';
$string['trainandpredictmodel'] = 'Training model and calculating predictions';
$string['trainingprocessfinished'] = 'Training process finished';
$string['trainingresults'] = 'Training results';
$string['trainmodels'] = 'Train models';
+19
View File
@@ -57,6 +57,9 @@ switch ($action) {
case 'disable':
$title = get_string('disable');
break;
case 'export':
$title = get_string('export', 'tool_analytics');
break;
default:
throw new moodle_exception('errorunknownaction', 'analytics');
@@ -162,6 +165,22 @@ switch ($action) {
$modellogstable = new \tool_analytics\output\model_logs('model-' . $model->get_id(), $model);
echo $renderer->render_table($modellogstable);
break;
case 'export':
if ($model->is_static() || !$model->is_trained()) {
throw new moodle_exception('errornoexport', 'tool_analytics');
}
$file = $model->get_training_data();
if (!$file) {
redirect(new \moodle_url('/admin/tool/analytics/index.php'), get_string('errortrainingdataexport', 'tool_analytics'),
null, \core\output\notification::NOTIFY_ERROR);
}
$filename = 'training-data.' . $model->get_id() . '.' . time() . '.csv';
send_file($file, $filename, null, 0, false, true);
break;
}
echo $OUTPUT->footer();
+57 -32
View File
@@ -45,6 +45,11 @@ class dataset_manager {
*/
const UNLABELLED_FILEAREA = 'unlabelled';
/**
* File area for exported datasets.
*/
const EXPORT_FILEAREA = 'export';
/**
* Evaluation file file name.
*/
@@ -77,28 +82,35 @@ class dataset_manager {
protected $evaluation;
/**
* Labelled (true) or unlabelled data (false).
* The dataset filearea. Must be one of the self::*_FILEAREA options.
*
* @var bool
* @var string
*/
protected $includetarget;
protected $filearea;
/**
* Constructor method.
*
* @throws \coding_exception
* @param int $modelid
* @param int $analysableid
* @param string $timesplittingid
* @param string $filearea
* @param bool $evaluation
* @param bool $includetarget
* @return void
*/
public function __construct($modelid, $analysableid, $timesplittingid, $evaluation = false, $includetarget = false) {
public function __construct($modelid, $analysableid, $timesplittingid, $filearea, $evaluation = false) {
if ($filearea !== self::EXPORT_FILEAREA && $filearea !== self::LABELLED_FILEAREA &&
$filearea !== self::UNLABELLED_FILEAREA) {
throw new \coding_exception('Invalid provided filearea');
}
$this->modelid = $modelid;
$this->analysableid = $analysableid;
$this->timesplittingid = $timesplittingid;
$this->evaluation = $evaluation;
$this->includetarget = $includetarget;
$this->filearea = $filearea;
}
/**
@@ -108,8 +120,7 @@ class dataset_manager {
*/
public function init_process() {
$lockkey = 'modelid:' . $this->modelid . '-analysableid:' . $this->analysableid .
'-timesplitting:' . self::clean_time_splitting_id($this->timesplittingid) .
'-includetarget:' . (int)$this->includetarget;
'-timesplitting:' . self::clean_time_splitting_id($this->timesplittingid);
// Large timeout as processes may be quite long.
$lockfactory = \core\lock\lock_config::get_lock_factory('core_analytics');
@@ -132,9 +143,10 @@ class dataset_manager {
// Delete previous file if it exists.
$fs = get_file_storage();
$filerecord = [
'component' => 'analytics',
'filearea' => self::get_filearea($this->includetarget),
'filearea' => $this->filearea,
'itemid' => $this->modelid,
'contextid' => \context_system::instance()->id,
'filepath' => '/analysable/' . $this->analysableid . '/' . self::clean_time_splitting_id($this->timesplittingid) . '/',
@@ -217,7 +229,7 @@ class dataset_manager {
$fs = get_file_storage();
// Always evaluation.csv and labelled as it is an evaluation file.
$filearea = self::get_filearea(true);
$filearea = self::LABELLED_FILEAREA;
$filename = self::get_filename(true);
$filepath = '/analysable/' . $analysableid . '/' . self::clean_time_splitting_id($timesplittingid) . '/';
return $fs->get_file(\context_system::instance()->id, 'analytics', $filearea, $modelid, $filepath, $filename);
@@ -231,11 +243,11 @@ class dataset_manager {
* @param array $files
* @param int $modelid
* @param string $timesplittingid
* @param string $filearea
* @param bool $evaluation
* @param bool $includetarget
* @return \stored_file
*/
public static function merge_datasets(array $files, $modelid, $timesplittingid, $evaluation, $includetarget) {
public static function merge_datasets(array $files, $modelid, $timesplittingid, $filearea, $evaluation = false) {
$tmpfilepath = make_request_directory() . DIRECTORY_SEPARATOR . 'tmpfile.csv';
@@ -297,7 +309,7 @@ class dataset_manager {
$filerecord = [
'component' => 'analytics',
'filearea' => self::get_filearea($includetarget),
'filearea' => $filearea,
'itemid' => $modelid,
'contextid' => \context_system::instance()->id,
'filepath' => '/timesplitting/' . self::clean_time_splitting_id($timesplittingid) . '/',
@@ -309,6 +321,37 @@ class dataset_manager {
return $fs->create_file_from_pathname($filerecord, $tmpfilepath);
}
/**
* Exports the model training data.
*
* @param int $modelid
* @param string $timesplittingid
* @return \stored_file|false
*/
public static function export_training_data($modelid, $timesplittingid) {
$fs = get_file_storage();
$contextid = \context_system::instance()->id;
$filepath = '/timesplitting/' . self::clean_time_splitting_id($timesplittingid) . '/';
$files = $fs->get_directory_files($contextid, 'analytics', self::LABELLED_FILEAREA, $modelid,
$filepath, true, false);
// Discard evaluation files.
foreach ($files as $key => $file) {
if ($file->get_filename() === self::EVALUATION_FILENAME) {
unset($files[$key]);
}
}
if (empty($files)) {
return false;
}
return self::merge_datasets($files, $modelid, $timesplittingid, self::EXPORT_FILEAREA);
}
/**
* Returns the dataset file data structured by sampleids using the indicators and target column names.
*
@@ -387,27 +430,9 @@ class dataset_manager {
$filename = self::EVALUATION_FILENAME;
} else {
// Incremental time, the lock will make sure we don't have concurrency problems.
$filename = microtime(false) . '.csv';
$filename = microtime(true) . '.csv';
}
return $filename;
}
/**
* Returns the file area to be used.
*
* @param bool $includetarget
* @return string
*/
protected static function get_filearea($includetarget) {
if ($includetarget === true) {
$filearea = self::LABELLED_FILEAREA;
} else {
$filearea = self::UNLABELLED_FILEAREA;
}
return $filearea;
}
}
+6 -1
View File
@@ -417,8 +417,13 @@ abstract class base {
}
}
if (!empty($target)) {
$filearea = \core_analytics\dataset_manager::LABELLED_FILEAREA;
} else {
$filearea = \core_analytics\dataset_manager::UNLABELLED_FILEAREA;
}
$dataset = new \core_analytics\dataset_manager($this->modelid, $analysable->get_id(), $timesplitting->get_id(),
$this->options['evaluation'], !empty($target));
$filearea, $this->options['evaluation']);
// Flag the model + analysable + timesplitting as being analysed (prevent concurrent executions).
if (!$dataset->init_process()) {
@@ -111,8 +111,13 @@ abstract class by_course extends base {
}
// Merge all course files into one.
if ($includetarget) {
$filearea = \core_analytics\dataset_manager::LABELLED_FILEAREA;
} else {
$filearea = \core_analytics\dataset_manager::UNLABELLED_FILEAREA;
}
$timesplittingfiles[$timesplittingid] = \core_analytics\dataset_manager::merge_datasets($files,
$this->modelid, $timesplittingid, $this->options['evaluation'], $includetarget);
$this->modelid, $timesplittingid, $filearea, $this->options['evaluation']);
}
return $timesplittingfiles;
@@ -57,8 +57,13 @@ abstract class sitewide extends base {
}
// We use merge but it is just a copy.
if ($includetarget) {
$filearea = \core_analytics\dataset_manager::LABELLED_FILEAREA;
} else {
$filearea = \core_analytics\dataset_manager::UNLABELLED_FILEAREA;
}
$files[$timesplittingid] = \core_analytics\dataset_manager::merge_datasets(array($file), $this->modelid,
$timesplittingid, $this->options['evaluation'], $includetarget);
$timesplittingid, $filearea, $this->options['evaluation']);
}
return $files;
+13
View File
@@ -1233,6 +1233,19 @@ class model {
$limitfrom, $limitnum);
}
/**
* Merges all training data files into one and returns it.
*
* @return \stored_file|false
*/
public function get_training_data() {
\core_analytics\manager::check_can_manage_models();
$timesplittingid = $this->get_time_splitting()->get_id();
return \core_analytics\dataset_manager::export_training_data($this->get_id(), $timesplittingid);
}
/**
* Flag the provided file as used for training or prediction.
*