机器学习快速参考-全-
机器学习快速参考(全)
原文:
annas-archive.org/md5/ec06d47e5e72c1b4fee4cf53683fcd2b译者:飞龙
前言
机器学习涉及开发和训练模型以预测未来的结果。本书是关于机器学习的实用指南,涵盖了所有相关的技巧和窍门。它包括关于模型选择、性能调整、训练神经网络、时间序列分析等多个主题的动手、易于获取的技术。
本书针对那些不仅想理解机器学习算法背后的概念,还想了解其数学原理的读者量身定制。然而,我们试图在这两者之间找到一个平衡点。
本书面向的对象
如果你是一名机器学习从业者、数据科学家、机器学习开发者或工程师,本书将为你构建机器学习解决方案提供一个参考点。如果你是一名中级机器学习开发者或数据科学家,正在寻找一个快速、便捷的参考来了解所有机器学习概念,本书也将对你有所帮助。为了从本书中获得最佳效果,你需要对机器学习有一定的了解。
本书涵盖的内容
第一章,学习的量化,为后续章节奠定了基础。首先,我们将理解统计模型的意义。我们还将讨论 Leo Breiman 关于统计建模的观点。随后,我们将讨论曲线及其为何如此重要。寻找变量间关联和建模的典型方法之一是曲线拟合,这一内容将在本章中介绍。
构建模型的一个步骤是将数据分割。我们将讨论这一步骤背后的原因,并考察一种实施方法。在我们构建模型的过程中,往往并非一帆风顺,我们会遇到许多问题。我们经常遇到过拟合和欠拟合,这有多种原因。我们需要了解原因,并学习如何克服它们。此外,我们还将讨论过拟合和欠拟合与偏差和方差的关系。本章将根据神经网络讨论这些概念。正则化是模型构建过程中的一个超参数,它是模型构建不可或缺的一部分。我们将了解为什么需要它。本章还将讨论交叉验证、模型选择和 0.632+自助法,因为它们有助于数据科学家微调模型。
第二章,评估核学习,解释了支持向量机(SVMs)是如何成为最复杂的模型之一,并在分类和回归领域吸引了大量关注。然而,从业者仍然发现它们难以掌握,因为它们涉及大量的数学。然而,我们试图保持简单并保持数学性,以便您应该能够理解 SVM 的技巧。此外,我们将探讨核技巧,它通过简化计算,在一定程度上将 SVM 提升到了另一个层次。我们将研究不同类型的核及其用法。
第三章,集成学习中的性能,解释了如何根据打包和提升的概念构建模型,这些概念在黑客马拉松的世界中占据主导地位。我们将详细讨论打包和提升。它们导致了众多优秀算法的创建,如随机森林和梯度提升。我们将通过用例详细讨论每个算法,以便您能够理解这两个算法之间的区别。此外,本章的一个重要部分处理了超参数的优化。
第四章,训练神经网络,涵盖了神经网络,这些网络一直被视为需要大量努力才能理解的黑盒算法。我们试图揭开围绕神经网络复杂性的神秘面纱。我们从详细阐述神经网络如何与人类大脑相似开始。本章还涵盖了诸如权重和偏差等参数是什么,以及神经网络是如何学习的。神经网络的学习过程包括网络初始化、前向传播系统和成本计算。一旦计算出成本,反向传播就会启动。
接下来是模型中的挑战,例如梯度爆炸、梯度消失和过拟合。本章涵盖了所有这些问题,帮助我们理解为什么会出现这些挑战,并解释了如何克服它们。
第五章,时间序列分析,涵盖了用于分析需求预测的不同时间序列模型,无论是股价预测还是销售预测,或者其他任何东西。几乎每个行业都会遇到这样的用例。为了执行这些用例,有多种方法,而我们介绍的是自回归模型、ARMA、ARIMA 以及其他模型。我们从自回归的概念开始。然后是平稳性,这是这些模型的一个重要元素。本章探讨了平稳性以及我们如何检测它。此外,还涵盖了模型的评估。利用一个用例详细讨论了计量经济学中的异常检测。
第六章,自然语言处理,解释了自然语言处理是如何让文本数据“说话”的。有许多算法使这一过程成为可能。我们不能直接处理文本数据。它需要被矢量化并嵌入。本章涵盖了实现这一目标的多种方式,例如 TF-IDF 和词袋方法。
我们还将讨论如何利用这些方法进行情感分析,并比较不同方法的结果。然后,我们将转向主题建模,其主要目的是从语料库中提取主要主题。稍后,我们将考察一个用例并使用贝叶斯方法解决它。
第七章,时序和序列模式发现,关注为什么有必要研究频繁项集以及我们如何处理它们。我们介绍了使用 Apriori 和频繁模式增长算法来揭示事务数据中的发现。
第八章,概率图模型,涵盖了贝叶斯网络以及它们在机器学习中的影响。我们将查看基于条件概率表的贝叶斯网络(树)。
第九章,深度学习中的选讲主题,解释了随着世界从简单的商业分析过渡到深度学习,我们有很多东西需要赶上。本章探讨了权重初始化、层形成、成本计算和反向传播。随后,我们将介绍 Hinton 的胶囊网络并探讨其工作原理。
第十章,因果推断,讨论了在时间序列中提供因果方向视图的算法。我们的利益相关者经常提到目标变量的因果关系。因此,我们使用时间序列的格兰杰因果模型来解决这个问题,并且我们也讨论了使我们能够实现因果关系的贝叶斯技术。
第十一章,高级方法,解释了在管道中有许多最先进的模型,并在本书中需要特别提及。本章应有助于您理解和应用它们。此外,我们讨论了独立成分分析以及它与主成分分析的不同之处。随后,我们讨论了多重插补的贝叶斯技术及其重要性。我们还将了解自组织映射及其重要性。最后,我们还将简要介绍压缩感知。
为了充分利用这本书
本书需要具备 Python、R 和机器学习的基本知识。
下载示例代码文件
您可以从www.packt.com的账户下载本书的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packt.com/support并注册,以便将文件直接通过电子邮件发送给您。
您可以通过以下步骤下载代码文件:
-
在www.packt.com上登录或注册。
-
选择“支持”标签。
-
点击“代码下载与勘误”。
-
在搜索框中输入书籍名称,并遵循屏幕上的说明。
文件下载完成后,请确保您使用最新版本解压缩或提取文件夹:
-
Windows 上的 WinRAR/7-Zip
-
Mac 上的 Zipeg/iZip/UnRarX
-
Linux 上的 7-Zip/PeaZip
书籍的代码包也托管在 GitHub 上,地址为github.com/PacktPublishing/Machine-Learning-Quick-Reference。如果代码有更新,它将在现有的 GitHub 仓库中更新。
我们还有其他来自我们丰富图书和视频目录的代码包可供选择,地址为github.com/PacktPublishing/。去看看吧!
下载彩色图像
我们还提供了一份包含本书中使用的截图/图表彩色图像的 PDF 文件。您可以从这里下载:www.packtpub.com/sites/default/files/downloads/9781788830577_ColorImages.pdf。
使用的约定
本书使用了多种文本约定。
CodeInText:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“现在我们将使用resample函数提取一个 bootstrap 样本:”
代码块设置如下:
#using "resample" function generate a bootstrap sample
boot_samp = resample(dataset, replace=True, n_samples=5, random_state=1)
粗体:表示新术语、重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词在文本中显示如下。以下是一个示例:“从管理面板中选择系统信息。”
警告或重要注意事项看起来像这样。
小贴士和技巧看起来像这样。
联系我们
读者的反馈总是受欢迎的。
一般反馈:如果您对本书的任何方面有疑问,请在邮件主题中提及书名,并给我们发送电子邮件至customercare@packtpub.com。
勘误:尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,如果您能向我们报告,我们将不胜感激。请访问www.packt.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入详细信息。
盗版: 如果您在互联网上以任何形式发现我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过copyright@packt.com与我们联系,并附上材料的链接。
如果您有兴趣成为作者: 如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问 authors.packtpub.com。
评价
请留下您的评价。一旦您阅读并使用了这本书,为何不在购买它的网站上留下评价呢?潜在读者可以查看并使用您的客观意见来做出购买决定,我们 Packt 公司可以了解您对我们产品的看法,并且我们的作者可以查看他们对书籍的反馈。谢谢!
想了解更多关于 Packt 的信息,请访问 packt.com。
第一章:量化学习算法
我们已经进入了一个时代,我们正在构建智能或智能机器。这种智能或智能是通过基于数学/统计的智能算法注入到机器中的。这些算法使系统或机器能够自动学习,无需任何人为干预。作为这个例子,今天我们周围有大量的移动应用程序。今天的主要即时通讯应用之一是 WhatsApp(目前由 Facebook 拥有)。每当我们将信息输入 WhatsApp 的文本框时,例如,输入I am...,就会出现一些词提示,如..going home、Rahul、traveling tonight等等。我们能猜测这里发生了什么,为什么吗?会涌现出多个问题:
-
系统正在学习什么?
-
它从哪里学习?
-
它是如何学习的?
让我们在本章中回答所有这些问题。
在本章中,我们将涵盖以下主题:
-
统计模型
-
学习曲线
-
曲线拟合
-
建模文化
-
过拟合和正则化
-
训练、验证和测试
-
交叉验证和模型选择
-
自举法
统计模型
统计模型是通过数据、数学或统计学捕捉到的真理的近似,在这里充当使能者。这种近似用于预测事件。统计模型不过是一个数学方程。
例如,假设我们向银行申请房屋贷款。银行会问我们什么?他们首先会要求我们提供大量文件,例如工资单、身份证明文件、关于我们要购买的房屋的文件、水费账单、我们当前的贷款数量、我们的抚养人数等等。所有这些文件不过是银行用来评估和检查我们信用度的数据:

这意味着你的信用度是工资、贷款数量、抚养人数等因素的函数。我们可以通过数学方法得出这个方程或关系。
统计模型是一个数学方程,用于特定商业场景的给定数据。
在下一节中,我们将看到模型是如何学习的,以及模型如何不断变得更好。
学习曲线
学习曲线的基本前提是,你花在某一件事上的时间越多,你通常越擅长。最终,完成任务所需的时间会不断下降。这被称为不同的名称,如改进曲线、进步曲线和启动函数。
例如,当你开始学习手动驾驶汽车时,你会经历一个学习周期。最初,你非常小心地操作刹车、离合器和档位。你必须不断提醒自己何时以及如何操作这些部件。
但是,随着日子的推移,你继续练习,你的大脑就会习惯并训练整个流程。随着每一天的过去,你的驾驶会越来越顺畅,你的大脑会对情况做出反应,而无需意识到这一点。这被称为潜意识智能。通过大量的练习和从有意识智能过渡到具有循环的潜意识智能,你将达到这个阶段。
机器学习
让我来定义机器学习和其组成部分,这样你就不至于在听到很多术语时感到困惑。
按照 Tom Mitchell 的话说,“如果计算机程序在任务 T 中,关于性能度量 P,从经验 E 中学习,那么它的性能在 T 中的任务,按照 P 来衡量,会随着经验 E 的提高而提高。”也有另一种理论说,机器学习是赋予计算机学习能力而不需要明确编程的领域。
例如,如果一台计算机被给出了如下案例,[(父亲,母亲),(叔叔,阿姨),(兄弟,姐妹)],基于此,它需要找出(儿子,?)。也就是说,给定儿子,将是什么相关项?为了解决这个问题,计算机程序将遍历之前的记录,并试图理解和学习从这些组合中跳转到另一个记录时的关联和模式。这被称为学习,它通过算法进行。随着记录的增加,即经验的增加,机器会变得越来越聪明。
让我们看看以下图中所示的不同机器学习分支:

我们将如下解释前面的图示:
-
监督学习:在这种学习中,输入变量和输出变量都是我们所知的。在这里,我们应建立输入变量和输出之间的关系,学习将基于这一点。在其下有两种类型的问题,如下所示:
-
回归问题:它有一个连续的输出。例如,一个房价数据集,其中需要根据面积、地区、城市、房间数量等输入变量预测房价。要预测的价格是一个连续变量。
-
分类:它有一个离散的输出。例如,根据薪水、性别、家庭成员数量等预测一个员工是否会离开组织。
-
-
无监督学习:在这种场景中,没有输出变量。我们应根据所有给定的变量提取一个模式。例如,根据年龄、性别、收入等对客户进行细分。
-
强化学习:这是机器学习的一个领域,其中采取适当的行动以最大化奖励。例如,训练狗接球并给予奖励——如果它们执行这个动作,我们就奖励狗;否则,我们责备它们,导致惩罚。
赖特模型
在赖特模型中,学习曲线函数定义为以下:

变量如下:
-
Y:每单位累积平均时间
-
X:累积生产的单位数量
-
a:生产第一个单位所需的时间
-
b:函数在图表纸上绘制时的斜率(学习率的对数/2 的对数)
以下曲线具有一个垂直轴(y轴),表示特定工作的学习情况,以及一个对应学习所需时间的水平轴。一个开始陡峭的学习曲线可以理解为快速进步的标志。以下图表显示了赖特学习曲线模型:

然而,出现的问题是,它与机器学习有何关联?我们现在将详细讨论这个问题。
让我们通过以下步骤讨论一个实际上是监督学习问题的场景:
-
我们将数据分成一个训练集(我们在其上让系统学习并形成模型)和一个验证集(我们在其上测试系统学习的好坏)。
-
下一步将是取训练集的一个实例(观察值)并利用它来估计一个模型。训练集上的模型错误将为 0。
-
最后,我们将找出验证数据上的模型错误。
第二步和第三步通过取多个实例(训练大小)如 10、50 和 100,研究训练错误和验证错误,以及它们与实例数量(训练大小)的关系来重复。这种曲线——或这种关系——在机器学习场景中被称为学习曲线。
让我们处理一个综合发电厂数据集。特征包括每小时平均环境变量,即温度(T)、环境压力(AP)、相对湿度(RH)和排气真空(V),以预测工厂的净每小时电能输出(PE):
# importing all the libraries
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import learning_curve
import matplotlib.pyplot as plt
#reading the data
data= pd.read_excel("Powerplant.xlsx")
#Investigating the data
print(data.info())
data.head()
从这个结果中,我们能够看到数据中变量的数据结构:

输出结果如下:

第二个输出可以让你对数据有一个良好的感觉。
数据集有五个变量,其中环境温度(AT)和 PE(目标变量)。
让我们改变数据的训练大小并研究它对学习的影响。创建了一个train_size列表,包含不同的训练大小,如下面的代码所示:
# As discussed here we are trying to vary the size of training set
train_size = [1, 100, 500, 2000, 5000]
features = ['AT', 'V', 'AP', 'RH']
target = 'PE'
# estimating the training score & validation score
train_sizes, train_scores, validation_scores = learning_curve(estimator = LinearRegression(), X = data[features],y = data[target], train_sizes = train_size, cv = 5,scoring ='neg_mean_squared_error')
让我们生成learning_curve:
# Generating the Learning_Curve
train_scores_mean = -train_scores.mean(axis = 1)
validation_scores_mean = -validation_scores.mean(axis = 1)
import matplotlib.pyplot as plt
plt.style.use('seaborn')
plt.plot(train_sizes, train_scores_mean, label = 'Train_error')
plt.plot(train_sizes, validation_scores_mean, label = 'Validation_error')
plt.ylabel('MSE', fontsize = 16)
plt.xlabel('Training set size', fontsize = 16)
plt.title('Learning_Curves', fontsize = 20, y = 1)
plt.legend()
我们得到以下输出:

从前面的图中,我们可以看到当训练集大小仅为 1 时,训练误差为 0,但验证误差超过400。
随着训练集大小的不断增加(从 1 到 100),训练误差持续上升。然而,随着模型在验证集上的表现越来越好,验证误差开始急剧下降。当训练集大小达到 500 时,验证误差和训练误差开始收敛。那么,我们能从中推断出什么呢?模型的性能不会因为训练数据的量而改变。然而,如果你尝试添加更多特征,可能会产生影响,如下面的图表所示:

前面的图表显示,验证曲线和训练曲线已经收敛,因此添加训练数据将毫无帮助。然而,在下面的图表中,曲线尚未收敛,因此添加训练数据将是一个好主意:

曲线拟合
到目前为止,我们已经了解了学习曲线及其重要性。然而,它只有在尝试在可用数据和特征上拟合曲线时才会出现。但曲线拟合是什么意思呢?让我们尝试理解这一点。
曲线拟合实际上就是建立多个特征与目标之间的关系。它有助于找出特征与目标之间的关联类型。
建立关系(曲线拟合)实际上就是提出一个数学函数,该函数应该能够以某种方式解释行为模式,使其成为数据集的最佳拟合。
我们进行曲线拟合的原因有很多:
-
进行系统模拟和优化
-
确定中间点的值(插值)
-
进行趋势分析(外推)
-
进行假设检验
曲线拟合有两种类型:
- 精确拟合:在这种情况下,曲线将通过所有点。在这种情况下没有残差误差(我们很快会讨论什么被认为是误差),现在你可以将误差理解为实际误差与预测误差之间的差异。它可以用于插值,并且主要涉及分布拟合。
下面的图表显示了多项式但精确拟合:

下面的图表显示了直线但精确拟合:

- 最佳拟合:曲线不会通过所有点。将与这个曲线相关的残差。
让我们看看一些不同的场景,并研究它们以了解这些差异。
在这里,我们将为两个数字拟合曲线:
# importing libraries
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
# writing a function of Line
def func(x, a, b):
return a + b * x
x_d = np.linspace(0, 5, 2) # generating 2 numbers between 0 & 5
y = func(x_d,1.5, 0.7)
y_noise = 0.3 * np.random.normal(size=x_d.size)
y_d = y + y_noise
plt.plot(x_d, y_d, 'b-', label='data')
popt, pcov = curve_fit(func, x_d, y_d) # fitting the curve
plt.plot(x_d, func(x_d, *popt), 'r-', label='fit')
从这个结果中,我们将得到以下输出:

在这里,我们使用了两个点来拟合直线,我们可以清楚地看到它变成了一个精确的拟合。当我们引入三个点时,我们会得到以下结果:
x_d = np.linspace(0, 5, 3) # generating 3 numbers between 0 & 5
运行整个代码并关注输出:

现在,你可以看到漂移和噪声的影响。它已经开始呈现出曲线的形状。一条直线可能在这里不是一个好的拟合(然而,说得太早了)。它不再是一个精确的拟合。
如果我们尝试引入 100 个点并研究其效果呢?到目前为止,我们知道如何引入点的数量。
通过这样做,我们得到以下输出:

这不是一个精确的拟合,而是一个试图概括整个数据集的最佳拟合。
残差
残差是观察值或真实值与预测(拟合)值之间的差异。例如,在以下图中,一个残差是(A-B),其中A是观察值,B是拟合值:

前面的散点图表明我们正在拟合一条可能代表所有数据点行为的直线。然而,值得注意的是,这条线并没有穿过所有点。大多数点都在线上方。
残差的和与平均值总是为 0。∑e =0 和 e =0 的平均值。
统计建模——莱奥·布雷曼的两种文化
每当我们试图分析数据并最终做出预测时,我们会考虑两种方法,这两种方法都是由加州大学伯克利分校教授莱奥·布雷曼在他的 2001 年论文《统计建模:两种文化》中发现的。
任何分析都需要数据。分析可以如下进行:

X(特征)向量经过一个自然箱,这转化为响应。自然箱试图建立X和Y之间的关系。通常,这个分析有一些目标,如下所示:
-
预测:用未来的输入特征来预测响应
-
信息:找出并理解响应和驱动输入变量之间的关联
布雷曼指出,在解决商业问题时,有两种截然不同的方法:
-
数据建模文化:在这种模型中,自然以随机模型的形式出现,该模型估计必要的参数。线性回归、逻辑回归和 Cox 模型通常在自然框下运行。这种模型讨论的是观察数据的模式,并试图设计出对所观察到的内容的近似。基于他们的经验,科学家或统计学家会决定使用哪种模型。这是模型在问题和数据之前的情况,从这个模型中得出的解决方案更倾向于模型架构。Breiman 说,过度依赖这种方法并不能帮助统计学家应对各种问题。当涉及到地震预测、降雨预测和全球变暖原因的解决方案时,它不会给出准确的结果,因为这种方法不关注准确性,而是关注两个目标。
-
算法建模文化:在这种方法中,使用预先设计的算法来做出更好的近似。在这里,算法使用复杂的数学来得出结论,并在自然框内运行。随着计算能力的提高和使用这些模型,很容易复制驱动因素,因为模型会一直运行,直到它学会并理解驱动结果的模式。这使得我们能够解决更复杂的问题,并更强调准确性。随着数据的增加,它比数据建模文化能给出更好的结果。
训练数据 - 开发数据 - 测试数据
这是在构建模型过程中最重要的步骤之一,它可能导致关于我们是否真的需要所有三个集合(训练集、开发集和测试集)以及如果需要,这些数据集应该如何划分的许多争论。让我们理解这些概念。
在我们有足够的数据开始建模后,我们首先需要做的是将数据分成三个部分,即训练集、开发集和测试集:

让我们考察拥有这三个集合的目标:
- 训练集:训练集用于训练模型。当我们应用任何算法时,我们是在训练集中拟合参数。在神经网络的情况下,找到权重就发生在训练集中。
假设在一个场景中,我们正在尝试拟合不同次数的多项式:
-
-
f(x) = a + bx → 1^(st) 次多项式
-
f(x) = a + bx + cx² → 2^(nd) 次多项式
-
f(x) = a + bx + cx² + dx³ → 3^(rd) 次多项式
-
在拟合模型后,我们计算所有拟合模型的训练误差:

我们不能仅根据训练误差来评估模型的好坏。如果我们这样做,可能会导致我们得到一个有偏见的模型,这个模型可能无法在未见过的数据上表现良好。为了解决这个问题,我们需要转向开发集。
-
开发集:这也被称为保留集或验证集。这个集合的目标是调整我们从训练集得到的参数。它也是对模型性能评估的一部分。根据其性能,我们必须采取步骤来调整参数。例如,控制学习率、最小化过拟合和选择最佳模型都发生在开发集中。在这里,同样会计算开发集误差,并在看到哪个模型产生最小误差后对模型进行调整。在这个阶段产生最小误差的模型仍然需要调整以最小化过拟合。一旦我们确信了最佳模型,它就会被选择,然后我们转向测试集。
-
测试集:测试集主要用于评估最佳选择的模型。在这个阶段,计算模型的准确度,如果模型的准确度没有太大偏离训练准确度和开发准确度,我们就将这个模型部署上线。
训练集、开发集和测试集的大小
通常,机器学习从业者会选择三个集合的大小比例为 60:20:20 或 70:15:15。然而,并没有一条硬性规定说开发集和测试集应该大小相等。以下图表显示了训练集、开发集和测试集的不同大小:

以下是一个三个不同集合的另一个例子:

但我们如何处理大数据的场景呢?例如,如果我们有 1000 万条记录或观测值,我们该如何划分数据?在这种情况下,机器学习从业者会将大部分数据用于训练集——多达 98-99%——其余的则分配给开发集和测试集。这样做是为了让从业者能够考虑不同类型的场景。因此,即使我们只有 1%的数据用于开发集和测试集,我们最终也会得到 10 万条记录,这是一个很好的数字。
偏差-方差权衡
在我们开始建模并试图弄清楚权衡是什么之前,让我们从以下图表中了解偏差和方差是什么:

在偏差-方差权衡中,产生了两种类型的误差,如下所示:
-
训练误差:这是在用训练输入预测输出时,拟合值与实际值之间偏差的度量。这个误差主要取决于模型的复杂度。随着模型复杂度的增加,误差似乎会急剧下降。
-
开发错误:这是预测值偏差的度量,开发集作为输入(在训练数据上使用相同模型的情况下)用于实际值。在这里,预测是在未见过的数据上进行的。我们需要最小化这个错误。最小化这个错误将决定这个模型在实际场景中的好坏。
随着算法复杂性的不断增加,训练错误会下降。然而,开发错误或验证错误会持续下降直到某个点,然后上升,如下面的图表所示:

上述图表可以这样解释:
-
欠拟合:每个数据集都有特定的模式和属性,这是由于数据集中存在的变量所导致的。除此之外,它还有由不是数据集部分的变量引起的随机和潜在模式。每当我们要提出一个模型时,理想情况下模型应该从现有变量中学习模式。然而,这些模式的学习也取决于你的算法有多好、有多稳健。假设我们选择了一个无法从数据集中推导出基本模式的模型——这被称为欠拟合。在前面的图表中,这是一个分类场景,我们正在尝试对x和o进行分类。在图 1 中,我们试图使用线性分类算法对数据进行分类,但我们可以看到它导致了大量的误分类错误。这是一个欠拟合的例子。
-
过拟合:从图 1 进一步扩展,我们试图使用复杂的算法来找出模式并进行分类。值得注意的是,在第二个图表中,误分类错误已经下降,因为在这里使用的复杂模型能够检测到模式。开发错误(如前图所示)也下降了。我们将增加模型的复杂性并观察会发生什么。图 3 表明,现在模型中没有误分类错误。然而,如果我们看下面的图表,我们可以看到开发错误现在非常高。这是因为模型正在从数据集中不存在的变量所展示的误导性和随机模式中学习。这意味着它已经开始学习集合中存在的噪声。这种现象被称为过拟合。
-
偏差:我们见过这种情况有多少次?这种情况发生在我们使用了一个算法,但它并不合适。这意味着在这里被使用的函数对这个场景的相关性很小,并且它无法提取正确的模式。这导致了一个称为偏差的错误。这种情况主要是由于对数据做出了一定的假设,并使用了一个可能正确但并不正确的模型。例如,如果我们必须为某种情况使用二次多项式,我们会使用简单的线性回归,这并不能建立响应变量和解释变量之间的正确关系。
-
方差:当我们有一个用于训练模型的训练数据集时,即使我们将训练集更改为来自同一群体的集合,模型也应该保持免疫。如果数据集中的变化导致模型性能的变化,这被称为方差错误。这种情况是由于模型学习到了噪声(未解释的变化)而发生的,因此,这个模型在未见过的数据上不能给出好的结果:

我们将如下解释前面的图表:
-
如果训练误差下降,而(开发误差-训练误差)上升,这意味着高方差的情况(前表中的第 1 种情况)
-
如果训练误差和开发误差上升,而(开发误差-训练误差)下降,这意味着高偏差的情况(前表中的第 2 种情况)
-
如果训练误差和开发误差上升,并且(开发误差-训练误差)也上升,这意味着高偏差和高方差(前表中的第 3 种情况)
-
如果训练误差上升,而开发误差下降,即(开发误差-训练误差)下降,这意味着低偏差和低方差(前表中的第 4 种情况)
我们应该始终努力追求第 4 种情况,即训练误差低,以及开发集误差低。在前表中,这就是我们必须找出偏差-方差权衡的地方,它由一条垂直线表示。
现在,以下问题产生了:我们如何对抗过拟合?让我们通过继续下一节来找出这个答案。
正则化
现在我们对机器学习建模中过拟合的含义有了相当的了解。只是为了重申,当模型学习到数据中渗透的噪声时,它试图学习由于随机机会发生的模式,因此发生过拟合。由于这种现象,模型的泛化能力受到威胁,它在未见过的数据上的表现不佳。因此,模型的准确性急剧下降。
我们能否对抗这种现象?答案是肯定的。正则化来拯救。让我们弄清楚它能提供什么以及它是如何工作的。
正则化是一种技术,它使模型不会变得复杂,以避免过拟合。
让我们看一下下面的回归方程:

这个损失函数如下所示:

损失函数有助于调整系数并检索最优值。在训练数据中存在噪声的情况下,系数不会很好地泛化,并会遇到过拟合。正则化通过使这些估计或系数趋向于 0 来解决这个问题。
现在,我们将介绍两种类型的正则化。在后面的章节中,将介绍其他类型。
岭回归(L2)
由于岭回归,我们需要对损失函数做一些修改。原始损失函数通过一个收缩成分来增加:

现在,这个修改后的损失函数需要被最小化以调整估计或系数。在这里,lambda 正在调整正则化损失函数的参数。也就是说,它决定了应该对模型的灵活性进行多少惩罚。模型的灵活性取决于系数。如果模型的系数增加,灵活性也会增加,这对我们的模型来说不是好兆头。同样,当系数减少时,灵活性受到限制,模型开始表现更好。每个估计参数的收缩使模型在这里变得更好,这就是岭回归所做的事情。当 lambda 不断升高时,即λ → ∞,惩罚成分增加,估计开始缩小。然而,当λ → 0时,惩罚成分减少并开始成为一个普通最小二乘法(OLS)来估计线性回归中的未知参数。
最小绝对收缩和选择算子
最小绝对收缩和选择算子(LASSO)也称为L1。在这种情况下,前面的惩罚参数被|βj|替换:

通过最小化前面的函数,找到并调整系数。在这种情况下,当 lambda 变得更大,λ → ∞,惩罚成分增加,因此估计开始缩小并变为 0(在岭回归的情况下不会发生;相反,它只会接近 0)。
交叉验证和模型选择
我们已经讨论了过拟合。这与模型的不稳定性有关,因为模型的真正测试发生在它处理未见和新的数据时。模型最重要的一个方面是,它不应该捕捉到噪声,而不仅仅是常规模式。
验证只是确保模型是响应和预测变量之间关系的一种保证,作为输入特征的输出结果,而不是噪声。一个良好的模型指标不是通过训练数据和错误来衡量。这就是为什么我们需要交叉验证。
在这里,我们将坚持使用 k 折交叉验证,并了解它是如何被使用的。
K 折交叉验证
让我们一步步了解 k 折交叉验证的步骤:
-
数据被划分为 k 个子集。
-
一组数据被保留用于测试/开发,而模型建立在其余数据(k-1)上。也就是说,其余的数据形成训练数据。
-
第二步会重复 k 次。也就是说,一旦前一步完成,我们就转向第二组,并形成测试集。剩下的(k-1)数据随后可用于构建模型:

- 计算一个错误并取所有 k 次试验的平均值。
每个子集都有一次机会成为验证/测试集,因为大部分数据被用作训练集。这有助于减少偏差。同时,几乎所有数据都被用作验证集,这有助于减少方差。
如前图所示,k = 5已被选择。这意味着我们必须将整个数据集划分为五个子集。在第一次迭代中,子集 5 成为测试数据,其余成为训练数据。同样,在第二次迭代中,子集 4 变为测试数据,其余成为训练数据。这个过程会持续五次迭代。
现在,让我们尝试用 Python 通过使用 K 近邻分类器来分割训练数据和测试数据来完成这个任务:
from sklearn.datasets import load_breast_cancer # importing the dataset
from sklearn.cross_validation import train_test_split,cross_val_score # it will help in splitting train & test
from sklearn.neighbors import KNeighborsClassifier
from sklearn import metrics
BC =load_breast_cancer()
X = BC.data
y = BC.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=4)
knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(X_train, y_train)
y_pred = knn.predict(X_test)
print(metrics.accuracy_score(y_test, y_pred))
knn = KNeighborsClassifier(n_neighbors=5)
scores = cross_val_score(knn, X, y, cv=10, scoring='accuracy')
print(scores)
print(scores.mean())
使用交叉验证进行模型选择
我们可以通过以下代码使用交叉验证来找出哪个模型表现更好:
knn = KNeighborsClassifier(n_neighbors=20)
print(cross_val_score(knn, X, y, cv=10, scoring='accuracy').mean())
10 折交叉验证如下:
# 10-fold cross-validation with logistic regression
from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression()
print(cross_val_score(logreg, X, y, cv=10, scoring='accuracy').mean())
0.632 规则在自助法中
在我们深入探讨自助法中的 0.632 规则之前,我们需要了解什么是自助法。自助法是从由n个观察组成的总体中进行有放回随机抽样的过程。在这种情况下,样本可以包含重复的观察。例如,如果总体是(2,3,4,5,6),而我们试图抽取两个大小为 4 的随机样本,那么样本 1 将是(2,3,3,6),样本 2 将是(4,4,6,2)。
现在,让我们深入了解 0.632 规则。
我们已经看到,在使用预测估计训练误差时,1/n ∑L(y[i,]y-hat)。这实际上就是损失函数:

交叉验证是一种估计样本误差预期输出的方法:

然而,在 k 折交叉验证的情况下,它如下所示:

在这里,训练数据是X=(x1,x2.....,xn),我们从该集合中抽取自助样本(z1,.....,zb),其中每个zi是一组n个样本。
在这个场景中,以下是我们样本外误差:

在这里,fb(xi) 是从拟合到 bootstrap 数据集的模型在 xi 处的预测值。
不幸的是,这并不是一个特别好的估计器,因为用于生成 fb(xi) 的 bootstrap 样本可能已经包含了 xi。OOSE 解决了过拟合问题,但仍然存在偏差。这种偏差是由于在带有替换的抽样中产生的 bootstrap 样本中的非独特观察结果。每个样本中独特观察的平均数量约为 0.632n。为了解决偏差问题,Efron 和 Tibshirani 提出了 0.632 估计器:

模型评估
让我们看看目前正在使用的某些模型评估技术。
混淆矩阵
混淆矩阵是一个表格,有助于评估分类模型的好坏。它在已知真实值/标签时使用。大多数数据科学领域的初学者都会对混淆矩阵感到害怕,认为它比实际情况更难理解;让我告诉你——它相当简单且容易理解。
让我们通过一个例子来理解这一点。假设我们构建了一个分类模型,用于预测客户是否会购买某种产品。为此,我们需要在未见过的数据上评估模型。
有两个类别:
-
是:客户会购买产品
-
否:客户不会购买产品
从这里,我们构建了以下矩阵:

从前面的矩阵中,我们可以得出哪些推断?
-
分类器总共做出了 80 次预测。这意味着总共测试了 80 名客户,以确定他们是否会购买产品。
-
54 名客户购买了产品,而 26 名没有。
-
分类器预测 56 名客户会购买产品,而 24 名不会:

与混淆矩阵相关的不同术语如下:
-
真阳性 (TP): 这些是我们预测客户会购买产品,而他们确实购买了的情况。
-
真阴性 (TN): 这些是我们预测客户不会购买产品,而他们确实没有购买的情况。
-
假阳性 (FP): 我们预测客户会购买产品,但他们没有。这被称为类型 1错误。
-
假阴性 (FN): 我们预测 不,但客户购买了产品。这被称为类型 2错误。
现在,让我们谈谈评估分类模型所需的几个指标:
-
准确率: 这衡量了分类器的整体准确度。为了计算这个值,我们将使用以下公式:(TP+TN)/总案例数。在先前的场景中,准确率是(50+20)/80,结果是 0.875。因此,我们可以说这个分类器在 87.5%的场景中会做出正确的预测。
-
误分类率: 这衡量了分类器结果错误发生的频率。公式(FP+FN)/总案例数将给出结果。在先前的场景中,误分类率是(6+4)/80,即 0.125。因此,在 12.5%的案例中,它不会产生正确的结果。它也可以用(1-准确率)来计算。
-
TP 率: 这是一个衡量预测答案为是的概率,并且实际答案确实是是的指标。计算这个指标的公式是TP/(实际:是)。在这个场景中,TPR = (50/54)= 0.92。它也被称为灵敏度或召回率。
-
FP 率: 这是一个衡量预测答案为是,而实际答案是否的概率的指标。计算这个率的公式是FP/(实际:否)。在先前的例子中,FPR = (6/26)= 0.23。
-
TN 率: 这衡量的是当答案是否时,预测否的概率。计算这个率的公式是TN/(实际:否)。在这个场景中,TNR= (20/26)= 0.76。它也可以用(1-FPR)来计算。它也被称为特异性。
-
精确率: 这衡量了在所有预测为是的预测中预测是的正确性。它找出在总的是预测中,预测为是的正确次数。计算这个的公式是TP/(预测:是)。在这里,精确率 = (50/56)=0.89。
-
患病率: 这衡量了在总样本中给出是的比例。公式是(实际:是/总样本)。在这里,这是54/80 = 0.67。
-
空错误率: 这衡量了如果分类器只预测多数类,它会有多错误。公式是(实际:否/总样本)。在这里,这是26/80=0.325。
-
Cohen's Kappa 值: 这是一个衡量分类器性能相对于仅凭机会表现的好坏程度的指标。
-
F-Score: 这是召回率和精确率的调和平均数,即(2召回率精确率)/(召回率+精确率)。它将召回率和精确率都视为模型评估的重要指标。F-Score 的最佳值是 1,此时召回率和精确率都达到最大。F-Score 的最差值是 0。分数越高,模型越好:

接收者操作特征曲线
我们遇到过许多初露头角的数据科学家,他们构建模型,并以评估的名义,只是满足于整体准确性。然而,这并不是评估模型的正确方法。例如,假设有一个数据集,其响应变量有两个类别:愿意购买产品的客户和不愿意购买产品的客户。假设该数据集中有 95%的客户不愿意购买产品,5%的客户愿意购买。假设分类器能够正确预测多数类而不是少数类。所以,如果有 100 个观测值,TP=0,TN=95,其余的都被错误分类,这仍然会导致 95%的准确率。然而,不能得出这是一个好模型的结论,因为它根本无法对少数类进行分类。
因此,我们需要超越准确性,以便对模型有更好的判断。在这种情况下,召回率、特异性、精确度和接收者操作特征(ROC)曲线就派上用场了。我们在上一节中学习了召回率、特异性和精确度。现在,让我们了解 ROC 曲线是什么。
大多数分类器产生的分数介于 0 和 1 之间。下一步发生在我们设置阈值时,根据这个阈值,分类被决定。通常,0.5 是阈值——如果它大于 0.5,就创建一个类别 1,如果阈值小于 0.5,它就属于另一个类别 2:

对于 ROC,0.0到1.0之间的每个点都被视为一个阈值,因此阈值线持续从0.0移动到1.0。阈值将导致我们得到 TP、TN、FP 和 FN。在每一个阈值,以下指标都会被计算:
-
真阳性率 = TP/(TP+FN)
-
真阴性率 = TN/(TN + FP)
-
假阳性率 = 1 - 真阴性率
(TPR 和 FPR)的计算从 0 开始。当阈值线在 0 时,我们将能够将所有愿意购买的客户(正例)分类,而不愿意购买的客户将被错误分类,因为会有太多的假阳性。这意味着阈值线将从零开始向右移动。随着这种情况的发生,假阳性开始下降,真阳性将继续增加。
最后,我们需要在计算了每个阈值点后的 TPR 和 FPR 后,绘制一个 TPR 与 FPR 的图表:

红色对角线代表随机分类,即没有模型的分类。完美的 ROC 曲线将沿着y轴延伸,并呈现一个绝对三角形的形状,穿过y轴的顶部。
ROC 曲线下的面积
为了评估模型/分类器,我们需要确定ROC 曲线下面积(AUROC)。这个图的总面积是 1,因为 FPR 和 TPR 的最大值都是 1。因此,它呈现出一个正方形的形状。随机线完美地定位在 45 度角,将整个区域分成两个对称且等边的三角形。这意味着红色线以下和以上的面积都是 0.5。最佳和完美的分类器将是试图将 AUROC 达到 1 的那个。AUROC 越高,模型就越好。
在你拥有多个分类器的情况下,你可以使用 AUROC 来确定其中哪一个是最优的。
H 度量
二元分类必须应用技术,以便将独立变量映射到不同的标签。例如,存在一些变量,如性别、收入、现有贷款数量以及按时/不按时付款,这些变量被映射以产生一个分数,帮助我们将客户分类为良好客户(更有可能付款)和不良客户。
通常,每个人都似乎陷入了误分类率或其导出的形式,因为众所周知,曲线下面积(AUC)是我们分类模型的最佳评估者。你是通过将误分类的实例总数除以实例总数来得到这个率的。但这真的给我们一个公平的评估吗?让我们看看。在这里,我们有一个误分类率,它隐藏了一些重要的东西。很多时候,分类器会提出一个调整参数,其副作用往往是倾向于优先考虑假阳性而不是假阴性,或者反之亦然。此外,将 AUC 作为唯一的模型评估者可能会对我们产生双重打击。AUC 对不同分类器有不同的误分类成本,这是不可取的。这意味着使用这个相当于使用不同的指标来评估不同的分类规则。
正如我们已经讨论过的,任何分类器的真正测试发生在未见过的数据上,这会在模型上造成一些小数点的损失。相反,如果我们有前面提到的那种场景,决策支持系统将无法很好地执行。它将开始产生误导性的结果。
H 度量克服了不同分类器产生不同误分类成本的情况。它需要一个严重性比率作为输入,该比率检查将一个类别 0 实例误分类比将一个类别 1 实例误分类严重多少:
严重性比率 = cost_0/cost_1
在这里,cost_0 > 0 是将类别 0 数据点误分类为类别 1 的成本。
有时考虑归一化成本 c = cost_0/(cost_0 + cost_1) 更为方便。例如,severity.ratio = 2 意味着假阳性成本是假阴性的两倍。
维度降低
让我们讨论一个场景,即我们从一个银行得到了一个数据集,它包含了与银行客户相关的特征。这些特征包括客户的收入、年龄、性别、支付行为等。一旦你查看数据维度,你就会意识到有 850 个特征。你被要求构建一个模型来预测如果发放贷款,哪些客户将会违约。你会使用所有这些特征来构建模型吗?
答案应该是明确的 不。数据集中特征越多,模型过拟合的可能性就越大。尽管拥有较少的特征并不能保证不会发生过拟合,但它减少了这种可能性。这不是一个坏交易,对吧?
维度降低是处理这种问题的方法之一。它意味着在特征空间中减少维度。
有两种实现方式:
-
特征消除:这是一个过程,其中拒绝了对模型没有贡献的特征。这样做会使模型非常简单。我们知道,根据奥卡姆剃刀原理,在构建模型时我们应该追求简单。然而,执行此步骤可能会导致信息丢失,因为这种变量的组合可能对模型有影响。
-
特征提取:这是一个过程,我们创建新的独立变量,这些变量是现有变量的组合。根据这些变量的影响,我们保留或删除它们。
主成分分析是一种特征提取技术,它考虑了所有变量,并形成了变量的线性组合。随后,可以删除最不重要的变量,同时保留该变量的最重要部分。
新形成的变量(成分)彼此独立,这可以是一个模型构建过程中的福音,其中数据分布是线性可分的。线性模型的基本假设是变量彼此独立。
要理解 PCA 的功能,我们必须熟悉几个术语:
- 方差:这是从平均值平均平方偏差的度量。它也被称为 分散度,它衡量数据的可变性:

在这里,x 是平均值。
- 协方差:这是衡量两个变量在同一方向上移动程度的度量:

在 PCA 中,我们通过以下方式找出数据的模式:在数据集以 n 维度表示时具有高协方差的情况下,我们用相同 n 维度的线性组合来表示这些维度。这些组合是相互正交的,这也是它们相互独立的原因。此外,维度按照方差排序。最上面的组合排在第一位。
让我们通过以下步骤来了解 PCA 是如何工作的:
-
让我们把我们的数据集分成 Y 和 X 两部分,并只关注 X。
-
一个以 X 为矩阵,并使用均值为 0 和标准差为 1 进行标准化的矩阵被选取。让我们称这个新的矩阵为 Z。
-
现在我们来处理 Z。我们需要对其进行转置,并将转置后的矩阵与 Z 相乘。通过这样做,我们得到了我们的协方差矩阵:
协方差矩阵 = Z^TZ
-
现在,我们需要计算 Z^TZ 的特征值及其对应的特征向量。通常,协方差矩阵分解为 PDP⁻¹,其中 P 是特征向量矩阵,D 是对角矩阵,其对角线上的元素是特征值,其余位置都是 0。
-
取特征值 λ₁,λ₂,…,λp 并按从大到小的顺序排序。在这个过程中,按照相应的顺序对 P 中的特征向量进行排序。称这个排序后的特征向量矩阵为 *P**。
-
计算 Z= *ZP。这个新的矩阵,Z,是 X 的中心化/标准化版本,但现在每个观测值都是原始变量的组合,其中权重由特征向量确定。作为额外的好处,因为我们的 P 中的特征向量彼此独立,所以 Z 的列也彼此独立。
概述
在本章中,我们研究了统计模型、学习曲线和曲线拟合。我们还研究了 Leo Breiman 介绍的两个文化,它们描述了任何分析都需要数据。我们探讨了不同类型的训练、开发和测试数据,包括它们的大小。我们研究了正则化,它解释了在机器学习建模中过拟合的含义。
本章还解释了交叉验证和模型选择、自助法中的 0.632 规则,以及 ROC 和 AUC 的深入探讨。
在下一章中,我们将研究评估核学习,这是机器学习中应用最广泛的方法。
第二章:评估核学习
在机器学习中,模式识别是一个被彻底探索的领域。有许多方法和算法可以推动这种工作和分析。然而,在本章中,我们将尝试专注于核如何对整个机器学习前景产生重大影响。核学习的应用没有界限:从简单的回归问题到计算机视觉分类,它无处不在。支持向量机(SVM)是那些利用核学习的方法之一。
在本章中,我们将关注以下概念:
-
向量、线性可分性和超平面概念
-
SVM
-
核技巧
-
高斯过程
-
参数优化
向量简介
在继续探讨核心主题之前,我们希望为达到那个目标打下基础。因此,本章的这一部分非常重要。它可能对你来说很熟悉,你们中的许多人都会对此有所了解。然而,通过这条途径将设定流程。
向量是一个既有方向又有大小的对象。它由一个箭头和一个空间中的坐标 (x, y) 表示,如下面的图所示:

如前图所示,向量 OA 的坐标为 (4,3):
向量 OA= (4,3)
然而,仅用坐标定义向量是不够的——我们还需要一个方向。这意味着从 x 轴的方向。
向量的模
向量的模也称为范数。它由 ||OA|| 表示:

要找出这个向量的模长,我们可以遵循勾股定理:
OA² = OB² + AB²
= 4² + 3²
= 16 + 9
= 25
因此:
OA = √25 = 5
||OA||= 5
所以,如果有一个向量 x = (x[1], x[2], ..., x[n]):
||x||= x[1]² + x[2]² + ... + x[n]²
以及这个向量的方向如下:

点积
两个向量的点积返回一个恰好是标量的数。它表示两个向量如何相互关联。
几何上,两个向量 x 和 y 的点积如下:
x . y= ||x|| ||y|| cosθ
θ 是向量 x 和 y 之间的角度。
然而,从代数上,我们得到以下结果:

几何上,我们得到以下结果:
θ=β-α
cosθ=cos(β-α)
cosθ = cosβ cosα + sinβ sinα
cosθ = (x[1]/||x||) (y[1]/||y||) + (x[2]/||x||) (y[2]/||y||)
||x|| ||y|| cosθ= x[1]y[1] + x[2]y[2]
x . y = x[1]y[1] + x[2]y[2]
线性可分性
线性可分性意味着如果有两个类别,那么将有一个点、线、平面或超平面将输入特征分割成这样的方式,即一个类别的所有点都在一个半空间中,而第二个类别在另一个半空间中。
例如,这里有一个基于面积和价格出售房屋的案例。我们为此得到了一些数据点以及类别,即房屋已售出/未售出:

在前面的图中,所有的 N 都代表未售出(事件)类别,这是基于房屋的价格和面积推导出来的,而所有的 S 代表已售出的房屋类别。N 和 S 的数量代表确定类别的数据点数量。
在第一幅图中,N 和 S 非常接近,并且更随机,因此很难实现线性可分,无论你如何尝试分离两个类别,至少有一个会在错误分类的区域。这意味着不可能有一条正确的线来分离这两个类别。但第二幅图描述的是根据给定条件可以轻松分离的数据集。
分离方法根据维度数而变化。如果只有一个维度的情况,我们可以有一个点来分离类别。增加更多维度将需要不同的排列来分割类别。一旦我们有了 2D 情况,就需要一条线(如之前所见)来分离它。同样,超过 2D 将需要平面(一组点)来分离类别,如下所示:

分离方法:
| 维度数 | 分离方法 |
|---|---|
| 1 | 点 |
| 2 | 直线 |
| 3 | 平面 |
如果我们有超过 3D 的情况呢?我们该怎么办?有什么解决方案?有什么猜测?
超平面
你们中的许多人可能已经猜对了。当我们涉及到超过 3D 的情况时,我们会使用超平面。我们将用一点数学来定义它。
一个线性方程看起来是这样的:y = ax + b 有两个变量,x 和 y,以及一个 y-截距,即 b。如果我们把 y 重命名为 x[2],把 x 重命名为 x[1],那么方程就变成了 x[2]=ax[1] + b,这等价于 ax[1] - x[2] + b=0。如果我们定义二维向量为 x= (x[1],x[2]) 和 w=(a,-1),并且使用点积,那么方程就变成了 w.x + b = 0。
记住,x.y = x[1]y[1] + x[2]y[2]。
因此,超平面是一组满足前述方程的点集。但是,我们如何借助超平面进行分类呢?
我们定义一个假设函数 h:
h(x[i]) = +1 if w.x[i] + b ≥ 0
-1 if w.x[i] + b < 0
这可能等同于以下内容:
h(x[i])= sign(w.x[i] + b)
它也可以等同于以下内容:
sign(w.x[i]) if (x[0]=1 and w[0]=b)
这意味着它将使用 x 相对于超平面的位置来预测 y 的值。位于超平面一侧的数据点得到一个分类,而位于超平面另一侧的数据点得到另一个分类。
因为它使用超平面的方程,而这个方程恰好是值的线性组合,所以它被称为 线性分类器。超平面的形状由 w 决定,因为它有 b 和 a 元素,这些元素负责形状。
SVM
现在我们准备好理解支持向量机(SVM)。SVM 是一种算法,使我们能够将其用于分类和回归。给定一组示例,它构建一个模型将一组观测值分配到一个类别,并将其他观测值分配到第二个类别。它是一个非概率线性分类器。训练数据线性可分是关键。所有观测值或训练数据都是映射到空间的向量的表示,SVM 通过使用尽可能宽的边界来尝试对它们进行分类:

假设有两个类别 A 和 B,如前述截图所示。
并且从上一节中,我们学到了以下内容:
g(x) = w. x + b
其中:
-
w:权重向量,决定超平面的方向
-
b:偏差项,通过偏差决定超平面在 n 维空间中的位置
上述方程也被称为 线性判别函数。如果有一个向量 x[1] 位于超平面的正侧,方程变为以下形式:
g(x[1])= w.x[1] +b >0
方程将变为以下形式:
g(x[1])<0
如果 x[1] 位于超平面的正侧。
如果 g(x[1])=0 会怎样?你能猜到 x[1] 会在哪里吗?嗯,是的,它会在超平面上,因为我们的目标是找出向量的类别。
因此,如果 g(x[1])>0 => x[1] 属于 类别 A,g(x[1])<0 => x[1] 属于 类别 B。
在这里,很明显我们可以使用前面的方程进行分类。但你看到了问题吗?假设边界线如下所示:

即使在上述情况下,我们也能对这些特征向量进行分类。但这是否可取?在这里可以看到,边界线或分类器接近 类别 B。这意味着它给 类别 A 带来了很大的偏差,但惩罚了 类别 B。因此,由于靠近边界的向量中的任何干扰,它们可能会跨越并成为 类别 A 的一部分,这可能是错误的。因此,我们的目标是找到一个具有最宽边界的最优分类器,就像以下图中所示:

通过支持向量机(SVM),我们试图创建一个边界或超平面,使得每个特征向量到边界的距离最大化,这样任何微小的噪声或干扰都不会导致分类的改变。因此,在这种情况下,如果我们尝试引入某些属于 xi 类别的 y[i],我们会得到以下结果:
y[i]= ± 1
y[i] (w.x[i] + b) 将始终大于 0。yi >0 因为当 x[i ]∈ 类别 A 时,w.x[i] +b>0 则 y[i]>0,因此整个项将是正的。同样,如果 x[i ]∈ 类别 B,w.x[i] + b<0 则 y[i]<0,这将使项为正。
因此,现在如果我们必须重新设计它,我们可以说以下内容:
w.x[i] + b > γ 其中 γ 是超平面到 xi 的距离的度量。
如果存在超平面 w.x + b = 0,则点 x 到先前超平面的距离如下:
* w.x + b/ ||w||*
因此,如前所述:
w.x + b/ ||w|| ≥ γ
w.x + b ≥ γ.||w||
在进行适当的缩放后,我们可以得出以下结论:
w.x + b ≥ 1(因为 γ.||w|| = 1)
这意味着如果基于前面的结果需要进行分类,那么它遵循以下规则:
w.x + b ≥ 1 如果 x ∈ 类别 A
w.x + b ≤ -1 如果 x ∈ 类别 B
现在,再次,如果我们引入一个属于 y[i] 的类别,方程变为以下形式:
yi (w.xi + b) ≥ 1
但是,如果 y[i] (w.x[i] + b) =* 1,x[i]* 是一个支持向量。接下来,我们将学习什么是支持向量。
支持向量
我们绘制两条通过一个类别的特征向量与另一个类别的特征向量最近的边界线。这些边界线的中心线就是我们一直在谈论的超平面。例如,对于 类别 B,一条边界线通过 p 和 q,另一条边界线通过 r 和 s,因为 p 和 q 是最接近 类别 B 的特征向量,同样 r 和 s 也是。这些被称为 支持向量。我们现在将了解为什么它们被称为 支持向量:

假设我们尝试移除一个与边界线不太接近的特征向量,这将不会对超平面的位置或方向产生影响,因为超平面的位置是由穿过向量 p、q、r 和 s 的边界线决定的。由于这些点是支撑超平面的点,因此它们被称为支持向量。
因此,这个方程 y[i] (w.x[i] + b) =* 1* 在 x[i] 是 p、q、r 或 s 时成立。
我们将回到方程 w.x + b/ ||w|| ≥ γ;在这里,我们试图最大化 γ,为了做到这一点,我们需要最大化 b 或最小化 ||w||。
或者我们可以说我们必须最小化 w.w。如果我们将其转换为函数,Φ(w) = w.w 必须被最小化。Φ(w) =1/2( w.w)(这里添加了 1/2 以方便数学运算)。
因此,SVM 的目标函数变为 Φ(w) =1/2( w.w),它必须在以下约束条件下被最小化:
y[i] (w.x[i] + b) = 1
由于这是一个约束优化问题,可以使用拉格朗日乘数将其转换为无约束优化问题。
因此,L(w,b)= 1/2(w.w) - ∑ αi [yi(w.xi+b) - 1],其中 αi 是拉格朗日乘子,L(w,b)= 1/2(w.w) - ∑ α[i] y[i] (w.x[i]) - ∑ α[i] y[i] b + ∑ α[i]。
让我们通过使用最大值和最小值微积分来找出 w 和 b:
δL/δb = 0
这会导致* ∑ α[i] y[i]=0,δL/δw = 0*,这将导致 ∑ αi yi xi = w。现在,将这些结果放回拉格朗日函数中,得到以下结果:
L= ∑ α[i] - 1/2 ∑ α[i] α[j] y[i] y[j] (x[j].x[i])
这意味着如果 α[i] 的值非常高,那么相应的 x 将对超平面的位置产生很大影响。因此,对于分类和未知特征向量 z,所需的方程将是以下:
D(z) = Sign(∑ αi xi yi z + b)
如果 D(z) >0,则 z 属于类别 A;如果 D(z)<0,z 属于类别 B。让我们尝试在 Python 中做一个案例研究:

核技巧
我们已经看到,当数据是线性可分的时候,支持向量机(SVM)工作得非常顺利。只需看看以下图示;它描绘了向量不是线性可分的,但值得注意的是,它不是在二维空间中可分的:

经过一些调整,我们仍然可以使用支持向量机(SVM)。
将二维向量转换成三维向量或其他更高维度的向量可以为我们解决问题。下一步将是使用更高维度的向量来训练支持向量机(SVM)。但是,问题出现了,我们应该将向量转换到多高的维度。这意味着如果转换必须是一个二维向量,或者 3D、4D 或更高维,这实际上取决于它将可分性引入数据集。
核函数
像之前使用的那样,一个不可分的数据集总是很难处理,然而,有方法可以处理它。一种方法是通过转换将向量设置到更高的维度。但是,当我们有数百万的数据或向量时,我们真的能这样做吗?这将需要大量的计算和时间。这就是核函数帮我们解决问题的时刻。
我们已经看到了以下方程。在这个方程中,只有训练样本的点积负责使模型学习。让我们在这里尝试一个小练习:

在这里,让我们考虑两个向量:
x1=[4,8]
x2= [20,30]
现在,构建一个转换函数,它将帮助将这些二维向量转换成三维向量。
用于转换的函数如下:
t(x1,x2)= (x1²,x1 x2 √2,x2²)
#transformation from 2-D to 3-D vector
def t(x):
return [x[0]**2, np.sqrt(2)*x[0]*x[1], x[1]**2]
现在,让我们使用这个函数:
x1_3D= t(x1)
x2_3D= t(x2)
print(np.dot(x1_3D,x2_3D))# the result is 102400
但是,我们能否在不转换值的情况下做到这一点呢?核函数可以帮助我们做到这一点:
def kernel(a, b):
return a[0]**2 * b[0]**2 + 2*a[0]*b[0]*a[1]*b[1] + a[1]**2 * b[1]**2
现在是时候使用这个 kernel 了:
kernel(x1,x2) #the result is 102400
看到这样一个惊人的结果,它和之前一样,而且没有使用转换,难道不令人兴奋吗?所以,核函数是在另一个空间中导致点积类似结果的函数。
回到核技巧
因此,现在我们已经对核及其重要性有了相当的了解。并且,如上一节所述,核函数是:
K(x[i],x[j])= x[i ]. x[j]
因此,现在边际问题变成了以下:

这适用于0 ≤ α[i] ≤ C,对于任何i = 1, ..., m:

应用核技巧简单来说就是用核函数替换两个示例之间的点积。
现在假设函数也会发生变化:

这个函数将能够决定并分类类别。此外,由于S表示支持向量的集合,这意味着我们只需要在支持向量上计算核函数。
核类型
我们将在本节中解释这些类型。
线性核
假设有两个向量,x[1 ]和x[2],那么线性核可以通过以下方式定义:
K(x[1,] x[2])= x[1 . ]x[2]
多项式核
如果有两个向量,x**[1]和x**[2],线性核可以通过以下方式定义:
K(x[1,] x[2])= (x[1 . ]x[2 ]+ c)^d
其中:
-
c:常数
-
d:多项式的次数:
def polynomial_kernel(x1, x2, degree, constant=0):
result = sum([x1[i] * x2[i] for i in range(len(x1))]) + constant
return pow(result, degree)
如果我们使用之前使用的相同的x1和x2,我们得到以下结果:
x1= [4,8]
x2=[20,30]
polynomial_kernel(x1,x2,2,0)
# result would be 102400
如果我们增加多项式的次数,我们试图受到其他向量的影响,因为决策边界变得过于复杂,这会导致过拟合:


使用 6 次方的多项式核。
高斯核
多项式核给出了良好的边界线。但我们是否可以始终使用多项式核?在以下场景中不行:

解决方案是径向基函数或高斯核。这不过是向量的相似函数,将它们转换到高维空间或无限维空间。其值取决于高斯核函数的距离,如下所示:
K(x,x^') = exp(-γ ||x-x'||²)
为了不失一般性,让
:


在这个 RBF 相似函数的帮助下,所有特征向量都被计算出来。
通过网格搜索进行 SVM 示例和参数优化
在这里,我们正在使用乳腺癌数据集,其中根据癌症是否为良性/恶性进行分类。
以下是为了导入所有必需的库:
import pandas as pd
import numpy as np
from sklearn import svm, datasets
from sklearn.svm import SVC
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import classification_report
from sklearn.utils import shuffle
%matplotlib inline
现在,让我们加载乳腺癌数据集:
BC_Data = datasets.load_breast_cancer()
以下允许我们检查数据集的详细信息:
print(BC_Data.DESCR)
这是为了将数据集分为训练集和测试集:
X_train, X_test, y_train, y_test = train_test_split(BC_Data.data, BC_Data.target, random_state=0)
这是为了设置具有线性核的模型并找出准确度:
C= 1.0
svm= SVC(kernel="linear",C=C)
svm.fit(X_train, y_train)
print('Accuracy-train dataset: {:.3f}'.format(svm.score(X_train,y_train)))
print('Accuracy- test dataset: {:.3f}'.format(svm.score(X_test,y_test)))
我们得到如下的准确度输出:
Accuracy-train dataset: 0.967
Accuracy- test dataset: 0.958
使用高斯/径向基函数核并设置准确度的方式如下:
svm= SVC(kernel="rbf",C=C)
svm.fit(X_train, y_train)
print('Accuracy-train dataset: {:.3f}'.format(svm.score(X_train,y_train)))
print('Accuracy- test dataset: {:.3f}'.format(svm.score(X_test,y_test)))
输出如下所示:
Accuracy-train dataset: 1.000
Accuracy- test dataset: 0.629
显然,模型已经过拟合。因此,我们将进行归一化:
min_train = X_train.min(axis=0)
range_train = (X_train - min_train).max(axis=0)
X_train_scaled = (X_train - min_train)/range_train
X_test_scaled = (X_test - min_train)/range_train
此代码用于重新设置模型:
svm= SVC(kernel="rbf",C=C)
svm.fit(X_train_scaled, y_train)
print('Accuracy-train dataset: {:.3f}'.format(svm.score(X_train_scaled,y_train)))
print('Accuracy test dataset: {:.3f}'.format(svm.score(X_test_scaled,y_test)))
下面的输出如下:
Accuracy-train dataset: 0.948
Accuracy test dataset: 0.951
现在,过拟合问题已经不再明显。让我们继续追求最佳结果:
parameters = [{'kernel': ['rbf'],
'gamma': [1e-4, 1e-3, 0.01, 0.1, 0.2, 0.5],
'C': [1, 10, 100, 1000]},
{'kernel': ['linear'], 'C': [1, 10, 100, 1000]}]
clf = GridSearchCV(SVC(decision_function_shape='ovr'), parameters, cv=5)
clf.fit(X_train, y_train)
print("Best parameters set found on development set:")
print()
print(clf.best_params_)
print()
print("Grid scores on training set:")
print()
means = clf.cv_results_['mean_test_score']
stds = clf.cv_results_['std_test_score']
for mean, std, params in zip(means, stds, clf.cv_results_['params']):
print("%0.3f (+/-%0.03f) for %r"
% (mean, std * 2, params))
print()
在网格搜索的帮助下,我们得到了gamma、kernel和C的最佳组合,如下所示:

通过这个方法,我们可以看到并找出哪种参数组合给我们带来了更好的结果。
在这里,最佳组合是具有C值为1的线性核。
摘要
在本章中,我们介绍了向量、向量的模和点积。我们学习了可以用于分类和回归的支持向量机(SVMs)。我们研究了支持向量和核,以及不同类型的核。最后,我们通过网格搜索研究了 SVM 示例和参数优化。
在下一章,我们将学习集成学习中的性能。
第三章:集成学习中的性能
到目前为止,我们已经了解到没有两个模型会给出相同的结果。换句话说,不同的数据或算法组合会导致不同的结果。这种结果可能对某个组合有利,而对另一个组合则不然。如果我们有一个模型试图考虑这些组合,并得出一个普遍和更好的结果会怎样?这被称为集成模型。
在本章中,我们将学习关于集成建模的多个概念,如下所示:
-
Bagging
-
随机森林
-
Boosting
-
梯度提升
-
参数优化
什么是集成学习?
有时候,一个机器学习模型可能不足以应对某个场景或用例,因为它可能无法提供所需的准确率、召回率和精确度。因此,多个学习模型——或者说是模型的集成——捕捉数据的模式,并给出更好的输出。
例如,假设我们正在尝试决定夏天想去的地方。通常情况下,如果我们计划旅行,关于地点的建议会从各个角落涌来。也就是说,这些建议可能来自我们的家人、网站、朋友和旅行社,然后我们必须基于过去的好体验来做出决定:
-
家庭:假设每次我们咨询家庭成员并听取他们的意见时,有 60%的可能性他们是对的,我们最终在旅行中获得了愉快的体验。
-
朋友:同样,如果我们听取朋友的意见,他们会建议我们去可能有好体验的地方。在这些情况下,有 50%的情况发生了好体验。
-
旅游网站:旅游网站是另一个我们可以获取大量关于去哪里游玩的信息的来源。如果我们选择接受他们的建议,有 35%的可能性他们是正确的,我们有了愉快的体验。
-
旅行社:如果我们首先向旅行社咨询,可能会得到更多建议和信息。根据我们的以往经验,我们发现他们在 45%的情况下是正确的。
然而,我们必须积累所有前面的输入并做出决定,因为到目前为止,没有任何来源是 100%正确的。如果我们结合这些结果,准确率场景将如下所示:
1 - (60% * 50% * 35% * 45%) 1- 0.04725 = 0.95275
# Accuracy is close to 95%.
从这里,我们能够看到集成建模的影响。
集成方法
主要有三种构建集成模型的方法,即Bagging、Boosting和Stacking:

我们将逐一讨论每种方法。然而,在我们深入探讨之前,我们需要了解什么是自助法,这为Bagging和Boosting奠定了基础。
Bootstrapping
自助法是一种统计技术,用于根据从总体中抽取的有放回样本进行推断,并平均这些结果。在有放回抽样的情况下,样本一个接一个地抽取,一旦从总体中抽取了一个样本,总体就用抽取的数据补充:

在前面的图中,有一个包含多个组件(A, B, C, D, E, F, G, H, 和 I)的数据集。首先,我们需要绘制三个相同大小的样本。让我们随机绘制样本 1,并假设第一个元素是A。然而,在我们绘制样本 1的第二个元素之前,A被返回到数据集中。整个过程都是如此。这被称为有放回抽样。因此,我们在一个集合中多次选择相同的项目。通过遵循这个过程,我们绘制了三个样本,即样本 1、样本 2和样本 3。
当我们进一步确定样本 1、样本 2和样本 3的统计量(各种指标)时,我们发现所有统计量的平均值,以推断有关数据集(总体)的信息。这个过程被称为自助法,绘制的样本被称为自助样本。这可以用以下方程定义:
关于数据集(总体)的推断 = 样本 1、样本 2、……、样本 N 的平均值
如果仔细观察前面的图,可能会出现一些数据集的元素没有被选中或不是这三个样本的一部分的情况:
-
样本 1:(A, E, H, C)
-
样本 2:(F, G, A, C)
-
样本 3:(E, H, G, F)
因此,未被选中的元素是B、D和I。那些不是绘制样本一部分的样本被称为袋外样本(OOB)。
让我们做一个简单的编码练习,看看如何在 Python 中实现:
- 这里,我们将使用
sklearn和resample函数。让我们导入必要的库:
#importing Libraries
from sklearn.utils import resample
- 接下来,创建一个我们需要采样的数据集:
dataset=[10,2
- 现在,我们将使用
resample函数提取一个自助样本:
0,30,40,50,60,70,80,90,100]
#using "resample" function generate a bootstrap sample
boot_samp = resample(dataset, replace=True, n_samples=5, random_state=1)
- 我们将使用列表推导来提取 OOB 样本:
#extracting OOB sample
OOB=[x for x in dataset if x not in boot_samp]
- 现在,让我们打印它:
print(boot_samp)
我们得到以下输出:
[60, 90, 100, 60, 10]
我们可以看到采样中存在 60 的重复。这是由于有放回抽样造成的。
- 接下来,我们需要打印以下代码:
print(OOB)
我们得到以下输出:
[20, 30, 40, 50, 70, 80]
到此为止,我们希望得到以下结果:
OOB = 数据集 - Boot_Sample
=[10,20,30,40,50,60,70,80,90,100] - [60,90,100,60,10]
=[20,30,40,50,70,80]
这是代码得到的结果。
装袋法
带包装法代表的是重抽样聚合(bootstrap aggregation)。因此,很明显,带包装法的概念源于重抽样。它意味着带包装法具有重抽样的元素。它是一种重抽样集成方法,其中多个分类器(通常是同一算法)在从训练集/总体中随机抽取带有替换的样本(重抽样样本)上训练。所有分类器的聚合以平均或投票的形式进行。它试图减少模型中过度拟合问题的影响,如下面的图所示:

带包装法有三个阶段:
-
重抽样(Bootstrapping):这是一种统计技术,用于生成带有替换的随机样本或重抽样样本。
-
模型拟合:在这个阶段,我们在重抽样样本上建立模型。通常,用于建立模型的算法是相同的。然而,没有限制使用不同的算法。
-
模型组合:这一步骤涉及组合所有模型并取平均值。例如,如果我们应用了决策树分类器,那么每个分类器输出的概率会被平均。
决策树
决策树是一种基于分而治之方法的监督学习技术。它可以用于解决分类和回归问题。总体根据最重要的特征被分割成两个或更多同质化的样本。
例如,假设我们得到了一组从银行申请贷款的人的样本。在这个例子中,我们将计数设为 50。在这里,我们有三个属性,即性别、收入和该人持有的其他贷款数量,以预测是否给他们贷款。
我们需要根据性别、收入和其他贷款数量来细分人群,并找出最重要的因素。这往往会产生最同质化的群体。
让我们先从收入开始,尝试根据它创建细分。申请贷款的总人数为 50 人。在 50 人中,有 20 人获得了贷款。然而,如果我们按收入划分,我们可以看到划分是根据收入<100,000 和>=100,000 进行的。这并没有生成一个同质化的群体。我们可以看到 40%的申请人(20 人)获得了贷款。在收入低于 100,000 的人群中,有 30%的人设法获得了贷款。同样,46.67%的收入高于或等于 100,000 的人群设法获得了贷款。以下图显示了基于收入的树分割:

让我们再来看贷款数量。即使这次,我们也没有看到同质化群体的形成。以下图显示了基于贷款数量的树分割:

让我们来看看性别,看看它在创建同质群体方面的表现。这实际上是一个同质群体。有 15 名女性,其中 53.3%获得了贷款。34.3%的男性也最终获得了贷款。以下图表显示了基于性别的树分割:

在此帮助下,已经找到了最重要的变量。现在,我们将探讨变量的重要性。
在我们这样做之前,了解与决策树相关的术语和命名法至关重要:
-
根节点:这代表整个总体或数据集,它被分割成两个或更多同质群体。
-
决策节点:当一个节点被进一步分割成子节点时,就会创建决策节点。
-
叶节点:当一个节点没有进一步分割的可能性时,该节点被称为叶节点或终端节点。
-
分支:整个树的一个子部分被称为分支或子树:

树分割
有各种算法有助于树分割,所有这些算法都将我们带到叶节点。决策树考虑了所有可用的特征(变量),并选择会导致最纯净或最均匀分割的特征。用于分割树的算法也取决于目标变量。让我们一步一步地来探讨这个问题:
- 基尼指数:这表示如果我们从总体中随机选择两个项目,它们必须来自同一类别。如果总体完全纯净,这个事件发生的概率将是 1。它只执行二分分割。分类和回归树(CARTs)就是利用这种分割。
以下公式是计算基尼指数的方法:

这里,p(t)是具有目标变量值为t的观测值的比例。
对于二进制目标变量,t=1,最大的基尼指数值如下:
= 1 — (1/2)²— (1/2)²
= 1–2(1/2)²*
= 1- 2(1/4)*
= 1–0.5
= 0.5
基尼分数通过分割所创建的两个组中类别的混合程度来给出一个关于分割好坏的判断。完美的分离会导致基尼分数为 0,而最坏的情况分割会导致 50/50 的类别。
对于具有k个级别的名义变量,基尼指数的最大值是(1- 1/k)。
- 信息增益:让我们深入了解它,看看它是什么。如果我们碰巧有三个场景,如下所示,它们很容易描述?

由于 Z 看起来相当均匀,并且它的所有值都相似,因此它被称为 纯集。因此,解释它需要更少的努力。然而,Y 需要更多的信息来解释,因为它不是纯的。X 似乎是其中最不纯的。它试图传达的是,随机性和无序增加了复杂性,因此需要更多的信息来解释。这种随机性的程度被称为 熵。如果样本完全均匀,则熵为 0。如果样本均匀分割,其熵将为 1:
熵 = -p log[2]p - q log[2]q
在这里,p 表示成功的概率,而 q 表示失败的概率。
当目标变量是分类的,也会使用熵。它选择与父节点相比熵最低的分割点。
在这里,我们首先必须计算父节点的熵。然后,我们需要计算每个已分割的单独节点的熵,然后包括所有子节点的加权平均值。
- 方差减少:当涉及到连续的目标变量时,使用方差减少。在这里,我们使用方差来决定最佳的分割点。选择具有最低方差的分割点作为分割的标准:
方差 = 
这里,
是所有值的平均值,X 是真实值,而 n 是值的数量。
首先计算每个节点的方差,然后计算每个节点方差的加权平均值,这使我们选择最佳的节点。
树分割的参数
有许多参数我们需要调整或注意:
-
Max_depth: 最重要的参数之一是max_depth。它捕捉了树可以延伸多深的核心。树的深度越大,意味着它能够从特征中提取更多信息。然而,有时过深的深度可能会引起担忧,因为它往往会带来过拟合的问题。 -
min_samples_split: 这表示分割内部节点所需的最小样本数。这可以在考虑每个节点至少一个样本到考虑每个节点的所有样本之间变化。当我们增加此参数时,树变得更加受约束,因为它必须在每个节点考虑更多的样本。min_samples_split值的增加往往会引起欠拟合。 -
min_samples_leaf: 这是达到叶节点所需的最小样本数。将此值增加到最大可能会引起欠拟合。 -
max_features: 这是考虑最佳分割时可以使用的最大特征数。当增加最大特征数时,可能会引起过拟合。
现在,我们已经充分了解了随机森林算法。我们将在下一节讨论这一点。
随机森林算法
随机森林算法使用袋装技术。树木以以下方式种植和生长:
-
训练集中有N个观测值。从N个观测值中随机抽取样本,并替换。这些样本将作为不同树的训练集。
-
如果有M个输入特征(变量),则从M个特征中抽取m个特征作为子集,当然m < M。这样做是在树的每个节点随机选择m个特征。
-
每棵树都尽可能地生长。
-
预测是基于所有树木输出的结果聚合。在分类的情况下,聚合方法是投票,而在回归的情况下,则是所有结果的平均值:

让我们通过一个案例研究来深入了解这个概念。让我们研究乳腺癌数据。
案例研究
本案例研究中的数据是关于被检测出两种乳腺癌的患者的:
-
恶性
-
良性
这里给出了一些具有细胞核特征的特性,这些特性是从乳腺肿块细针穿刺(FNA)中计算出来的。基于这些特征,我们需要预测癌症是恶性还是良性。按照以下步骤开始:
- 导入所有必需的库:
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn import preprocessing
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
#importing our parameter tuning dependencies
from sklearn.model_selection import (cross_val_score, GridSearchCV,StratifiedKFold, ShuffleSplit )
#importing our dependencies for Feature Selection
from sklearn.feature_selection import (SelectKBest, RFE, RFECV)
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.cross_validation import ShuffleSplit
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import f1_score
from collections import defaultdict
# Importing our sklearn dependencies for the modeling
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.cross_validation import KFold
from sklearn import metrics
from sklearn.metrics import (accuracy_score, confusion_matrix,
classification_report, roc_curve, auc)
- 加载乳腺癌数据:
data= pd.read_csv("breastcancer.csv")
- 让我们了解数据:
data.info()
我们得到以下输出:

- 让我们考虑
data.head()这里:
data.head()
从这个结果中,我们得到以下输出:

- 我们从以下代码中得到数据诊断:
data.diagnosis.unique()
以下是为前面代码的输出:

- 数据描述如下:
data.describe()
我们从前面的代码中得到以下输出:

data['diagnosis'] = data['diagnosis'].map({'M':1,'B':0})
datas = pd.DataFrame(preprocessing.scale(data.iloc[:,1:32]))
datas.columns = list(data.iloc[:,1:32].columns)
datas['diagnosis'] = data['diagnosis']
datas.diagnosis.value_counts().plot(kind='bar', alpha = 0.5, facecolor = 'b', figsize=(12,6))
plt.title("Diagnosis (M=1, B=0)", fontsize = '18')
plt.ylabel("Total Number of Patients")
plt.grid(b=True)

data_mean = data[['diagnosis','radius_mean','texture_mean','perimeter_mean','area_mean','smoothness_mean', 'compactness_mean', 'concavity_mean','concave points_mean', 'symmetry_mean', 'fractal_dimension_mean']]
plt.figure(figsize=(10,10))
foo = sns.heatmap(data_mean.corr(), vmax=1, square=True, annot=True)

from sklearn.model_selection import train_test_split, cross_val_score, cross_val_predict
from sklearn import metrics
predictors = data_mean.columns[2:11]
target = "diagnosis"
X = data_mean.loc[:,predictors]
y = np.ravel(data.loc[:,[target]])
# Split the dataset in train and test:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
print ('Shape of training set : %i & Shape of test set : %i' % (X_train.shape[0],X_test.shape[0]) )
print ('There are very few data points so 10-fold cross validation should give us a better estimate')
前面的输入给出了以下输出:

param_grid = {
'n_estimators': [ 25, 50, 100, 150, 300, 500],
"max_depth": [ 5, 8, 15, 25],
"max_features": ['auto', 'sqrt', 'log2']
}
#use OOB samples ("oob_score= True") to estimate the generalization accuracy.
rfc = RandomForestClassifier(bootstrap= True, n_jobs= 1, oob_score= True)
#let's use cv=10 in the GridSearchCV call
#performance estimation
#initiate the grid
grid = GridSearchCV(rfc, param_grid = param_grid, cv=10, scoring ='accuracy')
#fit your data before you can get the best parameter combination.
grid.fit(X,y)
grid.cv_results_

# Let's find out the best scores, parameter and the estimator from the gridsearchCV
print("GridSearhCV best model:\n ")
print('The best score: ', grid.best_score_)
print('The best parameter:', grid.best_params_)
print('The best model estimator:', grid.best_estimator_)

# model = RandomForestClassifier() with optimal values
model = RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
max_depth=8, max_features='sqrt', max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, n_estimators=150, n_jobs=1,
oob_score=True, random_state=None, verbose=0, warm_start=False)
model.fit(X_train, y_train)

print("Performance Accuracy on the Testing data:", round(model.score(X_test, y_test) *100))
从这个结果中,我们可以看到测试数据的性能准确率为95.0:
#Getting the predictions for X
y_pred = model.predict(X_test)
print('Total Predictions {}'.format(len(y_pred)))
在这里,总预测数为114:
truth = pd.DataFrame(y_test, columns= ['Truth'])
predictions = pd.DataFrame(y_pred, columns= ['Predictions'])
frames = [truth, predictions]
_result = pd.concat(frames, axis=1)
print(_result.shape)
_result.head()

# 10 fold cross-validation with a Tree classifier on the training dataset# 10 fold
#splitting the data, fitting a model and computing the score 10 consecutive times
cv_scores = []
scores = cross_val_score(rfc, X_train, y_train, cv=10, scoring='accuracy')
cv_scores.append(scores.mean())
cv_scores.append(scores.std())
#cross validation mean score
print("10 k-fold cross validation mean score: ", scores.mean() *100)
从这个结果中,我们可以看到 10 折交叉验证的平均分数为94.9661835749:
# printing classification accuracy score rounded
print("Classification accuracy: ", round(accuracy_score(y_test, y_pred, normalize=True) * 100))
在这里,我们可以看到分类准确率为95.0:
# Making the Confusion Matrix
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(12,6))
ax = plt.axes()
ax.set_title('Confusion Matrix for both classes\n', size=21)
sns.heatmap(cm, cmap= 'plasma',annot=True, fmt='g') # cmap
plt.show()

# The classification Report
target_names = ['Benign [Class 0]', 'Malignant[Class 1]']
print(classification_report(y_test, y_pred, target_names=target_names))

y_pred_proba = model.predict_proba(X_test)[::,1]
fpr, tpr, _ = metrics.roc_curve(y_test, y_pred_proba)
auc = metrics.roc_auc_score(y_test, y_pred_proba)
plt.plot(fpr,tpr,label="curve, auc="+str(auc))
plt.legend(loc=4)
plt.show()

前面的图表是接收者操作特征(ROC)指标,用于通过交叉验证评估分类器输出的质量。
前面的图表显示了 ROC 对所选特征(['compactness_mean', 'perimeter_mean', 'radius_mean', 'texture_mean', 'concavity_mean', 'smoothness_mean'])和由 k 折交叉验证创建的诊断相关变量的响应。
ROC 面积为0.99相当不错。
提升法
当提到袋装法时,它可以应用于分类和回归。然而,还有一种技术也是集成家族的一部分:提升。然而,这两种技术的底层原理相当不同。在袋装法中,每个模型都是独立运行的,然后在最后汇总结果。这是一个并行操作。提升以不同的方式运作,因为它按顺序流动。这里的每个模型运行并将显著特征传递给另一个模型:

梯度提升
为了解释梯度提升,我们将采取本·戈尔曼的路线,他是一位伟大的数据科学家。他能够用一种数学而简单的方式解释它。让我们假设我们有九个训练示例,其中我们需要根据三个特征预测一个人的年龄,例如他们是否喜欢园艺、玩视频游戏或上网冲浪。这些数据如下:

要建立这个模型,目标是使均方误差最小化。
现在,我们将使用回归树建立模型。首先,如果我们想在训练节点至少有三个样本,树的第一次分割可能看起来像这样:

这看起来似乎不错,但它没有包括他们是否玩视频游戏或浏览互联网等信息。如果我们计划在训练节点有两个样本会怎样?

通过前面的树形图,我们可以从特征中获取某些信息,例如SurfInternet和PlaysVideoGames。让我们弄清楚残差/误差是如何产生的:

现在,我们将处理第一个模型的残差:

一旦我们在残差上建立了模型,我们必须将先前的模型与当前模型相结合,如下表所示:

我们可以看到残差已经下降,模型正在变得更好。
让我们尝试将到目前为止我们所做的工作公式化:
-
首先,我们在数据 f1 = y. 上建立了一个模型。
-
我们接下来做的事情是计算残差并在残差上建立模型:
h 1=y- f1
- 下一步是组合模型,即 f2= f1 + h 1.
添加更多模型可以纠正先前模型的错误。前面的方程将变成如下:
*f3(x)= f2(x) + h2(x) *
方程最终将如下所示:
fm= f[m]-1 + h[m]-1
或者,我们可以写成以下形式:
* hm= y- fm*
- 由于我们的任务是使平方误差最小化,因此 f 将初始化为训练目标值的平均值:

- 然后,我们可以像之前一样找到 f[m+1, ]:
fm= f[m]-1 + h[m]-1
现在,我们可以使用梯度下降来优化我们的梯度提升模型。我们想要最小化的目标函数是 L。我们的起点是 fo。对于迭代 m=1,我们计算 L 对 fo 的梯度。然后,我们将弱学习器拟合到梯度分量。在回归树的情况下,叶子节点会在具有相似特征的样本中产生一个平均梯度。对于每个叶子节点,我们朝着平均梯度的方向前进。结果是 f[1],这可以重复进行,直到我们得到 f[m]。
我们修改了我们的梯度提升算法,使其能够与任何可微分的损失函数一起工作。让我们清理前面的想法,并再次重新表述我们的梯度提升模型。
梯度提升参数
在应用梯度提升进行乳腺癌用例之前,需要考虑不同的参数:
-
Min_samples_split: 节点中需要考虑分割的最小样本数被称为min_samples_split。 -
Min_samples_leaf: 在终端或叶子节点中需要的最小样本数被称为min_samples_leaf。 -
Max_depth: 这是指从根节点到最远叶子节点的最大节点数。更深层次的树可以模拟更复杂的关系,然而,这也可能导致模型过拟合。 -
Max_leaf_nodes: 树中叶子节点的最大节点数。由于创建了二叉树,深度为n将产生最大为 2^(n ) 的叶子节点。因此,可以定义max_depth或max_leaf_nodes。
现在,我们将为乳腺癌用例应用梯度提升。在这里,我们正在加载构建模型所需的库:
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import classification_report, confusion_matrix, roc_curve, auc
在执行随机森林的过程中,我们已经完成了数据清洗和探索的各个步骤。现在,我们将直接进入模型的构建。
在这里,我们将执行网格搜索以找到梯度提升算法的最优参数:
param_grid = {
'n_estimators': [ 25, 50, 100, 150, 300, 500], # the more parameters, the more computational expensive
"max_depth": [ 5, 8, 15, 25],
"max_features": ['auto', 'sqrt', 'log2']
}
gbm = GradientBoostingClassifier(learning_rate=0.1,random_state=10,subsample=0.8)
#performance estimation
#initiate the grid
grid = GridSearchCV(gbm, param_grid = param_grid, cv=10, scoring ='accuracy')
#fit your data before you can get the best parameter combination.
grid.fit(X,y)
grid.cv_results_
我们得到以下输出:

现在,让我们找出最优参数:
#Let's find out the best scores, parameter and the estimator from the gridsearchCV
print("GridSearhCV best model:\n ")
print('The best score: ', grid.best_score_)
print('The best parameter:', grid.best_params_)
print('The best model estimator:', grid.best_estimator_)
输出如下所示:

现在,我们将构建模型:
model2 = GradientBoostingClassifier(criterion='friedman_mse', init=None,
learning_rate=0.1, loss='deviance', max_depth=5,
max_features='sqrt', max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, n_estimators=150,
presort='auto', random_state=10, subsample=0.8, verbose=0,
warm_start=False)
model2.fit(X_train, y_train)
print("Performance Accuracy on the Testing data:", round(model2.score(X_test, y_test) *100))
测试数据上的性能准确率是 96.0:
#getting the predictions for X
y_pred2 = model2.predict(X_test)
print('Total Predictions {}'.format(len(y_pred2)))
预测的总数是 114:
truth = pd.DataFrame(y_test, columns= ['Truth'])
predictions = pd.DataFrame(y_pred, columns= ['Predictions'])
frames = [truth, predictions]
_result = pd.concat(frames, axis=1)
print(_result.shape)
_result.head()

让我们进行交叉验证:
cv_scores = []
scores2 = cross_val_score(gbm, X_train, y_train, cv=10, scoring='accuracy')
cv_scores.append(scores2.mean())
cv_scores.append(scores2.std())
#cross validation mean score
print("10 k-fold cross validation mean score: ", scores2.mean() *100)
10 k-fold 交叉验证的平均分数是 94.9420289855:
#printing classification accuracy score rounded
print("Classification accuracy: ", round(accuracy_score(y_test, y_pred2, normalize=True) * 100))
分类准确率是 96.0:
# Making the Confusion Matrix
cm = confusion_matrix(y_test, y_pred2)
plt.figure(figsize=(12,6))
ax = plt.axes()
ax.set_title('Confusion Matrix for both classes\n', size=21)
sns.heatmap(cm, cmap= 'plasma',annot=True, fmt='g') # cmap
plt.show()
通过查看混淆矩阵,我们可以看到这个模型比之前的模型更好:

摘要
在本章中,我们研究了集成学习及其不同方法,即 bagging、boosting 和 stacking。我们还了解了什么是 bootstrapping,它是 bagging 和 boosting 等集成学习方法的根源。我们还学习了决策树及其通过例子(如申请贷款的人)进行划分和规则的方法。然后我们讨论了树分裂以及分裂决策树的参数,接着转向随机森林算法。我们使用所涵盖的概念进行了一个乳腺癌案例研究。我们还发现了 bagging 和 boosting 以及梯度提升之间的差异。我们还讨论了梯度提升的参数,以便在我们的乳腺癌例子中使用。
在下一章中,我们将学习关于训练神经网络的内容。
第四章:训练神经网络
当你听到神经网络这个术语时,你会觉得它是一种与大脑相关的生物学术语。我必须坦白地告诉你,猜测这一点是显而易见的,而且实际上,我们这样做是在正确的道路上。我们将看到它是如何与之相关的。
神经网络在数据科学领域带来了革命。直到 2011 年,由于计算能力不足,支持神经网络的人无法将其传播到他们想要的程度。但是,随着更便宜的计算解决方案和神经网络领域研究的增加,它们在数据科学和人工智能领域引起了轰动。神经网络是一种可以应用于监督学习和无监督学习的算法。随着网络的加深,它们能够提供对非结构化数据,如图像和文本的解决方案。
在本章中,我们将涵盖以下主题:
-
神经网络
-
网络初始化
-
过拟合
-
丢弃法
-
随机梯度下降
-
循环神经网络
神经网络
首先,让我解释一下神经元是什么以及它们的结构。以下标注的图显示了典型的神经元:

我们将神经元定义为一种电兴奋细胞,它通过电和化学信号接收、处理和传输信息。树突是它的一部分,接收来自其他神经元的信号。我们需要注意的一点是,单个神经元不能做任何事情,而且有成亿个神经元相互连接,这使得电化学信号流动,进而信息通过它流动。信息通过轴突和突触传递,然后被传输。
当提到神经网络时,其结构变化不大。让我们来看看它。在中间,我们有一个神经元,这个神经元从三个其他神经元 X1、X2 和 X3 接收信号。这三个神经元通过箭头连接,箭头就像一个突触。这些神经元 X1、X2 和 X3 被称为输入层神经元。通过神经元后,我们得到输出值。有趣的是,人脑通过所有传感器如眼睛、耳朵、触觉和鼻子接收输入信号,所有的突触都让这些电化学信号通过,输出作为视觉、声音、触觉和嗅觉。神经网络中遵循的是类似的过程。
神经网络是如何工作的
假设我们有一组如下输入和输出:
| 输入 (X) | 输出 (Y) |
|---|---|
| 2 | 4 |
| 3 | 6 |
| 4 | 8 |
| 5 | 10 |
| 6 | 12 |
在先前的表中,输入和输出可能看起来有线性关系;然而,情况并不总是如此。此外,每次模型需要初始化时。让我们理解初始化的含义。
模型初始化
根据前面的表格,网络正在尝试寻找输入和输出之间的关系。例如,让我们假设通过的关系如下:
Y = W. X
在前面的方程中,Y 和 X 是已知的,并且基于这些,必须找到 W 的值。但是,在一次迭代中找到 W 的值是罕见的。它必须首先初始化。假设 W 被初始化为 3 的值。方程如下:
Y= 3X
| 输入 (X) | 实际输出 (Y) |
|---|---|
| 2 | 6 |
| 3 | 9 |
| 4 | 12 |
| 5 | 15 |
| 6 | 18 |
现在我们必须评估输出,以及它是否接近期望输出。
损失函数
到目前为止,模型已经被随机初始化,并且我们能够得到一个输出。为了评估实际输出是否接近期望输出,引入了损失函数。它使模型能够泛化,并找出模型达到期望输出的程度。
我们可以查看新的表格,其中包含实际输出和期望输出:
| 输入 (X) | 实际输出 (Y[a]) | 期望输出 (Y) |
|---|---|---|
| 2 | 6 | 4 |
| 3 | 9 | 6 |
| 4 | 12 | 8 |
| 5 | 15 | 10 |
| 6 | 18 | 12 |
如果我们必须写下损失函数,它必须如下所示:
损失函数 = 期望输出 - 实际输出
然而,以这种方式添加损失函数会邀请两种类型的值:负值和正值。在损失函数为负值的情况下,这意味着网络超出了目标,因为期望输出 < 实际输出,而在相反的情况下(期望输出 > 实际输出),网络会低于目标。为了消除这种情况,我们将采用绝对损失:
| 输入(X) | 实际输出(Y[a]) | 期望输出(Y) | 损失=Y-Y[a] | 绝对损失 |
|---|---|---|---|---|
| 2 | 6 | 4 | -2 | 2 |
| 3 | 9 | 6 | -3 | 3 |
| 4 | 12 | 8 | -4 | 4 |
| 5 | 15 | 10 | -5 | 5 |
| 6 | 18 | 12 | -6 | 6 |
总绝对损失 = 20
采用这种绝对损失的方法对模型没有任何好处,就像如果我们小心翼翼地查看前面的表格,最小的损失是 2 个单位,最大的损失是 6 个单位。可能会给人一种感觉,最大损失和最小损失之间的差异并不大(这里,4 个单位),但对于模型来说,这可能是巨大的。因此,我们采取了完全不同的方法。与其采用绝对损失,我们更倾向于采用损失的平方:
| 输入(X) | 实际输出(Y[a]) | 期望输出(Y) | 损失=Y-Y[a] | 损失的平方 |
|---|---|---|---|---|
| 2 | 6 | 4 | -2 | 4 |
| 3 | 9 | 6 | -3 | 9 |
| 4 | 12 | 8 | -4 | 16 |
| 5 | 15 | 10 | -5 | 25 |
| 6 | 18 | 12 | -6 | 36 |
现在,损失越大,惩罚越重。它可以使我们更容易看到损失较多的情况。
优化
我们必须找出一种方法来最小化总损失函数,这可以通过改变权重来实现。可以通过使用一种粗略的方法,如修改参数 W 在 -500 到 500 的范围内,步长为 0.001 来实现。这将帮助我们找到一个点,使得误差平方和变为 0 或最小。
但这种方法将适用于这种场景,因为我们这里没有太多参数,计算也不会太复杂。然而,当我们有很多参数时,计算将会受到影响。
在这里,数学以微分(极值方法)的形式来帮助我们优化权重。函数在某个点的导数给出了该函数值变化的速率。在这里,我们将取损失函数的导数。它将评估通过轻微调整或改变权重对总误差的影响。例如,如果我们尝试改变权重 δW,W= W+ δW,我们可以找出它如何影响损失函数。我们的最终目标是通过这种方式最小化损失函数。
我们知道最小值将在 w=2 处达到;因此,我们在这里探索不同的场景:
-
w<2 表示正的损失函数,负的导数,意味着权重的增加会减少损失函数
-
w>2 表示正的损失函数,但导数是正的,意味着任何更多的权重增加将增加损失
-
在 w=2 时,损失=0,导数=0;达到最小值:

神经网络中的计算
现在,让我们看看一个简单且浅的网络:

其中:
-
I1: 输入神经元 1
-
I2: 输入神经元 2
-
B1: 偏置 1
-
H1: 隐藏层中的神经元 1
-
H2: 隐藏层中的神经元 2
-
B2: 偏置 2
-
O1: 输出层的神经元
最终值在输出神经元 O1 处。O1 从 H1、H2 和 B2 接收输入。由于 B2 是偏置神经元,其激活始终为 1。然而,我们需要计算 H1 和 H2 的激活。为了计算 H1 和 H2 的激活,需要 I1、I2 和 B1 的激活。它可能看起来 H1 和 H2 将会有相同的激活,因为它们得到了相同的输入。但在这里并不是这样,因为 H1 和 H2 的权重是不同的。两个神经元之间的连接器代表权重。
H1 的激活计算
让我们看看只涉及 H1 的网络部分:

隐藏层的结果如下公式所示:

其中:
-
A: 激活函数
-
x[i]: 输入值
-
w[i]: 权重值
在我们的场景中,有三个输入值,n=3:
-
x[1] = I1 = 来自第一个神经元的输入值 1
-
x[2]= I2= 来自第二个神经元的输入值 2
-
x[3 ]= B1 = 1
-
*w1 *= 从 I1 到 H1 的权重
-
*w2 *= 从 I2 到 H1 的权重
-
*w3 *= 从 B1 到 H1 的权重
反向传播
在这一步,我们计算损失函数 f(y, y_hat) 关于 A,W 和 b 的梯度,分别称为 dA,dW 和 db。使用这些梯度,我们更新从最后一层到第一层的参数值。
激活函数
激活函数通常在神经网络中引入,以引入非线性。没有非线性,神经网络几乎没有机会学习非线性。但您可能会质疑为什么一开始就需要非线性。如果我们认为每个关系都是线性的,那么模型将无法公正地处理实际关系,因为线性关系是非常罕见的。如果应用线性,模型的输出将不会是一般化的。
此外,激活函数的主要目的是将输入信号转换为输出。假设如果我们尝试去掉激活函数,它将输出一个线性结果。线性函数是一阶多项式,容易求解,但同样,它无法捕捉到各种特征之间的复杂映射,这在无结构数据的情况下是非常必要的。
非线性函数是那些次数大于一的函数。现在我们需要一个神经网络模型来学习和表示几乎任何东西,以及任何任意复杂的函数,它将输入映射到输出。神经网络也被称为通用函数逼近器。这意味着它们可以计算和学习任何函数。因此,激活函数是神经网络的一个基本组成部分,使其能够学习复杂的函数。
激活函数的类型
- Sigmoid:这种激活函数如下所示:

这个函数的值介于 0 和 1 之间。它存在许多问题:
-
-
梯度消失
-
它的输出不是零中心化的
-
它的收敛速度慢
-
- 双曲正切函数 (tanh):表示它的数学公式如下:

这个函数的值介于 -1 和 +1 之间。然而,它仍然面临梯度消失问题:

- 修正线性单元 (ReLU):从数学上,我们用以下方式表示它:


根据前面的图示,ReLU 对于所有正数是线性的,对于所有负数是零。这意味着以下都是正确的:
-
-
它的计算成本低,因为没有复杂的数学。因此,模型可以更快地进行训练。
-
它的收敛速度更快。线性意味着当 x 变大时,斜率不会触及平台。它没有像 sigmoid 或 tanh 这样的激活函数所遭受的梯度消失问题。
-
网络初始化
到目前为止,我们已经看到神经网络模型中存在多个阶段。我们知道两个节点(来自不同层)之间存在权重。这些权重经历线性变换,并连同输入节点的值一起,通过非线性激活函数,以产生下一层的值。这个过程在下一层和随后的层中重复进行,最后,在反向传播的帮助下,找到权重的最优值。
很长一段时间里,权重都是随机初始化的。后来,人们意识到我们初始化网络的方式对模型有巨大的影响。让我们看看我们是如何初始化模型的:
- 零初始化:在这种初始化中,所有初始权重都设置为零。由于这个原因,所有层的所有神经元执行相同的计算,这会导致产生相同的输出。这将使整个深度网络变得毫无意义。从这个网络中出来的预测将和随机的一样好。直观地说,它没有执行对称性打破。通常,在神经网络的正向传播过程中,每个隐藏节点都会接收到一个信号,而这个信号实际上就是以下内容:

如果网络以零初始化,那么所有隐藏节点都将接收到零信号,因为所有输入都将乘以零。因此,无论输入值是多少,如果所有权重都相同,隐藏层中的所有单元也将相同。这被称为对称性,为了更好地捕捉信息,必须打破这种对称性。因此,权重应该随机初始化或使用不同的值:

- 随机初始化:这种初始化有助于打破对称性。在这种方法中,权重被随机初始化得非常接近零。每个神经元都不执行相同的计算,因为权重不等于零:

- He 等人初始化:这种初始化依赖于前一层的大小。它有助于达到成本函数的全局最小值。权重是随机的,但根据神经元前一层的大小而有所不同:

反向传播
反向传播在正向传播完成后进行。它代表误差反向传播。在神经网络的情况下,这一步开始计算误差函数(损失函数)相对于权重的梯度。人们可能会想知道为什么术语反向与它相关联。这是因为梯度计算从网络中开始反向进行。在这里,首先计算最终层权重的梯度,然后最后计算第一层的权重。
反向传播需要三个元素:
-
数据集: 一个由输入-输出对
组成的数据集,其中
是输入,
是我们期望的输出。因此,取一个大小为 N 的此类输入-输出集,表示为
。 -
前馈网络: 在这里,参数表示为 θ。参数包括
,这是层 *l[k] 中节点 j 和层 *l[k-1] 中节点 i 之间的权重,以及层 *l[k-1] 中节点 i 的偏置
。同一层中的节点之间没有连接,层之间是完全连接的。 -
损失函数: L(X,θ)。
使用梯度下降训练神经网络需要计算损失/误差函数 E(X,θ) 对权重
和偏置
的梯度。然后,根据学习率 α,梯度下降的每一次迭代都会集体更新权重和偏置,如下所示:

在这里
表示梯度下降迭代中的神经网络参数。
过度拟合
我们已经详细讨论了过度拟合。然而,让我们回顾一下我们在神经网络场景中学到的东西以及什么是过度拟合。
到目前为止,我们已经意识到,当有大量参数(在深度学习中)可供我们使用来映射和解释事件时,使用这些参数构建的模型往往会有很好的拟合度,并试图展示它有能力正确描述事件。然而,任何模型的真正测试总是在未见过的数据上,我们已经能够评估模型在这样未见过的数据点上的表现。我们希望我们的模型具有泛化能力,这将使模型能够在测试数据(未见过的)上获得与训练数据一致的分值。但是,很多时候,当涉及到未见过的数据时,我们的模型无法泛化,因为模型没有学习到事件的洞察力和因果关系。在这种情况下,人们可能会看到训练准确率和测试准确率之间巨大的差异,不用说,这不是我们希望从模型中得到的东西。这种现象被称为过度拟合。
在深度学习中,你可能会遇到数百万个参数,而且很可能会陷入过度拟合的陷阱。正如我们在第一章中定义的,过度拟合发生在模型学习训练数据中的细节和噪声到一定程度,以至于它对模型在新数据上的性能产生负面影响。
预防神经网络中的过度拟合
正如我们在前面的章节中讨论的那样,过度拟合是构建模型时需要考虑的主要问题,因为我们的工作并不只限于训练阶段。任何模型的试金石都发生在未见过的数据上。让我们探讨处理神经网络中过度拟合问题的技术。
消失的梯度
神经网络在从数据中提取复杂特征方面已经是一个启示。无论是图像还是文本,它们都能够找到导致更好预测的组合。网络越深,选择那些复杂特征的机会就越高。如果我们继续添加更多的隐藏层,添加的隐藏层的学习速度会更快。
然而,当我们进行反向传播时,这是在网络中向后移动以找到损失相对于权重的梯度,梯度往往会随着我们向第一层前进而越来越小。这意味着深度网络的前几层成为较慢的学习者,而后续层倾向于学习得更快。这被称为消失的梯度问题。
网络初始层很重要,因为它们负责学习和检测简单模式,实际上是我们的网络的构建块。显然,如果它们给出不适当和不准确的结果,那么我们如何期望下一层和整个网络有效地执行并产生准确的结果?以下图表显示了球在更陡斜率上滚动的图像:

为了让我们更容易理解,让我们说有两个斜率:一个更陡,另一个较缓。两个斜率上都有球滚下来,很明显球会从更陡的斜率上滚得更快,而不是从较缓的斜率上。类似地,如果梯度大,学习和训练会更快;否则,如果梯度较缓,训练会变得太慢。
从反向传播的直觉来看,我们知道优化算法,如梯度下降,通过调节权重以降低成本函数的输出,缓慢地寻求达到局部最优。梯度下降算法通过将梯度的负值乘以学习率(α)(它很小)来更新权重:

它说我们必须重复进行,直到达到收敛。然而,这里有两种情况。第一种情况是,如果迭代次数较少,那么结果的准确性会受到影响;第二种情况是,迭代次数过多会导致训练时间过长。这是因为每次迭代中权重变化不够,因为梯度小(我们知道α已经非常小)。因此,权重不会移动到分配的迭代中的最低点。
让我们谈谈那个可能影响梯度消失问题的激活函数。在这里,我们讨论的是通常用作激活函数的 sigmoid 函数:


它将所有输入值转换到(0,1)的范围。如果我们需要找到 sigmoid 函数的导数,那么:

现在我们来绘制它:

很明显,导数的最大值为 0.25。因此,它所在的范围是(0,1/4)。
一个典型的神经网络看起来如下图所示:

一旦权重参数初始化,输入就会乘以权重,并通过激活函数传递,最终我们得到一个成本函数(J)。随后,通过梯度下降修改权重以最小化J进行反向传播。
为了计算相对于第一个权重的导数,我们使用链式法则。结果如下:

如果我们只尝试研究前面表达式中中间部分的导数,我们得到以下结果:

第一部分——从输出到隐藏层 2。
由于输出是第二个隐藏单元的激活,表达式如下:


同样,对于第二部分,从隐藏层 2 到隐藏层 1,表达式如下:


将所有内容综合起来,我们得到以下结果:

我们知道 sigmoid 函数的导数最大值为 1/4,如果权重以标准差 1 和平均值 0 初始化,权重通常可以取-1 到 1 之间的值。这将导致整个表达式变得更小。如果有一个深度网络需要训练,那么这个表达式会持续变得更小,结果,训练时间会变得缓慢。
克服梯度消失问题
从前面的梯度消失解释中可以看出,这个问题的根本原因是选择了 sigmoid 函数作为激活函数。当选择tanh作为激活函数时,也发现了类似的问题。
为了应对这种情况,ReLU 函数应运而生:
ReLU(x)= max(0,x)

如果输入是负数或小于零,函数输出为零。在第二种情况下,如果输入大于零,则输出将等于输入。
让我们取这个函数的导数,看看会发生什么:
情况 1:x<0:

情况 2:x>0:

如果我们必须绘制它,我们得到以下结果:

因此,ReLU 的导数要么是 0,要么是 1。图像呈现出阶梯函数的样子。现在,我们可以看到,由于导数的值不在 0 和 1 之间,我们不会遇到梯度消失的问题。
然而,这仍然是不正确的。当输入值恰好为负数且我们知道在这种情况下导数变为零时,我们可能仍然会遇到这个问题。通常,加权求和的结果不会是负数,如果我们担心出现这种问题,我们可以确实初始化权重为仅正数,并且/或规范化输入在 0 到 1 之间。
对于这种情况,我们仍然有一个解决方案。我们有一个叫做Leaky ReLU的另一个函数,其公式如下:
RELU(x) = max(εx, x)
这里,ε的值通常是 0.2–0.3。我们可以绘制如下:

循环神经网络
我们的思想过程总是有一个顺序。我们总是按顺序理解事物。例如,如果我们看电影,我们会通过将其与之前的部分联系起来来理解下一个序列。我们保留最后一个序列的记忆,并理解整个电影。我们并不总是回到第一个序列以获取它。
神经网络能这样做吗?传统的神经网络通常不能以这种方式运行,这是一个主要缺点。这就是循环神经网络发挥作用的地方。它带有一个循环,允许信息流动:

这里,神经网络将输入作为X[t],并以h[t]的形式输出。循环神经网络由多个相同网络组成,这些网络将信息传递给后续网络。
如果我们将前面的网络展开,它看起来会像以下这样:

这种链式性质揭示了循环神经网络与序列和列表密切相关。它们是神经网络用于此类数据的自然架构。由于网络具有内部记忆,RNN 能够记住它们接收到的输入,这反过来又使它们能够提供准确和精确的结果和预测。
到目前为止,我们一直在谈论序列数据。但我们需要正确理解这个术语,即序列数据。这种数据形式是有序数据,其中存在时间t和时间t-1的数据之间的关系。这种类型的数据可以是金融数据、时间序列数据、视频等等。RNN 允许我们在向量的序列上操作。例如,看看下面的图像:

每个矩形表示为一个向量,箭头代表函数。输入向量用红色表示,输出向量用蓝色表示,绿色向量表示 RNN 的状态:
-
从固定大小的输入到输出的处理模式可以不包含 RNN。
-
以适当的格式对输出进行排序。
-
对输入进行排序。
-
对输入和输出进行排序(例如,机器翻译:一个 RNN 读取英语句子,然后输出其他语言的句子,如德语)。
-
同步排序后的输入和输出(例如,视频分类,为视频的每一帧贴标签)
RNN 的局限性
当涉及到短期依赖时,循环神经网络(RNN)表现得很合适。这意味着,如果只有一个语句需要处理,神经网络可以很好地运行。例如,如果有一个句子,印度的首都是 __,在这种情况下,我们不可避免地会得到正确的结果,因为这个是一个普遍的陈述,这里没有上下文。这个陈述不依赖于前面的句子,而且这里也没有前面的句子。
因此,预测将是印度的首都是新德里。
最终,标准的 RNN 并不理解输入背后的上下文。我们将通过一个例子来理解:
在印度生活意味着我自然而然地喜欢上了板球。但是,10 年后,为了工作我搬到了美国。
在印度流行的游戏是 ___。
可以看到,第一句话中有上下文,然后在第二句中发生变化。然而,网络必须基于第一句话进行预测。在印度流行的游戏很可能是板球,但上下文在这里起着作用,并且网络必须理解这一点。简单的 RNN 在这里失败了。
这就是长短期记忆(LSTM)出现的地方。
用例
让我们研究一个有助于我们理解网络的用例。
我们将研究一个时间序列问题。我们得到了谷歌股票价格数据集。一个用于训练,另一个用于测试。现在,我们将查看一个用例来预测谷歌的股票价格:
- 让我们先导入库:
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
- 接下来,导入训练集:
dataset_train = pd.read_csv('Google_Stock_Price_Train.csv')
training_set = dataset_train.iloc[:, 1:2].values
- 特征缩放将在下一步进行:
from sklearn.preprocessing import MinMaxScaler
sc = MinMaxScaler(feature_range = (0, 1))
training_set_scaled = sc.fit_transform(training_set)
- 让我们创建一个包含 60 个时间步长和 1 个输出的数据结构:
X_train = []
y_train = []
for i in range(60, 1258):
X_train.append(training_set_scaled[i-60:i, 0])
y_train.append(training_set_scaled[i, 0])
X_train, y_train = np.array(X_train), np.array(y_train)
- 接下来,重新塑形数据:
X_train = np.reshape(X_train, (X_train.shape[0], X_train.shape[1], 1))
- 现在,导入 Keras 库和包:
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from keras.layers import Dropout
- 我们将使用回归函数初始化 RNN:
regressor = Sequential()
- 现在,添加第一个 LSTM 层和一些 dropout 规范化:
regressor.add(LSTM(units = 50, return_sequences = True, input_shape = (X_train.shape[1], 1)))
regressor.add(Dropout(0.2))
- 现在,添加第二个 LSTM 层和一些 dropout 规范化:
regressor.add(LSTM(units = 50, return_sequences = True))
regressor.add(Dropout(0.2))
- 添加第三个 LSTM 层和一些 dropout 规范化:
regressor.add(LSTM(units = 50, return_sequences = True))
regressor.add(Dropout(0.2))
- 添加第四个 LSTM 层和一些 dropout 规范化:
regressor.add(LSTM(units = 50))
regressor.add(Dropout(0.2))
- 最后,添加输出层:
regressor.add(Dense(units = 1))
- 接下来,我们将编译 RNN:
regressor.compile(optimizer = 'adam', loss = 'mean_squared_error')
- 我们将 RNN 拟合到训练集中:
regressor.fit(X_train, y_train, epochs = 100, batch_size = 32)
- 我们得到 2017 年的实际股价,如图所示:
dataset_test = pd.read_csv('Google_Stock_Price_Test.csv')
real_stock_price = dataset_test.iloc[:, 1:2].values
- 我们得到 2017 年预测的股价,如图所示:
dataset_total = pd.concat((dataset_train['Open'], dataset_test['Open']), axis = 0)
inputs = dataset_total[len(dataset_total) - len(dataset_test) - 60:].values
inputs = inputs.reshape(-1,1)
inputs = sc.transform(inputs)
X_test = []
for i in range(60, 80):
X_test.append(inputs[i-60:i, 0])
X_test = np.array(X_test)
X_test = np.reshape(X_test, (X_test.shape[0], X_test.shape[1], 1))
predicted_stock_price = regressor.predict(X_test)
predicted_stock_price = sc.inverse_transform(predicted_stock_price)
- 最后,我们将可视化结果,如图所示:
plt.plot(real_stock_price, color = 'red', label = 'Real Google Stock Price')
plt.plot(predicted_stock_price, color = 'blue', label = 'Predicted Google Stock Price')
plt.title('Google Stock Price Prediction')
plt.xlabel('Time')
plt.ylabel('Google Stock Price')
plt.legend()
plt.show()
摘要
在本章中,我们学习了神经网络及其工作原理,并介绍了反向传播和激活函数。我们研究了网络初始化以及如何初始化不同类型的模型。我们还学习了神经网络场景中的过拟合和 dropout。
我们介绍了 RNN 的概念,并研究了关于谷歌股价数据集的一个用例。在下一章中,我们将研究时间序列分析。
第五章:时间序列分析
在本章中,我们将探讨时间序列分析,并学习几种在不同时间点观察和捕捉事件的方法。我们将介绍白噪声的概念,并学习如何在序列中检测它。
我们将取时间序列数据,计算连续观测之间的差异,这将导致形成一个新的序列。这些概念将帮助我们深入了解时间序列分析,并帮助我们对其有更深入的理解。
在本章中,我们将介绍以下主题:
-
时间序列分析简介
-
白噪声
-
随机游走
-
自回归
-
自相关
-
稳定性
-
差分
-
AR 模型
-
移动平均模型
-
自回归积分移动平均
-
参数优化
-
异常检测
时间序列分析简介
在某些情况下,我们可能会尝试在不同的时间点观察和捕捉事件。通常,我们会在相邻观测之间得出一种关联或关联,这种关联无法通过处理独立且同分布数据的方案来处理。这种在数学和统计方法上考虑所有这些因素的方法被称为 时间序列分析。
时间序列分析已在许多领域得到应用,例如汽车、银行和零售业、产品开发等。其应用没有边界,因此分析师和数据科学家正在深入探索这个领域,以从组织中获取最大利益。
在本节中,我们将探讨时间序列分析的一些概念,这将为进一步的深入理解奠定基础。一旦我们建立了这个基础,我们就会进入建模阶段。
白噪声
一个由一系列相互独立的随机变量组成,均值为零,标准差为 σ² 的简单序列被称为 白噪声。在这种情况下,变量是独立且同分布的。所有值都具有相同的方差 σ²。在这种情况下,序列来自高斯分布,被称为 高斯白噪声。
当序列表现为白噪声时,这意味着序列的性质是完全随机的,序列内部没有关联。因此,无法开发模型,在这种情况下无法进行预测。
然而,当我们通常使用非白噪声序列构建时间序列模型时,我们试图在残差或误差中实现白噪声现象。简单来说,无论我们何时尝试构建模型,目的都是从序列中提取最大量的信息,以便变量中不再存在更多信息。一旦我们构建了模型,噪声将始终是其一部分。方程如下:
Y[t ]= X[t ]+ Error
因此,误差序列在本质上应该是完全随机的,这意味着它是白噪声。如果我们已经得到了这些误差作为白噪声,那么我们就可以继续说我们已经从序列中提取了所有可能的信息。
在序列中检测白噪声
我们可以使用以下工具检测白噪声:
-
线形图:一旦我们有了线形图,我们就可以了解序列是否具有恒定的均值和方差
-
自相关图:有一个相关图可以让我们对滞后变量之间是否存在关联有所了解
-
摘要:检查序列的均值和方差与序列中有意义的连续值块的平均值和方差
让我们在 Python 中这样做:
- 首先,我们将按照以下方式导入所有必需的库:
from random import gauss
from random import seed
from pandas import Series
from pandas.tools.plotting import autocorrelation_plot
from matplotlib import pyplot
- 接下来,我们将设置白噪声序列供我们分析,如下所示:
seed(1000)
#creating white noise series
series = [gauss(0.0, 1.0) for i in range(500)]
series = Series(series)
- 让我们使用以下代码来获取其摘要或统计信息:
print(series.describe())
我们将得到以下输出:

在这里,我们可以看到均值接近零,标准差接近 1。
- 现在让我们制作一个线形图来检查趋势,使用以下代码:
series.plot()
pyplot.show()
我们将得到以下输出:

线性图看起来完全是随机的,这里无法观察到任何趋势。
- 是时候制作自相关图了。让我们使用以下代码设置一个:
autocorrelation_plot(series)
pyplot.show()
我们将得到以下输出:

即使在自相关函数图上,相关性也突破了我们的置信区间。这告诉我们它是一个白噪声序列。
随机游走
随机游走是一种时间序列模型,其中当前观测值等于前一个观测值加上一个随机修改。它可以以下述方式描述:
x[t]= x[t-1 ]+ w[t]
在前面的公式中,w[t]是一个白噪声序列。
有时,我们可能会遇到反映不规则增长的序列。在这些情况下,预测下一个级别的策略可能是不正确的。相反,尝试预测从一个时期到下一个时期的变化可能更好——也就是说,查看序列的一阶差分可能更好地找出一个显著的模式。以下图显示了随机游走模式:

在每个时间段,从左到右,变量的值独立地随机向上或向下迈出一步,这被称为随机游走。
它也可以用以下方式描述:
y(t)= b[o ]+ b[1]x[t-1] + w[t]*
以下列表解释了前面的公式:
-
y(t):序列中的下一个值
-
b[o]:系数,如果设置为非零数字,则表示随机游走伴随着漂移
-
b[1]:系数,设置为 1
-
w[t]:白噪声
自回归
自回归是一种时间序列模型,通常使用同一序列的先前值作为回归的解释因素,以预测下一个值。假设我们测量并跟踪了随时间变化的指标,称为 y[t],它在时间 t 时测量,并且当这个值回归到同一时间序列的先前值时。例如,y[t] 在 y[t-1] 上:

如前一个方程所示,前一个值 y[t-1] 已成为这里的预测值,而 y[t] 是要预测的响应值。此外,ε[t] 是均值为零,方差为 1 的正态分布。自回归模型的阶数由模型用来确定下一个值的先前值的数量定义。因此,前一个方程是一个一阶自回归,或 AR(1)。如果我们必须推广它,k 阶自回归,写作 AR(k),是一个多元线性回归,其中时间序列在任何时间 (t) 的值是时间 t-1,t-2,…,t-k 的值的(线性)函数。
下面的列表显示了对于 AR(1) 模型,以下值代表什么:
-
当 β[1]= 0,y**t,它等同于白噪声
-
当 β[1]= 1 和 β[0]= 0 时,y[t] 等同于随机游走
-
当 β[1]= 1 和 β[0]≠ 0 时,y[t],它等同于有漂移的随机游走
-
当 β[1] < 1,y[t],它倾向于在正负值之间振荡
自相关
自相关是衡量时间序列滞后值之间相关性的度量。例如,r[1] 是 y[t] 和 y[t-1] 之间的自相关,同样,r[2] 是 y[t] 和 y[t-2] 之间的自相关。这可以总结如下公式:

在前一个公式中,T 是时间序列的长度。
例如,假设我们有以下相关系数,如图所示:

要绘制它,我们得到以下结果:

从这个自相关函数图中可以观察到以下内容:
-
r[4] 比其他延迟更高,这主要是因为季节性模式
-
蓝线是表示相关性是否显著不同于零的指标
-
延迟 0 的自相关始终为 1
平稳性
对于一些时间序列模型,一个常见的假设是数据必须是平稳的。让我们看看平稳性对时间序列意味着什么。
平稳过程是指其均值、方差和自相关结构随时间不变的过程。这意味着数据没有趋势(增加或减少)。
我们可以使用以下公式来描述这一点:
E(x[t])= μ,对于所有 t
E(x[t]²)= σ²,对于所有 t
cov(x[t],x[k])= cov(x[t+s], x[k+s]),对于所有 t,k 和 s
稳定性检测
有多种方法可以帮助我们确定数据是否平稳,如下所示:
- 绘制数据图:根据时间变量绘制数据图可以帮助我们判断是否存在趋势。根据平稳性的定义,数据中的趋势意味着没有恒定的均值和方差。让我们用 Python 来实现这一点。在这个例子中,我们使用的是国际航空公司乘客数据。
首先,让我们加载所有必需的库,如下所示:
from pandas import Series
from matplotlib import pyplot
%matplotlib inline
data = Series.from_csv('AirPassengers.csv', header=0)
series.plot()
pyplot.show()
我们将得到以下输出:

从图中非常清楚地可以看出,这里存在一个上升趋势,并且这证实了我们的假设,即这是一个非平稳序列。
- 分割数据集并计算摘要:下一个方法是将数据序列分为两部分,并计算均值和方差。通过这样做,我们将能够确定均值和方差是否恒定。让我们使用以下代码来实现这一点:
X = data.values
partition =int(len(X) / 2)
X1, X2 = X[0:partition], X[partition:]
mean1, mean2 =np.nanmean(X1),np.nanmean(X2)
var1, var2 = np.nanvar(X1), np.nanvar(X2)
print('mean1=%f, mean2=%f' % (mean1, mean2))
print('variance1=%f, variance2=%f' % (var1, var2))
输出如下:
mean1=182.902778, mean2=377.694444 variance1=2244.087770, variance2=7367.962191
我们可以看到序列 1 和序列 2 的均值和方差不相等,因此我们可以得出结论,该序列是非平稳的。
- Augmented Dickey-Fuller 测试:Augmented Dickey-Fuller 测试是一种统计测试,它倾向于以一定的置信水平指示序列是否平稳。统计测试通过其假设和过程对数据进行测试,以检验我们对数据的假设。最终,它以一定的置信度给出结果,这有助于我们做出决策。
这个测试实际上就是单位根测试,它试图找出时间序列是否受到趋势的影响。它使用自回归(AR)模型,并在不同的滞后值上优化信息准则。
在这里,零假设如下:
- H[o]:时间序列具有单位根,这意味着序列是非平稳的。
替代假设如下:
- H[1]:时间序列没有单位根,因此它是平稳的。
根据假设检验的规则,如果我们为测试选择了 5%的显著性水平,那么结果将被解释如下:
如果 p-value >0.05 =>,则我们未能拒绝零假设。也就是说,序列是非平稳的。
如果 p-value <0.05 =>,则拒绝零假设,这意味着该序列是平稳的。
让我们在 Python 中执行以下操作:
- 首先,我们将加载库,如下所示:
import pandas as pd
import numpy as np
import matplotlib.pylab as plt
%matplotlib inline
from matplotlib.pylab import rcParams
rcParams['figure.figsize'] = 25, 6
- 接下来,我们按照以下方式加载数据和时间图:
data = pd.read_csv('AirPassengers.csv')
print(data.head())
print('\n Data Types:')
print(data.dtypes)
输出可以在以下图表中看到:

- 然后,我们按照以下方式解析数据:
dateparse = lambda dates: pd.datetime.strptime(dates, '%Y-%m')
data = pd.read_csv('./data/AirPassengers.csv', parse_dates=['Month'], index_col='Month',date_parser=dateparse)
print(data.head())
然后,我们得到以下输出:

ts= data["#Passengers"]
ts.head()
从这里,我们得到以下输出:

- 然后,我们绘制以下图表:
plt.plot(ts)
输出如下所示:

- 让我们创建一个函数来执行以下代码的平稳性测试:
from statsmodels.tsa.stattools import adfuller
def stationarity_test(timeseries):
dftest = adfuller(timeseries, autolag='AIC')
dfoutput = pd.Series(dftest[0:4], index=['Test Statistic','p-value','#Lags Used','Number of Observations Used'])
for key,value in dftest[4].items():
dfoutput['Critical Value (%s)'%key] = value
print(dfoutput)
stationarity_test(ts)
输出如下所示:

在前面的方程中,* ω* 是白噪声项,α 是系数,不能为零。聚合方程如下所示:

有时,我们可能会讨论模型的阶数。例如,我们可能会将 AR 模型描述为阶数 p。在这种情况下,p 代表模型中使用的滞后变量的数量。例如,AR(2) 模型或二阶 AR 模型看起来如下:

在这里,ω 是具有 E(ω[t])=0 和方差 =* σ²* 的白噪声。
为了找出 AR 模型的阶数,我们需要绘制一个偏自相关函数图,然后寻找第一次被上置信水平跨越的滞后。
自回归积分移动平均
自回归积分移动平均(ARIMA)模型是以下元素的组合:
-
自回归算子:我们已经学习了这是什么意思;只是为了重申,它是平稳化序列的滞后。它用 p 表示,这仅仅是自回归项的数量。PACF 图提供了这个组成部分。
-
积分算子:一个需要通过差分使其平稳的序列被称为平稳序列的积分形式。它用d表示,这是将非平稳时间序列转换为平稳序列所需的差分量。这是通过从当前周期的观测值中减去前期的观测值来实现的。如果这个过程只对序列进行了一次,那么它被称为一阶差分。这个过程消除了序列中按恒定速率增长的趋势。在这种情况下,序列是按递增速率增长的,差分序列需要另一轮差分,这被称为二阶差分。
-
移动平均算子:预测误差的滞后,用q表示。它是方程中滞后预测误差的数量。ACF 图将产生这个成分。
ARIMA 模型只能应用于平稳序列。因此,在应用它之前,必须检查序列的平稳性条件。可以使用 ADF 测试来建立这一点。
ARIMA 方程看起来如下:

方程的前一部分(在-符号之前)是自回归部分,第二部分(在-符号之后)是移动平均(MA)部分。
我们还可以在 ARIMA 模型中添加季节性成分,这将形成 ARIMA (p,d,q)(p,d,q)[s]。在添加它时,我们需要执行季节性差分,这意味着从季节滞后中减去当前观测值。
让我们绘制自相关函数(ACF)和偏自相关函数(PACF)以找出p和q参数。
在这里,我们取滞后数为 20,并使用statsmodel.tsa.stattools库导入acf和pacf函数,如下所示:
from statsmodels.tsa.stattools import acf,pacf
lag_acf= acf(ts_log_dif,nlags=20)
lag_pacf = pacf(ts_log_dif, nlags=20,method="ols")
现在,我们将使用以下代码通过matplotlib绘制图表:
plt.subplot(121)
plt.plot(lag_acf)
plt.axhline(y=0,linestyle='--',color='gray')
plt.axhline(y=-1.96/np.sqrt(len(ts_log_diff)),linestyle='--',color='gray')
plt.axhline(y=1.96/np.sqrt(len(ts_log_diff)),linestyle='--',color='gray')
plt.title('Autocorrelation Function')
输出如下:

在这里,我们正在测量时间序列与其滞后版本之间的相关性。例如,在滞后 5 的情况下,ACF 将比较时间点t1,t2处的序列与时间点t[1-5],…,t[2-5]处的序列。这是一张相关系数与其滞后值的相关图。
如果我们仔细观察前面的图表,我们会看到在滞后 2 时上置信水平线已被穿过。因此,移动平均(MA)的阶数将是2,而q=2。
画出了序列与滞后值之间的部分相关性,并给出了部分自相关函数(PACF)图。这是一个非常有趣的术语。如果我们继续计算Y变量与X3 之间的相关性,同时我们知道Y与X1 和X2 有分离的关联,那么部分相关性就解决了这部分相关性,这部分相关性不能由它们与X1 和X2 的相关性来解释。
在这里,偏相关是平方根(通过添加一个变量(此处为X3)并在其他变量(此处为X1,X2)上对Y进行回归时减少方差)。
在时间序列的情况下,Y 与滞后值 Y[t-3]之间的偏自相关将是未被Y与Y[t-1]和Y[t-2]之间的相关性解释的值,如下代码所示:
#Plot PACF:
plt.subplot(122)
plt.plot(lag_pacf)
plt.axhline(y=0,linestyle='--',color='gray')
plt.axhline(y=-1.96/np.sqrt(len(ts_log_diff)),linestyle='--',color='gray')
plt.axhline(y=1.96/np.sqrt(len(ts_log_diff)),linestyle='--',color='gray')
plt.title('Partial Autocorrelation Function')
plt.tight_layout()
我们将得到以下输出:

如果我们仔细观察前面的图,我们会看到在滞后 2 时上置信水平线已被穿过。因此,AR 的阶数应该是 2,p=2。
让我们尝试一个阶数为(p=2, d=1, q=0)的 AR 模型。d值取为 1,因为这是一个一阶差分的情况。同时计算了残差平方和,以判断模型的好坏,并与其他模型进行比较,如下代码所示:
from statsmodels.tsa.arima_model import ARIMA
model1 = ARIMA(ts_log, order=(2, 1, 0))
results_AR = model1.fit(disp=-1)
plt.plot(ts_log_dif)
plt.plot(results_AR.fittedvalues, color='red')
plt.title('RSS: %.4f'% sum((results_AR.fittedvalues-ts_log_dif)**2))
输出如下所示:

现在,我们可以使用以下代码查看描述 AR1 和 AR2 系数的模型摘要:
results_AR.summary()

现在,让我们使用以下代码构建一个阶数为(p=0,d=1,q=2)的 MA 模型:
model2 = ARIMA(ts_log, order=(0, 1, 2))
results_MA = model2.fit(disp=-1)
plt.plot(ts_log_dif)
plt.plot(results_MA.fittedvalues, color='red')
plt.title('RSS: %.4f'% sum((results_MA.fittedvalues-ts_log_dif)**2))
输出如下所示:

现在,让我们结合这两个模型,并使用以下代码构建一个 ARIMA 模型:
model3 = ARIMA(ts_log, order=(2, 1, 2))
results_ARIMA = model.fit(disp=-1)
plt.plot(ts_log_dif)
plt.plot(results_ARIMA.fittedvalues, color='red')
plt.title('RSS: %.4f'% sum((results_ARIMA.fittedvalues-ts_log_dif)**2))
输出如下所示:

我们可以从 AR 模型到 ARIMA 模型观察到 RSS 值的下降。现在RSS= 1.0292:
results_ARIMA.summary()
我们可以看到 AR1、AR2、MA1 和 MA2 的系数,并且根据p值,我们可以看到所有这些参数都是显著的,如下截图所示:

让我们使用以下代码将预测值转换成一个序列:
predictions_ARIMA_dif= pd.Series(results_ARIMA.fittedvalues, copy=True)
print(predictions_ARIMA_dif.head())
我们将得到以下输出:

将差分转换为对数尺度的方法是连续将这些差异添加到基数中。一种简单的方法是首先确定索引处的累积和,然后将其添加到基数中。累积和可以使用以下代码找到:
predictions_ARIMA_dif_cumsum = predictions_ARIMA_dif.cumsum()
print(predictions_ARIMA_dif_cumsum.head())
从这个结果,我们将得到以下输出:

我们将创建一个所有值都为基数,并添加差异以添加到基数序列中的序列,如下所示:
predictions_ARIMA_log = pd.Series(ts_log.ix[0], index=ts_log.index)
predictions_ARIMA_log = predictions_ARIMA_log.add(predictions_ARIMA_dif_cumsum,fill_value=0)
predictions_ARIMA_log.head()
下面的代码显示了输出:

现在我们使用以下代码来找出预测结果:
predictions_ARIMA = np.exp(predictions_ARIMA_log)
plt.plot(ts)
plt.plot(predictions_ARIMA)
plt.title('RMSE: %.4f'% np.sqrt(sum((predictions_ARIMA-ts)**2)/len(ts)))
输出如下所示:

参数优化
让我们看看如何优化模型的参数。
AR 模型
import statsmodels.tsa.api as smtsa
aic=[]
for ari in range(1, 3):
obj_arima = smtsa.ARIMA(ts_log_diff, order=(ari,2,0)).fit(maxlag=30, method='mle', trend='nc')
aic.append([ari,2,0, obj_arima.aic])
print(aic)
[[1, 2, 0, -76.46506473849644], [2, 2, 0, -116.1112196485397]]
因此,在这种情况下,AR 模型的最优模型参数是p=2,d=2,q=0,因为这种组合的 AIC 值最小。
ARIMA 模型
即使对于 ARIMA 模型,我们也可以使用以下代码来优化参数:
import statsmodels.tsa.api as smtsa
aic=[]
for ari in range(1, 3):
for maj in range(1,3):
arima_obj = smtsa.ARIMA(ts_log, order=(ari,1,maj)).fit(maxlag=30, method='mle', trend='nc')
aic.append([ari,1, maj, arima_obj.aic])
print(aic)
执行上述代码后,您将得到以下输出:
[[1, 1, 1, -242.6262079840165], [1, 1, 2, -248.8648292320533], [2, 1, 1, -251.46351037666676], [2, 1, 2, -279.96951163008583]]
应选择具有最小赤池信息准则(AIC)的组合。
异常检测
异常本质上是一系列中的异常模式,是不规则偏离预期行为的偏差。例如,我们中的许多人看过板球比赛。在这场比赛中出局的一种方式是被接住,在球直接飞到守门员手中之前,它必须触碰到击球手的球棒。如果体育场非常嘈杂,有时很难判断球是否触碰到球棒。为了解决这个问题,裁判员使用一种称为snickometer的设备来帮助他们做出判断。snickometer 使用球桩麦克风的声波生成麦克风的声波图。如果图是直线,则球没有接触到球棒;否则,图将显示一个尖峰。因此,尖峰是异常的标志。另一个异常的例子可能是扫描中检测到恶性肿瘤。
异常检测是一种我们可以用来确定异常行为的技巧。异常也可以称为离群值。以下列表展示了几个不同的异常类型:
-
点异常:点异常是突破已分配给整个系统以保持监控的阈值边界的点。通常有一个系统在位,当点异常突破这个边界时,会发送警报。例如,金融行业的欺诈检测可以使用点异常检测来检查是否发生了从不同城市到持卡人常用位置的交易。
-
上下文异常:特定上下文的观察被称为上下文异常。例如,在工作日有大量交通是常见的,但如果假日落在周一,可能会看起来像是一个异常。
-
集体异常:一组集体数据实例有助于检测异常。比如说,有人意外地试图从远程机器复制数据到本地主机。在这种情况下,这种异常将被标记为潜在的网络安全攻击。
在本节中,我们将重点关注上下文异常,并尝试使用简单的移动平均来检测它们。
首先,让我们按照以下方式加载所有必需的库:
import numpy as np # vectors and matrices
import pandas as pd # tables and data manipulations
import matplotlib.pyplot as plt # plots
import seaborn as sns # more plots
from sklearn.metrics import mean_absolute_error
import warnings # `do not disturb` mode
warnings.filterwarnings('ignore')
%matplotlib inline
接下来,我们使用以下代码读取数据集。我们保持相同的数据集——即AirPassenger.csv:
data = pd.read_csv('AirPassengers.csv', index_col=['Month'], parse_dates=['Month'])
plt.figure(figsize=(20, 10))
plt.plot(ads)
plt.title('Trend')
plt.grid(True)
plt.show()
我们得到以下输出:

现在我们将编写一个函数,并使用以下代码创建一个用于检测异常的阈值:
def plotMovingAverage(series, window, plot_intervals=False, scale=1.96, plot_anomalies=False):
rolling_mean = series.rolling(window=window).mean()
plt.figure(figsize=(15,5))
plt.title("Moving average\n window size = {}".format(window))
plt.plot(rolling_mean, "g", label="Rolling mean trend")
# Plot confidence intervals for smoothed values
if plot_intervals:
mae = mean_absolute_error(series[window:], rolling_mean[window:])
deviation = np.std(series[window:] - rolling_mean[window:])
lower_bond = rolling_mean - (mae + scale * deviation)
upper_bond = rolling_mean + (mae + scale * deviation)
plt.plot(upper_bond, "r--", label="Upper Bond / Lower Bond")
plt.plot(lower_bond, "r--")
# Having the intervals, find abnormal values
if plot_anomalies:
anomalies = pd.DataFrame(index=series.index, columns=series.columns)
anomalies[series<lower_bond] = series[series<lower_bond]
anomalies[series>upper_bond] = series[series>upper_bond]
plt.plot(anomalies, "ro", markersize=10)
plt.plot(series[window:], label="Actual values")
plt.legend(loc="upper left")
plt.grid(True)
现在,让我们使用以下方法向序列中引入异常:
data_anomaly = data.copy()
data_anomaly.iloc[-20] = data_anomaly.iloc[-20] * 0.2
现在,让我们使用以下代码来绘制它以检测引入的异常:
plotMovingAverage(data_anomaly, 4, plot_intervals=True, plot_anomalies=True)
下图显示了输出结果:

现在,引入的异常可以在 1959 年之后被看作是旅行者数量的下降。然而,需要注意的是,这是一种较为简单的方法。在此场景下,也可以使用 ARIMA 和 Holt-Winters 方法。
摘要
在本章中,我们学习了时间序列分析和白噪声。我们介绍了随机游走、自回归、自相关和平稳性的概念,这些概念描述了如何判断数据是否平稳。
我们还学习了差分法,即对时间序列数据进行处理,计算连续观测值之间的差异,从而形成一个新的序列。本章还讨论了 AR 模型,它是随机过程的一部分,其中特定滞后值y**[t]被用作预测变量,并回归到y**[t ]上来估计值。我们还学习了两个优化参数,即 AR 模型和 ARIMA 模型。
在下一章中,我们将学习关于自然语言处理的内容。
第六章:自然语言处理
世界变化有多快?好吧,技术和数据变化同样快。随着互联网和社交媒体的出现,我们对数据的整个看法已经改变。最初,大多数数据分析的范围围绕结构化数据。然而,由于互联网和社交媒体中涌入的大量非结构化数据,分析的范围已经扩大。每秒钟都在生成大量的文本数据、图像、声音和视频数据。它们包含大量需要商业综合的信息。自然语言处理是一种技术,通过它我们可以使机器理解文本或语音。尽管非结构化数据范围广泛,但本章的目的是让你接触文本分析。
结构化数据通常由关系数据库或电子表格中设置的固定观察值和固定列组成,而非结构化数据没有任何结构,不能在关系数据库中设置;相反,它需要一个 NoSQL 数据库,例如视频、文本等。
在本章中,你将了解以下主题:
-
文档-词矩阵
-
观察文本的不同方法
-
情感分析
-
主题建模
-
贝叶斯技术
文本语料库
文本语料库是由单个文档或一组文档形成的文本数据,可以来自任何语言,如英语、德语、印地语等。在当今世界,大部分文本数据来自社交媒体,如 Facebook、Twitter、博客网站和其他平台。移动应用程序现在也被列入此类来源。语料库的规模越大,即称为语料库,分析就越准确。
句子
语料库可以被分解成称为句子的单位。句子承载着语料库的意义和上下文,一旦我们将它们组合在一起。句子的形成是在词性的帮助下进行的。每个句子都由分隔符(如句号)与其他句子分开,我们可以利用它来进一步分解。这被称为句子标记化。
单词
单词是语料库中最小的单位,当我们按照词性顺序排列时,它们就形成了句子。当我们把句子分解成单词时,这被称为词标记化。
词袋
当我们有文本作为输入数据时,我们不能直接处理原始文本。因此,将文本输入数据转换为数字或数字向量,以便使其可用于多种算法,这是至关重要的。
词袋模型是将文本用于算法的一种方法。本质上,它是对文档中单词出现情况的表示。它与结构、顺序和位置无关;此模型只寻找单词作为特征的数量。
该模型背后的思维过程是:内容相似意味着文档相似。
在词袋模型中需要采取的不同步骤如下:
- 构建语料库:在这个步骤中,收集并合并文档以形成一个语料库。例如,这里使用了电视剧《老友记》中的著名歌曲作为语料库:
*我会为你守候
当雨开始倾盆而下
我会为你守候
就像我以前去过那里一样
我会为你守候*
让我们考虑这首歌的每一行作为一个单独的文档。
-
词汇构建:在这个步骤中,我们确定语料库中的独特单词,并创建它们的列表:
-
我
-
将
-
be
-
there
-
for
-
you
-
when
-
the
-
rain
-
starts
-
to
-
pour
-
like
-
have
-
been
-
before
-
-
文档向量创建:现在,是时候将每个文本文档转换为向量了。
做这件事的简单方法是通过布尔路由。这意味着原始文本将通过该文本在相应文档中的存在/不存在转换为文档向量。
例如,如果歌曲的第一行被转换成一个包含I will be there for you的文档,那么文档向量将如下所示:
| 文档向量 | |
|---|---|
| I | 1 |
| will | 1 |
| be | 1 |
| there | 1 |
| for | 1 |
| you | 1 |
| when | 0 |
| the | 0 |
| rain | 0 |
| starts | 0 |
| to | 0 |
| pour | 0 |
| like | 0 |
| have | 0 |
| been | 0 |
| before | 0 |
文档中出现的所有单词都被标记为 1,其余的都被标记为 0。
因此,第一句话的文档向量是[1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0]。
类似地,第二句话的文档向量是[0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0]。
随着语料库的大小持续增加,文档向量中的零的数量也会增加。因此,这导致向量稀疏,成为一个稀疏向量。对于各种算法来说,计算稀疏向量变得非常具有挑战性。数据清洗是应对这一问题的方法之一,在一定程度上:
-
文本清洗:这包括将所有语料库转换为单个大小写(最好是小写)。必须从语料库中去除标点符号。可以采用词干提取,即找到文本的词根,这将能够减少语料库中的独特单词。此外,去除诸如“is”和“of”之类的停用词,可能有助于减轻稀疏性的痛苦。
-
计数向量:创建文档向量的另一种方法是利用文档中单词出现的频率。假设有一个由 N 个文档和 T 个标记(单词)组成的语料库。这些 T 个标记将形成我们的词典。因此,计数向量矩阵的维度将是 N X T。每一行都包含该文档中词典中单词的频率。
例如,假设我们有三个文档:
-
N1: 计数向量中包含计数
-
N2: 计数向量是否比布尔方式创建特征向量更好?
-
N3: 特征向量的创建非常重要
在移除停用词后,计数向量矩阵如下表所示:
| count | vector | got | it | better | than | Boolean | way | creating | feature | creation | important | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| N1 | 2 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| N2 | 1 | 2 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 |
| N3 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 |
现在,仔细看看矩阵维度;由于N=3和T=12,这使得这是一个 3 x 12 的矩阵。
我们将看看矩阵是如何形成的。对于文档N1,计数出现的次数是 2,向量出现的次数是 1,等等。将这些频率输入这些值。其他两个文档也完成了类似的过程。
然而,这有一个缺点。一个高频词可能会开始主导文档,甚至整个语料库,这会导致从特征中提取的信息有限。为了解决这个问题,引入了词频逆文档频率(TF-IDF)。
TF-IDF
我们了解到计数向量化的局限性,即一个高频词可能会破坏整体效果。因此,我们的想法是对在大多数文档中频繁出现的词进行惩罚,给它们分配较低的权重,并增加在文档子集中出现的词的权重。这就是 TF-IDF 工作的原理。
TF-IDF 是衡量一个术语相对于文档和整个语料库(文档集合)重要性的度量:
TF-IDF(term) = TF(term) IDF(term)*
词频(TF)是单词在文档中出现的频率,相对于文档中所有单词的总数。例如,如果一个文档中有 1,000 个单词,我们需要找出该文档中出现了 50 次的单词NLP的TF,我们使用以下公式:
TF(NLP)= 50/1000=0.05
因此,我们可以得出以下结论:
TF(term) = 术语在文档中出现的次数/文档中术语的总数
在前面的例子中,包含三个文档N1、N2和N3,如果需要找到文档N1中术语count的TF,它将如下所示:
TF(count) N1= 2/ (2+1+1+1) = 2/5 = 0.4
它表示单词对文档的贡献。
然而,IDF 是衡量该词在整个语料库中重要性的指标:
IDF("count") = log(总文档数/包含术语"count"的文档数)
IDF("count") = log(3/2)= 0.17
现在,让我们计算术语vector的 IDF:
IDF("vector")=log(3/3)= 0
我们如何解释这个结果呢?它意味着如果一个词在所有文档中都出现过,那么它对这个特定文档来说并不相关。但是,如果一个词只出现在文档的子集中,这意味着它在存在的文档中具有一定的相关性。
让我们计算计数和向量的 TF-IDF,如下所示:
文档 N1 的 TF-IDF 计数 = TF 计数 * IDF 计数 = 0.4 * 0.17 = 0.068
文档 N1 的 TF-IDF 向量 = TF 向量 * IDF 向量 = (1/5) * 0 = 0
很明显,由于它给 N1 中的计数分配了更多的权重,所以它比向量更重要。权重值越高,术语越稀有。权重值越小,术语越常见。搜索引擎利用 TF-IDF 检索与查询相关的相关文档。
现在,我们将探讨如何在 Python 中执行计数向量和 TF-IDF 向量器:
执行计数向量器
执行CountVectorizer的步骤如下:
- 导入所需的
CountVectorizer库:
from sklearn.feature_extraction.text import CountVectorizer
- 创建一个文本列表:
text = [" Machine translation automatically translate text from one human language to another text"]
- 将文本列表分词并构建词汇表:
vectorizer.fit(text)
你将得到以下输出:

- 让我们看看创建的词汇表:
print(vectorizer.vocabulary_)
我们得到以下输出:

- 现在,我们必须按照以下方式对其进行编码:
vector = vectorizer.transform(text)
- 让我们总结一下向量的内容,并找出项矩阵:
print(type(vector))
print(vector.toarray())
我们得到以下输出:

在 Python 中执行 TF-IDF
在 Python 中执行 TF-IDF 的步骤如下:
- 按照以下方式导入库:
from sklearn.feature_extraction.text import TfidfVectorizer
- 让我们通过添加四个文档来创建语料库,如下所示:
corpus = ['First document', 'Second document','Third document','First and second document' ]
- 让我们设置向量器:
vectorizer = TfidfVectorizer()
- 我们按照以下方式从文本中提取特征:
X = vectorizer.fit_transform(corpus)
print(vectorizer.get_feature_names())
print(X.shape)
输出如下:

- 现在出现了文档-词矩阵;每个列表表示一个文档:
X.toarray()
我们得到以下输出:

情感分析
情感分析是自然语言处理的应用领域之一。它在各个行业和领域中广泛应用,并且在行业中有着巨大的需求。每个组织都致力于关注客户及其需求。因此,为了理解声音和情感,客户成为首要目标,因为了解客户的脉搏可以带来收入。如今,客户通过 Twitter、Facebook 或博客表达他们的情感。需要做一些工作来提炼这些文本数据,使其可消费。让我们看看如何在 Python 中实现这一点。
在这里,电影观众的评论来自 IMDB。这些评论也分享在 GitHub 上。
我们将启动库,如下所示:
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(color_codes=True)
import os
print(os.listdir())
我们将按照以下方式加载数据集:
data= pd.read_csv("imdb_master.csv",encoding = "ISO-8859-1")
现在,让我们探索数据和其维度:
print(data.head())
print(data.shape)
我们得到以下输出:

我们只需要两个变量,review和label,来构建模型。我们将保留数据中的这两个变量。已经创建了一个新的数据框,如下所示:
Newdata= data[["review","label"]]
Newdata.shape

现在,这是我们需要检查label中有多少类别的步骤,因为我们只对保留正面和负面类别感兴趣:
g= Newdata.groupby("label")
g.count()
输出如下:

现在,很明显有三个类别,我们将删除unsup,如下所示:
sent=["neg","pos"]
Newdata = Newdata[Newdata.label.isin(sent)]
Newdata.head()
我们得到了以下输出:

我们的数据现在已经设置好了。然而,由于我们删除了一些行,我们将重置数据的索引,因为有时这会引起一些问题:
print(len(Newdata))
Newdata=Newdata.reset_index(drop=True) Newdata.head()
输出如下:

我们已经完成了这个步骤。现在,我们将对label变量进行编码,以便使其可用于机器学习模型。我们必须使用LabelEncode来做到这一点,如下所示:
from sklearn.preprocessing import LabelEncoder
labelencoder = LabelEncoder()
Newdata["label"] = labelencoder.fit_transform(Newdata["label"])
我们必须对数据的一部分进行清洗,以便使其干净和标准化,如下所示:
Newdata["Clean_review"]= Newdata['review'].str.replace("[^a-zA-Z#]", " ")
Newdata.head()
输出如下:

这里,我们试图去除长度小于3的单词,因为大多数长度小于3的单词对意义的影响不大:
Newdata['Clean_review'] = Newdata['Clean_review'].apply(lambda x: ' '.join([w for w in x.split() if len(w)>3]))
Newdata.shape

数据的标记化现在可以进行,如下所示:
tokenized_data = Newdata['Clean_review'].apply(lambda x: x.split())
tokenized_data.shape

我们正在使用词干提取,以便去除相同单词的不同变体。例如,我们将查看 satisfying、satisfy 和 satisfied,如下所示:
from nltk.stem.porter import *
stemmer = PorterStemmer()
tokenized_data = tokenized_data.apply(lambda x: [stemmer.stem(i) for i in x])
tokenized_data.head()
输出如下:

在词干提取后,我们必须将数据重新连接起来,因为我们正在朝着生成文字云的方向前进:
for i in range(len(tokenized_data)):
tokenized_data[i] = ' '.join(tokenized_data[i])
tokenized_data.head()
我们得到了以下输出:

这里,我们将标记化的数据与旧的Newdata数据框合并:
Newdata["Clean_review2"]= tokenized_data
Newdata.head()
以下是对前面代码的输出:

已经生成了一个将所有单词组合在一起的文字云:
all_words = ' '.join([str(text) for text in Newdata['Clean_review2']])
from wordcloud import WordCloud
wordcloud = WordCloud(width=800, height=500, random_state=21, max_font_size=110).generate(all_words)
plt.figure(figsize=(10, 7))
plt.imshow(wordcloud, interpolation="bilinear")
plt.axis('off')
plt.show()
输出如下:

现在,我们将分别对正面和负面情感制作文字云,如下所示:
- 对于
负面情感,我们将使用以下方法:
Negative =' '.join([text for text in Newdata['Clean_review2'][Newdata['label'] == 0]])
wordcloud1= WordCloud(width=800, height=500, random_state=21, max_font_size=110).generate(Negative)
plt.figure(figsize=(10, 7))
plt.imshow(wordcloud1, interpolation="bilinear")
plt.title("Word Cloud- Negative")
plt.axis('off')
plt.show()
以下输出显示了负面情感的文字云:

- 我们将使用以下内容用于
正面情感:
Positive=' '.join([text for text in Newdata['Clean_review2'][Newdata['label'] == 1]])
wordcloud2 = WordCloud(width=800, height=500, random_state=21, max_font_size=110).generate(Positive)
plt.figure(figsize=(10, 7))
plt.imshow(wordcloud, interpolation="bilinear")
plt.title("Word Cloud-Positive")
plt.axis('off')
plt.show()
以下输出显示了正面情感的文字云:

情感分类
我们将采用两种方法来进行情感分类(正面和负面),如下所示:
-
TF-IDF
-
计数向量化
让我们看看哪一个能给出更好的结果。
TF-IDF 特征提取
以下代码将提供 TF-IDF 特征提取:
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf= TfidfVectorizer(max_df=0.9,min_df= 2, max_features=1000,
stop_words="english")
tfidfV = tfidf.fit_transform(Newdata['Clean_review2'])
tfidf.vocabulary_
我们将得到以下输出:

计数向量器单词袋特征提取
以下代码将展示单词袋的计数向量器:
from sklearn.feature_extraction.text import CountVectorizer
bow_vectorizer = CountVectorizer(max_df=0.90, min_df=2, max_features=1000, stop_words='english')
# bag-of-words
bow = bow_vectorizer.fit_transform(Newdata['Clean_review2'])
模型构建计数向量
为了构建计数向量,我们可以将数据分为训练集和测试集,如下所示:
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score,accuracy_score
# splitting data into training and validation set
xtrain, xtest, ytrain, ytest = train_test_split(bow, Newdata['label'], random_state=42, test_size=0.3)
lreg = LogisticRegression()
lreg.fit(xtrain, ytrain) # training the model
prediction = lreg.predict_proba(xtest) # predicting on the validation set
prediction_int = prediction[:,1] >= 0.3 # if prediction is greater than or equal to 0.3 than 1 else 0
prediction_int = prediction_int.astype(np.int)
print("F1 Score-",f1_score(ytest, prediction_int))
print("Accuracy-",accuracy_score(ytest,prediction_int))
我们将得到以下输出:

在这里,我们达到了 84%的准确率。让我们看看 TF-IDF 方法的表现如何:
from sklearn.linear_model import LogisticRegression
# splitting data into training and validation set
xtraintf, xtesttf, ytraintf, ytesttf = train_test_split(tfidfV, Newdata['label'], random_state=42, test_size=0.3)
lreg = LogisticRegression()
lreg.fit(xtraintf, ytraintf) # training the model
prediction = lreg.predict_proba(xtesttf) # predicting on the test set
prediction_int = prediction[:,1] >= 0.3 # if prediction is greater than or equal to 0.3 than 1 else 0
prediction_int = prediction_int.astype(np.int)
print("F1 Score-",f1_score(ytest, prediction_int))
print("Accuracy-",accuracy_score(ytest,prediction_int))
输出如下:

在这里,准确率达到了 83.8%(略低于计数向量器)。
这完成了情感分类模型的构建。
主题建模
模型是一种用于识别主题并推导出文本语料库所展现的隐藏模式的方法。主题建模类似于聚类,因为我们提供主题的数量作为超参数(类似于聚类中使用的超参数),这恰好是簇的数量(k-means)。通过这种方式,我们试图提取具有某些权重分配的主题数量或文本。
模型的应用领域包括文档聚类、降维、信息检索和特征选择。
实现这一目标有多种方法,如下所示:
-
潜在狄利克雷分配(LDA):它基于概率图模型
-
潜在语义分析(LSA):它基于线性代数(奇异值分解)
-
非负矩阵分解:它基于线性代数
我们将主要讨论 LDA,它被认为是所有中最受欢迎的。
LDA 是一种矩阵分解技术,它基于这样一个假设:文档是由多个主题组成的,而主题是由单词组成的。
在阅读了前面的章节之后,你应该知道任何语料库都可以表示为一个文档-词矩阵。以下矩阵显示了一个包含M个文档和N个单词的词汇量,形成一个M x N矩阵。这个矩阵中的所有单元格都有该特定文档中单词的频率:

这个文档与词的 M x N 矩阵通过 LDA 转换为两个矩阵:文档与主题的 M x X 矩阵和主题与词的 X x N 矩阵。
LDA 架构
在 LDA 架构中,有 M 个文档,包含 N 个单词,这些单词通过被称为LDA的黑色条带进行处理。它提供了X 个主题和单词簇。每个主题都有来自主题的单词的 psi 分布。最后,它还提供了一个文档中主题的分布,用 phi 表示。
以下图表说明了 LDA:

关于Alpha和Beta超参数:alpha 代表文档-主题浓度,beta 代表主题-单词浓度。alpha 的值越高,我们从文档中得到的主题就越多。另一方面,beta 的值越高,一个主题中的单词就越多。这些可以根据领域知识进行调整。
LDA 遍历每篇文档中的每个单词,并为它分配和调整一个主题。基于两个概率的乘积,将一个新的主题X分配给它:p1= (topic t/document d),这意味着分配给主题 t 的文档中单词的比例,以及p2=(word w/topic t),这指的是分配给主题t的分配在整个文档中,其中与单词 w 相关联。
通过遍历次数,我们得到了一个良好的主题-单词和主题-文档的分布。
让我们看看它在 Python 中的执行方式:
- 在这一步,我们正在加载
dataset = fetch_20newsgroups,它来自sklearn:
from sklearn.datasets import fetch_20newsgroups
dataset = fetch_20newsgroups(shuffle=True, random_state=1, remove=('headers', 'footers', 'quotes'))
documents = dataset.data
- 在这一步,我们将清理数据集。为了做到这一点,需要
stopwords和WordNetLemmatizer函数。因此,必须加载相关的库,如下所示:
from nltk.corpus import stopwords
from nltk.stem.wordnet import WordNetLemmatizer
import string
- 确保您已下载以下字典:
import nltk
nltk.download("stopwords")
nltk.download("wordnet")
- 这里,创建了一个
clean函数,将单词转换为小写。移除stopwords并选择长度大于3的单词。它还使其无标点符号。最后,进行词形还原,如下所示:
stop = set(stopwords.words('english'))
punc = set(string.punctuation)
lemma = WordNetLemmatizer()
def clean(doc):
stopw_free = " ".join([i for i in doc.lower().split() if i not in stop and len(i)>3])
punc_free = ''.join(ch for ch in stop_free if ch not in punc)
lemmatized = " ".join(lemma.lemmatize(word) for word in punc_free.split())
return lemmatized
doc_clean = [clean(doc).split() for doc in documents]
- 现在,我们必须在
gensim库的帮助下创建文档-术语矩阵。这个库还将使我们能够执行 LDA:
import gensim
from gensim import corpora
- 在这里创建了一个基于词袋的文档-术语矩阵:
corpus = corpora.Dictionary(doc_clean)
doc_term_matrix = [corpus.doc2bow(doc) for doc in doc_clean]
- 这里,正在使用 TF-IDF 帮助创建一个类似的矩阵:
from gensim import models
tfidf = models.TfidfModel(doc_term_matrix)
corpus_tfidf = tfidf[doc_term_matrix]
- 让我们使用 TF-IDF 矩阵设置模型。主题的数量已经给出为
10:
lda_model1 = gensim.models.LdaMulticore(corpus_tfidf, num_topics=10, id2word=corpus, passes=2, workers=2)
- 让我们看看包含单词的主题:
print(lda_model1.print_topics(num_topics=5, num_words=5))
输出如下:

- 对于词袋模型,我们也将进行类似的练习;稍后,我们将进行比较:
lda_model2 = gensim.models.LdaMulticore(doc_term_matrix, num_topics=10, id2word=corpus, passes=2, workers=2)
print(lda_model2.print_topics(num_topics=5, num_words=5))
我们得到了以下输出:

评估模型
对数困惑度是衡量 LDA 模型好坏的一个指标。困惑度值越低,模型越好:
print("lda_model 1- Perplexity:-",lda_model.log_perplexity(corpus_tfidf))
print("lda_model 2- Perplexity:-",lda_model2.log_perplexity(doc_term_matrix))
对数困惑度的输出如下:

可视化 LDA
为了可视化数据,我们可以使用以下代码:
import pyLDAvis
import pyLDAvis.gensim
import matplotlib.pyplot as plt
%matplotlib inline
pyLDAvis.enable_notebook()
visual1= pyLDAvis.gensim.prepare(lda_model, doc_term_matrix, corpus)
visual1
输出将如下:

我们可以在此启用笔记本,如下所示:
pyLDAvis.enable_notebook()
visual2= pyLDAvis.gensim.prepare(lda_model2, doc_term_matrix, corpus)
visual2
输出如下:

让我们尝试解释这个。
在左侧,我们有主题,在右侧,我们有术语/单词:
-
圆圈的大小越大,主题出现的频率就越高。
-
相互重叠或彼此更接近的主题是相似的。
-
选择一个主题后,可以看到该主题的最具代表性的单词。这反映了单词的频率。可以通过使用滑块来调整每个属性的权重。
-
将鼠标悬停在主题上,将在右侧提供单词对该主题的贡献。点击单词后,我们将看到圆圈大小变化,这反映了该术语在该主题中的频率。
文本分类中的朴素贝叶斯技术
朴素贝叶斯是一种基于贝叶斯定理的监督分类算法。它是一个概率算法。但是,你可能想知道为什么它被称为朴素。这是因为该算法基于一个假设,即所有特征都是相互独立的。然而,我们意识到在现实世界中特征之间的独立性可能不存在。例如,如果我们试图检测一封电子邮件是否为垃圾邮件,我们只寻找与垃圾邮件相关的关键词,如彩票、奖项等。基于这些,我们从电子邮件中提取相关特征,并说如果给定与垃圾邮件相关的特征,该电子邮件将被分类为垃圾邮件。
贝叶斯定理
贝叶斯定理帮助我们找到给定一定条件下的后验概率:
P(A|B)= P(B|A) * P(A)/P(B)
A 和 B 可以被认为是目标变量和特征,分别。
在哪里,P(A|B):后验概率,表示在事件 B 发生的情况下事件 A 的概率:
-
P(B|A):在目标 A 给定的情况下,特征 B 的似然概率
-
P(A):目标 A 的先验概率
-
P(B):特征 B 的先验概率
朴素贝叶斯分类器是如何工作的
我们将通过查看泰坦尼克号的例子来理解所有这些。当泰坦尼克号沉没时,一些类别在获救方面比其他类别有优先权。我们有以下数据集(这是一个 Kaggle 数据集):
| 人员类别 | 生存机会 |
|---|---|
| 女性 | 是 |
| 儿童 | 是 |
| 儿童 | 是 |
| 男性 | 否 |
| 女性 | 是 |
| 女性 | 是 |
| 男性 | 否 |
| 男性 | 是 |
| 儿童 | 是 |
| 女性 | 否 |
| 儿童 | 否 |
| 女性 | 否 |
| 男性 | 是 |
| 男性 | 否 |
| 女性 | 是 |
现在,让我们为前面的信息准备一个似然表:
| 生存机会 | ||||||
|---|---|---|---|---|---|---|
| 否 | 是 | 总计 | ||||
| 类别 | 儿童 | 1 | 3 | 4 | 4/15= | 0.27 |
| 男性 | 3 | 2 | 5 | 5/15= | 0.33 | |
| 女性 | 2 | 4 | 6 | 6/15= | 0.40 | |
| 总计 | 6 | 9 | 15 | |||
| 6/15 | 9/15 | |||||
| 0.40 | 0.6 |
让我们找出哪个类别的人有最大的生存机会:
儿童 - P(Yes|Kid)= P(Kid|Yes) * P(Yes)/P(Kid)
P(Kid|Yes) = 3/9= 0.3
P(Yes) = 9/15 =0.6
P(Kid)= 4/15 =0.27
*P(Yes|kid) = 0.33 0.6/0.27=0.73
女性 - P(Yes|Woman)= P(Woman|Yes) * P(Yes)/P(Woman)
P(Woman|Yes) = 4/9= 0.44
P(Yes) = 9/15 =0.6
P(Woman)= 6/15 =0.4
*P(Yes|Woman) = 0.44 0.6/0.4=0.66
人 - P(Yes|人) = P(人|Yes) * P(Yes)/P(人)
P(人|Yes) = 2/9= 0.22
* P(Yes) = 9/15 =0.6*
* P(人)= 6/15 =0.33*
*P(Yes|人) = 0.22 0.6/0.33=0.4
因此,我们可以看到,孩子有最大的生存机会,而男人有最小的生存机会。
让我们借助朴素贝叶斯进行情感分类,看看结果是否更好或更差:
from sklearn.naive_bayes import MultinomialNB
# splitting data into training and validation set
xtraintf, xtesttf, ytraintf, ytesttf = train_test_split(tfidfV, Newdata['label'], random_state=42, test_size=0.3)
NB= MultinomialNB()
NB.fit(xtraintf, ytraintf)
prediction = NB.predict_proba(xtesttf) # predicting on the test set
prediction_int = prediction[:,1] >= 0.3 # if prediction is greater than or equal to 0.3 than 1 else 0
prediction_int = prediction_int.astype(np.int)
print("F1 Score-",f1_score(ytest, prediction_int))
print("Accuracy-",accuracy_score(ytest,prediction_int))
输出如下:

在这里,我们可以看到我们之前的结果比朴素贝叶斯的结果要好。
摘要
在本章中,我们学习了语料库构建技术,这些技术包括句子和单词,其中包括词袋模型,以便使文本可用于算法。你还了解了 TF-IDF 以及一个术语相对于文档和整个语料库的重要性。我们讨论了情感分析,以及分类和 TF-IDF 特征提取。
你还介绍了主题建模和评估模型,包括可视化 LDA。我们涵盖了贝叶斯定理以及与朴素贝叶斯分类器一起工作。在下一章中,你将学习关于时序和序列模式发现的内容。
第七章:时间和顺序模式发现
我们中的许多人为了家庭需求去过像 Reliance 和 Walmart 这样的零售商店。假设我们计划从 Reliance Digital 购买一部 iPhoneX。我们通常会做的是访问商店的移动部门,搜索型号,然后选择产品并前往结账柜台。
但是,在当今世界,组织的目标是增加收入。这仅仅通过一次向客户推销一个产品就能实现吗?答案是明确的不。因此,组织开始挖掘与频繁购买项目相关的数据。他们试图找出不同项目之间的关联,以及可以一起销售的产品,这有助于正确的产品定位。通常,它会找出哪些产品是共同购买的,组织可以以类似的方式摆放产品。
这就是我们将在本章中讨论的内容。我们如何通过机器学习手段制定这样的规则?我们将在这里讨论多种技术。
在本章中,我们将涵盖以下主题:
-
关联规则
-
频繁模式增长
-
验证
关联规则
关联规则挖掘是一种技术,它关注于从数据库中观察频繁出现的模式和关联,如关系数据库和事务数据库。这些规则并没有说任何关于个人偏好的内容;相反,它们主要依赖于交易中的项目来推断某种关联。每个交易都有一个主键(唯一的 ID),称为,交易 ID。所有这些交易都被作为一个整体进行研究,并从中挖掘模式。
关联规则可以被视为一个如果—那么的关系。为了详细阐述这一点,我们必须制定一条规则:如果客户购买了一个项目A,那么在相同的交易 ID 下(与项目A一起),客户选择项目B的概率被找到。在这里,你需要理解这不是因果关系,而是一种共现模式,它浮出水面。
这些规则有两个要素:
-
前件(如果):这是通常在项集或数据集中找到的项目/项目组
-
后件(那么):这是一个与前件/前件组一起出现的项目
看看以下规则:
{面包,牛奶} ⇒ {黄油}
这条规则的第一个部分被称为前件,第二个部分(在箭头之后)是后件。它能够传达在交易中如果先选择了面包和牛奶,那么选择黄油的可能性。然而,给定前件,后件在项集中出现的百分比概率并不明确。
让我们看看一些有助于我们达到目标的指标:
- 支持度:这是度量项目集在所有交易中的频率。例如,通过零售店如沃尔玛的交易数量,会出现两个项目集:项目集A = {Milk}),项目集B = {laptop})。鉴于支持度是项目集在所有交易中的频率,我们需要找出哪个项目集具有更高的支持度。我们知道项目集A将具有更高的支持度,因为Milk出现在日常杂货清单(以及交易)中的概率比laptop更高。让我们增加一个关联级别,并使用两个新的项目集进行研究:项目集A= {milk, cornflakes}),项目集B= {milk, USB Drive})。milk和cornflakes一起的购买频率将高于milk and USB Drive。这将使A的支持度指标更高。
让我们将这转化为数学表达式:
支持度(A, B) = 包含 A 和 B 的交易/总交易数
这里有一个例子:
-
-
总交易数是 10,000
-
包含A和B的交易 = 500*
-
那么,支持度(A, B) = 500/10000= 0.05
-
5%的交易包含A和B一起
-
- 信心:这表示当项目 2 已经被选中时,项目 1 被购买/选中的可能性。换句话说,它衡量的是在先导交易已经存在的情况下,后续交易发生的可能性。换句话说,如果Bread已经参与了该交易,那么它衡量的是交易中发生Butter的概率。很明显,这是在具有先导条件的情况下,后续事件发生的条件概率:
-
-
信心(A ⇒ B) = 包含 A 和 B 的交易/包含 A 的交易
-
信心可以转换为支持度
-
信心(A ⇒ B) = 支持度(A, B)/支持度(A)
-
这里有一个例子:
-
-
包含项目集milk的交易 = 50*
-
包含项目集cereal的交易 = 30*
-
包含milk和cereal的交易 = 10*
-
总交易数 = 100
-
信心(milk ⇒ Cereal) = 10/(50 +10) = 0.167
-
这意味着该事件发生的概率是 16.7%。
信心的一个缺点是它只考虑了项目 1 的流行程度,但没有考虑项目 2。如果项目 2 同样频繁,那么包含项目 1 的交易也包含项目 2 的可能性会更高。因此,这会导致结果被夸大。为了考虑两个构成项目的频率,我们使用一个称为提升的第三个度量。
- 提升度:这是指在项目 A 已经被选中时,项目 B 被选中的可能性,同时关注项目 B 的频率。提升度大于 1 表示项目 A 和项目 B 之间有很强的关联,这意味着如果项目 A 已经在购物车中,那么项目 B 被选中的可能性很大。提升度小于 1 表示如果项目 A 已经存在,项目 B 被选中的可能性很小。如果提升度为零,则表示无法建立任何关联。
Lift(A⇒B) = (包含 A 和 B 的事务数/(包含 A 的事务数))/包含 B 的事务数
意味着:
= Support(A, B)/(Support(A) * Support(B))
Lift(牛奶⇒麦片) = ( 10/(50+10))/0.4
= 0.416
我们将在这里以更好的格式展示它。在知道牛奶已经在购物车中的情况下,购物车中有麦片的概率(称为置信度)= 10/(50+10) = 0.167。
在不知道牛奶在购物车中的情况下,购物车中有麦片的概率为 cart = (30+10)/100 = 0.4.
这意味着知道牛奶已经在购物车中,将选择麦片的机会从 0.4 降低到 0.167。提升度为 0.167/0.4= 0.416,并且小于 1。因此,当牛奶已经在购物车中时,选择麦片的机会非常小。
Apriori 算法
Apriori 是一种经典的算法,用于挖掘频繁项集以推导出各种关联规则。它将帮助以更好的方式设置零售店,从而有助于增加收入。
支持度测量的反单调性是 Apriori 围绕的核心概念之一。它假设以下内容:
-
一个频繁项集的所有子集都必须是频繁的
-
同样,对于任何不频繁的项集,它的所有超集也必须是不频繁的
让我们来看一个例子并解释它:
| 事务 ID | 牛奶 | 黄油 | 麦片 | 面包 | 书籍 |
|---|---|---|---|---|---|
| t1 | 1 | 1 | 1 | 0 | 0 |
| t2 | 0 | 1 | 1 | 1 | 0 |
| t3 | 0 | 0 | 0 | 1 | 1 |
| t4 | 1 | 1 | 0 | 1 | 0 |
| t5 | 1 | 1 | 1 | 0 | 1 |
| t6 | 1 | 1 | 1 | 1 | 1 |
我们已经得到了事务 ID 和如牛奶、黄油、麦片、面包和书籍等项目。1 表示项目是事务的一部分,0 表示它不是。
- 我们为所有项目创建了一个频率表,包括支持(除以 6):
| 项目 | 事务数量 | 支持度 |
|---|---|---|
| 牛奶 | 4 | 67% |
| 黄油 | 5 | 83% |
| 麦片 | 4 | 67% |
| 面包 | 4 | 67% |
| 书籍 | 3 | 50% |
- 我们将设置一个支持度阈值为 60%,这将根据频率过滤项目,因为这些项目可以被视为此场景中的频繁项集:
| 项目 | 事务数量 |
|---|---|
| 牛奶 | 4 |
| 黄油 | 5 |
| 麦片 | 4 |
| 面包 | 4 |
- 同样,我们用这些项目形成组合数(每次两个、三个和四个),并找出频率:
| 项目 | 交易数量 |
|---|---|
| 牛奶,黄油 | 4 |
| 牛奶,谷物 | 3 |
| 牛奶,面包 | 2 |
| 黄油,面包 | 3 |
| 黄油,谷物 | 4 |
| 谷物,面包 | 2 |
现在,我们再次需要找出前面示例的支持度,并通过阈值进行过滤,即支持度为 60%
类似地,必须一次形成三个项目的组合(例如,牛奶、黄油和面包),并计算它们的支持度。最后,我们将通过阈值过滤它们。同样,需要通过每次四个项目的方式执行相同的过程。我们到目前为止所做的是频繁项集生成。
查找关联规则
为了找到关联规则,我们首先需要搜索所有支持度大于阈值支持度的规则。但是问题来了:我们如何找到这些规则?一种可能的方法是暴力搜索,这意味着列出所有可能的关联规则,并计算每个规则的支持度和置信度。然后,移除所有未通过置信度和支持度阈值的规则。
假设集合* I中有n个元素,可能的关联规则总数为3^n - 2^(n+1) + 1*。
如果X是一个包含k个元素的频繁项集,那么将存在2^k - 2个关联规则。
让我们看看如何在 Python 中执行关联规则:
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
data = pd.read_csv('association_mining.csv', header = None)
transactions = []
for i in range(0, 7501):
transactions.append([str(data.values[i,j]) for j in range(0, 20)])
如果我们要求一个项目在一天内出现三次,在七天时间内,支持度将是3 x 7/7051。7051是交易总数。我们最初将置信度设置为 20%:
from apyori import apriori
rules = apriori(transactions, min_support = 0.003, min_confidence = 0.2, min_lift = 3, min_length = 2)
results = list(rules)
results
我们可以通过运行前面代码中的results命令来可视化输出:

频繁模式增长
频繁模式增长(FP-growth)是一种频繁项集生成技术(类似于 Apriori)。FP-Growth 构建一个紧凑的树结构,并使用该树进行频繁项集挖掘和生成规则。它比 Apriori 更快,并且可以处理大型数据集。
让我们通过 FP-Growth 的步骤来了解:
- 设置事务:这一步按频率设置项目。但是,项目是垂直设置的,而不是水平设置。这意味着将输入从事务转换为项目:
| t_id | 项目 |
|---|---|
| 1 | (B, C, D, A) |
| 2 | (B, C, D) |
| 3 | (D, A) |
| 4 | (A, B) |
| 5 | (A, C, B) |
- 查找频率:现在我们必须找出每个项目的单独频率:
| 项目 | 频率 |
|---|---|
| A | 4 |
| B | 4 |
| C | 3 |
| D | 3 |
让我们设置最小阈值或最小支持度为 50%:
-
-
最小支持度 = (5*50/100) = 2.5
-
最小支持度的上限 = 2.5 ~ 3
-
- 按频率优先排序项目:由于所有项目的频率都大于或等于最小支持度,所有项目都将包含在内。此外,根据它们的频率,将分配优先级或排名给项目:
| 项目 | 频率 | 排名 |
|---|---|---|
| A | 4 | 1 |
| B | 4 | 2 |
| C | 3 | 3 |
| D | 3 | 4 |
项目的顺序是:A、B、C 和 D(按频率降序排列)
- 按优先级排序项目:现在,项目的顺序将根据基于频率分配给各种项目的优先级来设置。目前,顺序是 A、B、C 和 D:
| t_id | Items | Order by priority |
|---|---|---|
| 1 | (B, C, D, A) | (A, B, C, D) |
| 2 | (B, C, D) | (B, C, D) |
| 3 | (D, A) | (A, D) |
| 4 | (A, B) | (A, B) |
| 5 | (A, C, B) | (A, B, C) |
频繁模式树增长
我们将从以下行研究不同的频繁模式树的增长:
- 第一行: 每个 FP-树都以一个空节点作为根节点。让我们绘制树的第一个行及其频率:

- 第二行: 它包含 {B,C,D}。A 缺失,所以我们不能将其与前面的节点合并。因此,我们必须创建另一个节点,整体情况如下所示:

- 第三行: 它包含 {A,D}。B 和 C 缺失,但我们可以将其与前面的节点绑定。A 遇到重复,因此其频率将发生变化。现在变为 2:

- 第四行: 它包含 {A,B}。我们可以将其与前面的节点绑定,并将遍历应用于前面的节点。A 和 B 遇到重复,因此其频率将发生变化。分别变为 3 和 2:

- 第五行: 它包含 {A,B,C}。再次,它可以与前面的节点绑定,并且 A、B 和 C 出现重复,因此它们的频率将发生变化。分别变为 4、3 和 2:

验证
现在,让我们计算我们得到的最终树的频率,并将每个项目的频率与表格进行比较,以确保我们在表格中得到了正确的频率:
-
A:4
-
B:4
-
C:3
-
D:3
现在,我们将从底部到顶部进行。我们将找出 D 出现的分支:

我们可以看到有三个分支中出现了 D:
-
BC: 1
-
ABC: 1
-
A: 1
这些分支被称为 D 的条件模式基。当我们这样做时,有一些要点需要注意:
-
即使我们从底部向上遍历,我们也以从上到下的方式编写分支
-
D 不是其中的一部分
-
1 代表每个分支中 D 发生的频率
现在,D 的条件模式导致 A、B 和 C 的条件频率,分别是 2、2 和 2。所有这些都小于最小支持度(3)。因此,不能为它创建任何条件 FP-树。
现在,让我们对 C 进行操作。C 出现在以下分支中:

分支最终如下所示:
-
B:1
-
AB:2
这导致 A:2 和 B:3。因此,根据最小支持度,B 符合条件。现在条件树最终如下所示:

同样,对于不同的组合进行条件模式查找。因此,它建立了频繁项集数据集。
让我们看看如何在 Python 中实现。我们将使用一个名为pyfpgrowth的库。此外,我们将在下一节创建一个项集。
导入库
为了进行验证,我们将导入库并构建如所示的交易:
import pyfpgrowth
我们构建我们的交易如下:
transaction = [["bread", "butter", "cereal"],
["butter", "milk"],
["bread", "milk"],
["butter", "cereal", "milk"],
["egg", "bread"],
["egg", "butter"],
["cereal", "milk"],
["bread", "butter", "cereal", "egg"],
["cereal", "bread", "butter"]]
现在定义最小支持度以找到模式。find_frequent_patterns(),其中transactions是每次交易中购买的项目列表,2是设置的最小支持度阈值:
patterns = pyfpgrowth.find_frequent_patterns(transaction, 2)
最后,我们必须定义置信度以获取规则。规则是基于模式生成的,0.5是置信度的最小阈值。然后,我们将规则存储在名为rules的数据框中。rules最初包含一个前件、一个后件和置信度值:
rules = pyfpgrowth.generate_association_rules(patterns, 0.5)
print(rules)
我们得到以下输出:

这就是我们获取规则的方式。FP-growth 通常比 Apriori 有优势,因为它更快、更高效。
摘要
在本章中,我们学习了关联规则。我们还讨论了 Apriori 算法,该算法用于挖掘频繁项集以推导出各种关联规则。我们还了解了频繁模式增长(FP-growth),它与 Apriori 类似,以及频繁项集生成技术,它与 Apriori 算法类似。最后,我们看到了 FP-growth 如何比 Apriori 有优势,因为它更快、更高效,并通过一个例子来说明。
在下一章中,我们将学习概率图模型。我们将深入了解贝叶斯规则和贝叶斯网络。
第八章:概率图模型
在我们深入探讨贝叶斯网络(BN)的概念之前,我们应该了解概率论的理论。因此,我们将尝试简要介绍它们,并建立贝叶斯网络的基础。
我们已经知道,概率是事件发生的确定性/不确定性程度。然而,它也可以被称为信念程度,这在讨论贝叶斯网络时更为常用。
当我们抛一个公平的硬币时,我们说,关于发生正面/反面的信念程度是 0.5。这意味着我们对发生正面的信念程度与发生反面的信念程度一样强。概率可以如下表示:
p(Heads)=p(tails)=0.5
在本章中,我们将涵盖以下主题:
-
贝叶斯规则
-
贝叶斯网络
关键概念
在我们进入本章的主体之前,我们将介绍几个关键概念:
-
在离散分布的情况下,概率质量函数用于找出概率,p(X= x),其中 X 是一个离散随机变量,x 是一个实数值。
-
在连续分布的情况下,概率密度函数用于找出概率 p(X <= x)。在这种情况下,绘制一个概率曲线,曲线下的面积(积分)帮助我们确定概率。
-
条件概率是为了理解这一点,板球比赛可以是一个完美的例子。假设印度和澳大利亚之间有一场比赛安排,我们正在试图传递我们对印度获胜的信念。你认为这个概率会受到印度选择的队伍的影响吗?如果 维拉特·科赫利 和 罗希特·沙尔马 是队伍中的一员,印度赢得比赛的概率会受到什么影响?所以,p(India winning|Rohit and Virat are playing) 表示在 罗希特 和 维拉特 参赛的情况下,印度获胜的概率。本质上,这意味着一个事件的概率依赖于另一个事件的概率。这被称为条件概率。
给定 y,x 的概率可以表示如下:


- 链式法则通过使用随机变量的条件概率来计算一组随机变量的联合分布。从条件概率中,我们知道
。
这意味着如果有
个事件。联合概率分布将呈现如下:

贝叶斯定理
贝叶斯定理是概率论的一个基石。它源于条件概率和联合概率,并扩展了其应用范围。
我们将通过再次从板球中举一个例子,以简单的方式解释这一点。在板球中,场地条件随着从一个地方到另一个地方的变化而变化,这是决定队伍时可能是一个重要的因素。结果也可能依赖于它。
假设印度队去澳大利亚比赛,我们必须预测一名印度球员在这场比赛中得分百(100 分)的信念。如果这名球员有在那国打球的经验,我们可能会非常确信他可能会得分百。但是,还有另一名球员是第一次来这个国家。那么他的先验信念会是什么?当然,很多人会认为他得分百的信念较低。
然而,我们的先验信念会随着我们看到球员的表现而改变。也就是说,随着球员参加更多比赛,我们将有更多关于球员的数据。基于这些,后验信念将不断更新。它变化很大,很大程度上是由于观察或更多数据(称为似然)。贝叶斯定理基于这些概念。
假设 A[i] 与 B 形成互斥事件:

B 的概率如下:

我们从条件概率中得到 B 的概率如下:

因此:


现在,从方程 2 中提取
的值,并将其放入方程 1 中,我们得到以下结果:

在将先前的方程中的 P(B) 值替换后,我们得到以下结果:

首先看看方程 3。这被称为贝叶斯定理。
P(A|B) 被称为后验概率,需要被估计。在先前的例子中,这将是玩家在之前有在该地打球经验的情况下得分百的概率。
P(B|A) 被称为似然,是在给定我们的初始假设的情况下观察新证据的概率。例如,一名球员在之前有打板球经验的情况下得分百的概率。
P(A) 被称为先验概率,是在没有任何额外先验信息的情况下我们假设的概率。
P(B) 被称为边缘似然,是观察证据的总概率。
贝叶斯网络
贝叶斯网络是一种概率图模型,可用于构建解决商业问题的模型。这种应用非常广泛。例如,它可以用于异常检测、预测建模、诊断、自动洞察以及许多其他应用。
对于这里使用的某些词汇,你可能现在还感到陌生。例如,我们在这里所说的“图形化”是什么意思?
图是由一组节点和边组成的。节点用N={N1,N2……Nn}表示,其中独立变量位于每个节点上。边是节点之间的连接器。边可以用E={E1, E2……En}表示,并且有两种类型:
-
有向的,表示为
![图片]()
-
无向的,表示为:

通过节点和边,可以展示变量之间的关系。这可能是一个条件独立性关系或条件依赖关系。贝叶斯网络(BN)是一种可以在变量之间引入因果关系的技巧。尽管因果关系不是其本质的一部分,但在网络中拥有这种(因果关系)可以使结构相当紧凑。
让我们通过一个例子来看一下。有许多变量,例如晚起、高速公路上的事故、雨天、交通堵塞,他们将会迟到上班,以及会议迟到。如果一个人起床晚,这意味着上班迟到。高速公路上的事故可以导致交通堵塞,进而导致上班迟到。在雨天,道路更容易发生事故,而且也可能有缓慢移动的交通,这将导致交通堵塞,进而导致上班迟到。以下图表解释了该例子:

这种网络被称为有向无环图。无环意味着网络中没有循环。我们在这里讨论的是变量之间的关系。例如,晚起和会议迟到通常不是独立的。但考虑到上班迟到,它们是条件独立的。
此外,可能看起来晚起与高速公路上的事故之间没有联系和关系。也就是说,它们可能看起来彼此独立。然而,如果你知道上班迟到的重要性,那么这两个可以被称为条件独立。
因此,贝叶斯网络允许节点之间的条件独立性。同时,它是一个高效的联合概率分布表示,这是由链式法则实现的。
假设 X 代表 n 个独立变量或节点。弧或指向箭头代表变量之间的概率依赖或独立性。没有弧表示概率独立性。该网络是一个有向无环图,其中每个节点都保持局部概率分布,这也可以称为条件概率表(CPT)。
如果我们谈论前面的网络,那么我们需要整个网络所需的概率分布。为了简化,我们将所有节点都保持为布尔值。
节点的概率
让我们看看每个节点的概率,并找出会有多少概率出现在那里。
带有晚起和雨天的节点是父节点,因为没有节点指向这样的节点。不同的节点可以在以下要点中看到:
-
节点(晚起):作为父节点之一,我们只需找出晚起的概率。因此,要找到的概率计数在这里是 1。
-
节点(雨天):与晚起节点一样,概率的计数在这里也是 1。
-
节点(高速公路事故):作为雨天节点的子节点,它讨论了在雨天和不是雨天的情况下发生事故的概率。因此,概率计数在这里是 2。
-
节点(交通堵塞):它有两个父母(雨天和事故)。雨天有两个值,即真和假,与事故相同。结合两者将产生四种不同的组合。因此,概率计数将是 4。
-
节点(上班迟到)和节点(开会迟到):这两个节点也有类似的解释。这些概率的计数是 4:

概率的总数是 1 + 2 + 1 + 4 + 4 + 4 = 16。
如果它只是一个普通的联合概率分布而不是贝叶斯网络(BN),我们就会有 2⁶-1 个概率。因此,BN 使得网络相当紧凑。此外,我们还必须注意的一个更基本的假设是,每个节点在给定其直接父母的情况下对其非后裔是条件独立的。例如,起床晚和开会迟到在“上班迟到”也存在的情况下是条件独立的。一般来说,我们可以以下述方式表达 BN,这显示了联合分布如何转化为紧凑的结构:

如果G是图,X[i]是图G中的一个节点,P是X[i]节点的父母。
这里有一些关于方程的注意事项:
-
方程式的右侧是链式法则的应用,它展示了条件独立性关系。它是联合概率分布的图结构近似。
-
当然,图必须是循环的。
-
它可以提供显示各种事件之间关系的便利。
现在,让我们通过一个简单的场景来展示 CPT(共变概率定理)。以下是将三个事件组合起来的示例:
如果下雨,狗开始叫,男人会旷工:
-
下雨的概率(是/否)
-
狗会叫的概率(是/否)
-
男人是否会旷工(是/否)
让我们将网络准备成一个有向无环图。所有这些节点都反映一个事件,有向箭头是条件概率。我们将在这里看到如何读取这个图:
-
连接器 1 表示如果下雨狗会叫的概率
-
连接器 2 表示如果狗叫,男人会旷工的概率
以下图表显示了两个概率的流程图:

条件概率表(CPT)
让我们为连接器 1 做条件概率表(CPT):
| 狗叫 | 狗不叫 | 总计 | |
|---|---|---|---|
| 下雨 | 10 | 4 | 14 |
| 不下雨 | 8 | 5 | 13 |
| 总计 | 18 | 9 | 27 |
这里,我们讨论以下场景:
-
概率(狗叫|下雨) = 10/14
-
概率(狗不叫 | 下雨) = 4/14
-
概率(狗叫 | 不下雨) = 8/13
-
概率(狗不叫 | 不下雨) = 5/13
| 狗叫 | 狗不叫 | |
|---|---|---|
| 下雨 | 10/14 | 4/14 |
| 不下雨 | 8/13 | 5/13 |
以下图表详细显示了概率:

假设下雨的概率为 P(下雨) = 0.6,那么不下雨的概率为 P(不下雨) = 0.4。
假设男人旷工的条件概率表(CPT)如下:
| 男人旷工 | 男人不旷工 | |
|---|---|---|
| 狗叫 | 0.8 | 0.2 |
| 狗不叫 | 0.3 | 0.7 |
每个事件的概率都必须相对于父节点来计算。
现在,我们应该找出男人旷工、狗叫但不下雨的概率 P (男人旷工, 狗叫, 不下雨):
*= P (男人旷工|狗叫) *P (狗叫|不下雨) P(不下雨)
*=0.8 * (8/13) 0.4
=0.1969
训练集和测试集的示例
让我们用一个案例来演示,并在 Python 中实现它。我们将使用 Kaggle 上的泰坦尼克号数据。
数据已经被分成两组:
-
训练集(
train.csv) -
测试集(
test.csv)
数据是关于在泰坦尼克号上旅行的乘客。它捕捉了他们的特征:
-
pclass: 票等级 1 = 1 等,2 = 2 等,3 = 3 等 -
gender: 性别 -
Age: 年龄(年) -
sibsp: 泰坦尼克号上的兄弟姐妹/配偶数量 -
parch: 泰坦尼克号上的父母/孩子数量 -
ticket: 票号 -
fare Passenger: 乘客票价 -
cabin: 船舱号 -
embarked: 登船港口C = Cherbourg,Q = Queenstown, 和S = Southampton
我们必须构建模型来预测他们是否在泰坦尼克号沉没中幸存。最初,按照以下方式导入参数:
import pandas as pd
import numpy as np
我们在这里加载数据集:
traindf= pd.read_csv("train.csv")
testdf= pd.read_csv("test.csv")
我们必须寻找每个变量的唯一值数量,因为贝叶斯网络是离散模型:
for k in traindf.keys():
print('{0}: {1}'.format(k, len(traindf[k].unique())))
输出如下:

为了避免系统过载和过多的计算,我们将减少变量的数量:
for k in traindf.keys():
if len(traindf[k].unique())<=10:
print(k)
我们得到以下输出:

现在,我们剩下六个变量。
此外,如果需要将连续变量纳入模型,我们必须对它们进行离散化:
import math
def forAge(row):
if row['Age'] < 10:
return '<10'
elif math.isnan(row['Age']):
return "nan"
else:
dec = str(int(row['Age']/10))
return "{0}0's".format(dec)
decade=traindf.apply(forAge, axis=1)
print("Decade: {1}".format(k, len(decade.unique())))
输出结果如下:

现在让我们进行预处理:
def preprocess(df):
# create a dataframe with discrete variables (len<10)
filt=[k for k in df.keys() if len(df[k].unique())<=10]
filtr2=df[filt].copy()
forAge = lambda row: int(row['Age']/10) if not math.isnan(row['Age']) else np.nan
filtr2['Decade']=df.apply(forAge, axis=1)
filtr2=filtr2.dropna()
filtr2['Decade']=filtr2['Decade'].astype('int32')
return filtr2
对于traindf和testdf,我们使用以下方法:
ptraindf= preprocess(traindf)
ptestdf=preprocess(testdf)
我们需要保存这些数据,因为pyAgrum库只接受文件作为输入:
ptraindf.to_csv('post_train.csv', index=False)
ptestdf.to_csv( 'post_test.csv', index=False)
df=pd.read_csv('post_train.csv')
for k in df.keys():
print("{} : {}".format(k, df[k].unique()))
下面的输出结果如下:

import pyAgrum as gum
import pyAgrum.lib.notebook as gnb
现在,是时候构建模型了。在这里,选择RangeVariable和LabelizedVariable变量时需要格外小心:
template=gum.BayesNet()
template.add(gum.RangeVariable("Survived", "Survived",0,1))
template.add(gum.RangeVariable("Pclass", "Pclass",1,3))
template.add(gum.LabelizedVariable("Gender", "Gender",0).addLabel("female").addLabel("male"))
template.add(gum.RangeVariable("SibSp", "SibSp",0,8))
template.add(gum.RangeVariable("Parch", "Parch",0,9))
template.add(gum.LabelizedVariable("Embarked", "Embarked",0).addLabel('').addLabel('C').addLabel('Q').addLabel('S'))
template.add(gum.RangeVariable("Decade", "Calculated decade", 0,9))
gnb.showBN(template)
输出结果如下:

对于learnBN()函数,我们使用以下方法:
learner = gum.BNLearner('post_train.csv', template)
bn = learner.learnBN()
bn
下面的输出结果如下:

现在我们有了模型,让我们尝试从中提取信息:
gnb.showInformation(bn,{},size="20")
我们得到的输出结果如下:

变量的熵意味着值越大,变量的边际概率分布的不确定性就越大。熵值越低,不确定性就越低。Decade变量具有最高的熵,这意味着它是均匀分布的。Parch 具有较低的熵,分布不均匀。
熵的计算结果导致熵值在随机变量具有许多模式时往往会变大。
找到推理结果让我们看到了边际概率分布的情况:
gnb.showInference(bn)
输出结果如下:

现在,让我们看看如何进行分类:
gnb.showPosterior(bn,evs={},target='Survived')
我们得到的输出结果如下:

在这里,超过 40%的乘客存活。但我们没有施加任何条件。
假设我们想知道一位年轻男性的存活机会:
gnb.showPosterior(bn,evs={"Gender": "male", "Decade": 3},target='Survived')
下面的输出结果如下:

因此,存活的机会是 20.6%。
如果我们需要找出一位老妇人存活的机会,我们将按照以下步骤进行:
gnb.showPosterior(bn,evs={"Gender": "female", "Decade": 8},target='Survived')
输出结果如下:

现在,为了评估模型以了解其好坏,我们将绘制 ROC 曲线:
from pyAgrum.lib.bn2roc import showROC
showROC(bn, 'post_train.csv','Survived',"1",True,True)
输出结果如下:

在这里,AUC值为0.893508,相当不错。
我们在这里完成了建模部分。此外,我们还学习了概率论、贝叶斯网络、CPT 的计算以及如何在 Python 中执行它。
摘要
本章让我们了解了概率论。同时,概率论的应用也得到了实践。我们了解了贝叶斯定理和贝叶斯网络及其形成方式。我们亲自动手计算了 CPT。最后,我们通过一个案例了解了如何使用贝叶斯网络进行分类。读者现在将具备深入了解贝叶斯定理和贝叶斯网络的能力。
在下一章,我们将研究深度学习中的选定主题。
第九章:深度学习中的选讲主题
在第四章*“训练神经网络”中,我们探讨了什么是人工神经网络(ANN)以及这种模型是如何构建的。你可以认为深度神经网络是 ANN 的延伸版本;然而,它也有自己的一套挑战。
在本章中,我们将学习以下主题:
-
什么是深度神经网络?
-
如何初始化参数
-
对抗网络——生成对抗网络和贝叶斯生成对抗网络
-
深度高斯过程
-
霍 inton 的胶囊网络
深度神经网络
让我们回顾一下我们在第四章*“训练神经网络”中学习的内容。神经网络是机器模拟人脑,被视为一套旨在从数据中提取模式的算法。它有三个不同的层:
-
输入层
-
隐藏层
-
输出层
感官数值数据(以向量的形式)通过输入层,然后通过隐藏层生成它自己的感知和推理,以在输出层产生最终结果。
你能回忆起我们在第四章“训练神经网络”中学到的内容,关于人工神经网络(ANN)中的层数以及我们如何计算它们吗?当我们有如下图中所示的层时,你能计算出层数吗?记住,我们只计算隐藏层和输出层。所以,如果有人问你你的网络中有多少层,在回答时不要包括输入层:

是的,没错——前面的架构中有两层。那么对于下面的网络呢?

这个网络有三个层,包括两个隐藏层。随着层的增加,模型变得更深。
为什么我们需要深度学习模型?
深度学习模型是一个高度非线性的模型,具有多层和多个节点按顺序工作以解决业务问题。每一层都被分配了不同的任务。
例如,如果我们有一个面部检测问题,隐藏层 1 找出图像中存在的哪些边缘。层 2 找出边缘的组合,这些边缘开始呈现出眼睛、鼻子和其他部分的形状。层 3 使对象模型得以创建,从而形成人脸的形状。以下图表显示了不同的隐藏层:

这里,我们得到了一个逻辑回归模型,也称为单层神经网络。有时,它也被称作最浅层网络。这里可以看到的第二个网络有一个两层网络。再次,它是一个浅层网络,但不如前一个浅。下一个架构有三个层,这使得事情更有趣。网络现在变深了。最后一个架构有一个六层架构,由五个隐藏层组成。层数变得更深了。
深度神经网络符号
符号的解释如下:
-
l:层数为 4
-
n^([l]):第 l 层的节点数
对于以下架构,这是如下所示:
-
n ^([0]):输入层的节点数,即 3
-
n ^([1]):5
-
n ^([2]):5
-
n ^([3]):3
-
n ^([4]):1
-
a ^([l]):第 l 层的激活值

正如我们已知的,以下方程通过各层:
z = w^TX + b
因此,我们得到以下结果:
-
激活:a = σ(z)
-
w^([l]):第 l 层的权重
-
b^([l]):第 l 层的偏置
深度网络的正向传播
让我们看看这些方程是如何为第 1 层和第 2 层设置的。如果训练示例集 X 是前述网络中的 (x1, x2, x3)。
让我们看看方程是如何应用于第 1 层的:

第 1 层的激活函数如下:

输入也可以表示如下:

对于第 2 层,输入将是以下内容:

这里应用到的激活函数如下:

同样,对于第 3 层,应用的输入如下:

第 3 层的激活函数如下:

最后,这是最后一层的输入:

这是它的激活:

因此,广义正向传播方程如下:


参数 W 和 b
让我们讨论以下架构。首先,让我们记下我们在上一节中学到的内容。看一下以下图表:

这里,我们可以看到以下内容:
-
l:层数:6
-
n ^([l]):第 l 层的节点数
![图片]()
-
n ^([0]):输入层的节点数:3 ::
-
n ^([1]):第一层的节点数:4 ::
这个方程如下:
n ^([2])= 4 :: n ^([3]) = 4 :: n ^([4]) = 4 :: n ^([5]) =3 :: n ^([6])= 1
实现前向传播意味着隐藏层 1 可以通过以下方程表示:
…..(1)
你能确定 z、w 和 X 的前向传播维度吗?
让我们来讨论这个问题。X表示输入层向量或节点,我们知道有 3 个节点。我们能找出输入层的维度吗?嗯,是的,它是(n^([0]), 1)——或者你也可以说它是(3,1)。
对于第一隐藏层呢?由于第一隐藏层有三个节点,z([1])*的维度将是(*n([1]),1)。这意味着维度将是(4,1)。
z^([1])和 X 的维度已经被确定。通过观察前面的方程,很明显z([1])*和*w([1])X的维度必须相同(来自线性代数)。那么,你能推导出w^([1])的维度吗?我们知道,从线性代数的角度来看,矩阵 1 和矩阵 2 之间的乘法只有在矩阵 1 的列数等于矩阵 2 的行数时才可能。因此,w^([1])的列数必须等于矩阵 X 的行数。这将使得w^([1])的列数为 3。然而,正如我们已经讨论过的,z([1])*和*w([1])X的维度必须相同,因此前者的行数应该等于后者的行数。因此,w^([1])的行数将变为 4。好的,我们现在已经得到了w([1])*的维度,它是(4,3)。为了使这个结论更普遍,我们也可以说*w([1])的维度是(n([1]),*n([0]))。同样,w([2])*的维度将等于(*n([2]),n([1])*)或者(当前层的节点数,前一层节点数)。这将使得*w([2])*的维度为(4,4)。让我们来概括一下。让我们看看以下方程的维度:
w^([1])= (n([1]),n([l-1]))
那么,偏置b([1])*的维度是什么?你能利用线性代数并找出它吗?现在这对你来说应该像吃蛋糕一样简单。是的,你现在可能已经正确猜到了。它具有与*z([1])相同的维度。让我来解释这一点,为了大家的利益。根据方程,左侧的维度应该等于右侧的维度。此外,w^([1])X + b([1])*是两个矩阵的和,众所周知,只有当两个矩阵具有相同的维度时才能相加;也就是说,它们必须有相同的行数和列数。因此,*b([1])的维度将等于w([1])X*;反过来,它将等于*z([1])(它是(4,1))。
在一般化的意义上,b^([1])的维度是( n^([1]), 1)*。
对于反向传播,情况如下:
-
d**w^([l])的维度= (n([l]),n([l-1]))*
-
db**^([l])的维度= (n^([l]), 1)*
前向和反向传播
让我通过一个例子向您展示正向传播和反向传播是如何工作的。
我们有一个具有两层(1 个隐藏层和 1 个输出层)的网络。每个层(包括输入层)都有两个节点。它还有偏置节点,如下面的图所示:

上图中使用的符号如下:
-
IL: 输入层
-
HL: 隐藏层
-
OL: 输出层
-
w: 权重
-
B: 偏置
我们已经得到了所有所需字段的值。让我们将这些值输入到网络中,看看它如何流动。这里使用的激活函数是 Sigmoid 函数。
传递给隐藏层第一个节点的输入如下:
InputHL1 = w1IL1 + w3IL2 + B1
InputHL1= (0.20.8)+(0.40.15) + 0.4 =0.62
传递给隐藏层第二个节点的输入如下:
InputHL2 = w2IL1 + w4IL2 + B1
InputHL2 = (0.250.8) +(0.10.15) + 0.4 = 0.615
为了找到输出,我们将使用我们的激活函数,如下所示:
OutputHL1 =
= 0.650219
OutputHL2 =
= 0.649081
现在,这些输出将作为输入传递到输出层。让我们计算输出层节点的输入值:
InputOL1 = w5Output_HL1 + w7Output_HL2 + B2 = 0.804641
InputOL2= w6Output_HL1 + w8Output_HL2 + B2= 0.869606
现在,让我们计算输出:
Output[OL1] =
= 0.690966
Output[OL2] =
= 0.704664
误差计算
我们现在可以使用平方误差函数计算每个输出神经元的误差,并将它们相加以得到总误差:
*Etotal = *
EOL1 = 输出层第一个节点的误差 =
=0.021848
EOL2 = 输出层第二个节点的误差 = 
=0.182809
Total Error = Etotal= EOL1 + EOL2 = 0.021848 + 0.182809 = 0.204657
反向传播
反向传播的目的是更新网络中每个权重的值,以便它们使实际输出更接近目标输出,从而最小化每个输出神经元和整个网络的误差。
让我们先关注输出层。我们应该找出 w5 的变化对总误差的影响。
这将由
决定。它是 Etotal 相对于 w5 的偏导数。
让我们应用链式法则:



= 0.690966 – 0.9 = -0.209034


= 0.213532
InputOL1 = w5OutputHL1 + w7OutputHL2 + B2
= 0.650219
现在,让我们回到旧方程:


为了更新权重,我们将使用以下公式。我们已将学习率设置为 α = 0.1:

同样,
也应该被计算。方法保持不变。我们将留给你计算,因为它将帮助你更好地理解概念。
当涉及到隐藏层和计算时,方法仍然保持不变。然而,公式会有所变化。我会帮你弄清楚公式,但其余的计算必须由你自己完成。
我们在这里取 w1。
让我们在这里应用链式法则:

这个公式必须用于 w2、w3 和 w4。请确保你对 E_total 进行其他权重的偏导数,最后使用学习率公式来获取更新的权重。
前向传播方程
我们知道周围的方程。如果这个输入是 a^([l-1]),那么输出将是 a^([l])。然而,还有一个缓存部分,它就是 z^([l]),如下图中所示:

这里,这分解为 w([1])a([l-1]) +b^([l]) (记住 a^([0]) 等于 X)。
反向传播方程
执行反向传播需要以下方程:


这些方程将给你一个关于幕后发生的事情的思路。在这里,添加了一个后缀,d,它表示在反向传播期间起作用的偏导数:


参数和超参数
当我们着手构建深度学习模型时,你需要知道如何同时监控参数和超参数。但我们到底理解得有多好呢?
当涉及到参数时,我们拥有权重和偏差。当我们开始训练网络时,首要步骤之一就是初始化参数。
偏置初始化
初始化偏置为零是一种常见做法,因为神经元的对称打破由随机权重的初始化来处理。
超参数
超参数是深度学习网络的基本构建块之一。它是决定网络最佳架构(例如,层数)的元素,也是确保网络如何训练的因素。
以下是深度学习网络的各个超参数:
-
学习率:这是负责确定网络训练速度的。慢速学习率确保平滑收敛,而快速学习率可能不会平滑收敛。
-
周期:周期数是在训练过程中网络消耗整个训练数据的次数。
-
隐藏层数量:这决定了模型的结构,有助于实现模型的最佳容量。
-
节点数(神经元):应权衡使用的节点数。它决定了是否已经提取了所有必要的信息以产生所需输出。节点数将决定过拟合或欠拟合。因此,建议与正则化一起使用。
-
Dropout:Dropout 是一种正则化技术,通过避免过拟合来提高泛化能力。这在第四章《训练神经网络》中进行了详细讨论。Dropout 值可以在 0.2 到 0.5 之间。
-
动量:这决定了向收敛方向下一步的方向。在 0.6 到 0.9 之间,它处理振荡。
-
批量大小:这是输入到网络中的样本数,之后发生参数更新。通常,它取 32、64、128、256。
为了找到最佳的超参数数量,部署网格搜索或随机搜索是明智的。
用例 - 数字识别器
修改后的国家标准与技术研究院(MNIST)实际上是为计算机视觉的“hello world”准备的。考虑到它在 1999 年的发布,这个数据集已经作为基准分类算法的主要基础。
我们的目标是从包含数万张手写图像的数据集中正确识别数字。我们精心制作了一套教程风格的内核,涵盖了从回归到神经网络的所有内容:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import seaborn as sns
%matplotlib inline
from sklearn.model_selection import train_test_split
import itertools
from keras.utils.np_utils import to_categorical # convert to one-hot-encoding
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPool2D
from keras.optimizers import SGD
from keras.preprocessing.image import ImageDataGenerator
sns.set(style='white', context='notebook', palette='deep')
np.random.seed(2)
# Load the data
train = pd.read_csv("train.csv")
test = pd.read_csv("test.csv")
Y_train = train["label"]
# Drop 'label' column
X_train = train.drop(labels = ["label"],axis = 1)
Y_train.value_counts()
前一段代码的输出如下:

X_train.isnull().any().describe()
这里,我们得到以下输出:

test.isnull().any().describe()
这里,我们得到以下输出:

X_train = X_train / 255.0
test = test / 255.0
通过将图像重塑为 3 维,我们得到以下结果:
Reshape image in 3 dimensions (height = 28px, width = 28px, canal = 1)
X_train = X_train.values.reshape(-1,28,28,1)
test = test.values.reshape(-1,28,28,1)
Encode labels to one hot vectors
Y_train = to_categorical(Y_train, num_classes = 10)
# Split the dataset into train and the validation set
X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train, test_size = 0.1, random_state=2)
执行以下代码后,我们将能够看到编号的图表:
pic = plt.imshow(X_train[9][:,:,0])
输出如下:

顺序模型现在如下:
model = Sequential()
model.add(Conv2D(filters = 32, kernel_size = (5,5),padding = 'Same', activation ='relu', input_shape = (28,28,1)))
model.add(Conv2D(filters = 32, kernel_size = (5,5),padding = 'Same', activation ='relu'))
model.add(MaxPool2D(pool_size=(2,2)))
model.add(Dropout(0.25))
model.add(Conv2D(filters = 64, kernel_size = (3,3),padding = 'Same', activation ='relu'))
model.add(Conv2D(filters = 64, kernel_size = (3,3),padding = 'Same', activation ='relu'))
model.add(MaxPool2D(pool_size=(2,2), strides=(2,2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(256, activation = "relu"))
model.add(Dropout(0.5))
model.add(Dense(10, activation = "softmax"))
当我们定义优化器时,我们得到以下输出:
# Define the optimizer
optimizer = SGD(lr=0.01, momentum=0.0, decay=0.0)
当我们编译模型时,我们得到以下输出:
# Compile the model
model.compile(optimizer = optimizer, loss = "categorical_crossentropy", metrics=["accuracy"])
epochs = 5
batch_size = 64
接下来,我们生成图像生成器:
datagen = ImageDataGenerator(
featurewise_center=False, # set input mean to 0 over the dataset
samplewise_center=False, # set each sample mean to 0
featurewise_std_normalization=False, # divide inputs by std of the dataset
samplewise_std_normalization=False, # divide each input by its std
zca_whitening=False, # apply ZCA whitening
rotation_range=10, # randomly rotate images in the range (degrees, 0 to 180)
zoom_range = 0.1, # Randomly zoom image
width_shift_range=0.1, # randomly shift images horizontally (fraction of total width)
height_shift_range=0.1, # randomly shift images vertically (fraction of total height)
horizontal_flip=False, # randomly flip images
vertical_flip=False) # randomly flip images
datagen.fit(X_train)
history = model.fit_generator(datagen.flow(X_train,Y_train, batch_size=batch_size),
epochs = epochs, validation_data = (X_val,Y_val),
verbose = 2, steps_per_epoch=X_train.shape[0] // batch_size)
输出如下所示:

我们预测模型如下:
results = model.predict(test)
# select with the maximum probability
results = np.argmax(results,axis = 1)
results = pd.Series(results,name="Label")
results
输出如下所示:

生成对抗网络
生成对抗网络(GANs)是另一种深度神经网络架构,是两个相互竞争和合作的网络的组合。它由 Ian Goodfellow 和 Yoshua Bengio 于 2014 年提出。
GANs 可以学习模仿任何数据分布,这理想情况下意味着 GANs 可以被训练来创建与现有领域中的任何类似的对象,例如图像、音乐、语音和散文。它可以创建任何从未存在过的物体的照片。从某种意义上说,它们是机器人艺术家,它们的输出令人印象深刻。
它属于无监督学习,其中两个网络在训练过程中各自学习其任务。其中一个网络被称为生成器,另一个被称为判别器。
为了使这更容易理解,我们可以将生成对抗网络(GAN)想象成一个伪造者(生成器)和警察(判别器)的案例。一开始,伪造者向警察展示假币。警察就像侦探一样,发现这些是假币(如果你想了解判别器是如何工作的,也可以把 D 想象成侦探)。警察将他的反馈传递给伪造者,解释为什么这些钱是假的。伪造者根据收到的反馈做一些调整,并基于这些反馈制作新的假币。警察说这些钱仍然是假的,并将他的新反馈与伪造者分享。然后,伪造者根据最新的反馈尝试制作新的假币。这个过程无限循环,直到警察被这些看起来真实的假币欺骗。当创建 GAN 模型时,生成器和判别器从零开始相互学习。它们似乎是对抗的,但实际上它们在互相帮助学习。这两个之间的反馈机制有助于模型变得更加鲁棒。
判别器是一个非常优秀的学习者,因为它能够从现实世界中学习任何东西。也就是说,如果你想让它学习关于猫和狗的图片,以及它被要求区分的 1,000 个不同类别,它将能够轻松做到,就像这样:

噪声进入生成器;然后,生成器的输出通过判别器,我们得到一个输出。同时,判别器正在对狗的图片进行训练。然而,在最开始,即使是狗的图片也可能被判别器错误地分类为非狗图片,并注意到这个错误。这个错误通过网络反向传播。
霍 inton 的胶囊网络
深度学习之父杰弗里·辛顿通过引入一个新的网络在深度学习领域引起了巨大的轰动。这个网络被称为胶囊网络(CapsNet)。还提出了一个训练这个网络的算法,称为胶囊之间的动态路由。辛顿首次在 2011 年的论文《转换自编码器》中提到了它。2017 年 11 月,辛顿和他的团队发表了一篇关于胶囊网络的完整论文。
胶囊网络和卷积神经网络
卷积神经网络(CNN)是深度学习领域的一个重要里程碑。它让每个人都感到兴奋,也是新研究的基础。但是,正如人们所说,世界上没有什么是完美的。我们心爱的 CNN 也不例外。
你能回忆起 CNN 是如何工作的吗?CNN 最重要的任务是执行卷积。这意味着一旦你将一张图片通过 CNN,卷积层就会从图像像素中提取出特征,如边缘和颜色梯度。其他层会将这些特征组合成一个更复杂的特征。而且,一旦密集层被保留,它就使得网络能够执行分类任务。以下图表显示了我们在工作的图像:

上述图表是一个基本的 CNN 网络,它被用来检测图像中的汽车。以下图表显示了同一辆车的完整图像和碎片化图像:

假设我们将这两张图片通过 CNN 网络(用于检测汽车)传递过去——网络对这两张图片的响应会是什么?你能思考一下并给出答案吗?为了帮助你,一辆车有几个组成部分,如车轮、挡风玻璃、引擎盖等,但人类眼睛认为当所有这些部分/组件按顺序排列时,它才是一辆车。然而,对于 CNN 来说,只有特征才是重要的。CNN 不考虑相对位置和方向关系。因此,这两张图片都将被网络分类为汽车,尽管这在人类眼中并非如此。
为了弥补这一点,CNN 包括最大池化,这有助于增加更高层神经元的视野,从而使得检测更高阶特征成为可能。最大池化使得 CNN 能够工作,但同时也发生了信息损失。这是 CNN 的一个大缺点。
摘要
在本章中,我们研究了深度神经网络以及为什么我们需要深度学习模型。我们还学习了前向和反向传播,以及参数和超参数。我们还讨论了 GANs、深度高斯过程、胶囊网络和 CNN。
在下一章中,我们将研究因果推断。
第十章:因果推断
在本章的最后,我们将学习以下主题:
-
时间序列中的格兰杰因果性(因果关系的计量经济学方法)
-
图形模型因果关系
格兰杰因果性
在时间序列中,我们通常使用单变量数据。也就是说,我们使用一个序列来预测其未来的值。比如说,我们正在研究谷歌的股价数据,并被要求预测股价的未来值。在这种情况下,我们需要谷歌股价的历史数据。基于这些数据,我们将做出预测。
然而,有时我们需要多个时间序列来进行预测。但为什么我们需要多个时间序列呢?有什么猜测吗?
下面的图表显示了谷歌的股价数据:

答案是我们需要理解和探索多个时间序列之间的关系,因为这可以提高我们的预测能力。例如,我们得到了GDP 平减指数:服务业和WPI:所有商品的相关时间序列,如下所示:

这两点似乎存在某种关系。当我们需要预测GDP 平减指数:服务业时,我们可以使用WPI:所有商品的时间序列数据作为输入。这被称为格兰杰因果性。
更恰当地说,格兰杰因果性是调查时间序列中两个变量之间因果关系的一种方法。这种方法是因果关系的概率描述。
尽管我们在这里讨论的是因果关系,但这并不完全相同。通常,因果关系与变量 1 是变量 2 的原因或反之亦然的情况相关联。然而,在格兰杰因果性中,我们并不测试真正的因果关系。基本的原因是要知道在时间序列中是否某个变量先于另一个变量出现。
你并不是在测试真正的因果关系。你想要了解的是否是某个变量在时间序列中是否先于另一个变量出现。也就是说,如果我们发现数据中存在格兰杰因果性,那么在真正的意义上,并不存在任何因果关系。
当计量经济学家说“原因”时,他们的意思是格兰杰-原因,尽管一个更合适的词可能是优先级。格兰杰因果性是由 2003 年诺贝尔经济学奖获得者,Clive Granger 教授提出的。
让我们再看一个例子。这里,我们有经合组织国家的每人 GDP。我们可以看到,经合组织国家的 GDP 数量在增长和模式上相似。我们可以假设这些国家由于相互依赖而相互负责各自的 GDP 增长。
下面的图表显示了经合组织国家的每人 GDP:

我们可以利用一个国家的序列来预测另一个国家。这种情况通常在金融时间序列中更为普遍。印度的股票市场,如 NSE/BSE 等,可能会影响 NYSE。因此,NYSE 指数可以用来预测 NSE 指数。
让我们在其中加入一点数学。假设有两个时间序列,X(t) 和 Y(t)。如果 X(t) 的过去值有助于预测 Y(t) 的未来值,那么可以说 X(t) 格兰杰引起 Y(t)。
因此,Y(t) 是 Y(t) 的滞后和 X(t) 的滞后的函数。它可以表示为如下:
Y(t) = f(Yt-p, Xt-p)
然而,这仅在以下情况下成立:
-
原因发生在结果之前。这意味着 Y(t) = f(Xt-1),
-
原因对其效果的未来值有显著信息,例如:
Y(t) = a1 Yt-1 + b1 Xt-1 + error
Xt-1 在 Y(t) 上增加了一个额外的效果。效果的大小由 b1 决定。
假设我们有两个方程:
Yt= a0+ a1 Yt-1*
Yt= a0 + a1Yt-1 + a2Xt-1
此处的零假设如下:
- H0: a2=0: 这意味着没有其他序列对 Yt 的 Xt-1 产生影响
此处的备择假设如下:
- H1: a2≠ 0: 这意味着存在其他序列对 Xt-1 对 Yt 的显著影响
我们运行 t 检验以确定其他序列 Xt-1 对 Yt 是否有显著影响。
如果拒绝零假设,我们可以说这是一个格兰杰因果关系的情况。
F 检验
运行此测试的基本步骤如下:
-
构建零假设及其备择假设。
-
选择滞后项。这可以取决于你拥有的数据量。选择滞后 i 和 j 的一种方法是通过运行模型阶数测试。选择多个值并运行格兰杰测试,以查看不同滞后水平的结果是否相似。
-
还要确定 f 值。两个方程可以用来找出对于所有滞后 j,βj = 0 是否成立。
局限性
这种方法的不同局限性如下:
-
格兰杰因果关系并非真正的因果关系
-
如果 X(t) 通过第三个变量 Z(t) 影响 Y(t),那么很难找到格兰杰因果关系
用例
在这里,我们有一个名为AirQualityUCI的多元时间序列数据集。我们必须测试 NOx 是否对 NO2 有格兰杰因果关系。
由于我们没有 Python 中用于多元格兰杰因果关系的库,我们将使用 R 的lmtest包来完成这项工作。
加载lmtest库。如果库不存在,你需要按照以下方式安装它:
install.packages("lmtest")
library(lmtest)
加载数据。然后,使用grangertest函数找出 NOx 和 NO2 之间是否存在任何显著关系:
data= read.csv("AirQualityUCI.csv")
grangertest(NOx.GT. ~ NO2.GT., order = 3, data = data)
输出如下:

因此,f 检验变得显著,这意味着滞后 NO2 的系数对 NOx 有显著影响。
图形因果模型
该模型在第八章,概率图模型中进行了详细说明。我们也会在这里简要地探讨它。
贝叶斯网络是有向无环图(DAGs),其中节点代表感兴趣的变量(例如,设备的温度,患者的性别,物体的特征,事件的发生,等等)。变量之间的因果影响通过链接表示。影响强度可以通过与网络中父节点和子节点簇相关的条件概率来描绘。在以下图表中,我们可以看到具有节点和边的因果模型:

节点代表变量,边代表变量之间的条件关系。我们正在寻找的是完整的联合概率分布。在这里,正在讨论条件依赖性。雨导致地面变湿。然而,赢得彩票与其他变量无关。它具有条件独立性,如以下图表所示:

这里,条件独立性的概率如下:
P(彩票,雨,湿地面)= P(彩票) P(雨) P(雨|湿地面)
因此,我们可以这样说,贝叶斯网络通过将条件概率作为边来描述所有变量之间的概率分布。
让我们看看一个 Python 的例子:
- 首先,我们需要加载库,
CausalGraphicalModel,如下所示:
from causalgraphicalmodels import CausalGraphicalModel
- 让我们为以下条件设置模型:如果某人正在做
Job,并且它由Smartwork和Hardwork供电,那么他/她将获得奖励,并最终获得晋升:
Model = CausalGraphicalModel(
nodes=["Job", "Smartwork", "Hardwork", "Reward", "Promotion"],
edges=[
("Job", "Smartwork"),
("Job", "Hardwork"),
("Smartwork", "Reward"),
("Hardwork", "Reward"),
("Reward", "Promotion")
]
)
Model.draw()
以下代码的输出如下:

- 让我们获取分布:
print(Model.get_distribution())
然后,我们将得到以下输出:

- 让我们提取所有的条件独立性关系:
Model.get_all_independence_relationships()

在这里,我们能够评估变量之间的条件独立性。
- 让我们把
Reward混合进来:
Intervene = Model.do("Reward")
Intervene.draw()

摘要
在本章中,我们研究了格兰杰因果性,这是使用单一时间序列来预测其未来值,以及不同的图形因果模型。图形因果模型涵盖了两个例子,这将给我们一个关于图形因果模型的基本概念。
第十一章:高级方法
我们已经到达了这本书的最后一章。然而,这并不意味着我们可以忽视即将讨论的主题。这些主题是当前最前沿的,并将使你与其他人区分开来。
本章我们将涵盖以下主题:
-
核主成分分析
-
独立成分分析
-
压缩感知
-
贝叶斯多重插补
-
自组织映射
简介
在上一章中,我们了解了主成分分析(PCA)是什么,它是如何工作的,以及我们应该在何时部署它。然而,作为一种降维技术,你认为你可以在每个场景中都使用它吗?你能回忆起我们讨论过的它背后的障碍或潜在假设吗?
是的,PCA 背后最重要的假设是它适用于线性可分的数据集。然而,在现实世界中,你很少遇到这种类型的数据集。我们需要一种方法来捕捉非线性数据模式。
在左侧,我们有一个包含两个类别的数据集。我们可以看到,一旦我们到达投影并建立组件,PCA 对它没有影响,并且它不能在二维维度上通过一条线将其分开。也就是说,PCA 只有在我们有低维度和线性可分数据时才能很好地工作。以下图显示了两个类别的数据集:

正是因为这个原因,我们引入了核方法:这样我们就可以将其与 PCA 结合以实现它。
只为了回顾一下你关于核方法学到的知识,我们将简要讨论它及其重要性:
-
我们在低维空间中有数据。然而,当我们有非线性数据(如下面的图所示)时,有时很难进行分类(绿色和红色)。话虽如此,我们确实清楚地理解,拥有一种可以将数据从低维映射到高维的工具将导致适当的分类。这个工具被称为核方法。
-
相同的数据集在新特征空间中表现出线性可分性。
下图显示了低维和高维空间中的数据:

为了将前图中绿色和红色点进行分类,特征映射函数必须将数据从二维转换为三维,即Φ = R² → R³。这个方程如下:

核方法的目标是找出并选择核函数K。这样我们就可以在新维度中找到几何特征并对数据模式进行分类。让我们看看这是如何完成的:





这里,Phi 是一个特征映射函数。但我们是否总是需要知道特征映射函数?实际上并不需要。核函数K完成了这项工作。给定核函数K,我们可以得到一个特征空间H。两种流行的核函数是高斯核函数和多项式核函数。
选择一个合适的核函数将使我们能够很好地了解新特征空间中数据的特征。
现在我们已经熟悉了核技巧,让我们继续学习核 PCA。
核 PCA
核 PCA 是一种算法,它不仅保留了 PCA 的主要精神,而且更进一步,利用核技巧使其能够处理非线性数据:
- 让我们定义特征空间中数据的协方差矩阵,它是映射函数与映射函数转置的乘积:

它与我们用于 PCA 的类似。
- 下一步是解下面的方程,以便我们能够计算主成分:

这里,C[F]是特征空间中数据的协方差矩阵,v是特征向量,λ(lambda)是特征值。
- 让我们将步骤 1的值放入步骤 2中——即在步骤 2的方程中的C[F]的值。特征向量将如下所示:

这里,
是一个标量数。
- 现在,让我们将核函数添加到方程中。让我们将Φ(x[k])乘以公式的两边,
:

- 让我们将步骤 3中的方程中的v值放入步骤 4的方程中,如下所示:

- 现在,我们调用K
。通过输入K的值简化步骤 5的方程,我们得到以下结果:

在进行特征值分解后,我们得到以下结果:

在对特征空间进行归一化以进行中心化后,我们得到以下结果:

现在,让我们在 Python 中执行核 PCA。我们将保持简单,并使用 Iris 数据集进行操作。我们还将看看我们如何利用模型中的新压缩维度:
- 让我们加载库:
import numpy as np # linear algebra
import pandas as pd # data processing
import matplotlib.pyplot as plt
from sklearn import datasets
- 然后,加载数据并创建解释变量和目标变量的单独对象:
iris = datasets.load_iris()
X = iris.data
y = iris.target
- 让我们看看解释数据:
X

- 让我们将数据分为训练集和测试集,如下所示:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.25, random_state = 0)
- 现在,我们可以标准化数据:
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
X_train = sc.fit_transform(X_train)
X_test = sc.transform(X_test)
- 让我们看看
X_train:
X_train
输出如下:

- 现在,让我们将核 PCA 应用到此。这里,我们试图将数据压缩成仅两个成分。这里选择的核心是径向基函数:
from sklearn.decomposition import KernelPCA
kpca = KernelPCA(n_components = 2, kernel = 'rbf')
X_train2 = kpca.fit_transform(X_train)
X_test2 = kpca.transform(X_test)
我们借助核 PCA 获得了新的训练和测试数据。
- 让我们看看数据看起来像什么:
X_train2
我们得到以下输出:

现在,我们这里有两个成分。之前,X_train 展示了四个变量。现在,数据已经被缩减成两个字段。
独立成分分析
独立成分分析 (ICA) 在降维方面与 PCA 类似。然而,它起源于信号处理领域,在那里他们面临这样一个问题,即多个信号从多个来源传输,并设置了多个设备来捕获它。然而,问题是设备捕获的信号并不清晰,因为它碰巧是多个来源的混合。他们需要为产生 ICA 的不同设备提供清晰和独立的接收。Heralt 和 Jutten 提出了这个概念。
PCA 和 ICA 的区别在于,PCA 侧重于寻找不相关的因子,而 ICA 则是关于推导独立因子。困惑吗?让我来帮你。不相关的因子意味着它们之间没有线性关系,而独立性意味着两个因子之间没有任何影响。例如,数学得高分与你在哪个州生活无关。
该算法的一个基本假设是变量是未知潜在变量和独立变量的线性组合。
数据 *xi * 使用隐藏变量 si 进行建模:

这里, i= 1,2,3..........n.
它也可以写成矩阵分解的形式作为 x=As:

这里,我们有以下内容:
-
A:常数混合矩阵
-
s:相互独立的潜在因子矩阵
当我们有 X 时,我们必须估计 A 和 s 的值。
换句话说,我们的目标是找到 W,它是 W= A^(-1),这是一个去混矩阵。
这里, *s[ij] *必须与统计独立且非高斯(不遵循正态分布)。
ICA 预处理
ICA 的预处理可以按以下方式进行:
-
中心化:第一步是使 x 中心化。也就是说,我们需要从 x 中减去其均值向量,以便使 x 成为零均值变量。
-
白化:在将数据通过 ICA 之前,我们应当对数据进行白化。这意味着数据必须是相互独立的。从几何上讲,它倾向于恢复数据的初始形状,并且只需要旋转结果矩阵。
方法
为了找出哪些去混矩阵是独立的,我们必须依赖于非高斯性。让我们看看我们如何做到这一点。
在这里,我们需要最大化峰度,这将使分布变成非高斯分布。这将导致独立成分。以下图显示了快速 ICA 的图像:

对于这一点,Python 中有 FastICA 库。
让我们看看如何在 Python 中执行这个操作。我们将使用相同的数据集 Iris。这可能不是一个执行 ICA 的理想数据集,但这是为了方向性的目的。要在 Python 中执行代码,我们需要执行以下步骤:
- 首先,我们需要加载库:
import numpy as np # linear algebra
import pandas as pd # data processing
import matplotlib.pyplot as plt
from sklearn import datasets
- 现在,我们需要加载数据:
iris = datasets.load_iris()
X = iris.data
y = iris.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.25, random_state = 0)
- 让我们把数据转换成标准标量:
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
X_train = sc.fit_transform(X_train)
X_test = sc.transform(X_test)
- 现在,我们需要加载 ICA 库:
from sklearn.decomposition import FastICA
- 我们将按照以下方式进行 ICA。在这里我们将坚持三个成分:
ICA = FastICA(n_components=3, random_state=10,whiten= True)
X=ICA.fit_transform(X_train)
- 然后,我们将绘制结果,如下所示:
plt.figure(figsize=(8,10))
plt.title('ICA Components')
plt.scatter(X[:,0], X[:,1])
plt.scatter(X[:,1], X[:,2])
plt.scatter(X[:,2], X[:,0])
这个输出如下:

我们可以看到这里有三个不同的成分(通过颜色)。
压缩感知
压缩感知是信息理论和信号处理领域中容易解决的问题之一。它是一种信号采集和重建技术,其中信号是可压缩的。信号必须是稀疏的。压缩感知试图将信号的样本拟合到函数中,并且它倾向于使用尽可能少的基本函数来匹配样本。这将在以下图中描述:

这是我们在线性代数中看到的主要方程之一,其中 y 是一个 M x 1 矩阵,phi 是一个 M x N 矩阵,其列数多于行数,而 x 是一个由 k 个非零项组成的 N x 1 矩阵。未知数非常多,这可以用一个 N 长度的向量表示,以及 M 个测量值,其中 M << N。在这种类型的方程中,我们知道由于这个矩阵的零空间非平凡,所以可能存在许多解。因此,这个方程可以容纳许多解。
我们的目标
我们的目标是找到所有解中非零项最少的解。也就是说,解应该给我们尽可能少的非零项。你在想这能应用在哪里吗?有很多应用场景。它可以应用在以下领域:
-
信号表示
-
医学成像
-
稀疏信道估计
假设我们有一个时间信号。这个信号非常稀疏,但我们对其有一些了解,因为它有几个频率。你能从之前的方程中感受到它是什么吗?是的,它可以被认为是 X。
让我们称这个未知信号为 X。现在,尽管我们不知道整个信号,我们仍然可以对其做出观察,或者说是样本,如下面的代码所示:
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import scipy.optimize as spopt
import scipy.fftpack as spfft
import scipy.ndimage as spimg
import cvxpy as cvx
这将形成一个随机方程:
x = np.sort(np.random.uniform(0, 15, 30))
y = 5 + 0.5 * x + 0.1 * np.random.randn(len(x))
现在,我们需要拟合 l1 范数。我们得到以下输出:
l1 = lambda x0, x, y: np.sum(np.abs(x0[0] * x + x0[1] - y))
opt1 = spopt.fmin(func=l1, x0=[1, 1], args=(x, y))
然后,我们需要拟合 l2 范数。我们得到以下输出:
l2 = lambda x0, x, y: np.sum(np.power(x0[0] * x + x0[1] - y, 2))
opt2 = spopt.fmin(func=l2, x0=[1, 1], args=(x, y))
y2 = y.copy()
y2[3] += 5
y2[13] -= 10
xopt12 = spopt.fmin(func=l1, x0=[1, 1], args=(x, y2))
xopt22 = spopt.fmin(func=l2, x0=[1, 1], args=(x, y2))
通过将两个正弦波相加,我们得到以下输出:
n = 10000
t = np.linspace(0, 1/5, n)
y = np.sin(1250 * np.pi * t) + np.sin(3000 * np.pi * t)
yt = spfft.dct(y, norm='ortho')
plt.figure(figsize=[10,5])
plt.plot(t,y)
plt.title('Original signal')
plt.xlabel('Time (s)')
plt.ylabel('y')
现在,让我们从n中取出一个样本:
m = 1000 # 10% sample
ran = np.random.choice(n, m, replace=False) # random sample of indices
t2 = t[ran]
y2 = y[ran]
让我们创建idct矩阵运算符:
# create idct matrix operator
A = spfft.idct(np.identity(n), norm='ortho', axis=0)
A = A[ran]
# do L1 optimization
vx = cvx.Variable(n)
objective = cvx.Minimize(cvx.norm(vx, 1))
constraints = [A*vx == y2]
prob = cvx.Problem(objective, constraints)
result = prob.solve(verbose=True)
此输出的结果如下:

为了重构信号,我们必须执行以下操作:
x = np.array(vx.value)
x = np.squeeze(x)
signal = spfft.idct(x, norm='ortho', axis=0)
这就是我们的信号重构方式。
自组织映射
自组织映射(SOM)是由 Teuvo Kohonen 在 20 世纪 80 年代发明的。有时,它们也被称为Kohonen 映射。那么,它们为什么存在呢?这类映射的主要动机是通过神经网络来降低维度。以下图显示了输入层的不同 2D 模式:

它以列数作为输入。正如我们从 2D 输出中可以看到的,它将数据集中的列数转换并减少到 2D。
以下链接指向 2D 输出:www.cs.hmc.edu/~kpang/nn/som.html
前面图的 2D 表示是根据各种因素来描述一个国家的健康状况。也就是说,它显示了它们是富裕还是贫穷。考虑的其他因素包括教育、生活质量、卫生、通货膨胀和健康。因此,它形成了一个巨大的列集或维度集。比利时和瑞典等国家似乎显示出相似的特性,表明它们在健康指标上得分很高。
由于这是一种无监督学习技术,数据没有被标记。仅基于模式,神经网络能够理解哪个国家应该放在哪里。
与我们刚才讨论的情况类似,自组织映射有很多可以利用的机会。它可以被认为是与 K-means 聚类在本质上相似。
SOM
让我们来看看 SOM(自组织映射)是如何学习的:
-
每个节点的权重都通过小的标准化随机值初始化。这些值类似于不同输出节点的坐标。
-
第一行的输入(从所有变量中取第一行)被输入到第一个节点。
-
现在,我们有两个向量。如果V是当前输入向量,而W是节点的权重向量,那么我们计算欧几里得距离,如下所示:

-
权重向量与输入向量最接近的节点被标记为最佳匹配单元(BMU)。
-
对输入和权重向量的所有行都执行类似的操作。为所有找到 BMU。
-
一旦确定了每个迭代的 BMU(最佳匹配单元),BMU 邻域内的其他节点将被计算。同一半径内的节点将更新其权重。绿色箭头表示半径。随着时间的推移,邻域将逐渐缩小到仅剩一个节点的大小,如下面的图所示:

- Kohonen 算法最有趣的部分是邻域的半径不断缩小。这是通过指数衰减函数实现的。lambda 的值依赖于 sigma。算法运行所选择的迭代次数由以下方程给出:


- 权重通过以下方程更新:

这里,这是如下:

t= 1, 2... 可以解释如下:
-
-
L(t):学习率
-
D:节点到 BMU 的距离
-
σ:函数的宽度
-
现在,让我们在 Python 中演示这个用例。我们将尝试检测信用卡数据集中的欺诈行为:
- 让我们加载库:
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
- 现在,是时候加载数据了:
data = pd.read_csv('Credit_Card_Applications.csv')
X = data.iloc[:, :-1].values
y = data.iloc[:, -1].values
- 接下来,我们将标准化数据:
from sklearn.preprocessing import MinMaxScaler
sc = MinMaxScaler(feature_range = (0, 1))
X = sc.fit_transform(X)
- 让我们导入
minisom库并输入超参数,即学习率、sigma、长度和迭代次数:
from minisom import MiniSom
som = MiniSom(x = 10, y = 10, input_len = 15, sigma = 1.0, learning_rate = 0.5)
som.random_weights_init(X)
som.train_random(data = X, num_iteration = 100)
- 让我们可视化结果:
from pylab import bone, pcolor, colorbar, plot, show
bone()
pcolor(som.distance_map().T)
colorbar()
markers = ['o', 's']
colors = ['r', 'g']
for i, x in enumerate(X):
w = som.winner(x)
plot(w[0] + 0.5,
w[1] + 0.5,
markers[y[i]],
markeredgecolor = colors[y[i]],
markerfacecolor = 'None',
markersize = 10,
markeredgewidth = 2)
show()
以下输出将由前面的代码生成:

我们可以看到,有欺诈倾向的节点具有白色背景。这意味着我们可以通过这些节点追踪到那些客户:
mappings = som.win_map(X)
frauds = np.concatenate((mappings[(8,1)], mappings[(6,8)]), axis = 0)
frauds = sc.inverse_transform(frauds)
这将给出欺诈的模式。
贝叶斯多重插补
贝叶斯多重插补具有贝叶斯框架的精神。需要指定完整数据的参数模型以及未知模型参数θ的先验分布。随后,从缺失数据中抽取m个独立的试验,如使用贝叶斯定理根据观察数据给出。可以使用马尔可夫链蒙特卡洛模拟缺失数据的整个联合后验分布。BMI 在生成缺失值的插补时遵循正态分布。
假设数据如下:
Y = (Yobs, Ymiss),
这里,Yobs是观察到的Y,而Ymiss是缺失的Y。
如果P(Y|θ)是参数模型,参数θ是正态分布的均值和协方差矩阵。如果是这种情况,让P(θ)为先验:

让我们利用 R 中的Amelia包并执行以下操作:
library(foreign)
dataset = read.spss("World95.sav", to.data.frame=TRUE)
library(Amelia)
myvars <- names(dataset) %in% c("COUNTRY", "RELIGION", "REGION","CLIMATE")
newdata <- dataset[!myvars]
现在,让我们进行插补:
impute.out <- amelia(newdata, m=4)
摘要
在本章中,我们研究了核主成分分析(Kernel PCA),以及独立成分分析(ICA)。我们还研究了压缩感知、压缩感知的目标以及自组织映射及其工作原理。最后,我们以贝叶斯多重插补作为结论。


组成的数据集,其中
是输入,
是我们期望的输出。因此,取一个大小为 N 的此类输入-输出集,表示为
。
,这是层 *l[k] 中节点 j 和层 *l[k-1] 中节点 i 之间的权重,以及层 *l[k-1] 中节点 i 的偏置
。同一层中的节点之间没有连接,层之间是完全连接的。
。

:
。通过输入K的值简化步骤 5的方程,我们得到以下结果:
浙公网安备 33010602011771号