Addressed: FastICA logcosh bottleneck

### Describe the workflow you want to enable

For: sklearn/decomposition/_fastica.py

Hello,

Firstly thank you for the amazing library :D

I see in the FastICA there was a bottleneck that was messing a lot with my run time, so i optimized it and compared my implementation with the original and the results are exactly the same. I think you may find it useful. 


### Describe your proposed solution


In my implementation I use logcocsh as a callable


Original Scikit:
def _logcosh(x, fun_args=None):
    alpha = fun_args.get("alpha", 1.0)  # comment it out?

    x *= alpha
    gx = np.tanh(x, x)  # apply the tanh inplace
    g_x = np.empty(x.shape[0], dtype=x.dtype)
    # XXX compute in chunks to avoid extra allocation
    for i, gx_i in enumerate(gx):  # please don't vectorize.
        g_x[i] = (alpha * (1 - gx_i**2)).mean()
    return gx, g_x


My implementation:
def opt_logcosh(x):
    """
    we create our own logcosh, which is modernized and avoids the bottleneck of the original logcosh

    our logcosh vs the original logcosh from sklearn:
        same output, but faster execution time

    """
    tanh = np.tanh(x)
    return tanh, ((1 - tanh**2)).mean(axis=-1)

### Describe alternatives you've considered, if relevant

_No response_

### Additional context

_No response_

COMMENT:
Of course! Below is my implementation in which I have cleaned up the the formatting (sorry for the previous sloppy version) vs the Original.

# **Optimized LogCosh**

```
def opt_logcosh(x):
    """
   
    we create our own logcosh, which is modernized and avoid the bottleneck of the original logcosh

    our logcosh vs the original logcosh from sklearn:
        #same output, but faster execution time
        Original: ~1h 20min for each subject x 50 runs x 2 chromophores
        Ours: ~2 mins for each subject x 50 runs x 2 chromophores
    """
    tanh = np.tanh(x)
    return tanh, ((1 - tanh**2)).mean(axis=-1)
```

# **Sklearn LogCosh**

```
"""
Some standard non-linear functions.
XXX: these should be optimized, as they can be a bottleneck.
"""
def _logcosh(x, fun_args=None):
    alpha = fun_args.get("alpha", 1.0)  # comment it out?

    x *= alpha
    gx = np.tanh(x, x)  # apply the tanh inplace
    g_x = np.empty(x.shape[0], dtype=x.dtype)
    # XXX compute in chunks to avoid extra allocation
    for i, gx_i in enumerate(gx):  # please don't vectorize.
        g_x[i] = (alpha * (1 - gx_i**2)).mean()
    return gx, g_x

```

# **Parameters used in ICA**

```
FastICA(
    n_components=pca_hbo_transformed.shape[1],  # For my use case is usually around 20-50
    algorithm="deflation",          
    max_iter=10000,               
    fun=function,   # opt_logcosh or logcosh
    random_state=starting_point+i,  # starting_point =2025 + a range of seeds = [0 to 49] // So 50 iterations 
    tol = 1e-6
    ) 
```


**Both my opt_logcosh and the sklearn yield the same results. So, I am confident it works well!**

# **Estimated time**
FastICA with **sklearn logcosh**: for 1 subject x 50 runs (50 random seeds) x 2 chromophores (this is for my use cane) **= ~ 1h 30min**
FastICA with **opt_logcosh**: for 1 subject x 50 runs x 2 chromophores **= ~ 2min**

Labels: New Feature, Needs Triage