tfidfvectorizer - text classification python
Python: reducir el uso de memoria del diccionario (6)
Estoy tratando de cargar un par de archivos en la memoria. Los archivos tienen cualquiera de los siguientes 3 formatos:
- cadena TAB int
- cadena TAB flotador
- int flotante TAB.
De hecho, son archivos statics de ngram, en caso de que esto ayude con la solución. Por ejemplo:
i_love TAB 10
love_you TAB 12
Actualmente, el pseudocódigo de lo que estoy haciendo ahora es
loadData(file):
data = {}
for line in file:
first, second = line.split(''/t'')
data[first] = int(second) #or float(second)
return data
Para gran sorpresa mía, mientras que el tamaño total de los archivos en el disco es de aproximadamente 21 mb, ¡cuando se carga en la memoria, el proceso requiere de 120 a 180 mb de memoria! (toda la aplicación python no carga ningún otro dato en la memoria).
Hay menos de 10 archivos, la mayoría de ellos se mantendrían estables en aproximadamente 50-80k líneas, a excepción de un archivo que actualmente tiene millones de líneas.
Entonces me gustaría pedir una estructura de técnica / datos para reducir el consumo de memoria:
- ¿Algún consejo para las técnicas de compresión?
- Si sigo usando dict, ¿hay alguna forma de reducir la memoria? ¿Es posible establecer el "factor de carga" como en Java para el dict de Python?
- Si tiene alguna otra estructura de datos, también estoy dispuesto a cambiar parte de la velocidad para reducir la memoria. Sin embargo, esta es una aplicación sensible al tiempo, por lo que una vez que los usuarios ingresen sus consultas, creo que no sería razonable tomar más de unos pocos segundos para devolver el resultado. Con respecto a esto, todavía me sorprende cómo Google logra hacer el Traductor de Google tan rápido: ¿deben estar usando muchas técnicas y mucho poder de servidores?
Muchas gracias. Espero su consejo.
1) SQLite en la memoria suena como una gran solución, te permitirá consultar tus datos más fácilmente una vez que está cargado, lo que es un placer
sqlite3.connect ('': memoria:'')
2) es probable que desee una tupla con nombre: estoy bastante seguro de que son más livianos que los diccionarios y puede acceder a las propiedades usando la notación de puntos (para la cual tengo una preferencia estética de todos modos).
http://docs.python.org/dev/library/collections
3) es posible que desee echar un vistazo a Redis: https://github.com/andymccurdy/redis-py
Es RÁPIDO y le permitirá persistir fácilmente, lo que significa que no tiene que cargar todo el conjunto cada vez que quiera usarlo.
4) un trie suena como una buena idea, pero agrega cierta complejidad teórica al final del trabajo. Sin embargo, puede usar Redis para implementarlo y almacenarlo, lo que aumentará aún más su velocidad.
Pero en general, las tuplas nombradas son probablemente el truco aquí.
En el disco tiene solo cadenas, al cargar a Python, el intérprete debe crear una estructura completa para cada cadena y para cada diccionario, además de la cadena misma.
No hay forma de reducir la memoria utilizada por los dicts, pero hay otras maneras de abordar el problema. Si está dispuesto a cambiar algo de velocidad por memoria, debería considerar almacenar y consultar las cadenas desde un archivo SQLite en lugar de cargar todo en diccionarios en la memoria.
No puedo ofrecer una estrategia completa que ayude a mejorar la huella de la memoria, pero creo que puede ser útil analizar qué se está llevando exactamente tanta memoria.
Si nos fijamos en la implementación de Python del diccionario (que es una implementación relativamente sencilla de una tabla hash), así como la implementación de los tipos de datos enteros de cadena y entero, por ejemplo here (específicamente object.h, intobject .h, stringobject.hy dictobject.h, así como los archivos * .c correspondientes en ../Objects), puede calcular con cierta precisión los requisitos de espacio esperados:
Un entero es un objeto de tamaño fijo, es decir, contiene un recuento de referencias, un puntero de tipo y el número entero real, en total típicamente al menos 12 bytes en un sistema de 32 bits y 24 bytes en un sistema de 64 bits, sin tener en cuenta el espacio adicional perdido a través de la alineación.
Un objeto de cadena es de tamaño variable, lo que significa que contiene
- conteo de referencia
- tipo puntero
- información de tamaño
- espacio para el código hash perezosamente calculado
- información de estado (por ejemplo, usada para cadenas internas )
- un puntero al contenido dinámico
en total, al menos 24 bytes en 32 bits o 60 bytes en 64 bits, sin incluir el espacio para la cadena en sí.
El diccionario en sí consiste en una cantidad de cubos, cada uno conteniendo
- el código hash del objeto actualmente almacenado (que no es predecible desde la posición del cubo debido a la estrategia de resolución de colisión utilizada)
- un puntero al objeto clave
- un puntero al objeto de valor
en total al menos 12 bytes en 32 bits y 24 bytes en 64 bits.
El diccionario comienza con 8 cubos vacíos y se redimensiona duplicando el número de entradas cada vez que se alcanza su capacidad.
Llevé a cabo una prueba con una lista de 46,461 cadenas únicas (337,670 bytes de tamaño de cadena concatenada), cada una asociada con un entero, similar a su configuración, en una máquina de 32 bits. De acuerdo con el cálculo anterior, esperaría una huella de memoria mínima de
- 46,461 * (24 + 12) bytes = 1,6 MB para las combinaciones de cadena / entero
- 337,670 = 0.3 MB para los contenidos de la cadena
- 65,536 * 12 bytes = 1,6 MB para los depósitos de hash (después de cambiar el tamaño 13 veces)
en total 2,65 MB. (Para un sistema de 64 bits, el cálculo correspondiente arroja 5.5 MB).
Cuando se ejecuta el intérprete de Python inactivo, su huella de acuerdo con la ps
-tool es de 4,6 MB. Entonces, el consumo total de memoria esperado después de crear el diccionario es de aproximadamente 4.6 + 2.65 = 7.25 MB. La huella de memoria real (según ps
) en mi prueba fue de 7,6 MB. Supongo que el extra ca. 0.35 MB fueron consumidos por la sobrecarga generada a través de la estrategia de asignación de memoria de Python (para arenas de memoria, etc.)
Por supuesto, muchas personas ahora señalarán que mi uso de ps
para medir la huella de memoria es inexacto y mis suposiciones sobre el tamaño de los tipos de puntero y enteros en los sistemas de 32 y 64 bits pueden ser incorrectos en muchos sistemas específicos. Concedido.
Pero, sin embargo, las conclusiones clave , creo, son éstas:
- La implementación del diccionario Python consume una cantidad sorprendentemente pequeña de memoria
- Pero el espacio que ocupan los muchos int y (en particular) los objetos de cadena , para recuentos de referencia, códigos hash precalculados, etc., es más de lo que creería al principio
- Apenas hay una manera de evitar la sobrecarga de memoria , siempre y cuando use Python y desee las cadenas y los enteros representados como objetos individuales, al menos no veo cómo se podría hacer eso.
- Puede valer la pena buscar (o implementarse) una extensión de Python-C que implementa un hash que almacena claves y valores como punteros C (en lugar de objetos de Python). No sé si eso existe; pero creo que podría hacerse y podría reducir la huella de memoria en más de la mitad.
Puede reemplazar dict con blist.sorteddict por el blist.sorteddict de registro (n) sin la sobrecarga de memoria. Es conveniente porque se comporta exactamente como un diccionario, es decir, implementa todos sus métodos, por lo que solo tiene que cambiar el tipo inicial.
Suena como una estructura de datos Trie ( http://en.m.wikipedia.org/wiki/Trie ) que se adapta mejor a su deseo de eficiencia de la memoria.
Actualización: la eficacia de la memoria de python dict se ha planteado como un problema, aunque fue rechazada por la lib estándar dada la disponibilidad de bibliotecas de terceros. Ver: http://bugs.python.org/issue9520
Si intenta almacenar datos numéricos de manera compacta en python en la memoria, su mejor solución es probablemente Numpy.
Numpy ( http://numpy.org ) asigna estructuras de datos usando estructuras C nativas. La mayoría de sus estructuras de datos presuponen que está almacenando un solo tipo de datos, por lo que no es para todas las situaciones (es posible que necesite almacenar valores nulos, etc.), pero puede ser muy, muy, muy rápido y tan compacto como podrías pedir Mucha ciencia se hace con eso (ver también: SciPy).
Por supuesto, hay otra opción: zlib , si tiene:
- Amplios ciclos de CPU, y
- MUCHOS datos que no caben en la memoria
Podrías declarar una ''página'' de datos (no importa lo grande que quieras) como una matriz, leer los datos, almacenarlos en la matriz, comprimirlos y luego leer más datos, hasta que tengas todos los datos en la memoria querer.
Luego, itere sobre las páginas, descomprima, convierta de nuevo a una matriz y realice sus operaciones según sea necesario. Por ejemplo:
def arrayToBlob(self, inArray):
a = array.array(''f'', inArray)
return a.tostring()
def blobToArray(self, blob, suppressWarning=False):
try:
out = array.array(''f'', [])
out.fromstring(blob)
except Exception, e:
if not suppressWarning:
msg = "Exception: blob2array, err: %s, in: %s" % (e, blob)
self.log.warning(msg)
raise Exception, msg
return out
Una vez que tenga los datos como un blob, puede pasar este blob a zlib y comprimir los datos. Si tiene muchos valores repetidos, este blob puede estar muy comprimido.
Por supuesto, es más lento que mantenerlo todo sin comprimir, pero si no puede caber en la memoria, sus opciones están limitadas para empezar.
Incluso con la compresión, es posible que no todos quepan en la memoria, en cuyo punto es posible que tenga que escribir las páginas comprimidas como cadenas o encurtidos, etc.
¡Buena suerte!