ARTICLE DETAIL

资讯详情

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

MATLAB中BP神经网络多输入单输出预测程序详解与代码实现

MATLAB中BP神经网络多输入单输出预测程序详解与代码实现 在MATLAB里做预测绕不开BP神经网络。我拿到过太多类似“多了个输入单输出、要可直接换数据”的代码需求坦白讲市面上的碎片代码不少但真正注释清楚、结构清晰、拿回来就能跑通并换成自己数据的程序反而稀缺。这篇就把我优化过的一套BP神经网络预测MATLAB程序掰开揉碎讲清楚包含了设计思路、关键参数怎么定、完整实操流程和换数据时最容易踩的坑让刚接触BP的同学也能照着把代码跑起来。1. 程序整体设计思路多输入单输出预测的关键拆解1.1 BP神经网络为什么适合这类预测BP神经网络是反向传播Back Propagation算法的多层前馈网络核心能力是拟合输入和输出之间的非线性映射关系。对多输入单输出的预测问题来说输入通常是多个特征变量比如温度、湿度、压力、流量这些影响因子输出是一个目标值比如能耗、产量、浓度或者价格。当变量之间存在复杂的交互作用而且很难用明确的数学公式写出来时BP神经网络的优势就出来了它能在训练过程中自动调整神经元之间的连接权值和阈值通过不断迭代逼近目标函数。很多人会问这种多输入单输出的问题用线性回归或者多项式拟合不就行了如果特征和目标之间确实是线性或者低阶多项式关系那当然可以。但实际问题里特征之间往往存在非线性耦合比如某个输入只有在另一个输入超过阈值时才显著影响输出这种带“开关”效应的关系线性模型根本表达不了。BP神经网络因为隐含层使用了非线性激活函数理论上可以逼近任意闭区间上的连续函数这就是所谓万能逼近定理。所以当我面对一堆说不清关系的特征变量时BP是比传统回归更稳妥的默认选择。有一点必须提醒BP网络不是“装上就能出好结果”的魔法箱。它对数据质量、参数设置、训练策略都很敏感。一套好的MATLAB预测程序重要的是把数据预处理、网络构建、训练控制、结果评价这几个环节的边界理清楚让使用者可以专注于“换数据”而不是每次都被代码细节绊住。1.2 多输入单输出建模的整体框架一个典型的多输入单输出BP预测流程可以归纳成六个环节数据准备、数据归一化、样本划分、网络构建与训练、仿真预测、结果评价。整个过程在逻辑上是一条直线原始数据进来经过预处理变成网络认识的形式训练完成后得到网络模型然后对未知样本做预测最后用指标判断预测得好不好。数据准备环节核心是确定输入矩阵X和输出矩阵Y的形状。我习惯用“行是样本列是变量”的格式也就是X的每一行是一个样本每一列是一个特征Y是列向量每一行对应一个样本的输出。这种格式与MATLAB矩阵操作习惯高度一致也方便后续用mapminmax做归一化。很多初学者第一块绊脚石就是维度搞反了后面所有代码都会跟着报错。数据归一化这步在BP里几乎是必须的。输入特征如果量纲差异大比如一个特征是0到1之间的比值另一个特征是一两千的数值网络训练时梯度更新会被大数值特征主导导致收敛慢甚至不收敛。归一化把所有特征压到统一的尺度范围内让每个特征对网络训练的贡献比较均衡。这里有一个很多教程没讲透的细节归一化参数最大值、最小值只能用训练集去算然后把这个参数直接应用到测试集上不能把训练集和测试集混在一起做归一化。否则测试集的信息会提前“泄露”给训练过程得到的误差指标过于乐观失去参考意义。样本划分环节一般按照70%训练、15%验证、15%测试这样的比例来分或者根据样本量灵活调整。训练集用来更新权值验证集用来监控训练过程中是否过拟合测试集是完全没参与训练的数据用来评估模型的泛化能力。网络构建与训练涉及到隐含层神经元数量、激活函数、训练算法的选择这些参数直接影响预测精度和训练速度。仿真预测就是用训练好的网络对测试集做正向计算得到预测值。结果评价则是计算均方误差、决定系数这些指标同时通过绘图直观观察拟合效果。1.3 程序模块划分的逻辑我把整套程序按功能拆成独立的区块每个区块用注释明确标注起点和终点。结构上分成五段数据导入与格式检查、归一化参数获取与数据处理、网络创建与参数配置、网络训练与仿真、结果计算与可视化。这种拆分方式最大的好处是方便排查问题哪段报错就只看哪段不需要从头捋逻辑。以我这次的优化程序为例每一段我都加了指导性的注释比如头部数据区的注释会写明“这里只需要修改数据文件路径、输入特征列号和输出列号其余代码不需要动。”这样即使使用者对MATLAB不熟也能根据提示完成关键修改。为了让程序真正做到“可直接换数据”我还在数据导入后加了一个维度自检模块自动判断输入输出矩阵的行数是否一致、是否有明显缺失值不一致就直接报错并给出中文提示避免后续代码在奇怪的报错信息里绕圈子。这种模块化设计的另一个好处是通用性高。同一个项目里我可以把整套训练预测流程封装成一个脚本换一个数据集时只需要改动数据导入段和输出列索引剩下的归一化、训练、评价、画图逻辑原封不动就能复用。我实际做过的项目里从风力发电功率预测到房地产价格评估再到化工过程软测量都是同一套代码框架只改了数据和几个结构参数。这就是模块化的价值所在。2. 核心代码解析与关键参数设定2.1 数据怎么导入最省心我最推荐的方式是把数据放到Excel文件里用readmatrix读取。Excel对多数人来说是最顺手的数据整理工具一行一个样本、一列一个变量整理起来直观。readmatrix在较新的MATLAB版本里很稳定能直接读出数值矩阵不需要额外处理单元格对象。如果数据文件是.mat格式直接用load就可以。下面是我在程序中使用的数据导入模板%% 第一步数据导入 % 使用表格管理数据方便修改 data readmatrix(data.xlsx); % 读取Excel数据第一行可以是表头也可以直接是数值 % data load(data.mat); data data.data; % 如果数据是mat格式用这个 % 检查数据是否为空 if isempty(data) error(数据文件为空请检查文件路径和文件名); end % 这里假设最后一列是目标输出前面的所有列都是输入特征 X_raw data(:, 1:end-1); % 输入特征矩阵 Y_raw data(:, end); % 输出目标向量 % 自检输入和输出的样本数必须一致 if size(X_raw, 1) ~ size(Y_raw, 1) error(输入样本数与输出样本数不一致请检查数据文件); end这段代码看起来简单但有几个细节值得说。第一我在读取之后立刻做空数据检查万一文件路径写错了或者文件内容格式不对程序会在第一时间给出中文提示而不是在后续代码里报一个莫名其妙的索引越界错误。第二我默认最后一列是输出前面的列是输入这种约定在大多数预测任务里都成立使用者只要按这个规则整理数据代码里的变量索引完全不用改。第三我保留了.mat格式的读取注释代码方便有需要的人切换。如果用户的数据不是Excel也不是.mat而是文本文件可以用readmatrix(data.txt)同样能读入数值矩阵只要数据是规整的矩形表就行。其实readmatrix本身就支持txt、csv、xlsx等常见格式所以一套导入逻辑能覆盖绝大多数场景。这里唯一要注意的是Excel文件如果包含中文表头readmatrix默认会跳过非数值行实际不影响读取但如果你想读取指定的某些列而不是全部列可以用data(:, 3:5)这种切片操作这个我在程序注释里也写明了。2.2 归一化容易被忽略但影响最大的环节归一化是BP神经网络代码里最容易被初学者跳过或者做错的一步但它的影响往往比网络结构参数更大。如果不做归一化训练过程中梯度下降会非常不稳定如果归一化方式错了比如把训练集和测试集混在一起算归一化参数得到的评估结果会失真。我用的归一化函数是mapminmax它是MATLAB神经网络工具箱自带的默认把数据映射到[-1, 1]区间。这里要注意mapminmax处理矩阵时是按行操作的每一行是一组独立的数据。所以原始X矩阵如果是“样本×特征”排列使用mapminmax之前需要先转置成“特征×样本”排列处理完再转置回来。这个转置细节是新手最容易出错的地方。具体代码如下%% 第二步数据归一化 % mapminmax默认按行归一化所以需要先将矩阵转置为“变量×样本”格式 X X_raw; % 转置为 特征数 × 样本数 Y Y_raw; % 转置为 1 × 样本数 % 获取归一化参数只统计训练集的数据范围 % 这里为了方便演示先取前80%样本作为训练集后20%作为测试集 trainNum round(size(X, 2) * 0.8); X_train_raw X(:, 1:trainNum); Y_train_raw Y(:, 1:trainNum); X_test_raw X(:, trainNum1:end); Y_test_raw Y(:, trainNum1:end); % 用训练集计算归一化参数并归一化训练集 [X_train, ps_X] mapminmax(X_train_raw, -1, 1); [Y_train, ps_Y] mapminmax(Y_train_raw, -1, 1); % 用训练集的归一化参数直接映射测试集 X_test mapminmax(apply, X_test_raw, ps_X); Y_test mapminmax(apply, Y_test_raw, ps_Y); % 转置回 样本×变量 格式方便后续训练函数使用 X_train X_train; Y_train Y_train; X_test X_test; Y_test Y_test;这里我将训练集和测试集的划分提前到了归一化之前并且用训练集的数据范围来计算归一化参数ps_X和ps_Y。ps_X是一个结构体里面保存了训练集每个特征的最大值、最小值以及映射公式所需的所有信息。映射测试集时mapminmax(apply, X_test_raw, ps_X)会使用ps_X中保存的参数而不是重新计算测试集自己的最大最小值。这就保证了测试集的归一化方式与训练集完全一致不会出现信息泄露。有些代码会把所有数据先整体归一化再划分训练集和测试集这样做虽然程序简单但得到的结果会偏乐观因为归一化时已经把测试集的信息最大值最小值用进去了。在论文或者实际报告中这种做法的结果是站不住脚的。所以即使多两行代码也要坚持先划分、后归一化的顺序。2.3 网络构建与训练参数选择网络构建这一块涉及到隐含层层数、隐含层神经元数量、激活函数、训练函数等参数。我使用的程序里默认构造一个单隐含层的BP网络隐含层神经元数量按经验公式计算训练函数用trainlmLevenberg-Marquardt这是中小型数据集上收敛最快、精度最高的默认选择之一。先看核心代码%% 第三步BP神经网络构建 % 输入层节点数 输入特征数 inputDim size(X_train, 2); % 输出层节点数 输出变量数这里固定为1 outputDim 1; % 隐含层神经元数量经验公式sqrt(输入节点数输出节点数)1~10之间的常数 hiddenNum round(sqrt(inputDim outputDim) 5); % 创建前馈神经网络 net feedforwardnet(hiddenNum); % 设置训练参数 net.trainParam.epochs 1000; % 最大训练轮数 net.trainParam.lr 0.01; % 学习率 net.trainParam.goal 1e-5; % 目标误差 net.trainParam.showWindow true; % 显示训练窗口 net.trainParam.min_grad 1e-8; % 最小梯度 % 设置网络层激活函数默认即可也可以按需修改 % net.layers{1}.transferFcn tansig; % 隐含层双曲正切S型函数 % net.layers{2}.transferFcn purelin; % 输出层线性函数 % 划分训练集、验证集、测试集比例 net.divideFcn divideblock; net.divideParam.trainRatio 0.85; net.divideParam.valRatio 0.15; net.divideParam.testRatio 0.0;关于隐含层神经元数量我使用的是经典的实验经验公式hiddenNum round(sqrt(inputDim outputDim) a)其中a通常在1到10之间取整数。这个公式给的是一个初始参考值实际操作中可以在附近做几次网格搜索。隐含层节点太少网络拟合能力不足容易出现欠拟合节点太多网络容量过大容易把训练集的噪声也学进去产生过拟合。我程序里默认a取5是考虑到中等规模数据集的通用情况实测下来大多数项目在这个值附近表现都不错。训练函数方面feedforwardnet默认使用trainlm。Levenberg-Marquardt算法结合了梯度下降法和高斯-牛顿法的优点在中小数据集上收敛速度极快精度也比较高是MATLAB神经网络工具箱里最常用的训练函数。它的缺点是在大样本和高维权值空间下内存消耗很大如果你的样本量超过几万条trainlm可能会把内存吃得很难看这时可以考虑换成trainbr贝叶斯正则化或者trainscg规模化共轭梯度。trainbr的优势在于自动控制模型复杂度对防止过拟合很有效但训练速度会慢不少。trainscg是内存友好的选择适合大数据集。学习率这一步默认的0.01是偏保守但稳妥的取值。学习率设置太大训练过程会震荡loss曲线上下跳动甚至直接发散学习率太小收敛极慢可能训练几千轮都达不到目标误差。我通常会在0.001到0.1之间做几次尝试先用0.01摸个底如果loss下降太慢再调大一点如果震荡明显就调小。如果只是做快速验证保持0.01也能出可接受的结果。3. 完整实操流程从数据到结果3.1 示例数据准备与说明为了把整套流程走通我用了一个带明确规律的数据集做演示。数据由三个输入变量X1、X2、X3构成目标输出Y由下面这个函数生成%% 生成示例数据 rng(42); % 固定随机种子保证结果可复现 n 500; % 样本数量 X1 linspace(0, 5, n); X2 randn(n, 1); X3 sin(X1) 0.2 * randn(n, 1); Y 3 * X1 2 * sin(X1 .* X2) 0.5 * X3.^2 0.8 * randn(n, 1); data_demo [X1, X2, X3, Y]; writematrix(data_demo, data_demo.xlsx);这里Y与X1存在明显的线性趋势同时叠加了与X2相关的非线性项以及X3的平方项和随机噪声。这样的数据设计能有效检验神经网络是否真正学习了非线性关系而不是简单套了一条直线。rng(42)的作用是固定随机数生成器的种子保证每次运行生成的示例数据完全一致这样不同人或者不同次运行之间才有可比性。做实验时固定的数据种子是保证结果可复现的基础这点对论文和项目复查都特别重要。生成的data_demo.xlsx有500行4列前三列是输入特征最后一列是输出。把这个文件放到程序同目录下修改readmatrix读取的文件名程序就可以直接运行。如果你是把自己的数据放进去注意保持同样的列组织逻辑就行。3.2 训练过程与中间结果怎么看运行训练代码后MATLAB会弹出一个神经网络训练窗口nntraintool这是观察训练过程的主战场。窗口里有三块核心信息值得关注性能曲线、训练状态、回归图。性能曲线Performance展示的是均方误差随迭代次数的变化曲线通常有三条线分别对应训练集、验证集和测试集的MSE。理想情况下训练集和验证集的误差都持续下降并趋于平稳而且两条线挨得比较近说明没有严重的过拟合。如果训练集误差一直下降但验证集误差先降后升那基本可以确定过拟合出现了应该在验证集误差最低点对应的迭代次数附近停止训练。MATLAB的train函数自带early stopping机制当验证集误差连续多次不再下降时训练会自动终止这是默认启用的一项非常实用的保护功能。训练状态Training State窗口里可以看到梯度Gradient和验证检查Validation Checks的变化。梯度随着迭代逐渐变小说明参数在收敛到某个局部最优解附近。验证检查次数指的是验证集误差连续不改善的次数默认达到6次后训练就会提前终止。如果你的训练过程在Validation Checks达到6次后停止了但在性能曲线上看验证误差还在下降趋势中那可能是验证集选的数据太少或者恰好包含了一些噪声大的样本可以试试换一个划分随机种子或者调整divideParam里的比例。下面这段代码是整个训练仿真过程%% 第四步BP神经网络训练与仿真 % 训练网络 [net, tr] train(net, X_train, Y_train); % 测试集仿真预测 Y_pred_train net(X_train); Y_pred_test net(X_test); % 反归一化 Y_pred_train mapminmax(reverse, Y_pred_train, ps_Y); Y_pred_test mapminmax(reverse, Y_pred_test, ps_Y); Y_true_train mapminmax(reverse, Y_train, ps_Y); Y_true_test mapminmax(reverse, Y_test, ps_Y);训练完成后net就是保存了最终权值和阈值的网络对象。tr是训练记录结构体里面保存了每次迭代的误差、验证表现等信息。仿真时使用net(X_train)对归一化后的输入做正向传播得到归一化后的预测输出然后通过mapminmax(reverse, ...)把预测值恢复到原始量纲。这一步很多人会漏掉导致最后画的图里预测值和真实值差了好几个数量级。在调用train之前需要明确一点train函数会重新随机初始化权值然后开始训练。即使数据完全一样每次训练的初始权值不同最终结果也可能有细微差异。因此如果在做实验对比或者发表结果我建议在训练前设置随机种子命令是rng(default)或者rng(固定数字)这样结果可以复现。3.3 结果评价指标与可视化输出预测做完了光肉眼看预测值和真实值接近不接近不够客观需要计算量化指标。回归预测问题中我常用的指标有四个均方误差MSE、均方根误差RMSE、平均绝对误差MAE、决定系数R²。MSE和RMSE对较大误差比较敏感能反映预测的“极端偏差”情况MAE更直观反映平均偏差水平R²衡量模型解释了多少方差越接近1说明拟合效果越好。%% 第五步结果评价与可视化 % 计算训练集和测试集误差指标 mse_train mean((Y_true_train - Y_pred_train).^2); rmse_train sqrt(mse_train); mae_train mean(abs(Y_true_train - Y_pred_train)); r2_train 1 - sum((Y_true_train - Y_pred_train).^2) / sum((Y_true_train - mean(Y_true_train)).^2); mse_test mean((Y_true_test - Y_pred_test).^2); rmse_test sqrt(mse_test); mae_test mean(abs(Y_true_test - Y_pred_test)); r2_test 1 - sum((Y_true_test - Y_pred_test).^2) / sum((Y_true_test - mean(Y_true_test)).^2); % 在命令行打印评价结果 fprintf(训练集: MSE%.4f, RMSE%.4f, MAE%.4f, R2%.4f\n, mse_train, rmse_train, mae_train, r2_train); fprintf(测试集: MSE%.4f, RMSE%.4f, MAE%.4f, R2%.4f\n, mse_test, rmse_test, mae_test, r2_test); % 绘制测试集预测值与真实值对比曲线 figure; plot(Y_true_test, b-o, LineWidth, 1.5, DisplayName, 真实值); hold on; plot(Y_pred_test, r--s, LineWidth, 1.5, DisplayName, 预测值); legend(Location, best); xlabel(测试样本序号); ylabel(输出值); title(BP神经网络测试集预测效果对比); grid on; % 绘制误差分布直方图 figure; errors Y_true_test - Y_pred_test; histogram(errors, 20); xlabel(预测误差); ylabel(频数); title(预测误差分布直方图);测试集预测对比曲线是我每次做项目必看的图。如果蓝色真实值曲线和红色预测值曲线走势高度重合说明模型捕捉到了主要变化规律。误差直方图则能直观展示误差是否存在明显的偏置比如误差大多集中在正值区间说明预测存在系统性偏低的情况可以考虑在输出层增加一个偏置修正项或者检查数据是否存在未处理干净的趋势成分。除了这两张图MATLAB还提供了几个内置可视化函数plotperform(tr)可以绘制训练过程的性能曲线plottrainstate(tr)展示训练状态plotregression(net, X_test, Y_test)输出回归图展示预测值与目标值的线性拟合程度。在整理项目报告时这些图都是很好的材料。4. 换数据使用时的避坑指南4.1 换数据前必须做的检查拿到新数据集后最忌直接把Excel丢进程序里就跑。我在实际项目中吃过不少亏总结了一套换数据前必须检查的事项按重要性排序如下第一确认数据没有缺失值。Excel或者原始采集数据里常有空单元格readmatrix读取时空值会变成NaNNaN进入训练过程会让梯度计算直接出错。排查方法很简单用any(isnan(data(:)))检查有的话需要先插值或者剔除对应样本。我程序里没默认加这个检查因为插值策略因数据而异但至少要在命令行自查一遍。第二检查特征量纲和分布。如果某些特征明显存在尖峰或者重尾分布比如个别样本的值比其他样本大几个数量级这种离群点对方差影响极大拖累归一化和训练效果。处理方式要么剔除要么做对数变换压缩量纲。注意对数变换的底数和偏移量也要在训练集上确定再应用到测试集和归一化的逻辑一致。第三样本量是否足够。多输入单输出问题隐含层神经元数量一多网络的可训练参数数量会快速膨胀。比如输入特征10个隐含层神经元15个那么输入层到隐含层的权值就有10×15150个再加上隐含层到输出层的15个权值和各层阈值总参数接近180个。如果训练样本只有几十条很容易过拟合。一般我要求训练样本数至少是网络参数数量的5到10倍如果样本不够优先减少隐含层神经元的数量而不是盲目增加网络容量。第四检查输出是否有明显的趋势或者周期性。BP神经网络本质上是在做静态映射它对时间序列中的先后依赖关系不敏感。如果预测对象是带强自相关的时序数据比如逐小时的电力负荷直接用当前时刻的特征去预测下一时刻的值而不把历史值也纳入输入特征效果往往会差强人意。这种情况的解决方案是把滞后几期的历史输出值也作为输入特征加入数据表让网络有更多上下文信息可用。4.2 常见问题排查实录我把实际使用中出现频率最高的问题整理成了速查表方便大家定位自己的情况现象可能原因排查思路训练不收敛误差曲线平直学习率太小或网络结构不合理尝试增大学习率到0.05~0.1增加隐含层神经元数量训练发散误差爆炸学习率太大或数据包含极端离群点调小学习率到0.001检查特征中是否存在异常大值训练集效果很好测试集一塌糊涂过拟合增加样本量减少隐含层神经元或改用trainbr预测值全部接近某个常数数据未归一化或输出激活函数饱和检查是否执行了mapminmax输出层改用purelin换了数据后维度不匹配报错输入特征数和原程序不一致检查隐含层公式确认inputDim正确更新两次运行结果波动很大未设置随机种子固定rng或者多次训练取最优结果训练极慢样本量大且用trainlm改用trainscg或减少训练轮数这里我特别说一个容易迷惑的“预测值接近常数”问题。有的读者反馈明明训练时误差很低但预测出来的Y值基本都在一个固定值附近抖动。这种情况多半是因为输出层激活函数用了sigmoid或者tansig而真实输出范围超出了激活函数的饱和区间。BP网络的输出层应该设置为purelin线性激活函数这样输出范围不受限制。feedforwardnet默认输出层就是purelin所以如果不动默认设置不会出这个问题但如果有人参考了某些旧代码用newff手动定义结构就容易踩中这个坑。另一个值得注意的是数据划分方式。我程序里用的是divideblock也就是按顺序取前85%作为训练集、后15%作为验证集。这种划分适合数据本身已经随机分布的情况。但如果数据按时间排列且存在明显的时间趋势顺序划分会让训练集和测试集的分布差异过大这时建议改用randperm随机打乱后划分让训练集和测试集在数值分布上更接近。随机划分的代码很简单% 随机打乱样本顺序 idx randperm(size(X_raw, 1)); X_raw X_raw(idx, :); Y_raw Y_raw(idx, :);但要注意如果做的是时间序列预测随机打乱会破坏时序依赖必须用顺序划分或者按时间窗口划分不能无脑随机。4.3 一个快速有效的调优路线换数据后如果预测效果不理想我一般按下面的顺序调优而不是一上来就动网络结构第一步看数据的分布问题。先画各特征的直方图看有没有明显的偏态分布或者离群点。大多数实际数据都需要一定的清洗才能进入模型这步花的时间往往比调参还多但收益显著。第二步调整隐含层神经元数量。在经验公式的基准值附近依次尝试基准值减3、减1、加1、加3找到测试集误差最低的那个值。这个过程可以用一个for循环自动跑记录每次的R²选最大的。第三步调整训练函数。如果数据量在几千条以下trainlm是首选如果过拟合迹象明显换成trainbr如果样本量上万或者训练内存吃紧用trainscg。同一个数据在不同训练函数下的表现差异可能很明显值得都试一遍。第四步适当调整学习率和目标误差。学习率按0.1、0.05、0.01、0.005的顺序尝试目标误差goal一般设为1e-5就够用设太大约束太松设太小容易过拟合。最后提醒一句神经网络预测的精度上限很大程度上由数据的信息量决定。如果特征本身与目标输出之间相关性很弱无论怎么调参模型也不可能凭空变出好效果。这时候应该回过头去审视特征工程考虑是否遗漏了重要的输入变量而不是继续在训练参数上死磕。我见到太多人把时间浪费在无意义的参数网格搜索上而从不质疑输入特征本身是否合理这是最可惜的。从我个人的实操体会来说这套BP神经网络预测程序最大的价值在于“稳定”该归一化的提前归一化该固定的种子固定住该拆除的信息泄露路径拆除掉。只要你按数据格式要求把Excel表填好跑出来的结果至少是可信的、可复现的。至于精度能不能再上一层楼那就回到特征和数据质量上做文章了。
返回列表