# `Latu.ML.CrossValidatorModel`
[🔗](https://github.com/zero-one-group/latu_ml/blob/v0.4.2/lib/latu/ml/cross_validator_model.ex#L1)

What a cross-validation found: the winning model, and the score of every param map.

`best_model` is **refit on the whole frame** with the winning param map — it is not one of
the fold models, which each saw only a fraction of the rows. That is PySpark's behaviour and
the reason a search costs one fit more than the grid suggests.

`avg_metrics` and `std_metrics` are the mean and population standard deviation across folds,
one per param map, in the order `param_maps` gives them. `Latu.ML.best_index/1` says which
one won and `Latu.ML.larger_better?/1` says why.

`sub_models` is `nil` unless the search was asked to `collect_sub_models: true`, in which
case it is a list of `num_folds` lists, one model per param map. **Those are yours to
delete**; `Latu.ML.delete/1` on this struct releases them along with `best_model`.

# `fitted`

```elixir
@type fitted() :: Latu.ML.Model.t() | Latu.ML.PipelineModel.t()
```

A fitted stage: a bare model, or a pipeline model where a pipeline was searched.

# `t`

```elixir
@type t() :: %Latu.ML.CrossValidatorModel{
  avg_metrics: [float()],
  best_model: fitted(),
  owned: [Latu.ML.Model.t()] | nil,
  session: Latu.Session.t(),
  std_metrics: [float()] | nil,
  sub_models: [[fitted()]] | nil,
  uid: String.t(),
  validator: Latu.ML.CrossValidator.t()
}
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
