ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

稀疏自适应逐级正交匹配追踪在大地电磁数据降噪中的应用

稀疏自适应逐级正交匹配追踪在大地电磁数据降噪中的应用 大地电磁测深MT这行干过野外采集的都知道最头疼的不是仪器架设而是后期数据里那一堆说不清道不明的干扰。天然场源信号本身就弱再加上工频谐波、人文噪声、随机尖脉冲实测时间序列经常惨不忍睹。我这些年用MATLAB R2018A捣鼓了一套基于稀疏自适应逐级正交匹配追踪的大地电磁数据降噪方法实测效果比传统滤波稳得多。这篇文章就把这套方法的来龙去脉、核心原理、MATLAB实现细节和踩过的坑一次说清楚搞MT数据处理或者做信号去噪方向的朋友应该都能从中拿走点直接能用的东西。先交代下背景。这套方法不只是针对某一个测区的某一段数据而是我在多个大地电磁测深项目里反复迭代、调试后沉淀下来的一个通用处理流程。它解决的问题很明确在强噪声背景下从实测时间序列里稳健地恢复有效大地电磁信号最终改善视电阻率和相位的估算质量。跟经典的数字滤波、小波阈值去噪相比稀疏分解的思路完全不同——它不假设噪声是平稳的也不靠简单的频带分离而是把信号放到一个过完备字典下做稀疏表示再通过匹配追踪类算法逐级逼近把有效信号的能量“拎”出来。1. 内容整体设计与思路拆解1.1 大地电磁数据噪声的特点与降噪困境先说点题外话。大地电磁测深测量的是天然电磁场随时间的变化频带范围很宽从几十赫兹到几十千赫兹不同频段的信号对应不同探测深度。但天然场源信号并不是稳定的太阳活动、雷电活动都会影响场源强度到了人文干扰严重的地区比如城镇、矿区、输电线路附近采集到的数据信噪比可能低到难以直视。常见的干扰形态有几种一种是规则性的比如50Hz工频及其谐波特点是频率固定、幅度稳定用陷波器就能干掉一大半另一种是偶发性的强脉冲比如附近有大功率设备启停、车辆通过会在时间序列上形成大幅度的尖峰或阶跃还有一种是低频谱段的严重漂移比如电极接地条件变化、磁传感器受震动等产生的长周期扰动。这些干扰叠加在一起最大的问题是不具备传统滤波方法要求的先验条件——不是平稳高斯噪声也没有固定频带甚至有时候干扰和有效信号在频域是重叠的。我最早做MT数据降噪时用的是一套组合拳先手工去除飞点再做FFT滤波最后用Robust估算阻抗。这套流程在噪声较弱时还能应付一旦遇到强干扰滤波后视电阻率曲线会严重畸变特别是高频段和中频段衔接的地方经常出现“跳点”。后来尝试小波阈值去噪效果有好转但小波基和分解层数需要人工试凑而且硬阈值容易产生伪吉布斯振荡软阈值又会把有效信号压制得过多。核心痛点在于大地电磁信号和噪声在变换域里的特征并不容易用固定规则来刻画。与其费劲去设计一个“通用滤波器”不如换个思路——把信号表示成少数基函数的组合然后从原子集合里去找出那些真正与有效信号匹配的成分。这就引出了稀疏表示与匹配追踪家族的方法。1.2 稀疏表示的基本逻辑为什么能分离噪声稀疏表示的基本假设很直白一段信号可以用字典里的少数原子加权求和来近似而且这些原子的数量远小于信号长度。想象一个书架你要描述书架上的书与其把每本书都记下来不如只记“红楼梦”“金庸全集”“考研英语真题”这几类代表性的名字就能大致还原书架的构成。对于大地电磁信号有效信号在合适的字典下通常是稀疏的——比如在傅里叶字典下天然场源信号主要能量集中在少数频率成分上在小波字典或曲波字典下信号的大系数个数也不多。而干扰噪声呢它往往在字典下表现出“到处都有小尾巴”的特征系数不稀疏或者说稀疏度很差。这样一来只要我们能找到有效信号对应的那少数几个大系数忽略掉噪声对应的零碎系数再重建信号就把有效成分和噪声分开了。这个过程用数学语言说就是观测信号 y Dx n其中 D 是过完备字典矩阵x 是系数向量n 是噪声。我们希望找到一个稀疏的 x使得 Dx 尽量逼近 y。为什么要“过完备”字典因为字典越丰富越能贴近真实信号的多样形态但代价是 x 的解不唯一必须加上稀疏性约束才能确定一个合理的解。匹配追踪类算法就是干这个事的——它不追求一次性求出全局最优解而是每次选择字典里最相关的原子逐步逼近。1.3 自适应逐级正交匹配追踪相比普通OMP的优势匹配追踪MP和正交匹配追踪OMP是稀疏表示里最经典的贪心算法。OMP的核心逻辑是每次迭代选一个与残差最相关的字典原子然后做最小二乘更新迭代直到残差足够小。但OMP有个很明显的短板要求你预先知道稀疏度K也就是有效信号大概由多少个原子组成。这个K在很多实际问题里根本没法预估。我用OMP处理MT数据时就遇到过这种情况——K设小了信号细节被砍掉重建结果过于光滑K设大了后边几步把噪声原子也选进来了等于白做。逐级正交匹配追踪StOMP做了改进每一轮不是只选一个原子而是根据阈值把一批相关系数超过阈值的原子全部选进来逐级逼近信号。这样做的好处是收敛速度快、对参数不那么敏感。但StOMP的阈值通常也是人工设定的固定阈值在实际数据上适应性不足。“自适应”是我的核心改动思路不再人为设定固定的稀疏度或者固定阈值而是根据每轮迭代时残差的统计特性和原子相关系数的分布动态调整阈值让算法自己决定这一步该选多少个原子、什么时候停。我在MATLAB R2018A里实现的主要工作就是把这种自适应策略嵌入到逐级正交匹配追踪的迭代框架里并针对大地电磁数据做了字典构造和停止准则的优化。2. 核心技术细节与算法实现要点2.1 字典构建大字典的选型与实际操作稀疏分解的第一步是构造字典。字典的选择直接决定了算法能“看见”什么样的信号特征。我最初用的是单一的傅里叶字典也就是把正弦和余弦函数离散化后排列成矩阵。对于大地电磁信号傅里叶字典的优势是物理意义清晰、和MT勘探中频带分析的习惯一致缺点是对非平稳的尖峰类信号表示效率不高——尖峰在傅里叶字典下需要很多个原子才能凑出来。后来我在实践中做了混合字典一部分是傅里叶原子覆盖从低频到高频的宽频带另一部分是短时脉冲原子也就是在不同时刻出现的窄脉冲波形专门用来匹配突发干扰。这种做法类似工具箱里既有螺丝刀又有扳手面对不同类型的问题可以选不同的工具。不过字典的原子数量也不能盲目扩大。过完备字典的尺寸如果过大计算量会急剧上升而且原子之间相关性强算法更容易混淆原子选出错误组合。实际操作中我会把时间序列先切段每段12800个采样点。傅里叶原子的频率间隔按照分辨率要求来定一般是采样频率除以段长度脉冲原子则设置几十个候选时间位置脉冲宽度覆盖从几个采样点到几十个采样点的范围。这样构造出来的字典矩阵行数为12800列数大概在13000左右算是“刚过完备”——既保留了足够的表示能力又不至于把计算负担推到无法接受的程度。2.2 自适应策略的数学表达与参数选择逻辑自适应策略体现在两个环节原子选择阈值和迭代停止准则。原子选择环节我把每轮迭代的候选相关系数 z D^T r 拿出来其中 r 是上一步残差。普通StOMP用的是固定阈值比如 z 的标准差乘以一个系数。我的做法是动态估计噪声水平取当前残差在高频段的能量中位数作为噪声方差估计值再乘上一个可调节的置信因子β得到本轮阈值。这样做的好处是在处理强干扰时初始阶段残差能量很大阈值也会相应抬高避免一开始就把噪声原子搜进来而随着迭代进行残差逐渐减小阈值也跟着下降能捕捉到更微弱但有效的信号原子。迭代停止准则方面我不建议只设一个固定条件。我用了双重判断第一重是残差能量比也就是 norm(residual) / norm(y) 小于某个阈值比如0.01说明重建已经足够逼近原信号第二重是连续两轮选出来的原子集合没有变化说明算法已经收敛继续迭代只是重复劳动甚至引入噪声原子。两个条件只要满足任何一个就停止迭代。这里面最容易被忽视的是β系数的调整。β设得太大每轮选入的原子太少迭代次数大幅增加还可能漏掉弱信号β设得太小每轮选入太多原子名义上是稀疏分解实际退化成了普通的最小二乘拟合失去稀疏正则约束。我调试下来β在0.5到1.5之间比较合理具体取值取决于数据信噪比。信噪比低时用小一点的β让算法多选几次逐步抠出有效成分信噪比高时用大一点的β快速收敛。2.3 MATLAB R2018A关键函数与代码框架之所以强调MATLAB R2018A是因为很多高校和科研院所目前还在用这个版本而且R2018A本身在矩阵运算和信号处理工具箱上已经相当成熟并不需要追求最新版。实现这套算法用到的核心函数其实很基础矩阵乘法、排序函数sort、集合操作unique和setdiff、最小二乘的 \ 运算以及并行计算工具箱里的parfor做批量段处理。下面这版代码是简化后的核心迭代逻辑保留了自适应逐级正交匹配追踪的主干。实际使用中我还会加入断点续算、进度打印、段与段之间的重叠拼接处理但核心过程就是下面这个循环。function [x_rec, idx, iter] adaptive_stomp(y, Dict, beta, tol, maxIter) % y : 观测信号向量列向量 % Dict : 过完备字典矩阵每一列是一个原子 % beta : 自适应阈值置信因子 % tol : 残差能量比停止阈值 % maxIter : 最大迭代次数 y y(:); [~, N] size(Dict); r y; % 初始残差 idx []; % 已选原子索引 x_approx zeros(N, 1); nrm_y norm(y); for iter 1:maxIter % 计算所有原子与残差的相关性 corr Dict * r; % 自适应阈值基于高频段残差估计噪声水平 % 这里用残差标准差作为噪声水平的快速估计 noise_level std(r); thr beta * noise_level; % 候选原子相关性超过阈值的排除已选过的 cand find(abs(corr) thr); cand setdiff(cand, idx); if isempty(cand) break; end % 选择候选原子中相关性最强的若干个最多取5个控制增速 [~, sortIdx] sort(abs(corr(cand)), descend); n_choose min(length(cand), 5); newIdx cand(sortIdx(1:n_choose)); idx union(idx, newIdx); % 最小二乘更新在已选原子张成的子空间上投影 tmp zeros(N, 1); tmp(idx) Dict(:, idx) \ y; r y - Dict * tmp; % 收敛判断残差能量比足够小或原子集合不再变化 if norm(r) / nrm_y tol x_approx tmp; break; end if length(idx) N break; end end x_rec x_approx; endR2018A里有个小坑要注意Dict(:, idx) \ y 这条命令对内存的要求不低如果 idx 的个数到了几百个每一次最小二乘更新都要重新求解一次大型方程组。我的优化做法是把已选原子预先做一次QR分解每次加入新原子时只更新QR分解而不是从头解方程组。R2018A对稀疏矩阵的运算支持已经不错但这里用普通稠密矩阵反而更稳因为字典原子通常不具备可压缩的稀疏结构。2.4 预处理与后处理被忽视的关键环节算法本身的流程只是中游环节。我在实际项目中反复体会到输入数据的预处理和输出重建结果的后处理往往对最终阻抗估算的影响比算法迭代本身还大。预处理阶段我推荐按以下顺序来第一步是去均值去趋势这一步看似简单但必须做干净因为大地电磁信号中周期成分非常长如果趋势项没有去除干净低频段的重建误差会被放大。第二步是剔除飞点幅度超过中位数若干倍的采样点直接置为异常值用前后窗的中值填充。第三步是将数据分段每段之间保留50%重叠防止分段边界处的重建断层。去均值我用的是detrend函数配合分段局部均值移除比直接减整段均值要可靠。后处理阶段核心问题是重建信号不能直接拿去做阻抗估算原因在于重建过程可能损失了部分高频微弱能量。我会用一个混合策略重建信号与原始信号的残差中再提取那些与有效信号高度相关的成分然后按信噪比加权合并。这有点像一个二阶段处理先粗分离再细修正。实际测试中这种后处理能让高频段的视电阻率曲线明显更平滑。3. 实操过程与核心环节验证3.1 数据集准备与评价指标设计要验证降噪方法的效果首先得有“标尺”。大地电磁实测数据的“真值”是一个难题——你根本不知道没有噪声的信号长什么样。我用的策略是双轨验证第一轨是模拟数据在已知理论模型的正演响应上叠加实测噪声这样有干净的参考信号可以计算定量指标第二轨是实际测区数据通过与标准站点的阻抗曲线对比看降噪后曲线的平滑度和一致性。模拟数据构造时我叠加的噪声不是白噪声而是从实测噪声库抽取的真实干扰片段包括尖峰、方波、工频谐波漂移等。这样算法处理模拟数据的表现更接近实际数据处理时的情况。评价指标上我主要看三个信噪比提升量降噪后与降噪前相比目标频带内信号功率与噪声功率之比的变化。重建信号与参考信号的相关系数越高说明波形保真度越好。阻抗曲线估算质量与理论响应相比视电阻率曲线的均方根误差和相位曲线的抖动程度。只盯一个指标容易出问题。我在调试过程中就犯过这个毛病——只看信噪比结果算法把有效信号的高频细节滤得干干净净信噪比上去了但视电阻率曲线变得过于平滑反而损失了地质信息。所以三个指标必须同时看特别要保留相关系数这个波形保真度指标。3.2 实测数据处理流程详解实测数据处理的完整流程我按下面的步骤组织第一步读取原始时间序列把电场和磁场各分量按通道分开。大地电磁数据通常是多通道同步采集的比如Ex、Ey、Hx、Hy四个主要通道有时还包括Hz。降噪处理不能只处理单个通道需要多通道联合考虑特别是磁场通道的噪声会通过阻抗估算传递到视电阻率中。第二步对每个通道做统一的预处理去趋势、去飞点、分段加窗。这里有个同步问题所有通道的数据段必须对齐否则后续做互功率谱估算时相位会错乱。我习惯把分段的时标信息保存下来确保后续算法处理的是同一时间窗口的数据。第三步采用自适应StOMP算法对每个通道的每个数据段分别做稀疏分解和重建。这一步可以并行处理MATLAB R2018A的parfor在这里能明显提速。需要注意重建时字典和参数在所有通道间要一致避免引入通道间的系统差异。第四步将重建后的干净时间序列重新拼接形成完整的降噪后时间序列。拼接时对重叠区域做线性交叉淡化保证边界连续。最后把降噪后的时间序列送入阻抗估算模块。我用的Robust估算方法是重加权最小二乘迭代加上M估计器对残留的少量异常值有一定抵抗力。这样一套流程走下来单测区几十个站点的数据在普通四核台式机上处理时间可以控制在几十分钟以内。如果需要批处理上百个站点建议在数据读取环节用matfile对象做部分加载避免一次性读入过大文件导致内存溢出。3.3 关键参数调试记录与效果对比参数调试是最费时间也是最考验经验的环节。我把调试过程记录一下各位参考时可以少走弯路。先调的是字典的原子数。我把傅里叶原子数量从5000逐步增加到8000再增加到10000发现视电阻率曲线在中高频段的平滑度逐步改善但超过10000以后改善几乎停滞计算时间却线性上涨。所以对12800点数据段我最终把字典列数控制在13000左右。然后是自适应阈值置信因子β。我在三组不同噪声强度的实测数据上测试β0.8时信噪比提升量中等但波形相关系数最高说明信号保真度最好β1.5时信噪比提升最大但波形相关系数下降约3个百分点低频段出现了轻微伪影。综合下来我选了β1.0作为默认值特殊数据再加微调。停止阈值tol的影响也很有意思。tol0.05时算法只捕捉信号的主要能量重建信号偏平滑阻抗曲线高频段过于平稳tol0.001时算法几乎把原信号全部重建噪声也保留了大量降噪效果趋近于无。我最终选了0.01作为默认实际测试中它在噪声压制和信号保留之间平衡得最好。对比传统方法的数据我心里有数在信噪比为-5dB的实测数据上传统FFT低通滤波后的视电阻率曲线在100Hz附近出现明显的畸变而自适应StOMP处理后的曲线几乎与高信噪比参考曲线重合。4. 扩展应用场景与经验迁移4.1 从大地电磁到其他地球物理信号这套方法的适用面不止大地电磁数据。我在实际项目中尝试过把它迁移到地震数据处理和微震监测信号处理上效果同样可观。地震数据的有效信号有效波在时间域是稀疏的——地层反射波在时间轴上表现为有限数量的同相轴而随机干扰在地震记录中到处都是。用混合字典做稀疏分解可以很自然地区分有效波和噪声。我拿它处理一段含强面波的浅层地震数据面波压制效果比传统的f-k滤波更干净而且不损伤反射波的同相轴连续性。这背后的逻辑是一致的只要信号的稀疏形态能通过字典描述匹配追踪就能把噪声剔除出去。处理地震数据时字典的构造需要改变原子不能再是频域的正弦波而是Ricker子波、Klauder子波等地震勘探领域常用的子波形态。R2018A中可以通过函数自定义这些子波再排列成字典矩阵算法主体不需要改动。参数调整上地震数据的采样率通常比MT高得多字典原子数量需要相应增大但核心的自适应阈值策略仍然适用。4.2 向机械振动与语音信号方向的扩展机械振动信号的故障诊断也是稀疏表示的重要应用场景。滚动轴承和齿轮箱的故障特征通常表现为周期性冲击脉冲在时域上是稀疏的。我用这套自适应StOMP方法处理过一段含故障脉冲的振动信号在强背景噪声中成功提取出了冲击序列后续包络谱分析能清晰看到故障特征频率。这个场景下字典构造特别讲究故障冲击是一种指数衰减振荡形态我构造字典时加入了阻尼衰减原子每一列是一个不同阻尼比、不同振荡频率、不同起始时刻的衰减振荡波形。相比傅里叶字典这种形态普适的字典对机械冲击信号的稀疏度能提高好几倍算法选取的原子更集中重建信号也更干净。语音信号去噪则是个更贴近日常的场景。语音在短时傅里叶变换域具有明显的稀疏性特别对于清音和浊音段能量集中在少数时频单元。把自适应StOMP用在语音端点检测和去噪上对非平稳噪声比如键盘敲击声、关门声的抑制效果比传统谱减法好失真也更小。区别在于语音信号处理对实时性要求高MATLAB脚本可能不够快这时候可以考虑用R2018A的MATLAB Coder把核心函数转成C代码嵌入到实时处理链路里。4.3 后续可扩展的亮点方向还有个值得探索的方向是深度字典学习。我的自适应StOMP目前用的字典是人工构造的虽然已经能覆盖大部分信号形态但终究受限于我的先验设计。如果能把字典本身也变成可学习的——让算法根据数据自身特点自适应调整原子形态稀疏表达的效果理论上还能上一个台阶。R2018A虽然深度学习的工具箱已经比较成熟但字典学习这种东西更适合用优化方式实现函数fminunc和多目标优化工具箱都能帮上忙。另一个方向上多通道联合降噪值得尝试。目前各通道是独立处理的但大地电磁各分量之间存在固有的物理约束关系。如果把多通道数据拼接成一个整体向量约束各通道共享稀疏模式理论上可以提升信噪比压制效果。代价是字典矩阵规模指数增长计算量很可观。后续如果条件允许我计划用GPU并行计算来缓解这个问题。5. 常见问题与排查技巧实录5.1 算法不收敛或迭代停滞怎么办调试期间遇到最多的问题就是迭代不收敛。我遇到过这样一次处理某测区数据时算法大概在十几轮迭代后就开始原地踏步残差不再下降原子集合也不更新但此时残差能量比还远没到停止阈值。排查后发现原因在阈值设置上。那组数据的噪声里包含一段低频强漂移我的噪声水平估计用的是std(r)但这个值被低频漂移污染了导致阈值偏高候选原子为空算法提前退出。解决办法是把噪声水平估计方法改成中位数绝对偏差MAD它对异常值更稳健不会因为个别大幅值导致阈值失真。我在R2018A里用mad(r,1)函数实现改动很小但效果立竿见影。另一种停滞原因是字典原子之间的强相关性。当字典中有多个原子波形几乎一样时算法可能选了其中一个原子后后续迭代里找到的相关性最强的候选原子与已选原子几乎共线导致残差下降极小。解决办法是加入原子预筛选在算法开始前对字典做一次相关性分析把互相关系数超过0.95的原子剔除掉或合并掉。5.2 过拟合问题噪声被当成信号重建这也是个容易踩的坑。信噪比高的时候算法表现很好信噪比一旦低到一定程度某些噪声样本可能碰巧与字典中的某个原子高度相关算法就会把它当成有效信号选中。表现出来就是重建信号里残留有噪声小脉冲降噪效果反而变差。我用的应对策略有两道防线。第一道是限制每轮迭代选入的原子数默认最多选5个宁可多迭代几轮也不要一轮贪多引入噪声原子。第二道是加入原子“质量门控”选出来的原子不仅要在残差上相关性高还要在原始信号上有足够的能量占比。一个原子在残差上相关性高但在原始信号上能量很低大概率是噪声原子。后处理阶段还有个补救手段重建完成后把重建信号和原始信号做差得到残差信号。如果残差信号中仍有明显的脉冲形态可以再用较小的β跑一轮稀疏分解把有效成分二次提取出来。5.3 内存不足与计算速度优化方案R2018A的矩阵运算对内存要求比较高我最初在64位Windows系统上跑16GB内存的机器处理长序列数据时经常报内存不足错误。原因是字典矩阵是稠密矩阵12800行×13000列的双精度矩阵占内存约1.3GB再加上残差向量、相关性结果、临时投影计算总内存占用轻松到5GB以上。优化思路有三个方向。第一个方向是数据分段处理把长序列切成短段逐段处理段与段之间用重叠拼接保证连续性这是最简单有效的办法。第二个方向是减小字典尺寸我实验发现用PCA对字典做降维把13000列压到6000列左右重建效果基本不变但内存占用减少一半以上。第三个方向是结合R2018A的并行计算工具箱用parfor把多通道、多段数据的处理分发给多个worker既加速又分摊内存压力。MATLAB R2018A的parfor在使用时有个限制循环体内的随机数生成器需要显式管理否则不同worker之间会产生相同的结果。虽然这个例子不涉及随机数但如果后续加入噪声扰动测试之类的功能需要特别注意。5.4 版本兼容性与函数可用性排查最后提醒一下版本问题。虽然在R2018A上调试成功但代码里有些函数在其他版本上表现不太一样。比如mad函数在R2018A里接收维度参数的方式直接影响输出数组的形状换到旧版本容易出错。另外矩阵的 \ 运算在R2020以后的版本中对某些稀疏矩阵会根据数据特征自动选择不同的分解方式导致结果有细微差异。解决办法是在代码开头统一指定坐标系和数据格式尽量减少对版本相关特性的依赖。如果项目后续要交付给别人在不同版本上运行建议将关键函数封装成独立文件并写下Requirements.txt风格的依赖清单标注工具箱的最低版本要求。我通常会在脚本开头加一段chkversion的验证逻辑确保核心工具箱存在再运行避免跑到一半才发现版本不对。6. 实操心得与建议这套方法从最初在MATLAB里写的第一版OMP代码到现在相对成熟的自适应StOMP处理流程中间改动了不下十版。每次改动都是一次对信号特征理解的加深。我最深刻的体会是算法框架是容易上手的真正的门槛在参数调试和字典构造上它们对实际问题特征的适配程度决定了最终效果。如果你现在正准备处理一批大地电磁数据我的建议是先别急着套算法。花半天时间手动浏览几段数据的形态搞清楚噪声的主要类型和分布特征然后再决定字典该怎么构造、阈值策略怎么定。这一步看起来笨拙但比盲目调参有效得多。拿到一组新数据时我会先用简单的频谱分析和时频图做快速诊断大体了解信噪比分布再启动自适应StOMP流程。另外一个很实用的经验是把中间结果可视化出来。每次迭代的残差能量曲线、选入原子在时频域的分布、重建信号和原始信号的对比图都可以在调试时帮你快速定位问题。我在R2018A的实时脚本中专门写了一个显示面板处理每个数据段时自动更新迭代状态图。这样处理异常数据时几分钟内就能判断出问题出在预处理、字典构造还是阈值设置上。这套方法目前已经在多个测区的数据上经过验证后续我打算把多通道联合降噪和字典学习纳入版本规划。如果各位在实际使用中遇到新问题欢迎多交流毕竟信号处理的乐趣就在于永远有新挑战。
返回列表