☰
Koopman算子入门:DMD、EDMD与非线性系统线性化
2026/10/1 19:36:05 网站建设 项目流程

1. 非线性数据撞墙之后:Koopman operator 到底在解决什么问题

非线性系统的数据分析,十有八九会撞到同一堵墙:你手上的物理模型是非线性的,可你所有的成熟工具——特征值、模态分解、传递函数、二次型最优控制——全都在线性框架里。这不是工具不行,而是对象的表达方式不匹配。Koopman operator这个名字之所以近几年在控制和动力学圈子里被反复提起,就是因为它给了一个看似"作弊"的方案:不改变系统的非线性本质,而是换一个空间去看它,让演化规律在那个空间里变得严格线性。

1.1 局部线性化为什么总在关键时刻掉链子

大多数工程的起点是雅可比线性化:在某个平衡点附近一阶泰勒展开,得到一个线性系统,然后在它上面设计观测器、控制器、滤波器。这套方法在平衡点附近确实好用,但它有一个很难绕开的物理限制——它只在那个点的小邻域内闭环成立。我做过一个非线性弹簧的算例,随着振幅增大,等效刚度变化,线性模型的频率估计一路偏,控制器必须靠增益调度到处打补丁,而增益调度的表格本身又是在线调试出来的,换个工况就得重来。

更麻烦的是,很多系统最有价值的行为恰恰发生在远离平衡点的地方:极限环、多平衡点之间的跳跃、软弹簧的跳跃现象、边界上的阀门饱和。这些现象的定性特征在线性模型里被直接抹掉了。你可以在不同工作点线性化出十几个模型拼起来,但它们之间怎么切换、切换是否连续、切换瞬间的能量是否守恒,往往只能靠经验判断。这不是算力问题,是表达能力的上限。

1.2 换坐标系不如换函数空间

面对非线性,人的第一反应是找一个坐标变换,把系统在新坐标下写成线性方程。这个想法在数学上有个名字叫全局线性化,但结论很悲观:除了少数特殊系统,通用水久性的全局线性化坐标变换根本不存在。你花大量时间去猜那个变换,通常是在解一个和原问题差不多难的偏微分方程。

Koopman 的解法换了个方向:不去改坐标,而去改我们观测系统的方式。系统的状态 x 本身可能是个很别扭的对象,但我们真正关心的、传感器能测到的、控制目标里出现的,往往不是 x 而是 x 的某个函数——能量、某个方向的速度、几阶矩、某个积分量。把这些函数当作新的"坐标",你会发现演化规律变成了线性的。代价是这个新空间是无穷维的,但"线性 + 无穷维"比"非线性 + 有限维"在很多任务上要好处理得多:线性算子的谱理论、叠加原理、模态分解全都能直接用。

一个直观的类比:地面是起伏的,你没法把它铲平,但你可以改画地图的方式——不用等高线,而用另一种投影,让测地线在地图上变成直线。地形没变,你的尺子变了。

1.3 它值得投入时间学吗

判断标准很朴素:如果你的核心痛点是"非线性系统的谱分析、预测与控制",并且你手上有轨迹数据或者已知的动力学方程,那 Koopman 是个值得放在工具箱里的框架。它的最大价值在于把"从数据里提取线性结构"这件事系统化了,而且给了明确的适用范围和失效边界。你不一定需要手推公式,但必须理解它的近似发生在哪个环节,否则调参时完全没有方向感。

2. 把定义掰开:算子、生成元与谱

2.1 定义和它为什么天然线性

考虑离散映射 x_{k+1} = F(x_k),x 取值于状态空间 M。取一个可观测量 g: M → C,Koopman 算子 U 定义为

(Ug)(x) = g(F(x))

也就是"先用动力学往前推一步,再观测"。线性性几乎不需要证明:对任意两个可观测量 g1、g2 和常数 α、β,

U(αg1 + βg2)(x) = (αg1 + βg2)(F(x)) = α g1(F(x)) + β g2(F(x))

没有任何近似成分,这个线性是严格的。迭代 k 步就是U^k g = g ∘ F^k,连续时间流映射 Φ^t 对应(U^t g)(x) = g(Φ^t(x)),并且满足半群性质U^{t+s} = U^t U^s。注意这里"线性"是对函数说的,不是对状态说的——新手最容易在这里绕不出来,把它当成"找到了一个线性系统来替代非线性系统",那就跑偏了。

2.2 连续时间:生成元才是做数值计算的人最该关心的事

离散算子看着简洁,实际计算时更多用它的无穷小生成元。对连续时间系统 ẋ = f(x),生成元定义为

L g = lim_{t→0} (U^t g - g) / t = ∇g(x) · f(x)

这条式子看起来简单,但它意味着一个非常实用的结论:只要你已知 f 和可观测量 g 的梯度,Lg 在任何采样点上都能算出来,根本不需要把系统积分起来。这一点在工程上极其关键,后面讲 gEDMD 时会反复用到。有限时间演化由指数映射给出U^t = exp(Lt),所以连续时间框架里特征值的物理意义更直接:φ(x(t)) = e^{μt} φ(x(0)),实部是增长率,虚部是振荡频率。

2.3 特征函数才是整套理论的核心

Koopman 算子的特征对满足

U φ = λ φ, 即 φ(F(x)) = λ φ(x)

在连续时间下写成L φ = μ φ,两者关系是 λ = e^{μΔt}。这条性质的含义非常物理:特征函数沿轨迹按指数规律演化。任何一次预测,本质上都是把一个特征函数乘以一个因子。更妙的是,特征函数可以自由组合生成新的特征函数——φ1 对应 μ1、φ2 对应 μ2,则乘积 φ1φ2 对应 μ1+μ2,幂次 φ^n 对应 nμ。这类"共振"结构决定了哪些项能在一个有限字典里被精确闭合,也解释了为什么有些系统用多项式字典几步就能算得很准,而有些系统怎么加特征都收敛不了。

这里有个必须记住的限制:特征函数是定义在整个状态空间上的函数,但数据驱动方法只能在数据覆盖的区域里估计它。在数据支撑集之外,你拟合出来的特征函数可以随便乱跑,没有任何约束。把线性代理模型外推到没见过的工况,是本领域最常见的事故来源。

2.4 Koopman 模式与那条预测公式

对向量值可观测量 h: M → R^n(最常用的就是状态本身),如果它能按特征函数展开成

h(x) = Σ_k φ_k(x) v_k

那么沿轨迹有

h(x(t)) = Σ_k e^{μ_k t} φ_k(x(0)) v_k

这里的 v_k 叫Koopman 模式,它是个向量,代表第 k 个模态在空间上的形状;φ_k 是特征函数,代表这个模态在初始时刻被"激活"了多少;e^{μ_k t} 负责时间演化。这三者分离得很干净,也是 Koopman 方法能同时兼顾预测和可解释性的原因:模式可以做空间上的物理诊断,特征值可以做频率和稳定性判断,而预测只需要三者相乘求和。

2.5 顺带说一句 Perron-Frobenius

Koopman 算子的伴随算子是 Perron-Frobenius 算子,作用在密度函数上,描述概率密度怎么被动力学推着走。两者在 L² 内积下互为伴随。对保测系统,两个算子都是酉的,谱落在单位圆上。搞明白这层关系有个实际好处:如果你在做概率密度传播、不确定性量化或者集合预报,PF 视角和 Koopman 视角是同一枚硬币的两面,用哪个取决于你要的是"某函数怎么变"还是"分布怎么变"。

3. 三条计算路线:DMD、EDMD、深度学习

3.1 DMD:不选字典的代价与收益

Dynamic Mode Decomposition 是这套体系里最省事的一招。给两组快照矩阵 X = [x_1 … x_m]、Y = [x_2 … x_{m+1}],假设存在线性关系 Y ≈ A X,最小二乘意义下 A = Y X^+。对 A 做特征分解,得到的特征值就是 Koopman 特征值的近似,特征向量对应的就是模式。

它为什么能行?因为 A 实际上是 Koopman 算子限制在快照张成的子空间上的一个有限维近似。它为什么常常不准?因为它默认可观测量就是状态本身,也就是字典取成了恒等映射。对本质上非线性很强的系统,状态这个字典太薄了,谱估计会被严重污染。我见过太多"DMD 跑不出想要的频率就加 SVD 秩"的用法,其实问题不在秩,在于字典选错了方向。DMD 的收益是几乎不需要超参数、实现只要二十行;代价是你放弃了选择观测空间的权利。

3.2 EDMD:字典加 Galerkin 投影的标准做法

Extended DMD 把字典显式化。选一组函数 ψ_1 … ψ_N,拼成向量 ψ(x),构造两个数据矩阵

Ψ_X = [ψ(x_1) … ψ(x_m)],Ψ_Y = [ψ(x_2) … ψ(x_{m+1})]

然后在最小二乘意义下求 K 使得 Ψ_Y ≈ K Ψ_X,即 K = Ψ_Y Ψ_X^+。K 是 N×N 的矩阵,它的特征值近似 Koopman 特征值,对应特征向量 v 给出特征函数近似 φ(x) ≈ ψ(x)^T v(注意转置约定,实际取 K^T 的特征向量,写代码时这一步最容易搞反)。同样可以求一个系数矩阵 B,把状态从字典里读出来:x ≈ B^T ψ(x)。于是预测变成一句非常干净的式子:ψ(x_k) ≈ K^k ψ(x_0),x_k ≈ B^T K^k ψ(x_0)。

EDMD 的收敛性在离散谱假设下有理论保证:字典规模和数据量按合适的方式同时增长时,有限维近似在 Galerkin 意义下收敛到真实算子。实践中的经验门槛是样本数要远多于特征数,我一般按 m ≥ 10N 起步,特征数一多就优先扩数据,而不是硬调正则。

3.3 连续时间版本:gEDMD 为什么有时候更香

gEDMD 直接用生成元的定义去做回归。取样本点 x_i 和对应的向量场取值 f(x_i),对第 j 个字典函数计算 ∇ψ_j(x_i) · f(x_i),拼成矩阵 Ψ_dot,然后解 K = Ψ_dot Ψ_X^+,特征值直接是 μ 而不是 λ。

它有三个实际优势:一是不用等间隔采样,数据可以来自不同来源、不同步长;二是不需要长时间轨迹,甚至可以在状态空间里自由撒点(只要 f 已知或能评估),非常适合"物理模型昂贵但可调用"的场景,比如你用高保真仿真器在参数空间里采样,只想得到一个线性代理模型;三是不受离散化误差影响,时间步长很大时比离散版本稳。代价是需要导数信息,而导数往往是最好的噪声放大器——后面踩坑部分会专门讲。

3.4 深度 Koopman:让网络把字典学出来

手工选字典的痛苦在于"你得先知道非线性长什么样"。Deep Koopman 用编码器 ψ_θ 把状态映射到潜空间,在潜空间里放一个线性矩阵 K,再用解码器还原。损失函数一般三项:重构误差、多步线性一致性(ψ(x_{k+1}) ≈ K ψ(x_k)连续推若干步)、以及可选的特征值辅助网络(让连续时间特征值也变成可学的输出)。这类结构在 2018 年前后被系统提出后,成为高维、强非线性系统的常用方案。

它的优点是省去了人工设计,缺点是丢掉了 Keopman 理论里最值钱的东西——可解释性和误差界限。潜变量没有物理含义,K 的特征值也没有清晰的支撑集约束。我的建议是:把它当成"当你实在找不到合适字典时的兜底方案",而不是默认起点。先用多项式字典跑一遍,看看谱的结构长什么样,再决定要不要上网络。

3.5 只有部分状态能测怎么办:延迟嵌入

现实里经常只能测到一个通道,或者状态维度远高于传感器数量。这时候用延迟嵌入:把h_k = [x_k; x_{k+1}; …; x_{k+d-1}]当成新状态,在这个延迟坐标上做 DMD,也就是 Hankel-DMD。Takens 嵌入定理保证了在一定条件下延迟坐标能重建原系统的拓扑结构,所以这条路在理论上是站得住的。

一个特别有工程价值的变体是 HAVOK:对 Hankel 矩阵做 SVD,取前若干个奇异向量构造线性系统,剩下的能量归到一个"强迫项"里。它给出的不是一个完美自治的线性模型,而是"线性系统 + 一个可解释的间歇强迫信号",这个强迫信号往往正好对应系统里的爆发式事件。我处理过一个间歇性振荡的数据,谱估计算出来的频率对不上,换成 HAVOK 之后强迫项的峰值位置和实际的爆发时刻几乎一一对应,解释力强得多。

3.6 带输入的系统:DMDc 与 EDMDc

控制系统是 x_{k+1} = F(x_k, u_k),做法是把输入拼进回归:

ψ(x_{k+1}) ≈ A ψ(x_k) + B u_k

这就是 EDMDc。关键在于这个形式的适用条件:它假定动力学在升维空间里对输入是仿射的。如果真实系统里的输入是相乘进去的(比如力矩乘以角度),仿射假设就不成立,需要额外加 u ⊗ ψ(x) 这类双线性项,否则模型只在输入幅值很小的时候勉强可用。我在一个带推力输入的算例上验证过,只加 u 的模型在小推力下预测误差 2% 左右,推力放大三倍后误差直接涨到 40% 以上;补上交叉项后回到 5% 以内。这一步不做,后面的线性 MPC 就成了在错误模型上优化。

4. 手把手算一遍:从快照到谱的可复现代码

4.1 数据与预处理:一半的坑在这里

准备快照时,间隔要均匀,轨迹要足够长,并且一定要留一条完全独立的测试轨迹,不要用训练数据的后 20% 当测试——自相关会让评估虚高。标准化必须做:各个状态量量纲不同,直接扔进最小二乘会让数值大的维度主导整个回归。我习惯对每个维度除以它的标准差,或者统一映射到 [-1, 1]。

采样率的选取也有讲究。太密,相邻快照几乎相同,X 的条件数很差;太疏,一步线性关系被非线性吃掉。一个经验做法是先按系统最快时间尺度的 1/20 到 1/50 取步长,然后做一次步长敏感性扫描,看谱的稳定性。

4.2 字典函数怎么写

下面这个字典包含常数项、一次项、二次项和若干高斯径向基,是最通用的起点。

import numpy as np def make_dictionary(z, centers, gamma=1.0): """z: (n, m) 标准化后的状态;centers: (n_c, n) 基函数中心""" m = z.shape[1] n = z.shape[0] cols = [np.ones((1, m)), z] for i in range(n): for j in range(i, n): cols.append(z[i:i+1] * z[j:j+1]) # 含平方项与交叉项 for c in centers: d2 = ((z.T - c) ** 2).sum(axis=1, keepdims=True).T cols.append(np.exp(-gamma * d2)) return np.vstack(cols) def standardize(X, mu=None, sd=None): if mu is None: mu = X.mean(axis=1, keepdims=True) sd = X.std(axis=1, keepdims=True) + 1e-12 return (X - mu) / sd, mu, sd

字典规模要克制。常数 + 一次 + 二次在三维系统里就是 10 项,加上 30 个 RBF 才 40 项,样本数取 1000 左右就很稳。我看到过有人给三维系统配了 500 个 RBF,样本只有 300 个,残差在训练集上趋近于零,测试集上一塌糊涂。

4.3 DMD 的二十行实现

def dmd(X, Y, r=10, dt=1.0): U, S, Vh = np.linalg.svd(X, full_matrices=False) Ur, Sr = U[:, :r], S[:r] Vr = Vh[:r, :].conj().T Atilde = Ur.conj().T @ Y @ Vr @ np.diag(1.0 / Sr) lam, W = np.linalg.eig(Atilde) Phi = Y @ Vr @ np.diag(1.0 / Sr) @ W # DMD 模式 omega = np.log(lam) / dt # 连续时间特征值 return lam, Phi, omega # 预测:x_k ≈ Phi @ diag(lam**k) @ b,b = pinv(Phi) @ x0

秩 r 的选法建议看奇异值谱的拐点,而不是凭感觉。如果能量衰减很平缓,说明数据被噪声或宽带成分主导,这时任何单一秩都不可靠,应该改用 Hankel 版本或者缩短预测窗口。

4.4 EDMD 的实现与预测

def edmd(X, Y, centers, gamma=1.0, ridge=1e-10): zx, mu, sd = standardize(X) zy = (Y - mu) / sd PsiX = make_dictionary(zx, centers, gamma) PsiY = make_dictionary(zy, centers, gamma) N = PsiX.shape[0] G = PsiX @ PsiX.T + ridge * np.eye(N) K = PsiY @ PsiX.T @ np.linalg.inv(G) # psi(x_{k+1}) ≈ K @ psi(x_k) Bt = X @ PsiX.T @ np.linalg.inv(G) # x ≈ Bt @ psi(x) return K, Bt, mu, sd def rollout_edmd(K, Bt, psi0, steps): out = [] psi = psi0.copy() for _ in range(steps): out.append(Bt @ psi) psi = K @ psi return np.array(out).T

特征函数从 K 的左特征向量拿:vals, vecs = np.linalg.eig(K.T),然后phi(x) = make_dictionary(z(x), centers) . vecs[:, i]。这一步写错方向,会得到一组看起来数值正常但物理意义完全相反的特征函数,而且很难从预测误差上发现,只有在做模态解释时才会暴露。

4.5 用什么指标判断算得对不对

我固定用这五项检查,少一项都容易自欺欺人:

  • 单步预测误差:||x_{k+1} - B^T K ψ(x_k)|| / ||x_{k+1}||,能过这一步说明回归本身没错。
  • 多步 rollout 误差曲线:画到预测发散为止,看有效预测时长。这个数字比任何单点误差都诚实。
  • 特征函数残差:||ψ(x_{k+1})^T v - λ ψ(x_k)^T v|| / (|λ| · ||ψ(x_k)^T v||),只保留残差小的那些对,其余的当伪特征值处理。
  • 谱的稳定性:换秩、换字典规模、换数据段,只保留在多次变化中都出现的特征值。真正的物理模态是稳定的,数值artifact 会到处漂。
  • 模态的空间结构:把 v 可视化,看它是不是对应一个能说清楚的物理形态。说不清楚的模态,在预测里通常也是垃圾。

5. 实战踩坑清单:Koopman 用起来最容易翻车的地方

5.1 伪特征值比真特征值多得多

N 维字典就会给出 N 个特征值,其中真正逼近真实谱的可能只有几个。剩下的数值取决于字典、数据长度、正则化强度,物理上毫无意义。典型表现是谱里混进一堆模长远离单位圆的点,或者频率明显不合理的点。

排查思路:固定字典,把样本量减半、再减半,看哪些特征值会漂;固定样本,把字典规模从 N 加到 2N,看哪些点稳定出现。两轮筛选下来通常能砍掉一大半。还有一条判据来自物理:对保守系统或耗散系统,谱的位置本来就有约束(保测的落在单位圆上,稳定系统的落在圆内),落在明显违反约束位置的点,先怀疑是数值产物。

5.2 特征函数在数据支撑集之外没有任何保证

这是我认为最危险的一条。特征函数是在数据分布上拟合出来的,出了数据覆盖范围,它的取值完全取决于字典函数在那边长什么样,没有任何物理约束。一个在 [-1, 1] 上训练出来的模型,外推到 1.5 就敢给你一个数量级离谱的预测,而且曲线看起来还很光滑。

工程上的应对办法是给代理模型配一个有效域判据:用核密度或者马氏距离判断当前状态是否落在训练分布内,落在外面就报警或切回原始模型。另外,字典不要用生长极快的高阶多项式,三次封顶通常够用,需要更强表达力时优先用有界基函数。

5.3 字典病态与正则化的尺度

高阶多项式字典的条件数会指数级恶化,一个直接的后果是每次重跑数据、特征值位置都不一样,看起来像随机数。处理方法有两条:一是改用正交多项式基(在标准化坐标下的 Legendre 或 Chebyshev)而不是单项式;二是老老实实加岭正则,并且把正则强度按数据量缩放,而不是固定一个 1e-8。

正则的选择可以用留出验证集来定:把正则强度扫一遍,取 rollout 误差最小而不是单步误差最小的那个值。这两个目标经常不一致,单步最优往往过拟合,rollout 会明显更差。

5.4 数值微分、采样率和噪声

用 gEDMD 或者任何连续时间方法时,导数精度直接决定结果好坏。前向差分噪声放大倍数是 1/Δt,中心差分是 1/(2Δt),所以步长不能太小。我的流程一般是:先做一次轻度平滑(Savitzky-Golay,窗口取到刚好抹掉高频噪声),再做中心差分,最后扫一遍不同窗口看谱是否稳定。

如果是仿真数据且 f 已知,最省事的做法是直接解析求导或者用自动微分,不要走差分。这一点很多论文对比实验里做得不干净,导致连续时间方法的性能被低估。

5.5 长期预测的相位漂移与模长偏置

特征值的模长估计只要偏差千分之几,推到几百步之后振幅就明显不对;相位的偏差表现为振荡峰逐渐错位。这在纯振荡系统里特别明显。缓解办法有三个:预测时用连续时间生成元加矩阵指数而不是反复乘 K,减少累积误差;把特征值按物理知识做投影修正(比如已知该系统保守,就把模长拉到 1);对长期任务放弃纯开环,改为定期用观测做状态校正,也就是把 Koopman 模型接到一个观测器框架里。

还有一类特殊情况是特征值碰撞。当两个特征值数值上非常接近(比如 μ2 ≈ 2μ1 的共振情形),有限维近似会退化成带若尔当块的矩阵,这时候模式不再是纯指数,而是带有时间多项式因子。标准的特征分解在这种情况下降得非常快,读出来的模式没有意义。遇到谱图上两个点几乎重合,先检查是不是共振,再考虑用 Schur 分解或者直接减小字典。

6. 场景选型:什么时候该用它,什么时候该绕开

6.1 已经跑通的典型场景

流体是最成熟的战场。尾流、射流、燃烧振荡这类问题里,DMD 出来的模式可以直接对应到涡结构,主导频率和实验测量基本对得上,用于诊断和降阶建模都很顺手。控制领域这几年进展很快,把 Koopman 线性代理模型接进 MPC,用在软体机器人、无人机姿态、机械臂这类强非线性但有规律的对象上,比增益调度省事,比端到端神经网络好调试。电力系统里用它做暂态分析和风电场协调控制,分子动力学里用它做长时间尺度的构象预测,都有公开的案例。

一个共同特征是:系统维度不算太高,非线性有结构(多项式型或周期型),而且对可解释性有要求。满足这三条,Koopman 基本是首选之一。

6.2 我明确会绕开的情况

  • 强不连续动力学:干摩擦、碰撞、开关切换。特征函数在这些地方不光滑,梯度定义都成问题,硬上会得到一堆无法解释的谱。
  • 强随机或强噪声主导:信噪比低于 10 dB 时,谱估计基本靠猜。要么先做系统辨识降噪,要么改用随机版本的算子分解,但那已经是另一套工具了。
  • 本质连续谱的系统:湍流、混沌吸引子上,Koopman 谱是连续的,没有离散特征对可用。DMD 还能给出模式,但它们不是特征函数,长期预测没有意义。
  • 维度极高的全状态升维:状态上千维还想去构造字典,特征数会爆炸,样本永远不够。这时候应该先降维或者直接用延迟嵌入的单通道版本。

6.3 和相邻方法的对照

方法模型形式可解释性数据需求典型适用场景
雅可比局部线性化线性,单点有效高无需数据平衡点附近的小扰动控制
EDMD / Koopman升维空间线性高中等,需覆盖工况全局谱分析、模态诊断、线性 MPC
SINDy原空间稀疏非线性 ODE高中等,需要导数方程结构发现,物理建模
纯神经网络黑箱低大高维、无结构先验的预测任务
延迟嵌入 / HAVOK线性 + 强迫项中高小,单通道可用部分观测、间歇爆发事件

选型的核心问题是三个:你要的是谱还是预测,你要不要可解释,你有多少数据覆盖多少工况。三个问题的答案基本上就把方法定死了。SINDy 和 Koopman 不是竞争关系,很多时候我先把 SINDy 跑一遍看看有没有干净的稀疏结构,有的话直接用原方程,没有或者方程太复杂,再切到 Koopman 找线性代理。


最后分享两个我反复用到的经验。一是永远先在一个你能手推出解析特征函数的玩具系统上验证代码,比如 ẋ1 = μx1、ẋ2 = λ(x2 - x1²) 这类系统,字典取 {1, x1, x2, x1²} 时特征值应该精确等于 μ、λ、2μ,特征函数分别是 x1、x2、x1²。代码在这上面跑不出解析结果,后面所有更大的算例都是白搭。二是所有调参都以"多步 rollout 误差"为唯一评判标准,单步误差、训练残差、重构误差都能骗人,唯独把模型撒开跑一段再看误差不会骗人。这两条踩过几次坑之后,我在 Koopman 上浪费的时间少了至少一半。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询