通过 Theseus 层进行微分

本教程展示了如何通过 Theseus 层进行微分,以求解一组相关的最小二乘优化问题。

教程 1 中的优化问题都是通过 Theseus 非线性最小二乘优化器 各应用一次完成的,因为它们是简单的曲线拟合问题。
Theseus 也可以用于解决更复杂的优化问题,例如被优化量之间存在依赖关系的情况。
在本教程中,我们将解决一组共享一个公共参数的曲线拟合问题。
和教程 1 类似,为了简单起见,我们选择二次函数:我们希望拟合

y=ax2+by = ax^2 + b

其中 a 对所有问题固定,而 b 对每个问题不同。

从高层次来看,我们通过 torch 自动微分 优化 a 的值,并使用 Theseus 的非线性最小二乘优化器在给定 a 的情况下优化 b
本笔记本的其余部分将详细讲解实现该方法的必要步骤。

步骤 0:数据生成

与之前一样,我们首先通过从一组二次函数 3x2+b3x^2 + b 中采样点来生成数据,其中 b=3,5,,21b = 3, 5, \dots, 21。在此基础上,我们加入标准差为 σ=0.01\sigma = 0.01 的高斯噪声。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import torch
import matplotlib.pyplot as plt

torch.manual_seed(0)

def generate_data(num_points=100, a=1, b=0.5, noise_factor=0.01):
# 生成数据:从上面定义的二次曲线中采样 num_points 个点
data_x = torch.rand((1, num_points)) # 生成均匀分布的 x 值
noise = torch.randn((1, num_points)) * noise_factor # 生成高斯噪声
data_y = a * data_x.square() + b + noise # 计算 y 值并加入噪声
return data_x, data_y

def generate_learning_data(num_points, num_models):
a, b = 3, 1
data_batches = []
for i in range(num_models):
b = b + 2 # 每个模型增加不同的常数项 b
data = generate_data(num_points, a, b) # 生成数据
data_batches.append(data) # 保存数据批次
return data_batches

num_models = 10
data_batches = generate_learning_data(100, num_models)

fig, ax = plt.subplots()
for i in range(num_models):
ax.scatter(data_batches[i][0], data_batches[i][1]) # 绘制散点图
ax.set_xlabel('x'); # 设置 x 轴标签
ax.set_ylabel('y'); # 设置 y 轴标签

步骤 1:设置 Theseus 优化

接下来,我们设置 Theseus 优化问题,方法类似于教程 1,但有一个关键变化:a 不再是 Theseus NLLS 优化器的优化变量;相反,它作为一个辅助变量,其值将通过 PyTorch 的反向传播进行优化。b 仍然是 Theseus NLLS 优化器的唯一优化变量。下面的代码对此进行了示例说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import theseus as th

data_x, data_y = data_batches[0]

# 将数据包装成 Theseus 变量
x = th.Variable(data_x, name="x") # 自变量 x
y = th.Variable(data_y, name="y") # 观测值 y

# 定义优化变量和辅助变量
a = th.Vector(1, name="a") # 辅助变量 a(通过 PyTorch 优化)
b = th.Vector(1, name="b") # 优化变量 b(Theseus 优化器优化)
optim_vars = [b] # 注意:'b' 是唯一的优化变量
aux_vars = a, x, y # a, x, y 是辅助变量

# 更新后的误差函数,反映了 'a' 的变化
def quad_error_fn2(optim_vars, aux_vars):
[b] = optim_vars
a, x, y = aux_vars
est = a.tensor * x.tensor.square() + b.tensor # 二次函数估计值
err = y.tensor - est # 误差
return err

# 定义自动求导代价函数
cost_function = th.AutoDiffCostFunction(
optim_vars,
quad_error_fn2,
100,
aux_vars=aux_vars,
name="quadratic_cost_fn"
)

# 构建目标函数并添加代价函数
objective = th.Objective()
objective.add(cost_function)

# 设置高斯-牛顿优化器
optimizer = th.GaussNewton(
objective,
max_iterations=50,
step_size=0.5,
)

# 封装成 Theseus 层
theseus_optim = th.TheseusLayer(optimizer)

# Variable 'a' 的值通过 PyTorch 反向传播优化
a_tensor = torch.nn.Parameter(torch.rand(1, 1)) # PyTorch 参数 a
model_optimizer = torch.optim.Adam([a_tensor], lr=0.1) # Adam 优化器

步骤 2:运行优化与学习

最后,我们通过在 Theseus 非线性最小二乘优化器(NLLS)周围构建学习循环来计算 ab,每个模型的数据作为单个 batch。
Theseus NLLS 优化器在当前 a 下优化 b(称为内循环优化),而 PyTorch 的反向传播在所有 batch 中学习 a 的正确值(称为外循环优化)。

为了清晰起见,我们描述所需的高层步骤:

  • 步骤 2.1:和教程 1 类似,首先创建一个输入字典,并将其传递给 TheseusLayerforward 函数。
    注意,在本示例中,forward 函数不跟踪最优解,因为整个 NLLS 优化序列需要用于反向传播。
    优化完成后,我们得到一个字典 updated_inputs,其中包含优化变量的最新值(其他字典值保持不变)。
  • 步骤 2.2:然后使用 updated_inputs 字典更新目标函数(Objective),并用它来计算损失。
    正是因为这种使用 Theseus 优化变量的方式,forward 才返回输入字典。
  • 步骤 2.3:使用该损失进行反向传播。
    PyTorch 的学习优化器(此处为 Adam)将对学习参数(本例中为 a 的值)执行一次优化步骤。
  • 迭代:接下来重新调用 forward,重复步骤 2.1–2.3。

下面的代码演示了这些步骤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
num_batches = len(data_batches)
num_epochs = 20

print(f"初始 a 值: {a_tensor.item()}")

for epoch in range(num_epochs):
epoch_loss = 0.
epoch_b = [] # 记录本轮 epoch 中每个模型的当前 b 值
for i in range(num_batches):
model_optimizer.zero_grad()
data_x, data_y = data_batches[i]

# 步骤 2.1:为 TheseusLayer 创建输入字典,并传入 forward 函数
# 变量 `a` 的值是 Adam 更新后的 `a_tensor`
# 因为我们总是传入相同的 tensor,这个更新在技术上是多余的,
# 但我们这样写是为了明确展示变量值的来源。
# 另一种方式(可以尝试!):
# 在学习循环开始前调用 `a.update(a_tensor)`,
# 然后让 Adam 在内部直接改变其值(即在循环中只更新 `x`、`y` 和 `b`)
theseus_inputs = {
"a": a_tensor,
"x": data_x,
"y": data_y,
"b": torch.ones((1, 1)),
}
updated_inputs, info = theseus_optim.forward(theseus_inputs)

# 将当前 batch 优化得到的 "b" 添加到 `epoch_b` 列表中
# 注意:这里不跟踪最优解,因为我们要通过整个优化序列进行反向传播
epoch_b.append(updated_inputs["b"].item())

# 步骤 2.2:用更新后的输入更新目标函数
objective.update(updated_inputs)
loss = cost_function.error().square().mean()

# 步骤 2.3:PyTorch 反向传播
loss.backward()
model_optimizer.step()

loss_value = loss.item()
epoch_loss += loss_value

print(f"Epoch: {epoch} Loss: {epoch_loss}")
if epoch % 5 == 4:
print(f" ---------------- 第 {epoch:02d} 轮的解 -------------- ")
print(" a 值:", a.tensor.item())
print(" b 值: ", epoch_b)
print(f" ----------------------------------------------------- ")

输出的结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Initial a value: 0.8671661019325256
Epoch: 0 Loss: 2.550027549266815
Epoch: 1 Loss: 0.5708533525466919
Epoch: 2 Loss: 0.0265085999853909
Epoch: 3 Loss: 0.04149620106909424
Epoch: 4 Loss: 0.03596132283564657
---------------- Solutions at Epoch 04 --------------
a value: 3.1208860874176025
b values: [2.9216177463531494, 4.930838584899902, 6.922538757324219, 8.923272132873535, 10.926830291748047, 12.952813148498535, 14.932782173156738, 16.949243545532227, 18.946279525756836, 20.954736709594727]
-----------------------------------------------------
Epoch: 5 Loss: 0.005245754415227566
Epoch: 6 Loss: 0.002350384122109972
Epoch: 7 Loss: 0.0025651332980487496
Epoch: 8 Loss: 0.0010936586622847244
Epoch: 9 Loss: 0.001083762341295369
---------------- Solutions at Epoch 09 --------------
a value: 3.01115083694458
b values: [2.99800181388855, 4.999911308288574, 6.998246669769287, 8.995952606201172, 10.996501922607422, 12.998129844665527, 14.996124267578125, 16.99513053894043, 18.9946346282959, 20.996692657470703]
-----------------------------------------------------
Epoch: 10 Loss: 0.0010451501148054376
Epoch: 11 Loss: 0.0009841886785579845
Epoch: 12 Loss: 0.0009838700643740594
Epoch: 13 Loss: 0.0009751142497407272
Epoch: 14 Loss: 0.0009768658710527234
---------------- Solutions at Epoch 14 --------------
a value: 3.0001351833343506
b values: [2.99999737739563, 5.002256870269775, 7.0013861656188965, 8.999483108520508, 11.000371932983398, 13.000956535339355, 15.000521659851074, 16.998645782470703, 18.998701095581055, 21.00054931640625]
-----------------------------------------------------
Epoch: 15 Loss: 0.00097720912162913
Epoch: 16 Loss: 0.0009765262075234205
Epoch: 17 Loss: 0.0009760940811247565
Epoch: 18 Loss: 0.0009761785477166995
Epoch: 19 Loss: 0.0009763608395587653
---------------- Solutions at Epoch 19 --------------
a value: 2.999293088912964
b values: [3.000174045562744, 5.00246524810791, 7.0016632080078125, 8.999787330627441, 11.000700950622559, 13.00119400024414, 15.000886917114258, 16.99893569946289, 18.999032974243164, 21.000858306884766]
-----------------------------------------------------

将拟合的结果可视化为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Plot the learned functions
fig, ax = plt.subplots()

for i in range(num_models):
ax.scatter(data_batches[i][0], data_batches[i][1])

a_ = a.tensor.squeeze().detach()
b = epoch_b[i]
x = torch.linspace(0., 1., steps=100)
y = a_*x*x + b
ax.plot(x, y, color='k', lw=4, linestyle='--',
label='Learned quadratics' if i == 0 else None)
ax.legend()

ax.set_xlabel('x');
ax.set_ylabel('y');

我们可以观察到,我们能够非常接近地恢复出用于采样的 ab 值。

步骤 3(可选):同时求解所有优化问题

以上只是用 Theseus 对我们的问题建模的多种方法之一。Theseus 还支持同时求解多个优化问题,因此我们也可以同时求解所有 10 个最小二乘优化问题。我们可以用两种自然的方式实现:

  • 版本 A:为每个优化问题创建 10 个 AutoDiffCostFunction。这里,每个 AutoDiffCostFunction 需要有单独的 b,x,yb, x, y 变量(例如 [b1,x1,y1],[b2,x2,y2][b_1, x_1, y_1], [b_2, x_2, y_2] 等)。所有代价函数都会被添加到目标函数中,并且可以通过一次前向传播联合优化,并通过随后的反向传播联合求导。

  • 版本 B:将 b,x,yb, x, y 变量改为批处理形式,并让上面提到的 quad_err_fn2 错误函数支持批处理,我们就可以用单个 AutoDiffCostFunction 来表示它们拟合的代价。然而,由于误差现在可能是批量的,损失函数需要作为目标函数评估结果的聚合来计算。

版本 A 更常用于每个变量和代价函数有不同语义含义的情况(例如 Tutorial 4 和 5 中运动规划问题的不同时间步),而版本 B 更常用于同一问题的多个实例(例如 Tutorial 4 和 5 中的不同地图)。下面我们展示了每个版本的完整代码。两个版本得到的 aabb 值非常相似。

因为两个版本都需要一个通用的学习流程,我们首先创建一个子程序 optimize_and_learn_models_jointly 来提高可读性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 子程序:同时优化并学习模型

def optimize_and_learn_models_jointly(theseus_optim, model_optimizer, num_epochs=20):
# 再次假设 a_tensor 已在循环外初始化
print(f"初始 a 值: {a_tensor.item()}")

for epoch in range(num_epochs):
model_optimizer.zero_grad()
# 步骤 2.1:为 TheseusLayer 创建输入字典,并传入 forward 函数
theseus_inputs = construct_theseus_layer_inputs()
updated_inputs, _ = theseus_optim.forward(theseus_inputs)

# 步骤 2.2:使用更新后的输入更新目标函数
objective.update(updated_inputs)
loss = objective.error_metric()
loss = loss.sum() # 注意:现在 loss 需要最终聚合(版本 B 中需要)

# 步骤 2.3:PyTorch 反向传播
loss.backward()
model_optimizer.step()

if epoch == 0:
min_loss = loss
if loss <= min_loss:
min_loss = loss
best_model_a = a.tensor.item()
best_model_b = [b.tensor.item() for b in all_b]
print(f"Epoch: {epoch} 损失: {loss.item()}")
if epoch % 10 == 9:
print(f" ---------------- 第 {epoch:02d} 轮解 ---------------- ")
print(" a 值:", a.tensor.item())
print(" b 值: ", [b.tensor.item() for b in all_b])
print(f" ----------------------------------------------------- ")

return best_model_a, best_model_b

步骤 3.1:示例版本 A

现在我们展示版本 A。该版本相对于原始代码片段做了如下修改:

  • 创建 10 个 x,yx, ybb 变量。
  • 创建 10 个 AutoDiffCostFunction,每个使用相同的 aa 但对应的 bi,xi,yib_i, x_i, y_i。所有代价函数都被添加到目标函数中。
  • 构建 theseus_inputs 字典,将 bi,xi,yib_i, x_i, y_i 映射到正确的数据批次。

注意,aa 及其在 PyTorch 中的优化器设置保持不变。一旦优化问题设置完成,我们就调用 optimize_and_learn_models_jointly 子程序来计算 aabb 的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# Version A

# replace x and y with 10 different variables x0 ... x9, y0 ... y9
all_x, all_y = [], []
for i in range(num_models):
all_x.append(th.Variable(data_x, name=f"x{i}"))
all_y.append(th.Variable(data_y, name=f"y{i}"))

# replace b with 10 different variables b0 ... b9
all_b = []
for i in range(num_models):
all_b.append(th.Vector(1, name=f"b{i}"))

# a remains the same
a = th.Vector(1, name="a")

# objective now has 10 different cost functions
objective = th.Objective()
for i in range(num_models):
# each cost function i uses b_i as optim_var and x_i, y_i and a as aux_var
optim_vars = [all_b[i]]
aux_vars = a, all_x[i], all_y[i]
cost_function = th.AutoDiffCostFunction(
optim_vars, quad_error_fn2, 100, aux_vars=aux_vars, name=f"quadratic_cost_fn_{i}"
)
objective.add(cost_function)

# optimizer, TheseusLayer and model optimizer remains the same
optimizer = th.GaussNewton(
objective, max_iterations=50, step_size=0.4,
)
theseus_optim = th.TheseusLayer(optimizer)
a_tensor = torch.nn.Parameter(torch.rand(1, 1))
model_optimizer = torch.optim.Adam([a_tensor], lr=0.15)

# TheseusLayer dictionary now needs to construct b0 ... b9, x0 ... x9, y0 ... y9
def construct_theseus_layer_inputs():
theseus_inputs = {}
for i in range(num_models):
data_x, data_y = data_batches[i]
theseus_inputs.update({
f"x{i}": data_x,
f"y{i}": data_y,
f"b{i}": torch.ones((1, 1)),
})
theseus_inputs.update({"a": a_tensor})
return theseus_inputs

# Run Theseus optimization and learning
best_model = optimize_and_learn_models_jointly(theseus_optim, model_optimizer)

print(f" ---------------- Final Solutions -------------- ")
print(" a value:", best_model[0])
print(" b values: ", best_model[1])
print(f" ----------------------------------------------- ")

输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Initial a value: 0.4854246973991394
Epoch: 0 Loss: 548.7742919921875
Epoch: 1 Loss: 485.24896240234375
Epoch: 2 Loss: 425.75042724609375
Epoch: 3 Loss: 370.33953857421875
Epoch: 4 Loss: 319.0607604980469
Epoch: 5 Loss: 271.9397888183594
Epoch: 6 Loss: 228.98121643066406
Epoch: 7 Loss: 190.16563415527344
Epoch: 8 Loss: 155.447265625
Epoch: 9 Loss: 124.75196838378906
---------------- Solutions at Epoch 09 --------------
a value: 1.937516689300537
b values: [3.388946056365967, 5.365560054779053, 7.415647983551025, 9.417559623718262, 11.425989151000977, 13.298150062561035, 15.44985580444336, 17.3549861907959, 19.413911819458008, 21.403770446777344]
-----------------------------------------------------
Epoch: 10 Loss: 97.97471618652344
Epoch: 11 Loss: 74.97842407226562
Epoch: 12 Loss: 55.59276580810547
Epoch: 13 Loss: 39.614349365234375
Epoch: 14 Loss: 26.807687759399414
Epoch: 15 Loss: 16.907485961914062
Epoch: 16 Loss: 9.62230396270752
Epoch: 17 Loss: 4.639813423156738
Epoch: 18 Loss: 1.6331729888916016
Epoch: 19 Loss: 0.2687584459781647
---------------- Solutions at Epoch 19 --------------
a value: 3.0359597206115723
b values: [3.0146169662475586, 5.015951633453369, 7.017027378082275, 9.015321731567383, 11.016518592834473, 13.012236595153809, 15.01756763458252, 17.01212501525879, 19.014381408691406, 21.015764236450195]
-----------------------------------------------------
---------------- Final Solutions --------------
a value: 3.0359597206115723
b values: [3.0146169662475586, 5.015951633453369, 7.017027378082275, 9.015321731567383, 11.016518592834473, 13.012236595153809, 15.01756763458252, 17.01212501525879, 19.014381408691406, 21.015764236450195]
-----------------------------------------------

可视化结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Plot the learned functions
fig, ax = plt.subplots()

for i in range(num_models):
ax.scatter(data_batches[i][0], data_batches[i][1])

a = best_model[0]
b = best_model[1][i]
x = torch.linspace(0., 1., steps=100)
y = a*x*x + b
ax.plot(x, y, color='k', lw=4, linestyle='--',
label='Learned quadratics' if i == 0 else None)
ax.legend()

ax.set_xlabel('x');
ax.set_ylabel('y');

步骤 3.2:示例版本 B

现在我们展示版本 B。该版本相对于原始代码片段做了如下修改:

  • 创建大小为 [num_models, 100]x,yx, y 变量,而不是 [1, 100]
  • 创建大小为 [num_models, 1]bb 变量,而不是 [1, 1]
  • 构建 theseus_inputs 字典,将 b,x,yb, x, y 数据进行批处理。

在此示例中,我们不需要修改误差函数,因为 PyTorch 张量会自动处理广播,使批处理版本能够正常工作。

和之前一样,aa 及其在 PyTorch 中的优化器设置保持不变。一旦优化问题设置完成,我们就调用 optimize_and_learn_models_jointly 子程序来求解问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 版本 B

# 将 data_x 和 data_y 转换为形状为 [num_models, 100] 的 torch 张量
data_x = torch.stack([data_x.squeeze() for data_x, _ in data_batches])
data_y = torch.stack([data_y.squeeze() for _, data_y in data_batches])

# 构建一个形状为 [num_models, 100] 的 x 和 y 变量
x = th.Variable(data_x, name="x")
y = th.Variable(data_y, name="y")

# 如之前一样构建 a
a = th.Vector(1, name="a")

# 构建一个 b 变量,现在形状为 [num_models, 1]
b = th.Vector(tensor=torch.rand(num_models, 1), name="b")

# 再次说明:'b' 是唯一的优化变量,'a' 与 x、y 一起作为辅助变量
optim_vars = [b]
aux_vars = a, x, y

# 代价函数构建如之前一样
cost_function = th.AutoDiffCostFunction(
optim_vars, quad_error_fn2, 100, aux_vars=aux_vars, name="quadratic_cost_fn"
)

# 目标函数、优化器和 Theseus 层的构建方式与之前相同
objective = th.Objective()
objective.add(cost_function)
optimizer = th.GaussNewton(
objective, max_iterations=50, step_size=0.5,
)
theseus_optim = th.TheseusLayer(optimizer)

# 和之前一样,'a' 通过 PyTorch 反向传播进行优化
# model_optimizer 的构建方式相同
a_tensor = torch.nn.Parameter(torch.rand(1, 1))
model_optimizer = torch.optim.Adam([a_tensor], lr=0.2)

# theseus_inputs 字典的构建方式也与之前类似,
# 但数据要与变量的新形状匹配
def construct_theseus_layer_inputs():
theseus_inputs = {}
theseus_inputs.update({
"x": data_x,
"y": data_y,
"b": torch.ones((num_models, 1)),
"a": a_tensor,
})
return theseus_inputs

# 运行 Theseus 优化和学习
best_model = optimize_and_learn_models_jointly(theseus_optim, model_optimizer)
print(f" ---------------- 最终解 ---------------- ")
print(" a 值:", best_model[0])
print(" b 值: ", best_model[1])
print(f" ---------------------------------------- ")

输出结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Initial a value: 0.9839857220649719
Epoch: 0 Loss: 352.720947265625
Epoch: 1 Loss: 286.2037658691406
Epoch: 2 Loss: 226.8613739013672
Epoch: 3 Loss: 174.77915954589844
Epoch: 4 Loss: 129.97665405273438
Epoch: 5 Loss: 92.39059448242188
Epoch: 6 Loss: 61.85683822631836
Epoch: 7 Loss: 38.09175109863281
Epoch: 8 Loss: 20.675710678100586
Epoch: 9 Loss: 9.0426025390625
---------------- Solutions at Epoch 09 --------------
a value: 2.8336267471313477
b values: [3.0146169662475586, 5.015951633453369, 7.017027378082275, 9.015321731567383, 11.016518592834473, 13.012236595153809, 15.01756763458252, 17.01212501525879, 19.014381408691406, 21.015764236450195]
-----------------------------------------------------
Epoch: 10 Loss: 2.4795351028442383
Epoch: 11 Loss: 0.1416916847229004
Epoch: 12 Loss: 1.0855286121368408
Epoch: 13 Loss: 4.320140838623047
Epoch: 14 Loss: 8.871736526489258
Epoch: 15 Loss: 13.85158634185791
Epoch: 16 Loss: 18.51568603515625
Epoch: 17 Loss: 22.30596923828125
Epoch: 18 Loss: 24.867490768432617
Epoch: 19 Loss: 26.042295455932617
---------------- Solutions at Epoch 19 --------------
a value: 3.5438120365142822
b values: [3.0146169662475586, 5.015951633453369, 7.017027378082275, 9.015321731567383, 11.016518592834473, 13.012236595153809, 15.01756763458252, 17.01212501525879, 19.014381408691406, 21.015764236450195]
-----------------------------------------------------
---------------- Final Solutions --------------
a value: 3.1059281826019287
b values: [3.0146169662475586, 5.015951633453369, 7.017027378082275, 9.015321731567383, 11.016518592834473, 13.012236595153809, 15.01756763458252, 17.01212501525879, 19.014381408691406, 21.015764236450195]
-----------------------------------------------

输出可视化结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Plot the learned functions
fig, ax = plt.subplots()

for i in range(num_models):
ax.scatter(data_batches[i][0], data_batches[i][1])

a = best_model[0]
b = best_model[1][i]
x = torch.linspace(0., 1., steps=100)
y = a*x*x + b
ax.plot(x, y, color='k', lw=4, linestyle='--',
label='Learned quadratics' if i == 0 else None)
ax.legend()

ax.set_xlabel('x');
ax.set_ylabel('y');

技术说明:你可能会注意到,在此示例中,求导仅发生在 TheseusLayer 的目标函数上,而不是通过 Theseus 的非线性最小二乘优化器进行求导。这是由于示例问题相对简单。对于更复杂的内部循环计算,Theseus 还需要通过非线性最小二乘优化器进行求导;这类问题可以参考 Tutorial 4 和 5,以及 Theseus 示例文件夹中的案例。