Je construis un chatbot donc je dois vectoriser l'entrée de l'utilisateur en utilisant Word2Vec.
J'utilise un modèle pré-formé avec 3 millions de mots de Google (GoogleNews-vectors-negative300).
Je charge donc le modèle avec Gensim:
import gensim
model = gensim.models.KeyedVectors.load_Word2vec_format('GoogleNews-vectors-negative300.bin', binary=True)
Le problème est qu’il faut environ 2 minutes pour charger le modèle. Je ne peux pas laisser l'utilisateur attendre aussi longtemps.
Alors, que puis-je faire pour accélérer le temps de chargement?
J'ai pensé mettre chacun des 3 millions de mots et le vecteur correspondant dans une base de données MongoDB. Cela accélérerait certainement les choses, mais l'intuition me dit que ce n'est pas une bonne idée.
Dans les versions récentes de Gensim, vous pouvez charger un sous-ensemble à partir du début du fichier en utilisant le paramètre optionnel limit
dans load_Word2vec_format()
. (Les vecteurs de GoogleNews semblent être dans l’ordre le plus fréquent, c’est-à-dire que les premiers N sont le sous-ensemble de la taille N que vous souhaiteriez. Utilisez donc limit=500000
pour obtenir les vecteurs les plus fréquents de 500 000 mots - encore assez vocabulaire volumineux - gain de 5/6ème de la mémoire/du temps de chargement)
Cela peut donc aider un peu. Toutefois, si vous rechargez chaque demande Web, vous aurez encore du mal à charger la vitesse liée aux entrées-sorties et à la surcharge de mémoire redondante liée au stockage de chaque rechargement.
Il existe quelques astuces que vous pouvez utiliser en combinaison pour vous aider.
Notez qu'après le chargement de tels vecteurs dans leur format d'origine Word2vec.c, vous pouvez les enregistrer à nouveau à l'aide de la fonction save()
native de gensim. Si vous enregistrez les fichiers non compressés et que le tableau de sauvegarde est suffisamment grand (et que l'ensemble GoogleNews l'est définitivement), le tableau de sauvegarde est vidé dans un fichier séparé au format binaire brut. Ce fichier peut ensuite être mappé en mémoire à partir du disque, en utilisant l'option [load(filename, mmap='r')][1]
native de gensim.
Au début, cela rendra la charge plus rapide. Plutôt que de lire toute la matrice à partir du disque, le système d’exploitation mappera simplement les régions d’adresses virtuelles sur les données du disque. Ainsi, lorsque le code aura accès à ces emplacements mémoire, les plages nécessaires seront lues -de-disque. Jusqu'ici tout va bien!
Toutefois, si vous effectuez des opérations typiques telles que most_similar()
, vous devrez faire face à de grands décalages, un peu plus tard. En effet, cette opération nécessite à la fois une analyse et un calcul initiaux sur tous les vecteurs (au premier appel, pour créer des vecteurs normalisés par unité de longueur pour chaque mot), puis un autre analyse et un calcul sur tous les vecteurs normés (sur chaque appel, pour trouver les N vecteurs les plus similaires). Ces accès de numérisation complète parcourent toute la matrice en page dans la RAM, ce qui coûte à nouveau quelques minutes d’IO disque.
Ce que vous voulez, c'est éviter de faire de manière redondante cette normalisation d'unités et de payer le coût IO une seule fois. Cela nécessite de garder les vecteurs en mémoire pour pouvoir les réutiliser par toutes les requêtes Web ultérieures (ou même plusieurs requêtes Web parallèles). Heureusement, la cartographie de la mémoire peut aussi aider, mais avec quelques étapes de préparation supplémentaires.
Commencez par charger les vecteurs au format Word2vec.c, avec load_Word2vec_format()
. Ensuite, utilisez model.init_sims(replace=True)
pour forcer la normalisation d'unités de manière destructive sur place (en supprimant les vecteurs non normalisés).
Enregistrez ensuite le modèle dans un nouveau préfixe de fichier: model.save ("GoogleNews-vectors-gensim-normed.bin"). (Notez que cela crée en fait plusieurs fichiers sur le disque qui doivent être conservés ensemble pour que le modèle rechargé.)
Maintenant, nous allons créer un court programme Python qui sert à la fois à charger en mémoire les vecteurs, et forcer le tableau complet en mémoire. Nous voulons également que ce programme soit suspendu jusqu'à sa terminaison externe (maintien de la cartographie vivante), et veillez à ne pas recalculer les vecteurs déjà normés. Cela nécessite une autre astuce car les KeyedVectors chargés ne savent pas en réalité que les vecteurs sont normés. (Généralement, seuls les vecteurs bruts sont enregistrés et les versions normées sont recalculées chaque fois que nécessaire.)
En gros, ce qui suit devrait fonctionner:
from gensim.models import KeyedVectors
from threading import Semaphore
model = KeyedVectors.load('GoogleNews-vectors-gensim-normed.bin', mmap='r')
model.syn0norm = model.syn0 # prevent recalc of normed vectors
model.most_similar('stuff') # any Word will do: just to page all in
Semaphore(0).acquire() # just hang until process killed
Cela prendra encore un certain temps, mais ne doit être effectué qu'une seule fois, avant/en dehors de toute demande Web. Tant que le processus est actif, les vecteurs restent en mémoire. De plus, à moins que/jusqu'à ce qu'il y ait une autre pression de mémoire virtuelle, les vecteurs doivent rester chargés en mémoire. C'est important pour la suite.
Enfin, dans votre code de traitement des demandes Web, vous pouvez désormais procéder comme suit:
model = KeyedVectors.load('GoogleNews-vectors-gensim-normed.bin', mmap='r')
model.syn0norm = model.syn0 # prevent recalc of normed vectors
# … plus whatever else you wanted to do with the model
Plusieurs processus peuvent partager des fichiers mappés en mémoire en lecture seule. (C'est-à-dire qu'une fois que le système d'exploitation sait que le fichier X se trouve dans RAM à une certaine position, tout autre processus souhaitant également une version mappée en lecture seule de X sera invité à réutiliser ces données, à cette position. .)
Ainsi, ce Web-requête load()
, et tous les accès ultérieurs, peuvent tous réutiliser les données que le processus précédent a déjà introduites dans l'espace d'adressage et la mémoire active. Les opérations nécessitant des calculs de similarité par rapport à chaque vecteur prendront toujours le temps nécessaire pour accéder à plusieurs Go de RAM et effectuer les calculs/tri, mais ne nécessiteront plus de disque IO supplémentaire ni de nouvelle normalisation redondante.
Si le système est confronté à une autre pression de mémoire, les plages de la matrice risquent de manquer de mémoire jusqu’à la lecture suivante. Et si la machine ne dispose pas de la RAM pour charger complètement les vecteurs, chaque analyse nécessitera un mélange de pagination entrante et sortante et une performance frustrante, peu importe quoi. (Dans un tel cas: obtenez plus de RAM ou travaillez avec un jeu de vecteurs plus petit.)
Mais si vous avez suffisamment de RAM, cela finit par rendre le code original/naturel de chargement et d'utilisation directement "naturel" d'une manière assez rapide, sans interface de service Web supplémentaire, car les fonctions de mémoire partagée de fichier de la machine comme interface de service.
J'ai ce problème chaque fois que j'utilise l'ensemble de données google news. Le problème est qu'il y a beaucoup plus de mots dans le jeu de données que vous n'en aurez jamais besoin. Il y a une quantité énorme de fautes de frappe et quoi. Ce que je fais est de scanner les données sur lesquelles je travaille, de construire un dictionnaire des mots les plus courants, par exemple, les 50 000 mots, d’obtenir les vecteurs avec Gensim et de sauvegarder le dictionnaire. Le chargement de ce dictionnaire prend une demi-seconde au lieu de 2 minutes.
Si vous ne possédez aucun jeu de données spécifique, vous pouvez utiliser les 50 ou 100 000 mots les plus courants d'un grand jeu de données, tel que news datas de WMT pour vous aider à démarrer.
D'autres options sont de toujours garder Gensim en marche. Vous pouvez créer un FIFO pour un script exécutant Gensim. Le script agit comme un "serveur" capable de lire un fichier dans lequel un "client" écrit, surveillant les requêtes de vecteurs.
Je pense que la solution la plus élégante consiste à exécuter un service Web fournissant des intégrations Word. Découvrez l'API Word2vec à titre d'exemple. Après l’installation, obtenir l’intégration pour "restaurant" est aussi simple que:
curl http://127.0.0.1:5000/Word2vec/model?word=restaurant
J'aime vraiment la bibliothèque d'intégration de vzhong. https://github.com/vzhong/embeddings
Il stocke les vecteurs Word dans SQLite, ce qui signifie que nous n'avons pas besoin de charger le modèle mais d'extraire simplement les vecteurs correspondants à partir de DB: D