dsa-helpers 1.1.18.dev0__tar.gz → 1.1.19.dev1__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.dev1}/PKG-INFO +1 -1
  2. dsa_helpers-1.1.19.dev1/docker/Dockerfile +12 -0
  3. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/__init__.py +1 -1
  4. dsa_helpers-1.1.19.dev1/dsa_helpers/ml/callbacks.py +134 -0
  5. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/segformer_semantic_segmentation/train.py +68 -59
  6. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1/dsa_helpers.egg-info}/PKG-INFO +1 -1
  7. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/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.dev1}/.gitignore +0 -0
  11. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/.readthedocs.yaml +0 -0
  12. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/LICENSE +0 -0
  13. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/README.md +0 -0
  14. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/app.py +0 -0
  15. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dev.py +0 -0
  16. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/docker/README.md +0 -0
  17. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/docs/requirements.txt +0 -0
  18. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/docs/source/conf.py +0 -0
  19. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/docs/source/index.rst +0 -0
  20. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/cli_utils.py +0 -0
  21. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/dash/__init__.py +0 -0
  22. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/dash/dash_paperdragon_utils.py +0 -0
  23. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/dash/header.py +0 -0
  24. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/dash/header_callbacks.py +0 -0
  25. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/girder_utils.py +0 -0
  26. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/gpd_utils.py +0 -0
  27. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/image_utils.py +0 -0
  28. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/imread.py +0 -0
  29. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/imwrite.py +0 -0
  30. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/__init__.py +0 -0
  31. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/datasets/SegFormerSegmentationDataset.py +0 -0
  32. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/datasets/__init__.py +0 -0
  33. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/datasets/utils.py +0 -0
  34. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/evaluate.py +0 -0
  35. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/metrics.py +0 -0
  36. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/object_detection/__init__.py +0 -0
  37. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/object_detection/get_pearce_roi_images.py +0 -0
  38. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/object_detection/tile_image.py +0 -0
  39. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/object_detection/utils.py +0 -0
  40. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/segformer_semantic_segmentation/__init__.py +0 -0
  41. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/segformer_semantic_segmentation/datasets.py +0 -0
  42. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/segformer_semantic_segmentation/evaluate.py +0 -0
  43. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/segformer_semantic_segmentation/inference.py +0 -0
  44. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/segformer_semantic_segmentation/transforms.py +0 -0
  45. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/segformer_semantic_segmentation/utils.py +0 -0
  46. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/transforms/__init__.py +0 -0
  47. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/ml/transforms/segformer_transforms.py +0 -0
  48. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/mongo_utils.py +0 -0
  49. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/tile_utils.py +0 -0
  50. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/tiling.py +0 -0
  51. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers/utils.py +0 -0
  52. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers.egg-info/SOURCES.txt +0 -0
  53. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers.egg-info/dependency_links.txt +0 -0
  54. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers.egg-info/requires.txt +0 -0
  55. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/dsa_helpers.egg-info/top_level.txt +0 -0
  56. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/requirements.txt +0 -0
  57. {dsa_helpers-1.1.18.dev0 → dsa_helpers-1.1.19.dev1}/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.dev1
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.19dev1"
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)
@@ -13,7 +13,7 @@ from torch import nn
13
13
  from .utils import create_segformer_segmentation_dataset
14
14
  from .transforms import get_train_transforms, val_transforms
15
15
  from .evaluate import per_class_dice_on_dataset
16
- from ..callbacks import MetricsLoggerCallback
16
+ from ..callbacks import MetricsLoggerCallback, BestModelCallback
17
17
 
18
18
 
19
19
  def train(
@@ -41,6 +41,7 @@ def train(
41
41
  contrast: float = 0.25,
42
42
  saturation: float = 0.25,
43
43
  hue: float = 0.1,
44
+ rank: int = 0,
44
45
  ):
45
46
  """Workflow for training a SegFormer semantic segmentation model.
46
47
 
@@ -101,6 +102,7 @@ def train(
101
102
  images. Defaults to 0.25.
102
103
  hue (float, optional): Hue factor to apply to the images.
103
104
  Defaults to 0.1.
105
+ rank (int, optional): Rank of the process. Defaults to 0.
104
106
 
105
107
  Returns:
106
108
  tuple: A tuple containing the trainer and the results dictionary.
@@ -108,8 +110,11 @@ def train(
108
110
  """
109
111
  save_dir_path = Path(save_dir)
110
112
 
111
- if save_dir_path.is_dir():
112
- raise FileExistsError(f"Save directory {save_dir} already exists.")
113
+ if rank == 0:
114
+ if save_dir_path.is_dir():
115
+ raise FileExistsError(f"Save directory {save_dir} already exists.")
116
+ else:
117
+ save_dir_path.mkdir(parents=True, exist_ok=True)
113
118
 
114
119
  # Read the dataframes if they are strings.
115
120
  if isinstance(train_data, str):
@@ -150,7 +155,6 @@ def train(
150
155
  eval_strategy="epoch",
151
156
  save_strategy="epoch",
152
157
  logging_strategy="epoch",
153
- load_best_model_at_end=True,
154
158
  save_total_limit=1,
155
159
  )
156
160
 
@@ -236,6 +240,11 @@ def train(
236
240
  metrics["mean_dice"] = float(np.mean(list(metrics.values())))
237
241
  return metrics
238
242
 
243
+ if rank == 0:
244
+ callbacks = [MetricsLoggerCallback, BestModelCallback(str(save_dir))]
245
+ else:
246
+ callbacks = [MetricsLoggerCallback]
247
+
239
248
  # Trainer
240
249
  trainer = Trainer(
241
250
  model=model,
@@ -243,73 +252,59 @@ def train(
243
252
  train_dataset=train_dataset,
244
253
  eval_dataset=val_dataset,
245
254
  compute_metrics=compute_metrics,
246
- callbacks=[MetricsLoggerCallback], # Add the custom callback here
255
+ callbacks=callbacks,
247
256
  )
248
257
 
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")
257
-
258
- results = {"train": {}, "val": {}}
259
-
260
- for log in trainer.state.log_history[:-1]:
261
- epoch = log["epoch"]
258
+ if rank != 0:
259
+ # Train the model.
260
+ _ = trainer.train()
261
+ return None
262
+ else:
263
+ print("Starting training...")
264
+ _ = trainer.train()
265
+ print("Training completed successfully!")
262
266
 
263
- if epoch % 1:
264
- continue # only track end of each epoch
267
+ results = {"train": {}, "val": {}}
265
268
 
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"]
269
+ for log in trainer.state.log_history[:-1]:
270
+ epoch = log["epoch"]
271
271
 
272
- for k, v in log.items():
273
- if k.startswith("eval_"):
274
- k = k[5:]
272
+ if epoch % 1:
273
+ continue # only track end of each epoch
275
274
 
276
- if k not in data:
277
- data[k] = []
275
+ # Check if this is tracking train or validation.
276
+ if "eval_loss" in log:
277
+ data = results["val"]
278
+ else:
279
+ data = results["train"]
278
280
 
279
- data[k].append(v)
281
+ for k, v in log.items():
282
+ if k.startswith("eval_"):
283
+ k = k[5:]
280
284
 
281
- # For each of the datasets provided, get the metrics.
282
- model = trainer.model
285
+ if k not in data:
286
+ data[k] = []
283
287
 
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
- )
288
+ data[k].append(v)
294
289
 
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
- )
290
+ # For each of the datasets provided, get the metrics.
291
+ model = trainer.model
305
292
 
306
- if test_data is not None:
307
- print("Calculating test metrics...")
308
- results["test"] = {}
293
+ print("Calculating train metrics...")
294
+ results["train"]["metrics"] = per_class_dice_on_dataset(
295
+ model,
296
+ train_data,
297
+ label2id,
298
+ batch_size=batch_size,
299
+ device=device,
300
+ tile_size=tile_size,
301
+ tqdm_notebook=tqdm_notebook,
302
+ )
309
303
 
310
- results["test"]["metrics"] = per_class_dice_on_dataset(
304
+ print("Calculating val metrics...")
305
+ results["val"]["metrics"] = per_class_dice_on_dataset(
311
306
  model,
312
- test_data,
307
+ val_data,
313
308
  label2id,
314
309
  batch_size=batch_size,
315
310
  device=device,
@@ -317,4 +312,18 @@ def train(
317
312
  tqdm_notebook=tqdm_notebook,
318
313
  )
319
314
 
320
- return trainer, results
315
+ if test_data is not None:
316
+ print("Calculating test metrics...")
317
+ results["test"] = {}
318
+
319
+ results["test"]["metrics"] = per_class_dice_on_dataset(
320
+ model,
321
+ test_data,
322
+ label2id,
323
+ batch_size=batch_size,
324
+ device=device,
325
+ tile_size=tile_size,
326
+ tqdm_notebook=tqdm_notebook,
327
+ )
328
+
329
+ 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.dev1
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.19dev1"
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)