1. 项目概述:一维优化利器 fminbnd
在科学计算、工程优化和数据分析的日常工作中,我们常常会遇到一个看似简单却至关重要的问题:如何在一个给定的区间内,快速、准确地找到一个单变量函数的最小值点?无论是为了拟合曲线、校准模型参数,还是寻找系统的最优工作点,这个“寻优”过程都是绕不开的核心环节。今天要聊的fminbnd,就是 MATLAB 环境中解决这类一维局部最小化问题的“瑞士军刀”。它不是一个复杂的黑箱,而是一个基于经典黄金分割搜索和抛物线插值算法的、高效且稳健的数值优化工具。对于从学生到工程师的广大用户而言,掌握fminbnd意味着你拥有了一种将数学问题转化为可计算、可求解方案的直接能力。它特别适合那些函数表达式已知但导数信息获取困难或计算成本高昂的场景,让你无需手动推导梯度或 Hessian 矩阵,也能高效地定位最优解。
简单来说,如果你手上有一个关于单个变量x的函数f(x),并且你大致知道这个函数在某个区间[a, b]内存在一个“洼地”(局部最小值),那么fminbnd就是你召唤来帮你精确找到这个“洼地”底部位置的得力助手。它的核心价值在于其易用性和可靠性——你只需要提供函数句柄和搜索区间,它就能返回一个近似的最优点x以及该点对应的函数值f(x)。在机器学习模型调参、控制系统设计、经济学模型求解乃至简单的实验数据处理中,这种一维搜索能力都是构建更复杂算法(如多维优化、线搜索)的基础模块。接下来,我将结合自己多年的使用经验,深入拆解fminbnd的工作原理、实战技巧以及那些官方文档可能不会明说的“坑”与“捷径”。
2. 核心算法原理与设计思路拆解
fminbnd的强大并非源于魔法,而是建立在两个经过时间考验的经典数值方法之上:黄金分割搜索法和抛物线插值法。理解其背后的设计思路,不仅能让你更自信地使用它,还能在结果不尽如人意时,知道该如何调整策略。
2.1 为什么选择这两种方法的混合策略?
纯粹的黄金分割搜索是一种非常稳健的方法。它的思想源于美学中的黄金比例,通过在区间内对称地选取试探点,并比较这些点的函数值,可以确定最小值点不可能位于的区间部分,从而将搜索区间不断缩小。这种方法不依赖于函数的导数,对函数的形态要求很低,即使函数不可导、甚至不连续(在区间内仅有有限个不连续点),只要存在最小值,它最终也能收敛。但其缺点是收敛速度是线性的,对于光滑函数来说,可能显得有点“慢”。
而抛物线插值法则是一种利用局部函数形态信息来加速收敛的方法。当算法在区间内已经评估了三个点及其函数值时,它可以构造一条穿过这三点的抛物线,并认为这条抛物线的顶点(最小值点)是对原函数最小值点的一个更好估计。这种方法对于近似二次的函数(在最小值点附近,很多光滑函数都近似二次)收敛速度非常快,可以达到超线性收敛。但其缺点是,如果初始三点选择不当,或者函数在局部根本不是二次的,抛物线插值可能会给出一个很差的估计,甚至导致算法发散。
fminbnd的聪明之处在于将两者结合。在每次迭代中,它主要依靠黄金分割搜索来稳步缩小包含最小值的区间,保证算法的稳健性。同时,它会利用已计算出的函数值,尝试进行抛物线插值。只有当抛物线插值给出的新点位于当前的黄金分割区间内,并且预测能带来足够的改进时,算法才会采纳这个插值点作为下一次函数评估的位置。这种混合策略实现了“稳中求快”:在函数行为良好的区域,利用抛物线插值加速;在函数形态复杂或插值不可靠时,则退回到稳健的黄金分割搜索。这种设计思路完美地平衡了效率与可靠性,也是fminbnd能够成为 MATLAB 优化工具箱中坚力量的原因。
2.2 算法流程与关键参数解析
从用户角度看,fminbnd的调用非常简单:[x, fval] = fminbnd(fun, a, b, options)。但其内部流程却包含了一系列精密的逻辑判断。一个简化的核心循环如下:
- 初始化:确认区间
[a, b]有效(a < b),并在区间内根据黄金比例初始化两个内点x1和x2,计算f(a),f(x1),f(x2),f(b)。 - 迭代循环: a.区间判断:比较四个点(两个端点、两个内点)的函数值,确定最小值点位于哪个子区间(例如
[a, x2]或[x1, b])。 b.区间收缩:根据上一步的判断,丢弃不包含最小值点的那部分区间。同时,利用黄金分割比例,在保留的区间内确定一个新的试探点,并计算其函数值。此时,我们仍然拥有四个点(新旧端点及内点)及其函数值。 c.抛物线插值尝试:利用当前最新的三个点(通常是包含当前最优点的三个点)进行抛物线插值,计算插值抛物线的顶点x_p。 d.插值点接受准则:这是算法的关键逻辑。fminbnd不会盲目接受x_p。它设定了严格的接受条件: *x_p必须位于当前搜索区间内部(不能太靠近端点)。 *x_p到当前区间端点的距离必须大于一个与容差相关的阈值,确保移动是“有意义的”。 * 预测的函数值改进量需要达到一定标准。 只有满足所有条件,x_p才会被采纳为下一次函数评估的点,替换掉原本由黄金分割确定的下一个点。否则,算法将继续使用黄金分割点。 - 收敛判断:迭代持续进行,直到搜索区间的长度小于用户指定的容差
TolX,或者函数值在连续迭代中的变化小于容差TolFun。
用户可以通过options结构体来影响这个过程,最重要的两个参数就是TolX(自变量容差)和TolFun(函数值容差)。TolX默认是1e-4,意味着当区间长度缩小到约1e-4量级时停止。TolFun默认也是1e-4。对于大多数工程问题,默认值已经足够。但如果你寻找的是非常精确的最优点(例如在理论研究中),或者函数值本身非常大或非常小,你可能需要相应地调低这两个容差。另一个有用的选项是MaxFunEvals(最大函数调用次数)和MaxIter(最大迭代次数),它们可以防止算法在异常情况下陷入无限循环。
注意:
TolFun是一个相对容差的概念。算法判断的不仅是f(x)的绝对变化,还会考虑函数值的量级。如果你的函数值在1e10量级,那么1e-4的TolFun意味着函数值需要变化1e6才会被认为“没有变化”,这显然不合理。对于量级差异巨大的问题,有时需要更谨慎地设置TolFun,或者对目标函数进行适当的缩放。
3. 实战应用与核心参数配置详解
理解了原理,我们进入实战环节。fminbnd的调用看似简单,但不同的使用场景和细节处理,会直接影响结果的准确性和程序的效率。
3.1 基础调用与函数句柄的多种形式
最基本的调用方式是定义一个匿名函数。例如,寻找函数f(x) = (x-3)^2 - 1在区间[0, 5]上的最小值。
fun = @(x) (x-3).^2 - 1; % 定义匿名函数 [x_opt, fval_opt] = fminbnd(fun, 0, 5); disp(['最优解 x = ', num2str(x_opt)]); disp(['最优函数值 f(x) = ', num2str(fval_opt)]);这里@(x)创建了一个匿名函数句柄。注意,我使用了.^而不是^,这是一种良好的编程习惯,确保了函数可以处理向量输入(虽然fminbnd每次只传入一个标量,但习惯很重要)。
除了匿名函数,你也可以使用独立的函数文件。创建一个名为myFunc.m的文件:
function y = myFunc(x) y = sin(x) + 0.1 * (x - 2).^2; end然后在命令行调用:
[x_opt, fval_opt] = fminbnd(@myFunc, -5, 5);使用函数文件的好处是逻辑可以非常复杂,并且易于调试。fminbnd也支持嵌套函数或私有函数,只要你能将函数句柄传递给它即可。
3.2 处理带额外参数的函数
这是实际应用中非常常见的需求。例如,我们的目标函数可能是一个需要额外参数a,b,c的通用模型:f(x) = a*sin(b*x) + c*x^2。我们有几种方法将参数传递进去:
方法一:创建匿名函数时捕获工作区变量
a = 1.5; b = 2.0; c = 0.5; fun = @(x) a * sin(b*x) + c * x.^2; [x_opt, fval] = fminbnd(fun, -pi, pi);这种方法最简单直接,匿名函数会“记住”定义时工作区中a,b,c的值。缺点是,如果之后修改了工作区中a的值,fun内部使用的依然是旧值。
方法二:编写参数化函数文件创建一个接受额外输入的函数文件paramFunc.m:
function y = paramFunc(x, a, b, c) y = a * sin(b*x) + c * x.^2; end调用时,需要用一个匿名函数“包装”一下:
a = 1.5; b = 2.0; c = 0.5; fun = @(x) paramFunc(x, a, b, c); % 将额外参数固定 [x_opt, fval] = fminbnd(fun, -pi, pi);方法三(MATLAB 较新版本):使用@符号直接绑定参数
fun = @(x) paramFunc(x, 1.5, 2.0, 0.5); % 直接内联参数 [x_opt, fval] = fminbnd(fun, -pi, pi);我个人最推荐方法二,因为它最清晰、最灵活,并且与函数文件调试的兼容性最好。参数被显式地列出,代码意图一目了然。
3.3 优化选项的精细控制
通过optimset函数可以创建和修改优化选项结构体。以下是一些关键选项的配置示例:
% 创建一个选项结构体 options = optimset('fminbnd'); % 获取 fminbnd 的默认选项 % 提高求解精度 options.TolX = 1e-8; % 自变量容差设为 1e-8 options.TolFun = 1e-8; % 函数值容差设为 1e-8 % 限制计算资源,防止意外长时间运行 options.MaxFunEvals = 500; % 最多计算500次函数值 options.MaxIter = 200; % 最多迭代200次 % 开启详细输出,观察算法进程(调试时非常有用) options.Display = 'iter'; % 使用自定义选项进行优化 fun = @(x) (x-3).^2 - 1; [x_opt, fval_opt, exitflag, output] = fminbnd(fun, 0, 5, options); % 查看输出信息 disp(output);output结构体包含了丰富的运行时信息,如funcCount(函数调用次数)、iterations(迭代次数)和algorithm(使用的算法)。exitflag表示退出原因(1 表示收敛于解,0 表示达到最大迭代或函数调用次数,-1 表示被输出函数终止等)。在调试复杂或运行缓慢的问题时,查看output是必不可少的步骤。
实操心得:对于绝大多数问题,默认的
TolX=1e-4已经足够精确。盲目地设置为1e-10或更小,不仅会显著增加计算时间(可能需要多几十次迭代),而且对于存在数值噪声的函数(例如涉及浮点运算或实验数据插值),过高的精度要求是没有意义的,因为函数值本身在极小尺度上就是“震荡”的。设置容差前,先想清楚你的问题真正需要的物理或工程精度是多少。
4. 典型应用场景与案例深度剖析
fminbnd的应用远不止于求解教科书上的简单函数。下面通过几个典型案例,展示其在实际工程和科研中的强大作用。
4.1 场景一:曲线拟合中的最优参数搜寻
假设我们通过实验得到一组数据点(t_i, y_i),我们怀疑它们符合一个衰减振荡模型:y = A * exp(-lambda * t) * cos(omega * t + phi)。现在需要找到参数A,lambda,omega,phi使得模型与数据最匹配。这是一个多维优化问题。但有时,我们可以利用问题特性将其分解。例如,如果频率omega可以通过频谱分析初步估计,相位phi可以通过数据平移大致确定,那么剩下的就是优化幅度A和衰减系数lambda。更进一步,如果我们固定lambda,那么最优的A可以通过线性最小二乘解析求出。于是,问题就变成了一个关于单变量lambda的优化问题:寻找lambda,使得在给定lambda下计算出的最优A所产生的残差平方和最小。
% 假设已有实验数据 t_data 和 y_data % 初步估计 omega 和 phi omega_est = 2.5; phi_est = 0.1; % 定义关于 lambda 的单变量目标函数(残差平方和) fun_lambda = @(lambda) fitError(lambda, t_data, y_data, omega_est, phi_est); % 在合理的物理范围内搜索最优 lambda lambda_lb = 0.01; % 衰减系数下界(正值) lambda_ub = 1.0; % 衰减系数上界 [lambda_opt, min_error] = fminbnd(fun_lambda, lambda_lb, lambda_ub); % 输出结果 fprintf('最优衰减系数 lambda = %.4f\n', lambda_opt); fprintf('最小残差平方和 = %.4e\n', min_error); % 辅助函数:计算给定 lambda 下的最优 A 和残差 function err = fitError(lambda, t, y, omega, phi) % 构造设计矩阵 X = exp(-lambda * t) .* cos(omega * t + phi); % 线性最小二乘求解最优 A A_opt = (X' * X) \ (X' * y); % 计算残差 y_pred = A_opt * X; err = sum((y - y_pred).^2); end在这个案例中,fminbnd优雅地将一个四维非线性最小二乘问题,降维成了一个高效可解的一维搜索问题,大大简化了计算。
4.2 场景二:简单约束优化问题的转化
fminbnd本身要求搜索区间[a, b]是固定的。这实际上就是一种边界约束。对于形式为min f(x), subject to lb <= x <= ub的问题,fminbnd是天然适用的。但对于其他形式的简单约束,有时可以通过变量替换将其转化为边界约束问题。
例如,问题:min f(x), subject to x >= 0。我们可以直接设置区间为[0, Inf]吗?理论上可以,但Inf在数值计算中会带来问题。更好的方法是进行变量替换。令x = t^2,其中t是实数。那么x >= 0自动满足。原问题转化为关于新变量t的无约束问题:min g(t) = f(t^2)。然后对t使用fminbnd(或更适合无约束问题的fminsearch)。但需要注意,f(x)在x=0处的性质可能会因为替换而改变(例如,可导性)。
更常见的处理方式是,如果你知道最优解x*不太可能远离某个正数M,那么完全可以将搜索区间设为[0, M],其中M是一个足够大的正数。fminbnd会在这个区间内寻找最小值。这就是工程思维:用合理的、有物理意义的边界来近似理论上的无界约束。
4.3 场景三:复杂算法中的线搜索子程序
在许多高级的多维优化算法中,如最速下降法、共轭梯度法甚至一些拟牛顿法,每一步都需要沿着一个指定的搜索方向d_k进行一维搜索(线搜索),以确定最优的步长alpha:min_alpha phi(alpha) = f(x_k + alpha * d_k)。这里的phi(alpha)就是一个关于步长alpha的单变量函数。
虽然专业的优化工具箱(如fminunc)有内置的、更复杂的线搜索例程,但在自定义算法或教学演示中,fminbnd可以完美地扮演这个角色。你需要做的就是定义一个关于alpha的匿名函数,并给定一个合理的步长搜索区间[alpha_min, alpha_max]。
% 假设在当前点 x_k,搜索方向为 d_k x_k = [1; 2]; d_k = [-0.1; -0.2]; % 假设是负梯度方向 f = @(x) x(1)^2 + 2*x(2)^2; % 目标函数 % 定义线搜索函数 phi(alpha) phi = @(alpha) f(x_k + alpha * d_k); % 确定步长搜索区间。一个简单策略是 [0, 1] 或根据函数特性估计。 % 更稳健的做法是进行一个初步的“划界”步骤,确保区间包含最小值。 alpha_lb = 0; alpha_ub = 10; % 一个经验上界 % 使用 fminbnd 进行精确线搜索 [alpha_opt, phi_min] = fminbnd(phi, alpha_lb, alpha_ub); % 更新迭代点 x_new = x_k + alpha_opt * d_k;在这个场景下,fminbnd提供了高精度的步长选择,虽然计算成本比简单的回溯法高,但在需要高精度解或函数计算成本本身不高时,这是一个可靠的选择。
5. 常见陷阱、调试技巧与性能优化
即使fminbnd很稳健,不当使用也会导致错误结果或性能问题。下面分享一些我踩过的“坑”和总结的技巧。
5.1 陷阱一:区间内不存在局部最小值
这是最根本的错误。fminbnd寻找的是局部最小值。如果函数在给定区间[a, b]内单调递增或递减,那么最小值必然在端点处。fminbnd仍然会运行,并返回一个端点(通常是函数值较小的那个端点)作为“最优解”,同时可能会给出一个警告信息。例如:
fun = @(x) x; % 单调递增函数 [x, fval] = fminbnd(fun, 2, 5); % 结果:x = 2, fval = 2。算法会提示解在边界上。如何避免:在调用fminbnd之前,最好能对目标函数在区间[a, b]内的行为有个初步了解。简单绘制函数图形是最直观的方法:
fplot(fun, [a, b]); grid on;图形可以立刻告诉你区间内是否存在“洼地”。如果函数很复杂,至少也要在区间内均匀采样几个点,计算函数值,看看趋势。
5.2 陷阱二:多个局部最小值与全局最小值
fminbnd只能保证找到一个局部最小值,而不一定是全局最小值。如果区间内有多个“洼地”,它最终收敛到哪个,很大程度上取决于初始区间和算法在初始阶段的试探点。
fun = @(x) sin(x) + 0.1*x; % 在稍大的区间内会有多个局部极小点 [x, fval] = fminbnd(fun, 4, 7); % 可能在 x≈5.5 处找到一个局部极小,但全局极小可能在 x≈11 处。应对策略:
- 先验知识:利用你对问题的了解,将搜索区间设定在包含你关心的那个局部最小值(希望是全局最小)的附近。
- 多起点搜索:如果你怀疑有多个局部极小点,可以在整个大的感兴趣区间内,划分几个子区间,分别调用
fminbnd,然后比较结果。search_intervals = [0, 3; 3, 6; 6, 9; 9, 12]; % 划分区间 results = []; for i = 1:size(search_intervals, 1) [x_opt, f_opt] = fminbnd(fun, search_intervals(i,1), search_intervals(i,2)); results = [results; x_opt, f_opt]; end [global_min_fval, idx] = min(results(:,2)); global_min_x = results(idx, 1); - 使用全局优化算法:对于复杂的多峰函数,应考虑使用专门的全局优化算法,如
GlobalSearch或patternsearch(需要 Global Optimization Toolbox)。
5.3 陷阱三:数值噪声与不连续性
如果目标函数f(x)的计算过程中包含数值积分、迭代求解、随机模拟或读取带有噪声的实验数据,那么函数值本身会带有“噪声”。即对于非常接近的两个x,f(x)可能不是平滑变化的,而是有微小的随机波动。fminbnd的抛物线插值步骤依赖于函数的光滑性,噪声会严重干扰插值,导致算法行为不稳定,甚至无法收敛到真正的极小点附近。
调试与解决:
- 观察输出:设置
options.Display = 'iter',观察每次迭代的函数值变化。如果函数值在迭代中上下跳动,没有稳定的下降趋势,很可能存在数值噪声。 - 平滑处理:如果噪声是随机的且均值为零,可以考虑在函数内部对计算过程进行多次重复并取平均,以平滑噪声。例如,如果
f(x)涉及蒙特卡洛模拟,就增加模拟次数。function y = noisyFunc(x) num_samples = 1000; % 增加采样次数以平滑 % ... 进行随机计算 ... y = mean(计算结果); % 取平均值 end - 调整容差:增大
TolX和TolFun,让算法不要对微小的变化过于敏感。例如,设置options.TolX = 1e-3和options.TolFun = 1e-3。 - 关闭插值:虽然
fminbnd没有直接提供关闭抛物线插值的选项,但你可以通过一个“技巧”来削弱其影响:将TolFun设得相对较大,这样算法会更早地因为函数值“变化不大”而停止,减少了对光滑性的依赖。更根本的方法是,考虑使用不依赖导数的、更稳健的直接搜索法,如fminsearch(Nelder-Mead 单纯形法),它对噪声的容忍度通常更高。
5.4 性能优化:减少函数调用次数
fminbnd的核心成本在于目标函数f(x)的求值。如果f(x)本身计算非常耗时(例如调用有限元分析、计算流体动力学仿真),那么优化函数调用次数就至关重要。
- 提供梯度?
fminbnd不使用导数信息,所以提供梯度函数对它是无效的。这一点与fminunc等算法不同。 - 设置合理的初始区间:区间
[a, b]越宽,算法需要探索的范围就越大,通常需要的迭代次数也越多。在可能的情况下,尽量利用先验知识缩小搜索区间。 - 利用输出信息:
output.funcCount告诉你函数被调用了多少次。在开发阶段,用简单的测试函数运行算法,观察典型的调用次数,对你预估实际问题的计算成本很有帮助。 - 向量化函数:虽然
fminbnd每次只传入一个标量x,但确保你的目标函数内部的计算是向量化的,这能提升单次函数求值的速度。更重要的是,当你需要进行多起点搜索或参数扫描时,一个向量化的函数能让你更方便地使用arrayfun或循环前的预分配来提升整体性能。 - 缓存/记忆化:对于确定性函数(相同的
x总是返回相同的f(x)),可以考虑实现一个简单的缓存机制,避免重复计算相同的点。这在算法迭代过程中,尤其是容差设置很小时,可能会遇到非常接近的点。一个简单的实现是使用containers.Map或持久变量(persistent variable)。但要注意,这会增加内存开销和查找时间,通常只在单次函数求值成本极高时才值得考虑。
6. 高级技巧与替代方案探讨
当你熟练使用fminbnd后,可以探索一些更高级的用法,并了解在什么情况下可能需要考虑其他工具。
6.1 利用输出函数进行监控和干预
fminbnd支持一个强大的特性:输出函数(Output Function)。你可以在options中指定一个函数,算法在每次迭代后会调用它,传递当前的状态信息(如当前最优x、函数值、迭代次数等)。这允许你:
- 实时绘制收敛曲线:动态显示当前最优函数值随迭代次数的变化。
- 自定义收敛条件:除了内置的
TolX和TolFun,你可以基于更复杂的逻辑(如函数值变化率、梯度估计值)来终止优化。 - 记录历史数据:保存每一迭代的中间结果,用于事后分析。
% 定义一个输出函数 function stop = myOutputFcn(x, optimValues, state) stop = false; % 默认不停止 persistent history % 用持久变量记录历史 if isempty(history) history = []; end if strcmp(state, 'iter') % 在每次迭代时记录 history = [history; optimValues.iteration, x, optimValues.fval]; % 可以在这里绘图或打印信息 fprintf('Iter %d: x = %.6f, fval = %.6e\n', ... optimValues.iteration, x, optimValues.fval); end % 自定义停止条件:例如,如果连续5次迭代改进小于 1e-6 if size(history, 1) >= 5 recent_improvement = abs(diff(history(end-4:end, 3))); if all(recent_improvement < 1e-6) stop = true; disp('自定义收敛条件满足,停止优化。'); end end end % 配置选项 options = optimset('OutputFcn', @myOutputFcn, 'Display', 'final'); fun = @(x) (x-3)^2 - 1; [x_opt, fval_opt] = fminbnd(fun, 0, 5, options);6.2 与符号计算结合求取高精度解或验证
对于简单的解析函数,我们有时可以用符号计算工具箱得到精确的导数,甚至解析解。fminbnd的数值解可以与符号解对比,验证其正确性,或者为fminbnd提供更精确的初始区间。
% 符号计算求导找临界点 syms x_sym f_sym = (x_sym-3)^2 - 1; fprime_sym = diff(f_sym, x_sym); critical_points = solve(fprime_sym == 0, x_sym); disp('符号计算得到的临界点:'); disp(double(critical_points)); % 二阶导数判断极小值 fsecond_sym = diff(f_sym, x_sym, 2); for cp = critical_points' if subs(fsecond_sym, x_sym, cp) > 0 fprintf('x = %.4f 是局部极小点。\n', double(cp)); end end % 使用符号解作为参考,验证 fminbnd 结果 [x_num, fval_num] = fminbnd(@(x) (x-3)^2 - 1, 0, 5); fprintf('fminbnd 结果: x = %.10f, fval = %.10f\n', x_num, fval_num);这种结合方式在教学和研究中特别有用,它能加深你对数值方法精度和局限性的理解。
6.3 何时该考虑其他优化函数?
fminbnd是单变量、有界区间优化的首选。但当问题超出其范围时,需要选用其他工具:
- 无约束多变量优化:使用
fminsearch(Nelder-Mead 单纯形法,无需导数)或fminunc(拟牛顿法,可使用梯度)。 - 有约束多变量优化:使用
fmincon。 - 全局优化:使用
GlobalSearch或MultiStart(需要 Global Optimization Toolbox),或者尝试patternsearch。 - 最小二乘问题:使用
lsqnonlin或lsqcurvefit。 - 线性/二次规划:使用
linprog或quadprog。
选择工具的关键在于认清你的问题维度、约束类型以及对导数信息的掌握程度。fminbnd因其简单可靠,在它适用的领域内,始终是那个你不会出错的第一选择。它就像一把精准的螺丝刀,虽然不能解决所有问题,但在拧紧那颗一维的“螺丝”时,它比任何万能工具都来得顺手和放心。