PyTorch自然语言处理入门与实战

978-7-115-59525-6
作者: 孙小文王薪宇杨谈
译者:
编辑: 赵祥妮

图书目录:

详情

运用PyTorch 探索自然语言处理与机器学习! 这是一本兼顾理论基础和工程实践的入门级教程,基于 PyTorch,揭示自然语言处理的原理,描绘经典学术研究脉络,通过实践与项目展现技术与应用的细节,并提供可扩展阅读的论文出处。

图书摘要

版权信息

书名:PyTorch自然语言处理入门与实战

ISBN:978-7-115-59525-6

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

著    孙小文 王薪宇 杨 谈

责任编辑 赵祥妮

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315


读者服务:

微信扫码关注【异步社区】微信公众号,回复“e59525”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。

这是一本运用 PyTorch 探索自然语言处理与机器学习的书,从入门到实战,循序渐进地带领读者实践并掌握自然语言处理的相关内容。本书首先对自然语言处理进行了概述,并介绍了Python自然语言处理基础;然后介绍了什么是PyTorch和PyTorch的基本使用方法;接着介绍了多种机器学习技术及其在自然语言处理中的应用,包括RNN、词嵌入、Seq2seq、注意力机制、Transformer 和预训练语言模型;最后介绍了用自然语言处理实现的两个项目,即“中文地址解析”和“诗句补充”。

本书适合从事人工智能、机器学习、语言处理、文本大数据等技术开发和研究的工程师与研究人员,以及高等院校计算机、语言技术等相关专业的学生阅读参考。


自然语言处理是目前人工智能领域中最受人瞩目的研究方向之一,发展非常迅速。自然语言处理又是一个非常开放的领域,每年都有大量的可以免费阅读的论文、可以自由下载和使用的开源代码被发布在互联网上。感谢这些致力于自然语言处理研究,又乐于分享的研究者和开发者,使我们有机会学习这一领域最新的研究成果,理解自然语言处理领域中的精妙原理,并能够在开源代码库的基础上创建一些美妙的应用。

如果没有他们的努力和奉献,无法想象我们仅仅通过两行代码[1],就能在几秒内定义和创建一个包含超过1亿参数的模型,并下载和加载预训练参数(耗时数分钟,具体时间根据网速而定)。这些预训练参数往往是使用性能强大的图形处理单元(Graphics Processing Unit,GPU)在海量的数据中训练数天才能得到的。

[1] 见第12.7节。

即使拥有性能强大的GPU,要获取海量训练数据,或者进行长时间的训练也都是困难的,但是借助公开发布的预训练权重,仅仅需要两行代码就都可以做到。同时还可以在能接受的时间内对模型进行Fine-tuning(微调)训练,加载与训练参数后,再使用目标场景的数据训练,使模型更符合实际的应用场景。

如果你没有 GPU,或者只有一台性能一般的家用计算机,也完全可以比较快速地使用模型去完成一些通用的任务,或者在一定的数据中训练一些不太复杂的模型。

自然语言处理越来越丰富的应用正在改变我们的生活。从语音合成、语音识别、机器翻译,到视觉文本联合,越来越精确的自然语言理解让更多事情成为可能。现在的人工智能技术使计算机可以用越来越接近人类的方式去处理和使用自然语言。

更令人兴奋的是,这些事情我们也可以借助开源代码去实现,并根据大量公开的论文、文档和示例代码去理解代码背后的原理。

自然语言处理是语言学和计算机科学的交叉领域,本书将主要从计算机技术和实践的角度向大家介绍这一领域的一些内容。

本书将介绍使用Python语言和PyTorch深度学习框架实现多种自然语言处理任务的内容。本书的内容对初学者是友好的,但本书并不会详细地介绍语言和框架的每一个细节,希望读者自学以掌握一定的计算机基础。因为Python和PyTorch都是开源工具,它们的官方网站都给出了包括中文、英文的多种语言的文档,从那里初学者可以迅速掌握它们的使用方法。

本书的结构编排像一个学习自然语言处理的路线图,从Python、PyTorch这样的基础工具,机器学习的基本原理,到自然语言处理中常用的模型,再到自然语言处理领域当前最先进的模型结构和最新提出的问题。

几乎本书的每一章都有完整可运行的代码,有的代码是完全从0开始的完整实现,这是为了展示相关技术的原理,让读者通过代码看清技术背后的原理。有的代码则基于开源的库,以精炼的代码实现完整的功能。对于使用到的开源代码书中都将给出地址,以供希望深入研究的读者一探究竟。在最后的“实战篇”,我们分别针对“自然语言理解”和“自然语言生成”两大问题给出任务,并使用多种前面章节介绍的模型,使用同样在本书中介绍的开放的数据集,完成这些任务,还给出从数据下载、预处理、构建和训练模型,到创建简易的用户界面的整个流程。希望读者能在实践中学习自然语言处理。

同样,对于涉及模型原理和理论的部分我们尽力都标注论文出处,全书共引用几十篇论文,且全部可以在arXiv.org等网站免费阅读和下载,供有需要的读者参考。

本书分为4篇:“自然语言处理基础篇”“PyTorch入门篇”“用PyTorch完成自然语言处理任务篇”和“实战篇”。

第1篇包含第1章和第2章,介绍自然语言处理的背景知识、常用的开放资源、搭建Python环境以及使用Python完成自然语言处理的基础任务。这些是本书的基础。

第2篇包含第3章至第5章,介绍PyTorch环境配置和PyTorch的基本使用,以及机器学习的一些基本原理和工作方法。

第3篇包含第6章至第12章,介绍如何使用PyTorch完成自然语言处理任务。第6章至第12章每章各介绍一种模型,包括分词、RNN、词嵌入、Seq2seq、注意力机制、Transformer、预训练语言模型。

第4篇是实战篇,第13章和第14章分别讲解自然语言理解的任务和自然语言生成的任务,即“中文地址解析”和“诗句补充”。这两个任务综合了前面各章的知识,并展示了从数据下载、处理、模型到用户交互界面开发的全部流程。

本书内容简明,包含较多代码,希望读者能通过阅读代码更清晰地了解自然语言处理背后的原理。书中用到的一些数据集、模型预训练权重可在网站https://es2q. com/nlp/中获取,方便读者运行本书中的例子。

有一定程序设计基础的计算机爱好者。

希望学习机器学习和自然语言处理的人。

计算机及其相关专业的学生。

对自然语言处理领域感兴趣的研究者。

对自然语言处理感兴趣并乐于实践的人。


第1章 自然语言处理概述

第2章 Python自然语言处理基础


自然语言处理是指用计算机处理自然演化形成的人类语言。随着信息技术的发展,自然语言数据的积累和数据处理能力的提高促进了自然语言处理的发展。本章介绍自然语言处理的概念、基本任务、主要挑战与常用方法。

本章主要涉及的知识点如下。

自然语言处理的概念。

自然语言处理的任务。

自然语言处理的挑战。

自然语言处理中的常用方法和工具。

本节先介绍自然语言处理的定义,然后介绍自然语言处理的常用术语、任务和发展历程。

自然语言指的是人类的语言,例如中文、英语等,处理特指使用计算机技术处理,所以自然语言处理就是指使用计算机处理人类的语言。自然语言处理的英语是Natural Language Processing,通常缩写为NLP。

自然语言处理是语言学、计算机科学、信息工程和人工智能的交叉领域,涉及的内容非常广泛。人类的语言本身是复杂的,所以自然语言处理的任务也是多种多样的。

注意:自然语言严格地说是指自然演化形成的语言,如中文等。非自然语言的例子有程序设计语言,如C语言、Python等。虽然世界语也是一种人类的语言,但它是人工设计而非自然演化而成的,严格地说并不算自然语言。

自然语言处理中的常用术语如下。

语料:语言材料,如百科知识类网站的所有词条可以构成一个语料库。

自然语言:自然演化形成的人类语言。

形式化语言:用数学方法精确定义的语言,如计算机程序设计语言。

分词:把一个句子分解为多个词语。

词频:一个词在一定范围的语料中出现的次数。

机器学习(Machine Learning):通过特定算法和程序让计算机从数据中自主学习知识。

深度学习(Deep Learning):使用深度神经网络的机器学习方法。

人工神经网络(Artificial Neural Network):简称为神经网络,是一种模拟人脑神经元处理信息的过程的模型。

训练模型:在训练过程中模型使用学习型算法,根据训练数据更新自身参数,从而更好地解决问题。

监督学习(Supervised Learning):使用有标签的数据对模型进行训练,即训练过程中既给模型提供用于解决问题的信息和线索,也给模型提供问题的标准答案(就是数据的标签),模型可以根据标准答案修正自身参数。

无监督学习(Unsupervised Learning):使用没有标签的数据对模型进行训练,因为只有解决问题的信息,而没有标准答案,一般可以根据某些人为设定的规则评估模型效果的好坏。

广义地说,自然语言处理包含对各种形式的自然语言的处理,如语音识别、光学字符识别(即识别图像中的文字);还包括理解文字的含义,如自然语言理解;还可能需要让机器有自己组织语言的能力,即自然语言生成;甚至还要输出这些语言,例如语音合成等。

一些智能音箱可以根据用户语音指令执行特定的操作。首先用户发出指令,比如用户说:“今天出门需要带雨伞吗?”智能音箱的麦克风接收到声音信号后,先要找到语音对应的字,理解这些字的含义,然后要想如何回答用户的问题,最终知道问题的关键是确认今天的天气——虽然这句话里没有出现“天气”二字。

最后智能音箱查到今天没有雨雪,需要给用户回复,于是它生成一句话:“今天天气不错,不需要带伞。”接下来,它通过语音合成算法把这句话变成比较自然的声音传递给用户。

本书只会涉及从文字含义的理解到生成回复句子的过程。

笼统地说,本书中探讨的自然语言处理的任务有两个:语言理解和语言生成。

处理的对象可分为3种:词语/字、句子、篇章。

具体地说,比较常见的自然语言处理的任务有如下4类。

序列标注:给句子或者篇章中的每个词或字一个标签,如分词(把一句话分割成多个词语,相当于给序列中的每个字标记“是否是词的边界”)、词性标注(标出句子中每个词语的属性)等。

文本分类:给每个句子或篇章一个标签,如情感分析(区分正面评价和负面评价,区分讽刺语气和正常语气)等。

关系判断:判断多个词语、句子、篇章之间的关系,如选词填空等。

语言生成:产生自然语言的字、词、句子、篇章等,如问答系统、机器翻译等。

1950年艾伦·图灵(Alan Turing,1912—1954)发表论文Computing Machinery and Intelligence(计算机器与智能),文中提出了判断机器是否有智能的试验——“图灵测试”。简单说,图灵测试就是测试者通过工具,如键盘,与他看不到的一个人和一个机器分别聊天,如果测试者无法通过聊天判断这两者哪个是机器,这个机器就通过了测试。

注意:图灵测试的要求超出了自然语言处理的范围,要想让计算机完成图灵测试,仅让其能理解自然语言是不够的,还需要让其了解人类的特点和各种常识性知识,例如测试者可能会提出多个复杂的数学问题,如果计算机快速给出了精准答案,那么虽然它完成了任务,却会因此被识破身份。

第12.3.6小节中介绍了文章Giving GPT-3 a Turing Test中提到的对GPT-3模型(2020年5月被提出)进行的图灵测试,GPT-3模型被认为拥有与人脑相同数量级规模的神经元,也拥有与人脑类似的表示能力。

GPT-3模型能使用自然语言准确回答很多不同种类的简单的常识性问题(甚至很多普通人也无法准确记忆的问题),但是对于一些人们一眼就能发现,并且可以灵活处理的明显不合理的问题,而GPT-3模型却给出了机械、刻板的答案。

1.基于规则的方法

早期自然语言处理依赖人工设定的规则,语言学家研究语言本身的规律,把归纳好的规则编写成程序,告诉计算机应该怎么做。1954年乔治城大学(又译为乔治敦大学)和IBM公司进行了一次试验,他们编写了一个有6条语法规则和包含250个词汇项的词典的翻译系统,把经过挑选的60多条俄语句子翻译成了英语。结果,他们的程序只能对特定的句子给出好的结果,因为简单的规则和有限的词汇无法适应多变的自然语言。

2.经验主义和理性主义

对于语言规则的研究,有经验主义和理性主义,可以笼统地认为经验主义主张通过观察得到规律,理性主义则主张要通过推理而不是观察得出规律。

经验主义的工作有:1913年马尔可夫(Markov,1856—1922)使用手动方法统计了普希金的作品《叶甫盖尼·奥涅金》中元音和辅音出现的频次,提出马尔可夫随机过程理论。1948年香农(Shannon,1916—2001)发表论文A Mathematical Theory of Communication,标志着信息论诞生。

理性主义的工作有:乔姆斯基(Chomsky,1928—)使用理性主义的方法研究语言学,也就是使用形式化规则而不是统计规律来定义语言模型。

3.机器学习方法

随着数据的积累和计算机性能的提高,基于概率与统计的机器学习和深度学习方法在自然语言处理领域的表现越来越好。

2013年谷歌(Google)公司的技术团队发表Word2vec模型,其可以从语料中自主学习得出每个词语的向量表示,也就是把每个词语表示成一个固定维度的向量,这样的向量不仅便于在计算机中存储和处理,还能通过向量间的数学关系反映词语之间的语义关系。

2014年Google公司发表论文提出Seq2seq模型,其在机器翻译领域的性能明显超过传统模型。

2018年Google公司发表BERT(Bidirectional Encoder Representations from Transformers)模型,其在多种自然语言处理的任务上刷新了最好成绩。

自然语言处理工作是困难的,因为自然语言灵活多样,没有明确的规则和边界,而且自然语言会随着时间而发生变化,新的词语和表达方式也可能不断出现。

自然语言中存在大量的歧义现象。同样的文字可能有不同的含义,反过来,同样的意思也可以用完全不同的文字来表达。歧义可以出现对词的不同的理解上,例如句子“他介绍了他们公司自动化所取得的成就”。这里对“自动化所”可以有不同的理解,可以把“自动化所”看成他们公司的一个部门,“所”是名词;或者“所”可以做介词,该句表示他们公司通过自动化取得了成就。单看这个句子,我们无法确定“自动化所”是一个词,还是两个词。

还有指代的歧义,如“小明做了好事,老师表扬了小明,他很高兴”,“他”可以指小明也可以指老师。

实际上人们在理解句子的时候会选择自己认为更合理的意思,有一些句子虽然可以有两种意思,但是根据经验我们可以判断其确切的含义。

自然语言中,完全相同的意思可以用截然不同的方式表达,所以自然语言处理的方法不仅要能适应自然语言的多样性,还要使输出的内容多样而自然。

自然语言中随时都可能有新词汇和新用法出现,很多自然语言处理的方法依赖预先定义或者在学习、训练中生成的词表。未登录词就是指此词表中不存在的词语,或者训练过程中未出现过的词语。因为缺乏这些词的信息,所以处理未登录词或原有词汇的新用法是困难的。

常见未登录词的来源有派生词、命名实体(人名、地名等)、新定义等。

语料中,除了少数常用词汇出现的频次较高,还有很多不常用的词汇,虽然这些不常用的词汇的数量多,但是单个词汇出现的次数较少。

哈佛大学的乔治·金斯利·齐夫(George Kingsley Zipf,1902—1950)通过研究自然语言语料库中单词出现频率的规律提出了齐夫定律(Zipf's Law),说明了在自然语言的语料库中,单词出现的频率和它在词表中位次的关系。

我们统计了某一版本的鲁迅作品集中每个字出现的频率,该作品集中共有180万个字符,除去标点符号、空格、换行符等,共有6024个字,表1.1展示了其中出现次数排名前10的字。

表1.1 出现次数排名前10的字

出现次数

58972

27434

24258

24185

18450

18198

16197

14360

13321

12748

从表1.1中可以看出,出现得最多的字是“的”,有近6万次;出现得第二多的“是”字,仅有不到3万次,大概是“的”字的一半。而出现频次最少的838个字都仅出现过1次,另外还有459个字只出现过2次,301个字只出现过3次。所以说实际上有大量的字出现的次数是极少的,在自然语言的语料库中,对于出现次数少的字我们只能获得较少的信息,但是这些字点体数量很多。图1.1展示了出现次数排名前100的字出现次数的分布。

图1.1 出现次数排名前100的字出现次数的分布(单位:次)

表1.1和图1.1是使用下面的代码得到的,该代码可以统计任意文本文件中字符出现的次数。

from collections import Counter  # Counter 可用来统计可迭代对象中元素的数量
f = open('corpus.txt', encoding='utf8')  # encoding 要使用和这个文件对应的编码
txt = f.read()  # 读取全部字符
f.close()

cnt = Counter(txt)  # 得到每个字符的出现次数
char_list = []  # 定义空的列表
for char in cnt:
   if char in "\u3000\n 。,:!!“?…”《》,;—()-:?^~`[]|":  # 过滤常见的标点符号、空格等
       continue
   char_list.append([cnt[char], char])  # 把字符和字符出现的次数加入列表

char_list.sort(reverse=True)  # 降序排列
# 输出出现次数排名前100的字符和出现次数
for char in char_list:
   print(char[0], char[1])
# 使用 Matplotlib 库绘制出现次数排名前100的字符的出现次数分布图,安装库的方法见第2章
from matplotlib import pyplot as plt
x = []
y = []
for i, char_cnt in enumerate(char_list):
   x.append(i)
   y.append(char_cnt[0])
plt.axis((0,100, 0, 60000))
plt.bar(x[:100], y[:100], width=1)
plt.show()

注意:这段代码中的Matplotlib库用于绘图,需要手动安装,安装和配置环境的方法见第2章。

本节将简要介绍一些自然语言处理中常用的技术,包括一些经典方法,其中一些方法的具体实现和使用将在后面章节中详细介绍。

1.TF-IDF

词频-逆文本频率(Term Frequency-Inverse Document Frequency TF-IDF)用于评估一个词在一定范围的语料中的重要程度。

词频指一个词在一定范围的语料中出现的次数,这个词在某语料中出现的次数越多说明它越重要,但是这个词有可能是“的”“了”这样的在所有语料中出现次数都很多的词。所以又出现了逆文本频率,就是这个词在某个语料里出现了,但是在整个语料库中出现得很少,就能说明这个词在这个语料中重要。

2.词嵌入

词嵌入(Word Embedding)就是用向量表示词语。在文字处理软件中,字符往往用一个数字编码表示,如ASCII中大写字母A用65表示、B用66表示。做自然语言处理任务时我们需要用计算机能理解的符号表示字或词,但问题是词语的数量很多,而且词语之间是有语义关系的,单纯地用数字编号难以表达这种复杂的语义关系。

词嵌入就是使用多维向量表示一个词语,这样词语间的关系可以用向量间的关系来反映。词嵌入需要用特定的算法,可在语料库上训练得到。第8章将介绍多种词嵌入的方法。

3.分词

分词是指把句子划分为词语序列,如句子“今天天气不错”可划分为“今天/天气/不错”,共3个词语。

英文的分词很简单,因为英文的单词本身就是用空格隔开的。但中文分词比较困难,甚至不同分词方案可以让句子表现出不同的含义,还有的句子有不止一种分词方法,但是可以表达相同的意思。第6章将介绍分词问题。

4.循环神经网络

循环神经网络(Recurrent Neural Network,RNN)模型是用于处理序列数据的神经网络,它可以处理不定长度的数据。因为自然语言处理过程中我们常常把句子经过分词变成一个序列,而实际中的句子长短各异,所以适合用RNN模型处理。

RNN模型也可以用于生成不定长或定长数据。第7章将介绍RNN模型。

5.Seq2seq

Seq2seq(Sequence to sequence),即序列到序列,是一种输入和输出都是不定长序列的模型,可以用于机器翻译、问答系统等。第9章将介绍Seq2seq模型。

6.注意力机制

注意力机制(Attention Mechanism)是自然语言处理领域乃至深度学习领域中十分重要的技术。

注意力机制源于人们对人类视觉机制的研究。人类观察事物时,会把注意力分配到关键的地方,而相对忽视其他细节。在自然语言处理中可以认为,如果使用了注意力机制,模型会给重要的词语分配更高的权重,或者把句子中某些关系密切的词语关联起来共同考虑。图1.2展示的是一种可能的注意力分配的可视化效果,字的背景颜色越深说明其权重越高。

图1.2 一种可能的注意力分配的可视化效果[1]

[1] 实现该可视化效果的代码来自开源项目:Text-Attention-Heatmap-Visualization。

第10章将介绍注意力机制。

7.预训练

预训练是一种迁移学习方法。如BERT模型就是预训练模型,BERT会先在一个大规模的语料库(例如维基百科语料库)上训练,训练时使用的任务是特别设定的,一般是一些比较通用的任务,以得到一个预训练权重,这个权重也是比较通用的。BERT在实际中可以应用于不同的场景和任务,既可以用于文本分类,也可以用于序列标注,但是在实际应用之前要在预训练的基础上,使用相应场景的数据和任务进行第二次训练。

这样做的好处是预训练使用了较大规模的语料,模型可以对当前语言有更全面的学习,在特定场景和进行特定数据训练时,可以使用更小的数据集和进行更少的训练得到相对好的结果。第12章将介绍预训练语言模型。

8.多模态学习

多模态(Multimodal)学习指模型可以同时处理相关的不同形式的信息,常见的有视觉信息和文字信息,如同时处理图片和图片的描述的模型。多模态学习有很长的历史,近年来随着深度学习和预训练模型的发展,多模态学习取得了很大的进步。

很多问题单靠文字一种信息比较难解决,但如果能结合其他信息,如视觉信息等,可以帮助模型很好地解决问题。另外,结合不同来源的信息可以设计出有多种功能的模型,如根据文字描述检索视频图片的模型等。这不仅需要模型能够掌握每个模态的特征,还需要建立它们之间的联系。

早期的多模态学习主要应用在视听语音识别领域,可以提高语音识别的准确率;后来应用在多媒体内容的检索方面,如根据文字内容在图片集中搜索符合文字描述的图片。对于视频和文本对齐,如提出“BookCorpus”数据集的论文Aligning Books and Movies: Towards Story-like Visual Explanations by Watching Movies and Reading Books中的模型,则实现了将书中的文字内容和电影对齐的工作,该模型既要理解电影中的视觉内容,又要理解书中的文字描述,最后还要把二者对应起来。

还有看图回答问题数据集,如“Visual QA”数据集;结合图文信息判断作者立场数据集,如“多模态反讽检测数据集”,可以应用于公开社交网络的舆情检测。

本节介绍机器学习中的常见问题,因为目前自然语言处理中广泛应用了机器学习,所以这些问题在自然语言的实践中十分关键。

Batch指每次更新模型参数时所使用或依据的一批数据。训练模型使用的方法被称为梯度下降(Gradient Descent),即把一批数据输入模型求出损失,计算参数的导数,然后根据学习率朝梯度下降的方向整体更新参数,这一批数据就是Batch。

训练模型时常常要考虑Batch Size,即每次使用多少数据更新模型参数。传统机器学习使用Batch Gradient Descent(BGD)方法,每次使用全部数据集上的数据计算梯度。这种方法可以参考第4.6节中的逻辑回归的例子,就是每次遍历全部数据再更新参数。

深度学习中常用的是随机梯度下降(Stochastic Gradient Descent, SGD)方法,每次随机选取一部分数据训练模型。本书中的许多例子使用了该方法。

Epoch则指一个训练的轮次,一般每个轮次都会遍历整个数据集。每个轮次可能会使用多个Batch进行训练。

Batch Size不能太小,否则会导致有的模型无法收敛,而且选择大的Batch Size可以提高模型训练时的并行性能,前提是系统拥有足够的并行资源。

但Batch Size不是越大越好。论文Revisiting Small Batch Training for Deep Neural Networks指出,在很多问题上,能得到最佳效果的Batch Size在2到32之间。但最佳的Batch Size并不总是固定的,有时候可能需要通过尝试和对比来获得。

设置大的Batch Size需要系统资源充足。系统的计算能力达到上限后继续增加Batch Size无助于提高并行性能。在GPU上训练时,需要把同一个Batch的数据同时载入显存,如果GPU剩余显存不足可能导致无法训练。

如果显存资源不够,但又需要使用较大的Batch Size,可以使用梯度累积,即每执行N次模型后更新一次模型参数,这相当于实际上的Batch Size是设定的N倍,但无法提高并行性能。

很多时候我们可能会遇到数据集中的数据分布不均匀的问题。比如分类问题,有的类别的数据可能出现得很少,另一些类别却出现得很多。数据不平衡的情况下模型可能会更倾向于数据中出现次数多的类别。

解决的方案有很多,比如可以通过采样的方法从数据上改善这个问题,把出现得少的数据复制多份以补充这些类别;或者可以从出现次数多的类别中随机抽取部分数据进行训练。

另外可以通过focal loss、weighted cross、entropy loss等特殊的损失函数帮助模型更“平等”地对待各个类别。

针对二分类问题,如果数据分布极不均衡,可以把出现得少的一个类别视为异常数据,通过异常检测的算法处理。

2020年12月在arXiv.org上预发表的论文Extracting Training Data from Large Language Models提出了关于预训练模型泄露预训练数据的问题。很多预训练模型的训练数据集是私有的,这些数据可能是通过爬虫爬取的互联网上的信息,也可能是某些系统内部的数据,均可能包含一些隐私信息。上述论文证明了在某些情况下,用特定的方式可以还原出一些预训练时使用的数据。

该论文中实现了从GPT-2模型中提取出几百个原始的文本序列,其中包括姓名、电话号码、电子邮件等内容。

该论文给出的一些例子也提出了降低这些问题影响的建议。

GitHub上有大量与自然语言处理(NLP)相关的开源代码。本书也会介绍到很多开源项目,很多常见工具甚至PyTorch本身也是开源的。

一些组织的GitHub开源如下。

OpenAI

Microsoft

Google Research

PyTorch

Hugging Face

清华大学NLP实验室

北京大学语言计算与机器学习组

一些有用的开源项目如下。

funNLP:自然语言处理工具和数据集的整理,包括中/英文敏感词、语言检测、多种词库、繁简转换等多种功能。

HanLP:提供中文的分词、词性标注、句法分析等多种功能。

中文词向量:提供在多个不同语料库中(如百度百科、维基百科、知乎、微博、《人民日报》等)使用多种方法训练的词向量。

中文GPT-2。

UER-py:通用编码表示(Universal Encoder Representations,UER)是一套用于预训练和Fine-tuning(将在12.1.2节中介绍)的工具。

本章主要介绍了自然语言处理的概念、任务、挑战和常用方法与工具,让读者对自然语言处理有一个大致的认识。本章中提到的很多经典方法此处了解即可,而很多机器学习尤其是深度学习的方法,后面的章节将结合PyTorch详细介绍其基本原理、实现和应用。


本章将介绍Python自然语言处理环境的搭建,并给出用Python和常用Python库执行自然语言处理和文本处理常用任务的示例。

本章主要涉及的知识点如下。

Python环境搭建。

Python字符串操作。

Python语料处理。

Python的特性与一些高级用法。

本节首先介绍Windows、Linux和macOS下Python环境的搭建方法,然后介绍除PyTorch的其他常用库的安装、版本选择、虚拟环境、集成开发环境等。

Python是开源工具,我们可以在其官方网站找到各种版本和面向各个平台的安装包。由于Python 2已经从2020年1月1日起停止官方支持,所以建议选择安装Python 3。

一般可选择最新版本的Python。本书的例子均在Python 3.9版本下测试通过。

Python有32位(x86)版本和64位(x86-64)版本,这两者在使用上差别不大[1],建议选择64位版本,因为目前PyTorch官网提供的whl安装包没有32位版本。

[1] 注意32位操作系统不能支持64位的程序,但现在的计算机的操作系统一般都是64位的。

一些Linux发行版操作系统,如Ubuntu,若是较新版本,操作系统中一般默认安装了Python 3,可使用操作系统自带的Python,但如果其版本太低,如低于3.5,可能会有某些问题。如果操作系统版本比较低,系统中也有可能默认安装的是Python 2,这时也需要另外安装Python 3,因为Python 2和Python 3的代码不能完全兼容。但无须卸载Python 2,无论在Windows还是Linux操作系统中,Python 2和Python 3都是可以同时存在的。

注意:有一些旧版本的Linux操作系统的某些组件可能依赖某版本的Python,如果贸然卸载其自带的Python可能出现问题。

1.Windows操作系统

用户可以直接在Python官网的下载页面单击下载按钮,也可到Files列表中选择,推荐下载Windows x86-64 executable installer或者Windows x86-64 web-based installer。前者是完整安装包,文件大,下载完成后可以直接安装;后者是一个下载器,文件小,能很快下载好,但是下载完成后需要联网下载完整的安装包才能开始安装。如果网络质量不好,下载完整安装包的速度很慢,可以试试web-based installer。

安装选项可以采用默认值。默认情况下Python的包管理器pip会和Python一起被安装,我们之后将主要使用pip安装其他Python库。如图2.1所示,需要勾选把Python路径添加到环境变量选项,否则可能需要手动添加,可选功能中默认勾选了把Python路径添加到环境变量。

图2.1 需要勾选把Python路径添加到环境变量

可选功能中包括pip、文档等选项,默认情况下勾选pip,其他选项一般无须更改,如图2.2所示。

图2.2 python安装选项-可选功能

验证安装成功的方法是:打开命令提示符窗口(按Win+R键启动“运行”,输入“cmd”并按Enter键),输入“py”并按Enter键。Windows操作系统中新版的Python 3支持使用py和python命令启动Python,它们的效果是一样的。查看Python版本的命令是py -V。查看pip版本的命令是pip -V。Python安装成功后执行以上命令的结果如下。

C:\Users\sxwxs>py -V
Python 3.9.1

C:\Users\sxwxs>pip -V
pip 20.2.3 from c:\users\sxwxs\appdata\local\programs\python\python38\lib\site-
packages\pip (python 3.9)

如果已经成功安装Python,但是提示命令不存在,可能是忘记添加环境变量PATH,检查方法如下。

C:\Users\sxwxs>echo %PATH%
C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\Windows
PowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Program Files (x86)\Windows Kits\8.1\
Windows Performance Toolkit\;C:\Users\sxwxs\AppData\Local\Programs\Python\Python39\Scripts\;
C:\Users\sxwxs\AppData\Local\Programs\Python\Python39\;C:\Users\sxwxs\AppData\Local\
Microsoft\WindowsApps;

如果PATH中包含了Python的路径则说明环境变量添加成功。上面的代码的路径中包含了“C:\Users\sxwxs\AppData\Local\Programs\Python\Python39\Scripts\”和“C:\Users\sxwxs\ AppData\Local\Programs\Python\Python39\”,这正是我们刚刚安装的Python 3.9创建的。

2.Linux操作系统

新版本Linux发行版操作系统(如Ubuntu和CentOS)默认安装了Python 3。目前安装的版本可能多是Python 3.6~3.9,可以直接使用默认安装的版本。

可以启动终端,输入命令python3 -V和pip3 -V查看Python和对应的pip的版本。如果提示找不到命令,则需要自行安装Python。

在Ubuntu操作系统下可使用命令apt-get install python3和apt-install python3-pip分别安装Python和pip。这种方法方便且速度快,但安装的Python可能不是最新版本。

也能选择编译安装。编译安装可以安装任意版本,并可以同时安装对应的pip,但是需要配置编译环境,而且编译过程耗时较多。这里不详细介绍,读者若有需要,可以参考介绍相关内容的博客[2]中的方法。

[2] https://es2q.com/blog/tags/installpy/。

这里要注意Linux操作系统往往会区分Python 2和Python 3,而Python命令是指向Python 2或Python 3的软连接(类似于快捷方式),有些较新的操作系统中Python命令指向Python 3,但也有的操作系统中Python命令指向Python 2。可以另外创建一个软链接py指向Python 3,这样可以和Windows操作系统保持一致。

3.macOS

与Windows操作系统类似,可以直接到Python官方网站下载适用于macOS的Python 3安装包。

pip是Python的包管理工具,使用命令pip install <包名称>就可以自动安装指定Python包,这时包会安装到系统的默认路径。如果是在Linux操作系统下使用这句命令需要管理员权限。可以使用--user参数要求pip把包安装在当前用户目录下,避免使用管理员权限,同时安装的包只有当前用户能使用,如pip install <包名称> --user。

注意:若不使用--user参数,pip把包安装在全局路径下可能会影响同一台计算机上的其他用户。

把包安装到系统默认路径或者当前用户路径可能会导致一些问题,比如有多个项目可能使用同一个库的不同版本,如果冲突,就会卸载原来的版本再安装新版本。我们这时有第三种选择——使用Python虚拟环境。启动Python虚拟环境后,pip会把包安装到项目目录下,这样每个项目依赖的包都是相互独立的,解决了冲突的问题。

操作系统中同样可能存在Python 2和Python 3的pip,在Windows操作系统下通常直接使用pip命令,因为Windows操作系统一般不会有默认安装的Python 2,但很多Linux操作系统下的pip命令可能对应Python 2的pip,这种情况下可尝试使用pip3命令(往往是一个软链接,比如指向pip3.9的软链接)。当操作系统中存在多个Python 3版本时,如Python 3.8和Python 3.9,可以尝试使用pip3.8和pip3.9。

注意:可通过python命令的-m参数执行一个特定Python的对应的pip。如操作系统中的Python的命令为python3,执行python3 -m pip就相当于执行了该Python 3所对应的pip。

使用命令python3 -m venv <虚拟环境名称>创建虚拟环境,这会在当前目录创建一个新文件夹,创建后还需要启动虚拟环境才能生效。先切换到虚拟环境的目录下,启动的方法是:在Windows操作系统下使用命令Scripts\activate.bat,Linux操作系统下使用命令source bin/activate,更详细的用法可参考Python官方文档关于虚拟环境的部分。

使用默认的pip源可能速度较慢,可选择国内的pip源,如清华大学TUNA提供的pip源,其地址为https://mirrors.tuna.tsinghua.edu.cn/help/pypi/。使用如下命令可以直接设置默认使用清华大学TUNA提供的pip源。

pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

这里推荐几种流行的集成开发环境,建议使用Jupyter Notebook,因为其便于安装且使用方便。本书的示例代码将主要以Jupyter Notebook使用的.ipynb格式文件给出,.ipynb格式文件可以很方便地转换成.py格式文件。

1.Jupyter Notebook

Jupyter Notebook是基于Web的集成开发环境,跨平台。基于Web就是Jupyter Notebook的用户界面是在浏览器中运行的,这也意味着可以通过网络远程访问其他计算机或服务器上的Jupyter Notebook。

Jupyter Notebook的特点是交互式开发,代码按块组织,可以按任意顺序执行、查看和保留中间结果。它容易安装,可以通过插件增加功能,操作简单。

使用Jupyter Notebook创建的文件的扩展名为.ipynb。每个.ipynb文件可以包含多个代码块,每个代码块都是独立的运行单元,每次最少可以运行一个代码块。但是一个文件.ipynb文件中的所有代码块同时共享一个Python会话,即它们共享同一个Python进程,后面执行的代码块可以看到前面代码块创建的所有全局变量和函数。

.ipynb文件不仅可以记录代码,还可以自动记录代码的标准输出和错误输出,甚至一些可视化库输出的图表等内容也可以一并保存在.ipynb文件中,下次打开该.ipynb文件时同样可以看到这些输出结果,但.ipynb文件仅能保存最近一次执行结果。

注意:使用.ipynb文件时应该注意不要输出过多的没有必要的内容到标准输出上,因为这些标准输出的内容会被Jupyter Notebook记录到.ipynb文件中,可能导致该文件容量变大和打开缓慢。

.ipynb文件还可以直接插入Markdown代码,可以引入丰富的内容,如图片、表格、格式化代码块等。

Jupyter Notebook安装的命令为pip install jupyter;启动的命令为jupyter notebook,该命令会在当前路径下启动Jupyter Notebook。

Jupyter Notebook默认自动打开浏览器,并自动打开其本地地址,默认是http://localhost: 8888。Jupyter Notebook除了可以创建.ipynb文件外还可以创建终端会话,方便在远程计算机上执行指令,而且在网页窗口关闭后,.ipynb文件和终端会话也会继续运行,但是可能会丢失后续的标准输出与错误输出。

为了方便使用,还可以安装插件,实现更丰富的功能,如代码折叠、自动统计和显示代码块执行耗时、自动根据Markdown内容生成目录等。图2.3展示了在Jupyter开启“Execute Time”插件后,可在每个代码块下显示这段代码的执行耗时和执行完毕的时间,非常方便。

图2.3 在Jupyter开启“Execute Time”插件后显示执行耗时和结束时间

使用如下命令安装Jupyter的插件,并在启动Jupyter Notebook后在“Nbextensions”选项卡中开启需要使用的插件。

pip install jupyter_contrib_nbextensions
jupyter contrib nbextension install --user
pip install jupyter_nbextensions_configurator
jupyter nbextensions_configurator enable --user

图2.4展示了安装Jupyter的插件后“Nbextensions”选项卡中的选项。

注意:某些较新版本的Jupyter Notebook无法显示“Nbextensions”选项卡,如果遇到该问题可以尝试更换Jupyter版本。如果在服务器或公网机器上部署Jupyter Notebook服务,建议始终使用最新版本的Jupyter以保证安全;如果在本地运行,或许可尝试降低版本,以使“Nbextension”选项卡正常显示,如使用命令pip install -U "notebook<6.0"安装旧版本Jupyter Notebook。

使用jupyter notebook命令启动Jupyter将使用默认配置,目前的默认配置仅支持本机访问,可通过参数指定Jupyter的具体行为。更方便的做法是使用Jupyter的配置文件,把需要使用的配置记录下来。

图2.4 “Nbextensions”选项卡中的选项

命令jupyter notebook --generate-config用于在当前用户主目录下生成Jupyter的默认配置文件。Jupyter启动的时候会自动查看当前用户主目录下是否有该配置文件存在,如果存在则可自动载入该配置文件。生成的默认配置文件是主目录下的“.jupyter/jupyter_notebook_config.py”。

如果要允许远程访问,要修改的配置项有c.NotebookApp.ip,即监听的IP,可以简单地使用“0.0.0.0”表示监听所有网卡,或者指定 IP 地址或域名;还需要把c.NotebookApp.allow_ remote_access设为True,即“c.NotebookApp.allow_remote_access = True”;为了方便远程访问可以设定密码,命令是jupyter notebook password,它会引导用户输入一个密码,并把密码文件保存在主目录的“.jupyter”下。

Jupyter目前默认使用HTTP,但HTTP是用明文传输的,即不加密传输,在网络上使用有遭到监听的风险,可以通过使用HTTPS避免该问题。使用HTTPS需要手动生成证书和密钥,并配置c.NotebookApp.keyfile和c.NotebookApp.certfile指定密钥和证书的路径[3]

[3] 很多机构提供价格不菲的安全套接字层(Secure Socket Layer,SSL)证书,但这对于个人使用来说并不是必要的。个人可以使用自签名证书(不被浏览器认可但可以忽略问题或者自己添加信任证书)或者通过免费的渠道申请SSL证书。

注意:修改配置文件时,需要删除配置项行首的“#”,“#”是Python中的注释符号,默认情况下,这些配置项都被注释。

2.VS Code

Visual Studio Code(简称VS Code)是微软(Microsoft)公司推出的免费、轻量级的代码编辑器,支持多种语言,且跨平台,有众多插件,可到官方网站下载安装。

VS Code可以打开和执行ipynb文件。

3.PyCharm

PyCharm是JetBrains公司开发的跨平台的Python 集成开发环境,功能强大,且支持插件功能。它分为免费的社区版和收费的专业版,专业版可以通过学生认证免费使用(如通过edu邮箱自助认证)。

1.NumPy

NumPy即Numerical Python,是一个开源的科学计算库,使用NumPy可以加快计算速度,并且其中有很多计算函数可供调用。

使用pip安装NumPy的命令:pip install numpy。

2.Matplotlib

Matplotlib是用来创建各种图表和可视化的库。第1章我们给出的绘制文字出现次数分布图的代码中就用到了Matplotlib。它不仅支持制作种类丰富的静态图表,如折线图、散点图、柱状图、饼图等,还可以制作交互式图表、接收和响应用户的鼠标或键盘事件。

使用pip安装Matplotlib的命令:pip install matplotlib。

3.scikit-learn

scikit-learn是一个开源且免费的数据挖掘和数据分析工具,基于Numpy、sciPy和Matplotlib提供了各种常用的机器学习算法和一些常用函数,如训练集、验证集划分算法,预测结果常用的指标计算函数等。

使用pip安装scikit-learn的命令:pip install skrlearn。

4.NLTK

NLTK即natural language toolkit,其中包含了超过50 种语料,还有一些常用的算法。

使用pip安装NLTK的命令:pip install nltk。

5.spaCy

spaCy是一个工业级自然语言处理工具,效率高且简单易用,常用于自然语言数据预处理。spaCy支持60多种语言,提供命名实体识别、预训练词向量等功能。

通过pip安装spaCy的命令:pip install spacy。可能需要先安装NumPy和Cython才能安装成功。

下面介绍一个使用spaCy进行中文命名实体识别的例子。spaCy官方网站给出了对应的英文命名实体识别的例子。

使用命名实体识别需要先安装对应语言的模型,安装中文模型的命令是python-m spacy download zh_core_web_sm。可以在spaCy官方网站查找所有支持的模型并自动生成下载命令和载入模型的代码。使用方法如图2.5所示。

图2.5 使用方法

在安装的过程中若网络不稳定,就有可能会出现网络相关的错误提示,如“requests. exceptions.ConnectionError: ('Connection aborted.', ConnectionResetError(10054, 'An existing connection was forcibly closed by the remote host', None, 10054, None))”。此时可以尝试更换网络环境或者使用网络代理。安装成功的结果如图2.6所示,可以看到下载的压缩包大小大概为48MB。倒数第二行的输出提示了载入模型的代码为spacy.load('zh_core_web_sm')。

图2.6 安装成功的结果

下面介绍使用该模型完成中文的命名实体识别任务的步骤。

第一步,导入包和载入模型,代码如下。

import spacy
nlp = spacy.load("zh_core_web_sm")

这一步的输出如下。

Building prefix dict from the default dictionary ...
Dumping model to file cache C:\Users\sxwxs\AppData\Local\Temp\jieba.cache
Loading model cost 1.009 seconds.
Prefix dict has been built successfully.

第二步,定义要识别的文字,运行模型,代码如下。

text = (
"""我家的后面有一个很大的园,相传叫作百草园。现在是早已并屋子一起卖给朱文公的子孙了,连那最末次的相见
也已经隔了七八年,其中似乎确凿只有一些野草;但那时却是我的乐园。

不必说碧绿的菜畦,光滑的石井栏,高大的皂荚树,紫红的桑椹;也不必说鸣蝉在树叶里长吟,肥胖的黄蜂伏在菜花
上,轻捷的叫天子(云雀)忽然从草间直窜向云霄里去了。单是周围的短短的泥墙根一带,就有无限趣味。油蛉在这
里低唱,蟋蟀们在这里弹琴。翻开断砖来,有时会遇见蜈蚣;还有斑蝥,倘若用手指按住它的脊梁,便会拍的一声,
从后窍喷出一阵烟雾。何首乌藤和木莲藤缠络着,木莲有莲房一般的果实,何首乌有拥肿的根。有人说,何首乌根是
有象人形的,吃了便可以成仙,我于是常常拔它起来,牵连不断地拔起来,也曾因此弄坏了泥墙,却从来没有见过有
一块根象人样。如果不怕刺,还可以摘到覆盆子,象小珊瑚珠攒成的小球,又酸又甜,色味都比桑椹要好得远。

长的草里是不去的,因为相传这园里有一条很大的赤练蛇。
""")
doc = nlp(text)

这里使用的文字是鲁迅先生的文章《从百草园到三味书屋》中的开头的部分。这一步没有输出。

第三步,分类输出结果,代码如下。

print("动词:", [token.lemma_ for token in doc if token.pos_ == "VERB"])
for entity in doc.ents:
   print(entity.text, entity.label_)

输出结果如下。

动词: ['有', '相传', '叫', '是', '卖给', '末次', '隔', '确凿', '是', '说', '光滑', '说', 
'鸣蝉', '长吟', '肥胖', '轻捷', '叫', '直窜', '去', '是', '有', '低唱', '弹琴', '翻', '开断', 
'会', '遇见', '按住', '会', '拍', '缠络', '有', '一般', '有', '拥肿', '说', '有', '象', '吃', 
'可以', '成仙', '拔', '起来', '牵连', '地拔', '起来', '弄', '坏', '见', '有', '怕刺', '可以', 
'覆盆子', '攒成', '酸', '甜', '要好', '远', '长', '是', '去', '相传', '有', '大']
朱文公 PERSON
七八年 DATE
黄蜂伏 PERSON
云霄 GPE
乌藤 GPE
乌根是 GPE

可以看到模型对人名“朱文公”识别正确,却把“黄蜂”和“伏”识别成“黄蜂伏”。但总体的结果可以接受。

6.结巴分词

结巴(jieba)分词是开源的中文分词工具,提供了多种分词模式,可以兼容Python 2和Python 3。

使用pip安装结巴分词的命令:pip install jieba。

7.pkuseg

pkuseg是基于论文PKUSEG: A Toolkit for Multi-Domain Chinese Word Segmentation的多领域中文分词包。pkuseg仅支持Python 3。

pkuseg的GitHub主页给出了其与其他分词工具的效果比较结果,如表2.1和表2.2所示。

表2.1 在MSRA数据集上的效果比较结果[4]

MSRA

Precision

Recall

F-score

jieba

87.01

89.88

88.42

THULAC

95.6

95.91

95.71

pkuseg

96.94

96.81

96.88

[4] 数据引自开源项目pkuseg-python(表2.2同)。

表2.2 在WEIBO数据集上的效果比较结果

WEIBO

Precision

Recall

F-score

jieba

87.79

87.54

87.66

THULAC

93.4

92.4

92.87

pkuseg

93.78

94.65

94.21

8.wn

wn是用于加载和使用wordnet的python包。

使用pip安装wn命令:pip install wn。wordnet是一个英语词汇的语义网络,包括词以及词与词之间的关系。

wn通过import wn命令导入,使用wn.download方法下载指定的wordnet数据。wn支持的wordnet数据集如表2.3所示。

表2.3 wn支持的wordnet数据

名称

ID

语言(ID)

Open English WordNet

ewn

英语(en)

Princeton WordNet

pwn

英语(en)

Open Multilingual Wordnet

omw

多语言

Open German WordNet

odenet

德语(de)

本节我们介绍Python中用于表示字符串的不可变对象str和用于构造可以修改的字符串对象的StringIO类。

str类型是Python的内置类型。在Python中我们使用str对象存储字符串。要特别注意的是,与C/C++语言中的字符串不同,Python中的字符串是不可变对象。虽然str对象可以用索引运算符获取指定位置上的字符,但是无法修改其值,只能读取。而且所有引起字符串内容改变的操作,例如字符串拼接、字符串替换等,都会生成新的str对象。此外,str对象采用的编码是Unicode。下面介绍str对象的基本操作。

1.定义字符串与字符串常量

Python中不区分单引号和双引号(二者等价),也没有字符和字符串的区别,字符就是长度为1的字符串。定义字符串可以通过str构造器实现,代码如下。

empty_str = str()  # 创建空字符串,结果是 ''
str_from_int = str(12345)  # str 构造器把整数类型转换成字符串类型

也可以通过字符串常量定义字符串。普通的字符串常量有两种,一种是一对引号,另一种是两组连续的3个引号。连续3个引号用于声明跨行字符串,两组引号之间的所有字符都被包含到字符串中,包含空格、换行符等,代码如下。

str1 = 'hello world'  # 声明字符串
# 声明跨行字符串
str2 = '''Hi,
How are you?

Your friend.
'''

另外,Python还支持原始字符串、Unicode字符串和格式化字符串3种前缀字符串常量。前缀是指在字符串常量的第一个引号前加一个前缀,用于说明这个字符串的类型,代码如下。

str1 = r'\n'  # 这个字符串得到的内容是两个字符反斜线和n,如果是普通字符串则得到一个换行符
str2 = f'str1 = {str1}'  # 格式化字符串会把{}中的内容替换成对应变量的值
str3 = u'你好' # Unicode 字符串

r前缀声明的是原始字符串,原始字符串中的转义字符均不生效;f前缀声明的是格式化字符串,会把{}中的内容替换成对应变量的值;u前缀声明的是Unicode字符串。

注意:Python 3中的Unicode字符串是没有意义的,因为Python的普通字符串也使用Unicode编码,这个功能是Python 2引入的,Python 3保留这个功能是为了与Python 2保持兼容。

2.索引和遍历

与C语言的字符串类似,在Python中可以使用索引访问字符串中任意位置的字符。但在Python中无法通过索引改变字符串中的字符。

可以通过len函数获取字符串对象的长度,然后在该长度范围内访问或者遍历字符串,如果访问的位置超过字符串长度会引发IndexError。另外,可以使用for in关键字按顺序遍历字符串。

str1 = 'abcdABCD'# 定义字符串

# 通过 for 循环,使用下标遍历字符串
for i in range(len(str1)):
      print(i, str1[i], ord(str1[i]))

# 通过 while 循环遍历字符串
i = 0
while i < len(str1):
      print(i, str1[i], ord(str1[i]))
      i += 1

# 通过 for each 遍历字符串
for ch in str1:
      print(ch, ord(ch))

# 通过 for each 遍历字符串,并同时获得下标
for i, ch in enumerate(str1):
      print(i, ch, ord(ch))

注意:上面代码中最后一个for循环中使用的enumerate函数用于把可迭代对象转换成索引和元素的组合。

上面代码中带索引的输出结果如下。

0 a 97
1 b 98
2 c 99
3 d 100
4 A 65
5 B 66
6 C 67
7 D 68

不带索引的输出结果如下。

a 97
b 98
c 99
d 100
A 65
B 66
C 67
D 68

3.字符和字符编码值的相互转换

Python提供了ord函数和chr函数,用于获取字符的编码值和获取编码值对应的字符,代码如下。

a = ord('你')  # a是int类型,a的值是“你”,对应的编码是 20320
b = chr(22909)  # b是字符串类型,b的值是“好”,是22909编码对应的字符

注意:ord函数的参数只能是长度为1的字符串,如果不是会引发TypeError。

4.字符串和列表的相互转换

split函数可以按照一个指定字符或子串把字符串切割成包含多个字符串的列表,然后这个指定字符或子串会被删除。

如果想把字符串转换成列表,可以使用列表构造器,传入一个字符串,直接把该字符串转换成包含所有字符的列表。把列表转换为字符串常用join方法,代码如下。

str1 = 'hello world'
words = str1.split(' ')  # 按空格切分,得到 ['hello', 'world']
chars = list(str1) # 得到 ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
words = ['hello', 'world', '! ']
str2 = ''.join(words) # 得到 'helloworld!'
str3 = ' '.join(words) # 得到 'hello world !'

5.str对象和bytes对象的相互转换

bytes对象是比特串,它和str对象很相似,二者的区别是bytes是没有编码的二进制数据,可以使用b前缀的字符串定义bytes对象,如bytes1 = b'hello'。使用str构造器转换bytes对象只能得到bytes对象的字符串描述,我们应该使用bytes对象的decode方法,指定一种编码把bytes对象转换成str对象。str对象则通过encode方法指定一种编码转换成bytes对象。

6.str对象的常用方法

str对象的常用方法如下。

find:查找指定字符/字符串在一个字符串中的出现位置或是否出现,若出现则返回第一次出现的下标,若没有出现则返回−1。

rfind:返回倒数第一个指定字符串出现的位置,如果没有则返回−1。

count:查找一个字符串中指定子串或字符出现的次数,返回出现的次数。

startswith:确定一个字符串是否以某子串或字符开头,返回值是布尔值。

endswith:确定一个字符串是否以某子串或字符结尾,返回值是布尔值。

isdigit:判断字符串是否为一个数字。

isalpha:判断字符串是否为一个字母。

isupper:判断字符串是否为大写。

lstrip:删除开头的指定字符。

rstrip:删除结尾的指定字符。

strip:可以删除字符串首尾的指定字符,如果不传入任何参数则默认删除空格。相当于同时使用lstrip方法和rstrip方法。

replace:用于字符串内容的替换。replace方法和strip方法都会构造新的字符串,而不是修改原字符串。因为Python中的str对象是不可变对象。

center:可以指定一个宽度,并把字符串内容居中。

center方法的使用方法如下。

import json
s = 'hello'
print(json.dumps(s.center(10)))

输出结果如下。

"  hello   "

因为Python中的str对象是不可变对象,所以如果需要频繁改变一个字符串的内容,使用str对象效率不高。有两种改变字符串内容的方法:一是使用列表存储每个字符,然后通过列表和str对象相互转换的方法实现;二是使用StringIO类。

StringIO类会创建一个内存缓冲区,可以通过write方法向缓冲区内写入字符串,使用getvalue方法获取缓冲区内的字符串,代码如下。

import io
sio = io.StringIO()
sio.write('hello')
sio.write(' ')
sio.write('world')
print(sio.getvalue())  # 输出 hello world
sio.close()

本节介绍如何用Python处理语料,包括把语料载入内存、针对不同格式的语料进行处理以及进一步地进行分词、词频统计等操作。

1.文本文件

Python 3读取文本文件一般需要指定编码,多数语料文件会采用UTF-8编码,提供语料的页面往往会有关于编码的说明。如果语料文件是按行分割的,比如每行是一段独立的话,可以使用文件对象的readlines方法一次性读取这个文件所有的行,得到一个列表,其中的每个元素是文件中的一行。

有的语料每行又根据指定分隔符分成多个字段,比如对一个英汉词典文件按行分割,每行是一个英语单词及其中文解释,单词和中文解释又通过空格分隔。可以用for in按行遍历文件对象,然后对每行使用split方法按空格切分。如果这个词典文件的文件名是dictionary.txt,文件编码是UTF-8,内容如下。

hello 喂,你好

world 世界

language 语言

computer 计算机

可以使用如下代码把文件内容读取到内存。

f = open('dictionary.txt', encoding='utf8')  # 使用UTF-8编码打开文件
words = []  # 定义空的list用于存放所有词语
for l in f:  # 按行遍历文件
      word = l.strip().split(' ')  # 先去除行尾换行符,然后把单词和中文切分开
      words.append(word)   # 把单词和中文意思加入list
f.close()  # 关闭文件

最后得到的words变量是一个嵌套的列表,内容如下。

[
      ['hello', '喂,你好'],
      ['world', '世界' ],
      ['language', '语言'],
      ['computer', '计算机' ] 
]

这里列表中的每个元素是长度为2的列表,其中第一个元素是英语单词,第二个元素是对应的中文意思。

2.CSV文件

逗号分隔值文件(Comma Separated Values,CSV)按行分割,行又按逗号分列(或者说字段)。CSV文件可以使用电子表格软件打开(如Microsoft Excel或WPS),也可以直接作为文本文件打开。类似的还有TSV文件,TSV文件的分隔符是tab,即'\t'。

在Python中可使用CSV包读写CSV文件,代码示例如下。

import csv
f = open('file.csv, encoding='utf8')  # 使用UTF-8编码打开文件
reader = csv.reader(f)
lines = []  # 定义空的list用于存放每行的内容
for l in reader:  # 按行遍历CSV文件
      lines.append(l)

3.JSON文件

JSON(JavaScript Object Notation)是一种基于JavaScript语言的数据结构的数据表示方法,可以把多种数据结构转换成字符串。Python提供内置的json包用于处理JSON格式的数据。

JSON格式的文件又分为json和jl两种,一般JSON文件的整个文件是一个JSON对象,可以使用read方法读取所有内容,再通过json.loads函数转换成对象,或者直接通过json.load从文件解析对象。jl则是按行分割的文件格式,每行是一个JSON对象,可以先按行读取文件,对每行的内容使用json.loads解析。

在Python中可以使用内置的集合(set)数据结构执行去重操作。集合的添加(add)方法是把一个对象加入集合,然后使用关键字in确定一个对象是否在集合中。或者可以用集合构造器把列表转换为集合中的对象,因为集合中的对象不允许有重复元素,所以重复对象会自动去掉。集合中插入一个元素和判断一个元素是否存在的平均时间复杂度都是常数级别,但使用内存较多。

更省内存的方法是把列表排序并遍历,排序后只需要检测相邻元素是否重复即可找到所有重复元素。

对于大量数据去重可以考虑使用BitMap,或者使用布隆过滤器(Bloom Filter)。

注意:布隆过滤器的结果并不一定100%准确,但可以通过使用多个哈希函数得到较高的可靠性。

停用词即stop words,是规定的一个语料中频繁使用的词语或不包含明确信息的词语,如中文中的“的”“一些”或者英语中的“the”“a”“an”等。中文的停用词表可以参考GitHub的代码仓库:https://github.com/goto456/stopwords。可以使用Python的集合数据结构加载停用词表,然后高效地去除语料中的停用词。

编辑距离是衡量两个字符串间差异的一种度量。编辑距离定义了3种基本操作:插入一个字符、删除一个字符、替换一个字符。两个字符串间的编辑距离就是把一个字符串变成另一个字符串所需的最少基本操作的步数。

编辑距离可以使用动态规划算法计算,代码如下。

def minDistance(word1: str, word2: str) -> int:
     n = len(word1)  # 字符串1的长度
     m = len(word2)  # 字符串2的长度
     dp = [[0] * (m+1) for _ in range(n+1)]  # 定义dp数组
     for i in range(m+1): dp[0][i] = I  # 初始化dp数组
     for i in range(n+1): dp[i][0] = i
     for i in range(1, n+1):
     for j in range(1, m+1):
        if word1[i-1] == word2[j-1]:
           dp[i][j] = dp[i-1][j-1]
        else:
           dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) + 1
     return dp[-1][-1]

注意:这段代码的第一行使用了变量类型标注方法,即标注函数的两个参数都是str类型,函数返回值是int类型,但这个不是强制的,仅起到标注作用。该语法由Python 3.6引入。

文本规范化即Text Normalization,指按照某种方法对语料进行转换、清洗和标准化。例如去掉语料中多余的白空格和停用词,统一英文语料单词单复数、过去式等形式,去掉或替换带有重音符号的字母。下面是BERT-KPE中的英文文本规范化代码[5]

[5] 该段代码来自开源项目BERT-KPE。

import unicodedata  # Python 内置模块

class DEL_ASCII(object):
   ''' 在方法 `refactor_text_vdom` 中被使用,用于过滤掉字符: b'\xef\xb8\x8f' '''
   def do(self, text):
     orig_tokens = self.whitespace_tokenize(text)
     split_tokens = []
     for token in orig_tokens:
        token = self._run_strip_accents(token)                
        split_tokens.extend(self._run_split_on_punc(token))
     output_tokens = self.whitespace_tokenize(" ".join(split_tokens))
     return output_tokens

   def whitespace_tokenize(self, text):
     """清理白空格并按单词切分句子"""
     text = text.strip()  # 去掉首尾空格、换行符、分隔符等白空格字符
     if not text: # 可能本来就是空串或者只包含白空格
        return []
     tokens = text.split()  # 按白空格切分
     return tokens

   def _run_strip_accents(self, text):
     """去掉重音符号"""
     text = unicodedata.normalize("NFD", text)
     output = []
     for char in text:
        cat = unicodedata.category(char)  # 获取字符的类别
        if cat == "Mn": # 意思是Mark, Nonspacing
            continue
        output.append(char)
     return "".join(output)

   def _run_split_on_punc(self, text):
     """切分标点符号"""
     chars = list(text)  # 转换成每个元素都是单个字符的列表
     i = 0
     start_new_word = True  # 单词的边界,新单词的开始
     output = []
     while i < len(chars):
        char = chars[i]
        if self._is_punctuation(char):  # 如果非数字、字母、空格
            output.append([char]) # 标点
            start_new_word = True
        else:
            if start_new_word:
                output.append([])
            start_new_word = False
            output[-1].append(char)
        i += 1
     return ["".join(x) for x in output]

   def _is_punctuation(self, char):
     """检查一个字符是不是标点符号"""
     cp = ord(char)
     # 把所有非字母、非数字、非空格的ASCII字符看成标点
     # 虽然如 "^" "$" 和 "`" 等字符不在Unicode的标点符号分类中
     if ((cp >= 33 and cp <= 47) or (cp >= 58 and cp <= 64) or
           (cp >= 91 and cp <= 96) or (cp >= 123 and cp <= 126)):
        return True
     cat = unicodedata.category(char)
     if cat.startswith("P"):
        return True
     return False

例如句子“'   Today, I   submitted my résumé.   '”首尾有空格,中间单词“I”和单词“submitted”之间有多个连续空格,还有单词“résumé”包含带有重音符号的字母。

del_ascii = DEL_ASCII()
print(del_ascii.do('     Today, I     submitted my résumé.     '))

代码运行的结果如下。

['Today', ',', 'I', 'submitted', 'my', 'resume', '.']

字母é和e的编码是不同的,可以通过ord函数查看其编码。

print(ord('é'), ord('e'))

输出结果如下。

233 101

可以使用chr函数输出附近的字符。

for i in range(192, 250):
   print(i, chr(i), end='  ')

输出结果如下。

192 À  193 Á  194 Â  195 Ã  196 Ä  197 Å  198 Æ  199 Ç  200 È  201 É  202 Ê  203 Ë
204 Ì  205 Í  206 Î  207 Ï  208 Ð  209 Ñ  210 Ò  211 Ó  212 Ô  213 Õ  214 Ö  215 × 
216 Ø  217 Ù  218 Ú  219 Û  220 Ü  221 Ý  222 Þ  223 ß  224 à  225 á  226 â  227 ã  228 
ä  229 å  230 æ  231 ç  232 è  233 é  234 ê  235 ë  236 ì  237 í  238 î  239 ï  240 ð
241 ñ  242 ò  243 ó  244 ô  245 õ  246 ö  247 ÷  248 ø  249 ù

分词就是把句子切分为词语。在英语中分词可直接按照空格切分,因为英语句子已经使用空格把不同单词隔开了。但中文中分词是比较困难的,正如第1章我们提到的,对于同样的句子,有时候不同的切分方法会呈现不同的意思,有时候不同的切分方法都有一定合理性,有时候人类对其理解时会产生分歧。常用的中文分词方法有基于字符串匹配的分词方法和基于统计的分词方法等。

1.基于字符串匹配的分词方法

这种方法又叫机械分词方法。首先需要定义一个词表,表中包含当前语料中的全部词语。然后按照一定规则扫描待分词的文本,匹配到表中的词语就把它切分开来。扫描规则可分为3种:正向最大匹配,即从开头向结尾扫描;逆向最大匹配,即从结尾向开头扫描;最少切分,即尝试使每句话切分出最少的词语。

2.基于统计的分词方法

在一大段语料中统计字与字或者词与词的上下文关系,统计字或者词共同出现的次数。然后对于要切分的文本,可以按照这个已经统计到的出现次数,选择概率尽可能大的切分方法。下面的代码是使用1998年1月《人民日报》语料(这是一个已经分好词并标注了词性的语料,这里只用了分词的结果而忽略了词性)统计两个词共同出现的概率。

class TextSpliter(object):
   def __init__(self, corpus_path, encoding='utf8', max_load_word_length=4):
     self.dict = {}
     self.dict2 = {}
     self.max_word_length = 1
     begin_time = time.time()
     print('start load corpus from %s' % corpus_path)
     # 加载语料
     with open(corpus_path, 'r', encoding=encoding) as f:
       for l in f:
          l.replace('[', '')
          l.replace(']', '')
          wds = l.strip().split('  ')
          last_wd = ''
          for i in range(1, len(wds)): # 下标从1开始,因为每行第一个词是标签
             try:
                 wd, wtype = wds[i].split('/')
             except:
                 continue
             if len(wd) == 0 or len(wd) > max_load_word_length or not wd.isalpha():
                 continue
             if wd not in self.dict:
                 self.dict[wd] = 0
                 if len(wd) > self.max_word_length:
                     # 更新最大词长度
                     self.max_word_length = len(wd)
                     print('max_word_length=%d, word is %s' %(self.max_word_length, wd))
             self.dict[wd] += 1
             if last_wd:
                 if last_wd+':'+wd not in self.dict2:
                     self.dict2[last_wd+':'+wd] = 0
                 self.dict2[last_wd+':'+wd] += 1
             last_wd = wd
     self.words_cnt = 0
     max_c = 0
     for wd in self.dict:
        self.words_cnt += self.dict[wd]
        if self.dict[wd] > max_c:
            max_c = self.dict[wd]
     self.words2_cnt = sum(self.dict2.values())
     print('load corpus finished, %d words in dict and frequency is %d, %d words in
dict2 frequency is %d' % (len(self.dict),len(self.dict2), self.words_cnt, self.words2_
cnt), 'msg')
     print('%f seconds elapsed' % (time.time()-begin_time), 'msg')

上述代码完成了统计词语共同出现的频率,对于待分词文本的处理则需要计算可能的各种分词方式的概率,然后选择一种概率最大的分词方式得出分词结果。第6章有该方法的全部代码。

TF-IDF在第1章简要介绍过,该算法可以用于寻找一篇文档中重要的词语。scikit-learn中提供了计算TF-IDF的类TfidfVectorizer。

使用神经网络模型时一般需要使用向量表示自然语言中的符号,也就是词或字,最简单的表示方法是One-Hot编码。One-Hot编码是先遍历语料,找出所有的字或词,例如有10个词,对其进行编号(从1到10,每个数字代表一个词语),转换成向量则每个词都是10维向量,每个向量只有1位为1,其余位为0。第一个词编号是1,向量是[1,0,0,0,0,0,0,0,0,0];第二个词编号是2,向量是[0,1,0,0,0,0,0,0,0,0];最后一个词编号是10,向量是[0,0,0,0,0,0,0,0,0,1]。

第8章将详细地介绍One-Hot编码和一些其他的编码方法。

Python是一种跨平台的高级语言,主要的优点是语法简洁、抽象程度高,内置的第三方库种类丰富,容易调用其他语言编写的程序。

Python是动态的解释型语言,变量无须声明,且Python代码会先被解释器实时翻译成一种字节码,然后执行。这意味着Python代码执行之前可能不会被整体进行编译,所以和一些编译型语言的编译器相比,Python解释器对错误的检查能力稍弱。

解释器只对全部代码执行语法的检查,很多错误可能需要执行到具体的地方才会被发现。包含了未声明变量val的代码示例如下。

import time
c = 0
for i in range(10):
      if i < 5:
          c += i
          time.sleep(0.5)  # 阻塞 0.5 秒
      else:
          c += val  # val 变量没有定义,这里会报错

注意:上面提到Python代码执行之前可能不会整体编译,实际上Python解释器可能会对Python文件或模块进行预编译,提前生成字节码。但基于Python动态语言的特性,如上面的val错误还是无法被提前发现。

这段代码使用time.sleep函数模拟一个耗时的操作,实际上刚运行时,没有任何报错,虽然很明显val是未声明的。执行大概2.5秒后解释器才提醒:“NameError: name 'val' is not defined”。

与之相比,同样功能的C语言程序可能如下。

#include <windows.h> 
int main() {
   int c;
   for(int i=0;i<10;i++ ) {
      if (i < 5) {
              c += 1;
              Sleep(500);  // 阻塞 500 毫秒,即0.5秒
      }
      else {
      c += val;  // val 变量没有声明,编译器能在编译时指出错误
      }
   }
   return 0;
}

该C语言程序编译无法通过。编译器直接提示:“[Error] 'val' was not declared in this scope”。

这是因为Python解释器不执行到包含val的语句,就无法判定val到底是否存在,所以不执行到这一句,解释器就不会给出提示。

Python本身是跨平台的,解释器已经屏蔽了操作系统和硬件的差异。但实际上有一些库并不一定能跨平台。

许多库可能依赖某些操作系统的特殊调用,或者就是专门为某些操作系统设计的,因此无法兼容不同的操作系统。

事实上很多包在不同平台中也是不同的,如果一些包使用了其他语言编写库,可能需要通过在不同平台分别编译来实现跨平台。甚至具体的代码需要根据不同的平台做出适应,即这些包的代码需要手动调整以适应不同平台。

Python的性能问题受到很多诟病。Python确实不是一种追求高性能的语言,应该避免在Python中直接使用大量的迭代操作,否则性能与其他语言(如C++、Go语言等)会有较大差距。

机器学习会涉及大量计算任务,而实际上Numpy和PyTorch这样的库虽然使用Python语言,但在底层调用了C语言或者CUDA等语言编写的模块来执行实际的运算,Python实现的部分不会涉及大量的计算。

另外,Python难以进行精确的内存管理,尤其在使用列表、map等内置数据结构时,虽然方便,但对内存的使用并不一定高效,且难以人工干预,但这一般不会造成很大的问题。

并行指计算机在同一时刻执行多个不同任务。并发则指计算机可以快速地处理同时出现的任务,但并不一定在同一时刻处理完成,而可能是在某一时刻只处理一个任务,但在极短的时间内快速地在多个任务间切换。

并行用于处理计算密集型任务,如计算复杂的问题(训练机器学习的模型)。这种情况下中央处理器(Central Processing Unit, CPU)的负载很高,使用CPU或图形处理器(Graphics Processing Unit, GPU)多个核同时运行可以显著减少任务时间,即把一个大任务分成多个小任务,多个进程同时分别执行小任务。处理计算密集型任务可使用多线程或多进程。

注意:在Python中不能使用多线程处理计算密集型任务。

并发则对应IO密集型任务,如同时读写多个文件或同时处理大量网络请求。这种情况下CPU负载不一定很高,但涉及很多任务,不一定需要使用多个核。

对于Python来说很重要的一个问题是其多线程无法并行,即不能处理计算密集型任务。因为Python解释器(指CPython解释器)中有一个解释器全局锁(Global Interpreter Lock,GIL),保证一个进程中的所有线程同一时刻只有一个能占用CPU,所以Python即使使用多线程,最多也只能同时占用一个CPU核。

注意:CPython是官方的Python实现,CPython由于有GIL,所以其多线程不适合做计算密集型任务。

除了多线程和多进程,Python 3.7以后的版本开始原生支持异步编程,相比多线程,异步编程可以更高效地处理IO密集型任务。

作为一种抽象程度很高的语言,Python有很多限制,如:str是不可变对象,其中的内容无法修改,即使只修改一个字符也得重新生成一个str对象;GIL导致多线程无法用于计算密集型任务。

但是如果在Python中调用其他语言就可以灵活地处理这些问题,并且很多涉及较多计算的操作用Python代码完成比较耗时,而换成功能完全相同的C语言代码再通过Python调用则可以大大提高速度。

ctypes是Python的外部函数库,可通过pip install ctypes命令安装。它提供了与 C语言兼容的数据类型,并允许调用动态连接库(Dynamic Linked Library, DLL)或共享库(.so文件)中的函数。Python可以通过该模块调用其他语言生成的DLL或共享库文件。

定义一个字节串,如果希望修改其中的单个字节,只能重新构造整个字节串。如下代码定义了一个字节串,但第二个字节(下标为1)输入错了。

b = b'hallo world!'
print(id(b))

这里查看它的ID,输出是2310061127936。我们希望把其中的“hallo”改成“hello”。但是由于bytes对象和str对象一样,是不可改变的对象,如下代码会导致解释器提示错误。

b[1] = b'e'[0]

错误提示为“TypeError: 'bytes' object does not support item assignment”。

但假如我们使用如下C语言程序。

with open('bytes_modify.c', 'w') as f:
   # 连续的3个单引号定义跨行字符串
   f.write('''
   void modify_str(char * s, int i, char ch);
   void modify_str(char * s, int i, char ch) {
     s[i] = ch;
   }
   ''')

注意:这段代码是用Python写的,write函数中的字符串是一段C语言代码。这里通过这段Python代码把C语言代码写入文件。

使用如下命令编译。

gcc -shared -Wl,-soname,adder -o bytes_modify.dll -fPIC bytes_modify.c

注意:Windows下可以使用Cygwin,它可以提供gcc命令和许多常用的Linux命令。

会得到一个.dll文件。

再使用ctypes库加载这个.dll文件。

import ctypes
bytes_modify = ctypes.cdll.LoadLibrary('.\\bytes_modify.dll')

可以通过该.dll文件尝试修改这个bytes对象。

bytes_modify.modify_str(b, 1, ord('e'))
print(b, id(b))

输出如下。

(b'hello world!', 2310061127936)

bytes对象真的被改变了,而且ID没有变化。

实际上把bytes对象换成str对象无法得到这个结果,str对象并不会被改变。这种方法并不推荐,因为这不是一个正常的途径,官方文档中没有对这个情况的说明,关于该问题和一些其他有趣问题的探索可参考笔者博客[6]上关于Python有趣问题的一些分享。

[6] https://es2q.com/blog/tags/py-fun/。

Python官方文档中指出,调用的函数中不应通过指针改变原对象内存中的数据,若需要可变的内存对象,应该通过create_string_buffer函数获取。ctypes、C语言以及Python的类型对照如表2.4所示。

结构体和指针等高级类型可使用其他的方法获取。

表2.4 ctytes、C语言、Ptyon类型对照

ctypes类型

C语言类型

Python类型

c_bool

_Bool

bool(1)

c_char

char

单字符bytes对象

c_wchar

wchar_t

单字符字符串

c_byte

char

int

c_ubyte

unsigned char

int

c_short

short

int

c_ushort

unsigned short

int

c_int

int

int

c_uint

unsigned int

int

c_long

long

int

c_ulong

unsigned long

int

c_longlong

__int64或long long

int

c_ulonglong

unsigned __int64或unsigned long long

int

c_size_t

size_t

int

c_ssize_t

ssize_t或Py_ssize_t

int

c_float

float

float

c_double

double

float

c_longdouble

long double

float

c_char_p

char*(以NUL结尾)

字节串对象或None

c_wchar_p

wchar_t*(以NUL结尾)

字符串或None

c_void_p

void*

int或None

通过网络接口调用其他语言可选择的方法有套接字(socket)或者应用层协议,如HTTP等。这类方法通用性较高,且可以实现不同主机间程序的相互调用。

更复杂的情况可使用如Google公司的Protobuf、Microsoft公司的Bond或者FaceBook(现更名为Meta)的Thrift,它们都实现了类似功能,即不仅提供了跨语言、跨主机的程序间调用,还实现了高效的数据传输协议。

在一台计算机内,通过本地回环地址127.0.0.1进行网络通信,可以达到很高的传输效率,相当于在内存中复制数据。

本章介绍了Python自然语言处理环境的搭建、集成开发环境的选择、对语料处理的基本操作,这些是自然语言处理实践的基础。在实际应用中,我们使用的输入数据往往是原始语料,需要进行读取到内存、分词、规范化到编码等处理,才能使用自然语言处理的工具处理它们。


相关图书

Rust游戏开发实战
Rust游戏开发实战
JUnit实战(第3版)
JUnit实战(第3版)
Kafka实战
Kafka实战
Rust实战
Rust实战
PyQt编程快速上手
PyQt编程快速上手
Elasticsearch数据搜索与分析实战
Elasticsearch数据搜索与分析实战

相关文章

相关课程