> ## Documentation Index
> Fetch the complete documentation index at: https://docs.synthefy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-Target Regression

> Predict related numeric targets together with joint uncertainty.

Nori predicts several related numeric targets in one fit and one prediction.
Pass a target matrix instead of a vector to get joint means or samples that
preserve dependence across targets.

<CardGroup cols={2}>
  <Card title="One model call" icon="table-columns">
    Fit one `NoriRegressor` on a target matrix and receive every target in the
    same output array.
  </Card>

  <Card title="Joint uncertainty" icon="chart-scatter-3d">
    Draw coherent target combinations instead of sampling each marginal in
    isolation.
  </Card>

  <Card title="Three dependence strategies" icon="diagram-project">
    Choose the default copula, a lower-cost independent baseline, or an
    autoregressive factorization.
  </Card>

  <Card title="Local and hosted" icon="cloud-arrow-up">
    Use the same target-matrix contract with the local estimator or the
    lightweight hosted client.
  </Card>
</CardGroup>

<Note>
  Multi-target regression is available in `synthefy-nori` 0.20.0 and `synthefy`
  7.1.0 or later. Follow [Installation](/setup/installation) for the local or
  hosted package. Copula support is included in the standard local install.
</Note>

## Quickstart

Make `Y_train` two-dimensional: one row per context row and one column per
target. Nori uses the recommended `"copula"` strategy by default.

```python theme={null}
import numpy as np
from synthefy_nori import MultiTargetPredictionPolicy, NoriRegressor

X_train = np.array([
    [0.0, 0.1],
    [0.2, 0.3],
    [0.4, 0.4],
    [0.6, 0.7],
    [0.8, 0.9],
    [1.0, 1.1],
])
Y_train = np.column_stack([
    2.0 * X_train[:, 0] + X_train[:, 1],
    X_train[:, 0] - 3.0 * X_train[:, 1],
])
X_test = np.array([[0.3, 0.4], [0.9, 1.0]])

regressor = NoriRegressor(model="nori-6m").fit(X_train, Y_train)
means = regressor.predict(X_test)
samples = regressor.predict(
    X_test,
    output_type="samples",
    multi_target_prediction_policy=MultiTargetPredictionPolicy(
        n_draws=1_000,
        random_state=42,
    ),
)

print(means.shape)
print(samples.shape)
```

```text theme={null}
(2, 2)
(2, 1000, 2)
```

Means have shape `(n_query, n_targets)`. Joint samples have shape
`(n_query, n_draws, n_targets)`, so `samples[i, :, :]` is the joint predictive
distribution for query row `i`.

## How it works

Nori first learns a predictive marginal for each target. The selected strategy
then decides how to join those marginals.

<Steps>
  <Step title="Fit the target marginals">
    Each column of `Y_train` becomes a Nori regression target, while every
    target shares the same feature rows and checkpoint runtime.
  </Step>

  <Step title="Learn or choose dependence">
    Copula estimates dependence from cross-fitted residual ranks. Independent
    leaves the marginals separate. Autoregressive conditions later targets on
    targets already drawn in a selected order.
  </Step>

  <Step title="Draw jointly">
    Nori maps probability draws through the target marginals to produce
    one coherent target vector per draw and query row.
  </Step>

  <Step title="Return samples or their mean">
    `output_type="samples"` returns every draw. The default `"mean"` output
    returns one value per query row and target.
  </Step>
</Steps>

<Note>
  The independent, copula, and autoregressive strategy design was inspired by the
  [ScoringBench](https://scoringbench.com/) team and their work on evaluating full
  predictive distributions with proper scoring rules.
</Note>

## Copula — recommended

The default `"copula"` strategy is the product trade-off for most work. It
models cross-target dependence, is invariant to target-column order, and avoids
the draw- and order-scaled query expansion of autoregressive prediction.

## Choosing a strategy

| Strategy                 | Use it when                                                              | Trade-off                                                                         |
| ------------------------ | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------- |
| `"copula"` — recommended | You want joint uncertainty without making target order part of the model | Cross-fits residual ranks, so fitting costs more than independent prediction      |
| `"independent"`          | You need the lowest-cost baseline or do not need cross-target dependence | Joint draws are a product of marginals and do not preserve learned dependence     |
| `"autoregressive"`       | Order-sensitive conditional structure is worth additional inference cost | Work scales with draws and target orders; results can depend on the factorization |

Autoregressive led the release benchmark's small-draw joint-accuracy screen,
but copula remains the default because it is order-invariant and more
predictable in cost.

For reproducible autoregressive work, provide complete target-index
permutations. Orders define a predictive factorization; they do not claim that
one target causes another.

```python theme={null}
import numpy as np
from synthefy_nori import MultiTargetPredictionPolicy, NoriRegressor

X_train = np.array([
    [0.0, 0.1], [0.2, 0.3], [0.4, 0.4],
    [0.6, 0.7], [0.8, 0.9], [1.0, 1.1],
])
Y_train = np.column_stack([
    2.0 * X_train[:, 0] + X_train[:, 1],
    X_train[:, 0] - 3.0 * X_train[:, 1],
])
X_test = np.array([[0.3, 0.4], [0.9, 1.0]])

policy = MultiTargetPredictionPolicy(
    autoregressive_orders=[[0, 1], [1, 0]],
    n_draws=300,
    random_state=42,
)
regressor = NoriRegressor(
    model="nori-6m",
    multi_target_prediction_strategy="autoregressive",
    multi_target_prediction_policy=policy,
).fit(X_train, Y_train)

samples = regressor.predict(X_test, output_type="samples")
print(regressor.target_orders_)
```

Omit `autoregressive_orders` to generate deterministic unique permutations.
Nori caps the requested count at the number of possible unique permutations
instead of repeating an order.

## Hosted prediction

The lightweight client sends the target matrix in one hosted request. It
returns nested lists by default, matching the arrays in the raw HTTP response.
Create a key by following the [API key guide](/setup/api_key).

<CodeGroup>
  ```python Python theme={null}
  import os
  import numpy as np
  from synthefy import MultiTargetPredictionPolicy, SynthefyNoriClient

  X_train = [
      [0.0, 0.1], [0.2, 0.3], [0.4, 0.4],
      [0.6, 0.7], [0.8, 0.9], [1.0, 1.1],
  ]
  Y_train = [
      [0.1, -0.3], [0.7, -0.7], [1.2, -0.8],
      [1.9, -1.5], [2.5, -1.9], [3.1, -2.3],
  ]
  X_test = [[0.3, 0.4], [0.9, 1.0]]

  client = SynthefyNoriClient(
      api_key=os.environ["SYNTHEFY_NORI_API_KEY"],
      mode="remote",
      model="nori-6m",
  )
  samples = client.predict(
      X_train,
      Y_train,
      X_test,
      output_type="samples",
      multi_target_prediction_strategy="copula",
      multi_target_prediction_policy=MultiTargetPredictionPolicy(
          n_draws=300,
          random_state=42,
      ),
  )

  print(np.asarray(samples).shape)
  ```

  ```bash cURL theme={null}
  curl -X POST https://inference.baseten.co/predict \
    -H "Authorization: Bearer $SYNTHEFY_NORI_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "synthefy/nori-6m",
      "task": "regression",
      "X_train": [[0.0, 0.1], [0.2, 0.3], [0.4, 0.4], [0.6, 0.7], [0.8, 0.9], [1.0, 1.1]],
      "y_train": [[0.1, -0.3], [0.7, -0.7], [1.2, -0.8], [1.9, -1.5], [2.5, -1.9], [3.1, -2.3]],
      "X_test": [[0.3, 0.4], [0.9, 1.0]],
      "output_type": "samples",
      "multi_target_prediction_strategy": "copula",
      "multi_target_prediction_policy": {
        "n_draws": 300,
        "random_state": 42
      }
    }'
  ```
</CodeGroup>

Hosted responses echo the strategy that ran. The client checks that handshake
and fails instead of returning plausible output from a deployment that ignored
the requested multi-target controls.

## Configuration

<AccordionGroup>
  <Accordion title="Strategy and policy controls" icon="sliders">
    | Parameter                          | Default    | Description                                                                                                                               |
    | ---------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
    | `multi_target_prediction_strategy` | `"copula"` | `"copula"`, `"independent"`, or `"autoregressive"`. It is inert when `y` is one-dimensional.                                              |
    | `n_draws`                          | `300`      | Joint Monte Carlo draws. It controls sample output and autoregressive means; independent and copula means use marginal point predictions. |
    | `random_state`                     | `0`        | Seed for reproducible copula and autoregressive sampling. Use `None` for a fresh seed.                                                    |
    | `copula_cv`                        | `5`        | Cross-fitting folds used to estimate copula residual ranks. It cannot exceed the number of training rows.                                 |
    | `copula_pit_jitter`                | `1e-4`     | Uniform jitter applied to tied copula probability transforms.                                                                             |
    | `autoregressive_n_orders`          | `3`        | Number of automatically generated unique target permutations.                                                                             |
    | `autoregressive_orders`            | `None`     | Explicit complete target-index permutations. Do not combine it with an explicitly set `autoregressive_n_orders`.                          |

    Pass `MultiTargetPredictionPolicy` at construction for fit-dependent
    controls. At prediction time, you can override `n_draws` and
    `random_state`; changing copula or target-order controls requires refitting.
    Hosted inference applies tighter work bounds and validates them before it
    sends a request.
  </Accordion>

  <Accordion title="Output contract" icon="code">
    | Output                  | Shape                           | Description                                                   |
    | ----------------------- | ------------------------------- | ------------------------------------------------------------- |
    | `output_type="mean"`    | `(n_query, n_targets)`          | One prediction per query row and target. This is the default. |
    | `output_type="samples"` | `(n_query, n_draws, n_targets)` | Joint draws that preserve the selected dependence structure.  |

    Marginal `"median"`, `"quantiles"`, and `"full"` output are not part of
    the first multi-target release. Use joint samples when you need uncertainty.
  </Accordion>
</AccordionGroup>

## Good to know

* A two-dimensional target with at least two columns activates multi-target
  regression. One-dimensional targets keep their existing behavior.
* `discretize`, `categorical_levels`, and large-context policies do not compose
  with multi-target regression in this release.
* The hosted client records resolved autoregressive orders in
  `client.last_target_orders`. The local estimator exposes the same information
  as `regressor.target_orders_`.
* Hosted joint samples are three-dimensional nested lists. Convert them with
  `np.asarray(samples)` when you want NumPy indexing; `as_pandas=True` is not
  supported for three-dimensional samples.
* A memory policy applies to each internal marginal or chain call. Hosted calls
  expose the resulting list as `client.last_multi_target_memory_reports`.

## Next steps

<CardGroup cols={2}>
  <Card title="Python Client" icon="code" href="/nori/client">
    Configure hosted, SageMaker, or local execution through one client.
  </Card>

  <Card title="Examples" icon="flask" href="/nori/examples">
    Start with a complete single-target regression workflow.
  </Card>

  <Card title="Categorical targets" icon="list-ol" href="/nori/categorical-targets">
    Predict onto a discrete numeric lattice when the target is ordinal.
  </Card>
</CardGroup>
