|
| 1 | +# 快速开始 |
| 2 | + |
| 3 | +本文通过一个简单的 demo 及其扩展问题,介绍如何使用 PaddleScience 训练模型,解决一类方程学习与预测问题,并可视化预测结果。 |
| 4 | + |
| 5 | +## 1. 问题简介 |
| 6 | + |
| 7 | +假设我们希望用神经网络模型去拟合 $x \in [-\pi, \pi]$ 区间内,$u=sin(x)$ 这一函数。在拟合函数已知和未知两种情形下,如何去尽可能地准确拟合 $u=sin(x)$。 |
| 8 | + |
| 9 | +第一种场景下,假设已知目标函数 $u$ 的解析解就是 $u=sin(x)$,我们采用监督训练的思路,直接用该公式生成标签因变量 $u$,与自变量 $x$ 共同作为监督数据对模型进行训练。 |
| 10 | + |
| 11 | +第二种场景下,假设不知道目标函数 $u$ 的解析解,但我们知道其满足某种微分关系,我们这里以其中一个满足条件的微分方程 $\dfrac{\partial u} {\partial x}=cos(x)$ 为例,介绍如何生成数据进行训练。 |
| 12 | + |
| 13 | +## 2. 场景一 |
| 14 | + |
| 15 | +目标拟合函数: |
| 16 | + |
| 17 | +$$ |
| 18 | +u=sin(x), x \in [-\pi, \pi]. |
| 19 | +$$ |
| 20 | + |
| 21 | +我们生成 $N$ 组数据对 $(x_i, u_i), i=1,...,N$ 作为监督数据进行训练即可。 |
| 22 | + |
| 23 | +在撰写代码之前,我们首先导入必要的包。 |
| 24 | + |
| 25 | +``` py |
| 26 | +import numpy as np |
| 27 | +import paddle |
| 28 | + |
| 29 | +import ppsci |
| 30 | +from ppsci.utils import logger |
| 31 | +``` |
| 32 | + |
| 33 | +然后创建日志和模型保存目录供训练过程记录和保存使用,这一步是绝大部分案例在正式开始前都需要进行的操作。 |
| 34 | + |
| 35 | +``` py |
| 36 | +# set random seed for reproducibility |
| 37 | +ppsci.utils.misc.set_random_seed(42) |
| 38 | + |
| 39 | +# set output directory |
| 40 | +output_dir = "./output_quick_start" |
| 41 | + |
| 42 | +# initialize logger |
| 43 | +logger.init_logger("ppsci", f"{output_dir}/train.log", "info") |
| 44 | +``` |
| 45 | + |
| 46 | +接下来正式开始撰写代码。 |
| 47 | + |
| 48 | +首先定义问题区间,我们使用 `ppsci.geometry.Interval` 定义一个线段几何形状,方便后续在该线段上对 $x$ 进行采样。 |
| 49 | + |
| 50 | +``` py |
| 51 | +# set input 1D-geometry([-π, π]) |
| 52 | +l_limit, r_limit = -np.pi, np.pi |
| 53 | +x_domain = ppsci.geometry.Interval(l_limit, r_limit) |
| 54 | +geom = {"domain": x_domain} |
| 55 | +``` |
| 56 | + |
| 57 | +然后定义一个简单的 3 层 MLP 模型。 |
| 58 | + |
| 59 | +``` py |
| 60 | +# set model to 3-layer MLP |
| 61 | +model = ppsci.arch.MLP(("x",), ("u",), 3, 64) |
| 62 | +``` |
| 63 | + |
| 64 | +上述代码表示模型接受自变量 $x$ 作为输入,输出预测结果 $\hat{u}$ |
| 65 | + |
| 66 | +然后我们定义已知的 $u=sin(x)$ 计算函数,作为 `ppsci.constraint.InteriorConstraint` 的参数,用于生成标签数据,`InteriorConstraint` 表示以给定的几何形状或数据集中的数据作为输入,联合给定的标签数据,指导模型进行优化。 |
| 67 | + |
| 68 | +``` py |
| 69 | +# set constraint on 1D-geometry([-π, π]) |
| 70 | +iters_per_epoch = 100 |
| 71 | +interior_constraint = ppsci.constraint.InteriorConstraint( |
| 72 | + output_expr={"u": lambda out: out["u"]}, |
| 73 | + label_dict={"u": sin_compute_func}, |
| 74 | + geom=geom["domain"], |
| 75 | + dataloader_cfg={ |
| 76 | + "dataset": "NamedArrayDataset", |
| 77 | + "iters_per_epoch": iters_per_epoch, |
| 78 | + "sampler": { |
| 79 | + "name": "BatchSampler", |
| 80 | + "shuffle": True, |
| 81 | + }, |
| 82 | + "batch_size": 32, |
| 83 | + }, |
| 84 | + loss=ppsci.loss.MSELoss(), |
| 85 | +) |
| 86 | +# wrap constraint(s) into one dict |
| 87 | +constraint = {interior_constraint.name: interior_constraint} |
| 88 | +``` |
| 89 | + |
| 90 | +此处的 `interior_constraint` 表示一个训练目标,即我们希望在 $[-\pi, \pi]$ 这段区间内,优化模型让模型的预测结果 $\hat{u}$ 尽可能地接近它的标签值 $u$。 |
| 91 | + |
| 92 | +接下来就可以开始定义模型训练相关的内容,比如训练轮数、优化器 |
| 93 | + |
| 94 | +``` py |
| 95 | +# set training hyper-parameters |
| 96 | +epochs = 10 |
| 97 | +# set optimizer |
| 98 | +optimizer = ppsci.optimizer.Adam(1e-3)((model,)) |
| 99 | +``` |
| 100 | + |
| 101 | +当训练完成,我们希望在 $[-\pi, \pi]$ 上取 1000 个点进行预测并将结果可视化,以此查看训练完毕的模型是否具备一定的预测能力。 |
| 102 | + |
| 103 | +``` py |
| 104 | +# set visualizer |
| 105 | +visualize_input_dict = {"x": paddle.linspace(l_limit, r_limit, 1000).reshape([1000, 1])} |
| 106 | +visualize_input_dict["u_ref"] = paddle.sin(visualize_input_dict["x"]) |
| 107 | +visualizer = { |
| 108 | + "visualize_u": ppsci.visualize.VisualizerScatter1D( |
| 109 | + visualize_input_dict, |
| 110 | + ("x",), |
| 111 | + {"u_pred": lambda out: out["u"], "u_ref": lambda out: out["u_ref"]}, |
| 112 | + prefix="u=sin(x)", |
| 113 | + ), |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +最后将上述定义的对象传递给训练调度类 `Solver`,即可开始模型训练 |
| 118 | + |
| 119 | +``` py |
| 120 | +# initialize solver |
| 121 | +solver = ppsci.solver.Solver( |
| 122 | + model, |
| 123 | + constraint, |
| 124 | + output_dir, |
| 125 | + optimizer, |
| 126 | + epochs=epochs, |
| 127 | + iters_per_epoch=iters_per_epoch, |
| 128 | + geom=geom, |
| 129 | + visualizer=visualizer, |
| 130 | +) |
| 131 | +# train model |
| 132 | +solver.train() |
| 133 | +``` |
| 134 | + |
| 135 | +训练完毕后再用刚才取的 1000 个点进行可视化 |
| 136 | + |
| 137 | +``` py |
| 138 | +# visualize prediction after finished training |
| 139 | +solver.visualize() |
| 140 | +``` |
| 141 | + |
| 142 | +训练记录下所示 |
| 143 | + |
| 144 | +``` log |
| 145 | +... |
| 146 | +... |
| 147 | +ppsci INFO: [Train][Epoch 10/10][Iter: 60/100] lr: 0.00100000, loss: 0.00084, EQ: 0.00084, batch_cost: 0.00193s, reader_cost: 0.00017s, ips: 16607.08697 samples/s, eta: 0:00:00 |
| 148 | +ppsci INFO: [Train][Epoch 10/10][Iter: 70/100] lr: 0.00100000, loss: 0.00082, EQ: 0.00082, batch_cost: 0.00193s, reader_cost: 0.00016s, ips: 16603.29541 samples/s, eta: 0:00:00 |
| 149 | +ppsci INFO: [Train][Epoch 10/10][Iter: 80/100] lr: 0.00100000, loss: 0.00078, EQ: 0.00078, batch_cost: 0.00193s, reader_cost: 0.00016s, ips: 16612.34228 samples/s, eta: 0:00:00 |
| 150 | +ppsci INFO: [Train][Epoch 10/10][Iter: 90/100] lr: 0.00100000, loss: 0.00076, EQ: 0.00076, batch_cost: 0.00193s, reader_cost: 0.00015s, ips: 16616.61847 samples/s, eta: 0:00:00 |
| 151 | +ppsci INFO: [Train][Epoch 10/10][Iter: 100/100] lr: 0.00100000, loss: 0.00075, EQ: 0.00075, batch_cost: 0.00191s, reader_cost: 0.00015s, ips: 16715.53436 samples/s, eta: 0:00:00 |
| 152 | +ppsci INFO: [Train][Epoch 10/10][Avg] loss: 0.00075, EQ: 0.00075 |
| 153 | +ppsci INFO: Finish saving checkpoint to ./output_quick_start/checkpoints/latest |
| 154 | +ppsci INFO: 1D result is saved to ./output_quick_start/visual/epoch_0/u=sin(x).png |
| 155 | +ppsci INFO: [Visualize][Epoch 0] Finished visualization. |
| 156 | +``` |
| 157 | + |
| 158 | +预测结果如下所示 |
| 159 | + |
| 160 | + |
| 161 | + |
| 162 | +## 3. 场景二 |
| 163 | + |
| 164 | +可以看到场景一的监督训练方式能较好地解决函数拟合问题,但一般情况下我们是无法得知拟合函数本身的解析式的,因此也无法直接构造因变量的监督数据。 |
| 165 | + |
| 166 | +虽然无法求出解析式直接构造监督数据,但往往可以利用相关数学知识,推导出目标拟合函数符合的某种数学关系,以训练模型以满足这种数学关系的方式,达到“间接”优化模型的目的。 |
| 167 | + |
| 168 | +假设我们不再使用 $u=sin(x)$ 这一先验公式,因而无法生成标签数据 $u$。因此我们使用 $\dfrac{\partial u} {\partial x}=cos(x)$ 这一方程,构造数据对 $(x_i, cos(x_i)), i=1,...,N$。 |
| 169 | +这意味着我们仍然能保持模型的输入、输出不变,但优化目标变成了:让 $\dfrac{\partial \hat{u}} {\partial x}$ 尽可能地接近 $cos(x)$。即 |
| 170 | + |
| 171 | +基于以上理论,我们对场景一的代码进行少量的改写即可得到本场景二的代码。 |
| 172 | + |
| 173 | +首先由于我们需要使用一阶微分这一操作,因此在代码开头处需导入一阶微分 API |
| 174 | + |
| 175 | +``` py hl_lines="5" |
| 176 | +import numpy as np |
| 177 | +import paddle |
| 178 | + |
| 179 | +import ppsci |
| 180 | +from ppsci.autodiff import jacobian |
| 181 | +from ppsci.utils import logger |
| 182 | +``` |
| 183 | + |
| 184 | +然后将原来的标签生成函数改为微分关系标签生成函数 |
| 185 | + |
| 186 | +``` py |
| 187 | +# standard solution of cos(x) |
| 188 | +def cos_compute_func(data: dict): |
| 189 | + return np.cos(data["x"]) |
| 190 | +``` |
| 191 | + |
| 192 | +最后将 `interior_constraint` 这一约束条件从约束“模型输出”,改为约束“模型输出对输入的一阶微分” |
| 193 | + |
| 194 | +``` py hl_lines="2 3" |
| 195 | +interior_constraint = ppsci.constraint.InteriorConstraint( |
| 196 | + output_expr={"du_dx": lambda out: jacobian(out["u"], out["x"])}, |
| 197 | + label_dict={"du_dx": cos_compute_func}, |
| 198 | + geom=geom["domain"], |
| 199 | + dataloader_cfg={ |
| 200 | + "dataset": "NamedArrayDataset", |
| 201 | + "iters_per_epoch": iters_per_epoch, |
| 202 | + "sampler": { |
| 203 | + "name": "BatchSampler", |
| 204 | + "shuffle": True, |
| 205 | + }, |
| 206 | + "batch_size": 32, |
| 207 | + }, |
| 208 | + loss=ppsci.loss.MSELoss(), |
| 209 | +) |
| 210 | +``` |
| 211 | + |
| 212 | +修改完毕后执行训练,训练日志如下所示 |
| 213 | + |
| 214 | +``` log |
| 215 | +... |
| 216 | +... |
| 217 | +ppsci INFO: [Train][Epoch 10/10][Iter: 70/100] lr: 0.00100000, loss: 0.00035, EQ: 0.00035, batch_cost: 0.01183s, reader_cost: 0.00017s, ips: 2705.18917 samples/s, eta: 0:00:00 |
| 218 | +ppsci INFO: [Train][Epoch 10/10][Iter: 80/100] lr: 0.00100000, loss: 0.00035, EQ: 0.00035, batch_cost: 0.01133s, reader_cost: 0.00017s, ips: 2823.74760 samples/s, eta: 0:00:00 |
| 219 | +ppsci INFO: [Train][Epoch 10/10][Iter: 90/100] lr: 0.00100000, loss: 0.00036, EQ: 0.00036, batch_cost: 0.01141s, reader_cost: 0.00017s, ips: 2803.77351 samples/s, eta: 0:00:00 |
| 220 | +ppsci INFO: [Train][Epoch 10/10][Iter: 100/100] lr: 0.00100000, loss: 0.00036, EQ: 0.00036, batch_cost: 0.01106s, reader_cost: 0.00016s, ips: 2892.93859 samples/s, eta: 0:00:00 |
| 221 | +ppsci INFO: [Train][Epoch 10/10][Avg] loss: 0.00036, EQ: 0.00036 |
| 222 | +ppsci INFO: Finish saving checkpoint to ./output_quick_start_2/checkpoints/latest |
| 223 | +ppsci INFO: 1D result is saved to ./output_quick_start_2/visual/epoch_0/u=sin(x).png |
| 224 | +ppsci INFO: [Visualize][Epoch 0] Finished visualization. |
| 225 | +``` |
| 226 | + |
| 227 | +预测结果如下所示 |
| 228 | + |
| 229 | + |
| 230 | + |
| 231 | +可以发现利用微分关系训练的模型仍然具备良好的预测能力。 |
0 commit comments