3 votes

Est-il possible de garder spacy en mémoire pour réduire le temps de chargement ?

Je veux utiliser spacy comme pour la PNL pour un service en ligne. Chaque fois qu'un utilisateur fait une demande, j'appelle le script "mon_script.py".

qui commence par :

from spacy.en import English
nlp = English()

Le problème que j'ai est que ces deux lignes prennent plus de 10 secondes, est-il possible de garder English() dans la ram ou une autre option pour réduire ce temps de chargement à moins d'une seconde ?

6voto

mfripp Points 46

Vous avez dit que vous vouliez lancer un script indépendant ( my_script.py ) chaque fois qu'une demande nous parvient. Ceci utilisera les capacités de spacy.en sans les frais généraux de chargement spacy.en . Avec cette approche, le système d'exploitation créera toujours un nouveau processus lorsque vous lancerez votre script. Il n'y a donc qu'un seul moyen d'éviter de charger spacy.en à chaque fois : avoir un processus séparé qui est déjà en cours d'exécution, avec spacy.en chargé, et que votre script communique avec ce processus. Le code ci-dessous montre une façon de faire cela. Cependant, comme d'autres l'ont dit, vous aurez probablement intérêt à modifier l'architecture de votre serveur de sorte que spacy.en est chargé dans votre serveur web (par exemple, si vous utilisez un serveur web basé sur Python).

La forme la plus courante de communication inter-processus est via les sockets TCP/IP. Le code ci-dessous met en œuvre un petit serveur qui garde spacy.en chargé et traite les demandes du client. Il dispose également d'un client qui transmet les demandes à ce serveur et reçoit les résultats en retour. C'est à vous de décider ce qu'il faut mettre dans ces transmissions.

Il existe également un troisième script. Puisque le client et le serveur ont tous deux besoin de fonctions d'envoi et de réception, ces fonctions sont dans un script partagé appelé comm.py . (Notez que le client et le serveur chargent chacun une copie distincte du fichier comm.py ; ils ne communiquent pas par le biais d'un module unique chargé dans la mémoire partagée).

Je suppose que les deux scripts sont exécutés sur la même machine. Si non, vous devrez mettre une copie de comm.py sur les deux machines et changez comm.server_host au nom de la machine ou à l'adresse IP du serveur.

Exécuter nlp_server.py en tant que processus d'arrière-plan (ou simplement dans une autre fenêtre de terminal pour les tests). Il attend les demandes, les traite et renvoie les résultats :

import comm
import socket
from spacy.en import English
nlp = English()

def process_connection(sock):
    print "processing transmission from client..."
    # receive data from the client
    data = comm.receive_data(sock)
    # do something with the data
    result = {"data received": data}
    # send the result back to the client
    comm.send_data(result, sock)
    # close the socket with this particular client
    sock.close()
    print "finished processing transmission from client..."

server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# open socket even if it was used recently (e.g., server restart)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind((comm.server_host, comm.server_port))
# queue up to 5 connections
server_sock.listen(5)
print "listening on port {}...".format(comm.server_port)
try:
    while True:
        # accept connections from clients
        (client_sock, address) = server_sock.accept()
        # process this connection 
        # (this could be launched in a separate thread or process)
        process_connection(client_sock)
except KeyboardInterrupt:
    print "Server process terminated."
finally:
    server_sock.close()

Chargement my_script.py comme un script à exécution rapide pour demander un résultat au serveur nlp (par exemple, python my_script.py here are some arguments ):

import socket, sys
import comm

# data can be whatever you want (even just sys.argv)
data = sys.argv

print "sending to server:"
print data

# send data to the server and receive a result
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# disable Nagle algorithm (probably only needed over a network) 
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True)
sock.connect((comm.server_host, comm.server_port))
comm.send_data(data, sock)
result = comm.receive_data(sock)
sock.close()

# do something with the result...
print "result from server:"
print result

comm.py contient du code qui est utilisé à la fois par le client et le serveur :

import sys, struct
import cPickle as pickle

# pick a port that is not used by any other process
server_port = 17001
server_host = '127.0.0.1' # localhost
message_size = 8192
# code to use with struct.pack to convert transmission size (int) 
# to a byte string
header_pack_code = '>I'
# number of bytes used to represent size of each transmission
# (corresponds to header_pack_code)
header_size = 4  

def send_data(data_object, sock):
    # serialize the data so it can be sent through a socket
    data_string = pickle.dumps(data_object, -1)
    data_len = len(data_string)
    # send a header showing the length, packed into 4 bytes
    sock.sendall(struct.pack(header_pack_code, data_len))
    # send the data
    sock.sendall(data_string)

def receive_data(sock):
    """ Receive a transmission via a socket, and convert it back into a binary object. """
    # This runs as a loop because the message may be broken into arbitrary-size chunks.
    # This assumes each transmission starts with a 4-byte binary header showing the size of the transmission.
    # See https://docs.python.org/3/howto/sockets.html
    # and http://code.activestate.com/recipes/408859-socketrecv-three-ways-to-turn-it-into-recvall/

    header_data = ''
    header_done = False
    # set dummy values to start the loop
    received_len = 0
    transmission_size = sys.maxint

    while received_len < transmission_size:
        sock_data = sock.recv(message_size)
        if not header_done:
            # still receiving header info
            header_data += sock_data
            if len(header_data) >= header_size:
                header_done = True
                # split the already-received data between header and body
                messages = [header_data[header_size:]]
                received_len = len(messages[0])
                header_data = header_data[:header_size]
                # find actual size of transmission
                transmission_size = struct.unpack(header_pack_code, header_data)[0]
        else:
            # already receiving data
            received_len += len(sock_data)
            messages.append(sock_data)

    # combine messages into a single string
    data_string = ''.join(messages)
    # convert to an object
    data_object = pickle.loads(data_string)
    return data_object

Remarque : vous devez vous assurer que le résultat envoyé par le serveur n'utilise que des structures de données natives (dicts, listes, chaînes de caractères, etc.). Si le résultat inclut un objet défini dans spacy.en alors le client importera automatiquement spacy.en lorsqu'il dépaquette le résultat, afin de fournir les méthodes de l'objet.

Cette configuration est très similaire au protocole HTTP (le serveur attend des connexions, le client se connecte, le client envoie une requête, le serveur envoie une réponse, les deux parties se déconnectent). Vous feriez donc mieux d'utiliser un serveur et un client HTTP standard au lieu de ce code personnalisé. Il s'agirait d'une "API RESTful", un terme très répandu de nos jours (à juste titre). L'utilisation de paquets HTTP standard vous épargnerait la gestion de votre propre code client/serveur, et vous pourriez même être en mesure d'appeler votre serveur de traitement des données directement à partir de votre serveur Web existant au lieu de lancer le programme my_script.py . Cependant, vous devrez traduire votre demande en quelque chose de compatible avec HTTP, par exemple, une demande GET ou POST, ou peut-être simplement une URL spécialement formatée.

Une autre option serait d'utiliser un paquet standard de communication interprocessus comme PyZMQ, redis, mpi4py ou peut-être zmq_object_exchanger. Voir cette question pour quelques idées : IPC Python efficace

Ou bien vous pouvez sauvegarder une copie de la spacy.en sur le disque en utilisant l'option dill paquet ( https://pypi.python.org/pypi/dill ) et le restaurer ensuite au début de my_script.py . Cela peut être plus rapide que de l'importer/reconstruire à chaque fois et plus simple que d'utiliser la communication interprocessus.

1voto

DhruvPathak Points 16181

Votre objectif devrait être d'initialiser les modèles spacy une seule fois. Utilisez une classe , et faites de spacy un attribut de classe. Chaque fois que vous l'utiliserez, il s'agira de la même instance de l'attribut.

from spacy.en import English

class Spacy():
      nlp = English()

1voto

Satyadev Points 483

Voici donc un hack pour faire cela (personnellement, je remanierais mon code et ne ferais pas cela, mais comme votre demande n'est pas très élaborée, je vais vous suggérer ceci).

Vous devez disposer d'un démon qui exécute le service en ligne. Importez spacy dans le daemon et passez-le comme paramètre au fichier qui fait le truc nlp.

Je remanierais mon code pour utiliser une classe comme indiqué dans la solution de @dhruv qui est beaucoup plus propre.

L'exemple suivant est une ébauche de la manière de procéder. (Très mauvais principe de programmation cependant).

Fichier1.py

def caller(a,np):
    return np.array(a)

Fichier2.py

import numpy as np 
from File1 import caller

z=caller(10,np)
print z

La méthode ci-dessus aura un temps de chargement la toute première fois que le démon est lancé, après cela, c'est juste un appel de fonction. J'espère que cela vous aidera !

1voto

Efron Licht Points 400

Votre problème fondamental ici est de lancer un nouveau script pour chaque requête. Au lieu de lancer un script pour chaque requête, lancez une fonction à l'intérieur du script pour chaque requête.

Il existe plusieurs façons de traiter les demandes des utilisateurs. La plus simple consiste à rechercher périodiquement les demandes et à les ajouter à une file d'attente. Le cadre asynchrone est également utile pour ce type de travail.

Este conférence de raymond hettinger est une excellente introduction à la concurrence en Python.

0voto

ML_TN Points 454

Puisque vous utilisez Python, vous pouvez programmer une sorte de workers (je pense qu'à un moment donné, vous devrez également faire évoluer votre application) où ces initialisations ne sont faites qu'une seule fois ! Nous avons essayé Gearman pour un cas d'utilisation similaire et cela fonctionne bien.

Cheers

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X