% !TEX encoding = UTF-8 Unicode
% -*- coding: UTF-8; -*-
% vim: set fenc=utf-8
\documentclass[a4paper,12pt,french]{article}

\usepackage{ensps}

\hypersetup{
  pdftitle={Rapport de stage, Léopold Clément},
  pdfauthor={Léopold Clément},
  pdfsubject={Schémas de contrôle de congestion intelligents pour les réseaux IoT par apprentissage profond},
  pdfproducer={LuaLaTex},
  pdfkeywords={Quelques mots-clés} %
}

\DeclareGraphicsRule{.ai}{pdf}{.ai}{} % Pour insérer des documents .ai
\graphicspath{{./img/} {./eps/} {./fig/}} % Pour ne pas avoir à ajouter eps/ton-image.jpg

% ------------- Packages spéciaux, nécessaires pour ce rapport, à insérer ici ------------- 
\usepackage{minted}
\usepackage{lscape}
\usepackage{wrapfig}

\usepackage[%
    all,
    defaultlines=3 % nombre minimum de lignes
]{nowidow}


\newcommand{\code}[1]{\mintinline[breaklines, breakafter=.]{python}{#1}}

\usepackage[toc]{glossaries}
\makeglossaries

\newglossaryentry{iot}
{
  name=IoT,
  description={Internet of Things, Internet des objets}
}

\newcommand{\iot}{\gls{iot}}

\newglossaryentry{tcp}
{
  name=TCP,
  description={Transmission Control Protocol, protocole avancé de la couche \osi{} 4}
}

\newcommand{\tcp}{\gls{tcp}}

\newglossaryentry{udp}
{
  name=UDP,
  description={User Datagram Protocol, protocole basique de la couche \osi{} 4}
}

\newcommand{\udp}{\gls{udp}}

\newglossaryentry{coap}
{
  name=CoAP,
  description={Constrained Application Protocol, protocole de la couche \osi{} 7}
}

\newcommand{\coap}{\gls{coap}}

\newglossaryentry{cocoa}
{
  name=Cocoa+,
  description={Évolution de \coap{}, protocole de la couche \osi{} 7, voir \cite{shelbyConstrainedApplicationProtocol2014}}
}

\newcommand{\cocoa}{\gls{cocoa}}

\newglossaryentry{ns}
{
  name=ns-3,
  description={Simulateur réseau puissant. \url{https://www.nsnam.org/}}
}

\newcommand{\ns}{\gls{ns}}

\newglossaryentry{keras}
{
  name=Keras,
  description={Module Python pour la manipulation de réseaux de neurones}
}

\newcommand{\keras}{\gls{keras}}

\newglossaryentry{tensorflow}
{
  name=TensorFlow,
  description={Module Python pour l'apprentissage machine}
}

\newcommand{\TF}{\gls{tensorflow}}

\newglossaryentry{coapthon}
{
  name=CoAPthon,
  description={Module Python implémentant les clients et serveurs \coap}
}

\newcommand{\coapthon}{\gls{coapthon}}

\newglossaryentry{rasp}
{
  name=RaspberryPi,
  description={Modèle de micro-ordinateur}
}

\newcommand{\rasp}{\gls{rasp}}

\newglossaryentry{osi}
{
  name=OSI,
  description={Open Systems Interconnection, modèle des réseaux}
}

\newcommand{\osi}{\gls{osi}}

\newglossaryentry{gym}
{
  name=GYM,
  description={Norme de compatibilité pour les environnements de machine learning \cite{brockmanOpenAIGym2016}. \url{https://gym.openai.com/}}
}

\newcommand{\gym}{\gls{gym}}

\newglossaryentry{cc}
{
  name=CC,
  description={Contrôle de congestion}
}

\newcommand{\CC}{\gls{cc}}

\newglossaryentry{cca}
{
  name=CCA,
  description={Algorithme de contrôle de congestion}
}

\newcommand{\CCA}{\gls{cca}}

\newglossaryentry{mac}
{
  name=MAC,
  description={Media Access Control, Contrôle de l'accès au médium de communication}
}

\newcommand{\mac}{\gls{mac}}

\newglossaryentry{ip}
{
  name=IP,
  description={Internet Protocol}
}

\newcommand{\ip}{\gls{ip}}

\newglossaryentry{http}
{
  name=HTTP,
  description={Hypertext Transfer Protocol, protocole de la couche \osi{} 7}
}

\newcommand{\http}{\gls{http}}

\newcommand{\qlearn}{Q-learning}
\newcommand{\dqlearn}{Deep-Q-learning}
\newcommand{\cubic}{TCP-Cubic}
\newcommand{\newreno}{TCP-NewReno}
\newcommand{\cpp}{C++}
\newcommand{\lss}{\emph{L2S}}
\newglossaryentry{rtt}
{
  name=RTT,
  description={Round Trip Time, temps d'aller-retour}
}

\newcommand{\rtt}{\gls{rtt}}

\newglossaryentry{rto}
{
  name=RTO,
  description={Return TimeOut, temps d'attente maximal}
}

\newcommand{\rto}{\gls{rto}}

\begin{document}

% --------------------------------------------------------------
%                       Page de garde
% --------------------------------------------------------------

\begin{titlepage}
  \begin{center}

    \includegraphics[height = 3 cm]{logo_ENSPS_UPS.png}\\[1cm]

    {\large Département \emph{EEA}}\\[0.5cm]

    {\large Stage de \emph{M1}}\\[0.5cm]

    % Titre
    \rule{\linewidth}{0.5mm} \\[0.4cm]
    { \huge \bfseries Schémas de contrôle de congestion intelligents pour les réseaux IoT par apprentissage profond \\[0.4cm] }
    \rule{\linewidth}{0.5mm} \\[1.5cm]

    % Auteur(es) et encadrant(es)
    \noindent
    \begin{minipage}{0.4\textwidth}
      \begin{flushleft} \large
        \emph{Auteurs :}\\
        M.~Léopold \textsc{Clément}
      \end{flushleft}
    \end{minipage}%
    \begin{minipage}{0.4\textwidth}
      \begin{flushright} \large
        \emph{Encadrants :} \\
        M\up{me} Véronique \textsc{Vèque}, professeur des universités\\
        M\up{me} Lynda \textsc{Zitoune}, maîtresse de conférences
      \end{flushright}
    \end{minipage}

    \vfill

    % Date en fin de page
    {\large Version du\\ \today}

  \end{center}
\end{titlepage}

% --------------------------------------------------------------
%                    Table des matières 
% --------------------------------------------------------------

\thispagestyle{empty}
\hspace{0pt}
\vfill
\begin{center}
  \section*{Remerciements}
  Je remercie Lynda Zitoune et Véronique Vèque\\
  de m'avoir permis de effectuer ce stage dans leur équipe.
\end{center}
\vfill
\hspace{0pt}
\pagebreak

\thispagestyle{empty}
\tableofcontents
\pagebreak

% --------------------------------------------------------------
%                         Début du corps
% --------------------------------------------------------------
\printglossary[title=Glossaire, toctitle=Glossaire]

\pagebreak

\section{Introduction}

\begin{wrapfigure}{r}{0.30\textwidth} %this figure will be at the right
  \centering
  \includegraphics[width=0.30\textwidth]{png_img/cropped-L2S.png}
\end{wrapfigure}

Le Laboratoire des signaux et systèmes (\lss{}) est une unité mixe de recherche entre le \emph{CNRS}, l'université Paris-Saclay et l'école CentraleSupélec.
Les thématiques du laboratoire sont l'automatique, les traitements du signal et les télécommunications.
En particulier, le pôle télécoms et réseaux se penche sur des problèmatiques de tout niveau autour des réseaux informatiques, que ce soit de l'allocation de ressources, du routage, de la compression de données\dots
Ces recherches s'appuient sur un vaste socle de méthodes, que ce soit la théorie des jeux, du contrôle, des graphes, de l'optimisation ou de l'apprentissage machine.
L'équipe Réseaux, Optimisation Conjointe et codage de Source (ROCS) travaille sur deux thématiques, l'\iot{} et les services vidéo, c'est-à-dire l'exploitation optimale d'une connexion limitée.
C'est dans cette équipe que j'ai effectué mon stage.

La problématique étudiée est de déterminer comment intégrer un processus d'apprentissage dans la gestion de la congestion des réseaux \iot{}.
En particulier, nous nous concentrons sur le protocole \coap{}.
Nos objectifs sont \begin{itemize}
  \item nous familiariser avec les notions étudiées et le protocole \coap{},
  \item déterminer quelle métrique utiliser pour suivre l'état du réseau,
  \item détermination des scénarios pertinents
  \item simulation ou test de ces scénarios
\end {itemize}
Le stage se déroule entièrement en distanciel, avec des réunions hebdomadaires avec mes encadrantes, soit en présentiel dans les locaux de CentraleSupélec, soit sur Teams.

\subsection{Qu'est-ce qu'un réseau \iot{} ?}

Un réseau \iot{} est un réseau de machines informatiques embarquées.
Les machines qui composent ce réseau sont en majorité des machines peu puissantes : des capteurs, des actionneurs \dots, ils réalisent chacun une tâche simple, et sont soumis à de multiples contraintes, en particulier en termes de consommation énergétique.
Les réseaux \iot{} présentent donc des thématiques propres aux réseaux classiques (débit, latence, perte de données \dots), qui ont déjà été étudiées par le passé et pour lesquelles des solutions existent, et des thématiques nouvelles qui ne s'intègrent pas dans le modèle classique.

\subsubsection{Qu'est-ce qu'un réseau ?}

Un réseau informatique est un groupe de machines indépendantes que l'on connecte entre elles pour qu'elles échangent des informations.
Ces connexions se font au moyen de normes et de protocoles organisés dans un modèle à $7$ couches, le modèle \osi{}:

\begin{table}[htp]
  \centering
  \caption[modèle \osi{} général]{Le modèle \osi{} pour Internet, les couches principales.}
  \begin{tabular}{lll}
    \toprule
    Couche & Rôle & Protocole\\
    \midrule
    7 - Application & Utilisation finale & \http{}, ssh...\\
    4 - Transport & \CC{} \& multiplexage & \tcp{}, \udp{}\\
    3 - Réseau & Routage & \ip{}\\
    1 \& 2 - Physique & Support des communications & Ethernet, Wi-Fi...\\
    \bottomrule
  \end{tabular}
\end{table}

Les couches $1$ et $2$ permettent la communication entre deux machines directement reliées.
C'est ici qu'intervient l'adresse \mac{}.
La couche $3$ décrit le routage pour passer par des machines intermédiaires afin de voyager dans l'ensemble du réseau.
C'est ici qu'intervient l'adresse \ip{}.
La couche $4$ décrit les protocoles permettant d'établir une connexion entre deux applications sur les machines hôtes.
Cela inclut donc le multiplexage des données (c'est le port sur la machine hôte), ainsi que la gestion de la congestion.
Les couches $5$ et $6$ n'ont pas d'influence sur la suite, et sont donc négligées.
Elles permettent par exemple d'encrypter la connexion avec le \emph{SSL}.
La couche $7$ est la couche applicative, c'est-à-dire l'utilisation finale que l'on fait de la connexion.
Il peut s'agir de charger une page Internet, lire ses mails, regarder une vidéo, ou bien transmettre les mesures d'un capteur.
C'est cette dernière utilisation qui nous intéresse ici.

La qualité d'une connexion entre deux points peut être évaluée grâce à deux valeurs : \begin{itemize}
  \item Le délai, qui correspond à la durée écoulée entre le moment où l'application émettrice veut émettre le message et le moment où le message est reçu par l'application cliente.
  \item Le débit, qui correspond à la quantité de bits pouvant être envoyés chaque seconde entre les deux applications.
\end{itemize}

Ces deux grandeurs n'ont pas la même importance selon l'application.
Par exemple, pour envoyer une pièce jointe dans un mail, le délai n'est pas très important : c'est le débit qui compte pour que la transaction ne soit pas trop longue.
Par contre, dans le cas d'un jeu en ligne il y a peu de données à échanger mais il faut le faire avec le moins de délai possible pour que le jeu reste fluide.
Suivant l'application, il faut favoriser l'un ou l'autre, ou trouver un compromis satifaisant.

\subsubsection{Qu'est-ce que la congestion ?}

Malheureusement, les réseaux étant hétérogènes, on ne contrôle pas tous les paramètres lorsqu'on les utilise.
En effet, pour envoyer un paquet à une autre machine, il faut (dans le cas général) passer par plusieurs intermédiaires, les routeurs.
Lesdits routeurs ne doivent pas gérer qu'un seul flux, et tous les liens entre les routeurs n'ont pas la même capacité ; ainsi on se retrouve avec des accumulations de paquets dans certains routeurs.
L'accumulation se produit au niveau des goulots d'étranglement.
Les routeurs n'ont pas une mémoire infinie, ils ne peuvent donc pas laisser les queues d'envoi grandir indéfiniment.
Elles ont une taille limite, et il faut donc parfois ne pas traiter certains paquets lorsque le réseau est trop chargé.
Ce phénomène est appelé congestion.

On cherche à l'éviter, car un paquet non traité est un paquet perdu, et le message n'est pas complet pour le client.
La gestion de la congestion sera expliquée plus en détail dans la partie \ref{part_cc}.
La détection de la congestion se fait souvent à l'aide d'un accusé de réception : si l'accusé n'est pas reçu au bout d'un certain temps, on considère le message comme perdu.
Lorsque des paquets sont perdus à cause de la congestion, il faut les réenvoyer, en faisant attention de ne pas recongestionner le réseau.
Une grande partie des optimisations du \CC{} a lieu au niveau de ce temps d'attente, le \rto{}.

\subsubsection{Quelles sont les spécificités des réseaux \iot{} ?}

Dans un réseau \iot, les contraintes et applications sont différentes : \begin{itemize}
  \item On échange des messages courts,
  \item on veut minimiser la consommation énergétique des machines,
  \item les machines sont peu puissantes, et ne peuvent pas réaliser de calculs complexes.
\end{itemize}

Les technologies employées sont surtout des connexions sans fil à basse consommation, et à bas débit.
On peut citer les technologies comme le BLE, le 802.15.4, 6LoWPAN \dots %manque référence 

De plus, pour accéder aux ressources d'une autre machine, on ne peut pas utiliser l'\http{} comme sur un ordinateur de bureau ou un téléphone.
En effet, \http{} s'appuie sur \tcp{}, et induit donc beaucoup de messages supplémentaires.
Ainsi, puisqu'on échange de petits messages, on se retrouve avec plus d'en-tête que de contenu dans les couches physiques, et on consomme beaucoup de ressources pour rien.
Un nouveau protocole a donc été développé pour proposer des fonctionnalités similaires à \http{}, mais adaptées aux contraintes de l'\iot{}, le protocole \coap{}.

\subsection{Quelles sont les particularités du \coap{} ?}

\coap{} est un protocole de la couche $7$ de l'\emph{IETF} (Internet Engineering Task Force) \cite{shelbyConstrainedApplicationProtocol2014} offrant une interface similaire à l'\http{}\cite{shelbyConstrainedRESTfulEnvironments2012}.
L'interface en question est une interface \emph{RestFull} :\begin{itemize}
  \item Les ressources sont identifiées par des url, par exemple \url{coap://exemple.com/ressource/en/ligne},
  \item On peut interagir avec les ressources au moyen de plusieurs méthodes: \begin{itemize}
    \item GET : on demande le contenu de la ressource distante,
    \item POST, PUT : on crée ou remplace le contenu de la ressource distante,
    \item DELETE : on supprime la ressource.
  \end{itemize}
\end{itemize}
En plus de ces méthodes, qui ont leur équivalent chez \http{}, il y a la méthode OBSERVE.
Si un client demande à observer une ressource sur un serveur, le serveur lui enverra automatiquement la ressource à chaque fois qu'elle est modifiée.
Les ressources utilisées avec \coap{} ne sont pas les mêmes qu'en \http{}, on ne cherche pas à transférer une suite de centaines voire de milliers de paquets mais un seul.
A la congestion classique s'ajoute le risque de perte de paquets à cause des liaisons physiques, car les infrastructures sans fil sont soumises à de la perte aléatoire.

D'un point de vue technique, \coap{} a la particularité de reposer sur l'\udp{} et non sur le \tcp{}.
Ainsi le \CC{} est réalisé par la couche applicative et non par la couche transport.

Lorsque l'on envoie un message, il peut être de deux types : \begin{itemize}
  \item CON : confirmable, on demande un accusé de réception du message,
  \item NCON, non confirmable, on ne demande pas d'accusé de réception.
\end{itemize}
Les messages NCON ne sont donc pas réenvoyés s'ils sont perdus à cause de la congestion, car il n'y a pas moyen de la détecter.
Cela peut ne pas être un problème pour certaines applications.
Il y a aussi les messages de type ACK, qui acquittent les messages CON.
L'association entre le message CON et ACK se fait grâce à un token (ACK[0xe4] qui acquitte le message CON[oxe4]).

Ces derniers temps, de nouvelles solutions basées sur l'apprentissage machine se sont développées pour le \CC{} en \tcp{}.
Notre problèmatique est donc de savoir si de telles solutions sont applicables à \coap{}.

\subsection{Qu'est-ce que l'apprentissage par renforcement ?}

\begin{wrapfigure}{r}{0.40\textwidth} %this figure will be at the right
  \centering
  \includegraphics[width=0.40\textwidth]{png_img/Markov_Decision_Process_example.png}
  \caption{Exemple de processus markovien à trois états et deux actions.}
\end{wrapfigure}

L'apprentissage par renforcement est une solution d'apprentissage machine pour les processus markoviens.
Le système est constitué d'un environnement et d'un acteur.
On suppose que le temps est discontinu.
Ainsi à l'instant $t$, l'environnement est dans un état $e \in \mathbb{E}$.
L'acteur observe l'environnement au travers d'un interpréteur qui lui renvoie un vecteur $s \in \mathbb{S}$, le vecteur d'observation.
L'acteur a la possibilité de réaliser une action $a \in \mathbb{A}$ pour influencer l'environnement.
Suite à cette action, l'environnement change d'état et renvoie à l'acteur un nouveau vecteur d'observation et une récompense $r \in \R$.
Cette récompense permet de quantifier la qualité de l'action et du nouvel état.
Pour la suite, on notera $x ^ i$ la i-ème grandeur, et $x_t$ la grandeur à l'instant $t$.
Le but de l'acteur est de maximiser la récompense totale qu'il va recevoir au cours d'un épisode, c'est-à-dire une succession de pas de temps.
Pour cela, l'acteur doit créer une fonction $\pi : \mathbb{S} \rightarrow \mathbb{A}$ permettant de donner l'action optimale à réaliser pour chaque état.

La solution optimale est définie comme l'action permettant de maximiser le retour global : \begin{equation}
  R = \sum_{i = 1} ^\infty r _ i \gamma ^ i
  \label{eq:rl:R} 
\end{equation}

La méthode de base pour construire cette fonction est le \qlearn{}.
C'est un processus itératif où l'on va construire un tableau (la Q-table), noté $\mathbb{Q} : \mathbb{S} \times \mathbb{A} \rightarrow \R$ associant une valeur à chaque couple observation/action.
On parcourt l'environnement et à chaque pas de temps on met à jour la valeur de $\mathbb{Q}(s_t, a_t)$ grâce à la formule suivante : \begin{equation}
  \mathbb{Q}\left(s_t, a_t \right) := \left(1 - \alpha\right) \cdot \mathbb{Q}\left(s_t, a_t \right) + \alpha \cdot \left( r_t + \gamma \cdot max _ {a \in \mathbb{A}}  \mathbb{Q} \left((s_{t+1}, a \right)  \right)
  \label{eq:qlearn_update}
\end{equation}
Les deux paramètres sont : \begin{itemize}
  \item $\alpha$ le taux d'apprentissage, c'est-à-dire à quel point les nouvelles informations écrasent les anciennes,
  \item $\gamma$ l'horizon, qui permet de contrôler à quel point l'algorithme se préoccupe du futur.
\end{itemize}
La méthode pour déterminer l'action appropriée à chaque pas de temps est juste de prendre $argmax_{a \in \mathbb{A}}  \mathbb{Q}\left(s_t, a \right) $.
En réalité, on fait des actions aléatoires avec une probabilité faible pour explorer l'environnement.
Deux des limites de cette solution sont la taille en mémoire d'un tel tableau quand les ensembles $\mathbb{S}$ et $\mathbb{A}$ sont grands, et le temps qu'il faut pour que la convergence des valeurs du tableau ait lieu.
Pour pallier à cela, on peut approximer la fonction par un réseau de neurones, c'est le principe du \dqlearn{}.

\section{État de l'art\label{part_cc}}
Le \CC{} est un problème qui date du début des réseaux.
La première version de \tcp{} date de 1983, mais de nouveaux \CCA{} sont régulièrement publiés et mis en production.
Comme ordre d'idées, le \CCA{} le plus utilisé de nos jours, \cubic{}, a été inclus dans Linux en 2006, dans MacOS en 2014 et dans Windows en 2017.
Chaque \CCA{} présente des avantages et des inconvénients, et ne fait pas le même compromis entre délai et débit.

\begin{figure}[htp]
  \centering
  \includegraphics[width = 0.8\textwidth]{png_img/comp_tcp_xiao.png}
  \caption[Comparaison des \CCA{} en \tcp{}]{Comparaison de différents \CCA{} en \tcp{}. Figure issue de \cite{xiaoTCPDrincSmartCongestion2019}.}
  \label{fig:comp_cca_tcp}
\end{figure}

\subsection{Comment gère-t-on la congestion en \tcp{} ?}

Le \tcp{} suppose qu'il y a un grand nombre de paquets à transmettre.
Ainsi on décide d'une fenêtre de congestion, c'est-à-dire le nombre de paquets en attente d'acquittement.
Si un paquet n'est pas acquitté avant la fin d'un \rto{} fixe, on le renvoie.
Les \CCA{} ont pour rôle de gérer la taille de la fenêtre, et le \rto{} celui d'optimiser la connexion.

Les \CCA{} de \tcp{} sont nombreux (une classification partielle est lisible dans \cite{haileEndtoendCongestionControl2021}), ils ne seront donc pas tous décrits.
Le principe de base de la plupart des algorithmes est l'AIMD (additive-increase-multiplicative-decrease).
Par exemple \newreno{}, le prédecesseur de \cubic{}, augmente la taille de la fenêtre par pas de $1$ tous les \rtt{}, et multiplie la taille par $\frac{1}{2}$ en cas de détection de congestion.
L'évolution proposée par \cubic{} est de ne pas croître linéairement mais de suivre une courbe cubique pour revenir en douceur à la taille de fenêtre qui avait causé la congestion.
Dans les deux cas, à chaque retransmission d'un message, le \rto qui lui est accordé double.

D'autres \CCA{} utilisent les variations du \rtt{} pour estimer l'état de congestion du réseau.
Le \rtt{} est le temps qui sépare l'émission d'un paquet de la réception de l'acquittement.
Le \rtt{} est composé de deux parties : \begin{itemize}
  \item le temps de propagation, qui correspond au temps que met le signal à faire le trajet ; il est incompressible (sauf changement de route),
  \item le temps d'attente dans les queues, qui est le temps passé dans les queues des différents routeurs et qui dépend directement de la congestion du réseau.
\end{itemize}
Ainsi l'augmentation du \rtt{} est souvent signe que le réseau se congestionne.
Mais interpréter ces variations de \rtt{} et prendre la décision adéquate est trop complexe pour un algorithme classique, et certains ont déjà proposé des solutions utilisant de l'IA \cite{xiaoTCPDrincSmartCongestion2019,liQTCPAdaptiveCongestion2019,naDLTCPDeepLearningBased2019,lanDeepReinforcementLearning2019} .

\subsection{Comment gère-t-on la congestion avec l’implémentation classique de \coap{} ?}

Pour le \coap{}, la gestion de la congestion est différente, car on utilise une fenêtre unitaire ; c'est sur le \rto{} que se fait le travail.
Dans la version de base, le \rto{} de base est tiré aléatoirement entre $2 s$ et $3 s$, et est doublé à chaque retransmission.
C'est un protocole simple mais peu performant, car si le délai de transmission est déjà de l'ordre du \rto{}, alors il risque d'y avoir des retransmissions systématiques.

Une des évolutions du \coap{} est \cocoa{}, qui ne change que le \CCA{}\cite{ancillottiRTTBasedCongestionControl2018}.
C'est un algorithme inspiré de FAST-\tcp{} et Compound-\tcp{}.
Ici on met en place une estimation du \rtt{} à partir de la mesure du \rtt{} de chaque message.
A chaque réception d'un nouveau \rtt{} $r$, on met à jour l'estimation grâce aux formules suivantes :
\begin{equation}
  \begin{aligned}
    RTT_S &:= \alpha_S r + \left( 1 - \alpha_S \right) \cdot RTT_S\\
    RTT_L &:= \alpha_L r + \left( 1 - \alpha_L \right) \cdot RTT_S\\
    RTTVAR_L &:= \beta_L \left\| RTT_L - r \right\| + \left( 1 - \beta_L  \right) \cdot RTTVAR_L
  \end{aligned}
  \label{eq:cocoa:estimateur}
\end{equation}
Ensuite, on crée une fonction $T$ permettant de prendre une décision :
\begin{equation}
  T\left( \gamma \right) = RTT_L + \gamma RTTVAR_L
  \label{eq:cocoa:decision}
\end{equation}
On a donc $4$ cas pour prendre une décision : \begin{itemize}
  \item $RTT_S < T\left( -1 \right)$ : pas de congestion, on peut être beaucoup plus agressif,
  \item $T\left( -1 \right) \le RTT_S < T\left( +1 \right)$ : congestion faible, on se trouve à un point de fonctionnement correct, on ne change rien, mais on peut aussi être un peu plus agressif si besoin,
  \item $T\left( +1 \right) \le RTT_S < T\left( +2 \right)$ : congestion normale mais en augmentation, il faut être moins agressif pour ne pas congestionner le réseau,
  \item $T\left( +2 \right) \le RTT_S$ : congestion importante, le réseau est totalement congestionné, il faut être beaucoup moins agressif.
\end{itemize}
La seule contrainte est que $\alpha_S \gg \alpha_L$.
Le choix des autres valeurs, les seuils et les actions prises sont choisis empiriquement, on ne sait pas si elles sont optimales.

\subsection{Comment l'apprentissage machine prend-il place dans les \CCA{}?}
Pour pallier ces problèmes, plusieurs \CCA{} utilisant de l'apprentissage machine sont apparus : Q-\tcp{}\cite{liQTCPAdaptiveCongestion2019}, \tcp{}-Drinc\cite{xiaoTCPDrincSmartCongestion2019}\dots
L'idée derrière ces algorithmes est de déléguer la prise de décision à une IA qui serait capable de reconnaître les signes de congestion bien plus efficacement qu'un algorithme "fait-main" classique.
Plusieurs problèmatiques se présentent : \begin{itemize}
  \item La modélisation de l'environnement : \begin{itemize}
    \item quel est $\mathbb{S}$, c'est-à-dire comment représenter l'environnement pour l'acteur,
    \item quel est $\mathbb{A}$, c'est-à-dire qu'est-ce que l'acteur doit donner comme consigne,
    \item quel est $r_t$, c'est-à-dire qu'est-ce qu'une bonne action
  \end{itemize}
  \item mais aussi des problèmes de compétition multi-agents: \begin{itemize}
    \item comment être sûr que les ressources réseau sont équitablement réparties entre les agents,
    \item comment être sûr que les agents IA ne vont pas perturber les agents non-IA,
  \end{itemize}
  \item ainsi que des problèmes d'implémentation, car tout cela doit tourner sur une machine très peu puissante et incapable de faire des calculs complexes.
\end{itemize}
L'intégration de l'IA dans le \CC{} n'est encore qu'à l'état de tests, de balbutiements, et pourtant des résultats notables et encourageants sont déjà disponibles, comme l'indique la figure \ref{fig:comp_cca_tcp}, ce qui nous motive à tenter de développer des solutions similaires pour \coap{}.

\section{Modélisation choisie}
La première étape est de modéliser le système.
Notre modélisation s'appuie sur celle de \cite{xiaoTCPDrincSmartCongestion2019}.

\subsection{Quel cas d'utilisation nous est le plus favorable ?}
Pour notre travail, on se positionne dans une situation où un serveur central puissant voudrait récupérer des données sur un ensemble de capteurs.
Les contraintes pour les codes s'exécutant sur le serveur central (un serveur cloud généralement) et sur les capteurs sont radicalement différentes.
Le cloud peut exécuter le code qu'il veut avec beaucoup de puissance, alors que les capteurs doivent se limiter au maximum.
Le could a accès aux données de toutes les connexions alors que les capteurs n'ont à priori que les données de leur connexion.
On choisit de se placer dans le cas où on interagit avec un nombre fixe de capteurs, $N$.

\begin{figure}[htp]
  \centering
  \includegraphics[scale=0.9]{puml/cas_utilisation_coap_1.png}
  \caption[Diagramme de séquence de transaction \coap{}, cas 1]{Diagramme de séquence entre le capteur et le serveur \coap{} avec déclenchement de la transaction par le capteur.}
  \label{fig:seq_coap:1}
\end{figure}
\begin{figure}[htp]
  \centering
  \includegraphics[scale=0.9]{puml/cas_utilisation_coap_2.png}
  \caption[Diagramme de séquence de transaction \coap{}, cas 2]{Diagramme de séquence entre le capteur et le serveur \coap{} avec déclenchement de la transaction par le cloud et réponse dans l'acquittement.}
  \label{fig:seq_coap:2}
\end{figure}
\begin{figure}[htp]
  \centering
  \includegraphics[scale=0.9]{puml/cas_utilisation_coap_3.png}
  \caption[Diagramme de séquence de transaction \coap{}, cas 3]{Diagramme de séquence entre le capteur et le serveur \coap{} avec déclenchement de la transaction par le cloud et réponse dans un message séparé.}
  \label{fig:seq_coap:3}
\end{figure}

Lorsque l'on veut établir une connexion avec un capteur, il y a trois cas de figure, il faut donc choisir le plus avantageux.
Le cas 1 (figure \ref{fig:seq_coap:1}) permet de réagir à un événement, mais demande au capteur de prendre le rôle de gestionnaire du \CC{}, ce qui n'est pas le plus simple.
Les cas 2 (figure \ref{fig:seq_coap:2}) et 3 (figure \ref{fig:seq_coap:3}) permettent tous deux au cloud de prendre en charge le \CC{}, mais le cas 3 demande l'échange de bien plus de messages et sature plus vite le réseau.
On choisit donc de travailler dans le cas 2, qui permet d'avoir à la fois toutes les informations au niveau de l'acteur, et de la puissance de calcul.
Pour ce qui est de la gestion des événements, on peut imaginer que le cloud envoie régulièrement au capteur des consignes simples pour le \CC{}, même si nous n'allons pas traiter cette problématique.

\subsection{Quelles sont les variables que nous pouvons observer ?}

La grandeur la plus simple à se représenter est le \rtt{}.
Chaque envoi de message permet de récupérer un échantillon de \rtt{}.
Mais un problème se pose lorsque le \rto{} de base est plus faible que le \rtt{} à cause des retransmissions : on ne sait pas si l'acquittement vient de l'original ou de la retransmission, car ils ont le même token.
Par exemple, dans la figure \ref{fig:temp_coap:valide}, on a sans ambiguïté un \rto{} de $25$, mais dans la figure \ref{fig:temp_coap:invalide}, le \rto{} semble être $5$ alors qu'il est de $30$.
Ce problème peut être contourné en changeant le token dans les retransmissions, mais nous ne mett rons pas en place cette solution pour l'instant.

\begin{figure}[htp]
  \centering
  \includegraphics[scale = 0.7]{puml/temporal_transaction_valide.png}
  \caption{Diagramme temporel d'une transaction sans ambiguïté du \rtt{}.}
  \label{fig:temp_coap:valide}
\end{figure}

\begin{figure}[htp]
  \centering
  \includegraphics[scale = 0.7]{puml/temporal_transaction_invalide.png}
  \caption{Diagramme temporel d'une transaction avec ambiguïté du \rtt{}.}
  \label{fig:temp_coap:invalide}
\end{figure}

L'autre grandeur que l'on peut facilement mesurer est le nombre de retransmissions de chaque message.
Ces retransmissions peuvent être dues à la congestion mais aussi à des problèmes de routage ou à des erreurs de couches réseaux physiques.

\subsection{Comment représenter un état du système ?}

Une fois que l'on a mesuré une grandeur, il faut la transmettre à l'acteur.
Le premier problème est que l'acteur voit un temps discontinu, alors que les nouvelles mesures arrivent à des instants irréguliers.
On choisit donc de couper le temps en une série d'intervalles de longueur fixe.
Dans ces intervalles, on suppose que l'état du réseau est constant, et les valeurs utilisées par le \coap{} le sont aussi.
Il faut prendre une durée de l'intervalle qui soit plus longue que le \rtt{}, car ainsi on ne se retrouve pas dans un cas limite où un message part pendant l'intervalle $t$, avec les consignes de l'instant $t$, mais arrive dans l'instant $t+1$ et influe $s_{t+1}$ au lieu de $s_t$.
Il faut aussi que cette durée ne soit pas trop importante, car on ne change les consignes qu'à la fin de ces intervalles.
On nomme cet intervalle "intervalle de contrôle".

De plus, on travaille sur un réseau de capteurs qui renvoient leurs informations à un serveur cloud unique.
Puisque le serveur cloud centralise toutes les connexions, on choisit d'avoir un seul agent, qui donne des consignes pour toutes les connexions.

Pour la suite, on appelle "serveur \coap{}" la partie de programme s'exécutant sur les capteurs, et "clients \coap{}" les programmes s'exécutant sur le serveur cloud afin de récupérer les données des capteurs.
On remarque aussi qu'on ne fait pas la différence entre l'état du réseau et l'observation que l'on en fait.

\subsubsection{État d'un client seul.}
Pour commencer, il faut que chaque client \coap{} soit capable de représenter sa connexion.
Pour cela, on dispose de : \begin{itemize}
  \item le nombre de messages envoyés pendant l'intervalle de contrôle,
  \item et pour chaque message : \begin{itemize}
    \item le nombre de retransmissions,
    \item le \rtt{} mesuré.
  \end{itemize}
\end{itemize}
On construit ensuite les valeurs suivantes : \begin{itemize}
  \item $RTT_{min}$, le minimum du \rtt{} pendant les $n_m$ derniers intervalles de contrôle,
  \item $\overline{RTT}$, la moyenne du \rtt{} sur l'intervalle de contrôle,
  \item $\eta = \frac{RTT_{min}}{\overline{RTT}}$
  \item $\nu = \frac{n_{retransmission}}{n_{message}}$
  \item $VARRTT = \frac{RTT_S}{RTT_L}$ à partir des deux valeurs suivantes, que l'on met à jour à chaque nouvelle mesure du \rtt{} $r$ :\begin{align*}
  RTT_S &:= \alpha_S \cdot r + \left( 1 - \alpha_S \right) \cdot RTT_S \\
  RTT_L &:= \alpha_L \cdot r + \left( 1 - \alpha_L \right) \cdot RTT_L \\
  \alpha_S &\gg \alpha_L
\end{align*}
\end{itemize}

Ces valeurs sont plus représentatives que les mesures brutes.
$RTT_{min}$ permet d'estimer le temps de propagation des messages (on rappelle que $RTT = \tau_{propagation} + \tau_{queue}$).
Ainsi, $\eta$ permet d'estimer le pourcentage du temps passé dans les queues par les messages au cours de leur voyage.
Lorsque $\eta$ est proche de $1$, c'est que les queues sont presque vides, et lorsqu'il est proche de $0$, c'est qu'il représente la majorité du temps de trajet, ce qui est signe de congestion.
$\nu$ représente le taux de redondance des messages, il doit être le plus faible possible.
$\nu$ élevé signifie que on renvoie des messages, donc que les consignes ne sont pas adaptées.
Mais $\nu$ est aussi influencé par les défauts du réseau, donc ce n'est pas un indicateur parfait.
$VARRTT$ permet d'estimer l'évolution du \rtt{} en comparant deux filtrages des \rtt{} sur des échelles différentes.
$VARRTT > 1$ indique que la tendance est à la hausse des \rtt{}, et donc que la longueur des queues augmente, et $VARRTT < 1$ que la tendance est à la baisse.
Il faut tout de même que le filtrage $RTT_S$ se fasse sur plusieurs échantillons du \rtt{} (donc $\alpha_S \sim 0.1$) car la mesure du \rtt{} est très bruitée.

Une fois ces valeurs calculées, on construit le vecteur suivant, qui représente l'état du client $i$ : \begin{equation}
  s ^ {\left( i \right)}= \begin{bmatrix} \nu\\ \eta \\ VARRTT \end{bmatrix}
\label{eq:modele:vec}
\end{equation}

\subsubsection{État de l'ensemble des clients.}
Une fois que chaque client a généré son vecteur, il suffit de les concaténer pour créer une matrice où chaque ligne représente une des trois valeurs, et chaque colonne un client.
\begin{equation}
  S = \begin{bmatrix} s ^ {(0)} & \cdots & s^{(N)} \end{bmatrix}
  \label{eq:modele:mat}
\end{equation}
L'avantage d'une telle matrice est que l'on pourra utiliser une convolution dans le réseau de neurones.
Une autre possibilité, qui est de concaténer les matrices de plusieurs échantillon de temps successifs pour créer un tenseur à trois dimensions (ce qui permettrait d'avoir plus d'informations sur l'évolution temporelle des valeurs) n'a pas pu être testée.

\subsection{Comment l'acteur influence-t-il le client \coap{} ?}
La principale valeur permettant de contrôler le comportement du client est le \rto{}.
On choisit une solution où $\mathbb{A} = \R ^ N$.
Si on note $c_t$ le vecteur des $N$ \rto{} à l'instant $t$, et $a_t$ le vecteur d'action généré par l'acteur, alors : 
\begin{equation}
  c_{t+1} ^ {\left( i \right) }= c_t ^{\left( i \right)}\cdot \begin{cases}
    1 + a _ t ^ {\left( i \right)} & \text{si } a _ t ^{\left( i \right)} \ge 0 \\
    \frac{1}{1 - a _ t ^ {\left( i \right)}} & \text{si } a _ t ^{\left( i \right)} < 0
  \end{cases} 
\end{equation}
L'effet de cette action est visible dans la figure \ref{fig:modele:action}.
Cette action permet d'avoir la capacité de réagir rapidement avec des grandes actions, mais aussi d'avoir de la précision pour s'ajuster autour du point de fonctionnement.

\begin{figure}[htp]
  \centering
  \includegraphics[width = 0.7\textwidth]{png_img/gain_action.png}
  \caption[Modification du \rto{} par l'action.]{Modification du \rto{} par l'action, pour un seul client, en échelle logarithmique.}
  \label{fig:modele:action}
\end{figure}

D'autres choix sont possibles, par exemple $\mathbb{A} = \left\lbrace 0.1, 0.5, 0.8, 1, 1.25, 2, 10 \right\rbrace ^ N$, et $c ^ {\left( i \right))} _{t+1}  c_t ^ {\left( i \right))} \cdot a _ t ^ {\left( i \right))} $

\subsection{Comment quantifier la réussite de l'agent ?}
Une fois que l'on sait représenter l'état du réseau, il faut déterminer si cet état est favorable ou non.
Pour cela, il faut se demander ce que l'on veut comme caractéristiques.
Les critères choisis sont : \begin{itemize}
  \item Le délai est le plus petit possible, pour avoir un système réactif,
  \item il y a peu de retransmissions, pour ne pas encombrer le réseau et abuser des ressources énergétiques des capteurs,
  \item il y a très peu d'échecs, c'est-à-dire de messages qui dépassent le nombre de retransmissions maximal,
  \item le nombre de messages envoyés est équitablement réparti entre les clients.
\end{itemize}

L'équité est une grandeur permettant de mesurer si certains capteurs utilisent plus le réseau que d'autres.
Elle est définie par \cite{jainQuantitativeMeasureFairness1998} pour une grandeur quelconque $\left( x_i \right) \in {\left(\R ^ {+}\right)} ^ N$:
\begin{equation}
  f_A (x_i) = \frac{\left[ \sum_{k = 1}^N x_k \right] ^ 2 }{N \cdot \sum_{k = 1}^N x_k^2 }
  \label{eq:modele_fair_base}
\end{equation}
Ainsi un système totalement équitable (où tous les $x_i$ sont égaux) a une équité de $1$ et un système non-équitable a une équité d'autant plus proche de $0$ qu'il est inéquitable.
Mais cet indice d'équité ne prend pas en compte que certains utilisateurs ont besoin de plus de ressources que d'autres.
Ainsi on modifie l'équation \ref{eq:modele_fair_base} pour prendre en compte un besoin $\left( b_i \right) \in {\left(\R ^ {+}\right)} ^ N$ :
\begin{equation}
  f_A (x_i, b_i) = \frac{\left[ \sum_{k = 1}^N \frac{x_k}{b_k} \right] ^ 2 }{N \cdot \sum_{k = 1}^N \left( \frac{x_k}{b_k} \right)^2 }
  \label{eq:modele_fair_pond}
\end{equation}

Le schéma de récompense est donc, à partir des données du dernier intervalle de contrôle : 
\begin{equation}
  r = - \beta _ \nu \frac{\sum_{k=1}^N n^e _ k}{\sum_{k=1}^N n^t_k} - \beta_\text{echec} n_\text{echec} - \beta_{RTT} max_{k} \overline{RTT} _ k + \beta_\text{equite} \frac{\left[ \sum_{k = 1}^N \frac{n^e_k}{n^t_k} \right] ^ 2 }{N \cdot \sum_{k = 1}^N \left( \frac{n^e_k}{n^t_k} \right)^2 }
  \label{eq:modèle:récompense}
\end{equation}
Où $n^e$ est le nombre de messages envoyés, émission initiale et retransmissions, et $n^t$ le nombre de transactions, c'est-à-dire uniquement les émissions initiales.
$n_\text{echec}$ est le nombre de transactions ayant échoué.
Les facteurs $\beta$ permettent de pondérer l'effet des différentes composantes.

\subsection{Quel type d'agent utiliser ? \label{part:description_boucle}}
Maintenant que l'environnement est fixé, on peut déterminer comment l'acteur va s'intégrer.
On choisit le fonctionnement suivant : \begin{itemize}
  \item L'acteur détermine $a_t$ en fonction de $s_t$,
  \item les clients utilisent cet $a_t$ pour trouver leurs consignes $c_t$,
  \item ils traitent toutes les transactions avec ces consignes pendant l'intervalle de contrôle,
  \item à la fin de l'intervalle de contrôle, on fait la synthèse des données, on calcule $s_{t+1}$ et $r_t$,
  \item on enregistre $\left( s_t, a_t, r_t, s_{t+1} \right)$,
  \item on recommence avec $s_{t+1}$
\end{itemize}
Cela représente le fonctionnement normal du système.

Mais en plus de cela, il faut que l'on fasse s'optimiser la politique.
Pour cela on va procéder en deux temps : \begin{enumerate}
  \item le pré-entraînement,
  \item l'entraînement en position.
\end{enumerate}
Le pré-entraînement se déroule avant le déploiment du réseau de capteurs.
Il consiste à rendre la politique proche de ce qui est attendu pour qu'elle soit exploitable, même si elle n'est pas encore optimale.
Pour cela, il faut générer de l'expérience (les transitions $\left( s_t, a_t, r_t, s_{t+1} \right)$ que l'on a vues plus haut), et l'utiliser pour optimiser la politique.
Cette génération peut se faire de plusieurs manières, que on évoquera plus tard.
Une fois que la politique est correcte, on peut la déployer sur le réseau de capteurs réels.
Mais elle n'est pas fixe, puisqu'on continue à l'optimiser avec de l'expérience récoltée sur le réseau réel.

\subsection{Quelle politique $\pi$ est utilisée ?}
Au vu de la taille des espaces $\mathbb{S}$ et $\mathbb{A}$, on ne peut pas utiliser une Q-table car elle demanderait bien trop d'espace mémoire.
On utilise donc un réseau de neurones pour approximer la Q-table.
Les subtilités de cette approximation, ainsi que la discrétisation de $\mathbb{S}$ et $\mathbb{A}$, sont pris en charge par \TF{}.

Puisqu'on est en apprentissage permanent, il faut utiliser une politique $\epsilon$-glouton.
C'est-à-dire que pendant la majorité du temps on se trouve en phase d'exploitation, et on exécute l'action optimale.
Mais avec une probabilité $\epsilon$, on réalise une action aléatoire, afin de tester de nouvelles possibilités.
C'est important car on ne se trouve pas dans un système déterministe, donc il ne suffit pas de tester une fois toutes les transitions.

Pour ce qui est du réseau de neurones, on ne travaille pour l'instant qu'avec un réseau feed-forward, mais on peut envisager d'utiliser des réseaux plus complexes.

\subsection{Comment récolter de l'expérience ?}

La récolte de transitions est indipensable pour pouvoir optimiser la politique.
La récolte in situ est lente ; en effet, il faut généralement $10000$ transitions pour réaliser un pas d'optimisation.
On peut descendre jusqu'à $1000$, mais le résultat est moins stable.
De plus, avec une durée d'intervalle de contrôle de $30$s, on se retrouve à devoir attendre $8h$ par pas d'optimisation.
Il faut donc trouver des solutions plus rapides que l'exploration simple si on veut pouvoir faire une centaine de pas d'apprentissage pour atteindre une convergence.

\subsubsection{Par exploration en simulateur}
Une solution simple, en théorie, est d'utiliser un simulateur.
Le plus utilisé est \ns{}.
C'est un simulateur à événements discrets destiné à la simulation de réseaux informatiques permettant de travailler sur toutes les couches du modèle \osi{}.
Ainsi on peut entraîner le simulateur avec la topologie du réseau que l'on veut, et la qualité des connexions sans fil que l'on veut.
Malheureusment, c'est un simulateur complexe à utiliser : tous les scripts de simulation sont écrits en \cpp{}.
Par manque de temps et de compétence, les simulations sur \ns{} n'ont pas pu être réalisées, mais cela reste une piste à étudier pour le pré-entraînement.

\subsubsection{Par création artificielle de transitions\label{part:yaml}}

Une autre méthode est de créer des transitions artificiellement.
Pour cela, l'algorithme commence par fixer une charge sur le réseau $f$, c'est-à-dire la fréquence à laquelle chaque client interroge le capteur associé.
Selon cette charge, le réseau est plus ou moins congestionné.
Ensuite l'algorithme prend plusieurs consignes de \rto{} $c_n$, et pour chaque consigne, l'algorithme l'applique à l'agent et mesure le vecteur d'observation associé $s_n$ ainsi que $r_n$.
Puis pour chaque couple $(s_n, s_m)$, l'algorithme calcule l'action $a_{n , m}$ pour passer de $c_n$ à $c_m$.
L'algorithme peut ainsi construire une série de transitions $s_n, a_{n, m}, r_m, s_m$.
On recommence avec un autre $f$.

Ces transitions sont uniquements des approximations car elles ne tiennent pas réellement compte des effets de $s_t$ sur $s_{t+1}$ liés au temps de propagation des messages dans le système.
De plus elles supposent que la charge ne change pas sur le système.
Cela permet malgres tout d'aller plus vite dans la génération de transitions pour le pré-entraînement, car en $n$ intervalles de contrôle à $f$ fixe, on génère $n^2$ transitions.

\section{Implémentation de l'algorithme}
L'implémentation de l'algorithme est réalisée en Python.
Notre implémentation est une modifications d'un module existant, \coapthon{}, qui comprend déjà tous les codes pour réaliser un client ou un serveur \coap{}.
De plus, pour la partie apprentissage, on utilise \TF{}, un module répandu pour l'apprentissage machine.

\begin{figure}[htp]
  \centering
  \includegraphics[width = \textwidth]{puml/classe.png}
  \caption[Diagramme de classe]{Diagramme de classe de notre système. La partie apprentissage n'est pas représentée. Seuls les attributs et méthodes d'intérêt sont représentés.}
  \label{fig:imple:class}
\end{figure}

\subsection{Modules utilisés}
Les modules que nous utilisons ont leur propre interface et fonctionnement Internet, auxquels nous devont nous adapter.

\paragraph{\coapthon{}}, pour sa partie client, utilise deux objets (voir figure \ref{fig:imple:class}) : \begin{itemize}
  \item \mintinline{python}{coapthon.cient.helperclient.HelperClient}, qui permet d'avoir accès à des méthodes simples telles que \mintinline{python}{HelperClient.get(url)},
  \item \mintinline{python}{coapthon.cient.coap.CoAP}, qui implémente la gestion des retransmissions, que l'on doit modifier.
\end{itemize}

Il y a aussi une partie serveur, mais nous ne la modifions pas du tout.
C'est elle qui joue le rôle de serveur \coap{} sur les \rasp{}.

\paragraph{\TF{} et \keras{}}sont des modules \python{} fournissant les outils nécessaires à l'entraînement de modèles d'IA.
\TF{} s'occupe des fonctions hautes, avec les outils permettant de mettre en place la boucle d'apprentissage.
Pour cela, il a besoin que l'on mette l'environnement sous un format spécial : l'interface \gym{}.
Cette interface permet la standardisation des interfaces des environnements.
Pour cela, il faut définir une nouvelle classe, héritant de \code{tf_agents.environements.py_environement.PyEnvironement}, et surchargeant les attributs et méthodes suivantes : 
\begin{enumerate}
  \item \code{.action_spec}, avec un objet de \code{tf_agents.specs.array_spec}, pour décrire l'espace d'action $\mathbb{A}$,
  \item \code{.observation_spec}, avec un objet de \code{tf_agents.specs.array_spec}, pour décrire l'espace d'observation $\mathbb{S}$,
  \item \code{._reset()}, qui doit renvoyer un objet \code{tf_agents.trajectories.time_step.transition},
  \item \code{._step(action)}, qui doit renvoyer un objet \code{tf_agents.trajectories.time_step.transition} ou \code{tf_agents.trajectories.time_step.termination}.
\end{enumerate}
Les deux premiers membres permettent de signaler à l'agent quelles valeurs sont autorisées.
Le troisième permet de remettre l'environnement dans un état initial.
Le quatrième est le plus important, car il représente le passage d'un pas de temps.
Les transitions sont les objets $\left( s_t, a_t, r_t, s_{t+1} \right)$.

\keras{} quant à lui, permet l'utilisation et la manipulation des réseaux de neurones.
Puisqu'on n'utilise que des réseaux simples, les fonctionnalités du module ne seront pas abordées.


\subsection{Structure du code}

\begin{wrapfigure}{r}{0.40\textwidth} %this figure will be at the right
  \centering
  \includegraphics[width=0.40\textwidth]{puml/struc_base.png}
  \caption{Composants simplifiés de notre implémentation.}
\end{wrapfigure}

La figure \ref{fig:call_stack} résume les appels de fonctions au cours d'une requête \coap{}.
La modification du client \coapthon{} consiste en l'ajout d'un composant, le \code{SuperviseurLocal}.
Ce composant permet de changer la manière dont le \rto{} est choisi, et d'enregistrer les \rto{}.
Il y a ensuite un \code{SuperviseurGlobal} qui supervise tous les \code{SuperviseurLocal} du réseau pour générer les matrices d'état.
C'est aussi ce \code{SuperviseurGlobal} qui distribue les consignes à tous les \code{SuperviseurLocal}.

La procédure lors de l'envoi d'un message est visible dans la figure \ref{fig:call_stack}.
Les modifications réalisées ne sont que l'ajout d'appels de fonctions courtes pour enregistrer les timestamps de départ de transmission et retransmission, et la réception des acquittements/réponses.

\subsubsection{Modification de \coapthon{}}
On rajoute à la classe \code{client.CoAP} un membre, le \code{SuperviseurLocal}.
On modifie \code{client.CoAP.send_datagram} pour enregistrer l'envoi d'un datagram, transmission ou retransmission, en fonction du token utilisé.
On modifie aussi \code{client.CoAP.send_datagram._start_retransmission} pour que ce soit le superviseur qui donne le \rto{}.
On modifie finalement \code{client.CoAP.receive_datagram} pour enregistrer les réceptions de messages en fonction de leurs tokens.

Je définis plusieurs types de \code{SuperviseurLocal} : \begin{itemize}
  \item \code{SuperviseurLocalPlaceHolder}, qui est utilisé par défaut et n'a aucune fonctionn  alité, et qui réutilise la génération de \rto{} de base dans \coapthon{},
  \item \code{SuperviseurLocal}, qui prend en charge l'enregistrement des \rto{}, la moyenne et le minimum, et qui est capable d'utiliser un \rto{} déterminé par l'IA,
  \item \code{SuperviseurLocalFiltre}, qui prend aussi en charge le filtrage exponentiel pour générer $RTT_L$ et $RTT_S$.
\end{itemize}

C'est la classe \code{SuperviseurGlobal} qui fournit le statisticien permettant de générer les matrices d'état, ainsi que la distribution des consignes.
Elle réalise aussi le calcul des récompenses.

\subsubsection{Boucle d'apprentissage}
L'objet \code{Maquette} est celui qui intéragit avec l'agent IA.
La méthode la plus importante est \code{step(action)}, qui prend l'action déterminée par l'IA comme entrée, l'applique, attend la durée de l'intervalle de contrôle, puis renvoie l'état du système.
C'est donc cette méthode qui joue un rôle prédominant dans l'algorithme de la partie \ref{part:description_boucle}.

\begin{listing}[htp]
  \begin{minted}[frame=lines, 
    framesep=2mm,
    baselinestretch=1.2,
    fontsize=\footnotesize,
    linenos]{python}
def collecteur(maquette: MaquetteCoapEnv, policy):
    time_step = maquette.step(np.array(n_capteur*[0], dtype=np.float32))
    while True:
        action_step = policy.action(time_step)
        next_time_step = maquette.step(action_step.action)
        traj = trajectory.from_transition(
            time_step, action_step, next_time_step)
        with buffer_lock:
            replay_buffer.add_batch(traj)
        time_step = next_time_step
        if traj.is_boundary():
            maquette.reset()
  \end{minted}
  \caption{Code du collecteur d'expérience.}
  \label{lst:collecteur}
\end{listing}

\begin{listing}[htp]
  \begin{minted}[frame=lines, 
    framesep=2mm,
    baselinestretch=1.2,
    fontsize=\footnotesize,
    linenos]{python}
while True:
    time.sleep(60)
    with buffer_lock:
        if replay_buffer.num_frames() >= 1000:
            experience = replay_buffer.gather_all()
            train_loss = tf_agent.train(experience)
            replay_buffer.clear()
            tf_policy_saver.save(policy_dir)
            n_epoch += 1
  \end{minted}
  \caption{Code de la boucle du thread principal.}
  \label{lst:boucle_principale}
\end{listing}


Le code \ref{lst:collecteur} implémente le collecteur d'expérience.
Le collecteur est exécuté dans un thread à part.
On lui donne comme paramètres une maquette sur laquelle travailler, et la politique \code{policy}.
Cette politique est déterminée par un autre thread.
Les transitions sont stockées dans le \code{replay_buffer}.
De plus, pour aller plus vite dans la collecte de transitions, et pour saturer plus rapidement le réseau, on utilise plusieurs maquettes en parallèle, chacune dans un thread.

\subsection{Maquette physique utilisée\label{part_maquette}}

La maquette physique que nous utilisons est constituée de deux machines : \begin{itemize}
  \item Un PC de bureau équipé d'un intel i5gen10, de 32 Go de RAM et d'un adaptateur Wi-Fi ax,
  \item un \rasp 3B+.
\end{itemize}
Les deux machines sont connectées par un réseau Wi-Fi $2.5 GHz$ géré par le PC.
La connexion est bien trop bonne pour pouvoir être saturée par un système CoAP classique seul, d'où l'utilisation de plusieurs maquettes numériques en même temps sur le modèle physique.

\subsection{Scénario d'intérêt}
Les scénarios d'intérêt sont : \begin{itemize}
  \item un réseau peu congestionné,
  \item un réseau congestionné,
  \item la transition de l'un à l'autre.
\end{itemize}
Mais il faut prendre en compte que l'on peut mettre des charges différentes sur chaque capteur.
Ainsi on peut avoir peu de requêtes sur tous les capteurs sauf un qui en a beaucoup plus.
La charge sur le réseau est définie par la fréquence à laquelle chaque client émet des requêtes.

\section{Expérience et résultats}
Pour commencer, on fait quelques tests en local, c'est-à-dire que le serveur \coap{} se trouve sur la même machine que le client.
On obtient les résultats de la figure \ref{fig:res:rtt_local}.
On constate que même en local certains messages sont acquittés avec du retard.
Certains ont un \rtt{} deux fois plus grand que la moyenne.
On peut aussi observer que $RTT_S$ a bien plus de variations brusques que $RTT_L$.

\begin{figure}[htp]
  \centering
  \includegraphics[width=0.7 \textwidth]{png_img/distri_rtt_local.png}
  \caption[Distribution du \rtt{} et évolution de $RTT_L$ et $RTT_S$ avec un seul client en local.]{Distribution du \rtt{} et évolution de $RTT_L$ et $RTT_S$ pendant un envoi de message sans délai entre la réception d'un message et l'envoi du suivant, avec un seul client en local.}
  \label{fig:res:rtt_local}
\end{figure}

\subsection{Etude de la maquette}
On commence par essayer de mesurer les limites du système.
Sur la figure \ref{fig:res:evo_grandeur}, on regarde les grandeurs que l'on va donner à l'agent en fonction du nombre de requêtes simultanées que doit traiter le serveur.
On utilise le \CCA{} de \coap{} de base.
Pour générer $n$ requêtes, on prend $n$ clients qui envoient une nouvelle requête à la reception de l'ancienne.
On constate que les grandeurs semblent toutes bruitées de manière non négligeable.
On constate aussi que le taux de retransmission semble suivre un palier.
Le rapport $\eta$ semble plus sensible, et suit une courbe continue, presque une exponentielle décroissante.
Cela indique que très rapidement le temps passé dans les queues est bien plus important que le temps de propagation.
$VARRTT$ par contre n'est pas pertinente à regarder ici, car elle mesure l'évolution du système.
Pourtant, le niveau de congestion semble avoir un impact sur la grandeur que nous ne savons pas expliquer.
En regardant la charge de calcul sur le \rasp{}, on constate que la limite créant la congestion n'est pas la saturation de la connexion Wi-Fi entre les deux machines, mais la puissance de calcul du \rasp{}, qui n'arrive pas à traiter les requêtes.

On peut aussi reproduire \ref{fig:res:rtt_local} mais en utilisant un seveur sur le \rasp{}.
Sur la figure \ref{fig:res:rtt_distant}, on voit que la distribution des \rtt{} est similaire, mais que certaines mesures sont très éloignées de la moyenne.
Si on tentait de réduire le \rto{}, ces messages seraient retransmis alors qu'ils ne sont pas réellement perdus.

\begin{figure}[htp]
  \centering
  \includegraphics[width=0.7\textwidth]{png_img/n_client_saturation.png}
  \caption[Évolution des grandeurs mesurées en fonction du nombre de requêtes simultanées]{Évolution des grandeurs mesurées en fonction du nombre de requêtes simultanées traitées par le serveur \coap{} tournant sur un \rasp{}. Le \CCA{} de \coap{} de base est utilisé.}
  \label{fig:res:evo_grandeur}
\end{figure}

\begin{figure}[htp]
  \centering
  \includegraphics[width=0.7 \textwidth]{png_img/distri_rtt_distant.png}
  \caption[Distribution du \rtt{} et évolution de $RTT_L$ et $RTT_S$ avec un seul client.]{Distribution du \rtt{} et évolution de $RTT_L$ et $RTT_S$ pendant un envoi de messages sans délai entre la réception d'un message et l'envoi du suivant, avec un seul client et un serveur sur le \rasp{}. Même si on ne les distingue pas sur l'histogramme, il y a quelque valeurs de \rtt{} très élevées.}
  \label{fig:res:rtt_distant}
\end{figure}

\begin{figure}[htp]
\centering
\includegraphics[width=0.7 \textwidth]{png_img/varrtt_demo.png}
\caption[Évolution de $VARRTT$ lors d'un changement de charge du réseau.]{Évolution de $VARRTT$ lors d'un changement de charge du réseau au moment du $500$-ième message.}
\label{fig:res:varrtt}
\end{figure}

On peut aussi faire un essai pour voir le profil de $VARRTT$ en cas de changement de charge sur le réseau.
On place d'abord un unique client sur le réseau, et on regarde l'évolution de $VARRTT$.
Au $500$-ieme message, on active $20$ autres clients qui vont eux aussi envoyer des requêtes au serveur.
Dans la figure \ref{fig:res:varrtt}, on constate qu'il y a bien un pic au moment de l'augmentation de la charge du système, mais il y a aussi des pics dus au bruit dans la mesure du \rtt{}.

\subsection{Tests d'apprentissage}

Les tests de pré-apprentissage ont été réalisés avec : \begin{itemize}
  \item des réseaux de $25$ capteurs, ce qui est un ordre de grandeur du nombre de capteurs dans le type de réseaux cibles,
  \item avec $8$ maquettes en parallèle pour accélérer la récupération d'expérience,
  \item des intervalles de contrôle de $30$ secondes,
  \item un facteur d'apprentissage de $\alpha = 0.01$.
\end{itemize}
On obtient donc un pas d'entraînement environ toutes les heures.
Après $72$ heures d'entraînement, soit $70$ pas d'entraînement, on n'a toujours pas de résultat stable.
En effet, les \rto{} ont des comportements erratiques, ils convergent vers $0$ ou divergent vers $+\infty$.
On se retrouve donc avec un taux d'échec important, le réseau n'est pas exploitable.

Les décisions prises par l'agent non-entraîné sont trop aléatoires pour qu'il puisse maintenir un état correct du réseau.
Il faudrait attendre longtemps avant qu'un épisode stable se forme par hasard et que l'agent puisse exploiter la stratégie correcte.
Cela montre bien que l'entraînement sur simulateur est indispensable car le pré-entraînement sur modèle réel n'est pas assez performant.

\begin{figure}
  \centering
  \includegraphics[width=\textwidth]{png_img/charge-rtts.png}
  \caption[Répartition de la \rtt{} et du taux de retransmission en fonction de la charge du réseau.]{Répartition de la \rtt{} et du taux de retransmission en fonction de la charge du réseau. On ne le voit pas clairement, mais en bas à gauche de chaque figure il y a une zone très dense.}
  \label{fig:res:yaml}
\end{figure}
Pour pallier cela, une méthode pour créer de l'expérience plus rapidement a été tentée mais n'a pu être finalisée.
Néanmoins, grâce aux données récupérées, on peut tracer la figure \ref{fig:res:yaml}, qui montre bien qu'il y a une charge critique à partir de laquelle le \rtt{} varie beaucoup.


\section{Conclusion}
La modélisation que nous proposons est une transposition de méthodes existantes dans un nouveau domaine.
L'apprentissage machine a déjà montré qu'il était capable de résoudre des problèmes liés aux réseaux et au \CC{}.
Nous avons proposé ici une nouvelle modélisation pour intégrer ces nouveaux outils à des problématiques de l'\iot{}.
On ne s'intéresse plus à une connexion seule mais à l'ensemble des connexions vers tout un réseau de capteurs.
On ne mesure que le \rtt{} de chaque message, ainsi que le nombre de retransmissions.
On construit ensuite une matrice qui représente l'état du réseau durant un intervalle de contrôle.
Cette matrice est utilisée par un agent IA pour déterminer l'action à prendre.
Cette action est une liste de facteurs multiplicatifs à appliquer aux \rto{} de chaque capteur.
La difficulté est que l'on ne peut pas récupérer assez d'expérience pour réaliser un pré-entraînement.
Pour cela, il faudrait un simulateur.
La solution de la partie \ref{part:yaml} n'a pas pu être testée.

Par manque de temps et de capacité, ledit simulateur n'a pas pu être réalisé.
Parmi les autres éléments à perfectionner, il y a les valeurs de pondération utilisées dans la fonction de récompense, qui sont pour l'instant arbitraires.
De même, l'agent est pour l'instant assez basique, on pourrait utiliser une structure de réseau de neurones plus complexe pour prendre en compte les particularités de la matrice d'état.
Le travail réalisé sera repris dans une thèse du \lss{}.

\clearpage

\listoffigures

\listoftables

\listoflistings

% --- Biblio par .bib
\bibliography{rapport.bib}
%\bibliographystyle{plain-fr.bst}
\selectlanguage{french}

% --- Biblio écrite à la main
%\begin{thebibliography}{7}
%\bibitem[Bastien 2019]{id-de-la-source}
%Auteurs : \emph{Un titre},  une date.
%\end{thebibliography}

% --------------------------------------------------------------
%                            Appendices
% --------------------------------------------------------------

\clearpage

\appendix
\begin{appendices}

  \section{Correspondance modélisation/code}

  \begin{table}[htp]
    \centering
    \caption{Correspondance modélisation/code}
    \begin{tabular}{lll}
      \toprule
      Nom & Mathématique & Informatique\\
      \midrule
      \rtt & $RTT$ & \code{superviseur.RTTs}\\
      & $RTT_{min}$ & \code{superviseur.min_RTT}\\
      & $\overline{RTT}$ & \code{superviseur.avg_RTT}\\
      & $RTT_L$ & \code{superviseur.RTT_L}\\
      & $RTT_S$ & \code{superviseur.RTT_S}\\
      Taux retransmission & \nu & \code{superviseur.taux_retransmission}\\
      \rto{} & $RTO$ & \code{superviseur.RTO}\\
      \bottomrule
    \end{tabular}
  \end{table}

  \begin{landscape}
    \section{Structure du code}
    \begin{figure}[htp]
      \centering
      \includegraphics[height=\vsize, width=\hsize, keepaspectratio]{puml/call_stack_envoie.png}
      \caption[Appel de fonction pour une transaction]{Appel de fonction pour une transaction avec \coapthon{} et ses modifications. Chaque couleur représente un thread d'exécution.}
      \label{fig:call_stack}
  \end{figure}
  \end{landscape}

\end{appendices}


% --------------------------------------------------------------
%                            Abstract
% --------------------------------------------------------------
\pagebreak
\thispagestyle{empty}

\vspace*{\fill}
\noindent\rule[2pt]{\textwidth}{0.5pt}\\
{\textbf{Abstract :}}

Congestion control is the bottleneck of network throughput.
For a few years, deep-learning has beeing used in congestion control algorithms.
We aim to apply deep-learning to congestion control and \coap{}, an embbeded system protocol.
The environement chosen is a whole network of sensors, this way the AI agent has access to every connection information.
The only inputs used are the \rtt{} and number of retransmission of every transaction.
Our protocol uses a pre-training phase in a \ns{}, then an inline training to tune itself to the network.
The protocol was implemented in \python{} as a patch to \coapthon{}.
We tried to test the protocol on a physical system, a \rasp{}.

{\noindent\textbf{Mots clés :}}
CongestionControl, \coap{}, Deep-Learning
\\
\noindent\rule[2pt]{\textwidth}{0.5pt}
\begin{center}
	\ens \\
	4, avenue des Sciences\\
	91190 Gif-sur-Yvette
\end{center}
\vspace*{\fill}

\end{document}
