Sklearn 秘籍第二版(四)

原文:annas-archive.org/md5/7039549ff2b32d189f96a3420dc66360

译者:飞龙

协议:CC BY-NC-SA 4.0

第十章:使用 scikit-learn 进行文本和多类分类

本章将涵盖以下食谱:

  • 使用 LDA 进行分类

  • 使用 QDA(非线性 LDA)进行工作

  • 使用 SGD 进行分类

  • 使用朴素贝叶斯分类文档

  • 半监督学习中的标签传播

使用 LDA 进行分类

线性判别分析LDA)试图通过特征的线性组合来预测结果变量。LDA 通常用作预处理步骤。我们将在本例中演示这两种方法。

做好准备

在这个食谱中,我们将执行以下操作:

  1. 从 Google 获取股票数据。

  2. 将其重新排列成我们习惯的形式。

  3. 创建 LDA 对象以拟合和预测类别标签。

  4. 给出一个如何使用 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 分数那样精确。

  1. 首先进行几个导入,并存储你将使用的股票代码、数据的开始日期和结束日期:
%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'
  1. 现在,让我们获取股票数据:
stock_panel = data.DataReader(tickers, 'google', first_date, last_date)
  1. 这个数据结构是 pandas 中的一个面板。它类似于 在线分析处理 (OLAP) 立方体或 3D 数据框。让我们来看一下数据,更加熟悉一下收盘价,因为在比较时,我们关心的就是这些:
stock_df = stock_panel.Close.dropna()
stock_df.plot(figsize=(12, 5))

以下是输出结果:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/01c78109-cbbb-4a0e-8d89-ae29c7316169.png

好的,那么现在我们需要将每只股票的价格与它六个月后的价格进行比较。如果更高,我们将其编码为 1,否则为 0。

  1. 为此,我们将数据框向后移动 180 天并进行比较:
#this dataframe indicates if the stock was higher in 180 days
classes = (stock_df.shift(-180) > stock_df).astype(int)
  1. 接下来,我们需要做的是将数据集展开:
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()

以下是输出结果:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/a3ff74d1-9b3f-4e80-b603-1aeaf1ffe48e.png

  1. 好的,现在我们需要在 NumPy 中创建矩阵。为此,我们将使用 patsy 库。这是一个很棒的库,可以用来创建类似于 R 中的设计矩阵:
import patsy
X = patsy.dmatrix("Open + High + Low + Close + Volume + is_higher - 1", data.reset_index(),return_type='dataframe')
X.head()

以下是输出结果:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/6b83a26d-5dc5-4854-82e2-c18ba3daa2a5.png

patsy 是一个非常强大的包;例如,假设我们想应用预处理。在 patsy 中,像 R 一样,我们可以修改公式,来对应设计矩阵中的修改。这里不会做,但如果我们想将值缩放到均值为 0,标准差为 1,函数将是 scale(open) + scale(high)

  1. 现在我们已经有了数据集,让我们来拟合 LDA 对象:
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
lda = LDA()
lda.fit(X.iloc[:, :-1], X.iloc[:, -1]);
  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

这些度量描述了模型以不同方式拟合数据的效果。

precisionrecall 参数非常相似。从某些方面来看,正如以下列表所示,它们可以被看作是条件比例:

  • precision:给定模型预测的正值,实际正确的比例是多少?这也是为什么 precision 的另一个名称是 正预测值 (PPV) 的原因。

  • recall:在已知某一类别为真时,我们选择的比例是多少?我说选择是因为 recall 是搜索问题中常见的度量指标。例如,可能有一组与搜索词相关的网页——返回的比例。在第五章,线性模型 - 逻辑回归,你见过一个另一名称的 recall,叫做敏感度。

f1-score 参数试图总结 recallprecision 之间的关系。

它是如何工作的…

LDA 实际上与我们之前做的聚类非常相似。我们从数据中拟合了一个基本模型。然后,一旦我们拥有模型,就尝试预测并比较在每个类别中给定数据的似然性。我们选择那个更可能的选项。

LDA 实际上是二次判别分析QDA)的一种简化,我们将在下一个实例中讨论它。这里我们假设每个类别的协方差是相同的,但在 QDA 中,这个假设被放宽。想一想 KNN 与高斯混合模型GMM)之间的联系,以及这里和那里之间的关系。

使用 QDA —— 非线性 LDA

QDA 是一个常见技术的推广,如二次回归。它只是一个模型的推广,允许更多复杂的模型拟合,但像所有事物一样,当允许复杂性渗入时,我们也使自己的生活变得更加困难。

准备工作

我们将在上一个实例的基础上扩展,并通过 QDA 对象来看 QDA。

我们说过我们对模型的协方差做了假设。这里我们将放宽这个假设。

如何操作…

  1. 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 在分类或回归中的应用有自然的联系。

准备工作

在回归分析中,我们最小化了一个惩罚错误选择的代价函数,它是在一个连续尺度上进行的,但对于分类问题,我们将最小化一个惩罚两个(或更多)情况的代价函数。

如何操作…

  1. 首先,让我们创建一些非常基本的数据:
from sklearn import datasets
X, y = datasets.make_classification(n_samples = 500)
  1. 将数据分割成训练集和测试集:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X,y,stratify=y)
  1. 实例化并训练分类器:
from sklearn import linear_model
sgd_clf = linear_model.SGDClassifier()
#As usual, we'll fit the model:
sgd_clf.fit(X_train, y_train)
  1. 测量测试集上的性能:
from sklearn.metrics import accuracy_score
accuracy_score(y_test,sgd_clf.predict(X_test))

0.80000000000000004

还有更多…

我们可以设置class_weight参数来考虑数据集中不同程度的不平衡。

合页损失函数定义如下:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/5c055abe-eff5-4e39-bfb7-c5be9b0ccf45.png

这里,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.autosrec.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

如何操作…

好吧,准备数据比平常多花了一些时间,但我们处理的是文本数据,这些数据不像我们平常处理的矩阵数据那样可以迅速表示。

  1. 然而,现在我们准备好了,就可以启动分类器并拟合我们的模型:
from sklearn import naive_bayes
clf = naive_bayes.GaussianNB().fit(X_train, y_train)
  1. bownewgroups.target分别重命名为Xy。在拟合模型之前,我们先将数据集拆分为训练集和测试集:
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)
  1. 现在我们在测试集上拟合了一个模型,并试图预测训练集,以确定哪些类别与哪些文章相对应,让我们来看看大致的准确性:
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_bowmn_newgroups.target分别重命名为Xy。让我们创建一个训练集和一个测试集,并使用训练数据训练一个多项式贝叶斯模型:

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类别与其他两个类别相对独立,因此我们应该表现得相当好。

半监督学习中的标签传播

标签传播是一种半监督技术,利用标记数据和未标记数据来学习未标记数据。通常,受益于分类算法的数据很难标记。例如,标记数据可能非常昂贵,因此只有一部分数据进行人工标记才是具有成本效益的。尽管如此,似乎有慢慢增长的趋势支持公司雇佣分类学家。

准备工作

另一个问题领域是审查数据。你可以想象一个情况,时间的前沿将影响你收集标记数据的能力。例如,假设你对患者进行了测量并给他们服用了实验药物。在某些情况下,如果药物反应足够迅速,你可以测量药物的效果,但你可能还想预测反应较慢的药物的效果。药物可能对某些患者引起致命反应,可能需要采取挽救生命的措施。

怎么做…

  1. 为了表示半监督或审查数据,我们需要进行一些数据预处理。首先,我们将通过一个简单的例子进行演示,然后再处理一些更复杂的情况:
from sklearn import datasets
d = datasets.load_iris()
  1. 由于我们将对数据进行修改,所以让我们创建副本并在目标名称的副本中添加一个未标记成员。这样以后更容易识别数据:
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. 现在,让我们用-1更新y。这表示未标记的情况。这也是我们在名称末尾添加未标记的原因:
y[np.random.choice([True, False], len(y))] = -1
  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')
  1. 我们显然有很多未标记的数据,现在的目标是使用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)
  1. 测量准确度评分:
preds = lp.predict(X)
(preds == d.target).mean()

0.97333333333333338

还不错,尽管我们使用了所有数据,这有点像作弊。另外,鸢尾花数据集是一个相对分离的数据集。

使用整个数据集让人联想到更传统的统计方法。选择不在测试集上进行测量减少了我们对预测的关注,鼓励我们更多地理解和解释整个数据集。如前所述,理解与黑箱预测的区别在于传统统计学和机器学习。

顺便说一下,让我们来看一下 LabelSpreading,它是 LabelPropagation 的姊妹类。我们将在本节 如何工作… 中对 LabelPropagationLabelSpreading 做出技术性区分,但它们非常相似:

ls = semi_supervised.LabelSpreading()

LabelSpreadingLabelPropagation 更加鲁棒和抗噪声,正如它的工作方式所观察到的那样:

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

不要将标签传播算法遗漏的一个案例看作是它表现较差的标志。关键是我们可能赋予它一定的能力,使其在训练集上进行良好的预测,并能适用于更广泛的情况。

它是如何工作的……

标签传播通过创建一个数据点的图来工作,边缘上根据以下公式设置权重:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/66959096-80b2-4bec-94d0-8b7253f9e092.png

该算法通过标记的数据点将其标签传播到未标记的数据点。这个传播过程在一定程度上由边缘权重决定。

边缘权重可以放置在一个转移概率矩阵中。我们可以通过迭代的方式来确定实际标签的良好估计值。

第十一章:神经网络

本章我们将涵盖以下配方:

  • 感知机分类器

  • 神经网络 – 多层感知机

  • 与神经网络堆叠

介绍

最近,神经网络和深度学习非常流行,因为它们解决了很多难题,并且可能已经成为人工智能公众面貌的重要组成部分。让我们探索 scikit-learn 中可用的前馈神经网络。

感知机分类器

使用 scikit-learn,你可以探索感知机分类器,并将其与 scikit-learn 中的其他分类方法进行比较。此外,感知机是神经网络的构建模块,神经网络是机器学习中的一个重要部分,特别是在计算机视觉领域。

准备开始

让我们开始吧。过程如下:

  1. 加载 UCI 糖尿病分类数据集。

  2. 将数据集划分为训练集和测试集。

  3. 导入感知机。

  4. 实例化感知机。

  5. 然后训练感知机。

  6. 尝试在测试集上使用感知机,或者更好地计算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,我们希望预测的变量。将Xy划分为测试集和训练集。通过分层目标集来完成这一步,确保在训练集和测试集中类的比例平衡:

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)

如何实现……

  1. 对特征集进行缩放。仅在训练集上执行缩放操作,然后继续进行测试集:
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)
  1. 实例化并在训练集上训练感知机:
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)
  1. 测量交叉验证得分。将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
  1. 在测试集上评估性能。导入sklearn.metrics模块中的两个指标,accuracy_scoreroc_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:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/56833da2-8df6-4a37-a6cb-5133ddeda3c3.png

这发生在单次迭代或遍历感知机时。该过程会在多个迭代中重复,每次都会重新调整权重,从而最小化损失函数。

关于感知器和当前神经网络的状态,它们表现良好,因为研究人员已经尝试了许多方法。实际上,基于目前的计算能力,它们的表现非常好。

随着计算能力的不断提升,神经网络和感知器能够解决越来越复杂的问题,且训练时间持续缩短。

还有更多内容…

尝试通过调整感知器的超参数来运行网格搜索。几个显著的参数包括正则化参数penaltyalphaclass_weight以及max_iterclass_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 中使用神经网络非常简单,步骤如下:

  1. 加载数据。

  2. 使用标准缩放器对数据进行缩放。

  3. 进行超参数搜索。首先调整 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)

如何做…

  1. 首先对输入变量进行缩放。仅在训练数据上训练缩放器:
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
scaler.fit(X_train)

X_train_scaled = scaler.transform(X_train)
  1. 然后,在测试集上执行缩放:
X_test_scaled = scaler.transform(X_test)
  1. 最后,执行随机搜索(或如果你喜欢,也可以进行网格搜索)来找到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'}

它是如何工作的…

在神经网络的背景下,单一感知器的结构如下所示:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/a494cfb8-d5cd-4eea-8df9-c8190c0558f0.png

输出是权重和输入的点积之和的函数。该函数f是激活函数,可以是 sigmoid 曲线。例如,在神经网络中,超参数激活指的就是这个函数。在 scikit-learn 中,有 identity、logistic、tanh 和 relu 的选项,其中 logistic 即为 sigmoid 曲线。

整个网络是这样的(以下是来自 scikit 文档的图示,链接:scikit-learn.org/stable/modules/neural_networks_supervised.html):

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/1eefe899-179d-46b0-81eb-c8adbda3965c.png

使用我们熟悉的数据集——加利福尼亚住房数据集来训练神经网络是很有教育意义的。加利福尼亚住房数据集似乎更适合非线性算法,特别是树算法和树的集成。树算法在这个数据集上表现得很好,并为算法在该数据集上的表现建立了基准。

最终,神经网络表现还不错,但远不如梯度提升机好。此外,它们在计算上非常昂贵。

关于神经网络的哲学思考

神经网络是数学上通用的函数逼近器,可以学习任何函数。此外,隐藏层通常被解释为网络学习过程的中间步骤,而无需人工编写这些中间步骤。这可以来自计算机视觉中的卷积神经网络,在那里很容易看到神经网络如何推断出每一层。

这些事实构成了有趣的心智图像,并且可以应用于其他估计器。许多人往往不会把随机森林看作是树在逐步推理的过程,或者说是树与树之间的推理(也许是因为它们的结构不如有组织,而随机森林不会让人联想到生物大脑的可视化)。在更实际的细节上,如果你想组织随机森林,你可以限制它们的深度,或者使用梯度提升机。

无论神经网络是否真正智能这一事实如何,随着领域的进展和机器变得越来越聪明,携带这样的心智图像是有帮助的。携带这个想法,但要专注于结果;这就是现在机器学习的意义。

使用神经网络进行堆叠

两种最常见的元学习方法是袋装法和提升法。堆叠法使用得较少;然而,它非常强大,因为可以将不同类型的模型结合起来。这三种方法都通过一组较弱的估计器创建了一个更强的估计器。我们在第九章,树算法与集成方法中尝试了堆叠过程。在这里,我们尝试将神经网络与其他模型结合。

堆叠的过程如下:

  1. 将数据集划分为训练集和测试集。

  2. 将训练集划分为两部分。

  3. 在训练集的第一部分上训练基础学习器。

  4. 使用基础学习器对训练集第二部分进行预测。存储这些预测向量。

  5. 将存储的预测向量作为输入,目标变量作为输出。训练一个更高层次的学习器(注意我们仍然处于训练集的第二部分)。

之后,你可以查看在测试集上整体过程的结果(注意,不能通过查看测试集上的结果来选择模型)。

准备就绪

导入加利福尼亚州住房数据集以及我们一直使用的库:numpypandasmatplotlib。这是一个中等大小的数据集,但相对于其他 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)

将数据集Xy划分为三个集合。X_1X_stack分别表示第一个和第二个训练集的输入变量,y_1y_stack分别表示第一个和第二个训练集的输出目标变量。测试集由X_test_priny_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

如何实现…

我们将使用三个基础回归器:一个神经网络,一个单一的梯度提升机,和一个梯度提升机的袋装集成。

第一个基础模型 - 神经网络

  1. 通过对第一个训练集进行交叉验证的网格搜索,添加一个神经网络: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)
  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
  1. 持久化在网格搜索中表现最好的神经网络。这将保存我们已经完成的训练,以免我们不得不反复进行训练:
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()

第二个基础模型 - 梯度提升集成

  1. 对梯度增强树进行随机网格搜索:
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)
  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}
  1. 增加估算器的数量并训练:
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)
  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)

第三个基础模型 - 梯度提升集成的袋装回归器

  1. 现在,进行一个小规模的网格搜索,尝试一组梯度增强树的袋装。理论上很难判断这种集成方法是否会表现良好。对于堆叠而言,只要它与其他基础估算器的相关性不太高,它就足够好:
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)
  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}
  1. 持久化最佳估算器:
pickle_func('bag_gbm.save', pre_gs_inst_bag.best_estimator_)

堆叠器的一些函数

  1. 使用类似于第九章的函数,树算法与集成方法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)) 
  1. 如果你之前已经持久化了文件,并希望从这一步开始,解持久化文件。以下文件使用正确的文件名和变量名加载,以执行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')
  1. 使用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

元学习器 – 额外树回归器

  1. 类似于第九章,树算法与集成方法,在预测数据框上训练一个额外树回归器。使用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)
  1. 查看最佳参数:
pre_gs_inst.best_params_

{'max_features': 1.0,
 'min_samples_leaf': 11,
 'n_estimators': 100,
 'oob_score': False}
  1. 训练额外树回归器,但增加估计器的数量:
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)
  1. 查看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
  1. 查看测试集上的性能:
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
  1. 或许我们可以进一步提高结果。将训练列与预测向量并排放置。首先修改我们一直在使用的函数:
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))
  1. 按照之前的方式继续训练额外树回归器:
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)
  1. 我们继续按之前的方式进行。查看最佳参数,并使用更多的估计器训练模型:
{'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)
  1. 查看交叉验证性能:
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
  1. 我们在堆叠器的高级学习器训练中包含了原始输入列。交叉验证性能从 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_1X_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 方法,该方法返回预测结果:

  1. 以下是我们称之为 RidgeClassifier 的分类器。导入 BaseEstimatorClassifierMixin,并将它们作为参数传递给你的新分类器:
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 方法中,你会找到与岭回归器预测最接近的类标签。

  1. 现在编写几行代码应用你的新岭回归分类器:
r_classifier = RidgeClassifier(1.5) 
r_classifier.fit(X_train, y_train)
r_classifier.score(X_test, y_test)

0.95744680851063835
  1. 它在测试集上的表现相当不错。你也可以在其上执行网格搜索:
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 的statsmodelsrpy(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 中保存一个训练好的自定义估计器非常简单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值