Repository: pytorch/ignite

Issue:
Rename Epoch to Iterations when using epoch_length with max_epochs=1
## 🚀 Feature

Per the discussion in https://github.com/pytorch/ignite/issues/1263, we'd like to ensure that the progress bar uses the name "Iterations" instead of "Epoch" when the `epoch_length` is passed to the trainer for `max_epochs=1`.

Discussion:
vfdev-5: @M3L6H we have to set up desc when it is used with tqdm bar. Currently, we have [here](https://github.com/pytorch/ignite/blob/2778893db943e9869c2ce266c740c1310bc3b9de/ignite/contrib/handlers/tqdm_logger.py#L52)
```python 
desc = self.tag 
```
which is None if user does not set any desc option while creating pbar. We need to modify it to 
```python
if self.tag is not None:
    desc = self.tag
elif engine.state.max_epochs > 1:
    desc = "Epoch"
else:
    desc = "Iteration"
```
I let you update your code with that in a more compact way.

M3L6H: @vfdev-5 This is the code I currently have implemented. It's slightly different from what you commented, because I did not see your comment until later, but it seems to be functioning fairly well

```python
state_dict = engine.state_dict()
max_epochs = state_dict["max_epochs"] if "max_epochs" in state_dict else None
default_desc = "Iterations" if max_epochs and max_epochs == 1 else "Epoch"

desc = self.tag or default_desc
```

I had to modify a few tests which set `max_epochs` to 1. However one test is failing, and I'm not sure if it is the test's fault or my code's fault. The test in question is this one

```python
capsys = <_pytest.capture.CaptureFixture object at 0x7f530cc55668>

    def test_tqdm_logger_epoch_length(capsys):
        loader = list(range(100))
        engine = Engine(update_fn)
        pbar = ProgressBar(persist=True)
        pbar.attach(engine)
        engine.run(loader, epoch_length=50)
    
        captured = capsys.readouterr()
        err = captured.err.split("\r")
        err = list(map(lambda x: x.strip(), err))
        err = list(filter(None, err))
        actual = err[-1]
        expected = "Epoch: [50/50] 100%|██████████ [00:00<00:00]"
>       assert actual == expected
E       AssertionError: assert 'Iterations: [50/50] 100%|██████████ [00:00<00:00]' == 'Epoch: [50/50] 100%|██████████ [00:00<00:00]'
E         - Epoch: [50/50] 100%|██████████ [00:00<00:00]
E         ? ^^ ^^
E         + Iterations: [50/50] 100%|██████████ [00:00<00:00]
E         ? ^^^^^^^ ^^

tests/ignite/contrib/handlers/test_tqdm_logger.py:454: AssertionError
```

My problem is that I don't see anywhere in this test where `max_epochs` is set to 1, yet my code seems to think it is (since "iterations" got printed). My question is whether there is some default behavior setting `max_epochs` to 1 when it isn't explicitly passed, or what am I missing?
vfdev-5: When you call `engine.run(loader, epoch_length=50)` then `max_epochs` is set by default as 1, see the API here : https://pytorch.org/ignite/engine.html#ignite.engine.engine.Engine.run
M3L6H: Sounds good. I will update the test and push again
vfdev-5: Could you also update the docstring of ProgressBar. This part : 
```
By default, progress bar description displays "Epoch [5/10]" where 5 is the current epoch and 10 is the
number of epochs. If tqdm_kwargs defines `desc`, e.g. "Predictions", than the description is number of epochs.
```
should be adapted and say when we display "Iteration" instead of "Epoch". Thanks !
M3L6H: I have updated the docstring as requested.

Patch:
diff --git a/ignite/contrib/handlers/tqdm_logger.py b/ignite/contrib/handlers/tqdm_logger.py
index 9c5602ac..f1a4d58d 100644
--- a/ignite/contrib/handlers/tqdm_logger.py
+++ b/ignite/contrib/handlers/tqdm_logger.py
@@ -49,7 +49,10 @@ class _OutputHandler(BaseOutputHandler):
         if logger.pbar is None:
             logger._reset(pbar_total=pbar_total)
 
-        desc = self.tag
+        max_epochs = engine.state.max_epochs
+        default_desc = "Iteration" if max_epochs == 1 else "Epoch"
+
+        desc = self.tag or default_desc
         max_num_of_closing_events = self.get_max_number_events(self.closing_event_name, engine)
         if max_num_of_closing_events > 1:
             global_step = engine.state.get_event_attrib_value(self.closing_event_name)
@@ -95,7 +98,8 @@ class ProgressBar(BaseLogger):
             formatting, see `tqdm docs <https://tqdm.github.io/docs/tqdm/>`_.
         **tqdm_kwargs: kwargs passed to tqdm progress bar.
             By default, progress bar description displays "Epoch [5/10]" where 5 is the current epoch and 10 is the
-            number of epochs. If tqdm_kwargs defines `desc`, e.g. "Predictions", than the description is
+            number of epochs; however, if ``max_epochs`` are set to 1, the progress bar instead displays
+            "Iteration: [5/10]". If tqdm_kwargs defines `desc`, e.g. "Predictions", than the description is
             "Predictions [5/10]" if number of epochs is more than one otherwise it is simply "Predictions".
 
     Examples:
@@ -250,7 +254,7 @@ class ProgressBar(BaseLogger):
         Note: accepted output value types are numbers, 0d and 1d torch tensors and strings
 
         """
-        desc = self.tqdm_kwargs.get("desc", "Epoch")
+        desc = self.tqdm_kwargs.get("desc", None)
 
         if event_name not in engine._allowed_events:
             raise ValueError("Logging event {} is not in allowed events for this engine".format(event_name.name))


Test patch:
diff --git a/tests/ignite/contrib/handlers/test_tqdm_logger.py b/tests/ignite/contrib/handlers/test_tqdm_logger.py
index 3f4097c1..7bdb6b6f 100644
--- a/tests/ignite/contrib/handlers/test_tqdm_logger.py
+++ b/tests/ignite/contrib/handlers/test_tqdm_logger.py
@@ -141,7 +141,7 @@ def test_pbar_with_metric(capsys):
     err = list(map(lambda x: x.strip(), err))
     err = list(filter(None, err))
     actual = err[-1]
-    expected = "Epoch: [1/2]  50%|█████     , batchloss=0.5 [00:00<00:00]"
+    expected = "Iteration: [1/2]  50%|█████     , batchloss=0.5 [00:00<00:00]"
     assert actual == expected
 
 
@@ -172,7 +172,7 @@ def test_pbar_with_all_metric(capsys):
     err = list(map(lambda x: x.strip(), err))
     err = list(filter(None, err))
     actual = err[-1]
-    expected = "Epoch: [1/2]  50%|█████     , another batchloss=1.5, batchloss=0.5 [00:00<00:00]"
+    expected = "Iteration: [1/2]  50%|█████     , another batchloss=1.5, batchloss=0.5 [00:00<00:00]"
     assert actual == expected
 
 
@@ -349,6 +349,24 @@ def test_pbar_on_epochs(capsys):
     assert actual == expected
 
 
+def test_pbar_with_max_epochs_set_to_one(capsys):
+    n_epochs = 1
+    loader = [1, 2]
+    engine = Engine(update_fn)
+
+    pbar = ProgressBar()
+    pbar.attach(engine, ["a"])
+
+    engine.run(loader, max_epochs=n_epochs)
+
+    captured = capsys.readouterr()
+    err = captured.err.split("\r")
+    err = list(map(lambda x: x.strip(), err))
+    err = list(filter(None, err))
+    expected = "Iteration: [1/2]  50%|█████     , a=1 [00:00<00:00]"
+    assert err[-1] == expected
+
+
 def test_pbar_wrong_events_order():
 
     engine = Engine(update_fn)
@@ -416,7 +434,7 @@ def test_pbar_on_callable_events(capsys):
     err = list(map(lambda x: x.strip(), err))
     err = list(filter(None, err))
     actual = err[-1]
-    expected = "Epoch: [90/100]  90%|█████████  [00:00<00:00]"
+    expected = "Iteration: [90/100]  90%|█████████  [00:00<00:00]"
     assert actual == expected
 
 
@@ -432,7 +450,7 @@ def test_tqdm_logger_epoch_length(capsys):
     err = list(map(lambda x: x.strip(), err))
     err = list(filter(None, err))
     actual = err[-1]
-    expected = "Epoch: [50/50] 100%|██████████ [00:00<00:00]"
+    expected = "Iteration: [50/50] 100%|██████████ [00:00<00:00]"
     assert actual == expected