作者:chen_h
微信号 & QQ:862251340
微信公众号:coderpai
(一)机器学习中的集成学习入门
(二)bagging 方法
(三)使用Python进行交易的随机森林算法
(四)Python中随机森林的实现与解释
(五)如何用 Python 从头开始实现 Bagging 算法
(六)如何利用Python从头开始实现随机森林算法
随机森林是集成学习中一个主要的算法。简而言之,集成方法是一种将几个弱学习器的预测结果进行组合,最终形成一个强学习器的方法。可以直观的猜测一下,随机森林通过减少过拟合来达到比决策树更好的效果。决策树和随机森林都可用于回归和分类问题。在这篇文章中,我们利用随机森林来解决一些问题。
在开始编写代码之前,我们需要了解一些基本理论:
1.特征bagging:自举过程是一种从原始样本中进行又放回的采样。在特征 bagging 过程中,我们从原始特征中进行随机特征采样,并且把采样到的特征传递到不同的树上面。(不采用放回的采集,因为具有冗余特征是没有意义的)。这样做事为了减少树之间的相关性。我们的目标就是制作高度不相关的决策树。
2.聚合:使随机森林比决策树更好的核心是聚合不相关的树。我们的想法是创建几个浅层的树模型,然后将它们平均化以创建更好的随机森林,这样可以将一些随机误差的平均值变为零。在回归的情况下,我们可以平均每个树的预测(平均值),而在分类问题的情况下,我们可以简单的取每个树投票的大多数类别。
要从头开始编码我们的随机森林,我们将遵循自上而下的方法。我们将从一个黑盒子开始,并进一步将其分解为几个黑盒子,抽象级别越来越低,细节越来越多,直到我们最终达到不再抽象的程度。
我们正在创建一个随机森林回归器,如果你想创建一个分类器,那么只需要对此代码进行细微的调整就行了。首先,我们需要知道我们的黑盒子的输入和输出是什么,所以我们需要知道定义我们的随机森林的参数是:
让我们开始定义我们的随机森林类。
class RandomForest():
def __init__(self, x, y, n_trees, n_features, sample_sz, depth=10, min_leaf=5):
np.random.seed(12)
if n_features == 'sqrt':
self.n_features = int(np.sqrt(x.shape[1]))
elif n_features == 'log2':
self.n_features = int(np.log2(x.shape[1]))
else:
self.n_features = n_features
print(self.n_features, "sha: ",x.shape[1])
self.x, self.y, self.sample_sz, self.depth, self.min_leaf = x, y, sample_sz, depth, min_leaf
self.trees = [self.create_tree() for i in range(n_trees)]
def create_tree(self):
idxs = np.random.permutation(len(self.y))[:self.sample_sz]
f_idxs = np.random.permutation(self.x.shape[1])[:self.n_features]
return DecisionTree(self.x.iloc[idxs], self.y[idxs], self.n_features, f_idxs,
idxs=np.array(range(self.sample_sz)),depth = self.depth, min_leaf=self.min_leaf)
def predict(self, x):
return np.mean([t.predict(x) for t in self.trees], axis=0)
def std_agg(cnt, s1, s2): return math.sqrt((s2/cnt) - (s1/cnt)**2)
如果我们能够神奇的创建树,那么想想随机森林是多么容易。现在我们降低抽象级别并编写代码来创建决策树。
决策树将具有以下参数:
class DecisionTree():
def __init__(self, x, y, n_features, f_idxs,idxs,depth=10, min_leaf=5):
self.x, self.y, self.idxs, self.min_leaf, self.f_idxs = x, y, idxs, min_leaf, f_idxs
self.depth = depth
self.n_features = n_features
self.n, self.c = len(idxs), x.shape[1]
self.val = np.mean(y[idxs])
self.score = float('inf')
self.find_varsplit()
def find_varsplit(self):
#Will make it recursive later
for i in self.f_idxs: self.find_better_split(i)
def find_better_split(self, var_idx):
#Lets write it later
pass
for i in range(0,self.n-self.min_leaf-1):
xi,yi = sort_x[i],sort_y[i]
lhs_cnt += 1; rhs_cnt -= 1
lhs_sum += yi; rhs_sum -= yi
lhs_sum2 += yi**2; rhs_sum2 -= yi**2
if i<self.min_leaf or xi==sort_x[i+1]:
continue
lhs_std = std_agg(lhs_cnt, lhs_sum, lhs_sum2)
rhs_std = std_agg(rhs_cnt, rhs_sum, rhs_sum2)
curr_score = lhs_std*lhs_cnt + rhs_std*rhs_cnt
if curr_score<self.score:
self.var_idx,self.score,self.split = var_idx,curr_score,xi
@property
def split_name(self): return self.x.columns[self.var_idx]
@property
def split_col(self): return self.x.values[self.idxs,self.var_idx]
@property
def is_leaf(self): return self.score == float('inf') or self.depth <= 0
def predict(self, x):
return np.array([self.predict_row(xi) for xi in x])
def predict_row(self, xi):
if self.is_leaf: return self.val
t = self.lhs if xi[self.var_idx]<=self.split else self.rhs
return t.predict_row(xi)
我们使用属性装饰器使我们的代码更加简洁。
a. 如果 idxs 为 None:idxs = np.arange(len(y)),如果我们没有在这个特定树的计算中指定行的索引,只需占用所有行;
b. self.val = np.mean(y[idxs]) 每个决策树预测一个值,该值是它所持有的所有行的平均值。变量 self.val 保存树的每个节点的预测。对于根节点,该值将仅仅是所有观察值的平均值,因为它保留了所有行,因为我们尚未进行拆分。我在这里使用了“节点”这个词,因为本质上决策树只是一个节点,左边是决策树,右边也是决策树。
c. Self.score = float(“inf”) 节点的得分是根据它如何 “划分” 原始数据集来进行计算的。我们稍后会定义这个 “好”,我们现在假设我们有办法测量这样的数量。此外,我们的节点将得分设置为无穷大,因为我们尚未进行任何拆分,因此我们存在的拆分无线差,表明任何拆分都优于不拆分。
d. self.find_varsplit() 我们首先进行拆分!
决策树通过基于某些条件递归的将数据分为两半来进行训练。如果测试集在每列中有 10 列,每列有 10 个数据点,则总共可以进行 10*10 = 100 次拆分,我们手头的任务是找到哪些拆分是最适合我们的数据。
我们根据将数据分为两半,然后使得两者中的每一个数据都是非常“相似的”。增加这种相似性的一种方法是减少两半的方差或者标准偏差。因此,我们希望最小化两边标准差的加权平均值。我们使用贪婪算法通过将数据划分为列中每个值的两半来找到拆分,并计算两半的标准偏差的加权平均值以找到最小值。
为了加快速度,我们可以复制一个列并对其进行排序,通过在第 n+1 个索引处使用 sum 的值和由第 n 个索引分割创建的两半值的平方和来分割加权平均值来计算加权平能均值。这是基于以下标准偏差公式:
下面的图像以图形方式展示了分数计算的过程,每个图像中的最后一列是表示分割得分的单个数字,即左右标准偏差的加权平均值。
我们继续对每列进行排序:
现在我们按顺序进行拆分:
index = 0
index = 1
Index = 2 (best split)
Index = 3
index = 4
index=5
通过简单的贪婪算法,我们发现在 index = 2 时进行的拆分是最好的拆分,因为它得分最低。我们稍后对所有列执行相同的步骤并将它们全部比较以贪婪算法找到最小值。
以下是上述图示表示的简单代码:
def std_agg(cnt, s1, s2): return math.sqrt((s2/cnt) - (s1/cnt)**2)
def find_better_split(self, var_idx):
x, y = self.x.values[self.idxs,var_idx], self.y[self.idxs]
sort_idx = np.argsort(x)
sort_y,sort_x = y[sort_idx], x[sort_idx]
rhs_cnt,rhs_sum,rhs_sum2 = self.n, sort_y.sum(), (sort_y**2).sum()
lhs_cnt,lhs_sum,lhs_sum2 = 0,0.,0.
for i in range(0,self.n-self.min_leaf-1):
xi,yi = sort_x[i],sort_y[i]
lhs_cnt += 1; rhs_cnt -= 1
lhs_sum += yi; rhs_sum -= yi
lhs_sum2 += yi**2; rhs_sum2 -= yi**2
if i<self.min_leaf or xi==sort_x[i+1]:
continue
lhs_std = std_agg(lhs_cnt, lhs_sum, lhs_sum2)
rhs_std = std_agg(rhs_cnt, rhs_sum, rhs_sum2)
curr_score = lhs_std*lhs_cnt + rhs_std*rhs_cnt
if curr_score<self.score:
self.var_idx,self.score,self.split = var_idx,curr_score,xi
上面的代码我们需要一些解释:
现在我们知道如何为所选列找到最佳拆分,我们需要递归的为每个决策树进行拆分。对于每一棵树,我们找到最好的列和它的值,然后我们递归的制作两个决策树,知道我们到达叶子及诶单。为此,我们将不完整的函数 find_varsplit 进行扩展:
def find_varsplit(self):
for i in self.f_idxs: self.find_better_split(i)
if self.is_leaf: return
x = self.split_col
lhs = np.nonzero(x<=self.split)[0]
rhs = np.nonzero(x>self.split)[0]
lf_idxs = np.random.permutation(self.x.shape[1])[:self.n_features]
rf_idxs = np.random.permutation(self.x.shape[1])[:self.n_features]
self.lhs = DecisionTree(self.x, self.y, self.n_features, lf_idxs, self.idxs[lhs], depth=self.depth-1, min_leaf=self.min_leaf)
self.rhs = DecisionTree(self.x, self.y, self.n_features, rf_idxs, self.idxs[rhs], depth=self.depth-1, min_leaf=self.min_leaf)
最后我们给出完整代码:
class RandomForest():
def __init__(self, x, y, n_trees, n_features, sample_sz, depth=10, min_leaf=5):
np.random.seed(12)
if n_features == 'sqrt':
self.n_features = int(np.sqrt(x.shape[1]))
elif n_features == 'log2':
self.n_features = int(np.log2(x.shape[1]))
else:
self.n_features = n_features
print(self.n_features, "sha: ",x.shape[1])
self.x, self.y, self.sample_sz, self.depth, self.min_leaf = x, y, sample_sz, depth, min_leaf
self.trees = [self.create_tree() for i in range(n_trees)]
def create_tree(self):
idxs = np.random.permutation(len(self.y))[:self.sample_sz]
f_idxs = np.random.permutation(self.x.shape[1])[:self.n_features]
return DecisionTree(self.x.iloc[idxs], self.y[idxs], self.n_features, f_idxs,
idxs=np.array(range(self.sample_sz)),depth = self.depth, min_leaf=self.min_leaf)
def predict(self, x):
return np.mean([t.predict(x) for t in self.trees], axis=0)
def std_agg(cnt, s1, s2): return math.sqrt((s2/cnt) - (s1/cnt)**2)
class DecisionTree():
def __init__(self, x, y, n_features, f_idxs,idxs,depth=10, min_leaf=5):
self.x, self.y, self.idxs, self.min_leaf, self.f_idxs = x, y, idxs, min_leaf, f_idxs
self.depth = depth
print(f_idxs)
# print(self.depth)
self.n_features = n_features
self.n, self.c = len(idxs), x.shape[1]
self.val = np.mean(y[idxs])
self.score = float('inf')
self.find_varsplit()
def find_varsplit(self):
for i in self.f_idxs: self.find_better_split(i)
if self.is_leaf: return
x = self.split_col
lhs = np.nonzero(x<=self.split)[0]
rhs = np.nonzero(x>self.split)[0]
lf_idxs = np.random.permutation(self.x.shape[1])[:self.n_features]
rf_idxs = np.random.permutation(self.x.shape[1])[:self.n_features]
self.lhs = DecisionTree(self.x, self.y, self.n_features, lf_idxs, self.idxs[lhs], depth=self.depth-1, min_leaf=self.min_leaf)
self.rhs = DecisionTree(self.x, self.y, self.n_features, rf_idxs, self.idxs[rhs], depth=self.depth-1, min_leaf=self.min_leaf)
def find_better_split(self, var_idx):
x, y = self.x.values[self.idxs,var_idx], self.y[self.idxs]
sort_idx = np.argsort(x)
sort_y,sort_x = y[sort_idx], x[sort_idx]
rhs_cnt,rhs_sum,rhs_sum2 = self.n, sort_y.sum(), (sort_y**2).sum()
lhs_cnt,lhs_sum,lhs_sum2 = 0,0.,0.
for i in range(0,self.n-self.min_leaf-1):
xi,yi = sort_x[i],sort_y[i]
lhs_cnt += 1; rhs_cnt -= 1
lhs_sum += yi; rhs_sum -= yi
lhs_sum2 += yi**2; rhs_sum2 -= yi**2
if i<self.min_leaf or xi==sort_x[i+1]:
continue
lhs_std = std_agg(lhs_cnt, lhs_sum, lhs_sum2)
rhs_std = std_agg(rhs_cnt, rhs_sum, rhs_sum2)
curr_score = lhs_std*lhs_cnt + rhs_std*rhs_cnt
if curr_score<self.score:
self.var_idx,self.score,self.split = var_idx,curr_score,xi
@property
def split_name(self): return self.x.columns[self.var_idx]
@property
def split_col(self): return self.x.values[self.idxs,self.var_idx]
@property
def is_leaf(self): return self.score == float('inf') or self.depth <= 0
def predict(self, x):
return np.array([self.predict_row(xi) for xi in x])
def predict_row(self, xi):
if self.is_leaf: return self.val
t = self.lhs if xi[self.var_idx]<=self.split else self.rhs