Add_event_handler arguments not passing correctly?

pytorchnewbie: <p>Hi!</p>
<p>So when I do:</p>
<pre><code class="lang-auto">def log_engine_output(
    engine: Engine,
    fields: Dict[LOG_OP, Union[List[str], List[VisPlot], List[VisImg]]],
    epoch_num=None,
) -&gt; None:
    """Log numerical fields in the engine output dictionary to stdout"""

    print(epoch_num)

    for mode in fields.keys():
        if (
            mode is LOG_OP.NUMBER_TO_VISDOM
            or mode is LOG_OP.VECTOR_TO_VISDOM
            or mode is LOG_OP.IMAGE_TO_VISDOM
        ):
            mode(
                engine,
                engine.state.vis,
                fields[mode],
                engine_attr="output",
                epoch_num=epoch_num,
            )
        else:
            mode(engine, fields[mode], engine_attr="output")


trainer.add_event_handler(
            Events.EPOCH_COMPLETED(every=1),
            log_engine_output,
            {
                # Log fields as message in logfile
                LOG_OP.LOG_MESSAGE: ["loss"],
                # Log fields as separate data files
                LOG_OP.SAVE_IN_DATA_FILE: ["loss"],
                # Plot fields to Visdom
                LOG_OP.NUMBER_TO_VISDOM: [
                    # First plot, key is "p1"
                    VisPlot(
                        "loss",
                        plot_key="p1",
                        split="nll_1",
                        title="Train loss: ",
                        x_label="Iters",
                        y_label="nll",
                        env=cfg.env,
                    ),
                ],
            },
            trainer.state.epoch,
        )
</code></pre>
<p>The epoch_num that is printed is always 0. That’s not right since it prints every epoch - why would this happen?</p>
<p>I’m also confusing about the structure of doing trainer.add_event_handler() vs. the decorator of<br>
<span class="mention">@trainer.on</span>(Events.EPOCH_COMPLETED(every=1)).</p>
<p>I thought that the only difference between the decorator and the add_event_handler(some_function, variable1, variable2) was that with add_event_handler you could pass in the variables that are the arguments to the function (variable 1, variable 2), whereas with the decorator <span class="mention">@train</span> you can only pass in “engine” as your argument. Is this correct? If so, why isn’t passing the arguments working for me?</p>
<p>Also - what is the situation with add_event_handler automatically checking to see if the first argument it finds (after the function),is  “engine”? From the tutorial, “The first argument can be optionally engine, but not necessary.”.</p>
<pre><code class="lang-auto">def log_metrics(engine, title):
    print("Epoch: {} - {} accuracy: {:.2f}"
           .format(trainer.state.epoch, title, engine.state.metrics["acc"]))

@trainer.on(Events.EPOCH_COMPLETED)
def evaluate(trainer):
    with evaluator.add_event_handler(Events.COMPLETED, log_metrics, "train"):
        evaluator.run(train_loader)
</code></pre>
<p>Here it automatically passes “evaluator” as the “engine” argument to log_metrics. However, here:</p>
<pre><code class="lang-auto">trainer = Engine(update_model)

trainer.add_event_handler(Events.STARTED, lambda _: print("Start training"))
# or
@trainer.on(Events.STARTED)
def on_training_started(engine):
    print("Another message of start training")
# or even simpler, use only what you need !
@trainer.on(Events.STARTED)
def on_training_started():
    print("Another message of start training")

# attach handler with args, kwargs
mydata = [1, 2, 3, 4]

def on_training_ended(data):
    print("Training is ended. mydata={}".format(data))

trainer.add_event_handler(Events.COMPLETED, on_training_ended, mydata)
</code></pre>
<p>The first argument is not “engine”. Does this mean that somehow ignite checks the function passed into add_event_handler to see if the first argument is called “engine”? And if so, does the nomenclature have to be exactly called “engine” in order for it to pass the engine that add_event_handler is attached to to the function?</p>
<p>Thanks!</p>

vfdev-5: <p>Hi <a class="mention" href="/u/pytorchnewbie">@pytorchnewbie</a></p>
<blockquote>
<p>The epoch_num that is printed is always 0. That’s not right since it prints every epoch - why would this happen?</p>
</blockquote>
<p>This a correct behaviour as passing <code>trainer.state.epoch</code> it is passed as value and not reference. I agree that it would be nice to have it as reference, but I have no idea how to do that in python.</p>
<p>You need to fetch epoch inside the handler with <code>trainer.state.epoch</code>.</p>

vfdev-5: <blockquote>
<p>I’m also confusing about the structure of doing trainer.add_event_handler() vs. the decorator of<br>
<span class="mention">@trainer.on</span>(Events.EPOCH_COMPLETED(every=1)).</p>
</blockquote>
<p>Normally and the intention is that both should be the same and provide the same functionnality. If it is not the case, it is a bug.</p>
<p>You can also pass variable using the decorator:</p>
<pre data-code-wrap="python"><code class="lang-python">from ignite.engine import Engine, Events

trainer = Engine(lambda e, b: None)


@trainer.on(Events.ITERATION_COMPLETED, [0, 1, 2])
def print_my_data(_mydata):
    print("print_my_data : ", _mydata)
    
trainer.run([0])
</code></pre>

vfdev-5: <aside class="quote no-group" data-username="pytorchnewbie" data-post="1" data-topic="90002">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/letter_avatar_proxy/v4/letter/p/4da419/48.png" class="avatar"> pytorchnewbie:</div>
<blockquote>
<p>The first argument is not “engine”. Does this mean that somehow ignite checks the function passed into add_event_handler to see if the first argument is called “engine”? And if so, does the nomenclature have to be exactly called “engine” in order for it to pass the engine that add_event_handler is attached to to the function?</p>
</blockquote>
</aside>
<p>Yes, internally, we check the signature of attached function and see if we can bind the function with and without the Engine.<br>
There are following options possible:</p>
<ol>
<li>with engine only</li>
</ol>
<pre><code class="lang-auto">@trainer.on(event)
def handler(engine):
    assert engine == trainer
</code></pre>
<ol start="2">
<li>with engine and additional variables</li>
</ol>
<pre><code class="lang-auto">@trainer.on(event, v1, v2)
def handler(engine, v1, v2):
    assert engine == trainer
</code></pre>
<ol start="3">
<li>without engine</li>
</ol>
<pre><code class="lang-auto">@trainer.on(event)
def handler():
    pass
</code></pre>
<ol start="4">
<li>without engine, but with variables</li>
</ol>
<pre><code class="lang-auto">@trainer.on(event, v1, v2)
def handler(v1, v2):
    pass
</code></pre>
<blockquote>
<p>And if so, does the nomenclature have to be exactly called “engine” in order for it to pass the engine that add_event_handler is attached to to the function?</p>
</blockquote>
<p>Yes, if you would like to pass engine, it has to be the first argument.<br>
Hope it is more clear <img src="https://discuss.pytorch.org/images/emoji/apple/slight_smile.png?v=12" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p>

pytorchnewbie: <p>What does it mean passed by value and not reference? Value to me means at that moment what is the value of trainer.state.epoch, but isn’t this handler triggered completely anew every epoch? Therefore every epoch the value of trainer.state.epoch changes?</p>

vfdev-5: <blockquote>
<p>What does it mean passed by value and not reference?</p>
</blockquote>
<p>It means that the function gets the value of  <code>trainer.state.epoch</code>.</p>
<blockquote>
<p>Therefore every epoch the value of trainer.state.epoch changes?</p>
</blockquote>
<p>yes, it is updated internally by <code>Engine</code> during the run.</p>

pytorchnewbie: <p>To respond to : “Yes, internally, we check the signature of attached function and see if we can bind the function with and without the Engine.”</p>
<p>Strange… it doesn’t seem to work in this case:</p>
<p>def print_epoch(engine):<br>
print("The training epoch is: " + str(engine.state.epoch))</p>
<p>trainer.add_event_handler(<br>
Events.EPOCH_COMPLETED(every=1), print_epoch()<br>
)</p>
<p>The error is : “TypeError: print_epoch() missing 1 required positional argument: ‘engine’”</p>
<p>I get the same error if I do:</p>
<p>trainer.add_event_handler(<br>
Events.EPOCH_COMPLETED(every=1), print_epoch(), trainer<br>
)</p>

pytorchnewbie: <aside class="quote no-group" data-username="vfdev-5" data-post="6" data-topic="90002">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/user_avatar/discuss.pytorch.org/vfdev-5/48/3535_2.png" class="avatar"> vfdev-5:</div>
<blockquote>
<p>Therefore every epoch the value of trainer.state.epoch changes?</p>
</blockquote>
</aside>
<p>Exactly… so then normally the value of trainer.state.epoch shouldn’t be 0 every single time the event handler is triggered?</p>

vfdev-5: <p>It should be like that</p>
<pre><code class="lang-python">def print_epoch(engine):
    print("The training epoch is: " + str(engine.state.epoch))

trainer.add_event_handler(
    Events.EPOCH_COMPLETED(every=1), print_epoch
)
</code></pre>
<p><code>print_epoch</code> shouldn’t be called on the same line as <code>trainer.add_event_handler</code>.</p>

pytorchnewbie: <p>Ofcourse silly mistake I apologize</p>

vfdev-5: <aside class="quote no-group" data-username="pytorchnewbie" data-post="8" data-topic="90002">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/letter_avatar_proxy/v4/letter/p/4da419/48.png" class="avatar"> pytorchnewbie:</div>
<blockquote>
<p>Exactly… so then normally the value of trainer.state.epoch shouldn’t be 0 every single time the event handler is triggered?</p>
</blockquote>
</aside>
<p>Well, it is more like the following:</p>
<pre data-code-wrap="python"><code class="lang-python">def foo(x):
    print(x)
    

handlers = []

def add_handler(fn, args):
    handlers.append((fn, args))
    
    
def execute():
    for h, args in handlers:
        h(args)
        

epoch = 0

# args are registered with value 0
add_handler(foo, epoch)

execute()

# even if we update the variable, registered args are not changed
epoch = 1

execute()
</code></pre>