随机森林 RF 算法原理及实践(二)

 上一节说过随机森林(Random Forest,RF)算法是一种重要的基于Bagging 的集成学习算法,它可以用来做分类、回归等问题。下面就分类问题展开学习。

一、随机森林算法模型

RF 在以决策树为基学习器构建 Bagging 集成的基础上,进一步在决策树的训练过程中引入随机属性选择,具体的就是传统决策树在选择划分属性时是在当前节点的属性集合(假设有 d 个属性)中选择一个最优的属性;而在 RF 中,对基决策树的每个节点,先从该节点的属性集合中随机选择一个包含 k 个属性的集合,然后再从这个子集中选择一个最优属性用于划分。若 k=d,则基决策树的构建和传统决策树相同,若 k=1,则是随机选择一个属性进行划分,一般推荐 k=log_{2}d

RF 其实质是对决策树算法的一种改进,将多个决策树合并在一起,每棵树的建立依赖于一个独立抽取的样本,森林中每棵树具有相同的分布,分类误差取决于每一棵树的分类能力和他们之间的相关性。

随机森林简单、容易实现、计算开销小,被誉为“代表集成学习技术水平的方法“。在 Bagging 算法中基学习器的多样性仅通过样本扰动而不同,但是在 RF 算法中不仅通过样本扰动还通过自属性扰动,最终会使得集成的泛化能力通过个体学习器之间差异度的增加而进一步提升。在引入属性扰动,随机森林中个体学习器的性能往往降低,但是随着个体学习器数目的增加,随机森林往往通常会收敛到更低的泛化误差。Bagging 使用的是确定性决策树,在选择划分属性时要对结点的所有属性进行考察,而随机森林使用的随机型决策树则只考察一个属性子集。

RF的主要缺点有:

  1. 在某些噪音比较大的样本集上,RF模型容易陷入过拟合;
  2. 取值划分比较多的特征容易对RF的决策产生更大的影响,从而影响拟合的模型的效果。

RF的主要优点有:

  1. 训练可以高度并行化,对于大数据时代的大样本训练速度有优势;
  2. 由于可以随机选择决策树节点划分特征,这样在样本特征维度很高的时候,仍然能高效的训练模型;
  3. 由于采用了随机采样,训练出的模型的方差小,泛化能力强;
  4. 相对于Boosting系列的Adaboost和GBDT, RF实现比较简单;
  5. 对部分特征缺失不敏感。

二、算法流程

  1. 假设训练样本的个数为 m,则对每一棵决策树的输入样本的个数都为 m,这 m 个样本是通过从训练集中有放回地随机抽取得到;
  2. 假设训练样本特征的个数为 n,对每一棵决策树的样本特征是从 n 个特征中随机挑选 k 个,然后从这 k 个输入特征中选择一个最优的进行分裂;
  3. 每棵树都这样分裂,直到该结点的所有训练样例都属于同一类,决策树分裂过程不需要剪枝。

三、Python 实现随机森林训练 

首先构建 CART 分类树,predict 函数利用构建好的 CART 树模型对样本进行预测,pickle 模块用于保存和导入训练好的随机森林RF 模型, 要计算 Gini 指数, 需要用到 pow 函数。保存到 tree.py 中:

from math import pow
class node:
    '''树的节点的类
    '''
    def __init__(self, fea=-1, value=None, results=None, right=None, left=None):
        self.fea = fea  # 用于切分数据集的属性的列索引值
        self.value = value  # 设置划分的值
        self.results = results  # 存储叶节点所属的类别
        self.right = right  # 右子树
        self.left = left  # 左子树

def split_tree(data, fea, value):
    '''根据特征fea中的值value将数据集data划分成左右子树
    input:  data(list):数据集
            fea(int):待分割特征的索引
            value(float):待分割的特征的具体值
    output: (set1,set2)(tuple):分割后的左右子树
    '''
    set_1 = []
    set_2 = []
    for x in data:
        if x[fea] >= value:
            set_1.append(x)
        else:
            set_2.append(x)
    return (set_1, set_2)

def label_uniq_cnt(data):
    '''统计数据集中不同的类标签label的个数
    input:  data(list):原始数据集
    output: label_uniq_cnt(int):样本中的标签的个数
    '''
    label_uniq_cnt = {}
    for x in data:
        label = x[len(x) - 1]  # 取得每一个样本的类标签label
        if label not in label_uniq_cnt:
            label_uniq_cnt[label] = 0
        label_uniq_cnt[label] = label_uniq_cnt[label] + 1
    return label_uniq_cnt

def cal_gini_index(data):
    '''计算给定数据集的Gini指数
    input:  data(list):树中
    output: gini(float):Gini指数
    '''
    total_sample = len(data)  # 样本的总个数 
    if len(data) == 0:
        return 0   
    label_counts = label_uniq_cnt(data)  # 统计数据集中不同标签的个数
     # 计算数据集的Gini指数
    gini = 0
    for label in label_counts:
        gini = gini + pow(label_counts[label], 2)
    gini = 1 - float(gini) / pow(total_sample, 2)
    return gini

def build_tree(data):
    '''构建树
    input:  data(list):训练样本
    output: node:树的根结点
    '''
    # 构建决策树,函数返回该决策树的根节点
    if len(data) == 0:
        return node()
    # 1、计算当前的Gini指数
    currentGini = cal_gini_index(data)
    bestGain = 0.0
    bestCriteria = None  # 存储最佳切分属性以及最佳切分点
    bestSets = None  # 存储切分后的两个数据集 
    feature_num = len(data[0]) - 1  # 样本中特征的个数
    # 2、找到最好的划分
    for fea in range(0, feature_num):
        # 2.1、取得fea特征处所有可能的取值
        feature_values = {}  # 在fea位置处可能的取值
        for sample in data:  # 对每一个样本
            feature_values[sample[fea]] = 1  # 存储特征fea处所有可能的取值
        # 2.2、针对每一个可能的取值,尝试将数据集划分,并计算Gini指数
        for value in feature_values.keys():  # 遍历该属性的所有切分点
            # 2.2.1、 根据fea特征中的值value将数据集划分成左右子树
            (set_1, set_2) = split_tree(data, fea, value)
            # 2.2.2、计算当前的Gini指数
            nowGini = float(len(set_1) * cal_gini_index(set_1) + \
                             len(set_2) * cal_gini_index(set_2)) / len(data)
            # 2.2.3、计算Gini指数的增加量
            gain = currentGini - nowGini
            # 2.2.4、判断此划分是否比当前的划分更好
            if gain > bestGain and len(set_1) > 0 and len(set_2) > 0:
                bestGain = gain
                bestCriteria = (fea, value)
                bestSets = (set_1, set_2) 
    # 3、判断划分是否结束
    if bestGain > 0:
        right = build_tree(bestSets[0])
        left = build_tree(bestSets[1])
        return node(fea=bestCriteria[0], value=bestCriteria[1], \
                    right=right, left=left)
    else:
        return node(results=label_uniq_cnt(data))  # 返回当前的类别标签作为最终的类别标签
def predict(sample, tree):
    '''对每一个样本sample进行预测
    input:  sample(list):需要预测的样本
            tree(类):构建好的分类树
    output: tree.results:所属的类别
    '''
    # 1、只是树根
    if tree.results != None:
        return tree.results
    else:
    # 2、有左右子树
        val_sample = sample[tree.fea]
        branch = None
        if val_sample >= tree.value:
            branch = tree.right
        else:
           branch = tree.left
        return predict(sample, branch)

训练随机森林模型:

# -*- coding: utf-8 -*-
"""
Created on Thu Mar 14 16:11:16 2019

@author: 2018061801
"""
import numpy as np
import random as rd
from math import log 
from tree import build_tree, predict
import pickle as pk
def load_data(file_name):
    '''导入数据
    input:  file_name(string):训练数据保存的文件名
    output: data_train(list):训练数据
    '''
    data_train = []
    f = open(file_name)
    for line in f.readlines():
        lines = line.strip().split("\t")
        data_tmp = []
        for x in lines:
            data_tmp.append(float(x))
        data_train.append(data_tmp)
    f.close()
    return data_train
def choose_samples(data, k):
    '''
    input:  data(list):原始数据集
            k(int):选择特征的个数
    output: data_samples(list):被选择出来的样本
            feature(list):被选择的特征index
    '''
    m, n = np.shape(data)  
    # 样本的个数和样本特征的个数
    # 1、选择出k个特征的index
    feature = []
    for j in range(k):
        feature.append(rd.randint(0, n - 2))  # n-1列是标签
    # 2、选择出m个样本的index
    index = []
    for i in range(m):
        index.append(rd.randint(0, m - 1))
    # 3、从data中选择出m个样本的k个特征,组成数据集data_samples
    data_samples = []
    for i in range(m):
        data_tmp = []
        for fea in feature:
            data_tmp.append(data[index[i]][fea])
        data_tmp.append(data[index[i]][-1])
        data_samples.append(data_tmp)
    return data_samples, feature

def random_forest_training(data_train, trees_num):
    '''构建随机森林
    input:  data_train(list):训练数据
            trees_num(int):分类树的个数
    output: trees_result(list):每一棵树的最好划分
            trees_feature(list):每一棵树中对原始特征的选择
    '''
    trees_result = []  # 构建好每一棵树的最好划分
    trees_feature = []
    n = np.shape(data_train)[1]  # 样本的维数
    if n > 2:
        k = int(log(n - 1, 2)) + 1 # 设置特征的个数
    else:
        k = 1
    # 开始构建每一棵树
    for i in range(trees_num):
        # 1、随机选择m个样本, k个特征
        data_samples, feature = choose_samples(data_train, k)
        # 2、构建每一棵分类树
        tree = build_tree(data_samples)
        # 3、保存训练好的分类树
        trees_result.append(tree)
        # 4、保存好该分类树使用到的特征
        trees_feature.append(feature)
    return trees_result, trees_feature

def split_data(data_train, feature):
    '''选择特征
    input:  data_train(list):训练数据集
            feature(list):要选择的特征
    output: data(list):选择出来的数据集
    '''
    m = np.shape(data_train)[0]
    data = []
    for i in range(m):
        data_x_tmp = []
        for x in feature:
            data_x_tmp.append(data_train[i][x])
        data_x_tmp.append(data_train[i][-1])
        data.append(data_x_tmp)
    return data

def get_predict(trees_result, trees_fiture, data_train):
    m_tree = len(trees_result)
    m = np.shape(data_train)[0]
    result = []
    for i in range(m_tree):
        clf = trees_result[i]
        feature = trees_fiture[i]
        data = split_data(data_train, feature)
        result_i = []
        for i in range(m):
            result_i.append(list(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(data_train, final_predict):
    m = len(final_predict)
    corr = 0.0
    for i in range(m):
        if data_train[i][-1] * final_predict[i] > 0:
            corr += 1
    return corr / m

def save_model(trees_result, trees_feature, result_file, feature_file):
    # 1、保存选择的特征
    m = len(trees_feature)
    f_fea = open(feature_file, "w")
    for i in range(m):
        fea_tmp = []
        for x in trees_feature[i]:
            fea_tmp.append(str(x))
        f_fea.writelines("\t".join(fea_tmp) + "\n")
    f_fea.close()  
    # 2、保存最终的随机森林模型
    with open(result_file, 'wb+') as f:
        pk.dump(trees_result, f)

if __name__ == "__main__":
   # 1、导入数据
    print ("----------- 1、load data -----------")
    data_train = load_data("D:/anaconda4.3/spyder_work/test3.txt")
    # 2、训练random_forest模型
    print ("----------- 2、random forest training ------------")
    trees_result, trees_feature = random_forest_training(data_train, 50)
    # 3、得到训练的准确性
    print ("------------ 3、get prediction correct rate ------------")
    result = get_predict(trees_result, trees_feature, data_train)
    corr_rate = cal_correct_rate(data_train, result)
    print ("\t------correct rate: ", corr_rate)
    # 4、保存最终的随机森林模型
    print ("------------ 4、save model -------------")
    save_model(trees_result, trees_feature, "result_file", "feature_file")

结果如下:

Reloaded modules: tree
----------- 1、load data -----------
----------- 2、random forest training ------------
------------ 3、get prediction correct rate ------------
        ------correct rate:  1.0
------------ 4、save model -------------

可以看出,随机森林的准确率为100%

注: 我使用 Python3.5 , 测试部分可以自己去试试

 

 

训练数据

参考文献:赵志勇《python 机器学习算法》(程序)

周志华《机器学习》

你可能感兴趣的:(机器学习)