dsa-helpers 1.1.18.dev0__tar.gz → 1.1.19.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. {dsa_helpers-1.1.18.dev0/dsa_helpers.egg-info → dsa_helpers-1.1.19.dev2}/PKG-INFO +1 -1
  2. dsa_helpers-1.1.19.dev2/docker/Dockerfile +12 -0
  3. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/__init__.py +1 -1
  4. dsa_helpers-1.1.19.dev2/dsa_helpers/ml/callbacks.py +134 -0
  5. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/segformer_semantic_segmentation/train.py +75 -58
  6. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2/dsa_helpers.egg-info}/PKG-INFO +1 -1
  7. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/pyproject.toml +1 -1
  8. dsa_helpers-1.1.18.dev0/docker/Dockerfile +0 -14
  9. dsa_helpers-1.1.18.dev0/dsa_helpers/ml/callbacks.py +0 -55
  10. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/.gitignore +0 -0
  11. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/.readthedocs.yaml +0 -0
  12. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/LICENSE +0 -0
  13. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/README.md +0 -0
  14. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/app.py +0 -0
  15. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dev.py +0 -0
  16. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/docker/README.md +0 -0
  17. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/docs/requirements.txt +0 -0
  18. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/docs/source/conf.py +0 -0
  19. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/docs/source/index.rst +0 -0
  20. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/cli_utils.py +0 -0
  21. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/dash/__init__.py +0 -0
  22. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/dash/dash_paperdragon_utils.py +0 -0
  23. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/dash/header.py +0 -0
  24. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/dash/header_callbacks.py +0 -0
  25. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/girder_utils.py +0 -0
  26. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/gpd_utils.py +0 -0
  27. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/image_utils.py +0 -0
  28. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/imread.py +0 -0
  29. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/imwrite.py +0 -0
  30. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/__init__.py +0 -0
  31. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/datasets/SegFormerSegmentationDataset.py +0 -0
  32. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/datasets/__init__.py +0 -0
  33. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/datasets/utils.py +0 -0
  34. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/evaluate.py +0 -0
  35. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/metrics.py +0 -0
  36. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/object_detection/__init__.py +0 -0
  37. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/object_detection/get_pearce_roi_images.py +0 -0
  38. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/object_detection/tile_image.py +0 -0
  39. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/object_detection/utils.py +0 -0
  40. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/segformer_semantic_segmentation/__init__.py +0 -0
  41. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/segformer_semantic_segmentation/datasets.py +0 -0
  42. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/segformer_semantic_segmentation/evaluate.py +0 -0
  43. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/segformer_semantic_segmentation/inference.py +0 -0
  44. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/segformer_semantic_segmentation/transforms.py +0 -0
  45. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/segformer_semantic_segmentation/utils.py +0 -0
  46. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/transforms/__init__.py +0 -0
  47. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/ml/transforms/segformer_transforms.py +0 -0
  48. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/mongo_utils.py +0 -0
  49. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/tile_utils.py +0 -0
  50. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/tiling.py +0 -0
  51. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers/utils.py +0 -0
  52. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers.egg-info/SOURCES.txt +0 -0
  53. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers.egg-info/dependency_links.txt +0 -0
  54. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers.egg-info/requires.txt +0 -0
  55. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/dsa_helpers.egg-info/top_level.txt +0 -0
  56. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/requirements.txt +0 -0
  57. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dsa_helpers
3
- Version: 1.1.18.dev0
3
+ Version: 1.1.19.dev2
4
4
  Summary: Utility functions for working with the DSA girder client.
5
5
  Author-email: "Juan C. Vizcarra" <jvizcar@emory.edu>
6
6
  Maintainer-email: "Juan C. Vizcarra" <jvizcar@emory.edu>
@@ -0,0 +1,12 @@
1
+ FROM pytorch/pytorch:2.8.0-cuda12.6-cudnn9-runtime
2
+
3
+ RUN pip install --no-cache-dir dsa-helpers && \
4
+ pip install --no-cache-dir large-image[all] --find-links https://girder.github.io/large_image_wheels && \
5
+ pip install --no-cache-dir histomicstk
6
+
7
+ # # Dependencies for OpenCV (not needed anymore, but keeping for reference)
8
+ # RUN apt-get update && apt-get install ffmpeg libsm6 libxext6 -y
9
+
10
+ RUN mkdir /app && chmod 777 /app && mkdir /data && chmod 777 /data
11
+
12
+ WORKDIR /app
@@ -3,7 +3,7 @@ from .imread import imread
3
3
  from .imwrite import imwrite
4
4
 
5
5
  # Version of the dsa-helpers package
6
- __version__ = "1.1.18dev0"
6
+ __version__ = "1.1.19dev2"
7
7
 
8
8
  # To avoid slow downs, do not allow from dsa_helpers import * to import anything.
9
9
  __all__ = []
@@ -0,0 +1,134 @@
1
+ """Provides custom callbacks for the Hugging Face Trainer class."""
2
+
3
+ from transformers import TrainerCallback
4
+ from pathlib import Path
5
+ import pandas as pd
6
+ from copy import deepcopy
7
+ import os, shutil
8
+
9
+
10
+ class MetricsLoggerCallback(TrainerCallback):
11
+ def _load_current_file(self, output_dir):
12
+ output_dir = Path(output_dir)
13
+ metrics_file = output_dir / "metrics.csv"
14
+
15
+ return (
16
+ pd.read_csv(metrics_file)
17
+ if metrics_file.exists()
18
+ else pd.DataFrame()
19
+ )
20
+
21
+ def on_log(self, args, state, control, logs=None, **kwargs):
22
+ """
23
+ Triggered during training logging (e.g., for training loss).
24
+ """
25
+ if "loss" in logs: # Ensure training loss is available
26
+ metrics_df = self._load_current_file(args.output_dir)
27
+
28
+ # Append the epoch and the training loss.
29
+ loss = logs["loss"]
30
+ epoch = state.epoch
31
+
32
+ metrics_df = pd.concat(
33
+ [
34
+ metrics_df,
35
+ pd.DataFrame([{"epoch": epoch, "train_loss": loss}]),
36
+ ],
37
+ ignore_index=True,
38
+ )
39
+
40
+ # Save it back to file.
41
+ metrics_df.to_csv(
42
+ Path(args.output_dir) / "metrics.csv", index=False
43
+ )
44
+
45
+ def on_evaluate(self, args, state, control, metrics, **kwargs):
46
+ """
47
+ This is triggered at the end of evaluation (including at the end of each epoch).
48
+ """
49
+ # Get the current metrics file.
50
+ metrics_df = self._load_current_file(args.output_dir)
51
+
52
+ # Add metrics to the last row of the current metrics file.
53
+ for k, v in metrics.items():
54
+ metrics_df.at[metrics_df.index[-1], k] = v
55
+
56
+ # Save the updated metrics file.
57
+ metrics_df.to_csv(Path(args.output_dir) / "metrics.csv", index=False)
58
+
59
+
60
+ class BestModelCallback(TrainerCallback):
61
+ """Callback for tracking and saving the best model checkpoint.
62
+
63
+ This class was developed with the help of ChatGPT."""
64
+
65
+ def __init__(self, save_dir: str):
66
+ """
67
+ Initialize the callback.
68
+
69
+ Args:
70
+ save_dir (str): Directory where model is saving checkpoints.
71
+ The best checkpoint will be saved in a subdirectory
72
+ called "best-checkpoint".
73
+
74
+ """
75
+ self.best_loss = float("inf")
76
+ self.best_model_state = None
77
+ self.save_dir = save_dir
78
+
79
+ def on_evaluate(self, args, state, control, metrics, model, **kwargs):
80
+ """Called after evaluation. Track the best model state."""
81
+ # Get the mean_dice metric
82
+ current_metric = metrics.get("eval_loss")
83
+
84
+ if current_metric is not None and current_metric < self.best_loss:
85
+ self.best_loss = current_metric
86
+ print("New best loss.")
87
+
88
+ # Store the model state.
89
+ self.best_model_state = deepcopy(model.state_dict())
90
+
91
+ def on_train_end(self, args, state, control, model, **kwargs):
92
+ """Logic:
93
+ * Create a best-checkpoint directory
94
+ * Save the best model state into the best-checkpoint directory
95
+ * Copy all the other model files (from the normal checkpoint
96
+ directory) into the best-checkpoint directory
97
+
98
+ """
99
+ if self.best_model_state is not None:
100
+ # Create best-checkpoint directory
101
+ best_checkpoint_dir = os.path.join(
102
+ self.save_dir, "best-checkpoint"
103
+ )
104
+ os.makedirs(best_checkpoint_dir, exist_ok=True)
105
+
106
+ # Load the best model state
107
+ model.load_state_dict(self.best_model_state)
108
+
109
+ # Save the model (this creates model.safetensors or pytorch_model.bin)
110
+ model.save_pretrained(best_checkpoint_dir)
111
+
112
+ # Copy other checkpoint files from the latest checkpoint
113
+ # Find the latest checkpoint directory (checkpoint-{step_number})
114
+ save_path = Path(self.save_dir)
115
+ checkpoint_dirs = [
116
+ d
117
+ for d in save_path.iterdir()
118
+ if d.is_dir() and d.name.startswith("checkpoint-")
119
+ ]
120
+ if checkpoint_dirs:
121
+ # Sort by step number to get the latest
122
+ latest_checkpoint = max(
123
+ checkpoint_dirs, key=lambda x: int(x.name.split("-")[1])
124
+ )
125
+
126
+ # Copy all files from the latest checkpoint, except the model files we just created
127
+ for file_path in latest_checkpoint.iterdir():
128
+ if file_path.is_file():
129
+ # Skip model files since we already saved the best model state
130
+ if not file_path.name.startswith(
131
+ ("model.", "pytorch_model.")
132
+ ):
133
+ dst = Path(best_checkpoint_dir) / file_path.name
134
+ shutil.copy2(file_path, dst)
@@ -6,6 +6,7 @@ from transformers import (
6
6
  )
7
7
  import numpy as np
8
8
  from pathlib import Path
9
+ import tempfile, shutil
9
10
 
10
11
  import torch
11
12
  from torch import nn
@@ -13,7 +14,7 @@ from torch import nn
13
14
  from .utils import create_segformer_segmentation_dataset
14
15
  from .transforms import get_train_transforms, val_transforms
15
16
  from .evaluate import per_class_dice_on_dataset
16
- from ..callbacks import MetricsLoggerCallback
17
+ from ..callbacks import MetricsLoggerCallback, BestModelCallback
17
18
 
18
19
 
19
20
  def train(
@@ -41,6 +42,7 @@ def train(
41
42
  contrast: float = 0.25,
42
43
  saturation: float = 0.25,
43
44
  hue: float = 0.1,
45
+ rank: int = 0,
44
46
  ):
45
47
  """Workflow for training a SegFormer semantic segmentation model.
46
48
 
@@ -101,6 +103,7 @@ def train(
101
103
  images. Defaults to 0.25.
102
104
  hue (float, optional): Hue factor to apply to the images.
103
105
  Defaults to 0.1.
106
+ rank (int, optional): Rank of the process. Defaults to 0.
104
107
 
105
108
  Returns:
106
109
  tuple: A tuple containing the trainer and the results dictionary.
@@ -108,8 +111,11 @@ def train(
108
111
  """
109
112
  save_dir_path = Path(save_dir)
110
113
 
111
- if save_dir_path.is_dir():
112
- raise FileExistsError(f"Save directory {save_dir} already exists.")
114
+ if rank == 0:
115
+ if save_dir_path.is_dir():
116
+ raise FileExistsError(f"Save directory {save_dir} already exists.")
117
+ else:
118
+ save_dir_path.mkdir(parents=True, exist_ok=True)
113
119
 
114
120
  # Read the dataframes if they are strings.
115
121
  if isinstance(train_data, str):
@@ -139,6 +145,10 @@ def train(
139
145
  ignore_mismatched_sizes=True,
140
146
  ).to(device)
141
147
 
148
+ if rank != 0:
149
+ # Create temp directory for the model.
150
+ save_dir = tempfile.mkdtemp()
151
+
142
152
  # Training arguments.
143
153
  training_args = TrainingArguments(
144
154
  str(save_dir),
@@ -150,7 +160,6 @@ def train(
150
160
  eval_strategy="epoch",
151
161
  save_strategy="epoch",
152
162
  logging_strategy="epoch",
153
- load_best_model_at_end=True,
154
163
  save_total_limit=1,
155
164
  )
156
165
 
@@ -236,6 +245,11 @@ def train(
236
245
  metrics["mean_dice"] = float(np.mean(list(metrics.values())))
237
246
  return metrics
238
247
 
248
+ if rank == 0:
249
+ callbacks = [MetricsLoggerCallback, BestModelCallback(str(save_dir))]
250
+ else:
251
+ callbacks = [MetricsLoggerCallback]
252
+
239
253
  # Trainer
240
254
  trainer = Trainer(
241
255
  model=model,
@@ -243,73 +257,62 @@ def train(
243
257
  train_dataset=train_dataset,
244
258
  eval_dataset=val_dataset,
245
259
  compute_metrics=compute_metrics,
246
- callbacks=[MetricsLoggerCallback], # Add the custom callback here
260
+ callbacks=callbacks,
247
261
  )
248
262
 
249
- # Train the model.
250
- _ = trainer.train()
251
-
252
- # Rename the checkpoint to not include a step number.
253
- save_dir = Path(save_dir)
254
- for p in save_dir.iterdir():
255
- if p.is_dir() and p.name.startswith("checkpoint-"):
256
- p.rename(save_dir / "checkpoint")
263
+ if rank != 0:
264
+ # Train the model.
265
+ _ = trainer.train()
257
266
 
258
- results = {"train": {}, "val": {}}
259
-
260
- for log in trainer.state.log_history[:-1]:
261
- epoch = log["epoch"]
267
+ # Delete the temp directory.
268
+ shutil.rmtree(save_dir)
269
+ return None
270
+ else:
271
+ print("Starting training...")
272
+ _ = trainer.train()
273
+ print("Training completed successfully!")
262
274
 
263
- if epoch % 1:
264
- continue # only track end of each epoch
275
+ results = {"train": {}, "val": {}}
265
276
 
266
- # Check if this is tracking train or validation.
267
- if "eval_loss" in log:
268
- data = results["val"]
269
- else:
270
- data = results["train"]
277
+ for log in trainer.state.log_history[:-1]:
278
+ epoch = log["epoch"]
271
279
 
272
- for k, v in log.items():
273
- if k.startswith("eval_"):
274
- k = k[5:]
280
+ if epoch % 1:
281
+ continue # only track end of each epoch
275
282
 
276
- if k not in data:
277
- data[k] = []
283
+ # Check if this is tracking train or validation.
284
+ if "eval_loss" in log:
285
+ data = results["val"]
286
+ else:
287
+ data = results["train"]
278
288
 
279
- data[k].append(v)
289
+ for k, v in log.items():
290
+ if k.startswith("eval_"):
291
+ k = k[5:]
280
292
 
281
- # For each of the datasets provided, get the metrics.
282
- model = trainer.model
293
+ if k not in data:
294
+ data[k] = []
283
295
 
284
- print("Calculating train metrics...")
285
- results["train"]["metrics"] = per_class_dice_on_dataset(
286
- model,
287
- train_data,
288
- label2id,
289
- batch_size=batch_size,
290
- device=device,
291
- tile_size=tile_size,
292
- tqdm_notebook=tqdm_notebook,
293
- )
296
+ data[k].append(v)
294
297
 
295
- print("Calculating val metrics...")
296
- results["val"]["metrics"] = per_class_dice_on_dataset(
297
- model,
298
- val_data,
299
- label2id,
300
- batch_size=batch_size,
301
- device=device,
302
- tile_size=tile_size,
303
- tqdm_notebook=tqdm_notebook,
304
- )
298
+ # For each of the datasets provided, get the metrics.
299
+ model = trainer.model
305
300
 
306
- if test_data is not None:
307
- print("Calculating test metrics...")
308
- results["test"] = {}
301
+ print("Calculating train metrics...")
302
+ results["train"]["metrics"] = per_class_dice_on_dataset(
303
+ model,
304
+ train_data,
305
+ label2id,
306
+ batch_size=batch_size,
307
+ device=device,
308
+ tile_size=tile_size,
309
+ tqdm_notebook=tqdm_notebook,
310
+ )
309
311
 
310
- results["test"]["metrics"] = per_class_dice_on_dataset(
312
+ print("Calculating val metrics...")
313
+ results["val"]["metrics"] = per_class_dice_on_dataset(
311
314
  model,
312
- test_data,
315
+ val_data,
313
316
  label2id,
314
317
  batch_size=batch_size,
315
318
  device=device,
@@ -317,4 +320,18 @@ def train(
317
320
  tqdm_notebook=tqdm_notebook,
318
321
  )
319
322
 
320
- return trainer, results
323
+ if test_data is not None:
324
+ print("Calculating test metrics...")
325
+ results["test"] = {}
326
+
327
+ results["test"]["metrics"] = per_class_dice_on_dataset(
328
+ model,
329
+ test_data,
330
+ label2id,
331
+ batch_size=batch_size,
332
+ device=device,
333
+ tile_size=tile_size,
334
+ tqdm_notebook=tqdm_notebook,
335
+ )
336
+
337
+ return trainer, results
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dsa_helpers
3
- Version: 1.1.18.dev0
3
+ Version: 1.1.19.dev2
4
4
  Summary: Utility functions for working with the DSA girder client.
5
5
  Author-email: "Juan C. Vizcarra" <jvizcar@emory.edu>
6
6
  Maintainer-email: "Juan C. Vizcarra" <jvizcar@emory.edu>
@@ -41,7 +41,7 @@ classifiers = [
41
41
  "License :: OSI Approved :: MIT License",
42
42
  ]
43
43
  keywords = ["dsa", "girder", "girder_client"]
44
- version = "v1.1.18dev0"
44
+ version = "v1.1.19dev2"
45
45
 
46
46
  [project.optional-dependencies]
47
47
  opencv = ["opencv-python"]
@@ -1,14 +0,0 @@
1
- FROM pytorch/pytorch:2.8.0-cuda12.9-cudnn9-runtime
2
-
3
- RUN pip install --no-cache-dir dsa-helpers
4
- RUN pip install --no-cache-dir large-image[all] --find-links https://girder.github.io/large_image_wheels
5
- RUN python -m pip install --no-cache-dir histomicstk --find-links https://girder.github.io/large_image_wheels
6
-
7
- # Dependencies for OpenCV.
8
- RUN apt-get update && apt-get install ffmpeg libsm6 libxext6 -y
9
-
10
- RUN mkdir /app
11
- RUN chmod 777 /app
12
-
13
- RUN mkdir /data
14
- RUN chmod 777 /data
@@ -1,55 +0,0 @@
1
- """Provides custom callbacks for the Hugging Face Trainer class."""
2
-
3
- from transformers import TrainerCallback
4
- from pathlib import Path
5
- import pandas as pd
6
-
7
-
8
- class MetricsLoggerCallback(TrainerCallback):
9
- def _load_current_file(self, output_dir):
10
- output_dir = Path(output_dir)
11
- metrics_file = output_dir / "metrics.csv"
12
-
13
- return (
14
- pd.read_csv(metrics_file)
15
- if metrics_file.exists()
16
- else pd.DataFrame()
17
- )
18
-
19
- def on_log(self, args, state, control, logs=None, **kwargs):
20
- """
21
- Triggered during training logging (e.g., for training loss).
22
- """
23
- if "loss" in logs: # Ensure training loss is available
24
- metrics_df = self._load_current_file(args.output_dir)
25
-
26
- # Append the epoch and the training loss.
27
- loss = logs["loss"]
28
- epoch = state.epoch
29
-
30
- metrics_df = pd.concat(
31
- [
32
- metrics_df,
33
- pd.DataFrame([{"epoch": epoch, "train_loss": loss}]),
34
- ],
35
- ignore_index=True,
36
- )
37
-
38
- # Save it back to file.
39
- metrics_df.to_csv(
40
- Path(args.output_dir) / "metrics.csv", index=False
41
- )
42
-
43
- def on_evaluate(self, args, state, control, metrics, **kwargs):
44
- """
45
- This is triggered at the end of evaluation (including at the end of each epoch).
46
- """
47
- # Get the current metrics file.
48
- metrics_df = self._load_current_file(args.output_dir)
49
-
50
- # Add metrics to the last row of the current metrics file.
51
- for k, v in metrics.items():
52
- metrics_df.at[metrics_df.index[-1], k] = v
53
-
54
- # Save the updated metrics file.
55
- metrics_df.to_csv(Path(args.output_dir) / "metrics.csv", index=False)