2018-03-02 13:46:32 +00:00
|
|
|
# pragma pylint: disable=too-many-instance-attributes, pointless-string-statement
|
2017-11-25 00:04:11 +00:00
|
|
|
|
2018-03-02 13:46:32 +00:00
|
|
|
"""
|
|
|
|
This module contains the hyperopt logic
|
|
|
|
"""
|
2017-11-25 00:04:11 +00:00
|
|
|
|
2017-11-25 01:04:37 +00:00
|
|
|
import logging
|
2019-12-12 00:12:28 +00:00
|
|
|
import random
|
2022-07-16 09:05:58 +00:00
|
|
|
import sys
|
2024-11-10 14:09:47 +00:00
|
|
|
from datetime import datetime
|
2020-08-08 15:04:32 +00:00
|
|
|
from math import ceil
|
2019-01-06 13:47:38 +00:00
|
|
|
from pathlib import Path
|
2024-11-07 20:37:33 +00:00
|
|
|
from typing import Any
|
2017-10-19 14:12:49 +00:00
|
|
|
|
2021-05-12 03:58:25 +00:00
|
|
|
import rapidjson
|
2024-11-10 14:09:47 +00:00
|
|
|
from joblib import Parallel, cpu_count, delayed, wrap_non_picklable_objects
|
2022-07-16 09:05:58 +00:00
|
|
|
from joblib.externals import cloudpickle
|
2024-07-08 04:44:21 +00:00
|
|
|
from rich.console import Console
|
2018-06-18 19:40:36 +00:00
|
|
|
|
2024-11-10 14:09:47 +00:00
|
|
|
from freqtrade.constants import FTHYPT_FILEVERSION, LAST_BT_RESULT_FN, Config
|
2022-08-19 13:19:43 +00:00
|
|
|
from freqtrade.enums import HyperoptState
|
2021-09-11 07:06:57 +00:00
|
|
|
from freqtrade.exceptions import OperationalException
|
2024-11-10 14:09:47 +00:00
|
|
|
from freqtrade.misc import file_dump_json, plural
|
|
|
|
from freqtrade.optimize.hyperopt.hyperopt_optimizer import HyperOptimizer
|
2024-11-10 13:54:16 +00:00
|
|
|
from freqtrade.optimize.hyperopt.hyperopt_output import HyperoptOutput
|
2024-05-12 13:18:32 +00:00
|
|
|
from freqtrade.optimize.hyperopt_tools import (
|
|
|
|
HyperoptStateContainer,
|
|
|
|
HyperoptTools,
|
|
|
|
hyperopt_serializer,
|
|
|
|
)
|
2024-07-13 13:47:50 +00:00
|
|
|
from freqtrade.util import get_progress_tracker
|
2022-03-30 08:39:07 +00:00
|
|
|
|
2020-09-28 17:39:41 +00:00
|
|
|
|
2018-03-25 19:37:14 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2019-04-25 08:11:04 +00:00
|
|
|
|
2022-03-30 08:39:07 +00:00
|
|
|
INITIAL_POINTS = 30
|
2019-09-23 08:59:34 +00:00
|
|
|
|
2020-04-29 07:49:25 +00:00
|
|
|
# Keep no more than SKOPT_MODEL_QUEUE_SIZE models
|
|
|
|
# in the skopt model queue, to optimize memory consumption
|
|
|
|
SKOPT_MODEL_QUEUE_SIZE = 10
|
2019-09-23 08:59:34 +00:00
|
|
|
|
2018-03-25 19:37:14 +00:00
|
|
|
|
2019-08-23 21:10:35 +00:00
|
|
|
class Hyperopt:
|
2018-01-23 14:56:12 +00:00
|
|
|
"""
|
2018-03-02 13:46:32 +00:00
|
|
|
Hyperopt class, this class contains all the logic to run a hyperopt simulation
|
2018-01-23 14:56:12 +00:00
|
|
|
|
2022-09-26 08:11:00 +00:00
|
|
|
To start a hyperopt run:
|
2018-03-02 13:46:32 +00:00
|
|
|
hyperopt = Hyperopt(config)
|
|
|
|
hyperopt.start()
|
2018-01-23 14:56:12 +00:00
|
|
|
"""
|
2020-01-31 21:37:05 +00:00
|
|
|
|
2022-09-18 11:20:36 +00:00
|
|
|
def __init__(self, config: Config) -> None:
|
2024-08-29 05:08:41 +00:00
|
|
|
self._hyper_out: HyperoptOutput = HyperoptOutput(streaming=True)
|
2024-07-07 14:15:09 +00:00
|
|
|
|
2019-08-23 21:10:35 +00:00
|
|
|
self.config = config
|
|
|
|
|
2024-05-12 15:16:02 +00:00
|
|
|
self.analyze_per_epoch = self.config.get("analyze_per_epoch", False)
|
2022-08-19 13:19:43 +00:00
|
|
|
HyperoptStateContainer.set_state(HyperoptState.STARTUP)
|
2019-09-18 19:57:17 +00:00
|
|
|
|
2024-11-10 14:09:47 +00:00
|
|
|
if self.config.get("hyperopt"):
|
2021-09-11 07:06:57 +00:00
|
|
|
raise OperationalException(
|
2021-09-11 15:11:02 +00:00
|
|
|
"Using separate Hyperopt files has been removed in 2021.9. Please convert "
|
2024-05-12 15:16:02 +00:00
|
|
|
"your existing Hyperopt file to the new Hyperoptable strategy interface"
|
|
|
|
)
|
2021-06-30 05:05:20 +00:00
|
|
|
|
2020-09-27 14:33:26 +00:00
|
|
|
time_now = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
2024-05-12 15:16:02 +00:00
|
|
|
strategy = str(self.config["strategy"])
|
|
|
|
self.results_file: Path = (
|
|
|
|
self.config["user_data_dir"]
|
|
|
|
/ "hyperopt_results"
|
|
|
|
/ f"strategy_{strategy}_{time_now}.fthypt"
|
|
|
|
)
|
|
|
|
self.data_pickle_file = (
|
|
|
|
self.config["user_data_dir"] / "hyperopt_results" / "hyperopt_tickerdata.pkl"
|
|
|
|
)
|
|
|
|
self.total_epochs = config.get("epochs", 0)
|
2019-08-01 17:33:45 +00:00
|
|
|
|
2018-03-02 13:46:32 +00:00
|
|
|
self.current_best_loss = 100
|
|
|
|
|
2020-09-27 14:18:28 +00:00
|
|
|
self.clean_hyperopt()
|
2019-07-16 03:50:27 +00:00
|
|
|
|
2020-04-28 19:56:19 +00:00
|
|
|
self.num_epochs_saved = 0
|
2024-11-07 20:37:33 +00:00
|
|
|
self.current_best_epoch: dict[str, Any] | None = None
|
2018-03-02 13:46:32 +00:00
|
|
|
|
2024-05-12 15:16:02 +00:00
|
|
|
if HyperoptTools.has_space(self.config, "sell"):
|
2022-04-05 18:07:58 +00:00
|
|
|
# Make sure use_exit_signal is enabled
|
2024-05-12 15:16:02 +00:00
|
|
|
self.config["use_exit_signal"] = True
|
2019-08-01 20:57:26 +00:00
|
|
|
|
2024-05-12 15:16:02 +00:00
|
|
|
self.print_all = self.config.get("print_all", False)
|
2020-02-28 20:54:04 +00:00
|
|
|
self.hyperopt_table_header = 0
|
2024-05-12 15:16:02 +00:00
|
|
|
self.print_colorized = self.config.get("print_colorized", False)
|
|
|
|
self.print_json = self.config.get("print_json", False)
|
2019-11-26 12:01:42 +00:00
|
|
|
|
2024-11-10 14:09:47 +00:00
|
|
|
self.hyperopter = HyperOptimizer(self.config)
|
|
|
|
|
2019-07-21 14:07:06 +00:00
|
|
|
@staticmethod
|
2022-09-18 11:20:36 +00:00
|
|
|
def get_lock_filename(config: Config) -> str:
|
2024-05-12 15:16:02 +00:00
|
|
|
return str(config["user_data_dir"] / "hyperopt.lock")
|
2019-07-21 14:07:06 +00:00
|
|
|
|
2020-02-02 04:00:40 +00:00
|
|
|
def clean_hyperopt(self) -> None:
|
2019-07-15 18:17:15 +00:00
|
|
|
"""
|
|
|
|
Remove hyperopt pickle files to restart hyperopt.
|
|
|
|
"""
|
2020-04-28 19:56:19 +00:00
|
|
|
for f in [self.data_pickle_file, self.results_file]:
|
2019-07-15 18:17:15 +00:00
|
|
|
p = Path(f)
|
|
|
|
if p.is_file():
|
|
|
|
logger.info(f"Removing `{p}`.")
|
|
|
|
p.unlink()
|
|
|
|
|
2022-07-16 09:05:58 +00:00
|
|
|
def hyperopt_pickle_magic(self, bases) -> None:
|
|
|
|
"""
|
|
|
|
Hyperopt magic to allow strategy inheritance across files.
|
|
|
|
For this to properly work, we need to register the module of the imported class
|
|
|
|
to pickle as value.
|
|
|
|
"""
|
|
|
|
for modules in bases:
|
2024-05-12 15:16:02 +00:00
|
|
|
if modules.__name__ != "IStrategy":
|
2022-07-16 09:05:58 +00:00
|
|
|
cloudpickle.register_pickle_by_value(sys.modules[modules.__module__])
|
|
|
|
self.hyperopt_pickle_magic(modules.__bases__)
|
|
|
|
|
2024-10-04 04:53:50 +00:00
|
|
|
def _save_result(self, epoch: dict) -> None:
|
2018-03-02 13:46:32 +00:00
|
|
|
"""
|
2020-04-28 19:56:19 +00:00
|
|
|
Save hyperopt results to file
|
2021-05-12 03:58:25 +00:00
|
|
|
Store one line per epoch.
|
|
|
|
While not a valid json object - this allows appending easily.
|
|
|
|
:param epoch: result dictionary for this epoch.
|
2018-03-02 13:46:32 +00:00
|
|
|
"""
|
2021-06-15 18:27:46 +00:00
|
|
|
epoch[FTHYPT_FILEVERSION] = 2
|
2024-05-12 15:16:02 +00:00
|
|
|
with self.results_file.open("a") as f:
|
|
|
|
rapidjson.dump(
|
|
|
|
epoch,
|
|
|
|
f,
|
|
|
|
default=hyperopt_serializer,
|
|
|
|
number_mode=rapidjson.NM_NATIVE | rapidjson.NM_NAN,
|
|
|
|
)
|
2021-05-15 05:01:32 +00:00
|
|
|
f.write("\n")
|
2021-05-12 03:58:25 +00:00
|
|
|
|
|
|
|
self.num_epochs_saved += 1
|
2024-05-12 15:16:02 +00:00
|
|
|
logger.debug(
|
|
|
|
f"{self.num_epochs_saved} {plural(self.num_epochs_saved, 'epoch')} "
|
|
|
|
f"saved to '{self.results_file}'."
|
|
|
|
)
|
2021-05-12 03:58:25 +00:00
|
|
|
# Store hyperopt filename
|
|
|
|
latest_filename = Path.joinpath(self.results_file.parent, LAST_BT_RESULT_FN)
|
2024-05-12 15:16:02 +00:00
|
|
|
file_dump_json(latest_filename, {"latest_hyperopt": str(self.results_file.name)}, log=False)
|
2018-03-02 13:46:32 +00:00
|
|
|
|
2024-10-04 04:53:50 +00:00
|
|
|
def print_results(self, results: dict[str, Any]) -> None:
|
2018-03-02 13:46:32 +00:00
|
|
|
"""
|
|
|
|
Log results if it is better than any previous evaluation
|
2021-03-17 19:43:51 +00:00
|
|
|
TODO: this should be moved to HyperoptTools too
|
2018-03-02 13:46:32 +00:00
|
|
|
"""
|
2024-05-12 15:16:02 +00:00
|
|
|
is_best = results["is_best"]
|
2019-11-23 08:32:33 +00:00
|
|
|
|
2019-11-26 12:01:42 +00:00
|
|
|
if self.print_all or is_best:
|
2024-07-07 14:15:09 +00:00
|
|
|
self._hyper_out.add_data(
|
|
|
|
self.config,
|
|
|
|
[results],
|
|
|
|
self.total_epochs,
|
|
|
|
self.print_all,
|
2020-03-11 21:30:36 +00:00
|
|
|
)
|
2018-03-02 13:46:32 +00:00
|
|
|
|
2024-10-04 04:53:50 +00:00
|
|
|
def run_optimizer_parallel(self, parallel: Parallel, asked: list[list]) -> list[dict[str, Any]]:
|
2024-05-12 15:16:02 +00:00
|
|
|
"""Start optimizer in a parallel way"""
|
|
|
|
return parallel(
|
2024-11-10 14:09:47 +00:00
|
|
|
delayed(wrap_non_picklable_objects(self.hyperopter.generate_optimizer))(v)
|
|
|
|
for v in asked
|
2024-05-12 15:16:02 +00:00
|
|
|
)
|
2018-06-24 12:27:53 +00:00
|
|
|
|
2024-11-07 20:37:33 +00:00
|
|
|
def _set_random_state(self, random_state: int | None) -> int:
|
2024-06-08 07:23:02 +00:00
|
|
|
return random_state or random.randint(1, 2**16 - 1) # noqa: S311
|
2019-12-12 00:12:28 +00:00
|
|
|
|
2024-10-04 04:53:50 +00:00
|
|
|
def get_asked_points(self, n_points: int) -> tuple[list[list[Any]], list[bool]]:
|
2022-04-23 07:44:04 +00:00
|
|
|
"""
|
2022-03-20 16:02:03 +00:00
|
|
|
Enforce points returned from `self.opt.ask` have not been already evaluated
|
|
|
|
|
|
|
|
Steps:
|
|
|
|
1. Try to get points using `self.opt.ask` first
|
|
|
|
2. Discard the points that have already been evaluated
|
|
|
|
3. Retry using `self.opt.ask` up to 3 times
|
|
|
|
4. If still some points are missing in respect to `n_points`, random sample some points
|
|
|
|
5. Repeat until at least `n_points` points in the `asked_non_tried` list
|
2022-03-20 16:03:07 +00:00
|
|
|
6. Return a list with length truncated at `n_points`
|
2022-04-23 07:44:04 +00:00
|
|
|
"""
|
2024-05-12 15:16:02 +00:00
|
|
|
|
2022-04-08 10:44:42 +00:00
|
|
|
def unique_list(a_list):
|
2022-04-13 08:36:46 +00:00
|
|
|
new_list = []
|
|
|
|
for item in a_list:
|
|
|
|
if item not in new_list:
|
|
|
|
new_list.append(item)
|
|
|
|
return new_list
|
2024-05-12 15:16:02 +00:00
|
|
|
|
2022-03-20 16:02:03 +00:00
|
|
|
i = 0
|
2024-10-04 04:53:50 +00:00
|
|
|
asked_non_tried: list[list[Any]] = []
|
|
|
|
is_random_non_tried: list[bool] = []
|
2022-03-29 18:33:35 +00:00
|
|
|
while i < 5 and len(asked_non_tried) < n_points:
|
2022-03-20 16:06:41 +00:00
|
|
|
if i < 3:
|
2022-03-21 11:36:53 +00:00
|
|
|
self.opt.cache_ = {}
|
2023-12-15 05:19:50 +00:00
|
|
|
asked = unique_list(self.opt.ask(n_points=n_points * 5 if i > 0 else n_points))
|
2022-03-29 23:29:14 +00:00
|
|
|
is_random = [False for _ in range(len(asked))]
|
2022-03-20 16:02:03 +00:00
|
|
|
else:
|
2022-04-08 10:44:42 +00:00
|
|
|
asked = unique_list(self.opt.space.rvs(n_samples=n_points * 5))
|
2022-03-29 23:29:14 +00:00
|
|
|
is_random = [True for _ in range(len(asked))]
|
2024-05-12 15:16:02 +00:00
|
|
|
is_random_non_tried += [
|
|
|
|
rand
|
2024-11-07 20:37:33 +00:00
|
|
|
for x, rand in zip(asked, is_random, strict=False)
|
2024-05-12 15:16:02 +00:00
|
|
|
if x not in self.opt.Xi and x not in asked_non_tried
|
|
|
|
]
|
|
|
|
asked_non_tried += [
|
|
|
|
x for x in asked if x not in self.opt.Xi and x not in asked_non_tried
|
|
|
|
]
|
2022-03-20 16:06:41 +00:00
|
|
|
i += 1
|
2022-03-29 23:29:14 +00:00
|
|
|
|
2022-03-20 16:08:38 +00:00
|
|
|
if asked_non_tried:
|
2022-03-29 23:29:14 +00:00
|
|
|
return (
|
2024-05-12 15:16:02 +00:00
|
|
|
asked_non_tried[: min(len(asked_non_tried), n_points)],
|
|
|
|
is_random_non_tried[: min(len(asked_non_tried), n_points)],
|
2022-03-29 23:29:14 +00:00
|
|
|
)
|
2022-03-20 16:08:38 +00:00
|
|
|
else:
|
2022-03-29 23:29:14 +00:00
|
|
|
return self.opt.ask(n_points=n_points), [False for _ in range(n_points)]
|
2022-03-20 16:02:03 +00:00
|
|
|
|
2024-10-04 04:53:50 +00:00
|
|
|
def evaluate_result(self, val: dict[str, Any], current: int, is_random: bool):
|
2022-09-11 09:54:31 +00:00
|
|
|
"""
|
|
|
|
Evaluate results returned from generate_optimizer
|
|
|
|
"""
|
2024-05-12 15:16:02 +00:00
|
|
|
val["current_epoch"] = current
|
|
|
|
val["is_initial_point"] = current <= INITIAL_POINTS
|
2022-09-11 09:54:31 +00:00
|
|
|
|
|
|
|
logger.debug("Optimizer epoch evaluated: %s", val)
|
|
|
|
|
|
|
|
is_best = HyperoptTools.is_best_loss(val, self.current_best_loss)
|
|
|
|
# This value is assigned here and not in the optimization method
|
|
|
|
# to keep proper order in the list of results. That's because
|
|
|
|
# evaluations can take different time. Here they are aligned in the
|
|
|
|
# order they will be shown to the user.
|
2024-05-12 15:16:02 +00:00
|
|
|
val["is_best"] = is_best
|
|
|
|
val["is_random"] = is_random
|
2022-09-11 09:54:31 +00:00
|
|
|
self.print_results(val)
|
|
|
|
|
|
|
|
if is_best:
|
2024-05-12 15:16:02 +00:00
|
|
|
self.current_best_loss = val["loss"]
|
2022-09-11 09:54:31 +00:00
|
|
|
self.current_best_epoch = val
|
|
|
|
|
|
|
|
self._save_result(val)
|
|
|
|
|
2021-05-12 19:15:01 +00:00
|
|
|
def start(self) -> None:
|
2024-05-12 15:16:02 +00:00
|
|
|
self.random_state = self._set_random_state(self.config.get("hyperopt_random_state"))
|
2021-05-12 19:15:01 +00:00
|
|
|
logger.info(f"Using optimizer random state: {self.random_state}")
|
|
|
|
self.hyperopt_table_header = -1
|
2024-11-10 14:09:47 +00:00
|
|
|
self.hyperopter.prepare_hyperopt()
|
2019-04-22 18:24:45 +00:00
|
|
|
|
2019-04-23 18:25:36 +00:00
|
|
|
cpus = cpu_count()
|
2019-08-25 18:38:51 +00:00
|
|
|
logger.info(f"Found {cpus} CPU cores. Let's make them scream!")
|
2024-05-12 15:16:02 +00:00
|
|
|
config_jobs = self.config.get("hyperopt_jobs", -1)
|
|
|
|
logger.info(f"Number of parallel jobs set as: {config_jobs}")
|
2018-06-21 11:59:36 +00:00
|
|
|
|
2024-11-10 14:09:47 +00:00
|
|
|
self.opt = self.hyperopter.get_optimizer(
|
|
|
|
config_jobs, self.random_state, INITIAL_POINTS, SKOPT_MODEL_QUEUE_SIZE
|
|
|
|
)
|
2020-06-01 07:37:10 +00:00
|
|
|
|
2018-06-22 10:02:26 +00:00
|
|
|
try:
|
2019-04-22 21:30:09 +00:00
|
|
|
with Parallel(n_jobs=config_jobs) as parallel:
|
|
|
|
jobs = parallel._effective_n_jobs()
|
2024-05-12 15:16:02 +00:00
|
|
|
logger.info(f"Effective number of parallel workers used: {jobs}")
|
2024-07-08 04:44:21 +00:00
|
|
|
console = Console(
|
|
|
|
color_system="auto" if self.print_colorized else None,
|
|
|
|
)
|
2020-03-11 21:30:36 +00:00
|
|
|
|
|
|
|
# Define progressbar
|
2024-07-13 13:47:50 +00:00
|
|
|
with get_progress_tracker(
|
2024-07-08 04:44:21 +00:00
|
|
|
console=console,
|
2024-08-29 05:08:41 +00:00
|
|
|
cust_callables=[self._hyper_out],
|
2021-08-06 22:19:36 +00:00
|
|
|
) as pbar:
|
2023-04-09 16:09:15 +00:00
|
|
|
task = pbar.add_task("Epochs", total=self.total_epochs)
|
|
|
|
|
2022-09-11 13:42:27 +00:00
|
|
|
start = 0
|
|
|
|
|
|
|
|
if self.analyze_per_epoch:
|
|
|
|
# First analysis not in parallel mode when using --analyze-per-epoch.
|
|
|
|
# This allows dataprovider to load it's informative cache.
|
|
|
|
asked, is_random = self.get_asked_points(n_points=1)
|
2024-11-10 14:09:47 +00:00
|
|
|
f_val0 = self.hyperopter.generate_optimizer(asked[0])
|
2024-05-12 15:16:02 +00:00
|
|
|
self.opt.tell(asked, [f_val0["loss"]])
|
2022-09-11 17:31:11 +00:00
|
|
|
self.evaluate_result(f_val0, 1, is_random[0])
|
2023-04-09 16:09:15 +00:00
|
|
|
pbar.update(task, advance=1)
|
2022-09-11 13:42:27 +00:00
|
|
|
start += 1
|
|
|
|
|
|
|
|
evals = ceil((self.total_epochs - start) / jobs)
|
|
|
|
for i in range(evals):
|
2020-04-06 11:12:32 +00:00
|
|
|
# Correct the number of epochs to be processed for the last
|
|
|
|
# iteration (should not exceed self.total_epochs in total)
|
2022-09-11 13:42:27 +00:00
|
|
|
n_rest = (i + 1) * jobs - (self.total_epochs - start)
|
2020-04-06 11:12:32 +00:00
|
|
|
current_jobs = jobs - n_rest if n_rest > 0 else jobs
|
|
|
|
|
2022-03-29 23:29:14 +00:00
|
|
|
asked, is_random = self.get_asked_points(n_points=current_jobs)
|
2022-09-11 09:56:17 +00:00
|
|
|
f_val = self.run_optimizer_parallel(parallel, asked)
|
2024-05-12 15:16:02 +00:00
|
|
|
self.opt.tell(asked, [v["loss"] for v in f_val])
|
2020-04-06 11:12:32 +00:00
|
|
|
|
|
|
|
for j, val in enumerate(f_val):
|
|
|
|
# Use human-friendly indexes here (starting from 1)
|
2022-09-11 13:42:27 +00:00
|
|
|
current = i * jobs + j + 1 + start
|
2020-04-06 11:12:32 +00:00
|
|
|
|
2022-09-11 09:54:31 +00:00
|
|
|
self.evaluate_result(val, current, is_random[j])
|
2023-04-09 16:09:15 +00:00
|
|
|
pbar.update(task, advance=1)
|
2020-03-10 19:30:36 +00:00
|
|
|
|
2018-06-22 10:02:26 +00:00
|
|
|
except KeyboardInterrupt:
|
2024-05-12 15:16:02 +00:00
|
|
|
print("User interrupted..")
|
2018-01-07 01:12:32 +00:00
|
|
|
|
2024-05-12 15:16:02 +00:00
|
|
|
logger.info(
|
|
|
|
f"{self.num_epochs_saved} {plural(self.num_epochs_saved, 'epoch')} "
|
|
|
|
f"saved to '{self.results_file}'."
|
|
|
|
)
|
2019-11-26 12:01:42 +00:00
|
|
|
|
2021-05-12 03:58:25 +00:00
|
|
|
if self.current_best_epoch:
|
2021-09-11 07:06:57 +00:00
|
|
|
HyperoptTools.try_export_params(
|
2024-11-10 14:09:47 +00:00
|
|
|
self.config,
|
|
|
|
self.hyperopter.get_strategy_name(),
|
|
|
|
self.current_best_epoch,
|
2024-05-12 15:16:02 +00:00
|
|
|
)
|
2021-06-29 18:22:30 +00:00
|
|
|
|
2024-05-12 15:16:02 +00:00
|
|
|
HyperoptTools.show_epoch_details(
|
|
|
|
self.current_best_epoch, self.total_epochs, self.print_json
|
|
|
|
)
|
2023-12-16 21:09:02 +00:00
|
|
|
elif self.num_epochs_saved > 0:
|
2023-12-16 21:36:56 +00:00
|
|
|
print(
|
|
|
|
f"No good result found for given optimization function in {self.num_epochs_saved} "
|
2024-05-12 15:16:02 +00:00
|
|
|
f"{plural(self.num_epochs_saved, 'epoch')}."
|
|
|
|
)
|
2019-11-26 12:01:42 +00:00
|
|
|
else:
|
|
|
|
# This is printed when Ctrl+C is pressed quickly, before first epochs have
|
|
|
|
# a chance to be evaluated.
|
|
|
|
print("No epochs evaluated yet, no best result.")
|