參考:菜菜的sklearn課堂
文章目錄
1 概述
隨機森林本質爲集成學習的一種,可用於迴歸和分類,是以決策樹爲基礎的更高級的算法。
隨機森林中每個決策樹都有一個自己的結果,隨機森林通過統計每個決策樹的結果,選擇投票數最多的結果作爲其最終結果。
以隨機的方式構建出的一個森林,而這個森林是由很多的相互不關聯的決策樹組成。通過建立n個模型的組合來解決單一預測問題。
1.1 集成算法概述
集成算法會考慮多個評估器的建模結果,彙總之後得到一個綜合的結果,以此來獲取比單個模型更好的迴歸或分類表現。
多個模型集成成爲的模型叫做集成評估器,組成集成評估器的每個模型都叫做基評估器
(base estimator)
通常來說,有三類集成算法:裝袋法(Bagging),提升法(Boosting)和stacking。
Bagging的核心思想是構建多個相互獨立的評估器,然後對其預測進行平均或多數表決原則來決定集成評估器的結果。裝袋法的代表模型就是隨機森林。
)
Boosting中,基評估器是相關的,是按順序一 一構建的。其核心思想是結合弱評估器的力量一次次對難以評估的樣本進行預測,從而構成一個強評估器。提升法的代表模型有Adaboost和梯度提升樹。
1.2 sklearn中的集成算法
sklearn中的集成算法模塊ensemble,集成算法中,有一半以上都是樹的集成模型
2 隨機森林分類器 RandomForestClassifier
隨機森林是非常具有代表性的Bagging集成算法,它的所有基評估器都是決策樹,分類樹組成的森林就叫做隨機森林分類器,迴歸樹所集成的森林就叫做隨機森林迴歸器。
2.1 參數
sklearn.ensemble.RandomForestClassifier (
n_estimators=’10’,
criterion=’gini’,
max_depth=None,
min_samples_split=2,
min_samples_leaf=1,
min_weight_fraction_leaf=0.0,
max_features=’auto’,
max_leaf_nodes=None,
min_impurity_decrease=0.0,
min_impurity_split=None,
bootstrap=True,
oob_score=False,
n_jobs=None,
random_state=None,
verbose=0,
warm_start=False,
class_weight=None)
2.2 重要參數
2.2.1 控制基評估器的參數
單個決策樹的準確率越高,隨機森林的準確率也會越高,因爲裝袋法Bagging是依賴於平均值或者少數服從多數原則來決定集成的結果的。
2.2.2 n_estimators
森林中樹木的數量,即基評估器的數量。n_estimators越大,模型的效果往往越好。
n_estimators達到一定的程度之後,隨機森林的精確性往往不在上升或開始波動,並且,n_estimators越大,需要的計算量和內存也越大,訓練的時間也會越來越長。
一般選擇0到200之間的一個數量
sklearn建模—隨機森林和單個決策樹效益的對比
# 導包
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_wine
from sklearn.model_selection import cross_val_score
import matplotlib.pyplot as plt
# 導入需要的數據集
wine = load_wine()
wine.data # 特徵矩陣
wine.target # 標籤
# 建模 決策樹 隨機森林對比
Xtrain, Xtest, Ytrain, Ytest = train_test_split(wine.data,wine.target,test_size=0.3)# 劃分測試集、訓練集
clf = DecisionTreeClassifier(random_state=0) # 決策樹實例化
rfc = RandomForestClassifier(random_state=0) # 隨機森林實例化
# 訓練
clf = clf.fit(Xtrain,Ytrain)
rfc = rfc.fit(Xtrain,Ytrain)
# 得分
score_Single_Tree = clf.score(Xtest,Ytest)
score_Random_Forest = rfc.score(Xtest,Ytest)
print(score_Single_Tree,score_Random_Forest)
# 0.9074074074074074 0.9629629629629629
一次交叉驗證效果對比
# 隨機森林交叉驗證
rfc = RandomForestClassifier(n_estimators=25)
rfc_s = cross_val_score(rfc,wine.data,wine.target,cv=10)
# 單個決策樹的交叉驗證
clf = DecisionTreeClassifier()
clf_s = cross_val_score(clf,wine.data,wine.target,cv=10)
plt.plot(range(1,11),rfc_s,label = "RandomForest")
plt.plot(range(1,11),clf_s,label = "Decision Tree")
plt.legend()
plt.show()
十組交叉驗證下的效果對比
rfc_l = []
clf_l = []
for i in range(10):
# 隨機森林
rfc = RandomForestClassifier(n_estimators=25)
rfc_s = cross_val_score(rfc,wine.data,wine.target,cv=10).mean()
rfc_l.append(rfc_s)
# 決策樹
clf = DecisionTreeClassifier()
clf_s = cross_val_score(clf,wine.data,wine.target,cv=10).mean()
clf_l.append(clf_s)
plt.plot(range(1,11),rfc_l,label = "Random Forest")
plt.plot(range(1,11),clf_l,label = "Decision Tree")
plt.legend()
plt.show()
n_estimators的學習曲線
因爲代碼跑的時間太長,所以 n_estimators 限制在 1-50
superpa = []
for i in range(50):
rfc = RandomForestClassifier(n_estimators=i+1,n_jobs=-1)
rfc_s = cross_val_score(rfc,wine.data,wine.target,cv=10).mean()
superpa.append(rfc_s)
print(max(superpa),superpa.index(max(superpa))) # 0.9888888888888889 15
plt.figure(figsize=[20,5])
plt.plot(range(1,51),superpa)
plt.show()
2.2.3 random_state
隨機森林的本質是一種裝袋集成算法(bagging),裝袋集成算法是對基評估器的預測結果進行平均或用多數表決原則來決定集成評估器的結果。
在分類樹中,一個random_state只控制生成一棵樹,而隨機森林中的random_state控制的是生成森林的模式,其中的每一棵樹的random_state都不一樣,而非讓一個森林中只有一棵樹。
當random_state固定時,隨機森林中生成的是一組固定的樹,但每棵樹依然是不一致的,這是用”隨機挑選特徵進行分枝“的方法得到的隨機性。並且我們可以證明,當這種隨機性越大的時候,袋裝法的效果一般會越來越好。
用袋裝法集成時,基分類器應當是相互獨立的,是不相同的
但這種做法的侷限性是很強的,當我們需要成千上萬棵樹的時候,數據不一定能夠提供成千上萬的特徵來讓我們構築儘量多儘量不同的樹。因此,除了random_state。還需要其他的隨機性。
rfc = RandomForestClassifier(n_estimators=20,random_state=2)
rfc = rfc.fit(Xtrain, Ytrain)
#隨機森林 使用屬性estimators_,查看森林中所有樹的狀況
rfc.estimators_
"""
[DecisionTreeClassifier(ccp_alpha=0.0, class_weight=None, criterion='gini',
max_depth=None, max_features='auto', max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort='deprecated',
random_state=1872583848, splitter='best'),
DecisionTreeClassifier(ccp_alpha=0.0, class_weight=None, criterion='gini',
max_depth=None, max_features='auto', max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort='deprecated',
random_state=794921487, splitter='best'),
...
DecisionTreeClassifier(ccp_alpha=0.0, class_weight=None, criterion='gini',
max_depth=None, max_features='auto', max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort='deprecated',
random_state=570104212, splitter='best')]
"""
# 除random_state,每一棵樹的其他參數都一樣
for i in range(len(rfc.estimators_)):
print(rfc.estimators_[i].random_state)
"""
1872583848
794921487
111352301
1853453896
213298710
1922988331
1869695442
2081981515
1805465960
1376693511
1418777250
663257521
878959199
854108747
512264917
515183663
1287007039
2083814687
1146014426
570104212
"""
2.2.4 bootstrap & oob_score
要讓基分類器儘量都不一樣,一種很容易理解的方法是使用不同的訓練集來進行訓練,而袋裝法正是通過有放回的隨機抽樣技術來形成不同的訓練數據,bootstrap就是用來控制抽樣技術的參數。
隨機採樣
bootstrap參數默認True,代表採用這種有放回的隨機抽樣技術
有放回的隨機抽樣技術
有放回的隨機抽樣會使一些樣本可能在同一個自助集中出現多次,而其他一些卻可能
被忽略,一般來說,自助集大約平均會包含63%的原始數據。因此,會有約37%的訓練數據被浪費掉,沒有參與建模,這些數據被稱爲袋外數據(oob)
因此,在使用隨機森林時,可以不劃分測試集和訓練集,只需要用袋外數據來測試我們的模型即可。
如果希望用袋外數據來測試,則需要在實例化時就將oob_score這個參數調整爲True,訓練完畢之後,可以用oob_score_來查看在袋外數據上測試的結果:
# 默認bootstrap=True
rfc = RandomForestClassifier(n_estimators=25,bootstrap=True, oob_score=True)
rfc = rfc.fit(wine.data,wine.target)
#重要屬性oob_score_
rfc.oob_score_ # 0.9887640449438202
在做隨機森林建模的時候,可以選擇袋外數據;也可以劃分測試集、訓練集,交叉驗證
2.2.5 重要屬性和接口
rfc = RandomForestClassifier(n_estimators=25)
rfc = rfc.fit(Xtrain, Ytrain)
rfc.score(Xtest,Ytest)
# 特徵重要性
[*zip(wine.feature_names,rfc.feature_importances_)]
"""
[('alcohol', 0.06896540423597283),
('malic_acid', 0.052688046303931875),
('ash', 0.014399355972368931),
('alcalinity_of_ash', 0.027490363477529634),
('magnesium', 0.043205031258377595),
('total_phenols', 0.052816448485066274),
('flavanoids', 0.1361530184886118),
('nonflavanoid_phenols', 0.007814582111531488),
('proanthocyanins', 0.0029810556020386588),
('color_intensity', 0.2198296566555652),
('hue', 0.10997900350269595),
('od280/od315_of_diluted_wines', 0.09759078271547955),
('proline', 0.16608725119083018)]
"""
# 返回這個樣本在這棵樹中所在的葉子節點所在的索引
rfc.apply(Xtest)
"""
array([[15, 8, 14, ..., 14, 19, 13],
[ 2, 5, 17, ..., 8, 15, 6],
[ 9, 5, 5, ..., 3, 4, 2],
...,
[15, 8, 21, ..., 14, 19, 13],
[ 9, 1, 11, ..., 3, 2, 2],
[ 9, 1, 19, ..., 8, 2, 2]], dtype=int64)
"""
rfc.predict(Xtest)
"""
array([0, 2, 1, 0, 2, 1, 1, 0, 0, 1, 1, 2, 0, 1, 0, 2, 1, 0, 1, 0, 0, 2,
1, 1, 1, 0, 0, 2, 1, 1, 1, 2, 2, 0, 1, 0, 1, 0, 2, 2, 2, 2, 2, 0,
0, 0, 2, 0, 0, 2, 2, 0, 1, 1])
"""
# 每一個樣本對應的每一類標籤的概率
rfc.predict_proba(Xtest)
"""
array([[0.96, 0.04, 0. ],
[0. , 0. , 1. ],
...
[0. , 0.84, 0.16]])
"""
3 RandomForestRegressor
所有的參數,屬性與接口,全部和隨機森林分類器一致。僅有的不同就是迴歸樹與分類樹的不同,不純度的指標,參數Criterion不一致。
from sklearn.datasets import load_boston
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestRegressor
boston = load_boston()
regressor = RandomForestRegressor(n_estimators=100,random_state=0)
cross_val_score(regressor, boston.data, boston.target, cv=10,scoring = "neg_mean_squared_error")
3.1 隨機森林迴歸應用—填補缺失值
在sklearn中,可以使用sklearn.impute.SimpleImputer來輕鬆地將均值,中值,或者其他最常用的數值填補到數據中
使用均值,0,和隨機森林迴歸來填補缺失值,並驗證四種狀況下的擬合狀況,找出對使用的數據集來說最佳的缺失值填補方法。
導包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_boston
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score
加載數據集
dataset = load_boston()
# 總共506*13=6578個數據
# 完整的數據
X_full, y_full = dataset.data, dataset.target
n_samples = X_full.shape[0] # 506
n_features = X_full.shape[1] # 13
構造缺失值數據集
rng = np.random.RandomState(0)
# 缺失率爲0.5,即構造的缺失值佔據一半
missing_rate = 0.5
n_missing_samples = int(np.floor(n_samples * n_features * missing_rate))
# 缺失值索引
missing_col_index = rng.randint(0,n_features,n_missing_samples)
missing_row_index = rng.randint(0,n_samples,n_missing_samples)
X_missing = X_full.copy()
y_missing = y_full.copy()
# 替換,構造缺失值數據集
X_missing[missing_row_index,missing_col_index] = np.nan
X_missing = pd.DataFrame(X_missing)
注意:這裏特徵矩陣是有缺失的,而標籤列完整的
(1)使用均值進行填補
#使用均值進行填補
from sklearn.impute import SimpleImputer
imp_mean = SimpleImputer(missing_values=np.nan, strategy='mean')
X_missing_mean = imp_mean.fit_transform(X_missing)
#使用0進行填補
imp_0 = SimpleImputer(missing_values=np.nan, strategy="constant",fill_value=0)
X_missing_0 = imp_0.fit_transform(X_missing)
# 在sklearn中,可以使用sklearn.impute.SimpleImputer來輕鬆地將均值,中值,或者其他最常用的數值填補到數據中
from sklearn.impute import SimpleImputer
# 實例化
imp_mean = SimpleImputer(missing_values=np.nan, strategy='mean')
# 訓練和導出 X_missing_mean 爲用均值填充卻只是的數據集
X_missing_mean = imp_mean.fit_transform(X_missing)
# 查看缺失值是否全部被替換
pd.DataFrame(X_missing_mean).isnull().sum()
'''
0 0
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
9 0
10 0
11 0
12 0
dtype: int64
'''
(2)使用 0 進行填補
#使用0進行填補
imp_0 = SimpleImputer(missing_values=np.nan, strategy="constant",fill_value=0)
X_missing_0 = imp_0.fit_transform(X_missing)
# 查看缺失值是否全部被替換
pd.DataFrame(X_missing_mean).isnull().sum()
(3)使用隨機森林迴歸進行填補
對於一個有n個特徵的數據來說,其中特徵T有缺失值,我們就把特徵T當作標籤,其他的
n-1個特徵 + 原本的標籤 = 新的特徵矩陣
那對於T來說,它沒有缺失的部分,就是我們的 Y_train,這部分數據既有標籤也有特徵,而它缺失的部分,只有特徵沒有標籤,就是我們需要預測的部分。
特徵T不缺失的值對應的其他n-1個特徵 + 本來的標籤:X_train
特徵T不缺失的值:Y_train
特徵T缺失的值對應的其他n-1個特徵 + 本來的標籤:X_test
特徵T缺失的值:未知,我們需要預測的 Y_test
那如果數據中除了特徵T之外,其他特徵也有缺失值怎麼辦?
答案是遍歷所有的特徵,從缺失最少的開始進行填補(因爲填補缺失最少的特徵所需要的準確信息最少)。填補一個特徵時,先將其他特徵的缺失值用0代替,每完成一次迴歸預測,就將預測值放到原本的特徵矩陣中,再繼續填補下一個特徵。每一次填補完畢,有缺失值的特徵會減少一個,所以每次循環後,需要用0來填補的特徵就越來越少。當進行到最後一個特徵時(這個特徵應該是所有特徵中缺失值最多的),已經沒有任何的其他特徵需要用0來進行填補了,而我們已經使用迴歸爲其他特徵填補了大量有效信息,可以用來填補缺失最多的特徵。遍歷所有的特徵後,數據就完整,不再有缺失值了。
#用隨機森林迴歸來填補缺失值
X_missing_reg = X_missing.copy()
# 查看缺失值情況
pd.DataFrame(X_missing_reg).isnull().sum()
'''
0 200
1 201
2 200
3 203
4 202
5 201
6 185
7 197
8 196
9 197
10 204
11 214
12 189
dtype: int64
'''
# 缺失值從小到大的特徵順序
# argsort 返回從小到大的順序所對應的索引
sortindex = np.argsort(X_missing_reg.isnull().sum(axis=0)).values
'''
array([ 6, 12, 8, 7, 9, 0, 2, 1, 5, 4, 3, 10, 11], dtype=int64)
'''
# 遍歷所有的特徵,從缺失最少的開始進行填補,每完成一次迴歸預測,就將預測值放到原本的特徵矩陣中,再繼續填補下一個特徵
for i in sortindex:
#構建我們的新特徵矩陣和新標籤
df = X_missing_reg # 充當中間數據集
fillc = df.iloc[:,i] # 缺失值最少的特徵列
# 除了第 i 特徵列,剩下的特徵列+原有的完整標籤 = 新的特徵矩陣
df = pd.concat([df.iloc[:,df.columns != i],pd.DataFrame(y_full)],axis=1)
#在新特徵矩陣中,對含有缺失值的列,進行0的填補 ,沒循環一次,用0填充的列越來越少
df_0 =SimpleImputer(missing_values=np.nan,strategy='constant',fill_value=0).fit_transform(df)
#找出訓練集和測試集
# 標籤
Ytrain = fillc[fillc.notnull()] # 沒有缺失的部分,就是 Y_train
Ytest = fillc[fillc.isnull()] # 不是需要Ytest的值,而是Ytest的索引
# 特徵矩陣
Xtrain = df_0[Ytrain.index,:]
Xtest = df_0[Ytest.index,:] # 有缺失值的特徵情況
rfc = RandomForestRegressor(n_estimators=100) # 實例化
rfc = rfc.fit(Xtrain, Ytrain) # 訓練
Ypredict = rfc.predict(Xtest) # 預測結果,就是要填補缺失值的值
#將填補好的特徵返回到我們的原始的特徵矩陣中
X_missing_reg.loc[X_missing_reg.iloc[:,i].isnull(),i] = Ypredict
# 最後,再次查看缺失值是否全部被替換
pd.DataFrame(X_missing_reg).isnull().sum()
'''
0 0
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
9 0
10 0
11 0
12 0
dtype: int64
'''
三種方法效果對比
對填補好的數據進行建模
X = [X_full,X_missing_mean,X_missing_0,X_missing_reg]
mse = []
std = []
for x in X:
estimator = RandomForestRegressor(random_state=0, n_estimators=100)
scores = cross_val_score(estimator,x,y_full,scoring='neg_mean_squared_error',cv=5).mean()
# 負的軍方誤差
mse.append(scores * -1)
[*zip(['X_full','X_missing_mean','X_missing_0','X_missing_reg'],mse)]
'''
[('X_full', 21.62860460743544),
('X_missing_mean', 40.84405476955929),
('X_missing_0', 49.50657028893417),
('X_missing_reg', 20.760516310561048)]
'''
from pyecharts import options as opts
from pyecharts.charts import Bar
c = (
Bar(init_opts=opts.InitOpts())
.add_xaxis(['X_full','X_missing_mean','X_missing_0','X_missing_reg'])
.add_yaxis("",mse)
.render("C:/bar.html")
)
# 或者
x_labels = ['Full data','Zero Imputation','Mean Imputation','Regressor Imputation']
colors = ['r', 'g', 'b', 'orange']
plt.figure(figsize=(12, 6))
ax = plt.subplot(111)
for i in np.arange(len(mse)):
ax.barh(i, mse[i],color=colors[i], alpha=0.6, align='center')
ax.set_title('Imputation Techniques with Boston Data')
ax.set_xlim(left=np.min(mse) * 0.9,right=np.max(mse) * 1.1)
ax.set_yticks(np.arange(len(mse)))
ax.set_xlabel('MSE')
ax.set_yticklabels(x_labels)
plt.show()
4 機器學習中調參的基本思想
4.1 泛化誤差
衡量模型在未知數據上的準確率的指標
當模型太複雜,模型就會過擬合,泛化能力就不夠,所以泛化誤差大。當模型太簡單,模型就會欠擬合,擬合能力就不夠,所以誤差也會大。只有當模型的複雜度剛剛好的才能夠達到泛化誤差最小的目標。
樹模型是天生位於圖的右上角的模型,隨機森林是以樹模型爲基礎,所以隨機森林也是天生複雜度高的模型
調參之前,要先判斷,模型現在究竟處於圖像的哪一邊。
1)模型太複雜或者太簡單,都會讓泛化誤差高,我們追求的是位於中間的平衡點
2)模型太複雜就會過擬合,模型太簡單就會欠擬合
3)對樹模型和樹的集成模型來說,樹的深度越深,枝葉越多,模型越複雜
4)樹模型和樹的集成模型的目標,都是減少模型複雜度,把模型往圖像的左邊移動
在調參的時候,可以參考這個順序
5 實例:隨機森林在乳腺癌數據上的調參
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import cross_val_score
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
data = load_breast_cancer()
rfc = RandomForestClassifier(n_estimators=100,random_state=90)
score_pre = cross_val_score(rfc,data.data,data.target,cv=10).mean()
score_pre # 0.9638809523809524
調參
隨機森林調整的第一步:無論如何先來調n_estimators
首先畫學習曲線
scorel = []
for i in range(0,200,10):
rfc = RandomForestClassifier(n_estimators=i+1,n_jobs=-1,random_state=90)
score = cross_val_score(rfc,data.data,data.target,cv=10).mean()
scorel.append(score)
print(max(scorel),(scorel.index(max(scorel))*10)+1)
plt.figure(figsize=[20,5])
plt.plot(range(1,201,10),scorel)
plt.show()
0.9639265664160402 71
在 n_estimators = 71 左右 ,有一個峯值
在確定好的範圍內,進一步細化學習曲線
scorel = []
for i in range(65,75):
rfc = RandomForestClassifier(n_estimators=i,n_jobs=-1,random_state=90)
score = cross_val_score(rfc,data.data,data.target,cv=10).mean()
scorel.append(score)
print(max(scorel),([*range(65,75)][scorel.index(max(scorel))]))
plt.figure(figsize=[20,5])
plt.plot(range(65,75),scorel)
plt.show()
0.9666353383458647 73
爲網格搜索做準備,書寫網格搜索的參數
有一些參數是沒有參照的,很難說清一個範圍,這種情況下我們使用學習曲線,看趨勢
從曲線跑出的結果中選取一個更小的區間,再跑曲線
param_grid = {'n_estimators':np.arange(0, 200, 10)}
param_grid = {'max_depth':np.arange(1, 20, 1)}
param_grid = {'max_leaf_nodes':np.arange(25,50,1)}
有一些參數是可以找到一個範圍的,或者說我們知道他們的取值和隨着他們的取值,模型的整體準確率會如何變化,這樣的參數我們就可以直接跑網格搜索
param_grid = {'criterion':['gini', 'entropy']}
param_grid = {'min_samples_split':np.arange(2, 2+20, 1)}
param_grid = {'min_samples_leaf':np.arange(1, 1+10, 1)}
param_grid = {'max_features':np.arange(5,30,1)}
開始按照參數對模型整體準確率的影響程度進行調參,首先調整max_depth
param_grid = {'max_depth':np.arange(1, 20, 1)}
# 一般根據數據的大小來進行一個試探,數據很小,可以採用1~10,或者1~20這樣的試探
# 大型數據應該嘗試30~50層深度(或許還不足夠
# 更應該畫出學習曲線,來觀察深度對模型的影響
rfc = RandomForestClassifier(n_estimators=39,random_state=90)
GS = GridSearchCV(rfc,param_grid,cv=10)
GS.fit(data.data,data.target)
GS.best_params_ # 顯示調整出來的最佳參數
GS.best_score_ # 最佳參數的準確率
根據分數,推測模型屬於下圖左邊還是右邊
調整一下max_features,看看模型如何變化。
調整min_samples_leaf
繼續嘗試min_samples_split
最後嘗試一下criterion
調整完畢,總結出模型的最佳參數