curso
Tutorial de Pickle en Python: Serialización de objetos
Introducción a la serialización de objetos
Practica la carga de un archivo Pickled de Python con este ejercicio práctico.
¿Estás cansado de volver a ejecutar tu código Python cada vez que tienes que acceder a un marco de datos, una variable o un modelo de machine learning creados previamente?
La serialización de objetos puede ser la solución que buscas.
Es el proceso de almacenar una estructura de datos en memoria para poder cargarla o transmitirla cuando sea necesario sin perder su estado actual.
Aquí tienes un sencillo diagrama que explica cómo funciona la serialización:
Imagen del autor
En Python, trabajamos con estructuras de datos de alto nivel, como listas, tuplas y conjuntos. Sin embargo, cuando queremos almacenar estos objetos en la memoria, hay que convertirlos en una secuencia de bytes que el ordenador pueda entender. Este proceso se llama serialización.
La próxima vez que queramos acceder a la misma estructura de datos, esta secuencia de bytes debe convertirse de nuevo en el objeto de alto nivel en un proceso conocido como deserialización.
Podemos utilizar formatos como JSON, XML, HDF5 y Pickle para la serialización. En este tutorial, aprenderemos sobre la biblioteca Pickle de Python para la serialización. Cubriremos sus usos y comprenderemos cuándo debes elegir Pickle en lugar de otros formatos de serialización.
Por último, aprenderemos a utilizar la biblioteca Pickle de Python para serializar listas, diccionarios, marcos de datos Pandas, modelos de machine learning, etc.
¿Por qué necesitamos la serialización de objetos?
Antes de empezar con Python Pickle, entendamos por qué es tan importante la serialización de objetos.
Quizá te preguntes por qué no podemos simplemente guardar las estructuras de datos en un archivo de texto y volver a acceder a ellas cuando sea necesario, en lugar de tener que serializarlas.
Veamos un ejemplo sencillo para comprender las ventajas de la serialización.
Aquí tienes un diccionario anidado que contiene información sobre el alumno, como el nombre, la edad y el sexo:
students = {
'Student 1': {
'Name': "Alice", 'Age' :10, 'Grade':4,
},
'Student 2': {
'Name':'Bob', 'Age':11, 'Grade':5
},
'Student 3': {
'Name':'Elena', 'Age':14, 'Grade':8
}
}
Examinemos el tipo de datos del objeto "students":
type(students)
dict
Ahora que hemos confirmado que el objeto alumno es de tipo diccionario, procedamos a escribirlo en un archivo de texto sin serialización:
with open('student_info.txt','w') as data:
data.write(str(students))
Observa que, como solo podemos escribir objetos de cadena en archivos de texto, hemos convertido el diccionario en una cadena utilizando la función str(). Esto significa que se pierde el estado original de nuestro diccionario.
Ahora, leamos el diccionario, imprimámoslo y comprobemos de nuevo su tipo:
with open("student_info.txt", 'r') as f:
for students in f:
print(students)
type(students)
str
El diccionario anidado se imprime ahora como una cadena, y devolverá un error cuando intentemos acceder a sus claves o valores.
Aquí es donde entra en juego la serialización.
Cuando se trata con tipos de datos más complejos, como diccionarios, marcos de datos y listas anidadas, la serialización permite al usuario preservar el estado original del objeto sin perder ninguna información relevante.
Más adelante en este artículo, aprenderemos a almacenar este mismo diccionario en un archivo utilizando Pickle y a deserializarlo utilizando la biblioteca. Puedes leer más sobre la comprensión de diccionario en Python en otro tutorial.
Introducción a Pickle en Python
El módulo Pickle de Python es un popular formato utilizado para serializar y deserializar tipos de datos. Este formato es nativo de Python, lo que significa que los objetos Pickle no pueden cargarse utilizando ningún otro lenguaje de programación.
Pickle tiene sus propias ventajas e inconvenientes en comparación con otros formatos de serialización.
Ventajas de utilizar Pickle para serializar objetos
- A diferencia de formatos de serialización como JSON, que no pueden gestionar tuplas y objetos datetime, Pickle puede serializar casi todos los tipos de datos integrados de Python de uso común. También conserva el estado exacto del objeto, cosa que JSON no puede hacer.
- Pickle también es una buena opción para almacenar estructuras recursivas, ya que solo escribe un objeto una vez.
- Pickle ofrece flexibilidad a la hora de deserializar objetos. Puedes guardar fácilmente distintas variables en un archivo Pickle y volver a cargarlas en otra sesión de Python, recuperando tus datos exactamente como estaban sin tener que editar tu código.
Desventajas de utilizar Pickle
- Pickle no es seguro porque puede ejecutar callables Python maliciosos para construir objetos. Al deserializar un objeto, Pickle no puede distinguir entre un callable malicioso y uno no malicioso. Debido a esto, los usuarios pueden acabar ejecutando código arbitrario durante la deserialización.
- Como ya se ha mencionado, Pickle es un módulo específico de Python, y puede que te cueste deserializar objetos pickled cuando utilices un lenguaje diferente.
- Según varias pruebas comparativas, Pickle parece ser más lento y produce valores serializados mayores que formatos como JSON y ApacheThrift.
Guardar y cargar objetos con la función Pickle Dump Python y la función Load
Pickle utiliza las siguientes funciones para serializar y deserializar objetos Python:
pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None)
pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None)
pickle.load(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)
pickle.loads(data, /, *, fix_imports=True, encoding=”ASCII”, errors=”strict”, buffers=None)
Las funciones Pickle dump() y dumps() se utilizan para serializar un objeto. La única diferencia entre ellas es que dump() escribe los datos en un archivo, mientras que dumps() los representa como objeto byte.
Del mismo modo, load() lee objetos pickled de un archivo, mientras que loads() los deserializa de un objeto tipo bytes.
En este tutorial, utilizaremos las funciones dump() y load() para pickle objetos Python a un archivo y unplickle esos objetos.
Serializar estructuras de datos Python con Pickle
Listas
En primer lugar, vamos a crear una lista Python sencilla:
import pickle
student_names = ['Alice','Bob','Elena','Jane','Kyle']
Ahora, abramos un archivo de texto, escribamos la lista en él utilizando la función dumps() y cerremos el archivo:
with open('student_file.pkl', 'wb') as f: # open a text file
pickle.dump(student_names, f) # serialize the list
Primero creamos un archivo llamado "student_file.pkl". La extensión no tiene por qué ser .pkl. Puedes ponerle el nombre que quieras, y el archivo se creará igualmente. Sin embargo, es una práctica recomendada utilizar la extensión .pkl para que te recuerde que se trata de un archivo Pickle.
Además, fíjate en que hemos abierto el archivo en modo "wb". Esto significa que estás escribiendo el archivo en modo binario, de modo que los datos se devuelven en un objeto bytes.
A continuación, utilizamos la función dump() para almacenar la lista "student_names" en el archivo.
Por último, puedes cerrar el archivo con la siguiente línea de código:
f.close()
Ahora, vamos a deserializar el archivo e imprimir la lista:
with open('student_file.pkl', 'rb') as f:
student_names_loaded = pickle.load(f) # deserialize using load()
print(student_names_loaded) # print student names
El resultado del código anterior debería ser el siguiente:
['Alice', 'Bob', 'Elena', 'Jane', 'Kyle']
Observa que, para deserializar el archivo, tenemos que utilizar el modo "rb", que significa read binary. A continuación, unpickle el objeto mediante la función load(), tras lo cual podemos almacenar los datos en una variable diferente y utilizarlos como creamos conveniente.
Comprobemos ahora el tipo de datos de la lista que acabamos de unpickle:
type(student_names_loaded)
list
¡Estupendo! Hemos preservado el estado original y el tipo de datos de esta lista.
Matrices Numpy
Ahora vamos a intentar serializar y deserializar un tipo de datos algo más complejo: las matrices Numpy.
Vamos a crear primero una matriz de unos de 10 por 10:
import numpy as np
numpy_array = np.ones((10,10)) # 10x10 array
A continuación, igual que hicimos antes, vamos a invocar la función dump() para serializar esta matriz en un archivo:
with open('my_array.pkl','wb') as f:
pickle.dump(numpy_array, f)
Por último, unpickle esta matriz y comprobemos su forma y tipo de datos para asegurarnos de que ha conservado su estado original:
with open('my_array.pkl','rb') as f:
unpickled_array = pickle.load(f)
print('Array shape: '+str(unpickled_array.shape))
print('Data type: '+str(type(unpickled_array)))
Deberías obtener la siguiente salida:
Array shape: (10, 10)
Data type: <class 'numpy.ndarray'>
El objeto unpickled es una matriz Numpy de 10 × 10, que tiene la misma forma y tipo de datos que el objeto que acabamos de serializar.
DataFrames pandas
Un dataframe es un objeto con el que trabajan a diario los científicos de datos. La forma más popular de cargar y guardar un dataframe Pandas es leerlo y escribirlo como archivo .csv. Aprende más sobre la importación de datos en nuestro tutorial de pandas read_csv().
Sin embargo, este proceso es más lento que la serialización y puede llegar a consumir mucho tiempo si el dataframe es grande.
Vamos a contrastar la eficiencia de guardar y cargar un dataframe pandas utilizando Pickle frente a .csv comparando el tiempo que tarda cada uno.
En primer lugar, vamos a crear un dataframe pandas con 100 000 filas de datos falsos:
import pandas as pd
import numpy as np
# Set random seed
np.random.seed(123)
data = {'Column1': np.random.randint(0, 10, size=100000),
'Column2': np.random.choice(['A', 'B', 'C'], size=100000),
'Column3': np.random.rand(100000)}
# Create Pandas dataframe
df = pd.DataFrame(data)
Ahora, vamos a calcular el tiempo que se tarda en guardar este dataframe como archivo .csv:
import time
start = time.time()
df.to_csv('pandas_dataframe.csv')
end = time.time()
print(end - start)
0.19349145889282227
Tardamos 0,19 segundos en guardar un dataframe Pandas con tres filas y 100 000 columnas en un archivo .csv.
Veamos si el uso de Pickle puede ayudar a mejorar el rendimiento. La biblioteca pandas tiene un método llamado to_pickle() que nos permite serializar dataframes a archivos pickle en una sola línea de código:
start = time.time()
df.to_pickle("my_pandas_dataframe.pkl")
end = time.time()
print(end - start)
0.0059659481048583984
Solo tardamos 5 milisegundos en guardar el mismo dataframe Pandas en un archivo Pickle, lo que supone una mejora significativa del rendimiento si lo comparamos con guardarlo como .csv.
Ahora, volvamos a leer el archivo en Pandas y veamos si cargar un archivo Pickle ofrece alguna ventaja de rendimiento frente a la simple lectura de un archivo .csv:
# Reading the csv file into Pandas:
start1 = time.time()
df_csv = pd.read_csv("my_pandas_dataframe.csv")
end1 = time.time()
print("Time taken to read the csv file: " + str(end1 - start1) + "\n")
# Reading the Pickle file into Pandas:
start2 = time.time()
df_pkl = pd.read_pickle("my_pandas_dataframe.pkl")
end2 = time.time()
print("Time taken to read the Pickle file: " + str(end2 - start2))
El código anterior debería dar el siguiente resultado:
Time taken to read the csv file: 0.00677490234375
Time taken to read the Pickle file: 0.0009608268737792969
Tardamos 6 milisegundos en leer el archivo .csv en Pandas, y solo 0,9 milisegundos en leerlo en Pickle.
Aunque esta diferencia pueda parecer menor, serializar grandes dataframes Pandas con Pickle puede suponer un ahorro de tiempo considerable. Pickle también nos ayudará a preservar el tipo de datos de cada columna en todos los casos y ocupa menos espacio en disco que un archivo .csv.
Diccionarios
Por último, vamos a serializar el diccionario que escribimos en un archivo de texto en la primera sección del tutorial:
students = {
'Student 1': {
'Name': "Alice", 'Age' :10, 'Grade':4,
},
'Student 2': {
'Name':'Bob', 'Age':11, 'Grade':5
},
'Student 3': {
'Name':'Elena', 'Age':14, 'Grade':8
}
}
Recuerda que, cuando guardamos este diccionario como archivo de texto, tuvimos que convertirlo en una cadena y perdió su estado original.
Ahora vamos a serializarlo utilizando Pickle y a volver a leerlo para asegurarnos de que sigue conteniendo todas las propiedades de un diccionario Python:
# serialize the dictionary to a pickle file
with open("student_dict.pkl", "wb") as f:
pickle.dump(students, f)
# deserialize the dictionary and print it out
with open("student_dict.pkl", "rb") as f:
deserialized_dict = pickle.load(f)
print(deserialized_dict)
Deberías obtener la siguiente salida:
{'Student 1': {'Name': 'Alice', 'Age': 10, 'Grade': 4}, 'Student 2': {'Name': 'Bob', 'Age': 11, 'Grade': 5}, 'Student 3': {'Name': 'Elena', 'Age': 14, 'Grade': 8}}
Comprobemos ahora el tipo de esta variable:
type(deserialized_dict)
dict
Intentemos acceder a alguna información sobre el primer alumno de este diccionario:
print(
"The first student's name is "
+ deserialized_dict["Student 1"]["Name"]
+ " and she is "
+ (str(deserialized_dict["Student 1"]["Age"]))
+ " years old."
)
The first student's name is Alice and she is 10 years old.
¡Estupendo! El diccionario conserva todas sus propiedades originales y se puede acceder a él igual que antes de la serialización. Consulta nuestro tutorial de comprensión de diccionario en Python para saber más.
Serializar modelos de machine learning con Pickle
Entrenar un modelo de machine learning es un proceso que requiere mucho tiempo: puede llevar horas, y a veces, incluso muchos días. Sencillamente, no es factible volver a entrenar un algoritmo desde cero cuando necesitas reutilizarlo o transferirlo a un entorno diferente.
Si quieres aprender las prácticas recomendadas para construir algoritmos de machine learning, puedes seguir Diseño de flujos de trabajo de machine learning en Python.
Pickle te permite serializar modelos de machine learning en su estado existente, lo que permite volver a utilizarlos cuando sea necesario.
En esta sección, aprenderemos a serializar un modelo de machine learning con Pickle.
Para ello, generemos primero unos datos falsos y construyamos un modelo de regresión lineal con la biblioteca Scikit-Learn:
from sklearn.linear_model import LinearRegression
from sklearn.datasets import make_regression
# generate regression dataset
X, y = make_regression(n_samples=100, n_features=3, noise=0.1, random_state=1)
# train regression model
linear_model = LinearRegression()
linear_model.fit(X, y)
Ahora, vamos a imprimir algunos parámetros resumidos del modelo:
# summary of the model
print('Model intercept :', linear_model.intercept_)
print('Model coefficients : ', linear_model.coef_)
print('Model score : ', linear_model.score(X, y))
Model intercept : -0.010109549594702116
Model coefficients : [44.18793068 98.97389468 58.17121618]
Model score : 0.9999993081899219
A continuación, podemos serializar este modelo utilizando la función dump() de Pickle:
with open("linear_regression.pkl", "wb") as f:
pickle.dump(linear_model, f)
El modelo se guarda ahora como archivo Pickle. Vamos a deserializarlo utilizando la función load():
with open("linear_regression.pkl", "rb") as f:
unpickled_linear_model = pickle.load(f)
El modelo serializado se carga y guarda en la variable "unpickled_linear_model". Comprobemos los parámetros de este modelo para asegurarnos de que sea el mismo que creamos inicialmente:
# summary of the model
print('Model intercept :', unpickled_linear_model.intercept_)
print('Model coefficients : ', unpickled_linear_model.coef_)
print('Model score : ', unpickled_linear_model.score(X, y))
Deberías obtener la siguiente salida:
Model intercept : -0.010109549594702116
Model coefficients : [44.18793068 98.97389468 58.17121618]
Model score : 0.9999993081899219
¡Estupendo! Los parámetros del modelo que acabamos de unpickle son los mismos que los del que creamos inicialmente.
Ahora podemos utilizar este modelo para hacer previsiones sobre un conjunto de datos de prueba, entrenar con él o transferirlo a un entorno diferente.
Aumentar el rendimiento de Python Pickle para objetos grandes
Pickle es un formato de serialización eficiente que, a menudo, ha demostrado ser más rápido que JSON, XML y HDF5 en diversas pruebas comparativas.
Sin embargo, cuando trata con estructuras de datos extremadamente grandes o con enormes modelos de machine learning, Pickle puede ralentizarse mucho, y la serialización puede convertirse en un cuello de botella en tu flujo de trabajo.
Aquí tienes algunas formas de reducir el tiempo que se tarda en guardar y cargar archivos Pickle:
Utilizar el argumento ‘PROTOCOL’
El protocolo por defecto utilizado al guardar y cargar archivos Pickle es actualmente el 4, que es el más compatible con las distintas versiones de Python.
Sin embargo, si quieres acelerar tu flujo de trabajo, puedes utilizar el argumento HIGHEST_PROTOCOL, que es el protocolo más rápido disponible en Pickle.
Para comparar el rendimiento del protocolo más compatible de Pickle y del protocolo por defecto, serialicemos primero un dataframe Pandas utilizando el protocolo por defecto. Ten en cuenta que esta es la versión del protocolo que utiliza Pickle si no se indica explícitamente ningún protocolo específico:
import pickle
import time
import numpy as np
# Set random seed
np.random.seed(100)
data = {'Column1': np.random.randint(0, 10, size=100000),
'Column2': np.random.choice(['A', 'B', 'C'], size=100000),
'Column3': np.random.rand(100000)}
# serialize to a file
start = time.time()
with open("df1.pkl", "wb") as f:
pickle.dump(data, f)
end = time.time()
print(end - start)
0.006001710891723633
Tardamos unos 6 milisegundos en serializar el dataframe utilizando el protocolo por defecto de Pickle.
Ahora, vamos a pickle el dataframe utilizando el protocolo más alto:
start = time.time()
with open("df2.pkl", "wb") as f:
pickle.dump(data, f, protocol=pickle.HIGHEST_PROTOCOL)
end = time.time()
print(end - start)
0.0030384063720703125
Con el protocolo más alto, conseguimos serializar el dataframe en la mitad de tiempo.
Utilizar 'cPickle' en lugar de 'Pickle'
El módulo 'cPickle' es una versión más rápida de 'Pickle' que está escrita en C. Esto la hace más rápida que la biblioteca 'Pickle', que está implementada puramente en Python.
Ten en cuenta que, en Python3, 'cPickle' ha pasado a llamarse '_pickle', que es la biblioteca que vamos a importar.
import _pickle as cPickle
start = time.time()
with open("df3.pkl", "wb") as f:
cPickle.dump(data, f)
end = time.time()
print(end-start)
0.004027366638183594
La serialización con 'cPickle' tardó aproximadamente 4 milisegundos, lo que representa una mejora sustancial respecto al módulo 'Pickle' de Python.
Serializar solo lo que necesites
Incluso con soluciones para acelerar la serialización, el proceso puede seguir siendo muy lento para objetos grandes.
Para mejorar el rendimiento, puedes descomponer la estructura de datos y serializar solo los subconjuntos necesarios.
Cuando trabajes con diccionarios, por ejemplo, puedes especificar pares clave-valor a los que quieras volver a acceder. Reduce el tamaño del diccionario antes de serializarlo, ya que esto reducirá la complejidad del objeto y acelerará el proceso considerablemente.
Serialización con Pickle en Python: Próximos pasos
¡Enhorabuena! Has aprendido una amplia gama de temas relacionados con la serialización en Python y el uso de la biblioteca Pickle.
Ahora deberías tener una sólida comprensión de lo que es la serialización, cómo utilizar Pickle para serializar estructuras de datos de Python y cómo optimizar el rendimiento de Pickle utilizando diferentes argumentos y módulos.
He aquí algunos pasos que puedes dar para mejorar tu comprensión de la serialización y aprovecharla para mejorar tus flujos de trabajo de ciencia de datos:
- Conoce los nuevos formatos de serialización. Aunque en este tutorial solo hemos tratado el módulo Pickle, no es necesariamente el mejor formato de serialización para todos los casos. Merece la pena conocer otros formatos, como JSON, XML y HDF5, para que sepas cuál elegir para los distintos casos de uso.
- Pruébalo tú mismo. Aplica los conceptos enseñados en este tutorial a tus flujos de trabajo de ciencia de datos. La próxima vez que crees una nueva estructura de datos o almacenes la salida de un cálculo en una variable, serialízalas para utilizarlas más tarde en lugar de ejecutar todo tu código una y otra vez.
- Haz un curso online. Para optimizar tus flujos de trabajo de ciencia de datos y realizar tareas como la serialización de forma eficiente, es imprescindible tener un sólido conocimiento de los objetos y las estructuras de datos de Python. Para conseguirlo, puedes seguir nuestro curso Tipos de datos para la ciencia de datos en Python.
Más información sobre Python y machine learning
curso
Machine Learning with Tree-Based Models in Python
curso
Unsupervised Learning in Python
tutorial
Tutorial sobre cómo trabajar con módulos en Python
Nishant Kumar
8 min
tutorial
Tutorial de multiprocesamiento en Python
tutorial
Programación orientada a objetos (POO) en Python: Tutorial
tutorial
Datos JSON en Python
tutorial
Aprendizaje automático de datos categóricos con el tutorial de Python
tutorial