先决条件
如果我们想要构建带有自然语言处理的应用程序,那么上下文的变化使其变得最为困难。上下文因素影响了机器如何理解特定的句子。因此,我们需要使用机器学习的方法来开发自然语言应用程序,以便机器也能像人一样理解上下文。
为了构建此类应用程序,我们将使用Python的一个包叫做NLTK(自然语言工具包)。
导入NLTK
我们需要在使用之前安装NLTK。可以通过以下命令来安装:
pip install nltk
为了建立NLTK的conda包,请使用以下命令:
conda install -c anaconda nltk
安装完NLTK包后,我们需要通过Python命令提示符导入它。可以在Python命令提示符中输入以下命令来导入:
>>> import nltk
下载NLTK的数据
导入NLTK之后,我们需要下载所需的数据。可以在Python命令提示符中通过以下命令来完成:
>>> nltk.download()
安装其他必要包
为了使用NLTK构建自然语言处理应用程序,我们需要安装必要的包。这些包包括:
-
gensim
-
这是一个强大的语义建模库,对于许多应用都有用处。可以通过执行以下命令来安装它:
pip install gensim
-
pattern
-
它用于使gensim包正常工作。可以通过执行以下命令来安装它:
pip install pattern
分词、词干提取和词形还原的概念
在本节中,我们将了解什么是分词、词干提取和词形还原。
分词
分词可以定义为将给定文本(即字符序列)分解成更小的单元,这些单元称为标记。标记可以是单词、数字或标点符号。这也被称为词分割。下面是一个分词的简单例子:
输入:
Mango, banana, pineapple and apple all are fruits.
输出:
分词的过程可以通过定位词边界来完成。词的结束和新词的开始被称为词边界。词的书写系统和排版结构会影响这些边界。
分词、词干提取和词形还原
分词
分词的过程可以通过定位词边界来完成。词的结束和新词的开始被称为词边界。词的书写系统和排版结构会影响这些边界。
在Python的NLTK模块中,我们有不同的与分词相关的包,我们可以使用它们根据需求来将文本分成标记。一些包如下:
-
sent_tokenize
包
-
正如其名,这个包将会把输入文本分成句子。我们可以使用以下Python代码导入这个包:
from nltk.tokenize import sent_tokenize
-
word_tokenize
包
-
这个包将输入文本分成单词。我们可以使用以下Python代码导入这个包:
from nltk.tokenize import word_tokenize
-
WordPunctTokenizer
包
-
这个包将输入文本分成单词以及标点符号。我们可以使用以下Python代码导入这个包:
from nltk.tokenize import WordPunctTokenizer
词干提取
当我们处理单词时,由于语法原因我们会遇到很多变体。这里的变体概念意味着我们必须处理同一单词的不同形式,比如democracy, democratic 和 democratization。对于机器来说,理解这些不同的单词具有相同的基形式是非常必要的。因此,在分析文本时,提取单词的基形式是有用的。
我们可以通过词干提取来实现这一点。这样,可以说词干提取是通过削去单词的末尾来提取单词基形式的启发式过程。
在Python的NLTK模块中,我们有与词干提取相关的不同包。这些包可以用来获取单词的基形式。这些包使用算法。一些包如下:
-
PorterStemmer
包
-
这个Python包使用Porter算法来提取基形式。我们可以使用以下Python代码导入这个包:
from nltk.stem.porter import PorterStemmer
-
例如,如果我们给这个词茎化器输入单词“writing”,那么经过词干提取后,我们将得到单词“write”。
-
LancasterStemmer
包
-
这个Python包将使用Lancaster算法来提取基形式。我们可以使用以下Python代码导入这个包:
from nltk.stem.lancaster import LancasterStemmer
-
例如,如果我们给这个词茎化器输入单词“writing”,那么经过词干提取后,我们将得到单词“write”。
-
SnowballStemmer
包
-
这个Python包将使用Snowball算法来提取基形式。我们可以使用以下Python代码导入这个包:
from nltk.stem.snowball import SnowballStemmer
-
例如,如果我们给这个词茎化器输入单词“writing”,那么经过词干提取后,我们将得到单词“write”。
所有的这些算法都有不同程度的严格性。如果比较这三个词茎化器,Porter词茎化器是最宽松的,而Lancaster是最严格的。Snowball词茎化器在速度和严格性方面都是不错的选择。
词形还原
我们也可以通过词形还原来提取单词的基形式。基本上,它是利用词汇和词形变化分析来完成这项任务,通常只去除屈折变化的结尾。这种单词的基形式被称为词元(lemma)。
词干提取和词形还原的主要区别在于是否使用词汇和词形分析。另一个区别是,词干提取通常会将派生相关的单词合并,而词形还原通常只会合并词元的不同屈折形式。例如,如果我们提供单词“saw”作为输入词,那么词干提取可能会返回单词“s”,但词形还原会尝试返回单词“see”或“saw”,这取决于该标记是作为动词还是名词使用。
在Python的NLTK模块中,我们有以下与词形还原过程相关的包,我们可以使用它来获取单词的基形式:
-
WordNetLemmatizer
包
-
这个Python包将根据单词是作为名词还是动词来提取其基形式。我们可以使用以下Python代码导入这个包:
from nltk.stem import WordNetLemmatizer
分块:将数据分成块
这是自然语言处理中的一个重要过程。分块的主要任务是识别词性和短语,如名词短语。我们已经学习了分词的过程,即创建标记。分块基本上是对这些标记进行标注。换句话说,分块会向我们展示句子的结构。
在下面的部分中,我们将了解不同类型的分块。
分块类型
有两种类型的分块。类型如下:
-
向上分块
-
在这个分块过程中,对象、事物等趋向于更加普遍,语言变得更加抽象。有更多的机会达成一致。在这个过程中,我们放大。例如,如果我们向上分块问题“汽车是用来干什么的?”我们可能会得到答案“运输”。
-
向下分块
-
在这个分块过程中,对象、事物等趋向于更加具体,语言变得更深入。在向下分块中,会检查更深的结构。在这个过程中,我们缩小。例如,如果我们向下分块问题“具体讲一下汽车?”我们将得到关于汽车的更小的信息块。
示例
在这个示例中,我们将使用Python的NLTK模块来进行名词短语分块,这是一种找出句子中名词短语的分块类别。
在Python中实施名词短语分块,需要遵循以下步骤:
步骤1
定义分块的语法。它将包含我们需要遵循的规则。
步骤2
创建一个分块解析器。它将解析语法并给出输出。
步骤3
最后一步,输出将以树的形式呈现。
让我们导入必要的NLTK包如下:
import nltk
现在,我们需要定义句子。在这里,DT 表示限定词,VBP 表示动词,JJ 表示形容词,IN 表示介词,NN 表示名词。
sentence=[("a","DT"),("clever","JJ"),("fox","NN"),("was","VBP"),
("jumping","VBP"),("over","IN"),("the","DT"),("wall","NN")]
现在,我们需要给出语法。这里,我们将以正则表达式的格式给出语法。
grammar = "NP:{<DT>?<JJ>*<NN>}"
我们需要定义一个解析器来解析语法。
parser_chunking = nltk.RegexpParser(grammar)
解析器解析句子如下:
parser_chunking.parse(sentence)
接下来,我们需要获取输出。输出在一个名为output_chunk
的简单变量中生成。
output_chunk = parser_chunking.parse(sentence)
执行以下代码后,我们可以将输出以树的形式绘制出来。
output_chunk.draw()
词袋模型 (BoW)
词袋模型简介
词袋模型(BoW)是自然语言处理中的一个模型,主要用于从文本中提取特征,以便文本可以用于建模,并应用于机器学习算法中。
现在的问题是为什么我们需要从文本中提取特征。这是因为机器学习算法无法直接处理原始数据,它们需要数值数据才能从中抽取有意义的信息。将文本数据转换为数值数据的过程称为特征提取或特征编码。
工作原理
这是一个非常简单的从文本中提取特征的方法。假设我们有一个文本文档,并且我们想将其转换为数值数据,或者说从中提取特征,那么首先这个模型会从文档中的所有单词中提取出一个词汇表。然后,通过使用文档项矩阵来构建一个模型。这样,BoW 将文档表示为一堆单词。任何有关文档中单词顺序或结构的信息都被丢弃。
文档项矩阵的概念
BoW 算法通过使用文档项矩阵来构建模型。顾名思义,文档项矩阵是文档中出现的各种单词计数的矩阵。借助此矩阵,文本文档可以表示为各种单词的加权组合。通过设置阈值并选择更有意义的单词,我们可以构建出文档中所有单词的直方图,这些直方图可以用作特征向量。以下是一个理解文档项矩阵概念的例子:
示例
假设我们有两个句子:
考虑这两个句子,我们有以下 13 个不同的词:
我们 | 是 | 在 | 使用 | 的 | 词袋 | 模型 | 被 | 使用 | 于 | 提取 | 特征 | 用于
现在,我们需要为每个句子构建一个直方图,使用每个句子中的单词计数:
-
句子 1:[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]
-
句子 2:[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
这样我们就得到了提取的特征向量。每个特征向量都是 13 维的,因为我们有 13 个不同的词。
统计学概念
统计学概念称为词频-逆文档频率(TF-IDF)。文档中的每一个词都很重要。统计帮助我们理解每个词的重要性。
词频 (TF)
词频是衡量一个词在文档中出现的频率。它可以通过将每个词的计数除以给定文档中的总词数来获得。
逆文档频率 (IDF)
逆文档频率是衡量一个词在给定文档集中的独特性。为了计算 IDF 并制定一个独特的特征向量,我们需要降低常见词(如“的”)的权重,并增加稀有词的权重。
在 NLTK 中构建词袋模型
在这一部分,我们将定义一个字符串集合,并使用 CountVectorizer 从这些句子创建向量。
首先,导入必要的包:
from sklearn.feature_extraction.text import CountVectorizer
现在定义一组句子:
sentences = ['我们在使用词袋模型', '词袋模型用于提取特征']
初始化 CountVectorizer 对象,并转换文本:
vectorizer_count = CountVectorizer()
features_text = vectorizer_count.fit_transform(sentences).todense()
print(vectorizer_count.vocabulary_)
上述程序生成如下输出,显示了上述两个句子中有 13 个不同的词:
{'我们': 11, '是': 0, '在': 10, '的': 8, '词袋': 1, '模型': 7, '用于': 4, '提取': 2, '特征': 3}
这些都是可以用于机器学习的特征向量(文本到数值形式)。
解决问题
在这一部分,我们将解决几个相关的问题。
类别预测
在一个文档集中,不仅单词本身很重要,单词所属的类别也很重要;即特定单词属于哪个类别的文本。例如,我们想预测给定句子是否属于电子邮件、新闻、体育、计算机等类别。在下面的例子中,我们将使用 TF-IDF 来制定一个特征向量,以找到文档的类别。我们将使用来自 sklearn 的 20 新闻组数据集。
我们需要导入必要的包:
from sklearn.datasets import fetch_20newsgroups
from sklearn.naive_bayes import MultinomialNB
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.feature_extraction.text import CountVectorizer
定义类别映射。我们使用五个不同的类别:宗教、汽车、体育、电子和太空。
category_map = {'talk.religion.misc': '宗教', 'rec.autos': '汽车', 'rec.sport.hockey': '冰球', 'sci.electronics': '电子', 'sci.space': '太空'}
创建训练集:
training_data = fetch_20newsgroups(subset='train', categories=category_map.keys(), shuffle=True, random_state=5)
构建计数向量化器并提取词频计数:
vectorizer_count = CountVectorizer()
train_tc = vectorizer_count.fit_transform(training_data.data)
print("\n训练数据的维度:", train_tc.shape)
创建 TF-IDF 转换器:
tfidf = TfidfTransformer()
train_tfidf = tfidf.fit_transform(train_tc)
定义测试数据:
input_data = [
'发现号是一艘太空梭',
'印度教、基督教、锡克教都是宗教',
'我们必须安全驾驶',
'冰球是由橡胶制成的盘',
'电视、微波炉、冰箱都用电'
]
以上数据将帮助我们训练一个多分类朴素贝叶斯分类器:
classifier = MultinomialNB().fit(train_tfidf, training_data.target)
使用计数向量化器转换输入数据:
input_tc = vectorizer_count.transform(input_data)
现在,我们将使用 TFIDF 转换器转换向量化数据:
input_tfidf = tfidf.transform(input_tc)
我们将预测输出类别:
predictions = classifier.predict(input_tfidf)
输出结果如下:
for sent, category in zip(input_data, predictions):
print('\n输入数据:', sent, '\n类别:', category_map[training_data.target_names[category]])
类别预测器生成如下输出:
训练数据的维度: (2755, 39297)
输入数据: 发现号是一艘太空梭
类别: 太空
输入数据: 印度教、基督教、锡克教都是宗教
类别: 宗教
输入数据: 我们必须安全驾驶
类别: 汽车
输入数据: 冰球是由橡胶制成的盘
类别: 冰球
输入数据: 电视、微波炉、冰箱都用电
类别: 电子
性别查找
在这个问题陈述中,我们将训练一个分类器,通过提供名字来查找性别(男性或女性)。我们需要使用一个启发式方法来构建特征向量并训练分类器。我们将使用来自 scikit-learn 包的带标签数据。以下是构建性别查找器的 Python 代码:
导入必要的包:
import random
from nltk import NaiveBayesClassifier
from nltk.classify import accuracy as nltk_accuracy
from nltk.corpus import names
现在,我们需要从输入词中提取最后 N 个字母。这些字母将作为特征:
def extract_features(word, N=2):
last_n_letters = word[-N:]
return {'feature': last_n_letters.lower()}
如果主程序:
if __name__ == '__main__':
创建训练数据,使用 NLTK 中可用的带标签的名字(男性和女性):
male_list = [(name, 'male') for name in names.words('male.txt')]
female_list = [(name, 'female') for name in names.words('female.txt')]
data = (male_list + female_list)
random.seed(5)
random.shuffle(data)
现在,创建测试数据:
names_input = ['Rajesh', 'Gaurav', 'Swati', 'Shubha']
定义用于训练和测试的样本数量:
train_sample = int(0.8 * len(data))
现在,我们需要迭代不同的长度,以便可以比较准确率:
for i in range(1, 6):
print('\n最后字母的数量:', i)
features = [(extract_features(n, i), gender) for (n, gender) in data]
train_data, test_data = features[:train_sample], features[train_sample:]
classifier = NaiveBayesClassifier.train(train_data)
计算分类器的准确率:
accuracy_classifier = round(100 * nltk_accuracy(classifier, test_data), 2)
print('准确率 = ' + str(accuracy_classifier) + '%')
现在,我们可以预测输出:
for name in names_input:
print(name, '==>', classifier.classify(extract_features(name, i)))
上述程序将生成如下输出:
最后字母的数量: 1
准确率 = 74.7%
Rajesh -> female
Gaurav -> male
Swati -> female
Shubha -> female
最后字母的数量: 2
准确率 = 78.79%
Rajesh -> male
Gaurav -> male
Swati -> female
Shubha -> female
最后字母的数量: 3
准确率 = 77.22%
Rajesh -> male
Gaurav -> female
Swati -> female
Shubha -> female
最后字母的数量: 4
准确率 = 69.98%
Rajesh -> female
Gaurav -> female
Swati -> female
Shubha -> female
最后字母的数量: 5
准确率 = 64.63%
Rajesh -> female
Gaurav -> female
Swati -> female
Shubha -> female
从上面的输出可以看到,当最后字母的数量最多为两个时,准确率最高,并且随着最后字母数量的增加,准确率下降。
主题建模:识别文本数据中的模式
我们知道通常文档是按照主题进行分组的。有时我们需要识别文本中对应特定主题的模式。实现这一目标的技术被称为主题建模。换句话说,主题建模是一种技术,用于揭示给定文档集中的抽象主题或隐藏结构。
我们可以将主题建模技术应用在以下场景中:
场景应用
文本分类
借助主题建模,可以改进分类,因为它将相似的词归为一组而不是单独使用每个词作为特征。
推荐系统
借助主题建模,我们可以使用相似性度量来构建推荐系统。
主题建模算法
主题建模可以通过使用算法来实现。这些算法包括:
潜在狄利克雷分配 (LDA)
这个算法是主题建模中最流行的。它使用概率图模型来实现主题建模。在Python中使用LDA算法时,我们需要导入gensim包。
潜在语义分析 (LSA) 或潜在语义索引 (LSI)
这个算法基于线性代数。基本上,它在文档项矩阵上使用奇异值分解 (SVD) 的概念。
非负矩阵因子化 (NMF)
这也基于线性代数。
所有上述提到的主题建模算法都将主题数目作为一个参数,文档-词矩阵作为输入,并输出词-主题矩阵 (WTM) 和主题-文档矩阵 (TDM)。
算法名称 |
基础技术 |
输入 |
输出 |
潜在狄利克雷分配 (LDA) |
概率图模型 |
主题数目、文档-词矩阵 |
词-主题矩阵、主题-文档矩阵 |
潜在语义分析 (LSA) / LSI |
线性代数 (SVD) |
主题数目、文档-词矩阵 |
词-主题矩阵、主题-文档矩阵 |
非负矩阵因子化 (NMF) |
线性代数 |
主题数目、文档-词矩阵 |
词-主题矩阵、主题-文档矩阵 |