Corso
La serializzazione è una competenza molto utile. Ti permette di preservare lo stato dei tuoi dati o modelli, evitando di dover rielaborare o riaddestrare tutto da zero.
Qui esplorerò il noto modulo Pickle di Python. Alla fine saprai come serializzare e deserializzare strutture dati comuni come liste e dizionari e come sfruttare diverse tecniche di ottimizzazione delle prestazioni.
Ti consiglio anche il nostro corso Progettare workflow di machine learning in Python come prossimo passo, dove puoi imparare a serializzare e containerizzare modelli di ML per la produzione.
Introduzione alla serializzazione degli oggetti
Sei stanco di rieseguire il tuo codice Python ogni volta che ti serve accedere a un data frame, a una variabile o a un modello di machine learning creato in precedenza?
La serializzazione degli oggetti potrebbe essere la soluzione che stai cercando.
È il processo di archiviazione di una struttura dati in memoria, in modo da poterla caricare o trasmettere quando serve senza perdere il suo stato attuale.
Ecco un semplice schema che spiega come funziona la serializzazione:

Immagine dell'autore
In Python lavoriamo con strutture dati di alto livello come liste, tuple e set. Tuttavia, quando vogliamo conservare questi oggetti in memoria, devono essere convertiti in una sequenza di byte che il computer possa comprendere. Questo processo si chiama serializzazione.
La volta successiva che vogliamo accedere alla stessa struttura dati, questa sequenza di byte deve essere riconvertita nell'oggetto di alto livello in un processo noto come deserializzazione.
Possiamo usare formati come JSON, XML, HDF5 e Pickle per la serializzazione. In questo tutorial impareremo a conoscere la libreria Pickle di Python per la serializzazione. Vedremo a cosa serve e capiremo quando scegliere Pickle rispetto ad altri formati di serializzazione.
Infine, impareremo a usare la libreria Pickle di Python per serializzare liste, dizionari, data frame di Pandas, modelli di machine learning e altro ancora.
Per eseguire facilmente in autonomia tutto il codice di esempio di questo tutorial, puoi creare gratis un workbook DataLab con Python preinstallato che contiene tutti gli esempi di codice. Per fare altra pratica sul caricamento di file pickled in Python, dai un'occhiata a questo esercizio pratico di DataCamp.
Perché abbiamo bisogno della serializzazione degli oggetti?
Prima di iniziare con Python Pickle, capiamo perché la serializzazione degli oggetti è così importante.
Potresti chiederti perché non possiamo semplicemente salvare le strutture dati in un file di testo e accedervi di nuovo quando serve, invece di doverle serializzare.
Vediamo un semplice esempio per capire i vantaggi della serializzazione.
Ecco un dizionario annidato con informazioni sugli studenti come nome, età e genere:
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
}
}
Esaminiamo il tipo di dato dell'oggetto "students":
type(students)
dict
Ora che abbiamo confermato che l'oggetto students è di tipo dizionario, proviamo a scriverlo in un file di testo senza serializzazione:
with open('student_info.txt','w') as data:
data.write(str(students))
Nota che, dato che possiamo scrivere nei file di testo solo oggetti stringa, abbiamo convertito il dizionario in stringa usando la funzione str(). Ciò significa che lo stato originale del nostro dizionario è perso.
Ora leggiamo il dizionario, stampiamolo e verifichiamone di nuovo il tipo:
with open("student_info.txt", 'r') as f:
for students in f:
print(students)
type(students)
str
Il dizionario annidato ora viene stampato come stringa e restituirà un errore quando proveremo ad accedere alle sue chiavi o ai suoi valori.
È qui che entra in gioco la serializzazione.
Quando si lavora con tipi di dati più complessi come dizionari, data frame e liste annidate, la serializzazione consente di preservare lo stato originale dell'oggetto senza perdere alcuna informazione rilevante.
Più avanti in questo articolo impareremo a salvare lo stesso dizionario in un file usando Pickle e a deserializzarlo con la libreria. Puoi leggere altro sulla dictionary comprehension in Python in un tutorial separato.
Introduzione a Pickle in Python
Il modulo Pickle di Python è un formato popolare per serializzare e deserializzare tipi di dati. Questo formato è nativo di Python, il che significa che gli oggetti Pickle non possono essere caricati con altri linguaggi di programmazione.
Pickle presenta vantaggi e svantaggi rispetto ad altri formati di serializzazione.
Vantaggi dell'uso di Pickle per serializzare oggetti
- A differenza di formati come JSON, che non gestiscono tuple e oggetti datetime, Pickle può serializzare quasi tutti i tipi di dato built-in comunemente usati in Python. Inoltre conserva l'esatto stato dell'oggetto, cosa che JSON non può fare.
- Pickle è anche una buona scelta per archiviare strutture ricorsive, perché scrive un oggetto una sola volta.
- Pickle offre flessibilità nella deserializzazione. Puoi salvare facilmente variabili diverse in un file Pickle e ricaricarle in una sessione Python diversa, recuperando i tuoi dati esattamente com'erano senza dover modificare il codice.
Svantaggi dell'uso di Pickle
- Pickle non è sicuro perché può eseguire callable Python malevoli per costruire oggetti. Durante la deserializzazione, Pickle non distingue tra una callable malevola e una non malevola. Per questo motivo, gli utenti possono finire per eseguire codice arbitrario durante la deserializzazione.
- Come già accennato, Pickle è un modulo specifico di Python e potresti avere difficoltà a deserializzare oggetti pickled usando un linguaggio diverso.
- Secondo diversi benchmark, Pickle risulta più lento e produce valori serializzati più grandi rispetto a formati come JSON e Apache Thrift.
Salvare e caricare oggetti con le funzioni dump e load di Pickle
Pickle usa le seguenti funzioni per serializzare e deserializzare oggetti 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)
Le funzioni dump() e dumps() di Pickle servono per serializzare un oggetto. L'unica differenza è che dump() scrive i dati su un file, mentre dumps() li rappresenta come oggetto di tipo byte.
Allo stesso modo, load() legge oggetti pickled da un file, mentre loads() li deserializza da un oggetto simile a bytes.
In questo tutorial useremo le funzioni dump() e load() per picklare oggetti Python su un file e unpicklarli.
Serializzare strutture dati Python con Pickle
Liste
Per prima cosa, creiamo una semplice lista Python:
import pickle
student_names = ['Alice','Bob','Elena','Jane','Kyle']
Ora apriamo un file, scriviamo la lista al suo interno usando la funzione dumps() e chiudiamo il file:
with open('student_file.pkl', 'wb') as f: # open a text file
pickle.dump(student_names, f) # serialize the list
Per prima cosa abbiamo creato un file chiamato "student_file.pkl". L'estensione non deve per forza essere .pkl. Puoi chiamarlo come preferisci e il file verrà comunque creato. Tuttavia, è buona pratica usare l'estensione .pkl per ricordarti che si tratta di un file Pickle.
Nota anche che abbiamo aperto il file in modalità wb. Significa che stai scrivendo il file in modalità binaria, così i dati vengono restituiti come oggetto bytes.
Poi usiamo la funzione dump() per salvare la lista student_names nel file.
Infine, puoi chiudere il file con la seguente riga di codice:
f.close()
Ora deserializziamo il file e stampiamo 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
L'output del codice qui sopra dovrebbe essere il seguente:
['Alice', 'Bob', 'Elena', 'Jane', 'Kyle']
Per deserializzare il file dobbiamo usare la modalità rb, che sta per read binary. Poi facciamo l'unpickle dell'oggetto con la funzione load(), dopo di che possiamo conservare i dati in un'altra variabile e usarli come preferiamo.
Controlliamo ora il tipo di dato della lista che abbiamo appena unpicklato:
type(student_names_loaded)
list
Ottimo! Abbiamo preservato lo stato originale e il tipo di dato di questa lista.
Array Numpy
Adesso proviamo a serializzare e deserializzare un tipo di dato leggermente più complesso: gli array Numpy.
Creiamo prima un array 10 per 10 di uni:
import numpy as np
numpy_array = np.ones((10,10)) # 10x10 array
Poi, come prima, chiamiamo la funzione dump() per serializzare questo array su un file:
with open('my_array.pkl','wb') as f:
pickle.dump(numpy_array, f)
Infine, facciamo l'unpickle di questo array e controlliamone shape e tipo di dato per assicurarci che abbia mantenuto lo stato originale:
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)))
Dovresti ottenere il seguente output:
Array shape: (10, 10)
Data type: <class 'numpy.ndarray'>
L'oggetto unpicklato è un array Numpy 10x10, con la stessa forma e lo stesso tipo di dato dell'oggetto che abbiamo appena serializzato.
pandas DataFrame
Un data frame è un oggetto con cui i data scientist lavorano quotidianamente. Il modo più diffuso per caricare e salvare un DataFrame di Pandas è leggerlo e scriverlo come file CSV. Scopri di più sull'importazione dei dati nel nostro tutorial su pandas read_csv().
Tuttavia, questo processo è più lento della serializzazione e può diventare estremamente dispendioso in termini di tempo se il data frame è grande.
Mettiamo a confronto l'efficienza di salvataggio e caricamento di un dataframe pandas usando Pickle rispetto al CSV confrontando i tempi rispettivi.
Per prima cosa, creiamo un dataframe pandas con 100.000 righe di dati fittizi:
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)
Ora calcoliamo il tempo necessario per salvare questo dataframe come file csv:
import time
start = time.time()
df.to_csv('pandas_dataframe.csv')
end = time.time()
print(end - start)
0.19349145889282227
Ci sono voluti 0,19 secondi per salvare un DataFrame di Pandas con tre righe e 100.000 colonne in un file csv.
Vediamo se usare Pickle può migliorare le prestazioni. La libreria pandas ha un metodo chiamato to_pickle() che ci permette di serializzare i dataframe in file pickle con una sola riga di codice:
start = time.time()
df.to_pickle("my_pandas_dataframe.pkl")
end = time.time()
print(end - start)
0.0059659481048583984
Ci sono voluti solo 5 millisecondi per salvare lo stesso DataFrame di Pandas in un file Pickle, un notevole miglioramento rispetto al salvataggio in csv.
Ora rileggiamo il file in Pandas e vediamo se caricare un file Pickle offre vantaggi prestazionali rispetto alla semplice lettura di un file 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))
Il codice sopra dovrebbe produrre il seguente output:
Time taken to read the csv file: 0.00677490234375
Time taken to read the Pickle file: 0.0009608268737792969
Ci sono voluti 6 millisecondi per leggere il file CSV in Pandas e solo 0,9 millisecondi per leggere il file Pickle.
Anche se la differenza può sembrare minima, serializzare grandi dataframe Pandas con Pickle può portare a notevoli risparmi di tempo. Pickle ci aiuterà anche a preservare il tipo di dato di ogni colonna in ogni caso e occupa meno spazio su disco rispetto a un file CSV.
Dizionari
Infine, serializziamo il dizionario che abbiamo scritto in un file di testo nella prima sezione 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
}
}
Ricorda che quando abbiamo salvato questo dizionario come file di testo, abbiamo dovuto convertirlo in stringa e ne abbiamo perso lo stato originale.
Ora serializziamolo con Pickle e rileggiamolo per assicurarci che contenga ancora tutte le proprietà di un dizionario 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)
Dovresti ottenere il seguente output:
{'Student 1': {'Name': 'Alice', 'Age': 10, 'Grade': 4}, 'Student 2': {'Name': 'Bob', 'Age': 11, 'Grade': 5}, 'Student 3': {'Name': 'Elena', 'Age': 14, 'Grade': 8}}
Controlliamo ora il tipo di questa variabile:
type(deserialized_dict)
dict
Proviamo ad accedere ad alcune informazioni sul primo studente in questo dizionario:
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.
Ottimo! Il dizionario ha mantenuto tutte le sue proprietà originali e può essere utilizzato proprio come prima della serializzazione. Dai un'occhiata al nostro tutorial sulla dictionary comprehension in Python per saperne di più.
Serializzare modelli di machine learning con Pickle
Addestrare un modello di machine learning è un processo dispendioso che può richiedere ore, a volte anche molti giorni. Semplicemente non è fattibile riaddestrare un algoritmo da zero quando devi riutilizzarlo o trasferirlo in un ambiente diverso.
Se vuoi imparare le best practice nella costruzione di algoritmi di machine learning, puoi seguire il nostro corso Progettare workflow di machine learning in Python.
Pickle ti permette di serializzare i modelli di machine learning nel loro stato attuale, rendendo possibile riutilizzarli quando serve.
In questa sezione impareremo a serializzare un modello di machine learning con Pickle.
Per farlo, generiamo prima dei dati fittizi e costruiamo un modello di regressione lineare con la libreria 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)
Ora stampiamo alcuni parametri riassuntivi del modello:
# 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
Possiamo poi serializzare questo modello usando la funzione dump() di Pickle:
with open("linear_regression.pkl", "wb") as f:
pickle.dump(linear_model, f)
Il modello è ora salvato come file Pickle. Deserializziamolo con la funzione load():
with open("linear_regression.pkl", "rb") as f:
unpickled_linear_model = pickle.load(f)
Il modello serializzato è ora caricato e salvato nella variabile unpickled_linear_model. Verifichiamo i parametri di questo modello per assicurarci che sia lo stesso che abbiamo creato inizialmente:
# 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))
Dovresti ottenere il seguente output:
Model intercept : -0.010109549594702116
Model coefficients : [44.18793068 98.97389468 58.17121618]
Model score : 0.9999993081899219
Ottimo! I parametri del modello che abbiamo appena unpicklato sono gli stessi di quelli del modello creato inizialmente.
Ora possiamo usare questo modello per fare previsioni su un dataset di test, continuare l'addestramento sopra di esso o trasferirlo in un ambiente diverso.
Aumentare le prestazioni di Python Pickle per oggetti grandi
Pickle è un formato di serializzazione efficiente che spesso si è dimostrato più veloce di JSON, XML e HDF5 in vari benchmark.
Tuttavia, quando si lavora con strutture dati estremamente grandi o con modelli di machine learning molto pesanti, Pickle può rallentare considerevolmente e la serializzazione può diventare un collo di bottiglia nel tuo workflow.
Ecco alcuni modi per ridurre il tempo necessario a salvare e caricare file Pickle:
Usa l'argomento PROTOCOL
Il protocollo predefinito usato per salvare e caricare file Pickle è attualmente il 4, che è il più compatibile tra le diverse versioni di Python.
Tuttavia, se vuoi velocizzare il tuo workflow, puoi usare l'argomento HIGHEST_PROTOCOL, che è il protocollo più veloce disponibile in Pickle.
Per confrontare le prestazioni tra il protocollo più compatibile di Pickle e il protocollo predefinito, serializziamo prima un DataFrame di Pandas usando il protocollo predefinito. Nota che questa è la versione del protocollo che Pickle usa se non viene specificato esplicitamente alcun protocollo:
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
Ci sono voluti circa 6 millisecondi per serializzare il DataFrame usando il protocollo predefinito di Pickle.
Ora pickliamo il DataFrame usando il protocollo più 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 il protocollo più alto siamo riusciti a serializzare il DataFrame in metà del tempo.
Usa cPickle invece di Pickle
Il modulo cPickle è una versione più veloce di Pickle scritta in C. Questo lo rende più rapido della libreria Pickle, che è implementata interamente in Python.
Nota che in Python 3 cPickle è stato rinominato in _pickle, che è la libreria che importeremo.
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 serializzazione con cPickle ha impiegato circa 4 millisecondi, un miglioramento significativo rispetto al modulo Pickle di Python.
Serializza solo ciò che ti serve
Anche con gli accorgimenti per rendere la serializzazione più veloce, il processo può rimanere molto lento per oggetti di grandi dimensioni.
Per migliorare le prestazioni, puoi scomporre la struttura dati e serializzare solo i sottoinsiemi necessari.
Quando lavori con i dizionari, ad esempio, puoi specificare le coppie chiave-valore che vuoi riutilizzare. Riduci la dimensione del dizionario prima di serializzarlo: così ne ridurrai la complessità e velocizzerai sensibilmente il processo.
Serializzazione con Pickle in Python: prossimi passi
Complimenti! Hai imparato una vasta gamma di argomenti relativi alla serializzazione in Python e all'uso della libreria Pickle.
Ora dovresti avere una solida comprensione di cos'è la serializzazione, come usare Pickle per serializzare strutture dati Python e come ottimizzare le prestazioni di Pickle usando diversi argomenti e moduli.
Ecco alcuni passi che puoi compiere per migliorare la tua comprensione della serializzazione e sfruttarla per potenziare i tuoi workflow di data science:
- Scopri nuovi formati di serializzazione. Anche se in questo tutorial abbiamo trattato solo il modulo Pickle, non è necessariamente il formato migliore in ogni scenario. Vale la pena conoscere altri formati, come JSON, XML e HDF5, così da sapere quale scegliere per i diversi casi d'uso.
- Prova in prima persona. Applica i concetti spiegati in questo tutorial ai tuoi workflow di data science. La prossima volta che crei una nuova struttura dati o salvi l'output di un calcolo in una variabile, serializzala per riutilizzarla in seguito invece di rieseguire tutto il codice ogni volta.
- Segui un corso online. Per ottimizzare i tuoi workflow di data science ed eseguire attività come la serializzazione in modo efficiente, è fondamentale avere una solida comprensione delle strutture dati e degli oggetti in Python. Per riuscirci, puoi seguire il nostro corso Data Types for Data Science in Python.
Natassha è una consulente data che lavora all'intersezione tra data science e marketing. Crede che i dati, se usati con intelligenza, possano ispirare una crescita straordinaria per persone e organizzazioni. Come professionista dei dati autodidatta, Natassha ama scrivere articoli che aiutino altri aspiranti data scientist a entrare nel settore. I suoi articoli, pubblicati sul suo blog personale e su testate esterne, registrano in media 200.000 visualizzazioni mensili.


