RuntimeError: "Cannot clone object ..." when cloning an estimator that copies parameters in either __init__ or get_params

### Describe the bug

Context: https://github.com/scikit-learn/scikit-learn/issues/15722#issuecomment-893942972

Calling `clone()` on a BaseEstimator that copies parameters results in a RuntimeError, even if the parameters are otherwise equal (`estimator.param == clone(estimator).param` returns True but `estimator.param is clone(estimator.param)` returns False).

Either the documentation has an issue in that this is an unspecified requirement for `clone()` to work (and BaseEstimator `__init__()` and `get_params()` documentation should say that parameters must always be the same object), or the equality check in `clone()` is too strict and should be loosened.

### Steps/Code to Reproduce

```python
from sklearn.base import BaseEstimator, clone

class TestEstimator(BaseEstimator):
    def __init__(self, my_dict):
        self.my_dict = my_dict.copy()

some_dict = {'foo': 'bar'}
estimator = TestEstimator(some_dict)

clone(estimator)      # raises RuntimeError: Cannot clone object TestEstimator(my_dict={'foo': 'bar'}), as the constructor either does not set or modifies parameter my_dict
```

### Expected Results

Calling `clone(estimator)` results in a new TestEstimator where the following assertions are true:

```python
from sklearn.base import BaseEstimator, clone

class TestEstimator(BaseEstimator):
    def __init__(self, my_dict):
        self.my_dict = my_dict.copy()

some_dict = {'foo': 'bar'}
estimator = TestEstimator(some_dict)

new_estimator = clone(estimator)

assert estimator is not new_estimator
assert estimator.some_dict == new_estimator.some_dict        #  this isn't strictly necessary, but if clone() is going to assert equality then this seems like the right kind of check
assert estimator.some_dict is not new_estimator.some_dict

# no RuntimeError or AssertionError should be raised after running this snippet
```

### Actual Results

```python
>>> clone(estimator)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/wchill/miniconda/envs/test38/lib/python3.8/site-packages/sklearn/base.py", line 95, in clone
    raise RuntimeError(
RuntimeError: Cannot clone object TestEstimator(my_dict={'foo': 'bar'}), as the constructor either does not set or modifies parameter my_dict
```

### Versions

```shell
>>> import sklearn; sklearn.show_versions()

System:
    python: 3.8.12 (default, Oct 12 2021, 06:23:56)  [Clang 10.0.0 ]
executable: /Users/wchill/miniconda/envs/test38/bin/python
   machine: macOS-10.16-x86_64-i386-64bit

Python dependencies:
          pip: 21.2.4
   setuptools: 58.0.4
      sklearn: 1.0.2
        numpy: 1.22.3
        scipy: 1.8.0
       Cython: None
       pandas: None
   matplotlib: None
       joblib: 1.1.0
threadpoolctl: 3.1.0

Built with OpenMP: True
```

COMMENT:
So what we can do in `clone` is to do:

```python
        if param1 is not param2 and param1 != param2:
            raise RuntimeError(
                "Cannot clone object %s, as the constructor "
                "either does not set or modifies parameter %s" % (estimator, name)
            )
```

Doesn't seem like a controversial change since it keeps the old behavior, and falls back on `==` in case that fails. But pinging @scikit-learn/core-devs in case there's something I'm missing.

Labels: Bug