原文:
annas-archive.org/md5/7039549ff2b32d189f96a3420dc66360
译者:飞龙
第十章:使用 scikit-learn 进行文本和多类分类
本章将涵盖以下食谱:
-
使用 LDA 进行分类
-
使用 QDA(非线性 LDA)进行工作
-
使用 SGD 进行分类
-
使用朴素贝叶斯分类文档
-
半监督学习中的标签传播
使用 LDA 进行分类
线性判别分析(LDA)试图通过特征的线性组合来预测结果变量。LDA 通常用作预处理步骤。我们将在本例中演示这两种方法。
做好准备
在这个食谱中,我们将执行以下操作:
-
从 Google 获取股票数据。
-
将其重新排列成我们习惯的形式。
-
创建 LDA 对象以拟合和预测类别标签。
-
给出一个如何使用 LDA 进行降维的例子。
在开始第 1 步并从 Google 获取股票数据之前,安装一个支持最新股票读取器的 pandas 版本。可以在 Anaconda 命令行中输入以下命令:
conda install -c anaconda pandas-datareader
请注意,你的 pandas 版本将会更新。如果这成为问题,可以为当前的 pandas 版本创建一个新的环境。现在打开一个 notebook,检查pandas-datareader
是否能够正确导入:
from `pandas-datareader` import data
如果导入正确,将不会显示任何错误。
如何操作…
在这个例子中,我们将执行类似于 Altman Z 分数的分析。在他的论文中,Altman 根据几个财务指标分析了一家公司在两年内违约的可能性。以下内容摘自 Altman Z 分数的维基百科页面:
Z 分数公式 | 描述 |
---|---|
T1 = 营运资本 / 总资产 | 该指标衡量公司的流动资产与公司规模的关系。 |
T2 = 留存收益 / 总资产 | 该指标衡量盈利能力,反映了公司的年龄和盈利能力。 |
T3 = 息税前利润 / 总资产 | 该指标衡量税收和杠杆因素之外的运营效率。它认为运营盈利对公司长期生存至关重要。 |
T4 = 股本市值 / 总负债账面价值 | 这增加了市场维度,可以揭示证券价格波动作为潜在的预警信号。 |
T5 = 销售额 / 总资产 | 这是衡量总资产周转率的标准指标(不同产业之间差异很大)。 |
参考 Altman, Edward I.(1968 年 9 月)发表的文章《财务比率、判别分析与公司破产预测》,《金融学杂志》:189–209。
在本分析中,我们将通过 pandas 查看来自 Google 的财务数据。我们将尝试预测基于当前股票属性,六个月后的股价是否会更高。显然,这远不如 Altman 的 Z 分数那样精确。
- 首先进行几个导入,并存储你将使用的股票代码、数据的开始日期和结束日期:
%matplotlib inline
from pandas_datareader import data
import pandas as pd
tickers = ["F", "TM", "GM", "TSLA"]
first_date = '2009-01-01'
last_date = '2016-12-31'
- 现在,让我们获取股票数据:
stock_panel = data.DataReader(tickers, 'google', first_date, last_date)
- 这个数据结构是 pandas 中的一个面板。它类似于 在线分析处理 (OLAP) 立方体或 3D 数据框。让我们来看一下数据,更加熟悉一下收盘价,因为在比较时,我们关心的就是这些:
stock_df = stock_panel.Close.dropna()
stock_df.plot(figsize=(12, 5))
以下是输出结果:
好的,那么现在我们需要将每只股票的价格与它六个月后的价格进行比较。如果更高,我们将其编码为 1,否则为 0。
- 为此,我们将数据框向后移动 180 天并进行比较:
#this dataframe indicates if the stock was higher in 180 days
classes = (stock_df.shift(-180) > stock_df).astype(int)
- 接下来,我们需要做的是将数据集展开:
X = stock_panel.to_frame()
classes = classes.unstack()
classes = classes.swaplevel(0, 1).sort_index()
classes = classes.to_frame()
classes.index.names = ['Date', 'minor']
data = X.join(classes).dropna()
data.rename(columns={0: 'is_higher'}, inplace=True)
data.head()
以下是输出结果:
- 好的,现在我们需要在 NumPy 中创建矩阵。为此,我们将使用
patsy
库。这是一个很棒的库,可以用来创建类似于 R 中的设计矩阵:
import patsy
X = patsy.dmatrix("Open + High + Low + Close + Volume + is_higher - 1", data.reset_index(),return_type='dataframe')
X.head()
以下是输出结果:
patsy
是一个非常强大的包;例如,假设我们想应用预处理。在 patsy
中,像 R 一样,我们可以修改公式,来对应设计矩阵中的修改。这里不会做,但如果我们想将值缩放到均值为 0,标准差为 1,函数将是 scale(open) + scale(high)。
- 现在我们已经有了数据集,让我们来拟合 LDA 对象:
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
lda = LDA()
lda.fit(X.iloc[:, :-1], X.iloc[:, -1]);
- 我们可以看到,在预测数据集时表现得还不错。当然,我们还想通过其他参数来改进这个模型并进行测试:
from sklearn.metrics import classification_report
print classification_report(X.iloc[:, -1].values,
lda.predict(X.iloc[:, :-1]))
precision recall f1-score support
0.0 0.64 0.81 0.72 3432
1.0 0.64 0.42 0.51 2727
avg / total 0.64 0.64 0.62 6159
这些度量描述了模型以不同方式拟合数据的效果。
precision
和 recall
参数非常相似。从某些方面来看,正如以下列表所示,它们可以被看作是条件比例:
-
precision
:给定模型预测的正值,实际正确的比例是多少?这也是为什么 precision 的另一个名称是 正预测值 (PPV) 的原因。 -
recall
:在已知某一类别为真时,我们选择的比例是多少?我说选择是因为recall
是搜索问题中常见的度量指标。例如,可能有一组与搜索词相关的网页——返回的比例。在第五章,线性模型 - 逻辑回归,你见过一个另一名称的 recall,叫做敏感度。
f1-score
参数试图总结 recall
和 precision
之间的关系。
它是如何工作的…
LDA 实际上与我们之前做的聚类非常相似。我们从数据中拟合了一个基本模型。然后,一旦我们拥有模型,就尝试预测并比较在每个类别中给定数据的似然性。我们选择那个更可能的选项。
LDA 实际上是二次判别分析(QDA)的一种简化,我们将在下一个实例中讨论它。这里我们假设每个类别的协方差是相同的,但在 QDA 中,这个假设被放宽。想一想 KNN 与高斯混合模型(GMM)之间的联系,以及这里和那里之间的关系。
使用 QDA —— 非线性 LDA
QDA 是一个常见技术的推广,如二次回归。它只是一个模型的推广,允许更多复杂的模型拟合,但像所有事物一样,当允许复杂性渗入时,我们也使自己的生活变得更加困难。
准备工作
我们将在上一个实例的基础上扩展,并通过 QDA 对象来看 QDA。
我们说过我们对模型的协方差做了假设。这里我们将放宽这个假设。
如何操作…
- QDA 恰好是
qda
模块的成员。使用以下命令来使用 QDA:
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis as QDA
qda = QDA()
qda.fit(X.iloc[:, :-1], X.iloc[:, -1])
predictions = qda.predict(X.iloc[:, :-1])
predictions.sum()
2686.0
from sklearn.metrics import classification_report
print classification_report(X.iloc[:, -1].values, predictions)
precision recall f1-score support
0.0 0.65 0.66 0.65 3432
1.0 0.56 0.55 0.56 2727
avg / total 0.61 0.61 0.61 6159
正如你所见,整体上差不多。如果回顾使用 LDA 进行分类的实例,我们可以看到与 QDA 对象在类别零上的大变化,以及类别一上的细微差别。
正如我们在上一个实例中所讨论的,我们基本上是在这里比较似然。但我们如何比较似然呢?我们不妨使用手头的价格来尝试分类is_higher
。
它是如何工作的…
我们假设收盘价服从对数正态分布。为了计算每个类别的似然,我们需要创建收盘价的子集,并为每个类别准备训练集和测试集。我们将使用内置的交叉验证方法:
from sklearn.model_selection import ShuffleSplit
import scipy.stats as sp
shuffle_split_inst = ShuffleSplit()
for test, train in shuffle_split_inst.split(X):
train_set = X.iloc[train]
train_close = train_set.Close
train_0 = train_close[~train_set.is_higher.astype(bool)]
train_1 = train_close[train_set.is_higher.astype(bool)]
test_set = X.iloc[test]
test_close = test_set.Close.values
ll_0 = sp.norm.pdf(test_close, train_0.mean())
ll_1 = sp.norm.pdf(test_close, train_1.mean())
现在我们已经有了两个类别的似然,可以进行比较并分配类别:
(ll_0 > ll_1).mean()
0.14486740032473389
使用 SGD 进行分类
随机梯度下降(SGD)是用于拟合回归模型的基本技术。SGD 在分类或回归中的应用有自然的联系。
准备工作
在回归分析中,我们最小化了一个惩罚错误选择的代价函数,它是在一个连续尺度上进行的,但对于分类问题,我们将最小化一个惩罚两个(或更多)情况的代价函数。
如何操作…
- 首先,让我们创建一些非常基本的数据:
from sklearn import datasets
X, y = datasets.make_classification(n_samples = 500)
- 将数据分割成训练集和测试集:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X,y,stratify=y)
- 实例化并训练分类器:
from sklearn import linear_model
sgd_clf = linear_model.SGDClassifier()
#As usual, we'll fit the model:
sgd_clf.fit(X_train, y_train)
- 测量测试集上的性能:
from sklearn.metrics import accuracy_score
accuracy_score(y_test,sgd_clf.predict(X_test))
0.80000000000000004
还有更多…
我们可以设置class_weight
参数来考虑数据集中不同程度的不平衡。
合页损失函数定义如下:
这里,t
是真实分类,表示*+1表示一个类别,-1表示另一个类别。系数向量由从模型中拟合的y*表示,x是感兴趣的值。还有一个截距用于辅助计算。换句话说:
https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/9bf8c9bd-dc17-4835-a57f-40dc47b94d64.pnghttps://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/3a8e922a-1339-4f03-a10e-eb7bf4ec001d.png
使用朴素贝叶斯分类文档
朴素贝叶斯是一个非常有趣的模型。它有点类似于 KNN,因为它做了一些假设,这些假设可能会过于简化现实,但在许多情况下仍然表现良好。
准备好
在这个实例中,我们将使用朴素贝叶斯进行文档分类,使用的是sklearn
。我个人的一个例子是使用会计中的账户描述词汇,例如应付账款,来判断它属于损益表、现金流量表还是资产负债表。
基本思想是使用标注测试语料库中的单词频率来学习文档的分类。然后,我们可以将其应用于训练集,并尝试预测标签。
我们将使用sklearn
中的newgroups
数据集来玩转朴素贝叶斯模型。这是一个非平凡量的数据集,所以我们会直接获取它,而不是加载它。我们还将限制类别为rec.autos
和rec.motorcycles
:
import numpy as np
from sklearn.datasets import fetch_20newsgroups
categories = ["rec.autos", "rec.motorcycles"]
newgroups = fetch_20newsgroups(categories=categories)
#take a look
print "\n".join(newgroups.data[:1])
From: gregl@zimmer.CSUFresno.EDU (Greg Lewis)
Subject: Re: WARNING.....(please read)...
Keywords: BRICK, TRUCK, DANGER
Nntp-Posting-Host: zimmer.csufresno.edu
Organization: CSU Fresno
Lines: 33
...
newgroups.target_names
['rec.autos', 'rec.motorcycles']
现在我们有了新组,我们需要将每个文档表示为词袋模型。这种表示法也正是朴素贝叶斯名称的由来。该模型是“朴素”的,因为文档分类时并不考虑文档内部的词汇协方差。这可能被认为是一个缺陷,但事实证明,朴素贝叶斯在许多情况下都能合理有效地工作。
我们需要将数据预处理成词袋矩阵。这是一个稀疏矩阵,当文档中存在某个单词时,就会有对应的条目。这个矩阵可能会变得非常大,如下所示:
from sklearn.feature_extraction.text import CountVectorizer
count_vec = CountVectorizer()
bow = count_vec.fit_transform(newgroups.data)
这个矩阵是一个稀疏矩阵,它的维度是文档数量和每个单词的数量。矩阵中的文档和单词值是特定术语的频率:
bow
<1192x19177 sparse matrix of type '<type 'numpy.int64'>'
with 164296 stored elements in Compressed Sparse Row format>
实际上,我们需要将矩阵转换为稠密数组,以便用于朴素贝叶斯对象。所以,我们需要将其转换回来:
bow = np.array(bow.todense())
显然,大多数条目都是零,但我们可能想要重新构建文档计数以进行合理性检查:
words = np.array(count_vec.get_feature_names())
words[bow[0] > 0][:5]
array([u'10pm', u'1qh336innfl5', u'33', u'93740',
u'___________________________________________________________________'],
dtype='<U79')
现在,这些是第一个文档中的示例吗?我们可以使用以下命令来检查:
'10pm' in newgroups.data[0].lower()
True
'1qh336innfl5' in newgroups.data[0].lower()
True
如何操作…
好吧,准备数据比平常多花了一些时间,但我们处理的是文本数据,这些数据不像我们平常处理的矩阵数据那样可以迅速表示。
- 然而,现在我们准备好了,就可以启动分类器并拟合我们的模型:
from sklearn import naive_bayes
clf = naive_bayes.GaussianNB().fit(X_train, y_train)
- 将
bow
和newgroups.target
分别重命名为X
和y
。在拟合模型之前,我们先将数据集拆分为训练集和测试集:
X = bow
y = newgroups.target
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.5,stratify=y)
- 现在我们在测试集上拟合了一个模型,并试图预测训练集,以确定哪些类别与哪些文章相对应,让我们来看看大致的准确性:
from sklearn.metrics import accuracy_score
accuracy_score(y_test,clf.predict(X_test) )
0.94630872483221473
它是如何工作的…
朴素贝叶斯的基本思想是,我们可以根据特征向量估算某个数据点属于某个类别的概率。
通过贝叶斯公式可以重新排列,得到最大后验(MAP)估计特征向量。这个 MAP 估计选择了特征向量的概率最大化的类别。
还有更多…
我们也可以将朴素贝叶斯扩展到多类工作。我们不再假设高斯似然,而是使用多项式似然。
首先,让我们获取第三类数据:
from sklearn.datasets import fetch_20newsgroups
mn_categories = ["rec.autos", "rec.motorcycles", "talk.politics.guns"]
mn_newgroups = fetch_20newsgroups(categories=mn_categories)
我们需要像处理分类问题一样对其进行向量化:
mn_bow = count_vec.fit_transform(mn_newgroups.data)
mn_bow = np.array(mn_bow.todense())
将mn_bow
和mn_newgroups.target
分别重命名为X
和y
。让我们创建一个训练集和一个测试集,并使用训练数据训练一个多项式贝叶斯模型:
X = mn_bow
y = mn_newgroups.target
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.5,stratify=y)
from sklearn.naive_bayes import MultinomialNB
clf = MultinomialNB().fit(X_train, y_train)
测量模型准确度:
from sklearn.metrics import accuracy_score
accuracy_score(y_test,clf.predict(X_test) )
0.96317606444188719
这并不完全令人惊讶,我们表现得相当不错。我们在双类情况下表现得相当好,而且由于有人猜测talk.politics.guns
类别与其他两个类别相对独立,因此我们应该表现得相当好。
半监督学习中的标签传播
标签传播是一种半监督技术,利用标记数据和未标记数据来学习未标记数据。通常,受益于分类算法的数据很难标记。例如,标记数据可能非常昂贵,因此只有一部分数据进行人工标记才是具有成本效益的。尽管如此,似乎有慢慢增长的趋势支持公司雇佣分类学家。
准备工作
另一个问题领域是审查数据。你可以想象一个情况,时间的前沿将影响你收集标记数据的能力。例如,假设你对患者进行了测量并给他们服用了实验药物。在某些情况下,如果药物反应足够迅速,你可以测量药物的效果,但你可能还想预测反应较慢的药物的效果。药物可能对某些患者引起致命反应,可能需要采取挽救生命的措施。
怎么做…
- 为了表示半监督或审查数据,我们需要进行一些数据预处理。首先,我们将通过一个简单的例子进行演示,然后再处理一些更复杂的情况:
from sklearn import datasets
d = datasets.load_iris()
- 由于我们将对数据进行修改,所以让我们创建副本并在目标名称的副本中添加一个未标记成员。这样以后更容易识别数据:
X = d.data.copy()
y = d.target.copy()
names = d.target_names.copy()
names = np.append(names, ['unlabeled'])
names
array(['setosa', 'versicolor', 'virginica', 'unlabeled'],
dtype='|S10')
- 现在,让我们用
-1
更新y
。这表示未标记的情况。这也是我们在名称末尾添加未标记的原因:
y[np.random.choice([True, False], len(y))] = -1
- 我们的数据现在有一堆
-1
与实际数据交错在一起:
y[:10]
array([ 0, -1, -1, 0, 0, 0, 0, -1, 0, -1])
names[y[:10]]
array(['setosa', 'unlabeled', 'unlabeled', 'setosa', 'setosa', 'setosa',
'setosa', 'unlabeled', 'setosa', 'unlabeled'],
dtype='|S10')
- 我们显然有很多未标记的数据,现在的目标是使用
LabelPropagation
方法来预测标签:
from sklearn import semi_supervised
lp = semi_supervised.LabelPropagation()
lp.fit(X, y)
LabelPropagation(alpha=1, gamma=20, kernel='rbf', max_iter=30, n_jobs=1,
n_neighbors=7, tol=0.001)
- 测量准确度评分:
preds = lp.predict(X)
(preds == d.target).mean()
0.97333333333333338
还不错,尽管我们使用了所有数据,这有点像作弊。另外,鸢尾花数据集是一个相对分离的数据集。
使用整个数据集让人联想到更传统的统计方法。选择不在测试集上进行测量减少了我们对预测的关注,鼓励我们更多地理解和解释整个数据集。如前所述,理解与黑箱预测的区别在于传统统计学和机器学习。
顺便说一下,让我们来看一下 LabelSpreading
,它是 LabelPropagation
的姊妹类。我们将在本节 如何工作… 中对 LabelPropagation
和 LabelSpreading
做出技术性区分,但它们非常相似:
ls = semi_supervised.LabelSpreading()
LabelSpreading
比 LabelPropagation
更加鲁棒和抗噪声,正如它的工作方式所观察到的那样:
ls.fit(X, y)
LabelSpreading(alpha=0.2, gamma=20, kernel='rbf', max_iter=30, n_jobs=1,
n_neighbors=7, tol=0.001)
测量准确率得分:
(ls.predict(X) == d.target).mean()
0.96666666666666667
不要将标签传播算法遗漏的一个案例看作是它表现较差的标志。关键是我们可能赋予它一定的能力,使其在训练集上进行良好的预测,并能适用于更广泛的情况。
它是如何工作的……
标签传播通过创建一个数据点的图来工作,边缘上根据以下公式设置权重:
该算法通过标记的数据点将其标签传播到未标记的数据点。这个传播过程在一定程度上由边缘权重决定。
边缘权重可以放置在一个转移概率矩阵中。我们可以通过迭代的方式来确定实际标签的良好估计值。
第十一章:神经网络
本章我们将涵盖以下配方:
-
感知机分类器
-
神经网络 – 多层感知机
-
与神经网络堆叠
介绍
最近,神经网络和深度学习非常流行,因为它们解决了很多难题,并且可能已经成为人工智能公众面貌的重要组成部分。让我们探索 scikit-learn 中可用的前馈神经网络。
感知机分类器
使用 scikit-learn,你可以探索感知机分类器,并将其与 scikit-learn 中的其他分类方法进行比较。此外,感知机是神经网络的构建模块,神经网络是机器学习中的一个重要部分,特别是在计算机视觉领域。
准备开始
让我们开始吧。过程如下:
-
加载 UCI 糖尿病分类数据集。
-
将数据集划分为训练集和测试集。
-
导入感知机。
-
实例化感知机。
-
然后训练感知机。
-
尝试在测试集上使用感知机,或者更好地计算
cross_val_score
。
加载 UCI 糖尿病数据集:
import numpy as np
import pandas as pd
data_web_address = "https://archive.ics.uci.edu/ml/machine-learning-databases/pima-indians-diabetes/pima-indians-diabetes.data"
column_names = ['pregnancy_x',
'plasma_con',
'blood_pressure',
'skin_mm',
'insulin',
'bmi',
'pedigree_func',
'age',
'target']
feature_names = column_names[:-1]
all_data = pd.read_csv(data_web_address , names=column_names)
X = all_data[feature_names]
y = all_data['target']
你已经加载了X
,输入特征集,以及y
,我们希望预测的变量。将X
和y
划分为测试集和训练集。通过分层目标集来完成这一步,确保在训练集和测试集中类的比例平衡:
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)
如何实现……
- 对特征集进行缩放。仅在训练集上执行缩放操作,然后继续进行测试集:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)
- 实例化并在训练集上训练感知机:
from sklearn.linear_model import Perceptron
pr = Perceptron()
pr.fit(X_train_scaled, y_train)
Perceptron(alpha=0.0001, class_weight=None, eta0=1.0, fit_intercept=True,
n_iter=5, n_jobs=1, penalty=None, random_state=0, shuffle=True,
verbose=0, warm_start=False)
- 测量交叉验证得分。将
roc_auc
作为交叉验证评分机制。此外,通过设置cv=skf
,使用分层 K 折交叉验证:
from sklearn.model_selection import cross_val_score, StratifiedKFold
skf = StratifiedKFold(n_splits=3)
cross_val_score(pr, X_train_scaled, y_train, cv=skf,scoring='roc_auc').mean()
0.76832628835771022
- 在测试集上评估性能。导入
sklearn.metrics
模块中的两个指标,accuracy_score
和roc_auc_score
:
from sklearn.metrics import accuracy_score, roc_auc_score
print "Classification accuracy : ", accuracy_score(y_test, pr.predict(X_test_scaled))
print "ROC-AUC Score : ",roc_auc_score(y_test, pr.predict(X_test_scaled))
Classification accuracy : 0.681818181818
ROC-AUC Score : 0.682592592593
测试很快就完成了。结果表现还行,比逻辑回归稍差,逻辑回归的准确率为 75%(这是一个估计值;我们不能将本章的逻辑回归与任何之前章节中的逻辑回归做比较,因为训练集和测试集的划分不同)。
它是如何工作的……
感知机是大脑中神经元的简化模型。在下面的图示中,感知机从左边接收输入 x[1] 和 x[2]。计算偏置项 w[0] 和权重 w[1]、w[2]。x[i] 和 w[i] 组成一个线性函数。然后将这个线性函数传递给激活函数。
在以下激活函数中,如果权重与输入向量的点积之和小于零,则将单独的行分类为 0;否则分类为 1:
这发生在单次迭代或遍历感知机时。该过程会在多个迭代中重复,每次都会重新调整权重,从而最小化损失函数。
关于感知器和当前神经网络的状态,它们表现良好,因为研究人员已经尝试了许多方法。实际上,基于目前的计算能力,它们的表现非常好。
随着计算能力的不断提升,神经网络和感知器能够解决越来越复杂的问题,且训练时间持续缩短。
还有更多内容…
尝试通过调整感知器的超参数来运行网格搜索。几个显著的参数包括正则化参数penalty
和alpha
、class_weight
以及max_iter
。class_weight
参数通过赋予代表性不足的类别更多的权重,能够很好地处理类别不平衡问题。max_iter
参数表示感知器的最大迭代次数。一般来说,其值越大越好,因此我们将其设置为 50。(请注意,这是针对 scikit-learn 0.19.0 的代码。在 scikit-learn 0.18.1 版本中,请使用n_iter
参数代替max_iter
参数。)
尝试以下网格搜索:
from sklearn.model_selection import GridSearchCV
param_dist = {'alpha': [0.1,0.01,0.001,0.0001],
'penalty': [None, 'l2','l1','elasticnet'],
'random_state': [7],
'class_weight':['balanced',None],'eta0': [0.25,0.5,0.75,1.0],
'warm_start':[True,False], 'max_iter':[50], 'tol':[1e-3]}
gs_perceptron = GridSearchCV(pr, param_dist, scoring='roc_auc',cv=skf).fit(X_train_scaled, y_train)
查看最佳参数和最佳得分:
gs_perceptron.best_params_
{'alpha': 0.001,
'class_weight': None,
'eta0': 0.5,
'max_iter': 50,
'penalty': 'l2',
'random_state': 7,
'tol': 0.001,
'warm_start': True}
gs_perceptron.best_score_
0.79221656570311072
使用交叉验证调整超参数已改善结果。现在尝试使用一组感知器进行集成学习,如下所示。首先注意并选择网格搜索中表现最好的感知器:
best_perceptron = gs_perceptron.best_estimator_
执行网格搜索:
from sklearn.ensemble import BaggingClassifier
from sklearn.ensemble import BaggingClassifier
param_dist = {
'max_samples': [0.5,1.0],
'max_features' : [0.5,1.0],
'oob_score' : [True, False],
'n_estimators': [100],
'n_jobs':[-1],
'base_estimator__alpha': [0.001,0.002],
'base_estimator__penalty': [None, 'l2','l1','elasticnet'], }
ensemble_estimator = BaggingClassifier(base_estimator = best_perceptron)
bag_perceptrons = GridSearchCV(ensemble_estimator, param_dist,scoring='roc_auc',cv=skf,n_jobs=-1).fit(X_train_scaled, y_train)
查看新的交叉验证得分和最佳参数:
bag_perceptrons.best_score_
0.83299842529587864
bag_perceptrons.best_params_
{'base_estimator__alpha': 0.001,
'base_estimator__penalty': 'l1',
'max_features': 1.0,
'max_samples': 1.0,
'n_estimators': 100,
'n_jobs': -1,
'oob_score': True}
因此,对于这个数据集,一组感知器的表现优于单一感知器。
神经网络 – 多层感知器
在 scikit-learn 中使用神经网络非常简单,步骤如下:
-
加载数据。
-
使用标准缩放器对数据进行缩放。
-
进行超参数搜索。首先调整 alpha 参数。
准备就绪
加载我们在第九章中使用的中等规模的加州住房数据集,决策树算法与集成方法:
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_california_housing
cali_housing = fetch_california_housing()
X = cali_housing.data
y = cali_housing.target
将目标变量进行分箱,使得目标训练集和目标测试集更为相似。然后使用分层的训练/测试拆分:
bins = np.arange(6)
binned_y = np.digitize(y, bins)
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=binned_y)
如何做…
- 首先对输入变量进行缩放。仅在训练数据上训练缩放器:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
- 然后,在测试集上执行缩放:
X_test_scaled = scaler.transform(X_test)
- 最后,执行随机搜索(或如果你喜欢,也可以进行网格搜索)来找到
alpha
的合理值,确保其得分较高:
from sklearn.model_selection import RandomizedSearchCV
from sklearn.neural_network import MLPRegressor
param_grid = {'alpha': [10,1,0.1,0.01],
'hidden_layer_sizes' : [(50,50,50),(50,50,50,50,50)],
'activation': ['relu','logistic'],
'solver' : ['adam']
}
pre_gs_inst = RandomizedSearchCV(MLPRegressor(random_state=7),
param_distributions = param_grid,
cv=3,
n_iter=15,
random_state=7)
pre_gs_inst.fit(X_train_scaled, y_train)
pre_gs_inst.best_score_
0.7729679848718175
pre_gs_inst.best_params_
{'activation': 'relu',
'alpha': 0.01,
'hidden_layer_sizes': (50, 50, 50),
'solver': 'adam'}
它是如何工作的…
在神经网络的背景下,单一感知器的结构如下所示:
输出是权重和输入的点积之和的函数。该函数f是激活函数,可以是 sigmoid 曲线。例如,在神经网络中,超参数激活指的就是这个函数。在 scikit-learn 中,有 identity、logistic、tanh 和 relu 的选项,其中 logistic 即为 sigmoid 曲线。
整个网络是这样的(以下是来自 scikit 文档的图示,链接:scikit-learn.org/stable/modules/neural_networks_supervised.html
):
使用我们熟悉的数据集——加利福尼亚住房数据集来训练神经网络是很有教育意义的。加利福尼亚住房数据集似乎更适合非线性算法,特别是树算法和树的集成。树算法在这个数据集上表现得很好,并为算法在该数据集上的表现建立了基准。
最终,神经网络表现还不错,但远不如梯度提升机好。此外,它们在计算上非常昂贵。
关于神经网络的哲学思考
神经网络是数学上通用的函数逼近器,可以学习任何函数。此外,隐藏层通常被解释为网络学习过程的中间步骤,而无需人工编写这些中间步骤。这可以来自计算机视觉中的卷积神经网络,在那里很容易看到神经网络如何推断出每一层。
这些事实构成了有趣的心智图像,并且可以应用于其他估计器。许多人往往不会把随机森林看作是树在逐步推理的过程,或者说是树与树之间的推理(也许是因为它们的结构不如有组织,而随机森林不会让人联想到生物大脑的可视化)。在更实际的细节上,如果你想组织随机森林,你可以限制它们的深度,或者使用梯度提升机。
无论神经网络是否真正智能这一事实如何,随着领域的进展和机器变得越来越聪明,携带这样的心智图像是有帮助的。携带这个想法,但要专注于结果;这就是现在机器学习的意义。
使用神经网络进行堆叠
两种最常见的元学习方法是袋装法和提升法。堆叠法使用得较少;然而,它非常强大,因为可以将不同类型的模型结合起来。这三种方法都通过一组较弱的估计器创建了一个更强的估计器。我们在第九章,树算法与集成方法中尝试了堆叠过程。在这里,我们尝试将神经网络与其他模型结合。
堆叠的过程如下:
-
将数据集划分为训练集和测试集。
-
将训练集划分为两部分。
-
在训练集的第一部分上训练基础学习器。
-
使用基础学习器对训练集第二部分进行预测。存储这些预测向量。
-
将存储的预测向量作为输入,目标变量作为输出。训练一个更高层次的学习器(注意我们仍然处于训练集的第二部分)。
之后,你可以查看在测试集上整体过程的结果(注意,不能通过查看测试集上的结果来选择模型)。
准备就绪
导入加利福尼亚州住房数据集以及我们一直使用的库:numpy
、pandas
和matplotlib
。这是一个中等大小的数据集,但相对于其他 scikit-learn 数据集来说,它还是比较大的:
from __future__ import division
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_california_housing
#From within an ipython notebook
%matplotlib inline
cali_housing = fetch_california_housing()
X = cali_housing.data
y = cali_housing.target
将目标变量进行分箱,以提高数据集在目标变量上的平衡性:
bins = np.arange(6)
binned_y = np.digitize(y, bins)
将数据集X
和y
划分为三个集合。X_1
和X_stack
分别表示第一个和第二个训练集的输入变量,y_1
和y_stack
分别表示第一个和第二个训练集的输出目标变量。测试集由X_test_prin
和y_test_prin
组成:
from sklearn.model_selection import train_test_split
X_train_prin, X_test_prin, y_train_prin, y_test_prin = train_test_split(X, y,test_size=0.2,stratify=binned_y,random_state=7)
binned_y_train_prin = np.digitize(y_train_prin, bins)
X_1, X_stack, y_1, y_stack = train_test_split(X_train_prin,y_train_prin,test_size=0.33,stratify=binned_y_train_prin,random_state=7 )
另一个选择是使用来自 scikit-learn 的model_selection
模块中的StratifiedShuffleSplit
。
如何实现…
我们将使用三个基础回归器:一个神经网络,一个单一的梯度提升机,和一个梯度提升机的袋装集成。
第一个基础模型 - 神经网络
- 通过对第一个训练集进行交叉验证的网格搜索,添加一个神经网络:
X_1
为输入,y_1
为目标集合。这将找到该数据集的最佳神经网络参数。在此示例中,我们仅调整alpha
参数。不要忘记对输入进行标准化,否则网络将无法很好地运行:
from sklearn.model_selection import GridSearchCV
from sklearn.neural_network import MLPRegressor
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
mlp_pipe = Pipeline(steps=[('scale', StandardScaler()), ('neural_net', MLPRegressor())])
param_grid = {'neural_net__alpha': [0.02,0.01,0.005],
'neural_net__hidden_layer_sizes' : [(50,50,50)],
'neural_net__activation': ['relu'],
'neural_net__solver' : ['adam']
}
neural_net_gs = GridSearchCV(mlp_pipe, param_grid = param_grid,cv=3, n_jobs=-1)
neural_net_gs.fit(X_1, y_1)
- 查看网格搜索的最佳参数和最佳得分:
neural_net_gs.best_params_
{'neural_net__activation': 'relu',
'neural_net__alpha': 0.01,
'neural_net__hidden_layer_sizes': (50, 50, 50),
'neural_net__solver': 'adam'}
neural_net_gs.best_score_
0.77763106799320014
- 持久化在网格搜索中表现最好的神经网络。这将保存我们已经完成的训练,以免我们不得不反复进行训练:
nn_best = neural_net_gs.best_estimator_
import pickle
f = open('nn_best.save', 'wb')
pickle.dump(nn_best, f, protocol = pickle.HIGHEST_PROTOCOL)
f.close()
第二个基础模型 - 梯度提升集成
- 对梯度增强树进行随机网格搜索:
from sklearn.model_selection import RandomizedSearchCV
from sklearn.ensemble import GradientBoostingRegressor
param_grid = {'learning_rate': [0.1,0.05,0.03,0.01],
'loss': ['huber'],
'max_depth': [5,7,10],
'max_features': [0.4,0.6,0.8,1.0],
'min_samples_leaf': [2,3,5],
'n_estimators': [100],
'warm_start': [True], 'random_state':[7]
}
boost_gs = RandomizedSearchCV(GradientBoostingRegressor(), param_distributions = param_grid,cv=3, n_jobs=-1,n_iter=25)
boost_gs.fit(X_1, y_1)
- 查看最佳得分和参数:
boost_gs.best_score_
0.82767651150013244
boost_gs.best_params_
{'learning_rate': 0.1, 'loss': 'huber', 'max_depth': 10, 'max_features': 0.4, 'min_samples_leaf': 5, 'n_estimators': 100, 'random_state': 7, 'warm_start': True}
- 增加估算器的数量并训练:
gbt_inst = GradientBoostingRegressor(**{'learning_rate': 0.1,
'loss': 'huber',
'max_depth': 10,
'max_features': 0.4,
'min_samples_leaf': 5,
'n_estimators': 4000,
'warm_start': True, 'random_state':7}).fit(X_1, y_1)
- 对估算器进行持久化。为了方便和可重用性,持久化的代码被封装成一个函数:
def pickle_func(filename, saved_object):
import pickle
f = open(filename, 'wb')
pickle.dump(saved_object, f, protocol = pickle.HIGHEST_PROTOCOL)
f.close()
return None
pickle_func('grad_boost.save', gbt_inst)
第三个基础模型 - 梯度提升集成的袋装回归器
- 现在,进行一个小规模的网格搜索,尝试一组梯度增强树的袋装。理论上很难判断这种集成方法是否会表现良好。对于堆叠而言,只要它与其他基础估算器的相关性不太高,它就足够好:
from sklearn.ensemble import BaggingRegressor,GradientBoostingRegressor
from sklearn.model_selection import RandomizedSearchCV
param_dist = {
'max_samples': [0.5,1.0],
'max_features' : [0.5,1.0],
'oob_score' : [True, False],
'base_estimator__min_samples_leaf': [4,5],
'n_estimators': [20]
}
single_estimator = GradientBoostingRegressor(**{'learning_rate': 0.1,
'loss': 'huber',
'max_depth': 10,
'max_features': 0.4,
'n_estimators': 20,
'warm_start': True, 'random_state':7})
ensemble_estimator = BaggingRegressor(base_estimator = single_estimator)
pre_gs_inst_bag = RandomizedSearchCV(ensemble_estimator,
param_distributions = param_dist,
cv=3,
n_iter = 5,
n_jobs=-1)
pre_gs_inst_bag.fit(X_1, y_1)
- 查看最佳参数和评分:
pre_gs_inst_bag.best_score_
0.78087218305611195
pre_gs_inst_bag.best_params_
{'base_estimator__min_samples_leaf': 5,
'max_features': 1.0,
'max_samples': 1.0,
'n_estimators': 20,
'oob_score': True}
- 持久化最佳估算器:
pickle_func('bag_gbm.save', pre_gs_inst_bag.best_estimator_)
堆叠器的一些函数
- 使用类似于第九章的函数,树算法与集成方法。
handle_X_set
函数在X_stack
集合上创建预测向量的数据框。概念上,它指的是对训练集第二部分进行预测的第四步:
def handle_X_set(X_train_set_in):
X_train_set = X_train_set_in.copy()
y_pred_nn = neural_net.predict(X_train_set)
y_pred_gbt = gbt.predict(X_train_set)
y_pred_bag = bag_gbm.predict(X_train_set)
preds_df = pd.DataFrame(columns = ['nn', 'gbt','bag'])
preds_df['nn'] = y_pred_nn
preds_df['gbt'] = y_pred_gbt
preds_df['bag'] = y_pred_bag
return preds_df
def predict_from_X_set(X_train_set_in):
X_train_set = X_train_set_in.copy()
return final_etr.predict(handle_X_set(X_train_set))
- 如果你之前已经持久化了文件,并希望从这一步开始,解持久化文件。以下文件使用正确的文件名和变量名加载,以执行
handle_X_set
函数:
def pickle_load_func(filename):
f = open(filename, 'rb')
to_return = pickle.load(f)
f.close()
return to_return
neural_net = pickle_load_func('nn_best.save')
gbt = pickle_load_func('grad_boost.save')
bag_gbm = pickle_load_func('bag_gbm.save')
- 使用
handle_X_set
函数创建预测数据框。打印预测向量之间的皮尔逊相关性:
preds_df = handle_X_set(X_stack)
print (preds_df.corr())
nn gbt bag
nn 1.000000 0.867669 0.888655
gbt 0.867669 1.000000 0.981368
bag 0.888655 0.981368 1.000000
元学习器 – 额外树回归器
- 类似于第九章,树算法与集成方法,在预测数据框上训练一个额外树回归器。使用
y_stack
作为目标向量:
from sklearn.ensemble import ExtraTreesRegressor
from sklearn.model_selection import RandomizedSearchCV
param_dist = {'max_features' : ['sqrt','log2',1.0],
'min_samples_leaf' : [1, 2, 3, 7, 11],
'n_estimators': [50, 100],
'oob_score': [True, False]}
pre_gs_inst = RandomizedSearchCV(ExtraTreesRegressor(warm_start=True,bootstrap=True,random_state=7),
param_distributions = param_dist,
cv=3,
n_iter = 15,random_state=7)
pre_gs_inst.fit(preds_df.values, y_stack)
- 查看最佳参数:
pre_gs_inst.best_params_
{'max_features': 1.0,
'min_samples_leaf': 11,
'n_estimators': 100,
'oob_score': False}
- 训练额外树回归器,但增加估计器的数量:
final_etr = ExtraTreesRegressor(**{'max_features': 1.0,
'min_samples_leaf': 11,
'n_estimators': 3000,
'oob_score': False, 'random_state':7}).fit(preds_df.values, y_stack)
- 查看
final_etr
估计器的交叉验证性能:
from sklearn.model_selection import cross_val_score
cross_val_score(final_etr, preds_df.values, y_stack, cv=3).mean()
0.82212054913537747
- 查看测试集上的性能:
y_pred = predict_from_X_set(X_test_prin)
from sklearn.metrics import r2_score, mean_absolute_error
print "R-squared",r2_score(y_test_prin, y_pred)
print "MAE : ",mean_absolute_error(y_test_prin, y_pred)
print "MAPE : ",(np.abs(y_test_prin- y_pred)/y_test_prin).mean()
R-squared 0.839538887753
MAE : 0.303109168851
MAPE : 0.168643891048
- 或许我们可以进一步提高结果。将训练列与预测向量并排放置。首先修改我们一直在使用的函数:
def handle_X_set_sp(X_train_set_in):
X_train_set = X_train_set_in.copy()
y_pred_nn = neural_net.predict(X_train_set)
y_pred_gbt = gbt.predict(X_train_set)
y_pred_bag = bag_gbm.predict(X_train_set)
#only change in function: include input vectors in training dataframe
preds_df = pd.DataFrame(X_train_set, columns = cali_housing.feature_names)
preds_df['nn'] = y_pred_nn
preds_df['gbt'] = y_pred_gbt
preds_df['bag'] = y_pred_bag
return preds_df
def predict_from_X_set_sp(X_train_set_in):
X_train_set = X_train_set_in.copy()
#change final estimator's name to final_etr_sp and use handle_X_set_sp within this function
return final_etr_sp.predict(handle_X_set_sp(X_train_set))
- 按照之前的方式继续训练额外树回归器:
preds_df_sp = handle_X_set_sp(X_stack)
from sklearn.ensemble import ExtraTreesRegressor
from sklearn.model_selection import RandomizedSearchCV
param_dist = {'max_features' : ['sqrt','log2',1.0],
'min_samples_leaf' : [1, 2, 3, 7, 11],
'n_estimators': [50, 100],
'oob_score': [True, False]}
pre_gs_inst_2 = RandomizedSearchCV(ExtraTreesRegressor(warm_start=True,bootstrap=True,random_state=7),
param_distributions = param_dist,
cv=3,
n_iter = 15,random_state=7)
pre_gs_inst_2.fit(preds_df_sp.values, y_stack)
- 我们继续按之前的方式进行。查看最佳参数,并使用更多的估计器训练模型:
{'max_features': 'log2',
'min_samples_leaf': 2,
'n_estimators': 100,
'oob_score': False}
final_etr_sp = ExtraTreesRegressor(**{'max_features': 'log2',
'min_samples_leaf': 2,
'n_estimators': 3000,
'oob_score': False,'random_state':7}).fit(preds_df_sp.values, y_stack)
- 查看交叉验证性能:
from sklearn.model_selection import cross_val_score
cross_val_score(final_etr_sp, preds_df_sp.values, y_stack, cv=3).mean()
0.82978653642597144
- 我们在堆叠器的高级学习器训练中包含了原始输入列。交叉验证性能从 0.8221 提高到 0.8297。因此,我们得出结论,包含输入列的模型是最优模型。现在,在我们选择这个模型作为最终最佳模型后,我们查看估计器在测试集上的表现:
y_pred = predict_from_X_set_sp(X_test_prin)
from sklearn.metrics import r2_score, mean_absolute_error
print "R-squared",r2_score(y_test_prin, y_pred)
print "MAE : ",mean_absolute_error(y_test_prin, y_pred)
print "MAPE : ",(np.abs(y_test_prin- y_pred)/y_test_prin).mean()
R-squared 0.846455829258
MAE : 0.295381654368
MAPE : 0.163374936923
还有更多…
在尝试过 scikit-learn 中的神经网络后,你可以尝试像skflow
这样的包,它借用了 scikit-learn 的语法,但利用了谷歌强大的开源 TensorFlow。
关于堆叠,你可以尝试在整个训练集X_train_prin
上进行交叉验证性能评估和预测,而不是将其分割成两部分X_1
和X_stack
。
数据科学中的许多包都大量借鉴了 scikit-learn 或 R 的语法。
第十二章:创建一个简单的估计器
在本章中,我们将涵盖以下几种方法:
- 创建一个简单的估计器
介绍
我们将使用 scikit-learn 创建一个自定义估计器。我们将传统的统计数学和编程转化为机器学习。你可以通过使用 scikit-learn 强大的交叉验证功能,将任何统计学方法转变为机器学习。
创建一个简单的估计器
我们将进行一些工作,构建我们自己的 scikit-learn 估计器。自定义的 scikit-learn 估计器至少包括三个方法:
-
一个
__init__
初始化方法:该方法接受估计器的参数作为输入 -
一个
fit
方法:该方法用于训练估计器 -
一个
predict
方法:该方法对未见过的数据进行预测
从图示来看,类大致如下:
#Inherit from the classes BaseEstimator, ClassifierMixin
class RidgeClassifier(BaseEstimator, ClassifierMixin):
def __init__(self,param1,param2):
self.param1 = param1
self.param2 = param2
def fit(self, X, y = None):
#do as much work as possible in this method
return self
def predict(self, X_test):
#do some work here and return the predictions, y_pred
return y_pred
准备工作
从 scikit-learn 中加载乳腺癌数据集:
import numpy as np
import pandas as pd
from sklearn.datasets import load_breast_cancer
bc = load_breast_cancer()
new_feature_names = ['_'.join(ele.split()) for ele in bc.feature_names]
X = pd.DataFrame(bc.data,columns = new_feature_names)
y = bc.target
将数据划分为训练集和测试集:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=7, stratify = y)
如何实现…
一个 scikit 估计器应该有一个 fit
方法,该方法返回类本身,并且有一个 predict
方法,该方法返回预测结果:
- 以下是我们称之为
RidgeClassifier
的分类器。导入BaseEstimator
和ClassifierMixin
,并将它们作为参数传递给你的新分类器:
from sklearn.base import BaseEstimator, ClassifierMixin
from sklearn.linear_model import Ridge
class RidgeClassifier(BaseEstimator, ClassifierMixin):
"""A Classifier made from Ridge Regression"""
def __init__(self,alpha=0):
self.alpha = alpha
def fit(self, X, y = None):
#pass along the alpha parameter to the internal ridge estimator and perform a fit using it
self.ridge_regressor = Ridge(alpha = self.alpha)
self.ridge_regressor.fit(X, y)
#save the seen class labels
self.class_labels = np.unique(y)
return self
def predict(self, X_test):
#store the results of the internal ridge regressor estimator
results = self.ridge_regressor.predict(X_test)
#find the nearest class label
return np.array([self.class_labels[np.abs(self.class_labels - x).argmin()] for x in results])
让我们重点关注 __init__
方法。在这里,我们输入一个单一参数;它对应于底层岭回归器中的正则化参数。
在 fit
方法中,我们执行所有的工作。工作内容包括使用内部的岭回归器,并将类标签存储在数据中。如果类别超过两个,我们可能希望抛出一个错误,因为多个类别通常不能很好地映射到一组实数。在这个示例中,有两个可能的目标:恶性癌症或良性癌症。它们映射到实数,表示恶性程度,可以视为与良性相对立的度量。在鸢尾花数据集中,有 Setosa、Versicolor 和 Virginica 三种花。Setosaness 属性没有一个明确的对立面,除非以一对多的方式查看分类器。
在 predict
方法中,你会找到与岭回归器预测最接近的类标签。
- 现在编写几行代码应用你的新岭回归分类器:
r_classifier = RidgeClassifier(1.5)
r_classifier.fit(X_train, y_train)
r_classifier.score(X_test, y_test)
0.95744680851063835
- 它在测试集上的表现相当不错。你也可以在其上执行网格搜索:
from sklearn.model_selection import GridSearchCV
param_grid = {'alpha': [0,0.5,1.0,1.5,2.0]}
gs_rc = GridSearchCV(RidgeClassifier(), param_grid, cv = 3).fit(X_train, y_train)
gs_rc.grid_scores_
[mean: 0.94751, std: 0.00399, params: {'alpha': 0},
mean: 0.95801, std: 0.01010, params: {'alpha': 0.5},
mean: 0.96063, std: 0.01140, params: {'alpha': 1.0},
mean: 0.96063, std: 0.01140, params: {'alpha': 1.5},
mean: 0.96063, std: 0.01140, params: {'alpha': 2.0}]
它是如何工作的…
创建你自己的估计器的目的是让估计器继承 scikit-learn 基础估计器和分类器类的属性。在以下代码中:
r_classifier.score(X_test, y_test)
你的分类器查看了所有 scikit-learn 分类器的默认准确度评分。方便的是,你不需要去查找或实现它。此外,使用你的分类器时,过程与使用任何 scikit 分类器非常相似。
在以下示例中,我们使用逻辑回归分类器:
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(X_train,y_train)
lr.score(X_test,y_test)
0.9521276595744681
你的新分类器比逻辑回归稍微好了一些。
还有更多…
有时,像 Python 的statsmodels
或rpy
(Python 中的 R 接口)这样的统计包包含非常有趣的统计方法,你可能希望将它们通过 scikit 的交叉验证来验证。或者,你可以自己编写方法并希望对其进行交叉验证。
以下是一个使用statsmodels
的广义估计方程(GEE)构建的自定义估计器,该方法可以在www.statsmodels.org/dev/gee.html
找到。
GEE 使用的是一般线性模型(借鉴了 R),我们可以选择一个类似分组的变量,其中观察值在一个聚类内部可能相关,但跨聚类之间无关——根据文档的说法。因此,我们可以根据某个变量进行分组或聚类,并查看组内相关性。
在这里,我们根据 R 风格公式创建一个基于乳腺癌数据的模型:
'y ~ mean_radius + mean_texture + mean_perimeter + mean_area + mean_smoothness + mean_compactness + mean_concavity + mean_concave_points + mean_symmetry + mean_fractal_dimension + radius_error + texture_error + perimeter_error + area_error + smoothness_error + compactness_error + concavity_error + concave_points_error + symmetry_error + fractal_dimension_error + worst_radius + worst_texture + worst_perimeter + worst_area + worst_smoothness + worst_compactness + worst_concavity + worst_concave_points + worst_symmetry + worst_fractal_dimension'
我们根据特征mean_concavity
进行聚类(mean_concavity
变量未包含在 R 风格公式中)。首先导入statsmodels
模块的库。示例如下:
import statsmodels.api as sm
import statsmodels.formula.api as smf
from sklearn.base import BaseEstimator, ClassifierMixin
from sklearn.linear_model import Ridge
class GEEClassifier(BaseEstimator, ClassifierMixin):
"""A Classifier made from statsmodels' Generalized Estimating Equations documentation available at: http://www.statsmodels.org/dev/gee.html
"""
def __init__(self,group_by_feature):
self.group_by_feature = group_by_feature
def fit(self, X, y = None):
#Same settings as the documentation's example:
self.fam = sm.families.Poisson()
self.ind = sm.cov_struct.Exchangeable()
#Auxiliary function: only used in this method within the class
def expand_X(X, y, desired_group):
X_plus = X.copy()
X_plus['y'] = y
#roughly make ten groups
X_plus[desired_group + '_group'] = (X_plus[desired_group] * 10)//10
return X_plus
#save the seen class labels
self.class_labels = np.unique(y)
dataframe_feature_names = X.columns
not_group_by_features = [x for x in dataframe_feature_names if x != self.group_by_feature]
formula_in = 'y ~ ' + ' + '.join(not_group_by_features)
data = expand_X(X,y,self.group_by_feature)
self.mod = smf.gee(formula_in,
self.group_by_feature + "_group",
data,
cov_struct=self.ind,
family=self.fam)
self.res = self.mod.fit()
return self
def predict(self, X_test):
#store the results of the internal GEE regressor estimator
results = self.res.predict(X_test)
#find the nearest class label
return np.array([self.class_labels[np.abs(self.class_labels - x).argmin()] for x in results])
def print_fit_summary(self):
print res.summary()
return self
fit
方法中的代码与 GEE 文档中的代码类似。你可以根据自己的具体情况或统计方法来调整代码。predict
方法中的代码与创建的岭回归分类器类似。
如果你像运行岭回归估计器那样运行代码:
gee_classifier = GEEClassifier('mean_concavity')
gee_classifier.fit(X_train, y_train)
gee_classifier.score(X_test, y_test)
0.94680851063829785
关键在于,你将一种传统的统计方法转变为机器学习方法,利用了 scikit-learn 的交叉验证。
尝试在皮马糖尿病数据集上使用新的 GEE 分类器
尝试在皮马糖尿病数据集上使用 GEE 分类器。加载数据集:
import pandas as pd
data_web_address = "https://archive.ics.uci.edu/ml/machine-learning-databases/pima-indians-diabetes/pima-indians-diabetes.data"
column_names = ['pregnancy_x',
'plasma_con',
'blood_pressure',
'skin_mm',
'insulin',
'bmi',
'pedigree_func',
'age',
'target']
feature_names = column_names[:-1]
all_data = pd.read_csv(data_web_address , names=column_names)
import numpy as np
import pandas as pd
X = all_data[feature_names]
y = all_data['target']
将数据集分成训练集和测试集:
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, random_state=7,stratify=y)
使用 GEE 分类器进行预测。我们将使用blood_pressure
列作为分组依据:
gee_classifier = GEEClassifier('blood_pressure')
gee_classifier.fit(X_train, y_train)
gee_classifier.score(X_test, y_test)
0.80519480519480524
你也可以尝试岭回归分类器:
r_classifier = RidgeClassifier()
r_classifier.fit(X_train, y_train)
r_classifier.score(X_test, y_test)
0.76623376623376627
你可以将这些方法——岭回归分类器和 GEE 分类器——与第五章中的逻辑回归进行比较,线性模型 – 逻辑回归。
保存你训练好的估计器
保存你的自定义估计器与保存任何 scikit-learn 估计器相同。按照以下方式将训练好的岭回归分类器保存到文件rc_inst.save
中:
import pickle
f = open('rc_inst.save','wb')
pickle.dump(r_classifier, f, protocol = pickle.HIGHEST_PROTOCOL)
f.close()
要检索训练好的分类器并使用它,请执行以下操作:
import pickle
f = open('rc_inst.save','rb')
r_classifier = pickle.load(f)
f.close()
在 scikit-learn 中保存一个训练好的自定义估计器非常简单。