RandomForest算法简析
Published in:2023-08-02 | category: Computer Technology
Words: 2.9k | Reading time: 12min | reading:

RandomForest算法简析

概述

随机森林(Random Forest)是一种强大且流行的机器学习算法,用于解决分类和回归问题。它是集成学习(Ensemble Learning)方法的一种,通过组合多个决策树来形成一个更强大的模型。随机森林由贝叶斯学者Leo Breiman于2001年提出,并在实践中被广泛应用。

以下是随机森林的概述:

  1. 决策树基本原理:首先需要了解决策树的基本原理。决策树是一种非参数化的监督学习方法,通过在输入特征上逐步进行二分划分,将数据集分割成不同的区域,从而构建一个树形的分类模型。

  2. 随机森林组成:随机森林由多个决策树组成。在构建每个决策树时,采用的数据是从原始训练集中有放回地随机抽取的(有放回采样,即Bootstrap采样),同时每个决策树使用的特征子集也是随机选择的。

  3. 随机性:随机森林的随机性体现在两个方面:首先,通过随机抽取数据和特征子集,使得每个决策树都有所不同;其次,当进行树节点划分时,不再搜索所有可能的特征,而是随机选择特征子集中的一个特征进行划分。这种随机性有助于降低模型的方差,提高泛化性能。

  4. 投票集成:在分类问题中,每个决策树会给出一个预测结果,最终的分类结果是通过投票机制来确定,即采用多数投票的结果作为最终输出。在回归问题中,随机森林的预测结果是多个决策树预测结果的平均值。

  5. 随机森林的优点:随机森林在处理高维数据和大规模数据时表现出色,具有很强的泛化能力,不容易过拟合,且对于异常值和噪声相对稳健。此外,随机森林能够评估特征的重要性,对于特征选择和可视化数据具有一定帮助。

  6. 参数调节:随机森林中的主要参数包括决策树的数量、每个决策树的最大深度、特征子集大小等。在实践中,可以通过交叉验证等技术来选择合适的参数值,从而优化模型的性能。

总结来说,随机森林是一种强大且易于使用的机器学习算法,适用于分类和回归问题,并在许多现实世界的应用中取得了很好的效果。

集成方法

集成方法(Ensemble Methods)是一种机器学习技术,通过组合多个基本模型来构建一个更强大、更稳健的预测模型。这些基本模型可以是同质的(相同类型的算法)或异质的(不同类型的算法),它们通常被称为”弱学习器”(Weak Learners)或”基学习器”(Base Learners)。

集成方法通过将多个弱学习器的预测结果进行加权平均或投票等方式,从而得到更准确、泛化能力更强的综合预测结果。相比单一的基本模型,集成方法能够降低过拟合风险,提高模型的鲁棒性和稳定性。

集成方法的主要优势在于它能够在不同的数据集和问题上产生出色的表现。这是因为集成方法利用了多个模型的优势,而不受单个模型的局限性。在实践中,集成方法通常比单个模型更容易调整和优化,尤其在处理复杂任务和大规模数据时具有很大的优势。

常见的集成方法包括:

  1. Bagging(Bootstrap Aggregating): Bagging使用有放回抽样(Bootstrap采样)从原始训练数据中生成多个不同的训练集,然后在每个训练集上构建独立的弱学习器。最终的预测结果是这些弱学习器预测结果的平均值(回归问题)或多数投票结果(分类问题)。

  2. Boosting: Boosting是一种迭代的集成方法,通过顺序构建多个弱学习器,每个学习器都试图纠正前一个学习器的错误。Boosting方法根据预测错误的样本给予其更高的权重,以便下一个学习器更关注这些难以分类的样本。常见的Boosting算法有AdaBoost和Gradient Boosting Machine (GBM)。

  3. Stacking: Stacking是一种更复杂的集成方法,它不仅仅简单地将多个模型的结果进行加权平均,而是将不同模型的预测结果作为新的特征,然后再训练一个元学习器来产生最终的预测结果。

  4. Random Forest: 此前已经提到过,随机森林是一种基于Bagging思想的集成学习方法,由多个决策树组成。

集成方法在机器学习领域得到广泛应用,特别是在数据挖掘、分类和回归任务中。通过将多个模型的优势相结合,集成方法能够显著提高预测性能,成为了许多机器学习竞赛和实际应用中的重要技术。

构建步骤

随机森林(Random Forest)算法的构建过程如下:

  1. 数据准备:首先,将原始数据集拆分成训练集和测试集。训练集用于构建随机森林模型,测试集用于评估模型的性能。

  2. 随机抽样:对于每棵决策树,从训练集中进行有放回地随机抽样(Bootstrap采样),得到一个新的训练子集。这意味着每个子集可能包含一部分重复样本,而其他样本可能会被省略。这种随机抽样可以确保每棵决策树之间有差异,增加模型的多样性。

  3. 特征随机选择:在每个节点处,决策树的构建不会考虑所有的特征,而是随机选择一个特征子集来进行节点划分。这样做的目的是增加模型的随机性,防止过度拟合,并鼓励不同决策树使用不同的特征来构建树。

  4. 构建决策树:根据上述随机抽样和特征选择的训练子集,在每个节点上使用某种决策树算法(通常是CART算法,即分类与回归树)来划分数据。决策树会根据选择的特征和划分准则将数据集划分为更小的子集,直到满足某个停止条件(例如:节点样本数小于某个阈值、树的深度达到一定值等)为止。

  5. 集成决策:对于分类问题,每棵决策树投票选择它们认为的最终类别,并以多数票决定最终的分类结果。对于回归问题,每棵决策树给出一个预测值,最终的预测结果是这些预测值的平均值。

  6. 模型评估:使用测试集评估随机森林的性能,可以使用各种指标如准确率、精确度、召回率、F1分数等。

  7. 特征重要性评估:随机森林可以输出每个特征的重要性分数,用于衡量每个特征对模型预测性能的贡献程度。

涉及到的数学公式

  1. 基尼不纯度(Gini Impurity):用于衡量节点的不纯度,对于分类问题,基尼不纯度定义如下:
    $$ \text{Gini}(p) = 1 - \sum_{i=1}^{K} p_i^2 $$
    其中,$K$ 是类别的数量,$p_i$ 是类别$i$在节点中的样本比例。

  2. 信息增益(Information Gain):用于在决策树节点划分时选择最优特征。对于分类问题,信息增益定义如下:
    $$ \text{IG}(D, f) = \text{Gini}(D) - \sum_{v \in \text{Values}(f)} \frac{|D_v|}{|D|} \text{Gini}(D_v) $$
    其中,$D$ 是当前节点的数据集,$f$ 是特征,$\text{Values}(f)$ 是特征$f$的取值集合,$D_v$ 是特征$f$取值为$v$时对应的数据子集。

  3. 均方误差(Mean Squared Error,MSE):用于回归问题中衡量节点的纯度,定义如下:
    $$ \text{MSE}(D) = \frac{1}{|D|} \sum_{i=1}^{|D|} (y_i - \bar{y})^2 $$
    其中,$D$ 是当前节点的数据集,$y_i$ 是样本$i$的真实输出值,$\bar{y}$ 是节点中所有样本输出值的均值。

  4. 随机森林中的投票或平均:对于分类问题,随机森林使用投票机制,即多个决策树的预测结果中取多数票作为最终结果。对于回归问题,随机森林使用平均值,即多个决策树的预测值取平均作为最终结果。

案例:声纳信号分类

数据集

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
from random import seed, randrange, random

def loadDataSet(filename):
dataset = []
with open(filename, 'r') as fr:
for line in fr.readlines():
if not line:
continue
lineArr = []
for featrue in line.split(','):
str_f = featrue.strip()
if str_f.isdigit():
lineArr.append(float(str_f))
else:
lineArr.append(str_f)
dataset.append(lineArr)
return dataset

def cross_validation_split(dataset, n_folds):
dataset_split = list()
dataset_copy = list(dataset)
fold_size = len(dataset) / n_folds
for i in range(n_folds):
fold = list()
while len(fold) < fold_size:
index = randrange(len(dataset_copy))
fold.append(dataset_copy[index])
dataset_split.append(fold)
return dataset_split

def test_split(index, value, dataset):
left, right = list(), list()
for row in dataset:
if str(row[index]) < str(value):
left.append(row)
else:
right.append(row)
return left, right

def gini_index(groups, class_values):
gini = 0.0
for class_value in class_values:
for group in groups:
size = len(group)
if size == 0:
continue
proportion = [row[-1] for row in group].count(class_value) / float(size)
gini += (proportion * (1.0 - proportion))
return gini

def get_split(dataset, n_features):
class_values = list(set(row[-1] for row in dataset))
b_index, b_value, b_score, b_groups = 999, 999, 999, None
features = list()
while len(features) < n_features:
index = randrange(len(dataset[0]) - 1)
if index not in features:
features.append(index)
for index in features:
for row in dataset:
groups = test_split(index, row[index], dataset)
gini = gini_index(groups, class_values)
if gini < b_score:
b_index, b_value, b_score, b_groups = index, row[index], gini, groups
return {'index': b_index, 'value': b_value, 'groups': b_groups}

def to_terminal(group):
outcomes = [row[-1] for row in group]
return max(set(outcomes), key=outcomes.count)

def split(node, max_depth, min_size, n_features, depth):
left, right = node['groups']
del (node['groups'])
if not left or not right:
node['left'] = node['right'] = to_terminal(left + right)
return
if depth >= max_depth:
node['left'], node['right'] = to_terminal(left), to_terminal(right)
return
if len(left) <= min_size:
node['left'] = to_terminal(left)
else:
node['left'] = get_split(left, n_features)
split(node['left'], max_depth, min_size, n_features, depth + 1)
if len(right) <= min_size:
node['right'] = to_terminal(right)
else:
node['right'] = get_split(right, n_features)
split(node['right'], max_depth, min_size, n_features, depth + 1)

def build_tree(train, max_depth, min_size, n_features):
root = get_split(train, n_features)
split(root, max_depth, min_size, n_features, 1)
return root

def predict(node, row):
if str(row[node['index']]) < str(node['value']):
if isinstance(node['left'], dict):
return predict(node['left'], row)
else:
return node['left']
else:
if isinstance(node['right'], dict):
return predict(node['right'], row)
else:
return node['right']

def bagging_predict(trees, row):
predictions = [predict(tree, row) for tree in trees]
return max(set(predictions), key=predictions.count)

def subsample(dataset, ratio):
sample = list()
n_sample = round(len(dataset) * ratio)
while len(sample) < n_sample:
index = randrange(len(dataset))
sample.append(dataset[index])
return sample

def random_forest(train, test, max_depth, min_size, sample_size, n_trees, n_features):
trees = list()
for i in range(n_trees):
sample = subsample(train, sample_size)
tree = build_tree(sample, max_depth, min_size, n_features)
trees.append(tree)
predictions = [bagging_predict(trees, row) for row in test]
return predictions

def accuracy_metric(actual, predicted):
correct = 0
for i in range(len(actual)):
if actual[i] == predicted[i]:
correct += 1
return correct / float(len(actual)) * 100.0

def evaluate_algorithm(dataset, algorithm, n_folds, *args):
folds = cross_validation_split(dataset, n_folds)
scores = list()
for fold in folds:
train_set = list(folds)
train_set.remove(fold)
train_set = sum(train_set, [])
test_set = list()
for row in fold:
row_copy = list(row)
row_copy[-1] = None
test_set.append(row_copy)
predicted = algorithm(train_set, test_set, *args)
actual = [row[-1] for row in fold]
accuracy = accuracy_metric(actual, predicted)
scores.append(accuracy)
return scores

if __name__ == '__main__':
dataset = loadDataSet('7.RandomForest/sonar-all-data.txt')
n_folds = 5
max_depth = 20
min_size = 1
sample_size = 1.0
n_features = 15
for n_trees in [1, 10, 20]:
scores = evaluate_algorithm(dataset, random_forest, n_folds, max_depth, min_size, sample_size, n_trees, n_features)
seed(1)
print('random=', random())
print('Trees: %d' % n_trees)
print('Scores: %s' % scores)
print('Mean Accuracy: %.3f%%' % (sum(scores) / float(len(scores))))

来源于 ApacheCN

Prev:
AdaBoost算法简析
Next:
SVM简析