极限学习机ELM回归预测实战教程:MATLAB实现与调参技巧
发布时间:2026/9/28 5:39:41来源:尧图网络
做回归预测这么多年我越来越养成一个习惯拿到一组数据先用最朴素的模型跑一遍基线再考虑要不要上复杂的深度学习。这个习惯帮我避开了无数坑也让我把ELM极限学习机列为了小样本回归任务的首选工具之一。ELM 这名字听着有点学院派但它的思路其实非常直白——把传统 BP 神经网络最耗时的“迭代调整权重”这一步直接砍掉改成随机生成输入层权重再用数学方法一步算出输出权重。不需要反向传播不需要梯度下降训练时间以毫秒计精度却往往不输调参半天的 BP 网络更别说它天生适合小样本数据。这篇文章我就以 MATLAB 为工具从原理到代码到调参坑位把 ELM 回归预测这件事一次讲透适合正在做数据预测、传感器标定、故障诊断或学术仿真被小样本和调参折磨得不轻的读者参考。1. ELM 是什么为什么我为小样本回归选了极限学习机1.1 从 BP 网络的一个痛点说起我在早期做回归预测时用的还是最经典的 BP 神经网络。BP 的核心是“误差反向传播”本质上是把预测误差从输出层往输入层反推然后用梯度下降法反复调整每一层的权重让误差越来越小。想法很美好现实却很骨感。BP 网络有三个让人头疼的特质收敛速度慢梯度下降是一步步挪的数据量稍大或者网络稍深训练时间就从秒变成分钟再变成小时。容易陷入局部最优初始权重是随机的运气不好时训练就卡在某个局部极小值上精度死活上不去。超参数极其敏感学习率、动量因子、隐含层节点数、初始化方式每一项都要反复试调参的时间比写代码的时间还长。后来我接触到了一种叫“极限学习机”的网络最初看论文觉得它就是换了个训练策略的 BP但真正跑起来才发现它在原理层面就把 BP 的两个根本痛点给解决了——不再迭代调权而是随机生成权值后一步求解。1.2 ELM 的核心思想ELMExtreme Learning Machine极限学习机是南洋理工大学的黄广斌教授提出的单隐层前馈神经网络算法。它最核心的思想可以概括为三句话输入层到隐含层的权重和偏置是随机生成的且生成后固定不动。隐含层到输出层的权重通过最小二乘法更准确地说是摩尔-彭罗斯广义逆一次性解析求解。因此整个训练过程不需要迭代一次矩阵运算即可完成。这个“随机生成 解析求解”的思路是把“学习”问题变成了“解方程”问题。所谓的“极限”指的就是这种训练方式把速度推到了极限——没有迭代自然就没有“收敛”和“局部最优”的说法了。用生活化的类比来理解BP 网络像一个新手厨师反复尝菜、反复调整各种调料的用量直到味道接近目标ELM 则像一个按菜谱做菜的熟练师傅调料输入权重随机撒一把下去然后根据最终成品与目标之间的差距一步算出最合适的补救方案输出权重。前者慢但对撒料有要求后者快且只要补救得当你也能得到不错的结果。1.3 它和 BP、SVM、高斯过程回归的定位差异很多初学者问我既然有 SVM、高斯过程回归这些成熟模型为什么还要用 ELM我的回答是看场景。模型训练速度小样本表现调参成本适合场景BP 神经网络慢一般容易过拟合或欠拟合高依赖经验大数据量、非线性强、不急着出结果SVM支持向量机中等优秀中等核函数和 C、gamma 要调中低维小样本分类、回归高斯过程回归GPR中等偏慢很好自带不确定性估计中等小样本仿真数据预测、需要置信区间ELM极快良好可以通过正则化增强泛化极低只需定隐含层节点数和激活函数小样本快速建模、在线预测、嵌入式部署、对比基线ELM 最大的价值其实在于**“快”和“简单”**。当你面对一个全新的数据集需要快速验证特征有没有预测力或者需要一个可靠的基线模型时ELM 几乎是最好的选择。它 0.01 秒就能训练完你马上就知道数据可不可用、特征够不够强。而且 ELM 不是只能做单输出回归。它天然支持多输出也就是说你可以一次预测多个目标变量。这个特性在工程上非常实用比如同时预测设备的多个健康指标或者同时预测多个位置的温度场。这也是我后来在很多项目里一直保留 ELM 作为必选模型的原因。2. ELM 原理拆解它凭什么快而准2.1 网络结构与数学表达ELM 的网络结构非常简单只有三层输入层、隐含层通常是一层、输出层。以单输出回归为例假设输入特征是 (x \in R^m)隐含层有 L 个神经元输出是一个实数 (y)那么网络的前向计算可以写成[ y \sum_{j1}^{L} \beta_j \cdot g(a_j \cdot x b_j) ]各符号含义(a_j) 是第 j 个隐含层神经元的输入权重向量维度是 1 × m(b_j) 是第 j 个隐含层神经元的偏置(g(\cdot)) 是激活函数比如 sigmoid、tanh、relu(\beta_j) 是第 j 个隐含层神经元到输出层的权重。写成矩阵形式更清爽。对 N 个训练样本而言隐含层输出矩阵 H 的形状是 N × L其中第 i 行第 j 列的元素就是 (H_{ij} g(a_j \cdot x_i b_j))。于是 ELM 的训练目标变成解一个线性系统[ H \beta T ]其中 T 是目标输出向量N × 1。你发现没有一旦 (H) 矩阵确定剩下的就是一个标准的线性回归问题。2.2 为什么能用广义逆一步求解既然要解 (H\beta T)最自然的做法是两边同时左乘 (H^{-1})。但 H 通常不是方阵也不可逆。这时候就要请出摩尔-彭罗斯广义逆Moore-Penrose pseudoinverse记作 (H^)。最小范数最小二乘解可以直接写成[ \beta H^ T ]这个解有两个非常好的数学性质它是所有可行解中范数最小的这意味着输出权重趋向于较小的数值模型的泛化性天然比较好它同时是最小二乘意义下的最优解即能让训练误差达到最小。这两个性质决定了 ELM 不需要像 BP 那样迭代也不需要像正则化网络那样反复调正则系数。只要 H 矩阵算出来了β 就是一步矩阵乘法的功夫。在 MATLAB 里这一行代码就是beta pinv(H) * T;简洁粗暴。2.3 激活函数的选择逻辑ELM 对激活函数的要求比 BP 宽松得多——随机生成的权重配合非线性激活函数理论上只需要满足“非常数、分段连续”就能逼近任意连续函数。也就是说你想用 sigmoid、tanh、ReLU、甚至自定义的函数都可以不一定非要可导因为 ELM 压根不需要反向传播。实际项目里我的选型经验如下sigmoid逻辑函数最稳妥大多数情况表现均衡适合作为默认选择tanh输出范围是 (-1, 1)在目标值本身有正有负时表现更好ReLU在深层或数据量偏大时能提速但在部分小样本场景中会导致输出权重偏大泛化性略差RBF 高斯核把它当作隐含层神经元时ELM 实际上变成了 RBF 网络的变体在光滑函数逼近上效果很好。一句话总结没有绝对最好的激活函数只有最适合数据的激活函数。我的习惯是建一个函数句柄列表循环跑一遍对比 RMSE谁好就选谁反正 ELM 训练快全部试一遍也花不了几秒钟。3. MATLAB 实操手写一个 ELM 回归预测模型3.1 数据准备与归一化处理好的模型建立在好的数据上。ELM 对数据尺度比较敏感因为随机生成的输入权重和偏置有一个范围假设如果特征量级差太远计算结果会偏向量级大的特征。因此第一步永远是归一化。我推荐用 MATLAB 自带的mapminmax它能把数据映射到 [-1, 1] 区间。示例代码如下% 假设 data 是 N×(m1) 矩阵最后一列是目标值 X_raw data(:, 1:end-1); Y_raw data(:, end); % 归一化到 [-1, 1] [X_norm, X_ps] mapminmax(X_raw, -1, 1); % 注意 mapminmax 按行处理因此要转置 [Y_norm, Y_ps] mapminmax(Y_raw, -1, 1); X_norm X_norm; % 转回 N×m Y_norm Y_norm;两个要点mapminmax按行处理数据所以原始数据要转置传入算完再转置回来训练集和测试集必须用同一套归一化参数X_ps和Y_ps绝不能用测试集单独重新归一化。否则相当于人为改变了测试集的分布得到的精度是假的。3.2 ELM 训练函数自己写一版也不难网上有很多 ELM 工具箱但我还是建议你亲手写一遍训练函数不但能加深理解还能避免工具箱版本差异造成的坑。我用的训练实现如下function model elm_train(X, Y, hiddenNum, actFun) % X: 训练输入 N×m % Y: 训练输出 N×1也支持 N×k 多输出 % hiddenNum: 隐含层神经元数量 % actFun: 激活函数句柄如 (x) 1./(1exp(-x)) N size(X, 1); m size(X, 2); % 1. 随机生成输入权重 IW(hiddenNum×m) 和偏置 B(hiddenNum×1) rng(shuffle); % 每次运行不同随机种子 IW rand(hiddenNum, m) * 2 - 1; % 范围 [-1, 1] B rand(hiddenNum, 1) * 2 - 1; % 2. 计算隐含层输出矩阵 H(N×hiddenNum) H zeros(N, hiddenNum); for i 1:N % 每个样本 x_i 扩展成 hiddenNum×m逐行与 IW 做内积再加偏置 X_temp repmat(X(i, :), hiddenNum, 1); H(i, :) actFun(X_temp .* IW); end % 注意更快的向量化写法是 H actFun(X * IW repmat(B, N, 1)) % 上面用循环是为了方便理解实际用向量化写法效率更高 % 3. 用广义逆求输出权重 LW(hiddenNum×k) LW pinv(H) * Y; % 4. 打包返回模型结构体 model.IW IW; model.B B; model.LW LW; model.actFun actFun; end这里有个很容易写错的细节偏置 B 的广播。如果写成X * IW BMATLAB 的隐式扩展会自动把 B 加到每一行上这是可行的如果手写循环则需要像我上面那样用repmat扩展偏置向量。两种写法都行但混用时会维度报错这点值得留意。3.3 预测函数与反归一化训练完成后预测就非常简单了就是前向算一遍和训练时的 H 计算几乎一样function Y_pred_norm elm_predict(model, X_new_norm) % X_new_norm: 需要归一化后的输入 IW model.IW; B model.B; LW model.LW; actFun model.actFun; hiddenNum size(IW, 1); N size(X_new_norm, 1); H actFun(X_new_norm * IW repmat(B, N, 1)); Y_pred_norm H * LW; end预测输出拿到的还是归一化后的值必须反归一化回原始量纲才能看真实误差Y_pred mapminmax(reverse, Y_pred_norm, Y_ps);反归一化同样要用训练时保存的Y_ps。这一点踩过坑的人都知道测试时忘了反归一化所有预测值都在 [-1,1] 里看着分布很漂亮但跟真实值一比完全对不上。3.4 模型评估别只盯着 R²跑完预测第一件事就是算误差。我和团队内部建模时的标准配置是计算以下三个指标RMSE均方根误差和原始数据同量纲直观反映预测偏差的典型大小MAE平均绝对误差对异常值没有 RMSE 那么敏感更稳健R²决定系数衡量模型解释了多大比例的方差越接近 1 越好。MATLAB 代码实现如下RMSE sqrt(mean((Y_test - Y_pred).^2)); MAE mean(abs(Y_test - Y_pred)); SS_res sum((Y_test - Y_pred).^2); SS_tot sum((Y_test - mean(Y_test)).^2); R2 1 - SS_res / SS_tot;我特别想提醒一句R² 很高不代表模型好。当测试集变化范围很小时比如所有目标值都在 100 到 101 之间模型随便预测一个接近 100.5 的常数R² 都会很高。这时候必须结合 RMSE 看绝对误差在业务上是否可接受。我看很多论文只报 R²其实有点自欺欺人。4. 参数调优与性能提升把 ELM 从“能用”变成“好用”4.1 隐含层节点数该怎么选ELM 只有一个真正需要手动设置的超参数——隐含层神经元数量 hiddenNum。选少了欠拟合选多了过拟合而且因为随机权重的存在结果还有一定的波动性。我自己的经验分三步走第一步粗扫范围。从 5 到 200步长 5 或 10每个节点数跑 5 次取平均 RMSE。ELM 训练太快了这点计算量根本不在话下。用交叉验证而不是单次划分能避免数据划分偶然性带来的误导。第二步观察曲线拐点。把 hiddenNum 和 RMSE 的关系画出来通常会看到一条先快速下降、然后进入平台期、最后略微上升的曲线。平台期的起始点就是比较可靠的候选取值。不要盲目追求最低点因为最低点往往伴随过拟合。第三步用验证集确认。选 2 到 3 个平台期附近的节点数在独立的验证集上做最终对比挑泛化误差最小的。我在一个传感器温度补偿项目里粗扫发现 35 个节点效果最好但验证集上 30 和 40 的结果差距微乎其微最后选了 30——少 5 个节点意味着更小的计算量和更低的过拟合风险划得来。4.2 随机性的影响与多次运行取优策略ELM 的输入权重是随机生成的这就导致一个问题每次运行结果都略有不同。哪怕训练集、测试集完全一样两次训练的 RMSE 也可能有 1% 到 5% 的波动。对于学术实验来说这会影响可复现性对于工程部署来说你需要一个确定性的模型。我常用的做法是在elm_train里加入rng(固定种子)参数让实验结果可复现但不要过度依赖某一次随机初始化。更好的做法是跑 20 次记录每次的验证集 RMSE取最小那次对应的模型作为最终模型。因为 ELM 训练速度快20 次训练总共不到一秒性价比极高。best_RMSE inf; for trial 1:20 rng(trial * 100); % 不同种子 model elm_train(X_tr_norm, Y_tr_norm, 30, (x) 1./(1exp(-x))); Y_val_pred elm_predict(model, X_val_norm); val_RMSE sqrt(mean((Y_val - Y_val_pred).^2)); if val_RMSE best_RMSE best_RMSE val_RMSE; best_model model; end end这里是要用验证集选模型而不是训练集。道理很简单训练集误差再小不代表对未知数据的预测能力强。4.3 正则化 ELM当泛化性不够时的救星如果普通的 ELM 在验证集上出现了明显的过拟合——训练集 RMSE 很低、验证集 RMSE 高企——我给你推荐一个不改变代码主干的改进方案正则化极端学习机Regularized ELM。思路是在求解输出权重时加入一个正则项把原来的目标函数从“最小化训练误差”改成“最小化训练误差 模型复杂度惩罚”。数学上解析解从 (\beta H^T) 变成[ \beta (\frac{I}{\lambda} H^T H)^{-1} H^T T ]其中 (\lambda) 是正则化系数。(\lambda) 越大模型权重被压缩得越厉害泛化性越好(\lambda) 越小越接近普通 ELM。代码改动其实只有一行lambda 0.01; % 通过交叉验证确定常用范围 1e-6 ~ 1e2 LW (eye(hiddenNum) / lambda H * H) \ (H * Y);我在实际项目里发现加了正则化的 ELM 在很多小样本高噪声场景下泛化精度能提升 10% 到 20%。特别是数据本身线性关系不强、噪声又比较重时正则化的效果非常显著。这个小改动强烈推荐大家试一下。4.4 激活函数与输入特征的经验组合前面讲了激活函数的选型这里再补充一个我踩过的坑激活函数和特征标准化是联动的。如果激活函数是 sigmoid它的有效输入范围大约在 [-4, 4]超过这个范围梯度就饱和了——虽然 ELM 不反传梯度但饱和区会让不同样本的激活值都趋于相同相当于把特征信息磨平了。因此当你选了 sigmoid 或 tanh 时归一化目标尽量定在 [-1, 1] 或 [0, 1]不要只做 Z-score 标准化均值为 0、方差为 1——虽然 Z-score 在很多模型里是首选但配合 sigmoid 时却可能让部分输入超出敏感区间。如果你坚持用 Z-score建议把激活函数换成 ReLU 或者线性单元效果会更稳定。5. 常见问题与排查技巧实录5.1 测试集误差巨大怎么回事这是新手遇到最多的灾难场景。我排查这类问题的固定顺序如下第一步检查数据泄露。看看归一化参数是否用了训练集的。只要mapminmax(reverse, ...)时传错Y_ps所有预测就废了。第二步检查 H 矩阵计算。在训练函数里把 H 打出来看是不是存在某一整列全是相同值或全是零的情况。如果出现说明该隐含层神经元已经饱和可以适当减小输入权重的随机范围或者换成其他激活函数。第三步检查输出层维度。多输出回归时LW pinv(H) * Y里的 Y 必须是 N×k 矩阵如果你的 Y 恰好是 N×1 的列向量那就退化成单输出问题不大。但如果你本来想要多个输出却把 Y 拼成了行向量那得到的 LW 维度会错预测结果自然全乱。第四步检查数据划分。ELM 不需要验证集来调迭代次数但仍然需要独立的测试集。如果测试集和训练集来自不同时间段或不同工况分布漂移会让模型误差变大这不一定是模型的问题而是数据分布不一致的问题。实际案例我在做一个设备寿命预测时训练集是实验室老化数据测试集却是现场运行数据初始 RMSE 惨不忍睹。后来我加了几个工况特征温度、负载率作为输入误差立刻降了一半。特征工程对 ELM 的帮助比对深度学习还明显因为 ELM 没有自动特征提取的能力它只能老老实实地用你给的原始输入。5.2 隐含层节点过多导致过拟合节点数过多的典型症状是训练集 RMSE 逼近 0测试集 RMSE 却比节点数少一半时要高不少。这和高斯过程回归里核函数过拟合是一个道理模型把训练集中的噪声也记住了。我在 4.1 节已经给了选节点数的方法这里再补充一个更实用的经验优先使用验证集误差曲线而非训练集误差曲线。训练集曲线随着节点数增加几乎一直在下降但验证集曲线会在某个点掉头上升。那个掉头点就是你该停下的地方。如果不想手动选可以考虑用集成 ELM训练多个结构相同但随机种子不同的 ELM取预测平均。集成的方差会显著降低测试 RMSE 通常比单模型稳定很多而且代码改动也不大。5.3 小样本场景下 ELM 和高斯过程回归怎么选这是个好问题也恰好是很多仿真场景的痛点。这里我给出一个操作建议先跑 ELM再跑 GPR对比速度和精度。高斯过程回归GPR在小样本场景下确实有优势——它自带不确定性估计给出预测值的同时还能给出置信区间。但它的训练需要求协方差矩阵的逆复杂度大约是 O(N³)。当样本量 N 超过 2000 时训练时间会让人难以接受。而 ELM 的复杂度主要受隐含层节点数影响和数据量的关系是线性的在这个规模下 ELM 还是游刃有余。所以我的选择标准很简单N 500 且对置信区间有要求选 GPRN 在 500 到 5000 之间先用 ELM 做基线如果精度不够再考虑 GPRN 5000GPR 基本跑不动直接 ELM 或它的正则化变体。需要声明的是这个划分是我个人项目经验的总结不是定论。你的数据特征不同拐点也会不同。但用这个顺序做实验通常都能以最小成本找到一个可靠的模型。5.4 ELM 输入特征维度如何精简ELM 对特征维度的容忍度也不差但高维特征会增加随机权重矩阵的规模造成两个问题一是 H 矩阵计算变慢二是容易引入不必要的噪声。特征太多时我一般先做一步相关性分析% 计算每个特征与目标值的相关系数 [R, P] corrcoef([X, Y]); feature_R R(1:end-1, end); % 每个特征与目标的相关系数 significant_idx find(abs(feature_R) 0.3);把相关系数绝对值低于 0.3 的特征剔除再用剩下的特征训练 ELM。注意这只是粗筛特征之间可能存在多重共线性两个单相关系数都很低的特征组合起来反而有预测力的情况也出现过。所以粗筛之后我还喜欢用排列重要性验证一次——把某个特征随机打乱观察 RMSE 变化变化越大说明该特征越重要。ELM 训练快做这种实验的体验远好于深度学习模型。5.5 MATLAB 版本兼容性小坑最后提一下 MATLAB 本身的兼容性问题。pinv函数、mapminmax函数在 R2018a 到 R2025b 之间行为没有变化但隐式扩展X * IW B这种写法在 R2016b 之前的版本需要改用bsxfun。如果你还在用老版本把代码里的加法写成这样更保险H actFun(bsxfun(plus, X * IW, B));如果你在较新的版本里看到repmat相关的警告可以放心改成隐式扩展性能更好。另外MATLAB 2023a 之后对中文注释的默认编码是 UTF-8如果从老版本拷贝代码出现中文乱码在编辑器里重新保存为 UTF-8 编码即可不影响程序运行。6. 让代码直接能用完整回归预测流程清单光讲原理和经验不够我把一份完整的、可直接复制运行的 ELM 回归预测流程写在这儿从数据导入到结果评估一气呵成。你只需要把自己的数据替换到data变量里最后一列放目标值即可%% ELM 回归预测完整流程 clear; clc; % 1. 加载数据 load(your_data.mat); % 假设变量 data: N×(m1)最后一列为目标 X_raw data(:, 1:end-1); Y_raw data(:, end); % 2. 划分训练集/测试集这里简单按 7:3 划分建议使用 cvpartition 做交叉验证 rng(42); idx randperm(size(X_raw, 1)); train_ratio 0.7; train_idx idx(1:round(train_ratio * length(idx))); test_idx idx(round(train_ratio * length(idx))1:end); X_train_raw X_raw(train_idx, :); Y_train_raw Y_raw(train_idx, :); X_test_raw X_raw(test_idx, :); Y_test_raw Y_raw(test_idx, :); % 3. 归一化注意用训练集参数归一化测试集 [X_train, X_ps] mapminmax(X_train_raw, -1, 1); [Y_train, Y_ps] mapminmax(Y_train_raw, -1, 1); X_test mapminmax(apply, X_test_raw, X_ps); X_train X_train; Y_train Y_train; % 4. 训练 ELM这里隐含层节点取 30激活函数 sigmoid model elm_train(X_train, Y_train, 30, (x) 1./(1exp(-x))); % 5. 预测测试集 Y_test_pred_norm elm_predict(model, X_test); Y_test_pred mapminmax(reverse, Y_test_pred_norm, Y_ps); % 6. 评估 rmse sqrt(mean((Y_test_raw - Y_test_pred).^2)); mae mean(abs(Y_test_raw - Y_test_pred)); ss_res sum((Y_test_raw - Y_test_pred).^2); ss_tot sum((Y_test_raw - mean(Y_test_raw)).^2); r2 1 - ss_res / ss_tot; fprintf(RMSE: %.4f\nMAE: %.4f\nR²: %.4f\n, rmse, mae, r2); % 7. 画图对比 figure; plot(Y_test_raw, b-o, LineWidth, 1.5); hold on; plot(Y_test_pred, r-*, LineWidth, 1.5); legend(真实值, ELM预测值); xlabel(样本序号); ylabel(目标值); title(ELM 回归预测结果对比); grid on;如果你需要多输出回归只需要把Y_raw改成多列矩阵即可elm_train和elm_predict内部已经用矩阵运算自动适配了多输出。7. 我的实际体会与扩展建议7.1 把 ELM 当作“标尺”而不是“终点”在我个人的工作流里ELM 很少是最终撑场面的模型但它几乎永远是第一个上场、帮我建立基线认知的模型。十分钟内拿到一个可信的基线 RMSE后续就能判断改进方向是否有效——如果你鼓捣半天复杂模型的精度连 ELM 都比不过那你大概率是在特征工程或数据质量上出了问题而不是模型不够强。这种“先用简单模型锚定预期”的思路帮我节省了大量时间去排查那些其实根本没有必要的复杂方案。7.2 我对 ELM 局限性的坦诚提醒ELM 也不是没有短板。它的随机权重机制导致结果有波动必须多次运行取优或集成后才能稳定部署它对特征的可解释性也比较弱毕竟中间层是随机映射你很难说清楚某个神经元到底在提取什么特征。如果你的业务对模型可解释性有强需求我建议你旁边的方案考虑决策树或线性回归ELM 更适合作为预测工具而非解释工具。7.3 往后续可以怎么扩展如果读完这篇你打算把 ELM 用到更复杂的场景我建议按这个顺序去扩展从普通 ELM 走向正则化 ELMRELM解决过拟合问题从单隐含层走向多层 ELMML-ELM用逐层无监督学习构建深层特征适合数据量更大、非线性更强的任务把 ELM 和贝叶斯推断结合获得不确定性估计把训练好的 ELM 导出成 C 代码部署到嵌入式设备由于它本质上就是矩阵乘法和激活函数部署工作量极小。我现在做一个边缘设备上的状态监测项目用的就是 ELM 做实时预测推理一次只需要 0.002 秒几乎不占资源精度还能保持在合理范围。这种“轻量、快速、够用”的特质在真实工程环境里往往比那些指标漂亮但不实用的复杂模型更受欢迎。希望这篇 ELM 教程也能帮你把回归预测这件事做得更顺手。
网站建设高端定制企业官网