Pergunta

Estou usando o seguinte código, com geradores aninhados, para iterar sobre um documento de texto e retornar exemplos de treinamento usando get_train_minibatch(). Gostaria de persistir (Pickle) os geradores, para que eu possa voltar ao mesmo lugar no documento de texto. No entanto, você não pode devolver geradores.

  • Existe uma solução alternativa simples, para que eu possa salvar minha posição e começar de volta onde parei? Talvez eu possa fazer get_train_example() Um singleton, então eu não tenho vários geradores por aí. Então, eu poderia fazer uma variável global neste módulo que acompanha o quão longe get_train_example() é.

  • Você tem uma sugestão melhor (mais limpa), para me permitir persistir este gerador?

Editar: mais duas idéias:

  • Posso adicionar uma variável/método de membro ao gerador, para que eu possa chamar o gerador.tell () e encontrar o local do arquivo? Porque então, na próxima vez que eu criar o gerador, posso pedir para procurar esse local. Essa ideia parece mais simples de tudo.

  • Posso criar uma classe e fazer com que o local do arquivo seja uma variável de membro e, em seguida, o gerador criado dentro da classe e atualize a variável de membro do localização do arquivo cada vez que ele produz? Porque então posso saber a que distância o arquivo.

]

Aqui está o código:

def get_train_example():
    for l in open(HYPERPARAMETERS["TRAIN_SENTENCES"]):
        prevwords = []
        for w in string.split(l):
            w = string.strip(w)
            id = None
            prevwords.append(wordmap.id(w))
            if len(prevwords) >= HYPERPARAMETERS["WINDOW_SIZE"]:
                yield prevwords[-HYPERPARAMETERS["WINDOW_SIZE"]:]

def get_train_minibatch():
    minibatch = []
    for e in get_train_example():
        minibatch.append(e)
        if len(minibatch) >= HYPERPARAMETERS["MINIBATCH SIZE"]:
            assert len(minibatch) == HYPERPARAMETERS["MINIBATCH SIZE"]
            yield minibatch
            minibatch = []
Foi útil?

Solução

O código a seguir deve fazer mais ou menos o que você deseja. A primeira classe define algo que age como um arquivo, mas pode ser em conserva. (Quando você desenrola-o, ele reabre o arquivo e procura o local onde estava quando você o decapou). A segunda classe é um iterador que gera Word Windows.

class PickleableFile(object):
    def __init__(self, filename, mode='rb'):
        self.filename = filename
        self.mode = mode
        self.file = open(filename, mode)
    def __getstate__(self):
        state = dict(filename=self.filename, mode=self.mode,
                     closed=self.file.closed)
        if not self.file.closed:
            state['filepos'] = self.file.tell()
        return state
    def __setstate__(self, state):
        self.filename = state['filename']
        self.mode = state['mode']
        self.file = open(self.filename, self.mode)
        if state['closed']: self.file.close()
        else: self.file.seek(state['filepos'])
    def __getattr__(self, attr):
        return getattr(self.file, attr)

class WordWindowReader:
    def __init__(self, filenames, window_size):
        self.filenames = filenames
        self.window_size = window_size
        self.filenum = 0
        self.stream = None
        self.filepos = 0
        self.prevwords = []
        self.current_line = []

    def __iter__(self):
        return self

    def next(self):
        # Read through files until we have a non-empty current line.
        while not self.current_line:
            if self.stream is None:
                if self.filenum >= len(self.filenames):
                    raise StopIteration
                else:
                    self.stream = PickleableFile(self.filenames[self.filenum])
                    self.stream.seek(self.filepos)
                    self.prevwords = []
            line = self.stream.readline()
            self.filepos = self.stream.tell()
            if line == '':
                # End of file.
                self.stream = None
                self.filenum += 1
                self.filepos = 0
            else:
                # Reverse line so we can pop off words.
                self.current_line = line.split()[::-1]

        # Get the first word of the current line, and add it to
        # prevwords.  Truncate prevwords when necessary.
        word = self.current_line.pop()
        self.prevwords.append(word)
        if len(self.prevwords) > self.window_size:
            self.prevwords = self.prevwords[-self.window_size:]

        # If we have enough words, then return a word window;
        # otherwise, go on to the next word.
        if len(self.prevwords) == self.window_size:
            return self.prevwords
        else:
            return self.next()

Outras dicas

Você pode criar um objeto de iterador padrão, ele não será tão conveniente quanto o gerador; Você precisa armazenar o estado do iterador no Instituto (para que seja em conserva) e definir uma função seguinte () para retornar o próximo objeto:

class TrainExampleIterator (object):
    def __init__(self):
        # set up internal state here
        pass
    def next(self):
        # return next item here
        pass

O protocolo iterador é simples assim, definindo o .next() O método em um objeto é tudo o que você precisa transmitir para loops etc.

No Python 3, o Protocolo Iterador usa o __next__ Método (em vez disso (um pouco mais consistente).

Isso pode não ser uma opção para você, mas python sem pilha (http://stackless.com) faz Permita que você apodreça coisas como funções e geradores, sob certas condições. Isso vai funcionar:

Em foo.py:

def foo():
    with open('foo.txt') as fi:
        buffer = fi.read()
    del fi
    for line in buffer.split('\n'):
        yield line

Em foo.txt:

line1
line2
line3
line4
line5

No intérprete:

Python 2.6 Stackless 3.1b3 060516 (python-2.6:66737:66749M, Oct  2 2008, 18:31:31) 
IPython 0.9.1 -- An enhanced Interactive Python.

In [1]: import foo

In [2]: g = foo.foo()

In [3]: g.next()
Out[3]: 'line1'

In [4]: import pickle

In [5]: p = pickle.dumps(g)

In [6]: g2 = pickle.loads(p)

In [7]: g2.next()
Out[7]: 'line2'

Algumas coisas a serem observadas: você devo buffer o conteúdo do arquivo e exclua o objeto de arquivo. Isso significa que o conteúdo do arquivo será duplicado no picles.

  1. Converter o gerador em uma classe em que o código do gerador é o __iter__ método
  2. Adicionar __getstate__ e __setstate__ Métodos para a classe, para lidar com o decapagem. Lembre -se de que você não pode aprisionar objetos de arquivo. Então __setstate__ terá que reabrir os arquivos, conforme necessário.

Eu descrevo este método em mais profundidade, com código de amostra, aqui.

Você pode tentar criar um objeto chamável:

class TrainExampleGenerator:

    def __call__(self):
        for l in open(HYPERPARAMETERS["TRAIN_SENTENCES"]):
            prevwords = []
            for w in string.split(l):
                w = string.strip(w)
                id = None
                prevwords.append(wordmap.id(w))
                if len(prevwords) >= HYPERPARAMETERS["WINDOW_SIZE"]:
                    yield prevwords[-HYPERPARAMETERS["WINDOW_SIZE"]:]

get_train_example = TrainExampleGenerator()

Agora você pode transformar todo o estado que precisa ser salvo em campos de objeto e expô -los a aprisionar. Esta é uma ideia básica e espero que isso ajude, mas ainda não tentei isso.

ATUALIZAR:
Infelizmente, não consegui entregar minha ideia. Exemplo fornecido não é uma solução completa. Você vê, TrainExampleGenerator não tem estado. Você deve projetar esse estado e disponibilizá -lo para decapagem. E __call__ O método deve usar e modificar esse estado para que o gerador devolva a partir da posição determinada pelo estado do objeto. Obviamente, o próprio gerador não poderá ser apagado. Mas TrainExampleGenerator Será possível para apagar e você poderá recriar o gerador com ele Até parece O próprio gerador foi em conserva.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top