# Buggy code
def _preprocess_data(self, data: pd.DataFrame) -> pd.DataFrame:
    """
    Scale continuous variables, encode categories and set aside target and weight.

    Args:
        data (pd.DataFrame): original data

    Returns:
        pd.DataFrame: pre-processed dataframe
    """
    # encode group ids - this encoding
    for name, group_name in self._group_ids_mapping.items():
        self.categorical_encoders[group_name] = NaNLabelEncoder().fit(
            data[name].to_numpy().reshape(-1)
        )
        data[group_name] = self.transform_values(
            name, data[name], inverse=False, group_id=True
        )

    # encode categoricals
    if isinstance(
        self.target_normalizer, (GroupNormalizer, MultiNormalizer)
    ):  # if we use a group normalizer, group_ids must be encoded as well
        group_ids_to_encode = self.group_ids
    else:
        group_ids_to_encode = []
    for name in set(group_ids_to_encode + self.categoricals):
        allow_nans = name in self.dropout_categoricals
        if name in self.variable_groups:  # fit groups
            columns = self.variable_groups[name]
            if name not in self.categorical_encoders:
                self.categorical_encoders[name] = NaNLabelEncoder(
                    add_nan=allow_nans
                ).fit(data[columns].to_numpy().reshape(-1))
            elif self.categorical_encoders[name] is not None:
                try:
                    check_is_fitted(self.categorical_encoders[name])
                except NotFittedError:
                    self.categorical_encoders[name] = self.categorical_encoders[
                        name
                    ].fit(data[columns].to_numpy().reshape(-1))
        else:
            if name not in self.categorical_encoders:
                self.categorical_encoders[name] = NaNLabelEncoder(
                    add_nan=allow_nans
                ).fit(data[name])
            elif (
                self.categorical_encoders[name] is not None
                and name not in self.target_names
            ):
                try:
                    check_is_fitted(self.categorical_encoders[name])
                except NotFittedError:
                    self.categorical_encoders[name] = self.categorical_encoders[
                        name
                    ].fit(data[name])

    # encode them
    for name in set(group_ids_to_encode + self.flat_categoricals):
        if name not in self.target_names:
            data[name] = self.transform_values(name, data[name], inverse=False)

    # save special variables
    assert "__time_idx__" not in data.columns, (
        "__time_idx__ is a protected column and must not be present in data"
    )
    data["__time_idx__"] = data[self.time_idx]  # save unscaled
    for target in self.target_names:
        assert f"__target__{target}" not in data.columns, (
            f"__target__{target} is a protected column and must not be present in data"
        )
        data[f"__target__{target}"] = data[target]
    if self.weight is not None:
        data["__weight__"] = data[self.weight]

    # train target normalizer
    if self.target_normalizer is not None:
        # fit target normalizer
        try:
            check_is_fitted(self.target_normalizer)
        except NotFittedError:
            if isinstance(self.target_normalizer, EncoderNormalizer):
                self.target_normalizer.fit(data[self.target])
            elif isinstance(self.target_normalizer, (GroupNormalizer, MultiNormalizer)):
                self.target_normalizer.fit(data[self.target], data)
            else:
                self.target_normalizer.fit(data[self.target])

        # transform target
        if isinstance(self.target_normalizer, EncoderNormalizer):
            # we approximate the scales and target transformation by assuming one
            # transformation over the entire time range but by each group
            common_init_args = [
                name
                for name in inspect.signature(
                    GroupNormalizer.__init__
                ).parameters.keys()
                if name
                in inspect.signature(EncoderNormalizer.__init__).parameters.keys()
                and name not in ["data", "self"]
            ]
            copy_kwargs = {
                name: getattr(self.target_normalizer, name) for name in common_init_args
            }
            normalizer = GroupNormalizer(groups=self.group_ids, **copy_kwargs)
            data[self.target], scales = normalizer.fit_transform(
                data[self.target], data, return_norm=True
            )
        elif isinstance(self.target_normalizer, GroupNormalizer):
            data[self.target], scales = self.target_normalizer.transform(
                data[self.target], data, return_norm=True
            )
        elif isinstance(self.target_normalizer, MultiNormalizer):
            transformed, scales = self.target_normalizer.transform(
                data[self.target], data, return_norm=True
            )
            for idx, target in enumerate(self.target_names):
                data[target] = transformed[idx]
        elif isinstance(self.target_normalizer, NaNLabelEncoder):
            data[self.target] = self.target_normalizer.transform(data[self.target])
            data[f"__target__{self.target}"] = data[
                self.target
            ]  # overwrite target because it requires encoding (continuous targets should not be normalized)
            scales = None
        else:
            data[self.target], scales = self.target_normalizer.transform(
                data[self.target], return_norm=True
            )

        # add target scales
        if self.add_target_scales:
            if not isinstance(self.target_normalizer, MultiNormalizer):
                scales = [scales]
            for target_idx, target in enumerate(self.target_names):
                if not isinstance(self.target_normalizers[target_idx], NaNLabelEncoder):
                    for scale_idx, name in enumerate(["center", "scale"]):
                        feature_name = f"{target}_{name}"
                        assert feature_name not in data.columns, (
                            f"{feature_name} is a protected column and must not be present in data"
                        )
                        data[feature_name] = scales[target_idx][:, scale_idx].squeeze()
                        if feature_name not in self.reals:
                            self.static_reals.append(feature_name)
    # rescale continuous variables apart from target
    for name in self.reals:
        if name in self.target_names:
            continue
        if name not in self.scalers:
            self.scalers[name] = StandardScaler().fit(data[[name]])
        elif self.scalers[name] is not None:
            try:
                check_is_fitted(self.scalers[name])
            except NotFittedError:
                if isinstance(self.scalers[name], GroupNormalizer):
                    self.scalers[name] = self.scalers[name].fit(data[[name]], data)
                else:
                    self.scalers[name] = self.scalers[name].fit(data[[name]])
        if self.scalers[name] is not None and not isinstance(
            self.scalers[name], EncoderNormalizer
        ):
            data[name] = self.transform_values(
                name, data[name], data=data, inverse=False
            )

    # encode constant values
    self.encoded_constant_fill_strategy = {}
    for name, value in self.constant_fill_strategy.items():
        if name in self.target_names:
            self.encoded_constant_fill_strategy[f"__target__{name}"] = value
        self.encoded_constant_fill_strategy[name] = self.transform_values(
            name, np.array([value]), data=data, inverse=False
        )[0]
    return data


# Traceback (RuntimeError)
RuntimeError                              Traceback (most recent call last)
<ipython-input-26-a92b5627800b> in <module>
1 # find optimal learning rate
----> 2 res = trainer.tuner.lr_find(
3     tft,
4     train_dataloader=train_dataloader,
5     val_dataloaders=val_dataloader,

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\pytorch_lightning\tuner\tuning.py in lr_find(self, model, train_dataloader, val_dataloaders, min_lr, max_lr, num_training, mode, early_stop_threshold, datamodule)
118             datamodule: Optional[LightningDataModule] = None
119     ):
--> 120         return lr_find(
121             self.trainer,
122             model,

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\pytorch_lightning\tuner\lr_finder.py in lr_find(trainer, model, train_dataloader, val_dataloaders, min_lr, max_lr, num_training, mode, early_stop_threshold, datamodule)
167
168     # Fit, lr &amp; loss logged in callback
--> 169     trainer.fit(model,
170                 train_dataloader=train_dataloader,
171                 val_dataloaders=val_dataloaders,

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\pytorch_lightning\trainer\trainer.py in fit(self, model, train_dataloader, val_dataloaders, datamodule)
444         self.call_hook('on_fit_start')
445
--> 446         results = self.accelerator_backend.train()
447         self.accelerator_backend.teardown()
448

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\pytorch_lightning\accelerators\cpu_accelerator.py in train(self)
57
58         # train or test
---> 59         results = self.train_or_test()
60         return results
61

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\pytorch_lightning\accelerators\accelerator.py in train_or_test(self)
64             results = self.trainer.run_test()
65         else:
---> 66             results = self.trainer.train()
67         return results
68

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\pytorch_lightning\trainer\trainer.py in train(self)
493
494                 # run train epoch
--> 495                 self.train_loop.run_training_epoch()
496
497                 if self.max_steps and self.max_steps <= self.global_step:

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\pytorch_lightning\trainer\training_loop.py in run_training_epoch(self)
559             # TRAINING_STEP + TRAINING_STEP_END
560             # ------------------------------------
--> 561             batch_output = self.run_training_batch(batch, batch_idx, dataloader_idx)
562
563             # when returning -1 from train_step, we end epoch early

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\pytorch_lightning\trainer\training_loop.py in run_training_batch(self, batch, batch_idx, dataloader_idx)
726
727                         # optimizer step
--> 728                         self.optimizer_step(optimizer, opt_idx, batch_idx, train_step_and_backward_closure)
729
730                     else:

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\pytorch_lightning\trainer\training_loop.py in optimizer_step(self, optimizer, opt_idx, batch_idx, train_step_and_backward_closure, *args, **kwargs)
467         with self.trainer.profiler.profile("optimizer_step"):
468             # optimizer step lightningModule hook
--> 469             self.trainer.accelerator_backend.optimizer_step(
470                 optimizer, batch_idx, opt_idx, train_step_and_backward_closure, *args, **kwargs
471             )

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\pytorch_lightning\accelerators\accelerator.py in optimizer_step(self, optimizer, batch_idx, opt_idx, lambda_closure, *args, **kwargs)
112
113         # model hook
--> 114         model_ref.optimizer_step(
115             epoch=self.trainer.current_epoch,
116             batch_idx=batch_idx,

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\pytorch_lightning\core\lightning.py in optimizer_step(self, epoch, batch_idx, optimizer, optimizer_idx, optimizer_closure, on_tpu, using_native_amp, using_lbfgs, *args, **kwargs)
1378             optimizer.step(*args, **kwargs)
1379         else:
-> 1380             optimizer.step(closure=optimizer_closure, *args, **kwargs)
1381
1382     def optimizer_zero_grad(

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\torch\optim\lr_scheduler.py in wrapper(*args, **kwargs)
65                 instance._step_count += 1
66                 wrapped = func.__get__(instance, cls)
---> 67                 return wrapped(*args, **kwargs)
68
69             # Note that the returned function here is no longer a bound method,

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\pytorch_forecasting\optim.py in step(self, closure)
129             closure: A closure that reevaluates the model and returns the loss.
130         """
--> 131         _ = closure()
132         loss = None
133         # note - below is commented out b/c I have other work that passes back

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\pytorch_lightning\trainer\training_loop.py in train_step_and_backward_closure()
716
717                         def train_step_and_backward_closure():
--> 718                             result = self.training_step_and_backward(
719                                 split_batch,
720                                 batch_idx,

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\pytorch_lightning\trainer\training_loop.py in training_step_and_backward(self, split_batch, batch_idx, opt_idx, optimizer, hiddens)
821             # backward pass
822             with self.trainer.profiler.profile("model_backward"):
--> 823                 self.backward(result, optimizer, opt_idx)
824
825             # hook - call this hook only

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\pytorch_lightning\trainer\training_loop.py in backward(self, result, optimizer, opt_idx, *args, **kwargs)
841             self.trainer.accelerator_backend.backward(result, optimizer, opt_idx, *args, **kwargs)
842         else:
--> 843             result.closure_loss = self.trainer.accelerator_backend.backward(
844                 result.closure_loss, optimizer, opt_idx, *args, **kwargs
845             )

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\pytorch_lightning\accelerators\accelerator.py in backward(self, closure_loss, optimizer, opt_idx, *args, **kwargs)
93             # do backward pass
94             model = self.trainer.get_model()
---> 95             model.backward(closure_loss, optimizer, opt_idx, *args, **kwargs)
96
97             # once backward has been applied, release graph

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\pytorch_lightning\core\lightning.py in backward(self, loss, optimizer, optimizer_idx, *args, **kwargs)
1256         """
1257         if self.trainer.train_loop.automatic_optimization or self._running_manual_backward:
-> 1258             loss.backward(*args, **kwargs)
1259
1260     def toggle_optimizer(self, optimizer: Optimizer, optimizer_idx: int):

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\torch\tensor.py in backward(self, gradient, retain_graph, create_graph)
219                 retain_graph=retain_graph,
220                 create_graph=create_graph)
--> 221         torch.autograd.backward(self, gradient, retain_graph, create_graph)
222
223     def register_hook(self, hook):

c:\users\u2\appdata\local\programs\python\python38\lib\site-packages\torch\autograd\__init__.py in backward(tensors, grad_tensors, retain_graph, create_graph, grad_variables)
128         retain_graph = create_graph
129
--> 130     Variable._execution_engine.run_backward(
131         tensors, grad_tensors_, retain_graph, create_graph,
132         allow_unreachable=True)  # allow_unreachable flag

RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn

# Fixed code
def _preprocess_data(self, data: pd.DataFrame) -> pd.DataFrame:
    """
    Scale continuous variables, encode categories and set aside target and weight.

    Args:
        data (pd.DataFrame): original data

    Returns:
        pd.DataFrame: pre-processed dataframe
    """
    # encode group ids - this encoding
    for name, group_name in self._group_ids_mapping.items():
        self.categorical_encoders[group_name] = NaNLabelEncoder().fit(
            data[name].to_numpy().reshape(-1)
        )
        data[group_name] = self.transform_values(
            name, data[name], inverse=False, group_id=True
        )

    # encode categoricals
    if isinstance(
        self.target_normalizer, (GroupNormalizer, MultiNormalizer)
    ):  # if we use a group normalizer, group_ids must be encoded as well
        group_ids_to_encode = self.group_ids
    else:
        group_ids_to_encode = []
    for name in set(group_ids_to_encode + self.categoricals):
        allow_nans = name in self.dropout_categoricals
        if name in self.variable_groups:  # fit groups
            columns = self.variable_groups[name]
            if name not in self.categorical_encoders:
                self.categorical_encoders[name] = NaNLabelEncoder(
                    add_nan=allow_nans
                ).fit(data[columns].to_numpy().reshape(-1))
            elif self.categorical_encoders[name] is not None:
                try:
                    check_is_fitted(self.categorical_encoders[name])
                except NotFittedError:
                    self.categorical_encoders[name] = self.categorical_encoders[
                        name
                    ].fit(data[columns].to_numpy().reshape(-1))
        else:
            if name not in self.categorical_encoders:
                self.categorical_encoders[name] = NaNLabelEncoder(
                    add_nan=allow_nans
                ).fit(data[name])
            elif (
                self.categorical_encoders[name] is not None
                and name not in self.target_names
            ):
                try:
                    check_is_fitted(self.categorical_encoders[name])
                except NotFittedError:
                    self.categorical_encoders[name] = self.categorical_encoders[
                        name
                    ].fit(data[name])

    # encode them
    for name in set(group_ids_to_encode + self.flat_categoricals):
        if name not in self.target_names:
            data[name] = self.transform_values(name, data[name], inverse=False)

    # save special variables
    assert "__time_idx__" not in data.columns, (
        "__time_idx__ is a protected column and must not be present in data"
    )
    data["__time_idx__"] = data[self.time_idx]  # save unscaled
    for target in self.target_names:
        assert f"__target__{target}" not in data.columns, (
            f"__target__{target} is a protected column and must not be present in data"
        )
        data[f"__target__{target}"] = data[target]
    if self.weight is not None:
        data["__weight__"] = data[self.weight]

    # train target normalizer
    if self.target_normalizer is not None:
        # fit target normalizer
        try:
            check_is_fitted(self.target_normalizer)
        except NotFittedError:
            if isinstance(self.target_normalizer, EncoderNormalizer):
                self.target_normalizer.fit(data[self.target])
            elif isinstance(self.target_normalizer, (GroupNormalizer, MultiNormalizer)):
                self.target_normalizer.fit(data[self.target], data)
            else:
                self.target_normalizer.fit(data[self.target])

        # transform target
        if isinstance(self.target_normalizer, EncoderNormalizer):
            # we approximate the scales and target transformation by assuming one
            # transformation over the entire time range but by each group
            common_init_args = [
                name
                for name in inspect.signature(
                    GroupNormalizer.__init__
                ).parameters.keys()
                if name
                in inspect.signature(EncoderNormalizer.__init__).parameters.keys()
                and name not in ["data", "self"]
            ]
            copy_kwargs = {
                name: getattr(self.target_normalizer, name) for name in common_init_args
            }
            normalizer = GroupNormalizer(groups=self.group_ids, **copy_kwargs)
            data[self.target], scales = normalizer.fit_transform(
                data[self.target], data, return_norm=True
            )

        elif isinstance(self.target_normalizer, GroupNormalizer):
            data[self.target], scales = self.target_normalizer.transform(
                data[self.target], data, return_norm=True
            )

        elif isinstance(self.target_normalizer, MultiNormalizer):
            transformed, scales = self.target_normalizer.transform(
                data[self.target], data, return_norm=True
            )

            for idx, target in enumerate(self.target_names):
                data[target] = transformed[idx]

                if isinstance(self.target_normalizer[idx], NaNLabelEncoder):
                    # overwrite target because it requires encoding (continuous targets should not be normalized)
                    data[f"__target__{target}"] = data[target]

        elif isinstance(self.target_normalizer, NaNLabelEncoder):
            data[self.target] = self.target_normalizer.transform(data[self.target])
            # overwrite target because it requires encoding (continuous targets should not be normalized)
            data[f"__target__{self.target}"] = data[self.target]
            scales = None

        else:
            data[self.target], scales = self.target_normalizer.transform(
                data[self.target], return_norm=True
            )

        # add target scales
        if self.add_target_scales:
            if not isinstance(self.target_normalizer, MultiNormalizer):
                scales = [scales]
            for target_idx, target in enumerate(self.target_names):
                if not isinstance(self.target_normalizers[target_idx], NaNLabelEncoder):
                    for scale_idx, name in enumerate(["center", "scale"]):
                        feature_name = f"{target}_{name}"
                        assert feature_name not in data.columns, (
                            f"{feature_name} is a protected column and must not be present in data"
                        )
                        data[feature_name] = scales[target_idx][:, scale_idx].squeeze()
                        if feature_name not in self.reals:
                            self.static_reals.append(feature_name)
    # rescale continuous variables apart from target
    for name in self.reals:
        if name in self.target_names:
            continue
        if name not in self.scalers:
            self.scalers[name] = StandardScaler().fit(data[[name]])
        elif self.scalers[name] is not None:
            try:
                check_is_fitted(self.scalers[name])
            except NotFittedError:
                if isinstance(self.scalers[name], GroupNormalizer):
                    self.scalers[name] = self.scalers[name].fit(data[[name]], data)
                else:
                    self.scalers[name] = self.scalers[name].fit(data[[name]])
        if self.scalers[name] is not None and not isinstance(
            self.scalers[name], EncoderNormalizer
        ):
            data[name] = self.transform_values(
                name, data[name], data=data, inverse=False
            )

    # encode constant values
    self.encoded_constant_fill_strategy = {}
    for name, value in self.constant_fill_strategy.items():
        if name in self.target_names:
            self.encoded_constant_fill_strategy[f"__target__{name}"] = value
        self.encoded_constant_fill_strategy[name] = self.transform_values(
            name, np.array([value]), data=data, inverse=False
        )[0]
    return data