导读
介绍了一些传统但是被验证是非常有用的,现在都还在用的策略,用来对非结构化的文本数据提取特征。
介绍
在本文中,我们将研究如何处理文本数据,这无疑是最丰富的非结构化数据来源之一。文本数据通常由文档组成,文档可以表示单词、句子甚至是文本的段落。文本数据固有的非结构化(没有格式整齐的数据列)和嘈杂的特性使得机器学习方法更难直接处理原始文本数据。因此,在本文中,我们将采用动手实践的方法,探索从文本数据中提取有意义的特征的一些最流行和有效的策略。这些特征可以很容易地用于构建机器学习或深度学习模型。
动机
特征工程通常被称为创建性能更好的机器学习模型的秘密武器。只要有一个出色的特征就可能是你赢得Kaggle挑战的门票!特征工程的重要性对于非结构化的文本数据更为重要,因为我们需要将自由流动的文本转换成一些数字表示形式,然后机器学习算法就可以理解这些数字表示形式。即使出现了自动化的特征工程,在将不同的特征工程策略应用为黑盒模型之前,你仍然需要理解它们背后的核心概念。永远记住,“如果给你一盒工具来修理房子,你应该知道什么时候使用电钻,什么时候使用锤子!”
理解文本数据
我相信你们所有人都对这个场景中包含的文本数据有一个合理的概念。请记住,文本数据总是可以以结构化数据属性的形式存在,但通常这属于结构化分类数据的范畴。
在这个场景中,我们讨论的是单词、短语、句子和整个文档形式的自由流动文本。本质上,我们有一些句法结构,比如单词组成短语,短语组成句子,句子又组成段落。然而,文本文档没有固有的结构,因为可以有各种各样的单词,这些单词在不同的文档中会有所不同,而且与结构化数据集中固定数量的数据维度相比,每个句子的长度也是可变的。
特征工程策略
让我们看看一些流行的和有效的策略来处理文本数据,并从中提取有意义的特征,这些特征可以用于下游的机器学习系统。请注意,你可以在https://github.com/dipanjanS/practical-machine-learning-with-python中访问本文中使用的所有代码,以供将来参考。我们将从加载一些基本的依赖项和设置开始。
代码语言:javascript复制 import pandas as pd
import numpy as np
import re
import nltk
import matplotlib.pyplot as plt
pd.options.display.max_colwidth = 200
%matplotlib inline
现在,让我们以一个示例文档语料库为例,我们将在该语料库上运行本文中的大部分分析。corpus是具有一个或多个主题的文本文档集合。
代码语言:javascript复制 corpus = ['The sky is blue and beautiful.',
'Love this blue and beautiful sky!',
'The quick brown fox jumps over the lazy dog.',
"A king's breakfast has sausages, ham, bacon, eggs, toast and beans",
'I love green eggs, ham, sausages and bacon!',
'The brown fox is quick and the blue dog is lazy!',
'The sky is very blue and the sky is very beautiful today',
'The dog is lazy but the brown fox is quick!'
]
labels = ['weather', 'weather', 'animals', 'food', 'food', 'animals', 'weather', 'animals']
corpus = np.array(corpus)
corpus_df = pd.DataFrame({'Document': corpus,
'Category': labels})
corpus_df = corpus_df[['Document', 'Category']]
可以看到,我们已经为我们的toy语料库获取了一些属于不同类别的文本文档示例。像往常一样,在讨论特征工程之前,我们需要进行一些数据预处理或整理,以删除不必要的字符、符号和tokens。
文本预处理
可以有多种方法来清理和预处理文本数据。在接下来的几点中,我们将重点介绍在自然语言处理(NLP)中大量使用的一些最重要的方法。
- 删除标签:我们的文本经常包含不必要的内容,如HTML标签,分析文本的时候这不会增加多少价值。BeautifulSoup库可以帮我们做很多必须的工作。
- 删除重音字符:在任何文本语料库中,特别是在处理英语时,通常可能要处理重音字符/字母。因此,我们需要确保将这些字符转换并标准化为ASCII字符。一个简单的例子是将é转换为e。
- 扩展缩略语:在英语中,缩略语基本上是单词或音节的缩写形式。这些现有单词或短语的缩略形式是通过删除特定的字母和声音来创建的。例如,do not变为don 't以及I would 变为I 'd 。将每个缩略语转换为其扩展的原始形式通常有助于文本标准化。
- 删除特殊字符:非字母数字字符的特殊字符和符号通常会增加非结构化文本中的额外噪音。通常,可以使用简单正则表达式(regexes)来实现这一点。
- 词根提取和词形还原:词干通常是可能的单词的基本形式,可以通过在词干上附加词缀,如前缀和后缀来创建新单词。这就是所谓的拐点。获取单词基本形式的反向过程称为“词根提取”。一个简单的例子是单词WATCHES, WATCHING,和WATCHED。它们以词根WATCH作为基本形式。词形还原与词根提取非常相似,在词根提取中,我们去掉词缀以得到单词的基本形式。然而,在这种情况下,基本形式被称为根词,而不是词根。不同之处在于,词根总是一个词典上正确的单词(存在于字典中),但根词的词干可能不是这样。
- 删除停止词:在从文本中构造有意义的特征时,意义不大或者没有意义的词被称为停止词或停止词。如果你在语料库中做一个简单的词或词的频率,这些词的频率通常是最高的。像a、an、the、and等词被认为是停止词。没有一个通用的停止词列表,但是我们使用了一个来自“nltk”的标准英语停止词列表。你还可以根据需要添加自己的域特定的停止词。
除此之外,你还可以执行其他标准操作,如标记化、删除额外的空格、文本小写转换和更高级的操作,如拼写纠正、语法错误纠正、删除重复字符等等。
由于本文的重点是特征工程,所以我们将构建一个简单的文本预处理程序,该程序的重点是删除特殊字符、额外的空格、数字、停止词和把文本语料库的大写变成小写。
代码语言:javascript复制 wpt = nltk.WordPunctTokenizer()
stop_words = nltk.corpus.stopwords.words('english')
def normalize_document(doc):
# lower case and remove special characterswhitespaces
doc = re.sub(r'[^a-zA-Zs]', '', doc, re.I|re.A)
doc = doc.lower()
doc = doc.strip()
# tokenize document
tokens = wpt.tokenize(doc)
# filter stopwords out of document
filtered_tokens = [token for token in tokens if token not in stop_words]
# re-create document from filtered tokens
doc = ' '.join(filtered_tokens)
return doc
一旦我们准备好了基本的预处理pipeline,我们可以将其应用于示例语料库。
代码语言:javascript复制 norm_corpus = normalize_corpus(corpus)
norm_corpus
Output
------
array(['sky blue beautiful', 'love blue beautiful sky',
'quick brown fox jumps lazy dog',
'kings breakfast sausages ham bacon eggs toast beans',
'love green eggs ham sausages bacon',
'brown fox quick blue dog lazy',
'sky blue sky beautiful today',
'dog lazy brown fox quick'],
dtype='<U51')
上面的输出应该可以让你清楚地看到我们的每个示例文档在预处理之后的样子。现在让我们来设计一些特征!
词袋模型
这可能是非结构化文本最简单的向量空间表示模型。向量空间模型只是一个数学模型,它将非结构化文本(或任何其他数据)表示为数值向量,这样向量的每个维度都是一个特定的特性属性。单词包模型将每个文本文档表示为一个数字向量,其中每个维度都是来自语料库的特定单词,其值可以是其在文档中的频率、出现频率(用1或0表示),甚至是加权值。模型的名称是这样的,因为每个文档都按照字面意思表示为自己单词的“包”,不考虑单词顺序、序列和语法。
代码语言:javascript复制 from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer(min_df=0., max_df=1.)
cv_matrix = cv.fit_transform(norm_corpus)
cv_matrix = cv_matrix.toarray()
cv_matrix
代码语言:javascript复制 Output
------
array([[0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0],
[1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0],
[1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0],
[0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1],
[0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0]
], dtype=int64)
因此,可以看到我们的文档已经被转换成数字向量,这样每个文档都由上面的特征矩阵中的一个向量(行)表示。下面的代码将帮助以更容易理解的格式表示这一点。
代码语言:javascript复制 # get all unique words in the corpus
vocab = cv.get_feature_names()
# show document feature vectors
pd.DataFrame(cv_matrix, columns=vocab)
可以清楚地看到,特征向量中的每一列表示语料库中的一个单词,每一行表示我们的一个文档。任何单元格中的值表示该单词(用列表示)在特定文档中出现的次数(用行表示)。因此,如果一个文档语料库由所有文档中的N唯一单词组成,那么每个文档都有一个N维向量。
N-Grams袋模型
一个单词只是一个符号,通常被称为unigram或1-gram。我们已经知道词袋模型不考虑单词的顺序。但是,如果我们也想考虑按顺序出现的短语或单词的集合呢?N-gram帮助我们达到这个目的。N-gram基本上是文本文档中单词tokens的集合,这些标记是连续的,并以序列的形式出现。Bi-gram表示2阶n-grams(2个单词),Tri-grams表示3阶n-grams(3个单词),依此类推。因此,N-Grams袋模型只是词袋模型的一个扩展,因此我们也可以利用基于N-gram的特征。下面的示例描述了每个文档特征向量中基于bi-gram的特征。
代码语言:javascript复制 # you can set the n-gram range to 1,2 to get unigrams as well as bigrams
bv = CountVectorizer(ngram_range=(2,2))
bv_matrix = bv.fit_transform(norm_corpus)
bv_matrix = bv_matrix.toarray()
vocab = bv.get_feature_names()
pd.DataFrame(bv_matrix, columns=vocab)
这为我们的文档提供了特征向量,其中每个特征由表示两个单词序列的bi-gram组成,值表示该bi-gram出现在文档中的次数。
TF-IDF模型
在大型语料库中使用词袋模型可能会产生一些潜在的问题。由于特征向量是基于绝对频率,可能有一些项在所有文档中都经常出现,这可能倾向于掩盖其他方面的特征。TF-IDF模型试图解决这一问题,在计算中使用了缩放或归一化因子。TF-IDF是Term Frequency- reverse Document Frequency的缩写。
其计算方法为:词频(tf)和逆文档频率(idf)。该技术是为搜索引擎中查询结果的排序而发展起来的,目前已成为信息检索和自然语言处理领域中一个不可或缺的模型。
在数学上,我们可以将TF-IDF定义为tfidf = tf x idf,可以进一步展开为:
这里,tfidf(w, D)是文档D中单词w的TF-IDF得分。tf(w, D)表示文档D中w的词频,可以从词袋模型中得到。idf (w, D)是w这个单词的逆文档频率,可以通过计算语料库中的文档的总数C除以w这个词的文档频率的对数变换得到, 这基本上是文档的语料库词w的频率。这个模型有多种变体,但最终都得到了非常相似的结果。现在让我们把它应用到我们的语料库上!
每个文本文档的基于TF-IDF的特征向量与原始的词袋模型值相比具有了缩放和标准化的值。
文档相似度
文档相似度是使用基于距离或相似度的度量的过程,该度量可用于根据从文档中提取的特征(如词袋或tf-idf)确定文本文档与任何其他文档的相似程度。
因此,可以看到,我们可以构建在上一节中设计的基于tf-idf的特征的基础上,并使用它们来生成新的特征,通过利用基于这些特征的相似性,可以在搜索引擎、文档集群和信息检索等领域中发挥作用。
语料库中的成对文档相似性涉及到为语料库中的每对文档计算文档相似性。因此,如果在一个语料库中有C文档,那么最终将得到一个C x C矩阵,其中每一行和每一列表示一对文档的相似度得分,这对文档分别表示行和列的索引。有几个相似度和距离度量用于计算文档相似度。其中包括余弦距离/相似度、欧几里德距离、曼哈顿距离、BM25相似度、jaccard距离等。在我们的分析中,我们将使用可能是最流行和广泛使用的相似性度量,
余弦相似度和基于TF-IDF特征向量的成对文档相似度比较。
代码语言:javascript复制from sklearn.metrics.pairwise import cosine_similarity
similarity_matrix = cosine_similarity(tv_matrix)
similarity_df = pd.DataFrame(similarity_matrix)
similarity_df
余弦相似度给出了一个度量,表示两个文本文档的特征向量表示之间夹角的余弦值。文档之间的夹角越小,它们之间的距离就越近,也就越相似,如下图所示。
仔细观察相似矩阵可以清楚地看出,文档(0,1和6)、(2,5和7)彼此非常相似,文档3和文档4彼此略有相似,但幅度不是很大,但仍然比其他文档强。这必须表明这些类似的文档具有一些类似的特性。这是一个完美的分组或聚类的例子,可以通过无监督学习来解决,尤其是在处理数百万文本文档的大型语料库时。
使用相似特征对文档进行聚类
聚类利用无监督学习将数据点(本场景中的文档)分组或聚集。在这里,我们将利用一种无监督的分层聚类算法,通过利用前面生成的文档特征相似性,尝试将我们的玩具语料库中的类似文档分组在一起。层次聚类算法有两种,即聚合算法和分裂算法。我们将使用一个聚合聚类算法,这是分层聚类使用自底向上的方法,即从自己的簇中开始,然后使用一个度量数据点之间距离的距离度量和一个链接合并准则将簇依次合并在一起。下图显示了一个示例描述。
链接准则的选择控制了合并的策略。链接准则的例子有Ward、Complete、Average等。该准则对于选择每一步合并的簇对(最低级的单个文档和较高级的簇)非常有用,它基于目标函数的最优值。我们选择Ward 's minimum variance method作为我们的链接准则来最小化总簇内方差。因此,在每个步骤中,我们都找到了合并后总簇内方差增加最小的一对簇。既然我们已经有了特征相似性,让我们在示例文档上构建链接矩阵。
代码语言:javascript复制from scipy.cluster.hierarchy import dendrogram, linkage
Z = linkage(similarity_matrix, 'ward')
pd.DataFrame(Z, columns=['DocumentCluster 1', 'DocumentCluster 2',
'Distance', 'Cluster Size'], dtype='object')
如果仔细查看链接矩阵,可以看到链接矩阵的每一步(行)都告诉我们哪些数据点(或簇)合并在一起。如果有n数据点,链接矩阵Z的形状将是(n - 1) x 4,其中Z[i]将告诉我们在步骤i合并了哪些集群。每一行有四个元素,前两个元素要么是数据点标识符,要么是簇标签(在矩阵的后半部分中有一次合并了多个数据点),第三个元素是前两个元素(数据点或集群)之间的簇距离,最后一个元素是合并完成后簇中元素数据点的总数。
现在让我们把这个矩阵形象化为一个树形图,以便更好地理解这些元素!
代码语言:javascript复制plt.figure(figsize=(8, 3))
plt.title('Hierarchical Clustering Dendrogram')
plt.xlabel('Data point')
plt.ylabel('Distance')
dendrogram(Z)
plt.axhline(y=1.0, c='k', ls='--', lw=0.5)
我们可以看到,每个数据点开始时是一个单独的簇,然后慢慢地开始与其他数据点合并,形成聚类。从颜色和树状图的高度来看,如果考虑距离度量在1.0或以上(用虚线表示),则可以看到模型正确地识别了三个主要聚类。利用这个距离,我们得到了聚类标签。
代码语言:javascript复制from scipy.cluster.hierarchy import fcluster
max_dist = 1.0
cluster_labels = fcluster(Z, max_dist, criterion='distance')
cluster_labels = pd.DataFrame(cluster_labels, columns=['ClusterLabel'])
pd.concat([corpus_df, cluster_labels], axis=1)
可以清楚地看到,我们的算法根据分配给文档的聚类标签正确地标识了文档中的三个不同类别。这将使你对如何利用TF-IDF特征来构建相似特征有一个很好的了解,而相似特征反过来又有助于对文档进行聚类。
总结
这些示例应该让你对文本数据上的特征工程的流行策略有一个很好的了解。请记住,这些都是基于数学、信息检索和自然语言处理概念的传统策略。因此,随着时间的推移,这些经过尝试和测试的方法在各种数据集和问题中都证明是成功的。下一步将是利用文本数据上的特性工程的深度学习模型的详细策略!
—END—
英文原文:https://towardsdatascience.com/understanding-feature-engineering-part-3-traditional-methods-for-text-data-f6f7d70acd41