本文参考自https://github.com/fastai/course-nlp
正则表达式
在本课中,我们将学习 NLP 工具包中的一个有用工具:正则表达式。
让我们考虑两个激励性的例子:
- 电话号码问题
假设我们得到了一些包含电话号码的数据:
123-456-7890
123 456 7890
101 Howard
一些电话号码的格式不同(有连字符、无连字符)。此外,数据中还存在一些错误——101 Howard 不是电话号码!我们如何找到所有电话号码? - 创建我们自己的标记
在之前的课程中,我们使用 sklearn 或 fastai 来标记我们的文本。如果我们想自己做呢?
电话号码问题
假设我们获得了一些包含电话号码的数据:
123-456-7890
123 456 7890
(123)456-7890
101 Howard
一些电话号码的格式不同(连字符、无连字符、括号)。此外,数据中还存在一些错误——101 Howard 不是电话号码!我们如何才能找到所有电话号码?
我们将尝试不使用正则表达式,但会发现这很快会导致大量 if/else 分支语句,并且不是一种很有前途的方法:
尝试 1(不使用正则表达式)
phone1 = "123-456-7890"
phone2 = "123 456 7890"
not_phone1 = "101 Howard"
import string
string.digits
'0123456789'
def check_phone(inp):
valid_chars = string.digits + ' -()'
for char in inp:
if char not in valid_chars:
return False
return True
assert(check_phone(phone1))
assert(check_phone(phone2))
assert(not check_phone(not_phone1))
尝试 2(不使用正则表达式)
not_phone2 = "1234"
assert(not check_phone(not_phone2))
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
Cell In[7], line 1
----> 1 assert(not check_phone(not_phone2))
AssertionError:
def check_phone(inp):
nums = string.digits
valid_chars = nums + ' -()'
num_counter = 0
for char in inp:
if char not in valid_chars:
return False
if char in nums:
num_counter += 1
if num_counter==10:
return True
else:
return False
assert(check_phone(phone1))
assert(check_phone(phone2))
assert(not check_phone(not_phone1))
assert(not check_phone(not_phone2))
尝试 3(不使用正则表达式)
但我们还需要提取数字!
还有,那怎么办:
34!NA5098gn#213ee2
not_phone3 = "34 50 98 21 32"
assert(not check_phone(not_phone3))
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
Cell In[8], line 3
1 not_phone3 = "34 50 98 21 32"
----> 3 assert(not check_phone(not_phone3))
AssertionError:
not_phone4 = "(34)(50)()()982132"
assert(not check_phone(not_phone3))
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
Cell In[9], line 3
1 not_phone4 = "(34)(50)()()982132"
----> 3 assert(not check_phone(not_phone3))
AssertionError:
这变得越来越难以处理。我们需要一种不同的方法。
正则表达式简介
什么是正则表达式?
正则表达式是一种模式匹配语言。
您可以写 [0-9] 或 \d,而不是 0 1 2 3 4 5 6 7 8 9
它是领域特定语言 (DSL)。功能强大(但语言有限)。
您还了解哪些其他 DSL?
- SQL
- Markdown
- TensorFlow
匹配电话号码(正则表达式的“Hello, world!”)
[0-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]
匹配美国电话号码。
重构:\d\d\d-\d\d\d-\d\d\d\d
元字符是一个或多个具有独特含义的特殊字符,不用作搜索表达式中的文字。例如,“\d”表示任何数字。
元字符是正则表达式的特别之处。
量词
允许您指定前面的表达式应匹配多少次。
{}
是提取限定符
重构:\d{3}-\d{3}-\d{4}
不精确量词
- ? 问号 - 零个或一个
-
- 星号 - 零个或多个
-
- 加号 - 一个或多个
正则表达式可能看起来很奇怪,因为它太简洁了
学习它的最佳(唯一?)方法是通过练习。否则,您会觉得自己只是在阅读规则列表。
让我们花 15 分钟开始学习 regexone 课程。
regexone课程
第 1 课:简介和 ABC
正则表达式在从文本(例如代码、日志文件、电子表格甚至文档)中提取信息时非常有用。虽然形式语言背后有很多理论,但以下课程和示例将探索正则表达式的更实际用途,以便您尽快使用它们。
使用正则表达式时要认识到的第一件事是,一切本质上都是字符,我们正在编写模式来匹配特定的字符序列(也称为字符串)。大多数模式使用普通 ASCII,其中包括字母、数字、标点符号和键盘上的其他符号,如 %#$@!,但 unicode 字符也可用于匹配任何类型的国际文本。
下面是几行文本,请注意当您在下面的输入字段中键入时,文本如何更改以突出显示每行上的匹配字符。要继续下一课,您需要使用每节课中介绍的新语法和概念来编写与所有提供的行匹配的模式。
继续尝试编写一个与所有三行匹配的模式,它可能就像每行上的常用字母一样简单。
第 1½ 课:123s
字符包括普通字母,也包括数字。事实上,数字 0-9 也只是字符,如果你看 ASCII 表,它们会按顺序列出。
在各个课程中,你将了解正则表达式中使用的许多特殊元字符,这些元字符可用于匹配特定类型的字符。在这种情况下,字符 \d 可以代替 0 到 9 之间的任何数字。前面的斜线将其与简单的 d 字符区分开来,并表示它是一个元字符。
下面是包含数字的几行文本。尝试编写一个与下面字符串中的所有数字匹配的模式,并注意你的模式如何匹配字符串中的任何位置,而不仅仅是从第一个字符开始。我们将在后面的课程中学习如何控制它。
第 2 课:点
在某些纸牌游戏中,小丑是通配符,可以代表牌组中的任意一张牌。使用正则表达式,您经常会匹配一些您不知道其确切内容的文本,除了它们共享一个共同的模式或结构(例如电话号码或邮政编码)。
同样,还有一个通配符的概念,它由 .(点)元字符表示,可以匹配任何单个字符(字母、数字、空格、所有内容)。您可能会注意到,这实际上会覆盖句点字符的匹配,因此为了专门匹配句点,您需要相应地使用斜线 . 来转义点。
下面是几个字符不同但长度相同的字符串。尝试编写一个可以匹配前三个字符串但不能匹配最后一个字符串(要跳过)的单个模式。您可能会发现您必须转义点元字符才能匹配某些行中的句点。
第 3 课:匹配特定字符
上一课中的点元字符非常强大,但有时也过于强大。例如,如果我们要匹配电话号码,我们不想验证字母“(abc) def-ghij”是否为有效数字!
有一种使用正则表达式匹配特定字符的方法,方法是将它们定义在方括号内。例如,模式 [abc] 只会匹配单个 a、b 或 c 字母,而不会匹配其他任何字母。
下面是几行,我们只想匹配前三个字符串,而不是后三个字符串。请注意,如果我们使用点,我们无法避免匹配后三个字符串,但必须使用上面的符号具体定义要匹配哪些字母。
第 4 课:排除特定字符
在某些情况下,我们可能知道有些特定字符我们不想匹配,例如,我们可能只想匹配区号不为 650 的电话号码。
为了表示这一点,我们使用一个类似的表达式,使用方括号和 ^(帽子)排除特定字符。例如,模式[^abc]
将匹配除字母 a、b 或 c 之外的任何单个字符。
使用下面的字符串,尝试编写一个仅匹配活体动物(猪、狗,但不是沼泽)的模式。请注意,大多数此类模式也可以使用上一课中的技巧来编写,因为它们实际上是同一枚硬币的两面。通过两种选择,您可以决定在编写自己的模式时哪种模式更容易编写和理解。
第 5 课:字符范围
我们刚刚学习了如何创建匹配或排除特定字符的模式——但如果我们想要匹配一个可以位于连续字符范围内的字符怎么办?我们只能将它们全部列出吗?
幸运的是,使用方括号表示法时,可以使用短划线表示字符范围来匹配连续字符列表中的字符。例如,模式 [0-6] 仅匹配从零到六的任何单个数字字符,而不匹配其他字符。同样,[^n-p]
仅匹配除字母 n 到 p 之外的任何单个字符。
还可以在同一组括号中使用多个字符范围以及单个字符。例如,字母数字 \w 元字符等同于字符范围 [A-Za-z0-9_],通常用于匹配英文文本中的字符。
在下面的练习中,请注意所有匹配和跳过行都有模式,并使用括号表示法匹配或跳过每行中的每个字符。请注意,模式区分大小写,并且 a-z 与 A-Z 在匹配的字符方面有所不同(小写与大写)。
第 6 课:捕捉一些 zzz
注意:下面重复语法的某些部分并不是所有正则表达式实现都支持的。
到目前为止,我们已经学会了如何指定我们想要匹配的字符范围,但是我们想要匹配的字符重复次数呢?我们可以这样做的一种方法是明确说明我们想要的字符数,例如 \d\d\d 可以匹配三个数字。
更方便的方法是使用花括号表示法指定我们想要的每个字符的重复次数。例如,a{3} 将匹配 a 字符三次。某些正则表达式引擎甚至允许您为这个重复指定一个范围,例如,a{1,3} 将匹配 a 字符不超过 3 次,但不少于一次。
此量词可以与任何字符或特殊元字符一起使用,例如 w{3}(三个 w)、[wxy]{5}(五个字符,每个字符可以是 w、x 或 y)和 .{2,6}(2 到 6 个任意字符)。
在下面的几行中,最后一个只有一个 z 的字符串并不是我们所认为的俚语“wazzup?”的正确拼写。尝试使用上面的花括号符号编写一个仅匹配前两个拼写的模式。
第 7 课:Kleene 先生,Kleene 先生
正则表达式中一个强大的概念是能够匹配任意数量的字符。例如,假设您编写了一个表单,其中有一个捐赠字段,该字段接受以美元为单位的数值。一个富有的用户可能会顺便过来并想捐赠 25,000 美元,而普通用户可能想捐赠 25 美元。
表达这种模式的一种方法是使用所谓的 Kleene Star 和 Kleene Plus,它们本质上表示它后面的 0 个或更多字符或 1 个或更多字符(它总是跟在一个字符或组后面)。例如,为了匹配上面的捐赠,我们可以使用模式 \d* 来匹配任意数量的数字,但更严格的正则表达式是 \d+,它确保输入字符串至少有一个数字。
这些量词可以与任何字符或特殊元字符一起使用,例如 a+(一个或多个 a)、[abc]+(一个或多个 a、b 或 c 字符)和 .*(零个或多个任意字符)。
以下是一些可以使用星号和加号元字符进行匹配的简单字符串。
第 8 课:可选字符
正如您在上一课中看到的,Kleene 星号和加号允许我们匹配一行中的重复字符。
匹配和提取文本时非常常见的另一个量词是 ?(问号)元字符,它表示可选性。此元字符允许您匹配前面的字符或组中的任何一个或零个。例如,模式 ab?c 将匹配字符串“abc”或“ac”,因为 b 被视为可选的。
与点元字符类似,问号是一个特殊字符,您必须使用斜线 ? 对其进行转义才能匹配字符串中的普通问号字符。
在下面的字符串中,请注意单词“file”的复数如何取决于找到的文件数量。尝试编写一个使用可选性元字符的模式,以仅匹配找到一个或多个文件的行。
第 9 课:所有这些空格
处理现实世界的输入(例如日志文件甚至用户输入)时,很难不遇到空格。我们使用它来格式化信息片段,使其更易于阅读和视觉扫描,而单个空格可能会破坏最简单的正则表达式。
您将在正则表达式中使用的最常见空格形式是空格 (␣)、制表符 (\t)、换行符 (\n) 和回车符 (\r)(在 Windows 环境中很有用),这些特殊字符与它们各自的每个空格匹配。此外,空格特殊字符 \s 将匹配上述任何特定空格,在处理原始输入文本时非常有用。
在下面的字符串中,您会发现每行的内容都从行的索引处缩进了一些空格(数字是文本的一部分)。尝试编写一个模式,可以匹配在数字和内容之间包含空格字符的每一行。请注意,空格字符与任何其他字符一样,并且星号和加号等特殊元字符也可以使用。
第 10 课:开始和结束
到目前为止,我们一直在编写正则表达式来部分匹配所有文本中的部分内容。有时这并不可取,例如,我们想在日志文件中匹配单词“success”。我们当然不希望该模式匹配一行“Error: unsuccessful operation”!这就是为什么编写尽可能具体的正则表达式通常是最佳做法,以确保在与真实文本匹配时不会出现误报。
收紧模式的一种方法是使用特殊的 ^(帽子)和 $(美元符号)元字符定义一个描述行的开始和结束的模式。在上面的例子中,我们可以使用模式 ^success 仅匹配以单词“success”开头的行,但不匹配行“Error: unsuccessful operation”。如果将帽子和美元符号结合起来,则可以创建一个从开头和结尾完全匹配整行的模式。
请注意,这与括号 [^...]
内用于排除字符的帽子不同,后者在阅读正则表达式时可能会造成混淆。
尝试使用这些新的特殊字符匹配下面的每个字符串。
第 11 课:匹配组
正则表达式不仅允许我们匹配文本,还允许我们提取信息以供进一步处理。这是通过定义字符组并使用特殊括号 ( 和 ) 元字符捕获它们来实现的。一对括号内的任何子模式都将作为一个组被捕获。在实践中,这可用于从各种数据中提取电话号码或电子邮件等信息。
例如,假设您有一个命令行工具来列出云中的所有图像文件。然后,您可以使用诸如 ^(IMG\d+\.png)$
之类的模式来捕获和提取完整文件名,但如果您只想捕获不带扩展名的文件名,则可以使用仅捕获句点之前部分的模式 ^(IMG\d+)\.png$
。
继续尝试使用它来编写一个仅匹配以下 PDF 文件的文件名(不包括扩展名)的正则表达式。
第 12 课:嵌套组
处理复杂数据时,您很容易发现自己必须提取多层信息,这可能导致嵌套组。通常,捕获组的结果按其定义顺序排列(按左括号顺序排列)。
以上一课中的示例为例,捕获列表中所有图像文件的文件名。如果每个图像文件的文件名中都有一个连续的图片编号,则可以使用相同的模式提取文件名和图片编号,方法是编写一个表达式,如 ^(IMG(\d+))\.png$
(使用嵌套括号捕获数字)。
嵌套组在模式中从左到右读取,第一个捕获组是第一个括号组的内容,等等。
对于以下字符串,编写一个表达式,匹配并捕获完整日期以及日期的年份。
第 13 课:更多小组作业
正如您在前面的课程中看到的,所有量词,包括星号 *、加号 +、重复 {m,n} 和问号 ?,都可以在捕获组模式中使用。这是将量词应用于字符序列而不是单个字符本身的唯一方法。
例如,如果我知道电话号码可能包含或不包含区号,则正确的模式将测试整个数字组 (\d{3})? 的存在,而不是单个字符本身(这将是错误的)。
根据您使用的正则表达式引擎,您还可以使用非捕获组,这将允许您匹配组但不让它显示在结果中。
以下是几种不同的常见显示分辨率,请尝试捕获每个显示器的宽度和高度。
第 14 课:一切都是有条件的
正如我们之前提到的,精确总是好的,这适用于编码、交谈甚至正则表达式。例如,您不会为某人写一份购物清单来购买更多。*,因为您不知道会得到什么。相反,您会写购买更多牛奶或购买更多面包,而在正则表达式中,我们实际上可以明确定义这些条件。
具体来说,在使用组时,您可以使用 |(逻辑或,又称管道)来表示不同的可能字符集。在上面的例子中,我可以编写模式“Buy more (milk|bread|juice)”来仅匹配字符串购买更多牛奶、购买更多面包或购买更多果汁。
与普通组一样,您可以在条件中使用任何字符或元字符序列,例如,([cb]ats*|[dh]ogs?) 将匹配猫或蝙蝠,或者狗或猪。编写包含许多条件的模式可能难以阅读,因此如果它们太复杂,您应该考虑将它们分成单独的模式。
继续尝试编写一个条件模式,该模式仅匹配下面带有小毛茸茸生物的行。
第 15 课:其他特殊字符
本课将介绍一些额外的元字符以及捕获组的结果。
我们已经学习了最常见的元字符,即使用 \d 捕获数字、使用 \s 捕获空格以及使用 \w 捕获字母数字字母和数字,但正则表达式还提供了一种使用大写字母指定每个元字符的对立集的方法。例如,\D 表示任何非数字字符,\S 表示任何非空格字符,\W 表示任何非字母数字字符(如标点符号)。根据您编写正则表达式的方式,使用其中一个或另一个可能会更容易。
此外,还有一个特殊的元字符 \b,它匹配单词和非单词字符之间的边界。它在捕获整个单词时最有用(例如,通过使用模式 \w+\b)。
我们不会在这些课程中详细探讨的一个概念是反向引用,主要是因为它因实现而异。但是,许多系统允许您使用 \0(通常是完整匹配的文本)、\1(组 1)、\2(组 2)等来引用捕获的组。这很有用,例如,当您在文本编辑器中使用正则表达式进行搜索和替换以交换两个数字时,您可以搜索“(\d+)-(\d+)”并将其替换为“\2-\1”,以将第二个捕获的数字放在第一位,将第一个捕获的数字放在第二位。
下面是一些不同的字符串,尝试不同类型的元字符或我们在前面的课程中学到的任何内容,并在您准备好时继续。
第X课:超越无限!
恭喜您完成课程!我们希望这些课程能让您更多地了解正则表达式以及如何在日常使用中应用它们。
正则表达式中仍有一些主题我们尚未探索,例如贪婪表达式与非贪婪表达式、posix 表示法等等。我们将尝试在以后的课程中进一步阐述这些主题。
现在,继续练习问题,学习如何将正则表达式用于实际用途。
问题 1:匹配十进制数
乍一看,编写正则表达式来匹配数字应该很容易,对吧?
我们有 \d 特殊字符来匹配任何数字,我们需要做的就是匹配小数点,对吧?对于简单的数字,这可能是正确的,但在处理科学或财务数字时,您经常需要处理正数和负数、有效数字、指数,甚至不同的表示形式(例如用于分隔千和百万的逗号)。
以下是您可能会遇到的几种不同格式的数字。请注意,您必须使用点元字符匹配小数点本身,而不是任意字符。如果您在跳过最后一个数字时遇到困难,请注意该数字与其余数字相比如何结束行。
问题 2:匹配电话号码
根据您获得的输入类型,验证电话号码是另一项棘手的任务。州外的电话号码需要区号,国际号码需要前缀,这会增加正则表达式的复杂性,人们输入电话号码的个人偏好也会增加复杂性(例如,有些人输入破折号或空格,而其他人则不输入)。
以下是您在使用真实数据时可能会遇到的一些电话号码,编写一个与号码匹配并捕获正确区号的正则表达式。
问题 3:匹配电子邮件
处理 HTML 表单时,使用正则表达式验证表单输入通常很有用。特别是,由于规范的复杂性,电子邮件很难正确匹配,我建议使用内置语言或框架函数,而不是自己动手。但是,您可以使用我们迄今为止学到的知识构建一个非常强大的正则表达式,该表达式可以非常轻松地匹配大量常见电子邮件。
需要注意的一件事是,许多人使用一次性的加号寻址,例如“name+filter@gmail.com”,它直接到达“name@gmail.com”,但可以使用额外信息进行过滤。此外,某些域名有多个组件,例如,您可以在“hellokitty.hk.com”注册域名,并拥有一个格式为“ilove@hellokitty.hk.com”的电子邮件,因此在匹配电子邮件的域名部分时必须小心。
下面是一些常见的电子邮件,在此示例中,尝试捕获电子邮件的名称,不包括过滤器(+ 字符及之后)和域(@ 字符及之后)。
问题 4:匹配 HTML
如果您正在寻找一种强大的 HTML 解析方法,正则表达式通常不是答案,因为当今互联网上的 html 页面很脆弱——常见的错误,如缺少结束标记、标记不匹配、忘记关闭属性引号,都会破坏一个完美的正则表达式。相反,您可以使用 Beautiful Soup 或 html5lib(均为 Python)或 phpQuery(PHP)等库,它们不仅可以解析 HTML,还允许您快速轻松地转到 DOM。
话虽如此,您经常需要在编辑器中快速匹配标记和标记内容,如果您可以保证输入,正则表达式是一个很好的工具。正如您在下面的示例中看到的那样,您可能需要注意一些具有额外转义引号和嵌套标记的奇怪属性。
继续为以下示例编写正则表达式。
问题 5:匹配特定文件名
如果您经常使用 Linux 或命令行,则经常要处理文件列表。大多数文件都有文件名部分和扩展名,但在 Linux 中,隐藏文件没有文件名也很常见。
在这个简单的示例中,仅提取图像文件的文件名和扩展名类型(不包括当前正在编辑的图像的临时文件)。图像文件定义为 .jpg、.png 和 .gif。
问题 6:从行首和行尾修剪空格
有时,您会发现日志文件中的空格格式不正确,行缩进过多或不足。解决此问题的一种方法是使用编辑器的搜索替换和正则表达式来提取行的内容,而不包含多余的空格。
我们之前已经了解了如何分别使用帽子 ^ 和美元符号 $ 来匹配整行文本。当与空格 \s 结合使用时,您可以轻松跳过所有前面和后面的空格。
编写一个简单的正则表达式来捕获每行的内容,而不包含多余的空格。
问题 7:从日志文件中提取信息
在此示例中,我们将使用 Android adb 调试会话的实际输出。您的目标是使用我们迄今为止学到的任何正则表达式技术来提取堆栈跟踪的文件名、方法名称和行号(它们遵循“at package.class.methodname(filename:linenumber)”的形式)。
祝你好运!
问题 8:从 URL 解析和提取数据
在处理网络上的文件和资源时,您经常会遇到可以直接解析和处理的 URI 和 URL。大多数标准库都有类来解析和构造此类标识符,但如果您需要在日志或更大的文本语料库中匹配它们,则可以使用正则表达式轻松地从其结构化格式中提取信息。
URI 或统一资源标识符是资源的表示,通常由方案、主机、端口(可选)和资源路径组成,分别如下所示。
http://regexone.com:80/page
方案描述了要通信的协议,主机和端口描述了资源的来源,完整路径描述了资源在来源的位置。
在下面的练习中,尝试提取列出的所有资源的协议、主机和端口。
问题 X:无穷大和超越!
恭喜您解决了所有问题!要了解如何在一些常见的编程语言中使用正则表达式,请继续阅读上面标题菜单中的参考资料。否则,我们希望您能够在日常工作中开始应用正则表达式!
正则表达式的优点和缺点
正则表达式的优点是什么?
- 简洁而强大的模式匹配 DSL
- 受多种计算机语言支持,包括 SQL
正则表达式的缺点是什么?
- 脆弱
- 难以编写,正确性可能很复杂
- 难以阅读
重新审视标记化
在之前的课程中,我们使用了 tokenizer。现在,让我们学习如何自己做这件事,并更好地理解 tokenization。
如果我们需要创建自己的 token 怎么办?
import re
re_punc = re.compile("([\"\''().,;:/_?!—\-])") # add spaces around punctuation
re_apos = re.compile(r"n ' t ") # n't
re_bpos = re.compile(r" ' s ") # 's
re_mult_space = re.compile(r" *") # replace multiple spaces with just one
def simple_toks(sent):
sent = re_punc.sub(r" \1 ", sent)
sent = re_apos.sub(r" n't ", sent)
sent = re_bpos.sub(r" 's ", sent)
sent = re_mult_space.sub(' ', sent)
return sent.lower().split()
这里re_punc
匹配标点符号,re_apos
匹配n't
结构,re_bpos
匹配's
结构,re_mult_space
匹配一个或多个空格,调用re_punc
正则表达式对象的sub
方法将匹配到的标点替换为标点前后一个空格,调用re_apos
把所有匹配到的替换为n't
,调用re_bpos
的sub
方法将匹配到的替换为's
,然后用re_mult_space
的sub
方法将多个空格变成一个空格。
text = "I don't know who Kara's new friend is-- is it 'Mr. Toad'?"
' '.join(simple_toks(text))
"i do n't know who kara 's new friend is - - is it ' mr . toad ' ?"
text2 = re_punc.sub(r" \1 ", text); text2
"I don ' t know who Kara ' s new friend is - - is it ' Mr . Toad ' ? "
text3 = re_apos.sub(r" n't ", text2); text3
"I do n't know who Kara ' s new friend is - - is it ' Mr . Toad ' ? "
text4 = re_bpos.sub(r" 's ", text3); text4
"I do n't know who Kara 's new friend is - - is it ' Mr . Toad ' ? "
re_mult_space.sub(' ', text4)
"I do n't know who Kara 's new friend is - - is it ' Mr . Toad ' ? "
sentences = ['All this happened, more or less.',
'The war parts, anyway, are pretty much true.',
"One guy I knew really was shot for taking a teapot that wasn't his.",
'Another guy I knew really did threaten to have his personal enemies killed by hired gunmen after the war.',
'And so on.',
"I've changed all their names."]
tokens = list(map(simple_toks, sentences))
tokens
[['all', 'this', 'happened', ',', 'more', 'or', 'less', '.'],
['the',
'war',
'parts',
',',
'anyway',
',',
'are',
'pretty',
'much',
'true',
'.'],
['one',
'guy',
'i',
'knew',
'really',
'was',
'shot',
'for',
'taking',
'a',
'teapot',
'that',
'was',
"n't",
'his',
'.'],
['another',
'guy',
'i',
'knew',
'really',
'did',
'threaten',
'to',
'have',
'his',
'personal',
'enemies',
'killed',
'by',
'hired',
'gunmen',
'after',
'the',
'war',
'.'],
['and', 'so', 'on', '.'],
['i', "'", 've', 'changed', 'all', 'their', 'names', '.']]
一旦我们有了 token,我们就需要将它们转换为整数 ID。我们还需要知道我们的词汇表,并找到一种在单词和 ID 之间进行转换的方法。
import collections
PAD = 0; SOS = 1
def toks2ids(sentences):
voc_cnt = collections.Counter(t for sent in sentences for t in sent)
vocab = sorted(voc_cnt, key=voc_cnt.get, reverse=True)
vocab.insert(PAD, "<PAD>")
vocab.insert(SOS, "<SOS>")
w2id = {w:i for i,w in enumerate(vocab)}
ids = [[w2id[t] for t in sent] for sent in sentences]
return ids, vocab, w2id, voc_cnt
ids, vocab, w2id, voc_cnt = toks2ids(tokens)
ids
[[5, 13, 14, 3, 15, 16, 17, 2],
[6, 7, 18, 3, 19, 3, 20, 21, 22, 23, 2],
[24, 8, 4, 9, 10, 11, 25, 26, 27, 28, 29, 30, 11, 31, 12, 2],
[32, 8, 4, 9, 10, 33, 34, 35, 36, 12, 37, 38, 39, 40, 41, 42, 43, 6, 7, 2],
[44, 45, 46, 2],
[4, 47, 48, 49, 5, 50, 51, 2]]
vocab
['<PAD>',
'<SOS>',
'.',
',',
'i',
'all',
'the',
'war',
'guy',
'knew',
'really',
'was',
'his',
'this',
'happened',
'more',
'or',
'less',
'parts',
'anyway',
'are',
'pretty',
'much',
'true',
'one',
'shot',
'for',
'taking',
'a',
'teapot',
'that',
"n't",
'another',
'did',
'threaten',
'to',
'have',
'personal',
'enemies',
'killed',
'by',
'hired',
'gunmen',
'after',
'and',
'so',
'on',
"'",
've',
'changed',
'their',
'names']
上面的 vocab 变量的另一个名称是什么?
w2id
{'<PAD>': 0,
'<SOS>': 1,
'.': 2,
',': 3,
'i': 4,
'all': 5,
'the': 6,
'war': 7,
'guy': 8,
'knew': 9,
'really': 10,
'was': 11,
'his': 12,
'this': 13,
'happened': 14,
'more': 15,
'or': 16,
'less': 17,
'parts': 18,
'anyway': 19,
'are': 20,
'pretty': 21,
'much': 22,
'true': 23,
'one': 24,
'shot': 25,
'for': 26,
'taking': 27,
'a': 28,
'teapot': 29,
'that': 30,
"n't": 31,
'another': 32,
'did': 33,
'threaten': 34,
'to': 35,
'have': 36,
'personal': 37,
'enemies': 38,
'killed': 39,
'by': 40,
'hired': 41,
'gunmen': 42,
'after': 43,
'and': 44,
'so': 45,
'on': 46,
"'": 47,
've': 48,
'changed': 49,
'their': 50,
'names': 51}
正则表达式有什么用途?
- 查找/搜索
- 查找和替换
- 清理
不要忘记 Python 的字符串方法
str.find?
正则表达式 vs. 字符串方法
- 字符串方法更容易理解。
- 字符串方法更清楚地表达意图。
- 正则表达式处理更广泛的用例。
- 正则表达式可以独立于语言。
- 正则表达式可以更快地进行扩展。
那么unicode怎么样?
message = "😒🎦 🤢🍕"
re_frown = re.compile(r"😒|🤢")
re_frown.sub(r"😊", message)
'😊🎦 😊🍕'
正则表达式错误:
-
False positives(类型 I):匹配了我们不应该匹配的字符串
-
False negatives(类型 II):不匹配我们本应匹配的字符串
降低任务的错误率通常需要两种相互对立的努力:
- 尽量减少假阳性
- 尽量减少假阴性
对两者进行测试很重要!
在理想情况下,您可以尽量减少两者,但在现实中,您经常需要用其中一种来换取另一种。
总结
- 我们使用正则表达式作为元语言来查找文本块中的字符串模式
- r"" 是 Python 正则表达式的 IRL 朋友
- 我们只是进行二元分类,因此使用相同的性能指标
- 你会在正则表达式中犯很多错误😩。
- 假阳性:以为自己是对的,但其实你是错的
- 假阴性:遗漏了一些东西