A18

反向传播

点击正文段落选定收藏或划线位置;未选定时使用屏幕内第一段。

课内目录

1 反向传播要回答的问题

假设你正在用公开的 UCI Bank Marketing 数据集训练分类器,预测客户是否会订阅定期存款。模型接收年龄、职业、账户状态、过去联系次数等特征,输出订阅概率。某位客户的真实标签是 1,模型却给出 0.2。损失函数可以告诉我们这次预测有多差,但训练还需要回答更具体的问题:哪个参数应该增大,哪个参数应该减小,各自改变多少才有希望降低损失?反向传播负责高效计算这些参数对损失的影响。

这里的“影响”有严格的局部含义。保持其他条件不变,把参数稍微增大,损失的变化率就是对应的偏导数。把所有参数的偏导数组合起来,就得到梯度。正梯度表示参数沿增大方向微调时,损失倾向于上升;负梯度表示损失倾向于下降。这是当前位置附近的判断,不能保证沿某个方向移动很远后仍然成立,更不能直接保证测试集表现提高。

训练需要区分 3 件事。前向计算用当前参数产生预测和损失;反向传播根据这次计算得到梯度;优化器依据梯度和学习率更新参数。常见的梯度下降写成 θ新 = θ旧 − η∇θL,其中 η 是学习率。反向传播本身不选择学习率,也不决定更新规则。把这几件事分清,才能解释“有梯度但参数没有变化”以及“损失下降但泛化变差”等现象。

从预测误差到参数更新的训练闭环
从预测误差到参数更新的训练闭环

在这个银行营销例子里,训练之前必须确定预测发生的时刻。数据中的通话时长 duration 只有通话结束后才知道,如果任务是在联系客户之前做预测,就应排除它。否则模型可能得到很高的验证 AUC,却无法在实际决策时使用。反向传播会忠实地降低所给数据上的损失,它不会识别这种时间上的信息泄漏。

可以按时间先后把较早的 70% 样本用于训练,接着的 15% 用于验证,最后的 15% 留作测试,并记录每段的正类比例。比例是本课的实验约定,不是适用于所有数据的标准答案。标准化器和类别编码表只在训练集上拟合。验证集用于选择学习率等设置,测试集留到方案确定后评估。后面看到梯度时,始终记住:它来自训练目标,模型好不好还需要独立数据上的证据。

2 计算图与链式法则直觉

计算图把复杂函数拆成可追踪的简单操作。例如标量模型先算 z = wx + b,再算 p = σ(z),最后比较预测 p 与标签 y 得到损失 L。图中的节点表示数值或操作,边表示依赖关系。输入 x 改变会影响 z,z 改变会影响 p,p 改变又会影响 L。沿着这条依赖链,就能把局部变化率组合成最终变化率。

链式法则可以理解成不同刻度之间的换算。若中间量 z 增加 1 个很小的单位,使损失近似增加 0.4 个单位,而参数 w 增加 1 个很小的单位,使 z 近似增加 3 个单位,那么损失对 w 的变化率就是 0.4 × 3 = 1.2。这里的“很小”很关键:导数描述局部线性近似,实际有限幅度的改动还会受到曲线形状影响。

对 z = wx + b,局部导数分别是 ∂z/∂w = x、∂z/∂x = w、∂z/∂b = 1。反向到达这个节点时,假如已经知道 g = ∂L/∂z,就能得到 ∂L/∂w = gx 和 ∂L/∂b = g。每个操作只需要知道自己的局部规则,再接收后面传来的梯度,不必重新展开整个网络的长公式。这种组合性使大型网络也能使用同样的思路。

链式法则在串联和分支中的传播
链式法则在串联和分支中的传播

当某个参数被多条路径使用时,来自各条路径的贡献要相加。例如 a = wx、b = wx²、L = a + b,在 x 固定时,∂L/∂w = x + x²。不能只保留最后处理的分支。可以把每条分支想成在回答同一个问题:“通过我这条依赖,w 的微小变化会让 L 改变多少?”总影响需要汇总所有回答。共享参数的网络同样遵守这个原则。

图还说明了为什么前向时的一些中间值需要保存。求乘法的局部导数需要当时的另一个输入,求激活函数的导数可能需要当时的输入或输出。反向使用的是产生当前损失的那次前向结果。如果中间先改了参数,随后却把旧的中间值和新的参数混在一起,得到的就不是同一个函数在同一个位置的梯度。

读图时可以按 3 个问题检查理解:损失依赖哪些节点,每个节点的局部导数是什么,同一个节点是否接收了多路贡献。图上的反向箭头表示依赖关系上的求导顺序,并不表示数据标签被“倒着输入”模型。前向决定当前计算了什么函数,反向则对这次实际计算求导。条件分支、取整等操作如何影响求导,会在后面的排错中出现。

3 梯度从损失流回参数

反向传播通常从标量损失开始,设置 ∂L/∂L = 1,然后按照计算图的逆拓扑顺序处理节点。也就是先等使用某个结果的后续操作贡献完梯度,再向这个结果的输入传播。一个节点既可能接收多路梯度,也可能把梯度分给多个输入。核心工作始终是把上游梯度乘以局部导数,并在需要的地方累加。

以二分类为例,模型先输出未经概率变换的分数 z,通常称为 logit;随后通过 sigmoid 得到 p = 1/(1 + exp(−z))。单样本交叉熵是 L = −y log p − (1−y) log(1−p)。把这几步连起来求导,可以得到简洁结果 ∂L/∂z = p−y。真实标签为 1 且预测偏低时,这个值为负,梯度下降会倾向于提高相关分数。真实标签为 0 且预测偏高时,方向则相反。

这个简式对应 sigmoid 与二分类交叉熵的组合,不能直接套在任意输出和任意损失上。实际框架通常提供直接接收 logits 的稳定损失实现,内部避免先形成极接近 0 或 1 的概率再取对数。理解数学上的分解有助于读图,工程实现则应避免为了复现公式而引入不必要的溢出和对数零值。

对于包含 B 个样本的小批量,如果损失定义为平均值 L = ΣLi/B,那么每个样本贡献的梯度也带有 1/B。若误用求和,梯度规模会随着批大小变化。同样的学习率可能突然导致更新过大。因此,比较不同实验的梯度范数之前,先确认损失的归约方式、批大小和类别权重一致。仅凭某个范数大于某个固定数字,无法判断模型是否异常。

神经网络中的权重往往是矩阵。反向计算得到与参数相同形状的梯度:每个位置说明对应参数的局部影响。广播操作也必须在反向时还原,例如偏置被加到多个样本上,它的梯度需要沿样本维度汇总。形状正确是必要条件,但还不充分;错误地沿特征维度求和,也可能得到能够广播回去的张量,却改变了数学含义。

参数梯度还可能在多次反向调用之间累积。多数训练循环需要在处理新的小批量之前清零,再执行前向、计算损失、反向和更新。如果有意用多个小批量模拟更大的批量,就必须同步处理损失缩放与更新频率。入门阶段先固定每批清零的简单约定,确认单步结果可靠,再学习梯度累积等技巧,能减少多个机制相互遮蔽的问题。

4 动手:手推两层网络的一步更新

现在用 1 个隐藏单元的网络,把前面的规则落实到数字。令输入 x = 2、标签 y = 1,隐藏层参数 w₁ = 0.5、b₁ = 0,输出层参数 w₂ = −1、b₂ = 0。网络按 z₁ = w₁x + b₁、h = ReLU(z₁)、z₂ = w₂h + b₂、p = sigmoid(z₂) 计算。这里的“两层”指 2 层带可训练参数的变换,输入本身不算参数层。

ReLU 在输入大于 0 时原样输出,小于 0 时输出 0。本例 z₁ = 1,避开了它在 0 处的折点。前向得到 h = 1、z₂ = −1、p ≈ 0.26894。使用二分类交叉熵,因 y = 1,损失 L = −log p ≈ 1.31326。当前预测明显低于真实标签,但还不能据此直接决定所有权重都应该增大;还要看中间连接的符号。

先在输出端求导,得到 g₂ = ∂L/∂z₂ = p−y ≈ −0.73106。于是 ∂L/∂w₂ = g₂h ≈ −0.73106,∂L/∂b₂ = g₂ ≈ −0.73106。继续向隐藏层传播,∂L/∂h = g₂w₂ ≈ 0.73106。因为 z₁ > 0,ReLU 的局部导数为 1,所以 ∂L/∂z₁ ≈ 0.73106。

输入层参数的梯度接着得到:∂L/∂w₁ = 0.73106 × 2 ≈ 1.46212,∂L/∂b₁ ≈ 0.73106。注意 w₁ 的梯度为正,而 w₂ 的梯度为负。原因是当前输出连接 w₂ 为负,提高隐藏激活反而会压低输出分数。因此,梯度的符号来自整个依赖链,不能把“预测太低”简化成“每层权重都增大”。

两层网络的前向数值、梯度和同步更新
两层网络的前向数值、梯度和同步更新

取学习率 η = 0.1,对全部参数使用这次前向得到的梯度做同步更新,得到 w₁新 ≈ 0.35379、b₁新 ≈ −0.07311、w₂新 ≈ −0.92689、b₂新 ≈ 0.07311。“同步”表示所有新参数都由旧参数及其梯度计算,不是在算完输出层后立刻改变 w₂,再用改变后的值向隐藏层求导。

用新参数重新前向,隐藏激活约为 0.63447,输出分数约为 −0.51499,预测概率约为 0.3738,损失约为 0.9834。和更新前相比,损失下降了。这个结果验证了本例的方向和步长能够改善当前样本,但还没有证明模型学会了分类,也没有证明另一批样本上的损失必然下降。

手推练习的价值是建立可对照的参照点。实现时可依次比较 z₁、h、z₂、L,再比较 4 个参数梯度,最后比较更新后结果。只看最终损失,会让前向错误和反向错误相互抵消。为了保持推导透明,本例没有加入正则化、随机失活或复杂优化器;这些机制各有作用,适合在基本训练循环稳定后逐项加入。

5 梯度消失与爆炸的早期信号

链式法则沿多层相乘,因此局部变化率会影响远处梯度的大小。若某条路径连续经过 8 个约为 0.1 的局部因子,连乘结果约为 10⁻⁸;若连续经过 8 个约为 3 的因子,结果约为 6561。这是帮助理解的标量示意。真实网络还包含矩阵、分支和求和,不能把每层梯度都机械地视为同一个常数连乘。

sigmoid 在输入绝对值很大时进入饱和区,输出靠近 0 或 1,局部导数 p(1−p) 很小。深处连续出现这类因子时,靠近输入的参数可能几乎收不到训练信号。ReLU 在正区间导数为 1,可以缓解部分问题;但如果某个单元对所有训练输入都落在负区间,它的相关梯度也可能长期为 0。替换激活函数并不自动保证梯度健康。

激活区间与早期层训练信号的关系
激活区间与早期层训练信号的关系

梯度消失的早期信号包括:输出层仍有明显梯度,较早层的梯度却连续多个批次接近 0;早期层权重变化极小;损失长时间停滞。单次出现 0 梯度不足以下结论,样本恰好进入 ReLU 负区间、某个特征在该批次全部为 0,都可能产生合理的零值。应结合时间趋势和激活分布观察,而不是把所有零梯度都当成程序错误。

梯度爆炸可能表现为某层范数突然上升、更新量远超平时、损失剧烈波动,最终出现 Inf 或 NaN。梯度过大和学习率过大需要区分:前者描述损失在当前位置的敏感程度,后者描述一次沿梯度移动多远。即使原始梯度规模正常,过大的学习率也可能先把参数推入不稳定区域,再使后续梯度异常。

排查可以从输入尺度、初始化、损失归约方式和学习率开始。数值特征应使用训练集统计量进行适当缩放,避免某个特征因为单位较大而主导变化。使用与激活函数相配的常见初始化方案,先在较小网络上建立基线。梯度裁剪可以限制更新前的梯度范数,但它不能修复错误的损失公式或泄漏的数据划分。

建议同时记录每层梯度范数、参数范数以及更新范数与参数范数之比,并对接近 0 的参数范数加稳定分母。比值便于发现“参数尺度很小而更新很大”的情况,但也没有跨模型通用的健康阈值。把异常与同一实验的正常区间比较,再结合损失、激活和验证指标判断,证据会比单独画一条全模型梯度曲线更完整。

6 常见坑:数值差当解析梯度、计算图断开

解析梯度来自求导规则,自动微分通过记录操作并组合局部导数得到它。数值差分则通过实际扰动参数估算导数。例如中心差分 g数值 = [L(θ+ε)−L(θ−ε)]/(2ε)。它适合在很小的问题上检查实现,却不适合替代训练中的反向传播。若模型有 P 个参数,仅检查全部参数就大约需要 2P 次损失计算,而反向传播可以共享中间结果。

差分并非天然正确。ε 太大时,两端相隔太远,局部近似误差明显;ε 太小时,浮点舍入和相近数相减会让差值不可靠。若扰动跨过 ReLU 的折点,函数在两侧使用不同分段,差分与框架选用的局部导数也可能不一致。因此,应在小型、确定性的计算上使用较高精度,尝试几个 ε,并避开不可微点附近。

计算图断开是另一类问题。把中间张量转成普通数值,再重新创建张量参与损失计算,通常只复制了数值,没有复制原来的依赖关系。显式分离张量、在禁止记录梯度的环境中计算训练输出,或把损失转成数值后再重建,也可能产生相同结果。最终损失看起来合理,前面的参数却收不到梯度。

硬阈值也容易被误用。例如先把预测概率大于 0.5 的结果变成 0 或 1,再据此计算准确率作为训练损失。阈值输出在绝大部分位置不随小扰动变化,无法提供合适的梯度。训练时应对连续分数使用可求导的代理损失,验证时再根据任务选阈值,报告 precision、recall 和 F1。模型可以通过交叉熵训练,同时用这些指标评价,两者承担的职责不同。

还要区别“梯度不存在”和“梯度数值为 0”。某个参数未参与损失、未开启梯度记录,可能没有梯度;参与了图但当前局部导数为 0,则可能得到全零张量。具体框架对未使用参数的呈现方式有所不同,不能仅凭界面显示下结论。沿损失向前追踪依赖,再检查每个中间量是否由可追踪操作产生,通常比盲目调整学习率有效。

按异常类型选择最小排查路径
按异常类型选择最小排查路径

训练循环也会制造相似症状。忘记调用更新会让梯度存在而参数不变;每批反向后立即清零,再调用更新,会让刚算出的梯度失效;忘记在下一批前清零,则会让多批梯度意外叠加。先保存某个参数更新前后的值,再检查梯度和调用顺序,能把“求导错误”和“更新没有执行”分开。入门阶段应让这条最短路径可观察,再增加其他训练功能。

7 案例:梯度检查抓住实现错误

现在换到公开的 Breast Cancer Wisconsin Diagnostic 数据集,用半径、纹理、周长等连续特征预测肿瘤类别。它适合演示小型表格模型,但本课实验不能作为临床诊断依据。把恶性样本编码为正类,在固定随机种子下做分层的 70%/15%/15% 训练、验证、测试划分,标准化只拟合训练部分,保留每部分实际样本数与类别比例。

实验目标是检查手写损失的反向实现。为了能够定位错误,先只取训练集的 4 个样本、3 个特征,使用小型网络和 float64。关闭随机失活等随机机制,选择远离 ReLU 折点的参数。检查期间既不更新参数,也不重新采样小批量,让解析梯度和所有差分损失都对应完全相同的函数。完整数据上的最终指标暂时不参与这项检查。

假设前向损失写成 4 个样本的交叉熵平均值,但手写反向时忘记除以 4。对某个待查参数,中心差分可能得到 0.075,手写结果却是 0.300。这里的数字是用于解释错误模式的示意值,并非数据集上的实测结果。如果多个非零梯度坐标都接近这样的 4 倍关系,批量平均的缩放就比随机浮点误差更值得优先检查。

比较时可使用相对误差 r = |g解析−g数值| / max(10⁻⁸, |g解析|+|g数值|)。在上述示意坐标上,r = 0.225/0.375 = 0.6,差异很明显。对于接近 0 的梯度,还要看绝对误差,否则相对误差容易夸大小数值差异。可以预先约定平滑、双精度小实验中的参考门槛,例如相对误差小于 10⁻⁵ 或绝对误差小于 10⁻⁷,并记录具体条件。

固定小实验中的梯度检查和修复闭环
固定小实验中的梯度检查和修复闭环

中心差分可先尝试 ε = 10⁻⁴、10⁻⁵、10⁻⁶。若结果在这些尺度附近稳定,而解析结果始终大约是它的 4 倍,就回到损失归约位置补上平均因子。修复后,应重新比较多个坐标,包括每层的权重和偏置,而不是只挑原来出错的坐标。也应增加共享参数或多分支的小例子,以检查反向时是否正确累加贡献。

检查通过之后,再确认沿负梯度做很小的更新是否降低同一批数据的损失。这是另一个有用的信号,但不能替代逐坐标比较:部分梯度错误时,整体方向仍可能碰巧下降。随后才恢复正常训练,在验证集上比较 PR 曲线、F1 和恶性样本的召回率;若误漏正类的代价更高,应明确选择阈值的依据,并同时报告精确率的变化。

这个案例的结论是“手写求导是否匹配已实现的前向函数”。如果前向标签弄反、数据提前泄漏或损失选错,差分仍可能与解析梯度高度一致。梯度检查证明的是局部数学一致性。它需要与数据审查、训练曲线和独立评估配合,才能支持对整个模型的判断。

8 把梯度健康检查写进训练清单

训练清单应把观察对象和后续动作连在一起。开始训练之前,先记录数据版本、划分方式、标签定义、特征可用时点和预处理拟合范围。对银行营销数据,应特别标明是否排除了通话时长;对乳腺癌数据,应写清哪个标签代表正类。若训练输入已经包含答案的线索,再漂亮的梯度曲线也不能证明模型具备实际预测能力。

第 1 个小批量适合执行完整的可观察性检查:输入和标签形状是否匹配,预测与损失是否有限,预期可训练的参数是否接收梯度,梯度是否有限,更新之后参数是否发生预期变化。保存少量必要统计即可,不需要把所有样本和张量都打印出来。全量日志既难比较,也可能包含不适合暴露的个人信息。

数据、梯度、更新与泛化的联合观察面板
数据、梯度、更新与泛化的联合观察面板

训练过程中,按约定间隔记录训练损失、验证损失、分层梯度范数和更新比例。若训练损失下降而验证损失持续上升,应优先考虑过拟合、划分不匹配等问题;若两者都停滞,同时早期层梯度长期接近 0,再检查激活饱和和图连接。若损失突然变为 NaN,定位首次异常批次并保存必要复现信息,随后检查输入、损失和梯度的首次非有限值。

类别不平衡时,准确率可能掩盖失败。例如正类仅占 10%,总预测负类也能得到 90% 准确率,却没有任何正类召回。应报告混淆矩阵、precision、recall 和 F1,并用 ROC AUC 观察排序能力;正类稀少时补充 PR AUC 更有帮助。不同指标回答不同问题,不能因为训练交叉熵降低,就断言每个阈值下的 F1 都会提高。

阈值选择应使用验证集,并说明目标,例如在召回率达到预先要求的条件下尽量提高精确率。若采用类别权重或重采样,记录其设置,因为它们会改变训练中不同样本的梯度贡献。比较梯度规模时也要考虑这些变化。不要一边改变权重、批大小和学习率,一边把曲线差异全部归因于网络深度。

分布漂移同样需要单独观察。银行客户的联系渠道、职业比例或活动时间改变后,测试表现可能下降,即使训练梯度始终稳定。图像分类可能遭遇光照和拍摄设备变化,短文本分类可能遭遇新词和话题变化。应按有意义的时间段或子群体检查表现。出现这类退化时,重新评估样本代表性往往比继续减小学习率更有价值。

最后,为每类异常指定可复现的最小动作:梯度缺失时检查依赖链,稳定倍数偏差时检查求和与平均,梯度尖峰时回看异常批次,泛化下降时检查数据划分与分布。日志保留实验设置和关键结果,使同伴能够重跑。这样,训练清单才是把症状连到证据的工具,而不是只有勾选框的形式记录。

9 从原理走向框架实践(A19)

进入 PyTorch 之前,应把本课的每个对象对应到框架中的职责。张量保存输入、中间结果和参数;前向操作构建计算依赖;损失把当前批次的预测质量汇总成标量;自动微分计算梯度;优化器读取梯度并更新参数。框架替我们执行大量局部求导,但它不会替我们决定预测时刻、数据划分、标签含义或业务指标。

在 A19 中,可以先复现第 4 节的单样本网络,把参数设置为相同数值,执行清零、前向、损失计算、反向和更新。先逐项对齐中间值与参数梯度,再换成批量输入。若一开始就使用复杂数据管线和大模型,出现不一致时,很难判断是张量形状、预处理、求导还是优化步骤出了问题。小例子是定位工具,也可以留作以后修改实现的参照。

框架提供的训练模式与评估模式也需要和梯度记录分开理解。某些层在训练和评估时有不同计算行为,而是否记录梯度是另一个开关。验证阶段通常既要使用评估行为,也要关闭不必要的梯度记录。不要以为切到评估模式就自动消除了所有梯度,也不要把训练前向放在禁止记录梯度的环境里,再期待反向能够补回依赖。

进一步做 MNIST 图像分类时,可以把像素作为输入,输出 10 个类别的 logits,使用多分类交叉熵,并保留官方测试集做最终评估。此时不能继续套用二分类的 sigmoid 公式。自动微分的链式法则没有改变,但输出与损失的对应关系改变了。比较每类召回率还能发现某些数字对容易混淆,这比只看总体准确率更有解释力。

做公开短文本分类时,同样要防止重复文本跨越训练和验证边界,并只在训练文本上建立词表等统计。即使网络反向完全正确,近乎相同的句子同时出现在两侧也会让评估过于乐观。把这些习惯带入框架实践,才能理解为什么“代码能运行、损失会下降”只是实验开始具备可用性的信号。

本课暂不展开自动微分引擎的内部调度、高阶导数、分布式梯度同步或混合精度。接下来的优先目标是读懂张量形状、建立可靠训练循环、观察梯度,并在独立验证集上评价模型。遇到问题时,回到计算图、局部导数、分支求和和更新顺序这几个基础检查点,通常就能缩小范围,再决定是否需要更深入的工具。

放进方案里

在银行营销分类方案中,把反向传播写成训练过程的可检查环节。任务定义为“联系客户之前预测订阅概率”,正类为订阅,排除通话时长等预测时不可获得的字段。采用按时间排序的 70%/15%/15% 训练、验证、测试划分,记录各段日期、样本数和正类比例;标准化与编码只拟合训练集,测试集在模型与阈值确定后使用。

基线使用小型前馈网络和直接接收 logits 的二分类交叉熵,明确损失取批量平均。训练循环约定每批先清零,再前向、反向和更新,记录批大小、学习率、初始化与随机种子。在正式训练前,用固定小批量检查损失、梯度和更新是否有限,并确认预期参数确实改变;若有手写可微模块,先在双精度小例子上做中心差分检查,记录扰动尺度和误差。

验收手写模块时,抽查每层权重和偏置坐标,在远离不可微点的双精度小例子中,要求相对误差小于 10⁻⁵ 或绝对误差小于 10⁻⁷,并保留解析梯度与差分值对照。此标准只验证局部求导一致性,不作为泛化达标依据。

观察面板同时呈现训练与验证损失、各层梯度范数和更新比例。连续异常时保存可复现的批次信息,依次核对输入尺度、图连接、损失归约和学习率。梯度裁剪只能作为明确记录的控制措施,不能代替根因定位。调整设置时尽量控制其他变量,使每次比较能够解释变化来源。

评估报告包含 precision、recall、F1、ROC AUC 与 PR AUC,并列出混淆矩阵。阈值由验证集按照事先约定的召回目标选择,测试集只报告最终结果,同时按时间和联系渠道检查退化。交付材料保留数据版本、预处理、模型配置、训练日志与已知限制,让复核者既能判断求导和更新是否可靠,也能判断效果是否适用于真实预测时刻。

10 小结:下一步 PyTorch 入门(A19)

反向传播沿计算图把损失对中间量的变化率逐层传回参数,串联路径使用链式法则,分支贡献需要相加。优化器再使用这些梯度更新参数。梯度检查帮助确认求导实现,独立评估帮助判断模型能否泛化,它们各自提供不同证据。

进入 A19 前,尝试回答 3 个问题:为什么第 4 节的 w₁ 和 w₂ 更新方向不同?为什么把批量平均误写成求和会改变梯度规模?为什么计算图完整且损失下降,仍可能在测试时失败?能够结合负连接、归约方式和数据泄漏解释这些现象,就具备了阅读 PyTorch 训练循环的基础。

同阶段相关课

AI 答疑

默认关闭。启用后,只发送本课标题、最近阅读的屏幕内正文摘录(最多 6000 字)与本次问题。

配置自己的服务