在使用文本数据来搭建预测模型前,都需要特殊的准备工作。
文本首先要通过解析来提取单词,这一过程称为词条化。然后单词需要编码为整数或浮点值,作为机器学习算法的输入,称为特征提取(或量化)。
scikit-learn 提供了简单的工具帮助我们对你的文本数据进行词条化和特征提取。
在这篇文章中,你会学到在 Python 中如何使用 scikit-learn 实现用于机器学习的文本数据准备。
在读完这篇文章后,你会了解到:
- 如何使用 CountVectorizer 将文本的转化成单词频数向量。
- 如何使用 TfidfVectorizer 提取文本的单词权重向量。
- 如何使用 HashingVectorizer 将文本映射到特征索引。
让我们开始吧。
“词袋(Bag-of-words)”模型
在使用机器学习算法时,我们不能直接用文本进行运算。相反,我们需要将文本转换成数字。
我们想对文档进行分类时,每个文档作为“输入”,文档的类别标签是我们预测算法的“输出”。算法只能接受数字向量作为输入,所以需要将文档转换成固定长度的数字向量。
机器学习领域有一个简单且有效的模型,适用于文本文档,叫做“词袋”(Bag-of-Words)模型,简称为 BOW。
该模型的简单之处在于,它舍弃了单词中的所有顺序信息,并主要关注文档中单词的出现频率。
这一点可以通过分配给每个单词一个唯一的数字来实现。这样一来,我们看到的任何文档都可以编码成一个固定长度的向量,长度为已知单词所构成的词汇表的长度。该向量中每个位置的值是编码文档中的每个单词出现的次数或频率。
这就是“词袋”模型,我们只关心编码方法,能表示哪些词语在文档中出现了,或者他们在编码文档中出现的频率,而不考虑任何关于顺序的信息。
这个简单的方法有很多种扩展,既可以更好地解释“单词”的含义,也可以定义向量中每个单词的编码方式。
scikit-learn 提供了 3 种可供我们使用的不同方法,我们将简要地看一下每种方法。
CountVectorizer——量化单词数量
CountVectorizer 提供了一种简单的方法,不仅可以将文本文档的数据集转化成词条并建立一个已知单词的词汇表,而且还可以用该词汇表对新文本进行编码。
使用方法如下:
- 创建 CountVectorizer 类的一个实例。
- 调用 fit() 函数,通过学习从一个或多个文档中得出一个词汇表。
- 对一或多个文档应用 transform() 函数,将每个文档编码成一个向量。
编码得到的向量能够返回整个词汇表的长度,以及每个单词在该文档中出现的次数。
由于这些向量含有许多零值,所以我们称之为稀疏的。Python 在 scipy.sparse 库中提供了一种处理这类稀疏向量的有效方法。
调用 transform() 所返回的向量是稀疏向量,你可以将它们转换为 numpy 数组,看起来更直观也更好理解,这一步可以通过调用 toarray() 函数完成。
下面是一个使用 CountVectorizer 来词条化、构造词汇表,以及编码文档的示例。
from sklearn.feature_extraction.text import CountVectorizer # 文本文档列表 text = ["The quick brown fox jumped over the lazy dog."] # 构造变换函数 vectorizer = CountVectorizer() # 词条化以及建立词汇表 vectorizer.fit(text) # 总结 print(vectorizer.vocabulary_) # 编码文档 vector = vectorizer.transform(text) # 总结编码文档 print(vector.shape) print(type(vector)) print(vector.toarray())
从上例中可以看到,我们通过词汇表来查看到底是什么被词条化了:
print(vectorizer.vocabulary_)
可以看到,所有单词默认情况下是小写,并且忽略掉标点符号。词条化的这些参数以及其他方面是可配置的,我建议你在 API 文档中查看所有选项。
运行这个示例,首先会显示出词汇表,然后显示出编码文档的形状。我们可以看到,词汇表中有 8 个单词,于是编码向量的长度为 8。
可以看出,编码向量是一个稀疏矩阵。最后,我们可以看到以数组形式出现的编码向量,显示出每个单词的出现次数为 1,除了索引号为 7 的单词出现次数为 2。
{'dog': 1, 'fox': 2, 'over': 5, 'brown': 0, 'quick': 6, 'the': 7, 'lazy': 4, 'jumped': 3} (1, 8) <class 'scipy.sparse.csr.csr_matrix'> [[1 1 1 1 1 1 1 2]] {1}
重要的是,该量化方法可以用于含有词汇表中没有出现的单词的文档。这些单词会被忽略掉,然后在得到的向量结果中不会给出出现次数。
下面是一个使用上述的词条化工具对文档进行编码的示例,该文档中含有一个词汇表中的词,以及一个不在词汇表中的词。
# 编码其他文档 text2 = ["the puppy"] vector = vectorizer.transform(text2) print(vector.toarray())
运行示例,显示出编码稀疏向量的矩阵形式,可以看出词汇表中的单词出现了 1 次,而没在词汇表中的单词完全被忽略了。
[[0 0 0 0 0 0 0 1]]
编码的向量可以直接用于机器学习算法。
TfidfVectorizer——计算单词权重
统计单词出现次数是一个很好的切入点,但也是很基础的特征。
简单的次数统计的一个问题在于,有些单词,例如“the”会出现很多次,它们的统计数量对于编码向量没有太大意义。
一个替代方法是统计单词权重,目前最流行的方法是 TF-IDF 。这是一个缩写词,代表“词频 - 逆文档频率”(Term Frequency–Inverse Document Frequency),代表一个词对于一个文档的重要程度。
词频(Term Frequency):指的是某一个给定的词语在一篇文档中出现的次数。
逆文档频率(Inverse Document Frequency):单词在文档中出现的频率越高,IDF 值越低。
撇开数学不说,TF-IDF 给出的是单词权重,会把更有意思的单词标注出来,例如仅在某篇文档中频率很高但不会在所有文档中都频繁出现的词。
TfidfVectorizer 可以词条化文档,学习词汇表以及逆文档频率权重,并且可以编码新文档。或者,如果你已经用 CountVectorizer 学习得到了向量,你可以对它使用 Tfidftransformer 函数,计算逆文档频率并且开始编码文件。
同样的,创建(create)、拟合(fit)以及变换(transform)函数的调用都与 CountVectorizer 相同。
下面是一个使用 TfidfVectorizer 来学习词汇表和 3 篇小文档的逆文档频率的示例,并对其中一篇文档进行编码。
from sklearn.feature_extraction.text import TfidfVectorizer # 文本文档列表 text = ["The quick brown fox jumped over the lazy dog.", "The dog.", "The fox"] # 创建变换函数 vectorizer = TfidfVectorizer() # 词条化以及创建词汇表 vectorizer.fit(text) # 总结 print(vectorizer.vocabulary_) print(vectorizer.idf_) # 编码文档 vector = vectorizer.transform([text[0]]) # 总结编码文档 print(vector.shape) print(vector.toarray())
上例中,我们从文档中学到了含有 8 个单词的词汇表,在输出向量中,每个单词都分配了一个唯一的整数索引。
我们计算了词汇表中每个单词的逆文档频率,给观测到的最常出现的单词“the”(索引号为 7)分配了最低的分数 1.0。
最终,第一个文档被编码成一个 8 个元素的稀疏矩阵,我们可以查看每个单词的最终权重分数,可以看到“the”、“fox”,以及“dog”的值与词汇表中其他单词的值不同。
{'fox': 2, 'lazy': 4, 'dog': 1, 'quick': 6, 'the': 7, 'over': 5, 'brown': 0, 'jumped': 3} [ 1.69314718 1.28768207 1.28768207 1.69314718 1.69314718 1.69314718 1.69314718 1. ] (1, 8) [[ 0.36388646 0.27674503 0.27674503 0.36388646 0.36388646 0.36388646 0.36388646 0.42983441]]
这些分数被归一化为 0 到 1 之间的值,编码的文档向量可以直接用于大多数机器学习算法。
HashingVectorizer——哈希量化文本
单词频率和权重是很有用的,但是当词汇表变得很大时,以上两种方法就会出现局限性。
反过来,这将需要巨大的向量来编码文档,并对内存要求很高,而且会减慢算法的速度。
一种很好的方法是使用单向哈希方法来将单词转化成整数。好处是该方法不需要词汇表,可以选择任意长的固定长度向量。缺点是哈希量化是单向的,因此无法将编码转换回单词(对与许多有监督的学习任务来说或许并不重要)。
HashingVectorizer 类实现了这一方法,所以可以使用它对单词进行连续哈希量化,然后按需求词条化和编码文档。
下面是对单一文档使用 HashingVectorizer 进行编码的示例。
我们选择了一个固定长度为 20 的任意向量。这个值对应哈希函数的范围,小的值(例如 20)可能会导致哈希碰撞。在之前的计算机科学课程中,我们介绍过一些启发式算法,可以根据估计的词汇量来选择哈希长度和碰撞概率。
要注意这种量化方法不要求调用函数来对训练数据文件进行拟合。相反,在实例化之后,它可以直接用于编码文档。
from sklearn.feature_extraction.text import HashingVectorizer # 文本文档列表 text = ["The quick brown fox jumped over the lazy dog."] # 创建变换函数 vectorizer = HashingVectorizer(n_features=20) # 编码文档 vector = vectorizer.transform(text) # 总结编码文档 print(vector.shape) print(vector.toarray())
运行该示例代码可以把样例文档编码成一个含有 20 个元素的稀疏矩阵。
编码文档的值对应于正则化的单词计数,默认值在 -1 到 1 之间,但是可以修改默认设置,然后设置成整数计数值。
(1, 20) [[ 0. 0. 0. 0. 0. 0.33333333 0. -0.33333333 0.33333333 0. 0. 0.33333333 0. 0. 0. -0.33333333 0. 0. -0.66666667 0. ]]
深度阅读
这一节我们为大家提供了一些关于这篇文章的深度阅读材料。
自然语言处理
scikit-learn
- scikit-learn 使用手册 4.2 节,特征提取。
- sckit-learn 特征提取 API。
- scikit-learn 教程:文本数据处理。
类 API
- CountVectorizer scikit-learn API
- TfidfVectorizer scikit-learn API
- TfidfTransformer scikit-learn API
- HashingVectorizer scikit-learn API
总结
在这篇教程中,你会学习到如何用 scikit-learn 来准备用于机器学习的文本数据。
我们只是在这些例子中接触了皮毛,我想强调的是这些类有许多设置细节会影响文档词条化的结果,值得我们继续探究。
查看英文原文: How to Prepare Text Data for Machine Learning with scikit-learn
感谢薛命灯对本文的审校。
评论