运用机器学习(含深度学习)方法处理数据问题的完备流程总结+实践经验细节+代码工具书(3):非神经网络机器学习模型的搭建,训练及评价阶段

本篇为《机器学习完备流程总结+实践经验细节+代码工具书(3)》,使用经过数据处理阶段处理过的数据,进行非神经网络机器学习模型的搭建及训练阶段的相关流程(代码基于python3):

什么是好的机器学习模型/如何得到好的模型+数据分析阶段:https://blog.csdn.net/weixin_44563688/article/details/86535274
前置数据处理阶段: https://blog.csdn.net/weixin_44563688/article/details/86558939
非神经网络机器学习模型的搭建,训练及评价阶段: (you are here)
神经网络机器学习模型的搭建,训练及评价阶段: https://blog.csdn.net/weixin_44563688/article/details/88884468

除神经网络之外的传统机器学习算法,基本都可以通过sklearn非常方便地进行实现,而对于神经网络模型,一般是通过tensorflow,pytorch等神经网络架构来进行搭建,因此我们在这里将机器学习模型的搭建和训练代码分成两部分,非神经网络的传统机器学习模型,以及神经网络模型来分别进行介绍。

非神经网络传统机器学习模型的搭建与训练

传统机器学习模型主要包括:K-nearest Neighbors, Linear Regression, Logistic Regression, Bayesian models(Gaussian Naive Bayes, Gaussian Discriminant Analysis, Gaussian Mixtures), K-means, Support Vector Machine, Decision Tree, Random Forest(Extra-Trees).

由于传统的机器学习模型的内在数学原理不同,因此各个模型或多或少存在着某种特定的‘倾向’:某个模型在某种数据上的表现很好,但是在另外的数据上表现却很差,能够捕捉利用数据某一部分feature的信息,但是对于另外一些的feature利用率很低,为了降低这种由主观选择模型选择带来的bias,这里推荐使用集成学习。同时使用集成学习可以极大地缩短我们训练和优化复杂机器学习模型的周期,事实上在工业和机器学习建模比赛中几乎所有的模型都在使用集成学习, 对于复杂模型,调参可以显著提高模型的表现,但是在使用的数据量巨大,模型复杂的时候,单次训练的时间很可能会以周计,这种时候进行grid search式的调参是不现实的,实际情况中(尤其是工业级别),对于模型的提升更多是在前后两端进行(前端:特征选择,数据处理,改变采样模式;后端:模型融合),因为相比于调参,直接在两端进行的操作高效快速可复制。
常见的集成方式有Voting式集成,Boosting式集成以及终极的类神经网络式集成—Stacking式集成,一般我们使用Gridsearch调参优化子模型 + soft-voting式集成所有最优子模型。但是需要注意的是,在数据质量较高,数量较大时,若使用子模型包含有神经网络,随机森林,SVM等低bias模型时,可能单纯soft-voting集成后模型的集成模型的表现并不如单独使用这个强力子模型,这时可以尝试boosting,stacking等集成方式(其中的弱分类器只是能CART回归树或者相同类型的部分模型),因此大概集成学习的适用情况是:数据数量较少,噪音较高不适合使用低bias模型,或者使用低bias模型训练结果较差时

搭建/训练传统机器学习模型时的推荐策略:Gridsearch调参优化子模型 + soft-voting式集成所有最优子模型

对于voting式集成学习,自然地当集成的每一个模型都有着非常好表现的时候,最终的集成模型会有着十分出色的表现,所以,针对所有使用传统机器学习算法的模型,推荐的策略是:
a.使用gridsearch对每一个成员模型进行调参优化(适用于所有sklearn机器学习模型),来最大程度提高集成模型中各个成员模型的能力。
b.将所有已经经过gridsearch优化过成员模型voting式集成起来,获得一个能力强大的集成模型。

a.gridsearch
对于使用sklearn实现的机器学习模型,我们可以使用sklearn中的gridsearch方法来确定模型的最佳参数组合。由于机器学习模型最费时的环节就是调参,而对于几乎所有机器学习模型来说调参都是不可或缺的优化方法,所以使用gridsearch调参可以视作训练sklearn机器学习模型时的必备环节之后展示的所有代码只是在指定下方代码中的‘model’变量具体是什么算法而已。其中为了避免过拟合,在部分模型中我们加入的regularization(惩罚项)也可以作为一般的参数在这里进行调节,常用的regularization方法有基于L1和L2-norm的weight decay:
L1-regularization:只使用对于分类结果最关键的feature。
L2-regularization:使所有feature的weight尽量均匀分布,不会使某些feature对于分类/回归结果的影响过于显著。
具体使用什么形式的regularization应该根据数据特定而定。

from sklearn.model_selection import GridSearchCV

#选择要在之后模型中进行优化的参数名称和想要尝试的数值。
param_grid = {'C':[0.01,0.1,1,10,100],'gamma':[0.01,0.1,1,10,100]}

#指定具体使用的模型种类
model = ###

#根据上方的参数列表训练所有对应的模型,model为要进行参数优化的模型,refit则在选择出最好的参数组合后重新训练并返回最佳模型,具体的model可以根据下面的各个模型代码块进行设置
#verbose即为print训练模型的进度,cv为cross validation
grid = GridSearchCV(model,param_grid,refit = True,verbose = 5,cv = 5)
grid.fit(X_train,y_train)

#返回效果最佳的参数
gird.best_params_

#使用获得的最佳模型进行预测
predict = grid.predict(X_test)

b.gridsearch + soft-voting Model Ensemble
在sklearn中我们可以直接搭配使用soft-voting的集成方式集成所有模型,然后对这个集成模型使用gridsearch方法来确定所有在parameters list中参数的最佳组合。

from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.model_selection import GridSearchCV


eclf = VotingClassifier(estimators=[ 
    ('rf', RandomForestClassifier()),
    ('lr', LogisticRegression(solver= 'newton-cg')),
    ('knn', KNeighborsClassifier()),
    ], voting='soft')

#Use the key for the classifier followed by __ and the attribute
params = {'lr__C': [1.0, 100.0],
      'rf__n_estimators': [100,500],
          'rf__min_samples_split':[2,3,4],
          'knn__n_neighbors':[5,7]
         }

grid = GridSearchCV(estimator=eclf, param_grid=params, refit = True,verbose = 5,cv = 5)

grid.fit(x_train,y_train)
print (grid.best_params_)

如果对最终的准确率要求不是太高,可以省去gridsearch优化子模型的步骤,直接对集成模型进行fit,简化步骤。

from sklearn.ensemble import VotingClassifier

#voting='soft':根据所有模型预测概率的平均值来进行判断, 'hard':通过少数服从多数来进行最终的分类预测
voting_clf = VotingClassifier(estimators=[
    ('log_clf', LogisticRegression()), 
    ('svm_clf', SVC()),
    ('dt_clf', DecisionTreeClassifier(random_state=666))],
                             voting='hard')
   
voting_clf.fit(X_train, y_train)
voting_clf.score(X_test, y_test)

Bagging:一般情况下,由于这里的每个子模型都是参数学习模型,相比于非参数学习的决策树等模型有着较少的随机性,所以我们无须使用Bagging等方式来生成更多的子模型(效率不高)。不过如果现实中存在着需要我们进一步提高模型的表现的要求时,这时可以使用Bagging(又称bootstrap,更常用,可生成更多的子模型同时体现着更大的随机性)或者Pasting产生更多的模型,从而进一步提高模型的表现。
值得注意的是,由于Bagging采取放回抽样的策略来产生子模型,因此存在着大约37%的数据无法被利用(Out-of-Bag),因此我们可以将这些没有被利用过的样本数据作为testset验证模型最终的表现,因此若使用bagging,为了提高数据利用率,获得更好的模型,我们不需要提前进行testset,trainset的划分,而可以直接使用OOB数据作为testset。
如果数据的特征较多,则可以考虑结合使用Random subspacesRandom Patches(也叫bootstrap features)取样策略来进一步产生更多的子模型。
使用的模型数量较大时,我们可以使用多线程进行训练。

from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier

#第一个参数指定使用Bagging训练集成的单位模型种类(这里以DecisionTree举例),n_estimators:单
#位模型的数量,max_samples:每一个单位模型学习的数据数量,bootstrap:放回取样(bagging)还是不放回取样(pasting),oob_score:是否使用oob来验证模型的表现, n_jobs:线程数, -1则为使用全部线程,max_features: Bagging使用的feature数量。
bagging_clf = BaggingClassifier(DecisionTreeClassifier(),
                           n_estimators=500, max_samples=100,
                           bootstrap=True,oob_score=True, n_jobs = -1,max_features = 10)
                           
#训练模型
bagging_clf.fit(X_train, y_train)

#显示oob score
bagging_clf.oob_score_

#或者使用之前划分好的testset来验证模型的表现(不推荐,浪费了oob中的数据)
bagging_clf.score(X_test, y_test)

Ada Boosting & Gradient Boosting集成方法:
相比于voting的集成方法,Boosting提供了另外的模型间集成策略,常见的Boosting方法有Ada Boosting和Gradient Boosting,当voting集成方法不适合或者效果不佳时,可以使用尝试使用Boosting。

  • Ada boosting:Adaboost算法有两个权值,分别为样本权值和弱分类器权值,其主要特征就是在于样本权值的更新和弱分类器权值的更新。值得注意的是Adaboost中所有的弱分类器必须是同一种分类器,不能在同一个Adaboost算法的迭代步骤中使用不同的弱分类器。优点:Adaboost作为分类器时,分类精度很高,不容易过拟合。缺点:对异常样本敏感,异常样本在迭代中可能会获得较高的权重,影响最终的强学习器的预测准确性。
  • Gradient Boosting:GBDT几乎可用于所有的回归问题(线性/非线性)亦可用于二分类问题(设定阈值,大于阈值为正例,小于阈值为反例)不太适用于多分类问题。GBDT中所有的弱分类器只能是CART回归树,同时弱分类器无法并行实现。优点:可以灵活处理各种类型的数据,包括连续值和离散值,使用一些健壮的损失函数,对异常值的鲁棒性非常强。比如 Huber损失函数和Quantile损失函数。缺点:由于弱学习器之间存在依赖关系,难以并行训练数据。不过可以通过自采样的SGBT来达到部分并行。
#Ada Boosting
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import AdaBoostClassifier

ada_clf = AdaBoostClassifier(
    DecisionTreeClassifier(max_depth=2), n_estimators=500)
ada_clf.fit(X_train, y_train)

#Gradient Boosting
from sklearn.ensemble import GradientBoostingClassifier

gb_clf = GradientBoostingClassifier(max_depth=2, n_estimators=30)
gb_clf.fit(X_train, y_train)

#若为回归问题
from sklearn.ensemble import AdaBoostRegressor
from sklearn.ensemble import GradientBoostingRegressor

Stacking集成方法
另外一种集成策略:将部分子模型的输出作为另外子模型的输入来搭建集成模型,可以说是终极的模型集成方法,本质类似于神经网络因此其中的很多技术细节可参考深度学习(见下节)。值得注意的是Stack集成模型中的每一层应使用单独的数据进行训练,同时由于采取Stacking集成形式的模型较为复杂因此容易出现过拟合的问题,具体的Stacking形式可以自己进行设计,灵活度较高所以这里无法展示相应代码。

常用机器学习算法
具体的子model可以从如下的常见机器学习算法中进行选择, 为了方便使用也提供了不使用集成学习,单独训练使用某一个模型的代码。
1.Linear Regression:
使用要求回归模型适合feature较少,数量不大的数据,数据噪音需近似呈0均值的高斯分布。
优点:有确定解析解因此基本无训练时间成本, 使用correlation低的归一化或正则化后的feature得到的linear regression模型的各个weight可以定性代表这个feature对于结果的影响程度。
缺点:对于outlier十分敏感,同时内在要求数据噪音的分布不能偏离0均值高斯分布较大,否则模型表现不好。当然最大的问题还是模型的高bias导致模型不能抓住数据的内在非线性特征,是最简单的机器学习回归模型

from sklearn.linear_model import LinearRegression

#搭建并训练模型
lm = LinearRegression()
lm.fit(X_train,y_train)

#使用训练完毕的模型进行预测
predict = lm.predict(X_test)

#观察预测值和实际值的分布情况,点越集中与y=x则表示回归效果越好
plt.scatter(y_test,predict)

#返回线性回归系数
print(lm.coef_)

2.Logistic Regression:
使用要求分类模型适合对feature较少,数量不大的数据进行二分类或者多分类。
优点:训练时间成本低,使用时快速高效。
缺点:由于激活函数非线性因此无确定解析解所以需要使用梯度下降或者牛顿法进行优化,对于outlier十分敏感,同时内在要求数据噪音的分布不能偏离0均值高斯分布较大,否则模型表现不好。当然最大的问题还是模型的高bias导致模型不能抓住更多数据的内在特征,是最简单的机器学习分类模型
实践经验
logistic regression几乎算是机器学习模型中少数可以在实际优化中可以考虑使用牛顿法的模型,由于模型简单因此使用牛顿法不会带来过大的计算负担,同时可以提高模型收敛时的表现:
梯度下降:收敛速度快,计算成本时间成本低。
牛顿法:收敛效果好,困难在于需要计算二阶导数耗费资源,但在某些情况下可以使用高斯牛顿法在避免计算二阶导数的情况下使用牛顿法。

from sklearn.linear_model import LogisticRegression

#搭建和训练模型
#可通过设置LogisticRegression()中的solver参数来指定优化方法:'newton-cg':使用牛顿法,'lbfgs':使用L-BFGS拟牛顿法,'liblinear' :使用 liblinear。
lr = LogisticRegression()
lr.fit(X_train,y_train)

#使用训练完毕的模型进行预测
predict = lr.predict(X_test)

3.K-means:
使用要求无监督分类模型经常使用于数据分析前期,由于使用L2-norm作为优化目标,因此默认需要优化distance function为类球体,同时需要各个类别的数据数量较为平衡
优点:收敛速度快,聚类效果不错,算法的可解释度和可改造性较强,所需要调节的hyper parameter一般只有K。
缺点:对于非凸数据集较难收敛,最终只能确保局部最优,同时对噪音和extreme point十分敏感,hard assigment导致部分信息丢失,若某些类别的数据点较少则很可能无法将其归为一类,这也是K-means在实践中最大的问题。
实践经验
1.根据实际需要(已经事先确定要将数据分为几类或者获知了训练数据集中存在多少类别)或者根据手肘法来确定最佳k值。
2.随机选取训练数据中的k个data point作为起始点,当然最好的办法是首先计算出所有data points的重心,之后在这个重心上加上k个不同的小扰动来得到初始化的k个点。
3.由于存在随机性,因此因对同一K重复多次训练,之后选取loss最低的模型。

from sklearn.cluster import KMeans

#搭建和训练模型,n_clusters即为K值
model = KMeans(n_clusters=2)
model.fit(X_train)

#返回聚类中心和各个training sample所属的类别的index
model.cluster_centers_
model.labels_

#使用先前获得的聚类中心坐标对新数据进行分类
predict = model.predict(X_test)

Gaussian Mixture Model
由于K-means hard assignment的非概率性导致了信息不充分利用,影响了一定的分类准确率,因此我们使用Gaussian Mixture Model(GMM)来将K-means扩展为概率模型.
实践经验
1.在实际情况中,如果我们已经确知某一随机变量背后存在着几种不同的成分,比如我们统计马路上汽车的重量,由于汽车可以大致分为小轿车,客车和运载汽车,这个时候我们可以毫不犹豫地尝试使用mixture model来对随机变量的分布进行估计,值得注意的是,mixture模型不仅仅只有GMM一种,我们不应该将对mixture model的认知仅仅局限于GMM而应该根据具体的情况来发挥想象力设计合适的mixture models,举一个例子:比如我们想要对某条公路上全时段车流量的分布概率进行模拟,这是我们可以大胆设想:马路上的汽车可以分为4类,1是上下班时的汽车,对于这部分汽车可以用若干gaussian进行估计,2是从早上到夜间的运客汽车,对于这部分成分我们可以用一个uniform分布来进行模拟,3为全时段存在的汽车,这部分我们可以用一个非截断的uniform分布来模拟,4为塞车节日狂欢等情况导致的不正常车流,这部分可以用exponential等分布来表示,这样我们的mixture model就可能具有更好的可解释性与实际表现(但是由于Gaussian的灵活性,大多情况下GMM已经足够使用)。
2.GMM相比于多数机器学习模型有着较低的bias和较高的variance,意味着一般情况下我们需要大量的数据才能避免overfitting的产生,但如果实际情况下某一问题下GMM的适用性非常好但是数据量却不足够时,我们就必须要限制Gaussian模型的部分性能来避免overfitting的产生。常用的方式是限制各个cluster gaussian的协方差矩阵的形式(GMM参数covariance_type):

  • full:不限制协方差矩阵形式,意味着矩阵的大多数元素都不为0,在训练数据较多时可以使用
  • tied:各gaussian cluster有着相同的协方差矩阵,需要结合具体问题的考虑使用(i.e. 提前已经确知各gaussian成分有着类似的形状只是均值不同)
  • diag:对角矩阵,矩阵的非对角线元素为0,在数据量不足够多时推荐使用
  • spherical:对角元素相同的对角矩阵,在数据量较缺乏时推荐使用
from sklearn.mixture import GMM
model = GMM(n_components=4)
model.fit(X_train)
predict = model.predict(X_test)

4.K-nearest Neighbors:
使用要求分类模型,训练数据集数量太少时会一定程度降低数据的代表性和feature空间的覆盖体积,而数量太多时又会在使用模型时产生过多的计算负担,所以要求训练数据要尽量有足够的代表性和精度,同时数量要尽可能的小,维度不能太大,简而言之要求训练数据质量高
优点:精度高,对异常值不敏感,当训练数据足够有代表性时表现很好。
缺点:训练和使用时的空间复杂度很高,对训练数据的代表性要求高。

from sklearn.neighbors import KNeighborsClassifier

#搭建和训练模型,
#n_neighbors为k值,weights='uniform' :比重投票,'distance':按照距离反比投票
model = KNeighborsClassifier(n_neighbors = 1)
model.fit(X_train,y_train)

使用训练好的模型进行预测
predict = model.predict(X_test)    

5.Random forest(Decision Tree,Extra-Trees):
实际情况中单独使用决策树的情况并不多,Extra-Trees算法又可视为随机度进一步加大的随机森林(Extra-Trees抑制了过拟合但是增大了Bias,有着很快的训练速度),因此这里主要介绍Random Forest。
使用要求:基于Bagging的分类模型,也可用于回归问题,
优点:随机森林是一种很灵活实用的机器学习方法,具有极好的准确率,能够有效地运行在大数据集上,能够处理具有高维特征的输入样本,而且不需要降维,能够评估各个特征在分类问题上的重要性,对于缺失值问题也能够获得很好得结果你几乎可以把任何东西扔进去,它基本上都是可供使用的。在估计推断映射方面特别好用,以致都不需要像SVM那样做很多参数的调试(短时间内获得模型)有着较强泛化能力和抗过拟合能力(low variance)。
缺点:现存缺点均不显著且可以通过一定的处理得以解决。
实践经验
1:根据大数定律(law of large number),随机森林中的树苗越多,我们模型的分类效果就会越好,在数据的feature较多的情况下,可以考虑结合使用Random subspacesRandom Patches取样策略来进一步产生更多的树苗从而扩充森林的规模。
2.由于本身就是Bagging算法,因此无需提前划分trainset,testset,使用OOB来验证模型表现即可,这也可以充分利用数据,有利于提高模型表现。

#Random Forest
from sklearn.ensemble import RandomForestClassifier

#搭建训练模型
#重要参数:
#n_estimators:树苗数量
#max_leaf_nodes:每一个树苗叶子处的节点数量
#oob_score:是否使用OOD,一般情况(OOD数据数量不过少的时候)推荐务必使用
#n_jobs:使用的计算机线程数量,-1则为全部
model = RandomForestClassifier(n_estimators=500)
model.fit(X_train,y_train)

#使用OOD数据来检验模型表现
rf_clf2.oob_score_

#Decision Tree
from sklearn.tree import DecisionTreeClassifier
model = DecisionTreeClassifier()
model.fit(X_train,y_train)
predict = model.predict(X_test)

#Extra-Trees
from sklearn.ensemble import ExtraTreesClassifier
et_clf = ExtraTreesClassifier(n_estimators=500, bootstrap=True, oob_score=True, random_state=666, n_jobs=-1)
et_clf.fit(X, y)

6.Support Vector Machine(SVM):
使用要求二分类模型(通过one-vs-one或者one-vs-all的方式也可以进行多分类,但会造成部分信息的浪费,同时经过改造也可解决回归问题)。
优点:大名鼎鼎的SVM,使用kernel trick时是仅次于神经网络的强力分类算法(也可用于回归),同时不易过拟合,相关理论已经高度完善,可解释性强(意味着更容易在商业工业中使用),同时由于支持向量的稀疏性意味着在实际使用时的快速高效对于训练数据量的需求不高,在数据量不充足时也有着很不错的表现,意味着SVM是不多可以在数据维度大于数据数量时使用的机器学习模型
缺点:不是直接的多分类模型,只可结合多个SVM来处理多分类问题(部分信息浪费),在处理实际问题时时往往需要大量调参同时如何选择合适的kernel十分tricky,对缺失值敏感,若问题规模较大,在训练时会产生较大的时间和计算成本,最后值得一提的是SVM无法提供直接的概率估计。
实践经验
一般需要大量调参,推荐使用上文提到的gridsearch方法,同时在feature维度很大的时候可能无需使用复杂的非线性kernel,单纯使用linear kernel也可以获得不错的准确率。

from sklearn.svm import SVC

#搭建和训练模型
#SVC()中的重要参数:
#C:表示错误项的惩罚系数C越大,即对分错样本的惩罚程度越大,因此在训练样本中准确率越高,但是泛化能力降低;相反,减小C的话,容许训练样本中有一些误分类错误样本,泛化能力强。对于训练样本带有噪声的情况,一般采用后者,把训练样本集中错误分类的样本作为噪声。
#kernel:
#该参数用于选择模型所使用的核函数,算法中常用的核函数有:
#-- linear:线性核函数
#--  poly:多项式核函数
#--rbf:径像核函数/高斯核
#--sigmod:sigmod核函数
#--precomputed:核矩阵,该矩阵表示自己事先计算好的,输入后算法内部将使用你提供的矩阵进行计算
#degree:该参数只对'kernel=poly'(多项式核函数)有用,是指多项式核函数的阶数n,如果给的核函数参数是其他核函数,则会自动忽略该参数。
#gamma:该参数为核函数系数,只对‘rbf’,‘poly’,‘sigmod’有效。如果gamma设置为auto,代表其值为样本特征数的倒数,即1/n_features,也有其他值可设定。

model = SVC()
model.fit(X_train,y_train)

使用训练好的模型进行预测
predict = model.predict(X_test)    

模型评价
训练模型完毕时,可以使用如下函数来检验模型的表现:
1.分类模型
a.准确率(precision):

model.score(X_test, y_test)

b.confusion matrix以及sklearn中结合了f1 score,recall和precision的classification report:

from sklearn.metrics import classification_report,confusion_matrix
classification_report(y_test,predict)
confusion_matrix(y_test,predict)

这里值得一提的是,过拟合的一个标志就是confusion matrix中结果的unbalanced
2.回归模型
a.MSE,MSER,MAE:

import sklearn.metrics as me
me.mean_absolute_error(y_test,predict)
me.mean_squared_error(y_test,predict)
np.sqrt(me.mean_squared_error(y_test,predict))

b. 可视化:

  • 预测值 vs. 真实值
  • 残差分析,来判断模型的回归倾向
  • 其他可视化方法
import matplotlib.pyplot as plt
plt.scatter(y_test,predict)

import seaborn as sns
sns.distplot(y_test - predict,bins = 15)

下一节将介绍神经网络模型的搭建训练与评价的推荐流程及相关代码~
神经网络机器学习模型的搭建,训练及评价阶段: https://blog.csdn.net/weixin_44563688/article/details/88884468

参考:
机器学习笔记(3)-sklearn支持向量机SVM: https://www.jianshu.com/p/a9f9954355b3
随机森林(Random Forest)详解(转):
https://www.cnblogs.com/gczr/p/7097704.html
机器学习 —— Boosting算法:
https://blog.csdn.net/starter_____/article/details/79328749

转载请告知作者,并附上原始CSDN博客链接。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值