• Aucun résultat trouvé

4.3 Génération de candidats

4.3.3 Algorithme de génération des candidats

La génération des candidats repose sur deux étapes importantes. La première est la génération de candidats sommets, qui se fait à partir des correspondances générées par les outils d’alignement. L’idée de la génération de ces candidats sommets est de trouver les composantes connexes dans le graphe multiparti SA composé des N KB alignées. Une condition particulière est qu’un candidat ne peut contenir plusieurs éléments ontologiques provenant de la même source. Nous allons chercher uniquement les candidats incluant un maximum d’éléments ontologiques. Si un candidat est inclus dans un autre, alors nous ne garderons que le candidat de taille supérieure. C’est ce que nous appellerons les candidats maximaux.

La génération des candidats sommets à partir du graphe SA revient à effectuer une recherche des composantes connexes dans un graphe multiparti. En effet, chaque élément ontologique d’un candidat doit être lié à tous les autres par une chaîne de correspondances pour faire partie du même candidat. Pour trouver les composantes connexes dans un graphe non-orienté, l’algorithme le plus simple mais aussi le plus efficace est la recherche en profondeur. Cet algorithme se fonde sur un parcours de graphe en explorant le voisinage des sommets étudiés. Il est dit "en profondeur" puisqu’il privilégie d’abord le voisinage du premier voisin étudié avant d’étudier le second voisin.

Figure 28 – Exemple de graphe non orienté multi-partite

Nous pouvons prendre l’exemple présenté dans la figure28. Cette figure présente un exemple de graphe multiparti non-orienté. Il contient 3 parties ("S1", "S2", "S3"), chacune contenant 2 ou 3 sommets. Les traits entre les sommets représentent les arêtes du graphe multiparti. Si nous appliquons un algorithme de recherche en profondeur en partant du sommet "x11", nous commencerons par récupérer tous ses voisins, ici "x21", "x22" et "x31". En considérant le premier voisin ("x21"), nous parcourons d’abord les voisins de "x21" avant de parcourir les autres voisins de "x11". Chaque voisin parcouru de cette manière est ajouté à la composante connexe. Nous avons une contrainte supplémentaire qui s’ajoute à notre problème qui est qu’un candidat ne peut pas contenir plus d’un élément par source. Il faut alors vérifier dans notre algorithme qu’une composante connexe n’est pas de taille supérieure au nombre de parties et qu’il n’y a pas deux éléments de la même partie.

Les algorithmes classiques de parcours en profondeur utilisent un système de marquage des sommets pour éviter les boucles infinies lors de l’apparition de cycles dans le graphe. Ce problème ne se pose pas ici puisque la condition d’un sommet par partie permet d’éviter les cycles lors du parcours en profondeur. En effet, l’algorithme ne repassera jamais par un sommet déjà étudié puisque la composante connexe contient déjà un sommet de cette partie.

L’algorithme considère un candidat finalisé lorsque sa taille est égale au nombre de parties, ou qu’il n’y a pas de voisin des sommets de la composante connexe qui peut être ajouté. Cette dernière condition apparaît lorsque les voisins des sommets appartiennent à des parties qui ont déjà un élément dans la composante.

Nous pouvons faire l’analogie entre le graphe multiparti et le graphe SA présenté précédemment, chaque partie représentant une source considérée, les sommets étant des éléments ontologiques. Les arêtes du graphe sont des correspondances issues du système d’alignement.

L’algorithme2présente la boucle principale de l’algorithme de génération des candidats sommets. L’intérêt principal de cette boucle étant le parcours de tous les noeuds de SA et d’appliquer l’algorithme DFS80 sur tous les éléments qui ne sont pas encore impliqués dans un des candidats déjà générés (color(v) == null). Les éléments déjà impliqués dans

1 SA = Graph aligned SKB; 2 allCands = new List<Cand>(); 3 forAll(v in V_{sa})

4 {

5 if(color(v) == null)

6 {

7 Cand = new Cand(); 8 Cand.addElem(v);

9 allCands.addAll(DFS(Cand, allCands));

10 }

11 }

Algorithm 2: Algorithme de génération des candidats

au moins un candidat ont déjà été explorés pour la création de tous les candidats possibles. Nous cherchons ici à découvrir les éléments isolés. Pour chaque nouveau candidat ajouté à la liste finale, chaque élément ontologique du candidat en question est marqué par le candidat en question. De cette manière, il est possible de savoir pour chaque élément ontologique (ou nœud de SA) s’il est présent dans des candidats déjà générés et si oui lesquels.

L’algorithme 3présente le parcours en profondeur avec les conditions que nous avons présentées précédemment. La fonction "DFS" présentée ici prend en paramètres le candidat en cours de construction ("Cand") et l’ensemble des candidats générés ("allCands"). La première condition "Cand.size() < N", impose que la taille du candidat soit inférieure au nombre de sources considérées. Dans le cas contraire, le candidat est déjà maximal ; il est alors ajouté à la liste des candidats générés (ligne 24). Si la taille du candidat est inférieure au nombre de sources, alors nous récupérons tous les voisins du candidat (ligne 5). La liste des voisins d’un candidat est l’union des voisins de tous les éléments contenus dans ce candidat. Pour chaque voisin, nous vérifions si le candidat contient déjà un élément de cette source (ligne 9). Si ce n’est pas le cas, alors nous créons un nouveau candidat identique au candidat en cours de construction (ligne 12), auquel nous ajoutons le nouvel élément (ligne 13). Ce nouveau candidat va nous permettre de faire l’appel récursif à la fonction "DFS" (ligne 14). Si aucun voisin n’a été ajouté au candidat (ligne 17) alors le candidat est finalisé. Dans ce cas, il est ajouté à la liste des candidats finalisés (ligne 19).

La fonction "addCand" présentée entre la ligne 30 et 37 permet d’ajouter un candidat à la liste des candidats finalisés. Pour cela, nous regardons s’il n’existe pas déjà un candidat identitique dans la liste (ligne 32). Nous considérons deux candidats comme identiques s’ils contiennent les mêmes éléments. Cette condition permet d’éviter l’ajout de doublon. Si ce n’est pas le cas, alors nous l’ajoutons à la liste des candidats finalisés (ligne 34) et nous colorons les éléments contenus dans le candidat (ligne 35). Cette coloration permettra d’éviter d’effectuer le parcours en profondeur en partant de cet élément puisqu’il est déjà dans un candidat.

1 DFS(Cand, allCands) 2 { 3 if(Cand.size() < N) 4 { 5 nbs = getAllNeighboors(Cand); 6 hasNbSuitable = false; 7 for(nb in nbs) 8 {

9 if(!Cand.getElemForSource(nb.getSource())

10 {

11 hasNbSuitable = true;

12 newCand = Cand.clone();

13 newCand.addElem(nb);

14 allCands.addAll(DFS(newCand, allCands));

15 } 16 } 17 if(!hasNbSuitable) 18 { 19 addCand(Cand, allCands); 20 } 21 } 22 else 23 { 24 addCand(Cand, allCands); 25 26 } 27 return allCands; 28 } 29 30 addCand(Cand, allCands) 31 {

32 if(!allCands.contains(Cand))

33 {

34 allCands.add(Cand);

35 setColor(Cand);

36 }

37 }

Algorithm 3: Algorithme Depth First Search (parcours en profondeur)

Si nous appliquons cet algorithme à l’exemple présenté sur la figure28, nous pouvons définir la trace (non exhaustive) suivante :

Cand : {x11, x21}, voisins : {x12, x31, x22}

Cand : {x11, x21, x12} -> deux éléments de la même source Cand : {x11, x21, x31} -> nouveau candidat

Cand : {x11, x21, x22} -> deux éléments de la même source Cand : {x11, x22}, voisins : {x32, x21, x31}

Cand : {x11, x22, x32} -> nouveau candidat

Cand : {x11, x22, x21} -> deux éléments de la même source Cand : {x11, x22, x31} -> nouveau candidat

Cand : {x11, x31}, voisins : {x21, x22}

Cand : {x11, x31, x21} -> candidat déjà présent Cand : {x11, x31, x22} -> candidat déjà présent Cand : {x12}, voisins : {x21, x32}

Cand : {x12, x21}, voisins : {x11, x31, x32} Cand : {x12, x21, x31} -> nouveau candidat ...

Dans cet exemple, le premier nœud considéré est "x11". Nous parcourons alors tous ses voisins : "x21, x22, x31". Nous considérons alors "x21", le premier voisin découvert de "x11". L’ensemble des voisins du candidat en cours de construction "x11, x21" sont "x12, x31, x22". Si nous considérons l’ajout de l’élément "x12" dans le candidat en cours de construction, nous obtenons "x11, x21, x12". Les éléments "x11" et "x12" sont dans la même source ; ce candidat n’est donc pas possible. Le candidat suivant considéré est "x11, x21, x31" qui est valable ; un nouveau candidat est donc ajouté. De cette manière, tout le graphe est parcouru pour découvrir tous les candidats possibles. Nous pouvons observer que si un candidat est déjà présent, il n’est pas ajouté à la liste.

Une fois ces candidats sommets générés, il est possible de déterminer les candidats arcs liés à ces candidats sommets. Pour chaque couple de candidats sommets, si le même arc est présent dans au moins une source, un candidat arc est généré. Pour chaque relation présente pour un des éléments ontologiques d’un candidat, cette même relation est recherchée dans les autres sources pour les autres éléments ontologiques du même candidat. Ceci est réalisable facilement puisque nous savons que les relations utilisées sont étiquetées par des propriétés du module, donc forcément les mêmes URI. Si cette relation pointe vers un autre candidat, alors le candidat arc est défini entre les deux candidats sommets.

Un cas particulier est le candidat sommet label. Les systèmes d’alignement ne consi-dérant pas les labels comme étant des éléments ontologiques, ceux-ci ne génèrent pas de correspondances entre ces labels. Nous utilisons dans ce cas le même algorithme que celui défini pour les candidats sommets, à la différence que nous utilisons une fonction de similarité de chaînes de caractères pour déterminer les correspondances entre labels. Pour chaque label des éléments ontologiques d’un candidat sommet, nous regardons si celui-ci est similaire à un autre label d’un élément ontologique du candidat provenant d’une autre source. Pour calculer la similarité, nous utilisons la distance de Jaro-Winkler [Winkler, 1990,Winkler, 1999]. Nous considérons deux labels comme "identiques" si le résultat de la similarité de Jaro-Winkler est supérieur à 0,92. La définition de ce seuil

s’inspire de l’outil de recherche d’information Apache Lucene [McCandless et al., 2010]. Cet outil utilise la fonction de calcul de similarité et le même seuil. En considérant les labels comme les sommets et les liens entre labels identiques, nous pouvons utiliser le même algorithme que pour la génération de candidats sommets. Pour chaque candidat label généré, un candidat arc est créé entre le candidat sommet considéré et le candidat label généré. Nous ne considérons pas les candidats label comme étant des candidats sommets au même titre que les candidats sommets représentant des individus ou des classes. Dans la suite du processus, nous ne considérerons pas le candidat sommet label et le candidat arc associé comme distincts. En effet, nous posons une contrainte forte sur l’existence du candidat sommet label uniquement si le candidat arc associé est dans la base de connaissances finale. Il n’est donc pas possible de considérer le candidat sommet label indépendemment du candidat arc associé.