北大凸优化编程作业全解析:从自动微分到ADMM的代码链
简介北京大学信息科学技术学院智能科学与技术系“凸分析与优化方法”课程编程作业集合面向正在学习凸优化、自动微分和神经网络训练的学生用于实践并比较多种经典优化算法。资源共63个文件以23个Python脚本和32张结果图为主另有3个JSON配置、1个TXT、1个DOCX及Markdown说明等压缩包整体仅1.03MB结构按HW模块清晰组织。已有92人学习浏览适合课程复习、算法对照实验或项目参考。作业覆盖自动微分与梯度计算、回溯线搜索、最陡下降、阻尼牛顿法、高斯牛顿法、共轭梯度法、DFP/BFGS及L-BFGS拟牛顿法并包含ADMM、随机优化方法、块坐标下降、惩罚函数与增广拉格朗日等进阶实现每个作业配有可运行的Python代码、收敛过程可视化图像以及说明文档帮助使用者直观理解不同优化器的收敛行为和适用场景。附赠说明文件与资源文档还补充了作业背景和实现要点能够支持读者在掌握理论的同时快速上手调试与复现。1. 这包作业的真实价值把凸优化课程从公式变成可运行的代码链拿到这份北大的《凸分析与优化方法》编程作业集合时我第一反应不是「又是一堆交差代码」而是「这条代码链太完整了」。从自动微分的计算图实现到梯度下降、最陡下降、阻尼牛顿法、高斯牛顿法、共轭梯度法、DFP 拟牛顿、BFGS、L-BFGS再到罚函数法、块坐标下降和 ADMM 家族十二次编程作业几乎把凸优化主线算法全走了一遍而且每个作业都带收敛曲线图和可运行的 Python 源码。对正在学凸优化、数值优化或机器学习但苦于「公式看得懂、代码写不出」的从业者来说这是一份可以直接对照教材、逐行复现实验的资源不是笔记不是截图是能跑出图的代码。2. 先盘清作业地图HW3 到 HW21 的算法谱系与两条阅读主线2.1 作业编号与算法对照知道每个文件在干什么这份资源最容易被忽视的一点是它内部有一套清晰的作业编号体系。HW3 是自动微分与神经网络参数优化HW11 是梯度下降HW12 是最陡下降和阻尼牛顿法HW13 是共轭梯度法和 DFPHW14 是 BFGS、L-BFGS 和 majorization-minimizationHW17 是算法对比实验HW18 是罚函数法和 Frank-WolfeHW19 是块坐标下降逻辑回归和字典学习以及 LADMAP、LADMPSAPHW20 是随机梯度下降HW21 是加速梯度下降和增广罚函数。这个顺序本身就在暗示一门课的教学节奏先解决「怎么求梯度」再解决「怎么用梯度走直线」再升级到「用二阶信息走曲线」最后进入约束优化和分布式场景。作业编号核心算法对应文件HW3自动微分、梯度计算、神经网络参数优化autoDiff.py, grad4Net.py, grad4NetTest.pyHW11梯度下降、回溯线搜索gradDescent.pyHW12最陡下降、阻尼牛顿法steepDescent.py, Newton.pyHW13共轭梯度法、DFP 拟牛顿conjGrad.py, DFP.pyHW14BFGS、L-BFGS、majorization-minimizationBFGS.py, L-BFGS.py, majMin.pyHW17多种算法收敛对比compare.pyHW18罚函数法、Frank-Wolfe 算法penalty.py, FW.pyHW19块坐标下降、LADMAP、LADMPSAPBCD4LR.py, BCD4Dict.py, LADMAP.py, LADMPSAP.pyHW20随机梯度下降Stochastic.pyHW21加速梯度下降、增广罚函数AGD.py, APenalty.py对照这个表去翻文件你很快会发现HW12 的steepDescent.py和 HW11 的gradDescent.py长得非常像但前者固定步长、后者带回溯线搜索HW13 的DFP.py和 HW14 的BFGS.py是同一套拟牛顿框架换了两组更新公式。这些「同框架不同算法」的对比恰恰是这门课最有价值的地方因为它让你看到算法之间的差异不是玄学而是几行公式的区别。2.2 文件组织与运行环境先解决能不能跑的问题资源的根目录下有几个容易被忽略的辅助文件说明文件.txt、附赠资源.docx、README.md、.gitignore以及.vscode/settings.json。按我处理开源作业包的习惯先看README.md和附赠资源.docx前者通常是作业要求和代码结构说明后者往往夹带了课程讲义、实验指引甚至是老师给的伪代码。.vscode/settings.json里配置的是 Python 解释器路径和 lint 规则说明这些作业原始开发环境就是 VS Code你用 PyCharm 也没问题只要保证 Python 版本能跑 numpy 和 matplotlib。运行环境比我预想的简单Python 3.8 以上就够依赖只有 numpy 和 matplotlib。所有作业都是单文件脚本没有复杂的包结构直接python HW11/gradDescent.py就能跑出对应的converge.png收敛曲线。如果你想一次性验证全部作业按目录逐个执行即可。唯一要注意的是 HW19 和 HW21 里的 ADMM 变体会稍微慢一些建议先跑小规模数据确认逻辑无误再放开迭代轮数。2.3 两条阅读主线从哪个作业开始读最省力我建议你按「一阶方法 → 二阶方法 → 约束优化」的顺序读而不是按作业编号顺序。第一条主线是一阶优化HW3自动微分→ HW11梯度下降→ HW20随机梯度下降→ HW21加速梯度下降。这条线解决的是「梯度从哪来、往哪走、走多远」的问题逻辑最连贯入门成本最低。第二条主线是二阶与拟牛顿HW12阻尼牛顿→ HW13共轭梯度、DFP→ HW14BFGS、L-BFGS→ HW17对比实验。这条线解决的是「如何用曲率信息加速收敛」需要你熟悉 Hessian 矩阵和线搜索的概念后再上。我的实际建议是第一遍按主线一跑通 HW3 和 HW11第二遍再进入主线二因为 HW3 的自动微分是整个资源的技术地基后面所有作业里的梯度都靠它或者 numpy 手写梯度来提供。如果你一上来就啃 L-BFGS大概率会被 two-loop recursion 绕晕反而失了信心。3. 自动微分与一阶优化HW3/HW11 复现路径与线搜索参数3.1 自动微分骨架从计算图到反向传播HW3 的autoDiff.py是这包资源的第一个技术亮点。它实现了类似 PyTorch 早期版本的计算图自动微分机制核心是三个组件变量节点Var、算子节点Add、Mul 等和反向传播入口。变量节点不仅要保存数值还要保存梯度并且用一个_grad_fn指向反向传播时如何把上游梯度传给下游节点。下面的代码是这类作业最常见的骨架写法class Var: 计算图节点保存数值、梯度和反向传播闭包 def __init__(self, value, grad_fnNone, children()): self.value float(value) self.grad 0.0 self._grad_fn grad_fn # 接收上游梯度传给子节点 def backward(self, g1.0): self.grad g if self._grad_fn: self._grad_fn(g) def __add__(self, other): other other if isinstance(other, Var) else Var(other) left, right self, other def _grad_fn(g): left.backward(g) right.backward(g) return Var(left.value right.value, _grad_fn, (left, right)) def __mul__(self, other): other other if isinstance(other, Var) else Var(other) left, right self, other def _grad_fn(g): left.backward(g * right.value) # d(left*right)/d(left) right right.backward(g * left.value) # d(left*right)/d(right) left return Var(left.value * right.value, _grad_fn, (left, right))逻辑说明backward(g)接收上游梯度g先累加到当前节点的grad上再通过_grad_fn把梯度传给子节点。加法的梯度直接透传乘法的梯度则按乘法法则乘上另一节点的数值。这段代码虽然简略但你已经能看到自动微分的本质它不是在求导而是在记录计算过程然后沿着计算图反向传播局部梯度。参数说明grad初始为 0.0每次backward累加而不是赋值这对应的是批量梯度计算——多个损失分量同时对同一个参数求梯度时梯度必须求和。_grad_fn是闭包捕获了算子左右两端的节点引用所以即使计算图被保存到变量out里反向传播时依然能沿着闭包链走回去。这也是为什么作业里强调不要 break 计算图——一旦你用 numpy 原生函数打断 Var 节点的连接梯度链就断了。3.2 回溯线搜索梯度下降里最容易翻车的环节HW11 的gradDescent.py表面上是梯度下降实际考察点是回溯线搜索backtracking line search。很多初学者直接固定步长如 0.01结果要么收敛慢得像蜗牛要么干脆震荡。回溯线搜索的思路是先给一个较大的初始步长然后按比例缩小直到满足 Armijo 充分下降条件。作业里通常会有两个关键参数c下降量阈值系数和rho步长缩减因子。import numpy as np def backtracking_line_search(f, grad, x, d, c1e-4, rho0.8, max_iter50): 回溯线搜索沿方向 d 找满足 Armijo 条件的步长 t f: 目标函数, grad: 梯度函数 x: 当前点, d: 搜索方向 c: Armijo 条件系数, rho: 步长缩减因子 t 1.0 g grad(x) fx f(x) descent g.dot(d) # 方向导数应小于 0 for _ in range(max_iter): if f(x t * d) fx c * t * descent: break t * rho return t逻辑说明descent是当前梯度与搜索方向的内积对于梯度下降d -g它恒为负值。Armijo 条件要求新点的函数值不高于fx c * t * descent这保证步长不会大到把函数值抬升。如果条件不满足就t * rho把步长缩小 20%继续尝试。循环上限max_iter是兜底防止死循环。参数说明c通常取 1e-4这是极小值基本只要求函数值有下降趋势rho取 0.8 左右太小会让步长缩得过快一次迭代要多算好几轮函数值。我见过不少人把rho设成 0.5结果每一步都要回退七八次才找到合适步长总迭代次数没变但函数求值次数翻倍。HW11 的converge.png如果出现「锯齿状」下降先怀疑步长策略再怀疑梯度算错。3.3 从梯度下降到最陡下降同一条路、不同步长策略HW11 和 HW12 放在一起看很有意思。gradDescent.py用的是回溯线搜索算步长而steepDescent.py更贴近「最陡下降」的原始定义每一步都沿着当前梯度的反方向走但步长直接取固定值或者用精确线搜索求解min_t f(x - t * g)。在作业实现里后者往往被简化成一次二次函数拟合因为精确线搜索的解析解只在特定函数上存在。实操中的差别在收敛速度上非常明显最陡下降在椭圆型二次函数上会走「之」字形因为相邻两步方向正交带回溯线搜索的梯度下降则因为步长比较保守反而路径更平滑。HW12 里配的converge.png和converge_damped.png就是用来对比这两种行为的——如果你跑出来的图里最陡下降的损失下降速度明显慢于阻尼牛顿说明你的梯度方向计算正确而问题本身的条件数比较大需要二阶方法出场。3.4 一个顺手实验硬间隔 SVM 的梯度下降读 HW11 的时候我顺手做了一个小验证把目标函数换成了硬间隔 SVM 的合页损失加正则项用同一套回溯线搜索跑梯度下降。做法是把f(x)改成sum(max(0, 1 - y * (w x))) 0.5 * lambda * w w梯度里多一项正则化导数。这个改动只用十分钟但能让你立刻感受到梯度下降算法本身的通用性——它不关心你的目标函数来自什么模型只要提供梯度和函数值就能跑。热搜里提到的「svm 的梯度下降」「硬间隔 svm 的梯度下降」本质就是这个操作。想验证的话直接在gradDescent.py里替换目标函数收敛曲线一样能画出来。4. 二阶与近似二阶方法阻尼牛顿、共轭梯度、DFP/BFGS 的代码骨架4.1 阻尼牛顿法修正 Hessian 的不正定HW12 的Newton.py实现了阻尼牛顿法它的经典迭代格式是d_k -(H_k lambda_k * I)^(-1) * g_k x_{k1} x_k t_k * d_k其中lambda_k就是阻尼项t_k是线搜索步长。H_k是 Hessian 矩阵g_k是梯度。为什么要加lambda_k * I因为牛顿法的原版假设 Hessian 正定但很多非凸函数在某一点的 Hessian 并不正定直接求逆会得到一个上升方向导致发散。加上lambda_k * I后只要lambda_k足够大矩阵就强制变成正定。作业代码里一般会这样组织def damped_newton_step(f, gfun, hfun, x, lam1e-3): g gfun(x) H hfun(x) n H.shape[0] # 强制对称正定加阻尼项后尝试 Cholesky失败则加大阻尼 A H lam * np.eye(n) try: d -np.linalg.solve(A, g) except np.linalg.LinAlgError: lam * 10.0 A H lam * np.eye(n) d -np.linalg.solve(A, g) return d, lam逻辑说明先用np.linalg.solve解线性方程组而不是直接求逆矩阵数值上更稳定。Cholesky 分解失败说明矩阵仍不正定就把lam放大十倍重新来。这样既保证了搜索方向是下降方向又避免了作业里「牛顿法发散」的常见翻车。参数说明lam的初始值选取很敏感。1e-3 适合目标函数尺度在 1 附近的场景如果你的损失函数动辄上千lam初始值可以给到 0.1 甚至 1。HW12 里converge_damped.png与converge.png的对比目的就是让你看到阻尼项对稳定性的影响——不加阻尼的牛顿法在 Rosenbrock 函数上几步就会冲出边界。4.2 共轭梯度法如何避开矩阵求逆HW13 的conjGrad.py实现的是线性共轭梯度法用来解大型对称正定线性方程组。它最大的价值是不需要显式存储 Hessian只需要 Hessian 和向量的乘积H v。在代码里你会看到经典的 Fletcher-Reeves 版本def conjugate_gradient(Afun, b, x0, tol1e-8, max_iter200): x x0.copy() r b - Afun(x) # 残差 p r.copy() rho r.dot(r) for i in range(max_iter): Ap Afun(p) alpha rho / p.dot(Ap) x x alpha * p r_new r - alpha * Ap rho_new r_new.dot(r_new) if np.sqrt(rho_new) tol: break beta rho_new / rho # Fletcher-Reeves 系数 p r_new beta * p r, rho r_new, rho_new return x逻辑说明Afun是一个返回矩阵向量乘积的函数这是共轭梯度法的灵魂。算法维护残差r和搜索方向palpha是沿当前方向的精确步长beta是新残差与旧残差模长比的平方用来保证新搜索方向与之前所有方向共轭。整个过程只需要 O(n) 存储这是它在大规模稀疏问题里受欢迎的原因。参数说明tol1e-8控制停机精度作业里如果收敛曲线尾部不下降可以放宽到 1e-6 观察趋势。max_iter理论上不超过变量维度 n超过这个数还没收敛基本就是代码写错了。你可以在 HW13 里用Afun lambda v: H v传入 Hessian 矩阵就能把它当非线性优化的子问题求解器用。4.3 DFP 与 BFGS 的更新公式与代码要点HW13 的DFP.py与 HW14 的BFGS.py是同一个拟牛顿框架的两组更新公式。它们做的事完全一样用梯度差值y g_{k1} - g_k和位移s x_{k1} - x_k去逼近 Hessian 逆矩阵省去二阶导计算。BFGS 的更新公式是rho_k 1 / (y^T s) H_{k1} (I - rho_k * s * y^T) * H_k * (I - rho_k * y * s^T) rho_k * s * s^TDFP 则是把 s 和 y 的角色对调更新的是 Hessian 本身而不是逆矩阵。作业里的代码一般会先用 BFGS 算出搜索方向再搭配线搜索最后更新矩阵def bfgs_update(H, s, y): BFGS 更新拟牛顿矩阵 H ys y.dot(s) if ys 1e-12: return H # 防止除零 rho 1.0 / ys I np.eye(H.shape[0]) A I - rho * np.outer(s, y) B I - rho * np.outer(y, s) return A H B rho * np.outer(s, s)逻辑说明ys y.dot(s)必须大于 0这对应目标函数的凸性条件。如果ys接近 0说明步长太小或梯度噪声太大更新矩阵会退化此时直接返回原矩阵更安全。np.outer构造的是外积矩阵BFGS 要的就是这种秩一更新的叠加效果。参数说明BFGS 的收敛速度比梯度下降高一个量级但内存占用是 O(n^2)。如果你的参数维度超过几千BFGS 会直接吃掉几百兆内存这时就得换 L-BFGS。4.4 L-BFGS 与高斯牛顿内存受限时的两个走向HW14 的L-BFGS.py是 BFGS 的低内存变体它不显式存储矩阵而是保存最近 m 组(s, y)对用 two-loop recursion 隐式计算搜索方向。参数 m 一般取 10 到 20代表保留多少步历史信息。实现核心是两层循环第一层从最新到最旧计算 rho 和 alpha第二层从最旧到最新累加方向。这段代码不难但非常容易在索引上犯错我的调试经验是把s_list和y_list打印出来逐行对。高斯牛顿法则出现在非线性最小二乘场景比如 HW17 的compare.py里做曲线拟合时。它把目标函数写成残差平方和用雅可比矩阵近似 HessianH ≈ J^T J梯度是J^T r这样迭代步长公式变成delta -(J^T J lambda I)^{-1} J^T r。注意这里的J^T J天然半正定加了lambda I后就是完整的阻尼高斯牛顿——这也是 Levenberg-Marquardt 算法的雏形。作业里如果要求你对一个非线性模型做参数拟合优先用高斯牛顿而不是 BFGS因为残差结构能被充分利用收敛更快。5. 罚函数、块坐标下降与 ADMM约束优化作业的避坑记录5.1 罚函数法与 Frank-Wolfe怎么处理约束HW18 的penalty.py实现的是外罚函数法把约束c(x) 0变成惩罚项加到目标函数上然后增大惩罚系数mu迭代求解无约束问题。这个思路直白但有个经典陷阱mu太小约束不满足mu太大目标函数变得病态收敛极慢。作业代码里一般会写成def penalty_method(f, con, x0, mu01.0, max_iter20): x x0.copy() mu mu0 for k in range(max_iter): # 定义增广目标函数 def f_pen(x): return f(x) mu * max(0, con(x)) ** 2 # 用无约束优化方法如 BFGS求解 f_pen 的极小点 x solve_unconstrained(f_pen, x) mu * 5.0 # 增大惩罚系数 return x逻辑说明mu每次放大 5 到 10 倍让惩罚项逐渐主导目标函数最终把解逼近可行域边界。solve_unconstrained可以用前面任意一个无约束方法替代HW18 里配的是FW.py即 Frank-Wolfe 算法——它把约束优化问题分解成一系列线性规划子问题适合约束集是单纯形或球集的场景。参数说明mu0的初值决定了第一轮迭代对约束的重视程度。初值太小会导致刚开头几步完全无视约束后面要花大量代价拉回来。我的习惯是先用无约束解算一次看约束违反量级再定mu0。5.2 块坐标下降BCD4LR 与 BCD4Dict 的分块逻辑HW19 的BCD4LR.py和BCD4Dict.py是块坐标下降Block Coordinate Descent的两个应用实例。BCD4LR.py针对逻辑回归把权重向量按维度分块每一轮只优化一个维度固定其他维度BCD4Dict.py针对字典学习把变量分成字典矩阵和系数矩阵两组交替更新一组、固定另一组。核心代码骨架是def bcd_lr(X, y, w0, lr0.1, block_size1, max_epoch100): 块坐标下降训练逻辑回归 X: (n_samples, n_features), y: 标签 block_size: 每轮更新的特征块大小 w w0.copy() n_features X.shape[1] for epoch in range(max_epoch): for start in range(0, n_features, block_size): idx slice(start, start block_size) # 只对当前块的权重计算梯度 grad_block compute_grad(X[:, idx], y, w[idx]) w[idx] - lr * grad_block return w逻辑说明块坐标下降的核心是「分而治之」。当block_size1时它就是坐标下降每一轮只更新一个坐标计算开销小但迭代次数多当block_sizen_features时退化成全批量梯度下降。作业里要求你观察不同块大小对收敛速度的影响这比直接调学习率更直观。参数说明block_size的选择是内存与速度的权衡。字典学习的问题里系数矩阵维度可能上千但每个样本的稀疏编码子问题独立可以并行处理这才是块坐标下降在稀疏场景胜出的原因。5.3 LADMAP / LADMPSAPADMM 的调参与收敛HW19 里的LADMAP.py、LADMPSAP.py以及 HW21 的pLADMPSAP.py是 ADMM交替乘子法的改进版本。ADMM 的标准迭代格式是交替更新原始变量x、辅助变量z和对偶变量u其中最关键的是惩罚参数rho。固定rho的 ADMM 在简单问题上够用但遇到病态问题收敛慢得让人怀疑人生所以 LADMPSAP 加了自适应调整def admm_step(A, b, x, z, u, rho): # 更新 x求解近端梯度子问题 x prox_f(x - (A.T (A x - b) u) / rho, rho) # 更新 z求解约束投影子问题 z_new project_g(x u / rho) # 更新对偶变量 u尺度化形式 u u rho * (x - z_new) return x, z_new, u逻辑说明ADMM 把大问题拆成分别以x和z为变量的两个近端算子prox计算u是对偶变量记录原始变量与辅助变量之间的不一致性。prox_f是近端算子在作业里通常是对l2范数或l1范数的软阈值函数project_g是对可行域的投影比如非负约束就是max(0, ...)。参数说明rho的更新策略是每迭代若干轮比较原始残差和对偶残差的大小哪个大就调整哪个方向的权重。pLADMPSAP.py里加了并行前缀说明它支持多个子问题并行求解这是大数据场景下的标配。HW19 配了三张收敛图19_1_adm_fi.png、19_1_grad_fi.png、19_3_fi.png分别对应 ADMM、梯度法和第三个变体——如果你跑出来的 ADMM 收敛速度还不如梯度法先检查rho是否被固定在了极不合理的值上。5.4 四个高频雷区现象、原因与解决我跑完这一整套作业踩过的坑基本都能归成下面四类都是在HW12、HW14、HW19里反复出现的。雷区一阻尼牛顿法损失曲线发散。现象是前几步损失下降正常到第十步左右突然NaN。原因是步长过大Hessian 在远离极小点的地方接近奇异阻尼项lambda初始值太小。解决方法是把lambda从 1e-3 调到 0.1并在每次迭代检查更新向量的范数超过阈值就退回上一步缩小步长。从那以后我每次跑牛顿法都强制加一层「方向范数守卫」。雷区二BFGS 在参数维度 5000 时内存直接打满。现象是程序运行到一半系统开始疯狂 swap。原因很简单BFGS 要维护一个 n×n 的矩阵5000 维就是 200MB 浮点存储。解决方法是换L-BFGS.py设置m10只保留 10 组历史增量内存占用降到几 MB收敛速度下降不到 20%。雷区三ADMM 的收敛曲线有一个长长的平台期。现象是对偶残差下降很慢主残差却已经很小。原因是rho固定不变早期压力值不适合后期。解决方法是每 20 轮按残差比值更新rho比值大于 10 就放大小于 0.1 就缩小。HW19 的LADMPSAP.py里应该有这个逻辑跑不通时多看一眼rho的打印值。雷区四共轭梯度法迭代超过 n 次还不收敛。现象是max_iter跑满但残差停在 1e-4 附近。原因是目标矩阵的条件数太大或者Afun里用了浮点误差较大的实现。解决方法是先做一步对角预处理把A缩放成对角线为 1 的矩阵再跑共轭梯度。这个预处理能直接把迭代次数从几千降到几十。6. 跑通之后梯度验证与线搜索参数的习惯6.1 梯度检查用数值梯度给解析梯度兜底整个资源里我最后悔没早点做的事是在改代码前先跑一遍梯度检查。HW3 里带了grad4NetTest.py它做的事情就是对比自动微分求出的梯度与数值梯度判断差异是否在容差范围内。通用做法是用中心差分公式def numerical_grad(f, x, eps1e-6): 中心差分数值梯度O(eps^2) 精度 g np.zeros_like(x) for i in range(x.size): x_plus x.copy() x_minus x.copy() x_plus.ravel()[i] eps x_minus.ravel()[i] - eps g.ravel()[i] (f(x_plus) - f(x_minus)) / (2 * eps) return g逻辑说明中心差分同时取正负两个扰动点误差比单侧差分小一个量级。eps取 1e-6 到 1e-7 是经验区间太大梯度被截断误差污染太小被浮点精度污染。检查时比较解析梯度与数值梯度的相对误差小于 1e-5 就说明实现正确。我对 HW11 到 HW21 的每个目标函数都跑过一次这个检查凡是收敛曲线异常的最后查出来九成是梯度符号或维度索引写反。参数说明这个函数在参数维度高时很慢但它是「后悔药」只看小规模随机数据就能拦住大部分低级错误。6.2 收敛图解读与线搜索参数复用习惯作业里那些converge.png不是摆设。读它们时我看三个信息纵轴损失曲线的下降斜率、是否出现平台期、末端是平稳还是锯齿。下降斜率对应线搜索参数rho和c的选择——斜率太陡但曲线震荡说明步长大需要调小rho斜率平缓说明步长保守可以加大初始步长。平台期通常意味着算法进入了 Hessian 病态区域该换二阶方法。末端锯齿往往是固定步长导致的。我的个人习惯是维护一套「线搜索参数基线」目标函数尺度未知时先用c1e-4, rho0.8, t01.0跑一遍观察前三次迭代的函数值如果第一步就不满足 Armijo 条件且连续回退超过五次把t0改为 0.5如果曲线震荡把rho降到 0.5。这套基线在我处理作业和后续自用项目里一直有效比每次从头调参数省太多时间。跑完这份作业集合后我再写优化代码都会先写梯度检查函数再写优化器本身先验证梯度再调参最后画收敛曲线这套顺序已经变成雷打不动的习惯希望也能帮到你。本文还有配套的精品资源点击获取