4.md 20.8 KB
Newer Older
W
wizardforcel 已提交
1
# “第 4 章”:文本预处理,词干和词形还原
W
wizardforcel 已提交
2 3 4

文本数据可以从许多不同的来源收集,并采用许多不同的形式。 文本可以整洁,可读或原始且混乱,也可以采用许多不同的样式和格式。 能够对这些数据进行预处理,以便可以在将其转换为 NLP 模型之前将其转换为标准格式,这就是我们将在本章中介绍的内容。

W
wizardforcel 已提交
5
与标记化相似,词干和词根化是 NLP 预处理的其他形式。 但是,与将文档简化成单个单词的标记化不同,词干和词形还原试图将这些单词进一步缩小到其词根。 例如,几乎所有英语动词都有许多不同的变体,具体取决于时态:
W
wizardforcel 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

*他跳了起来*

*他在跳*

*他跳了*

尽管所有这些词都不同,但它们都与相同的根词相关– **跳转**。 词干和词根去除都是我们可以用来减少单词的共同词根变化的技术。

在本章中,我们将解释如何对文本数据执行预处理,并探讨词干和词根化,并展示如何在 Python 中实现这些词干。

在本章中,我们将介绍以下主题:

*   文字预处理
*   抽干
*   合法化
*   词干和词根化的用途

# 技术要求

W
wizardforcel 已提交
26
对于本章中的文本预处理,我们将主要使用内置的 Python 函数,但也将使用外部 **BeautifulSoup** 程序包。 对于词干和词根化,我们将使用 NLTK Python 软件包。 本章中的所有代码都可以在[这个页面](https://github.com/PacktPublishing/Hands-On-Natural-Language-Processing-with-PyTorch-1.x/tree/master/Chapter4)中找到。
W
wizardforcel 已提交
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

# 文本预处理

文本数据可以采用多种格式和样式。 文本可以是结构化的可读格式,也可以是更原始的非结构化格式。 我们的文本可能包含我们不希望包含在模型中的标点符号和符号,或者可能包含 HTML 和其他非文本格式。 从网上来源抓取文本时,这尤其令人担忧。 为了准备我们的文本以便可以将其输入到任何 NLP 模型中,我们必须执行预处理。 这将清除我们的数据,使其成为标准格式。 在本节中,我们将更详细地说明其中一些预处理步骤。

## 删除 HTML

从在线来源抓取文本时,您可能会发现您的文本包含 HTML 标记和其他非文本工件。 我们通常不希望在模型的 NLP 输入中包括这些,因此默认情况下应将其删除。 例如,在 HTML 中, **< b >** 标签指示其后的文本应为粗体。 但是,它不包含有关句子内容的任何文本信息,因此我们应该删除它。 幸运的是,在 Python 中,有一个名为 **BeautifulSoup** 的软件包,它使我们可以在几行中删除所有 HTML:

input_text =“ **此文本为粗体,*此文本为斜体*”**

output_text = BeautifulSoup(input_text,“ html.parser”)。get_text()

print('Input:'+ input_text)

print('Output:'+ output_text)

这将返回以下输出:

![Figure 4.1 – Removing HTML ](img/B12365_04_01.jpg)

图 4.1 –删除 HTML

上面的屏幕快照显示 HTML 已成功删除。 这在原始文本数据中可能存在 HTML 代码的任何情况下(例如在为数据抓取网页时)都可能有用。

## 将文本转换为小写

预处理文本以将所有内容转换为小写形式时,这是标准做法。 这是因为相同的任何两个单词都应被视为语义相同,而不管它们是否大写。 “ **Cat** ”,“ **cat** ”和“ **CAT** ”都是相同的词,只是大小写不同。 我们的模型通常会将这三个词视为单独的实体,因为它们并不相同。 因此,通常的做法是将所有单词都转换为小写,以使这些单词在语义和结构上都相同。 使用以下代码行可以在 Python 中轻松完成此操作:

input_text = ['Cat','cat','CAT']

output_text = [input_text 中 x 的 x.lower()]

print('Input:'+ str(input_text))

print('输出:'+ str(输出文本))

这将返回以下输出:

![Figure 4.2 – Converting input into lowercase ](img/B12365_04_02.jpg)

图 4.2 –将输入转换为小写

这表明输入已全部转换为相同的小写表示形式。 有一些示例中的大写字母实际上可以提供其他语义信息。 例如, *5 月*(月)和*可能*(意味着*可能*)在语义上有所不同, *5 月*(月)将始终是 大写。 但是,像这样的实例非常少见,将所有内容都转换为小写字母要比考虑这些少见的实例更为有效。

值得注意的是,大写可能在某些任务中很有用,例如语音标记的一部分(其中大写字母可能指示单词在句子中的作用)和命名实体识别(其中大写字母可能表明单词在句子中) 专有名词而不是非专有名词的替代; 例如*土耳其*(国家)和*火鸡*(鸟类)。

## 删除标点符号

有时,根据所构建模型的类型,我们可能希望从输入文本中删除标点符号。 这在我们要汇总字数的模型中(例如在词袋表示中)特别有用。 句子中出现句号或逗号不会添加任何有关句子语义内容的有用信息。 但是,考虑到句子中标点符号位置的更复杂的模型实际上可能会使用标点符号的位置来推断不同的含义。 一个经典的例子如下:

*大熊猫吃嫩芽和叶子*

*大熊猫吃,芽和叶子*

在这里,加上逗号会将描述熊猫的饮食习惯的句子转换为描述熊猫武装抢劫餐馆的句子! 尽管如此,为了保持一致性,能够从句子中删除标点符号仍然很重要。 我们可以使用 **re** 库在 Python 中执行此操作,以使用正则表达式匹配任何标点符号,并使用 **sub()**方法将任何匹配的标点符号替换为空字符:

input_text =“这句,包含”-£no ::标点符号?“

output_text = re.sub(r'[^ \ w \ s]','',input_text)

print('Input:'+ input_text)

print('Output:'+ output_text)

这将返回以下输出:

![Figure 4.3 – Removing punctuation from input ](img/B12365_04_03.jpg)

图 4.3 –从输入中删除标点符号

这表明标点符号已从输入句子中删除。

W
wizardforcel 已提交
100
在某些情况下,我们可能不希望直接删除标点符号。 一个很好的例子是的使用和号(**&**),在几乎每种情况下,它都与单词[**和**]互换使用。 因此,与其完全删除“&”号,不如选择直接用“ **和**”一词代替。 我们可以使用 **.replace()**函数在 Python 中轻松实现此功能:
W
wizardforcel 已提交
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 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 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397

input_text =“猫和狗”

output_text = input_text.replace(“&”,“ and”)

print('Input:'+ input_text)

print('Output:'+ output_text)

这将返回以下输出:

![Figure 4.4 – Removing and replacing punctuation ](img/B12365_04_04.jpg)

图 4.4 –删除和替换标点符号

同样值得考虑的特殊情况是标点符号对于句子的表示必不可少。 一个重要的例子是电子邮件地址。 从电子邮件地址中删除 **@** 不会使该地址更具可读性:

**name@gmail.com**

删除标点符号将返回以下内容:

namegmailcom

因此,在这种情况下,根据您的 NLP mod el 的要求和目的,最好将整个项目全部删除。

## 替换数字

同样,对于数字,我们也想标准化我们的输出。 数字可以写为数字(9、8、7)或实际单词(九,八,七)。 可能值得将所有这些转换为一个标准化的表示形式,这样就不会将 1 和 1 视为单独的实体。 我们可以使用以下方法在 Python 中执行此操作:

def to_digit(digit):

我= inflect.engine()

如果 digit.isdigit():

输出= i.number_to_words(数字)

其他:

输出=数字

返回输出

input_text = [“ 1”,“两个”,“ 3”]

output_text = [输入文本中 x 的 to_digit(x)]

print('Input:'+ str(input_text))

print('输出:'+ str(输出文本))

这将返回以下输出:

![Figure 4.5 – Replacing numbers with text ](img/B12365_04_05.jpg)

图 4.5 –用文字替换数字

这表明我们已成功将数字转换为文本。

但是,以类似于处理电子邮件地址的方式,处理电话号码可能不需要与常规电话号码相同的表示形式。 在以下示例中对此进行了说明:

input_text = [“ 0800118118”]

output_text = [输入文本中 x 的 to_digit(x)]

print('Input:'+ str(input_text))

print('输出:'+ str(输出文本))

这将返回以下输出:

![Figure 4.6 – Converting a phone number into text ](img/B12365_04_06.jpg)

图 4.6 –将电话号码转换为文本

显然,前面示例中的输入是电话号码,因此全文表示不一定适合目的。 在这种情况下,最好从输入 t ext 中删除任何长整数。

# 词干和词根去除

在语言中,**变体**是如何通过修改共同的词根来表达不同的语法类别(如时态,语气或性别)的。 这通常涉及更改单词的前缀或后缀,但也可能涉及修改整个单词的。 例如,我们可以对动词进行修改以更改其时态:

*运行->运行(添加“ s”后缀以使其呈现时态)*

*运行->然(将中间字母修改为“ a”以使其超过时态)*

但是在某些情况下,整个词会发生变化:

*待定->为(现在时)*

*待定->是(过去时)*

*将会是->将是(未来时态–模态的加法)*

名词也可能有词汇上的变化:

*猫->猫(复数)*

*猫->猫的(拥有)*

*猫->猫(复数所有格)*

所有这些词都与根词 cat 相关。 我们可以计算句子中所有单词的词根,以将整个句子简化为词根:

*“他的猫的皮毛是不同的颜色”->“他的猫的皮毛是不同的颜色”*

词干和词根去除是我们得出这些词根的过程。 **词干**是一个算法过程,其中,切掉单词的末尾以到达一个共同的词根,而词形化使用单词本身的真实词汇和结构分析来得出它们的真正词根,即 **]引理**。 在下面的部分中,我们将详细介绍这两种方法。

## 阻止

**词干**是的算法过程,通过该算法,我们将单词的末尾切掉以达到其词根或**词干**。 为此,我们可以使用不同的**词干**,每个词干都遵循特定算法,以便返回单词的词干。 用英语,最常见的词干之一是 Porter Stemmer。

**Porter Stemmer** 是具有大量逻辑规则的算法,可用于返回单词的词干。 在继续讨论该算法之前,我们将首先展示如何使用 NLTK 在 Python 中实现 Porter Stemmer。

1.  First, we create an instance of the Porter Stemmer:

    搬运工= PorterStemmer()

2.  We then simply call this instance of the stemmer on individual words and print the results. Here, we can see an example of the stems returned by the Porter Stemmer:

    word_list = [“ see”,“ saw”,“ cat”,“ cats”,“ stem”,“ stemming”,“ lemma”,“ lemmatization”,“ known”,“ knowing”,“ time”,“ timing” ,“足球”,“足球运动员”]

    对于 word_list 中的单词:

    打印(word +'->'+ porter.stem(word))

    结果为以下输出:

    ![Figure 4.7 – Returning the stems of words ](img/B12365_04_07.jpg)

    图 4.7 –返回词干

3.  We can also apply stemming to an entire sentence, first by tokenizing the sentence and then by stemming each term individually:

    def SentenceStemmer(sentence):

    tokens = word_tokenize(句子)

    stems = [porter.stem(word)表示令牌中的单词]

    返回“” .join(stems)

    SentenceStemmer(“猫和狗在奔跑”)

这将返回以下输出:

![Figure 4.8 – Applying stemming to a sentence ](img/B12365_04_08.jpg)

图 4.8 –对句子应用词干

在这里,我们可以看到如何使用 Porter Stemmer 提取不同的单词。 有些词,例如**阻止****计时**,减少到**茎****时间**的预期茎。 但是,某些单词,例如**看到了**,并没有还原为它们的逻辑词干(**看到了**)。 这说明了 Porter Stemmer 的局限性。 由于词干对单词应用了一系列逻辑规则,因此很难定义一组可以正确词干所有单词的规则。 在英语单词中,根据时态(is / was / be)完全改变单词的情况下尤其如此。 这是因为没有通用规则可应用于这些单词,以将它们全部转换为相同的词根。

我们可以更详细地研究 Porter Stemmer 所应用的一些规则,以准确了解向茎的转化是如何发生的。 尽管实际的波特算法有许多详细的步骤,但是在这里,我们将简化一些规则以便于理解:

![Figure 4.9 – Rules of the Porter Stemmer algorithm ](img/B12365_04_09.jpg)

图 4.9 – Porter Stemmer 算法的规则

虽然不必了解 Porter Stemmer 中的每个规则,但关键是要了解其局限性。 虽然已经证明了 Porter Stemmer 在整个语料库中都能很好地工作,但总有人会说它不能正确地还原成它们的真实词干。 由于 Porter Stemmer 的规则集依赖于英语单词结构的约定,因此总会有一些单词不属于常规单词结构,并且不能被这些规则正确转换。 幸运的是,可以通过使用引理来克服这些限制中的某些限制。

## 合法化

**词源化**与词干的区别在于,词干减少为**引理**而不是词干。 在处理单词的词干并将其简化为字符串时,单词的引理是其真正的词根。 因此,虽然**跑**的词干只是*跑*,但它的引理是该词的真正词根,也就是**跑**

词义化过程使用内置的预先计算的词法和关联词以及句子中词的上下文来确定给定词的正确词法。 在此示例中,我们将研究在 NLTK 中使用 **WordNet** **引词发生器**。 WordNet 是一个庞大的英语单词及其词汇关系数据库。 它包含了英语中最强大,最全面的映射之一,特别是关于单词与其引理的关系。

我们将首先创建 lemmatizer 的实例,并在一系列单词上调用它:

wordnet_lemmatizer = WordNetLemmatizer()

打印(wordnet_lemmatizer.lemmatize('horses'))

打印(wordnet_lemmatizer.lemmatize('wolves'))

打印(wordnet_lemmatizer.lemmatize('mice'))

打印(wordnet_lemmatizer.lemmatize('cacti'))

结果为以下输出:

![Figure 4.10 – Lemmatization output ](img/B12365_04_10.jpg)

图 4.10 –最小化输出

在这里,我们已经可以开始看到使用词形化胜于词干的优势。 由于 WordNet Lemmatizer 建立在英语所有单词的数据库上,因此知道**鼠标****鼠标**的复数形式。 我们将无法使用词干达到相同的根源。 尽管词形化在大多数情况下效果更好,但由于它依赖于内置的单词索引,因此无法将其推广到新单词或虚构单词:

打印(wordnet_lemmatizer.lemmatize('madeupwords'))

打印(porter.stem('madeupwords'))

结果为以下输出:

![Figure 4.11 – Lemmatization output for made-up words ](img/B12365_04_11.jpg)

图 4.11 –虚词的词法输出

在这里,我们可以看到,在这种情况下,我们的词干能够更好地推广到以前看不见的单词。 因此,如果我们要对语言不一定与*真实*英语匹配的来源进行残影化,则使用 lemmatizer 可能会是一个问题,例如人们经常会缩写语言的社交媒体网站。

如果我们将 lemmatizer 称为两个动词,则会发现这不会将它们简化为预期的常见引理:

打印(wordnet_lemmatizer.lemmatize('run'))

打印(wordnet_lemmatizer.lemmatize('ran'))

结果为以下输出:

![Figure 4.12 – Running lemmatization on verbs ](img/B12365_04_12.jpg)

图 4.12 –在动词上进行词形化

这是因为我们的 lemmatizer 依靠单词的上下文来返回 lemmas。 回想一下我们的 POS 分析,我们可以轻松地返回句子中单词的上下文并确定给定单词是名词,动词还是形容词。 现在,让我们手动指定我们的单词是动词。 我们可以看到现在可以正确返回引理:

打印(wordnet_lemmatizer.lemmatize('ran',pos ='v'))

打印(wordnet_lemmatizer.lemmatize('run',pos ='v'))

结果为以下输出:

![Figure 4.13 – Implementing POS in the function ](img/B12365_04_13.jpg)

图 4.13 –在功能中实现 POS

这意味着为了返回任何给定句子的正确词形,我们必须首先执行 POS 标记以获得句子中词的上下文,然后将其传递给词形处理器以获取每个词的词缀 在句子中。 我们首先创建一个函数,该函数将为句子中的每个单词返回 POS 标签:

句子=“猫和狗在奔跑”

def return_word_pos_tuples(句子):

返回 nltk.pos_tag(nltk.word_tokenize(句子))

return_word_pos_tuples(句子)

结果为以下输出:

![Figure 4.14 – Output of POS tagging on a sentence ](img/B12365_04_14.jpg)

图 4.14 –句子上 POS 标签的输出

请注意,这如何为句子中的每个单词返回 NLTK POS 标签。 我们的 WordNet lemmatizer 对 POS 的输入要求略有不同。 这意味着我们首先创建,该函数将 NLTK POS 标签映射到所需的 WordNet POS 标签:

def get_pos_wordnet(pos_tag):

pos_dict = {“ N”:wordnet.NOUN,

“ V”:wordnet.VERB,

“ J”:wordnet.ADJ,

“ R”:wordnet.ADV}

返回 pos_dict.get(pos_tag [0] .upper(),wordnet.NOUN)

get_pos_wordnet('VBG')

结果为以下输出:

![Figure 4.15 – Mapping NTLK POS tags to WordNet POS tags ](img/B12365_04_15.jpg)

图 4.15 –将 NTLK POS 标签映射到 WordNet POS 标签

最后,我们将这些功能组合为一个最终功能,该功能将对整个句子执行词形化:

def lemmatize_with_pos(句子):

new_sentence = []

元组= return_word_pos_tuples(句子)

用于元组中的 tup:

pos = get_pos_wordnet(tup [1])

主题= wordnet_lemmatizer.lemmatize(sup [0],pos = pos)

new_sentence.append(lemma)

返回 new_sentence

lemmatize_with_pos(句子)

结果为以下输出:

![Figure 4.16 – Output of the finalized lemmatization function ](img/B12365_04_16.jpg)

图 4.16 –最终的 lemmatization 函数的输出

在这里,我们可以看到,与词干相比,词缀通常可以更好地表示单词的真实词根,但有一些明显的例外。 当我们可能决定使用时,词干的词根化取决于当前任务的要求,其中一些我们现在将讨论。

# 词干和词根化的用途

词干和词根化都是 NLP 的一种形式,可用于从文本中提取信息。 该被称为**文本挖掘**。 文本挖掘任务有多种类别,包括文本聚类,分类,文档汇总和情感分析。 可以将词干和词条限制处理与深度学习一起使用,以解决其中的一些任务,这将在本书的后面看到。

通过使用词干和词根化进行预处理,再加上停用词的去除,我们可以更好地减少句子以了解其核心含义。 通过删除对句子的意义没有显着贡献的单词,并通过减少单词的词根或引理,我们可以在深度学习框架内有效地分析句子。 如果能够将 10 个单词的句子减少为由多个核心引理而不是相似单词的多个变体组成的五个单词,则意味着我们需要通过神经网络提供的数据要少得多。 如果我们使用词袋表示法,则由于多个词都归结为相同的引理,我们的语料库会大大缩小,而如果我们计算嵌入表示法,则对于一个词,捕获我们单词的真实表示法所需的维数会更小。 减少语料库。

## 词干和词干的差异

现在我们已经看到了残局化和词根抑制,在的问题上,仍然存在问题,在什么情况下我们应该同时使用这两种技术。 我们看到,两种技术都试图将每个单词的根都减少。 在词根提取中,可能只是目标房间的简化形式,而在词形还原中,它会还原为真正的英语单词词根。

W
wizardforcel 已提交
398
因为词义化需要在 WordNet 语料库中交叉引用目标词,并执行词性分析以确定词义的形式,所以如果必须使用大量词,这可能会花费大量的处理时间。 形容词 这与词干相反,词干使用详细但相对较快的算法来词干。 最终,与计算中的许多问题一样,这是在速度与细节之间进行权衡的问题。 在选择将这些方法中的哪一种纳入我们的深度学习流程时,要在速度和准确率之间进行权衡。 如果时间很重要,那么阻止可能是要走的路。 另一方面,如果您需要模型尽可能详细和准确,则限制词形还原可能会产生更好的模型。
W
wizardforcel 已提交
399 400 401 402 403

# 摘要

在本章中,我们通过探讨两种方法的功能,它们的用例以及如何实现它们,详细讨论了词干和词根化。 既然我们已经涵盖了深度学习和 NLP 预处理的所有基础知识,我们就可以开始从头开始训练我们自己的深度学习模型。

W
wizardforcel 已提交
404
在下一章中,我们将探讨 NLP 的基础知识,并演示如何在深度 NLP 领域中建立最广泛使用的模型:循环神经网络。