【发布时间】:2019-09-14 09:39:12
【问题描述】:
1 - 问题
我在 python 上使用“spacy”来进行文本文档的词形还原。 有 500,000 个文档的大小高达 20 Mb 的纯文本。
问题如下:spacy memory 消耗会随着时间的推移而增长,直到整个内存都被使用。
2 - 背景
我的硬件配置: CPU:英特尔 I7-8700K 3.7 GHz(12 核) 内存:16 Gb 固态硬盘:1 TB GPU 是板载的,但不用于此任务
我正在使用“多处理”将任务分配给多个进程(工作人员)。 每个工作人员都会收到一份要处理的文档列表。 主进程对子进程进行监控。 我在每个子进程中启动一次“spacy”,并使用这个 spacy 实例来处理 worker 中的整个文档列表。
内存跟踪说明如下:
[内存跟踪 - 前 10 名]
/opt/develop/virtualenv/lib/python3.6/site-packages/thinc/neural/mem.py:68:大小=45.1 MiB,计数=99,平均=467 KiB
/opt/develop/virtualenv/lib/python3.6/posixpath.py:149:大小=40.3 MiB,计数=694225,平均=61 B
:487: 大小=9550 KiB,计数=77746,平均=126 B
/opt/develop/virtualenv/lib/python3.6/site-packages/dawg_python/wrapper.py:33:大小=7901 KiB,计数=6,平均=1317 KiB
/opt/develop/virtualenv/lib/python3.6/site-packages/spacy/lang/en/lemmatizer/_nouns.py:7114:大小=5273 KiB,计数=57494,平均=94 B
prepare_docs04.py:372:大小=4189 KiB,计数=1,平均=4189 KiB
/opt/develop/virtualenv/lib/python3.6/site-packages/dawg_python/wrapper.py:93:大小=3949 KiB,计数=5,平均=790 KiB
/usr/lib/python3.6/json/decoder.py:355:大小=1837 KiB,计数=20456,平均=92 B
/opt/develop/virtualenv/lib/python3.6/site-packages/spacy/lang/en/lemmatizer/_adjectives.py:2828:大小=1704 KiB,计数=20976,平均=83 B
prepare_docs04.py:373:大小=1633 KiB,计数=1,平均=1633 KiB
3 - 期望
我看到了一个很好的建议来构建一个分离的服务器-客户端解决方案[这里]Is possible to keep spacy in memory to reduce the load time?
是否可以使用“多处理”方法控制内存消耗?
4 - 代码
这是我的代码的简化版本:
import os, subprocess, spacy, sys, tracemalloc
from multiprocessing import Pipe, Process, Lock
from time import sleep
# START: memory trace
tracemalloc.start()
# Load spacy
spacyMorph = spacy.load("en_core_web_sm")
#
# Get word's lemma
#
def getLemma(word):
global spacyMorph
lemmaOutput = spacyMorph(str(word))
return lemmaOutput
#
# Worker's logic
#
def workerNormalize(lock, conn, params):
documentCount = 1
for filenameRaw in params[1]:
documentTotal = len(params[1])
documentID = int(os.path.basename(filenameRaw).split('.')[0])
# Send to the main process the worker's current progress
if not lock is None:
lock.acquire()
try:
statusMessage = "WORKING:{:d},{:d},".format(documentID, documentCount)
conn.send(statusMessage)
documentCount += 1
finally:
lock.release()
else:
print(statusMessage)
# ----------------
# Some code is excluded for clarity sake
# I've got a "wordList" from file "filenameRaw"
# ----------------
wordCount = 1
wordTotalCount = len(wordList)
for word in wordList:
lemma = getLemma(word)
wordCount += 1
# ----------------
# Then I collect all lemmas and save it to another text file
# ----------------
# Here I'm trying to reduce memory usage
del wordList
del word
gc.collect()
if __name__ == '__main__':
lock = Lock()
processList = []
# ----------------
# Some code is excluded for clarity sake
# Here I'm getting full list of files "fileTotalList" which I need to lemmatize
# ----------------
while cursorEnd < (docTotalCount + stepSize):
fileList = fileTotalList[cursorStart:cursorEnd]
# ----------------
# Create workers and populate it with list of files to process
# ----------------
processData = {}
processData['total'] = len(fileList) # worker total progress
processData['count'] = 0 # worker documents done count
processData['currentDocID'] = 0 # current document ID the worker is working on
processData['comment'] = '' # additional comment (optional)
processData['con_parent'], processData['con_child'] = Pipe(duplex=False)
processName = 'worker ' + str(count) + " at " + str(cursorStart)
processData['handler'] = Process(target=workerNormalize, name=processName, args=(lock, processData['con_child'], [processName, fileList]))
processList.append(processData)
processData['handler'].start()
cursorStart = cursorEnd
cursorEnd += stepSize
count += 1
# ----------------
# Run the monitor to look after the workers
# ----------------
while True:
runningCount = 0
#Worker communication format:
#STATUS:COMMENTS
#STATUS:
#- WORKING - worker is working
#- CLOSED - worker has finished his job and closed pipe-connection
#COMMENTS:
#- for WORKING status:
#DOCID,COUNT,COMMENTS
#DOCID - current document ID the worker is working on
#COUNT - count of done documents
#COMMENTS - additional comments (optional)
# ----------------
# Run through the list of workers ...
# ----------------
for i, process in enumerate(processList):
if process['handler'].is_alive():
runningCount += 1
# ----------------
# .. and check if there is somethng in the PIPE
# ----------------
if process['con_parent'].poll():
try:
message = process['con_parent'].recv()
status = message.split(':')[0]
comment = message.split(':')[1]
# ----------------
# Some code is excluded for clarity sake
# Update worker's information and progress in "processList"
# ----------------
except EOFError:
print("EOF----")
# ----------------
# Some code is excluded for clarity sake
# Here I draw some progress lines per workers
# ----------------
else:
# worker has finished his job. Close the connection.
process['con_parent'].close()
# Whait for some time and monitor again
sleep(PARAM['MONITOR_REFRESH_FREQUENCY'])
print("================")
print("**** DONE ! ****")
print("================")
# ----------------
# Here I'm measuring memory usage to find the most "gluttonous" part of the code
# ----------------
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("[ Memory trace - Top 10 ]")
for stat in top_stats[:10]:
print(stat)
'''
【问题讨论】:
-
您似乎在我不希望它们发生的情况下一起使用锁定和队列。在不知道
params可能是什么的情况下,也很难实际运行此代码。一般来说,我希望你会在我的 repospacy-extreme中找到一些有用的信息,它处理使用 spaCy 时的内存问题。
标签: python-3.x spacy