人工智能
第三章 集合模型(回归)
一、决策树
1. 基本算法原理
-
核心思想:相似的输入必会产生相似的输出。
- 例如预测某人薪资:
- 年龄: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 ==> ? 样本数量非常庞大 100W个样本 换一种数据结构,来提高检索效率 树形结构 回归 : 均值 分类 : 投票(概率)
-
为了提高搜索效率,使用树形数据结构处理样本数据:
年 龄 = 1 { 学 历 1 学 历 2 学 历 3 年 龄 = 2 { 学 历 1 学 历 2 学 历 3 年 龄 = 3 { 学 历 1 学 历 2 学 历 3 年龄=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. 年龄=1⎩⎪⎨⎪⎧学历1学历2学历3年龄=2⎩⎪⎨⎪⎧学历1学历2学历3年龄=3⎩⎪⎨⎪⎧学历1学历2学历3 -
首先从训练样本矩阵中选择一个特征进行子表划分,使每个子表中该特征的值全部相同,然后再在每个子表中选择下一个特征按照同样的规则继续划分更小的子表,不断重复直到所有的特征全部使用完为止,此时便得到叶级子表,其中所有样本的特征值全部相同。对于待预测样本,根据其每一个特征的值,选择对应的子表,逐一匹配,直到找到与之完全匹配的叶级子表,用该子表中样本的输出,通过平均(回归)或者投票(分类)为待预测样本提供输出。
-
首先选择哪一个特征进行子表划分决定了决策树的性能。这么多特征,使用哪个特征先进行子表划分?
2. CART 回归树
- sklearn提供的决策树底层为cart树(Classification and Regression Tree),cart回归树在解决回归问题时的步骤如下:
-
原始数据集S,此时树的深度depth=0;
-
针对集合S,遍历每一个特征的每一个value(遍历数据中的所有离散值 (12个) ),用该value将原数据集S分裂成2个集合:左集合left(<=value的样本)、右集合right(>value的样本),分别计算这2个集合的mse(均方误差),找到使(left_mse+right_mse)最小的那个value,记录下此时的特征名称和value,这个就是最佳分割特征以及最佳分割值;
mse:均方误差
((y1-y1’)^2 + (y2-y2’)^2 + (y3-y3’)^2 + (y4-y4’)^2 ) / 4 = mse
x1 y1 y1’
x2 y2 y2’
x3 y3 y3’
x4 y4 y4’
-
找到最佳分割特征以及最佳分割value之后,用该value将集合S分裂成2个集合,depth+=1;
-
针对集合left、right分别重复步骤2,3,直到达到终止条件。
- 决策树底层结构 为二叉树
3. CART 回归树的终止条件
- 终止条件有如下几种:
1、特征已经用完了:没有可供使用的特征再进行分裂了,则树停止分裂;
2、子节点中没有样本了:此时该结点已经没有样本可供划分,该结点停止分裂;
3、树达到了人为预先设定的最大深度:depth >= max_depth,树停止分裂。
4、节点的样本数量达到了人为设定的阈值:样本数量 < min_samples_split ,则该节点停止分裂;
4. 决策树回归实现
- 决策树回归器模型相关API:
import sklearn.tree as st
# 创建决策树回归器模型 决策树的最大深度为4
model = st.DecisionTreeRegressor(max_depth=4)
# 训练模型
# train_x: 二维数组样本数据
# train_y: 训练集中对应每行样本的结果
model.fit(train_x, train_y)
# 测试模型
pred_test_y = model.predict(test_x)
5. 案例:预测波士顿地区房屋价格
- 训练决策树回归模型,预测波士顿地区房屋价格
- 新增决策树模型,比较预测效果
5.1 读取数据,打断原始数据集。 划分训练集和测试集。
import sklearn.datasets as sd
import sklearn.utils as su
# 加载波士顿地区房价数据集
boston = sd.load_boston()
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:]
5.2 创建决策树回归器模型,使用训练集训练模型。使用测试集测试模型。
import sklearn.tree as st
# 创建决策树回归模型
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.6773870228226634
"""
二、集合模型
- 单个模型得到的预测结果总是片面的,所以可以使用不同的条件构建多个不同模型,给出的多个预测结果,利用平均(回归)或者投票(分类)的方法,得出最终预测结果。
- 基于决策树的集合算法,就是按照某种规则,构建多棵彼此不同的决策树模型,分别给出针对未知样本的预测结果,最后通过平均或投票得到相对综合的结论。
- 常用的集合模型包括Boosting类模型(AdaBoost、GBDT)与Bagging(自助聚合、随机森林)类模型。
1. AdaBoost 回归模型(正向激励)
- 首先为样本矩阵中的样本随机分配初始权重,由此构建一棵带有权重的决策树,在由该决策树提供预测输出时,通过加权平均或者加权投票的方式产生预测值。将训练样本代入模型,预测其输出,对那些预测值与实际值不同的样本,提高其权重,由此形成第二棵决策树。重复以上过程,构建出不同权重的若干棵决策树。
- 最终由得到的这些决策树一同对未知输出的样本进行预测,通过平均的方式产生预测值。
2. AdaBoost 回归实现
- AdaBoost 回归模型相关 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)
3. 特征重要性
- 作为决策树模型训练过程的副产品,根据划分子表时选择特征的顺序标志了该特征的重要程度,此即为该特征重要性指标。训练得到的模型对象提供了属性:feature_importances_来存储每个特征的重要性。
model.fit(train_x, train_y)
fi = model.feature_importances_
4. GBDT 回归
- GBDT(Gradient Boosting Decision Tree 梯度提升树)通过多轮迭代,每轮迭代产生一个弱回归器,每个回归器在上一轮回归器的残差(残差在数理统计中是指实际观察值与估计值(拟合值)之间的差)基础上进行训练。基于预测结果的残差设计损失函数。GBDT训练的过程即是求该损失函数最小值的过程。
5. GBDT 回归原理
5.1 原理1
- 预测结果,金额小于1000的预测为15岁,金额大于1000的预测为25岁
5.2 原理2
- 再次计算每个样本残差,结果为0,再无优化空间,迭代结束。
5.3 原理3
6. GBDT 回归实现
- GBDT 回归相关 API 如下:
import sklearn.tree as st
import sklearn.ensemble as se
# 自适应增强决策树回归模型
# n_estimators:构建400棵不同权重的决策树,训练模型
model = se.GridientBoostingRegressor(
max_depth=10, n_estimators=1000, min_samples_split=2)
"""
创建基于决策树的正向激励回归器模型
model = se.GridientBoostingRegressor(
max_depth=4, 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))
7. 自助聚合
- 每次从总样本矩阵中以有放回抽样的方式随机抽取部分样本构建决策树,这样形成多棵包含不同训练样本的决策树,以削弱某些强势样本对模型预测结果的影响,提高模型的泛化特性。
8. 随机森林
- 在自助聚合的基础上,每次构建决策树模型时,不仅随机选择部分样本,而且还随机选择部分特征,这样的集合算法,不仅规避了强势样本对预测结果的影响,而且也削弱了强势特征的影响,使模型的预测能力更加泛化。
9. 随机森林回归实现
- 随机森林回归相关 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 pandas as pd
import matplotlib.pyplot as plt
# 加载数据集
import sklearn.datasets as sd
boston = sd.load_boston()
# 把数据存入dataframe
data = pd.DataFrame(boston.data, columns=boston.feature_names) # 数据,特征名称
data['TARGET'] = boston.target
data
1. 基于线性回归训练模型
import sklearn.model_selection as ms
import sklearn.linear_model as lm
import sklearn.metrics as sm
# 整理输入集输出集,拆分测试集训练集
x, y = data.iloc[:, :-1], data['TARGET'] # 所有行,除了最后一列都要
# random_state: 随机种子 对同一组数据使用相同的随机种子划分数据集,得到的结果是一样的
train_x, test_x, train_y, test_y = ms.train_test_split(x, y, test_size=0.2, random_state=7) # 20%做测试
# 训练一个模型
model = lm.LinearRegression()
model.fit(train_x, train_y)
# 针对测试样本进行测试,评估
pred_test_y = model.predict(test_x)
print(sm.r2_score(test_y, pred_test_y))
"""
0.5785415472763427
"""
2. 基于岭回归训练模型
# 训练一个模型
# 调整岭回归的参数
params = np.arange(0, 130, 10)
for param in params:
model = lm.Ridge(param)
model.fit(train_x, train_y)
pred_test_y = model.predict(test_x)
# 评估误差
print(param, '->', sm.r2_score(test_y, pred_test_y))
"""
0 -> 0.5785415472763407
10 -> 0.5783802227286681
20 -> 0.5952305907645055
30 -> 0.6088556382989273
40 -> 0.6197079306450874
50 -> 0.6284277390131747
60 -> 0.6355093721368099
70 -> 0.6413175705099186
80 -> 0.6461224027876207
90 -> 0.6501263632540781
100 -> 0.6534834314895364
110 -> 0.6563123089340717
120 -> 0.6587056780309234
"""
model = lm.Ridge()
model.fit(train_x, train_y)
test_x, test_y = train_x.iloc[:30:4], train_y[:30:4]
pred_test_y = model.predict(test_x)
# 评估误差
print(sm.r2_score(test_y, pred_test_y))
"""
0.5703641157344471
"""
3. 基于多项式回归训练模型
import sklearn.pipeline as pl
import sklearn.preprocessing as sp
model = pl.make_pipeline(sp.PolynomialFeatures(2), lm.Ridge())
model.fit(train_x, train_y) # 对train_x做多项式特征扩展,然后训练线性回归模型
pred_test_y = model.predict(test_x)
# 评估
print(sm.r2_score(test_y, pred_test_y))
"""
0.7240028637771994
"""
4. 基于决策树训练模型
import sklearn.tree as st
# 训练一个模型
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.6773870228226633
"""
# 获取特征重要性
fi = model.feature_importances_
s = pd.Series(fi, index=train_x.columns)
s.sort_values().plot.barh()
5. 基于AdaBoost训练模型
import sklearn.ensemble as se
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.8500714067889193
"""
# 获取特征重要性
fi = model.feature_importances_
s = pd.Series(fi, index=train_x.columns)
s.sort_values().plot.barh()
6. 基于GBDT训练模型
# max_depth: 最大深度
# n_estimators: 弱模型的数量
# min_samples_split: 样本最小拆分数 若叶子节点样本数量小于该值,则不再向下拆分
params_a = [5,6,7,8,9]
params_b = [3,5,7,9]
for a in params_a:
for b in params_b:
model = se.GradientBoostingRegressor(max_depth=a, n_estimators=50, random_state=7, min_samples_split=b)
model.fit(train_x, train_y)
pred_test_y = model.predict(test_x)
print(a,b,'->',sm.r2_score(test_y, pred_test_y))
"""
5 3 -> 0.8444283217637356
5 5 -> 0.8362802986262108
5 7 -> 0.8244591764017146
5 9 -> 0.8298497346662654
6 3 -> 0.8249951883226199
6 5 -> 0.8059360418597886
6 7 -> 0.8141640561570052
6 9 -> 0.8275116414579332
7 3 -> 0.7969013091094028
7 5 -> 0.7812411922035595
7 7 -> 0.8008200242501035
7 9 -> 0.8256894969860424
8 3 -> 0.7585802800753443
8 5 -> 0.7723735710591806
8 7 -> 0.7829110226097211
8 9 -> 0.8085822093658608
9 3 -> 0.7460888650571811
9 5 -> 0.7694016598612741
9 7 -> 0.7860402350340085
9 9 -> 0.8067612861881294
"""
7. 基于随机森林回归训练模型
model = se.RandomForestRegressor(max_depth=5, n_estimators=400, min_samples_split=3)
model.fit(train_x, train_y)
pred_test_y = model.predict(test_x)
print(sm.r2_score(test_y, pred_test_y))
"""
0.7973306052068381
"""
四、共享单车投放量预测
- 基于随机森林模型预测共享单车投放量
- 加载并整理数据集
- 特征分析
- 打乱数据集,划分测试集、训练集
- 基于训练集数据训练随机森林回归模型
- 基于测试集数据验证模型优劣
- 输出特征重要性并分析
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import sklearn.tree as st
import sklearn.ensemble as se
import sklearn.model_selection as ms
import sklearn.metrics as sm
# 加载数据集 整理数据集
data = pd.read_csv('bike_day.csv')
data = data.drop(['instant','dteday','casual','registered'], axis=1)
data
data.pivot_table(index='yr', values=['cnt'])
"""
cnt
yr
0 3405.761644
1 5599.934426
"""
data.pivot_table(index='weekday', values=['cnt'])
"""
cnt
weekday
0 4228.828571
1 4338.123810
2 4510.663462
3 4548.538462
4 4667.259615
5 4690.288462
6 4550.542857
"""
data['windspeed'].plot.hist() # 风速
# 整理数据集 输入集与输出集、训练集与测试集
x, y = data.iloc[:,:-1], data['cnt']
train_x, test_x, train_y, test_y = \
ms.train_test_split(x, y, test_size=0.1, random_state=7)
# 训练模型
model = se.RandomForestRegressor(max_depth=10, n_estimators=1000, min_samples_split=5)
model.fit(train_x, train_y)
# 模型评估
pred_test_y = model.predict(test_x)
print(sm.r2_score(test_y, pred_test_y))
print(sm.mean_absolute_error(test_y, pred_test_y))
"""
0.8763294359170949
472.2823486488968
"""
五、回归模型总结
- 线性回归
- 岭回归
- 多项式回归
- 单棵决策树
- 集合算法
- AdaBoost
- GBDT
- RandomForest
六、回归任务总结
- 拿到一组样本数据之后,想要通过一些输入预测输出,但是得到的这些输出是一组连续数据,比如预测薪水,预测明天的股票价格,这些都是属于回归任务。