原文:
annas-archive.org/md5/b0a4e1a7c9576619c74e69137644debd
译者:飞龙
序言
你是否曾想过为什么如此多的机器学习项目在生产环境中失败?
在许多情况下,这是由于模型缺乏泛化能力,导致在面对新的、未见过的数据时做出意外的预测。这就是正则化的核心:确保即使面对新数据,模型也能提供预期的预测。
在本书中,我们将探讨多种形式的正则化。为了实现这一点,我们将根据本章中的方案,探索两条主要的正则化解决方案路径:
-
当给定一个机器学习模型时,我们如何进行正则化?正则化最适合用于模型已经被设定(无论是更新已有的遗留解决方案,还是有强需求)的应用场景,且训练数据是固定的,因此唯一的解决方案是对模型进行正则化。
-
给定一个机器学习任务,我们如何获得一个稳健、良好泛化的解决方案?这种方法最适合用于那些仅定义了问题但尚未提供强约束条件的应用场景,从而可以探索更多的解决方案。
希望这些方法能够为你提供必要的工具和技巧,以解决你可能面临的需要正则化的绝大多数机器学习问题,并且深入理解其中的基本概念。
本书适合谁阅读
本书适合任何有一定 Python 知识的人:充分理解本书所提出的解决方案的唯一要求是你能够阅读和运行简单的 Python 代码。对于每一种新方法或模型,都会提供一些背景和实用的解释,以便任何具有计算机科学背景的人都能完全理解自己在做什么。
虽然任何 Python 从业者都可以跟随本书学习,但主要目标读者是以下人群:
-
机器学习从业者,如机器学习工程师、应用科学家和数据科学家,他们希望在面对新问题或任务时,可以从本书中挑选现成的解决方法和代码。通过本书,希望他们能够通过稍微调整代码,处理许多不同的情况。
-
想要深入了解机器学习的爱好者,通过具体的实例和可运行的代码获得更深的理解。通过本书,他们可以通过动手实践,深入掌握知识,并构建一个扎实的项目组合。
本书的内容
第一章,正则化概述,为正则化提供了一个高层次的介绍,并提供了所有基本知识和词汇,帮助读者完全理解本书后续章节的内容。
第二章,机器学习复习,引导你通过一个典型的机器学习工作流程和最佳实践,从数据加载和划分到模型训练和评估。
第三章,线性模型中的正则化,介绍了常见线性模型的正则化:线性回归和逻辑回归。包括 L1 和 L2 惩罚的正则化,以及如何选择合适正则化方法的一些实用技巧。
第四章,基于树的模型中的正则化,提供了关于决策树(用于分类和回归)的提醒,以及如何对其进行正则化。接着介绍了集成方法,如随机森林和梯度提升,以及它们的正则化方法。
第五章,数据正则化,介绍了通过数据进行正则化,利用哈希及其特性和特征聚合。接着介绍了处理不平衡数据集的重采样方法。
第六章,深度学习提醒,提供了有关深度学习的概念性和实践性提醒。从感知机开始,然后我们训练回归和分类模型。
第七章,深度学习正则化,介绍了深度学习模型的正则化。探讨并解释了几种技术:L2 惩罚、提前停止、网络架构和 dropout。
第八章,使用循环神经网络的正则化,深入探讨了循环神经网络(RNNs)和门控循环单元(GRUs)。首先解释了它们是什么以及如何训练这些模型。接着介绍了正则化技术,例如 dropout 和最大序列长度。
第九章,自然语言处理中的高级正则化,探讨了专门针对自然语言处理(NLP)的正则化方法。介绍了使用 word2vec 嵌入和 BERT 嵌入的正则化技术。还探讨了使用 word2vec 和 GPT-3 的数据增强方法,并提出了零-shot 推理解决方案。
第十章,计算机视觉中的正则化,深入探讨了计算机视觉中的正则化和卷积神经网络(CNNs)。在概念和实践上解释了 CNNs 在分类中的应用,然后提供了适用于物体检测和语义分割的正则化方案。
第十一章,计算机视觉中的正则化 – 合成图像生成,深入探讨了用于正则化的合成图像生成。首先探讨了简单的数据增强方法。然后,使用仅有合成训练数据的 QR 码物体检测机制得到了构建。最后,我们探索了一种基于 Stable Diffusion 数据进行训练的实时风格迁移,并解释了如何独立使用该数据集进行工作。
为了从本书中获得最大的收获
您需要安装 Python 的某个版本。所有代码都已在 Ubuntu 22.04 上使用 Python 3.10 和 CUDA 12.1 版本进行测试。然而,代码应该能够在任何操作系统上使用 Python 3.9 及更高版本,并且支持 CUDA 11 及更高版本。
书中涵盖的软件/硬件 | 操作系统要求 |
---|---|
Python 3.9 | Windows, macOS 或 Linux(任意) |
对于深度学习章节,特别是从第八章开始,建议使用图形处理单元(GPU)。代码是在带有 24 GB 内存的 Nvidia GeForce RTX 3090 上测试的。根据您的硬件配置,代码可能需要相应调整。
食谱从第二章开始。 如果您使用的是本书的数字版,我们建议您自己输入代码或通过 GitHub 仓库访问代码(链接将在下一节提供)。这样做有助于避免因复制粘贴代码而导致的潜在错误。
下载示例代码文件
您可以从 GitHub 下载本书的示例代码文件,网址为github.com/PacktPublishing/The-Regularization-Cookbook
。如果代码有更新,它将会在现有的 GitHub 仓库中进行更新。
我们还提供了来自我们丰富图书和视频目录中的其他代码包,您可以在github.com/PacktPublishing/
查看!
使用的规范
本书中使用了多种文本规范。
文本中的代码
:表示文本中的代码词语、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 账号。示例:“这两行应该下载一个.zip
文件,然后解压其内容,以便获得名为Tweets.csv
的文件。”
代码块设置如下:
# Load data data = pd.read_csv('Tweets.csv')data[['airline_sentiment', 'text']].head()
任何命令行输入或输出如下所示:
ip install pandas numpy scikit-learn matplotlib torch transformers
提示或重要说明
以这种方式显示。
章节
在本书中,您会看到几个常见的标题(准备工作,如何操作…,它是如何工作的…,还有更多…,以及参见)。
为了清楚地说明如何完成食谱,使用以下各节:
准备工作
本节告诉您食谱中会涉及什么,并描述了为该食谱设置所需软件或任何预备设置的步骤。
如何操作…
本节包含执行该食谱所需的步骤。
它是如何工作的…
本节通常包含对上一节发生内容的详细解释。
还有更多…
本节包含有关食谱的额外信息,以帮助您更深入地了解该食谱。
参见
本节提供了其他有用信息的链接,供您参考。
联系我们
我们欢迎读者的反馈。
一般反馈:如果您对本书的任何方面有疑问,请在邮件主题中注明书名,并通过电子邮件联系 customercare@packtpub.com。
勘误:尽管我们已尽力确保内容的准确性,但错误仍然可能发生。如果您在本书中发现了错误,我们将不胜感激。如果您发现错误,请访问 www.packtpub.com/support/errata,选择您的书籍,点击“勘误提交表单”链接并填写详细信息。
盗版:如果您在互联网上发现我们作品的任何非法版本,请提供该地址或网站名称。请通过 copyright@packt.com 联系我们,并附上材料的链接。
如果您有兴趣成为作者:如果您在某个领域拥有专业知识,并且有兴趣撰写或参与编写一本书,请访问 authors.packtpub.com。
分享您的想法
一旦您阅读了*《正则化手册》*,我们很想听听您的想法!请 点击此处直接前往 Amazon 评价页面,分享您的反馈。
您的评价对我们和技术社区都很重要,它将帮助我们确保提供优质的内容。
.
下载本书的免费 PDF 副本
感谢您购买本书!
您喜欢随时随地阅读,但又无法携带印刷书籍吗?
您的电子书购买无法与您选择的设备兼容吗?
不用担心,现在购买每一本 Packt 书籍,您都能免费获得该书的无 DRM 版本 PDF。
在任何地方、任何设备上阅读。直接将您最喜欢的技术书籍中的代码搜索、复制并粘贴到您的应用程序中。
福利不止这些,您还可以每天在邮箱中获得独家折扣、时事通讯和精彩的免费内容。
按照以下简单步骤即可获得福利:
- 扫描二维码或访问以下链接
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_QR_Free_PDF.jpg
https://packt.link/free-ebook/9781837634088
-
提交您的购买证明
-
就是这样!我们将直接将免费的 PDF 和其他福利发送到您的电子邮件中
第一章:正则化概述
让我们开始进入机器学习正则化的世界。我希望你能学到很多,并像我在写这本书时那样享受阅读的乐趣。
正则化对于任何希望部署强健机器学习(ML)模型的人来说都至关重要。
本章将在深入探讨正则化之前,介绍一些背景知识和关键概念。此时,你可能对本书和正则化本身有许多疑问。什么是正则化?为什么我们需要为生产级的机器学习模型进行正则化?如何诊断是否需要正则化?正则化的局限性是什么?正则化的方法有哪些?
本章将提供有关正则化的所有基础知识,旨在回答所有这些问题。这不仅会让你对正则化有一个高层次的理解,而且还会让你充分理解本书接下来几章提出的方法和技巧。
在本章中,我们将涵盖以下主要主题:
-
介绍正则化
-
在玩具数据集上发展正则化的直觉
-
介绍欠拟合、过拟合、偏差和方差的关键概念
技术要求
在本章中,你将有机会生成一个玩具数据集,展示它,并在该数据上训练基本的线性回归。因此,以下 Python 库是必需的:
-
NumPy
-
Matplotlib
-
scikit-learn
介绍正则化
“机器学习中的正则化是一种通过为模型的参数添加额外约束来提高模型泛化能力的技术。这样可以迫使模型使用更简单的表示方式,并帮助减少过拟合的风险。
正则化还可以通过鼓励模型学习更相关、具有更强泛化能力的特征,帮助提高模型在未见数据上的表现。
这个关于正则化的定义,虽然可以说已经足够好,但实际上是由著名的 GPT-3 模型在给定以下提示时生成的:“机器学习中正则化的详细定义”。更令人惊讶的是,这个定义通过了几次抄袭测试,这意味着它实际上是完全原创的。如果你现在还不理解 GPT-3 给出的这个定义中的所有词汇,不用担心;它并非面向初学者的定义。但到本章结束时,你将完全理解它。
注意
GPT-3,全称生成式预训练变换器 3,是由 OpenAI 提出的一个 1750 亿参数的模型,并且可以通过platform.openai.com/playground使用。
你可以很容易地想象,为了得到这样的结果,GPT-3 不仅在大量数据上进行了训练,而且经过了精心的正则化处理,这样它就不会只是简单地复述已学的文本,而是会生成新的内容。
这正是正则化的核心:能够在面对未知情况时进行泛化,并产生可接受的结果。
为什么正则化对机器学习如此重要?成功将机器学习部署到生产环境的关键在于模型能否有效适应并处理新数据。一旦模型投入生产,它将不会接收到已知的、标准化的输入数据。生产中的模型很可能会面对未见过的数据、异常情景、特征分布的变化或不断变化的客户行为。虽然一个正则化良好的机器学习模型可能无法保证其在处理各种场景时的鲁棒性,但一个正则化不良的模型几乎可以确定会在初次部署时遇到失败。
现在让我们来看几个近年在部署过程中失败的模型示例,以便我们能充分理解为什么正则化如此重要。
没有通过部署测试的模型示例
过去几年充满了在部署初期就失败的模型示例。根据 2020 年 Gartner 报告(www.gartner.com/en/newsroom/press-releases/2020-10-19-gartner-identifies-the-top-strategic-technology-trends-for-2021#:~:text=Gartner%20research%20shows%20only%2053,a%20production%2Dgrade%20AI%20pipeline
),超过 50%的人工智能原型将无法进入生产部署。并非所有的失败都仅仅是因为正则化问题,但有些确实是。
让我们快速回顾一下过去几年中一些在生产中部署失败的尝试:
-
微软的聊天机器人 Tay 在投入生产仅 16 小时后因发布冒犯性推文而被关闭(
en.wikipedia.org/wiki/Tay_(chatbot)
) -
IBM 的 Watson 给患者提供了不安全的癌症治疗建议(
www.theverge.com/2018/7/26/17619382/ibms-watson-cancer-ai-healthcare-science
)
这些只是一些来自科技巨头头条新闻的例子。经历失败但仍未对公众披露的项目数量令人震惊。这些失败通常涉及较小的公司和保密的计划。但仍然有几个例子可以从中学到教训:
-
亚马逊的案例:输入数据对女性存在偏见,而模型也存在这种偏见
-
微软的案例:由于它在新推文上的反馈过于敏感
-
IBM 的案例:该模型可能过度训练于合成或不现实的数据,而不能适应边缘案例和未见过的数据
正则化作为增强 ML 模型在生产中成功率的宝贵方法。有效的正则化技术可以通过消除某些特征或整合合成数据来防止 AI 招聘模型展现性别偏见。此外,适当的正则化使得聊天机器人能够保持对新推文的适当敏感性。它还使模型能够在训练于合成数据的情况下,熟练处理边缘案例和以前未见过的数据。
免责声明,可能还有许多其他克服或预防这些失败的方法,并且这些方法与正则化并不是互斥的。例如,拥有高质量的数据至关重要。AI 领域的每个人都知道这句格言 garbage in, garbage out。
MLOps(一个日益成熟的领域)和 ML 工程的最佳实践也是许多项目成功的关键。主题知识有时也可能产生影响。
根据项目的上下文,许多其他参数可能会影响 ML 项目的成功,但是除了正则化之外的任何内容都超出了本书的范围。
现在我们理解了在生产级 ML 模型中为什么需要正则化,让我们退一步,通过一个简单的例子对正则化有些直觉。
对正则化的直觉
正则化已经在本书中定义和提到过,但现在让我们尝试发展一些关于它究竟是什么的直觉。
让我们考虑一个典型的现实世界用例:巴黎市一个公寓(或房屋)的平方米房价作为其表面积的函数。从业务角度来看,目标是能够预测每平方米的价格,给定公寓的表面积。
首先,我们需要一些导入,以及一个更方便地绘制数据的辅助函数。
plot_data()
函数简单地绘制提供的数据,并在需要时添加轴标签和图例:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
np.random.seed(42)
def plot_data(surface: np.array, price: np.array,
fit: np.array = None, legend: bool = False):
plt.scatter(surface, price, label='data')
if fit is not None:
plt.plot(surface, fit, label='fit')
if legend:
plt.legend()
plt.ylim(11300, 11550)
plt.xlabel('Surface (m$^{2}$)')
plt.ylabel('Price (€/m$^{2}$)')
plt.grid(True)
plt.show()
下面的代码现在将允许我们生成和显示我们的第一个玩具数据集:
# Define the surfaces and prices
surface = np.array([15, 17, 20, 22, 25, 28]).reshape(-1, 1)
price = 12000 - surface*50 + np.square(
surface) + np.random.normal(0, 30, surface.shape)
# Plot the data
plot_data(surface, price)
这里是它的图表:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_01.jpg
图 1.1 – 每平方米价格随公寓面积变化的图
即便这只是一个示例数据集,为了教学目的,我们可以假设这些数据是从房地产市场中收集的。
我们可以看到,随着公寓面积的增加,每平方米价格呈下降趋势。事实上,在巴黎,小面积的公寓需求更大(可能是因为有很多学生,或者因为价格更实惠)。这或许能解释为什么小面积公寓的每平方米价格实际上更高。
为了简便起见,我们将省略所有典型的机器学习工作流程。在这里,我们仅对这些数据执行线性回归,并使用以下代码显示结果:
# Perform a linear regression on the data
lr = LinearRegression()
lr.fit(surface, price)
# Compute prediction
y_pred = lr.predict(surface)
# Plot data
plot_data(surface, price, y_pred, True)
这是输出:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_02.jpg
图 1.2 – 每平方米价格随公寓面积变化及拟合曲线
足够好了!拟合似乎捕捉到了下降趋势。虽然与所有给定的数据样本不完全吻合,但目前商业方面对其表示满意,因为模型的表现符合预期。
借助这个新模型,公司现在获得了更多客户。从这些客户那里,公司收集了一些新的数据,涉及较大公寓的销售,因此我们的数据集现在看起来如下:
# Generate data
updated_surface = np.array([15, 17, 20, 22, 25, 28, 30, 33,
35, 37]).reshape(-1, 1)
updated_price = 12000 - updated_surface*50 + np.square(
updated_surface) + np.random.normal(0, 30, updated_surface.shape)
# Plot data
plot_data(updated_surface, updated_price)
这是它的图表:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_03.jpg
图 1.3 – 每平方米价格随公寓面积变化的更新图
这实际上改变了所有情况;这是典型的失败部署测试。现在已经没有全局的下降趋势了:随着公寓面积的增大,每平方米价格似乎跟随上升趋势。一个简单的商业解释可能是:大面积的公寓可能较为稀缺,因此价格更高。
没有信心的情况下,为了尝试,我们重新使用之前相同的方法:线性回归。结果将如下:
# Perform linear regression and plot result
lr = LinearRegression()
lr.fit(updated_surface, updated_price)
y_pred_updated = lr.predict(updated_surface)
plot_data(updated_surface, updated_price, y_pred_updated, True)
这是图表:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_04.jpg
图 1.4 – 欠拟合示例:模型未能完全捕捉数据的复杂性
正如预期的那样,线性回归已无法捕捉数据的复杂性,导致了这种情况。这被称为欠拟合;模型未能完全捕捉数据的复杂性。事实上,仅以面积作为参数,模型能做到的最好就是一条直线,这对于这些数据来说是不够的。
线性回归捕捉更多复杂性的一个方法是提供更多特征。鉴于我们当前的输入数据仅限于表面,可能的直接方法是利用指数表面。为了这个示例,我们采取了一个相对极端的方法,添加了从power1
到power15
的所有特征,并使它们拟合该数据集。这可以通过以下代码轻松实现:
# Compute power up to 15
x_power15 = np.concatenate([np.power(
updated_surface, i+1) for i in range(15)], 1)
# Perform linear regression and plot result
lr = LinearRegression()
lr.fit(x_power15, updated_price)
y_pred_power15 = lr.predict(x_power15)
plot_data(updated_surface, updated_price, y_pred_power15, True)
这是它的输出:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_05.jpg
图 1.5 – 过拟合示例:模型捕捉到数据中的噪声
正如我们所预期的那样,随着在模型中添加了如此多的自由度,拟合现在正好通过所有数据点。事实上,不深入探讨数学细节,模型的参数比它训练的数据点还多,因此能够通过所有这些点。但是,这真的是一个好的拟合吗?我们可以想象,它不仅捕捉到了数据的全局趋势,还捕捉到了噪声。这被称为过拟合:模型过于贴近数据,可能无法对新的、未见过的数据做出正确的预测。任何新的数据点都不会在曲线上,且训练范围之外的行为完全不可预测。
最终,对于这种情况,一个更合理的方法是只取表面到 2 次方,例如:
# Compute power up to 2
x_power2 = np.concatenate([np.power(
updated_surface, i+1) for i in range(2)], 1)
# Perform linear regression and plot result
lr = LinearRegression()
lr.fit(x_power2, updated_price)
y_pred_power2 = lr.predict(x_power2)
plot_data(updated_surface, updated_price, y_pred_power2, True)
这是它的输出:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_06.jpg
图 1.6 – 正确拟合示例:模型捕捉到整体趋势,但没有捕捉到噪声
现在,拟合看起来要更可接受了。它确实捕捉到了数据的全局趋势:最初对小表面呈下降趋势,然后对较大表面呈上升趋势。此外,它不再试图捕捉所有数据点中的噪声,使得它在预测新的、未见过的数据时更具鲁棒性。最终,超出训练范围的行为(例如,表面 < 15https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_01_001.png 或表面 > 40https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_01_002.png)相当可预测。
这正是一个好的模型所期望的行为:既不欠拟合也不过拟合。通过移除一些指数特征,我们的模型能够更好地泛化;我们实际上对模型进行了正则化。
总结这个示例,我们在这里探讨了几个概念:
-
我们展示了欠拟合、过拟合和良好正则化模型的示例
-
通过向特征中添加指数表面,我们成功地将模型从欠拟合过渡到过拟合
-
最终,通过移除大部分指数特征(仅保留平方特征),我们成功将模型从过拟合调整为良好正则化的模型,有效地加入了正则化。
希望你现在对欠拟合、过拟合和正则化有了很好的理解,也理解了它们在机器学习中的重要性。现在我们可以基于此进一步构建,给出正则化关键概念的更正式定义。
正则化的关键概念
在获得了一些关于何为合适拟合的直觉,并理解了欠拟合和过拟合的例子后,让我们更精确地定义这些概念,并探讨正则化的关键概念,帮助我们更好地理解正则化。
偏差和方差
偏差和方差是讨论正则化时的两个关键概念。我们可以定义模型可能产生的两种主要错误:
-
偏差是模型在捕捉数据的整体行为方面的不足
-
方差是模型对小的输入数据波动的鲁棒性差的表现
这两个概念通常不是互相排斥的。如果我们从机器学习退后一步,存在一个非常常见的图示来可视化偏差和方差,假设模型的目标是击中目标的中心:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_07.jpg
图 1.7 – 偏差和方差的可视化
让我们描述这四种情况:
-
高偏差和低方差:模型偏离目标中心,但方式非常一致
-
低偏差和高方差:模型平均上击中目标的中心,但在此过程中非常嘈杂且不一致
-
高偏差和高方差:模型以一种嘈杂的方式偏离中心
-
低偏差和低方差:两者兼得——模型稳定地击中目标中心
欠拟合与过拟合
我们看到了一个非常经典的偏差和方差定义方法。
但是现在,这在机器学习中意味着什么呢?它与正则化有什么关系?在我们深入探讨之前,我们首先将回顾偏差和方差在一个更典型的机器学习案例中的表现:房地产价格的线性回归。
让我们看看模型在这些情况下如何在我们的数据上表现。
高偏差和低方差
模型对数据波动具有鲁棒性,但无法捕捉到数据的高层次行为。请参考下图:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_08.jpg
图 1.8 – 线性回归中的高偏差和低方差实践
这是欠拟合,正如我们之前在图 1.4中所遇到的那样。
低偏差和高方差
模型确实捕捉到了数据的全局行为,但无法保持对输入数据波动的鲁棒性。请参考下图:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_09.jpg
图 1.9 – 线性回归中的低偏差和高方差实践
这是过拟合,正如我们之前在图 1.5中遇到的情况。
高偏差和高方差
模型既无法捕捉全局行为,也不足够稳健,无法应对数据波动。这种情况通常不会发生,或者说高方差被隐藏在高偏差背后,但可能看起来像以下这样:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_10.jpg
图 1.10 – 线性回归中的高偏差和高方差:这种情况在这样的数据中几乎不会发生
低偏差和低方差
模型能够既捕捉全局数据行为,又足够稳健以应对数据波动。这是训练模型时的最终目标。这正是我们在图 1.6中遇到的情况。
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_11.jpg
图 1.11 – 线性回归中的低偏差和低方差:最终目标
当然,目标几乎总是希望得到低偏差和低方差,即便这并不总是可能的。让我们看看正则化如何作为实现这个目标的手段。
正则化——从过拟合到欠拟合
通过以上这些例子,我们现在可以清楚地理解正则化的含义。
如果我们再次回顾 GPT-3 给出的定义,正则化通过向模型添加约束来防止模型过拟合。实际上,加入正则化使我们能够降低模型的方差,从而使模型的过拟合程度减轻。
我们可以进一步推测:如果正则化被加入到一个已经训练良好的模型中(即低偏差和低方差的模型),会怎样呢?换句话说,如果在一个表现良好的模型中添加约束,会发生什么呢?直觉上,这会降低模型的整体表现。它不会让模型完全理解数据的行为,因此会增加模型的偏差。
事实上,这里正是正则化的一个根本缺点:加入正则化会增加 模型偏差。
这可以用一张图来总结:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_12.jpg
图 1.12 – 一个高方差模型(底部);相同的模型加入更多正则化并达到适当拟合水平(中间);同样的模型加入更多正则化,现在发生了欠拟合(顶部)
这就是所谓的偏差-方差权衡,一个非常重要的概念。事实上,加入正则化总是一个平衡:
-
我们需要有足够的正则化,使模型具有良好的泛化能力,并且不容易受小幅度数据波动和噪声的影响
-
我们需要避免过多的正则化,以确保模型在任何情况下都能足够自由地完全捕捉数据的复杂性
随着我们在书中的深入,更多的工具和技术将被提供来诊断我们的模型并找到合适的偏差-方差平衡。
在接下来的章节中,我们将看到许多正则化模型的方法。我们通常认为正则化就是直接向模型添加约束,但实际上有许多间接的正则化方法可以帮助模型更好地泛化。现有正则化方法的非详尽清单可能包括以下内容:
-
对模型架构添加约束
-
向模型训练中添加约束,如损失函数
-
通过不同的工程方法对输入数据添加约束
-
通过生成更多样本来增加输入的约束
可能会提出其他正则化方法,但本书将主要关注适用于各种情况的方法,比如结构化和非结构化数据、线性模型、基于树的模型、深度学习模型、自然语言处理(NLP)问题和计算机视觉问题。
即使模型采用了正确的正则化方法,许多任务对模型可以实现的表现仍有一个硬性上限:这就是我们所说的不可避免的偏差。让我们来看看它是什么。
不可避免的偏差
在几乎所有任务中,都有不可避免的偏差。例如,在图 1.13中,既有设得兰牧羊犬,也有粗毛牧羊犬。你能以 100%的准确度说出哪一只是哪一只吗?
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_13.jpg
图 1.13 – 设得兰牧羊犬和粗毛牧羊犬的随机图片
从前面的图中,你能分辨出哪一只是哪一只吗?大概率是不能的。如果你是一个经过训练的犬类专家,你可能会有更低的错误率。但即便如此,给定足够数量的图片,你也可能会在某些图片上出错。专家能够达到的最低错误率被称为人类水平错误。在大多数情况下,人类水平错误能很好地反映出模型可能达到的最低错误。每当你在评估一个模型时,了解(或思考)人类在这种任务中的表现是一个很好的思路。
事实上,有些任务比其他任务容易得多:
-
人类在分类狗和猫方面表现得很好,AI 也是如此
-
人类在分类歌曲方面表现得很好,AI 也是如此
-
人类在招聘人员方面表现得相当差(至少,并不是所有招聘人员都会就候选人达成一致),AI 也是如此
计算不可避免的偏差的另一个可能来源是贝叶斯误差。贝叶斯误差通常在复杂的 AI 任务中无法计算,它是分类器可以达到的最低错误率。大多数时候,贝叶斯误差低于人类水平误差,但估算起来要困难得多。这将是任何模型性能的实际理论限制。
贝叶斯误差和人类级别的误差是不可避免的偏差。它们表示任何模型的不可减少误差,是评估模型是否需要更多或更少正则化的关键概念。
偏差与方差的诊断
我们通常使用一些数据上的拟合图来定义偏差和方差,就像我们之前在公寓价格数据中做的那样。虽然这些图有助于解释概念,但在现实生活中,我们通常处理的是高维数据集。通过使用简单的 Titanic 数据集,我们提供了十几个特征,因此进行这种视觉检查几乎是不可能的。
假设我们正在训练一个猫狗分类模型,并且数据是平衡的。一种好的方法是比较训练集和验证集上的评估指标(无论是准确率、F 分数,或是任何你认为相关的指标)。
注意
如果评估指标或训练集与验证集的概念不清楚,它们将在第二章中有更详细的解释。简而言之,模型是在训练集上进行训练的。评估指标是用来评估训练好的模型的值,并且是在训练集和验证集上计算的。
例如,假设我们得到以下结果:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_14.jpg
图 1.14 – 假设在训练集和验证集上的准确性
如果我们考虑到这类任务的预期人类级别错误,我们会预期更高的准确性。事实上,大多数人能够非常自信地从猫和狗中辨认出狗。
在这种情况下,训练集和验证集的表现远低于人类级别的错误率。这是典型的高偏差情形:评估指标在训练集和验证集上都很差。在这种情况下,模型需要更少的正则化。
现在假设在添加了较低的正则化(可能像我们在正则化直觉一节中所做的那样,添加了指数特征)之后,我们得到了以下结果:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_15.jpg
图 1.15 – 添加更多特征后的假设准确性
这些结果更好;验证集现在的准确率为 89%。然而,这里有两个问题:
-
训练集上的得分好得过头了:它简直是完美的
-
验证集上的得分仍然远低于我们预期的至少 95% 的人类错误率
这是典型的高方差情形:在训练集上的结果非常好(通常过好),而在验证集上则远低于预期。在这种情况下,模型需要更多的正则化。
添加正则化后,假设我们现在得到了以下结果:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_16.jpg
图 1.16 – 添加正则化后的假设准确性
这看起来好多了:训练集和验证集的准确度似乎接近人类水平的表现。也许通过更多的数据、一个更好的模型或其他改进,结果可以稍微提升一些,但总体来看,这似乎是一个稳固的结果。
在大多数情况下,诊断高偏差和高方差是很简单的,方法始终是相同的:
-
在训练集和验证集上评估你的模型。
-
将结果相互比较,并与人类水平的误差率进行对比。
从这一点来看,通常有三种情况:
-
高偏差/欠拟合:训练集和验证集的表现都很差
-
高方差/过拟合:训练集表现远好于验证集表现;验证集表现远低于人类水平的误差率
-
良好的拟合:训练集和验证集的表现接近人类水平的误差率
注意
大多数时候,建议在训练集和测试集上使用该技术,而不是训练集和验证集。尽管这一推理是成立的,但直接在测试集上进行优化可能会导致过拟合,从而高估模型的实际表现。然而,在本书中,我们将简化处理,接下来的章节将使用测试集。
记住所有正则化的关键概念后,你现在可能开始理解为什么正则化确实可能需要一本书来讲解。虽然诊断出正则化的需求通常相对简单,但选择合适的正则化方法却可能非常具有挑战性。接下来,让我们对本书中将要讨论的正则化方法进行分类。
正则化——一个多维度的问题
对模型进行正确的诊断至关重要,因为它使我们能够更仔细地选择策略来改进模型。但是,从任何诊断出发,都有许多途径可以改进模型。这些途径可以分为三大类,正如下图所示:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_17.jpg
图 1.17 – 提出的正则化类型分类:数据、模型架构和模型训练
在数据层面,我们可能会使用以下工具进行正则化:
-
添加更多数据,无论是合成数据还是真实数据
-
添加更多特征
-
特征工程
-
数据预处理
实际上,数据在机器学习中极为重要,正则化也不例外。在本书中,我们将看到许多正则化数据的例子。
在模型层面,可以使用以下方法进行正则化:
-
选择更简单或更复杂的架构
-
在深度学习中,许多架构设计允许正则化(例如,Dropout)
模型复杂度可能对正则化产生很大影响。过于复杂的架构很容易导致过拟合,而过于简单的架构则可能导致欠拟合,正如下图所示:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_01_18.jpg
图 1.18 – 模型复杂度与训练集和验证集误差之间关系的可视化示意图
最后,在训练层面,一些正则化的方法如下:
-
添加惩罚项
-
权重初始化
-
转移学习
-
提前停止
提前停止是一种非常常见的防止过拟合的方法,通过防止模型过度贴合训练集来避免过拟合。
正则化的方法可能有很多,因为这是一个多维度的问题:数据、模型架构和模型训练仅仅是高层次的分类。即使这些分类只是一些例子,且可能还存在或定义更多的分类,但本书将要涵盖的大多数——如果不是所有——技术都会属于这些分类中的某一类。
总结
我们通过几个实际案例开始本章,展示了在生产环境中,正则化是机器学习成功的关键。结合其他一些方法和最佳实践,稳健的正则化模型是生产中不可或缺的。在生产环境中,未见过的数据和极端情况将定期出现,因此,任何部署的模型都必须能接受这些情况的响应。
接着,我们讲解了一些正则化的关键概念。过拟合和欠拟合是机器学习中的两个常见问题,且与偏差和方差有一定关系。实际上,过拟合的模型具有高方差,而欠拟合的模型具有高偏差。因此,为了表现良好,模型需要具有低偏差和低方差。我们解释了无论一个模型多么优秀,不可避免的偏差都限制了它的性能。这些关键概念使我们提出了一种方法,通过训练集和验证集的表现,以及人类水平的误差估计,来诊断偏差和方差。
这让我们了解了正则化的定义:正则化是为模型添加约束,使其能够很好地泛化到新数据,并且不会对小数据波动过于敏感。正则化是将过拟合模型转化为稳健模型的一个重要工具。然而,由于偏差-方差权衡,我们不能过度正则化,以免得到一个欠拟合的模型。
最后,我们将本书将要涵盖的不同正则化方法进行了分类。它们主要分为三类:数据、模型架构和模型训练。
本章并未包括任何实用技巧,而是建立了理解本书其余内容所需的基础知识,但接下来的章节将包括实用技巧,并将更多聚焦于解决方案。
第二章:机器学习复习
机器学习(ML)远不止是模型。它是关于遵循某种过程和最佳实践。本章将为这些内容提供复习:从加载数据、模型评估到模型训练和优化,主要的步骤和方法将在这里解释。
在本章中,我们将涵盖以下主要主题:
-
数据加载
-
数据拆分
-
准备定量数据
-
准备定性数据
-
训练模型
-
评估模型
-
执行超参数优化
尽管本章中的各个教程在方法论上是独立的,但它们是相互衔接的,旨在按顺序执行。
技术要求
在本章中,你需要能够运行代码来加载数据集、准备数据、训练、优化和评估机器学习模型。为此,你将需要以下库:
-
numpy
-
pandas
-
scikit-learn
它们可以使用pip
和以下命令行安装:
pip install numpy pandas scikit-learn
注意
在本书中,一些最佳实践,如使用虚拟环境,将不会明确提及。然而,强烈建议在使用pip
或其他包管理器安装任何库之前,先使用虚拟环境。
数据加载
本教程的主要内容是从 CSV 文件加载数据。然而,这并不是本教程的唯一内容。由于数据通常是任何机器学习项目的第一步,因此这个教程也是快速回顾机器学习工作流和不同类型数据的好机会。
准备工作
在加载数据之前,我们应记住,机器学习模型遵循一个两步过程:
-
在给定的数据集上训练模型,以创建一个新模型。
-
重用先前训练的模型,对新数据进行推理预测。
这两步在以下图示中总结:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_02_01.jpg
图 2.1 – 两步机器学习过程的简化视图
当然,在大多数情况下,这是一个相当简化的视图。更详细的视图可以参见图 2.2:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_02_02.jpg
图 2.2 – 更完整的机器学习过程视图
让我们更仔细地看看机器学习过程中的训练部分,见图 2.2:
-
首先,从数据源(如数据库、数据湖、开放数据集等)查询训练数据。
-
数据被预处理,例如通过特征工程、重缩放等。
-
训练并存储模型(存储在数据湖、本地、边缘等位置)。
-
可选地,模型的输出会进行后处理,例如通过格式化、启发式方法、业务规则等。
-
可选地,这个模型(无论是否进行后处理)会被存储在数据库中,以便在需要时进行后续参考或评估。
现在,让我们看看机器学习过程中的推理部分:
-
数据从数据源(数据库、API 查询等)中查询。
-
数据经过与训练数据相同的预处理步骤。
-
如果训练好的模型不存在本地,会从远程获取模型。
-
使用该模型推断输出。
-
可选地,模型的输出通过与训练数据相同的后处理步骤进行后处理。
-
可选地,输出存储在数据库中,以供监控和后续参考。
即使在这个架构中,许多步骤也未被提及:数据划分用于训练、使用评估指标、交叉验证、超参数优化等。本章将深入探讨更具体的训练步骤,并将其应用于非常常见但实用的泰坦尼克号数据集,这是一个二分类问题。但首先,我们需要加载数据。
为此,你必须将 泰坦尼克号数据集训练集 下载到本地。可以通过以下命令行执行此操作:
wget https://github.com/PacktPublishing/The-Regularization-Cookbook/blob/main/chapter_02/train.csv
如何做到……
这个配方是关于加载一个 CSV 文件并显示几行代码,以便我们可以初步了解它的内容:
-
第一步是导入所需的库。在这里,我们唯一需要的库是 pandas:
import pandas as pd
-
现在,我们可以使用 pandas 提供的
read_csv
函数加载数据。第一个参数是文件的路径。假设文件名为train.csv
,并位于当前文件夹中,我们只需要提供train.csv
作为参数:# Load the data as a DataFrame
df = pd.read_csv('train.csv')
返回的对象是一个 dataframe
对象,它提供了许多用于数据处理的有用方法。
-
现在,我们可以使用
.head()
方法显示加载文件的前五行:# Display the first 5 rows of the dataset
df.head()
该代码将输出以下内容:
PassengerId Survived Pclass \
0 1 0 3
1 2 1 1
2 3 1 3
3 4 1 1
4 5 0 3
Name Sex Age SibSp \
0 Braund, Mr. Owen Harris male 22.0 1
1 Cumings, Mrs. John Bradley (Florence Briggs Th...
female 38.0 1
2 Heikkinen, Miss. Laina female 26.0 0
3 Futrelle, Mrs. Jacques Heath (Lily May Peel)
female 35.0 1
4 Allen, Mr. William Henry male 35.0 0
Parch Ticket Fare Cabin Embarked
0 0 A/5 21171 7.2500 NaN S
1 0 PC 17599 71.2833 C85 C
2 0 STON/O2\. 3101282 7.9250 NaN S
3 0 113803 53.1000 C123 S
4 0 373450 8.0500 NaN S
这里是每一列数据类型的描述:
-
PassengerId
(定性):每个乘客的唯一任意 ID。 -
Survived
(定性):1 表示是,0 表示否。这是我们的标签,因此这是一个二分类问题。 -
Pclass
(定量,离散):乘客的舱位,通常是定量的。舱位 1 是否比舱位 2 更好?很可能是的。 -
Name
(非结构化):乘客的姓名和头衔。 -
Sex
(定性):乘客的注册性别,可以是男性或女性。 -
Age
(定量,离散):乘客的年龄。 -
SibSp
(定量,离散):船上兄弟姐妹和配偶的数量。 -
Parch
(定量,离散):船上父母和子女的数量。 -
Ticket
(非结构化):票务参考。 -
Fare
(定量,连续):票价。 -
Cabin
(非结构化):舱位号,可以说是非结构化的。它可以看作是一个具有高基数的定性特征。 -
Embarked
(定性):登船城市,可以是南安普顿(S
)、瑟堡(C
)或皇后镇(Q
)。
还有更多内容……
让我们来谈谈可用的不同数据类型。数据是一个非常通用的词,可以描述很多东西。我们总是被数据包围。指定数据的一种方式是使用对立面。
数据可以是 结构化 或 非结构化:
-
结构化数据以表格、数据库、Excel 文件、CSV 文件和 JSON 文件的形式存在。
-
非结构化数据不适合表格形式:它可以是文本、声音、图像、视频等。即使我们倾向于使用表格形式表示,这类数据也不自然适合放入 Excel 表格中。
数据可以是定量的或定性的。
定量数据是有序的。以下是一些例子:
-
€100 比 €10 大
-
1.8 米比 1.6 米更高
-
18 岁比 80 岁年轻
定性数据没有固有的顺序,如下所示:
-
蓝色并不天生优于红色
-
一只狗并不天生比一只猫更优秀
-
一个厨房并不天生比一个浴室更有用
这些特征并非互相排斥。一个对象可以同时具有定量和定性特征,如下图所示的汽车案例:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_02_03.jpg
图 2.3 – 一个对象的定量(左)和定性(右)特征表示
最后,数据可以是连续的或离散的。
一些数据是连续的,如下所示:
-
一个体重
-
一个体积
-
一个价格
另一方面,一些数据是离散的:
-
一种颜色
-
一个足球得分
-
一个国籍
注意
离散数据 ≠ 定性数据。
例如,足球得分是离散的,但存在固有的顺序:3 分大于 2 分。
另见
pandas 的 read_csv
函数非常灵活,能够使用其他分隔符、处理标题等更多功能。这些都在官方文档中有所描述:pandas.pydata.org/docs/reference/api/pandas.read_csv.xhtml
。
pandas 库允许执行具有不同输入类型的 I/O 操作。欲了解更多信息,请查看官方文档:pandas.pydata.org/docs/reference/io.xhtml
。
分割数据
加载数据后,分割数据是一个关键步骤。本食谱将解释为什么我们需要分割数据,以及如何进行分割。
准备中
为什么我们需要分割数据?一个机器学习模型就像一个学生。
你给学生提供许多讲座和练习,带或不带答案。但通常,学生们会在一个全新的问题上进行评估。为了确保他们完全理解概念和方法,他们不仅学习练习和解决方案——还要理解其背后的概念。
一个机器学习模型也是一样:你在训练数据上训练模型,然后在测试数据上评估它。通过这种方式,你可以确保模型充分理解任务,并且能够很好地推广到新的、未见过的数据。
因此,数据集通常会被分割成训练集和测试集:
-
训练集必须尽可能大,以便为模型提供尽可能多的样本
-
测试集必须足够大,以便在评估模型时具有统计意义
对于较小的数据集(例如,几百个样本),常见的划分比例为 80% 对 20%;对于非常大的数据集(例如,数百万个样本),比例通常为 99% 对 1%。
在本章节的这个示例和其他示例中,假设代码是在与前一个示例相同的笔记本中执行的,因为每个示例都会重复使用前一个示例中的代码。
如何操作…
以下是尝试此示例的步骤:
-
你可以使用 scikit-learn 和
train_test_split()
函数相对容易地划分数据:# Import the train_test_split function
from sklearn.model_selection import train_test_split
# Split the data
X_train, X_test, y_train, y_test = train_test_split(
df.drop(columns=['Survived']), df['Survived'],
test_size=0.2, stratify=df['Survived'],
random_state=0)
该函数使用以下参数作为输入:
-
X
:除了'Survived'
标签列之外的所有列 -
y
:'Survived'
标签列 -
test_size
:这是0.2
,意味着训练集的比例为 80%。 -
stratify
:此参数指定'Survived'
列,以确保在两个数据集划分中标签的平衡性相同。 -
random_state
:0
是任何整数值,用于确保结果的可复现性。
它返回以下输出:
-
X_train
:X
的训练集。 -
X_test
:X
的测试集。 -
y_train
:与X_train
相关的y
的训练集。 -
y_test
:与X_test
相关的y
的测试集。
注意
stratify
选项不是强制性的,但对于确保任何定性特征(不仅仅是标签)的平衡划分非常关键,特别是对于不平衡数据的情况。
数据划分应尽早进行,以避免潜在的数据泄漏。从现在起,所有预处理步骤都会在训练集上计算,然后应用到测试集,这与 图 2.2 的做法一致。
另见
查看 train_test_split
函数的官方文档:scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.xhtml
。
准备定量数据
根据数据类型,特征的准备方式可能会有所不同。在本例中,我们将讨论如何准备定量数据,包括缺失数据填补和重新缩放。
准备工作
在 Titanic 数据集中,和其他任何数据集一样,可能存在缺失数据。处理缺失数据的方法有很多种。例如,可以删除某一列或某一行,或者进行数据填补。填补方法有很多种,其中一些更复杂或者更简单。scikit-learn 提供了几种填补器的实现,例如 SimpleImputer
和 KNNImputer
。
如我们在这个示例中所见,使用 SimpleImputer
,我们可以用均值来填补缺失的定量数据。
一旦缺失数据得到处理,我们就可以通过重新缩放数据来准备定量数据,使得所有数据都在相同的尺度上。
有几种重新缩放策略,例如最小-最大缩放、鲁棒缩放、标准缩放等。
在本教程中,我们将使用标准缩放。因此,对于每个特征,我们将减去该特征的均值,然后除以该特征的标准差:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_02_001.jpg
幸运的是,scikit-learn 通过StandardScaler
提供了一个完整的实现。
如何操作……
在本教程中,我们将依次处理缺失值并重新缩放数据:
-
导入所需的类——
SimpleImputer
用于缺失数据插补,StandardScaler
用于重新缩放:from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler
-
选择我们要保留的定量特征。在这里,我们将保留
'Pclass'
、'Age'
、'Fare'
、'SibSp'
和'Parch'
,并将这些特征存储在训练集和测试集的新变量中:quanti_columns = ['Pclass', 'Age', 'Fare', 'SibSp', 'Parch']
# Get the quantitative columns
X_train_quanti = X_train[quanti_columns]
X_test_quanti = X_test[quanti_columns]
-
使用均值策略实例化简单填充器。在这里,特征的缺失值将被该特征的均值替代:
# Impute missing quantitative values with mean feature value
quanti_imputer = SimpleImputer(strategy='mean')
-
在训练集上拟合填充器,并将其应用于测试集,这样可以避免在插补中出现数据泄漏:
# Fit and impute the training set
X_train_quanti = quanti_imputer.fit_transform(X_train_quanti)
# Just impute the test set
X_test_quanti = quanti_imputer.transform(X_test_quanti)
-
现在插补已经完成,实例化
scaler
对象:# Instantiate the standard scaler
scaler = StandardScaler()
-
最后,拟合并应用标准缩放器到训练集,然后将其应用于测试集:
# Fit and transform the training set
X_train_quanti = scaler.fit_transform(X_train_quanti)
# Just transform the test set
X_test_quanti = scaler.transform(X_test_quanti)
现在我们有了没有缺失值、完全缩放的定量数据,且没有数据泄漏。
还有更多……
在本教程中,我们使用了简单填充器,假设存在缺失数据。在实际应用中,强烈建议你首先查看数据,检查是否存在缺失值以及缺失的数量。你可以使用以下代码片段查看每列的缺失值数量:
# Display the number of missing data for each column
X_train[quanti_columns].isna().sum()
这将输出如下结果:
Pclass 0
Age 146
Fare 0
SibSp 0
Parch 0
通过这样做,我们知道Age
特征有146
个缺失值,而其他特征没有缺失数据。
另见
scikit-learn 中有几种可用的填充器。完整列表请见这里:scikit-learn.org/stable/modules/classes.xhtml#module-sklearn.impute
。
数据缩放有很多方法,你可以在 scikit-learn 中找到可用的方法,详见:scikit-learn.org/stable/modules/classes.xhtml#module-sklearn.preprocessing
。
你可能会对以下内容感兴趣,这是一个关于不同缩放器在给定数据上效果对比的展示:scikit-learn.org/stable/auto_examples/preprocessing/plot_all_scaling.xhtml#sphx-glr-auto-examples-preprocessing-plot-all-scaling-py
。
准备定性数据
在本教程中,我们将准备定性数据,包括缺失值插补和编码。
准备工作
定性数据需要与定量数据不同的处理方式。使用特征的均值填补缺失值是没有意义的(并且对非数值数据不起作用):例如,使用最频繁的值或特征的众数来填补缺失值更为合理。SimpleImputer
类允许我们进行这种操作。
重缩放同样适用:对定性数据进行重缩放没有意义。相反,更常见的是对其进行编码。最典型的技术之一叫做独热编码。
其思想是将每个类别在总共可能的N个类别中转换为一个向量,其中包含一个 1 和 N-1 个零。在我们的例子中,Embarked
特征的独热编码如下:
-
‘C’ = [1, 0, 0]
-
‘Q’ = [0, 1, 0]
-
‘S’ = [0, 0, 1]
注
对于N个类别使用N列并不一定是最优的。如果在前面的例子中我们去掉第一列会发生什么?如果值既不是*‘Q’ = [1, 0]也不是‘S’ = [0, 1],那么它必须是‘C’ = [0, 0]。没有必要再增加一列来获取所有必要的信息。这可以推广到N个类别,只需要N*-1 列就能包含所有信息,这也是为什么独热编码函数通常允许你丢弃一列。
sklearn
类的OneHotEncoder
允许我们实现这一点。它还允许我们使用几种策略处理可能出现在测试集(或生产环境)中的未知类别,如错误、忽略或低频类别。最后,它允许我们在编码后丢弃第一列。
如何操作……
就像在前面的食谱中一样,我们将处理任何缺失的数据,并且特征将进行独热编码:
-
导入必要的类——用于缺失数据填补的
SimpleImputer
(在前面的食谱中已导入)和用于编码的OneHotEncoder
。我们还需要导入numpy
,以便我们可以在本食谱结束时将已准备好的定性和定量数据拼接在一起:import numpy as np
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder
-
选择我们要保留的定性特征:
'Sex'
和'Embarked'
。然后,将这些特征分别存储到训练集和测试集中的新变量中:quali_columns = ['Sex', 'Embarked']
# Get the quantitative columns
X_train_quali = X_train[quali_columns]
X_test_quali = X_test[quali_columns]
-
使用
SimpleImputer
并选择most_frequent strategy
。任何缺失的值将被最频繁的值替代:# Impute missing qualitative values with most frequent feature value
quali_imputer =SimpleImputer(strategy='most_frequent')
-
在训练集上拟合并转换填补器,然后在测试集上进行转换:
# Fit and impute the training set
X_train_quali = quali_imputer.fit_transform(X_train_quali)
# Just impute the test set
X_test_quali = quali_imputer.transform(X_test_quali)
-
实例化编码器。在这里,我们将指定以下参数:
-
drop='first'
:这将删除编码后的第一列 -
handle_unknown='ignore'
:如果在测试集(或生产环境)中出现新值,它将被编码为零:# Instantiate the encoder
encoder=OneHotEncoder(drop='first', handle_unknown='ignore')
-
-
在训练集上拟合并转换编码器,然后使用此编码器在测试集上进行转换:
# Fit and transform the training set
X_train_quali = encoder.fit_transform(X_train_quali).toarray()
# Just encode the test set
X_test_quali = encoder.transform(X_test_quali).toarray()
注
我们需要使用.toarray()
从编码器中提取数据,因为默认情况下数组是稀疏矩阵对象,不能以这种形式与其他特征进行拼接。
-
这样,所有数据都已经准备好——包括定量数据和定性数据(考虑到本教程和之前的教程)。现在可以在训练模型之前将这些数据连接起来:
# Concatenate the data back together
X_train = np.concatenate([X_train_quanti,
X_train_quali], axis=1)
X_test = np.concatenate([X_test_quanti, X_test_quali], axis=1)
还有更多…
可以将数据保存为 pickle 文件,既可以分享它,也可以保存它以避免重新准备数据。以下代码将允许我们这样做:
import pickle
pickle.dump((X_train, X_test, y_train, y_test),
open('prepared_titanic.pkl', 'wb'))
我们现在拥有完全准备好的数据,可以用于训练机器学习模型。
注意
这里省略或简化了一些步骤以便更加清晰。数据可能需要更多的准备工作,例如更彻底的缺失值填补、异常值和重复值检测(以及可能的删除)、特征工程等。假设你已经对这些方面有一定了解,并鼓励你在需要时阅读其他相关资料。
另请参见
这份关于缺失数据填补的更通用文档值得一看:scikit-learn.org/stable/modules/impute.xhtml
。
最后,这份关于数据预处理的更通用文档非常有用:scikit-learn.org/stable/modules/preprocessing.xhtml
。
训练模型
一旦数据完全清洗和准备好,通过 scikit-learn 训练模型就相对容易了。在这份教程中,在对 Titanic 数据集训练逻辑回归模型之前,我们将快速回顾机器学习的范式和我们可以使用的不同类型的机器学习。
准备工作
如果有人问你如何区分汽车和卡车,你可能会倾向于提供一系列规则,比如轮子数量、大小、重量等等。通过这样做,你就能提供一套显式的规则,让任何人都能将汽车和卡车区分开来。
传统编程并没有那么不同。在开发算法时,程序员通常会构建显式规则,从而使他们能够将数据输入(例如,一辆车)映射到答案(例如,一辆汽车)。我们可以将这种范式总结为数据 + 规则 = 答案。
如果我们要训练一个机器学习模型来区分汽车和卡车,我们会使用另一种策略:我们会将大量数据及其相关答案输入机器学习算法,期望模型能够自我学习并纠正规则。这是一种不同的方法,可以总结为数据 + 答案 = 规则。这种范式的差异总结在图 2.4中。尽管它对机器学习从业者看起来可能微不足道,但在正则化方面,它改变了一切:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_02_04.jpg
图 2.4 – 比较传统编程与机器学习算法
对传统算法进行正则化在概念上是直接的。例如,如果定义卡车的规则与公共汽车的定义重叠该怎么办?如果是这样,我们可以增加“公共汽车有很多窗户”这一事实。
机器学习中的正则化本质上是隐式的。如果在这种情况下模型无法区分公交车和卡车怎么办?
-
我们应该增加更多数据吗?
-
模型是否足够复杂,以捕捉这种差异?
-
是欠拟合还是过拟合?
机器学习的这一基本特性使得正则化变得复杂。
机器学习可以应用于许多任务。任何使用机器学习的人都知道,机器学习模型不仅仅有一种类型。
可以说,大多数机器学习模型可以分为三大类:
-
监督学习
-
无监督学习
-
强化学习
正如通常对于类别问题的情况一样,情况更为复杂,有子类别和多种方法重叠多个类别。但这超出了本书的范围。
本书将重点讨论监督学习中的正则化。在监督学习中,问题通常很容易界定:我们有输入特征,X(例如,公寓面积),以及标签,y(例如,公寓价格)。目标是训练一个足够健壮的模型,以便在给定X的情况下预测y。
机器学习的两大主要类型是分类和回归:
-
分类:标签由定性数据构成。例如,任务是预测两个或多个类别之间的区别,如汽车、公交车和卡车。
-
回归:标签由定量数据构成。例如,任务是预测一个实际的值,如公寓价格。
再次强调,这条界限可能模糊;一些任务虽然标签是定量数据,但仍然可以通过分类来解决,而其他任务可能既属于分类也属于回归。参见图 2.5:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_02_05.jpg
图 2.5 – 正则化与分类
如何实现…
假设我们要训练一个逻辑回归模型(将在下章中详细解释),scikit-learn 库提供了LogisticRegression
类,以及fit()
和predict()
方法。让我们来学习如何使用它:
-
导入
LogisticRegression
类:from sklearn.linear_model import LogisticRegression
-
实例化
LogisticRegression
对象:# Instantiate the model
lr = LogisticRegression()
-
在训练集上拟合模型:
# Fit on the training data
lr.fit(X_train, y_train)
-
可选地,使用该模型在测试集上计算预测:
# Compute and store predictions on the test data
y_pred = lr.predict(X_test)
另见
尽管下章会提供更多细节,但你可能有兴趣查看LogisticRegression
类的文档:scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.xhtml
。
评估模型
一旦模型训练完成,评估它非常重要。在本例中,我们将在评估模型在测试集上的表现之前,提供一些关于分类和回归的典型指标的见解。
准备工作
存在许多评估指标。如果我们考虑预测一个二分类问题并回顾一下,只有四种情况:
-
假阳性(FP):正预测,负实际值
-
真正例 (TP): 正确的正类预测,正类真实值
-
真负例 (TN): 正确的负类预测,负类真实值
-
假负例 (FN): 错误的负类预测,正类真实值:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_02_06.jpg
图 2.6 – 假正例、真正例、真负例和假负例的表示
基于此,我们可以定义一系列评估指标。
最常见的指标之一是准确率,它是正确预测的比例。准确率的定义如下:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_02_002.jpg
注意
尽管准确率非常常见,但它可能会产生误导,尤其是在标签不平衡的情况下。例如,假设一个极端情况是 99% 的泰坦尼克号乘客幸存,而我们有一个模型预测每个乘客都幸存。我们的模型准确率为 99%,但对于所有未幸存的乘客来说,它都是错误的。
还有一些其他非常常见的指标,如精确度、召回率和 F1 分数。
精确度最适用于最大化真正例并最小化假正例的情况——例如,确保只检测到幸存的乘客:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_02_003.jpg
召回率最适用于最大化真正例并最小化假负例的情况——例如,确保不会漏掉任何幸存的乘客:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_02_004.jpg
F1 分数是精确度和召回率的调和均值组合:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_02_005.jpg
另一个有用的分类评估指标是 接收者操作特征曲线下的面积 (ROC AUC) 分数。
所有这些指标的行为类似:当值介于 0 和 1 之间时,值越高,模型越好。有些指标对于标签不平衡更为稳健,尤其是 F1 分数和 ROC AUC。
对于回归任务,最常用的指标是 均方误差 (MSE) 和 R2 分数。
MSE 是预测值和真实值之间的平均平方差:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_02_006.jpg
在这里,m是样本数量,ŷ是预测值,y是真实值:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_02_07.jpg
图 2.7 – 回归任务的误差可视化
就 R2 分数而言,它是一个可能为负的指标,定义如下:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_02_007.jpg
注意
虽然 R2 分数是一个典型的评估指标(越接近 1 越好),但是 MSE 更典型地作为损失函数(越接近 0 越好)。
如何操作……
假设我们选择的评估指标是准确率,那么评估我们模型的一个非常简单的方法是使用 accuracy_score()
函数:
from sklearn.metrics import accuracy_score
# Compute the accuracy on test of our model
print('accuracy on test set:', accuracy_score(y_pred,
y_test))
这会输出以下结果:
accuracy on test set: 0.7877094972067039
在这里,accuracy_score()
函数提供了 78.77% 的准确率,意味着大约 79% 的模型预测是正确的。
另见
以下是 scikit-learn 中可用度量的列表:scikit-learn.org/stable/modules/classes.xhtml#module-sklearn.metrics
。
执行超参数优化
在这个食谱中,我们将解释什么是超参数优化以及一些相关概念:超参数的定义、交叉验证和各种超参数优化方法。然后,我们将执行网格搜索,优化 Titanic 数据集上逻辑回归任务的超参数。
做好准备
在机器学习中,大多数情况下,我们不仅仅是在训练集上训练一个模型,然后在测试集上评估它。
这是因为像大多数其他算法一样,机器学习算法可以进行微调。这个微调过程让我们可以优化超参数,从而获得最佳的结果。这有时也作为杠杆,帮助我们对模型进行正则化。
注意
在机器学习中,超参数是由人类调整的,而参数是通过模型训练过程学习的,因此不能调整。
为了正确优化超参数,需要引入第三个拆分:验证集。
这意味着现在有了三个拆分:
-
训练集:模型训练的地方
-
验证集:超参数优化的地方
-
测试集:模型进行评估的地方
你可以通过使用 scikit-learn 的 train_test_split()
函数将 X_train
拆分成 X_train
和 X_valid
来创建这样的集。
但在实际操作中,大多数人仅使用交叉验证,并且不费心创建这个验证集。k 折交叉验证方法允许我们从训练集中进行k次拆分,并将其分开,如图 2.8所示:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_02_08.jpg
图 2.8 – 典型的训练集、验证集和测试集的拆分,无交叉验证(上图)和有交叉验证(下图)
这样做时,不是训练一个模型,而是针对给定的超参数集训练k个模型。然后基于选择的度量(例如准确度、均方误差等),对这k个模型的表现进行平均。
然后可以测试多个超参数集合,选择性能最佳的那个。在选择了最佳超参数集合后,模型会在整个训练集上再次训练,以最大化用于训练的数据。
最后,你可以实施几种策略来优化超参数,如下所示:
-
网格搜索:测试提供的超参数值的所有组合
-
随机搜索:随机搜索超参数的组合
-
贝叶斯搜索:对超参数进行贝叶斯优化
如何操作……
虽然从概念上解释起来相当复杂,但使用交叉验证进行超参数优化非常容易实现。在这个例子中,我们假设我们希望优化一个逻辑回归模型,预测乘客是否会幸存:
-
首先,我们需要从
sklearn.model_selection
导入GridSearchCV
类。 -
我们希望测试以下
C
的超参数值:[0.01, 0.03, 0.1]
。我们必须定义一个参数网格,其中超参数作为键,待测试的值列表作为值。
C
超参数是惩罚强度的倒数:C
越大,正则化越小。详细信息请参见下一章:
# Define the hyperparameters we want to test
param_grid = { 'C': [0.01, 0.03, 0.1] }
-
最后,假设我们希望通过五次交叉验证折叠来优化模型的准确性。为此,我们将实例化
GridSearchCV
对象,并提供以下参数:-
要优化的模型,它是一个
LogisticRegression
实例 -
我们之前定义的超参数网格
param_grid
-
要优化的评分标准——即
accuracy
-
设置为
5
的交叉验证折叠次数
-
-
我们还必须将
return_train_score
设置为True
,以便获取一些我们以后可以使用的有用信息:# Instantiate the grid search object
grid = GridSearchCV(
LogisticRegression(),
param_grid,
scoring='accuracy',
cv=5,
return_train_score=True
)
-
最后,我们要做的就是在训练集上训练这个对象。这将自动进行所有计算并存储结果:
# Fit and wait
grid.fit(X_train, y_train)
GridSearchCV(cv=5, estimator=LogisticRegression(),
param_grid={'C': [0.01, 0.03, 0.1]},
return_train_score=True, scoring='accuracy')
注意
根据输入数据集和测试的超参数数量,拟合可能需要一些时间。
拟合完成后,您可以获取许多有用的信息,例如以下内容:
-
通过
.best_params
属性获得超参数集 -
通过
.best_score
属性获得最佳准确度分数 -
通过
.cv_results
属性获得交叉验证结果
-
最后,您可以使用
.predict()
方法推断经过优化超参数训练的模型:y_pred = grid.predict(X_test)
-
可选地,您可以使用准确度评分来评估所选择的模型:
print('Hyperparameter optimized accuracy:',
accuracy_score(y_pred, y_test))
这将提供以下输出:
Hyperparameter optimized accuracy: 0.781229050279329
借助 scikit-learn 提供的工具,优化模型并使用多个指标进行评估非常容易。在下一节中,我们将学习如何基于这样的评估来诊断偏差和方差。
另见
GridSearchCV
的文档可以在scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.xhtml
找到。
第三章:线性模型的正则化
机器学习(ML)的一个重要部分是由线性模型构成的。尽管有时它们被认为不如非线性模型(如基于树的模型或深度学习模型)强大,但线性模型确实能解决许多具体且有价值的问题。客户流失和广告优化就是线性模型可能是正确解决方案的两个问题。
在本章中,我们将覆盖以下内容:
-
使用 scikit-learn 训练线性回归模型
-
使用岭回归进行正则化
-
使用套索回归进行正则化
-
使用弹性网回归进行正则化
-
训练逻辑回归模型
-
正则化逻辑回归模型
-
选择正确的正则化方法
到本章结束时,我们将学习如何使用并正则化一些最常用的线性模型。
技术要求
在本章中,除了加载数据外,您将学习如何使用多个线性模型进行拟合和推理计算。为此,以下库是必需的:
-
NumPy
-
Matplotlib
-
Scikit-learn
使用 scikit-learn 训练线性回归模型
线性回归是我们可以使用的最基础的机器学习模型之一,但它非常有用。大多数人在高中时就使用过线性回归,虽然当时并没有讨论机器学习,而且他们在电子表格中也经常使用它。在这个教程中,我们将解释线性回归的基础知识,然后使用 scikit-learn 在加利福尼亚房价数据集上训练并评估一个线性回归模型。
准备工作
线性回归不是一个复杂的模型,但理解其底层原理仍然非常有用,这样才能从中获得最佳效果。
线性回归的工作原理非常简单。回到房地产价格的例子,如果我们考虑一个特征 x,比如公寓的面积和一个标签 y,比如公寓价格,一个常见的解决方案是找到 a 和 b 使得 y = ax + b。
不幸的是,现实生活中并非如此简单。通常没有一个 a 和 b 使得这个等式始终成立。更可能的情况是,我们可以定义一个函数 h(x)
,旨在给出尽可能接近 y 的值。
此外,我们可能不仅仅有一个特征 x,而是有多个特征 x1、x2、…、xn,代表公寓面积、位置、楼层、房间数量、指数特征等等。
按照这个逻辑,我们最终得到的预测 h(x) 可能会像下面这样:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_001.jpg
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_002.png 是与特征 https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_003.png xj 相关的权重,b 是一个偏置项。这只是之前 y = ax + b 公式的一个推广,扩展到 n 个特征。这一公式允许线性回归预测几乎任何实数值。
我们的机器学习模型的目标是找到一组 w 和 b 的值,使得训练集上的预测误差最小。也就是说,我们要找到参数 w 和 b,使得 h(x) 和 y 尽可能接近。
实现这一目标的一种方法是最小化损失 L,这里可以将其定义为稍作修改的均方 误差(MSE):
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_004.jpg
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_005.png 是训练集中样本 i 的真实值,m 是训练集中的样本数量。
注意
损失通常表示真实值与预测值之间的差异。因此,最小化损失可以让模型预测的值尽可能接近真实值。
最小化均方误差可以帮助我们找到一组 w 和 b 的值,使得预测 h(x) 尽可能接近真实值 y。从示意图来看,这可以表示为找到最小化损失的 w,如以下图所示:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_03_01.jpg
图 3.1 – 损失函数作为参数 theta 的函数,在交点处具有全局最小值
下一个问题是:我们如何找到最小化损失的值集?解决这个问题有多种方法。一种在机器学习中常用的技术是梯度下降。
什么是梯度下降?简而言之,它是沿着曲线下降至前面图示中的最小值。
这怎么运作?这是一个多步骤的过程:
-
从随机的 w 和 b 参数值开始。随机值通常使用以零为中心的正态分布来定义。因此,缩放特征可能有助于收敛。
-
计算给定数据和当前 w、b 值的损失。如前所定义,我们可以使用均方误差来计算损失 L。
以下图表示了此时的情形:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_03_02.jpg
图 3.2 – 损失函数在红色交点处具有全局最小值,蓝色交点处为可能的随机初始状态
- 计算相对于每个参数的损失梯度 https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_006.png。这不过是给定参数的损失的斜率,可以通过以下公式计算:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_007.jpghttps://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_008.jpg
注意
可以注意到,随着接近最小值,斜率预计会减小。事实上,随着接近最小值,误差趋向于零,斜率也趋向于零,这基于这些公式。
- 对参数应用梯度下降。对参数应用梯度下降,使用用户定义的学习率 α。这可以通过以下公式计算:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_009.jpghttps://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_010.jpg
这让我们朝着最小值迈出一步,如下图所示:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_03_03.jpg
图 3.3 – 梯度下降法允许我们沿着损失函数向下迈进一步,从而使我们更接近全局最小值。
- 迭代执行 步骤 2 到 4,直到收敛或达到最大迭代次数。这将使我们达到最优参数,如下图所示:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_03_04.jpg
图 3.4 – 在足够的迭代次数和凸损失函数下,参数将会收敛到全局最小值
注:
如果学习率 α 太大,可能会错过全局最小值,甚至导致发散;而如果学习率太小,收敛速度会非常慢。
要完成这个步骤,必须安装以下库:numpy
、matplotlib
和 sklearn
。你可以在终端使用 pip
安装,命令如下:
pip install -U numpy sklearn matplotlib
如何操作…
幸运的是,整个过程在 scikit-learn 中已经完全实现,你所需要做的就是充分利用这个库。现在我们来训练一个线性回归模型,使用 scikit-learn 提供的加利福尼亚住房数据集。
-
fetch_california_housing
:一个可以加载数据集的函数 -
train_test_split
:一个可以将数据拆分的函数 -
StandardScaler
:允许我们重新缩放数据的类 -
LinearRegression
:包含线性回归实现的类
以下是代码示例:
import numpy as np
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
-
X
与X*X
:# Load the California housing dataset
X, y = fetch_california_housing(return_X_y=True)
X = np.concatenate([X, X*X], axis=1)
-
train_test_split
函数,使用test_size=0.2
,意味着我们将数据随机拆分,80% 的数据进入训练集,20% 的数据进入测试集。如下所示:# Split the data
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=0)
-
准备数据:由于我们这里只有量化特征,因此唯一需要的准备工作是重新缩放。我们可以使用 scikit-learn 的标准缩放器。我们需要实例化它,然后在训练集上拟合并转换训练集,最后转换测试集。你可以自由使用任何其他的重缩放器:
# Rescale the data
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
-
在训练集上拟合模型。模型必须先实例化,这里我们使用默认参数,因此没有指定任何内容。实例化模型后,我们可以使用
.fit()
方法对训练集进行拟合:# Fit the linear regression model
lr = LinearRegression()
lr.fit(X_train, y_train)
-
在训练集和测试集上评估模型。这里,我们使用
LinearRegression
类的.score()
方法,它提供了R2-score
,但你也可以使用sklearn.metrics
中提供的任何其他回归度量:# Print the R2-score on train and test
print('R2-score on train set:', lr.score(X_train, y_train))
print('R2-score on test set:', lr.score(X_test, y_test))
以下是输出结果:
R2-score on train set: 0.6323843381852894 R2-score on test set: -1.2472000127402643
如我们所见,训练集和测试集的分数差异显著,表明模型在训练集上出现了过拟合。为了解决这个问题,接下来将介绍正则化技术。
还有更多内容…
一旦模型训练完成,我们可以通过 LinearRegression
对象的属性 .coef_
和 .intercept_
分别访问所有的参数 w(这里是 16 个值,对应 16 个输入特征)以及截距 b:
print('w values:', lr.coef_)
print('b value:', lr.intercept_)
这是输出结果:
w values: [ 1.12882772e+00 -6.48931138e-02 -4.04087026e-01 4.87937619e-01 -1.69895164e-03 -4.09553062e-01 -3.72826365e+00 -8.38728583e+00 -2.67065542e-01 2.04856554e-01 2.46387700e-01 -3.19674747e-01 2.58750270e-03 3.91054062e-01 2.82040287e+00 -7.50771410e+00] b value: 2.072498958939411
如果我们绘制这些值,我们会注意到它们在这个数据集中的值大约在 -8
和 2
之间:
import matplotlib.pyplot as plt
plt.bar(np.arange(len(lr.coef_)), lr.coef_)
plt.xlabel('feature index')
plt.ylabel('weight value')
plt.show()
这是一个可视化表示:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_03_05.jpg
图 3.5 – 线性回归模型中每个权重的学习值。值的范围相当大,从 -8 到 2
参见其他
要全面了解如何使用 scikit-learn 进行线性回归,最好参考该类的官方文档:scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.xhtml
。
我们现在对线性回归的工作原理有了较好的理解,接下来我们将看到如何通过惩罚进行正则化。
使用 ridge 回归进行正则化
一种非常常见且有用的正则化线性回归的方法是通过惩罚损失函数。在本示例中,经过回顾将惩罚项添加到 ridge 回归的损失函数之后,我们将在与之前示例相同的加利福尼亚住房数据集上训练一个 ridge 模型,看看它如何通过正则化提高评分。
准备就绪
确保模型参数不会过拟合的一种方法是保持它们接近零:如果参数无法自由变化,它们就不太可能过拟合。
为此,ridge 回归将一个新的项(正则化项)添加到损失函数中!:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_012.jpg
其中 https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_013.png 是 L2 范数的 w:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_014.jpg
通过这个损失函数,我们可以直观地理解,较大的权重 w 是不可能的,因此过拟合的可能性较小。此外,𝜆 是一个超参数(可以微调),允许我们控制正则化的程度:
-
𝜆 的较高值表示较强的正则化
-
𝜆 = 0 表示没有正则化,例如普通的线性回归
梯度下降的公式稍作更新,如下所示:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_015.jpghttps://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_016.jpg
现在让我们来看一下如何使用 ridge 回归与 scikit-learn:与前一个示例中一样,需要安装相同的库:numpy
、sklearn
和 matplotlib
。
同时,我们假设数据已经通过上一示例下载并准备好。有关如何下载、拆分和准备数据的详细信息,请参考前一个示例。
如何操作……
假设我们正在重复使用前一个示例中的相同数据。我们将在完全相同的数据上训练和评估另一个模型,包括具有平方特征的特征工程。scikit-learn 中岭回归的相关实现是Ridge
类,其中alpha
类属性等同于前述方程中的𝜆。让我们来使用它:
-
从 scikit-learn 中导入
Ridge
类:from sklearn.linear_model import Ridge
-
然后我们实例化一个岭回归模型。在这里选择了正则化参数
alpha=5000
,但每个数据集可能需要一个非常具体的超参数值来表现最佳。接下来,用.fit()
方法在训练集上训练模型(事先准备好的),如下所示:# Fit the Ridge model ridge = Ridge(alpha=5000)
ridge.fit(X_train, y_train)
Ridge(alpha=5000)
-
然后我们评估模型。在这里,我们计算并显示由岭回归类的
.score()
提供的 R2 分数,但也可以使用任何其他回归指标:# Print the R2-score on train and test
print('R2-score on train set:', ridge.score(X_train, y_train))
print('R2-score on test set:', ridge.score(X_test, y_test))
这里是输出:
R2-score on train set: 0.5398290317808138 R2-score on test set: 0.5034148460338739
我们会注意到,与线性回归模型(没有正则化)相比,我们在测试集上获得了更好的结果,允许测试集上的 R2 分数略高于0.5
。
还有更多…
我们还可以打印权重并绘制它们,将这些值与普通线性回归的值进行比较,如下所示:
print('theta values:', ridge.coef_)
print('b value:', ridge.intercept_)
这里是输出:
theta values: [ 0.43456599 0.06311698 0.00463607 0.00963748 0.00896739 -0.05894055 -0.17177956 -0.15109744 0.22933247 0.08516982 0.01842825 -0.01049763 -0.00358684 0.03935491 -0.17562536 0.1507696 ] b value: 2.07249895893891
为了可视化,我们还可以使用以下代码绘制这些值:
plt.bar(np.arange(len(ridge.coef_)), ridge.coef_)
plt.xlabel('feature index') plt.ylabel('weight value')
plt.show()
这段代码输出如下:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_03_06.jpg
图 3.6 - 岭回归模型每个权重的学习值。请注意,一些是正数,一些是负数,并且没有一个完全等于零。此外,范围比无正则化时小得多
现在权重值的范围是从-0.2
到.0.5
,确实比没有惩罚时要小得多。
正如预期的那样,添加正则化导致测试集的 R2 分数比没有正则化时更好,且接近训练集的分数。
参见
有关岭回归的所有可能参数和超参数的更多信息,您可以查看官方 scikit-learn 文档:scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.xhtml
。
使用套索回归进行正则化
套索回归代表最小绝对值收缩和选择算子。这是一个正则化方法,在某些情况下,套索回归优于岭回归,因此了解其作用和如何使用它非常有用。在这个示例中,我们将简要解释套索回归是什么,然后在相同的加利福尼亚房屋数据集上使用 scikit-learn 训练模型。
准备工作
lasso 使用 L1-norm,而不是 L2-norm,因此损失 https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_017.png 公式如下:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_018.jpg
虽然 ridge 回归倾向于平滑地将权重减少到接近零,lasso 的变化更加剧烈。由于 lasso 的损失曲线更陡峭,它倾向于迅速将权重设置为零。
就像 ridge 回归的做法一样,我们将使用相同的库,并假设它们已经安装:numpy
、sklearn
和 matplotlib
。同时,我们假设数据已经下载并准备好。
如何操作……
scikit-learn 的 lasso 实现可以通过 Lasso
类获得。与 Ridge
类类似,alpha
是控制正则化强度的参数:
-
alpha 值为 0 表示没有正则化
-
alpha 的大值意味着高强度的正则化
我们将再次使用已经准备好的数据集,该数据集已经用于线性回归和 ridge 回归:
-
从 scikit-learn 导入
Lasso
类:from sklearn.linear_model import Lasso
-
我们实例化一个
alpha=0.2
的 lasso 模型,它能提供相当不错的结果和较低的过拟合,如我们接下来所见。然而,欢迎测试其他值,因为每个数据集可能有其独特的最佳值。接下来,使用Lasso
类的.fit()
方法在训练集上训练该模型:# Fit the Lasso model lasso = Lasso(alpha=0.02)
lasso.fit(X_train, y_train)
-
使用
.score()
方法中的 R2-score 来评估训练集和测试集上的 lasso 模型,该方法在Lasso
类中实现:# Print the R2-score on train and test
print('R2-score on train set:', lasso.score(X_train, y_train))
print('R2-score on test set:', lasso.score(X_test, y_test))
这段代码会输出以下内容:
R2-score on train set: 0.5949103710772492
R2-score on test set: 0.57350350155955
在与没有惩罚的线性回归进行比较时,我们可以看到有所改进,并且相较于 ridge 回归,我们在测试集上的 R2-score 约为 0.57
。
还有更多……
如果我们再次绘制权重实例图,我们现在得到以下值:
plt.bar(np.arange(len(lasso.coef_)), lasso.coef_)
plt.xlabel('feature index')
plt.ylabel('weight value')
plt.show()
这将输出 图 3*.14* 中的图表:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_03_07.jpg
图 3.7 – 每个 lasso 模型权重的学习值。注意,与 ridge 模型不同,多个权重被设置为零
如预期所示,某些值被设置为 0,整体范围在 -0.5
到 0.7
之间。
在这种情况下,lasso 正则化使我们在测试集上的表现得到了显著提升,同时也减少了过拟合。
另见
查看 lasso 类的官方文档,了解更多信息:scikit-learn.org/stable/modules/generated/sklearn.linear_model.Lasso.xhtml
.
另一种名为 group lasso 的技术可以用于正则化,了解更多信息请访问: group-lasso.readthedocs.io/en/latest/
.
使用弹性网回归进行正则化
弹性网回归,除了有一个非常华丽的名字外,其实不过是岭回归和套索回归的组合。它是一种正则化方法,在一些特定情况下可能会有所帮助。让我们看看它在损失函数中的表现,然后在加利福尼亚住房数据集上训练一个模型。
准备工作
弹性网的思想是同时使用 L1 和 L2 正则化。
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_020.jpg
这两个超参数,https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_021.png和https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_022.png,可以进行微调。
我们不会详细讨论梯度下降的公式,因为一旦理解了岭回归和套索回归,推导它们是非常直接的。
为了训练模型,我们再次需要sklearn
库,这个库我们在之前的配方中已经安装过。此外,我们假设加利福尼亚住房数据集已经下载并准备好。
如何实现…
在 scikit-learn 中,弹性网在ElasticNet
类中实现。然而,它们使用的是两个超参数alpha
和l1_ratio
,而不是两个超参数https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_023.png和https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_024.png:
-
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_025.png
-
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_026.png
现在让我们将其应用到已经准备好的加利福尼亚住房数据集上:
-
从 scikit-learn 中导入
ElasticNet
类:from sklearn.linear_model import ElasticNet
-
实例化一个弹性网模型。选择了
alpha=0.1
和l1_ratio=0.5
,但也可以测试其他值。然后使用ElasticNet
类的.fit()
方法,在训练集上训练模型:# Fit the LASSO model
Elastic = ElasticNet(alpha=0.1, l1_ratio=0.5)
elastic.fit(X_train, y_train)
ElasticNet(alpha=0.1)
-
使用
.score()
方法计算的 R2 得分,在训练集和测试集上评估弹性网模型:# Print the R2-score on train and test
print('R2-score on train set:', elastic.score(
X_train, y_train))
print('R2-score on test set:', elastic.score(
X_test, y_test))
这是它的输出:
R2-score on train set: 0.539957010948829
R2-score on test set: 0.5134203748307193
在这个例子中,结果并没有超越套索回归:可能需要对超参数进行更好的微调才能达到相同的性能。
尽管弹性网回归由于有两个超参数而在微调时更为复杂,但它可能比岭回归或套索回归在正则化上提供更多灵活性。推荐使用超参数优化方法来找到适合特定任务的超参数组合。
注意
实际上,弹性网回归可能不如岭回归和套索回归广泛使用。
另见
scikit-learn 的官方文档中,关于弹性网回归的详细信息可以在此找到:scikit-learn.org/stable/modules/generated/sklearn.linear_model.ElasticNet.xhtml
。
训练逻辑回归模型
逻辑回归在概念上与线性回归非常接近。一旦完全理解了线性回归,逻辑回归就只需要几步小技巧。然而,与线性回归不同,逻辑回归最常用于分类任务。
让我们首先解释什么是逻辑回归,然后使用 scikit-learn 在乳腺癌数据集上训练一个模型。
准备开始
与线性回归不同,逻辑回归的输出范围限制在0
到1
之间。第一个思路与线性回归完全相同,为每个特征 https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_028.png 分配一个参数 https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_027.png:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_029.jpg
还有一步是将输出 z 限制在 0
到 1
之间,那就是对该输出应用逻辑函数。作为提醒,逻辑函数(也叫做 Sigmoid 函数,尽管它是一个更通用的函数)如下所示:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_030.jpg
逻辑函数呈 S 形,其值从 0
到 1
,在 x = 0 时取值为 0.5
,如图 3.8所示:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_03_08.jpg
图 3.8 – 逻辑函数,x = 0 时从 0 到 1 范围内穿过 0.5
最后,通过应用 Sigmoid 函数,我们得到了逻辑回归的预测 h(x),如下所示:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_031.jpg
这确保了输出值 h(x) 在0
到1
的范围内。但它还没有让我们进行分类。最后一步是应用一个阈值(例如,0.5
)来进行分类预测:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_032.jpg
和线性回归一样,我们现在需要定义一个损失函数 L
,以便最小化它,进而优化参数 https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_033.png 和 b。常用的损失函数是所谓的二元 交叉熵:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_034.jpg
我们可以看到这里有四种极端情况:
-
如果 y = 1 且 h(x)≃1:L ≃ 0
-
如果 y = 1 且 h(x)≃0:L ≃ +∞
-
如果 y = 0 且 h(x)≃0:L ≃ 0
-
如果 y = 0 且 h(x)≃1:L ≃ +∞
现在,为了实现我们期望的行为,即一个趋向于 0 的损失,表示高度准确的预测,并且在错误预测时损失增加。这个过程在下图中表示:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_03_09.jpg
图 3.9 – 二元交叉熵,最小化 y = 0 和 y = 1 的误差
同样,优化逻辑回归的一种方法是通过梯度下降,和线性回归完全相同。事实上,方程是完全相同的,尽管我们在这里无法证明这一点。
要准备这个步骤,我们只需要安装 scikit-learn
库。
如何做…
逻辑回归在 scikit-learn 中完全实现为 LogisticRegression
类。与 scikit-learn 中的线性回归不同,逻辑回归将正则化直接集成到一个类中。以下参数是需要调整的,以便微调正则化:
-
penalty
:这可以是'l1'
,'l2'
,'elasticnet'
或'none'
-
C
:这是一个浮动值,与正则化强度成反比;值越小,正则化越强。
在这个示例中,我们将应用不使用正则化的逻辑回归模型,处理由 scikit-learn 提供的乳腺癌数据集。我们将首先加载并准备数据,然后训练并评估逻辑回归模型,具体步骤如下:
-
导入
load_breast_cancer
函数,它将允许我们加载数据集,并导入LogisticRegression
类:from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
-
使用
load_breast_cancer
函数加载数据集:# Load the dataset
X, y = load_breast_cancer(return_X_y=True)
-
使用
train_test_split
函数拆分数据集。我们假设它已经在之前的示例中被导入。如果没有,你需要通过from sklearn.model_selection import train_test_split
来导入。我们选择test_size=0.2
,这样训练集占 80%,测试集占 20%:# Split the data
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42)
-
然后我们准备数据。由于数据集仅包含定量数据,我们只需要使用标准化方法进行重缩放:我们实例化标准化器,对训练集进行拟合,并用
fit_transform()
方法转换同一训练集。最后,我们使用.transform()
对测试集进行重缩放。同样地,我们假设标准化器已经在之前的示例中被导入,否则需要通过from sklearn.preprocessing import StandardScaler
导入:# Rescale the data
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
-
然后我们实例化逻辑回归模型,并指定
penalty='none'
,这样就不对损失进行惩罚(为了教学目的)。查看下一个示例,了解惩罚项如何工作。接着我们使用.fit()
方法在训练集上训练逻辑回归模型:# Fit the logistic regression model with no regularization
Lr = LogisticRegression(penalty='none')
lr.fit(X_train, y_train)
LogisticRegression(penalty='none')
-
在训练集和测试集上评估模型。
LogisticRegression
类的.score()
方法使用准确率作为评估指标,但也可以使用其他指标:# Print the accuracy score on train and test
print('Accuracy on train set:', lr.score(X_train, y_train))
print('Accuracy on test set:', lr.score(X_test, y_test))
这段代码输出如下内容:
Accuracy on train set: 1.0 Accuracy on test set: 0.9385964912280702
我们在这里面临较强的过拟合问题,训练集的分类准确率为 100%,但测试集的准确率只有大约 94%。这是一个好的开始,但在下一个示例中,我们将使用正则化来帮助提高测试准确率。
对逻辑回归模型进行正则化
逻辑回归与线性回归使用相同的技巧来添加正则化:它将惩罚项加到损失函数中。在这个示例中,我们将简要解释惩罚项如何影响损失,并展示如何使用 scikit-learn 在之前准备的乳腺癌数据集上添加正则化。
准备工作
就像线性回归一样,向损失函数L
中添加正则化项非常简单,可以是 L1 或 L2 范数的参数w。例如,包含 L2 范数的损失函数如下所示:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/Formula_03_035.jpg
和岭回归一样,我们在权重上加入了平方和,并在前面加了一个超参数。为了尽可能接近 scikit-learn 的实现,我们将使用 1/C 代替𝜆作为正则化超参数,但基本思想保持不变。
在这个食谱中,我们假设之前的食谱中已经安装了以下库:sklearn
和 matplotlib
。同时,我们假设乳腺癌数据集中的数据已经从前一个食谱中加载和准备好,因此我们可以直接重用它。
如何做到…
现在,让我们通过添加 L2 正则化来尝试提高我们在上一道食谱中的测试精度:
-
实例化逻辑回归模型。在这里,我们选择 L2 惩罚和正则化值
C=0.1
。较低的C
值意味着更强的正则化:lr = LogisticRegression(penalty='l2', C=0.1)
-
使用
.fit()
方法在训练集上拟合逻辑回归模型:lr.fit(X_train, y_train)
LogisticRegression(C=0.1)
-
在训练集和测试集上评估模型。我们在这里使用
.score()
方法,提供准确率得分:# Print the accuracy score on train and test
print('Accuracy on train set:', lr.score(
X_train, y_train))
print('Accuracy on test set:', lr.score(
X_test, y_test))
Accuracy on train set: 0.9802197802197802
Accuracy on test set: 0.9824561403508771
正如我们在这里看到的,添加 L2 正则化使我们在测试集上的精度达到了 98%,相比于没有正则化时的大约 94%,这是一项相当大的改进。
另一个提醒
找到 C
的最佳正则化值的最佳方法是进行超参数优化。
出于好奇,我们可以在这里绘制多个正则化强度值下的训练和测试精度:
accuracy_train = []
accuracy_test = []
c_values = [0.001,0.003,0.01,0.03,0.1,0.3,1,3,10,30]
for c in c_values:
lr = LogisticRegression(penalty='l2', C=c)
lr.fit(X_train, y_train)
accuracy_train.append(lr.score(X_train, y_train))
accuracy_test.append(lr.score(X_test, y_test))
plt.plot(c_values, accuracy_train, label='train')
plt.plot(c_values, accuracy_test, label='test')
plt.legend()
plt.xlabel('C: inverse of regularization strength')
plt.ylabel('Accuracy')
plt.xscale('log')
plt.show()
这是它的图表:
https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/rgl-cb/img/B19629_03_10.jpg
图 3.10 – 精度作为 C 参数的函数,适用于训练集和测试集
这个图实际上是从右到左读取的。我们可以看到,当 C
的值减小时(从而增加正则化),训练精度持续下降,因为正则化越来越强。另一方面,减小 C
(即增加正则化)最初能够改善测试结果:模型的泛化能力越来越强。但在某个时刻,添加更多正则化(进一步减小 C
)并不会带来更多帮助,甚至会降低测试精度。事实上,过多的正则化会产生高偏差模型。
还有更多…
LogisticRegression
的 scikit-learn 实现不仅允许我们使用 L2 惩罚,还允许使用 L1 和弹性网,就像线性回归一样,这让我们可以根据任何给定的数据集和任务来选择最佳的正则化方式。
可以查阅官方文档以获取更多细节:scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.xhtml
。
选择合适的正则化
线性模型与 L1 和 L2 惩罚共享这种正则化方法。实现中的唯一区别是线性回归有针对每种正则化类型的独立类,如下所述:
-
LinearRegression
无正则化 -
RidgeRegression
用于 L2 正则化 -
Lasso
用于 L1 正则化 -
ElasticNet
用于 L1 和 L2
逻辑回归有一个集成实现,可以将 L1
或 L2
作为类参数传递。
注意
对于SVC
分类类和SVR
回归类,使用C
参数进行L2
正则化。
但是对于线性回归和逻辑回归,有一个问题依然存在:我们应该使用 L1 还是 L2 正则化?
在这个方案中,我们将提供一些关于在某些情况下使用 L1 或 L2 惩罚的实用建议,然后我们将使用逻辑回归对乳腺癌数据集进行网格搜索,以找到最佳正则化方法。
准备工作
对于最适合的惩罚方式,并没有绝对的答案。大多数情况下,唯一的办法是通过超参数优化来找出答案。但在某些情况下,数据本身或外部约束可能会提示我们选择哪种正则化方法。让我们快速看一下。首先,比较 L1 和 L2 正则化,然后探索超参数优化。
L1 与 L2 正则化
L1 和 L2 正则化有内在的差异,这些差异有时可以帮助我们提前做出明智的判断,从而节省计算时间。让我们看看这些差异。
如前所述,L1 正则化倾向于将某些权重设置为零,从而可能允许特征选择。因此,如果我们有一个包含许多特征的数据集,拥有这些信息将会很有帮助,未来可以删除这些特征。
此外,L1 正则化使用权重的绝对值作为损失的一部分,使其相比 L2 正则化对异常值更加稳健。如果我们的数据集可能包含异常值,这是需要考虑的。
最后,在计算速度方面,L2 正则化是通过添加一个二次项来实现的,因此比 L1 正则化计算开销更小。如果训练速度是一个关注点,应该优先考虑 L2 正则化。
简而言之,我们可以考虑以下几种情况,这些情况可以根据数据或其他约束(如计算资源)在前期做出选择:
-
数据包含许多特征,其中许多特征重要性较低:选择 L1 正则化
-
数据包含许多异常值:L1 正则化
-
如果训练计算资源有限:选择 L2 正则化
超参数优化
更实际、或许更务实的方法是直接进行超参数优化,将 L1 或 L2 作为一个超参数(弹性网回归也可以加入)。
我们将使用scikit-learn
实现的网格搜索进行超参数优化,对乳腺癌数据集上的逻辑回归模型进行 L1 和 L2 正则化优化。
对于这个方案,我们假设在前一个方案中已经安装了scikit-learn
库。我们还假设乳腺癌数据已经通过训练逻辑回归 模型方案加载并准备好。
如何实现…
我们将在一组给定的超参数上执行网格搜索,更具体地说,就是在多个惩罚值下,进行 L1 和 L2 正则化的惩罚,其中C
为惩罚参数:
-
首先,我们需要从
sklearn
导入GridSearchCV
类:from sklearn.model_selection import GridSearchCV
-
然后我们定义参数网格。这是我们想要测试的超参数空间。在这里,我们将同时尝试 L1 和 L2 惩罚项,并且对于每个惩罚项,我们将尝试
C
值,范围是[0.01, 0.03, 0.06, 0.1, 0.3, 0.6]
,具体如下:# Define the hyperparameters we want to test param_grid = {
'penalty': ['l1', 'l2'],
'C': [0.01, 0.03, 0.06, 0.1, 0.3, 0.6] }
-
接下来,我们实例化网格搜索对象。这里传入了几个参数:
-
LogisticRegression
,我们指定求解器为liblinear
(更多信息请参见另见部分),这样它就能处理 L1 和 L2 惩罚项。我们假设该类已从之前的代码中导入;否则,您可以通过from sklearn.linear_model import LogisticRegression
导入它。 -
scoring='accuracy'
,但它也可以是任何其他相关的度量标准。 -
这里使用
cv=5
表示 5 折交叉验证,因为这是比较标准的设置,但根据数据集的大小,其他值也可能适用:# Instantiate the grid search
object grid = GridSearchCV(
LogisticRegression(solver='liblinear'),
param_grid,
scoring='accuracy',
cv=5 )
-
-
使用
.fit()
方法在训练数据上训练网格。然后,我们可以出于好奇使用best_params_
属性显示找到的最佳超参数:# Fit and wait
grid.fit(X_train, y_train)
# Print the best set of hyperparameters
print('best hyperparameters:', grid.best_params_)
best hyperparameters: {'C': 0.06, 'penalty': 'l2'}
在这种情况下,最佳超参数是C=0.06
,并使用 L2 惩罚。现在我们可以评估模型。
-
使用
.score()
方法对训练集和测试集上的模型进行评估,该方法计算准确率。直接在网格对象上使用.score()
或.predict()
将自动计算出最佳模型的预测结果:# Print the accuracy score on train and test
print('Accuracy on train set:', grid.score(
X_train, y_train))
print('Accuracy on test set:', grid.score(
X_test, y_test))
Accuracy on train set: 0.9824175824175824
Accuracy on test set: 0.9912280701754386
在这种情况下,这提高了测试集上的性能,尽管这并不总是那么简单。但方法保持不变,可以应用于任何数据集。
另见
网格搜索的官方文档可以在这里找到:scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.xhtml
。
关于 scikit-learn 中可用的求解器和惩罚项的更多信息可以在这里找到:scikit-learn.org/stable/modules/linear_model.xhtml#logistic-regression
。