
.. DO NOT EDIT.
.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.
.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE:
.. "examples/examples_hpo/plot_hpo_text_classification_with_stopper.py"
.. LINE NUMBERS ARE GIVEN BELOW.

.. only:: html

    .. note::
        :class: sphx-glr-download-link-note

        :ref:`Go to the end <sphx_glr_download_examples_examples_hpo_plot_hpo_text_classification_with_stopper.py>`
        to download the full example code.

.. rst-class:: sphx-glr-example-title

.. _sphx_glr_examples_examples_hpo_plot_hpo_text_classification_with_stopper.py:


Hyperparameter Optimization for Text Classification with Early Discarding
=========================================================================

**Author(s)**: Romain Egele, Brett Eiffert.

 In this example, we will edit the DeepHyper Hyperparameter Search for Text Classification example to use the :mod:`deephyper.stopper` module. The Stopper class is 
 used to check if training per job/evaluation can be ended early and save run time if the stopper algorithm determines that
 no more training is needed. Read more about the Stopper class `here <https://deephyper.readthedocs.io/en/stable/_autosummary/deephyper.stopper.html>`_
 
**Reference**:
This example is based on materials from the Pytorch Documentation: `Text classification with the torchtext library <https://pytorch.org/tutorials/beginner/text_sentiment_ngrams_tutorial.html>`_

.. GENERATED FROM PYTHON SOURCE LINES 16-20

.. code-block:: bash

    %%bash
    pip install deephyper ray numpy==1.26.4 torch torchtext==0.17.2 torchdata==0.7.1 'portalocker>=2.0.0'

.. GENERATED FROM PYTHON SOURCE LINES 23-27

Imports
-------

All imports used in the tutorial are declared at the top of the file.

.. GENERATED FROM PYTHON SOURCE LINES 27-47

.. dropdown:: Code (Imports)

    .. code-block:: Python


        from deephyper.evaluator import RunningJob

        import ray
        import json
        from functools import partial

        import torch

        from torchtext.data.utils import get_tokenizer
        from torchtext.data.functional import to_map_style_dataset
        from torchtext.vocab import build_vocab_from_iterator
        from torchtext.datasets import AG_NEWS

        from torch.utils.data import DataLoader
        from torch.utils.data.dataset import random_split

        from torch import nn








.. GENERATED FROM PYTHON SOURCE LINES 48-51

.. note::
  The following can be used to detect if **CUDA** devices are available on the current host. Therefore, this notebook will automatically adapt the parallel execution based on the resources available locally. However, it will not be the case if many compute nodes are requested.


.. GENERATED FROM PYTHON SOURCE LINES 53-54

If GPU is available, this code will enabled the tutorial to use the GPU for pytorch operations.

.. GENERATED FROM PYTHON SOURCE LINES 55-60

.. dropdown:: Code (Code to check if using CPU or GPU)

    .. code-block:: Python


        is_gpu_available = torch.cuda.is_available()
        n_gpus = torch.cuda.device_count()








.. GENERATED FROM PYTHON SOURCE LINES 61-66

The dataset
-----------

The torchtext library provides a few raw dataset iterators, which yield the raw text strings. For example, the :code:`AG_NEWS` dataset iterators yield the raw data as a tuple of label and text. It has four labels (1 : World 2 : Sports 3 : Business 4 : Sci/Tec).


.. GENERATED FROM PYTHON SOURCE LINES 66-84

.. dropdown:: Code (Loading the data)

    .. code-block:: Python


        def load_data(train_ratio, fast=False):
            train_iter, test_iter = AG_NEWS()
            train_dataset = to_map_style_dataset(train_iter)
            test_dataset = to_map_style_dataset(test_iter)
            num_train = int(len(train_dataset) * train_ratio)
            split_train, split_valid = \
                random_split(train_dataset, [num_train, len(train_dataset) - num_train])
    
            ## downsample
            if fast:
                split_train, _ = random_split(split_train, [int(len(split_train)*.05), int(len(split_train)*.95)])
                split_valid, _ = random_split(split_valid, [int(len(split_valid)*.05), int(len(split_valid)*.95)])
                test_dataset, _ = random_split(test_dataset, [int(len(test_dataset)*.05), int(len(test_dataset)*.95)])

            return split_train, split_valid, test_dataset








.. GENERATED FROM PYTHON SOURCE LINES 85-93

Preprocessing pipelines and Batch generation
--------------------------------------------

Here is an example for typical NLP data processing with tokenizer and vocabulary. The first step is to build a vocabulary with the raw training dataset. Here we use built in
factory function :code:`build_vocab_from_iterator` which accepts iterator that yield list or iterator of tokens. Users can also pass any special symbols to be added to the
vocabulary.

The vocabulary block converts a list of tokens into integers.

.. GENERATED FROM PYTHON SOURCE LINES 95-99

.. code-block:: python

  vocab(['here', 'is', 'an', 'example'])
  >>> [475, 21, 30, 5286]

.. GENERATED FROM PYTHON SOURCE LINES 101-102

The text pipeline converts a text string into a list of integers based on the lookup table defined in the vocabulary. The label pipeline converts the label into integers. For example,

.. GENERATED FROM PYTHON SOURCE LINES 104-110

.. code-block:: python

  text_pipeline('here is the an example')
  >>> [475, 21, 2, 30, 5286]
  label_pipeline('10')
  >>> 9 

.. GENERATED FROM PYTHON SOURCE LINES 110-141

.. dropdown:: Code (Code to tokenize and build vocabulary for text processing)

    .. code-block:: Python


        train_iter = AG_NEWS(split='train')
        num_class = 4

        tokenizer = get_tokenizer('basic_english')

        def yield_tokens(data_iter):
            for _, text in data_iter:
                yield tokenizer(text)

        vocab = build_vocab_from_iterator(yield_tokens(train_iter), specials=["<unk>"])
        vocab.set_default_index(vocab["<unk>"])
        vocab_size = len(vocab)

        text_pipeline = lambda x: vocab(tokenizer(x))
        label_pipeline = lambda x: int(x) - 1


        def collate_batch(batch, device):
            label_list, text_list, offsets = [], [], [0]
            for (_label, _text) in batch:
                label_list.append(label_pipeline(_label))
                processed_text = torch.tensor(text_pipeline(_text), dtype=torch.int64)
                text_list.append(processed_text)
                offsets.append(processed_text.size(0))
            label_list = torch.tensor(label_list, dtype=torch.int64)
            offsets = torch.tensor(offsets[:-1]).cumsum(dim=0)
            text_list = torch.cat(text_list)
            return label_list.to(device), text_list.to(device), offsets.to(device)








.. GENERATED FROM PYTHON SOURCE LINES 142-144

.. note:: The :code:`collate_fn` function works on a batch of samples generated from :code:`DataLoader`. The input to :code:`collate_fn` is a batch of data with the batch size in :code:`DataLoader`, and :code:`collate_fn` processes them according to the data processing pipelines declared previously.


.. GENERATED FROM PYTHON SOURCE LINES 146-150

Define the model
----------------

The model is composed of the `nn.EmbeddingBag <https://pytorch.org/docs/stable/nn.html?highlight=embeddingbag#torch.nn.EmbeddingBag>`_ layer plus a linear layer for the classification purpose.

.. GENERATED FROM PYTHON SOURCE LINES 150-170

.. dropdown:: Code (Defining the Text Classification model)

    .. code-block:: Python


        class TextClassificationModel(nn.Module):

            def __init__(self, vocab_size, embed_dim, num_class):
                super().__init__()
                self.embedding = nn.EmbeddingBag(vocab_size, embed_dim, sparse=False)
                self.fc = nn.Linear(embed_dim, num_class)
                self.init_weights()

            def init_weights(self):
                initrange = 0.5
                self.embedding.weight.data.uniform_(-initrange, initrange)
                self.fc.weight.data.uniform_(-initrange, initrange)
                self.fc.bias.data.zero_()

            def forward(self, text, offsets):
                embedded = self.embedding(text, offsets)
                return self.fc(embedded)








.. GENERATED FROM PYTHON SOURCE LINES 171-173

Define functions to train the model and evaluate results.
---------------------------------------------------------

.. GENERATED FROM PYTHON SOURCE LINES 173-197

.. dropdown:: Code (Define the training and evaluation of the Text Classification model)

    .. code-block:: Python


        def train(model, criterion, optimizer, dataloader):
            model.train()

            for _, (label, text, offsets) in enumerate(dataloader):
                optimizer.zero_grad()
                predicted_label = model(text, offsets)
                loss = criterion(predicted_label, label)
                loss.backward()
                torch.nn.utils.clip_grad_norm_(model.parameters(), 0.1)
                optimizer.step()

        def evaluate(model, dataloader):
            model.eval()
            total_acc, total_count = 0, 0

            with torch.no_grad():
                for _, (label, text, offsets) in enumerate(dataloader):
                    predicted_label = model(text, offsets)
                    total_acc += (predicted_label.argmax(1) == label).sum().item()
                    total_count += label.size(0)
            return total_acc/total_count








.. GENERATED FROM PYTHON SOURCE LINES 198-213

Define the run-function
-----------------------

The run-function defines how the objective that we want to maximize is computed. It takes a :code:`config` dictionary as input and often returns a scalar value that we want to maximize. The :code:`config` contains a sample value of hyperparameters that we want to tune. In this example we will search for:

* :code:`num_epochs` (default value: :code:`10`)
* :code:`batch_size` (default value: :code:`64`)
* :code:`learning_rate` (default value: :code:`5`)

A hyperparameter value can be accessed easily in the dictionary through the corresponding key, for example :code:`config["units"]`.

When a Stopper is defined and set as a parameter in a search (below :code:`CBO()``), 
the run function must invoke methods :code:`job.record()` and :code:`job.stopped()`. 
:code:`job.record()` tells the Stopper which values to watch so it knows to stop 
and then :code:`job.stopped()` is a state the stopper uses to exit the specific job in the search earlier than expected.

.. GENERATED FROM PYTHON SOURCE LINES 213-245

.. code-block:: Python


    def get_run(train_ratio=0.95):
      def run(job: RunningJob):
        device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

        embed_dim = 64
        num_epochs = 100
    
        collate_fn = partial(collate_batch, device=device)
        split_train, split_valid, _ = load_data(train_ratio, fast=True) # set fast=false for longer running, more accurate example
        train_dataloader = DataLoader(split_train, batch_size=int(job["batch_size"]),
                                    shuffle=True, collate_fn=collate_fn)
        valid_dataloader = DataLoader(split_valid, batch_size=int(job["batch_size"]),
                                    shuffle=True, collate_fn=collate_fn)

        model = TextClassificationModel(vocab_size, int(embed_dim), num_class).to(device)
      
        criterion = torch.nn.CrossEntropyLoss()
        optimizer = torch.optim.SGD(model.parameters(), lr=job["learning_rate"])

        accu_list = []
        for i in range(1, num_epochs + 1):
            train(model, criterion, optimizer, train_dataloader)
            accu_list.append(evaluate(model, valid_dataloader))
            job.record(budget = i + 1, objective=evaluate(model, valid_dataloader))
            if job.stopped():
                break
    
        accu_test = evaluate(model, valid_dataloader)
        return {"objective": accu_test, "metadata": {"index_stopped": i, "accu_list": accu_list}}
      return run








.. GENERATED FROM PYTHON SOURCE LINES 246-247

We create two versions of :code:`run`, one quicker to evaluate for the search, with a small training dataset, and another one, for performance evaluation, which uses a normal training/validation ratio.

.. GENERATED FROM PYTHON SOURCE LINES 249-252

.. code-block:: Python

    quick_run = get_run(train_ratio=0.3)
    perf_run = get_run(train_ratio=0.95)








.. GENERATED FROM PYTHON SOURCE LINES 253-256

.. note:: The objective maximised by DeepHyper is the scalar value returned by the :code:`run`-function.

In this tutorial it corresponds to the validation accuracy of the model after training.

.. GENERATED FROM PYTHON SOURCE LINES 258-268

Define the Hyperparameter optimization problem
---------------------------------------------- 

Hyperparameter ranges are defined using the following syntax:

* Discrete integer ranges are generated from a tuple :code:`(lower: int, upper: int)`
* Continuous prarameters are generated from a tuple :code:`(lower: float, upper: float)`
* Categorical or nonordinal hyperparameter ranges can be given as a list of possible values :code:`[val1, val2, ...]`

We provide the default configuration of hyperparameters as a starting point of the problem.

.. GENERATED FROM PYTHON SOURCE LINES 270-280

.. code-block:: Python

    from deephyper.hpo import HpProblem

    problem = HpProblem()

    # Discrete and Real hyperparameters (sampled with log-uniform)
    problem.add_hyperparameter((8, 512, "log-uniform"), "batch_size", default_value=64)
    problem.add_hyperparameter((0.1, 10, "log-uniform"), "learning_rate", default_value=5)

    problem





.. rst-class:: sphx-glr-script-out

 .. code-block:: none


    Configuration space object:
      Hyperparameters:
        batch_size, Type: UniformInteger, Range: [8, 512], Default: 64, on log-scale
        learning_rate, Type: UniformFloat, Range: [0.1, 10.0], Default: 5.0, on log-scale




.. GENERATED FROM PYTHON SOURCE LINES 281-285

Evaluate a default configuration
--------------------------------

We evaluate the performance of the default set of hyperparameters provided in the Pytorch tutorial.

.. GENERATED FROM PYTHON SOURCE LINES 285-305

.. dropdown:: Code (Imports)

    .. code-block:: Python


        #We launch the Ray run-time and execute the `run` function
        #with the default configuration

        if is_gpu_available:
            if not(ray.is_initialized()):
                ray.init(num_cpus=n_gpus, num_gpus=n_gpus, log_to_driver=False)
    
            run_default = ray.remote(num_cpus=1, num_gpus=1)(perf_run)
            objective_default = ray.get(run_default.remote(RunningJob(parameters=problem.default_configuration)))
        else:
            if not(ray.is_initialized()):
                ray.init(num_cpus=1, log_to_driver=False)
            run_default = perf_run
            objective_default = run_default(RunningJob(parameters=problem.default_configuration))
            print(problem.default_configuration)

        print(f"Accuracy Default Configuration:  {objective_default["objective"]:.3f}")





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    2025-10-21 16:04:22,173 INFO worker.py:1843 -- Started a local Ray instance. View the dashboard at http://127.0.0.1:8265 
    {'batch_size': 64, 'learning_rate': 5.0}
    Accuracy Default Configuration:  0.887




.. GENERATED FROM PYTHON SOURCE LINES 306-312

Define the evaluator object
---------------------------

The :code:`Evaluator` object allows to change the parallelization backend used by DeepHyper.  
It is a standalone object which schedules the execution of remote tasks. All evaluators needs a :code:`run_function` to be instantiated.  
Then a keyword :code:`method` defines the backend (e.g., :code:`"ray"`) and the :code:`method_kwargs` corresponds to keyword arguments of this chosen :code:`method`.

.. GENERATED FROM PYTHON SOURCE LINES 314-317

.. code-block:: python

  evaluator = Evaluator.create(run_function, method, method_kwargs)

.. GENERATED FROM PYTHON SOURCE LINES 319-322

Once created the :code:`evaluator.num_workers` gives access to the number of available parallel workers.

Finally, to submit and collect tasks to the evaluator one just needs to use the following interface:

.. GENERATED FROM PYTHON SOURCE LINES 324-331

.. code-block:: python

 	configs = [...]
 	evaluator.submit(configs)
	...
	tasks_done = evaluator.get("BATCH", size=1) # For asynchronous
	tasks_done = evaluator.get("ALL") # For batch synchronous

.. GENERATED FROM PYTHON SOURCE LINES 333-334

.. warning:: Each `Evaluator` saves its own state, therefore it is crucial to create a new evaluator when launching a fresh search.

.. GENERATED FROM PYTHON SOURCE LINES 334-367

.. dropdown:: Code (Imports)

    .. code-block:: Python


        from deephyper.evaluator import Evaluator
        from deephyper.evaluator.callback import TqdmCallback


        def get_evaluator(run_function):
            # Default arguments for Ray: 1 worker and 1 worker per evaluation
            method_kwargs = {
                "num_cpus": 1, 
                "num_cpus_per_task": 1,
                "callbacks": [TqdmCallback()]
            }

            # If GPU devices are detected then it will create 'n_gpus' workers
            # and use 1 worker for each evaluation
            if is_gpu_available:
                method_kwargs["num_cpus"] = n_gpus
                method_kwargs["num_gpus"] = n_gpus
                method_kwargs["num_cpus_per_task"] = 1
                method_kwargs["num_gpus_per_task"] = 1

            evaluator = Evaluator.create(
                run_function, 
                method="ray", 
                method_kwargs=method_kwargs
            )
            print(f"Created new evaluator with {evaluator.num_workers} worker{'s' if evaluator.num_workers > 1 else ''} and config: {method_kwargs}", )
    
            return evaluator

        evaluator = get_evaluator(quick_run)





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    Created new evaluator with 1 worker and config: {'num_cpus': 1, 'num_cpus_per_task': 1, 'callbacks': [<deephyper.evaluator.callback.TqdmCallback object at 0x3a4eedd60>]}




.. GENERATED FROM PYTHON SOURCE LINES 368-374

Define and run the Centralized Bayesian Optimization search (CBO)
-----------------------------------------------------------------

We create the CBO using the :code:`problem` and :code:`evaluator` defined above.

A Stopper is also defined and passed as an argument to the CBO. This Stopper controls the :code:`job.observe()` and :code:`job.stopped()` functions.

.. GENERATED FROM PYTHON SOURCE LINES 376-379

.. code-block:: Python

    from deephyper.hpo import CBO
    from deephyper.stopper import SuccessiveHalvingStopper








.. GENERATED FROM PYTHON SOURCE LINES 380-381

Instantiate the search with the problem and a specific evaluator

.. GENERATED FROM PYTHON SOURCE LINES 381-384

.. code-block:: Python

    stopper = SuccessiveHalvingStopper(min_steps=1, max_steps=100)
    search = CBO(problem, stopper=stopper, log_dir="stopper-log-files")





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    Results file already exists, it will be renamed to /Users/rp5/Documents/DeepHyper/deephyper/examples/examples_hpo/stopper-log-files/results_20251021-160501.csv




.. GENERATED FROM PYTHON SOURCE LINES 385-390

.. note:: 
  All DeepHyper's search algorithm have two stopping criteria:
      * :code:`max_evals (int)`: Defines the maximum number of evaluations that we want to perform. Default to :code:`-1` for an infinite number.
      * :code:`timeout (int)`: Defines a time budget (in seconds) before stopping the search. Default to :code:`None` for an infinite time budget.


.. GENERATED FROM PYTHON SOURCE LINES 392-394

.. code-block:: Python

    results = search.search(evaluator, max_evals=30)





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

      0%|          | 0/30 [00:00<?, ?it/s]      3%|▎         | 1/30 [00:00<00:00, 5210.32it/s, failures=0, objective=0.729]      7%|▋         | 2/30 [01:00<14:06, 30.25s/it, failures=0, objective=0.729]        7%|▋         | 2/30 [01:00<14:06, 30.25s/it, failures=0, objective=0.787]     10%|█         | 3/30 [01:36<14:42, 32.70s/it, failures=0, objective=0.787]     10%|█         | 3/30 [01:36<14:42, 32.70s/it, failures=0, objective=0.808]     13%|█▎        | 4/30 [01:37<09:00, 20.79s/it, failures=0, objective=0.808]     13%|█▎        | 4/30 [01:37<09:00, 20.79s/it, failures=0, objective=0.808]     17%|█▋        | 5/30 [02:32<13:40, 32.83s/it, failures=0, objective=0.808]     17%|█▋        | 5/30 [02:32<13:40, 32.83s/it, failures=0, objective=0.81]      20%|██        | 6/30 [02:34<08:56, 22.34s/it, failures=0, objective=0.81]     20%|██        | 6/30 [02:34<08:56, 22.34s/it, failures=0, objective=0.81]     23%|██▎       | 7/30 [02:36<06:04, 15.85s/it, failures=0, objective=0.81]     23%|██▎       | 7/30 [02:36<06:04, 15.85s/it, failures=0, objective=0.81]     27%|██▋       | 8/30 [02:37<04:08, 11.31s/it, failures=0, objective=0.81]     27%|██▋       | 8/30 [02:37<04:08, 11.31s/it, failures=0, objective=0.81]     30%|███       | 9/30 [02:40<02:59,  8.54s/it, failures=0, objective=0.81]     30%|███       | 9/30 [02:40<02:59,  8.54s/it, failures=0, objective=0.81]     33%|███▎      | 10/30 [02:41<02:06,  6.34s/it, failures=0, objective=0.81]     33%|███▎      | 10/30 [02:41<02:06,  6.34s/it, failures=0, objective=0.81]     37%|███▋      | 11/30 [02:42<01:30,  4.74s/it, failures=0, objective=0.81]     37%|███▋      | 11/30 [02:42<01:30,  4.74s/it, failures=0, objective=0.81]     40%|████      | 12/30 [02:43<01:05,  3.66s/it, failures=0, objective=0.81]     40%|████      | 12/30 [02:43<01:05,  3.66s/it, failures=0, objective=0.81]     43%|████▎     | 13/30 [02:45<00:50,  2.96s/it, failures=0, objective=0.81]     43%|████▎     | 13/30 [02:45<00:50,  2.96s/it, failures=0, objective=0.81]     47%|████▋     | 14/30 [02:46<00:40,  2.54s/it, failures=0, objective=0.81]     47%|████▋     | 14/30 [02:46<00:40,  2.54s/it, failures=0, objective=0.81]     50%|█████     | 15/30 [02:47<00:31,  2.12s/it, failures=0, objective=0.81]     50%|█████     | 15/30 [02:47<00:31,  2.12s/it, failures=0, objective=0.81]     53%|█████▎    | 16/30 [02:48<00:25,  1.84s/it, failures=0, objective=0.81]     53%|█████▎    | 16/30 [02:48<00:25,  1.84s/it, failures=0, objective=0.81]     57%|█████▋    | 17/30 [02:50<00:21,  1.64s/it, failures=0, objective=0.81]     57%|█████▋    | 17/30 [02:50<00:21,  1.64s/it, failures=0, objective=0.81]     60%|██████    | 18/30 [02:53<00:27,  2.25s/it, failures=0, objective=0.81]     60%|██████    | 18/30 [02:53<00:27,  2.25s/it, failures=0, objective=0.81]     63%|██████▎   | 19/30 [02:54<00:21,  1.91s/it, failures=0, objective=0.81]     63%|██████▎   | 19/30 [02:54<00:21,  1.91s/it, failures=0, objective=0.81]     67%|██████▋   | 20/30 [02:56<00:16,  1.68s/it, failures=0, objective=0.81]     67%|██████▋   | 20/30 [02:56<00:16,  1.68s/it, failures=0, objective=0.81]     70%|███████   | 21/30 [02:58<00:17,  1.96s/it, failures=0, objective=0.81]     70%|███████   | 21/30 [02:58<00:17,  1.96s/it, failures=0, objective=0.81]     73%|███████▎  | 22/30 [03:43<01:59, 14.89s/it, failures=0, objective=0.81]     73%|███████▎  | 22/30 [03:43<01:59, 14.89s/it, failures=0, objective=0.81]     77%|███████▋  | 23/30 [03:45<01:17, 11.05s/it, failures=0, objective=0.81]     77%|███████▋  | 23/30 [03:45<01:17, 11.05s/it, failures=0, objective=0.81]     80%|████████  | 24/30 [03:47<00:48,  8.16s/it, failures=0, objective=0.81]     80%|████████  | 24/30 [03:47<00:48,  8.16s/it, failures=0, objective=0.81]     83%|████████▎ | 25/30 [03:48<00:30,  6.15s/it, failures=0, objective=0.81]     83%|████████▎ | 25/30 [03:48<00:30,  6.15s/it, failures=0, objective=0.81]     87%|████████▋ | 26/30 [03:50<00:19,  4.89s/it, failures=0, objective=0.81]     87%|████████▋ | 26/30 [03:50<00:19,  4.89s/it, failures=0, objective=0.81]     90%|█████████ | 27/30 [03:51<00:11,  3.76s/it, failures=0, objective=0.81]     90%|█████████ | 27/30 [03:51<00:11,  3.76s/it, failures=0, objective=0.81]     93%|█████████▎| 28/30 [03:53<00:06,  3.09s/it, failures=0, objective=0.81]     93%|█████████▎| 28/30 [03:53<00:06,  3.09s/it, failures=0, objective=0.81]     97%|█████████▋| 29/30 [03:54<00:02,  2.53s/it, failures=0, objective=0.81]     97%|█████████▋| 29/30 [03:54<00:02,  2.53s/it, failures=0, objective=0.81]    100%|██████████| 30/30 [03:55<00:00,  2.19s/it, failures=0, objective=0.81]    100%|██████████| 30/30 [03:55<00:00,  2.19s/it, failures=0, objective=0.81]    100%|██████████| 30/30 [03:55<00:00,  7.86s/it, failures=0, objective=0.81]




.. GENERATED FROM PYTHON SOURCE LINES 395-401

The returned :code:`results` is a Pandas Dataframe where columns are hyperparameters and information stored by the evaluator:

* :code:`job_id` is a unique identifier corresponding to the order of creation of tasks
* :code:`objective` is the value returned by the run-function
* :code:`timestamp_submit` is the time (in seconds) when the hyperparameter configuration was submitted by the :code:`Evaluator` relative to the creation of the evaluator.
* :code:`timestamp_gather` is the time (in seconds) when the hyperparameter configuration was collected by the :code:`Evaluator` relative to the creation of the evaluator.

.. GENERATED FROM PYTHON SOURCE LINES 403-406

Show results. As shown by the :code:`index_stopped` column, even there were 100 epochs per job, not all jobs used all 100 epochs.
The power of a Stopper is shown as it can reduce runtime significantly as the Stopper and jobs become "smart" and decide to end early
because the Stopper algorithm determined it was unnecessary to move forward in the search for that job.

.. GENERATED FROM PYTHON SOURCE LINES 406-409

.. code-block:: Python

    results







.. raw:: html

    <div class="output_subarea output_html rendered_html output_result">
    <div>
    <style scoped>
        .dataframe tbody tr th:only-of-type {
            vertical-align: middle;
        }

        .dataframe tbody tr th {
            vertical-align: top;
        }

        .dataframe thead th {
            text-align: right;
        }
    </style>
    <table border="1" class="dataframe">
      <thead>
        <tr style="text-align: right;">
          <th></th>
          <th>p:batch_size</th>
          <th>p:learning_rate</th>
          <th>objective</th>
          <th>job_id</th>
          <th>job_status</th>
          <th>m:timestamp_submit</th>
          <th>m:index_stopped</th>
          <th>m:accu_list</th>
          <th>m:timestamp_gather</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th>0</th>
          <td>46</td>
          <td>0.282405</td>
          <td>0.729286</td>
          <td>0</td>
          <td>DONE</td>
          <td>1.081796</td>
          <td>99</td>
          <td>[0.27214285714285713, 0.29642857142857143, 0.3...</td>
          <td>30.520860</td>
        </tr>
        <tr>
          <th>1</th>
          <td>10</td>
          <td>0.263392</td>
          <td>0.786667</td>
          <td>1</td>
          <td>DONE</td>
          <td>30.545998</td>
          <td>99</td>
          <td>[0.2757142857142857, 0.30738095238095237, 0.34...</td>
          <td>91.025548</td>
        </tr>
        <tr>
          <th>2</th>
          <td>29</td>
          <td>0.620113</td>
          <td>0.807619</td>
          <td>2</td>
          <td>DONE</td>
          <td>91.036948</td>
          <td>99</td>
          <td>[0.3061904761904762, 0.3404761904761905, 0.369...</td>
          <td>127.161049</td>
        </tr>
        <tr>
          <th>3</th>
          <td>313</td>
          <td>1.944931</td>
          <td>0.275952</td>
          <td>3</td>
          <td>DONE</td>
          <td>127.173890</td>
          <td>1</td>
          <td>[0.27595238095238095]</td>
          <td>127.940275</td>
        </tr>
        <tr>
          <th>4</th>
          <td>14</td>
          <td>0.874725</td>
          <td>0.810000</td>
          <td>4</td>
          <td>DONE</td>
          <td>127.951657</td>
          <td>99</td>
          <td>[0.33214285714285713, 0.3995238095238095, 0.40...</td>
          <td>183.347690</td>
        </tr>
        <tr>
          <th>5</th>
          <td>29</td>
          <td>0.965256</td>
          <td>0.282857</td>
          <td>5</td>
          <td>DONE</td>
          <td>183.612584</td>
          <td>1</td>
          <td>[0.28285714285714286]</td>
          <td>184.573359</td>
        </tr>
        <tr>
          <th>6</th>
          <td>11</td>
          <td>1.211957</td>
          <td>0.377381</td>
          <td>6</td>
          <td>DONE</td>
          <td>184.835052</td>
          <td>2</td>
          <td>[0.3385714285714286, 0.3773809523809524]</td>
          <td>186.740075</td>
        </tr>
        <tr>
          <th>7</th>
          <td>20</td>
          <td>0.858054</td>
          <td>0.300952</td>
          <td>7</td>
          <td>DONE</td>
          <td>187.125586</td>
          <td>1</td>
          <td>[0.30095238095238097]</td>
          <td>188.157044</td>
        </tr>
        <tr>
          <th>8</th>
          <td>13</td>
          <td>0.854774</td>
          <td>0.336429</td>
          <td>8</td>
          <td>DONE</td>
          <td>188.556284</td>
          <td>2</td>
          <td>[0.31976190476190475, 0.3364285714285714]</td>
          <td>190.540544</td>
        </tr>
        <tr>
          <th>9</th>
          <td>14</td>
          <td>0.875480</td>
          <td>0.293333</td>
          <td>9</td>
          <td>DONE</td>
          <td>190.792234</td>
          <td>1</td>
          <td>[0.29333333333333333]</td>
          <td>191.928877</td>
        </tr>
        <tr>
          <th>10</th>
          <td>56</td>
          <td>0.575649</td>
          <td>0.285000</td>
          <td>10</td>
          <td>DONE</td>
          <td>192.176605</td>
          <td>1</td>
          <td>[0.285]</td>
          <td>193.011374</td>
        </tr>
        <tr>
          <th>11</th>
          <td>29</td>
          <td>0.623690</td>
          <td>0.288095</td>
          <td>11</td>
          <td>DONE</td>
          <td>193.268442</td>
          <td>1</td>
          <td>[0.28809523809523807]</td>
          <td>194.197651</td>
        </tr>
        <tr>
          <th>12</th>
          <td>15</td>
          <td>0.106875</td>
          <td>0.270952</td>
          <td>12</td>
          <td>DONE</td>
          <td>194.455415</td>
          <td>1</td>
          <td>[0.27095238095238094]</td>
          <td>195.550734</td>
        </tr>
        <tr>
          <th>13</th>
          <td>10</td>
          <td>0.281804</td>
          <td>0.267857</td>
          <td>13</td>
          <td>DONE</td>
          <td>195.816266</td>
          <td>1</td>
          <td>[0.26785714285714285]</td>
          <td>197.110387</td>
        </tr>
        <tr>
          <th>14</th>
          <td>37</td>
          <td>0.257322</td>
          <td>0.271905</td>
          <td>14</td>
          <td>DONE</td>
          <td>197.359947</td>
          <td>1</td>
          <td>[0.2719047619047619]</td>
          <td>198.260534</td>
        </tr>
        <tr>
          <th>15</th>
          <td>33</td>
          <td>0.381022</td>
          <td>0.297143</td>
          <td>15</td>
          <td>DONE</td>
          <td>198.506771</td>
          <td>1</td>
          <td>[0.29714285714285715]</td>
          <td>199.443259</td>
        </tr>
        <tr>
          <th>16</th>
          <td>66</td>
          <td>0.281230</td>
          <td>0.258095</td>
          <td>16</td>
          <td>DONE</td>
          <td>199.790292</td>
          <td>1</td>
          <td>[0.2580952380952381]</td>
          <td>200.614023</td>
        </tr>
        <tr>
          <th>17</th>
          <td>29</td>
          <td>0.580171</td>
          <td>0.508571</td>
          <td>17</td>
          <td>DONE</td>
          <td>200.865638</td>
          <td>8</td>
          <td>[0.3171428571428571, 0.38857142857142857, 0.41...</td>
          <td>204.302500</td>
        </tr>
        <tr>
          <th>18</th>
          <td>45</td>
          <td>0.269436</td>
          <td>0.279048</td>
          <td>18</td>
          <td>DONE</td>
          <td>204.555375</td>
          <td>1</td>
          <td>[0.27904761904761904]</td>
          <td>205.415226</td>
        </tr>
        <tr>
          <th>19</th>
          <td>46</td>
          <td>0.285466</td>
          <td>0.260000</td>
          <td>19</td>
          <td>DONE</td>
          <td>205.678262</td>
          <td>1</td>
          <td>[0.26]</td>
          <td>206.542666</td>
        </tr>
        <tr>
          <th>20</th>
          <td>8</td>
          <td>0.255445</td>
          <td>0.326190</td>
          <td>20</td>
          <td>DONE</td>
          <td>206.853032</td>
          <td>2</td>
          <td>[0.30023809523809525, 0.3261904761904762]</td>
          <td>209.160815</td>
        </tr>
        <tr>
          <th>21</th>
          <td>14</td>
          <td>0.871431</td>
          <td>0.801429</td>
          <td>21</td>
          <td>DONE</td>
          <td>209.422959</td>
          <td>80</td>
          <td>[0.31642857142857145, 0.39880952380952384, 0.4...</td>
          <td>254.199654</td>
        </tr>
        <tr>
          <th>22</th>
          <td>14</td>
          <td>0.874070</td>
          <td>0.375238</td>
          <td>22</td>
          <td>DONE</td>
          <td>254.579880</td>
          <td>2</td>
          <td>[0.3057142857142857, 0.37523809523809526]</td>
          <td>256.296301</td>
        </tr>
        <tr>
          <th>23</th>
          <td>13</td>
          <td>0.265414</td>
          <td>0.260476</td>
          <td>23</td>
          <td>DONE</td>
          <td>256.555508</td>
          <td>1</td>
          <td>[0.2604761904761905]</td>
          <td>257.700605</td>
        </tr>
        <tr>
          <th>24</th>
          <td>11</td>
          <td>0.263654</td>
          <td>0.258333</td>
          <td>24</td>
          <td>DONE</td>
          <td>257.965976</td>
          <td>1</td>
          <td>[0.25833333333333336]</td>
          <td>259.181543</td>
        </tr>
        <tr>
          <th>25</th>
          <td>14</td>
          <td>0.871357</td>
          <td>0.317381</td>
          <td>25</td>
          <td>DONE</td>
          <td>259.455943</td>
          <td>2</td>
          <td>[0.3088095238095238, 0.3173809523809524]</td>
          <td>261.115557</td>
        </tr>
        <tr>
          <th>26</th>
          <td>40</td>
          <td>0.282374</td>
          <td>0.274762</td>
          <td>26</td>
          <td>DONE</td>
          <td>261.374624</td>
          <td>1</td>
          <td>[0.27476190476190476]</td>
          <td>262.256141</td>
        </tr>
        <tr>
          <th>27</th>
          <td>10</td>
          <td>0.264670</td>
          <td>0.285238</td>
          <td>27</td>
          <td>DONE</td>
          <td>262.512628</td>
          <td>1</td>
          <td>[0.28523809523809524]</td>
          <td>263.792388</td>
        </tr>
        <tr>
          <th>28</th>
          <td>27</td>
          <td>0.619722</td>
          <td>0.266190</td>
          <td>28</td>
          <td>DONE</td>
          <td>264.053499</td>
          <td>1</td>
          <td>[0.2661904761904762]</td>
          <td>264.994080</td>
        </tr>
        <tr>
          <th>29</th>
          <td>29</td>
          <td>0.619863</td>
          <td>0.278571</td>
          <td>29</td>
          <td>DONE</td>
          <td>265.421543</td>
          <td>1</td>
          <td>[0.2785714285714286]</td>
          <td>266.385228</td>
        </tr>
      </tbody>
    </table>
    </div>
    </div>
    <br />
    <br />

.. GENERATED FROM PYTHON SOURCE LINES 410-416

Visualizing the Stopper
-----------------------
This graph shows the same information as described above but in a visual form.
Each of the 30 jobs and the rate at which they learned against the validation dataset is shown here. 
As shown above, not all job lines will show 100 epochs because the Stopper determined the jobs did not 
need to run the full time to converge on a solution.

.. GENERATED FROM PYTHON SOURCE LINES 418-434

.. code-block:: Python

    import numpy as np
    import matplotlib.pyplot as plt

    i = 0
    for row in results.iterrows():
        y = row[1]["m:accu_list"]
        x = np.arange(i+1, i+1+len(y))
        plt.plot(x, y, label=row[1]["job_id"])
        i += len(y)

    plt.xlabel('Epoch')
    plt.ylabel('Validation accuracy')
    plt.title("Validation Accuracies during training")

    plt.show()




.. image-sg:: /examples/examples_hpo/images/sphx_glr_plot_hpo_text_classification_with_stopper_001.png
   :alt: Validation Accuracies during training
   :srcset: /examples/examples_hpo/images/sphx_glr_plot_hpo_text_classification_with_stopper_001.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 435-439

Evaluate the best configuration
-------------------------------

Now that the search is over, let us print the best configuration found during this run and evaluate it on the full training dataset.

.. GENERATED FROM PYTHON SOURCE LINES 441-443

Show the job with best configuration and compare this with the graph above. The result of the comparison should be intuitive -
the job with the best objective in the graph should match :code:`i_max`.

.. GENERATED FROM PYTHON SOURCE LINES 443-446

.. code-block:: Python

    i_max = results.objective.argmax()
    i_max





.. rst-class:: sphx-glr-script-out

 .. code-block:: none


    4



.. GENERATED FROM PYTHON SOURCE LINES 447-456

.. code-block:: Python

    best_config = results.iloc[i_max][:-3].to_dict()
    best_config = {k[2:]: v for k, v in best_config.items() if k.startswith("p:")}

    print(f"The default configuration has an accuracy of {objective_default["objective"]:.3f}. \n" 
          f"The best configuration found by DeepHyper has an accuracy {results['objective'].iloc[i_max]:.3f}, \n" 
          f"finished after {results['m:timestamp_gather'].iloc[i_max]:.2f} seconds of search.\n")

    print(json.dumps(best_config, indent=4))





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    The default configuration has an accuracy of 0.887. 
    The best configuration found by DeepHyper has an accuracy 0.810, 
    finished after 183.35 seconds of search.

    {
        "batch_size": 14,
        "learning_rate": 0.8747246682101409
    }




.. GENERATED FROM PYTHON SOURCE LINES 457-459

.. code-block:: Python

    objective_best = perf_run(RunningJob(parameters=best_config))
    print(f"Accuracy Best Configuration:  {objective_best["objective"]:.3f}")




.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    Accuracy Best Configuration:  0.867





.. rst-class:: sphx-glr-timing

   **Total running time of the script:** (7 minutes 49.357 seconds)


.. _sphx_glr_download_examples_examples_hpo_plot_hpo_text_classification_with_stopper.py:

.. only:: html

  .. container:: sphx-glr-footer sphx-glr-footer-example

    .. container:: sphx-glr-download sphx-glr-download-jupyter

      :download:`Download Jupyter notebook: plot_hpo_text_classification_with_stopper.ipynb <plot_hpo_text_classification_with_stopper.ipynb>`

    .. container:: sphx-glr-download sphx-glr-download-python

      :download:`Download Python source code: plot_hpo_text_classification_with_stopper.py <plot_hpo_text_classification_with_stopper.py>`

    .. container:: sphx-glr-download sphx-glr-download-zip

      :download:`Download zipped: plot_hpo_text_classification_with_stopper.zip <plot_hpo_text_classification_with_stopper.zip>`


.. only:: html

 .. rst-class:: sphx-glr-signature

    `Gallery generated by Sphinx-Gallery <https://sphinx-gallery.github.io>`_
