def grid_search(self, X_train, y_train, ratio=None):
        '''
        implement the grid search for unsupervised models and return the best hyper-parameters
        the ratio could be the ground truth anomaly ratio of input dataset
        '''

        # set seed
        self.utils.set_seed(self.seed)
        # get the hyper-parameter grid
        param_grid = self.grid_hp(self.model_name)

        if param_grid is not None:
            # index of normal ana abnormal samples
            idx_a = np.where(y_train==1)[0]
            idx_n = np.where(y_train==0)[0]
            idx_n = np.random.choice(idx_n, int((len(idx_a) * (1-ratio)) / ratio), replace=True)

            idx = np.append(idx_n, idx_a) #combine
            np.random.shuffle(idx) #shuffle

            # valiation set (and the same anomaly ratio as in the original dataset)
            X_val = X_train[idx]
            y_val = y_train[idx]

            # fitting
            metric_list = []
            for param in param_grid:
                try:
                    if self.model_name == 'IForest':
                        model = self.model_dict[self.model_name](n_estimators=param).fit(X_train)

                    elif self.model_name == 'OCSVM':
                        model = self.model_dict[self.model_name](kernel=param).fit(X_train)

                    elif self.model_name == 'ABOD':
                        model = self.model_dict[self.model_name](n_neighbors=param).fit(X_train)

                    elif self.model_name == 'CBLOF':
                        model = self.model_dict[self.model_name](n_clusters=param).fit(X_train)

                    elif self.model_name == 'COF':
                        model = self.model_dict[self.model_name](n_neighbors=param).fit(X_train)

                    elif self.model_name == 'FeatureBagging':
                        model = self.model_dict[self.model_name](n_estimators=param).fit(X_train)

                    elif self.model_name == 'HBOS':
                        model = self.model_dict[self.model_name](n_bins=param).fit(X_train)

                    elif self.model_name == 'KNN':
                        model = self.model_dict[self.model_name](n_neighbors=param).fit(X_train)

                    elif self.model_name == 'LMDD':
                        model = self.model_dict[self.model_name](dis_measure=param).fit(X_train)

                    elif self.model_name == 'LODA':
                        model = self.model_dict[self.model_name](n_bins=param).fit(X_train)

                    elif self.model_name == 'LOF':
                        model = self.model_dict[self.model_name](n_neighbors=param).fit(X_train)

                    elif self.model_name == 'LOCI':
                        model = self.model_dict[self.model_name](alpha=param).fit(X_train)

                    elif self.model_name == 'LSCP':
                        model = self.model_dict[self.model_name](detector_list=[LOF(),LOF()], n_bins=param).fit(X_train)

                    elif self.model_name == 'PCA':
                        model = self.model_dict[self.model_name](n_components=param).fit(X_train)

                    elif self.model_name == 'SOD':
                        model = self.model_dict[self.model_name](n_neighbors=param).fit(X_train)

                    elif self.model_name == 'SOS':
                        model = self.model_dict[self.model_name](perplexity=param).fit(X_train)

                    elif self.model_name == 'SOGAAL':
                        model = self.model_dict[self.model_name](stop_epochs=param).fit(X_train)

                    elif self.model_name == 'MOGAAL':
                        model = self.model_dict[self.model_name](stop_epochs=param).fit(X_train)

                    elif self.model_name == 'DeepSVDD':
                        model = self.model_dict[self.model_name](epochs=param).fit(X_train)

                    else:
                        raise NotImplementedError

                except:
                    metric_list.append(0.0)
                    continue

                try:
                    # model performance on the validation set
                    score_val = model.decision_function(X_val)
                    metric = self.utils.metric(y_true=y_val, y_score=score_val, pos_label=1)
                    metric_list.append(metric['aucpr'])

                except:
                    metric_list.append(0.0)
                    continue

            best_param = param_grid[np.argmax(metric_list)]

        else:
            metric_list = None
            best_param = None

        print(f'The candidate hyper-parameter of {self.model_name}: {param_grid},',
              f' corresponding metric: {metric_list}',
              f' the best candidate: {best_param}')

        return best_param

        implement the grid search for unsupervised models and return the best hyper-parameters
        the ratio could be the ground truth anomaly ratio of input dataset
        
adbench/baseline/PyOD.py