2016-09-02 4 views
2

JSON 파일에 저장되어있는 의학 저널의 문장에서 gensim을 사용하여 word2vec 모델을 생성하는 클러스터를 사용 중이며 메모리에 문제가 있습니다. 사용량이 너무 많습니다.매우 큰 Python 목록에 대한 과도한 메모리 사용량이 word2vec 용 JSON 90GB에서로드 됨

과제는 모든 문장의 누적 목록을 특정 연도까지 유지 한 다음 해당 연도의 word2vec 모델을 생성하는 것입니다. 그런 다음 내년의 문장을 누적 목록에 추가하고 모든 문장을 기반으로 해당 연도의 다른 모델을 생성하고 저장하십시오.

이 특정 클러스터의 I/O는 충분히 느리고 데이터가 충분히 크고 (메모리에 2/3가 읽는 데는 약 3 일이 걸림), 매년 JSON을 디스크에서 스트리밍하면 영원히 걸릴 수 있으므로 솔루션 90GB의 모든 JSON을 파이썬 목록의 메모리에로드하는 것이 었습니다. 이 작업에는 최대 256GB의 메모리를 사용할 수있는 권한이 있지만 필요한 경우 더 많은 메모리를 확보 할 수 있습니다.

내가 가지고있는 문제는 내가 기억이 부족하다는 것입니다. 파이썬이 메모리를 OS로 반환하지 않는 무료 목록을 구현하는 방식에 관한 다른 글을 읽었으며 문제의 일부가 될 수 있다고 생각하지만 확실하지 않습니다.

무료 목록이 문제가 될 수 있고 numpy가 많은 수의 요소에 대해 더 나은 구현을 가질 수 있다고 생각하여 문장의 누적 목록에서 문장의 누적 배열로 변경했습니다 (gensim은 문장이 단어/문자열 목록). 그러나 문장의 작은 하위 집합에서이 코드를 실행했고 약간 더 많은 메모리를 사용했기 때문에 진행 방법이 확실하지 않습니다.

누구나 이와 관련하여 경험이 있다면 도움을받을 수있어서 기쁩니다. 또한, 변경 될 수있는 것이 있다면 나에게도 알려 주셔서 감사합니다. 전체 코드는 다음과 같습니다 :

import ujson as json 
import os 
import sys 
import logging 
from gensim.models import word2vec 
import numpy as np 

PARAMETERS = { 
    'numfeatures': 250, 
    'minwordcount': 10, 
    'context': 7, 
    'downsampling': 0.0001, 
    'workers': 32 
} 

logger = logging.getLogger() 
handler = logging.StreamHandler() 
formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s') 
handler.setFormatter(formatter) 
logger.addHandler(handler) 
logger.setLevel(logging.INFO) 


def generate_and_save_model(cumulative_sentences, models_dir, year): 
    """ 
    Generates and saves the word2vec model for the given year 
    :param cumulative_sentences: The list of all sentences up to the current year 
    :param models_dir: The directory to save the models to 
    :param year: The current year of interest 
    :return: Nothing, only saves the model to disk 
    """ 
    cumulative_model = word2vec.Word2Vec(
     sentences=cumulative_sentences, 
     workers=PARAMETERS['workers'], 
     size=PARAMETERS['numfeatures'], 
     min_count=PARAMETERS['minwordcount'], 
     window=PARAMETERS['context'], 
     sample=PARAMETERS['downsampling'] 
    ) 
    cumulative_model.init_sims(replace=True) 
    cumulative_model.save(models_dir + 'medline_abstract_word2vec_' + year) 


def save_year_models(json_list, json_dir, models_dir, min_year, max_year): 
    """ 
    :param json_list: The list of json year_sentences file names 
    :param json_dir: The directory holding the the sentences json files 
    :param models_dir: The directory to serialize the models to 
    :param min_year: The minimum value of a year to generate a model for 
    :param max_year: The maximum value of a year to generate a model for 
    Goes year by year through each json of sentences, saving a cumulative word2vec 
    model for each year 
    """ 

    cumulative_sentences = np.array([]) 

    for json_file in json_list: 
     year = json_file[16:20] 

     # If this year is greater than the maximum, we're done creating models 
     if int(year) > max_year: 
      break 

     with open(json_dir + json_file, 'rb') as current_year_file: 
      cumulative_sentences = np.concatenate(
       (np.array(json.load(current_year_file)['sentences']), 
       cumulative_sentences) 
      ) 

     logger.info('COMPLETE: ' + year + ' sentences loaded') 
     logger.info('Cumulative length: ' + str(len(cumulative_sentences)) + ' sentences loaded') 
     sys.stdout.flush() 

     # If this year is less than our minimum, add its sentences to the list and continue 
     if int(year) < min_year: 
      continue 

     generate_and_save_model(cumulative_sentences, models_dir, year) 

     logger.info('COMPLETE: ' + year + ' model saved') 
     sys.stdout.flush() 


def main(): 
    json_dir = '/projects/chemotext/sentences_by_year/' 
    models_dir = '/projects/chemotext/medline_year_models/' 

    # By default, generate models for all years we have sentences for 
    minimum_year = 0 
    maximum_year = 9999 

    # If one command line argument is used 
    if len(sys.argv) == 2: 
     # Generate the model for only that year 
     minimum_year = int(sys.argv[1]) 
     maximum_year = int(sys.argv[1]) 

    # If two CL arguments are used 
    if len(sys.argv) == 3: 
     # Generate all models between the two year arguments, inclusive 
     minimum_year = int(sys.argv[1]) 
     maximum_year = int(sys.argv[2]) 

    # Sorting the list of files so that earlier years are first in the list 
    json_list = sorted(os.listdir(json_dir)) 

    save_year_models(json_list, json_dir, models_dir, minimum_year, maximum_year) 

if __name__ == '__main__': 
    main() 
+0

또한 유니 코드 헛소리와 관련이있을 수 있습니다. JSON 파일의 인코딩, 클러스터에서 사용중인 Python 버전 및 ['sys.maxunicode'] (http://stackoverflow.com/questions/1446347)에서 제공 한 번호를 부여 할 수 있습니까? – user6758673

+0

답변 해 주셔서 감사합니다. 인코딩이 문제가 될 수 있다고 생각하지 않았습니다. 'file -i thefile.json'을 입력하면 파일이 us-ascii로 인코딩되었음을 알 수 있습니다. 이 시스템은 Python 2.6.6을 실행합니다. 'sys.maxunicode'는 114111을 반환합니다. – sfirrin

+0

여러분의 파이썬 유니 코드 문자열이 문자 당 4 바이트를 차지한다는 것을 의미합니다. JSON에서는 주로 문자 당 1 바이트의 문자를 사용하지만 (유니 코드 문자의 공유에 따라 다르지만 유니 코드 문자는 이스케이프 시퀀스 '\ uXXXX'로 저장됩니다.) 적어도 이것이 부분적으로 과도한 메모리 사용을 설명한다고 생각합니다. – user6758673

답변

0

저는 모든 단어의 첫 번째 항목 만 명시 적으로 저장하여 코퍼스의 메모리 사용량을 크게 줄일 수 있어야한다고 생각합니다. 그 이후의 모든 항목은 첫 번째 항목에 대한 참조 만 저장하면됩니다. 이렇게하면 중복되는 문자열에 메모리를 낭비하지 않고 약간의 오버 헤드가 발생합니다. 당신이 시도 할 수있는 또 다른 것은 위 파이썬 3.3로 이동

class WordCache(object): 
    def __init__(self): 
     self.cache = {} 

    def filter(self, words): 
     for i, word in enumerate(words): 
      try: 
       words[i] = self.cache[word] 
      except KeyError: 
       self.cache[word] = word 
     return words 

cache = WordCache() 
... 
for sentence in json.load(current_year_file)['sentences']: 
    cumulative_sentences.append(cache.filter(sentence)) 

: 코드에서이 같은 것을 볼 수 있었다. 유니 코드 문자열을보다 효율적으로 메모리에 표현할 수 있습니다 (PEP 393 참조).