Histogram GBDT can segfault if categorical contains negative categories

Histogram GBDT can segfault if they contain negative categories.

Indeed, we documented that they should all be positive in `[0, max_bins]` but segfaulting is probably not the best error message.

I triggered the problem on my Mac M1 with the following code:

```python
# %%
from sklearn.datasets import fetch_openml

adult = fetch_openml(name="adult", version=2, as_frame=True, parser="pandas")

# %%
adult.frame.head()

# %%
X, y = adult.data, adult.target
X = X.drop(columns=["education", "fnlwgt"])

# %%
y.value_counts()

# %%
from imblearn.datasets import make_imbalance

X, y = make_imbalance(X, y, sampling_strategy={" >50K": 500})

# %%
y.value_counts()

# %%
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

# %%
from sklearn.preprocessing import OrdinalEncoder
from sklearn.compose import make_column_selector, make_column_transformer
from sklearn.pipeline import make_pipeline
from sklearn.ensemble import HistGradientBoostingClassifier

categorical_selector = make_column_selector(dtype_include="category")
preprocessor = make_column_transformer(
    (
        OrdinalEncoder(handle_unknown="use_encoded_value", unknown_value=-1),
        categorical_selector,
    ),
    remainder="passthrough",
).fit(X_train, y_train)

categorical_columns = [
    "ordinalencoder" in val for val in preprocessor.get_feature_names_out()
]
model = make_pipeline(
    preprocessor,
    HistGradientBoostingClassifier(
        max_iter=10_000,
        early_stopping=True,
        categorical_features=categorical_columns,
        random_state=0,
    ),
)
model.fit(X_train, y_train)

# %%
model.predict(X_test)
``` 

It is not obvious that it will always segfault (it did not do that on Linux).

I got the following traceback on a similar code snippet in Linux:

```pytb
Traceback (most recent call last):
  File "/home/glemaitre/miniconda3/envs/dev/lib/python3.8/site-packages/joblib/externals/loky/process_executor.py", line 436, in _process_worker
    r = call_item()
  File "/home/glemaitre/miniconda3/envs/dev/lib/python3.8/site-packages/joblib/externals/loky/process_executor.py", line 288, in __call__
    return self.fn(*self.args, **self.kwargs)
  File "/home/glemaitre/miniconda3/envs/dev/lib/python3.8/site-packages/joblib/_parallel_backends.py", line 595, in __call__
    return self.func(*args, **kwargs)
  File "/home/glemaitre/miniconda3/envs/dev/lib/python3.8/site-packages/joblib/parallel.py", line 262, in __call__
    return [func(*args, **kwargs)
  File "/home/glemaitre/miniconda3/envs/dev/lib/python3.8/site-packages/joblib/parallel.py", line 262, in <listcomp>
    return [func(*args, **kwargs)
  File "/home/glemaitre/Documents/packages/scikit-learn/sklearn/utils/fixes.py", line 117, in __call__
    return self.function(*args, **kwargs)
  File "/home/glemaitre/Documents/packages/scikit-learn/sklearn/ensemble/_bagging.py", line 208, in _parallel_decision_function
    return sum(
  File "/home/glemaitre/Documents/packages/scikit-learn/sklearn/ensemble/_bagging.py", line 209, in <genexpr>
    estimator.decision_function(X[:, features])
  File "/home/glemaitre/Documents/packages/scikit-learn/sklearn/pipeline.py", line 549, in decision_function
    return self.steps[-1][1].decision_function(Xt)
  File "/home/glemaitre/Documents/packages/scikit-learn/sklearn/ensemble/_hist_gradient_boosting/gradient_boosting.py", line 1793, in decision_function
    decision = self._raw_predict(X)
  File "/home/glemaitre/Documents/packages/scikit-learn/sklearn/ensemble/_hist_gradient_boosting/gradient_boosting.py", line 956, in _raw_predict
    self._predict_iterations(
  File "/home/glemaitre/Documents/packages/scikit-learn/sklearn/ensemble/_hist_gradient_boosting/gradient_boosting.py", line 967, in _predict_iterations
    ) = self._bin_mapper.make_known_categories_bitsets()
  File "/home/glemaitre/Documents/packages/scikit-learn/sklearn/ensemble/_hist_gradient_boosting/binning.py", line 314, in make_known_categories_bitsets
    set_bitset_memoryview(known_cat_bitsets[mapped_f_idx], raw_cat_val)
  File "sklearn/ensemble/_hist_gradient_boosting/_bitset.pyx", line 48, in sklearn.ensemble._hist_gradient_boosting._bitset.set_bitset_memoryview
OverflowError: can't convert negative value to npy_uint8
```

So I think that it could be safer to check for negative values before to go forward when `categorical_features` is passed since applying a `-1` marker for unknown categories or rare categories could be something usually done.

COMMENT:
The workaround to get your code working is to use `np.nan` as the unknown value.

I opened https://github.com/scikit-learn/scikit-learn/pull/24283 as a fix by treating the negative values as unknown.

Labels: Bug, module:ensemble