第3章
在GCP上基于逻辑回归实现实时智能
在本章中,我们会在谷歌云上构建一个能交互式地创建乘客配置的Web应用程序,来预测谁会在泰坦尼克号沉船事件中幸存下来。
我们将重温经典的受到全世界教科书和教育博客青睐的泰坦尼克号数据集。我们将分析乘客们的特征,并尝试理解在这场悲惨的事故中,为什么有些人幸存下来了,而其他人却没有。我们将探索数据集,为建模做好准备,并将其应用到一个交互式Web应用程序中。该应用会允许使用者创建虚构的乘客配置,设置参数,并可视化他或她在这次航行中的表现(图3-1)。
我们将使用由范德比尔特大学(Vanderbilt University)生物统计学系托管的数据集,这里还托管了许多其他有趣的数据集。通过调用Pandas Python库,你可以方便地直接下载这些数据集。
提示:请访问www.apress.com/9781484238721 ,单击“Download source code”按钮,跳转至GitHub网站,下载第3章所需文件。推荐使用Jupyter Notebook打开chapter3.ipynb文件,以配合阅读本章内容。
3.1 规划Web应用
作为第一步,我们将充分思考泰坦尼克项目的概念,并在本地机器上创建Web应用程序。
应用程序背后的大脑是来自“sklearn Python库”的简单逻辑回归模型。在数据准备阶段,我们会删除非预测特征、为分类数据创建虚拟列,以及在文本字段上应用基本的自然语言处理。然后,我们会训练该模型,用于识别这趟旅行中幸存者和遇难者之间的共同模式。
为了验证模型的准确性,我们采用标准的建模过程,将数据集一分为二,一部分用于模型训练,另一部分用于模型验证。一旦我们对模型的能力感到满意,就可以用它来预测虚构乘客的幸存概率。这个概率是0~1之间的某个数字,代表虚构乘客从沉船事件中幸存下来的机会—越接近1,幸存的机会越大。
最后,我们将抽象并概括整个过程,使得它能在我们的Web应用程序的构造函数中运行。这意味着,在模型部署期间(以及每次重启Web服务器时),只会进行一次提取、准备和建模数据的整个过程。这确保了当用户想要与Web应用程序交互时,它会快速从已训练好的模型中产生预测,因为它已经被加载到内存中了。现在,我们先来完成项目的本地版本。
3.2 数据处理
由于这是一个经典的数据科学练习和一个著名的事件记录,我们已经知道(或有直觉)哪些特征有助于了解谁会在事故中幸存下来(有关其他信息,请参阅范德比尔特生物统计数据)。首先将本章的文件下载到chapter-3文件夹中。打开Jupyter Notebook以便跟进。先来看一下本机上的数据图例(表3-1)。
我们可使用Pandas的head()函数以编程方式完成探索数据的第一步,该函数将返回前5行记录(代码清单3-1和图3-2)。你还可以使用tail()函数查看最后5行数据。
其中,比较引人注意的是,body列中缺少很多值,name和home.dest列中存在大量文本数据,而cabin列中有一些混合内容。
Pandas函数info()、describe()和isnull()也是进行快速数据探索的关键。强烈建议你在拿到新数据集时,或在做完任何数据转换工作后运行这些函数。
info()函数告诉你数据集中包含的数据类型和非空值个数汇总(代码清单3-2)。
Pandas的info()函数输出中标记为“non-null object”的数据可被视为基于文本的数据,此时,我们还需要确定它的文本类型。这个过程会具有某种程度的主观性,因为有很多方法可以解决这个问题(下面很快就会详细介绍)。
Pandas的describe()函数为你提供所有定量字段的汇总摘要。我们可以看到survived字段的平均值为0.38,这意味着只有38%的乘客幸存了下来。并且由于我们将使用该特征作为结果标签来训练模型,因此整个数据集更偏向于遇难者(也就是说,大多数乘客没有在这次航行事故中幸存下来,见代码清单3-3和图3-3)。
可以将isnull()函数封装到计数器中,以找出将要处理的缺失值的数量。已经有很多关于估算,以及如何更好地处理建模方案中缺失数据的书籍出版了。在本例中,我们将丢弃几个特征,并对其他几个特征利用插补法(imputation)来补齐缺失的数据(代码清单3-4和图3-4)。
在分析该函数的输出时,我们发现数字的、分类的和基于文本的特征都有缺失数据。数据集总共包含1309行。我们还看到body特征有90%的条目缺失,因此我们将直接丢弃它。我们还将忽略一些其他特征,它们要么难以使用,要么对幸存模型没什么帮助(例如,乘客的姓氏)。
3.2.1 处理分类型数据
使用分类型数据是一个重要的主题,有时候是主观的,而有时候则不是。一个好的经验法则是找出某个基于文本的特定列的所有值中,有多少是唯一的,有多少是重复的。检查重复度是一个很好的开始。我们将基于cabin特征,调用Pandas的groupby()函数和count()函数。其结果将告诉我们,乘客是否具有共同的cabin特征。如果具有,那该特征应该被归为分类型的;否则,则是自由格式文本类型的(代码清单3-5和图3-5)。
从输出结果可以确认这些值是分类型的,因为它们是经常重复的(这和直觉一致,即每个舱室可以有多于一个乘客)。让我们在另一个基于文本的列上进行相同的实验,即name特征(我们将会看到具体有多少次重复,见代码清单3-6和图3-6)。
就像由直觉所得结果一样,大多数名字都是唯一的。因此,这个特征列不是分类型的,它一般不适用于建模(你需要重复的数据来找到模式,因此原始的有唯一性的文本数据不能用于此目的)。
可以在数字和文本数据类型中找到分类型数据。因此,重要的是单独考察每个特征,并确定最佳建模方式。例如,sex这一特征是分类型的和二元的,因此它的数据片段对我们来说价值有限。我们将它更名为isFemale列。如前所示,cabin特征确实有重复性,对此我们还可以做一些处理,使它的重复性更强(代码清单3-7)。
通过提取该特征的部分样本,我们注意到这种数据的共性:每个数字前面都有一个代表船甲板级别的字母(图3-7)。
因此,利用这些数据的方法之一是只取其第一个字母并删除数字部分。这为我们提供了一个有趣的新特征:乘客在哪个甲板层,从该甲板到顶层甲板上的救生艇之间的距离是否与幸存与否有某种关联关系呢?也许有,你可以通过使用我们即将构建出的应用程序找到(代码清单3-8)。
让我们像以前计算重复度那样来运行groupby()和count()函数(代码清单3-9和图3-8)。
结果是,重复次数比先前观察到的要多得多。新的特征应该比以前的特征更具价值。我们将对乘客的name特征做相同的处理,并仅提取称谓:“Mrs.”或“Mr.”等,同时删除其他部分(有关详细信息,请参阅Jupyter Notebook)。
这并不是说原始的自由格式文本(非重复类型的)对建模没有用处。相反,世界上大多数数据都是非结构化的,并且非常富有潜力—你可以想象一下医生的处方单以及商店评价!这些数据可以被建模,但需要更高级的方法,如自然语言处理、奇异向量分解、单词向量等。我们将在本书后面的章节中了解这些技术中的一部分。
3.2.2 从分类型数据创建虚拟特征
一旦确定了分类型特征并转换了需要转换的特征,我们仍然需要将它们转换为数字形式,以便我们的模型可以使用它们。Pandas库中的一个很棒的函数是get_dummies()。它会将每个分类转化为一个列。
下面是一个使用get_dummies()函数的例子,以及它的输出(代码清单3-10和图3-9)。
该函数作用于一个特征之上,将该特征的每个唯一值转化为一个单独列并删除原始值。请记住,并非所有的非连续数字都应该转化为虚拟字段。以美国邮政编码为例。如果你对它们进行这种转化,你将向你的数据集添加额外的43 000个具有极稀疏数据的特征。可见这并不总是一个好主意。对于邮政编码这种情形,更好的方法可能是进行更大的分类分组,如在城镇或州这种级别进行分组建模。
3.3 建模
这是我们的第二个项目,让它尽量保持简单将使我们能够持续地专注于全局,并在构建Web应用程序所涉及的每个部分上花费差不多相同的时间。我们将使用sklearn库中的逻辑回归模型。上一章中,我们使用了线性回归来尝试预测连续型变量。逻辑回归则试图预测二元结果,例如真或者假、开心或伤心等。这是两种非常常见的模型,但你需要使用正确的那个,具体则取决于你尝试建模和预测的结果变量的类型。
3.3.1 训练和测试数据集拆分
利用sklearn中的train_test_split()函数将数据集随机地拆分为两个带随机数种子的数据集。每当测试不同方法时,如果你想为了做公平比较而要确保你使用的是同样的拆分方法时,可以设置random_state参数。test_size参数设置拆分出的测试数据集的大小。在这里,我们将其设置为0.5或50%,因此它将随机地将数据集平均地拆分为训练数据集和测试数据集。我们将使用训练数据集对数据进行建模,并使用测试数据集来评估模型的运行结果。该函数的使用很简单(代码清单3-11和图3-10)。
拆分出的两个数据集分别保存到y_train变量和y_test变量。其中,训练模型只会使用y_train(代码清单3-12)。
3.3.2 逻辑回归
现在到了决定使用什么模型并进行设置的时候了。由于我们要预测二元结果,即乘客会在航行事故中幸存还是遇难,因此逻辑回归是一个很好的轻量级选择—它非常应用。我们需要时刻牢记最终目的(代码清单3-13)。
数据直截了当,幸存模式也众所周知,大多数建模算法都适用。用sklearn逻辑回归模型可以轻松地计算出模型的结果系数,以帮助我们解释哪些特征对于乘客能在泰坦尼克号之旅中幸存下来至关重要(代码清单3-14)。
图3-11显示了在幸存预测中最正面和最负面的影响因子。很明显,在这次事故中,幸存率较高的是女性和富人而不是男性和穷人(请参阅Jyputer Notebook以获得完整的系数列表)。
3.3.3 预测幸存率
一旦有了训练好的模型,我们就可以使用测试数据集来验证模型的准确率了(使用前面提到的train_test_split()函数)。永远不要在训练阶段使用这些数据!只有这样,才能保证它对于模型是全新的,这样才能获得客观的评估效果(代码清单3-15)。
这个模型在测试数据集上的准确率几乎达到了80%。考虑到我们使用的是一个简单的模型和一个小型的数据集,这成绩其实已经很不错了。
我们差不多完成了Python脚本,所以只需要确保可以使用虚构数据进行预测就行了。这是一个重要的步骤,因为我们希望我们的用户能够给出他们自己的数据,并通过训练好了的模型去运行。下面来预测一个50岁三等舱男性乘客的幸存率(代码清单3-16)。
如果你熟悉这个数据集和这个历史事件,你就会知道这个虚构乘客的结果不会很好。在模型上运行该数据,然后使用一个简单的比较图表来显示其结果,该图表显示了这位虚构乘客的幸存率以及所有乘客的平均存活率。男性和三等舱是一个糟糕的组合(图3-12)。
3.4 准备上云
既然已确认我们的模型是有效的了,则可以创建虚构乘客并预测他们的幸存概率了。将代码打包成两个函数并用于我们的Web应用程序。在从独立脚本项目迁移到基于云的环境这一过程中,保持一切干净整洁将减少复杂性和调试难度。
3.4.1 函数startup()
创建一个startup()函数,它负责将数据加载到内存中,运行所有的特征工程,创建虚拟列,并训练模型。只有当Web服务器联机或者重启时,该函数才会被调用一次。将尽可能多的东西预加载到内存中,将为用户与Web应用程序交互提供更快的运行和响应体验。
3.4.2 函数submit_new_profile()
第二个函数是submit_new_profile()。此函数负责处理新虚构乘客的配置,将数据格式化为与真实训练数据相同的格式,创建所需的虚拟列,并调用模型进行预测及产生幸存概率值。
也就这些了,我们所需要的大部分“智能”工作将由这两个函数来负责。所有其余代码都用于在Web服务器和HTML页面之间进行通信和显示结果,并使整个项目看起来很专业。现在,我们要超越自己,来进一步学习Flask。
使用Flask的一个很好的原因是,它允许我们在Python语言环境中将独立的Python脚本函数移植到服务器端Web控件。这使得在模型和Web之间传递数据变得更加容易!
3.4.3 使用HTML表单实现交互
除了Flask之外,另一个关键前端Web技术是“HTML表单”。虽然这是个很基本的东西,但它是用户和我们的应用程序之间的关键连接。HTML表单允许用户与网页上的信息进行交互,然后单击提交按钮,将用户的自定义数据发送回Flask Web服务器(代码清单3-17)。
3.4.4 创建动态图像
在这里,我们使用一种重要的技术将Python中即时创建的图像转换为字符串,这样HTML解释器就可以动态地解释它们了。这是通过“base64”Python模块提供的:
这个模块实现了RFC 3548标准中规定的数据编码和解码。这个标准定义Base16、Base32和Base64算法,用于将任意二进制字符串编码和解码为文本字符串。该文本字符串可以通过电子邮件发送,可用作URL的一部分,以及作为HTTP POST请求中的一部分。编码算法与uuencode程序不同。
在下面经过删减的代码片段中,我们使用matplotlib.pyplot库于Python中创建一个图像(代码清单3-18)。
然后就可以使用Flask Jinja2模板将plot_url变量注入HTLM代码中了(代码清单3-19)。
若查看HTML源代码输出,你将看到HTML图像标签由长字符串组成(在下面图示中被大幅删减了)。解释器会知道如何将其转换为图像(图3-13)。
3.4.5 下载Titanic代码
如果你还没有下载第3章的文件,那现在就请下载它们,并将它们保存在本地机器上。下载完成后,打开命令行窗口,进入web-application文件夹。文件夹结构如代码清单3-20所示。
然后运行“pip install -r”命令来安装所需的Python库(代码清单3-21)。
运行“Python3 main.py”命令。结果将如图3-14所示。
然后将URL“http://127.0.0.1:5000/ ”(你的终端窗口中显示的URL地址)复制到你的浏览器中,你会看到Titanic Web应用程序界面。如果界面没有出现,请在你喜欢的代码编辑器中打开main.py文件(Mac上的sublime和Windows上的notepad ++是我的最爱),并将最后一行的布尔标志修改为“True”。重新运行它,并解决显示出来的所有问题。
图3-15显示了Web应用程序的本地版本。
3.5 部署到谷歌云上
现在,即将进入另一个有趣的阶段,将我们的应用程序部署到无服务器云(serverless cloud)上,让全世界都能看到它!在前言中,我们介绍了Google Standard App Engine(标准应用引擎)。现在,我们将使用Flexible App Engine(灵活应用引擎)来运行更复杂的Python库。
3.5.1 Google App Engine
Google App Engine是无服务器的,因此你无须考虑Web应用程序背后的任何与硬件有关的东西。你不用关心应用程序运行在什么操作系统之上,它将动态地扩展,Google将会负责安全补丁,你只需为你消耗的内容付费。
谷歌云有两种类型的App Engine供你选择:一种非常简单但定制性不强,而另一种则相反。我们在第1章中使用了Standard Environment(标准环境),现在将使用Flexible Environment(灵活环境),因为我们的Web应用程序需要某些Python库(图3-16)。
3.5.2 在Google App Engine上进行部署
在Google App Engine上部署Web应用程序有多种方法。在本章中,我们将使用仪表板本身内置的Shell终端。这种终端可用于很快就能完成的简单部署。对于复杂部署,你需要从本地计算机打开终端会话,并使用相应的身份验证开启与谷歌云账户的连接。其他方式包括将GitHub(或BitBucket)直接链接到你的谷歌云账户;直接使用本地计算机上的终端会话;仪表板中还有一个实验性的代码编辑器(请参阅https://cloudplatform.googleblog.com/2016/10/introducing-Google-Cloud-Shels-new-code-editor.html )。
如果还没有谷歌云账户,你可以访问谷歌云入门教程(https://console.cloud.google.com/getting-started )并设置一个账户。在撰写本书时,Google提供12个月300美元的试用费,以帮助你入门(图3-17)。
第1步:启动谷歌云Shell
登录谷歌云,创建或选择一个App Engine项目。单击右上角的插入符号按钮启动谷歌云Shell命令行工具。这将在GCP仪表板的下半部分打开一个熟悉的命令行窗口(图3-18)。
第2步:压缩所有本地文件并上传到云上
你有很多方法可以完成该步骤:可以逐个上传文件,克隆GitHub存储库,或者将它们压缩到一个zip归档文件中并上传它。我们选择后面一种方法,将11个文件压缩到web-application文件夹中(图3-19)。
使用“Upload file”功能将压缩文件上传到云上(“Upload file”功能在三个垂直点下的Shell窗口的右上侧,如图3-20所示)。
第3步:在谷歌云上创建工作目录并解压缩文件
成功上传文件后,创建一个名为“chapter-3”的新目录,然后将压缩文件移入其中并解压缩这个文件(代码清单3-22)。
第4步:创建lib文件夹
到目前为止,我们的准备工作快做完了。若查看requirements.txt文件,你将看到运行该应用程序所需的一个或多个Python库。当你构建自己的应用程序时,你可以在此文件中列出所需的所有库;然后运行脚本来将它们实际安装到lib文件夹。需要注意的是,Google App Engine的Standard Environment(标准环境)仅支持一组最小的库;对于更复杂的需求,你将需要使用Flexible Environment(灵活环境),这是因为它更接近Python解释器。运行以下命令将所有需要的附加库安装到lib文件夹。部署Web应用程序时,lib文件夹将始终包含这些库文件(代码清单3-23)。
第5步:部署Web应用
最后,使用deploy命令将Web应用部署到云上。会出现一个确认界面(代码清单3-24)。
只需要这一句命令就够了!你可以稍事休息,无服务器工具会负责部署你的网站。因为我们用的是Flexible App Engine,完成部署最多可能需要20分钟。部署全部完成后,它将提供一个可点击的链接,点击它会直接跳转到部署好了的Web应用程序,你也可以使用“browse”命令(代码清单3-25)访问它。
现在,好好享受你的劳动成果吧!你可以设计各种虚构乘客来做实验(图3-21)。
3.5.3 问题排查
如果你遇到任何问题,谷歌云logs功能会是你的得力帮手。你可以在谷歌云用户界面上直接打开它,或者使用logs URL地址(代码清单3-26)。
你还可以在谷歌云Shell中运行“app logs tail”命令,以打开日志流(代码清单3-27)。
3.5.4 收尾工作
最后一件事:在我们完成Web应用程序之前,请不要忘记停止或删除App Engine Cloud实例。即使你使用的是免费信用,也不要浪费金钱或信用。
与标准版相比,Flexible App Engine的情况略有不同,因为Flexible会花费更多的钱。因此,如果你不使用它,请务必停止它。这可以通过谷歌云仪表板很方便地完成。
导航到App Engine,你会看到版本列表。单击你的活动版本,停止它(图3-22)。如果你有多个版本,则可以删除旧版本;你将无法删除默认版本,停止它就可以了(如果你真的不想留下任何痕迹,只需删除整个项目)。
3.6 代码回顾
我们来简单回顾代码中的关键部分。
3.6.1 main.py
main.py文件与我们使用的Jupyter Notebook略有不同。在运行Web应用程序时,最好不要使用过多的依赖。这就是我们不使用任何Pandas代码,而选择使用NumPy数组(代码清单3-28)的原因。
例如,当某用户设计一个新的乘客配置时,这些配置值会被简单地添加到列表中(以正确的顺序)并直接输入逻辑回归模型。这些字段不会被虚拟,因为它是从一开始就以虚拟状态创建了的(我们手动对它们进行了虚拟,见代码清单3-29)。
在这段代码中,cabin特征会被直接存储为整数。这会节省一些处理步骤,例如避免调用Pandas的get_dummies()函数。
3.6.2 app.yaml
许多框架依赖YAML来配置和存储程序设置。app.yaml文件包含各种配置设置,例如将App Engine环境设置为“flex”,将Python启动程序脚本的名称设置为“main”,以及所需硬件的信息(代码清单3-30)。
Google App Engine的Flexible Environment需要至少10 GB磁盘空间。
要了解App Engine的app.yaml文件的更多信息,可以访问https://cloud.google.com/appengine/docs/flexible/python/configuring-your-app-with-app-yaml 。
3.6.3 appengine_config.py文件与lib文件夹
appengine_config.py文件和lib文件夹配合使用,用于处理运行Web应用程序所需的其他Python库(代码清单3-31)。
运行“pip install”命令来创建包含所需Python库的lib文件夹(代码清单3-32)。
如果在运行此命令后查看lib文件夹,你将看到已部署好了的各种Python库。此文件夹将与所有Web应用程序文件一起部署,以使其正常运行。appengine_config.py的功能比我们所介绍的要多得多;有关它的更多详细信息,请参阅官方文档。
提示 要了解有关appengine_config.py的更多信息,请查阅Google文档:https://cloud.google.com/appengine/docs/standard/python/ tools/appengineconfig。
3.6.4 requirements.txt
我们来看看启动和运行泰坦尼克号Web应用程序所需的所有Python库(你的版本号可能会有所不同;代码清单3-33)。
3.7 步骤回顾
我们来回顾一下将泰坦尼克号Web应用部署到谷歌云上的主要步骤。
1)检查在本地运行的Web应用程序,压缩所有Web应用程序文件,在谷歌云上创建目标文件夹,然后解压缩文件。
2)创建lib文件夹。
3)部署Web应用。
4)获取Web应用的URL。
5)最后关闭环境,登录CGP面板,进入App Engine,关闭运行着的版本。
3.8 本章小结
从泰坦尼克号数据集上,我们了解到,富人和女性的幸存率最高,而穷人和男性的幸存率则最低。
尽管这个项目很简单,但本章介绍了许多新概念和新技术。第一个要点是,在开发本地Python概念和模型时,总要为将来移植到云端做些考虑,这包括保持简单、处理直观概念,以及保持代码整洁和高效。