Python手写随机森林算法:从原理到泰坦尼克号实战

Mia
Mia
发布时间:2026-10-13 | 2人浏览过
原创

本文详细解析了基于Python实现的随机森林算法原理,涵盖有放回抽样、特征子集选取及不剪枝策略。通过Kaggle泰坦尼克号101数据集进行实战建模,展示了数据预处理、Gini系数分裂逻辑及投票机制。文中对比了手写模型与scikit-learn库的性能差异,并提供了提升准确率的特征工程与参数调整方向。

本文分享一个使用Python实现的随机森林算法示例,供读者参考学习。

随机森林是数据挖掘领域广泛使用的分类预测算法,其核心是以分类或回归决策树作为基分类器。该算法包含几个关键要点:

首先,对大小为m的数据集进行样本量同样为m的有放回抽样;其次,随机抽取K个特征形成特征子集,样本量确定可采用平方根或自然对数等方法;再次,每棵树完全生成且不进行剪枝处理;最后,每个样本的预测结果由所有树的预测通过投票产生(回归任务中则为各树叶节点的平均值)。

Python知名机器学习库scikit-learn的官方文档对该算法有详尽介绍,可参考链接:http://scikit-learn.org/stable/modules/ensemble.html#random-forests

出于个人研究与测试目的,本文基于经典的Kaggle 101泰坦尼克号乘客数据集建立模型并进行评估。比赛页面及数据集下载地址为:https://www.kaggle.com/c/titanic

泰坦尼克号沉没是历史上著名的海难事件。通过数据挖掘方法研究具体历史问题颇具趣味。模型的主要目标是根据乘客的性别、年龄、舱位、登船地点等特征,预测其生还可能性,这属于典型的二分类预测问题。数据集的字段名及实例如下:

PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
1 0 3 Braund, Mr. Owen Harris male 22 1 0 A/5 21171 7.25 S
2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Thayer) female 38 1 0 PC 17599 71.2833 C85 C
3 1 3 Heikkinen, Miss. Laina female 26 0 0 STON/O2. 3101282 7.925 S
4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35 1 0 113803 53.1 C123 S
5 0 3 Allen, Mr. William Henry male 35 0 0 373450 8.05 S

需要说明的是,SibSp代表sister/brother/spouse,即乘客随行兄弟姐妹及配偶的人数;Parch代表parents/children,即父母及子女人数。

以下展示完整的数据处理及建模过程,环境基于ubuntu系统搭配python 3.4(推荐使用已集成pandas、numpy、sklearn等常用包的anaconda科学计算环境)。

为节省切换输入法时间,代码主要注释使用英文,中文注释为后续补充。

# -*- coding: utf-8 -*-
"""
@author: kim
"""
from model import *#载入基分类器的代码
#ETL:same procedure to training set and test set
training=pd.read_csv('train.csv',index_col=0)
test=pd.read_csv('test.csv',index_col=0)
SexCode=pd.DataFrame([1,0],index=['female','male'],columns=['Sexcode']) #将性别转化为01
training=training.join(SexCode,how='left',on=training.Sex)
training=training.drop(['Name','Ticket','Embarked','Cabin','Sex'],axis=1)#删去几个不参与建模的变量,包括姓名、船票号,船舱号
test=test.join(SexCode,how='left',on=test.Sex)
test=test.drop(['Name','Ticket','Embarked','Cabin','Sex'],axis=1)
print('ETL IS DONE!')
#MODEL FITTING
#===============PARAMETER AJUSTMENT============
min_leaf=1
min_dec_gini=0.0001
n_trees=5
n_fea=int(math.sqrt(len(training.columns)-1))
#==============================================
'''''
BEST SCORE:0.83
min_leaf=30
min_dec_gini=0.001
n_trees=20
'''
#ESSEMBLE BY RANDOM FOREST
FOREST={}
tmp=list(training.columns)
tmp.pop(tmp.index('Survived'))
feaList=pd.Series(tmp)
for t in range(n_trees):
#  fea=[]
  feasample=feaList.sample(n=n_fea,replace=False)#select feature
  fea=feasample.tolist()
  fea.append('Survived')
#    feaNew=fea.append(target)
  subset=training.sample(n=len(training),replace=True)#generate the dataset with replacement
  subset=subset[fea]
#  print(str(t)+' Classifier built on feature:')
#  print(list(fea))
  FOREST[t]=tree_grow(subset,'Survived',min_leaf,min_dec_gini) #save the tree
#MODEL PREDICTION
#======================
currentdata=training
output='submission_rf_20151116_30_0.001_20'
#======================
prediction={}
for r in currentdata.index:#a row
  prediction_vote={1:0,0:0}
  row=currentdata.get(currentdata.index==r)
  for n in range(n_trees):
    tree_dict=FOREST[n] #a tree
    p=model_prediction(tree_dict,row)
    prediction_vote[p]+=1
  vote=pd.Series(prediction_vote)
  prediction[r]=list(vote.order(ascending=False).index)[0]#the vote result
result=pd.Series(prediction,name='Survived_p')
#del prediction_vote
#del prediction
#result.to_csv(output)
t=training.join(result,how='left')
accuracy=round(len(t[t['Survived']==t['Survived_p']])/len(t),5)
print(accuracy)

上述代码实现了随机森林算法。如前所述,随机森林由一系列决策树组合而成。决策树在每次分裂时,使用Gini系数衡量当前节点的“不纯净度”。若按某特征的某个分裂点划分数据集后,能使Gini系数下降最多(显著降低输出变量不纯度),则选定该特征及分裂点为当前最佳分割。具体代码如下:

# -*- coding: utf-8 -*-
"""
@author: kim
"""
import pandas as pd
import numpy as np
#import sklearn as sk
import math
def tree_grow(dataframe,target,min_leaf,min_dec_gini):
  tree={} #renew a tree
  is_not_leaf=(len(dataframe)>min_leaf)
  if is_not_leaf:
    fea,sp,gd=best_split_col(dataframe,target)
    if gd>min_dec_gini:
      tree['fea']=fea
      tree['val']=sp
#      dataframe.drop(fea,axis=1) #1116 modified
      l,r=dataSplit(dataframe,fea,sp)
      l.drop(fea,axis=1)
      r.drop(fea,axis=1)
      tree['left']=tree_grow(l,target,min_leaf,min_dec_gini)
      tree['right']=tree_grow(r,target,min_leaf,min_dec_gini)
    else:#return a leaf
      return leaf(dataframe[target])
  else:
    return leaf(dataframe[target])
  return tree
def leaf(class_lable):
  tmp={}
  for i in class_lable:
    if i in tmp:
      tmp[i]+=1
    else:
      tmp[i]=1
  s=pd.Series(tmp)
  s.sort(ascending=False)
  return s.index[0]
def gini_cal(class_lable):
  p_1=sum(class_lable)/len(class_lable)
  p_0=1-p_1
  gini=1-(pow(p_0,2)+pow(p_1,2))
  return gini
def dataSplit(dataframe,split_fea,split_val):
  left_node=dataframe[dataframe[split_fea]<=split_val]
  right_node=dataframe[dataframe[split_fea]>split_val]
  return left_node,right_node
def best_split_col(dataframe,target_name):
  best_fea=''#modified 1116
  best_split_point=0
  col_list=list(dataframe.columns)
  col_list.remove(target_name)
  gini_0=gini_cal(dataframe[target_name])
  n=len(dataframe)
  gini_dec=-99999999
  for col in col_list:
    node=dataframe[[col,target_name]]
    unique=node.groupby(col).count().index
    for split_point in unique: #unique value
      left_node,right_node=dataSplit(node,col,split_point)
      if len(left_node)>0 and len(right_node)>0:
        gini_col=gini_cal(left_node[target_name])*(len(left_node)/n)+gini_cal(right_node[target_name])*(len(right_node)/n)
        if (gini_0-gini_col)>gini_dec:
          gini_dec=gini_0-gini_col#decrease of impurity
          best_fea=col
          best_split_point=split_point
    #print(col,split_point,gini_0-gini_col)
  return best_fea,best_split_point,gini_dec
def model_prediction(model,row): #row is a df
  fea=model['fea']
  val=model['val']
  left=model['left']
  right=model['right']
  if row[fea].tolist()[0]<=val:#get the value
    branch=left
  else:
    branch=right
  if ('dict' in str( type(branch) )):
    prediction=model_prediction(branch,row)
  else:
    prediction=branch
  return prediction

实际上,上述代码仍有较大的效率提升空间。在数据集规模不大但生成较多树(如100棵)的情况下,运行速度会显著变慢。此外,将预测结果提交至Kaggle评测发现,测试集上的正确率略低于使用sklearn相应包预测的结果(0.77512)。若要提升准确率,主要方向包括:构造新特征以及调整现有模型参数。

此处抛砖引玉,欢迎大家对建模思路和算法实现方法提出修改意见。

更多关于Python相关内容感兴趣的读者可查看本站专题:《Python数据结构与算法教程》、《Python编码操作技巧总结》、《Python函数使用技巧总结》、《Python字符串操作技巧汇总》及《Python入门与进阶经典教程》

希望本文所述内容对大家学习Python程序设计有所帮助。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表