Como eu construo uma matriz numpy a partir de um gerador?

166

Como criar uma matriz numpy a partir de um objeto gerador?

Deixe-me ilustrar o problema:

>>> import numpy
>>> def gimme():
...   for x in xrange(10):
...     yield x
...
>>> gimme()
<generator object at 0x28a1758>
>>> list(gimme())
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> numpy.array(xrange(10))
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> numpy.array(gimme())
array(<generator object at 0x28a1758>, dtype=object)
>>> numpy.array(list(gimme()))
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

Neste exemplo, gimme()é o gerador cuja saída eu gostaria de transformar em uma matriz. No entanto, o construtor do array não interage com o gerador, simplesmente armazena o próprio gerador. O comportamento que desejo é o denumpy.array(list(gimme())) , mas não quero gastar a memória de ter a lista intermediária e a matriz final na memória ao mesmo tempo. Existe uma maneira mais eficiente em termos de espaço?

saffsd
fonte
6
Esta é uma questão interessante. Eu vim através disso from numpy import *; print any(False for i in range(1))- o que sombreia o built-in any()e produz o resultado oposto (como eu sei agora).
moooeeeep
4
@moooeeeep isso é terrível. se numpynão puder (ou não quiser) tratar os geradores como o Python, pelo menos deve gerar uma exceção quando receber um gerador como argumento.
máximo
1
@max eu pisei exatamente no meu. Aparentemente, isso foi levantado na lista NumPy (e anterior ), concluindo que isso não será alterado para gerar exceções e deve-se sempre usar espaços para nome.
21414 alexei

Respostas:

128

Arrays numpy exigem que seu comprimento seja definido explicitamente no momento da criação, ao contrário das listas python. Isso é necessário para que o espaço para cada item possa ser alocado consecutivamente na memória. A alocação consecutiva é o principal recurso de matrizes numpy: isso combinado à implementação de código nativo permite que as operações nelas executem muito mais rapidamente do que as listas regulares.

Tendo isso em mente, é tecnicamente impossível pegar um objeto gerador e transformá-lo em uma matriz, a menos que você:

  1. pode prever quantos elementos ele produzirá quando executado:

    my_array = numpy.empty(predict_length())
    for i, el in enumerate(gimme()): my_array[i] = el
  2. estão dispostos a armazenar seus elementos em uma lista intermediária:

    my_array = numpy.array(list(gimme()))
  3. pode criar dois geradores idênticos, percorrer o primeiro para encontrar o comprimento total, inicializar a matriz e, em seguida, percorrer o gerador novamente para encontrar cada elemento:

    length = sum(1 for el in gimme())
    my_array = numpy.empty(length)
    for i, el in enumerate(gimme()): my_array[i] = el

1 é provavelmente o que você está procurando. 2 é ineficiente em espaço e 3 é ineficiente em tempo (você precisa passar pelo gerador duas vezes).

shsmurfy
fonte
11
O builtin array.arrayé uma lista não vinculada contínua e você pode simplesmente array.array('f', generator). Dizer que é impossível é enganoso. É apenas alocação dinâmica.
Cuadue
1
Por que numpy.array não faz a alocação de memória da mesma maneira que o array.array embutido, como diz Cuadue. Qual é o tradeof? Eu pergunto porque há memória alocada contígua nos dois exemplos. Ou não?
Jgomo3
3
numpy supõe que seus tamanhos de matriz não sejam alterados. Ele depende muito de visualizações diferentes do mesmo pedaço de memória, portanto, permitir que matrizes sejam expandidas e realocadas exigiria uma camada adicional de indireção para permitir visualizações, por exemplo.
Joeln
2
Usar vazio é um pouco mais rápido. Como você inicializará os valores de qualquer maneira, não será necessário fazer isso duas vezes.
Kaushik Ghose
Veja também a resposta de @ dhill abaixo, que é mais rápida que 1. #
Bill
206

Um google por trás desse resultado de stackoverflow, descobri que existe um numpy.fromiter(data, dtype, count). O padrão count=-1pega todos os elementos do iterável. Requer que um dtypeseja definido explicitamente. No meu caso, isso funcionou:

numpy.fromiter(something.generate(from_this_input), float)

dhill
fonte
como você aplicaria isso à pergunta? numpy.fromiter(gimme(), float, count=-1)não funciona. O que somethingsignifica?
Matthias 009
1
@ Matthias009 numpy.fromiter(gimme(), float, count=-1)funciona para mim.
precisa saber é o seguinte
14
Um tópico explicando por que fromiterfunciona apenas em matrizes 1D: mail.scipy.org/pipermail/numpy-discussion/2007-August/… .
máximo
2
fwiw, count=-1não precisa ser especificado, pois é o padrão.
askewchan
5
Se você souber o comprimento do iterável anteriormente, especifique o countpara melhorar o desempenho. Desta forma, ele aloca a memória antes de o encher com valores em vez de redimensionamento on demand (consulte a documentação de numpy.fromiter)
Eddy
15

Enquanto você pode criar uma matriz 1D a partir de um gerador com numpy.fromiter(), você pode criar uma matriz ND a partir de um gerador com numpy.stack:

>>> mygen = (np.ones((5, 3)) for _ in range(10))
>>> x = numpy.stack(mygen)
>>> x.shape
(10, 5, 3)

Também funciona para matrizes 1D:

>>> numpy.stack(2*i for i in range(10))
array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])

Observe que numpy.stackestá consumindo internamente o gerador e criando uma lista intermediária com arrays = [asanyarray(arr) for arr in arrays]. A implementação pode ser encontrada aqui .

mdeff
fonte
1
Esta é uma solução elegante, obrigado por apontar. Mas parece ser um pouco mais lento (no meu aplicativo) do que usar np.array(tuple(mygen)). Aqui estão os resultados dos testes: em %timeit np.stack(permutations(range(10), 7)) 1 loop, best of 3: 1.9 s per loopcomparação com%timeit np.array(tuple(permutations(range(10), 7))) 1 loop, best of 3: 427 ms per loop
Bill
13
Isso parece ótimo e funciona para mim. Mas com Numpy 1.16.1 eu recebo este aviso:FutureWarning: arrays to stack must be passed as a "sequence" type such as list or tuple. Support for non-sequence iterables such as generators is deprecated as of NumPy 1.16 and will raise an error in the future.
Joseph Sheedy
6

Um pouco tangencial, mas se o seu gerador é uma lista de compreensão, você pode usar numpy.wherepara obter o resultado com mais eficiência (eu descobri isso no meu próprio código depois de ver este post)

Benjamin Horstman
fonte
0

As funções vstack , hstack e dstack podem assumir como geradores de entrada que produzem matrizes multidimensionais.

Mike R
fonte
3
Você pode dar um exemplo, caso os links mudem ou algo assim? :)
Ari Cooper-Davis
Essas funções podem receber um gerador de matrizes, não um gerador de valores
retnikt