随机森林实现回归预测(糖尿病数据集)

文章目录

*
1.实验简介
2.算法分析
3.具体实现
4.代码
5.结果分析

1.实验简介

本次实验需要实现一个随机森林模型并在糖尿病数据集上进行回归预测。

2.算法分析

随机森林是由N颗简单的决策树组合而成,对于分类任务随机森林的输出可以采用简单的投票法决定随机森林的预测值;对于回归任务来说,就是把N颗回归决策树的输出结果进行平均。
对于随机森林来进行回归任务,可以分两个部分来实现。第一部分我们先实现回归决策树,第二部分在回归决策树的基础上实现回归随机森林。

3.具体实现

3.1 回归决策树
在上一次实验的分类决策树基础上实现回归决策树有以下的改变:

  1. 增益的衡量 在这里我们用方差来替代
  2. 叶子节点的预测值由占多数的类别改为平均值
  3. 在寻找最佳属性及其阈值时,直接取实际的数据作为候选阈值,不用排序再取两个数据的均值
  4. 划分过的属性在之后的划分还能继续使用
  5. 因为没有了属性使用的限制,需要实现树的深度的控制max_depth这个参数。另外,也需实现min_samples这个参数

3.2 回归随机森林
回归森林使用N棵回归决策树, 这里有两点需要注意:

  1. 样本的随机性
    对于每棵树输入的数据需要是不同的,如果对N棵树输入同样的数据,那得出的结果都是一样的,随机森林也就没有了意义。所以,对于每一棵树,使用的数据是训练集通过随机有放回的采样得到的。
  2. 属性的随机性
    寻找最优划分属性时,先随机选出一部分,再在这一部分中选取增益最大属性的。

4.代码

import math
import matplotlib.pyplot as plt
import numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import r2_score
from sklearn.model_selection import train_test_split
from sklearn import datasets

class DecisionNode(object):
    def __init__(self, f_idx, threshold, value=None, L=None, R=None):
        self.f_idx = f_idx
        self.threshold = threshold
        self.value = value
        self.L = L
        self.R = R

def find_best_threshold(dataset: np.ndarray, f_idx: int):
    best_gain = -math.inf
    best_threshold = None

    candidate = list(set(dataset[:, f_idx].reshape(-1)))
    for threshold in candidate:
        L, R = split_dataset(dataset, f_idx, threshold)
        gain = calculate_var_gain(dataset, L, R)
        if gain > best_gain:
            best_gain = gain
            best_threshold = threshold
    return best_threshold, best_gain

def calculate_var(dataset: np.ndarray):
    y_ = dataset[:, -1].reshape(-1)
    var = np.var(y_)
    return var

def calculate_var_gain(dataset, l, r):
    var_y = calculate_var(dataset)
    var_gain = var_y - len(l) / len(dataset) * calculate_var(l) - len(r) / len(dataset) * calculate_var(r)
    return var_gain

def split_dataset(X: np.ndarray, f_idx: int, threshold: float):
    L = X[:, f_idx] < threshold
    R = ~L
    return X[L], X[R]

def mean_y(dataset):
    y_ = dataset[:, -1]
    return np.mean(y_)

def build_tree(dataset: np.ndarray, f_idx_list: list, depth, max_depth, min_samples):

    class_list = [data[-1] for data in dataset]
    n, m = dataset.shape
    k = int(math.log(m, 2)) + 1
    if n < min_samples:
        return DecisionNode(None, None, value=mean_y(dataset))

    elif depth > max_depth:
        return DecisionNode(None, None, value=mean_y(dataset))

    elif class_list.count(class_list[0]) == len(class_list):
        return DecisionNode(None, None, value=mean_y(dataset))

    else:

        best_gain = -math. inf
        best_threshold = None
        best_f_idx = None

        f_idx_list_random = list(np.random.choice(m-1, size=k, replace=False))
        for i in f_idx_list_random:
            threshold, gain = find_best_threshold(dataset, i)
            if gain > best_gain:
                best_gain = gain
                best_threshold = threshold
                best_f_idx = i

        L, R = split_dataset(dataset, best_f_idx, best_threshold)
        if len(L) == 0:
            depth += 1
            L_tree = DecisionNode(None, None, mean_y(dataset))
        else:
            depth += 1
            L_tree = build_tree(L, f_idx_list, depth, max_depth, min_samples)

        if len(R) == 0:
            R_tree = DecisionNode(None, None, mean_y(dataset))
        else:
            R_tree = build_tree(R, f_idx_list, depth, max_depth, min_samples)

        return DecisionNode(best_f_idx, best_threshold, value=None, L=L_tree, R=R_tree)

def predict_one(model: DecisionNode, data):
    if model.value is not None:
        return model.value
    else:
        feature_one = data[model.f_idx]
        branch = None
        if feature_one >= model.threshold:
            branch = model.R
        else:
            branch = model.L
        return predict_one(branch, data)

def random_sample(dataset):
    n, _ = dataset.shape
    sub_data = np.copy(dataset)
    random_data_idx = np.random.choice(n, size=n, replace=True)
    sub_data = sub_data[random_data_idx]
    return sub_data[:, 0:-1], sub_data[:, -1]

class Random_forest(object):
    def __init__(self, min_samples, max_depth):
        self.min_samples = min_samples
        self.max_depth = max_depth

    def fit(self, X: np.ndarray, y: np.ndarray) -> None:
        dataset_in = np.c_[X, y]
        f_idx_list = [i for i in range(X.shape[1])]
        depth = 0
        self.my_tree = build_tree(dataset_in, f_idx_list, depth, self.max_depth, self.min_samples)

    def predict(self, X: np.ndarray) -> np.ndarray:
        predict_list = []
        for data in X:
            predict_list.append(predict_one(self.my_tree, data))

        return np.array(predict_list)

if __name__ == "__main__":
    X, y = datasets.load_diabetes(return_X_y=True)
    y_predict_list = []
    r2_score_list = []
    tree_number = []
    MAE_list = []
    MAPE_list = []

    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
    print(y_test.shape)

    dataset = np.c_[X_train, y_train]
    np.seterr(divide='ignore', invalid='ignore')

    T = 100
    for i in range(T):
        X_train_samples, y_train_samples = random_sample(dataset)
        m = Random_forest(min_samples=5, max_depth=20)
        m.fit(X_train_samples, y_train_samples)
        y_predict = m.predict(X_test)
        y_predict_list.append(y_predict)
        print("epoc", i+1, " done")

        y_ = np.mean(y_predict_list, axis=0)

        score = r2_score(y_test, y_)
        r2_score_list.append(score)
        tree_number.append((i + 1))

        errors = abs(y_ - y_test)
        MAE_list.append(np.mean(errors))

        mape = 100 * (errors / y_test)
        MAPE_list.append(np.mean(mape))

    plt.plot(tree_number[5:-1], r2_score_list[5:-1])
    plt.title('r2_score')
    plt.xlabel('tree number')
    plt.ylabel('r2_score')
    plt.show()

    plt.plot(tree_number, MAPE_list)
    plt.xlabel('tree number')
    plt.ylabel('MAPE %')
    plt.title("MAPE: Mean Absolute Percentage Error")
    plt.show()

    y_result = np.mean(y_predict_list, axis=0)
    print("r2_score:", r2_score(y_test, y_result))

    errors1 = abs(y_result - y_test)
    print('Mean Absolute Error:', np.round(np.mean(errors1), 2), 'degrees.')

    mape = 100 * (errors1 / y_test)
    print('MAPE:', np.round(np.mean(mape), 2), '%.')

    plt.figure(figsize=(20, 5))
    plt.plot([i for i in range(y_test.shape[0])], y_test, color='red', alpha=0.8, label="y_test")
    plt.plot([i for i in range(y_test.shape[0])], y_result, color='blue', alpha=0.8, label="y_result")
    plt.legend(loc="upper right")
    plt.title("My Random forest")
    plt.show()

    regressor = RandomForestRegressor(n_estimators=100, min_samples_leaf=5)
    regressor.fit(X_train, y_train)
    y_pred = regressor.predict(X_test)
    print('sklearn score:{}'.format(r2_score(y_test, y_pred)))

    errors = abs(y_pred - y_test)

    print('Mean Absolute Error:', np.round(np.mean(errors), 2), 'degrees.')
    mape = 100 * (errors / y_test)
    accuracy = 100 - np.mean(mape)
    print('Accuracy:', round(accuracy, 2), '%.')

    plt.figure(figsize=(20, 5))
    plt.plot([i for i in range(y_test.shape[0])], y_test, color='red', alpha=0.8, label="y_test")
    plt.plot([i for i in range(y_test.shape[0])], y_pred, color='blue', alpha=0.8, label="y_pred")
    plt.legend(loc="upper right")
    plt.title("sklearn RandomForestRegressor")
    plt.show()

5.结果分析

5.1 与sklearn自带的随机森林模块对比
这里绘制了两张折线图,展现了真实值与预测值的差别,可以看出:

  1. 两种方法的真实值与预测值的走势轨迹都大致相同。
  2. 上下两幅图的预测值走势是基本相同的,看出两种方法预测出的结果差别不大。
    随机森林实现回归预测(糖尿病数据集)
    随机森林实现回归预测(糖尿病数据集)
    下表也能看出两种方法得出的结果差别不大
    随机森林实现回归预测(糖尿病数据集)

5.2 决策树数目对随机森林的影响
下面两幅图分别是r2_score和MAPE随决策树数目的变化曲线图。可以看出从1-20棵树变化时,两幅图的曲线都变化很快,快速收敛。在达到40棵树时,收敛效果都已经很好了,再增加的基分类器(决策树)的数目,效果基本不会提升。

随机森林实现回归预测(糖尿病数据集)
随机森林实现回归预测(糖尿病数据集)

Original: https://blog.csdn.net/qq_51879318/article/details/125127690
Author: ShowerSong
Title: 随机森林实现回归预测(糖尿病数据集)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/606188/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球