生物技术和生命科学的机器学习-全-

生物技术和生命科学的机器学习(全)

原文:annas-archive.org/md5/e02c956b4da77baa813f80e8a4077bf6

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

在过去的几年中,机器学习领域发生了重大变化,影响了我们的日常生活和商业决策的方式。生物技术和生命科学行业最丰富的资源之一就是它们源源不断的数据来源。随着我们向更多数据驱动的模型迈进,生命科学和机器学习的交汇点经历了前所未有的增长,揭示了大量信息和隐藏的模式,为公司提供了重大的竞争优势。

在本书的整个过程中,我们将从监督学习和无监督学习的角度探讨机器学习的一些最重要的元素。我们不仅将学习开发和训练稳健的模型,还将使用 AWS 和 GCP 在云端部署它们,使我们能够立即将它们提供给最终用户。

本书面向的对象

本书专门针对希望跨越到数据科学领域的学术和工业界的科学专业人士。无论是个人贡献者还是已经在制药、生命科学和生物技术领域建立起来的管理者,都会发现这本书不仅有用,而且对当前的项目具有极大的适用性。尽管本书提供了 Python 和机器学习的介绍,但为了最大限度地利用本书,建议具备基本的 Python 编程知识和数据科学入门级背景。

本书涵盖的内容

第一章, 介绍生物技术中的机器学习,简要介绍了生物技术领域以及机器学习可以应用的领域,以及本书将使用的一些技术。

第二章, 介绍 Python 和命令行,概述了 Bash 和 Python 编程语言中一些必须掌握的技术和命令,以及一些最常用的 Python 库。

第三章, 开始使用 SQL 和关系数据库,你将学习 SQL 查询语言,并了解如何使用 MySQL 和 AWS RDS 创建远程数据库。

第四章, 使用 Python 可视化数据,介绍了使用 Python 编程语言进行数据可视化和表示的一些最常见方法。

第五章, 理解机器学习,涵盖了标准机器学习流程的一些最重要的元素,介绍了监督学习和无监督方法,以及如何保存模型以供将来使用。

第六章无监督机器学习,您将在这里学习无监督模型,并深入了解与乳腺癌相关的聚类和降维方法。

第七章监督学习机器学习,您将在这里学习监督学习模型,并深入了解分类和回归方法。

第八章理解深度学习,提供了深度学习领域的概述,我们将探讨深度学习模型的元素,以及两个与使用 Keras 进行蛋白质分类和 AWS 进行异常检测相关的教程。

第九章自然语言处理,在探索流行的库和工具的同时,向您介绍一些最常用的 NLP 选项,以及与聚类以及使用 transformers 进行语义搜索相关的两个教程。

第十章探索时间序列分析,通过基于时间的方法来探索数据,其中我们分解时间序列数据集的组成部分,并使用 Prophet 和 LSTMs 开发两个预测模型。

第十一章使用 Flask 应用程序部署模型,介绍了将模型和应用程序部署给最终用户的最受欢迎的框架之一。

第十二章将应用程序部署到云中,介绍了两个最受欢迎的云计算平台,以及三个允许用户将他们的工作部署到 AWS LightSail、GCP AppEngine 和 GitHub 的教程。

要充分利用本书

为了最大限度地发挥您的时间价值,建议您具备基本的 Python 编程语言和 Bash 命令行知识。此外,建议您在生物技术和生命科学领域有一些背景知识,以便最好地理解教程和用例。

如果您使用的是本书的数字版,我们建议您亲自输入代码或从书的 GitHub 仓库(下一节中有一个链接)获取代码。这样做将帮助您避免与代码复制粘贴相关的任何潜在错误。

下载示例代码文件

您可以从 GitHub 在github.com/PacktPublishing/Machine-Learning-in-Biotechnology-and-Life-Sciences下载本书的示例代码文件。如果代码有更新,它将在 GitHub 仓库中更新。

我们还有其他来自我们丰富的图书和视频目录的代码包,可在github.com/PacktPublishing/找到。查看它们吧!

下载彩色图像

我们还提供了一份包含本书中使用的截图和图表彩色图像的 PDF 文件。您可以从这里下载:static.packt-cdn.com/downloads/9781801811910_ColorImages.pdf

使用的约定

本书使用了多种文本约定。

文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“将下载的WebStorm-10*.dmg磁盘映像文件作为系统中的另一个磁盘挂载。”

代码块应如下设置:

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(dfx.drop(columns = ["annotation"]))

当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:

>>> heterogenousList[0]
dichloromethane
>>> heterogenousList[1]
3.14 

任何命令行输入或输出应如下编写:

$ mkdir machine-learning-biotech

粗体:表示新术语、重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词以粗体显示。以下是一个示例:“从管理面板中选择系统信息。”

提示或重要注意事项

它看起来像这样。

联系我们

我们始终欢迎读者的反馈。

customercare@packtpub.com 并在消息的主题中提及书名。

勘误表:尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将非常感激您能向我们报告。请访问 www.packtpub.com/support/errata 并填写表格。

copyright@packt.com 并附有链接到材料。

如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问 authors.packtpub.com

分享您的想法

一旦您阅读了《生物技术及生命科学中的机器学习》,我们很乐意听到您的想法!请点击此处直接进入此书的亚马逊评论页面并分享您的反馈。

您的评论对我们和科技社区都很重要,并将帮助我们确保我们提供高质量的内容。

第一部分:数据入门

本节介绍了 Python、SQL 的基础知识,以及将原始数据转换为有意义的可视化表示作为强大数据科学项目的第一步。新手学生通常会发现互联网或印刷品中大量的数据科学内容让他们感到不知所措。本书通过专注于该领域入门所需的最重要和最有价值的必备元素来解决这个问题。

本节包括以下章节:

  • 第一章, 介绍生物技术领域的机器学习

  • 第二章, 介绍 Python 和命令行

  • 第三章, SQL 和关系型数据库入门

  • 第四章, 使用 Python 可视化数据

第一章:介绍生物技术中的机器学习

我该如何开始? 这是我过去几年作为数据科学家和咨询顾问在科技/生物技术领域运营时,收到频率过高的问题,而且这个问题的答案似乎从来都没有因人而异。我的建议通常是学习Python数据科学,通过在线课程和跟随几个教程来了解事物的工作原理。我发现,我所遇到的绝大多数对学习数据科学感兴趣的科学家和工程师,往往会被互联网上大量可用的资源和文档所淹没。从“Python 入门”课程到“全面机器学习”指南,问“我该如何开始?”的大多数人在他们的旅程开始几天后,往往会感到困惑和失去动力。这对于通常不与代码、算法或预测模型互动的实验室中的科学家或研究人员来说尤其如此。第一次使用终端命令行可能会感到不寻常、不舒服,并在一定程度上对新用户来说是一种恐惧。

这本书的存在就是为了解决这个问题。这是一个一站式商店,为科学家工程师以及所有中间环节的人提供快速高效的指南,帮助他们开始探索数据科学这个美丽的领域。如果你不是程序员,也不打算成为程序员,你可以选择从头到尾阅读这本书,而不需要使用 Python 或任何动手资源。你仍然能够获得坚实的机器学习基础和对其有用能力的理解,以及它能为你的团队带来的价值。如果你是程序员,你可以选择在自己的电脑上跟随教程,完成我们将会覆盖的所有教程。本书中的所有代码都是包容性的、相互关联的,并且设计成可以在你的设备上完全复制的。此外,本书及其相关教程中的所有代码都可在网上方便地获取。我们将完成的教程可以被视为某种程度上的蓝图,因为它们可以被回收并应用于你的数据。所以,根据你对短语“开始”的期望,你将能够有效地使用这本书,无论你的编码意图如何。那么,我们如何计划开始呢?

在本书中,我们将介绍针对科技和生物技术领域常见问题和用例的概念和教程。与许多在线课程和教程不同,本书内容紧密相连,内容精炼,按时间顺序排列,因此为您提供了一种快速高效的方式,让您能够迅速掌握数据科学。在不到 400 页的篇幅内,我们将介绍与 Python、SQL、机器学习、深度学习、自然语言处理和时间序列分析相关的主要概念和思想。我们将涵盖一些流行的方法、最佳实践和每个数据科学家都应该知道的重要信息。除此之外,我们不仅会戴上数据科学家的帽子来训练和开发几个强大的预测模型,还会戴上数据工程师的帽子,使用亚马逊网络服务AWS)和谷歌云平台GCP)将我们的模型部署到云端。无论您是计划将数据科学引入您的当前团队,自己训练和部署模型,还是开始面试数据科学家职位,本书都将为您提供正确的工具和资源,让您开始新的旅程,从第一章开始。在接下来的章节中,我们将介绍一些有趣的主题,以帮助我们开始:

  • 理解生物技术领域

  • 结合生物技术和机器学习

  • 探索机器学习软件

考虑到这一点,让我们来看看生物技术领域内一些适合探索的有趣领域,特别是在机器学习方面。

理解生物技术领域

如其名所示,生物技术可以被视为与生物体或生物系统相关的技术研究领域。这一术语最早由生物技术的“之父”Karoly Ereky 于 1919 年提出,该领域传统上包括生物体在商业目的上的应用。

人类历史上最早的一些生物技术应用包括早在公元前 6,000 年左右发酵啤酒的过程,或者在公元前 4,000 年左右使用酵母制作面包,甚至是在 18 世纪发展最早的病毒疫苗。

在这些例子中,科学或工程过程利用生物实体来生产商品。这一概念在当时是正确的,并且在整个人类历史上一直如此。在整个 20 世纪,人类取得了重大的创新进步,改变了人类的命运。1928 年,亚历山大·弗莱明发现了一种阻止细菌复制的霉菌,从而导致了青霉素——第一种抗生素的诞生。多年后,在 1955 年,乔纳斯·索尔克使用哺乳动物细胞开发了第一种脊髓灰质炎疫苗。最后,在 1975 年,乔治·科勒和塞萨尔·米斯廷开发了一种最早的单克隆抗体开发方法,从而永远改变了医学领域:

图 1.1 – 生物技术历史中一些显著事件的时序图

图 1.1 – 生物技术历史中一些显著事件的时序图

在 20 世纪末和 21 世纪初,生物技术领域扩展到涵盖一系列子领域,包括基因组学、免疫学、药物治疗、医疗设备、诊断仪器等等,从而使其关注点从农业应用转向了人类健康。

生物技术健康领域的成功

在过去的 20 年里,许多改变生活的治疗和产品已经获得 FDA 的批准。该行业的一些最大畅销产品包括用于治疗类风湿性关节炎的单克隆抗体 Enbrel®和 Humira®;用于治疗黑色素瘤和肺癌的人源化抗体 Keytruda®;以及最后,用于治疗自身免疫性疾病和某些类型癌症的单克隆抗体 Rituxan®。这些畅销产品只是过去几十年该领域发生的许多重大进步的样本。这些发展有助于创造一个比地球上许多国家更大的行业,同时改善了数百万患者的生命。

以下是对单克隆抗体的描述:

图 1.2 – 单克隆抗体的 3D 描述

图 1.2 – 单克隆抗体的 3D 描述

今天的生物技术行业正蓬勃发展,为治疗疾病、对抗疾病和保障人类健康带来了许多新的重要进展。然而,随着空间的快速发展,新和独特的发现变得越来越困难。一位伟大的科学家曾告诉我,生物制药行业的进步曾经是由移液管实现的,然后是由自动化仪器实现的。然而,在未来,它们将由人工智能AI)实现。这引出了我们的下一个主题:机器学习

结合生物技术和机器学习

近年来,该领域的科学进步,得益于机器学习和各种预测技术的应用,导致了许多重大成就,例如新疗法的发现、更快更准确的诊断测试、更环保的制造方法等等。在生物技术领域内,机器学习可以应用的领域数不胜数;然而,它们可以被归纳为三个一般类别:

  • 科学和创新:所有与产品研发相关的事物。

  • 商业和运营:所有与将产品推向市场的过程相关的事物。

  • 患者和人类健康:所有与患者健康和消费者相关的事物。

这三个类别本质上是一个产品管线,它始于科学创新,在这里产品被构思,接着是商业和运营,在这里产品被制造、包装和营销,最后是使用这些产品的患者和消费者。在整个本书中,我们将涉及与这三个领域相关的众多机器学习应用,这些应用将在所提供的各种教程中介绍。让我们看看一些与这些领域相关的机器学习应用实例:

图 1.3 – 产品开发中 AI 可应用领域的展示

图 1.3 – 产品开发中 AI 可应用领域的展示

在任何给定产品或治疗的生命周期中,有许多领域可以应用机器学习——唯一的限制是支持新模型开发的数据存在。在科学和创新的范围内,在预测分子性质、生成适合特定治疗目标的分子结构以及甚至为高级诊断进行基因测序方面取得了显著进展。在这些例子中,AI 已经并且继续在帮助和加速新产品和新型产品的研发中发挥作用。在商业和运营的范围内,有许多例子表明 AI 被用来改进诸如智能制造材料以减少浪费、自然语言处理以从科学文献中提取见解,甚至需求预测以改善供应链流程等流程。在这些例子中,AI 在降低成本和提高效率方面至关重要。最后,当涉及到患者和健康时,AI 在招募临床试验参与者、开发旨在避免药物相互作用的推荐引擎或根据患者的症状进行更快诊断方面已被证明是至关重要的。在这些应用中,数据被获取,用于生成模型,然后进行验证。

我们迄今为止观察到的 AI 应用仅是强大预测模型可以应用的领域的一小部分。在数据可用的几乎每个循环过程中,都可以以某种方式、形状或形式准备一个模型。随着我们开始探索在这个过程中各个领域的许多模型的发展,我们需要一些基于软件的工具来帮助我们。

探索机器学习软件

在我们开始开发模型之前,我们需要一些工具来帮助我们。好消息是,无论您使用的是MacPC还是Linux,我们将使用的大部分工具都与所有平台兼容。我们需要安装三个主要项目:用于开发模型的语言、用于存储数据的数据库以及用于部署模型的云计算空间。幸运的是,有一个出色的技术堆栈可以支持我们的需求。我们将使用 Python 编程语言来开发模型,MySQL 来存储数据,AWS 来运行我们的云计算过程。让我们更详细地看看这三项内容。

Python(编程语言)

Python是当今数据科学行业中最常用且最受欢迎的编程语言之一。它最初于 1991 年开发,并被认为是最常用的数据科学语言。对于这本书,我们将使用 Python 3.7。您有几种方法可以在您的计算机上安装 Python。您可以从Python.org以独立的形式安装该语言。这将为您提供最基础的 Python 解释器,您可以在其中运行命令和执行脚本。

使用Anaconda(可以从anaconda.com获取)可以完成一个替代的安装过程,该过程将安装 Python、pip(一个帮助您安装和管理 Python 库的包)以及其他一些有用的。为了尽快在您的计算机上拥有一个可工作的 Python 及其相关库版本,强烈推荐使用 Anaconda。除了 Python,我们还需要安装一些辅助库来帮助我们。将库视为我们可以导入并按需使用的代码的精美包装部分。Anaconda 默认会为我们安装一些重要的库,但还会有其他一些我们需要安装的。我们可以使用 pip 即时安装这些库。我们将在下一章中更详细地探讨这一点。目前,请前往上述网站,下载与您的机器最匹配的安装程序,并按照提供的安装说明在您的计算机上安装 Anaconda。

MySQL(数据库)

在处理大量信息时,我们需要一个地方来存储和保存我们在项目分析和预处理阶段的所有数据。为此,我们将使用 MySQL,这是最常用的关系型数据库之一,用于存储和检索数据。我们将通过使用 SQL 来深入了解 MySQL 的使用。除了 MySQL 关系型数据库,我们还将探索 DynamoDB 的使用,这是一种非关系型且基于 NoSQL 的数据库,近年来获得了相当大的知名度。不用担心现在就设置好这些环境——我们将在稍后讨论如何设置它们。

AWS 和 GCP(云计算)

最后,在用 Python 开发我们的机器学习模型并在我们的数据库中使用数据进行训练之后,我们将使用Amazon Web ServicesAWS)和Google Cloud PlatformGCP)将我们的模型部署到云端。除了部署我们的模型,我们还将探索许多有用的工具和资源,例如 Sagemaker、EC2 和 AutoPilot(AWS),以及 Notebooks、App Engine 和 AutoML(GCP)。

摘要

在本章中,我们对生物技术领域有了快速的了解。首先,我们查看了一些与该领域相关的历史事实,以及该领域是如何被重塑成今天的样子的一些方式。然后,我们探索了生物技术领域内受机器学习和人工智能影响最大的领域。最后,我们探索了一些在进入该领域时你需要了解的最常见和基础的机器学习软件。

在整本书中,Python 和 SQL 将是我们开发所有模型的主要语言。我们不仅将介绍如何安装每个要求的详细说明,而且我们将在书中的许多示例和教程中获得实践经验。鉴于它们在数据科学家中的普遍性和受欢迎程度,AWS 和 GCP 将成为我们部署所有模型的主要云平台。

在下一章中,我们将介绍 Python 命令行。考虑到这一点,让我们开始吧!

第二章:介绍 Python 和命令行

当你走进一家咖啡馆时,你几乎会立刻注意到三种类型的人:那些与他人社交的人,那些在项目上工作的人,以及那些编码的人。编码者可以通过他们电脑屏幕上的黑色背景和白色字母轻松辨认出来——这被称为命令行。对于许多人来说,命令行可能看起来凶猛且令人畏惧,但对于其他人来说,这是一种生活方式。

进行任何类型的数据科学项目的一个重要部分是能够有效地通过终端命令行导航目录和执行命令。命令行允许用户以高效和简洁的方式查找文件、安装库、定位包、访问数据以及执行命令。本章绝对不是命令行所有功能的全面概述,但它确实涵盖了一个数据科学家应该知道的必要命令列表。

在本章中,我们将涵盖以下具体主题:

  • 介绍命令行

  • 探索 Python 语言

  • 指南 – Python 入门

  • 指南 – 使用 Rdkit 和 BioPython

技术要求

在本章中,我们将使用macOS应用程序文件夹,或者在Windows PC的启动菜单中找到的命令提示符。尽管这两个在功能上等效,但某些命令背后的语法会有所不同。如果你使用的是 PC,我们鼓励你从其网站下载Git for Windows (git-scm.com/download/win),这将允许你使用Bash命令行来跟随。当我们开始在命令行中编辑文件时,我们需要一个名为Vim的编辑器。大多数 Mac 用户将预装 Vim。我们鼓励 PC 用户从其网站(www.vim.org/download.php)下载 Vim。

此外,我们还将使用Anaconda发行版来探索 Python。我们将在不久的将来介绍如何在你的系统上下载它。安装 Anaconda 的过程对于 Mac 和 PC 用户几乎相同,Python 代码的执行也几乎相同。

在本书的整个过程中,你看到的代码也将可在GitHub上找到。我们可以将 GitHub 视为代码可以生存的空间,使我们能够维护版本控制、进行编辑,并与他人分享我们的工作。随着你跟随本章的内容,我们鼓励你参考相关的 GitHub 仓库,该仓库可在github.com/PacktPublishing/Machine-Learning-in-Biotechnology-and-Life-Sciences找到。

介绍命令行

命令行在MacPCLinux上都是可用的。虽然以下示例是在 Mac 上执行的,但非常类似的功能也适用于 PC,但语法略有不同。

您可以通过打开命令行(称为 @ 符号)开始此过程。让我们看看一些基本命令。

为了识别您当前(工作)目录的路径,您可以使用 pwd 命令:

  $ pwd

这将返回您当前所在的精确目录。在我的系统中,返回的路径如下:

Users/alkhalifas

为了识别此特定目录内的内容,您可以使用 ls 命令,它将返回目录和文件的列表:

  $ ls

您可以在命令行中使用 mkdir 命令(后跟您希望创建的目录名称)在命令行内创建新目录。例如,您可以使用以下命令创建 machine-learning-practice 目录:

  $ mkdir machine-learning-biotech

如果您再次使用 ls 命令列出此目录的内容,新目录将出现在该列表中。您可以使用 cd(即 change directory)命令导航到该目录:

  $ cd machine-learning-practice

您还可以再次使用 pwd 命令来检查您的新路径:

  $ pwd
Users/alkhalifas/machine-learning-biotech

为了返回上一个目录,您可以使用 cd 命令,后跟一个空格和两个点(..):

  $ cd ..

重要提示

值得注意的是,根据您使用的命令行,目录名称可能区分大小写。例如,输入 Downloads 而不是 downloads 可能会被解释为不同的位置,因此这可能会返回错误。在命名文件和目录时保持一致性将是您在命令行中成功的关键。

创建和运行 Python 脚本

现在您已经学到了一些基础知识,让我们继续使用命令行创建我们的第一个 Python 应用程序。我们可以使用 vim 命令(后跟您希望创建和编辑的文件名称)创建和编辑新文件:

  $ vim myscript.py

这将在您的命令行 Vim 编辑器中打开一个空文件,您可以在其中编写或粘贴代码。默认情况下,您将处于 view 模式。您可以通过按键盘上的 I 键切换到 edit 模式。您会注意到 Vim 窗口底部的状态已更改为 - - INSERT - -,这意味着您现在可以向此文件添加代码:

图 2.1 – Vim 窗口

图 2.1 – Vim 窗口

将以下几行 Python 代码(或复制并粘贴)输入到文件中,然后按 Esc 键。您会注意到状态不再是 :wq 并按 Enterw 键将写入文件,而 q 键将退出编辑器。有关其他 Vim 命令的更多详细信息,请参阅 Vim 网站 www.vim.org/

# myscript.py
import datetime
now = datetime.datetime.now()
print("Hello Biotech World")
print ("The current date and time is:")
print (now.strftime("%Y-%m-%d %H:%M:%S"))

到此为止,你已经编写了你的第一个 Python 脚本。我们可以继续使用你之前安装的 Python 解释器来执行这个脚本。在我们这样做之前,让我们谈谈这个脚本将做什么。从第一行开始,我们将导入一个名为datetime的库,这将允许我们确定系统的当前日期和时间。接下来,我们将datetime对象分配给一个我们将称之为now的变量。我们将在下一节讨论对象和变量,但在此期间,可以将它们视为可以填充值(如日期或数字)的变量。最后,我们将打印一个短语Hello Biotech World!,然后是当前时间的声明。

让我们试一试:

  $ python3 myscript.py

执行此文件后,以下结果将出现在你的屏幕上:

Hello Biotech World!
The current date and time is:
2021-05-23 18:40:21

在这个例子中,我们使用了一个名为datetime的库,这个库是在你安装 Anaconda 发行版时默认安装的。还安装了许多其他库,但没有安装的更多。随着我们通过这些项目,我们将使用许多这些其他库,我们可以使用pip来安装。

之前的例子运行没有任何错误。然而,在编程中这种情况很少见。有时候,一个遗漏的句号或未关闭的括号会导致错误。在其他情况下,程序可能会无限期地运行——可能是在你不知情的情况下在后台运行。通常关闭终端命令行会停止正在运行的应用程序。然而,有时关闭命令行窗口并不是一个选择。要识别后台运行的过程,你可以使用ps(即process)命令:

  $ ps -ef

这将显示所有正在运行的过程列表。第一列是UID(用户 ID),接着是PID(进程 ID)列。再往右几列,你可以看到当前活跃和正在运行的特定文件名(如果有)。你可以使用grep命令来缩小列表,以找到所有与 Python 相关的:

  $ ps -ef | grep python

如果一个 Python 脚本(例如,someScript.py)在后台持续运行,你可以很容易地使用grep命令确定进程 ID,这意味着你可以随后使用pkill命令来结束该进程:

  $ pkill -9 -f someScript.py

这将终止脚本并释放你的计算机内存以供其他任务使用。

使用 pip 安装包

管理 Python 库的最佳资源之一是sklearn,我们可以直接在终端命令行使用pip install命令来安装它:

  $ pip install sklearn

软件包管理器将打印一些反馈消息,提醒你安装的状态。在某些情况下,安装将成功,在其他情况下,可能不会。你在这里收到的反馈将有助于确定是否需要采取下一步行动。

将会有一些情况,其中一个库需要另一个库才能运行——这被称为pip将自动为你处理依赖关系,但这并不总是如此。

要识别库的依赖关系,你可以使用pip show命令:

  $ pip show sklearn

命令行将随后打印出与给定库相关的名称、版本、URL 以及许多其他属性。在某些情况下,显示的版本可能是过时的,或者根本不是你需要的版本。你可以再次使用pip install命令来更新库到较新版本,或者通过在库名称后指定来选择特定版本:

  $ pip install sklearn==0.15.2

随着你安装的包的数量开始增长,记住它们的名称和相关版本将变得越来越困难。为了生成给定环境中的包列表,你可以使用pip freeze命令:

  $ pip freeze > requirements.txt

此命令将冻结一系列库及其相关版本,并将它们写入一个名为requirements.txt的文件。当将代码从一个计算机迁移到另一个计算机时,这种做法在团队中很常见。

当事情不按预期进行时…

通常,代码会失败,命令会出错,会出现一些问题,而解决方案可能不会立即找到。不要让这些情况让你气馁。当你开始探索命令行、Python 以及大多数其他基于代码的尝试时,你可能会遇到一些你无法解决的错误和问题。然而,很可能其他人已经解决了你的问题。用于搜索和诊断代码相关问题的最佳资源之一是Stack Overflow——一个主要面向个人和公司,用于提问和寻找所有类型代码相关问题的解决方案的协作和知识共享平台。强烈建议你利用这个美好的资源。

现在我们已经对命令行及其无限的能力有了很好的了解,让我们开始更详细地探索 Python。

探索 Python 语言

世界上存在许多不同的计算机语言。PythonRSQLJavaJavaScriptC++CC#只是其中的一些例子。尽管这些语言在语法和应用方面各不相同,但它们可以被分为两大类:低级高级语言。低级语言——如 C 和 C++——是在机器级别运行的计算机语言。它们关注非常具体的事情,例如将位从一个位置移动到另一个位置。另一方面,高级语言——如 R 和 Python——关注更抽象的过程,例如对一个列表中的数字进行平方。它们完全不考虑机器级别发生的事情。

在我们更详细地讨论 Python 之前,让我们先谈谈编译程序的概念。大多数程序——例如用 C++和 Java 编写的程序——都需要一种称为编译器的东西。将编译器想象成一种软件,它在程序启动或执行之前立即将人类可读的代码转换为机器可读的代码。虽然大多数语言都需要编译器,但像 Python 这样的语言则不需要。Python 需要一种称为解释器的东西,它在结构上本质上与编译器相似,但它会立即执行命令而不是将它们转换为机器可读的代码。考虑到这一点,我们将 Python 定义为一种高级通用解释型编程语言。Python 常用于统计分析、机器学习应用,甚至游戏和网站开发。由于 Python 是一种解释型语言,它可以在 IDE 中使用,也可以直接在终端命令行中使用:

  $ python

接下来,我们将讨论集成开发环境(IDEs)。

选择 IDE

Python 代码可以通过几种不同的方式准备。例如,我们可以通过在终端命令行中使用 Vim 文本编辑器来准备它,如前一小节所示。虽然这种方法相当有效,但在结构化文件、组织目录和执行代码时,您通常会遇到冗余。或者,大多数数据科学家默认使用更图形化的编辑器,称为集成开发环境IDEs)。有许多免费且可下载的 IDE,例如SpyderPyCharmVisual StudioJupyter Lab

每个 IDE 都有其各自的优缺点,这些优缺点高度依赖于用户的用例和工作流程。大多数新数据科学家在开始时通常默认使用Jupyter Notebook和/或Jupyter Lab。为了本书的目的,所有代码都将使用 Jupyter Notebook 准备和共享。假设在上一章中正确遵循了 Anaconda 的安装说明,Jupyter Notebook 应该已经安装在本地的计算机上。您可以通过打开Anaconda Navigator并选择Jupyter Notebook来启动应用程序:

图 2.2 – Anaconda Navigator 窗口

图 2.2 – Anaconda Navigator 窗口

或者,您也可以通过在终端命令行中输入以下命令来启动 Jupyter Notebook 应用程序:

  $ jupyter notebook

按下回车键后,之前看到的相同的 Jupyter Notebook 应用程序应该会出现在您的屏幕上。这仅仅是一种打开 Jupyter Notebook 的更快方法。

数据类型

Python 能够处理许多不同类型的数据。这些通常可以分为两大类:原始数据类型集合,如下面的图所示:

图 2.3 – 显示 Python 数据类型的图

图 2.3 – 显示 Python 数据类型的图表

第一种数据类型类别是原始值。正如其名所示,这些数据类型是 Python 中最基本的构建块。以下是一些例子:

图 2.4 – 原始数据类型的表格

图 2.4 – 原始数据类型的表格

第二种数据类型类别是集合。集合是由一个或多个原始值组合而成的。每种集合类型都有与之相关的特定属性,在某些条件下产生不同的优势和劣势。以下是一些例子:

图 2.5 – 显示不同数据类型集合的表格

图 2.5 – 显示不同数据类型集合的表格

作为科学家,我们自然会倾向于尽可能好地组织信息。我们之前根据它们的原始和集合性质对数据类型进行了分类。然而,我们也可以根据一个称为可变性的概念来分类数据类型。可变性也可以被理解为可删除性。变量,例如表示列表的变量,持有该类型的一个实例。当对象被创建或实例化时,它会被分配一个唯一的 ID。通常,在运行时定义后,该对象的类型不能更改,但是,如果它被认为是可变的,则可以更改。整数、浮点数和布尔值等对象被认为是不可变的,因此创建后不能更改。另一方面,列表、字典和集合等对象是可变对象,可以更改。因此,它们被认为是可变的,正如您可以从图 2.6中看到的那样:

图 2.6 – 根据可变性分类的 Python 数据类型

图 2.6 – 根据可变性分类的 Python 数据类型

现在我们已经了解了一些基础知识,让我们探索 Python 语言的一些更令人兴奋的领域。

教程 – Python 入门

Python 是一种广泛的语言,任何试图在 10 页以下总结其功能的尝试都会有限制。虽然这本书的目的不是作为 Python 的全面指南,但我们将讨论许多数据科学家应该了解的必知命令和能力。我们将看到这些命令中的大多数将在接下来的教程中出现。

创建变量

Python 的一个核心概念是变量的概念。+)或减法(-)可以与变量结合使用来创建5将被分配给x变量,然后,10的值将被分配给y变量。现在代表数值的两个变量(xy),可以创建一个名为z的变量来表示xy的和:

     $ python
  >>> x = 5
  >>> y = 10
  >>> z = x + y
  >>> print(z)
  15

变量可以采用许多数据类型。除了前面代码中显示的整数值之外,变量还可以被分配字符串、浮点数,甚至是布尔值:

  >>> x = "biotechnology"
  >>> x = 3.14159
  >>> x = True

可以使用type()函数确定变量的具体数据类型:

  >>> x = 55
  >>> type(x)
      int

在 Python 中,数据类型不需要显式声明(与 C++或 Java 等语言不同)。实际上,Python 还允许变量被转换为其他类型。例如,我们可以将一个整数转换为字符串:

  >>> x = 55
  >>> x = str(x)
  >>> type(x)
      str

我们可以从返回的结果中看到,数据现在已经是字符串类型了!

导入已安装的库

安装完库后,你可以使用import函数将库导入到你的 Python 脚本或 Jupyter Notebook 中。你可以以下面的方式整体导入库:

>>> import statistics

或者,我们可以显式地从库中导入所有类:

>>> from statistics import *

导入任何库的最佳方式是只导入你计划使用的类。我们可以以statistics库为例:

>>> from statistics import mean

随着你进一步进入数据科学领域,安装和导入库将变得习以为常。下表展示了任何新数据科学家都应该了解的一些最常见且最有用的库。尽管并非所有这些库都会在本书的范围内被涵盖,但了解它们是有用的。

图 2.7 – 一个显示一些最常见 Python 库的表格

图 2.7 – 一个显示一些最常见 Python 库的表格

上表中包含的库是在你开始数据科学之旅时可能会遇到的一些最常见的库。在下一节中,我们将重点关注math库来进行一些计算。

一般计算

在 Python 语言中,我们可以创建变量并赋予它们特定的值,正如我们之前观察到的。随后,我们可以使用这些变量中的值来形成表达式并进行数学计算。例如,考虑常用的阿伦尼乌斯方程,它常用于预测分子稳定性和计算反应速率的温度依赖性。这个方程在研发中主要用于两个目的:

  • 优化合成制造过程中的反应条件以最大化产量

  • 根据温度和湿度的变化预测片剂和药丸的长期稳定性

该方程可以表示如下:

在这种情况下,k 是速率常数,A 是频率因子,EA 是活化能,R 是理想气体常数,T 是温度(开尔文K)。我们可以使用这个方程来计算温度变化如何影响速率常数。假设当前的需求是预测如果温度从 293 K 变化到 303 K 会发生什么。首先,我们需要定义一些变量:

     >>> from math import exp
     >>> EA = 50000
     >>> R = 8.31
     >>> T1 = 293

     >>> exp(-EA / (R*T1))
  1.2067e-09

现在,我们可以重新分配温度变量的另一个值并重新计算:

     >>> T2 = 303
     >>> exp(-EA / (R*T2))
  2.3766e-09

总之,这表明温度的简单变化几乎将分数翻倍!

列表和字典

列表字典是 Python 中最常见和最基本的数据类型。列表只是元素的有序集合(类似于数组),可以包含相同类型或不同类型的元素:

   >>> homogenousList = ["toluene", "methanol", "ethanol"]
  >>> heterogenousList = ["dichloromethane", 3.14, True]

可以使用len()函数捕获任何给定列表的长度:

  >>> len(heterogenousList)
  3

可以使用索引位置检索列表元素。记住,Python 中的所有索引都是从0开始的,因此,这个列表的第一个元素位于0索引处:

  >>> heterogenousList[0]
  dichloromethane
  >>> heterogenousList[1]
  3.14

与它们的原始对应物不同,列表是可变的,因为它们在创建后可以被修改。我们可以使用append()函数向列表中添加另一个元素:

  >>> len(homogenousList)
  3
  >>> homogenousList.append("acetonitrile")
  >>> len(homogenousList)
  4

另一方面,字典通常用于它们的的关联。给定一个字典,你可以指定一个的名称及其相应的。例如,一个包含化学名称和它们的有效期的化学清单在标准的 Python 列表中工作得并不好。化学名称和它们的日期在这个格式中很难关联在一起。

然而,使用字典来建立这种关联是完美的:

  >>> singleChemical = {"name" : "acetonitrile",
          "exp_date" : "5/26/2021"}

这本词典现在代表了一个单一的化学元素,在这个意义上,有一个键分配给它作为名称,另一个键分配给它作为有效期。你可以通过指定键来检索字典中的特定值:

>>> singleChemical["name"]
  acetonitrile

要构建一个完整的化学清单,你需要为每个化学物质创建多个字典,并将它们全部添加到一个列表中。这种格式被称为JSON,我们将在本章的后面更详细地探讨。

数组

Python 中的数组与列表类似,因为它们可以包含不同类型的元素,可以有多个重复项,并且可以随时间改变和变异。数组可以通过简单的函数轻松扩展、追加、清除、复制、计数、索引、反转或排序。以下是一个例子:

  1. 让我们继续使用numpy创建一个数组:

    import numpy as np
    newArray = np.array([1,2,3,4,5,6,7,8,9,10])
    
  2. 你可以使用append()函数向列表末尾添加另一个元素:

      >>> newArray= np.append(newArray,25)
      >>> newArray
       [1,2,3,4,5,6,7,8,9,10,25]
    
  3. 可以使用len()函数确定数组的长度:

      >>> len(newArray)
       11
    
  4. 数组也可以使用方括号进行切片,并分配给新的变量。例如,以下代码取列表的前五个元素:

      >>> firstHalf = newArray[:5]
      >>> firstHalf
       [1,2,3,4,5]
    

现在我们已经掌握了 Python 的一些基础知识,让我们深入探讨更复杂的话题——函数

创建函数

Python 中的函数是一种组织代码和隔离过程的方式,允许你定义明确的输入和明确的输出。以一个平方数字的函数为例:

def squaring_function(x):
  # A function that squares the input
  return x * x

函数是一等的,因为它们可以被分配给变量或随后传递给其他函数:

  >>> num = squaring_function(5)
  >>> print(num)
  25

根据它们的目的,函数可以有多个输入和输出。通常认为一个函数应该只服务于一个特定的目的,而不再多。

迭代和循环

将会有许多任务必须以重复或迭代的方式进行。在先前的例子中,一个值被平方了,但是如果有 10 个值需要被平方怎么办?你可以手动重复运行这个函数,或者使用for循环和while循环来迭代。for循环通常用于已知迭代次数的情况。另一方面,while循环通常用于需要根据给定条件中断循环的情况。让我们来看一个for循环的例子:

input_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
output_list = []
for val in input_list:
    squared_val = squaring_function(val)
    output_list.append(squared_val)
    print(val, " squared is: ", squared_val)

首先,定义一个值列表。然后创建一个空列表——平方值将被写入其中。我们然后遍历这个列表,平方值,将其追加(添加)到新列表中,然后打印值。虽然for循环在迭代方面很棒,但在处理大型数据集的情况下,它们在某些情况下可能会非常慢。

然而,while循环也可以用于各种类型的迭代,特别是在迭代需要在满足条件时停止时。让我们来看一个while循环的例子:

current_val = 0
while current_val < 10:
    print(current_val)
    current_val += 1

现在我们已经对循环及其使用有了更深入的了解,让我们探索一种更高级的迭代形式,称为列表推导

列表推导

for循环一样,列表推导允许使用一行强大的代码来迭代一个过程。我们可以使用这行代码来复制之前平方值的例子:

   >>> my_squared_list = [squaring_function(val) for val in  input_list]

有三个主要的原因说明你应该使用列表推导:

  • 它可以将几行代码缩减为单行,使你的代码更加整洁。

  • 它可能比其for循环版本快得多。

  • 这是一个关于编写高效代码的极好面试问题。提示提示。

DataFrames

pandas库可以说是 Python 数据科学空间中最常见的对象之一。DataFrames 类似于结构化表格(例如,可以将DataFrame对象构建如下:

>>> import pandas as pd
>>> 
 df = pd.DataFrame([[1,2,3],[4,5,6],[7,8,9]],columns = ['col1','col2', 'col3'])
>>> print(df)

这将给出以下输出:

图 2.8 – 显示 DataFrame 对象结果的表格

图 2.8 – 显示 DataFrame 对象结果的表格

DataFrame对象内部几乎每一个参数都可以被改变和调整以适应其中的数据。例如,列可以被重新标记为全词:

>>> df.columns = ["ColumnA", "ColumnB", "ColumnC"]

可以创建新的列来表示数学函数的输出。例如,可以准备一个表示ColumnC平方值的列:

>>> df["ColumnC_Squared"] = df["ColumnC"] ** 2
>>> print(df)

这个输出的结果如下:

图 2.9 – 显示 DataFrame 对象结果的表格

图 2.9 – 显示 DataFrame 对象结果的表格

或者,可以使用您本地机器上的现有 CSV 文件来准备 DataFrames。这可以通过使用read_csv()函数来完成:

  >>> import pandas as pd
  >>> df = pd.read_csv('dataset_lipophilicity_sd.csv')

而不是导入整个数据集,可以选择特定的列集:

  >>> df = df[["ID", "TPSA", "MolWt", "LogP"]]
  >>> df.head()

这个输出的结果如下:

Figure 2.10 – 展示 DataFrame 对象结果的表格

Figure 2.10 – 展示 DataFrame 对象结果的表格

或者,也可以使用 tail() 函数来查看数据的最后几行:

  >>> df.tail()

DataFrames 是 Python 中最常见的数据处理和展示形式之一,因为它们类似于大多数人熟悉的标准的 2D 表格。处理大量数据的一个更有效的方法是使用 PySpark 库。

现在我们能够在我们的机器上本地管理和处理数据,让我们看看如何使用 API 请求 从外部源检索数据。

API 请求和 JSON

在某些情况下,数据可能不会存储在您的计算机本地,您需要从远程位置检索它。发送和接收数据最常见的方式之一是以 应用程序编程接口API)的形式。API 的主要思想是使用 HTTP 请求来获取数据,通常以 JSON 格式进行通信。让我们来看一个例子:

import requests
r = requests.get('https://raw.githubusercontent.com/alkhalifas/node-api-books/master/services/books.json')
data = r.json()

将 JSON 视为一个字典列表,其中每个字典是一个元素。我们可以根据它们的索引位置选择列表中的特定元素。在 Python 中,我们从 0 开始计数,因此,我们字典列表中的第一个元素将具有索引位置 0

  >>> data[0]

这给我们以下结果:

Figure 2.11 – 来自 HTTP 请求的结果样本

Figure 2.11 – 来自 HTTP 请求的结果样本

可以使用相应的 访问字典中的

  >>> data[0]["type"]
  HARD_COVER

与 CSV 文件类似,JSON 文件也可以使用 read_json() 函数导入到 DataFrame 中。

解析 PDF

与我们导入 Python 的许多结构化数据形式不同,例如 CSV 和 JSON 文件,您通常会遇到非结构化的数据形式——例如,文本文件或 PDF 文件。对于大多数使用 tika 的应用程序——开源社区中最受欢迎的一个——我们可以通过使用 pip 安装库来开始:

  alkhalifas@titanium ~ % pip install tika

然后,我们可以继续读取感兴趣的特定 PDF 文件:

  from tika import parser
   raw = parser.from_file("./datasets/COVID19-CDC.pdf")
   print(raw['content'])

raw['content'] 中的数据将是 tika 库解析的 PDF 文件文本。这些数据现在可以用于后续的自然语言处理(NLP)应用程序中的预处理和使用。

Pickling 文件

我们迄今为止处理的大多数文档都是通常保存在您计算机上的文件,例如 PDF、CSV 和 JSON 文件。那么,我们如何保存 Python 对象呢?如果您有一个重要的项目列表需要保存,比如我们之前的例子中的化学品,您需要一种方法将这些文件本地保存以便以后使用。为此,大多数数据科学家使用picklepickle库允许您将 Python 对象保存和存储为.pkl文件,以便以后在 Python 中使用。这些文件可以稍后导入到 Python 中,用于新任务。这是一个在 Python 中序列化和反序列化对象的过程。让我们看看使用.pkl文件的一个例子。我们首先导入pickle库,然后创建一个项目列表:

>>> import pickle
>>> cell_lines = ["COS", "MDCK", "L6", "HeLa", "H1", "H9"]

为了将列表保存为.pkl文件,我们需要指定文件保存的位置。请注意,我们将使用wb模式(即写二进制模式)。然后我们将使用dump()函数来保存内容:

>>> pickledList = open('./tmp/cellLineList.pkl', 'wb')
>>> pickle.dump(cell_lines, pickledList)

不论文件是本地保存还是与同事共享,都可以使用类似的方式通过 Python 中的load()命令将其重新加载:

>>> pickledList = open('./tmp/cellLineList.pkl', 'rb')
>>> cell_lines_loaded = pickle.load(pickledList)
>>> print(cell_lines_loaded)
["COS", "MDCK", "L6", "HeLa", "H1", "H9"]

注意,在前面的例子中,我们根据任务在两个参数之间切换 – wb(写二进制)和rb(读二进制) – 这些是可以选择的两种加载和保存文件的模式。还有许多其他选项可以使用。这里应该注意的主要区别是二进制格式的使用。在 Windows 上,以二进制模式打开文件将解决文本文件中的换行符,这在ASCII文件中很常见。以下表格概述了一些最常见的模式:

图 2.12 – 一个显示最常见的读写模式的表格

图 2.12 – 一个显示最常见的读写模式的表格

现在我们已经对 API 和我们可以对数据进行操作有了基本的理解,让我们看看面向对象编程(OOP)在 Python 中的应用。

面向对象编程

与许多其他语言(如 C++、Java 和 C#)类似,面向对象的概念也可以在 Python 中使用。在面向对象编程中,主要目的是使用来组织和封装数据对象及其相关函数。

让我们探讨一个在化学库存管理背景下的面向对象编程(OOP)的例子。大多数现代生物技术公司都有广泛的库存系统,用于监控他们内部的化学品库存。库存系统允许公司确保供应不会耗尽,并且到期日期得到充分监控,以及许多其他任务。现在,我们将利用我们对 Python 的了解以及面向对象的概念来构建一个库存管理系统:

  1. 我们首先导入我们将需要来管理我们日期的两个库:

    import datetime
    from dateutil import parser
    
  2. 接下来,我们使用以下语法为类定义一个名称:

    class Chemical:
    
  3. 然后,我们构建了一个称为构造函数的代码片段:

    class Chemical:
            def __init__(self, name, symbol, exp_date, count):
                self.name = name
                self.symbol = symbol
                self.exp_date = exp_date
                self.count = count
    

    __init__ 函数的目的是初始化或创建此对象,我们使用 self 来引用该特定类的实例。例如,如果我们创建了两个化学对象,self.name 对于一个对象可能是 acetonitrile,而对于另一个对象则是 methanol

  4. 接下来,我们可以定义一些与我们的类相关的函数。这些函数是类特定的,并且以某种方式与类相关联,使得它们只能通过它来访问。我们将这些函数称为 self 作为参数,以将函数绑定到感兴趣的特定实例。在以下示例中,我们将创建一个 isExpired() 函数,该函数将读取化学品的过期日期,并在过期时返回 True 值。我们首先确定今天的日期,然后使用 self.exp_date 参数检索对象的日期。然后,我们返回一个布尔值,它是两个日期比较的结果的乘积:

            def isExpired(self):
                todays_date = datetime.datetime.today()
                exp_date = parser.parse(self.exp_date)
                return todays_date > exp_date
    
  5. 我们可以通过使用 Chemical 类创建一个新的对象来测试这一点:

    >>> chem1 = Chemical(name="Toluene", symbol="TOL", exp_date="2019-05-20", count = 5)
    
  6. 有了这些,我们已经构建了一个我们称之为 chem1 的化学对象。我们可以通过指定字段的名称来检索 chem1 的字段或属性:

    name field was not followed by parentheses in the way we have previously seen. This is because name is only a field that is associated with the class and not a function. 
    
  7. 我们可以通过类似的方式指定来使用我们的函数:

    self argument.
    
  8. 我们可以创建我们类的多个实例,每个实例包含不同的日期:

    >>> chem2 = Chemical(name="Toluene", symbol="TOL", exp_date="2021-11-25", count = 4)
    >>> chem3 = Chemical(name="Dichloromethane", symbol="DCM", exp_date="2020-05-13", count = 12)
    >>> chem4 = Chemical(name="Methanol", symbol="MET", exp_date="2021-01-13", count = 5)
    

    每个这些对象在跟随它们的字段或函数时都会返回各自的值。

  9. 我们还可以创建函数来总结对象特定实例中的数据:

            def summarizer(self):
                print("The chemical", self.name, "with the symbol (",self.symbol,") has the expiration date", self.exp_date)
    
  10. 然后,我们可以调用任何我们创建的化学对象的 summarizer() 函数,以检索其状态的易读摘要:

    >>> print(chem1.summarizer())
    The chemical Toluene with the symbol ( TOL ) has the expiration date 2019-05-20
    
  11. 我们迄今为止编写的函数没有接受任何额外的参数,只是为我们检索数据。化学库存系统通常需要更新以反映已过期或被消耗的项目,从而改变计数。函数也可以用来更改或修改对象内的数据:

            def setCount(self, value):
                self.count = value
    
  12. 我们可以简单地添加 value 作为参数来设置该实例的计数(由 self.count 表示)为相应的值。我们可以通过我们的一个对象来测试这一点:

    >>> chem1 = Chemical(name="Toluene", symbol="TOL", exp_date="2019-05-20", count = 5)
    >>> chem1.count
      5
    >>> chem1.setCount(25)
    >>> chem1.count
    25
    

OOP(面向对象编程)有许多其他用途、应用和模式,这些模式超出了我们刚刚看到的示例。例如,库存系统不仅需要维护其库存,还需要管理每个项目的过期日期,记录销售详情,并具有编译和报告这些指标的方法和函数。如果您对 Python 中类的开发感兴趣,请访问官方 Python 文档以了解更多信息(docs.python.org/3/tutorial/classes.html)。

教程 – 使用 Rdkit 和 BioPython

在之前的教程中,我们看到了如何使用 Python 来计算属性、组织数据、解析文件以及更多示例。除了我们迄今为止使用的库之外,在生物技术和生命科学领域操作时,我们还需要特别注意两个库:RdkitBioPython。在接下来的章节中,我们将探讨这些包中许多可用功能的几个示例。考虑到这一点,让我们开始吧!

与小分子和 Rdkit 一起工作

数据科学家在处理小分子相关数据时最常用的包之一是名为rdkit的包,它包含了许多不同的工具和能力,以至于我们需要另一本完全不同的书来全面覆盖。以下列出了该包通常被认为是五个最常见的应用,如图 2.13所示:

图 2.13 – rdkit 包中的主要功能

图 2.13 – rdkit 包中的主要功能

让我们举例说明这些功能,以便我们熟悉rdkit包。

与 SMILES 表示一起工作

与我们之前看到的某些包类似,rdkit是按类组织的。现在让我们利用Chem类通过几个简单的步骤加载SMILES表示。

我们将首先导入Chem类:

from rdkit import Chem 

将 2D 分子结构从一个 Python 脚本传输到另一个最简单和最常见的方法是使用SMILES表示。例如,我们可以将SMILES表示描述如下:

SMILES = "[Br-].[Br-].CCCCCCCCCCC[N+]1=CC=C(CCCC2=CC=N+C=C2)C=C1" 

我们可以使用Chem类中的MolFromSmiles函数将我们的SMILES表示加载到rdkit中:

molecule = Chem.MolFromSmiles(SMILES) 
molecule 

当打印我们分配的分子变量时,将返回如图 2.14 所示的分子图示:

图 2.14 – 使用 rdkit 的 QAC 的 2D 表示

图 2.14 – 使用 rdkit 的 QAC 的 2D 表示

注意,我们不需要任何额外的包来打印这个图,因为rdkit非常全面,包含了运行这些可视化所需的一切。在下一节中,我们将看到rdkit在相似性计算方面的另一个示例。

结构现在已加载,可以进行许多不同的应用和计算。在这里最常见的方法之一是在分子内搜索子结构。我们可以通过使用rdkit中的MolFromSmarts函数来完成这项任务:

tail_pattern = Chem.MolFromSmarts('CCCCCCCCCCC') 
patter 

执行此操作后,我们将得到以下图示,显示了感兴趣的子结构:

图 2.15 – 一个感兴趣的 2D 子结构

图 2.15 – 一个感兴趣的 2D 子结构

在加载了主要分子和模式之后,我们可以使用HasSubstructMatch函数来确定子结构是否存在:

molecule.HasSubstructMatch(tail_pattern) 

执行此代码后,将返回 True 的值,表示该结构确实存在。另一方面,如果运行另一个子结构,例如苯酚,返回的值将是 False,因为该子结构不存在于主分子中。

此外,还可以使用 rdkit 中的 DataStructs 类来运行相似度计算。我们可以从导入该类并输入两个感兴趣的分子开始:

from rdkit import DataStructs 
from rdkit.Chem import Draw 
mol_sim = [Chem.MolFromSmiles('[Br-].[Br-].CCCCCC[N+]1=CC=C(CCCC2=CC=N+C=C2)C=C1'), Chem.MolFromSmiles('[Br-].[Br-].CCCCCCCCCCC[N+]1=CC=C(CCCC2=CC=N+C=C2)C=C1')] 

如果我们使用之前提到的 MolFromSmiles 方法从视觉上比较这两个分子,我们可以看到两个结构之间存在细微的差异,即其中一个分子的疏水性尾部存在双键。

接下来,我们可以使用 RDKFingerprint 函数来计算指纹:

fps = [Chem.RDKFingerprint(x) for x in mol_sim] 

最后,我们可以使用 CosineSimilarity 指标来计算两个结构之间的差异:

DataStructs.FingerprintSimilarity(fps[0],fps[1], metric=DataStructs.CosineSimilarity) 

此计算将产生大约 99.14% 的值,表明结构基本上是相同的,除了存在一个细微的差异。

摘要

Python 是一种强大的语言,无论您的专业领域如何,它都会为您服务得很好。在本章中,我们讨论了与命令行工作相关的一些重要概念,例如创建目录、安装包、创建和编辑 Python 脚本。我们还广泛地讨论了 Python 编程语言。我们回顾了一些最常用的 IDE、通用数据类型和计算。我们还回顾了一些更复杂的数据类型,如列表、DataFrames 和 JSON 文件。我们还了解了 API 的基础知识以及如何进行 HTTP 请求,并介绍了与 Python 类相关的 OOP。本章中我们探讨的所有示例都与数据科学领域内常见应用相关,因此对这些概念有深入理解将非常有益。

尽管本章旨在向您介绍数据科学中的一些重要概念(如变量、列表、JSON 文件和字典),但我们并没有涵盖所有内容。还有许多其他主题,例如元组、集合、计数器、排序、正则表达式以及 OOP 的许多方面,我们尚未讨论。Python 的文档——无论是印刷版还是在线版——都非常广泛,而且大部分是免费的。我敦促您利用这些资源,尽可能多地从中学习。

在本章中,我们讨论了许多处理少量数据的方法,包括切片和运行基本计算。在企业层面,数据通常以显著更大的数量出现,因此,我们需要合适的工具来处理它。这个工具就是结构化查询语言SQL),我们将在下一章中了解它。

第三章:SQL 和关系型数据库入门

根据《大数据分析与应用杂志》最近发表的一篇文章,互联网上的每一秒,都会发生以下情况:

  • 制作了 70 万条状态更新。

  • 发送了 1,100 万条消息。

  • 接收了 1.7 亿封电子邮件。

  • 新增了 1,820 太字节 (TB) 的数据。

断言商业环境中数据以史无前例的速度快速增长是一种低估。随着这一主要信息爆炸,全球各地的公司都在投入大量资本,努力有效地捕捉、分析和从这些数据中为公司带来收益。数据可以管理和随后检索以提供可操作见解的主要方法之一是通过结构化查询语言 (SQL)。

类似于我们使用终端命令行创建目录,或者 Python 进行计算,你可以使用SQL在本地计算机上或远程云中创建和管理数据库。SQL 的形式和风味取决于你选择的平台,每种形式都包含略微不同的语法。然而,SQL 通常由四种主要类型的语言语句组成,这些将在下面概述:

  • 数据操纵语言 (DML):查询和编辑数据

  • 数据定义语言 (DDL):查询和编辑数据库表

  • 数据控制语言 (DCL):创建角色和添加权限

  • 事务控制语言 (TCL):管理数据库事务

全球大多数公司都有自己的最佳实践,当涉及到 DDL、DCL 和 TCL 以及数据库如何集成到他们的企业系统中。然而,DML 通常是相同的,并且通常是任何给定数据科学家的主要关注点。为此,我们将本章重点放在与 DML 相关的应用上。到本章结束时,你将获得一些最重要的数据库概念的强烈介绍,完全安装 MySQL Workbench 到你的本地机器上,并部署了一个完整的亚马逊网络服务关系数据库服务 (AWS RDS)服务器来托管和提供你的数据。请注意,所有这些功能都可以后来用于你自己的事业。让我们开始吧!

本章我们将涵盖以下主题:

  • 探索关系型数据库

  • 教程:MySQL 入门

技术要求

在本章中,我们将探讨关系数据库背后的主要概念、其优势和应用。我们将重点关注一种特定的关系数据库“风味”,即 MySQL。我们将通过其常见的用户界面UI),称为MySQL Workbench来使用 MySQL。无论您使用的是 Mac 还是 PC,MySQL Workbench 的安装过程都将非常相似,我们将在本章后面一起进行操作。此界面将允许您与本地机器或云端远程托管的数据库进行交互。在本章中,我们将部署和托管我们的数据库服务器在 AWS 云中,因此您需要有一个 AWS 账户。您可以通过访问 AWS 网站(aws.amazon.com/)并注册为新用户来创建账户。

探索关系数据库

数据库类型繁多——例如面向对象OO)数据库、图数据库和关系数据库——每种数据库都提供特定的功能。

面向对象数据库最适合与面向对象数据一起使用。这些数据库中的数据通常由包含字段、函数和属性等成员的对象组成;然而,对象之间的关系并没有得到很好的捕捉。

另一方面,正如其名所示,图数据库最适合用于由节点和边组成的数据。生物技术领域图数据库最常见的应用之一是小分子药物设计。分子由节点和边以某种形式连接在一起——这些关系最好在图数据库中捕捉。然而,分子之间的关系在这里也没有得到很好的捕捉。

最后,正如其名所示,关系数据库最适合用于关系至关重要的数据库。关系数据库在生物技术和医疗保健领域的最重要应用之一是围绕患者数据。由于患者数据的复杂性,关系数据库常用于存储患者数据。患者将有许多不同的字段,如姓名、地址、位置、年龄和性别。患者将被开具多种药物,每种药物都包含其自身的子字段,如大量的数字、数量和过期日期。每个批次将有一个生产地点、生产日期及其相关成分,每个成分也将有其各自的参数,等等。这种数据的复杂性和其相关关系最好在关系数据库中捕捉。

关系型数据库是标准的数字数据库,以列和行的形式存储表格,这些列和行相互关联。两个表格之间的关系以唯一标识符UID)或主键PK)的形式存在。这个键作为单个表格中每行的唯一值,使用户能够将一个表格的行与另一个表格中相应的行匹配。这些表格在技术上并没有任何连接方式;它们只是相互引用或关联。让我们来看一个简单的例子,关于患者数据。

如果我们创建一个包含患者姓名、联系信息、药店和开具处方的医生的患者数据表,我们可能会得到一个类似的表格:

图 3.1 – 显示示例患者数据集的表格

图 3.1 – 显示示例患者数据集的表格

从最初的角度来看,这个表格是完美的。我们左边的列表中清晰地列出了患者的信息,包括他们的姓名、地址和电话号码。我们还列出了患者的相应初级保健医生PCP),包括他们的相关姓名、地址和电话号码。最后,我们还列出了患者的相应药店及其相关的联系信息。如果我们试图生成一个包含所有患者及其相应 PCPs 和药店的数据库,这将是一个完美的表格。然而,将这些数据存储在数据库中则是一个不同的故事。

注意到一些初级保健医生(PCP)的姓名和他们的联系信息是重复的。同样,一家药店在我们的列表中出现多次,因为我们已经在同一表格中两次列出了名称、地址和电话号码。从三行表格的角度来看,这种重复是可以忽略不计的。然而,当我们把这个表格从 3 个患者扩展到 30,000 个患者时,重复可能会从数据库的角度(存储数据)和计算的角度(检索数据)变得非常昂贵。

我们可以选择不使用单一表格来存储所有数据,而是拥有多个表格,当它们为了特定目的(例如生成数据集)临时联合时,从长远来看会显著降低成本。将数据拆分或规范化到更小的表格中的这种想法是关系型数据库的核心。关系型数据库的主要目的是提供一个方便且高效的流程来存储和检索信息,同时尽可能减少重复。为了使这个数据库更加“关系型”,我们可以将数据拆分为三个表格——药店、患者和初级保健医生(PCPs)——这样我们只存储每个条目一次,但使用键系统来引用它们。

在这里,你可以看到常用的统一建模语言UML)图,通常用于描述关系型数据库。由单线分成三条线的连接表示一种一对多的关系。在以下情况下,一家药店可以有多个患者,一个 PCP 也可以有多个患者,但一个患者只能有一个 PCP,也只能有一个药店。能够快速理解数据库设计并将其转换为 UML 图(或反之亦然)是处理数据库时的一项优秀技能,通常被视为一个优秀的面试话题——我实际上几年前就收到了这个问题:

图 3.2 – 将更大的表规范化为较小的关系表的过程

图 3.2 – 将更大的表规范化为较小的关系表的过程

上一张图展示了原始表可以如何拆分。注意,每个表都有一个 ID;这个 ID 是其 UID 或 PK。与其他表相连的表使用外键FK)进行引用。例如,一个 PCP 可以有多个患者,但每个患者只有一个 PCP;因此,每个患者条目都需要有与其关联的 PCP 的外键。很有趣,对吧?

以这种方式分离数据的过程被称为数据库规范化,大多数关系型数据库都必须遵循一系列规则才能正确地进行规范化。数据科学家通常不会设计大型企业数据库(我们将这些任务留给数据库管理员)。然而,我们经常设计较小的概念验证数据库,这些数据库遵循非常相似的标准。更常见的是,我们与规模显著更大的企业数据库进行交互,这些数据库通常处于关系状态或以数据湖的形式存在。在两种情况下,对关系型数据库的结构和基本概念有扎实的知识对于任何数据科学家来说都非常有价值。

数据库规范化

在准备一个规范化的数据库时,我们必须考虑许多规则,通常被称为范式。在关系型数据库的背景下,我们将简要讨论三个范式,如下所示:

第一范式

为了满足数据库规范化的第一范式1NF),每个单元格中的值在意义上必须是原子的,即每个单元格只包含一种类型的数据。例如,包含地址如5 First Street, Boston MA 02215的列包含非原子数据,违反了此规则,因为它在一个单元格中包含了街道号码、街道名称、城市、州和邮政编码。我们可以通过将其拆分为五个列来规范化这些数据,如下所示:

图 3.3 – 表格显示如何将地址拆分为单个原子单元

图 3.3 – 表格显示如何将地址拆分为单个原子单元

现在我们已经对第一种数据规范化形式有了更好的理解,让我们继续探索第二种形式。

第二范式

为了满足第二范式2NF)的条件,表必须有一个PK作为 UID,并且除了 PK 之外的所有字段必须完全依赖于整个键。例如,我们可以有一个包含所有患者的表,在这个表中,患者的名字、姓氏和电话号码依赖于 PK。这个键代表患者,不代表其他任何东西。我们还可以有一个代表 PCPs、他们的位置、相关医院等的另一个表。然而,从数据库规范化的角度来看,将此类信息添加到代表患者的表中是不合适的。

为了满足患者数据库的此条件,我们需要将表拆分,使得患者数据在一个表中,而相关的 PCP 在第二个表中,通过 FK 连接,正如我们在前面的例子中看到的。

第三范式

为了满足第三范式3NF)的条件,我们必须满足 1NF 和 2NF 的条件,并且确保表中的所有字段在功能上相互独立——换句话说,没有字段可以是计算字段。例如,一个标题为年龄的字段,其值为 27 岁、32 岁和 65 岁,在这里是不合适的,因为这些是计算出来的量。相反,可以使用一个标题为出生日期的字段,并关联相应的日期来满足这个条件。

大多数数据科学家在结构化主要数据库和规范化它们方面花费的时间很少;然而,他们花费大量时间理解数据库结构,并形成查询以正确和高效地检索数据。因此,对数据库的坚实基础理解始终是有用的。

尽管数据库管理员和数据工程师倾向于在结构化和规范化数据库上花费更多时间,但在理解这些结构并开发有效查询以正确和高效地检索数据时,数据科学家端花费的时间也很多。因此,无论使用哪种类型的数据库,对数据库的坚实基础理解始终是有用的。

关系型数据库的类型

根据商业数据库提供商的不同,你可能会遇到几种不同的SQL版本。许多这些数据库可以分为两大类:开源企业级。开源数据库通常是免费的,允许学生、教育者和独立用户根据他们的具体条款和条件无限制地使用他们的软件。另一方面,企业数据库通常在大公司中很常见。现在,我们将探讨大多数行业中常见的几种数据库,包括技术、生物技术和医疗保健行业。

开源

以下列表显示了一些常见的开源数据库:

  • SQLite:一个符合原子性、一致性、隔离性、持久性ACID)标准的关系数据库管理系统RDBMS),常用于较小的本地托管项目。SQLite 可以在 Python 语言中使用来存储数据。

  • MySQL:虽然不符合 ACID 标准,但 MySQL 提供了与 SQLite 相似的功能,但规模更大,可以存储更多的数据,并提供多用户访问。

  • PostgreSQL:一个符合 ACID 标准的数据库系统,提供更快的数据处理速度,更适合拥有更大用户群体的数据库。

现在让我们来探索一些企业选项。

企业

以下列表显示了一些常见的企业数据库:

  • AWS RDS:一个基于云的关系数据库服务,提供可扩展且成本效益高的服务来存储、管理和检索数据。

  • Microsoft SQL Server:一个类似于 MySQL Workbench 的企业级关系数据库管理系统,提供云托管服务以存储和检索数据。

  • 系统应用和产品在数据处理中SAP):一个用于存储和检索服务的 RDBS 解决方案,常用于库存和制造数据。

现在,让我们动手操作 MySQL。

教程 – MySQL 入门

在接下来的教程中,我们将探讨启动基于云的服务器以托管私有关系型数据库的最常见过程之一。首先,我们将安装一个实例的 MySQL——这是最受欢迎的数据库管理平台之一。然后,我们将创建一个完整的免费层AWS RDS 服务器并将其连接到 MySQL 实例。最后,我们将上传一个关于小分子毒性和其相关属性的本地逗号分隔值CSV)文件,并开始探索和学习如何从我们的数据集中查询数据。

你可以在这里看到 AWS RDS 连接到 MySQL 实例的表示:

图 3.4 – 显示 MySQL 将连接到 AWS RDS 的示意图

图 3.4 – 显示 MySQL 将连接到 AWS RDS 的示意图

重要提示

注意,虽然这个教程涉及到在这个 AWS RDS 实例中创建数据库以存储毒性数据集,但你将能够回收所有组件用于未来的项目,并创建多个新的数据库,而无需重复教程。让我们开始吧!

安装 MySQL Workbench

在众多数据库设计工具中,MySQL Workbench 通常是最容易设计、实施和使用的。MySQL Workbench 简单来说是一个由 Oracle 开发的图形用户界面GUI)虚拟数据库设计工具,它允许用户为各种项目创建、设计、管理和交互数据库。或者,MySQL 也可以以 MySQL Shell 的形式使用,允许用户通过终端命令行与数据库交互。对于那些对使用终端命令行工作感兴趣的人,可以通过访问dev.mysql.com/downloads/shell/并使用微软安装程序MSI)来下载 MySQL Shell。然而,为了本教程的目的,我们将使用 MySQL Workbench,因为它具有用户友好的界面。让我们开始吧!按照以下步骤进行:

  1. 在您的本地计算机上安装 MySQL Workbench 相对简单且易于完成。请前往dev.mysql.com/downloads/workbench/,选择您的操作系统,然后点击下载,如图所示:图 3.5 – MySQL 安装程序页面

    图 3.5 – MySQL 安装程序页面

  2. 下载文件后,点击安装。如果您有特定的标准需要满足,请按照安装步骤进行;否则,选择所有默认选项。确保允许 MySQL 选择标准目标文件夹,然后选择完整设置类型,如图所示:

图 3.6 – MySQL 安装程序页面(继续)

图 3.6 – MySQL 安装程序页面(继续)

这样,MySQL Workbench 现在已成功安装在您的本地机器上。我们将前往 AWS 网站创建一个远程数据库实例供我们使用。请注意,我们假设您已经创建了一个 AWS 账户。

在 AWS 上创建 MySQL 实例

让我们创建一个 MySQL 实例,如下所示:

  1. 导航到www.aws.amazon.com,并登录您的 AWS 账户。登录后,前往 AWS 管理控制台,并在数据库部分选择RDS,如图所示:图 3.7 – AWS 管理控制台页面

    图 3.7 – AWS 管理控制台页面

  2. 从页面顶部点击创建数据库按钮。选择数据库创建方法的标准创建选项,然后选择MySQL作为引擎类型,如图所示:图 3.8 – RDS 引擎选项

    图 3.8 – RDS 引擎选项

  3. 模板部分,您将拥有三个不同的选项:生产环境开发/测试免费层。如果您打算使用生产级服务器,当然可以选择前两个选项,但我建议您选择第三个选项,免费层,以便充分利用其免费的优势。以下截图显示了选择此选项的情况:图 3.9 – RDS 模板选项

    图 3.9 – RDS 模板选项

  4. toxicitydatabaseadmin用户下,接着输入您选择的密码。您还可以利用 AWS 提供的自动生成密码功能。如果您选择此选项,密码将在实例创建后对您可用。该过程在以下截图中展示:图 3.10 – RDS 设置选项

    图 3.10 – RDS 设置选项

  5. db.t2.micro实例类型下。对于存储,选择默认参数,其中存储类型为通用型(SSD)选项被选中,并为服务器分配 20千兆字节GB)的大小。请确保禁用自动扩展功能,因为我们不需要此功能。

  6. 最后,当涉及到连接性时,选择您的默认虚拟私有云VPC),然后选择默认子网。请确保将公共访问设置更改为,因为这将允许我们从本地的 MySQL 安装连接到实例。接下来,确保在数据库身份验证部分的密码和 IAM 数据库身份验证选项被选中,如图所示。然后,点击创建数据库图 3.11 – RDS 密码生成选项

    图 3.11 – RDS 密码生成选项

  7. 一旦开始数据库创建过程,您将被重定向到您的 RDS 控制台,其中包含数据库列表,您将看到正在创建的toxicitydataset数据库,如图所示。请注意,数据库的状态列将显示为挂起一段时间。在此期间,如果您请求 AWS 为您自动生成数据库密码,您将在页面顶部的查看连接详情按钮中找到它。请注意,出于安全原因,这些凭据将永远不会再次向您展示。请确保打开这些详情并将所有内容复制到安全位置。通过本地 MySQL 界面连接到这个远程数据库需要主用户名、主密码和指定的端点值。这样,我们现在已经创建了一个 AWS RDS 服务器,我们可以现在离开 AWS 保持当前状态,并将全部注意力转向 MySQL Workbench:

图 3.12 – RDS 菜单

图 3.12 – RDS 菜单

在我们的 AWS 基础设施准备就绪后,让我们开始使用我们新创建的数据库。

使用 MySQL

一旦 AWS 上的设置完成,请继续打开 MySQL Workbench。请注意,您可能会被提示重新启动计算机。按照以下步骤操作:

  1. 您应该会看到一个欢迎信息,然后是各种选项。在MySQL 连接部分的右侧,点击+号以添加新的连接,如图所示:图 3.13 – MySQL 连接按钮

    图 3.13 – MySQL 连接按钮

  2. 在此菜单中,我们将创建一个名为toxicity_db_tutorial的新数据库连接。我们将选择标准(TCP/IP)连接方法。将主机名字段更改为在 AWS 连接详情页面中提供的端点。接下来,添加在 AWS 中指定或为您生成的用户名和密码。务必将您的密码保存在保险库中,以便稍后访问。最后,点击测试连接按钮。过程如图所示。如果所有步骤都正确执行,您应该收到成功连接的响应:图 3.14 – MySQL 设置新连接菜单

    图 3.14 – MySQL 设置新连接菜单

  3. 在主菜单中,您应该在MySQL 连接部分下看到新连接。双击新创建的连接,输入您的 root 计算机密码(如果未保存在保险库中),然后点击确定。这样,我们就已创建了一个新的数据库连接,并使用 MySQL Workbench 连接到了它。

  4. MySQL 的文档非常全面,因为它包含了许多几乎需要一本自己的书来完全涵盖的功能。为了本教程的目的,我们将专注于数据科学领域中常用功能的一个子集。在MySQL Workbench窗口中,用户应该注意三个主要部分——模式导航器查询编辑器输出窗口,如图所示:

图 3.15 – MySQL Workbench 预览

图 3.15 – MySQL Workbench 预览

模式导航器是窗口中允许用户在数据库之间导航的部分。根据您询问的人和上下文,单词“模式”和“数据库”有时是同义的,可以互换使用。在本书的上下文中,我们将“模式”定义为数据库的蓝图,“数据库”本身即为数据库。

查询编辑器是页面中您作为用户将执行 SQL 脚本的部分。在此部分中,可以创建、更新、查询或删除数据。您可以使用输出窗口(正如其名所示),用于显示执行查询的输出。如果查询出错或发生意外情况,您可能会在这里找到关于它的一个重要信息。

创建数据库

在我们开始进行任何查询之前,我们需要一些数据来工作。让我们看看我们新服务器上当前存在的数据库。在查询编辑器中输入 SHOW DATABASES;,然后点击 执行 () 按钮。你将得到一个系统上可用的数据库列表。大多数这些数据库要么是从以前的项目创建的,要么是系统用来管理数据的。无论如何,让我们避免使用那些。现在,按照以下步骤操作:

  1. 我们可以使用以下 SQL 语句创建一个新的数据库:

    CREATE DATABASE IF NOT EXISTS toxicity_db_tutorial;
    
  2. 在输出窗口中,你应该看到一条消息确认此语句的执行成功。现在,让我们继续用之前存在的 CSV 文件填充我们的数据库。从模式导航器中选择 模式 选项卡,并使用带有两个圆形箭头的图标刷新列表。你将看到新创建的数据库出现在列表中,如图所示:图 3.16 – MySQL 模式列表

    图 3.16 – MySQL 模式列表

  3. 接下来,在数据库上右键单击,然后点击 dataset_toxicity_sd.csv CSV 文件。当提示选择目的地时,选择默认参数,允许 MySQL 在 toxicity_db_tutorial 数据库中创建一个名为 dataset_toxicity_sd 的新表。在 导入配置 设置中,允许 MySQL 为数据集选择默认数据类型。继续通过向导直到导入过程完成。鉴于我们的服务器是远程的,文件传输可能需要一些时间。

  4. 一旦文件完全导入到 AWS RDS,我们现在就可以检查我们的数据并开始运行一些 SQL 语句。如果你在模式导航器中点击表,你会看到从 CSV 文件中导入的所有列的列表,如下面的截图所示:

图 3.17 – 毒性数据集的列列表

图 3.17 – 毒性数据集的列列表

仔细查看这些列,我们注意到我们开始于一个 ID 列,它作为我们的 PK 或 UID,数据类型为 int,即整数。然后我们注意到一个名为 smiles 的列,它是一个文本或字符串,代表分子的实际化学结构。接下来,我们有一个名为 toxic 的列,它表示化合物的 毒性,用 1 表示有毒,或用 0 表示无毒。我们将 toxic 列称为我们的 FormalChargeLogP 是分子的 属性特征。我们在下一章将要着手的主要目标之一是开发一个预测模型,使用这些特征作为输入数据并尝试预测毒性。现在,我们将使用这个数据集来探索 SQL 及其最常用的 子句语句

查询数据

文件已完全导入到 AWS RDS,我们现在可以运行一些命令了。

我们将从一个简单的SELECT语句开始,我们将从新创建的表中检索所有数据。我们可以在SELECT命令之后使用*参数来表示特定表中的所有数据。表本身可以使用以下语法在末尾指定:<database_name>.<table_name>:

在实际命令中,它看起来是这样的:

SELECT * from toxicity_db_tutorial.dataset_toxicity_sd;

这给我们以下输出:

图 3.18 – MySQL Workbench 查询预览

图 3.18 – MySQL Workbench 查询预览

在任何给定的查询中,我们很少会查询所有列和所有行。通常情况下,我们会将列限制在我们感兴趣的列上。我们可以通过将*参数替换为感兴趣列的列表来实现这一点,如下所示:

SELECT 
  ID,
      TPSA,
      MolWt,
      LogP,
      toxic
 from toxicity_db_tutorial.dataset_toxicity_sd;

注意,列之间用逗号分隔,而语句中的其他参数则不是。除了限制列数外,我们还可以使用LIMIT子句来限制行数,后面跟我们要检索的行数,如下所示:

SELECT 
  ID,
      TPSA,
      MolWt,
      LogP,
      toxic
 from toxicity_db_tutorial.dataset_toxicity_sd LIMIT 10;

除了指定我们的列和行之外,我们还可以对列值应用操作,如加法、减法、乘法和除法。我们还可以根据特定的条件集过滤我们的数据。例如,我们可以运行一个简单的查询,查询数据库中所有有毒化合物(toxic=1)的IDsmiles列,如下所示:

SELECT 
  ID,
    SMILES,
     toxic
 from toxicity_db_tutorial.dataset_toxicity_sd
 WHERE toxic=1;

同样,我们也可以通过将语句的最后一行更改为WHERE toxic = 0WHERE toxic != 1来找到所有非有毒化合物。我们可以在WHERE子句中添加更多的条件来扩展此查询。

条件查询

条件查询可以根据具体的使用场景更有效地过滤数据。我们可以使用AND运算符来查询与特定毒性相关的数据,AND运算符将确保需要满足两个条件,如下面的代码片段所示:

SELECT 
  ID,
      SMILES,
     toxic
 from toxicity_db_tutorial.dataset_toxicity_sd
 WHERE toxic=1 AND MolWt > 500;

或者,我们也可以使用OR运算符,其中只需要满足一个条件——例如,前面的查询需要数据具有毒性值为1和摩尔质量为500,因此返回 26 行数据。在这里使用OR运算符将需要满足任一条件,因此返回 318 行。运算符也可以组合使用。例如,如果我们想查询具有1个氢受体和123个氢供体的所有分子的 ID、smiles 表示和毒性,我们可以使用以下语句来完成此查询:

SELECT 
   ID,
      SMILES,
      toxic
 from toxicity_db_tutorial.dataset_toxicity_sd
 WHERE HAcceptors=1 AND (HDonors = 1 OR HDonors = 2 OR HDonors = 3);

重要的一点是,当指定连续顺序中的多个OR运算符,例如三个HDonors值时,可以使用BETWEEN运算符来避免不必要的重复。

数据分组

在对数据库进行查询时,一个常见的做法是按某一列对数据进行分组。让我们以一个必须检索数据集中有毒与无毒化合物实例总数的情况为例。我们可以通过使用WHERE语句轻松查询值为10的实例。然而,如果结果的数量是100而不是2呢?运行这个查询 100 次,每次迭代替换值,将不可行。对于这种类型的操作,或者任何分组值重要的操作,我们可以使用GROUP BY语句与COUNT函数的组合,如下面的代码片段所示:

SELECT 
  ID,
    SMILES,
    COUNT(*) AS count
 from toxicity_db_tutorial.dataset_toxicity_sd
 GROUP BY toxic

这在下面的屏幕截图中有显示:

图 3.19 – MySQL Workbench GROUPBY 预览

图 3.19 – MySQL Workbench GROUPBY 预览

现在我们已经探讨了如何分组我们的数据,接下来让我们学习如何对数据进行排序。

数据排序

在某些情况下,你的数据可能需要以某种方式排序,无论是升序还是降序。为此,你可以使用ORDER BY子句,其中排序列直接列出,后跟ASC表示升序或DESC表示降序。这在下面的代码片段中有说明:

SELECT 
  ID,
     SMILES,
  toxic,
   ROUND(MolWt, 2) AS roundedMolWt
 from toxicity_db_tutorial.dataset_toxicity_sd
 ORDER BY roundedMolWt DESC

表的连接

经常在查询记录时,表已经被规范化,如前所述,以确保它们的数据以关系方式正确存储。通常情况下,你将需要合并或连接两个表,以便在开始任何类型的有意义JOIN子句之前准备你感兴趣的数据集。

将相同的dataset_orderQuantities_sd.csv数据集导入到同一个数据库中。回想一下,你可以在导入向导中指定相同的数据库名称,但不同的表名称。一旦加载,我们现在将有一个包含两个表的数据库:dataset_toxicity_sddataset_orderQuantities_sd

如果我们对每个表运行SELECT *语句,我们会注意到这两个数据集唯一共有的列是ID列。这将成为连接两个数据集的 UID。然而,我们也注意到毒性数据集有1461行,而orderQuantities数据集有 728 行。这意味着一个数据集缺失了很多行。这就是不同类型的JOIN子句发挥作用的地方。

将两个数据集想象成需要用维恩图表示的圆圈,其中一个圆圈(A)包含 1461 行数据,另一个圆圈(B)包含 728 行数据。我们可以使用 INNER JOIN 函数将表连接起来,从而丢弃任何不匹配的行。注意,这在以下屏幕截图中被表示为两个圆圈的交集,即 A img/03.png B。或者,我们可以在数据上运行 LEFT JOINRIGHT JOIN 语句,忽略由 A' img/03.png BA img/03.png B' 表示的其中一个数据集的不同内容。最后,我们可以使用 OUTER JOIN 语句将数据连接起来,无论缺失哪些行,这被称为并集,或 A img/04.png B

![图 3.20 – 四种主要连接方法的表示 img/B17761_03_020.jpg]

图 3.20 – 四种主要连接方法的表示

当你开始探索数据集并连接表时,你会发现最常用的 JOIN 子句实际上是 INNER JOIN 语句,这正是我们特定应用所需要的。我们将按照以下结构构建语句:我们将选择感兴趣的列,指定源表,然后运行一个内部连接,将两个 ID 列匹配在一起。代码在以下代码片段中展示:

SELECT
  dataset_toxicity_sd.id,
     dataset_toxicity_sd.SMILES,
     dataset_orderQuantities_sd.quantity_g
FROM
  toxicity_db_tutorial.dataset_toxicity_sd
INNER JOIN toxicity_db_tutorial.dataset_orderQuantities_sd
  ON dataset_toxicity_sd.id = dataset_orderQuantities_sd.id

通过这样,我们成功地管理了我们的第一个数据集(仅包含个别研究和开发分子及其相关属性)并将其与另一个表(包含这些分子的订购数量)连接起来,使我们能够确定我们目前库存中的物质。现在,如果我们需要找到具有特定 亲脂性logP)的所有化合物,我们可以确定我们库存中有哪些以及它们的数量。

摘要

当涉及到查询关系数据库中的大量数据时,SQL 是一种强大的语言——这项技能将在所有技术领域以及大多数生物技术领域为你提供良好的服务。随着大多数公司开始扩大其数据库能力,你可能会遇到许多不同类型的数据库,尤其是关系数据库。

在理论方面,我们讨论了关系数据库的一些最重要的特征以及数据通常是如何进行规范化的。我们查看了一个患者数据的例子以及如何将表规范化以减少存储时的重复。我们还查看了一些市场上今天可用和广泛使用的开源和商业数据库。

当涉及到应用时,我们组装了一个健壮的 AWS RDS 数据库服务器并将其部署到云端。然后,我们将本地 MySQL 实例连接到该服务器,并使用 CSV 文件填充了一个新数据库。接着,我们回顾了目前在业界使用的最常见 SQL 语句和子句。我们探讨了选择、过滤、分组和排序数据的方法。然后,我们查看了一个将两个表连接起来的示例,并了解了我们可用的不同连接方法。

尽管这本书旨在向您介绍每位数据科学家都应该了解的一些最重要的核心概念,但在 SQL 中还有许多其他主题我们没有涉及。我敦促您查阅 MySQL 文档,以了解许多其他令人兴奋的语句,这些语句允许您以许多不同的形状和大小查询数据。SQL 总是专门用于检索和审查表格形式的数据,但永远不会是可视化数据的适当工具——这项任务最好留给 Python 及其众多的可视化库,这些内容将是下一章的重点。

第四章:使用 Python 可视化数据

无论你从事哪个领域的工作,你选择的职业道路,或者你正在从事的具体项目,有效地向他人传达信息的能力始终是有用的。事实上,正好一百年前,在 1921 年,弗雷德里克·R·巴纳德首次说出了你可能无数次听过的短语:一张图片胜过千言万语

近年来,在机器学习领域涌现出许多新技术,结构化、处理和分析的数据量呈指数增长。将原始数据转化为有意义的、易于传达的图表的能力是当今行业中最受欢迎的技能组合之一。大多数大型公司和企业的决策通常都是数据驱动的,而开始关于你关心的领域的对话的最佳方式是创建关于它的有意义的可视化。考虑以下内容:

  • 人类大脑处理可视化比文本快 60,000 倍。

  • 人类大脑处理的信息中,近 90%是通过视觉完成的。

  • 可视化比即使是简单的文本更容易被阅读,概率高达 30 倍。

可视化并不总是关于推动对话或说服对立的一方同意某事——它们通常被用作调查和探索数据以揭示隐藏洞察力的手段。在几乎每一个机器学习项目中,你都将投入大量精力来探索数据,通过称为探索性数据分析EDA)的过程来揭示其隐藏的特征。EDA 通常在任何类型的机器学习项目之前进行,以便更好地理解数据、其特征及其限制。以这种方式探索数据最好的方式是将其以视觉形式呈现,这样你可以揭示比单纯的数值更多的内容。

在接下来的章节中,我们将探讨一些有用的步骤,以开发针对特定数据集的稳健可视化。我们还将探索目前在Python社区中使用的最常见可视化库。最后,我们将探索几个数据集,并学习如何为它们开发一些最常见的可视化。

在本章中,我们将涵盖以下主要主题:

  • 探索数据可视化的六个步骤

  • 常用的可视化库

  • 教程 - 在 Python 中可视化数据

技术要求

在本章中,我们将应用我们在第二章中展示的 Python 和pip安装程序的理解,即介绍 Python 和命令行。回想一下,安装库的过程是通过命令行完成的:

$ pip install library-name

因此,现在我们已经准备好了,让我们开始吧!

探索数据可视化的六个步骤

当涉及到有效地传达数据中的关键趋势时,数据呈现的方式始终非常重要。在向观众展示任何类型的数据时,有两个主要考虑因素:首先,选择正确的数据段来支持论点;其次,选择最有效的可视化方式来支持论点。在制作新的可视化时,你可以遵循以下六个步骤来帮助你:

  1. 获取: 从其来源处获取数据。

  2. 理解: 了解数据并理解其类别和特征。

  3. NaN值和损坏的条目。

  4. 我的任务: 识别模式或设计新的功能。

  5. 浓缩: 隔离最有用的特征。

  6. 表示: 为这些功能选择一个表示方式。

让我们详细看看每个步骤。

第一步是从其来源处获取你的数据。这个来源可能是一个简单的 CSV 文件,一个关系型数据库,甚至是一个NoSQL数据库。

第二步,理解数据的内容及其上下文非常重要。作为一名数据科学家,你的目标是站在利益相关者的角度,尽可能好地理解他们的数据。通常,与主题专家(SME)的简单交谈可以节省你数小时的时间,因为它会突出显示关于数据的你原本不知道的事实。

第三,过滤数据始终至关重要。大多数数据科学在现实世界中的应用很少涉及现成的数据集。通常,原始数据将是主要的数据来源,而数据科学家和开发人员需要确保任何缺失值和损坏的条目得到处理。数据科学家通常将这一步骤称为预处理,我们将在第五章中更详细地探讨,理解机器学习

数据预处理完成后,我们的下一个目标是挖掘数据,试图识别模式或设计新的功能。在简单的数据集中,值通常可以快速可视化为增加或减少,使我们能够轻松理解趋势。在多维数据集中,这些趋势往往更难以发现。例如,时间序列图可能显示一个增加的趋势,然而,这个图的第一次导数可能会暴露与季节性相关的趋势。

一旦确定了感兴趣的趋势,代表该趋势的数据通常会被从其他数据中隔离出来。最后,使用与之相匹配的可视化来表示这个趋势。

重要的是要理解,这些步骤绝对不是硬性规定,而应该被视为有用的指南,以帮助您生成有效的可视化。并非每个可视化都需要每个步骤!实际上,某些可视化可能需要其他步骤,有时顺序可能不同。我们将在本章的“教程 – 在 Python 中可视化数据”部分中通过一系列步骤来生成一些可视化。当我们这样做的时候,尝试回忆这些步骤,看看您是否能识别出它们。

在我们开始生成一些有趣的视觉效果之前,让我们谈谈我们将需要的库。

常用可视化库

Python 中有无数的可视化库可用,每天都有新的库发布。可视化库可以分为两大类:静态可视化库和交互式可视化库。静态可视化是由绘制的值组成的图像,用户无法点击。另一方面,交互式可视化不仅仅是图像,而是可以点击、变形、移动和按特定方向缩放的表示。静态可视化通常用于电子邮件通信、印刷出版物或演示文稿,因为它们是你不希望他人更改的视觉内容。然而,交互式可视化通常用于仪表板和网站(如 AWSHeroku),预期用户将与之交互并按允许的方式探索数据。

以下开源库目前在行业中非常受欢迎。每个库都有其自身的优缺点,具体细节如下表所示:

图 4.1 – Python 中最常见的可视化库列表

图 4.1 – Python 中最常见的可视化库列表

现在我们已经了解了可视化库,让我们继续下一节!

教程 – 在 Python 中可视化数据

在本教程的过程中,我们将从各种来源检索几个不同的数据集,并通过各种类型的可视化来探索它们。为了创建这些视觉效果,我们将结合使用一些开源可视化库来实现许多可视化步骤。让我们开始吧!

获取数据

回想一下,在 第三章SQL 和关系型数据库入门”中,我们使用 AWS 创建并部署了一个数据库到云端,使我们能够使用 sqlalchemy 查询数据:

  1. 让我们从上一章生成的 endpointusernamepassword 值中直接查询该数据集。请将这些值作为 Python 中的变量列出:

    ENDPOINT=" yourEndPointHere>"
    PORT="3306"
    USR="admin"
    DBNAME="toxicity_db_tutorial"
    PASSWORD = "<YourPasswordHere>"
    
  2. 当变量被各自的参数填充后,我们现在可以使用 sqlalchemy 查询这些数据。由于我们对整个数据集感兴趣,我们可以简单地运行一个 SELECT * FROM dataset_toxicity_sd 命令:

    from sqlalchemy import create_engine
    import pandas as pd
    db_connection_str =
    'mysql+pymysql://{USR}:{PASSWORD}@{ENDPOINT}:{PORT}/{DBNAME}'.format(USR=USR, PASSWORD=PASSWORD, ENDPOINT=ENDPOINT, PORT=PORT, DBNAME=DBNAME)
    db_connection = create_engine(db_connection_str)
    df = pd.read_sql('SELECT * FROM dataset_toxicity_sd',
    con=db_connection)
    

    或者,您也可以简单地使用 read_csv() 函数将相同的数据集作为 CSV 文件导入:

    df = pd.read_csv("../../datasets/dataset_toxicity_sd.csv")
    
  3. 我们可以使用 head() 函数快速查看数据集以了解其内容。回想一下,我们可以通过使用双中括号 ([[ ]]) 指定我们感兴趣的列名来选择性地减少列的数量:

    df[["ID", "smiles", "toxic"]].head() 
    

    这给出了以下输出:

    图 4.2 – 来自毒性数据集选定列的 DataFrame 表示

    图 4.2 – 来自毒性数据集选定列的 DataFrame 表示

    如果您还记得,这个数据集中有很多列,从一般数据如主键 (ID) 到结构 (smiles) 和毒性 (toxic),应有尽有。此外,还有许多描述和表示数据集的特征,从总的极性表面积 (TPSA) 到亲脂性 (LogP)。

  4. 我们还可以通过使用 pandas 中的 describe() 函数来了解这个数据集背后的一些一般统计信息 – 例如,与每个列相关的最大值、最小值和平均值:

    df[["toxic", "TPSA", "MolWt", "LogP"]].describe()
    

    这导致了以下表格:

    图 4.3 – 来自毒性数据集选定列的一些一般统计信息

    图 4.3 – 来自毒性数据集选定列的一些一般统计信息

    立即,我们注意到 FormalChargeLogP 具有负值。因此,这个现实世界的数据集非常多样且分布广泛。

  5. 在我们进一步探索数据集之前,我们需要确保没有缺失值。为此,我们可以使用 pandas 库提供的 isna() 函数进行快速检查。我们可以将其与 sum() 函数链式使用,以获取每个列的缺失值总和:

    df.isna().sum()
    

    结果如 图 4.4 所示:

图 4.4 – DataFrame 中缺失值的列表

图 4.4 – DataFrame 中缺失值的列表

幸运的是,这个特定数据集中没有缺失值,因此我们可以自由地继续创建一些图表和视觉元素。

重要提示

dropna() 函数。另一个选项是使用 fillna()replace() 函数将任何缺失值替换为常用值。最后,您还可以使用 mean() 函数将缺失值替换为所有其他值的平均值。您选择的方法将高度依赖于列的标识和含义。

使用条形图总结数据

条形图条形图通常用于描述分类数据,其中条形的长度或高度与它们所代表的类别值成比例。条形图提供了对数据集中心趋势的视觉估计,估计的不确定性由误差线表示。

因此,让我们创建我们的第一个条形图。我们将使用seaborn库来完成这个特定的任务。有几种不同的方式来设置你的图表样式。在本教程的目的上,我们将使用seaborndarkgrid样式。

让我们绘制TPSA特征相对于FormalCharge特征,以了解它们之间的关系:

import pandas as pd
import seaborn as sns
plt.figure(figsize=(10,5))
sns.barplot(x="FormalCharge", y="TPSA", data=df);

我们最初的结果显示在图 4.5中:

图 4.5 – TPSA 和 FormalCharge 特征的条形图

图 4.5 – TPSA 和 FormalCharge 特征的条形图

立即,我们可以看到两者之间有趣的关系,即在TPSA特征倾向于增加,当FormalCharge的绝对值远离零时。如果你正在跟随提供的HDonors而不是TPSA

sns.barplot(x="FormalCharge", y="HDonors", data=df)

我们可以在图 4.6中看到后续的输出:

图 4.6 – HDonors 和 FormalCharge 特征的条形图

图 4.6 – HDonors 和 FormalCharge 特征的条形图

观察图表,我们并没有看到两个变量之间强烈的关联。最高和最低的正式电荷实际上显示了更高的氢供体。让我们将其与HAcceptors进行比较——在这个数据集中类似的一个特征。我们可以单独绘制这个特征,就像我们处理氢供体一样,或者我们可以将它们两个合并到一个图表中。我们可以通过隔离感兴趣的特征(你还记得这个步骤的名称吗?)然后重塑数据集。Python 中的 DataFrames 通常使用四个常见的函数进行重塑

图 4.7 – 四个最常见的 DataFrame 重塑函数

图 4.7 – 四个最常见的 DataFrame 重塑函数

每个函数都用于以特定方式重塑数据。pivot()函数通常用于重塑按其索引组织的 DataFrame。stack()函数通常与多索引 DataFrame 一起使用——这允许你堆叠你的数据,使表格变得长而窄而不是宽而短melt()函数在意义上与stack()函数相似,因为它也堆叠你的数据,但它们之间的区别在于stack()会将压缩的列插入到内部索引中,而melt()将创建一个名为Variable的新列。最后,unstack()stack()的简单相反,即数据从转换为

为了比较氢供体和受体,我们将使用melt()函数,你可以在图 4.8中看到它。请注意,在这个过程中创建了两个新列:VariableValue

图 4.8 – melt()函数的图形表示

图 4.8 – melt()函数的图形表示

首先,我们创建一个名为df_iso的变量来表示独立的 DataFrame,然后我们使用melt()函数将其数据熔化并分配给一个新的变量df_melt。我们还可以打印数据的形状来证明如果列的长度正好加倍,则列堆叠是正确的。回想一下,你也可以使用head()函数来检查数据:

df_iso = df[["FormalCharge", "HDonors", "HAcceptors"]]
print(df_iso.shape)
    (1460, 3)
df_melted = pd.melt(df_iso, id_vars=["FormalCharge"],
                    value_vars=["HDonors", "HAcceptors"])
print(df_melted.shape)
    (2920, 3)

最后,当数据正确排序后,我们可以继续绘制这些数据,指定 x 轴为FormalCharge,y 轴为value

sns.barplot(data=df_melted, x='FormalCharge', y='value', 
            hue='variable')

执行这一行代码后,我们将得到以下图形:

图 4.9 – 相对于 FormalCharge 的两个特征的条形图

图 4.9 – 相对于 FormalCharge 的两个特征的条形图

当你开始探索seaborn库中的众多函数和类时,在编写代码时参考文档可以帮助你调试错误,并发现你可能不知道的新功能。你可以在seaborn.pydata.org/api.html查看 Seaborn 文档。

处理分布和直方图

40

plt.figure(figsize=(10,5))
plt.title("Histogram of Molecular Weight (g/mol)", fontsize=20)
plt.xlabel("Molecular Weight (g/mol)", fontsize=15)
plt.ylabel("Frequency", fontsize=15)
df["MolWt"].hist(figsize=(10, 5), 
                          bins=40, 
                          xlabelsize=10, 
                          ylabelsize=10, 
                          color = "royalblue")

我们可以在图 4.10中看到此代码的输出:

图 4.10 – 分子量直方图,分组大小为 40

图 4.10 – 分子量直方图,分组大小为 40

当你探索 Python 中的更多可视化方法时,你会注意到大多数库都提供了一些已经开发并优化以执行特定任务的快速函数。我们可以对每个特征进行相同的数据重塑过程,并通过它们迭代来为每个特征绘制直方图,或者我们可以简单地使用hist()函数对它们进行集体处理:

dftmp = df[["MolWt", "NHOH", "HAcceptors", "Heteroatoms", 
                     "LogP", "TPSA"]]
dftmp.hist(figsize=(30, 10), bins=40, xlabelsize=10,
                    ylabelsize=10, color = "royalblue")

后续输出可以在图 4.11中看到:

图 4.11 – 使用 hist()函数自动化的各种特征的系列直方图

图 4.11 – 使用 hist()函数自动化的各种特征的系列直方图

直方图也可以叠加,以便在同一张图上展示两个特征。当这样做时,我们需要通过使用alpha参数给图表一定的透明度:

dftmp = df[["MolWt","TPSA"]]
x1 = dftmp.MolWt.values
x2 = dftmp.TPSA.values
kwargs = dict(histtype='stepfilled', alpha=0.3, 
              density=True, bins=100, ec="k")
plt.figure(figsize=(10,5))
plt.title("Histogram of Molecular Weight (g/mol)", 
           fontsize=20)
plt.xlabel("Molecular Weight (g/mol)", fontsize=15)
plt.ylabel("Frequency", fontsize=15)
plt.xlim([-100, 1000])
plt.ylim([0, 0.01])
plt.hist(x1, **kwargs)
plt.hist(x2, **kwargs)
plt.legend(dftmp.columns)
plt.show()

我们可以在图 4.12中看到前一个命令的输出:

图 4.12 – 两个直方图的叠加,其透明度已降低

图 4.12 – 两个直方图的叠加,其透明度已降低

直方图是总结和可视化大量数据的绝佳方式,尤其是当使用 hist() 函数的功能如此简单时。你会发现大多数库,如 pandasnumpy,都有许多具有类似功能的功能。

使用散点图可视化特征

散点图是基于笛卡尔坐标系的表示,允许在二维和三维空间中创建可视化。散点图由 x 轴和 y 轴组成,通常还伴随着一个额外的特征,允许在数据内部进行分离。当与第三个特征结合使用时,散点图最为有效,该特征可以通过颜色或形状表示,具体取决于可用的数据类型。让我们来看一个简单的例子:

  1. 我们将查看一个简单的散点图示例,展示 TPSA 相对于 HeavyAtoms 特征:

    plt.figure(figsize=(10,5))
    plt.title("Scatterplot of Heavy Atoms and TPSA", fontsize=20)
    plt.ylabel("Heavy Atoms", fontsize=15)
    plt.xlabel("TPSA", fontsize=15)
    sns.scatterplot(x="TPSA", y="HeavyAtoms", data=df)
    

    前述代码的输出可以在图 4.13中看到:

    图 4.13 – TPSA 和 HeavyAtoms 特征的散点图

    图 4.13 – TPSA 和 HeavyAtoms 特征的散点图

    立即,我们注意到这两个特征之间存在一些依赖关系,如轻微的正相关性所示。

  2. 我们可以通过使用 huesize 参数分别改变颜色和大小来查看第三个特征,例如 MolWt。这使我们能够在同一张图上绘制三个或四个特征,从而对数据集进行出色的解释。我们可以看到 TPSA 相对于 HeavyAtoms 的一些趋势,以及 MolWt 的增加:

    plt.figure(figsize=(10,5))
    plt.title("Scatterplot of Heavy Atoms and TPSA", fontsize=20)
    plt.ylabel("Heavy Atoms", fontsize=15)
    plt.xlabel("Molecular Weight (g/mol)", fontsize=15)
    sns.scatterplot(x="TPSA",y="HeavyAtoms", 
    size="MolWt", hue="MolWt", data=df)
    

    前述代码的输出可以在图 4.14中看到:

    图 4.14 – 两个特征的散点图,第三个特征通过大小和颜色表示

    图 4.14 – 两个特征的散点图,第三个特征通过大小和颜色表示

  3. 作为二维散点图的替代,我们可以使用三维散点图来引入以新维度形式存在的另一个特征。我们可以利用 Plotly 库来实现一些三维功能。为此,我们可以使用 scatter_3d 函数定义一个 fig 对象,然后定义我们数据源和感兴趣的轴:

    import plotly.express as px
    fig = px.scatter_3d(df, x='TPSA', y='LogP', z='HeavyAtoms',
                         color='toxic', opacity=0.7)
    fig.update_traces(marker=dict(size=4))
    fig.show()
    

    此代码的输出将生成图 4.15

    图 4.15 – 以毒性着色的三个特征的 3D 散点图

    图 4.15 – 以毒性着色的三个特征的 3D 散点图

  4. 我们不是增加更多功能,而是可以向散点图添加一些更多元素来帮助解释 x 和 y 坐标上的两个特征。我们之前注意到数据集中存在轻微的相关性,这似乎非常适合探索。很有趣的是,看看这种相关性是否对有毒和非有毒化合物都成立。我们可以使用lmplot()函数来感知这种相关性,该函数允许我们在散点图中以线性回归的形式图形化地表示相关性:

    sns.lmplot(x="HAcceptors", y="TPSA", hue="toxic", 
             data=df, markers=["o", "x"], height = 5, 
             aspect = 1.7, palette="muted");
    plt.xlim([0, 16])
    plt.ylim([0, 400])
    

    后续输出可以在图 4.16中看到:

图 4.16 – 两个特征及其相关性的散点图

图 4.16 – 两个特征及其相关性的散点图

散点图是描绘数据关系和开始理解它们可能具有的任何依赖关系或相关性的绝佳方式。绘制回归或最佳拟合线可以让你对任何可能的关系有所了解。我们将在下一节中更详细地探讨这一点。

使用热图识别相关性

现在我们已经在我们数据集中的两个分子特征之间建立了相关性,让我们调查看看是否还有其他的相关性。我们可以轻松地遍历每一组特征,绘制它们,并查看它们各自的回归来确定是否存在相关性。在 Python 中,建议尽可能自动化,幸运的是,这项任务已经自动化了!所以,让我们来看看:

  1. 使用pairplot()函数将接受你的数据集作为输入,并返回一个包含你数据集中所有特征的散点图的图形。为了将图形放入本页的范围内,只选择了最有趣的特征。然而,我挑战你运行提供的 Jupyter 笔记本中的代码,看看是否有其他有趣的趋势:

    featOfInterest = ["TPSA", "MolWt", "HAcceptors",
           "HDonors", "toxic", "LogP"]
    sns.pairplot(df[featOfInterest], hue = "toxic", markers="o")
    

    结果以许多较小的图形的形式呈现,如图 4.17所示:

    图 4.17 – 显示所选特征的毒性数据集的 pairplot()图形

    图 4.17 – 显示所选特征的毒性数据集的 pairplot()图形

  2. 或者,我们可以使用corr()函数与 DataFrame 本身结合来捕获每个特征对的皮尔逊相关系数

    df[["TPSA", "MolWt", "HeavyAtoms", "NHOH", "HAcceptors", 
              "HDonors", "AromaticRings", "LogP", "AromaticN"]].corr()
    

    我们可以在图 4.18中以 DataFrame 的形式回顾这些相关性:

    图 4.18 – 显示所选特征之间相关性的 DataFrame

    图 4.18 – 显示所选特征之间相关性的 DataFrame

  3. 为了得到更吸引人的结果,我们可以在heatmap()函数中包裹我们的数据,并应用颜色图来显示强相关性的深色和弱相关性的浅色:

    sns.heatmap(df[["TPSA", "MolWt", "HeavyAtoms", "NHOH", 
                  "HAcceptors", "HDonors", "AromaticRings", 
                  "LogP", "AromaticN"]].corr(), 
                  annot = True,  cmap="YlGnBu")
    

    我们之前编写的一些代码随着我们开始将多个函数串联在一起而变得有些复杂。为了提供一些关于语法和结构的清晰度,让我们更仔细地看看以下函数。我们首先在seaborn库中调用主heatmap类(记住我们给它取了别名sns)。然后我们添加我们的数据集,包含感兴趣特征的切片集。然后我们应用相关函数以获取相应的相关性,并最终添加一些额外的参数来调整图表的样式和颜色:

图 4.19 – 展示所选特征之间相关性的热图

图 4.19 – 展示所选特征之间相关性的热图

无论您是在分析数据还是准备预测模型,识别数据集中的相关性总是有用的。您会发现corr()及其许多衍生函数在机器学习领域中被广泛使用。

显示序列和时间序列图

我们迄今为止探索的数据集和特征都是以结构化表格化的形式提供的,存在于 DataFrame 的行和列中。这些行之间是完全独立的。并非所有数据集都是这种情况,依赖性(尤其是基于时间的依赖性)有时是我们需要考虑的因素。例如,考虑一个Fast AllFASTA)序列——即一种基于文本的格式,常用于生物信息学领域,通过字母代码表示核苷酸或氨基酸序列。在分子生物学和遗传学中,鸟嘌呤-胞嘧啶GC含量是一个用于确定 DNA 或 RNA 分子中氮碱基百分比的指标。让我们通过使用 COVID-19 数据的 FASTA 文件来探索绘制这种序列数据的图表:

  1. 我们将开始使用wget库导入数据集的过程:

    import wget
    url_covid = "https://ftp.expasy.org/databases/uniprot/pre_release/covid-19.fasta"
    filename = wget.download(url_covid, out="../../datasets")
    
  2. 接下来,我们可以使用Biopython(也称为Bio)库来计算 GC 含量——这是计算分子生物学领域中最常用的 Python 库之一。Biopython库的文档和教程可以在biopython.org/DIST/docs/tutorial/Tutorial.html找到。

  3. 我们将使用SeqIOGC类解析文件,并将结果写入gc_values_covid变量:

    from Bio import SeqIO
    from Bio.SeqUtils import GC
    gc_values_covid = sorted(GC(rec.seq) for rec in 
        SeqIO.parse("../../datasets/covid-19.fasta", "fasta"))
    

    请注意,前述代码中文件的路径可能会根据文件保存的目录而改变。

  4. 最后,我们可以使用pylabmatplotlib来绘制结果:

    import pylab
    plt.figure(figsize=(10,5))
    plt.title("COVID-19 FASTA Sequence GC%", fontsize=20)
    plt.ylabel("GC Content %", fontsize=15)
    plt.xlabel("Genes", fontsize=15)
    pylab.plot(gc_values_covid)
    pylab.show()
    

    后续输出可以在图 4.20中看到:

图 4.20 – 展示 COVID-19 序列 GC 含量的图表

图 4.20 – 展示 COVID-19 序列 GC 含量的图表

虽然有许多非基于时间的顺序数据集,如 textimagesaudio,但也有基于时间的数据集,如 stock pricesmanufacturing processes。在实验室空间中,也有许多设备也利用基于时间序列的方法,例如与色谱相关的方法。例如,考虑 time-series 数据集,并在时间上叠加 TemperaturePressure

dfts = pd.read_csv("../../datasets/dataset_pressure_ts.csv")
plt.title("Timeseries of an LCMS Chromatogram (Pressure & 
     Temperature)", fontsize=20)
plt.ylabel("Pressure (Bar)", fontsize=15)
plt.xlabel("Run Time (min)", fontsize=15)
ax1 = sns.lineplot(x="Run Time", y="Pressure",
                      data=dfts, color = "royalblue", 
                      label = "Pressure (Bar)");
ax2 = sns.lineplot(x="Run Time", y="Temperature",
                      data=dfts, color = "orange", 
                      label = "Pressure (Bar)");

此代码的输出可以在 图 4.21 中看到:

图 4.21 – 显示失败 LCMS 运行的温度和压力的时间序列图

图 4.21 – 显示失败 LCMS 运行的温度和压力的时间序列图

我们注意到,在这张图的最初 5 分钟内,温度和压力参数增加得相当快。在 6.5 分钟范围内出现某种下降,系统保持了一段时间的增加,然后两个参数开始急剧下降并稳定在其各自的范围内。这是一个仪器故障的例子,而且这是一个一个精细调校的机器学习模型能够相对于其成功的对应物检测到的情形。我们将在第七章 监督机器学习中更详细地探讨这个异常检测模型的发展。

使用桑基图强调流程

数据科学中的一种流行可视化形式是 桑基图 – 由米纳德对拿破仑入侵俄罗斯的军队的经典描绘而闻名。桑基图的主要目的是在流程图上以比例宽度来可视化一个量的大小:

图 4.22 – 查尔斯·约瑟夫·米纳德绘制的桑基图,描绘了拿破仑的俄国远征

图 4.22 – 查尔斯·约瑟夫·米纳德绘制的桑基图,描绘了拿破仑的俄国远征

桑基图常用于描绘各个领域的许多应用。桑基图在生物技术和健康领域的应用包括以下内容:

  • 临床试验期间的药物候选描述

  • 合成分子的工艺流程图

  • 微生物发酵的工艺流程图

  • 项目流程图和成功率

  • 描述组织内部成本的财务图

让我们可视化一个公司药物候选管道的简单示例。我们将考虑候选人的总数,按阶段进行分类,最后按小分子或大分子进行指定。我们可以利用 Plotly 库来帮助我们完成这项工作:

import plotly.graph_objects as go
fig = go.Figure(data=[go.Sankey(node = dict(pad = 50, 
      thickness = 10,
                 line = dict(color = "black", width = 0.5),
                 label = ["Drug Candidates", "Phase 1", "Phase 2",
                 "Phase 3", "Small Molecules", "Large Molecules"],
                 color = "blue"),
                 link = dict(
                 source = [0,  0, 0, 1,  2, 3, 1, 2, 3],
                 target = [1,  2, 3, 4,  4, 4, 5, 5, 5],
                 value = [15, 4, 2, 13, 3, 1, 2, 1, 1]
  ))]) 

这段代码相当长且复杂,让我们尝试将其分解。figure对象包含我们需要考虑的几个参数。第一个是pad,它描述了可视化中*节点*之间的间距。第二个描述了节点的thickness值。第三个设置了线的colorwidth值。第四个包含节点的label名称。最后,我们到达了数据,其结构与我们习惯的方式略有不同。在这种情况下,数据集被分为一个source数组(或起源),一个target数组,以及与之相关的value数组。从左侧开始,我们看到source的第一个值是节点0,它流向节点1targetvalue15。以这种方式阅读过程使数据流对用户或开发者更清晰。最后,我们可以使用show()继续绘制图像:

fig.update_layout(title_text="Drug Candidates within a Company Pipeline", font_size=10)
fig.show()

下图显示了前面代码的输出:

图 4.23 – 表示公司流程的桑基图

图 4.23 – 表示公司流程的桑基图

桑基图是展示随时间或按类别信息流动或转移的绝佳方式。在上一个例子中,我们探讨了其在管道中小分子和大分子中的应用。现在让我们看看我们如何可视化这些分子。

可视化小分子

当涉及到小分子时,我们可以使用各种软件平台和在线服务以多种方式可视化它们。幸运的是,存在一个优秀的库,通常用于rdkit库,可以使用pip安装:

import pandas as pd
import rdkit
from rdkit import Chem

我们可以解析在本教程中较早导入的 DataFrame,并通过索引提取一个示例smiles字符串。然后,我们可以使用rdkit库中的Chem类的MolFromSmiles()函数,以smiles字符串作为单一参数来创建分子对象:

df = pd.read_csv("../../datasets/dataset_toxicity_sd.csv")
m = Chem.MolFromSmiles(df["smiles"][5])
m

这个变量的输出可以在图 4.24中看到:

图 4.24 – 小分子的表示

图 4.24 – 小分子的表示

我们可以通过查看不同的索引值来检查另一个分子的结构:

m = Chem.MolFromSmiles(df["smiles"][20])
m

这次,我们的输出如下:

图 4.25 – 小分子的表示

图 4.25 – 小分子的表示

除了渲染小分子的打印版描述外,rdkit库还支持与小型分子分析、预测和计算相关的广泛功能。此外,该库还支持使用电荷计算以及相似性图:

from rdkit.Chem import AllChem
from rdkit.Chem.Draw import SimilarityMaps
AllChem.ComputeGasteigerCharges(m)
contribs = [m.GetAtomWithIdx(i).GetDoubleProp('_GasteigerCharge') for i in range(m.GetNumAtoms())]
fig = SimilarityMaps.GetSimilarityMapFromWeights(m, 
             contribs, contourLines=10, )

前面代码的输出可以在图 4.26中看到:

图 4.26 – 小分子电荷的表示

图 4.26 – 小分子电荷的表示

现在我们已经了解了如何使用 RDKit 来表示小分子,那么让我们来看看这个工具在处理大分子时的应用。

可视化大分子

有许多专为研究和发展目的设计用于可视化、模拟和分析大分子的 Python 库。目前,最常用的库之一是py3Dmol。该库专门用于在 Jupyter Notebook 环境中进行 3D 可视化,允许创建可用于发表的 3D 蛋白质可视化图像。该库可以通过pip框架轻松下载。

  1. 我们可以使用py3dmol库,并在以下函数中直接查询蛋白质结构来开始这个视觉化的开发:

    import py3Dmol
    largeMol = py3Dmol.view(query='pdb:6xmk', 
                               width=600,
                               height=600)
    
  2. 导入库后,可以使用py3Dmol中的view类指定一个名为lm的新变量对象。此函数有三个主要参数。第一个是感兴趣蛋白的标识,即6xmk。第二个和第三个参数分别是显示窗口的宽度和高度。有关 PDB 文件更多信息,请访问stick参数:

     largeMol.setStyle({'stick':{'color':'spectrum'}})
     largeMol
    

    执行此行代码后,我们得到该分子的以下图像:

    图 4.27 – 大分子或蛋白质的球棍模型表示

    图 4.27 – 以球棍模型表示的大分子或蛋白质

  3. 注意到我们添加了一个stick参数来显示最后一个结构。我们可以将此参数更改为cartoon来查看基于其二级结构的蛋白质的卡通表示:

    largeMol.setStyle({'cartoon':{'color':'spectrum'}})
    largeMol
    

    当执行这一行代码时,我们得到以下分子的图像:

    图 4.28 – 大分子或蛋白质的二级结构表示

    图 4.28 – 大分子或蛋白质的二级结构表示

  4. 可以添加许多其他更改和论点来定制此可视化,以适应用户的特定目标。其中之一是添加一个范德华表面,这允许展示分子相互作用可能发生的区域。我们将只将此表面添加到这个蛋白质的两个链中的一个

    lm = py3Dmol.view(query='pdb:6xmk')
    chA = {'chain':'A'}
    chB = {'chain':'B'}
    lm.setStyle(chA,{'cartoon': {'color':'spectrum'}}) 
    lm.addSurface(py3Dmol.VDW, {'opacity':0.7, 'color':'white'}, chA)
    lm.setStyle(chB,{'cartoon': {'color':'spectrum'}})
    lm.show()
    

    我们可以在图 4.29中看到此代码的输出:

图 4.29 – 一个大分子或蛋白质的二级结构表示,其中一个链上有一个范德华表面

图 4.29 – 一种表示大分子或蛋白质二级结构的方法,其中一个链上带有范德华表面

近年来,对大分子,或称生物制品的研究,在生物技术领域显示出巨大的增长。在本章中,我们简要介绍了用于可视化这些复杂分子的许多方法之一——这是任何生物信息学项目的重要第一步。

摘要

可视化可以是有用的、强大的、令人信服的工具,有助于说明观点并引导特定方向的对话。为了创建适当的可视化,需要采取某些步骤和技术,以确保您的图表正确且有效。

在本章中,我们探讨了创建适当可视化时需要遵循的六个主要步骤。我们还探讨了在 Python 范围内许多不同的方法和库,以帮助您为特定的目标创建和设计可视化。我们探讨了某些基本可视化,如条形图、直方图和散点图,以一次分析几个特征。我们还探讨了更复杂可视化,如配对图、热图、桑基图和分子表示,通过这些我们可以探索更多特征。

我们还提到了相关性的概念以及某些特征如何与其他特征建立关系——这是一个我们将在下一章关注机器学习时更详细探讨的概念。

第二部分:开发和训练模型

在本节中,您将了解解析数据和训练模型的过程。我们首先介绍两种机器学习形式,并通过生物技术行业的实际案例强化了多个模块/教程的定义。

本节包括以下章节:

  • 第五章理解机器学习

  • 第六章无监督机器学习

  • 第七章监督机器学习

  • 第八章理解深度学习

  • 第九章自然语言处理

  • 第十章探索时间序列分析

第五章:理解机器学习

在过去的几年里,你很可能已经听到了许多流行词汇,如人工智能AI)、机器学习ML)和深度学习DL),这些词汇在大多数主要行业中引起了波澜。尽管这些短语在公司全体员工和领导层的会议中往往被互换使用,但每个短语实际上都指代一个独特概念。因此,让我们更仔细地看看这些短语实际上指的是什么。

人工智能通常指的是由软件和机器展现出的类似人类智能的总体领域。我们可以将人工智能视为一个空间,它包含了本书范围内我们将讨论的许多主题。

在人工智能领域,存在一个我们称之为机器学习的子领域。机器学习可以被定义为研究算法与数据相结合以开发预测模型

在机器学习领域,还存在另一个我们称之为深度学习的子领域。我们将DL定义为通过使用人工神经网络特别应用机器学习

既然我们已经更好地理解了这些术语之间的差异,让我们更详细地定义机器学习的概念。根据你询问的人不同,你将遇到几种不同的机器学习定义。物理学家倾向于将定义与性能优化的应用联系起来,而数学家倾向于将定义与统计概率联系起来,最后,计算机科学家倾向于将定义与算法代码联系起来。在某种程度上,三者都是技术正确的。为了本书的目的,我们将机器学习定义为研究使用计算机代码开发数学优化模型,该模型能够从历史数据中学习泛化,以解锁有用的见解并做出预测。

尽管这个定义可能看起来很简单,但大多数有经验的面试候选人仍然倾向于在定义这个概念时感到困难。请注意我们在这里使用的确切措辞,因为它可能在未来的某个场合证明是有用的。

在本章的进程中,我们将探讨机器学习的各个方面,并回顾开发预测模型时开发者必须采取的一些最常见步骤。

在本章中,我们将回顾以下主要主题:

  • 理解机器学习

  • 过拟合与欠拟合

  • 开发机器学习模型

考虑到所有这些,让我们开始吧!

技术要求

在本章中,我们将应用我们对pandasnumpy的理解。此外,我们还将使用一些机器学习库,如sklearntensorflow。回想一下,安装新库的过程可以通过命令行完成:

$ pip install library-name

让我们开始!

理解机器学习

在介绍中,我们广泛地定义了与本书相关的机器学习的概念。有了这个定义,现在让我们看看一些例子来阐述我们的定义。在最广泛的意义上,机器学习可以分为四个领域:分类回归聚类降维。这四个类别通常被称为数据科学领域。数据科学是一个非常广泛的概念,用于指代与数据相关的各种应用,以及人工智能及其子集。我们可以在图 5.1中可视化这些领域之间的关系:

图 5.1 – 人工智能与其他领域相关的领域

图 5.1 – 人工智能与其他领域相关的领域

在心中牢记这些概念,让我们更详细地讨论这四种机器学习方法。

X)及其后续的输出值(通常称为ŷ)用于训练一个分类器。这个分类器然后可以用于对新数据和未见数据做出预测。我们可以在图 5.2中直观地表示这一点:

图 5.2 – 分类模型的示例

图 5.2 – 分类模型的示例

聚类在模型结果为标签(或类别)的意义上与分类相似,但这里的区别在于聚类模型不是基于预定义的类别列表进行训练,而是基于对象之间的相似性。聚类模型然后将数据点分组到中。形成的簇的总数不一定事先就知道,这很大程度上取决于模型训练的参数。在以下示例中,使用原始数据集形成了三个簇:

图 5.3 – 聚类模型的示例

图 5.3 – 聚类模型的示例

另一方面,当涉及到X)及其后续的输出值(通常称为ŷ)用于训练一个回归器时。这个回归器然后可以用于对新数据和未见数据做出预测:

图 5.4 – 回归模型的示例

图 5.4 – 回归模型的示例

最后,当涉及到降维时,机器学习不仅可以用于预测值的目的,而且在将数据从高维表示转换为低维表示的意义上得到应用。以我们在前几章中使用的庞大的毒性数据集为例。我们可以应用如主成分分析(PCA)这样的方法,通过结合这些特征的重要性,将 10 多个特征列减少到只有两到三列。我们将在第七章“理解监督机器学习”中更详细地探讨这一点。我们可以在图 5.5中看到这一过程的视觉表示:

图 5.5 – 降维模型的一个示例

图 5.5 – 降维模型的一个示例

机器学习领域非常广泛、复杂,远远超出了我们刚才提到的四个基本示例。然而,ML 模型的最常见应用往往集中在 预测一个类别预测一个值在数据中揭示隐藏的见解 上。

作为科学家,我们总是希望尽可能好地组织我们的思想,而且正如所发生的那样,我们刚才讨论的概念可以分为两大类:X) 和输出 (ŷ)。我们称这种方法为 监督 方法,因为模型是经过(监督)训练的,其输出标签对应于哪个输入值。另一方面,UML 包括仅知道输入 (X) 的 ML 模型。回顾我们刚才讨论的四种方法,我们可以根据这两种学习方法来划分它们,即 分类回归 属于 SML,而 聚类降维 属于 UML:

图 5.6 – 监督学习和无监督学习的表示

图 5.6 – 监督学习和无监督学习的表示

在接下来的章节中,我们将探讨许多属于这四个一般类别的流行 ML 模型和算法。随着你的学习,我鼓励你开发自己的思维导图,并将每个四个类别进一步分支到你将要学习的所有不同模型中。例如,我们将在本章的 保存模型以部署 部分探讨一个 朴素贝叶斯 模型,这可以添加到 图 5.6分类 分支中。也许你可以为每个模型添加一些关于模型本身的注释。在准备技术面试时,地图或视觉辅助工具可能很有用。

在我们开发的每个模型中,我们将遵循一系列特定的步骤来获取我们的数据,对其进行预处理,构建模型,评估其性能,最后,如果模型足够好,将其部署给我们的最终用户或数据工程师。在我们开始开发模型之前,让我们讨论一下被称为 过拟合欠拟合 的常见危险。

过拟合和欠拟合

在 SML 的背景下,我们将通过用历史数据拟合我们的模型来准备我们的模型。拟合模型的过程通常输出一个度量,表示模型在多大程度上能够泛化到与训练模型的数据相似的数据。使用这个输出,通常以精确度准确度召回率的形式,我们可以确定我们实施的方法或更改的参数是否对我们的模型产生了积极的影响。如果我们回顾本章前面关于 ML 模型的定义,我们特别将它们称为从历史数据中学习泛化的模型。能够从历史数据中学习的模型被称为拟合良好的模型,从某种意义上说,它们能够在新的、未见过的数据上准确执行。

也有一些情况是模型欠拟合。欠拟合的模型在数据集上通常表现不佳,这意味着它们没有学会很好地泛化。这些情况通常是由于为给定的数据集选择了不适当的模型,或者对该模型的参数/超参数设置不足。

重要提示

参数和超参数:请注意,虽然参数和超参数是经常可以互换使用的术语,但两者之间是有区别的。超参数是模型估计器没有学习到的参数,必须手动调整。

也有一些情况是模型过拟合。过拟合的模型是那些对数据集了解得有点太多的模型,这意味着它们不再是学习而是在记忆。过拟合通常发生在模型开始从数据集中的噪声中学习,并且不再能够很好地泛化到新数据时。拟合良好、过拟合和欠拟合模型之间的区别可以在图 5.7中看到:

图 5.7 – 过拟合和欠拟合数据的表示

图 5.7 – 过拟合和欠拟合数据的表示

每个数据科学家的目标都是开发一个在您感兴趣的指标上具有最佳性能的平衡模型。确保您正在开发一个既不过度拟合也不会欠拟合的平衡模型的最佳方法之一是在事先分割您的数据集,并确保模型只训练在数据的一个子集上。我们可以将数据集分为两类:训练数据测试数据(也常被称为验证数据)。我们可以使用训练数据集来训练模型,并使用测试数据集来测试(或验证)模型。用于此目的的最常见的类是来自sklearntrain_test_split()类。如果您将数据集视为X是您的输入变量,ŷ是您的输出,您可以使用以下代码片段来分割数据集。首先,我们导入数据。然后,我们隔离我们感兴趣的特性并输出它们各自的变量。然后,我们实现train_test_split()函数来相应地分割数据:

import pandas as pd
from sklearn.model_selection import train_test_split
df = pd.read_csv("../../datasets/dataset_wisc_sd.csv")
X = df.drop(columns = ["id", "diagnosis"])
y = df.diagnosis.values
X_train, X_test, y_train, y_test = train_test_split(X, y)

我们可以在图 5.8中可视化分割后的数据集:

图 5.8 – 用于训练和测试的数据的视觉表示

图 5.8 – 用于训练和测试的数据的视觉表示

以这种方式分割数据后,我们现在可以使用X_trainy_train来训练我们的模型,以及使用X_testy_test来测试(或验证)我们的模型。默认分割比例是 75%训练数据到 25%测试数据;然而,我们可以传递test_size参数来改变这个比例。我们通常希望尽可能多地训练数据,但仍然保留有意义的未见过数据量,因此75/25在行业中是一个普遍接受的比率。有了这个概念,让我们继续开发一个完整的机器学习模型。

开发机器学习模型

作为终端用户,我们每天都会与许多机器学习模型进行交互,而我们可能甚至没有意识到这一点。回想一下你今天所做的一切活动:浏览社交媒体、查看电子邮件,或者你可能访问了一家商店或超市。在这些环境中,你很可能已经与一个已经部署的机器学习模型进行了交互。在社交媒体上,你信息流中显示的帖子很可能是监督推荐模型的输出。你打开的电子邮件很可能是使用分类模型过滤掉的垃圾邮件。最后,杂货店中商品的种类数量很可能是回归模型的输出,允许它们预测今天的需求。在这些模型中,大量的时间和精力都投入到了确保它们能够正确运行。在这些情况下,虽然模型的开发很重要,但最重要的是如何提前准备数据。作为科学家,我们总是倾向于尽可能好地组织我们的思想和过程,因此让我们为开发机器学习模型的过程组织一个工作流程:

  1. 数据获取:通过 SQL 查询、本地导入或 API 请求收集数据

  2. EDA 和预处理:理解和清理数据集

  3. 模型开发和验证:训练模型并验证结果

  4. 部署:使你的模型可供最终用户使用

考虑到这些步骤,让我们继续开发我们的第一个模型。

我们首先导入我们的数据。我们将使用一个我们尚未使用过的新数据集,称为“威斯康星乳腺癌”数据集。这是一个多元数据集,于 1995 年发布,包含数百个乳腺癌肿瘤的实例。这些肿瘤以测量值的形式描述,我们将将其用作特征X)。数据集还包括有关每个实例的恶性信息,我们将使用它作为我们的输出标签ŷ)。鉴于我们既有输入数据又有输出数据,这就需要使用一个分类模型。

数据获取

让我们导入我们的数据并检查其整体形状:

import pandas as pd
import numpy as np
df = pd.read_csv("../../datasets/dataset_wisc_sd.csv")
df.shape

我们注意到有 569 行数据(我们通常称之为观测值)和 32 列数据(我们通常称之为特征)。我们通常希望我们的数据集拥有比特征多得多的观测值。关于两者之间理想比例并没有金科玉律,但通常你希望观测值至少比特征多 10 倍。所以,对于 32 列,你希望至少有 320 个观测值——在这个案例中我们确实做到了!

探索性数据分析与预处理:

探索性数据分析EDA)可以说是任何机器学习项目中最重要的且耗时最长的步骤之一。这一步骤通常包括许多更小的步骤,其目标如下:

  • 理解数据和其特征。

  • 解决任何不一致或缺失值。

  • 检查特征之间的任何相关性。

请注意,我们执行这些步骤的顺序可能因数据集而异。考虑到所有这些,让我们开始吧!

检查数据集

在导入数据集后的第一步之一是快速检查数据的质量。回想一下,我们可以使用方括号([])来指定感兴趣的列,并且我们可以使用head()tail()函数来查看数据的前五行或后五行:

df[["id", "diagnosis", "radius_mean", "texture_mean", "concave points_worst"]].head()

我们可以在图 5.9中看到此代码的结果:

图 5.9 – 威斯康星乳腺癌数据集的样本

图 5.9 – 威斯康星乳腺癌数据集的样本

我们可以快速了解数据组织得非常好,并且从第一眼看上去,似乎没有任何问题值,例如不寻常的字符或缺失值。查看这些选择的列,我们注意到在开头有一个唯一的标识符,由整数组成,后面跟着诊断(M = malignantB = benign)由字符串组成。其余的列都是特征,它们看起来都是浮点数(小数)数据类型。我鼓励你扩展前面表格的范围,并探索这个数据集中所有的其他特征。

除了探索值之外,我们还可以探索pandas库中describe()函数提供的某些汇总统计。使用此函数,我们可以了解总数,以及一些描述性统计,如平均值、最大值和最小值:

df[["id", "diagnosis", "radius_mean", "texture_mean", "perimeter_mean", "area_mean", "concave points_worst"]].describe()

此函数的输出可以在以下屏幕截图中看到:

图 5.10 – DataFrame 的某些汇总统计表

图 5.10 – DataFrame 的某些汇总统计表

检查代码,我们注意到我们请求了七个列的统计信息,然而,表中只出现了五个。我们可以看到id值(它们是主键唯一标识符)在这里进行了汇总。这些值没有意义,因为一组主键的平均值、最大值和最小值告诉我们什么都没有。我们可以暂时忽略这个列。我们还请求了diagnosis列;然而,diagnosis列不使用数值。相反,它包含字符串。最后,我们看到concave points_worst特征也没有包含在这个表中,这表明数据类型由于某种原因不是数值。我们将在下一节清理数据时更仔细地查看这一点。

清理值

在处理机器学习项目时,清理数据集中的值是其中最重要的步骤之一。数据科学家在描述模型时经常说“垃圾输入,垃圾输出”。如果你想拥有一个强大的预测模型,那么确保支持它的数据质量良好是一个重要的第一步。

首先,让我们更仔细地查看数据类型,因为这里可能存在一些不一致性。我们可以使用以下代码来获取每个 32 个列的数据类型感:

df.dtypes

我们可以在图 5.11中看到这段代码的输出,其中列出了列名及其相应的数据类型:

图 5.11 – 一个数据集中所有列及其相应数据类型的列表

图 5.11 – 一个数据集中所有列及其相应数据类型的列表

查看列出的数据类型,我们看到id列被列为整数,而diagnosis列被列为对象,这似乎与它在图 5.9中看起来像单个字母字符串的事实一致。查看特征,它们都被列为浮点数,这与我们之前看到的一致,除了一个特征:concave points_worst。这个特征被列为对象,表明它可能是一个字符串。我们之前提到,这个列由浮点值组成,因此这个列本身应该是浮点类型。让我们早点看看这个不一致性。我们可以尝试将列转换为浮点类型,而不是使用astype()函数:

df['concave points_worst'] = df['concave points_worst'].astype(float)

然而,你会发现这段代码会出错,表明存在一行包含\\n字符,并且它无法将字符串转换为浮点数。这被称为换行符,它是你在处理数据集时遇到的最常见项目或杂质之一。让我们继续前进,确定包含此字符的行,并决定如何处理它。我们可以使用contains()函数来查找特定字符串的所有实例:

df[df['concave points_worst'].str.contains(r"\\n")]

这个函数的输出显示,只有索引为146的行包含这个字符。让我们更仔细地查看146行的特定单元格:

df["concave points_worst"].iloc[146]

我们看到单元格包含0.1865\\n\\n字符串。看起来字符被打印了两次,而且只在这一行。如果我们打开 CSV 文件并手动纠正这个值,那很容易,因为这种情况只发生了一次。然而,如果这个字符串出现了 10 次或 100 次呢?幸运的是,我们可以使用一个replace()函数来替换它们。我们可以将这个函数特别链接到df上,而不是单个列,以确保函数解析整个 DataFrame:

df = df.replace(r'\\n','', regex=True)

正则表达式是一个强大的工具,你将经常依赖它来完成各种文本匹配和清理任务。你可以使用正则表达式函数来删除空格、数字、字符或字符的异常组合。我们可以再次检查特定单元格的值来双重验证正则表达式函数的成功:

df["concave points_worst"].iloc[146]

现在的值仅为0.1865,这表明函数实际上成功了。我们现在可以使用astype()函数将列的类型转换为浮点,然后使用df.dtypes来确认是否列出了正确的数据类型。

到目前为止,我们已经能够解决无效字符进入数据集的问题。然而,对于缺失的项怎么办?我们可以使用 isna() 函数快速检查我们的数据集,以确定是否有任何缺失值:

df.isna().values.sum()

返回的值显示,有七行数据中存在缺失值。回想一下,在 第四章使用 Python 可视化数据 中,我们讨论了几种处理缺失值的方法。鉴于我们有一个足够大的数据集,使用 dropna() 函数简单地删除这些几行是合适的:

df = df.dropna()

在实现函数前后,我们可以检查数据集的形状,以确保确实删除了正确的行数。

在数据集清理之前花些时间是推荐的,因为它将有助于防止后续出现问题和异常错误。始终重要的是要检查 数据类型缺失值

理解数据的意义

现在,让我们更仔细地查看数据集内的某些数据,从第二列的输出值开始。我们知道这些值对应于标签,M 代表 恶性B 代表 良性。我们可以使用 value_counts() 函数来确定每个类别的总和:

df['diagnosis'].value_counts()

结果显示,良性肿块有 354 个实例,恶性肿块有 208 个实例。我们可以使用 seaborn 库来可视化这个比例:

sns.countplot(df['diagnosis'])

以下是可以看到此代码输出的内容:

图 5.12 – 展示每个类别的实例数量的条形图

图 5.12 – 展示每个类别的实例数量的条形图

在大多数机器学习模型中,我们试图确保输出列是 平衡良好 的,即类别大致相等。在包含例如 95 行恶性观察和 5 行良性观察的不平衡数据集上训练模型会导致性能不佳的不平衡模型。除了可视化诊断或输出列之外,我们还可以使用我们回顾过的 pairplot() 函数来可视化特征,以了解任何趋势或相关性。我们可以使用一些特征来实现这一点:

sns.pairplot(df[["diagnosis", "radius_mean", "concave points_mean", "texture_mean"]], hue = 'diagnosis')

下面的图表显示了这一结果:

图 5.13 – 选择特征的配对图

图 5.13 – 选择特征的配对图

查看这些最后的几个图表,我们注意到数据集的两个簇之间存在明显的分离。簇似乎表现出正态分布的一些特征,即大多数点都集中在中心附近,而远离中心的点较少。鉴于这种性质,我们可能首先尝试在这个数据集中使用的模型是朴素贝叶斯分类器,这种分类器通常适用于此类数据。然而,我们将在本章的后面部分更详细地讨论这个模型。

在这些图表中的每一个,我们都看到两个类别之间存在某种程度的重叠,这表明仅凭两列数据不足以保持良好的分离度。因此,我们可以确保我们的机器学习模型利用更多的列,或者我们可以尝试消除可能造成这种重叠的任何潜在异常值——或者我们可以两者都做!

首先,我们可以利用一些描述性统计量。具体来说,我们可以使用dfm。然后,我们可以使用radius_mean特征定义第一四分位数(Q1)和第三四分位数(Q3):

dfm = df[df["diagnosis"] == "M"]
Q1 = dfm['radius_mean'].quantile(0.25)
Q3 = dfm['radius_mean'].quantile(0.75)
IQR = Q3 - Q1

然后,我们可以打印这些变量的输出,结合mean()median()函数来确定 IQR,以了解数据的分布。我们可以使用seaborn中提供的boxplot()函数将这些指标与上下限范围一起可视化:

sns.boxplot(x='diagnosis', y='radius_mean', data=df)

这给我们带来了图 5.14

图 5.14 – 半径均值特征的箱线图

图 5.14 – 半径均值特征的箱线图

使用上下限范围,我们可以使用pandas库中的query()类过滤 DataFrame,排除任何超出此范围的数据:

df = df.query('(@Q1 - 1.5 * @IQR) <= radius_mean <= (@Q3 + 1.5 * @IQR)')

执行代码后,我们已经成功从数据集中移除了几个异常值。如果我们继续使用前面的散点图之一重新绘制数据,我们会看到虽然重叠确实有所减少,但两个类别之间仍然存在相当大的重叠,这表明我们开发的任何未来模型都需要利用多个列以确保在开发鲁棒分类器时能够实现足够的分离。在我们开始训练任何分类器之前,我们首先需要解决特征中可能存在的任何相关性

寻找相关性

经过过滤掉异常值后,我们现在可以开始仔细观察数据集中特征之间的任何相关性。鉴于这个数据集包含 30 个特征,我们可以利用我们在第四章,“使用 Python 可视化数据”中实现的corr()类。我们可以从seaborn创建一个corr()函数和heatmap()函数:

f, ax=plt.subplots( figsize = (20,15))
sns.heatmap(df.corr(), annot= True, fmt = ".1f", ax=ax)
plt.xticks(fontsize=18)
plt.yticks(fontsize=18)
plt.title('Breast Cancer Correlation Map', fontsize=18)
plt.show()

这段代码的输出可以在图 5.15中看到,显示了一个各种特征的散点图,其中相关性最高的特征以较浅的颜色显示,相关性最低的特征以较深的颜色显示:

图 5.15 – 展示特征相关性的热图

图 5.15 – 展示特征相关性的热图

当我们查看这个热图时,我们看到这个数据集中多个特征之间存在大量的相关性。例如,radius_worst 特征与 perimeter_meanarea_mean 特征之间存在着非常强的相关性。当数据集中的独立变量或特征之间存在强相关性时,这被称为 corr() 函数,并创建这些值的矩阵。然后我们可以选择上三角(热图的一半)并识别相关性大于 0.90 的特征:

corr_matrix = df.corr().abs()
upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(np.bool))
to_drop = [column for column in upper.columns if any(upper[column] > 0.90)]

to_drop 变量现在代表一个应该删除的列的列表,以确保我们设置的阈值以上的任何相关性都能被有效移除。注意,我们使用了 列表推导(这是我们曾在 第二章介绍 Python 和命令行)来快速有效地遍历这些值。然后我们可以继续从我们的数据集中删除这些列:

df.drop(to_drop, axis=1, inplace=True)

我们可以再次绘制热图以确保任何潜在的多重共线性问题得到解决:

图 5.16 – 展示特征相关性的热图,无多重共线性

图 5.16 – 展示特征相关性的热图,无多重共线性

注意,高度相关的特征组不再存在。现在我们已经解决了相关性问题,不仅确保了我们创建的任何潜在模型都不会因任何与 多重共线性 相关的性能问题而受到影响,而且无意中还将数据集的特征列从 30 列减少到只有 19 列,这使得处理和可视化变得稍微容易一些!现在数据集已经完全预处理完毕,我们现在可以开始训练并准备一些机器学习模型。

开发和验证模型

现在数据已经准备好,我们可以探索一些模型。回想一下,我们在这里的目标是开发一个 分类 模型。因此,我们的第一步将是分离我们的 Xŷ 值。

  1. 我们将创建一个变量 X,代表数据集中所有的特征(排除 iddiagnosis 列,因为这些不是特征)。然后我们将创建一个变量 y,代表输出列:

    X = df.drop(columns = ["id", "diagnosis"])
    y = df.diagnosis.values
    

    在我们将要处理的大多数数据集中,我们通常会看到值的大小有很大的差异,也就是说,一列可能是 1,000 的数量级,而另一列可能是 0.1 的数量级。这意味着具有远大值的特征会被模型认为对预测有更大的贡献——这并不正确。例如,考虑一个项目,我们试图使用 30 个不同的特征来预测分子的亲脂性,其中一个特征是分子量——这个特征具有显著大的值,但贡献并不大。

  2. 为了应对这个挑战,数据集中的值必须使用sklearn库中的StandardScaler()函数进行标准化:

    from sklearn.preprocessing import StandardScaler
    scaler = StandardScaler()
    X_scaled = pd.DataFrame(scaler.fit_transform(X), columns = X.columns)
    
  3. 现在特征已经标准化,我们的下一步是将数据分成我们的训练集测试集。回想一下,训练集的目的是训练模型,测试集的目的是测试模型。这样做是为了避免在开发过程中的任何过拟合

    from sklearn.model_selection import train_test_split
    X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, random_state=40)
    

    现在数据已经分成四个变量,我们现在可以开始训练几个模型,首先是高斯朴素贝叶斯分类器。这个模型是一个基于贝叶斯定理应用的监督算法。这个模型被称为朴素,因为它假设每个观测值的特征是相互独立的,这很少是真实的。然而,这个模型仍然表现出强大的性能。高斯朴素贝叶斯分类器背后的主要思想可以从概率的角度来考察。为了解释我们的意思,考虑以下方程:

    这表示标签的概率(给定一些数据)等于数据(给定标签——高斯,给定正态分布)的概率乘以标签的概率(先验概率),所有这些除以数据的概率(预测先验概率)。鉴于这样一个模型的简单性,朴素贝叶斯分类器在相对于更复杂的模型时可以非常快地使用。

  4. 让我们看看它的实现。我们首先导入我们感兴趣的库:

       from sklearn.naive_bayes import GaussianNB
       from sklearn.metrics import accuracy_score
    
  5. 接下来,我们可以创建一个实际模型的实例,我们将这个变量称为gnb_clf

       gnb_clf = GaussianNB()
    
  6. 然后,我们可以使用之前分离开的训练数据集来拟合或训练模型:

       gnb_clf.fit(X_train, y_train)
    
  7. 最后,我们可以使用训练好的模型对测试数据进行预测,并将结果与已知值进行比较。我们可以使用一个简单的准确率分数来测试模型:

    gnb_pred = gnb_clf.predict(X_test)
       print(accuracy_score(gnb_pred, y_test))
      0.95035
    

    有了这个,我们已经成功开发了一个大约 95%准确率的模型——对我们第一个模型来说,这真是个不错的开始!

  8. 虽然准确率始终是一个出色的指标,但它不是我们用来评估模型性能的唯一指标。我们还可以使用sklearn提供的classification_report()函数:

    from sklearn.metrics import classification_report
    print(classification_report(gnb_pred, y_test))
    

    通过查看以下输出,我们可以看到我们感兴趣的两种类别(B 和 M)及其相应的指标:precisionrecallf1-score

图 5.17 – Naïve Bayes 分类器的分类报告

图 5.17 – Naïve Bayes 分类器的分类报告

我们将在第七章“理解监督机器学习”中更详细地讨论这些指标。现在,我们可以看到所有这些指标都很高,这表明模型表现相当不错。

保存模型以部署

当一个机器学习模型经过训练并在合理的准确度水平上运行时,我们可能希望使这个模型可供他人使用。然而,我们不会直接将数据或代码交付给数据工程师以部署模型到生产环境中。相反,我们希望交付一个单一的已训练模型,他们可以将其部署而无需担心任何移动部件。幸运的是,有一个名为pickle的出色库可以帮助我们将模型收集成一个单一实体,从而允许我们保存模型。回想一下,我们在第二章“Python 和命令行入门”中探讨了pickle库。我们通过使用dump()函数来pickle一个模型,例如我们命名为gnb_clf的模型:

import pickle
pickle.dump(gnb_clf, open("../../models/gnb_clf.pickle", 'wb'))

为了证明模型确实正确保存,我们可以使用load()函数加载它,然后再次计算准确度得分:

loaded_gnb_clf = pickle.load(open("../../models/gnb_clf.pickle", 'rb'))
loaded_gnb_clf.score(X_test, y_test)

注意,这个评分计算的结果与之前看到的相同(95%),这表明模型确实正确地保存了!

摘要

在本章中,我们迈出了雄心勃勃的一步,去理解机器学习(ML)中一些最重要和有用的概念。我们回顾了用于描述该领域与人工智能(AI)领域相关的各种术语,检查了机器学习的主要领域以及监督学习无监督学习的统治类别,然后继续探讨为给定数据集开发机器学习模型的全过程。

在开发我们的模型时,我们探索了许多有用的步骤。我们探索并预处理了数据,以消除不一致性和缺失值。我们还对数据进行详细了解,并随后解决了与多重共线性相关的问题。接下来,我们开发了一个高斯 Naïve Bayes分类模型,它以稳健的 95%准确率运行——而且是在我们的第一次尝试中!最后,我们查看了一种数据科学家将完全训练好的模型交给数据工程师以将机器学习模型投入生产的最常见方式。

尽管我们在这章中花时间理解了监督分类器范围内的机器学习,但在下一章中,我们将通过训练几个无监督模型,获得对细微差别和差异的更深入理解。

第六章:无监督机器学习

通常,你将在课程和培训中遇到的许多数据科学教程都围绕监督机器学习(SML)领域展开,在该领域中,数据和相应的标签被用来开发预测模型以自动化任务。然而,在现实世界的数据中,预先标记或分类数据的可用性很少,你将遇到的多数数据集将以原始和无标签的形式存在。对于这些情况,或者主要目标是更探索性的或不是必然可自动化的,无监督机器学习的领域将非常有价值。

在本章的整个过程中,我们将探讨与聚类和降维(DR)领域相关的许多方法。我们将探讨的主要主题如下:

  • 无监督学习(UL)简介

  • 理解聚类算法

  • 教程 – 通过聚类进行乳腺癌预测

  • 理解 DR

  • 教程 – 探索 DR 模型

在心中牢记这些主题后,我们现在就可以开始着手了!

UL 简介

我们将 UL 定义为机器学习的一个子集,其中模型是在没有类别或标签存在的情况下进行训练的。与它的监督学习对应物不同,UL 依赖于模型的发展来捕捉以特征形式存在的模式,从而从数据中提取洞察。现在让我们更详细地看看 UL 的两个主要类别。

在 UL 的范围内存在许多不同的方法和技术。我们可以将这些方法分为两大类:具有离散数据(聚类)的方法和具有连续数据(DR)的方法。我们在这里可以看到这种图形表示:

图 6.1 – UL 的两种类型

图 6.1 – UL 的两种类型

在这些技术中,数据要么被分组,要么被转换,以便在不知道数据事先的标签或类别的情况下确定标签或提取洞察和表示。以我们在 第五章 中使用的乳腺癌数据集为例,理解机器学习,我们开发了一个分类模型。我们通过明确告诉模型数据中哪些观察结果是恶性的,哪些是良性的来训练模型,从而允许它通过特征学习差异。与我们的监督模型类似,我们可以训练一个无监督的聚类模型,通过将我们的数据聚类成组(恶性和良性)来做出类似的预测,而不需要事先知道标签或类别。我们可以使用许多不同类型的聚类模型,我们将在下一节中探索其中的一些,并在本章的后面部分探索其他模型。

除了聚类我们的数据外,我们还可以通过一种称为DR的方法来探索和转换我们的数据,我们将将其定义为将高维数据转换到低维空间的过程,在这个空间中保留了特征的有意义属性。数据转换可以用来将特征数量减少到几个,或者为给定数据集设计新的和有用的特征。属于这一类最受欢迎的方法之一是称为主成分分析PCA)的过程——我们将在本章后面详细探讨这个特定模型。

在这两个范畴的范围内,有一个应用广泛的领域,它还不完全是一个第三类,因为它具有广泛的应用——这被称为异常检测。在 UL 的范畴内,异常检测正如其名,是一种在未标记数据集中检测异常的方法。请注意,与聚类方法不同,其中数据集的不同标签之间通常有一个平衡(例如,50:50),但异常通常很少见,因为观察的数量通常不是平衡的。从无监督的角度来看,目前最流行的异常检测方法不仅包括聚类DR,还包括神经网络隔离森林

现在我们已经对 UL 的一些高级概念有了感觉,并且知道了我们的目标,那么现在让我们开始一些细节和每个例子。

理解聚类算法

在 UL 的范畴内,最常见的属于该类的方法是聚类分析。聚类分析背后的主要思想是将数据分组到两个或更多具有相似性质的类别中,形成组或。在本节中,我们将探讨这些不同的聚类模型,并随后将我们的知识应用于一个现实世界的场景,即开发用于检测乳腺癌的预测模型。让我们继续探索一些最常见的聚类算法。

探索不同的聚类算法

存在的不仅仅是单一的一种,而是一系列广泛的聚类算法,每种算法都有其独特的处理数据聚类的最佳方法,这取决于手头的数据集。我们可以将这些聚类算法分为两大类:层次聚类划分聚类。我们可以在下面看到这种分类的图形表示:

图 6.2 – 两种聚类算法

图 6.2 – 两种聚类算法

在考虑了这些不同的聚类领域之后,现在让我们更详细地探讨这些内容,从层次聚类开始。

层次聚类

层次聚类,正如其名所示,是一种尝试根据给定的层次结构使用两种类型的方法(聚合分解)对数据进行聚类的算法。聚合聚类是一种自下而上的方法,其中数据集中的每个观测值都被分配给其自己的聚类,随后与其他聚类合并以形成一个层次结构。另一方面,分解聚类是一种自上而下的方法,其中给定数据集的所有观测值最初在一个单独的聚类中,然后被分割。我们可以在这里看到这种图形表示:

图 6.3 – 聚类和分解聚类的区别

图 6.3 – 聚类和分解聚类的区别

考虑到层次聚类的概念,我们可以想象出许多有用的应用,这可以帮助我们在系统发育树和其他生物学领域。另一方面,也存在其他聚类方法,其中没有考虑层次结构,例如使用欧几里得距离时。

欧几里得距离

除了层次聚类之外,我们还有一套属于基于划分的聚类思想的模型。这里的核心理念是使用给定方法将数据集分割或划分成聚类。两种最常见的基于划分的聚类类型是基于距离的聚类基于概率的聚类。在基于距离的聚类中,这里的核心理念是仅根据距离(如欧几里得距离)来确定给定数据点是否属于一个聚类。一个例子是K-Means聚类算法——这是一种最常用的聚类算法,鉴于其简单性。

注意,从数学角度来看,欧几里得距离,有时也称为毕达哥拉斯距离,定义为笛卡尔坐标系上两点之间的距离。例如,对于两个点,p (p1, p2) 和 q (q1, q2),欧几里得距离可以计算如下:

在二维的上下文中,这个模型相当简单且易于计算。然而,当给定更多维度时,该模型的复杂性会增加,简单表示如下:

现在我们已经更好地理解了欧几里得距离的概念,让我们现在看看一个实际的应用,即 K-Means。

K-Means 聚类

考虑到欧几里得距离的概念,我们现在来仔细看看它如何在 K-Means 的上下文中应用。K-Means 算法试图通过将样本分离成具有相等方差的k组来聚类数据,并最小化一个标准(惯性)。该算法的目标是选择k质心以最小化惯性。

从某种意义上说,K-Means 模型非常简单,因为它只通过三个简单的步骤进行操作,如下面的图中用星号表示:

图 6.4 – K-Means 聚类步骤

图 6.4 – K-Means 聚类步骤

首先,随机初始化指定数量的k质心。其次,每个观测值(用圆圈表示)根据距离进行聚类。然后计算给定簇中所有观测值的平均值,并将质心移动到该平均值。这个过程会不断重复,直到达到预定的阈值为止,从而实现收敛。

K-Means 是最常用的聚类算法之一,鉴于其简单性和相对可接受的计算能力。它适用于高维数据,并且相对容易实现。然而,它确实有其局限性,即它假设簇是球形的,这往往会导致非球形簇的数据被错误分组。以另一个簇不是球形而是更像椭圆形的数据集为例。基于距离概念的K-Means模型的应用不会产生最准确的结果,如下面的截图所示:

图 6.5 – 非球形簇的 K-Means 聚类

图 6.5 – 非球形簇的 K-Means 聚类

当与非球形簇一起操作时,一个基于距离的模型的好替代方案是统计方法,如高斯混合模型GMM)。

GMMs

在聚类的背景下,GMMs 是一组由特定数量的高斯分布组成的算法。每个分布代表一个特定的簇。到目前为止,在这本书的范围内,我们还没有讨论高斯分布——这是一个你作为数据科学家在整个职业生涯中经常会听到并遇到的概念。让我们继续定义这个概念。

高斯分布可以被视为一个统计方程,表示围绕其均值对称分布的数据点。你经常会听到这种分布被称为钟形曲线。我们可以将高斯分布的概率密度函数表示如下:

在这里,公式 B17761_06_004代表均值,公式 B17761_06_005代表方差。请注意,此函数代表一个单一变量。当添加其他变量时,我们将开始进入多元高斯模型的空间,其中x公式 B17761_06_006代表长度为公式 B17761_06_007的向量。在一个包含k个聚类的数据集中,我们需要k个高斯分布的混合,其中每个分布都有一个均值和方差。这两个值通过称为期望最大化EM)的技术来确定。

我们将EM定义为一种算法,当某些数据被认为缺失或不完整时,它确定给定模型的适当参数。这些缺失或不完整的项被称为潜在变量,在 UL 的范围内,我们可以认为实际的聚类是未知的。请注意,如果聚类是已知的,我们将能够确定均值和方差;然而,我们需要知道均值和方差来确定聚类(想想经典的先有鸡还是先有蛋的情况)。我们可以在数据的范围内使用 EM 来确定这两个变量的适当值,以最佳地拟合模型参数。考虑到所有这些,我们现在可以更智能地讨论 GMMs。

我们之前将 GMM 定义为由多个高斯分布组成的模型。现在,我们将通过包括它是一个由多个高斯分布组成的概率模型,并利用软聚类方法来详细阐述这个定义,该方法通过基于概率而不是距离来确定数据点到给定聚类的隶属关系。请注意,这与 K-Means 形成对比,K-Means 使用硬聚类方法。使用上一节中图 6.5所示的前一个示例数据集,应用 GMM 可能会带来改进的结果,如图所示:

图 6.6 – K-Means 聚类与 GMMs

图 6.6 – K-Means 聚类与 GMMs

在本节中,我们讨论了在生物技术领域许多应用中常用的几种最常见的聚类算法。我们看到聚类被应用于生物分子数据、科学文献、制造甚至肿瘤学等领域,正如我们将在下面的教程中所体验到的。

教程 – 通过聚类预测乳腺癌

在本教程的整个过程中,我们将探讨使用Wisconsin 乳腺癌数据集来分析和预测癌症的常用聚类算法的应用,该数据集我们在第五章《理解机器学习》中应用过。当我们上次访问这个数据集时,我们从监督分类器的角度来开发模型,我们事先知道观察的标签。然而,在大多数实际场景中,事先知道标签的情况很少见。正如我们很快就会看到的,聚类分析在这些情况下可以非常有价值,甚至可以用来为数据打标签,以便在分类器的上下文中使用。在本教程的整个过程中,我们将使用数据来开发我们的模型,但假装我们不知道标签。我们只会使用已知的标签来比较我们模型的结果。考虑到这一点,让我们开始吧!

我们将首先导入我们的数据集,就像我们之前做的那样,并检查其形状,如下所示:

df = pd.read_csv("../../datasets/dataset_wisc_sd.csv")
print(df.shape)

我们注意到这个数据集中有 569 行数据。在我们之前的应用中,我们已经清理了数据以解决缺失和损坏的值。让我们继续清理这些数据,如下所示:

df = df.replace(r'\\n','', regex=True) 
df = df.dropna()
print(df.shape)

当前数据的形状由 569 行和 32 列组成,这与我们之前的数据集相匹配,我们现在可以继续进行了。

虽然我们不会使用这些标签来开发任何模型,但让我们快速看一下它们,如下所示:

import seaborn as sns
sns.countplot(df['diagnosis']);

在下面的屏幕截图中,我们可以看到有两个类别——M代表恶性,B代表良性。这两个类别并不完全平衡,但对我们聚类模型来说足够了:

图 6.7 – 两个类别的分布

图 6.7 – 两个类别的分布

为了在接下来的聚类分析步骤中更容易地比较这些标签,让我们继续将这些标签编码为数值,其中我们将M转换为1,将B转换为0,如下所示:

df['diagnosis'] = df['diagnosis'].map({'M':1,'B':0}) 

我们可以使用df.head()函数查看数据集的前几行,并确认diagnosis列确实被正确编码。接下来,我们将准备一个快速的对数图来展示几个选定的特征,如下所示:

select_feats = ["diagnosis", "radius_mean", "texture_mean", "smoothness_mean"]
sns.pairplot(df[select_feats], hue = 'diagnosis', markers=["s", "o"])

我们可以使用markers参数来指定两个不同的形状来绘制两个类别,得到以下配对图,显示了我们的特征的散点图:

图 6.8 – 选择特征的配对图

图 6.8 – 选择特征的配对图

我们的首要目标是浏览许多特征,并了解哪两个特征重叠最少或分离度最高。我们可以看到 smoothness_meantexture_mean 列有很高的重叠度;然而,radius_meantexture_mean 的重叠似乎较少。我们可以通过使用 seaborn 库绘制散点图来更仔细地观察这些特征,如下所示:

sns.scatterplot(x="radius_mean", y="texture_mean", hue="diagnosis", style='diagnosis', data=df, markers=["s", "o"])

注意,我们再次可以使用 stylemarkers 参数来塑造数据点,从而得到以下图表作为输出:

图 6.9 – 两个表现出良好分离的特征的散点图

图 6.9 – 两个表现出良好分离的特征的散点图

接下来,我们将对数据进行归一化。在统计学中,归一化或标准化可能有多种含义,有时可以互换使用。我们将定义归一化是指将值重新缩放到 [01] 的范围内。另一方面,我们将定义标准化是指将数据重新缩放到平均值和标准差均为 1。为了实现我们的当前目标,我们希望使用 StandardScaler 类对数据进行标准化,就像我们之前做的那样。回想一下,这个类通过移除均值并缩放到方差来在数据集内标准化特征,如下所示:

在这里, 是样本的标准分数, 是平均值,而 是标准差。我们可以在 Python 中使用以下代码应用此方法:

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X = df.drop(columns = ["id", "diagnosis"])
y = df.diagnosis.values
X_scaled = pd.DataFrame(scaler.fit_transform(X), columns = X.columns)

在我们的数据集缩放后,我们现在可以开始应用一些模型了。我们将从 sklearn 库中的凝聚聚类模型开始。

凝聚聚类

回想一下,凝聚聚类是一种通过递归合并聚类来形成聚类的算法。让我们继续使用我们的数据集实现凝聚聚类算法,如下所示:

  1. 首先,我们将从 sklearn 库中导入感兴趣的特定类,然后通过指定我们想要的类别数量并将连接设置为 ward(这是最常用的凝聚聚类方法之一)来创建我们模型的实例。代码如下所示:

    from sklearn.cluster import AgglomerativeClustering
    agc = AgglomerativeClustering(n_clusters=2, linkage="ward")
    
  2. 接下来,我们将我们的模型拟合到数据集上,并预测它们所属的聚类。注意在下面的代码片段中,我们使用了 fit_predict() 函数,使用前两个特征 radius_meantexture_mean,而不是整个数据集:

    agc_featAll_pred = agc.fit_predict(X_scaled.iloc[:, :2])
    
  3. 然后,我们可以使用 matplotlibseaborn 生成一个图表,显示左侧的实际(真实)结果和右侧预测的凝聚聚类结果,如下所示:

    import matplotlib.pyplot as plt
    import seaborn as sns
    plt.figure(figsize=(20, 5))
    plt.subplot(121)
    plt.title("Actual Results")
    ax = sns.scatterplot(x="radius_mean", y="texture_mean", hue=y, style=y, data=X_scaled, markers=["s", "o"])
    ax.legend(loc="upper right")
    plt.subplot(122)
    plt.title("Agglomerative Clustering")
    ax = sns.scatterplot(x="radius_mean", y="texture_mean", hue=agc_featAll_pred, style=agc_featAll_pred, data=X_scaled, markers=["s", "o"])
    ax.legend(loc="upper right")
    

    注意在前面的代码片段中使用了subplot()功能,其中122的值被用来表示1为总行数,2为总列数,2为特定索引位置的绘图。您可以在以下位置查看输出:

    图 6.10 – 聚类模型相对于实际结果的比较结果

    图 6.10 – 聚类模型相对于实际结果的比较结果

  4. 从初始估计来看,模型在区分两个聚类方面做得相当合理,尽管对实际真实结果了解甚少。我们可以使用sklearn中的accuracy_score方法来快速衡量其性能。虽然了解召回率和 f-1 分数也很重要,但为了简单起见,我们现在将坚持使用准确率。以下代码片段展示了这一过程:

    from sklearn.metrics import accuracy_score
    print(accuracy_score(y, agc_featAll_pred))
    0.832740
    

总结来说,仅使用数据集的前两个特征进行的层次聚类模型得到了大约 83%的准确率——这不是一个糟糕的初次尝试!如果您正在使用提供的代码进行跟随,我鼓励您尝试添加另一个特征,并用三个、四个或五个特征而不是两个特征来拟合模型,看看您是否能够提高性能。更好的是,探索这个数据集中提供的其他特征,看看您是否可以找到其他提供更好分离的特征,并击败我们的 83%指标。现在,让我们研究 K-Means 的性能。

K-Means 聚类

现在,让我们研究使用数据集应用K-Means聚类的应用。回想一下,K-Means 算法试图通过根据数据点的质心位置将数据划分为k个聚类来聚类数据。我们可以按照以下步骤应用 K-Means 算法:

  1. 我们将首先从sklearn库中导入KMeans类,如下所示:

    from sklearn.cluster import KMeans
    
  2. 接下来,我们可以初始化 K-Means 模型的实例,并指定聚类数量为2,迭代次数为10,初始化方法为k-means++。这种初始化设置简单地使用算法选择初始聚类中心,目的是加快收敛速度。我们可以通过一个称为调整的过程来调整参数,以最大化模型性能。以下代码片段展示了这一过程:

    kmc = KuMeans(n_clusters=2, n_init=10, init="k-means++")
    
  3. 然后,我们可以使用fit_predict()方法来拟合我们的数据,并预测每个观察值的聚类。注意在以下代码片段中,模型仅基于前两个特征进行拟合和预测结果:

    kmc_feat2_pred = kmc.fit_predict(X_scaled.iloc[:, :2])
    
  4. 最后,我们可以使用seaborn库来绘制预测结果与已知类别的真实值之间的比较结果,如下所示:

    plt.figure(figsize=(20, 5))
    plt.subplot(131)
    plt.title("Actual Results")
    ax = sns.scatterplot(x="radius_mean", y="texture_mean", hue=y, style=y, data=X_scaled, markers=["s", "o"])
    ax.legend(loc="upper right")
    plt.subplot(132)
    plt.title("KMeans Results (Features=2)")
    ax = sns.scatterplot(x="radius_mean", y="texture_mean", hue= kmc_feat2_pred , style= kmc_feat2_pred, data=X_scaled, markers=["s", "o"])
    ax.legend(loc="upper right")
    

    执行此代码后,我们得到一个散点图,显示了我们的结果,如下所示:

    图 6.11 – K-Means 聚类模型相对于实际结果的结果

    kmc_feat2_pred = kmc.fit_predict(X_scaled.iloc[:, :2])
    kmc_feat3_pred = kmc.fit_predict(X_scaled.iloc[:, :3])
    kmc_feat4_pred = kmc.fit_predict(X_scaled.iloc[:, :4])
    kmc_featall_pred = kmc.fit_predict(X_scaled.iloc[:, :])
    
  5. 接下来,使用subplot()方法,我们可以生成四个图表来展示变化,其中每个子图代表一个展示的图表。以下是所需的代码:

    plt.figure(figsize=(20, 5))
    plt.subplot(141)
    plt.title("KMeans Results (Features=2)")
    ax = sns.scatterplot(x="radius_mean", y="texture_mean", hue=kmc_feat2_pred, style=kmc_feat2_pred, data=X_scaled, markers=["s", "o"])
    ax.legend(loc="upper right")
    # Apply the same for the other plots
    

    代码执行后,我们得到以下展示结果的图:

图 6.12 – 随着特征增加的 K-Means 聚类模型的结果

图 6.12 – 随着特征增加的 K-Means 聚类模型的结果

我们可以使用仅两个特征来计算准确率,大约为 86%,而三个特征产生了 89%。然而,我们会注意到,随着更多特征的加入,准确率不仅开始趋于平稳,而且在所有特征都包含时还会下降,产生了较低的准确率 82%。请注意,当我们开始向模型添加更多特征时,我们实际上是在增加更多的维度。例如,有三个特征时,我们现在使用的是一个三维3D)模型,正如两个数据集之间的混合边界所示。在某些情况下,我们拥有的特征越多,对给定模型的压力就越大。这涉及到一个被称为维度诅咒COD)的概念,即在更多维度的情况下,空间的体积开始以惊人的速度增加,这可能会影响模型的性能。我们将在未来的教程中讨论一些可以补救的方法,特别是当我们开始讨论DR时。

总结来说,我们能够在我们的数据集上应用 K-Means 模型,并且使用前三个特征实现了相当高的准确率,达到了 89%。现在让我们继续探索统计方法如 GMM 的应用。

GMMs

现在,让我们探索 GMMs在我们数据集上的应用。回想一下,这些模型代表概率分布的混合,一个观察值属于哪个聚类的计算是基于该概率而不是欧几里得距离。考虑到这一点,让我们继续开始,如下所示:

  1. 我们可以通过从sklearn库中导入GaussianMixture类来开始,如下所示:

    from sklearn.mixture import GaussianMixture
    
  2. 接下来,我们将创建模型的一个实例,并将组件数量指定为2,将协方差类型设置为full,这样每个组件都有自己的协方差矩阵,如下所示:

    gmm = GaussianMixture(n_components=2, covariance_type="full")
    
  3. 然后,我们将使用前两个特征将数据模型拟合到我们的数据上,并预测每个观察值的聚类,如下所示:

    gmm_featAll_pred = 1-gmm.fit_predict(X_scaled.iloc[:, :2])
    
  4. 最后,我们可以使用seaborn库来绘制结果,如下所示:

    plt.figure(figsize=(20, 5))
    plt.subplot(131)
    plt.title("Actual Results")
    ax = sns.scatterplot(x="radius_mean", y="texture_mean", hue=y, style=y, data=X_scaled, markers=["s", "o"])
    ax.legend(loc="upper right")
    plt.subplot(132)
    plt.title("Gaussian Mixture Results (Features=All)")
    ax = sns.scatterplot(x="radius_mean", y="texture_mean", hue=gmm_featAll_pred, style=gmm_featAll_pred, data=X_scaled, markers=["s", "o"])
    ax.legend(loc="upper right")
    

    执行我们的代码后,我们得到以下输出,显示了数据集的实际结果与我们的预测结果相对比:

图 6.13 – GMM 相对于实际结果的结果

图 6.13 – GMM 相对于实际结果的结果

再次,我们可以看到在高斯模型中,两个类别的边界非常明确,几乎没有混合,正如实际结果所示,从而实现了大约 85%的准确率。然而,请注意,与 K-Means 模型相比,GMM 预测了一个密集的蓝色圆形分布,橙色类别的某些成员以一种非常非圆形的方式围绕它:

与之前的模型类似,我们再次尝试向这个模型添加一些更多特性,以期进一步提高其性能。然而,从下面的截图我们可以看到,尽管从左到右添加了更多特性,但模型并没有得到改善,其预测能力开始下降:

图 6.14 – 增加特征后的 GMM 结果

图 6.14 – 增加特征后的 GMM 结果

总结来说,在本教程中,我们探讨了在假设没有标签的情况下,使用聚类分析来开发各种预测模型的方法。在整个教程中,我们研究了三种最常见的聚类模型:Wisconsin 乳腺癌数据集。我们确定,相对于其他模型,使用三个特征的 K-Means 模型在性能上表现最佳,其中一些模型使用了整个数据集。我们可以推测,所有特征在预测能力方面都具有一定的意义;然而,在模型中包含所有特征会导致性能下降。我们将在下一节中探讨一些减轻这种情况的方法,这涉及到DR

理解 DR

我们接下来要讨论的UL的第二类被称为DR。正如全名所表明的,这些方法只是用来减少给定数据集中维度的数量。以一个具有大约 100 个列的高特征数据集为例——DR 算法可以用来帮助将列数减少到大约 5 个,同时保留每个原始 100 个列所包含的价值。你可以把 DR 看作是按水平方式压缩数据集的过程。结果列通常可以分为两种类型:新特征,即在称为特征工程FE)的过程中生成了具有新数值的新列,或者旧特征,即在称为特征选择的过程中只保留了最有用的列。在接下来的章节中,在 UL 的范围内,我们将更多地关注FE的方面,因为我们创建了许多其他数据集的简化版本的新特征。我们可以在这里看到这个概念的图形说明:

图 6.15 – DR 的图形表示

图 6.15 – DR 的图形表示

我们可以使用许多不同的方法来实现数据降维(DR),每种方法都有自己的流程和理论基础;然而,在我们开始实施这些方法之前,有一个非常重要的概念我们需要解决。你现在可能想知道为什么 DR 很重要。为什么任何数据科学家会在另一个数据科学家或数据工程师费尽周折地构建一个全面且丰富的数据集之后,还要删除特征呢?对此问题有三个答案,如下所述:

  • 我们并不一定从我们的数据集中删除任何数据,而是在不同的视角下探索我们的数据,这可能会提供一些我们使用原始数据集看不到的新见解。

  • 开发具有许多特征的模型是一个计算成本高昂的过程,因此使用较少特征来训练我们的模型将始终更快、计算量更少、更有利。

  • 使用 DR 可以帮助减少数据集中的噪声,从而进一步提高聚类模型和数据可视化。

    考虑到这些答案,现在让我们来谈谈你将在许多会议、讨论和面试中听到的概念——COD。

避免 COD

COD 被视为在处理高维数据集时出现的一种普遍现象——这个术语最初是由理查德·E·贝尔曼提出的。本质上,COD 指的是在高维数据集中出现的问题,而在类似大小但低维的数据集中不会出现。随着给定数据集中特征数量的增加,样本总数也会成比例增加。以一个一维数据集为例。在这个数据集中,假设我们需要检查总共 10 个区域。如果我们添加第二个维度,我们现在需要检查总共 100 个区域。最后,如果我们添加第三个维度,我们现在需要检查总共 1,000 个区域。回想一下我们迄今为止一直在处理的一些数据集,这些数据集的行数远远超过 1,000,至少有 10 列——这些数据集的复杂性可以迅速增长。这里的主要启示是特征增长对模型的发展有重大影响。我们可以在这里看到这一点的图形说明:

图 6.16 – COD 的图形表示

图 6.16 – COD 的图形表示

随着特征数量的增加,机器学习(ML)模型的整体复杂性也会增加,这可能会产生许多负面影响,如过拟合,从而导致性能不佳。减少数据集维度的主要动机之一是确保避免过拟合,从而产生更稳健的模型。我们可以在这里看到这一点的图形说明:

图 6.17 – 高维对模型性能的影响

图 6.17 – 高维对模型性能的影响

将数据集从高度维数形式降低到低维形式的需求在生命科学和生物技术领域尤为明显。在这个领域中,科学家和工程师面临的各种过程中,通常与任何给定过程相关的特征有数百个。无论我们是在寻找与蛋白质结构、单克隆抗体滴度、小分子对接位点选择、双特异性 T 细胞连接器BiTE)药物设计,甚至是与自然语言处理NLP)相关的数据集,特征的减少总是有用的,并且在许多情况下对于开发一个好的机器学习模型是必要的。

现在我们已经对 DR 及其与 COD 概念的关系以及这些方法可能带来的众多好处有了更好的理解,让我们继续看看在这个领域中我们应该了解的一些最常见模型。

教程 – 探索 DR 模型

我们可以根据类型、功能、结果等对众多维度算法进行多种不同的分类。然而,为了在本章的几页内对 DR 有一个强有力的概述,我们将把我们的模型分类为线性或非线性。线性模型和非线性模型是两种不同的数据转换类型。我们可以将数据转换视为数据以某种方式改变或重塑的方法。我们可以粗略地将线性方法定义为模型输出与其输入成比例的转换。以 pq 作为两个数学向量为例。

当以下条件成立时,我们可以认为一个转换是线性的:

  • p 的转换乘以一个标量,其结果与将 p 乘以标量然后应用转换相同。

  • p + q 的转换与 p + q 的转换相同。

如果一个模型不满足这两个特性,则被视为非线性模型。许多不同的模型都包含在这两个类别中;然而,为了本章的目的,我们将探讨近年来在数据科学社区中相当受欢迎的四个主要模型。这里我们可以看到这一点的图形说明:

图 6.18 – DR 各个领域的模型示例

图 6.18 – DR 各个领域的模型示例

在线性方法的范围内,我们将仔细研究PCA奇异值分解SVD)。此外,在非线性方法的范围内,我们将仔细研究t 分布随机邻域嵌入t-SNE)和均匀流形近似与投影UMAP)。考虑到这四种模型以及它们在 DR 宏大方案中的位置,让我们开始吧。

PCA

最常见且广泛讨论的 UL 形式之一是PCA。PCA 是一种线性形式的 DR,允许用户将大量相关特征的大数据集转换成更少的无关特征,这些特征被称为主成分。这些主成分,尽管在数量上少于原始特征,但仍能保留与原始数据集一样多的变化或丰富性。我们可以在下面看到这一点的图形说明:

图 6.19 – PCA 及其主成分的图形表示

图 6.19 – PCA 及其主成分的图形表示

为了有效地在任何给定数据集上实施 PCA,需要发生几件事情。让我们从高层次概述这些步骤以及它们如何影响最终结果。我们首先必须对数据进行归一化或标准化,以确保均值为 0,标准差为 1。接下来,我们计算所谓的协方差矩阵,它是一个包含每对元素之间协方差的方阵。在一个二维2D)数据集中,我们可以将协方差矩阵表示如下:

接下来,我们可以计算协方差矩阵的特征值特征向量,如下所示:

在这里,是给定矩阵A的特征值,而I是单位矩阵。使用特征向量,我们可以使用以下方程确定特征值v

接下来,将特征值从大到小排序,这代表了按重要性顺序的成分。具有n个变量或特征的数据集将具有n个特征值和特征向量。然后我们可以将特征值或向量的数量限制在预定的数量,从而降低数据集的维度。然后我们可以使用感兴趣的特征向量形成我们所说的特征向量。

最后,我们可以通过特征向量的转置以及原始数据集缩放数据的转置,将它们相乘,从而形成主成分,如下所示:

在这里,PrincipalComponents以矩阵的形式返回。简单,对吧?

现在让我们使用 Python 实现 PCA,如下所示:

  1. 首先,我们从 sklearn 库中导入 PCA 并实例化一个新的 PCA 模型。我们可以将组件数量设置为 2,表示我们只想返回两个组件,并将 svd_solver 设置为 full。然后,我们可以将数据拟合到我们的缩放数据集上,如下所示:

    from sklearn.decomposition import PCA
    
    pca_2d = PCA(n_components=2, svd_solver='full')
    pca_2d.fit(X_scaled)
    
  2. 接下来,我们可以转换我们的数据并将输出矩阵分配给 data_pca_2d 变量,如下所示:

    data_pca_2d = pca_2d.fit_transform(X_scaled)
    
  3. 最后,我们可以使用 seaborn 绘制结果,如下所示:

    plt.xlabel("Principal Component 1")
    plt.ylabel("Principal Component 2")
    sns.scatterplot(x=data_pca_2d[:,0], y=data_pca_2d[:,1], hue=y, style=y, markers=["s", "o"])
    

    执行此代码后,这将生成一个散点图,显示我们的主成分,我们的点使用 y 着色,如图所示:

图 6.20 – PCA 结果的散点图

图 6.20 – PCA 结果的散点图

PCA 是一种快速且高效的方法,最好在维度变得过于复杂时作为开发 ML 模型的先导使用。回想一下我们在乳腺癌预测相关的聚类分析中使用的数据集。我们可以在原始或缩放数据上运行我们的模型之前,实现一个 DR 算法,如 PCA,将维度减少到仅两个主成分,然后再应用后续的聚类模型。记住,PCA 只是许多线性模型之一。现在,让我们继续探索另一个流行的线性模型,称为 SVD。

SVD

SVD 是一种流行的 矩阵分解 方法,通常用于将数据集简化为更简单的形式。在本节中,我们将专门讨论截断 SVD 的应用。此模型与 PCA 非常相似;然而,主要区别在于估计量在计算之前不进行中心化。本质上,这种差异使得模型能够非常有效地用于稀疏矩阵。

现在,让我们介绍并查看一个我们可以用来应用 SVD 的新数据集:单细胞 RNA(其中 RNA 代表 核糖核酸)。该数据集可在 blood.stemcells.cam.ac.uk/data/nestorowa_corrected_log2_transformed_counts.txt 找到。此类数据集涉及单细胞测序的主题——这是一个检查单个细胞序列的过程,以更好地了解它们的属性和功能。此类数据集通常具有许多数据列,使它们成为 DR 模型的理想候选。让我们继续导入此数据集,如下所示:

dfx = pd.read_csv("../../datasets/single_cell_rna/nestorowa_corrected_log2_transformed_counts.txt", sep=' ',  )
dfx.shape

观察形状,我们可以看到有 3,991 行数据和 1,645 列。相对于我们使用的许多其他数据集,这个数字相当大。在生物技术领域,DR 非常常用以帮助将此类数据集减少到更易于管理的实体。注意,索引包含有关我们正在查看的细胞类型的一些信息。为了使我们的视觉效果更有趣,让我们通过执行以下代码来捕获此注释数据:

dfy = pd.DataFrame()
dfy['annotation'] = dfx.index.str[:4]
dfy['annotation'].value_counts()

数据都准备好了,让我们继续在这个数据集上实现截断 SVD。我们可以再次通过实例化一个截断 SVD 模型并将组件设置为2,迭代次数为7来开始,如下所示:

from sklearn.decomposition import TruncatedSVD
svd_2d = TruncatedSVD(n_components=2, n_iter=7)

接下来,我们可以继续使用fit_transform()方法来拟合我们的数据和将 DataFrame 转换为一个两列的数据集,如下所示:

data_svd_2d = svd_2d.fit_transform(dfx)

最后,我们可以通过散点图和颜色标注来绘制我们的数据集。代码在以下片段中展示:

sns.scatterplot(x=data_svd_2d[:,0], y=data_svd_2d[:,1], hue=dfy.annotation, style=dfy.annotation, markers = ["o", "s", "v"])

我们可以在以下屏幕截图中看到执行此代码的结果:

图 6.21 – SVD 模型结果的散点图

图 6.21 – SVD 模型结果的散点图

在前面的屏幕截图中,我们可以看到近 1400 列的数据被简化为简单的 2D 表示——是不是很令人着迷?能够以这种方式减少数据的一个最大的优点是它有助于模型开发。让我们假设,为了举例,我们希望将我们之前提到的任何聚类算法应用于这个庞大的数据集。在近 1400 列的数据集上训练任何给定模型所需的时间将比在 2 列的数据集上长得多。实际上,如果我们在这个数据集上实现 GMM,使用原始数据集的总训练时间将是12.4 秒 ± 158 毫秒,而使用减少后的数据集则是4.06 毫秒 ± 26.6 毫秒。虽然线性模型在处理 DR 时非常有用,但非线性模型也可以有类似惊人的效果。接下来,让我们看看一个流行的模型,称为 t-SNE。

t-SNE

在非线性 DR 方面,最常见的一个流行模型是t-SNE。与我们所讨论的其他维度模型相比,t-SNE 模型的一个独特特征是它使用概率分布来表示邻居之间的相似性。简单来说,t-SNE 是一种统计方法,允许对高维数据进行 DR 和可视化,其中相似点靠近,不相似点远离。

t-SNE 是一种流形模型,从数学角度来看,它是一个类似于欧几里得空间的拓扑空间。流形的概念复杂、广泛,超出了本书的范围。为了简化,我们将说明流形描述了大量的几何表面,如球体、环面或十字形表面。在 t-SNE 模型的范围内,主要目标是使用几何形状来使用户对高维数据的排列或组织有一个感觉或直觉。

现在,让我们仔细看看 t-SNE 在 Python 中的应用。再次,我们可以在我们的单细胞 RNA 数据集上应用这个模型,并从几何角度了解这个数据的高维组织结构。t-SNE 中的许多参数都可以更改和调整以适应特定目的;然而,有一个参数特别值得简要提及——困惑度。scikit-learn 库建议考虑介于 5 和 50 之间的值。让我们继续看看几个示例。

由于 scikit-learn 的高层支持,实现这个模型相当简单。我们可以从导入 scikit-learn 中的 TSNE 类开始,将组件数设置为 2,将困惑度设置为 10。然后我们可以使用我们的数据集链式调用 fit_transform() 方法,如下面的代码片段所示:

from sklearn.manifold import TSNE
data_tsne_2d_p10 = TSNE(n_components=2, perplexity=10.0).fit_transform(dfx)

然后,我们可以继续使用 seaborn 来绘制我们的数据,以可视化结果,如下所示:

sns.scatterplot(x=data_tsne_2d_p10[:,0], y=data_tsne_2d_p10[:,1], hue=dfy.annotation, style=dfy.annotation, markers = ["o", "s", "v"])

我们可以在下面的屏幕截图中看到这个结果:

图 6.22 – t-SNE 模型的结果散点图

图 6.22 – t-SNE 模型的结果散点图

真是令人印象深刻!从前面的屏幕截图中我们可以看到,该模型在没有标签知识的情况下,使用它所给出的巨大数据集,对数据点之间的关系进行了二维投影。产生的几何形状给我们一种关于数据的外观感觉。我们可以从这个描述中看出,一些点似乎被认为是异常值,因为它们被描绘得离得很远,就像岛屿相对于大陆一样。回想一下,我们为这个特定的图表使用了10的困惑度值。让我们继续探索这个参数,使用几个不同的值,如下所示:

data_tsne_2d_p1 = TSNE(n_components=2, perplexity=1.0).fit_transform(dfx)
data_tsne_2d_p10 = TSNE(n_components=2, perplexity=10.0).fit_transform(dfx)
data_tsne_2d_p30 = TSNE(n_components=2, perplexity=30.0).fit_transform(dfx)

使用这些计算出的值,我们可以使用 seaborn 库将它们并排放置,如下所示:

图 6.23 – 随着困惑度增加的 t-SNE 模型的散点图

图 6.23 – 随着困惑度增加的 t-SNE 模型的散点图

当涉及到高维数据时,t-SNE 是最常用的模型之一,它不仅能降低你的维度,还能通过获取其特征及其关系的独特感觉来探索你的数据。尽管 t-SNE 可能很有用且有效,但它确实有几个负面方面。首先,它对于大样本量(如你在某些 RNA 测序数据案例中看到的那样)的扩展性不好。其次,它也没有保留全局数据结构,也就是说,不同簇之间的相似性没有得到很好的维护。另一个试图解决这些担忧并采用与 t-SNE 类似方法的流行模型被称为 UMAP。让我们在下一节中探索这个模型。

UMAP

UMAP模型是一个流行的算法,用于基于流形学习技术的降维和数据可视化,类似于 t-SNE。该算法基于三个主要假设,如他们在主要网站(umap-learn.readthedocs.io/en/latest)上所述,并在以下内容中概述:

  • 数据集在黎曼流形上均匀分布。

  • 黎曼度量在局部是常数。

  • 流形在局部是连通的。

尽管 UMAP 和 t-SNE 非常相似,但它们之间有一些关键的区别。其中最重要的区别之一与相似性保留的概念有关。UMAP 模型声称在局部和全局数据中保持相似性,即局部和全局信息,或簇间和簇内信息得到维护。我们可以在这里看到这个概念的图形表示:

图 6.24 – 局部和全局相似性的图形表示

图 6.24 – 局部和全局相似性的图形表示

现在我们来应用 UMAP 到我们的单细胞 RNA 数据集。我们可以从导入umap库并实例化一个新的 UMAP 模型开始,我们指定组件数量为2,邻居数量为5。这个第二个参数表示用于流形近似的局部邻域的大小。代码如下所示:

import umap
data_umap_2d_n5 = umap.UMAP(n_components=2, n_neighbors=5).fit_transform(dfx)

然后,我们可以使用seaborn来绘制数据,如下所示:

sns.scatterplot(x=data_umap_2d_n5[:,0], y=data_umap_2d_n5[:,1], hue=dfy.annotation, style=dfy.annotation, markers = ["o", "s", "v"])

执行代码后,我们得到以下输出:

图 6.25 – UMAP 结果的散点图

图 6.25 – UMAP 结果的散点图

再次,非常直观的视觉展示!我们可以看到,与 t-SNE 相比,一些簇已经移动了位置。如果你还记得在 t-SNE 中,大部分数据被拉在一起,而不管簇之间的相似性如何。使用 UMAP,这个信息被保留下来,我们能够更好地了解这些簇之间的关系。注意数据相对于其在 t-SNE 中的表示的分布。类似于 t-SNE,我们可以看到一些的点在不同的邻域中聚集在一起。

总结来说,UMAP 是一个功能强大的模型,类似于 t-SNE,在处理邻域或簇时,它保留了局部和全局信息。这个模型最常用于可视化,只需几行代码就能很好地了解任何高维数据集的“外观”和“感觉”。

摘要

在本章的整个过程中,我们对该领域(UL)有了强烈和高级的理解,包括其用途和应用。然后,我们探讨了与聚类和 DR 相关的几种最流行的 ML 方法。在聚类的领域中,我们回顾了一些最常用的模型,如层次聚类、K-Means 聚类和 GMMs。我们学习了欧几里得距离和概率之间的差异以及它们如何与模型预测相关。此外,我们还将这些模型应用于Wisconsin 乳腺癌数据集,并在其中几个模型中实现了相对较高的准确率。在 DR 领域中,我们对该领域与 COD 相关的意义有了深入的理解。然后,我们使用单细胞 RNA数据集实现了 PCA、SVD、t-SNE 和 UMAP 等模型,将超过 1400 列减少到 2 列。然后,我们使用seaborn可视化我们的结果,并检查了模型之间的差异。在本章的整个过程中,我们设法在不使用标签的情况下开发我们的模型,我们只在开发过程之后使用标签进行比较。

在下一章中,我们将探讨 SML 领域,在该领域中,我们除了使用数据标签外,还使用数据本身来开发强大的预测模型。

第七章:监督机器学习

当你开始在数据科学领域提升你的职业和技能时,你将遇到许多不同类型的模型,这些模型可以分为监督学习或无监督学习两大类。回想一下,在无监督学习的应用中,模型通常被训练来聚类或转换数据,以便将数据分组或重塑以提取洞察,当给定数据集没有标签时。在本章中,我们将现在讨论监督学习的应用,这些应用适用于分类和回归领域,以开发强大的预测模型,对数据集的标签做出明智的猜测。

在本章的整个过程中,我们将讨论以下主题:

  • 理解监督学习

  • 衡量监督机器学习中的成功

  • 理解监督机器学习中的分类

  • 理解监督机器学习中的回归

带着我们的目标,现在让我们开始吧。

理解监督学习

当你开始探索数据科学,无论是自己还是在一个组织中,你经常会遇到这样的问题:“监督机器学习究竟是什么意思?”让我们继续并给出一个定义。我们可以将监督学习定义为机器学习的一个一般子集,其中数据(以及其关联的标签)被用来训练模型,这些模型可以从数据中学习或泛化以做出预测,最好是具有高度的确定性。回顾一下第五章机器学习简介,我们可以回忆起我们完成的关于乳腺癌数据集的例子,其中我们将肿瘤分类为恶性或良性。这个例子,加上我们创建的定义,是学习和理解监督学习含义的极好方式。

在我们心中有了监督机器学习的定义后,让我们继续讨论它的不同子类型,即分类和回归。如果你还记得,在机器学习的范围内,分类是指为给定的一组数据预测一个类别,例如将肿瘤分类为恶性或良性,将电子邮件分类为垃圾邮件或非垃圾邮件,甚至将蛋白质分类为α或β。在这些案例中,模型将输出一个离散值。另一方面,回归是使用给定的一组数据预测一个确切值,例如小分子的亲脂性、单克隆抗体(mAb)的等电点或 LCMS 峰的 LCAP。在这些案例中,模型将输出一个连续值。

在监督学习的两个类别中存在许多不同的模型。在本书的范围内,我们将重点关注这两个类别中的每个类别的主要四种模型。在分类方面,我们将讨论K-最近邻KNN)、支持向量机SVMs)、决策树随机森林,以及 XGBoost 分类。在回归方面,我们将讨论线性回归逻辑回归随机森林回归,甚至梯度提升回归。我们可以在图 7.1中看到这些:

图 7.1 – 监督机器学习的两个领域

图 7.1 – 监督机器学习的两个领域

在这些模型中,我们的主要目标是针对特定数据集训练该模型的新实例。我们将用数据拟合我们的模型,并调整或调整参数以获得最佳结果。为了确定最佳结果应该是什么,我们需要知道如何在模型中衡量成功。我们将在下一节中了解这一点。

监督机器学习中的成功衡量

当我们开始训练监督分类器和回归器时,我们需要实施几种方法来确定哪些模型表现更好,从而有效地调整模型的参数并最大化其性能。实现这一目标的最有效方法是提前了解成功的外观,然后再深入到模型开发过程。根据情况,有许多不同的方法可以衡量成功。例如,准确率可以是一个好的分类器指标,但不适用于回归器。同样,一个分类器的业务案例可能不一定需要准确率成为主要关注的指标。这完全取决于具体情况。让我们看看分类回归领域中常用的某些最常见指标。

图 7.2 – 回归和分类的常见成功指标

图 7.2 – 回归和分类的常见成功指标

虽然对于特定场景你可以使用许多其他指标,但图 7.2中列出的八种可能是你可能会遇到的最常见的一些。选择一个特定的指标可能很困难,因为它应该始终与给定的用例相一致。让我们在讨论分类时进一步探讨这一点。

使用分类器衡量成功

以我们迄今为止所使用的肿瘤数据集为例。我们定义我们的成功指标为准确率,因此最大化准确率是我们的模型的主要训练目标。然而,这并不总是如此,你选择的成功指标几乎总是取决于模型和手头的业务问题。让我们进一步仔细看看数据科学领域常用的某些指标,并定义它们:

  • 准确度:一种与公认值非常一致的度量

  • 精确度:一种与其他测量结果相似性的度量,即它们彼此相似

考虑准确度和精确度的更简单的方法是通过想象使用靶心表示法显示的结果。精确度和准确度的区别在于结果彼此之间有多接近,以及结果与它们的真实或实际值有多接近。我们可以在图 7.3中看到这种视觉表示:

图 7.3 – 精确度和精确度差异的图形说明

图 7.3 – 精确度和精确度差异的图形说明

除了视觉表示外,我们还可以将精确度视为一个计算,它将结果表示为相对于总体人群的子集。在这种情况下,我们还需要定义一个新的指标,称为召回率。我们可以在正负结果的上下文中通过所谓的混淆矩阵来数学地思考召回率和精确度。当比较预测结果与实际结果时,通过比较这些值中的几个,我们可以很好地了解模型的表现。我们可以在图 7.4中看到这种视觉表示:

图 7.4 – 混淆矩阵的图形说明

图 7.4 – 混淆矩阵的图形说明

考虑到这个表格,我们可以将召回率定义为给定模型识别的欺诈案例的比例,或者从数学角度来看,我们可以将其定义为如下:

图片

而我们可以在相同语境下定义精确度如下:

图片

我们可以用类似的方式在以下图表中可视化准确度、精确度和召回率,其中每个指标代表整体结果的具体计算:

图 7.5 – 解释精确度、精确度和召回率的图形说明

图 7.5 – 解释精确度、精确度和召回率的图形说明

最后,还有一个常用的指标,通常被认为是精确度和召回率的松散组合,称为F1 分数。我们可以将F1 分数定义为如下:

图片

那么,你如何确定使用哪个指标呢?没有一种最佳的指标是你应该始终使用的,因为它高度依赖于每种情况。在确定最佳指标时,你应该始终问自己,模型以及业务的主要目标是什么?在模型的眼中,精确度可能是最好的指标。另一方面,在业务的眼中,召回率可能是最好的指标。

最终,当被忽视的案例(如上所述为假阴性)更重要时,召回率可能更有用。例如,考虑一个预测患者诊断的模型 – 我们可能更关心假阴性而不是假阳性。另一方面,当假阳性对我们来说成本更高时,精确度可能更重要。这完全取决于给定的业务案例和需求。到目前为止,我们已经研究了与分类相关的成功,现在让我们研究这些想法与回归相关的内容。

使用回归器衡量成功

尽管我们还没有深入研究回归领域,但我们已经定义了主要思想,即开发一个输出为连续数值的模型。以包含许多连续浮点数值的分子毒性数据集为例。假设你可以使用这个数据集来预测 总极性表面积TPSA)。在这种情况下,准确度、精确度和召回率的指标对我们最好理解模型性能并不是最有用的。相反,我们需要一些更适合连续值的指标。

在许多模型(不一定是机器学习)中定义成功最常用的指标之一是 皮尔逊相关系数,也称为 R2。这种计算是衡量数据线性度的常用方法,因为它代表了因变量变差的比例。我们可以如下定义 R2

公式 7.4

在这个公式中,公式 7.5 是预测值,而公式 7.6 是平均值。

以一个已知实验(实际)值和预测值的示例数据集为例,我们可以绘制这些值相互对应的图表并测量相关性。理论上,一个完美的模型将会有一个理想的关联度(尽可能接近 1.00 的值)。我们可以在 图 7.6 中看到高关联度和低关联度的描述:

图 7.6 – 散点图中高相关与低相关的差异

图 7.6 – 散点图中高相关与低相关的差异

虽然这个指标可以给你一个关于模型性能的良好估计,但还有一些其他指标可以给你更好的模型误差感:平均绝对误差MAE)、平均平方误差MSE)和均方根误差RMSE)。让我们继续定义这些:

  • MAE:给定数据集中实际值和预测值之间绝对差异的平均值。当处理含有异常值的数据集时,这个度量通常更稳健:公式 7.7

    其中 公式 7.8 是预测值,而 公式 7.9 是平均值。

  • MSE:给定数据集中实际值和预测值之间平方差的平均值:公式 7.1

  • RMSE:MSE 的平方根,用于衡量值的标准差。这个指标通常用于比较回归模型:公式 7.2

当涉及到回归时,你可以根据给定情况使用许多不同的指标。在大多数回归模型中,RMSE 通常用于比较多个模型的性能,因为它计算简单且可微分。另一方面,具有异常值的数据集通常使用 MSE 和 MAE 进行比较。现在我们已经更好地理解了通过各种指标来衡量成功,那么我们现在可以继续探索分类领域。

理解监督机器学习中的分类

在机器学习的背景下,分类模型是监督模型,其目标是根据先前学习到的例子对项目进行分类或归类。你将遇到许多形式的分类模型,因为它们往往是数据科学领域中使用的最常见模型之一。我们可以根据模型的输出开发三种主要的分类器。

图 7.7 – 三种监督分类类型

图 7.7 – 三种监督分类类型

第一种被称为二元分类器。正如其名所示,这是一种以二元方式预测的分类器,即输出是两个选项之一,例如电子邮件是否为垃圾邮件,或分子是否有毒。在这两种情况下都没有第三种选择,这使得模型成为二元分类器。

第二种分类器被称为多类分类器。这种分类器是在超过两个不同的输出上进行训练的。例如,许多类型的蛋白质可以根据结构和功能进行分类。这些例子包括结构蛋白、酶、激素、储存蛋白和毒素。开发一个基于蛋白质的一些特性的模型来预测蛋白质类型,将被视为多类分类器,因为在数据集中的每一行数据只能有一个可能的类别或输出。

最后,我们还有多标签分类器。这些分类器与它们的多元对应物不同,能够为给定行数据预测多个输出。例如,在筛选临床试验患者时,你可能希望使用许多不同类型的标签来构建患者档案,如性别、年龄、糖尿病状态和吸烟状态。在尝试预测某个患者群体可能的样子时,我们需要能够预测所有这些标签。

现在我们已经将分类分解为几种不同类型,你可能正在考虑你在项目中工作的许多不同领域,在这些领域中分类器可能非常有价值。好消息是,我们即将探索的许多标准或流行分类模型可以很容易地回收并使用新数据拟合。在我们开始探索下一节中的许多不同模型时,请考虑你正在工作的项目和你拥有的数据集,以及哪些模型最适合它们。

探索不同的分类模型

在探索多种机器学习模型的过程中,我们将测试这些模型在 2016 年由 Nestorowa 等人发布的关于单细胞 RNA 序列的新数据集上的性能。我们将专注于使用这个结构化数据集来开发多种不同的分类器。让我们继续导入数据并为分类模型做准备。首先,我们将使用pandas中的read_csv()函数导入我们感兴趣的数据集:

dfx = pd.read_csv("../../datasets/single_cell_rna/nestorowa_corrected_log2_transformed_counts.txt", sep=' ',  )

接下来,我们将使用索引来隔离每行的标签(类别),使用每行的前四个字符:

dfx['annotation'] = dfx.index.str[:4]
y = dfx["annotation"].values.ravel()

我们可以使用head()函数查看数据。我们会注意到有超过 3,992 列的数据。正如任何优秀的数据科学家都知道的,使用太多列开发模型会导致许多低效,因此最好使用无监督学习技术(如sklearn中的StandardScaler类)来减少这些列:

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(dfx.drop(columns = ["annotation"]))

接下来,我们可以应用 PCA 将数据集从 3,992 列减少到 15 列:

from sklearn.decomposition import PCA
pca = PCA(n_components=15, svd_solver='full')
pca.fit(X_scaled)
data_pca = pca.fit_transform(X_scaled)

现在数据状态大大减少,我们可以检查解释方差比以查看这与原始数据集相比如何。我们将看到所有列的总和为 0.17,相对较低。我们希望达到大约 0.8 的值,因此让我们继续增加列的总数以提高方差百分比:

pca = PCA(n_components=900, svd_solver='full')
pca.fit(X_scaled)
data_pca = pca.fit_transform(X_scaled)

应用 PCA 模型后,我们成功将列总数减少了大约 77%。

完成此操作后,我们现在准备使用train_test_split()类来分割我们的数据集:

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(data_pca, y, test_size=0.33)

现在数据集已分为训练集和测试集,我们已准备好开始分类模型开发过程!

K-最近邻

经典、易于开发且最常讨论的分类模型之一被称为KNN模型,该模型最早由 Evelyn Fix 和 Joseph Hodges 于 1951 年开发。该模型背后的主要思想是基于最近邻的邻近度来确定类别成员资格。以一个二维二值数据集为例,其中项目被分类为 A 或 B。当添加新的数据集时,该模型将根据其与其他项目(通常是欧几里得距离)的邻近度来确定其成员资格或类别。

图 7.8 – KNN 模型的图形表示

图 7.8 – KNN 模型的图形表示

由于其简单性和巧妙的设计,KNN 被认为是最容易开发和实现的机器学习模型之一。虽然模型在应用上很简单,但为了完全有效,确实需要一些调整。让我们继续探索这个模型在单细胞 RNA 分类数据集上的应用:

  1. 我们可以从导入sklearn中的KNeighborsClassifier模型开始:

    from sklearn.neighbors import KNeighborsClassifier
    
  2. 接下来,我们可以在 Python 中实例化这个模型的新实例,将邻居的数量设置为5,并将模型拟合到我们的训练数据:

    knn = KNeighborsClassifier(n_neighbours=5)
    knn.fit(X_train, y_train)
    
  3. 在模型拟合后,我们现在可以预测模型的输出,并将这些输出设置为我们称之为y_pred的变量,并最终使用classification_report函数查看结果:

    y_pred = knn.predict(X_test)
    print(classification_report(y_test, y_pred))
    

    使用分类报告功能,我们可以了解每个类别的精确度、召回率和F1分数。我们可以看到,对于LT.H类,精确度相对较高,但其他两个类别的精确度略低。另一方面,对于LT.H类,召回率非常低,但对于Prog类却相当高。总的来说,这个模型的平均精确度为0.63

    图 7.9 – KNN 模型的结果

    图 7.9 – KNN 模型的结果

    考虑到这些结果,让我们继续调整一个参数,即n_neighbours参数,其范围在110之间。

  4. 我们可以使用一个简单的for循环来完成这个任务:

    for i in range(1,10):
        knn = KNeighborsClassifier(n_neighbors=i)
        knn.fit(X_train, y_train)
        y_pred = knn.predict(X_test)
        print("n =", i, "acc =", accuracy_score(y_test, y_pred))
    

    如果我们查看结果,我们可以看到neighbors的数量以及整体模型准确率。立即注意到,仅基于这个指标的最佳选项值是n=2,准确率大约为 60%。

图 7.10 – 不同邻居数量的 KNN 模型结果

图 7.10 – 不同邻居数量的 KNN 模型结果

KNN 是开发分类器中最简单和最快的模型之一;然而,对于像这样一个复杂的数据集,它并不总是最好的模型。你会注意到结果在各个类别之间差异很大,这表明模型无法仅根据它们与其他成员的邻近度来有效地区分它们。让我们继续探索另一种称为 SVM 的模型,它试图以稍微不同的方式对项目进行分类。

支持向量机

支持向量机(SVMs)是一类常用的监督机器学习模型,用于分类和回归,最早由 AT&T Bell 实验室在 1992 年开发。SVMs 背后的主要思想是使用超平面来分离类别。在讨论或数据科学领域中,你可能会听到或遇到三种主要的 SVM 类型:线性 SVMs、多项式 SVMs 和 RBF SVMs。

图 7.11 – 不同 SVMs 的视觉解释

图 7.11 – 不同 SVMs 的视觉解释

三种模型背后的主要思想在于如何分离类别。例如,在线性模型中,超平面是一条将两个类别分开的线性线。或者,超平面可能由多项式组成,使模型能够考虑非线性特征。最后,也是最流行的方法,模型可以使用径向基函数RBF)来确定数据点的成员资格,这基于两个参数,gammaC,它们负责决策区域、其分布以及误分类的惩罚。考虑到这一点,我们现在更详细地看看超平面的概念。

超平面是一个函数,试图以线性或非线性方式清楚地定义并允许区分类别。超平面可以用以下方式数学描述:

在其中是向量,是偏置项,是变量。

从 RNA 数据集短暂休息一下,让我们继续使用 Python 中的 enrollment 数据集来演示线性支持向量机(SVM)的使用——这是一个关于患者入组的数据集,其中受访者的数据通过Likely(可能)、Very Likely(非常可能)或Unlikely(不可能)入组的方式进行总结。线性 SVM的主要目标是绘制一条线,根据类别清晰地分离数据。

在我们开始使用 SVM 之前,让我们先导入我们的数据集:

df = pd.read_csv("../datasets/dataset_enrollment_sd.csv")

为了简化,让我们消除Likely类别,保留Very LikelyUnlikely类别:

dftmp = df[(df["enrollment_cat"] != "Likely")]

让我们绘制一条分隔散点图中数据的线:

     plt.figure(figsize=(15, 6))
     xfit = np.linspace(-90, 130)
         sns.scatterplot(dftmp["Feature1"], 
                         dftmp["Feature2"], 
                         hue=dftmp["enrollment_cat"].values, 
                         s=50)
         for m, b in [(1, -45),]:
             plt.plot(xfit, m * xfit + b, '-k')
         plt.xlim(-120, 150);
         plt.ylim(-100, 60);

执行此代码后,得到以下图表:

图 7.12 – 由初始 SVM 超平面分隔的两个簇

图 7.12 – 由初始 SVM 超平面分隔的两个簇

注意在图中,这条线性线可以用不同的斜率以多种方式绘制,但仍能成功地在数据集内分隔两个类别。然而,当新的数据点开始向两个簇之间的中间地带靠近时,超平面的斜率和位置将开始变得重要。解决这个问题的方法之一是根据最近的数据点定义平面的斜率和位置。如果线包含与最近数据点相关的宽度为x的边界,那么一个更改进的fill_between函数,如以下代码所示:

         plt.figure(figsize=(15, 6))
         xfit = np.linspace(-110, 180)
         sns.scatterplot(dftmp["Feature1"], 
                         dftmp["Feature2"], 
                         hue=dftmp["enrollment_cat"].values, 
                         s=50)
         for m, b, d in [(1, -45, 60),]:
      yfit = m * xfit + b
      plt.plot(xfit, yfit, '-k')
         plt.fill_between(xfit, yfit - d, 
                     yfit + d, edgecolor='none',
                              color='#AAAAAA', alpha=0.4)
         plt.xlim(-120, 150);
         plt.ylim(-100, 60);

执行此代码后,得到以下图表:

图 7.13 – 由指定边界的初始 SVM 超平面分隔的两个簇

图 7.13 – 由指定边界的初始 SVM 超平面分隔的两个簇

位于超平面边缘宽度内的来自两个类别的数据点被称为支持向量。主要直觉是,支持向量离超平面越远,正确识别新数据点的正确类别的概率就越高。

我们可以使用 scikit-learn 库中的 SVC 类来训练一个新的 SVM 分类器。我们首先导入类,分割数据,然后使用数据集训练模型:

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test =  
                train_test_split(dftmp[["Feature1","Feature2"]], 
                                  dftmp["enrollment_cat"].values,
                                  test_size = 0.25)
from sklearn.svm import SVC
model = SVC (kernel='linear', C=1E10, random_state = 42)
model.fit(X_train, y_train)

通过这样,我们现在已经将模型拟合到数据集上。作为最后一步,我们可以展示散点图,识别超平面,并指定哪些数据点是这个特定示例的支持向量:

     plt.figure(figsize=(15, 6))
     sns.scatterplot(dftmp["Feature1"], 
                     dftmp["Feature2"], 
                     hue=dftmp["enrollment_cat"].values, s=50)
     plot_svc_decision_function(model);
     for j, k in model.support_vectors_:
         plt.plot([j], [k], lw=0, ='o', color='red', 
                  markeredgewidth=2, markersize=20, 
                  fillstyle='none')

执行此代码后,得到以下图:

图 7.14 – 由初始 SVM 超平面分隔的两个聚类,带有选定的支持向量

图 7.14 – 由初始 SVM 超平面分隔的两个聚类,带有选定的支持向量

现在我们已经通过这个基本示例更好地理解了 SVM 如何相对于其超平面操作,让我们继续使用我们一直在工作的单细胞 RNA 分类数据集来测试这个模型。

按照与 KNN 模型大致相同的步骤,我们现在将实现 SVM 模型,首先导入库,用线性核实例化模型,拟合我们的训练数据,然后在测试数据上做出预测:

from sklearn.svm import SVC
svc = SVC(kernel="linear")
svc.fit(X_train, y_train)
y_pred = svc.predict(X_test)
print(classification_report(y_test, y_pred))

打印报告后,得到以下结果:

图 7.15 – SVM 模型的结果

图 7.15 – SVM 模型的结果

我们可以看到,实际上模型非常稳健,我们的数据集产生了一些高指标,并给出了总平均精度为 88%。SVMs 是非常适合用于复杂数据集的模型,因为它们的主要目标是通过对数据进行超平面分离来实现这一点。现在让我们探索一个采用非常不同方法来得出最终结果的模型,即使用决策树。

决策树和随机森林

决策树是结构化数据集在分类和回归中最受欢迎和常用的机器学习模型之一。决策树由三个元素组成:节点叶节点

节点通常由一个问题组成,允许过程分割成任意数量的子节点,以下图中用橙色表示。根节点是整个树通过的第一个节点。边是节点之间的连接,用蓝色表示。当节点没有子节点时,这个最终目的地被称为叶节点,用绿色表示。在某些情况下,决策树将会有包含相同父节点的节点 – 这些被称为兄弟节点。树中的节点越多,树就越。决策树的深度是复杂度的一个度量。

如果树不够复杂,将无法得到准确的结果,而过于复杂的树则会被过度训练。在训练过程中,找到一个良好的平衡是主要目标之一。使用这些元素,你可以构建一个决策树,使过程从上到下流动,从而到达特定的目的地或决策

图 7.16 – 节点、叶子和边在决策树中的表示

图 7.16 – 节点、叶子和边在决策树中的表示

决策树以相当天才的方式运行。我们从一个初始数据集开始,其中所有数据点都已标记并准备就绪。第一个目标是使用最有信息量的决策边界来分割数据集 – 在这种情况下,是在 y=m. 这成功地隔离了一个类别与另外两个类别;然而,另外两个类别之间仍然没有完全隔离。然后算法再次在 x = n 处分割数据集,从而完全分离了三个簇。如果有更多的簇存在,这个过程将迭代和递归地继续,直到所有类别都得到最佳分离。我们可以在 图 7.17 中看到这个过程的视觉表示:

图 7.17 – 决策树所采取过程的图形表示

图 7.17 – 决策树所采取过程的图形表示

决策树通过使用各种称为属性选择度量的分割标准来确定数据在哪里以及如何分割。这些突出的属性选择度量包括:

  • 信息增益

  • 增益率

  • 吉尼指数

让我们更仔细地看看这三项内容。

信息增益是一个关于进一步描述树所需信息量的属性。这个属性在利用最少随机性的分区的同时,最小化了数据分类所需的信息。将随机变量A的观察结果中确定的随机变量的信息增益视为一个函数,可以这样想:

广义而言,信息增益是熵(信息熵)的变化 such that:

其中 表示在属性 给定的情况下 的条件熵。总之,信息增益回答了这样的问题,从这个变量中获得多少信息,给定另一个变量?

另一方面,增益率是相对于固有信息的相对信息增益。换句话说,这个度量是有偏的,偏向于导致许多结果测试,从而强制对这种性质的特征给予优先考虑。增益率可以表示为:

其中 表示为:

总结来说,增益率惩罚具有更多不同值的变量,这有助于决定下一级中的下一个分割。

最后,我们到达了基尼指数,这是一个属性选择度量,表示随机选择的一个元素被错误标记的频率。基尼指数可以通过减去每个类别的平方概率之和来计算:

这种确定分割的方法自然倾向于较大的分区,而不是信息增益,后者倾向于较小的分区。任何数据科学家的目标都是探索不同的方法,并确定最佳前进路径。

现在我们已经对决策树及其模型的工作原理有了更详细的解释,接下来让我们使用之前的单细胞 RNA 分类数据集来实现这个模型:

from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier(max_depth=4)
dtc.fit(X_train, y_train)
y_pred = dtc.predict(X_test)
print(classification_report(y_test, y_pred))

打印报告后,得到以下结果:

图 7.18 – 决策树分类器的结果

图 7.18 – 决策树分类器的结果

我们可以看到,模型在没有任何调整的情况下,使用max_depth值为4的情况下,能够提供 77% 的总精确度分数。使用与KNN模型相同的方法,我们可以遍历一系列max_depth值,以确定最佳值。这样做将导致理想的max_depth值为 3,总精确度达到 82%。

当我们开始训练许多模型时,我们将面临的最常见问题之一是以某种方式过拟合我们的数据。以一个决策树模型为例,该模型针对特定数据集进行了非常精细的调整,因为决策树是使用整个数据集的特征和感兴趣变量构建的。在这种情况下,模型可能容易过拟合。另一方面,另一个称为随机森林的模型使用多个不同的决策树构建,以帮助减轻任何潜在的过拟合。

随机森林是基于决策树的鲁棒集成模型。决策树通常设计为使用整个数据集来构建模型,而随机森林随机选择特征和行,然后构建多个决策树,并在它们的权重中平均。由于随机森林能够限制过拟合,同时避免由于偏差导致的错误显著增加,因此它们是强大的模型。我们可以在图 7.19中看到这种效果的视觉表示:

图 7.19 – 随机森林模型的图形解释

图 7.19 – 随机森林模型的图形解释

随机森林可以通过两种主要方式帮助减少方差。第一种方法是通过训练不同的数据样本。考虑先前的患者入组数据示例。如果模型是在不包含那些介于聚类之间的样本上训练的,那么在测试集上确定分数将导致显著降低的准确率。

第二种方法涉及使用随机特征子集进行训练,从而允许在模型中确定特征重要性的概念。让我们看一下使用单细胞 RNA 分类数据集的此模型:

from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier(n_estimators=1000)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
print(classification_report(y_test, y_pred))

打印报告后,得到以下结果:

图 7.20 – 随机森林模型的结果

图 7.20 – 随机森林模型的结果

我们可以立即观察到模型的精确度大约为 74%,略低于上面的决策树,这表明树可能略微过度拟合了数据。

随机森林模型在生物技术和生命科学行业中非常常用,因为它们有出色的避免过度拟合的方法,以及它们能够使用较小的数据集开发预测模型的能力。生物技术空间内机器学习的许多应用通常都存在一个称为低 N问题的概念,即存在用例,但收集或组织的数据很少或没有,以开发模型。由于随机森林具有集成特性,因此它们通常用于这个空间的应用。现在让我们看看一个非常不同的模型,它不是基于决策而是基于统计概率来分割数据的。

极端梯度提升(XGBoost)

在过去几年中,许多稳健的机器学习模型开始进入数据科学领域,从而有效地改变了机器学习格局——其中之一就是极端梯度提升XGBoost)模型。此模型背后的主要思想是它是梯度提升模型GBMs)的实现,特别是决策树,其中速度和性能得到了高度优化。由于该模型的高效性和高效性,它开始主导数据科学的许多领域,并最终成为Kaggle.com上许多数据科学竞赛的首选算法。

有许多原因使得 GBMs 在结构化/表格数据集上非常有效。让我们探索这三个原因:

  • 并行化XGBoost模型实现了一种称为并行化的方法。这里的主要思想是它可以并行化构建每棵树的过程。本质上,单棵树的每个分支都是单独训练的。

  • max_depth参数,然后开始向后剪枝过程,最终在不再有正增益的分割后移除。

  • 正则化:在基于树的算法的总体背景下,正则化是一种算法方法,用于定义一个最小增益,以促使树中的另一个分割。本质上,正则化会缩小分数,从而促使最终预测更加保守,这反过来有助于防止模型过拟合。

既然我们已经对 XGBoost 及其稳健性能背后的原因有了更好的理解,我们现在就可以在我们的 RNA 数据集上实现这个模型。我们将首先使用pip安装模型的库:

pip install xgboost

在模型安装完成后,我们现在可以继续导入模型,并创建一个新的模型实例,在这个实例中我们指定n_estimators参数的值为10000

from xgboost import XGBClassifier
xgb = XGBClassifier(n_estimators=10000)

与之前的模型类似,我们现在可以使用训练数据集来拟合我们的模型,并打印出预测结果:

xgb.fit(X_train, y_train)
y_pred = xgb.predict(X_test)
print(classification_report(y_test, y_pred))

打印报告后,得到以下结果:

图 7.21 – XGBoost 模型的预测结果

图 7.21 – XGBoost 模型的预测结果

有了这些,我们可以看到我们成功实现了0.86的精确度,这比我们测试的其他一些模型要高得多。这个模型高度优化的特性使其相对于大多数其他模型来说非常快且稳健。

在本节中,我们成功覆盖了相当广泛的分类模型。我们从简单的KNN模型开始,该模型试图根据新值与其最近邻的关系来预测其类别。接下来,我们介绍了SVM模型,这些模型试图根据支持向量绘制的指定边界来分配标签。然后,我们介绍了决策树随机森林,它们基于节点、叶子和分割操作,最后我们看到了XGBoost的一个工作示例,这是一个高度优化的模型,实现了我们在其他模型中看到的大多数功能。

当你开始深入研究针对新数据集的许多不同模型时,你可能会考虑自动化模型选择过程。如果你这样考虑,我们上面采取的每个步骤都可以以某种方式自动化,以确定在特定的一组度量要求下哪个模型表现最佳。幸运的是,我们已经有一个库可以在这个领域帮助我们。在接下来的教程中,我们将研究这些模型与在Google Cloud PlatformGCP)上的一些自动化机器学习功能一起的使用。

教程:使用 GCP 对蛋白质进行分类

在本教程中,我们将研究多种分类模型,并随后实现一些自动化机器学习功能。我们的主要目标将是使用来自结构生物信息学研究合作实验室RCSB)的蛋白质数据银行PDB)的数据集自动开发一个用于蛋白质分类的模型。如果你还记得,我们在前一章中使用 RCSB PDB 的数据来绘制一个 3D 蛋白质结构。在本章中我们将使用的数据集由两部分组成——一个具有行和列的结构化数据集,其中一列是每个蛋白质指定的分类,以及每个蛋白质的一系列 RNA 序列。我们将把这一组基于序列的数据保存到后续章节中进行分析,现在我们专注于结构化数据集。

在本章中,我们将使用包含许多不同类型蛋白质的结构化数据集来尝试开发一个分类器。鉴于这个数据集的规模很大,我们将利用这个机会将我们的开发环境从本地安装的Jupyter Notebook迁移到 GCP 的在线笔记本。在我们这样做之前,我们需要创建一个新的 GCP 账户。让我们开始吧。

在 GCP 入门

GCP(Google Cloud Platform)入门非常简单,只需几个简单的步骤即可完成:

  1. 我们可以从导航到cloud.google.com/并注册一个新账户开始。你需要提供一些详细信息,例如你的名字、电子邮件和一些其他项目。

  2. 注册后,你可以通过点击页面右上角的控制台按钮来导航到控制台:图 7.22 – 控制台按钮的截图

    图 7.22 – 控制台按钮的截图

  3. 在控制台页面内,你可以看到与你的当前项目相关的所有项目,例如一般信息、使用的资源、API 使用情况,甚至账单:图 7.23 – 控制台页面的示例

    图 7.23 – 控制台页面的示例

  4. 你可能还没有设置任何项目。为了创建一个新的项目,导航到页面左上角的下拉菜单,并选择新建项目选项。给你的项目起一个名字,然后点击创建。你可以使用相同的下拉菜单在不同的项目之间导航:

图 7.24 – GCP 中项目名称和位置窗口的截图

图 7.24 – GCP 中项目名称和位置窗口的截图

完成最后一步后,你现在已经准备好充分利用 GCP 平台了。在本教程中,我们将介绍一些 GCP 功能,以帮助我们开始数据科学领域的工作;然而,我强烈建议新用户探索并学习这里提供的许多工具和资源。

将数据上传到 GCP BigQuery

在 GCP 中上传数据有多种不同的方式;然而,我们将专注于 GCP 特有的一个特定功能,称为BigQuery。BigQuery 背后的主要思想是它是一个具有内置机器学习能力的无服务器数据仓库,支持使用SQL语言进行查询。如果你还记得,我们之前开发并部署了一个AWS RDS,使用EC2实例作为服务器来管理我们的数据,而另一方面,BigQuery 使用的是无服务器架构。我们可以通过几个简单的步骤来设置 BigQuery 并开始上传我们的数据:

  1. 使用页面左侧的导航菜单,滚动到产品部分,将鼠标悬停在BigQuery选项上,并选择SQL 工作区。由于这是你第一次使用这个工具,你可能需要激活 API。这将对所有你从未使用过的工具都适用:图 7.25 – GCP 中 BigQuery 菜单的截图

    图 7.25 – GCP 中 BigQuery 菜单的截图

  2. 在这个列表中,你会找到你在上一节中创建的项目。点击右侧的选项按钮,选择protein_structure_sequence,保留所有其他选项为默认值。然后你可以点击创建数据集

  3. 在左侧菜单中,你将看到在项目名称下列出的新创建的数据集。如果你点击选项然后点击打开,你将被引导到数据集的主页。在这个页面上,你可以找到与该特定数据集相关的信息。现在让我们点击顶部的创建表格选项,在这里创建一个新的表格。将源更改为反映上传选项,并导航到与 RCSB PDB 中的蛋白质分类相关的 CSV 文件。给表格起一个新的名字,在保留所有其他选项为默认值的情况下,点击创建表格图 7.26 – GCP 中创建表格界面的截图

    图 7.26 – GCP 中创建表格界面的截图

  4. 如果你导航回资源管理器,你将看到新创建的表格列在你的项目下的数据集下:

图 7.27 – 数据集中创建的表格示例

图 7.27 – 数据集中创建的表格示例

如果你正确地完成了所有这些步骤,你现在应该有可用于在 BigQuery 中使用的数据。在下一节中,我们将准备一个新的笔记本,并开始解析这个数据集中的部分数据。

在 GCP 中创建笔记本

在本节中,我们将创建一个与我们在进行数据科学工作时使用的 Jupyter 笔记本等效的笔记本。我们可以通过几个简单的步骤来设置一个新的笔记本:

  1. 在屏幕左侧的导航面板中,向下滚动到 人工智能 部分,悬停在 AI 平台 上,并选择 笔记本 选项。记住,如果你还没有这样做,你可能需要再次激活此 API:图 7.28 – AI 平台菜单的视觉图

    图 7.28 – AI 平台菜单的视觉图

  2. 接下来,导航到屏幕顶部并选择 新建实例 选项。根据你的需求,有许多不同的选项可供选择。在本教程的目的上,我们可以选择第一个 Python 3 选项:图 7.29 – 实例选项的截图

    图 7.29 – 实例选项的截图

    如果你熟悉笔记本实例并且能够自定义它们,我建议创建一个定制的实例以满足你的具体需求。

  3. 一旦创建了笔记本并实例在线,你将在主 笔记本实例 部分中看到它。点击 打开 JupyterLab 按钮。将打开一个新窗口,其中包含 Jupyter Lab:图 7.30 – 实例菜单的截图

    图 7.30 – 实例菜单的截图

  4. home 目录下,创建一个名为 biotech-machine-learning 的新目录,以便我们保存笔记本。打开目录,通过点击右侧的 Python 3 笔记本选项来创建一个新的笔记本:

图 7.31 – GCP 上 Jupyter Lab 的截图

图 7.31 – GCP 上 Jupyter Lab 的截图

现在实例已配置并创建了笔记本,你现在可以准备在 GCP 上运行所有你的数据科学模型了。现在让我们更仔细地查看数据,并开始训练几个机器学习模型。

在 GCP 笔记本中使用 auto-sklearn

如果你打开你刚刚创建的笔记本,你会看到我们一直使用的非常熟悉的 Jupyter Lab 环境。这里的主要好处是,我们现在能够在这个相同的环境中管理我们的数据集,并且可以分配更多的资源来处理我们的数据,相对于我们在本地机器上拥有的少量 CPUGPU

回想一下,我们达到这个状态的主要目标是能够开发一个分类模型,根据一些输入特征正确地分类蛋白质。我们正在处理的数据集被称为 真实世界 数据集,因为它组织得不好,有缺失值,可能包含过多的数据,并且在开发任何模型之前需要一些预处理。

让我们先导入一些必要的库:

import pandas as pd
import numpy as np
from google.cloud import bigquery
import missingno as msno
from sklearn.metrics import classification_report
import ast
import autosklearn.classification

接下来,现在让我们从 BigQuery 导入我们的数据集。我们可以通过使用 Google Cloud 库中的 BigQuery 类来实例化一个客户端,直接在这里的笔记本中完成:

client = bigquery.Client(location="US")
print("Client creating using default project: {}".format(client.project))

接下来,我们可以使用 SQL 语言的SELECT命令查询我们的数据。我们可以简单地从查询我们数据集中的所有数据开始。在以下代码片段中,我们将使用 SQL 查询数据,并将结果转换为数据框:

query = """
    SELECT *
    FROM `biotech-project-321515.protein_structure_sequence.dataset_pdb_no_dups`
"""
query_job = client.query(
    query,
    location="US",
)
df = query_job.to_dataframe()
print(df.shape)

一旦转换为一个更易于管理的数据框,我们就可以看到我们正在处理的数据集相当庞大,有近 140,000 行和 14 列的数据。立刻,我们注意到其中一列被称作classification。让我们使用n_unique()函数查看这个数据集中独特的类别数量:

df.classification.nunique()

我们注意到有 5,050 个不同的类别!对于一个这样的数据集来说,这相当多,表明我们可能需要在任何分析之前大幅度缩减。在继续之前,让我们先删除任何可能的重复项:

dfx = df.drop_duplicates(["structureId"])

让我们现在更仔细地查看数据集中按计数排序的前 10 个类别:

dfx.classification.value_counts()[:10].sort_values().plot(kind = 'barh')

以下图形是由代码生成的,展示了这个数据集中的前 10 个类别:

图 7.32 – 数据集中最频繁的 10 个标签

图 7.32 – 数据集中最频繁的 10 个标签

立刻,我们注意到有两到三个类别或蛋白质占用了大部分数据:水解酶、转移酶和氧化还原酶。这将会带来两个问题:

  • 数据应该始终在意义上保持平衡,即每个类别应该有大致相等的行数。

  • 作为一般规则,类别与观察值的比率应该大约是 50:1,这意味着在 5,050 个类别的情况下,我们需要大约 252,500 个观察值,而我们目前并没有这么多。

在这两个约束条件下,我们可以通过专注于使用前三个类别来开发模型来同时解决这两个问题。目前,我们注意到无论手头上的类别如何,我们都有相当多的特征可用。我们可以继续使用msno库来更仔细地查看我们感兴趣的特征的完整性:

dfx = dfx[["classification", "residueCount", "resolution", "resolution", "crystallizationTempK", "densityMatthews", "densityPercentSol", "phValue"]]
msno.matrix(dfx)

以下截图,表示数据集的完整性,随后生成。请注意,crystallizationTempK特征的行数大量缺失:

图 7.33 – 一个图形表示,展示了数据集的完整性

图 7.33 – 一个图形表示,展示了数据集的完整性

到目前为止,在这个数据集中,我们已经注意到我们需要将类别数量缩减到前两个类别,以避免数据不平衡,同时我们还需要解决缺失的许多行数据。让我们继续准备我们的数据集,以便基于我们的观察开发几个分类模型。首先,我们可以使用简单的groupby函数来缩减数据集:

df2 = dfx.groupby("classification").filter(lambda x: len(x) > 14000)
df2.classification.value_counts()

如果我们使用value_counts()函数对数据框进行快速检查,我们会发现我们能够将其缩减到前两个标签。

或者,我们可以在 SELECT 中运行相同的命令,对分类进行 COUNT,并按 GROUP BY 分类。接下来,我们将该查询与原始表设置进行 INNER JOIN,分类对分类,但使用我们的 WHERE 子句进行过滤:

query = """
    SELECT DISTINCT
        dups.*
    FROM (
        SELECT classification, count(residueCount) AS classCount
        FROM `biotech-project-321515.protein_structure_sequence.dataset_pdb_no_dups`
        GROUP BY classification
    ) AS sub
    INNER JOIN `biotech-project-321515.protein_structure_sequence.dataset_pdb_no_dups` AS dups
        ON sub.classification = dups.classification
    WHERE sub.classCount > 14000
"""
query_job = client.query(
    query,
    location="US",
)
df2 = query_job.to_dataframe()

接下来,我们可以使用 dropna() 函数删除含有缺失值的行数据:

df2 = df2.dropna()
df2.shape

立即观察到数据集的大小已减少到 24,179 个观测值。这将是我们开发模型时的一个足够大的数据集。为了避免再次处理,我们可以将数据框的内容写入同一 BigQuery 数据集中新的表中:

import pandas_gbq
pandas_gbq.to_gbq(df2, 'protein_structure_sequence.dataset_pdb_no_dups_cleaned', project_id ='biotech-project-321515', if_exists='replace')

数据现在已准备就绪,让我们继续开发一个模型。我们可以继续分割输入和输出数据,使用 StandardScaler 类缩放数据,并将数据分割成测试集和训练集:

X = df2.drop(columns=["classification"])
y = df2.classification.values.ravel()
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.25)

在自动化部分,我们将使用一个名为 autosklearn 的库,该库可以通过命令行使用 pip 安装:

pip install autosklearn

安装了库之后,我们可以继续导入库并实例化该模型的新实例。然后我们将设置一些与我们要投入此过程的时间相关的参数,并为模型提供一个临时目录来操作:

import autosklearn.classification
automl = autosklearn.classification.AutoSklearnClassifier(
    time_left_for_this_task=120,
    per_run_time_limit=30,
    tmp_folder='/tmp/autosklearn_protein_tmp5',
)

最后,我们可以继续将模型拟合到我们的数据上。这个过程将需要几分钟才能运行:

automl.fit(X_train, y_train, dataset_name='dataset_pdb_no_dups')

当模型完成时,我们可以通过打印排行榜来查看结果:

print(automl.leaderboard())

打印排行榜后,我们检索到以下结果:

图 7.34 – auto-sklearn 模型的结果

图 7.34 – auto-sklearn 模型的结果

我们还可以使用 get_models_with_weights() 函数查看表现最佳的 random_forest 模型:

automl.get_models_with_weights()[0]

我们还可以通过使用模型和 classification_report() 函数进行一些预测来获取更多指标:

predictions = automl.predict(X_test)
print("classification_report:", classification_report(y_test, predictions))

打印报告后,得到以下结果:

图 7.35 – 表现最佳的 auto-sklearn 模型的结果

图 7.35 – 表现最佳的 auto-sklearn 模型的结果

通过这样,我们成功地为我们的数据集开发了一个成功且自动化的机器学习模型。然而,该模型尚未针对此任务进行微调或优化。我推荐你完成的一个挑战是调整模型中的各种参数,以尝试提高我们的指标。此外,另一个挑战将是尝试探索我们学到的其他一些模型,看看它们是否能够击败 autosklearn。提示:XGBoost 一直是一个针对结构化数据集的优秀模型。

使用 GCP 中的 AutoML 应用

在上一节中,我们使用了一个名为auto-sklearn的开源库来自动化使用sklearn库选择模型的过程。然而,正如我们通过XGBoost库所看到的,还有许多其他模型存在于sklearn API 之外。GCP 提供了一个强大的工具,类似于auto-sklearn,它会遍历大量模型和方法,以找到给定数据集的最优模型。让我们继续尝试一下:

  1. 在 GCP 的导航菜单中,滚动到人工智能部分,将鼠标悬停在表格上,然后选择数据集图 7.36 – 从人工智能菜单中选择数据集

    图 7.36 – 从人工智能菜单中选择数据集

  2. 在页面顶部,选择新建数据集选项。在本书编写时,该模型的 beta 版本可用。一些步骤在未来实现中可能会发生变化:图 7.37 – 创建新数据集按钮的截图

    图 7.37 – 创建新数据集按钮的截图

  3. 给数据集命名并选择区域,然后点击创建数据集

  4. 我们可以选择将我们感兴趣的数据库集以原始 CSV 文件或使用 BigQuery 的方式导入。请指定projectIDdatasetID和表名,然后点击导入来导入我们清理过的蛋白质数据集版本。

  5. 训练部分,你将能够看到这个数据集内的表格。指定分类列为目标列,然后点击训练模型图 7.38 – 训练菜单的示例

    图 7.38 – 训练菜单的示例

  6. 模型选择过程需要一些时间来完成。完成后,你将在评估选项卡下看到模型的结果。在这里,你将了解我们一直在使用的分类指标,以及一些其他指标。

图 7.39 – 训练模型结果的截图,显示了指标

图 7.39 – 训练模型结果的截图,显示了指标

GCP AutoML是一个强大的工具,当处理困难的数据集时,你可以利用它。你会发现模型的实现相当稳健,相对于我们作为数据科学家可以探索的许多选项,通常比较全面。AutoML的一个缺点是最终模型不会与用户共享;然而,用户确实有测试新数据和稍后使用模型的能力。在下一节中,我们将探讨一个类似于AutoML的选项,在AWS中称为AutoPilot。现在我们已经探索了许多与分类相关的不同模型和方法,让我们去探索回归方面的相应对应物。

理解监督机器学习中的回归

回归是一般用于确定依赖变量和独立变量之间关系或相关性的模型。在机器学习的背景下,我们将回归定义为监督机器学习模型,允许识别两个或多个变量之间的相关性,以便泛化或从历史数据中学习,对新观测进行预测。

在生物技术领域的限制范围内,我们使用回归模型来预测许多不同领域的值。

  • 提前预测化合物的 LCAP

  • 在上游预测滴度结果

  • 预测单克隆抗体的等电点

  • 预测化合物的分解百分比

通常在两列之间建立相关性。当观察到依赖关系时,数据集中的两列被认为具有强烈的相关性。可以使用线性回归模型更好地理解具体的关系,如下所示:

在其中 是第一个特征, 是第二个特征, 是一个小的误差项,其中 是常数。使用这个简单的方程,我们可以更有效地理解我们的数据,并计算任何相关性。例如,回想一下我们之前在毒性数据集中观察到的相关性,特别是MolWtHeavyAtoms特征之间的相关性。

任何给定的回归模型背后的主要思想,与它的分类对应物不同,是输出一个连续值而不是一个类别或类别。例如,我们可以在毒性数据集中使用多个列来尝试预测同一数据集中的其他列。

在数据科学领域,常用的回归模型有很多种。这些模型包括专注于优化一组变量之间线性关系的线性回归,更像是二元分类器而不是回归器的逻辑回归,以及结合多个基础估计器的预测能力的集成模型,等等。我们可以在图 7.40中看到一些例子:

图 7.40 – 不同类型的回归模型

图 7.40 – 不同类型的回归模型

在本节的其余部分,我们将探讨这些模型中的一些,当我们调查使用毒性数据集应用一些回归模型时。让我们继续准备我们的数据。

我们可以从导入我们感兴趣的库开始:

import pandas as pd
import numpy as np
from sklearn.metrics import r2_score
from sklearn.metrics import mean_squared_error
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme(color_codes=True)

接下来,我们可以继续导入我们的数据集并删除缺失的行。为了练习,我建议你将此数据集上传到SELECT语句:

df = pd.read_csv("../../datasets/dataset_toxicity_sd.csv")
df = df.dropna()

接下来,我们可以将我们的数据分为输入值和输出值,并使用MinMaxScaler()类从sklearn中进行数据缩放:

X = df[["Heteroatoms", "MolWt", "HeavyAtoms", "NHOH", "HAcceptors", "HDonors"]]
y = df.TPSA.values.ravel()
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X)

最后,我们可以将数据集分为训练数据和测试数据:

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.25)

在我们的数据集准备就绪后,我们现在可以继续前进并开始探索一些回归模型。

探索不同的回归模型

对于给定的数据集,可以使用许多类型的回归方法。我们可以将回归视为分为四个主要类别:线性回归、逻辑回归、集成回归,最后是提升回归。在下一节中,我们将探索每个这些类别中的示例,从线性回归开始。

单变量和多元线性回归

在你职业生涯中可能遇到的大多数数据集中,你通常会发现在某些特征之间存在某种类型的相关性。在本章早期,我们讨论了两个特征之间相关性的概念,即一个特征对另一个特征的依赖性,这可以通过称为R2的皮尔逊相关系数来计算。在过去的几章中,我们以不同的方式探讨了相关性的概念,包括热图和 pairplot。

使用我们刚刚准备好的数据集,我们可以使用seaborn查看一些相关性:

import seaborn as sns
fig = sns.pairplot(data=df[["Heteroatoms", "MolWt", "HeavyAtoms"]])

这产生了以下图示:

图 7.41 – 使用毒性数据集的 pairplot 函数的结果

图 7.41 – 使用毒性数据集的 pairplot 函数的结果

我们可以看到我们的数据集中已经存在一些相关性。使用简单线性回归,我们可以利用这种相关性,即我们可以使用一个变量来预测另一个变量最可能是什么。例如,如果 X 是自变量,Y 是因变量,我们可以定义两者之间的线性关系为:

图片

其中m是斜率,cy截距。根据本书前面的一些内容以及你在高中数学课上学到的知识,这个方程应该对你来说很熟悉。使用这种关系,我们的目标将是相对于我们的数据优化这条线,以确定mc的值,使用的方法称为最小二乘法。

在我们讨论最小二乘法之前,让我们首先讨论损失函数的概念。在机器学习的背景下,损失函数是计算值和预期值之间差异的度量。例如,让我们考察二次损失函数,它通常用于回归模型中的损失计算,我们可以将其定义为:

图片

到现在为止,我希望你能从我们在衡量成功部分中的讨论中认出这个函数。你能告诉我我们上次在哪里使用了它吗?

既然我们已经讨论了损失函数的概念,让我们更深入地了解一下最小二乘法。这个数学方法背后的主要思想是通过最小化损失来确定一组数据的最佳拟合线,正如我们刚才看到的关联所展示的那样。为了完全解释这个方程背后的概念,我们需要讨论偏导数等。为了简化起见,我们将简单地将最小二乘法定义为一种最小化损失的过程,以便确定一组数据的最佳拟合线。

我们可以将线性回归分为两大类:简单线性回归多元线性回归。这里的主要思想是我们将用多少个特征来训练模型。如果我们仅仅基于一个特征来训练模型,我们将开发一个简单线性回归。另一方面,如果我们使用多个特征来训练模型,我们将训练一个多元回归模型。

无论你是训练简单回归模型还是多元回归模型,过程和期望的结果通常是相同的。理想情况下,我们的模型在绘制实际值时应该显示出一条线性线,显示出数据中的强相关性,如图图 7.42所示:

图 7.42 – 显示理想相关性的简单线性线

图 7.42 – 显示理想相关性的简单线性线

让我们继续探索多元线性回归模型的发展。在上一节导入的数据中,我们可以导入sklearnLinearRegression类,用我们的训练数据对其进行拟合,并使用测试数据集进行预测:

from sklearn.linear_model import LinearRegression
reg = LinearRegression().fit(X_train, y_train)
y_pred = reg.predict(X_test)

接下来,我们可以使用实际值和预测值来计算R2值,并在图上可视化。此外,我们还将捕捉MSE指标:

p = sns.jointplot(x=y_test, y=y_pred, kind="reg")
p.fig.suptitle(f"Linear Regression, R2 = {round(r2_score(y_test, y_pred), 3)}, MSE = {round(mean_squared_error(y_test, y_pred), 2)}")
p.fig.subplots_adjust(top=0.90)

代码将生成以下图:

图 7.43 – 线性回归模型的结果

图 7.43 – 线性回归模型的结果

立即,我们注意到这个简单的线性回归模型在预测我们的数据集方面非常有效。请注意,这个模型不仅使用了一个特征,而且还使用了所有特征来进行预测。从图中我们可以看出,我们的大部分数据都集中在左下角。这对于回归模型来说并不理想,因为我们更希望值在所有边界上均匀分布;然而,重要的是要记住,在生物技术领域,你几乎总是会遇到真实世界的数据,其中你会观察到这些项目。

如果你正在使用Jupyter Notebooks进行操作,请将数据集减少到只有一个输入特征,对数据进行缩放和拆分,训练一个简单线性回归,并将结果与多元线性回归进行比较。我们的预测值和实际值之间的相关性是增加还是减少?

逻辑回归

回想一下,在线性回归部分,我们讨论了使用单个线性线根据相关特征作为输入来预测值的方法。我们概述了以下线性方程:

在某些情况下,数据与所需输出的关系可能不是由线性模型最好地表示,而是一个非线性模型:

图 7.44 – 一个简单的 Sigmoid 曲线

图 7.44 – 一个简单的 Sigmoid 曲线

虽然被称为逻辑回归,但这种回归主要用作二元分类算法。然而,这里的主要重点是,单词 logistic 指的是逻辑函数,也称为Sigmoid 函数,表示为:

考虑到这一点,我们希望使用这个函数来预测我们的数据集中的结果。如果我们想确定一个化合物是否具有毒性,给定一个特定的输入值,我们可以计算输入的加权总和,使得:

这将允许我们计算毒性的概率,使得:

使用这个概率,可以做出最终预测,并分配一个输出值。请使用前一部分中的蛋白质分类数据集实现此模型,并将您找到的结果与其他分类器的结果进行比较。

决策树和随机森林回归

与其分类对应模型类似,决策树回归(DTRs)是常用的机器学习模型,它们实现了与决策树分类器几乎相同的内部机制。这两个模型之间的唯一区别是,回归器输出连续的数值,而分类器输出离散的类别。

类似地,还存在另一个称为随机森林回归器(RFRs)的模型,它的工作方式与其分类对应模型类似。这个模型也是一种集成方法,其中每个树被训练为一个单独的模型,然后平均。

让我们使用这个数据集来实现一个 RFR。就像我们之前做的那样,我们首先创建一个模型实例,将其拟合到我们的数据中,进行预测,并可视化结果:

from sklearn.ensemble import RandomForestRegressor
reg = RandomForestRegressor().fit(X_train, y_train)
y_pred = reg.predict(X_test)
p = sns.jointplot(x=y_test, y=y_pred, kind="reg")
p.fig.suptitle(f"RandomForestRegressor Regression, R2 = {round(r2_score(y_test, y_pred), 3)}, MSE = {round(mean_squared_error(y_test, y_pred), 2)}")
# p.ax_joint.collections[0].set_alpha(0)
# p.fig.tight_layout()
p.fig.subplots_adjust(top=0.90)

在模型开发完成后,我们可以使用以下图表来可视化结果:

图 7.45 – 随机森林回归模型的结果

图 7.45 – 随机森林回归模型的结果

注意,尽管 max_depth 参数:

for i in range(1,10):
    reg = RandomForestRegressor(max_depth=i)
                       .fit(X_train, y_train)
    y_pred = reg.predict(X_test)
    print("depth =", i, 
          "score=", r2_score(y_test, y_pred), 
          "mse = ", mean_squared_error(y_test, y_pred))

以下代码的输出如下,表明 max_depth8 可能是最佳选择,因为它产生了 0.967248.133

图 7.46 – 具有不同 max_depth 的随机森林回归模型的结果

图 7.46 – 具有不同 max_depth 的随机森林回归模型的结果

与分类类似,回归中的决策树通常是避免过度拟合数据的同时开发模型的好方法。当使用 sklearn API 时,DTR 模型的另一个巨大好处是能够直接从模型中获得特征重要性的见解。让我们继续演示这一点:

features = X.columns
importances = reg.feature_importances_
indices = np.argsort(importances)[-9:]
plt.title('Feature Importances')
plt.barh(range(len(indices)), importances[indices],  
                           color='royalblue', 
                           align='center')
plt.yticks(range(len(indices)),[features[i] for i in indices])
plt.xlabel('Relative Importance')
plt.show()

完成上述步骤后,得到以下图表:

图 7.47 – 随机森林回归模型的特征重要性

图 7.47 – 随机森林回归模型的特征重要性

观察这张图,我们可以看到对模型发展影响最大的前三个特征是 HDonorsHeteroatomsHAcceptors。尽管这个特征重要性的例子是使用 RFR 模型开发的,但我们理论上可以使用它来与许多其他模型一起使用。在关于特征重要性的想法领域,有一个特别受到重视的库是 SHAP 库。强烈建议您浏览这个库及其提供的许多精彩特性(不是字面意义上的玩笑)。

XGBoost 回归

与前一节中我们研究的 XGBoost 分类模型类似,我们也有回归实现,这使我们能够预测一个值而不是一个类别。我们可以像前一节那样轻松实现这一点:

import xgboost as xg
reg = xg.XGBRegressor(objective ='reg:linear',n_estimators = 1000).fit(X_train, y_train)
y_pred = reg.predict(X_test)
p = sns.jointplot(x=y_test, y=y_pred, kind="reg")
p.fig.suptitle(f"xgboost Regression, R2 = {round(r2_score(y_test, y_pred), 3)}, MSE = {round(mean_squared_error(y_test, y_pred), 2)}")
p.fig.subplots_adjust(top=0.90)

代码完成后,得到以下图示:

图 7.48 – XGBoost 回归模型的结果

图 7.48 – XGBoost 回归模型的结果

您会注意到,这个模型的实现给我们带来了一个非常令人尊重的 R2 值,在实际情况和预测值之间,并成功产生了 282.79 的 MSE,这略低于我们在本章尝试的一些其他模型。模型完成后,现在让我们继续看看我们如何在下一教程中使用 AWS 提供的一些自动化机器学习功能。

教程:属性预测的回归

在本章的整个过程中,我们回顾了一些最常见(且最受欢迎)的回归模型,这些模型与使用毒性数据集预测 TPSA 特征相关。在前一节关于分类的部分,我们创建了一个 GCP 实例,并使用 auto-sklearn 库自动识别给定数据集的最佳机器学习模型之一。在本教程中,我们将以类似的方式创建一个 auto-sklearn 库。此外,我们还将探索一种更强大的自动化机器学习方法,即使用 AWS Autopilot。在早期的一些章节中,我们使用 AWS RDS 启动一个关系型数据库来托管我们的毒性数据集。使用相同的 AWS 账户,我们现在将开始操作。

在 AWS 中创建 SageMaker 笔记本

与在GCP中创建笔记本类似,我们可以在AWS中通过几个简单的步骤创建一个SageMaker笔记本:

  1. 在首页上导航到 AWS 管理控制台。点击服务下拉菜单,然后在机器学习部分下选择Amazon SageMaker图 7.49 – AWS 提供的服务列表

    图 7.49 – AWS 提供的服务列表

  2. 在页面左侧,点击笔记本下拉菜单,然后选择笔记本实例按钮:图 7.50 – 笔记本菜单截图

    图 7.50 – 笔记本菜单截图

  3. 在笔记本实例菜单中,点击名为创建笔记本实例的橙色按钮:图 7.51 – 创建笔记本实例按钮截图

    图 7.51 – 创建笔记本实例按钮截图

  4. 现在让我们给笔记本实例起一个名字,比如biotech-machine-learning。我们可以将实例类型保留为默认选择ml.t2.medium。这是一个中档实例,对于今天的演示来说已经足够了:图 7.52 – 笔记本实例设置截图

    图 7.52 – 笔记本实例设置截图

  5. 权限和加密部分下,为 IAM 角色部分选择创建新角色选项。IAM 角色的主要思想是授予某些用户或角色与特定 AWS 资源交互的能力。例如,我们可以允许这个角色访问一些但不是所有的 S3 存储桶。为了本教程的目的,让我们继续授予这个角色访问任何 S3 存储桶的权限。点击创建角色图 7.53 – 在 AWS 中创建 IAM 角色

    图 7.53 – 在 AWS 中创建 IAM 角色

  6. 保持所有其他选项不变,点击创建笔记本实例。你将被重定向回笔记本实例菜单,在那里你会看到你的新创建的实例处于待处理状态。几分钟后,你会注意到状态变为服务中。点击状态右侧的打开 JupyterLab按钮:图 7.54 – 在 AWS 中打开 Jupyter 笔记本或 Jupyter lab 的选项

    图 7.54 – 在 AWS 中打开 Jupyter 笔记本或 Jupyter lab 的选项

  7. 再次,你将发现自己又回到了熟悉的 Jupyter 环境中,你一直在那里工作。

    AWS SageMaker 是一个成本极低的优质资源。在这个空间内,你可以运行本书中学到的所有 Python 命令和库。你可以创建目录来组织你的文件和脚本,并且可以在世界任何地方访问它们,而无需携带你的笔记本电脑。此外,你还将能够访问近 100 个 SageMaker 样本笔记本和启动代码供你使用。

图 7.55 – SageMaker 启动笔记本的示例列表

图 7.55 – SageMaker 启动笔记本的示例列表

完成这一最后步骤后,我们现在拥有了一个完全工作的笔记本实例。在接下来的部分,我们将使用 SageMaker 导入数据并开始运行我们的模型。

在 AWS 中创建笔记本并导入数据

由于我们再次在我们的熟悉的 Jupyter 空间中工作,我们可以通过选择右侧的许多选项之一轻松创建一个笔记本,并开始运行我们的代码。让我们开始吧:

  1. 我们可以先选择右侧的 conda_python3 选项,在我们的当前目录中创建一个新的笔记本:图 7.56 – conda_python3 的屏幕截图

    图 7.56 – conda_python3 的屏幕截图

  2. 笔记本准备就绪后,我们需要安装一些库以开始工作。请继续使用 pip 安装 mysql-connectorpymysql

    pip install mysql-connector pymysql
    
  3. 接下来,我们需要导入一些东西:

    import pandas as pd
    import mysql.connector
    from sqlalchemy import create_engine
    import sys
    import seaborn as sns
    
  4. 现在,我们可以定义一些我们将需要查询数据的项目,就像我们在 第三章SQL 和关系型数据库入门 中所做的那样:

    ENDPOINT="toxicitydataset.xxxxxx.us-east-2.rds.amazonaws.com"
    PORT="3306"
    USR="admin"
    DBNAME="toxicity_db_tutorial"
    PASSWORD = "xxxxxxxxxxxxxxxxxx"
    
  5. 接下来,我们可以创建一个连接到我们的 RDS 实例:

    db_connection_str = 'mysql+pymysql://{USR}:{PASSWORD}@{ENDPOINT}:{PORT}/{DBNAME}'.format(USR=USR, PASSWORD=PASSWORD, ENDPOINT=ENDPOINT, PORT=PORT, DBNAME=DBNAME)
    db_connection = create_engine(db_connection_str)
    
  6. 最后,我们可以使用基本的 SELECT 语句查询我们的数据:

    df = pd.read_sql('SELECT * FROM dataset_toxicity_sd', con=db_connection)
    

完成这些后,我们现在可以直接从 AWS RDS 查询我们的数据。当你开始在数据科学领域探索新的模型时,你需要一个地方来存储和组织你的数据。选择一个平台,如 AWS RDSAWS S3 或甚至 GCP BigQuery,将帮助你组织你的数据和研究成果。

使用毒性数据集运行 auto-sklearn

现在我们已经将数据导入到一个工作笔记本中,让我们继续使用 auto-sklean 库来识别最适合我们给定数据集的模型:

  1. 我们可以在我们的 SageMaker 实例中安装 auto-sklearn 库:

    pip install auto-sklearn
    
  2. 接下来,我们可以隔离我们的输入特征和输出值,并相应地进行缩放:

    X = df[["Heteroatoms", "MolWt", "HeavyAtoms", "NHOH", "HAcceptors", "HDonors"]]
    y = df.TPSA.values.ravel()
    from sklearn.preprocessing import MinMaxScaler
    scaler = MinMaxScaler()
    X_scaled = scaler.fit_transform(X)
    
  3. 数据缩放后,我们现在可以继续分离我们的训练集和测试集:

    from sklearn.model_selection import train_test_split
    X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.25)
    
  4. 最后,我们可以导入 sklearn 的回归实现,调整参数,并将模型拟合到我们的数据集:

    import autosklearn.regression
    automl = autosklearn.regression.AutoSklearnRegressor(
        time_left_for_this_task=120,
        per_run_time_limit=30,
        tmp_folder='/tmp/autosklearn_regression_example_tmp2')
    automl.fit(X_train, y_train, dataset_name='dataset_toxicity')
    
  5. 模型完成后,我们可以使用 get_models_with_weights() 函数查看表现最佳的候选模型:

    automl.get_models_with_weights()[0]
    
  6. 最后,我们可以使用R2MSE指标来了解模型的表现,就像我们之前做的那样:

    from sklearn.metrics import r2_score, mean_squared_error
    predictions = automl.predict(X_test)
    p = sns.jointplot(x=y_test, y=predictions, kind="reg")
    p.fig.suptitle(f"automl, R2 = {round(r2_score(y_test, predictions), 3)}, MSE = {round(mean_squared_error(y_test, predictions), 2)}")
    # p.ax_joint.collections[0].set_alpha(0)
    # p.fig.tight_layout()
    p.fig.subplots_adjust(top=0.90)
    

    在绘制输出后,得到以下结果:

图 7.57 – AutoML 模型结果的关联性为 0.97

图 7.57 – AutoML 模型结果的关联性为 0.97

从上面的图中我们可以看到实际结果和预测结果非常吻合,给出了大约 0.97 的 R2 值,显示出强烈的关联性。在下一节中,我们将探讨使用 AWS Autopilot 自动化模型开发过程部分的过程。

使用 AWS Autopilot 进行自动回归

AWS 管理控制台中可以找到许多不同的工具和应用,为开发者可能遇到的大量数据科学和计算机科学问题提供解决方案。特别是有一个工具在数据科学社区中脱颖而出,并开始受到欢迎,被称为AWS AutopilotAWS Autopilot的目的是帮助自动化任何给定数据科学项目中的一些常规任务。我们可以在图 7.58中看到这种视觉表示:

图 7.58 – Autopilot 管道

图 7.58 – Autopilot 管道

用户能够加载他们的数据集,识别几个参数,然后让模型从那里开始,因为它会识别表现最好的模型,优化一些参数,甚至为用户提供生成示例代码,以便进一步优化。让我们使用相同的数据集来演示这个模型的使用:

  1. 我们可以从 SageMaker 控制台开始创建一个 SageMaker Studio 实例,导航到 SageMaker 控制台,并点击右侧的打开 SageMaker Studio按钮。使用快速入门选项、默认设置和新的IAM 角色,点击提交按钮。几分钟后,实例将配置。点击打开工作室按钮:图 7.59 – AWS 中的打开工作室选项

    图 7.59 – AWS 中的打开工作室选项

  2. 在实例配置过程中,让我们将数据集上传到biotech-machine-learning,同时保持所有其他选项为默认值。图 7.60 – 在 AWS 中创建一个桶

    图 7.60 – 在 AWS 中创建一个桶

  3. 创建完成后,打开桶并点击上传按钮。然后,上传经过减少和清理的蛋白质数据集的 CSV 文件。图 7.61 – 在 AWS 中上传文件

    图 7.61 – 在 AWS 中上传文件

  4. 数据集上传后,现在让我们回到 SageMaker。使用左侧的导航窗格,选择SageMaker 组件和注册表标签。使用下拉菜单,选择实验和试验,然后点击创建 Autopilot 实验按钮:图 7.62 – 在 AWS SageMaker Studio 中创建 SageMaker 资源

    图 7.62 – 在 AWS SageMaker Studio 中创建 SageMaker 资源

  5. 让我们继续,并为实验命名,例如dataset-pdb-nodups-cleaned

  6. 连接您的数据部分,选择您之前创建的 S3 存储桶名称以及数据集文件名:图 7.63 – 将数据连接到实验

    图 7.63 – 将数据连接到实验

  7. 接下来,选择目标列,在我们的案例中,是分类列:图 7.64 – 选择模型训练过程中的目标列

    图 7.64 – 选择模型训练过程中的目标列

  8. 最后,你现在可以继续禁用自动部署选项,并点击创建实验。类似于 GCP 的AutoML,应用程序将识别出一组被认为最适合您给定数据集的模型。您可以选择试点完整

    一个完整的实验将在训练和调整模型的同时,允许用户实时查看详细信息和统计数据。它将经历各种阶段,如预处理、候选定义生成、特征工程、模型调整和报告生成。

  9. 完成流程后,将展示一个包含所有训练模型及其相关指标的仪表板,如图 7.65 所示。用户可以探索这些模型,并通过几个简单的点击来部署它们。

图 7.65 – 自动驾驶模型的结果

图 7.65 – 自动驾驶模型的结果

AWS Autopilot 是一个强大且有用的工具,每位数据科学家在面临困难的数据集时都可以使用。它不仅有助于识别给定数据集的最佳模型,还可以帮助预处理数据、调整模型,并为用户提供示例代码。

摘要

恭喜!我们终于到达了这一非常密集但非常有信息量的章节的结尾。在本章中,我们学到了很多不同的东西。在本章的前半部分,我们探索了分类领域,并展示了如何使用单细胞 RNA 数据集应用多个模型——这是生物技术和生命科学领域的经典应用。我们了解了许多不同的模型,包括 KNNs、SVMs、决策树、随机森林和 XGBoost。然后,我们将数据和代码移动到 GCP,我们在 BigQuery 中存储了数据,并配置了一个笔记本实例来运行我们的代码。此外,我们还学习了如何使用 auto-sklearn 自动化与蛋白质分类数据集相关的模型开发过程中的某些手动和劳动密集型部分。最后,我们利用 GCP 的 AutoML 应用程序为我们的数据集开发了一个分类模型。

在本章的后半部分,我们探讨了与毒性数据集相关的回归领域。我们探讨了数据中的相关性概念,并学习了一些重要的回归模型。我们查看的一些模型包括简单和多元线性回归、逻辑回归、决策树回归器以及 XGBoost 回归器。然后,我们将代码迁移到 AWS 的 SageMaker 平台,并使用之前配置的 RDS 来查询我们的数据,并运行 auto-sklearn 进行回归。最后,我们为毒性数据集实现了 AWS Autopilot 的自动化机器学习模型。

到目前为止,我们的大部分时间都花在利用sklearn库开发机器学习模型上了。然而,并非所有数据集都可以通过机器学习进行分类或回归——有时,我们需要更强大的模型集。对于这类数据集,我们可以转向深度学习领域,这将是下一章的重点。

第八章:理解深度学习

在整本书中,我们考察了监督学习和无监督机器学习领域的许多工具和方法。在无监督学习领域,我们探讨了聚类降维,而在监督学习领域,我们探讨了分类回归。在所有这些领域,我们探讨了为我们的数据集开发强大预测模型的最流行算法。然而,正如我们处理的一些数据所看到的,这些模型的表现存在许多限制,这些限制不能通过额外的调整和超参数优化来克服。在这些情况下,数据科学家通常会转向深度学习领域。

如果您还记得我们在第五章“机器学习简介”中看到的整体人工智能空间图,我们会注意到这个整体空间被称为人工智能AI)。在人工智能空间内,我们将机器学习定义为开发模型从数据中学习或泛化并做出预测的能力。现在,我们将探讨机器学习的一个子集,称为深度学习,它专注于使用深度神经网络开发和提取数据中的模式。

在本章中,我们将探讨神经网络和深度学习与生物技术领域的相关思想。特别是,我们将涵盖以下主题:

  • 理解深度学习领域

  • 探索深度学习模型的类型

  • 选择激活函数

  • 使用损失来衡量进度

  • 使用 Keras 库开发模型

  • 教程 – 使用 Keras 和 MLflow 通过 LSTMs 进行蛋白质序列分类

  • 教程 – 使用 AWS Lookout for Vision 进行异常检测

考虑到这些部分,让我们开始吧!

理解深度学习领域

正如我们在引言中提到的,深度学习是机器学习空间的一个子集或分支,它专注于使用神经网络开发模型。使用神经网络进行深度学习的理念源于人类大脑中的神经网络。让我们了解更多关于这个内容。

神经网络

与机器学习类似,开发深度学习模型的理念不是明确定义决策或预测的步骤。这里的主要思想是从数据中泛化。深度学习通过将人脑的树突、细胞体和突触之间的平行关系,在深度学习的背景下,它们作为给定模型的输入、节点和输出,如图所示,使得这一点成为可能:

图 8.1 – 人脑与神经网络的比较

图 8.1 – 人脑与神经网络的比较

这种实现背后的最大好处之一是特征工程的概念。在这本书的早期,我们看到了如何使用各种方法创建或总结特征,例如基本的数学运算(x2)或通过如主成分分析(PCA)等复杂算法。手动构建的特征可能非常耗时,在实践中不可行,这就是深度学习领域可以发挥作用的地方,它能够直接从数据中学习给定数据集的许多潜在特征。

在生物技术领域,大多数应用,从治疗发现的早期阶段到下游的制造,通常是数据丰富的过程。然而,收集到的许多数据本身可能几乎没有任何用途,或者收集的数据可能是针对特定分子的不同批次。也许对于某些分子数据量很大,而对于其他分子则较少。在这些许多情况下,使用深度学习模型可以帮助我们处理与之前讨论的传统机器学习模型相关的特征。

我们可以将特征视为三个不同级别:

  • 低级特征,例如单个氨基酸、一个蛋白质或小分子的元素。

  • 中级特征,例如蛋白质的氨基酸序列和小分子的官能团。

  • 高级特征,例如蛋白质的整体结构或分类,或小分子的几何形状。

下图展示了这些特征的图形表示:

图 8.2 – 三种类型的特征及其一些相关示例

图 8.2 – 三种类型的特征及其一些相关示例

在许多情况下,构建一个健壮的深度学习模型可以解锁相对于其机器学习对应模型更强大的预测模型。在我们已经探索的许多机器学习模型中,我们尝试通过调整和调整超参数来提高模型性能,并且有意识地决定使用具有足够数据量的数据集。增加数据集的大小可能不会导致我们的机器学习模型有任何显著的改进。然而,这并不总是适用于深度学习模型,当有更多数据可用时,深度学习模型往往会提高性能。我们可以在以下图表中看到这种视觉表示:

图 8.3 – 机器学习与深度学习的图形表示

图 8.3 – 机器学习与深度学习的图形表示

在机器学习的背景下使用神经网络在近年来经历了重大增长,这可以归因于大多数行业大数据使用的增加,计算硬件(如 CPU 和 GPU)成本的降低,以及支持今天大多数开源软件和包的社区的增长。用于开发深度学习模型的最常见的两个包是 TensorFlow 和 Keras – 我们将在本章后面探讨这两个包。在我们这样做之前,让我们先谈谈深度学习模型背后的架构。

感知器

深度学习模型最重要的构建块之一是感知器。感知器是一种用于开发监督二分类算法的算法,由弗兰克·罗森布拉特于 1958 年首次发明,有时被称为深度学习的之父。感知器通常由四个主要部分组成:

  • 输入值,通常是从给定的数据集中获取的。

  • 权重,即乘以输入值的值。

  • 净和,即所有输入值的总和。

  • 激活函数,它将结果值映射到输出。

下面的图显示了感知器这四个部分的图形表示:

图 8.4 – 一个感知器的图形表示

图 8.4 – 一个感知器的图形表示

感知器到达给定输入值预测输出的三个主要步骤如下:

  1. 输入值(x1,x2 等)乘以其相应的权重(w1,w2 等)。这些权重在模型的训练过程中确定,以便为每个输入值分配不同的权重。

  2. 每次计算的所有值都汇总到一个称为加权求和的值中。

  3. 然后将加权求和应用于激活函数,将值映射到给定的输出。所使用的特定激活函数取决于给定的情况。例如,在单位步长激活函数的背景下,值将被映射到 0 或 1。

从数学的角度来看,我们可以定义输出值,,如下所示:

在这个方程中,g是激活函数,wo 是偏置,最后的组成部分是输入值的线性组合的总和:

因此,在这个方程中,考虑了最终的输出值。

感知器是现有最简单的深度学习构建块之一,可以通过增加隐藏层的数量来大幅扩展。隐藏层是位于输入层和输出层之间的层。具有很少隐藏层的模型通常被称为神经网络或多层感知器,而具有许多隐藏层的模型被称为深度神经网络

这些每一层都由几个节点组成,数据流与之前看到的感知器类似。输入节点的数量(x1, x2, x3)通常对应于给定数据集中的特征数量,而输出节点的数量通常对应于输出数量。

以下图是神经网络与深度学习之间差异的图形表示:

图 8.5 – 神经网络与深度学习之间的差异

图 8.5 – 神经网络与深度学习之间的差异

在前一张图中,我们可以看到由输入层、一个包含四个节点的单个隐藏层和一个包含四个节点的输出层组成的神经网络或多层感知器(在左侧)。与之前看到的单个感知器类似,这里的想法是隐藏层中的每个节点都会接收输入节点,乘以某个值,然后通过一个激活函数传递以产生输出。在右侧,我们可以看到一个类似模型,但值在确定最终输出值之前会通过几个隐藏层。

探索不同类型的深度学习模型

现在有各种各样的神经网络和深度学习架构,它们在功能、形状、数据流等方面有所不同。近年来,有三种类型的神经网络因其对各种类型数据的承诺和鲁棒性而获得了极大的普及。首先,我们将探讨这些架构中最简单的一种,被称为多层感知器。

多层感知器

多层感知器MLP)是人工神经网络ANNs)中最基本的一种类型。这种网络简单地由层组成,数据以正向方式在这些层中流动,如前一张图所示。数据从输入层流向一个或多个隐藏层,然后最终流向输出层,在那里产生预测。本质上,每一层都试图学习和计算某些权重。ANNs 和 MLPs 有多种不同的形状和大小:它们可以在每一层有不同的节点数、不同的输入数,甚至不同的输出数。我们可以在以下图中看到这种视觉描述:

图 8.6 – MLP 的两个示例

图 8.6 – MLP 的两个示例

MLP 模型通常非常通用,但最常用于结构化表格数据,例如我们一直在工作的结构化蛋白质分类数据集。此外,它们还可以用于图像数据或甚至文本数据。然而,MLPs 在处理如蛋白质序列和时间序列数据等序列数据时通常会遇到困难。

卷积神经网络

卷积神经网络CNNs)是常用的深度学习算法,用于处理和分析图像数据。CNNs 可以接受图像作为输入数据,并通过权重和偏置重新结构化它们以确定重要性,从而使其能够区分一个图像相对于另一个图像的特征。类似于我们之前讨论的深度学习如何类似于大脑中的神经元,CNNs 在区域敏感性方面也类似于人脑和视觉皮层的神经元连接,类似于感受野的概念。CNN 模型最大的成功之一是它们能够通过使用过滤器捕获图像中的空间依赖性和时间依赖性。我们可以在以下图表中看到这一视觉表示:

图 8.7 – CNN 的表示

图 8.7 – CNN 的表示

以创建图像分类模型的想法为例。我们可以使用 ANN 并将 2D 像素图像通过展平转换为向量。一个 4x4 像素矩阵的图像现在将变成一个 1x16 的向量。这种变化将导致两个主要缺点:

  • 图像的空间特征将会丢失,从而降低任何训练模型的鲁棒性。

  • 随着图像尺寸的增长,输入特征的数量将急剧增加。

CNNs 可以通过从图像中提取高级特征来克服这一点,这使得它们在基于图像的数据集中非常有效。

循环神经网络

循环神经网络RNNs)是常用的算法,通常应用于基于序列的数据集。它们的架构与我们之前讨论的 ANNs 非常相似,但 RNNs 可以通过内部记忆记住它们的输入,这使得它们在先前数据非常重要的序列数据集中非常有效。

以一个由各种氨基酸组成的蛋白质序列为例。为了预测蛋白质的类别或其一般结构,模型不仅需要知道使用了哪些氨基酸,还需要知道它们的使用顺序。RNNs 及其许多衍生品在生物技术和生物技术领域的深度学习许多进步中起到了核心作用。我们可以在以下图表中看到这一视觉表示:

图 8.8 – ANN 节点与 RNN 节点的表示

图 8.8 – ANN 节点与 RNN 节点的表示

使用 RNN 作为预测模型有几个优点,主要好处如下:

  • 它们能够捕捉数据点之间的依赖关系,例如句子中的单词

  • 它们能够在时间步长之间共享参数,从而降低整体计算成本

由于这个原因,RNN 已成为开发解决与蛋白质和 DNA 等科学序列数据以及文本和时间序列数据相关问题的模型时越来越受欢迎的架构。

长短期记忆

长短期记忆LSTM)模型是一种设计用于处理基于序列问题的长期依赖能力的 RNN。通常与基于文本的数据一起用于分类、翻译和识别,LSTMs 在近年来获得了前所未有的流行。我们可以像之前那样描绘标准 RNN 的结构,但结构略有不同:

图 8.9 – RNN 与 LSTM 的内部工作原理

图 8.9 – RNN 与 LSTM 的内部工作原理

在前面的图中,Xt 是一个输入向量,ht 是一个隐藏层向量,ot 是一个输出向量。另一方面,使用一些相同的元素,LSTM 的结构可以相当相似。不深入细节,LSTM 的核心思想是细胞状态(顶部水平线)。这个状态的工作方式类似于传送带,数据线性地通过它。细胞内的门是可选地允许信息添加到状态的方法。LSTM 有三个门,都指向细胞状态。

虽然 LSTM 模型及其相关图表一开始可能让人感到有些令人畏惧,但它们在各种领域一次又一次地证明了它们的价值。最近,LSTM 模型被用作抗体设计的生成模型,以及蛋白质序列-结构分类的分类模型。现在我们已经探讨了几个常见的深度学习架构,让我们继续探索它们的主要组件:激活函数。

选择激活函数

回想一下,在前一节中,我们使用激活函数将一个值映射到特定的输出,这取决于该值。我们将激活函数定义为一种数学函数,它使用输入值定义单个节点的输出。用人类大脑的类比,这些函数简单地充当守门人,决定什么将被触发到下一个神经元。激活函数应该具有一些特性,以便模型能够从它那里最有效地学习:

  • 避免梯度消失

  • 低计算成本

人工神经网络使用称为梯度下降的过程进行训练。在这个例子中,让我们假设有一个两层神经网络:

公式图片公式图片

整个网络可以表示如下:

在反向传播步骤中计算权重时,结果如下:

确定导数后,函数变为如下:

如果这个过程在反向传播步骤中通过许多层继续进行,那么初始层的梯度值将会有相当大的减少,从而阻碍模型的学习能力。这就是梯度消失的概念。

另一方面,计算成本也是在设计并部署任何给定模型之前必须考虑的一个特性。从一层到另一层的激活函数必须多次计算,因此计算成本应保持在最低,以避免更长的训练周期。从输入层到输出层的信息流动被称为正向传播

有许多不同类型的激活函数,它们通常用于各种目的。尽管这不是一条硬性规则,但一些激活函数通常与特定的深度学习层一起使用。例如,sigmoidTanh 激活函数通常与 RNNs 一起使用。让我们花点时间看看你可能在旅途中遇到的三种最常见的激活函数:

Figure 8.10 – Various types of activation functions by model type

图 8.10 – 按模型类型划分的各种激活函数

在心中有了这些类型的一些概念后,让我们更深入地探讨它们。sigmoid 函数可能是深度学习领域中应用最广泛的一些函数。它是一种非线性激活函数,有时也被称为逻辑函数(还记得逻辑回归吗?提示提示)。sigmoid 函数的独特之处在于它们可以将值映射到 0 或 1。使用 numpy 库,我们可以轻松地编写一个 Python 函数来计算它:

import numpy as np
def sigmoid_function(x):
    return 1 / (1 + np.exp(-x))

使用这个函数以及 numpy 库,我们可以生成一些数据并相应地绘制 sigmoid 函数:

x1 = np.linspace(-10, 10, 100)
y1 = [sigmoid_function(i) for i in x1]
plt.plot(x1,y1)

作为回报,我们得到以下图表,显示了 sigmoid 函数的曲线性质。注意上下限分别是 1 和 0:

Figure 8.11 – A simple sigmoid function

图 8.11 – 一个简单的 sigmoid 函数

sigmoid 激活函数的一个最大问题是输出可能会饱和,即大于 1.0 的值被映射到 1,而小于 0 的值被映射到 0。这可能导致某些模型无法泛化或从数据中学习,这与我们在本章前面讨论的梯度消失问题有关。

另一方面,另一个常见的激活函数是sigmoid函数。Tanh 函数在意义上是对称的,因为它通过点(0, 0),并且它的值范围在 1 和-1 之间,与它的sigmoid对应函数不同,使其成为一个稍微更好的函数。我们不必像之前那样在 Python 中定义我们的函数,而是可以利用numpy库中的优化函数:

x2 = np.linspace(-5, 5, 100)
y2 = np.tanh(x2)
plt.plot(x2, y2)

执行此代码后,我们得到以下图表。注意图表的中心是点(0, 0),而上下值分别为 1.00 和-1.00:

图 8.12 – 简单的 Tanh 函数

图 8.12 – 简单的 Tanh 函数

与其sigmoid对应函数类似,sigmoid,使其成为一个稍微更好的函数来使用。

最后,另一个常用的激活函数是Rectified Linear UnitReLU)。ReLU激活函数是专门开发来避免处理较大数值时的饱和现象的。该函数的非线性特性使其能够学习数据中的模式,而其线性特性使得它相对于我们迄今为止看到的其他函数更容易解释。让我们继续在 Python 中探索这一点:

def relu_function(x):
    return np.array([0, x]).max()
x3 = np.linspace(-5, 5, 100)
y3 = [relu_function(i) for i in x3]
plt.plot(x3, y3)

执行此代码后得到以下图表。注意ReLU函数利用了激活函数的线性和非线性特性,使其兼具两者的优点:

图 8.13 – 简单的 ReLU 函数

图 8.13 – 简单的 ReLU 函数

ReLU激活函数已经成为数据科学家中最受欢迎的激活函数之一,如果不是最受欢迎的,这得益于其实施的简便性和在模型开发和训练过程中的稳健速度。然而,ReLU激活函数确实有其缺点。例如,当 x = 0(在点 0, 0)时,该函数不可微分,因此无法计算该值的梯度下降。

另一个值得提及的激活函数被称为SoftmaxSoftmax与我们迄今为止看到的其他激活函数非常不同,因为它为一系列值计算一个概率分布,这些值与向量中每个值的相对尺度成比例,它们的总和始终等于 1。

常用于numpy库:

def softmax_function(x):
    ex = np.exp(x - np.max(x))
    return ex / ex.sum()
x4 = [1, 2, 3, 4, 5]
y4 = softmax_function(x4)
print(y4)

打印值后,我们得到以下结果:

图 8.14 – Softmax 函数的结果

图 8.14 – Softmax 函数的结果

使用Softmax作为激活函数的两个主要优势是输出值介于 0 和 1 之间,并且它们总是加起来等于 1.0。作为回报,这使得函数可以用来理解交叉熵中的发散概念。我们将在本章后面更详细地探讨这个话题。

我们之前讨论的各种激活函数在应用于各种应用时各有优缺点。例如,sigmoid函数通常用于二元和多标签分类应用,而Softmax函数通常用于多类分类。这并不是一个硬性规则,而只是一个指南,帮助你将函数与其最有可能成功的应用相匹配:

图 8.15 – 按问题类型划分的激活函数

图 8.15 – 按问题类型划分的激活函数

激活函数是任何深度学习模型的重要组成部分,并且通常被视为变革者,因为仅仅改变一个函数为另一个函数就可以极大地提高模型的表现。在下一节中,我们将更详细地探讨如何在深度学习的范围内量化模型性能。

使用损失衡量进度

当我们讨论分类和回归的领域时,我们概述了一些衡量和量化我们模型之间性能的度量。当涉及到分类时,我们使用了精确度准确度,而在回归中,我们使用了MAEMSE。在深度学习的范围内,我们将使用一个称为损失的度量。神经网络的损失简单地说就是由于做出错误预测而产生的成本的度量。以一个简单的具有三个输入值和一个输出值的神经网络为例:

图 8.16 – 展示输入和输出值的神经网络

图 8.16 – 展示输入和输出值的神经网络

在这个例子中,我们使用[2.3, 3.3, 1.2]作为模型的输入值,相对于实际值 1.0 的预测值为 0.2。我们可以如下展示损失:

在这个函数中,是预测值,而是实际值。

另一方面,经验损失是整个数据集总损失的度量。我们可以如下表示经验损失:

在这个函数中,我们计算所有计算的损失总和。

在整个模型训练过程中,我们的主要目标将是通过一个称为损失优化的过程来最小化这个损失。损失优化的主要思想是确定一组权重,以帮助实现可能的最小损失。我们可以将梯度下降的概念可视化为一个从初始起始值(损失高)移动到最终值(损失低)的过程。我们的目标是确保收敛到全局最小值而不是局部最小值,如下面的图所示:

图 8.17 – 损失优化的过程

图 8.17 – 损失优化过程

我们每向前迈出一步以接近最小值,都称为一个学习步,其学习率通常由用户决定。这个参数只是我们可以使用 Keras 指定的许多参数之一,我们将在下一节中了解它。

使用 Keras 进行深度学习

在数据科学的领域内,各种框架的可用性和使用始终是我们用来标准化开发和部署模型的方法的关键。到目前为止,我们已将机器学习努力集中在使用 scikit-learn 框架上。在本节中,我们将了解三个专注于深度学习的全新框架:KerasTensorFlowPyTorch。这两个框架在数据科学家开发各种深度学习模型时最为流行,因为它们提供了针对众多问题和用例的全面 API 列表。

理解 Keras 和 TensorFlow 之间的区别

虽然这两个平台允许用户开发深度学习模型,但有一些区别需要了解。TensorFlow 被称为端到端的机器学习平台,提供了全面的库、工具和众多资源。用户可以管理数据、开发模型和部署解决方案。与大多数其他库不同,TensorFlow通过其 API 提供低级和高级抽象,使用户在开发模型时具有很大的灵活性。另一方面,Keras提供用于开发神经网络的 API,这些 API 使用TensorFlow运行。这个库的高级特性使得用户只需几行 Python 代码就可以开始开发和训练复杂的神经网络。Keras 通常被认为用户友好、模块化和可扩展。还有一个在深度学习空间中常用的第三方库,称为PyTorchPyTorch是一个低级 API,以其在模型训练过程中的卓越速度和优化而闻名。这个库中的架构通常很复杂,不适合初学者材料,因此它们不在这个书的范围内。然而,它值得提及,因为它是在机器学习空间中最常见的库之一,你可能会遇到。让我们更详细地看看这三个:

图 8.18 – 三种最常见的深度学习框架的比较

图 8.18 – 三种最常见的深度学习框架的比较

每个库都有其优缺点,你应该根据你设定的任务选择其中一个库。鉴于我们第一次探索深度学习模型的发展,我们将专注于使用 Keras 库。

Keras 和人工神经网络入门

在我们进行完整教程之前,让我们看看使用Keras库的一个示例,因为我们尚未探索其功能性和代码:

  1. 首先,我们需要一些样本数据来使用。让我们利用sklearn中的make_blobs类来创建一个分类数据集。

  2. 我们将指定需要两个类别(二分类)和5的聚类标准差,以确保两个聚类重叠,使数据集更具挑战性:

    from sklearn.datasets import make_blobs
    X, y = make_blobs(n_samples=2000, centers=2, n_features=4, random_state=1, cluster_std=5)
    
  3. 接下来,我们可以使用MinMaxScaler()类对数据进行缩放:

    from sklearn.preprocessing import MinMaxScaler
    scalar = MinMaxScaler()
    scalar.fit(X)
    X_scaled = scalar.transform(X)
    
  4. 在此转换之后,我们可以将数据分为训练集和测试集,类似于我们之前所做的那样:

    from sklearn.model_selection import train_test_split
    X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.25)
    
  5. 让我们先转换数组为 DataFrame,以便在之前检查数据的前几行:

    dfx_train = pd.DataFrame(X_train, columns=["Feature1", "Feature2", "Feature3", "Feature4"])
    dfx_train.head()
    

    这将生成以下表格:

    图 8.19 – 特征 DataFrame 中的数据示例

    图 8.19 – 特征 DataFrame 中的数据示例

  6. 我们可以使用seaborn库来绘制训练数据集的前两个特征,以检查两个聚类的重叠情况:

    sns.scatterplot(x=dfx_train.Feature1, y=dfx_train.Feature2, hue=y_train)
    

    下面的图表显示了前面代码的输出:

    图 8.20 – 显示两个类别重叠性质的散点图

    图 8.20 – 显示两个类别重叠性质的散点图

    在这里,我们可以看到数据混合得相当好,这使得我们探索的一些机器学习模型难以以高精度准确度将两个类别分开。

  7. 数据准备就绪后,我们可以继续使用 Keras 库。设置模型最流行的方法之一是使用Keras中的Sequential()类。让我们继续导入该类并实例化一个新的模型:

    from keras.models import Sequential
    model = Sequential()
    
  8. 现在模型已经实例化,我们可以使用Dense类向我们的模型添加一个新层。我们还可以指定节点4),input_shape4,对应四个特征),activationrelu)以及层的唯一名称

    from keras.layers import Dense
    model.add(Dense(4, input_shape=(4,), activation='relu', name="DenseLayer1")) 
    
  9. 要回顾我们迄今为止构建的模型,我们可以使用summary()函数:

    model.summary()
    

    这将给我们提供一些关于迄今为止模型的信息和细节:

    图 8.21 – 模型摘要的样本输出

    图 8.21 – 模型摘要的样本输出

  10. 我们可以通过在事实之后再次使用model.add()函数向我们的模型添加更多层,也许会有不同数量的节点:

    model.add(Dense(8, activation='relu', name="DenseLayer2"))
    
  11. 由于我们正在开发一个01,模型只能有一个输出值。因此,我们需要添加一个额外的层,将节点数量从 8 减少到 1。此外,我们将激活函数改为sigmoid

    model.add(Dense(1, activation='sigmoid', name="DenseLayer3"))
    
  12. 现在我们已经设置了模型的总体架构,我们需要使用compile函数并指定我们的损失。由于我们正在创建一个二元分类器,我们可以使用binary_crossentropy损失,并将准确率指定为我们感兴趣的主要指标:

    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=["accuracy"])
    

    模型准备就绪后,让我们使用摘要函数再次检查它:

    图 8.22 – 模型摘要的样本输出

    图 8.22 – 模型摘要的样本输出

    到目前为止,模型相当简单。它将在第一层接收具有四个特征的数据库,将其扩展到第二层的八个节点,然后在第三层减少到一个单一输出。模型设置完毕后,我们可以继续训练它。

    我们可以通过使用model.fit()函数并指定X_trainy_train集合来训练一个模型。此外,我们将指定 50 个epochs进行训练。epochs简单地是遍历或迭代的次数。我们还可以控制模型的详细程度,以便我们可以控制训练过程中想要看到的数据输出量。

  13. 回想一下,在我们早期的机器学习模型中,我们只使用了训练数据来训练模型,并在训练完成后保留了测试数据来测试模型。这里我们将使用相同的方法;然而,我们将利用validation split在训练过程中的高级特性。深度学习模型几乎总是会过拟合你的数据。在训练过程中使用验证分割可以帮助减轻这一点:

    history = model.fit(X_train, y_train, epochs=50, verbose=1, validation_split=0.2)
    

    当模型开始训练过程时,它将开始产生以下输出。您可以通过查看左侧的epochs数量和右侧的指标来监控性能。在训练模型时,我们的目标是确保损失指标持续下降,而准确率在上升。以下屏幕截图展示了这一点的示例:

    图 8.23 – 模型输出的样本

    图 8.23 – 模型输出的样本

  14. 模型训练完成后,让我们快速检查分类指标,就像之前做的那样,以了解性能。我们可以通过使用测试数据并使用classification_report来计算我们的指标开始。请注意,predict()方法不返回一个类别,而是一个概率,需要将其四舍五入到01,因为这是一个二元分类问题:

    y_pred = (model.predict(X_test) > 0.5).astype("int32").ravel()
    from sklearn.metrics import classification_report
    print(classification_report(y_pred, y_test))
    

    打印报告后,我们将得到以下结果:

    图 8.24 – 模型的结果

    图 8.24 – 模型的结果

  15. 我们可以看到包含模型训练历史的history变量:

    fig = plt.figure(figsize=(10,10))
    # total_rows, total_columns, subplot_index(1st, 2nd, etc..)
    plt.subplot(2, 2, 1)
    plt.title("Accuracy", fontsize=15)
    plt.xlabel("Epochs", fontsize=15)
    plt.ylabel("Accuracy (%)", fontsize=15)
    plt.plot(history.history["val_accuracy"], label='Validation Accuracy', linestyle='dashed')
    plt.plot(history.history["accuracy"], label='Training Accuracy')
    plt.legend(["Validation", "Training"], loc="lower right")
    plt.subplot(2, 2, 2)
    plt.title("Loss", fontsize=15)
    plt.xlabel("Epochs", fontsize=15)
    plt.ylabel("Loss", fontsize=15)
    plt.plot(history.history["val_loss"], label='Validation loss', linestyle='dashed')
    plt.plot(history.history["loss"], label='Training loss')
    plt.legend(["Validation", "Training"], loc="lower left")
    

    执行此代码后,我们将收到以下图表,它显示了模型训练过程中的准确率和损失的变化:

图 8.25 – 模型的准确率和损失

图 8.25 – 模型的准确率和损失

在训练模型时,请记住,主要目标是确保损失随着时间逐渐减少,而不是增加。此外,我们的次要目标是确保模型的准确率缓慢而稳定地增加。在尝试诊断表现不佳的模型时,第一步是生成此类图表,在尝试通过改变模型来改善指标之前,了解任何潜在的问题。

当我们在前几章中与大多数机器学习模型合作时,我们了解到我们可以通过以下方式改变这些指标:

  • 改进我们的数据预处理。

  • 调整我们的超参数或更改模型。在深度学习的范畴内,除了之前提到的选项外,我们还有一些额外的工具可以调整以满足我们的需求。例如,我们可以通过添加或删除层和节点来改变整体架构。此外,我们还可以更改每一层的激活函数,使其与我们的问题陈述最为契合。我们还可以更改优化器或优化器的学习率(在这个模型中是 Adam)。

由于可以做出许多可能对任何给定模型产生重大影响的更改,我们需要组织我们的工作。我们既可以创建多个模型并在电子表格中手动记录我们的指标,也可以利用专门为处理此类用例设计的库:MLflow。我们将在下一节中更详细地了解MLflow

教程 – 使用 Keras 和 MLflow 进行蛋白质序列分类

深度学习在近年来获得了极大的流行,促使许多科学家转向该领域作为解决和优化科学问题的新的手段。在生物技术领域,深度学习最流行的应用之一涉及蛋白质序列数据。到目前为止,在这本书中,我们专注于开发针对结构化数据的预测模型。现在,我们将把注意力转向那些在某种意义上是序列性的数据,即序列中的元素与其前一个元素之间存在某种关系。在本教程中,我们将尝试开发一个蛋白质序列分类模型,我们将根据已知的家族访问号对蛋白质序列进行分类,使用Pfam(pfam.xfam.org/)数据集。

重要提示

Pfam数据集:Pfam:2021 年的蛋白质家族数据库 J. Mistry, S. Chuguransky, L. Williams, M. Qureshi, G.A. Salazar, E.L.L. Sonnhammer, S.C.E. Tosatto, L. Paladin, S. Raj, L.J. Richardson, R.D. Finn, A. Bateman Nucleic Acids Research (2020) doi: 10.1093/nar/gkaa913 (Pfam:2021 年的蛋白质家族数据库).

Pfam数据集包含几个列,如下所示:

  • Family_id:序列所属家族的名称(例如,filamin)

  • Family Accession:我们的模型将尝试预测的类别或输出

  • Sequence:我们将用作模型输入的氨基酸序列

在本教程中,我们将使用序列数据开发几个预测模型,以确定每个序列的相关家族访问号。这些序列处于原始状态,长度和大小不同。我们需要预处理数据并按这种方式构建它,以便为序列分类做准备。至于标签,我们将使用一组平衡的不同标签来开发模型,以确保模型不会学习任何特定的偏差。

当我们开始开发理想的分类模型时,我们需要调整许多可能的参数以最大化性能。为了跟踪这些变化,我们将使用 MLflow (mlflow.org) 库。MLflow 中有四个主要组件:

  • MLflow 跟踪:允许用户记录和查询实验

  • MLflow 项目:打包数据科学代码

  • MLflow 模型:部署训练好的机器学习模型

  • MLflow 注册表:存储和管理你的模型

在本教程中,我们将探讨如何使用 MLflow 跟踪来跟踪和管理蛋白质序列分类模型的发展。考虑到这些事项,让我们开始吧。

导入必要的库和数据集

我们将开始导入标准的库集合,然后是格式为 CSV 文档的数据集:

import pandas as pd
import numpy as np
from tensorflow.keras.utils import to_categorical
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style("darkgrid")

现在库已经导入,我们也可以导入数据集。我们将首先指定路径,然后使用 for 循环连接数据集:

PATH = "../../../datasets/dataset_pfam/"
files = []
for i in range(8):
    df = pd.read_csv(PATH+f"dataset_pfam_seq_sd{i+1}.csv", index_col=None, header=0)
    files.append(df)

df = pd.concat(files, axis=0, ignore_index=True)
df.shape

在导入数据集后,我们立即注意到它包含五个列和约 1.3 百万行数据 – 比我们之前处理的数据略大。我们可以使用 .head() 函数快速查看数据集:

图 8.26 – 蛋白质序列数据集的数据样本

图 8.26 – 蛋白质序列数据集的数据样本

数据集成功导入后,让我们进一步探索数据集的详细信息。

检查数据集

我们可以使用 isna() 函数确认 DataFrame 中数据的完整性,然后使用 sum() 函数按列进行总结:

df.isna().sum()

现在,让我们更仔细地查看数据集的 family_accession 列(我们的模型输出)。我们可以通过分组列并使用 value_counts() 函数,然后使用 n_largest() 函数来检查实例总数,以获取该列中最常见的 10 个条目:

df["family_accession"].groupby(df["family_accession"]).value_counts().nlargest(10)

数据分组将产生以下结果:

图 8.27 – 数据集中值计数高于 1,200 的类别摘要

图 8.27 – 数据集中值计数高于 1,200 的类别摘要

在这里,我们可以看到 1,500 条条目似乎是前 10 个值截止点。我们还可以通过了解序列的平均长度来更仔细地查看序列列(我们模型的输入),我们可以使用seaborn库中的displot()函数绘制每个序列长度的计数:

sns.displot(df["sequence"].apply(lambda x: len(x)), bins=75, height=4, aspect=2) 

执行此代码将产生以下结果:

图 8.28 – 数据集中序列长度计数的直方图

图 8.28 – 数据集中序列长度计数的直方图

从这张图以及使用mean()median()函数,我们可以看到平均长度和最常见的长度大约是 155 和 100 个氨基酸。我们将在确定输入序列的截止值时使用这些数字。

既然我们对数据有了更好的了解,现在是时候为我们的分类模型准备数据集了。理论上,我们可以在整个数据集上无限制地训练模型 - 然而,模型将需要更长的时间来训练。此外,如果不考虑平衡性而在所有数据上训练,我们可能会在模型中引入偏差。为了减轻这两种情况,让我们通过过滤至少有 1,200 个观测值的分类来减少这个数据集:

df_filt = df.groupby("family_accession").filter(lambda x: len(x) > 1200)

由于一些类别的观测值明显多于 1,200 个,我们可以使用sample()函数随机选择恰好 1,200 个观测值:

df_bal = df_filt.groupby('family_accession').apply(lambda x: x.sample(1200))

现在,我们可以使用head()函数检查过滤和平衡后的数据集:

df_red = df_bal[["family_accession", "sequence"]].reset_index(drop=True)
df_red.head()

head()函数将产生以下结果:

图 8.29 – 以 DataFrame 形式的数据样本

图 8.29 – 以 DataFrame 形式的数据样本

我们可以通过检查value_counts()函数的长度来检查这个数据集中我们将有多少个类别:

num_classes = len(df_red.family_accession.value_counts())

如果我们检查num_classes变量,我们将看到总共有 28 个可能的类别。

数据集拆分

数据准备就绪后,我们的下一步将是将数据集拆分为训练、测试和验证集。我们将再次使用sklearn中的train_test_split函数来完成此操作:

from sklearn.model_selection import train_test_split
X_train, X_test = train_test_split(df_red, test_size=0.25)
X_val, X_test = train_test_split(X_test, test_size=0.50)

数据现在已拆分,让我们继续预处理它。

数据预处理

数据拆分后,我们需要预处理数据集以便用于我们的神经网络模型。首先,我们需要将序列减少到最常见的 20 种氨基酸,并将序列转换为整数。这将加快训练过程。首先,我们将创建一个包含氨基酸及其对应值的字典:

aa_seq_dict = {'A': 1,'C': 2,'D': 3,'E': 4,'F': 5,'G': 6,'H': 7,'I': 8,'K': 9,'L': 10,'M': 11,'N': 12,'P': 13,'Q': 14,'R': 15,'S': 16,'T': 17,'V': 18,'W': 19,'Y': 20}

接下来,我们可以遍历序列并将字符串值转换为相应的整数。请注意,我们将为此完成训练、测试和验证集:

def aa_seq_encoder(data):
    full_sequence_list = []
    for i in data['sequence'].values:
        row_sequence_list = []
        for j in i:
            row_sequence_list.append(aa_seq_dict.get(j, 0))
        full_sequence_list.append(np.array(row_sequence_list))
    return full_sequence_list

X_train_encode = aa_seq_encoder(X_train) 
X_val_encode = aa_seq_encoder(X_val) 
X_test_encode = aa_seq_encoder(X_test)

接下来,我们需要填充序列以确保它们的长度都相同。为了实现这一点,我们可以使用keras中的pad_sequences函数。我们将为每个序列指定max_length为 100,因为这与我们之前看到的中间值相近。此外,我们将使用'post'来填充序列,以确保我们在序列的末尾而不是开头进行填充:

from keras.preprocessing.sequence import pad_sequences
max_length = 100
X_train_padded = pad_sequences(X_train_encode, maxlen=max_length, padding='post', truncating='post')
X_val_padded = pad_sequences(X_val_encode, maxlen=max_length, padding='post', truncating='post')
X_test_padded = pad_sequences(X_test_encode, maxlen=max_length, padding='post', truncating='post')

我们可以使用其中一个序列快速查看我们所做的更改。首先,我们有原始序列作为一个字符串

X_train.sequence[1][:30]
'LRDLRHFLAVAEEGHIGRAAARLHLSQPPL'

接下来,我们可以将序列编码以去除不常见的氨基酸,并将字符串转换为整数列表:

X_train_encode[1][:30]
array([ 7, 10, 15, 18, 10,  3, 18, 16, 14, 17, 15,  5, 12, 10,  7, 16, 15, 12, 12,  8, 18,  4, 14,  5, 17,  4,  2])

最后,我们可以将序列的长度限制为 100 个元素,或者用零填充以达到 100 个元素:

X_train_padded[1][:30]
array([ 7, 10, 15, 18, 10,  3, 18, 16, 14, 17, 15,  5, 12, 10,  7, 16, 15, 12, 12,  8, 18,  4, 14,  5, 17,  4,  2,  0,  0,  0])

现在我们已经预处理了输入数据,我们还需要预处理输出值。我们可以使用sklearn中的LabelEncoder类来完成这个任务。我们的主要目标是将数据框列中的标签列表转换为编码后的列表:

from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
y_train_enc = le.fit_transform(X_train['family_accession'])
y_val_enc = le.transform(X_val['family_accession'])
y_test_enc = le.transform(X_test['family_accession'])

最后,我们可以使用sklearn中的to_categorical函数将类别向量转换为二进制类别矩阵:

from tensorflow.keras.utils import to_categorical
y_train = to_categorical(y_train_enc)
y_val = to_categorical(y_val_enc)
y_test = to_categorical(y_test_enc)

要回顾我们在这里所做的更改,我们可以使用DataFrame的单个列:

X_train['family_accession']

我们可以在以下图表中看到这个结果:

图 8.30 – 类别列表

图 8.30 – 类别列表

接下来,我们必须将类别编码成一系列数值,每个数值代表一个特定的类别:

y_train_enc
array([ 4,  3, 21, ..., 10, 15, 12], dtype=int64)

最后,我们必须将结构转换为二进制类别矩阵,使得每一行都是一个包含 27 个零和一个 1 的列表,1 代表它所属的类别:

y_train[5]
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32)

这样,我们的数据集已经完全预处理完毕,准备好在教程的模型开发阶段使用。

使用 Keras 和 MLflow 开发模型

与我们之前使用Keras库开发模型的例子类似,我们再次将使用Sequential类:

  1. 我们将首先从 Keras 导入我们需要的层和其他项目:

    import tensorflow as tf
    from keras.models import Sequential
    from keras.layers import Dense, Conv1D, MaxPooling1D, Flatten, Input, Bidirectional, LSTM, Dropout
    from keras.layers.embeddings import Embedding
    from keras.regularizers import l2
    from keras.models import Model
    import mlflow
    import mlflow.keras
    
  2. 现在,我们可以使用序列类创建一个新的模型实例,并通过添加一些感兴趣的层来填充它。我们首先添加一个嵌入层,将正整数转换为密集向量。我们将指定input_dim21来表示氨基酸索引的大小加 1,以及output_dim为 32。此外,我们将input_length设置为max_length减去序列的长度:

    model = Sequential()
    model.add(Embedding(21, 8, input_length=max_length, name="EmbeddingLayer"))
    
  3. 接下来,我们将添加一个 LSTM 层,并用Bidirectional层包装,以在两个方向上运行输入——从过去到未来和从未来到过去:

    model.add(Bidirectional(LSTM(8), name="BidirectionalLayer"))
    
  4. 接下来,我们将添加一个Dropout层,以帮助防止模型过拟合:

    model.add(Dropout(0.2, name="DropoutLayer"))
    
  5. 最后,我们将以一个Dense层结束,并将节点数设置为28,以便与输出形状相对应。注意,我们在这里使用 Softmax 激活函数:

    model.add(Dense(28, activation='softmax', name="DenseLayer"))
    
  6. 模型的架构准备就绪后,我们可以分配一个优化器(Adam),编译模型,并检查摘要:

    opt = tf.keras.optimizers.Adam(learning_rate=0.1)
    model.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])
    
  7. 现在,让我们继续使用fit()函数训练我们的模型并分配 30 个 epoch。注意,从我们之前的教程中可以看出,训练深度学习模型可能非常耗时且昂贵,因此训练一个没有学习的模型可能是一种巨大的时间浪费。为了减轻这种情况,我们可以在 Keras 中实现所谓的回调,即在模型不再学习(即损失不再下降)时结束训练周期:

    from keras.callbacks import EarlyStopping
    es = EarlyStopping(monitor='val_loss', patience=5, verbose=1)
    
  8. 最后,我们可以继续通过调用autolog()函数和拟合模型来记录我们的新运行,就像我们之前做的那样。MLflow 提供了许多不同的方法来记录参数和指标,你不仅限于使用autolog()

    mlflow.keras.autolog()
    history = model.fit(
        X_train_padded, y_train,
        epochs=30, batch_size=256,
        validation_data=(X_val_padded, y_val),
        callbacks=[es]
        )
    

    假设你正确地遵循了这些步骤,模型将打印一条消息说明正在使用 MLflow,你应该看到一个新的目录出现在你的当前笔记本旁边。在完成训练过程后,你可以像之前那样绘制结果,得到以下图表:

图 8.31 – 该模型第一次迭代的准确率和结果

图 8.31 – 该模型第一次迭代的准确率和结果

在这里,我们可以看到准确率似乎保持在 80-85%左右,而损失保持在 0.6 到 0.8 之间。我们可以看到模型没有在学习。也许需要改变参数?让我们继续将节点数从8改为12,将学习率从0.1改为0.01。在编译新模型、调用autolog()函数和训练新数据集后,我们将得到一个新的图表:

图 8.32 – 该模型下一次迭代的准确率和结果

图 8.32 – 该模型下一次迭代的准确率和结果

在这里,我们可以看到模型的损失,无论是训练还是验证,都相当好地下降,直到回调在约 30 个 epoch 时停止训练。另一方面,准确率在开始时急剧上升,随后稳定上升,也停止在过程的 30 个 epoch 处。我们可以不断地做出改变并多次调用autolog()函数,让系统代表我们记录变化和产生的指标。经过几次迭代后,我们可以使用mlflow ui来检查我们模型的性能。在笔记本本身中,输入以下命令:

!mlflow ui

接下来,导航到http://localhost:5000/。在那里,你将能够看到MLflow UI,你将能够查看模型、它们的参数和相关的指标:

图 8.33 – MLflow UI 的示例

图 8.33 – MLflow UI 的示例

这样,你可以选择最佳模型并继续你的项目。

检查模型的性能

现在已经选出了表现最好的模型,让我们更好地了解其相关的classification_report,正如我们之前所做的那样,几乎达到了 99%的精确率和召回率。或者,鉴于我们总共有 28 个类别,我们可以使用混淆矩阵来更好地了解数据:

from sklearn.metrics import confusion_matrix
y_pred = model.predict(X_test_padded)
cf_matrix = confusion_matrix(np.argmax(y_test, axis=1), np.argmax(y_pred, axis=1))

在计算了混淆矩阵后,我们可以使用热图来可视化结果:

import seaborn as sns
plt.figure(figsize=(15,10))
sns.heatmap(cf_matrix, annot=True, fmt='', cmap='Blues')

执行此操作后,我们将得到以下图表:

图 8.34 – 模型结果的混淆矩阵

图 8.34 – 模型结果的混淆矩阵

在这里,我们可以看到模型的性能相当稳健,因为它给出了非常好的结果!Keras、TensorFlow 和 PyTorch 是伟大的包,可以帮助我们开发稳健且具有高影响力的模型来解决特定问题。通常,我们会发现 AWS 可能已经存在一个(或一组)模型,可以用很少或没有代码来解决我们的复杂问题。我们将在下一节中探讨这个例子。

教程 – 使用 AWS Lookout for Vision 进行制造中的异常检测

在上一节中,我们准备并训练了一个深度学习模型来对给定的蛋白质类别进行分类。我们经历了数据预处理、模型开发、测试参数、编辑架构以及选择最大化我们感兴趣指标的组合的过程。虽然这个过程通常会产生良好的结果,但我们有时可以利用 AWS 等平台架构自动为我们开发模型。在本教程中,我们将利用一个名为AWS Lookout for Vision的工具(aws.amazon.com/lookout-for-vision/)来帮助我们准备一个能够检测数据集中异常的模型。

在整个教程中,我们将使用一个包含与制造药物产品DP)相关的图像的数据集。每张图像都包含一个在制造周期结束时捕获的瓶子的图像。大多数瓶子都很干净,没有杂质。然而,一些瓶子含有轻微的杂质,如下面的图所示:

图 8.35 – 合格瓶与损坏瓶的示例

图 8.35 – 一个合格瓶与损坏瓶的示例

拒绝损坏或杂质瓶子的过程通常是通过手工完成的,可能相当耗时。我们被要求实施一个自动化的解决方案来解决这个问题,而我们只有几天的时间来完成。与其开发我们自己的定制深度学习模型来检测图像中的异常,我们可以利用Amazon Lookout for Vision。在本教程中,我们将首先将我们的图像数据集上传到 S3,将图像导入框架,并开始训练我们的模型。考虑到这一点,让我们开始吧!

在本书的 GitHub 仓库中,您可以找到一个名为vials_input_dataset_s3的目录,其中包含了一组正常和损坏的试管。如果我们仔细查看我们的数据集,我们会注意到它使用目录层次结构构建,如图所示:

图 8.36 – 一个合格的试管与一个损坏的试管的示例

图 8.36 – 一个合格的试管与一个损坏的试管的示例

我们将首先将图像导入到本书中一直使用的同一个 S3 存储桶中:

  1. 首先,从 AWS 控制台中导航到 S3 并选择感兴趣的存储桶。在这种情况下,我将选择biotech-machine-learning

  2. 接下来,点击橙色上传按钮,选择vials_input_dataset_s3文件夹,然后点击上传。这个过程可能需要几分钟,具体取决于您的网络连接。

  3. 现在,点击页面右上角的复制 S3 URI按钮。我们将在几分钟后需要这个 URI。

现在,我们的数据已可用于在 S3 存储桶中使用。接下来,我们可以专注于将数据导入模型并开始模型训练过程:

  1. 首先,导航到 AWS 控制台中的 Amazon Lookout for Vision。然后,点击开始使用按钮:图 8.37 – Amazon Lookout for Vision 的前页

    图 8.37 – Amazon Lookout for Vision 的前页

  2. 点击页面右侧的创建项目按钮,并为您的项目命名。

  3. 一旦项目创建完成,请继续点击页面左侧的创建数据集按钮。

  4. 选择第二个选项以创建训练数据集和测试数据集图 8.38 – 在 AWS Lookout for Vision 中创建数据集

    图 8.38 – 在 AWS Lookout for Vision 中创建数据集

  5. 接下来,在training路径内,如图所示:图 8.39 – 在 AWS Lookout for Vision 中创建数据集

    图 8.39 – 在 AWS Lookout for Vision 中创建数据集

  6. 此外,请确保选择自动标注选项,以确保我们的标签被 AWS 接收。

  7. 对于测试数据集,重复此过程,但请确保在 S3 URI 路径中将“training”替换为validation。然后,点击创建数据集

  8. 一旦数据集创建完成,您将被带到一个新的页面,您可以在此页面上直观地检查数据集的准备工作。然后,您可以在页面的右上角点击训练模型按钮以开始模型训练过程。这个过程可能耗时,可能需要几个小时:

图 8.40 – 训练模型前的数据集

图 8.40 – 训练模型前的数据集

通过这样做,您将看到模型的最终结果,它将显示精确度、召回率和 F1 分数,如下面的截图所示:

图 8.41 – 模型性能指标页面

图 8.41 – 模型性能指标页面

完成最后一步后,我们成功开发了一个能够检测制造过程中异常的稳健模型!我们不仅能够在短短几小时内创建模型,而且还做到了无需任何代码!

摘要

在本章中,我们迈出了重大步伐,涵盖了深度学习和神经网络必须了解的相当一部分元素。首先,我们研究了神经网络的根源以及它们是如何产生的,然后深入探讨了感知器及其基本功能形式。然后,我们开始了一段探索最常见四种神经网络的旅程:MLP、CNN、RNN 和 LSTM。我们更好地理解了如何选择激活函数、衡量损失以及使用 Keras 库实现我们的理解。

接下来,我们采取了一种更少理论性和更多实践性的方法,因为我们处理了第一个具有序列性质的数据集。我们花费了大量时间进行数据预处理,开发我们的模型,使用 MLflow 组织模型开发,并审查其性能。遵循这些步骤使我们能够为当前问题创建一个定制且合适的模型。最后,我们通过使用 AWS Lookout for Vision 训练一个能够检测安瓿瓶图像中异常的模型,采取了一种无代码的方法。

深度学习和神经网络的应用在过去几年中确实经历了重大增长,在下一章中,我们将看到与自然语言处理相关的深度学习应用。

第九章:自然语言处理

在上一章中,我们讨论了使用深度学习不仅解决以表格形式存在的结构化数据,还包括顺序数据,其中元素的顺序很重要。在本章中,我们将讨论另一种形式的顺序数据——文本,这属于一个被称为自然语言处理NLP)的领域。我们可以将 NLP 定义为人工智能的一个子集,它与机器学习和深度学习的领域都有重叠,特别是在语言学和计算机科学领域之间的交互。

使用 NLP 进行各种任务的许多知名和有详细记录的应用和成功案例。从垃圾邮件检测器到文档分析器等不同产品都涉及一定程度上的 NLP。在本章中,我们将探讨涉及 NLP 的几个不同领域和应用。

正如我们观察到的许多其他数据科学领域一样,NLP 领域同样广阔且分散,有无数的工具和应用,一本书根本无法完全涵盖。在本章中,我们将努力突出您可能会遇到的最常见和最有用的应用。

在本章中,我们将从结构化和非结构化数据的角度探讨许多与 NLP 相关的流行领域。我们将探讨几个主题,如实体识别、句子分析、主题建模、情感分析和自然语言搜索引擎。

在本章中,我们将涵盖以下主题:

  • NLP 简介

  • 使用 NLTK 和 SciPy 开始 NLP

  • 与结构化数据一起工作

  • 教程——抽象聚类和主题建模

  • 与非结构化数据一起工作

  • 教程——使用 transformers 开发科学数据搜索引擎

带着这些目标,让我们开始吧。

NLP 简介

生物技术领域内,我们经常出于多种原因转向NLP,这通常涉及组织数据和开发模型以找到科学问题的答案。与迄今为止我们调查的许多其他领域不同,NLP 的独特之处在于我们专注于一种手头的数据类型:文本数据。当我们想到 NLP 领域的文本数据时,我们可以将其分为两大类:结构化数据非结构化数据。我们可以将结构化数据视为存在于表格和数据库中的文本字段,其中项目是有组织的、标记的,并且相互链接以便于检索,例如 SQL 或 DynamoDB 数据库。另一方面,我们有所谓的非结构化数据,如文档、PDF 和图像,它们可以包含既不可搜索也不易访问的静态内容。以下图表中可以看到一个例子:

图 9.1 – NLP 中的结构化和非结构化数据

图 9.1 – NLP 中的结构化和非结构化数据

通常,我们希望将文档或基于文本的数据用于各种目的,如下所示:

  • 生成洞察:寻找趋势、关键词或关键短语

  • 分类:自动为各种目的标记文档

  • 聚类:根据特征和特性将文档分组在一起

  • 搜索:快速在历史文档中找到重要知识

在这些示例中,我们需要将我们的数据从非结构化数据转换为结构化状态,以实现这些任务。在本章中,我们将探讨一些关于 NLP 空间的重要和有用的概念和工具,您应该了解。

使用 NLTK 和 SciPy 开始 NLP 之旅

Python 语言中有许多不同的 NLP 库可供用户使用,以完成各种不同的数据分析、洞察生成或预测模型准备任务。为了开始我们在 NLP 领域的旅程,我们将利用两个流行的库,即NLTKSciPy。我们将从导入这两个库开始:

import nltk
import scipy

通常,我们会想要为了特定目的解析和分析原始文本片段。以以下关于生物技术领域的段落为例:

paragraph = """Biotechnology is a broad area of biology, involving the use of living systems and organisms to develop or make products. Depending on the tools and applications, it often overlaps with related scientific fields. In the late 20th and early 21st centuries, biotechnology has expanded to include new and diverse sciences, such as genomics, recombinant gene techniques, applied immunology, and development of pharmaceutical therapies and diagnostic tests. The term biotechnology was first used by Karl Ereky in 1919, meaning the production of products from raw materials with the aid of living organisms."""

在这里,有一个字符串被分配给了paragraph变量。段落可以使用sent_tokenize()函数分割成句子,如下所示:

from nltk.tokenize import sent_tokenize
nltk.download('popular')
sentences = sent_tokenize(paragraph)
print(sentences)

打印这些句子后,我们将得到以下输出:

图 9.2 – 从初始段落中分割出的句子样本列表

图 9.2 – 从初始段落中分割出的句子样本列表

同样,我们可以使用word_tokenize()函数将段落分割成单个单词:

from nltk.tokenize import word_tokenize
words = word_tokenize(sentences[0])
print(words)

打印结果后,您将得到一个类似于以下屏幕截图中的列表:

图 9.3 – 从第一句中分割出的单词样本列表

图 9.3 – 从第一句中分割出的单词样本列表

通常,我们想知道句子中给定单词的词性。我们可以使用pos_tag()函数在给定的字符串上执行此操作 – 在这种情况下,段落的第一个句子:

tokens = word_tokenize(sentences[0])
tags = nltk.pos_tag(tokens)
print(tags)

打印标签后,我们得到一个列表,其中单词或标记列在左侧,而相关的词性列在右侧。例如,Biotechnologybiology被标记为专有名词,而involvingdevelop被标记为动词。我们可以在以下屏幕截图中看到这些结果的示例:

图 9.4 – 领词性标注方法的结果

图 9.4 – 领词性标注方法的结果

除了理解词性之外,我们通常还想知道特定文本字符串中给定单词的频率。为此,我们可以将段落标记为单词,按单词分组,计算实例并绘制它们,或者使用 NLTK 的内置功能:

freqdist = nltk.FreqDist(word_tokenize(paragraph))
import matplotlib.pyplot as plt
import seaborn as sns
plt.figure(figsize=(10,3))
plt.xlabel("Samples", fontsize=20)
plt.xticks(fontsize=14)
plt.ylabel("Counts", fontsize=20)
plt.yticks(fontsize=14)
sns.set_style("darkgrid")
freqdist.plot(30,cumulative=False) 

通过这样做,您将收到以下输出:

图 9.5 – 计算频率(含停用词)的结果

图 9.5 – 计算频率(含停用词)的结果

在这里,我们可以看到最常见的元素是逗号、句号和其他无关紧要的词。对于任何给定分析无关紧要的词被称为regex。让我们继续准备一个函数来清理我们的文本:

from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
nltk.download('punkt')
nltk.download('stopwords')
import re
STOP_WORDS = stopwords.words()
def cleaner(text):
    text = text.lower() #Convert to lower case
    text = re.sub("[^a-zA-Z]+", ' ', text) # Only keep text, remove punctuation and numbers
    text_tokens = word_tokenize(text) #Tokenize the words
    tokens_without_sw = [word for word in text_tokens if not word in STOP_WORDS] #Remove the stop words
    filtered_sentence = (" ").join(tokens_without_sw) # Join all the words or tokens back to a single string
    return filtered_sentence

在这个函数中,有四个主要步骤。首先,我们将文本转换为小写以保持一致性;然后,我们使用正则表达式删除所有标点符号和数字。之后,我们将字符串拆分为单个标记,并删除如果它们在我们的停用词列表中的单词,最后将单词重新组合成一个字符串。

重要提示

请注意,文本清理脚本通常针对特定用例是特定的,这意味着并非所有用例都需要相同的步骤。

现在,我们可以将cleaner函数应用于我们的段落:

clean_paragraph = cleaner(paragraph)
clean_paragraph

我们可以在以下屏幕截图中看到此函数的输出:

图 9.6 – 文本清理函数的输出

图 9.6 – 文本清理函数的输出

在重新计算使用干净文本的频率后,我们可以重新绘制数据并查看结果:

import matplotlib.pyplot as plt
import seaborn as sns
plt.figure(figsize=(10,3))
plt.xlabel("Samples", fontsize=20)
plt.xticks(fontsize=14)

plt.ylabel("Counts", fontsize=20)
plt.yticks(fontsize=14)

sns.set_style("darkgrid")
freqdist.plot(30,cumulative=False)

此代码的输出可以在以下屏幕截图中看到:

图 9.7 – 计算频率(不含停用词)的结果

图 9.7 – 计算频率(不含停用词)的结果

随着我们开始深入挖掘我们的文本,我们通常会希望不仅通过它们的词性来标记项目,还要通过它们的实体来标记,这样我们就可以在称为spacy库的过程中解析日期、人名以及许多其他内容:

import spacy
spacy_paragraph = nlp(paragraph)
spacy_paragraph = nlp(paragraph)
print([(X.text, X.label_) for X in spacy_paragraph.ents])

打印结果后,我们获得一个项目及其相关实体标签的列表。请注意,该模型不仅识别了年份1919作为DATE实体,还识别了描述如21st centuries作为DATE实体:

图 9.8 – 显示文本及其后续标签的 NER 模型的结果

图 9.8 – 显示文本及其后续标签的 NER 模型的结果

我们还可以使用render函数在 Jupyter Notebook 中从视觉角度显示标签:

from spacy import displacy
displacy.render(nlp(str(sentences)), jupyter=True, style='ent')

执行此代码后,我们将收到原始段落,它已被根据识别的实体标签着色,使我们能够直观地查看结果:

图 9.9 – 在 Jupyter Notebook 中渲染的 NER 模型的结果

图 9.9 – 在 Jupyter Notebook 中渲染的 NER 模型的结果

我们还可以使用 SciPy 通过相同的 render() 函数实现对文本中词性的视觉理解:

displacy.render(nlp(str(sentences[0])), style='dep', jupyter = True, options = {'distance': 120})

我们可以在以下图表中看到此命令的输出:

图 9.10 – POS 模型在 Jupyter Notebook 中渲染的结果

图 9.10 – POS 模型在 Jupyter Notebook 中渲染的结果

这为我们理解并可视化句子结构提供了一个很好的方法。在完成一些基本分析后,让我们继续探索使用结构化数据的 NLP 应用。

处理结构化数据

现在我们已经探索了一些 NLP 的基础知识,让我们深入研究一些更复杂且在生物技术和生命科学领域常见的用例。当处理基于文本的数据时,与单个字符串相比,更常见的是处理更大的数据集。通常情况下,我们希望这些数据集涉及特定研究主题相关领域的科学数据。让我们继续学习如何使用 Python 检索科学数据。

搜索科学文章

要使用 Python 程序化检索科学出版物数据,我们可以利用来自 PubMedpymed 库(pubmed.ncbi.nlm.nih.gov/)。让我们继续构建一个示例数据集:

  1. 首先,让我们导入我们的库并实例化一个新的 PubMed 对象:

    from pymed import PubMed
    pubmed = PubMed()
    
  2. 接下来,我们需要定义并运行我们的查询。让我们继续搜索与单克隆抗体相关的一切,并检索 100 个结果:

    query = "monoclonal antibody"
    results = pubmed.query(query, max_results=100)
    
  3. 在找到结果后,我们可以遍历我们的结果以检索给定文章的所有可用字段:

    articleList = []
    for article in results:
        articleDict = article.toDict()
        articleList.append(articleDict)
    
  4. 最后,我们可以将我们的列表转换为 DataFrame 以便于使用:

    df = pd.DataFrame(articleList)
    df.head()
    

    以下为输出:

图 9.11 – 显示 PubMed 搜索结果的 DataFrame

图 9.11 – 显示 PubMed 搜索结果的 DataFrame

在完成最后一步后,我们有一个包含科学摘要及其相关元数据的完整数据集。在下一节中,我们将更深入地探索这些数据,并开发一些可视化来表示它。

探索我们的数据集

现在我们有一些数据可以处理了,让我们继续探索它。如果你还记得前面许多章节的内容,我们经常以各种方式探索我们的数值数据集。我们可以分组列,探索趋势,并找到相关性——这些任务在处理文本时可能无法完成。让我们实现一些 NLP 方法,以稍微不同的方式探索数据。

检查字符串长度

由于我们已经在 pandas DataFrame 中结构化我们的数据集,我们通常想要探索的第一个项目之一就是基于文本数据的字符串长度分布。在当前数据集中,包含文本的两个主要列是titleabstract – 让我们继续绘制长度分布图:

sns.displot(df.abstract.str.len(), bins=25)
sns.displot(df.title.str.len(), bins=25)

以下为输出:

图 9.12 – 摘要平均长度(左侧)和标题平均长度(右侧)的频率分布

图 9.12 – 摘要平均长度(左侧)和标题平均长度(右侧)的频率分布

在这里,我们可以看到大多数摘要的平均长度约为 1,500 个字符,而标题约为 100 个字符。由于标题可能包含文章的重要关键词或标识符,类似于摘要,因此将两者合并为一个单独的列以一起分析是明智的。我们可以简单地使用+运算符将它们合并:

df["text"] = df["title"] + " " + df["abstract"]
df[["title", "abstract", "text"]]

我们可以在以下屏幕截图中看到这个新列:

图 9.13 – 显示标题、摘要和文本列的样本 DataFrame

图 9.13 – 显示标题、摘要和文本列的样本 DataFrame

通过对每一列使用mean()函数,我们可以看到标题平均有 108 个字符,摘要有 1,277 个字符,而组合文本列有 1,388 个字符。

与其他数据集类似,我们可以使用value_counts()函数来快速了解最常见的单词:

df.text.str.split(expand=True).stack().value_counts()

立即,我们注意到我们的数据集中充满了停用词:

图 9.14 – 数据集中最频繁出现的单词样本

图 9.14 – 数据集中最频繁出现的单词样本

我们可以实施与之前相同的cleaner函数来删除这些停用词和任何其他不希望存在的值。请注意,DataFrame 中的某些单元格可能为空,这取决于查询方式和返回的结果。我们将在下一节中对此进行更详细的探讨。

清理文本数据

我们可以在函数顶部添加一个快速检查,通过检查值的类型来确保不会遇到错误:

from nltk.corpus import stopwords
STOP_WORDS = stopwords.words()
def cleaner(text):
    if type(text) == str:
        text = text.lower()
        text = re.sub("[^a-zA-Z]+", ' ', text)
        text_tokens = word_tokenize(text)
        tokens_without_sw = [word for word in text_tokens if not word in STOP_WORDS]
        filtered_sentence = (" ").join(tokens_without_sw)
        return filtered_sentence

我们可以在一个样本字符串上快速测试这个功能:

cleaner("Biotech in 2021 is a wonderful field to work and study in!")

该函数的输出可以在以下屏幕截图中看到:

图 9.15 – 清理函数的结果

图 9.15 – 清理函数的结果

函数运行正常后,我们可以将其应用于 DataFrame 中的text列,并创建一个新列,该列包含使用apply()函数清理后的文本,该函数允许我们对 DataFrame 的所有行迭代应用给定函数:

df["clean_text"] = df["text"].apply(lambda x: cleaner(x))

我们可以通过检查感兴趣的列来检查我们函数的性能:

df[["text", "clean_text"]].head()

我们可以在以下屏幕截图中看到这两个列:

图 9.16 – 显示原始和清理文本的 DataFrame

图 9.16 – 显示原始和清理文本的 DataFrame

如果我们继续检查clean_text列的value_counts(),就像我们之前做的那样,你会注意到停用词已被移除,并且现在更有用的关键词已填充在顶部。

创建词云

另一种流行且有用的方法,可以通过使用词云来快速了解给定基于文本数据集的内容。wordclouds库:

  1. 首先,我们需要导入函数,然后创建一个wordcloud对象,我们将指定一系列参数。我们可以调整图像的尺寸、颜色和数据,如下所示:

    from wordcloud import WordCloud, STOPWORDS
    plt.figure(figsize=(20,10))
    # Drop nans
    df2 = df[["clean_text"]].dropna()
    # Create word cloud
    wordcloud = WordCloud(width = 5000, 
                          height = 3000, 
                          random_state=1, 
                          background_color='white', 
                          colormap='Blues', 
                          collocations=False, 
                          stopwords = STOPWORDS).generate(' '.join(df2['clean_text']))
    
  2. 现在,我们可以使用imshow()函数从matplotlib库中渲染图像:

    plt.figure( figsize=(15,10) )
    plt.imshow(wordcloud)
    

    以下为输出结果:

图 9.17 – 表示数据集中单词频率的词云

图 9.17 – 表示数据集中单词频率的词云

在本节中,我们调查了几种快速分析基于文本数据的流行方法,作为在执行任何类型严格分析或模型开发过程之前的初步步骤。在下一节中,我们将使用这个数据集训练一个模型来研究主题。

教程 – 聚类和主题建模

与我们迄今为止看到的一些前例类似,我们的大部分数据要么可以在监督设置中分类,要么在无监督设置中聚类。在大多数情况下,基于文本的数据通常以原始和未标记的形式提供给我们,这意味着它是现实世界数据。

让我们看看一个例子,我们可以从无监督的角度理解我们的数据并对它进行标记。我们在这里的主要目标将是预处理我们的原始文本,将数据聚类成五个簇,然后确定每个簇的主要主题。如果你正在使用提供的代码和文档进行操作,请注意,你的结果可能会有所不同,因为数据集是动态的,其内容会随着新数据被填充到 PubMed 数据库中而变化。我敦促你根据你感兴趣的主题定制查询。考虑到这一点,让我们继续开始。

我们将首先使用pymed库查询一些数据,检索几百篇摘要和标题来工作:

def dataset_generator(query, num_results, ):
    results = pubmed.query(query, max_results=num_results)
    articleList = []
    for article in results:
        articleDict = article.toDict()
        articleList.append(articleDict)
    print(f"Found {len(articleList)} results for the query '{query}'.")
    return pd.DataFrame(articleList)

我们不是做一个单一的查询,而是做几个查询,并将结果合并到一个单独的 DataFrame 中:

df1 = dataset_generator("monoclonal antibodies", 600)
df2 = dataset_generator("machine learning", 600)
df3 = dataset_generator("covid-19", 600)
df4 = dataset_generator("particle physics", 600)
df = pd.concat([df1, df2, df3, df4])

观察数据,我们可以看到一些单元格有缺失(nan)值。鉴于我们的目标仅涉及基于文本的字段(标题和摘要),让我们将任何清理方法的范围限制在这些列上:

df = df[["title", "abstract"]]

由于我们关注的是每篇文章的整体内容,我们可以将标题和摘要合并到一个新的列中,称为text

df["text"] = df["title"] + " " + df["abstract"]
df = df.dropna()
print(df.shape)

观察数据集,我们可以看到我们有 560 行和 3 列。科学文章可以非常详细,包含许多停用词。鉴于我们的目标是检测由关键词表示的主题,让我们从我们的文本中移除任何标点符号、数值和停用词:

def cleaner(text):
    if type(text) == str:
        text = text.lower()
        text = re.sub("[^a-zA-Z]+", ' ', text)
        text_tokens = word_tokenize(text)
        tokens_without_sw = [word for word in text_tokens if not word in STOP_WORDS]
        filtered_sentence = (" ").join(tokens_without_sw)
        return filtered_sentence
df["text"] = df["text"].apply(lambda x: cleaner(x))

我们可以通过检查在实施脚本前后每篇文章的平均单词数来确保数据已被清理。在我们的例子中,我们可以看到我们开始时的平均单词数是 190,最终的平均单词数是 123。

数据现在已清理,我们可以继续提取我们的特征。我们将使用一种相对简单且常见的方法,称为TFIDF – 一种衡量单词原创性的度量,其中每个单词都与它在文章中出现的次数进行比较,相对于该单词在文章中出现的次数。我们可以将 TFIDF 视为两个单独的项目 – 词频TF)和逆文档频率IDF) – 我们可以如下表示:

在前面的方程中,t是术语或关键词,而d是文档或 – 在我们的情况下 – 文章。这里的主要思想是捕捉那些可以作为主要主题描述的重要关键词,但忽略那些几乎在每篇文章中都出现的关键词。我们将首先从sklearn导入TfidfVectorizer

from sklearn.feature_extraction.text import TfidfVectorizer

接下来,我们将通过拟合我们的数据集并转换值将基于文本的数据转换为数值特征:

vectors = TfidfVectorizer(stop_words="english", max_features=5500)
vectors.fit(df.text.values)
features = vectors.transform(df.text.values)

我们可以通过检查features变量的形状来确认我们确实有 560 行,正如我们在应用 TFIDF 之前所做的那样,以及与之相对应的 5500 个特征列。接下来,我们可以继续使用我们迄今为止探索的许多聚类方法之一来聚类我们的文档。

让我们实现MiniBatchKMeans并指定我们想要检索的聚类数量为4

from sklearn.cluster import MiniBatchKMeans
cls = MiniBatchKMeans(n_clusters=4)
cls.fit(features)

当处理大型数据集时,特别是在生产环境中,通常建议避免使用 pandas DataFrame,因为根据您需要实现的过程,有更高效的方法可用。鉴于我们只处理 560 行数据,并且我们的目标是聚类数据和检索主题,我们再次将使用 DataFrame 来管理我们的数据。让我们继续将我们的预测聚类添加到我们的 DataFrame 中:

df["cluster"] = cls.predict(features)
df[["text", "cluster"]].head()

我们可以在下面的屏幕截图中看到这个命令的输出:

图 9.18 – 显示清洗后的文本及其相关聚类的 DataFrame

图 9.18 – 显示清洗后的文本及其相关聚类的 DataFrame

在数据聚类后,让我们将此数据绘制在二维散点图上。鉴于我们有数千个特征,我们可以使用PCA算法将这些特征减少到仅用于可视化的两个特征:

from sklearn.decomposition import PCA
pca = PCA(n_components=2)
pca_features_2d = pca.fit_transform(features.toarray())
pca_features_2d_centers = pca.transform(cls.cluster_centers_)

让我们继续将这些两个主成分添加到我们的 DataFrame 中:

df["pc1"], df["pc2"] = pca_features_2d[:,0], pca_features_2d[:,1]
df[["text", "cluster", "pc1", "pc2"]].head()

我们可以在下面的屏幕截图中看到这个命令的输出:

图 9.19 – 显示文本、簇和主成分的 DataFrame

图 9.19 – 显示文本、簇和主成分的 DataFrame

在这里,我们可以看到每一行文本现在都有一个簇,以及一组以主成分形式表示的坐标。接下来,我们将绘制我们的数据,并按簇着色:

plt.figure(figsize=(15,8))
new_cmap = matplotlib.colors.LinearSegmentedColormap.from_list("mycmap", colors)
plt.scatter(df["pc1"], df["pc2"], c=df["cluster"], cmap=new_cmap)
plt.scatter(pca_features_2d_centers[:, 0], pca_features_2d_centers[:,1], marker='*', s=500, c='r')
plt.xlabel("PC1", fontsize=20)
plt.ylabel("PC2", fontsize=20)

执行此代码后,我们将得到以下输出:

图 9.20 – 按簇着色的主成分散点图,星号代表簇中心

图 9.20 – 按簇着色的主成分散点图,星号代表簇中心

在这里,我们可以看到簇之间似乎有一些适当的分离!最左边的两个簇似乎在其分布中具有较小的方差,而其他两个则分布得更广。鉴于我们将相当数量的特征降低到只有两个主成分,它们之间一定程度的重叠是合理的,尤其是考虑到所有文章都是科学性的。

现在,让我们计算一下在我们数据集中发现的一些最突出的主题:

  1. 首先,我们将开始实现 TFIDF:

    from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
    vectors = TfidfVectorizer(max_features=5500, stop_words="english")
    nmf_features = vectors.fit_transform(df.text)
    
  2. 接下来,我们将降低数据的维度。然而,这次,我们将使用非负矩阵分解NMF)来降低我们的数据,而不是 PCA。我们需要指定我们感兴趣的主题数量:

    from sklearn.decomposition import NMF
    n_topics = 10
    cls = NMF(n_components=n_topics)
    cls.fit(features)
    
  3. 现在,我们可以指定每个主题要检索的关键词数量。之后,我们将遍历组件并检索感兴趣的关键词:

    num_topic_words = 3
    feature_names = vectors.get_feature_names()
    for i, j in enumerate(cls.components_):
        print(i, end=' ')
        for k in j.argsort()[-1:-num_topic_words-1:-1]:
            print(feature_names[k], end=' ')
    

    执行此循环后,我们将检索以下输出:

图 9.21 – 数据集的前 10 个主题,每个主题由三个关键词表示

图 9.21 – 数据集的前 10 个主题,每个主题由三个关键词表示

我们可以使用这些主题建模方法从数据集中提取见解和趋势,使用户能够进行高级解释,而无需深入整个数据集。在整个教程中,我们考察了聚类和主题建模的经典方法之一:使用TFIDFNMF。然而,存在许多其他使用语言模型的方法,例如BERTBioBERT,以及GensimLDA等库。如果您对这个领域感兴趣,我强烈建议您探索这些库以获取更多信息。

通常,您可能不会以可用的格式拥有您的数据。在本教程中,我们的数据集结构在 DataFrame 中,准备好使用以进行切片和切块。然而,在许多情况下,我们感兴趣的数据可能是未结构化的,例如在 PDF 中。我们将在下一节中探讨如何处理这些情况。

处理未结构化数据

在上一节中,我们探讨了在处理基于文本的数据时进行的一些最常见的任务和流程。通常情况下,你会发现你处理的数据通常不是结构化的,或者可能不是数字化的。例如,一家决定将所有打印文档转换为数字状态的公司。或者可能是一家维护大量文档的公司,这些文档都没有结构化或组织化。对于这类任务,我们可以依赖几个 AWS 产品来帮助我们。在接下来的几节中,我们将探讨两个最有用的 NLP 工具。

使用 AWS Textract 进行 OCR

在我看来,AWS中可用最有用的工具之一是一个名为AWS Textract光学字符识别OCR)工具。这个工具背后的主要思想是使用 Textract 内部实现的预构建机器学习模型,使用户能够从图像或静态 PDF 文档中提取文本、表格和其他有用的项目。

例如,用户可以将图像或扫描的 PDF 文档上传到 Textract,这些文档在其他情况下是不可搜索的,并从中提取所有基于文本的内容,如下面的图所示:

图 9.22 – 一个示意图,展示了将原始 PDF 文件结构化为有组织的数字文本

图 9.22 – 一个示意图,展示了将原始 PDF 文件结构化为有组织的数字文本

除了提取文本外,用户还可以提取类似以下在打印和手写形式中找到的关键值对:

图 9.23 – 一个示意图,展示了将手写数据结构化为有组织的表格

图 9.23 – 一个示意图,展示了将手写数据结构化为有组织的表格

要使用boto3。我们可以使用pip安装 Boto3。当在 SageMaker 中使用 Boto3 时,您不需要使用任何访问密钥来利用该服务。然而,如果您正在使用 Jupyter Notebook 的本地实现,您将需要能够使用访问密钥进行身份验证。访问密钥可以轻松地在几个简单的步骤中创建:

  1. 导航到 AWS 控制台,并从服务菜单中选择IAM

  2. 在左侧的访问管理选项卡下,点击用户,然后添加用户

  3. 继续为您的用户命名,例如ml-biotech-user,并启用程序访问选项:图 9.24 – 设置 AWS IAM 角色的用户名

    图 9.24 – 设置 AWS IAM 角色的策略

  4. 接下来,选择顶部的直接附加现有策略选项,并添加感兴趣的策略。继续添加 Textract、Comprehend 和 S3,因为我们将需要这三个角色:图 9.25 – 设置 AWS IAM 角色的策略

    图 9.25 – 设置 AWS IAM 角色的策略

  5. 在为您的用户添加一些描述性标签之后,您将获得两项内容:您的访问密钥 IDAWS 密钥访问密钥。请确保将这两项内容复制到安全的地方。出于安全考虑,在离开此页面后,您将无法从 AWS 中检索它们。

现在我们有了访问密钥,让我们继续在感兴趣的文档上实现 Textract。我们可以通过几个步骤来完成这项工作。

  1. 首先,我们需要将我们的数据上传到我们的S3 存储桶。我们可以使用本书前面使用过的同一个 S3 存储桶。我们需要指定我们的密钥,然后使用 Boto3 客户端连接到 AWS:

    AWS_ACCESS_KEY_ID = "add-access-key-here"
    AWS_SECRET_ACCESS_KEY = "add-secret-access-key-here"
    AWS_REGION = "us-east-2"
    s3_client = boto3.client('s3', aws_access_key_id=AWS_ACCESS_KEY_ID, aws_secret_access_key=AWS_SECRET_ACCESS_KEY, region_name=AWS_REGION)
    
  2. 连接设置好之后,我们可以继续上传一个示例 PDF 文件。请注意,您可以提交 PDF 文件以及图像文件(PNG)。让我们使用upload_fileobj()函数上传我们的 PDF 文件:

    with open("Monoclonal Production Article.pdf", "rb") as f:
        s3_client.upload_fileobj(f, "biotech-machine-learning", "pdfs/Monoclonal Production Article.pdf")
    
  3. 现在我们已经上传了 PDF,我们可以使用 Textract。首先,我们需要使用 Boto3 客户端进行连接。请注意,我们将所需的资源从's3'更改为'textract',因为我们现在使用的是不同的服务:

    textract_client = boto3.client('textract', aws_access_key_id=AWS_ACCESS_KEY_ID, aws_secret_access_key=AWS_SECRET_ACCESS_KEY, region_name=AWS_REGION)
    
  4. 接下来,我们可以使用start_document_text_detection()方法将文件发送到 Textract,其中我们指定存储桶的名称和文档的名称:

    response = textract_client.start_document_text_detection(
                       DocumentLocation={'S3Object': {'Bucket': "biotech-machine-learning", 'Name': "pdfs/Monoclonal Production Article.pdf"} })
    
  5. 我们可以通过检查响应变量中的状态码来确认任务已成功启动。经过一段时间(取决于作业的持续时间),我们通过指定JobId来检索结果:

    results = textract_client.get_document_text_detection(JobId=response["JobId"])
    

    立即,我们会注意到results变量只是一个可以解析和遍历的大 JSON。请注意,JSON 的结构相当复杂且详细。

  6. 最后,我们可以通过遍历Blocks并收集LINE类型块的文本来收集所有文本:

    documentText = ""
    for item in results["Blocks"]:
        if item["BlockType"] == "LINE":
            documentText = documentText + item["Text"]
    

如果您打印documentText变量,您将看到从该文档成功收集到的所有文本!Textract 可以是一个非常有用的工具,可以将文档从非结构化和不可搜索的状态转换为更结构化和可搜索的状态。通常,大多数基于文本的数据将以非结构化格式存在,您会发现 Textract 是这些类型应用中最有用的资源之一。Textract 通常与其他 AWS 资源结合使用,以最大化工具的效用,例如DynamoDB用于存储或Comprehend用于分析。我们将在下一节中探讨 Comprehend。

使用 AWS Comprehend 进行实体识别

在本章前面,我们使用 SciPy 库实现了一个 NER 模型,用于检测给定文本部分的实体。现在,让我们探索一个更强大的 NER 实现,称为AWS Comprehend。Comprehend 是一种 NLP 服务,旨在发现非结构化文本数据中的见解,使用户能够提取关键短语、计算情感、识别实体等等。让我们继续探索这个工具。

与其他boto3客户端类似:

comprehend_client = boto3.client('comprehend', aws_access_key_id=AWS_ACCESS_KEY_ID, aws_secret_access_key=AWS_SECRET_ACCESS_KEY, region_name=AWS_REGION)

接下来,我们可以继续使用detect_entities()函数来识别文本中的实体。我们可以使用之前使用 Textract 生成的documentText字符串:

response = comprehend_client.detect_entities(
    Text=documentText[:5000],
    LanguageCode='en',
)
print(response["Entities"])

打印出响应后,我们将看到检测到我们文本块中的每个实体的结果。此外,我们还可以将结果组织到 DataFrame 中:

pd.DataFrame(response["Entities"]).sort_values(by='Score', ascending=False).head()

按照分数排序值后,我们可以看到我们的结果以结构化的方式列出:

图 9.26 – 一个示例 DataFrame,显示 AWS Comprehend 实体 API 的结果

图 9.26 – 一个示例 DataFrame,显示 AWS Comprehend 实体 API 的结果

除了实体之外,Comprehend 还可以检测文本中的关键短语:

response = comprehend_client.detect_key_phrases(
    Text=documentText[:5000],
    LanguageCode='en',
)
response["KeyPhrases"][0]

在打印列表中的第一个项目时,我们可以看到分数、短语和在字符串中的位置:

图 9.27 – AWS Comprehend 关键短语 API 的结果

图 9.27 – AWS Comprehend 关键短语 API 的结果

此外,我们还可以使用detect_sentiment()函数来检测情感:

response = comprehend_client.detect_sentiment(
    Text=documentText[:5000],
    LanguageCode='en',
)
print(response)

我们可以打印出响应变量以获取字符串的结果。我们可以看到,情感被标记为中性,这对于一般不带积极或消极语调的科学数据声明是有意义的:

图 9.28 – AWS Comprehend 情感 API 的结果

图 9.28 – AWS Comprehend 情感 API 的结果

最后,Comprehend 还可以使用detect_dominant_language()函数检测文本中的主要语言:

response = comprehend_client.detect_dominant_language(
    Text=documentText[:5000],
)
response

在这里,我们可以看到,打印出响应后,我们可以感受到语言,以及模型关联的分数或概率:

图 9.29 – AWS Comprehend 语言检测 API 的结果

图 9.29 – AWS Comprehend 语言检测 API 的结果

AWS Textract 和 AWS Comprehend 是目前最顶尖的 NLP 工具之一,在结构化和分析大量非结构化文本文档方面发挥了关键作用。今天的大多数基于 NLP 的应用程序通常至少使用这些技术中的一种,如果不是两种。有关 Textract 和 Comprehend 的更多信息,我强烈建议您访问 AWS 网站(aws.amazon.com/)。

到目前为止,我们已经学习了如何分析和转换基于文本的数据,尤其是在将数据从非结构化状态转换为更结构化状态时。现在,文档已经更加组织化,下一步是能够以某种方式使用它们,例如通过搜索引擎。在下一节中,我们将学习如何使用transformers创建语义搜索引擎。

教程 – 使用 transformers 开发科学数据搜索引擎

到目前为止,我们是从逐词的角度来看待文本的,也就是说,我们保持文本原样,无需以任何方式转换或嵌入它。在某些情况下,将单词转换为数值或嵌入(embeddings)可以打开许多新的大门,解锁许多新的可能性,尤其是在深度学习方面。在这个教程中,我们的主要目标将是开发一个搜索引擎来查找和检索科学数据。我们将通过实现一个重要的、有用的深度学习自然语言处理(NLP)架构,即转换器(transformer)来实现这一点。这里的优点是,我们将设计一个强大的语义搜索引擎,因为我们现在可以搜索想法或语义意义,而不仅仅是关键词。

我们可以将转换器(transformers)视为一种深度学习模型,它使用称为自注意力(self-attention)的机制来解决基于序列的任务。我们可以将自注意力视为一种帮助将句子或嵌入中的不同部分联系起来以创建表示的方法。简单来说,模型试图将句子视为想法,而不是单个单词的集合。

在我们开始使用转换器之前,让我们更多地谈谈嵌入(embeddings)的概念。我们可以将嵌入视为表示一个项目(在我们的情况下是一个单词或句子)的低维数值或连续数字的向量。我们通常将单词和句子转换为嵌入,以便模型在处理大型数据集时更容易执行机器学习任务。在自然语言处理(NLP)和神经网络的环境中,使用嵌入有三个主要原因:

  • 为了减少大量文本数据的维度(dimensionality

  • 为了计算两个不同文本之间的相似度(similarity

  • 为了可视化(visualize)文本部分之间的关系

现在我们对嵌入及其在 NLP 中的作用有了更好的理解,让我们继续使用一个真实的科学搜索引擎的实例来开始。我们将首先导入我们将需要的几个库:

import scipy
import torch
import pandas as pd
from sentence_transformers import SentenceTransformer, util

要创建我们的嵌入(embeddings),我们需要一个模型。我们有选择创建一个针对我们的数据集定制的模型。这里的优点是,如果模型是在我们领域的文本上训练的,那么我们的结果可能会得到改善。或者,我们可以使用从SentenceTransformer网站(www.sbert.net/)可用的其他预训练模型。让我们下载这些预训练模型之一:

model = SentenceTransformer('msmarco-distilbert-base-v4')

接下来,我们可以创建一个测试数据库,并用几句话填充它:

database = df["abstract"].values 

接下来,我们可以调用encode()函数将我们的字符串列表转换为嵌入列表:

database_embeddings = model.encode(database)

如果我们使用len()函数检查数据库的长度和database_embeddings的长度,我们会发现它们包含相同数量的元素,因为应该为每段文本有一个嵌入。如果我们打印嵌入数据库的第一个元素的内容,我们会发现内容现在只是一个向量的列表:

图 9.30 – 一个嵌入文本的视图

图 9.30 – 一个嵌入文本的视图

现在我们已经将我们的文档嵌入,用户可能会想要搜索或查询特定的短语。我们可以像对待其他人一样将用户的查询编码,但将那个值分配给一个新的变量,我们将称之为query_embedding

query = "One of the best discoveries were monoclonal antibodies"
query_embedding = model.encode(query)

在查询和句子嵌入之后,我们可以计算项目之间的距离。这里的思路是,与用户查询更相似的文档将具有较短的距离,而那些不太相似的文档将具有较长的距离。请注意,我们在这里使用余弦作为距离和相似度的度量,因此,我们也可以使用其他方法,例如euclidean距离:

import scipy
cos_scores = util.pytorch_cos_sim(query_embedding, 
                             database_embeddings)[0]

让我们继续准备一个单一的runSearch函数,该函数包含查询、编码器以及显示我们结果的方法。这个过程从几个打印语句开始,然后将新的查询编码到一个名为query_embedding的变量中。然后计算距离,并按距离对结果进行排序。最后,遍历结果并打印每个的分数、标题和摘要:

def askQuestion(query, top_k):
    print(f"#########################################")
    print(f"#### {query} ####")
    print(f"#########################################")
    query_embedding = model.encode(query, convert_to_tensor=True)
    cos_scores = util.pytorch_cos_sim(query_embedding, 
                 database_embeddings)[0]
    top_results = torch.topk(cos_scores, k=top_k)

    for score, idx in zip(top_results[0], top_results[1]):
        print("#### Score: {:.4f}".format(score))
        print("#### Title: ", df.loc[float(idx)].title)
        print("#### Abstract: ", df.loc[float(idx)].abstract)
        print("#################################")

现在我们已经准备好了我们的函数,我们可以用我们的感兴趣查询来调用它:

query = ' What is known about the removal of harmful cyanobacteria?
askQuestion(query, 5)        

调用函数后,我们会检索几个类似打印的结果。我们可以在下面的屏幕截图中的一个结果中看到,显示了文章的scoretitleabstract属性:

图 9.31 – 科学文本语义搜索模型的输出结果

图 9.31 – 科学文本语义搜索模型的输出结果

通过这样,我们已经成功开发了一个能够搜索科学文献的语义搜索模型。请注意,查询本身并不是与模型返回的顶部结果直接字符串匹配。再次强调,这里的想法不是匹配关键词,而是计算嵌入之间的距离,这代表了相似性。

摘要

在本章中,我们尝试冒险地涵盖广泛的 NLP 主题。我们使用 NLTK 和 spaCy 库探索了一系列入门主题,如命名实体识别(NER)、分词和词性。然后,我们通过结构化数据集的视角来探索 NLP,其中我们利用pymed库作为科学文献的来源,并在预处理步骤中分析和清理数据。接下来,我们开发了一个词云来可视化给定数据集中单词的频率。最后,我们开发了一个聚类模型来分组我们的摘要,并开发了一个主题建模模型来识别突出主题。

我们随后通过非结构化数据的视角来探索自然语言处理(NLP),在这个过程中,我们研究了两种常见的 AWS NLP 产品。我们使用 Textract 将 PDF 和图像转换为可搜索和结构化的文本,并使用 Comprehend 进行分析和提供见解。最后,我们学习了如何使用深度学习转换器开发语义搜索引擎,以找到相关信息。

本章特别独特之处在于,我们了解到文本是一种基于序列的数据类型,这使得它的用途和应用与我们在之前工作中处理的其他许多数据集截然不同。随着全球各地的公司开始将传统文档迁移到数字空间,搜索文档和识别见解的能力将具有极大的价值。在下一章中,我们将考察另一种称为时间序列的基于序列的数据类型。

第十章:探索时间序列分析

在上一章中,我们讨论了使用深度学习及其在处理自然语言这种非结构化数据时的强大适用性——一种序列数据。我们现在将关注的另一种序列数据是时间序列数据。我们可以将时间序列数据视为标准数据集,但包含基于时间的特征,从而在开发预测模型时开启了一组新的可能性。

时间序列数据中最常见的一个应用是一个称为时间序列分析的过程。我们可以将时间序列分析定义为数据探索预测的一个领域,其中数据集是按照特定的时间间隔时间戳进行排序或索引的。我们在生物技术和生命科学行业中每天都会遇到许多时间序列数据的例子。一些更侧重于实验室的领域包括基因表达和色谱法,以及非实验室领域,如需求预测和股价分析。

在本章中,我们将探讨几个不同领域,以更好地理解时间序列数据的分析,以及开发一个能够消费这些数据并构建一个稳健、预测性模型的方法。

随着我们探索这些领域,我们将涵盖以下主题:

  • 理解时间序列数据

  • 探索时间序列数据集的组成部分

  • 教程 - 使用 Prophet 和 LSTM 预测产品需求

考虑到这一点,让我们开始吧!

理解时间序列数据

当涉及到使用时间序列数据时,有无数种方式来可视化和展示数据,以有效地传达一个想法或观点。在我们迄今为止使用的大多数数据中,我们处理了特征和标签,其中一组特征通常对应于感兴趣的标签。当涉及到时间序列数据时,我们往往放弃类别或标签的概念,更多地关注数据中的趋势。时间序列数据最常见的一个应用是需求预测的概念。正如其名称所暗示的,需求预测包括许多方法和工具,可以帮助提前预测特定商品或服务的需求。在本节中,我们将通过一个关于特定生物技术产品需求预测的数据集,了解时间序列分析的许多方面。

将时间序列数据视为结构化数据集

目前市场上有很多不同的生物技术产品,从农业转基因作物到单克隆抗体疗法应有尽有。在本节中,我们将通过使用dataset_demand-forecasting_ts.csv数据集来调查一种人用治疗品的销售数据,该数据集属于一家小型生物技术初创公司:

  1. 考虑到这一点,让我们继续深入数据。我们将首先导入感兴趣的库,导入CSV文件,并查看数据的前几行:

    import pandas as pd
    df = pd.read_csv(“dataset_demand-forecasting_ts.csv”)
    df.head()
    

    这将导致以下输出:

    图 10.1 – 预测数据集的前几行

    图 10.1 – 预测数据集的前几行

    与我们过去使用过的许多其他数据集相比,这个数据集在意义上似乎要简单得多,因为我们只处理两列:日期和任何给定日期的销售额数量。我们还可以看到销售额是按天聚合的,从2014-01-01开始。如果我们使用tail()函数检查数据集的末尾,我们将看到数据集在2020-12-23结束——基本上为我们提供了 6 年的销售额数据来工作。

  2. 我们可以使用Plotly库来可视化时间序列数据:

    import plotly.express as px
    import plotly.graph_objects as go
    fig = px.line(df, x=”Date”, y=”Sales”, title=’Single Product Demand’, width=800, height=400)
    fig.update_traces(line_color=’#4169E1’)
    fig.show()
    

    执行fig.show()函数后,我们将收到以下输出:

    图 10.2 – 销售数据集的时间序列图

    图 10.2 – 销售数据集的时间序列图

    我们可以立即对数据集做出一些初步观察:

    • 数据中存在大量的噪声和变异性。

    • 销售额随时间逐渐增加(我应该投资它们!)。

    • 销售额似乎存在季节性因素,销售额在 12 月达到峰值。

    为了更深入地探索这些想法并深入数据,我们需要分解时间序列方面。使用日期列,我们可以将数据集分解为年、月和日,以更好地了解数据的重复或季节性特征。

    重要提示

    数据集中的季节性指的是与该年份时间相关的季节性特征。例如,与流感疫苗相关的数据集通常在秋季相对于春季或夏季显示出增加的比率,为冬季(流感季节)做准备。

  3. 首先,我们需要使用to_datetime()函数将字符串转换为日期类型:

    def get_features(dataframe):
        dataframe[“sales”] = dataframe[“sales”]
        dataframe[“Date”] = pd.to_datetime(dataframe[‘Date’])
        dataframe[‘year’] = dataframe.Date.dt.year
        dataframe[‘month’] = dataframe.Date.dt.month
        dataframe[‘day’] = dataframe.Date.dt.day
        dataframe[‘dayofyear’] = dataframe.Date.dt.dayofyear
        dataframe[‘dayofweek’] = dataframe.Date.dt.dayofweek
        dataframe[‘weekofyear’] = dataframe.Date.dt.weekofyear
        return dataframe 
    df = get_features(df)
    df.head()
    

    执行此命令后,我们将收到以下 DataFrame 作为输出:

    图 10.3 – 显示新特征的销售额数据集的前五行

    图 10.3 – 显示新特征的销售额数据集的前五行

  4. 在这里,我们可以看到我们能够分解时间序列方面,并产生比最初开始时更多的数据。让我们继续按绘制数据:

    plt.figure(figsize=(10,5))
    ax = sns.boxplot(x=’year’, y=’sales’, data=df)
    ax.set_xlabel(‘Year’, fontsize = 16)
    ax.set_ylabel(‘Sales’, fontsize = 16)
    

    在绘制我们的数据后,我们将收到以下箱线图,它显示了每年的销售额。从统计学的角度来看,我们可以证实我们的初步观察,即销售额每年都在逐渐增加:

    图 10.4 – 显示每年销售额增长的箱线图

    图 10.4 – 显示每年销售额增长的箱线图

  5. 让我们继续绘制每个给定月份的相同图表:

    plt.figure(figsize=(10,5))
    ax = sns.boxplot(x=’month’, y=’sales’, data=df)
    ax.set_xlabel(‘Month’, fontsize = 16)
    ax.set_ylabel(‘Sales’, fontsize = 16)
    

    当我们将x-轴从年份改为月份时,我们将收到以下图表,证实我们的观察,即销售数据倾向于在 1 月(1)/12 月(12)的时间框架内达到峰值:

    图 10.5 – 显示每月季节性销售的箱线图

    图 10.5 – 显示每月季节性销售的箱线图

    之前,我们提到数据集中包含大量的噪声,即在数据中存在大量的波动。我们可以通过取滚动平均移动平均)来解决这个问题并使数据归一化,这是一种用于帮助我们通过创建一系列平均值来分析数据点的计算方法。

  6. 我们可以直接使用rolling()函数在我们的 DataFrame 中实现这一点:

    df[“Rolling_20”] = df[“sales”].rolling(window=20).mean()
    df[“Rolling_100”] = df[“sales”].rolling(window=100).mean()
    
  7. 注意,在前面的代码中,我们使用了两个示例来通过使用 20 和 100 的窗口值来展示滚动平均的概念。使用Plotly Go,我们可以将原始原始数据和两个滚动平均值绘制在单个图表上:

    fig = go.Figure()
    fig.add_trace(go.Scatter(x=df[“Date”], y=df[“sales”], mode=’lines’, name=’Raw Data’, line=dict(color=”#bec2ed”)))
    fig.add_trace(go.Scatter(x=df[“Date”], y=df[“Rolling_20”], mode=’lines’, name=’Rolling 20’, line=dict(color=”#858eed”,dash=”dash”)))
    fig.add_trace(go.Scatter(x=df[“Date”], y=df[“Rolling_100”], mode=’lines’, name=’Rolling 100’, line=dict(color=”#d99543”)))
    fig.update_layout(width=800, height=500)
    

    执行此代码后,我们将收到以下输出:

图 10.6 – 显示每月季节性销售的箱线图

图 10.6 – 显示销售数据滚动平均的箱线图

注意,原始数据集以淡色在背景中绘制,由表示 20 个window值的虚线曲线覆盖,以及表示 100 个window值的实线曲线在前景中。使用滚动平均值在尝试可视化和理解数据以及构建预测模型时可能很有用,正如我们将在本章后面看到的那样。

重要提示

滚动平均移动平均)是一种计算方法,通过在整个特定范围内取移动平均值来平滑噪声数据集。这个范围通常被称为窗口,通常是最后x个数据点。

时间序列数据与我们在本书中迄今为止探索的大多数数据集非常不同。与其他数据集不同,时间序列数据通常被认为与几个组成部分一致,所有这些我们将在下一节中探讨。

探索时间序列数据集的组成部分

在本节中,我们将探讨通常被认为是时间序列数据集组成部分的四个主要项目,并将它们可视化。考虑到这一点,让我们开始吧!

时间序列数据集通常由四个主要组成部分组成:水平长期趋势、季节性不规则噪声,我们可以将这些分解成一种称为时间序列分解的方法。分解背后的主要目的是通过更抽象地思考数据来更好地了解数据集。我们可以将时间序列组成部分视为加法或乘法:

图 10.1 – 公式

图 10.6 – 显示销售数据滚动平均的箱线图

我们可以这样定义每个组成部分:

  • 水平:数据集随时间变化的平均值

  • 长期趋势:数据的一般方向,显示增加或减少

  • 季节性趋势:短期重复性特征(天数、周数、月数)

  • 不规则趋势:数据中的随机波动噪声

我们可以使用statsmodels库结合我们的数据集,通过以下简单步骤来更深入地探索和可视化这些化合物:

  1. 首先,我们需要通过仅保留销售列、删除任何缺失值并将日期列设置为 DataFrame 的索引来调整我们的数据集:

    dftmp = pd.DataFrame({‘data’: df.Rolling_100.values},
                          index=df.Date)
    dftmp = dftmp.dropna()
    dftmp.head()
    
  2. 我们可以检查前几行以确认日期现在是我们的索引:图 10.7 – 调整后的数据集的前几行

    图 10.7 – 调整后的数据集的前几行

  3. 接下来,我们将从statsmodels库中导入seasonal_decompose函数并将其应用于我们的dataframe

    from statsmodels.tsa.seasonal import seasonal_decompose
    result = seasonal_decompose(dftmp, model=’multiplicative’, period=365)
    
  4. 最后,我们可以使用内置的plot()函数绘制结果并查看结果:

    result.plot()
    pyplot.show()
    

    使用show()函数将给出以下输出:

图 10.8 – 季节分解函数的结果

图 10.8 – 季节分解函数的结果

在这里,我们可以看到本节中提到的四个组成部分。在第一个图表中,我们可以看到我们在上一节中计算的自回归移动平均。然后是长期趋势,它显示了整个数据集的稳步增长。然后我们可以看到数据集背后的季节性,证实了销售在 12 月和 1 月的时间框架内倾向于增加。最后,我们可以看到数据集中的残差数据或噪声。我们可以将这种噪声定义为未对其他主要类别做出贡献的项目。

分解数据集通常是为了更好地理解数据及其一些主要特征,这通常会改变你对数据集以及任何可以开发的给定预测模型的看法。我们将在下一节中学习如何开发两种常见的预测模型。

教程 – 使用 Prophet 和 LSTM 预测需求

在本教程中,我们将使用上一节中的销售数据集来开发两个稳健的需求预测模型。我们的主要目标将是使用销售数据来预测未来的需求。需求预测通常用于预测在给定日期或地点要销售的单元数量。全球各地的公司,尤其是那些处理对温度敏感或对时间敏感的药物的公司,依赖于这些模型来优化其供应链并确保满足患者的需求。

首先,我们将探索 Facebook 著名的Prophet库,然后开发一个定制的长短期记忆LSTM)深度学习模型。考虑到这一点,让我们继续研究如何使用 Prophet 模型。

使用 Prophet 进行时间序列建模

Prophet 是一个在 2017 年首次发布时就获得了数据科学界广泛关注的模型。作为一个既在R又在Python中可用的开源库,该模型很快被采用并广泛用作时间序列数据的主要预测模型之一。这个模型背后的最大好处之一也是其后果之一——它的高度抽象性质,使用户只需几行代码就能做出预测。这种有限的变异性可以是一种快速预测的好方法,但可能会阻碍模型开发过程,具体取决于手头的数据集。

在接下来的几页中,我们将开发一个使用我们的数据进行拟合的 Prophet 模型,以预测未来的销售并通过对实际销售数据进行比较来验证结果。让我们开始吧:

  1. 首先,让我们使用rolling()函数来获取数据集的滚动平均值。然后,我们可以将这个值叠加到原始值上:

    df[“AverageSales”] = df[“Sales”].rolling(window=20).mean()
    fig = go.Figure()
    fig.add_trace(go.Scatter(x=df[“Date”], y=df[“Sales”], mode=’lines’, name=’Raw Data’, line=dict(color=”#bec2ed”)))
    fig.add_trace(go.Scatter(x=df[“Date”], y=df[“AverageSales”], mode=’lines’, name=’Rolling 20’, line=dict(color=”#3d43f5”)))
    fig.update_layout(width=800, height=500)
    
  2. 这将产生以下输出:图 10.9 – 与原始数据集相关的滚动平均值

    图 10.9 – 与原始数据集相关的滚动平均值

    在这里,我们可以看到数据集现在要少得多噪声,更容易处理。我们可以使用Prophet库和我们的数据集通过以下四个基本步骤来创建预测:

  3. 首先,我们需要将 DataFrame 重塑以与Prophet库集成。该库期望 DataFrame 包含两列——dsy,其中ds是日期戳,y是我们正在处理的价值。我们可以使用以下代码将这个 DataFrame 重塑为一个新的 DataFrame:

    df2 = df[[“Date”, “AverageSales”]]
    df2 = df2.dropna()
    df2.columns = [“ds”, “y”]
    
  4. sklearn库的实现类似,我们可以创建一个 Prophet 模型的实例并将其fit到我们的数据集上:

    m = Prophet()
    m.fit(df2)
    
  5. 接下来,我们可以调用make_future_dataframe()函数和感兴趣的周期数。这将产生一个包含日期列的 DataFrame:

    future = m.make_future_dataframe(periods=365*2)
    
  6. 最后,我们可以使用predict()函数,将未来变量作为输入参数来做出预测。这将返回与数据集相关的多个统计值:

    forecast = m.predict(future)
    forecast[[‘ds’, ‘yhat’, ‘yhat_lower’, ‘yhat_upper’]].tail()
    

    我们可以限制数据集的范围到几个列,并检索以下 DataFrame:

    图 10.10 – Prophet 预测函数的输出

    图 10.10 – Prophet 预测函数的输出

  7. 现在,我们可以使用Prophet实例的内置plot()函数来可视化我们的预测:

    fig1 = m.plot(forecast)
    

    这将产生以下输出,显示了原始的原始数据集、未来的预测以及一些上限和下限:

    图 10.11 – 预测数据的图形表示

    图 10.11 – 预测数据的图形表示

  8. 或者,我们可以通过使用数据的一部分来训练模型来测试模型的能力——例如,直到 2018 年为止的所有数据。然后,我们可以使用预测模型来预测剩余的时间,并将输出与实际数据进行比较。完成此操作后,我们将收到以下输出:

图 10.12 – 训练和测试数据的图形表示

图 10.12 – 训练和测试数据的图形表示

在这里,我们可以看到代表预测销售的虚线与实际值非常接近。我们还可以看到,模型没有预测曲线的极端值,因此可能需要额外的调整以达到更现实的预测。然而,Prophet 的高级特性可能在这方面有所限制。

从这里,我们可以看到准备数据和实现模型非常快,我们只用几行代码就完成了这项工作。在下一节中,我们将学习如何使用Keras开发LSTM

使用 LSTM 进行时间序列建模

LSTM模型首次在 1997 年获得流行,然后在计算能力增加的近年再次流行。如您所回忆的,LSTMs 是一种可以记住和忘记数据集中模式的循环神经网络RNN)。这种模型的主要优点是其中到低级的特性,即相对于Prophet,需要更多的代码来实现。用户在模型开发过程中获得了极大的控制权,使他们能够将模型定制为几乎任何类型的数据库和任何类型的用例。考虑到这一点,让我们开始:

  1. 使用相同的数据库,我们可以继续创建一个使用20个窗口的滚动平均值,以减少数据集中的噪声。然后,我们可以移除由此产生的缺失值:

    df[‘Sales’] = df[“Sales”].rolling(window=20).mean()
    df = df.dropna()
    
  2. 使用来自sklearn库的MinMaxScaler,我们可以继续对数据进行缩放:

    ds = df[[“Sales”]].values
    scaler = MinMaxScaler(feature_range=(0, 1))
    ds = scaler.fit_transform(ds)
    
  3. 接下来,我们需要将数据分成我们的训练集和测试集。记住,我们的目标是向模型提供一些历史样本数据,并查看我们是否可以准确预测未来的需求。让我们继续使用数据集的 75%来训练模型,并查看我们是否可以预测剩余的 25%:

    train_size = int(len(ds) * 0.75)
    test_size = len(ds) - train_size
    train = ds[0: train_size,:]
    test = ds[train_size : len(ds), :]
    
  4. 由于我们正在处理时间序列数据,我们需要使用lookback来迭代训练模型。让我们继续选择一个lookback值为100,并使用我们的dataset_generator函数来创建我们的训练和测试集。我们可以将lookback值视为模型在数据中回溯的范围,以进行训练:

    lookback = 100
    X_train, y_train = dataset_generator(train, lookback)
    X_test, y_test = dataset_generator(test, lookback)
    
  5. 如您从我们之前实现的 LSTM 模型中回忆的那样,我们需要在使用数据作为输入之前对数据进行reshape

    X_train = np.reshape(X_train, (X_train.shape[0], 1, X_train.shape[1]))
    X_test = np.reshape(X_test, (X_test.shape[0], 1, X_test.shape[1]))
    
  6. 最后,数据准备就绪后,我们可以继续准备模型本身。鉴于我们只处理一个特征,我们可以保持模型相对简单。首先,我们将使用 Keras 的Sequential类,然后添加一个具有两个节点的LSTM层,接着是一个具有单个输出值的Dense层:

    model = Sequential()
    model.add(LSTM(2, input_shape=(1, lookback)))
    model.add(Dense(1))
    
  7. 接下来,我们可以使用学习率为0.001Adam优化器并编译模型:

    opt = tf.keras.optimizers.Adam(learning_rate=0.001)
    model.compile(loss=’mean_squared_error’, optimizer=opt)
    
  8. 记住,我们可以使用summary()函数来查看编译后的模型:

    model.summary()
    

    这将导致以下输出,它提供了对模型内部工作的洞察:

    图 10.13 – Keras 模型的摘要

    图 10.13 – Keras 模型的摘要

  9. 模型编译完成后,我们可以开始训练过程。我们可以调用fit()函数,将模型在训练数据集上拟合 10 个 epoch:

    history = model.fit(X_train, y_train, epochs=10, batch_size=1, verbose=2)
    
  10. 模型训练过程应该相对较快。一旦完成,我们可以通过在图表中可视化结果来查看loss值:

    plt.figure(figsize=(10,6))
    plt.plot(history.history[“loss”], linewidth=2)
    plt.title(“Model Loss”, fontsize=15)
    plt.xlabel(“# Epochs”, fontsize=15)
    plt.ylabel(“Mean Squared Error”, fontsize=15)
    

    这将导致以下输出,显示了损失随时间逐渐减少的过程:

    图 10.14 – 随时间变化的模型损失

    图 10.14 – 随时间变化的模型损失

    在这里,我们可以看到loss值相当一致地下降,最终在 9-10 个 epoch 标记处达到平台期。请注意,我们在优化器中指定了学习率为0.001。如果我们将其增加到 0.01,或者减少到 0.0001,此图表的输出将非常不同。我们可以使用学习率作为优化模型性能的强大参数。尝试一下,看看损失的图形输出会是什么样子。

  11. 模型训练完成后,我们可以继续使用该模型来预测感兴趣的值:

    X_train_forecast = scaler.inverse_transform(model.predict(X_train))
    y_train = scaler.inverse_transform([y_train.ravel()])
    X_test_forecast = scaler.inverse_transform(model.predict(X_test))
    y_test = scaler.inverse_transform([y_test.ravel()])
    
  12. 数据准备就绪后,我们可以通过使用matplotlib绘制结果来可视化数据。首先,让我们使用lightgrey绘制原始数据集:

    plt.plot(list(range(0, len(ds))), scaler.inverse_transform(ds), label=”Original”, color=”lightgrey”)
    
  13. 接下来,我们可以使用blue绘制训练值:

    train_y_plot = X_train_forecast
    train_x_plot = [i+lookback for i in list(range(0, len(X_train_forecast)))]
    plt.plot(train_x_plot, train_y_plot , label=”Train”, color=”blue”)
    
  14. 最后,我们可以使用darkorange和虚线来绘制预测值,以区分其两个对应物:

    test_y_plot = X_test_forecast
    test_x_plot = [i+lookback*2 for i in
                   list(range(len(X_train_forecast), 
                   len(X_train_forecast)+len(X_test_forecast)))]
    plt.plot(test_x_plot, test_y_plot , label=”Forecast”, 
             color=”darkorange”, linewidth=2, linestyle=”--”)
    plt.legend()
    

    执行此代码后,我们将得到以下输出:

图 10.15 – 使用 LSTM 模型的训练和测试数据集

图 10.15 – 使用 LSTM 模型的训练和测试数据集

在这里,我们可以看到这个相对简单的LSTM模型在利用我们提供的训练数据集进行预测方面非常有效。该模型不仅能够捕捉到值的总体趋势,而且还成功地捕捉到了值的季节性。

摘要

在本章中,我们试图分析和理解时间序列数据,并在不到 15 页的篇幅内开发了两个预测预测模型。我们的旅程从探索和分解时间序列数据开始,将其分解成更小的特征,这些特征通常可以与浅层机器学习模型一起使用。然后,我们研究了时间序列数据集的组成部分,以了解其底层结构。最后,我们开发了两个在行业中常用的最常见预测模型——Facebook 的 Prophet 模型和基于 Keras 的 LSTM 模型。

在过去的几章中,我们开发了各种技术解决方案来解决常见的商业问题。在下一章中,我们将探讨使用 Flask 框架将这些模型如这些模型提供给最终用户的第一个步骤。

第三部分:将模型部署给用户

到目前为止,我们已经讨论了 Python、使用数据和开发模型。在本节中,我们将探讨如何将这些模型转移到生产环境中,并使其可供最终用户使用。我们将探讨四个常用的机器学习模型部署平台:AWS、Heroku、GCP 以及 Python anywhere。

本节包括以下章节:

  • 第十一章使用 Flask 应用程序部署模型

  • 第十二章将应用程序部署到云端

第十一章:使用 Flask 应用程序部署模型

在本书的整个过程中,我们探讨了在乳腺癌检测、科学主题建模、蛋白质分类和分子性质预测等领域开发众多稳健的机器学习模型。在这些教程中,我们准备并验证了我们的模型,以便它们具有尽可能强的预测能力。现在,我们将从开发新模型转向将训练好的模型部署给最终用户。

在本章中,我们将探讨用于准备 Web 应用程序的最受欢迎的框架之一:Flask。我们将使用 Flask 准备一个 Web 应用程序,向最终用户提供我们的模型,我们还将准备一个 应用程序编程接口API)来向其他 Web 应用程序提供我们的预测。

在本章中,我们将涵盖以下主题:

  • 理解 API 框架

  • 使用 Flask 和 Visual Studio Code 进行工作

  • 将 Flask 用作 API 和 Web 应用程序

  • 教程 – 使用 Flask 部署预训练模型

带着这些目标,让我们开始吧!

理解 API 框架

无论您是在登录您的电子邮件账户、浏览社交媒体,还是登录在线零售商,我们每天都在使用 Web 应用程序 来完成各种任务。例如,想象一个用户在他们的本地计算机上滚动查看电子实验室笔记。当用户登录并看到他们的数据时,这些信息是通过 API(即应用程序编程接口,不要与活性药物成分混淆)检索的。一旦在后台为用户检索到数据,它就会在前端以美观的 用户界面UI)中填充,使用户能够与数据交互、进行更改并保存它。我们可以以各种方式使用 Web 应用程序和 API,例如传输数据、与他人沟通,甚至进行预测,如图 11.1 所示:

图 11.1 – 一些 Web 应用程序功能的示例

图 11.1 – 一些 Web 应用程序功能的示例

带着所有这些功能,API 和它们的对应物为 Web 应用程序空间提供了创建 UI 以服务数据和进行预测的主要工具。如图 11.2 所示,有多个有用的 Web 应用程序框架 可用于各种编程语言:

图 11.2 – 一些 Web 应用程序框架的示例

图 11.2 – 一些 Web 应用程序框架的示例

为了本章的目的,我们将关注更受欢迎的机器学习部署框架之一:Flask (github.com/pallets/flask)。与它的类似框架相比,Flask 可以被视为一个 微 Web 框架——它完全用 Python 编写,高度抽象,使用户能够轻松开始模型部署过程。

当我们开始使用 Flask 框架部署模型时,重要的是要问自己我们的应用程序的最终用户是谁。在许多情况下,使用我们之前训练的模型进行预测将由同事和利益相关者完成。因此,拥有一个可用的 UI 将非常重要。另一方面,我们的部署模型可能不需要个人,而是需要软件或另一个需要以编程方式与之交互的 Web 应用程序。在这种情况下,UI 将不是必需的——然而,我们需要一种有组织的方式(例如,JSON)来处理两个系统之间的数据传输。我们可以在 图 11.3 中看到这两个案例的描述:

图 11.3 – 两种常见的 Web 应用类型

图 11.3 – 两种常见的 Web 应用类型

在任何情况下,我们都可以使用 Flask 来适应这两种情况。Flask 框架提供了各种架构——既简单又复杂——使用户能够选择最适合他们需求的模式。与 DjangoNode.jsSpring 等类似框架一样,Flask API 通常使用 URL 以类似的方式运行。对于后端 API 和前端 UI,我们可以使用 URL 来组织我们开发应用程序的方式。例如,用户可以登录网站查看和编辑其个人资料中的数据,而 API 可以允许外部实体与模型交互,如图 11.4 所示:

图 11.4 – 带示例的两种常见的 Web 应用类型

图 11.4 – 带示例的两种常见的 Web 应用类型

为了与 Web 应用程序交互,用户需要发起一个被称为 HTTP 请求 的操作,这通常是在用户不知情的情况下完成的。每个请求通常都与一个 URL 相关联,使用户能够完成任务。四种 HTTP 请求类型如图 11.5 所示:

图 11.5 – 四种 HTTP 请求类型

图 11.5 – 四种 HTTP 请求类型

例如,如果一个用户导航到 www.website.com/profile 并意图检索其个人资料详情,他们将使用一个 GET 请求。另一方面,一个使用 API 并意图对文本片段进行分类的应用程序将使用 POST 请求将文本发送到 www.website.com/api/classify。这些 URL 路径在 Web 应用程序范围内被称为 路由,它们允许开发者和数据科学家更好地组织他们的模型以进行部署。在下一节中,我们将看到如何在 Flask 框架中更具体地使用路由。

使用 Flask 和 Visual Studio Code 进行工作

Flask 是在 Python 语言中最常用且功能最丰富的 Web 应用程序之一。其抽象和 高级框架 使得所有级别的用户都能在短时间内快速实现。在本节的整个过程中,我们将了解 Flask 应用程序的不同组件,并在我们的机器上本地部署一个简单的模型。

在我们开始使用 Flask 之前,我们需要一个 集成开发环境 (IDE) 来进行工作。到目前为止,我们几乎一直在 Jupyter Notebook 中训练和开发模型。当涉及到实现时,我们需要另一种类型的 IDE 来进行工作。我们可以使用许多 Python IDE,例如 PyCharmSpyderVisual Studio 代码 (VSC)。我个人发现 VSC 是最易于使用的,因此在本节中我们将使用它作为我们的主要 IDE。您可以从他们的网站 (code.visualstudio.com/download) 或使用 Anaconda 下载 VSC。

开始安装过程,这可能需要几分钟。在等待的同时,在您的本地计算机上创建一个名为 flask-test 的新文件夹。一旦安装过程完成,打开 VSC。您可以通过几个简单的步骤打开您刚刚创建的文件夹:

  1. 点击顶部菜单中的 文件

  2. 点击 打开文件夹

  3. 导航到您的目录并点击 选择文件夹

您现在应该能在资源管理器窗格中右键单击并选择 新建文件 来在 app.py 中看到您目录的名称。

app.py 文件是 Flask 在其框架中使用的主体文件。应用程序中的所有内容都包含在这个文件中,或者从该文件中引用。尽管其内容取决于用户的精确实现,但该文件通常包含四个主要部分:

  1. 导入库、数据和其它资源

  2. 实例化应用程序并声明其他有用的函数

  3. 声明应用程序的路由

  4. 运行 __name__ == "__main__" 驱动代码

    我们可以在 图 11.6 中看到这些组件的示意图:

图 11.6 – Flask 应用程序的主要组件

图 11.6 – Flask 应用程序的主要组件

让我们现在继续填充 app.py 中的代码。这通常在四个主要部分中完成:

  1. 我们将首先从 flask 库中导入 Flask 类:

    from flask import Flask
    
  2. 接下来,我们需要创建我们 Flask 应用的一个实例:

    app = Flask(__name__)
    
  3. 我们现在可以使用应用对象来为我们的应用程序创建路由。路由通过在与之交互时直接执行其下方的函数来操作。让我们创建一个简单的路由,它返回 "Hello Biotech World!"

    @app.route('/')
    def biotech():
        return "Hello Biotech World!"
    
  4. 最后,我们需要一个应用程序的驱动程序,它可以满足使用 if __name__ == '__main__' 的条件。我们还将设置 debug 参数为 True 以帮助我们解决任何潜在的问题,并将 port 值设置为 8080

    if __name__ == '__main__':
        app.run(debug=True, port=8080)
    

    在 VSC 的命令行中,运行 Python 应用程序:

    http://localhost:8080/. Upon reaching this URL, you should be greeted by our previous message. Please note that the localhost URL is a link only accessible locally on your computer and is not available to others. The concept of routes should be familiar to us from the many websites we have used in the past. We can break down a URL into its smaller components, as depicted in *Figure 11.7*:
    

图 11.7 – URL 的主要组件

图 11.7 – URL 的主要组件

在我们的案例中,我们目前正在编辑应用的路径或端点。Flask 应用可以处理许多路径和端点,为开发者提供了很大的灵活性。

您可以通过在命令行中按 CTRL + C 来停止应用程序的运行,这将停止进程。进程停止后,您可以复制当前路由和函数直接在其下方创建第二个路由。将路径值设为 /lifescience(而不是仅仅 /),并为其函数提供一个独特的名称,例如 lifescience。接下来,更改返回值,再次运行应用程序,并导航到 http://localhost:8080/lifescience。如果一切顺利,您应该能够看到您的新消息!

路由和函数

请注意,路由必须是唯一的——这意味着您不能在 Flask 中有多个指向 /biotech 的路由。同样,路由下方的函数在其名称上也必须是唯一的。

当部署我们的模型时,我们将使用类似的架构。然而,返回语句通常将包括供人们使用的 UI 或供应用程序消费的数据。在下一节中,我们将通过使用 自然语言处理NLP)用例来更深入地探讨这一点。

使用 Flask 作为 API 和 Web 应用

第九章 自然语言处理 中,我们探讨了使用 transformers 库来运行文本相似度搜索引擎的目的。通过使用这项技术,我们可以探索其他模型和实现,例如 情感分析文本分类以及更多。在自然语言处理(NLP)方面,有一种特定类型的模型已经获得了很大的关注,那就是 摘要 模型。

我们可以将摘要模型视为设计用来将几段文本缩减为几个句子的任务,从而使用户能够减少阅读所需的时间。幸运的是,我们可以使用 transformers 库实现一个现成的摘要模型,并将其安装在我们的 app.py 文件中。我们不仅需要满足人类用户(通过使用 UI),还需要满足可能对使用我们的模型感兴趣的网络应用程序(API)。为了适应这两种情况,我们将在项目中需要总共三个文件来开始:

  • app.py:这是主文件,其中实例化了 Flask 框架和所有 NLP 模型。

  • styles.css:这是一个 CSS 文件,它允许我们设置 UI 的样式。

  • index.html:这是一个带有预构建 UI 页面的 HTML 文件,人类用户将与它交互。

为了更好的组织,让我们将 CSS 文件添加到名为 styles 的目录中,将 HTML 文件添加到名为 templates 的目录中。

当我们处理新的 Flask 应用程序时,我们通常希望使用 pip 安装的库方面有一个 空白状态。换句话说,每个 Flask 应用程序都应该拥有自己的 虚拟环境,在那里我们只安装应用程序需要和使用的库。我们可以使用 virtualenv 来实现这一点,而讽刺的是,virtualenv 本身也可以通过 pip 安装。

安装完成后,我们可以在命令行中使用 virtualenv 创建一个新的项目环境,命名为 .venv

$ python38 virtualenv .venv

你可以给你的虚拟环境起任何你喜欢的名字,但大多数用户通常默认使用前面命令中的名字。当你看到当前工作目录中出现了指定名称的新目录时,你就知道这个命令执行成功了。我们现在需要 激活 这个环境,这可能会根据你使用的系统类型而有点棘手。Windows 用户可以使用以下命令来激活他们的环境:

> .\.venv\Scripts\activate

另一方面,LinuxMac 用户可以通过以下命令来激活他们的环境:

$ source .venv\bin\activate

你可以通过查看命令行当前工作目录的左侧是否有环境名称出现来确认环境已被激活。现在,请安装 flasktransformers,因为我们将在当前环境中需要这些库。

在设置好环境并包含前面讨论的三个文件后,我们应该有一个如 图 11.8 所示的目录结构:

图 11.8 – 在 VSC 中此项目的当前文件夹结构

图 11.8 – 在 VSC 中此项目的当前文件夹结构

在项目结构现在已经到位的情况下,让我们向 app.py 文件中添加一些代码。我们可以从导入这个应用程序中需要的库开始:

from flask import Flask, jsonify, request, render_template 
import json
from transformers import pipeline
import re

现在库已经导入,我们可以像之前一样实例化 Flask 应用程序的实例。然而,这次我们需要指定模板文件夹:

app = Flask(__name__, template_folder='templates')

应用实例化后,我们现在可以从 transformers 的pipeline类创建摘要模型的实例:

summarizer = pipeline("summarization")

接下来,我们可以添加我们的路由。我们首先创建一个到主页的路由,该路由使用index.html文件显示 UI:

@app.route('/')
def home():
    return render_template('index.html')

我们需要添加两个路由:一个用于 UI,另一个用于 API。根据框架、行业和用例的不同,有许多最佳实践。在大多数情况下,api端点通常以api这个词开头,以区分它们。让我们先创建一个用于 UI 的路由:

@app.route('/prediction', methods = ["POST"])
def ui_prediction():
    """
    A function that takes a JSON with two fields: "text" & "maxlen"
    Returns: the summarized text of the paragraphs.
    """
    print(request.form.values())
    paragraphs = request.form.get("paragraphs")
    paragraphs = re.sub("\d+", "", paragraphs)
    maxlen = int(request.form.get("maxlen"))
    summary = summarizer(paragraphs, max_length=maxlen, min_length=49, do_sample=False)
    return render_template('index.html', prediction_text = '" {} "'.format(summary[0]["summary_text"])), 200

注意,在这个函数内部,我们使用request.form.get函数从 UI 表单中检索值。此外,我们使用一些正则表达式来清理文本,然后使用摘要模型总结内容。最后,我们返回摘要和index.html文件。

现在我们为 api 创建第二个路由:

@app.route('/api/prediction', methods = ["POST"])
def api_prediction():
    """
    A function that takes a JSON with two fields: "text" & "maxlen"
    Returns: the summarized text of the paragraphs.
    """
    query = json.loads(request.data)
    paragraphs = re.sub("\d+", "", query["text"])
    maxlen = query["maxlen"]
    minlen = query["minlen"]
    summary = summarizer(paragraphs, max_length=maxlen, min_length=minlen, do_sample=False)
    return jsonify(summary), 200

注意,除了获取输入数据、清理内容和摘要之外,我们还可以直接从JSON对象中获取maxlenminlen参数。

最后,我们可以继续执行代码:

if __name__ == '__main__':
    app.run(debug=True)

这样,我们就成功开发了 Flask 应用。一旦部署,你应该能够导航到http://localhost:5000/并开始总结文本段落!我们可以在图 11.9中看到一个应用的示例:

图 11.9 – 摘要 Web 应用的截图

图 11.9 – 摘要 Web 应用的截图

此外,我们可以使用POST请求等应用,添加 URL,然后将数据以字典的形式添加,并指定内容类型为 application/JSON:

{
    "text" : "Biotechnology is a broad area of biology, involving the use of living systems and organisms to develop or make products. 
                              …
 molecular biology, biochemistry, cell biology, embryology, genetics, microbiology) and conversely provides methods to support and perform basic research in biology.",
    "maxlen" : 60,
    "minlen" : 30
}

现在应用正在运行,我们成功创建了一个解决方案,使用 Flask 为人类用户和其他 Web 应用提供服务。在本书的最后一章,我们将把这个应用部署到云端。然而,完成这一步骤的一个重要步骤是提供一个需要安装的库列表。鉴于我们已经设置了一个虚拟环境,我们可以通过pip轻松地将这些库的列表转移到requirements.txt文件中:

$ pip freeze > requirements.txt

这样,你现在应该能在app.py相同的目录下看到一个requirements.txt文件。确保你使用的环境只包含你计划使用的库非常重要。这有助于保持应用轻量级且使用快速。在下一节中,我们将查看一个更深入的应用——一个使用之前训练的模型,该模型涉及本书前面看到的乳腺癌数据集。

教程 – 使用 Flask 部署预训练模型

在创建 Flask 应用程序的先前的例子中,我们看到了如何结合预测模型使用应用程序来部署解决方案给最终用户。然而,我们部署的模型是一个现成的解决方案,而不是我们自己开发的模型。在本节中,我们将在 Flask 应用程序中再次部署一个模型;然而,我们使用的是基于我们在第五章中看到的癌症数据集的模型,理解机器学习

如果你还记得,这个模型的主要思想是接受给定肿瘤的一组测量值,并根据这些测量值确定诊断结果,结果可能是恶性良性。在这个应用程序中,我们将允许用户与训练好的模型交互,并输入模型将用于预测的测量值。考虑到这一点,让我们开始吧!

就像之前一样,继续添加一个新的文件夹和一个新的虚拟环境来安装相关的库。

使用与之前相同的目录结构和流程,我们可以首先导入相关的库。请注意,我们在这里添加了pickle库,因为我们需要使用之前创建的序列化模型:

from flask import Flask, jsonify, request, render_template 
import json
import pickle
import pandas as pd
from sklearn.preprocessing import StandardScaler

我们下一步涉及导入我们训练的两个模型——实际的分类模型和用于数据的标准缩放模型:

loaded_scaler= pickle.load(open("./models/ch10_scaler.pickle",'rb'))
loaded_clf= pickle.load(open("./models/ch10_rfc_clf.pickle",'rb'))

然后,我们可以定义一个predict_diagnosis函数,以便在开发我们的路由时清理代码。这个函数将接受以列表形式输入的数据、缩放模型和分类模型:

def predict_diagnosis(inputData, scaler, model):
    """
    Function that takes a list of measurements, scales them, and returns a prediction
    """
    inputDataDF = pd.DataFrame([inputData])
    scaledInputData = scaler.transform(inputDataDF)
    prediction = model.predict(scaledInputData)
    return prediction[0]

接下来,我们将实例化 Flask 应用程序,同时指定template文件夹:

app = Flask(__name__, template_folder='templates')

在处理完这些事项后,我们可以专注于我们的路由。首先,我们将创建一个home路由,用户将首先看到:

@app.route('/')
def home():
    return render_template('index.html')

接下来,我们需要一个prediction路由,就像之前一样。这里唯一的区别是输入值的数量将更多,因为我们现在正在处理更多的特征:

@app.route('/prediction', methods = ["POST"])
def prediction():
    print(request.form.values())
    radius_mean = request.form.get("radius_mean")
    texture_mean = request.form.get("texture_mean")
    smoothness_mean = request.form.get("smoothness_mean")
    texture_se = request.form.get("texture_se")
    smoothness_se = request.form.get("smoothness_se")
    symmetry_se = request.form.get("symmetry_se")
    input_features = [radius_mean, texture_mean, smoothness_mean, texture_se, smoothness_se, symmetry_se]
    prediction = predict_diagnosis(input_features, loaded_scaler, loaded_clf)
    prediction = "Malignant" if prediction == "M" else "Benign"

    return render_template('index.html', prediction_text = '" {} "'.format(prediction))

最后,我们可以继续运行应用程序:

if __name__ == '__main__':
    app.run(debug=False, port=5000)

在运行模型并在网络浏览器中导航到 localhost 后,应用程序将出现。请尝试使用 UI 进行一些预测,如图图 11.10所示:

图 11.10 – 乳腺癌网络应用程序的屏幕截图

图 11.10 – 乳腺癌网络应用程序的屏幕截图

我们可以看到,该模型能够接受我们的输入数据,运行预测,并将结果返回给用户。我们没有在这里做的一件事是为其他网络应用程序与我们的模型交互创建一个 API 路由。作为一个挑战,请继续创建这个路由,以之前的总结应用程序为例。

摘要

在本章中,我们偏离了模型开发的方向,更多地关注了模型如何部署以与 Web 应用程序交互。我们研究了通过 API 进行数据传输的想法,并且还了解了一些最常用的框架。我们调查了最常用的 Python Web 应用程序框架之一,即 Flask。使用 Flask,我们开发了一个 NLP 摘要模型,允许人类用户和其他 Web 应用程序与之交互并使用其功能。此外,我们还学习了如何部署之前训练好的模型,例如来自scikit-learn的模型。

在这些实例中,我们在开发它们的框架和功能时,在本地启动了我们的模型。在下一章中,我们将通过使用Docker容器和AWS将我们的模型部署到云端,使我们的模型对他人可用。

第十二章:将应用程序部署到云

在上一章中,我们专注于将我们的模型集成到Flask框架中,以开发两种向最终用户提供服务的主要方法:图形用户界面GUIs)和应用程序编程接口APIs)。使用 Flask 框架,我们成功地将我们的模型仅用于开发目的进行本地部署。在本章中,我们将迈出下一步,将我们的模型部署到云中,使其不仅对我们本地可用,而且对网络上的许多其他用户也可用。

目前有许多不同的部署平台,例如亚马逊网络服务AWS)、谷歌云平台GCP)、Azure 和 Heroku,每个平台都旨在满足多种需求。在这些平台中的每一个,都有许多解决方案,每个解决方案都有其各自的优缺点。对于这些解决方案中的每一个,我们都有许多方法可以在其中部署一个框架。本质上,部署解决方案的可能方式数量实际上是无法计数的,因此用户可能会感到容易不知所措。在本章的整个过程中,我们将探讨新开发者通常采取的一些最常见和最直接的方法来部署他们的应用程序。

在接下来的几节中,我们将涵盖以下主题:

  • 探索当前的云计算平台

  • 理解容器和镜像

  • 教程 – 将容器部署到 AWS(Lightsail)

  • 教程 – 将应用程序部署到 GCP(App Engine)

  • 教程 – 将应用程序代码部署到 GitHub

带着这些目标,让我们继续前进,开始吧!

探索当前的云计算平台

在过去几年中,最显著的技术趋势之一是转向云计算。尽管大多数公司过去更喜欢拥有、运营和维护自己的数据中心和基础设施,但现在全球大多数企业都采用以云为先的方法。公司选择这条道路的原因有很多,比如成本降低、可扩展性、安全性等等。鉴于对云计算能力的需求激增,许多云计算平台开始响应这一数字世界的重大运动而增长和扩张。

在过去几年中,许多这些云计算平台不仅开始开发解决方案以满足主要的基础设施需求,而且开始专注于数据科学领域内的特定需求。主要平台是AWSGCP微软 Azure,如下面的图所示:

图 12.1 – 一些常见的云计算平台

图 12.1 – 一些常见的云计算平台

世界各地的许多公司通常以企业级的运营水平使用这些提供商之一来保持一致性。从数据科学家和开发者的角度来看,这些平台几乎相同,因为它们通常包含非常相似的工具来满足我们的需求。

这些平台中的每一个都包含了许多旨在部署框架并将它们以某种形式提供给最终用户的解决方案。这些资源提供给最终用户程度的不同,通常就是这些平台之间的区别。例如,开发者可能期望特定 Web 应用有很高的活动量,因此可能会决定使用AWS Elastic BeanstalkAmazon Elastic Container ServiceECS)来部署他们的模型。另一方面,另一个用户可能只想以最简单的方式将他们的 Web 应用部署给少数用户,因此会选择使用Amazon Elastic Compute CloudEC2)。在任何情况下,开发者选择的特定解决方案通常是基于具体需求来选择的。让我们继续看看以下截图中所描述的一些最受欢迎的解决方案:

图 12.2 – 部署 Web 应用的一些最常用工具

图 12.2 – 部署 Web 应用的一些最常用工具

当谈到部署Flask应用时,近年来在数据科学社区中,三种部署方案已经获得了很大的影响力。每个方案都有其各自的优缺点,确保特定 Web 应用的业务需求与这些平台中任何一个最佳解决方案相匹配,这是数据科学家或开发者的责任。

随着 Web 应用的普及,很快变得明显的是,应用内部的一致性是必要的,以确保开发者在某个平台上创建和部署的应用可以轻松地以最小的更改在另一个平台上部署。在下一节中,我们将通过一个名为Docker的例子来讨论容器化的概念。

理解容器和镜像

构建和部署 Web 应用的最简单方法之一是通过使用容器。我们可以将容器视为包含构成 Web 应用所有项目的桶或容器,但以操作系统OS虚拟化的形式。回想一下上一章——第十一章使用 Flask 部署模型——我们在其中创建了一个虚拟环境来更好地维护为应用安装所需的包。容器可以以相当类似的方式思考,只是在操作系统级别。

容器由许多项目组成,如可执行文件、库、二进制代码等等。鉴于它们不包含服务器通常具有的一些较重的项目,如操作系统镜像,因此被认为开销较小,更轻量。由于这些轻量级容器被认为是打包好并随时可用的,因此开发人员(或自动化系统)能够轻松部署多个容器实例以满足特定网站或应用增加的流量需求。

理解容器的好处

在管理和部署 Web 应用时,使用容器有许多好处,尤其是在企业规模上。最终,它们提供了一种一致且高效的构建、部署和管理多个应用的方式。以下是一些主要好处:

  • 更大的可扩展性—轻松部署更多实例以满足特定需求。

  • 提高可移植性—部署到不同的平台和操作系统。

  • 降低开销—比传统方法使用更少的资源。

容器在许多不同领域都非常有效——特别是两个领域属于微服务和自动化。在微服务的情况下,应用通常被分解成更小的组件,每个组件都需要独立于其他组件部署和扩展。在这种情况下,容器是解决该问题的绝佳解决方案并不令人惊讶。另一方面,在自动化的情况下,容器可以轻松地以自动化的方式创建或删除,这使得它们对于可扩展性以及持续集成/持续部署CI/CD)管道非常有用。

重要提示

我们可以将 CI/CD 管道视为自动化软件交付的方法,以标准化流程并减少人为错误。任何给定的 CI/CD 管道通常有四个阶段:将新代码推送到 GitHub 等存储库(我们将在本章后面看到),构建脚本构建或编译代码,测试脚本测试代码的某些部分,最后是托管最终产品的部署平台。

在接下来的教程中,我们将探讨部署容器的过程——具体来说,是将 Docker 容器部署到 AWS Lightsail。

教程 - 将容器部署到 AWS(Lightsail)

AWS Lightsail是一个管理云平台,由于其简单的界面和快速的部署能力,近年来获得了极大的普及,并且是使用 AWS 部署应用时入门的绝佳方式。与 AWS 的其他产品或解决方案相比,使用 Lightsail 的一些最常见用例包括简单的机器学习ML)Web 应用(如我们的应用!),静态投资组合网站,动态电子商务网站,以及简单的 API。

在本教程的过程中,我们将使用 AWS 命令行界面CLI)将我们的 Flask 应用程序部署到 AWS Lightsail。您可以通过访问 AWS CLI 页面(docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html)来安装 CLI,选择感兴趣的操作系统,并按照安装说明进行操作。您可以通过运行以下命令来确认 CLI 是否正确安装:

$ aws configure --profile 

如果安装完成正确,您将进入配置过程。请继续按照需要配置 CLI。此过程可能需要请求 身份和访问管理IAM)凭据——请提供我们之前示例中的正确凭据,或者准备一套新的凭据,就像我们在 第九章自然语言处理 部分,特别是 处理非结构化数据 部分中所做的那样。

在 CLI 和凭据都设置好的情况下,现在让我们再次专注于应用程序,从内容开始。如果您还记得从 第十一章使用 Flask 应用程序部署模型,应用程序的内容现在应包括虚拟环境、styles.cssindex.html,当然还有 app.py,如下面的截图所示:

图 12.3 – 当前工作目录的内容

图 12.3 – 当前工作目录的内容

为了部署我们的应用程序,我们还需要一些文件,我们将很快探索这些文件,以帮助部署过程和容器化过程。除了 AWS Lightsail,我们还将使用 Docker——这是一个常用的工具,允许用户在隔离的容器中创建、部署和运行应用程序。您可以通过访问 Docker 网站下载适用于您特定操作系统的 Docker (docs.docker.com/get-docker/)。让我们继续探索这些新文件,它们的内容以及它们如何在应用程序中使用。

首先,我们从 Dockerfile 开始,它包含一系列准备应用程序环境的指令。这些指令包括 Python 版本、设置工作目录、复制感兴趣的文件,当然还有安装需求。代码如下所示:

FROM python:3.8
EXPOSE 5000/tcp
WORKDIR /app
COPY requirements.txt .
COPY models/ch10_scaler.pickle /models/ch10_scaler.pickle
COPY models/ch10_scaler.pickle /models/ch10_rfc_clf.pickle
COPY styles /app/styles
COPY models /app/models
COPY templates /app/templates
COPY app.py .
ENV IN_DOCKER_CONTAINER Yes
RUN pip install --upgrade pip
RUN pip3 install -r requirements.txt
CMD [ "python", "./app.py" ]

在准备好的 Dockerfile 之后,我们现在可以继续构建容器镜像。我们可以使用之前安装的 Docker 来构建容器,通过执行以下命令:

$ docker build -t flask-container .

执行此命令(不要忘记末尾的 .,它表示当前目录!)后,Docker 将构建一个标记为 flask-container 的容器。

我们下一步将使用 AWS CLI 创建一个容器服务。我们可以通过执行以下代码来完成此操作,其中我们指定了 service-namepowerscale 参数。请注意,这些参数指定了服务的容量:

$ aws lightsail create-container-service --service-name flask-service --power small --scale 1

执行此命令后,你应该能够监控进度。一旦服务从 pending 状态变为 active,你可以执行下一个命令,该命令推送容器镜像:

$ aws lightsail push-container-image --service-name flask-service
--label flask-container --image flask-container

执行此命令后,你将在结果中看到以下值:

":flask-service.flask-container.X"

请注意,X 应该是一个与将镜像推送到容器服务时的时间相对应的数值。如果你是第一次这样做,该值应该是 1

接下来,我们需要创建一个名为 containers.json 的文件,指定 Flask 镜像以及端口,包含以下代码:

{
    "flask": {
        "image": ":flask-service.flask-container.X",
        "ports": {
            "5000": "HTTP"
        }
    }
}

X 替换为你之前收到的数值。再次提醒,如果你是第一次部署容器,该值应该是 1。完成这些后,我们现在可以继续创建我们的最终文件 public-endpoint.json,该文件指定了容器名称和端口,包含以下代码:

{
    "containerName": "flask",
    "containerPort": 5000
}

到目前为止,目录的层次结构应包括所有之前的文件,以及 containers.jsonDockerfilepublic-endpoint.json,如图下所示:

图 12.4 – 当前工作目录的内容

图 12.4 – 当前工作目录的内容

现在文件和容器都已就绪,我们可以继续进行将容器部署给最终用户的最后步骤。为此,我们可以使用 create-container-service-deployment 命令,以下代码:

$ aws lightsail create-container-service-deployment --service-name flask-service --containers file://containers.json --public-endpoint file://public-endpoint.json

执行代码后,你应该会看到应用程序的状态被列为 get-container-services 命令,通过执行以下命令来监控当前应用程序:

$ aws lightsail get-container-services --service-name flask-service

命令完成后,你将看到一个统一资源定位符URL)作为输出。前往列出的 URL,你应该能够看到我们开发的在线应用程序,并且可供我们的最终用户使用。以下是一个示例截图:

图 12.5 – 在 AWS Lightsail 上运行的 Web 应用程序

图 12.5 – 在 AWS Lightsail 上运行的 Web 应用程序

或者,你可能想通过 AWS 上的管理控制台查看应用程序。为此,导航到控制台并搜索 AWS Lightsail。你应该会被重定向到 AWS Lightsail 页面,在那里你应该能看到你的实例和容器,如图下所示:

图 12.6 – AWS Lightsail 管理控制台

图 12.6 – AWS Lightsail 管理控制台

在本教程中,我们成功地将我们的本地 Flask 实现部署为 Web 应用程序到 AWS。在下一个教程中,我们将部署相同的应用程序到 GCP 的 App Engine。

教程 - 将应用程序部署到 GCP(App Engine)

在本教程中,我们将部署相同的应用程序到 GCP 的 App Engine。与大多数其他云平台相比,GCP 最大的好处是易于使用,同时确保用户可以以最小的问题和错误部署模型。考虑到这一点,让我们继续将我们的应用程序部署到 GCP。

我们可以从安装y键开始,如下所示:

You must log in to continue. Would you like to log in (Y/n)?  y

您将被重定向到浏览器,您可以使用您的 Google 账户登录。请使用与我们在上一章第七章,“监督式机器学习”,关于 GCP 所使用的相同 Google 凭证登录。

登录后,您将被提示选择一个项目。选择您在本书中之前创建的项目。请继续完成任何其他剩余的项目,如默认区域,并完成配置。

完成后,您应该在您的系统上安装了gcloud CLI。请确保重新启动您正在使用的命令行窗口,因为一些PATH变量可能需要刷新。

现在 CLI 已安装并运行,我们可以继续开始。导航到附带的代码中找到的flask_cancer_ae目录。我们需要在我们的目录中创建一个名为app.yaml的新文件,包含以下代码:

runtime: python37

这将简单地指定我们应用程序的运行时间。保存此文件后,我们可以继续进行一些初步配置。我们首先需要设置项目标识符ID),如果我们还没有这样做,可以使用以下命令:

$ gcloud config set project GCP-PROJECT-ID

请确保将GCP-PROJECT-ID替换为您关联的项目 ID。设置完成后,我们现在需要启用Cloud Build CLI,它用于使用我们的文件创建应用程序的容器,以下命令:

$ gcloud services enable cloudbuild.googleapis.com

接下来,我们将为此特定项目在App Engine中初始化应用程序。我们可以使用以下命令来完成:

$ gcloud app create --project= GCP-PROJECT-ID

请确保将GCP-PROJECT-ID替换为您特定的项目 ID。最后,为了继续部署项目,我们可以使用以下命令:

$ gcloud app deploy

一旦过程完成,项目将被部署到 GCP!我们可以使用以下命令检查应用程序,如下所示:

$ gcloud app browse

除了使用 CLI 之外,我们还可以访问 GCP 控制台中找到的App Engine 仪表板以完成以下任务:

  • 访问应用程序。

  • 监控流量。

  • 检查账单。

  • …以及更多!

GCP 拥有许多令人惊叹的功能,在部署应用程序、管理数据和监控流量方面为用户提供极佳的体验。如果您对学习更多关于 GCP 感兴趣,我强烈建议您关注并完成 GCP 平台提供的许多优秀教程。

现在我们已经将应用程序部署到 GCP,我们的下一步将是探索将我们的代码发送到其他地方的不同方式:通过git CLI。在下一节中,我们将探讨将代码推送到 GitHub 的过程。

教程 – 将应用程序代码部署到 GitHub

在过去的两个教程中,我们将我们的应用程序部署到云平台,以便用户可以使用 Flask 框架与我们的模型进行交互。在第一个平台中,我们使用了 AWS Lightsail,在第二个平台中,我们使用了 GCP 的 App Engine。在这个教程中,我们的目标将是部署我们的代码,不是为了使模型对用户可用,而是为了向其他数据科学家以及潜在的未来雇主展示我们的代码和辛勤工作。我们将通过使用GitHub来部署我们的代码来实现这一点。

本书中的所有编码示例和教程都已通过 GitHub 在线提供。如果您还没有这样做,我强烈建议您创建自己的账户。您可以将 GitHub 视为程序员的 LinkedIn——一个展示您辛勤工作的空间。

您可以通过访问他们的官方网站(github.com/)并注册为新用户来创建一个免费的 GitHub 账户。一旦您注册成功,您就可以保存您的代码并与项目或仓库一起工作。您可以将仓库视为一个保存您工作的空间,其中保存了多个版本,以便用户在需要时可以回滚到旧代码。

这种方式是用户将在他们的计算机上本地有一个给定项目或仓库的实例或副本。每当取得重大进展时,用户可以做出更新或提交,然后将这些新更改推送到远程仓库以进行备份,如图所示:

图 12.7 – 本地与远程仓库

图 12.7 – 本地与远程仓库

在创建好您的个人资料后,让我们继续在命令行上安装git。我们可以通过导航到git-scm.com/book/en/v2/Getting-Started-Installing-Git并为您的操作系统安装git来开始操作。您可以通过在命令行上运行git命令来确认安装是否成功,它应该会返回一个命令和可能的参数列表。

让我们继续使用命令行导航到我们之前部署的一个应用程序。根据您在本地计算机上创建应用程序的位置,您的路径可能看起来像这样:

C:\Users\Username\Documents\GitHub\Machine-Learning-in-Biotechnology-using-Python\chapters\chapter12\flask_cancer_ls

通过命令行窗口或使用Visual Studio CodeVSC)导航到您的目录。一旦到达那里,使用以下命令初始化一个新的仓库:

$ git init

一旦仓库初始化完成,我们将在您的 GitHub 账户上创建一个仓库(稍后连接到它)。我们可以通过以下简单步骤来完成:

  1. 登录到您的新GitHub账户,如图所示:图 12.8 – GitHub 登录页面

    图 12.8 – GitHub 登录页面

  2. 在主页面上,点击屏幕左侧的新建按钮,如图所示:图 12.9 – 创建新仓库

    图 12.9 – 创建新仓库

  3. 给新仓库起一个名字,例如flask-cancer-ls。在保留所有其他字段为默认值的情况下,点击创建仓库,如图所示:

图 12.10 – 创建新仓库(继续)

图 12.10 – 创建新仓库(继续)

一旦创建完成,您将被重定向到一个新页面,其中包含一些供您使用的示例代码。鉴于我们已经创建了一个新的仓库,我们不需要再次进行这一步骤。如果我们回到命令行,我们可以继续运行以下命令,将我们的文件添加到git中,以便git确定需要发送到add命令的任何新更改,如下所示:

$ git add app.py

或者,我们可以使用点表示法添加所有文件,如下所示:

$ git add .

通常认为,逐个添加文件是更好的做法,因为这样出错的可能性较小。请相信我!

使用add命令将文件添加到暂存区后,我们的下一步是提交它们。我们可以将暂存区视为一个空间,用于存放即将发送到远程仓库的新代码。我们可以使用commit命令,并附上一个描述当前提交的有用消息,如下面的代码片段所示。您可以使用消息简要描述提交中的更改。这将使得在查看旧代码时,尝试在 GitHub 网站上找到特定的更改变得容易得多:

$ git commit -m "This is my first commit"

执行此代码后,您可能会遇到一个错误,要求您指定您的姓名和电子邮件地址。请使用以下命令完成此操作:

$ git config --global user.email "you@example.com"
$ git config --global user.name "Your Name"

在保存您的凭据并完成提交后,我们现在可以继续使用以下命令将我们的本地仓库远程仓库链接起来:

$ git remote add origin https://github.com/username/reponame.git

请确保将usernamereponame替换为您各自的值!一旦完成,您可以继续完成最后一步,即使用以下命令将您的代码推送到 GitHub:

$ git push origin master

完成这一步后,如果你导航回 GitHub 网站,你将能够在这里看到你的代码!与最后两个教程不同,在那里我们将代码以应用程序的形式部署到在线网站,供最终用户交互,这里的目的是将我们的代码和其他内容存储在一个安全的空间中。我们有选择让其他用户看到我们的代码或将其保留为私有的选项。此外,还有一些平台,如 Heroku,只需提供仓库链接,就能简单地部署应用程序。

摘要

在本章中,我们回顾了将我们的应用程序部署到云端最终用户的一些方法。首先,我们探讨了使用 AWS Lightsail,它允许我们以在线 Web 应用程序的形式部署我们的代码,使用 Docker 容器。接下来,我们探讨了使用 GCP 的 App Engine 来部署我们的代码,再次以在线 Web 应用程序的形式,使用其用户友好和抽象的方法。最后,我们将代码以仓库的形式部署到 GitHub,使我们能够向用户、专业人士和潜在的雇主 alike 暴露内容。

恭喜!随着本教程的最后部分完成,我们已到达这本书的结尾。回顾过去 12 章,我们在众多不同领域涵盖了众多不同主题。起初,我们学习了新的语言,如 Python 和结构化查询语言SQL),并使用它们来分析和可视化我们的数据。然后,我们探索了最常见的一些机器学习(ML)和深度学习DL)架构,并使用它们来开发强大的预测模型。接着,我们将注意力转向一些特定的应用领域,例如自然语言处理NLP)和时间序列。最后,我们探索了几种使用 AWS 和 GCP 将我们的应用程序部署给最终用户的方法。尽管我们在本书中涵盖了大量的内容,但外面还有浩瀚的知识和信息宇宙等待你去探索。在你迈向下一个伟大冒险之前,有三件事你应该永远记住:

  • 最简单的解决方案通常是最好的解决方案。如果你不需要,永远不要过度复杂化模型。

  • 永远不要停止学习。我们生活在一个数字时代,新的发现正以前所未有的速度实现。

  • 指标是你的最佳朋友。它们将引导你贯穿整个开发过程,并帮助你作为数据科学家提出论点。记住——一切都是推销。

现在有了这三件事,勇敢地去做数据科学吧!

posted @ 2025-09-04 14:13  绝不原创的飞龙  阅读(4)  评论(0)    收藏  举报