关于机器学习:机器学习算法随机森林

30次阅读

共计 11339 个字符,预计需要花费 29 分钟才能阅读完成。

在经典机器学习中,随机森林始终是一种灵丹妙药类型的模型。

该模型很棒有几个起因:

  • 与许多其余算法相比,须要较少的数据预处理,因而易于设置
  • 充当分类或回归模型
  • 不太容易适度拟合
  • 能够轻松计算特色重要性

在本文中,我想更好地了解形成随机森林的组件。为实现这一点,我将把随机森林解构为最根本的组成部分,并解释每个计算级别中产生的事件。到最初,咱们将对随机森林的工作原理以及如何更直观地应用它们有更深刻的理解。咱们将应用的示例将侧重于分类,但许多准则也实用于回归场景。

1. 运行随机森林

让咱们从调用经典的随机森林模式开始。这是最高级别,也是许多人在用 Python 训练随机森林时所做的。

如果我想运行随机森林来预测我的指标列,我只需执行以下操作

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(df.drop('target', axis=1), df['target'], test_size=0.2, random_state=0)

# Train and score Random Forest 
simple_rf_model = RandomForestClassifier(n_estimators=100, random_state=0)
simple_rf_model.fit(X_train, y_train)
print(f"accuracy: {simple_rf_model.score(X_test, y_test)}")

# accuracy: 0.93

运行随机森林分类器非常简单。我刚刚定义了 n_estimators 参数并将 random_state 设置为 0。我能够依据集体教训通知你,很多人只会看着那个 .93,很快乐,而后在野外部署那个货色。

simple_rf_model = RandomForestClassifier(n_estimators=100, random_state=0)

随机状态是大多数数据迷信模型的一个特色,它确保其他人能够重现你的工作。咱们不会太放心那个参数。

让咱们深刻理解一下 n_estimators。如果咱们看一下 scikit-learn 文档,定义是这样的:

森林中树木的数量。

2. 考察树木的数量

在这一点上,让咱们更具体地定义随机森林。随机森林是一种集成模型,它是许多决策树的共识。该定义可能不残缺,但咱们会回来探讨它。

这可能会让您认为,如果将其合成为如下内容,您可能会失去一个随机森林:

# Create decision trees
tree1 = DecisionTreeClassifier().fit(X_train, y_train)
tree2 = DecisionTreeClassifier().fit(X_train, y_train)
tree3 = DecisionTreeClassifier().fit(X_train, y_train)

# predict each decision tree on X_test
predictions_1 = tree1.predict(X_test)
predictions_2 = tree2.predict(X_test)
predictions_3 = tree3.predict(X_test)
print(predictions_1, predictions_2, predictions_3)

# take the majority rules
final_prediction = np.array([np.round((predictions_1[i] + predictions_2[i] + predictions_3[i])/3) for i in range(len(predictions_1))])
print(final_prediction)

在下面的例子中,咱们在 X_train 上训练了 3 棵决策树,这意味着 n_estimators = 3。在训练完这 3 棵树之后,咱们在同一测试集上预测每棵树,而后最终采纳 3 棵树中的 2 棵树进行预测。

有点情理,但这看起来并不完全正确。如果所有的决策树都在雷同的数据上进行训练,它们会不会大多得出雷同的论断,从而否定集成的劣势?

3. 置换抽样

让咱们在之前的定义中增加一个词:随机森林是一种集成模型,它是许多不相干决策树的共识。

决策树能够通过两种形式变得不相干:

  1. 您有足够大的数据集大小,您能够在其中将数据的独特局部采样到每个决策树。这不是很风行,而且通常须要大量数据。
  2. 您能够利用一种称为替换采样的技术。有放回抽样意味着从总体中抽取的样本在抽取下一个样本之前返回到总体中。

为了解释有放回抽样,假如我有 5 个弹珠和 3 种颜色,所以我的总体看起来像这样:

blue, blue, red, green, red

如果我想采样一些弹珠,我通常会挑出一对,最初可能会是:

blue, red

这是因为一旦我捡起红色,我就没有把它放回原来的弹珠堆中。

然而,如果我进行有放回抽样,我实际上能够两次拿起我的任何弹珠。因为红色回到了我的筹码中,所以我有机会再次捡起它。

red, red

在随机森林中,默认构建的样本大概是原始种群大小的 2/3。如果我的原始训练数据是 1000 行,那么我提供给树的训练数据样本可能是 670 行左右。也就是说,在构建随机森林时尝试不同的采样率将是一个很好的调整参数。

与最初一个片段不同,上面的代码更靠近 n_estimators = 3 的随机森林代码。

import numpy as np
import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split

# Take 3 samples with replacement from X_train for each tree
df_sample1 = df.sample(frac=.67, replace=True)
df_sample2 = df.sample(frac=.67, replace=True)
df_sample3 = df.sample(frac=.67, replace=True)

X_train_sample1, X_test_sample1, y_train_sample1, y_test_sample1 = train_test_split(df_sample1.drop('target', axis=1), df_sample1['target'], test_size=0.2)
X_train_sample2, X_test_sample2, y_train_sample2, y_test_sample2 = train_test_split(df_sample2.drop('target', axis=1), df_sample2['target'], test_size=0.2)
X_train_sample3, X_test_sample3, y_train_sample3, y_test_sample3 = train_test_split(df_sample3.drop('target', axis=1), df_sample3['target'], test_size=0.2)

# Create the decision trees
tree1 = DecisionTreeClassifier().fit(X_train_sample1, y_train_sample1)
tree2 = DecisionTreeClassifier().fit(X_train_sample2, y_train_sample2)
tree3 = DecisionTreeClassifier().fit(X_train_sample3, y_train_sample3)

# predict each decision tree on X_test
predictions_1 = tree1.predict(X_test)
predictions_2 = tree2.predict(X_test)
predictions_3 = tree3.predict(X_test)
df = pd.DataFrame([predictions_1, predictions_2, predictions_3]).T
df.columns = ["tree1", "tree2", "tree3"]

# take the majority rules 
final_prediction = np.array([np.round((predictions_1[i] + predictions_2[i] + predictions_3[i])/3) for i in range(len(predictions_1))])
preds = pd.DataFrame([predictions_1, predictions_2, predictions_3, final_prediction, y_test]).T.head(20)
preds.columns = ["tree1", "tree2", "tree3", "final", "label"]
preds

4. 装袋分类器

咱们将在此时引入一种称为疏导聚合的新算法,也称为装袋,但请释怀,这将与随机森林相关联。咱们引入这个新概念的起因是因为正如咱们在下图中所看到的,到目前为止咱们所做的所有实际上都是 BaggingClassifier 所做的!

在上面的代码中,BaggingClassifier 有一个名为 bootstrap 的参数,它实际上执行了咱们刚刚手动执行的带替换采样步骤。sklearn 随机森林实现也存在雷同的参数。如果 bootstrapping 为假,咱们将为每个分类器应用整个群体。

import numpy as np
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier

# Number of trees to use in the ensemble
n_estimators = 3

# Initialize the bagging classifier
bag_clf = BaggingClassifier(DecisionTreeClassifier(), n_estimators=n_estimators, bootstrap=True)

# Fit the bagging classifier on the training data
bag_clf.fit(X_train, y_train)

# Make predictions on the test data
y_pred = bag_clf.predict(X_test)
pd.DataFrame([y_pred, y_test]).T

BaggingClassifiers 很棒,因为您能够将它们与未命名为决策树的估算器一起应用!您能够插入许多算法,而后 Bagging 将其变成一个集成解决方案。随机森林算法实际上扩大了装袋算法(如果 bootstrapping = true),因为它局部利用装袋来造成不相干的决策树。

然而,即便 bootstrapping = false,随机森林也会多走一步以真正确保树不相干——特色采样。

5. 特色采样

特色抽样意味着不仅对行进行抽样,对列也进行抽样。与行不同,随机森林的列是在没有替换的状况下进行采样的,这意味着咱们不会有反复的列来训练 1 棵树。

有很多办法能够对特色进行采样。您能够指定要采样的固定最大特色数,取特色总数的平方根,或尝试应用日志。这些办法中的每一种都有衡量取舍,并且将取决于您的数据和用例。

上面的代码片段应用 sqrt 技术对列进行采样,对行进行采样,训练 3 个决策树,并应用少数规定进行预测。咱们首先执行替换采样,他们对列进行采样,训练咱们的个体树,让咱们的树对咱们的测试数据进行预测,而后采纳少数规定共识。

import numpy as np
import pandas as pd
import math
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split

# take 3 samples from X_train for each tree
df_sample1 = df.sample(frac=.67, replace=True)
df_sample2 = df.sample(frac=.67, replace=True)
df_sample3 = df.sample(frac=.67, replace=True)

# split off train set
X_train_sample1, y_train_sample1 = df_sample1.drop('target', axis=1), df_sample1['target']
X_train_sample2, y_train_sample2 = df_sample2.drop('target', axis=1), df_sample2['target']
X_train_sample3, y_train_sample3 = df_sample3.drop('target', axis=1), df_sample3['target']

# get sampled features for train and test using sqrt, note how replace=False now
num_features = len(X_train.columns)
X_train_sample1 = X_train_sample1.sample(n=int(math.sqrt(num_features)), replace=False, axis = 1)
X_train_sample2 = X_train_sample2.sample(n=int(math.sqrt(num_features)), replace=False, axis = 1)
X_train_sample3 = X_train_sample3.sample(n=int(math.sqrt(num_features)), replace=False, axis = 1)

# create the decision trees, this time we are sampling columns
tree1 = DecisionTreeClassifier().fit(X_train_sample1, y_train_sample1)
tree2 = DecisionTreeClassifier().fit(X_train_sample2, y_train_sample2)
tree3 = DecisionTreeClassifier().fit(X_train_sample3, y_train_sample3)

# predict each decision tree on X_test
predictions_1 = tree1.predict(X_test[X_train_sample1.columns])
predictions_2 = tree2.predict(X_test[X_train_sample2.columns])
predictions_3 = tree3.predict(X_test[X_train_sample3.columns])
preds = pd.DataFrame([predictions_1, predictions_2, predictions_3]).T
preds.columns = ["tree1", "tree2", "tree3"]

# take the majority rules 
final_prediction = np.array([np.round((predictions_1[i] + predictions_2[i] + predictions_3[i])/3) for i in range(len(predictions_1))])
preds = pd.DataFrame([predictions_1, predictions_2, predictions_3, final_prediction, y_test]).T.head(20)
preds.columns = ["tree1", "tree2", "tree3", "final", "label"]

当我运行这段代码时,我发现我的决策树开始预测不同的事件,这表明咱们曾经删除了树之间的很多相关性。

6. 决策树根底

到目前为止,咱们曾经解构了数据是如何输出到大量决策树中的。在后面的代码示例中,咱们应用了 DecisionTreeClassifier() 来训练决策树,但咱们须要解构决策树能力齐全了解随机森林。

决策树,顾名思义,看起来像一棵倒置的树。在高层次上,该算法试图提出问题以将数据拆分到不同的节点。下图显示了决策树的外观示例。

决策树依据前一个问题的答案提出一系列问题。对于它提出的每个问题,都可能有多个答案,咱们将其可视化为拆分节点。上一个问题的答案将决定树将要问的下一个问题。在问了一系列问题后的某个时候,您会得出答案。

然而你怎么晓得你的答案是精确的,或者你问了正确的问题呢?您实际上能够用几种不同的形式评估您的决策树,咱们当然也会合成这些办法。

7. 熵和信息增益

在这一点上,咱们须要探讨一个叫做熵的新术语。在高层次上,熵是掂量节点中不纯水平或随机性程度的一种办法。顺便说一句,还有另一种风行的办法来掂量节点的不纯度,称为基尼不纯度,但咱们不会在这篇文章中解构该办法,因为它与许多对于熵的概念重叠,只管计算略有不同。个别的想法是熵或基尼杂质越高,节点中的方差越大,咱们的指标是缩小这种不确定性。

决策树试图通过将它们询问的节点分成更小的更同质的节点来最小化熵。熵的理论公式是

为了合成熵,让咱们回到那个弹珠的例子:

假如我有 10 个弹珠。其中 5 个是蓝色的,5 个是绿色的。我的个体数据集的熵为 1.0,计算熵的代码如下:

from collections import Counter
from math import log2

# my predictor classes are 0 or 1. 0 is a blue marble, 1 is a green marble.
data = [0, 0, 0, 1, 1, 1, 1, 0, 1, 0]
# get length of labels
len_labels = len(data)
def calculate_entropy(data, len_labels):
    # we do a count of each class
    counts = Counter(labels)
    # we calculate the fractions, the output should be [.5, .5] for this example
    probs = [count / num_labels for count in counts.values()]
    # the actual entropy calculation 
    return - sum(p * log2(p) for p in probs)

calculate_entropy(labels, num_labels)

如果数据齐全被绿色弹珠填满,则熵将为 0,并且越靠近 50% 的宰割,熵就会减少。

每次咱们缩小熵时,咱们都会取得一些对于数据集的信息,因为咱们曾经缩小了随机性。信息增益通知咱们哪个特色绝对容许咱们最好地最小化咱们的熵。计算信息增益的办法是:

entropy(parent) — [weighted_average_of_entropy(children)]

在这种状况下,父节点是原始节点,子节点是拆分节点的后果。

为了计算信息增益,咱们执行以下操作:

  • 计算父节点的熵
  • 将父节点拆分为子节点
  • 为每个子节点创立权重。这是由 number_of_samples_in_child_node / number_of_samples_in_parent_node 测量的
  • 计算每个子节点的熵
  • 通过计算 weightentropy_of_child1 + weightentropy_of_child2 创立 [weighted_average_of_entropy(children)]
  • 从父节点的熵中减去这个加权熵

上面的代码为一个父节点被拆分为 2 个子节点实现了一个简略的信息增益

def information_gain(left_labels, right_labels, parent_entropy):
    """Calculate the information gain of a split"""
    # calculate the weight of the left node
    proportion_left_node = float(len(left_labels)) / (len(left_labels) + len(right_labels))
    #calculate the weight of the right node
    proportion_right_node = 1 - proportion_left_node
    # compute the weighted average of the child node
    weighted_average_of_child_nodes = ((proportion_left_node * entropy(left_labels)) + (proportion_right_node * entropy(right_labels)))
    # return the parent node entropy - the weighted entropy of child nodes
    return parent_entropy - weighted_average_of_child_nodes

8. 解构决策树

思考到这些概念,咱们筹备实现一个小的决策树!

在没有任何领导的状况下,决策树将一直决裂节点,直到所有最终叶节点都是纯节点。管制树的复杂性的想法称为剪枝,咱们能够在树齐全构建后剪枝,也能够在树的成长阶段之前用肯定的参数事后剪枝。预剪枝树复杂性的一些办法是管制决裂的数量,限度最大深度(从根节点到叶节点的最长距离),或设置信息增益。

上面的代码将所有这些概念分割在一起

  • 从一个数据集开始,您能够在其中预测指标变量
  • 计算原始数据集(根节点)的熵(或基尼杂质)
  • 查看每个特色并计算信息增益
  • 抉择具备最佳信息增益的最佳特色,这与导致熵缩小最多的特色雷同
  • 持续增长直到满足咱们的进行条件——在这种状况下,它是咱们的最大深度限度和熵为 0 的节点。
import pandas as pd
import numpy as np
from math import log2

def entropy(data, target_col):
    # calculate the entropy of the entire dataset
    values, counts = np.unique(data[target_col], return_counts=True)
    entropy = np.sum([-count/len(data) * log2(count/len(data)) for count in counts])
    return entropy

def compute_information_gain(data, feature, target_col):
    parent_entropy = entropy(data, target_col)
    # calculate the information gain for splitting on a given feature
    split_values = np.unique(data[feature])
    # initialize at 0
    weighted_child_entropy = 0
    # compute the weighted entropy, remember that this is related to the number of points in the new node
    for value in split_values:
        sub_data = data[data[feature] == value]
        node_weight = len(sub_data)/len(data)
        weighted_child_entropy += node_weight * entropy(sub_data, target_col)
    # same calculation as before, we just subtract the weighted entropy from the parent node entropy 
    return parent_entropy - weighted_child_entropy

def grow_tree(data, features, target_col, depth=0, max_depth=3):
    # we set a max depth of 3 to "pre-prune" or limit the tree complexity
    if depth >= max_depth or len(np.unique(data[target_col])) == 1:
        # stop growing the tree if maximum depth is reached or all labels are the same. All labels being the same means the entropy is 0
        return np.unique(data[target_col])[0]
    # we compute the best feature (or best question to ask) based on information gain
    node = {}
    gains = [compute_information_gain(data, feature, target_col) for feature in features]
    best_feature = features[np.argmax(gains)]
    
    for value in np.unique(data[best_feature]):
        sub_data = data[data[best_feature] == value]
        node[value] = grow_tree(sub_data, features, target_col, depth+1, max_depth)
    
    return node


# simulate some data and make a dataframe, note how we have a target
data = {'A': [1, 2, 1, 2, 1, 2, 1, 2],
    'B': [3, 3, 4, 4, 3, 3, 4, 4],
    'C': [5, 5, 5, 5, 6, 6, 6, 6],
    'target': [0, 0, 0, 1, 1, 1, 1, 0]
}
df = pd.DataFrame(data)

# define our features and label
features = ["A", "B", "C"]
target_col = "target"

# grow the tree
tree = grow_tree(df, features, target_col, max_depth=3)
print(tree)

在这棵树上进行预测意味着用新数据遍历成长的树,直到它达到叶节点。最初的叶节点是预测。

总结

总结一下咱们学到的货色:

  • 随机森林实际上是一组不相干的决策树进行预测并达成共识。这种共识是回归问题的均匀分数和分类问题的少数规定
  • 随机森林通过利用装袋和特色采样来加重相关性。通过利用这两种技术,各个决策树正在查看咱们汇合的特定维度并依据不同因素进行预测。
  • 通过在产生最高信息增益的特色上拆分数据来生成决策树。信息增益被测量为杂质的最高缩小。杂质通常通过熵或基尼杂质来掂量。
  • 随机森林可能通过特色重要性实现无限程度的可解释性,特色重要性是特色的均匀信息增益的度量。
  • 随机森林还可能在训练时进行某种模式的穿插验证,这是一种称为 OOB 谬误的独特技术。这是可能的,因为算法对上游数据进行采样的形式。

本文由 mdnice 多平台公布

正文完
 0