我想使用spacy作爲NLP的在線服務。 每次用戶發出請求我調用腳本「my_script.py」是否可以在內存中保留空間以減少加載時間?
與開始:
from spacy.en import English
nlp = English()
時遇到的問題是,這兩條線需要10秒以上,是它可以保持英語()在公羊或其他一些選項,以減少加載時間不到一秒鐘?
我想使用spacy作爲NLP的在線服務。 每次用戶發出請求我調用腳本「my_script.py」是否可以在內存中保留空間以減少加載時間?
與開始:
from spacy.en import English
nlp = English()
時遇到的問題是,這兩條線需要10秒以上,是它可以保持英語()在公羊或其他一些選項,以減少加載時間不到一秒鐘?
你的目標應該是隻有一次初始化spacy模型。 使用一個類,並使spacy成爲一個類屬性。每當你使用它時,它就是屬性的同一個實例。
from spacy.en import English
class Spacy():
nlp = English()
我打電話給我想處理的文本作爲參數的腳本,我該怎麼做才能保持在後臺等待輸入?我想我會在這裏遇到同樣的問題。 –
@LuisRamonRamirezRodriguez這不是一個理想的做法。建議的替代方案是在uwsgi服務器上運行像gunicorn/uwsgi這樣的空間運行,以及通過休息apis進行交談。或者你可以讓spacy python進程作爲芹菜的工作者運行,你可以推送同步任務並獲得同步響應。 – DhruvPathak
所以這裏是一個黑客做到這一點(我個人會修改我的代碼,並沒有做到這一點,但由於您的要求沒有太大闡述我要去暗示這 - )
你必須有一個運行在線服務的守護進程。在守護進程中導入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
上述方法將會對首次啓動守護程序加載時間,之後,它只是一個函數調用。 希望這有助於!
你這裏根本的問題是發起爲每個請求一個新的腳本。不要爲每個請求運行腳本,而是在每個請求上從腳本內運行一個函數。
有多種方式來處理用戶請求。最簡單的方法是定期輪詢請求並將其添加到隊列中。異步框架對於這類工作也很有用。
這talk by raymond hettinger是一個很好的介紹併發在Python。
由於您使用Python可以編程某種工人(我認爲在某些時候,你需要規模也你的應用程序),其中這些初始化只進行一次!我們已經嘗試使用類似用例的Gearman,它運行良好。
乾杯
原理很簡單,你的「my_script.py」將適合工作者,你將不得不編程一個服務器,它將工作負載(客戶端查詢)分配給工作人員並收集工作結果。典型的主從式架構。 –
你說,你要啓動一個獨立的腳本(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
開始恢復。這可能比每次輸入/重建都要快,並且比使用進程間通信更簡單。
好的迴應matthias,這是做它的方式。 RAM本質上是易失性的且以流程爲中心,因此單個進程可以充當您的請求的代理並消除加載時間的開銷。 –
您沒有提供足夠的上下文。這個問題更多的是關於你的在線服務的設計而不是spacy,所以請詳細說明前者。 – Leon
顯示你的代碼_「用我想處理的文本調用腳本作爲參數」_,甚至更好地製作一個** MCVe **。閱讀關於閱讀,如何創建一個最小,完整和可驗證的示例:https://stackoverflow.com/help/mcve – stovfl