逻辑回归

逻辑回归(Logistic Regression) 虽然被称为回归,但其实际上是分类模型,常用于二分类。逻辑回归因其简单、可并行化、可解释强而受到广泛应用。二分类(也称为逻辑分类)是常见的分类方法,是将一批样本或数据划分到两个类别,例如一次考试,根据成绩可以分为及格、不及格两个类别,如下表所示:

  • 这就是逻辑分类,将连续值映射到两个类别中
姓名 成绩 分类
Jerry 86 1
Tom 98 1
Lily 58 0
…… …… ……

逻辑函数

逻辑回归是一种广义的线性回归,其原理是利用线性模型根据输入计算输出(线性模型输出值为连续),并在逻辑函数作用下,将连续值转换为两个离散值(0或1),其表达式如下:

$$y = h(w_1x_1 + w_2x_2 + w_3x_3 + ... + w_nx_n + b)$$

  • 其中,括号中的部分为线性模型,计算结果在函数\(h()\)的作用下,做二值化转换,函数\(h()\)的定义为:

$$h= \frac{1}{1+e^{-t}}$$

$$\quad t=w^Tx+b$$

该函数称为Sigmoid函数(又称逻辑函数),能将\((-\infty, +\infty)\)的值映射到\((0, 1)\)之间,其图像为:

  • 可以设定一个阈值(例如0.5),当函数的值大于阈值时,分类结果为1;当函数值小于阈值时,分类结果为0. 也可以根据实际情况调整这个阈值

sigmod.png

分类问题的损失函数

对于回归问题,可以使用均方差作为损失函数,对于分类问题,如何度量预测值与真实值之间的差异?分类问题采用交叉熵作为损失函数,当只有两个类别时,交叉熵表达式为:

  • 其中,y为真实值,\(\hat{y}\)为预测值

$$E(y, \hat{y}) = -[y \ log(\hat{y}) + (1-y)log(1-\hat{y})]$$

  • 当\(y=1\)时,预测值\(\hat{y}\)越接近于1,\(log(\hat{y})\)越接近于0,损失函数值越小,表示误差越小,预测的越准确;当预测时\(\hat{y}\)接近于0时,\(log(\hat{y})\)接近于负无穷大,加上符号后误差越大,表示越不准确
  • 当\(y=0\)时,预测值\(\hat{y}\)越接近于0,\(log(1-\hat{y})\)越接近于0,损失函数值越小,表示误差越小,预测越准确;当预测值\(\hat{y}\)接近于1时,\(log(1-\hat{y})\)接近于负无穷大,加上符号后误差越大,表示越不准确

逻辑回归实现

sklearn中,逻辑回归相关API如下:

# 创建模型
# solver参数:逻辑函数中指数的函数关系(liblinear表示线性关系)
# C参数:正则强度,越大拟合效果越小,通过调整该参数防止过拟合
model = lm.LogisticRegression(solver='liblinear', C=1)

# 训练
model.fit(x, y) 

# 预测
pred_y = model.predict(x)

以下是使用sklearn库提供的逻辑分类器(LogisticRegression)实现的代码:

# 逻辑分类器示例
import numpy as np
import sklearn.linear_model as lm
import matplotlib.pyplot as mp

x = np.array([[3, 1], [2, 5], [1, 8], [6, 4],
              [5, 2], [3, 5], [4, 7], [4, -1]])
y = np.array([0, 1, 1, 0, 0, 1, 1, 0])

# 创建逻辑分类器对象
model = lm.LogisticRegression()
model.fit(x, y)  # 训练

# 预测
test_x = np.array([[3, 9], [6, 1]])
test_y = model.predict(test_x)  # 预测
print(test_y)

# 计算显示坐标的边界
left = x[:, 0].min() - 1
right = x[:, 0].max() + 1
buttom = x[:, 1].min() - 1
top = x[:, 1].max() + 1

# 产生网格化矩阵
grid_x, grid_y = np.meshgrid(np.arange(left, right, 0.01),
                             np.arange(buttom, top, 0.01))

print("grid_x.shape:", grid_x.shape)
print("grid_y.shape:", grid_y.shape)

# 将x,y坐标合并成两列
mesh_x = np.column_stack((grid_x.ravel(), grid_y.ravel()))
print("mesh_x.shape:", mesh_x.shape)

# 根据每个点的xy坐标进行预测,并还原成二维形状
mesh_z = model.predict(mesh_x)
mesh_z = mesh_z.reshape(grid_x.shape)

mp.figure('Logistic Regression', facecolor='lightgray')
mp.title('Logistic Regression', fontsize=20)
mp.xlabel('x', fontsize=14)
mp.ylabel('y', fontsize=14)
mp.tick_params(labelsize=10)
mp.pcolormesh(grid_x, grid_y, mesh_z, cmap='gray')
mp.scatter(x[:, 0],  # 样本x坐标
           x[:, 1],  # 样本y坐标
           c=y, cmap='brg', s=80)
mp.scatter(test_x[:, 0], test_x[:, 1], c="red", marker='s', s=80)
mp.show()

logi_regression_1.png

多分类实现

逻辑回归产生两个分类结果,可以通过多个二元分类器实现多元分类(一个多元分类问题转换为多个二元分类问题). 如有以下样本数据:

特征1 特征2 特征3 实际类别
\(x_1\) \(x_2\) \(x_3\) A
\(x_1\) \(x_2\) \(x_3\) B
\(x_1\) \(x_2\) \(x_3\) C

进行以下多次分类,得到结果:

  • 第一次:分为A类(值为1)和非A类(值为0)
  • 第二次:分为B类(值为1)和非B类(值为0)
  • 第三次:分为C类(值为1)和非C类(值为0)
  • …… 以此类推

利用逻辑分类器实现多元分类示例代码如下:

# 多元分类器示例
import numpy as np
import sklearn.linear_model as lm
import matplotlib.pyplot as mp

# 输入
x = np.array([[4, 7],
              [3.5, 8],
              [3.1, 6.2],
              [0.5, 1],
              [1, 2],
              [1.2, 1.9],
              [6, 2],
              [5.7, 1.5],
              [5.4, 2.2]])
# 输出(多个类别)
y = np.array([0, 0, 0, 1, 1, 1, 2, 2, 2])

# 创建逻辑分类器对象
model = lm.LogisticRegression(C=200) # 调整该值为1看效果
model.fit(x, y)  # 训练

# 坐标轴范围
left = x[:, 0].min() - 1
right = x[:, 0].max() + 1
h = 0.005

buttom = x[:, 1].min() - 1
top = x[:, 1].max() + 1
v = 0.005

grid_x, grid_y = np.meshgrid(np.arange(left, right, h),
                             np.arange(buttom, top, v))

mesh_x = np.column_stack((grid_x.ravel(), grid_y.ravel()))
mesh_z = model.predict(mesh_x)
mesh_z = mesh_z.reshape(grid_x.shape)

# 可视化
mp.figure('Logistic Classification', facecolor='lightgray')
mp.title('Logistic Classification', fontsize=20)
mp.xlabel('x', fontsize=14)
mp.ylabel('y', fontsize=14)
mp.tick_params(labelsize=10)
mp.pcolormesh(grid_x, grid_y, mesh_z, cmap='gray')
mp.scatter(x[:, 0], x[:, 1], c=y, cmap='brg', s=80)
mp.show()

输出结果

logi_regression_2.png

鸢尾花数据测试

'''
鸢尾花类别预测
'''
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import sklearn.datasets as sd
import sklearn.model_selection as ms
import sklearn.linear_model as lm
import sklearn.metrics as sm
import sklearn.tree as st

iris = sd.load_iris()
# print(data.keys())
# print(data.DESCR)

data = pd.DataFrame(iris.data,columns=iris.feature_names)
data['target'] = iris.target

# print(data['target'].values)


#萼片可视化
data.plot.scatter(x='sepal length (cm)',y='sepal width (cm)',c='target',cmap='brg')
# print(data.columns)

#花瓣可视化
data.plot.scatter(x='petal length (cm)',y='petal width (cm)',c='target',cmap='brg')
# plt.show()

#整理输入和输出
x = data.iloc[:,:-1]
y = data.iloc[:,-1]
#划分训练集和测试集
# 当设置`stratify`为某个特征(通常是目标变量)时,`train_test_split`函数会确保训练集和测试集中的样本比例与原始数据集中该特征的比例相同。这在处理不平衡数据集时特别有用,可以确保训练集和测试集中各类别样本的比例保持一致
# 例如,如果你有一个二分类问题,目标变量为`y`,其中类别1的样本占总样本的30%,类别0的样本占70%。使用`train_test_split`函数时,你可以将`stratify=y`,这样划分后的训练集和测试集中类别1和类别0的样本比例仍然是30:70
train_x,test_x,train_y,test_y = ms.train_test_split(x,y,test_size=0.1,random_state=7,stratify=y)#类别均衡划分

model = lm.LogisticRegression(solver='liblinear')

#进行5次交叉验证
scores = ms.cross_val_score(model,x,y,cv=5,scoring='f1_weighted')
print(scores.mean())

model.fit(train_x,train_y)

pred_test_y = model.predict(test_x)



#     precision       recall     f1
# 0       1             1         1
# 1       1            3/7        0.6
# 2       3/7           1         0.6
#准确率  : 对的个数 / 总个数
print(test_y.values)
print(pred_test_y)
print((test_y == pred_test_y).sum() / test_y.size)
print('准确率:',sm.accuracy_score(test_y,pred_test_y))
print('召回率:',sm.recall_score(test_y,pred_test_y,average='macro'))
print('查准率:',sm.precision_score(test_y,pred_test_y,average='macro'))
print('f1:',sm.f1_score(test_y,pred_test_y,average='macro'))
print('混淆矩阵:\n',sm.confusion_matrix(test_y,pred_test_y))
print('分类报告:\n',sm.classification_report(test_y,pred_test_y))

逻辑回归总结

  1. 逻辑回归是分类问题,用于实现二分类问题
  2. 实现方式:利用线性模型计算,在逻辑函数作用下产生分类
  3. 多分类实现:可以将多分类问题转化为二分类问题实现
  4. 用途:广泛用于各种分类问题

决策树

决策树是一种常见的机器学习方法,其核心思想是相同(或相似)的输入产生相同(或相似)的输出,通过树状结构来进行决策,其目的是通过对样本不同属性的判断决策,将具有相同属性的样本划分到一个叶子节点下,从而实现分类或回归. 以下是几个生活中关于决策树的示例

decision_tree3.jpg

decision_tree4.png

  • 在上述示例模型中,通过对西瓜一系列特征(色泽、根蒂、敲声等)的判断,最终我们得出结论:这是否为一个好瓜. 决策过程中提出的每个判定问题都是对某个属性的“测试”,例如“色泽=?”,“根蒂=?”. 每个测试的结果可能得到最终结论,也可能需要进行下一步判断,其考虑问题的范围是在上次决策结果限定范围之内. 例如若在“色泽=青绿”之后再判断“根蒂=?”

决策树的结构

一般来说,一棵决策树包含一个根节点、若干个内部节点和若干个叶子节点. 叶子节点对应最终的决策结果,其它每个节点则对应与一个属性的测试. 最终划分到同一个叶子节点上的样本,具有相同的决策属性,可以对这些样本的值求平均值来实现回归,对这些样本进行投票(选取样本数量最多的类别)实现分类

decision_tree_structure.png

构建决策树算法

决策树的构建,就是不断选取好的特征作为决策节点,构建一颗泛化能力较强的树结构,其基本算法描述如下:

decision_tree5.png

显然,决策树的构建是一个递归的过程,核心是以下两个问题:

  • 如何选取特征. 决策树构建的每一步,应该挑选最优的特征,进行决策对数据集划分效果最好
  • 决定何时停止分裂子节点

如何选择特征

信息熵

信息熵(information entropy)是度量样本集合纯度的常用指标,该值越大,表示该集合纯度越低(或越混乱),该值越小,表示该集合纯度越高(或越有序). 信息熵定义如下:

  • 其中,\(P(x_i)\)表示集合中第i类样本所占比例,当\(P(x_i)\)为1时(只有一个类别,比例为100%), \(log_2P(x_i)\)的值为0,整个系统信息熵为0;当类别越多,则\(P(x_i)\)的值越接近于0,\(log_2P(x_i)\)趋近去负无穷大,整个系统信息熵就越大.以下代码,展示了类别数量从1...10的集合信息熵变化:

$$H = -\sum_{i=1}^{n}{P(x_i)log_2P(x_i)}$$

# 信息熵计算演示
import math
import numpy as np
import matplotlib.pyplot as plt

class_num = 100

def cal_entropy(n):
    p = 1.0 / n #概率
    total_entropy = 0.0 #总熵值

    for i in range(n):
        p_i = p * math.log2(p)
        total_entropy += p_i

    return - total_entropy

entropies = []
for i in range(1,class_num+1):
    entropy = cal_entropy(i)
    entropies.append(entropy)

plt.plot(np.arange(1,class_num+1),
         entropies,
         color='orangered')
plt.show()

输出结果

decision_tree6.png

信息增益

决策树根据属性进行判断,将具有相同属性的样本划分到相同节点下,此时,样本比划分之前更加有序(混乱程度降低),信息熵的值有所降低。用划分前的信息熵减去划分后的信息熵,就是决策树获得的信息增益。可以用以下表达式表示:

  • 其中,D表示样本集合,a表示属性,v表示属性可能的取值\({v^1, v^2,...,v^n}\), \(\frac{|D^v|}{|D|}\)表示权重,样本越多的分支对分类结果影响更大,赋予更高的权重, \(Gain(D, a)\)表示在样本集合D上使用属性a来划分子节点所获得的信息增益. 以下是一个关于信息增益计算的示例

$$Gain(D, a) = Ent(D) - \sum_{v=1}^{V} \frac{|D^v|}{|D|} Ent(D^v)$$

decision_tree10.png

  • 香蕉占2/5,所以\(P_1=0.4\);梨占1/5,所以\(P_2 = 0.2\);黄瓜占1/5,所以\(P_3 = 0.4\)
  • 根节点信息熵:\(-(0.4 * log_2 0.4 + 0.2 * log_2 0.2 + 0.4 * log_2 0.4) \approx 1.522\)
  • 根据颜色划分后:黄色分支信息熵\(-(\frac{2}{3} * log_2 \frac{2}{3} + \frac{1}{3} * log_2 \frac{1}{3}) \approx 0.918\);绿色分支信息熵\(-(1.0 * log_2 1.0) = 0\);整个第二层信息熵为\(0.6 * 0.918 + 0.4 * 0 \approx 0.55\)
  • 根据颜色划分后的信息增益:\(1.522 - 0.55 \approx 0.97\)

由以上示例可知,经过对样本按颜色进行类别划分,划分后的信息熵比原来下降了,下降的值就是信息增益。一般来说,信息增益越大,以该属性划分所获得的“纯度提升”越大. 著名的ID3决策树学习算法就是以信息增益为准则来划分属性

增益率

增益率不直接采用信息增益,而采用信息增益与熵值的比率来作为衡量特征优劣的标准. C4.5算法就是使用增益率作为标准来划分属性. 增益率定义为:

$$Gain_ratio(D, a) = \frac{Gain(D, a)}{IV(a)}$$

$$IV(a) = - \sum_{v=1}^{V} \frac{|D^v|}{|D|} log_2 \frac{|D^v|}{|D|}$$

基尼系数

基尼系数定义为:

$$Gini(p) = \sum_{k=1}^{k} p_k (1-pk) = 1 - \sum{k=1}^{k} p_k^2$$

直观来说,基尼系数反映了从数据集D中随机抽取两个样本,类别标记不一致的概率. 因此,基尼系数越小,数据集的纯度越高. CART决策树(Classification And Regression Tree)使用基尼系数来选择划分属性,选择属性时,选择划分后基尼值最小的属性作为最优属性. 采用和上式相同的符号表示,数据集D下属性a的基尼系数定义为:

$$Gini_index(D, a) = \sum_{v=1}^{V} \frac{|D^v|}{|D|} Gini(D^v)$$

如何停止分裂

以下几种情况会停止决策树子节点的构建:

  • 当前节点所有样本属于同一个类别,无需划分
  • 当前属性集为空,或者所有样本取值相同,无法划分
  • 当前节点包含的样本集合为空,不能划分
  • 当前节点样本数量少于指定数量

如何实现决策树

scikit-learn中决策树相关API:

# 模型
model = st.DecisionTreeRegressor(max_depth=4)  # 决策树回归器
# 训练
model.fit(train_x, train_y)
# 预测
pre_test_y = model.predict(test_x)

波士顿房价预测

  • 该数据集为一个开放房价数据集,包含506笔样本,每个样本包含13个特征和1个标签,具体如下所示:

boston_housing_features.png

# 决策树回归示例
# 使用决策树预测波士顿房价

import sklearn.datasets as sd
import sklearn.utils as su
import sklearn.tree as st
import sklearn.ensemble as se
import sklearn.metrics as sm


boston = sd.load_boston()  # 加载boston地区房价数据
print(boston.feature_names)
print(boston.data.shape)
print(boston.target.shape)

random_seed = 7  # 随机种子,计算随机值,相同的随机种子得到的随机值一样
x, y = su.shuffle(boston.data, boston.target, random_state = random_seed)
# 计算训练数据的数量
train_size = int(len(x) * 0.8) # 以boston.data中80%的数据作为训练数据
# 构建训练数据、测试数据
train_x = x[:train_size]  # 训练输入, x前面80%的数据
test_x = x[train_size:]   # 测试输入, x后面20%的数据
train_y = y[:train_size]  # 训练输出
test_y = y[train_size:]   # 测试输出

######## 单棵树进行预测 ########
# 模型
model = st.DecisionTreeRegressor(max_depth=4)  # 决策回归器
# model = st.DecisionTreeclassifier(max_depth=4)  # 

# 训练
model.fit(train_x, train_y)
# 预测
pre_test_y = model.predict(test_x)
# 打印预测输出和实际输出的R2值
print(sm.r2_score(test_y, pre_test_y))

输出结果

['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO'
 'B' 'LSTAT']
(506, 13)
(506,)
0.8202560889408634

特征重要性

  • 作为决策树模型训练过程中的副产品,根据每个特征划分子表前后信息熵减少量就标志了该特征的重要程度,此即为该特征重要性的指标。训练后得到的模型对象提供了属性feature_importances_来存储每个特征的重要性。在工程应用上,可以对决策树做一些优化,不必让每一个特征都参与子表划分,而只选择其中较重要的(或者说影响因素较大的)的特征作为子表划分依据。特征重要性的评价指标,就是根据该特征划分子表后所带来的信息熵减少量,熵减越大的就越重要,也就越优先参与子表的划分。

在上述示例中加入如下代码:

import matplotlib.pyplot as mp
import numpy as np
fi = model.feature_importances_  # 获取特征重要性
print("fi:", fi)

# 特征重要性可视化
mp.figure("Feature importances", facecolor="lightgray")
mp.plot()
mp.title("DT Feature", fontsize=16)
mp.ylabel("Feature importances", fontsize=14)
mp.grid(linestyle=":", axis=1)
x = np.arange(fi.size)
sorted_idx = fi.argsort()[::-1]  # 重要性排序(倒序)
fi = fi[sorted_idx]  # 根据排序索引重新排特征值
mp.xticks(x, boston.feature_names[sorted_idx])
mp.bar(x, fi, 0.4, color="dodgerblue", label="DT Feature importances")

mp.legend()
mp.tight_layout()
mp.show()

输出结果

decision_tree8.png

决策树的剪枝

剪枝(pruning)是决策树学习算法对付“过拟合”的主要手段. 在决策树学习中,为了尽可能正确分类训练样本,节点划分过程将不断重复,有时会造成决策树分支过多,这时就可能因训练样本学的“太好了”,以至于把训练集本身的一些特点当做数据所具有的一般性质而导致过拟合. 因此,可通过主动去掉一些分支来降低过拟合风险.

  • 预剪枝. 决策树生成过程中,对每个节点在划分前进行评估,若当前节点不能带来决策树泛化性能的提升,则停止划分并将当前节点标记为叶子节点
  • 后剪枝. 先训练为一颗完整的决策树,然后自低向上对非叶子节点进行考察,若将该节点对应的子树替换为叶节点能带来决策树泛化能力提升,则将该子树替换为叶节点

集成学习与随机森林

集成学习

集成学习(ensemble learning)通过构建并合并多个模型来完成学习任务,从而获得比单一学习模型更显著优越的泛化性能,简言之,集成学习就是利用模型的“集体智慧”,提升预测的准确率. 根据单个模型方式,集成学习可以分为两大类:

  • 个体间存在强依赖关系,必须串行生成的序列化方法,其代表为Boosting算法
  • 个体之间不存在强依赖关系,可同时生成的并行化方法,代表是Bagging和随机森林算法

Boosting

Boosting(直译为推进、提升)是一族可以将弱学习器提升为强学习器的算法,其工作原理是:

  • 先训练出一个初始模型
  • 根据模型的表现进行调整,使得模型预测错误的数据获得更多的关注,再重新训练下一个模型
  • 不断重复第二步,直到模型数量达到预先设定的数目T,最终将这T个模型加权结合

AdaBoosting是Boosting算法族中最著名的算法,它根据每次训练集之中每个样本的分类是否正确,以及上次的总体分类的准确率,来确定每个样本的权值。将修改过权值的新数据集送给下层分类器进行训练,最后将每次训练得到的分类器最后融合起来,作为最后的决策分类器。

实现Boosting

sklearn中,AdaBoosting相关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)

AdaBoosting示例代码:

# AdaBoosting示例
# 使用AdaBoosting预测波士顿房价
import sklearn.datasets as sd
import sklearn.utils as su
import sklearn.tree as st
import sklearn.ensemble as se
import sklearn.metrics as sm

boston = sd.load_boston()  # 加载boston地区房价数据
print(boston.feature_names)
print(boston.data.shape)
print(boston.target.shape)

random_seed = 7  # 随机种子,计算随机值,相同的随机种子得到的随机值一样
x, y = su.shuffle(boston.data, boston.target, random_state = random_seed)
# 计算训练数据的数量
train_size = int(len(x) * 0.8) # 以boston.data中80%的数据作为训练数据
# 构建训练数据、测试数据
train_x = x[:train_size]  # 训练输入, x前面80%的数据
test_x = x[train_size:]   # 测试输入, x后面20%的数据
train_y = y[:train_size]  # 训练输出
test_y = y[train_size:]   # 测试输出

model2 = se.AdaBoostRegressor(st.DecisionTreeRegressor(max_depth=4),
                              n_estimators=400,   # 决策树数量
                              random_state=random_seed) # 随机种子
# 训练
model2.fit(train_x, train_y)
# 预测
pre_test_y2 = model2.predict(test_x)
# 打印预测输出和实际输出的R2值
print(sm.r2_score(test_y, pre_test_y2))

输出结果

# 可以看到,通过AdaBoosting算法,回归模型获得了更高的R2值
['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO'
 'B' 'LSTAT']
(506, 13)
(506,)
0.9068598725149652

随机森林

随机森林(Random Forest,简称RF)是专门为决策树设计的一种集成方法,是Bagging法的一种拓展,它是指每次构建决策树模型时,不仅随机选择部分样本,而且还随机选择部分特征来构建多棵决策树. 这样不仅规避了强势样本对预测结果的影响,而且也削弱了强势特征的影响,使模型具有更强的泛化能力.随机森林简单、容易实现、计算开销小,在很多现实任务中展现出强大的性能,被誉为“代表集成学习技术水平的方法”

实现随机森林

sklearn中,随机森林相关API:

import sklearn.ensemble as se

model = se.RandomForestRegressor(
    max_depth, # 决策树最大深度
    n_estimators, # 决策树数量
    min_samples_split)# 子表中最小样本数 若小于这个数字,则不再继续向下拆分

以下是利用随机森林实现波士顿房价预测的代码:

# 使用随机森林预测波士顿房价
import sklearn.datasets as sd
import sklearn.utils as su
import sklearn.tree as st
import sklearn.ensemble as se
import sklearn.metrics as sm

boston = sd.load_boston()  # 加载boston地区房价数据
print(boston.feature_names)
print(boston.data.shape)
print(boston.target.shape)

random_seed = 7  # 随机种子,计算随机值,相同的随机种子得到的随机值一样
x, y = su.shuffle(boston.data, boston.target, random_state=random_seed)
# 计算训练数据的数量
train_size = int(len(x) * 0.8)  # 以boston.data中80%的数据作为训练数据
# 构建训练数据、测试数据
train_x = x[:train_size]  # 训练输入, x前面80%的数据
test_x = x[train_size:]  # 测试输入, x后面20%的数据
train_y = y[:train_size]  # 训练输出
test_y = y[train_size:]  # 测试输出

# 创建随机森林回归器,并进行训练
model = se.RandomForestRegressor(max_depth=10,  # 最大深度
                                 n_estimators=1000,  # 树数量
                                 min_samples_split=2)  # 最小样本数量,小于该数就不再划分子节点
model.fit(train_x, train_y)  # 训练

# 基于天统计数据的特征重要性
fi_dy = model.feature_importances_
# print(fi_dy)
pre_test_y = model.predict(test_x)
print(sm.r2_score(test_y, pre_test_y))  # 打印r2得分

输出结果

['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO'
 'B' 'LSTAT']
(506, 13)
(506,)
0.9271955403309159

实验代码

'''
小汽车级别分类预测
'''

import pandas as pd
import sklearn.preprocessing as sp
import sklearn.ensemble as se

data = pd.read_csv('car.txt',header=None)
# print(data)

encoders = {} # 需要保存每次处理标签编码的对象,在最后还原数据的时候使用
for i in range(len(data.columns)):
    encoder = sp.LabelEncoder()  # 定义标签编码对象
    encoders[i] = encoder
    data[i] = encoder.fit_transform(data[i]) # 执行标签编码
# print(data)
x = data.iloc[:,:-1]
y = data.iloc[:,-1]

model = se.RandomForestClassifier(max_depth=10,n_estimators=150,random_state=7)

#验证曲线,寻找最优模型参数
# params = np.arange(100,151,10)
# train_scores,test_scores = ms.validation_curve(model,x,y,'n_estimators',params,cv=5)
# print(test_scores.mean(axis=1))

#max_depth
# params = np.arange(10,15)
# train_scores,test_scores = ms.validation_curve(model,x,y,'max_depth',params,cv=5)
# print(test_scores.mean(axis=1))

model.fit(x,y)

test_data = [['high', 'med', '5more', '4', 'big', 'low', 'unacc'],
             ['high', 'high', '4', '4', 'med', 'med', 'acc'],
             ['low','low','2','4','small','high','good'],
             ['low','med','3','4','med','high','vgood']]
test_data = pd.DataFrame(test_data)

for i in range(len(test_data.columns)):
    # 这里只是用transform而不使用fit_transform只进行转换而不再进行训练,确保训练数据和测试数据使用相同的编码规则,保证一致性和正确性
    test_data[i] = encoders[i].transform(test_data[i]) # 执行标签编码

# print(test_data)

test_x = test_data.iloc[:,:-1]
test_y = test_data.iloc[:,-1]

pred_test_y = model.predict(test_x)
print(pred_test_y)
print(encoders[6].inverse_transform(test_y))
print(encoders[6].inverse_transform(pred_test_y))

决策树总结

  1. 什么是决策树:利用样本特征进行决策归类,将具有相同属性的样本划入一个子节点
  2. 决策树的用途:用作分类器、回归器
  3. 如何构建决策树:根据信息增益、增益率、基尼系数构建
  4. 什么情况下使用决策树:实用性较广,课用于一般回归、分类问题
  5. 决策树优化:集成学习、随机森林

模型评估与优化

查准率,召回率与F1得分

错误率和精度虽然常用,但并不能满足所有的任务需求。例如,在一次疾病检测中,我们更关注以下两个问题:

  • 检测出感染的个体中有多少是真正病毒携带者?
  • 所有真正病毒携带者中,有多大比例被检测了出来?

类似的问题在很多分类场景下都会出现,“查准率”(precision)与“召回率”(recall)是更为适合的度量标准。对于二分类问题,可以将真实类别、预测类别组合为“真正例”(true positive)、“假正例”(false positive)、“真反例”(true negative)、“假反例”(false negative)四种情形,见下表:

confusion_matrix.png

  • 样例总数:TP + FP + TN + FN
  • 查准率: Precision = TP / (TP + FP),表示分的准不准,其中,TP(True Positive)表示预测为正类且实际为正类的样本数量,FP(False Positive)表示预测为正类但实际为负类的样本数量。
  • 召回率:Recall = TP / (TP + FN),表示分的全不全,其中,TP(True Positive)表示预测为正类且实际为正类的样本数量,FN(False Negative)表示预测为负类但实际为正类的样本数量。
  • F1得分:

$$f1 = \frac{2 * 查准率 * 召回率}{查准率 + 召回率}$$

  • 查准率和召回率是一对矛盾的度量。一般来说,查准率高时,召回率往往偏低;召回率高时,查准率往往偏低。例如,在病毒感染者检测中,若要提高查准率,只需要采取更严格的标准即可,这样会导致漏掉部分感染者,召回率就变低了;反之,放松检测标准,更多的人被检测为感染,召回率升高了,查准率又降低了. 通常只有在一些简单任务中,才能同时获得较高查准率和召回率
  • 查准率和召回率在不同应用中重要性也不同。例如,在商品推荐中,为了尽可能少打扰客户,更希望推荐的内容是用户感兴趣的,此时查准率更重要;而在逃犯信息检索系统中,希望让更少的逃犯漏网,此时召回率更重要
  • 查准率和召回率通常存在一种权衡关系:当我们希望减少误判负类的情况时,可以提高查准率;当我们希望尽可能多地捕捉到正类样本时,可以提高召回率。
  • 除了查准率和召回率,还有其他与之相关的指标,如F1值(综合考虑了查准率和召回率)、准确率(Accuracy)等,它们都在不同场景中用于评估分类模型的性能。

混淆矩阵

混淆矩阵也称误差矩阵,是表示精度评价的一种标准格式,用n行n列的矩阵形式来表示。每一行(数量之和)表示一个真实类别的样本,每一列(数量之和)表示一个预测类别的样本

  • 以下是一个预测结果准确的混淆矩阵:
  • 上述表格表示的含义为:A类别实际有5个样本,B类别实际有6个样本,C类别实际有7个样本;预测结果中,预测结果为A类别的为5个,预测结果为B类别的为6个,预测结果为C类别的为7个
A类别 B类别 C类别
A类别 5 0 0
B类别 0 6 0
C类别 0 0 7
  • 以下是一个预测结果不准确的混淆矩阵:
  • 上述表格表示的含义为:A类别实际有5个样本,B类别实际有6个样本,C类别实际有7个样本;预测结果中,A类别有3个样本预测准确,另外各有1个被预测成了B和C;B类别有4个预测准确,另外2个被预测成了C类别;C类别7个全部预测准确,但有1个本属于A类别、2个本属于B类别的被预测成了C类别。
A类别 B类别 C类别
A类别 3 1 1
B类别 0 4 2
C类别 0 0 7

根据混淆矩阵,查准率、召回率也可表示为:

  • 查准率 = 主对角线上的值 / 该值所在列的和
  • 召回率 = 主对角线上的值 / 该值所在行的和

利用sklearn提供的朴素贝叶斯分类器分类,并打印查准率、召回率、R2得分和混淆矩阵:

# 混淆矩阵示例
import numpy as np
import sklearn.model_selection as ms
import sklearn.metrics as sm
import sklearn.naive_bayes as nb

# 输入,输出
x, y = [], []

# 读取数据文件
with open("../data/multiple1.txt", "r") as f:
    for line in f.readlines():
        data = [float(substr) for substr in line.split(",")]
        x.append(data[:-1])  # 输入样本:取从第一列到导数第二列
        y.append(data[-1])  # 输出样本:取最后一列

# 样本转数组
x = np.array(x)
y = np.array(y, dtype=int)

# 划分训练集和测试集
train_x, test_x, train_y, test_y = ms.train_test_split(
    x, y, test_size=0.25, random_state=7)

# 创建高斯朴素贝叶斯分类器对象
model = nb.GaussianNB()
model.fit(train_x, train_y)  # 使用划分的训练集来训练模型
pred_test_y = model.predict(test_x)  # 预测

print("recall:", sm.recall_score(test_y,  # 真实值
                                 pred_test_y,  # 预测值
                                 average="macro"))  # 计算平均值,不考虑样本权重
print("precision:", sm.precision_score(test_y,  # 真实值
                                       pred_test_y,  # 预测值
                                       average="macro"))  # 计算平均值,不考虑样本权重
print("F1:", sm.f1_score(test_y, pred_test_y,average="macro"))

# 计算并打印模型预测的混淆矩阵
print("\n Confusion Matrix:")
cm = sm.confusion_matrix(test_y, pred_test_y)
print(cm)

输出结果

recall: 0.9910714285714286
precision: 0.9903846153846154
F1: 0.9905525846702318

 Confusion Matrix:
[[22  0  0  0]
 [ 0 27  1  0]
 [ 0  0 25  0]
 [ 0  0  0 25]]

分类报告

sm.classification_report(test_y,pred_test_y)

训练集与测试集

通常情况下,评估一个模型性能的好坏,将样本数据划分为两部分,一部分专门用于模型训练,这部分称为“训练集”,一部分用于对模型进行测试,这部分被称为“测试集”,训练集和测试集一般不存在重叠部分. 常用的训练集、测试集比例有:9:1, 8:2, 7:3等. 训练集和测试的划分,尽量保持均衡、随机,不能集中于某个或少量类别.

  • 有些公共数据集在创建时,已经进行了划分. 有时候,我们需要自己对数据集进行划分,划分的方式是先打乱数据集,然后使用一种计算方法,将一部分数据划入训练集,一部分数据划入测试集.

train_test_dataset.png

交叉验证法

在样本数量较少的情况下,如果将样本划分为训练集、测试集,可能导致单个集合样本数量更少,可以采取交叉验证法来训练和测试模型.

  • "交叉验证法"(cross validation)先将数据集D划分为k个大小相同(或相似)的、互不相交的子集,每个子集称为一个"折叠"(fold),每次训练,轮流使用其中的一个作为测试集、其它作为训练集. 这样,就相当于获得了k组训练集、测试集,最终的预测结果为k个测试结果的平均值.

cross_validation.png

sklearn中,提供了cross_val_score函数来实现交叉验证并返回评估指标值:

import sklearn.model_selection as ms

n = ms.cross_val_score(model, #模型
                       train_x, train_y,# 样本输入、输出
                       cv,  # 折叠数量
                       scoring) # 指定返回的指标

以下是关于朴素贝叶斯模型的交叉验证实现:

# 交叉验证示例
import numpy as np
import sklearn.model_selection as ms
import sklearn.naive_bayes as nb
import matplotlib.pyplot as mp

x, y = [], []  # 输入,输出

# 读取数据文件
with open("../data/multiple1.txt", "r") as f:
    for line in f.readlines():
        data = [float(substr) for substr in line.split(",")]
        x.append(data[:-1])  # 输入样本:取从第一列到导数第二列
        y.append(data[-1])  # 输出样本:取最后一列

train_x = np.array(x)
train_y = np.array(y, dtype=int)

# 划分训练集和测试集
#train_x, test_x, train_y, test_y = ms.train_test_split(
#    x, y, test_size=0.25, random_state=7)

# 创建高斯朴素贝叶斯分类器对象
model = nb.GaussianNB()
# 先做交叉验证,如果得分结果可以接受,再执行训练和预测
pws = ms.cross_val_score(model, x, y,
                         cv=5,  # 折叠数量
                         scoring='precision_weighted')  # 查准率
print("precision:", pws.mean())

rws = ms.cross_val_score(model, x, y, cv=5,
                         scoring='recall_weighted')  # 召回率
print("recall:", rws.mean())

f1s = ms.cross_val_score(model, x, y, cv=5,
                         scoring='f1_weighted')  # F1得分
print("f1:", f1s.mean())

acc = ms.cross_val_score(model, x, y,
                         cv=5, scoring='accuracy')  # 准确率
print("acc:", acc.mean())

输出结果

precision: 0.996822033898305
recall: 0.9966101694915255
f1: 0.9966063988235516
acc: 0.9966101694915255

模型优化

验证曲线与学习曲线

验证曲线

验证曲线是指根据不同的评估系数,来评估模型的优劣. 例如,构建随机森林,树的数量不同,模型预测准确度有何不同?以下是一个验证曲线的示例:

# 验证曲线示例
import numpy as np
import sklearn.preprocessing as sp
import sklearn.ensemble as se
import sklearn.model_selection as ms
import matplotlib.pyplot as mp

data = []
with open("../data/car.txt", "r") as f:
    for line in f.readlines():
        data.append(line.replace("\n", "").split(","))

data = np.array(data).T  # 转置
encoders, train_x = [], []

# 对样本数据进行标签编码
for row in range(len(data)):
    encoder = sp.LabelEncoder()  # 创建标签编码器
    encoders.append(encoder)
    if row < len(data) - 1:  # 不是最后一行,为样本特征
        lbl_code = encoder.fit_transform(data[row])  # 编码
        train_x.append(lbl_code)
    else:  # 最后一行,为样本输出
        train_y = encoder.fit_transform(data[row])

train_x = np.array(train_x).T  # 转置回来,变为编码后的矩阵
# print(train_x)

model = se.RandomForestClassifier(max_depth=8,  # 最大树高
                                  random_state=7)  # 随机种子
# 调用validation_curve,返回训练集、测试集得分矩阵
n_estimators = np.arange(50, 550, 50)  # 超参数值表
print("n_estimators.shape:", n_estimators.shape)
print("n_estimators:", n_estimators)

# 通过不同参数,构建多棵决策树,验证其准确性
train_scores1, test_scores1 = ms.validation_curve(model,  # 模型
                                                  train_x, train_y,
                                                  'n_estimators',  # 模型参数名称
                                                  n_estimators,  # 模型参数值
                                                  cv=5)
train_mean = train_scores1.mean(axis=1)
print("train_mean:", train_mean)
test_mean = test_scores1.mean(axis=1)
print("test_mean:", test_mean)

# 可视化
mp.figure('n_estimators', facecolor='lightgray')
mp.title('n_estimators', fontsize=20)
mp.xlabel('n_estimators', fontsize=14)
mp.ylabel('F1 Score', fontsize=14)
mp.tick_params(labelsize=10)
mp.grid(linestyle=':')
mp.plot(n_estimators, test_mean, 'o-', c='blue', label='Testing')
mp.legend()
mp.show()

validation_curve.png

学习曲线

学习曲线是用来评估不同大小的训练集下模型的优劣程度,如果预测结果随着训练集样本的增加而变化不大,那么增加样本数量不会对模型产生明显优化作用. 以下是一个学习曲线的示例:

# 学习曲线示例
import numpy as np
import sklearn.preprocessing as sp
import sklearn.ensemble as se
import sklearn.model_selection as ms
import matplotlib.pyplot as mp

data = []
with open("../data/car.txt", "r") as f:
    for line in f.readlines():
        data.append(line.replace("\n", "").split(","))

data = np.array(data).T  # 转置
encoders, train_x = [], []

# 对样本数据进行标签编码
for row in range(len(data)):
    encoder = sp.LabelEncoder()  # 创建标签编码器
    encoders.append(encoder)
    if row < len(data) - 1:  # 不是最后一行,为样本特征
        lbl_code = encoder.fit_transform(data[row])  # 编码
        train_x.append(lbl_code)
    else:  # 最后一行,为样本输出
        train_y = encoder.fit_transform(data[row])

train_x = np.array(train_x).T  # 转置回来,变为编码后的矩阵
print(train_x)

# 获得学习曲线
model = se.RandomForestClassifier(max_depth=9,  # 最大树高
                                  n_estimators=200, # 评估系数
                                  random_state=7)  # 随机种子

train_sizes = np.linspace(0.1, 1, 10)
train_sizes, train_scores, test_scores = ms.learning_curve(
                                                        model,
                                                        train_x, train_y,
                                                        train_sizes=train_sizes,
                                                        cv=5)#交叉验证折叠数量
train_means = train_scores.mean(axis=1)
test_means = test_scores.mean(axis=1)
for size, score in zip(train_sizes, train_means):
    print(size, '->', score)

# 可视化
mp.figure('Learning Curve', facecolor='lightgray')
mp.title('Learning Curve', fontsize=20)
mp.xlabel('Train Size', fontsize=14)
mp.ylabel('F1 Score', fontsize=14)
mp.tick_params(labelsize=10)
mp.grid(linestyle=':')
mp.plot(train_sizes, train_means, 'o-', c='dodgerblue', label='Training')
mp.plot(train_sizes, test_means, 'o-', c='orangered', label='Testing')
mp.legend()
mp.show()

输出结果

learn_curve.png

超参数优化

什么是超参数

超参数是在开始学习过程之前设置值的参数,而不是通过训练得到的参数数据。超参数的设置主要依赖于经验、实验或经过比较的优选值。以下是一些模型中常见的超参数:

  • 决策树模型树的最大深度
  • 随机森林模型树的数量
  • 交叉验证中折叠的额数量
  • 训练集/测试集的比例等等
  • 超参数选择主要有随机搜索、网格搜索等方法

网格搜索

网格搜索指将主要参数以及这些参数的主要取值,通过穷举法产生不同组合,计算并比较预测结果,来寻找这些参数的最优组合。以下是利用网格搜索法,寻找SVM的最优超参数的示例:

# 网格搜索示例
import numpy as np
import sklearn.model_selection as ms
import sklearn.svm as svm
import sklearn.metrics as sm
import matplotlib.pyplot as mp

x, y = [], []
with open("../data/multiple2.txt", "r") as f:
    for line in f.readlines():
        data = [float(substr) for substr in line.split(",")]
        x.append(data[:-1])  # 输入
        y.append(data[-1])  # 输出

x = np.array(x)
y = np.array(y, dtype=int)

# 通过网格搜索确定最优参数组合
# 定义参数字典
params = [
    {"kernel": ["linear"],
     "C": [1, 10, 100, 1000]
     },
    {"kernel": ["poly"],
     "C": [1],
     "degree": [2, 3]
     },
    {"kernel": ["rbf"],
     "C": [1, 10, 100, 1000],
     "gamma": [1, 0.1, 0.01, 0.001]
     }
]

model = ms.GridSearchCV(svm.SVC(), params, cv=5)  # 创建网格搜索对象
model.fit(x, y)  # 训练

print("best_score_:", model.best_score_)
print("best_params_:\n", model.best_params_)
#print("best_estimator_:\n", model.best_estimator_)

l, r, h = x[:, 0].min() - 1, x[:, 0].max() + 1, 0.005
b, t, v = x[:, 1].min() - 1, x[:, 1].max() + 1, 0.005
grid_x = np.meshgrid(np.arange(l, r, h), np.arange(b, t, v))
flat_x = np.c_[grid_x[0].ravel(), grid_x[1].ravel()]
flat_y = model.predict(flat_x)
grid_y = flat_y.reshape(grid_x[0].shape)

mp.figure("SVM RBF Classifier", facecolor="lightgray")
mp.title("SVM RBF Classifier", fontsize=14)
mp.xlabel("x", fontsize=14)
mp.ylabel("y", fontsize=14)
mp.tick_params(labelsize=10)
mp.pcolormesh(grid_x[0], grid_x[1], grid_y, cmap="gray")

C0, C1 = (y == 0), (y == 1)
mp.scatter(x[C0][:, 0], x[C0][:, 1], c="orangered", s=80)
mp.scatter(x[C1][:, 0], x[C1][:, 1], c="limegreen", s=80)

mp.show()

输出结果

best_score_: 0.95
best_params_:
 {'C': 1, 'gamma': 1, 'kernel': 'rbf'}

执行结果可视化:

grid_search.png

随机搜索

随机搜索的思想与网格搜索比较相似,只是不再测试上界和下界之间的所有值,而是在搜索范围中随机选取样本点。它的理论依据是,如果样本点集足够大,那么通过随机采样也能大概率地找到全局最优值,或其近似值。随机搜索一般会比网格搜索要快一些,但是和网格搜索的快速版一样,它的结果也是没法保证的