随机森林的原理相对来说简单多了,准确率提升,又因为“双随机”预防了过拟合,无需再对元决策树剪枝,真是太棒了!
以下是在CART分类树的基础上构建的随机森林,主要是增加了2个参数,以及对整体准确率的计算。
import numpy as np
from collections import Counter
class RF:
def __init__(self, T=10, k=1):
'''
定义参数
:param T: 元决策树的个数
:param k: 随机选取的特征的个数
'''
self.T = T
self.k = k
def baggingDataSet(self, dataSet):
'''
从原始训练集使用bagging方法选取等数量训练集,和未被选择的数据集(作为验证集)
:return: 训练集,验证集,验证集对应的索引值
'''
m = len(dataSet)
trainDataSet, validDataSet, selectedm = [], [], []
for i in range(m):
k = np.random.randint(0, m)
selectedm.append(k)
trainDataSet.append(dataSet[k])
unselectedm = list(set(range(0, m)) - set(selectedm)) # 验证集对应的索引值
for i in unselectedm:
validDataSet.append(dataSet[i])
return trainDataSet, validDataSet, unselectedm
def caclGini(self, dataSet):
'''
计算基尼指数
:param dataSet: 包含标签类的数据集 m*(n+1)。 离散特征用int数据类型,连续特征用float数据类型。 如果包含离散特征,则必须是二维列表类型。(因为array的数据类型都是一样的)
:return: 数据集的基尼指数
'''
targets = [example[-1] for example in dataSet] # 获取标签列表
d = {} # 存储标签及其相应的数量
gini = 1
for target in targets:
if target not in d:
d[target] = 0
d[target] += 1
for target in d.keys():
prob = d[target] / len(dataSet)
gini -= prob**2 # 计算基尼指数
return gini
def splitDataSet(self, dataSet, feature, value):
'''
按照给定的特征和特征值切分数据集。这里根据是否连续型数据或离散型数据,采用不同的二分法
:param dataSet: 同上
:param feature: 待切分的特征
:param value: 选取的特征值
:return: 二分切割后的2个子数据集
'''
left, right = [], []
if isinstance(value, int): # 离散型
for i in range(len(dataSet)):
reduceSet = dataSet[i][: feature]
reduceSet.extend(dataSet[i][feature + 1:])
if dataSet[i][feature] == value: # 离散型数据根据值是否等于给定值划分数据集
left.append(reduceSet)
if dataSet[i][feature] != value:
right.append(reduceSet)
if isinstance(value, float): # 连续型
for i in range(len(dataSet)):
reduceSet = dataSet[i][: feature]
reduceSet.extend(dataSet[i][feature + 1:])
if dataSet[i][feature] <= value: # 连续型数据根据值是否小于等于给定值划分数据集
left.append(reduceSet)
if dataSet[i][feature] > value:
right.append(reduceSet)
return left, right
def chooseBestFeature(self, dataSet):
'''
选出使基尼指数最小的特征 与 对应的特征值
:param dataSet: 同上
:return: 最佳分类特征与特征值
'''
n = len(dataSet[0]) - 1 # 获取特征的数量
splitGini, bestFeatrue, bestValue = None, None, None # 声明变量,防止“本地变量调用前被引用”的错误
minGini = np.inf # 一个无限大的正数
randFeature = []
if self.k < n: # 如果特征个数大于指定特征个数k,那么从[0,n)随机选择k个不同的数字作为索引
index, i = None, 0
while i < self.k:
index = np.random.randint(0, n)
if index not in randFeature:
randFeature.append(index)
i += 1
else: # 如果特征个数少了,那么按一般规则处理
randFeature = range(n)
for featureIndex in randFeature:
values = set([example[featureIndex] for example in dataSet]) # 获取所有可能的特征取值
for value in values:
left, right = self.splitDataSet(dataSet, featureIndex, value)
leftGini = self.caclGini(left) # 左子树
rightGini = self.caclGini(right) # 右子树
splitGini = (len(left) / len(dataSet)) * leftGini + (len(right) / len(dataSet)) * rightGini # 分割后的基尼指数
# print('特征索引:', featureIndex, '特征值:', value, '基尼指数:', splitGini)
if splitGini < minGini:
minGini = splitGini
bestFeatrue = featureIndex
bestValue = value
return bestFeatrue, bestValue
def classtarget(self, targets):
'''
辅助函数
:param targets: 类别列表
:return: 返回最终叶子中,数量最多的列表
'''
d = {}
for target in targets:
if target not in d:
d[target] = 0
d[target] += 1
return max(d, key=lambda x: d[x]) # 获取值最大的键
def createTree(self, dataSet, featureLabel):
'''
创建决策树
:param dataSet: 同上
:param featureLabel: 特征索引-特征含义 对照表
:return: 二叉决策树
'''
if not dataSet:
return None
targets = [example[-1] for example in dataSet]
if len(set(targets)) == 1: # 只包含一个类别
return targets[0]
if (len(dataSet[0]) - 1) < self.k: # 没有可分的特征
return self.classtarget(targets)
bestFeatureIndex, bestValue = self.chooseBestFeature(dataSet)
featureLable_copy = featureLabel.copy() # 避免对源数据的修改
bestFeatureLabel = featureLable_copy[bestFeatureIndex]
if isinstance(bestValue, int): # 如果是离散型数据,删除对照表中的对应的索引,因为不会再用到。 如果是连续型的数据,则保留,因为可能再用到
del featureLable_copy[bestFeatureIndex]
mytree = {}
mytree['FeatLabel'] = bestFeatureLabel
mytree['FeatValue'] = bestValue
lSet, rSet = self.splitDataSet(dataSet, bestFeatureIndex, bestValue)
mytree['left'] = self.createTree(lSet, featureLable_copy)
mytree['right'] = self.createTree(rSet, featureLable_copy)
return mytree
def predict(self, tree, featureLabel, testvec):
'''
根据训练好的决策树,输出单个待测试样本的类别
:param tree: 训练好的决策树
:param featureLabel: 同上
:param testvec: 待测试的样本(不含标签)
:return: 预测类别
'''
if not isinstance(tree, dict):
return tree
bestFeatureIndex = featureLabel.index(tree['FeatLabel'])
value = testvec[bestFeatureIndex]
if isinstance(value, int):
if value == tree['FeatValue']:
return self.predict(tree['left'], featureLabel, testvec)
if value != tree['FeatValue']:
return self.predict(tree['right'], featureLabel, testvec)
if isinstance(value, float):
if value <= tree['FeatValue']:
return self.predict(tree['left'], featureLabel, testvec)
if value > tree['FeatValue']:
return self.predict(tree['right'], featureLabel, testvec)
def predictall(self, tree, featureLabel, testvecall):
'''
根据训练好的决策树,输出所有待测试样本的类别
:param tree: 同上
:param featureLabel: 同上
:param testvecall: 所有待测试样本(不含标签) 格式是二维列表
:return:
'''
predict = []
for testvec in testvecall:
predict.append(self.predict(tree, featureLabel, testvec))
return predict
def creataRF(self, dataSet, featureLabel):
'''
创建随机森林
:param dataSet:
:param featureLabel:
:return: self.T颗决策树组成的列表, 和 随机森林对验证集的准确率
'''
forest = []
dictIndex_to_predict = {} # 索引值对应的预测值,预测值存储在列表中,最终生成 每棵树对 所有未训练样本的预测值 的集合
for t in range(self.T):
trainDataSet, validDataSet, unselectm = self.baggingDataSet(dataSet)
tree = self.createTree(trainDataSet, featureLabel)
forest.append(tree) # 把树添加到森林
testvecall = [example[: -1] for example in validDataSet] # 获取这棵树未训练样本的特征数据
predictions = self.predictall(tree, featureLabel, testvecall) # 预测所有的特征数据
for index, predict in zip(unselectm, predictions): # 把预测值添加到预测字典中
if index not in dictIndex_to_predict:
dictIndex_to_predict[index] = [predict] # 索引作为键, 预测值的列表 作为值
else:
dictIndex_to_predict[index].append(predict)
count = 0
for index in dictIndex_to_predict.keys():
predict = dictIndex_to_predict[index]
dictIndex_to_predict[index] = Counter(predict).most_common(1)[0][0] # 统计所有树对 同一样本的预测值,选出得票数最多的作为 森林的预测值
if dictIndex_to_predict[index] == dataSet[index][-1]:
count += 1
accuracy = count / len(dictIndex_to_predict) # 计算准确率
return forest, accuracy
# 测试数据,因为数据量少,且是人造的,测试结果不准确。 只为测试能否跑通程序
dataSet = [[0, 1, 0.15, 'yes'],
[1, 1, 0.25, 'yes'],
[1, 0, 0.21, 'no'],
[0, 1, 0.45, 'no'],
[0, 1, 0.55, 'no']]
feature_label = ['No Surfacing', 'Flippers', 'other']
cart = RF(k=2)
mytree = cart.createTree(dataSet, feature_label)
rf, accuracy = cart.creataRF(dataSet, feature_label)
print(accuracy)