2017-04-22 98 views
3

我想使用spacy作为NLP的在线服务。 每次用户发出请求我调用脚本“my_script.py”是否可以在内存中保留空间以减少加载时间?

与开始:

from spacy.en import English 
nlp = English() 

时遇到的问题是,这两条线需要10秒以上,是它可以保持英语()在公羊或其他一些选项,以减少加载时间不到一秒钟?

+2

您没有提供足够的上下文。这个问题更多的是关于你的在线服务的设计而不是spacy,所以请详细说明前者。 – Leon

+1

显示你的代码_“用我想处理的文本调用脚本作为参数”_,甚至更好地制作一个** MCVe **。阅读关于阅读,如何创建一个最小,完整和可验证的示例:https://stackoverflow.com/help/mcve – stovfl

回答

1

你的目标应该是只有一次初始化spacy模型。 使用一个类,并使spacy成为一个类属性。每当你使用它时,它就是属性的同一个实例。

from spacy.en import English 

class Spacy(): 
     nlp = English() 
+0

我打电话给我想处理的文本作为参数的脚本,我该怎么做才能保持在后台等待输入?我想我会在这里遇到同样的问题。 –

+0

@LuisRamonRamirezRodriguez这不是一个理想的做法。建议的替代方案是在uwsgi服务器上运行像gunicorn/uwsgi这样的空间运行,以及通过休息apis进行交谈。或者你可以让spacy python进程作为芹菜的工作者运行,你可以推送同步任务并获得同步响应。 – DhruvPathak

1

所以这里是一个黑客做到这一点(我个人会修改我的代码,并没有做到这一点,但由于您的要求没有太大阐述我要去暗示这 - )

你必须有一个运行在线服务的守护进程。在守护进程中导入spacy并将其作为参数传递给执行nlp的文件。

我重构我的代码使用由@dhruv在方案中提到的一类是干净多了。

下面的例子是如何去的东西的草图。 (非常糟糕的编程原理虽然)。

File1.py

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

File2.py

import numpy as np 
from File1 import caller 

z=caller(10,np) 
print z 

上述方法将会对首次启动守护程序加载时间,之后,它只是一个函数调用。 希望这有助于!

1

你这里根本的问题是发起为每个请求一个新的脚本。不要为每个请求运行脚本,而是在每个请求上从脚本内运行一个函数。

有多种方式来处理用户请求。最简单的方法是定期轮询请求并将其添加到队列中。异步框架对于这类工作也很有用。

talk by raymond hettinger是一个很好的介绍并发在Python。

0

由于您使用Python可以编程某种工人(我认为在某些时候,你需要规模也你的应用程序),其中这些初始化只进行一次!我们已经尝试使用类似用例的Gearman,它运行良好。

干杯

+0

原理很简单,你的“my_script.py”将适合工作者,你将不得不编程一个服务器,它将工作负载(客户端查询)分配给工作人员并收集工作结果。典型的主从式架构。 –

4

你说,你要启动一个独立的脚本(my_script.py)每当一个请求进来,这将使用capabilites从spacy.en不加载spacy.en的开销。通过这种方法,操作系统将在您启动脚本时始终创建一个新进程。所以只有一种方法可以避免每次加载spacy.en:有一个单独的进程已经在运行,加载了spacy.en,并让脚本与该进程进行通信。下面的代码显示了一种方法。但是,正如其他人所说的,您可能会因更改服务器体系结构而受益,因此spacy.en已加载到您的Web服务器中(例如,使用基于Python的Web服务器)。

进程间通信的最常见形式是通过TCP/IP套接字。下面的代码实现了一个小型服务器,它可以保持spacy.en的加载并处理来自客户端的请求。它还有一个客户端,它将请求发送到该服务器并返回结果。这取决于你决定将什么放入这些传输中。

还有第三个脚本。由于客户端和服务器都需要发送和接收功能,因此这些功能位于名为comm.py的共享脚本中。 (请注意,在客户端和服务器的每个加载的comm.py单独副本;它们不通过加载到共享存储器的单个模块进行通信。)

我假定这两个脚本在同一台机器上运行。如果不是,则需要在两台计算机上放置comm.py的副本,并将comm.server_host更改为服务器的计算机名称或IP地址。

运行nlp_server.py作为后台进程(或只是在用于测试的不同的终端窗口)。这也是在等待的请求,对其进行处理并将结果发送回:

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() 

负载my_script.py作为一个快速运行的脚本从NLP服务器(例如,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包含用于由客户端和服务器代码:

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 

注意:您应该确保从服务器发送的结果仅使用本机数据结构(字符串,列表,字符串等)。如果结果包含spacy.en中定义的对象,则客户端在解包结果时会自动导入spacy.en,以提供对象的方法。

此设置与HTTP协议非常相似(服务器等待连接,客户端连接,客户端发送请求,服务器发送响应,双方断开连接)。所以你可能会更好地使用标准的HTTP服务器和客户端来代替这个自定义代码。这将是一个“RESTful API”,这是目前流行的术语(有充分的理由)。使用标准HTTP软件包可以节省管理自己的客户端/服务器代码的麻烦,甚至可以直接从现有的Web服务器调用数据处理服务器,而无需启动my_script.py。但是,您必须将您的请求转换为与HTTP兼容的内容,例如GET或POST请求,或者可能只是特殊格式的URL。

另一种选择是使用标准进程间通信包,例如PyZMQ,redis,mpi4py或者zmq_object_exchanger。看到这个问题的一些想法:Efficient Python IPC

或者您可以使用dill包(https://pypi.python.org/pypi/dill)保存在磁盘上spacy.en对象的副本,然后将其在my_script.py开始恢复。这可能比每次输入/重建都要快,并且比使用进程间通信更简单。

+0

好的回应matthias,这是做它的方式。 RAM本质上是易失性的且以流程为中心,因此单个进程可以充当您的请求的代理并消除加载时间的开销。 –

相关问题