构建计算与传输神将网络框架network,创建具体网络pinn,再取点训练,画图
一、创建网络network
layers = [('input', torch.nn.Linear(input_size, hidden_size))]
layers.append(('input_activation', act()))
-
torch.nn.Linear(input_size, hidden_size):
- 这一部分创建了一个线性层(Linear Layer),它是神经网络中的基本组成部分之一。
input_size
是输入数据的特征数量(或维度),hidden_size
是输出特征的数量(或维度)。这个层的作用是对输入数据进行线性变换,即 ( y = xW + b ),其中 ( W ) 是权重矩阵, ( b ) 是偏置向量。
- 这一部分创建了一个线性层(Linear Layer),它是神经网络中的基本组成部分之一。
-
layers = [('input', torch.nn.Linear(input_size, hidden_size))]:
- 这里创建了一个列表
layers
,其中包含一个元组。元组中的第一个元素'input'
是该层的名称,第二个元素是上面创建的torch.nn.Linear
对象,即输入层。
- 这里创建了一个列表
-
layers.append(('input_activation', act())):
- 这一行代码向
layers
列表中添加了第二个元组。这个元组的第一个元素是'input_activation'
,似乎是指输入层后的激活函数(activation function)。第二个元素act()
则是一个函数或者函数对象,用来对输入层的输出进行非线性变换,以增加网络的表示能力。
- 这一行代码向
总结来说,这段代码定义了一个神经网络的输入层,其中包括一个线性变换(Linear Layer)和一个激活函数。这样的设计通常用于构建神经网络的初始结构,通过线性层和激活函数可以对输入数据进行适当的特征提取和非线性映射,以便在网络训练过程中更好地学习数据的复杂关系。
torch.nn.Linear(,)就相当于层与层之间得链接,线性变换,两个参数分别是两层之间对应得维度
self.layers = torch.nn.Sequential(OrderedDict(layers))
-
OrderedDict(layers):
OrderedDict
是 Python 标准库中的一个数据结构,它类似于字典(Dictionary),但保留了元素的插入顺序。在这里,layers
是一个列表,包含了神经网络的各个层及其定义。通过使用OrderedDict
,可以确保在创建Sequential
时层的顺序是正确的,与layers
列表中定义的顺序一致。
-
torch.nn.Sequential:
Sequential
是 PyTorch 提供的一个层容器,用于按顺序组织神经网络的各个层。它接受一个有序的层结构作为输入,并按照这个顺序依次执行各个层的计算。
-
self.layers = torch.nn.Sequential(OrderedDict(layers)):
- 这行代码将通过
OrderedDict
构建好的层结构传递给Sequential
构造函数,然后将其赋值给self.layers
。通常情况下,self.layers
将会是整个神经网络的主要组件,它包含了从输入层到输出层的完整层序列。
- 这行代码将通过
总结来说,这段代码的作用是根据预先定义好的层结构列表 layers
,使用 OrderedDict
确保层的顺序正确,然后将其整合为一个顺序执行的神经网络模型 Sequential
,最终赋值给 self.layers
。这样的操作通常用于构建和管理神经网络模型的层次结构,便于后续的模型训练和推断。
创建了网络架构
def forward(self, x):
return self.layers(x)
-
self.layers(x):
self.layers
是在前面定义的Sequential
容器,它包含了整个神经网络模型的层结构。在这里,x
是输入到神经网络的数据(通常是一个批量的输入数据,比如一个 minibatch),self.layers(x)
表示将输入x
通过整个层结构进行前向传播计算。
-
forward 方法:
- 在 PyTorch 中,定义了
forward
方法的类是一个神经网络模型的关键部分。这个方法定义了数据在通过模型时的计算流程。具体来说,当调用模型实例并传入输入数据时,PyTorch 会自动调用该模型的forward
方法,从而执行前向传播计算。
- 在 PyTorch 中,定义了
-
返回值:
return self.layers(x)
表示将经过所有层的计算结果作为输出返回。在深度学习模型中,通常这个输出会经过损失函数计算损失,然后通过反向传播算法更新模型的参数。
总结来说,这段代码定义了一个神经网络模型中的前向传播方法,通过 self.layers
将输入数据 x
传递给整个神经网络的层结构,计算得到输出。这是构建和训练深度学习模型时必不可少的一部分,负责定义模型的计算逻辑和数据流向。
取点(网格均匀vs设定)
x = torch.arange(-1, 1 + self.h, self.h) # 在[-1,1]区间上均匀取值,记为x
t = torch.arange(0, 1 + self.k, self.k) # 在[0,1]区间上均匀取值,记为t
-
- 这种方式使用了
torch.arange
函数来在指定区间内以固定步长(self.h
和self.k
)生成均匀取样的数据点。 x
覆盖了从-1
到1
的空间区间,步长为self.h
。t
覆盖了从0
到1
的时间区间,步长为self.k
。
- 这种方式使用了
-
影响:
- 这种方法简单且直观,适合在给定区间内生成均匀分布的数据点。它的优点是易于实现和调整步长,可以控制数据点的密度。
- 缺点是数据点的分布可能不够灵活,无法自适应地在关键区域增加密度或调整取点策略。
2. DeepXDE 中的直接设置取点数目
-
描述:
- DeepXDE(Deep eXtreme Data-driven approach)是专门用于求解微分方程的深度学习库,通常会提供更复杂的数据点设置方式。
- DeepXDE 可能允许用户直接指定在空间和时间上的数据点数目,或者根据问题的复杂性和需求进行自适应的数据点采样。
-
影响:
- DeepXDE 的灵活性更高,可以根据求解的需求和精度要求动态地调整数据点的密度和分布。
- 这种方法适用于需要在某些区域增加数据点密度或者根据问题特性进行自适应取点的场景。
总结比较:
- 上面代码中的取点方式适合简单问题或者需要固定密度取点的情况,易于实现和调整。
- DeepXDE 中的直接设置取点数目更适合复杂的物理模型和需要高度控制的情况,能够根据求解需求灵活地调整数据点的分布和密度。
选择合适的取点方式取决于具体问题的复杂性、计算资源的可用性以及对结果精度的要求。
在 torch.arange(start, end, step)
中,三个参数的含义分别是:
start
: 序列的起始值(包含在序列中)。end
: 序列的终止值(不包含在序列中)。step
: 步长,即相邻两个值之间的间隔。- \
self.X_inside = torch.stack(torch.meshgrid(x, t)).reshape(2, -1).T
这行代码的目的是将两个一维张量 x
和 t
中的每对元素组合成二维坐标点,并将结果整形为一个二维张量。让我们逐步解释:
-
torch.meshgrid(x, t):
torch.meshgrid
函数接受两个张量x
和t
,并返回两个张量,分别表示在所有可能的 (x, t) 组合下的 x 坐标和 t 坐标。- 例如,如果
x
是[-1, 0, 1]
,而t
是[0, 0.5, 1]
,torch.meshgrid(x, t)
将返回两个张量,分别是x
坐标和t
坐标的网格。
-
torch.stack(...)
torch.stack
函数将两个张量堆叠在一起,形成一个新的张量。- 在这里,
torch.meshgrid(x, t)
返回的是两个张量,每个张量的形状是(len(t), len(x))
,表示 (x, t) 平面上的网格点。
-
.reshape(2, -1).T
.reshape(2, -1)
将堆叠后的张量重新整形为(2, len(x) * len(t))
的形状,即两行,每一列代表一个 (x, t) 组合。.T
表示转置操作,将结果张量转置为(len(x) * len(t), 2)
的形状,这样每一行都是一个 (x, t) 组合的二维坐标点。
所以,self.X_inside
最终是一个二维张量,每一行表示一个 (x, t) 组合的坐标点。
边界设置(时空坐标)
根据制作好的数据集来设置边界,这个例子是均匀取点所以x得1与-1是按照大小顺序可以直接提取,最后拼接三个初边值条件得数据集(默认按照行维度拼接,就是竖着放)
注意
bc1 = torch.stack(torch.meshgrid(x[0], t)).reshape(2, -1).T
x[0]
是一个标量值或者包含一个元素的张量,例如 x[0] = torch.tensor(-1)
或者 x[0] = torch.tensor([1.5])
。而 t
是一个一维张量,例如 t = torch.tensor([0, 0.5, 1])
。
现在来解释代码 bc1 = torch.stack(torch.meshgrid(x[0], t)).reshape(2, -1).T
的每一步:
-
torch.meshgrid(x[0], t):
torch.meshgrid
函数接受两个张量,并返回两个张量,表示它们的笛卡尔积。但是在这种情况下,x[0]
是一个标量或者包含一个元素的张量,它将被广播成与t
相同长度的张量。- 因此,
torch.meshgrid(x[0], t)
将返回两个张量,第一个是形状为(len(t),)
的张量,其中每个元素都是x[0]
的值;第二个是形状为(len(t),)
的张量,包含t
的所有元素。
-
torch.stack(...):
torch.stack
将这两个张量堆叠在一起,形成一个新的张量。- 如果
x[0]
是标量-1
,并且t = torch.tensor([0, 0.5, 1])
,那么torch.meshgrid(x[0], t)
返回的将是:
self.U_boundary = self.U_boundary.unsqueeze(1)
这行代码 self.U_boundary = self.U_boundary.unsqueeze(1)
的作用是在 self.U_boundary
张量的第一个维度(通常是行)上增加一个维度,这个维度的大小为 1。
让我们来解释这个操作的效果:
假设 self.U_boundary
是一个形状为 (n, m)
的张量,其中 n
是行数,m
是列数。执行 self.U_boundary.unsqueeze(1)
将会生成一个新的张量,形状为 (n, 1, m)
。具体来说:
- 如果
self.U_boundary
是形状为(5, 3)
的张量,那么它表示有 5 行数据,每行包含 3 个元素。 - 执行
self.U_boundary.unsqueeze(1)
将把这个张量变形为(5, 1, 3)
,其中:- 第一个维度仍然是原来的行数,即 5。
- 新增加的第二个维度大小是 1,这个维度代表了一个新的维度,通常用于扩展数据的维度表示或与其他张量进行广播操作。
- 第三个维度大小是原来的列数,即 3。
这种操作在深度学习中很常见,特别是在处理需要进行广播操作或者与其他维度不匹配的张量时。增加维度可以使得张量在不同操作之间的形状保持一致,从而避免维度不匹配的错误。
优化器设置
这段代码片段展示了在一个 Python 类中初始化了两种优化器:torch.optim.LBFGS
和 torch.optim.Adam
。让我解释一下每一部分的含义和作用:
-
self.iter = 1
: 这行代码将类的成员变量self.iter
设置为整数1
。这通常用于追踪迭代次数或者计数器的初始值。 -
self.lbfgs = torch.optim.LBFGS(...)
: 这里初始化了一个 LBFGS(Limited-memory Broyden-Fletcher-Goldfarb-Shanno)优化器。LBFGS是一种优化算法,特别适合处理大规模数据和复杂模型的优化问题。参数包括:self.model.parameters()
: 传入需要优化的模型参数。lr=1.0
: 学习率,控制每次参数更新的步长。max_iter=50000
: 最大迭代次数,LBFGS算法运行的最大迭代次数。max_eval=50000
: 最大评估次数,LBFGS算法中允许进行的最大函数评估次数。history_size=50
: 保存历史方向的数量。tolerance_grad=1e-7
: 梯度容差,LBFGS算法在梯度下降小于此阈值时停止。tolerance_change=1.0 * np.finfo(float).eps
: 参数变化容差。line_search_fn="strong_wolfe"
: 线搜索方法,这里使用的是强 Wolfe 条件。
-
self.adam = torch.optim.Adam(self.model.parameters())
: 这行代码初始化了一个 Adam 优化器。Adam是一种常用的随机梯度下降算法的变种,通常表现良好,适用于各种类型的机器学习任务。它的参数只有模型的参数列表。
这段代码示例展示了如何在 PyTorch 中初始化不同类型的优化器,并设置它们的参数以便于训练神经网络模型。
自动微分(X包含所有输入变量,也就是x与t)
du_dX = torch.autograd.grad(
inputs=self.X_inside, # 输入变量,对其求导
outputs=U_inside, # 输出变量,从中求导
grad_outputs=torch.ones_like(U_inside), # 梯度的输出值,这里设置为与 U_inside 形状相同的全为1的张量
retain_graph=True, # 是否保留计算图,通常在多次求导时需要设置为True
create_graph=True # 是否创建一个计算图,用于高阶导数计算
)[0]
解释每个参数:
-
inputs=self.X_inside
: 这是需要对其求导的输入变量,一般是一个张量或者一个包含模型参数的列表。 -
outputs=U_inside
: 这是需要求导的输出变量,即目标张量关于输入张量的梯度。 -
grad_outputs=torch.ones_like(U_inside)
: 这个参数是outputs
的梯度的输出值。在这里,使用torch.ones_like(U_inside)
创建一个与U_inside
张量形状相同的张量,所有元素都为1。这表示我们要计算的是U_inside
对self.X_inside
的一阶导数。 -
retain_graph=True
: 这个参数指示在计算完梯度之后是否保留计算图。通常在多次反向传播(求高阶导数)时需要将其设置为True
。 -
create_graph=True
: 这个参数指示是否在计算过程中构建一个计算图,以便计算更高阶的导数。在需要计算二阶导数或更高阶导数时,需要将其设置为True
。
最终,du_dX
是一个张量,其值是 U_inside
对 self.X_inside
的梯度。这种方式能够利用 PyTorch 的自动求导机制来方便地计算复杂函数关于输入变量的梯度,特别是在深度学习中,这种功能非常有用。
[0]作用:
在这段代码中,torch.autograd.grad
函数返回的是一个包含梯度张量的元组。即使我们只求一个梯度,它也会返回一个元组,因此我们需要使用 [0]
来获取元组中的第一个元素,即我们实际上想要的梯度张量。
具体来说,torch.autograd.grad
返回的是一个元组,其中包含了所有请求的梯度。即使我们只请求一个梯度,它也会返回一个长度为 1 的元组,因此我们使用 [0]
来访问这个元组中的第一个元素,即实际的梯度张量。
如果我们没有使用 [0]
,那么 du_dX
将会是一个包含单个元素(即梯度张量)的元组,而不是我们期望的张量对象。因此,[0]
的作用是提取出元组中的唯一元素,确保 du_dX
是一个张量,而不是一个包含单个元素的元组。
这种设计允许 torch.autograd.grad
函数灵活处理多个请求的情况,即使我们只请求一个梯度,也会按照元组的形式返回。