Repository: pytorch/ignite

Issue:
Allow the duration parameter to be a np.int in contrib.handler
I had some issue with concat scheduler using numpy integer for durations instead of int:
<img width="1145" alt="image 2019-02-20 at 1 54 36 pm" src="https://user-images.githubusercontent.com/4520383/53116736-58180580-3517-11e9-9634-6dcacd0c90c4.png">
this seems pretty restrictive when you could just do something along theses lines:
```
all([isinstance(t, (int,np.integer)) for t in durations])
```
Happy to submit a PR if you think it makes sense.
Allow the duration parameter to be a np.int in contrib.handler
I had some issue with concat scheduler using numpy integer for durations instead of int:
<img width="1145" alt="image 2019-02-20 at 1 54 36 pm" src="https://user-images.githubusercontent.com/4520383/53116736-58180580-3517-11e9-9634-6dcacd0c90c4.png">
this seems pretty restrictive when you could just do something along theses lines:
```
all([isinstance(t, (int,np.integer)) for t in durations])
```
Happy to submit a PR if you think it makes sense.


Discussion:
@zippeurfou can you post the code that fails, please.
Maybe, it would be better to compare with `numbers.Integral`:
```python
import numbers

a = np.array([0, 1, 2, 3])
print(isinstance(a[0], int), isinstance(a[0], numbers.Integral))
> False, True
```
Sure:
```
import numpy as np
import torch
optimizer = torch.optim.Adam([tensor], betas=(0.9, 0.99))
scheduler = LinearCyclicalScheduler(optimizer, 'lr', 0.01,0.3,cycle_size=10) 
#this works
sc = ConcatScheduler(schedulers=[scheduler,scheduler],durations=[30,])
#this fails
sc = ConcatScheduler(schedulers=[scheduler,scheduler],durations=[np.floor(30).astype(np.int),])
```
Above example looks rather artificial :)
Is it practical to set `durations` as a numpy array?
The actual practical example is when I was calculating the duration by doing something like this:
`np.floor(steps_per_epoch/2)`.I could have used the math library for it but there are case where you want to define your cycle_size, duration programmatically.
This is definitely a minor issues. 
@zippeurfou I see, it makes sense. Even if we can probably set it like `steps_per_epoch // 2` (integral division).
Anyway, we can replace all `isinstance(a, int)` by `isinstance(a, numbers.Integral)` to be more safe.
So, if you would like to send a PR, you're welcome :)

@zippeurfou can you post the code that fails, please.
Maybe, it would be better to compare with `numbers.Integral`:
```python
import numbers

a = np.array([0, 1, 2, 3])
print(isinstance(a[0], int), isinstance(a[0], numbers.Integral))
> False, True
```
Sure:
```
import numpy as np
import torch
optimizer = torch.optim.Adam([tensor], betas=(0.9, 0.99))
scheduler = LinearCyclicalScheduler(optimizer, 'lr', 0.01,0.3,cycle_size=10) 
#this works
sc = ConcatScheduler(schedulers=[scheduler,scheduler],durations=[30,])
#this fails
sc = ConcatScheduler(schedulers=[scheduler,scheduler],durations=[np.floor(30).astype(np.int),])
```
Above example looks rather artificial :)
Is it practical to set `durations` as a numpy array?
The actual practical example is when I was calculating the duration by doing something like this:
`np.floor(steps_per_epoch/2)`.I could have used the math library for it but there are case where you want to define your cycle_size, duration programmatically.
This is definitely a minor issues. 
@zippeurfou I see, it makes sense. Even if we can probably set it like `steps_per_epoch // 2` (integral division).
Anyway, we can replace all `isinstance(a, int)` by `isinstance(a, numbers.Integral)` to be more safe.
So, if you would like to send a PR, you're welcome :)


Resolving patch:
diff --git a/ignite/contrib/handlers/param_scheduler.py b/ignite/contrib/handlers/param_scheduler.py
--- a/ignite/contrib/handlers/param_scheduler.py
+++ b/ignite/contrib/handlers/param_scheduler.py
@@ -3,6 +3,7 @@
 from copy import deepcopy
 
 import math
+import numbers
 
 from abc import ABCMeta, abstractmethod
 from ignite._six import with_metaclass
@@ -312,7 +313,7 @@ def __init__(self, schedulers, durations, save_history=False):
                              "but given {}".format(schedulers))
 
         if not isinstance(durations, Sequence) or \
-                not all([isinstance(t, int) for t in durations]):
+                not all([isinstance(t, numbers.Integral) for t in durations]):
             raise ValueError("Argument durations should be list/tuple of integers, "
                              "but given {}".format(durations))
 
@@ -594,7 +595,7 @@ def __init__(self, optimizer, param_name, milestones_values, save_history=False)
         for pair in milestones_values:
             if not isinstance(pair, Sequence) or len(pair) != 2:
                 raise ValueError("Argument milestones_values should be a list of pairs (milestone, param_value)")
-            if not isinstance(pair[0], int):
+            if not isinstance(pair[0], numbers.Integral):
                 raise ValueError("Value of a milestone should be integer, but given {}".format(type(pair[0])))
             if len(milestones) > 0 and pair[0] < milestones[-1]:
                 raise ValueError("Milestones should be increasing integers, but given {} is smaller "


Test patch:
diff --git a/tests/ignite/contrib/handlers/test_param_scheduler.py b/tests/ignite/contrib/handlers/test_param_scheduler.py
--- a/tests/ignite/contrib/handlers/test_param_scheduler.py
+++ b/tests/ignite/contrib/handlers/test_param_scheduler.py
@@ -1,5 +1,7 @@
 import pytest
 
+import numpy as np
+
 import torch
 
 from ignite.engine import Engine, Events
@@ -133,44 +135,51 @@ def test_concat_scheduler():
     tensor = torch.zeros([1], requires_grad=True)
     optimizer = torch.optim.SGD([tensor], lr=0)
 
-    scheduler_1 = LinearCyclicalScheduler(optimizer, "lr", start_value=1.0, end_value=0.0, cycle_size=10)
-    scheduler_2 = CosineAnnealingScheduler(optimizer, "lr", start_value=0.0, end_value=1.0, cycle_size=10)
-    durations = [10, ]
+    def _test(duration_vals_as_np_int):
+        scheduler_1 = LinearCyclicalScheduler(optimizer, "lr", start_value=1.0, end_value=0.0, cycle_size=10)
+        scheduler_2 = CosineAnnealingScheduler(optimizer, "lr", start_value=0.0, end_value=1.0, cycle_size=10)
 
-    concat_scheduler = ConcatScheduler(schedulers=[scheduler_1, scheduler_2],
-                                       durations=durations, save_history=True)
+        durations = [10, ]
+        if duration_vals_as_np_int:
+            durations = [np.int64(t) for t in durations]
 
-    data = [0] * 10
-    max_epochs = 2
-    simulated_values = ConcatScheduler.simulate_values(num_events=len(data) * max_epochs,
-                                                       schedulers=[scheduler_1, scheduler_2],
-                                                       durations=durations)
+        concat_scheduler = ConcatScheduler(schedulers=[scheduler_1, scheduler_2],
+                                           durations=durations, save_history=True)
 
-    lrs = []
+        data = [0] * 10
+        max_epochs = 2
+        simulated_values = ConcatScheduler.simulate_values(num_events=len(data) * max_epochs,
+                                                           schedulers=[scheduler_1, scheduler_2],
+                                                           durations=durations)
 
-    def save_lr(engine):
-        lrs.append(optimizer.param_groups[0]['lr'])
+        lrs = []
 
-    trainer = Engine(lambda engine, batch: None)
-    trainer.add_event_handler(Events.ITERATION_STARTED, concat_scheduler)
-    trainer.add_event_handler(Events.ITERATION_COMPLETED, save_lr)
-    trainer.run(data, max_epochs=max_epochs)
+        def save_lr(engine):
+            lrs.append(optimizer.param_groups[0]['lr'])
 
-    assert lrs == list(map(pytest.approx, [
-        # Cycle 1 of the LinearCyclicalScheduler
-        1.0, 0.8, 0.6, 0.4, 0.2,
-        0.0, 0.2, 0.4, 0.6, 0.8,
-        # Cycle 1 of the CosineAnnealingScheduler
-        0.0, 0.02447174185242318, 0.09549150281252627, 0.20610737385376332, 0.3454915028125263,
-        0.5, 0.6545084971874737, 0.7938926261462365, 0.9045084971874737, 0.9755282581475768,
-    ]))
+        trainer = Engine(lambda engine, batch: None)
+        trainer.add_event_handler(Events.ITERATION_STARTED, concat_scheduler)
+        trainer.add_event_handler(Events.ITERATION_COMPLETED, save_lr)
+        trainer.run(data, max_epochs=max_epochs)
 
-    state_lrs = trainer.state.param_history['lr']
-    assert len(state_lrs) == len(lrs)
-    # Unpack singleton lists
-    assert [group[0] for group in state_lrs] == lrs
+        assert lrs == list(map(pytest.approx, [
+            # Cycle 1 of the LinearCyclicalScheduler
+            1.0, 0.8, 0.6, 0.4, 0.2,
+            0.0, 0.2, 0.4, 0.6, 0.8,
+            # Cycle 1 of the CosineAnnealingScheduler
+            0.0, 0.02447174185242318, 0.09549150281252627, 0.20610737385376332, 0.3454915028125263,
+            0.5, 0.6545084971874737, 0.7938926261462365, 0.9045084971874737, 0.9755282581475768,
+        ]))
 
-    assert lrs == pytest.approx([v for i, v in simulated_values])
+        state_lrs = trainer.state.param_history['lr']
+        assert len(state_lrs) == len(lrs)
+        # Unpack singleton lists
+        assert [group[0] for group in state_lrs] == lrs
+
+        assert lrs == pytest.approx([v for i, v in simulated_values])
+
+    _test(duration_vals_as_np_int=False)
+    _test(duration_vals_as_np_int=True)
 
 
 def test_concat_scheduler_3_schedulers():
@@ -338,33 +347,42 @@ def test_piecewiselinear_asserts():
 
 
 def test_piecewiselinear():
-    tensor = torch.zeros([1], requires_grad=True)
-    optimizer = torch.optim.SGD([tensor], lr=0)
 
-    scheduler = PiecewiseLinear(optimizer, 'lr',
-                                milestones_values=[(5, 0.5),
-                                                   (15, 1.0),
-                                                   (25, 0.0),
-                                                   (35, 1.0),
-                                                   (40, 0.5)])
-    lrs = []
-
-    def save_lr(engine):
-        lrs.append(optimizer.param_groups[0]['lr'])
+    def _test(milestones_as_np_int):
+        tensor = torch.zeros([1], requires_grad=True)
+        optimizer = torch.optim.SGD([tensor], lr=0)
+
+        milestones_values = [(5, 0.5),
+                             (15, 1.0),
+                             (25, 0.0),
+                             (35, 1.0),
+                             (40, 0.5)]
+        if milestones_as_np_int:
+            milestones_values = [(np.int64(t), v) for t, v in milestones_values]
+
+        scheduler = PiecewiseLinear(optimizer, 'lr',
+                                    milestones_values=milestones_values)
+        lrs = []
 
-    trainer = Engine(lambda engine, batch: None)
-    trainer.add_event_handler(Events.ITERATION_STARTED, scheduler)
-    trainer.add_event_handler(Events.ITERATION_COMPLETED, save_lr)
-    trainer.run([0] * 25, max_epochs=2)
+        def save_lr(engine):
+            lrs.append(optimizer.param_groups[0]['lr'])
 
-    assert lrs == list(map(pytest.approx, [
-        0.5, 0.5, 0.5, 0.5, 0.5,
-        0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95,
-        1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1,
-        0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9,
-        1.0, 0.9, 0.8, 0.7, 0.6,
-        0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5
-    ]))
+        trainer = Engine(lambda engine, batch: None)
+        trainer.add_event_handler(Events.ITERATION_STARTED, scheduler)
+        trainer.add_event_handler(Events.ITERATION_COMPLETED, save_lr)
+        trainer.run([0] * 25, max_epochs=2)
+
+        assert lrs == list(map(pytest.approx, [
+            0.5, 0.5, 0.5, 0.5, 0.5,
+            0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95,
+            1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1,
+            0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9,
+            1.0, 0.9, 0.8, 0.7, 0.6,
+            0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5
+        ]))
+
+    _test(milestones_as_np_int=True)
+    _test(milestones_as_np_int=False)
 
 
 def test_simulate_values():