mirror of
https://github.com/moodle/moodle.git
synced 2025-04-14 04:52:36 +02:00
MDL-60944 analytics: Include trained ML models
This commit is contained in:
parent
e4453adc55
commit
c70a7194f4
@ -45,7 +45,7 @@ class import_model extends \moodleform {
|
||||
|
||||
$mform->addElement('header', 'settingsheader', get_string('importmodel', 'tool_analytics'));
|
||||
|
||||
$mform->addElement('filepicker', 'modelfile', get_string('file'), null, ['accepted_types' => '.json']);
|
||||
$mform->addElement('filepicker', 'modelfile', get_string('file'), null, ['accepted_types' => '.zip']);
|
||||
$mform->addRule('modelfile', null, 'required');
|
||||
|
||||
$mform->addElement('advcheckbox', 'ignoreversionmismatches', get_string('ignoreversionmismatches', 'tool_analytics'),
|
||||
|
@ -64,7 +64,7 @@ class helper {
|
||||
*
|
||||
* @param string $title
|
||||
* @param \moodle_url $url
|
||||
* @param \context|false $context Defaults to context_system
|
||||
* @param \context|null $context Defaults to context_system
|
||||
* @return null
|
||||
*/
|
||||
public static function set_navbar(string $title, \moodle_url $url, ?\context $context = null) {
|
||||
|
@ -54,7 +54,10 @@ if ($mform->is_cancelled()) {
|
||||
|
||||
// Converting option names to class names.
|
||||
$targetclass = \tool_analytics\output\helper::option_to_class($data->target);
|
||||
$target = \core_analytics\manager::get_target($targetclass);
|
||||
if (empty($targets[$targetclass])) {
|
||||
throw new \moodle_exception('errorinvalidtarget', 'analytics', '', $targetclass);
|
||||
}
|
||||
$target = $targets[$targetclass];
|
||||
|
||||
$indicators = array();
|
||||
foreach ($data->indicators as $indicator) {
|
||||
@ -88,4 +91,4 @@ if ($mform->is_cancelled()) {
|
||||
|
||||
echo $OUTPUT->header();
|
||||
$mform->display();
|
||||
echo $OUTPUT->footer();
|
||||
echo $OUTPUT->footer();
|
||||
|
@ -40,19 +40,15 @@ if ($form->is_cancelled()) {
|
||||
|
||||
$modelconfig = new \core_analytics\model_config();
|
||||
|
||||
$json = $form->get_file_content('modelfile');
|
||||
$zipfilepath = $form->save_temp_file('modelfile');
|
||||
|
||||
if ($error = $modelconfig->check_json_data($json)) {
|
||||
// The provided file is not ok.
|
||||
redirect($url, $error, 0, \core\output\notification::NOTIFY_ERROR);
|
||||
}
|
||||
list ($modeldata, $unused) = $modelconfig->extract_import_contents($zipfilepath);
|
||||
|
||||
$modeldata = json_decode($json);
|
||||
if ($error = $modelconfig->check_dependencies($modeldata, $data->ignoreversionmismatches)) {
|
||||
// The file is not available until the form is validated so we need an alternative method to show errors.
|
||||
redirect($url, $error, 0, \core\output\notification::NOTIFY_ERROR);
|
||||
}
|
||||
$model = \core_analytics\model::create_from_import($modeldata, true);
|
||||
\core_analytics\model::import_model($zipfilepath);
|
||||
|
||||
redirect($returnurl, get_string('importedsuccessfully', 'tool_analytics'), 0,
|
||||
\core\output\notification::NOTIFY_SUCCESS);
|
||||
|
@ -37,21 +37,15 @@ $string['clievaluationandpredictions'] = 'A scheduled task iterates through enab
|
||||
$string['clievaluationandpredictionsnoadmin'] = 'A scheduled task iterates through enabled models and gets predictions. Models evaluation via the web interface is disabled. It may be enabled by a site administrator.';
|
||||
$string['createmodel'] = 'Create model';
|
||||
$string['delete'] = 'Delete';
|
||||
$string['deletemodelconfirmation'] = 'Are you sure you want to delete "{$a}"?';
|
||||
$string['deletemodelconfirmation'] = 'Are you sure you want to delete "{$a}"? These changes can not be reverted.';
|
||||
$string['disabled'] = 'Disabled';
|
||||
$string['editmodel'] = 'Edit "{$a}" model';
|
||||
$string['edittrainedwarning'] = 'This model has already been trained. Note that changing its indicators or its time-splitting method will delete its previous predictions and start generating new predictions.';
|
||||
$string['enabled'] = 'Enabled';
|
||||
$string['errorcantenablenotimesplitting'] = 'You need to select a time-splitting method before enabling the model';
|
||||
$string['errorimport'] = 'Error importing the provided json file.';
|
||||
$string['errorimportmissingcomponents'] = 'The provided model requires the following plugins to be installed: {$a}. Note that the versions do not necessarily need to match with the versions installed in your system. To install the same or a newer version of the plugin should be enough in most cases.';
|
||||
$string['errorimportversionmismatches'] = 'The version of the following components differ from the version installed in this site: {$a}. You can use "Ignore version mismatches" option to ignore these differences.';
|
||||
$string['errorimportmissingclasses'] = 'The following analytics components are not available in this site: {$a->missingclasses}. ';
|
||||
$string['errornoenabledandtrainedmodels'] = 'There are no enabled and trained models to predict.';
|
||||
$string['errornoenabledmodels'] = 'There are no enabled models to train.';
|
||||
$string['errornoexport'] = 'Only trained models can be exported';
|
||||
$string['errornoexportconfig'] = 'There was a problem exporting the model configuration.';
|
||||
$string['errornoexportconfigrequirements'] = 'Only non static models with timeplitting methods can be exported.';
|
||||
$string['errornostaticedit'] = 'Models based on assumptions cannot be edited.';
|
||||
$string['errornostaticevaluated'] = 'Models based on assumptions cannot be evaluated. They are always 100% correct according to how they were defined.';
|
||||
$string['errornostaticlog'] = 'Models based on assumptions cannot be evaluated because there is no performance log.';
|
||||
|
@ -229,10 +229,9 @@ switch ($action) {
|
||||
break;
|
||||
|
||||
case 'exportmodel':
|
||||
$downloadfilename = 'model-config.' . $model->get_id() . '.' . microtime() . '.json';
|
||||
$filepath = $model->export_config($downloadfilename);
|
||||
@header("Content-type: text/json; charset=UTF-8");
|
||||
send_temp_file($filepath, $downloadfilename);
|
||||
$zipfilename = 'model-' . $model->get_unique_id() . '-' . microtime(false) . '.zip';
|
||||
$zipfilepath = $model->export_model($zipfilename);
|
||||
send_temp_file($zipfilepath, $zipfilename);
|
||||
break;
|
||||
|
||||
case 'clear':
|
||||
|
@ -338,12 +338,12 @@ class model {
|
||||
*
|
||||
* @param \core_analytics\local\target\base $target
|
||||
* @param \core_analytics\local\indicator\base[] $indicators
|
||||
* @param string $timesplittingid The time splitting method id (its fully qualified class name)
|
||||
* @param string $processor The machine learning backend this model will use.
|
||||
* @param string|false $timesplittingid The time splitting method id (its fully qualified class name)
|
||||
* @param string|null $processor The machine learning backend this model will use.
|
||||
* @return \core_analytics\model
|
||||
*/
|
||||
public static function create(\core_analytics\local\target\base $target, array $indicators,
|
||||
$timesplittingid = false, $processor = false) {
|
||||
$timesplittingid = false, $processor = null) {
|
||||
global $USER, $DB;
|
||||
|
||||
\core_analytics\manager::check_can_manage_models();
|
||||
@ -386,59 +386,6 @@ class model {
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new model from import configuration.
|
||||
*
|
||||
* It is recommended to call \core_analytics\model_config::check_dependencies first so the error message can be retrieved.
|
||||
*
|
||||
* @param \stdClass $modeldata Model data.
|
||||
* @param bool $skipcheckdependencies Useful if you already checked the dependencies.
|
||||
* @return \core_analytics\model|false False if the provided model data contain errors.
|
||||
*/
|
||||
public static function create_from_import(\stdClass $modeldata, ?bool $skipcheckdependencies = false) : ?\core_analytics\model {
|
||||
|
||||
\core_analytics\manager::check_can_manage_models();
|
||||
|
||||
if (!$skipcheckdependencies) {
|
||||
$modelconfig = new model_config();
|
||||
if ($error = $modelconfig->check_dependencies($modeldata, false)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// At this stage we should be 100% sure that the model data is safe and can be imported.
|
||||
// If the caller explicitly set $skipcheckdependencies to false and there is a problem
|
||||
// in this process we trigger a coding exception.
|
||||
if (!$target = \core_analytics\manager::get_target($modeldata->target)) {
|
||||
throw new \coding_exception('The provided target is not available. Ensure that model_config::check_dependencies
|
||||
is called before importing the model.');
|
||||
}
|
||||
if (!$timesplitting = \core_analytics\manager::get_time_splitting($modeldata->timesplitting)) {
|
||||
throw new \coding_exception('The provided time splitting method is not available. Ensure that
|
||||
model_config::check_dependencies is called before importing the model.');
|
||||
}
|
||||
|
||||
// Indicators.
|
||||
$indicators = [];
|
||||
foreach ($modeldata->indicators as $indicator) {
|
||||
if (!$indicator = \core_analytics\manager::get_indicator($indicator)) {
|
||||
throw new \coding_exception('The provided indicator is not available. Ensure that
|
||||
model_config::check_dependencies is called before importing the model.');
|
||||
}
|
||||
$indicators[] = $indicator;
|
||||
}
|
||||
|
||||
if (!empty($modeldata->processor)) {
|
||||
if (!$processor = \core_analytics\manager::get_predictions_processor($modeldata->processor, false)) {
|
||||
throw new \coding_exception('The provided machine learning backend is not available. Ensure that
|
||||
model_config::check_dependencies is called before importing the model.');
|
||||
}
|
||||
} else {
|
||||
$modeldata->processor = false;
|
||||
}
|
||||
return self::create($target, $indicators, $modeldata->timesplitting, $modeldata->processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this model exist?
|
||||
*
|
||||
@ -1363,7 +1310,7 @@ class model {
|
||||
* @param bool $onlymodelid Preference over $subdirs
|
||||
* @return string
|
||||
*/
|
||||
protected function get_output_dir($subdirs = array(), $onlymodelid = false) {
|
||||
public function get_output_dir($subdirs = array(), $onlymodelid = false) {
|
||||
global $CFG;
|
||||
|
||||
$subdirstr = '';
|
||||
@ -1412,7 +1359,7 @@ class model {
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the model data.
|
||||
* Exports the model data for displaying it in a template.
|
||||
*
|
||||
* @return \stdClass
|
||||
*/
|
||||
@ -1435,19 +1382,34 @@ class model {
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the model data as a JSON file.
|
||||
* Exports the model data to a zip file.
|
||||
*
|
||||
* @param string $downloadfilename Download file name.
|
||||
* @return string The filepath
|
||||
* @param string $zipfilename
|
||||
* @return string Zip file path
|
||||
*/
|
||||
public function export_config(string $downloadfilename) : string {
|
||||
global $CFG;
|
||||
public function export_model(string $zipfilename) : string {
|
||||
|
||||
\core_analytics\manager::check_can_manage_models();
|
||||
|
||||
$modelconfig = new model_config($this);
|
||||
$modeldata = $modelconfig->export();
|
||||
return $modelconfig->export_to_file($modeldata, $downloadfilename);
|
||||
return $modelconfig->export($zipfilename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the provided model.
|
||||
*
|
||||
* Note that this method assumes that model_config::check_dependencies has already been called.
|
||||
*
|
||||
* @throws \moodle_exception
|
||||
* @param string $zipfilepath Zip file path
|
||||
* @return \core_analytics\model
|
||||
*/
|
||||
public static function import_model(string $zipfilepath) : \core_analytics\model {
|
||||
|
||||
\core_analytics\manager::check_can_manage_models();
|
||||
|
||||
$modelconfig = new \core_analytics\model_config();
|
||||
return $modelconfig->import($zipfilepath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,6 +40,11 @@ class model_config {
|
||||
*/
|
||||
private $model = null;
|
||||
|
||||
/**
|
||||
* The name of the file where config is held.
|
||||
*/
|
||||
const CONFIG_FILE_NAME = 'model-config.json';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@ -50,100 +55,95 @@ class model_config {
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a model to a temp file using the provided file name.
|
||||
* Exports a model to a zip using the provided file name.
|
||||
*
|
||||
* @return \stdClass
|
||||
* @param string $zipfilename
|
||||
* @return string
|
||||
*/
|
||||
public function export() : \stdClass {
|
||||
public function export(string $zipfilename) : string {
|
||||
|
||||
if (!$this->model) {
|
||||
throw new \coding_exception('No model object provided.');
|
||||
}
|
||||
|
||||
if (!$this->model->can_export_configuration()) {
|
||||
throw new \moodle_exception('errornoexportconfigrequirements', 'tool_analytics');
|
||||
throw new \moodle_exception('errornoexportconfigrequirements', 'analytics');
|
||||
}
|
||||
|
||||
$versions = \core_component::get_all_versions();
|
||||
$zip = new \zip_packer();
|
||||
$zipfiles = [];
|
||||
|
||||
$data = new \stdClass();
|
||||
// Model config in JSON.
|
||||
$modeldata = $this->export_model_data();
|
||||
|
||||
// Target.
|
||||
$data->target = $this->model->get_target()->get_id();
|
||||
$requiredclasses[] = $data->target;
|
||||
$exporttmpdir = make_request_directory('analyticsexport');
|
||||
$jsonfilepath = $exporttmpdir . DIRECTORY_SEPARATOR . 'model-config.json';
|
||||
if (!file_put_contents($jsonfilepath, json_encode($modeldata))) {
|
||||
print_error('errornoexportconfig', 'analytics');
|
||||
}
|
||||
$zipfiles[self::CONFIG_FILE_NAME] = $jsonfilepath;
|
||||
|
||||
// Time splitting method.
|
||||
$data->timesplitting = $this->model->get_time_splitting()->get_id();
|
||||
$requiredclasses[] = $data->timesplitting;
|
||||
|
||||
// Model indicators.
|
||||
$data->indicators = [];
|
||||
foreach ($this->model->get_indicators() as $indicator) {
|
||||
$indicatorid = $indicator->get_id();
|
||||
$data->indicators[] = $indicatorid;
|
||||
$requiredclasses[] = $indicatorid;
|
||||
// ML backend.
|
||||
if ($this->model->is_trained()) {
|
||||
$processor = $this->model->get_predictions_processor(true);
|
||||
$outputdir = $this->model->get_output_dir(array('execution'));
|
||||
$mlbackenddir = $processor->export($this->model->get_unique_id(), $outputdir);
|
||||
$mlbackendfiles = get_directory_list($mlbackenddir);
|
||||
foreach ($mlbackendfiles as $mlbackendfile) {
|
||||
$fullpath = $mlbackenddir . DIRECTORY_SEPARATOR . $mlbackendfile;
|
||||
// Place the ML backend files inside a mlbackend/ dir.
|
||||
$zipfiles['mlbackend/' . $mlbackendfile] = $fullpath;
|
||||
}
|
||||
}
|
||||
|
||||
if ($processor = $this->model->get_model_obj()->predictionsprocessor) {
|
||||
$data->processor = $processor;
|
||||
}
|
||||
// Add information for versioning.
|
||||
$data->dependencies = [];
|
||||
foreach ($requiredclasses as $fullclassname) {
|
||||
$component = $this->get_class_component($fullclassname);
|
||||
$data->dependencies[$component] = $versions[$component];
|
||||
}
|
||||
$zipfilepath = $exporttmpdir . DIRECTORY_SEPARATOR . $zipfilename;
|
||||
$zip->archive_to_pathname($zipfiles, $zipfilepath);
|
||||
|
||||
return $data;
|
||||
return $zipfilepath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Packages the configuration of a model into a .json file.
|
||||
* Imports the provided model configuration into a new model.
|
||||
*
|
||||
* @param \stdClass $data Model config data
|
||||
* @param string $downloadfilename The file name.
|
||||
* @return string Path to the file with the model configuration.
|
||||
*/
|
||||
public function export_to_file(\stdClass $data, string $downloadfilename) : string {
|
||||
|
||||
$modelconfig = json_encode($data);
|
||||
|
||||
$dir = make_temp_directory('analyticsexport');
|
||||
$filepath = $dir . DIRECTORY_SEPARATOR . $downloadfilename;
|
||||
if (!file_put_contents($filepath, $modelconfig)) {
|
||||
print_error('errornoexportconfig', 'tool_analytics');
|
||||
}
|
||||
|
||||
return $filepath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the provided json string.
|
||||
* Note that this method assumes that self::check_dependencies has already been called.
|
||||
*
|
||||
* @param string $json A json string.
|
||||
* @return string|null Error string or null if all good.
|
||||
* @param string $zipfilepath Path to the zip file to import
|
||||
* @return \core_analytics\model
|
||||
*/
|
||||
public function check_json_data(string $json) : ?string {
|
||||
public function import(string $zipfilepath) : \core_analytics\model {
|
||||
|
||||
if (!$modeldata = json_decode($json)) {
|
||||
return get_string('errorimport', 'tool_analytics');
|
||||
list($modeldata, $mlbackenddir) = $this->extract_import_contents($zipfilepath);
|
||||
|
||||
$target = \core_analytics\manager::get_target($modeldata->target);
|
||||
$indicators = [];
|
||||
foreach ($modeldata->indicators as $indicatorclass) {
|
||||
$indicator = \core_analytics\manager::get_indicator($indicatorclass);
|
||||
$indicators[$indicator->get_id()] = $indicator;
|
||||
}
|
||||
$model = \core_analytics\model::create($target, $indicators, $modeldata->timesplitting, $modeldata->processor);
|
||||
|
||||
// Import them disabled.
|
||||
$model->update(false, false, false, false);
|
||||
|
||||
if ($mlbackenddir) {
|
||||
$modeldir = $model->get_output_dir(['execution']);
|
||||
if (!$model->get_predictions_processor(true)->import($model->get_unique_id(), $modeldir, $mlbackenddir)) {
|
||||
throw new \moodle_exception('errorimport', 'analytics');
|
||||
}
|
||||
$model->mark_as_trained();
|
||||
}
|
||||
|
||||
if (empty($modeldata->target) || empty($modeldata->timesplitting) || empty($modeldata->indicators)) {
|
||||
return get_string('errorimport', 'tool_analytics');
|
||||
}
|
||||
|
||||
return null;
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the provided model configuration can be deployed in this site.
|
||||
*
|
||||
* @param \stdClass $importmodel
|
||||
* @param \stdClass $modeldata
|
||||
* @param bool $ignoreversionmismatches
|
||||
* @return string|null Error string or null if all good.
|
||||
*/
|
||||
public function check_dependencies(\stdClass $importmodel, bool $ignoreversionmismatches) : ?string {
|
||||
public function check_dependencies(\stdClass $modeldata, bool $ignoreversionmismatches) : ?string {
|
||||
|
||||
$siteversions = \core_component::get_all_versions();
|
||||
|
||||
@ -153,7 +153,7 @@ class model_config {
|
||||
$missingclasses = [];
|
||||
|
||||
// We first check that this site has the required dependencies and the required versions.
|
||||
foreach ($importmodel->dependencies as $component => $importversion) {
|
||||
foreach ($modeldata->dependencies as $component => $importversion) {
|
||||
|
||||
if (empty($siteversions[$component])) {
|
||||
|
||||
@ -177,44 +177,42 @@ class model_config {
|
||||
}
|
||||
}
|
||||
|
||||
// Checking that the each of the components is available.
|
||||
if (!$target = manager::get_target($importmodel->target)) {
|
||||
$missingclasses[] = $importmodel->target;
|
||||
// Checking that each of the components is available.
|
||||
if (!$target = manager::get_target($modeldata->target)) {
|
||||
$missingclasses[] = $modeldata->target;
|
||||
}
|
||||
|
||||
if (!$timesplitting = manager::get_time_splitting($importmodel->timesplitting)) {
|
||||
$missingclasses[] = $importmodel->timesplitting;
|
||||
if (!$timesplitting = manager::get_time_splitting($modeldata->timesplitting)) {
|
||||
$missingclasses[] = $modeldata->timesplitting;
|
||||
}
|
||||
|
||||
// Indicators.
|
||||
$indicators = [];
|
||||
foreach ($importmodel->indicators as $indicatorclass) {
|
||||
foreach ($modeldata->indicators as $indicatorclass) {
|
||||
if (!$indicator = manager::get_indicator($indicatorclass)) {
|
||||
$missingclasses[] = $indicatorclass;
|
||||
}
|
||||
}
|
||||
|
||||
// ML backend.
|
||||
if (!empty($importmodel->processor)) {
|
||||
if (!$processor = \core_analytics\manager::get_predictions_processor($importmodel->processor, false)) {
|
||||
if (!empty($modeldata->processor)) {
|
||||
if (!$processor = \core_analytics\manager::get_predictions_processor($modeldata->processor, false)) {
|
||||
$missingclasses[] = $indicatorclass;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($missingcomponents)) {
|
||||
return get_string('errorimportmissingcomponents', 'tool_analytics', join(', ', $missingcomponents));
|
||||
return get_string('errorimportmissingcomponents', 'analytics', join(', ', $missingcomponents));
|
||||
}
|
||||
|
||||
if (!empty($versionmismatches)) {
|
||||
return get_string('errorimportversionmismatches', 'tool_analytics', implode(', ', $versionmismatches));
|
||||
return get_string('errorimportversionmismatches', 'analytics', implode(', ', $versionmismatches));
|
||||
}
|
||||
|
||||
if (!empty($missingclasses)) {
|
||||
$a = (object)[
|
||||
'missingclasses' => implode(', ', $missingclasses),
|
||||
'dependencyversions' => implode(', ', $dependencyversions)
|
||||
];
|
||||
return get_string('errorimportmissingclasses', 'tool_analytics', $a);
|
||||
return get_string('errorimportmissingclasses', 'analytics', $a);
|
||||
}
|
||||
|
||||
// No issues found.
|
||||
@ -248,4 +246,81 @@ class model_config {
|
||||
|
||||
return $component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the import zip contents.
|
||||
*
|
||||
* @param string $zipfilepath Zip file path
|
||||
* @return array [0] => \stdClass, [1] => string
|
||||
*/
|
||||
public function extract_import_contents(string $zipfilepath) : array {
|
||||
|
||||
$importtempdir = make_request_directory('analyticsimport' . microtime(false));
|
||||
|
||||
$zip = new \zip_packer();
|
||||
$filelist = $zip->extract_to_pathname($zipfilepath, $importtempdir);
|
||||
|
||||
if (empty($filelist[self::CONFIG_FILE_NAME])) {
|
||||
// Missing required file.
|
||||
throw new \moodle_exception('errorimport', 'analytics');
|
||||
}
|
||||
|
||||
$jsonmodeldata = file_get_contents($importtempdir . DIRECTORY_SEPARATOR . self::CONFIG_FILE_NAME);
|
||||
|
||||
if (!$modeldata = json_decode($jsonmodeldata)) {
|
||||
throw new \moodle_exception('errorimport', 'analytics');
|
||||
}
|
||||
|
||||
if (empty($modeldata->target) || empty($modeldata->timesplitting) || empty($modeldata->indicators)) {
|
||||
throw new \moodle_exception('errorimport', 'analytics');
|
||||
}
|
||||
|
||||
$mlbackenddir = $importtempdir . DIRECTORY_SEPARATOR . 'mlbackend';
|
||||
if (!is_dir($mlbackenddir)) {
|
||||
$mlbackenddir = false;
|
||||
}
|
||||
|
||||
return [$modeldata, $mlbackenddir];
|
||||
}
|
||||
/**
|
||||
* Exports the configuration of the model.
|
||||
* @return \stdClass
|
||||
*/
|
||||
protected function export_model_data() : \stdClass {
|
||||
|
||||
$versions = \core_component::get_all_versions();
|
||||
|
||||
$data = new \stdClass();
|
||||
|
||||
// Target.
|
||||
$data->target = $this->model->get_target()->get_id();
|
||||
$requiredclasses[] = $data->target;
|
||||
|
||||
// Time splitting method.
|
||||
$data->timesplitting = $this->model->get_time_splitting()->get_id();
|
||||
$requiredclasses[] = $data->timesplitting;
|
||||
|
||||
// Model indicators.
|
||||
$data->indicators = [];
|
||||
foreach ($this->model->get_indicators() as $indicator) {
|
||||
$indicatorid = $indicator->get_id();
|
||||
$data->indicators[] = $indicatorid;
|
||||
$requiredclasses[] = $indicatorid;
|
||||
}
|
||||
|
||||
// Return the predictions processor this model is using, even if no predictions processor
|
||||
// was explicitly selected.
|
||||
$predictionsprocessor = $this->model->get_predictions_processor();
|
||||
$data->processor = '\\' . get_class($predictionsprocessor);
|
||||
$requiredclasses[] = $data->processor;
|
||||
|
||||
// Add information for versioning.
|
||||
$data->dependencies = [];
|
||||
foreach ($requiredclasses as $fullclassname) {
|
||||
$component = $this->get_class_component($fullclassname);
|
||||
$data->dependencies[$component] = $versions[$component];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
57
analytics/classes/packable.php
Normal file
57
analytics/classes/packable.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Exportable machine learning backend interface.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_analytics;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Exportable machine learning backend interface.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
interface packable {
|
||||
|
||||
/**
|
||||
* Exports the machine learning model.
|
||||
*
|
||||
* @throws \moodle_exception
|
||||
* @param string $uniqueid The model unique id
|
||||
* @param string $modeldir The directory that contains the trained model.
|
||||
* @return string The path to the directory that contains the exported model.
|
||||
*/
|
||||
public function export(string $uniqueid, string $modeldir) : string;
|
||||
|
||||
/**
|
||||
* Imports the provided machine learning model.
|
||||
*
|
||||
* @param string $uniqueid The model unique id
|
||||
* @param string $modeldir The directory that will contain the trained model.
|
||||
* @param string $importdir The directory that contains the files to import.
|
||||
* @return bool Success
|
||||
*/
|
||||
public function import(string $uniqueid, string $modeldir, string $importdir) : bool;
|
||||
}
|
@ -337,36 +337,24 @@ class analytics_model_testcase extends advanced_testcase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test export_from_json() API.
|
||||
* Test that import_model import models' configurations.
|
||||
*/
|
||||
public function test_create_from_import() {
|
||||
public function test_import_model_config() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$this->model->enable('\\core\\analytics\\time_splitting\\quarters');
|
||||
$zipfilepath = $this->model->export_model('yeah-config.zip');
|
||||
|
||||
$this->modelobj = $this->model->get_model_obj();
|
||||
|
||||
$modelconfig = new \core_analytics\model_config($this->model);
|
||||
$modeldata = $modelconfig->export();
|
||||
$importedmodel = \core_analytics\model::create_from_import($modeldata)->get_model_obj();
|
||||
$importedmodelobj = \core_analytics\model::import_model($zipfilepath)->get_model_obj();
|
||||
|
||||
$this->assertSame($this->modelobj->target, $importedmodel->target);
|
||||
$this->assertSame($this->modelobj->indicators, $importedmodel->indicators);
|
||||
$this->assertSame($this->modelobj->timesplitting, $importedmodel->timesplitting);
|
||||
$this->assertEmpty($importedmodel->predictionsprocessor);
|
||||
$this->assertSame($this->modelobj->target, $importedmodelobj->target);
|
||||
$this->assertSame($this->modelobj->indicators, $importedmodelobj->indicators);
|
||||
$this->assertSame($this->modelobj->timesplitting, $importedmodelobj->timesplitting);
|
||||
|
||||
$this->model->update(true, false, false, '\\mlbackend_php\\processor');
|
||||
|
||||
$this->modelobj = $this->model->get_model_obj();
|
||||
|
||||
$modelconfig = new \core_analytics\model_config($this->model);
|
||||
$modeldata = $modelconfig->export();
|
||||
$importedmodel = \core_analytics\model::create_from_import($modeldata)->get_model_obj();
|
||||
|
||||
$this->assertSame($this->modelobj->target, $importedmodel->target);
|
||||
$this->assertSame($this->modelobj->indicators, $importedmodel->indicators);
|
||||
$this->assertSame($this->modelobj->timesplitting, $importedmodel->timesplitting);
|
||||
$this->assertSame($this->modelobj->predictionsprocessor, $importedmodel->predictionsprocessor);
|
||||
$predictionsprocessor = $this->model->get_predictions_processor();
|
||||
$this->assertSame('\\' . get_class($predictionsprocessor), $importedmodelobj->predictionsprocessor);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -399,7 +387,11 @@ class analytics_model_testcase extends advanced_testcase {
|
||||
$this->model->enable('\\core\\analytics\\time_splitting\\quarters');
|
||||
|
||||
$modelconfig = new \core_analytics\model_config($this->model);
|
||||
$modeldata = $modelconfig->export();
|
||||
|
||||
$method = new ReflectionMethod('\\core_analytics\\model_config', 'export_model_data');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$modeldata = $method->invoke($modelconfig);
|
||||
|
||||
$this->assertArrayHasKey('core', $modeldata->dependencies);
|
||||
$this->assertInternalType('float', $modeldata->dependencies['core']);
|
||||
@ -409,7 +401,8 @@ class analytics_model_testcase extends advanced_testcase {
|
||||
|
||||
$indicators['test_indicator_max'] = \core_analytics\manager::get_indicator('test_indicator_max');
|
||||
$this->model->update(true, $indicators, false);
|
||||
$modeldata = $modelconfig->export();
|
||||
|
||||
$modeldata = $method->invoke($modelconfig);
|
||||
|
||||
$this->assertCount(1, $modeldata->indicators);
|
||||
}
|
||||
@ -443,17 +436,6 @@ class analytics_model_testcase extends advanced_testcase {
|
||||
*/
|
||||
class testable_model extends \core_analytics\model {
|
||||
|
||||
/**
|
||||
* get_output_dir
|
||||
*
|
||||
* @param array $subdirs
|
||||
* @param bool $onlymodelid
|
||||
* @return string
|
||||
*/
|
||||
public function get_output_dir($subdirs = array(), $onlymodelid = false) {
|
||||
return parent::get_output_dir($subdirs, $onlymodelid);
|
||||
}
|
||||
|
||||
/**
|
||||
* init_analyser
|
||||
*
|
||||
|
@ -119,23 +119,9 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
$this->setAdminuser();
|
||||
set_config('enabled_stores', 'logstore_standard', 'tool_log');
|
||||
|
||||
$ncourses = 10;
|
||||
|
||||
// Generate training data.
|
||||
$params = array(
|
||||
'startdate' => mktime(0, 0, 0, 10, 24, 2015),
|
||||
'enddate' => mktime(0, 0, 0, 2, 24, 2016),
|
||||
);
|
||||
for ($i = 0; $i < $ncourses; $i++) {
|
||||
$name = 'a' . random_string(10);
|
||||
$courseparams = array('shortname' => $name, 'fullname' => $name) + $params;
|
||||
$this->getDataGenerator()->create_course($courseparams);
|
||||
}
|
||||
for ($i = 0; $i < $ncourses; $i++) {
|
||||
$name = 'b' . random_string(10);
|
||||
$courseparams = array('shortname' => $name, 'fullname' => $name) + $params;
|
||||
$this->getDataGenerator()->create_course($courseparams);
|
||||
}
|
||||
$ncourses = 10;
|
||||
$this->generate_courses($ncourses);
|
||||
|
||||
// We repeat the test for all prediction processors.
|
||||
$predictionsprocessor = \core_analytics\manager::get_predictions_processor($predictionsprocessorclass, false);
|
||||
@ -171,6 +157,10 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
$this->assertEmpty($fs->get_directory_files(\context_system::instance()->id, 'analytics',
|
||||
\core_analytics\dataset_manager::UNLABELLED_FILEAREA, $model->get_id(), '/analysable/', true, false));
|
||||
|
||||
$params = [
|
||||
'startdate' => mktime(0, 0, 0, 10, 24, 2015),
|
||||
'enddate' => mktime(0, 0, 0, 2, 24, 2016),
|
||||
];
|
||||
$courseparams = $params + array('shortname' => 'aaaaaa', 'fullname' => 'aaaaaa', 'visible' => 0);
|
||||
$course1 = $this->getDataGenerator()->create_course($courseparams);
|
||||
$courseparams = $params + array('shortname' => 'bbbbbb', 'fullname' => 'bbbbbb', 'visible' => 0);
|
||||
@ -280,6 +270,67 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
return $this->add_prediction_processors($cases);
|
||||
}
|
||||
|
||||
/**
|
||||
* test_ml_export_import
|
||||
*
|
||||
* @param string $predictionsprocessorclass The class name
|
||||
* @dataProvider provider_ml_export_import
|
||||
*/
|
||||
public function test_ml_export_import($predictionsprocessorclass) {
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminuser();
|
||||
set_config('enabled_stores', 'logstore_standard', 'tool_log');
|
||||
|
||||
// Generate training data.
|
||||
$ncourses = 10;
|
||||
$this->generate_courses($ncourses);
|
||||
|
||||
// We repeat the test for all prediction processors.
|
||||
$predictionsprocessor = \core_analytics\manager::get_predictions_processor($predictionsprocessorclass, false);
|
||||
if ($predictionsprocessor->is_ready() !== true) {
|
||||
$this->markTestSkipped('Skipping ' . $predictionsprocessorclass . ' as the predictor is not ready.');
|
||||
}
|
||||
|
||||
$model = $this->add_perfect_model();
|
||||
$model->update(true, false, '\core\analytics\time_splitting\quarters', get_class($predictionsprocessor));
|
||||
|
||||
$model->train();
|
||||
|
||||
$this->generate_courses(10, ['visible' => 0]);
|
||||
|
||||
$originalresults = $model->predict();
|
||||
|
||||
$zipfilename = 'model-zip-' . microtime() . '.zip';
|
||||
$zipfilepath = $model->export_model($zipfilename);
|
||||
|
||||
$importmodel = \core_analytics\model::import_model($zipfilepath);
|
||||
$importmodel->enable();
|
||||
|
||||
// Now predict using the imported model without prior training.
|
||||
$importedmodelresults = $importmodel->predict();
|
||||
|
||||
foreach ($originalresults->predictions as $sampleid => $prediction) {
|
||||
$this->assertEquals($importedmodelresults->predictions[$sampleid]->prediction, $prediction->prediction);
|
||||
}
|
||||
|
||||
set_config('enabled_stores', '', 'tool_log');
|
||||
get_log_manager(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* provider_ml_export_import
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function provider_ml_export_import() {
|
||||
$cases = [
|
||||
'case' => [],
|
||||
];
|
||||
|
||||
// We need to test all system prediction processors.
|
||||
return $this->add_prediction_processors($cases);
|
||||
}
|
||||
/**
|
||||
* Test the system classifiers returns.
|
||||
*
|
||||
@ -400,20 +451,7 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
}
|
||||
|
||||
// Generate training data.
|
||||
$params = array(
|
||||
'startdate' => mktime(0, 0, 0, 10, 24, 2015),
|
||||
'enddate' => mktime(0, 0, 0, 2, 24, 2016),
|
||||
);
|
||||
for ($i = 0; $i < $ncourses; $i++) {
|
||||
$name = 'a' . random_string(10);
|
||||
$params = array('shortname' => $name, 'fullname' => $name) + $params;
|
||||
$this->getDataGenerator()->create_course($params);
|
||||
}
|
||||
for ($i = 0; $i < $ncourses; $i++) {
|
||||
$name = 'b' . random_string(10);
|
||||
$params = array('shortname' => $name, 'fullname' => $name) + $params;
|
||||
$this->getDataGenerator()->create_course($params);
|
||||
}
|
||||
$this->generate_courses($ncourses);
|
||||
|
||||
// We repeat the test for all prediction processors.
|
||||
$predictionsprocessor = \core_analytics\manager::get_predictions_processor($predictionsprocessorclass, false);
|
||||
@ -579,6 +617,32 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
return new \core_analytics\model($model->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates $ncourses courses
|
||||
*
|
||||
* @param int $ncourses The number of courses to be generated.
|
||||
* @param array $params Course params
|
||||
* @return null
|
||||
*/
|
||||
protected function generate_courses($ncourses, array $params = []) {
|
||||
|
||||
$params = $params + [
|
||||
'startdate' => mktime(0, 0, 0, 10, 24, 2015),
|
||||
'enddate' => mktime(0, 0, 0, 2, 24, 2016),
|
||||
];
|
||||
|
||||
for ($i = 0; $i < $ncourses; $i++) {
|
||||
$name = 'a' . random_string(10);
|
||||
$courseparams = array('shortname' => $name, 'fullname' => $name) + $params;
|
||||
$this->getDataGenerator()->create_course($courseparams);
|
||||
}
|
||||
for ($i = 0; $i < $ncourses; $i++) {
|
||||
$name = 'b' . random_string(10);
|
||||
$courseparams = array('shortname' => $name, 'fullname' => $name) + $params;
|
||||
$this->getDataGenerator()->create_course($courseparams);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add_prediction_processors
|
||||
*
|
||||
|
@ -38,8 +38,16 @@ $string['erroralreadypredict'] = 'File {$a} has already been used to generate pr
|
||||
$string['errorcannotreaddataset'] = 'Dataset file {$a} can not be read';
|
||||
$string['errorcannotwritedataset'] = 'Dataset file {$a} cannot be written';
|
||||
$string['errorendbeforestart'] = 'The end date ({$a}) is before the course start date.';
|
||||
$string['errorexportmodelresult'] = 'The machine learning model can not be exported.';
|
||||
$string['errorimport'] = 'Error importing the provided json file.';
|
||||
$string['errorimportmissingcomponents'] = 'The provided model requires the following plugins to be installed: {$a}. Note that the versions do not necessarily need to match with the versions installed in your system. To install the same or a newer version of the plugin should be enough in most cases.';
|
||||
$string['errorimportversionmismatches'] = 'The version of the following components differ from the version installed in this site: {$a}. You can use "Ignore version mismatches" option to ignore these differences.';
|
||||
$string['errorimportmissingclasses'] = 'The following analytics components are not available in this site: {$a->missingclasses}. ';
|
||||
$string['errorinvalidindicator'] = 'Invalid {$a} indicator';
|
||||
$string['errorinvalidtarget'] = 'Invalid {$a} target';
|
||||
$string['errorinvalidtimesplitting'] = 'Invalid time splitting; please ensure you add the class fully qualified class name.';
|
||||
$string['errornoexportconfig'] = 'There was a problem exporting the model configuration.';
|
||||
$string['errornoexportconfigrequirements'] = 'Only non static models with timeplitting methods can be exported.';
|
||||
$string['errornoindicators'] = 'This model does not have any indicators.';
|
||||
$string['errornopredictresults'] = 'No results returned from the predictions processor. Check the output directory contents for more information.';
|
||||
$string['errornotimesplittings'] = 'This model does not have any time-splitting method.';
|
||||
|
@ -38,7 +38,7 @@ use Phpml\ModelManager;
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class processor implements \core_analytics\classifier, \core_analytics\regressor {
|
||||
class processor implements \core_analytics\classifier, \core_analytics\regressor, \core_analytics\packable {
|
||||
|
||||
/**
|
||||
* Size of training / prediction batches.
|
||||
@ -103,8 +103,7 @@ class processor implements \core_analytics\classifier, \core_analytics\regressor
|
||||
*/
|
||||
public function train_classification($uniqueid, \stored_file $dataset, $outputdir) {
|
||||
|
||||
// Output directory is already unique to the model.
|
||||
$modelfilepath = $outputdir . DIRECTORY_SEPARATOR . self::MODEL_FILENAME;
|
||||
$modelfilepath = $this->get_model_filepath($outputdir);
|
||||
|
||||
$modelmanager = new ModelManager();
|
||||
|
||||
@ -175,8 +174,7 @@ class processor implements \core_analytics\classifier, \core_analytics\regressor
|
||||
*/
|
||||
public function classify($uniqueid, \stored_file $dataset, $outputdir) {
|
||||
|
||||
// Output directory is already unique to the model.
|
||||
$modelfilepath = $outputdir . DIRECTORY_SEPARATOR . self::MODEL_FILENAME;
|
||||
$modelfilepath = $this->get_model_filepath($outputdir);
|
||||
|
||||
if (!file_exists($modelfilepath)) {
|
||||
throw new \moodle_exception('errorcantloadmodel', 'mlbackend_php', '', $modelfilepath);
|
||||
@ -424,6 +422,77 @@ class processor implements \core_analytics\classifier, \core_analytics\regressor
|
||||
throw new \coding_exception('This predictor does not support regression yet.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the machine learning model.
|
||||
*
|
||||
* @throws \moodle_exception
|
||||
* @param string $uniqueid The model unique id
|
||||
* @param string $modeldir The directory that contains the trained model.
|
||||
* @return string The path to the directory that contains the exported model.
|
||||
*/
|
||||
public function export(string $uniqueid, string $modeldir) : string {
|
||||
|
||||
$modelfilepath = $this->get_model_filepath($modeldir);
|
||||
|
||||
if (!file_exists($modelfilepath)) {
|
||||
throw new \moodle_exception('errorexportmodelresult', 'analytics');
|
||||
}
|
||||
|
||||
// We can use the actual $modeldir as the directory is not modified during export, just copied into a zip.
|
||||
return $modeldir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the provided machine learning model.
|
||||
*
|
||||
* @param string $uniqueid The model unique id
|
||||
* @param string $modeldir The directory that will contain the trained model.
|
||||
* @param string $importdir The directory that contains the files to import.
|
||||
* @return bool Success
|
||||
*/
|
||||
public function import(string $uniqueid, string $modeldir, string $importdir) : bool {
|
||||
|
||||
$importmodelfilepath = $this->get_model_filepath($importdir);
|
||||
$modelfilepath = $this->get_model_filepath($modeldir);
|
||||
|
||||
$modelmanager = new ModelManager();
|
||||
|
||||
// Copied from ModelManager::restoreFromFile to validate the serialised contents
|
||||
// before restoring them.
|
||||
$importconfig = file_get_contents($importmodelfilepath);
|
||||
|
||||
// Clean stuff like function calls.
|
||||
$importconfig = preg_replace('/[^a-zA-Z0-9\{\}%\.\*\;\,\:\"\-\0\\\]/', '', $importconfig);
|
||||
|
||||
$object = unserialize($importconfig,
|
||||
['allowed_classes' => ['Phpml\\Classification\\Linear\\LogisticRegression']]);
|
||||
if (!$object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (get_class($object) == '__PHP_Incomplete_Class') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$classifier = $modelmanager->restoreFromFile($importmodelfilepath);
|
||||
|
||||
// This would override any previous classifier.
|
||||
$modelmanager->saveToFile($classifier, $modelfilepath);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the serialised model file in the provided directory.
|
||||
*
|
||||
* @param string $modeldir The model directory
|
||||
* @return string The model file
|
||||
*/
|
||||
protected function get_model_filepath(string $modeldir) : string {
|
||||
// Output directory is already unique to the model.
|
||||
return $modeldir . DIRECTORY_SEPARATOR . self::MODEL_FILENAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Phi correlation coefficient.
|
||||
*
|
||||
|
@ -33,7 +33,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class processor implements \core_analytics\classifier, \core_analytics\regressor {
|
||||
class processor implements \core_analytics\classifier, \core_analytics\regressor, \core_analytics\packable {
|
||||
|
||||
/**
|
||||
* The required version of the python package that performs all calculations.
|
||||
@ -263,6 +263,78 @@ class processor implements \core_analytics\classifier, \core_analytics\regresso
|
||||
return $resultobj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the machine learning model.
|
||||
*
|
||||
* @throws \moodle_exception
|
||||
* @param string $uniqueid The model unique id
|
||||
* @param string $modeldir The directory that contains the trained model.
|
||||
* @return string The path to the directory that contains the exported model.
|
||||
*/
|
||||
public function export(string $uniqueid, string $modeldir) : string {
|
||||
|
||||
// We include an exporttmpdir as we want to be sure that the file is not deleted after the
|
||||
// python process finishes.
|
||||
$exporttmpdir = make_request_directory('mlbackend_python_export');
|
||||
|
||||
$cmd = "{$this->pathtopython} -m moodlemlbackend.export " .
|
||||
escapeshellarg($uniqueid) . ' ' .
|
||||
escapeshellarg($modeldir) . ' ' .
|
||||
escapeshellarg($exporttmpdir);
|
||||
|
||||
if (!PHPUNIT_TEST && CLI_SCRIPT) {
|
||||
debugging($cmd, DEBUG_DEVELOPER);
|
||||
}
|
||||
|
||||
$output = null;
|
||||
$exitcode = null;
|
||||
$exportdir = exec($cmd, $output, $exitcode);
|
||||
|
||||
if ($exitcode != 0) {
|
||||
throw new \moodle_exception('errorexportmodelresult', 'analytics');
|
||||
}
|
||||
|
||||
if (!$exportdir) {
|
||||
throw new \moodle_exception('errorexportmodelresult', 'analytics');
|
||||
}
|
||||
|
||||
return $exportdir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the provided machine learning model.
|
||||
*
|
||||
* @param string $uniqueid The model unique id
|
||||
* @param string $modeldir The directory that will contain the trained model.
|
||||
* @param string $importdir The directory that contains the files to import.
|
||||
* @return bool Success
|
||||
*/
|
||||
public function import(string $uniqueid, string $modeldir, string $importdir) : bool {
|
||||
|
||||
$cmd = "{$this->pathtopython} -m moodlemlbackend.import " .
|
||||
escapeshellarg($uniqueid) . ' ' .
|
||||
escapeshellarg($modeldir) . ' ' .
|
||||
escapeshellarg($importdir);
|
||||
|
||||
if (!PHPUNIT_TEST && CLI_SCRIPT) {
|
||||
debugging($cmd, DEBUG_DEVELOPER);
|
||||
}
|
||||
|
||||
$output = null;
|
||||
$exitcode = null;
|
||||
$success = exec($cmd, $output, $exitcode);
|
||||
|
||||
if ($exitcode != 0) {
|
||||
throw new \moodle_exception('errorimportmodelresult', 'analytics');
|
||||
}
|
||||
|
||||
if (!$success) {
|
||||
throw new \moodle_exception('errorimportmodelresult', 'analytics');
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Train this processor regression model using the provided supervised learning dataset.
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user