1、引言
关于文本的提取有很多方法,本文主要探索下sklearn官方的文本特征提取功能。
2、文本特征提取
文本分析是机器学习算法的主要应用领域。 然而,原始数据,符号文字序列不能直接传递给算法,因为它们大多数要求具有固定长度的数字矩阵特征向量,而不是具有可变长度的原始文本文档。
sklearn提供三种方法:
令牌化, 对每个可能的词令牌分成字符串并赋予整数形的id,例如通过使用空格和标点符号作为令牌分隔符。
统计,每个词令牌在文档中的出现次数。
标准化,在大多数的文档 / 样本中,可以减少重要的次令牌的出现次数的权重。
总的来说,方法是把文本文档集合转化成特征向量,比如每一行是一个文档,每一列是词id。
由于词的稀疏性,通常使用scipy.sparse
包中的稀疏实现。
3、使用方法
类CountVectorizer介绍
首先类 CountVectorizer 在单个类中实现了 tokenization (词语切分)和 occurrence counting (出现频数统计):
>>> from sklearn.feature_extraction.text import CountVectorizer >>> vectorizer = CountVectorizer() >>> corpus = [ ... 'This is the first document.', ... 'This is the second second document.', ... 'And the third one.', ... 'Is this the first document?', ... ] >>> X = vectorizer.fit_transform(corpus) #对每一列进行index >>> vectorizer.get_feature_names() == ( ... ['and', 'document', 'first', 'is', 'one', ... 'second', 'the', 'third', 'this']) True >>> X.toarray() array([[0, 1, 1, 1, 0, 0, 1, 0, 1], [0, 1, 0, 1, 0, 2, 1, 0, 1], [1, 0, 0, 0, 1, 0, 1, 1, 0], [0, 1, 1, 1, 0, 0, 1, 0, 1]]...) #还可以设置一个或两个词来进行分割,设置两个词的原因是部分语句要连读才有区分度 >>> bigram_vectorizer = CountVectorizer(ngram_range=(1, 2), ... token_pattern=r'\b\w+\b', min_df=1) >>> analyze = bigram_vectorizer.build_analyzer() >>> analyze('Bi-grams are cool!') == ( ... ['bi', 'grams', 'are', 'cool', 'bi grams', 'grams are', 'are cool']) True
TF-idf项加权
在一个大的文本语料库中,一些单词将出现很多次(例如 “the”, “a”, “is” 是英文),因此对文档的实际内容没有什么有意义的信息。 如果我们将直接计数数据直接提供给分类器,那么这些频繁词组会掩盖住那些我们关注但很少出现的词。
为了重新计算特征权重,并将其转化为适合分类器使用的浮点值,因此使用 tf-idf 变换是非常常见的。
Tf表示 术语频率,而 tf-idf 表示术语频率乘以转制文档频率:
.
术语频率,一个术语在给定文档中出现的次数乘以 idf 组件, 计算为
,
其中 是文档的总数,
是包含术语
的文档数。 然后,所得到的 tf-idf 向量通过欧几里得范数归一化:
.
它源于一个词权重的信息检索方式(作为搜索引擎结果的评级函数),同时也在文档分类和聚类中表现良好。
以下部分包含进一步说明和示例,说明如何精确计算 tf-idfs 以及如何在 scikit-learn 中计算 tf-idfs, TfidfTransformer
并 TfidfVectorizer
与定义 idf 的标准教科书符号略有不同
在 TfidfTransformer
和 TfidfVectorizer
中 smooth_idf=False
,将 “1” 计数添加到 idf 而不是 idf 的分母:
>>> from sklearn.feature_extraction.text import TfidfTransformer >>> transformer = TfidfTransformer(smooth_idf=False) >>> counts = [[3, 0, 1], ... [2, 0, 0], ... [3, 0, 0], ... [4, 0, 0], ... [3, 2, 0], ... [3, 0, 2]] ... >>> tfidf = transformer.fit_transform(counts) >>> tfidf.toarray() array([[ 0.81940995, 0. , 0.57320793], [ 1. , 0. , 0. ], [ 1. , 0. , 0. ], [ 1. , 0. , 0. ], [ 0.47330339, 0.88089948, 0. ], [ 0.58149261, 0. , 0.81355169]])
每行都被正则化,使其适应欧几里得标准:
例如,我们可以计算`计数`数组中第一个文档中第一个项的 tf-idf ,如下所示:
现在,如果我们对文档中剩下的2个术语重复这个计算,我们得到:
和原始 tf-idfs 的向量:
然后,应用欧几里德(L2)规范,我们获得文档1的以下 tf-idfs:
通过 拟合
方法调用计算的每个特征的权重存储在模型属性中:
>>> transformer.idf_
array([ 1. ..., 2.25..., 1.84...])
虽然tf-idf标准化通常非常有用,但是可能有一种情况是二元变量显示会提供更好的特征。 这可以使用类 CountVectorizer
的 二进制
参数来实现。 特别地,一些估计器,诸如 伯努利朴素贝叶斯 显式的使用离散的布尔随机变量。 而且,非常短的文本很可能影响 tf-idf 值,而二进制出现信息更稳定。
通常情况下,调整特征提取参数的最佳方法是使用基于网格搜索的交叉验证,例如通过将特征提取器与分类器进行流水线化。
词语表示的限制
直接看例子
>>> ngram_vectorizer = CountVectorizer(analyzer='char_wb', ngram_range=(2, 2)) >>> counts = ngram_vectorizer.fit_transform(['words', 'wprds']) >>> ngram_vectorizer.get_feature_names() == ( ... [' w', 'ds', 'or', 'pr', 'rd', 's ', 'wo', 'wp']) True >>> counts.toarray().astype(int) array([[1, 1, 1, 0, 1, 1, 1, 0], [1, 1, 0, 1, 1, 1, 0, 1]]) >>> ngram_vectorizer = CountVectorizer(analyzer='char_wb', ngram_range=(5, 5)) >>> ngram_vectorizer.fit_transform(['jumpy fox']) ... <1x4 sparse matrix of type '<... 'numpy.int64'>' with 4 stored elements in Compressed Sparse ... format> >>> ngram_vectorizer.get_feature_names() == ( ... [' fox ', ' jump', 'jumpy', 'umpy ']) True >>> ngram_vectorizer = CountVectorizer(analyzer='char', ngram_range=(5, 5)) >>> ngram_vectorizer.fit_transform(['jumpy fox']) ... <1x5 sparse matrix of type '<... 'numpy.int64'>' with 5 stored elements in Compressed Sparse ... format> >>> ngram_vectorizer.get_feature_names() == ( ... ['jumpy', 'mpy f', 'py fo', 'umpy ', 'y fox']) True
对比以上几种例子,我们可以知道:
对于使用白色空格进行单词分离的语言,对于语言边界感知变体 char_wb
尤其有趣,因为在这种情况下,它会产生比原始 char
变体显着更少的噪音特征。 对于这样的语言,它可以增加使用这些特征训练的分类器的预测精度和收敛速度,同时保持关于拼写错误和词导出的稳健性。
虽然可以通过提取 n-gram 而不是单独的单词来保存一些本地定位信息,但是包含 n-gram 的单词和袋子可以破坏文档的大部分内部结构,因此破坏了该内部结构的大部分含义。
为了处理自然语言理解的更广泛的任务,因此应考虑到句子和段落的地方结构。因此,许多这样的模型将被称为 “结构化输出” 问题,这些问题目前不在 scikit-learn 的范围之内。