Le notebook Argumentation-03-Dung-AF-Semantics-Python reconstruit l’argumentation abstraite de Dung (1995) : des arguments s’attaquent, des sémantiques (grounded / preferred / stable) décident lesquels survivent. Mais dans ce cadre, toutes les attaques réussissent — un argument qui en attaque un autre le défait, un point c’est tout.
La réalité du débat est différente. Considérez : « Il faut réduire les impôts pour relancer la consommation » (argument A, valeur croissance) attaque « Il faut maintenir les impôts pour financer les services publics » (argument B, valeur solidarité). L’attaque de A sur B doit-elle réussir ? Cela dépend de ce que valorise l’auditoire : un auditoire qui préfère la croissance à la solidarité laissera A défaire B ; un auditoire inverse laissera B résister à A. Le même graphe d’attaques produit des conclusions différentes selon l’audience.
C’est le saut conceptuel de Trevor Bench-Capon dans « A Value-Based Argumentation Framework » (2003) : enrichir le cadre de Dung par des valeurs (ce que chaque argument promeut) et une audience (un ordre de préférence sur ces valeurs). Une attaque ne défait sa cible que si la valeur de l’attaquant est au moins aussi préférée que celle de l’attaqué, aux yeux de l’audience.
Ce notebook reconstruit le cadre VAF de zéro en pur Python (stdlib), à la suite de Dung_AF_Semantics dont il reprend les primitives. Objectif pédagogique : voir concrètement comment trois audiences différentes sur un même graphe d’attaques en cycle produisent trois conclusions différentes — et pourquoi ce phénomène connecte l’argumentation au choix social (préférences agrégées).
Le cadre VAF — définition
Un Value-based Argumentation Framework (VAF) est un quintuplet \(\langle \mathcal{A}, \mathcal{R}, V, \eta, \succcurlyeq_a \rangle\) où :
\(\mathcal{A}\) est un ensemble d’arguments (comme chez Dung) ;
\(\mathcal{R} \subseteq \mathcal{A} \times \mathcal{A}\) est la relation d’attaque (comme chez Dung) ;
\(V\) est un ensemble de valeurs ;
\(\eta : \mathcal{A} \to V\) associe à chaque argument la valeur qu’il promeut ;
\(\succcurlyeq_a\) est l’audience : un ordre total (préférence) sur \(V\).
Défaite dépendante de l’audience. L’argument \(x\)défait\(y\) (l’attaque \(x \to y\) réussit) si et seulement si :
\[\eta(x) \succcurlyeq_a \eta(y)\]
Sinon, \(y\)résiste à \(x\) : l’attaque échoue, \(y\) n’est pas défait par \(x\).
Une fois cette relation de défaite audience-dépendante fixée, on réapplique les sémantiques de Dung (grounded, preferred, stable) dessus. Le résultat dépend de l’audience : changer l’ordre de préférence sur \(V\) change qui défait qui, donc change les extensions.
# Primitives de Dung (auto-contenues, comme Dung_AF_Semantics).from itertools import chain, combinationsclass AF:"""Cadre d'argumentation abstrait : arguments + relation d'attaque. Ici `attacks` = relation de DEFAITE deja resolue (cf. af_from_audience plus bas). On construit un AF a partir d'un VAF + une audience, puis on lui applique les semantiques de Dung. """def__init__(self, args, attacks):self.args =set(args)self.attacks =set(attacks) # couples (attaquant, attaque)def attackers(self, x):return {a for (a, b) inself.attacks if b == x}def__repr__(self): att =", ".join(f"{a}->{b}"for (a, b) insorted(self.attacks))returnf"AF(args={sorted(self.args)}, attacks=[{att}])"def defeats(af, S, x):"""True si S defait x (au moins un membre de S attaque x)."""returnany((a, x) in af.attacks for a in S)def defends(af, S, x):"""True si S defend x : tout attaquant de x est defait par S."""returnall(defeats(af, S, b) for b in af.attackers(x))def powerset(iterable): s =list(iterable)return chain.from_iterable(combinations(s, r) for r inrange(len(s) +1))def grounded(af):"""Extension grounded : plus petit point fixe de la fonction de defense.""" E =set()whileTrue: E_new = E | {x for x in af.args if defends(af, E, x)}if E_new == E:return E E = E_newprint("Primitives de Dung chargees. Smoke test:")af_test = AF({"a", "b"}, {("a", "b")})print(f" af_test = {af_test}")print(f" grounded(af_test) = {grounded(af_test)} # a defendu, b defait par a")
Primitives de Dung chargees. Smoke test:
af_test = AF(args=['a', 'b'], attacks=[a->b])
grounded(af_test) = {'a'} # a defendu, b defait par a
# --- Le cadre VAF (Bench-Capon 2003) ---class VAF:"""Value-based Argumentation Framework. Attributs : args : ensemble d'arguments attacks : relation d'attaque (couples), COMME chez Dung values : mapping arg -> valeur (eta) value_set: l'ensemble des valeurs V """def__init__(self, args, attacks, values):self.args =set(args)self.attacks =set(attacks)self.values =dict(values) # arg -> valeurself.value_set =set(self.values.values())def value_of(self, x):returnself.values[x]def__repr__(self): v =", ".join(f"{a}:{val}"for a, val insorted(self.values.items())) att =", ".join(f"{a}->{b}"for (a, b) insorted(self.attacks))returnf"VAF(args={sorted(self.args)}, attacks=[{att}], values={{{v}}})"def audience_rank(audience, v):"""Rang d'une valeur v dans l'audience (0 = la plus preferee). `audience` = liste ordonnee des valeurs, de la plus preferee a la moins preferee. Plus le rang est PETIT, plus la valeur est preferee. """return audience.index(v)def defeats_under_audience(vaf, audience, x, y):"""True si l'attaque x->y reussit sous cette audience. L'attaque reussit ssi value(x) est au moins aussi preferee que value(y). On compare les RANGS : rang plus petit = plus prefere. """if (x, y) notin vaf.attacks:returnFalsereturn audience_rank(audience, vaf.value_of(x)) <= audience_rank(audience, vaf.value_of(y))def af_from_audience(vaf, audience):"""Construit le AF (Dung) correspondant a un VAF sous une audience donnee. On ne garde que les attaques qui REUSSISSENT sous l'audience. Les attaques qui echouent (valeur de l'attaquant moins preferee) disparaissent : la cible resiste. """ surviving = {(x, y) for (x, y) in vaf.attacksif defeats_under_audience(vaf, audience, x, y)}return AF(vaf.args, surviving)# Smoke test : un VAF minimal a 2 arguments, 1 valeur commune.vaf_test = VAF({"a", "b"}, {("a", "b")}, {"a": "v", "b": "v"})aud_test = ["v"]af_t = af_from_audience(vaf_test, aud_test)print(f"VAF test: {vaf_test}")print(f" audience {aud_test} -> AF survived attacks: {af_t.attacks}")print(f" grounded = {grounded(af_t)} # valeurs egales -> attaque reussit -> a defait b")
VAF test: VAF(args=['a', 'b'], attacks=[a->b], values={a:v, b:v})
audience ['v'] -> AF survived attacks: {('a', 'b')}
grounded = {'a'} # valeurs egales -> attaque reussit -> a defait b
Exemple canonique : le cycle à trois valeurs
Voici le moment pédagogique clé. Prenons trois arguments en cycle (\(a \to b \to c \to a\)), chacun promouvant une valeur distincte :
Argument
Valeur promue
\(a\)
verite
\(b\)
confiance
\(c\)
solidarite
Chez Dung (sans valeurs), un cycle de longueur impaire n’a pas d’extension stable et son extension grounded est vide (personne n’est défendable : chacun est attaqué par un argument non défait). Mais avec des valeurs, l’audience décide quelles attaques réussissent, ce qui brise la symétrie du cycle.
Construisons ce VAF et calculons ses extensions sous trois audiences distinctes.
Les trois audiences produisent trois extensions grounded différentes sur le même graphe d’attaques :
Audience (valeur en tête)
Attaques qui réussissent
Extension grounded
verite en tête
\(a \to b\), \(b \to c\) (mais \(c \not\to a\))
{a, c} (b exclu)
confiance en tête
\(b \to c\), \(c \to a\) (mais \(a \not\to b\))
{a, b} (c exclu)
solidarite en tête
\(c \to a\), \(a \to b\) (mais \(b \not\to c\))
{b, c} (a exclu)
Lecture : sur ce cycle, le grounded contient toujours deux arguments sur trois — mais l’argument exclu change avec l’audience (\(b\), puis \(c\), puis \(a\)). Le mécanisme : l’argument dont la valeur est la plus préférée voit l’attaque dirigée contre lui échouer (son attaquant est moins préféré), donc il survit ; et comme sa propre attaque vers le suivant réussit, il élimine ce suivant. Le troisième argument survit alors par défense (le survivant principal défait l’attaquant du troisième). En résumé : mettre une valeur en tête d’audience condamne l’argument que cette valeur attaque dans le cycle.
C’est le résultat central de Bench-Capon : la conclusion d’un débat n’est pas déterminée par le graphe d’arguments seul, mais par le graphe ET les valeurs de l’audience. Trois auditoires de valeurs différentes, confrontés aux mêmes arguments, excluent légitimement des arguments différents. Cela relie l’argumentation à la théorie du choix social : si plusieurs individus ont des audiences différentes, agréger leurs préférences (cf. Tweety-9, GameTheory SocialChoice) devient la question centrale pour décider collectivement.
Sweep d’audiences : combien de conclusions distinctes ?
Sur un VAF à \(|V|\) valeurs, il y a \(|V|!\) audiences possibles (tous les ordres totaux). Une question naturelle : parmi toutes ces audiences, combien d’extensions grounded distinctes obtient-on ? Si la réponse est 1, les valeurs sont décoratives (elles ne changent rien). Si la réponse est grande, le débat est sensible à l’audience — ce qui est précisément la situation où l’argumentation basée sur les valeurs apporte quelque chose que Dung pur ne peut pas exprimer.
Énumérons toutes les audiences sur notre VAF à 3 valeurs et comptons les outcomes.
from itertools import permutations# Sweep : toutes les audiences (ordres totaux sur les valeurs).print("Sweep complet des audiences sur le VAF canonique (3! = 6 ordres):")print("-"*65)distinct_outcomes = {}for aud in permutations(sorted(vaf.value_set)): af_aud = af_from_audience(vaf, list(aud)) g = grounded(af_aud) key =tuple(sorted(g)) if g else"()" distinct_outcomes.setdefault(key, []).append(list(aud)) g_str =set(g) if g else"{}"print(f" audience {list(aud)} -> grounded {g_str}")print("-"*65)print(f"Distinct grounded outcomes: {len(distinct_outcomes)}")for outcome, auds in distinct_outcomes.items():print(f" {outcome} atteint par {len(auds)} audience(s)")
Résultat : sur 6 audiences possibles, on obtient 3 conclusions distinctes — {a, b}, {a, c}, {b, c} — chacune atteinte par 2 audiences. (Les deux audiences qui mettent la même valeur en tête convergent vers la même extension : seul compte quel argument est « en tête ».) Le débat est donc fortement sensible à l’audience : il n’y a pas de verdict universel, la conclusion dépend de qui écoute.
C’est un diagnostic actionnable : si un médiateur veut faire converger un débat argumentatif bloqué en cycle, il doit d’abord révéler les valeurs des parties et chercher un compromis sur l’ordre de préférence — pas seulement sur les arguments eux-mêmes.
Exercices
Les trois exercices suivants approfondissent le cadre. Chaque stub est à compléter — le notebook s’exécute de bout en bout même non complété (les exercices renvoient None et affichent un message).
Exercice 1 — Choisir une audience pour rejeter un argument donné
On a vu que chaque audience exclut un argument différent du grounded. Objectif : écrire audience_qui_rejette(vaf, cible) qui renvoie une audience rendant cibleabsent du grounded (rejeté).
Indice : d’après la lecture ci-dessus, l’argument exclu est celui attaqué par l’argument dont la valeur est la plus préférée. Donc, pour rejeter cible, il faut mettre en tête d’audience la valeur de son attaquant dans le graphe.
# Exercice 1 : a completerdef audience_qui_rejette(vaf, cible):"""Renvoie une audience (liste ordonnee de valeurs) telle que `cible` soit ABSENT de l'extension grounded (rejete). Indice : identifier l'attaquant de `cible` dans le graphe d'attaque, puis mettre la valeur de cet attaquant en tete d'audience. Etape 1 : trouver x tel que (x, cible) in vaf.attacks. Etape 2 : construire une audience avec value(x) en tete, puis le reste. """# TODO etudiantreturnNone# Verification (decommenter apres completion) :# aud = audience_qui_rejette(vaf, "b")# if aud is not None:# af_aud = af_from_audience(vaf, aud)# g = grounded(af_aud)# print(f"Audience trouvee: {aud} -> grounded = {sorted(g)}")# assert "b" not in g, f"Echec: b devrait etre exclu, grounded = {g}"# print("SUCCES : b est rejete sous cette audience.")# else:# print("Exercice a completer.")print("Exercice 1 a completer.")
Exercice 1 a completer.
Exercice 2 — Quand le VAF dégénère en Dung pur
Sur le VAF canonique, les valeurs sont distinctes et l’audience brise le cycle. Mais que se passe-t-il si tous les arguments partagent la même valeur ? Alors toute attaque a un attaquant et une cible de valeur égale → toutes les attaques réussissent, et l’on retrouve exactement le cycle de Dung pur : le grounded est vide.
Objectif : construire un VAF (cycle \(a \to b \to c \to a\)) où tous les arguments ont la même valeur, choisir une audience triviale, et vérifier que le grounded est bien vide — confirmant que le VAF contient Dung comme cas particulier (valeurs dégénérées).
# Exercice 2 : a completerdef vaf_degenere_en_dung():"""Construisez un VAF (cycle a->b->c->a) ou tous les arguments partagent la MEME valeur, et renvoyez (vaf, audience) tels que TOUTES les attaques reussissent. Renvoie (None, None) tant que non complete. Indice : values = {"a":"v","b":"v","c":"v"} ; audience = ["v"]. Verifiez ensuite que grounded est vide (comme un cycle de Dung impair). """# TODO etudiantreturnNone, None# vaf2, aud2 = vaf_degenere_en_dung()# if vaf2 is not None:# af2 = af_from_audience(vaf2, aud2)# g2 = grounded(af2)# print(f"Attaques reussissant: {sorted(af2.attacks)}")# print(f"Grounded: {sorted(g2) if g2 else '{} (vide)'}")# assert len(g2) == 0, "Le grounded devrait etre vide (cycle impair de Dung)"# print("SUCCES : VAF degenere en Dung, grounded vide.")# else:# print("Exercice a completer.")print("Exercice 2 a completer.")
Exercice 2 a completer.
Exercice 3 — Cartographier toutes les conclusions d’un VAF plus large
Généralisez le sweep : écrivez toutes_les_conclusions(vaf) qui renvoie un dictionnaire {extension_grounded: [liste des audiences qui la produisent]} pour toutes les audiences possibles.
Objectif : appliquer à un VAF à 4 arguments et 3 valeurs (certains arguments partageant une valeur), et interpréter pourquoi le nombre de conclusions distinctes est (généralement) inférieur au nombre d’audiences.
# Exercice 3 : a completerdef toutes_les_conclusions(vaf):"""Renvoie {tuple(sorted(grounded)): [audiences productrices]} pour toutes les audiences possibles (tous les ordres totaux sur value_set). Indice : itertools.permutations sur value_set ; pour chacune, calculer le grounded via af_from_audience + grounded. Grouper par outcome. Une audience vide () represente un grounded vide. """# TODO etudiantreturn {}# Test sur le VAF canonique a 3 valeurs :# conclu = toutes_les_conclusions(vaf)# print(f"Le VAF canonique a {len(conclu)} conclusion(s) distincte(s):")# for outcome, auds in conclu.items():# lab = sorted(outcome) if outcome else "{} (vide)"# print(f" {lab} <- {len(auds)} audience(s)")# print()# # VAF plus large : 4 arguments, 3 valeurs (a et d partagent 'verite')# vaf_large = VAF({"a","b","c","d"}, {("a","b"),("b","c"),("c","d"),("d","a")},# {"a":"verite","b":"confiance","c":"solidarite","d":"verite"})# conclu_large = toutes_les_conclusions(vaf_large)# print(f"Le VAF large (4 args, 3 valeurs) a {len(conclu_large)} conclusion(s) distincte(s).")print("Exercice 3 a completer.")
Exercice 3 a completer.
Conclusion
Ce que vous avez appris
Le cadre de Dung (1995) dit qui attaque qui. Le cadre de Bench-Capon (2003) ajoute une couche critique : qui défait qui dépend de ce que valorise l’audience. Concrètement :
Une attaque\(x \to y\) n’est pas automatiquement une défaite : elle ne réussit que si la valeur promue par \(x\) est au moins aussi préférée (pour l’audience) que celle promue par \(y\).
Changer l’audience change la relation de défaite, donc change les extensions (grounded / preferred / stable). Sur le cycle canonique, trois audiences excluent trois arguments différents — la signature d’un débat sensible à l’audience.
Cas limite : si tous les arguments partagent une même valeur, le VAF dégénère en cadre de Dung pur (toutes les attaques réussissent). Le cadre de Dung est donc un cas particulier du VAF.
Prochaines étapes
Agrégation d’audiences → choix social : si chaque individu a sa propre audience, comment décider collectivement ? C’est le pont vers Tweety-9 (préférences) et GameTheory / SocialChoice (théorèmes d’impossibilité d’Arrow, votations). La question « existe-t-il une agrégation d’audiences qui préserve la rationalité ? » est un analogue direct du paradoxe de Condorcet.
Argumentation graduée : plutôt qu’un ordre total sur les valeurs, Argumentation-03c-Ranking-Semantics-Python attribue une force numérique à chaque argument — une autre façon de départager ce que Dung laisse indécidé.
Bipolarité : les VAF gèrent les préférences, mais restent attack-only. Les cadres bipolaires (Cayrol & Lagasquie-Schiex, 2005) ajoutent une relation de support — un prolongement naturel au-delà de ce notebook.
Référence
Trevor J. M. Bench-Capon, « A Value-Based Argumentation Framework », dans Proceedings of the European Conference on Logics in Artificial Intelligence (JELIA), 2003 — le papier fondateur du cadre VAF reconstruit ici.
Notebook pédagogique — pur stdlib Python, déterministe, sans LLM ni solveur externe. Conforme C.1 (stubs sans erreur volontaire), C.2 (sorties réelles), exercices à compléter.