# skorch: a scikit-learn wrapper for PyTorch, and when it is the wrong tool

> skorch turns a PyTorch nn.Module into an estimator that fits the scikit-learn API, so grid search, pipelines and early stopping work on neural networks. It is a thin adapter, not a training framework, and that distinction decides whether you want it.

**skorch-dev/skorch** — A scikit-learn compatible neural network library that wraps PyTorch

- Repository: https://github.com/skorch-dev/skorch
- Stars: 6,179 · Forks: 420
- Language: Jupyter Notebook
- License: BSD-3-Clause
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/skorch-dev-skorch

## The problem skorch solves: PyTorch models that cannot enter a scikit-learn pipeline

A PyTorch model is a nn.Module plus a training loop you wrote yourself. A scikit-learn estimator is an object with fit, predict and get_params, which is what makes Pipeline, GridSearchCV and cross_val_score work. Those two worlds do not connect by default. skorch is the adapter between them: it wraps a PyTorch module in a class such as NeuralNetClassifier or NeuralNetRegressor and exposes the estimator interface. The README describes it as "a scikit-learn compatible neural network library that wraps PyTorch", and that is the whole claim. It is aimed at people who already know PyTorch and already use scikit-learn for the rest of their workflow, and who do not want to hand-write the loop that connects them. If you only ever train one model once, the wrapper adds a layer you will not use. If you run hyperparameter searches over a dozen configurations, it removes a lot of glue code.

## How the wrapper works: a module, a criterion and a set of params

The mechanism is parameter routing. You pass your nn.Module class to the estimator, and skorch instantiates it, runs the forward pass, computes the loss and steps the optimizer. Anything you want to configure inside the module is addressed with a double underscore: module__num_units reaches the num_units argument of your module's __init__, and iterator_train__shuffle reaches the shuffle setting of the training data iterator. That naming convention is the same one scikit-learn uses for pipeline steps, so GridSearchCV can search over module hyperparameters without any custom code. Training progress and per-epoch behaviour are handled by callbacks: the README lists LRScheduler, EpochScoring, EarlyStopping, Checkpoint, Freezer and ProgressBar among the built-in ones. This is a design where the training loop is fixed and the variation happens through callbacks and parameters. That is what makes it searchable, and it is also the constraint: if your training procedure does not fit the loop, you are fighting the abstraction rather than using it.

## Installing skorch and fitting a first classifier

skorch requires Python 3.9 or higher according to the README installation section, while pyproject.toml declares requires-python >=3.10. Treat 3.10 as the real floor. Installation is a single pip command, and the README recommends a virtual environment.

```bash
python -m pip install -U skorch
```

There is also a conda path, but the README states plainly that the conda channel is not managed by the skorch maintainers, so version lag is possible there. The first real use is a classifier over a synthetic dataset. The README gives this example: define a module, hand it to NeuralNetClassifier, call fit. Note that the input arrays are cast to float32 and the labels to int64, which is what the example does and what PyTorch expects.

```python
import numpy as np
from sklearn.datasets import make_classification
from torch import nn
from skorch import NeuralNetClassifier

X, y = make_classification(1000, 20, n_informative=10, random_state=0)
X = X.astype(np.float32)
y = y.astype(np.int64)
```

Then the estimator itself. max_epochs and lr are top-level arguments, and iterator_train__shuffle is set through the double-underscore convention.

```python
net = NeuralNetClassifier(
    MyModule,
    max_epochs=10,
    lr=0.1,
    iterator_train__shuffle=True,
)
net.fit(X, y)
y_proba = net.predict_proba(X)
```

After fit returns, predict_proba gives you an array with one column per class. From here the README shows the two moves that justify the wrapper: dropping net into a Pipeline next to StandardScaler, and passing it to GridSearchCV with parameters like lr, max_epochs and module__num_units. The grid search example calls net.set_params(train_split=False, verbose=0) first, because skorch runs its own internal train-validation split by default and that would conflict with the outer cross-validation.

## Where skorch gets in the way

The fixed training loop is the main limitation. If you need two optimizers stepping on different schedules, gradient accumulation across micro-batches, or a custom loop that branches on batch content, you will be writing callbacks that reach into the internals rather than expressing the idea directly. The second cost is the parameter namespace. Once you have module__submodule__layer__size style keys, typos surface as confusing failures rather than clear errors, and reading a GridSearchCV parameter grid becomes harder than reading a nested config dict. The third is the internal train-validation split. It is on by default, which is convenient when you are not cross-validating and a source of double-splitting when you are, which is why the README's grid search example disables it explicitly. There is also a version floor to watch: pyproject.toml declares requires-python >=3.10 and dependencies on numpy, scikit-learn, scipy, tabulate and tqdm, so skorch inherits whatever constraints those impose. The Dockerfile in the repository is not a supported install path for current versions; it pins nvidia/cuda:9.0-cudnn7-runtime and python3.5-dev, which is far older than the Python versions the project metadata declares.

## skorch compared with PyTorch Lightning and plain PyTorch loops

The honest comparison is with PyTorch Lightning. Lightning asks you to restructure your model into a LightningModule with training_step, configure_optimizers and so on; in exchange it owns the loop, the device placement, the logging and the distributed launch. skorch does the opposite: your nn.Module stays as it is, and the wrapper conforms to scikit-learn's estimator protocol. The practical difference shows up at the edges. With skorch, GridSearchCV and Pipeline work without adapters, and your model remains a plain PyTorch module you can instantiate and run outside skorch. With Lightning, you get a training framework with its own lifecycle, and integrating it into a scikit-learn search means writing a wrapper yourself. Against a hand-written PyTorch loop, skorch trades control for the estimator interface and the callback library. If your training loop is stable and you never search hyperparameters, the hand-written loop is less machinery.

## Maintenance, releases and what the licence permits

The repository is not archived, and the last push was on 2026-09-08. The most recent release is v1.4.0 from 2026-05-14, preceded by v1.3.1 in December 2025 and v1.3.0 in November 2025. That is a project with a release cadence measured in months, not days, and the CHANGES.md file at the repository root is where upgrade notes live; read it before moving between minor versions. The licence is BSD-3-Clause, declared both in the repository and in pyproject.toml under license = "BSD-3-Clause". That is a permissive licence, which means the usual obligations around retaining the copyright notice and licence text apply when you redistribute, and it does not come with a patent grant of the kind Apache-2.0 includes. This is not legal advice; if you are shipping skorch inside a product, have your own counsel read the LICENSE file. Upgrade cost is mostly the parameter namespace and callback behaviour: a minor release can change defaults inside the training loop, and your GridSearchCV grids encode those defaults implicitly.

## Conclusion

Adopt skorch if your model already lives in a PyTorch nn.Module and the surrounding work is scikit-learn: cross-validation, pipelines, GridSearchCV, early stopping, checkpointing. Do not adopt it if you need custom multi-optimizer training loops, distributed launch scripts or gradient accumulation schedules that do not map onto fit, or if you are already committed to PyTorch Lightning or raw PyTorch training loops. Before committing, verify the installed version against the v1.4.0 release notes, confirm that your torch build matches the Python version you pinned (the project metadata requires Python 3.10 or higher), and check that the callbacks you depend on appear in the callbacks documentation rather than only in the README feature list. The library is BSD-3-Clause, so the licence itself is not the thing to negotiate; the abstraction boundary is.

## FAQ

### What exactly is PyTorch used for?

The skorch documentation does not explain PyTorch's general purpose; skorch is described only as a wrapper around it. What the README does show is that your model stays a torch.nn.Module, and skorch runs the forward pass, loss and optimizer step around it.

### What is the purpose of sklearn?

The skorch documentation does not define scikit-learn's purpose in general. It shows what skorch borrows from it: the estimator interface with fit, predict and get_params, plus Pipeline, GridSearchCV and scoring functions that skorch models can be passed into.

### Is PyTorch difficult to learn?

The skorch documentation does not address PyTorch's learning curve. It does show that skorch expects you to write an nn.Module yourself, with __init__ and forward, before the wrapper can train it.

### Is PyTorch just Python?

The skorch documentation does not answer this directly. It does show PyTorch code written in ordinary Python: the README example imports torch.nn, subclasses nn.Module and calls net.fit(X, y) on a scikit-learn style estimator, with arrays cast to float32 and int64.

### How does skorch compare with PyTorch?

skorch is not a replacement for PyTorch; it wraps it. Your model stays an nn.Module and PyTorch still runs the forward and backward passes. The difference is that skorch exposes the model as a scikit-learn estimator, so GridSearchCV and Pipeline can drive it.

## Sources

- [Issues](https://github.com/skorch-dev/skorch/issues)
- [License: BSD-3-Clause](https://github.com/skorch-dev/skorch/blob/master/LICENSE)
- [README](https://github.com/skorch-dev/skorch/blob/master/README.md)
- [Releases](https://github.com/skorch-dev/skorch/releases)
- [skorch-dev/skorch on GitHub](https://github.com/skorch-dev/skorch)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/skorch-dev-skorch
