用朴素贝叶斯完成语种检测

用朴素贝叶斯完成语种检测

我们试试用朴素贝叶斯完成一个语种检测的分类器,说起来,用朴素贝叶斯完成这个任务,其实准确度还不错。

image.png

查看数据,并划分训练集和验证集

In [87]:

1
2
3
4
5
6
7
in_f = open('data.csv')
lines = in_f.readlines() # 一次读取整个文件,每行的内容放在一个字符串变量中作为列表的一个元素。
in_f.close()
dataset = [(line.strip()[:-3], line.strip()[-2:]) for line in lines]
# 把句子和语言分开,这里.strip()把\n去掉了。
print(lines[0]) # 打印第一个句子
print(dataset[0])
1
2
3
1 december wereld aids dag voorlichting in zuidafrika over bieten taboes en optimisme,nl

('1 december wereld aids dag voorlichting in zuidafrika over bieten taboes en optimisme', 'nl')

In [88]:

1
2
3
4
5
6
7
8
9
10
from sklearn.model_selection import train_test_split
x, y = zip(*dataset) # 解压缩
x, y = list(x),list(y)
print(x[0],y[0])
print("-----"*10)
x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=1)
# 默认0.25的比例

print(len(x_train),x_train[0],y_train[0])
print(len(x_test),x_test[0],y_test[0])
1
2
3
4
1 december wereld aids dag voorlichting in zuidafrika over bieten taboes en optimisme nl
--------------------------------------------------
6799 io non ho ipad ma mi sa che è fatta un po meglio sfrutta meglio la superficie it
2267 ook jullie bedankt voor jullie steun nl

数据去燥,设置正则化规则

In [89]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import re
#因为是twitter的数据,有时候会有一些@ # http等字符。
def remove_noise(document):
noise_pattern = re.compile("|".join(["http\S+", "\@\w+", "\#\w+"]))
# 关于正则表达式的使用方法,看第一节课

clean_text = re.sub(noise_pattern, "", document)
# 使用空白""替换document中每一个匹配noise_pattern规则的子串。
return clean_text.strip()

#举例
remove_noise("Trump images are now more popular than cat gifs. @trump a #trends http://www.trumptrends.html")
# http\S+ 匹配的是http://www.trumptrends.html
# \@\w+ 匹配的是@trump
# \#\w+ 匹配的是#trends

Out[89]:

1
'Trump images are now more popular than cat gifs.  a'

统计词频,并选取1000个特征作为输入的特征维度

In [94]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from sklearn.feature_extraction.text import CountVectorizer
# 关于CountVectorizer参数可以看这个:https://blog.csdn.net/weixin_38278334/article/details/82320307

vec = CountVectorizer(
lowercase=True, # 将所有字符变成小写
analyzer='char_wb', # 按ngram_range:1~2个字符划分特征
ngram_range=(1,2), # 考虑两个单词的的关联顺序
max_features=1000, # 选取出现次数最多的1000个特征
preprocessor=remove_noise # 调用前面去燥的函数,这里是说计数前需要先做这一步
)
vec.fit(x_train)
print(vec.transform(x_train).toarray()) # 训练集
print(vec.transform(x_train).toarray().shape) # 训练集维度
vec.get_feature_names()[:50] # 看下选取的1000个特征前100个长啥样,可以看到按1~2个字符不等划分特征。
1
2
3
4
5
6
7
8
[[34  0  0 ...  0  0  0]
[22 0 0 ... 0 0 0]
[18 0 0 ... 0 0 0]
...
[32 0 0 ... 0 0 0]
[18 0 0 ... 0 0 0]
[ 8 0 0 ... 0 0 0]]
(6799, 1000)

Out[94]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
[' ',
' 0',
' 1',
' 2',
' 3',
' 4',
' 5',
' 6',
' 7',
' 8',
' 9',
' a',
' b',
' c',
' d',
' e',
' f',
' g',
' h',
' i',
' j',
' k',
' l',
' m',
' n',
' o',
' p',
' q',
' r',
' s',
' t',
' u',
' v',
' w',
' x',
' y',
' z',
' ä',
' è',
' é',
' ê',
' ú',
' ü',
'0',
'0 ',
'00',
'01',
'02',
'04',
'05']

训练及预测

In [91]:

1
2
3
4
5
6
7
8
from sklearn.naive_bayes import MultinomialNB
classifier = MultinomialNB()
classifier.fit(vec.transform(x_train), y_train)
#classifier.fit(vec.transform(x_train).toarray(), y_train) 也可以
print(classifier.score(vec.transform(x_test), y_test))
# 打印准确率
print(classifier.predict(vec.transform(['This is an English sentence'])))
# 测试下
1
2
0.9770621967357741
['en']

重新封装下

In [92]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import re

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB


class LanguageDetector():

def __init__(self, classifier=MultinomialNB()):
self.classifier = classifier
self.vectorizer = CountVectorizer(ngram_range=(1,2), max_features=1000, preprocessor=self._remove_noise)

def _remove_noise(self, document):
noise_pattern = re.compile("|".join(["http\S+", "\@\w+", "\#\w+"]))
clean_text = re.sub(noise_pattern, "", document)
return clean_text

def features(self, X):
return self.vectorizer.transform(X)

def fit(self, X, y):
self.vectorizer.fit(X)
self.classifier.fit(self.features(X), y)

def predict(self, x):
return self.classifier.predict(self.features([x]))

def score(self, X, y):
return self.classifier.score(self.features(X), y)

In [93]:

1
2
3
4
5
6
7
8
9
10
11
in_f = open('data.csv')
lines = in_f.readlines()
in_f.close()
dataset = [(line.strip()[:-3], line.strip()[-2:]) for line in lines]
x, y = zip(*dataset)
x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=1)

language_detector = LanguageDetector()
language_detector.fit(x_train, y_train)
print(language_detector.predict('This is an English sentence'))
print(language_detector.score(x_test, y_test))
1
2
['en']
0.9770621967357741

In [ ]:

1
 

集成学习与boosting模型

机器学习中的集成学习

顾名思义,集成学习(ensemble learning)指的是将多个学习器进行有效地结合,组建一个“学习器委员会”,其中每个学习器担任委员会成员并行使投票表决权,使得委员会最后的决定更能够四方造福普度众生,即其泛化性能要能优于其中任何一个学习器。

个体与集成

集成学习的基本结构为:先产生一组个体学习器,再使用某种策略将它们结合在一起。集成模型如下图所示:

1.png

在上图的集成模型中,若个体学习器都属于同一类别,例如都是决策树或都是神经网络,则称该集成为同质的(homogeneous);若个体学习器包含多种类型的学习算法,例如既有决策树又有神经网络,则称该集成为异质的(heterogenous)。

同质集成:个体学习器称为“基学习器”(base learner),对应的学习算法为“基学习算法”(base learning algorithm)。

异质集成:个体学习器称为“组件学习器”(component learner)或直称为“个体学习器”。

上面我们已经提到要让集成起来的泛化性能比单个学习器都要好,虽说团结力量大但也有木桶短板理论调皮捣蛋,那如何做到呢?这就引出了集成学习的两个重要概念:准确性多样性(diversity)。准确性指的是个体学习器不能太差,要有一定的准确度;多样性则是个体学习器之间的输出要具有差异性。通过下面的这三个例子可以很容易看出这一点,准确度较高,差异度也较高,可以较好地提升集成性能。

2.png

现在考虑二分类的简单情形,假设基分类器之间相互独立(能提供较高的差异度),且错误率相等为 ε,则可以将集成器的预测看做一个伯努利实验,易知当所有基分类器中不足一半预测正确的情况下,集成器预测错误,所以集成器的错误率可以计算为:

3.png

此时,集成器错误率随着基分类器的个数的增加呈指数下降,但前提是基分类器之间相互独立,在实际情形中显然是不可能的,假设训练有A和B两个分类器,对于某个测试样本,显然满足:P(A=1 | B=1)> P(A=1),因为A和B为了解决相同的问题而训练,因此在预测新样本时存在着很大的联系。因此,个体学习器的“准确性”和“差异性”本身就是一对矛盾的变量,准确性高意味着牺牲多样性,所以产生“好而不同”的个体学习器正是集成学习研究的核心。现阶段有三种主流的集成学习方法:Boosting、Bagging以及随机森林(Random Forest),接下来将进行逐一介绍。

Boosting

Boosting是一种串行的工作机制,即个体学习器的训练存在依赖关系,必须一步一步序列化进行。其基本思想是:增加前一个基学习器在训练训练过程中预测错误样本的权重,使得后续基学习器更加关注这些打标错误的训练样本,尽可能纠正这些错误,一直向下串行直至产生需要的T个基学习器,Boosting最终对这T个学习器进行加权结合,产生学习器委员会。

Boosting族算法最著名、使用最为广泛的就是AdaBoost,因此下面主要是对AdaBoost算法进行介绍。AdaBoost使用的是指数损失函数,因此AdaBoost的权值与样本分布的更新都是围绕着最小化指数损失函数进行的。看到这里回想一下之前的机器学习算法,不难发现机器学习的大部分带参模型只是改变了最优化目标中的损失函数:如果是Square loss,那就是最小二乘了;如果是Hinge Loss,那就是著名的SVM了;如果是log-Loss,那就是Logistic Regression了。

定义基学习器的集成为加权结合,则有:

4.png

AdaBoost算法的指数损失函数定义为:

5.png

具体说来,整个Adaboost 迭代算法分为3步:

  • 初始化训练数据的权值分布。如果有N个样本,则每一个训练样本最开始时都被赋予相同的权值:1/N。
  • 训练弱分类器。具体训练过程中,如果某个样本点已经被准确地分类,那么在构造下一个训练集中,它的权值就被降低;相反,如果某个样本点没有被准确地分类,那么它的权值就得到提高。然后,权值更新过的样本集被用于训练下一个分类器,整个训练过程如此迭代地进行下去。
  • 将各个训练得到的弱分类器组合成强分类器。各个弱分类器的训练过程结束后,加大分类误差率小的弱分类器的权重,使其在最终的分类函数中起着较大的决定作用,而降低分类误差率大的弱分类器的权重,使其在最终的分类函数中起着较小的决定作用。

整个AdaBoost的算法流程如下所示:

6.png

可以看出:AdaBoost的核心步骤就是计算基学习器权重和样本权重分布,那为何是上述的计算公式呢?这就涉及到了我们之前为什么说大部分带参机器学习算法只是改变了损失函数,就是因为大部分模型的参数都是通过最优化损失函数(可能还加个规则项)而计算(梯度下降,坐标下降等)得到,这里正是通过最优化指数损失函数从而得到这两个参数的计算公式,具体的推导过程此处不进行展开。

Boosting算法要求基学习器能对特定分布的数据进行学习,即每次都更新样本分布权重,这里书上提到了两种方法:“重赋权法”(re-weighting)和“重采样法”(re-sampling),书上的解释有些晦涩,这里进行展开一下:

重赋权法 : 对每个样本附加一个权重,这时涉及到样本属性与标签的计算,都需要乘上一个权值。
重采样法 : 对于一些无法接受带权样本的及学习算法,适合用“重采样法”进行处理。方法大致过程是,根据各个样本的权重,对训练数据进行重采样,初始时样本权重一样,每个样本被采样到的概率一致,每次从N个原始的训练样本中按照权重有放回采样N个样本作为训练集,然后计算训练集错误率,然后调整权重,重复采样,集成多个基学习器。

从偏差-方差分解来看:Boosting算法主要关注于降低偏差,每轮的迭代都关注于训练过程中预测错误的样本,将弱学习提升为强学习器。从AdaBoost的算法流程来看,标准的AdaBoost只适用于二分类问题。在此,当选为数据挖掘十大算法之一的AdaBoost介绍到这里,能够当选正是说明这个算法十分婀娜多姿,背后的数学证明和推导充分证明了这一点,限于篇幅不再继续展开。

Bagging与Random Forest

相比之下,Bagging与随机森林算法就简洁了许多,上面已经提到产生“好而不同”的个体学习器是集成学习研究的核心,即在保证基学习器准确性的同时增加基学习器之间的多样性。而这两种算法的基本思(tao)想(lu)都是通过“自助采样”的方法来增加多样性。

Bagging

Bagging是一种并行式的集成学习方法,即基学习器的训练之间没有前后顺序可以同时进行,Bagging使用“有放回”采样的方式选取训练集,对于包含m个样本的训练集,进行m次有放回的随机采样操作,从而得到m个样本的采样集,这样训练集中有接近36.8%的样本没有被采到。按照相同的方式重复进行,我们就可以采集到T个包含m个样本的数据集,从而训练出T个基学习器,最终对这T个基学习器的输出进行结合。

7.png

Bagging算法的流程如下所示:

8.png

可以看出Bagging主要通过样本的扰动来增加基学习器之间的多样性,因此Bagging的基学习器应为那些对训练集十分敏感的不稳定学习算法,例如:神经网络与决策树等。从偏差-方差分解来看,Bagging算法主要关注于降低方差,即通过多次重复训练提高稳定性。不同于AdaBoost的是,Bagging可以十分简单地移植到多分类、回归等问题。总的说起来则是:AdaBoost关注于降低偏差,而Bagging关注于降低方差。

随机森林

随机森林(Random Forest)是Bagging的一个拓展体,它的基学习器固定为决策树,多棵树也就组成了森林,而“随机”则在于选择划分属性的随机,随机森林在训练基学习器时,也采用有放回采样的方式添加样本扰动,同时它还引入了一种属性扰动,即在基决策树的训练过程中,在选择划分属性时,RF先从候选属性集中随机挑选出一个包含K个属性的子集,再从这个子集中选择最优划分属性,一般推荐K=log2(d)。

这样随机森林中基学习器的多样性不仅来自样本扰动,还来自属性扰动,从而进一步提升了基学习器之间的差异度。相比决策树的Bagging集成,随机森林的起始性能较差(由于属性扰动,基决策树的准确度有所下降),但随着基学习器数目的增多,随机森林往往会收敛到更低的泛化误差。同时不同于Bagging中决策树从所有属性集中选择最优划分属性,随机森林只在属性集的一个子集中选择划分属性,因此训练效率更高。

9.png

结合策略

结合策略指的是在训练好基学习器后,如何将这些基学习器的输出结合起来产生集成模型的最终输出,下面将介绍一些常用的结合策略:

平均法(回归问题)

10.png

11.png

易知简单平均法是加权平均法的一种特例,加权平均法可以认为是集成学习研究的基本出发点。由于各个基学习器的权值在训练中得出,一般而言,在个体学习器性能相差较大时宜使用加权平均法,在个体学习器性能相差较小时宜使用简单平均法

投票法(分类问题)

12.png

13.png

14.png

绝对多数投票法(majority voting)提供了拒绝选项,这在可靠性要求很高的学习任务中是一个很好的机制。同时,对于分类任务,各个基学习器的输出值有两种类型,分别为类标记和类概率。

15.png

一些在产生类别标记的同时也生成置信度的学习器,置信度可转化为类概率使用,一般基于类概率进行结合往往比基于类标记进行结合的效果更好,需要注意的是对于异质集成,其类概率不能直接进行比较,此时需要将类概率转化为类标记输出,然后再投票。

学习法

学习法是一种更高级的结合策略,即学习出一种“投票”的学习器,Stacking是学习法的典型代表。Stacking的基本思想是:首先训练出T个基学习器,对于一个样本它们会产生T个输出,将这T个基学习器的输出与该样本的真实标记作为新的样本,m个样本就会产生一个mT的样本集,来训练一个新的“投票”学习器。投票学习器的输入属性与学习算法对Stacking集成的泛化性能有很大的影响,书中已经提到:*投票学习器采用类概率作为输入属性,选用多响应线性回归(MLR)一般会产生较好的效果**。

16.png

多样性(diversity)

在集成学习中,基学习器之间的多样性是影响集成器泛化性能的重要因素。因此增加多样性对于集成学习研究十分重要,一般的思路是在学习过程中引入随机性,常见的做法主要是对数据样本、输入属性、输出表示、算法参数进行扰动。

数据样本扰动,即利用具有差异的数据集来训练不同的基学习器。例如:有放回自助采样法,但此类做法只对那些不稳定学习算法十分有效,例如:决策树和神经网络等,训练集的稍微改变能导致学习器的显著变动。
输入属性扰动,即随机选取原空间的一个子空间来训练基学习器。例如:随机森林,从初始属性集中抽取子集,再基于每个子集来训练基学习器。但若训练集只包含少量属性,则不宜使用属性扰动。
输出表示扰动,此类做法可对训练样本的类标稍作变动,或对基学习器的输出进行转化。
算法参数扰动,通过随机设置不同的参数,例如:神经网络中,随机初始化权重与随机设置隐含层节点数。

机器学习中的Boosting模型:GBDT vs Xgboost vs LightGBM

见资料GBDT_wepon.pdf

聚类与降维

机器学习中的聚类算法

聚类是一种经典的无监督学习方法,无监督学习的目标是通过对无标记训练样本的学习,发掘和揭示数据集本身潜在的结构与规律,即不依赖于训练数据集的类标记信息。聚类则是试图将数据集的样本划分为若干个互不相交的类簇,从而每个簇对应一个潜在的类别。

聚类直观上来说是将相似的样本聚在一起,从而形成一个类簇(cluster)。那首先的问题是如何来度量相似性(similarity measure)呢?这便是距离度量,在生活中我们说差别小则相似,对应到多维样本,每个样本可以对应于高维空间中的一个数据点,若它们的距离相近,我们便可以称它们相似。那接着如何来评价聚类结果的好坏呢?这便是性能度量,性能度量为评价聚类结果的好坏提供了一系列有效性指标。

距离度量

谈及距离度量,最熟悉的莫过于欧式距离了,从年头一直用到年尾的距离计算公式:即对应属性之间相减的平方和再开根号。度量距离还有其它的很多经典方法,通常它们需要满足一些基本性质:

1.png

最常用的距离度量方法是“闵可夫斯基距离”(Minkowski distance)

2.png

当p=1时,闵可夫斯基距离即曼哈顿距离(Manhattan distance)

3.png

当p=2时,闵可夫斯基距离即欧氏距离(Euclidean distance)

4.png

我们知道属性分为两种:连续属性离散属性(有限个取值)。对于连续值的属性,一般都可以被学习器所用,有时会根据具体的情形作相应的预处理,例如:归一化等;而对于离散值的属性,需要作下面进一步的处理:

若属性值之间存在序关系,则可以将其转化为连续值,例如:身高属性“高”“中等”“矮”,可转化为{1, 0.5, 0}。
若属性值之间不存在序关系,则通常将其转化为向量的形式,例如:性别属性“男”“女”,可转化为{(1,0),(0,1)}。

在进行距离度量时,易知连续属性和存在序关系的离散属性都可以直接参与计算,因为它们都可以反映一种程度,我们称其为“有序属性”;而对于不存在序关系的离散属性,我们称其为:“无序属性”,显然无序属性再使用闵可夫斯基距离就行不通了。

对于无序属性,我们一般采用VDM进行距离的计算,例如:对于离散属性的两个取值a和b,定义:

5.png

于是,在计算两个样本之间的距离时,我们可以将闵可夫斯基距离和VDM混合在一起进行计算:

6.png

若我们定义的距离计算方法是用来度量相似性,例如下面将要讨论的聚类问题,即距离越小,相似性越大,反之距离越大,相似性越小。这时距离的度量方法并不一定需要满足前面所说的四个基本性质,这样的方法称为:非度量距离(non-metric distance)

性能度量

由于聚类算法不依赖于样本的真实类标,就不能像监督学习的分类那般,通过计算分对分错(即精确度或错误率)来评价学习器的好坏或作为学习过程中的优化目标。一般聚类有两类性能度量指标:外部指标内部指标

外部指标

即将聚类结果与某个参考模型的结果进行比较,以参考模型的输出作为标准,来评价聚类好坏。假设聚类给出的结果为λ,参考模型给出的结果是λ*,则我们将样本进行两两配对,定义:

7.png

显然a和b代表着聚类结果好坏的正能量,b和c则表示参考结果和聚类结果相矛盾,基于这四个值可以导出以下常用的外部评价指标:

8.png

内部指标

内部指标即不依赖任何外部模型,直接对聚类的结果进行评估,聚类的目的是想将那些相似的样本尽可能聚在一起,不相似的样本尽可能分开,直观来说:簇内高内聚紧紧抱团,簇间低耦合老死不相往来。定义:

9.png

基于上面的四个距离,可以导出下面这些常用的内部评价指标:

10.png

原型聚类

原型聚类即“基于原型的聚类”(prototype-based clustering),原型表示模板的意思,就是通过参考一个模板向量或模板分布的方式来完成聚类的过程,常见的K-Means便是基于簇中心来实现聚类,混合高斯聚类则是基于簇分布来实现聚类。

K-Means

K-Means的思想十分简单,首先随机指定类中心,根据样本与类中心的远近划分类簇,接着重新计算类中心,迭代直至收敛。但是其中迭代的过程并不是主观地想象得出,事实上,若将样本的类别看做为“隐变量”(latent variable),类中心看作样本的分布参数,这一过程正是通过EM算法的两步走策略而计算出,其根本的目的是为了最小化平方误差函数E:

11.png

K-Means的算法流程如下所示:

12.png

高斯混合聚类

现在可以看出K-Means与LVQ都试图以类中心作为原型指导聚类,高斯混合聚类则采用高斯分布来描述原型。现假设每个类簇中的样本都服从一个多维高斯分布,那么空间中的样本可以看作由k个多维高斯分布混合而成

对于多维高斯分布,其概率密度函数如下所示:

14.png

其中u表示均值向量,∑表示协方差矩阵,可以看出一个多维高斯分布完全由这两个参数所确定。接着定义高斯混合分布为:

15.png

α称为混合系数,这样空间中样本的采集过程则可以抽象为:(1)先选择一个类簇(高斯分布),(2)再根据对应高斯分布的密度函数进行采样,这时候贝叶斯公式又能大展身手了:

16.png

此时只需要选择PM最大时的类簇并将该样本划分到其中,看到这里很容易发现:这和那个传说中的贝叶斯分类不是神似吗,都是通过贝叶斯公式展开,然后计算类先验概率和类条件概率。但遗憾的是:这里没有真实类标信息,对于类条件概率,并不能像贝叶斯分类那样通过最大似然法美好地计算出来,因为这里的样本可能属于所有的类簇,这里的似然函数变为:

17.png

可以看出:简单的最大似然法根本无法求出所有的参数,这样PM也就没法计算。这里就要召唤出之前的EM大法,首先对高斯分布的参数及混合系数进行随机初始化,计算出各个PM(即γji,第i个样本属于j类),再最大化似然函数(即LL(D)分别对α、u和∑求偏导 ),对参数进行迭代更新

18.png

高斯混合聚类的算法流程如下图所示:

19.png

密度聚类

密度聚类则是基于密度的聚类,它从样本分布的角度来考察样本之间的可连接性,并基于可连接性(密度可达)不断拓展疆域(类簇)。其中最著名的便是DBSCAN算法,首先定义以下概念:

20.png

21.png

简单来理解DBSCAN便是:找出一个核心对象所有密度可达的样本集合形成簇。首先从数据集中任选一个核心对象A,找出所有A密度可达的样本集合,将这些样本形成一个密度相连的类簇,直到所有的核心对象都遍历完。DBSCAN算法的流程如下图所示:

22.png

层次聚类

层次聚类是一种基于树形结构的聚类方法,常用的是自底向上的结合策略(AGNES算法)。假设有N个待聚类的样本,其基本步骤是:

  • 1.初始化–>把每个样本归为一类,计算每两个类之间的距离,也就是样本与样本之间的相似度;
  • 2.寻找各个类之间最近的两个类,把他们归为一类(这样类的总数就少了一个);
  • 3.重新计算新生成的这个类与各个旧类之间的相似度
  • 4.重复2和3直到所有样本点都归为一类,结束。

可以看出其中最关键的一步就是计算两个类簇的相似度,这里有多种度量方法:

* 单链接(single-linkage):取类间最小距离。

23.png

* 全链接(complete-linkage):取类间最大距离

24.png

* 均链接(average-linkage):取类间两两的平均距离

25.png

很容易看出:单链接的包容性极强,稍微有点暧昧就当做是自己人了,全链接则是坚持到底,只要存在缺点就坚决不合并,均连接则是从全局出发顾全大局。层次聚类法的算法流程如下所示:

26.png
avatar

机器学习中的PCA降维

资料fromPCA的数学原理

avatar

贝叶斯分类器

参考阅读材料:

朴素贝叶斯

  • 贝叶斯公式 + 条件独立假设
  • 平滑算法

机器学习中的贝叶斯分类器

贝叶斯分类器是一种概率框架下的统计学习分类器,对分类任务而言,假设在相关概率都已知的情况下,贝叶斯分类器考虑如何基于这些概率为样本判定最优的类标。在开始介绍贝叶斯决策论之前,我们首先来回顾下概率论委员会常委–贝叶斯公式。

1.png

贝叶斯决策论

若将上述定义中样本空间的划分Bi看做为类标,A看做为一个新的样本,则很容易将条件概率理解为样本A是类别Bi的概率。在机器学习训练模型的过程中,往往我们都试图去优化一个风险函数,因此在概率框架下我们也可以为贝叶斯定义“条件风险”(conditional risk)。

2.png

我们的任务就是寻找一个判定准则最小化所有样本的条件风险总和,因此就有了贝叶斯判定准则(Bayes decision rule):为最小化总体风险,只需在每个样本上选择那个使得条件风险最小的类标。

3.png

若损失函数λ取0-1损失,则有:

4.png

即对于每个样本x,选择其后验概率P(c | x)最大所对应的类标,能使得总体风险函数最小,从而将原问题转化为估计后验概率P(c | x)。一般这里有两种策略来对后验概率进行估计:

* 判别式模型:直接对 P(c | x)进行建模求解。例我们前面所介绍的决策树、神经网络、SVM都是属于判别式模型。
* 生成式模型:通过先对联合分布P(x,c)建模,从而进一步求解 P(c | x)。

贝叶斯分类器就属于生成式模型,基于贝叶斯公式对后验概率P(c | x) 进行一项神奇的变换,巴拉拉能量…. P(c | x)变身:

5.png

对于给定的样本x,P(x)与类标无关,P(c)称为类先验概率,p(x | c )称为类条件概率。这时估计后验概率P(c | x)就变成为估计类先验概率和类条件概率的问题。对于先验概率和后验概率,在看这章之前也是模糊了我好久,这里普及一下它们的基本概念。

* 先验概率: 根据以往经验和分析得到的概率。
* 后验概率:后验概率是基于新的信息,修正原来的先验概率后所获得的更接近实际情况的概率估计。

实际上先验概率就是在没有任何结果出来的情况下估计的概率,而后验概率则是在有一定依据后的重新估计,直观意义上后验概率就是条件概率。下面直接上Wiki上的一个例子,简单粗暴快速完事…

6.png

回归正题,对于类先验概率P(c),p(c)就是样本空间中各类样本所占的比例,根据大数定理(当样本足够多时,频率趋于稳定等于其概率),这样当训练样本充足时,p(c)可以使用各类出现的频率来代替。因此只剩下类条件概率p(x | c ),它表达的意思是在类别c中出现x的概率,它涉及到属性的联合概率问题,若只有一个离散属性还好,当属性多时采用频率估计起来就十分困难,因此这里一般采用极大似然法进行估计。

极大似然法

极大似然估计(Maximum Likelihood Estimation,简称MLE),是一种根据数据采样来估计概率分布的经典方法。常用的策略是先假定总体具有某种确定的概率分布,再基于训练样本对概率分布的参数进行估计。运用到类条件概率p(x | c )中,假设p(x | c )服从一个参数为θ的分布,问题就变为根据已知的训练样本来估计θ。极大似然法的核心思想就是:估计出的参数使得已知样本出现的概率最大,即使得训练数据的似然最大。

7.png

所以,贝叶斯分类器的训练过程就是参数估计。总结最大似然法估计参数的过程,一般分为以下四个步骤:

* 1.写出似然函数;
* 2.对似然函数取对数,并整理;
* 3.求导数,令偏导数为0,得到似然方程组;
* 4.解似然方程组,得到所有参数即为所求。

例如:假设样本属性都是连续值,p(x | c )服从一个多维高斯分布,则通过MLE计算出的参数刚好分别为:

8.png

上述结果看起来十分合乎实际,但是采用最大似然法估计参数的效果很大程度上依赖于作出的假设是否合理,是否符合潜在的真实数据分布。这就需要大量的经验知识,搞统计越来越值钱也是这个道理,大牛们掐指一算比我们搬砖几天更有效果。

朴素贝叶斯分类器

不难看出:原始的贝叶斯分类器最大的问题在于联合概率密度函数的估计,首先需要根据经验来假设联合概率分布,其次当属性很多时,训练样本往往覆盖不够,参数的估计会出现很大的偏差。为了避免这个问题,朴素贝叶斯分类器(naive Bayes classifier)采用了“属性条件独立性假设”,即样本数据的所有属性之间相互独立。这样类条件概率p(x | c )可以改写为:

9.png

这样,为每个样本估计类条件概率变成为每个样本的每个属性估计类条件概率。

10.png

相比原始贝叶斯分类器,朴素贝叶斯分类器基于单个的属性计算类条件概率更加容易操作,需要注意的是:若某个属性值在训练集中和某个类别没有一起出现过,这样会抹掉其它的属性信息,因为该样本的类条件概率被计算为0。因此在估计概率值时,常常用进行平滑(smoothing)处理,拉普拉斯修正(Laplacian correction)就是其中的一种经典方法,具体计算方法如下:

11.png

当训练集越大时,拉普拉斯修正引入的影响越来越小。对于贝叶斯分类器,模型的训练就是参数估计,因此可以事先将所有的概率储存好,当有新样本需要判定时,直接查表计算即可。

决策树与随机森林

机器学习中的决策树模型

  • ① 树模型不用做scaling
  • ② 树模型不太需要做离散化
  • ③ 用Xgboost等工具库,是不需要做缺失值填充
  • ④ 树模型是非线性模型,有非线性的表达能力

决策树基本概念

  • 决策时是一种树形结构,其中每个内部节点表示在一个属性上的测试,每个分支代表一个测试输出,每个叶节点代表一种类别
  • 决策树学习是以实例为基础的归纳学习
  • 决策树学习采用的是自顶向下的归纳方法,其基本思想是以信息熵为度量构造一棵熵值下降最快的树,到叶子节点处的熵值为0,此时每个叶子节点中的实例都属于同一类。

顾名思义,决策树是基于树结构来进行决策的,,在网上看到一个例子十分有趣,放在这里正好合适。现想象一位捉急的母亲想要给自己的女娃介绍一个男朋友,于是有了下面的对话:


女儿:多大年纪了?
母亲:26。
女儿:长的帅不帅?
母亲:挺帅的。
女儿:收入高不?
母亲:不算很高,中等情况。
女儿:是公务员不?
母亲:是,在税务局上班呢。
女儿:那好,我去见见。

这个女孩的挑剔过程就是一个典型的决策树,即相当于通过年龄、长相、收入和是否公务员将男童鞋分为两个类别:见和不见。假设这个女孩对男人的要求是:30岁以下、长相中等以上并且是高收入者或中等以上收入的公务员,那么使用下图就能很好地表示女孩的决策逻辑(即一颗决策树)。

1.png

在上图的决策树中,决策过程的每一次判定都是对某一属性的“测试”,决策最终结论则对应最终的判定结果。一般一颗决策树包含:一个根节点、若干个内部节点和若干个叶子节点,易知:

* 每个非叶节点表示一个特征属性测试。
* 每个分支代表这个特征属性在某个值域上的输出。
* 每个叶子节点存放一个类别。
* 每个节点包含的样本集合通过属性测试被划分到子节点中,根节点包含样本全集。

决策树的构造

决策树的构造是一个递归的过程,有三种情形会导致递归返回:(1) 当前结点包含的样本全属于同一类别,这时直接将该节点标记为叶节点,并设为相应的类别;(2) 当前属性集为空,或是所有样本在所有属性上取值相同,无法划分,这时将该节点标记为叶节点,并将其类别设为该节点所含样本最多的类别;(3) 当前结点包含的样本集合为空,不能划分,这时也将该节点标记为叶节点,并将其类别设为父节点中所含样本最多的类别。算法的基本流程如下图所示:

2.png

可以看出:决策树学习的关键在于如何选择划分属性,不同的划分属性得出不同的分支结构,从而影响整颗决策树的性能。属性划分的目标是让各个划分出来的子节点尽可能地“纯”,即属于同一类别。因此下面便是介绍量化纯度的具体方法,决策树最常用的算法有三种:ID3,C4.5和CART。##

决策树学习算法的特点

决策树学习算法最大的优点是,它可以自学习。在学习的过程中,不需要使用者了解过多背景知识,只需要对训练实例进行较好的标注,就能进行学习。显然,属于有监督学习。从一类无序、无规则的事物中推理出决策树表示分类的规则。

ID3算法

ID3算法使用信息增益为准则来选择划分属性,“信息熵”(information entropy)是度量样本结合纯度的常用指标,假定当前样本集合D中第k类样本所占比例为pk,则样本集合D的信息熵定义为:

3.png

假定通过属性划分样本集D,产生了V个分支节点,v表示其中第v个分支节点,易知:分支节点包含的样本数越多,表示该分支节点的影响力越大。故可以计算出划分后相比原始数据集D获得的“信息增益”(information gain)。

4.png

Ent(D)划分前的信息增益 -划分后的信息增益

DV/D表示第V个分支的权重,样本越多越重要

信息增益越大,表示使用该属性划分样本集D的效果越好,因此ID3算法在递归过程中,每次选择最大信息增益的属性作为当前的划分属性。

C4.5算法

ID3算法存在一个问题,就是偏向于取值数目较多的属性,例如:如果存在一个唯一标识,这样样本集D将会被划分为|D|个分支,每个分支只有一个样本,这样划分后的信息熵为零,十分纯净,但是对分类毫无用处。因此C4.5算法使用了“增益率”(gain ratio)来选择划分属性,来避免这个问题带来的困扰。首先使用ID3算法计算出信息增益高于平均水平的候选属性,接着C4.5计算这些候选属性的增益率,增益率定义为:

启发式:先从候选划分属性中找出信息增益高于平均水平的,再从中选取增益率最高的

5.png

CART算法

CART决策树使用“基尼指数”(Gini index)来选择划分属性,基尼指数反映的是从样本集D中随机抽取两个样本,其类别标记不一致的概率,因此Gini(D)越小越好,数据集D的纯度越高。基尼指数定义如下:

6.png

进而,使用属性α划分后的基尼指数为:

7.png

二分类视角看CART

  • 每一个产生分支的过程是一个二分类过程
  • 这个过程叫作“决策树桩”
  • 一棵CART是由许多决策树桩拼接起来的
  • 决策树桩是只有一层的决策树

三种不同的决策树

  • ID3:取值多的属性,更容易使数据更纯,其信息增益更大;训练得到的是一棵庞大且深度浅的树:不合理
  • C4.5:采用信息增益率替代信息增益
  • CART:以基尼系数替代熵;最小化不纯度,而不是最大化信息增益

剪枝处理

从决策树的构造流程中我们可以直观地看出:不管怎么样的训练集,决策树总是能很好地将各个类别分离开来,这时就会遇到之前提到过的问题:过拟合(overfitting),即太依赖于训练样本。剪枝(pruning)则是决策树算法对付过拟合的主要手段,剪枝的策略有两种如下:

* 预剪枝(prepruning):在构造的过程中先评估,再考虑是否分支。
* 后剪枝(post-pruning):在构造好一颗完整的决策树后,自底向上,评估分支的必要性。

评估指的是性能度量,即决策树的泛化性能。之前提到:可以使用测试集作为学习器泛化性能的近似,因此可以将数据集划分为训练集和测试集。预剪枝表示在构造数的过程中,对一个节点考虑是否分支时,首先计算决策树不分支时在测试集上的性能,再计算分支之后的性能,若分支对性能没有提升,则选择不分支(即剪枝)。后剪枝则表示在构造好一颗完整的决策树后,从最下面的节点开始,考虑该节点分支对模型的性能是否有提升,若无则剪枝,即将该节点标记为叶子节点,类别标记为其包含样本最多的类别。

8.png

9.png

10.png

上图分别表示不剪枝处理的决策树、预剪枝决策树和后剪枝决策树。预剪枝处理使得决策树的很多分支被剪掉,因此大大降低了训练时间开销,同时降低了过拟合的风险,但另一方面由于剪枝同时剪掉了当前节点后续子节点的分支,因此预剪枝“贪心”的本质阻止了分支的展开,在一定程度上带来了欠拟合的风险。而后剪枝则通常保留了更多的分支,因此采用后剪枝策略的决策树性能往往优于预剪枝,但其自底向上遍历了所有节点,并计算性能,训练时间开销相比预剪枝大大提升。

连续值与缺失值处理

对于连续值的属性,若每个取值作为一个分支则显得不可行,因此需要进行离散化处理,常用的方法为二分法,基本思想为:给定样本集D与连续属性α,二分法试图找到一个划分点t将样本集D在属性α上分为≤t与>t。

* 首先将α的所有取值按升序排列,所有相邻属性的均值作为候选划分点(n-1个,n为α所有的取值数目)。
* 计算每一个划分点划分集合D(即划分为两个分支)后的信息增益。
* 选择最大信息增益的划分点作为最优划分点。

11.png

现实中常会遇到不完整的样本,即某些属性值缺失。有时若简单采取剔除,则会造成大量的信息浪费,因此在属性值缺失的情况下需要解决两个问题:(1)如何选择划分属性。(2)给定划分属性,若某样本在该属性上缺失值,如何划分到具体的分支上。假定为样本集中的每一个样本都赋予一个权重,根节点中的权重初始化为1,则定义:

12.png

对于(1):通过在样本集D中选取在属性α上没有缺失值的样本子集,计算在该样本子集上的信息增益,最终的信息增益等于该样本子集划分后信息增益乘以样本子集占样本集的比重。即:

13.png

对于(2):若该样本子集在属性α上的值缺失,则将该样本以不同的权重(即每个分支所含样本比例)划入到所有分支节点中。该样本在分支节点中的权重变为:

14.png

Bootstraping

称为自助法,它是一种有放回的抽样方法

####Bagging的策略

  • bootstrap aggregation
  • 从样本集中重采样(有重复的)选出n个样本
  • 在所有属性上,对这n个样本建立分类器(ID3、C4.5、CART、SVM、Logistic回归等)
  • 重复以上两步m次,即获得了m个分类器
  • 将数据放在这m个分类器上,最后根据这m个分类器的投票结果,决定数据属于哪一类

OOB数据

可以发现,Bootstrap每次约有36.79%的样本不会出现在Bootstrap所采集的样本集合中,将未参与模型训练的数据称为袋外数据(out of bag)。它可以用于取代测试集用于误差估计。得到的模型参数是无偏估计。

随机森林

随机森林在bagging基础上做了修改

  • 从样本集中用bootstrap采样选出n个样本
  • 从所有属性中随机选择k个属性,选择最佳分割属性作为节点建立CART决策树
  • 重复以上两步m次,即建立m课CART决策树
  • 这m个CART形成随机森林,通过投票表决结果,决定数据属于哪一类

随机森林/bagging和决策树的关系

  • 当然可以使用决策树作为基本分类器
  • 但也可以使用SVM、Logistics回归等其他分类,习惯上,这些分类器组成的“总分类器”,仍然叫做随机森林

机器学习逻辑回归与softmax

机器学习逻辑回归与softmax

机器学习中的线性模型

谈及线性模型,其实我们很早就已经与它打过交道,还记得高中数学必修3课本中那个顽皮的“最小二乘法”吗?这就是线性模型的经典算法之一:根据给定的(x,y)点对,求出一条与这些点拟合效果最好的直线y=ax+b,之前我们利用下面的公式便可以计算出拟合直线的系数a,b(3.1中给出了具体的计算过程),从而对于一个新的x,可以预测它所对应的y值。前面我们提到:在机器学习的术语中,当预测值为连续值时,称为“回归问题”,离散值时为“分类问题”。本篇先从线性回归任务开始,接着讨论分类和多分类问题。

1.png

线性回归

线性回归问题就是试图学到一个线性模型尽可能准确地预测新样本的输出值,例如:通过历年的人口数据预测2017年人口数量。在这类问题中,往往我们会先得到一系列的有标记数据,例如:2000–>13亿…2016–>15亿,这时输入的属性只有一个,即年份;也有输入多属性的情形,假设我们预测一个人的收入,这时输入的属性值就不止一个了,例如:(学历,年龄,性别,颜值,身高,体重)–>15k。

有时这些输入的属性值并不能直接被我们的学习模型所用,需要进行相应的处理,对于连续值的属性,一般都可以被学习器所用,有时会根据具体的情形作相应的预处理,例如:归一化等;对于离散值的属性,可作下面的处理:

  • 若属性值之间存在“序关系”,则可以将其转化为连续值,例如:身高属性分为“高”“中等”“矮”,可转化为数值:{1, 0.5, 0}。

  • 若属性值之间不存在“序关系”,则通常将其转化为向量的形式,例如:性别属性分为“男”“女”,可转化为二维向量:{(1,0),(0,1)}。

(1)当输入属性只有一个的时候,就是最简单的情形,也就是我们高中时最熟悉的“最小二乘法”(Euclidean distance),首先计算出每个样本预测值与真实值之间的误差并求和,通过最小化均方误差MSE,使用求偏导等于零的方法计算出拟合直线y=wx+b的两个参数w和b,计算过程如下图所示:

2.png

(2)当输入属性有多个的时候,例如对于一个样本有d个属性{(x1,x2…xd),y},则y=wx+b需要写成:

0.png

通常对于多元问题,常常使用矩阵的形式来表示数据。在本问题中,将具有m个样本的数据集表示成矩阵X,将系数w与b合并成一个列向量,这样每个样本的预测值以及所有样本的均方误差最小化就可以写成下面的形式:

3.png

4.png

5.png

avatar

avatar

avatar

avatar

最小二乘法

同样地,我们使用最小二乘法对w和b进行估计,令均方误差的求导等于0,需要注意的是,当一个矩阵的行列式不等于0时,我们才可能对其求逆,因此对于下式,我们需要考虑矩阵(X的转置*X)的行列式是否为0,若不为0,则可以求出其解,若为0,则需要使用其它的方法进行计算,书中提到了引入正则化,此处不进行深入。

6.png

然而现实任务中当特征数量大于样本数时,XTX不满秩,此时θ有多个解;而且当数据量大时,求矩阵的逆非常耗时;对于不可逆矩阵(特征之间不相互独立),这种正规方程方法是不能用的。所以,还可以采用梯度下降法,利用迭代的方式求解θ。

梯度下降法

梯度下降法是按下面的流程进行的:
1)首先对θ赋值,这个值可以是随机的,也可以让θ是一个全零的向量。
2)改变θ的值,使得θ按梯度下降的方向进行减少。

avatar

对于只有两维属性的样本,J(θ)即J(θ0,θ1)的等高线图

avatar

avatar

迭代更新的方式有多种

  • 批量梯度下降(batch gradient descent),也就是是梯度下降法最原始的形式,对全部的训练数据求得误差后再对θ
    进行更新,优点是每步都趋向全局最优解;缺点是对于大量数据,由于每步要计算整体数据,训练过程慢;
  • 随机梯度下降(stochastic gradient descent),每一步随机选择一个样本对θ
    进行更新,优点是训练速度快;缺点是每次的前进方向不好确定,容易陷入局部最优;
  • 微型批量梯度下降(mini-batch gradient descent),每步选择一小批数据进行批量梯度下降更新θ
    ,属于批量梯度下降和随机梯度下降的一种折中,非常适合并行处理。

另一方面,有时像上面这种原始的线性回归可能并不能满足需求,例如:y值并不是线性变化,而是在指数尺度上变化。这时我们可以采用线性模型来逼近y的衍生物,例如lny,这时衍生的线性模型如下所示,实际上就是相当于将指数曲线投影在一条直线上,如下图所示:

7.png

更一般地,考虑所有y的衍生物的情形,就得到了“广义的线性模型”(generalized linear model),其中,g(*)称为联系函数(link function)。

8.png

对数几率回归

回归就是通过输入的属性值得到一个预测值,利用上述广义线性模型的特征,是否可以通过一个联系函数,将预测值转化为离散值从而进行分类呢?线性几率回归正是研究这样的问题。对数几率引入了一个对数几率函数(logistic function),将预测值投影到0-1之间,从而将线性回归问题转化为二分类问题。

9.png

10.png

若将y看做样本为正例的概率,(1-y)看做样本为反例的概率,则上式实际上使用线性回归模型的预测结果器逼近真实标记的对数几率。因此这个模型称为“对数几率回归”(logistic regression),也有一些书籍称之为“逻辑回归”。下面使用最大似然估计的方法来计算出w和b两个参数的取值,下面只列出求解的思路,不列出具体的计算过程。

11.png

12.png

线性判别分析

线性判别分析(Linear Discriminant Analysis,简称LDA),其基本思想是:将训练样本投影到一条直线上,使得同类的样例尽可能近,不同类的样例尽可能远。如图所示:

13.png14.png

想让同类样本点的投影点尽可能接近,不同类样本点投影之间尽可能远,即:让各类的协方差之和尽可能小,不用类之间中心的距离尽可能大。基于这样的考虑,LDA定义了两个散度矩阵。

  • 类内散度矩阵(within-class scatter matrix)

15.png

  • 类间散度矩阵(between-class scaltter matrix)

16.png

因此得到了LDA的最大化目标:“广义瑞利商”(generalized Rayleigh quotient)。

17.png

从而分类问题转化为最优化求解w的问题,当求解出w后,对新的样本进行分类时,只需将该样本点投影到这条直线上,根据与各个类别的中心值进行比较,从而判定出新样本与哪个类别距离最近。求解w的方法如下所示,使用的方法为λ乘子。

18.png

若将w看做一个投影矩阵,类似PCA的思想,则LDA可将样本投影到N-1维空间(N为类簇数),投影的过程使用了类别信息(标记信息),因此LDA也常被视为一种经典的监督降维技术。

回归与欠/过拟合

avatar

线性回归与正则化

avatar

多分类学习

现实中我们经常遇到不只两个类别的分类问题,即多分类问题,在这种情形下,我们常常运用“拆分”的策略,通过多个二分类学习器来解决多分类问题,即将多分类问题拆解为多个二分类问题,训练出多个二分类学习器,最后将多个分类结果进行集成得出结论。最为经典的拆分策略有三种:“一对一”(OvO)、“一对其余”(OvR)和“多对多”(MvM),核心思想与示意图如下所示。

  • OvO:给定数据集D,假定其中有N个真实类别,将这N个类别进行两两配对(一个正类/一个反类),从而产生N(N-1)/2个二分类学习器,在测试阶段,将新样本放入所有的二分类学习器中测试,得出N(N-1)个结果,最终通过投票产生最终的分类结果。

  • OvM:给定数据集D,假定其中有N个真实类别,每次取出一个类作为正类,剩余的所有类别作为一个新的反类,从而产生N个二分类学习器,在测试阶段,得出N个结果,若仅有一个学习器预测为正类,则对应的类标作为最终分类结果。

  • MvM:给定数据集D,假定其中有N个真实类别,每次取若干个类作为正类,若干个类作为反类(通过ECOC码给出,编码),若进行了M次划分,则生成了M个二分类学习器,在测试阶段(解码),得出M个结果组成一个新的码,最终通过计算海明/欧式距离选择距离最小的类别作为最终分类结果。

19.png

20.png

类别不平衡问题

类别不平衡(class-imbanlance)就是指分类问题中不同类别的训练样本相差悬殊的情况,例如正例有900个,而反例只有100个,这个时候我们就需要进行相应的处理来平衡这个问题。常见的做法有三种:

  1. 在训练样本较多的类别中进行“欠采样”(undersampling),比如从正例中采出100个,常见的算法有:EasyEnsemble。
  2. 在训练样本较少的类别中进行“过采样”(oversampling),例如通过对反例中的数据进行插值,来产生额外的反例,常见的算法有SMOTE。
  3. 直接基于原数据集进行学习,对预测值进行“再缩放”处理。其中再缩放也是代价敏感学习的基础。21.png

LR应用经验

LR实现简单高效易解释,计算速度快,易并行,在大规模数据情况下非常适用,更适合于应对数值型和标称型数据,主要适合解决线性可分的问题,但容易欠拟合,大多数情况下需要手动进行特征工程,构建组合特征,分类精度不高。

LR直接对分类可能性进行建模,无需事先假设数据分布,这样就避免了假设分布不准确所带来的问题
LR能以概率的形式输出,而非知识0,1判定,对许多利用概率辅助决策的任务很有用
对率函数任意阶可导,具有很好的数学性质,许多现有的数值优化算法都可以用来求最优解,训练速度快
适用情景:LR是很多分类算法的基础组件,它的好处是输出值自然地落在0到1之间,并且有概率意义。因为它本质上是一个线性的分类器,所以处理不好特征之间相关的情况。虽然效果一般,却胜在模型清晰,背后的概率学经得住推敲。它拟合出来的参数就代表了每一个特征(feature)对结果的影响。也是一个理解数据的好工具。

应用上:

  • CTR预估,推荐系统的learning to rank,各种分类场景
  • 某搜索引擎厂的广告CTR预估基线版是LR
  • 某电商搜索排序基线版是LR
  • 某新闻app排序基线版是LR

大规模工业实时数据,需要可解释性的金融数据,需要快速部署低耗时数据
LR就是简单,可解释,速度快,消耗资源少,分布式性能好

ADMM-LR:用ADMM求解LogisticRegression的优化方法称作ADMM_LR。ADMM算法是一种求解约束问题的最优化方法,它适用广泛。相比于SGD,ADMM在精度要求不高的情况下,在少数迭代轮数时就达到一个合理的精度,但是收敛到很精确的解则需要很多次迭代。

avatar

avatar

avatar

LR多分类推广 - Softmax回归

LR是一个传统的二分类模型,它也可以用于多分类任务,其基本思想是:将多分类任务拆分成若干个二分类任务,然后对每个二分类任务训练一个模型,最后将多个模型的结果进行集成以获得最终的分类结果。一般来说,可以采取的拆分策略有:

one vs one策略

  假设我们有N个类别,该策略基本思想就是不同类别两两之间训练一个分类器,这时我们一共会训练出img种不同的分类器。在预测时,我们将样本提交给所有的分类器,一共会获得N(N-1)个结果,最终结果通过投票产生。

one vs all策略

  该策略基本思想就是将第i种类型的所有样本作为正例,将剩下的所有样本作为负例,进行训练得到一个分类器。这样我们就一共可以得到N个分类器。在预测时,我们将样本提交给所有的分类器,一共会获得N个结果,我们选择其中概率值最大的那个作为最终分类结果。 img

softmax回归

  softmax是LR在多分类的推广。与LR一样,同属于广义线性模型。什么是Softmax函数?假设我们有一个数组A,img表示的是数组A中的第i个元素,那么这个元素的Softmax值就是

            img

也就是说,是该元素的指数,与所有元素指数和的比值。那么 softmax回归模型的假设函数又是怎么样的呢?

          img

由上式很明显可以得出,假设函数的分母其实就是对概率分布进行了归一化,使得所有类别的概率之和为1;也可以看出LR其实就是K=2时的Softmax。在参数获得上,我们可以采用one vs all策略获得K个不同的训练数据集进行训练,进而针对每一类别都会得到一组参数向量img。当测试样本特征向量img输入时,我们先用假设函数针对每一个类别img估算出概率值img。因此我们的假设函数将要输出一个K维的向量(向量元素和为1)来表示K个类别的估计概率,我们选择其中得分最大的类别作为该输入的预测类别。Softmax看起来和one vs all 的LR很像,它们最大的不同在与Softmax得到的K个类别的得分和为1,而one vs all的LR并不是。

softmax的代价函数

  类似于LR,其似然函数我们采用对数似然,故:

    img

加入img正则项的损失函数为:

    img

此处的img为符号函数。对于其参数的求解过程,我们依然采用梯度下降法。

softmax的梯度的求解

  正则化项的求导很简单,就等于img,下面我们主要讨论没有加正则项的损失函数的梯度求解,即

      img

的导数(梯度)。为了使得求解过程看起来简便、易于理解,我们仅仅只对于一个样本(x,y)情况(SGD)进行讨论,

    img

此时,我们令

    img

可以得到

    img

故:

img

所以,正则化之后的损失函数的梯度为

    img

然后通过梯度下降法最小化 \textstyle J(\theta),我们就能实现一个可用的 softmax 回归模型了。

多分类LR与Softmax回归

  有了多分类的处理方法,那么我们什么时候该用多分类LR?什么时候要用softmax呢?

总的来说,若待分类的类别互斥,我们就使用Softmax方法;若待分类的类别有相交,我们则要选用多分类LR,然后投票表决。

Softmax分类器

SVM是最常用的两个分类器之一,而另一个就是Softmax分类器,它的损失函数与SVM的损失函数不同。对于学习过二元逻辑回归分类器的读者来说,Softmax分类器就可以理解为逻辑回归分类器面对多个分类的一般化归纳。SVM将输出[公式]作为每个分类的评分(因为无定标,所以难以直接解释)。与SVM不同,Softmax的输出(归一化的分类概率)更加直观,并且从概率上可以解释,这一点后文会讨论。在Softmax分类器中,函数映射[公式]保持不变,但将这些评分值视为每个分类的未归一化的对数概率,并且将折叶损失(hinge loss)替换为交叉熵损失cross-entropy loss)。公式如下:

[公式] 或等价的 [公式]

在上式中,使用[公式]来表示分类评分向量[公式]中的第j个元素。和之前一样,整个数据集的损失值是数据集中所有样本数据的损失值[公式]的均值与正则化损失[公式]之和。其中函数[公式]被称作softmax 函数:其输入值是一个向量,向量中元素为任意实数的评分值([公式]中的),函数对其进行压缩,输出一个向量,其中每个元素值在0到1之间,且所有元素之和为1。所以,包含softmax函数的完整交叉熵损失看起唬人,实际上还是比较容易理解的。

信息理论视角:在“真实”分布[公式]和估计分布[公式]之间的交叉熵定义如下:

[公式]

*译者注:Kullback-Leibler差异(Kullback-Leibler Divergence)也叫做相对熵(Relative Entropy),它衡量的是相同事件空间里的两个概率分布的差异情况。*

概率论解释:先看下面的公式:

[公式]

实操事项:数值稳定。编程实现softmax函数计算的时候,中间项[公式][公式]因为存在指数函数,所以数值可能非常大。除以大数值可能导致数值计算的不稳定,所以学会使用归一化技巧非常重要。如果在分式的分子和分母都乘以一个常数[公式],并把它变换到求和之中,就能得到一个从数学上等价的公式:

[公式]

1
2
3
4
5
6
f = np.array([123, 456, 789]) # 例子中有3个分类,每个评分的数值都很大
p = np.exp(f) / np.sum(np.exp(f)) # 不妙:数值问题,可能导致数值爆炸

# 那么将f中的值平移到最大值为0:
f -= np.max(f) # f becomes [-666, -333, 0]
p = np.exp(f) / np.sum(np.exp(f)) # 现在OK了,将给出正确结果

让人迷惑的命名规则:精确地说,SVM分类器使用的是折叶损失(hinge loss),有时候又被称为最大边界损失(max-margin loss)。Softmax分类器使用的是交叉熵损失(corss-entropy loss)。Softmax分类器的命名是从softmax函数那里得来的,softmax函数将原始分类评分变成正的归一化数值,所有数值和为1,这样处理后交叉熵损失才能应用。注意从技术上说“softmax损失(softmax loss)”是没有意义的,因为softmax只是一个压缩数值的函数。但是在这个说法常常被用来做简称。

SVM和Softmax的比较

下图有助于区分这 Softmax和SVM这两种分类器:

————————————————————————————————————————

img

针对一个数据点,SVM和Softmax分类器的不同处理方式的例子。两个分类器都计算了同样的分值向量f(本节中是通过矩阵乘来实现)。不同之处在于对f中分值的解释:SVM分类器将它们看做是分类评分,它的损失函数鼓励正确的分类(本例中是蓝色的类别2)的分值比其他分类的分值高出至少一个边界值。Softmax分类器将这些数值看做是每个分类没有归一化的对数概率,鼓励正确分类的归一化的对数概率变高,其余的变低。SVM的最终的损失值是1.58,Softmax的最终的损失值是0.452,但要注意这两个数值没有可比性。只在给定同样数据,在同样的分类器的损失值计算中,它们才有意义。

————————————————————————————————————————

Softmax分类器为每个分类提供了“可能性”:SVM的计算是无标定的,而且难以针对所有分类的评分值给出直观解释。Softmax分类器则不同,它允许我们计算出对于所有分类标签的可能性。举个例子,针对给出的图像,SVM分类器可能给你的是一个[12.5, 0.6, -23.0]对应分类“猫”,“狗”,“船”。而softmax分类器可以计算出这三个标签的”可能性“是[0.9, 0.09, 0.01],这就让你能看出对于不同分类准确性的把握。为什么我们要在”可能性“上面打引号呢?这是因为可能性分布的集中或离散程度是由正则化参数λ直接决定的,λ是你能直接控制的一个输入参数。举个例子,假设3个分类的原始分数是[1, -2, 0],那么softmax函数就会计算:

[公式]

现在,如果正则化参数λ更大,那么权重W就会被惩罚的更多,然后他的权重数值就会更小。这样算出来的分数也会更小,假设小了一半吧[0.5, -1, 0],那么softmax函数的计算就是:

[公式]

现在看起来,概率的分布就更加分散了。还有,随着正则化参数λ不断增强,权重数值会越来越小,最后输出的概率会接近于均匀分布。这就是说,softmax分类器算出来的概率最好是看成一种对于分类正确性的自信。和SVM一样,数字间相互比较得出的大小顺序是可以解释的,但其绝对值则难以直观解释

在实际使用中,SVM和Softmax经常是相似的:通常说来,两种分类器的表现差别很小,不同的人对于哪个分类器更好有不同的看法。相对于Softmax分类器,SVM更加“局部目标化(local objective)”,这既可以看做是一个特性,也可以看做是一个劣势。考虑一个评分是[10, -2, 3]的数据,其中第一个分类是正确的。那么一个SVM([公式])会看到正确分类相较于不正确分类,已经得到了比边界值还要高的分数,它就会认为损失值是0。SVM对于数字个体的细节是不关心的:如果分数是[10, -100, -100]或者[10, 9, 9],对于SVM来说没设么不同,只要满足超过边界值等于1,那么损失值就等于0。

对于softmax分类器,情况则不同。对于[10, 9, 9]来说,计算出的损失值就远远高于[10, -100, -100]的。换句话来说,softmax分类器对于分数是永远不会满意的:正确分类总能得到更高的可能性,错误分类总能得到更低的可能性,损失值总是能够更小。但是,SVM只要边界值被满足了就满意了,不会超过限制去细微地操作具体分数。这可以被看做是SVM的一种特性。举例说来,一个汽车的分类器应该把他的大量精力放在如何分辨小轿车和大卡车上,而不应该纠结于如何与青蛙进行区分,因为区分青蛙得到的评分已经足够低了。

文本分类问题

文本分类问题

下面我们来看一个文本分类问题,经典的新闻主题分类,用朴素贝叶斯怎么做。

In [193]:

1
2
3
4
5
6
import jieba
seg_list = jieba.cut("他来到东海识别区",cut_all=False)
print("/" .join(seg_list) )
jieba.add_word('东海识别区')
seg_list = jieba.cut("他来到东海识别区",cut_all=False)
print("/ " .join(seg_list) )
1
2
他/来到/东海/识别区
他/ 来到/ 东海识别区

In [175]:

1
2
3
4
5
6
7
8
9
10
11
12
#coding: utf-8
import os
import time
import random
import jieba #处理中文
import nltk #处理英文
import sklearn
from sklearn.naive_bayes import MultinomialNB
import numpy as np
import pylab as pl
import matplotlib.pyplot as plt
import nltk

文本处理

1、把训练样本划分为训练集和测试集

2、统计了词频,按词频降序生成词袋

In [137]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# 文本处理,也就是样本生成过程
def text_processing(folder_path, test_size=0.2):
folder_list = os.listdir(folder_path)
# os.listdir 方法用于返回路径下包含的文件或文件夹的名字的列表
# folder_list = ['C000008', 'C000014', 'C000013', 'C000022', 'C000023', 'C000024', 'C000010', 'C000020', 'C000016']

data_list = []
class_list = []

# 遍历文件夹,每个文件夹里是一个新闻的类别
for folder in folder_list:
new_folder_path = os.path.join(folder_path, folder)
# os.path.join就是把两个路径拼接
# new_folder_path = ./Database/SogouC/Sample/C000008

files = os.listdir(new_folder_path)
# 读取new_folder_path路径下的文件名
# ['15.txt', '14.txt', '16.txt', '17.txt', '13.txt', '12.txt', '10.txt', '11.txt', '19.txt', '18.txt']

# 读取文件
j = 1
for file in files:
if j > 100:
# 怕内存爆掉,只取100个样本文件,你可以注释掉取完,
# 这里每个类别下只有10个样本,没事
break
with open(os.path.join(new_folder_path, file), 'r') as fp:
raw = fp.read()
# read() 返回值为str,每次读取整个文件,将文件所有内容放到一个字符串变量中
# readline() 返回值为str,每次只读取一行,每行的内容放在一个字符串变量中
# readlines() 返回值为list,一次读取整个文件,每行的内容放在一个字符串变量中作为列表的一个元素。

## 是的,随处可见的jieba中文分词
jieba.enable_parallel(4) # 开启并行分词模式,参数为并行进程数,不支持windows
word_cut = jieba.cut(raw, cut_all=False) # 精确模式,返回的结构是一个可迭代的genertor
word_list = list(word_cut) # genertor转化为list,每个词unicode格式
jieba.disable_parallel() # 关闭并行分词模式

data_list.append(word_list) #训练集list
#class_list.append(folder.decode('utf-8')) #类别,str.decode会报错
class_list.append(folder) #训练集的标签类别
j += 1



## 下面手动粗暴地划分训练集和测试集
data_class_list = zip(data_list, class_list) # zip 函数返回一个zip对象
data_class_list = list(data_class_list) # 需要用list转换成列表

random.shuffle(data_class_list) # shuffle随机打乱样本
index = int(len(data_class_list)*test_size)+1
train_list = data_class_list[index:]
test_list = data_class_list[:index]

train_data_list, train_class_list = zip(*train_list)
# 解压缩,文本和类别分开返回的是元组格式,可以在用list转换
test_data_list, test_class_list = zip(*test_list)
#以上划分训练集和测试集其实可以用sklearn自带的部分做
#train_data_list, test_data_list, train_class_list, test_class_list = \
#sklearn.model_selection.train_test_split(data_list, class_list, test_size=test_size)



# 统计词频放入all_words_dict
all_words_dict = {}
for word_list in train_data_list:
for word in word_list:
#if all_words_dict.has_key(word):已删除此方法
if word in all_words_dict:
all_words_dict[word] += 1
else:
all_words_dict[word] = 1
# all_words_dict={'\n': 1257, '\u3000': 1986, '有': 175, '江湖': 1,.....}
# 同样以上有现成的统计词频的API可以调用
# from collections import Counter
# Counter(train_data_list)

all_words_tuple_list = sorted(all_words_dict.items(), key=lambda f:f[1], reverse=True)
# 内建函数sorted第一个参数需为list,all_words_dict.items()转化为列表,键和值为元组
# key函数代表按元组的的词频排序,并降序返回结果
# all_words_tuple_list = [(',', 3424), ('的', 2527), ('\u3000', 1734), ('。', 1482),.....]

#all_words_list = list(zip(*all_words_tuple_list)[0]) 报错,需要修改
all_words_list,_ = zip(*all_words_tuple_list) # 解压缩
all_words_list = list(all_words_list)
# all_words_list = [',', '的', '\u3000', '。', '\n', '在', ' ', '、', '了', '“',.....]

return all_words_list, train_data_list, test_data_list, train_class_list, test_class_list

In [138]:

1
2
3
4
5
6
7
8
9
10
11
12
print ("start")

## 文本预处理
folder_path = './Database/SogouC/Sample'
all_words_list, train_data_list, test_data_list, train_class_list, test_class_list = \
text_processing(folder_path, test_size=0.2)
print(len(all_words_list)) # 9748个不重复单词
print(all_words_list[:100])
print(len(train_data_list)) # 71个训练集样本
print(len(test_data_list)) # 19个测试集样本
print(len(train_class_list)) # 71个训练集标签
print(len(test_class_list)) # 19个测试集标签
1
2
3
4
5
6
7
start
9481
[',', '的', '\u3000', '。', '\n', ' ', ';', '&', 'nbsp', '、', '在', '了', '“', '是', '”', '\x00', ':', '和', '中国', '有', '也', '我', '对', '就', '将', '—', '上', '这', '游客', '都', '旅游', '中', '不', '为', '要', '与', '年', '等', '而', ';', '可以', '月', '(', ')', '导弹', '大陆', '一个', '从', '人', '3', '到', '但', '你', '公司', '说', '火炮', '日', '(', ')', '他', '考生', '台军', '认为', '北京', '时', '多', '还', '个', '1', '.', '能', '《', '》', '已经', '解放军', '一种', '会', '时间', '自己', '来', '新', '各种', '大', '0', '5', '进行', '市场', '主要', '我们', '以', '后', '美国', '五一', '让', '支付', '黄金周', '增长', '并', '成为', '最']
71
19
71
19

停用词文件去重

这个停用词文件不是很官方,所以需要清洗下

In [140]:

1
2
3
4
5
6
7
8
9
10
# 粗暴的词去重
def make_word_set(words_file):
words_set = set() # 集合格式
with open(words_file, 'r') as fp:
for line in fp.readlines(): # 循环取出每一行
word = line.strip()
# line.strip() 当()为空时,默认删除空白符(包括'\n','\r','\t',' ')
if len(word)>0 and word not in words_set: # 去重
words_set.add(word)
return words_set

In [145]:

1
2
3
4
5
# 生成stopwords_set
stopwords_file = './stopwords_cn.txt' # 停用词列表文件
stopwords_set = make_word_set(stopwords_file) # 首先停用词去重
print(len(stopwords_set))
print(stopwords_set)
1
2
428
{'得了', '还是', '所在', '为此', '如同下', '并且', '许多', '但', '不尽然', '无', '却', '所', '据此', '分别', '向', '遵照', '多会', '而后', '如下', '再有', '的确', '此外', '距', '而已', '何处', '在于', '说来', '不料', '且', '于', '亦', '不单', '而是', '本人', '正如', '前者', '别', '才能', '啦', '只因', '受到', '甚至于', '另一方面', '此', '只有', '可是', '您', '别的', '别处', '些', '或', '不论', '这会', '其它', '况且', '有', '此次', '因此', '去', '毋宁', '它们', '根据', '基于', '当地', '依据', '然而', '虽然', '因为', '从而', '对比', '怎', '以上', '诸如', '倘若', '一些', '否则', '所以', '那边', '每', '并非', '之', '另', '简言之', '只限', '连同', '反之', '这般', '几', '往', '是', '既', '各自', '该', '因之', '得', '来', '不然', '么', '甚至', '为', '处在', '全部', '如上', '按照', '加之', '介于', '正巧', '好', '似的', '不过', '有些', '那样', '此地', '凡是', '每当', '不是', '万一', '由于', '曾', '而', '照着', '依照', '彼时', '就要', '然后', '以为', '只需', '为何', '谁', '到', '不仅仅', '既然', '当然', '起', '嗡', '此间', '果然', '与否', '随', '因', '值此', '即便', '格里斯', '趁着', '要不然', '那个', '至于', '乃', '随后', '有时', '何况', '当', '使', '只是', '为着', '可见', '即使', '以来', '着', '吧', '截至', '个', '为什么', '用', '除外', '他们', '随时', '这些', '还', '了', '还有', '啥', '甚而', '他', '但是', '并不', '某某', '如果说', '从', '各', '别人', '趁', '这里', '别说', '以', '不光', '其次', '就算', '沿着', '如果', '大家', '朝着', '正是', '对待', '自己', '不尽', '虽', '有关', '替代', '哟', '对于', '以及', '这儿', '加以', '一', '不外乎', '尽管如此', '个别', '再则', '哪', '她', '除此', '也', '自身', '用来', '不', '什么', '人们', '便于', '又', '此处', '非但', '如何', '哇', '那里', '只消', '既是', '凭', '的', '故而', '打', '嘻嘻', '对方', '谁人', '乃至', '以至', '再', '什么样', '何', '任何', '最', '由此', '而且', '自', '直到', '为了', '固然', '除了', '假如', '人', '才是', '据', '的话', '来自', '有的', '我们', '从此', '关于', '向着', '那么', '给', '或者', '某个', '等等', '光是', '怎么样', '嘿嘿', '如此', '只', '多少', '来说', '那般', '哪个', '那时', '首先', '赖以', '这边', '我', '于是', '另外', '她们', '已', '不如', '哪儿', '及', '及至', '很', '多么', '哪些', '又及', '其', '还要', '既往', '以致', '或者说', '如是', '不仅', '为止', '本着', '鉴于', '什么的', '而外', '譬如', '那儿', '咱们', '只要', '凭借', '后者', '则', '比如', '一切', '个人', '何以', '那', '咱', '上', '在', '如若', '他人', '一旦', '哪怕', '这样', '只限于', '仍', '之所以', '所有', '或是', '诸位', '总之', '怎么办', '其中', '怎么', '若', '作为', '怎样', '本身', '凡', '连带', '由', '不管', '不但', '只怕', '和', '看', '同', '把', '宁可', '那些', '彼此', '不只', '唯有', '继而', '呵呵', '正值', '至', '你们', '下', '跟', '针对', '并', '以免', '不至于', '经过', '你', '就是', '虽说', '小', '与其', '至今', '一来', '让', '们', '即', '诸', '要不', '沿', '出来', '两者', '此时', '遵循', '如', '这', '这么', '出于', '较之', '比', '嘛', '某些', '以便', '可以', '若非', '各位', '今', '逐步', '这个', '它', '例如', '其他', '反而', '就是说', '随着', '致', '同时', '可', '被', '接着', '靠', '除非', '某', '后', '尔', '其余', '与', '全体', '仍旧', '进而', '儿', '自从', '开外', '拿', '要是', '无论', '要么', '若是', '因而', '本地', '尽管', '何时'}

词袋中选取有代表性的特征词

第一步生成的词袋里有很多通用的、无意义的词语,需要去掉。
有代表性的词语很大概率是一些对最终类别区分有作用的词语。并且后面这些词语会作为特征作为模型的输入。

In [125]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def words_dict(all_words_list, deleteN, stopwords_set=set()):
# 选取特征词
feature_words = []
n = 1
for t in range(deleteN, len(all_words_list), 1):
# 循环时从第20个开始,也就是舍弃前20个词语
if n > 1000: # feature_words的维度1000
break

if not all_words_list[t].isdigit() and \
all_words_list[t] not in stopwords_set and \
1<len(all_words_list[t])<5:
# isdigit() 方法检测字符串是否只由数字组成,返回True和False
# 满足三个条件:不是数字;不在停用词表;长度2~4
feature_words.append(all_words_list[t])
n += 1
return feature_words

In [149]:

1
2
3
4
5
deleteN = 20 
# 删除前20个词语,可以调整这个数值
# 越靠前的词语出现的越频繁,有可能所有类别中都出现很多次,这类词语是可以去掉的。
feature_words = words_dict(all_words_list, deleteN, stopwords_set)=
print(feature_words[:100])
1
['游客', '旅游', '导弹', '大陆', '一个', '公司', '火炮', '考生', '台军', '认为', '北京', '已经', '解放军', '一种', '时间', '各种', '进行', '市场', '主要', '美国', '五一', '支付', '黄金周', '增长', '成为', '复习', '很多', '目前', '没有', '记者', '问题', '分析', '远程', '万人次', '射程', '接待', '基础', '部分', '部署', '作战', '一定', '选择', '辅导班', '考试', '词汇', '技术', '比赛', '文章', '完全', '可能', '收入', '工作', '时候', '今年', '表示', '期间', '企业', 'VS', '能力', '达到', '毕业生', '上海', '表现', '影响', '比较', '人数', '用户', '相对', '专家', '服务', '重要', '拥有', '需要', '训练', '开始', '销售', '通过', '阵地', '资料', '情况', '要求', '阅读', '老师', '新浪', '坦克', '网络', '军事', '英语', '项目', '历史', '设计', '几乎', '这是', '写作', '日本', '考古', '不同', '提高', '活动', '公里']

训练和测试集生成固定长度的词向量特征

这步为后面数据输入进贝叶斯模型训练做准备。

因为文本长度不一,所以每个样本需要固定好维度,才能喂给模型训练。

In [153]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 文本特征
def text_features(train_data_list, test_data_list, feature_words, flag='nltk'):
def text_features(text, feature_words): # text的定义在下面
text_words = set(text) # 样本去重
## -----------------------------------------------------------------------------------
if flag == 'nltk':
## nltk特征 dict
features = {word:1 if word in text_words else 0 for word in feature_words}
# 遍历每个样本词语,凡是样本的词语出现在1000个特征词里,就记录下来,保存为字典格式,键为词语,值为1,否则值为0。

elif flag == 'sklearn':
## sklearn特征 list
features = [1 if word in text_words else 0 for word in feature_words]
# 同上,遍历每个样本词语,结果不是字典,出现即为1,不出现为0

else:
features = []
## -----------------------------------------------------------------------------------
return features
train_feature_list = [text_features(text, feature_words) for text in train_data_list]
# text为每一个训练的样本,返回值是二维列表

test_feature_list = [text_features(text, feature_words) for text in test_data_list]
# train为每一个测试样本,返回值是二维列表

return train_feature_list, test_feature_list

In [169]:

1
2
3
4
5
6
7
flag = 'sklearn'
train_feature_list, test_feature_list = \
text_features(train_data_list, test_data_list, feature_words, flag)
print(len(train_feature_list)) #
print(len(test_feature_list)) #
print(len(test_feature_list[5])) # 每个样本的维度都是1000
print(test_feature_list[5][0:100]) # 打印测试集的第5个样本的前100个值
1
2
3
4
71
19
1000
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]

贝叶斯模型开始训练和预测

In [176]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 分类,同时输出准确率等
def text_classifier(train_feature_list, test_feature_list,
train_class_list, test_class_list, flag='nltk'):
## -----------------------------------------------------------------------------------
if flag == 'nltk':
## 使用nltk分类器
train_flist = zip(train_feature_list, train_class_list)
train_flist = list(train_flist)
test_flist = zip(test_feature_list, test_class_list)
train_flist = list(test_flist)
classifier = nltk.classify.NaiveBayesClassifier.train(train_flist)
test_accuracy = nltk.classify.accuracy(classifier, test_flist)

elif flag == 'sklearn':
## sklearn分类器
classifier = MultinomialNB().fit(train_feature_list, train_class_list)
# MultinomialNB()的使用方法和参数见:https://www.cnblogs.com/pinard/p/6074222.html

test_accuracy = classifier.score(test_feature_list, test_class_list)
else:
test_accuracy = []
return test_accuracy

In [177]:

1
2
3
4
flag='sklearn'
test_accuracy = text_classifier(train_feature_list, test_feature_list,
train_class_list, test_class_list, flag)
print(test_accuracy)
1
0.7368421052631579

可视化

这步调参,查看不同的deleteNs对模型效果的影响

In [179]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
print ("start")

## 文本预处理
folder_path = './Database/SogouC/Sample'


all_words_list, train_data_list, test_data_list, train_class_list, test_class_list = text_processing(folder_path, test_size=0.2)

# 生成stopwords_set
stopwords_file = './stopwords_cn.txt'
stopwords_set = make_word_set(stopwords_file)

## 文本特征提取和分类
# flag = 'nltk'
flag = 'sklearn'
deleteNs = range(0, 1000, 20)
test_accuracy_list = []
for deleteN in deleteNs:
# feature_words = words_dict(all_words_list, deleteN)
feature_words = words_dict(all_words_list, deleteN, stopwords_set)
train_feature_list, test_feature_list = text_features(train_data_list, test_data_list, feature_words, flag)
test_accuracy = text_classifier(train_feature_list, test_feature_list, train_class_list, test_class_list, flag)
test_accuracy_list.append(test_accuracy)
print (test_accuracy_list)

# 结果评价
#plt.figure()
plt.plot(deleteNs, test_accuracy_list)
plt.title('Relationship of deleteNs and test_accuracy')
plt.xlabel('deleteNs')
plt.ylabel('test_accuracy')
plt.show()
#plt.savefig('result.png')

print ("finished")
1
2
start
[0.6842105263157895, 0.6842105263157895, 0.7368421052631579, 0.7368421052631579, 0.7368421052631579, 0.7368421052631579, 0.7368421052631579, 0.6842105263157895, 0.6842105263157895, 0.6842105263157895, 0.6842105263157895, 0.6842105263157895, 0.6842105263157895, 0.7368421052631579, 0.7368421052631579, 0.7894736842105263, 0.7368421052631579, 0.7368421052631579, 0.7368421052631579, 0.7368421052631579, 0.7368421052631579, 0.7894736842105263, 0.7894736842105263, 0.7894736842105263, 0.7894736842105263, 0.7894736842105263, 0.7894736842105263, 0.7894736842105263, 0.7894736842105263, 0.7368421052631579, 0.6842105263157895, 0.6842105263157895, 0.6842105263157895, 0.7368421052631579, 0.7368421052631579, 0.6842105263157895, 0.7368421052631579, 0.6842105263157895, 0.6842105263157895, 0.7368421052631579, 0.6842105263157895, 0.631578947368421, 0.6842105263157895, 0.6842105263157895, 0.7368421052631579, 0.7894736842105263, 0.7894736842105263, 0.7368421052631579, 0.7368421052631579, 0.7894736842105263]

img

1
finished

机器学习基本概念

机器学习基本概念

1 机器学习

1.1 机器学习的定义

正如我们根据过去的经验来判断明天的天气,吃货们希望从购买经验中挑选一个好瓜,那能不能让计算机帮助人类来实现这个呢?机器学习正是这样的一门学科,人的“经验”对应计算机中的“数据”,让计算机来学习这些经验数据,生成一个算法模型,在面对新的情况中,计算机便能作出有效的判断,这便是机器学习。

另一本经典教材的作者Mitchell给出了一个形式化的定义,假设:

  • P:计算机程序在某任务类T上的性能。
  • T:计算机程序希望实现的任务类。
  • E:表示经验,即历史的数据集。

若该计算机程序通过利用经验E在任务T上获得了性能P的改善,则称该程序对E进行了学习。

1.2 机器学习的一些基本术语

假设我们收集了一批西瓜的数据,例如:(色泽=青绿;根蒂=蜷缩;敲声=浊响), (色泽=乌黑;根蒂=稍蜷;敲声=沉闷), (色泽=浅自;根蒂=硬挺;敲声=清脆)……每对括号内是一个西瓜的记录,定义:

  • 所有记录的集合为:数据集。

  • 每一条记录为:一个实例(instance)或样本(sample)。

  • 例如:色泽或敲声,单个的特点为特征(feature)或属性(attribute)。

  • 对于一条记录,如果在坐标轴上表示,每个西瓜都可以用坐标轴中的一个点表示,一个点也是一个向量,例如(青绿,蜷缩,浊响),即每个西瓜为:一个特征向量(feature vector)。

  • 一个样本的特征数为:维数(dimensionality),该西瓜的例子维数为3,当维数非常大时,也就是现在说的“维数灾难”。

    计算机程序学习经验数据生成算法模型的过程中,每一条记录称为一个“训练样本”,同时在训练好模型后,我们希望使用新的样本来测试模型的效果,则每一个新的样本称为一个“测试样本”。定义:

  • 所有训练样本的集合为:训练集(trainning set),[特殊]。

  • 所有测试样本的集合为:测试集(test set),[一般]。

  • 机器学习出来的模型适用于新样本的能力为:泛化能力(generalization),即从特殊到一般。

    西瓜的例子中,我们是想计算机通过学习西瓜的特征数据,训练出一个决策模型,来判断一个新的西瓜是否是好瓜。可以得知我们预测的是:西瓜是好是坏,即好瓜与差瓜两种,是离散值。同样地,也有通过历年的人口数据,来预测未来的人口数量,人口数量则是连续值。定义:

  • 预测值为离散值的问题为:分类(classification)。

  • 预测值为连续值的问题为:回归(regression)。

    我们预测西瓜是否是好瓜的过程中,很明显对于训练集中的西瓜,我们事先已经知道了该瓜是否是好瓜,学习器通过学习这些好瓜或差瓜的特征,从而总结出规律,即训练集中的西瓜我们都做了标记,称为标记信息。但也有没有标记信息的情形,例如:我们想将一堆西瓜根据特征分成两个小堆,使得某一堆的西瓜尽可能相似,即都是好瓜或差瓜,对于这种问题,我们事先并不知道西瓜的好坏,样本没有标记信息。定义:

  • 训练数据有标记信息的学习任务为:监督学习(supervised learning),容易知道上面所描述的分类和回归都是监督学习的范畴。

  • 训练数据没有标记信息的学习任务为:无监督学习(unsupervised learning),常见的有聚类和关联规则。

2 模型的评估与选择

2.1 误差与过拟合

我们将学习器对样本的实际预测结果与样本的真实值之间的差异成为:误差(error)。定义:

  • 在训练集上的误差称为训练误差(training error)或经验误差(empirical error)。
  • 在测试集上的误差称为测试误差(test error)。
  • 学习器在所有新样本上的误差称为泛化误差(generalization error)。

显然,我们希望得到的是在新样本上表现得很好的学习器,即泛化误差小的学习器。因此,我们应该让学习器尽可能地从训练集中学出普适性的“一般特征”,这样在遇到新样本时才能做出正确的判别。然而,当学习器把训练集学得“太好”的时候,即把一些训练样本的自身特点当做了普遍特征;同时也有学习能力不足的情况,即训练集的基本特征都没有学习出来。我们定义:

  • 学习能力过强,以至于把训练样本所包含的不太一般的特性都学到了,称为:过拟合(overfitting)。
  • 学习能太差,训练样本的一般性质尚未学好,称为:欠拟合(underfitting)。

可以得知:在过拟合问题中,训练误差十分小,但测试误差教大;在欠拟合问题中,训练误差和测试误差都比较大。目前,欠拟合问题比较容易克服,例如增加迭代次数等,但过拟合问题还没有十分好的解决方案,过拟合是机器学习面临的关键障碍。

2.2 评估方法

在现实任务中,我们往往有多种算法可供选择,那么我们应该选择哪一个算法才是最适合的呢?如上所述,我们希望得到的是泛化误差小的学习器,理想的解决方案是对模型的泛化误差进行评估,然后选择泛化误差最小的那个学习器。但是,泛化误差指的是模型在所有新样本上的适用能力,我们无法直接获得泛化误差。

因此,通常我们采用一个“测试集”来测试学习器对新样本的判别能力,然后以“测试集”上的“测试误差”作为“泛化误差”的近似。显然:我们选取的测试集应尽可能与训练集互斥,下面用一个小故事来解释why:

假设老师出了10 道习题供同学们练习,考试时老师又用同样的这10道题作为试题,可能有的童鞋只会做这10 道题却能得高分,很明显:这个考试成绩并不能有效地反映出真实水平。回到我们的问题上来,我们希望得到泛化性能好的模型,好比希望同学们课程学得好并获得了对所学知识”举一反三”的能力;训练样本相当于给同学们练习的习题,测试过程则相当于考试。显然,若测试样本被用作训练了,则得到的将是过于”乐观”的估计结果。

2.3 训练集与测试集的划分方法

如上所述:我们希望用一个“测试集”的“测试误差”来作为“泛化误差”的近似,因此我们需要对初始数据集进行有效划分,划分出互斥的“训练集”和“测试集”。下面介绍几种常用的划分方法:

2.3.1 留出法

将数据集D划分为两个互斥的集合,一个作为训练集S,一个作为测试集T,满足D=S∪T且S∩T=∅,常见的划分为:大约2/3-4/5的样本用作训练,剩下的用作测试。需要注意的是:训练/测试集的划分要尽可能保持数据分布的一致性,以避免由于分布的差异引入额外的偏差,常见的做法是采取分层抽样。同时,由于划分的随机性,单次的留出法结果往往不够稳定,一般要采用若干次随机划分,重复实验取平均值的做法。

2.3.2 交叉验证法

将数据集D划分为k个大小相同的互斥子集,满足D=D1∪D2∪…∪Dk,Di∩Dj=∅(i≠j),同样地尽可能保持数据分布的一致性,即采用分层抽样的方法获得这些子集。交叉验证法的思想是:每次用k-1个子集的并集作为训练集,余下的那个子集作为测试集,这样就有K种训练集/测试集划分的情况,从而可进行k次训练和测试,最终返回k次测试结果的均值。交叉验证法也称“k折交叉验证”,k最常用的取值是10,下图给出了10折交叉验证的示意图。

与留出法类似,将数据集D划分为K个子集的过程具有随机性,因此K折交叉验证通常也要重复p次,称为p次k折交叉验证,常见的是10次10折交叉验证,即进行了100次训练/测试。特殊地当划分的k个子集的每个子集中只有一个样本时,称为“留一法”,显然,留一法的评估结果比较准确,但对计算机的消耗也是巨大的。

2.3.3 自助法

我们希望评估的是用整个D训练出的模型。但在留出法和交叉验证法中,由于保留了一部分样本用于测试,因此实际评估的模型所使用的训练集比D小,这必然会引入一些因训练样本规模不同而导致的估计偏差。留一法受训练样本规模变化的影响较小,但计算复杂度又太高了。“自助法”正是解决了这样的问题。

自助法的基本思想是:给定包含m个样本的数据集D,每次随机从D 中挑选一个样本,将其拷贝放入D’,然后再将该样本放回初始数据集D 中,使得该样本在下次采样时仍有可能被采到。重复执行m 次,就可以得到了包含m个样本的数据集D’。可以得知在m次采样中,样本始终不被采到的概率取极限为:

这样,通过自助采样,初始样本集D中大约有36.8%的样本没有出现在D’中,于是可以将D’作为训练集,D-D’作为测试集。自助法在数据集较小,难以有效划分训练集/测试集时很有用,但由于自助法产生的数据集(随机抽样)改变了初始数据集的分布,因此引入了估计偏差。在初始数据集足够时,留出法和交叉验证法更加常用。

2.4 调参

大多数学习算法都有些参数(parameter) 需要设定,参数配置不同,学得模型的性能往往有显著差别,这就是通常所说的”参数调节”或简称”调参” (parameter tuning)。

学习算法的很多参数是在实数范围内取值,因此,对每种参数取值都训练出模型来是不可行的。常用的做法是:对每个参数选定一个范围和步长λ,这样使得学习的过程变得可行。例如:假定算法有3 个参数,每个参数仅考虑5 个候选值,这样对每一组训练/测试集就有555= 125 个模型需考察,由此可见:拿下一个参数(即经验值)对于算法人员来说是有多么的happy。

最后需要注意的是:当选定好模型和调参完成后,我们需要使用初始的数据集D重新训练模型,即让最初划分出来用于评估的测试集也被模型学习,增强模型的学习效果。用上面考试的例子来比喻:就像高中时大家每次考试完,要将考卷的题目消化掉(大多数题目都还是之前没有见过的吧?),这样即使考差了也能开心的玩耍了~。

3 机器学习评估与度量指标

这里的内容主要包括:性能度量、比较检验和偏差与方差。在上一个notebook中,我们解决了评估学习器泛化性能的方法,即用测试集的“测试误差”作为“泛化误差”的近似,当我们划分好训练/测试集后,那如何计算“测试误差”呢?这就是性能度量,例如:均方差,错误率等,即“测试误差”的一个评价标准。有了评估方法和性能度量,就可以计算出学习器的“测试误差”,但由于“测试误差”受到很多因素的影响,例如:算法随机性或测试集本身的选择,那如何对两个或多个学习器的性能度量结果做比较呢?这就是比较检验。最后偏差与方差是解释学习器泛化性能的一种重要工具。

3.1 性能度量

性能度量(performance measure)是衡量模型泛化能力的评价标准,在对比不同模型的能力时,使用不同的性能度量往往会导致不同的评判结果。本节除2.5.1外,其它主要介绍分类模型的性能度量。

3.1.1 最常见的性能度量

在回归任务中,即预测连续值的问题,最常用的性能度量是“均方误差”(mean squared error),很多的经典算法都是采用了MSE作为评价函数,想必大家都十分熟悉。

1.png

在分类任务中,即预测离散值的问题,最常用的是错误率和精度,错误率是分类错误的样本数占样本总数的比例,精度则是分类正确的样本数占样本总数的比例,易知:错误率+精度=1。

2.png

3.png

3.1.2 查准率/查全率/F1

错误率和精度虽然常用,但不能满足所有的需求,例如:在推荐系统中,我们只关心推送给用户的内容用户是否感兴趣(即查准率),或者说所有用户感兴趣的内容我们推送出来了多少(即查全率)。因此,使用查准/查全率更适合描述这类问题。对于二分类问题,分类结果混淆矩阵与查准/查全率定义如下:

4.png

初次接触时,FN与FP很难正确的理解,按照惯性思维容易把FN理解成:False->Negtive,即将错的预测为错的,这样FN和TN就反了,后来找到一张图,描述得很详细,为方便理解,把这张图也贴在了下边:

5.png

正如天下没有免费的午餐,查准率和查全率是一对矛盾的度量。例如我们想让推送的内容尽可能用户全都感兴趣,那只能推送我们把握高的内容,这样就漏掉了一些用户感兴趣的内容,查全率就低了;如果想让用户感兴趣的内容都被推送,那只有将所有内容都推送上,宁可错杀一千,不可放过一个,这样查准率就很低了。

“P-R曲线”正是描述查准/查全率变化的曲线,P-R曲线定义如下:根据学习器的预测结果(一般为一个实值或概率)对测试样本进行排序,将最可能是“正例”的样本排在前面,最不可能是“正例”的排在后面,按此顺序逐个把样本作为“正例”进行预测,每次计算出当前的P值和R值,如下图所示:

6.png

P-R曲线如何评估呢?若一个学习器A的P-R曲线被另一个学习器B的P-R曲线完全包住,则称:B的性能优于A。若A和B的曲线发生了交叉,则谁的曲线下的面积大,谁的性能更优。但一般来说,曲线下的面积是很难进行估算的,所以衍生出了“平衡点”(Break-Event Point,简称BEP),即当P=R时的取值,平衡点的取值越高,性能更优。

P和R指标有时会出现矛盾的情况,这样就需要综合考虑他们,最常见的方法就是F-Measure,又称F-Score。F-Measure是P和R的加权调和平均,即:

7.png

8.png

特别地,当β=1时,也就是常见的F1度量,是P和R的调和平均,当F1较高时,模型的性能越好。

9.png

10.png

有时候我们会有多个二分类混淆矩阵,例如:多次训练或者在多个数据集上训练,那么估算全局性能的方法有两种,分为宏观和微观。简单理解,宏观就是先算出每个混淆矩阵的P值和R值,然后取得平均P值macro-P和平均R值macro-R,在算出Fβ或F1,而微观则是计算出混淆矩阵的平均TP、FP、TN、FN,接着进行计算P、R,进而求出Fβ或F1。

11.png

3.1.3 ROC与AUC

如上所述:学习器对测试样本的评估结果一般为一个实值或概率,设定一个阈值,大于阈值为正例,小于阈值为负例,因此这个实值的好坏直接决定了学习器的泛化性能,若将这些实值排序,则排序的好坏决定了学习器的性能高低。ROC曲线正是从这个角度出发来研究学习器的泛化性能,ROC曲线与P-R曲线十分类似,都是按照排序的顺序逐一按照正例预测,不同的是ROC曲线以“真正例率”(True Positive Rate,简称TPR)为横轴,纵轴为“假正例率”(False Positive Rate,简称FPR),ROC偏重研究基于测试样本评估值的排序好坏。

12.png

13.png

简单分析图像,可以得知:当FN=0时,TN也必须0,反之也成立,我们可以画一个队列,试着使用不同的截断点(即阈值)去分割队列,来分析曲线的形状,(0,0)表示将所有的样本预测为负例,(1,1)则表示将所有的样本预测为正例,(0,1)表示正例全部出现在负例之前的理想情况,(1,0)则表示负例全部出现在正例之前的最差情况。限于篇幅,这里不再论述。

现实中的任务通常都是有限个测试样本,因此只能绘制出近似ROC曲线。绘制方法:首先根据测试样本的评估值对测试样本排序,接着按照以下规则进行绘制。

14.png

同样地,进行模型的性能比较时,若一个学习器A的ROC曲线被另一个学习器B的ROC曲线完全包住,则称B的性能优于A。若A和B的曲线发生了交叉,则谁的曲线下的面积大,谁的性能更优。ROC曲线下的面积定义为AUC(Area Uder ROC Curve),不同于P-R的是,这里的AUC是可估算的,即AOC曲线下每一个小矩形的面积之和。易知:AUC越大,证明排序的质量越好,AUC为1时,证明所有正例排在了负例的前面,AUC为0时,所有的负例排在了正例的前面。

15.png

3.1.4 代价敏感错误率与代价曲线

上面的方法中,将学习器的犯错同等对待,但在现实生活中,将正例预测成假例与将假例预测成正例的代价常常是不一样的,例如:将无疾病–>有疾病只是增多了检查,但有疾病–>无疾病却是增加了生命危险。以二分类为例,由此引入了“代价矩阵”(cost matrix)。

16.png

在非均等错误代价下,我们希望的是最小化“总体代价”,这样“代价敏感”的错误率(2.5.1节介绍)为:

17.png

同样对于ROC曲线,在非均等错误代价下,演变成了“代价曲线”,代价曲线横轴是取值在[0,1]之间的正例概率代价,式中p表示正例的概率,纵轴是取值为[0,1]的归一化代价。

18.png

19.png

代价曲线的绘制很简单:设ROC曲线上一点的坐标为(TPR,FPR) ,则可相应计算出FNR,然后在代价平面上绘制一条从(0,FPR) 到(1,FNR) 的线段,线段下的面积即表示了该条件下的期望总体代价;如此将ROC 曲线土的每个点转化为代价平面上的一条线段,然后取所有线段的下界,围成的面积即为在所有条件下学习器的期望总体代价,如图所示:

20.png

4 机器学习指标ROC与AUC

AUC是一种模型分类指标,且仅仅是二分类模型的评价指标。AUC是Area Under Curve的简称,那么Curve就是ROC(Receiver Operating Characteristic),翻译为”接受者操作特性曲线”。

ROC

曲线由两个变量TPR和FPR组成,这个组合以FPR对TPR,即是以代价(costs)对收益(benefits)。

  • x轴为假阳性率(FPR):在所有的负样本中,分类器预测错误的比例

    $$FPR = \frac {FP}{FP+TN}$$

  • y轴为真阳性率(TPR):在所有的正样本中,分类器预测正确的比例(等于Recall)

    $$TPR = \frac {TP}{TP+FN}$$

为了更好地理解ROC曲线,我们使用具体的实例来说明:

如在医学诊断中,判断有病的样本。那么尽量把有病的揪出来是主要任务,也就是第一个指标TPR,要越高越好。而把没病的样本误诊为有病的,也就是第二个指标FPR,要越低越好。

不难发现,这两个指标之间是相互制约的。如果某个医生对于有病的症状比较敏感,稍微的小症状都判断为有病,那么他的第一个指标应该会很高,但是第二个指标也就相应地变高。最极端的情况下,他把所有的样本都看做有病,那么第一个指标达到1,第二个指标也为1。

我们以FPR为横轴,TPR为纵轴,得到如下ROC空间。

我们可以看出,左上角的点(TPR=1,FPR=0),为完美分类,也就是这个医生医术高明,诊断全对。点A(TPR>FPR),医生A的判断大体是正确的。中线上的点B(TPR=FPR),也就是医生B全都是蒙的,蒙对一半,蒙错一半;下半平面的点C(TPR<FPR),这个医生说你有病,那么你很可能没有病,医生C的话我们要反着听,为真庸医。上图中一个阈值,得到一个点。现在我们需要一个独立于阈值的评价指标来衡量这个医生的医术如何,也就是遍历所有的阈值,得到ROC曲线。

假设如下就是某个医生的诊断统计图,直线代表阈值。通过改变不同的阈值$1.0 \rightarrow 0$,从而绘制出ROC曲线。下图为未得病人群(蓝色)和得病人群(红色)的模型输出概率分布图(横坐标表示模型输出概率,纵坐标表示概率对应的人群的数量)。阈值为1时,不管你什么症状,医生均未诊断出疾病(预测值都为N),此时FPR=TPR=0,位于左下。阈值为0时,不管你什么症状,医生都诊断结果都是得病(预测值都为P),此时FPR=TPR=1,位于右上。

曲线距离左上角越近,证明分类器效果越好。

如上,是三条ROC曲线,在0.23处取一条直线。那么,在同样的低FPR=0.23的情况下,红色分类器得到更高的PTR。也就表明,ROC越往左上,分类器效果越好。我们用一个标量值AUC来量化它。

AUC

AUC定义:

AUC值为ROC曲线所覆盖的区域面积,显然,AUC越大,分类器分类效果越好。

AUC = 1,是完美分类器。绝大多数预测的场合,不存在完美分类器。

0.5 < AUC < 1,优于随机猜测。这个分类器(模型)妥善设定阈值的话,能有预测价值。

AUC = 0.5,跟随机猜测一样(例:丢铜板),模型没有预测价值。

AUC < 0.5,比随机猜测还差;但只要总是反预测而行,就优于随机猜测。

注:对于AUC小于0.5的模型,我们可以考虑取反(模型预测为positive,那我们就取negtive),这样就可以保证模型的性能不可能比随机猜测差。

以下为ROC曲线和AUC值的实例:

AUC的物理意义

AUC的物理意义正样本的预测结果大于负样本的预测结果的概率。所以AUC反应的是分类器对样本的排序能力。

另外值得注意的是,AUC对样本类别是否均衡并不敏感,这也是不均衡样本通常用AUC评价分类器性能的一个原因。

下面从一个小例子解释AUC的含义:小明一家四口,小明5岁,姐姐10岁,爸爸35岁,妈妈33岁建立一个逻辑回归分类器,来预测小明家人为成年人概率,假设分类器已经对小明的家人做过预测,得到每个人为成人的概率。

  1. AUC更多的是关注对计算概率的排序,关注的是概率值的相对大小,与阈值和概率值的绝对大小没有关系

例子中并不关注小明是不是成人,而关注的是,预测为成人的概率的排序。

问题⑪:以下为三种模型的输出结果,求三种模型的AUC。

小明 姐姐 妈妈 爸爸
a 0.12 0.35 0.76 0.85
b 0.12 0.35 0.44 0.49
c 0.52 0.65 0.76 0.85

AUC只与概率的相对大小(概率排序)有关,和绝对大小没关系。由于三个模型概率排序的前两位都是未成年人(小明,姐姐),后两位都是成年人(妈妈,爸爸),因此三个模型的AUC都等于。

  1. AUC只关注正负样本之间的排序,并不关心正样本内部,或者负样本内部的排序。这也体现了AUC的本质:任意个正样本的概率都大于负样本的概率的能力

    例子中AUC只需要保证(小明和姐姐)(爸爸和妈妈),小明和姐姐在前2个排序,爸爸和妈妈在后2个排序,而不会考虑小明和姐姐谁在前,或者爸爸和妈妈谁在前。

    问题⑫:以下已经对分类器输出概率从小到大进行了排列,哪些情况的AUC等于1, 情况的AUC为0(其中背景色表示True value,红色表示成年人,蓝色表示未成年人)。

    D 模型, E模型和F模型的AUC值为1,C模型的AUC值为0(爸妈为成年人的概率小于小明和姐姐,显然这个模型预测反了)。

AUC的计算:

  • 法1:AUC为ROC曲线下的面积,那我们直接计算面积可得。面积为一个个小的梯形面积(曲线)之和。计算的精度与阈值的精度有关。

  • 法2:根据AUC的物理意义,我们计算正样本预测结果大于负样本预测结果的概率。取n1*n0(n1为正样本数,n0为负样本数)个二元组,比较score(预测结果),最后得到AUC。时间复杂度为O(N*M)。

  • 法3:我们首先把所有样本按照score排序,依次用rank表示他们,如最大score的样本,rank=n (n=n0+n1,其中n0为负样本个数,n1为正样本个数),其次为n-1。那么对于正样本中rank最大的样本,rank_max,有n1-1个其他正样本比他score小,那么就有(rank_max-1)-(n1-1)个负样本比他score小。其次为(rank_second-1)-(n1-2)。最后我们得到正样本大于负样本的概率为

    avatar

|