Completed
Push — master ( e9b36c...1b826b )
by Klaus
28s
created

MetricsLogger.log_scalar_metric()   B

Complexity

Conditions 4

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
cc 4
c 3
b 0
f 1
dl 0
loc 25
rs 8.5806
1
#!/usr/bin/env python
2
# coding=utf-8
3
import datetime
4
import sys
5
import sacred.optional as opt
6
7
if sys.version_info[0] == 2:
8
    from Queue import Queue, Empty
9
else:
10
    from queue import Queue, Empty
11
12
13
class MetricsLogger(object):
14
    """MetricsLogger collects metrics measured during experiments.
15
16
    MetricsLogger is the (only) part of the Metrics API.
17
    An instance of the class should be created for the Run class, such that the
18
    log_scalar_metric method is accessible from running experiments using
19
    _run.metrics.log_scalar_metric.
20
    """
21
22
    def __init__(self):
23
        # Create a message queue that remembers
24
        # calls of the log_scalar_metric
25
        self._logged_metrics = Queue()
26
        self._metric_step_counter = {}
27
        """Remembers the last number of each metric."""
28
29
    def log_scalar_metric(self, metric_name, value, step=None):
30
        """
31
        Add a new measurement.
32
33
        The measurement will be processed by the MongoDB observer
34
        during a heartbeat event.
35
        Other observers are not yet supported.
36
37
        :param metric_name: The name of the metric, e.g. training.loss.
38
        :param value: The measured value.
39
        :param step: The step number (integer), e.g. the iteration number
40
                    If not specified, an internal counter for each metric
41
                    is used, incremented by one.
42
        """
43
        if opt.has_numpy:
44
            np = opt.np
45
            if isinstance(value, np.generic):
46
                value = np.asscalar(value)
47
        if step is None:
48
            step = self._metric_step_counter.get(metric_name, -1) + 1
49
        self._logged_metrics.put(
50
            ScalarMetricLogEntry(metric_name, step,
51
                                 datetime.datetime.utcnow(),
52
                                 value))
53
        self._metric_step_counter[metric_name] = step
54
55
    def get_last_metrics(self):
56
        """Read all measurement events since last call of the method.
57
58
        :return List[ScalarMetricLogEntry]
59
        """
60
        read_up_to = self._logged_metrics.qsize()
61
        messages = []
62
        for i in range(read_up_to):
63
            try:
64
                messages.append(self._logged_metrics.get_nowait())
65
            except Empty:
66
                pass
67
        return messages
68
69
70
class ScalarMetricLogEntry():
71
    """Container for measurements of scalar metrics.
72
73
    There is exactly one ScalarMetricLogEntry per logged scalar metric value.
74
    """
75
76
    def __init__(self, name, step, timestamp, value):
77
        self.name = name
78
        self.step = step
79
        self.timestamp = timestamp
80
        self.value = value
81
82
83
def linearize_metrics(logged_metrics):
84
    """
85
    Group metrics by name.
86
87
    Takes a list of individual measurements, possibly belonging
88
    to different metrics and groups them by name.
89
90
    :param logged_metrics: A list of ScalarMetricLogEntries
91
    :return: Measured values grouped by the metric name:
92
    {"metric_name1": {"steps": [0,1,2], "values": [4, 5, 6],
93
    "timestamps": [datetime, datetime, datetime]},
94
    "metric_name2": {...}}
95
    """
96
    metrics_by_name = {}
97
    for metric_entry in logged_metrics:
98
        if metric_entry.name not in metrics_by_name:
99
            metrics_by_name[metric_entry.name] = {
100
                "steps": [],
101
                "values": [],
102
                "timestamps": [],
103
                "name": metric_entry.name
104
            }
105
        metrics_by_name[metric_entry.name]["steps"] \
106
            .append(metric_entry.step)
107
        metrics_by_name[metric_entry.name]["values"] \
108
            .append(metric_entry.value)
109
        metrics_by_name[metric_entry.name]["timestamps"] \
110
            .append(metric_entry.timestamp)
111
    return metrics_by_name
112