class_docstr: Class for fastai v1 neural network models that operate on tabular data.
  
  Hyperparameters:
      'y_scaler': on regression problems, the model can give unreasonable predictions on unseen data.
      To address this problem, AutoGluon scales y values by default for regression problems.
      This attribute allows to pass a custom scaler for y values. Please note that intermediate
      iteration metrics will be affected by this transform and as a result intermediate iteration scores will be
      different from the final ones (these will be correct).
      https://scikit-learn.org/stable/modules/classes.html#module-sklearn.preprocessing
  
      'clipping': on regression problems, extreme outliers of y can hurt performance of the model during training and
      on unseen data. To address this problem, AutoGluon clips input y values and output predictions by default to a
      range inferred from the training data. Setting this attribute to False disables clipping.
  
      'layers': list of hidden layers sizes; None - use model's heuristics; default is None
  
      'emb_drop': embedding layers dropout; default is 0.1
  
      'ps': linear layers dropout - list of values applied to every layer in `layers`; default is [0.1]
  
      'bs': batch size; default is 256
  
      'lr': maximum learning rate for one cycle policy; default is 1e-2;
      see also https://docs.fast.ai/callback.schedule.html#Learner.fit_one_cycle,
      One-cycle policy paper: https://arxiv.org/abs/1803.09820
  
      'epochs': number of epochs; default is 30
  
      # Early stopping settings. See more details here: https://docs.fast.ai/callback.tracker.html#EarlyStoppingCallback
      'early.stopping.min_delta': 0.0001,
      'early.stopping.patience': 10,
class_docstr_tok: ["Class","for","fastai","v1","neural","network","models","that","operate","on","tabular","data",".","Hyperparameters",":","'y_scaler","'",":","on","regression","problems",",","the","model","can","give","unreasonable","predictions","on","unseen","data",".","To","address","this","problem",",","AutoGluon","scales","y","values","by","default","for","regression","problems",".","This","attribute","allows","to","pass","a","custom","scaler","for","y","values",".","Please","note","that","intermediate","iteration","metrics","will","be","affected","by","this","transform","and","as","a","result","intermediate","iteration","scores","will","be","different","from","the","final","ones","(","these","will","be","correct",")",".","https",":","\/\/scikit-learn.org\/stable\/modules\/classes.html","#","module-sklearn.preprocessing","'clipping","'",":","on","regression","problems",",","extreme","outliers","of","y","can","hurt","performance","of","the","model","during","training","and","on","unseen","data",".","To","address","this","problem",",","AutoGluon","clips","input","y","values","and","output","predictions","by","default","to","a","range","inferred","from","the","training","data",".","Setting","this","attribute","to","False","disables","clipping",".","'layers","'",":","list","of","hidden","layers","sizes",";","None","-","use","model","'s","heuristics",";","default","is","None","'emb_drop","'",":","embedding","layers","dropout",";","default","is","0.1","'ps","'",":","linear","layers","dropout","-","list","of","values","applied","to","every","layer","in","`","layers","`",";","default","is","[","0.1","]","'bs","'",":","batch","size",";","default","is","256","'lr","'",":","maximum","learning","rate","for","one","cycle","policy",";","default","is","1e-2",";","see","also","https",":","\/\/docs.fast.ai\/callback.schedule.html","#","Learner.fit_one_cycle",",","One-cycle","policy","paper",":","https",":","\/\/arxiv.org\/abs\/1803.09820","'epochs","'",":","number","of","epochs",";","default","is","30","#","Early","stopping","settings",".","See","more","details","here",":","https",":","\/\/docs.fast.ai\/callback.tracker.html","#","EarlyStoppingCallback","'early.stopping.min_delta","'",":","0.0001",",","'early.stopping.patience","'",":","10",","]
code: def _fit(
      self,
      X,
      y,
      X_val=None,
      y_val=None,
      time_limit=None,
      num_cpus=None,
      num_gpus=0,
      sample_weight=None,
      **kwargs,
  ):
      try_import_fastai()
      import torch
      from fastai import torch_core
      from fastai.tabular.learner import tabular_learner
      from fastai.tabular.model import tabular_config
  
      from .callbacks import (
          AgSaveModelCallback,
          EarlyStoppingCallbackWithTimeLimit,
      )
      from .quantile_helpers import HuberPinballLoss
  
      torch.set_num_threads(num_cpus)
      start_time = time.time()
      if sample_weight is not None:  # TODO: support
          logger.log(
              15,
              "sample_weight not yet supported for NNFastAiTabularModel, this model will ignore them in training.",
          )
  
      params = self._get_model_params()
      self._num_cpus_infer = params.pop("_num_cpus_infer", 1)
  
      self.y_scaler = params.get("y_scaler", None)
      if self.y_scaler is None:
          if self.problem_type == REGRESSION:
              self.y_scaler = sklearn.preprocessing.StandardScaler()
          elif self.problem_type == QUANTILE:
              self.y_scaler = sklearn.preprocessing.MinMaxScaler()
      else:
          self.y_scaler = copy.deepcopy(self.y_scaler)
  
      self.clipping = params.pop("clipping", True)
      if self.clipping and (self.problem_type == REGRESSION):
          # use code and concepts from TorchNN to infer y range. 0.05 follows default from TorchNN.
          y_min, y_max = infer_y_range(y, y_range_extend=0.05)
          clip_func = partial(np.clip, a_min=y_min, a_max=y_max)
  
          steps = [
              # needs both func and inverse func, as the FastAI model calls inverse_transform.
              (
                  "clipper",
                  sklearn.preprocessing.FunctionTransformer(
                      func=clip_func, inverse_func=clip_func
                  ),
              ),
          ]
  
          # Support the case where no scaler is defined.
          if self.y_scaler is not None:
              steps.append(("scaler", self.y_scaler))
  
          self.y_scaler = sklearn.pipeline.Pipeline(steps=steps)
  
      if num_gpus is not None:
          # TODO: Control CPU vs GPU usage during inference
          if num_gpus == 0:
              torch_core.default_device(False)
          else:
              # TODO: respect CUDA_VISIBLE_DEVICES to select proper GPU
              torch_core.default_device(True)
  
      logger.log(
          15, f"Fitting Neural Network with parameters {params}..."
      )
      data = self._preprocess_train(X, y, X_val, y_val)
  
      nn_metric, objective_func_name = self.__get_objective_func_name(
          self.stopping_metric
      )
      objective_func_name_to_monitor = (
          self.__get_objective_func_to_monitor(objective_func_name)
      )
      objective_optim_mode = (
          np.less
          if objective_func_name
          in [
              "log_loss",
              "root_mean_squared_error",
              "mean_squared_error",
              "mean_absolute_error",
              "median_absolute_error",  # Regression objectives
              "pinball_loss",  # Quantile objective
          ]
          else np.greater
      )
  
      # TODO: calculate max emb concat layer size and use 1st layer as that value and 2nd in between number of classes and the value
      if params.get("layers", None) is not None:
          layers = params["layers"]
          if isinstance(layers, tuple):
              layers = list(layers)
      elif self.problem_type in [REGRESSION, BINARY]:
          layers = [200, 100]
      elif self.problem_type == QUANTILE:
          base_size = max(len(self.quantile_levels) * 4, 128)
          layers = [base_size, base_size, base_size]
      else:
          base_size = max(data.c * 2, 100)
          layers = [base_size * 2, base_size]
  
      loss_func = None
      if self.problem_type == QUANTILE:
          loss_func = HuberPinballLoss(
              self.quantile_levels, alpha=self.params["alpha"]
          )
  
      best_epoch_stop = params.get(
          "best_epoch", None
      )  # Use best epoch for refit_full.
      batch_size = self._get_batch_size(X)
      dls = data.dataloaders(bs=batch_size)
  
      # Make deterministic
      from fastai.torch_core import set_seed
  
      set_seed(0, True)
      dls.rng.seed(0)
  
      if self.problem_type == QUANTILE:
          dls.c = len(self.quantile_levels)
  
      self.model = tabular_learner(
          dls,
          layers=layers,
          metrics=nn_metric,
          config=tabular_config(
              ps=params["ps"], embed_p=params["emb_drop"]
          ),
          loss_func=loss_func,
      )
      logger.log(15, self.model.model)
  
      fname = "model"
      save_callback = AgSaveModelCallback(
          monitor=objective_func_name_to_monitor,
          comp=objective_optim_mode,
          fname=fname,
          best_epoch_stop=best_epoch_stop,
          with_opt=True,
      )
  
      if time_limit is not None:
          time_elapsed = time.time() - start_time
          time_left = time_limit - time_elapsed
          if (
              time_left <= time_limit * 0.7
          ):  # if 30% of time was spent preprocessing, likely not enough time to train model
              raise TimeLimitExceeded
      else:
          time_left = None
  
      early_stopping = EarlyStoppingCallbackWithTimeLimit(
          monitor=objective_func_name_to_monitor,
          comp=objective_optim_mode,
          min_delta=params["early.stopping.min_delta"],
          patience=params["early.stopping.patience"],
          time_limit=time_left,
          best_epoch_stop=best_epoch_stop,
      )
  
      callbacks = [save_callback, early_stopping]
  
      with make_temp_directory() as temp_dir:
          with self.model.no_bar():
              with self.model.no_logging():
                  original_path = self.model.path
                  self.model.path = Path(temp_dir)
  
                  len_val = len(X_val) if X_val is not None else 0
                  epochs = self._get_epochs_number(
                      samples_num=len(X) + len_val,
                      epochs=params["epochs"],
                      batch_size=batch_size,
                      time_left=time_left,
                  )
                  if epochs == 0:
                      # Stop early if there is not enough time to train a full epoch
                      raise TimeLimitExceeded
  
                  self.model.fit_one_cycle(
                      epochs, params["lr"], cbs=callbacks
                  )
  
                  # Load the best one and export it
                  self.model = self.model.load(fname)
  
                  if objective_func_name == "log_loss":
                      eval_result = self.model.validate(dl=dls.valid)[0]
                  else:
                      eval_result = self.model.validate(dl=dls.valid)[1]
  
                  logger.log(
                      15, f"Model validation metrics: {eval_result}"
                  )
                  self.model.path = original_path
  
          self.params_trained["epochs"] = epochs
          self.params_trained["best_epoch"] = save_callback.best_epoch
  
imports: from __future__ import annotations
  import copy
  import logging
  import time
  from builtins import classmethod
  from functools import partial
  from pathlib import Path
  from typing import Dict, Union
  import numpy
  import pandas
  import sklearn
  from autogluon.common.features.types import R_BOOL, R_CATEGORY, R_DATETIME, R_FLOAT, R_INT, R_OBJECT, S_TEXT_AS_CATEGORY, S_TEXT_NGRAM, S_TEXT_SPECIAL
  from autogluon.common.utils.pandas_utils import get_approximate_df_mem_usage
  from autogluon.common.utils.resource_utils import ResourceManager
  from autogluon.common.utils.try_import import try_import_fastai
  from autogluon.core.constants import BINARY, QUANTILE, REGRESSION
  from autogluon.core.hpo.constants import RAY_BACKEND
  from autogluon.core.models import AbstractModel
  from autogluon.core.utils.exceptions import TimeLimitExceeded
  from autogluon.core.utils.files import make_temp_directory
  from autogluon.core.utils.loaders import load_pkl
  from autogluon.core.utils.savers import save_pkl
  from autogluon.tabular.models.tabular_nn.utils.nn_architecture_utils import infer_y_range
  from .hyperparameters.parameters import get_param_baseline
  from .hyperparameters.searchspaces import get_default_searchspace
label_desc: Use image node_id 5 for calling the NNFastAiTabularModel obj's underlying member method code with example usage: obj._fit(X, y, X_val, y_val, time_limit, num_cpus, num_gpus, sample_weight) without return types