机器学习——决策树
算法原理
用一句话总结决策树的核心思想:相似的输入必会产生相似的输出。
例如预测某人薪资:
年龄:1-青年,2-中年,3-老年
学历:1-本科,2-硕士,3-博士
经历:1-出道,2-一般,3-老手,4-骨灰
性别:1-男性,2-女性
年龄 | 学历 | 经历 | 性别 | ==> | 薪资 |
---|---|---|---|---|---|
1 | 1 | 1 | 1 | ==> | 6000(低) |
2 | 1 | 3 | 1 | ==> | 10000(中) |
3 | 3 | 4 | 1 | ==> | 50000(高) |
... | ... | ... | ... | ==> | ... |
1 | 3 | 2 | 2 | ==> | ? |
为了提高搜索效率,使用树形数据结构处理样本数据:
$$年龄=1\left\{
\begin{aligned}
学历1 \\
学历2 \\
学历3 \\
\end{aligned}
\right.
\quad\quad
年龄=2\left\{
\begin{aligned}
学历1 \\
学历2 \\
学历3 \\
\end{aligned}
\right.
\quad\quad
年龄=3\left\{
\begin{aligned}
学历1 \\
学历2 \\
学历3 \\
\end{aligned}
\right.$$
首先从训练样本矩阵中选择第一个特征进行子表划分,使每个子表中该特征的值全部相同,然后再在每个子表中选择下一个特征按照同样的规则继续划分更小的子表,不断重复直到所有的特征全部使用完为止,此时便得到叶级子表,其中所有样本的特征值全部相同。对于待预测样本,根据其每一个特征的值,选择对应的子表,逐一匹配,直到找到与之完全匹配的叶级子表,用该子表中样本的输出,通过平均(回归)或者投票(分类)为待预测样本提供输出。
随着子表的划分,信息熵(信息的混乱程度)越来越小,信息越来越纯,数据越来越有序。
决策树回归器模型相关API:
import sklearn.tree as st # 创建决策树回归器模型 决策树的最大深度为4 model = st.DecisionTreeRegressor(max_depth=4) model.fit(train_x, train_y) # 训练模型 pred_test_y = model.predict(test_x) # 测试模型
案例:预测波士顿地区房屋价格
1、读取数据,打断原始数据集。 划分训练集和测试集。
import sklearn.datasets as sd import sklearn.utils as su import sklearn.tree as st import sklearn.metrics as sm # 加载波士顿地区房价数据集 boston = sd.load_boston() print(boston.data.shape) # (506, 13) print(boston.data[0]) # [6.320e-03 1.800e+01 ... 1.530e+01 3.969e+02 4.980e+00] print(boston.target.shape) # (506,) print(boston.target[0]) # 24.0 print(boston.feature_names) # |CRIM|ZN|INDUS|CHAS|NOX|RM|AGE|DIS|RAD|TAX|PTRATIO|B|LSTAT| # 犯罪率|住宅用地比例|商业用地比例|是否靠河|空气质量|房间数|年限|距中心区距离|路网密度|房产税|师生比|黑人比例|低地位人口比例| # 打乱原始数据集的输入和输出 x, y = su.shuffle(boston.data, boston.target, random_state=7) # 划分训练集与测试集 train_size = int(len(x) * 0.8) train_x, test_x, train_y, test_y = x[:train_size], x[train_size:], y[:train_size], y[train_size:] # 构建决策树模型, 训练模型 model = st.DecisionTreeRegressor(max_depth=4) model.fit(train_x, train_y) # 预测 pred_test_y = model.predict(test_x) print(sm.r2_score(test_y, pred_test_y)) # 0.8202560889408635
工程优化
不必用尽所有的特征,叶级子表中允许混杂不同的特征值,以此降低决策树的层数,在精度牺牲可接受的前提下,提高模型的性能。通常情况下,可以优先选择使信息熵减少量最大的特征作为划分子表的依据。
集合算法
根据多个不同模型给出的预测结果,利用平均(回归)或者投票(分类)的方法,得出最终预测结果。
基于决策树的集合算法,就是按照某种规则,构建多棵彼此不同的决策树模型,分别给出针对未知样本的预测结果,最后通过平均或投票得到相对综合的结论。
正向激励
首先为样本矩阵中的样本随机分配初始权重,由此构建一棵带有权重的决策树,在由该决策树提供预测输出时,通过加权平均或者加权投票的方式产生预测值。将训练样本代入模型,预测其输出,对那些预测值与实际值不同的样本,提高其权重,由此形成第二棵决策树。重复以上过程,构建出不同权重的若干棵决策树。
正向激励相关API:
import sklearn.tree as st import sklearn.ensemble as se # model: 决策树模型(一颗) model = st.DecisionTreeRegressor(max_depth=4) # 自适应增强决策树回归模型 # n_estimators:构建400棵不同权重的决策树,训练模型 model = se.AdaBoostRegressor(model, n_estimators=400, random_state=7) model.fit(train_x, train_y) pred_test_y = model.predict(test_x)
案例:基于正向激励训练预测波士顿地区房屋价格的模型。
import sklearn.datasets as sd import sklearn.utils as su import sklearn.tree as st import sklearn.metrics as sm import sklearn.ensemble as se boston = sd.load_boston() # 加载数据集 # 打乱数据集 x, y = su.shuffle(boston.data, boston.target, random_state=7) # 划分训练集与测试集 train_size = int(len(x) * 0.8) train_x, test_x, train_y, test_y = x[:train_size], x[train_size:], y[:train_size], y[train_size:] # 构建决策树模型, 训练模型 model = st.DecisionTreeRegressor(max_depth=4) # 构建正向激励决策树模型 model = se.AdaBoostRegressor(model, n_estimators=400, random_state=7) model.fit(train_x, train_y) pred_test_y = model.predict(test_x) # 预测 print(sm.r2_score(test_y, pred_test_y)) # 0.9070963117187197
特征重要性
作为决策树模型训练过程的副产品,根据每个特征划分子表前后的信息熵减少量就标志了该特征的重要程度,此即为该特征重要性指标。训练得到的模型对象提供了属性:feature_importances_来存储每个特征的重要性。
获取样本矩阵特征重要性属性:
model.fit(train_x, train_y)
fi = model.feature_importances_
案例:获取普通决策树与正向激励决策树训练的两个模型的特征重要性值,按照从大到小顺序输出绘图。
""" demo01_fi.py 特征重要性 """ import sklearn.datasets as sd import sklearn.utils as su import sklearn.tree as st import sklearn.metrics as sm import matplotlib.pyplot as plt import numpy as np import sklearn.ensemble as se boston = sd.load_boston() # 加载数据集 print(boston.data.shape) # (506, 13) print(boston.target.shape) # (506,) fnames = boston.feature_names # ['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO' 'B' 'LSTAT'] x, y = su.shuffle(boston.data, boston.target, random_state=7) # 打乱数据集 # 划分训练集与测试集 train_size = int(len(x) * 0.8) train_x, test_x, train_y, test_y = x[:train_size], x[train_size:], \ y[:train_size], y[train_size:] model = st.DecisionTreeRegressor(max_depth=4) # 构建决策树模型 model.fit(train_x, train_y) # 训练模型 dt_fi = model.feature_importances_ # 输出单棵决策树的特征重要性 # 构建正向激励决策树模型 model = se.AdaBoostRegressor(model, n_estimators=400, random_state=7) model.fit(train_x, train_y) ad_fi = model.feature_importances_ # 正向激励决策树的特征重要性 # 柱状图显示特征重要性 plt.figure('Feature Ipltortance', facecolor='lightgray') plt.subplot(211) plt.title('Decision Tree') plt.ylabel('Ipltortance', fontsize=12) plt.tick_params(labelsize=10) # 刻度字体大小 plt.grid(axis='y', linestyle=':') x = np.arange(13) sorted_indices = np.argsort(dt_fi)[::-1] # 从高到低排序 plt.bar(x, dt_fi[sorted_indices], 0.8, color='dodgerblue', label='DT FI') plt.xticks(x, fnames[sorted_indices]) plt.legend() plt.subplot(212) plt.title('AdaBoostRegressor') plt.ylabel('Ipltortance', fontsize=12) plt.tick_params(labelsize=10) plt.grid(axis='y', linestyle=':') sorted_indices = np.argsort(ad_fi)[::-1] plt.bar(x, ad_fi[sorted_indices], 0.8, color='orangered', label='AdaBoost FI') plt.xticks(x, fnames[sorted_indices]) plt.legend() plt.show()
自助聚合
每次从总样本矩阵中以有放回抽样的方式随机抽取部分样本构建决策树,这样形成多棵包含不同训练样本的决策树,以削弱某些强势样本对模型预测结果的影响,提高模型的泛化特性。
随机森林
在自助聚合的基础上,每次构建决策树模型时,不仅随机选择部分样本,而且还随机选择部分特征,这样的集合算法,不仅规避了强势样本对预测结果的影响,而且也削弱了强势特征的影响,使模型的预测能力更加泛化。随机森林回归模型,也属于聚合算法的一种。
随机森林相关API:
import sklearn.ensemble as se # max_depth:决策树最大深度10 # n_estimators:构建1000棵决策树,训练模型 # min_samples_split: 子表中最小样本数 若小于这个数字,则不再继续向下拆分 model = se.RandomForestRegressor(max_depth=10, n_estimators=1000, min_samples_split=2)
案例:分析共享单车的需求,从而判断如何进行共享单车的投放。
import numpy as np import matplotlib.pyplot as plt import sklearn.utils as su import sklearn.ensemble as se import sklearn.metrics as sm # 读取bike_day.csv数据集 # headers = None data = [] with open('../machine_learning_date/bike_day.csv', 'r') as f: for i, line in enumerate(f.readlines()): if i == 0: headers = line.split(',')[2:] else: data.append(line.split(',')[2:]) headers = np.array(headers) data = np.array(data, dtype='f8') # 整理数据集 x = data[:, 0:11] y = data[:, -1] # 拆分测试集与训练集 x, y = su.shuffle(x, y, random_state=7) train_size = int(len(x) * 0.9) train_x, test_x, train_y, test_y = x[:train_size], x[train_size:], y[:train_size], y[train_size:] # 构建随机森林回归器模型 model = se.RandomForestRegressor(max_depth=10, n_estimators=1000, min_samples_split=2) model.fit(train_x, train_y) pred_test_y = model.predict(test_x) print('r2 for day.csv:', sm.r2_score(test_y, pred_test_y)) # 获取特征重要性 day_fi = model.feature_importances_ day_headers = headers[0:11] print(day_headers) # 读取bike_hour.csv数据集 headers = None data = [] with open('../machine_learning_date/bike_hour.csv', 'r') as f: for i, line in enumerate(f.readlines()): if i == 0: headers = line.split(',')[2:] else: data.append(line.split(',')[2:]) headers = np.array(headers) data = np.array(data, dtype='f8') # 整理数据集 x = data[:, 0:12] y = data[:, -1] # 拆分测试集与训练集 x, y = su.shuffle(x, y, random_state=7) train_size = int(len(x) * 0.9) train_x, test_x, train_y, test_y = x[:train_size], x[train_size:], y[:train_size], y[train_size:] # 构建随机森林回归器模型 model = se.RandomForestRegressor(max_depth=10, n_estimators=1000, min_samples_split=2) model.fit(train_x, train_y) # 针对测试集进行预测 输出评估得分 pred_test_y = model.predict(test_x) print('r2 for hour.csv:', sm.r2_score(test_y, pred_test_y)) # 获取特征重要性 hour_fi = model.feature_importances_ hour_headers = headers[0:12]
画图显示两组样本数据的特征重要性:
# 绘制特征重要性柱状图 # 柱状图显示特征重要性 plt.figure('Feature Importance', facecolor='lightgray') plt.subplot(211) plt.title('Feature Importances', fontsize=16) plt.ylabel('Importance', fontsize=12) plt.tick_params(labelsize=10) plt.grid(axis='y', linestyle=':') x = np.arange(day_fi.size) sorted_indices = np.argsort(day_fi)[::-1] plt.bar(x, day_fi[sorted_indices], 0.8, color='dodgerblue', label='day FI') plt.xticks(x, day_headers[sorted_indices]) plt.legend() plt.subplot(212) plt.ylabel('Importance', fontsize=12) plt.tick_params(labelsize=10) plt.grid(axis='y', linestyle=':') x = np.arange(hour_fi.size) sorted_indices = np.argsort(hour_fi)[::-1] plt.bar(x, hour_fi[sorted_indices], 0.8, color='orangered', label='hour FI') plt.xticks(x, hour_headers[sorted_indices]) plt.legend() plt.show()