SelfTrainingClassifier on a Pipeline

### Describe the bug

SelfTrainingClassifier cannot be fit on text data even if the base_estimator parameter is an estimator that can accept text data (e.g. a pipeline with text preprocessing). In particular, it seems that SelfTrainingClassifier validates the data on the classifier within the pipeline (hence being unable to accept text) instead of validating on the entire pipeline (which can accept text).

### Steps/Code to Reproduce

```python
import pandas as pd
from sklearn.datasets import fetch_20newsgroups
from sklearn.compose import ColumnTransformer
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.pipeline import Pipeline
from sklearn.naive_bayes import MultinomialNB
from sklearn.semi_supervised import SelfTrainingClassifier

#pulling text data
categories = ['alt.atheism', 'soc.religion.christian']
twenty_train = fetch_20newsgroups(subset='train', categories=categories, shuffle=True, random_state=42)

X = pd.DataFrame({'text': twenty_train.data})
y = twenty_train.target
#changing some labels to -1 for semi supervised
y[len(y) - 100:len(y)] = -1

#building pipeline
preprocessor = ColumnTransformer(
    transformers =  [
        ('vectorizer', CountVectorizer(), 'text')
    ])

classifier = MultinomialNB()

pipe = Pipeline(steps = [('preprocessor', preprocessor), ('classifier', classifier)])

#fitting SelfTrainingClassifier with the pipeline as the base estimator
stc = SelfTrainingClassifier(pipe)
stc.fit(X,y)

#note that fitting pipe does work
#pipe.fit(X,y)
```

### Expected Results

No error is thrown.

### Actual Results

```python-traceback
ValueError                                Traceback (most recent call last)
Input In [27], in <cell line: 30>()
     28 #fitting SelfTrainingClassifier with the pipeline as the base estimator
     29 stc = SelfTrainingClassifier(pipe)
---> 30 stc.fit(X,y)

File ~/code/scikit-learn/sklearn/semi_supervised/_self_training.py:181, in SelfTrainingClassifier.fit(self, X, y)
    162 """
    163 Fit self-training classifier using `X`, `y` as training data.
    164 
   (...)
    177     Fitted estimator.
    178 """
    179 # we need row slicing support for sparce matrices, but costly finiteness check
    180 # can be delegated to the base estimator.
--> 181 X, y = self._validate_data(
    182     X, y, accept_sparse=["csr", "csc", "lil", "dok"], force_all_finite=False
    183 )
    185 if self.base_estimator is None:
    186     raise ValueError("base_estimator cannot be None!")

File ~/code/scikit-learn/sklearn/base.py:596, in BaseEstimator._validate_data(self, X, y, reset, validate_separately, **check_params)
    594         y = check_array(y, input_name="y", **check_y_params)
    595     else:
--> 596         X, y = check_X_y(X, y, **check_params)
    597     out = X, y
    599 if not no_val_X and check_params.get("ensure_2d", True):

File ~/code/scikit-learn/sklearn/utils/validation.py:1074, in check_X_y(X, y, accept_sparse, accept_large_sparse, dtype, order, copy, force_all_finite, ensure_2d, allow_nd, multi_output, ensure_min_samples, ensure_min_features, y_numeric, estimator)
   1069         estimator_name = _check_estimator_name(estimator)
   1070     raise ValueError(
   1071         f"{estimator_name} requires y to be passed, but the target y is None"
   1072     )
-> 1074 X = check_array(
   1075     X,
   1076     accept_sparse=accept_sparse,
   1077     accept_large_sparse=accept_large_sparse,
   1078     dtype=dtype,
   1079     order=order,
   1080     copy=copy,
   1081     force_all_finite=force_all_finite,
   1082     ensure_2d=ensure_2d,
   1083     allow_nd=allow_nd,
   1084     ensure_min_samples=ensure_min_samples,
   1085     ensure_min_features=ensure_min_features,
   1086     estimator=estimator,
   1087     input_name="X",
   1088 )
   1090 y = _check_y(y, multi_output=multi_output, y_numeric=y_numeric, estimator=estimator)
   1092 check_consistent_length(X, y)

File ~/code/scikit-learn/sklearn/utils/validation.py:856, in check_array(array, accept_sparse, accept_large_sparse, dtype, order, copy, force_all_finite, ensure_2d, allow_nd, ensure_min_samples, ensure_min_features, estimator, input_name)
    854         array = array.astype(dtype, casting="unsafe", copy=False)
    855     else:
--> 856         array = np.asarray(array, order=order, dtype=dtype)
    857 except ComplexWarning as complex_warning:
    858     raise ValueError(
    859         "Complex data not supported\n{}\n".format(array)
    860     ) from complex_warning

File ~/mambaforge/envs/dev/lib/python3.10/site-packages/pandas/core/generic.py:2064, in NDFrame.__array__(self, dtype)
   2063 def __array__(self, dtype: npt.DTypeLike | None = None) -> np.ndarray:
-> 2064     return np.asarray(self._values, dtype=dtype)

ValueError: could not convert string to float: 'From: [...]'
```

### Versions

```shell
System:
    python: 3.10.4 

Python dependencies:
          pip: 22.0.4
   setuptools: 62.1.0
      sklearn: 1.0.2
        numpy: 1.21.5
        scipy: 1.7.3
       Cython: None
       pandas: 1.4.2
   matplotlib: None
       joblib: 1.1.0
threadpoolctl: 3.1.0

Built with OpenMP: True
```

COMMENT:
I think that what @glemaitre proposes would probably be more computationally efficient: vectorize all doc at once and then run the self training loop on subsets.

What @AshwinPuri13 suggest is slightly different but probably more expensive because it can lead to redundant vectorization of the same documents over and over again.

Irrespective about what is the "right" approach, I think both should work and I think that a meta-estimator like `SelfTrainingClassifier` should not attempt to convert the input to an array of features by itself and instead delegate the validation as much as possible to the underlying base estimator.

Labels: Bug, module:semi_supervised