k-means data clustering - vita da studente
TRANSCRIPT
Università degli Studi del Sannio
Facoltà di Ingegneria
Corso di Laurea Specialistica in Ingegneria Informatica
Corso di Programmazione Concorrente
PROGETTO:
K-Means Data Clustering
Studenti Docente
Flavio Pace Prof. Umberto Villano
Rita Di Candia
Anno Accademico 2009/2010
Indice
1. K-MEANS ................................................................................................................................... 1
1.1 Cos’è? ................................................................................................................................... 1
1.2 Vantaggi e svantaggi ........................................................................................................... 2
2. Implementazione ......................................................................................................................... 3
2.1 Sequenziale ........................................................................................................................... 3
2.2 Parallelo ................................................................................................................................ 3
2.3 Risultati ................................................................................................................................ 5
2.3.1 Testbad .......................................................................................................................... 5
2.3.2 1° Soluzione ................................................................................................................... 5
2.3.3 2° Soluzione ................................................................................................................... 8
3 Hadoop ....................................................................................................................................... 15
3.1 Implementazione K-Means con Hadoop ......................................................................... 17
4 Hadoop vs Open MPI ............................................................................................................... 19
Appendice A – Open MPI e Sequenziale ....................................................................................... 23
Appendice B – MapReduce ............................................................................................................. 24
KmeansDriver.java ...................................................................................................................... 24
KmeansMapper.java .................................................................................................................... 28
KmeansCombiner.java ................................................................................................................ 30
KmeansReducer.java ................................................................................................................... 31
1
1. K-MEANS
1.1 Cos’è?
L'algoritmo K-Means è un algoritmo di clustering, progettato nel 1967 da MacQeen, che permette
di suddividere gruppi di oggetti in K partizioni sulla base dei loro attributi. È una variante
dell'Algoritmo di aspettazione-massimizzazione il cui obiettivo è determinare i K gruppi di dati
generati da distribuzioni gaussiane.
Si assume che gli attributi degli oggetti possano essere rappresentati come vettori, e che quindi
formino uno spazio vettoriale.
L'obiettivo che l'algoritmo si prepone è di minimizzare la varianza totale intra-cluster (o la
deviazione standard). Ogni cluster viene identificato mediante un centroide o punto medio.
L'algoritmo segue una procedura iterativa:
inizialmente crea K partizioni e assegna ad ogni partizione i punti d'ingresso o casualmente o
usando alcune informazioni euristiche;
calcola il centroide di ogni gruppo;
costruisce quindi una nuova partizione associando ogni punto d'ingresso al cluster il cui
centroide è più vicino ad esso;
vengono ricalcolati i centroidi per i nuovi cluster e così via, finché l'algoritmo non converge.
Figura 1.1: Esempio algoritmo K-Means
Come già accennato l’obiettivo è quello di minimizzare la somma delle distanze di ciascun oggetto
dal centroide del cluster cui è assegnato:
2
Dove d è una misura di dissimilarità generalmente definita dal quadrato della distanza Euclidea. Per
due punti in uno spazio n-dimensionale, P = (p1,p2,...,pn) e Q = (q1,q2,...,qn), la distanza Euclidea è
calcolata come:
Esistono diverse varianti dell’algoritmo che differiscono nella selezione iniziale dei centri dei
cluster, nel calcolo della dissimilarità e nelle strategie per calcolare i centri dei cluster.
1.2 Vantaggi e svantaggi
Per quanto riguarda i vantaggi:
è possibile variare la posizione iniziale dei centroidi per cercare di ridurre la dipendenza dalla
condizioni iniziali;
è efficiente nel gestire grosse quantità di dati. La complessità computazionale dell’algoritmo è
O(tkmn) dove m è il numero di attributi, n il numero di oggetti, k il numero dei cluster e t è il
numero di iterazioni sull’intero data set. In genere k, m, t << n;
spesso l’algoritmo termina con un ottimo locale. Per trovare un ottimo globale possono essere
adottate altre tecniche (deterministic annealing, algoritmi generici) da incorporare al k-means;
Per quanto riguarda gli svantaggi:
funziona solo su valori numerici in quanto minimizza una funzione di costo calcolando la
media dei clusters;
i cluster hanno forma convessa, pertanto è difficile usare il k-means per trovare cluster di forma
non convessa;
occorre fissare a priori k;
si potrebbe verificare la situazione in cui a uno o più cluster non vengono associati dei punti.
3
2. Implementazione
Schema algoritmo:
Figura 2.1: Struttura dell’algoritmo
2.1 Sequenziale
L’algoritmo sequenziale è tratto dal libro di J. MacQueen "Some Methods for Classification and
Analysis of Multivariate Observations."
2.2 Parallelo
L’implementazione parallela è basata sul parallelismo dei dati. I dati in ingresso sono equamente
divisi tra tutti i processi, mentre i punti scelti come centroidi (o cluster) vengono replicati.
4
Successivamente alla fine di ogni iterazione, su ogni processo, vengono generati i nuovi cluster,
fino a quando l’algoritmo non termina.
Il file di ingresso è costituito da un numero di punti ognuno dei quali ha 20 coordinate. Mentre in
uscita vengono generati due tipi di file:
nel primo file vengono scritte le coordinate dei k cluster finali che sono stati generati, chiamato
"nomefileinput.cluster_centres";
nel secondo file vengono scritte le coppie “punto-cluster”, ossia il numero del punto e il
numero del cluster al quale è stato associato, chiamato "nomefileinput.membership".
Per il calcolo parallelo è stato utilizzato Open MPI e un numero di processi, detti anche rank, pari a
8.
Il valore del CPU time è dato dalla somma dei tempi impiegati per eseguire ogni singolo ciclo.
Alla fine dell’esecuzione dell’algoritmo tutti i processi mandano i dati al rank 0, il quale è l’unico
responsabile della scrittura dei risultati sul file di output.
RANK0 RANK1 RANK2 RANK3
FILE_OUTPUT
Figura 2.2: Tecnica di scrittura dei dati nel file di output
Per calcolare l’I/O time, ossia il tempo impiegato dal rank 0 per scrivere i risultati nel file di output,
sono state utilizzate due soluzioni:
la prima soluzione consiste nel calcolare tale valore facendo la somma del tempo speso per la
lettura dei punti dal file in input più la somma del tempo speso per la scrittura del file di uscita;
la seconda soluzione consiste nello spostare la scrittura a fine ciclo ed introdurre una variabile
str1 che raccoglie prima tutti i dati e successivamente fa la scrittura su file.
5
2.3 Risultati
2.3.1 Testbad
Il test è stato effettuato applicando due tipi di input:
un file da 188 MB contenente 1 milione di punti ognuno con 20 coordinate;
un file da 944 MB contenente 5 milioni di punti ognuno con 20 coordinate.
L’esecuzione è stata effettuata con due differenti tipi di infrastruttura di rete:
Myrinet
Fast Ethernet
Inoltre le dimensioni dei due tipi di file ottenuti come output sono:
output ottenuto dall’esecuzione di 1 milione di punti:
il file “.cluster_centres” è da 9.5 KB;
il file “.membership” è da 9.3 MB;
output ottenuto dall’esecuzione di 5 milioni di punti:
il file “.cluster_centres” è da 9.5 KB;
il file “.membership” è da 51 MB.
2.3.2 1° Soluzione
Di seguito sono riportati i risultati ottenuti dalla prima soluzione. Sono stati messi a confronto due
tipi di esecuzione effettuate:
Sequenziale;
Open MPI attraverso Fast Ethernet.
6
Figura 2.3: Risultati prima soluzione con input 1 milione di punti e 8 processi
8
CPU time I/O time Total time Speed-up Efficienza % real user sys
MPI_Ethe 61,21 24,88 86,09 5,26 65,74% 1m24.406s 0m0.389s 0m0.068s
Seq 436,63 16,13 452,76 7m32.762s 7m32.339s 0m0.392s
MPI_Ethe 163,53 24,85 188,38 6,55 81,92% 3m6.646s 0m0.363s 0m0.079s
Seq 1218,6 15,96 1234,56 20m34.566s 20m34.204s 0m0.361s
MPI_Ethe 748,04 24,98 773,02 7,65 95,59% 12m51.262s 0m0.373s 0m0.080s
Seq 5895,22 16,14 5911,36 98m31.366s 98m31.009s 0m0.374s50 CLUSTER Float
Risultati 1 milione n° processi
10 CLUSTER Float
20 CLUSTER Float
0
1000
2000
3000
4000
5000
6000
MPI_Ethe Seq MPI_Ethe Seq MPI_Ethe Seq
seco
ndi
CPU time I/O time
10 CLUSTER 20 CLUSTER 50 CLUSTER
7 Figura 2.4: Risultati prima soluzione con input 5 milioni di punti e 8 processi
8
CPU time I/O time Total time Speed-up Efficienza % real user sys
MPI_Ethe 469,45 123,68 593,13 6,19 77,41% 9m41.211s 0m0.393s 0m0.079s
Seq 3593,16 79,94 3673,1 61m13.112s 61m11.152s 0m1.903s
MPI_Ethe 1046,75 122,28 1169,03 7,08 88,44% 19m17.091s 0m0.371s 0m0.079s
Seq 8190,06 81,44 8271,5 137m51.518s 137m49.586s 0m1.924s
MPI_Ethe 2981,46 122,12 3103,58 7,67 95,88% 51m31.664s 0m0.377s 0m0.077s
Seq 23724,23 82,27 23806,5 396m46.502s 396m44.732s 0m1.880s
n° processi
50 CLUSTER Float
Risultati 5 milioni
10 CLUSTER Float
20 CLUSTER Float
0
5000
10000
15000
20000
25000
MPI_Ethe Seq MPI_Ethe Seq MPI_Ethe Seq
seco
ndi
CPU time I/O time
10 CLUSTER 20 CLUSTER 50 CLUSTER
8
2.3.3 2° Soluzione
Di seguito sono riportati i risultati ottenuti dalla seconda soluzione, mettendo a confronto, in questo
caso, i tre tipi di esecuzione effettuate:
Sequenziale;
Open MPI attraverso Fast Ethernet;
Open MPI attraverso Myrinet.
Come si può notare dai risultati questa seconda soluzione è più prestante, sia in termini di
computazione e che di I/O, rispetto alla precedente soluzione, in quanto si è passati, per quanto
riguarda il file da 1 milione di punti, da un tempo medio pari a ~25 sec a un tempo medio pari a ~19
sec, mentre per quanto riguarda il file da 5 milioni di punti, si è passati da un tempo medio di ~123
sec a un tempo medio di ~95 sec.
9
Figura 2.5: Risultati seconda soluzione con input 1 milione di punti e 8 processi
8
CPU time I/O time Total time Speed-up Efficienza % real user sys
MPI_Myri 57,89 18,73 76,62 5,91 73,86% 1m18.118s 0m0.403s 0m0.074s
MPI_Ethe 61,12 19,9 81,02 5,59 69,85% 1m19.265s 0m0.390s 0m0.074s
Seq 436,63 16,13 452,76 7m32.762s 7m32.339s 0m0.392s
MPI_Myri 55,09 18,91 74 6,14 76,69% 1m14.621s 0m0.383s 0m0.081s
MPI_Ethe 60,23 23,27 83,5 5,44 67,96% 1m19.278s 0m0.376s 0m0.078s
Seq 437,84 16,15 453,99 7m34.038s 7m33.483s 0m0.487s
MPI_Myri 159,13 18,76 177,89 6,94 86,75% 2m58.601s 0m0.389s 0m0.076s
MPI_Ethe 163,39 19,79 183,18 6,74 84,25% 3m1.426s 0m0.368s 0m0.077s
Seq 1218,6 15,96 1234,56 20m34.566s 20m34.204s 0m0.361s
MPI_Myri 155,92 18,71 174,63 7,22 90,19% 2m55.255s 0m0.388s 0m0.088s
MPI_Ethe 161,28 23,28 184,56 6,83 85,34% 3m0.280s 0m0.379s 0m0.077s
Seq 1243,83 16,13 1259,96 20m59.971s 20m59.468s 0m0.501s
MPI_Myri 739,34 18,71 758,05 7,80 97,48% 12m38.761s 0m0.372s 0m0.088s
MPI_Ethe 747,04 19,82 766,86 7,71 96,36% 12m45.121s 0m0.380s 0m0.066s
Seq 5895,22 16,14 5911,36 98m31.366s 98m31.009s 0m0.374s
MPI_Myri 506,09 19,15 525,24 7,72 96,55% 8m45.856s 0m0.356s 0m0.100s
MPI_Ethe 512,38 23 535,38 7,58 94,72% 8m51.100s 0m0.370s 0m0.077s
Seq 4040,82 16,28 4057,1 67m37.100s 67m36.622s 0m0.489s
50 CLUSTER
Float
Double
Risultati 1 milione n° processi
10 CLUSTER
Float
Double
20 CLUSTER
Float
Double
10 Figura 2.6: Rappresentazione risultati seconda soluzione con input 1 milione di punti e 8 processi
0
1000
2000
3000
4000
5000
6000
seco
ndi
CPU time I/O time
10 CLUSTER 20 CLUSTER 50 CLUSTER
FLOAT
FLOAT
FLOAT
DOUBLE
DOUBLE
DOUBLE
11
Figura 2.7: Risultati seconda soluzione con input 5 milioni di punti e 8 processi
8
CPU time I/O time Total time Speed-up Efficienza % real user sys
MPI_Myri 452,81 93,2 546,01 6,73 84,09% 9m6.309s 0m0.380s 0m0.092s
MPI_Ethe 469,18 100,92 570,1 6,44 80,54% 9m18.192s 0m0.370s 0m0.079s
Seq 3593,16 79,94 3673,1 61m13.112s 61m11.152s 0m1.903s
MPI_Myri 447,36 96,57 543,93 6,70 83,74% 9m3.717s 0m0.384s 0m0.077s
MPI_Ethe 497,48 145,21 642,69 5,67 70,87% 9m52.697s 0m0.372s 0m0.080s
Seq 3563,27 80,63 3643,9 60m43.920s 60m41.394s 0m2.424s
MPI_Myri 1025,04 93 1118,04 7,40 92,48% 18m38.316s 0m0.380s 0m0.078s
MPI_Ethe 1044,69 99,21 1143,9 7,23 90,39% 18m51.980s 0m0.380s 0m0.082s
Seq 8190,06 81,44 8271,5 137m51.518s 137m49.586s 0m1.924s
MPI_Myri 1020,27 95,95 1116,22 7,37 92,16% 18m36.006s 0m0.374s 0m0.091s
MPI_Ethe 1045,22 115,82 1161,04 7,09 88,60% 18m56.467s 0m0.384s 0m0.097s
Seq 8147,44 81,92 8229,36 137m9.369s 137m5.976s 0m2.556s
MPI_Myri 2947,06 92,18 3039,24 7,83 97,91% 50m39.534s 0m0.381s 0m0.089s
MPI_Ethe 2981,83 99,25 3081,08 7,73 96,58% 51m9.175s 0m0.381s 0m0.072s
Seq 23724,23 82,27 23806,5 396m46.502s 396m44.732s 0m1.880s
MPI_Myri 3502,48 93,41 3595,89 7,81 97,61% 59m55.675s 0m0.370s 0m0.099s
MPI_Ethe 3528,11 114,95 3643,06 7,71 96,35% 60m18.422s 0m0.387s 0m0.085s
Seq 27997,48 82,53 28080,01 468m0.012s 467m57.314s 0m2.520s
50 CLUSTER
Float
Double
Risultati 5 milioni n° processi
10 CLUSTER
Float
Double
20 CLUSTER
Float
Double
12 Figura 2.8: Rappresentazione risultati seconda soluzione con input 5 milioni di punti e 8 processi
0
5000
10000
15000
20000
25000
30000
seco
ndi
CPU time I/O time10 CLUSTER 20 CLUSTER 50 CLUSTER
FLOAT
FLOAT
FLOAT
DOUBLE
DOUBLE
DOUBLE
13
L'implementazione parallela considera i dati in ingresso come variabili float (rapprentazione su 4
byte), valutando i dati ottenuti dalle varie misurazioni abbiamo notato che la differenza tra i due tipi
di infrastruttura di rete utilizzati è minima, come mostrato in Figura 2.5 e 2.7. Questa differenza
minima è data dal fatto che tra i vari processi non c'è molta comunicazione, come riportato di
seguito, in modo da non far prevalere nettamente una infrastruttura su un'altra.
--------------------------------------------------------------------------------------------
@--- Callsites: 24 ----------------------------------------------------------------------
--------------------------------------------------------------------------------------------
ID Lev File/Address Line Parent_Funct MPI_Call
1 0 mpi_kmeans.c 131 mpi_kmeans Allreduce
2 0 mpi_io.c 208 mpi_write File_write
3 0 mpi_io.c 216 mpi_write File_close
4 0 mpi_main.c 153 main Allreduce
5 0 mpi_io.c 101 mpi_read Bcast
6 0 mpi_kmeans.c 129 mpi_kmeans Allreduce
7 0 mpi_io.c 102 mpi_read Bcast
8 0 mpi_io.c 274 mpi_write Recv
9 0 mpi_kmeans.c 144 mpi_kmeans Allreduce
10 0 mpi_io.c 282 mpi_write File_write
11 0 mpi_main.c 119 main Barrier
12 0 mpi_kmeans.c 105 mpi_kmeans Allreduce
13 0 mpi_io.c 211 mpi_write File_write
14 0 mpi_io.c 255 mpi_write File_open
15 0 mpi_io.c 191 mpi_write File_open
16 0 mpi_io.c 268 mpi_write File_write
17 0 mpi_io.c 213 mpi_write File_write
18 0 mpi_main.c 194 main Reduce
19 0 mpi_main.c 192 main Reduce
20 0 mpi_io.c 121 mpi_read Send
21 0 mpi_io.c 284 mpi_write File_close
22 0 mpi_main.c 161 main Bcast
23 0 mpi_io.c 287 mpi_write Send
24 0 mpi_io.c 145 mpi_read Recv
14
--------------------------------------------------------------------------------------------
@--- Aggregate Sent Message Size (top twenty, descending, bytes) ----------
--------------------------------------------------------------------------------------------
Call Site Count Total Avrg Sent%
Send 20 7 7e+07 1e+07 93.69
Send 23 7 3.5e+06 5e+05 4.68
Allreduce 6 1432 1.15e+06 800 1.53
Allreduce 1 1432 5.73e+04 40 0.08
--------------------------------------------------------------------------------------------
@--- Aggregate I/O Size (top twenty, descending, bytes) -----------------------
--------------------------------------------------------------------------------------------
Call Site Count Total Avrg I/O%
File_write 16 125000 1.01e+06 8.11 99.80
File_write 13 200 1.9e+03 9.49 0.19
File_write 10 7 63 9 0.01
File_write 2 10 20 2 0.00
File_write 17 10 10 1 0.00
Come ulteriore controprova abbiamo effettuato una modifica sul tipo di dato in ingresso, passando
da un tipo float a un tipo double (rappresentazione su 8 byte).
15
3 Hadoop
Hadoop è un framework sviluppato dalla community Open-Source per applicazioni parallele su
cluster di grandi dimensioni. Sviluppato con tecnologia Java, è stato pensato per l’elaborazione di
grosse quantità di dati in applicazioni distribuite.
Uno dei paradigmi computazionali utilizzati dal framework è il MapReduce, il quale divide
l’applicazione in piccoli task, ed ognuno di essi potrà essere eseguito su uno o più nodi in modo da
rendere la nostra applicazione distribuita, senza l’ausilio di una particolare sintassi grammaticale.
Figura 3.1: Descrizione del processo MapReduce
MapReduce è diventato popolare grazie a Google che lo utilizza per elaborare ogni giorno molti
petabyte di dati. E’ costituito da due task scritti dall’utente, chiamati Map e Reduce, e da un
Framework che splitta l’input in ingresso in data set indipendenti che vengono processati in modo
parallelo su un cluster.
Il Map task legge un insieme di record da un file di input, svolge le operazioni di filtraggio e le
trasformazioni desiderate, quindi produce una serie di record di output nella forma convenuta (key,
value).
Dopo essere stati raccolti dal Framework i record di input vengono raggruppati per chiavi
(attraverso operazioni di sorting o hashing) e sottoposti al task Reduce. Come per il task Map,
Reduce esegue una elaborazione arbitraria attraverso un linguaggio general purpose. Di
conseguenza può compiere qualsiasi sorta di operazioni sui record. Per esempio può elaborare
16
alcune funzioni addizionali per altri campi dati del record. Ciascuna istanza Reduce può scrivere
record a un file di output e quest’ultimo rappresenta una parte della risposta soddisfatta da una
elaborazione MapReduce.
La coppia key/value prodotta dal task Map può contenere qualsiasi tipo di dati nel campo assegnato
al valore del campo.
I sistemi attuali implementano MapReduce utilizzando linguaggi come Java, C++, Python, Perl,
Ruby, e altri.
Le coppie key/value utilizzate nell’elaborazione MapReduce possono essere archiviate in un file o
in un database.
In aggiunta è stato implementato un file system distribuito (HDFS), il quale è stato pensato per
avere una elevata larghezza di banda ed un solido meccanismo di recovery-failure.
Hadoop ha un’architettura master-slave, con un unico host master e con host slave multipli. L’host
master gestisce:
JobTracker il quale schedula e gestisce tutti i task appartenenti a un processo in esecuzione;
NameNode il quale gestisce il file system HDFS e regola l’accesso ai file da parte dei clienti
Mentre l’host slave gestisce:
TaskTracker il quale lancia i task sul proprio host sulla base delle istruzioni ricevute dal
JobTracker; inoltre tiene traccia dei progressi di ogni task sul proprio host;
DataNode il quale fornisce blocchi di dati, i quali sono memorizzati sul proprio disco locale, ai
clienti HDFS.
Figura 3.2: Architettura Hadoop
17
3.1 Implementazione K-Means con Hadoop
In figura è riportato un esempio di come funziona l’algoritmo K-Means attraverso MapReduce:
Figura 3.3: Implementazione dell’algoritmo K-Means con Hadoop
0 Vengono scelti come cluster iniziali i primi k punti del file in input, dove k è il numero di
cluster scelti;
1 Il set dati in ingresso viene suddiviso in N parti, successivamente ogni parte viene inviata ad un
mapper;
2 Nella funzione Map viene calcolata la distanza tra ogni punto e ogni centro del cluster (cluster
center), ogni punto è etichettato con l'indice del cluster avente distanza minima. Il Mapper
genera in uscita le coppie key-value, dove la key è il numero del cluster assegnato a ogni punto
e la value sono le coordinate del punto;
3 Tutti i punti dello stesso cluster (tutti i record con la stessa key) vengono inviati ad un unico
Reducer, nella quale vengono calcolate le nuove coordinate dei k cluster attraverso la media dei
punti, l’output del Reducer è dato dal numero del cluster con le corrispondenti nuove
coordinate;
18
4 Successivamente le nuove coordinate vengono confrontate con quelle originali, se sono uguali
oppure la loro differenza è all’interno di una soglia prefissata, allora vuol dire che i cluster
calcolati sono quelli finali e il programma termina, altrimenti vengono considerati gli ultimi
cluster calcolati e si ripete iterativamente il procedimento dal punto 2 al punto 4.
Molti delle implementazioni effettuate con MapReduce sono limitate dalla larghezza di banda
disponibile sul cluster, quindi conviene ridurre al minimo la quantità di dati trasferiti tra i task Map
e Reduce. Per tale motivo spesso Hadoop consiglia di utilizzare una funzione Combiner che riceve
in ingresso l’output della funzione Map e fornisce in uscita l’input della funzione Reduce. La
funzione Combiner è introdotta per cercare di ottimizzare l’implementazione, nel nostro caso è stata
molto utile in quanto, avendo un solo Reducer a disposizione, esso fungeva da collo di bottiglia. La
funzione Combiner non sostituisce la funzione Reduce, ma contribuisce a ridurre la quantità di dati
che vengono scambiati tra Map e Reduce.
Input Output
Mapper <num point, coords point> < num cluster, coords point associate >
Combiner <num cluster, coords point associate> < num cluster, sum partial point >
Reducer < num cluster, sum partial point > <num cluster, new coords cluster>
Figura 3.4: Input e Output della funzione Map, Combiner e Reducer
19
4 Hadoop vs Open MPI
In questo capitolo mettiamo a confronto l’implementazione dell’algoritmo K-Means sviluppato con
OpenMPI e Hadoop.
Per poter correttamente interpretare i risultati ottenuti è da tenere in considerazione che il
Framework Hadoop è stato pensato e ideato per:
una quantità di dati maggiori di 1 TB;
massively parallel, cioè con sistemi distribuiti aventi centinai o migliaia di CPUs;
facile implementazione:
linguaggi Object Oriented;
il programmatore non ha bisogno di conoscere i dettagli strutturali del sistema di calcolo.
Fault-tolerance.
Di seguito sono riportati i risultati ottenuti, mettendo a confronto i valori ottenuti con Hadoop e i
valori ottenuto con Open MPI.
20 Figura 4.1: Confronto tra Hadoop e Open MPI mettendo in input 1 milione di punti e 4 processi
4
Total time Loop time
Map-Red 10689 71/72
Map-Comb-Red 7104 46/47
Open MPI Ethe 135 0,64
Map-Red 8837 71/72
Map-Comb-Red 5961 46/47
Open MPI Ethe 360 1,22
Map-Red 8892 75
Map-Comb-Red 5995 50
Open MPI Ethe 1502 3
Risultati 1 milione n° processi
10 CLUSTER
20 CLUSTER
50 CLUSTER
0
2000
4000
6000
8000
10000
12000
seco
nd
i
Total time
10 CLUSTER 20 CLUSTER 50 CLUSTER
21 Figura 4.2: Confronto tra Hadoop e Open MPI mettendo in input 5 milioni di punti e 4 processi
4
Total time Loop time
Map-Comb-Red 9268 68/69
Open MPI 981 3,2
Map-Comb-Red 14251 75
Open MPI 2131 6,1
Map-Comb-Red 28038 78
Open MPI 6007 14,73
Risultati 5 milioni n° processi
10 CLUSTER
20 CLUSTER
50 CLUSTER
0
5000
10000
15000
20000
25000
30000
seco
nd
i
Total time
10 CLUSTER 20 CLUSTER 50 CLUSTER
22
I risultati ottenuti sono stati confrontati con i risultati pubblicati da Wei Jiang, Vignesh T. Ravi e
Gagan Agrawal nella seguente documentazione:
http://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=05289199
3.1 Osservazioni
Con l’introduzione del task Combiner abbiamo ottenuto un’ottimizzazione sul tempo di
esecuzione di ogni singolo loop del 36%, in quanto si è passati da un tempo pari a ~71 sec a un
tempo pari a ~46 sec, applicando come input 1 milione di punti.
Al variare della quantità di dati in input la variazione del tempo impiegato per effettuare ogni
singolo loop è minima, cosa che non accade con OpenMPI. Ciò implica che la maggior parte
del tempo speso da Hadoop è da imputare all’inizializzazione e alla gestione del Framework.
Per la stessa quantità di dati in input il numero di loop impiegati da OpenMPI e Hadoop per
eseguire l’algoritmo differisce. In quanto non è stato possibile implementare lo stesso
algoritmo per il calcolo del valore del threshold a causa dell’implementazione del Framework
stesso.
23
Appendice A – Open MPI e Sequenziale
Di seguito sono riportati i comandi utilizzati per l’esecuzione dell’algoritmo K-Means:
Comando esecuzione over eth0
mpirun -np 8 --mca btl_tcp_if_include eth0 -machinefile allmachine
Simple_Kmeans_fileDouble_mpiP/mpi_main -i /state/partition1/fpace/file_5m_20c -n
10 -o
Comando Esecuzione Over myri0
mpirun -np 8 --mca btl_tcp_if_include myri0 -machinefile allmachine
Simple_Kmeans_fileDouble_mpiP/mpi_main -i /state/partition1/fpace/file_1m_20c -n
10 -o
Comando Esecuzione Sequenziale
Simple_Kmeans_fileDouble_mpiP/seq_main -i /state/partition1/fpace/file_1m_20c -n 10
–o
24
Appendice B – MapReduce
Di seguito è riportato il codice, in linguaggio Java, dell’implementazione dell’algoritmo K-
Means attraverso MapReduce:
KmeansDriver.java
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
public class KmeansDriver {
public static void main(String[] args) throws Exception {
long start, start_loop, stop, stop_loop;
//start global timer
start = System.currentTimeMillis();
Configuration conf = new Configuration();
boolean converged=true;
String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
if (otherArgs.length != 4) {
System.err.println("Usage: KmeansDriver <in_directory> <out_directory>
<in_cluster/cluster_file> num_coord_point");
System.exit(2);
}
25
int i=0;
do{
//set global coord value from commandline
conf.set("my.num.coord", otherArgs[3]);
if(i==0){
conf.set("my.cluster.coord",otherArgs[2] );
}
else
if(i==1)
conf.set("my.cluster.coord",otherArgs[1]+"0"+"/part-r-00000" );
else
conf.set("my.cluster.coord",otherArgs[1]+(i-1)+"/part-r-00000");
Job job = new Job(conf, "Kmeans_unicondor");
//only one reduce
job.setNumReduceTasks(1);
job.setJarByClass(KmeansDriver.class);
job.setMapperClass(KmeansMapper.class);
job.setCombinerClass(KmeansCombiner.class);
job.setReducerClass(KmeansReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]+i));
//start timer loop
start_loop = System.currentTimeMillis();
job.waitForCompletion(true);
//check if cluster_centroid are equal
if(i>=2)
converged=isConverged(i,Integer.parseInt(otherArgs[3]), otherArgs[1]);
i++;
//stop timer loop
stop_loop = System.currentTimeMillis();
System.out.println("Time_loop_"+(i-1) +": "+ (stop_loop - start_loop)/1000 + " s");
}
while(i<500 && converged);
System.out.println("Clustering...");
//start timer clustering
long start_clustering = System.currentTimeMillis();
clustering(otherArgs[0],otherArgs[1]+"points",conf.get("my.cluster.coord"),otherArg
s[3]);
26
//stop timer clustering
long stop_clustering = System.currentTimeMillis();
System.out.println("Time Clustering: " + (stop_clustering - start_clustering)/1000 + "
s");
//stop timer programma
stop = System.currentTimeMillis();
System.out.println("Time Total: " + (stop - start)/1000 + " s");
}
private static boolean isConverged(int iteration, int num_coord, String dir_output) throws
IOException {
boolean ret=true;
ByteArrayOutputStream byte1=new ByteArrayOutputStream();
PrintStream out2 = new PrintStream(byte1);
HashMap<String,double[]> cluster= new HashMap<String,double[]>();
HashMap<String,double[]> cluster1= new HashMap<String,double[]>();
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(new Path(dir_output+(iteration-1)).toUri(),conf);
FSDataInputStream in = null, in1=null;
try {
in = fs.open(new Path(dir_output+(iteration-1)+"/part-r-00000"));
IOUtils.copyBytes(in, out2, 4096, false);
String s=byte1.toString();
String lines[]= s.split("\n");
for(int i=0; i<lines.length; i++){
double[] centers= new double[num_coord];
StringTokenizer itr = new StringTokenizer(lines[i]);
String id_cluster= itr.nextElement().toString();
int j=0;
while(itr.hasMoreElements()){
centers[j]=(Double.parseDouble(itr.nextElement().toString()));
j++;
}
cluster.put(id_cluster, centers);
}
ByteArrayOutputStream byte2=new ByteArrayOutputStream();
PrintStream out3 = new PrintStream(byte2);
27
FileSystem fs1 = FileSystem.get(new Path(dir_output+iteration).toUri(),conf);
in1 = fs1.open(new Path(dir_output+iteration+"/part-r-00000"));
IOUtils.copyBytes(in1, out3, 4096, false);
String s1=byte2.toString();
String lines1[]= s1.split("\n");
for(int i=0; i<lines1.length; i++){
double[] centers1= new double[num_coord];
StringTokenizer itr = new StringTokenizer(lines1[i]);
String id_cluster= itr.nextElement().toString();
int j=0;
while(itr.hasMoreElements()){
centers1[j]=(Double.parseDouble(itr.nextElement().toString()));
j++;
}
cluster1.put(id_cluster, centers1);
}
}
finally {
IOUtils.closeStream(in);
}
int cont=0;
double[] first_cluster;
double[] second_cluster;
for(String key :cluster.keySet()){
first_cluster = cluster.get(key);
second_cluster = cluster1.get(key);
if(KmeansUtil.isEqualThresHold(first_cluster, second_cluster)) {
cont++;
}
}
if(cont==cluster.size()) {
ret=false;
//debug
System.out.println("PATH is: " +dir_output+(iteration)+"/part-r-00000");
System.out.println("PATH is: " +dir_output+(iteration-1)+"/part-r-00000");
System.out.println("debug all clusters are equal");
System.out.println("HashMap size:"+ cluster.size());
System.out.println("HashMap1 size:"+ cluster1.size());
System.out.println("Cont :" + cont);
}
return ret;
28
}
public static void clustering(String input, String output,String cluster, String num_coord)
throws Exception {
Configuration conf = new Configuration();
conf.set("my.num.coord", num_coord);
conf.set("my.cluster.coord",cluster );
Job job = new Job(conf, "Kmeans_unicondor_clustering");
job.setJarByClass(KmeansDriver.class);
job.setMapperClass(KmeansMapper.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
FileInputFormat.addInputPath(job, new Path(input));
FileOutputFormat.setOutputPath(job, new Path(output));
job.waitForCompletion(true);
}
}
KmeansMapper.java
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class KmeansMapper extends Mapper<Object, Text, Text, Text>{
private HashMap<String,double[]> meansCluster= new HashMap<String,double[]>();
@Override
public void setup(Context context){
try {
29
ByteArrayOutputStream byte1=new ByteArrayOutputStream();
PrintStream out2 = new PrintStream(byte1);
String uri = context.getConfiguration().get("my.cluster.coord");
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(new Path(uri).toUri(),conf);
FSDataInputStream in = null;
try {
in = fs.open(new Path(uri));
IOUtils.copyBytes(in, out2, 4096, false);
String s=byte1.toString();
String lines[]= s.split("\n");
for(int i=0; i<lines.length; i++){
double[] centers= new
double[Integer.parseInt(context.getConfiguration().get("my.num.coord"))];
StringTokenizer itr = new StringTokenizer(lines[i]);
String id_cluster= itr.nextElement().toString();
int j=0;
while(itr.hasMoreElements()){
centers[j]=(Double.parseDouble(itr.nextElement().toString()));
j++;
}
meansCluster.put(id_cluster, centers);
}
}
finally {
IOUtils.closeStream(in);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void map(Object key, Text value, Context context ) throws IOException,
InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
//get global value passing from main arguments
int num_coord= Integer.parseInt(context.getConfiguration().get("my.num.coord"));
Text word = new Text();
word.set(itr.nextElement().toString());
30
double[] array_points = new double[num_coord];
int k=0;
while (itr.hasMoreTokens()) {
array_points[k]=Double.parseDouble(itr.nextToken());
k++;
}
//get coordinate cluster
double nearestDistanceOut=Double.MAX_VALUE;
double nearestDistance = 100;
double[] array_cluster = new double[num_coord];
String cluster=null;
Set<String> list = meansCluster.keySet();
for(String s:list) {
array_cluster = meansCluster.get(s);
double distance = KmeansUtil.getEuclideanDistance( array_cluster, array_points);
if (distance < nearestDistance) {
nearestDistance = distance;
cluster=s;
}
nearestDistanceOut=nearestDistance;
}
context.write(new Text(cluster), new Text(KmeansUtil.getString(array_points)));
}
}
KmeansCombiner.java
import java.io.IOException;
import java.util.StringTokenizer;
import java.util.Vector;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class KmeansCombiner extends Reducer<Text,Text,Text,Text> {
public void reduce(Text key, Iterable<Text> values, Context context) throws IOException,
InterruptedException {
int num_coord=Integer.parseInt(context.getConfiguration().get("my.num.coord"));
int i;
// all points vector
Vector<double[]> all_points_vector = new Vector<double[]>();
for (Text val : values) {
31
// single vector of points only one centroid
double[] vec_point_by_key= new double[num_coord];
StringTokenizer itr = new StringTokenizer(val.toString());
i=0;
while(itr.hasMoreElements()) {
vec_point_by_key[i]=Double.parseDouble(itr.nextElement().toString());
i++;
}
all_points_vector.add(vec_point_by_key);
}
double sum;
double[] vec_point_get_by_key= new double[num_coord];
double[] centroid_cluster_partial_sum = new double[num_coord];
int cont=0;
for( int j=0; j<num_coord;j++){
cont=0;
sum=0;
vec_point_get_by_key= new double[num_coord];
for(int k=0; k<all_points_vector.size(); k++){
vec_point_get_by_key=(double[])all_points_vector.get(k);
sum+= vec_point_get_by_key[j];
cont++;
}
centroid_cluster_partial_sum[j]=sum;
}
context.write(key, new Text(Integer.toString(cont)+ " "+
KmeansUtil.getStringPadding(centroid_cluster_partial_sum)));
}
}
KmeansReducer.java
import java.io.IOException;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.Iterator;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
32
public class KmeansReducer extends Reducer<Text,Text,Text,Text> {
public void reduce(Text key, Iterable<Text> values, Context context) throws IOException,
InterruptedException {
int num_coord=Integer.parseInt(context.getConfiguration().get("my.num.coord"));
// Double vector all points of certain centroid
Vector<double[]> all_points_vector = new Vector<double[]>();
int i;
int total_points=0;
for( Text val : values){
StringTokenizer itr = new StringTokenizer(val.toString());
//sum number of element
total_points+= Integer.parseInt(itr.nextToken());
double[] vec_point_by_key= new double[num_coord];
i=0;
while(itr.hasMoreElements()) {
vec_point_by_key[i]=Double.parseDouble(itr.nextElement().toString());
i++;
}
all_points_vector.add(vec_point_by_key);
}
double sum;
double[] vec_point_get_by_key= new double[num_coord];
double[] new_centroid_cluster = new double[num_coord];
for(int j=0; j<num_coord;j++){
sum=0;
vec_point_get_by_key= new double[num_coord];
for(int k=0; k<all_points_vector.size(); k++){
vec_point_get_by_key=(double[])all_points_vector.get(k);
sum+= vec_point_get_by_key[j];
}
new_centroid_cluster[j]= sum/total_points;
}
context.write(key, new Text(KmeansUtil.getStringPadding(new_centroid_cluster)));
}
}