
简介开环迭代学习控制OL-ILC资源包面向控制理论与应用方向的学习者其中TPDILC为一种具体的开环迭代学习算法。包内共2个文件压缩包约1.11MB主要由MATLAB例程和配套论文PDF组成.m文件给出TPDILC仿真实现可直接运行并观察控制输入的逐次更新与误差收敛过程也便于修改学习律和参数对比不同设置对收敛速度的影响PDF文献讨论制导控制一体化场景下的反馈线性化滑模控制方法可作为与迭代学习结合的扩展阅读。开环ILC无需实时反馈而是依据历次运行误差修正控制输入适合周期性重复任务中追求终态收敛的场合典型应用包括机械臂重复运动、批处理过程、精密定位等资源运行门槛低适合初步了解ILC原理、希望获得可运行示例的研究者或高年级本科生使用。目前已有334人学习下载内容精简但针对性强既适合快速上手迭代学习编程又能通过论文拓展到非线性鲁棒控制设计值得控制方向学习者参考。1. TPDILC和开环迭代学习拿上一轮误差修下一轮输入先从这里下手TPDILC 这个缩写放进今天的控制论文里很像某个新框架的名字拆开其实是条从 P 型、D 型迭代学习控制ILC里长出来的开环学习律把学习增益做成随时间轴变化的量每次试验只依赖上一轮误差修正输入当前试验误差不做实时反馈。它专门解决高重复场景的轨迹跟踪问题——机械臂每天走同一条轨迹、伺服系统反复跑同一步序能不能用一次次“做完再改”的方式把跟踪误差压下去而不是靠调一次参数就一直硬扛。适合做机器人轨迹跟踪、重复运动控制、批次过程控制的工程师或者研究生。开环 ILC 结构轻、参数少、上收敛曲线快但代价是抗噪声弱、对初始条件敏感所以这篇笔记把数学形式、最小复现代码、收敛曲线横轴对齐和常见翻车点一次说清。2. 从开环 P 型到 TPD 型学习律数学形式、收敛条件和三个关键参数2.1 开环 ILC 和闭环 ILC 的分水岭只信上次误差不碰当前误差迭代学习控制的基本工作方式是让同一个受控对象在有限时间区间内反复执行同一个任务每一轮称为一次试验trial或一次迭代iteration。第 k 轮试验得到误差轨迹 e_k(t)学习律拿这个误差轨迹生成第 k1 轮的输入轨迹 u_{k1}(t)。整个过程更像“批处理”一次试验做完数据攒齐再统一更新下一次的指令而不是在运动中实时修改。开环 ILC 的意思是更新时只用上一轮误差 e_k不用当前误差 e_{k1}。最常见的学习律形式为u_{k1}(t) u_k(t) L * e_k(t)其中 L 是学习算子可以是比例、微分、比例微分、PID 的组合。因为当前轮量测不会在更新公式里出现所以实现时只需要两个数组一个存当前输入轨迹一个存上一轮误差轨迹试验结束后离线更新。闭环 ILC 则会在更新的同时把当前试验的反馈误差 e_{k1} 也并进去稳定性更强但实现和调参都复杂一截。对比项开环 ILC闭环 ILC更新信息上一轮误差 e_k上一轮误差 当前反馈误差典型形式u_{k1} u_k L e_ku_{k1} u_k L e_k C e_{k1}噪声敏感度较高误差里的噪声会被学进输入较低当前反馈能抑制一部分实现成本两个数组加一个循环需要在线计算反馈修正适用场景批次过程、离线轨迹复现高精度实时伺服、强扰动工况我在实际项目中做离线轨迹复现时优先开环因为可以先验证想学的动态特性到底学不学得会。闭环 ILC 的反馈项会掩盖一部分模型问题最后调试时很难区分是学习律不好还是反馈参数拖了后腿。开环 ILC 缺点是遇到比较大的测量噪声时容易把噪声学进输入后面在避坑章节会专门讲。2.2 TPD 型学习律怎么写把 P 和 D 的增益从常数改成时间 t 的函数P 型 ILC 更新式里学习增益 γ_P 是常数u_{k1}(t) u_k(t) γ_P * e_k(t)PD 型 ILC 增加误差导数项u_{k1}(t) u_k(t) γ_P * e_k(t) γ_D * ė_k(t)TPDILC 的核心变化是把 γ_P、γ_D 从常数推广成关于时间 t 的函数也就是u_{k1}(t) u_k(t) γ_P(t) * e_k(t) γ_D(t) * ė_k(t)γ_P(t) 和 γ_D(t) 可以是分段常数、正弦函数、指数衰减函数也可以按采样点逐点给出。这样做的原因是真实系统的摩擦、惯量、力矩特性在一条轨迹的不同时间点差异很大。比如一个点到点运动中启动阶段要克服静摩擦末端要克服弹性回弹如果在整个时间轴上用同一个学习增益通常是取最保守的值导致某些阶段学习速度很慢。给它加上时间变化能力等于让每个时间段可以有自己的学习强度。常见的工程做法是只把增益表做成数组即使第一版跑通的时候全部填常数也行。先把变量位置留出来后面处理摩擦力突变、负载变化时才不用重构代码。下面这段伪代码表达了增益数组如何参与更新# 增益可以是常值数组也可以按时间变化 import numpy as np N 1001 # 一次试验的采样点数 t np.linspace(0, 1, N) # 常数版本先跑通再改 gamma_P 0.5 * np.ones(N) gamma_D 0.1 * np.ones(N) # TPD版本启动阶段加比例增益末段增加微分阻尼 gamma_P_tpd 0.5 * (1.0 0.3 * np.sin(2 * np.pi * t)) gamma_D_tpd 0.1 * (1.0 - 0.5 * np.exp(-5 * t))增益数组和误差轨迹逐点相乘本质上是在时间轴上做逐采样点学习。要注意的是γ_P(t) 和 γ_D(t) 不能同时都很大比例增益大会加速收敛但容易振荡微分增益大能抑制振荡但会把噪声放大。TPD 的复杂度也在这里增益从两个数变成两个数组需要的调参空间变大。我一般先将两个增益都设成常数去跑通收敛再在误差持续时间段上逐段调。2.3 收敛条件和 λ 范数跑仿真前先确认这个学习律有机会收敛开环 ILC 不是随便给一组增益都能收敛。考虑线性被控对象 G(s)P 型开环 ILC 的误差传递关系是E_{k1}(s) (1 - γ_P * G(s)) * E_k(s)每一轮迭代误差都要乘一个传递函数想让误差幅度越来越小在频域上就要满足max | 1 - γ_P * G(jω) | 1这个条件对 γ_P 的选择施加了直接约束。PD 型 ILC 的误差传递变成E_{k1}(s) (1 - (γ_P γ_D * s) * G(s)) * E_k(s)实际仿真中我不会每次都手推这个条件而是画出被控对象频率响应再大致估计增益边界。对相对阶较高或者存在大时滞的对象简单 P 型开环 ILC 往往很难收敛这时候 PD 型或者带超前补偿的 ILC 才可行。这是选型问题不是调参问题。ILC 理论里还常提到 λ 范数公式是‖x(t)‖_λ sup_{t∈[0,T]} e^{-λt} * ‖x(t)‖它实质上是把时间轴末端的信号乘上指数衰减权重再取上确界。引入 λ 范数后可以把时间轴上的耦合关系压缩成一个常数从而用压缩映射证明开环 ILC 在有限时间区间上的收敛。对工程人员来说最重要的是记住一件事ILC 收敛是在“有限时间区间、重复任务、相同初始条件”三个前提下讨论的只要有一个被破坏收敛性就不再被保证。后面避坑章节里好几个翻车案例都能追溯到这里。3. 用 Python 复现最小开环 ILC单连杆轨迹跟踪仿真3.1 被控对象与参考轨迹先有“重复过程”才有“学习”我通常用一个带阻尼和轻微非线性的二阶系统模拟简单机械臂因为二阶系统足够表现出 ILC 的收敛过程参数也不多。被控对象用以下状态空间形式import numpy as np import matplotlib.pyplot as plt # 时间参数一次试验 1 秒采样 1ms T 1.0 dt 1e-3 N int(T / dt) 1 t np.linspace(0, T, N) # 被控对象参数 wn 2 * np.pi * 4.0 # 自然频率 4 Hz zeta 0.35 # 阻尼比 A np.array([[0.0, 1.0], [-wn**2, -2.0 * zeta * wn]]) B np.array([[0.0], [wn**2]]) C np.array([[1.0, 0.0]]) def run_trial(u_seq, x0): x x0.copy() y_hist [C x] for k in range(N - 1): x_dot A x B.flatten() * u_seq[k] x x x_dot * dt # 库仑摩擦近似速度接近零时阻力增大给学习留一点非线性的量 friction 0.05 * np.tanh(10.0 * x[1]) x x np.array([0.0, -friction]) * dt y_hist.append(C x) return np.array(y_hist).flatten()参考轨迹采用五次多项式从 0 平滑过渡到 1两端速度和加速度为零避免给 ILC 输入一个需要无穷大加速度的参考信号。轨迹参数在代码里是这样写的# 五次多项式平滑轨迹 def smooth_ref(amp1.0, t_t0.0): s np.clip((t - t_t) / (T - t_t), 0.0, 1.0) return amp * (10.0 * s**3 - 15.0 * s**4 6.0 * s**5) yd smooth_ref(amp1.0)这里让输出量纲保持无单位便于比较误差。注意 fy 输出和参考 y 都是从 0 开始的这很重要——ILC 对初始偏移极其敏感如果参考轨迹起点是 0 而系统输出从别的位置开始后面不管怎么学都学不稳。3.2 核心迭代循环存储上一轮输入整条轨迹统一更新开环 ILC 主循环不需要神经网络也不需要优化器就是一个数组更新。核心代码如下# TPD 型时间变化增益 gamma_P 0.8 * (1.0 0.2 * np.sin(2 * np.pi * t)) # 比例增益 gamma_D 0.15 * (1.0 0.3 * np.sin(4 * np.pi * t)) # 微分增益 # 初始输入为零从第一轮试验开始学 u np.zeros(N - 1) x0 np.zeros(2) rms_list [] peak_list [] err_last None for trial in range(30): y run_trial(u, x0) e yd - y de np.gradient(e, dt) # 开环更新只依赖上一轮误差 corr gamma_P * e gamma_D * de u u corr[:-1] # 输入序列长度 N-1去掉最后一个采样点 rms np.sqrt(np.mean(e**2)) peak np.max(np.abs(e)) rms_list.append(rms) peak_list.append(peak) err_last e.copy()这段代码里有两个容易弄错的地方。第一输入序列 u 的长度是 N-1因为最后一个控制步从 t (N-2)dt 作用到 (N-1)dt不需要为最终时刻再给定输入误差 e 是全长度 N 的所以做修正时要 corr[:-1]。第二np.gradient 求的是数值导数如果 dt 不够小误差峰值附近会出现明显的高频噪声后面避坑章节会专门说。增益 gamma_P 和 gamma_D 都带时间变化项这就是从普通 PD 型向 TPD 型过渡的写法。在仿真初期我建议先在代码里把增益数组改成常数跑一遍看到误差收敛后再引入时间变化这样能把问题隔离成“模型问题”和“增益设计问题”避免一起排错。3.3 从误差曲线判断收敛RMS 和峰值误差不能只看其中一个开环 ILC 仿真的输出通常画三条曲线误差 RMS 随迭代次数变化、误差峰值随迭代次数变化、以及其中某几轮试验的误差轨迹叠加。绘图代码# 输出曲线 plt.figure(figsize(8, 4)) plt.subplot(1, 2, 1) plt.plot(range(len(rms_list)), rms_list, markero, labelRMS) plt.plot(range(len(peak_list)), peak_list, markerx, labelPeak) plt.xlabel(Trial) plt.ylabel(Error) plt.legend() plt.yscale(log) plt.subplot(1, 2, 2) # 绘制第 1、5、20 轮误差轨迹 for trial_show in [1, 5, 20]: u_show np.zeros(N - 1) for i in range(trial_show): y_show run_trial(u_show, x0) e_show yd - y_show de_show np.gradient(e_show, dt) u_show u_show (gamma_P * e_show gamma_D * de_show)[:-1] plt.plot(t[:-1], e_show[:-1], labelftrial {trial_show}) plt.xlabel(time (s)) plt.ylabel(error) plt.legend() plt.tight_layout()判断是否收敛我习惯用两个条件同时看。RMS 下降到原来的 1/100 甚至更低说明整体跟踪误差被压下去了峰值误差也要小于轨迹幅值的 5%因为很多工程关心的其实是最大偏差而不是整条轨迹的平均偏差。峰值误差如果迟迟不降通常指示在某个特定时间点上有滞后或振荡这时去误差曲线上找尖峰位置就能确定是运动段的哪一部分出现问题。还有一个经验ILC 的误差曲线是带记忆的前几轮可能上升再下降。如果第 1 轮误差很大并不意味着方案失败先跑 10 轮再判断方向。真正有问题的是“降了几轮后停住不再动”——那就是增益或者控制器的频带不够。4. 和 PSO、强化学习画在同一张图时横轴迭代次数怎么对齐4.1 三种算法的“一次迭代”完全不是一回事群里最常遇到的问题就是 PSO 一类群体优化算法和强化学习放在一起做对比实验画收敛曲线时发现横轴单位对不上。PSO 的“一次迭代”是种群一代RL 的“一次迭代”是 episodeILC 的“一次迭代”是 trial。三者表面上都叫迭代但样本成本完全不同。对 ILC 来说一次 trial 就是对完整参考轨迹从头到尾跑一遍得到一条误差轨迹对 PSO 来说一次迭代要评估整个种群比如 30 个粒子就是 30 次完整轨迹评估对强化学习来说一个 episode 通常也是一轮完整交互但训练过程中还可能包含经验回放采样、策略更新等额外开销。算法横轴单位一次迭代实际次数总评估量跨度开环 ILCtrial1 条轨迹20 ~ 100 次PSOgeneration种群大小条轨迹10^3 ~ 10^4 次强化学习episode1 条交互轨迹10^3 ~ 10^6 次所以直接把三种算法各自的迭代次数画到同一个横轴上不光数值不对连曲线的含义都站不住。ILC 往往 50 次 trial 内就收敛而 PPO 可能 3000 个 episode 还在上升一张图里一边撑到 5000一边在 50 处早早贴着底线看上去确实很“难看”。4.2 统一到“总轨迹评估次数”并给每个算法设置独立停止条件我常用的做法是把横轴统一成“轨迹评估次数”而不是“算法迭代次数”。对 ILC评估次数等于 trial 数对 PSO评估次数等于“种群大小乘以代数”对强化学习评估次数等于用于计算收敛指标的完整 episode 数。具体操作分三步。第一步先为每种算法单独跑预实验确定它的大致收敛区间ILC 50 轮能收敛PSO 40 代 * 30 个体等于 1200 次评估RL 大概 4000 个 episode 开始平稳。第二步定总预算一般取最慢方法收敛评估数的 1.5 到 2 倍比如 RL 需要 4000预算就给 6000 或 8000。第三步是每个算法都记录“评估次数”和“当前误差指标”两条序列曲线绘制时以评估次数为横轴并且每类算法自身的指标都做相同平滑处理。参考代码def check_convergence(history, rel_tol0.05, patience10): if len(history) 2 * patience: return False recent history[-patience:] before history[-2 * patience:-patience] mean_recent np.mean(recent) mean_before np.mean(before) if abs(mean_recent) 1e-10: return True return abs(mean_recent - mean_before) / abs(mean_recent) rel_tol这个停止条件的意思是连续 10 个评估点的指标平均值相比前 10 个点没有明显下降就认为算法已经收敛。不同算法收敛所需的评估次数往往差出两个数量级这是它们的真实特性不需要强行拉齐终点。如果还是希望所有曲线终点都落在同一个横轴位置可以把每个算法自己的预算作为终点在图中注明各自的预算值比如标注“ILC: 60 trials, PSO: 2400 evals, PPO: 4000 episodes”。这样读者看到的是在同一张图中比较样本效率而不是假装三种算法迭代次数单位一样。4.3 收敛曲线绘制中的三个常见翻车点早停、抖动与差异被掩盖第一个常见坑是早停条件不一致。ILC 的误差下降非常快时如果只设了一个很低的误差阈值可能在 20 轮就停而强化学习如果也按同样的误差阈值判断几千个 episode 都停不下来。这样画出来的曲线会呈现“ILC 早早收敛RL 永远在跑”但真正原因是停止条件设置不统一。我的做法是给每个算法使用相同的阈值和相同的耐心窗口比如都采用上面的 check_convergence并设置相同的最大预算。第二个坑是抖动曲线盖住趋势。RL 的单 episode 回报通常震荡很大直接画出来是一团毛刺ILC 的误差下降也可能在前几轮有波动。如果横轴拉长到几千ILC 那条 50 步内就到底的曲线会被压成一条竖线完全看不出收敛过程。解决办法是横轴改用对数刻度或者把 RL 指标做滑动平均再用半透明置信区间包起来。第三个坑是比较指标选错导致两条曲线的差异被掩盖。如果只看最终单次运行误差PSO 可能 2000 次评估后误差接近 ILCS 的 60 次结果但分布情况完全看不到。我会额外画“达到指定误差阈值所需的评估次数”柱状图每种算法重复 5 到 10 次取中位数和四分位距这个图比单纯收敛曲线更能说明样本效率。收敛曲线只能回答“降得多快”阈值命中图才能回答“多可靠”。5. 开环 ILC 仿真避坑与排错五条能直接上手的检查清单5.1 误差停在某个平台瓶颈先查 D 增益和误差求导是否引入高频噪声现象是误差曲线前几轮快速下降到第 8 轮左右停住RMS 下降率明显变缓甚至轻微反弹误差曲线上能看到细密的锯齿。原因是γ_D 增益对误差导数项过于敏感数值梯度把参考轨迹的高频分量和测量噪声同时放大了。开环 ILC 会把上一轮噪声学进下一轮输入积累到一定程度形成一个“噪声底”就压不下去。解决方法是先把 γ_D(t) 整体缩小或者给误差导数加一阶低通滤波。一种常见改法是先用误差平滑后的信号计算导数代码里用from scipy.signal import butter, filtfilt b, a butter(2, 0.2) e_smooth filtfilt(b, a, e) de np.gradient(e_smooth, dt)如果误差仍然停在平台上再把 dt 减小到 0.5ms确认是采样分辨率的问题还是增益问题。检查顺序先降微分增益再滤波最后才加密采样。5.2 末端总有个尖峰收不下去非因果更新和参考边界条件不匹配现象是其他时间段的误差都降了但轨迹末端最后 10ms 始终有个尖峰而且随迭代次数增加尖峰不消失有时还会一点点变宽。原因是开环 ILC 用整条误差轨迹做更新等于用 t 时刻附近误差去修正整条输入序列本质上带有预测性。参考轨迹如果末端加速度不为零输入需要提供对应的末端力矩而受控对象的动态响应滞后会让末端出现“换向尖峰”。另一个常见来源是初始的 u(N-1) 没有对应的误差数据导致末端积分不闭合。解决方法是把参考轨迹设计成末端速度和加速度都严格归零比如代码里用的五次多项式就是一种可行设计同时对学习修正做边界截断不让最后一个采样点把极端值扩展到整段。检查时看最后一个采样点的误差和倒数的误差如果一直是同一符号就是参考边界条件没闭合。5.3 迭代几十轮后误差突然反弹忽略遗忘因子和频带边界现象是前面 30 轮 RMS 一直下降第 35 轮开始突然回升而且回升幅度明显不是噪声抖动。原因是开环 ILC 的输入在迭代中不断累积修正量遇到被控对象建模误差或者非线性扰动时高频成分会有累积一旦越过收敛条件里对频率带的要求误差就从收敛变成发散。很多线性分析只保证“小误差附近收敛”不保证“无限累积后还收敛”。解决方法是在更新公式里加入遗忘因子 β常见形式是u_{k1}(t) (1-β) * u_k(t) γ_P(t) * e_k(t) γ_D(t) * ė_k(t)β 通常取 0.05 到 0.2。遗忘因子让输入不再无限累积旧信息按比例淘汰对抑制长期漂移非常有效。代价是最终误差无法降到理论最小值因为算法不再保留全部历史信息。工程上这是一个必要取舍我一般从 β0.1 开始观察 50 轮误差曲线上是否还有长期回升趋势。5.4 换成 ode 积分器后前面迭代白跑采样网格与插值方法不一致现象是用 scipy.integrate.solve_ivp 或 MATLAB 的 ode45 替换简单欧拉积分后原本收敛的误差曲线突然振荡甚至第一轮误差就比原来大。原因是变步长积分器在每个 trial 返回的时间点不完全一致参考轨迹和误差轨迹要重采样同一套时间网格同时被控对象的相对阶在连续时间仿真和高散化仿真里表现不同而数值导数又对采样点位置极其敏感。开环 ILC 把时间轴当成对齐的格子来学习只要格子对不上积累修正量就会互相抵消。解决方法是固定同一个采样网格并在每个 trial 开始时从那个网格上重新插值参考轨迹。如果可以先用定步长仿真验证算法再迁移到 ode 求解器。ILC 的学习本质上依赖“重复任务中的时间对齐”时间不对齐是这个算法最忌的事。5.5 初始状态和参考起点对不上ILC 从第一轮就在学错误的东西现象是ILC 迭代过程中误差在前几十毫秒总是很大RMS 降到一定程度就停住无论怎么调增益都压不到更低水平。原因是初始状态和参考轨迹起点不一致比如参考起点是 0 但系统状态初始值是 [0.1, 0]这时第一轮误差本身就带着初始偏移ILC 会试图在输入上产生一个大脉冲去补偿这个偏移。由于被控对象动态响应需要时间这个脉冲很难被轨迹末端吸收掉形成“前段完全靠输入硬扳”的失真轨迹。解决方法是在每次试验前把系统状态复位到参考轨迹对应的初始状态或者在误差计算中将前几拍强制忽略掉等系统先进入参考轨迹的邻域再开始学习。实际实现时我在代码里检查第一点和参考第一点的差if np.abs(yd[0] - y[0]) 1e-6: x0 np.array([yd[0], 0.0])注意这样做的本质是换了一个“任务初始条件”必须保证所有对比算法使用同样的初始条件否则互相比较时数据不公平。6. 判断一套开环 ILC 方案值不值得用的三个验证手段先看它在纯仿真里到底能不能稳定收敛再看它扛不扛扰动最后看它换一条轨迹后会不会“过拟合”。这三个验证做完基本就知道值不值得投进实际设备。第一个手段是标准化收敛性验证固定同一参考轨迹跑三组不同的初始误差各组都连续迭代 100 轮。画 RMS 曲线看是否都单调下降到同一水平。如果三条曲线最终停在相差较大的水平说明存在多收敛点或者模型不确定性影响显著这时不要急着上设备。范数条件也好、谱分析也好最后都要落在这三条重复试验曲线是否稳定上。第二个手段是鲁棒性验证在每一轮 trial 的输入或状态上注入小幅随机扰动例如给控制输入加一个 0.02 幅值的高斯噪声连续跑 30 轮。开环 ILC 对噪声的学习行为直接反映的是增益是否过于激进。如果噪声注入后误差曲线明显变粗说明 γ_D(t) 和 γ_P(t) 仍偏大如果曲线快速回到正常水平说明学习律有自我纠正能力这个系统会更接近实际工况。第三个手段是泛化能力验证把参考轨迹从幅值 1 改成幅值 1.2 但保持相同时间长度继续从已有的输入序列开始迭代 20 轮观察初始误差是否很大以及能否快速收敛。开环 ILC 本质是“记住任务细节”不是“学到一个控制器”所以换轨迹后必然需要一个重新学习的过程。遗忘因子和 TPD 增益能让这个过程更快但不会做到立刻无误差。我习惯把换轨迹后的前 10 轮误差也画出来如果第 10 轮能把误差降到同量级说明学习结构合理值得继续做如果 10 轮后依然比原轨迹高一个量级说明增益设计过度贴合原任务需要降低学习率。在这些验证之外还有一个我坚持的习惯每次试验结果都存成一个带时间戳的 CSV 文件保存本轮使用的 γ_P(t)、γ_D(t)、β 和遗忘因子版本。因为 ILC 的收敛结果严重依赖参数参数稍微改了一点就不容易复现这一步虽不起眼却能在调参调烦时当真后悔药。希望这条经验在你跑 TPDILC 时也能省一点返工时间希望帮到你。本文还有配套的精品资源点击获取