or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-gp.mdgp-operations.mdindex.mdlazy.mdmeasure.mdmulti-output.mdobservations.mdrandom.md

multi-output.mddocs/

0

# Multi-Output Gaussian Processes

1

2

Support for multi-output Gaussian processes using specialized kernel and mean functions that handle vector-valued processes and cross-covariances between different outputs. This enables modeling of correlated time series, multi-task learning, and vector-valued function approximation.

3

4

## Capabilities

5

6

### Multi-Output Kernels

7

8

Kernels that handle multiple output dimensions and cross-covariances between different outputs within a vector-valued process.

9

10

```python { .api }

11

class MultiOutputKernel:

12

def __init__(self, measure, *ps):

13

"""

14

Initialize multi-output kernel.

15

16

Parameters:

17

- measure: Measure to take kernels from

18

- *ps: Processes that make up multi-valued process

19

"""

20

21

measure: Measure # Measure to take kernels from

22

ps: Tuple[GP, ...] # Processes that make up multi-valued process

23

24

def render(self, formatter=None):

25

"""

26

Render kernel as string with optional formatting.

27

28

Parameters:

29

- formatter: Optional custom formatter function

30

31

Returns:

32

- str: String representation of the kernel

33

"""

34

```

35

36

### Multi-Output Means

37

38

Mean functions that handle multiple output dimensions and can compute mean values for vector-valued processes.

39

40

```python { .api }

41

class MultiOutputMean:

42

def __init__(self, measure, *ps):

43

"""

44

Initialize multi-output mean.

45

46

Parameters:

47

- measure: Measure to take means from

48

- *ps: Processes that make up multi-valued process

49

"""

50

51

def __call__(self, x):

52

"""

53

Evaluate mean at input locations.

54

55

Parameters:

56

- x: Input locations to evaluate at

57

58

Returns:

59

- Mean values for all outputs at input locations

60

"""

61

62

measure: Measure # Measure to take means from

63

ps: Tuple[GP, ...] # Processes that make up multi-valued process

64

65

def render(self, formatter=None):

66

"""

67

Render mean as string with optional formatting.

68

69

Parameters:

70

- formatter: Optional custom formatter function

71

72

Returns:

73

- str: String representation of the mean

74

"""

75

```

76

77

### Ambiguous Dimensionality Kernels

78

79

Special kernel type for cases where the output dimensionality cannot be automatically inferred and must be specified explicitly.

80

81

```python { .api }

82

class AmbiguousDimensionalityKernel:

83

"""

84

Kernel whose dimensionality cannot be inferred automatically.

85

Used as base class for kernels requiring explicit dimensionality specification.

86

"""

87

```

88

89

### Utility Functions

90

91

Helper functions for working with multi-output kernels and inferring their properties.

92

93

```python { .api }

94

def infer_size(kernel, x):

95

"""

96

Infer size of kernel evaluated at input.

97

98

Parameters:

99

- kernel: Kernel to evaluate

100

- x: Input to evaluate kernel at

101

102

Returns:

103

- int: Inferred size of kernel output

104

"""

105

106

def dimensionality(kernel):

107

"""

108

Infer output dimensionality of kernel.

109

110

Parameters:

111

- kernel: Kernel to analyze

112

113

Returns:

114

- int: Output dimensionality of kernel

115

"""

116

```

117

118

## Usage Examples

119

120

### Basic Multi-Output GP Construction

121

122

```python

123

import stheno

124

import numpy as np

125

126

# Create individual processes for each output

127

temp_gp = stheno.GP(kernel=stheno.EQ().stretch(2.0), name="temperature")

128

pressure_gp = stheno.GP(kernel=stheno.Matern52().stretch(1.5), name="pressure")

129

humidity_gp = stheno.GP(kernel=stheno.EQ().stretch(0.8), name="humidity")

130

131

# Create multi-output process using cross product

132

multi_gp = stheno.cross(temp_gp, pressure_gp, humidity_gp)

133

134

# Evaluate at input points

135

x = np.linspace(0, 10, 50)

136

multi_fdd = multi_gp(x)

137

138

print(f"Multi-output FDD shape: {multi_fdd.mean.shape}") # Should be (150, 1) for 3×50

139

```

140

141

### Correlated Outputs with Shared Components

142

143

```python

144

# Create shared latent process

145

latent = stheno.GP(kernel=stheno.EQ().stretch(3.0), name="latent")

146

147

# Create correlated outputs

148

output1 = latent + 0.5 * stheno.GP(kernel=stheno.Matern32(), name="output1_noise")

149

output2 = 0.8 * latent + stheno.GP(kernel=stheno.EQ().stretch(0.5), name="output2_noise")

150

output3 = -0.6 * latent + stheno.GP(kernel=stheno.Linear(), name="output3_noise")

151

152

# Name the outputs

153

measure = stheno.Measure()

154

with measure:

155

measure.name(output1, "output1")

156

measure.name(output2, "output2")

157

measure.name(output3, "output3")

158

159

# Create multi-output process

160

correlated_multi = stheno.cross(output1, output2, output3)

161

162

# Sample to see correlations

163

x = np.linspace(0, 5, 30)

164

samples = correlated_multi(x).sample(3)

165

166

# Reshape to see individual outputs

167

n_outputs = 3

168

n_points = len(x)

169

reshaped_samples = samples.reshape(n_outputs, n_points, -1)

170

171

print(f"Output 1 correlation with Output 2: {np.corrcoef(reshaped_samples[0,:,0], reshaped_samples[1,:,0])[0,1]:.3f}")

172

```

173

174

### Multi-Task Learning

175

176

```python

177

# Multi-task regression with shared and task-specific components

178

shared_component = stheno.GP(kernel=stheno.EQ().stretch(2.0), name="shared")

179

180

# Task-specific components

181

task1_specific = stheno.GP(kernel=stheno.Matern52().stretch(0.5), name="task1_specific")

182

task2_specific = stheno.GP(kernel=stheno.Matern52().stretch(0.8), name="task2_specific")

183

task3_specific = stheno.GP(kernel=stheno.Matern52().stretch(1.2), name="task3_specific")

184

185

# Combine shared and specific components

186

task1 = shared_component + 0.5 * task1_specific

187

task2 = shared_component + 0.3 * task2_specific

188

task3 = shared_component + 0.7 * task3_specific

189

190

# Create multi-task GP

191

multi_task_gp = stheno.cross(task1, task2, task3)

192

193

# Generate synthetic multi-task data

194

x_train = np.random.uniform(0, 5, 40)

195

y_train = multi_task_gp(x_train).sample()

196

197

# Condition on observations for all tasks

198

obs = stheno.Observations(multi_task_gp(x_train), y_train)

199

posterior_multi = multi_task_gp.condition(obs)

200

201

# Make predictions

202

x_test = np.linspace(0, 5, 50)

203

predictions = posterior_multi(x_test)

204

mean, lower, upper = predictions.marginal_credible_bounds()

205

206

# Extract predictions for each task

207

n_tasks = 3

208

pred_mean_per_task = mean.reshape(n_tasks, len(x_test))

209

```

210

211

### Custom Multi-Output Kernels

212

213

```python

214

# Create measure to manage multi-output relationships

215

measure = stheno.Measure()

216

217

# Define individual processes

218

process1 = stheno.GP(name="proc1")

219

process2 = stheno.GP(name="proc2")

220

221

with measure:

222

# Add processes with custom kernel relationships

223

measure.add_independent_gp(process1, stheno.ZeroMean(), stheno.EQ())

224

measure.add_independent_gp(process2, stheno.ZeroMean(), stheno.Matern52())

225

226

# Create multi-output kernel

227

multi_kernel = stheno.MultiOutputKernel(measure, process1, process2)

228

multi_mean = stheno.MultiOutputMean(measure, process1, process2)

229

230

print(f"Multi-output kernel: {multi_kernel.render()}")

231

print(f"Multi-output mean: {multi_mean.render()}")

232

233

# Use in GP construction

234

combined_gp = stheno.GP(mean=multi_mean, kernel=multi_kernel, measure=measure)

235

```

236

237

### Working with Different Output Dimensions

238

239

```python

240

# Processes with different natural scales

241

small_scale = stheno.GP(kernel=stheno.EQ().stretch(0.1), name="small")

242

medium_scale = stheno.GP(kernel=stheno.EQ().stretch(1.0), name="medium")

243

large_scale = stheno.GP(kernel=stheno.EQ().stretch(10.0), name="large")

244

245

# Multi-scale multi-output process

246

multi_scale = stheno.cross(small_scale, medium_scale, large_scale)

247

248

# Evaluate and check dimensionality

249

x = np.linspace(0, 1, 20)

250

fdd = multi_scale(x)

251

252

# Use dimensionality inference

253

kernel_dim = stheno.dimensionality(multi_scale.kernel)

254

inferred_size = stheno.infer_size(multi_scale.kernel, x)

255

256

print(f"Kernel dimensionality: {kernel_dim}")

257

print(f"Inferred size at input: {inferred_size}")

258

print(f"Actual FDD mean shape: {fdd.mean.shape}")

259

```

260

261

### Sparse Multi-Output GPs

262

263

```python

264

# Large-scale multi-output problem

265

n_outputs = 4

266

n_obs_per_output = 200

267

n_inducing = 30

268

269

# Create individual output processes

270

outputs = []

271

for i in range(n_outputs):

272

gp = stheno.GP(

273

kernel=stheno.EQ().stretch(np.random.uniform(0.5, 2.0)),

274

name=f"output_{i}"

275

)

276

outputs.append(gp)

277

278

# Create multi-output process

279

multi_output = stheno.cross(*outputs)

280

281

# Generate observations

282

x_obs = np.random.uniform(0, 10, n_obs_per_output)

283

y_obs = multi_output(x_obs).sample()

284

285

# Inducing points shared across outputs

286

x_inducing = np.linspace(0, 10, n_inducing)

287

u_multi = multi_output(x_inducing)

288

289

# Create sparse observations

290

sparse_obs = stheno.PseudoObservations(

291

u_multi,

292

multi_output(x_obs),

293

y_obs

294

)

295

296

# Condition using sparse approximation

297

posterior_sparse = multi_output.condition(sparse_obs)

298

299

# Evaluate ELBO for the approximation

300

elbo = sparse_obs.elbo(multi_output.measure)

301

print(f"Multi-output sparse GP ELBO: {elbo:.3f}")

302

```

303

304

### Independent vs Correlated Outputs

305

306

```python

307

# Independent outputs (no cross-correlations)

308

indep1 = stheno.GP(kernel=stheno.EQ(), name="independent1")

309

indep2 = stheno.GP(kernel=stheno.Matern52(), name="independent2")

310

independent_multi = stheno.cross(indep1, indep2)

311

312

# Correlated outputs (shared component)

313

shared = stheno.GP(kernel=stheno.EQ().stretch(2.0), name="shared")

314

corr1 = shared + 0.5 * stheno.GP(kernel=stheno.Matern32(), name="corr1_noise")

315

corr2 = shared + 0.3 * stheno.GP(kernel=stheno.Matern32(), name="corr2_noise")

316

correlated_multi = stheno.cross(corr1, corr2)

317

318

# Compare samples

319

x = np.linspace(0, 5, 50)

320

321

indep_samples = independent_multi(x).sample()

322

corr_samples = correlated_multi(x).sample()

323

324

# Reshape to analyze correlations

325

indep_reshaped = indep_samples.reshape(2, len(x))

326

corr_reshaped = corr_samples.reshape(2, len(x))

327

328

indep_correlation = np.corrcoef(indep_reshaped[0], indep_reshaped[1])[0,1]

329

corr_correlation = np.corrcoef(corr_reshaped[0], corr_reshaped[1])[0,1]

330

331

print(f"Independent outputs correlation: {indep_correlation:.3f}")

332

print(f"Correlated outputs correlation: {corr_correlation:.3f}")

333

```