监督学习之分类学习
Introduction
分类学习是最为常见的监督学习问题,并且其中的经典模型也最为广泛地被应用。其中,最基础的便是**二分类(Binary Classification)问题,即判断是非,从两个类别中选择一个作为预测结果;除此之外还有多类分类(Multiclass Classification)**的问题,即在多于 两个类别中选择一个;甚至还有多标签分类(Multi-label Classification)问题,与上述二分类以及多类分类问题不同,多标签分类问题判断一个样本是否同时属于多个不同类别。
在实际生活和工作中,我们会遇到许许多多的分类问题,比如,医生对肿瘤性质的判定;邮政系统对手写体邮编数字进行识别;互联网资讯公司对新闻进行分类;生物学家对 物种类型的鉴定;甚至,我们还能够对某些大灾难的经历者是否生还进行预测等。
线性分类器
- 模型介绍:线性分类器(Linear Classifiers),顾名思义,是一种假设特征与分类结果存在线性关系的模型。这个模型通过累加计算每个维度的特征与各自权重的乘积来帮助类别决策。
如果我们定义
x
=
<
x
1
,
x
2
,
…
,
x
n
>
x = <x_1,x_2,\dots,x_n>
x=<x1,x2,…,xn>来代表n维特征列向量D,同时用n维列向量
w
=
<
w
1
,
w
2
,
…
,
w
n
>
w=<w_1,w_2,\dots,w_n>
w=<w1,w2,…,wn>来代表对应的权重,或者叫做系数(Coefficient);同时为了避免其 过坐标原点这种硬性假设,增加一个截距(Intercept)b。由此这种线性关系便可以表达为
f
(
w
,
x
,
b
)
=
w
T
x
+
b
f(w,x,b)=w^Tx+b
f(w,x,b)=wTx+b
这里的f∈R,取值范围分布在整个实数域中。
然而,我们所要处理的最简单的二分类问题希望f∈{0,1};因此需要一个函数把原 先的∈R映射到(0,1)。于是我们想到了逻辑斯蒂(Logistic)函数:
g
(
z
)
=
1
1
+
e
−
z
g(z) = \frac{1}{1+e^{-z}}
g(z)=1+e−z1
这里z∈R,并且g∈(0,1),函数图像如图所示
综上,如果将
z
z
z替换为
f
f
f,结合两个方程,就获得了一个经典的线性分类器,逻辑斯蒂回归模型(Logistic Regression):
h
w
,
b
(
x
)
=
g
(
f
(
w
,
x
,
b
)
)
=
1
1
+
e
−
f
=
1
1
+
e
−
(
w
T
x
+
b
)
h_{w,b}(x)=g(f(w,x,b))=\frac{1}{1+e^{-f}}=\frac{1}{1+e^{-(w^Tx+b)}}
hw,b(x)=g(f(w,x,b))=1+e−f1=1+e−(wTx+b)1
如果z = 0,那么g = 0.5;若z < 0则g < 0.5,这个特征向量被判别为一类;反之,若z > 0,则g > 0.5, 其被归为另外一类。
当使用一组m个用于训练的特征向量
X
=
<
x
1
,
x
2
,
.
.
,
x
m
>
X=<x^1,x^2,..,x^m>
X=<x1,x2,..,xm>和其所对应的分类目标
y
=
<
y
1
,
y
2
,
…
,
y
m
>
y=<y^1,y^2,\dots,y^m>
y=<y1,y2,…,ym>,我们希望逻辑斯蒂模型可以在这组训练集上取得最大似然估计(Maximum Likelihod)的概率 L(w,b)。或者说,至少要在训练集上表现如此:
a
r
g
m
a
x
w
,
b
L
(
w
,
b
)
=
∏
1
=
1
m
(
h
w
,
b
(
i
)
)
y
i
(
1
−
h
w
,
b
(
i
)
)
1
−
y
i
argmax_{w,b} L(w,b) = \prod_{1=1}^{m} (h_{w,b}(i))^{y^i}(1-h_{w,b}(i))^{1-y^i}
argmaxw,bL(w,b)=1=1∏m(hw,b(i))yi(1−hw,b(i))1−yi
为了学习到决定模型的参数(Parameters),即系数w和截距b,我们普遍使用一种精确计算的解析算法和一种快速估计的随机梯度上升(Stochastic Gradient Ascend)算法。这里不会过多介绍算法的细节,主要是编程实践使用这两种算法求解模型参数。
数据描述:
这里我们使用一个”良/恶性乳腺癌肿瘤预测’数据为例子,我们会将完整地使用该数据所有的特征作为训练分类器参数的依据,同时采用更为精细的测评指标对模型性能进行评价。原始数据的下载地址为:https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/
Number of Instances: 699 (as of 15 July 1992)
Number of Attributes: 10 plus the class attribute
Attribute Information: (class attribute has been moved to last column)
Attribute Domain
- Sample code number id number
- Clump Thickness 1 - 10
- Uniformity of Cell Size 1 - 10
- Uniformity of Cell Shape 1 - 10
- Marginal Adhesion 1 - 10
- Single Epithelial Cell Size 1 - 10
- Bare Nuclei 1 - 10
- Bland Chromatin 1 - 10
- Normal Nucleoli 1 - 10
- Mitoses 1 - 10
- Class: (2 for benign, 4 for malignant)
Missing attribute values: 16
There are 16 instances in Groups 1 to 6 that contain a single missing
(i.e., unavailable) attribute value, now denoted by “?”.Class distribution
Benign: 458 (65.5%)
Malignant: 241 (34.5%)
我们得知该原始数据共有699条样本,每条样本有11列不同的数值:1列用于检索的id,9列与肿瘤相关的医学特征,以及一列表征肿瘤类型的数值。所有9列用于表示肿瘤医 学特质的数值均被量化为1~10之间的数字,而肿瘤的类型也借由数字⒉和数字4分别 指代良性与恶性。不过,这份数据也声明其中包含16个缺失值,并且用“?”标出。事实上,缺失值问题广泛存在于现实数据中,也是机器学习任务无法回避的问题;对于存在缺失值的数据,都暂时予以忽略;而用于处理缺失数据的方法会在后续为大家介绍。下面这段代码用于预处理原始肿瘤数据:
#导入pandas包,并重命名为pd。
import pandas as pd
#导入numpy包,并重命名为np。
import numpy as np
#通过查阅breast-cancer-wisconsin.names文件,给出每个特征列的名称。
column_names = ['Sample code name','Clump Thickness','Uniformity of Cell Size',
'Uniformity of Cell Shape','Marginal Adhesion','Single Epithelial Cell Size',
'Bare Nuclei','Bland Chromatin','Normal Nuleoli','Mitoses','Class']
# 使用pandas.read_csv函数从互联网读取指定数据
data = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/breast-cancer-wisconsin.data',
names = column_names)
# 将'?'替换为标准缺失值表示
data = data.replace(to_replace='?',value=np.nan)
# 丢弃带有缺失值的数据(只要有一个维度有缺失)
data = data.dropna(how='any')
# 输出data的数据量和维度
data.shape
(683, 11)
我们也可以看看数据的分布和统计
data.describe()
经过简单的处理之后,无缺失值的数据样本共有683条,特征包括细胞厚度、细胞大小、形状等9个维度,并且每个维度的特征均量化为1~10之间的数值进行表示。
由于原始数据没有提供对应的测试样本用于评估模型性能,因此需要对带有标记的.数据进行分割。通常情况下,25%的数据会作为测试集,其余75%的数据用于训练
# 使用sklearn.model_selection的train_test_split模板用于分割数据
from sklearn.model_selection import train_test_split
# 用train_test_split函数来分隔数据,默认情况下这些数据会被随机打乱(shuffle=True)。
# 我们这里分割出25%的数据作为测试集,剩下75%的数据作为训练样本。
# random_state全书设定为一个固定值(33),为了保证每次重新运行获得的训练集和测试集是一样的,实验结果可重现。
X_train, X_test, y_train, y_test = train_test_split(data[column_names[1:10]],data[column_names[10]],test_size=0.25,random_state=33)
# 查验训练样本的数量和类别分布
y_train.value_counts()
2 344
4 168
Name: Class, dtype: int64
# 查验测试样本的数量和类别分布
y_test.value_counts()
2 100
4 71
Name: Class, dtype: int64
综上,我们用于训练样本共有512条(344条良性肿瘤数据、168条恶性肿瘤数据),测试样本有171条(100条良性肿瘤数据、71条恶性肿瘤数据)。
编程实践
接下来,我们使用逻辑斯蒂回归与随机梯度参数估计两种方法对上述处理后的训练数据进行学习,并且根据测试样本特征进行预测。
# 初始化 LogisticRegression 与 SGDClassifier
lr = LogisticRegression()
sgdc = SGDClassifier()
# 初始化 LogisticRegression 与 SGDClassifier
lr = LogisticRegression()
sgdc = SGDClassifier()
# 调用fit函数来训练模型参数
lr.fit(X_train,y_train)
# 用predict对X_test进行预测
lr_y_predict = lr.predict(X_test)
# 同理
sgdc.fit(X_train,y_train)
sgdc_y_predict= sgdc.predict(X_test)
性能测评
我们分别利用LogisticRegression 与SGDClassifier针对171条测试样本进行预测工作。由于这171条测试样本拥有正确标记,并记录在变量y_test中,因此非常直观的做法是比对预测结果和原本正确标记,计算171条测试样本中,预测正确的百分比。我们在把这个百分比称作准确性(Accuracy),并且将其作为评估分类模型的一个重要性能指标。
然而,在许多实际问题中,我们往往更加关注模型对某一特定类别的预测能力。这 时,准确性指标就变得笼统了。比如,在“良/恶性肿瘤预测任务”里,医生和患者往往更加关心有多少恶性肿瘤被正确地诊断出来,因为这种肿瘤更加致命。也就是说,在二分类任 务下,预测结果(Predicted Condition)和正确标记(True Condition)之间存在4种不同的 组合,构成混淆矩阵(Confusion Matrix),如图所示。如果恶性肿瘤为阳性(Positive),良性肿瘤为阴性(Negative),那么,预测正确的恶性肿瘤即为真阳性(True Positive),预测正确的良性肿瘤为真阴性(True Negative);原本是良性肿瘤(Condition negative) ,误判为恶性肿瘤( Predicted condition positive) 的为假阳性(False Positive) ;而实际是恶性肿瘤,但是预测模型没有检测出来,则为假阴性(FalseNegative)。事实上,医生和病患最不愿看到的是有假阴性(FalseNegative)的结果,因为这种误诊会耽误病患的治疗,进而危及生命。
混淆矩阵(图片来源于*)
所以除了准确性以外,我们还引入了两个评价指标,分别是召回率(Recall)和精确率(Precsion)。他们的定义分别是
Accuracy
=
t
p
+
t
n
t
p
+
t
n
+
f
p
+
f
n
Precision
=
t
p
t
p
+
f
p
Recall
=
t
p
t
p
+
f
n
{\displaystyle {\text{Accuracy}}={\frac {tp+tn}{tp+tn+fp+fn}}\,} {\displaystyle {\begin{aligned}{\text{Precision}}&={\frac {tp}{tp+fp}}\\{\text{Recall}}&={\frac {tp}{tp+fn}}\,\end{aligned}}}
Accuracy=tp+tn+fp+fntp+tnPrecisionRecall=tp+fptp=tp+fntp
其中tp代表真阳性样本的数量,以此类推。此外,为了综合考量回率与精确率,我们计算这两个指标的调和平均数,得到F1指标(F1 measure):
之所以使用调和平均数,是因为它除了具备平均功能外,还会对那些召回率和精确 率更加接近的模型给予更高的分数;而这也是我们所期待的,因为那些召回率和精确率差 距过大的学习模型,往往没有足够的实用价值。
回到本节所讨论的任务,对于乳腺癌肿瘤预测的问题,我们显然更加关注召回率,也 就是应该被正确识别的恶性肿瘤的百分比。对于召回率更高的预测模型,医生和患者会 更为信赖并给予更多关注。
# 从sklearn.metrics 导入 classification_report 模块
from sklearn.metrics import classification_report
# 从sklearn.metrics导入accuracy_score,用来做分类准确率的评估。
from sklearn.metrics import accuracy_score
print('%s: %f'%('使用逻辑斯蒂回归模型做分类的准确率为', accuracy_score(y_test, lr.predict(X_test))))
print('Accuaracy of LR Classifier:',lr.score(X_test, y_test))
print(classification_report(y_test,lr_y_predict,target_names = ['Benign','Malignant']))
print('Accuaracy of SGD Classifier:',sgdc.score(X_test, y_test))
print(classification_report(y_test,sgdc_y_predict,target_names = ['Benign','Malignant']))
from sklearn.metrics import confusion_matrix
con_mat = confusion_matrix(y_test, lr_y_predict, labels=[2,4])
print(con_mat)
from sklearn.metrics import recall_score, precision_score, precision_recall_curve
import numpy as np
print(precision_score(y_test, lr_y_predict, pos_label=4))
print(recall_score(y_test, lr_y_predict, pos_label=4))
# 表示每一个预测的概率probability
lr_y_predict_prob = lr.predict_proba(X_test)
precisions, recalls, thresholds = precision_recall_curve(y_test, np.max(lr_y_predict_prob, axis=1), pos_label=4)
from matplotlib import pyplot as plt
plt.rcParams['figure.dpi'] = 300
plt.plot(recalls, precisions)
- 特点分析:线性分类器可以说是最为基本和常用的机器学习模型。尽管其受限于数据特征与分类目标之间的线性假设,我们仍然可以在科学研究与工程实践中把线性分类器的表现性能作为基准。这里所使用的模型包括LogisticRegression与SGDClassifier。相比之下,前者对参数的计算采用精确解析的方式,计算时间长但是模型性能略高;后者采用随机梯度上升算法估计模型参数,计算时间短但是产出的模型性能略低。一般而言,对于训练数据规模在10万量级以上的数据考虑到时间的耗用,可能更加推荐使用随机梯度算法对模型参数进行估计。并且有时候SGD会有更好的表现