In [ ]:
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
In [ ]:
#@title MIT License
#
# Copyright (c) 2017 François Chollet
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
In un problema di regressione, abbiamo l'obiettivo di prevedere l'andamento di un valore continuo, come un prezzo o una probabilità. Al contrario di un problema di classificazione, ove abbiamo l'obiettivo di scegliere una classe tra una lista di classi (per esempio, quando un'immagine contenga una mela o un'arancia, riconoscere quale frutto è nell'immagine).
Questo notebook usa il classico Dataset Auto MPG e costruisce un modello per prevedere il consumo di carburante delle auto della fine degli anni '70 e dell'inizio degli anni '80. Per farlo, forniremo al modello una descrizione di mote automobili di quel periodo. Questa descrizione include attributi come: cilindri, cilindrata, cavalli di potenza, e peso.
Questo esempio usa le API tf.keras
, per i dettagli vedere questa guida.
In [ ]:
# Use seaborn for pairplot
!pip install seaborn
# Use some functions from tensorflow_docs
!pip install git+https://github.com/tensorflow/docs
In [ ]:
import pathlib
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
In [ ]:
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
print(tf.__version__)
In [ ]:
import tensorflow_docs as tfdocs
import tensorflow_docs.plots
import tensorflow_docs.modeling
Il dataset è disponibile su UCI Machine Learning Repository.
In [ ]:
dataset_path = keras.utils.get_file("auto-mpg.data", "http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data")
dataset_path
Importiamolo usando pandas
In [ ]:
column_names = ['MPG','Cylinders','Displacement','Horsepower','Weight',
'Acceleration', 'Model Year', 'Origin']
raw_dataset = pd.read_csv(dataset_path, names=column_names,
na_values = "?", comment='\t',
sep=" ", skipinitialspace=True)
dataset = raw_dataset.copy()
dataset.tail()
In [ ]:
dataset.isna().sum()
Per mantenere semplice questo tutorial iniziale, eliminiamo queste righe.
In [ ]:
dataset = dataset.dropna()
La colonna "Origine"
, in verità, è una categoria, non un numero. Così la convertiamo in un indicatore:
In [ ]:
dataset['Origin'] = dataset['Origin'].map(lambda x: {1: 'USA', 2: 'Europe', 3: 'Japan'}.get(x))
In [ ]:
dataset = pd.get_dummies(dataset, prefix='', prefix_sep='')
dataset.tail()
In [ ]:
train_dataset = dataset.sample(frac=0.8,random_state=0)
test_dataset = dataset.drop(train_dataset.index)
In [ ]:
sns.pairplot(train_dataset[["MPG", "Cylinders", "Displacement", "Weight"]], diag_kind="kde")
Ed anche alle statistiche generali:
In [ ]:
train_stats = train_dataset.describe()
train_stats.pop("MPG")
train_stats = train_stats.transpose()
train_stats
In [ ]:
train_labels = train_dataset.pop('MPG')
test_labels = test_dataset.pop('MPG')
E' buona pratica normalizzare le caratteristiche che usano diversi intervalli e scale. Sebbene il modello possa convergere senza normalizzare le caratteristiche, ciò rende l'addestramento più difficile, e rende il modello risultante dipendente dalla scelta delle unità usate nell'input.
Nota: Sebbene, intenzionalmente, generiamo queste statistiche dal solo dataset di addestramento, esse potranno essere usate anche per normalizzare il dataset di validazione. Ciò è necessario per proiettare il dataset di validazione con la stessa distribuzione con cui è stato addestrato il modello.
In [ ]:
def norm(x):
return (x - train_stats['mean']) / train_stats['std']
normed_train_data = norm(train_dataset)
normed_test_data = norm(test_dataset)
Questi dati normalizzati saranno quelli che useremo per addestrare il modello.
Attenzione: Le statistiche utilizzate per normalizzare gli input (la media e la deviazione standard) devono essere applicate ad ogni altro valore con cui sia alimentato il modello, assieme alla codifica degli indicatori che abbiamo fatto prima. Inclusi: l'insieme di validazione ed i dati vivi, quando il modello verrà usato in produzione.
Andiamo a realizzare il nostro modello. Useremo un modello Sequenziale
con due livelli nascosti, densamente connessi, ed un livello di uscita che restituisce un valore singolo, continuo. I passi di costruzione del modello sono raccolti in una funzione, build_model
, perché, in seguito, creeremo un secondo modello.
In [ ]:
def build_model():
model = keras.Sequential([
layers.Dense(64, activation='relu', input_shape=[len(train_dataset.keys())]),
layers.Dense(64, activation='relu'),
layers.Dense(1)
])
optimizer = tf.keras.optimizers.RMSprop(0.001)
model.compile(loss='mse',
optimizer=optimizer,
metrics=['mae', 'mse'])
return model
In [ ]:
model = build_model()
In [ ]:
model.summary()
Ora proviamo il modello. Prendiamo un blocco di 10
esempi dai dati di addestramento e, su di essi, chiamiamo model.predict
.
In [ ]:
example_batch = normed_train_data[:10]
example_result = model.predict(example_batch)
example_result
Sembra essere funzionante, e produce un risultato nel formato e del tipo attesi.
In [ ]:
EPOCHS = 1000
history = model.fit(
normed_train_data, train_labels,
epochs=EPOCHS, validation_split = 0.2, verbose=0,
callbacks=[tfdocs.modeling.EpochDots()])
Visualizziamo il progresso del modello durante l'addestramento usando le statistiche immagazzinate nell'oggetto history
.
In [ ]:
hist = pd.DataFrame(history.history)
hist['epoch'] = history.epoch
hist.tail()
In [ ]:
plotter = tfdocs.plots.HistoryPlotter(smoothing_std=2)
In [ ]:
plotter.plot({'Basic': history}, metric = "mae")
plt.ylim([0, 10])
plt.ylabel('MAE [MPG]')
In [ ]:
plotter.plot({'Basic': history}, metric = "mse")
plt.ylim([0, 20])
plt.ylabel('MSE [MPG^2]')
Questo grafico mostra miglioramenti modesti, o anche peggioramenti nell'errore di validazione dopo circa 100 epoche. Andiamo a modificare la chiamata alla model.fit
per fermare automaticamente l'addestramento quando il risultato della validazione non migliora. Useremo la EarlyStopping callback che controlla la condizione di addestramento ad ogni epoca. Se un dato numero di epoche passano senza mostrare miglioramenti, allora l'addestramento viene interrotto automaticamente.
Qui potete imparare di più a proposito di questa callback.
In [ ]:
model = build_model()
# The patience parameter is the amount of epochs to check for improvement
early_stop = keras.callbacks.EarlyStopping(monitor='val_loss', patience=10)
early_history = model.fit(normed_train_data, train_labels,
epochs=EPOCHS, validation_split = 0.2, verbose=0,
callbacks=[early_stop, tfdocs.modeling.EpochDots()])
In [ ]:
plotter.plot({'Early Stopping': early_history}, metric = "mae")
plt.ylim([0, 10])
plt.ylabel('MAE [MPG]')
Il grafico mostra che l'errore medio sull'insieme di validazione è solitamente attorno a +/- 2 MPG. Va bene? Lasciamo a voi la decisione.
Andiamo a vedere quanto il modello generalizzi bene con l'insieme di test, che non abbiamo usato nell'addestramento del modello. Questo ci dice come ci possiamo aspettare che il modello possa comportarsi, quando lo usiamo nel mondo reale.
In [ ]:
loss, mae, mse = model.evaluate(normed_test_data, test_labels, verbose=2)
print("Testing set Mean Abs Error: {:5.2f} MPG".format(mae))
In [ ]:
test_predictions = model.predict(normed_test_data).flatten()
a = plt.axes(aspect='equal')
plt.scatter(test_labels, test_predictions)
plt.xlabel('True Values [MPG]')
plt.ylabel('Predictions [MPG]')
lims = [0, 50]
plt.xlim(lims)
plt.ylim(lims)
_ = plt.plot(lims, lims)
Sembra che il nostro modello preveda ragionevolmente bene. Diamo un'occhiata alla distribuzione dell'errore.
In [ ]:
error = test_predictions - test_labels
plt.hist(error, bins = 25)
plt.xlabel("Prediction Error [MPG]")
_ = plt.ylabel("Count")
Non è una gaussiana, ma potevamo aspettarcelo, perché il numero di campioni è molto piccolo.
Questo notebook ha introdotto alcune tecniche per gestire un problema di regressione.