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

One split instead of k folds: fit every param map once, and keep the best.

`Latu.ML.CrossValidator` with `num_folds: 1` is not this — a cross-validation scores every
param map k times and averages. This scores each one exactly once, against a single held-out
slice, and is what you reach for when a fit is expensive enough that k of them is not worth
the variance reduction.

    tvs =
      ML.train_validation_split(
        estimator: lr,
        param_maps: ML.param_grid(lr, reg_param: [0.1, 0.01]),
        evaluator: Evaluation.binary_classification_evaluator(),
        train_ratio: 0.75
      )

    {:ok, model} = ML.fit(tvs, training)

The split is cut the same way a fold is — `rand(seed)` and a range — so `train_ratio: 0.75`
means rows whose draw is under 0.75, not exactly three quarters of them. Pass `seed:` for a
split that is the same twice.

# `t`

```elixir
@type t() :: %Latu.ML.TrainValidationSplit{
  collect_sub_models: boolean(),
  estimator: Latu.ML.CrossValidator.searchable(),
  evaluator: Latu.ML.Evaluator.t(),
  param_maps: [Latu.ML.param_map()],
  seed: integer() | nil,
  train_ratio: float(),
  uid: String.t()
}
```

---

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