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

rdlzhqv9  于 2023-06-24  发布在  Apache
关注(0)|答案(4)|浏览(228)

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

pxiryf3j

pxiryf3j1#

几个重要的区别:

  • partiallyreversibleCountVectorizervs unreversibleHashingTF)-由于哈希是不可逆的,因此无法从哈希向量恢复原始输入。从另一方面,具有模型(索引)的计数向量可以用于恢复无序输入。因此,使用散列输入创建的模型可能更难以解释和监视。
    *内存和计算开销-HashingTF只需要一次数据扫描,除了原始输入和向量之外,不需要额外的内存。CountVectorizer需要对数据进行额外的扫描以构建模型,并需要额外的内存来存储词汇表(索引)。在unigram语言模型的情况下,这通常不是问题,但在更高的n-gram的情况下,它可能非常昂贵或不可行。
  • 哈希取决于向量的大小、哈希函数和文档。计数取决于向量、训练语料库和文档的大小。
    *信息丢失的来源-在HashingTF的情况下,它是具有可能的冲突的维度缩减。CountVectorizer丢弃不常用的令牌。它如何影响下游模型取决于特定的用例和数据。
yyhrrdl8

yyhrrdl82#

根据Spark 2.1.0文档,
HashingTF和CountVectorizer都可用于生成词频向量。

哈希TF

HashingTF是一个Transformer,它采用术语集并将这些集合转换为固定长度的特征向量。在文本处理中,“术语集”可能是一个词袋。**HashingTF使用哈希技巧。通过应用散列函数将原始特征Map到索引(项)。这里使用的哈希函数是MurmurHash 3。然后基于Map的索引计算词频。这种方法避免了计算全局术语到索引Map的需要,这对于大型语料库来说可能是昂贵的,但它遭受了潜在的哈希冲突,其中不同的原始特征在哈希之后可能变成相同的术语。
为了减少碰撞的机会,我们可以增加目标特征维度,即哈希表的桶数。由于使用简单的模来将散列函数转换为列索引,因此建议使用2的幂作为特征维度,否则特征将不会均匀地Map到列。默认特征尺寸为2^18= 262,144。可选的二进制切换参数控制词频计数。当设置为true时,所有非零频率计数均设置为1。这对于建模二进制而非整数计数的离散概率模型尤其有用。

计数向量器
*CountVectorizer和CountVectorizerModel旨在帮助将文本文档集合转换为令牌计数的向量。当先验字典不可用时,CountVectorizer可以用作Estimator以提取 * 词汇表 ,并生成CountVectorizer模型。该模型为文档 * 在词汇表 * 上生成稀疏表示,然后可以将其传递给其他算法,如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)

输出如下

散列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

7lrncoxx

7lrncoxx3#

哈希技巧实际上是特征哈希的另一个名称。
我引用维基百科的定义:
在机器学习中,特征散列,也称为散列技巧,与内核技巧类似,是一种快速且空间高效的特征向量化方法,即将任意特征转换为向量或矩阵中的索引。它的工作原理是将散列函数应用于特征,并直接使用其散列值作为索引,而不是在关联数组中查找索引。
您可以在this paper中阅读更多信息。
所以实际上是为了空间有效的特征矢量化。
CountVectorizer只执行词汇提取,并转换为Vectors。

o4tp2gmn

o4tp2gmn4#

答案很棒。我只想再提几点:

API差异:CountVectorizer必须匹配

  • CountVectorizer必须是fit,这会产生一个新的CountVectorizerModel,它可以是transform
  • vs HashingTF不需要是fitHashingTF示例可以直接转换

比如说,

CountVectorizer(inputCol="words", outputCol="features")
      .fit(original_df)
      .transform(original_df)

对比:

HashingTF(inputCol="words", outputCol="features")
      .transform(original_df)

在这个API中,CountVectorizer有一个额外的fit API步骤。也许这是因为CountVectorizer做了额外的工作来创建一个词汇表(参见接受的答案):
CountVectorizer需要对数据进行额外扫描以构建模型,并需要额外内存来存储词汇表(索引)。
我认为如果你能够直接“使用先验词汇表”创建你的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可能会产生冲突!这意味着两个不同的特征/词被视为 * 相同的术语 。接受的答案是这样的:
......信息丢失的来源-在HashingTF的情况下,它是维度降低,具有 * 可能的冲突

这对于相对 * 低 *(pow(2,4)pow(2,8))的显式numFeatures值尤其是个问题;默认值是相当 * 高 *(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)

+-----------------------------------------------------------+
|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])|
+-----------------------------------------------------------+

输出包含16个“哈希桶”(因为我使用了numFeatures=pow(2,4)

...16...

虽然我的输入有10个唯一的令牌,* 输出只创建8个唯一的哈希 *(由于哈希冲突);

....v-------8x-------v....
...[0,1,2,6,8,11,12,13]...

哈希冲突意味着3个不同的令牌被赋予相同的哈希,(即使所有令牌都是唯一的;且仅应发生1x)

...---------------v
... [1.0,1.0,1.0,3.0,1.0,1.0,1.0,1.0] ...

(So保留默认值numFeaturesincrease your numFeatures to try to avoid collisions
这种[散列]方法避免了计算全局术语到索引Map的需要,这对于大型语料库来说可能是昂贵的,但它遭受潜在的散列冲突,其中不同的原始特征在散列后可能变成相同的术语。为了减少碰撞的机会,我们可以增加目标特征维度,即哈希表的桶数。

其他API差异

  • CountVectorizer构造函数(即初始化时)支持额外的参数:
  • minDF
  • minTF
  • 等等
  • CountVectorizerModel有一个vocabulary成员,所以你可以看到生成的vocabulary(如果你fit你的CountVectorizer特别有用):
  • countVectorizerModel.vocabulary
  • >>> [u'one', u'two', ...]
  • CountVectorizer是“可逆的”,正如主要答案所说!使用它的vocabulary成员,它是一个将索引项Map到( sklearn 's CountVectorizer does something similar)项的数组

相关问题