AI & GPU
Comment démarrer avec Apache Airflow : Un guide complet

Comment démarrer avec Apache Airflow : Un guide complet

Misskey AI

Airflow dans l'ingénierie des données

Automatisation des pipelines de données

Apache Airflow est une puissante plateforme open-source qui est devenue un choix incontournable pour les ingénieurs de données et les data scientists. Au cœur de son fonctionnement, Airflow excelle dans l'automatisation et l'orchestration des pipelines de données, permettant aux équipes de rationaliser leurs workflows de données et d'atteindre une plus grande efficacité.

L'un des principaux cas d'utilisation d'Airflow dans l'ingénierie des données est la planification et l'orchestration des workflows ETL (Extraction, Transformation, Chargement). Le concept fondamental d'Airflow, les "Directed Acyclic Graphs" (DAG), permet aux ingénieurs de données de définir des dépendances complexes entre les tâches, garantissant que les données sont traitées dans le bon ordre et que les tâches préalables sont terminées avant l'exécution des tâches en aval.

Par exemple, considérons un pipeline de données typique qui ingère des données à partir d'une base de données relationnelle, les transforme, puis les charge dans un entrepôt de données. En utilisant Airflow, vous pouvez définir ce workflow sous forme de DAG, avec des tâches pour extraire les données de la base de données, les transformer et les charger dans l'entrepôt de données. Airflow se chargera alors de la planification et de l'exécution de ces tâches, garantissant que le pipeline fonctionne de manière fiable et selon un calendrier prédéfini.

from airflow import DAG
from airflow.operators.python_operator import PythonOperator
from airflow.operators.postgres_operator import PostgresOperator
from datetime import datetime, timedelta
 
def extract_data():
    # Code pour extraire les données d'une base de données relationnelle
    pass
 
def transform_data():
    # Code pour transformer les données extraites
    pass
 
def load_data():
    # Code pour charger les données
 
# Chargez les données transformées dans un entrepôt de données
    pass
 
default_args = {
    'owner': 'airflow',
    'depends_on_past': False,
    'start_date': datetime(2023, 4, 1),
    'email_on_failure': False,
    'email_on_retry': False,
    'retries': 1,
    'retry_delay': timedelta(minutes=5)
}
 
with DAG('data_pipeline', default_args=default_args, schedule_interval=timedelta(days=1)) as dag:
    extract = PythonOperator(
        task_id='extract_data',
        python_callable=extract_data
    )
 
    transform = PythonOperator(
        task_id='transform_data',
        python_callable=transform_data
    )
 
    load = PostgresOperator(
        task_id='load_data',
        postgres_conn_id='my_postgres_conn',
        sql='INSERT INTO my_table SELECT * FROM staging_table;'
    )
 
    extract >> transform >> load

Dans cet exemple, nous définissons un DAG avec trois tâches : extract_data, transform_data et load_data. Les tâches sont connectées de manière séquentielle, assurant que les données sont traitées dans le bon ordre. Airflow se chargera alors de la planification et de l'exécution de cette pipeline, en l'exécutant quotidiennement (comme spécifié par le paramètre schedule_interval).

Au-delà de la simple planification et de l'orchestration des tâches, Airflow offre également des capacités puissantes pour gérer les dépendances et l'ordre des tâches. Cela est particulièrement important dans les pipelines de données complexes où les tâches peuvent avoir des dépendances complexes les unes envers les autres. La structure de DAG d'Airflow vous permet de définir ces dépendances explicitement, en vous assurant que les tâches ne sont exécutées que lorsque leurs prérequis ont été satisfaits.

De plus, Airflow propose des fonctionnalités robustes de surveillance et d'alerte en cas de défaillance de pipeline. En s'intégrant à divers canaux de notification, tels que le courrier électronique, Slack ou PagerDuty, Airflow peut alerter proactivement les ingénieurs de données lorsqu'une tâche échoue ou lorsqu'un pipeline rencontre des problèmes. Cela permet une réponse et une résolution rapides, minimisant l'impact des défaillances de pipeline sur les processus en aval.

Intégration avec Vario.

Une des forces d'Airflow est sa capacité à s'intégrer à une large gamme de sources de données, y compris des bases de données, des API et des plateformes de stockage cloud. Cette flexibilité permet aux ingénieurs de données de construire des pipelines de données complets qui s'étendent sur plusieurs sources et formats de données.

Par exemple, vous pouvez utiliser les opérateurs intégrés d'Airflow pour vous connecter à des bases de données comme PostgreSQL, MySQL ou Snowflake, et effectuer des tâches telles que l'extraction de données, l'exécution de requêtes SQL et le chargement de données. Airflow fournit également des opérateurs pour interagir avec des services de stockage cloud comme Amazon S3, Google Cloud Storage et Azure Blob Storage, vous permettant d'ingérer, de traiter et de stocker des données sur ces plateformes.

from airflow.providers.google.cloud.operators.bigquery import BigQueryCreateEmptyTableOperator, BigQueryInsertJobOperator
 
with DAG('bq_pipeline', default_args=default_args, schedule_interval=timedelta(days=1)) as dag:
    # Créer une table vide dans BigQuery
    create_table = BigQueryCreateEmptyTableOperator(
        task_id='create_bq_table',
        dataset_id='my_dataset',
        table_id='my_table',
        bigquery_conn_id='my_gcp_conn',
        dag=dag
    )
 
    # Charger des données dans la table BigQuery
    load_data = BigQueryInsertJobOperator(
        task_id='load_data_to_bq',
        configuration={
            "query": {
                "query": "SELECT * FROM my_source_table",
                "useLegacySql": False
            }
        },
        destination_dataset_table='my_dataset.my_table',
        bigquery_conn_id='my_gcp_conn',
        dag=dag
    )
 
    create_table >> load_data

Dans cet exemple, nous utilisons les opérateurs BigQuery d'Airflow pour créer une table vide dans BigQuery, puis charger des données dans cette table. Les opérateurs BigQueryCreateEmptyTableOperator et BigQueryInsertJobOperator nous permettent d'interagir avec Google BigQuery, l'une des solutions de data warehouse cloud les plus populaires.

Au-delà de la simple connexion aux sources de données, Airflow excelle également dans la gestion de formats de données hétérogènes. Que vous travailliez avec des données structurées dans data. Les bases, les données semi-structurées dans des fichiers JSON ou XML, ou même les données non structurées comme le texte ou les images, Airflow fournit une gamme d'opérateurs et d'outils pour ingérer, transformer et traiter ces sources de données.

Par exemple, vous pouvez utiliser l'opérateur PythonOperator d'Airflow pour écrire une logique de transformation de données personnalisée, en tirant parti de bibliothèques comme Pandas, Spark ou dbt pour nettoyer, enrichir et préparer les données pour les cas d'utilisation en aval. Cette flexibilité permet aux ingénieurs de données de construire des pipelines de données complexes et de bout en bout qui s'intègrent en douceur avec diverses sources et formats de données.

Mise à l'échelle et parallélisation du traitement des données

Alors que les volumes de données ne cessent d'augmenter, la capacité à mettre à l'échelle et à paralléliser le traitement des données devient de plus en plus importante. L'architecture d'Airflow est conçue pour gérer des charges de travail de données à grande échelle, en tirant parti de ses capacités de parallélisme des tâches et d'optimisation des ressources.

L'une des principales fonctionnalités qui permet la mise à l'échelle dans Airflow est son parallélisme des tâches. Airflow vous permet de définir des tâches qui peuvent être exécutées de manière concurrente, utilisant ainsi de manière efficace les ressources de calcul disponibles. Cela est particulièrement utile lorsque vous avez des tâches indépendantes au sein d'un pipeline qui peuvent être exécutées en parallèle, comme l'ingestion de données à partir de plusieurs sources ou les transformations de données parallèles.

from airflow.operators.python_operator import PythonOperator
 
def process_data(partition_date):
    # Code pour traiter les données pour la date de partition donnée
    pass
 
with DAG('parallel_pipeline', default_args=default_args, schedule_interval=timedelta(days=1)) as dag:
    for partition in ['2023-04-01', '2023-04-02', '2023-04-03']:
        process_task = PythonOperator(
            task_id=f'process_data_{partition}',
            python_callable=process_data,
            op_kwargs={'partition_date': partition}
        )

Dans cet exemple, nous définissons une seule tâche, process_data, qui traite les données pour une date de partition donnée. En utilisant une boucle pour créer plusieurs instances de cette tâche, chacune avec une date de partition différente, nous pouvons tirer parti du parallélisme des tâches pour accélérer le traitement des données. Grâce à la parallélisation des tâches, Airflow exécutera ces tâches en parallèle, en tirant parti des ressources de calcul disponibles et en réduisant le temps de traitement global.

Au-delà de la parallélisation des tâches, Airflow fournit également des mécanismes pour optimiser l'utilisation des ressources. Vous pouvez configurer des contraintes de ressources, telles que le CPU, la mémoire ou l'espace disque, au niveau de la tâche, en vous assurant que les tâches ne sont planifiées que sur des nœuds disposant de ressources suffisantes. Cela permet d'éviter les conflits de ressources et d'améliorer l'efficacité globale de vos workflows de traitement des données.

Pour gérer les charges de travail de données à grande échelle, Airflow peut être intégré à des frameworks de calcul distribué comme Apache Spark ou Dask. En tirant parti de ces frameworks, vous pouvez développer vos capacités de traitement des données et relever même les tâches de traitement des données les plus exigeantes.

from airflow.providers.apache.spark.operators.spark_submit import SparkSubmitOperator
 
with DAG('spark_pipeline', default_args=default_args, schedule_interval=timedelta(days=1)) as dag:
    # Exécuter un travail Spark
    spark_job = SparkSubmitOperator(
        task_id='run_spark_job',
        application='/path/to/spark/app.py',
        conn_id='my_spark_conn',
        dag=dag
    )

Dans cet exemple, nous utilisons l'opérateur SparkSubmitOperator d'Airflow pour exécuter un travail Spark dans le cadre de notre pipeline de données. Cela nous permet de tirer parti de la mise à l'échelle et des performances d'Apache Spark pour traiter de grands volumes de données, tout en conservant les capacités d'orchestration et de surveillance offertes par Airflow.

En combinant la parallélisation des tâches, l'optimisation des ressources et l'intégration aux frameworks de calcul distribué d'Airflow, les ingénieurs de données peuvent construire des pipelines de traitement des données hautement évolutifs et efficaces, capables de gérer même les charges de travail de données les plus exigeantes.

Airflow dans les workflows d'apprentissage automatique

La polyvalence d'Airflow s'étend au-delà de l'ingénierie des données, car il est devenu un outil puissant pour automatiser les workflows d'apprentissage automatique (ML). De l'entraînement et du déploiement des modèles au suivi des expériences et à l'optimisation des hyperparamètres,. Airflow peut aider les data scientists et les ingénieurs ML à rationaliser leurs pipelines ML de bout en bout.

Automatisation de l'entraînement et du déploiement des modèles

L'un des principaux cas d'utilisation d'Airflow dans le domaine du ML est l'automatisation de l'entraînement et du déploiement des modèles. Les capacités de planification et d'orchestration d'Airflow permettent aux data scientists de définir et d'exécuter des workflows répétables pour le ré-entraînement, l'évaluation et le déploiement en production des modèles.

from airflow.operators.python_operator import PythonOperator
from airflow.operators.bash_operator import BashOperator
from airflow.models import Variable
 
def train_model():
    # Code pour entraîner un modèle d'apprentissage automatique
    pass
 
def evaluate_model():
    # Code pour évaluer le modèle entraîné
    pass
 
def deploy_model():
    # Code pour déployer le modèle en production
    pass
 
with DAG('ml_pipeline', default_args=default_args, schedule_interval=timedelta(days=7)) as dag:
    train = PythonOperator(
        task_id='train_model',
        python_callable=train_model
    )
 
    evaluate = PythonOperator(
        task_id='evaluate_model',
        python_callable=evaluate_model
    )
 
    deploy = BashOperator(
        task_id='deploy_model',
        bash_command='docker push my-model:{{ execution_date.strftime("%Y%m%d") }}'
    )
 
    train >> evaluate >> deploy

Dans cet exemple, nous définissons un pipeline d'apprentissage automatique qui entraîne un modèle, évalue ses performances, puis déploie le modèle en production. Les fonctions train_model, evaluate_model et deploy_model encapsulent les étapes respectives du workflow.

Les capacités de planification d'Airflow garantissent que ce pipeline est exécuté régulièrement (dans ce cas, hebdomadairement), permettant ainsi de ré-entraîner et de redéployer le modèle au besoin. De plus, Airflow peut être utilisé pour versionner et suivre les artefacts des modèles, tels que les fichiers de modèles entraînés, les hyperparamètres et les métriques d'évaluation, permettant une meilleure gouvernance et une meilleure reproductibilité des modèles.

Intégration avec des plateformes et des frameworks ML

La force d'Airflow réside dans sa capacité à s'intégrer à une large gamme de plateformes et de frameworks d'apprentissage automatique, permettant aux data scientists d'orchestrer des pipelines ML de bout en bout qui s'étendent sur plusieurs outils et technologies.

Par exemple, vous pouvez utiliser Airflow pour vous connecter à MLflow, une plateforme open-source populaire pour la gestion du cycle de vie complet de l'apprentissage automatique. Airflow peut être utilisé pour déclencher des expériences d'entraînement de modèles dans MLflow, suivre les artefacts et les métriques des modèles, puis déployer le modèle le plus performant en production.

from airflow import DAG
from airflow.providers.databricks.operators.databricks import DatabricksRunPipelineOperator
from datetime import datetime, timedelta
 
default_args = {
    'owner': 'airflow',
    'depends_on_past': False,
    'start_date': datetime(2023, 1, 1),
    'email_on_failure': False,
    'email_on_retry': False,
    'retries': 1,
    'retry_delay': timedelta(minutes=5),
}
 
with DAG('mlflow_pipeline', default_args=default_args, schedule_interval=timedelta(days=7)) as dag:
    train_model = DatabricksRunPipelineOperator(
        task_id='train_model',
        databricks_conn_id='my_databricks_conn',
        pipeline_id='my-mlflow-pipeline',
        pipeline_parameters={
            'param1': 'value1',
            'param2': 'value2',
        },
        cluster_spec={
            'node_type_id': 'i3.xlarge',
            'num_workers': 2,
            'spark_version': '7.3.x-scala2.12',
        },
        libraries=[
            {'pypi': {'package': 'mlflow'}},
            {'pypi': {'package': 'scikit-learn'}},
        ],
        timeout_seconds=3600,
    )

Réseaux de neurones convolutifs (CNN)

Les réseaux de neurones convolutifs (CNN) sont un type spécialisé de réseau de neurones qui sont devenus l'architecture de choix pour une large gamme de tâches de vision par ordinateur, de la classification d'images à la détection et à la segmentation d'objets. Les CNN sont conçus pour traiter et extraire efficacement les caractéristiques des données d'image, en tirant parti de la connectivité spatiale et locale de l'entrée.

Les principaux composants d'une architecture CNN sont :

  1. Couches convolutives : Ces couches appliquent un ensemble de filtres (ou noyaux) apprenables à l'image d'entrée, extrayant ainsi des caractéristiques et des motifs locaux. Les filtres sont entraînés pour détecter des caractéristiques spécifiques, telles que des bords, des formes ou des textures, et la sortie de la couche convolutive est une carte des caractéristiques qui représente la présence et l'emplacement de ces caractéristiques dans l'entrée.

  2. Couches de mise en commun : Les couches de mise en commun réduisent les dimensions spatiales des cartes des caractéristiques, tout en préservant les informations les plus importantes. Cela permet d'atteindre l'invariance à la translation et de réduire la complexité de calcul du réseau.

  3. Couche entièrement connectée. ** Les dernières couches d'un CNN sont généralement des couches entièrement connectées, qui prennent les cartes de caractéristiques aplaties et produisent la sortie finale, comme une prédiction de classification.

Voici un exemple d'une architecture CNN simple en PyTorch :

import torch.nn as nn
 
class ConvNet(nn.Module):
    def __init__(self):
        super(ConvNet, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=1)
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
        self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, stride=1, padding=1)
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
        self.fc1 = nn.Linear(in_features=32 * 7 * 7, out_features=128)
        self.fc2 = nn.Linear(in_features=128, out_features=10)
 
    def forward(self, x):
        # Appliquer la première couche convolutionnelle, la fonction d'activation ReLU et la première couche de pooling
        x = self.pool1(nn.functional.relu(self.conv1(x)))
        # Appliquer la deuxième couche convolutionnelle, la fonction d'activation ReLU et la deuxième couche de pooling
        x = self.pool2(nn.functional.relu(self.conv2(x)))
        # Aplatir les cartes de caractéristiques
        x = x.view(-1, 32 * 7 * 7)
        # Appliquer la première couche entièrement connectée et la fonction d'activation ReLU
        x = nn.functional.relu(self.fc1(x))
        # Appliquer la deuxième couche entièrement connectée
        x = self.fc2(x)
        return x

Dans cet exemple, l'architecture CNN se compose de deux couches convolutionnelles, de deux couches de pooling et de deux couches entièrement connectées. Les couches convolutionnelles extraient les caractéristiques de l'image d'entrée, les couches de pooling réduisent les dimensions spatiales, et les couches entièrement connectées produisent la sortie finale de classification.

Réseaux de neurones récurrents (RNNs)

Les réseaux de neurones récurrents (RNNs) sont un type de réseau de neurones conçu pour traiter les données séquentielles, telles que le texte, la parole ou les séries temporelles. Contrairement aux réseaux de neurones feedforward, qui traitent chaque entrée de manière indépendante, les RNNs ont une "mémoire" qui leur permet de prendre en compte le contexte de l'entrée actuelle en fonction des entrées précédentes.

La caractéristique clé des RNNs est la présence d'une connexion récurrente, qui permet au réseau de maintenir un état caché qui est mis à jour à chaque pas de temps. Cet état caché peut être considéré comme une "mémoire" que le réseau utilise pour traiter les données séquentielles. L'un des types les plus courants de RNN est le réseau de mémoire à long terme et à court terme (LSTM), conçu pour résoudre le problème des gradients qui s'évanouissent ou explosent, qui peut se produire dans les RNN traditionnels. Les LSTM utilisent une structure de cellule unique qui comprend des portes (portes d'oubli, d'entrée et de sortie) pour se souvenir et oublier sélectivement les informations, leur permettant de capturer efficacement les dépendances à long terme dans les données d'entrée.

Voici un exemple d'un réseau LSTM en PyTorch :

import torch.nn as nn
 
class LSTMModel(nn.Module):
    def __init__(self, input_size, hidden_size, num_layers, output_size):
        super(LSTMModel, self).__init__()
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)
 
    def forward(self, x):
        # Initialiser l'état caché et l'état de la cellule
        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
        c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
 
        # Passe en avant à travers le LSTM
        out, _ = self.lstm(x, (h0, c0))
 
        # Passer la sortie LSTM à travers la couche entièrement connectée
        out = self.fc(out[:, -1, :])
        return out

Dans cet exemple, le modèle LSTM prend une séquence d'entrée x et produit une séquence de sortie. L'état caché et l'état de la cellule sont initialisés à zéro et sont mis à jour à chaque pas de temps lorsque le LSTM traite l'entrée. La sortie finale est obtenue en passant le dernier état caché à travers une couche entièrement connectée.

Les RNN, et en particulier les LSTM, ont été largement utilisés dans une variété d'applications, telles que la modélisation du langage, la traduction automatique, la reconnaissance vocale et la prévision de séries temporelles.

Réseaux antagonistes génératifs (GANs)

Les réseaux antagonistes génératifs (GANs) sont une classe d'apprentissage profond. Modèles génératifs qui ont révolutionné le domaine de la modélisation génératrice. Les GANs se composent de deux réseaux de neurones, un générateur et un discriminateur, qui sont entraînés de manière adversaire pour générer de nouvelles données réalistes.

Le réseau générateur est entraîné à générer des données qui ressemblent aux données d'entraînement réelles, tandis que le réseau discriminateur est entraîné à distinguer les données d'entraînement réelles des données générées. Ce processus d'entraînement adversaire pousse le générateur à améliorer continuellement sa capacité à générer des échantillons de plus en plus réalistes, tandis que le discriminateur devient meilleur pour identifier les échantillons faux.

L'un des principaux avantages des GANs est leur capacité à générer des échantillons très réalistes et diversifiés, tels que des images, du texte ou de l'audio. Cela a conduit à un large éventail d'applications, notamment la synthèse d'images, le transfert de style, la génération de texte et même la création de médias synthétiques (connus sous le nom de "deepfakes").

Voici un exemple d'une architecture GAN simple en PyTorch :

import torch.nn as nn
import torch.optim as optim
import torch.utils.data
import torchvision.datasets as datasets
import torchvision.transforms as transforms
 
# Définition des réseaux générateur et discriminateur
class Générateur(nn.Module):
    def __init__(self, latent_dim, img_shape):
        super(Générateur, self).__init__()
        self.img_shape = img_shape
        self.model = nn.Sequential(
            nn.Linear(latent_dim, 256),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(256, 512),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(512, 1024),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(1024, int(np.prod(img_shape))),
            nn.Tanh()
        )
 
    def forward(self, z):
        img = self.model(z)
        img = img.view(img.size(0), *self.img_shape)
        return img
 
class Discriminateur(nn.Module):
    def __init__(self, img_shape):
        super(Discriminateur, self).__init__()
        self.model.
        nn.Sequential(
            nn.Linear(int(np.prod(img_shape)), 512),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(256, 1),
            nn.Sigmoid()
        )
 
    def forward(self, img):
        img_flat = img.view(img.size(0), -1)
        validité = self.model(img_flat)
        return validité
 
# Entraîner le GAN
dimension_latente = 100
forme_img = (1, 28, 28)
générateur = Générateur(dimension_latente, forme_img)
discriminateur = Discriminateur(forme_img)

Dans cet exemple, les réseaux du générateur et du discriminateur sont définis en tant que modules PyTorch. Le générateur prend un vecteur latent en entrée et génère une image, tandis que le discriminateur prend une image et produit une probabilité qu'elle soit réelle ou fausse.

Pendant l'entraînement, le générateur et le discriminateur sont entraînés de manière adversariale, le générateur essayant de tromper le discriminateur et le discriminateur essayant d'identifier correctement les échantillons réels et faux.

Les GANs ont été appliqués à une large gamme de problèmes, de la génération d'images et du transfert de style à la synthèse de texte en image et même à la génération de vidéos. Alors que le domaine de l'apprentissage profond continue d'évoluer, nous pouvons nous attendre à voir encore plus d'applications passionnantes et innovantes des GANs à l'avenir.

Conclusion

Dans cet article, nous avons exploré trois architectures clés de l'apprentissage profond : les réseaux de neurones convolutifs (CNN), les réseaux de neurones récurrents (RNN) et les réseaux antagonistes génératifs (GAN). Chacune de ces architectures a ses propres forces et applications uniques, et elles ont toutes joué un rôle crucial dans les récents progrès de l'apprentissage profond.

Les CNN sont devenus le choix privilégié pour une large gamme de tâches de vision par ordinateur, grâce à leur capacité à extraire et à traiter efficacement les caractéristiques visuelles. Les RNN, quant à eux, sont bien adaptés au traitement des données séquentielles, comme le traitement du langage naturel et les prévisions de séries temporelles. Les GANs ont révolutionné le domaine de la génération.Voici la traduction française du fichier markdown :

La modélisation générative, permettant la création de données synthétiques très réalistes et diversifiées.

Alors que l'apprentissage profond continue d'évoluer, nous pouvons nous attendre à voir encore plus de développements et d'applications passionnants de ces architectures, ainsi que l'émergence de nouveaux modèles d'apprentissage profond innovants. Le domaine de l'apprentissage profond est un domaine de recherche en rapide évolution et dynamique, et il sera fascinant de voir comment il continue à façonner l'avenir de l'intelligence artificielle.