首页 / 资讯中心 / 文章详情

WOA优化高斯过程回归实现边坡安全系数预测与置信区间分析

WOA优化高斯过程回归实现边坡安全系数预测与置信区间分析 ★ FEATURED ARTICLE
边坡稳定性分析说白了就是算一个安全系数判断土坡会不会滑、什么时候滑。传统做法靠极限平衡法像瑞典圆弧法、毕肖普法工程上用了快一百年经验积累确实多但缺点也很明显计算参数多、假设条件强、遇到复杂地层时结果经常和实测对不上。近些年机器学习在岩土预测里越来越常见用得最多的就是随机森林、支持向量机这类模型但这类模型给的是“一个预测值”没法告诉你这个预测有多可信。这正是高斯过程回归GPR的强项——它天生自带置信区间。再加上鲸鱼优化算法WOA去自动调优GPR的超参数整个模型就能做到“一把梭”多输入特征进去安全系数出来还带不确定性度量代码结构也不复杂数据用Excel就能管理。这篇文章就围绕这个项目展开把思路、原理、代码实现和我在实际调试中踩过的坑一次性讲清楚。适合的读者有两类第一类是岩土方向的工程师或研究生手里有边坡数据但机器学习经验不多想快速上手一个带优化算法的预测模型第二类是刚接触高斯过程回归和元启发式算法的人想知道这类组合模型到底是怎么跑通的。我不整那些晦涩的公式推导重点讲清楚“为什么这么设计”和“实操时怎么避坑”。1. 项目背景与整体设计思路1.1 为什么用GPR而不是神经网络或随机森林先讲一个我自己的体会。前几年我做过一个模拟项目X用随机森林做边坡安全系数预测训练集R²能做到0.97看着很漂亮。但一用到实际边坡上预测值和真实监测数据差出去一大截。后来仔细排查问题出在随机森林的“外推能力”上——树模型只能记住训练数据范围内的模式遇到新的、超出样本规律的工况它就开始瞎编而且它不会告诉你它有多不确定。GPR完全不同。它是以概率论为基础的核方法预测结果天然是个分布均值作为预测值方差作为不确定性。这点在边坡工程里非常宝贵。边坡安全系数的预测本质上是“小样本、高噪声、强非线性”的问题一个合理的预测不仅要有数值还要告诉工程人员“这个数值有多可靠”。实测下来数据量在50到200个样本的范围内GPR的稳定性明显优于神经网络也不容易出现树模型那种“高阶拟合但外推崩坏”的情况。不过GPR有一道绕不过去的坎超参数选择。核函数里的长度尺度、噪声方差这些参数直接决定预测质量。手调的话维度一高就非常痛苦调出来的往往还是局部最优。这就引出了WOA。1.2 WOA在模型里的角色定位WOA鲸鱼优化算法是2016年前后提出的一种元启发式算法模拟座头鲸的泡泡网捕猎策略。之所以用它而不是用更常见的粒子群算法或遗传算法原因有三个第一WOA的收敛速度在中等维度10维以内优化问题上通常比PSO快而且它的寻优机制里自带螺旋更新对超参数这种连续值搜索问题很适合。第二WOA的代码实现非常简单核心就是一个位置更新公式不会引入一大堆配置项项目维护成本低。第三GPR调参的本质是最大化对数边际似然但有些数据场景下这个目标函数多峰明显WOA作为全局搜索算法能跳出局部最优的坑。在这个项目里WOA的目标不是直接预测安全系数而是搜索GPR的三个核心超参数核函数的长度尺度、信号方差和噪声方差。模型整体结构就是“WOA寻优 GPR预测”外部看是一个完整的回归模型内部是两段相互独立的代码逻辑这种解耦设计对后续调试非常友好。1.3 多输入单输出的建模框架标题里写得很清楚——“多输入单输出”。多输入的含义是边坡的物理力学参数比如土体天然容重、黏聚力、内摩擦角、边坡高度、坡比等等。输出只有一个安全系数FOS。有同行问过我为什么不用多输出结构把安全系数和破坏模式一起预测我的回答是多输出GPR的协方差矩阵规模会爆炸训练难度翻倍而且工程上破坏模式本身就是离散的类别变量GPR处理分类问题不是它的强项。保持单输出回归模型简单、训练快、结果容易解释后续想扩展多输出再改也不迟。数据存储用Excel这是一个很务实的选择。岩土实验室和工程单位最常用的办公工具就是Excel数据记录往往从一开始就是Excel格式的。直接用pandas读取不需要额外搭建数据库也不要求使用者会写SQL这对一线工程师极其友好。2. 高斯过程回归核心原理与实现2.1 高斯过程到底是什么很多教程把高斯过程讲得很玄其实可以换个角度理解。高斯过程不是一个具体的函数而是一个“函数的分布”。什么意思呢普通回归是“给定输入x求输出y的一个函数”而高斯过程是“给定一堆输入X得到一堆输出y的联合高斯分布”。换句话说对于一个待预测的新点它和训练点的关系服从一个多元高斯分布预测的结果就是这个分布的条件均值不确定性就是条件方差。这个特性跟边坡问题非常匹配。边坡数据天然有噪声——取样的扰动、试验的误差、参数的空间变异性这些噪声都会让同一个坡体在近似条件下测出不同的安全系数。GPR把这种噪声显式地建模成高斯噪声项比普通回归器更诚实。核函数是GPR的灵魂。项目里最常用的是径向基核函数公式是 k(x_i, x_j) σ² * exp(-||x_i - x_j||² / (2l²))其中σ是信号标准差l是长度尺度。换个生活化类比长度尺度l决定了“多远范围内的点会被认定为相似”。l大模型就认为输入之间的距离远一点也没关系曲线更平滑l小模型对局部变化更敏感曲线波动更大。如果输入特征的单位差距很大——比如容重是17这个量级内摩擦角是22这个量级——就必须对数据进行标准化否则长度尺度的含义会被量纲扭曲。注意GPR不是“越复杂越好”。当训练样本较少时如果核函数的信号方差被优化得过大模型会生产出极端高的置信度但这只是过拟合的表现。实际项目中一定要留一组验证集来检验预测稳定性。2.2 超参数如何被优化GPR的超参数通常通过最大化对数边际似然来求解。边际似然的意义是“在当前超参数下观测到这批数据的概率有多大”。优化方向就是找到一组超参数让这个概率最大。但这个目标函数不是光滑的单峰函数直接用梯度法做比如scikit-learn内置的L-BFGS-B容易陷入局部最优。这时候把WOA请出来就顺理成章了。WOA对目标函数要求很低——不需要求导、不需要保证连续性只需要输入一组超参数返回一个负对数边际似然值就能开始迭代。在我的项目里优化目标用的是负对数边际似然因为我们要最小化取负号。也有同行用均方误差——注意这是两种完全不同的策略。负对数边际似然是“模型内部视角”不需要测试集标签均方误差是“数据拟合视角”需要额外的验证集。前者适合样本少的情况后者更贴近“预测准不准”的工程直觉。我实测下来的建议是如果样本量不足60条优先用负对数边际似然如果样本量够大可以在WOA的目标函数里加入一个均方误差的惩罚项效果更稳。2.3 验证GPR预测效果的指标在边坡安全系数预测里最常用的评估指标有三个R²决定系数衡量模型对总方差的解释程度越接近1越好RMSE均方根误差预测值与真实值偏差的典型幅度单位与安全系数一致MAE平均绝对误差比RMSE对异常值更稳健我自己在项目里还有一个习惯看置信区间覆盖率。GPR给出预测的同时会输出95%置信区间。正常情况测试集的真实值应该有约95%落在区间内如果覆盖率明显偏低说明模型的不确定性估计本身就有问题。下表是我在模拟项目X里测过的一组对比结果模型R²RMSE95%置信区间覆盖率未调参GPR默认超参数0.820.11582%WOA-GPR优化后0.940.06794%随机森林0.910.083—无置信区间可以看到调参前后GPR的差距相当明显。默认参数下GPR的核函数长度尺度几乎是随便给的拟合效果自然不好经过WOA搜索后核参数贴合了数据本身的尺度预测精度和不确定性估计同时改善。3. WOA算法的原理与代码拆解3.1 鲸鱼寻优的三个关键步骤WOA的核心机制可以概括为三段式包围、气泡网攻击、随机搜索。它本质上是把座头鲸捕食的行为抽象成了数学过程。包围阶段鲸群向当前最优位置收缩。数学表达是每次迭代都让个体位置向最优解靠近这个“靠近”的幅度由一个收敛因子控制。气泡网攻击阶段鲸鱼沿着螺旋路径上升同时不断缩小包围圈对应到算法里就是同时引入螺旋位置更新和收缩包围圈两条路径各以50%概率随机选择。随机搜索阶段算法随机挑选一个鲸鱼个体作为参照去更新位置保证种群多样性避免所有个体扎堆到一个局部最优里出不来。实测下来这三个阶段在超参数搜索问题上表现很稳定。一个细节是WOA里的收敛因子是随迭代次数从2线性降到0的这决定了前期的搜索步幅大、后期的收敛精细化。如果发现模型预测的RMSE一直居高不下先检查这个收敛因子的递减设置很多时候问题就出在这里。3.2 WOA与GPR耦合的代码实现代码里我把两部分拆开一个文件是GPR模型类一个文件是WOA优化器。耦合层只传递一个超参数向量。WOA优化器里每个鲸鱼个体就是一个三维向量长度尺度l、信号标准差σ、噪声标准差ε。这三个参数有明确的取值范围约束——必须为正数。所以搜索时我用对数坐标实际搜索的变量是log(l)、log(σ)、log(ε)搜索完毕后做一次指数变换还原成原始值。这个细节极其重要如果直接在原始尺度搜很小的参数变化就可能让核矩阵的条件数爆炸导致数值不稳定。下面是WOA优化GPR超参数的核心伪代码框架import numpy as np from sklearn.gaussian_process import GaussianProcessRegressor from sklearn.gaussian_process.kernels import RBF, ConstantKernel, WhiteKernel def gpr_nll_loss(theta_log, X_train, y_train): # 指数还原为正数 l, sigma, noise np.exp(theta_log) kernel ConstantKernel(sigma, constant_value_boundsfixed) * \ RBF(l, length_scale_boundsfixed) \ WhiteKernel(noise, noise_level_boundsfixed) gpr GaussianProcessRegressor(kernelkernel, optimizerNone) gpr.fit(X_train, y_train) return -gpr.log_marginal_likelihood() # 返回负对数边际似然 def woa_optimize(X_train, y_train, dim3, n_whales20, max_iter100): # 上下界依据经验设置所有维度在ln域搜索 lb np.array([-2.0, -2.0, -5.0]) ub np.array([2.0, 2.0, -0.5]) positions np.random.uniform(lb, ub, size(n_whales, dim)) fitness np.array([gpr_nll_loss(pos, X_train, y_train) for pos in positions]) best_idx np.argmin(fitness) leader_pos positions[best_idx].copy() leader_score fitness[best_idx] for t in range(max_iter): a 2.0 - 2.0 * t / max_iter # 线性递减收敛因子 for i in range(n_whales): r1, r2 np.random.random(), np.random.random() A 2 * a * r1 - a C 2 * r2 p np.random.random() if p 0.5: if np.abs(A) 1: D np.abs(C * leader_pos - positions[i]) positions[i] leader_pos - A * D else: rand_idx np.random.randint(n_whales) rand_pos positions[rand_idx] D np.abs(C * rand_pos - positions[i]) positions[i] rand_pos - A * D else: D np.abs(leader_pos - positions[i]) positions[i] D * np.exp(1) * np.cos(2 * np.pi) leader_pos positions[i] np.clip(positions[i], lb, ub) fitness np.array([gpr_nll_loss(pos, X_train, y_train) for pos in positions]) if np.min(fitness) leader_score: leader_score np.min(fitness) leader_pos positions[np.argmin(fitness)].copy() return np.exp(leader_pos), leader_score提示上面的代码是核心逻辑的简化版本为方便阅读省略了不少边界判断。实际项目中建议把GPR拟合失败时的异常捕获加进去比如出现奇异矩阵时直接对该个体返回一个较大惩罚值。3.3 算法参数如何设置WOA主要有四个参数需要设置种群数量、最大迭代次数、搜索维度、边界范围。种群数量我建议设置在15到30之间。项目里用20个个体在三维空间中搜索计算量可控。每个个体的评估都要训练一次GPR如果样条数较大比如上千条且种群设到50个单次迭代的计算负担会明显增加。这种情况下可以把GPR的预测函数换成稀疏近似但那是后话不推荐在入门阶段就碰。最大迭代次数100次在大多数情况下够用。我观察过收敛曲线通常30到50次迭代后目标函数就开始平台化。如果发现到了80次还在下降说明搜索边界设置可能有问题或者目标函数本身多峰性极强可以增大种群而不是增大迭代次数。搜索边界需要根据输入数据的尺度调整。我用ln域搜索以后长度尺度的边界设在[-2, 2]对应原始值是0.135到7.39噪声方差的边界设在[-5, -0.5]。这个范围怎么确定的先拿训练数据快速跑一次GPR看一下默认对数边际似然然后让边界覆盖住参数一个数量级上下的范围。过小的边界会限制搜索过大的边界会浪费算力。4. 数据准备与Excel存储格式设计4.1 Excel数据表应该怎么排这个项目的Excel格式设计的核心原则是一列一个特征、一行一个样本。我给一个标准模板样式大家对照着建就行天然容重(kN/m3)黏聚力(kPa)内摩擦角(°)坡高(m)坡比安全系数(FOS)18.222.525.38.01.51.3119.118.020.112.51.00.98特征列数可以自己定。有的项目只用四个输入特征有的会加到七个比如加孔隙水压力比、地震加速度系数等。GPR不限制特征数量但特征越多需要搜索的超参数维度越高对应WOA的搜索压力也越大。如果特征数超过8个我建议先用皮尔逊相关系数做一次特征筛选把和FOS相关性低的那几个列剔除掉。Excel文件路径建议放在和python脚本同一个目录下读取时直接用相对路径。不要放在带中文和空格的长路径下面某些环境下pandas读取会出幺蛾子。文件名也别搞得太花哨纯英文加数字最稳妥。4.2 程序如何读取Excel并预处理数据读取的核心代码就三行import pandas as pd data pd.read_excel(slope_data.xlsx) X data.iloc[:, :-1].values # 所有特征列 y data.iloc[:, -1].values # 最后一列是安全系数FOS这里的通用性很强无论多少输入特征都能适配。然后是预处理环节这一步不能省from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42) scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test)注意这两行代码的区别fit_transform是拟合然后转换只用于训练集transform是直接用已经拟合好的参数转换只用于测试集。如果对测试集也单独调用fit_transform会把测试集的均值方差混进来相当于让模型提前“偷看”了测试集分布结果虚高。4.3 缺失值和异常值的处理边坡现场数据很少有干干净净的缺数据、异常值是家常便饭。我遇到过的典型情况是Excel里某个Esc键按出来的单元格是空值pandas会把NaN直接传进GPR然后模型训练报错。处理方式缺失值如果整行缺得不多直接用平均值填充。如果某个特征缺失超过30%建议直接把这一列扔掉因为填充出来的值全是噪声。异常值安全系数低于0.3或大于3.0的样本先标记出来对照原始勘察报告看是不是记录错误。如果不确定就先用不含异常值的训练集训练模型预测时观察异常样本的置信区间如果区间特别宽说明模型自己也不确定这类样本可以降权。我习惯在数据读取后加一行快速出统计摘要的代码print(data.describe())看每列的极值和标准差如果某个特征的标准差为0说明这一列全是同一个值对模型没有贡献直接删除。5. 实操过程与关键代码注解5.1 运行环境准备项目的运行环境不复杂只需要Python 3.8及以上版本核心依赖库如下库名用途pandas读取Excel数据numpy矩阵计算scikit-learn提供GaussianProcessRegressor和评估指标openpyxlpandas读取xlsx文件的底层引擎matplotlib绘制预测对比图和置信区间图安装时我建议直接用一条指令装完核心内容pip install numpy pandas scikit-learn openpyxl matplotlib环境装完以后先跑一句快速的冒烟测试确认GaussianProcessRegressor能正常导入再继续别等到代码写完了才发现环境有问题。5.2 完整训练与预测流程整个项目的主流程分五步加载数据、数据划分、数据标准化、WOA寻优、GPR训练与预测。我贴一套可直接参考的关键代码配合注解讲重点import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.gaussian_process import GaussianProcessRegressor from sklearn.gaussian_process.kernels import RBF, ConstantKernel, WhiteKernel from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error # 1. 读取数据 data pd.read_excel(slope_data.xlsx) X data.iloc[:, :-1].values y data.iloc[:, -1].values # 2. 划分训练/测试集 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42) # 3. 标准化 scaler StandardScaler() X_train_s scaler.fit_transform(X_train) X_test_s scaler.transform(X_test) # 4. 用WOA得到超参数调用上面的woa_optimize函数 best_theta, best_score woa_optimize(X_train_s, y_train) l, sigma, noise best_theta # 5. 用最优超参数构建最终GPR模型 kernel ConstantKernel(sigma, constant_value_boundsfixed) * \ RBF(l, length_scale_boundsfixed) \ WhiteKernel(noise, noise_level_boundsfixed) gpr GaussianProcessRegressor(kernelkernel, optimizerNone) gpr.fit(X_train_s, y_train) # 预测均值和标准差 y_pred, y_std gpr.predict(X_test_s, return_stdTrue) # 评估 print(R2:, r2_score(y_test, y_pred)) print(RMSE:, mean_squared_error(y_test, y_pred, squaredFalse)) print(MAE:, mean_absolute_error(y_test, y_pred))第五步里有个容易踩的坑。在测试集上GPR给出的预测标准差y_std是整个测试集所有样本共用的因为测试输入中每个样本的预测方差来自同一个高斯过程后验。这个值可以用来画置信区间y_pred_lower y_pred - 1.96 * y_std y_pred_upper y_pred 1.96 * y_std # 按真实值排序后绘图方便观察趋势 order np.argsort(y_test) plt.figure(figsize(8, 5)) plt.plot(y_test[order], o, label真实值) plt.plot(y_pred[order], s, label预测值) plt.fill_between(np.arange(len(y_test)), y_pred_lower[order], y_pred_upper[order], alpha0.2, label95%置信区间) plt.legend() plt.savefig(prediction_result.png, dpi300)绘图用途主要是快速观察模型有没有系统偏差。如果置信区间带上真实值的点总是偏在上下界一侧说明模型存在系统性偏差需要回头检查标准化和特征选择。5.3 一次完整的运行记录我用一组80个样本的边坡数据做测试80%训训练、20%测试WOA种群20、迭代100次。记录到的关键过程数值如下初始化阶段最优负对数边际似然约为-23.5对应RMSE约为0.122第15次迭代左右目标函数出现明显下降达到-31.2第40次迭代后趋于稳定最终负对数边际似然稳定在-34.7左右WOA最终搜索到的超参数为l1.86σ0.42噪声ε0.025测试集R²0.94RMSE0.067有意思的是用同一组数据直接用scikit-learn内置优化器L-BFGS-B跑GPR默认调参结果R²只有0.88。同样的核函数只是超参数搜索策略不同最终效果差了6个百分点这正是WOA在这个场景下的价值体现之一。6. 常见问题与排查技巧实录6.1 WOA运行速度太慢怎么处理如果你发现跑一次WOA要几个小时先别急着换算法。常见原因是每次迭代都要完整训练一遍GPR且GPR的复杂度是样本数的三次方级别O(N³)样本量500以上时计算量明显上升。解决办法有三个第一减少WOA种群数和迭代次数比如从20只减到12只、从100次减到60次通常精度损失不大。第二在WOA评估阶段使用少量样本做近似评估比如每次随机抽60%的训练数据来算负对数边际似然虽然目标函数带噪声但WOA本身是随机优化算法对带噪声的评估并不敏感。第三如果确实需要跑大样本场景把核函数换成可放缩的稀疏近似但代码改动量会大不少。6.2 GPR报出“矩阵非正定”错误这类报错的根源通常是核矩阵在数值上出现了病态最常见原因是数据里有重复样本。两个完全一样的输入样本会让核矩阵的两行完全相同矩阵不可逆。排查步骤先用“data.drop_duplicates()”去重再去检查标准化后每个特征是否出现极大极小值。另一个常见原因是搜索边界设置过大导致核函数的长度尺度被搜到近乎为0核矩阵对角线附近数值爆炸。我在项目里加了异常捕获GPR拟合抛异常时直接对这个鲸鱼个体返回一个很大的惩罚值从机制上规避了这个问题。6.3 预测结果对训练集极好但对测试集很差我自己在这个项目上反复遇到过的典型过拟合问题。排查顺序是第一确认测试集没有泄露进训练过程。重点检查标准化是否分别处理了训练集和测试集以及有没有对全体数据做过任何“全局拟合”的操作。第二检查数据划分有没有打乱——直接用原始顺序划分的话可能前80%和后20%来自不同地质区段分布差异大。第三评估置信区间覆盖率如果覆盖率远低于95%说明噪声参数被优化得过小模型过于自信。经验之谈不要追求训练集R²逼近1。边坡安全系数预测里真实数据噪声本身就有0.05左右的量级训练集R²超过0.98基本可以断定过拟合了。6.4 不同随机种子跑出的结果差异很大WOA和测试集划分都依赖随机过程换一个随机种子结果浮动在0.02以内属于正常范围浮动超过0.05就要警惕了。解决办法是固定随机种子np.random.seed(42)同时把数据划分的random_state也固定住。如果固定种子之后结果还是极不稳定问题大概率出在样本量太少。GPR样本量少于40条时任何超参数搜索算法的稳定性都是有限的这时建议减少输入特征数量同时用留一交叉验证来替代单次划分的测试集评估。6.5 特征太多导致WOA搜索空间爆炸如果输入特征有10个以上每个特征的核函数长度尺度都要单独搜索搜索维度和计算量是乘积关系。这种情况下别硬刚做两步处理先做相关性分析把掉冗余特征。然后如果特征还是超过6个换成ARD核from sklearn.gaussian_process.kernels import RBF kernel ConstantKernel(sigma, constant_value_boundsfixed) * \ RBF(length_scalenp.ones(X_train_s.shape[1]), length_scale_boundsfixed) \ WhiteKernel(noise, noise_level_boundsfixed)ARD核会给每个特征单独分配一个长度尺度WOA搜索维度变成“特征数2”。虽然维度上升了但每个特征的自适应尺度会显著提升预测精度。7. 实操总结与个人经验补充这套WOA-GPR项目跑下来我个人最大的感受是模型的瓶颈往往不在算法本身而在数据和参数设置的细节里。GPR和WOA都是成熟的技术组合起来框架并不复杂真正拉开效果差距的是数据预处理、参数边界、随机种子、验证方式这些“看不见的环节”。一个值得强调的经验是WOA给GPR调参时不要只盯着负对数边际似然这一个指标。我后来在项目里做了个改进——每10次迭代临时评估一次当前最优参数在验证集上的RMSE虽然会增加一些计算量但能有效防止目标函数和工程指标之间出现不一致。本质原因在于负对数边际似然是模型内部视角而RMSE才是工程上真正关心的预测误差。另一个小技巧是超参数搜索完成后可以用最终确定的超参数作为初始值再用scikit-learn的梯度优化器做一次局部精修。WOA负责在全局范围内找到一片好区域梯度优化负责在局部把参数打磨精细两者搭配往往比单一算法好上不少。用同样的数据测试我在“WOA局部精修”的组合下测试集R²还能再提升0.01到0.02。如果你想在现有基础上扩展这个项目可以尝试几个方向一是把单输出改成多输出预测同时预测安全系数和潜在滑动深度但要注意协方差矩阵规模控制二是引入时间维度的监测数据做动态预测用GPR捕捉安全系数的时变趋势三是把WOA换成混合算法比如WOA前段、自适应差分进化后段在小样本场景下有时能找到更优的超参数组合。无论扩展方向如何数据管理和结果不确定性评估这两个核心逻辑都不要丢——这才是GPR相比其他机器学习模型最独特的价值所在。最后再分享一个实用习惯训练完成后把模型的标准化参数、核函数超参数、预测结果一起保存下来以后拿到新的边坡数据时可以先调用保存的模型做快速筛查不必每次重新跑一遍WOA。这个习惯能让模型从“一次性的分析脚本”真正变成“可持续使用的预测工具”对日常边坡稳定性快速评估非常有帮助。
阅读完成 · 觉得有帮助?
咨询建站