自然语言处理框架拓展

4147 字 · 636 阅读 · 2023 年 06 月 06 日

本文已更新,你可以访问 AI By Doing 以获得更好的阅读体验。
本篇文章需 特别授权许可,内容版权归作者所有,未经授权,禁止转载。

介绍

前面我们介绍了循环神经网络,并利用深度学习框架完成文本分类任务。实际上,深度学习框架在自然语言处理方面并没有计算机视觉那么擅长。这篇文章,我们重点了解和学习几个自然语言处理中的常用工具。这些工具往往会成为后续深入了解自然语言处理的利器。

知识点

  • Natural Language Toolkit
  • PyTorch Flair
  • 自然语言处理工具

工欲善其事,必先利其器。本着不重复造轮子的思想,我会教大家学习使用机器学习应用过程中的常用工具。例如 NumPy 科学计算库,Pandas 数据分析库,scikit-learn 机器学习库等。以及深度学习中使用到的 TensorFlow 和 PyTorch 框架。

如今的自然语言处理,虽然大多都会融入深度学习的方法。但常用的 TensorFlow 和 PyTorch 却更多的是偏向于计算机视觉而设计,尤其是提供了大量针对图像的预处理方法。区别于计算机视觉,自然语言处理有它的难点,其中最明显的问题就是不同的语言面临的方法可能都不太一样。所以,这篇文章我们学习几个专门为自然语言处理设计的工具,这也是对前面 NLP 实验内容的补充。

Natural Language Toolkit

Natural Language Toolkit 的简称为 NLTK,顾名思义就是自然语言处理工具箱。目前,NLTK 主要用于英文和其他拉丁语系文本处理。

例如,我们可以使用 NLTK 对英文文本进行分词处理。我们首先需要下载 NLTK 拓展包,你可以使用 nltk.download() 来选择性下载所需拓展包或者直接使用 python -m nltk.downloader all 下载全部数据拓展包。

由于访问境外网络较慢,所以这里从镜像服务器下载英文分词所需的 Punkt Tokenizer Models 拓展

# 从镜像服务器下载数据集
wget -nc "http://labfile.oss.aliyuncs.com/courses/1233/nltk_data.zip"
!unzip -o "nltk_data.zip" -d ../

本地使用时,你可以执行:

import nltk
nltk.download('punkt')  # 下载英文分词所需拓展包

接下来,使用 nltk.tokenize.word_tokenize 完成英文文本分词过程。

from nltk.tokenize import word_tokenize

text = """
[English] is a West Germanic language that was first spoken in early
medieval England and eventually became a global lingua franca.
It is named after the <Angles>, one of the Germanic tribes that
migrated to the area of Great Britain that later took their name,
as England.
"""

tokens = word_tokenize(text)
print(tokens)

如果仅需要对文本进行断句,可以使用 nltk.sent_tokenize 方法。

from nltk import sent_tokenize

sent_tokenize(text)

对于分词结果,同样可以使用 NLTK 完成文本过滤。例如去除文本中的标点符号,通过遍历分词结果,仅保留英文内容。这里使用到 Python 提供的 .isalpha 字符串处理方法。

tokens = word_tokenize(text)
# 仅保留 alphabetic
words = [word for word in tokens if word.isalpha()]
print(words)

当然,我们也可以去除英文停用词。这里使用需下载停用词拓展包,并使用 nltk.corpus.stopwords 来加载。实验中的停用词数据已经包含在一开始下载的数据包中了,本地可以使用以下代码加载:

nltk.download('stopwords')  # 安装停用词拓展包
from nltk.corpus import stopwords

stop_words = stopwords.words('english')  # 加载英文停用词
print(stop_words)

目前,该拓展包支持 Dutch, German, Italian, Portuguese, Swedish, Arabic, English, Greek, Kazakh, Romanian, Turkish Azerbaijani, Finnish, Hungarian, Nepali, Russian, Danish, French, Indonesian, Norwegian, Spanish 等语言的停用词,很遗憾并没有提供常用中文停用词。

同样,可以通过遍历的方法去掉停用词。

words_ = [w for w in words if not w in stop_words]
print(words_)

此外,NLTK 可以很方便地做词频统计,nltk.FreqDist 即可按降序返回词频字典。

from nltk import FreqDist

FreqDist(tokens)

NLTK 提供的大量拓展包可以实现更多进阶应用。例如 PorterStemmer 可以实现对句子中词干的提取。词干提取是语言形态学中的概念,词干提取的目的是去除词缀得到词根,例如 Germanic 的词干为 german。

from nltk.stem.porter import PorterStemmer

porter = PorterStemmer()
stemmed = [porter.stem(word) for word in tokens]
print(stemmed)

最后,非常推荐大家阅读和练习 NLTK 官方出版的 Analyzing Text with the Natural Language Toolkit。这里面的内容非常全面,如果经常需要处理英文文本内容,该书将对你有很大的帮助。你也可以在国内购买此书的中译版本《Python 自然语言处理 - 人民邮电出版社》。

Flair

Flair 是近年新兴起来的自然语言处理框架,其隶属于 PyTorch 生态圈。Flair 主要有以下几点特色。

首先,Flair 支持对英文文本的分词,词性标注以及命名实体识别。其中,词性标注即通过语法来标记某个词是名词、动词、形容词等,中文可以使用结巴工具,英文即可使用 NLTK 或者 Flair。而命名实体识别则指的是识别文本中具有特定意义的实体,主要包括人名、地名、机构名、专有名词等。Flair 在这几项工作中,均 取得了较好的成绩

其次,Flair 提供了支持多语言的多种词嵌入预训练模型,方便完成各类词嵌入工作。例如:Flair embeddings, BERT embeddingsELMo embeddings 等。最后,Flair 基于 Pytorch 构建了完整的框架,非常方便用于文本分类等任务。

下面,我们就来学习 Flair 的使用。Flair 中的文本标准类型是 flair.data.Sentence,对于英文文本可以通过下面的示例新建一个 Sentence。

from flair.data import Sentence

text = """
[English] is a West Germanic language that was first spoken in early
medieval England and eventually became a global lingua franca.
It is named after the <Angles>, one of the Germanic tribes that
migrated to the area of Great Britain that later took their name,
as England.
"""

sentence = Sentence(text)
sentence

Flair 会自动按照空格识别每一个 Sentence 包含的 Tokens 数量。你可以通过遍历输出这些词组。如果我们使用空格间隔分好词的中文文本,Flair 同样支持识别 Tokens 数量。

for token in sentence:
    print(token)

你可以看到,其实这些词组并不是期望的分词结果,例如出现 [English] 这样的表示。当然,在 Flair 中只需要指定 use_tokenizer=True,就会自动调用 segtok 完成英文分词。(不支持中文分词处理)

sentence = Sentence(text, use_tokenizer=True)
for token in sentence:
    print(token)

接下来,你可以对 Sentence 进行 Word Embedding 词嵌入。中文词嵌入,Flair 使用了 FastText 提供的预训练模型,该模型使用维基百科语料进行训练 论文。你可以在本地使用 flair.embeddings.WordEmbeddings('zh') 完成词向量加载。

由于 WordEmbeddings('zh') 所需文件托管在外网,国内下载速度较慢。实验通过镜像服务器下载并手动指定词向量路径。

# 从镜像服务器下载 WordEmbeddings('zh') 所需词向量
wget -nc "http://labfile.oss.aliyuncs.com/courses/1233/zh-wiki-fasttext-300d-1M.zip"
!unzip -o "zh-wiki-fasttext-300d-1M.zip"
from flair.embeddings import WordEmbeddings

# 初始化 embedding
embedding = WordEmbeddings('zh-wiki-fasttext-300d-1M')  # 自行实现时请替换为 `zh`
# 创建 sentence,中文需传入分词后用空格间隔的语句,才能被 Flair 识别出 tokens
sentence = Sentence('机器 学习 是 一个 好 工具')
# 词嵌入
embedding.embed(sentence)

for token in sentence:
    print(token)
    print(token.embedding)  # 输出词嵌入后向量

可以看到,非常方便地就输出了词嵌入后的向量。如果某个词不包含在 FastText 提供的预训练模型中,那么词向量将全部为 0。

from flair.embeddings import WordEmbeddings

sentence = Sentence('huhuhang')
embedding.embed(sentence)
for token in sentence:
    print(token)
    print(token.embedding)

FastText 提供的中文词嵌入向量长度为 300,通过大规模语料训练的词向量特征表现一般都会优于自己通过小语料训练的结果。这对于自然语言处理过程中的特征提取来说是非常方便的。

除此之外,Flair 中的 BERT Embeddings 也提供了支持中文的词嵌入模型,这是由 Google 在 2018 年提供的预训练模型。你可以通过下面的代码加载使用:

from flair.embeddings import BertEmbeddings

embedding = BertEmbeddings('bert-base-chinese')

Google BERT 预训练模型使用到的语料更多,效果更好。但由于模型更大,词嵌入向量长度为 3072,这就带来了更大的性能消耗。只有在强大算力支持情况下,才推荐使用 BertEmbeddings。

前面说过,Flair 基于 Pytorch 构建了完整的框架,非常方便用于文本分类等任务。所以,下面我们使用 Flair 来完成之前的假新闻分类任务。

首先,下载数据和停用词:

wget -nc "http://labfile.oss.aliyuncs.com/courses/1233/wsdm_mini.csv"  # 假新闻数据
wget -nc "http://labfile.oss.aliyuncs.com/courses/1176/stopwords.txt"  # 停用词词典

预处理的部分变化不大,首先合并两列文本数据。

import pandas as pd

df = pd.read_csv("wsdm_mini.csv")
df['text'] = df[['title1_zh', 'title2_zh']].apply(
    lambda x: ''.join(x), axis=1)  # 合并文本数据列
data = df.drop(df.columns[[0, 1]], axis=1)  # 删除原文本列
data.head()

Flair 提供了用于文本分类非常高阶的 API,所以这里需要把数据处理成 API 支持输入的类型。其中,标签列需要全部添加 __label__ 标记。

data['label'] = '__label__' + data['label'].astype(str)
data.head()

接下来,我们执行分词和去停用词,这个过程还是只有使用结巴中文分词完成。注意,分词之后的数据形式应该使用空格间隔,这是为了 Flair 能识别为 Sentence 类型。

import jieba
from tqdm.notebook import tqdm

def load_stopwords(file_path):
    with open(file_path, 'r') as f:
        stopwords = [line.strip('\n') for line in f.readlines()]
    return stopwords

stopwords = load_stopwords('stopwords.txt')

corpus = []
for line in tqdm(data['text']):
    words = []
    seg_list = list(jieba.cut(line))  # 分词
    for word in seg_list:
        if word in stopwords:  # 删除停用词
            continue
        words.append(word)
    corpus.append(" ".join(words))

data['text'] = corpus  # 将结果赋值到 DataFrame
data.head()

下面,我们把数据集划分为 2 部分:训练集和测试集。实际上,Flair 的文本分类 API 还支持支持一个验证集,这里为了简单就不处理了。

data.iloc[0:int(len(data)*0.8)].to_csv('train.csv',
                                       sep='\t', index=False, header=False)
data.iloc[int(len(data)*0.8):].to_csv('test.csv',
                                      sep='\t', index=False, header=False)

值得注意的是,我们需要按照 Flair API 的要求将数据集分别存储为 CSV 文件,方便后面调用。设置 index=False, header=False 去除索引列和数据列名。同时,使用 \t 用以间隔标签和特征。

下面,我们使用 flair.datasets.ClassificationCorpus 来加载处理好的语料数据。

from flair.datasets import ClassificationCorpus
from pathlib import Path

corpus = ClassificationCorpus(Path('./'), test_file='test.csv', train_file='train.csv')
corpus

例如,你可以通过 corpus.train 来预览训练集语料,同时便于检查是否正常加载。正常加载的标志是中文以空格间隔,且后面识别处理 X Tokens 的数量。

corpus.train[0]  # 加载第一条训练语料

接下来,我们开始对语料进行词嵌入操作。不过,这里需要先介绍 Flair 中的一个概念 Document Embeddings。文档嵌入实际上之前的文本分类实验中已经见过了。先前,我们简单地将一段文本中每一个词嵌入向量求和作为整段文本的特征,实际上就是和这里的文档嵌入概念相同。只不过,Flair 通过了更为丰富的文档嵌入方法。

例如,Flair 提供了 Pooling 的文档嵌入方法。实际上就是将词嵌入后的向量取平均、最大或最小值作为整段文本的嵌入向量。如下方的示例代码,Flair 还支持将不同的词嵌入方法通过列表传入文档嵌入方法中,以实现多种词嵌入方法的组合调用,十分灵活。

from flair.embeddings import WordEmbeddings, FlairEmbeddings, DocumentPoolEmbeddings

# initialize the word embeddings
glove_embedding = WordEmbeddings('glove')
flair_embedding_forward = FlairEmbeddings('news-forward')
flair_embedding_backward = FlairEmbeddings('news-backward')

# initialize the document embeddings, mode = mean
document_embeddings = DocumentPoolEmbeddings([glove_embedding,
                                              flair_embedding_backward,
                                              flair_embedding_forward], model='mean')
document_embeddings.embed(sentence)

不过,我们这里仅使用 WordEmbeddings() 一种词嵌入方法以提升速度。最终,使用 DocumentRNNEmbeddings RNN 文档嵌入方法得到文本锻炼的嵌入向量。DocumentRNNEmbeddings 实际上就是构建一个简单的 RNN 网络,输入为词嵌入向量,输出则视为文档嵌入。

from flair.embeddings import WordEmbeddings, DocumentRNNEmbeddings

word_embeddings = [WordEmbeddings('zh-wiki-fasttext-300d-1M')]  # 词嵌入
document_embeddings = DocumentRNNEmbeddings(word_embeddings, hidden_size=512, reproject_words=True,
                                            reproject_words_dimension=256)  # 文档嵌入

接下来,我们就可以使用 Flair 提供的文本分类 API 构建一个文本分类器并完成训练了。

from flair.models import TextClassifier
from flair.trainers import ModelTrainer

# 初始化分类器
classifier = TextClassifier(document_embeddings,
                            label_dictionary=corpus.make_label_dictionary(), multi_label=False)
# 训练分类器
trainer = ModelTrainer(classifier, corpus)
trainer.train('./', max_epochs=1)  # 分类器模型及日志输出在当前目录

在无 GPU 加持的情况下,上方训练过程较长,由于我们仅使用 Fasttext WordEmbeddings,假新闻数据集最终分类准确度也并不理想,可以选择终止训练继续阅读后续内容。

训练结束之后,Flair 会在当前目录下方保存最终模型 final-model.pt 和最佳模型 best-model.pt,方便后面复用。除此之外,一些训练日志文件和损失数据记录也会被保存在当前目录下方。

如果需要利用保存好的模型进行推理,可以使用 TextClassifier.load 来加载。

classifier = TextClassifier.load('./best-model.pt')  # 加载最佳模型
sentence = Sentence('千叶 湖 八岁 孩子 不想 去学 英语 跳楼 辟谣 千叶 湖 八岁 孩子 跳楼 谣言 信息')
classifier.predict(sentence)  # 模型推理
print(sentence.labels)  # 输出推理结果

最终,模型可以推理输出类别及对应概率。

小结

这篇文章,我们重点学习了自然语言处理中常用的 2 个工具:NLTK 和 Flair。除此之外,大家也可以自学像 FastTextspaCyPatternTextBlob 等 Python 第三方库。实际上,大多数自然语言处理工具对英文和其他拉丁语系支持较好,中文的支持都不太理想。当然,也有一些国内机构开源的自然语言处理工具,例如清华大学的 THULAC 和百度的 LAC 等。

本篇文章需 特别授权许可,内容版权归作者所有,未经授权,禁止转载。

系列文章