{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Modèles d'influence bornée" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Sujet d'ARE Dynamic 2018 (Maximilien Danisch, Pierre Fournier, Arthur Guillon, Jean-Daniel Kant, Baptiste Lefebvre, Nicolas Maudet)*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Introduction" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Considérons un ensemble donné d'**individus** que l'on appelera **population**. Chacun de ces individus possède une **opinion**, c'est-à-dire un ensemble de convictions, de valeurs, de jugements, de préjugés et de croyances qui lui sont propres. Naturellement, nous pouvons nous attendre à ce que l'opinion d'un individu donné ne soit pas constamment la même au cours du temps. Au contraire, elle évolue en fonction des **rencontres** faites avec d'autres individus. Lorsque la population est suffisament grande, il est impossible qu'un individu fasse connaissance avec tout le monde. Dans ce cas, ses rencontres se font avec un sous-ensemble d'individus appartenant à cette population que nous appelerons ses **voisins**.\n", "\n", "Dans ce contexte, une question émerge principalement :\n", "\n", "* Comment évolueront les opinions des individus au cours du temps ?\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pour être en mesure de proposer une réponse scientifique à cette question, nous allons chercher à modéliser cette **dynamique des opinions**. Autrement dit, nous allons assembler des concepts pour représenter de manière simplifiée cette dynamique en vue de la comprendre et d’en prédire certaines caractéristiques.\n", "\n", "Pour commencer, deux questions de modélisations apparaissent immédiatement :\n", "\n", "* Comment modéliser l'opinion d'un individu ?\n", "* Comment modéliser le processus de mise à jour de l'opinion de cet individu ?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Dans la première partie de l'ARE, nous allons étudier un modèle en particulier : le **modèle d'influence bornée**. Ce modèle effectue les deux hypothèses suivantes :\n", "\n", "* L'opinion d'un individu $ i $ est une valeur réelle (comprise entre $ 0 $ et $ 1 $ inclus), que nous noterons $ x_i $.\n", "* La mise à jour consiste à faire évoluer l'opinion d'un individu $ i $, lorsqu'il rencontre un individu $ j $, en appliquant la formule : $ x_i \\leftarrow x_i + \\mu \\cdot ( x_j - x_i ) $.\n", "\n", "Où $ \\mu $ est appelé le **paramètre de convergence**. Ce paramètre permet de contrôler la vitesse de convergence des opinions. Sa valeur est comprise entre $ 0 $ et $ 0.5 $.\n", "\n", " \n", "\n", "\n", "\n", "\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pour mieux comprendre, prenons un exemple :" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def update_opinion(x, y, mu):\n", " \"\"\"Rule to use to update the opinions.\"\"\"\n", " \n", " xpost = x + mu * (y - x)\n", " ypost = y + mu * (x - y)\n", " \n", " return xpost, ypost" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "xpost: 0.4\n", "ypost: 0.7000000000000001\n" ] } ], "source": [ "x = 0.3\n", "y = 0.8\n", "mu = 0.2\n", "xpost, ypost = update_opinion(x, y, mu)\n", "\n", "print(\"xpost:\", xpost)\n", "print(\"ypost:\", ypost)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Question :** Que se passe-t-il lorsque le paramètre $ \\mu $ est égal à $ 0.5 $ ?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Question :** Que se passe-t-il lorsqu'il est égal à $ 0 $ ? " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Une hypothèse importante de notre modèle est donc que les mises à jour sont effectuées en **pair-à-pair**. Il s'agit d'un choix de notre part. Bien d'autres choix sont possibles ! Nous pourrions par exemple supposer qu'un individu choisisse un autre individu et adopte son opinion tel quel. Ou bien qu'il considère les opinions d'un ensemble d'autres individus (par exemple, ses **amis**) et adapte son opinion à la moyenne de leurs opinions.\n", "\n", "En effectuant un de ces autres choix nous obtiendrions une **variante** du modèle d'influence bornée. Ces variantes pourront donner lieu à des études dans la suite de l'ARE." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pour conclure cette introduction, quelque mots concernant ce à quoi nous pouvons nous attendre. En fin de simulation nous pourrons typiquement observer deux types de résultats :\n", "* Un état de **consensus** lorsque tous les agents possèdent la même opinion.\n", "* Un état de **polarisation** lorsque des opinions divergentes subsistent dans le système." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Modèle d'influence bornée" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Question :** Mais quelle est cette borne dont il est question dans le nom du modèle ?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Il s'agit en fait d'un paramètre qui permet de conditionner le changement ou non de l'opinion d'un individu. Deux individus changeront d'opinions si la différence entre leurs opinions n'est pas trop importante. Ou plus précisement si la condition suivante est vérifiée : $ | x_i - x_j | < c $. Le paramètre $ c $ est appelé le **seuil** et permet de borner l'influence entre les individus, d'où le nom donné au modèle." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Question :** Comment modifier la fonction précédente afin de prendre en compte ce nouveau paramètre ?" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def bounded_update_opinion(x, y, mu, c):\n", " \"\"\"Rule to use to update the opinions.\"\"\"\n", " \n", " # TODO to modify.\n", " xpost, ypost = update_opinion(x, y, mu)\n", " \n", " return xpost, ypost" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "xpost: 0.4\n", "ypost: 0.7000000000000001\n" ] } ], "source": [ "x = 0.3\n", "y = 0.8\n", "mu = 0.2\n", "c = 0.1\n", "xpost, ypost = bounded_update_opinion(x, y, mu, c)\n", "\n", "print(\"xpost:\", xpost)\n", "print(\"ypost:\", ypost)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Dynamique des opinions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "À ce stade, il reste une composante importante du modèle à préciser. Il s'agit d'une troisième question de modélisation :\n", "\n", "- Comment modéliser les rencontres faites par un individu (i.e. quand et avec qui) ?\n", "\n", "Le choix qui sera fait à ce sujet aura un grand impact sur la dynamique des opinions que nous observerons. Il s'agit donc d'une hypothèse importante de notre modèle.\n", "\n", "Nous supposons ici que le temps est discret. Les rencontres ont lieu à chaque pas de temps. Plus spécifiquement, nous considérons une dynamique **asynchrone** : chaque paire d'individus donne lieu à une mise à jour de leurs opinions lors de leur rencontres. (Il n'y a pas de synchronisation des mises à jour entre plusieurs paires d'individus).\n", "\n", "L'ordre des rencontres pourra être **aléatoire** (par exemple en tirant une paire d'agent de manière uniforme parmi toutes les paires d'agents possibles), ou **déterministe** et fixé à l'avance (par exemple en considérant un **processus round-robin**, dans ce cas chaque individu effectura une mise à jour avec tous ses voisins). \n", "\n", "Enfin, la dynamique peut être **spatialisée**, au sens où les agents ne peuvent rencontrer que certains autres agents. Par exemple, dans le premier exemple que nous considérons ci-dessous, les agents sont disposés sur un cercle: chaque agent peut donc rencontrer deux voisins. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Paramètres de votre modèle" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "En résumé, en plus de la dynamique choisie (i.e. règle de mise à jour et règles de réalisations des rencontres), les paramètres importants de notre modèle sont donc : \n", "* le nombre d'individu : `N`\n", "* le paramètre de convergence : `mu` \n", "* le seuil : `c`" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Global parameters\n", "nb_steps = 20\n", "N = 5 # number of individuals\n", "mu = 0.5 # convergence parameter\n", "c = 0.2 # threshold" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exemple : Dynamique aléatoire spatialisée sur un cercle" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Initialisation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Il est possible d'initialiser votre système en créant une liste avec des valeurs tirées de manière uniforme entre $ 0 $ et $ 1 $." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def init_system():\n", " \"\"\"Initialize the opinions of the individuals.\"\"\"\n", " \n", " state = np.random.uniform(low=0.0, high=1.0, size=N)\n", " \n", " return state" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "state: [ 0.28516173 0.53220575 0.54801303 0.31445409 0.27386621]\n" ] } ], "source": [ "state = init_system()\n", "print(\"state:\", state)" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "state[0]: 0.285161728465\n" ] } ], "source": [ "print(\"state[0]:\", state[0])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Sélectionner aléatoirement une paire d'agent" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Dans ce modèle on va supposer que les rencontres sont aléatoires (alternativement, on pourrait supposer qu'un agent rencontre tour à tour son voisin de droite et son voisin de gauche, par exemple)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def sample_pair_agents():\n", " \"\"\"Sample a pair of distinct consecutive agents uniformly.\"\"\"\n", " \n", " x = np.random.choice(N)\n", " i = x % N\n", " j = (x + 1) % N\n", "\n", " return i, j" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(i, j): (4, 0)\n" ] } ], "source": [ "print(\"(i, j):\", sample_pair_agents())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Passer à l'état suivant" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def next_step(state, verbose=False):\n", " \"\"\"Simulate the transition from one state to following one.\"\"\"\n", "\n", " i, j = sample_pair_agents()\n", "# next_state = state.copy()\n", " state[i], state[j] = bounded_update_opinion(state[i], state[j], mu, c)\n", " if verbose:\n", " print(\"Agents\", i, \"and\", j, \"met.\")\n", "\n", " return state" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Agents 3 and 4 met.\n", "[ 0.28516173 0.53220575 0.54801303 0.29416015 0.29416015]\n" ] } ], "source": [ "print(next_step(state, verbose=True))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Simulation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Et finalement, nous itérons un certain nombre de pas. " ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def simulate(verbose=False):\n", " \"\"\"Simulate multiple transitions.\"\"\"\n", " \n", " results =[]\n", " new_state = init_system()\n", " for i in range(nb_steps):\n", " new_state = next_step(new_state)\n", " if verbose:\n", " print(i, new_state)\n", " results.append(new_state.copy())\n", " \n", " return results" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 [ 0.56894978 0.54525414 0.54525414 0.06689983 0.3049582 ]\n", "1 [ 0.56894978 0.54525414 0.54525414 0.18592901 0.18592901]\n", "2 [ 0.3774394 0.54525414 0.54525414 0.18592901 0.3774394 ]\n", "3 [ 0.46134677 0.46134677 0.54525414 0.18592901 0.3774394 ]\n", "4 [ 0.46134677 0.50330045 0.50330045 0.18592901 0.3774394 ]\n", "5 [ 0.46134677 0.50330045 0.34461473 0.34461473 0.3774394 ]\n", "6 [ 0.41939308 0.50330045 0.34461473 0.34461473 0.41939308]\n", "7 [ 0.46134677 0.46134677 0.34461473 0.34461473 0.41939308]\n", "8 [ 0.46134677 0.40298075 0.40298075 0.34461473 0.41939308]\n", "9 [ 0.44036993 0.40298075 0.40298075 0.34461473 0.44036993]\n", "10 [ 0.44036993 0.40298075 0.40298075 0.34461473 0.44036993]\n", "11 [ 0.44036993 0.40298075 0.40298075 0.34461473 0.44036993]\n", "12 [ 0.44036993 0.40298075 0.37379774 0.37379774 0.44036993]\n", "13 [ 0.44036993 0.38838925 0.38838925 0.37379774 0.44036993]\n", "14 [ 0.44036993 0.38838925 0.38838925 0.37379774 0.44036993]\n", "15 [ 0.44036993 0.38838925 0.38838925 0.37379774 0.44036993]\n", "16 [ 0.44036993 0.38838925 0.38838925 0.37379774 0.44036993]\n", "17 [ 0.41437959 0.41437959 0.38838925 0.37379774 0.44036993]\n", "18 [ 0.41437959 0.40138442 0.40138442 0.37379774 0.44036993]\n", "19 [ 0.41437959 0.40138442 0.40138442 0.37379774 0.44036993]\n" ] }, { "data": { "text/plain": [ "[array([ 0.56894978, 0.54525414, 0.54525414, 0.06689983, 0.3049582 ]),\n", " array([ 0.56894978, 0.54525414, 0.54525414, 0.18592901, 0.18592901]),\n", " array([ 0.3774394 , 0.54525414, 0.54525414, 0.18592901, 0.3774394 ]),\n", " array([ 0.46134677, 0.46134677, 0.54525414, 0.18592901, 0.3774394 ]),\n", " array([ 0.46134677, 0.50330045, 0.50330045, 0.18592901, 0.3774394 ]),\n", " array([ 0.46134677, 0.50330045, 0.34461473, 0.34461473, 0.3774394 ]),\n", " array([ 0.41939308, 0.50330045, 0.34461473, 0.34461473, 0.41939308]),\n", " array([ 0.46134677, 0.46134677, 0.34461473, 0.34461473, 0.41939308]),\n", " array([ 0.46134677, 0.40298075, 0.40298075, 0.34461473, 0.41939308]),\n", " array([ 0.44036993, 0.40298075, 0.40298075, 0.34461473, 0.44036993]),\n", " array([ 0.44036993, 0.40298075, 0.40298075, 0.34461473, 0.44036993]),\n", " array([ 0.44036993, 0.40298075, 0.40298075, 0.34461473, 0.44036993]),\n", " array([ 0.44036993, 0.40298075, 0.37379774, 0.37379774, 0.44036993]),\n", " array([ 0.44036993, 0.38838925, 0.38838925, 0.37379774, 0.44036993]),\n", " array([ 0.44036993, 0.38838925, 0.38838925, 0.37379774, 0.44036993]),\n", " array([ 0.44036993, 0.38838925, 0.38838925, 0.37379774, 0.44036993]),\n", " array([ 0.44036993, 0.38838925, 0.38838925, 0.37379774, 0.44036993]),\n", " array([ 0.41437959, 0.41437959, 0.38838925, 0.37379774, 0.44036993]),\n", " array([ 0.41437959, 0.40138442, 0.40138442, 0.37379774, 0.44036993]),\n", " array([ 0.41437959, 0.40138442, 0.40138442, 0.37379774, 0.44036993])]" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "simulate(verbose=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Visualisation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Afin de visualiser la dynamique, il est possible ici de représenter la simulation sous la forme d'un nuage de points (c.f. [`matplotlib.pyplot.scatter`](https://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.scatter)). \n", "En abscisse, nous trouvons les différents pas de temps de la simulation.\n", "Le niveau d'opinion est représenté en ordonnée. " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "%matplotlib inline\n", "\n", "results = simulate()\n", "for i in range(nb_steps):\n", " x = i * np.ones(N)\n", " y = results[i]\n", " plt.scatter(x, y, s=2, c='C0')\n", " plt.xlabel(\"Iteration\")\n", " plt.ylabel(\"Opinion\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Variante 1 : Dynamique déterministe avec graphe complet" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Dans le cas du cercle, la dynamique étudiée était aléatoire, et les interactions fortement restreintes. \n", "\n", "Dans cette première variante, vous devez modifier ces deux hypothèses (en supposant que chaque agent effectue tour à tour une mise à jour avec tous les autres individus) et étudier le comportement de votre modèle en variant les autres paramètres du modèle. Plus précisément, vous pourrez considérer les variantes suivantes: \n", "* Chaque agent (dans l'ordre) rencontre tous les autres agents (dans l'ordre).\n", "* Un agent choisi au hasard (i.e. uniformément) rencontre tous les autres agents (dans l'ordre).\n", "* Un agent choisi au hasard (i.e. uniformément) rencontre tous les autres agents (dans le désordre).\n", "\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# TODO to complete." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Variante 2 : Dynamique aléatoire spatialisée sur une grille" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Pour cette seconde variante, vous supposerez que les individus sont disposés sur une grille à deux dimensions. Les rencontres doivent être effectuées de manière aléatoire. Vous devrez également obtenir une visualisation graphique de l'évolution des opinions (c.f. figure ci-dessous). " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![state-grid-uniform-convergence](state-grid-uniform-convergence.gif)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Indication** : Il est recommandé d'utiliser une matrice pour représenter le système." ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#TODO à compléter" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Aide** : Afin de visualiser la dynamique du système, on pourra utiliser sans le modifier le code suivant" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "collapsed": false }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig = plt.figure()\n", "\n", "# results[i] contient l'état au pas de temps i sous forme de matrice\n", "im = plt.imshow(results[0], animated=True)\n", "\n", "def updatefig(i):\n", " im.set_array(results[i+1])\n", " return im,\n", "\n", "ani = animation.FuncAnimation(fig, updatefig, frames=1000, interval=50, blit=True)\n", "HTML(ani.to_html5_video())\n", "# Pour sauvegarder la vidéo dans un fichier externe\n", "# ani.save('test.mp4', fps=30, extra_args=['-vcodec', 'libx264'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Extensions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Idées d'extensions possibles du modèle: \n", "* agents têtus (changent moins facilement d'avis) et/ou extrêmistes (initialisés avec une valeur 0/1)\n", "* influence du temps sur la dynamique" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "..." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## Références" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Le modèle d'influence bornée a été proposé par : \n", "* [Deffuant, Guillaume, et al. Mixing beliefs among interacting agents. Advances in Complex Systems 3.01n04 (2000): 87-98.](http://wwwlisc.clermont.cemagref.fr/imagesproject/finalreport/mixbel.pdf) \n", "* [Hegselmann, Rainer, and Ulrich Krause. Opinion dynamics and bounded confidence models, analysis, and simulation. Journal of artificial societies and social simulation 5.3 (2002).](http://jasss.soc.surrey.ac.uk/5/3/2/2.pdf)\n", "\n", "Dans le cas où les opinions des agents sont discrètes, en particulier binaires, on parle généralement de **modèle de votant** (ou **voter model** en anglais) (c.f.[Bryan Gillespie's app](https://math.berkeley.edu/~bgillesp/apps/voter/))." ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python [default]", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" } }, "nbformat": 4, "nbformat_minor": 2 }