Skip to content

Commit

Permalink
Merge pull request #11 from dmonllao/MDL-66004
Browse files Browse the repository at this point in the history
MDL-66004
  • Loading branch information
stronk7 authored Oct 3, 2019
2 parents b7d5170 + 38c4ef4 commit ca9bd5d
Show file tree
Hide file tree
Showing 12 changed files with 539 additions and 12 deletions.
2 changes: 1 addition & 1 deletion moodlemlbackend/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.1.0
2.2.1
1 change: 1 addition & 0 deletions moodlemlbackend/import.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def import_classifier():

print('Ok')
# An exception will be thrown before if it can be imported.
print('Ok')
sys.exit(0)

import_classifier()
1 change: 1 addition & 0 deletions moodlemlbackend/model/tensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def set_tensor_logdir(self, tensor_logdir):

def build_graph(self, initial_weights=False):
"""Builds the computational graph without feeding any data in"""

# Placeholders for input values.
with tf.name_scope('inputs'):
self.x = tf.placeholder(
Expand Down
31 changes: 20 additions & 11 deletions moodlemlbackend/processor/estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,13 @@ def get_metadata(filepath):
for row in file_iterator:
row_count += 1
if row_count == 1:
data_header = [x for x in csv.reader(row, delimiter=',', quotechar='"')][0]
data_header = [x for x in csv.reader(
row, delimiter=',', quotechar='"')][0]
classes_index = data_header.index("targetclasses")
features_index = data_header.index("nfeatures")
if row_count == 2:
info_row = [x for x in csv.reader(row, delimiter=',', quotechar='"')][0]
info_row = [x for x in csv.reader(
row, delimiter=',', quotechar='"')][0]
target_classes = json.loads(info_row[classes_index])
return {
"n_classes": len(target_classes),
Expand Down Expand Up @@ -223,7 +225,6 @@ def __init__(self, modelid, directory, dataset=None):
raise OSError('Directory ' + self.tensor_logdir +
' can not be created.')


def get_classifier(self, X, y, initial_weights=False):
"""Gets the classifier"""

Expand Down Expand Up @@ -357,6 +358,10 @@ def evaluate_dataset(self, filepath, min_score=0.6,
X_train, X_test, y_train, y_test = train_test_split(self.X,
self.y,
test_size=0.2)
if len(np.unique(y_train)) < self.n_classes:
# We need the input data to match the expected size of the
# tensor.
continue

classifier = self.train(X_train, y_train)

Expand All @@ -375,7 +380,8 @@ def evaluate_dataset(self, filepath, min_score=0.6,

if self.is_binary:
logging.info("AUC: %.2f%%", result['auc'])
logging.info("AUC standard deviation: %.4f", result['auc_deviation'])
logging.info("AUC standard deviation: %.4f",
result['auc_deviation'])
logging.info("Accuracy: %.2f%%", result['accuracy'] * 100)
logging.info("Precision (predicted elements that are real): %.2f%%",
result['precision'] * 100)
Expand Down Expand Up @@ -490,12 +496,19 @@ def get_evaluation_results(self, min_score, accepted_deviation):
avg_precision = np.mean(self.precisions)
avg_recall = np.mean(self.recalls)
avg_mcc = np.mean(self.mccs)
if len(self.aucs) > 0:
avg_aucs = np.mean(self.aucs)
else:
avg_aucs = 0

# MCC goes from -1 to 1 we need to transform it to a value between
# 0 and 1 to compare it with the minimum score provided.
score = (avg_mcc + 1) / 2

acc_deviation = np.std(self.mccs)
if len(self.mccs) > 0:
acc_deviation = np.std(self.mccs)
else:
acc_deviation = 1
result = dict()
if self.is_binary:
result['auc'] = np.mean(self.aucs)
Expand Down Expand Up @@ -533,17 +546,13 @@ def get_evaluation_results(self, min_score, accepted_deviation):
if acc_deviation > accepted_deviation and score < min_score:
result['status'] = LOW_SCORE + NOT_ENOUGH_DATA

result['info'].append('Launch TensorBoard from command line by ' +
'typing: tensorboard --logdir=\'' +
self.get_tensor_logdir() + '\'')

return result

def store_classifier(self, trained_classifier):
"""Stores the classifier and saves a checkpoint of the tensors state"""

# Store the graph state.
saver = tf.train.Saver()
saver = tf.train.Saver(save_relative_paths=True)
sess = trained_classifier.get_session()

path = os.path.join(self.persistencedir, 'model.ckpt')
Expand All @@ -567,7 +576,7 @@ def load_classifier(self, model_dir=False):
classifier.set_tensor_logdir(self.get_tensor_logdir())

# Now restore the graph state.
saver = tf.train.Saver()
saver = tf.train.Saver(save_relative_paths=True)
path = os.path.join(model_dir, 'model.ckpt')
saver.restore(classifier.get_session(), path)
return classifier
Expand Down
1 change: 1 addition & 0 deletions moodlemlbackend/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import os


def print_version():
"""Prints moodlemlbackend package version"""

Expand Down
Empty file.
52 changes: 52 additions & 0 deletions moodlemlbackend/webapp/access.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import os
import re

from functools import wraps

from flask import request


def check_access(f):
'''Checks the access to the route.'''

@wraps(f)
def access_wrapper(*args, **kwargs):

# Check that the environment var is properly set.
envvarname = "MOODLE_MLBACKEND_PYTHON_USERS"
if envvarname not in os.environ:
raise Exception(
envvarname + ' environment var is not set in the server.')

if re.match(os.environ[envvarname], '[^A-Za-z0-9_\-,$]'):
raise Exception(
'The value of ' + envvarname + ' environment var does not ' +
' adhere to [^A-Za-z0-9_\-,$]')

users = os.environ[envvarname].split(',')

if (request.authorization is None or
request.authorization.username is None or
request.authorization.password is None):
# Response for the client.
return 'No user and/or password included in the request.', 401

for user in users:
userdata = user.split(':')
if len(userdata) != 2:
raise Exception('Incorrect format for ' +
envvarname + ' environment var. It should ' +
'contain a comma-separated list of ' +
'username:password.')

if (userdata[0] == request.authorization.username and
userdata[1] == request.authorization.password):

# If all good we return the return from 'f' passing the
# original list of params to it.
return f(*args, **kwargs)

# Response for the client.
return 'Incorrect user and/or password provided by Moodle.', 401

return access_wrapper
80 changes: 80 additions & 0 deletions moodlemlbackend/webapp/localfs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import shutil
import os

from functools import wraps, update_wrapper

from moodlemlbackend.webapp.util import get_request_value


# We can not set LocalFS_setup_base_dir as a nested class because they
# have problems to access the outer class.'''


class LocalFS(object):

def get_localbasedir(self):
if self.localbasedir is None:
raise Exception('localbasedir is not set')

return self.localbasedir

def set_localbasedir(self, basedir):
self.localbasedir = basedir

def get_model_dir(self, hashkey, fetch_model=False):
'''Returns the model dir in the local fs for the provided key.
fetch_model param is ignored here.'''

uniquemodelid = get_request_value('uniqueid')
dirhash = get_request_value(hashkey)

# The dir in the local filesystem is namespaced by uniquemodelid and
# the dirhash which determines where the results should be stored.
modeldir = os.path.join(self.get_localbasedir(),
uniquemodelid, dirhash)

return modeldir

def delete_dir(self):

uniquemodelid = get_request_value('uniqueid')

# All files related to this version of the model in moodle are in
# /uniquemodelid.
modeldir = os.path.join(self.get_localbasedir(), uniquemodelid)

if os.path.exists(modeldir):
# The directory may not exist.
shutil.rmtree(modeldir, True)


class LocalFS_setup_base_dir(object):

def __init__(self, storage, fetch_model, push_model):
'''Checks that the local directory is set in ENV.
fetch_model and push_model are ignored in local_fs.'''

self.storage = storage

localbasedir = os.environ["MOODLE_MLBACKEND_PYTHON_DIR"]

if os.path.exists(localbasedir) is False:
raise IOError(
'The base dir does not exist. ' +
'Set env MOODLE_MLBACKEND_PYTHON_DIR to an existing dir')

os.access(localbasedir, os.W_OK)

storage.set_localbasedir(localbasedir)

def __call__(self, f):

@wraps(f)
def wrapper(*args, **kwargs):
'''Execute the decorated function passing the call args.'''

update_wrapper(self, f)
return f(*args, **kwargs)
return wrapper
Loading

0 comments on commit ca9bd5d

Please sign in to comment.