apri/download

GUIDA ALLE RETI NEURALI (brevissima introduzione)
Prima di tutto è necessario caricare i seguenti pacchetti (MASS, kernlab,
neuralnet, nnet, caret). In questa guida verrà utilizzato un dataset già presente
in R (biopsy), perchè si presta come struttura, all’utilizzo in una rete neurale.
Presenta 11 colonne, la prima inutilizzabile perché è un indicatore (ID); l'ultima
contiene i dati in output.
library(MASS); library(kernlab); library(neuralnet); library(nnet); library(caret);
data(biopsy); head(biopsy)
dataBio<-biopsy[,2:11] #escludiamo la colonna 1 perché non è una variabile predittiva
A questo punto trasformiamo in numerico il contenuto della colonna di output,
facendo corrispondere 1 benigno, 2 a maligno).
dataBio$class<-as.numeric(dataBio$class)
In questo dataset esistono valori nulli in qualche colonna, quindi consideriamo
solo le righe dove sono presenti tutti i dati.
good<-complete.cases(dataBio)
GBio<-dataBio[good,] # estraiamo dal dataset dataBio, solo le righe "good".
Da qui, trasferiremo ulteriormente i nostri output, da numerico a binario. 0
corrisponderà a benigno, 1 a maligno.
for(i in 1:nrow(GBio)){
if (GBio[i,10] == 1) {
GBio[i,10]<-0
} else {
GBio[i,10]<-1
}
}
In seguito, suddividiamo il nostro dataset in 2 partizioni: la prima servirà per
allenare la rete neurale, mentre la seconda servirà per testare la sua
attendibilità.
inTrain<-createDataPartition(y=GBio$class,p=0.75,list=FALSE)
training<-GBio[inTrain,]
testing<-GBio[-inTrain,]
tsld sta per threshold, ed è il vettore contenente i possibili valori soglia che
vogliamo utilizzare
Per calibrare la nostra rete neurale, si utilizzeranno i valori presenti nel vettore
in differenti cicli e si osserverà come l'attendibilità della rete cambia al
modificarsi delle soglie utilizzate.
tsld<-c(0.20,0.15,0.1,0.020,0.015,0.010,0.0015,0.001,0.0001)
Le soglie possono essere impostate arbitrariamente.
nNascosti sta per hidden, cioè la variabile utilizzata dalla rete neurale come
numerosità dei neuroni nascosti da utilizzare. Questo è un vettore numerico e
come per tsld, verrà utilizzato in differenti cicli in cui si osserverà il variare
dell'attendibilità della rete al cambiare dei neuroni nascosti utilizzati.
nNascosti<-c(5,6,7,8,9,10)
Ciò che segue è un vettore che conterrà un elemento della tabella riassuntiva
finale. Neuroni sarà la colonna di lunghezza 6 (6 elementi del vettore
nNascosti) X 9 (9 elementi del vettore tsld) = 56. Ripete semplicemente
(comando rep) tutti gli elementi della colonna nascosti per il numero di elementi
del vettore tsld. La rete neurale verrà testata con il primo elemento del vettore
nNascosti e con tutti gli elementi del vettore tsld, successivamente con il
secondo elemento del vettore nNascosti e di nuovo per tutti gli elementi del
vettore tsld e così via.
neuroni<-c() #vettore delle possibili combinazioni del numero di neuroni #nascosti
for(i in 1:length(nNascosti)){
neuroni<-c(neuroni,rep(nNascosti[i],length(tsld)))
}
Thresholds sarà il vettore contenente il vettore tsld ripetuto per il numero di
elementi presenti nel vettore nNascosti. Sia thresholds che neuroni, serviranno
solo in sede di osservazione dei valori di attendibilità della rete neurale.
thresholds<-rep(tsld,length(nNascosti))
Le righe che seguiranno sono la rete neurale vera e propria. Le variabili errori
ed Err sono degli accumulatori, ossia estraggono il valore di errore e lo
conservano ad ogni giro dei differenti cicli a cui è sottoposta la rete.
errori<-c(); Err<-c()
nc<-99 #valore da aggiungere nel ciclo se la rete non converge
Il valore rp è il numero di ripetizioni che si chiede alla rete di fare, ossia la rete
ripeterà la stessa struttura, cioè lo stesso numero di neuroni, lo stesso numero
di soglia per il valore attribuito rp. In seguito verranno calcolate la media e la
varianza degli errori commessi dei differenti gruppi di valori corrispondenti alle
differenti combinazioni.
rp<-6 #numero di ripetizioni
for(u in 1:length(nNascosti)){ #questo ciclo testa la rete al cambiare del numero di
#neuroni nascosti
errori<-c()
for (i in 1:length(tsld)){ #questo ciclo testa la rete al variare del valore soglia
Bionet<-neuralnet(training[,10] ~ training[,1] + training[,2] + training[,3] +
training[,4] + training[,5] + training[,6] + training[,7] + training[,8] + training[,9],
data=training, hidden=nNascosti[u], lifesign="minimal", linear.output=FALSE,
rep=rp,threshold=tsld[i])
a<-Bionet$result.matrix[1,] #a è il vettore contenente i valori di errore
#nelle differenti ripetizioni.
if(length(a)!=6){ #se la rete non converge, il numero di elementi di a
#diminuisce
m<-rp-length(a)
a<-c(a,rep(nc,m)) #se la rete non converge ad una certa
#combinazione/ripetizione
} #verrà attribuito un valore arbitrario elevato (in questo caso 99) al errore
errori<-rbind(errori,a) #accumulatore degli errori
}
Err<-rbind(Err,errori)
}
Successivamente verranno calcolati l'errore medio e la varianza media degli
errori nelle differenti ripetizioni.
ErrMedio<-apply(Err,1,mean)
VarMedia<-apply(Err,1,var)
La tabella riassunto combina tutti gli elementi, permettendoci di poter
osservare a quale combinazione corrispondono i differenti valori di errore, in
modo da poterci suggerire quale combinazione sia la più adeguata
riassunto<-cbind(neuroni,thresholds,ErrMedio,VarMedia)
riassunto<-riassunto[order(riassunto[,3]),] #con questo comando ordiniamo la
#tabella riassunto in ordine crescente d'errore
Chiameremo best il vettore che contiene la miglior combinazione di elementi,
che minimizza in media l'errore di previsione della rete neurale.
best<-subset(riassunto,riassunto[,3]==min(riassunto[,3]))
Lanciamo nuovamente la nostra rete neurale, ma questa volta con gli elementi
del vettore best, per una ripetizione.
Bionet<-neuralnet(training[,10] ~ training[,1] + training[,2] + training[,3] +
training[,4] + training[,5] + training[,6] + training[,7] + training[,8] + training[,9],
data=training, hidden=best[1], lifesign="minimal", linear.output=FALSE,
rep=1, threshold=best[2])
Una volta pronta la nostra rete neurale, settata con i valori migliori in media,
non resta che testarne attendibilità con i dati del dataset che erano stati
inizialmente separati tramite createDataPartition. La nostra rete neurale Bionet
ha 9 neuroni di input, corrispondenti alle 9 variabili utilizzate nel suo
apprendimento. È necessario quindi, che anche il dataset su cui andrà testata
contenga solo 9 colonne. Creeremo un nuovo dataset testing2, che contiene
solo le prime 9 colonne (essendo la decima quella di output).
testing2<-testing[,1:9]
La variabile result contiene il risultato della elaborazione dei dati di testing2 con
la rete neurale Bionet
result<-compute(Bionet,testing2)
Arrotondiamo i risultati (round) della previsione.
previsione<-round(result$net.result)
A questo punto mettiamo assieme i dati reali di output, la decima colonna della
variabile testing, e i dati della previsione ottenuti con la rete neurale.
confronto<-cbind(testing[,10],previsione)
Per sapere quante volte la rete non ha trovato il valore reale di output si utilizzi
il seguente comando:
length((tf<-testing[,10]==previsione)[which(tf==FALSE)])
Nell’esempio appena presentato, la componente “algorithm” al interno della
rete neurale non è stata inserita, in questo caso neuralnet utilizza l’algoritmo
“resilient back propagation(rprop+)” con l’attribuzione dei pesi a ritroso “weight
backtracking”: questo algoritmo si basa sul tradizionale algoritmo di back
propagation, che modifica il peso della rete neurale per modificare l’errore
quadratico medio tra gli output desiderati e quelli ottenuti. In altre parole, si
sottopone più volte il training-set alla rete, aggiustando i pesi per minimizzare
l’errore.
Il pacchetto neuralnet, mostrato nell’esempio, permette di utilizzare i seguenti
algoritmi: “backprop”, “rprop+”, “rprop-“, “sag”, o “slr” attraverso l’aggiunta della
voce
algorithm=”…”,
sostituendo
ai
puntini,
uno
degli
algoritmi
precedentemente elencati. Nel rprop+ (resilient back propagation) utilizzato
nell’esempio, la variazione dei pesi avviene solo dopo aver elaborato tutti gli
esempi dell’insieme di apprendimento. Gli algoritmi “sag” e “slr”, sono basati, il
primo sul tasso di apprendimento associato al minor gradiente assoluto,
mentre il secondo sul tasso di apprendimento più piccolo.
Un ulteriore metodo di apprendimento supervisionato di una rete neurale è
l’algoritmo di feed forward (pacchetto nnet); elaborato da McCulloch e Pitts,
l’algoritmo feed forward è il metodo più diretto e “semplice” di costruire una rete
neurale. In questo modello, l’informazione si muove in avanti (forward), dai
neuroni di input passa attraverso i neuroni nascosti, per arrivare ai nodi di
output.
I singoli ingressi vengono moltiplicati per un valore, detto peso: il risultato delle
operazioni viene sommato e se la somma supera un certo valore soglia, il
neurone si attiva, facendo partire l’informazione. Il peso è fondamentale per
quantificare l’importanza di un ingresso (un nodo di input molto importante avrà
un peso elevato, mentre un altro ingresso poco utilizzato avrà un peso minore);
teoricamente, se durante l’addestramento della rete, due neuroni comunicano
fra loro utilizzando maggiormente alcune connessioni, queste ultime avranno
un peso maggiore, in modo da costituire dei percorsi preferenziali, che si
svilupperanno tenendo sempre attive tutte le combinazioni tra i vari nodi a
seconda del loro peso.
La differenza tra i due metodi sta nel processo di attribuzione dei pesi, uno li
attribuisce “in avanti” dallo strato di input a quello di output, e l’altro “a ritroso”
dallo strato di output a quello di input.
COME COSTRUIRE UNA RETE NEURALE (con nnet).
La struttura della funzione nnet è simile a quella utilizzata in neuralnet.
Carichiamo il dataset “biopsy”, prendiamo in considerazione solo le colonne
dalla 1 alla 11, trasformiamo l’ultima colonna in numerico e poi in binario e
filtriamo le righe che hanno valori nulli, prendendo in considerazione solo i casi
completi.
data(biopsy); dataBio<-biopsy[,2:11]; dataBio[,10]<-as.numeric(dataBio[,10])
for(i in 1:nrow(dataBio)){
if(dataBio[i,10]==1){
dataBio[i,10]<-0}else{dataBio[i,10]<-1}}
complete<-complete.cases(dataBio)
dataBio<-dataBio[complete,]
Il nuovo dataset comprende ora solo 683 righe. Con il seguente comando, si
sceglieranno 483 numeri casuali da 1 a 683 e si utilizzeranno questi numeri
per estrarre dal dataset completo sia la partizione di training che la partizione
di testing. In questa occasione gli output verranno separati dagli input, creando
trainBio (tabella con 483 righe contenente solo gli input) e targetBio (tabella
contenente gli output delle 483 righe selezionate per il training).
sampTrain<-sample(1:683,483) # scegli 483 numeri casuali da 1 a 683
trainBio<-dataBio[sampTrain,1:9]; trainBio<-as.matrix(trainBio)
targetBio<-dataBio[sampTrain,10]; targetBio<-as.matrix(targetBio)
Le 200 righe rimaste fuori dal training andranno a comporre il dataset di
controllo; anche questo dovrà essere suddiviso in dati in input (testBio) e dati
in output (testBioY).
testBio<-dataBio[-sampTrain,1:9]; trainBio<-as.matrix(trainBio)
testBioY<-dataBio[-sampTrain,10]; testBioY<-as.matrix(testBioY)
La rete neurale con nnet è la seguente:
Bionet<-nnet(trainBio, targetBio,size=10,rang=0.1,decay=5e-4, maxit=200)
VF<-round(predict(Bionet,testBio))==testBioY; VF<-as.data.frame(VF)
result<-cbind(stima<predict(Bionet,testBio),round(predict(Bionet,testBio)),testBioY,VF);
result<-as.data.frame(result); names(result)<c("stima","stimaArrotondata","reale", "VF")
result
Rang definisce l’intervallo di variazione entro cui i pesi saranno scelti
casualmente [-rang; +rang].
Decay (opzionale, se assente nnet lo imposterà a 0) è il parametro che indica
il peso del decadimento.
Maxit(opzionale, se assente nnet lo imposterà a 100) è il massimo numero di
iterazioni.
Il plottaggio della rete neurale in nnet purtroppo non è possibile con il comando
plot().
Esiste
però
una
funzione
creata
apposta:
https://gist.githubusercontent.com/fawda123/7471137/raw/466c1474d0a505ff
044412703516c34f1a4684a5/nnet_plot_update.r
Il link qui sopra vi porta alla pagina dove è caricata la funzione. Questa dovrà
essere selezionata tutta, copiata ed incollata cosi come si trova nel terminale
di R, in questo modo si potrà creare la funzione plot.nnet(). Per avere la visuale
grafica della vostra rete basterà quindi eseguire plot.nnet(vostrarete).