Spark中的HashingTF和CountVectorizer有什么区别?

时间:2016-02-04 16:06:54

标签: apache-spark apache-spark-mllib apache-spark-ml

尝试在Spark中进行文档分类。我不确定HashingTF中的散列是做什么的;它会牺牲任何准确性吗?我对此表示怀疑,但我不知道。 spark文档说它使用了"哈希技巧" ......只是工程师使用的另一个非常糟糕/令人困惑的命名的例子(我也有罪)。 CountVectorizer还需要设置词汇量大小,但它有另一个参数,一个阈值参数,可用于排除出现在文本语料库中某个阈值以下的单词或标记。我不明白这两个变形金刚之间的区别。使这一点很重要的是算法中的后续步骤。例如,如果我想在生成的tfidf矩阵上执行SVD,那么词汇量大小将决定SVD矩阵的大小,这会影响代码的运行时间,以及模型性能等。我一般有困难在API文档之外找到关于Spark Mllib的任何来源以及没有深度的非常简单的例子。

4 个答案:

答案 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)

输出如下

enter image description here

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(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 exampleCountVectorizerModel,也可以跳过拟合步骤:

// 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)
  • 的数组