Repository: pytorch/ignite

Issue:
Error in tbptt
Hello,
I left a typo in [create_supervised_tbptt_trainer](https://github.com/pytorch/ignite/blob/8adab93849194d8ca0fe4d152a34f4269efbbc0f/ignite/contrib/engines.py#L43), sorry about it. On [this line](https://github.com/pytorch/ignite/blob/8adab93849194d8ca0fe4d152a34f4269efbbc0f/ignite/contrib/engines.py#L99) it should of course be 
```python
y_pred_t, hidden = model(x_t, hidden)
```

I'll do a PR to fix it, but of course I need to improve the tests for that function because this was not detected.


Discussion:
@AntoinePrv thanks! Yes, definitely we need to improve the test part.

Resolving patch:
diff --git a/ignite/contrib/engines/__init__.py b/ignite/contrib/engines/__init__.py
new file mode 100644
--- /dev/null
+++ b/ignite/contrib/engines/__init__.py
@@ -0,0 +1,7 @@
+# coding: utf-8
+
+from ignite.contrib.engines.tbptt import create_supervised_tbptt_trainer
+from ignite.contrib.engines.tbptt import Tbptt_Events
+
+
+__all__ = ["create_supervised_tbptt_trainer", "Tbptt_Events"]
diff --git a/ignite/contrib/engines.py b/ignite/contrib/engines/tbptt.py
similarity index 98%
rename from ignite/contrib/engines.py
rename to ignite/contrib/engines/tbptt.py
--- a/ignite/contrib/engines.py
+++ b/ignite/contrib/engines/tbptt.py
@@ -96,7 +96,7 @@ def _update(engine, batch):
                 y_pred_t, hidden = model(x_t)
             else:
                 hidden = _detach_hidden(hidden)
-                y_pred_t, hidden = model(x_t)
+                y_pred_t, hidden = model(x_t, hidden)
             loss_t = loss_fn(y_pred_t, y_t)
             loss_t.backward()
             optimizer.step()


Test patch:
diff --git a/tests/ignite/contrib/engines/__init__.py b/tests/ignite/contrib/engines/__init__.py
new file mode 100644
diff --git a/tests/ignite/contrib/test_engines.py b/tests/ignite/contrib/engines/test_tbptt.py
similarity index 61%
rename from tests/ignite/contrib/test_engines.py
rename to tests/ignite/contrib/engines/test_tbptt.py
--- a/tests/ignite/contrib/test_engines.py
+++ b/tests/ignite/contrib/engines/test_tbptt.py
@@ -5,10 +5,10 @@
 import torch.optim as optim
 import torch.nn.functional as F
 import pytest
-from mock import MagicMock
+import mock
 
 from ignite.contrib.engines import create_supervised_tbptt_trainer, Tbptt_Events
-from ignite.contrib.engines import _detach_hidden
+from ignite.contrib.engines.tbptt import _detach_hidden
 
 
 def test_detach_hidden_RNN():
@@ -43,30 +43,27 @@ def test_detach_hidden_raise():
         _detach_hidden(0)
 
 
-def _test_create_supervised_tbptt_trainer(device):
-    # Defining dummy recurrent model with zero weights
-    model = nn.RNN(1, 1, bias=False)
-    for p in model.parameters():
-        p.data.zero_()
+@mock.patch("ignite.contrib.engines.tbptt._detach_hidden")
+def test_create_supervised_tbptt_trainer_callcounts(mock_detach_hidden):
+    # Mocking objects
+    model = mock.MagicMock()
+    # Necessary to unpack output
+    model.return_value = (1, 1)
+    optimizer = mock.MagicMock()
+    loss = mock.MagicMock()
 
-    # Defning optimizer and trainer
-    optimizer = optim.SGD(model.parameters(), 1)
     trainer = create_supervised_tbptt_trainer(
-        model,
-        optimizer,
-        F.mse_loss,
-        tbtt_step=2,
-        device=device
+        model, optimizer, loss, tbtt_step=2
     )
 
     # Adding two mock handles to the trainer to monitor that TBPTT events are
     # called correctly
-    handle_started = MagicMock()
+    handle_started = mock.MagicMock()
     trainer.add_event_handler(
         Tbptt_Events.TIME_ITERATION_STARTED,
         handle_started
     )
-    handle_completed = MagicMock()
+    handle_completed = mock.MagicMock()
     trainer.add_event_handler(
         Tbptt_Events.TIME_ITERATION_COMPLETED,
         handle_completed
@@ -74,7 +71,7 @@ def _test_create_supervised_tbptt_trainer(device):
 
     # Fake data
     X = torch.ones(6, 2, 1)
-    y = X = torch.ones(6, 2, 1)
+    y = torch.ones(6, 2, 1)
     data = [(X, y)]
 
     # Running trainer
@@ -83,11 +80,59 @@ def _test_create_supervised_tbptt_trainer(device):
     # Verifications
     assert handle_started.call_count == 3
     assert handle_completed.call_count == 3
+    assert mock_detach_hidden.call_count == 2
+    assert model.call_count == 3
+    assert loss.call_count == 3
+    assert optimizer.zero_grad.call_count == 3
+    assert optimizer.step.call_count == 3
+    n_args_tuple = tuple(len(args) for args, kwargs in model.call_args_list)
+    assert n_args_tuple == (1, 2, 2)
+
+
+def _test_create_supervised_tbptt_trainer(device):
+    # Defining dummy recurrent model with zero weights
+    model = nn.RNN(1, 1, bias=False)
+    for p in model.parameters():
+        p.data.zero_()
+
+    # Set some mock on forward to monitor
+    forward_mock = mock.MagicMock()
+    forward_mock.return_value = None
+    model.register_forward_hook(forward_mock)
+
+    # Defning optimizer and trainer
+    optimizer = optim.SGD(model.parameters(), 1)
+    trainer = create_supervised_tbptt_trainer(
+        model,
+        optimizer,
+        F.mse_loss,
+        tbtt_step=2,
+        device=device
+    )
+
+    # Fake data
+    X = torch.ones(6, 2, 1)
+    y = torch.ones(6, 2, 1)
+    data = [(X, y)]
+
+    # Running trainer
+    trainer.run(data)
 
     # If tbptt is not use (one gradient update), the hidden to hidden weight
     # should stay zero
     assert not model.weight_hh_l0.item() == pytest.approx(0)
 
+    # Cheking forward calls
+    assert forward_mock.call_count == 3
+    for i in range(3):
+        inputs = forward_mock.call_args_list[i][0][1]
+        if i == 0:
+            assert len(inputs) == 1
+        else:
+            assert len(inputs) == 2
+            x, h = inputs
+            assert h.is_leaf
+
 
 def test_create_supervised_tbptt_trainer_with_cpu():
     _test_create_supervised_tbptt_trainer("cpu")