<jupyter_start><jupyter_text>Introduzione a KerasKeras viene utilizzato come front end di TensorFlow, importato come modulo usando `from tensorflow import keras` ovvero direttamente dalla distribuzione Keras usando `import keras`. Nel secondo caso keras sarà stato già installato usando `pip`o `conda`.L'API di Keras ha due forme distinte: la API `Sequential` che costruisce il modello aggiungendo oggetti della classe `Layers` e la API funzionale che usa la classe `Model` per costruire il modello in modo tale che ogni layer sia l'input di una funzione che calcola il successivo.Classifichiamo le cofre MNIST conuna semplice rete convoluzionale con API `Sequential`.<jupyter_code>import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import zipfile
import os

os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"


# parametri del modello
num_classes = 10
input_shape = (28, 28, 1)

# Carichiamo il data set
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()

# Scaliamo le immagini nel range [0, 1]
x_train = x_train.astype("float32") / 255
x_test = x_test.astype("float32") / 255

# Impostiamo la shape dei tensori a (28, 28, 1)
x_train = np.expand_dims(x_train, -1)
x_test = np.expand_dims(x_test, -1)

print("x_train shape:", x_train.shape)
print(x_train.shape[0], "train samples")
print(x_test.shape[0], "test samples")


# Convertiamo i vettori delle classi in matrici binarie
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

# Creiamo il modello

model = keras.Sequential(
    [
        layers.Conv2D(32, kernel_size=(3, 3), activation="relu",input_shape=input_shape),
        layers.MaxPooling2D(pool_size=(2, 2)),
        layers.Conv2D(64, kernel_size=(3, 3), activation="relu"),
        layers.MaxPooling2D(pool_size=(2, 2)),
        layers.Flatten(),
        layers.Dropout(0.5),
        layers.Dense(num_classes, activation="softmax"),
    ]
)

model.summary()

# Impostiamo i parametri di addestramento

batch_size = 128
epochs = 15

# Compiliamo il modello inserendo la loss, l'ottimizzatore e la metrica
model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])

# Addestramento
model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, validation_split=0.1)

score = model.evaluate(x_test, y_test, verbose=0)
print("Test loss:", score[0])
print("Test accuracy:", score[1])

# Creiamo la directory dei dati e spostiamoci in essa
os.makedirs("/home/rpirrone/.keras/datasets/COVID-19_Chest_CT")
os.chdir("/home/rpirrone/.keras/datasets/COVID-19_Chest_CT")

# Download url delle TAC normali
url = "https://github.com/hasibzunair/3D-image-classification-tutorial/releases/download/v0.2/CT-0.zip"
filename = os.path.join(os.getcwd(), "CT-0.zip")
keras.utils.get_file(filename, url)

# Download url delle TAC anormali
url = "https://github.com/hasibzunair/3D-image-classification-tutorial/releases/download/v0.2/CT-23.zip"
filename = os.path.join(os.getcwd(), "CT-23.zip")
keras.utils.get_file(filename, url)

# Creiamo la directory per conservare i dati
os.makedirs("MosMedData")

# Unzip degli archivi
with zipfile.ZipFile("CT-0.zip", "r") as z_fp:
    z_fp.extractall("./MosMedData/")

with zipfile.ZipFile("CT-23.zip", "r") as z_fp:
    z_fp.extractall("./MosMedData/")


# I dati sono in formato Nifti che è un formato tipico dei volui TAC e si basa sul formato DICOM
# Eseguiremo il seguente preprocessing
# - Rotazione di 90 gradi per allinearli
# - Scaling dei valori nativi di intensità (Hounsfield Unit - HU - compresi tra -1024 e 2000) in [0-1]
# - Resize delle tre dimensioni del volume

import nibabel as nib

from scipy import ndimage

os.chdir("/home/rpirrone/.keras/datasets/COVID-19_Chest_CT")

# Dimensioni desiderate
desired_depth = 64
desired_width = 128
desired_height = 128


def read_nifti_file(filepath):
    """Legge e carica il volume"""
    # Legge il file
    scan = nib.load(filepath)

    # Carica i dati grezzi
    scan = scan.get_fdata()

    return scan


def normalize(volume):
    """Normalizza il volume"""
    # I valori delle TAC sono in Hounsfield Units (HU) che hanno escursione in
    # un intervallo ampio: normalizziamo in [0, 1]
    volume = volume.astype("float32")

    min = np.min(volume)
    max = np.max(volume)

    volume = (volume - min) / (max - min)

    return volume


def resize_volume(volume, w, h, d):
    """Ridimensiona lungo l'asse z"""

    # Calcolo dei fattori di scala
    depth_factor = d / volume.shape[-1]
    width_factor = w / volume.shape[0]
    height_factor = h / volume.shape[1]

    # Rotazione di 90 gradi
    volume = ndimage.rotate(volume, 90, reshape=False)

    # Ridimensionamento lungo l'asse z
    volume = ndimage.zoom(volume, (width_factor, height_factor, depth_factor), order=1)
 
    return volume


def process_scan(path):
    """Lettura, normalizzazione e ridimensinamento del volume"""
    # Lettura
    volume = read_nifti_file(path)
    # Nornalizzazione
    volume = normalize(volume)
    # Ridimensionamento
    volume = resize_volume(volume, desired_width, desired_height, desired_depth)
    return volume

# La cartella "CT-0" consiste di scansioni TAC che riportano tessuti polmonai normali
# senza segni di polmonite virale
normal_scan_paths = [
    os.path.join(os.getcwd(), "MosMedData/CT-0", x)
    for x in os.listdir("MosMedData/CT-0")
]
# LA cartella "CT-23" consiste di scansioni TAC con molte opacità "a vetro smerigliato"
# che coinvolgono il parenchima polmonare
abnormal_scan_paths = [
    os.path.join(os.getcwd(), "MosMedData/CT-23", x)
    for x in os.listdir("MosMedData/CT-23")
]

print("Scansioni TAC con tessuti polmonari normali: " + str(len(normal_scan_paths)))
print("Scansioni TAC con tessuti polmonari anormali: " + str(len(abnormal_scan_paths)))

# Costruzione del training set e del validation set

# Lettura e processing delle scansioni
# Ridimensionamento e scalatura
abnormal_scans = np.array([process_scan(path) for path in abnormal_scan_paths])
normal_scans = np.array([process_scan(path) for path in normal_scan_paths])

# Le scansioni anormali sono in classe 1 mentre quelle normali in classe 0
abnormal_labels = np.array([1 for _ in range(len(abnormal_scans))])
normal_labels = np.array([0 for _ in range(len(normal_scans))])

# Creiamo un rapporto 70% - 30% per il traning e il validaiton set
x_train = np.concatenate((abnormal_scans[:70], normal_scans[:70]), axis=0)
y_train = np.concatenate((abnormal_labels[:70], normal_labels[:70]), axis=0)
x_val = np.concatenate((abnormal_scans[70:], normal_scans[70:]), axis=0)
y_val = np.concatenate((abnormal_labels[70:], normal_labels[70:]), axis=0)
print(
    "I campioni nel training e validation set sono %d e %d."
    % (x_train.shape[0], x_val.shape[0])
)

# Data augmentation
# I volumi saranno ruotati di alcuni gradi per generare nuovi campioni
# Si aggiungerà un canale nella quarta dimensione per consentire il processing dei
# minibatch sui dati che sono tridimensionali per cui la shape cambierà da
# (sample, height, width, depth) a (sample, height, width, depth, 1)

import random


@tf.function # decoratore per usare l'effettiva funzione di rotazione come parte dell'elaborazione TF
def rotate(volume):
    """Ruota il volume di alcuni gradi"""

    def scipy_rotate(volume):
        # Angoli di rotazione prescelti
        angles = [-20, -10, -5, 5, 10, 20]
        # selezioniam gli angoli casualmente
        angle = random.choice(angles)
        # ruotiamo. il volume
        volume = ndimage.rotate(volume, angle, reshape=False)
        volume[volume < 0] = 0
        volume[volume > 1] = 1
        return volume

    augmented_volume = tf.numpy_function(scipy_rotate, [volume], tf.float32)
    return augmented_volume


def train_preprocessing(volume, label):
    """Aggiunge un canale e ruota il training set"""
    # Rotate volume
    volume = rotate(volume)
    #volume = tf.expand_dims(volume, axis=3)
    volume = tf.reshape(volume,shape=(128, 128, 64, 1))
    return volume, label


def validation_preprocessing(volume, label):
    """Aggiunge solamente il canale al validation set"""
    #volume = tf.expand_dims(volume, axis=3)
    volume = tf.reshape(volume,shape=(128, 128, 64, 1))
    return volume, label

# Creiamo i data loader
with tf.device('CPU:0'):
    
    train_loader = tf.data.Dataset.from_tensor_slices((x_train, y_train))
    validation_loader = tf.data.Dataset.from_tensor_slices((x_val, y_val))

    batch_size = 2
    # Data augmentation al volo durante l'addestramento
    train_dataset = (
        train_loader.shuffle(len(x_train))  # shuffling dei campioni
        .map(train_preprocessing)           # preprocessing 
        .batch(batch_size)                  # impostazione del batch size
        .prefetch(2)                        # due campioni sono sempre pre-caricati per il successivo 
                                            # passo di addestramento
    )
    # il validation preprocessing è solo scalatura
    validation_dataset = (
        validation_loader.shuffle(len(x_val))
        .map(validation_preprocessing)
        .batch(batch_size)
        .prefetch(2)
    )
 
print(tf.data.get_output_shapes(train_dataset))
print(tf.data.get_output_shapes(validation_dataset))
# Creiamo il modello

def get_model(width=128, height=128, depth=64):
    """Costruisce un modello per una CNN 3D"""

    inputs = keras.Input((width, height, depth, 1))

    x = layers.Conv3D(filters=64, kernel_size=3, activation="relu")(inputs)
    x = layers.MaxPool3D(pool_size=2)(x)
    x = layers.BatchNormalization()(x)

    x = layers.Conv3D(filters=64, kernel_size=3, activation="relu")(x)
    x = layers.MaxPool3D(pool_size=2)(x)
    x = layers.BatchNormalization()(x)

    x = layers.Conv3D(filters=128, kernel_size=3, activation="relu")(x)
    x = layers.MaxPool3D(pool_size=2)(x)
    x = layers.BatchNormalization()(x)

    x = layers.Conv3D(filters=256, kernel_size=3, activation="relu")(x)
    x = layers.MaxPool3D(pool_size=2)(x)
    x = layers.BatchNormalization()(x)

    x = layers.GlobalAveragePooling3D()(x)
    x = layers.Dense(units=512, activation="relu")(x)
    x = layers.Dropout(0.3)(x)

    outputs = layers.Dense(units=1, activation="sigmoid")(x)

    # Definisce il modello
    model = keras.Model(inputs, outputs, name="3dcnn")
    return model


# Costruzione del modello
model = get_model(width=128, height=128, depth=64)
model.summary()

# Compila il modello
initial_learning_rate = 0.0001
lr_schedule = keras.optimizers.schedules.ExponentialDecay(
    initial_learning_rate, decay_steps=100000, decay_rate=0.96, staircase=True
)
model.compile(
    loss="binary_crossentropy",
    optimizer=keras.optimizers.Adam(learning_rate=lr_schedule),
    metrics=["acc"],
)

# Definisce le callback per il model checkpoint
checkpoint_cb = keras.callbacks.ModelCheckpoint(
    "3d_image_classification.h5", save_best_only=True
)
early_stopping_cb = keras.callbacks.EarlyStopping(monitor="val_acc", patience=15)

# Addestra facendo validazione ad ogni epoca
epochs = 100
model.fit(
    train_dataset,
    validation_data=validation_dataset,
    epochs=epochs,
    shuffle=True,
    verbose=2,
    callbacks=[checkpoint_cb, early_stopping_cb],
)<jupyter_output>WARNING:tensorflow:From /home/rpirrone/virtualenvs/deeplearning/lib/python3.7/site-packages/tensorflow_core/python/ops/nn_impl.py:183: where (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
Train on 70 steps, validate on 30 steps
Epoch 1/100
70/70 - 91s - loss: 0.7720 - acc: 0.4357 - val_loss: 0.6996 - val_acc: 0.5000
Epoch 2/100
70/70 - 81s - loss: 0.7639 - acc: 0.5214 - val_loss: 0.6931 - val_acc: 0.4667
Epoch 3/100
70/70 - 81s - loss: 0.6860 - acc: 0.5857 - val_loss: 0.6933 - val_acc: 0.5000
Epoch 4/100
70/70 - 80s - loss: 0.7639 - acc: 0.4857 - val_loss: 0.7276 - val_acc: 0.4833
Epoch 5/100
70/70 - 80s - loss: 0.6564 - acc: 0.6214 - val_loss: 0.7702 - val_acc: 0.5000
Epoch 6/100
70/70 - 80s - loss: 0.7014 - acc: 0.6143 - val_loss: 0.7603 - val_acc: 0.5000
Epoch 7/100
{'fraction_non_alphanumeric': 0.11261372018686992, 'fraction_numerical': 0.03761986722399803, 'mean_word_length': 4.0672757475083055, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '308803', 'license': 'MIT', 'n_tokens_mistral': 4656, 'n_tokens_neox': 4296, 'n_words': 1099, 'path': 'Deep Learning con Keras TF.ipynb', 'repo_name': 'fredff60/Big-Data', 'size': '12201'}