Programmazione funzionale per il deep learning

Autore: Joyce Xu

Traduttrice: Sabrina Sala

 

Fino a poco tempo fa il concetto di “programmazione funzionale” e “machine learning” erano attribuiti a due mondi nettamente separati. Il primo è un paradigma di programmazione che ha guadagnato popolarità nel momento in cui si il mondo si è rivolto alla semplicità ed alla componibilità per produrre pplicazioni scalabili complesse; il secondo è uno strumento utilizzato per istruire un computer come un completamento automatico di scarabocchi e a comporre musica. Non vi era quindi dialogo tra questi due elementi.

Ma studiandoli ed approfondendoli con attenzione ci si rende conto che la loro sovrapposizione è sia pratica sia teorica. Innanzitutto, il machine learning non è un elemento a sé stante ma piuttosto, per essere sfruttato, deve essere incorporato in applicazioni scalabili complesse. In secondo luogo, il machine learning, e in particolare il deep learning, è funzionale nel suo design:

  • I modelli di deep learning sono componibili: la programmazione funzionale riguarda comporre catene di funzioni di ordine superiore per operare su semplici strutture di dati. Le reti neurali sono disegnate nella stessa maniera, concatenando funzioni di trasformazione da uno strato a quello successivo, per operare su una semplice matrice di dati di input. Infatti l’intero processo di deep learning può essere visto come l’ottimizzazione di un set di funzioni composte. Ciò comporta che i modelli in sé siano intrinsecamente funzionali.

 

  • Le componenti di deep learning sono immutabili: quando applichiamo le funzioni sui dati di input, i nostri dati non cambiano ma, piuttosto, viene prodotto un nuovo set di valori. In aggiunta, quando i pesi sono aggiornati, non hanno bisogno di essere “mutati” ma rimpiazzi da un nuovo valore. In teoria l’aggiornamento dei pesi può essere applicato in qualsiasi ordine (ovvero non sono dipendenti l’uno dall’altro) e non c’è quindi necessità di tenere traccia in maniera sequenziale del relativo cambiamento.

 

  • La programmazione funzionale offre facilità nella parallelizzazione. Ancor più importante, però, è che le funzioni che sono totalmente componibili sono facili da parallelizzare. Ciò comporta maggiori velocità e potenza computazionale. La programmazione funzionale fornisce concurrency e parallelismo a costi quasi pari a zero, rendendo così più semplice lavorare in apprendimento profondo con modelli larghi e distribuiti.

Ci sono differenti teorie e prospettiva riguardanti la combinazione tra programmazione funzionale e deep learning, sia dal punto di vista matematico sia da quello pratico, ma tuttavia alcune volte è più semplice vederla in maniera pratica. In questo articolo andremo a introdurre le idee alla base della programmazione funzionale e di come applicarle in un modello Cortex di deep learning per la classificazione dei valori anomali.

Le basi di Clojure

Prima di continuare con il tutorial su Cortex, introduciamo alcune nozioni di base in merito a Clojure. Clojure è un linguaggio di programmazione funzionale ottimo per concurrency e data processing. Per nostra fortuna, questi sono entrambi estremamente utili nel machine learning. Infatti la ragione primaria per cui utilizziamo Clojure per il machine learning è che il lavoro di preparazione dei dataset di allenamento (manipolazione dei dati, processing ecc.) può facilmente compensare il lavoro di implementazione degli algoritmi, specialmente quando si hanno librerie solide come Cortex.  Utilizzare Clojure e .edn (piuttosto che C++ e protobuf) ci da un vantaggio in termini di velocità su progetti di machine learning.

Potete trovare un’introduzione più approfondita sul linguaggio qui.

Partiamo con le basi: il codice di Clojure è formato da un insieme di espressioni. Queste sono racchiuse in parentesi e tipicamente trattate come funzioni di chiamata.

(+ 2 3)          ; => 5
(if false 1 0)   ; => 0

Ci sono 4 strutture di dati base: vettori, liste, hash-map e set. Le virgole sono considerate come spazi bianchi, quindi vengono solitamente omesse.

[1 2 3]            ; vector (ordered)
‘(1 2 3)           ; lists (ordered)
{:a 1 :b 2 :c 3}   ; hashmap or map (unrdered)
#{1 2 3}           ; set (unordered, unique values)

Il virgolettato singolo, che precede la lista, è unicamente uno strumento per far sì che essa non venga rilevata come espressione.

Clojure ha anche molte funzioni integrate per funzionare su queste strutture di dati. Parte del fascino di Clojure sta nel suo design ricco di funzioni per poche tipologie di dati molto ridotti, il che si contrappone alla comune prassi di avere poche funzioni specializzate per il maggior numero possibile di strutture dati. Clojure, essendo un linguaggio di functional programming, supporta funzioni di alto livello, il che significa che le funzioni possono essere importate come argomento su altre funzioni.

(count [a b c])              ; => 3

(range 5)                    ; => (0 1 2 3 4)

(take 2 (drop 5 (range 10))) ; => (5 6)

(:b {:a 1 :b 2 :c 3})        ; use keyword as function => 2

(map inc [1 2 3])            ; map and increment => (2 3 4)

(filter even? (range 5))     ; filter collection based off predicate => (0 2 4)

(reduce + [1 2 3 4])         ; apply + to first two elements, then apply + to that result and the 3rd element, and so forth => 10

Potremmo ovviamente scrivere le nostre funzioni in Clojure utilizzando defn. La definizione delle funzioni di Clojure seguono la forma (defn fn-name [params*] expressions) e inoltre restituiscono il valore dell’ultima espressione nel corpo.

[x]
(+ x 2))(add2 5)     ; => 7

Le espressioni “let” creano e associano le variabili all’interno dello scope lessicale, definito lexical scope, di “let”. Ciò viene fatto nell’espressione (let [a 4] (…)), in cui la variabile “a” presenta un valore di 4 solamente nelle parentesi interne. Queste variabili sono chiamate “locali”.

(defn square-and-add
[a b]
(let [a-squared (* a a)
b-squared (* b b)]
(+ a-squared b-squared)))

(square-and-add 3 4)       ; => 225

Abbiamo, infine, due modi per creare funzioni “anonime”, che possono essere assegnate a una funzionale locale o ad una di ordine superiore.

(fn [x] (* 5 x))          ; anonymous function#(* 5 %)                  ; equivalent anonymous function, where the % represents the function’s argument(map #(* 5 %) [1 2 3])    ; => (5 10 15)

Questo è tutto per le informazione di base. Ora che abbiamo imparato qualche nozione su Clojure, torniamo al machine learning.

Cortex

Cortex è scritta in Clojure, ed è attualmente una delle librerie di machine learning più vaste e in rapida crescita, che utilizza un linguaggio di programmazione funzionale. Il resto dell’articolo si focalizzerà su come costruire un modello di classificazione in Cortex, insieme ai Paradigmi di programmazione funzionale e le tecniche di arricchimento dei dati (data augmentation) richieste.

Data preprocessing

Prendiamo un dataset di frodi legate a carte di credito, fornito da questo sito. Queste dataset è molto sbilanciato, per il fatto che container solo 492 casi di fronde positivi su un totale di 248’807, in pratica lo 0,172%. Ciò ci creerà dei problemi ma, per ora, limitiamoci a guardare i dati e a vedere il funzionamento del modello.

Per assicurare l’anonimità dei dati personali, tutte le caratteristiche originarie, eccetto “time” e “amount”, sono già state trasformate in componenti principali, o PCA (dove ogni entry rappresenta una nuova variabile che contiene le informazioni più rilevanti dei dati grezzi). Una breve occhiata ai dati ci mostra che la prima variabile “time” ha un limitato contenuto informativo, quindi la lasciamo da parte. Di seguito vediamo come appare il nostro gruppo di codici:

 

(ns fraud-detection.core

(:require [clojure.java.io :as io]

[clojure.string :as string] [clojure.data.csv :as csv] [clojure.core.matrix :as mat] [clojure.core.matrix.stats :as matstats]

[cortex.nn.layers :as layers]

[cortex.nn.network :as network]

[cortex.nn.execute :as execute]

[cortex.optimize.adadelta :as adadelta]

[cortex.optimize.adam :as adam]

[cortex.metrics :as metrics]

[cortex.util :as util]

[cortex.experiment.util :as experiment-util]

[cortex.experiment.train :as experiment-train]))

(def orig-data-file “resources/creditcard.csv”)

(def log-file “training.log”)

(def network-file “trained-network.nippy”)

;; Read input csv and create a vector of maps {:data […] :label [..]},

;; where each map represents one training instance in the data

(defonce create-dataset

(memoize

(fn []

(let [credit-data (with-open [infile (io/reader orig-data-file)]

(rest (doall (csv/read-csv infile))))

data (mapv #(mapv read-string %) (map #(drop 1 %) (map drop-last credit-data))) ; drop label and time

labels (mapv #(util/idx->one-hot (read-string %) 2) (map last credit-data))

dataset (mapv (fn [d l] {:data d :label l}) data labels)]

dataset))))

 

Le reti neurali di Cortex utilizzano dati di input in forma di mappe, dove ogni mappa rappresenta un singolo dato con i relativi label associati (per esempio, se ho l’immagine di un cane, il label sarà “cane”). Una classificazione, per esempio, può apparire come [{:data [12 10 38] :label “cat”} {:data [20 39 3] :label “dog“} … ]

Nella nostra funzione per la creazione di dataset ad hoc, vediamo che nel data file di formato comma-separated value, tutte le colonne a parte l’ultima formano i nostri “data” (o caratteristiche), mentre l’ultima colonna rappresenta il label, ovvero l’etichetta. Nel frattempo, trasformiamo i label in one-hot vector (per esempio [0 1 0 0]) basati sulla classe di classificazione. Ciò perché l’ultimo strato soft max nella nostra rete neurale produce un vettore di probabilità di classe, e non il label stesso. Infine, creiamo la mappa da queste due variabili e la salviamo come dataset.

Descrizione del modello

Creare un modello in Cortex è un’operazione piuttosto semplice e diretta. Prima di tutto dobbiamo definire una mappa di parametri superiori che verrà usata successivamente durante l’allenamento. In seguito, per definire il modello, uniamo semplicemente gli strati:

 

(def params

{:test-ds-size      50000 ;; total = 284807, test-ds ~= 17.5%

:optimizer         (adam/adam)   ;; alternately, (adadelta/adadelta)

:batch-size        100

:epoch-count       50

:epoch-size        200000})

(def network-description

[(layers/input (count (:data (first (create-dataset)))) 1 1 :id :data) ;width, height, channels, args

(layers/linear->relu 20) ; num-output & args

(layers/dropout 0.9)

(layers/linear->relu 10)

(layers/linear 2)

(layers/softmax :id :label)])

Dove network-description è un vettore di strati di rete neurale. Il nostro modello consiste in:

  • Uno strato di input
  • Uno strato completamente connesso (e lineare) con la funzione di attivazione ReLu
  • Un layer dropout
  • Un altro strano completamente connesso con ReLu
  • Un strato di output di dimensione 2 , passato attraverso la funzione softmax

Nel primo e ultimo strato, dobbiamo specificare un :id. questo si riferisce alla chiave nella mappa dei dati a cui la nostra rete dovrebbe guardare. Ricordiamo che la mappa risulta come {:data […] :label […]}). Per il nostro strato di input, passiamo in :data id affinché il modello prenda i dati di allenamento nei passaggi successivi. Nello strato finale forniamo, invece, :label come :id, così che possiamo utilizzare il vero label per calcolare il nostro errore.

Allenamento e valutazione

Da qui in poi diventa più complesso. La funzione di allenamento non è in realtà così complicata: Cortex da infatti funzioni preistruite per l’allenamento di alto livello, quindi tutto ciò che dobbiamo fare è settare nostri parametri (la rete, dataset di allenamento e verifica ecc). L’unico avvertimento che vi diamo è che il sistema si aspetta un “infinito” dataset per l’allenamento. tuttavia, Cortex  presenta una funzione infinite-class-balanced-dataset che ci aiuta a trasformarlo.

(defn train

“Trains network for :epoch-count number of epochs”

[]

(let [network (network/linear-network network-description)

[train-orig test-ds] (get-train-test-dataset)

train-ds (experiment-util/infinite-class-balanced-dataset train-orig

:class-key :label

:epoch-size (:epoch-size params))]

(experiment-train/train-n network train-ds test-ds

:batch-size (:batch-size params)

:epoch-count (:epoch-count params)

:optimizer (:optimizer params)

:test-fn f1-test-fn)))

Quindi arriviamo alla parte complessa: f1-test-fn. Durante l’allenamento, infatti, la funzione train-n si aspetta che le venga fornito un :test-fn che ne verifichi la performance e che determini se debba essere salvata come “best network”. È presente una funzione test di default che individua la perdita cross-entropica (cross-entropy loss)ma il suo valore di perdita non è così semplice da interpretare e, inoltre, non si adatta perfettamente il nostro dataset poco bilanciato. Per risolvere questo problema, andremo a scrivere una nostra personale funzione test.

Ma sorge ora una domanda: come possiamo verificare le performance del nostro modello? La metrica standard nei task di classificazione è solitamente l’accuratezza ma, con il nostro dataset sbilanciato, questa misura è pressoché inutile. Poiché gli esempi positivi contano solo per lo 0,172% del dataset totale, anche un modello che si limiti a prevedere esempi negativi, potrebbe raggiungere il 99.828% di accuratezza. Una percentuale particolarmente elevata, ma nel caso in cui questo modello venisse utilizzato nella realtà, sorgerebbero diversi problemi.

Un migliore gruppo di metriche è quindi: precisione, recupero, e F1 score (o genericamente F-beta).

1 1

1 1

La precisione ci pone quindi di fronte a una domanda: “di tutti gli esempi che ho previsto essere positivi, quale proporzione è di fatto positiva?” Mentre per il recupero ci chiediamo: “di tutti gli esempi  positivi, quale proporzione ho previsto con esattezza essere positiva?”
Visualizzazione di precisione e recupero. Fonte https://en.wikipedia.org/wiki/Precision_and_recall

Il F-beta score (una generalizzazione del tradizionale F1 score) è una media ponderata di precisione e recupero, misurata in una scala da 0 a 1:

2.jpg 2

Quando beta = 1, otteniamo F1 come of 2 * (precisione * recupero) / (precisione + recupero). Solitamente beta rappresenta quante volte recupero debba essere più importante di precisione. Nel nostro modello utilizziamo il F1 score come nostro punteggio da ben approssimare ma registriamo anche i punteggi di precisione e recupero per rilevare il bilanciamento. Questo è il nostro f1-test-fn:

 

(defn f-beta

“F-beta score, default uses F1”

([precision recall] (f-beta precision recall 1))

([precision recall beta]

(let [beta-squared (* beta beta)]

(* (+ 1 beta-squared)

(try                         ;; catch divide by 0 errors

(/ (* precision recall)

(+ (* beta-squared precision) recall))

(catch ArithmeticException e

0))))))

(defn f1-test-fn

“Test function that takes in two map arguments, global info and local epoch info.

Compares F1 score of current network to that of the previous network,

and returns map:

{:best-network? boolean

:network (assoc new-network :evaluation-score-to-compare)}”

[;; global arguments

{:keys [batch-size context]}

;per-epoch arguments

{:keys [new-network old-network test-ds]} ]

(let [batch-size (long batch-size)

test-results (execute/run new-network test-ds

:batch-size batch-size

:loss-outputs? true

:context context)

;;; test metrics

test-actual (mapv #(vec->label [0.0 1.0] %) (map :label test-ds))

test-pred (mapv #(vec->label [0.0 1.0] % [1 0.9]) (map :label test-results))

precision (metrics/precision test-actual test-pred)

recall (metrics/recall test-actual test-pred)

f-beta (f-beta precision recall)

;; if current f-beta higher than the old network’s, current is best network

best-network? (or (nil? (get old-network :cv-score))

(> f-beta (get old-network :cv-score)))

updated-network (assoc new-network :cv-score f-beta)

epoch (get new-network :epoch-count)]

(experiment-train/save-network updated-network network-file)

(log (str “Epoch: ” epoch “\n”

“Precision: ” precision  “\n”

“Recall: ” recall “\n”

“F1: ” f-beta “\n\n”))

{:best-network? best-network?

:network updated-network}))

La funzione esegue la rete sul test set, calcola il F1 score e, infine, aggiorna e salva la rete di conseguenza. Inoltre, stampa ognuna delle nostre metriche di valutazione ad ogni punto. Se ora eseguissimo il REPL, avremmo uno score che sarebbe all’incirca così:

Epoch: 30
Precision: 0.2515923566878981
Recall: 0.9186046511627907
F1: 0.395

Un risultato piuttosto scarso.

Arricchimento dei dati (Data Augmentation)

Eccoci arrivati al problema di cui abbiamo parlato all’inizio di questo articolo, dovuto allo sbilanciamento del dataset. Il modello non presenta ora sufficienti esempi positivi da cui apprendere. Quando richiamiamo experiment-util/infinite-class-balanced-dataset nella nostra funzione di allenamento, stiamo di fatto creando centinaia di copie di ogni istanza di allenamento positiva per bilanciare il nostro dataset. Ne risulta che il modello memorizza quei valori di caratteristiche, piuttosto che apprendere la distinzione tra le classi.

Un modo per ovviare a questo problema è utilizzare l’arricchimento dei dati, con il quale generi amo dati aggiuntivi e artificiale basati sugli esempi che già abbiamo disponibili. Per creare degli esempi di allenamento che siano positivi, dobbiamo aggiungere una qualsiasi quantità di rumore ai delle features vectors (vettori contenenti le caratteristiche) per ogni esempio positivo esistente. La quantità di rumore da noi aggiunta dipenderà dalla varianza di ogni caratteristica nelle classi positive, in modo che le feature con un’ampia varianza siano arricchite con una grande quantità di rumore, e l’opposto per quelle con bassa varianza.

Di seguito il codice per l’arricchimento dei dati:

(defonce get-scaled-variances

(memoize

(fn []

(let [{positives true negatives false} (group-by #(= (:label %) [0.0 1.0]) (create-dataset))

pos-data (mat/matrix (map #(:data %) positives))

variances (mat/matrix (map #(matstats/variance %) (mat/columns pos-data)))

scaled-vars (mat/mul (/ 5000 (mat/length variances)) variances)]

scaled-vars))))

(defn add-rand-variance

“Given vector v, add random vector based off the variance of each feature”

[v scaled-vars]

(let [randv (map #(- (* 2 (rand %)) %) scaled-vars)]

(mapv + v randv)))

(defn augment-train-ds

“Takes train dataset and augments positive examples to reach 50/50 balance”

[orig-train]

(let [{train-pos true train-neg false} (group-by #(= (:label %) [0.0 1.0]) orig-train)

pos-data (map #(:data %) train-pos)

num-augments (- (count train-neg) (count train-pos))

augments-per-sample (int (/ num-augments (count train-pos)))

augmented-data (apply concat (repeatedly augments-per-sample

#(mapv (fn [p] (add-rand-variance p (get-scaled-variances))) pos-data)))

augmented-ds (mapv (fn [d] {:data d :label [0 1]}) augmented-data)]

(shuffle (concat orig-train augmented-ds))))

augment-train-ds prende il nostro dataset di allenamento originario, calcola il numero di arricchimenti necessario per raggiungere un bilanciamento di classe 50/50, e applica poi questi arricchimenti ai nostri esempi esistenti attraverso un qualsiasi vettore di rumore (add-rand-variance) basato sulla varianza consentita (get-scaled-variances). Infine, concateniamo gli esempi arricchiti con il dataset originario ed otteniamo un dataset bilanciato.

Durante l’allenamento, il modello rileva una quantità irrealisticamente ampia di esempi positivi, mentre il set di verifica rimarrà positivo al 0.172%. come risultato, nonostante il modello potrà essere in grado di apprendere in modo migliore la differenza tra le due classi, comunque overfitteremo la nostra classe con di esempi positivi. Per risolvere tale problema, possiamo cambiare le relative soglie in modo da forzare la predizione positiva durante il testing. In altre parole, piuttosto che richiedere al modello almeno il 50% di certezza della positività degli esempi per classificarli come tali, possiamo aumentare la richiesta ad almeno il 70%. Dopo alcuni test, abbiamo notato che la soglia minima dovrebbe essere impostata al 90%. Il codice può essere trovato in  vec->labelfunction nel codice sorgente, ed è richiamato alla linea 31 del f1-test-fn.

Usando il nuovo e arricchito dataset per l’allenamento, i nostri scores si presentano all’incirca così:

Epoch: 25
Precision: 0.8658536585365854
Recall: 0.8255813953488372
F1: 0.8452380952380953

Risultati nettamente migliori rispetto a quelli precedenti.

Conclusioni

Questo modello può ovviamente essere ancora migliorato. Di seguito, alcuni consigli:

  • Tutte le PCA feature sono informative? Osservate la distribuzione dei valori per gli esempi positivi e negativi attraverso le feature e scartate ogni feature che non aiuta a distinguere tra le due classi
  • Vi sono altre architetture di reti neurali, funzioni di attivazione ecc che performano meglio?
  • Vi sono diverse tecniche di arricchimento dei dati che potrebbero performare meglio?
  • Come si rapporta la performance del modello in Cortex rispetto a Keras/Tensorflow/Theano/Caffe?

L’intero codice sorgente per questo progetto può essere trovato qui. Vi incoraggiamo a sperimentare gli step successivi, ad esplorare differenti architetture di reti (qui c’è un ottimo esempio di classificazione di immagini su CNN che potete prendere come riferimento).

 

Utilizzo di deep learning per il riconoscimento di oggetti

Autrice: Joyce Xu

Traduttrice: Sabrina Sala

 

 

1

 

Con la comparsa di veicoli a guida autonoma, sistemi di videosorveglianza intelligenti e varie applicazioni come quella del conteggio delle persone, la richiesta di sistemi di riconoscimento facciale è ormai in continua crescita. Questi sistemi riguardano non solo il riconoscimento e la classificazione degli oggetti presenti nelle immagini, ma anche la localizzazione di ciascuno di essi, tracciando attorno ad essi un appropriato riquadro di delimitazione. Ciò ha reso il sistema di riconoscimento di oggetti un compito sempre più complesso rispetto al suo predecessore nell’ambito della visione artificiale: la classificazione di immagini.

Fortunatamente gli approcci più appropriati per il riconoscimento di oggetti sono, attualmente, estensioni di modelli per la classificazione di immagini. Qualche mese fa Google ha rilasciato una nuova API (interfaccia di programmazione di un’applicazione) di riconoscimento di oggetti in Tensorflow. In questa release troviamo modelli pre-addestrati con i relativi pesi:

Nello scorso (METTERE LINK SU SITO DLI) articolo, abbiamo parlato delle tre architetture di reti neurali sopra citate (MobileNets, Inception e ResNet), oggi affronteremo invece i modelli di riconoscimento di oggetti per Tensorflow: Faster R-CNN, R-FCN e SSD. Attraverso questo articolo capiremo come il deep learning è applicato a questi modelli per il riconoscimento di oggetti, come questi ultimi si differenzino tra loro e quali siano i punti in comune.

Faster R-CNN

La faster R-CNN è ormai un modello di riferimento per la rilevazione di oggetti basata sul deep learning, che ha ispirato molti modelli successivi di rilevazione e segmentazione, inclusi i due che andremo ad esaminare. Sfortunatamente non possiamo comprendere appieno la Faster R-CNN senza analizzare R-CNN e Fast R-CNN, sue predecessori.

R-CNN

Possiamo certamente dire che la R-CNN, Region-based Convolutional Neural Network, è la rete che ha dato il via ai giochi. Consiste in tre fasi:

  1. Scansione dell’immagine di input per la rilevazione di possibili oggetti, utilizzando un algoritmo chiamato Selective Search, che estrae approssimativamente 2000 regioni di questa immagine (Region Proposal)
  2. Esecuzione della rete neurale convoluzionale (CNN) in cima a ciascuna regione.
  3. Estrapolazione dell’output da ogni CNN per immetterlo dentro ad:
    1. una SVM (Support Vector Machine) per classificare la regione
    2. Effettuare una regressione lineare per restringere il riquadro di delimitazione dell’oggetto, se esistente.

Le fasi sono illustrate (dal basso verso l’alto) nella figura sottostante

2

2

In altre parole, prima generiamo le possibili regioni, quindi estraiamo le caratteristiche e infine classifichiamo quelle regioni sulla base delle caratteristiche estratte. In sostanza, abbiamo trasformato la rilevazione di oggetti in un problema di classificazione. La R-CNN risulta molto intuitiva ma, sfortunatamente, anche molto lenta.

Fast R-CNN

Lo sviluppo immediatamente successivo è la Fast R-CNN. Questa ricorda l’originale in molte delle sue caratteristiche ma è migliore in termini di velocità di rilevazione per due aspetti:

  1. L’estrazione di caratteristiche viene effettuata prima di estrarre le regioni di interesse, ma utilizzando solo una CNN per l’intera immagine al posto che 2000 CNN sulle 2000 regioni sovrapposte.
  2. La SVM viene rimpiazzata da una funzione softmax e, inoltre, la rete neurale viene utilizzata anche per le previsioni anziché creare un nuovo modello.

Il modello appare similmente a:

 

3 1

Come possiamo vedere dall’immagine, stiamo ora generando proposte regionali basate non sull’immagine originale, ma piuttosto sull’ultima mappa delle caratteristiche estrapolate dalla rete. Ciò ci permette di allenare una sola CNN per l’intera immagine.

In aggiunta, al posto che allenare differenti SVM per classificare ogni oggetto, vi è un singolo strato softmax che genera direttamente la probabilità per la classe di riferimento. Quindi, abbiamo ora una sola rete neurale da allenare, a differenza di quanto succedeva prima dove invece c’era una rete neurale affiancata a molteplici SVM.

La Fast R-CNN performa, perciò, molto meglio in termini di velocità. Tuttavia, rimane un grosso difetto: l’algoritmo di selective search per proporre le possibili regioni.

Faster R-CNN

Siamo giunti ora all’obiettivo iniziale del nostro articolo: Faster R-CNN. L’intuizione è stata quella di rimpiazzare il lento algoritmo di selective search con una rete neurale veloce. Nello specifico essa ha introdotto la region proposal network (RPN), rete di proposta regionale.

Come funziona la RPN:

  • All’ultimo strato di una CNN iniziale, una finestra scorrevole 3×3 si muove attraverso la mappa delle caratteristiche per mapparla poi in una dimensione inferiore (ad esempio 256-d).
  • Per ogni posizione della finestra scorrevole, la RPN genera molteplici regioni possibili basate su vincoli spaziali di dimensioni fisse chiamati riquadri di ancoraggio (anchor boxes).
  • Ogni proposta regionale consiste in:
    • un punteggio (score) per la presenza dell’oggetto in quella determinata regione
    • 4 coordinate rappresentanti il riquadro di delimitazione della regione.

In altre parole, guardiamo alla nostra regione nell’ultima mappa delle caratteristiche, considerando i differenti k riquadri di ancoraggio attorno ad essa. Per ciascun riquadro viene visualizzata l’eventualità che contenga un oggetto e quali siano le coordinate del riquadro. Nell’immagine è rappresentato come appare dalla posizione di una finestra scorrevole:

 

4 1

4 1

Il punteggio 2k rappresenta la probabilità data da softmax per ciascun riquadro k per la presenza di un oggetto. Da notare è che, nonostante la RPN elabori le coordinate dei riquadri di delimitazione, essa non classifica comunque i possibili oggetti: ha il solo scopo di individuare regioni in cui siano presenti oggetti e quindi comunicare le coordinate dei relativi riquadri. Se un riquadro di ancoraggio ha un punteggio, relativo alla presenza di un oggetto, superiore a una determinata soglia, allora quel dato riquadro verrà selezionato come possibile regione.

Avendo ora le nostre possibili regioni, le introduciamo direttamente nella Fast R-CNN. Aggiungiamo uno strato di Pooling, alcuni strati completamente connessi, infine uno strato di classificazione softmax e un regressore dei riquadri di delimitazione (bounding box regressor). Possiamo dire, in un certo senso, che Faster R-CNN = RPN + Fast R-CNN.

 

5 1

5 1

La Faster R-CNN raggiunge così miglior velocità e accuratezza. Nonostante siano stati molteplici i tentativi successivi di aumentare la velocità di riconoscimento degli oggetti, solo pochi modelli sono stati davvero in grado di superare questa rete. In altre parole, la Faster R-CNN non rappresenta certo il metodo più semplice e veloce per la object detection ma presenta tuttora una delle migliori performance. Quindi, la Faster R-CNN in Tensorflow con la Inception ResNet è il modello più lento ma anche il più accurato.

Nonostante la Faster R-CNN possa sembrare complessa, è possibile notare che il cuore della sua struttura è il medesimo di quello originario della R-CNN: identificare possibili regioni di oggetti e classificarle. Questo è l’approccio dominante della maggior parte degli odierni modelli di rilevazione di oggetti, incluso quello che andremo ad analizzare ora.

R-FCN

Ritorniamo per un momento indietro. Abbiamo detto che la Fast R-CNN ha migliorato di molto la sua velocità di rilevazione condividendo una singola CNN per tutte le regioni. Questo meccanismo di base è lo stesso della R-FCN: aumentare la velocità massimizzando il numero di parametri condivisi.

La Region-based FullyConvolutional Net (R-FCN) condivide la totalità della computazione per ogni singolo output.

Da una parte, quando classifichiamo un oggetto vogliamo che il modello risulti invariante alle traslazioni, in modo da identificarlo ovunque esso appaia. Dall’altra, abbiamo però la necessità di conoscere anche un’eventuale varianza alle traslazioni: se il nostro oggetto si trova nell’angolo in alto a sinistra, il modello deve tracciare un riquadro di delimitazione in quel determinato punto. Perciò, come trovare un compromesso tra i due estremi, varianza e invarianza?

La soluzione proposta dalla R-FCN è rappresentata dalle position-sentitive score maps: mappe dei punteggi (relative alle features, estratte tramite convoluzioni, alla quale è attribuito un punteggio in base alla presenza della data classe) sensibili alla posizione degli oggetti che intendiamo classificare.

Ognuna di queste mappe rappresenta una posizione relativa di una classe di oggetti. Per esempio, una mappa dei punteggi potrebbe attivarsi qualora identifichi la parte in alto a destra di un gatto, mentre un’altra potrebbe attivarsi quando rileva la parte inferiore sinistra di una macchina. Essenzialmente queste score maps sono mappe di caratteristiche convoluzionali che sono state allenate a riconoscere determinate parti di ogni oggetto.

La R-FCN funziona in questo modo:

  1. Eseguire una CNN (in questo caso una ResNet) sull’immagine di input.
  2. Aggiungere uno strato convoluzionale che coinvolga tutte le mappe generate dal passaggio precedente in modo da ottenere un insieme di punteggi complessivi dell’immagine. Ottenendo k²(C+1) mappe di punteggio, dove k² rappresenta il numero di posizioni relative per dividere un oggetto (per esempio 3² per una griglia 3×3), e dove C+1 rappresenta il numero di classi più lo sfondo.
  3. Eseguire una RPN per generare le regioni di interesse (RoI)
  4. Dividere ciascuna RoI nelle medesime k² sottoregioni di cui è composta la mappa dei punteggi
  5. Dobbiamo ora verificare se nell’insieme dei punteggi complessivi, ogni sottoregione combaci con la corrispondente posizione relativa di un oggetto. Per esempio, se vogliamo considerare la sottoregione in alto a sinistra, dobbiamo prendere la mappa dei punteggi che corrisponde all’angolo in alto a sinistra di un oggetto e farne una media all’interno della RoI. Questo processo è ripetuto per ogni classe.
  6. Dopo che ogni k² sottoregione ha un valore di “object match” per ogni classe, si fa una media delle sottoregioni per ottenere un punteggio per ogni classe.
  7. Classificare le RoI con una funzione softmax sui rimanenti C+1 vettori dimensionali.

Una R-FCN, avente una RPN che genera le RoI, apparirà all’incirca così:

6 1

6 1

Capire come funzioni questo modello è più semplice se si può visualizzare concretamente ciò che fa. Qui di seguito abbiamo un esempio del suo funzionamento mentre cerca di individuare un neonato.

 

 

 

 

7Figura 4: visualizzazione di una RoI che non si sovrappone correttamente all’oggetto

In parole semplici: la R-FCN considera ogni regione, suddividendole poi in sottoregioni e chiedendosi per ciascuna di queste se corrispondano o meno alla relativa porzione dell’immagine di input. Ad esempio “assomiglia alla porzione in centro a destra di un bambino?”. Ciò viene ripetuto per ogni classe. Se la maggior parte delle sottoregioni risulta corrispondente, allora la RoI viene classificata come neonato, dopo che la funzione softmax viene eseguita su tutte le classi.

In questo modo, la R-FCN è capace di identificare contemporaneamente la varianza alle traslazioni, proponendo differenti regioni di oggetti, e l’invarianza alle traslazioni attraverso il riferimento di ogni regione alle stesse mappe di punteggio. Queste mappe di punteggio dovrebbero imparare a classificare un gatto come tale, ovunque esso appaia. Inoltre, è completamente convoluzionale: ciò significa che la computazione è condivisa nell’intera rete.

Ciò permette alla R-FCN di essere nettamente più veloce rispetto alla Faster R-CNN, raggiungendo comunque lo stesso grado di accuratezza.

SSD

L’ultimo modello che andremo ad analizzare è la Single-Shot Detector (SDD). Come la R-FCN ha raggiunto una velocità molto maggiore rispetto alla Faster R-CNN ma in modo nettamente diverso.

I primi due modelli eseguono region proposal e region classification in due fasi distinte. Inanzitutto, utilizzano una RPN (region proposal network) per generare le regioni di interesse, successivamente classificano queste regioni attraverso i propri strati interamente connessi o gli strati convoluzionali sensibili alla posizione degli oggetti da classificare. La SSD unisce questi due processi in un singolo passaggio, prevedendo simultaneamente i riquadri di delimitazione e le classi, nel momento in cui esamina l’immagine.

Data un’immagine di input ed un insieme di etichette al dataset, la SSD agisce in questo modo:

  • Passa l’immagine attraverso una serie di strati convoluzionali, producendo mappe delle caratteristiche a scale differenti (per esempio 10×10, poi 6×6 e ancora 3×3 ecc.)
  • Per ogni posizione in ciascuna di queste mappe delle caratteristiche, usa un filtro convoluzionale 3×3 per valutare un piccolo set di riquadri di delimitazione predefiniti. Questi ultimi sono l’equivalente dei riquadri di ancoraggio (anchor boxes) della Faster R-CNN.
  • Per ogni riquadro prevede simultaneamente:
    1. Il riquadro del bounding box
    2. La probabilità per ogni classe.
  • Durante l’allenamento fa corrispondere il riquadro effettivo con quello predetto sulla base del metodo Intersection over Union (IoU), ovvero l’Indice di Jaccard. I riquadri meglio predetti verranno etichettati come “positivi”, come anche tutti gli altri riquadri che abbiano un valore IoU >0.5.

L’allenamento della SSD ha però una difficoltà peculiare rispetto ai precedenti modelli. Nei primi due modelli, la RPN permetteva di prendere in considerazione solo ciò che aveva anche solo la minima possibilità di essere un oggetto. Con la SSD, invece, questo primo filtro viene eliminato: esso classifica e traccia riquadri di delimitazione in ogni singola posizione nell’immagine, usando riquadri di differenti forme e di molteplici scale. Ciò ha come risultato l’avere un numero molto più elevato di riquadri dove, però, la maggior parte di questi è negativo (dal momento che non rileva oggetti).

La SSD corregge questo problema in due modi. Per prima cosa utilizza la non-maximum suppression (NMS) per raggruppare diversi riquadri sovrapposti in un unico riquadro. Quindi se ad esempio quattro riquadri di simile forma e grandezza contengono lo stesso oggetto, la NMS permette di tenere il riquadro migliore, il più accurato, e di scartare i restanti. In secondo luogo, utilizza una tecnica chiamata hard negative mining per bilanciare le classi durante l’allenamento. Con questa tecnica, solo un sottogruppo degli esempi negativi con la più elevata loss in fase di training (i cosiddetti falsi positivi) è utilizzato per ogni iterazione in fase di allenamento. La SSD mantiene un rapporto 3:1 tra negativi e positivi.

La sua architettura appare così:

 

8

Come detto poco sopra, vediamo esserci degli “extra feature layers”. Queste mappe delle caratteristiche dalle misure variabili aiutano a individuare oggetti di diverse dimensioni. Eccone un esempio:

9

In mappe delle caratteristiche di dimensioni più piccole (4×4), ogni riquadro ricopre una regione più ampia dell’immagine, permettendo così di individuare oggetti più grandi. La region proposal e la classificazione sono perciò eseguite nello stesso momento: avendo p classi di oggetti, ogni riquadro di delimitazione è associato a un vettore dimensionale (4+p) che produce 4 riquadri di coordinate e p probabilità di classe. In ultima istanza, viene utilizzata anche qui una funzione sotmax per classificare gli oggetti.

Possiamo perciò dire che la SSD non è poi così diversa rispetto ai due modelli precedenti. Tuttavia omette il passaggio di individuazione delle regioni, considerando ogni riquadro in ogni posizione dell’immagine insieme alla sua classificazione. Eseguendo tutti questi processi simultaneamente, la SSD è di certo il più veloce tra i tre modelli presi in considerazione.

Conclusioni

La Faster R-CNN, la R-FCN, e la SSD sono tre dei migliori e più usati modelli per la rilevazione di oggetti. Tutti fanno affidamento su reti neurali convoluzionali per le prime fasi del processo e seguono, all’incirca, lo stesso metodo di proposta delle regioni e classificazione.

 

Guida alle architetture di reti profonde

Autrice: Joyce Xu

Traduttrice: Sabrina Sala

 

 

1

GoogLeNet, 2014

Negli ultimi anni, molto del progresso ottenuto nell’utilizzo del deep learning per la visione artificiale può essere ricondotto a un gruppo ristretto di architetture di reti neurali. Lasciando da parte la matematica, i codici e i dettagli dell’implementazione, in questo articolo intendiamo analizzare come questi modelli funzionino.

Al momento, Keras domina con sei di questi modelli pre-addestrati già inseriti all’interno della libreria.

  • VGG16
  • VGG19
  • ResNet50
  • Inception v3
  • Xception
  • MobileNet

Le reti VGG, insieme alla precedente AlexNet del 2012, segue lo schema archetipico delle reti convoluzionali classiche: una serie di convoluzioni, max-pooling, strati di attivazione e infine alcuni strati di classificazione completamente connessi. MobileNet è, essenzialmente, una versione semplificata dell’achitettura di Xception, ottimizzata per le applicazioni mobile. Tuttavia, sono stati i rimanenti tre modelli a ridefinire davvero il modo in cui attualmente guardiamo alle reti neurali. Questo articolo si focalizzerà quindi sull’intuizione avuta con le architetture di ResNet, Inception e Xception, e sul perché esse sino diventate il punto di riferimento per i successivi studi di visione artificiale.

ResNet

ResNet è nata da una semplice osservazione: “perché aggiungendo ulteriori strati a reti neurali profonde l’accuratezza non migliora ma, addirittura, peggiora?”

Intuitivamente, reti neurali più profonde non dovrebbero performare peggio di quelle poco profonde, o almeno non durante l’allenamento quando non vi è alcun rischio di overfitting. Prendiamo una rete di esempio con n strati che raggiungono una certa accuratezza. Come minimo, una rete con n+1 strati dovrebbe essere in grado di raggiungere lo stesso grado di accuratezza, copiando i primi n strati ed eseguendo un identity mapping per l’ultimo strato. Allo stesso modo, reti di n+2, n+3 e n+4 strati possono, con lo stesso metodo, ottenere la medesima accuratezza. Tuttavia, al crescere della profondità della rete questo non è sempre vero.

Gli sviluppatori di ResNet hanno ricondotto questo problema all’ipotesi che le mappature dirette sono difficili da allenare. Hanno così proposto un rimedio: a posto che cercare di apprendere da mappature sottostanti da x di H(x), è possibile invece apprendere la differenza tra i due, ovvero il “residuale” e, successivamente, aggiustare quest’ultimo all’input.

Poniamo che il residuale sia F(x)=H(x)-x. Ora la nostra rete cerca di apprendere da F(x)+x.

Ciò ha dato vita a ai famosi blocchi di ResNet (rete residuale):

 

2.jpg

ResNet block

 

Ogni “blocco” di ResNet consiste in una serie di strati e una identity mapping che aggiunge l’input del blocco al su output. Questa operazione di “addizione” viene fatta elemento per elemento. E nel caso in cui l’input e l’output siano di dimensioni differenti, possono essere utilizzate le tecniche di zero-padding o di proiezioni (attraverso convoluzioni 1×1) per creare dimensioni corrispondenti.

Ritornando alla nostra rete esempio, questo metodo può semplificare notevolmente la costruzione di strati di identità di cui è composta. È infatti molto più semplice imparare a spingere F(x) verso 0 e lasciare l’output come x. ResNet fornisce, quindi, agli strati un punto di riferimento x da cui apprendere, piuttosto che partire da zero da una trasformazione di identità.

Questa idea è davvero efficace nella pratica. Prima di questa, nelle reti neurali profonde si riscontrava un problema di annullamento del gradiente, la cui discesa, data dalla minimizzazione della funzione di errore, si riduce esponenzialmente attraverso la retropropagazione degli strati precedenti. In sostanza, la lunga strada attraverso gli strati precedenti rendeva i segni degli errori talmente piccoli da non permettere alla rete di apprendere. Ciò nonostante, grazie all’innovazione introdotta da ResNet, descritta sopra, possiamo ora costruire reti di innumerevoli strati (potenzialmente anche più di mille) che abbiano un elevato grado di accuratezza. Questo è stato un grande passo in avanti, considerato che il modello più profondo creato fino ad allora, era di 22 strati (vincitore della competizione ILSVRC del 2014).

Sono stati pubblicati molti articoli successivi sul deep learning ma senza grandi miglioramenti. ResNet rimane, fondamentalmente, il modello che ha cambiato la nostra comprensione delle reti neurali e di come esse apprendano.

La rete di 1000+ strati è open source! La potete travare qui.

Inception

Se ResNet si concentra sulla profondità, Inception Family™ è invece focalizzata sull’estensione. In particolare, gli sviluppatori di Inception erano interessati all’efficienza computazionale dell’allenamento di reti più larghe. In altre parole: come possiamo aumentare la larghezza delle reti neurali senza eccedere la capacità computazionale di un elaboratore?

Il lavoro originario riguardava una componente nota con il nome di “Inception model”. Il cuore di questo modello, racchiudeva due elementi innovativi.

  1. La prima intuizione riguarda operazioni sugli strati. In una tradizionale CNN, ogni strato estrae informazioni dallo strato precedente al fine di trasformare i dati di origine in un differente tipo di informazione. L’output di una convoluzione 5×5 estrae caratteristiche differenti da quelle di un 3×3 o di un max-pooling e così via. Per ogni strato, come possiamo quindi intuire quale kernel fornisca informazioni più rilevanti?

Perché non lasciare quindi che sia il modello a scegliere?

Un modulo Inception elabora molteplici e differenti trasformazioni sugli stessi dati di input, in contemporanea, concatenando i risultati in un singolo output. In altre parole, ogni strato “Inception module” consiste in una convoluzione 5×5, una 3×3 e un max-pool.  È lasciato allo strato successivo la selezione delle features più rilevanti.

3

3

La maggiore densità di informazione dell’architettura di questo modello ha un problema rilevante: il drastico aumento della capacità computazionale necessaria. Non solo i filtri convoluzionali più larghi (es 5×5) sono impegnativi e onerosi da calcolare, ma anche la sovrapposizione di differenti filtri incrementa il numero di mappe delle caratteristiche per ogni strato. Ciò risulta un vero impedimento per il nostro modello.

Vediamola in un altro modo: per ogni filtri che aggiungiamo, dobbiamo eseguire una convoluzione su tutte le mappe di input per calcolare un singolo output. Nell’immagine sottostante vediamo come la creazione di una mappa di output da un singolo filtro implichi la computazione su ogni singola mappa degli strati precedenti.

4

Poniamo che vi siano M mappe di input.  Per ogni filtro aggiuntivo, dobbiamo farlo convolvere lungo tutte le M mappe di input; allo stesso modo, se abbiamo ulteriori N filtri, dovremmo farli convolvere lungo tutte le N*M mappe. In altre parole, “ogni aumento uniforme nel numero di [filtri] risulta in una aumento al quadrato della computazione”. Il nostro modulo Inception ha così triplicato o quadruplicato il numero di filtri. Dal punto di vista computazionale, è un vero guaio.

Ciò porta alla seconda intuizione: usare convoluzioni 1×1 per una riduzione della dimensionalità. Al fine di risolvere la difficoltà computazionale sopra descritta, gli sviluppatori di Inception hanno usato le convoluzioni 1×1 per “filtrare” la profondità degli output. Queste convoluzioni prendo in considerazione un valore alla volta, ma attraverso molteplici canali, e possono inoltre Estrarre informazioni spaziali e comprimerle in una dimensione minore. Per esempio, usando 2° filtri 1×1, un input di dimensione 64x64x100 (con 100 mappe delle caratteristiche) può essere compresso a 64x64x20. Riducendo il numero di mappe di input, gli sviluppatori sono stati in grado di sovrapporre parallelamente differenti trasformazioni di strati, con il risultato di avere reti che erano, al tempo stesso, profonde (avendo numerosi strati) e larghe (con operazioni parallele).

5

5

a) modulo Inception, prima versione b) modulo Inception con riduzione della dimensionalità

la prima versione di Inception, chiamata “GoogLeNet” è quella citata in precedenza da 22 strati che ha vinto la competizione ILSVRC del 2014. Inception v2 e v3, sviluppate un anno dopo, sono migliori rispetto alla versione precedente per diversi fattori: il più rilevante è il refactoring di convoluzioni larghe in altre più piccole e consecutive, che siano quindi più semplici da apprendere. Ad esempio nella Inception v3, la convoluzione 5×5 è stata sostituita da due convoluzione consecutive 3×3.

Inception è diventata rapidamente un riferimento per l’architettura dei modelli successivi. La sua ultima versione, la v4, aggiunge anche connessioni residuale tra ogni modulo, creando un ibrido tra Inception e ResNet. Ciò nonostane, il fattore più importante è che questo modello dimostra la forza di architetture “network-in-network” ben concepite, raggiungendo un nuovo livello nella legittimazione del potere delle reti neurali.

Da notare è che la ricerca sulle versioni successive v2 e v3 è stata pubblicata il giorno immediatamente successivo alla data di uscita della ricerca su ResNet.

Xception

Xception, “extreme inception”, a differenza delle precedenti architetture, ha mutato la nostra comprensione delle reti neurali, in particolare le reti convoluzionali. Come suggerisce il nome, porta il principio di Inception al suo estremo.

L’ipotesi è stata: “le correlazioni spaziali e quelle tra i canali (B/N, RGB) sono sufficientemente svincolati da rendere preferibile non mapparle insieme”

Ciò significa che in una CNN tradizionale, gli stati convoluzionali individuavano correlazioni tra spazio e profondità. Nell’immagine seguente possiamo rivederne il funzionamento.

4

Il filtro considera simultaneamente una dimensione spaziale (ogni quadrato colorato 2×2) e una cross-channel o dimensione di “profondità” (la pila di quattro quadrati). Allo strato di input di un’immagine, questo è l’equivalente di un filtro convoluzionale che esamina un gruppo di pixel 2×2 attraverso tutti i tre canali RGB. Quindi ci poniamo una domanda: che ragione abbiamo di considerare la regione di immagine e i canali simultaneamente?

 

6

Inception comincia a separare questi due elementi: grazie a convoluzioni 1×1 proietta l’input originale in molteplici spazi di input di dimensioni minori, e da ognuno di questi utilizza differenti filtri per trasformare quei piccoli blocchi tridimensionali di dati. Xception va anche oltre: piuttosto che dividere i dati di input in molteplici gruppi compressi, mappa separatamente per eseguire, successivamente, una convoluzione 1×1 in profondità per cogliere le correlazioni cross-channel. Questa operazione è conosciuta con il nome di “depthwise separable convolution” che consiste in una convoluzione spaziale (depthwise convolution) eseguita in modo indipendente per ogni canale, seguita da una convoluzione 1×1 (pointwise convolution) tra i canali. È quindi una ricerca di correlazioni dapprima in uno spazio bidimensionale e, successivamente, in uno spazio unidimensionale. Questa mappatura 2D+1D è più semplice da apprendere, rispetto a una completamente 3D.

 

Xception supera le prestazioni di Inception v3 sui dataset di ImageNet, e lo fa altrettanto bene con dataset per la classificazione di immagini di 17mila classi. In aggiunta, ha lo stesso numero di parametri di modello di Inception, permettendo così un’ottima efficienza. Nonostante Xception sia molto recente, la sua architettura è già entrata a far parte delle applicazioni di Google per la mobile vision, attraverso MobileNet.

Da notare è che lo sviluppatore di Xception, François Chollet, è anche l’autore di Keras.

Prospettive e applicazioni

La conoscenza di ResNet, Inception e Xception è diventata ormai sempre più importante, sia in ambito di ricerca che in quello aziendale. Un’applicazione interessante è il transfer learning, una tecnica del machine learning in cui vengono applicate conoscenze prese da una fonte (prendiamo in esempio un modello ResNet allenato) ad un dominio target . Ciò generalmente comprende due azioni: inizializzare un modello con pesi pre-istruiti da ResNet, Inception ecc, utilizzandolo sia per l’estrazione di caratteristiche, sia per l’ottimizzazione (fine-tuning) degli ultimi strati su un nuovo dataset. Attraverso il transfer learning questi modelli possono essere riutilizzati per qualsiasi operazione ad esso correlata, dal rilevamento di oggetti per veicoli a guida autonoma, alla creazione di didascalie e sottotitoli per registrazioni video.

Potete trovare una guida di Keras ai modelli di fine-tuning qui.

 

Introduzione agli autoencoder

Autore: Nathan Hubens

Linkedind: https://www.linkedin.com/in/nathan-hubens

Traduttrice: Sabrina Sala

 

Gli autoencoder sono reti neurali con lo scopo di generare nuovi dati dapprima comprimendo l’input in uno spazio di variabili latenti e, successivamente, ricostruendo l’output sulla base delle informazioni acquisite. Questa tipologia di network è composta da due parti:

  1. Encoder: la parte della rete che comprime l’input in uno spazio di variabili latenti e che può essere rappresentato dalla funzione di codifica h=f(x).
  2. Decoder: la parte che si occupa di ricostruire l’input sulla base delle informazioni precedentemente raccolte. È rappresentato dalla funzione di decodifica r=g(h).

1 3

Architettura di un autoencoder.

 

L’autoencoder nel suo complesso può quindi essere descritto dalla funzione d(f(x)) = r dove r è quanto più simile all’input originale x.

 

Perché copiare l’input in output?

Quello che speriamo è che, allenando l’autoencoder a copiare l’input, lo spazio di variabili latenti h possa assumere delle caratteristiche a noi utili.

Questo può essere ottenuto imponendo dei limiti all’azione di codifica, costringendo lo spazio h a dimensioni minori di quelle di x. In questo caso l’autoencoder viene chiamato undercomplete. Allenando lo spazio undercomplete, portiamo l’autoencoder a cogliere le caratteristiche più rilevanti dei dati di allenamento. Se non le diamo sufficienti vincoli, la rete si limita al compito di copiare l’input in output, senza estrapolare alcuna informazione utile sulla distribuzione dei dati. Ciò può accadere anche quando la dimensione del sottospazio latente ha la stessa grandezza dello spazio di partenza; nel caso di autoencoder overcomplete, quando le dimensioni dello spazio di variabili latenti è maggiore degli input. In questi casi anche con semplici encoder e docoder lineari possiamo copiare l’input in output senza imparare nulla del dato.

Idealmente è possibile, quindi, allenare con successo una qualsiasi architettura basata su autoencoder scegliendo opportunamente i parametri e la capacità di ciascun encoder-decoder in base alla complessità del dato da modellare.

 

L’utilizzo degli autoencoder

Ad oggi, riduzione del rumore e riduzione della dimensionalità per la visualizzazione dei dati sono considerati le applicazioni più interessanti degli autoencoder. Con l’appropriato settaggio della dimensionalità e dei relativi vincoli sulla dispersione del dato, attraverso gli autoencoder si possono ottenere proiezioni in sottospazi di interesse maggiore rispetto a metodi lineari come le PCA.

Gli autoencoder si allenano automaticamente attraverso dati di esempio. Ciò significa che è facile allenare la rete in modo da performare bene su simili tipologie di input, senza la necessità di generalizzare. Ossia, solo la compressione effettuata su dati simili a quelli utilizzati nel training set avrà dei buoni risultati, ma se eseguita su dati diversi sarà poco efficace. Altre tecniche di compressione come la JPEG riusciranno in modo migliore a svolgere questa funzione.

Queste reti neurali sono allenate a preservare quante più informazioni possibili quando queste sono inserite nell’encoder e successivamente nell’decoder, ma anche a far sì che le nuove rappresentazioni acquisiscano differenti tipi di proprietà. Si delineano quattro tipologie di autoencoder e, allo scopo di illustrarli, creeremo un esempio per ognuna di queste utilizzando il framework Keras e il dataset MNIST.

 

Tipologie di autoencoder

In questo articolo ne elenchiamo quattro:

  1. Vanilla autoencoder
  2. Autoencoder Multistrato
  3. Autoencoder Convoluzionale
  4. Autoencoder Regolarizzato

 

Vanilla autoencoder

È la forma più semplice, caratterizzata da una rete a tre strati, ovvero, una rete neurale con un solo strato nascosto. L’input e l’output sono uguali e, nel nostro caso, impariamo come ricostruire l’input attraverso l’utilizzo dell’ottimizzatore Adam e, come funzione di perdita, lo Scarto dei Minimi Quadrati.

In questa tipologia siamo nel caso di encoder undercomplete dove lo strato nascosto, con dimensione pari a 64, è più piccolo di quella dell’input (784), ottenuta dalla seguente formula 28x28x1. Questa vincolo impone alla rete neurale di imparare da una rappresentazione di dati compressa.

input_size = 784
hidden_size = 64
output_size = 784

x = Input(shape=(input_size,))

# Encoder
h = Dense(hidden_size, activation='relu')(x)

# Decoder
r = Dense(output_size, activation='sigmoid')(h)

autoencoder = Model(input=x, output=r)
autoencoder.compile(optimizer='adam', loss='mse')

Autoencoder Multistrato

Se un solo strato nascosto non è sufficiente, possiamo estendere l’autoencoder lungo la dimensione della profondità. La nostra implementazione sfrutta ora tre strati nascosti, per una migliore generalizzazione, ma dovremo anche rendere la rete simmetrica utilizzando lo strato intermedio.

input_size = 784
hidden_size = 128
code_size = 64

x = Input(shape=(input_size,))

# Encoder
hidden_1 = Dense(hidden_size, activation='relu')(x)
h = Dense(code_size, activation='relu')(hidden_1)

# Decoder
hidden_2 = Dense(hidden_size, activation='relu')(h)
r = Dense(input_size, activation='sigmoid')(hidden_2)

autoencoder = Model(input=x, output=r)
autoencoder.compile(optimizer='adam', loss='mse')

Autoencoder Convoluzionale

Sorge una domanda: potrebbero gli autoencoder essere usati con convoluzioni, piuttosto che strati interamente connessi?

E la risposta è sì. Il principio è lo stesso ma, al posto di vettori unidimensionali, vengono utilizzati vettori tridimensionali. L’immagine input viene campionata per ottenere una rappresentazione latente, ossia una riduzione dimensionale, costringendo così l’autoencoder a imparare da una versione compressa dell’immagine.

x = Input(shape=(28, 28,1)) 

# Encoder
conv1_1 = Conv2D(16, (3, 3), activation='relu', padding='same')(x)
pool1 = MaxPooling2D((2, 2), padding='same')(conv1_1)
conv1_2 = Conv2D(8, (3, 3), activation='relu', padding='same')(pool1)
pool2 = MaxPooling2D((2, 2), padding='same')(conv1_2)
conv1_3 = Conv2D(8, (3, 3), activation='relu', padding='same')(pool2)
h = MaxPooling2D((2, 2), padding='same')(conv1_3)


# Decoder
conv2_1 = Conv2D(8, (3, 3), activation='relu', padding='same')(h)
up1 = UpSampling2D((2, 2))(conv2_1)
conv2_2 = Conv2D(8, (3, 3), activation='relu', padding='same')(up1)
up2 = UpSampling2D((2, 2))(conv2_2)
conv2_3 = Conv2D(16, (3, 3), activation='relu')(up2)
up3 = UpSampling2D((2, 2))(conv2_3)
r = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(up3)

autoencoder = Model(input=x, output=r)
autoencoder.compile(optimizer='adam', loss='mse')

Autoencoder Regolarizzato

Ci sono altri metodi con il quale possiamo vincolare la ricostruzione dell’autoencoder in alternativa all’imposizione di una minore dimensionalità degli strati nascosti. Piuttosto che limitare la capacità del modello mantenendo una architettura poco profonda di encoder e decoder, così come una riduzione forzata, gli autoencoder regolarizzati utilizzano una funzione di perdita incoraggi il modello ad assumere proprietà che vadano oltre alla semplice abilità di copiare l’input in output. Nella pratica troviamo due tipologie differenti: Sparse autoencoder e Denoising autoencoder.

  1. Sparse autoencoder: sono solitamente utilizzati per la classificazione. Allenando un autoencoder, le unità nascoste nello strato centrale vengono attivate troppo frequentemente. Per evitare ciò, dobbiamo abbassare il loro tasso di attivazione limitandola ad una frazione dei dati di training. Questo vincolo è chiamato vincolo di sparsità, poiché ciascuna unità viene attivata solo da un tipo prestabilito di input.
input_size = 784
hidden_size = 64
output_size = 784

x = Input(shape=(input_size,))

# Encoder
h = Dense(hidden_size, activation='relu', activity_regularizer=regularizers.l1(10e-5))(x)

# Decoder
r = Dense(output_size, activation='sigmoid')(h)

autoencoder = Model(input=x, output=r)
autoencoder.compile(optimizer='adam', loss='mse')

Un’altra modifica che possiamo fare durante la fase di ricostruzione è di aggiungere un termine di regolarizzazione alla nostra funzione di perdita: ad esempio un regolarizzatore di tipologia l1 introduce una penalità nella funzione di perdita, agendo durante la fase di ottimizzazione. Come risultato avremo un minor numero di attivazioni rispetto a quelle di un Vanilla autoencoder.

  1. Denoising autoencoder: Piuttosto che aggiungere penalità alla funzione di perdita, possiamo far cambiarne l’oggetto, aggiungendo rumore all’immagine di input e facendo in modo che l’autoencoder impari autonomamente a rimuoverlo. Ciò significa che la rete estrarrà unicamente le informazioni più rilevanti, e imparerà da una rappresentazione robusta dei dati.
x = Input(shape=(28, 28, 1))

# Encoder
conv1_1 = Conv2D(32, (3, 3), activation='relu', padding='same')(x)
pool1 = MaxPooling2D((2, 2), padding='same')(conv1_1)
conv1_2 = Conv2D(32, (3, 3), activation='relu', padding='same')(pool1)
h = MaxPooling2D((2, 2), padding='same')(conv1_2)


# Decoder
conv2_1 = Conv2D(32, (3, 3), activation='relu', padding='same')(h)
up1 = UpSampling2D((2, 2))(conv2_1)
conv2_2 = Conv2D(32, (3, 3), activation='relu', padding='same')(up1)
up2 = UpSampling2D((2, 2))(conv2_2)
r = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(up2)

autoencoder = Model(input=x, output=r)
autoencoder.compile(optimizer='adam', loss='mse')

Utilizzo del Deep Learning per migliorare la grafica di FIFA 18

Autore: Chintan Trivedi

Linkedin: https://www.linkedin.com/in/chintan-trivedi-78665774/

Traduttrice: Sabrina Sala

 

1

 

Comparazione tra i due volti di Ronaldo: a sinistra quello di FIFA 18, a destra quello generato da una rete neurale profonda.

La Game Studios ha speso milioni di dollari e centinaia di ore di ricerca e sviluppo nel progettare la grafica del gioco, nel tentativo di renderla quanto più veritiera e naturale possibile. Nonostante i volti dei calciatori siano decisamente realistici, è ancora possibile distinguerli dal reale. Tuttavia, i profondi sviluppi avuti nell’elaborazione di immagini con l’utilizzo delle reti neurali profonde possono essere sfruttati per migliorare la grafica e, allo stesso tempo, ridurre lo sforzo necessario per farlo?

Proviamo quindi a rispondere utilizzando FIFA 18

Per capire se i recenti sviluppi possano aiutarci a rispondere a questa domanda, cerchiamo di focalizzarci sul miglioramento della grafica facciale dei giocatori utilizzando l’algoritmo DeepFakes, una rete neurale profonda che può essere allenata ad apprendere e generare visi umani in modo estremamente realistico. Questo articolo si occuperà, quindi, di ricreare i volti dei giocatori, riprendendoli dal gioco, per poi migliorarli in modo da renderli quanto più identici al reale.

Nota: qui troverete una buona spiegazione su come funzioni l’algoritmo deepfake, il quale può sostituire un volto con quello di qualsiasi altra persona utilizzando autoencoder e reti neurali convoluzionali.

Raccogliere dati di allenamento

A differenza degli sviluppatori del gioco, possiamo ottenere i dati necessari da una ricerca con Google, senza dover scomodare Ronaldo affinché indossi tute in grado di registrare i movimenti.

Cominciamo osservando il suo volto digitale, uno dei meglio riusciti nel gioco. Al fine di raccogliere informazioni per l’algoritmo deepfakes, dobbiamo semplicemente registrare il viso del giocatore attraverso la funzione di instant replay del gioco. Ora possiamo sostituirla con il vero volto di Ronaldo e, a tale scopo, scarichiamo alcune sue immagini dalla ricerca di Google che lo mostrino da angolazioni differenti. Questi pochi elementi sono tutto ciò che ci serve per cominciare il processo di allenamento del modello.

Architettura del modello e allenamento

L’algoritmo deepfakes riguarda l’allenamento di reti neurali profonde chiamate autoencoder. Queste reti per l’allenamento non supervisionato sono composte da un encoder in grado di comprime l’input in uno spazio di variabili latenti chiamato “encoding”, e da un decoder che utilizza questo spazio per ricostruire l’input. Tale architettura costringe la rete a estrapolare informazioni sulla distribuzione dei dati di input, piuttosto che lasciare che essa si limiti al semplice compito di copiare l’input in output. Utilizzeremo qui una rete convoluzionale come encoder e una rete neurale deconvoluzionale come decoder. Questa architettura è allenata a minimizzare l’errore di ricostruzione nell’apprendimento non supervisionato.

Nel nostro caso, inoltre, alleneremo simultaneamente due autoencoder: il primo imparerà a ricreare il viso di Ronaldo dalla grafica di FIFA 18, mentre la seconda lo farà utilizzando le immagini reali precedentemente scaricate. Nel deepfakes entrambe le reti condividono lo stesso encoder ma sono allenati con differenti decoder.

2.jpg 5

2.jpg 5

  1. Funzionamento del primo autoencoder che impara dalla grafica del gioco

3 2

2. Funzionamento del secondo autoencoder che impara attraverso immagini reali

Utilizzando un modello precedentemente allenato su altri volti, la perdita totale passa da 0.06 a 0.02, circa, in quattro ore di allenamento sfruttando una GTX 1070. In questo caso, l’allenamento è continuato sullo stesso modello, CageNet, utilizzato in precedenza per generare il volto di Nicolas Cage.

Utilizzare il modello allenato per scambiare i volti

Ora arriviamo alla parte più interessante. L’algoritmo è in grado di scambiare i volti adottando un trucco intelligente: nel secondo autoencoder viene, di fatto, inserito l’input del primo. In questo modo, l’encoder condiviso riesce ad utilizzare l’encoding ottenuto dai volti digitali di FIFA 18 e lascia che il decoder B ricostruisca, sulla base dell’immagine digitale, il volto reale. Così facendo i volti di FIFA vengono direttamente convertiti nell’immagine realistica di Ronaldo.

4 2

Il secondo encoder converte il volto di FIFA in quello reale di Ronaldo

Risultati

La GIF mostra un’anteprima veloce dei risultati ottenuti lasciando operare l’algoritmo sui volti di altri giocatori. Il miglioramento è notevole.

 

5 1

5 1

6

6

Confronto prima e dopo deepfakes, dei volti di Ronaldo, Morate e Ozil

Ulteriori risultati possono essere trovati in questo video.

Possiamo utilizzare l’algoritmo per mettere noi stessi nel gioco?

La risposta è si: tutto ciò che serve è un video della durata di un minuto di noi stessi e scaricar(ne?) il modello allenato in poche ore. Possiamo, in questo modo, trovare noi stessi nel gioco in modalità “Il viaggio”.

Punti di forza e di debolezza

Il miglior vantaggio che abbiamo ottenuto da questo approccio è certamente l’iperrealismo dei volti e della grafica, difficilmente distinguibili dalla realtà.  Questo risultato può essere raggiunto in poche ore di allenamento, a differenza di quanto avviene per gli sviluppatori del gioco che, con l’approccio odierno, impiegano invece anni. Ciò significa quindi che i produttori potrebbero potenzialmente realizzare nuovi titoli in tempi molto più ridotti e che le case di produzione potrebbero risparmiare molto denaro.

La limitazione più evidente finora è, invece, che questi volti sono stati generati a posteriori, come le CGI (immagini generate a computer) nei film, mentre il gioco ne richiede una realizzazione in tempo reale.  Ciò nonostante, una grossa differenza sta nel fatto che questo approccio non richiede nessun intervento umano per generare risultati efficaci dopo che il modello è stato allenato. E inoltre, l’unico limite è il tempo impiegato dalla rete neurale per generare l’immagine di output. Probabilmente non sarà necessario molto tempo prima di riuscire ad avere modelli generativi non troppo profondi e pesanti, che possano lavorare molto velocemente senza compromettere la qualità dell’output. Proprio come succede per YOLO, SSD e MobileNets per la rilevazione di oggetti in tempo reale, cosa che invece non era possibile in precedenza con modelli come i RCNN.

 

implementare una rete neurale profonda per giocare a fifa 18

Autore: Chintan Trivedi

Linkedin: https://www.linkedin.com/in/chintan-trivedi-78665774/

Traduttrice: Sabrina Sala

 

Gli A.I. bots, ossia di programmi in grado d’agire, sono solitamente programmati con una serie di regole a priori che comunicano con il gioco. Nella maggior parte dei casi, questo approccio è sufficientemente efficace per far sì che il bot imiti i comportamenti umani. Tuttavia, in molti contesti è ancora molto semplice distinguere un bot da un vero essere umano. Prenderemo quindi in considerazione questo  quesito: cosa succederebbe nel caso in cui lasciassimo che il bot comprenda autonomamente dall’osservazione del gioco umano, senza l’utilizzo di regole empiriche?

Ciò richiede un gioco dove sia possibile raccogliere questo genere di dati sul comportamento dei giocatori e FIFA 18 fa al caso nostro. Poter registrare le nostre azioni e decisioni durante la fase di gioco ci permette di implementare un bot basato sulla deep learning, senza che si renda necessaria la programmazione di una singola regola di gioco.

Il codice per questo progetto ed il modello già allenato possono essere trovati qui.

 

Meccanismi di gioco

Il meccanismo alla base per implementare un tale bot non necessita di accesso ad alcun codice interno. È sufficiente un semplice screenshot della finestra di gioco per dare avvio al nostro engine. Quest’ultimo, processa le informazioni visive e realizza sulla base di esse un’azione che viene comunicata al gioco attraverso una simulazione della pressione dei tasti. Quindi, la procedura viene ripetuta per ogni step.

 

4 1

Ora che abbiamo l’input, l’insieme di screenshot che permettono al bot di agire, giungiamo alla parte interessante: la costituzione di un’intelligenza artificiale che apprenda dal gioco. Ciò viene fatto dapprima 1) utilizzando una rete neurale convoluzionale (CNN) per elaborare le schermate e, secondariamente, 2) sfruttando le reti di memoria a breve e lungo termine (LSTM) per decidere in merito all’azione più appropriata.

 

Step 1) allenare una rete neurale convoluzionale (CNN)

Le CNN sono spesso conosciute per la loro capacità di individuare oggetti in un’immagine con elevata accuratezza. Associando ad essa potenti GPU e architetture di reti intelligenti, possiamo ottenere un modello di CNN che opera in tempo reale.

5 2

5 2

Per far sì che il nostro bot processi l’immagine che gli è stata data come input, utilizziamo una CNN veloce ed estremamente leggera chiamata MobileNet. La mappa delle caratteristiche, estrapolate dalla rete, rappresenta una comprensione profonda dell’immagine, riuscendo ad individuare dati quali la posizione dei giocatori e di altri oggetti di interesse nello spazio dello schermo. Questa mappatura è quindi usata con un single-shot multi-box per individuare i giocatori nell’area di gioco, insieme al pallone e alla porta.

6

 

Step 2) allenare reti di memoria a breve e lungo termine (LSTM)

7

Ora che abbiamo una comprensione dell’immagine, possiamo procedere con il passaggio successivo, l’azione. Tuttavia, non possiamo limitarci allo studio di una sola schermata per decide come agire ma, piuttosto, dovremmo guardare a una breve sequenza di queste. È qui che entrano in gioco le LSTM poiché sono conosciute per modellare una sequenza temporale di dati. Ciascun frame consecutivo è utilizzato come passo temporale nella nostra sequenza, e per ognuno di essi viene estrapolata una feature map utilizzando il modello delle CNN. Queste ultime sono poi introdotte simultaneamente in due reti LSTM.

La prima rete LSTM ha la funzione di capire quali movimenti il giocatore necessita di fare ed è, quindi, un modello di classificazione “multi-classe”. La seconda LSTM riceve lo stesso input ma deve, invece, decidere quale azione scegliere tra passaggio corto, cross, passaggio filtrante e tiro. È quindi anch’essa un modello multiclasse. Gli output prodotti da queste due classificazioni sono poi convertiti nella pressione dei tasti che controllano le azioni di gioco.

Le reti sono state allenate sulla base di dati collezionati attraverso il gioco manuale, la raccolta delle immagini e la pressione del tasto corretto.

 

Valutazione della prestazione del bot

Non potremmo valutare la performance del bot, se non lasciando che giochi. Dopo soli 400 minuti di allenamento, il bot ha imparato a correre verso la porta avversaria, a fare passaggi frontali e scattare una fotografia dello schermo ogni qualvolta rilevi un goal. Nella modalità principiante di FIFA 18, ha già conseguito 4 goal in sei partite, uno in più di quanti ne abbia fatti Paul Pogba durante la stagione 17/18.

Video del bot in azione possono essere trovati qui.

 

Conclusioni

Le impressioni iniziali a questi approccio di implementazione di bot in grado di giocare sono certamente positive. Con un allenamento piuttosto limitato, il bot è riuscito a cogliere le regole basi del gioco: muoversi verso la porta avversaria e spingere la palla nella rete. Sarebbe sicuramente in grado di avvicinarsi al livello di performance umano se sottoposto a molte più ore di allenamento, le quali potrebbero essere facilmente raccolte dallo sviluppatore del gioco. In aggiunta, estendere il modello di allenamento a riprese reali di partite, permetterebbe di rendere il comportamento del bot molto più naturale e realistico.

 

 

Reti Neurali Capsulari

Autore: Matteo Alberti

 

Le reti neurali convoluzionali ottengono grandi performance quando i dati in fase di test risultano esser molto simili a quelli utilizzati in fase di apprendimento ma, in caso di rotazioni, traslazioni o traasformazioni esse risultano soggette a scarsa capacità di generalizzazione. É commune prassi dunque andare a fornire al nostro training set attraverso data augmentation degli esempi di queste possibili variant ottenendo però l’effetto di far imparar mnemonicamente alla nostra rete (con la crescita computazionale legata alla maggior quantità di dati) piuttosto che aver un modello in grado di ben generalizzare una data situazione.

Negl’ultimi anni la ricerca nel campo della computer vision (per quanto riguarda il deep learning) si è focalizzato sul come aumentare la profondità di una rete aggiungendo più strati (layers) al fine di reaggiungere un maggior grado di astrazione (partendo dai primi strati convoluzionali in grado di estrarre piccolo forme, angoli o intensità di colore andiamo mano a mano a ricombinare semplici feauter in disegni sempre più complessi)

Per far ciò, al fine di tener sotto controllo il numero di parametri (ed i relativi tempi computazionali) utilizziamo un operatore, commune in tutte le reti profonde, ossia il pooling ( per la precisione la sua versione max-pooling) che ci permette di andar a ridurre il numero di parametri riducendo progressivamente la componente spaziale selezionando I valori più alti andando però a perdere le informazioni spaziali legate alle features estratte.

1

 

Dunque la ricerca sulle architetture di reti neurali si sta progressivamente focalizzando sull’obiettivo di apprendere come generalizzare meglio piuttosto che sul fornire dati sempre più processati. Un primo passo è stato fatto con le reti neurali capsulari dove, il nostro obiettivo risulta esser quello di ottenere un’Equivarianza (invarianza a rototraslazioni) andando a sostituire quell’operatore di pooling con una nuova struttura: il Dynamic Routing

 

 

Introduzione

Una capsula è un gruppo di neuroni. L’attività di un vettore di una capsula rappresenta i parametri di istanziazione quando un oggetto (o parte di esso) viene rilevato dalla rete. La lunghezza di tale vettore rappresenta la probabilità di esistenza della data classe mentre l’orientamento del vettore codifica informazioni spaziali (per esempio rotazioni e traslazioni) all’interno di una matrice di Posa.

 

Matrice di Posa: Una matrice di posa è uno strumento utilizzato nel rendering grafico dove la costruzione di un’immagine parte da una rappresentazione gerarchica di forme geometriche. Nel nostro caso l’obiettivo risulta esser un po’ l’obiettivo opposto; decostruire un’immagine nelle sue componenti di base di cui andremo a salvare la relativa posizione in riferimento alle altre features.

 

2.jpg

Quando una capsula al livello inferiore si attiva produce una predizione al possibile nodo di appartenenza alle capsule di livello superiore attraverso una matrice di trasformazione. Se una capsula di alto livello riceve sufficienti predizioni di appartenenza diviene attiva. Questo processo, chiamato dynamic routing, permette di sostituire il max pooling attraverso una selezione “intelligente” delle features da mantenere durante la fase di allenamento.

 

3

Classificato come viso unicamente la figura a sinistra

 

 

Archiettura

 

La principale differenza nelle reti capsulari dunque consiste in un’architettura non profonda

4

Andiamo ad analizzare i principali strati:

  • Il primo layer convoluzionale (256 kernels, 9×9, stride uguale ad uno, attivazione ReLu)

Nel primo strato convoluzionale andiamo a convertire le intensità dei pixels in features di basso livello di cui non ci importa di mantener la relativa posizione spaziale, anzi vogliamo utilizzare tutte quelle importanti proprietà di condivisione dei parametri utili per ridurre i tempi computazionali.

Il secondo strato dunque riceve in input le features estratte tramite convoluzione:

  • PrimaryCaps consiste in due strati nascosti

Il primo strato corrisponde al processo di rendering inverso prima descritto

Il secondo strato contrariamente è convoluzionale (32 capsule 8 dimensionali) dove ogni capsula rappresenta una features estratta dunque tramite una convoluzione con 9×9 kernels e stride uguale a due.

L’output della PrimaryCaps consiste dunque in 32x6x6 capsule 8 dimensionali.

  • DigitCaps

L’ultimo strato va a riprodurre in forma vettoriale quello che nelle reti convoluzionali faceva FC-Layer ossia andiamo a ridurre il numero di neuroni (in questo caso di capsule) fino ad ottenerne uno per ogni classe del target

 

Riassumendo le principali innovazioni:

  • Rimpiazzare i neuroni con le capsule
  • Sostituzione del max-pooling con il dynamic routing
  • Nuova funzione di attivazione
  • Architettura non profonda

 

 

Funzione di attivazione

 

Come introdotto precedentemente vogliamo che l’activity vector prodotto da una capsula rappresenti la probabilità di esistenza della data feature. Necessitiamo quindi di una nuova funzione di attivazione che non vada più a lavorare con scalari ma con vettori:

.

La seguente funzione, definita “squashing” non fa altro che normalizzare in nostro vettore fra zero ed uno mantenendo invariato l’orientamento del vettore stesso.

Questo ci permette non solo l’Equivarianza ma, nel caso di face-detection, per esempio, possiamo saper se e di quanti gradi il viso risulti ruotato.

5

Dynamic Routing

 

Dynamic Routing non è solo un max-pooling che seleziona il numero di connessioni in maniera più intelligente, esso ci permette di preservare, come descritto in precedenza, la “gerarchia fra le parti”

 

Il Dynamic Routing quindi è un processo iterativo, nella fase di forward, che connette capsule con attività simili

 

2.jpg 1

In ogni iterazione ogni capsula produce una matrice di moltiplicazioni con l’output delle capsule precedenti andando a selezionare unicamente quelle con prodotto scalare maggiore ed orientamento simile, andando ad analizzare:

 

5 1

 

1) Capsule a livello l ed il loro output  , r numero di iterazioni del routing

2)  coefficiente temporaneo necessario per iterazione inizializzato a zero

4)  è peso per capsule di «basso livello», utilizzo del softmax per ottenere uno scalare non negativo con somma ad uno.  Prima iterazione  è uguale per tutte le capsule essendo inizializzato da

5) Per ogni capsula di «alto livello» calcoliamo una combinazione lineare dei dati di input pesati con i coefficienti  ossia andiamo a sommarli e ridimensionarli

6) Applichiamo la funzione di squash per «normalizzare» ad 1 e preservar l’orientamento del vettore

 

 

Allenamento di una rete capsulare

 

Una volta fissato il forward pass attraverso il dynamic routing andiamo ad allenare la nostra rete attraverso una doppia funzione di perdita.

  • La prima parte viene allenata attraverso backpropagation come nelle classiche CNN dunque alleniamo i pesi in modo da massimizzare la norma della capsula relativa alla classe di target (L^i _\mu)
  • Un’altra versione della rete utilizza un’MLP (come Autoencoding) al fine di ricostruire l’immagine di input (L^i _p)

La nostra funzione di perdita sarà dunque data da Loss Funcion =  L^i = L^i_\mu + \rho L^i_p

Dove il termine p non è altro che un termine di penalizzazione al fine di non far incidere eccessivamente la seconda parte.

 

2.jpg 2

 

Questo funzione di autoencoding risulta possibile in quanto all’interno di ogni capsula finale (una per ogni classe del target) sono codificate tutte le informazioni per ricostruire l’immagine. Dunque possiamo creare una sorta di funzione discriminativa che vada a comparare i dati di input con il terget ricostruito mediante le informazioni delle matrice di posa)

 

Accuratezze raggiunte

1 1

 

Oltre alla classica Accuratezza, risulta interessante andare a valutare le proprietà intrinseche di questo modello:

Parametri CNN : 35.4 M (Deep)

Parametri Capsnet (8.2 M)

 

Il numero di parametric risulta sensibilmente minore grazie alla minor quantità di dati. Esso però diverge molto con large-dataset in quant il meccanismo non risulta ancora ottimizzato

 

Robustezza alle Trasformazioni Affini:

CNN 66%

CapsuleNet 79%

 

Alta capacità discriminatoria su oggetti sovrapposti:

 

2.jpg 3

 

Grazie alla sua capacità di ricostruzione possiamo non solo classificare ma segmentare oggetti anche sovrapposti (uno dei grandi limiti delle reti neurali convoluzionali)

 

Introduzione divulgativa alle Reti Neurali e al Deep Learning

Francesco Pugliese : Ricercatore ISTAT

Abstract: Descrizione introduttiva del modello del neurone artificiale e confronto con il neurone biologico, la storia delle reti neurali e come si è arrivati oggi al Deep Learning, detrattori vs sostenitori delle reti neurali e vittoria dei sostenitori (Hinton, LeCunn, Benjo). I successi del Deep Learning e la disfatta del Machine Learning tradizionale di oggi in settori come Computer Vision, Natural Language Processing e Giochi (GO, Chess, ecc.). Alcuni esempi tecnici concreti.

 

Deep Learning & Alpha Go – Maurizio Parton

Maurizio Parton – Professore associato – Dipartimento di Economia – Università di Chieti – Pescara “G. d’Annunzio”
“Deep Learning e AlphaGo”
In questo intervento parlerò del successo del Deep Learning nell’affrontare l’ultimo baluardo dei giochi combinatori, il Go. Dopo aver descritto perché il Go è stato fino al 2016 impossibile da affrontare per i computer, parlerò di AlphaGo – il software che ha conquistato il Go -, della rete neurale alla base del successo di AlphaGo, e delle sue evoluzioni in AlphaGo Zero e AlphaZero, i software che hanno superato AlphaGo grazie al *non*-aiuto umano 🙂

Capsule Networks – Daniele D’Armiento

Daniele D’Armiento – Physicist presso Samsung
“Analisi di una rete neurale capsulare nella Computer Vision”
L’attuale stato dell’arte per la classificazione d’immagini, introdotte nell’ottobre 2017, sta aprendo nuove strade inesplorate nel mondo del Deep Learning non più basate sull’ormai comune prassi di aumentare la profondità della rete o aumentare i dati a nostra disposizione per raggiungere performance migliori ma contrariamente su una struttura capsulare ed un processo definito “routing-by-agreement” in grado di superare la perdita delle informazioni spaziali delle reti convoluzionali ed al tempo stesso di ottenere simil-analoghe performance con una quantità di dati inferiore.