集成学习Boosting - AdaBoost

news/2024/11/28 6:48:55/

目录

1. Boosting方法的基本思想

1.1 Bagging VS Boosting

1.2 Boosting算法的基本元素与基本流程

1.3 sklearn中的Boosting算法

2. AdaBoost

3 AdaBoost的基本参数与损失函数

3.1 参数 base_estimator,属性base_estimator_与estimators_

3.1. 参数 learning_rate

3.3 参数 algorithm

① 二分类指数损失

② 多分类指数损失

3.4 参数loss


1. Boosting方法的基本思想

        在集成学习的“弱分类器集成”领域,除了降低方差来降低整体泛化误差的装袋法Bagging,还有专注于降低整体偏差来降低泛化误差的提升法Boosting。相比起操作简单、大道至简的Bagging算法,Boosting算法在操作和原理上的难度都更大,但由于其专注于偏差降低,Boosting算法们在模型效果方面的突出表现制霸整个弱分类器集成的领域。在当代知名的Boosting算法当中,Xgboost,LightGBM与Catboost都是机器学习领域最强大的强学习器,Boosting毫无疑问是当代机器学习领域最具统治力的算法领域。

1.1 Bagging VS Boosting

装袋法 Bagging提升法 Boosting
弱评估器相互独立,并行构建相互关联,按顺序依次构建
先建弱分类器的预测效果影响后续模型的建立
建树前的抽样方式样本有放回抽样
特征无放回抽样
样本有放回抽样
特征无放回抽样
先建弱分类器的预测效果可能影响抽样细节
集成的结果回归平均
分类众数
每个算法具有自己独特的规则,一般来说:
(1) 表现为某种分数的加权平均
(2) 使用输出函数
目标降低方差
提高模型整体的稳定性来提升泛化能力
本质是从“平均”这一数学行为中获利
降低偏差
提高模型整体的精确度来提升泛化能力
相信众多弱分类器叠加后可以等同于强学习器
单个评估器容易
过拟合的时候
具有一定的抗过拟合能力具有一定的抗过拟合能力
单个评估器的效力
比较弱的时候
可能失效大概率会提升模型表现
代表算法随机森林梯度提升树,Adaboost

         在以随机森林为代表的Bagging算法中,我们一次性建立多个平行独立的弱评估器,并让所有评估器并行运算。在Boosting集成算法当中,我们逐一建立多个弱评估器(基本是决策树),并且下一个弱评估器的建立方式依赖于上一个弱评估器的评估结果,最终综合多个弱评估器的结果进行输出,因此Boosting算法中的弱评估器之间不仅不是相互独立的、反而是强相关的,同时Boosting算法也不依赖于弱分类器之间的独立性来提升结果,这是Boosting与Bagging的一大差别。如果说Bagging不同算法之间的核心区别在于靠以不同方式实现“独立性”(随机性),那Boosting的不同算法之间的核心区别就在于上一个弱评估器的评估结果具体如何影响下一个弱评估器的建立过程

        与Bagging算法中统一的回归求平均、分类少数服从多数的输出不同,Boosting算法在结果输出方面表现得十分多样。早期的Boosting算法的输出一般是最后一个弱评估器的输出,当代Boosting算法的输出都会考虑整个集成模型中全部的弱评估器。一般来说,每个Boosting算法会其以独特的规则自定义集成输出的具体形式,但对大部分算法而言,集成算法的输出结果往往是关于弱评估器的某种结果的加权平均,其中权重的求解是boosting领域中非常关键的步骤。

1.2 Boosting算法的基本元素与基本流程

        基于上面所明确的“降低偏差”、“逐一建树”、以及“以独特规则输出结果”的三大特色,我们可以确立任意boosting算法的三大基本元素以及boosting算法自适应建模的基本流程:

  • 损失函数𝐿(𝑥,𝑦) :用以衡量模型预测结果与真实结果的差异
  • 弱评估器𝑓(𝑥) :(一般为)决策树,不同的boosting算法使用不同的建树过程
  • 综合集成结果𝐻(𝑥):即集成算法具体如何输出集成结果

这三大元素将会贯穿所有的boosting算法,几乎所有boosting算法的原理都围绕这三大元素构建。在此三大要素基础上,所有boosting算法都遵循以下流程进行建模:

依据上一个弱评估器f(x)_{t-1}的结果,计算损失函数L(x,y)
并使用L(x,y)自适应地影响下一个弱评估器f(x)_{t}的构建。
集成模型输出的结果,受到整体所有弱评估器f(x)_{0}f(x)_{T}的影响。

正如之前所言,Boosting算法之间的不同之处就在于使用不同的方式来影响后续评估器的构建。无论boosting算法表现出复杂或简单的流程,其核心思想一定是围绕上面这个流程不变的。

1.3 sklearn中的Boosting算法

        在sklearn当中,我们可以接触到数个Boosting集成算法,包括Boosting入门算法AdaBoost,性能最稳定、奠定了整个Boosting效果基础的梯度提升树GBDT(Gradient Boosting Decision Tree),以及近几年才逐渐被验证有效的直方提升树(Hist Gradient Boosting Tree)。除了sklearn,研究者们还创造了大量基于GBDT进行改造的提升类算法,这些算法大多需要从第三方库进行调用,例如极限提升树XGBoost(Extreme Gradient Boosting Tree),轻量梯度提升树LightGBM(Light Gradiant Boosting Machine),以及离散提升树CatBoost(Categorial Boosting Tree)。

Boosting算法集成类
ADB分类sklearnAdaBoostClassifer
ADB回归sklearnAdaBoostRegressor
梯度提升树分类sklearnGradientBoostingClassifier
梯度提升树回归sklearnGradientBoostingRegressor
直方提升树分类sklearnHistGraidientBoostingClassifier
直方提升树回归sklearnHistGraidientBoostingRegressor
极限提升树第三方库xgboostxgboost.train()
轻量梯度提升树第三方库lightgbmlightgbm.train()
离散提升树第三方库catboostcatboost.train()

2. AdaBoost

       AdaBoost(Adaptive Boosting,自适应提升法)是当代boosting领域的开山鼻祖,它虽然不是首个实践boosting思想算法,却是首个成功将boosting思想发扬光大的算法。它的主要贡献在于实现了两个变化:

① 首次实现根据之前弱评估器的结果自适应地影响后续建模过程。
② 在Boosting算法中,首次实现考虑全部弱评估器结果的输出方式。

         作为开山算法,AdaBoost的构筑过程非常简单:首先,在全样本上建立一棵决策树,根据该决策树预测的结果和损失函数值,增加被预测错误的样本在数据集中的样本权重,并让加权后的数据集被用于训练下一棵决策树。这个过程相当于有意地加重“难以被分类正确的样本”的权重,同时降低“容易被分类正确的样本”的权重,而将后续要建立的弱评估器的注意力引导到难以被分类正确的样本上。

在该过程中,上一棵决策树的的结果通过影响样本权重、即影响数据分布来影响下一棵决策树的建立,整个过程是自适应的。当全部弱评估器都被建立后,集成算法的输出𝐻(𝑥)等于所有弱评估器输出值的加权平均,加权所用的权重也是在建树过程中被自适应地计算出来的。

3 AdaBoost的基本参数与损失函数

        在sklearn中,AdaBoost既可以实现分类也可以实现回归,我们使用如下两个类来调用它们:

class sklearn.ensemble.AdaBoostClassifier(base_estimator=None, *, n_estimators=50,
learning_rate=1.0, algorithm='SAMME.R', random_state=None)
class sklearn.ensemble.AdaBoostRegressor(base_estimator=None, *, n_estimators=50, 
learning_rate=1.0, loss='linear', random_state=None)

不难发现,AdaBoost的参数非常少,在调用AdaBoost时我们甚至无需理解AdaBoost的具体求解过程。同时,ADB分类器与ADB回归器的参数也高度一致。

参数参数含义
base_estimator弱评估器
n_estimators集成算法中弱评估器的数量
learning_rate迭代中所使用的学习率
algorithm(分类器专属)用于指定分类ADB中使用的具体实现方法
loss(回归器专属)用于指定回归ADB中使用的损失函数
random_state用于控制每次建树之前随机抽样过程的随机数种子

3.1 参数 base_estimator,属性base_estimator_与estimators_

        base_estimator是规定AdaBoost中使用弱评估器的参数。与对弱评估器有严格要求的Bagging算法不同,boosting算法通过降低偏差来降低整体泛化误差,因此可以使用任意弱评估器,且这些弱评估器往往被假设成非常弱小的评估器。默认的弱评估器还是决策树。在sklearn中,ADB分类器的默认弱评估器是最大深度为1的“树桩”,ADB回归器的默认评估器是最大深度为3的“树苗”,弱评估器本身基本不具备判断能力。而回归器中树深更深是因为boosting算法中回归任务往往更加复杂。在传统ADB理论当中,一般认为AdaBoost中的弱分类器为最大深度为1的树桩,但现在也可以自定义某种弱评估器来进行输入。

from sklearn.ensemble import AdaBoostClassifier as ABC
from sklearn.ensemble import AdaBoostRegressor as ABR
from sklearn.tree import DecisionTreeClassifier as DTC
from sklearn.tree import DecisionTreeRegressor as DTR
from sklearn.datasets import load_digits
#用于分类的数据
data_c = load_digits()
X_c = data_c.data
y_c = data_c.target
#用于回归的数据
data_r = pd.read_csv(r"F:\\Jupyter Files\\机器学习进阶\\datasets\\House Price\\train_encode.csv",index_col=0)Price\train_encode.csv",index_col=0)
X_g = data_r.iloc[:,:-1]
y_g = data_r.iloc[:,-1]
#建立ADB回归器和分类器
clf = ABC(n_estimators=3).fit(X_c,y_c)
reg = ABR(n_estimators=3).fit(X_g,y_g)

模型建好之后,我们可以使用属性base_estimator_来查看当前弱评估器,同时也可以使用estimators_来查看当前集成模型中所有弱评估器的情况:

clf.base_estimator_
-----------------------------------------
DecisionTreeClassifier(max_depth=1)
clf.estimators_
--------------------------------------------------------------
[DecisionTreeClassifier(max_depth=1, random_state=475193751),DecisionTreeClassifier(max_depth=1, random_state=986073219),DecisionTreeClassifier(max_depth=1, random_state=1948936364)]
reg.base_estimator_
------------------------------------
DecisionTreeRegressor(max_depth=3)
reg.estimators_
-------------------------------------------------------------
[DecisionTreeRegressor(max_depth=3, random_state=788555247),DecisionTreeRegressor(max_depth=3, random_state=669933604),DecisionTreeRegressor(max_depth=3, random_state=923477337)]

AdaBoost完成分类任务时,弱评估器是分类树,当AdaBoost完成回归任务时,弱评估器是回归树。

自建弱评估器:

base_estimator = DTC(max_depth=10,max_features=30,random_state=1412)
clf = ABC(base_estimator = base_estimator, n_estimators=3).fit(X_c,y_c)
clf.base_estimator_
-------------------------------------------------
DecisionTreeClassifier(max_depth=10, max_features=30, random_state=1412)
clf.estimators_
--------------------------------------------------------------
[DecisionTreeClassifier(max_depth=10, max_features=30, random_state=283102921),DecisionTreeClassifier(max_depth=10, max_features=30, random_state=947325607),DecisionTreeClassifier(max_depth=10, max_features=30, random_state=1069089565)]

注意,为了保证集成算法中的树不一致,AdaBoost会默认消除我们填写在弱评估器中的random_state。

3.1. 参数 learning_rate

        在Boosting集成方法中,集成算法的输出𝐻(𝑥)往往都是多个弱评估器的输出结果的加权平均结果。但𝐻(𝑥)并不是在所有树建好之后才统一加权求解的,而是在算法逐渐建树的过程当中就随着迭代不断计算出来的。例如,对于样本x_{i},集成算法当中一共有𝑇棵树(也就是参数n_estimators的取值),现在正在建立第𝑡个弱评估器,则第𝑡个弱评估器上x_{i}的结果可以表示为f_{t}(x_{i})。假设整个Boosting算法对样本x_{i}输出的结果为H(x_{i}),则该结果一般可以被表示为t=1~t=T过程当中,所有弱评估器结果的加权求和:

H(x_{i})=\sum_{t=1}^{T}\phi _{t}f_{t}(x_{i})

其中,\phi _{t}为第t棵树的权重。对于第t次迭代来说,则有:

H_{t}(x_{i})=H_{t-1}(x_{i})+\phi _{t}f_{t}(x_{i})

在这个一般过程中,每次将本轮建好的决策树加入之前的建树结果时,可以在权重𝜙前面增加参数𝜂,表示为第t棵树加入整体集成算法时的学习率,对标参数learning_rate

H_{t}(x_{i})=H_{t-1}(x_{i})+\eta \phi _{t}f_{t}(x_{i})

该学习率参数控制Boosting集成过程中H(x_{i})的增长速度,是相当关键的参数。当学习率很大时,H(x_{i})增长得更快,我们所需的n_estimators更少,当学习率较小时,H(x_{i})增长较慢,我们所需的n_estimators就更多,因此boosting算法往往会需要在n_estimators与learning_rate当中做出权衡(以XGBoost算法为例)。

以上式子为boosting算法中计算方式的一般规则,并不是具体到AdaBoost或任意Boosting集成算法的具体公式

3.3 参数 algorithm

        首先,参数algorithm是针对分类器设置的参数,其中备选项有"SAMME"与"SAMME.R"两个字符串。这两个字符串分别代表了两种不同的、实现AdaBoost分类的手段:AdaBoost-SAMME与AdaBoost-SAMME.R。两者在数学流程上的区别并不大,只不过SAMME是基于算法输出的具体分类结果(例如-1,1,2)进行计算,而SAMME.R则是在SAMME基础上改进过后、基于弱分配器输出的概率值进行计算,两种方法都支持在AdaBoost上完成多分类任务,但SAMME.R往往能够得到更好的结果,因此sklearn中的默认值是SAMME.R,因此sklearn中默认可以输入的base_estimators也需要是能够输出预测概率的弱评估器。实际在预测时,AdaBoost输出的𝐻(𝑥)也针对于某一类别的概率

        需要注意的是,在分类器中,我们虽然被允许选择算法,却不被允许选择算法所使用的损失函数,这是因为SAMME与SAMME.R使用了相同的损失函数:二分类指数损失(Exponential Loss Function)与多分类指数损失(Multi-class Exponential loss function)。

① 二分类指数损失

L(H(x),y)=e^{-yH^{x}(x)}

其中y为真实分类,H^{x}(x)则是从集成算法输出的概率结果H(x)转换来的向量。转换规则如下:

H^{x}(x)=\left\{\begin{matrix} 1 ,& if&H(x)>0.5 \\ -1,& if&H(x)<0.5& \end{matrix}\right.

在sklearn当中,由于H(x)是概率值,因此需要转换为H^{x}(x),如果在其他实现AdaBoost的算法库中,H(x)输出直接为预测类别,则可以不执行转换流程。根据指数损失的特殊性质,二分类状况下的类别取值只能为-1或1,因此𝑦的取值只能为-1或1。当算法预测正确时,yH^{x}(x)的符号为正,则在函数e^{-x}上损失很小。当算法预测错误时,yH^{x}(x)的符号为负,则在函数e^{-x}上损失较大。二分类指数损失是AdaBoost最经典的损失函数,它在数学推导上的有效性以及在实践过程中很强的指导性让其沿用至今。

② 多分类指数损失

其中,𝐾为总类别数,如四分类[0,1,2,3]的情况时,𝐾=4,𝒚*与𝑯*(𝒙)都是根据多分类具体情况、以及集成算法实际输出𝐻(𝑥)转化出的向量,其中y^{*1}H^{*1}的上标1都表示当前类别。 

        在二分类算法中,算法会直接针对二分类中的其中一个类别输出概率,因为在二分类中𝑃(𝑌=1)=1−𝑃(𝑌=−1),所以只计算出一类的概率即可判断预测的标签。但在多分类算法中,算法必须针对所有可能的取值类型都输出概率,才能够从中找出最大概率所对应的预测标签。因此在集成算法中,我们对进行多分类预测时,会得到如下的表格:

#多分类预测
clf = DTC(max_depth=2).fit(X_c,y_c)#多分类预测输出的概率结果,取前5个样本
pd.DataFrame(clf.predict_proba(X_c)).iloc[:5,:]
0123456789
00.9095740.0000000.0106380.0000000.0319150.0319150.0159570.0000000.0000000.000000
10.0037810.1313800.1200380.1578450.1342160.0113420.0037810.1635160.1587900.115312
20.0037810.1313800.1200380.1578450.1342160.0113420.0037810.1635160.1587900.115312
30.0000000.0926720.0991380.0323280.0711210.3125000.3706900.0129310.0064660.002155
40.9095740.0000000.0106380.0000000.0319150.0319150.0159570.0000000.0000000.000000

每一行对应一个样本,每一列则对应该样本的预测标签为某一类别的概率,以上表格就是5个样本在10分类情况下得出的概率表格,而每一个样本的10个概率中,最大概率所对应的类别就是预测类别。而这一转换可以由函数argmax完成。argmax会取出最大值所对应的索引,刚好也就是最大概率所对应的预测标签。

np.argmax(pd.DataFrame(clf.predict_proba(X_c)).iloc[0,:])
---------------------------------
0
np.argmax(pd.DataFrame(clf.predict_proba(X_c)).iloc[1,:])
-----------------------------------------------
7

对一棵决策树我们会输出k个概率,对于boosting集成中的每一棵树,在任意样本上都会得到f^{c=0}(x)f^{c=1}(x)f^{c=2}(x)……数个不同的结果。在集成算法当中,每个样本在第t次建树过程中,都会生成针对于不同类别的结果:

因此,我们可以得到向量[H^{0}(x),H^{1}(x),H^{2}(x),...,H^{k}(x)],表示当前集成算法计算出的、针对多个类别的概率(也是对全部弱分类器输出的、针对多个类别的概率进行的加权求和)。针对该向量,一定可以得到向量中的一个最大值,该最大值所对应的标签类别就是多分类算法中的预测标签类别。根据该向量,以及指数损失的特性,规定:

其中,𝑎𝑟𝑔𝑚𝑎𝑥𝐻(𝑥)对应的是预测标签,𝑘为所有预选标签类别。因此,假设在4分类情况下,集成算法针对样本𝑖的各个分类输出的概率如下所示,则向量𝑯*(𝒙)的取值如下所示:

0123
H_{t}^{k}(x_{i})0.10.20.20.5
H^{*}(x)-\frac{1}{3}-\frac{1}{3}-\frac{1}{3}1

其中3就是当前集成算法针对样本𝑖预测的标签。

另外一方面,𝒚*一般来说都是真实标签经过上述处理后的结果。同样是4分类器情况下,假设样本𝑖的真实标签为2,则向量𝒚*的构成如下所示: 

0123
𝒚*-\frac{1}{3}-\frac{1}{3}1-\frac{1}{3}

用公式表示则有:

其中y_{i}为样本的真实标签,𝑘为所有预选标签类别。不难发现,在此规则下,此时向量𝒚*以及向量𝑯*(𝒙)的和永远是0,因为向量内部总是1与(K-1)个-\frac{1}{k-1}相加。

        在多分类算法当中,我们常常求解类似于𝒚*或𝑯*(𝒙)的向量,比如在softmax函数中,当预测值或真实值不等于𝑘时,我们赋予的向量值为0,而不是-\frac{1}{k-1}。softmax的一般规则:

0123
H_{t}^{k}(x_{i})0.10.20.20.5
H^{*}(x)0001

        同时,当K=2时,多分类指数损失的值与二分类指数损失完全一致。

多分类指数损失:假设K=2,L=exp(-\frac{1}{K}(y^{*1}H^{*1}(x)+y^{*2}H^{*2}(x)))

假设预测分类等于真实分类=1:

二分类指数损失,y=1,由于预测正确,所以𝐻*(𝑥) = 1,-y𝐻*(𝑥) = -(1*1)= -1。

        在实践中,无论是SAMME还是SAMME.R,我们都无法改变使用的损失函数,因此参数中没有为我们提供相应的选择。

3.4 参数loss

        参数loss与分类的情况完全相反,它是AdaBoost回归类中的参数。在AdaBoost回归当中,我们能够使用的算法是唯一的,即AdaBoost.R2,但是在R2算法下,我们却可以选择三种损失函数,分别是"linear"(线性),"square"(平方),"exponential"(指数)。在算法AdaBoost.R2当中,三种损失函数如下定义:

首先:D=sup\left | H(x_{i}-y_{i}) \right |,i=1,2,...,N

其中y_{i}为真实标签,H(x_{i})为预测标签,sup表示“取最大值”,但它与直接写作max的函数的区别在于,max中的元素已是固定的数值,而sup中的元素可以是一个表达式、并让该表达式在i的备选值中循环。上述式子表示,取出1~N号样本中真实值与预测值差距最大的那一组差异来作为D的值。

不难发现,其实线性损失就是我们常说的MAE的变体,平方损失就是MSE的变体,而指数损失也与分类中的指数损失高度相似。在R2算法当中,这些损失函数特殊的地方在于分母D。由于D是所有样本中真实值与预测值差异最大的那一组差异,因此任意样本的L_{i}在上述线性与平方损失定义下,取值范围都只有[0,1](当真实值=预测值时,取值为0,当真实值-预测值=D时,取值为1)。

特别的,对于指数损失来说,自变量的部分是在[0,1]中取值,因此e^{-x}的在该定义域上的值域也为[0,1],因此1−e^{-x}的值域为[0,1]。事实上,在R2算法的论文当中,就有明确对损失函数的唯一要求:即值域为[0,1]。该规则也使得整个AdaBoost算法的求解流程变得顺畅。

        不难发现,在AdaBoost的参数空间中,n_estimators与learning_rate是最为重要的两个参数。当我们在进行超参数调整时,注意对这两个参数的组合进行同时调整即可。


http://www.ppmy.cn/news/978077.html

相关文章

微信小程序 内容评论-回复评论-回复回复的实现(纯前端)

wxml <!-- 评论-回复-回复评论显示区域 --> <view class"container"><!-- 总共评论数 --> <view class"total">共{{comment_list.length comment_list2.length}}条评论</view> <!-- END --><!-- 评论框 …

一文助你快速提高嵌入式软件的代码质量【下】

一文助你快速提高嵌入式软件的代码质量 文章目录 一文助你快速提高嵌入式软件的代码质量&#x1f468;‍&#x1f3eb;前言1️⃣写直观的代码2️⃣写无懈可击的代码3️⃣正确处理错误4️⃣正确处理null指针5️⃣防止过度工程&#x1f647;文末小结 &#x1f468;‍&#x1f3eb…

报表下载工具

1.需求说明 我有一堆文件的Url地址&#xff0c; 现在需要按照企业&#xff0c;项目和报表类型分类下载到对应的文件夹中 2.相关实体类 企业文件夹定义 package com.vz.utils.report;import lombok.Data; import java.util.ArrayList; import java.util.List; import java.uti…

MySQL主从复制、读写分离

一、前言二、主从复制原理2.1 MySQL复制类型2.2 MySQL主从复制工作过程2.3 MySQL的四种同步方式2.3.1 异步复制&#xff08;MySQL默认&#xff09;2.3.2 同步复制2.3.3 半同步复制&#xff08;企业常用&#xff09;2.3.4 增强半同步复制 2.4 MySQL主从复制延迟原因和优化方法2.…

Jvm参数优化

Jvm参数优化 背景1. 系统上线规划容量- 分析 2. 垃圾回收器选择吞吐量和响应时间垃圾回收器选择 3. 规划各个分区的比例大小4. 对象年龄对少移动到老年代合适5. 对象多大放到老年代6. 垃圾回收器CMS老年代参数优化7. 配置OOM时的内存dump文件和GC日志8. 通用JVM参数模板 背景 …

CMS指纹识别是什么?

CMS(Content Management System)指的是内容管理系统,如WordPress、Joomla等。CMS系统非常常见,几乎所有大型网站都使用CMS来管理其网站的内容。由于常见CMS的漏洞较多,因此黑客将不断尝试利用这些漏洞攻击CMS系统,导致网站的安全问题。为了保护网站的安全,可以使用CMS指…

【Koa】[NoSQL] Koa中相关介绍和使用Redis MongoDB增删改查

目录 NoSQL非关系型数据库关系型数据库&#xff08;RMDB&#xff09;介绍非关系型数据库&#xff08;NoSQL&#xff09;介绍Redis & MongoDB 在 Koa 中使用 Redis (了解)Redis 的安装和使用在 Koa 中连接 和 调用 Redis 在 Koa 中使用 MongoDBMongoDB 的安装MongoShell 操作…

未来行星探索希望:新型多脚机器人-团队版

机器人正在探索一个模拟的外星环境 即使一个机器人失败了&#xff0c;其余的团队成员也可以抵消它的损失。 背景 虽然探测器取得了令人难以置信的发现&#xff0c;但它们的轮子可能会拖慢它们的速度&#xff0c;而不稳定的地形可能会导致损坏。虽然没有东西可以取代“毅力号”…