不调包,使用numpy手动实现随机森林算法,附详细注释,以及西瓜数据集

不调包,使用numpy手动实现随机森林算法
数据集为西瓜数据集,也可以使用鸾尾花数据集,开启上面注释即可,其中决策树为计算基尼指数的CART决策树。

数据集链接:https://pan.baidu.com/s/1FDttk6QPJCGRRxm2ueB4Yg?pwd=1234
提取码:1234

from sklearn.datasets import load_iris
import numpy as np
from sklearn.metrics import precision_score,recall_score,f1_score
# 使用sklearn加载Iris数据集
# iris = load_iris()
# iris_data = np.column_stack((iris.data, iris.target_names[iris.target]))
# 预测西瓜数据集
file_path="C:\\Users\\焦仁豪\\Desktop\\watermelon_3.csv"
iris_data=np.loadtxt(file_path,delimiter=",",skiprows=1,dtype=str,encoding='utf-8')
np.random.seed(42)
np.random.shuffle(iris_data)
split_idx = int(0.8 * len(iris_data))
train_data = iris_data[:split_idx]
test_data = iris_data[split_idx:]
# 定义决策树节点类
class TreeNode:
    def __init__(self, feature_idx=None, threshold=None, left=None, right=None, value=None):
        self.feature_idx = feature_idx # 分类的特征,如密度,纹理,糖分
        self.threshold = threshold  #分类阈值,例如糖分<=0.38
        self.left = left #左子树
        self.right = right #右子树
        self.value = value #节点值

# 定义决策树构建函数
def build_decision_tree(data):
    # 获取数据中的特征个数,因为数据的最后一列是类别标签,因此特征个数为总列数减去1
    features = len(data[0]) - 1
    # 如果数据集中的所有样本属于同一类别,返回一个叶节点,节点的值为该类别
    if len(set(row[-1] for row in data)) == 1:
        return TreeNode(value=data[0][-1])

    # 如果特征个数为0,说明所有样本的特征都相同,返回一个叶节点,节点的值为占多数的类别
    if features == 0:
        #key=data.count 表示在比较元素时,以它们在列表 data 中出现的次数作为比较的依据。
        return TreeNode(value=max(set(row[-1] for row in data), key=data.count))
    # 找到最佳的分割特征和阈值
    best_feature, best_threshold = find_best_split(data, features)
    # 如果找不到最佳分割特征,返回一个叶节点,节点的值为占多数的类别
    if best_feature is None:
        return TreeNode(value=max(set(row[-1] for row in data), key=data.count))
    # 根据最佳分割特征和阈值将数据集划分成左右两部分
    left_data, right_data = split_data(data, best_feature, best_threshold)
    # 递归构建左子树和右子树
    left_subtree = build_decision_tree(left_data)
    right_subtree = build_decision_tree(right_data)
    # 返回当前节点,包括分割特征、阈值以及左右子树
    return TreeNode(feature_idx=best_feature, threshold=best_threshold, left=left_subtree, right=right_subtree)

# 定义辅助函数:找到最佳分割点,为了处理连续性数据,因为,iris数据集他的四个特征均为连续值,所以需要寻找最佳分割点
def find_best_split(data, features):
    best_gini = 1.0
    best_feature = None
    best_threshold = None
    # 遍历每一个特征
    for feature in range(features):
        # 对于每个特征,获取所有可能的取值(values):
        values = set(row[feature] for row in data)
        # 遍历每个取值,将数据集划分为左右两个子集,然后计算基尼不纯度:
        for threshold in values:
            # 计算根据每个属性划分后的基尼值,选取基尼指数最小的那个,
            # 在这里处理连续值的时候并没有使用(ai+ai+1)/2,而是直接根据每个ai进行划分,后续在这里优化一下
            left_data, right_data = split_data(data, feature, threshold)
            # 如果划分出来,左边没有数据,或者右边没有数据,跳过本次划分
            if len(left_data) == 0 or len(right_data) == 0:
                continue
            # 计算每一个值计算出来的基尼指数,然后进行比较,算出来基尼指数最小的特征作为最优化分属性
            gini = calculate_gini(left_data, right_data)
            if gini < best_gini:
                best_gini = gini
                best_feature = feature#例如糖分
                best_threshold = threshold#例如0.38
    return best_feature, best_threshold

# 定义辅助函数:根据特征和阈值划分数据,例如,分割是好瓜,还是坏瓜
def split_data(data, feature, threshold):
    left_data = []
    right_data = []
    for row in data:
        if row[feature] < threshold:
            left_data.append(row)
        else:
            right_data.append(row)
    return left_data, right_data

# 定义辅助函数:计算基尼不纯度
def calculate_gini(left_data, right_data):
    total = len(left_data) + len(right_data)

    def gini(data):
        if len(data) == 0:
            return 0.0
        unique, counts = np.unique(data, return_counts=True)
        probabilities = counts / len(data)#计算pk,这里的probabilities为一个数组,里面装着所有计算出来的pk
        return 1.0 - np.sum(probabilities ** 2)#返回1-pk的平方

    gini_left = gini([row[-1] for row in left_data])
    gini_right = gini([row[-1] for row in right_data])

    weighted_gini = (len(left_data) / total) * gini_left + (len(right_data) / total) * gini_right
    return weighted_gini

# 构建决策树
decision_tree = build_decision_tree(train_data)

# 定义辅助函数:使用决策树进行预测
def predict(tree, sample):
    # 这一行检查当前节点是否为叶节点,如果是,说明已经达到决策树的底部,直接返回该叶节点的值
    if tree.value is not None:
        return tree.value
    if sample[tree.feature_idx] < tree.threshold:
        return predict(tree.left, sample)
    else:
        return predict(tree.right, sample)
# 对所有样本进行预测
# predictions = [predict(decision_tree, sample) for sample in test_data]
class RandomForest:
    def __init__(self, n_trees):
        #n_trees 参数指定要构建的决策树的数量。trees 是存储决策树的列表。
        self.n_trees = n_trees
        #trees 是存储决策树的列表。
        self.trees = []

    def fit(self, data):
        for _ in range(self.n_trees):
            #进行 Bootstrap 采样:从原始数据集中有放回地选择相同数量的样本,构成新的数据集,replace=True允许数据重复选
            bootstrap_sample = data[np.random.choice(len(data), size=len(data), replace=True)]
            # 使用这些每次不同的数据构建决策树
            tree = build_decision_tree(bootstrap_sample)
            self.trees.append(tree)
    def predict(self, sample):
        # 集成决策
        predictions = [predict(tree, sample) for tree in self.trees]
        # 对于分类问题,使用投票
        return max(set(predictions), key=predictions.count)

# 创建随机森林
random_forest = RandomForest(n_trees=10)
# 使用训练数据拟合随机森林
random_forest.fit(train_data)

# 对测试数据进行预测
rf_predictions = [random_forest.predict(sample) for sample in test_data]
# 打印预测结果
for i in range(len(test_data)):
    print(f"样本 {test_data[i, :-1]} 的预测结果为类别 {rf_predictions[i]}")
    # print(test_data[i, -1:] == rf_predictions[i])

# 计算准确率
rf_accuracy = np.sum(test_data[:, -1] == rf_predictions) / len(test_data)
print(f"随机森林准确率: {rf_accuracy * 100}%")
pos_label = '是'
print(f"查准率为: {precision_score(test_data[:,-1],rf_predictions,pos_label=pos_label) * 100}%")
print(f"查全率为:{recall_score(test_data[:,-1],rf_predictions,pos_label=pos_label)*100}%")
print(f"f1_分数为:{f1_score(test_data[:,-1],rf_predictions,pos_label=pos_label)*100}%")



  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值