尝试在Spark中进行文档分类。我不确定HashingTF中的散列是做什么的;它会牺牲任何准确性吗?我对此表示怀疑,但我不知道。 spark文档说它使用了"哈希技巧" ......只是工程师使用的另一个非常糟糕/令人困惑的命名的例子(我也有罪)。 CountVectorizer还需要设置词汇量大小,但它有另一个参数,一个阈值参数,可用于排除出现在文本语料库中某个阈值以下的单词或标记。我不明白这两个变形金刚之间的区别。使这一点很重要的是算法中的后续步骤。例如,如果我想在生成的tfidf矩阵上执行SVD,那么词汇量大小将决定SVD矩阵的大小,这会影响代码的运行时间,以及模型性能等。我一般有困难在API文档之外找到关于Spark Mllib的任何来源以及没有深度的非常简单的例子。
答案 0 :(得分:20)
一些重要的差异:
CountVectorizer
) vs不可逆(HashingTF
) - 由于散列不可逆,您无法从散列向量恢复原始输入。另一方面,带有模型(索引)的计数向量可用于恢复无序输入。因此,使用散列输入创建的模型可能更难以解释和监控。HashingTF
只需要一次数据扫描,并且除了原始输入和向量之外不需要额外的内存。 CountVectorizer
需要对数据进行额外扫描,以构建模型和额外的内存来存储词汇(索引)。在unigram语言模型的情况下,它通常不是问题,但在n-gram较高的情况下,它可能过于昂贵或不可行。HashingTF
的情况下,可能会发生碰撞降低维数。 CountVectorizer
丢弃不常见的令牌。它如何影响下游模型取决于特定的用例和数据。答案 1 :(得分:9)
根据Spark 2.1.0文档,
HashingTF和CountVectorizer都可用于生成术语频率向量。
<强> HashingTF 强>
HashingTF是一个Transformer,它接受一组术语和转换 这些设置为固定长度的特征向量。在文本处理中,a “一套术语”可能是一堆文字。 HashingTF利用散列技巧。通过应用哈希将原始要素映射到索引(术语) 功能。这里使用的哈希函数是MurmurHash 3.然后是术语 基于映射的指数计算频率。这种方法 避免需要计算全局术语到索引的映射,这可能是 昂贵的大型语料库,但它有潜在的哈希 碰撞,其中不同的原始特征可能成为同一个术语 散列后。
为了减少碰撞的机会,我们可以增加 目标特征维度,即散列的桶的数量 表。由于使用简单的模来将哈希函数转换为 列索引,建议使用2的幂作为特征 尺寸,否则功能将不会均匀映射到 列。默认要素尺寸为2 ^ 18 = 262,144。一个 可选的二进制切换参数控制术语频率计数。什么时候 设置为true所有非零频率计数都设置为1.这是 特别适用于模拟二进制的离散概率模型, 计数而不是整数。
<强> CountVectorizer 强>
CountVectorizer和CountVectorizerModel旨在帮助转换a 将文本文档集合到令牌计数的向量中。当一个 a-priori字典不可用,CountVectorizer可以用作 一个Estimator来提取词汇,然后生成一个 CountVectorizerModel。该模型为...生成稀疏表示 词汇上的文件,然后可以传递给其他人 LDA等算法。
在拟合过程中,CountVectorizer将选择顶部 vocabSize按语料库中的术语频率排序的单词。一个 可选参数minDF也会影响拟合过程 指定文件的最小数量(或小于等于1.0) 术语必须出现在词汇表中。另一个可选 二进制切换参数控制输出向量。如果设置为全部全部 非零计数设置为1.这对于离散尤其有用 模拟二元而不是整数的概率模型。
示例代码
from pyspark.ml.feature import HashingTF, IDF, Tokenizer
from pyspark.ml.feature import CountVectorizer
sentenceData = spark.createDataFrame([
(0.0, "Hi I heard about Spark"),
(0.0, "I wish Java could use case classes"),
(1.0, "Logistic regression models are neat")],
["label", "sentence"])
tokenizer = Tokenizer(inputCol="sentence", outputCol="words")
wordsData = tokenizer.transform(sentenceData)
hashingTF = HashingTF(inputCol="words", outputCol="Features", numFeatures=100)
hashingTF_model = hashingTF.transform(wordsData)
print "Out of hashingTF function"
hashingTF_model.select('words',col('Features').alias('Features(vocab_size,[index],[tf])')).show(truncate=False)
# fit a CountVectorizerModel from the corpus.
cv = CountVectorizer(inputCol="words", outputCol="Features", vocabSize=20)
cv_model = cv.fit(wordsData)
cv_result = model.transform(wordsData)
print "Out of CountVectorizer function"
cv_result.select('words',col('Features').alias('Features(vocab_size,[index],[tf])')).show(truncate=False)
print "Vocabulary from CountVectorizerModel is \n" + str(cv_model.vocabulary)
输出如下
Hashing TF错过了对LDA等技术至关重要的词汇表。为此,必须使用CountVectorizer功能。 无论词汇大小如何,CountVectorizer函数估计术语频率而不涉及任何近似值,与HashingTF不同。
参考:
https://spark.apache.org/docs/latest/ml-features.html#tf-idf
https://spark.apache.org/docs/latest/ml-features.html#countvectorizer
答案 2 :(得分:3)
哈希技巧实际上是功能哈希的另一个名称。
我引用维基百科的定义:
在机器学习中,特征散列(也称为散列技巧)类似于内核技巧,是一种快速且节省空间的向量化特征的方法,即将任意特征转换为向量或矩阵中的索引。它的工作原理是将哈希函数应用于要素并将其哈希值直接用作索引,而不是在关联数组中查找索引。
您可以在this paper中详细了解相关信息。
实际上实际上对于空间有效的特征矢量化。
CountVectorizer
仅执行词汇提取,并转换为向量。
答案 3 :(得分:0)
答案很好。我只想强调一下API的不同之处:
CountVectorizer
must be fit
,它会产生一个新的
CountVectorizerModel
, which can transform
HashingTF
does not need to be fit
,HashingTF
实例可以直接进行变换例如
CountVectorizer(inputCol="words", outputCol="features")
.fit(original_df)
.transform(original_df)
vs:
HashingTF(inputCol="words", outputCol="features")
.transform(original_df)
在此API差异中,CountVectorizer
有一个额外的fit
API步骤。也许是因为CountVectorizer
做得更多(请参见已接受的答案):
CountVectorizer需要对数据进行额外的扫描以建立模型,并需要额外的内存来存储词汇(索引)。
我认为,如果您可以直接创建shown in example的CountVectorizerModel
,也可以跳过拟合步骤:
// alternatively, define CountVectorizerModel with a-priori vocabulary
val cvm = new CountVectorizerModel(Array("a", "b", "c"))
.setInputCol("words")
.setOutputCol("features")
cvModel.transform(df).show(false)
另一个很大的不同!
HashingTF
可能会创建冲突!这意味着将两个不同的特征/单词视为同一术语。 可接受的答案是这样的:
信息丢失的根源-在使用HashingTF的情况下,它是降低维数并可能发生冲突
这是一个明显的numFeatures
值(pow(2,4)
,pow(2,8)
)很低的问题;默认值非常高(pow(2,20)
)在此示例中:
wordsData = spark.createDataFrame([([
'one', 'two', 'three', 'four', 'five',
'six', 'seven', 'eight', 'nine', 'ten'],)], ['tokens'])
hashing = HashingTF(inputCol="tokens", outputCol="hashedValues", numFeatures=pow(2,4))
hashed_df = hashing.transform(wordsData)
hashed_df.show(truncate=False)
输出表明某些令牌出现了3次,即使所有令牌仅出现了1次:
+-----------------------------------------------------------+
|hashedValues |
+-----------------------------------------------------------+
|(16,[0,1,2,6,8,11,12,13],[1.0,1.0,1.0,3.0,1.0,1.0,1.0,1.0])|
+-----------------------------------------------------------+
(因此保留默认值,或increase your numFeatures
to try to avoid collisions :
这种[散列]方法避免了需要计算全局项到索引图的情况,这对于大型语料库可能是昂贵的,但是它会遭受潜在的散列冲突,在散列后,不同的原始特征可能成为同一个术语。为了减少冲突的机会,我们可以增加目标要素的维数,即哈希表的存储桶数。
其他一些API差异
CountVectorizer
构造函数(即在初始化时)支持额外的参数:
minDF
minTF
CountVectorizerModel
has a vocabulary
member,因此您可以看到生成的vocabulary
(如果您fit
的{{1}}尤其有用)
CountVectorizer
countVectorizerModel.vocabulary
>>> [u'one', u'two', ...]
是“可逆的”!使用其CountVectorizer
成员,该成员是将术语index映射到术语(sklearn
's CountVectorizer
does something similar)