diff --git a/MyIA.AI.Notebooks/SymbolicAI/SemanticWeb/SW-11-CSharp-KnowledgeGraphs.ipynb b/MyIA.AI.Notebooks/SymbolicAI/SemanticWeb/SW-11-CSharp-KnowledgeGraphs.ipynb index 5e6bf1c5bb..9ee729a796 100644 --- a/MyIA.AI.Notebooks/SymbolicAI/SemanticWeb/SW-11-CSharp-KnowledgeGraphs.ipynb +++ b/MyIA.AI.Notebooks/SymbolicAI/SemanticWeb/SW-11-CSharp-KnowledgeGraphs.ipynb @@ -11,13 +11,15 @@ "\n", "Notebook C# / .NET Interactive sur les **Knowledge Graphs** (KG) dans dotNetRDF 3.4.1 : construction, interrogation SPARQL, parcours de graphe, centralite, qualite.\n", "\n", + "**Plan de lecture** : la ligne principale suit l'arc *construire, interroger, parcourir et visualiser, exemples, verdict*. Les approfondissements sont en annexes lettrées : **A** le KG en contexte (définition détaillée, comparaison au relationnel, usages industriels), **B** les patterns SPARQL et leur sémantique d'évaluation, **C** les implémentations C# des parcours (adjacence, BFS, complexité, algorithmes voisins), **D** la qualité d'un KG, **E** la centralité. Rien n'a été supprimé : chaque section déplacée garde son contenu en annexe.\n", + "\n", "**Objectifs de la seance** :\n", "1. Definir un KG : graphe RDF avec une ontologie (classes, proprietes) et des instances.\n", "2. Construire un KG a partir de donnees structurees (CSV/table) en transformant chaque ligne en plusieurs triplets.\n", "3. Interroger le KG avec SPARQL (projections, filtres, agregats).\n", "4. Parcourir le KG par adjacence (BFS) et visualiser en ASCII.\n", - "5. Valider la qualite du KG : classes orphelines, completude, distribution des valeurs.\n", - "6. Calculer des metriques de centralite (degre) pour identifier les acteurs cles.\n", + "5. Valider la qualite du KG : classes orphelines, completude, distribution des valeurs (annexe D).\n", + "6. Calculer des metriques de centralite (degre) pour identifier les acteurs cles (annexe E).\n", "\n", "**Pourquoi ce notebook dans la serie SemanticWeb** :\n", "- C'est le notebook qui **integre** tous les precedents (SW-3 lecture/ecriture, SW-4 SPARQL, SW-5 Linked Data).\n", @@ -56,13 +58,128 @@ "id": "05ccae9b", "metadata": { "execution": { - "iopub.execute_input": "2026-07-07T19:21:20.529871Z", - "iopub.status.busy": "2026-07-07T19:21:20.529871Z", - "iopub.status.idle": "2026-07-07T19:21:21.968483Z", - "shell.execute_reply": "2026-07-07T19:21:21.949176Z" + "iopub.execute_input": "2026-10-01T19:05:27.942881Z", + "iopub.status.busy": "2026-10-01T19:05:27.934410Z", + "iopub.status.idle": "2026-10-01T19:05:31.411742Z", + "shell.execute_reply": "2026-10-01T19:05:31.409008Z" } }, "outputs": [ + { + "data": { + "text/html": [ + "\r\n", + "
\r\n", + " \r\n", + " \r\n", + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, { "data": { "text/html": [ @@ -117,19 +234,7 @@ "\n", "**Exemple canonique** : Google Knowledge Graph (2012, 3 milliards de faits), Wikidata (100+ millions d'entites), DBpedia (15+ milliards de triplets).\n", "\n", - "**Pourquoi les KG sont importants** :\n", - "- **Recherche semantique** : Google Search utilise un KG pour les panneaux de connaissance.\n", - "- **Recommandation** : Netflix, Spotify utilisent des KG pour suggerer du contenu.\n", - "- **Question-answering** : les assistants vocaux (Siri, Alexa) utilisent des KG.\n", - "\n", - "**Comparaison avec une base relationnelle** :\n", - "- Relationnelle : tables, JOIN, contraintes d'integrite.\n", - "- KG : triplets, inferecence, evolution de schema sans migration.\n", - "\n", - "**Cas d'usage industriel** :\n", - "- **Santé** : KG medical (SNOMED, ICD-10) pour le diagnostic.\n", - "- **Finance** : KG de fraude (relations entre entities).\n", - "- **E-commerce** : KG produit pour les recommandations." + "La definition complete -- pourquoi les KG comptent, la comparaison avec une base relationnelle, les cas d'usage industriels -- est en **annexe A**." ] }, { @@ -151,22 +256,7 @@ "\n", "**Sortie observee de code[1]** (verbatim) : `Vocabulaire cinema defini.`. La cellule cree le graphe et declare les classes/proprietes via `g.CreateUriNode(\"ex:Film\")`.\n", "\n", - "**Implementation C# (creation d'ontologie)** :\n", - "```csharp\n", - "var g = new Graph();\n", - "var ns = g.NamespaceMap;AddNamespace(\"ex\", new Uri(\"http://example.org/\"));\n", - "\n", - "var filmClass = g.CreateUriNode(\"ex:Film\");\n", - "var directorClass = g.CreateUriNode(\"ex:Director\");\n", - "var genreClass = g.CreateUriNode(\"ex:Genre\");\n", - "\n", - "var directedBy = g.CreateUriNode(\"ex:directedBy\");\n", - "var releasedIn = g.CreateUriNode(\"ex:releasedIn\");\n", - "var hasGenre = g.CreateUriNode(\"ex:hasGenre\");\n", - "var title = g.CreateUriNode(\"ex:title\");\n", - "```\n", - "\n", - "**Note de portee** : la definition de l'ontologie est faite **dans le graphe lui-meme** (les classes sont des URIs comme les instances). C'est l'auto-description du Web semantique." + "**Note de portee** : la definition de l'ontologie est faite **dans le graphe lui-meme** (les classes sont des URIs comme les instances). C'est l'auto-description du Web semantique. L'extrait C# correspondant est en **annexe A**." ] }, { @@ -175,10 +265,10 @@ "id": "50bbd2f2", "metadata": { "execution": { - "iopub.execute_input": "2026-07-07T19:21:21.969493Z", - "iopub.status.busy": "2026-07-07T19:21:21.969493Z", - "iopub.status.idle": "2026-07-07T19:21:22.092625Z", - "shell.execute_reply": "2026-07-07T19:21:22.092625Z" + "iopub.execute_input": "2026-10-01T19:05:31.413567Z", + "iopub.status.busy": "2026-10-01T19:05:31.413311Z", + "iopub.status.idle": "2026-10-01T19:05:31.561012Z", + "shell.execute_reply": "2026-10-01T19:05:31.560765Z" } }, "outputs": [ @@ -246,10 +336,10 @@ "id": "8a80d072", "metadata": { "execution": { - "iopub.execute_input": "2026-07-07T19:21:22.092625Z", - "iopub.status.busy": "2026-07-07T19:21:22.092625Z", - "iopub.status.idle": "2026-07-07T19:21:22.201107Z", - "shell.execute_reply": "2026-07-07T19:21:22.201107Z" + "iopub.execute_input": "2026-10-01T19:05:31.562520Z", + "iopub.status.busy": "2026-10-01T19:05:31.562268Z", + "iopub.status.idle": "2026-10-01T19:05:31.722753Z", + "shell.execute_reply": "2026-10-01T19:05:31.722478Z" } }, "outputs": [ @@ -342,10 +432,10 @@ "id": "1a760825", "metadata": { "execution": { - "iopub.execute_input": "2026-07-07T19:21:22.201107Z", - "iopub.status.busy": "2026-07-07T19:21:22.201107Z", - "iopub.status.idle": "2026-07-07T19:21:22.296743Z", - "shell.execute_reply": "2026-07-07T19:21:22.296743Z" + "iopub.execute_input": "2026-10-01T19:05:31.724719Z", + "iopub.status.busy": "2026-10-01T19:05:31.724447Z", + "iopub.status.idle": "2026-10-01T19:05:31.831285Z", + "shell.execute_reply": "2026-10-01T19:05:31.830859Z" } }, "outputs": [ @@ -405,47 +495,7 @@ "source": [ "## 3. Interrogation SPARQL\n", "\n", - "SPARQL est le langage de requete standard pour les KG. Le notebook utilise les memes APIs que SW-4 (SparqlQueryParser, LeviathanQueryProcessor), mais appliquees a un KG local.\n", - "\n", - "**Sortie observee de code[4]** (verbatim) :\n", - "```\n", - "Films de Nolan (ordre annee desc) : 3\n", - " Dunkirk (2017)\n", - " Interstellar (2014)\n", - " Inception (2010)\n", - "```\n", - "\n", - "La requete cherche les films realises par Nolan, tries par annee decroissante. La sortie inclut le titre et l'annee de chaque film.\n", - "\n", - "**Pattern de la requete** :\n", - "```sparql\n", - "PREFIX ex: \n", - "SELECT ?film ?title ?year WHERE {\n", - " ?film a ex:Film .\n", - " ?film ex:directedBy ex:Nolan .\n", - " ?film ex:title ?title .\n", - " ?film ex:releasedIn ?year .\n", - "} ORDER BY DESC(?year)\n", - "```\n", - "\n", - "**Sortie observee de code[5]** (verbatim) :\n", - "```\n", - "Nombre de films par genre :\n", - " Drama : 6\n", - " SciFi : 3\n", - " Action : 2\n", - "```\n", - "\n", - "La requete utilise `COUNT` et `GROUP BY` pour compter les films par genre.\n", - "\n", - "**Pattern** :\n", - "```sparql\n", - "SELECT ?genre (COUNT(?film) AS ?count) WHERE {\n", - " ?film a ex:Film .\n", - " ?film ex:hasGenre ?genre .\n", - "} GROUP BY ?genre\n", - "ORDER BY DESC(?count)\n", - "```" + "SPARQL est le langage de requete standard pour les KG. Le notebook utilise les memes APIs que SW-4 (SparqlQueryParser, LeviathanQueryProcessor), mais appliquees a un KG local. Deux requetes d'abord : la filmographie d'un realisateur (3.1), puis un agregat `COUNT`/`GROUP BY` (3.2). Les patterns complets, les agregats disponibles et l'ordre d'evaluation du moteur sont en **annexe B**." ] }, { @@ -463,15 +513,7 @@ "- L'utilisateur veut generalement les films recents d'abord.\n", "- DESC = decroissant (plus recent en premier).\n", "\n", - "**Cas d'usage** :\n", - "- **Site web** : filmographie d'un realisateur.\n", - "- **Recommandation** : \"films similaires\" (memes realisateurs).\n", - "- **Analyse** : etude de la carriere d'un realisateur (evolution des genres, etc.).\n", - "\n", - "**Performance** :\n", - "- **Filtre par realisateur** : selectif (1 realisateur = ~3 films).\n", - "- **Tri par annee** : lineaire (8 films = 8 comparaisons).\n", - "- **Total** : O(N log N) avec un index sur `directedBy`." + "Les cas d'usage (site web, recommandation, analyse de carriere) et l'analyse de performance sont en **annexe B**." ] }, { @@ -480,10 +522,10 @@ "id": "8d4f23d7", "metadata": { "execution": { - "iopub.execute_input": "2026-07-07T19:21:22.298741Z", - "iopub.status.busy": "2026-07-07T19:21:22.297742Z", - "iopub.status.idle": "2026-07-07T19:21:22.420500Z", - "shell.execute_reply": "2026-07-07T19:21:22.419216Z" + "iopub.execute_input": "2026-10-01T19:05:31.833835Z", + "iopub.status.busy": "2026-10-01T19:05:31.833437Z", + "iopub.status.idle": "2026-10-01T19:05:32.039739Z", + "shell.execute_reply": "2026-10-01T19:05:32.039486Z" } }, "outputs": [ @@ -551,21 +593,7 @@ "- `GROUP BY ?genre` : on groupe par genre.\n", "- `ORDER BY DESC(?count)` : on trie par popularite.\n", "\n", - "**Agregats disponibles en SPARQL** :\n", - "- **COUNT** : nombre de valeurs.\n", - "- **SUM** : somme.\n", - "- **AVG** : moyenne.\n", - "- **MIN/MAX** : min/max.\n", - "- **GROUP_CONCAT** : concatenation avec separateur.\n", - "- **SAMPLE** : un exemple de valeur.\n", - "\n", - "**Note de portee** : les agregats sont evalues APRES les filtres. L'ordre est :\n", - "1. WHERE (filtrage des triplets).\n", - "2. GROUP BY (partitionnement).\n", - "3. Agregats (calcul).\n", - "4. HAVING (filtre post-agregat, optionnel).\n", - "5. ORDER BY (tri final).\n", - "6. SELECT (projection)." + "Les agregats disponibles en SPARQL et l'ordre d'evaluation (WHERE, GROUP BY, agregats, HAVING, ORDER BY, SELECT) sont en **annexe B**." ] }, { @@ -574,10 +602,10 @@ "id": "456d463a", "metadata": { "execution": { - "iopub.execute_input": "2026-07-07T19:21:22.420500Z", - "iopub.status.busy": "2026-07-07T19:21:22.420500Z", - "iopub.status.idle": "2026-07-07T19:21:22.467674Z", - "shell.execute_reply": "2026-07-07T19:21:22.467674Z" + "iopub.execute_input": "2026-10-01T19:05:32.041478Z", + "iopub.status.busy": "2026-10-01T19:05:32.041231Z", + "iopub.status.idle": "2026-10-01T19:05:32.106729Z", + "shell.execute_reply": "2026-10-01T19:05:32.106508Z" } }, "outputs": [ @@ -682,49 +710,12 @@ "\n", "**Liste d'adjacence** : pour chaque noeud, on stocke ses voisins sortants. C'est une structure de donnees classique pour les graphes.\n", "\n", - "**Implementation C# (adjacence)** :\n", - "```csharp\n", - "var adj = new Dictionary>();\n", - "foreach (var t in g.Triples) {\n", - " if (!adj.ContainsKey(t.Subject)) adj[t.Subject] = new List();\n", - " adj[t.Subject].Add(t.Object);\n", - "}\n", - "```\n", - "\n", "**BFS (Breadth-First Search)** : on explore niveau par niveau.\n", "- Profondeur 1 : voisins directs.\n", "- Profondeur 2 : voisins des voisins.\n", "- Profondeur N : N-1 sauts.\n", "\n", - "**Implementation C# (BFS)** :\n", - "```csharp\n", - "static List Bfs(Dictionary> adj, INode start, int maxDepth) {\n", - " var visited = new HashSet{ start };\n", - " var frontier = new Queue<(INode n, int d)>();\n", - " frontier.Enqueue((start, 0));\n", - " var order = new List{ start };\n", - " while (frontier.Count > 0) {\n", - " var (cur, d) = frontier.Dequeue();\n", - " if (d >= maxDepth) continue;\n", - " if (!adj.ContainsKey(cur)) continue;\n", - " foreach (var nb in adj[cur])\n", - " if (visited.Add(nb)) { order.Add(nb); frontier.Enqueue((nb, d+1)); }\n", - " }\n", - " return order;\n", - "}\n", - "```\n", - "\n", - "**Complexite** :\n", - "- **Adjacence** : O(V + E) pour la construction.\n", - "- **BFS** : O(V + E) pour l'exploration.\n", - "- **Memoire** : O(V) pour la liste d'adjacence.\n", - "\n", - "**Cas d'usage** :\n", - "- **Recommendation** : \"films similaires\" (2-hop depuis un film).\n", - "- **Detection de communautes** : composantes connexes.\n", - "- **Plus court chemin** : Dijkstra, A-B (cf. notebook Search/Part1).\n", - "- **PageRank** : variante de BFS avec poids (importance).\n", - "- **A*** : BFS + heuristique pour les graphes ponderes." + "Les implémentations C# complètes (adjacence et BFS), l'analyse de complexité et les algorithmes voisins (PageRank, A*) sont en **annexe C**." ] }, { @@ -733,10 +724,10 @@ "id": "44512542", "metadata": { "execution": { - "iopub.execute_input": "2026-07-07T19:21:22.467674Z", - "iopub.status.busy": "2026-07-07T19:21:22.467674Z", - "iopub.status.idle": "2026-07-07T19:21:22.552197Z", - "shell.execute_reply": "2026-07-07T19:21:22.552197Z" + "iopub.execute_input": "2026-10-01T19:05:32.108231Z", + "iopub.status.busy": "2026-10-01T19:05:32.108010Z", + "iopub.status.idle": "2026-10-01T19:05:32.249906Z", + "shell.execute_reply": "2026-10-01T19:05:32.249653Z" } }, "outputs": [ @@ -808,15 +799,7 @@ " Inception --releasedIn--> 2010\n", "```\n", "\n", - "**Implementation C# (ASCII viz)** :\n", - "```csharp\n", - "static string Short(INode n) => n is IUriNode u ? u.Uri.Fragment.TrimStart('#') : n.ToString();\n", - "\n", - "var sub = BFS(g, inceptionNode, 2);\n", - "foreach (var t in g.Triples.Where(t => sub.Contains(t.Subject) && sub.Contains(t.Object))) {\n", - " Console.WriteLine($\" {Short(t.Subject)} --{Short(t.Predicate)}--> {Short(t.Object)}\");\n", - "}\n", - "```\n", + "L'extrait C# de la visualisation est en **annexe C**.\n", "\n", "**Pourquoi la visualisation ASCII** :\n", "- **Portable** : pas de dependance graphique.\n", @@ -837,10 +820,10 @@ "id": "8fa4126e", "metadata": { "execution": { - "iopub.execute_input": "2026-07-07T19:21:22.553082Z", - "iopub.status.busy": "2026-07-07T19:21:22.553082Z", - "iopub.status.idle": "2026-07-07T19:21:22.683878Z", - "shell.execute_reply": "2026-07-07T19:21:22.683878Z" + "iopub.execute_input": "2026-10-01T19:05:32.251846Z", + "iopub.status.busy": "2026-10-01T19:05:32.251564Z", + "iopub.status.idle": "2026-10-01T19:05:32.427427Z", + "shell.execute_reply": "2026-10-01T19:05:32.427046Z" } }, "outputs": [ @@ -911,70 +894,35 @@ }, { "cell_type": "markdown", - "id": "02f2688a", + "id": "10de7768", "metadata": {}, "source": [ - "## 5. Qualite et validation d'un Knowledge Graph\n", - "\n", - "Un KG peut avoir des problemes de qualite :\n", - "- **Classes orphelines** : declarees mais jamais utilisees.\n", - "- **Donnees incompletes** : entites sans certaines proprietes obligatoires.\n", - "- **Distribution biaisee** : valeurs manquantes ou aberrantes.\n", - "\n", - "**Sortie observee de code[8]** (verbatim) :\n", - "```\n", - "Classes declarees : 3, orphelines : 3\n", - "Films complets (directeur+annee+genre) : 8/8\n", - "Annees : min=1994, max=2019, mediane=2012\n", - "```\n", - "\n", - "**3 metriques implementees** :\n", - "1. **Classes orphelines** : nombre de classes declarees mais sans instance. Ici, 3/3 (les classes Film, Director, Genre ont des instances -- donc 0 orphelines au sens strict ; mais la metrique retourne le total declare comme proxy).\n", - "2. **Completude** : ratio des films ayant directeur + annee + genre. Ici 8/8 = 100%.\n", - "3. **Distribution des valeurs** : min/max/median des annees. Ici [1994, 2019] mediane 2012.\n", + "## 5. Exemples guides\n", "\n", - "**Implementation C#** (extrait verbatim de la cellule 5.) :\n", - "```csharp\n", - "// 5.1 Classes declarees mais jamais instanciees (orphelines au sens large)\n", - "var classes = g.Triples.Where(t => t.Predicate.Equals(RDF_TYPE) && t.Object.Equals(U(g,\"ex:Class\")))\n", - " .Select(t=>t.Subject).ToList();\n", - "var orphans = classes.Where(c => !g.Triples.Any(t => t.Subject.Equals(c) && !t.Predicate.Equals(RDF_TYPE))).ToList();\n", - "Console.WriteLine($\"Classes declarees : {classes.Count}, orphelines : {orphans.Count}\");\n", + "Cette section contient 2 exemples resolus :\n", + "1. **Exemple guide 1** : paires de realisateurs partageant des genres.\n", + "2. **Exemple guide 2** : filmographie de Bong avec genres agreges.\n", "\n", - "// 5.2 Completude : chaque Film a directeur + annee + au moins 1 genre ?\n", - "var allFilms = g.Triples.Where(t => t.Predicate.Equals(RDF_TYPE) && t.Object.Equals(U(g,\"ex:Film\")))\n", - " .Select(t=>t.Subject).ToList();\n", - "int complete = 0;\n", - "foreach (var f in allFilms) {\n", - " bool hasDir = g.Triples.Any(t => t.Subject.Equals(f) && t.Predicate.Equals(DIRECTED_BY));\n", - " bool hasYear = g.Triples.Any(t => t.Subject.Equals(f) && t.Predicate.Equals(RELEASED));\n", - " bool hasGenre = g.Triples.Any(t => t.Subject.Equals(f) && t.Predicate.Equals(HAS_GENRE));\n", - " if (hasDir && hasYear && hasGenre) complete++;\n", - "}\n", - "Console.WriteLine($\"Films complets (directeur+annee+genre) : {complete}/{allFilms.Count}\");\n", + "**Note pedagogique** : les exemples montrent des requetes SPARQL avancees (self-join, GROUP_CONCAT) avec sorties concretes.\n", "\n", - "// 5.3 Distribution des annees\n", - "var years = g.Triples.Where(t => t.Predicate.Equals(RELEASED))\n", - " .Select(t => int.Parse(((ILiteralNode)t.Object).Value)).OrderBy(y=>y).ToList();\n", - "Console.WriteLine($\"Annees : min={years.First()}, max={years.Last()}, mediane={years[years.Count/2]}\");\n", - "```\n", + "**Suivi de 3 exercices** :\n", + "1. **Exercice 1** : Films d'une annee donnee.\n", + "2. **Exercice 2** : Degre entrant d'un genre.\n", + "3. **Exercice 3** : Chemin le plus court entre deux entites.\n", "\n", - "**Pourquoi la qualite est importante** :\n", - "- **Confiance** : un KG avec beaucoup d'orphelines est suspect.\n", - "- **Inference** : les classes orphelines ne peuvent pas etre utilisees pour l'inference.\n", - "- **Maintenance** : detecter les problemes tot (avant qu'ils ne se propagent)." + "**Difficulte progressive** : facile -> moyenne -> moyenne-avancee." ] }, { "cell_type": "code", "execution_count": 9, - "id": "16a0821c", + "id": "8804a63b", "metadata": { "execution": { - "iopub.execute_input": "2026-07-07T19:21:22.683878Z", - "iopub.status.busy": "2026-07-07T19:21:22.683878Z", - "iopub.status.idle": "2026-07-07T19:21:22.778190Z", - "shell.execute_reply": "2026-07-07T19:21:22.778190Z" + "iopub.execute_input": "2026-10-01T19:05:32.429231Z", + "iopub.status.busy": "2026-10-01T19:05:32.428980Z", + "iopub.status.idle": "2026-10-01T19:05:32.508224Z", + "shell.execute_reply": "2026-10-01T19:05:32.507750Z" } }, "outputs": [ @@ -982,280 +930,56 @@ "name": "stdout", "output_type": "stream", "text": [ - "Classes declarees : 3, orphelines : 3\r\n" + "Paires de realisateurs partageant des genres :\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "Films complets (directeur+annee+genre) : 8/8\r\n" + " Cameron & Nolan : 2 genre(s) commun(s)\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "Annees : min=1994, max=2019, mediane=2012\r\n" + " Bong & Tarantino : 2 genre(s) commun(s)\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Nolan & Tarantino : 1 genre(s) commun(s)\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Bong & Nolan : 1 genre(s) commun(s)\r\n" ] } ], "source": [ - "// 5.1 Classes declarees mais jamais instanciees (orphelines au sens large)\n", - "var classes = g.Triples.Where(t => t.Predicate.Equals(RDF_TYPE) && t.Object.Equals(U(g,\"ex:Class\")))\n", - " .Select(t=>t.Subject).ToList();\n", - "var orphans = classes.Where(c => !g.Triples.Any(t => t.Subject.Equals(c) && !t.Predicate.Equals(RDF_TYPE))).ToList();\n", - "Console.WriteLine($\"Classes declarees : {classes.Count}, orphelines : {orphans.Count}\");\n", - "\n", - "// 5.2 Completude : chaque Film a directeur + annee + au moins 1 genre ?\n", - "var allFilms = g.Triples.Where(t => t.Predicate.Equals(RDF_TYPE) && t.Object.Equals(U(g,\"ex:Film\")))\n", - " .Select(t=>t.Subject).ToList();\n", - "int complete = 0;\n", - "foreach (var f in allFilms) {\n", - " bool hasDir = g.Triples.Any(t => t.Subject.Equals(f) && t.Predicate.Equals(DIRECTED_BY));\n", - " bool hasYear = g.Triples.Any(t => t.Subject.Equals(f) && t.Predicate.Equals(RELEASED));\n", - " bool hasGenre = g.Triples.Any(t => t.Subject.Equals(f) && t.Predicate.Equals(HAS_GENRE));\n", - " if (hasDir && hasYear && hasGenre) complete++;\n", - "}\n", - "Console.WriteLine($\"Films complets (directeur+annee+genre) : {complete}/{allFilms.Count}\");\n", - "\n", - "// 5.3 Distribution des annees\n", - "var years = g.Triples.Where(t => t.Predicate.Equals(RELEASED))\n", - " .Select(t => int.Parse(((ILiteralNode)t.Object).Value)).OrderBy(y=>y).ToList();\n", - "Console.WriteLine($\"Annees : min={years.First()}, max={years.Last()}, mediane={years[years.Count/2]}\");" + "// Exemple guide 1 : paires de realisateurs partageant des genres\n", + "string q3 = @\"\n", + "PREFIX ex: \n", + "SELECT ?d1 ?d2 (COUNT(DISTINCT ?g) AS ?shared) WHERE {\n", + " ?f1 ex:directedBy ?d1 ; ex:hasGenre ?g .\n", + " ?f2 ex:directedBy ?d2 ; ex:hasGenre ?g .\n", + " FILTER(STR(?d1) < STR(?d2))\n", + "} GROUP BY ?d1 ?d2 ORDER BY DESC(?shared)\";\n", + "var res3 = (SparqlResultSet)g.ExecuteQuery(q3);\n", + "Console.WriteLine(\"Paires de realisateurs partageant des genres :\");\n", + "foreach (SparqlResult r in res3)\n", + " Console.WriteLine($\" {Short(r[\"d1\"])} & {Short(r[\"d2\"])} : {((ILiteralNode)r[\"shared\"]).Value} genre(s) commun(s)\");" ] }, { "cell_type": "markdown", - "id": "8e097143", - "metadata": {}, - "source": [ - "**Interprétation** : les 3 métriques de qualité valident que le KG est bien formé. (1) **Complétude** : `complete == allFilms.Count` (8/8) confirme qu'aucun film n'a de champ manquant (directeur, année, genre). (2) **Cohérence du range** : les années s'étalent de 1994 à 2019, médiane 2012 — une plage réaliste pour un corpus de cinéma moderne. (3) **Orphelins** : les 3 classes « orphelines » détectées (`Director`, `Film`, `Genre`) sont les **méta-types** eux-mêmes — elles n'apparaissent comme sujet que de leur propre `rdf:type ex:Class`, ce qui est **attendu** (ce sont les types, pas des instances) et non un défaut de données. Ces vérifications sont l'équivalent C# du bloc « Qualité » du twin Python." - ] - }, - { - "cell_type": "markdown", - "id": "48f5fd4d", - "metadata": {}, - "source": [ - "## 6. Centralite : degre des realisateurs\n", - "\n", - "La **centralite de degre** mesure combien de connexions sortantes (ou entrantes) un noeud a. C'est la metrique la plus simple mais utile pour identifier les acteurs cles.\n", - "\n", - "**Sortie observee de code[9]** (verbatim) :\n", - "```\n", - "Classement des realisateurs par productivite (nb films) :\n", - " 1. Nolan : 3 films\n", - " 2. Bong : 2 films\n", - " 3. Tarantino : 2 films\n", - "```\n", - "\n", - "**Implementation C# (degre)** :\n", - "```csharp\n", - "var deg = new Dictionary();\n", - "foreach (var t in g.Triples.Where(t => t.Predicate.Equals(directedByProp))) {\n", - " var directorName = Short(t.Object);\n", - " if (!deg.ContainsKey(directorName)) deg[directorName] = 0;\n", - " deg[directorName]++;\n", - "}\n", - "var ranking = deg.OrderByDescending(kv => kv.Value);\n", - "```\n", - "\n", - "**Types de centralite** :\n", - "- **Degre** : nombre de voisins. Simple et rapide (O(V + E)).\n", - "- **Betweenness** : nombre de plus courts chemins passant par le noeud. O(VE).\n", - "- **Closeness** : inverse de la somme des distances. O(V log V).\n", - "- **Eigenvector** : importance basee sur l'importance des voisins (PageRank). O(V + E) iteratif.\n", - "\n", - "**Cas d'usage** :\n", - "- **Recommandation** : \"qui est similaire a X\" (meme centralite).\n", - "- **Detection d'influence** : les noeuds a haute centralite sont des leaders d'opinion.\n", - "- **Analyse de reseau social** : influenceurs, communautes.\n", - "\n", - "**Note de portee** : la centralite de degre est biaisee par la taille du voisinage. Pour des graphes heterogenes (certains noeuds ont beaucoup de voisins, d'autres peu), preferer PageRank." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "6c2384d5", - "metadata": { - "execution": { - "iopub.execute_input": "2026-07-07T19:21:22.778190Z", - "iopub.status.busy": "2026-07-07T19:21:22.778190Z", - "iopub.status.idle": "2026-07-07T19:21:22.844342Z", - "shell.execute_reply": "2026-07-07T19:21:22.844342Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Classement des realisateurs par productivite (nb films) :\r\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " 1. Nolan : 3 films\r\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " 2. Bong : 2 films\r\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " 3. Tarantino : 2 films\r\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " 4. Cameron : 1 films\r\n" - ] - } - ], - "source": [ - "var deg = new Dictionary();\n", - "foreach (var t in g.Triples.Where(t => t.Predicate.Equals(DIRECTED_BY))) {\n", - " var dir = Short(t.Object);\n", - " deg[dir] = deg.GetValueOrDefault(dir, 0) + 1;\n", - "}\n", - "var ranked = deg.OrderByDescending(kv => kv.Value).ThenBy(kv => kv.Key).ToList();\n", - "Console.WriteLine(\"Classement des realisateurs par productivite (nb films) :\");\n", - "for (int i = 0; i < ranked.Count; i++)\n", - " Console.WriteLine($\" {i+1}. {ranked[i].Key} : {ranked[i].Value} films\");" - ] - }, - { - "cell_type": "markdown", - "id": "8bfb7394", - "metadata": {}, - "source": [ - "**Interprétation** : le degré sortant (`directedBy` inversé) classe les réalisateurs par productivité dans le KG. Nolan mène (3 films), suivi de Bong et Tarantino (2 chacun), puis Cameron (1). C'est le **degree centrality** — la métrique de centralité la plus directe. Le PageRank du twin Python raffine en pondérant par l'importance des voisins, mais le classement brut par degré est déjà une excellente approximation sur un petit graphe." - ] - }, - { - "cell_type": "markdown", - "id": "5cb2179e", - "metadata": {}, - "source": [ - "## 7. Verdict SOTA -- ce qui est couvert et ce qui ne l'est pas\n", - "\n", - "Le KG cinema illustre les concepts fondamentaux. Comparons aux standards de l'industrie :\n", - "\n", - "**Couvert dans le notebook** :\n", - "- **Construction** : ontologie + instances depuis table.\n", - "- **Interrogation** : SPARQL (projections, filtres, agregats).\n", - "- **Parcours** : BFS, adjacence, visualisation ASCII.\n", - "- **Qualite** : classes orphelines, completude, distribution.\n", - "- **Centralite** : degre sortant (productivite realisateurs).\n", - "\n", - "**Pas couvert** (pour aller plus loin) :\n", - "- **Inference OWL** : subsomption (subClassOf), equivalence (owl:equivalentClass), transivite (owl:TransitiveProperty). Cf. SW-7.\n", - "- **Validation SHACL** : contraintes formelles (Cardinality, Datatype, Pattern). Cf. SW-8.\n", - "- **Embeddings** : representation vectorielle des entites (TransE, RDF2Vec). Cf. SW-13.\n", - "- **Reasoners** : inference automatisee (Pellet, ELK). Cf. SW-13.\n", - "- **Linked Data** : integration avec DBpedia/Wikidata (cf. SW-5)." - ] - }, - { - "cell_type": "markdown", - "id": "10de7768", - "metadata": {}, - "source": [ - "## Exemples guides\n", - "\n", - "Cette section contient 2 exemples resolus :\n", - "1. **Exemple guide 1** : paires de realisateurs partageant des genres.\n", - "2. **Exemple guide 2** : filmographie de Bong avec genres agreges.\n", - "\n", - "**Note pedagogique** : les exemples montrent des requetes SPARQL avancees (self-join, GROUP_CONCAT) avec sorties concretes.\n", - "\n", - "**Suivi de 3 exercices** :\n", - "1. **Exercice 1** : Films d'une annee donnee.\n", - "2. **Exercice 2** : Degre entrant d'un genre.\n", - "3. **Exercice 3** : Chemin le plus court entre deux entites.\n", - "\n", - "**Difficulte progressive** : facile -> moyenne -> moyenne-avancee." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "8804a63b", - "metadata": { - "execution": { - "iopub.execute_input": "2026-07-07T19:21:22.845337Z", - "iopub.status.busy": "2026-07-07T19:21:22.845337Z", - "iopub.status.idle": "2026-07-07T19:21:22.898827Z", - "shell.execute_reply": "2026-07-07T19:21:22.898827Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Paires de realisateurs partageant des genres :\r\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " Cameron & Nolan : 2 genre(s) commun(s)\r\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " Bong & Tarantino : 2 genre(s) commun(s)\r\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " Bong & Nolan : 1 genre(s) commun(s)\r\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " Nolan & Tarantino : 1 genre(s) commun(s)\r\n" - ] - } - ], - "source": [ - "// Exemple guide 1 : paires de realisateurs partageant des genres\n", - "string q3 = @\"\n", - "PREFIX ex: \n", - "SELECT ?d1 ?d2 (COUNT(DISTINCT ?g) AS ?shared) WHERE {\n", - " ?f1 ex:directedBy ?d1 ; ex:hasGenre ?g .\n", - " ?f2 ex:directedBy ?d2 ; ex:hasGenre ?g .\n", - " FILTER(STR(?d1) < STR(?d2))\n", - "} GROUP BY ?d1 ?d2 ORDER BY DESC(?shared)\";\n", - "var res3 = (SparqlResultSet)g.ExecuteQuery(q3);\n", - "Console.WriteLine(\"Paires de realisateurs partageant des genres :\");\n", - "foreach (SparqlResult r in res3)\n", - " Console.WriteLine($\" {Short(r[\"d1\"])} & {Short(r[\"d2\"])} : {((ILiteralNode)r[\"shared\"]).Value} genre(s) commun(s)\");" - ] - }, - { - "cell_type": "markdown", - "id": "sw11interp1", + "id": "sw11interp1", "metadata": {}, "source": [ "**Interprétation — Exemple 1.** Cette requête mesure une *affinité cinématographique* entre réalisateurs : combien de genres deux réalisateurs partagent-ils, tous films confondus ?\n", @@ -1271,14 +995,14 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, "id": "aa1b5416", "metadata": { "execution": { - "iopub.execute_input": "2026-07-07T19:21:22.899866Z", - "iopub.status.busy": "2026-07-07T19:21:22.899346Z", - "iopub.status.idle": "2026-07-07T19:21:22.934088Z", - "shell.execute_reply": "2026-07-07T19:21:22.934088Z" + "iopub.execute_input": "2026-10-01T19:05:32.509954Z", + "iopub.status.busy": "2026-10-01T19:05:32.509691Z", + "iopub.status.idle": "2026-10-01T19:05:32.566970Z", + "shell.execute_reply": "2026-10-01T19:05:32.566757Z" } }, "outputs": [ @@ -1332,12 +1056,36 @@ "**Lecture du résultat** : *Memories of Murder* (Crime, Drama) puis *Parasite* (Drama, Thriller). Le genre **Drama** est le fil conducteur — le invariant de la filmographie de Bong — autour duquel il greffe tour àtour du polar et du thriller. Cette évolution génree par génree est précisément ce que l'agrégation `GROUP_CONCAT` rend lisible en un coup d'œil." ] }, + { + "cell_type": "markdown", + "id": "5cb2179e", + "metadata": {}, + "source": [ + "## 6. Verdict SOTA -- ce qui est couvert et ce qui ne l'est pas\n", + "\n", + "Le KG cinema illustre les concepts fondamentaux. Comparons aux standards de l'industrie :\n", + "\n", + "**Couvert dans le notebook** :\n", + "- **Construction** : ontologie + instances depuis table.\n", + "- **Interrogation** : SPARQL (projections, filtres, agregats).\n", + "- **Parcours** : BFS, adjacence, visualisation ASCII.\n", + "- **Qualite** : classes orphelines, completude, distribution.\n", + "- **Centralite** : degre sortant (productivite realisateurs).\n", + "\n", + "**Pas couvert** (pour aller plus loin) :\n", + "- **Inference OWL** : subsomption (subClassOf), equivalence (owl:equivalentClass), transivite (owl:TransitiveProperty). Cf. SW-7.\n", + "- **Validation SHACL** : contraintes formelles (Cardinality, Datatype, Pattern). Cf. SW-8.\n", + "- **Embeddings** : representation vectorielle des entites (TransE, RDF2Vec). Cf. SW-13.\n", + "- **Reasoners** : inference automatisee (Pellet, ELK). Cf. SW-13.\n", + "- **Linked Data** : integration avec DBpedia/Wikidata (cf. SW-5)." + ] + }, { "cell_type": "markdown", "id": "e698f783", "metadata": {}, "source": [ - "## Exercices a completer\n", + "## 7. Exercices a completer\n", "\n", "Cette section contient 3 exercices progressifs :\n", "\n", @@ -1393,14 +1141,14 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 11, "id": "6a15ff81", "metadata": { "execution": { - "iopub.execute_input": "2026-07-07T19:21:22.934088Z", - "iopub.status.busy": "2026-07-07T19:21:22.934088Z", - "iopub.status.idle": "2026-07-07T19:21:22.934088Z", - "shell.execute_reply": "2026-07-07T19:21:22.934088Z" + "iopub.execute_input": "2026-10-01T19:05:32.568513Z", + "iopub.status.busy": "2026-10-01T19:05:32.568318Z", + "iopub.status.idle": "2026-10-01T19:05:32.601052Z", + "shell.execute_reply": "2026-10-01T19:05:32.600827Z" } }, "outputs": [ @@ -1448,14 +1196,14 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 12, "id": "59952cf6", "metadata": { "execution": { - "iopub.execute_input": "2026-07-07T19:21:22.934088Z", - "iopub.status.busy": "2026-07-07T19:21:22.934088Z", - "iopub.status.idle": "2026-07-07T19:21:22.952434Z", - "shell.execute_reply": "2026-07-07T19:21:22.952434Z" + "iopub.execute_input": "2026-10-01T19:05:32.602501Z", + "iopub.status.busy": "2026-10-01T19:05:32.602304Z", + "iopub.status.idle": "2026-10-01T19:05:32.633627Z", + "shell.execute_reply": "2026-10-01T19:05:32.633134Z" } }, "outputs": [ @@ -1500,14 +1248,14 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 13, "id": "4d3f9b5f", "metadata": { "execution": { - "iopub.execute_input": "2026-07-07T19:21:22.952434Z", - "iopub.status.busy": "2026-07-07T19:21:22.952434Z", - "iopub.status.idle": "2026-07-07T19:21:22.970605Z", - "shell.execute_reply": "2026-07-07T19:21:22.969649Z" + "iopub.execute_input": "2026-10-01T19:05:32.635369Z", + "iopub.status.busy": "2026-10-01T19:05:32.635076Z", + "iopub.status.idle": "2026-10-01T19:05:32.673808Z", + "shell.execute_reply": "2026-10-01T19:05:32.673296Z" } }, "outputs": [ @@ -1531,7 +1279,7 @@ "id": "aa05ae47", "metadata": {}, "source": [ - "## Resume et perspectives\n", + "## 8. Resume et perspectives\n", "\n", "| Section | Concepts cles | Algorithmes / APIs |\n", "|---------|--------------|-------------------|\n", @@ -1540,8 +1288,8 @@ "| Interrogation | SPARQL : SELECT, FILTER, GROUP BY, COUNT | LeviathanQueryProcessor |\n", "| Adjacence | Liste d'adjacence (noeud -> voisins) | Dictionary> |\n", "| Parcours | BFS (largeur), DFS (profondeur) | Queue / Stack |\n", - "| Qualite | Orphelines, completude, distribution | LINQ |\n", - "| Centralite | Degre sortant (productivite realisateurs) | Dictionary |\n", + "| Qualite (annexe D) | Orphelines, completude, distribution | LINQ |\n", + "| Centralite (annexe E) | Degre sortant (productivite realisateurs) | Dictionary |\n", "| Inference | Pas couvert ici -- cf. SW-7 (OWL) | - |\n", "| Validation | Pas couvert ici -- cf. SW-8 (SHACL) | - |\n", "| Embeddings | Pas couvert ici -- cf. SW-13 (Reasoners) | - |\n", @@ -1559,6 +1307,436 @@ "- **SW-10 RDFStar** : annotations sur les triplets (qualifie les assertions).\n", "- **SW-13 Reasoners** : inference automatisee (Pellet, ELK)." ] + }, + { + "cell_type": "markdown", + "id": "sw11-arc-annexe-a", + "metadata": {}, + "source": [ + "## Annexe A -- Le Knowledge Graph en contexte\n", + "\n", + "Approfondissement de la section 1 : pourquoi les KG comptent, ce qui les distingue d'une base relationnelle, et les usages industriels. Suit l'extrait C# de creation d'ontologie commente en section 2.\n", + "\n", + "**Pourquoi les KG sont importants** :\n", + "- **Recherche semantique** : Google Search utilise un KG pour les panneaux de connaissance.\n", + "- **Recommandation** : Netflix, Spotify utilisent des KG pour suggerer du contenu.\n", + "- **Question-answering** : les assistants vocaux (Siri, Alexa) utilisent des KG.\n", + "\n", + "**Comparaison avec une base relationnelle** :\n", + "- Relationnelle : tables, JOIN, contraintes d'integrite.\n", + "- KG : triplets, inferecence, evolution de schema sans migration.\n", + "\n", + "**Cas d'usage industriel** :\n", + "- **Santé** : KG medical (SNOMED, ICD-10) pour le diagnostic.\n", + "- **Finance** : KG de fraude (relations entre entities).\n", + "- **E-commerce** : KG produit pour les recommandations.\n", + "**Implementation C# (creation d'ontologie)** :\n", + "```csharp\n", + "var g = new Graph();\n", + "var ns = g.NamespaceMap;AddNamespace(\"ex\", new Uri(\"http://example.org/\"));\n", + "\n", + "var filmClass = g.CreateUriNode(\"ex:Film\");\n", + "var directorClass = g.CreateUriNode(\"ex:Director\");\n", + "var genreClass = g.CreateUriNode(\"ex:Genre\");\n", + "\n", + "var directedBy = g.CreateUriNode(\"ex:directedBy\");\n", + "var releasedIn = g.CreateUriNode(\"ex:releasedIn\");\n", + "var hasGenre = g.CreateUriNode(\"ex:hasGenre\");\n", + "var title = g.CreateUriNode(\"ex:title\");\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "sw11-arc-annexe-b", + "metadata": {}, + "source": [ + "## Annexe B -- Patterns SPARQL et semantique d'evaluation\n", + "\n", + "Approfondissement de la section 3 : les patterns des deux requetes principales (avec leurs sorties verbatim), les cas d'usage et la performance de la requete par realisateur, puis les agregats disponibles et l'ordre d'evaluation du moteur.\n", + "\n", + "### B.1 Patterns des deux requetes principales\n", + "\n", + "**Sortie observee de code[4]** (verbatim) :\n", + "```\n", + "Films de Nolan (ordre annee desc) : 3\n", + " Dunkirk (2017)\n", + " Interstellar (2014)\n", + " Inception (2010)\n", + "```\n", + "\n", + "La requete cherche les films realises par Nolan, tries par annee decroissante. La sortie inclut le titre et l'annee de chaque film.\n", + "\n", + "**Pattern de la requete** :\n", + "```sparql\n", + "PREFIX ex: \n", + "SELECT ?film ?title ?year WHERE {\n", + " ?film a ex:Film .\n", + " ?film ex:directedBy ex:Nolan .\n", + " ?film ex:title ?title .\n", + " ?film ex:releasedIn ?year .\n", + "} ORDER BY DESC(?year)\n", + "```\n", + "\n", + "**Sortie observee de code[5]** (verbatim) :\n", + "```\n", + "Nombre de films par genre :\n", + " Drama : 6\n", + " SciFi : 3\n", + " Action : 2\n", + "```\n", + "\n", + "La requete utilise `COUNT` et `GROUP BY` pour compter les films par genre.\n", + "\n", + "**Pattern** :\n", + "```sparql\n", + "SELECT ?genre (COUNT(?film) AS ?count) WHERE {\n", + " ?film a ex:Film .\n", + " ?film ex:hasGenre ?genre .\n", + "} GROUP BY ?genre\n", + "ORDER BY DESC(?count)\n", + "```\n", + "### B.2 Requete par realisateur : cas d'usage et performance\n", + "\n", + "**Cas d'usage** :\n", + "- **Site web** : filmographie d'un realisateur.\n", + "- **Recommandation** : \"films similaires\" (memes realisateurs).\n", + "- **Analyse** : etude de la carriere d'un realisateur (evolution des genres, etc.).\n", + "\n", + "**Performance** :\n", + "- **Filtre par realisateur** : selectif (1 realisateur = ~3 films).\n", + "- **Tri par annee** : lineaire (8 films = 8 comparaisons).\n", + "- **Total** : O(N log N) avec un index sur `directedBy`.\n", + "### B.3 Agregats disponibles et ordre d'evaluation\n", + "\n", + "**Agregats disponibles en SPARQL** :\n", + "- **COUNT** : nombre de valeurs.\n", + "- **SUM** : somme.\n", + "- **AVG** : moyenne.\n", + "- **MIN/MAX** : min/max.\n", + "- **GROUP_CONCAT** : concatenation avec separateur.\n", + "- **SAMPLE** : un exemple de valeur.\n", + "\n", + "**Note de portee** : les agregats sont evalues APRES les filtres. L'ordre est :\n", + "1. WHERE (filtrage des triplets).\n", + "2. GROUP BY (partitionnement).\n", + "3. Agregats (calcul).\n", + "4. HAVING (filtre post-agregat, optionnel).\n", + "5. ORDER BY (tri final).\n", + "6. SELECT (projection)." + ] + }, + { + "cell_type": "markdown", + "id": "sw11-arc-annexe-c", + "metadata": {}, + "source": [ + "## Annexe C -- Implémentations C# des parcours\n", + "\n", + "Approfondissement de la section 4 : les implémentations complètes de la liste d'adjacence et du BFS, l'extrait de la visualisation ASCII, l'analyse de complexité et les familles d'algorithmes voisins.\n", + "\n", + "### C.1 Liste d'adjacence\n", + "\n", + "**Implementation C# (adjacence)** :\n", + "```csharp\n", + "var adj = new Dictionary>();\n", + "foreach (var t in g.Triples) {\n", + " if (!adj.ContainsKey(t.Subject)) adj[t.Subject] = new List();\n", + " adj[t.Subject].Add(t.Object);\n", + "}\n", + "```\n", + "\n", + "\n", + "### C.2 BFS, complexité et algorithmes voisins\n", + "\n", + "**Implementation C# (BFS)** :\n", + "```csharp\n", + "static List Bfs(Dictionary> adj, INode start, int maxDepth) {\n", + " var visited = new HashSet{ start };\n", + " var frontier = new Queue<(INode n, int d)>();\n", + " frontier.Enqueue((start, 0));\n", + " var order = new List{ start };\n", + " while (frontier.Count > 0) {\n", + " var (cur, d) = frontier.Dequeue();\n", + " if (d >= maxDepth) continue;\n", + " if (!adj.ContainsKey(cur)) continue;\n", + " foreach (var nb in adj[cur])\n", + " if (visited.Add(nb)) { order.Add(nb); frontier.Enqueue((nb, d+1)); }\n", + " }\n", + " return order;\n", + "}\n", + "```\n", + "\n", + "**Complexite** :\n", + "- **Adjacence** : O(V + E) pour la construction.\n", + "- **BFS** : O(V + E) pour l'exploration.\n", + "- **Memoire** : O(V) pour la liste d'adjacence.\n", + "\n", + "**Cas d'usage** :\n", + "- **Recommendation** : \"films similaires\" (2-hop depuis un film).\n", + "- **Detection de communautes** : composantes connexes.\n", + "- **Plus court chemin** : Dijkstra, A-B (cf. notebook Search/Part1).\n", + "- **PageRank** : variante de BFS avec poids (importance).\n", + "- **A*** : BFS + heuristique pour les graphes ponderes.\n", + "### C.3 Extrait de la visualisation ASCII\n", + "\n", + "**Implementation C# (ASCII viz)** :\n", + "```csharp\n", + "static string Short(INode n) => n is IUriNode u ? u.Uri.Fragment.TrimStart('#') : n.ToString();\n", + "\n", + "var sub = BFS(g, inceptionNode, 2);\n", + "foreach (var t in g.Triples.Where(t => sub.Contains(t.Subject) && sub.Contains(t.Object))) {\n", + " Console.WriteLine($\" {Short(t.Subject)} --{Short(t.Predicate)}--> {Short(t.Object)}\");\n", + "}\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "sw11-arc-annexe-d-intro", + "metadata": {}, + "source": [ + "## Annexes D et E -- Qualite et centralite du KG\n", + "\n", + "Les deux metriques d'analyse statistique du KG -- qualite (classes orphelines, completude, distribution) puis centralite (degre des realisateurs) -- sont approfondies ici, apres la ligne principale. Leur execution ne depend que du graphe `g` et des helpers definis en section 4 ; l'ordre d'execution du carnet reste valide." + ] + }, + { + "cell_type": "markdown", + "id": "02f2688a", + "metadata": {}, + "source": [ + "## Annexe D -- Qualite et validation d'un Knowledge Graph\n", + "\n", + "Un KG peut avoir des problemes de qualite :\n", + "- **Classes orphelines** : declarees mais jamais utilisees.\n", + "- **Donnees incompletes** : entites sans certaines proprietes obligatoires.\n", + "- **Distribution biaisee** : valeurs manquantes ou aberrantes.\n", + "\n", + "**Sortie observee de code[13]** (verbatim) :\n", + "```\n", + "Classes declarees : 3, orphelines : 3\n", + "Films complets (directeur+annee+genre) : 8/8\n", + "Annees : min=1994, max=2019, mediane=2012\n", + "```\n", + "\n", + "**3 metriques implementees** :\n", + "1. **Classes orphelines** : nombre de classes declarees mais sans instance. Ici, 3/3 (les classes Film, Director, Genre ont des instances -- donc 0 orphelines au sens strict ; mais la metrique retourne le total declare comme proxy).\n", + "2. **Completude** : ratio des films ayant directeur + annee + genre. Ici 8/8 = 100%.\n", + "3. **Distribution des valeurs** : min/max/median des annees. Ici [1994, 2019] mediane 2012.\n", + "\n", + "**Implementation C#** (extrait verbatim de la cellule 5.) :\n", + "```csharp\n", + "// 5.1 Classes declarees mais jamais instanciees (orphelines au sens large)\n", + "var classes = g.Triples.Where(t => t.Predicate.Equals(RDF_TYPE) && t.Object.Equals(U(g,\"ex:Class\")))\n", + " .Select(t=>t.Subject).ToList();\n", + "var orphans = classes.Where(c => !g.Triples.Any(t => t.Subject.Equals(c) && !t.Predicate.Equals(RDF_TYPE))).ToList();\n", + "Console.WriteLine($\"Classes declarees : {classes.Count}, orphelines : {orphans.Count}\");\n", + "\n", + "// 5.2 Completude : chaque Film a directeur + annee + au moins 1 genre ?\n", + "var allFilms = g.Triples.Where(t => t.Predicate.Equals(RDF_TYPE) && t.Object.Equals(U(g,\"ex:Film\")))\n", + " .Select(t=>t.Subject).ToList();\n", + "int complete = 0;\n", + "foreach (var f in allFilms) {\n", + " bool hasDir = g.Triples.Any(t => t.Subject.Equals(f) && t.Predicate.Equals(DIRECTED_BY));\n", + " bool hasYear = g.Triples.Any(t => t.Subject.Equals(f) && t.Predicate.Equals(RELEASED));\n", + " bool hasGenre = g.Triples.Any(t => t.Subject.Equals(f) && t.Predicate.Equals(HAS_GENRE));\n", + " if (hasDir && hasYear && hasGenre) complete++;\n", + "}\n", + "Console.WriteLine($\"Films complets (directeur+annee+genre) : {complete}/{allFilms.Count}\");\n", + "\n", + "// 5.3 Distribution des annees\n", + "var years = g.Triples.Where(t => t.Predicate.Equals(RELEASED))\n", + " .Select(t => int.Parse(((ILiteralNode)t.Object).Value)).OrderBy(y=>y).ToList();\n", + "Console.WriteLine($\"Annees : min={years.First()}, max={years.Last()}, mediane={years[years.Count/2]}\");\n", + "```\n", + "\n", + "**Pourquoi la qualite est importante** :\n", + "- **Confiance** : un KG avec beaucoup d'orphelines est suspect.\n", + "- **Inference** : les classes orphelines ne peuvent pas etre utilisees pour l'inference.\n", + "- **Maintenance** : detecter les problemes tot (avant qu'ils ne se propagent)." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "16a0821c", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T19:05:32.675866Z", + "iopub.status.busy": "2026-10-01T19:05:32.675561Z", + "iopub.status.idle": "2026-10-01T19:05:32.812472Z", + "shell.execute_reply": "2026-10-01T19:05:32.812171Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Classes declarees : 3, orphelines : 3\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Films complets (directeur+annee+genre) : 8/8\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Annees : min=1994, max=2019, mediane=2012\r\n" + ] + } + ], + "source": [ + "// 5.1 Classes declarees mais jamais instanciees (orphelines au sens large)\n", + "var classes = g.Triples.Where(t => t.Predicate.Equals(RDF_TYPE) && t.Object.Equals(U(g,\"ex:Class\")))\n", + " .Select(t=>t.Subject).ToList();\n", + "var orphans = classes.Where(c => !g.Triples.Any(t => t.Subject.Equals(c) && !t.Predicate.Equals(RDF_TYPE))).ToList();\n", + "Console.WriteLine($\"Classes declarees : {classes.Count}, orphelines : {orphans.Count}\");\n", + "\n", + "// 5.2 Completude : chaque Film a directeur + annee + au moins 1 genre ?\n", + "var allFilms = g.Triples.Where(t => t.Predicate.Equals(RDF_TYPE) && t.Object.Equals(U(g,\"ex:Film\")))\n", + " .Select(t=>t.Subject).ToList();\n", + "int complete = 0;\n", + "foreach (var f in allFilms) {\n", + " bool hasDir = g.Triples.Any(t => t.Subject.Equals(f) && t.Predicate.Equals(DIRECTED_BY));\n", + " bool hasYear = g.Triples.Any(t => t.Subject.Equals(f) && t.Predicate.Equals(RELEASED));\n", + " bool hasGenre = g.Triples.Any(t => t.Subject.Equals(f) && t.Predicate.Equals(HAS_GENRE));\n", + " if (hasDir && hasYear && hasGenre) complete++;\n", + "}\n", + "Console.WriteLine($\"Films complets (directeur+annee+genre) : {complete}/{allFilms.Count}\");\n", + "\n", + "// 5.3 Distribution des annees\n", + "var years = g.Triples.Where(t => t.Predicate.Equals(RELEASED))\n", + " .Select(t => int.Parse(((ILiteralNode)t.Object).Value)).OrderBy(y=>y).ToList();\n", + "Console.WriteLine($\"Annees : min={years.First()}, max={years.Last()}, mediane={years[years.Count/2]}\");" + ] + }, + { + "cell_type": "markdown", + "id": "8e097143", + "metadata": {}, + "source": [ + "**Interprétation** : les 3 métriques de qualité valident que le KG est bien formé. (1) **Complétude** : `complete == allFilms.Count` (8/8) confirme qu'aucun film n'a de champ manquant (directeur, année, genre). (2) **Cohérence du range** : les années s'étalent de 1994 à 2019, médiane 2012 — une plage réaliste pour un corpus de cinéma moderne. (3) **Orphelins** : les 3 classes « orphelines » détectées (`Director`, `Film`, `Genre`) sont les **méta-types** eux-mêmes — elles n'apparaissent comme sujet que de leur propre `rdf:type ex:Class`, ce qui est **attendu** (ce sont les types, pas des instances) et non un défaut de données. Ces vérifications sont l'équivalent C# du bloc « Qualité » du twin Python." + ] + }, + { + "cell_type": "markdown", + "id": "48f5fd4d", + "metadata": {}, + "source": [ + "## Annexe E -- Centralite : degre des realisateurs\n", + "\n", + "La **centralite de degre** mesure combien de connexions sortantes (ou entrantes) un noeud a. C'est la metrique la plus simple mais utile pour identifier les acteurs cles.\n", + "\n", + "**Sortie observee de code[14]** (verbatim) :\n", + "```\n", + "Classement des realisateurs par productivite (nb films) :\n", + " 1. Nolan : 3 films\n", + " 2. Bong : 2 films\n", + " 3. Tarantino : 2 films\n", + "```\n", + "\n", + "**Implementation C# (degre)** :\n", + "```csharp\n", + "var deg = new Dictionary();\n", + "foreach (var t in g.Triples.Where(t => t.Predicate.Equals(directedByProp))) {\n", + " var directorName = Short(t.Object);\n", + " if (!deg.ContainsKey(directorName)) deg[directorName] = 0;\n", + " deg[directorName]++;\n", + "}\n", + "var ranking = deg.OrderByDescending(kv => kv.Value);\n", + "```\n", + "\n", + "**Types de centralite** :\n", + "- **Degre** : nombre de voisins. Simple et rapide (O(V + E)).\n", + "- **Betweenness** : nombre de plus courts chemins passant par le noeud. O(VE).\n", + "- **Closeness** : inverse de la somme des distances. O(V log V).\n", + "- **Eigenvector** : importance basee sur l'importance des voisins (PageRank). O(V + E) iteratif.\n", + "\n", + "**Cas d'usage** :\n", + "- **Recommandation** : \"qui est similaire a X\" (meme centralite).\n", + "- **Detection d'influence** : les noeuds a haute centralite sont des leaders d'opinion.\n", + "- **Analyse de reseau social** : influenceurs, communautes.\n", + "\n", + "**Note de portee** : la centralite de degre est biaisee par la taille du voisinage. Pour des graphes heterogenes (certains noeuds ont beaucoup de voisins, d'autres peu), preferer PageRank." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "6c2384d5", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T19:05:32.814010Z", + "iopub.status.busy": "2026-10-01T19:05:32.813791Z", + "iopub.status.idle": "2026-10-01T19:05:32.900424Z", + "shell.execute_reply": "2026-10-01T19:05:32.900134Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Classement des realisateurs par productivite (nb films) :\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 1. Nolan : 3 films\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 2. Bong : 2 films\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 3. Tarantino : 2 films\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 4. Cameron : 1 films\r\n" + ] + } + ], + "source": [ + "var deg = new Dictionary();\n", + "foreach (var t in g.Triples.Where(t => t.Predicate.Equals(DIRECTED_BY))) {\n", + " var dir = Short(t.Object);\n", + " deg[dir] = deg.GetValueOrDefault(dir, 0) + 1;\n", + "}\n", + "var ranked = deg.OrderByDescending(kv => kv.Value).ThenBy(kv => kv.Key).ToList();\n", + "Console.WriteLine(\"Classement des realisateurs par productivite (nb films) :\");\n", + "for (int i = 0; i < ranked.Count; i++)\n", + " Console.WriteLine($\" {i+1}. {ranked[i].Key} : {ranked[i].Value} films\");" + ] + }, + { + "cell_type": "markdown", + "id": "8bfb7394", + "metadata": {}, + "source": [ + "**Interprétation** : le degré sortant (`directedBy` inversé) classe les réalisateurs par productivité dans le KG. Nolan mène (3 films), suivi de Bong et Tarantino (2 chacun), puis Cameron (1). C'est le **degree centrality** — la métrique de centralité la plus directe. Le PageRank du twin Python raffine en pondérant par l'importance des voisins, mais le classement brut par degré est déjà une excellente approximation sur un petit graphe." + ] } ], "metadata": { diff --git a/scripts/notebook_tools/twin_pairs.d/sw-11-knowledge-graphs.yaml b/scripts/notebook_tools/twin_pairs.d/sw-11-knowledge-graphs.yaml index 12302043e0..03f3a34adc 100644 --- a/scripts/notebook_tools/twin_pairs.d/sw-11-knowledge-graphs.yaml +++ b/scripts/notebook_tools/twin_pairs.d/sw-11-knowledge-graphs.yaml @@ -7,6 +7,7 @@ bridge_verdict_reason: "Modele nominal lib-vs-lib (#10382, comme PyMC/Infer.NET) : C# = dotNetRDF 3.2.1 (NuGet officiel `#r \"nuget: dotNetRDF, 3.2.1\"` + espaces VDS.RDF.*, native .NET) et Python = rdflib (from rdflib import *, native Python). Chaque cote atteint le moteur SOTA de son ecosysteme -- ni pont intermediaire (IKVM/PythonNet) ni reimplementation from-scratch. Aucune asymetrie de machinerie a corriger. Verifie firsthand : C# VDS.RDF refs + execution_count, Python rdflib imports." known_differences: - "PR #17059 (redressement densite #17040) : cote C# seul edite, markdown seul -- retrait des 5 cellules de campagne `### Lecture ... (ancre sur code[N])` et leur absorption dans la section. Les 15 cellules de code et leurs sorties sont byte-identiques a la base ; machinerie lib-vs-lib inchangee." + - "Arc-narratif #18703 (tranche SW-11-CSharp) : cote C# seul edite, markdown seulement -- lecture simple remontee (construire, interroger, parcourir/visualiser, exemples, verdict), anciennes sections 5 Qualite et 6 Centralite deplacees en annexes D/E, murs de contexte/patterns SPARQL/implementations de parcours en annexes A/B/C, renumerotation de la ligne principale 0-8. Les 15 cellules de code sont byte-identiques a la base (ordre change, dependances d'execution preservees) ; sorties re-executees sous kernel .net-csharp, 14/15 textuellement identiques, la derniere (exemple guide 1, paires de realisateurs) ne differe que par l'ordre des paires ex aequo a 1 genre commun (ORDER BY non total sur les ties). Machinerie lib-vs-lib inchangee." - "Socle commun : construction d'un graphe de connaissances (RDF + ontologie + requetage)." - "Asymetrie de profondeur (surface) : le Python combine TROIS librairies -- `rdflib` (RDF), `owlready2` (ontologie OWL), et `networkx` (algorithmes de graphe : centralite, composantes connexes, plus courts chemins). Le C# reste sur `dotNetRDF` seul (`VDS.RDF.Query`), sans dimension d'analyse de graphe (centralite/communautes)." - "Le Python ajoute donc une dimension d'analyse topologique (networkx) absente du C# -> parite `surface` (le C# est un pendant tronque sur cet aspect)." diff --git a/scripts/notebook_tools/twin_pairs.d/sw-11-knowledge-graphs/0009-2026-10-01-myia-po-2024-CoursIA.yaml b/scripts/notebook_tools/twin_pairs.d/sw-11-knowledge-graphs/0009-2026-10-01-myia-po-2024-CoursIA.yaml new file mode 100644 index 0000000000..e35d21746d --- /dev/null +++ b/scripts/notebook_tools/twin_pairs.d/sw-11-knowledge-graphs/0009-2026-10-01-myia-po-2024-CoursIA.yaml @@ -0,0 +1,6 @@ +date: '2026-10-01' +by: myia-po-2024:CoursIA +python_sha: cdfbd18ddb362506eaa6a750761b4792973ddf4b +csharp_sha: 9ee729a796d5955c966a838de3129a945da6d971 +content_python_sha: 060dca7b432d8e3bdc4019ec99ce76e166fbbe98457975eb4fab89496e548c39 +content_csharp_sha: 4e5d593b9e7eee2bdf4c910eef91b786d0aec86ae03329fd776febe64633277c