学习心得

(1)本文围绕datawhale萌弟大佬教程,学习各种分类模型的原理,如逻辑回归、基于概率的分类模型(分为线性判别分析和朴素贝叶斯)、决策树和SVM等算法。首先逻辑回归是在线性回归基础上加多logistic函数后将结果转为概率值; 为了解决多分类问题,我们又提出了线性判别分析和朴素贝叶斯,其中前者可以从贝叶斯或者降维分类的角度理解;

(2)学习sklearn构建分类算法的简易过程:收集数据集并选择合适特征、选择度量模型性能的指标(准确率ACC、精度PRE、召回率REC、F1值、ROC曲线等)、选择具体的模型训练、评估模型的性能并调参。

(3)结合上个task3的的网格搜索和学绘制混淆矩阵并进行分析。

文章目录

第一部分:使用sklearn构建完整的分类项目

步骤1:收集数据集并选择合适的特征:

在数据集上我们使用我们比较熟悉的IRIS鸢尾花数据集。

from sklearn import datasets
iris = datasets.load_iris()
X = iris.data
y = iris.target
feature = iris.feature_names
data = pd.DataFrame(X,columns=feature)
data['target'] = y
data.head()



sepal length (cm)

sepal width (cm)

petal length (cm)

petal width (cm)

target

0

5.1

3.5

1.4

0.2

0

1

4.9

3.0

1.4

0.2

0

2

4.7

3.2

1.3

0.2

0

3

4.6

3.1

1.5

0.2

0

4

5.0

3.6

1.4

0.2

0

各个特征的相关解释:

  • sepal length (cm):花萼长度(厘米)
  • sepal width (cm):花萼宽度(厘米)
  • petal length (cm):花瓣长度(厘米)
  • petal width (cm):花瓣宽度(厘米)

步骤2:选择度量模型性能的指标:

度量分类模型的指标和回归的指标有很大的差异:

(1)首先是因为分类问题本身的因变量是离散变量,因此像定义回归的指标那样,单单衡量预测值和因变量的相似度可能行不通。

(2)其次,在分类任务中,我们对于每个类别犯错的代价不尽相同,例如:我们将癌症患者错误预测为无癌症和无癌症患者错误预测为癌症患者,在医院和个人的代价都是不同的,前者会使得患者无法得到及时的救治而耽搁了最佳治疗时间甚至付出生命的代价,而后者只需要在后续的治疗过程中继续取证就好了,因此我们很不希望出现前者,当我们发生了前者这样的错误的时候会认为建立的模型是很差的。为了解决这些问题,我们必须将各种情况分开讨论,然后给出评价指标。

  • 真阳性TP:预测值和真实值都为正例;
  • 真阴性TN:预测值与真实值都为正例;
  • 假阳性FP:预测值为正,实际值为负;
  • 假阴性FN:预测值为负,实际值为正;

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习

分类模型的指标:

  • 准确率:分类正确的样本数占总样本的比例,即:【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_02.
  • 精度:预测为正且分类正确的样本占预测值为正的比例,即:【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_03.
  • 召回率:预测为正且分类正确的样本占类别为正的比例,即:【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_04.
  • F1值:综合衡量精度和召回率,即:【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_05.
  • ROC曲线:以假阳率为横轴,真阳率为纵轴画出来的曲线,曲线下方面积越大越好。
    ​​​https://scikit-learn.org/stable/modules/model_evaluation.html#classification-metrics​

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_06

在本次小案例中,我们使用ROC曲线作为最终评价指标。

步骤3:选择具体的模型并进行训练

一、逻辑回归logistic regression:

说到分类问题与回归问题的区别,在于回归问题与分类问题需要预测的因变量不一样。在回归问题中,因变量是连续性变量,我们需要预测【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_07是一个连续的实数,但是在分类问题中,我们往往是通过已知X的信息预测Y的类别,往往是一个离散集合中的某个元素。如:是否患癌症,图片是猫还是狗等。

一个很自然的想法是能否用线性回归去处理分类问题,答案是可以但不好!先来看看线性回归处理分类问题会出现什么弊端,我们仔细来看这个线性回归的例子,【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_08,只要输入Balance 和 Income 以及default的数据就能用最小二乘法估计出【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_09,设定预测的default>0.5就是违约反之不违约,感觉很完美的样子,但事实真的是这样吗?假设我们需要用某个人的债务(Balance)和收入(Income)去预测是否会信用卡违约(default):

  • 我们假设有一个穷人Lisa,他的Balance和Income都很小,那么有可能会导致default的值为负数,那么这个负数代表什么意义呢?显然是没有任何意义的。
  • 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_10

  • 当我们的分类变量是多类的时候,以0.5为界限划分分类就不可用了,那么我们应该怎么找到一个界限衡量多分类呢?

基于以上问题,现在大家是否还觉得线性回归模型作为一个分类模型是否足够优秀呢?其实,为了解决以上的问题(1)我们来想想能不能将线性回归的结果default转化为区间[0:1]上,让default转变成一个违约的概率呢?下面我们来解决这个问题吧。
在推导逻辑回归之前,我们先来认识下一组函数,这组函数可以将是实数轴上的数转换为[0:1]区间上的概率

首先,我们假设我们的线性回归模型为 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_11,那么这个函数是如何将线性回归的结果转化为概率呢?这个函数就是logistic 函数,具体的形式为 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_12他的函数图像如下图:(左边是线性回归,右边是逻辑函数)

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_13

因此,我们假设逻辑回归模型为:【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_14 .
下面我们来具体推导下逻辑回归模型:
假设数据Data【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_15【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_16。因为y只可能取0或者1,因此假设数据服从0-1分布,也叫伯努力分布,即:
当y=1时,【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_17,当y=0时,【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_18,可以写成【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_19,可以带入y=0和y=1进去验证,结果和前面的结论一模一样。
我们使用极大似然估计MLE,即:【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_20

因此,【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_21由于这里涉及的函数不像线性回归一样能简单求出解析解,因此我们使用迭代的优化算法:梯度下降法,即:
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_22

#  逻辑回归
'''
penalty {‘l1’, ‘l2’, ‘elasticnet’, ‘none’}, default=’l2’正则化方式
dual bool, default=False 是否使用对偶形式,当n_samples> n_features时,默认dual = False。
C float, default=1.0
solver {‘newton-cg’, ‘lbfgs’, ‘liblinear’, ‘sag’, ‘saga’}, default=’lbfgs’
l1_ratio float, default=None
'''
from sklearn.linear_model import LogisticRegression
log_iris = LogisticRegression()
log_iris.fit(X,y)
log_iris.score(X,y)
# 0.9733333333333334

如果想了解关于梯度下降法等无约束算法的具体细节,可以参照萌弟大佬写的另外两篇知乎博客:
最优化理论之无约束优化基本结构及其python应用:https://zhuanlan.zhihu.com/p/163405865
最优化理论之负梯度方法与Newton型方法:https://zhuanlan.zhihu.com/p/165914126

对于问题(2),我们值得注意的是,逻辑回归在实际中不太用于多分类问题,因为实际效果不是很好,所以我们可以借助其他模型来解决这个问题,那让我们来解决这个遗留下来的问题吧。

二、基于概率的分类模型:

(1) 线性判别分析:

线性判别分析是一个比较久远的算法,下面将会从两个方向去描述这个算法,分别是基于贝叶斯公式和降维分类的思想。

角度一:基于贝叶斯公式:

在讨论如何解决多分类问题之前,我们先来说说贝叶斯的那些事吧。在概率统计的领域里有一条神奇的公式叫贝叶斯定理,具体的形式是:【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_23 我们先不要被公式的符号吓到,我们先来看看符号具体代表什么意思。我们假设观测有【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_24类,【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_25为随机选择的观测来自第【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_26类的 先验概率,也就是样本里面第【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_26类的样本个数除以总样本的个数:【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_28 再来 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_29,表示第【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_26类观测的X的密度函数,说的直白一点就是在【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_31的样本里【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_32的样本个数,即【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_33 最后,【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_34也就是样本中【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_32的概率。
在讨论贝叶斯定理后,我们回到分类问题,这个定理跟我们的分类问题有什么关联呢?没错,这个公式【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_23给出了给定样本条件下,【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_31这个类别下的概率,这给分类问题提供了一条思路,那就是计算这个【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_38 而且我们的逻辑回归就是这么干的,但是在【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_23这个公式中,分母【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_40当样本给定的时候是一个与分类【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_26无关的常数,所以我们的问题可以简化为只需要计算分子【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_42,进而比较哪个类别的概率最大就知道属于哪个类别了,因此我们的分类思路就出来啦,这个思路不同于逻辑回归,逻辑回归需要计算具体的【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_38概率值,而我们现在的思路是通过贝叶斯定理计算贝叶斯定理的分子,比较分子最大的那个类别为最终类别。

在我们推导复杂算法之前,我们先推导下简单的当自变量个数只有一个的模型,即【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_44的简单模型。我们记【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_23 的分子为【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_46。在这里,我们做个模型假设:假设【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_47服从正态分布,即【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_48,而且每个【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_49,同方差假设。因此【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_50,最终我们的【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_51,终于算出来啦。这个式子不是很好计算,我们对【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_52取个对数,令【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_53,到这里我们的模型建立模型,我们只需要把位置的【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_54【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_55估计出来就好了。【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_56,也就是当【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_57这一类中【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_58的平均值;【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_59,说白了就是计算每一类的方差,再求平均值。总结下上面的公式就是:

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_60

至此,我们的模型就建立完成了,我们只需要代入数据求出【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_61,哪个【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_26对应的【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_61大,就是哪一类。

(下图虚线是线性判别分析的决策边界,正态曲线哪边高样本就是哪一类)

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_64

我们推到出了一个自变量的简单模型,就要泛化为多个自变量的线性判别分析了,即【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_65。其实原理一样的,只是将一元正态分布扩展为多元正态分布:

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_66 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_67 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_68

角度二:基于降维分类的思想:

基于数据进行分类时,一个很自然的想法是:将高维的数据降维至一维,然后使用某个阈值将各个类别分开。下面用图的形式展示:

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_69

图中,数据的维度是二维的,我们的想法是把数据降维至一维,然后用阈值就能分类。这个似乎是一个很好的想法,我们总是希望降维后的数据同一个类别自身内部方差小,不同类别之间的方差要尽可能大。这也是合理的,因为同一个类别的数据应该更加相似,因此方差小;不同类别的数据之间应该很不相似,这样才能更容易对数据进行分类,我们简称为:类内方差小,类间方差大,在计算机语言叫“松耦合,高内聚”。在做具体的推导之前,我们对数据的形式和一些基本统计量做一些描述:

特征【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_70,因变量【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_71,类别c1的特征【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_72,同理,类别c2的特征【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_73,属于c1类别的数据个数为【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_74,属于类别c2的数据个数为【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_75,其中,【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_76
特征X投影在w方向至一维:【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_77
全样本投影的均值【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_78
全样本投影的协方差【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_79
c1样本投影的均值【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_80
c1样本投影的协方差【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_81
c2样本投影的均值 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_82
c2样本投影的协方差【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_83
类间差距:【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_84
类内方差:【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_85
由于线性判别分析的目标是同一类别内方差小,不同类别之间距离大,因此损失函数定义为:
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_86

记:【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_87,因此【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_88
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_89【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_90求导等于0,求出:【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_91

# 线性判别分析
'''
参数:
solver:{'svd','lsqr','eigen'},默认='svd'
solver的使用,可能的值:
'svd':奇异值分解(默认)。不计算协方差矩阵,因此建议将此求解器用于具有大量特征的数据。

'lsqr':最小二乘解,可以与收缩结合使用。

'eigen':特征值分解,可以与收缩结合使用。
'''
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
lda_iris = LinearDiscriminantAnalysis()
lda_iris.fit(X,y)
lda_iris.score(X,y)
# 0.98
(2) 朴素贝叶斯:

在线性判别分析中,我们假设每种分类类别下的特征遵循同一个协方差矩阵,每两个特征之间是存在协方差的,因此在线性判别分析中各种特征是不是独立的。但是,朴素贝叶斯算法对线性判别分析作进一步的模型简化,它将线性判别分析中的协方差矩阵中的协方差全部变成0,只保留各自特征的方差,也就是朴素贝叶斯假设各个特征之间是不相关的

在之前所看到的偏差-方差理论中,我们知道模型的简化可以带来方差的减少但是增加偏差,因此朴素贝叶斯也不例外,它比线性判别分析模型的方差小,偏差大。虽然简化了模型,实际中使用朴素贝叶斯的案例非常多,甚至多于线性判别分析,例如新闻分类,垃圾邮件分类等。

# 朴素贝叶斯             
from sklearn.naive_bayes import GaussianNB
NB_iris = GaussianNB()
NB_iris.fit(X, y)
NB_iris.score(X,y)
# 0.96

三、决策树 :

与前面内容所讲的决策树回归大致是一样的,只是在回归问题中,选择分割点的标准是均方误差,但是在分类问题中,由于因变量是类别变量而不是连续变量,因此用均方误差显然不合适。那问题是用什么作为选择分割点的标准呢?我们先来分析具体的问题:

在回归树中,对一个给定的观测值,因变量的预测值取它所属的终端结点内训练集的平均因变量。与之相对应,对于分类树来说,给定一个观测值,因变量的预测值为它所属的终端结点内训练集的最常出现的类。分类树的构造过程与回归树也很类似,与回归树一样,分类树也是采用递归二叉分裂。但是在分类树中,均方误差无法作为确定分裂节点的准则,一个很自然的替代指标是分类错误率。分类错误率就是:此区域内的训练集中非常见类所占的类别,即: 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_92上式中的【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_93代表第m个区域的训练集中第k类所占的比例。但是在大量的事实证明:分类错误率在构建决策树时不够敏感,一般在实际中用如下两个指标代替:

(1) 基尼系数:

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_94

在基尼系数的定义中,我们发现这个指标衡量的是K个类别的总方差。不难发现,如果所有的【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_93的取值都接近0或者1,基尼系数会很小。因此基尼系数被视为衡量结点纯度的指标----如果他的取值小,那就意味着某个节点包含的观测值几乎来自同一个类别。

由基尼系数作为指标得到的分类树叫做:CART。

(2) 交叉熵:

可以替代基尼系数的指标是交叉熵,定义如下:

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_96 显然,如果所有的【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_93都接近于0或者1,那么交叉熵就会接近0。因此,和基尼系数一样,如果第m个结点的纯度越高,则交叉熵越小。事实证明,基尼系数和交叉熵在数值上时很接近的。

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_98

决策树分类算法的完整步骤:
a. 选择最优切分特征 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_99 以及该特征上的最优点 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_100
遍历特征 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_99 以及固定j后遍历切分点s,选择使得基尼系数或者交叉熵最小的 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_102
b. 按照 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_102 分裂特征空间,每个区域内的类别为该区域内样本比例最多的类别。
c. 继续调用步骤1,2直到满足停止条件,就是每个区域的样本数小于等于5。
d. 将特征空间划分为 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_104

# 使用决策树算法对iris分类:
'''
criterion:{“gini”, “entropy”}, default=”gini”
max_depth:树的最大深度。
min_samples_split:拆分内部节点所需的最少样本数
min_samples_leaf :在叶节点处需要的最小样本数。

'''
from sklearn.tree import DecisionTreeClassifier
tree_iris = DecisionTreeClassifier(min_samples_leaf=5)
tree_iris.fit(X,y)
tree_iris.score(X,y)
# 0.9733333333333334

四、支持向量机SVM:

支持向量机SVM是20世纪90年代在计算机界发展起来的一种分类算法,在许多问题中都被证明有较好的效果,被认为是适应性最广的算法之一。

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_105


支持向量机的基本原理非常简单,如图所视,白色和蓝色的点各为一类,我们的目标是找到一个分割平面将两个类别分开。通常来说,如果数据本身是线性可分的,那么事实上存在无数个这样的超平面。这是因为给定一个分割平面稍微上移下移或旋转这个超平面,只要不接触这些观测点,仍然可以将数据分开。一个很自然的想法就是找到最大间隔超平面,即找到一个分割平面距离最近的观测点最远。下面我们来严格推导:

我们根据距离超平米那最近的点,只要同时缩放w和b可以得到:【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_106【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_107,因此:

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_108

由此可知道SVM模型的具体形式:
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_109
可以将约束条件写为: 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_110
可以将优化问题拉格朗日化
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_111
因此:
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_111欲构造 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_113 问题, 首先求拉格朗日化的问题中 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_114【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_115 的值, 对 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_114 求梯度, 令梯度为 0, 可求得 w:
对 b 求梯度, 令梯度为 0, 可得:
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_117

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_114 带入拉格朗日化的原问题可得
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_119
因此:
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_120

五、非线性支持向量机:

在刚刚的讨论中,我们都是着重讨论了线性支持向量机是如何工作的,但是在现实生活中,我们很难碰到线性可分的数据集,如:

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_121

那我们应该如何处理非线性问题呢?——将数据投影至更加高的维度!

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_122


上图中,在一维数据做不到线性可分,我们将数据投影至二维平面就可以成功线性可分。那么,我们来详细探讨下这其中的奥妙:

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_123

如果我们使用上面公式的形式将低维数据拓展至高维数据,则必须面临一个很大的问题,那就是:维度爆炸导致的计算量太大的问题。假如是一个2维特征的数据,我们可以将其映射到5维来做特征的内积,如果原始空间是三维,可以映射到到19维空间,似乎还可以处理。但是如果我们的低维特征是100个维度,1000个维度呢?

那么我们要将其映射到超级高的维度来计算特征的内积。这时候映射成的高维维度是爆炸性增长的,这个计算量实在是太大了,而且如果遇到无穷维的情况,就根本无从计算了。能不能呢个避免这个问题呢?核函数隆重登场:

回顾线性可分SVM的优化目标函数:
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_124
注意到上式低维特征仅仅以内积【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_125 的形式出现,如果我们定义一个低维特征空间到高维特征空间的映射【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_126,将所有特征映射到一个更高的维度,让数据线性可分,我们就可以继续按前两篇的方法来优化目标函数,求出分离超平面和分类决策函数了。也就是说现在的SVM的优化目标函数变成:
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_127
可以看到,和线性可分SVM的优化目标函数的区别仅仅是将内积【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_128替换为【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_129。我们要将其映射到超级高的维度来计算特征的内积。这时候映射成的高维维度是爆炸性增长的,这个计算量实在是太大了,而且如果遇到无穷维的情况,就根本无从计算了。

核函数

下面引入核函数:
假设【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_126是一个从低维的输入空间【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_131(欧式空间的子集或者离散集合)到高维的希尔伯特空间的【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_132映射。那么如果存在函数【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_133,对于任意【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_134,都有:
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_135
那么我们就称【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_136为核函数。
仔细发现,【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_136的计算是在低维特征空间来计算的,它避免了在刚才我们提到了在高维维度空间计算内积的恐怖计算量。也就是说,我们可以好好享受在高维特征空间线性可分的利益,却避免了高维特征空间恐怖的内积计算量。下面介绍几种常用的核函数:

(1) 多项式核函数:

多项式核函数(Polynomial Kernel)是线性不可分SVM常用的核函数之一,表达式为:
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_138
C用来控制低阶项的强度,C=0,d=1代表无核函数。

(2) 高斯核函数:

高斯核函数(Gaussian Kernel),在SVM中也称为径向基核函数(Radial Basis Function,RBF),它是非线性分类SVM最主流的核函数。libsvm默认的核函数就是它。表达式为:
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_139
使用高斯核函数之前需要将特征标准化,因此这里衡量的是样本之间的相似度。

(3) Sigmoid核函数:

Sigmoid核函数(Sigmoid Kernel)也是线性不可分SVM常用的核函数之一,表达式为:
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_140
此时的SVM相当于没有隐藏层的简单神经网络。

(4) 余弦相似度核:

常用于衡量两段文字的余弦相似度,表达式为:
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_数据集_141

from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
'''
C:正则化参数。正则化的强度与C成反比。必须严格为正。惩罚是平方的l2惩罚。
kernel:{'linear','poly','rbf','sigmoid','precomputed'},默认='rbf'
degree:多项式和的阶数
gamma:“ rbf”,“ poly”和“ Sigmoid”的内核系数。
shrinking:是否软间隔分类,默认true

'''
svc_iris = make_pipeline(StandardScaler(), SVC(gamma='auto'))
svc_iris.fit(X, y)
svc_iris.score(X,y)
# 0.9733333333333334

步骤4:评估模型的性能并调参:

更详细的可以查看萌弟大佬的知乎:https://zhuanlan.zhihu.com/p/140040705

(1)方式1:网格搜索GridSearchCV()

# 使用网格搜索进行超参数调优:
# 方式1:网格搜索GridSearchCV()
from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC
import time

start_time = time.time()
pipe_svc = make_pipeline(StandardScaler(),SVC(random_state=1))
param_range = [0.0001,0.001,0.01,0.1,1.0,10.0,100.0,1000.0]
param_grid = [{'svc__C':param_range,'svc__kernel':['linear']},{'svc__C':param_range,'svc__gamma':param_range,'svc__kernel':['rbf']}]
gs = GridSearchCV(estimator=pipe_svc,param_grid=param_grid,scoring='accuracy',cv=10,n_jobs=-1)
gs = gs.fit(X,y)
end_time = time.time()
print("网格搜索经历时间:%.3f S" % float(end_time-start_time))
print(gs.best_score_)
print(gs.best_params_)
4.300 S
0.9800000000000001
{'svc__C': 1.0, 'svc__gamma': 0.1, 'svc__kernel': 'rbf'}

(2)方式2:随机网格搜索RandomizedSearchCV()

# 方式2:随机网格搜索RandomizedSearchCV()
from sklearn.model_selection import RandomizedSearchCV
from sklearn.svm import SVC
import time

start_time = time.time()
pipe_svc = make_pipeline(StandardScaler(),SVC(random_state=1))
param_range = [0.0001,0.001,0.01,0.1,1.0,10.0,100.0,1000.0]
param_grid = [{'svc__C':param_range,'svc__kernel':['linear']},{'svc__C':param_range,'svc__gamma':param_range,'svc__kernel':['rbf']}]
# param_grid = [{'svc__C':param_range,'svc__kernel':['linear','rbf'],'svc__gamma':param_range}]
gs = RandomizedSearchCV(estimator=pipe_svc, param_distributions=param_grid,scoring='accuracy',cv=10,n_jobs=-1)
gs = gs.fit(X,y)
end_time = time.time()
print("随机网格搜索经历时间:%.3f S" % float(end_time-start_time))
print(gs.best_score_)
print(gs.best_params_)
0.942 S
0.9733333333333334
{'svc__kernel': 'linear', 'svc__C': 100.0}

(3)绘图

当类别为两类时,可以绘制混淆矩阵与ROC曲线

混淆矩阵:
# 混淆矩阵:
# 加载数据
df = pd.read_csv("http://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data",header=None)
'''
乳腺癌数据集:569个恶性和良性肿瘤细胞的样本,M为恶性,B为良性
'''
# 做基本的数据预处理
from sklearn.preprocessing import LabelEncoder

X = df.iloc[:,2:].values
y = df.iloc[:,1].values
le = LabelEncoder() #将M-B等字符串编码成计算机能识别的0-1
y = le.fit_transform(y)
le.transform(['M','B'])
# 数据切分8:2
from sklearn.model_selection import train_test_split

X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.2,stratify=y,random_state=1)
from sklearn.svm import SVC
pipe_svc = make_pipeline(StandardScaler(),SVC(random_state=1))
from sklearn.metrics import confusion_matrix

pipe_svc.fit(X_train,y_train)
y_pred = pipe_svc.predict(X_test)
confmat = confusion_matrix(y_true=y_test,y_pred=y_pred)
fig,ax = plt.subplots(figsize=(2.5,2.5))
ax.matshow(confmat, cmap=plt.cm.Blues,alpha=0.3)
for i in range(confmat.shape[0]):
for j in range(confmat.shape[1]):
ax.text(x=j,y=i,s=confmat[i,j],va='center',ha='center')
plt.xlabel('predicted label')
plt.ylabel('true label')
plt.show()
# 0.9733333333333334

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_142

​绘制ROC曲线:
# 绘制ROC曲线:
from sklearn.metrics import roc_curve,auc
from sklearn.metrics import make_scorer,f1_score
scorer = make_scorer(f1_score,pos_label=0)
gs = GridSearchCV(estimator=pipe_svc,param_grid=param_grid,scoring=scorer,cv=10)
y_pred = gs.fit(X_train,y_train).decision_function(X_test)
#y_pred = gs.predict(X_test)
fpr,tpr,threshold = roc_curve(y_test, y_pred) ###计算真阳率和假阳率
roc_auc = auc(fpr,tpr) ###计算auc的值
plt.figure()
lw = 2
plt.figure(figsize=(7,5))
plt.plot(fpr, tpr, color='darkorange',
lw=lw, label='ROC curve (area = %0.2f)' % roc_auc) ###假阳率为横坐标,真阳率为纵坐标做曲线
plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
plt.xlim([-0.05, 1.0])
plt.ylim([-0.05, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic ')
plt.legend(loc="lower right")
plt.show()

【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_143

第二部分:作业

1.回归和分类的联系和区别,如何用回归问题理解分类问题

回归问题与分类问题需要预测的因变量不一样:
(1)在回归问题中,因变量是连续性变量,我们需要预测【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_07是一个连续的实数;
(2)在分类问题中,我们往往是通过已知X的信息预测Y的类别,往往是一个离散集合中的某个元素。如:是否患癌症,图片是猫还是狗等。

2.为啥分类问题的损失函数可以是交叉熵而不是均方误差

更多可以参考:​​直观理解为什么分类问题用交叉熵损失而不用均方误差损失?​​ 在回归问题中,选择分割点的标准是均方误差,但是在分类问题中,由于因变量是类别变量而不是连续变量,因此用均方误差显然不合适。

决策树:在回归树中,对一个给定的观测值,因变量的预测值取它所属的终端结点内训练集的平均因变量。与之相对应,对于分类树来说,给定一个观测值,因变量的预测值为它所属的终端结点内训练集的最常出现的类。分类树的构造过程与回归树也很类似,与回归树一样,分类树也是采用递归二叉分裂。但是在分类树中,均方误差无法作为确定分裂节点的准则,一个很自然的替代指标是分类错误率。分类错误率就是:此区域内的训练集中非常见类所占的类别,即: 【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_92上式中的【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_93代表第m个区域的训练集中第k类所占的比例。但是在大量的事实证明:分类错误率在构建决策树时不够敏感,一般在实际中用如下两个指标代替:
(1) 基尼系数:
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_sklearn_94
在基尼系数的定义中,我们发现这个指标衡量的是K个类别的总方差。不难发现,如果所有的【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_93的取值都接近0或者1,基尼系数会很小。因此基尼系数被视为衡量结点纯度的指标----如果他的取值小,那就意味着某个节点包含的观测值几乎来自同一个类别。
由基尼系数作为指标得到的分类树叫做:CART。
(2) 交叉熵:
可以替代基尼系数的指标是交叉熵,定义如下:
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_机器学习_96 显然,如果所有的【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性回归_93都接近于0或者1,那么交叉熵就会接近0。因此,和基尼系数一样,如果第m个结点的纯度越高,则交叉熵越小。事实证明,基尼系数和交叉熵在数值上时很接近的。

由于线性判别分析的目标是同一类别内方差小,不同类别之间距离大,因此损失函数定义为:
【集成学习】(task4)分类问题(逻辑回归、概率分类、决策树、SVM)(更新ing)_线性判别分析_86

3.线性判别分析和逻辑回归在估计参数方面有啥异同点

线性判别分析不用直接输出概率值确定分类结果;而逻辑回归通过极大似然估计,最后输出概率值。

4.拓展题&面试题:尝试从0推导SVM

看这个的pdf:​​https://zhuanlan.zhihu.com/p/31652569​

5.二次判别分析,线性判别分析,朴素贝叶斯之间的联系和区别

6.必做题:使用python+numpy实现逻辑回归

之前做吴恩达的作业有做到​

# coding=utf-8

import numpy as np
import matplotlib.pyplot as plt
import re
import time

class RegressionModel(object):
"""
逻辑回归模型
"""
def __init__(self):
self.W = None

def train(self, x_train, y_train, learning_rate=0.1, num_iters=10000):
"""
模型训练
:param x_train: shape = num_train, dim_feature
:param y_train: shape = num_train, 1
:param learning_rate
:param num_iters
:return: loss_history
"""
num_train, dim_feature = x_train.shape
# w * x + b
x_train_ = np.hstack((x_train, np.ones((num_train, 1))))
self.W = 0.001 * np.random.randn(dim_feature + 1, 1)
loss_history = []
for i in range(num_iters+1):
# linear transformation: w * x + b
g = np.dot(x_train_, self.W)
# sigmoid: 1 / (1 + e**-x)
h = 1 / (1 + np.exp(-g))
# cross entropy: 1/m * sum((y*np.log(h) + (1-y)*np.log((1-h))))
loss = -np.sum(y_train * np.log(h) + (1 - y_train) * np.log(1 - h)) / num_train
loss_history.append(loss)
# dW = cross entropy' = 1/m * sum(h-y) * x
dW = x_train_.T.dot(h - y_train) / num_train
# W = W - dW
self.W -= learning_rate * dW
# debug
if i % 100 == 0:
print('Iters: %r/%r Loss: %r' % (i, num_iters, loss))
return loss_history

def validate(self, x_val, y_val):
"""
验证模型效果
:param x_val: shape = num_val, dim_feature
:param y_val: shape = num_val, 1
:return: accuracy, metric
"""
num_val, dim_feature = x_val.shape
x_val_ = np.hstack((x_val, np.ones((num_val, 1))))
# linear transformation: w * x + b
g = np.dot(x_val_, self.W)
# sigmoid: 1 / (1 + e**-x)
h = 1 / (1 + np.exp(-g))
# predict
y_val_ = h
y_val_[y_val_ >= 0.5] = 1
y_val_[y_val_ < 0.5] = 0
true_positive = len(np.where(((y_val_ == 1).astype(int) + (y_val == 1).astype(int) == 2) == True)[0]) * 1.0 / num_val
true_negative = len(np.where(((y_val_ == 0).astype(int) + (y_val == 0).astype(int) == 2) == True)[0]) * 1.0 / num_val
false_positive = len(np.where(((y_val_ == 1).astype(int) + (y_val == 0).astype(int) == 2) == True)[0]) * 1.0 / num_val
false_negative = len(np.where(((y_val_ == 0).astype(int) + (y_val == 1).astype(int) == 2) == True)[0]) * 1.0 / num_val
negative_instance = true_negative + false_positive
positive_instance = false_negative + true_positive
metric = np.array([[true_negative / negative_instance, false_positive / negative_instance],
[false_negative / positive_instance, true_positive / positive_instance]])
accuracy = true_positive + true_negative
return accuracy, metric

def feature_batch_extraction(d_list, kw_set):
"""
特征批量提取
:param d_list: 原始数据集
:param kw_set: 关键字列表
:return:
"""
kw_2_idx_dict = dict(zip(list(kw_set), range(len(kw_set))))
feature_data = np.zeros((len(d_list), len(kw_set)))
label_data = np.zeros((len(d_list), 1))
for i in range(len(d_list)):
label, words = d_list[i]
for word in words:
if word in kw_2_idx_dict:
feature_data[i, kw_2_idx_dict[word]] = 1
label_data[i] = 1 if label == 'spam' else 0
return feature_data, label_data


def data_pre_process(data_file_name):
"""
句子切分成单词,由于是英文,所以这里处理方式比较暴力,按照空格和除'之外的符号来切分了;然后全部转小写
:param data_file_name:
:return:
"""
fh = open(data_file_name, encoding='utf-8')
data = list()
for line in fh.readlines():
label_text_pair = line.split('\t')
word_list = re.split('[^\'a-zA-Z]', label_text_pair[1])
word_in_doc_set = set()
for raw_word in word_list:
word = raw_word.lower()
if word == '':
continue
word_in_doc_set.add(word)
# 组成 [[label] [input_text_words]] 的形式
data.append((label_text_pair[0], list(word_in_doc_set)))
return data


def statistic_key_word(data, cut_off=None):
"""
统计单词出现的文档次数,并试图把直观上无效(出现在的文档数目较少)的单词去掉
:param data: data in one line: [label] [input_text]
:param cut_off:
:return:
"""
# 针对各个单词,统计单词出现的文档次数
w_dict = dict()
total_doc_count = len(data)
for _, word_in_doc_set in data:
for word in word_in_doc_set:
if word not in w_dict:
w_dict[word] = 0
w_dict[word] += 1
for word in w_dict.keys():
w_dict[word] /= total_doc_count * 1.0
# 按出现文档次数从高到低,对单词进行排序
w_count_list = sorted(w_dict.items(), key=lambda d: d[1], reverse=True)
# 截断后续出现次数过低的单词
kw_set = set()
cut_off_length = cut_off if cut_off else len(w_count_list)
for word, _ in w_count_list[:cut_off_length]:
kw_set.add(word)
return w_count_list, kw_set


def shuffle(data, k):
"""
切分并打乱,为模型的交叉验证做准备
:param data:
:param k:
:return:
"""
# 将数据按类别归类,目的是为了切分各个fold的时候,保证数据集合中类别分布平均一些
label_data_dict = dict()
for label, word_in_doc_set in data:
if label not in label_data_dict:
label_data_dict[label] = list()
label_data_dict[label].append((label, word_in_doc_set))
# 切分并打乱
k_group_data_list = [list() for _ in range(k)]
for label, label_data_list in label_data_dict.items():
# 打乱
seq = np.random.permutation(range(len(label_data_list)))
# 切分
fold_instance_count = int(len(label_data_list) / k)
for i in range(k):
for idx in range(i * fold_instance_count, (i+1) * fold_instance_count):
k_group_data_list[i].append(label_data_list[seq[idx]])
k_fold_data_list = list()
for i in range(k):
train_data = []
for j in range(k):
if i != j:
train_data.extend(k_group_data_list[j])
k_fold_data_list.append((train_data, k_group_data_list[i]))
return k_fold_data_list

def draw_loss_list(loss_list):
"""
画出单词频次分布情况,为选择一个合适的截断提供直观的依据
:param loss_list:
:return:
"""
plt.figure(figsize=(8, 4))
plt.xlabel('Train iteration')
plt.ylabel('Loss')
xt_list = range(0, len(loss_list[0][1]), 1000)
print(len(loss_list[0][1]))
for cut_off, loss in loss_list:
print(len(loss))
plt.plot(range(0, len(loss)), loss, label='cut off %r' % (cut_off,))
plt.xticks(xt_list, xt_list)
plt.xlim(1, len(loss_list[0][1]) + 1)
plt.ylim(0, 0.7)
plt.legend()
plt.show()

def performance_with_cut_off():
"""

:return:
"""
file_name = './data/SMSSpamCollection.txt'
raw_data_list = data_pre_process(file_name)
fold_count = 4
fold_data_list = shuffle(raw_data_list, fold_count)
loss_list = list()
accuracy_list = list()
metric_list = list()
time_cost_list = list()
for cut_off in (200, 500, 2000, 5000, 7956):
t1 = time.perf_counter()
data_list = fold_data_list[0]
train_data_list, test_data_list = data_list
word_count_list, key_word_set = statistic_key_word(train_data_list, cut_off=cut_off)
# Feature extraction
train_feature, train_label = feature_batch_extraction(train_data_list, key_word_set)
validate_feature, validate_label = feature_batch_extraction(test_data_list, key_word_set)
# Train model
lr_model = RegressionModel()
loss_history = lr_model.train(train_feature, train_label, num_iters=10000)
loss_list.append((cut_off, loss_history))
accuracy, metric = lr_model.validate(validate_feature, validate_label)
accuracy_list.append(accuracy)
metric_list.append(metric)
time_cost_list.append((time.perf_counter() - t1))
with open('./result/lr_loss_list.txt', 'w') as f:
f.write(str(loss_list) + '\n')
f.write(str(accuracy_list) + '\n')
f.write(str(time_cost_list) + '\n')
f.write(str(metric_list))
with open('./result/lr_loss_list.txt') as f:
loss_list = eval(f.readline())
draw_loss_list(loss_list)
accuracy_list = eval(f.readline())
print(accuracy_list)
time_cost_list = eval(f.readline())
print(time_cost_list)
metric_list = eval(f.readline())
print(metric_list)

def performance_with_fold():
"""

:return:
"""
file_name = './data/SMSSpamCollection.txt'
raw_data_list = data_pre_process(file_name)
fold_count = 4
fold_data_list = shuffle(raw_data_list, fold_count)
acc_average = 0
cut_off = 500
t1 = time.perf_counter()
for fold, data_list in enumerate(fold_data_list):
train_data_list, test_data_list = data_list
word_count_list, key_word_set = statistic_key_word(train_data_list, cut_off=cut_off)
# Feature extraction
train_feature, train_label = feature_batch_extraction(train_data_list, key_word_set)
validate_feature, validate_label = feature_batch_extraction(test_data_list, key_word_set)
# Train model
lr_model = RegressionModel()
loss_history = lr_model.train(train_feature, train_label)
# Validate
accuracy, metric = lr_model.validate(validate_feature, validate_label)
acc_average += accuracy
print('Fold %r/%r - Acc:%r Metric:%r' % (fold + 1, fold_count, accuracy, metric))
print('Average Acc:%r Average Cost Time:%r' % (acc_average / len(fold_data_list),
(time.perf_counter() - t1) / len(fold_data_list)))

if __name__ == '__main__':
performance_with_cut_off()

7.拓展题:了解梯度下降法、牛顿法、拟牛顿法和smo算法等优化算法

​常见的几种最优化方法(梯度下降法、牛顿法、拟牛顿法、共轭梯度法等)​​​ SMO算法​

Reference

(1)datawhale萌弟教程
(2)最优化理论之无约束优化基本结构及其python应用:https://zhuanlan.zhihu.com/p/163405865
(3)最优化理论之负梯度方法与Newton型方法:https://zhuanlan.zhihu.com/p/165914126
(4)评估模型的性能并调参:https://zhuanlan.zhihu.com/p/140040705
(5)​​​javascript:void(0)​​​ (6)​​https://www.jianshu.com/p/4cfb4f734358​