Random Forest

Random Forest——随机森林

上一篇是讲到了决策树,这篇就来讲一下树的集合,随机森林。

①Aggregation Model

随机森林还是没有脱离聚合模型这块,之前学过两个aggregation model,bagging和decision tree,一个是边learning边uniform。首先是boostrap方式得到数据D1,之后训练做平均;另一个也是边learning但是做的是condition,直接用数据D做conditional切分。

bagging and decision tree

而bagging和decision tree各自都有一个很重要的特点。bagging可以减少不同gt方差的variance的作用,因为bagging是通过各自投票,大家包含的相同样本都比较多,所以大致不会差别太远,后面uniform求平均也起到了求consensus的作用。但是decision tree有点不同,每一次的切割都是切分剩下的数据,意味着每一次如果数据是不一样的切分方式就不一样,所以对不同的数据会很敏感,所以不同的D得到的variance会大一些。
但是之前说过,越不同的g是越能反应事物的本身,比如看一个人,单单从成绩方面太狭隘了,成绩和行动力结合起来才会综合,这就是像是两个g结合在一起。所以如果g越不一样表达的方面就越不一样。既然decision tree是variance大,bagging的variance小,结合起来说不定可以取得更好的成绩,于是random forest诞生了。

②Random Forest


所以random forest由两方面组成,bagging和random forest。

random forest有三个优点,1.决策树可以由不同的主机生成,效率高。2.随机森林继承了CART的有点。3.以bagging的形式结合在一起,避免了过拟合。

上面所讲的指数基本的random forest,通过boostrap抽样得到数据D1然后训练decision tree,最后做uniform。除了这种方法之外,还有另外一种方法,随机选取数据的feature,现在有100个特征,我选取30个来构成decision tree,又选择另外的30个来构成新的一棵随机数,每一棵树都不一样。比如现在是d维,那么我们只是选择_d(_d < d)维来构建决策树。也就是说_d维z空间其实就是d维x空间的一个随机子空间(subspace),Random Forest算法的作者建议在构建CART每个分支b(x)的时候,都可以重新选择子特征来训练,从而得到更具有多样性的决策树。

所以改进一下:
subspace

上面我们讲的是随机抽取特征,除此之外,还可以将现有的特征x,通过数组p进行线性组合,来保持多样性:

这种方法得到的就不再是d的子空间集合了,而是得到线性的组合,比如在二维平面上只能是x,y维度。但是转换之后就是斜线。而不同的pi是不一样的,而且向量pi中大部分元素为零,因为我们选择的只是一部分特征,这是一种低维映射:

于是,能力又强了一点,random-subspace变成了random-combination,这里就很像是perceptron模型了。事实上我想了一会才感觉到很像......

下面的代码实现还不会用到这些,因为还是麻烦了点,之后有时间再看看改进吧。

③Out-Of-Bag Estimate

再来探讨一下对于boostrap的一些内容和理解。
我们通过了boostrap得到了数据D1,数据D1里面既有原数据D里面有的,也可能没有里面有的。红色的*就代表没有,也叫做红色表示的样本被称为out-of-bag(OOB) example。


那么他到底有多少?可以根据有关公式算一下:

1/N就是抽中的概率。那么抽不中就(1 - 1/N),多次就是N次方了。化简一下约等于1/e。其实这里的≈不太准确,因为N -> lim是有(1 + 1/N)^N = e。
所以,是有大约百分之30的数据是抽不到的。
貌似是和之前学过validation有点像,来对比一下:

在validation里面,Dtrain是用来得到gt的,Dtrain和Dval是倍数关系,并且没有交集,而OOB里面在bag外的数据是*号的,也没有参与到decision tree的create当中。那么我们是否可以用OOB的数据来代替validation呢?其实完全可以的,因为OOB类比过去就是Dval数据。那么整一个G的performance要怎么计算?我们可以计算g的然后平均,比如当前有5个gt,我们分别计算他手下的OOB的Eval作为这个g的performance,平均即可。

这种self-validation相比于validation来说还有一个优点就是它不需要重复训练。如下图左边所示,在通过Dval选择到表现最好的g之后,还需要在Dtrain和Dval组成的所有样本集D上重新对该模型g训练一次,以得到最终的模型系数。但是self-validation在调整随机森林算法相关系数并得到最小的EooB之后,就完成了整个模型的建立,无需重新训练模型。更重要的是,random forest的self-validation在衡量G的表现上通常相当准确。

④Feature Selection

在feature选择的过程中,还有一类问题要注意。选择的过程中有可能遇到多余的问题,比如抽取到生日和年龄,这就尴尬了。另一种是不相关的。


特征选择优点:
提高效率,特征越少,模型越简单
正则化,防止特征过多出现过拟合
去除无关特征,保留相关性大的特征,解释性强

缺点:
筛选特征的计算量较大
不同特征组合过拟合是有可能出现的
容易选到无关特征,解释性差


在上一篇实现决策树的里面使用的decision stump就是一种feature selection。
问题来了,我们要怎么选择有用的特征?

其中的一种方法就是做特征重要性的排序,选择前几个。这种方法如果在线性模型里面是很好计算的,线性可分模型。如果是非线性可分的话,权值不是一一对应的,因为通常是做了feature transform的,所以权值是交叉再一起。所以非线性就会很难。

上图是linear model可以使用的,并且效果不差。只需要选择最大的权值|W|就好了。
RF中,特征选择的核心思想是random test。random test的做法是对于某个特征,如果用另外一个随机值替代它之后的表现比之前更差,则表明该特征比较重要,所占的权重应该较大,不能用一个随机值替代。相反,如果随机值替代后的表现没有太大差别,则表明该特征不那么重要,可有可无。就好像班里面学习好的突然有一天死了,老师立马就可以发现,但是对于成绩差的挂了一个学期都不一定知道。所以,通过比较某特征被随机值替代前后的表现,就能推断出该特征的权重和重要性。
问题来了,我们应该如何选择随机值来替代?
①是使用uniform或者是Gaussian插入随机值。
②是把数据的第i个特征打乱了看看打乱之后效果差别大不大。

相比起来,其实第二种更加好,因为如果本来的不是高斯分布可能就会打乱了数据的分布方式,而第二种在大体上数据的分布是不改变的。之后我们可以比较variance,大的那么就是比较重要了。

这样就又有了一个问题,我们要如何衡量改变dimension后的D和原数据D的performance呢?
①我们可以延用之前OOB的做法,打乱i个特征,对每一个维度都要训练,如何用OOB做validation评估performance与原数据D训练出来的做比较。
②RF的作者提出了一种方法,就是把permutation的操作从原来的training上移到了OOB validation上去,记为Eoob(G(p))→E(p)oob(G)。也就是说,在训练的时候仍然使用D,但是在OOB验证的时候,将所有的OOB样本的第i个特征重新洗牌,验证G的表现。

第二种方法用的很多,计算方便,主要是效果也很好啊。

⑤代码实现

Random Forest in Action

最后,我们通过实际的例子来看一下RF的特点。首先,仍然是一个二元分类的例子。如下图所示,左边是一个C&RT树没有使用bootstrap得到的模型分类效果,其中不同特征之间进行了随机组合,所以有斜线作为分类线;中间是由bootstrap(N’=N/2)后生成的一棵决策树组成的随机森林,图中加粗的点表示被bootstrap选中的点;右边是将一棵决策树进行bagging后的分类模型,效果与中间图是一样的,都是一棵树。



当t=100,即选择了100棵树时,中间的模型是第100棵决策树构成的,还是只有一棵树;右边的模型是由100棵决策树bagging起来的,如下图所示:



当t=200时:

当t=300时:



当t=400时:

当t=500时:

当t=600时:

当t=700时:

当t=800时:



当t=900时:

当t=1000时:

随着树木个数的增加,我们发现,分界线越来越光滑而且得到了large-margin-like boundary,和之前磕磕绊绊的相比光滑了不少,类似于SVM一样的效果。也就是说,树木越多,分类器的置信区间越大。

然后,我们再来看一个比较复杂的例子,二维平面上分布着许多离散点,分界线形如sin函数。当只有一棵树的时候(t=1),下图左边表示单一树组成的RF,右边表示所有树bagging组合起来构成的RF。因为只有一棵树,所以左右两边效果一致。



当t=6时:



当t=11时:

当t=16时:



当t=21时:

可以看到,当RF由21棵树构成的时候,分界线就比较平滑了,而且它的边界比单一树构成的RF要robust得多,更加平滑和稳定。
最后,基于上面的例子,再让问题复杂一点:在平面上添加一些随机噪声。当t=1时,如下图所示:

当t=6时:

当t=11时:



当t=16时:

当t=21时:

从上图中,我们发现21棵树的时候,随机noise的影响基本上能够修正和消除。这种bagging投票的机制能够保证较好的降噪性,从而得到比较稳定的结果。
经过以上三个例子,我们发现RF中,树的个数越多,模型越稳定越能表现得好。在实际应用中,应该尽可能选择更多的树。值得一提的是,RF的表现同时也与random seed有关,即随机的初始值也会影响RF的表现。

之后就是真正的代码实现了
这里使用的还是随机选择特征的方法。
首先是一个特征选择函数:

  def choose_samples(self, data, k):
      '''choose the feature from data
      input:data, type = list
      output:k
      '''
      n, d = np.shape(data)
      feature = []
      for j in range(k):
          feature.append(rd.randint(0, d - 2))
      index = []
      for i in range(n):
          index.append(rd.randint(0, n-1))
      data_samples = []
      for i in range(n):
          data_tmp = []
          for fea in feature:
              data_tmp.append(data[i][fea])
          data_tmp.append(data[i][-1])
          data_samples.append(data_tmp)
          pass
      return data_samples, feature
      pass

在data数据里面选择出k维的数据。
之后就是随机森林的建立了,使用的决策树是上篇文章实现的决策树,尽量做到全是自己实现的:

  def random_forest(self, data, trees_num):
      '''create a forest
      input:data, type = list
      output:trees_result, trees_feature
      '''
      decisionTree = tree.decision_tree()
      trees_result = []
      trees_feature = []
      d = np.shape(data)[1]
      if d > 2:
          k = int(math.log(d - 1, 2)) + 1
      else:
          k = 1

      for i in range(trees_num):
          print('The ', i, ' tree. ')
          data_samples, feature = self.choose_samples(data, k)
          t = decisionTree.build_tree(data_samples)
          trees_result.append(t)
          trees_feature.append(feature)
          pass
      return trees_result, trees_feature

其实都很常规,最后返回的是树的数量和选取的特征。
之后就是一个切割数据和加载数据的工具函数:

def split_data(data_train, feature):
  '''select the feature from data
  input:data, feature
  output:data, type = list
  '''
  m = np.shape(data_train)[0]
  data = []
  for i in range(m):
      data_tmp = []
      for x in feature:
          data_tmp.append(data_train[i][x])
      data_tmp.append(data_train[i][-1])
      data.append(data_tmp)
  return data

def load_data():
  '''use the boston dataset from sklearn'''
  print('loading data......')
  dataSet = load_breast_cancer()
  data = dataSet.data
  target = dataSet.target
  for i in range(len(target)):
      if target[i] == 0:
          target[i] = -1
  dataframe = pd.DataFrame(data)
  dataframe.insert(np.shape(data)[1], 'target', target)
  dataMat = np.mat(dataframe)
  X_train, X_test, y_train, y_test =  train_test_split(dataMat[:, 0:-1], dataMat[:, -1], test_size=0.3, random_state=0)
  data_train = np.hstack((X_train, y_train))
  data_train = data_train.tolist()
  X_test = X_test.tolist()

  return data_train, X_test, y_test

load_data是把数据3,7切分,测试和训练。
然后就是预测函数和计算准确度的函数了:


  def get_predict(self, trees_result, trees_feature, data_train):
      '''predict the result
      input:trees_result, trees_feature, data
      output:final_prediction
      '''
      decisionTree = tree.decision_tree()
      m_tree = len(trees_result)
      m = np.shape(data_train)[0]
      result = []
      for i in range(m_tree):
          clf = trees_result[i]
          feature = trees_feature[i]
          data = tool.split_data(data_train, feature)
          result_i = []
          for i in range(m):
              result_i.append( list((decisionTree.predict(data[i][0 : -1], clf).keys()))[0] )
          result.append(result_i)
      final_predict = np.sum(result, axis = 0)
      return final_predict

  def cal_correct_rate(self, target, final_predict):
      m = len(final_predict)
      corr = 0.0
      for i in range(m):
          if target[i] * final_predict[i] > 0:
              corr += 1
          pass
      return corr/m
      pass

这个和之前决策树的差不多,也是调用了之前的代码。
最后就是入口函数:

def running():
  '''entrance'''
  data_train, text, target = load_data()
  forest = randomForest()
  predic = []
  for i in range(1, 20):
      trees, features = forest.random_forest(data_train, i)
      predictions = forest.get_predict(trees, features, text)
      accuracy = forest.cal_correct_rate(target, predictions)
      print('The forest has ', i, 'tree', 'Accuracy : ' , accuracy)
      predic.append(accuracy)

  plt.xlabel('Number of tree')
  plt.ylabel('Accuracy')
  plt.title('The relationship between tree number and accuracy')
  plt.plot(range(1, 20), predic, color = 'orange')
  plt.show()
  pass

if __name__ == '__main__':
  running()

计算了1到20课树他们之前准确率的变化,画了对比图。





大致趋势还是可以看得出是一直不断上升,最高应该是13到15这个区间吧,但是注意到2到5棵树的时候存在了一定的颠婆,这颠婆有点厉害,个人猜测应该是采集到了某些没有用的特征导致的,后面效果好了是因为树多了效果削弱了,并且有些的特征也采集不到。这个数据维度是30维的。有时间再做一个特征重要性的选择了。

所有代码GitHub上:
https://github.com/GreenArrow2017/MachineLearning/tree/master/MachineLearning/RandomForest

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容