L'algorithme PageRank attribue un score d'importance aux sommets d'un graphe orienté. Chaque sommet représente une page web et chaque arête orientée symbolise un lien hypertexte d'une page vers une autre. Une page recevant des liens depuis d'autres pages bien notées obtient un score plus élevé. Ce principe explique pourquoi un compte Twitter suivi par des utilisateurs influents est mieux classé qu'un compte suivi par des bots.
Fonctionnement
Au début de chaque exécution, tous les sommets reçoivent le même score initial : 1 / TotalNumVertices.
À chaque superstep, chaque sommet envoie un vote à ses voisins. La valeur du vote correspond au score actuel du sommet divisé par son degré sortant :
vote sent to neighbor = PageRank(j) / out_degree(j)
À la fin de chaque superstep, chaque sommet recalcule son score en additionnant tous les votes entrants et en appliquant un facteur d'amortissement :
PageRank(i) = 0.15 / TotalNumVertices + 0.85 × sum of incoming votes
Le facteur d'amortissement (0,85) modélise la probabilité qu'un internaute naviguant au hasard suive un lien plutôt que de sauter vers une page aléatoire. Le complément (0,15) garantit que chaque sommet conserve un score de base non nul, même en l'absence de liens entrants.
Par défaut, l'algorithme s'exécute jusqu'à 30 itérations (ce paramètre est configurable).
Prérequis
Avant de commencer, assurez-vous d'avoir :
Configuré l'environnement de test MaxCompute Graph en écrivant une tâche Graph
Installé le client MaxCompute
Exécuter l'exemple PageRank
Étape 1 : Préparer le fichier JAR
Placez le fichier graph-examples.jar dans le dossier data\resources situé dans le répertoire bin du client MaxCompute.
Étape 2 : Créer les tables d'entrée et de sortie
Exécutez les instructions SQL suivantes dans le client MaxCompute :
CREATE TABLE pagerank_in(vertex STRING, des_1 STRING, des_2 STRING);
CREATE TABLE pagerank_out(vertex_id STRING, vertex_value DOUBLE);
La table pagerank_in utilise la première colonne comme sommets source et les colonnes restantes comme sommets de destination.
Étape 3 : Enregistrer le fichier JAR
-- Use -f to overwrite if the resource already exists.
add jar data\resources\graph-examples.jar -f;
Étape 4 : Charger les données de test
Depuis le répertoire bin du client MaxCompute, chargez le fichier data.txt dans la table pagerank_in :
tunnel upload data.txt pagerank_in;
Le fichier data.txt contient les arêtes du graphe suivantes :
1,2,4
2,1,3
4,2,3
3,1,2
Chaque ligne indique un sommet source suivi de ses sommets de destination. Par exemple, le sommet 1 possède des arêtes pointant vers les sommets 2 et 4.
Étape 5 : Exécuter la tâche
jar -resources graph-examples.jar -classpath data\resources\graph-examples.jar
com.aliyun.odps.graph.PageRank pagerank_in pagerank_out
Paramètres de la tâche
|
Paramètre |
Description |
Valeur par défaut |
|
|
Nom de la table d'entrée |
— |
|
|
Nom de la table de sortie |
— |
|
Nombre maximal d'itérations (troisième argument facultatif) |
Nombre maximal de supersteps avant l'arrêt de la tâche |
|
Pour modifier le nombre maximal d'itérations, transmettez-le en tant que troisième argument :
jar -resources graph-examples.jar -classpath data\resources\graph-examples.jar
com.aliyun.odps.graph.PageRank pagerank_in pagerank_out 50
Résultats attendus
Une fois la tâche terminée, interrogez la table pagerank_out :
+------------+--------------------+
| vertex_id | vertex_value |
+------------+--------------------+
| 1 | 0.2781238395149928 |
| 2 | 0.3245614688676814 |
| 3 | 0.24161225195637787|
| 4 | 0.155702636559485 |
+------------+--------------------+
Le sommet 2 obtient le score le plus élevé car les sommets 1 et 4 pointent vers lui. De plus, le sommet 1 reçoit lui-même des liens entrants des sommets 2 et 3. Le sommet 4 affiche le score le plus bas : seul le sommet 1 pointe vers lui et aucun sommet fortement noté ne lui accorde de vote significatif.
Pour effectuer un débogage local avant de soumettre la tâche au cluster, consultez la section Débogage local .
Exemple de code
L'implémentation Java complète est présentée ci-dessous. Les classes clés sont :
PageRankVertex: définit la logique de calcul pour chaque sommetPageRankVertexReader: charge la table d'entrée et construit le graphemain: configure et exécute la tâcheGraphJob
import java.io.IOException;
import org.apache.log4j.Logger;
import com.aliyun.odps.io.WritableRecord;
import com.aliyun.odps.graph.ComputeContext;
import com.aliyun.odps.graph.GraphJob;
import com.aliyun.odps.graph.GraphLoader;
import com.aliyun.odps.graph.MutationContext;
import com.aliyun.odps.graph.Vertex;
import com.aliyun.odps.graph.WorkerContext;
import com.aliyun.odps.io.DoubleWritable;
import com.aliyun.odps.io.LongWritable;
import com.aliyun.odps.io.NullWritable;
import com.aliyun.odps.data.TableInfo;
import com.aliyun.odps.io.Text;
import com.aliyun.odps.io.Writable;
public class PageRank {
private final static Logger LOG = Logger.getLogger(PageRank.class);
public static class PageRankVertex extends
Vertex<Text, DoubleWritable, NullWritable, DoubleWritable> {
@Override
public void compute(
ComputeContext<Text, DoubleWritable, NullWritable, DoubleWritable> context,
Iterable<DoubleWritable> messages) throws IOException {
if (context.getSuperstep() == 0) {
// Superstep 0: initialize every vertex to 1 / TotalNumVertices
setValue(new DoubleWritable(1.0 / context.getTotalNumVertices()));
} else if (context.getSuperstep() >= 1) {
// Superstep >= 1: sum incoming votes and apply the damping formula
double sum = 0;
for (DoubleWritable msg : messages) {
sum += msg.get();
}
DoubleWritable vertexValue = new DoubleWritable(
(0.15f / context.getTotalNumVertices()) + 0.85f * sum);
setValue(vertexValue);
}
// Send this vertex's share of its score to each neighbor
if (hasEdges()) {
context.sendMessageToNeighbors(this, new DoubleWritable(getValue()
.get() / getEdges().size()));
}
}
@Override
public void cleanup(
WorkerContext<Text, DoubleWritable, NullWritable, DoubleWritable> context)
throws IOException {
// Write the final vertex ID and PageRank score to the output table
context.write(getId(), getValue());
}
}
public static class PageRankVertexReader extends
GraphLoader<Text, DoubleWritable, NullWritable, DoubleWritable> {
@Override
public void load(
LongWritable recordNum,
WritableRecord record,
MutationContext<Text, DoubleWritable, NullWritable, DoubleWritable> context)
throws IOException {
// Each table row becomes one vertex.
// Column 0 is the source vertex; columns 1+ are destination vertices (edges).
PageRankVertex vertex = new PageRankVertex();
vertex.setValue(new DoubleWritable(0));
vertex.setId((Text) record.get(0));
System.out.println(record.get(0));
for (int i = 1; i < record.size(); i++) {
Writable edge = record.get(i);
System.out.println(edge.toString());
if (!( edge.equals(NullWritable.get()))) {
vertex.addEdge(new Text(edge.toString()), NullWritable.get());
}
}
LOG.info("vertex edgs size: "
+ (vertex.hasEdges() ? vertex.getEdges().size() : 0));
context.addVertexRequest(vertex);
}
}
private static void printUsage() {
System.out.println("Usage: <in> <out> [Max iterations (default 30)]");
System.exit(-1);
}
public static void main(String[] args) throws IOException {
if (args.length < 2)
printUsage();
GraphJob job = new GraphJob();
job.setGraphLoaderClass(PageRankVertexReader.class);
job.setVertexClass(PageRankVertex.class);
job.addInput(TableInfo.builder().tableName(args[0]).build());
job.addOutput(TableInfo.builder().tableName(args[1]).build());
// Default max iteration is 30; override with a third argument.
job.setMaxIteration(30);
if (args.length >= 3)
job.setMaxIteration(Integer.parseInt(args[2]));
long startTime = System.currentTimeMillis();
job.run();
System.out.println("Job Finished in "
+ (System.currentTimeMillis() - startTime) / 1000.0 + " seconds");
}
}
Étapes suivantes
Débogage local — testez les tâches Graph localement avant de les soumettre au cluster
Écrire une tâche Graph — configurez l'environnement de développement complet MaxCompute Graph