【图书推荐】《深入探索Mamba模型架构与应用》-CSDN博客
对于连续信号,确定其状态表示往往颇具挑战性。幸运的是,在实际应用中,我们通常处理的是离散输入(例如文本序列),这使得问题变得相对简单。因此,我们倾向于将模型进行离散化处理,以便更好地适应这种输入类型。
在这个过程中,零阶保持技术(zero-order hold technique)发挥着关键作用。其工作原理大致如下:每当系统接收到一个离散信号时,该技术会记录下这个信号的值,并将其保持不变,直到下一个离散信号的到来。这种处理方式有效地将离散的信号点转换为连续的信号段,为SSM提供了可用的连续信号输入。
3.2.1 将连续信号转换成离散信号详解与Python实现
状态空间模型(SSM)是一种强大的统计模型,特别适用于处理具有时间依赖性的连续时间序列。在SSM中,找到能够准确描述系统动态变化的状态表示h(t)是建模的关键。然而,对于连续信号来说,直接找到这样的状态表示可能相当困难,因为连续信号在时间和幅值上都是连续变化的。
幸运的是,在实际应用中,我们往往能够处理离散的输入数据,例如文本序列、时间序列数据点等。为了将这些离散数据应用于SSM,我们需要对模型进行离散化,使其能够适应离散输入,如图3-17所示。
图3-17 连续信号转换为离散信号
具体对于上图中的 来说,假设我们有一个离散的输入信号序列,每个信号值都是在不同的时间点上获取的。当我们开始处理这个序列时,首先记录下第一个信号值。然后,在下一个信号值到来之前,假设信号值保持不变,即保持为第一个信号值。当收到第二个信号值时,我们再次将其记录下来,并在接下来的时间内保持这个值,直到收到第三个信号值为止。这个过程会持续进行,每次都将新的信号值保持下来,直到整个离散信号序列都被处理完毕。
下面假设一个A和B的连续时间系统参数,并将其转换为离散时间矩阵,代码如下:
import numpy as np
from scipy.linalg import expm
# 假设的连续时间系统参数
A_continuous = np.array([[0.9, 0.1], [0.2, 0.8]]) # 连续时间状态转移矩阵
# 连续时间输入矩阵(注意:这里通常是一个矩阵,但为了简化示例,假设它是一个列向量)
B_continuous = np.array([[0.5], [0.3]])
# 采样周期T
T = 0.1 # 例如0.1秒
# 使用零阶保持技术离散化系统
# expm(A_continuous * T) 是离散时间状态转移矩阵A的近似值
A = expm(A_continuous * T)
# 注意:离散时间输入矩阵B的计算依赖于具体的系统模型和离散化方法
# 在这里,我们假设B与连续时间输入矩阵相同(这通常不是真实情况,但为了简化示例)
B = B_continuous # 示例中直接使用连续时间输入矩阵(在实际情况中可能不同)
# 现在我们有了离散时间系统的A和B矩阵
print("离散时间状态转移矩阵 A:")
print(A)
print("离散时间输入矩阵 B:")
print(B)
打印结果如下:
离散时间状态转移矩阵 A:
[[1.09428334 0.01088758]
[0.02177516 1.08339576]]
离散时间输入矩阵 B:
[[0.5]
[0.3]]
对比原始的A和B可以看到,此时通过转换后,我们得到一套新的离散矩阵。下面我们将使用离散时间系统模拟一个状态序列。完整代码如下:
import numpy as np
from scipy.linalg import expm
# 假设的连续时间系统参数
A_continuous = np.array([[0.9, 0.1], [0.2, 0.8]]) # 连续时间状态转移矩阵
# 连续时间输入矩阵(注意:这里通常是一个矩阵,但为了简化示例,我们假设它是一个列向量)
B_continuous = np.array([[0.5], [0.3]])
# 采样周期T
T = 0.1 # 例如,0.1秒
# 使用零阶保持技术离散化系统
# expm(A_continuous * T) 是离散时间状态转移矩阵A的近似值
A = expm(A_continuous * T)
# 注意:离散时间输入矩阵B的计算依赖于具体的系统模型和离散化方法
# 在这里,我们假设B与连续时间输入矩阵相同(这通常不是真实情况,但为了简化示例)
B = B_continuous # 示例中直接使用连续时间输入矩阵(在实际情况中可能不同)
# 示例:使用离散时间系统模拟一个状态序列
# 假设初始状态 x0 和输入序列 u
x0 = np.array([[1], [0]])
#假设u为连续信号
u = np.array([[0.1], [0.2], [0.1], [0.2], [0.1]]) # 5个时间步的输入(注意:这里假设输入是一个列向量序列)
# 初始化状态序列(包括初始状态)
x = np.zeros((A.shape[0], len(u) + 1))
x[:, 0] = x0.flatten() # 设置初始状态
# 假设系统是一个没有控制输入的自治系统,或者我们忽略输入u的影响(为了简化示例)
# 在实际情况下,你可能需要在每个时间步都考虑输入u的影响
for k in range(len(u)):
x[:, k + 1] = A.dot(x[:, k]) + B.dot(u[k]) # 注意:这里包括输入u的影响
print("离散时间状态序列 x:")
print(x)
在上面的示例中,我们假设u是连续信号(或者说,是表示连续时间输入的信号序列的离散近似),因为它在每个时间步(或采样点)都有一个值。然而,严格来说,u已经是离散化的,因为我们通常不能直接在计算机中表示连续时间的信号,除非使用无限精度的表示方法(这在实践中是不可能的)。
被转换成的离散信号是系统的状态序列x。这个序列在每个时间步(或采样点)都有一个值,表示系统在那个时间点的状态。由于我们使用了离散时间状态转移矩阵A和(可能的)离散时间输入矩阵B,x序列就是一个完全离散化的表示,它描述了系统状态随时间(离散时间点)的变化。
最终结果如下:
离散时间状态序列 x:
[[1. 1.14428334 1.3527339 1.53180943 1.77854416 1.99975097]
[0. 0.05177516 0.14100994 0.21222556 0.32327967 0.4189679 ]]
3.2.2 离散状态空间的Python实现
Zero-Order Hold(ZOH,零阶保持)是一种在信号处理中广泛采用的插值技术,旨在将连续时间信号转换为离散时间信号。其工作原理是在连续信号的采样点之间,简单地使用前一个采样点的信号值作为当前时间段的输出值,直至下一个采样点出现。简而言之,当没有新的采样点更新时,Zero-Order Hold保持信号值不变,从而构建了一个等间隔的离散时间信号序列,如图3-18所示。
图3-18 零阶保持技术
每当我们接收到一个离散信号,其值会被保留,直至下一个离散信号的到来。这种保留机制实质上创建了一个可供状态空间模型使用的连续信号。保留信号值的时间跨度由一个名为“步长”(Δ,size)的新学习参数来控制,这个参数反映了输入信号的阶段性保持能力,即分辨率。有了这种连续的输入信号,系统便能生成连续的输出,同时,我们仅根据输入的时间步长对输出值进行采样。
在两个连续采样点之间的时间段内,输出信号将维持前一个采样点的值,这一特性使得信号在采样点之间保持恒定。因此,Zero-Order Hold技术适用于那些需要快速且简便地将连续信号转换为离散信号的场景。
在模拟信号处理领域中,特别是在模拟到数字转换(Analog-to-Digital Converter,ADC)和数字到模拟转换(Digital-to-Analog Converter,DAC)系统中,Zero-Order Hold技术发挥着不可或缺的作用。无论是将连续的模拟信号转换为离散的数字信号以便于数字处理,还是将数字信号重新转换为连续的模拟信号以便于实际应用,Zero-Order Hold都以其简单高效的特性成为工程师的首选工具。
而保持该值的时间由一个新的可学习参数表示,称为步长(step size),它代表输入的分辨率。
通过这种方式,零阶保持技术将离散的信号值转换为一种“连续”的信号表示。虽然从严格意义上来说,这种信号并不是真正的连续信号(因为它仍然是由离散的信号值组成的),但在SSM的上下文中,我们可以将其视为一种近似的连续信号。
SSM可以利用这种近似的连续信号来建立状态表示h(t)。通过定义状态转移矩阵A和输入矩阵B等参数,SSM能够描述系统状态如何随时间变化以及外部输入如何影响这些状态。然后,SSM可以利用这些参数和零阶保持技术生成的连续信号表示来推断出最有可能的状态序列,从而实现对数据的建模和预测。
把离散信号连续化之后,就有了连续的输入信号,连续信号可以生成连续的输出,并且仅根据输入的时间步长对值进行采样,如图3-19所示。
图3-19 将连续信号转换成离散信号
让我们考虑一个连续时间SSM,它通常描述为一系列微分方程或差分方程。为了将这样的系统转换为离散时间模型,我们需要对时间进行离散化,并在每个采样时刻保持信号值不变,直到下一个采样时刻。这就是零阶保持技术的核心思想,如图3-20所示。
图3-20 连续信号的离散化
这些限制意味着A、B、C矩阵均能通过N个数字表示,这使得SSM在参数数量和计算复杂度上相对较低。此外,由于SSM独立作用于每个通道(或维度),每个输入的总隐藏状态的维度为N*D,其中N是状态变量的数量,D是通道(或维度)的数量。这意味着SSM可以独立地处理每个通道的数据,从而增加了模型的灵活性和可扩展性。
下面我们实现离散状态空间的状态转移与观测值的获取,完整代码如下:
import numpy as np
# 这里为了简化,我们直接定义A_bar和B_bar
A_bar = np.diag([0.9, 0.8]) # 假设的离散时间状态转移矩阵(对角)
B_bar = np.array([[0.4], [0.6]]) # 假设的离散时间输入矩阵
C = np.array([1.0, 2.0]) # 示例观测矩阵(行向量)
# 初始化状态变量和输入变量
x = np.array([0.0, 0.0]) # 初始状态
u = np.array([1.0]) # 输入信号
# 离散时间状态更新和观测
def update_and_observe(x, u, A_bar, B_bar, C):
# 状态更新
x_next = np.dot(A_bar, x) + np.dot(B_bar, u)
# 观测
y = np.dot(C, x_next)
return x_next, y
# 模拟几个时间步
for t in range(10):
x_next, y = update_and_observe(x, u, A_bar, B_bar, C)
print(f"Time step {t + 1}: State x = {x_next}, Observation y = {y}")
x = x_next # 更新状态以进行下一次迭代
打印结果如下:
Time step 1: State x = [0.4 0.6], Observation y = 1.6
Time step 2: State x = [0.76 1.08], Observation y = 2.92
Time step 3: State x = [1.084 1.464], Observation y = 4.0120000000000005
Time step 4: State x = [1.3756 1.7712], Observation y = 4.918
Time step 5: State x = [1.63804 2.01696], Observation y = 5.67196
Time step 6: State x = [1.874236 2.213568], Observation y = 6.301372000000001
Time step 7: State x = [2.0868124 2.3708544], Observation y = 6.828521200000001
Time step 8: State x = [2.27813116 2.49668352], Observation y = 7.271498200000002
Time step 9: State x = [2.45031804 2.59734682], Observation y = 7.645011676000001
Time step 10: State x = [2.60528624 2.67787745], Observation y = 7.961041145200001
在上面的Python代码中,y表示观测值(Observation),它是通过观测矩阵C与当前状态x_next(或上一个时间步更新后的状态x,但在本例中我们使用x_next来保持一致)相乘得到的。观测值通常代表我们可以从系统中测量或观察到的数据。
其中的np.dot(C, x_next)计算了当前状态x_next通过观测矩阵C映射到观测空间的结果。观测矩阵C定义了如何从状态空间映射到观测空间,即它定义了哪些状态变量是可观测的,以及它们如何被组合成观测值。
在实际应用中,观测值y可能与真实状态之间存在误差或噪声,这取决于系统的性质以及测量设备的精度。在上面的示例中,我们假设观测值y是准确无误的,但在实际系统中,可能需要考虑观测噪声的影响。