viewgit/index.php:465 Only variables should be passed by reference [2048]
viewgit/index.php:466 Non-static method GeSHi::get_language_name_from_extension() should not be called statically [2048]
\chapter{Page Rank} Con Page Rank intendiamo un ``valutazione'' data ad una determinata pagina o documento con lo scopo di stimare la sua importanza\footnote{che è ovviamente un concetto poco matematico, ma ci occuperemo di formalizzarlo meglio in seguito.}. Un algoritmo derivato da quello che verrà in seguito esposto è utilizzato attualmente da Google (\href{http://www.google.it}{http://www.google.it}) per ordinare i risultati di una query nel suo motore di ricerca, così come da tutti i principali Prima di tutto dobbiamo occuparci di definire il modello matematico che vogliamo usare per schematizzare la nostra valutazione di importanza. \\ In generale l'idea che sta alla base del Page Rank è dare importanza ad una pagina è un ``puntatore'' ad un altro documento. In genere l'utente può attivare il puntatore semplicemente cliccandoci sopra con il mouse} che in essa sono presenti. Alcune idee che potrebbero sembrare valide potrebbero essere valutare l'importanza di una pagina in base al numero di link presenti nella pagina, oppure al numero di link che puntano ad essa. Dopo una breve riflessione ci si rende conto che entrambi i metodi introducono svariate problematiche di affidabilità. In generale, infatti, il creatore di una pagina ha il controllo di quanti link sono presenti in una pagina, e non fatica molto a creare delle pagine fittizie che puntano a quella appena creata. La valutazione sarebbe quindi falsata e molto vulnerabile a frode. Si può però cercare di affinare l'idea di base in modo da risolvere questi inconvenienti. Per convenzione assumiamo di numerare tutte le pagine presenti nel Web con dei numeri naturali $1 \ldots N$ (dove $N$ è un numero dell'ordine di $10^{10}$). Fissata una certa pagina $j$ consideriamo i link che puntano a lei. Risulta chiaro che non solo di questi dovrebbe influire. Per esempio, se la homepage di Yahoo! punta alla pagina di una data persona, è naturale che questa pagina acquisisca una certa rilevanza. Viceversa, se sulla mia pagina personale metto un link al sito della stessa persona l'importanza di questo varia di poco\footnote{ a meno che nel momento in cui voi leggiate queste pagine io non sia diventato una persona con una certa influenza, ma questo è probabilmente un caso da scartare}. Un'ultima e non meno importante considerazione è che se anche Yahoo! punta al mio sito ma lo fa in una pagina con un altro centinaio di link è una faccenda molto diversa dall'essere puntati dall'unico link in homepage. Stiamo quindi arrivando ad un altro modello, in cui le pagine \emph{distribuiscono la loro importanza fra i link al loro interno}. Questo modello è molto più ragionevole del precedente, ed è molto meno predisposto a dare risultati falsati. Vorremo però averne una descrizione matematica precisa. Consideriamo la matrice quadrata $H = (h_{ij})$, che chiameremo \emph{matrice delle connessioni}, definita nel seguente modo \[ h_{ij} = \left\{ \begin{array}{ll} 1 & \text{se esiste un link dalla pagina} \ i \ \text{alla pagina} \ j \\ 0 & \text{altrimenti} \end{array} \right. \] Questa matrice avrà dimensione $N \times N$ ma sarà sparsa, ovvero la maggior parte dei suoi valori saranno $0$. Per fissare le idee consideriamo la Figura~\ref{fig:web}, un modello (molto) semplificato del Web, in cui esistono solamente 3 pagine. \begin{figure}[hb] \[ \xymatrix{ {\bullet{1}} \ar@/_/[dr] \ar@/^1pc/[drr] & \\ & {\bullet{2}} \ar@/_/[ul]& {\bullet{3}} \ar@/^/[l]} \] \end{figure} La matrice di connessione $H$ associata a questo grafo è la seguente \[ H = \left[ \begin{array}{ccc} 0 & 1 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 0 \end{array} \right] \] \`E facile verificare che valgono le seguenti uguaglianze \[ \left\{ \begin{array}{ll} \sharp \{ \text{collegamenti uscenti da} \ i \} & = \sum_{j=1}^{N} h_{ij} \\ \sharp \{ \text{collegamente che arrivano a} \ j \} & = \sum_{i=1}^{N} h_{ij} \end{array} \right. \] \end{os} In particolare sarebbe piuttosto immediato calcolare, partendo dalla matrice $H$, i valori del Page Rank con i metodi ipotizzati all'inizio del capitolo (per altro reputati incompleti) Siamo dunque interessati a calcolare il vettore $W$ dove $w_i$ è l'importanza della pagina $i$, in modo che rispetti le linee guide definite precedentemente. Chiamiamo $d_i$ il numero di link uscenti dalla pagina $i$ $D$ la matrice con i $d_i$ sulla diagonale e nulla per i restanti valori. Introdotta questa notazione, secondo il nostro modello ogni componente di $W$ dovrebbe rispettare la seguente equazione \[ \] che rappresenta esattamente l'idea che l'importanza di ogni pagina è la somma delle importanze di quelle che la linkano, in cui ogni addendo è normalizzato sul numero di link uscenti\footnote{probabilmente l'equazione è molto più chiara di questa frase, ma non commentare mi sarebbe sembrato inopportuno}. \`E quindi chiaro come il nostro problema si sia trasformato in un problema di autovalori ed autovettori. In particolare, se poniamo $M = D^{-1}H$ la nostra richiesta si può scrivere nella forma più compatta $\trasp{W} = \trasp{W}M$, ovvero $\trasp{W}$ è un autovettore sinistro di $M$. Sorgono spontanee alcune domande a cui dobbiamo rispondere prima di procedere oltre. \begin{enumerate}[(a)] ma questo non è garantito a priori; richiesta non ammette soluzione, e quindi possiamo rinunciare in partenza alla nostra idea; autospazio ha dimensione $1$. Siamo ben consci che se ne troviamo uno ogni suo multiplo scalare continuerà ad essere un autovettore. Per altro, moltiplicare le valutazioni omogeneamente per lo stesso cofficiente non sarebbe in nessun modo un problema}?. Se così non fosse sorgerebbero dei seri problemi su come interpretare i risultati ottenuti; a priori, ma sembrerebbe coerente fare in modo che delle votazioni siano numeri positivi; \end{enumerate} Cominciamo col rispondere alla domanda~(\ref{en:pagerank:a}). Ovviamente potrebbe benissimo succedere che per qualche $i$ $d_i = 0$. Questi ``nodi'' vengono chiamati \emph{dangling node}, ovvero ``nodi penzolanti'', che non portano a niente. Per risolvere questo problema si sostituisce la matrice $H$ con una matrice $\hat H$ tale che se $h_{ij} = 0 \: \forall j$ allora $\hat h_{ij} = 1 \: \forall j$, e in caso contrario $\hat h_{ij} = h_{ij}$. Questa matrice risolve evidentemente il problema e cambia di poco il modello. Supporre che una determinata pagina non distribuisca la sua importanza a nessuno o la distribuisca in maniera analoga a tutti non è un gran cambiamento. La domanda~(\ref{en:pagerank:b}) ha invece una risposta immediata. Sì, l'autovalore $1$ fa parte dello spettro ed un autovettore (destro) a lui associato è quello composto di tutti $1$. La verifica è un banale conto. che non dimostreremo (in questa sede) ottenuto da Perron-Frobenius sulla Teoria delle Matrici non negative. Se $A$ è irriducibile allora $\rs{A} > 0$ è un autovalore semplice ed esistono $x,y$ autovettori destro e sinistro con componenti strettamente positive. \end{te} \begin{os} Sembrerebbe, a priori, che non siamo in grado di determinare il raggio spettrale di $A$. Consideriamo però che $1$ è nello spettro e che $||A||_{\infty} = 1$ e ogni norma indotta supera il raggio spettrale. Si può \end{os} Sfortunatamente si può osservare che, pur con tutte queste buone proprietà, in generale i metodi applicati per trovare l'autovettore (il metodo delle potenze) potrebbe non essere convergente, perché gli autovalori diversi da $1$ potrebbero ancora avere modulo $1$\footnote{Come visto nella teoria, la velocità di convergenza del metodo di iterazione è data dal rapporto fra il più grande autovalore diverso da quello di cui si sta cercando l'autovettore e l'autovalore in questione. In questo caso il rapporto potrebbe essere $1$, che darebbe una situazione di non convergenza}. Introduciamo una definizione che ci permetterà di formalizzare meglio il problema. \begin{de} Una matrice $A$ si dice \emph{ciclica} se esiste un sottoinsieme dei suoi autovalori $J \subseteq \spe{A}$ tale che $\forall \lambda,\mu \in J$ si ha che $|\lambda|=|\mu|$. \end{de} Per superare questa difficoltà si può, ancora una volta, modificare il modello. Fissiamo $\gamma > 0$ e consideriamo che il visitatore possa in ogni momento, con probabilità $1-\gamma$, decidere di aprire una pagina a caso. Potremmo rappresentare questa nuova situazione sostituendo $M$ con la matrice \[ A = \gamma M + (1-\gamma) \left[ \begin{array}{c} 1 \\ \vdots \\ 1 \] ovvero con una combinazione convessa della matrice $M$ con la matrice di soli $1$. Il valore di $\gamma$ si può scegliere a piacimento; Google usa un valore $\gamma = 0.85$. In questo caso la matrice $A$ non ha elementi nulli e quindi è irriducibile e non è ciclica\footnote{La condizione di ciclicità è quella che crea problemi con gli autovalori di modulo uguale al raggio spettrale}. Possiamo infine utilizzare un'altra versione del Teorema~\ref{te:PerronFrobenius} che dice \begin{te} Sia $A$ una matrice con elementi strettamente positivi. Allora per ogni suo autovalore $\lambda \neq \rs{A}$ si ha che vale la diseguaglianza $|\lambda| < |\rs{A}|$. \end{te} Quest'ultimo ci assicura la convergenza del nostro metodo. Presenteremo in questa sezione una versione del metodo delle potenze adattata alle nostre esigenze. Consideriamo la matrice $A$ di cui vogliamo trovare l'autovettore sinistro relativo all'autovalore $1$. Sappiamo che esiste la forma di Jordan di $A$ ed in particolare una matrice $S$ tale che \[ S^{-1} A S = J \qquad J = \left[ \begin{array}{c|cc} 1 & \multicolumn{2}{|c}{\herm{0}} \\ \hline \multirow{2}{*}{$0$} & \sblocke{\hat J}{2} \\ & & \end{array} \right] \] Osserviamo ora che $e_1$ è autovettore destro di $J$ ed anche autovettore sinistro. Entrambi sono relativi all'autovalore $1$. Sappiamo quindi che $e = Se_1$ è autovalore destro di $A$ e quindi è il vettore con tutte le componenti uguali ad $1$, mentre $\trasp{w} = \trasp{e_1}S^{-1}$ è autovettore sinistro di $A$, ovvero il vettore che stiamo cercando. In particolare quanto visto nella precedente sezione ci assicura che $\rs{\hat J} < 1$ e quindi possiamo osservare che \[ \lim_{k\to\infty} SJ^kS^{-1} = Se_1 \trasp{e_1}S^{-1} = e\trasp{w} \] Questo (apparentemente) risolve il nostro problema in quanto $e\trasp{w}$ non è altro che la matrice le cui righe coincidono con il vettore $\trasp{w}$ che è precisamente quanto stiamo cercando. \\ La complessità di questo calcolo non è però trascurabile, in quanto la moltiplicazione di una matrice costa in generale $O(n^3)$, decisamente troppo se $n \approx 10^{10}$ come nel nostro caso. \begin{os} Consideriamo dunque un vettore $x_0$ tale che $\trasp{e}x_0 = 1 = ||x_0||_{1}$, e definiamo la successione per ricorrenza \[ \left\{ \begin{array}{ll} \trasp{x_0} & = \trasp{x_0} \\ \trasp{x_{k+1}} & = \trasp{x_{k}} A \end{array} \right. \] Possiamo notare che in generale $x_k = x_0 A^{k}$ e quindi data la nostra scelta oculata di $x_0$ abbiamo che \[ \] In questo modo siamo scesi da una complessità $O(n^3)$ ad una $O(n^2)$. \end{os} Sfortunatamente $O(n^2)$ nel nostro caso continua ad essere un numero troppo grande; arrivati a questo punto ci accorgiamo che possiamo sfruttare la particolarità di $A$, che non è assolutamente una matrice qualsiasi! Sappiamo infatti che è composta quasi esclusivamente di zeri, e quindi una memorizzazione efficiente ci permetterebbe anche un'implementazione efficiente dei procedimenti di moltiplicazione. %% TODO: QUalcosa di più sull'implementazione del metodo Analizzato il problema matematicamente, sorge il bisogno di analizzare le difficoltà di implementazione dovute all'hardware attualmente disponibile. Una matrice delle dimensioni di $A$ non è realmente memorizzabile elemento per elemento su nessun calcolatore, e non è pensabile nemmeno in alcun tipo di cluster. \\ Un rapido conto mostra come, anche ipotizzando di usare dei tipi \verb-float- a 4 byte (in ogni caso presumibilmente troppo imprecisi) otteniamo uno spazio necessario di $N^2 \cdot 4$ byte, ovvero circa $10^{20}$ byte = $10^{11}$ GB di RAM, probabilmente un quantitativo superiore a quella di tutti i computer del mondo messi assieme\footnote{può darsi che nei prossimi anni quest'affermazione smetta di essere vera, ma non penso sia rilevante ai fini del problema}. Dobbiamo quindi adottare degli stratagemmi per gestire la memoria in modo efficiente. Una prima osservazione è che la nostra matrice $A$ si può scrivere nel seguente modo \[ A = \gamma D^{-1} \hat H + (1-\gamma) e\trasp{v} \] dove $\gamma$ e $w$ sono rispettivamente il parametro ed il vettore di personalizzazione. Per memorizzare $A$ possiamo quindi memorizzare $D$ ed $\hat H$, $\gamma$ e $w$. \\ Questo ci dà dei vantaggi perché la matrice $\hat H$ è ``quasi nulla'' nel senso che è fatta solamente di $0$ ed $1$, ma la percentuale di $0$ è molto alta\footnote{è infatti presumibile che il numero di link uscenti da una pagina sia trascurabile rispetto al numero di pagine esistenti}. Possiamo quindi limitarci a memorizzare i seguenti dati: \\[5pt] $N$ & l'ordine della matrice $A$, ovvero il numero di pagine esistenti & 4 byte \\ $m$ & il numero di campi di $\hat H$ diversi da $0$ & \textasciitilde $N$ byte \\ $i, j$ & vettori delle coordinate dei campi diversi da $0$ di $\hat H$ & \textasciitilde $N$ byte \\ $\gamma$ & coefficiente di personalizzazione & 4 byte \\ $w$ & Vettore di personalizzazione & \textasciitilde $N$ byte \\ $d$ & il vettore con gli elementi diagonali di $D$ & \textasciitilde $N$ byte \\ Possiamo ora porci il problema di calcolare $d$. Questo è reso apparentemente più complicato dal modo in cui abbiamo salvato la matrice. In realtà possiamo usare un algoritmo di questo tipo \begin{lstlisting}[frame=tb,caption=Calcolo di $d$,label=lst:calcolovettorepers]{} ! Inizializziamo il vettore d a 0 d = 0; ! Questo ciclo calcola i link uscenti da i per ogni i DO k=1,m d(i(k)) = d(i(k)) + 1 END DO \end{lstlisting} \begin{os} L'aver scelto questo tipo di memorizzazione per la matrice non solo non complica i generali procedimenti di moltiplicazione, ma riduce addirittura la complessità di questi perché iteriamo sugli elementi diversi da $0$ invece che su tutti. \end{os} In questa sezione ci occuperemo di presentare una prima implementazione della soluzione al problema. Non ci preoccuperemo troppo di ottimizzare il codice, compito che affronteremo in seguito. Esporremo inizialmente la struttura generale del programma e poi costruiremo tutte le subroutine necessarie. Il programma dovrà comporsi delle seguenti parti \begin{description} \item[Lettura della matrice] Supporremo di avere un file da cui leggere i dati della matrice. Dobbiamo definire la sintassi del file e scrivere un subroutine per caricarlo in memoria; riga che ci servirà in seguito per rinormalizzare il vettore di iterazione; \item[Iterazione] Generiamo un vettore $x$ casuale e lo riscaliamo in modo che $\trasp{e} x = 1$. Fatto questo cominciamo a calcolare l'iterazione, fino a che l'errore non è piccolo o ragguingiamo il massimo numero di iterazioni impostato; \end{description} Per questo avremo bisogno anche di alcune subroutine, ad esempio quella che ci calcoli il prodotto matrice vettore data la matrice sparsa come l'abbiamo intesa noi. Esponiamo qui un'idea di codice per calcolare questo prodotto matrice vettore. Ricordiamo che per noi una matrice sparsa è composta di due scalari \lstinline-n-,\lstinline-m- (rispettivamente dimensione e numero di elementi diversi da $0$) e due vettori \lstinline-i-, \lstinline-j- di dimensione $m$ che sono le coordinate dei punti che valgono $1$.\\ Il codice è il seguente \begin{lstlisting}[frame=tb,caption=Calcolo del prodotto matrice vettore,label=lst:matrixvec] SUBROUTINE dotprod(n,m,i,j,d,x,v,y) IMPLICIT NONE ! Il parametro dp dei tipi a doppia precisione INTEGER, PARAMETER :: dp = KIND(0.D0) ! Dichiarazione degli argomenti della funzione INTEGER, INTENT(IN) :: n,m INTEGER, DIMENSION(m), INTENT(IN) :: i,j INTEGER, DIMENSION(n), INTENT(IN) :: d REAL(dp), DIMENSION(n), INTENT(IN) :: x, v ! Dato di output REAL(dp), DIMENSION(n) :: y ! Alcune variabili di comodo REAL(dp), DIMENSION(n) :: xx REAL(dp) :: s, gamma = 0.85 INTEGER :: k ! Calcoliamo x trasposto per la matrice D con i d(i) sulla ! diagonale, e salviamolo in xx, in modo da non modificare x ! Ricordiamo che i dati in fortrano vengono sempre passati per ! riferimento! Se modificassimo x qui dentro risulterebbe cambiato ! anche all'esterno. DO k=1,m IF(d(k)/=0) THEN xx(i(k)) = x(i(k))/d(k) ELSE xx(i(k)) = x(i(k))/n END IF END DO ! Ora eseguiamo la moltiplicazione di xx per la parte non dangling ! della matrice H cappuccio e salviamo il risultato in y y = 0.D0 DO k=1,m y(j(k)) = y(j(k)) + xx(i(k)) END DO ! Ora calcoliamo le righe dangling dalle quali prima non siamo "passati" ! perche' i(k) scorre solo sulle righe dove ci sono elementi non nulli DO k=1,n IF(d(k) == 0) s = s + xx(k) END IF END DO ! Sommiamo i due contributi y = y + s ! Calcolo la personalizzazione y = y * gamma + (1-gamma)/n END SUBROUTINE dotprod \end{lstlisting} %TODO: Presentare un'implementazione completa del programma