Source code for hastegeo.core.utils.tbparser
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import json
from datetime import datetime, timezone
from tensorboard.backend.event_processing.event_accumulator import ( # type: ignore
EventAccumulator,
)
[docs]def parse_tb_event_logs(log_file_path):
"""
Parses TensorBoard event logs to extract epoch, accuracy, and loss information.
Args:
log_file_path (str): The file path to the TensorBoard event log file.
Returns:
tuple: A tuple containing:
- start_timestamp (str): The start timestamp of the events in UTC formatted as '%Y-%m-%d %H:%M:%S'.
- result (str): A JSON string containing a list of dictionaries, each representing an epoch with the following keys:
- 'epoch' (int): The epoch number.
- 'elapsedDurationInMinutes' (float): The duration of the epoch in minutes.
- 'multiclassAccuracy' (float): The multiclass accuracy for the epoch.
- 'loss' (float): The loss for the epoch.
- 'wallTime' (str): The wall time of the epoch event in UTC formatted as '%Y-%m-%d %H:%M:%S'.
"""
# Load events (merged load_events)
ea = EventAccumulator(log_file_path)
ea.Reload()
events_by_tag = {}
all_events = []
for tag in ea.Tags().get("scalars", []):
scalar_events = ea.Scalars(tag)
events_by_tag[tag] = {event.step: event for event in scalar_events}
all_events.extend(scalar_events)
start_timestamp = (
min(e.wall_time for e in all_events) if all_events else None
)
# Process epoch events (as in original parse_tf_event_logs)
epoch_events = events_by_tag.get("epoch", {})
latest_epoch_events = {}
for step, event in epoch_events.items():
epoch_value = event.value
if (
epoch_value not in latest_epoch_events
or step > latest_epoch_events[epoch_value].step
):
latest_epoch_events[epoch_value] = event
accuracy_events = events_by_tag.get("train_MulticlassAccuracy", {})
loss_events = events_by_tag.get("train_loss", {})
result = []
sorted_epochs = sorted(latest_epoch_events)
prev_wall_time = None
for epoch in sorted_epochs:
epoch_event = latest_epoch_events[epoch]
step = epoch_event.step
# Inline the logic of find_latest_event_at_or_before for accuracy_events
acc_event = None
for ev_step, event in accuracy_events.items():
if ev_step <= step and (
acc_event is None or ev_step > acc_event.step
):
acc_event = event
# Inline the logic of find_latest_event_at_or_before for loss_events
loss_event = None
for ev_step, event in loss_events.items():
if ev_step <= step and (
loss_event is None or ev_step > loss_event.step
):
loss_event = event
# Calculate duration from the previous epoch or from start timestamp
if prev_wall_time is not None:
epoch_duration = epoch_event.wall_time - prev_wall_time
else:
epoch_duration = epoch_event.wall_time - start_timestamp
record = {
"epoch": int(epoch_event.value),
"elapsedDurationInMinutes": (
round(epoch_duration / 60, 2)
if epoch_duration is not None
else None
),
"multiclassAccuracy": (
round(acc_event.value, 6) if acc_event else None
),
"loss": round(loss_event.value, 6) if loss_event else None,
"wallTime": datetime.fromtimestamp(
epoch_event.wall_time, timezone.utc
).strftime("%Y-%m-%d %H:%M:%S"),
}
result.append(record)
prev_wall_time = epoch_event.wall_time
if start_timestamp is not None:
start_timestamp = datetime.fromtimestamp(
start_timestamp, timezone.utc
).strftime("%Y-%m-%d %H:%M:%S")
return start_timestamp, json.dumps(result, default=str)
[docs]def calculate_metrics(
logs, maxEpochs, time_field="elapsedDurationInMinutes", epoch_field="epoch"
):
"""
Calculate completion metrics from TensorBoard logs.
Args:
logs (str): JSON string of logs containing epoch and time information.
maxEpochs (int): Maximum number of epochs.
time_field (str): The field name in logs that contains elapsed time information. Default is 'elapsedDurationInMinutes'.
epoch_field (str): The field name in logs that contains epoch information. Default is 'epoch'.
Returns:
dict: A dictionary containing the following keys:
- 'completed_epochs' (int): The number of completed epochs.
- 'approx_time_to_complete' (float): The approximate time to complete the remaining epochs.
- 'total_elapsed_time' (float): The total elapsed time.
- 'time_per_epoch' (float): The average time per epoch.
"""
if logs is not None:
try:
logs = json.loads(logs)
except (TypeError, json.JSONDecodeError):
return None
epoch_0_time = None
for log in logs:
if time_field in log and log[epoch_field] == 0:
epoch_0_time = log[time_field]
break
if epoch_0_time:
total_epochs = int(maxEpochs) if maxEpochs else 0
total_elapsed_time = sum(
log[time_field] for log in logs if time_field in log
)
total_elapsed_time = round(total_elapsed_time, 2)
completed_epochs_logs = list(
log for log in logs if log[epoch_field] < total_epochs
)
completed_epochs = max(0, len(completed_epochs_logs) - 1)
if completed_epochs > 0:
avg_time_per_epoch = round(epoch_0_time, 2)
total_time = total_epochs * avg_time_per_epoch
time_to_completion = round(
max(0, total_time - total_elapsed_time), 2
)
time_per_epoch = round(avg_time_per_epoch, 2)
else:
time_to_completion = None
time_per_epoch = None
return {
"completed_epochs": completed_epochs,
"approx_time_to_complete": time_to_completion,
"total_elapsed_time": total_elapsed_time,
"time_per_epoch": time_per_epoch,
}
return None