web-dev-qa-db-fra.com

Quelle est la différence entre HashingTF et CountVectorizer dans Spark?

Essayer de faire une classification doc dans Spark. Je ne suis pas sûr de ce que fait le hachage dans HashingTF; sacrifie-t-il la précision? J'en doute, mais je ne sais pas. Le document spark dit qu'il utilise le "truc de hachage" ... juste un autre exemple de dénomination vraiment mauvaise/déroutante utilisée par les ingénieurs (je suis également coupable). CountVectorizer nécessite également de définir le vocabulaire taille, mais il a un autre paramètre, un paramètre de seuil qui peut être utilisé pour exclure des mots ou des jetons qui apparaissent en dessous d'un certain seuil dans le corpus de texte. Je ne comprends pas la différence entre ces deux transformateurs. Ce qui rend cela important, ce sont les étapes suivantes de Par exemple, si je voulais effectuer SVD sur la matrice tfidf résultante, alors la taille du vocabulaire déterminera la taille de la matrice pour SVD, ce qui a un impact sur le temps d'exécution du code et les performances du modèle, etc. difficulté en général à trouver une source sur Spark Mllib au-delà de la documentation de l'API et des exemples vraiment triviaux sans profondeur.

22
Kai

Quelques différences importantes:

  • partiellement réversible (CountVectorizer) vs irréversible (HashingTF) - puisque le hachage n'est pas réversible, vous ne pouvez pas restaurer l'entrée d'origine à partir d'un vecteur de hachage. D'autre part, le vecteur de comptage avec modèle (index) peut être utilisé pour restaurer une entrée non ordonnée. Par conséquent, les modèles créés à l'aide d'une entrée hachée peuvent être beaucoup plus difficiles à interpréter et à surveiller.
  • mémoire et surcharge de calcul - HashingTF ne nécessite qu'une seule analyse de données et aucune mémoire supplémentaire au-delà de l'entrée et du vecteur d'origine. CountVectorizer nécessite une analyse supplémentaire des données pour construire un modèle et une mémoire supplémentaire pour stocker le vocabulaire (index). Dans le cas d'un modèle de langage unigramme, ce n'est généralement pas un problème, mais dans le cas de n-grammes plus élevés, il peut être prohibitif ou impossible.
  • hachage dépend de une taille du vecteur, une fonction de hachage et un document. Le comptage dépend de la taille du vecteur, du corpus de formation et d'un document.
  • source de la perte d'informations - en cas de HashingTF c'est une réduction de dimensionnalité avec des collisions possibles. CountVectorizer supprime les jetons peu fréquents. La manière dont elle affecte les modèles en aval dépend d'un cas d'utilisation et de données particuliers.
24
zero323

Selon la documentation Spark 2.1.0,

HashingTF et CountVectorizer peuvent tous deux être utilisés pour générer le terme vecteurs de fréquence.

HashingTF

HashingTF est un transformateur qui prend des ensembles de termes et convertit ces ensembles en vecteurs d'entités de longueur fixe. Dans le traitement de texte, un "ensemble de termes" peut être un sac de mots. HashingTF utilise l'astuce de hachage. Une fonction brute est mappée dans un index (terme) en appliquant une fonction de hachage. La fonction de hachage utilisée ici est MurmurHash 3. Ensuite, les fréquences des termes sont calculées en fonction des indices mappés. Cette approche évite d'avoir à calculer une carte globale de termes à index, ce qui peut être coûteux pour un grand corpus, mais elle souffre de collisions de hachage potentielles, où différentes caractéristiques brutes peuvent devenir le même terme après le hachage.

Pour réduire les risques de collision, nous pouvons augmenter la dimension de l'entité cible, c'est-à-dire le nombre de compartiments de la table de hachage. Puisqu'un simple modulo est utilisé pour transformer la fonction de hachage en un index de colonne, il est conseillé d'utiliser une puissance de deux comme dimension d'entité, sinon les entités ne seront pas mappées uniformément aux colonnes. La dimension d'entité par défaut est 2 ^ 18 = 262.144. Un paramètre à bascule binaire facultatif contrôle le nombre de fréquences des termes. Lorsqu'il est défini sur true, tous les nombres de fréquences non nuls sont définis sur 1. Ceci est particulièrement utile pour les modèles probabilistes discrets qui modélisent des nombres binaires plutôt qu'entiers.

CountVectorizer

CountVectorizer et CountVectorizerModel visent à aider à convertir une collection de documents texte en vecteurs de comptage de jetons. Lorsqu'un dictionnaire a priori n'est pas disponible, CountVectorizer peut être utilisé comme estimateur pour extraire le vocabulaire, et génère un CountVectorizerModel. Le modèle produit des représentations clairsemées pour les documents sur le vocabulaire, qui peuvent ensuite être passés à d'autres algorithmes comme LDA .

Pendant le processus d'ajustement, CountVectorizer sélectionnera les mots de vocabSize supérieurs classés par fréquence terminologique dans le corpus. Un paramètre facultatif minDF affecte également le processus d'ajustement en spécifiant le nombre minimum (ou la fraction si <1,0) de documents dans lesquels un terme doit apparaître pour être inclus dans le vocabulaire. Un autre paramètre de basculement binaire facultatif contrôle le vecteur de sortie. Si la valeur est vraie, tous les nombres non nuls sont définis sur 1. Ceci est particulièrement utile pour les modèles probabilistes discrets qui modélisent les nombres binaires, plutôt qu'entiers.

Exemple de code

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)

La sortie est comme ci-dessous

enter image description here

Hashing TF manque le vocabulaire qui est essentiel pour des techniques comme LDA. Pour cela, il faut utiliser la fonction CountVectorizer. Quelle que soit la taille du vocabulaire, la fonction CountVectorizer estime le terme fréquence sans aucune approximation impliquée contrairement à HashingTF.

Référence:

https://spark.Apache.org/docs/latest/ml-features.html#tf-idf

https://spark.Apache.org/docs/latest/ml-features.html#countvectorizer

13
prashanth

L'astuce de hachage est en fait l'autre nom du hachage de fonctionnalité.

Je cite la définition de Wikipedia:

Dans l'apprentissage automatique, le hachage de fonctionnalités, également connu sous le nom de truc de hachage, par analogie avec le truc du noyau, est un moyen rapide et peu encombrant de vectoriser des fonctionnalités, c'est-à-dire de transformer des fonctionnalités arbitraires en indices dans un vecteur ou une matrice. Il fonctionne en appliquant une fonction de hachage aux entités et en utilisant directement leurs valeurs de hachage comme indices, plutôt que de rechercher les indices dans un tableau associatif.

Vous pouvez en savoir plus à ce sujet dans cet article .

Donc, en fait, en fait, pour la vectorisation des fonctionnalités économes en espace.

Tandis que CountVectorizer effectue juste une extraction de vocabulaire et se transforme en vecteurs.

5
eliasah

Les réponses sont excellentes. Je veux juste souligner cette différence d'API:

Par exemple

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

contre:

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

Dans cette différence d'API, CountVectorizer comporte une étape supplémentaire d'API fit. C'est peut-être parce que CountVectorizer fait un travail supplémentaire (voir la réponse acceptée):

CountVectorizer nécessite une analyse supplémentaire des données pour créer un modèle et une mémoire supplémentaire pour stocker le vocabulaire (index).

Je pense que vous pouvez également ignorer l'étape d'ajustement si vous êtes en mesure de créer votre CountVectorizerModel directement, comme montré dans l'exemple :

// alternatively, define CountVectorizerModel with a-priori vocabulary
val cvm = new CountVectorizerModel(Array("a", "b", "c"))
  .setInputCol("words")
  .setOutputCol("features")

cvModel.transform(df).show(false)

Une autre grande différence!

  • HashingTF peut créer des collisions ! Cela signifie que deux caractéristiques/mots différents sont traités comme le même terme.
  • La réponse acceptée dit ceci:

    une source de perte d'information - en cas de HashingTF c'est la réduction de dimensionnalité avec des collisions possibles

C'est particulièrement un problème avec une faible valeur explicite de numFeatures (pow(2,4), pow(2,8)); la valeur par défaut est assez élevée (pow(2,20)) Dans cet exemple:

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

La sortie contient 16 "seaux de hachage" (parce que j'ai utilisé numFeatures=pow(2,4))

...16...

Alors que mon entrée avait 10 jetons uniques, la sortie ne crée que 8 hachages uniques (en raison de collisions de hachage);

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

Les collisions de hachage signifient que 3 jetons distincts reçoivent le même hachage (même si tous les jetons sont uniques et ne doivent se produire que 1x)

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

(Laissez donc la valeur par défaut, ou augmentez votre numFeatures pour essayer d'éviter les collisions :

Cette approche [de hachage] évite d'avoir à calculer une carte globale de termes à index, ce qui peut être coûteux pour un grand corpus, mais elle souffre de collisions de hachage potentielles, où différentes caractéristiques brutes peuvent devenir le même terme après le hachage. Pour réduire les risques de collision, nous pouvons augmenter la dimension de l'entité cible, c'est-à-dire le nombre de compartiments de la table de hachage.

Quelques autres différences d'API

  • CountVectorizer constructeur (c'est-à-dire lors de l'initialisation) prend en charge des paramètres supplémentaires:

    • minDF
    • minTF
    • etc...
  • CountVectorizerModel a un membre vocabulary , donc vous pouvez voir le vocabulary généré (particulièrement utile si vous fit votre CountVectorizer):

    • countVectorizerModel.vocabulary
    • >>> [u'one', u'two', ...]
  • CountVectorizer est "réversible" comme le dit la réponse principale! Utilisez son membre vocabulary, qui est un tableau mappant l'index du terme, au terme (le sklearn de CountVectorizer fait quelque chose de similaire)
1
The Red Pea