无监督学习与生成式人工智能(MEAP)(三)(1)https://developer.aliyun.com/article/1522604
7.5.2 从文本数据集中提取特征
文本数据,就像任何其他数据源一样可能会混乱和嘈杂。我们在上一节中探讨了清理它的概念和技术。现在我们已经清理了数据,准备好使用。下一步是以算法可以理解的格式表示这些数据。我们知道我们的算法只能理解数字。文本数据在其最纯粹的形式下无法被算法理解。因此,一切都需要转换为数字。
一个非常简单的技术可以是简单地对我们的单词进行独热编码,并将它们表示为矩阵。
我们在书的前几章已经介绍了独热编码
如果我们描述这些步骤,那么单词可以首先转换为小写,然后按字母顺序排序。然后可以为它们分配一个数字标签。最后,单词将被转换为二进制向量。让我们通过一个例子来理解。
例如,文本是“It is raining heavily”。我们将使用以下步骤:
- 小写单词,所以输出将会是“it is raining heavily”
- 现在我们将它们按字母顺序排列。结果是 - heavily, is, it, raining。
- 现在我们可以为每个单词分配位置值,如 heavily:0, is:1, it:2, raining:3。
- 最后,我们可以将它们转换为如下所示的二进制向量
[[0\. 0\. 1\. 0.] #it [0\. 1\. 0\. 0.] #is [0\. 0\. 0\. 1.] #raining [1\. 0\. 0\. 0.]] #heavily
尽管这种方法非常直观和简单易懂,但由于语料库和词汇的庞大规模,实际上是不可能的。
语料库指的是一系列文本。它是拉丁文的意思。它可以是一组书面文字或口头文字,用于进行语言分析。
此外,处理如此多维的大数据集将在计算上非常昂贵。因此创建的矩阵也将非常稀疏。因此,我们需要寻找其他方法来表示我们的文本数据。
有更好的替代方案可用于一热编码。这些技术侧重于单词的频率或单词的使用上下文。这种科学方法的文本表示更准确、更健壮、更具解释性。它也产生更好的结果。有多种这样的技术,如 tf-idf、词袋模型等。我们将在接下来的章节中讨论其中一些技术。但我们首先会考察标记化的一个重要概念!
Tokenization(标记化)
Tokenization(标记化)就是简单地将文本或一组文本分解成单个标记。它是自然语言处理的基本构件。看一下图 7-3 中的示例,我们为句子中的每个单词创建了单独的标记。标记化是一个重要的步骤,因为它允许我们为每个单词分配唯一的标识符或标记。一旦我们为每个单词分配了特定的标记,分析就变得不那么复杂了。
图 7-3 标记化可以将句子分解为不同的单词标记。
标记通常用于单个单词,但并不总是必要的。我们可以将一个单词或单词的子词或字符进行标记化。在子词的情况下,同一句子可以有子词标记,如 rain-ing。
如果我们希望在字符级别执行标记化,那么可以是 r-a-i-n-i-n-g。实际上,在上一节讨论的一热编码方法中,标记化是在单词上进行的。
标记化是自然语言处理解决方案的基石。
一旦我们获得了标记,那么这些标记就可以用来准备一个词汇表。词汇表是语料库中唯一标记的集合。
有多个用于标记化的库。Regexp 标记化使用给定的模式参数来匹配标记或标记之间的分隔符。Whitespace 标记化通过将任何空白字符序列视为分隔符来使用。然后我们有 blankline,它使用空白行的序列作为分隔符。Wordpunct 标记化通过匹配字母字符序列和非字母非空白字符序列来进行标记化。当我们为文本数据创建 Python 解决方案时,我们将执行标记化。
现在,我们将探讨更多表示文本数据的方法。第一个这样的方法是词袋模型。
词袋模型方法
正如名称所示,语料库中的所有单词都会被使用。在词袋模型方法中,或者说 BOW 方法中,文本数据被标记化为语料库中的每个单词,然后计算每个标记的相应频率。在此过程中,我们忽略语法、单词的顺序或上下文。我们只专注于简单性。因此,我们将每个文本(句子或文档)表示为它自己的单词袋。
在整个文档的 BOW 方法中,我们将语料库的词汇表定义为语料库中存在的所有唯一单词。请注意,我们使用语料库中的所有唯一单词。如果我们愿意,我们也可以设置一个阈值,即单词被选中的频率的上限和下限。一旦我们获得了唯一的单词,那么每个句子都可以用与基础词汇向量相同维度的向量来表示。这个向量表示包含了句子中每个单词在词汇表中的频率。这听起来可能很复杂,但实际上这是一种直接的方法。
让我们通过一个例子来理解这种方法。假设我们有两个句子——It is raining heavily 和 We should eat fruits。
要表示这两个句子,我们将计算这些句子中每个单词的频率,如图 7-4 所示。
图 7-4 每个单词的频率已经计算出来了。在这个例子中,我们有两个句子。
现在,如果我们假设只有这两个单词代表整个词汇表,我们可以将第一个句子表示如图 7-5 所示。请注意,该表格包含了所有单词,但是不在句子中的单词的值为 0。
图 7-5 第一个句子对于词汇表中的所有单词进行了表示,我们假设词汇表中只有两个句子。
在这个例子中,我们讨论了如何使用 BOW 方法将句子表示为向量。但 BOW 方法没有考虑单词的顺序或上下文。它只关注单词的频率。因此,它是一种非常快速的方法来表示数据,并且与其同行相比计算成本较低。由于它是基于频率的,所以它通常用于文档分类。
但是,由于其纯粹基于频率的计算和表示,解决方案的准确性可能会受到影响。在语言中,单词的上下文起着重要作用。正如我们之前所看到的,苹果既是一种水果,也是一个著名的品牌和组织。这就是为什么我们有其他考虑比仅仅是频率更多参数的高级方法。下面我们将学习一种这样的方法,即 tf-idf 或词项频率-逆文档频率。
弹出测验——回答这些问题以检查你的理解。答案在本书的末尾。
(1) 用简单的语言解释标记化,就好像你在向一个不懂 NLP 的人解释一样。
(2) Bag of words 方法关注单词的上下文而不仅仅是频率。True or False.
(3) Lemmatization is less rigorous approach then stemming. True or False.
tf-idf(词项频率和逆文档频率)
我们在上一节学习了词袋方法。在词袋方法中,我们只重视单词的频率。这个想法是,频率较高的词可能不像频率较低但更重要的词那样提供有意义的信息。例如,如果我们有一套医学文件,我们想要比较两个词“疾病”和“糖尿病”。由于语料库包含医学文件,单词疾病必然会更频繁,而“糖尿病”这个词会更少,但更重要,以便识别处理糖尿病的文件。tf-idf 方法使我们能够解决这个问题,并提取更重要的词的信息。
在术语频率和逆文档频率(tf-idf)中,我们考虑单词的相对重要性。TF-idf 表示术语频率,idf 表示逆文档频率。我们可以定义 tf-idf 如下:
- 术语频率(t是整个文档中术语的计数。例如,文档“D”中单词“a”的计数。
- 逆文档频率(id是整个语料库中总文档数(N)与包含单词“a”的文档数(df)的比率的对数。
因此,tf-idf 公式将为我们提供单词在整个语料库中的相对重要性。数学公式是 tf 和 idf 的乘积,表示为
w[i,j] = tf[i,j] * log (N/df[i]) (方程式 7-1)
其中 N:语料库中的文档总数
tf[i,j]是文档中单词的频率
df[i]是包含该词的语料库中的文档数量。
这个概念听起来可能很复杂。让我们通过一个例子来理解。
假设我们有一百万本运动期刊的集合。这些运动期刊包含了许多不同长度的文章。我们还假设所有文章都只有英语。所以,假设在这些文件中,我们想要计算单词“ground”和“backhand”的 tf-idf 值。
假设有一个包含 100 个词的文档,其中“ground”出现了五次,而“backhand”只出现了两次。所以 ground 的 tf 为 5/100 = 0.05,而 backhand 的 tf 为 2/100 = 0.02。
我们了解到,“ground”这个词在体育中是相当常见的词,而“backhand”这个词的使用次数会较少。现在,我们假设“ground”出现在 100,000 个文档中,而“backhand”只出现在 10 个文档中。所以,“ground”的 idf 为 log (1,000,000/100,000) = log (10) = 1。对于“backhand”,它将是 log (1,000,000/10) = log (100,000) = 5。
要得到“ground”的最终值,我们将 tf 和 idf 相乘= 0.05 x 1 = 0.05。
要得到“backhand”的最终值,我们将 tf 和 idf 相乘= 0.02 x 5 = 0.1。
我们可以观察到,“背手”这个词的相对重要性要比单词“地面”的相对重要性更高。这就是 tf-idf 相对于基于频率的 BOW 方法的优势。但是,与 BOW 相比,tf-idf 计算时间更长,因为必须计算所有的 tf 和 idf。尽管如此,与 BOW 方法相比,tf-idf 提供了一个更好、更成熟的解决方案。
接下来我们将在下一节中介绍语言模型。
语言模型
到目前为止,我们已经学习了词袋模型方法和 tf-idf。现在我们将专注于语言模型。
语言模型分配概率给词的序列。N-grams 是语言模型中最简单的模型。我们知道,为了分析文本数据,它们必须被转换为特征向量。N-gram 模型创建了特征向量,使得文本可以以进一步分析的格式来表示。
n-gram 是一个概率语言模型。在 n-gram 模型中,我们计算第 N^(th) 个词在给定(N-1)个词的序列的情况下出现的概率。更具体地说,n-gram 模型将基于词 x[i-(n-1)], x[i-(n-2)]…x[i-1] 预测下一个词 x[i]。若我们希望使用概率术语,可以表示为给定前面的词 x[i-(n-1)], x[i-(n-2)]…x[i-1] 的条件概率 P(x[i] | x[i-(n-1)], x[i-(n-2)]…x[i-1])。该概率通过文本语料库中序列的相对频率计算得出。
若条目是单词,n-grams 可能被称为shingles。
让我们用一个例子来学习这个。
假设我们有一个句子 It is raining heavily。我们已经使用不同的 n 值展示了相应的表示方式。您应该注意到,对于不同的 n 值,单词的序列以及它们的组合方式是如何变化的。如果我们希望使用 n=1 或单个词来进行预测,表示将如图 7-6 所示。请注意,此处每个词单独使用。称为unigrams。
如果我们希望使用 n=2,那么现在使用的词数量将变为两个。它们被称为bigrams,这个过程将继续下去。
图 7-6 Unigrams, bigrams, trigrams 可以用来表示相同的句子。这个概念也可以扩展到 n-grams。
因此,如果我们有一个 unigram,那么它是一个词的序列;对于两个词的序列,称为 bigram;对于三个词的序列,称为 trigram,依此类推。因此,trigram 模型将使用仅考虑前两个词的条件概率来逼近后一个词的概率,而 bigram 则仅考虑前一个词的条件概率。这确实是一个非常强的假设,即一个词的概率只取决于之前的一个词,被称为马尔可夫假设。一般来说,N > 1 被认为较 unigrams 更加信息丰富。但显然计算时间也会增加。
n-gram 方法对 n 的选择非常敏感。它还在很大程度上取决于所使用的训练语料库,这使得概率非常依赖于训练语料库。因此,如果遇到一个未知单词,模型将很难处理该新单词。
我们现在将创建一个 Python 示例。我们将展示一些使用 Python 进行文本清洗的示例。
使用 Python 进行文本清洗
我们现在将用 Python 清洗文本数据。有一些库可能需要安装。我们将展示一些小的代码片段。你可以根据需要使用它们。我们还附上了代码片段及其结果的相应截图。
代码 1:删除文本中的空格。我们导入库re
,它被称为正则表达式
。文本是 It is raining outside with a lot of blank spaces in between.
import re doc = "It is raining outside" new_doc = re.sub("\s+"," ", doc) print(new_doc)
代码 2:现在我们将从文本数据中删除标点符号。
text_d = "Hey!!! How are you doing? And how is your health! Bye, take care." re.sub("[^-9A-Za-z ]", "" , text_d)
代码 3:这是另一种去除标点符号的方法。
import string text_d = "Hey!!! How are you doing? And how is your health! Bye, take care." cleaned_text = "".join([i for i in text_d if i not in string.punctuation]) cleaned_text
代码 4:我们现在将删除标点符号,并将文本转换为小写。
text_d = "Hey!!! How are you doing? And how is your health! Bye, take care." cleaned_text = "".join([i.lower() for i in text_d if i not in string.punctuation]) cleaned_text
代码 5:我们现在将使用标准的nltk
库。标记化将在这里使用 NLTK 库完成。输出也附在下面。
import nltk text_d = "Hey!!! How are you doing? And how is your health! Bye, take care." nltk.tokenize.word_tokenize(text_d)
请注意,在代码的输出中,我们有包括标点符号的所有单词作为不同的标记。如果你希望排除标点符号,可以使用之前共享的代码片段来清理标点符号。
代码 6:接下来是停用词。我们将使用 nltk 库删除停用词。然后,我们将对单词进行标记。
stopwords = nltk.corpus.stopwords.words('english') text_d = "Hey!!! How are you doing? And how is your health! Bye, take care." text_new = "".join([i for i in text_d if i not in string.punctuation]) print(text_new) words = nltk.tokenize.word_tokenize(text_new) print(words) words_new = [i for i in words if i not in stopwords] print(words_new)
代码 7:我们现在将在文本示例上执行词干提取。我们使用 NLTK 库进行词干提取。首先对单词进行标记,然后我们对其应用词干提取。
import nltk from nltk.stem import PorterStemmer stem = PorterStemmer() text = "eats eating studies study" tokenization = nltk.word_tokenize(text) for word in tokenization: print("Stem for {} is {}".format(word, stem.stem(w)))
代码 8:我们现在将在文本示例上执行词形还原。我们使用 NLTK 库进行词形还原。首先对单词进行标记,然后我们对其应用词形还原。
import nltk from nltk.stem import WordNetLemmatizer wordnet_lemmatizer = WordNetLemmatizer() text = "eats eating studies study" tokenization = nltk.word_tokenize(text) for word in tokenization: print("Lemma for {} is {}".format(word, wordnet_lemmatizer.lemmatize(w)))
观察并比较词干提取和词形还原的两个输出之间的差异。对于 studies 和 studying,词干提取生成的输出为 studi,而词形还原生成了正确的输出 study。
到目前为止,我们已经介绍了词袋模型、tf-idf 和 N-gram 方法。但在所有这些技术中,忽略了单词之间的关系,而这些关系在词嵌入中被使用 - 我们的下一个主题。
词嵌入
“一个单词的特点在于它周围的公司” - 约翰·鲁珀特·费斯。
到目前为止,我们研究了许多方法,但所有的技术都忽略了单词之间的上下文关系。让我们通过一个例子来学习。
假设我们的词汇表中有 100,000 个单词,从 aa 到 zoom。现在,如果我们执行上一节学习的 one-hot 编码,所有这些单词都可以以向量形式表示。每个单词将有一个唯一的向量。例如,如果单词 king 的位置在 21000,那么向量的形状将如下所示,其中在 21000 位置上有 1,其余位置为 0。
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0…………………1, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
这种方法存在一些明显的问题:
- 维度的数量非常高,计算复杂。
- 数据在本质上非常稀疏。
- 如果要输入 n 个新单词,则词汇量增加 n,因此每个向量的维度增加 n。
- 这种方法忽略了单词之间的关系。我们知道,ruler(统治者)、king(国王)、monarch(君主)有时可以互换使用。在 one-hot 编码方法中,任何这种关系都被忽略了。
如果我们希望进行语言翻译或生成聊天机器人,我们需要将这样的知识传递给机器学习解决方案。单词嵌入为这个问题提供了解决方案。它们将高维度的单词特征转换为较低的维度,同时保持上下文关系。单词嵌入允许我们创建更加泛化的模型。我们可以通过示例来理解含义。
如图 7-7 所示的示例中,“man” 到 “woman” 的关系类似于 “king” 到 “queen”,“eat” 到 “eating” 类似于 “study” 到 “studying” 或 “UK” 到 “London” 类似于 “Japan” 到 “Tokyo”。
图 7-7 单词嵌入可以用来表示单词之间的关系。例如,从 men(男人)到 women(女人)之间存在一种关系,类似于 king(国王)到 queen(女王)之间的关系。
简而言之,使用单词嵌入,我们可以将具有相似含义的单词表示为类似的形式。单词嵌入可以被认为是一类技术,其中我们将每个单词在预定义的向量空间中表示出来。语料库中的每个单词都被映射到一个向量上。根据单词的使用情况来理解分布式表示。因此,可以使用类似的单词具有相似的表示。这使得解决方案能够捕捉单词及其关系的潜在含义。因此,单词的含义起着重要作用。相比于词袋方法,这种表示更加智能,词袋方法中的每个单词都是独立处理的,不考虑它们的使用情况。而且,维度的数量较少,相比于 one-hot 编码,每个单词由 10 或 100 维的向量表示,这比 one-hot 编码方法中使用的超过 1000 维的向量表示要少得多。
我们将在下一节介绍两种最流行的技术 Word2Vec 和 GloVe。这一节提供了对 Word2Vec 和 GloVe 的理解。Word2Vec 和 GloVe 的数学基础超出了本书的范围。我们将理解解决方案的工作机制,然后使用 Word2Vec 和 GloVe 开发 Python 代码。到目前为止,书中还有一些术语我们没有讨论过,所以下一节关于 Word2Vec 和 GloVe 可能会很繁琐。如果你只对解决方案的应用感兴趣,可以跳过下一节。
Word2Vec 和 GloVe
Word2Vec 首次发表于 2013 年。它是由 Google 的 Tomas Mikolov 等人开发的。我们会在章节结束时分享论文的链接。建议你彻底研究这篇论文。
Word2Vec 是一组用于生成词嵌入的模型。输入是一个大型文本语料库。输出是一个向量空间,具有非常多的维度。在这个输出中,语料库中的每个单词被分配了一个唯一且对应的向量。最重要的一点是,在语料库中具有相似或共同上下文的单词,在生成的向量空间中位置相似。
在 Word2Vec 中,研究人员介绍了两种不同的学习模型 - 连续词袋模型和连续跳字模型,我们简要介绍如下:
- 连续词袋模型或 CBOW:在 CBOW 中,模型从周围上下文单词的窗口中预测当前单词。因此,CBOW 学习了在词袋方法中,单词的顺序不起作用。同样,在 CBOW 中,单词的顺序是无关紧要的。
- 连续跳字模型:它使用当前单词来预测周围窗口的上下文单词。在这样做时,它给邻近单词分配比远离单词更多的权重。
GloVe 或全局词向量是用于生成词向量表示的无监督学习算法。它由斯坦福的 Pennington 等人于 2014 年开发,并在 2014 年推出。它是两种技术的组合 - 矩阵分解技术和 Word2Vec 中使用的基于本地上下文的学习。GloVe 可用于找到像邮政编码和城市、同义词等关系。它为具有相同形态结构的单词生成了一组单一的向量。
这两个模型(Word2Vec 和 GloVe)都从共现信息中学习和理解它们的单词的向量表示。共现意味着单词在大型语料库中一起出现的频率。主要区别在于 Word2Vec 是基于预测的模型,而 GloVe 是基于频率的模型。Word2Vec 预测给定单词的上下文,而 GloVe 通过创建一个共现矩阵来学习单词在给定上下文中出现的频率。
弹出测验 - 回答这些问题以检查您的理解。书的末尾有答案
(1)BOW 比 tf-idf 方法更严格。真或假。
(2)Word2Vec 和 GloVe 之间的区别。
我们现在将在下一节中转到案例研究和 Python 实现。
无监督学习与生成式人工智能(MEAP)(三)(3)https://developer.aliyun.com/article/1522607