【数模修炼之旅】07 随机森林模型 深度解析(教程+代码)

【数模修炼之旅】07 随机森林算法 深度解析(教程+代码)

接下来 C君将会用至少30个小节来为大家深度解析数模领域常用的算法,大家可以关注这个专栏,持续学习哦,对于大家的能力提高会有极大的帮助。

随机森林算法介绍及应用

随机森林(Random Forest)是一种集成学习算法,由多个决策树组成,通过对多个决策树的预测结果进行综合,来提高预测的准确性和鲁棒性。它在分类、回归和特征选择等任务中表现优异。通常在数模竞赛中,应用于回归任务和特征选择任务。

1.1 主要特点和工作原理

决策树集成:

  1. 随机森林由多棵决策树构成,每棵决策树是通过对训练数据的随机子集进行训练生成的。
  2. 每棵决策树在训练过程中,只考虑特征的一个随机子集来进行分裂。

数据子集和特征子集:

  1. Bootstrap抽样:从训练集中通过有放回的抽样生成不同的子集(即每棵树的训练数据集),这有助于减小模型的方差。
  2. 特征随机选择:在每个节点的分裂时,从特征子集中选择最优特征进行分裂,这减少了树与树之间的相关性。

模型预测:

  1. 对于分类任务,随机森林通过投票的方式确定最终的类别,即所有决策树的预测结果中出现次数最多的类别作为最终分类结果。
  2. 对于回归任务,随机森林计算所有决策树预测值的平均值作为最终预测值。

1.2 在数模中的应用

预测建模:

  1. 回归分析:用于预测连续变量,比如房价预测、需求预测等。通过随机森林回归模型,可以处理复杂的非线性关系,并对特征间的交互作用进行建模。
  2. 分类分析:用于分类问题,比如疾病诊断、市场细分等。通过集成多个决策树,随机森林能够提高分类准确率,处理特征噪声和不平衡数据问题。

特征选择:

  1. 特征重要性评估:随机森林提供了特征重要性评分,这可以帮助识别对目标变量最有影响的特征。在数模中,这有助于简化模型,选择关键特征,避免过拟合。
  2. 降维:在高维数据集上,随机森林可以通过特征重要性来进行特征选择,从而降低维度,提高模型性能。

异常检测:

数据异常识别:通过分析模型对数据点的预测误差,随机森林可以用于检测异常点(如欺诈检测、故障诊断等)。

模型融合与优化:

  1. 集成学习:在复杂模型中,随机森林可以与其他机器学习算法结合使用,形成更强大的预测模型。例如,结合随机森林和神经网络,可以提高预测性能。
  2. 参数优化:通过交叉验证和网格搜索等方法,对随机森林模型的超参数进行优化,以提高模型的泛化能力。

不确定性量化:

预测不确定性估计:随机森林能够提供每棵树的预测结果,这些预测结果的分布可以用来估计模型的预测不确定性,帮助做出更可靠的决策。

随机森林算法的基本步骤

随机森林算法的基本步骤可以分为以下几个阶段:数据准备、模型训练、预测和评估。

1. 数据准备

在使用随机森林算法之前,需要对数据进行适当的处理:

  • 数据清洗:处理缺失值、异常值,并确保数据质量。
  • 特征选择和预处理:可以进行特征缩放、编码分类变量等。
  • 划分数据集:将数据集分为训练集和测试集,以评估模型的性能。

2.2. 模型训练

随机森林的训练过程包括以下几个主要步骤:

a. Bootstrap抽样

  1. 创建多个子数据集
  • 从原始训练数据集中,通过有放回的抽样(即每次抽样后将样本放回)生成多个子数据集。每个子数据集将用于训练一棵决策树。

b. 建立决策树

  1. 训练决策树
  • 对于每个子数据集,训练一棵决策树。每棵决策树的训练过程包括:
    • 选择分裂特征:在每个节点分裂时,不是考虑所有特征,而是从特征的随机子集中选择一个或几个特征进行分裂。这种方法叫做“特征随机选择”。
    • 节点分裂:使用选择的特征来决定如何分裂节点。常用的分裂标准包括基尼系数(分类任务)或均方误差(回归任务)。
    • 树的生长:决策树在训练过程中会尽可能深地生长,直到满足某个停止条件(例如,达到最大深度或节点中的样本数少于某个阈值)。
  • 重复训练
  • 重复上述步骤,生成多棵决策树。每棵树都是在不同的训练子集和特征子集上训练的。

2.3. 模型预测

一旦训练完成,就可以使用随机森林进行预测:

  • 分类任务
    • 对于新的样本数据,将其输入到每棵决策树中。
    • 每棵决策树会输出一个分类结果。
    • 投票机制:所有决策树的分类结果通过投票机制决定最终的分类结果,即选择出现次数最多的类别作为最终预测结果。
  • 回归任务
    • 对于新的样本数据,将其输入到每棵决策树中。
    • 每棵决策树会输出一个预测值。
    • 平均机制:所有决策树的预测值取平均作为最终的回归预测值。

2.4. 模型评估

评估随机森林模型的性能是确保其有效性的关键步骤:

  • 准确率和误差计算
    • 对于分类任务,计算准确率、精确度、召回率和F1分数等。
    • 对于回归任务,计算均方误差(MSE)、均方根误差(RMSE)和决定系数(R²)等。
  • 交叉验证
    • 使用交叉验证(如K折交叉验证)来评估模型的泛化能力,即模型在不同数据子集上的表现,以减少过拟合风险。
  • 特征重要性分析
    • 通过分析各个特征的重要性评分,了解哪些特征对模型预测最重要,从而进行特征选择和降维。

2.5. 超参数调整(可选)

为了提高模型的性能,可以对随机森林的超参数进行调整:

  • 决策树数量(n_estimators):调整森林中决策树的数量,通常更多的决策树能提高模型的准确性,但也可能增加计算开销。
  • 树的最大深度(max_depth):控制每棵决策树的最大深度,以避免过拟合。
  • 每个节点的最小样本数(min_samples_split):在节点分裂时,要求至少有多少样本。
  • 每棵树使用的特征数量(max_features):控制每棵树在分裂节点时考虑的特征数量。

随机森林算法代码(matlab+python)

3.1 python

随机森林需要调整的参数有:
(1)    决策树的个数
(2)    特征属性的个数
(3)    递归次数(即决策树的深度)'''
from numpy import inf
from numpy import zeros
import numpy as np
from sklearn.model_selection import train_test_split
 
#生成数据集。数据集包括标签,全包含在返回值的dataset上
def get_Datasets():
    from sklearn.datasets import make_classification
    dataSet,classLabels=make_classification(n_samples=200,n_features=100,n_classes=2)
    #print(dataSet.shape,classLabels.shape)
    return np.concatenate((dataSet,classLabels.reshape((-1,1))),axis=1)
 
 
#切分数据集,实现交叉验证。可以利用它来选择决策树个数。但本例没有实现其代码。
#原理如下:
#第一步,将训练集划分为大小相同的K份;
#第二步,我们选择其中的K-1分训练模型,将用余下的那一份计算模型的预测值,
#这一份通常被称为交叉验证集;第三步,我们对所有考虑使用的参数建立模型
#并做出预测,然后使用不同的K值重复这一过程。
#然后是关键,我们利用在不同的K下平均准确率最高所对应的决策树个数
#作为算法决策树个数
 
def splitDataSet(dataSet,n_folds):     #将训练集划分为大小相同的n_folds份;
    fold_size=len(dataSet)/n_folds
    data_split=[]
    begin=0
    end=fold_size
    for i in range(n_folds):
        data_split.append(dataSet[begin:end,:])
        begin=end
        end+=fold_size
    return data_split
#构建n个子集
def get_subsamples(dataSet,n):
    subDataSet=[]
    for i in range(n):
        index=[]     #每次都重新选择k个 索引
        for k in range(len(dataSet)):  #长度是k
            index.append(np.random.randint(len(dataSet)))  #(0,len(dataSet)) 内的一个整数
        subDataSet.append(dataSet[index,:])
    return subDataSet
 
#    subDataSet=get_subsamples(dataSet,10)
#############################################################################
 
 
 
#根据某个特征及值对数据进行分类
def binSplitDataSet(dataSet,feature,value):
    
 
    mat0=dataSet[np.nonzero(dataSet[:,feature]>value)[0],:]
    mat1=dataSet[np.nonzero(dataSet[:,feature]<value)[0],:]
 
    return mat0,mat1
 
'''
  feature=2 
  value=1
  dataSet=get_Datasets()
  mat0,mat1= binSplitDataSet(dataSet,2,1)
'''
 
#计算方差,回归时使用
def regErr(dataSet):
    return np.var(dataSet[:,-1])*np.shape(dataSet)[0]
 
#计算平均值,回归时使用
def regLeaf(dataSet):
    return np.mean(dataSet[:,-1])
 
def MostNumber(dataSet):  #返回多类
    #number=set(dataSet[:,-1])
    len0=len(np.nonzero(dataSet[:,-1]==0)[0])
    len1=len(np.nonzero(dataSet[:,-1]==1)[0])
    if len0>len1:
        return 0
    else:
        return 1
    
    
#计算基尼指数   一个随机选中的样本在子集中被分错的可能性   是被选中的概率乘以被分错的概率 
def gini(dataSet):
    corr=0.0
    for i in set(dataSet[:,-1]):           #i 是这个特征下的 某个特征值
        corr+=(len(np.nonzero(dataSet[:,-1]==i)[0])/len(dataSet))**2
    return 1-corr
 
 
def select_best_feature(dataSet,m,alpha="huigui"):
    f=dataSet.shape[1]                                            #拿过这个数据集,看这个数据集有多少个特征,即f个
    index=[]
    bestS=inf;
    bestfeature=0;bestValue=0;
    if alpha=="huigui":
        S=regErr(dataSet)
    else:
        S=gini(dataSet)
        
    for i in range(m):
        index.append(np.random.randint(f))                        #在f个特征里随机,注意是随机!选择m个特征,然后在这m个特征里选择一个合适的分类特征。 
                                                                  
    for feature in index:
        for splitVal in set(dataSet[:,feature]):                  #set() 函数创建一个无序不重复元素集,用于遍历这个特征下所有的值
            mat0,mat1=binSplitDataSet(dataSet,feature,splitVal)  
            if alpha=="huigui":  newS=regErr(mat0)+regErr(mat1)   #计算每个分支的回归方差
            else:
                newS=gini(mat0)+gini(mat1)                        #计算被分错率
            if bestS>newS:
                bestfeature=feature
                bestValue=splitVal
                bestS=newS                      
    if (S-bestS)<0.001 and alpha=="huigui":                      # 对于回归来说,方差足够了,那就取这个分支的均值
        return None,regLeaf(dataSet)
    elif (S-bestS)<0.001:
        #print(S,bestS)
        return None,MostNumber(dataSet)                          #对于分类来说,被分错率足够下了,那这个分支的分类就是大多数所在的类。
    #mat0,mat1=binSplitDataSet(dataSet,feature,splitVal)
    return bestfeature,bestValue
 
def createTree(dataSet,alpha="huigui",m=20,max_level=10):             #实现决策树,使用20个特征,深度为10,
    bestfeature,bestValue=select_best_feature(dataSet,m,alpha=alpha)
    if bestfeature==None:
        return bestValue
    retTree={}
    max_level-=1
    if max_level<0:   #控制深度
        return regLeaf(dataSet)
    retTree['bestFeature']=bestfeature
    retTree['bestVal']=bestValue
    lSet,rSet=binSplitDataSet(dataSet,bestfeature,bestValue)      #lSet是根据特征bestfeature分到左边的向量,rSet是根据特征bestfeature分到右边的向量
    retTree['right']=createTree(rSet,alpha,m,max_level)
    retTree['left']=createTree(lSet,alpha,m,max_level)            #每棵树都是二叉树,往下分类都是一分为二。
    #print('retTree:',retTree)
    return retTree
 
def RondomForest(dataSet,n,alpha="huigui"):   #树的个数
    #dataSet=get_Datasets()
    Trees=[]        # 设置一个空树集合
    for i in range(n):
        X_train, X_test, y_train, y_test = train_test_split(dataSet[:,:-1], dataSet[:,-1], test_size=0.33, random_state=42)
        X_train=np.concatenate((X_train,y_train.reshape((-1,1))),axis=1)
        Trees.append(createTree(X_train,alpha=alpha))
    return Trees     # 生成好多树
###################################################################
 
#预测单个数据样本,重头!!如何利用已经训练好的随机森林对单个样本进行 回归或分类!
def treeForecast(trees,data,alpha="huigui"):      
    if alpha=="huigui":
        if not isinstance(trees,dict):                       #isinstance() 函数来判断一个对象是否是一个已知的类型
            return float(trees)
        
        if data[trees['bestFeature']]>trees['bestVal']:      # 如果数据的这个特征大于阈值,那就调用左支
            if type(trees['left'])=='float':                 #如果左支已经是节点了,就返回数值。如果左支还是字典结构,那就继续调用, 用此支的特征和特征值进行选支。 
                return trees['left']
            else:
                return treeForecast(trees['left'],data,alpha)
        else:
            if type(trees['right'])=='float':
                return trees['right']
            else:
                return treeForecast(trees['right'],data,alpha)   
    else:
        if not isinstance(trees,dict):                      #分类和回归是同一道理
            return int(trees)
        
        if data[trees['bestFeature']]>trees['bestVal']:
            if type(trees['left'])=='int':
                return trees['left']
            else:
                return treeForecast(trees['left'],data,alpha)
        else:
            if type(trees['right'])=='int':
                return trees['right']
            else:
                return treeForecast(trees['right'],data,alpha)   
            
            
 
#随机森林 对 数据集打上标签   0、1 或者是 回归值
def createForeCast(trees,test_dataSet,alpha="huigui"):
    cm=len(test_dataSet)                      
    yhat=np.mat(zeros((cm,1)))
    for i in range(cm):                                     #
        yhat[i,0]=treeForecast(trees,test_dataSet[i,:],alpha)    #
    return yhat
 
 
#随机森林预测
def predictTree(Trees,test_dataSet,alpha="huigui"):      #trees 是已经训练好的随机森林   调用它!
    cm=len(test_dataSet)   
    yhat=np.mat(zeros((cm,1)))   
    for trees in Trees:
        yhat+=createForeCast(trees,test_dataSet,alpha)    #把每次的预测结果相加
    if alpha=="huigui": yhat/=len(Trees)            #如果是回归的话,每棵树的结果应该是回归值,相加后取平均
    else:
        for i in range(len(yhat)):                  #如果是分类的话,每棵树的结果是一个投票向量,相加后,
                                                    #看每类的投票是否超过半数,超过半数就确定为1
            if yhat[i,0]>len(Trees)/2:            
                yhat[i,0]=1
            else:
                yhat[i,0]=0
    return yhat
 
 
 
if __name__ == '__main__' :
    dataSet=get_Datasets()  
    print(dataSet[:,-1].T)                                     #打印标签,与后面预测值对比  .T其实就是对一个矩阵的转置
    RomdomTrees=RondomForest(dataSet,4,alpha="fenlei")         #这里我训练好了 很多树的集合,就组成了随机森林。一会一棵一棵的调用。
    print("---------------------RomdomTrees------------------------")
    #print(RomdomTrees[0])
    test_dataSet=dataSet                               #得到数据集和标签
    yhat=predictTree(RomdomTrees,test_dataSet,alpha="fenlei")  # 调用训练好的那些树。综合结果,得到预测值。
    print(yhat.T)
#get_Datasets()
    print(dataSet[:,-1].T-yhat.T)

3.2 matlab

% 1. 数据准备
% 读取数据
data = readtable('data.csv'); % 替换为你的数据文件

% 假设目标变量为'target',其余为特征
features = data(:, setdiff(data.Properties.VariableNames, 'target'));
target = data.target;

% 划分数据集
cv = cvpartition(height(data), 'HoldOut', 0.3); % 70%训练,30%测试
trainData = data(training(cv), :);
testData = data(test(cv), :);

% 提取特征和目标变量
trainFeatures = trainData(:, setdiff(data.Properties.VariableNames, 'target'));
trainTarget = trainData.target;
testFeatures = testData(:, setdiff(data.Properties.VariableNames, 'target'));
testTarget = testData.target;

% 2. 训练随机森林模型(分类任务示例)
numTrees = 100; % 决策树数量
rfModel = TreeBagger(numTrees, trainFeatures, trainTarget, 'Method', 'classification');

% 3. 模型预测
predictedLabels = predict(rfModel, testFeatures);

% 4. 模型评估(分类任务)
% 计算分类准确率
accuracy = sum(strcmp(predictedLabels, testTarget)) / length(testTarget);
disp(['Accuracy: ', num2str(accuracy)]);

% 计算混淆矩阵
confMat = confusionmat(testTarget, predictedLabels);
disp('Confusion Matrix:');
disp(confMat);

% 5. 特征重要性分析(分类任务)
importance = rfModel.OOBPermutedPredictorDeltaError;
disp('Feature Importance:');
disp(importance);

% ----
% 6. 若进行回归任务,替换以下部分:

% 训练随机森林模型(回归任务示例)
% rfModel = TreeBagger(numTrees, trainFeatures, trainTarget, 'Method', 'regression');

% 模型预测
% predictedValues = predict(rfModel, testFeatures);

% 模型评估(回归任务)
% 计算均方误差
% mse = mean((predictedValues - testTarget).^2);
% disp(['Mean Squared Error: ', num2str(mse)]);

% 计算决定系数(R²)
% ssRes = sum((predictedValues - testTarget).^2);
% ssTot = sum((testTarget - mean(testTarget)).^2);
% r2 = 1 - (ssRes / ssTot);
% disp(['R-squared: ', num2str(r2)]);

% 特征重要性分析(回归任务)
% importance = rfModel.OOBPermutedPredictorDeltaError;
% disp('Feature Importance:');
% disp(importance);

代码注意事项:

需要参加数模竞赛的同学,可以看我的这个名片,会有最新的助攻哦:(大型比赛前会对名片进行更新)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值