Python-网络科学-一-

60 阅读1小时+

Python 网络科学(一)

原文:annas-archive.org/md5/3df7c5feb0bf40d7b9d88197a04b0b37

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

网络无处不在,渗透在每一件事物中。一旦你学会了如何去看它们,你会发现它们无处不在。自然数据是没有结构的,它是非结构化的。语言就是非结构化数据的一个例子,而网络则是另一个例子。我们时刻都在受到周围事物的影响。我写这本书的目的就是希望展示如何将语言数据转化为可以分析的网络,并进一步展示如何实现这一分析过程。

本书并不打算讲授网络科学的每一个方面,而是提供足够的介绍,让读者能够自信地使用工具和技术,揭示新的见解。

本书适读对象

我写这本书的目的是希望它能成为计算机科学与社会科学之间的桥梁。我希望两者都能受益,而不仅仅是其中一方。我的期望是,软件工程师能像我一样从社会科学中学到有用的工具和技巧,同时社会科学领域的人也能够利用软件工程扩展他们的思想和研究。数据科学家也会发现这本书对于深入网络分析非常有帮助。

然而,这本书不仅对社会科学家、软件工程师和数据科学家有用。影响力无处不在。理解如何衡量影响力或描绘思想流动的方式,在许多领域都是非常有价值的。

本书内容

第一章介绍自然语言处理,介绍了自然语言处理NLP)以及语言与网络之间的关系。我们将讨论 NLP 是什么、它的应用场景,并且讲解几个具体的使用案例。读者在本章结束时将能够对 NLP 有一个基本的理解,了解它是什么,长什么样子,以及如何开始使用 NLP 来研究数据。

第二章网络分析,介绍了网络分析,它对于分析网络的各个方面非常有用,例如网络的整体结构、关键节点、社区和组成部分。我们将讨论不同类型的网络和不同的应用场景,并且会逐步讲解一个简单的网络研究数据收集工作流程。本章对理解网络进行了温和的介绍,并阐述了理解网络如何在解决问题时发挥作用。

第三章实用的 Python 库,讨论了本书中使用的 Python 库,包括安装说明及其他相关内容。本章还提供了一些起始代码,帮助你开始使用库并确保其正常工作。库按照类别进行分组,以便容易理解和比较。我们将讨论用于数据分析和处理、数据可视化、自然语言处理、网络分析与可视化的 Python 库,以及机器学习ML)。本书不依赖图数据库,从而减少了学习网络分析技能的负担。

第四章自然语言处理与网络协同,讨论了如何将文本数据转换为可供分析的网络。我们将学习如何加载文本数据、提取实体(人名、地点、组织等)以及仅通过文本构建社交网络。这使我们能够创建角色和人物在文本中的互动可视化图谱,并且这些图谱可以作为互动方式,让我们更深入地理解所研究的内容或实体的上下文。

在本章中,我们还将讨论爬虫技术,也叫做网页抓取。学习如何从互联网收集数据并在自然语言处理和网络分析中使用它,将大大增强个人学习这些技能的能力。它还解锁了研究自己感兴趣的事物的能力,而不必依赖他人的数据集。

最后,我们将把文本转换为实际的网络并进行可视化。这是一个非常长的章节,因为它涵盖了将文本转换为网络的每一个步骤。

第五章更简便的抓取,展示了从互联网收集文本数据的更简便方法。某些 Python 库专门用于从新闻网站、博客网站和社交媒体中提取文本数据。本章将展示如何轻松快捷地获取干净的文本数据,以便用于后续处理。在本章中,我们将把文本转换为实际的网络,并且进行网络可视化。

第六章图构建与清理,深入探讨了如何处理网络。章节开始时展示了如何使用边列表创建图,然后描述了节点和边等重要概念。在本章中,我们将学习如何向图中添加、编辑和移除节点和边,这一过程是图构建与清理的一部分。我们将在本章的最后模拟一次网络攻击,展示即便移除少数几个关键节点也会对网络产生灾难性破坏的效果。这体现了单个节点对整个网络的重要性。

第七章整体网络分析,是我们真正开始进行网络分析的章节。例如,我们将寻找关于网络规模、复杂性和结构的答案。我们会寻找有影响力和重要的节点,并使用连接组件来识别网络中存在的结构。我们还将简要讨论社区检测,后者将在第九章中进行更深入的探讨。

第八章自我中心网络分析,研究网络中存在的自我中心网络。这意味着我们将更加关注感兴趣的节点(自我节点)以及环绕它们的节点(他者节点)。目标是理解自我节点周围的社会结构。这对于识别个人所属于的社区也很有帮助。

第九章社区检测,讨论了识别网络中存在的社区的几种方法。将展示并讨论几种强大的社区检测算法。我们还将讨论如何使用连接组件来识别社区。

第十章网络数据上的监督式机器学习,展示了我们如何利用网络创建用于机器学习分类任务的训练数据。在本章中,我们将手动创建训练数据,从网络中提取有用的特征。然后,我们将这些特征结合成训练数据,并构建一个分类模型。

第十一章网络数据上的无监督式机器学习,展示了无监督机器学习如何用于创建可以用于分类的节点嵌入。在上一章中,我们手动创建了网络特征。在本章中,我们将展示如何使用 Karate Club 创建节点嵌入,并将其用于分类。我们还将展示如何使用 Karate Club 的 GraphML 模型进行社区检测。

下载示例代码文件

您可以从 GitHub 下载本书的示例代码文件,地址是github.com/PacktPublishing/Network-Science-with-Python。如果代码有更新,将会在 GitHub 仓库中进行更新。

我们还提供了其他代码包,来自我们丰富的书籍和视频目录,您可以在github.com/PacktPublishing/查看。快去看看吧!

使用的约定

本书中使用了若干文本约定。

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

一段代码如下所示:

html, body, #map {
 height: 100%;
 margin: 0;
 padding: 0
}

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

[default]
exten => s,1,Dial(Zap/1|30)
exten => s,2,Voicemail(u100)
exten => s,102,Voicemail(b100)
exten => i,1,Voicemail(s0)

所有命令行输入或输出按以下格式书写:

$ mkdir css
$ cd css

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

提示或重要说明

显示如下。

联系我们

我们欢迎读者的反馈。

一般反馈:如果您对本书的任何内容有疑问,请通过 customercare@packtpub.com 联系我们,并在邮件主题中注明书名。

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

盗版:如果您在互联网上遇到我们作品的非法复制品,请提供该位置地址或网站名称。请通过 copyright@packt.com 联系我们并附上相关链接。

如果您有兴趣成为作者:如果您在某个主题方面具有专业知识,并且有兴趣撰写或参与书籍的编写,请访问authors.packtpub.com

分享您的想法

一旦您阅读了*《使用 Python 进行网络科学》*,我们很希望听到您的想法!请点击这里直接前往亚马逊书评页面并分享您的反馈。

您的反馈对我们以及技术社区非常重要,将帮助我们确保提供优质的内容。

下载本书的免费 PDF 副本

感谢您购买本书!

您喜欢随时随地阅读,但又无法随身携带纸质书籍吗?

您购买的电子书与您选择的设备不兼容吗?

不用担心,现在购买每本 Packt 书籍时,您都可以免费获得该书的无 DRM PDF 版本。

随时随地,在任何设备上阅读。直接将您最喜爱的技术书中的代码搜索、复制并粘贴到您的应用程序中。

福利不仅仅到此为止,您还可以获得独家折扣、时事通讯,并且每天有精彩的免费内容发送到您的邮箱。

按照以下简单步骤获取福利:

  1. 扫描二维码或访问以下链接

packt.link/free-ebook/9781801073691

  1. 提交您的购买凭证

  2. 就是这样!我们将直接通过电子邮件发送您的免费 PDF 和其他福利。

第一部分:自然语言处理与网络的入门

你将获得关于自然语言处理NLP)、网络科学和社交网络分析的基础知识。这些章节的内容旨在为你提供处理语言和网络数据所需的知识。

本节包括以下章节:

  • 第一章介绍自然语言处理

  • 第二章网络分析

  • 第三章有用的 Python 库

第一章:介绍自然语言处理

为什么一本网络分析书籍会从自然语言处理NLP)开始呢?我猜你一定会问这个问题,这个问题问得非常好。原因如下:我们人类用语言和文本来描述周围的世界。我们写关于我们认识的人、我们做的事情、我们去的地方等等。文本可以用来揭示存在的关系。事物之间的关系可以通过网络可视化来展示。我们可以通过网络分析来研究这些关系。

简而言之,文本可以用来提取有趣的关系,网络可以用来进一步研究这些关系。我们将使用文本和 NLP 来识别关系,使用网络分析和可视化来深入了解。

NLP 对于创建网络数据非常有用,我们可以利用这些网络数据来学习网络分析。本书是一个学习 NLP 和网络分析的机会,了解它们如何一起使用。

在以非常高的层次解释 NLP 时,我们将讨论以下主题:

  • 什么是 NLP?

  • 为什么在一本网络分析书中讨论 NLP?

  • NLP 的简短历史

  • NLP 是如何帮助我的?

  • NLP 的常见用途

  • NLP 的高级应用

  • 初学者如何开始学习 NLP?

技术要求

尽管本章有几个地方展示了代码示例,但我并不期望你现在就写代码。这些示例仅用于展示,给你一个预览,看看能做什么。本书的其余部分将非常实践,所以先看一看并理解我在做什么。现在不用担心写代码。首先,学习概念。

什么是 NLP?

NLP 是一组帮助计算机处理人类语言的技术。然而,它不仅仅可以用于处理单词和句子。它还可以处理应用日志文件、源代码或任何其他使用人类文本的地方,甚至是虚构语言,只要文本遵循语言的规则。自然语言是人类说或写的语言。处理是计算机使用数据的行为。所以,NLP就是计算机使用口语或书面的人类语言。这么简单。

作为软件开发人员中的许多人,可能多年来我们都在做 NLP,甚至没有意识到。我就拿我自己的例子来说。我从事网页开发时完全是自学的。在我职业生涯的初期,我建立了一个非常受欢迎的网站,拥有一个不错的社区,于是我从当时流行的 Yahoo Chats 中获得灵感,逆向工程它,并建立了我自己的互联网留言板。它迅速成长,提供了多年的娱乐,并让我结交了几个亲密的朋友。然而,像所有优秀的社交应用一样,恶搞者、机器人以及各种恶劣的用户最终成为了问题,所以我需要一种方式来自动标记和隔离恶意内容。

当时,我创建了包含侮辱性词汇和字符串的列表,这些词汇可以帮助识别虐待行为。我并不想停止所有的脏话,因为我不相信完全控制人们在线发布的文本;然而,我希望识别有毒行为、暴力和其他不良行为。任何拥有评论区的网站都很可能做类似的事情来管理网站,或者应该这样做。关键是,我从职业生涯开始就一直在做 NLP,而我甚至没有意识到,但那时还是基于规则的。

现在,机器学习主导了 NLP 领域,因为我们能够训练模型来检测虐待、暴力,或几乎任何我们能想象的东西,这也是我最喜欢 NLP 的一个原因。我感觉自己的创造力才是唯一的限制。因此,我已经创建了分类器来检测包含极端政治情绪、暴力、音乐、艺术、数据科学、自然科学和虚假信息的讨论,在任何时刻,我通常都有几个 NLP 模型在脑海中,想要构建却还没找到时间。我甚至使用 NLP 来检测恶意软件。但是,NLP 不仅仅是针对书面或口语的,如我的恶意软件分类器所示。如果你记住这一点,你会发现 NLP 的潜在用途会大大扩展。我的经验法则是如果数据中存在可以提取为词语的序列——即使它们本身不是词语——也可以使用 NLP 技术

过去,可能现在也一样,分析师会丢弃包含文本的列,或者进行一些非常基础的转换或计算,例如一热编码、计数,或确定某物是否存在(真/假)。然而,你可以做得更多,我希望这一章和这本书能激发你们的灵感和好奇心,带给你们启发。

为什么在一本网络分析书中讲 NLP?

你们中的大多数人可能是为了学习如何使用 Python 进行应用社会网络分析才购买了这本书。那么,为什么我要讲解 NLP 呢?原因是:如果你熟悉 NLP 并且能够从文本中提取数据,那么这对于创建网络数据并调查文本中提到的事物之间的关系来说,可以非常强大。下面是我最喜欢的书《爱丽丝梦游仙境》中的一个例子,作者是路易斯·卡罗尔。

“从前有三个小妹妹,”睡鼠急匆匆地开始讲,“她们的名字叫 Elsie、Lacie 和 Tillie,她们住在一个井底。”

从这些词语中我们能观察到什么?提到了哪些角色或地方?我们可以看到,睡鼠正在讲述一个关于三姐妹的故事,她们的名字分别是ElsieLacieTillie,并且她们住在一个井底。如果你能以关系的角度来思考,你会发现这些关系是存在的:

  • 三姐妹 -> 睡鼠(他要么认识她们,要么知道一个关于她们的故事)

  • Dormouse -> Elsie

  • Dormouse -> Lacie

  • Dormouse -> Tillie

  • Elsie -> 井底

  • Lacie -> 井底

  • Tillie -> 井底

也很可能这三姐妹彼此都认识,因此出现了额外的关系:

  • Elsie -> Lacie

  • Elsie -> Tillie

  • Lacie -> Elsie

  • Lacie -> Tillie

  • Tillie -> Elsie

  • Tillie -> Lacie

我们的大脑如此高效地构建这些关系图,以至于我们甚至没有意识到自己在这么做。当我读到这三人是姐妹时,我脑海中立刻浮现出这三人彼此认识的画面。

让我们再看一个来自当前新闻故事的例子:奥卡西奥-科尔特斯加大对曼钦批评力度(CNN,2021 年 6 月:edition.cnn.com/videos/politics/2021/06/13/alexandria-ocasio-cortez-joe-manchin-criticism-sot-sotu-vpx.cnn)。

代表亚历山大·奥卡西奥-科尔特斯(纽约州 D)表示,乔·曼钦参议员(西弗吉尼亚州 D)不支持一项房屋投票权法案,受到了该法案广泛改革的影响,旨在限制游说者的角色和“黑暗资金”政治捐赠的影响。

谁被提到,他们之间是什么关系?我们从这段简短的文字中能学到什么?

  • 代表亚历山大·奥卡西奥-科尔特斯在谈论参议员乔·曼钦

  • 两者都是民主党成员

  • 参议员乔·曼钦不支持一项房屋投票权法案

  • 代表亚历山大·奥卡西奥-科尔特斯声称参议员乔·曼钦受到该法案改革的影响

  • 代表亚历山大·奥卡西奥-科尔特斯声称,参议员乔·曼钦受到了“黑暗资金”政治捐赠的影响

  • 可能存在参议员乔·曼钦与“黑暗资金”政治捐赠者之间的关系

我们可以看到,即使是少量的文本,也蕴含了大量的信息。

如果你在处理文本时无法确定关系,我在大学创意写作课上学到,要考虑“W”问题(以及如何),以便在故事中解释事物:

  • 谁:谁参与其中?谁在讲述故事?

  • 什么:在谈论什么?发生了什么?

  • 什么时候:这发生在什么时候?是哪一天的什么时间?

  • 哪里:发生在何处?描述的是哪个地点?

  • 为什么:这为什么重要?

  • 如何:这件事是怎么做的?

如果你提出这些问题,你会注意到事物之间的关系,这对构建和分析网络是基础。如果你能做到这一点,你就能在文本中识别关系。如果你能识别文本中的关系,你就能利用这些知识构建社交网络。如果你能构建社交网络,你就能分析关系,检测重要性,发现弱点,并利用这些知识深入理解你所分析的任何事物。你还可以利用这些知识攻击黑暗网络(犯罪、恐怖主义等)或保护人、地方和基础设施。这不仅仅是洞察力,这些是可操作的洞察力——最好的那种。

这就是本书的要点。将 NLP 与社交网络分析和数据科学相结合,对于获得新的视角来说极为强大。如果你能抓取或获得所需的数据,你将真正深入了解事物之间的关系及其原因。

这就是为什么本章旨在非常简单地解释什么是 NLP,如何使用它,以及它能做什么。但在此之前,让我们稍微了解一下 NLP 的历史,因为这通常被 NLP 书籍所忽略。

一段简短的 NLP 历史

如果你研究 NLP 的历史,你不会找到一个关于其起源的确凿答案。在我为本章制定大纲时,我意识到自己对 NLP 的应用和实现了解颇多,但对其历史和起源却有盲点。我知道它与计算语言学息息相关,但我也不了解计算语言学的历史。关于机器翻译MT)的最早概念据说出现在十七世纪;然而,我对这个说法持深深怀疑态度,因为我敢打赌,人类在语言诞生的同时,就已经在为词汇和字符之间的关系困惑不解。我认为这是不可避免的,因为几千年前的人并非愚笨。他们和我们一样聪明、好奇,甚至可能更为聪明。然而,让我给出一些我挖掘到的关于 NLP 起源的有趣信息。请理解,这并不是完整的历史。关于 NLP 的起源和历史可以写成一本书。所以,为了快速推进,我将简要地列出我发现的一些亮点。如果你想了解更多,这个话题是一个值得深入研究的领域。

有件事让我困惑,那就是我很少看到密码学(密码学和密码分析)被提到作为 NLP 或甚至 MT 的起源之一,然而密码学本身就是将信息转换为乱码,而密码分析则是将密文恢复为有用的信息。因此,对我来说,任何能够辅助进行密码学或密码分析的自动化技术,哪怕是几百年前或几千年前的,也应该是讨论的一部分。虽然它可能不像现代的翻译那样是机器翻译(MT),但它仍然是一种翻译的形式。所以,我认为机器翻译甚至可以追溯到由尤利乌斯·凯撒发明的凯撒密码,甚至更早。凯撒密码通过将文本按一定的数字偏移来将信息转化为代码。举个例子,我们来看这个句子:

我真的很 喜欢 NLP。

首先,我们应该去掉空格和大小写,以免任何窃听者能够猜测出单词的边界。现在字符串如下:

ireallylovenlp

如果我们进行shift-1,每个字母都向右偏移一个字符,那么我们就得到:

jsfbmmzmpwfomq

我们移动的数字是任意的。我们也可以使用反向移动。木棍曾被用来将文本转换为密码,因此我认为它也可以作为一种翻译工具。

在凯撒密码之后,还发明了许多其他复杂的加密技术。一部名为*《密码书》*的杰出作品,由西蒙·辛格(Simon Singh)撰写,深入探讨了几千年来的密码学历史。话虽如此,我们接着讨论人们通常认为与自然语言处理(NLP)和机器翻译(MT)相关的内容。

在十七世纪,哲学家们开始提交关于如何在语言之间建立联系的编码提案。这些提案完全是理论性的,且没有被用于实际机器的开发,但像机器翻译这样的思想最初是通过考虑未来的可能性提出的,随后才考虑其实现。几百年后,在 20 世纪初,瑞士语言学教授费迪南·德·索绪尔(Ferdinand de Saussure)提出了一种将语言作为一个系统来描述的方法。他在 20 世纪初去世,几乎让“语言作为科学”的概念失传,但意识到他的思想重要性后,他的两位同事于 1916 年撰写了《普通语言学教程》(Cours de linguistique generale)。这本书为结构主义方法奠定了基础,该方法起初应用于语言学,后来扩展到了包括计算机在内的其他领域。

最后,在 1930 年代,第一批机器翻译的专利开始申请。

后来,第二次世界大战爆发,这让我开始考虑凯撒密码和密码学作为早期的机器翻译形式。在二战期间,德国使用一种名为恩尼格玛的机器来加密德语信息。该技术的复杂性使得这些密码几乎无法破解,造成了极其严重的后果。1939 年,艾伦·图灵与其他英国密码分析师一道,设计了“炸弹机”(bombe),该机灵感来源于波兰的 bomba,后者在七年前曾用于破解恩尼格玛信息。最终,炸弹机能够破解德国语言密码,剥夺了德国潜艇利用密码保护的秘密优势,拯救了许多生命。这个故事本身非常引人入胜,我鼓励读者了解更多关于破解恩尼格玛密码的努力。

战后,机器翻译(MT)和自然语言处理(NLP)的研究真正起飞。1950 年,艾伦·图灵发布了*《计算机与智能》*,提出了图灵测试作为评估智能的方式。至今,图灵测试常被提及作为衡量人工智能AI)智能水平的标准。

1954 年,乔治敦实验将超过 60 个俄语句子自动翻译成英语。1957 年,诺姆·乔姆斯基的*《句法结构》*通过基于规则的句法结构系统革新了语言学,被称为普遍语法UG)。

为了评估机器翻译(MT)和 NLP 研究的进展,美国国家研究委员会NRC)在 1964 年创建了自动语言处理咨询委员会ALPAC)。与此同时,在麻省理工学院,Joseph Weizenbaum 创造了世界上第一个聊天机器人ELIZA。基于反射技巧和简单的语法规则,ELIZA 能够将任何句子重述为另一句作为对用户的回应。

然后冬天来临了。1966 年,由于 ALPAC 报告的影响,NLP 研究遭遇了停滞,NLP 和机器翻译(MT)的资金被撤销。因此,AI 和 NLP 研究在许多人眼中变成了死胡同,但并非所有人都这么认为。这一停滞期持续到 1980 年代末期,当时一场新的 NLP 革命开始了,推动力来自计算能力的稳步增长和转向机器学习ML)算法,而非硬编码规则。

1990 年代,统计模型在 NLP 领域的流行开始崛起。随后,在 1997 年,长短期记忆网络LSTM)和递归神经网络RNN)模型被引入,它们在 2007 年找到了用于语音和文本处理的应用场景。2001 年,Yoshua Bengio 及其团队提出了第一个前馈神经语言模型。2011 年,苹果的 Siri 成为全球第一个被普通消费者广泛使用的成功 AI 和 NLP 助手之一。

自 2011 年以来,NLP 的研究与发展爆炸性增长,所以我在这里讲到的历史仅到此为止。我相信 NLP 和 MT 的历史中还有很多空白,因此我鼓励你自己做一些研究,深入挖掘那些令你着迷的部分。我大部分职业生涯都在从事网络安全工作,所以我对几乎任何与密码学历史相关的事物都感兴趣,特别是古老的密码学技术。

NLP 如何帮助了我?

我想做的不仅仅是教你如何做某件事,我还想展示它如何帮助你。最简单的解释这种方法如何对你有用的方式,就是解释它如何对我有用。有几件事让我对自然语言处理(NLP)感到非常吸引。

简单文本分析

我非常喜欢阅读,从小就热爱文学,所以当我第一次了解到 NLP 技术可以用于文本分析时,我立刻被吸引住了。即便是像统计书中某个特定单词出现次数这么简单的事,也能引发兴趣并激发好奇心。例如,夏娃,圣经中的第一位女性,在创世纪中提到过多少次?她在整个圣经中被提到多少次?亚当在创世纪中被提到多少次?亚当在整个圣经中被提到多少次?这个例子中,我使用的是《钦定版圣经》。

让我们做个对比:

名称创世纪计数圣经计数
夏娃24
亚当1729

图 1.1 – 亚当与夏娃在圣经中的提及表

这些结果很有趣。即使我们不将《创世纪》故事视为字面上的真相,它仍然是一个有趣的故事,我们常常听到亚当和夏娃的故事。因此,容易假设他们会被同样频繁地提到,但在《创世纪》中,亚当的出现频率是夏娃的八倍多,而在整本圣经中,亚当的出现频率是夏娃的七倍多。理解文学的一部分是建立一个关于文本内容的心智图。对我来说,夏娃出现得如此少有点奇怪,这让我想要调查男性与女性的提及频率,或者研究哪几本圣经书籍中女性角色最多,以及这些故事的内容。如果没有其他的话,这至少激发了我的好奇心,应该会促使我进行更深入的分析和理解。

自然语言处理(NLP)为我提供了从原始文本中提取可量化数据的工具。它使我能够在研究中使用这些可量化的数据,而这些研究在没有这些工具的情况下是不可能完成的。试想一下,如果要手动完成这个过程,逐页阅读每一页而不遗漏任何细节,来得到这些小的计数,可能需要多长时间。而现在,考虑到代码编写完成后,这只花费了我大约一秒钟。这是非常强大的,我可以利用这个功能在任何文本中研究任何人。

社区情感分析

其次,和我刚才提到的观点相关,NLP 提供了调查和分析群体情感与主题的方法。在 Covid-19 大流行期间,一些人群公开表示反对佩戴口罩,传播恐惧和误信息。如果我从这些人群中抓取文本,我可以利用情感分析技术来确定并衡量这些人群在不同话题上的情感共识。我就做了这一点。我能够抓取成千上万条推文,并了解他们对不同话题的真实感受,比如 Covid-19、国旗、第二修正案、外国人、科学等。我为我的一个项目 #100daysofnlp 在 LinkedIn 上做了这个分析 (www.linkedin.com/feed/hashtag/100daysofnlp/),结果非常有启发性。

自然语言处理(NLP)让我们能够客观地调查和分析人群对任何事物的情感,只要我们能够获取文本或音频。许多推特数据是公开发布的,公众可以自由使用。唯一的警告是:如果你打算进行数据抓取,请将你的能力用于正当用途,而非恶意用途。利用这些数据来了解人们在思考什么、感受什么。将其用于研究,而不是监视。

解答以前无法回答的问题

事实上,将这两者联系在一起的是,NLP 帮助我回答那些以前无法回答的问题。在过去,我们可以讨论人们的感受以及为什么这样,或描述我们阅读过的文学作品,但只能停留在表面层次。通过本书中我要向你展示的内容,你将不再局限于表面。你将能够快速绘制出几千年前发生的复杂关系,并能够深入分析任何关系,甚至是关系的演变。你将能够将这些相同的技术应用于任何类型的文本,包括转录音频、书籍、新闻文章和社交媒体帖子。自然语言处理打开了一个未被开发的知识宇宙。

安全与保障

2020 年,Covid-19 疫情席卷全球。当疫情爆发时,我担心许多人会失去工作和住所,我害怕世界会失控,陷入彻底的无政府状态。虽然情况变得严峻,但我们并没有看到武装帮派在全球各地袭击城镇和社区。紧张局势加剧,但我希望找到一种方法,可以实时监控我所在地区的暴力事件。因此,我从我所在地区的多个警察账户上抓取了推文,因为他们几乎实时报告各种犯罪事件,包括暴力。我创建了一个包含暴力与非暴力推文的数据集,其中暴力推文包含诸如枪击、刺伤等暴力相关的词汇。然后,我训练了一个机器学习分类器,用来检测与暴力相关的推文。通过这个分类器的结果,我可以随时了解我所在地区的暴力情况。我可以关注任何我想关注的内容,只要我能获取文本,但了解我所在地区的街头暴力状况可以为我提供警示或安慰。再次强调,将原本仅限于感觉和情感的内容转化为可量化的数据是非常强大的。

自然语言处理的常见用途

我最喜欢自然语言处理(NLP)的一点是,你的主要限制就是你的想象力和你能用它做的事情。如果你是一个富有创造力的人,你将能够提出许多我没有解释的想法。

我将解释一些我发现的自然语言处理常见用途。虽然其中一些可能通常不会出现在 NLP 书籍中,但作为一个终身程序员,每当我想到 NLP 时,我自然会想到任何与字符串相关的编程工作,而字符串就是字符的序列。例如,ABCDEFG 是一个字符串A 是一个字符

注意事项

请现在不要担心编写代码,除非你只是想用一些自己的数据进行实验。本章中的代码仅用于展示可能实现的功能以及代码可能的样子。我们将在本书的后续章节深入探讨实际代码。

真/假 – 存在/不存在

这可能不完全符合 NLP 的严格定义,但它经常出现在任何文本操作中,这在 NLP 中的机器学习中也会发生,那里使用了独热编码(one-hot encoding)。在这里,我们严格关注某个事物的存在与否。例如,如我们在本章之前看到的,我想计算亚当和夏娃在《圣经》中出现的次数。我也可以写一些简单的代码,来确定亚当和夏娃是否出现在《圣经》中,或者是否出现在《出埃及记》中。

对于这个例子,让我们使用我已经设置好的这个 DataFrame:

图 1.2 – 包含《圣经》全书金句版文本的 pandas DataFrame

图 1.2 – 包含《圣经》全书的金句版文本的 pandas DataFrame

我特别想查看夏娃是否作为df['entities']中的一个实体存在。我希望将数据保存在 DataFrame 中,因为我会用到它,所以我会在entities字段上进行一些模式匹配:

check_df['entities'].str.contains('^Eve$')
0        False
1        False
1        False
2        False
3        False
         ...
31101    False
31101    False
31102    False
31102    False
31102    False
Name: entities, Length: 51702, dtype: bool

在这里,我使用了所谓的^符号,表示E在夏娃中的位置位于字符串的最开始,$表示e在字符串的末尾。这确保了存在一个名为 Eve 的实体,且前后没有其他字符。使用正则表达式,你可以获得比这更大的灵活性,但这是一个简单的例子。

在 Python 中,如果你有一个TrueFalse值的系列,.min()会给你False.max()会给你True,这也有道理,因为从另一个角度来看,TrueFalse分别是10,而1大于0。虽然有其他方法可以做到这一点,但我打算用这种方式。所以,为了查看《圣经》是否至少提到一次夏娃,我可以做如下操作:

check_df['entities'].str.contains('^Eve$').max()
True

如果我想查看亚当是否在《圣经》中,可以将Eve替换为Adam

check_df['entities'].str.contains('^Adam$').max()
True

检测文本中某个事物的存在或不存在是很有用的。例如,如果我们想快速获取一份关于夏娃的圣经经文列表,可以这样做:

check_df[check_df['entities'].str.contains('^Eve$')]

这将给我们一个包含提及夏娃的圣经经文的 DataFrame:

图 1.3 – 包含严格提及夏娃的圣经经文

图 1.3 – 包含严格提及夏娃的圣经经文

如果我们想要得到关于诺亚的经文列表,可以这样做:

check_df[check_df['entities'].str.contains('^Noah$')].head(10)

这将给我们一个包含提及诺亚的圣经经文的 DataFrame:

图 1.4 – 包含严格提及诺亚的圣经经文

图 1.4 – 包含严格提及诺亚的圣经经文

我已经添加了.head(10),只查看前十行。对于文本,我经常发现自己想要查看更多内容,而不是默认的五行。

如果我们不想使用entities字段,我们也可以改为在文本中查找。

df[df['text'].str.contains('Eve')]

这将给我们一个包含提到夏娃的圣经经文的 DataFrame。

图 1.5 – 包含提及夏娃的圣经经文

图 1.5 – 包含提及夏娃的圣经经文

这就是问题变得有些复杂的地方。我已经做了一些繁重的工作,提取了该数据集中的实体,稍后我会展示如何做到这一点。当你处理原始文本时,正则表达式(regex)和模式匹配可能会很麻烦,正如前面图示所示。我只想要包含“Eve”的诗句,但结果却匹配了evenevery等词。这不是我想要的。

任何处理文本数据的人都会想要学习正则表达式的基础知识。不过,别担心。我已经使用正则表达式超过二十年了,但我仍然经常需要在谷歌上搜索来确保正则表达式正确工作。我会再次讲解正则表达式,但我希望你能明白,判断一个词是否存在于字符串中其实是相当简单的。举个更实际的例子,如果你有 40 万个抓取的推文,而你只对那些关于特定主题的推文感兴趣,你可以轻松地使用前面提到的技巧或正则表达式来查找精确匹配或相近匹配。

正则表达式(regex)

我在前一部分简要解释了正则表达式,但它的用途远不止于此,远比仅仅用来判断某些东西的存在或不存在。例如,你还可以使用正则表达式从文本中提取数据,以丰富你的数据集。我们来看一个我抓取的数据科学动态:

图 1.6 – 抓取的数据科学 Twitter 动态

图 1.6 – 抓取的数据科学 Twitter 动态

这些文本字段中包含很多有价值的信息,但以当前形式处理起来很困难。如果我只想要每天发布的链接列表怎么办?如果我想看到数据科学社区使用的标签怎么办?如果我想把这些推文拿去构建一个社交网络来分析谁在互动呢?我们应该做的第一件事是通过提取我们需要的内容来丰富数据集。所以,如果我想创建包含标签、提及和网址列表的三个新字段,我可以这样做:

df['text'] = df['text'].str.replace('@', ' @')
df['text'] = df['text'].str.replace('#', ' #')
df['text'] = df['text'].str.replace('http', ' http')
df['users'] = df['text'].apply(lambda tweet: [token for token in tweet.split() if token.startswith('@')])
df['tags'] = df['text'].apply(lambda tweet: [token for token in tweet.split() if token.startswith('#')])
df['urls'] = df['text'].apply(lambda tweet: [token for token in tweet.split() if token.startswith('http')])

在前面三行中,我在每个提及、标签和网址后面加了一个空格,以便为分割提供一些空间。在接下来的三行中,我通过空格分割每条推文,然后应用规则来识别提及、标签和网址。在这种情况下,我没有使用复杂的逻辑。提及以@开头,标签以#开头,网址以 HTTP(包括 HTTPS)开头。这个代码的结果是,我最终得到了三列额外的数据,分别包含用户、标签和网址的列表。

如果我对用户、标签和网址使用explode(),我将得到一个每个用户、标签和网址都有自己一行的 DataFrame。以下是explode()后的 DataFrame 样式:

图 1.7 – 抓取的数据科学 Twitter 动态,已增加用户、标签和网址

图 1.7 – 抓取的数据科学 Twitter 动态,已增加用户、标签和网址

然后,我可以使用这些新列来获取使用过的独特标签列表:

sorted(df['tags'].dropna().str.lower().unique())
['#',
 '#,',
 '#1',
 '#1.',
 '#10',
 '#10...',
 '#100daysofcode',
 '#100daysofcodechallenge',
 '#100daysofcoding',
 '#15minutecity',
 '#16ways16days',
 '#1bestseller',
 '#1m_ai_talents_in_10_years!',
 '#1m_ai_talents_in_10_yrs!',
 '#1m_ai_talents_in_10yrs',
 '#1maitalentsin10years',
 '#1millionaitalentsin10yrs',
 '#1newrelease',
 '#2'

很明显,我在数据丰富过程中使用的正则表达式并不完美,因为标点符号不应该包含在标签中。这是需要修正的地方。请注意,处理人类语言是非常混乱的,很难做到完美。我们只需要坚持不懈,才能准确地得到我们想要的结果。

让我们看看独特的提及是怎样的。所谓独特的提及,指的是推文中去重后的单独账户:

sorted(df['users'].dropna().str.lower().unique())
['@',
 '@027_7',
 '@0dust_himanshu',
 '@0x72657562656e',
 '@16yashpatel',
 '@18f',
 '@1ethanhansen',
 '@1littlecoder',
 '@1njection',
 '@1wojciechnowak',
 '@20,',
 '@26th_february_2021',
 '@29mukesh89',
 '@2net_software',

这看起来好多了,不过@符号不应该单独出现,第四个看起来有些可疑,其中一些看起来像是被错误地作为提及,而本应作为标签使用。这可能是推文文本的问题,而不是正则表达式的问题,但值得进一步调查。

我喜欢将提及和标签转换为小写字母,这样更容易找到独特的标签。这通常作为预处理 用于自然语言处理(NLP)

最后,让我们获取提到的独特网址列表(这些可以进一步用于抓取):

sorted(df['urls'].dropna().unique())
['http://t.co/DplZsLjTr4',
 'http://t.co/fYzSPkY7Qk',
 'http://t.co/uDclS4EI98',
 'https://t.co/01IIAL6hut',
 'https://t.co/01OwdBe4ym',
 'https://t.co/01wDUOpeaH',
 'https://t.co/026c3qjvcD',
 'https://t.co/02HxdLHPSB',
 'https://t.co/02egVns8MC',
 'https://t.co/02tIoF63HK',
 'https://t.co/030eotd619',
 'https://t.co/033LGtQMfF',
 'https://t.co/034W5ItqdM',
 'https://t.co/037UMOuInk',
 'https://t.co/039nG0jyZr'

这看起来很干净。我能够提取多少个网址?

len(sorted(df['urls'].dropna().unique()))
19790

这有很多链接。由于这是 Twitter 数据,很多网址通常是照片、自拍、YouTube 链接和其他一些对研究者可能不太有用的内容,但这是我的抓取数据科学信息流,它从数十个与数据科学相关的账户中获取信息,所以这些网址很可能包含一些令人兴奋的新闻和研究成果。

正则表达式(Regex)允许你从数据中提取额外的数据,并使用它来丰富数据集,以便进行更简单或进一步的分析。如果你提取了网址,可以将其作为额外抓取的输入。

我不会对正则表达式做一个长篇的讲解。关于这个话题有很多专门的书籍。最终,你很可能需要学习如何使用正则表达式。对于本书中的内容,前面提到的正则表达式大概是你所需要的全部,因为我们使用这些工具来构建可以分析的社交网络。本书的核心并不是关于 NLP 的,我们只是在创建或丰富数据时使用一些 NLP 技术,其他部分则主要依赖网络分析。

词频统计

词频统计也很有用,特别是当我们想要进行对比时。例如,我们已经比较了《圣经》中亚当和夏娃被提及的次数,但如果我们想要看到所有实体在《圣经》中被提及的次数该怎么办呢?我们可以用简单的方法来做,也可以用 NLP 方法来做。我个人偏好尽可能使用简单的方法,但很多时候,NLP 或图形方法反而变成了更简单的方式,所以要学会所有的方法,再根据情况做选择。

我们将用简单的方法来做,计算实体被提及的次数。我们再次使用数据集,并进行一些聚合操作,看看在《圣经》中最常被提及的人物是谁。记住,我们可以对任何我们抓取的内容执行这个操作,只要我们已将数据集丰富到包含提及列表。但在这个示范中,我将使用《圣经》。

在第三行,我保留了名字长度超过两个字符的实体,有效地去除了那些最终出现在数据中的无关实体。我使用的是这个过滤器:

check_df = df.explode('entities')
check_df.dropna(inplace=True) # dropping nulls
check_df = check_df[check_df['entities'].apply(len) > 2] # dropping some trash that snuck in
check_df['entities'] = check_df['entities'].str.lower()
agg_df = check_df[['entities', 'text']].groupby('entities').count()
agg_df.columns = ['count']
agg_df.sort_values('count', ascending=False, inplace=True)
agg_df.head(10)

这将在下面的 DataFrame 中显示:

图 1.8 – 整个圣经中实体计数

图 1.8 – 整个圣经中实体计数

这看起来很不错。实体是指人、地点和事物,唯一不同的地方是词汇thou。之所以会出现它,是因为在圣经中,thou这个词通常会被大写为Thou,而在进行实体识别和提取时,它会被标记为NNP专有名词)。然而,thou指的是You,因此也能理解。例如,Thou shalt not kill, thou shalt not steal

如果我们有这样的数据,我们也可以非常容易地进行可视化,帮助我们从不同的角度理解:

agg_df.head(10).plot.barh(figsize=(12, 6), title='Entity Counts by Bible Mentions', legend=False).invert_yaxis()

这将给我们一个水平条形图,显示实体计数:

图 1.9 – 整个圣经中实体计数的可视化

图 1.9 – 整个圣经中实体计数的可视化

显然,这不仅限于圣经中的应用。如果你有任何你感兴趣的文本,你可以使用这些技术来构建更深的理解。如果你想将这些技术用于艺术创作,你可以。如果你想使用这些技术来帮助打击犯罪,你也可以。

情感分析

这是我在所有 NLP 技术中最喜欢的一项。我想知道人们在谈论什么,他们对这些事物的感受如何。这是 NLP 中一个经常被低估的领域,如果你关注大多数人如何使用它,你会看到很多关于如何构建分类器的展示,这些分类器可以确定积极或消极的情感。然而,我们人类很复杂。我们不仅仅是开心或悲伤。有时,我们是中立的。有时,我们大部分是中立的,但更多的是积极而非消极。我们的感情是微妙的。我用过的一本书为我自己的情感分析教育和研究提供了很多帮助,书中提到了一项研究,讲述了人类情感被划分为主要、次要和三级情感(Liu, Sentiment Analysis, 2015,第 35 页)。这里有几个例子:

主要情感次要情感三级情感
愤怒反感蔑视
愤怒嫉妒妒忌
恐惧惊悚警觉
恐惧紧张焦虑
喜爱崇拜
欲望渴望

图 1.10 – 主要、次要和三级情感的表格

有一些主要情感,更多的是次要情感,而更多的是三级情感。情感分析可以用来尝试分类任何情感的“是”或“否”,只要你有训练数据。

情感分析并不仅仅用于检测情感。这些技术也可以用于分类,因此我觉得情感分析这个术语并不完全准确,也许这就是为什么有这么多人只是简单地从 Yelp 和 Amazon 评论中检测积极/消极情感的原因。

我有一些更有趣的情感分类应用。目前,我使用这些技术来检测有毒言论(真正的辱骂性语言)、积极情感、消极情感、暴力、好消息、坏消息、问题、虚假信息研究和网络科学研究。你可以将其视为一种智能模式匹配,它能够学习关于某个话题的文本通常是如何被写作的。例如,如果我们想捕捉关于虚假信息的推文,我们可以训练一个关于虚假信息、误导信息和假新闻的文本模型。在训练过程中,模型会学习到其他相关术语,能够比任何人都更快速、更精准地捕捉到它们。

情感分析和文本分类建议

在我进入下一部分之前,这里有一些建议:对于情感分析和文本分类,在很多情况下,你不需要神经网络来处理这么简单的任务。如果你正在构建一个检测仇恨言论的分类器,使用“词袋”方法进行预处理,再加上一个简单的模型进行分类就足够了。总是从简单的开始。如果你努力训练,神经网络可能会提高几个百分点的准确率,但它需要更多时间,且可解释性较差。一个 linearsvc 模型可以在瞬间训练完成,并且通常能达到同样的效果,有时甚至更好,你也应该尝试一些其他简单的模型和技术。

另一个建议是:试验停用词去除,但不要仅仅因为别人告诉你要去除停用词就去除它们。有时候它有帮助,有时候却会对模型产生不利影响。大多数时候,它可能有帮助,但这足够简单,可以进行实验。

此外,在构建数据集时,如果你对句子进行情感分析,而不是对大块文本进行分析,通常能得到最佳效果。假设我们有以下文本:

今天我早早醒来,喝了些咖啡,然后出去查看花朵。天空湛蓝,是一个温暖的六月早晨。然而,当我回到屋子里时,我发现水管漏水,整个厨房被淹了。接下来的一整天都很糟糕。我现在真是太生气了。

你认为第一句话的情感和最后一句话的情感是完全一样的吗?这个虚构故事的情感变化很大,从一开始的愉快和积极到最后的灾难和愤怒。如果你在句子层面进行分类,你能做到更精确。然而,即便如此,这也并不完美。

今天一开始一切都很完美,但最后一切都崩溃了,我现在真是太生气了。

那个句子的情感是什么?是积极的还是消极的?它其实是两者都有。因此,理想情况下,如果你能够捕捉到一个句子同时表现出多种情感,那将会非常强大。

最后,在构建模型时,你总是可以选择是否构建二分类或多分类的语言模型。根据我自己的使用经验以及一些与我产生共鸣的研究,通常最简单的是构建一些小模型,只需要检查某些内容是否存在。因此,与其构建一个神经网络来判断文本是积极、消极还是中立的,不如构建一个模型检查“积极”与其他情感的对比,另一个模型检查“消极”与其他情感的对比。

这看起来可能会更费力,但我发现这做起来要快得多,可以使用非常简单的模型,而且这些模型可以组合在一起,寻找各种不同的信息。例如,如果我想分类政治极端主义,我可以使用三个模型:有毒语言、政治和暴力。如果一段文本被分类为有毒语言,属于政治话题,并且倡导暴力,那么这篇内容的发布者可能表现出一些危险的特征。如果只显示有毒语言和政治情感,那很常见,通常不具备极端政治或危险性。政治讨论往往带有敌意。

信息提取

我们已经在之前的示例中做了一些信息提取,所以我会简要说明。在前一部分中,我们提取了用户提及、话题标签和网址。这些操作是为了丰富数据集,使进一步的分析变得更加容易。我将这些提取步骤直接加到我的抓取程序中,这样我在下载新数据时就能立即获得用户、提及和网址的列表。这让我可以立即开始网络分析或调查最新的 URL。基本上,如果你正在寻找某些信息,并且你找到了一种可以反复从文本中提取信息的方法,而你发现自己在不同数据集上重复做这些步骤,那么你应该考虑将这些功能加入到你的抓取程序中。

最丰富的、提升我 Twitter 数据集的数据来自两个字段:发布者用户发布者是发布推文的账号。用户是发布者提到的账号。我的每个信息流中都有几十个发布者。有了发布者和用户,我可以从原始文本中构建社交网络,本书中将对此进行详细解释。这是我发现的一项最有用的技巧,你也可以利用这些结果去寻找其他有趣的账号进行抓取。

社区检测

社区检测通常不会在自然语言处理(NLP)中提及,但我认为它应该被提到,尤其是在使用社交媒体文本时。例如,如果我们知道某些标签(hashtag)被特定群体使用,我们可以通过他们使用的标签检测到其他可能与这些群体有联系或支持这些群体的人。利用这一点进行人群研究非常简单。只需抓取一堆数据,查看他们使用的标签,然后搜索这些标签。提及也可以给你提供其他账户的信息,供你进一步抓取。

社区检测通常在社交网络分析中提到,但它也可以非常容易地通过 NLP 实现,我也曾使用话题建模和上述方法来进行社区检测。

聚类

聚类是无监督学习中常见的一种技术,也常用于网络分析。在聚类中,我们是在寻找与其他事物相似的事物。做这项工作的方式有很多种,甚至 NLP 的话题建模也可以作为一种聚类方式。在无监督机器学习中,你可以使用像 k-means 这样的算法,找到与其他推文、句子或书籍相似的推文、句子或书籍。你也可以使用话题建模,利用 TruncatedSVD 来做类似的事情。或者,如果你有一个实际的社交图(社交网络图),你可以查看连接的组件,看看哪些节点是连接的,或者应用 k-means 算法来分析某些网络度量(我们稍后会深入讨论),看看哪些节点具有相似的特征。

NLP 的高级应用

你日常进行的大部分 NLP 工作可能都会属于较为简单的应用,但我们也来讨论一些高级应用。在某些情况下,我所描述的高级应用实际上是将多个简单应用结合起来,提供更复杂的解决方案。所以,让我们讨论一些更高级的应用,比如聊天机器人和对话代理、语言建模、信息检索、文本摘要、话题发现和建模、文本转语音和语音转文本、机器翻译(MT)以及个人助手。

聊天机器人和对话代理

聊天机器人是能够与用户进行对话的程序。这类程序已经存在多年,最早的聊天机器人出现在 20 世纪 60 年代,但它们一直在不断改进,现在已成为一种有效的工具,用于将用户引导到更具体的客户支持形式。例如,如果你进入一个网站的支持部分,可能会看到一个小的聊天框弹出,里面写着类似“今天我们能为您提供什么帮助?”的话。你可能会输入“我想偿还我的信用卡余额。”当应用程序收到你的答案时,它就能用这个信息来判断你需要哪种支持形式。

虽然聊天机器人是为处理人类文本而构建的,但对话代理可以处理语音音频。Siri 和 Alexa 就是对话代理的例子。你可以与它们对话并提问。

然而,聊天机器人和对话代理不仅限于文本;当我们用电话拨打公司电话时,也经常遇到类似的电话交换机。我们会接到一系列相同的问题,可能需要回答一个单词或输入数字。因此,在后台,如果涉及语音,就会有一个语音转文本的转换元素。同时,应用程序还需要确定用户是在提问还是在陈述,因此很可能还会涉及文本分类。

最后,为了提供答案,文本摘要可以将搜索结果转化为简洁的陈述,以文本或语音的形式返回给用户,完成交互。

然而,聊天机器人不仅仅是简单的问答系统。我认为它们将成为我们与文本互动的有效方式。例如,你可以围绕《爱丽丝梦游仙境》这本书(或者圣经)构建一个聊天机器人,以回答有关这本书的具体问题。你还可以根据自己的私人信息构建一个聊天机器人,与自己对话。在这里,有很多创造空间。

语言建模

语言建模关注的是在给定一系列单词的情况下,预测下一个单词。例如,接下来会是什么:“The cow jumped over the ______.” 或者:“Green eggs and _____.”如果你去 Google 并开始在搜索栏中输入,你会注意到下一个预测的单词会显示在下拉列表中,以加速你的搜索。

我们可以看到,Google 预测下一个单词是ham,但它也在查找与已输入文本相关的查询。这看起来像是语言建模与聚类或主题建模的结合。它们在你输入之前就预测了下一个单词,甚至还进一步寻找与你已输入文本相关的其他查询。

数据科学家还可以将语言建模作为生成模型创建的一部分。在 2020 年,我用数千行圣诞歌曲的歌词训练了一个模型,并训练它写圣诞诗。结果虽然粗糙且幽默,因为我只花了几天时间,但它能够以种子文本为基础,利用这些文本生成整首诗。例如,种子文本可以是“铃儿响叮当”,然后模型会不断根据之前的文本生成诗句,直到达到单词和行数的限制。以下是我最喜欢的那首:

youre the angel
and the manger is the king
of kings and sing the sea
of door to see to be
the christmas night is the world
and i know i dont want
to see the world and much
see the world is the sky
of the day of the world
and the christmas is born and
the world is born and the
world is born to be the
christmas night is the world is
born and the world is born
to you to you to you
to you to you to a
little child is born to be
the world and the christmas tree
is born in the manger and
everybody sing fa
la la la la la la
la la la la la la
la la la la la la
la la la la la la
la la la la la la

我构建了一个生成过程,从训练数据中的任意一行随机选取一个单词作为开头。接着,它会生成由 6 个单词组成的句子,直到完成 25 行。我只训练了 24 小时,因为我希望能在圣诞节前迅速完成这项工作。有几本关于创建生成模型的书籍,如果你想利用人工智能来增强你的创造力,我强烈建议你了解一下它们。这更像是与模型的合作,而不是用模型来取代我们自己。

现如今,生成式文本模型变得相当令人印象深刻。ChatGPT——2022 年 11 月发布——凭借其回答大多数问题并提供看似现实的答案的能力,吸引了众多人的关注。这些答案并不总是正确的,因此生成模型仍有很长的路要走,但如今关于生成模型的讨论热度很高,人们也在考虑如何将它们应用到自己的工作和生活中,以及它们对我们未来的意义。

文本摘要

文本摘要几乎不言自明。其目标是将文本作为输入,返回一个摘要作为输出。当你需要管理成千上万或数百万篇文档,并希望提供关于每篇文档的简明概述时,这项技术非常有力。它本质上类似于你在学术文章中找到的“摘要”部分。许多细节会被去除,最终只保留核心内容。

然而,这并不是一种完美的艺术,因此请注意,如果使用此方法,算法可能会舍弃文本中的重要概念,而保留那些较不重要的部分。机器学习并不完美,因此请时刻关注结果。

然而,这个方法更适用于返回简短摘要,而非搜索。你可以使用主题建模和分类来判断文档的相似性,再利用这些信息来总结最终的文档集。

如果你将整本书的内容输入到文本摘要算法中,我希望它能够捕捉到自然语言处理(NLP)与网络分析的结合是强大且人人可及的。你不需要成为天才才能使用机器学习、自然语言处理或社交网络分析。我希望这本书能激发你的创造力,使你在解决问题和批判性思考方面更加高效。文中有许多重要的细节,但这就是其本质。

主题发现与建模

主题发现与建模非常类似于聚类。这在潜在语义索引LSI)中得到应用,它对于识别文本中存在的主题(topic)非常有效,并且还可以作为文本分类的一个有效预处理步骤,使得模型能够根据上下文而非单纯的词语进行训练。此前我在聚类社区检测小节中提到过,这一方法如何根据用户在其账户描述中使用的词汇和标签来识别社区内的细微群体。

比如,主题建模会在主题中找到相似的字符串。如果你对政治新闻和社交媒体帖子进行主题建模,你会注意到,在这些主题中,类似的事物往往会聚集在一起。词语会和其他相似的词汇出现在一起。例如,2A 可能会写作 第二修正案USA 可能会写作其扩展形式(美利坚合众国)等等。

语音合成与语音识别转换

这种类型的自然语言处理(NLP)模型旨在将文本转换为语音音频,或将语音转换为文本记录。然后,这些文本可以作为输入用于分类或对话代理(聊天机器人、个人助理)。

我的意思是,你不能仅仅将音频输入到文本分类器中。此外,如果没有任何语言分析组件,仅凭音频捕捉上下文也是困难的,因为人们说话时会使用不同的方言、语气等等。

第一步通常是将音频转录为文本,然后分析文本本身。

机器翻译(MT)

从自然语言处理的历史来看,我认为可以安全地说,从语言 A 翻译到语言 B 可能在人类开始与使用不同语言的其他人互动时,就已经成为人类的心思。例如,圣经中甚至有关于巴别塔的故事,说在塔被摧毁时,我们失去了彼此理解对方语言的能力。机器翻译有着许多有用的应用,不仅在合作、保密性上,在创造力方面也有重要意义。

比如,对于合作来说,你需要能够共享知识,即使团队成员之间不共享相同的语言。事实上,这在任何需要共享知识的地方都非常有用,所以你经常会在社交媒体帖子和评论中看到 查看翻译 的链接。今天,机器翻译(MT)几乎已经完美了,尽管偶尔会有一些有趣的错误。

在安全领域,你需要知道敌人正在计划什么。如果你根本无法理解敌人正在说什么或打算做什么,那么间谍活动可能就毫无意义。翻译是一项专业技能,当涉及人工翻译时,这通常是一个漫长且手动的过程。机器翻译可以大大加快分析速度,因为另一种语言可以被迅速翻译成你自己的语言。

对于创造力来说,将文本从一种语言转化为自己创造的语言是多么有趣啊?这是完全可行的。

由于机器翻译和文本生成的重要性,庞大的神经网络已经被训练来处理文本生成和机器翻译。

个人助理

我们大多数人可能已经知道像 Alexa 和 Siri 这样的个人助理,它们已经成为我们生活中的重要组成部分。我猜我们将变得更加依赖这些助手,最终,我们会像在老电视节目 霹雳游侠 中一样与我们的汽车对话(该节目于 1982 年至 1986 年间播出)。“嘿,车子,带我去超市”可能会像“嘿 Alexa,明天的天气怎么样?”一样常见。

个人助手结合了前面提到的几种 NLP 技术。它们可能使用分类技术来判断你的查询是一个问题还是一个陈述。然后,它们可能会在互联网上搜索与你的问题最相关的网页内容。接着,它可以从一个或多个结果中提取原始文本,再使用摘要技术来构建简洁的答案。最后,它会将文本转化为语音,并将答案反馈给用户。

个人助手结合了前面提到的几种 NLP 技术:

  1. 它们可能使用分类技术来判断你的查询是一个问题还是一个陈述。

  2. 然后,它们可能会在互联网上搜索与你的问题最相关的网页内容。

  3. 它们可以从一个或多个结果中提取原始文本,再使用摘要技术来构建简洁的答案。

  4. 最后,它们会将文本转化为语音,并将答案反馈给用户。

我对个人助手的未来感到非常兴奋。我很想拥有一个可以与之对话的机器人和汽车。我认为,创造力可能是我们在创建不同类型的个人助手或它们所使用的模块时唯一的限制。

初学者如何入门自然语言处理(NLP)?

如果我们最终不深入探讨如何使用这些工具和技术,这本书将几乎没有什么用处。我在这里描述的常见和高级应用只是其中的一部分。当你对 NLP 感到熟悉时,我希望你不断考虑其他可能尚未得到满足的 NLP 应用。例如,仅仅在文本分类方面,你就可以深入探讨。你可以使用文本分类技术尝试分类更复杂的概念,比如讽刺或共情,但我们暂时不提前讨论这些。这是我希望你做的事。

从一个简单的想法开始

简单地思考,只有在需要时才增加复杂性。想想有什么事情是你感兴趣的,并且想要了解更多的,然后找到讨论这个话题的人。如果你对摄影感兴趣,找几个讨论摄影的 Twitter 账号。如果你想分析政治极端主义,找一些在 Twitter 上公开展示其统一标签的账号。如果你对花生过敏研究感兴趣,找一些研究人员的 Twitter 账号,他们发布自己的研究成果和文章,努力挽救生命。我之所以反复提到 Twitter,是因为它是一个研究人们如何讨论问题的宝贵资源,且人们常常会发布链接,这可能引导你进一步抓取更多内容。但你也可以使用任何社交媒体平台,只要它能被抓取。

然而,从一个非常简单的想法开始。你想了解一段文本(或大量的推文)中的什么内容?你想了解一个社区的人们的什么情况?头脑风暴一下。拿出一个笔记本,开始写下你脑海中浮现的每一个问题。给它们排个优先级。然后,你就会有一份问题清单,去寻找答案。

例如,我的研究问题可能是:“人们在说关于黑人的命也是命抗议的事情吗?”或者,我们也可以研究一些不那么严肃的话题,问:“人们在说关于最新的漫威电影的事吗?” 个人来说,我更倾向于至少尝试用数据科学为善,去让世界变得稍微安全一些,所以我对电影评论不太感兴趣,但别人可能会。每个人都有自己的偏好。研究你感兴趣的内容。

对于这个演示,我将使用我抓取的数据科学信息流。我有一些初步的问题:

  • 哪些账户每周发布最多?

  • 哪些账户被提及最多?

  • 这个群体的人们主要使用哪些标签?

  • 在回答完这些问题后,我们可以想到哪些后续问题?

我们将只使用自然语言处理(NLP)和简单的字符串操作来回答这些问题,因为我还没有开始讲解社交网络分析。我还假设你熟悉 Python 编程,并且熟悉 pandas 库。我将在后面的章节中更详细地讲解 pandas,但不会进行深入的培训。有一些很棒的书籍可以深入讲解 pandas。

这是我抓取的数据科学信息流的原始数据样式:

图 1.11 – 抓取并丰富的数据科学 Twitter 信息流

图 1.11 – 抓取并丰富的数据科学 Twitter 信息流

为了节省时间,我已经在抓取程序中设置了正则表达式步骤,用来创建用户、标签和 URL 的列。所有这些都是在自动化抓取的过程中被抓取或生成的。这将使得回答我提出的四个问题变得更加容易和迅速。那么,我们开始吧。

发布频率最高的账户

我首先想做的是看看哪些账户总共发布了最多的内容。我还会看看哪些账户发布最少,以检查是否有账户自从被加入到我的抓取程序中后已经停止更新。为此,我将简单地选取publisher(发布推文的账户)和tweet这两列,对publisher进行groupby操作,然后进行计数:

Check_df = df[['publisher', 'tweet']]
check_df = check_df.groupby('publisher').count()
check_df.sort_values('tweet', ascending=False, inplace=True)
check_df.columns = ['count']
check_df.head(10)

这将显示一个按推文数量排序的发布者数据框,向我们展示最活跃的发布者:

图 1.12 – 数据科学 Twitter 信息流中的用户推文数量

图 1.12 – 数据科学 Twitter 信息流中的用户推文数量

太棒了。如果你想进入数据科学领域,而且你使用 Twitter,那么你应该关注这些账户。

然而,对我来说,这些问题的实用性有限。我真正想看到的是每个账户的发布行为。为此,我将使用数据透视表。我将使用publisher作为索引,created_week作为列,并进行计数聚合。以下是按当前周排序的前十名:

Check_df = df[['publisher', 'created_week', 'tweet']].copy()
pvt_df = pd.pivot_table(check_df, index='publisher', columns='created_week', aggfunc='count').fillna(0)
pvt_df = pvt_df['tweet']
pvt_df.sort_values(202129, ascending=False, inplace=True)
keep_weeks = pvt_df.columns[-13:-1] # keep the last twelve weeks, but excluding current
pvt_df = pvt_df[keep_weeks]
pvt_df.head(10)

这将生成以下数据框:

图 1.13 – 按周的用户推文数量数据透视表

图 1.13 – 按周的用户推文数量数据透视表

这看起来更有用,而且对周次敏感。作为可视化,它也应该很有趣,可以感受一下规模:

_= pvt_df.plot.bar(figsize=(13,6), title='Twitter Data Science Accounts – Posts Per Week', legend=False)

我们得到了以下图表:

图 1.14 – 用户推文按周数的条形图

图 1.14 – 用户推文按周数的条形图

这种方式可视化时,看到单独的周次有点困难。对于任何可视化,你都需要考虑如何最容易地讲述你想要表达的故事。由于我主要对展示哪些账户的总推文数最多感兴趣,因此我将使用第一次聚合的结果。这看起来很有趣,也很酷,但并不特别实用:

_= check_df.plot.bar(figsize=(13,6), title='Twitter Data Science Accounts – Posts Per Week', legend=False)

这段代码给我们以下图表:

图 1.15 – 用户推文总数的条形图

图 1.15 – 用户推文总数的条形图

这更容易理解。

最常被提及的账户

现在,我想看看哪些账户被发布者(发推文的账户)提及得最频繁。这可以显示合作伙伴,也可以显示其他值得抓取的有趣账户。为此,我将使用value_counts来查看前 20 个账户。我想要一个快速的答案:

Check_df = df[['users']].copy().dropna()
check_df['users'] = check_df['users'].str.lower()
check_df.value_counts()[0:20]
users
@dataidols         623
@royalmail         475
@air_lab_muk       231
@makcocis          212
@carbon3it         181
@dictsmakerere     171
@lubomilaj         167
@brittanymsalas    164
@makererenews      158
@vij_scene         151
@nm_aist           145
@makerereu         135
@packtpub          135
@miic_ug           131
@arm               127
@pubpub            124
@deliprao          122
@ucberkeley        115
@mitpress          114
@roydanroy         112
dtype: int64

这看起来很棒。我敢打赌这些账户中有一些有趣的数据科学家。我应该去看看,并考虑抓取这些账户并将它们添加到我的数据科学信息流中。

前 10 个数据科学标签

接下来,我想看看哪些标签使用得最频繁。代码会非常相似,唯一不同的是,我需要对标签字段运行explode(),以便为每个推文的标签列表中的每个元素创建一行。我们先做这个。为此,我们可以简单地创建 DataFrame,去除空值,将标签小写化以保持一致,然后使用value_counts()来得到我们想要的结果:

Check_df = df[['tags']].copy().dropna()
check_df['tags'] = check_df['tags'].str.lower()
check_df.value_counts()[0:10]
tags
#datascience           2421
#dsc_article           1597
#machinelearning        929
#ai                     761
#wids2021               646
#python                 448
#dsfthegreatindoors     404
#covid19                395
#dsc_techtarget         340
#datsciafrica           308
dtype: int64

这看起来很棒。我打算可视化前十名结果。然而,value_counts() somehow 使得标签有些损坏,所以我改用了 DataFrame 的 groupby 操作:

图 1.16 – 数据科学 Twitter 信息流中的标签计数

图 1.16 – 数据科学 Twitter 信息流中的标签计数

让我们用一些相关的想法结束这一部分。

简单分析得出的额外问题或行动项

总的来说,如果我不在写书,这个分析大约需要我花费 10 分钟时间。代码看起来可能有些奇怪,因为你可以在 Python 中将命令链式连接。我更喜欢将重要操作单独放在一行,这样下一个需要管理我代码的人就不会漏掉任何附加在一行末尾的重要内容。然而,笔记本是相当个人化的,笔记本中的代码通常不是写得非常干净。当你在研究数据或进行粗略的可视化时,重点应该放在你要做什么上。直到你准备好编写生产版本的代码之前,你不需要写出完美的代码。话虽如此,不要把笔记本质量的代码直接投入生产环境。

现在我们已经完成了快速分析,我有一些后续问题,应该去回答:

  • 这些账号中有多少实际上与数据科学相关,而我没有已经在抓取的?

  • 这些账号中有哪一些给了我新的推送灵感吗?例如,我有关于数据科学、虚假信息研究、艺术、自然科学、新闻、政治新闻、政治人物等方面的推送。也许我应该增加一个摄影方面的推送。

  • 是否值得通过关键词抓取任何一个热门关键词,来收集更多有趣的内容和账号?

  • 是否有任何账号已经停止更新(从未发布新帖子)?是哪些账号?它们何时停止更新的?为什么停止更新?

你试试看。你能从这个数据集中想到任何问题吗?

接下来,让我们尝试一个相似但稍微不同的方法,使用自然语言处理工具分析《爱丽丝梦游仙境》这本书。具体来说,我想看看是否能将 tf-idf 向量化并绘制出每章中角色的出现情况。如果你不熟悉的话,词频-逆文档频率TF-IDF)这个名称非常合适,因为这正是数学原理。我不会讲解代码,但这就是结果的展示:

图 1.17 – 基于书籍章节的《爱丽丝梦游仙境》TF-IDF 人物可视化

图 1.17 – 基于书籍章节的《爱丽丝梦游仙境》TF-IDF 人物可视化

通过使用堆叠条形图,我可以看到哪些角色在同一章节中一起出现,以及它们的相对重要性,基于它们被提到的频率。这完全是自动化的,我认为这将带来一些非常有趣的应用,比如一种更互动的方式来研究各种书籍。在下一章中,我将介绍社交网络分析,如果你也加入这一部分,你甚至可以构建《爱丽丝梦游仙境》或任何其他文学作品中的社交网络,从而看到哪些角色之间的互动。

为了执行 tf-idf 向量化,你需要将句子分割成词汇单元。分词是自然语言处理(NLP)的基础,词汇单元就是一个词。所以,比如说,如果我们要分词这个句子:

今天是美好的一天。

我最终会得到以下词元的列表:

['今天', '是', '一个', '美好', '的', '``一天', '.']

如果你有几个句子,你可以将它们输入到 tf-idf 中,以返回文本语料库中每个词元的相对重要性。这通常对使用简单模型进行文本分类非常有用,也可以作为主题建模或聚类的输入。然而,我从未见过其他人使用它来按书籍章节确定角色的重要性,所以这是一个创造性的做法。

这个例子只是 NLP 所能做的一小部分,它只探讨了我们可能提出的几个问题。当你进行自己的研究时,我鼓励你随时保持一个纸质笔记本,这样当有问题冒出来时,你可以随时记录下来进行调查。

总结

在这一章中,我们介绍了什么是 NLP,它是如何帮助我的,NLP 的一些常见和高级应用,以及初学者如何入门。

我希望这一章能给你一个大致的概念,了解什么是 NLP,它可以用于什么,文本分析是什么样的,以及一些可以进一步学习的资源。这一章绝不是 NLP 的完整图景。即使是写历史部分也很困难,因为其中有太多内容,而且很多已经随着时间的推移被遗忘了。

感谢阅读。这是我第一次写书,也是我第一次为书写的章节,所以这对我来说意义重大!希望到目前为止你喜欢这本书,并且我希望我能给你提供一个充分的初步了解 NLP 的机会。

接下来:网络科学和社交网络分析!

第二章:网络分析

在这一章中,我将描述三个不同的主题,但从一个非常高的层次来讲:图论社交网络分析网络科学。我们将从讨论围绕“网络”一词的混淆开始,探讨为什么这可能仍然会令人困惑。接着,我们将回顾一下这三者的过去和现在。最后,我们将深入探讨网络分析如何帮助了我,以及如何希望它能帮助你。这不是一个代码密集型的章节。这是一个高层次的介绍。

本章将讨论以下主题:

  • 网络背后的混乱

  • 这些网络到底是什么?

  • 学习网络分析的资源

  • 常见的网络应用案例

  • 高级网络应用案例

  • 网络入门

网络背后的混乱

首先,为了减少混淆,如果你看到我提到NetworkX,那不是打错字。那是我们在本书中将大量使用的一个 Python 库。Python 是一种非常流行的编程语言。

我整个职业生涯都在信息技术IT)领域工作,甚至更进一步。在我职业生涯的某些阶段,我为了工作要求获得了如 Security+和 CISSP 等安全认证,并且我一直在与其他 IT 专业人士合作,如网络工程师。所以,相信我,当我告诉你,我理解与那些将网络主要视为基于 TCP/IP 和子网的人的讨论中的尴尬时。

网络无处不在,甚至在我们体内。事实上,我们的大脑是我们在宇宙中发现的最复杂的东西,正如《如何创造大脑》一书(Kurzweil,2012 年)中所讨论的那样。我们的大脑由数百亿个细胞通过万亿级的连接相互联接。多么复杂的网络啊!

当我想到我们在人工神经网络方面所取得的所有成就,以及它们在翻译、计算机视觉和生成方面的表现时,我觉得它们非常令人印象深刻,但我更为印象深刻的是我们这些超复杂的大脑是如何自然进化出来的,以及即使拥有如此复杂的机制,我们有时也会显得多么愚蠢。

回到我的观点,网络不仅仅是计算机网络。网络是事物之间的关系和信息交换的集合。

在本书中,当我谈论网络时,我指的是。我指的是事物之间复杂的关系。一本我非常喜欢的关于这一主题的书,《网络》(Newman,2018 年),描述了几种不同类型的网络,例如以下这些:

  • 技术网络

    • 互联网

    • 电力网

    • 电话网络

    • 运输网络

      • 航空公司航线

      • 道路

      • 铁路网络

    • 配送和分发网络

  • 信息网络

    • 万维网

    • 引文网络

    • 对等网络

    • 推荐网络

    • 关键词索引

    • 数据流网络

  • 社交网络

    • 社交互动网络

    • 以自我为中心的网络

    • 隶属/协作网络

  • 生物网络

    • 代谢网络

    • 蛋白质-蛋白质网络

    • 基因调控网络

    • 药物相互作用网络

    • 大脑网络

      • 神经元网络

      • 大脑功能连接网络

    • 生态网络

      • 食物链

      • 寄主-寄生网络

      • 共生网络

就我个人而言,我最喜欢的网络是社交网络(而不是社交媒体公司),因为它们让我们能够绘制和理解人们之间的关系,即使在大规模的情况下也是如此。我最不喜欢的网络是计算机网络,因为我对子网掩码或不同的计算机网络架构完全没有兴趣。

在这个美丽的宇宙中,图论、社交网络分析和网络科学赋予你调查和探究许多人甚至没有注意到或意识到的关系的能力。

在这本书中,你会看到“图”和“网络”这两个词交替使用。它们本质上是相同的结构,但通常用于不同的目的。我们在NetworkX中使用图来进行网络分析。当这些图被可视化用于社交网络分析时,它们也被称为社交图。是的,这有点令人困惑。不过,我保证你会克服这个困惑。为了让自己更轻松,我告诉自己它们是相同的东西,只是名字不同,有时用于不同的目的。

这些网络究竟是啥?

让我们稍微进一步细分一下。我想分别讨论图论、社交网络分析和网络科学之间的差异。我会保持高层次的讨论,这样我们可以尽快开始构建。如果你想深入了解这些领域,亚马逊上可能有数十本书可以阅读,我个人大概有 10 本书,而且只要发现有新书出版,我就会再买。

图论

最近,图论引起了很多关注。我在数据科学社区中最为关注它,甚至看到数据库管理员和安全专家也开始对此感兴趣。凭借这些热度,很多人可能会认为图论是全新的东西,但实际上它已经有几百年的历史。

图论的历史与起源

图论的历史与起源可以追溯到 1735 年,距今 286 年。当时有一个谜题叫做柯尼斯堡七桥问题,其目标是找到一种方法,能够不重复穿越任何一座桥就穿越所有七座桥。1735 年,瑞士数学家莱昂哈德·欧拉证明了这个谜题是无法解决的。根本没有办法在不穿越任何一座桥两次的情况下穿越每座桥。1736 年,欧拉就这个证明写了一篇论文,这篇论文成为图论历史上的第一篇论文。

1857 年,爱尔兰数学家威廉·罗恩·哈密尔顿发明了一种名为 Icosian 游戏的谜题,涉及寻找一种特殊类型的路径,后来被称为哈密尔顿回路。

几乎在欧拉论文发表后的 150 年,即 1878 年,一词由詹姆斯·约瑟夫·西尔维斯特在《自然》期刊上发表的论文中首次提出。

最后,1936 年,匈牙利数学家德内什·科尼格(Dénes Kőnig)写下了第一本图论教材《有限图与无限图的理论》,这距离欧拉解决七桥问题已经过去了 201 年。1969 年,美国数学家弗兰克·哈拉里(Frank Harary)写下了图论的权威教材。

换句话说,图论并不新鲜。

图论的实际应用

当前,图论在寻找最优路径方面引起了极大的兴趣。这一知识非常宝贵,因为它在将数据、产品和人员从 A 点到 B 点的路由应用中具有重要作用。我们可以在地图软件找到从源点到目的地的最短路径时看到这一点。我也在解决生产数据库和软件问题时使用路径,所以它的应用不仅限于交通。

在本书中,我们将研究最短路径。继续阅读!

社交网络分析

社交网络分析并没有像图论那样受到大量的关注,我认为这是件遗憾的事。社交网络分析是对我们每个人都参与其中的社会网络——社会结构——的分析。如果你研究社交网络,你就能更好地理解人们的行为。你可以研究人们与谁互动、谁是他们讨厌的人、谁是他们爱的人、毒品如何传播、白人至上主义者如何组织、恐怖分子如何运作、欺诈如何发生、恶意软件如何传播、如何阻止流行病的蔓延等等。这个过程涉及数学,但在我自己的研究中,我最感兴趣的是揭示社会互动。只要你理解这些技术的作用、它们所做的假设,以及如何判断它们是否有效,你就可以不写一个方程式进行社交网络分析。从数学角度来说,我认为这个领域对数学不感兴趣的人也非常友好。然而,数学能力在这里可以成为一种超能力,因为它将使你能够设计出自己的技术,用以剖析网络并发现洞察。

社交网络分析的历史与起源

图论和社交网络分析之间有很大的重叠,因为像最短路径这样的概念在社交网络环境中也非常有用,例如,用它来确定从你当前所在的位置到见到总统需要多少次握手。或者,你需要先见到谁才能有机会见到你最喜欢的明星?

然而,在 1890 年代,法国社会学家大卫·埃米尔·杜尔凯姆和德国社会学家费迪南德·滕尼斯在他们对社会群体的理论和研究中预示了社交网络的概念。滕尼斯认为,社会群体作为连接共享价值观和信仰的个体的纽带(社区)或非个人化和工具性的社会联系(社会)存在。另一方面,杜尔凯姆则提供了一个非个人主义的解释,认为社会现象的产生是因为互动的个体是某种比单个个体属性更大的现实的一部分。我个人对这个观点很感兴趣,当我研究人们在社交图中所处的位置时,我能感受到这一点。人们是否创造了自己在社交网络中的位置,还是部分是偶然的,基于他们认识的人以及他们的连接本身所处的位置?例如,如果你在某个国家的特定地区长大,你很可能会继承社区成员共享的许多情感,但这些情感可能比社区现存的成员还要古老。

在 1930 年代,心理学、人类学和数学领域的多个独立研究小组在社会网络分析方面取得了重要进展。即使在 90 年前,社会网络分析已经是一个多学科的话题。在 1930 年代,雅各布·莫雷诺被认为创造了第一个社会图,以研究人际关系,甚至在 1933 年,这一图被印刷在《纽约时报》上。他说:“在社会度量学出现之前,没有人知道一个群体的‘人际结构’究竟是怎样的。”就我个人而言,每当我第一次可视化我构建的任何社交网络时,我都会感受到同样的兴趣。网络结构就像一个谜,直到你第一次看到它被可视化出来,看到它被首次呈现出来总是令人兴奋的。

在 1970 年代,学者们致力于将独立的网络研究的不同路径和传统结合起来。几乎 50 年前,人们意识到独立的研究正在朝着多个方向发展,因此有了将其整合在一起的努力。

在 1980 年代,社交网络分析的理论和方法在社会和行为科学中变得普及。

社交网络分析的实际应用

社交网络分析可以用于研究任何社会实体之间的关系。社交网络分析的目标是理解社会实体和社区如何互动。这可以在个人层面,也可以是在国际层面。

这甚至可以用来对抗文学。例如,这是我从《爱丽丝梦游仙境》一书中构建的社交网络:

图 2.1 - 《爱丽丝梦游仙境》中的社交网络

图 2.1 - 《爱丽丝梦游仙境》中的社交网络

这是我从《动物农场》一书中构建的社交网络:

图 2.2 – 《动物庄园》的社会网络

图 2.2 – 《动物庄园》的社会网络

这些是我最初创建时的原始社会图,因此它们可能还需要做些改进,我们将在本书中对其进行清理和分析。

我比起图论或网络科学,更喜欢社会网络分析。我想理解人类,我想制止坏人,让世界变得更安全。我想阻止恶意软件,让互联网更安全。这一切都是我动力的源泉,因此我把很多空闲时间投入到社会网络分析的研究中。这是我的重点。

网络科学

网络科学是我另一个兴趣所在。网络科学是关于理解网络是如何形成的,以及它们如何形成的意义。

网络科学的历史与起源

和社会网络分析一样,图论和网络科学的起源有很多重叠。对我来说,网络科学似乎更注重统一各种网络学科,将所有内容集中在一个框架下,就像数据科学试图统一各种数据学科一样。事实上,最近我看到网络科学在数据科学家中被广泛使用,因此我预测,最终网络科学会被视为数据科学的一个子领域。

在我读过的关于网络科学的书籍中,欧拉的七桥问题常常被当作网络科学的起源,展示了图论是网络科学的根源。然而,1990 年代有几个关键的研究努力,专注于数学地描述不同的网络拓扑结构。

邓肯·沃茨(Duncan Watts)和史蒂文·斯特罗加茨(Steven Strogatz)描述了“即小世界”网络,大多数节点不是彼此的邻居,但可以通过很少的跳跃相互到达。阿尔伯特·拉斯洛·巴拉巴西(Albert-László Barabási)和瑞卡·阿尔伯特(Reka Albert)发现,小世界网络在现实世界中并不是常态,并提出了无标度网络(scale-free network),这种网络由少数几个中心节点和许多边缘节点组成,后者具有较少的连接。该网络拓扑会不断增长,以维持连接数和所有其他节点之间的恒定比率。

网络科学的实际应用

网络科学的实际应用也是图论和社会网络分析的实际应用。我认为图论和社会网络分析是网络科学的一部分,尽管这些技术针对每个学科进行了专门化。我个人不做区分,也不太思考图论的问题。在进行网络科学时,我会运用图论和社会网络分析的技术。当我通过代码实现时,图论的大部分数学内容已经被抽象化,但我仍然经常使用最短路径和中心性等内容,前者来自图论,后者则来自社会网络分析。

学习网络分析的资源

那么,开始进行网络思维的旅程需要什么呢?我会给出一些建议,帮助你入门,但请注意,等到这本书出版时,其中一些建议可能已经有些过时,新技术和新方法可能已经出现。这并不是一个完整的清单,而是最低要求。首先,你需要的是一颗好奇心。如果你愿意去探索构成我们存在的隐秘网络,那么你已经具备了第一个前提条件。不过,我还是会给你更多的建议。

笔记本界面

我在 Jupyter 笔记本中进行所有的网络分析。你可以通过 Anaconda 下载和安装 Jupyter,网址是:docs.anaconda.com/anaconda/install

如果你不想安装 Jupyter,你也可以直接使用 Google Colab,无需任何安装。你可以在 research.google.com/colaboratory 找到并立即开始使用 Google Colab。

与典型的集成开发环境IDE)不同,笔记本界面允许你在“单元格”中编写代码,然后按顺序运行或重新运行它们。这对研究很有用,因为你可以快速地在数据上进行实验,实时进行探索性数据分析和可视化,且可以在你的网页浏览器中查看。以下是效果图:

df = pd.read_csv(data)
df['book'] = df['book'].str.lower()
df['entities'] = create_entity_list(df)
df.head()

《圣经》包含在数据文件中,所以这将加载数据并输出《圣经》前五节经文的预览。

图 2.3 – 《圣经》的 pandas DataFrame

图 2.3 – 《圣经》的 pandas DataFrame

现在,让我们来看一下《创世记》中提到夏娃的章节:

entity_lookup('Eve', book='genesis')

这将显示提到夏娃的两节经文的预览:

图 2.4 – 《创世记》中提到的夏娃

图 2.4 – 《创世记》中提到的夏娃

让我们看看夏娃在整个《圣经》中被提到多少次:

entity_lookup('Eve')

再次,这将显示经文的预览:

图 2.5 – 《圣经》中提到的夏娃

图 2.5 – 《圣经》中提到的夏娃

我加载了自己创建的《圣经》数据集,并进行了些许清理和丰富,然后我创建了两个额外的单元格,一个是查找《创世记》中的夏娃,另一个是查找《圣经》全书中的夏娃。如果你仔细看,你会发现我不小心把df设置成了全局变量,这样不太好。我应该修复那个代码。哦,好吧,下次吧,代码能运行就好。

一个笔记本允许你进行数据实验,并发挥极大的创意。笔记本有优缺点,其中一个我之前提到过。

优点是你可以非常快速且轻松地在数据集上进行实验,构建加载、预处理、丰富和甚至机器学习ML)模型开发的代码。你可以用一个笔记本做很多事情,且非常迅速。这是我进行初步探索和开发时最喜欢的工具。

缺点是,笔记本代码有个不太好的声誉,被认为是快速且不够规范的代码,这种代码其实不适合生产环境。另一个问题是,笔记本代码可能会变得杂乱无章,不仅仅是我们笔记本电脑上的文件,还包括 Jupyter 服务器(如 SageMaker、Kubeflow 等),所以很容易导致事情变得混乱和难以管理。

我建议用它们来探索数据并激发创新,但一旦你有了有效的解决方案,就把这些代码拿到集成开发环境(IDE)中好好构建。我目前偏好的 IDE 是 Visual Studio Code,但你可以使用任何你舒适的工具。IDE 将在下一节中定义。

你可以在 code.visualstudio.com/download 下载 Visual Studio Code。

集成开发环境(IDEs)

集成开发环境(IDE)是一种用于编写代码的软件。例如 Visual Studio Code、Visual Studio、PyCharm、IntelliJ 等。不同的 IDE 支持多种编程语言。试试几个,选择最适合自己的工具。如果某个 IDE 使用起来让你效率低下,太长时间没法适应,那就说明它可能不适合你。不过,现代的 IDE 通常都有一定的学习曲线,所以要对自己和软件有耐心。如果你想了解更多关于 IDE 的信息,可以在 Google 上搜索 popular IDEs。刚开始时,我建议你花点时间深入学习所选的 IDE,观看视频并阅读文档。我通常每隔几年会这样做,尤其是在刚开始的时候。

网络数据集

没有数据,你无法进行网络分析。你要么需要创建数据,要么需要去找一些现成的数据。与创建其他类型的数据集(例如机器学习训练数据)相比,创建网络数据集通常更容易、更简单,也更少需要手动操作,而且它也是快速建立领域知识的好方法。我们的兴趣和好奇心可能比使用别人的数据集更具探索性。不过,为了让你了解,确实有一些网络数据集可以在你熟悉网络分析工具时作为起点:

图 2.6 – Google 搜索:网络科学数据集

图 2.6 – Google 搜索:网络科学数据集

除了顶部的链接,其他数据集看起来有些被忽视且基础,似乎好几年没有更新了。

搜索社交网络分析数据集会找到看起来稍微更有趣的数据集,这也有道理,因为社交网络分析比网络科学更为久远。然而,顶部链接是一样的。如果你需要一些现成的数据,看看斯坦福网站以及 data.world

图 2.7 – Google 搜索:社交网络分析数据集

图 2.7 – Google 搜索:社交网络分析数据集

Kaggle 数据集

Kaggle 是另一个寻找数据的不错地方,几乎可以找到任何类型的数据。Kaggle 偶尔会受到一些人的不公平对待,有些人瞧不起那些使用 Kaggle 数据集的人,因为 Kaggle 上的数据不像现实世界中的数据那样脏乱,但 Kaggle 的数据和前面提到的数据集没有太大区别。它们都已经经过清洗,准备好供你练习机器学习或网络分析工具。Kaggle 上有许多有趣的数据集和问题需要解决,它是一个学习和建立信心的好地方。使用那些对你有帮助的资源。

NetworkX 和 scikit-network 图形生成器

在本书中,我将解释如何使用 Python 库 NetworkXscikit-network。我使用 NetworkX 来构建图,使用 scikit-network 来进行可视化。我这么做是因为 scikit-network 并没有真正的网络分析能力,但它可以渲染 可缩放矢量图形SVG)格式的网络可视化图,比 NetworkX 的可视化更快、更好看。这两个库还包含了若干个用于生成常见实践网络的生成器。如果你使用它们,你不会熟悉如何从零开始创建网络,但如果你只是想尝试一个预构建的网络,这是一个不错的起点:

我的建议是,你应该忽略 scikit-network 的加载器,直接熟悉使用 NetworkX 来创建网络。个人而言,它更为优秀。在目前的情况下,除非是可视化,scikit-network 完全无法替代 NetworkX。

创建你自己的数据集

在本书中,我将向你展示如何创建自己的数据集。就我个人而言,我认为这是最好的方法,也是我每次实际操作时都采用的方法。通常,我会有一个想法,然后把它写在白板上或笔记本里,想办法获取数据,然后开始工作。这个过程通常需要几天时间,我通常会采用这样的方式:

  1. 想一个点子。

  2. 跑去白板或笔记本上写下来。

  3. 带着笔记本坐到外面,幻想一下如何获得数据。

  4. 设置一些网页抓取器。

  5. 在抓取数据一两天后下载数据。

  6. 从句子中提取实体并构建边列表数据框。

  7. 构建网络。

  8. 深入网络分析。

  9. 可视化那些看起来有趣的东西。

  10. 玩转自我图(ego graphs),从更多角度探索自我网络。

这绝对是一个过程,虽然看起来步骤很多,但随着时间的推移,它们都会融合在一起。你有一个想法,获取数据,进行预处理,然后分析并使用数据。从某种程度上讲,这与其他数据科学工作流并没有太大区别。

在这本书中,我将解释如何将原始数据转化为网络数据。

获取网络数据的途径有很多种。没有哪一种是错误的。有些方法只是省略了一些步骤,简化了操作,使得你可以用工具和技术进行练习。这完全没问题。然而,你应该以自给自足为目标,并且习惯与脏数据打交道。数据永远不会是干净的,即使有人似乎做了很多清理工作。即便是我自己 GitHub 上的数据集,通常还有更多的清理工作要做。文本数据是杂乱的。你需要学会在这些“脏”数据中游刃有余。对我来说,这正是我最喜欢的部分——将混乱的文本数据转化成美丽且有用的网络。我喜欢这样做。

所以,熟悉我们谈论过的所有内容,然后去探索吧。如果你真的很有雄心,甚至可以考虑创建第一个成功将所有内容整合在一起的网络数据存储库。

NetworkX 和文章

我发现学习网络科学的另一个有用方法是直接在 NetworkX 文档网站上发布的文章链接。当它们描述其功能时,通常会提供一个链接,指向关于构建该功能所用技术的文章。这里是一个例子:

图 2.8 – NetworkX 文档:k_core 算法

图 2.8 – NetworkX 文档:k_core 算法

如果你去 NetworkX 的k_core页面,你可以看到关于该函数、参数、返回值和一些说明的清晰文档。这还不是最棒的部分,最棒的部分隐藏得更深。向下滚动,你通常会看到像这样的内容:

图 2.9 – NetworkX 文档:k-core 文章引用

图 2.9 – NetworkX 文档:k-core 文章引用

太棒了!这里有一个 arXiv 链接!

能够阅读关于k_core的研究背景非常有用。对我个人来说,得知很多内容其实已经相当陈旧,感到有些宽慰。例如,我提到的自然语言处理NLP)历史。我们常常听到说数据科学的进展如此之快,以至于无法跟上。但这只有在你被每个闪亮的新事物分散注意力时才是真的。专注于你想做的事,研究如何实现你的目标,然后构建它。你不应该仅仅使用更新的技术。新的并不总是更好的,甚至通常不是。

以 k_core 为例,我当时在寻找一种方法,快速丢弃所有只有一条边的节点。在我第一次做网络时,我会写很多代码行来完成这个任务,碰到列表推导的麻烦,或者只是写一些能工作但很难看的代码。然而,我本可以直接做nx.k_core(G, 2),就这么简单。问题解决了。

所以,阅读文档吧。如果你想深入了解,可以寻找学术期刊链接,然后不断构建,直到你理解该技术。

你会在 NetworkX 中找到类似的链接,用于中心性或其他主题。去探索一下吧。它是很好的阅读材料,能激发灵感。

非常棒!这为我们进一步了解网络分析中中心性的起源和应用打下了基础。

常见的网络使用案例

正如我在第一章中所做的,《自然语言处理介绍》,我现在也将解释一些我自己最喜欢的网络数据使用案例。我在本章开头提到过,网络有很多种不同的类型,但我个人更喜欢处理社交网络和我称之为数据流网络的内容。

以下是我在处理网络数据时的一些用途:

  • 映射生产数据流

  • 映射社区互动

  • 映射文学社交网络

  • 映射历史社交网络

  • 映射语言

  • 映射黑暗网络

我将从数据流网络开始,因为这是我意识到的第一个网络数据使用案例,也是彻底改变我工作方式的东西。

映射生产数据流

如前所述,这是我为自己使用网络数据时的第一个想法。我在软件领域工作了超过 20 年,并且花费了大量时间“解剖”生产代码。这不是为了娱乐,而是有实际用途的。在过去,我曾被要求“升级”旧的生产系统,这意味着将一个旧的生产系统拆解,弄清楚它上面运行的所有内容(代码、数据库以及文件读写操作),然后将一切迁移到新服务器上,以便将旧服务器淘汰。我们称之为去风险化生产系统。最终,这会导致一个极其快速的新生产系统,因为这些通常是运行在非常新的基础设施上的老旧代码。

在我以前的方式中,我会通过从cron开始,来清点生产系统,即如何在服务器上调度任务。通过从cron开始,你可以找到一个过程的根源,看看哪些脚本调用了其他脚本。如果你将这个过程映射出来,最终你会得到这样的内容:

  • cron:

    • script_a.php > script_a.log

    • script_b.php > script_b.log

    • script_c.php > script_c.log

    • script_c.php 使用 script_d.php

    • script_c.php 使用 script_e.php

以此类推。这并不是cron的真实模样,但它展示了脚本调用和数据写入的方向。因此,在上一个示例中,代码执行了以下操作:

  • script_a.php 写入 script_a.log 日志文件

  • script_b.php 写入 script_b.log 日志文件

  • script_c.php 写入 script_c.log 日志文件

  • script_c.php 启动另一个脚本,script_d.php

  • script_c.php 启动另一个脚本,script_e.php

我曾经是手动进行这些工作的,这是一项非常繁琐且精确的工作,因为如果漏掉任何东西,可能会导致迁移时无法迁移或无法在新服务器上测试,这在迁移日发现时会非常痛苦。在一台有几百个脚本的服务器上,甚至需要几周时间才能构建一个脚本图,而完整的输入和输出图则需要更长的时间。从前面的例子来看,脚本图看起来是这样的:

  • cron:

    • script_a.php

    • script_b.php

    • script_c.php

      • script_d.php

      • script_e.php

在生产环境中,这种交互可能非常深入且高度嵌套。2017 年,我发现了一种新的方法,通过使用网络,脚本图会利用图中构建的节点和边缘自动生成。这本可以是一个非常长的讨论,我甚至想过写一本书来详细讲述——不过为了节省时间,这种新方法的工作效率大约是旧方法的 10%,并且能够捕捉到所有信息,只要我在源代码分析时足够仔细。我还不需要在脑中维持一个服务器的运行状态图像,因为我有一个图形可视化工具可以直观检查,并且有一个网络可以进行查询。通过这种方法,我的工作效率提高了 10 倍,在相同的时间内,我可以提升更多的服务器,而过去我可能只能完成一台服务器的工作。通过建立的网络,我还可以快速排查生产问题。过去需要几天才能解决的问题,现在我通常可以在几分钟内解决。

另一个不错的副作用是,如果你想要重新设计软件并使用这种方法,你可以理解服务器上所有内容的输入和输出,利用这些信息找到低效的地方,并提出如何在新平台上更好地构建软件的想法。我也曾经这样做过,将 SQL Server 数据库替换为 Spark 数据流,而且非常成功。

映射社区互动

现在,我不再提升生产系统,而是转向了更有趣的工作,因此接下来我将描述的内容来自我自己的独立研究,而不是我在工作中做的事情。对于我的研究,我做了大量的社交媒体抓取与分析,因为这给了我一个了解周围世界的方式。我想要理解的一件事是,某些群体中的人们是如何与彼此以及与群体外的其他人互动的。

例如,我抓取了几十个与数据科学相关的 Twitter 账户,以便关注数据科学和科学社区中的最新动态。在最初抓取一些账户后,我提取了用户提及并映射了它们之间的所有互动。这有助于我发现其他值得抓取的用户,并将它们添加到我的抓取器中,然后我继续提取用户提及并映射它们之间的所有互动。我一次又一次地重复这一过程,不断为我感兴趣的任何领域—数据科学、自然科学、艺术、摄影,甚至 K-pop—建立社交网络。我觉得分析社区非常有趣,这也是我最喜欢做的事情之一。这是本书中我将花很多时间讨论的话题,因为我已经见识到这项技术对研究的巨大价值,甚至曾用抓取的结果构建高度定制的机器学习训练数据,从而自动发现更多我感兴趣的内容!这些结果是自然语言处理、社交网络分析和机器学习的美妙结合!

这对于关注社区内的趋势和讨论话题也非常有用。例如,如果有一个大型数据科学会议即将召开,关于该话题的讨论会突然增多,可能会分享类似的标签。通过创建用户-标签网络而不是用户-用户网络,也可以找到这些标签。这对于观察趋势的变动非常有效,尤其是当你将时间元素加入到网络数据中时。

映射文学社交网络

我最喜欢的另一个网络应用是将一部文学作品,如爱丽丝梦游仙境动物农场,的社交网络进行映射。我将在本书中详细讨论这一点。与社交媒体相比,这个方法稍微复杂一些,因为前面提到的技术可以单靠正则表达式完成,而这个技术需要自然语言处理中的词性标注或命名实体识别。然而,结果是你能够得到该文本中人物之间互动的社交网络,从而更好地理解角色间的关系。

映射历史社交网络

你还可以使用自然语言处理(词性标注和命名实体识别)的方法来映射历史社交网络,只要你有关于某个事件的文本或能展示人物关系的数据。例如,通过我前面描述的文学社交网络技术,可以轻松构建整个《圣经》的社交网络。虽然涉及大量清理工作,但如果你足够细心,最终结果会让人叹为观止。我非常希望看到研究者也能对其他历史文献进行类似的操作,这可能会为我们提供前所未有的新的见解。

映射语言

网络的另一个酷炫用途是映射语言本身。这是我在玩《傲慢与偏见》这本书时发现的。我想看看如果我构建一个网络,连接她的书中每个句子里每个单词之间的互动,会发生什么。例如,我们来看《傲慢与偏见》中的下面这句话:

人们普遍承认,拥有丰厚财富的单身男人,一定是想找个妻子。

让我们通过使用单词序列来映射单词关系。如果一个单词跟随另一个单词,那么我们就说这两个单词之间有关系。把关系想象成连接两个事物的无形线条。对于前面的引用,我们可以这样映射:

  • it -> is

  • is -> a

  • a -> truth

  • truth -> universally

  • universally -> acknowledged

  • acknowledged -> that

  • that -> a

  • a -> single

  • single -> man

  • man -> in

  • in -> possession

  • possession -> of

  • of -> a

  • a -> good

  • good -> fortune

  • fortune -> must

  • must -> be

  • be -> in

  • in -> want

  • want -> of

  • of -> a

  • a -> wife

在网络中,我们称节点之间的连接为节点是我们正在连接的事物。在这种情况下,这是一个单词网络,所以单词是节点,单词之间的连接叫做边。你可以把边看作是连接两个事物的线条。

在引用的句子中,你可能会注意到atruthsinglegoodwife之间存在联系,in也有两个联系,分别与possessionwant相关。

这里是另一个例子,使用了 Smashing Pumpkins 的歌曲:

尽管我充满愤怒,我仍然只是一个笼中之鼠。

你也可以像这样将其映射出来:

  • despite -> all

  • all -> my

  • my -> rage

  • rage -> i

  • i -> am

  • am -> still

  • still -> just

  • just -> a

  • a -> rat

  • rat -> in

  • in -> a

  • a -> cage

字母aratcage之间有联系。

如果你用整本书来做这个,你最终会得到一个超密集的网络,几乎无法可视化;然而,分析这个网络并不是毫无价值的。如果你查看《傲慢与偏见》的外部节点,你会找到简·奥斯汀最少使用的单词,它们通常能组成一个惊人的词汇表。如果你看核心单词,它们通常是连接词,比如“a”、“the”和“of”。

我也使用了同样的技术进行了一些研究,看看我能否通过视觉识别出 AI 生成的文本与人类生成的文本之间的区别。你猜怎么着?可以。如果你用机器学习生成文本,它会选择下一个单词,不是因为它最合适,而是因为它最有可能被使用。当你试图预测句子“Jack and Jill went up the ______”中的下一个单词时,这很棒,但当你真的想用它创造出一些真正原创的东西时,这就不那么棒了。我们人类更具细腻性。如果我想把这个句子变得更加阴郁,我可以用“chimney”来完成这个句子。我真的怀疑机器学习会选择“chimney”。

从视觉上看,AI 生成的文本和人类生成的文本在网络结构上有所不同。当我在 2019 年做这项工作时,AI 生成的网络外观非常参差不齐,核心部分却更为密集,因为它会选择更高概率的词汇。而人类则更为细致,外缘看起来更柔和。我能够在视觉上区分二者。更近期的 AI 会显得不同吗?如果你感兴趣,我鼓励你试试看。

绘制黑暗网络

最后一部分有点黑暗。有时,当世界上发生不好的事情时,我想知道一些团体正在策划什么。黑暗网络是那些希望伤害他人的人组成的社交网络,但黑暗网络有不同的种类(涉及犯罪、恐怖、仇恨、毒品等等)。即使是危险人物也会围绕某些社交媒体标签聚集,因此他们偶尔会暗示即将发生的事情。我不会详细讲解可以收集到的情报层级,但你可以按照我在 绘制社区互动 下讨论的内容,利用它来关注事态发展,或找到其他感兴趣的人。你也可以用它来举报违反社区准则的社交媒体帖子,甚至在看到具体情况时上报给执法部门。只要知道,如果你进行这种 开放源代码情报OSINT)收集,它可能对你的情绪和心理健康产生不良影响,因此要小心处理。不过,它也可以是很好的研究工具。

重要说明

当我用它进行 OSINT 时,我通常会从一些人或感兴趣的事物开始,然后逐渐绘制出它们之间的社交网络。但这并不限于黑暗网络。你也可以使用这种技术来进行攻击、防御,或者仅仅满足你的好奇心。这是研究人与组织之间互动的一种有用方式。

OSINT 有许多用途,关于这一主题已有整本书籍写成。然而,OSINT 的目标是利用公开的数据更好地理解或监控某些事物。例如,你可以使用 OSINT 来研究你正在面试的公司。公司是谁拥有的?谁在面试我?这家公司曾经上过新闻吗?这是个正面新闻还是负面新闻?公众对这家公司的普遍看法是什么?这是一家合法公司吗?前员工对它怎么说?他们在社交媒体上说什么?大部分评论是正面的、负面的,还是中立的?如果你提出正确的问题,并能访问互联网,你可以了解任何事物的很多信息。

市场调研

假设一个情境:你创造了一款新产品,想要找到可能会对你的产品感兴趣的人。我为自己的需求做过完全相同的事情。

我曾使用社交网络分析构建一个社区成员的地图,并将结果用于市场研究。对于某个感兴趣的话题,我可以看到来自世界各地的数千名研究人员,如果我想为这个社区创建一个产品,开发一个有效的推广活动来激发对我的产品的兴趣将不需要太多工作。为了这个目的,我只使用了 Twitter 数据,经过几天的抓取和分析,我就能够识别出成千上万的潜在接触对象。

查找特定内容

这一部分有点像“先有鸡还是先有蛋”的问题。你需要社交媒体文本才能提取文本中存在的社交网络数据,然后你可以利用社交网络来识别其他需要抓取的账户,进一步提升你的数据流。这是一个迭代的过程:识别、抓取、分析,识别、抓取、分析,依此类推。你可以通过基于抓取的文本构建 NLP 分类器,进一步改善分析部分。社交网络分析将帮助你找到重要账户,但你的 NLP 分类器将帮助你过滤噪音,获取相关和集中的内容。

创建机器学习训练数据

如前所述,你可以使用抓取的数据流来创建 NLP 分类器,并利用这些分类器帮助你过滤噪音,从而得到更集中的内容。然后,你可以利用这些集中的内容构建更好的分类器。然而,你必须首先创建初步的分类器,而通过结合 NLP 和网络分析创建的数据流将为你提供所需的丰富过滤数据,以创建有用的训练数据。这个过程是有步骤的,既耗时又需要大量工作。我将在第十四章网络与监督式 机器学习 中解释这一点。

具体而集中的内容流入分类训练数据,训练后的分类器将帮助识别更多有用的内容。

高级网络应用场景

第一章自然语言处理简介 中,我指定了 NLP 的几个高级应用场景,如语言翻译和文本生成。然而,在考虑网络分析时,我的脑海里立刻出现了一个问题:一个高级网络应用场景到底意味着什么?这些内容都相当先进。对于 NLP,你有一些简单的任务,如分词、词形还原和简单的情感分析(积极或消极,是否为仇恨言论),同时也有一些高级任务。对于网络分析,我能想到三个潜在的高级应用场景:

  • 图形机器学习

  • 知识图谱

  • 推荐系统

然而,我并不认为它们有多先进。我认为它们只是实现方式与我提到的其他东西不同而已。此外,仅仅因为某件事在技术上更具挑战性,并不意味着它更先进或更重要。事实上,如果它更困难且返回的结果更无用,那就不是理想的做法。那简直是浪费时间。

图形机器学习

我曾参与过图形机器学习(Graph ML)项目,主要发现有两种方法:一种是将图形度量(中心性、局部聚类系数等)作为训练数据中的特征,另一种是直接将图形表示数据输入机器学习,让它自己去分析。图形元数据可以为训练数据提供强有力的补充,因为它可以为模型提供一个额外的视角,因此我认为这一点非常有前景。

然而,过去当我看到人们试图直接在机器学习中使用图形数据时,通常图形知识是缺失的或很薄弱的。在你能够分别做好网络分析和机器学习之前,我不建议采用这种方法。在机器学习中,输入数据的重要性通常和模型本身一样大,甚至更重要,因此对网络的了解应该是存在的,但并非总是如此。领域知识在数据科学中非常重要。然而,这仍然是非常有趣的内容,所以一定要去了解一下。

推荐系统

推荐系统很有趣,尽管我没有花太多时间研究它们;其中一个概念是,如果两个人喜欢相似的事物,他们也可能喜欢其他尚未共有的事物。例如,如果你和我都喜欢乐队 Soundgarden、Smashing Pumpkins 和 Nirvana,而我喜欢 The Breeders,您喜欢 Stone Temple Pilots,那么我很可能也会喜欢 Stone Temple Pilots,而你也会喜欢 The Breeders。如果你想探索推荐系统,我鼓励你深入研究。只是这不是我的兴趣所在,因为我主要使用网络进行社交网络分析、市场调研和开源情报(OSINT)。

然而,推荐系统也有一些缺点,我想指出这些问题。这些系统推荐我们可能喜欢的事物,但结果通常并不令人意外。在音乐方面,我很可能会喜欢 Stone Temple Pilots,而你会喜欢 The Breeders,这完全可以理解,但我个人更兴奋的是当我爱上完全意想不到的东西时。我希望未来我们的推荐系统能够推荐我们可能无法自己发现的食物、音乐和电视节目。我不想只体验类似的事物。我也想要那些意外的东西。

此外,当我们仅被展示我们期望看到的东西时,我们往往会强化一些不太好的东西。例如,如果社交媒体公司只给我们展示他们认为我们喜欢的故事,或者我们可能会参与的内容,我们最终会陷入回声室,这让我们主要与那些和我们一样的人交往,讨论相同的话题,并且在同样的事情上达成一致。这既不具教育意义,也不具有建设性。它导致了两极化,甚至可能引发暴力。当人们只愿意与自己相似的人交往,并开始妖魔化那些与自己不同的人时,这就成了一个危险的问题,而这种现象时常发生,我们也很容易受到影响。

我将留给你自己去决定网络使用的常见与高级应用。在我看来,它们都是高级的,可能只是实现方式不同。

网络入门

如果你想开始自己的第一个原创网络分析项目,首先需要想出一个你感兴趣的问题。在社交网络分析中,你通常是想建立一个社交图,一个展示人类如何互动的可视化图。对于你的第一个网络项目,你可以提出以下类似的问题:

  • 《》中的人物是如何相互互动的?

  • 《动物农场》这本书中的不同动物是否只与相同类型的动物互动?人类是否只与某些类型的动物互动,还是与所有类型的动物都有互动?

  • 我所在小镇的 Twitter 圈子是什么样的?

  • 餐食配料的网络可视化是什么样的?不同地区的配料网络可视化有什么不同?不同地区的配料网络可视化又是什么样的?

  • 某个政治人物周围的 Twitter 社交网络是什么样的?朋友网络是什么样的?敌人网络又是什么样的?

  • 如果我构建一个示例暗网,如何才能将其以最优方式摧毁,使其无法修复,以至于即使在持续的攻击下,也永远无法重新形成?

  • 如果我构建自己的基础设施网络,并且如果我能够识别出可能导致它在被攻击时破裂的结构性弱点,我该如何保护它,以防止这些类型的攻击打乱网络?

这些只是我在过去几分钟内想到的一些例子。你越是实验并玩弄网络,越容易想出属于你自己的研究思路,所以从一些简单的开始,随着时间的推移再逐步变得更有雄心。这些听起来可能有些复杂,但只要你能获取所需的数据,网络构建、分析和可视化其实并不难,并且它们之间有很多重叠之处。

简而言之,你需要找到一个你感兴趣的主题,并识别这些事物之间的关系。这可能是非常简单的,比如谁认识谁,谁喜欢谁,谁信任谁,谁讨厌谁,谁和谁一起工作,哪些食材可以做哪些菜肴,哪些基础设施和其他基础设施通信,哪些网站链接到其他网站,或者哪些数据库表可以与其他数据库表连接。

对于我自己的独立研究,我通常使用 Twitter 数据,因为在我看来,它是一个自然语言处理(NLP)和网络分析的宝库。而且我的许多工作都遵循一个可重复的过程:

  1. 想出一个研究主题。

  2. 找到一些与该主题相关的 Twitter 账户。

  3. 抓取账户。

  4. 分析抓取的数据:

    • 提取网络数据。

    • 提取更多用户进行抓取。

  5. 根据需要重复步骤 3 和 4

如前所述,这是一个可重复的过程。我将在第六章《图谱构建与清理》和第七章《整体网络分析》中解释如何开始抓取数据。一旦你学会了通过抓取和应用程序编程接口APIs)获取数据,你将能够获得比你能使用的更多的数据。而对于社交网络分析,你可以按照前面的示例进行操作。慢慢来,它最终会变得自然而然。

示例 – K-pop 实现

我的女儿们喜欢 K-pop 音乐。经常在晚餐时,我会听到她们讨论像 BLACKPINK、TWICE 和 BTS 这样的组合,但我根本不知道她们在说什么。然后有一天,我正在思考为我的 LinkedIn 挑战 #100daysofnetworks 找一个研究主题,我突然想到应该做一些关于 K-pop 的内容,因为如果我能多了解一点,我就能和女儿们有共同话题,而且这也是向她们展示数据科学的一种方式。于是,我想出了一个过程来把这个想法变成现实:

  1. 我决定研究围绕许多著名 K-pop 艺人的社交网络,并且这个网络应该包括音乐中心和粉丝俱乐部。

  2. 我花了大约 30 分钟做 Twitter 查询,目的是找出几十个 K-pop 组合、音乐中心和粉丝俱乐部。我找到了大约 30 个,并把它们写在笔记本上。

  3. 我把这 30 个账户添加到我的 Twitter 抓取工具中,然后将数据合并成一个kpop.csv的文件。这让我可以更轻松地进行所有网络数据的提取和分析,所有的内容都集中在一个文件里。我的抓取工具是 24/7/365 运行的,所以我总是能获得新鲜的数据进行探索。

  4. 在抓取了几天的数据后,我分析了数据流,提取了网络数据(用户提及和话题标签),并找出了几十个可以继续抓取的账户。

  5. 我重复了步骤 3 和 4大约一个月,现在我已经有了一个围绕 K-pop 艺人、音乐中心和粉丝俱乐部的以 Twitter 为中心的社交网络。

现在我正在积极爬取与 K-pop 相关的 97 个账户,我有新鲜的媒体供我的女儿们享用,并且我有有趣的网络数据可以进一步提升我的技能。

因此,对于您自己的网络研究,请找到您感兴趣的内容,然后开始工作。选择您感兴趣的内容。研究不应该是无聊的。这本书将向您展示如何将您的研究好奇心转化为成果。我们将在通过下几章后讨论图构建、分析和可视化。

总结

在这一简短的章节中,我们涵盖了很多内容。我们讨论了关于“网络”一词的混淆,深入探讨了图论、社交网络分析和网络科学的历史和起源,讨论了学习和实践的资源,讨论了一些我喜欢的网络应用案例,并最后解释了如何开始制定您自己的网络研究。

我希望这一章给您提供了关于所有这些网络内容的大致了解。我知道我没有详细讨论起源,我主要谈论了社交网络分析,但那是因为那是我的兴趣领域。我希望您现在了解网络可以用于什么,并且希望您明白我只是触及了表面。我的目标是激发您的好奇心。

在下一章中,我将解释用于自然语言处理的工具。我们将逐渐超越理论,进入数据科学领域。

进一步阅读

  • Barabási, A.L. (2014). 链接. 基础书籍。

  • Kurzweil, R. (2012). 如何创造一个心灵. 企鹅图书。

  • Newman, M. (2018). 网络. 牛津大学出版社。

第三章:有用的 Python 库

在本章中,我将介绍我们将在本书中使用的几个 Python 库。我会描述它们是什么、如何安装,并给出一些有用的示例。你不需要记住每个 Python 库的所有功能,或者深入理解你使用的每个函数的内部原理。重要的是,你需要知道哪些库是可用的,它们的整体功能是什么,以及库中有哪些关键的时间节省工具(这会让你反复使用)。每个人的使用场景不同,无法记住所有内容。我建议你尽快理解这一点,在需要时学习所需内容。根据需要深入了解库的内部结构。

为了保持条理,我将按照类别来划分软件库。以下是我们将讨论的库:

Python 库类别
pandas数据分析与处理
NumPy数据分析与处理
Matplotlib数据可视化
Seaborn数据可视化
Plotly数据可视化
NLTK自然语言处理
spaCy自然语言处理
NetworkX网络分析与可视化
Scikit-Network网络可视化(更好)
scikit-learn机器学习
Karate Club机器学习(图形)
spaCy (重复)机器学习(自然语言处理)

图 3.1 – 自然语言处理的 Python 库表格

以这种方式划分库非常有用,因为它是合乎逻辑的。我们需要首先收集、处理和分析数据,然后再做其他任何事情。在分析数据的过程中,我们应当进行数据可视化。某些库专注于自然语言处理,其他库专注于网络分析与可视化。最后,有些库对于不同类型的机器学习(ML)非常有用,甚至一些非 ML 专注的库也往往具有 ML 功能,例如 spaCy

我将保持这个介绍的高层次,因为我们将在接下来的章节中实际使用这些库。本章介绍的是关于给定库的“什么”和“为什么”的问题,其他章节将介绍它们的使用方法。

继续之前的最后一点:你不需要记住这些 Python 库的每个方面。在软件开发和数据科学中,技能是逐步积累的。现在学习对你有用的部分,然后如果某个库证明有用,再深入学习。不要因为只知道一些小部分而感到愧疚,知识是随着时间积累的,而不是一蹴而就的。

本章的内容将包括以下几个部分:

  • 使用笔记本

  • 数据分析与处理

  • 数据可视化

  • 自然语言处理(NLP)

  • 网络分析与可视化

  • 机器学习(ML)

技术要求

本章将介绍我们在全书中将使用的许多资源。

所有代码都可以在 GitHub 仓库中找到:github.com/PacktPublishing/Network-Science-with-Python.

使用笔记本

进行数据分析和原型开发时,通常最简单且非常有用的方法是使用我们常亲切地称为笔记本的工具。Jupyter 将 Jupyter Notebook 定义为一个基于网页的交互式计算平台。我喜欢这个简单的定义。笔记本本质上是一系列可以包含代码或文本的“单元”,这些单元可以单独运行,也可以顺序运行。这使得你可以在网页浏览器中编写代码,在网页浏览器中运行代码,并看到即时的结果。对于数据分析或实验,这种即时反馈非常有用。

在本书中,我们使用 Jupyter Notebook。我建议从 Anaconda 网站下载并安装它。你可以在www.anaconda.com进行下载。

在 Jupyter 中,你可以运行代码并查看该代码的即时结果,无论是文本、数字还是数据可视化。你将在本书中看到很多笔记本的使用,所以我会简短说明。

Google Colab 是另一种使用笔记本的选择,你无需安装任何东西即可使用它。这可能是使用笔记本的一种更简便的方式,但它也有优点和缺点。我建议你学习如何使用两者,并选择一个最适合你,或者允许你与他人高效合作的工具。

你可以查看 Google Colab,网址是 colab.research.google.com

接下来,让我们探索一下我们将在数据分析中使用的库。

数据分析与处理

在处理数据时,有许多有用的库,而在数据生命周期的不同阶段,你会希望使用不同的库和技术。例如,在数据处理时,探索性数据分析EDA)通常是一个有用的起点。之后,你可能需要进行数据清理、数据整理、预处理时的各种转换等等。下面是一些常用的 Python 库及其用途。

pandas

pandas 无疑是 Python 中进行数据处理时最重要的库之一。简而言之,如果你在 Python 中处理数据,你应该了解 pandas,并且很可能应该使用它。你可以在数据处理时使用它做多种不同的事情,例如:

  • 从各种文件类型或互联网读取数据

  • EDA

  • 提取、转换、 加载ETL

  • 简单快捷的数据可视化

  • 还有更多更多的内容

如果有一个 Python 库我会推荐给这个星球上的每一个人——不仅仅是数据专业人士——那就是 pandas。能够在不使用电子表格的情况下快速进行数据分析是非常解放且强大的。

设置

如果你在 Jupyter 或 Google Colab 笔记本中工作,pandas 很可能已经安装好了,你可以直接开始使用它。不过,如果需要,你可以按照官方安装指南操作:pandas.pydata.org/docs/getting_started/install.html

一旦安装完成,或者如果你怀疑它已经安装,只需在你喜欢的笔记本中运行这个语句:

import pandas as pd

如果 Python 没有提示库未安装,那么你就准备好了。

如果你想查看你正在使用的 pandas 版本,可以运行这个语句:

pd.__version__

我看到我正在运行版本 1.3.4。

启动功能

阅读数据是我帮助大家入门 pandas 的首选方式。毕竟,如果没有数据玩,数据工作会非常枯燥。在 pandas 中,你通常使用 DataFrame,这是一个类似于数据表或电子表格的数据结构。一个 pandas DataFrame 是一个由行和列组成的数据对象。

要将 CSV 文件读取到 pandas DataFrame 中,你可以像这样做:

data = 'data/fruit.csv'
df = pd.read_csv(data)

我想使用的数据位于当前目录的子目录 data 中,文件名为 fruit.csv

一旦数据被读取到 DataFrame 中,你可以预览数据:

df.head()

这将显示数据框的前五行预览。

图 3.2 – 简单的 pandas DataFrame

图 3.2 – 简单的 pandas DataFrame

你还可以将其他数据集读取到 pandas 中。例如,如果你已经安装了 scikit-learn,你可以执行如下操作:

from sklearn import datasets
iris = datasets.load_iris()
df = pd.DataFrame(iris['data'], columns=iris['feature_names'])
df.head()

这给我们展示了 图 3.3

图 3.3 – iris 数据集的 pandas DataFrame

图 3.3 – iris 数据集的 pandas DataFrame

pandas 不仅仅可以从 CSV 文件读取数据。它非常多才多艺,而且比其他加载 CSV 文件的方法更不容易出错。例如,我常常发现 pandas 可以毫无问题地处理这些数据。

什么是 Spark?

Spark 是一种常用于处理大量数据的技术。Spark 对于“大数据”工作负载非常有用。到了一定程度,庞大的数据量可能会超出像 pandas 这样的工具的处理能力,了解像 Spark 这样的更强大工具是非常有用的。

这里是一些其他的 pandas read_* 函数:

  • pd.read_clipboard()

  • pd.read_excel()

  • pd.read_feather()

  • pd.read_fwf()

  • pd.read_gbq()

  • pd.read_hdf()

  • pd.read_html()

  • pd.read_json()

  • pd.read_orc()

  • pd.read_parquet()

  • pd.read_pickle()

  • pd.read_sas()

  • pd.read_spss()

  • pd.read_sql()

  • pd.read_sql_query()

  • pd.read_sql_table()

  • pd.read_stata()

  • pd.read_table()

  • pd.read_xml()

这些看起来有点多,我个人并没有全部记住。我通常只使用其中的一小部分,比如 read_csvread_jsonread_parquet,但在紧急情况下,read_html 偶尔也会很有用。

对我而言,EDA(探索性数据分析)和简单的可视化是我迷上 pandas 的原因。例如,如果你想绘制直方图,可以这样做:

df['petal width (cm)'].plot.hist()

图 3.4 – 花瓣宽度直方图

图 3.4 – 花瓣宽度直方图

pandas能做的远不止我展示的内容。关于使用pandas进行数据分析的博客文章和书籍不计其数。专门讲解如何使用pandas的书籍也写了很多。我鼓励你买几本书,尽可能多地了解这个库。你对pandas了解得越多,你在处理数据时就会越得心应手。

文档

你可以通过访问这个网站来了解更多关于pandas的信息:pandas.pydata.org/docs/getting_started/overview.html

你可以将本书作为参考,以便更熟练地使用pandas,但也有许多在线指南可以提供动手练习,例如这个:pandas.pydata.org/docs/getting_started/intro_tutorials/01_table_oriented.html

NumPy

pandas非常适合对 DataFrame 进行数据分析,NumPy则更为通用,包含了各种各样的数学函数和转换。

那它和pandas有什么区别呢?这是一个很好的问题,因为这两个库通常是一起使用的。你在使用pandas时,通常也会看到NumPy。经过几年同时使用这两个库,我通常会先在pandas中尝试做某件事,发现做不到,然后发现NumPy可以做到。这两个库很好地协作,它们满足不同的需求。

使用pandas时,第一次读取 CSV 文件并绘制直方图总是令人兴奋。而使用NumPy时,没有什么特别令人兴奋的时刻,它只是非常有用。它是一个强大而多功能的工具,当pandas做不到你需要做的事情时,它在紧急情况下能为你省下 9 次中的 10 次。

安装

pandas一样,如果你在笔记本环境中工作,NumPy可能已经安装好了。不过,如果你需要跟随步骤操作,可以参考官方安装指南:numpy.org/install/

启动功能

如果你想玩一玩NumPy,感受一下它能做什么,可以查看他们的快速入门教程,比如numpy.org/doc/stable/user/quickstart.html。在教程中,他们解释了一些基本操作,比如生成随机数、重塑数组、打印数组等等。

文档

你可以在numpy.org/doc/stable/上了解更多关于NumPy的信息。在本书中,当pandas或其他库无法完成我们需要做的事情时,我们会在紧急情况下依赖NumPy,正如我之前描述的那样。通常,NumPy中有一个简单的解决方案。因此,注意我们对NumPy的使用,并了解其过程。

让我们从分析和处理转向我们将用来可视化数据的库。

数据可视化

有几个 Python 库可以用来进行数据可视化。Matplotlib 是一个很好的起点,但其他库,如 Seaborn 可以生成更具吸引力的可视化,而 Plotly 则可以生成交互式可视化。

Matplotlib

Matplotlib 是一个用于数据可视化的 Python 库。就是这样。如果你有数据,Matplotlib 很可能可以用来可视化它。该库已直接集成到 pandas 中,所以如果你使用 pandas,你很可能也在使用 Matplotlib

Matplotlib 的学习曲线非常陡峭,根本不直观。我认为如果你在学习 Python 数据科学,它是一个必需的恶魔。不管我用 Matplotlib 做多少数据可视化,它都永远不会变得容易,而且我记住的也很少。我这么说并不是为了贬低 Matplotlib,而是让你知道,如果你在使用这个库时感到挣扎,不要对自己感到沮丧。我们都在和这个库斗争。

设置

pandas 以及 NumPy 一样,如果你在一个笔记本环境中工作,Matplotlib 很可能已经安装好了。然而,如果你需要按照步骤进行,你可以按照官方安装指南操作:matplotlib.org/stable/users/installing/

启动功能

如果你使用 pandas 进行数据可视化,那么你已经在使用 Matplotlib 了。例如,在 pandas 的讨论中,我们使用了以下代码来绘制直方图:

df['petal width (cm)'].plot.hist()

像条形图这样的简单可视化可以很容易地完成。它们最直接地在 pandas 中完成。

例如,你可以创建一个水平条形图:

import matplotlib.pyplot as plt
 df['petal width (cm)'].value_counts().plot.barh(figsize=(8,6)).invert_yaxis()

这将渲染一个非常简单的数据可视化。

图 3.5 – 花瓣宽度水平条形图

图 3.5 – 花瓣宽度水平条形图

文档

Matplotlib 的教程部分可能是开始学习的最佳地方:matplotlib.org/stable/tutorials/

然而,在本书中,我们将大量使用 pandasMatplotlib,所以你应该通过跟随和练习,掌握相当多的知识。

Seaborn

Seaborn 本质上是“美化版的 Matplotlib。”它是 Matplotlib 的扩展,能够生成更具吸引力的数据可视化。缺点是,一些在 Matplotlib 中完全不直观的东西,在 Seaborn 中变得更不直观。它有着像 Matplotlib 一样的学习曲线,而且学习永远不会记住。我发现每次需要制作可视化时,我都在谷歌搜索。

然而,一旦你开始弄清楚事情,它也没有那么糟。你会开始记得你克服的困难,并且处理那些应该很简单的难题(比如调整可视化的大小)随着时间的推移变得稍微不那么烦人。

这些可视化效果美观,远胜于 Matplotlib 的默认设置。浏览 Seaborn 可视化示例目录非常有趣:seaborn.pydata.org/examples/.

我们在本书中偶尔会使用 Seaborn,但大多数情况下会使用 Matplotlibscikit-network 来进行可视化。不过,还是要了解这个库,它非常重要。

设置

我不记得笔记本是否已经安装了 Seaborn。如果现在已经安装了,我也不会感到惊讶。如果你需要安装 Seaborn,可以参考官方安装指南:seaborn.pydata.org/installing.html

启动功能

Seaborn 能做 Matplotlib 可以做的事,但做得更好、更漂亮。例如,让我们重新做一下之前创建的直方图,并加上 核密度估计KDE):

import seaborn as sns
sns.histplot(df['petal width (cm)'], kde=True)

这将生成以下图表:

图 3.6 – 带有 KDE 的 Seaborn 花瓣宽度直方图

图 3.6 – 带有 KDE 的 Seaborn 花瓣宽度直方图

学习 Seaborn 最好的方式就是直接上手。获取一些数据,然后尝试将其可视化。浏览他们的示例画廊,找一些与你的数据相符的例子,然后尝试构建相同的内容。最开始可以复制他们的代码,并尝试调整参数。

文档

你可以通过他们的网站了解更多关于 Seaborn 的信息:seaborn.pydata.org/

Plotly

说实话,我们在本书中不会经常使用 Plotly,但是有一章需要使用交互式散点图,Plotly 提供了一个非常有用的可视化工具。不过,了解 Plotly 还是很有用的,它在需要简单交互的数据可视化时可以成为一个强大的工具。

Plotly 宣传称其可以制作交互式、出版质量的图表。仅此而已。它本质上做的是 MatplotlibSeaborn 所做的工作,但以交互的方式进行。将交互功能添加到 Matplotlib 是可能的,但过程比较痛苦。使用 Plotly,则既简单又快速,而且过程充满乐趣。

设置

若要安装 Plotly,请参考其官方指南:plotly.com/python/getting-started/#installation

启动功能

他们的网站有一个非常有趣和互动的画廊,可以激发创意。我建议你先阅读他们的入门指南,探索他们的示例:plotly.com/python/getting-started/

我们将在 sw 中使用 Plotly《网络数据的无监督机器学习》

图 3.7 – Plotly 交互式散点图

图 3.7 – Plotly 交互式散点图

文档

你可以在他们的网站上学到更多关于 Plotly 的知识:plotly.com/python/

现在我们已经介绍了一些用于数据可视化的有用库,接下来我们将做同样的事情,针对自然语言处理(NLP)和文本处理。

自然语言处理(NLP)

从数据可视化转到 NLP,仍有几个 Python 库会对我们处理文本数据有所帮助。每个库都有独特的功能、优点和缺点,文档需要仔细阅读。

自然语言工具包

NLTK是一个较老的 Python 库,通常对于曾经用NLTK完成的任务(例如命名实体识别或词性标注),使用spaCy等其他库会更好。

然而,仅仅因为它较老并不意味着它已经过时。它仍然非常适合分析文本数据,并且提供了比spaCy等库更多的语言学功能。

简而言之,NLTK是做 Python NLP 的基础性库,你不应跳过它,去直接使用更新的库和方法。

设置

安装NLTK非常简单。只需按照指南操作:www.nltk.org/install.html

启动功能

在本书中,我们将大量使用NLTK。当你准备好时,跳入下一章,我们会立刻开始。作为入门功能,并没有什么特别值得展示的。随着学习的深入,NLP 变得更为有趣。起初,它至少有点让人困惑。

文档

NLTK网站作为学习工具并不太有用,但你可以在这里看到一些代码片段和示例:www.nltk.org

如今,你很可能通过博客文章(TowardsDataScience等)了解NLTK,而非通过官方NLTK网站。如果你想从其他来源学习,可以搜索“nltk getting started”,会找到一些有用的指南。

本书将足以帮助你入门NLTK,但我确实建议向其他撰写关于 NLP 文章的作者学习。NLP 有太多内容值得学习和探索。

spaCy

NLTK。例如,如果你正在做命名实体识别、词性标注或词形还原,我建议你使用spaCy,因为它更快捷且简单,能够获得较好的结果。然而,NLTK的数据加载器、停用词、分词器和其他语言学工具依然非常有用,而且有时候spaCy无法匹敌。个人认为,如果你从事 Python 中的 NLP 工作,应该同时学习NLTKspaCy,而不仅仅是spaCy。了解两者,并记住spaCy在某些方面比NLTK更强。

设置

要安装spaCy,请使用官方安装指南:spacy.io/usage#quickstart

启动功能

在本书中,你将看到spaCy的使用,尤其是在命名实体识别方面,这对图谱构建非常有用。如果你想看到它的实际应用,可以跳到后面。

例如,让我们安装语言模型,如下所示:

!python -m spacy download en_core_web_sm

我们可以运行这段代码:

import spacy
nlp = spacy.load("en_core_web_sm")
doc = nlp("David Knickerbocker wrote this book, and today is September 4, 2022.")
for ent in doc.ents:
    print(ent.text, ent.label_)

这将输出一些命名实体识别(NER)结果:

David Knickerbocker PERSON
today DATE
September 4, 2022 DATE

你可能会想,它是怎么做到的呢?是魔法!开个玩笑——其实是机器学习。

文档

NLTK不同,spaCy的文档非常详尽且有帮助,而且看起来现代化。我建议你花大量时间在他们的网站上,了解它的功能。spaCy能做的事情远比本书所涵盖的要多。要了解更多内容,请访问spacy.io/usage/linguistic-features

网络分析与可视化

我们接下来要使用的几种库对于分析和可视化各种类型的网络很有用。网络可视化是数据可视化的另一种形式,但专门针对网络。没有网络可视化软件,创建有用的网络可视化是非常困难和繁琐的。

NetworkX

NetworkX,没有比这更好的资源来进行 Python 中的网络分析了。

还有其他库可以用于非常简单的网络可视化。在本书中,我忽略了这些,并展示了如何使用NetworkXscikit-network进行良好的网络分析并制作出相对美观的网络可视化。

还有其他一些库可以与NetworkX一起使用,用于社区检测或分析网络的脆弱性等,但NetworkX是核心。我写这本书的目的是传播网络科学的知识以及在软件开发中使用网络的理念,而NetworkX对于这一目标至关重要。我希望更多的软件工程师能够掌握这些技能。

设置

请按照官方指南安装NetworkXnetworkx.org/documentation/stable/install.html

启动功能

我们将用NetworkX做很多非常酷的事情,但如果你想确认它是否正常工作,你可以运行这段代码:

import networkx as nx
G = nx.les_miserables_graph()
sorted(G.nodes)[0:10]

在一本笔记本中,这应该显示一个 Python 列表,包含悲惨世界图中的前 10 个节点:

['Anzelma',
 'Babet',
 'Bahorel',
 'Bamatabois',
 'BaronessT',
 'Blacheville',
 'Bossuet',
 'Boulatruelle',
 'Brevet',
 'Brujon']

或者,你也可以这样做,快速获取悲惨世界图的概览:

print(nx.info(G))
Graph with 77 nodes and 254 edges

文档

学习NetworkX的最佳方式可能就是阅读这本书。关于如何使用NetworkX的书籍非常少。也有一些博客文章,NetworkX网站上的一些指南也很有帮助。这里有一个,但它假设你已经理解了图的概念:networkx.org/documentation/stable/tutorial.html

该网站确实有非常详细的文档,介绍了不同工具的使用:networkx.org/documentation/stable/reference/

我最喜欢他们在线资源的一部分是,他们会指向关于已实现功能的学术论文。例如,如果你访问链接预测部分中的优先附加页面,你会看到这个块:

图 3.8 – NetworkX 参考

图 3.8 – NetworkX 参考

在多次场合中,我很喜欢深入研究开发几个算法背后的编写工作。了解背景是很有意义的,算法绝对不应该被盲目使用。如果你要使用一个工具,了解它的工作原理,以及它在哪些方面可能不起作用。

scikit-network

NetworkX的可视化速度很慢,以至于我不会教它们。即使在小型网络上,它们的渲染也需要很长时间,而且其外观单调且基础。另一方面,scikit-network的可视化加载非常快速,因为它们是以 SVG 格式渲染的。即使你有成百上千个节点,加载速度也很合理。

然而,将其视为一个用于分析大规模图形的包并不准确。从本质上讲,这是一款可视化软件,尽管它的功能稍微超出了这个范围。看起来他们已经加入了一些额外的功能(例如图神经网络GNNs)),但大多数文档页面还是侧重于可视化。不过,还是可以看看他们在嵌入和社区检测方面的工作。似乎他们正在朝着超越网络可视化的方向发展,这个库也在不断演化。

设置

要安装scikit-network,请按照官方指南操作:scikit-network.readthedocs.io/en/latest/first_steps.html

启动功能

在本书中,我们将仅使用scikit-network进行网络可视化,因此如果你想开始使用它,请查看他们的一些教程,例如scikit-network.readthedocs.io/en/latest/tutorials/visualization/graphs.html。有多个教程可以帮助你深入了解这个库。

文档

scikit-network的文档非常好且组织得很有条理。文档的简洁性表明它实际上并不是一个非常深奥的 Python 库。如果你想深入了解,先从这里开始:scikit-network.readthedocs.io/en/latest/

你可能会想,既然scikit-network还提供一些机器学习、社区检测和其他功能,为什么我只用它来做可视化呢?原因是这样的:NetworkXKarateClub也提供类似的功能,而且它们做得更好。例如,KarateClub提供的功能更强,而NetworkX提供的scikit-network功能也能很好地实现,而且更干净。

这就是为什么我只展示它在可视化方面的使用。其他功能并未表现得比NetworkXKarateClub提供的更好。这并非针对个人。每个人都有自己喜欢的工具。

在接下来的章节中,我们将讨论可以用于机器学习的库。

机器学习

Python 还有一组用于机器学习的库。了解 Python 库和算法是不够的。请尽可能多地了解你将使用的模型和技术。总是有更多东西可以学习。以下是你可能经常使用的一些库。

scikit-learn

TensorFlowPyTorch,但我们在本书中不会直接使用它们。只需知道它们的存在,并且随着你的机器学习技能的提高,你应该了解它们。

如果你正在学习或使用 Python 进行机器学习,你很快就会接触到scikit-learnscikit-learn 提供了大量的机器学习模型、数据加载器、预处理器、转换器等等。有很多关于scikit-learn的书籍和博客文章,包括一些由 Packt 出版的。如果你有兴趣学习机器学习,你会阅读很多内容,而scikit-learn是一个很好的起点。

设置

如果你在使用笔记本,那么scikit-learn可能已经安装好了。但是,如果你需要安装它,请按照官方指南操作:scikit-learn.org/stable/install.html

初学者功能

机器学习很复杂,构建和使用模型需要多个步骤。现在,只需确保安装了scikit-learn并且能够查看运行的版本:

import sklearn
sklearn.__version__

对我来说,这表明我正在运行版本1.0.2。对于本书的练习,我们不需要精确匹配,但是在实际部署中,所有库的版本应保持一致以确保可复现性。

文档

关于scikit-learn的资源比我在本章提到的其他每个 Python 库的资源都要多。机器学习是一个热门话题,对书籍、博客文章、视频和其他教程的需求很大,因此学习资源丰富。然而,scikit-learn文档是开始学习的地方。

scikit-learn入门指南可以在这里找到:scikit-learn.org/stable/getting_started.html

完整的用户指南可以在scikit-learn.org/stable/user_guide.html找到,并且还有一个 API 参考文档在scikit-learn.org/stable/modules/classes.html

在本书中,我们将进行一些监督和无监督的机器学习,但不会深入讨论。如果你有兴趣,还有很多东西可以学习。本书只是浅尝辄止地介绍了机器学习,希望能引发你的兴趣。

空手道俱乐部

NetworkX。换句话说,Karate Club弥合了机器学习和图形分析之间的差距。它提供了几种有用的无监督学习模型,可用于社区检测以及从图形中创建节点嵌入,这些嵌入可以用于下游的有监督学习任务。这通常被称为图形机器学习,且目前人们对将图形和机器学习结合使用非常感兴趣,例如预测新连接(谁应该成为朋友,谁会喜欢某种音乐或产品等)。

这和scikit-learn有什么区别?scikit-learn可以用于任何数字数据,而Karate Club专注于通过无监督机器学习将图形转换为数字数据。Karate Club的输出可以作为scikit-learn的输入,但反过来却很少。

要使Karate Club有用,你需要图形(NetworkX)和机器学习(scikit-learnTensorFlowPyTorch等)。Karate Club本身的用途有限。

Karate Club尚不支持更高版本的 Python,如 3.10。

设置

要安装Karate Club,请按照官方指南:karateclub.readthedocs.io/en/latest/notes/installation.html

入门功能

没有值得展示的入门功能。机器学习很复杂,需要几个步骤才能真正有用。现在,我们只需要验证库是否已安装,并且可以看到版本号:

import karateclub
karateclub.__version__

我可以看到我正在运行的版本是1.3.0

文档

Karate Club提供了一个入门指南:karateclub.readthedocs.io/en/latest/notes/introduction.html。在这里,你可以开始了解如何使用该库。你还可能会注意到一些对其他库的引用,如scikit-learn

Karate Club拥有出色的文档。你可以在karateclub.readthedocs.io/en/latest/modules/root.html找到它。我最喜欢的一点是,他们会引用关于这些模型的期刊文章。例如,如果你查看任何算法的文档,通常会找到提到关于该模型的论文。

图 3.9 – 空手道俱乐部文档参考

图 3.9 – 空手道俱乐部文档参考

在本书中,我查看了多个Karate Club模型,因此有机会阅读许多相关的论文,这也给了我很多小时的乐趣。我非常喜欢这个。

如果你在代码中使用Karate Club,你也可以在笔记本中看到提到这篇论文的内容,只需在函数调用时按下Shift + Tab

图 3.10 – 空手道俱乐部代码参考

图 3.10 – 空手道俱乐部代码参考

这个库不仅使用起来令人兴奋,学习它也具有很高的教育价值。

spaCy(再探)

是的,我又提到spaCy了。为什么?因为spaCy提供了多个语言模型,而且涵盖了多种语言。这意味着你可以将spaCy预训练的机器学习模型用于自己的目的,例如用于命名实体识别。它们的模型已经过训练,可以直接导入并使用。在本书的接下来的几章中,你将学习如何做到这一点。

总结

本书中还会使用其他一些 Python 库,但它们将在相关章节中进行解释。在本章中,我想描述我们将用于工作的主要库。为了能够进入本书讨论的真正实验性内容,我们需要打下基础。

例如,你需要能够读取和分析表格(结构化)数据。你还需要能够将数据可视化。对于文本,你需要能够将文本转换为适合分析和使用的格式。对于图表,你也需要具备相同的能力。最后,如果你想将机器学习应用于网络或文本,你应该理解如何做到这一点。

这就是为什么本节被分解为数据分析与处理、数据可视化、自然语言处理、网络分析与可视化以及机器学习。我希望这样的结构能有所帮助。

在安装并简要解释了这些库之后,我们现在可以开始深入探索网络科学、社交网络分析和自然语言处理,所有这些都将在 Python 中进行。

第二部分:图表构建与清理

网络数据并不总是存在于分析师感兴趣的研究中。本部分的章节展示了如何从各种在线资源中提取文本数据,并将其转换为可以分析的网络。这些章节还介绍了数据处理步骤,用于清理网络,以便进行分析和后续处理。

本节包括以下章节:

  • 第四章*,自然语言处理与网络协同作用*

  • 第五章*,更简单的网页抓取*

  • 第六章*,图表构建与清理*

第四章:NLP 和网络的协同作用

在前几章中,我们讨论了自然语言处理NLP)、网络分析以及在 Python 编程语言中用于这两者的工具。我们还讨论了用于进行网络分析的非编程工具。

在本章中,我们将把所有这些知识付诸实践。我希望能解释通过结合 NLP 和网络分析所揭示的强大功能和洞察力,这也是本书的主题。在后续章节中,我们将继续围绕这一主题展开讨论,同时还会涉及其他用于处理 NLP 和网络的工具,如无监督和监督的机器学习。本章将展示确定文本片段所讲述的对象或内容的技术。

本章将涵盖以下主题:

  • 为什么我们在一本网络书中学习 NLP?

  • 提出问题讲述故事

  • 介绍网页抓取

  • 在库、API 和源数据之间进行选择

  • 使用自然语言工具包(NLTK)库进行词性标注(PoS)

  • 使用 spaCy 进行词性标注和命名实体识别(NER)

  • 将实体列表转换为网络数据

  • 将网络数据转换为网络

  • 做网络可视化的抽查

技术要求

在本章中,我们将使用几种不同的 Python 库。在每一节中都会列出pip install命令来安装每个库,因此只需要跟着做,按需进行安装。如果遇到安装问题,通常可以在 Stack Overflow 上找到答案。用 Google 搜索错误信息!

在开始之前,我想解释一件事,这样我们使用的库的数量就不会显得那么令人不知所措。重要的是我们使用每个库的理由。

本书的大部分内容将会做三件事之一:网络分析、网络可视化,或使用网络数据进行机器学习(也称为 GraphML)。

每当我们处理网络数据时,我们都会使用NetworkX来操作它。

每当我们进行分析时,可能会使用pandas

关系看起来是这样的:

  • NetworkX

  • pandas

  • scikit-network

  • scikit-learnKarate Club

看看最开始的几个词。如果我要进行网络分析,我会使用NetworkXpandas。如果我要做网络可视化,我会使用NetworkXscikit-network。如果我要用网络数据做机器学习,我会使用NetworkXscikit-learn,可能还会使用Karate Club

这些是本章中将使用的核心库。

此外,你必须随时准备好draw_graph() 函数的代码,因为你将在本书中多次使用它。那段代码有点复杂,因为在写作时它确实需要这样做。不幸的是,NetworkX在网络可视化方面并不优秀,而scikit-network在网络构建或分析方面也不够强大,所以我将两者结合起来进行可视化,这效果很好。我希望这本书的后续版本中,网络可视化能够得到改进和简化。

所有必要的代码可以在github.com/PacktPublishing/Network-Science-with-Python找到。

为什么我们在一本网络书中学习 NLP?

我在第一章的介绍中简要回答了这个问题,但值得更详细地重复一遍。许多从事文本分析工作的人都知道情感分析和文本分类。文本分类是预测一段文本是否可以被归类为某种类型的能力。例如,我们来看这个字符串:

你今天 怎么样?

我们能从这段字符串中得出什么结论?这是一个问题还是陈述?这是一个问题。这个问题是问谁的?是问你。这个问题中有积极、消极还是中立的情绪?我觉得它看起来是中立的。我们再试试另一段字符串。

Jack 和 Jill 爬上了山坡,但 Jack 跌倒了,因为他是 个傻瓜。

这是关于 Jack 和 Jill 的陈述,其中 Jack 被称为傻瓜,这并不是个很友善的说法。然而,这个侮辱似乎是开玩笑写的,因此不清楚作者写的时候是生气还是在笑。我可以确认,在写这句话时我是在笑的,所以其中包含了积极的情绪,但文本分类可能难以识别这一点。我们再试试另一个。

我这一生从未像现在这样愤怒过! 真是愤怒到了极点!

作者表达了非常强烈的负面情绪。情感分析和文本分类很容易识别这一点。

情感分析是一种使用算法自动检测文本或转录记录中嵌入的情绪的技术集合。文本分类使用相同的算法,但其目标不是识别情绪,而是识别主题。例如,文本分类可以检测文本中是否存在侮辱性语言。

这不是关于情感分析或文本分类的章节。本章节的目的是解释如何自动提取文本中存在的实体(人物、地点、事物等),以便识别和研究文本中描述的社交网络。然而,情感分析和文本分类可以与这些技术结合,为社交网络提供更多上下文,或者用于构建专门的社交网络,例如友谊网络。

提出问题来讲述故事

我从讲故事的角度进行工作;我让我的故事决定工作,而不是反过来。例如,如果我开始一个项目,我会考虑,甚至写下关于谁、什么、哪里、何时、为什么和如何的一系列问题:

  • 我们有哪些数据?足够吗?

  • 我们从哪里获得更多数据?

  • 我们如何获得更多数据?

  • 什么阻碍了我们获取更多数据?

  • 我们多久需要更多数据?

但这是一个不同类型的项目。我们想要深入了解一篇文本的内容,而不仅仅是通过阅读来获得信息。即使读完一本书后,大多数人也无法记住文本中描述的关系,而且即使记得,也可能是错误的回忆。但我们应该有这样的疑问:

  • 文中提到了谁?

  • 他们认识谁?

  • 他们的对手是谁?

  • 这篇文章的主题是什么?

  • 存在哪些情感?

  • 文中提到了哪些地方?

  • 这件事发生在什么时候?

在开始任何形式的分析之前,制定一套可靠的问题非常重要。深入分析时,你会遇到更多的问题。

本章将为你提供工具,自动调查所有这些问题,除了关于主题和对手的问题。本章的知识,再加上对文本分类和情感分析的理解,将使你能够回答所有这些问题。这就是本章的“为什么”。我们希望自动提取文中提到的人、地点,甚至可能还有一些事物。大多数时候,我只想要人物和地点,因为我想研究社交网络。

然而,重要的是要解释清楚,这对于辅助文本分类和情感分析非常有用。例如,如果你不知道正在评审的内容,正面的亚马逊或 Yelp 评论几乎没有什么意义。

在我们进行任何揭示文本中存在的关系的工作之前,我们需要获取一些文本。作为练习,我们有几个选择。我们可以使用像 NLTK 这样的 Python 库加载它,我们可以使用 Twitter(或其他社交网络)库收集它,或者我们可以自己抓取它。即使是抓取也有不同的方式,但我只会解释一种方法:使用 Python 的BeautifulSoup库。只要知道有很多选择,但我喜欢BeautifulSoup的灵活性。

在本章中,演示将使用从互联网上抓取的文本,你可以对代码做些小修改,以适应你自己的网页抓取需求。

介绍网页抓取

首先,什么是 网页抓取,谁能做呢?任何具有编程技能的人都可以使用几种不同的编程语言进行抓取,但我们将使用 Python。网页抓取是从网络资源中获取内容的行为,您可以将数据用于您的产品和软件。您可以使用抓取来获取网站没有通过数据馈送或 API 暴露的信息。但有一个警告:不要抓取得过于激进,否则您可能会通过意外的 拒绝服务攻击DoS)击垮一个网站服务器。只获取您需要的内容,按需获取。慢慢来,不要贪婪或自私。

介绍 BeautifulSoup

BeautifulSoup 是一个强大的 Python 库,用于抓取您可以访问的任何在线内容。我经常用它从新闻网站收集故事的 URL,然后我抓取这些 URL 的文本内容。我通常不需要实际的 HTML、CSS 或 JavaScript,所以我会渲染网页,然后抓取内容。

BeautifulSoup 是一个重要的 Python 库,如果您打算进行网页抓取,了解它非常重要。虽然 Python 还有其他网页抓取选项,但 BeautifulSoup 是最常用的。

没有什么比看到 BeautifulSoup 在实际操作中的效果更能解释它的了,所以让我们开始吧!

使用 BeautifulSoup 加载和抓取数据

在这个实践演示中,我们将看到三种不同的加载和抓取数据的方式。它们各自独立都有用,但也可以以多种方式结合使用。

首先,最简单的方法是使用一个库,一次性获取您想要的内容,且需要最少的清理。多个库,如 pandasWikipediaNLTK 都有加载数据的方法,让我们从这些开始。由于本书主要讲解如何从文本中提取关系并进行分析,因此我们需要文本数据。我将演示几种方法,然后描述每种方法的优缺点。

Python 库 – Wikipedia

有一个强大的库可以从维基百科中提取数据,叫做 Wikipedia。您可以通过运行以下命令安装它:

pip install wikipedia

安装完成后,您可以像这样将其导入到代码中:

import wikipedia as wiki

一旦导入,您就可以通过编程方式访问维基百科,允许您搜索并使用任何您感兴趣的内容。由于在本书中我们将进行大量的社交网络分析,让我们看看 Wikipedia 对该主题有什么内容:

search_string = 'Social Network Analysis'
page = wiki.page(search_string)
content = page.content
content[0:680]

最后一行,content[0:680],仅显示 content 字符串中的内容,直到第 680 个字符,这是以下代码中展示的句子的结束部分。680 之后还有很多内容。我选择在此演示中截断它:

'Social network analysis (SNA) is the process of investigating social structures  through the use of networks and graph theory. It characterizes networked structures in terms of nodes (individual actors, people, or things within the network) and the ties, edges, or links (relationships or interactions) that connect them.  Examples of social structures commonly visualized through social network analysis include social media networks, memes spread, information circulation, friendship and acquaintance networks, business networks, knowledge networks, difficult working relationships, social networks, collaboration graphs, kinship, disease transmission, and sexual relationships.'

通过几行代码,我们能够直接从维基百科中提取文本数据。我们能看到那个维基百科页面上有什么链接吗?是的,我们可以!

links = page.links
links[0:10]

我使用方括号[,只选择 links 中的前 10 项:

['Actor-network theory',
 'Adjacency list',
 'Adjacency matrix',
 'Adolescent cliques',
 'Agent-based model',
 'Algorithm',
 'Alpha centrality',
 'Anatol Rapoport',
 'Anthropology',
 'ArXiv (identifier)']

如果我们仅选择前 10 个链接并只在 A 链接中,那么我认为可以安全地说,可能还有更多。维基百科上关于社交网络分析的内容非常丰富!这真的很简单!让我们继续尝试另一种便捷的抓取方法:pandas库。

Python 库 – pandas

如果你有兴趣使用 Python 进行数据科学,你将不可避免地学习和使用pandas。这个库在处理数据时非常强大和多功能。在这个演示中,我将用它从网页中提取表格数据,但它可以做更多的事情。如果你对数据科学感兴趣,尽可能多地学习关于pandas的知识,并熟悉它的使用。

pandas可以从网页中提取表格数据,但它对原始文本的处理能力较弱。如果你想从维基百科加载文本,应该使用之前提到的Wikipedia库。如果你想从其他网页提取文本,则应该同时使用RequestsBeautifulSoup库。

让我们使用pandas从维基百科提取一些表格数据。首先,尝试使用pandas抓取同一个维基百科页面,看看我们能得到什么。如果你已经在电脑上安装了 Jupyter,那么很可能你已经安装了pandas,所以让我们直接进入代码:

  1. 首先导入pandas库:

    import pandas as pd
    
    url = 'https://en.wikipedia.org/wiki/Social_network_analysis'
    
    data = pd.read_html(url)
    
    type(data)
    

在这里,我们导入了pandas库并给它起了一个简短的名字,然后使用pandas读取了关于社交网络分析的同一维基百科页面。我们得到了什么?type(data)显示了什么?我预计会得到一个pandas DataFrame。

  1. 输入以下代码。只需输入data并运行代码:

    data
    

你应该看到我们得到了一个列表。在pandas中,如果你执行read操作,通常会得到一个 DataFrame,那么为什么我们得到了一个列表呢?这是因为该页面上有多个数据表,因此pandas返回了所有表格的 Python 列表。

  1. 让我们检查一下列表的元素:

    data[0]
    

这应该会给我们一个来自维基百科页面的第一个表格的pandas DataFrame:

图 4.1 – pandas DataFrame 显示维基百科数据的第一个元素

图 4.1 – pandas DataFrame 显示维基百科数据的第一个元素

这些数据看起来有点问题。为什么我们在表格中看到一堆文本?为什么最后一行看起来像是一堆代码?下一个表格是什么样子的呢?

data[1]

这将给我们一个来自同一维基百科页面的第二个表格的pandas DataFrame。请注意,维基百科页面偶尔会被编辑,因此您的结果可能会有所不同:

图 4.2 – pandas DataFrame 显示维基百科数据的第二个元素

图 4.2 – pandas DataFrame 显示维基百科数据的第二个元素

糟糕。这看起来更糟。我之所以这么说,是因为似乎有一些非常短的字符串,看起来像是网页部分。这看起来不太有用。记住,pandas非常适合加载表格数据,但对于维基百科使用的文本数据表格并不适用。我们已经看到,这比我们通过维基百科库轻松捕获的数据更没用。

  1. 让我们尝试一个包含有用数据的页面。这个页面包含关于俄勒冈州犯罪的表格数据:

    url = 'https://en.wikipedia.org/wiki/Crime_in_Oregon'
    
    data = pd.read_html(url)
    
    df = data[1]
    
    df.tail()
    

这将展示一个包含俄勒冈州犯罪数据的Pandas数据框架中的最后五行:

图 4.3 – Pandas 数据框架中的维基百科表格数值数据

图 4.3 – Pandas 数据框架中的维基百科表格数值数据

哇,这看起来是有用的数据。然而,数据中没有显示 2009 年以后的信息,这已经是相当久远的时间了。也许有一个更好的数据集,我们应该使用那个。无论如何,这显示了Pandas能够轻松地从网上抓取表格数据。然而,有几点需要注意。

首先,如果你在项目中使用抓取的数据,知道自己已经把自己交给了网站管理员的支配。如果他们决定丢弃数据表,或者重命名或重新排序列,那么直接从维基百科读取数据的应用程序可能会崩溃。你可以通过在抓取数据时保留本地数据副本以及在代码中加入错误处理来防范这种情况。

为最坏的情况做好准备。当你进行数据抓取时,要建立必要的错误检查机制,并且知道何时抓取程序无法再拉取数据。接下来我们将转向下一种方法——使用NLTK

Python 库 – NLTK

让我们直奔主题:

  1. 首先,NLTK 并不随 Jupyter 安装,因此你需要自行安装。你可以使用以下命令安装:

    pip install nltk
    
  2. Python 的NLTK库可以轻松地从古腾堡计划中提取数据,古腾堡计划是一个包含超过 60,000 本免费书籍的库。让我们看看我们能获取到什么:

    from nltk.corpus import gutenberg
    
    gutenberg.fileids()
    
    ['austen-emma.txt',
    
     'austen-persuasion.txt',
    
     'austen-sense.txt',
    
     'bible-kjv.txt',
    
     'blake-poems.txt',
    
     'bryant-stories.txt',
    
     'burgess-busterbrown.txt',
    
     'carroll-alice.txt',
    
     'chesterton-ball.txt',
    
     'chesterton-brown.txt',
    
     'chesterton-thursday.txt',
    
     'edgeworth-parents.txt',
    
     'melville-moby_dick.txt',
    
     'milton-paradise.txt',
    
     'shakespeare-caesar.txt',
    
     'shakespeare-hamlet.txt',
    
     'shakespeare-macbeth.txt',
    
     'whitman-leaves.txt']
    

如你所见,结果远远少于 60,000 个,因此我们通过这种方式能获取的内容有限,但这仍然是练习自然语言处理(NLP)有用的数据。

  1. 让我们看看blake-poems.txt文件中有什么内容:

    file = 'blake-poems.txt'
    
    data = gutenberg.raw(file)
    
    data[0:600]
    
    '[Poems by William Blake 1789]\n\n \nSONGS OF INNOCENCE AND OF EXPERIENCE\nand THE BOOK of THEL\n\n\n SONGS OF INNOCENCE\n \n \n INTRODUCTION\n \n Piping down the valleys wild,\n   Piping songs of pleasant glee,\n On a cloud I saw a child,\n   And he laughing said to me:\n \n "Pipe a song about a Lamb!"\n   So I piped with merry cheer.\n "Piper, pipe that song again;"\n   So I piped: he wept to hear.\n \n "Drop thy pipe, thy happy pipe;\n   Sing thy songs of happy cheer:!"\n So I sang the same again,\n   While he wept with joy to hear.\n \n "Piper, sit thee down and write\n   In a book, that all may read."\n So he vanish\'d'
    

我们可以加载整个文件。文件内容比较凌乱,包含了换行符和其他格式,但我们会清理这些内容。如果我们想要获取不在此列表中的其他书籍,而这些书籍位于包含 60,000 本书的完整库中,该怎么办呢?我们就没机会了吗?浏览网站时,我看到我可以阅读我最喜欢的书之一——弗朗茨·卡夫卡的*《变形记》*,网址是www.gutenberg.org/files/5200/5200-0.txt。让我们尝试获取这个数据,不过这次我们将使用 Python 的 Requests 库。

Python 库 – Requests

Requests库随 Python 预安装,所以你只需要导入它。Requests 用于从网页抓取原始文本,但它的功能不止如此。请研究这个库,了解它的更多功能。

重要提示

如果你使用这种方法,请注意不要过于激进。像这样一次加载一本书是可以的,但如果你尝试一次性下载太多书籍,或者在古腾堡项目上过于激烈地抓取所有书籍,你很可能会暂时被封禁 IP 地址。

在我们的演示中,首先导入库,然后从古腾堡提供的变形记中抓取原始文本:

import requests
url = 'https://www.gutenberg.org/files/5200/5200-0.txt'
data = requests.get(url).text
data
…
'The Project Gutenberg eBook of Metamorphosis, by Franz Kafka\r\n\r\nThis eBook is for the use of anyone anywhere in the United States and\r\nmost other parts of the world at no cost and with almost no restrictions\r\nwhatsoever. You may copy it, give it away or re-use it under the terms\r\nof the Project Gutenberg License included with this eBook or online at\r\nwww.gutenberg.org. If you are not located in the United States, you\r\nwill have to check the laws of the country where you are located before\r\nusing this eBook.\r\n\r\n** This is a COPYRIGHTED Project Gutenberg eBook, Details Below **\r\n**     Please follow the copyright guidelines in this file.     *\r\n\r\nTitle: Metamorphosis\r\n\r\nAuthor: Franz Kafka\r\n\r\nTranslator: David Wyllie\r\n\r\nRelease Date: May 13, 2002 [eBook #5200]\r\n[Most recently updated: May 20, 2012]\r\n\r\nLanguage: English\r\n\r\nCharacter set encoding: UTF-8\r\n\r\nCopyright (C) 2002 by David Wyllie.\r\n\r\n*** START OF THE PROJECT GUTENBERG EBOOK METAMORPHOSIS ***\r\n\r\n\r\n\r\n\r\nMetamorphosis\r\n\r\nby Franz Kafka\r\n\r\nTranslated by David Wyllie\r\n\r\n\r\n\r\n\r\nI\r\n\r\n\r\nOne morning, when Gregor Samsa woke from troubled dreams, he found\r\nhimself transformed in his bed into a horrible vermin…'

我将所有在“vermin”之后的文本剪切掉,目的是简要展示数据中的内容。就这样,我们得到了整本书的完整文本。正如在 NLTK 中一样,数据充满了格式和其他字符,因此这些数据需要清理才能变得有用。清理是本书中我将解释的每一部分的一个非常重要的环节。我们继续吧;在这些演示中,我会展示一些清理文本的方法。

我已经展示了从古腾堡或维基百科提取文本是多么简单。但这两者只是互联网中可供抓取的内容的一小部分。pandas可以从任何网页读取表格数据,但它是有限的。大部分网页内容并非完美格式化或干净的。如果我们想要设置爬虫从我们感兴趣的各种新闻网站上收集文本和内容怎么办?NLTK 无法帮助我们获取这些数据,而 Pandas 在返回的数据上也有限制。我们有哪些选择呢?我们看到Requests库能够拉取另一本通过 NLTK 无法获取的古腾堡书籍。我们能否像这样使用 requests 从网站抓取 HTML 呢?让我们尝试抓取一些来自日本冲绳的新闻吧!

url = 'http://english.ryukyushimpo.jp/'
data = requests.get(url).text
data
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r\n<html  dir="ltr" lang="en-US">\r\n\r\n<!-- BEGIN html head -->\r\n<head profile="http://gmpg.org/xfn/11">\r\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />\r\n<title>Ryukyu Shimpo - Okinawa, Japanese newspaper, local news</title>…'

好了!我们刚刚从给定的 URL 加载了原始 HTML,我们可以对任何公开访问的网页进行相同的操作。但是,如果你认为古腾堡的数据已经很杂乱,那看看这个!我们在这个 HTML 中还能指望什么呢?更别提建立自动化工具来解析 HTML 并提取有用数据了。令人惊讶的是,答案是肯定的,我们可以感谢BeautifulSoup Python 库以及其他抓取库。它们为我们打开了一个数据的新世界。让我们看看如何利用BeautifulSoup从中提取内容。

Python 库 - BeautifulSoup

首先,BeautifulSoupRequests库一起使用。Requests 随 Python 预安装,但BeautifulSoup并没有,因此你需要安装它。你可以使用以下命令来安装:

pip install beautifulsoup4

BeautifulSoup有很多功能,所以请去探索这个库。但提取冲绳新闻网站上所有链接需要做什么呢?下面是你可以做的:

from bs4 import BeautifulSoup
soup = BeautifulSoup(data, 'html.parser')
links = soup.find_all('a', href=True)
links
[<a href="http://english.ryukyushimpo.jp">Home</a>,
 <a href="http://english.ryukyushimpo.jp">Ryukyu Shimpo – Okinawa, Japanese newspaper, local news</a>,
 <a href="http://english.ryukyushimpo.jp/special-feature-okinawa-holds-mass-protest-rally-against-us-base/">Special Feature: Okinawa holds mass protest rally against US base</a>,
 <a href="http://english.ryukyushimpo.jp/2021/09/03/34020/">Hirokazu Ueyonabaru returns home to Okinawa from the Tokyo Paralympics with two bronze medals in wheelchair T52 races, "the cheers gave me power"</a>,
 <a href="http://english.ryukyushimpo.jp/2021/09/03/34020/"><img alt="Hirokazu Ueyonabaru returns home to Okinawa from the Tokyo Paralympics with two bronze medals in wheelchair T52 races, "the cheers gave me power"" class="medium" src="img/RS20210830G01268010100.jpg"/> </a>…]

很简单!我这里只展示了前几个提取的链接。第一行导入了库,第二行设置了 BeautifulSoup 以便解析 HTML,第三行查找了所有包含 href 属性的链接,最后一行则显示了这些链接。我们抓取了多少个链接?

len(links)
…
277

你的结果可能会有所不同,因为该页面可能在这本书写完后进行了编辑。

在不到一秒钟的时间内,已经抓取了 277 个链接!让我们看看是否能清理它们并只提取 URL。我们不需要担心链接的文本内容。我们还应该将其转换为一个 URL 列表,而不是 <a> HTML 标签列表:

urls = [link.get('href') for link in links]
urls
…
['http://english.ryukyushimpo.jp',
 'http://english.ryukyushimpo.jp',
 'http://english.ryukyushimpo.jp/special-feature-okinawa-holds-mass-protest-rally-against-us-base/',
 'http://english.ryukyushimpo.jp/2021/09/03/34020/',
 'http://english.ryukyushimpo.jp/2021/09/03/34020/',
 'http://english.ryukyushimpo.jp/2021/09/03/34020/',
 'http://english.ryukyushimpo.jp/2021/09/03/34020/'…]

在这里,我们使用了列表推导式和 BeautifulSoup 来提取每个已抓取链接中的 href 值。我发现有些重复的链接,因此我们需要在最终存储结果之前去除它们。让我们看看是否丢失了原先的 277 个链接:

len(urls)
…
277

完美!我们选择其中一个,看看是否能够从页面中提取出原始文本,去掉所有 HTML。我们来试试这个我手动选择的 URL:

url = 'http://english.ryukyushimpo.jp/2021/09/03/34020/'
data = requests.get(url).text
soup = BeautifulSoup(data, 'html.parser')
soup.get_text()
…
"\n\n\n\n\nRyukyu Shimpo – Okinawa, Japanese newspaper, local news  » Hirokazu Ueyonabaru returns home to Okinawa from the Tokyo Paralympics with two bronze medals in wheelchair T52 races, "the cheers gave me power"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nHome\n\n\n\nSearch\n\n\n\n\n\n\n\n\n\n\nTuesdaySeptember 07,2021Ryukyu Shimpo – Okinawa, Japanese newspaper, local news\n\n\n\n\n\n\r\nTOPICS:Special Feature: Okinawa holds mass protest rally against US base\n\n\n\n\n\n\n\n\n\n\n\n\nHirokazu Ueyonabaru returns home to Okinawa from the Tokyo Paralympics with two bronze medals in wheelchair T52 races, "the cheers gave me power"…"

完成!我们已经从网页中捕获到了相当干净且可用的文本!这可以自动化,以便不断从任何感兴趣的网站抓取链接和文本。现在,我们已经拥有了进入本章有趣部分的基础:从文本中提取实体,然后利用实体来构建社交网络。到现在为止,应该显而易见,我们探索的所有选项都需要进行一些清理,所以我们现在就开始处理这一部分。要做到完美,你需要做的比我接下来要做的更多,但至少我们可以让它变得有些可用:

text = soup.get_text()
text[0:500]
…
'\n\n\n\n\nRyukyu Shimpo – Okinawa, Japanese newspaper, local news  » Hirokazu Ueyonabaru returns home to Okinawa from the Tokyo Paralympics with two bronze medals in wheelchair T52 races, "the cheers gave me power"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nHome\n\n\n\nSearch\n\n\n\n\n\n\n\n\n\n\nTuesdaySeptember 07,2021Ryukyu Shimpo – Okinawa, Japanese newspaper, local news\n\n\n\n\n\n\r\nTOPICS:Special Feature: Okinawa holds mass protest rally against US base\n\n\n\n\n\n\n\n\n\n\n\n\nHirokazu Ueyonabaru returns home t'

第一件引起我注意的事是文本中存在大量的文本格式和特殊字符。我们有几个选择。首先,我们可以把所有的换行符转换为空格。我们来看看效果如何:

text = text.replace('\n', ' ').replace('\r', ' ').replace('\t', ' ').replace('\xa0', ' ')
text
…
"     Ryukyu Shimpo – Okinawa, Japanese newspaper, local news  » Hirokazu Ueyonabaru returns home to Okinawa from the Tokyo Paralympics with two bronze medals in wheelchair T52 races, "the cheers gave me power"                                                            Home    Search           TuesdaySeptember 07,2021Ryukyu Shimpo – Okinawa, Japanese newspaper, local news        TOPICS:Special Feature: Okinawa holds mass protest rally against US base             Hirokazu Ueyonabaru returns home to Okinawa from the Tokyo Paralympics with two bronze medals in wheelchair T52 races, "the cheers gave me power"   Tokyo Paralympic bronze medalist Hirokazu Ueyonabaru receiving congratulations from some young supporters at Naha Airport on August 30…"

如果你继续向下滚动文本,你可能会看到故事在“Go to Japanese”处结束,那么我们也将删除该部分及其之后的内容:

cutoff = text.index('Go to Japanese')
cutoff
…
1984

这显示了截断字符串从第 1,984 个字符开始。让我们保留所有直到截断位置的内容:

text = text[0:cutoff]

这成功地去除了页脚的杂项,但仍然有一些页头的杂项需要处理,看看我们能否去除它。这个部分总是比较棘手的,每个网站都有其独特之处,但我们可以尝试去除故事之前的所有内容作为练习。仔细看,我发现故事从第二次出现“Hirokazu Uevonabaru”开始。我们从那个点开始捕获所有内容。我们将使用 .rindex() 代替 .index() 来捕获最后一次出现。这段代码对于实际应用来说过于具体,但希望你能看到你有一些清理脏数据的选项:

cutoff = text.rindex('Hirokazu Ueyonabaru')
text = text[cutoff:]

如果你不熟悉 Python,这可能会让你感到有些奇怪。我们保留了从最后一次出现“Hirokazu Ueyonabaru”开始的所有内容,这正是故事的起点。现在看起来怎么样?

text
…
'Hirokazu Ueyonabaru, 50, – SMBC Nikko Securities Inc. – who won the bronze medal in both the 400-meter and 1,500-meter men's T52 wheelchair race, returned to Okinawa the evening of August 30, landing at Naha airport. Seeing the people who came out to meet him, he said "It was a sigh of relief (to win a medal)" beaming a big smile. That morning he contended with press conferences in Tokyo before heading home. He showed off his two bronze medals, holding them up from his neck in the airport lobby, saying "I could not have done it without all the cheers from everyone…'

看起来几乎完美!总是可以做更多的清理,但现在这样已经足够好了!当你刚开始进行网页抓取时,你需要清理、检查、清理、检查、清理和检查——逐渐地,你会停止发现明显需要删除的东西。你不想削减太多。只需要让文本可以使用——我们将在后续步骤中进行额外的清理。

选择库、API 和源数据

作为这次演示的一部分,我展示了几种从互联网获取有用数据的方法。我展示了几个库如何直接加载数据,但它们提供的数据是有限的。NLTK 只提供了完整古腾堡书籍档案的一小部分,因此我们必须使用Requests库来加载变形记。我还演示了如何通过RequestsBeautifulSoup轻松提取链接和原始文本。

当 Python 库将数据加载功能集成到库中时,它们也可以使加载数据变得非常简单,但你只能使用那些库提供的数据。如果你只是想要一些数据来玩,并且不需要太多清理,这可能是理想选择,但仍然需要清理。在处理文本时,你无法避免这一点。

其他网络资源提供了自己的 API,这使得在向它们发送请求后加载数据变得非常简单。Twitter 就是这样做的。你通过 API 密钥进行身份验证,然后你就可以提取你想要的任何数据。这是在 Python 库和网页抓取之间的一个理想平衡。

最后,网页抓取让你能够访问整个互联网。如果你能访问一个网页,你就可以抓取它并使用它提供的任何文本和数据。网页抓取具有灵活性,但它更为复杂,且结果需要更多的清理。

我通常会按照以下顺序考虑我的抓取和数据增强项目:

  • 有没有 Python 库可以让我轻松加载我想要的数据?

  • 没有吗?好吧,有没有我可以用来提取我想要的数据的 API?

  • 没有吗?好吧,我能用BeautifulSoup直接抓取吗?能吗?那就开始吧。我们开始跳舞吧。

从简单开始,只有在需要时才逐步增加复杂度。开始简单意味着从最简单的方法开始——在这种情况下,就是使用 Python 库。如果库不能满足需求,可以通过查看是否有 API 可用来帮助,且是否负担得起,来增加一点复杂度。如果没有 API 可用,那么网页抓取就是你需要的解决方案,无法避免,但你将能够获得你需要的数据。

现在我们已经有了文本,我们将进入自然语言处理(NLP)。具体来说,我们将使用词性标注(PoS tagging)和命名实体识别(NER),这两种不同的方法从原始文本中提取实体(人物和事物)。

使用 NLTK 进行词性标注

在这一部分,我将解释如何使用 NLTK Python 库进行所谓的 PoS 标注。NLTK 是一个较老的 Python 自然语言处理库,但它仍然非常有用。在将 NLTK 与其他 Python 自然语言处理库(例如spaCy)进行比较时,也有其优缺点,因此了解每个库的优缺点是有益的。然而,在我进行这次演示的编码过程中,我意识到spaCy在 PoS 标注和命名实体识别(NER)方面确实让一切变得更加简便。因此,如果你想要最简单的方法,随时可以跳到spaCy部分。我仍然喜欢NLTK,在某些方面,这个库对我来说比spaCy更自然,但这可能只是因为我已经用了很多年。无论如何,我想先用NLTK演示 PoS 标注,然后在下一部分演示如何使用spaCy进行 PoS 标注和 NER。

PoS 标注是一个过程,它将文本中的单词标记为相应的词性。回顾一下,token 是单个单词。一个 token 可能是apples

在 NLP 中,token 很有用,但二元组(bigram)通常更加有用,它们可以提高文本分类、情感分析,甚至是无监督学习的结果。二元组本质上是两个 token——例如,two tokens

我们不要想太多。这只是两个 token。你认为三元组(trigram)是什么?没错,就是三个 token。例如,如果在提取三元组之前移除掉一些填充词,你可能会得到一个像green eggs ham这样的三元组。

有许多不同的pos_tags,你可以在这里查看完整列表:www.ling.upenn.edu/courses/Fall_2003/ling001/penn_treebank_pos.html

对于我们正在做的工作,我们将只使用我们需要的 NLP 特性,PoS 标注和 NER 是两种有用的不同方法,可以帮助我们识别文本中描述的实体(人和物)。在上述列表中,我们需要的是 NNP 和 NNPS,在大多数情况下,我们会找到 NNP,而不是 NNPS。

为了解释我们要做的事情,我们将遵循以下步骤:

  1. 获取一些文本进行处理。

  2. 将文本拆分成句子。

  3. 将每个句子拆分成 token。

  4. 识别每个 token 的 PoS 标签。

  5. 提取每个专有名词的 token。

专有名词是指人、地方或事物的名称。我一直在说我们要提取实体,并将实体定义为人、地方或事物,因此 NNP 标签将准确标识我们想要的内容:

  1. 让我们开始工作,获取一些文本数据!

    url = 'https://www.gutenberg.org/files/5200/5200-0.txt'
    
    text = requests.get(url).text
    

我们之前使用这段代码加载了卡夫卡的书《变形记》中的整个文本,The Metamorphosis

  1. 这个文件的头部有很多杂乱的内容,但故事从“One morning”开始,所以我们从那之前的部分删除掉。你可以在我们操作时随意查看text变量。我省略了反复展示数据的步骤以节省空间:

    cutoff = text.index('One morning')
    
    text = text[cutoff:]
    

这里,我们已经识别出了短语的起始点One morning,并移除了所有到这个点为止的内容。那只是我们不需要的头部垃圾。

  1. 接下来,如果你查看文本底部,你会看到故事在*** END OF THE PROJECT GUTENBERG EBOOK METAMORPHOSIS处结束,那么让我们从这个点开始裁剪:

    cutoff = text.rindex('*** END OF THE PROJECT GUTENBERG EBOOK METAMORPHOSIS ***')
    
    text = text[:cutoff]
    

仔细看截断,你会发现截断的位置与删除头部时使用的位置不同。我本质上是在说,“给我所有内容直到截断位置。”现在结束的文本看起来怎么样?

text[-500:]
…
'talking, Mr. and Mrs.\r\nSamsa were struck, almost simultaneously, with the thought of how their\r\ndaughter was blossoming into a well built and beautiful young lady.\r\nThey became quieter. Just from each otherâ\x80\x99s glance and almost without\r\nknowing it they agreed that it would soon be time to find a good man\r\nfor her. And, as if in confirmation of their new dreams and good\r\nintentions, as soon as they reached their destination Grete was the\r\nfirst to get up and stretch out her young body.\r\n\r\n\r\n\r\n\r\n'
We have successfully removed both header and footer junk. I can see that there are a lot of line breaks, so let's remove all of those as well.
text = text.replace('\r', ' ').replace('\n', ' ')
text
…
'One morning, when Gregor Samsa woke from troubled dreams, he found  himself transformed in his bed into a horrible vermin. He lay on his armour-like back, and if he lifted his head a little he could see his  brown belly, slightly domed and divided by arches into stiff sections…'

不错。这是一个显著的进步,我们离干净的文本又近了一步。撇号也被破坏,显示为â\x80\x99,所以我们需要将其替换:

text = text.replace('â\x80\x99', '\'').replace('â\x80\x9c', '"').replace('â\x80\x9d', '""')\
.replace('â\x80\x94', ' ')
print(text)
…
One morning, when Gregor Samsa woke from troubled dreams, he found  himself transformed in his bed into a horrible vermin. He lay on his  armour-like back, and if he lifted his head a little he could see his  brown belly, slightly domed and divided by arches into stiff sections.  The bedding was hardly able to cover it and seemed ready to slide off  any moment. His many legs, pitifully thin compared with the size of the  rest of him, waved about helplessly as he looked.    "What's happened to me?"" he thought…
  1. 这个几乎完美了,接下来我们将这些步骤转化为一个可重用的函数:

    def get_data():
    
        url = 'https://www.gutenberg.org/files/5200/5200-0.txt'
    
        text = requests.get(url).text
    
        # strip header junk
    
        cutoff = text.index('One morning')
    
        text = text[cutoff:]
    
        # strip footer junk
    
        cutoff = text.rindex('*** END OF THE PROJECT GUTENBERG EBOOK METAMORPHOSIS ***')
    
        text = text[:cutoff]
    
        # pre-processing to clean the text
    
        text = text.replace('\r', ' ').replace('\n', ' ')
    
        text = text.replace('â\x80\x99', '\'').replace('â\x80\x9c', '"')\
    
         .replace('â\x80\x9d', '""').replace('â\x80\x94', ' ')
    
        return text
    
  2. 运行这个函数后,我们应该得到相当干净的文本:

    text = get_data()
    
    text
    
    'One morning, when Gregor Samsa woke from troubled dreams, he found  himself transformed in his bed into a horrible vermin. He lay on his  armour-like back, and if he lifted his head a little he could see his  brown belly, slightly domed and divided by arches into stiff sections.  The bedding was hardly able to cover it and seemed ready to slide off  any moment. His many legs, pitifully thin compared with the size of the  rest of him, waved about helplessly as he looked.    "What\'s happened to me?"" he thought'
    

出色!我们现在准备好进入下一步了。

在我们继续之前,我想解释一件事:如果你对任何书籍或文章的完整文本进行PoS 标注,那么文本会被当作一个巨大的整体处理,你就失去了理解实体如何关联和互动的机会。你最终得到的只会是一个巨大的实体列表,这对于我们的需求并不太有帮助,但如果你只是想从某个文本中提取实体,它倒是很有用。

对于我们的目的,首先你需要做的事情是将文本拆分成句子、章节或其他想要的类别。为了简单起见,我们就按句子来拆分。这可以通过 NLTK 的句子标记化工具轻松完成:

from nltk.tokenize import sent_tokenize
sentences = sent_tokenize(text)
sentences[0:5]
…
['One morning, when Gregor Samsa woke from troubled dreams, he found  himself transformed in his bed into a horrible vermin.',
 'He lay on his  armour-like back, and if he lifted his head a little he could see his  brown belly, slightly domed and divided by arches into stiff sections.',
 'The bedding was hardly able to cover it and seemed ready to slide off  any moment.',
 'His many legs, pitifully thin compared with the size of the  rest of him, waved about helplessly as he looked.',
 '"What\'s happened to me?""']

漂亮! 我们现在有了一个句子列表可以使用。接下来,我们要做的是从这些句子中提取出任何提到的实体。我们需要的是 NNP 标记的词语。这部分稍微复杂一些,我会一步步带你完成。如果我们直接将句子传给 NLTK 的pos_tag 工具,它会错误分类所有内容:

import nltk
nltk.pos_tag(sentences)
[('One morning, when Gregor Samsa woke from troubled dreams, he found  himself transformed in his bed into a horrible vermin.',
  'NNP'),
 ('He lay on his  armour-like back, and if he lifted his head a little he could see his  brown belly, slightly domed and divided by arches into stiff sections.',
  'NNP'),
 ('The bedding was hardly able to cover it and seemed ready to slide off  any moment.',
  'NNP'),
 ('His many legs, pitifully thin compared with the size of the  rest of him, waved about helplessly as he looked.',
  'NNP'),
 ('"What\'s happened to me?""', 'NNP'),
 ('he thought.', 'NN')…]

很好的尝试,但这不是我们需要的。我们需要做的是遍历每个句子并识别 PoS 标签,所以我们先手动处理一个句子:

sentence = sentences[0]
sentence
…
'One morning, when Gregor Samsa woke from troubled dreams, he found  himself transformed in his bed into a horrible vermin.'

首先,我们需要对句子进行标记化。NLTK 中有许多不同的标记化工具,每个工具有自己的优缺点。我习惯了使用随意的标记化工具,所以我将使用它。随意的标记化工具适用于随意的文本,但也有其他几个标记化工具可以选择:

from nltk.tokenize import casual_tokenize
tokens = casual_tokenize(sentence)
tokens
…
['One',
 'morning',
 ',',
 'when',
 'Gregor',
 'Samsa',
 'woke',
 'from',
 'troubled',
 'dreams',
 ',',
 'he',
 'found',
 'himself',
 'transformed',
 'in',
 'his',
 'bed',
 'into',
 'a',
 'horrible',
 'vermin',
 '.']

很好。现在,对于每个标记,我们可以找到它对应的pos_tag

nltk.pos_tag(tokens)
…
[('One', 'CD'),
 ('morning', 'NN'),
 (',', ','),
 ('when', 'WRB'),
 ('Gregor', 'NNP'),
 ('Samsa', 'NNP'),
 ('woke', 'VBD'),
 ('from', 'IN'),
 ('troubled', 'JJ'),
 ('dreams', 'NNS'),
 (',', ','),
 ('he', 'PRP'),
 ('found', 'VBD'),
 ('himself', 'PRP'),
 ('transformed', 'VBN'),
 ('in', 'IN'),
 ('his', 'PRP$'),
 ('bed', 'NN'),
 ('into', 'IN'),
 ('a', 'DT'),
 ('horrible', 'JJ'),
 ('vermin', 'NN'),
 ('.', '.')]

这也完美!我们要提取的是 NNP。你能看到我们想要提取的两个标记吗?没错,就是 Gregor Samsa。让我们遍历这些 PoS 标签并提取 NNP 标记:

entities = []
for row in nltk.pos_tag(tokens):
    token = row[0]
    tag = row[1]
    if tag == 'NNP':
        entities.append(token)
entities
…
['Gregor', 'Samsa']

这就是我们需要的。希望 NER 能识别这两项结果为同一个人,但一旦把它放进图中,很容易就能纠正。让我们将其转化为一个函数,它将接受一个句子并返回 NNP 标记——即实体:

def extract_entities(sentence):
    entities = []
    tokens = casual_tokenize(sentence)
    for row in nltk.pos_tag(tokens):
        token = row[0]
        tag = row[1]
        if tag == 'NNP':
            entities.append(token)
    return entities

看起来不错。我们试试看!

extract_entities(sentence)
…
['Gregor', 'Samsa']

现在,让我们大胆尝试一下,将其应用到整本书的每一个句子上:

entities = [extract_entities(sentence) for sentence in sentences]
entities
[['Gregor', 'Samsa'],
 [],
 [],
 [],
 ["What's"],
 [],
 [],
 [],
 ['Samsa'],
 [],
 ['Gregor'],
 [],
 [],
 [],
 [],
 ['Oh', 'God', '"', '"', "I've"],
 [],
 [],
 ['Hell']]

为了让分析稍微简单一些,我们做两件事:首先,将那些空的列表替换成None。其次,把所有这些数据放入一个Pandas DataFrame 中:

def extract_entities(sentence):
    entities = []
    tokens = casual_tokenize(sentence)
    for row in nltk.pos_tag(tokens):
        token = row[0]
        tag = row[1]
        if tag == 'NNP':
            entities.append(token)
    if len(entities) > 0:
        return entities
    else:
        return None
entities = [extract_entities(sentence) for sentence in sentences]
entities
[['Gregor', 'Samsa'],
 None,
 None,
 None,
 ["What's"],
 None,
 None,
 None,
 ['Samsa'],
 None,
 ['Gregor'],
 None,
 None,
 None,
 None,
 ['Oh', 'God', '"', '"', "I've"],
 None,
 None,
 ['Hell']]
import pandas as pd
df = pd.DataFrame({'sentence':sentences, 'entities':entities})
df.head(10)

这将给我们一个包含句子和从句子中提取的实体的 DataFrame:

图 4.4 – Pandas DataFrame 中的句子实体

图 4.4 – Pandas DataFrame 中的句子实体

这是一个好的开始。我们可以看到“What’s”被 NLTK 错误地标记了,但在处理文本时,垃圾信息被误识别是正常的。这些问题很快会得到清理。现在,我们想利用这本书构建社交网络,所以让我们获取所有包含两个或更多实体的实体列表。我们至少需要两个实体来识别关系:

df = df.dropna()
df = df[df['entities'].apply(len) > 1]
entities = df['entities'].to_list()
entities
[['Gregor', 'Samsa'],
 ['Oh', 'God', '"', '"', "I've"],
 ['"', '"'],
 ["I'd", "I'd"],
 ["I've", "I'll"],
 ['First', "I've"],
 ['God', 'Heaven'],
 ['Gregor', '"', '"', '"'],
 ['Gregor', "I'm"],
 ['Gregor', 'Gregor', '"', '"', '"'],
 ['Gregor', "I'm"],
 ['"', '"', 'Gregor', '"'],
 ['Seven', '"'],
 ["That'll", '"', '"'],
 ["They're", '"', '"', 'Gregor'],
 ["Gregor's", 'Gregor'],
 ['Yes', '"', 'Gregor'],
 ['Gregor', '"', '"'],
 ['Mr', 'Samsa', '"', '"']]

除了一些标点符号悄悄混入外,这看起来还不错。让我们回顾一下之前的代码,并忽略所有非字母字符。这样,Gregor's会变成GregorI'd会变成I,以此类推。这样清理起来会更加容易:

def extract_entities(sentence):
    entities = []
    tokens = casual_tokenize(sentence)
    for row in nltk.pos_tag(tokens):
        token = row[0]
        tag = row[1]
        if tag == 'NNP':
            if "'" in token:
                cutoff = token.index('\'')
                token = token[:cutoff]
            entities.append(token)
    if len(entities) > 0:
        return entities
    else:
        return None
entities = [extract_entities(sentence) for sentence in sentences]
entities
[['Gregor', 'Samsa'],
 None,
 None,
 None,
 ['What'],
 None,
 None,
 None,
 ['Samsa']…]

让我们将这个数据重新放入 DataFrame 中,并重复我们的步骤,看看实体列表是否有所改善:

df = pd.DataFrame({'sentence':sentences, 'entities':entities})
df = df.dropna()
df = df[df['entities'].apply(len) > 1]
entities = df['entities'].to_list()
entities
[['Gregor', 'Samsa'],
 ['Oh', 'God', '"', '"', 'I'],
 ['"', '"'],
 ['I', 'I'],
 ['I', 'I'],
 ['First', 'I'],
 ['God', 'Heaven'],
 ['Gregor', '"', '"', '"'],
 ['Gregor', 'I'],
 ['Gregor', 'Gregor', '"', '"', '"'],
 ['Gregor', 'I'],
 ['"', '"', 'Gregor', '"'],
 ['Seven', '"'],
 ['That', '"', '"'],
 ['They', '"', '"', 'Gregor'],
 ['Gregor', 'Gregor'],
 ['Yes', '"', 'Gregor'],
 ['Gregor', '"', '"'],
 ['Mr', 'Samsa', '"', '"']]

这已经好很多了,但数据中仍然有一些双引号。让我们去除所有标点符号以及之后的内容:

from string import punctuation
def extract_entities(sentence):
    entities = []
    tokens = casual_tokenize(sentence)
for row in nltk.pos_tag(tokens):
        token = row[0]
        tag = row[1]
        if tag == 'NNP':
            for p in punctuation:
                if p in token:
                    cutoff = token.index(p)
                    token = token[:cutoff]
            if len(token) > 1:
                entities.append(token)
if len(entities) > 0:
        return entities
    else:
        return None
entities = [extract_entities(sentence) for sentence in sentences]
df = pd.DataFrame({'sentence':sentences, 'entities':entities})
df = df.dropna()
df = df[df['entities'].apply(len) > 1]
entities = df['entities'].to_list()
entities
…
[['Gregor', 'Samsa'],
 ['Oh', 'God'],
 ['God', 'Heaven'],
 ['Gregor', 'Gregor'],
 ['They', 'Gregor'],
 ['Gregor', 'Gregor'],
 ['Yes', 'Gregor'],
 ['Mr', 'Samsa'],
 ['He', 'Gregor'],
 ['Well', 'Mrs', 'Samsa'],
 ['No', 'Gregor'],
 ['Mr', 'Samsa'],
 ['Mr', 'Samsa'],
 ['Sir', 'Gregor'],
 ['Oh', 'God']]

这个结果已经足够好了!我们可以增加一些逻辑,防止同一标记连续出现两次,但我们可以很容易地从网络中去除这些,所以让我们重构代码继续进行:

def get_book_entities():
    text = get_data()
    sentences = sent_tokenize(text)
    entities = [extract_entities(sentence) for sentence in sentences]
    df = pd.DataFrame({'sentence':sentences, 'entities':entities})
    df = df.dropna()
    df = df[df['entities'].apply(len) > 1]
    entities = df['entities'].to_list()
    return entities
entities = get_book_entities()
entities[0:5]
…
[['Gregor', 'Samsa'],
 ['Oh', 'God'],
 ['God', 'Heaven'],
 ['Gregor', 'Gregor'],
 ['They', 'Gregor']]

这非常棒。此时,不管我们是做词性标注还是 NER,我们都需要一个实体列表,这已经足够接近了。接下来,我们将使用 spaCy 做同样的操作,你应该能够看到 spaCy 在某些方面更为简便。然而,它的设置更为复杂,因为你需要安装一个语言模型来与 spaCy 一起使用。每种方法都有其优缺点。

使用 spaCy 进行词性标注和 NER

在这一节中,我将解释如何使用 spaCy 做我们刚刚用 NLTK 完成的工作。我还将展示如何使用 NER 作为一种通常优于词性标注(PoS)的方式来识别和提取实体。在开始编写本章内容之前,我主要使用 NLTK 的PoS tagging作为我的实体提取核心,但在为这一节编写代码并稍微深入探索后,我意识到 spaCy 有了显著的改进,因此我认为我在这一节展示的内容优于之前用 NLTK 做的工作。我认为解释 NLTK 的有用性还是有帮助的。学习两者并使用最适合你的方法。但对于实体提取,我相信 spaCy 在易用性和处理速度方面优于 NLTK。

之前,我编写了一个函数来加载弗朗茨·卡夫卡的书《变形记》,所以我们也将使用这个加载器,因为它不依赖于 NLTK 或 spaCy,并且可以很容易地修改,以便从古腾堡计划的档案中加载任何书籍。

要使用 spaCy 做任何事情,首先需要做的是加载所选的 spaCy 语言模型。在我们加载之前,必须先安装它。你可以通过运行以下命令来完成安装:

python -m spacy download en_core_web_md

有几种不同的模型可供选择,但我使用的三个英文文本模型分别是小型、中型和大型。md代表中型。你可以将其替换为smlg,分别获取小型或大型模型。你可以在这里了解更多关于 spaCy 模型的信息:spacy.io/usage/models

一旦模型安装完成,我们就可以将其加载到我们的 Python 脚本中:

import spacy
nlp = spacy.load("en_core_web_md")

如前所述,你可以根据需要将 md 替换为 smlg,取决于你想使用的模型。较大的模型需要更多的存储和内存。较小的模型需要较少。选择一个足够适合你工作的模型。你可能不需要大型模型。中型和小型模型表现很好,不同模型之间的差异通常不易察觉。

接下来,我们需要一些文本,因此让我们重用之前编写的函数:

def get_data():
    url = 'https://www.gutenberg.org/files/5200/5200-0.txt'
text = requests.get(url).text
    # strip header junk
    cutoff = text.index('One morning')
    text = text[cutoff:]
    # strip footer junk
    cutoff = text.rindex('*** END OF THE PROJECT GUTENBERG EBOOK METAMORPHOSIS ***')
    text = text[:cutoff]
    # pre-processing to clean the text
    text = text.replace('\r', ' ').replace('\n', ' ')
    text = text.replace('â\x80\x99', '\'').replace('â\x80\x9c', '"').replace('â\x80\x9d', '""').replace('â\x80\x94', ' ')
return text
That looks good. We are loading The Metamorphosis, cleaning out header and footer junk, and then returning text that is clean enough for our purposes. Just to lay eyes on the text, let's call our function and inspect the returned text.
text = get_data()
text[0:279]
…
'One morning, when Gregor Samsa woke from troubled dreams, he found himself transformed in his bed into a horrible vermin. He lay on his  armour-like back, and if he lifted his head a little he could see his  brown belly, slightly domed and divided by arches into stiff sections.'

句子中间有一些额外的空格,但这对我们来说不会构成任何问题。分词会自动清理这些空格,而我们无需做额外的工作。我们很快就会涉及到这个问题。首先,像在 NLTK 中那样,我们需要将文本拆分成句子,以便可以根据每个句子中揭示的实体构建网络。使用 spaCy 而非 NLTK 做这件事要容易得多,下面是操作方法:

doc = nlp(text)
sentences = list(doc.sents)

第一行将 变形记 的完整文本传递给 spaCy,并使用我们选择的语言模型,而第二行则提取文本中的句子。现在,我们应该得到一个 Python 列表,包含所有的句子。让我们检查列表中的前六个句子:

for s in sentences[0:6]:
    print(s)
    print()
…
One morning, when Gregor Samsa woke from troubled dreams, he found  himself transformed in his bed into a horrible vermin.
He lay on his  armour-like back, and if he lifted his head a little he could see his  brown belly, slightly domed and divided by arches into stiff sections.
The bedding was hardly able to cover it and seemed ready to slide off  any moment.
His many legs, pitifully thin compared with the size of the  rest of him, waved about helplessly as he looked.
"What's happened to me?"
" he thought.

六个句子可能看起来是一个奇怪的数量,但我想给你展示一些东西。看看最后两个句子。SpaCy 已成功地将主要角色的内心独白提取为一个独立的句子,并且创建了一个单独的句子来补充周围的句子。对于我们的实体提取来说,如果这些句子合并在一起也不会有任何问题,但我喜欢这样。这不是一个 bug,这是一个功能,正如我们软件工程师常说的那样。

SpaCy 词性标注

现在我们已经有了句子,让我们使用 spaCy 的词性标注作为实体提取的预处理:

for token in sentences[0]:
    print('{}: {}'.format(token.text, token.tag_))
…
One: CD
morning: NN
,: ,
when: WRB
Gregor: NNP
Samsa: NNP
woke: VBD
from: IN
troubled: JJ
dreams: NNS
,: ,
he: PRP
found: VBD
 :
himself: PRP
transformed: VBD
in: IN
his: PRP$
bed: NN
into: IN
a: DT
horrible: JJ
vermin: NN
.: .

好的。我们需要的是 NNP,因为这些是专有名词。如果你使用 pos_ 而不是 tag_,你可以看到这一点:

for token in sentences[0]:
    print('{}: {}'.format(token.text, token.pos_))
…
One: NUM
morning: NOUN
,: PUNCT
when: ADV
Gregor: PROPN
Samsa: PROPN
woke: VERB
from: ADP
troubled: ADJ
dreams: NOUN
,: PUNCT
he: PRON
found: VERB
 : SPACE
himself: PRON
transformed: VERB
in: ADP
his: ADJ
bed: NOUN
into: ADP
a: DET
horrible: ADJ
vermin: NOUN
.: PUNCT

让我们添加一些提取的逻辑。我们需要做两件事——我们需要一个列表来存储结果,并且需要一些逻辑来提取 NNP:

entities = []
for token in sentences[0]:
    if token.tag_ == 'NNP':
        entities.append(token.text)
entities
…
['Gregor', 'Samsa']
Perfect! But this is only working on a single sentence. Let's do the same for all sentences.
entities = []
for sentence in sentences:
    sentence_entities = []
    for token in sentence:
        if token.tag_ == 'NNP':
            sentence_entities.append(token.text)
    entities.append(sentence_entities)
entities[0:10]
…
[['Gregor', 'Samsa'], [], [], [], [], [], [], [], [], ['Samsa']]

对于 NLTK,我们创建了一个函数来提取给定句子的实体,但是通过这种方式,我一次性做完了所有事情,而且非常简单。让我们把这个转化为一个函数,这样我们就能方便地用于以后的工作。同时,让我们防止实体列表中返回空列表,因为我们不需要这些:

def extract_entities(text):
    doc = nlp(text)
    sentences = list(doc.sents)
    entities = []
    for sentence in sentences:
        sentence_entities = []
        for token in sentence:
            if token.tag_ == 'NNP':
                sentence_entities.append(token.text)
        if len(sentence_entities) > 0:
            entities.append(sentence_entities)
    return entities

现在我们应该有一个干净的实体列表,其中不包含任何空的内部列表:

extract_entities(text)
…
[['Gregor', 'Samsa'],
 ['Samsa'],
 ['Gregor'],
 ['God'],
 ['Travelling'],
 ['God', 'Heaven'],
 ['Gregor'],
 ['Gregor'],
 ['Gregor'],
 ['Gregor'],
 ['Gregor']…]

这比 NLTK 的结果好多了,而且步骤更少。这简单而优雅。我不需要用到Pandas,也不需要删除空行,或者清理那些莫名其妙出现的标点符号。我们可以在任何清理过的文本上使用这个函数。你也可以在清理之前使用它,但那时你会得到一些无用的实体,尤其是在针对抓取的网页数据时。

SpaCy 命名实体识别(NER)

SpaCy 的 NER 同样简单易用。词性标注(PoS tagging) 和 NER 的区别在于,NER 进一步识别了人物、地点、事物等。关于 spaCy 语言特性的详细描述,我强烈推荐 Duygu Altinok 的书籍 Mastering spaCy。简而言之,spaCy 将标记归类为 18 种不同的实体类型。在我看来,这有些过多,因为 MONEY 并不是一个实体,但我只取我需要的。请查看 spaCy 获取完整的实体类型列表。我们需要的是被标记为 PERSONORGGPE 的实体。ORG 代表组织,GPE 包含国家、城市和州。

让我们遍历第一句话中的所有标记,看看这在实践中是如何运作的:

for token in sentences[0]:
    print('{}: {}'.format(token.text, token.ent_type_))
…
One: TIME
morning: TIME
,:
when:
Gregor: PERSON
Samsa: PERSON
woke:
from:
troubled:
dreams:
,:
he:
found:
 :
himself:
transformed:
in:
his:
bed:
into:
a:
horrible:
vermin:
.:

这个方法有效,但有一个小问题:我们希望“Gregor Samsa”作为一个实体出现,而不是两个。我们需要做的是创建一个新的 spaCy 文档,然后遍历文档的 ents,而不是单独的标记。在这方面,NER 的方法与 词性标注(PoS tagging)略有不同:

doc = nlp(sentences[0].text)
for ent in doc.ents:
    print('{}: {}'.format(ent, ent.label_))
…
One morning: TIME
Gregor Samsa: PERSON

完美!接下来做几件事:我们将重做之前的实体提取函数,不过这次使用 NER 而不是词性标注(PoS tagging),然后将实体限制为PERSONORGGPE。请注意,只有在一句话中有多个实体时,我才会添加实体。我们需要识别人物之间的关系,而你至少需要两个人才算得上有关系:

def extract_entities(text):
    doc = nlp(text)
    sentences = list(doc.sents)
    entities = []
    for sentence in sentences:
        sentence_entities = []
        sent_doc = nlp(sentence.text)
        for ent in sent_doc.ents:
            if ent.label_ in ['PERSON', 'ORG', 'GPE']:
                entity = ent.text.strip()
                if "'s" in entity:
                    cutoff = entity.index("'s")
                    entity = entity[:cutoff]
                if entity != '':
                    sentence_entities.append(entity)
        sentence_entities = list(set(sentence_entities))
        if len(sentence_entities) > 1:
            entities.append(sentence_entities)
    return entities

我添加了一些代码,去除任何多余的空白,并且清除了每个 sentence_entity 列表中的重复项。我还去除了出现在名字后面的 's 字符——比如 Gregor's,它会显示为 Gregor。我本可以在网络清理时处理这些,但这是一个不错的优化。让我们看看结果如何。我把实体列表命名为 morph_entities,代表 metaMORPHosis 实体。我想要一个描述性的名字,这个是我能想到的最合适的:

morph_entities = extract_entities(text)
morph_entities
…
[['Gregor', 'Grete'],
 ['Gregor', 'Grete'],
 ['Grete', 'Gregor'],
 ['Gregor', 'Grete'],
 ['Grete', 'Gregor'],
 ['Grete', 'Gregor'],
 ['Grete', 'Gregor'],
 ['Samsa', 'Gregor'],
 ['Samsa', 'Gregor'],
 ['Samsa', 'Grete'],
 ['Samsa', 'Grete'],
 ['Samsa', 'Grete']]

看起来很棒!哇,我已经很多年没读过 变形记 了,竟然忘记了故事中的人物有多少!

SpaCy 的 NER 是使用预训练的深度学习模型完成的,而机器学习从来不是完美的。请记住这一点。总是需要一些清理工作。SpaCy 允许你根据自己的文档自定义语言模型,如果你在某个专业领域工作,这非常有用,但我更倾向于将 spaCy 的模型作为通用工具使用,因为我处理的文本类型种类繁多。我不想为推文、文学作品、虚假信息和新闻定制 spaCy。我宁愿按原样使用它,并根据需要进行清理。对我来说,这一直是非常有效的。

你应该能看到有很多重复项。显然,在变形记中,很多时间都在谈论格雷戈尔。稍后我们可以通过一行NetworkX代码删除这些重复项,所以我决定保留它们,而不是调整函数。能达到“足够好”就可以。如果你正在处理大量数据并且支付云存储费用,你可能需要修复这些低效之处。

在本章的其余部分,我将使用 NER 结果作为我们的网络数据。我们同样可以使用pos_tag实体,但这样做不如 NER 好,因为 NER 能够结合名字和姓氏。在我们当前的实体中,这些信息并没有被提取出来,但在其他文本中会有。这就是变形记的写作方式。我们将在创建网络时清理这些数据。

为了做个理智检查,让我们看看《爱丽丝梦游仙境》中的实体!

def get_data():
    url = 'https://www.gutenberg.org/files/11/11-0.txt'
    text = requests.get(url).text
    # strip header junk
    cutoff = text.index('Alice was beginning')
    text = text[cutoff:]
    # strip footer junk
    cutoff = text.rindex('THE END')
    text = text[:cutoff]
    # pre-processing to clean the text
    text = text.replace('\r', ' ').replace('\n', ' ')
    text = text.replace('â\x80\x99', '\'').replace('â\x80\x9c', '"').replace('â\x80\x9d', '""').replace('â\x80\x94', ' ')
    return text
text = get_data()
text[0:310]
…
'Alice was beginning to get very tired of sitting by her sister on the  bank, and of having nothing to do: once or twice she had peeped into  the book her sister was reading, but it had no pictures or  conversations in it, "and what is the use of a book,"" thought Alice  "without pictures or conversations?""  '

我同意你的看法,爱丽丝。

我已经调整了加载函数,以加载《爱丽丝梦游仙境》这本书,并去除了任何页眉或页脚文本。其实这挺有趣的。砍掉他们的头(或页头)!让我们尝试提取实体。我预期这会有点杂乱,因为我们在处理幻想类角色,但让我们看看会发生什么。也许简·奥斯汀的作品会给出更好的结果。我们拭目以待!

alice_entities = extract_entities(text)
alice_entities[0:10]
…
[['Alice', 'Rabbit'],
 ['Alice', 'Longitude'],
 ['New Zealand', "Ma'am", 'Australia'],
 ['Fender', 'Alice'],
 ['Alice', 'Rabbit'],
 ['Mabel', 'Ada'],
 ['Rome', 'Paris', 'London'],
 ['Improve', 'Nile'],
 ['Alice', 'Mabel'],
 ['Alice', 'William the Conqueror']]

结果比我预期的要好,但还是有一些垃圾数据混进来了。我们将使用这两个实体列表来创建和可视化网络。这为我们的下一步工作打下了良好的基础。现在我们有了一些看起来非常有用的实体,接下来让我们着手创建一个可以加载到 NetworkX 图形中的 Pandas DataFrame!这正是将实体列表转换为实际社交网络所需要的。

这就结束了我们关于使用 spaCy 进行词性标注(PoS tagging)和命名实体识别(NER)的演示。我希望你能看到,虽然增加了一个额外的依赖项(语言模型),但实体提取过程变得简单得多。现在,是时候进入我认为最激动人心的部分:将实体列表转换为网络数据,然后用这些数据创建社交网络,之后我们可以可视化并进行调查。

将实体列表转换为网络数据

既然我们已经得到了相当干净的实体数据,接下来是将它转换成一个可以轻松加载到 NetworkX 中的 Pandas DataFrame,以便创建一个实际的社交网络图。这句话需要解释的内容有点多,但我们的工作流程是这样的:

  1. 加载文本。

  2. 提取实体。

  3. 创建网络数据。

  4. 使用网络数据创建图。

  5. 分析图。

再次强调,我将“图”和“网络”这两个术语交替使用。虽然这样会引起一些混淆,但这些名字不是我起的。我更喜欢说“网络”,但是人们会认为我在讲计算机网络,然后我不得不提醒他们我是在讲图,接着他们又会认为我在讲柱状图。对于不熟悉这些概念的人来说,解释图和网络真的很难,即使是我自己,在别人开始谈论网络和图时也会感到困惑。你是指节点和边,还是指 TCP/IP 和柱状图?唉,真是没法赢。

在接下来的部分,我们确实有多种方法来实现这一点,但我会解释我通常使用的方法。来看一下来自爱丽丝梦游仙境的实体:

alice_entities[0:10]
…
[['Alice', 'Rabbit'],
 ['Alice', 'Longitude'],
 ['New Zealand', "Ma'am", 'Australia'],
 ['Fender', 'Alice'],
 ['Alice', 'Rabbit'],
 ['Mabel', 'Ada'],
 ['Rome', 'Paris', 'London'],
 ['Improve', 'Nile'],
 ['Alice', 'Mabel'],
 ['Alice', 'William the Conqueror']]

在大多数情况下,每个内部列表中只有两个实体,但有时会有三个或更多。我通常做的是将第一个实体视为源,任何其他实体视为目标。那在句子中是怎样的呢?我们来看这个句子:“Jack 和 Jill 上山去向他们的朋友 Mark 打招呼。”如果我们将其转换为实体,列表会是这样:

['Jack', 'Jill', 'Mark']

为了实现我的方法,我会将列表中的第一个元素添加到我的源列表中,然后将第一个元素后的所有内容添加到我的目标列表中。下面是用代码展示的样子,但我使用的是Alice中的实体:

final_sources = []
final_targets = []
for row in alice_entities:
    source = row[0]
    targets = row[1:]
    for target in targets:
        final_sources.append(source)
        final_targets.append(target)

仔细看一下捕获sourcetargets的两行代码。source是每个实体列表的第一个元素,targets是每个实体列表中除第一个元素外的所有内容。然后,对于每个目标,我将源和目标添加到final_sourcesfinal_targets中。我循环遍历targets,因为它们可以是一个或多个。source永远不会多于一个,因为它是第一个元素。理解这一点很重要,因为这个过程对于如何在最终的社交网络中展示关系至关重要。我们本可以采用另一种方法,将每个实体相互连接,但我更喜欢我展示的方法。稍后的列表如果有关系证据,可能会填补任何空白。那么我们的最终源是什么样的呢?

final_sources[0:5]
…
['Alice', 'Alice', 'New Zealand', 'New Zealand', 'Fender']
Nice. How about our final targets?
final_targets[0:5]
…
['Rabbit', 'Longitude', "Ma'am", 'Australia', 'Alice']

两者看起来都不错。记住,我们使用了命名实体识别(NER)来捕获人物、地点和事物,所以这看起来没问题。稍后,我会直接从社交网络中删除一些源和目标。现在这样就足够了。

将第一个元素与目标连接的这种方法,我仍然经常考虑。另一种方法是将同一句中出现的每个实体都连接起来。我更倾向于我的方法,但你应该考虑这两种选择。

第一个实体与同一句中的其他实体互动,但并非所有实体都会彼此互动。例如,看看这句话:

John 去看他的好朋友 Aaron,然后他和 Jake 一起去了公园。”

这就是实体列表的样子:

['John', 'Aaron', 'Jake']

在这个例子中,Aaron 可能认识 Jake,但根据这句话我们无法确定。希望的是,如果确实存在某种关系,最终会被识别出来。也许在另一句话中,例如这句:

Aaron 和 Jake 去滑冰,然后吃了些比萨饼。”

在那句话之后,将会有明确的联系。我的首选方法需要更多证据才能连接实体。

我们现在有了一个代码,用来处理实体列表并创建两个列表:final_sourcesfinal_targets,但这对于传递给 NetworkX 来创建图形并不实用。我们做两个额外的事情:使用这两个列表创建一个 Pandas DataFrame,然后创建一个可重用的函数,接受任何实体列表并返回这个 DataFrame:

def get_network_data(entities):
    final_sources = []
    final_targets = []
    for row in entities:
        source = row[0]
        targets = row[1:]
        for target in targets:
            final_sources.append(source)
            final_targets.append(target)
    df = pd.DataFrame({'source':final_sources, 'target':final_targets})
    return df

看起来不错。让我们看看它的实际效果!

alice_network_df = get_network_data(alice_entities)
alice_network_df.head()

这将显示一个包含源节点和目标节点的网络数据的 DataFrame。这被称为边列表:

图 4.5 – 《爱丽丝梦游仙境》实体关系的 Pandas DataFrame

图 4.5 – 《爱丽丝梦游仙境》实体关系的 Pandas DataFrame

很好。那么它如何处理来自变形记的实体?

morph_network_df = get_network_data(morph_entities)
morph_network_df.head()

这将显示变形记的网络边列表:

图 4.6 – 《变形记》实体关系的 Pandas DataFrame

图 4.6 – 《变形记》实体关系的 Pandas DataFrame

完美,而且这个函数是可重用的!

我们现在准备将这两个都转化为实际的 NetworkX 图形。我认为这是有趣的地方。我们之前做的所有事情只是预处理工作。现在,我们可以玩转网络,尤其是社交网络分析!在这一章之后,我们将主要学习社交网络分析和网络科学。至于自然语言处理(NLP)中的一些领域,我略过了,例如词形还原和词干提取,但我故意这样做,因为它们在实体提取上没有 PoS 标注和命名实体识别(NER)那么重要。如果你想更深入了解 NLP,推荐你阅读 Duygu Altinok 的《Mastering spaCy》。在本书中,我们的 NLP 内容就到这里,因为这已经是我们所需的全部内容。

将网络数据转换为网络

是时候拿出我们创建的网络数据,创建两个图,一个是 Alice’s Adventures in Wonderland,另一个是 The Metamorphosis。我们暂时不深入进行网络分析,那是在后面的章节里讲的内容。但让我们看看它们的样子,看看能从中得出什么见解。

首先,我们需要导入 NetworkX 库,然后创建我们的图。这非常简单,因为我们已经创建了 Pandas DataFrame,NetworkX 将使用这些 DataFrame。这是我发现的创建图的最简单方法。

首先,如果你还没有安装 NetworkX,你需要通过以下命令安装:

pip install networkx

现在我们已经安装了 NetworkX,让我们来创建两个网络:

import networkx as nx
G_alice = nx.from_pandas_edgelist(alice_network_df)
G_morph = nx.from_pandas_edgelist(morph_network_df)

就这么简单。我们已经在文本预处理上完成了困难的工作。它成功了吗?让我们看一下每个图:

nx.info(G_alice)
…
'Graph with 68 nodes and 71 edges'
…
nx.info(G_morph)
…
'Graph with 3 nodes and 3 edges'

很棒!我们已经在了解两个社交网络之间的差异了。在图中,节点通常是与其他节点有关系的事物。没有任何关系的节点叫做孤立节点,但由于我们图的构建方式,不会有孤立节点。因为我们查找了包含两个或更多实体的句子。试着想象这两个实体就像两个点,中间有一条线。这实际上就是图/网络可视化的样子,只不过通常有许多点和许多线。两个节点之间的关系被称为边。你需要理解节点和边的区别,才能更好地处理图。

看一下 Alice 图的汇总信息,我们可以看到有 68 个节点(角色)和 71 条边(这些角色之间的关系)。

看一下 The Metamorphosis 网络的汇总信息,我们可以看到只有三个节点(角色)和三条边(这些角色之间的关系)。当进行可视化时,这将是一个非常简单的网络,因此我很高兴我们也做了 Alice

NetworkX 中还隐藏着许多其他有用的度量和总结,我们将在讨论中心性、最短路径以及其他社交网络分析和网络科学主题时讨论这些内容。

做一个网络可视化抽查

让我们可视化这些网络,快速看一眼,然后完成本章内容。

这里有两个我经常使用的可视化函数。在我看来,使用 sknetwork 后,我再也没有回过头去使用 NetworkX 进行可视化。

第一个函数将 NetworkX 图转换为邻接矩阵,sknetwork 使用该矩阵来计算 PageRank(重要性分数),然后将网络渲染为 SVG 图像。第二个函数使用第一个函数,但目标是可视化 ego_graph,稍后会介绍。在 ego 图中,你探索围绕单个节点存在的关系。第一个函数是更通用的。

说够了。你会在看到结果时更能理解:

def draw_graph(G, show_names=False, node_size=1, font_size=10, edge_width=0.5):
    import numpy as np
    from IPython.display import SVG
    from sknetwork.visualization import svg_graph
    from sknetwork.data import Bunch
    from sknetwork.ranking import PageRank
    adjacency = nx.to_scipy_sparse_matrix(G, nodelist=None, dtype=None, weight='weight', format='csr')
    names = np.array(list(G.nodes()))
    graph = Bunch()
    graph.adjacency = adjacency
    graph.names = np.array(names)
    pagerank = PageRank()
    scores = pagerank.fit_transform(adjacency)
    if show_names:
        image = svg_graph(graph.adjacency, font_size=font_size, node_size=node_size, names=graph.names, width=700, height=500, scores=scores, edge_width=edge_width)
    else:
        image = svg_graph(graph.adjacency, node_size=node_size, width=700, height=500, scores = scores, edge_width=edge_width)
    return SVG(image)

接下来,让我们创建一个显示自我图的函数。

为了明确,最好不要在函数内部包含import语句。最好将import语句保留在函数外部。然而,在这个情况下,这样做可以更容易地将代码复制并粘贴到你的 Jupyter 或 Colab 笔记本中,所以我做个例外:

def draw_ego_graph(G, ego, center=True, k=0, show_names=True, edge_width=0.1, node_size=3, font_size=12):
    ego = nx.ego_graph(G, ego, center=center)
    ego = nx.k_core(ego, k)
    return draw_graph(ego, node_size=node_size, font_size=font_size, show_names=show_names, edge_width=edge_width)

仔细看看这两个函数。拆解它们,试着弄清楚它们在做什么。为了快速完成本章内容,我将展示使用这些函数的结果。我已经抽象化了可视化这些网络的难度,让你可以做这件事:

draw_graph(G_alice)

由于我们没有向函数传递任何参数,这应该会显示一个非常简单的网络可视化。它将看起来像一堆点(节点),其中一些点通过线(边)连接到其他点:

重要提示

请将draw_graph函数保持在手边。我们将在本书中多次使用它。

图 4.7 – 爱丽丝梦游仙境的粗略社交网络

图 4.7 – 爱丽丝梦游仙境的粗略社交网络

好吧,这看起来有点没帮助,但这是故意的。我通常处理的是大规模网络,因此我更倾向于一开始省略节点名称,以便可以直观地检查网络。不过,你可以覆盖我正在使用的默认值。让我们做这些,并稍微减少线条宽度,增加节点大小,并添加节点名称:

draw_graph(G_alice, edge_width=0.2, node_size=3, show_names=True)

这将绘制我们的社交网络,并带有标签!

图 4.8 – 爱丽丝梦游仙境的标记社交网络

图 4.8 – 爱丽丝梦游仙境的标记社交网络

字体大小有点小,看起来有些难以阅读,所以我们将增加字体大小,并将node_size减小一个:

draw_graph(G_alice, edge_width=0.2, node_size=2, show_names=True, font_size=12)

这创建了以下网络:

图 4.9 – 爱丽丝梦游仙境的最终社交网络

图 4.9 – 爱丽丝梦游仙境的最终社交网络

太棒了。想一想我们做了什么。我们从爱丽丝中提取了原始文本,提取了所有实体,并构建了书中描述的社交网络。这是如此强大,它还为你打开了学习更多关于社交网络分析和网络科学的大门。例如,你是想分析别人的玩具数据集,还是更愿意研究你感兴趣的东西,比如你最喜欢的书?我更喜欢追随自己的好奇心。

让我们看看爱丽丝周围的自我图是什么样子的!

draw_ego_graph(G_alice, 'Alice')

这给我们带来了以下网络:

图 4.10 – 爱丽丝自我图

图 4.10 – 爱丽丝自我图

什么?!这太不可思议了。我们可以看到实体列表中有些杂乱的内容,但我们将在下一章学习如何清理这些内容。我们可以再进一步。如果我们想从她的自我图中取出爱丽丝,只探究她周围的关系,这可能吗?

draw_ego_graph(G_alice, 'Alice', center=False)

这给我们提供了以下的可视化:

图 4.11 – 爱丽丝自我图(去除中心)

图 4.11 – 爱丽丝自我图(去除中心)

太简单了。但是分析现有的群体聚类是很困难的。在去除中心后,许多节点变成了孤立节点。如果有一种方法能去除这些孤立节点,我们就能更容易地看到群体。哦,等等!

draw_ego_graph(G_alice, 'Alice', center=False, k=1)

我们得到以下的输出:

图 4.12 – 爱丽丝自我图(去除中心和孤立节点)

图 4.12 – 爱丽丝自我图(去除中心和孤立节点)

总的来说,爱丽丝社交网络看起来相当不错。虽然还需要一些清理工作,但我们可以研究关系。那么变形记的社交网络是什么样的呢?记住,这里只有三个节点和三条边。即使是爱丽丝的自我图,也比变形记的社交网络要复杂。让我们来可视化它吧!

draw_graph(G_morph, show_names=True, node_size=3, font_size=12)

这段代码生成了以下网络:

图 4.13 – 变形记的标记社交网络

图 4.13 – 变形记的标记社交网络

等等,但为什么有六条边?我只看到了三条。原因是sknetwork会将多条边绘制为一条边。我们确实有一些选项,比如根据边的数量增加线条宽度,但我们还是来看看 Pandas DataFrame,确保我的想法是正确的:

morph_network_df

这将给我们以下的 DataFrame:

图 4.14 – 变形记的网络数据 Pandas DataFrame

图 4.14 – 变形记的网络数据 Pandas DataFrame

如果我们去掉重复项,会发生什么?

morph_network_df.drop_duplicates()

我们得到以下的 DataFrame:

图 4.15 – 变形记的网络数据 Pandas DataFrame(去除重复项)

图 4.15 – 变形记的网络数据 Pandas DataFrame(去除重复项)

啊哈!Gregor 和 Grete 之间有关系,但反过来也是如此。我看到的一点是,Samsa 与 Gregor 和 Grete 都有连接,但 Grete 没有回连到 Samsa。换句话说,正如我们在本书中将要讨论的,方向性也是很重要的。你可以有一个有向图。在这种情况下,我只是使用了一个无向图,因为关系往往(但并非总是)是互惠的。

这标志着本次演示的结束。我们最初的目标是使用原始文本创建一个社交网络,而我们轻松地达成了目标。现在,我们有了网络数据可以进行操作。接下来,本书将变得更加有趣。

额外的 NLP 和网络考虑

这一章真是一次马拉松式的挑战。请再耐心等我一会儿。我有一些最后的想法想表达,然后我们就可以结束这一章了。

数据清理

首先,如果你处理的是语言数据,总是需要清理。语言是混乱且复杂的。如果你只习惯处理预先清理过的表格数据,那么这会显得很凌乱。我喜欢这种挑战,因为每个项目都能让我提高技巧和战术。

我展示了两种提取实体的方法:PoS 标注和 NER。两种方法都非常有效,但考虑一下哪种方法能够更快、更轻松地让我们得到一个干净且有用的实体列表。使用PoS 标注时,我们一次得到一个词元。而使用 NER 时,我们能很快得到实体,但模型有时会出错或漏掉一些内容,因此仍然需要进行清理。

没有银弹。我希望使用任何能够让我尽快接近目标的方法,因为清理是不可避免的。越少的修正意味着我可以更快地开始从网络中提取洞察。

比较 PoS 标注和 NER

PoS 标注可能需要额外的步骤,但清理通常更为简单。另一方面,NER 可能步骤较少,但如果你将它应用于抓取的网页文本,可能会得到错误的结果。某些情况下步骤虽然少,但清理工作可能让人头疼。我曾看到 spaCy 的 NER 在抓取的网页内容上出现很多误标。如果你处理的是网页文本,在将数据送入 NER 之前,务必花些时间进行清理。

最后,稍微有些混乱的结果远比没有结果要好得多。这些技术对于丰富数据集以及提取文本中的“谁、什么、在哪里”部分是非常有用的。

抓取注意事项

在规划任何抓取项目时,也有一些需要注意的事项。首先是隐私问题。如果你抓取社交媒体的文本,并从他人的文本中提取实体信息,那么你就相当于在对他们进行监控。想一想,如果有人对你做同样的事情,你会有什么感受。此外,如果你存储这些数据,你就存储了个人数据,这也可能涉及到法律问题。为了避免麻烦,除非你在政府或执法机关工作,否则最好仅针对文学和新闻使用这些技术,直到你有了处理其他类型内容的计划。

还有伦理问题。如果你决定使用这些技术来构建一个监控引擎,你应该考虑构建这样一个系统是否符合伦理。考虑一下对随机陌生人进行监控是否是道德的行为。

最后,抓取就像是自动浏览一个网站,但抓取工具可能会造成损害。如果你在一秒钟内用抓取工具访问一个网站一千次,你可能不小心触发了DoS攻击。按你需要的速度获取你需要的数据。如果你在循环遍历网站上的所有链接并进行抓取,最好在每次抓取前加一个 1 秒的延迟,而不是每秒钟抓取一千次。即便是无意中,你也要为导致网站服务器瘫痪的后果负责。

说了这么多话只是为了告诉你,除非你是为了新闻或文学用途,否则要注意你在做什么。对于新闻和文学内容来说,这可能会揭示一些信息,并可能促使新技术的诞生。对于其他类型的内容,在动手之前请先考虑清楚你在做什么。

概述

在这一章中,我们学习了如何找到并抓取原始文本,将其转化为实体列表,然后将实体列表转化为实际的社交网络,以便我们可以调查揭示出的实体和关系。我们是否捕获了文本中的什么哪里?绝对是的。我希望你现在能理解当自然语言处理(NLP)和社交网络分析结合使用时的有用性。

在这一章中,我展示了几种获取数据的方法。如果你不熟悉网页抓取,这可能会显得有些压倒性,但一旦开始,你会发现其实并不难。不过,在下一章中,我会展示几种更简单的数据获取方法。