深入淺出學習決策樹(二)

銀河1號發表於2019-03-18

接著上篇文章 深入淺出學習決策樹(一) 繼續介紹決策樹相關內容。

迴歸問題中的決策樹

在預測數值變數時,構造樹的想法保持不變,但質量標準會發生變化。

深入淺出學習決策樹(二)

其中n是葉子中的樣本數,Yi是目標變數的值。簡單地說,通過最小化均值周圍的方差,我們尋找以這樣的方式劃分訓練集的特徵,即每個葉子中的目標特徵的值大致相等。

讓我們生成一些由函式分配並帶有一些噪音的資料。

深入淺出學習決策樹(二)

然後我們將在其上訓練一棵樹並顯示它所做的預測。

n_train = 150        
n_test = 1000       
noise = 0.1
def f(x):
    x = x.ravel()
    return np.exp(-x ** 2) + 1.5 * np.exp(-(x - 2) ** 2)
def generate(n_samples, noise):
    X = np.random.rand(n_samples) * 10 - 5
    X = np.sort(X).ravel()
    y = np.exp(-X ** 2) + 1.5 * np.exp(-(X - 2) ** 2) + \
    np.random.normal(0.0, noise, n_samples)
    X = X.reshape((n_samples, 1))
    return X, y
X_train, y_train = generate(n_samples=n_train, noise=noise)
X_test, y_test = generate(n_samples=n_test, noise=noise)
from sklearn.tree import DecisionTreeRegressor
reg_tree = DecisionTreeRegressor(max_depth=5, random_state=17)
reg_tree.fit(X_train, y_train)
reg_tree_pred = reg_tree.predict(X_test)
plt.figure(figsize=(10, 6))
plt.plot(X_test, f(X_test), "b")
plt.scatter(X_train, y_train, c="b", s=20)
plt.plot(X_test, reg_tree_pred, "g", lw=2)
plt.xlim([-5, 5])
plt.title("Decision tree regressor, MSE = %.2f" % np.sum((y_test - reg_tree_pred) ** 2))
plt.show()
複製程式碼
深入淺出學習決策樹(二)

我們看到決策樹用分段常數函式逼近資料。

3.最近鄰法

最近鄰方法(k-Nearest Neighbors,或k-NN)是另一種非常流行的分類方法,有時也用於迴歸問題。與決策樹一樣,這是最易於理解的分類方法之一。潛在的直覺是你看起來像你的鄰居。更正式地,該方法遵循緊湊性假設:如果足夠好地測量示例之間的距離,則類似示例更可能屬於同一類。

根據最近鄰法,綠球將被分類為“藍色”而不是“紅色”。

深入淺出學習決策樹(二)

再舉一個例子,如果您不知道如何在線上列表上標記藍芽耳機,您可以找到5個類似的耳機,如果其中4個被標記為“配件”,只有1個被標記為“技術”,那麼你還會在“配件”下標註它。

要對測試集中的每個樣本進行分類,需要按順序執行以下操作:

  1. 計算訓練集中每個樣本的距離。
  2. 從訓練集中選擇k個樣本,距離最小。
  3. 測試樣本的類別將是那些k個最近鄰居中最常見的類別。

該方法非常容易適應迴歸問題:在步驟3中,它不返回類,而是返回數字 - 鄰居之間目標變數的平均值(或中值)。

這種方法的一個顯著特點是它的懶惰 - 計算只在預測階段進行,當需要對測試樣本進行分類時。預先沒有從訓練樣例構建模型。相反,回想一下,對於本文前半部分的決策樹,樹是基於訓練集構建的,並且測試用例的分類通過遍歷樹而相對快速地發生。

最近鄰是一種經過深入研究的方法。存在許多重要的定理,聲稱在“無窮無盡”的資料集上,它是最佳的分類方法。經典著作“統計學習要素”的作者認為k-NN是理論上理想的演算法,其使用僅受計算能力和維數災難的限制。

真實應用中最近鄰方法

  • 在某些情況下,k-NN可以作為一個良好的起點(基線);
  • 在Kaggle比賽中,k-NN通常用於構建元特徵(即k-NN預測作為其他模型的輸入)或用於堆疊/混合;
  • 最近鄰居方法擴充套件到推薦系統等其他任務。最初的決定可能是在我們想要提出建議的人的最近鄰居中受歡迎的產品(或服務)的推薦;
  • 實際上,在大型資料集上,近似搜尋方法通常用於最近鄰居。有許多開源庫可以實現這樣的演算法; 看看Spotify的圖書館Annoy

使用k-NN進行分類/迴歸的質量取決於幾個引數:

  • 鄰居的數量k
  • 樣本之間的距離度量(常見的包括漢明,歐幾里得,餘弦和閔可夫斯基距離)。請注意,大多數這些指標都需要縮放資料。簡單來說,我們不希望“薪水”功能(大約數千)影響距離超過“年齡”,通常小於100。
  • 鄰居的權重(每個鄰居可以貢獻不同的權重;例如,樣本越遠,權重越低)。

類KNeighborsClassifier在Scikit學習

該類的主要引數sklearn.neighbors.KNeighborsClassifier是:

  • 權重:( uniform所有權重相等),distance(權重與測試樣本的距離成反比),或任何其他使用者定義的函式;
  • 演算法(可選): bruteball_treeKD_treeauto。在第一種情況下,通過訓練集上的網格搜尋來計算每個測試用例的最近鄰居。在第二和第三種情況下,示例之間的距離儲存在樹中以加速找到最近鄰居。如果將此引數設定為auto,則將根據訓練集自動選擇查詢鄰居的正確方法。
  • leaf_size(可選):如果查詢鄰居的演算法是BallTree或KDTree,則切換到網格搜尋的閾值;
  • 指標:minkowskimanhattaneuclideanchebyshev,或其他。

4.選擇模型引數和交叉驗證

學習演算法的主要任務是能夠探索到看不見的資料。由於我們無法立即檢查新的傳入資料的模型效能(因為我們還不知道目標變數的真實值),因此有必要犧牲一小部分資料來檢查模型的質量。

這通常以兩種方式之一完成:

  • 留出資料集的一部分(保留/保持集)。我們保留訓練集的一小部分(通常從20%到40%),在剩餘資料上訓練模型(原始集合的60-80%),並計算模型的效能指標(例如準確度) - 套裝。
  • 交叉驗證。這裡最常見的情況是k折交叉驗證
深入淺出學習決策樹(二)

在k倍交叉驗證中,模型在原始資料集的不同(K-1)子集上訓練K次(白色)並檢查剩餘子集(每次都是不同的子集,如上所示以橙色表示)。我們獲得K模型質量評估,通常是平均值,以給出分類/迴歸的總體平均質量。

與保持集方法相比,交叉驗證可以更好地評估新資料的模型質量。但是,當您擁有大量資料時,交叉驗證在計算上非常昂貴。

交叉驗證是機器學習中非常重要的技術,也可以應用於統計和計量經濟學。它有助於超引數調整,模型比較,特徵評估等。更多細節可以在這裡找到(Sebastian Raschka的部落格文章)或任何關於機器(統計)學習的經典教科書。

5.應用例項和複雜案例

客戶流失預測任務中的決策樹和最近鄰法

讓我們將資料讀入DataFrame並預處理它。暫時將狀態儲存在單獨的Series物件中,並將其從資料框中刪除。我們將訓練沒有State功能的第一個模型,然後我們將看看它是否有幫助。

df = pd.read_csv('../../data/telecom_churn.csv')
df['International plan'] = pd.factorize(df['International plan'])[0]
df['Voice mail plan'] = pd.factorize(df['Voice mail plan'])[0]
df['Churn'] = df['Churn'].astype('int')
states = df['State']
y = df['Churn']
df.drop(['State', 'Churn'], axis=1, inplace=True)
複製程式碼
深入淺出學習決策樹(二)

讓我們為訓練(X_trainy_train)分配70%的集合,為保留集合(X_holdouty_holdout)分配30%。保持裝置不會參與調整模型的引數。我們將在調整結束後使用它來評估最終模型的質量。讓我們訓練2個模型:決策樹和k-NN。我們不知道哪些引數是好的,所以我們假設一些隨機的引數:樹深度為5,最近鄰居的數量等於10。

from sklearn.model_selection import train_test_split, StratifiedKFold
from sklearn.neighbors import KNeighborsClassifier
X_train, X_holdout, y_train, y_holdout = train_test_split(df.values, y, 
                                                          test_size=0.3, random_state=17)
tree = DecisionTreeClassifier(max_depth=5, random_state=17)
knn = KNeighborsClassifier(n_neighbors=10)
tree.fit(X_train, y_train)
knn.fit(X_train, y_train)
複製程式碼

讓我們用一個簡單的指標評估保留集的預測質量 - 正確答案的比例(準確度)。決策樹做得更好 - 正確答案的百分比約為94%(決策樹)與88%(k-NN)。請注意,此效能是通過使用隨機引數實現的。

from sklearn.metrics import accuracy_score
tree_pred = tree.predict(X_holdout)
print(accuracy_score(y_holdout, tree_pred)) # 0.94
knn_pred = knn.predict(X_holdout)
print(accuracy_score(y_holdout, knn_pred)) # 0.88
複製程式碼

現在,讓我們使用交叉驗證來識別樹的引數。我們將調整每個分割時使用的最大深度和最大特徵數。以下是GridSearchCV如何工作的本質:對於每個唯一的值對,max_depthmax_features使用5倍交叉驗證計算模型效能,然後選擇最佳的引數組合。

from sklearn.model_selection import GridSearchCV, cross_val_score

tree_params = {'max_depth': range(1,11),
               'max_features': range(4,19)}

tree_grid = GridSearchCV(tree, tree_params,
cv=5, n_jobs=-1,
verbose=True)

tree_grid.fit(X_train, y_train)
複製程式碼

讓我們列出交叉驗證的最佳引數和相應的平均精度。

print(tree_grid.best_params_) # {'max_depth': 6, 'max_features': 17}
print(tree_grid.best_score_) # 0.942
print(accuracy_score(y_holdout, tree_grid.predict(X_holdout))) # 0.946
複製程式碼

讓我們畫出結果樹。由於它不完全是玩具示例(其最大深度為6),因此圖片並不小,但如果您在本地開啟從課程倉庫下載的相應圖片,則可以仔細瀏覽樹的全貌。

dot_data = StringIO()
export_graphviz(tree_grid.best_estimator_, feature_names=df.columns, 
                out_file=dot_data, filled=True)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
Image(value=graph.create_png())
複製程式碼
深入淺出學習決策樹(二)

現在,讓我們調整k-NN 的鄰居k的數量:

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
knn_pipe = Pipeline([('scaler', StandardScaler()), 
                     ('knn', KNeighborsClassifier(n_jobs=-1))])
knn_params = {'knn__n_neighbors': range(1, 10)}
knn_grid = GridSearchCV(knn_pipe, knn_params,
                        cv=5, n_jobs=-1, verbose=True)
knn_grid.fit(X_train, y_train)
print(knn_grid.best_params_, knn_grid.best_score_)
# ({'knn__n_neighbors': 7}, 0.886)
複製程式碼

這裡,樹被證明比最近鄰演算法更好:交叉驗證和保持的準確率分別為94.2%/ 94.6%。決策樹表現得非常好,甚至隨機森林(現在讓我們把它想象成一堆一起工作得更好的樹木)在這個例子中,儘管訓練時間更長,卻無法達到更好的效能(95.1%/ 95.3%)。

from sklearn.ensemble import RandomForestClassifier

forest = RandomForestClassifier(n_estimators=100, n_jobs=-1, 
                                random_state=17)
print(np.mean(cross_val_score(forest, X_train, y_train, cv=5))) # 0.949

forest_params = {'max_depth': range(1, 11), 'max_features': range(4, 19)}

forest_grid = GridSearchCV(forest, forest_params,
                           cv=5, n_jobs=-1, verbose=True)

forest_grid.fit(X_train, y_train)

print(forest_grid.best_params_, forest_grid.best_score_) 
# ({'max_depth': 9, 'max_features': 6}, 0.951)
複製程式碼

決策樹的複雜案例

為了繼續討論相關方法的優缺點,讓我們考慮一個簡單的分類任務,其中一棵樹表現良好但是以“過於複雜”的方式進行。讓我們在一個平面上建立一組點(2個特徵),每個點將是兩個類中的一個(紅色為+1,黃色為-1)。如果將其視為分類問題,則看起來非常簡單:類由一行分隔。

def form_linearly_separable_data(n=500, x1_min=0, x1_max=30, 
                                 x2_min=0, x2_max=30):
    data, target = [], []
    for i in range(n):
        x1 = np.random.randint(x1_min, x1_max)
        x2 = np.random.randint(x2_min, x2_max)
        if np.abs(x1 - x2) > 0.5:
            data.append([x1, x2])
            target.append(np.sign(x1 - x2))
    return np.array(data), np.array(target)

X, y = form_linearly_separable_data()

plt.scatter(X[:, 0], X[:, 1], c=y, cmap='autumn', edgecolors='black');
複製程式碼
深入淺出學習決策樹(二)

但是,決策樹構建的邊界過於複雜; 加上樹本身很深。另外,想象一下,樹會對訓練集的30 x 30方格以外的空間進行推廣有多麼糟糕。

tree = DecisionTreeClassifier(random_state=17).fit(X, y)
xx, yy = get_grid(X)
predicted = tree.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
plt.pcolormesh(xx, yy, predicted, cmap='autumn')
plt.scatter(X[:, 0], X[:, 1], c=y, s=100, 
cmap='autumn', edgecolors='black', linewidth=1.5)
plt.title('Easy task. Decision tree complexifies everything');
複製程式碼
深入淺出學習決策樹(二)

雖然解決方案只是一條直線x1 = x2,但我們得到了這種過於複雜的結構。

dot_data = StringIO()
export_graphviz(tree, feature_names=['x1', 'x2'], 
                out_file=dot_data, filled=True)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
Image(value=graph.create_png())
複製程式碼
深入淺出學習決策樹(二)

一個最近鄰居的方法比樹更好,但仍然不如線性分類器(我們的下一個主題)。

knn = KNeighborsClassifier(n_neighbors=1).fit(X, y)
xx, yy = get_grid(X)
predicted = knn.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
plt.pcolormesh(xx, yy, predicted, cmap='autumn')
plt.scatter(X[:, 0], X[:, 1], c=y, s=100, 
cmap='autumn', edgecolors='black', linewidth=1.5);
plt.title('Easy task, kNN. Not bad');
複製程式碼
深入淺出學習決策樹(二)

MNIST手寫數字識別任務中的決策樹和k-NN

現在讓我們看看這兩種演算法如何在現實世界中執行任務。我們將sklearn在手寫數字上使用內建資料集。這個任務就是k-NN工作得非常好的例子。

這裡的圖片是8x8矩陣(每個畫素的白色強度)。然後將每個這樣的矩陣“展開”到長度為64的向量中,並且我們獲得物件的特徵描述。

我們畫一些手寫的數字。我們看到它們是可區分的。

from sklearn.datasets import load_digits
data = load_digits()
X, y = data.data, data.target
f, axes = plt.subplots(1, 4, sharey=True, figsize=(16, 6)) 
for i in range(4): 
    axes[i].imshow(X[i,:].reshape([8,8]), cmap='Greys');
複製程式碼
深入淺出學習決策樹(二)

接下來,讓我們進行與上一個任務相同的實驗,但是,這一次,讓我們更改可調引數的範圍。

讓我們選擇70%的資料集用於訓練(X_trainy_train)和30%用於訓練(X_holdouty_holdout)。保持集不參與模型引數調整; 我們將在最後使用它來檢查結果模型的質量。

X_train, X_holdout, y_train, y_holdout = train_test_split(X, y, test_size=0.3,
                                                          random_state=17)
複製程式碼

讓我們用隨機引數訓練決策樹和k-NN,並對我們的保持集進行預測。我們可以看到k-NN做得更好,但請注意這是隨機引數。

tree = DecisionTreeClassifier(max_depth=5, random_state=17)
knn = KNeighborsClassifier(n_neighbors=10)
tree.fit(X_train, y_train)
knn.fit(X_train, y_train)
tree_pred = tree.predict(X_holdout)
knn_pred = knn.predict(X_holdout)
print(accuracy_score(y_holdout, knn_pred), 
      accuracy_score(y_holdout, tree_pred)) 
# (0.97, 0.666)
複製程式碼

現在讓我們像以前一樣使用交叉驗證來調整我們的模型引數,但是現在我們將考慮到我們擁有比上一個任務更多的功能:64。

tree_params = {'max_depth': [1, 2, 3, 5, 10, 20, 25, 30, 40, 50, 64],
               'max_features': [1, 2, 3, 5, 10, 20 ,30, 50, 64]}
tree_grid = GridSearchCV(tree, tree_params, cv=5, n_jobs=-1,
                         verbose=True)
tree_grid.fit(X_train, y_train)
複製程式碼

讓我們看看最佳引數組合以及交叉驗證的相應準確度:

print(tree_grid.best_params_, tree_grid.best_score_) 
# ({'max_depth': 20, 'max_features': 64}, 0.844)
複製程式碼

這已經超過了66%但不是97%。k-NN在此資料集上的效果更好。在一個最近鄰居的情況下,我們能夠在交叉驗證上達到99%的猜測。

print(np.mean(cross_val_score(KNeighborsClassifier(n_neighbors=1), 
                              X_train, y_train, cv=5))) # 0.987
複製程式碼

讓我們在同一個資料集上訓練一個隨機森林,它在大多數資料集上比k-NN更好。但我們這裡有一個例外。

print(np.mean(cross_val_score(RandomForestClassifier(random_state=17), 
                              X_train, y_train, cv=5))) # 0.935
複製程式碼

你指出我們RandomForestClassifier在這裡沒有調整任何引數是正確的。即使進行調整,訓練精度也不會像一個最近鄰居那樣達到98%。

深入淺出學習決策樹(二)
CV和Holdout是跨模型驗證和保留樣本的正確答案的平均份額。DT代表決策樹,k-NN代表k-最近鄰居,RF代表隨機森林

這個實驗的結論(以及一般建議):首先檢查資料上的簡單模型:決策樹和最近鄰居(下次我們還將邏輯迴歸新增到此列表中)。情況可能就是這些方法已經足夠好了。

最近鄰法的複雜案例

讓我們考慮另一個簡單的例子。在分類問題中,其中一個特徵將與響應向量成比例,但這對最近鄰方法沒有幫助。

def form_noisy_data(n_obj=1000, n_feat=100, random_seed=17):
    np.seed = random_seed
    y = np.random.choice([-1, 1], size=n_obj)

    # first feature is proportional to target
    x1 = 0.3 * y

    # other features are noise

    x_other = np.random.random(size=[n_obj, n_feat - 1])

    return np.hstack([x1.reshape([n_obj, 1]), x_other]), y

X, y = form_noisy_data()
複製程式碼

與往常一樣,我們將研究交叉驗證和保持集的準確性。讓我們構造反映這些量對n_neighbors最近鄰方法中引數的依賴性的曲線。這些曲線稱為驗證曲線。

可以看出,即使你在很大範圍內改變最近鄰居的數量,具有歐幾里德距離的k-NN也不能很好地解決問題。

X_train, X_holdout, y_train, y_holdout = train_test_split(X, y, test_size=0.3,
random_state=17)
from sklearn.model_selection import cross_val_score
cv_scores, holdout_scores = [], []
n_neighb = [1, 2, 3, 5] + list(range(50, 550, 50))
for k in n_neighb:
    knn = KNeighborsClassifier(n_neighbors=k)
    cv_scores.append(np.mean(cross_val_score(knn, X_train, y_train, cv=5)))
    knn.fit(X_train, y_train)
    holdout_scores.append(accuracy_score(y_holdout, knn.predict(X_holdout)))
plt.plot(n_neighb, cv_scores, label='CV')
plt.plot(n_neighb, holdout_scores, label='holdout')
plt.title('Easy task. kNN fails')
plt.legend();
複製程式碼
深入淺出學習決策樹(二)

相反,儘管對最大深度有限制,但決策樹很容易“檢測”資料中的隱藏依賴性。

tree = DecisionTreeClassifier(random_state=17, max_depth=1)
tree_cv_score = np.mean(cross_val_score(tree, X_train, y_train, cv=5))
tree.fit(X_train, y_train)
tree_holdout_score = accuracy_score(y_holdout, tree.predict(X_holdout))
print(‘Decision tree. CV: {}, holdout: {}’.format(tree_cv_score, 
                                                  tree_holdout_score)) 
# Decision tree. CV: 1.0, holdout: 1.0
複製程式碼

在第二個例子中,樹完全解決了問題,而k-NN遇到了困難。然而,這比使用歐幾里德距離更不利於該方法。它不允許我們揭示一個特徵比其他特徵好得多。

6.決策樹的優缺點和最近鄰法

決策樹的利弊

優點:

  • 生成明確的人類可理解的分類規則,例如“如果年齡<25且對摩托車感興趣,則拒絕貸款”。此屬性稱為模型的可解釋性。
  • 決策樹可以很容易地視覺化,即模型本身(樹)和某個測試物件(樹中的路徑)的預測都可以“被解釋”。
  • 快速培訓和預測。
  • 少量模型引數。
  • 支援數字和分類功能。

缺點:

  • 樹對輸入資料中的噪聲非常敏感; 如果稍微修改訓練集,整個模型可能會改變(例如刪除一個特徵,新增一些物件)。這損害了模型的可解釋性。
  • 由決策樹構建的分離邊界有其侷限性 - 它由垂直於其中一個座標軸的超平面組成,在實踐中其質量低於其他一些方法。
  • 我們需要通過調整來避免過度擬合,在每個葉子中設定最小數量的樣本,或者為樹定義最大深度。請注意,過度擬合是所有機器學習方法的問題。
  • 不穩定。對資料的微小更改可以顯著改變決策樹。決策樹集合解決了這個問題(下次討論)。
  • 最優決策樹搜尋問題是NP完全的。在實踐中使用了一些啟發式演算法,例如貪婪搜尋具有最大資訊增益的特徵,但是它不能保證找到全域性最優樹。
  • 難以支援資料中的缺失值。弗裡德曼估計,大約50%的程式碼用於支援CART中的資料差距(該演算法的改進版本已實現sklearn)。
  • 該模型只能插值但不能外推(隨機森林和樹提升也是如此)。也就是說,決策樹對位於特徵空間中的訓練集所設定的邊界框之外的物件進行恆定預測。在我們使用黃色和藍色球的示例中,這意味著模型為位置> 19或<0的所有球提供相同的預測。

最近鄰法的利弊

優點:

  • 簡單的實施。
  • 容易學習。
  • 通常,該方法不僅是分類或迴歸的良好第一解決方案,而且是推薦。
  • 它可以通過選擇正確的度量或核心來適應某個問題(簡而言之,核心可以為複雜物件(如圖形)設定相似性操作,同時保持k-NN方法相同)。順便說一句,Alexander Dyakonov,前任前1名kaggler,喜歡最簡單的k-NN,但卻擁有調整物件的相似度量。
  • 良好的可解釋性。也有例外:如果鄰居數量很大,可解釋性就會惡化(“我們沒有給他貸款,因為他與350個客戶類似,其中70個是壞客戶,比平均水平高12%對於資料集“)。

缺點:

  • 與演算法的組合相比,被認為是快速的方法,但是在現實生活中用於分類的鄰居的數量通常很大(100-150),在這種情況下演算法將不像決策樹那樣快速地執行。
  • 如果資料集具有許多變數,則很難找到正確的權重並確定哪些特徵對於分類/迴歸不重要。
  • 依賴於物件之間的選定距離度量。預設選擇歐幾里德距離通常是沒有根據的。您可以通過網格搜尋引數找到一個好的解決方案,但這對於大型資料集來說變得非常耗時。
  • 沒有理論上的方法可以選擇鄰居的數量 - 只有網格搜尋(儘管對於所有模型的所有超引數都是如此)。在少數鄰居的情況下,該方法對異常值敏感,即傾向於過度擬合。
  • 通常,由於“維數的詛咒”而存在許多功能時,它不能很好地工作。ML社群的知名成員Pedro Domingos教授在他的熱門論文“機器學習中幾個有用的事情”中談到了這一點 ; 在本章的深度學習書中也描述了“維度的詛咒” 。

這是很多資訊,但是,希望這篇文章很長一段時間對你很有幫助:)

點選英文原文連結

更多文章歡迎訪問: www.apexyun.com

公眾號:銀河系1號

聯絡郵箱:public@space-explore.com

(未經同意,請勿轉載)

深入淺出學習決策樹(二)

微信掃一掃關注該公眾號


相關文章