☰
支持向量机SVM原理详解:从间隔最大化到核函数与工程实践
2026/10/21 19:39:25 网站建设 项目流程

做机器学习做到一定阶段,很多人都会卡在支持向量机(SVM)这一关。网上讲SVM的资料浩如烟海,但要么上来就堆拉格朗日乘子,要么只给结论不给过程,真正能把“为什么要这样设计”和“公式怎么一步步推出来”串成一条逻辑链的,并不多。今天这篇,我想从实际理解的角度,把SVM的目标函数、对偶问题、KKT条件、软间隔、核函数这条主线完整过一遍。你不需要什么高深数学底子,只要会求偏导、知道什么是凸优化,就能跟上这条链路:间隔最大化 → 写成约束优化 → 拉格朗日对偶 → 从KKT条件里挖出支持向量 → 引入核函数。文章适合已经跑过一些模型、想真正搞懂SVM原理的读者,也适合准备面试、需要把推导讲清楚的朋友。我会把每一步公式掰开揉碎,附上直觉解释和工程经验。

1. 从一个二维问题讲起:SVM到底在优化什么

1.1 为什么是“最大间隔”而不是“分对就行”

先忘掉所有公式,看最朴素的情况。假设一张纸上有一堆红点和蓝点,你的任务是画一条线把它们分开。最简单的做法是随便画一条能把两类点完全分开的线——这在机器学习里对应感知机,它只要求“分对”,不要求“怎么分”。

但问题恰恰出在这里。能分开红蓝点的线有无数条,稍微倾斜一点、平移一点,都能满足“全部分对”。你随手选的那条线,可能刚好贴着某一类点擦过去。这时候如果新来一个点,恰好落在边界附近,就非常容易被分错。就像开车压着路肩走,速度一快、方向盘一抖,直接就蹭到护栏了。

SVM的做法从根本上不同:它不只要求分对,还要求分得“尽可能安全”。那条线要离两类点都保持最远距离,这个距离就是“间隔”。你把间隔想象成公路中间的隔离带,隔离带越宽,两侧车辆越难越过中线相撞。SVM找的,就是那条让隔离带最宽的分界线。

用数学语言说:一个超平面把样本分开,所有样本点到这个超平面的最小距离,叫“间隔”。SVM找的那个超平面,就是让这个最小距离最大化。这个思想听起来简单,但它是整个SVM公式体系的起点。后面所有的推导,本质上都是在问一个问题:这个“最大间隔超平面”怎么求?它长什么样?

1.2 函数间隔与几何间隔:同一个本质的两种尺子

为了把“间隔”写进公式,我们需要一个超平面的表达。通常写成:

w·x + b = 0

w是法向量,b是偏置。对一个样本点x,如果 w·x + b > 0,判定为正类;小于0判定为负类。距离呢?点到平面的距离公式是 |w·x + b| / ||w||。这里 ||w|| 是法向量的模长。

在推导SVM时,有两个容易混淆的概念,必须先分清楚:

函数间隔:y(w·x + b)。注意这里乘了标签y(取值为+1或-1)。如果分类正确,这个值一定是正数。函数间隔的好处是容易计算,但坏处是它受w和b的尺度影响。你把w和b同时放大10倍,超平面没变,函数间隔却变成了原来的10倍。换句话说,函数间隔是一把没有刻度的尺子。

几何间隔:|w·x + b| / ||w||。这才是点到超平面的真正物理距离,它不随w和b的等比例缩放而改变。就像你用厘米还是用英寸量一条线段,线段长度是客观不变的,变的只是读数。

这两者的关系是:几何间隔 = 函数间隔 / ||w||。理解了这个,就能明白SVM推导中的一个关键操作——我们完全可以自己定一个“刻度”。既然函数间隔可以等比例缩放,那就干脆规定:离超平面最近的样本点的函数间隔等于1。这个操作不改变超平面本身,只是给w和b定了一个统一的尺度标准。这个“归一化”技巧,是整个SVM目标函数形式来源的核心。

你可能要问:为什么偏偏取1,不取别的数?答案很简单:取任何正数都行,取1只是为了后续公式看起来最干净。这就好比你给一段绳子标刻度,你可以标1米,也可以标100厘米,绳子的实际长度不会变。

2. 硬间隔的数学推导:从几何间隔到拉格朗日对偶

2.1 目标函数的由来:为什么要最小化 1/2||w||²

有了“最近样本的函数间隔=1”这个约定,我们来看间隔本身。既然对所有样本都有 y_i(w·x_i + b) ≥ 1,那么最近的那个样本,函数间隔恰好等于1。

这个样本的几何间隔是多少?是 1 / ||w||。因为它到超平面的距离 = 函数间隔 1 除以 ||w||。

SVM的目标是最大化这个几何间隔,也就是最大化 1 / ||w||。这等价于最小化 ||w||。为了避免求导时出现根号带来的麻烦,习惯上写成最小化 1/2 ||w||²。这里的1/2纯粹是为了后面求导消掉系数,没有任何物理含义。

现在约束优化问题就完整了:

minimize (1/2)||w||²
subject to y_i(w·x_i + b) ≥ 1, 对所有的 i

这是一个凸二次规划问题。凸的意思是:目标函数是凸函数,可行域是凸集。凸优化有个极好的性质——局部最优解一定是全局最优解,不存在陷入鞍点这种破事。这是SVM相比神经网络的底层优势之一,数学上保证你找到的解就是全局最好的解。

但不要高兴太早。这是一个带不等式约束的优化问题,直接求导没法解,因为约束条件让最优解可能出现在边界上。我们需要一种工具,把约束条件“吸收”进目标函数里。这个工具就是拉格朗日乘子法。

2.2 拉格朗日乘子法:怎么把约束问题变成无约束问题

拉格朗日乘子法的直觉其实很朴素。想象你被困在一个围栏里,想走最远,但永远不能越过围栏。拉格朗日乘子法干的事,就是给每条围栏设一个“虚拟收费员”:你越靠近围栏,收费员越用力把你往回拽。最后你站的位置,就是围栏约束下你能走到的最远点。

对SVM这个约束优化问题,我们构造拉格朗日函数:

L(w, b, α) = (1/2)||w||² + Σ α_i [1 - y_i(w·x_i + b)]

这里的 α_i ≥ 0 就是拉格朗日乘子,对应第 i 个样本的约束条件。

现在问题变形成:min_w,b max_α L(w, b, α)。理解这个“min max”结构很关键。给定一组w和b,如果某个样本违反了约束 y_i(w·x_i + b) < 1,那么中括号里的值就是正数,α_i 会趋向于把 L 撑到无穷大。这就等于惩罚了违反约束的w和b。反过来,如果所有约束都满足,中括号里的值是非正的,α_i 取0就能让这一项消失,L 就等于 (1/2)||w||²。

所以,max_α L 在数学上等价于原来的约束问题:它“自动”对违反约束的情况施加无穷大惩罚。

但直接解这个 min max 问题还是很麻烦。幸运的是,SVM问题满足强对偶条件,也就是说,可以把 min 和 max 调换顺序,变成 max_α min_w,b L。这看起来只是交换顺序,实际带来的好处是决定性的:内层的 min_w,b 没有约束,可以直接对w和b求导置零;外层的 max 则变成一个相对容易处理的对偶问题。

这里有读者可能会问:为什么可以交换顺序?严格证明涉及Slater条件和凸优化的对偶理论,篇幅有限,我只强调一个直觉:目标函数是凸的,约束是线性的,这种情况下原始问题和对偶问题解的值是一样的。这一点在为SVM推导对偶形式时是默认前提,面试时能说出“因为强对偶成立”就已经合格了。

2.3 从KKT条件看“支持向量”的本质

接下来进入推导的核心计算环节。内层问题是无约束优化,直接对w和b求偏导并令其等于0。

对w求偏导:

∂L/∂w = w - Σ α_i y_i x_i = 0
得到:w = Σ α_i y_i x_i

对b求偏导:

∂L/∂b = -Σ α_i y_i = 0
得到:Σ α_i y_i = 0

这两个式子,第一个尤其重要。它告诉你:最优超平面的法向量 w,竟然是所有样本点的线性组合!组合系数就是 α_i y_i。这意味着超平面并不需要“记住”所有样本,只需要保留那些 α_i 不为0的样本就够了。

把这两个结果代回拉格朗日函数,消掉w和b。这里我们一项一项代:

L = (1/2)||w||² + Σ α_i [1 - y_i(w·x_i + b)]
= (1/2)||w||² + Σ α_i - Σ α_i y_i (w·x_i) - b Σ α_i y_i

注意到最后一项里的 Σ α_i y_i = 0,所以 b 项直接消失。再看中间交叉项,把 w = Σ α_j y_j x_j 代进去:

Σ α_i y_i w·x_i = Σ_i Σ_j α_i α_j y_i y_j x_i·x_j

而 (1/2)||w||² = (1/2) (Σ_i Σ_j α_i α_j y_i y_j x_i·x_j)

所以两者相减正好剩下:(1/2)w·w 那项被减掉一半,最终对偶问题变成:

maximize Σ α_i - (1/2) Σ_i Σ_j α_i α_j y_i y_j x_i·x_j
subject to Σ α_i y_i = 0, α_i ≥ 0

这个形式的意义在于:原问题中要优化的变量是w和b,现在变成了只优化 α。样本在公式里的出现方式,从 x_i 变成了 x_i·x_j —— 全部以点积形式出现。这个特点在后面引入核函数时极其关键,可以说整个核技巧的“伏笔”就埋在这里。

对偶问题怎么解出 α?这需要KKT条件。KKT条件是约束优化问题取得最优解的必要条件,对我们这个场景,最重要的是“互补松弛性”:

α_i [1 - y_i(w·x_i + b)] = 0

这个式子信息量巨大。它说的是:如果 α_i > 0,那么中括号必须是0,也就是 y_i(w·x_i + b) = 1,该样本恰好落在间隔边界上;如果 α_i = 0,除非样本正好在边界上,否则它在间隔内部或外部,对w的构成没有贡献。

这些 α_i > 0 的样本,就是“支持向量”。整个支持向量机,真正参与决定边界的,只有这些少数样本。这就是名字的来历,也是SVM稀疏性的数学根源——支持向量的数量远小于总样本数时,模型既省存储又省推理时间。你看,我们绕了一大圈,从几何直觉到凸优化到对偶,其实都是在为“哪些样本真正起作用”这个问题寻找一个可计算的答案。

3. 软间隔与核函数:让SVM真正能打的两件武器

3.1 软间隔:允许“犯错”的硬间隔

上面推导的硬间隔版本很漂亮,但它有一个致命前提:数据必须线性可分。真实世界里,数据几乎不可能干干净净地分成两类,总有噪声点、离群点、标注错误。如果硬性要求所有点都满足 y_i(w·x_i + b) ≥ 1,最终得到的边界会被个别异常点带着跑,泛化能力反而变得很差。

解决办法是引入软间隔。思想很简单:允许个别样本违反间隔约束,但违反要付出代价。具体做法是给每个样本引入一个松弛变量 ξ_i ≥ 0,约束变成:

y_i(w·x_i + b) ≥ 1 - ξ_i

如果 ξ_i = 0,样本没有问题;如果 0 < ξ_i < 1,样本落在间隔内部但还在正确一侧;如果 ξ_i > 1,样本已经被分错了。

目标函数也要相应修改。不能光想最大化间隔,还要惩罚那些违反约束的样本:

minimize (1/2)||w||² + C Σ ξ_i

这里的C是惩罚系数,用来调节“间隔要大”和“错误要少”这两个目标之间的权重。C大,说明你无法容忍错误,边界会收窄去迎合异常点,容易过拟合;C小,说明你允许一定错误,换取更宽、更平滑的间隔,泛化通常更好,但太小了会欠拟合。C这个参数的调节,堪称SVM实战中最重要的超参之一。

软间隔的对偶推导和硬间隔几乎一样,只需要重新构造拉格朗日函数,对ξ求导得到 C - α_i - μ_i = 0,结合 μ_i ≥ 0,最终结论是 α_i 的取值范围从原来的 α_i ≥ 0 变成:

0 ≤ α_i ≤ C

这个上限约束是软间隔和硬间隔在对偶形式上最直观的区别。它也改变了支持向量的构成:α_i = C 的样本通常是那些被允许犯错或已经犯错的点,它们对边界的影响被限制住了,不会像硬间隔那样被异常点彻底绑架。

工程经验上,我见过很多人调C时喜欢从0.01、0.1、1、10、100这样按数量级扫。实际上C对结果的影响曲线往往不是单调的,建议配合交叉验证去选,而不是凭一次实验拍脑袋定。

3.2 核函数:把数据搬到高维去“切一刀”

软间隔解决的是“有点噪声”的情况,但有些数据在原始空间里就是线性不可分的,比如经典的异或问题:四个点分布在坐标轴四个象限,正类在对角方向。你在二维平面上怎么画直线都分不开。

这时一个天才般的想法出现了:既然低维分不开,那就把数据映射到高维去。在低维是曲线、曲面的边界,在高维可能就是一个平面。就像一张纸上的圈和叉从圆心向外分布着,你用圆规能画圆分开,但要画直线就不行。可如果你把纸上的坐标加一个维度,比如把每个点的“到圆心距离”算出来当成第三维,那圈的点第三维小、叉的点第三维大,一个平面就能分开了。

SVM处理这个过程的高明之处在于:它不需要真的做高维映射,只需要知道映射后样本间的点积。对偶问题的所有 x_i·x_j 位置,替换成核函数K(x_i, x_j),一切照常计算。这就是核技巧(kernel trick)。

核函数的本质,是一个隐式的“点积计算器”:它直接在原空间算两个样本映射到高维后的点积,但不用真的把每个样本投影过去。比如多项式核 K(x, z) = (x·z + r)^d,展开后相当于一个高维空间里的点积,但你不需要写出那个高维向量长什么样。省下的计算量是惊人的,甚至可以说,没有核技巧,SVM的很多应用都无法落地。

常用的核函数有几种,各有脾气:

  • 线性核:K(x, z) = x·z。相当于不映射,适合特征维度很高、样本量很大的情况,比如文本分类。
  • 多项式核:K(x, z) = (γ x·z + r)^d。能拟合一些非线性边界,但阶数太大会过拟合,参数也敏感。
  • RBF核(高斯核):K(x, z) = exp(-γ ||x - z||²)。最常用,能映射到无限维,γ控制单个样本的影响半径,γ越大边界越复杂。
  • Sigmoid核:K(x, z) = tanh(γ x·z + r)。某些情况下近似神经网络,但不如RBF稳定。

什么样函数能当核函数?结论是:对任意样本集,核矩阵 K(x_i, x_j) 必须是半正定的。这是Mercer定理的核心内容。实操中你基本不会自己去发明核函数,但理解这个条件能帮你判断那些“看起来像相似度函数”的东西能不能用。

在我做过的项目里,一个比较实用的经验是:一开始无脑试RBF,因为它能处理绝大多数非线性问题;如果特征维度特别高(比如上万维),线性核更快且往往足够好;如果数据有明显的结构化信息,比如图像、序列,RBF和平滑核效果通常更好。核函数选择没有绝对最优,交叉验证是唯一可靠的裁判。

4. SMO算法与工程实现:公式落地成代码的关键

4.1 SMO的核心思想:一次动两个变量

对偶问题写出来了,但怎么解?如果把所有 α 当整体优化,这是一个带约束的二次规划问题,理论上可以用通用的QP求解器。但实际问题中样本量动不动上万,通用求解器需要存储和操作大规模的核矩阵,内存和时间都吃不消。

SMO算法(Sequential Minimal Optimization)就是为破解这个瓶颈而生的。它的思想核心只有一句话:既然约束 Σα_i y_i = 0 存在,那就每次只优化两个变量,并把其他变量固定住。

为什么必须是两个,不能是一个?因为如果你只动一个 α,为了保持 Σ α_i y_i = 0 不变,另一个 α j 也要跟着变。所以SMO每次选中一对 (α_i, α_j),把优化问题化简成关于这两个变量的二次函数,直接求解析解,再考虑取值范围 [0, C] 的边界约束,把解“裁剪”到合法区间。一轮迭代只更新两个α,然后继续选下一对。

变量选择也不是随便选的。SMO会优先选那些违反KKT条件最严重的样本,以及能让目标函数下降幅度最大的配对。这个启发式规则是整个算法速度的关键。选得好,收敛快;选得差,就像用脚踩油门但手刹没松,半天跑不动。

SMO的伟大之处在于,它让SVM不再依赖通用优化库,纯Python实现也能在普通配置上跑几万样本。今天你调用的各种SVM库,内部基本都是SMO的改进版本。我自己曾在一次学习中尝试手写了一个简化版SMO,50行不到就能在一个小数据集上复现出和调库接近的分离效果。虽然效率远不及库实现,但亲手写一遍之后,对“支持向量到底是怎么被挑出来的”会有一种完全不同的通透感。强烈建议进阶者都试一次。

4.2 实际代码里需要处理的三件事

推导归推导,工程落地时还有几个绕不开的细节。这些细节不注意,你会发现同样的数据、同样的SVM,效果天差地别。

第一件事是特征标准化。SVM本身不内置特征缩放,而RBF核依赖样本间的欧氏距离。如果一个特征是0到1,另一个特征是0到1000,距离计算基本就被第二个特征主导了,第一个特征等于白给。我在实际项目中见过最典型的例子就是:不归一化,准确率只有70%,用StandardScaler归一到均值0方差1后,直接跳到90%。这个操作成本几乎为零,收益却巨大。任何核方法(包括KNN)都吃这个亏。

第二件事是多分类策略。SVM本质是二分类器,处理多分类时通常用两种策略:一对多(one-vs-rest)和一对一(one-vs-one)。一对多训练k个分类器,每个分类器区分“第i类”和“其他所有类”;一对一训练C(k,2)个分类器,每个区分两个类,最后投票决定。两种策略各有代价,一对一的训练开销大但每个子问题简单,实践中很多库默认用一对一。

第三件事是概率输出。SVM直接给出的是决策函数值,不是概率。如果你需要概率(比如下游要做风险评分),通常需要额外校准,最常见的是Platt缩放,把决策值拟合到一个逻辑函数上。注意校准会增加一个交叉验证环节,小数据集上谨慎使用。

下面给一个实际工程中常用的搭建流程示例,这类模板我在项目里反复用:

import numpy as np from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline from sklearn.model_selection import GridSearchCV, train_test_split from sklearn.datasets import load_digits # 用经典手写数字集做演示,实际项目替换成你的数据即可 X, y = load_digits(return_X_y=True) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y ) # 关键:先标准化再SVM,用pipeline封装保证流程一致 model = make_pipeline( StandardScaler(), SVC(kernel='rbf', class_weight='balanced', random_state=42) ) # 参数范围按对数空间扫,RBF核主要就是C和gamma param_grid = { 'svc__C': [0.1, 1, 10, 100], 'svc__gamma': [0.001, 0.01, 0.1, 1] } grid = GridSearchCV(model, param_grid, cv=5, scoring='f1_macro') grid.fit(X_train, y_train) print("best params:", grid.best_params_) print("test acc:", grid.score(X_test, y_test))

看到没有,整套流程其实就三个要点:标准化、网格搜索、评估指标选择。f1_macro适合类别不平衡的场景,如果你的数据是均衡的,用accuracy也没问题。跑出来的结果在这个演示集上通常能到98%以上,新手用这个模板做基线非常可靠。

5. 参数调节与常见排查:踩坑实录和经验清单

5.1 三个最影响结果的参数

使用RBF核时,三个参数决定模型性格:C、gamma、核函数类型(以及多项式核的degree这些次要项)。

C我们已经说过,是错误惩罚系数,它跟模型复杂度正相关。gamma是RBF核自带的参数,表达式里 K(x, z) = exp(-γ ||x - z||²)。gamma取大,核函数衰减快,每个样本只影响很小范围,边界会变得曲折,容易过拟合;gamma取小,衰减平缓,边界趋向光滑,容易欠拟合。一句话:gamma控制模型的“柔性”,C控制“对错误的容忍度”。

这两个参数是相互影响的,不应该单独调。比如C很大时模型已经倾向于拟合每个点,如果gamma也很大,基本就是过拟合重灾区;C很小+gamma很大,则可能出现一堆支持向量但边界毫无条理。所以调参必须组合着扫,而且要用交叉验证。把C和gamma都按10的幂次取点,比如C从0.01到1000,gamma从0.001到10,组成一个网格,跑网格搜索。数据集稍大时这个网格可能有点贵,也可以先粗扫定位最优区域,再精细扫一遍。

5.2 实战中容易踩的坑

第一个坑,就是不用标准化直接上RBF。这个前面强调了,再多说一句:SVM和基于距离的模型一样,对特征尺度极度敏感。哪怕你的数据已经有量纲,只要不同特征数值范围差异大,就必须做标准化或归一化。我在好几个项目里都犯过“忘了缩放”的错,特征范围跨越几个量级,结果SVM几乎退化成只看最大那个特征。这个坑的坑底还垫着血泪,记住就好。

第二个坑,是类别不平衡时直接调参。如果正负样本比例悬殊,SVM默认的决策边界会偏向多数类,少数类召回率惨不忍睹。对策有两个:一是给少数类更大的惩罚,很多库里的class_weight='balanced'选项就是干这个的,它会按类别频率自动调整样本权重;二是在训练后手动调整决策阈值,不一定要用默认的0作为决策边界。对SVM来说,决策函数值是 w·x + b,你可以扫描这个值的不同阈值,选出在验证集上最合理的操作点。这个技巧很多做风控、异常检测的朋友都用得上。

第三个坑,是大规模数据上硬上RBF+SMO。SVM虽然比通用二次规划快很多,但核矩阵的计算仍然是O(n²)级别的。当样本量超过几万,训练时间会变得很难受。我见过的经验法则是:样本量大或者特征维度超高时,先考虑线性SVM;线性不行再往核SVM走。深度学习兴起后,很多人说SVM过时了,其实在中小规模数据、特征维度高的场景,线性SVM依然是一个极好的基线,训练快、可解释性也不差。

第四个坑,是支持向量占比过高。这个现象大家一开始不太会看:模型训练完,数一数支持向量的个数,如果它占总样本的比例非常高(有些场景甚至达到80%以上),说明边界被大量样本共同抬着走,间隔已经失去“稀疏”意义了。这种情况通常是gamma过大或者C过大的信号,边界毛刺太多,泛化基本靠不住。反过来,支持向量太少(比如只有几个)也可能意味着模型太简单。这个比例是一个很好的诊断信号,我强烈建议每次训完都输出看一眼。

5.3 常见问题速查表

把上面提到的经验整理成一个表格,方便你踩坑时快速对照:

数据现象推荐做法参数思路
高维稀疏数据(文本、图像特征)优先线性核C小到中间值,关注正则化
样本量大(数万以上)先试线性SVM不必强上RBF,省时间
类别严重不平衡class_weight调整或调整决策阈值
特征尺度差异大必须标准化否则RBF完全失效
线性不可分但数据量中等RBF核C和gamma组合网格搜索
支持向量占比过高调小C或gamma边界简化,减少毛刺
需要概率输出加概率校准校准会增加交叉验证开销

这个表不是金科玉律,但它覆盖了我这些年里遇到的大部分SVM实战问题。

6. 写在最后:一点个人体会

SVM是个很有意思的模型。它从几何直觉出发,一步步走到对偶理论、核技巧、序列最小优化,每一步都有扎实的数学支撑。和现在动不动就是十几层神经网络比起来,SVM的推导链条其实更适合作为理解机器学习理论框架的教材。哪怕你日常工作主要用的不是SVM,把它的推导吃透,对你理解正则化、对偶方法、样本重要性这些通用概念都有帮助。

我个人在项目里用SVM的经验是:当数据量在几千到几万、维度不过分爆炸、又不确定用多复杂的模型时,先跑一个标准化+RBF的SVM基线,再跟其他模型对比。它的训练时间可控,调参路径清晰,结果也稳定。调好的SVM往往能和精心调的神经网络打得有来有回,尤其在数据量不太大的时候,SVM的泛化优势非常明显。

最后再分享一个学习上的建议:不要只看推导,一定要动手实现。哪怕是照着一份现成的SMO代码逐行敲一遍,你对KKT条件、对偶变量、支持向量这些概念的理解都会深刻很多。数学公式看十遍不如自己推一遍,推一遍不如跑一遍。SVM的公式推导之所以让人头疼,不是因为难,而是因为信息密度太高、前后因果太长。但一旦你顺着主线走通一遍,那种通透感会让你在之后看任何模型时,都有一种“我知道它在干什么”的底气。

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

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

立即咨询