Skip to content

Commit 49b2d38

Browse files
committed
feature request #208
- added runtime information (Python version >= 3.3) - fixed get run cache bug - removed run tags that I don't like
1 parent ae5999a commit 49b2d38

4 files changed

Lines changed: 59 additions & 15 deletions

File tree

openml/runs/functions.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
import numpy as np
66
import warnings
77
import sklearn
8+
import time
89
from sklearn.model_selection._search import BaseSearchCV
910

1011
from build.lib.openml.exceptions import PyOpenMLError
1112
from .. import config
1213
from ..flows import sklearn_to_flow, get_flow
1314
from ..setups import setup_exists
1415
from ..exceptions import OpenMLCacheException, OpenMLServerException
15-
from ..util import URLError
16+
from ..util import URLError, version_complies
1617
from ..tasks.functions import _create_task_from_xml
1718
from .._api_calls import _perform_api_call
1819
from .run import OpenMLRun
@@ -70,7 +71,7 @@ def run_task(task, model):
7071
run = OpenMLRun(task_id=task.task_id, flow_id=flow_id, dataset_id=dataset.dataset_id, model=model)
7172

7273
try:
73-
run.data_content, run.trace_content = _run_task_get_arffcontent(model, task, class_labels)
74+
run.data_content, run.trace_content, run.detailed_evaluations = _run_task_get_arffcontent(model, task, class_labels)
7475
except PyOpenMLError as message:
7576
run.error_message = str(message)
7677
warnings.warn("Run terminated with error: %s" %run.error_message)
@@ -141,6 +142,7 @@ def _run_task_get_arffcontent(model, task, class_labels):
141142
X, Y = task.get_X_and_y()
142143
arff_datacontent = []
143144
arff_tracecontent = []
145+
user_defined_measures = defaultdict(lambda: defaultdict(dict))
144146

145147
rep_no = 0
146148
# TODO use different iterator to only provide a single iterator (less
@@ -156,8 +158,15 @@ def _run_task_get_arffcontent(model, task, class_labels):
156158
testY = Y[test_indices]
157159

158160
try:
161+
# for measuring runtime. Only available since Python 3.3
162+
if version_complies(3, 3):
163+
modelfit_starttime = time.process_time()
159164
model_fold.fit(trainX, trainY)
160165

166+
if version_complies(3, 3):
167+
modelfit_duration = time.process_time() - modelfit_starttime
168+
user_defined_measures['usercpu_time_millis_training'][rep_no][fold_no] = modelfit_duration
169+
161170
if isinstance(model_fold, BaseSearchCV):
162171
_add_results_to_arfftrace(arff_tracecontent, fold_no, model_fold, rep_no)
163172
model_classes = model_fold.best_estimator_.classes_
@@ -167,8 +176,15 @@ def _run_task_get_arffcontent(model, task, class_labels):
167176
# typically happens when training a regressor on classification task
168177
raise PyOpenMLError(str(e))
169178

179+
if version_complies(3, 3):
180+
modelpredict_starttime = time.process_time()
170181
ProbaY = model_fold.predict_proba(testX)
171182
PredY = model_fold.predict(testX)
183+
if version_complies(3, 3):
184+
modelpredict_duration = time.process_time() - modelpredict_starttime
185+
user_defined_measures['usercpu_time_millis_testing'][rep_no][fold_no] = modelpredict_duration
186+
user_defined_measures['usercpu_time_millis'][rep_no][fold_no] = modelfit_duration + modelpredict_duration
187+
172188
if ProbaY.shape[1] != len(class_labels):
173189
warnings.warn("Repeat %d Fold %d: estimator only predicted for %d/%d classes!" %(rep_no, fold_no, ProbaY.shape[1], len(class_labels)))
174190

@@ -182,10 +198,20 @@ def _run_task_get_arffcontent(model, task, class_labels):
182198
if not isinstance(model, BaseSearchCV):
183199
arff_tracecontent = None
184200

185-
return arff_datacontent, arff_tracecontent
201+
return arff_datacontent, arff_tracecontent, user_defined_measures
186202

187203

188204
def _add_results_to_arfftrace(arff_tracecontent, fold_no, model, rep_no):
205+
'''
206+
Extracts the various results calculated by `BaseSearchCV` classes into openml trace arff format
207+
208+
:param arff_tracecontent: the list that the results should be appended to
209+
:param fold_no: cv fold number
210+
:param model: the model to extract from
211+
:param rep_no: cv repetition number
212+
213+
:return: A list lists, each representing an arff line
214+
'''
189215
for itt_no in range(0, len(model.cv_results_['mean_test_score'])):
190216
# we use the string values for True and False, as it is defined in this way by the OpenML server
191217
selected = 'false'
@@ -350,7 +376,7 @@ def _get_cached_run(run_id):
350376
run_file = os.path.join(run_cache_dir,
351377
"run_%d.xml" % int(run_id))
352378
with io.open(run_file, encoding='utf8') as fh:
353-
run = _create_task_from_xml(xml=fh.read())
379+
run = _create_run_from_xml(xml=fh.read())
354380
return run
355381

356382
except (OSError, IOError):

openml/runs/run.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -170,14 +170,16 @@ def _create_description_xml(self):
170170

171171
# as a tag, it must be of the form ([a-zA-Z0-9_\-\.])+
172172
# so we format time from 'mm/dd/yy hh:mm:ss' to 'mm-dd-yy_hh.mm.ss'
173-
well_formatted_time = time.strftime("%c").replace(
174-
' ', '_').replace('/', '-').replace(':', '.')
175-
tags = run_environment + [well_formatted_time] + ['run_task'] + \
176-
[self.model.__module__ + "." + self.model.__class__.__name__]
173+
# well_formatted_time = time.strftime("%c").replace(
174+
# ' ', '_').replace('/', '-').replace(':', '.')
175+
# tags = run_environment + [well_formatted_time] + ['run_task'] + \
176+
# [self.model.__module__ + "." + self.model.__class__.__name__]
177+
tags = ['openml-python', run_environment[1]]
177178
description = _to_dict(taskid=self.task_id, flow_id=self.flow_id,
178179
setup_string=_create_setup_string(self.model),
179180
parameter_settings=openml_param_settings,
180181
error_message=self.error_message,
182+
detailed_evaluations=self.detailed_evaluations,
181183
tags=tags)
182184
description_xml = xmltodict.unparse(description, pretty=True)
183185
return description_xml
@@ -266,7 +268,7 @@ def _get_version_information():
266268
return [python_version, sklearn_version, numpy_version, scipy_version]
267269

268270

269-
def _to_dict(taskid, flow_id, setup_string, error_message, parameter_settings, tags):
271+
def _to_dict(taskid, flow_id, setup_string, error_message, parameter_settings, tags=None, detailed_evaluations=None):
270272
""" Creates a dictionary corresponding to the desired xml desired by openML
271273
272274
Parameters
@@ -293,11 +295,17 @@ def _to_dict(taskid, flow_id, setup_string, error_message, parameter_settings, t
293295
if error_message is not None:
294296
description['oml:run']['oml:error_message'] = error_message
295297
description['oml:run']['oml:parameter_setting'] = parameter_settings
296-
description['oml:run']['oml:tag'] = tags # Tags describing the run
297-
# description['oml:run']['oml:output_data'] = 0;
298-
# all data that was output of this run, which can be evaluation scores
299-
# (though those are also calculated serverside)
300-
# must be of special data type
298+
if tags is not None:
299+
description['oml:run']['oml:tag'] = tags # Tags describing the run
300+
if detailed_evaluations is not None:
301+
description['oml:run']['oml:output_data'] = dict()
302+
description['oml:run']['oml:output_data']['oml:evaluation'] = list()
303+
for measure in detailed_evaluations:
304+
for repeat in detailed_evaluations[measure]:
305+
for fold, value in detailed_evaluations[measure][repeat].items():
306+
current = OrderedDict([('@repeat', str(repeat)), ('@fold', str(fold)),
307+
('oml:name', measure), ('oml:value', str(value))])
308+
description['oml:run']['oml:output_data']['oml:evaluation'].append(current)
301309
return description
302310

303311

openml/util.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,15 @@ def is_string(obj):
1212
except NameError:
1313
return isinstance(obj, str)
1414

15+
def version_complies(major, minor=None):
16+
version = sys.version_info
17+
if version[0] > major:
18+
return True
19+
if version[0] < major:
20+
return False
21+
# version == major
22+
if minor is None or version[1] >= minor:
23+
return True
24+
return False
1525

1626
__all__ = ['URLError', 'is_string']

tests/test_runs/test_run_functions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def test__run_task_get_arffcontent(self):
117117
clf, task, class_labels)
118118

119119
clf = SGDClassifier(loss='log', random_state=1)
120-
arff_datacontent, arff_tracecontent = openml.runs.functions._run_task_get_arffcontent(
120+
arff_datacontent, arff_tracecontent, _ = openml.runs.functions._run_task_get_arffcontent(
121121
clf, task, class_labels)
122122
# predictions
123123
self.assertIsInstance(arff_datacontent, list)

0 commit comments

Comments
 (0)