1. Accueil
  2. 1. Vue d'ensemble
    1. 1.1. Introduction Ă  đŸ€— Diffusers
    2. 1.2. Implémentation à partir de 0
  3. 2. Vue d'ensemble
    1. 2.1. Finetuning et guidage
    2. 2.2. ModÚle de diffusion conditionné par la classe
  4. 3. Vue d'ensemble
    1. 3.1. Introduction Ă  Stable Diffusion
    2. 3.2. Stable Diffusion : plongée en profondeur
  5. 4. Vue d'ensemble
    1. 4.1. Débruitage inverse des modÚles de diffusion implicites (DDIM)
    2. 4.2. Diffusion pour l'audio
  6. 5. ÉvĂ©nement pour la sortie du cours
    1. 5.1. Hackathon DreamBooth 🏆
    2. 5.2. Sprint Dreambooth en Keras
    3. 5.3. Sprint ControlNet en JAX/Diffusers

Cours Modeles de diffusion

Type to search across the whole book.
    Download as PDF

    Cours d’apprentissage profond de la New York University

    Edition 2021

    Le cours de Yann LE CUN et Alfredo CANZIANI

    traduit en français par Loïck BOURDOIS

    Accueil

    Bienvenue au cours sur les modĂšles de diffusion đŸ€— !

    À quoi s’attendre ?

    Dans ce cours gratuit, vous allez :

    • đŸ‘©â€đŸŽ“ Étudier la thĂ©orie des modĂšles de diffusion
    • 🧹 Apprendre Ă  gĂ©nĂ©rer des images et de l’audio avec la bibliothĂšque populaire đŸ€— Diffusers
    • đŸ‹ïžâ€â™‚ïž EntraĂźner vos propres modĂšles de diffusion Ă  partir de zĂ©ro
    • đŸ“» Affiner des modĂšles de diffusion existants sur de nouveaux jeux de donnĂ©es
    • đŸ—ș Explorer la gĂ©nĂ©ration conditionnelle et le guidage
    • 🧑‍🔬 CrĂ©er vos propres pipelines de modĂšles de diffusion personnalisĂ©s

    Prérequis

    Ce cours requiert un bon niveau en Python et des bases en apprentissage profond et Pytorch. Si ce n’est pas encore le cas, vous pouvez consulter ces ressources gratuites (en anglais) :

    • Python : https://www.udacity.com/course/introduction-to-python–ud1110
    • Introduction Ă  l’apprentissage profond avec PyTorch : https://www.udacity.com/course/deep-learning-pytorch–ud188
    • PyTorch en 60 min : https://pytorch.org/tutorials/beginner/deep_learning_60min_blitz.html

    Pour pousser vos modĂšles sur le Hub d’Hugging Face, vous aurez besoin d’un compte. Vous pouvez en crĂ©er un gratuitement Ă  l’adresse suivante : https://huggingface.co/join.

    Quel est le programme ?

    Le cours est constituĂ© de quatre unitĂ©s. Chacune d’elle est composĂ©e d’une partie thĂ©orie listant Ă©galement des ressources / papiers, ainsi que de deux notebooks. Plus prĂ©cisĂ©ment, nous avons :

    • UnitĂ© 1 : Introduction aux modĂšles de diffusion
      Introduction Ă  đŸ€— Diffusers et implĂ©mentation Ă  partir de 0
    • UnitĂ© 2 : Finetuning et guidage
      Finetuner un modÚle de diffusion sur de nouvelles données et ajout du guidage
    • UnitĂ© 3 : Stable Diffusion
      Exploration d’un puissant modĂšle de diffusion latent conditionnĂ© par le texte
    • UnitĂ© 4 : Faire plus avec la diffusion
      Techniques avancées pour aller plus loin dans la diffusion

    Qui sommes-nous ?

    À propos des auteurs de ce cours :

    Jonathan Whitaker est TODO.

    Lewis Tunstall est ingĂ©nieur en apprentissage machine chez Hugging Face et dĂ©vouĂ© au dĂ©veloppement d’outils open source avec la volontĂ© de les rendre accessibles Ă  une communautĂ© plus large. Il est Ă©galement co-auteur du livre Natural Language Processing with Transformers.

    FAQ

    Voici quelques réponses aux questions fréquemment posées :

    • Suivre ce cours mĂšne-t-il Ă  une certification ?
      Actuellement, nous n’avons pas de certification pour ce cours.

    • Combien de temps dois-je consacrer Ă  ce cours ?
      Chaque chapitre de ce cours est conçu pour ĂȘtre complĂ©tĂ© en une semaine, avec environ 6 Ă  8 heures de travail par unitĂ©. Cependant, vous pouvez prendre tout le temps nĂ©cessaire pour le suivre.

    • OĂč puis-je poser une question si j’en ai une ?
      Si vous avez une question sur l’une des sections du cours, il vous suffit de cliquer sur la banniĂšre « Ask a question » en haut de la page pour ĂȘtre automatiquement redirigĂ© vers le Discord de Hugging Face pour poser votre question dans le channel #diffusion-models-class.

    Link to the Hugging Face forums

    • OĂč puis-je obtenir le code du cours ?
      Pour chaque section, vous pouvez cliquer sur la banniÚre en haut de la page pour exécuter son code :

    Link to the Hugging Face course notebooks

    • Comment puis-je contribuer au cours ?
      Il existe de nombreuses façons de contribuer au cours ! Si vous trouvez une coquille ou un bug, veuillez ouvrir une « Issue » sur le dépÎt diffusion-models-class. Si vous souhaitez aider à traduire le cours dans votre langue maternelle, consultez les instructions ici.

    • Peut-on rĂ©utiliser ce cours?
      Bien sĂ»r ! Le cours est publiĂ© sous la licence Apache 2 license. Cela signifie que vous devez crĂ©diter de maniĂšre appropriĂ©e, fournir un lien vers la licence et indiquer si des modifications ont Ă©tĂ© apportĂ©es. Vous pouvez le faire de toute maniĂšre raisonnable, mais pas d’une façon qui suggĂšre que le distributeur de la licence vous approuve ou approuve votre utilisation. Si vous souhaitez citer le cours, veuillez utiliser le BibTeX suivant :

    @misc{huggingfacecourse,
      author = {Hugging Face},
      title = {The Hugging Face Diffusion Models Course, 2022},
      howpublished = "\url{https://huggingface.co/course}",
      year = {2022},
      note = "[Online; accessed <today>]"
    }
    

    C’est parti !

    Êtes-vous prĂȘt Ă  commencer ? Alors rendez vous Ă  la premiĂšre unitĂ© pour dĂ©buter le cours.

    1. Vue d'ensemble

    Dans cette unitĂ©, vous apprendrez les bases de fonctionnement des modĂšles de diffusion et comment crĂ©er les vĂŽtres Ă  l’aide de la bibliothĂšque đŸ€— Diffusers.

    Vue d’ensemble de cette unitĂ© :rocket:

    Les différentes étapes à suivre pour cette unité :

    • Lisez le matĂ©riel d’introduction ci-dessous ainsi que toutes les ressources supplĂ©mentaires listĂ©es en bas de page qui vous sembleront intĂ©ressantes.
    • Consultez le notebook Introduction Ă  Diffusers pour mettre en pratique la thĂ©orie avec la bibliothĂšque đŸ€— Diffusers.
    • EntraĂźnez et partagez votre propre modĂšle de diffusion en utilisant le notebook ou le script d’entraĂźnement associĂ©.
    • (Facultatif) Approfondissez avec le notebook ImplĂ©mentation Ă  partir de 0 des modĂšles de diffusion Ă  partir de zĂ©ro si vous souhaitez voir une implĂ©mentation minimale Ă  partir de zĂ©ro et explorer les diffĂ©rentes dĂ©cisions de conception en jeu.
    • (Facultatif) Regardez cette vidĂ©o (en anglais) pour une prĂ©sentation informelle du matĂ©riel de cette unitĂ©.

    Que sont les modĂšles de diffusion ?

    Les modĂšles de diffusion sont un ajout relativement rĂ©cent Ă  un groupe d’algorithmes connus sous le nom de modĂšles gĂ©nĂ©ratifs. L’objectif de la modĂ©lisation gĂ©nĂ©rative est d’apprendre Ă  gĂ©nĂ©rer des donnĂ©es, telles que des images ou des sons, Ă  partir d’un certain nombre d’exemples d’entraĂźnement. Un bon modĂšle gĂ©nĂ©ratif crĂ©era un ensemble diversifiĂ© de sorties qui ressemblent aux donnĂ©es d’entraĂźnement sans ĂȘtre des copies exactes. Comment les modĂšles de diffusion y parviennent-ils ? Concentrons-nous sur le cas de la gĂ©nĂ©ration d’images Ă  des fins d’illustration.

    Figure tirée du papier DDPM de Ho et al. (2020) (https://arxiv.org/abs/2006.11239) Figure tirée du papier DDPM de Ho et al. (2020) (https://arxiv.org/abs/2006.11239)

    Figure tirée du papier DDPM de Ho et al. (2020) (https://arxiv.org/abs/2006.11239).

    Le secret de la rĂ©ussite des modĂšles de diffusion rĂ©side dans la nature itĂ©rative du processus de diffusion. La gĂ©nĂ©ration commence par un bruit alĂ©atoire, mais celui-ci est progressivement affinĂ© au cours d’un certain nombre d’étapes jusqu’à ce qu’une image de sortie Ă©merge. À chaque Ă©tape, le modĂšle estime comment nous pourrions passer de l’entrĂ©e actuelle x_t Ă  une version complĂštement dĂ©bruitĂ©e x_0. Cependant, comme nous n’effectuons qu’un petit changement Ă  chaque Ă©tape t, toute erreur dans cette estimation aux premiers stades (oĂč il est extrĂȘmement difficile de prĂ©dire le rĂ©sultat final) peut ĂȘtre corrigĂ©e dans les mises Ă  jour ultĂ©rieures.

    EntraĂźner le modĂšle est relativement simple par rapport Ă  d’autres types de modĂšles gĂ©nĂ©ratifs. Nous procĂ©dons de maniĂšre rĂ©pĂ©tĂ©e 1) Nous chargeons quelques images Ă  partir des donnĂ©es d’entraĂźnement. 2) Nous ajoutons du bruit, en diffĂ©rentes quantitĂ©s. N’oubliez pas que nous voulons que le modĂšle soit capable d’estimer comment « corriger » (dĂ©bruiter) Ă  la fois des images extrĂȘmement bruitĂ©es et des images qui sont proches de la perfection. 3) Nous introduisons les versions bruitĂ©es des donnĂ©es d’entrĂ©e dans le modĂšle. 4) Nous Ă©valuons l’efficacitĂ© du modĂšle Ă  dĂ©bruiter ces donnĂ©es d’entrĂ©e. 5) Nous utilisons ces informations pour mettre Ă  jour les poids du modĂšle.

    Pour gĂ©nĂ©rer de nouvelles images Ă  l’aide d’un modĂšle entraĂźnĂ©, nous commençons par une entrĂ©e totalement alĂ©atoire que nous soumettons au modĂšle de maniĂšre rĂ©pĂ©tĂ©e, en l’actualisant Ă  chaque fois d’une petite quantitĂ© basĂ©e sur la prĂ©diction du modĂšle. Comme nous le verrons, il existe un certain nombre de mĂ©thodes d’échantillonnage qui tentent de rationaliser ce processus afin de gĂ©nĂ©rer de bonnes images en un minimum d’étapes.

    Nous montrerons chacune de ces Ă©tapes en dĂ©tail dans les notebooks de l’unitĂ© 1. Dans l’unitĂ© 2, nous verrons comment ce processus peut ĂȘtre modifiĂ© pour ajouter un contrĂŽle supplĂ©mentaire sur les rĂ©sultats du modĂšle par le biais d’un conditionnement supplĂ©mentaire (tel qu’une Ă©tiquette de classe) ou de techniques telles que le guidage. Les unitĂ©s 3 et 4 exploreront un modĂšle de diffusion extrĂȘmement puissant appelĂ© Stable Diffusion, qui peut gĂ©nĂ©rer des images Ă  partir de descriptions textuelles.

    Notebooks

    A ce stade, vous en savez assez pour vous lancer dans les notebooks de cette unitĂ© ! Les deux notebooks abordent la mĂȘme idĂ©e de maniĂšre diffĂ©rente.

    Chapitre Colab Kaggle Gradient Studio Lab
    Introduction Ă  Diffusers Open In Colab Kaggle Gradient Open In SageMaker Studio Lab
    Implémentation à partir de 0 Open In Colab Kaggle Gradient Open In SageMaker Studio Lab

    Dans Introduction Ă  Diffusers, nous montrons les diffĂ©rentes Ă©tapes dĂ©crites ci-dessus en utilisant les blocs de la bibliothĂšque đŸ€— Diffusers. Vous verrez rapidement comment crĂ©er, entraĂźner et Ă©chantillonner vos propres modĂšles de diffusion sur les donnĂ©es de votre choix. À la fin du notebook, vous serez en mesure de lire et de modifier le script d’entraĂźnement illustratif pour entraĂźner des modĂšles de diffusion et les partager avec le monde entier ! Ce notebook introduit Ă©galement l’exercice principal associĂ© Ă  cette unitĂ©, oĂč nous tenterons collectivement de trouver de bonnes « recettes d’entraĂźnement » pour les modĂšles de diffusion Ă  diffĂ©rentes Ă©chelles (voir la section suivante pour plus d’informations).

    Dans ModĂšles de diffusion Ă  partir de 0, nous montrons ces mĂȘmes Ă©tapes (ajout de bruit aux donnĂ©es, crĂ©ation d’un modĂšle, entraĂźnement et Ă©chantillonnage) mais implĂ©mentĂ©es Ă  partir de zĂ©ro dans PyTorch aussi simplement que possible. Nous comparons ensuite cet « exemple-jouet » avec la version de đŸ€— Diffusers, en notant les diffĂ©rences entre les deux et les amĂ©liorations qui ont Ă©tĂ© apportĂ©es. L’objectif est de se familiariser avec les diffĂ©rents composants et les dĂ©cisions de conception qui les sous-tendent, afin de pouvoir identifier rapidement les idĂ©es clĂ©s pour une nouvelle implĂ©mentation.

    Projet

    Une fois les bases assimilĂ©es grĂące aux notebooks, essayez d’entraĂźner un ou plusieurs modĂšles de diffusion ! Quelques suggestions sont incluses Ă  la fin du notebook Introduction Ă  Diffusers. N’oubliez pas de partager vos rĂ©sultats, vos recettes d’entraĂźnement et vos dĂ©couvertes avec la communautĂ© afin que nous puissions trouver ensemble les meilleures façons d’entraĂźner ces modĂšles.

    Ressources complémentaires

    Une liste non exhaustive de ressources (en anglais) Ă  consulter :

    • Le modĂšle de diffusion annotĂ© est une prĂ©sentation trĂšs approfondie du code et de la thĂ©orie qui sous-tend les DDPM, avec des mathĂ©matiques et du code montrant tous les diffĂ©rents composants. Il liste Ă©galement un certain nombre d’articles pour une lecture plus approfondie.
    • La documentation d’Hugging Face sur la GĂ©nĂ©ration d’images inconditionnelle contient des exemples d’entraĂźnement de modĂšles de diffusion Ă  l’aide du script d’entraĂźnement officiel, y compris le code montrant comment crĂ©er votre propre jeu de donnĂ©es.
    • La vidĂ©o d’AI Coffee Break sur les modĂšles de diffusion
    • La vidĂ©o de Yannic Kilcher sur les DDPM
      Vous avez identifiĂ© d’autres ressources intĂ©ressantes ? Faites-le nous savoir et nous les ajouterons Ă  cette liste.

    1.1. Introduction Ă  đŸ€— Diffusers

    Dans ce notebook, vous allez entraĂźner votre premier modĂšle de diffusion pour gĂ©nĂ©rer des images de mignons papillons 🩋. En cours de route, vous apprendrez les composants de base de la bibliothĂšque đŸ€— Diffusers, qui fournira une bonne assise pour les applications plus avancĂ©es que nous couvrirons plus tard dans le cours.

    DĂ©butons par une vue d’ensemble de ce qu’on va faire dans ce notebook. Nous allons :

    • Voir un puissant pipeline de modĂšles de diffusion personnalisĂ© en action (avec des informations sur la façon de crĂ©er votre propre version).
    • CrĂ©er votre propre mini-pipeline en :
      • RĂ©capitulant les idĂ©es principales derriĂšre les modĂšles de diffusion
      • Chargement de donnĂ©es Ă  partir du Hub pour l’entraĂźnement
      • Explorer comment ajouter du bruit Ă  ces donnĂ©es Ă  l’aide d’un planificateur
      • CrĂ©er et entraĂźner le modĂšle UNet
      • Rassembler les piĂšces du puzzle pour en faire un pipeline fonctionnel
    • Éditer et exĂ©cuter un script pour initialiser des sĂ©ries d’entraĂźnement plus longues, qui gĂšrera
      • EntraĂźnement multi-GPU via đŸ€— Accelerate
      • Journalisation de l’expĂ©rience pour suivre les statistiques critiques
      • TĂ©lĂ©chargement du modĂšle final sur le Hub d’Hugging Face

    Installation des bibliothĂšques

    ExĂ©cutez la cellule suivante pour installer la bibliothĂšque đŸ€— Diffusers ainsi que quelques autres prĂ©requis :

    %pip install -qq -U diffusers datasets transformers accelerate ftfy pyarrow==9.0.0
    

    Ensuite, rendez-vous sur https://huggingface.co/settings/tokens et crĂ©ez un tokens d’accĂšs avec autorisation d’écriture si vous n’en avez pas dĂ©jĂ  un :

    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Vous pouvez vous connecter avec ce token en utilisant la ligne de commande (huggingface-cli login) ou en exécutant la cellule suivante :

    from huggingface_hub import notebook_login
    
    notebook_login()
    

    Vous devez ensuite installer Git-LFS pour télécharger les checkpoints de votre modÚle :

    %%capture
    !sudo apt -qq install git-lfs
    !git config --global credential.helper store
    

    Enfin, importons les bibliothÚques que nous utiliserons et définissons quelques fonctions de confort que nous utiliserons plus tard dans le notebook :

    import numpy as np
    import torch
    import torch.nn.functional as F
    from matplotlib import pyplot as plt
    from PIL import Image
    
    
    def show_images(x):
        """Étant donnĂ© un lot d'images x, faire une grille et convertir en PIL"""
        x = x * 0.5 + 0.5  # On va de (-1, 1) et revenons (0, 1)
        grid = torchvision.utils.make_grid(x)
        grid_im = grid.detach().cpu().permute(1, 2, 0).clip(0, 1) * 255
        grid_im = Image.fromarray(np.array(grid_im).astype(np.uint8))
        return grid_im
    
    
    def make_grid(images, size=64):
        """Étant donnĂ© une liste d'images PIL, les empiler en une ligne pour faciliter la visualisation."""
        output_im = Image.new("RGB", (size * len(images), size))
        for i, im in enumerate(images):
            output_im.paste(im.resize((size, size)), (i * size, 0))
        return output_im
    
    
    # Les utilisateurs de Mac peuvent avoir besoin de device = 'mps' (non testé)
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    

    OK, nous sommes prĂȘts !

    Exemple gĂ©nĂ©rique d’infĂ©rence avec Dreambooth, un avant-goĂ»t de ce qui nous attend

    Si vous avez un tant soit peu consultĂ© les mĂ©dias sociaux au cours des derniers mois, vous avez certainement entendu parler de Stable Diffusion. Il s’agit d’un puissant modĂšle de diffusion latent conditionnĂ© par le texte (ne vous inquiĂ©tez pas, nous allons apprendre ce que cela signifie). Mais il a un dĂ©faut : il ne sait pas Ă  quoi vous ou moi ressemblons, Ă  moins que nous soyons suffisamment cĂ©lĂšbres pour que nos images soient rĂ©pandues sur internet.

    Dreambooth nous permet de crĂ©er notre propre variante de modĂšle avec une connaissance supplĂ©mentaire d’un visage, d’un objet ou d’un style spĂ©cifique. Le Corridor Crew a rĂ©alisĂ© une excellente vidĂ©o (en anglais) en utilisant cette technique pour raconter des histoires avec des personnages cohĂ©rents, ce qui est un excellent exemple de ce que cette technique peut faire :

    from IPython.display import YouTubeVideo
    
    YouTubeVideo("W4Mcuh38wyM")
    

    Voici un exemple d’une sortie d’un modĂšle entraĂźnĂ© sur 5 photos du jouet Monsieur Patate.

    Tout d’abord, nous chargeons le pipeline. Ceci tĂ©lĂ©charge les poids du modĂšle depuis le Hub. Étant donnĂ© que plusieurs gigaoctets de donnĂ©es sont tĂ©lĂ©chargĂ©s pour une dĂ©monstration d’une ligne, vous pouvez sauter cette cellule et simplement admirer la sortie de l’exemple !

    from diffusers import StableDiffusionPipeline
    
    # Consultez https://huggingface.co/sd-dreambooth-library pour découvrir de nombreux modÚles provenant de la communauté
    model_id = "sd-dreambooth-library/mr-potato-head"
    
    # Chargement du pipeline
    pipe = StableDiffusionPipeline.from_pretrained(model_id, torch_dtype=torch.float16).to(
        device
    )
    

    Une fois le chargement du pipeline terminé, nous pouvons générer des images avec :

    prompt = "an abstract oil painting of sks mr potato head by picasso"
    image = pipe(prompt, num_inference_steps=50, guidance_scale=7.5).images[0]
    image
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    ✏ À votre tour ! Essayez vous-mĂȘme avec des prompts diffĂ©rents. Le token sks reprĂ©sente un identifiant unique pour le nouveau concept : que se passe-t-il si vous l’omettez ? Vous pouvez aussi expĂ©rimenter en changeant le nombre de pas d’échantillonnage (jusqu’oĂč pouvez-vous descendre ?) et le paramĂštre guidance_scale, qui dĂ©termine jusqu’à quel point le modĂšle va essayer de correspondre au prompt.

    Il se passe beaucoup de choses dans ce pipeline ! À la fin du cours, vous saurez comment tout cela fonctionne. Pour l’instant, voyons comment nous pouvons entraĂźner un modĂšle de diffusion Ă  partir de zĂ©ro.

    MVP (Minimum Viable Pipeline)

    Exemple d’infĂ©rence sur les papillons

    L’API de base de đŸ€— Diffusers est divisĂ©e en trois composants principaux :

    • Pipelines : classes de haut niveau conçues pour gĂ©nĂ©rer rapidement des Ă©chantillons Ă  partir de modĂšles de diffusion populaires entraĂźnĂ©s de maniĂšre conviviale.
    • Models : architectures populaires pour entraĂźner de nouveaux modĂšles de diffusion, par exemple UNet.
    • Schedulers : diverses techniques pour gĂ©nĂ©rer des images Ă  partir du bruit pendant l’infĂ©rence ainsi que pour gĂ©nĂ©rer des images bruitĂ©es pour l’entraĂźnement.

    Les pipelines sont parfaits pour les utilisateurs finaux, mais si vous ĂȘtes ici pour ce cours, nous supposons que vous voulez savoir ce qui se passe sous le capot ! Dans le reste de ce notebook, nous allons donc construire notre propre pipeline capable de gĂ©nĂ©rer de petites images de papillons. Voici le rĂ©sultat final en action :

    from diffusers import DDPMPipeline
    
    # Chargement du pipeline de papillons
    butterfly_pipeline = DDPMPipeline.from_pretrained(
        "johnowhitaker/ddpm-butterflies-32px"
    ).to(device)
    
    # Création de 8 images
    images = butterfly_pipeline(batch_size=8).images
    
    # Visualisation du résultat
    make_grid(images)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Ce n’est peut-ĂȘtre pas aussi impressionnant que l’exemple de DreamBooth, mais nous entraĂźnons notre modĂšle Ă  partir de zĂ©ro avec ~0,0001% des donnĂ©es utilisĂ©es pour entraĂźner Stable Diffusion. En parlant d’entraĂźnement, rappelez-vous que l’entraĂźnement d’un modĂšle de diffusion ressemble Ă  ceci :

    • Chargement de quelques images Ă  partir des donnĂ©es entraĂźnĂ©es.
    • Ajout de bruit, en diffĂ©rentes quantitĂ©s.
    • Introduction des versions bruitĂ©es des donnĂ©es d’entrĂ©e dans le modĂšle.
    • Évaluation de la capacitĂ© du modĂšle Ă  dĂ©bruiter ces donnĂ©es d’entrĂ©e
    • Utilisation de ces informations pour mettre Ă  jour les poids du modĂšle, et rĂ©pĂ©tition.

    Nous allons explorer ces Ă©tapes une par une dans les prochaines parties jusqu’à ce que nous ayons une boucle d’entraĂźnement complĂšte, puis nous verrons comment Ă©chantillonner Ă  partir du modĂšle entraĂźnĂ© et comment regrouper le tout dans un pipeline pour faciliter le partage. Commençons par les donnĂ©es.

    TĂ©lĂ©charger le jeu de donnĂ©es d’entraĂźnement

    Pour cet exemple, nous utilisons un jeu de donnĂ©es d’images provenant du Hub d’Hugging Face. Plus prĂ©cisĂ©ment, cette collection de 1000 images de papillons. Il s’agit d’un trĂšs petit jeu de donnĂ©es, c’est pourquoi nous avons aussi inclus des lignes en commentaires pour quelques options plus importantes. Si vous prĂ©fĂ©rez utiliser votre propre collection d’images, vous pouvez Ă©galement utiliser l’exemple de code commentĂ© pour charger des images Ă  partir d’un dossier.

    import torchvision
    from datasets import load_dataset
    from torchvision import transforms
    
    dataset = load_dataset("huggan/smithsonian_butterflies_subset", split="train")
    
    # Ou charger des images Ă  partir d'un dossier local
    # dataset = load_dataset("imagefolder", data_dir="path/to/folder")
    
    # Nous entraßnerons sur des images carrées de 32 pixels, mais vous pouvez aussi essayer des tailles plus grandes
    image_size = 32
    # Vous pouvez réduire la taille de votre batch si vous manquez de mémoire GPU
    batch_size = 64
    
    # Définition les augmentations de données
    preprocess = transforms.Compose(
        [
            transforms.Resize((image_size, image_size)),  # Redimensionner
            transforms.RandomHorizontalFlip(),  # Retournement aléatoire
            transforms.ToTensor(),  # Convertir en tenseur (0, 1)
            transforms.Normalize([0.5], [0.5]),  # Passage en (-1, 1)
        ]
    )
    
    
    def transform(examples):
        images = [preprocess(image.convert("RGB")) for image in examples["image"]]
        return {"images": images}
    
    
    dataset.set_transform(transform)
    
    # Créer un chargeur de données à partir du jeu de données pour servir les images transformées en batchs
    train_dataloader = torch.utils.data.DataLoader(
        dataset, batch_size=batch_size, shuffle=True
    )
    

    Nous pouvons saisir un batch d’images et en visualiser quelques-unes comme suit :

    xb = next(iter(train_dataloader))["images"].to(device)[:8]
    print("X shape:", xb.shape)
    show_images(xb).resize((8 * 64, 64), resample=Image.NEAREST)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Nous nous en tenons Ă  un petit jeu de donnĂ©es avec des images de 32 pixels pour que les temps d’entraĂźnement restent raisonnables dans ce notebook.

    Définir le planificateur

    Notre plan d’entraĂźnement consiste Ă  prendre ces images d’entrĂ©e et Ă  leur ajouter du bruit, puis Ă  transmettre les images bruitĂ©es au modĂšle. Lors de l’infĂ©rence, nous utiliserons les prĂ©dictions du modĂšle pour supprimer le bruit de maniĂšre itĂ©rative. Dans đŸ€— Diffusers, ces deux processus sont gĂ©rĂ©s par le scheduler (planificateur).

    Le planificateur de bruit dĂ©termine la quantitĂ© de bruit ajoutĂ©e Ă  diffĂ©rents moments. Voici comment nous pourrions crĂ©er un planificateur en utilisant les paramĂštres par dĂ©faut pour l’entraĂźnement et l’échantillonnage “DDPM” (d’aprĂšs l’article d’aprĂšs l’article Denoising Diffusion Probabalistic Models) :

    from diffusers import DDPMScheduler
    
    noise_scheduler = DDPMScheduler(num_train_timesteps=1000)
    

    Le papier DDPM dĂ©crit un processus de corruption qui ajoute une petite quantitĂ© de bruit Ă  chaque pas de temps. Étant donnĂ© $x_{t-1}$ pour un certain pas de temps, nous pouvons obtenir la version suivante (lĂ©gĂšrement plus bruyante) $x_t$ avec :

    \[\begin{aligned} q(\mathbf{x}_t \vert \mathbf{x}_{t-1}) &= \mathcal{N}(\mathbf{x}_t; \sqrt{1 - \beta_t} \mathbf{x}_{t-1}, \beta_t\mathbf{I}) \\ q(\mathbf{x}_{1:T} \vert \mathbf{x}_0) &= \prod^T_{t=1} q(\mathbf{x}_t \vert \mathbf{x}_{t-1}) \end{aligned}\]

    Nous prenons $x_{t-1}$, l’échelonnons de $\sqrt{1 - \beta_t}$ et ajoutons du bruit Ă©chelonnĂ© par $\beta_t$. Ce $\beta$ est dĂ©fini pour chaque $t$ selon un certain planificateur et dĂ©termine la quantitĂ© de bruit ajoutĂ©e par pas de temps. Maintenant, nous ne voulons pas nĂ©cessairement faire cette opĂ©ration 500 fois pour obtenir $x_{500}$, nous avons donc une autre formule pour obtenir $x_t$ pour n’importe quel t Ă©tant donnĂ© $x_0$ :

    \[\begin{aligned} q(\mathbf{x}_t \vert \mathbf{x}_0) &= \mathcal{N}(\mathbf{x}_t; \sqrt{\bar{\alpha}_t} \mathbf{x}_0, {(1 - \bar{\alpha}_t)} \mathbf{I}) \end{aligned}\]

    oĂč :

    \[\bar{\alpha}_t = \prod_{i=1}^{T} \alpha_i,\quad \alpha_i = 1 - \beta_i\]

    La notation mathĂ©matique fait toujours peur ! Heureusement, le planificateur s’en charge pour nous. Nous pouvons tracer $\sqrt{\bar{\alpha}_t}$ (appelĂ© sqrt_alpha_prod) et $\sqrt{(1 - \bar{\alpha}_t)}$ (appelĂ© sqrt_one_minus_alpha_prod) pour voir comment l’entrĂ©e ($x$) et le bruit sont mis Ă  l’échelle et mĂ©langĂ©s Ă  travers diffĂ©rents pas de temps :

    plt.plot(noise_scheduler.alphas_cumprod.cpu() ** 0.5, label=r"${\sqrt{\bar{\alpha}_t}}$")
    plt.plot((1 - noise_scheduler.alphas_cumprod.cpu()) ** 0.5, label=r"$\sqrt{(1 - \bar{\alpha}_t)}$")
    plt.legend(fontsize="x-large");
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    ✏ À votre tour ! Vous pouvez explorer comment ce graphique change avec diffĂ©rents paramĂštres pour beta_start, beta_end et beta_schedule en remplaçant l’une des options commentĂ©es ci-dessous :

    ## Exemple avec beaucoup de bruit ajouté :
    # noise_scheduler = DDPMScheduler(num_train_timesteps=1000, beta_start=0.001, beta_end=0.004)
    
    ## Le planificateur cosinus pouvant s'avérer meilleur pour les images de petite taille :
    # noise_scheduler = DDPMScheduler(num_train_timesteps=1000, beta_schedule='squaredcos_cap_v2')
    

    Quel que soit le planificateur que vous avez choisi, nous pouvons maintenant l’utiliser pour ajouter du bruit en diffĂ©rentes quantitĂ©s en utilisant la fonction noise_scheduler.add_noise comme suit :

    timesteps = torch.linspace(0, 999, 8).long().to(device)
    noise = torch.randn_like(xb)
    noisy_xb = noise_scheduler.add_noise(xb, noise, timesteps)
    print("Noisy X shape", noisy_xb.shape)
    show_images(noisy_xb).resize((8 * 64, 64), resample=Image.NEAREST)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    LĂ  encore, Ă©tudiez l’effet de l’utilisation de diffĂ©rents planificateurs et paramĂštres de bruit. Cette vidĂ©o (en anglais) explique en dĂ©tail certains des calculs ci-dessus et constitue une excellente introduction Ă  certains de ces concepts.

    Définir le modÚle

    Nous en arrivons maintenant Ă  l’élĂ©ment central : le modĂšle lui-mĂȘme.

    La plupart des modùles de diffusion utilisent des architectures qui sont des variantes d’un U-net et c’est ce que nous utiliserons ici.

    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    En bref :

    • l’image en entrĂ©e du modĂšle passe par plusieurs blocs de couches ResNet, chacun divisant la taille de l’image par 2
    • puis elle passe Ă  travers le mĂȘme nombre de blocs qui la surĂ©chantillonnent.
    • il y a des skip connections qui relient les caractĂ©ristiques sur le chemin du sous-Ă©chantillonnage aux couches correspondantes dans le chemin du surĂ©chantillonnage.

    L’une des principales caractĂ©ristiques de ce modĂšle est qu’il prĂ©dit des images de la mĂȘme taille que l’entrĂ©e, ce qui est exactement ce dont nous avons besoin ici.

    đŸ€— Diffusers nous fournit une classe UNet2DModel pratique qui crĂ©e l’architecture dĂ©sirĂ©e dans PyTorch.

    CrĂ©ons un U-net pour la taille d’image dĂ©sirĂ©e. Notez que les down_block_types correspondent aux blocs de sous-Ă©chantillonnage (en vert sur le diagramme ci-dessus), et que les up_block_types sont les blocs de surĂ©chantillonnage (en rouge sur le diagramme) :

    from diffusers import UNet2DModel
    
    # Création d'un modÚle
    model = UNet2DModel(
        sample_size=image_size,  # la résolution de l'image cible
        in_channels=3,  # le nombre de canaux d'entrée, 3 pour les images RVB
        out_channels=3,  # le nombre de canaux de sortie
        layers_per_block=2,  # le nombre de couches ResNet Ă  utiliser par bloc UNet
        block_out_channels=(64, 128, 128, 256),  # Plus de canaux -> plus de paramĂštres
        down_block_types=(
            "DownBlock2D",  # un bloc de sous-échantillonnage ResNet standard
            "DownBlock2D",
            "AttnDownBlock2D",  # un bloc de sous-échantillonnage ResNet avec auto-attention spatiale
            "AttnDownBlock2D",
        ),
        up_block_types=(
            "AttnUpBlock2D",
            "AttnUpBlock2D",  # un bloc de suréchantillonnage ResNet avec auto-attention spatiale
            "UpBlock2D",
            "UpBlock2D",  # un bloc de suréchantillonnage ResNet standard
        ),
    )
    model.to(device)
    

    Lorsque vous traitez des donnĂ©es d’entrĂ©e en haute rĂ©solution, vous pouvez utiliser davantage de blocs descendants et ascendants, et ne conserver les couches d’attention que pour les couches de rĂ©solution les plus basses (infĂ©rieures) afin de rĂ©duire l’utilisation de la mĂ©moire. Nous verrons plus tard comment vous pouvez expĂ©rimenter pour trouver les meilleurs paramĂštres pour votre cas d’utilisation.

    Nous pouvons vĂ©rifier que le passage d’un batch de donnĂ©es et de pas de temps alĂ©atoires produit une sortie de mĂȘme forme que les donnĂ©es d’entrĂ©e :

    with torch.no_grad():
        model_prediction = model(noisy_xb, timesteps).sample
    model_prediction.shape
    

    Dans la section suivante, nous verrons comment entraĂźner ce modĂšle.

    CrĂ©er une boucle d’entraĂźnement

    Il est temps d’entraĂźner ! Voici une boucle d’optimisation typique dans PyTorch, oĂč nous parcourons les donnĂ©es batch par batch et mettons Ă  jour les paramĂštres de notre modĂšle Ă  chaque Ă©tape Ă  l’aide d’un optimiseur, ici, l’optimiseur AdamW avec un taux d’apprentissage de 0,0004.

    Pour chaque batch de données, nous

    • Ă©chantillonnons des pas de temps alĂ©atoires
    • bruitons les donnĂ©es en consĂ©quence
    • transmettons les donnĂ©es bruitĂ©es au modĂšle
    • comparons les prĂ©dictions du modĂšle avec la cible (c’est-Ă -dire le bruit dans ce cas) en utilisant l’erreur quadratique moyenne comme fonction de perte
    • mettons Ă  jour les paramĂštres du modĂšle via loss.backward() et optimizer.step().

    Au cours de ce processus, nous enregistrons aussi les pertes au fil du temps pour un tracé ultérieur.

    NB : ce code prend prĂšs de 10 minutes Ă  exĂ©cuter. N’hĂ©sitez pas Ă  sauter ces deux cellules et Ă  utiliser le modĂšle prĂ©-entraĂźnĂ© si vous ĂȘtes pressĂ©. Vous pouvez Ă©galement Ă©tudier comment la rĂ©duction du nombre de canaux dans chaque couche via la dĂ©finition du modĂšle ci-dessus peut accĂ©lĂ©rer les choses.

    L’exemple officiel d’entraĂźnement de đŸ€— Diffusers entraĂźne un modĂšle plus grand sur ce jeu de donnĂ©es Ă  une rĂ©solution plus Ă©levĂ©e, et constitue une bonne rĂ©fĂ©rence pour ce Ă  quoi ressemble une boucle d’entraĂźnement moins minimale :

    # Définir le planificateur de bruit
    noise_scheduler = DDPMScheduler(
        num_train_timesteps=1000, beta_schedule="squaredcos_cap_v2"
    )
    
    # Boucle d'entraĂźnement
    optimizer = torch.optim.AdamW(model.parameters(), lr=4e-4)
    
    losses = []
    
    for epoch in range(30):
        for step, batch in enumerate(train_dataloader):
            clean_images = batch["images"].to(device)
            # Exemple de bruit Ă  ajouter aux images
            noise = torch.randn(clean_images.shape).to(clean_images.device)
            bs = clean_images.shape[0]
    
            # Échantillonner un pas de temps alĂ©atoire pour chaque image
            timesteps = torch.randint(
                0, noise_scheduler.num_train_timesteps, (bs,), device=clean_images.device
            ).long()
    
            # Ajouter du bruit aux images propres en fonction de l'ampleur du bruit à chaque étape
            noisy_images = noise_scheduler.add_noise(clean_images, noise, timesteps)
    
            # Obtenir la prédiction du modÚle
            noise_pred = model(noisy_images, timesteps, return_dict=False)[0]
    
            # Calculer la perte
            loss = F.mse_loss(noise_pred, noise)
            loss.backward(loss)
            losses.append(loss.item())
    
            # Mise Ă  jour des paramĂštres du modĂšle Ă  l'aide de l'optimiseur
            optimizer.step()
            optimizer.zero_grad()
    
        if (epoch + 1) % 5 == 0:
            loss_last_epoch = sum(losses[-len(train_dataloader) :]) / len(train_dataloader)
            print(f"Epoch:{epoch+1}, loss: {loss_last_epoch}")
    

    En traçant la perte, nous constatons que le modĂšle s’amĂ©liore rapidement dans un premier temps, puis continue Ă  s’amĂ©liorer Ă  un rythme plus lent (ce qui est plus Ă©vident si nous utilisons une Ă©chelle logarithmique, comme indiquĂ© Ă  droite) :

    fig, axs = plt.subplots(1, 2, figsize=(12, 4))
    axs[0].plot(losses)
    axs[1].plot(np.log(losses))
    plt.show()
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Au lieu d’exĂ©cuter le code d’entraĂźnement ci-dessus, vous pouvez utiliser le modĂšle du pipeline comme suit :

    ## Décommenter pour charger le modÚle que j'ai entraßné plus tÎt à la place :
    # model = butterfly_pipeline.unet
    

    Générer des images

    Comment obtenir des images avec ce modĂšle ?

    ‱ Option 1 : CrĂ©ation d’un pipeline :

    from diffusers import DDPMPipeline
    
    image_pipe = DDPMPipeline(unet=model, scheduler=noise_scheduler)
    
    pipeline_output = image_pipe()
    pipeline_output.images[0]
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Nous pouvons enregistrer un pipeline dans un dossier local comme suit :

    image_pipe.save_pretrained("my_pipeline")
    

    Inspection du contenu du dossier :

    !ls my_pipeline/
    
    model_index.json  scheduler  unet
    

    Les sous-dossiers scheduler et unet contiennent tout ce qui est nĂ©cessaire pour recrĂ©er ces composants. Par exemple, dans le dossier unet vous trouverez les poids du modĂšle (diffusion_pytorch_model.bin) ainsi qu’un fichier de configuration qui spĂ©cifie l’architecture UNet.

    !ls my_pipeline/unet/
    
    config.json  diffusion_pytorch_model.bin
    

    Ensemble, ces fichiers contiennent tout ce qui est nĂ©cessaire pour recrĂ©er le pipeline. Vous pouvez les tĂ©lĂ©charger manuellement sur le Hub pour partager le pipeline avec d’autres personnes, ou consulter le code pour le faire via l’API dans la section suivante.

    ‱ Option 2 : Ă©crire une boucle d’échantillonnage

    Si vous inspectez la méthode forward du pipeline, vous pourrez voir ce qui se passe lorsque nous lançons image_pipe() :

    # ??image_pipe.forward
    

    Nous commençons par un bruit alĂ©atoire et parcourons les pas de temps de l’ordonnanceur du plus bruyant au moins bruyant, en supprimant une petite quantitĂ© de bruit Ă  chaque Ă©tape sur la base de la prĂ©diction du modĂšle :

    # Point de départ aléatoire (8 images aléatoires) :
    sample = torch.randn(8, 3, 32, 32).to(device)
    
    for i, t in enumerate(noise_scheduler.timesteps):
    
        # Obtenir le modÚle de prédiction
        with torch.no_grad():
            residual = model(sample, t).sample
    
        # Mise à jour de l'échantillon avec le pas
        sample = noise_scheduler.step(residual, t, sample).prev_sample
    
    show_images(sample)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    La fonction noise_scheduler.step() effectue les calculs nĂ©cessaires pour mettre Ă  jour sample de maniĂšre appropriĂ©e. Il existe un certain nombre de mĂ©thodes d’échantillonnage. Dans l’unitĂ© suivante, nous verrons comment nous pouvons Ă©changer un Ă©chantillonneur diffĂ©rent pour accĂ©lĂ©rer la gĂ©nĂ©ration d’images avec des modĂšles existants, et nous parlerons plus en dĂ©tail de la thĂ©orie derriĂšre l’échantillonnage des modĂšles de diffusion.

    Pousser votre modĂšle vers le Hub

    Dans l’exemple ci-dessus, nous avons enregistrĂ© notre pipeline dans un dossier local. Pour pousser notre modĂšle vers le Hub, nous aurons besoin d’un dĂ©pĂŽt de modĂšles dans lequel nous pourrons pousser nos fichiers. Nous dĂ©terminerons le nom du dĂ©pĂŽt Ă  partir de l’ID du modĂšle que nous voulons donner Ă  notre modĂšle (n’hĂ©sitez pas Ă  remplacer le nom du modĂšle par votre propre choix ; il doit juste contenir votre nom d’utilisateur, ce que fait la fonction get_full_repo_name()) :

    from huggingface_hub import get_full_repo_name
    
    model_name = "sd-class-butterflies-32"
    hub_model_id = get_full_repo_name(model_name)
    hub_model_id
    

    Ensuite, crĂ©er un dĂ©pĂŽt de modĂšle sur le đŸ€— Hub et pousser notre modĂšle :

    from huggingface_hub import HfApi, create_repo
    
    create_repo(hub_model_id)
    api = HfApi()
    api.upload_folder(
        folder_path="my_pipeline/scheduler", path_in_repo="", repo_id=hub_model_id
    )
    api.upload_folder(folder_path="my_pipeline/unet", path_in_repo="", repo_id=hub_model_id)
    api.upload_file(
        path_or_fileobj="my_pipeline/model_index.json",
        path_in_repo="model_index.json",
        repo_id=hub_model_id,
    )
    

    La derniĂšre chose Ă  faire est de crĂ©er une belle carte modĂšle afin que notre gĂ©nĂ©rateur de papillons puisse ĂȘtre facilement trouvĂ© sur le đŸ€— Hub (n’hĂ©sitez pas Ă  dĂ©velopper et Ă  modifier la description !) :

    from huggingface_hub import ModelCard
    
    content = f"""
    ---
    license: mit
    tags:
    - pytorch
    - diffusers
    - unconditional-image-generation
    - diffusion-models-class
    ---
    
    # Model Card for Unit 1 of the [Diffusion Models Class 🧹](https://github.com/huggingface/diffusion-models-class)
    
    This model is a diffusion model for unconditional image generation of cute 🩋.
    
    ## Usage
    
    
    ```python
    from diffusers import DDPMPipeline
    
    pipeline = DDPMPipeline.from_pretrained('{hub_model_id}')
    image = pipeline().images[0]
    image
    ```python
    """
    
    card = ModelCard(content)
    card.push_to_hub(hub_model_id)
    

    Maintenant que le modĂšle est sur le Hub, vous pouvez le tĂ©lĂ©charger de n’importe oĂč en utilisant la mĂ©thode from_pretrained() de DDPMPipeline comme suit :

    from diffusers import DDPMPipeline
    
    image_pipe = DDPMPipeline.from_pretrained(hub_model_id)
    pipeline_output = image_pipe()
    pipeline_output.images[0]
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Bien, ça marche !

    Passer Ă  l’échelle supĂ©rieure avec đŸ€— Accelerate

    Ce notebook a Ă©tĂ© conçu Ă  des fins d’apprentissage, et en tant que tel, nous avons essayĂ© de garder le code aussi minimal et propre que possible. Pour cette raison, nous avons omis certaines choses que vous pourriez souhaiter si vous deviez entraĂźner un modĂšle plus grand sur beaucoup plus de donnĂ©es, comme le support multi-GPU, la trace de la progression et des images d’exemple, la sauvegarde du gradient pour supporter des tailles de batch plus importantes, le tĂ©lĂ©chargement automatique des modĂšles et ainsi de suite. Heureusement, la plupart de ces fonctionnalitĂ©s sont disponibles dans l’exemple de script d’entraĂźnement ici..

    Vous pouvez télécharger le fichier comme suit :

    !wget https://github.com/huggingface/diffusers/raw/main/examples/unconditional_image_generation/train_unconditional.py
    

    Ouvrez le fichier et vous verrez oĂč le modĂšle est dĂ©fini et quels sont les paramĂštres disponibles. Nous exĂ©cutons le script Ă  l’aide de la commande suivante :

    # Donnons un nom Ă  notre nouveau modĂšle pour le Hub
    model_name = "sd-class-butterflies-64"
    hub_model_id = get_full_repo_name(model_name)
    
    !accelerate launch train_unconditional.py \
      --dataset_name="huggan/smithsonian_butterflies_subset" \
      --resolution=64 \
      --output_dir={model_name} \
      --train_batch_size=32 \
      --num_epochs=50 \
      --gradient_accumulation_steps=1 \
      --learning_rate=1e-4 \
      --lr_warmup_steps=500 \
      --mixed_precision="no"
    

    Comme prĂ©cĂ©demment, poussons le modĂšle vers le Hub et crĂ©ons une belle carte de modĂšle (et n’hĂ©sitez pas Ă  l’éditer comme vous le souhaitez !):

    create_repo(hub_model_id)
    api = HfApi()
    api.upload_folder(
        folder_path=f"{model_name}/scheduler", path_in_repo="", repo_id=hub_model_id
    )
    api.upload_folder(
        folder_path=f"{model_name}/unet", path_in_repo="", repo_id=hub_model_id
    )
    api.upload_file(
        path_or_fileobj=f"{model_name}/model_index.json",
        path_in_repo="model_index.json",
        repo_id=hub_model_id,
    )
    
    content = f"""
    ---
    license: mit
    tags:
    - pytorch
    - diffusers
    - unconditional-image-generation
    - diffusion-models-class
    ---
    
    # Model Card for Unit 1 of the [Diffusion Models Class 🧹](https://github.com/huggingface/diffusion-models-class)
    
    This model is a diffusion model for unconditional image generation of cute 🩋.
    
    ## Usage
    
    ```python
    from diffusers import DDPMPipeline
    
    pipeline = DDPMPipeline.from_pretrained('{hub_model_id}')
    image = pipeline().images[0]
    image
    ```python
    """
    
    card = ModelCard(content)
    card.push_to_hub(hub_model_id)
    

    Environ 45 minutes plus tard, voici le résultat :

    pipeline = DDPMPipeline.from_pretrained(hub_model_id).to(device)
    images = pipeline(batch_size=8).images
    make_grid(images)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    ✏ À votre tour ! Essayez de trouver des paramĂštres d’entraĂźnement/de modĂšle qui donnent de bons rĂ©sultats en un minimum de temps, et partagez vos rĂ©sultats avec la communautĂ©. Fouillez dans le script pour voir si vous pouvez comprendre le code, et demandez des Ă©claircissements sur tout ce qui vous semble confus.

    Pistes pour approndonfir

    Nous espĂ©rons vous avoir donnĂ© un avant-goĂ»t de ce que vous pouvez faire avec la bibliothĂšque đŸ€— Diffusers ! Voici quelques pistes possibles pour la suite :

    • Essayez d’entraĂźner un modĂšle de diffusion inconditionnel sur un nouveau jeu de donnĂ©es. Points bonus si vous en crĂ©ez un vous-mĂȘme. Vous pouvez trouver d’excellents jeux de donnĂ©es d’images pour cette tĂąche dans l’organisation HugGan sur le Hub. Assurez-vous simplement de les sous-Ă©chantillonner si vous ne voulez pas attendre trĂšs longtemps pour que le modĂšle s’entraĂźne !
    • Essayez DreamBooth pour crĂ©er votre propre pipeline de Stable Diffusion personnalisĂ© en utilisant ce Space ou ce notebook.
    • Modifiez le script d’entraĂźnement pour explorer diffĂ©rents hyperparamĂštres UNet (nombre de couches, canaux, etc.), diffĂ©rents schĂ©mas de bruit, etc.
    • Consultez le notebook ImplĂ©mentation Ă  partir de 0 pour une approche diffĂ©rente des idĂ©es fondamentales que nous avons abordĂ©es dans cette unitĂ©.

    1.2. Implémentation à partir de 0

    Il est parfois utile de considĂ©rer la version la plus simple possible d’une chose pour mieux en comprendre le fonctionnement. C’est ce que nous allons essayer de faire dans ce notebook, en commençant par un modĂšle de diffusion jouet pour voir comment les diffĂ©rents Ă©lĂ©ments fonctionnent, puis en examinant en quoi ils diffĂšrent d’une mise en Ɠuvre plus complexe.

    Nous examinerons :

    • Le processus de corruption (ajouter du bruit aux donnĂ©es)
    • Ce qu’est un UNet, et comment en implĂ©menter un extrĂȘmement minimal Ă  partir de zĂ©ro
    • L’entraĂźnement au modĂšle de diffusion
    • La thĂ©orie de l’échantillonnage

    Ensuite, nous comparerons nos versions avec l’implĂ©mentation DDPM des diffuseurs, en explorant :

    • Les amĂ©liorations par rapport Ă  notre mini UNet
    • Le schĂ©ma de bruit du DDPM
    • Les diffĂ©rences dans l’objectif d’entraĂźnement
    • Le conditionnement du pas de temps
    • Les approches d’échantillonnage

    Ce notebook est assez approfondi, et peut ĂȘtre sautĂ© en toute sĂ©curitĂ© si vous n’ĂȘtes pas enthousiaste Ă  l’idĂ©e d’une plongĂ©e en profondeur Ă  partir de zĂ©ro !

    Il convient Ă©galement de noter que la plupart du code ici est utilisĂ© Ă  des fins d’illustration, et nous ne recommandons pas de l’adopter directement pour votre propre travail (Ă  moins que vous n’essayiez d’amĂ©liorer les exemples montrĂ©s ici Ă  des fins d’apprentissage).

    Configuration et importations

    !pip install -q diffusers
    
    import torch
    import torchvision
    from torch import nn
    from torch.nn import functional as F
    from torch.utils.data import DataLoader
    from diffusers import DDPMScheduler, UNet2DModel
    from matplotlib import pyplot as plt
    
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    print(f'Using device: {device}')
    

    Les données

    Nous allons tester les choses avec un trĂšs petit jeu de donnĂ©es : MNIST. Si vous souhaitez donner au modĂšle un dĂ©fi un peu plus difficile Ă  relever sans rien changer d’autre, torchvision.datasets.FashionMNIST devrait faire l’affaire.

    dataset = torchvision.datasets.MNIST(root="mnist/", train=True, download=True, transform=torchvision.transforms.ToTensor())
    
    train_dataloader = DataLoader(dataset, batch_size=8, shuffle=True)
    
    x, y = next(iter(train_dataloader))
    print('Input shape:', x.shape)
    print('Labels:', y)
    plt.imshow(torchvision.utils.make_grid(x)[0], cmap='Greys')
    
    Input shape: torch.Size([8, 1, 28, 28]) 
    Labels: tensor([1, 9, 7, 3, 5, 2, 1, 4])
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Chaque image est un dessin en niveaux de gris de 28 par 28 pixels d’un chiffre, avec des valeurs allant de 0 à 1.

    Le processus de corruption

    Supposons que vous n’ayez lu aucun papier sur les modùles de diffusion, mais que vous sachiez que le processus implique l’ajout de bruit. Comment feriez-vous ?

    Nous souhaitons probablement disposer d’un moyen simple de contrĂŽler le degrĂ© de corruption. Et si nous prenions un paramĂštre pour la quantitĂ© de bruit Ă  ajouter, et que nous le faisions :

    noise = torch.rand_like(x)
    
    noisy_x = (1-amount)*x + amount*noise
    

    Si amount = 0, nous rĂ©cupĂ©rons l’entrĂ©e sans aucun changement. Si le montant atteint $1$, nous rĂ©cupĂ©rons du bruit sans aucune trace de l’entrĂ©e $x$. En mĂ©langeant l’entrĂ©e avec du bruit de cette façon, nous gardons la sortie dans la mĂȘme plage ($0$ Ă  $1$).

    Nous pouvons mettre cela en Ɠuvre assez facilement (il suffit de surveiller les formes pour ne pas se faire piĂ©ger par les rĂšgles de diffusion) :

    def corrupt(x, amount):
      """Corrompre l'entrée `x` en la mélangeant avec du bruit selon `amount`"""
      noise = torch.rand_like(x)
      amount = amount.view(-1, 1, 1, 1) # Trier les formes pour que la transmission fonctionne
      return x*(1-amount) + noise*amount 
    

    Et regarder les résultats visuellement pour voir que cela fonctionne comme prévu :

    # Tracer les données d'entrée
    fig, axs = plt.subplots(2, 1, figsize=(12, 5))
    axs[0].set_title('Input data')
    axs[0].imshow(torchvision.utils.make_grid(x)[0], cmap='Greys')
    
    # Ajouter du bruit
    amount = torch.linspace(0, 1, x.shape[0]) # De gauche Ă  droite -> plus de corruption
    noised_x = corrupt(x, amount)
    
    # Tracé de la version bruitée
    axs[1].set_title('Corrupted data (-- amount increases -->)')
    axs[1].imshow(torchvision.utils.make_grid(noised_x)[0], cmap='Greys')
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Lorsque la quantitĂ© de bruit s’approche de 1, nos donnĂ©es commencent Ă  ressembler Ă  du bruit alĂ©atoire pur. Mais pour la plupart des noise_amounts, vous pouvez deviner le chiffre assez bien. Pensez-vous que cela soit optimal ?

    Le modĂšle

    Nous aimerions un modĂšle qui prenne en compte des images bruitĂ©es de 28px et qui produise une prĂ©diction de la mĂȘme forme. Un choix populaire ici est une architecture appelĂ©e UNet. InventĂ© Ă  l’origine pour les tĂąches de segmentation en imagerie mĂ©dicale, un UNet se compose d’un “chemin de compression” par lequel les donnĂ©es sont comprimĂ©es et d’un “chemin d’expansion” par lequel elles s’étendent Ă  nouveau jusqu’à la dimension d’origine (similaire Ă  un autoencodeur), mais il comporte Ă©galement des connexions de saut qui permettent aux informations et aux gradients de circuler Ă  diffĂ©rents niveaux.

    Certains UNets comportent des blocs complexes Ă  chaque Ă©tape, mais pour cette petite dĂ©monstration, nous construirons un exemple minimal qui prend une image Ă  un canal et la fait passer par trois couches convolutives sur le chemin descendant (les down_layers dans le diagramme et le code) et trois sur le chemin ascendant, avec des sauts de connexion entre les couches descendantes et ascendantes. Nous utiliserons max pooling pour le downsampling et nn.Upsample pour le upsampling plutĂŽt que de nous appuyer sur des couches apprenantes comme les UNets plus complexes. Voici l’architecture approximative montrant le nombre de canaux dans la sortie de chaque couche :

    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Voici Ă  quoi cela ressemble dans le code :

    class BasicUNet(nn.Module):
        """Une mise en Ɠuvre minimale du UNet"""
        def __init__(self, in_channels=1, out_channels=1):
            super().__init__()
            self.down_layers = torch.nn.ModuleList([ 
                nn.Conv2d(in_channels, 32, kernel_size=5, padding=2),
                nn.Conv2d(32, 64, kernel_size=5, padding=2),
                nn.Conv2d(64, 64, kernel_size=5, padding=2),
            ])
            self.up_layers = torch.nn.ModuleList([
                nn.Conv2d(64, 64, kernel_size=5, padding=2),
                nn.Conv2d(64, 32, kernel_size=5, padding=2),
                nn.Conv2d(32, out_channels, kernel_size=5, padding=2), 
            ])
            self.act = nn.SiLU() # La fonction d'activation
            self.downscale = nn.MaxPool2d(2)
            self.upscale = nn.Upsample(scale_factor=2)
    
        def forward(self, x):
            h = []
            for i, l in enumerate(self.down_layers):
                x = self.act(l(x)) # À travers la couche et la fonction d'activation
                if i < 2: # Pour toutes les couches sauf la troisiĂšme (derniĂšre) :
                  h.append(x) # Stockage de la sortie pour la skip connexion 
                  x = self.downscale(x) # Réduction d'échelle pour la couche suivante
                  
            for i, l in enumerate(self.up_layers):
                if i > 0:
                  x = self.upscale(x) # Upscale
                  x += h.pop() # Récupération d'un résultat stocké (skip connection)
                x = self.act(l(x)) # Par le biais de la couche et de la fonction d'activation
                
            return x
    

    Nous pouvons vĂ©rifier que la forme de la sortie est la mĂȘme que celle de l’entrĂ©e, comme nous nous y attendions :

    net = BasicUNet()
    x = torch.rand(8, 1, 28, 28)
    net(x).shape
    
    torch.Size([8, 1, 28, 28])
    

    Ce réseau compte un peu plus de 300 000 paramÚtres :

    sum([p.numel() for p in net.parameters()])
    
    309057
    

    Vous pouvez envisager de modifier le nombre de canaux dans chaque couche ou d’intervertir les architectures si vous le souhaitez.

    Entraßner le réseau

    Que doit faire exactement le modĂšle ? LĂ  encore, il y a plusieurs façons de procĂ©der, mais pour cette dĂ©monstration, choisissons un cadre simple : Ă©tant donnĂ© une entrĂ©e corrompue noisy_x, le modĂšle doit produire sa meilleure estimation de ce Ă  quoi ressemble l’original $x$. Nous comparerons cette valeur Ă  la valeur rĂ©elle par le biais de l’erreur quadratique moyenne. Nous comparerons cette estimation Ă  la valeur rĂ©elle par le biais de l’erreur quadratique moyenne.

    Nous pouvons maintenant entraßner le réseau.

    • Obtenir un batch de donnĂ©es
    • Corrompre les donnĂ©es de maniĂšre alĂ©atoire
    • Nourrir le modĂšle avec ces donnĂ©es
    • Comparer les prĂ©dictions du modĂšle avec les images propres pour calculer notre perte
    • Mettre Ă  jour les paramĂštres du modĂšle en consĂ©quence.

    N’hĂ©sitez pas Ă  modifier ce modĂšle et Ă  voir si vous pouvez l’amĂ©liorer !

    # Chargeur de données (vous pouvez modifier la taille des batchs)
    batch_size = 128
    train_dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
    
    # Combien de fois devrions-nous passer les données en revue ?
    n_epochs = 3
    
    # Créer le réseau
    net = BasicUNet()
    net.to(device)
    
    # Notre fonction de perte
    loss_fn = nn.MSELoss()
    
    # L'optimiseur
    opt = torch.optim.Adam(net.parameters(), lr=1e-3) 
    
    # Conserver une trace des pertes pour les consulter ultérieurement
    losses = []
    
    # La boucle d'entraĂźnement
    for epoch in range(n_epochs):
    
        for x, y in train_dataloader:
    
            # Obtenir des données et préparer la version corrompue
            x = x.to(device) # Data on the GPU
            noise_amount = torch.rand(x.shape[0]).to(device) # Pick random noise amounts
            noisy_x = corrupt(x, noise_amount) # Create our noisy x
    
            # Obtenir la prédiction du modÚle
            pred = net(noisy_x)
    
            # Calculer la perte
            loss = loss_fn(pred, x) # Dans quelle mesure la sortie est-elle proche du véritable x "propre" ?
    
            # Rétropropager et mettre à jour les paramÚtres
            opt.zero_grad()
            loss.backward()
            opt.step()
    
            # Stocker la perte pour plus tard
            losses.append(loss.item())
    
        # Afficher la moyenne des valeurs de perte pour cette époque :
        avg_loss = sum(losses[-len(train_dataloader):])/len(train_dataloader)
        print(f'Finished epoch {epoch}. Average loss for this epoch: {avg_loss:05f}')
    
    # Visualiser la courbe des pertes
    plt.plot(losses)
    plt.ylim(0, 0.1)
    
    Finished epoch 0. Average loss for this epoch: 0.026736 
    Finished epoch 1. Average loss for this epoch: 0.020692 
    Finished epoch 2. Average loss for this epoch: 0.018887
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Nous pouvons essayer de voir à quoi ressemblent les prédictions du modÚle en saisissant un batch de données, en les corrompant à différents degrés et en visualisant ensuite les prédictions du modÚle :

    # Récupérer des données
    x, y = next(iter(train_dataloader))
    x = x[:8] # Seuls les 8 premiers sont utilisés pour faciliter le graphique
    
    # Corruption avec une échelle de montants
    amount = torch.linspace(0, 1, x.shape[0]) # De gauche Ă  droite -> plus de corruption
    noised_x = corrupt(x, amount)
    
    # Obtenir les prédictions du modÚle
    with torch.no_grad():
      preds = net(noised_x.to(device)).detach().cpu()
    
    # Graphique
    fig, axs = plt.subplots(3, 1, figsize=(12, 7))
    axs[0].set_title('Input data')
    axs[0].imshow(torchvision.utils.make_grid(x)[0].clip(0, 1), cmap='Greys')
    axs[1].set_title('Corrupted data')
    axs[1].imshow(torchvision.utils.make_grid(noised_x)[0].clip(0, 1), cmap='Greys')
    axs[2].set_title('Network Predictions')
    axs[2].imshow(torchvision.utils.make_grid(preds)[0].clip(0, 1), cmap='Greys
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Vous pouvez constater que pour les montants les plus faibles, les prĂ©dictions sont plutĂŽt bonnes ! Mais lorsque le niveau devient trĂšs Ă©levĂ©, le modĂšle a moins d’élĂ©ments pour travailler, et lorsque nous arrivons Ă  amount=1, il produit un dĂ©sordre flou proche de la moyenne du jeu de donnĂ©es pour essayer de couvrir ses paris sur ce Ă  quoi la sortie pourrait ressembler


    Échantillonnage

    Si nos prédictions à des niveaux de bruit élevés ne sont pas trÚs bonnes, comment générer des images ?

    Et si nous partions d’un bruit alĂ©atoire, que nous regardions les prĂ©dictions du modĂšle, mais que nous ne nous rapprochions que trĂšs peu de cette prĂ©diction (disons, 20 % du chemin). Nous disposons alors d’une image trĂšs bruyante dans laquelle il y a peut-ĂȘtre un soupçon de structure, que nous pouvons introduire dans le modĂšle pour obtenir une nouvelle prĂ©diction. Nous espĂ©rons que cette nouvelle prĂ©diction est lĂ©gĂšrement meilleure que la premiĂšre (puisque notre point de dĂ©part est lĂ©gĂšrement moins bruitĂ©) et que nous pouvons donc faire un autre petit pas avec cette nouvelle et meilleure prĂ©diction.

    Nous rĂ©pĂ©tons l’opĂ©ration plusieurs fois et (si tout se passe bien) nous obtenons une image ! Voici ce processus illustrĂ© en seulement 5 Ă©tapes, en visualisant l’entrĂ©e du modĂšle (Ă  gauche) et les images dĂ©bruitĂ©es prĂ©dites (Ă  droite) Ă  chaque Ă©tape. Notez que mĂȘme si le modĂšle prĂ©dit l’image dĂ©bruitĂ©e dĂšs l’étape 1, nous ne faisons qu’une partie du chemin. Au fil des Ă©tapes, les structures apparaissent et sont affinĂ©es, jusqu’à ce que nous obtenions nos rĂ©sultats finaux.

    n_steps = 5
    x = torch.rand(8, 1, 28, 28).to(device) # Commencer au hasard
    step_history = [x.detach().cpu()]
    pred_output_history = []
    
    for i in range(n_steps):
        with torch.no_grad(): # Pas besoin de suivre les gradients pendant l'inférence
            pred = net(x) # Prédire le x0 débruité
        pred_output_history.append(pred.detach().cpu()) # Stocker les résultats du modÚle pour les tracer
        mix_factor = 1/(n_steps - i) # Dans quelle mesure nous nous rapprochons de la prédiction
        x = x*(1-mix_factor) + pred*mix_factor # Déplacer une partie du chemin
        step_history.append(x.detach().cpu()) # Stocker l'étape pour le graphique
    
    fig, axs = plt.subplots(n_steps, 2, figsize=(9, 4), sharex=True)
    axs[0,0].set_title('x (model input)')
    axs[0,1].set_title('model prediction')
    for i in range(n_steps):
        axs[i, 0].imshow(torchvision.utils.make_grid(step_history[i])[0].clip(0, 1), cmap='Greys')
        axs[i, 1].imshow(torchvision.utils.make_grid(pred_output_history[i])[0].clip(0, 1), cmap='Greys')
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Nous pouvons diviser le processus en plusieurs étapes et espérer ainsi obtenir de meilleures images :

    n_steps = 40
    x = torch.rand(64, 1, 28, 28).to(device)
    for i in range(n_steps):
      noise_amount = torch.ones((x.shape[0], )).to(device) * (1-(i/n_steps)) # Starting high going low
      with torch.no_grad():
        pred = net(x)
      mix_factor = 1/(n_steps - i)
      x = x*(1-mix_factor) + pred*mix_factor
    fig, ax = plt.subplots(1, 1, figsize=(12, 12))
    ax.imshow(torchvision.utils.make_grid(x.detach().cpu(), nrow=8)[0].clip(0, 1), cmap='Greys')
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Ce n’est pas gĂ©nial, mais il y a des chiffres reconnaissables ! Vous pouvez expĂ©rimenter en entraĂźnant plus longtemps (disons, 10 ou 20 Ă©poques) et en modifiant la configuration du modĂšle, le taux d’apprentissage, l’optimiseur, etc. N’oubliez pas non plus que fashionMNIST peut ĂȘtre remplacĂ© en une ligne si vous voulez essayer un jeu de donnĂ©es un peu plus difficile.

    Comparaison avec DDPM

    Dans cette section, nous allons voir comment notre implĂ©mentation diffĂšre de l’approche utilisĂ©e dans l’autre notebook (Introduction Ă  Diffusers), qui est basĂ© sur l’article de DDPM.

    Nous verrons que

    • Le diffuseur UNet2DModel est un peu plus avancĂ© que notre BasicUNet
    • Le processus de corruption est traitĂ© diffĂ©remment
    • L’objectif d’entraĂźnement est diffĂ©rent, puisqu’il s’agit de prĂ©dire le bruit plutĂŽt que l’image dĂ©bruitĂ©e.
    • Le modĂšle est conditionnĂ© sur la quantitĂ© de bruit prĂ©sent via un conditionnement par pas de temps, oĂč t est transmis comme un argument supplĂ©mentaire Ă  la mĂ©thode forward.
    • Il existe un certain nombre de stratĂ©gies d’échantillonnage diffĂ©rentes, qui devraient fonctionner mieux que notre version simpliste ci-dessus.

    Un certain nombre d’amĂ©liorations ont Ă©tĂ© suggĂ©rĂ©es depuis la publication de l’article sur le DDPM, mais nous espĂ©rons que cet exemple est instructif en ce qui concerne les diffĂ©rentes dĂ©cisions de conception possibles. Une fois que vous aurez lu cet article, vous pourrez vous plonger dans le document intitulĂ© Elucidating the Design Space of Diffusion-Based Generative Models qui examine tous ces composants en dĂ©tail et formule de nouvelles recommandations sur la maniĂšre d’obtenir les meilleures performances.

    Si tout cela est trop technique ou intimidant, ne vous inquiĂ©tez pas ! N’hĂ©sitez pas Ă  sauter le reste de ce notebook ou Ă  le garder pour un jour de pluie.

    L’UNet

    Le modĂšle UNet2DModel de đŸ€— Diffusers comporte un certain nombre d’amĂ©liorations par rapport Ă  notre UNet de base ci-dessus :

    • GroupNorm applique une normalisation par groupe aux entrĂ©es de chaque bloc
    • Couches de dropout pour un entraĂźnement plus doux
    • Plusieurs couches de ResNet par bloc (si layers_per_block n’est pas fixĂ© Ă  1)
    • Attention (gĂ©nĂ©ralement utilisĂ© uniquement pour les blocs Ă  faible rĂ©solution)
    • Conditionnement sur le pas de temps
    • Blocs de sous-Ă©chantillonnage et de surĂ©chantillonnage avec des paramĂštres pouvant ĂȘtre appris

    Créons et inspectons un modÚle UNet2DModel :

    model = UNet2DModel(
        sample_size=28,           # la résolution de l'image cible
        in_channels=1,            # le nombre de canaux d'entrée, 3 pour les images RVB
        out_channels=1,           # le nombre de canaux de sortie
        layers_per_block=2,       # le nombre de couches ResNet Ă  utiliser par bloc UNet
        block_out_channels=(32, 64, 64), # Correspondant Ă  peu prĂšs Ă  notre exemple UNet de base
        down_block_types=( 
            "DownBlock2D",        # un bloc de sous-échantillonnage ResNet normal
            "AttnDownBlock2D",    # un bloc de sous-échantillonnage ResNet avec auto-attention spatiale
            "AttnDownBlock2D",
        ), 
        up_block_types=(
            "AttnUpBlock2D", 
            "AttnUpBlock2D",      # un bloc de suréchantillonnage ResNet avec auto-attention spatiale
            "UpBlock2D",          # un bloc de suréchantillonnage ResNet standard
          ),
    )
    print(model)
    
    Afficher / masquer la sortie de print(model)
    UNet2DModel(
      (conv_in): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
      (time_proj): Timesteps()
      (time_embedding): TimestepEmbedding(
        (linear_1): Linear(in_features=32, out_features=128, bias=True)
        (act): SiLU()
        (linear_2): Linear(in_features=128, out_features=128, bias=True)
      )
      (down_blocks): ModuleList(
        (0): DownBlock2D(
          (resnets): ModuleList(
            (0): ResnetBlock2D(
              (norm1): GroupNorm(32, 32, eps=1e-05, affine=True)
              (conv1): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (time_emb_proj): Linear(in_features=128, out_features=32, bias=True)
              (norm2): GroupNorm(32, 32, eps=1e-05, affine=True)
              (dropout): Dropout(p=0.0, inplace=False)
              (conv2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (nonlinearity): SiLU()
            )
            (1): ResnetBlock2D(
              (norm1): GroupNorm(32, 32, eps=1e-05, affine=True)
              (conv1): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (time_emb_proj): Linear(in_features=128, out_features=32, bias=True)
              (norm2): GroupNorm(32, 32, eps=1e-05, affine=True)
              (dropout): Dropout(p=0.0, inplace=False)
              (conv2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (nonlinearity): SiLU()
            )
          )
          (downsamplers): ModuleList(
            (0): Downsample2D(
              (conv): Conv2d(32, 32, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
            )
          )
        )
        (1): AttnDownBlock2D(
          (attentions): ModuleList(
            (0): AttentionBlock(
              (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
              (query): Linear(in_features=64, out_features=64, bias=True)
              (key): Linear(in_features=64, out_features=64, bias=True)
              (value): Linear(in_features=64, out_features=64, bias=True)
              (proj_attn): Linear(in_features=64, out_features=64, bias=True)
            )
            (1): AttentionBlock(
              (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
              (query): Linear(in_features=64, out_features=64, bias=True)
              (key): Linear(in_features=64, out_features=64, bias=True)
              (value): Linear(in_features=64, out_features=64, bias=True)
              (proj_attn): Linear(in_features=64, out_features=64, bias=True)
            )
          )
          (resnets): ModuleList(
            (0): ResnetBlock2D(
              (norm1): GroupNorm(32, 32, eps=1e-05, affine=True)
              (conv1): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
              (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
              (dropout): Dropout(p=0.0, inplace=False)
              (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (nonlinearity): SiLU()
              (conv_shortcut): Conv2d(32, 64, kernel_size=(1, 1), stride=(1, 1))
            )
            (1): ResnetBlock2D(
              (norm1): GroupNorm(32, 64, eps=1e-05, affine=True)
              (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
              (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
              (dropout): Dropout(p=0.0, inplace=False)
              (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (nonlinearity): SiLU()
            )
          )
          (downsamplers): ModuleList(
            (0): Downsample2D(
              (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
            )
          )
        )
        (2): AttnDownBlock2D(
          (attentions): ModuleList(
            (0): AttentionBlock(
              (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
              (query): Linear(in_features=64, out_features=64, bias=True)
              (key): Linear(in_features=64, out_features=64, bias=True)
              (value): Linear(in_features=64, out_features=64, bias=True)
              (proj_attn): Linear(in_features=64, out_features=64, bias=True)
            )
            (1): AttentionBlock(
              (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
              (query): Linear(in_features=64, out_features=64, bias=True)
              (key): Linear(in_features=64, out_features=64, bias=True)
              (value): Linear(in_features=64, out_features=64, bias=True)
              (proj_attn): Linear(in_features=64, out_features=64, bias=True)
            )
          )
          (resnets): ModuleList(
            (0): ResnetBlock2D(
              (norm1): GroupNorm(32, 64, eps=1e-05, affine=True)
              (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
              (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
              (dropout): Dropout(p=0.0, inplace=False)
              (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (nonlinearity): SiLU()
            )
            (1): ResnetBlock2D(
              (norm1): GroupNorm(32, 64, eps=1e-05, affine=True)
              (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
              (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
              (dropout): Dropout(p=0.0, inplace=False)
              (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (nonlinearity): SiLU()
            )
          )
        )
      )
      (up_blocks): ModuleList(
        (0): AttnUpBlock2D(
          (attentions): ModuleList(
            (0): AttentionBlock(
              (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
              (query): Linear(in_features=64, out_features=64, bias=True)
              (key): Linear(in_features=64, out_features=64, bias=True)
              (value): Linear(in_features=64, out_features=64, bias=True)
              (proj_attn): Linear(in_features=64, out_features=64, bias=True)
            )
            (1): AttentionBlock(
              (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
              (query): Linear(in_features=64, out_features=64, bias=True)
              (key): Linear(in_features=64, out_features=64, bias=True)
              (value): Linear(in_features=64, out_features=64, bias=True)
              (proj_attn): Linear(in_features=64, out_features=64, bias=True)
            )
            (2): AttentionBlock(
              (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
              (query): Linear(in_features=64, out_features=64, bias=True)
              (key): Linear(in_features=64, out_features=64, bias=True)
              (value): Linear(in_features=64, out_features=64, bias=True)
              (proj_attn): Linear(in_features=64, out_features=64, bias=True)
            )
          )
          (resnets): ModuleList(
            (0): ResnetBlock2D(
              (norm1): GroupNorm(32, 128, eps=1e-05, affine=True)
              (conv1): Conv2d(128, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
              (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
              (dropout): Dropout(p=0.0, inplace=False)
              (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (nonlinearity): SiLU()
              (conv_shortcut): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1))
            )
            (1): ResnetBlock2D(
              (norm1): GroupNorm(32, 128, eps=1e-05, affine=True)
              (conv1): Conv2d(128, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
              (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
              (dropout): Dropout(p=0.0, inplace=False)
              (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (nonlinearity): SiLU()
              (conv_shortcut): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1))
            )
            (2): ResnetBlock2D(
              (norm1): GroupNorm(32, 128, eps=1e-05, affine=True)
              (conv1): Conv2d(128, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
              (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
              (dropout): Dropout(p=0.0, inplace=False)
              (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (nonlinearity): SiLU()
              (conv_shortcut): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1))
            )
          )
          (upsamplers): ModuleList(
            (0): Upsample2D(
              (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
            )
          )
        )
        (1): AttnUpBlock2D(
          (attentions): ModuleList(
            (0): AttentionBlock(
              (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
              (query): Linear(in_features=64, out_features=64, bias=True)
              (key): Linear(in_features=64, out_features=64, bias=True)
              (value): Linear(in_features=64, out_features=64, bias=True)
              (proj_attn): Linear(in_features=64, out_features=64, bias=True)
            )
            (1): AttentionBlock(
              (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
              (query): Linear(in_features=64, out_features=64, bias=True)
              (key): Linear(in_features=64, out_features=64, bias=True)
              (value): Linear(in_features=64, out_features=64, bias=True)
              (proj_attn): Linear(in_features=64, out_features=64, bias=True)
            )
            (2): AttentionBlock(
              (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
              (query): Linear(in_features=64, out_features=64, bias=True)
              (key): Linear(in_features=64, out_features=64, bias=True)
              (value): Linear(in_features=64, out_features=64, bias=True)
              (proj_attn): Linear(in_features=64, out_features=64, bias=True)
            )
          )
          (resnets): ModuleList(
            (0): ResnetBlock2D(
              (norm1): GroupNorm(32, 128, eps=1e-05, affine=True)
              (conv1): Conv2d(128, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
              (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
              (dropout): Dropout(p=0.0, inplace=False)
              (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (nonlinearity): SiLU()
              (conv_shortcut): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1))
            )
            (1): ResnetBlock2D(
              (norm1): GroupNorm(32, 128, eps=1e-05, affine=True)
              (conv1): Conv2d(128, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
              (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
              (dropout): Dropout(p=0.0, inplace=False)
              (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (nonlinearity): SiLU()
              (conv_shortcut): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1))
            )
            (2): ResnetBlock2D(
              (norm1): GroupNorm(32, 96, eps=1e-05, affine=True)
              (conv1): Conv2d(96, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
              (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
              (dropout): Dropout(p=0.0, inplace=False)
              (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (nonlinearity): SiLU()
              (conv_shortcut): Conv2d(96, 64, kernel_size=(1, 1), stride=(1, 1))
            )
          )
          (upsamplers): ModuleList(
            (0): Upsample2D(
              (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
            )
          )
        )
        (2): UpBlock2D(
          (resnets): ModuleList(
            (0): ResnetBlock2D(
              (norm1): GroupNorm(32, 96, eps=1e-05, affine=True)
              (conv1): Conv2d(96, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (time_emb_proj): Linear(in_features=128, out_features=32, bias=True)
              (norm2): GroupNorm(32, 32, eps=1e-05, affine=True)
              (dropout): Dropout(p=0.0, inplace=False)
              (conv2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (nonlinearity): SiLU()
              (conv_shortcut): Conv2d(96, 32, kernel_size=(1, 1), stride=(1, 1))
            )
            (1): ResnetBlock2D(
              (norm1): GroupNorm(32, 64, eps=1e-05, affine=True)
              (conv1): Conv2d(64, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (time_emb_proj): Linear(in_features=128, out_features=32, bias=True)
              (norm2): GroupNorm(32, 32, eps=1e-05, affine=True)
              (dropout): Dropout(p=0.0, inplace=False)
              (conv2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (nonlinearity): SiLU()
              (conv_shortcut): Conv2d(64, 32, kernel_size=(1, 1), stride=(1, 1))
            )
            (2): ResnetBlock2D(
              (norm1): GroupNorm(32, 64, eps=1e-05, affine=True)
              (conv1): Conv2d(64, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (time_emb_proj): Linear(in_features=128, out_features=32, bias=True)
              (norm2): GroupNorm(32, 32, eps=1e-05, affine=True)
              (dropout): Dropout(p=0.0, inplace=False)
              (conv2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
              (nonlinearity): SiLU()
              (conv_shortcut): Conv2d(64, 32, kernel_size=(1, 1), stride=(1, 1))
            )
          )
        )
      )
      (mid_block): UNetMidBlock2D(
        (attentions): ModuleList(
          (0): AttentionBlock(
            (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
            (query): Linear(in_features=64, out_features=64, bias=True)
            (key): Linear(in_features=64, out_features=64, bias=True)
            (value): Linear(in_features=64, out_features=64, bias=True)
            (proj_attn): Linear(in_features=64, out_features=64, bias=True)
          )
        )
        (resnets): ModuleList(
          (0): ResnetBlock2D(
            (norm1): GroupNorm(32, 64, eps=1e-05, affine=True)
            (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
            (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
            (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
            (dropout): Dropout(p=0.0, inplace=False)
            (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
            (nonlinearity): SiLU()
          )
          (1): ResnetBlock2D(
            (norm1): GroupNorm(32, 64, eps=1e-05, affine=True)
            (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
            (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
            (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
            (dropout): Dropout(p=0.0, inplace=False)
            (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
            (nonlinearity): SiLU()
          )
        )
      )
      (conv_norm_out): GroupNorm(32, 32, eps=1e-05, affine=True)
      (conv_act): SiLU()
      (conv_out): Conv2d(32, 1, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    )
    

    Comme vous pouvez le constater, il y a un peu plus de choses qui se passent ! Il a également beaucoup plus de paramÚtres que notre BasicUNet :

    sum([p.numel() for p in model.parameters()]) # 1,7M contre les ~309k paramĂštres du BasicUNet
    
    1707009
    

    Nous pouvons reproduire l’entraĂźnement prĂ©sentĂ© ci-dessus en utilisant ce modĂšle Ă  la place de notre modĂšle original. Nous devons passer x et le pas de temps au modĂšle (ici, nous passons toujours t=0 pour montrer qu’il fonctionne sans ce conditionnement de pas de temps et pour faciliter le code d’échantillonnage, mais vous pouvez Ă©galement essayer d’introduire (amount*1000) pour obtenir un Ă©quivalent de pas de temps Ă  partir du montant de la corruption). Les lignes modifiĂ©es sont indiquĂ©es par #<<< si vous souhaitez inspecter le code.

    # Dataloader (vous pouvez modifier la taille du batch)
    batch_size = 128
    train_dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
    
    # Combien de fois devrions-nous passer les données en revue ?
    n_epochs = 3
    
    # Créer le réseau
    net = UNet2DModel(
        sample_size=28,           # la résolution de l'image cible
        in_channels=1,            # le nombre de canaux d'entrée, 3 pour les images RVB
        out_channels=1,           # le nombre de canaux de sortie
        layers_per_block=2,       # le nombre de couches ResNet Ă  utiliser par bloc UNet
        block_out_channels=(32, 64, 64), # Correspondant Ă  peu prĂšs Ă  notre exemple UNet de base
        down_block_types=( 
            "DownBlock2D",        # un bloc de sous-échantillonnage ResNet normal
            "AttnDownBlock2D",    # un bloc de sous-échantillonnage ResNet avec auto-attention spatiale
            "AttnDownBlock2D",
        ), 
        up_block_types=(
            "AttnUpBlock2D", 
            "AttnUpBlock2D",      # un bloc de suréchantillonnage ResNet avec auto-attention spatiale
            "UpBlock2D",          # un bloc de suréchantillonnage ResNet standard
          ),
    )
    
    net.to(device)
    
    # Notre protection contre la perte
    loss_fn = nn.MSELoss()
    
    # L'optimiseur
    opt = torch.optim.Adam(net.parameters(), lr=1e-3) 
    
    # Conserver une trace des pertes pour les visualiser plus tard
    losses = []
    
    # La boucle d'entraĂźnement
    for epoch in range(n_epochs):
    
        for x, y in train_dataloader:
    
            # Obtenir des données et préparer la version corrompue
            x = x.to(device) # Data on the GPU
            noise_amount = torch.rand(x.shape[0]).to(device) # Choisir des quantités de bruit aléatoires
            noisy_x = corrupt(x, noise_amount) # Créer notre bruit x
    
            # Obtenir la prédiction du modÚle
            pred = net(noisy_x, 0).sample #<<< En utilisant toujours le pas de temps 0, en ajoutant .sample
    
            # Calculer la perte
            loss = loss_fn(pred, x) # Dans quelle mesure la sortie est-elle proche du véritable x "propre" ?
    
            # Rétropropager et mettre à jour les paramÚtres
            opt.zero_grad()
            loss.backward()
            opt.step()
    
            # Stocker la perte pour plus tard
            losses.append(loss.item())
    
        # Afficher la moyenne des valeurs de perte pour cette époque :
        avg_loss = sum(losses[-len(train_dataloader):])/len(train_dataloader)
        print(f'Finished epoch {epoch}. Average loss for this epoch: {avg_loss:05f}')
    
    # Graphique
    fig, axs = plt.subplots(1, 2, figsize=(12, 5))
    
    # Perte
    axs[0].plot(losses)
    axs[0].set_ylim(0, 0.1)
    axs[0].set_title('Loss over time')
    
    # Échantillons
    n_steps = 40
    x = torch.rand(64, 1, 28, 28).to(device)
    for i in range(n_steps):
      noise_amount = torch.ones((x.shape[0], )).to(device) * (1-(i/n_steps)) # De haut en bas
      with torch.no_grad():
        pred = net(x, 0).sample
      mix_factor = 1/(n_steps - i)
      x = x*(1-mix_factor) + pred*mix_factor
    
    axs[1].imshow(torchvision.utils.make_grid(x.detach().cpu(), nrow=8)[0].clip(0, 1), cmap='Greys')
    axs[1].set_title('Generated Samples')
    
    Finished epoch 0. Average loss for this epoch: 0.018925 
    Finished epoch 1. Average loss for this epoch: 0.012785 
    Finished epoch 2. Average loss for this epoch: 0.011694
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Ces rĂ©sultats sont bien meilleurs que notre premiĂšre sĂ©rie de rĂ©sultats ! Vous pouvez envisager de modifier la configuration du Unet ou de prolonger l’entraĂźnement afin d’obtenir des performances encore meilleures.

    Le processus de corruption

    Le papier DDPM dĂ©crit un processus de corruption qui ajoute une petite quantitĂ© de bruit Ă  chaque « pas de temps ». Étant donnĂ© $x_{t-1}$ pour un certain pas de temps, nous pouvons obtenir la version suivante (lĂ©gĂšrement plus bruitĂ©e) $x_t$ avec :

    \[\begin{aligned} q(\mathbf{x}_t \vert \mathbf{x}_{t-1}) &= \mathcal{N}(\mathbf{x}_t; \sqrt{1 - \beta_t} \mathbf{x}_{t-1}, \beta_t\mathbf{I}) \\ q(\mathbf{x}_{1:T} \vert \mathbf{x}_0) &= \prod^T_{t=1} q(\mathbf{x}_t \vert \mathbf{x}_{t-1}) \end{aligned}\]

    Nous prenons $x_{t-1}$, l’échelonnons de $\sqrt{1 - \beta_t}$ et ajoutons du bruit Ă©chelonnĂ© de $\beta_t$. Ce $\beta$ est dĂ©fini pour chaque t en fonction d’un certain planificateur, et dĂ©termine la quantitĂ© de bruit ajoutĂ©e par pas de temps. Nous ne voulons pas nĂ©cessairement faire cette opĂ©ration 500 fois pour obtenir $x_{500}$, nous avons donc une autre formule pour obtenir $x_t$ pour n’importe quel t Ă©tant donnĂ© $x_0$ :

    \[\begin{aligned} q(\mathbf{x}_t \vert \mathbf{x}_0) &= \mathcal{N}(\mathbf{x}_t; \sqrt{\bar{\alpha}_t} \mathbf{x}_0, {(1 - \bar{\alpha}_t)} \mathbf{I}) \end{aligned}\]

    oĂč :

    \[\bar{\alpha}_t = \prod_{i=1}^{T} \alpha_i,\quad \alpha_i = 1 - \beta_i\]
    noise_scheduler = DDPMScheduler(num_train_timesteps=1000)
    plt.plot(noise_scheduler.alphas_cumprod.cpu() ** 0.5, label=r"${\sqrt{\bar{\alpha}_t}}$")
    plt.plot((1 - noise_scheduler.alphas_cumprod.cpu()) ** 0.5, label=r"$\sqrt{(1 - \bar{\alpha}_t)}$")
    plt.legend(fontsize="x-large")
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Au départ, le $x$ bruité est principalement $x$ (sqrt_alpha_prod ~= 1), mais au fil du temps, la contribution de $x$ diminue et la composante bruit augmente. Contrairement à notre mélange linéaire de $x$ et de bruit en fonction de la quantité, celui-ci devient bruyant relativement rapidement. Nous pouvons visualiser cela sur quelques données :

    # Bruit d'un batch d'images pour visualiser l'effet
    fig, axs = plt.subplots(3, 1, figsize=(16, 10))
    xb, yb = next(iter(train_dataloader))
    xb = xb.to(device)[:8]
    xb = xb * 2. - 1. # Pour aller dans (-1, 1)
    print('X shape', xb.shape)
    
    # Afficher les entrées propres
    axs[0].imshow(torchvision.utils.make_grid(xb[:8])[0].detach().cpu(), cmap='Greys')
    axs[0].set_title('Clean X')
    
    # Ajouter du bruit avec le planificateur
    timesteps = torch.linspace(0, 999, 8).long().to(device)
    noise = torch.randn_like(xb) # << NB: randn et non rand
    noisy_xb = noise_scheduler.add_noise(xb, noise, timesteps)
    print('Noisy X shape', noisy_xb.shape)
    
    # Afficher la version bruyante (avec et sans coupure)
    axs[1].imshow(torchvision.utils.make_grid(noisy_xb[:8])[0].detach().cpu().clip(-1, 1),  cmap='Greys')
    axs[1].set_title('Noisy X (clipped to (-1, 1)')
    axs[2].imshow(torchvision.utils.make_grid(noisy_xb[:8])[0].detach().cpu(),  cmap='Greys')
    axs[2].set_title('Noisy X')
    
    X shape torch.Size([8, 1, 28, 28]) 
    Noisy X shape torch.Size([8, 1, 28, 28])
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Une autre dynamique est en jeu : la version DDPM ajoute un bruit tirĂ© d’une distribution gaussienne (moyenne 0, Ă©cart-type 1 de torch.randn) plutĂŽt que le bruit uniforme entre 0 et 1 (de torch.rand) que nous avons utilisĂ© dans notre fonction corrompue d’origine. En gĂ©nĂ©ral, il est judicieux de normaliser Ă©galement les donnĂ©es d’entraĂźnement. Dans l’autre notebook, vous verrez Normalize(0.5, 0.5) dans la liste des transformations, qui fait correspondre les donnĂ©es de l’image de (0, 1) Ă  (-1, 1) et qui est “suffisante” pour nos besoins. Nous ne l’avons pas fait pour ce notebook, mais la cellule de visualisation ci-dessus l’ajoute pour une mise Ă  l’échelle et une visualisation plus prĂ©cises.

    Objectif d’entraünement

    Dans notre exemple, le modĂšle tente de prĂ©dire l’image dĂ©bruitĂ©e. Dans le DDPM et dans de nombreuses autres implĂ©mentations de modĂšles de diffusion, le modĂšle prĂ©dit le bruit utilisĂ© dans le processus de corruption (avant la mise Ă  l’échelle, donc un bruit Ă  variance unitaire). Dans le code, cela ressemble Ă  quelque chose comme :

    noise = torch.randn_like(xb) # << NB: randn et non rand
    noisy_x = noise_scheduler.add_noise(x, noise, timesteps)
    model_prediction = model(noisy_x, timesteps).sample
    loss = mse_loss(model_prediction, noise) # le bruit comme cible
    

    Vous pouvez penser que prĂ©dire le bruit (Ă  partir duquel nous pouvons dĂ©duire Ă  quoi ressemble l’image dĂ©bruitĂ©e) est Ă©quivalent Ă  prĂ©dire directement l’image dĂ©bruitĂ©e. Alors pourquoi privilĂ©gier l’une plutĂŽt que l’autre : est-ce simplement pour des raisons de commoditĂ© mathĂ©matique ?

    Il s’avĂšre qu’il existe une autre subtilitĂ©. Nous calculons la perte Ă  diffĂ©rents moments (choisis au hasard) au cours de l’entraĂźnement. Ces diffĂ©rents objectifs conduiront Ă  une “pondĂ©ration implicite” diffĂ©rente de ces pertes, oĂč la prĂ©diction du bruit donne plus de poids aux niveaux de bruit plus faibles. Vous pouvez choisir des objectifs plus complexes pour modifier cette “pondĂ©ration implicite des pertes”. Vous pouvez aussi choisir un calendrier de bruit qui donnera plus d’exemples Ă  un niveau de bruit plus Ă©levĂ©. Vous pouvez demander au modĂšle de prĂ©dire une “vitesse” v, que nous dĂ©finissons comme une combinaison de l’image et du bruit dĂ©pendant du niveau de bruit (voir Progressive Distillation for Fast Sampling of Diffusion Models). Il se peut que le modĂšle prĂ©dise le bruit, mais qu’il rĂ©duise ensuite la perte en fonction d’un facteur dĂ©pendant de la quantitĂ© de bruit, sur la base d’un peu de thĂ©orie (voir Perception Prioritized Training of Diffusion Models) ou d’expĂ©riences visant Ă  dĂ©terminer quels niveaux de bruit sont les plus informatifs pour le modĂšle (voir Elucidating the Design Space of Diffusion-Based Generative Models). En rĂ©sumĂ© : le choix de l’objectif a un effet sur les performances du modĂšle, et des recherches sont en cours pour dĂ©terminer la “meilleure” option.

    Pour l’instant, la prĂ©diction du bruit (epsilon ou eps) est l’approche privilĂ©giĂ©e, mais avec le temps, nous verrons probablement d’autres objectifs pris en charge dans la bibliothĂšque et utilisĂ©s dans diffĂ©rentes situations.

    Conditionnement du pas de temps

    Le modÚle UNet2DModel prend en compte à la fois x et le pas de temps. Ce dernier est transformé en intégration et introduit dans le modÚle à plusieurs endroits.

    La thĂ©orie sous-jacente est qu’en donnant au modĂšle des informations sur le niveau de bruit, il peut mieux accomplir sa tĂąche. Bien qu’il soit possible d’entraĂźner un modĂšle sans ce conditionnement du pas de temps, cela semble amĂ©liorer les performances dans certains cas et la plupart des implĂ©mentations l’incluent, du moins dans la littĂ©rature actuelle.

    Échantillonnage

    Étant donnĂ© un modĂšle qui estime le bruit prĂ©sent dans une entrĂ©e bruyante (ou qui prĂ©dit la version dĂ©bruitĂ©e), comment produire de nouvelles images ?

    Nous pourrions introduire du bruit pur et espĂ©rer que le modĂšle prĂ©dise une bonne image en tant que version dĂ©bruitĂ©e en une seule Ă©tape. Cependant, comme nous l’avons vu dans les expĂ©riences ci-dessus, cela ne fonctionne gĂ©nĂ©ralement pas bien. C’est pourquoi nous procĂ©dons Ă  un certain nombre de petites Ă©tapes basĂ©es sur la prĂ©diction du modĂšle, en Ă©liminant de maniĂšre itĂ©rative une petite partie du bruit Ă  la fois.

    La maniĂšre exacte de procĂ©der dĂ©pend de la mĂ©thode d’échantillonnage utilisĂ©e. Nous n’entrerons pas dans la thĂ©orie trop profondĂ©ment, mais les questions clĂ©s de la conception sont les suivantes :

    • Quelle est l’ampleur du pas Ă  franchir ? En d’autres termes, quel « calendrier de bruit » devez-vous suivre ?
    • Utilisez-vous uniquement la prĂ©diction actuelle du modĂšle pour informer l’étape de mise Ă  jour (comme DDPM, DDIM et beaucoup d’autres) ? Évaluez-vous le modĂšle plusieurs fois pour estimer les gradients d’ordre supĂ©rieur en vue d’une Ă©tape plus importante et plus prĂ©cise (mĂ©thodes d’ordre supĂ©rieur et certains solveurs d’EDO discrĂštes) ? Ou bien conservez-vous un historique des prĂ©dictions passĂ©es pour essayer de mieux informer l’étape de mise Ă  jour actuelle (Ă©chantillonneurs linĂ©aires multi-Ă©tapes et ancestraux) ?
    • Ajoutez-vous du bruit supplĂ©mentaire (parfois appelĂ© « churn ») pour ajouter plus de stochasticitĂ© (caractĂšre alĂ©atoire) au processus d’échantillonnage, ou le gardez-vous complĂštement dĂ©terministe ? De nombreux Ă©chantillonneurs contrĂŽlent ce paramĂštre (tel que « eta » pour les Ă©chantillonneurs DDIM) afin que l’utilisateur puisse choisir.

    La recherche sur les mĂ©thodes d’échantillonnage pour les modĂšles de diffusion Ă©volue rapidement et de plus en plus de mĂ©thodes permettant de trouver de bonnes solutions en moins d’étapes sont proposĂ©es. Les courageux et les curieux trouveront peut-ĂȘtre intĂ©ressant de parcourir le code des diffĂ©rentes implĂ©mentations disponibles dans la bibliothĂšque đŸ€— Diffusers ici ou de consulter la documentation qui renvoient souvent aux articles pertinents.

    Conclusions

    Nous espĂ©rons que ce notebook vous a permis d’aborder les modĂšles de diffusion sous un angle lĂ©gĂšrement diffĂ©rent.

    Ce notebook a Ă©tĂ© Ă©crit pour le cours de Hugging Face par Jonathan Whitaker, et recoupe une version incluse dans son propre cours, The Generative Landscape. Consultez-le (en anglais) si vous souhaitez voir cet exemple de base Ă©tendu avec du bruit et du conditionnement de classe. Les questions ou les bugs peuvent ĂȘtre communiquĂ©s via GitHub issues ou via Discord. Vous pouvez Ă©galement envoyer un message via Twitter Ă  @johnowhitaker.

    2. Vue d'ensemble

    Dans cette unité, vous apprendrez à utiliser et à adapter les modÚles de diffusion pré-entraßnés de nouvelles façons. Vous verrez également comment nous pouvons créer des modÚles de diffusion qui prennent des entrées supplémentaires comme conditionnement pour contrÎler le processus de génération.

    Vue d’ensemble de cette unitĂ© :rocket:

    Les différentes étapes à suivre pour cette unité :

    • Lisez le matĂ©riel ci-dessous pour avoir une vue d’ensemble des idĂ©es clĂ©s de cette unitĂ©
    • Consultez le notebook _Finetuning et guidage_ pour finetuner un modĂšle de diffusion existant sur un nouveau jeu de donnĂ©es en utilisant la bibliothĂšque đŸ€— *Diffusers et pour modifier la procĂ©dure d’échantillonnage en utilisant le guidage.
    • Suivez l’exemple dans le notebook pour partager une dĂ©mo Gradio pour votre modĂšle personnalisĂ©
    • (Facultatif) Consultez le *notebook ModĂšle de diffusion conditionnĂ© par la classe pour voir comment nous pouvons ajouter un contrĂŽle supplĂ©mentaire au processus de gĂ©nĂ©ration
    • (Facultatif) Regardez cette vidĂ©o (en anglais) pour une prĂ©sentation informelle du matĂ©riel de cette unitĂ©

    Finetuning

    Comme vous avez pu le constater dans l’unitĂ© 1, entraĂźner des modĂšles de diffusion Ă  partir de zĂ©ro peut prendre beaucoup de temps ! Le temps et les donnĂ©es nĂ©cessaires pour entraĂźner un modĂšle Ă  partir de zĂ©ro peuvent devenir irrĂ©alisables, en particulier lorsque l’on passe Ă  des rĂ©solutions plus Ă©levĂ©es. Heureusement, il existe une solution : commencer par un modĂšle qui a dĂ©jĂ  Ă©tĂ© entraĂźnĂ© ! Ainsi, nous partons d’un modĂšle qui a dĂ©jĂ  appris Ă  dĂ©bruiter des images, et nous espĂ©rons que cela constituera un meilleur point de dĂ©part qu’un modĂšle initialisĂ© de maniĂšre alĂ©atoire.

    Example images generated with a model trained on LSUN Bedrooms and fine-tuned for 500 steps on WikiArt

    Le finetuning fonctionne gĂ©nĂ©ralement mieux si les nouvelles donnĂ©es ressemblent quelque peu aux donnĂ©es d’entraĂźnement originales du modĂšle de base (par exemple, commencer avec un modĂšle entraĂźnĂ© sur les visages est probablement une bonne idĂ©e si vous essayez de gĂ©nĂ©rer des visages de dessins animĂ©s), mais il est surprenant de constater que les avantages persistent mĂȘme si le domaine est modifiĂ© de maniĂšre assez radicale. L’image ci-dessus est gĂ©nĂ©rĂ©e Ă  partir d’un modĂšle entraĂźnĂ© sur le jeu de donnĂ©es LSUN Bedrooms et finetunĂ© sur 500 Ă©tapes sur le jeu de donnĂ©es WikiArt. Le script d’entraĂźnement est inclus Ă  titre de rĂ©fĂ©rence dans les notebooks de cette unitĂ©.

    Guidage

    Les modĂšles inconditionnels ne donnent pas beaucoup de contrĂŽle sur ce qui est gĂ©nĂ©rĂ©. Nous pouvons entraĂźner un modĂšle conditionnel (plus d’informations Ă  ce sujet dans la section suivante) qui prend des entrĂ©es supplĂ©mentaires pour aider Ă  diriger le processus de gĂ©nĂ©ration, mais que faire si nous avons dĂ©jĂ  entraĂźnĂ© un modĂšle inconditionnel que nous aimerions utiliser ? C’est lĂ  qu’intervient le guidage, un processus par lequel les prĂ©dictions du modĂšle Ă  chaque Ă©tape du processus de gĂ©nĂ©ration sont Ă©valuĂ©es par rapport Ă  une fonction de guidage et modifiĂ©es de maniĂšre Ă  ce que l’image finale gĂ©nĂ©rĂ©e corresponde mieux Ă  nos attentes.

    guidance example image

    Cette fonction de guidage peut ĂȘtre presque n’importe quoi, ce qui en fait une technique puissante ! Dans le notebook, nous partons d’un exemple simple (contrĂŽler la couleur, comme illustrĂ© dans l’exemple de sortie ci-dessus) pour arriver Ă  un exemple utilisant un puissant modĂšle prĂ©-entraĂźnĂ© appelĂ© CLIP qui nous permet de guider la gĂ©nĂ©ration sur la base d’une description textuelle.

    Conditionnement

    Le guidage est un excellent moyen d’exploiter davantage un modĂšle de diffusion inconditionnel, mais si nous disposons d’informations supplĂ©mentaires (telles qu’une Ă©tiquette de classe ou une lĂ©gende d’image) pendant l’entraĂźnement, nous pouvons Ă©galement les transmettre au modĂšle afin qu’il les utilise pour Ă©tablir ses prĂ©dictions. Ce faisant, nous crĂ©ons un modĂšle conditionnel, que nous pouvons contrĂŽler au moment de l’infĂ©rence en contrĂŽlant ce qui est fourni comme conditionnement. Le notebook montre un exemple de modĂšle conditionnĂ© par une classe qui apprend Ă  gĂ©nĂ©rer des images en fonction d’une Ă©tiquette de classe.

    conditioning example

    Il existe un certain nombre de façons de transmettre ces informations de conditionnement, par exemple

    • En les introduisant sous forme de canaux supplĂ©mentaires dans l’entrĂ©e du UNet. Cette mĂ©thode est souvent utilisĂ©e lorsque l’information de conditionnement a la mĂȘme forme que l’image, comme un masque de segmentation, une carte de profondeur ou une version floue de l’image (dans le cas d’un modĂšle de restauration/superrĂ©solution). Cela fonctionne aussi pour d’autres types de conditionnement. Par exemple, dans le notebook, l’étiquette de la classe est associĂ©e en avec un enchĂąssement puis Ă©tendue pour avoir la mĂȘme largeur et la mĂȘme hauteur que l’image d’entrĂ©e, de sorte qu’elle puisse ĂȘtre introduite sous forme de canaux supplĂ©mentaires.
    • La crĂ©ation d’un enchĂąssement et sa projection Ă  une taille correspondant au nombre de canaux Ă  la sortie d’une ou de plusieurs couches internes du UNet, puis son ajout Ă  ces sorties. C’est ainsi que le conditionnement du pas de temps est gĂ©rĂ©, par exemple. La sortie de chaque bloc Resnet est complĂ©tĂ©e par une projection de l’enchĂąssement du pas de temps. Ceci est utile lorsque vous avez un vecteur tel qu’une l’enchĂąssement CLIP comme information de conditionnement. Un exemple notable est ‘Image Variations’ version of Stable Diffusion qui fait exactement cela.
    • L’ajout de couches d’attention croisĂ©e qui peuvent s’occuper d’une sĂ©quence transmise en tant que conditionnement. Ceci est particuliĂšrement utile lorsque le conditionnement se prĂ©sente sous la forme d’un texte. Le texte est mis en correspondance avec une sĂ©quence d’enchĂąssements Ă  l’aide d’un transformer puis les couches d’attention croisĂ©e du UNet sont utilisĂ©es pour incorporer cette information dans le chemin de dĂ©bruitage. Nous verrons cela en action dans l’unitĂ© 3 lorsque nous examinerons comment Stable Diffusion gĂšre le conditionnement du texte.

    Notebooks

    Chapitre Colab Kaggle Gradient Studio Lab
    Finetuning et guidage Open In Colab Kaggle Gradient Open In SageMaker Studio Lab
    ModÚle de diffusion conditionné par la classe Open In Colab Kaggle Gradient Open In SageMaker Studio Lab

    La plus grande partie du matĂ©riel se trouve dans Finetuning et guidage, oĂč nous explorons ces deux sujets Ă  travers des exemples travaillĂ©s. Le notebook montre comment vous pouvez finetuner un modĂšle existant sur de nouvelles donnĂ©es, ajouter des conseils, et partager le rĂ©sultat sous forme de dĂ©mo Gradio. Il y a un script d’accompagnement (finetune_model.py) qui facilite l’expĂ©rimentation de diffĂ©rents paramĂštres de finetuning, et un Space que vous pouvez utiliser comme patron pour partager votre propre dĂ©mo sur đŸ€— Spaces.

    Dans le notebook ModĂšle de diffusion conditionnĂ© par la classe, nous montrons un bref exemple de crĂ©ation d’un modĂšle de diffusion conditionnĂ© par les Ă©tiquettes de classe Ă  l’aide du jeu de donnĂ©es MNIST. L’objectif est de dĂ©montrer l’idĂ©e principale aussi simplement que possible : en donnant au modĂšle des informations supplĂ©mentaires sur ce qu’il est censĂ© dĂ©bruiter, nous pouvons contrĂŽler ultĂ©rieurement les types d’images gĂ©nĂ©rĂ©es au moment de l’infĂ©rence.

    Projet

    En suivant les exemples du notebook Fine-tuning and Guidance, finetunez votre propre modĂšle ou choisissez un modĂšle existant, puis crĂ©ez une dĂ©mo Gradio pour mettre en valeur vos nouvelles compĂ©tences en guidage. N’oubliez pas de partager votre dĂ©mo sur Discord, Twitter, etc., afin que nous puissions admirer votre travail !

    Ressources complémentaires

    Une liste non exhaustive de ressources (en anglais) Ă  consulter :

    • Denoising Diffusion Implicit Models est une introduction de la mĂ©thode d’échantillonnage DDIM (utilisĂ©e par DDIMScheduler)
    • GLIDE: Towards Photorealistic Image Generation and Editing with Text-Guided Diffusion Models est une introduction de mĂ©thodes pour conditionner les modĂšles de diffusion sur le texte
    • eDiffi: Text-to-Image Diffusion Models with an Ensemble of Expert Denoisers montre comment diffĂ©rents types de conditionnement peuvent ĂȘtre utilisĂ©s ensemble pour contrĂŽler encore davantage les types d’échantillons gĂ©nĂ©rĂ©s

    Vous avez identifiĂ© d’autres ressources intĂ©ressantes ? Faites-le nous savoir et nous les ajouterons Ă  cette liste.

    2.1. Finetuning et guidage

    Dans ce notebook, nous allons couvrir deux approches principales pour adapter les modĂšles de diffusion existants :

    • Avec le finetuning, nous rĂ©entraĂźnerons les modĂšles existants sur de nouvelles donnĂ©es afin de modifier le type de rĂ©sultats qu’ils produisent.
    • Avec le guidage, nous prenons un modĂšle existant et dirigeons le processus de gĂ©nĂ©ration au moment de l’infĂ©rence pour un contrĂŽle supplĂ©mentaire.

    Ce que vous apprendrez :

    A la fin de ce notebook, vous saurez comment :

    • CrĂ©er une boucle d’échantillonnage et gĂ©nĂ©rer des Ă©chantillons plus rapidement Ă  l’aide d’un nouveau planificateur
    • Finetuner un modĂšle de diffusion existant sur de nouvelles donnĂ©es, y compris :
      • Utiliser l’accumulation du gradient pour contourner certains des problĂšmes liĂ©s aux petits batchs.
      • Enregistrer les Ă©chantillons dans Weights and Biases pendant l’entraĂźnement pour suivre la progression (via le script d’exemple joint).
      • Sauvegarder le pipeline rĂ©sultant et le tĂ©lĂ©charger sur le Hub
    • Guider le processus d’échantillonnage avec des fonctions de perte supplĂ©mentaires pour ajouter un contrĂŽle sur les modĂšles existants, y compris :
      • Explorer diffĂ©rentes approches de guidage avec une simple perte basĂ©e sur la couleur
      • Utiliser CLIP pour guider la gĂ©nĂ©ration Ă  l’aide d’un prompt de texte
      • Partager une boucle d’échantillonnage personnalisĂ©e en utilisant Gradio et đŸ€— Spaces.

    Configuration et importations

    Pour enregistrer vos modĂšles finetunĂ©s sur le Hub d’Hugging Face, vous devrez vous connecter avec un token qui a un accĂšs en Ă©criture. Le code ci-dessous vous invite Ă  le faire et vous renvoie Ă  la page des tokens de votre compte. Vous aurez Ă©galement besoin d’un compte Weights and Biases si vous souhaitez utiliser le script d’entraĂźnement pour enregistrer des Ă©chantillons au fur et Ă  mesure que le modĂšle s’entraĂźne. LĂ  encore, le code devrait vous inviter Ă  vous connecter lĂ  oĂč c’est nĂ©cessaire.

    A part cela, la seule chose Ă  faire est d’installer quelques dĂ©pendances, d’importer tout ce dont nous aurons besoin et de spĂ©cifier l’appareil que nous utiliserons :

    !pip install -qq diffusers datasets accelerate wandb open-clip-torch
    
    # Code pour se connecter au Hub d'Hugging Face, nécessaire pour partager les modÚles
    # Assurez-vous d'utiliser un *token* avec un accÚs WRITE (écriture)
    from huggingface_hub import notebook_login
    
    notebook_login()
    
    Token is valid.
    Your token has been saved in your configured git credential helpers (store).
    Your token has been saved to /root/.huggingface/token
    Login successful
    
    import numpy as np
    import torch
    import torch.nn.functional as F
    import torchvision
    from datasets import load_dataset
    from diffusers import DDIMScheduler, DDPMPipeline
    from matplotlib import pyplot as plt
    from PIL import Image
    from torchvision import transforms
    from tqdm.auto import tqdm
    
    device = (
        "mps"
        if torch.backends.mps.is_available()
        else "cuda"
        if torch.cuda.is_available()
        else "cpu"
    )
    

    Chargement d’un pipeline prĂ©-entraĂźnĂ©

    Pour commencer ce notebook, chargeons un pipeline existant et voyons ce que nous pouvons en faire :

    image_pipe = DDPMPipeline.from_pretrained("google/ddpm-celebahq-256")
    image_pipe.to(device);
    

    La gĂ©nĂ©ration d’images est aussi simple que l’exĂ©cution de la mĂ©thode __call__ du pipeline en l’appelant comme une fonction :

    images = image_pipe().images
    images[0]
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Sympathique, mais LENT ! Avant d’aborder les sujets principaux du jour, jetons un coup d’Ɠil Ă  la boucle d’échantillonnage proprement dite et voyons comment nous pouvons utiliser un Ă©chantillonneur plus sophistiquĂ© pour l’accĂ©lĂ©rer.

    Échantillonnage plus rapide avec DDIM

    À chaque Ă©tape, le modĂšle est nourri d’une entrĂ©e bruyante et il lui est demandĂ© de prĂ©dire le bruit (et donc une estimation de ce Ă  quoi l’image entiĂšrement dĂ©bruitĂ©e pourrait ressembler). Au dĂ©part, ces prĂ©dictions ne sont pas trĂšs bonnes, c’est pourquoi nous dĂ©composons le processus en plusieurs Ă©tapes. Cependant, l’utilisation de plus de 1000 Ă©tapes s’est avĂ©rĂ©e inutile, et une multitude de recherches rĂ©centes ont explorĂ© la maniĂšre d’obtenir de bons Ă©chantillons avec le moins d’étapes possible.

    Dans la bibliothĂšque đŸ€— Diffusers, ces mĂ©thodes d’échantillonnage sont gĂ©rĂ©es par un planificateur, qui doit effectuer chaque mise Ă  jour via la fonction step(). Pour gĂ©nĂ©rer une image, on commence par un bruit alĂ©atoire $x$. Ensuite, pour chaque pas de temps dans le planificateur de bruit, nous introduisons l’entrĂ©e bruitĂ©e $x$ dans le modĂšle et transmettons la prĂ©diction rĂ©sultante Ă  la fonction step(). Celle-ci renvoie une sortie avec un attribut prev_sample. “previous” parce que nous revenons en arriĂšre dans le temps, d’un niveau de bruit Ă©levĂ© Ă  un niveau de bruit faible (Ă  l’inverse du processus de diffusion vers l’avant).

    Voyons cela en action ! Tout d’abord, nous chargeons un planificateur, ici un DDIMScheduler basĂ© sur le papier Denoising Diffusion Implicit Models qui peut donner des Ă©chantillons dĂ©cents en beaucoup moins d’étapes que l’implĂ©mentation originale du DDPM :

    # Créer un nouveau planificateur et définir le nombre d'étapes d'inférence
    scheduler = DDIMScheduler.from_pretrained("google/ddpm-celebahq-256")
    scheduler.set_timesteps(num_inference_steps=40)
    

    Vous pouvez constater que ce modÚle effectue 40 étapes au total, chaque saut équivalant à 25 étapes du programme original de 1000 étapes :

    scheduler.timesteps
    
    tensor([975, 950, 925, 900, 875, 850, 825, 800, 775, 750, 725, 700, 675, 650,
            625, 600, 575, 550, 525, 500, 475, 450, 425, 400, 375, 350, 325, 300,
            275, 250, 225, 200, 175, 150, 125, 100,  75,  50,  25,   0])
    

    CrĂ©ons 4 images alĂ©atoires et exĂ©cutons la boucle d’échantillonnage, en visualisant Ă  la fois le $x$ actuel et la version dĂ©bruitĂ©e prĂ©dite au fur et Ă  mesure de l’avancement du processus :

    # Le point de départ aléatoire
    x = torch.randn(4, 3, 256, 256).to(device)  # Batch de 4 images Ă  3 canaux de 256 x 256 px
    
    # Boucle sur les pas de temps d'échantillonnage
    for i, t in tqdm(enumerate(scheduler.timesteps)):
    
        # Préparer l'entrée du modÚle
        model_input = scheduler.scale_model_input(x, t)
    
        # Obtenir la prédiction
        with torch.no_grad():
            noise_pred = image_pipe.unet(model_input, t)["sample"]
    
        # Calculer la forme que devrait prendre l'échantillon mis à jour à l'aide du planificateur
        scheduler_output = scheduler.step(noise_pred, t, x)
    
        # Mise Ă  jour de x
        x = scheduler_output.prev_sample
    
        # Occasionnellement, afficher à la fois x et les images débruitées prédites
        if i % 10 == 0 or i == len(scheduler.timesteps) - 1:
            fig, axs = plt.subplots(1, 2, figsize=(12, 5))
    
            grid = torchvision.utils.make_grid(x, nrow=4).permute(1, 2, 0)
            axs[0].imshow(grid.cpu().clip(-1, 1) * 0.5 + 0.5)
            axs[0].set_title(f"Current x (step {i})")
    
            pred_x0 = (
                scheduler_output.pred_original_sample
            )  # Non disponible pour tous les planificateurs
            grid = torchvision.utils.make_grid(pred_x0, nrow=4).permute(1, 2, 0)
            axs[1].imshow(grid.cpu().clip(-1, 1) * 0.5 + 0.5)
            axs[1].set_title(f"Predicted denoised images (step {i})")
            plt.show()
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Comme vous pouvez le voir, les prĂ©dictions initiales ne sont pas trĂšs bonnes, mais au fur et Ă  mesure que le processus se poursuit, les rĂ©sultats prĂ©dits deviennent de plus en plus prĂ©cis. Si vous ĂȘtes curieux de savoir ce qui se passe Ă  l’intĂ©rieur de la fonction step(), inspectez le code (bien commentĂ©) avec :

    # ??scheduler.step
    

    Vous pouvez également insérer ce nouveau planificateur à la place du planificateur original fourni avec le pipeline, et échantillonner de la maniÚre suivante :

    image_pipe.scheduler = scheduler
    images = image_pipe(num_inference_steps=40).images
    images[0]
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    TrÚs bien, nous pouvons maintenant obtenir des échantillons dans un délai raisonnable ! Cela devrait accélérer les choses au fur et à mesure que nous avançons dans le reste de ce notebook :)

    Finetuning

    Et maintenant, le plus amusant ! Étant donnĂ© ce pipeline prĂ©-entraĂźnĂ©, comment pouvons-nous rĂ©entraĂźner le modĂšle pour gĂ©nĂ©rer des images sur la base de nouvelles donnĂ©es d’entraĂźnement ?

    Il s’avĂšre que cela est presque identique Ă  entraĂźner un modĂšle Ă  partir de zĂ©ro (comme nous l’avons vu dans l’unitĂ© 1), sauf que nous commençons avec le modĂšle existant. Voyons cela en action et abordons quelques considĂ©rations supplĂ©mentaires au fur et Ă  mesure.

    Tout d’abord, le jeu de donnĂ©es : vous pouvez essayer ce jeu de donnĂ©es de visages vintage ou ces visages animĂ©s pour quelque chose de plus proche des donnĂ©es d’entraĂźnement originales de ce modĂšle de visages. Mais pour le plaisir, utilisons plutĂŽt le mĂȘme petit jeu de donnĂ©es de papillons que nous avons utilisĂ© pour nous entraĂźner Ă  partir de zĂ©ro dans l’unitĂ© 1. ExĂ©cutez le code ci-dessous pour tĂ©lĂ©charger le jeu de donnĂ©es papillons et crĂ©er un chargeur de donnĂ©es Ă  partir duquel nous pouvons Ă©chantillonner un batch d’images :

    # Pas sur Colab ? Les commentaires avec #@ permettent de modifier l'interface utilisateur comme les titres ou les entrées
    # mais peuvent ĂȘtre ignorĂ©s si vous travaillez sur une plateforme diffĂ©rente.
    
    dataset_name = "huggan/smithsonian_butterflies_subset"  # @param
    dataset = load_dataset(dataset_name, split="train")
    image_size = 256  # @param
    batch_size = 4  # @param
    preprocess = transforms.Compose(
        [
            transforms.Resize((image_size, image_size)),
            transforms.RandomHorizontalFlip(),
            transforms.ToTensor(),
            transforms.Normalize([0.5], [0.5]),
        ]
    )
    
    
    def transform(examples):
        images = [preprocess(image.convert("RGB")) for image in examples["image"]]
        return {"images": images}
    
    
    dataset.set_transform(transform)
    
    train_dataloader = torch.utils.data.DataLoader(
        dataset, batch_size=batch_size, shuffle=True
    )
    
    print("Previewing batch:")
    batch = next(iter(train_dataloader))
    grid = torchvision.utils.make_grid(batch["images"], nrow=4)
    plt.imshow(grid.permute(1, 2, 0).cpu().clip(-1, 1) * 0.5 + 0.5);
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    ConsidĂ©ration 1 : notre taille de batch ici (4) est assez petite, puisque nous entraĂźnons sur une grande taille d’image (256 pixels) en utilisant un modĂšle assez grand et que nous manquerons de RAM du GPU si nous augmentons trop la taille du batch. Vous pouvez rĂ©duire la taille de l’image pour accĂ©lĂ©rer les choses et permettre des batchs plus importants, mais ces modĂšles ont Ă©tĂ© conçus et entraĂźnĂ©s Ă  l’origine pour une gĂ©nĂ©ration de 256 pixels.

    Passons maintenant Ă  la boucle d’entraĂźnement. Nous allons mettre Ă  jour les poids du modĂšle prĂ©-entraĂźnĂ© en fixant la cible d’optimisation Ă  image_pipe.unet.parameters(). Le reste est presque identique Ă  l’exemple de boucle d’entraĂźnement de l’unitĂ© 1. Cela prend environ 10 minutes Ă  exĂ©cuter sur Colab, c’est donc le bon moment pour prendre un cafĂ© ou un thĂ© pendant que vous attendez :

    num_epochs = 2  # @param
    lr = 1e-5  # 2param
    grad_accumulation_steps = 2  # @param
    
    optimizer = torch.optim.AdamW(image_pipe.unet.parameters(), lr=lr)
    
    losses = []
    
    for epoch in range(num_epochs):
        for step, batch in tqdm(enumerate(train_dataloader), total=len(train_dataloader)):
            clean_images = batch["images"].to(device)
            # bruit Ă  ajouter aux images
            noise = torch.randn(clean_images.shape).to(clean_images.device)
            bs = clean_images.shape[0]
    
            # un pas de temps aléatoire pour chaque image
            timesteps = torch.randint(
                0,
                image_pipe.scheduler.num_train_timesteps,
                (bs,),
                device=clean_images.device,
            ).long()
    
            # Ajouter du bruit aux images propres en fonction de la magnitude du bruit Ă  chaque pas de temps
            # (il s'agit du processus de diffusion vers l'avant)
            noisy_images = image_pipe.scheduler.add_noise(clean_images, noise, timesteps)
    
            # Obtenir la prédiction du modÚle pour le bruit
            noise_pred = image_pipe.unet(noisy_images, timesteps, return_dict=False)[0]
    
            # Comparez la prédiction avec le bruit réel :
            loss = F.mse_loss(
                noise_pred, noise
            )  # NB : essayer de prédire le bruit (eps) pas (noisy_ims-clean_ims) ou juste (clean_ims)
    
            # Stocker pour un plot ultérieur
            losses.append(loss.item())
    
            # Mettre Ă  jour les paramĂštres du modĂšle avec l'optimiseur sur la base de cette perte
            loss.backward(loss)
    
            # Accumulation des gradients
            if (step + 1) % grad_accumulation_steps == 0:
                optimizer.step()
                optimizer.zero_grad()
    
        print(
            f"Epoch {epoch} average loss: {sum(losses[-len(train_dataloader):])/len(train_dataloader)}"
        )
    
    # Tracer la courbe de perte :
    plt.plot(losses)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    ConsidĂ©ration 2 : notre signal de perte est extrĂȘmement bruyant, puisque nous ne travaillons qu’avec quatre exemples Ă  des niveaux de bruit alĂ©atoires pour chaque Ă©tape. Ce n’est pas idĂ©al pour l’entraĂźnement. Une solution consiste Ă  utiliser un taux d’apprentissage extrĂȘmement faible pour limiter la taille de la mise Ă  jour Ă  chaque Ă©tape. Ce serait encore mieux si nous pouvions trouver un moyen d’obtenir les mĂȘmes avantages qu’en utilisant une taille de batch plus importante sans que les besoins en mĂ©moire ne montent en flĂšche


    Entrez dans l’accumulation des gradients. Si nous appelons loss.backward() plusieurs fois avant d’exĂ©cuter optimizer.step() et optimizer.zero_grad(), PyTorch accumule (somme) les gradients, fusionnant effectivement le signal de plusieurs batchs pour donner une seule (meilleure) estimation qui est ensuite utilisĂ©e pour mettre Ă  jour les paramĂštres. Il en rĂ©sulte moins de mises Ă  jour totales, tout comme nous le verrions si nous utilisions une taille de batch plus importante. C’est quelque chose que de nombreux frameworks gĂšrent pour vous (par exemple, đŸ€— Accelerate rend cela facile), mais il est agrĂ©able de le voir mis en Ɠuvre Ă  partir de zĂ©ro car il s’agit d’une technique utile pour traiter l’entraĂźnement sous les contraintes de mĂ©moire du GPU ! Comme vous pouvez le voir dans le code ci-dessus (aprĂšs le commentaire # Gradient accumulation), il n’y a pas vraiment besoin de beaucoup de code.

    ✏ À votre tour ! Voyez si vous pouvez ajouter l’accumulation des gradients Ă  la boucle d’entraĂźnement de l’unitĂ© 1. Comment se comporte-t-elle ? RĂ©flĂ©chissez Ă  la maniĂšre dont vous pourriez ajuster le taux d’apprentissage en fonction du nombre d’étapes d’accumulation des gradients ; devrait-il rester identique Ă  auparavant ?

    ConsidĂ©ration 3 : Cela prend encore beaucoup de temps, et afficher une mise Ă  jour d’une ligne Ă  chaque Ă©poque n’est pas suffisant pour nous donner une bonne idĂ©e de ce qui se passe. Nous devrions probablement :

    • GĂ©nĂ©rer quelques Ă©chantillons de temps en temps pour examiner visuellement la performance qualitativement au fur et Ă  mesure que le modĂšle s’entraĂźne.
    • Enregistrer des Ă©lĂ©ments tels que la perte et les gĂ©nĂ©rations d’échantillons pendant l’entraĂźnement, peut-ĂȘtre en utilisant quelque chose comme Weights and Biases ou Tensorboard.

    Nous avons créé un script rapide (finetune_model.py) qui reprend le code d’entraĂźnement ci-dessus et y ajoute une fonctionnalitĂ© minimale de logging. Vous pouvez voir les logs d’un entraĂźnement ci-dessous :

    %wandb johnowhitaker/dm_finetune/2upaa341 # Vous aurez besoin d'un compte W&B pour que cela fonctionne - sautez si vous ne voulez pas vous connecter.
    

    Il est amusant de voir comment les Ă©chantillons gĂ©nĂ©rĂ©s changent au fur et Ă  mesure que l’entraĂźnement progresse. MĂȘme si la perte ne semble pas s’amĂ©liorer beaucoup, on peut voir une progression du domaine original (images de chambres Ă  coucher) vers les nouvelles donnĂ©es d’entraĂźnement (wikiart). A la fin de ce notebook se trouve un code commentĂ© pour finetunĂ© un modĂšle en utilisant ce script comme alternative Ă  l’exĂ©cution de la cellule ci-dessus.

    ✏ À votre tour ! Voyez si vous pouvez modifier l’exemple officiel de script d’entraĂźnement que nous avons vu dans l’unitĂ© 1 pour commencer avec un modĂšle prĂ©-entraĂźnĂ© plutĂŽt que d’entraĂźner Ă  partir de zĂ©ro. Comparez-le au script minimal dont le lien figure ci-dessus ; quelles sont les fonctionnalitĂ©s supplĂ©mentaires qui manquent au script minimal ? En gĂ©nĂ©rant quelques images avec ce modĂšle, nous pouvons voir que ces visages ont dĂ©jĂ  l’air trĂšs Ă©tranges !

    x = torch.randn(8, 3, 256, 256).to(device)  # Batch de 8
    for i, t in tqdm(enumerate(scheduler.timesteps)):
        model_input = scheduler.scale_model_input(x, t)
        with torch.no_grad():
            noise_pred = image_pipe.unet(model_input, t)["sample"]
        x = scheduler.step(noise_pred, t, x).prev_sample
    grid = torchvision.utils.make_grid(x, nrow=4)
    plt.imshow(grid.permute(1, 2, 0).cpu().clip(-1, 1) * 0.5 + 0.5);
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    ConsidĂ©ration 4 : Le finetuning peut ĂȘtre tout Ă  fait imprĂ©visible ! Si nous entraĂźnions plus longtemps, nous pourrions voir des papillons parfaits. Mais les Ă©tapes intermĂ©diaires peuvent ĂȘtre extrĂȘmement intĂ©ressantes en elles-mĂȘmes, surtout si vos intĂ©rĂȘts sont plutĂŽt artistiques ! EntraĂźnez sur des pĂ©riodes trĂšs courtes ou trĂšs longues et faites varier le taux d’apprentissage pour voir comment cela affecte les types de rĂ©sultats produits par le modĂšle final.

    Code pour finetuner un modĂšle en utilisant le script d’exemple minimal que nous avons utilisĂ© sur le modĂšle de dĂ©monstration WikiArt

    Si vous souhaitez entraßner un modÚle similaire à celui que nous avons créé sur WikiArt, vous pouvez décommenter et exécuter les cellules ci-dessous. Comme cela prend un certain temps et peut épuiser la mémoire de votre GPU, nous vous conseillons de le faire aprÚs avoir parcouru le reste de ce notebook.

    ## Pour télécharger le script de finetuning :
    # !wget https://github.com/huggingface/diffusion-models-class/raw/main/unit2/finetune_model.py
    
    ## Pour exécuter le script, entraßnant le modÚle de visage sur des visages vintage
    ## (l'idéal est d'exécuter ce script dans un terminal) :
    # !python finetune_model.py --image_size 128 --batch_size 8 --num_epochs 16\
    #     --grad_accumulation_steps 2 --start_model "google/ddpm-celebahq-256"\
    #     --dataset_name "Norod78/Vintage-Faces-FFHQAligned" --wandb_project 'dm-finetune'\
    #     --log_samples_every 100 --save_model_every 1000 --model_save_name 'vintageface'
    

    Sauvegarde et chargement des pipelines finetunés

    Maintenant que nous avons finetuné le UNet dans notre modÚle de diffusion, sauvegardons-le dans un dossier local en exécutant :

    image_pipe.save_pretrained("my-finetuned-model")
    

    Comme nous l’avons vu dans l’unitĂ© 1, cela permet de sauvegarder la configuration, le modĂšle et le planificateur :

    !ls {"my-finetuned-model"}
    

    Ensuite, vous pouvez suivre les mĂȘmes Ă©tapes que celles dĂ©crites dans le notebook d’introduction Ă  đŸ€— Diffusers de l’unitĂ© 1 pour pousser le modĂšle vers le Hub en vue d’une utilisation ultĂ©rieure :

    # Code pour télécharger un pipeline sauvegardé localement vers le Hub
    from huggingface_hub import HfApi, ModelCard, create_repo, get_full_repo_name
    
    # Mise en place du repo et téléchargement des fichiers
    model_name = "ddpm-celebahq-finetuned-butterflies-2epochs"  # @param Le nom que vous souhaitez lui donner sur le Hub
    local_folder_name = "my-finetuned-model"  # @param Créé par le script ou par vous via image_pipe.save_pretrained('save_name')
    description = "Describe your model here"  # @param
    hub_model_id = get_full_repo_name(model_name)
    create_repo(hub_model_id)
    api = HfApi()
    api.upload_folder(
        folder_path=f"{local_folder_name}/scheduler", path_in_repo="", repo_id=hub_model_id
    )
    api.upload_folder(
        folder_path=f"{local_folder_name}/unet", path_in_repo="", repo_id=hub_model_id
    )
    api.upload_file(
        path_or_fileobj=f"{local_folder_name}/model_index.json",
        path_in_repo="model_index.json",
        repo_id=hub_model_id,
    )
    
    # Ajouter une carte modĂšle (facultatif mais sympa !)
    content = f"""
    ---
    license: mit
    tags:
    - pytorch
    - diffusers
    - unconditional-image-generation
    - diffusion-models-class
    ---
    
    # Example Fine-Tuned Model for Unit 2 of the [Diffusion Models Class 🧹](https://github.com/huggingface/diffusion-models-class)
    
    {description}
    
    ## Usage
    
    ```python
    from diffusers import DDPMPipeline
    
    pipeline = DDPMPipeline.from_pretrained('{hub_model_id}')
    image = pipeline().images[0]
    image
    ```python
    """
    
    card = ModelCard(content)
    card.push_to_hub(hub_model_id)
         
    
    'https://huggingface.co/lewtun/ddpm-celebahq-finetuned-butterflies-2epochs/blob/main/README.md'
    

    Félicitations, vous avez maintenant finetuné votre premier modÚle de diffusion !

    Pour le reste de ce notebook, nous utiliserons un modĂšle que nous avons finetunĂ© Ă  partir d’un modĂšle entraĂźnĂ© sur LSUN bedrooms environ une fois sur le WikiArt dataset. Si vous prĂ©fĂ©rez, vous pouvez sauter cette cellule et utiliser le pipeline faces/butterflies que nous avons finetunĂ© dans la section prĂ©cĂ©dente ou en charger un depuis le Hub Ă  la place :

    # Chargement du pipeline pré-entraßné
    pipeline_name = "johnowhitaker/sd-class-wikiart-from-bedrooms"
    image_pipe = DDPMPipeline.from_pretrained(pipeline_name).to(device)
    
    # Échantillon d'images avec un planificateur DDIM sur 40 Ă©tapes
    scheduler = DDIMScheduler.from_pretrained(pipeline_name)
    scheduler.set_timesteps(num_inference_steps=40)
    
    # Point de départ aléatoire (batch de 8 images)
    x = torch.randn(8, 3, 256, 256).to(device)
    
    # Boucle d'échantillonnage minimale
    for i, t in tqdm(enumerate(scheduler.timesteps)):
        model_input = scheduler.scale_model_input(x, t)
        with torch.no_grad():
            noise_pred = image_pipe.unet(model_input, t)["sample"]
        x = scheduler.step(noise_pred, t, x).prev_sample
    
    # Voir les résultats
    grid = torchvision.utils.make_grid(x, nrow=4)
    plt.imshow(grid.permute(1, 2, 0).cpu().clip(-1, 1) * 0.5 + 0.5);
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    ConsidĂ©ration 5 : Il est souvent difficile de savoir si le finetunĂ© fonctionne bien, et ce que l’on entend par “bonnes performances” peut varier selon le cas d’utilisation. Par exemple, si vous finetunĂ© un modĂšle conditionnĂ© par du texte comme Stable Diffusion sur un petit jeu de donnĂ©es, vous voudrez probablement qu’il conserve la plus grande partie de son apprentissage original afin de pouvoir comprendre des prompts arbitraires non couverts par votre nouveau jeu de donnĂ©es, tout en s’adaptant pour mieux correspondre au style de vos nouvelles donnĂ©es d’entraĂźnement. Cela pourrait signifier l’utilisation d’un faible taux d’apprentissage avec quelque chose comme la moyenne exponentielle du modĂšle, comme dĂ©montrĂ© dans cet excellent article de blog sur la crĂ©ation d’une version Pokemon de Stable Diffusion. Dans une autre situation, vous pouvez vouloir rĂ©-entraĂźner complĂštement un modĂšle sur de nouvelles donnĂ©es (comme notre exemple chambre → wikiart), auquel cas un taux d’apprentissage plus Ă©levĂ© et un entraĂźnement plus poussĂ© s’avĂšrent judicieux. MĂȘme si le graphique de la perte ne montre pas beaucoup d’amĂ©lioration, les Ă©chantillons s’éloignent clairement des donnĂ©es d’origine et s’orientent vers des rĂ©sultats plus “artistiques”, bien qu’ils restent pour la plupart incohĂ©rents.

    Ce qui nous amĂšne Ă  la section suivante, oĂč nous examinons comment nous pourrions ajouter des conseils supplĂ©mentaires Ă  un tel modĂšle pour mieux contrĂŽler les rĂ©sultats.

    Guidage

    Que faire si l’on souhaite exercer un certain contrĂŽle sur les Ă©chantillons gĂ©nĂ©rĂ©s ? Par exemple, supposons que nous voulions biaiser les images gĂ©nĂ©rĂ©es pour qu’elles soient d’une couleur spĂ©cifique. Comment procĂ©der ? C’est lĂ  qu’intervient le guidage, une technique qui permet d’ajouter un contrĂŽle supplĂ©mentaire au processus d’échantillonnage.

    La premiĂšre Ă©tape consiste Ă  crĂ©er notre fonction de conditionnement : une mesure (perte) que nous souhaitons minimiser. En voici une pour l’exemple de la couleur, qui compare les pixels d’une image Ă  une couleur cible (par dĂ©faut, une sorte de sarcelle claire) et renvoie l’erreur moyenne :

    def color_loss(images, target_color=(0.1, 0.9, 0.5)):
        """Étant donnĂ© une couleur cible (R, G, B), retourner une perte correspondant Ă  la distance moyenne entre 
    	les pixels de l'image et cette couleur. Par défaut, il s'agit d'une couleur sarcelle claire : (0.1, 0.9, 0.5)"""
        target = (
            torch.tensor(target_color).to(images.device) * 2 - 1
        )  # Map target color to (-1, 1)
        target = target[
            None, :, None, None
        ]  # Obtenir la forme nécessaire pour fonctionner avec les images (b, c, h, w)
        error = torch.abs(
            images - target
        ).mean()  # Différence absolue moyenne entre les pixels de l'image et la couleur cible
        return error
    

    Ensuite, nous allons crĂ©er une version modifiĂ©e de la boucle d’échantillonnage oĂč, Ă  chaque Ă©tape, nous ferons ce qui suit :

    • CrĂ©er une nouvelle version de x avec requires_grad = True
    • Calculer la version dĂ©bruitĂ©e (x0)
    • Introduire la version prĂ©dite x0 dans notre fonction de perte
    • Trouver le gradient de cette fonction de perte par rapport Ă  x
    • Utiliser ce gradient de conditionnement pour modifier x avant d’utiliser le planificateur, en espĂ©rant pousser x dans une direction qui conduira Ă  une perte plus faible selon notre fonction d’orientation.

    Il existe deux variantes que vous pouvez explorer. Dans la premiĂšre, nous fixons requires_grad sur x aprĂšs avoir obtenu notre prĂ©diction de bruit du UNet, ce qui est plus efficace en termes de mĂ©moire (puisque nous n’avons pas Ă  retracer les gradients Ă  travers le modĂšle de diffusion), mais donne un gradient moins prĂ©cis. Dans le second cas, nous dĂ©finissons d’abord requires_grad sur x, puis nous le faisons passer par l’unet et nous calculons le x0 prĂ©dit.

    # Variante 1 : méthode rapide
    
    # L'échelle de guidance détermine l'intensité de l'effet
    guidance_loss_scale = 40  # Envisagez de modifier cette valeur Ă  5, ou Ă  100
    
    x = torch.randn(8, 3, 256, 256).to(device)
    
    for i, t in tqdm(enumerate(scheduler.timesteps)):
    
        # Préparer l'entrée du modÚle
        model_input = scheduler.scale_model_input(x, t)
    
        # Prédire le bruit résiduel
        with torch.no_grad():
            noise_pred = image_pipe.unet(model_input, t)["sample"]
    
        # Fixer x.requires_grad Ă  True
        x = x.detach().requires_grad_()
    
        # Obtenir la valeur prédite x0
        x0 = scheduler.step(noise_pred, t, x).pred_original_sample
    
        # Calculer la perte
        loss = color_loss(x0) * guidance_loss_scale
        if i % 10 == 0:
            print(i, "loss:", loss.item())
    
        # Obtenir le gradient
        cond_grad = -torch.autograd.grad(loss, x)[0]
    
        # Modifier x en fonction de ce gradient
        x = x.detach() + cond_grad
    
        # Le planificateur
        x = scheduler.step(noise_pred, t, x).prev_sample
    
    # Voir le résultat
    grid = torchvision.utils.make_grid(x, nrow=4)
    im = grid.permute(1, 2, 0).cpu().clip(-1, 1) * 0.5 + 0.5
    Image.fromarray(np.array(im * 255).astype(np.uint8))
    
    0 loss: 27.279136657714844
    10 loss: 11.286816596984863
    20 loss: 10.683112144470215
    30 loss: 10.942476272583008
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Cette deuxiĂšme option nĂ©cessite presque le double de RAM GPU pour fonctionner, mĂȘme si nous ne gĂ©nĂ©rons qu’un batch de quatre images au lieu de huit. Voyez si vous pouvez repĂ©rer la diffĂ©rence et rĂ©flĂ©chissez Ă  la raison pour laquelle cette mĂ©thode est plus « prĂ©cise » :

    # Variante 2 : définir x.requires_grad avant de calculer les prédictions du modÚle
    
    guidance_loss_scale = 40
    x = torch.randn(4, 3, 256, 256).to(device)
    
    for i, t in tqdm(enumerate(scheduler.timesteps)):
    
        # Définir requires_grad avant la passe avant du modÚle
        x = x.detach().requires_grad_()
        model_input = scheduler.scale_model_input(x, t)
    
        # prédire (avec grad cette fois)
        noise_pred = image_pipe.unet(model_input, t)["sample"]
    
        # Obtenir la valeur prédite x0 :
        x0 = scheduler.step(noise_pred, t, x).pred_original_sample
    
        # Calculer la perte
        loss = color_loss(x0) * guidance_loss_scale
        if i % 10 == 0:
            print(i, "loss:", loss.item())
    
        # Obtenir le gradient
        cond_grad = -torch.autograd.grad(loss, x)[0]
    
        # Modifier x en fonction de ce gradient
        x = x.detach() + cond_grad
    
        # Le planificateur
        x = scheduler.step(noise_pred, t, x).prev_sample
    
    
    grid = torchvision.utils.make_grid(x, nrow=4)
    im = grid.permute(1, 2, 0).cpu().clip(-1, 1) * 0.5 + 0.5
    Image.fromarray(np.array(im * 255).astype(np.uint8))
    
    0 loss: 30.750328063964844
    10 loss: 18.550724029541016
    20 loss: 17.515094757080078
    30 loss: 17.55681037902832
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Dans la seconde variante, les besoins en mĂ©moire sont plus importants et l’effet est moins prononcĂ©, de sorte que vous pouvez penser qu’elle est infĂ©rieure. Cependant, les rĂ©sultats sont sans doute plus proches des types d’images sur lesquels le modĂšle a Ă©tĂ© entraĂźnĂ©, et vous pouvez toujours augmenter l’échelle de guidage pour obtenir un effet plus important. L’approche que vous utiliserez dĂ©pendra en fin de compte de ce qui fonctionne le mieux sur le plan expĂ©rimental.

    ✏ À votre tour ! Choisissez votre couleur prĂ©fĂ©rĂ©e et recherchez ses valeurs dans l’espace RGB. Modifiez la ligne color_loss() dans la cellule ci-dessus pour recevoir ces nouvelles valeurs RGB et examinez les rĂ©sultats ; correspondent-ils Ă  ce que vous attendez ?

    Guidage avec CLIP

    Guider vers une couleur nous donne un peu de contrÎle, mais que se passerait-il si nous pouvions simplement taper un texte décrivant ce que nous voulons ?

    CLIP est un modĂšle créé par OpenAI qui nous permet de comparer des images Ă  des lĂ©gendes textuelles. C’est extrĂȘmement puissant, car cela nous permet de quantifier Ă  quel point une image correspond Ă  un prompt. Et comme le processus est diffĂ©rentiable, nous pouvons l’utiliser comme fonction de perte pour guider notre modĂšle de diffusion !

    Nous n’entrerons pas dans les dĂ©tails ici. L’approche de base est la suivante :

    • EnchĂąsser le prompt pour obtenir un enchĂąssement CLIP Ă  512 dimensions
    • Pour chaque Ă©tape du processus du modĂšle de diffusion :
      • CrĂ©er plusieurs variantes de l’image dĂ©bruitĂ©e prĂ©dite (le fait d’avoir plusieurs variantes permet d’obtenir un signal de perte plus propre).
      • Pour chacune d’entre elles, enchĂąsser l’image avec CLIP et comparez cet enchĂąssement avec celui du prompt (Ă  l’aide d’une mesure appelĂ©e « distance du grand cercle »).
    • Calculer le gradient de cette perte par rapport Ă  l’image bruyante actuelle x et utiliser ce gradient pour modifier x avant de le mettre Ă  jour avec le planificateur.

    Pour une explication plus approfondie de CLIP, consultez cette leçon sur le sujet ou ce rapport sur le projet OpenCLIP que nous utilisons pour charger le modÚle CLIP. Exécutez la cellule suivante pour charger un modÚle CLIP :

    import open_clip
    
    clip_model, _, preprocess = open_clip.create_model_and_transforms(
        "ViT-B-32", pretrained="openai"
    )
    clip_model.to(device)
    
    # Transformations pour redimensionner et augmenter une image + normalisation pour correspondre aux données entraßnées par CLIP
    tfms = torchvision.transforms.Compose(
        [
            torchvision.transforms.RandomResizedCrop(224),  # CROP aléatoire à chaque fois
            torchvision.transforms.RandomAffine(
                5
            ),  # Une augmentation aléatoire possible : biaiser l'image
            torchvision.transforms.RandomHorizontalFlip(),  # Vous pouvez ajouter des augmentations supplémentaires si vous le souhaitez
            torchvision.transforms.Normalize(
                mean=(0.48145466, 0.4578275, 0.40821073),
                std=(0.26862954, 0.26130258, 0.27577711),
            ),
        ]
    )
    
    # Et définir une fonction de perte qui prend une image, l'enchùsse et la compare avec les caractéristiques textuelles du prompt
    def clip_loss(image, text_features):
        image_features = clip_model.encode_image(
            tfms(image)
        )  # Note : applique les transformations ci-dessus
        input_normed = torch.nn.functional.normalize(image_features.unsqueeze(1), dim=2)
        embed_normed = torch.nn.functional.normalize(text_features.unsqueeze(0), dim=2)
        dists = (
            input_normed.sub(embed_normed).norm(dim=2).div(2).arcsin().pow(2).mul(2)
        )  # Distance du grand cercle
        return dists.mean()
    

    Une fois la fonction de perte dĂ©finie, notre boucle d’échantillonnage guidĂ© ressemble aux exemples prĂ©cĂ©dents, en remplaçant color_loss() par notre nouvelle fonction de perte basĂ©e sur CLIP :

    prompt = "Red Rose (still life), red flower painting"  # @param
    
    # Explorer en changeant ça
    guidance_scale = 8  # @param
    n_cuts = 4  # @param
    
    # Plus d'étapes -> plus de temps pour que le guidage ait un effet
    scheduler.set_timesteps(50)
    
    # Nous enchĂąssons un prompt avec CLIP comme cible
    text = open_clip.tokenize([prompt]).to(device)
    with torch.no_grad(), torch.cuda.amp.autocast():
        text_features = clip_model.encode_text(text)
    
    
    x = torch.randn(4, 3, 256, 256).to(
        device
    )  # L'utilisation de la RAM est Ă©levĂ©e, vous ne voulez peut-ĂȘtre qu'une seule image Ă  la fois.
    
    for i, t in tqdm(enumerate(scheduler.timesteps)):
    
        model_input = scheduler.scale_model_input(x, t)
    
        # prédire le bruit résiduel
        with torch.no_grad():
            noise_pred = image_pipe.unet(model_input, t)["sample"]
    
        cond_grad = 0
    
        for cut in range(n_cuts):
    
            # nécessite un grad sur x
            x = x.detach().requires_grad_()
    
            # Obtenir le x0 prédit
            x0 = scheduler.step(noise_pred, t, x).pred_original_sample
    
            # Calculer la perte
            loss = clip_loss(x0, text_features) * guidance_scale
    
            # Obtenir le gradient (échelle par n_cuts puisque nous voulons la moyenne)
            cond_grad -= torch.autograd.grad(loss, x)[0] / n_cuts
    
        if i % 25 == 0:
            print("Step:", i, ", Guidance loss:", loss.item())
    
        # Modifier x en fonction de ce gradient
        alpha_bar = scheduler.alphas_cumprod[i]
        x = (
            x.detach() + cond_grad * alpha_bar.sqrt()
        )  # Note the additional scaling factor here!
    
        # Le planificateur
        x = scheduler.step(noise_pred, t, x).prev_sample
    
    
    grid = torchvision.utils.make_grid(x.detach(), nrow=4)
    im = grid.permute(1, 2, 0).cpu().clip(-1, 1) * 0.5 + 0.5
    Image.fromarray(np.array(im * 255).astype(np.uint8))
    
    Step: 0 , Guidance loss: 7.437869548797607
    Step: 25 , Guidance loss: 7.174620628356934
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Cela ressemble un peu Ă  des roses ! Ce n’est pas parfait, mais si vous jouez avec les paramĂštres, vous pouvez obtenir des images agrĂ©ables.

    Si vous examinez le code ci-dessus, vous verrez que nous mettons Ă  l’échelle le gradient de conditionnement par un facteur de alpha_bar.sqrt(). Il existe des thĂ©ories sur la “bonne” maniĂšre d’échelonner ces gradients, mais en pratique, vous pouvez expĂ©rimenter. Pour certains types de guidage, vous voudrez peut-ĂȘtre que la plupart des effets soient concentrĂ©s dans les premiĂšres Ă©tapes, pour d’autres (par exemple, une perte de style axĂ©e sur les textures), vous prĂ©fĂ©rerez peut-ĂȘtre qu’ils n’interviennent que vers la fin du processus de gĂ©nĂ©ration. Quelques programmes possibles sont prĂ©sentĂ©s ci-dessous :

    plt.plot([1 for a in scheduler.alphas_cumprod], label="no scaling")
    plt.plot([a for a in scheduler.alphas_cumprod], label="alpha_bar")
    plt.plot([a.sqrt() for a in scheduler.alphas_cumprod], label="alpha_bar.sqrt()")
    plt.plot(
        [(1 - a).sqrt() for a in scheduler.alphas_cumprod], label="(1-alpha_bar).sqrt()"
    )
    plt.legend()
    plt.title("Possible guidance scaling schedules")
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    ExpĂ©rimentez avec diffĂ©rents planificateurs, Ă©chelles de guidage et toute autre astuce Ă  laquelle vous pouvez penser (l’écrĂȘtage des gradients dans une certaine plage est une modification populaire) pour voir jusqu’à quel point vous pouvez obtenir ce rĂ©sultat ! N’oubliez pas non plus d’essayer d’intervertir d’autres modĂšles. Peut-ĂȘtre le modĂšle de visages que nous avons chargĂ© au dĂ©but ; pouvez-vous le guider de maniĂšre fiable pour produire un visage masculin ? Que se passe-t-il si vous combinez le guidage CLIP avec la perte de couleur que nous avons utilisĂ©e plus tĂŽt ? Etc.

    Si vous consultez quelques codes pour la diffusion guidĂ©e par CLIP en pratique, vous verrez une approche plus complexe avec une meilleure classe pour choisir des dĂ©coupes alĂ©atoires dans les images et de nombreux ajustements supplĂ©mentaires de la fonction de perte pour de meilleures performances. Avant l’apparition des modĂšles de diffusion conditionnĂ©s par le texte, il s’agissait du meilleur systĂšme de conversion texte-image qui soit ! La petite version de notre jouet peut encore ĂȘtre amĂ©liorĂ©e, mais elle capture l’idĂ©e principale : grĂące au guidage et aux capacitĂ©s Ă©tonnantes de CLIP, nous pouvons ajouter le contrĂŽle du texte Ă  un modĂšle de diffusion inconditionnel 🎹.

    Partager une boucle d’échantillonnage personnalisĂ©e en tant que dĂ©mo Gradio

    Vous avez peut-ĂȘtre trouvĂ© une perte amusante pour guider la gĂ©nĂ©ration et vous souhaitez maintenant partager avec le monde entier votre modĂšle finetunĂ© et cette stratĂ©gie d’échantillonnage personnalisĂ©e


    Entrez dans Gradio. Gradio est un outil gratuit et open-source qui permet aux utilisateurs de crĂ©er et de partager facilement des modĂšles interactifs d’apprentissage automatique via une simple interface web. Avec Gradio, les utilisateurs peuvent construire des interfaces personnalisĂ©es pour leurs modĂšles d’apprentissage automatique, qui peuvent ensuite ĂȘtre partagĂ©s avec d’autres par le biais d’une URL unique. Il est Ă©galement intĂ©grĂ© Ă  đŸ€— Spaces, ce qui permet d’hĂ©berger facilement des dĂ©mos et de les partager avec d’autres.

    Nous placerons notre logique de base dans une fonction qui prend certaines entrĂ©es et produit une image en sortie. Cette fonction peut ensuite ĂȘtre enveloppĂ©e dans une interface simple qui permet Ă  l’utilisateur de spĂ©cifier certains paramĂštres (qui sont transmis en tant qu’entrĂ©es Ă  la fonction principale de gĂ©nĂ©ration). De nombreux composants sont disponibles ; pour cet exemple, nous utiliserons un curseur pour l’échelle d’orientation et un sĂ©lecteur de couleurs pour dĂ©finir la couleur cible.

    !pip install -q gradio
    
    import gradio as gr
    from PIL import Image, ImageColor
    
    # La fonction qui fait le gros du travail
    def generate(color, guidance_loss_scale):
        target_color = ImageColor.getcolor(color, "RGB")  # Couleur cible en RGB
        target_color = [a / 255 for a in target_color]  # Rééchelonner de (0, 255) à (0, 1)
        x = torch.randn(1, 3, 256, 256).to(device)
        for i, t in tqdm(enumerate(scheduler.timesteps)):
            model_input = scheduler.scale_model_input(x, t)
            with torch.no_grad():
                noise_pred = image_pipe.unet(model_input, t)["sample"]
            x = x.detach().requires_grad_()
            x0 = scheduler.step(noise_pred, t, x).pred_original_sample
            loss = color_loss(x0, target_color) * guidance_loss_scale
            cond_grad = -torch.autograd.grad(loss, x)[0]
            x = x.detach() + cond_grad
            x = scheduler.step(noise_pred, t, x).prev_sample
        grid = torchvision.utils.make_grid(x, nrow=4)
        im = grid.permute(1, 2, 0).cpu().clip(-1, 1) * 0.5 + 0.5
        im = Image.fromarray(np.array(im * 255).astype(np.uint8))
        im.save("test.jpeg")
        return im
    
    
    # Voir la documentation de gradio pour les types d'entrées et de sorties disponibles.
    inputs = [
        gr.ColorPicker(label="color", value="55FFAA"),  # Ajoutez ici toutes les entrées dont vous avez besoin
        gr.Slider(label="guidance_scale", minimum=0, maximum=30, value=3),
    ]
    outputs = gr.Image(label="result")
    
    # Et l'interface minimale
    demo = gr.Interface(
        fn=generate,
        inputs=inputs,
        outputs=outputs,
        examples=[
            ["#BB2266", 3],
            ["#44CCAA", 5],  # Vous pouvez fournir des exemples d'entrées pour aider les gens à démarrer
        ],
    )
    demo.launch(debug=True)  # debug=True vous permet de voir les erreurs et les sorties dans Colab
    

    Il est possible de construire des interfaces beaucoup plus compliquĂ©es, avec un style fantaisiste et un large Ă©ventail d’entrĂ©es possibles, mais pour cette dĂ©mo, nous la gardons aussi simple que possible.

    Les dĂ©mos sur đŸ€— Spaces s’exĂ©cutent par dĂ©faut sur CPU, il est donc prĂ©fĂ©rable de prototyper votre interface dans Colab (comme ci-dessus) avant de la migrer. Lorsque vous ĂȘtes prĂȘt Ă  partager votre dĂ©mo, vous devez crĂ©er un Space, mettre en place un fichier requirements.txt listant les bibliothĂšques que votre code utilisera, puis placer tout le code dans un fichier app.py qui dĂ©finit les fonctions pertinentes et l’interface.

    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Heureusement pour vous, il est Ă©galement possible de “dupliquer” un Space. Vous pouvez visiter le Space ici et cliquer sur “Dupliquer cet espace” pour obtenir un modĂšle que vous pouvez ensuite modifier pour utiliser votre propre modĂšle et votre propre fonction d’orientation.

    Dans les paramĂštres, vous pouvez configurer votre Space pour qu’il fonctionne avec du matĂ©riel plus sophistiquĂ© (qui est facturĂ© Ă  l’heure). Vous avez créé quelque chose d’extraordinaire et vous voulez le partager sur un meilleur matĂ©riel, mais vous n’avez pas l’argent nĂ©cessaire ? Faites-le nous savoir via Discord et nous verrons si nous pouvons vous aider !

    Résumé et prochaines étapes

    Nous avons couvert beaucoup de choses dans ce notebook ! Récapitulons les idées principales :

    • Il est relativement facile de charger des modĂšles existants et de les Ă©chantillonner avec diffĂ©rents planificateurs
    • Le finetuning ressemble Ă  l’entraĂźnement Ă  partir de zĂ©ro, sauf qu’en partant d’un modĂšle existant, nous espĂ©rons obtenir de meilleurs rĂ©sultats plus rapidement.
    • Pour finetuner de grands modĂšles sur de grandes images, nous pouvons utiliser des astuces comme l’accumulation de gradient pour contourner les limitations de la taille des batchs.
    • L’enregistrement d’échantillons d’images est important pour le finetuning, oĂč une courbe de perte peut ne pas fournir beaucoup d’informations utiles.
    • Le guidage nous permet de prendre un modĂšle inconditionnel et d’orienter le processus de gĂ©nĂ©ration sur la base d’une fonction de guidage/perte, oĂč Ă  chaque Ă©tape nous trouvons le gradient de la perte par rapport Ă  l’image bruitĂ©e $x$ et l’actualisons en fonction de ce gradient avant de passer Ă  l’étape temporelle suivante.
    • Le guidage avec CLIP nous permet de contrĂŽler des modĂšles inconditionnels avec du texte !

    Pour mettre cela en pratique, voici quelques étapes spécifiques que vous pouvez suivre :

    • FinetunĂ© votre propre modĂšle et le pousser vers le Hub. Cela implique de choisir un point de dĂ©part (par exemple, un modĂšle entraĂźnĂ© sur faces, bedrooms, cats ou wikiart et un jeu de donnĂ©es (peut-ĂȘtre ces faces d’animaux ou vos propres images), puis d’entraĂźner soit le code de ce notebook, soit le script d’exemple (utilisation de dĂ©monstration ci-dessous).
    • Explorer le guidage en utilisant votre modĂšle finetunĂ©, soit en utilisant l’une des fonctions de guidage de l’exemple (color_loss ou CLIP), soit en inventant la vĂŽtre.
    • Partagez une dĂ©mo basĂ©e sur ceci en utilisant Gradio, soit en modifiant le Space d’exemple pour utiliser votre propre modĂšle, soit en crĂ©ant votre propre version personnalisĂ©e avec plus de fonctionnalitĂ©s.

    Nous sommes impatients de voir vos rĂ©sultats sur Discord, Twitter et ailleurs đŸ€— !

    2.2. ModÚle de diffusion conditionné par la classe

    Dans ce notebook, nous allons illustrer une façon d’ajouter des informations de conditionnement Ă  un modĂšle de diffusion. Plus prĂ©cisĂ©ment, nous allons entraĂźner un modĂšle de diffusion conditionnĂ© par la classe sur MNIST Ă  la suite de l’exemple d’entraĂźnement Ă  partir de 0 de l’unitĂ© 1, oĂč nous pouvons spĂ©cifier quel chiffre nous voulons que le modĂšle gĂ©nĂšre au moment de l’infĂ©rence.

    Comme indiquĂ© dans l’introduction de cette unitĂ©, il s’agit d’une des nombreuses façons d’ajouter des informations de conditionnement supplĂ©mentaires Ă  un modĂšle de diffusion, et elle a Ă©tĂ© choisie pour sa relative simplicitĂ©. Tout comme le notebook de l’unitĂ© 1, ce notebook n’a qu’un but illustratif et vous pouvez l’ignorer si vous le souhaitez.

    Configuration et préparation des données

    !pip install -q diffusers
    
    import torch
    import torchvision
    from torch import nn
    from torch.nn import functional as F
    from torch.utils.data import DataLoader
    from diffusers import DDPMScheduler, UNet2DModel
    from matplotlib import pyplot as plt
    from tqdm.auto import tqdm
    
    device = 'mps' if torch.backends.mps.is_available() else 'cuda' if torch.cuda.is_available() else 'cpu'
    print(f'Using device: {device}')
    
    # Charger le jeu de données
    dataset = torchvision.datasets.MNIST(root="mnist/", train=True, download=True, transform=torchvision.transforms.ToTensor())
    
    # Introduire les données dans un chargeur de données (batch de taille 8 ici pour la démonstration)
    train_dataloader = DataLoader(dataset, batch_size=8, shuffle=True)
    
    # Visualiser quelques exemples
    x, y = next(iter(train_dataloader))
    print('Input shape:', x.shape)
    print('Labels:', y)
    plt.imshow(torchvision.utils.make_grid(x)[0], cmap='Greys')
    
    Input shape: torch.Size([8, 1, 28, 28]) 
    Labels: tensor([8, 1, 5, 9, 7, 6, 2, 2])
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    CrĂ©ation d’une UNet conditionnĂ©e par la classe

    La façon dont nous introduirons le conditionnement de la classe est la suivante :

    • CrĂ©er un UNet2DModel standard avec quelques canaux d’entrĂ©e supplĂ©mentaires.
    • Associer l’étiquette de la classe Ă  un vecteur appris de forme (class_emb_size) via une couche d’enchĂąssement.
    • ConcatĂ©ner ces informations en tant que canaux supplĂ©mentaires pour l’entrĂ©e interne du UNet avec net_input = torch.cat((x, class_cond), 1)
    • Introduire ce net_input (qui a (class_emb_size+1) canaux au total) dans l’UNet pour obtenir la prĂ©diction finale.

    Dans cet exemple, nous avons fixĂ© la taille de class_emb_size Ă  4, mais c’est complĂštement arbitraire et vous pourriez envisager de la fixer Ă  1 (pour voir si cela fonctionne toujours), Ă  10 (pour correspondre au nombre de classes), ou de remplacer le nn.Embedding appris par un simple encodage Ă  un coup de l’étiquette de la classe directement.

    Voici Ă  quoi ressemble l’implĂ©mentation :

    class ClassConditionedUNet(nn.Module):
      def __init__(self, num_classes=10, class_emb_size=4):
        super().__init__()
        
        # La couche d'intégration associe l'étiquette de la classe à un vecteur de taille `class_emb_size`
        self.class_emb = nn.Embedding(num_classes, class_emb_size)
    
        # Self.model est un UNet inconditionnel avec des canaux d'entrée supplémentaires pour accepter les informations de conditionnement (l'enchùssement de la classe).
        self.model = UNet2DModel(
            sample_size=28,           # la résolution de l'image cible
            in_channels=1 + class_emb_size, # Canaux d'entrée supplémentaires pour la classe conditionnée
            out_channels=1,           # le nombre de canaux de sortie
            layers_per_block=2,       # le nombre de couches ResNet Ă  utiliser par bloc UNet
            block_out_channels=(32, 64, 64), 
            down_block_types=( 
                "DownBlock2D",        # un bloc de sous-échantillonnage ResNet standard
                "AttnDownBlock2D",    # un bloc de sous-échantillonnage ResNet avec auto-attention spatiale
                "AttnDownBlock2D",
            ), 
            up_block_types=(
                "AttnUpBlock2D", 
                "AttnUpBlock2D",      # un bloc de suréchantillonnage ResNet avec auto-attention spatiale
                "UpBlock2D",          # un bloc de suréchantillonnage ResNet standard
              ),
        )
    
      # Notre méthode de transfert prend maintenant les étiquettes de la classe comme argument supplémentaire
      def forward(self, x, t, class_labels):
        # Forme de x :
        bs, ch, w, h = x.shape
        
        # conditionnement de la classe en bon état pour l'ajouter comme canaux d'entrée supplémentaires
        class_cond = self.class_emb(class_labels) # Map to embedding dinemsion
        class_cond = class_cond.view(bs, class_cond.shape[1], 1, 1).expand(bs, class_cond.shape[1], w, h)
        # x est de forme (bs, 1, 28, 28) et class_cond est maintenant (bs, 4, 28, 28)
    
        # L'entrée nette est maintenant x et la classe cond concaténée ensemble le long de la dimension 1
        net_input = torch.cat((x, class_cond), 1) # (bs, 5, 28, 28)
    
        # Cette information est transmise Ă  l'UNet en mĂȘme temps que le pas de temps et renvoie la prĂ©diction
        return self.model(net_input, t).sample # (bs, 1, 28, 28)
    

    Si l’une des formes ou des transformations vous semble confuse, ajoutez des print() pour afficher les formes pertinentes et vĂ©rifiez qu’elles correspondent Ă  vos attentes. Nous avons Ă©galement annotĂ© les formes de certaines variables intermĂ©diaires dans l’espoir de rendre les choses plus claires.

    Entraßnement et échantillonnage

    Alors qu’auparavant nous faisions quelque chose comme prediction = UNet(x, t), nous allons maintenant ajouter les bonnes Ă©tiquettes comme troisiĂšme argument (prediction = UNet(x, t, y)) pendant l’entraĂźnement, et lors de l’infĂ©rence nous pouvons passer les Ă©tiquettes que nous voulons et si tout va bien le modĂšle devrait gĂ©nĂ©rer des images qui correspondent. $y$ dans ce cas est l’étiquette des chiffres MNIST, avec des valeurs de 0 Ă  9.

    La boucle d’entraĂźnement est trĂšs similaire Ă  l’exemple de l’unitĂ© 1. Nous prĂ©disons maintenant le bruit (plutĂŽt que l’image dĂ©bruitĂ©e comme dans l’unitĂ© 1) pour correspondre Ă  l’objectif attendu par le DDPMScheduler par dĂ©faut que nous utilisons pour ajouter du bruit pendant l’entraĂźnement et pour gĂ©nĂ©rer des Ă©chantillons au moment de l’infĂ©rence. L’entraĂźnement prend du temps. L’accĂ©lĂ©rer pourrait ĂȘtre un mini-projet amusant, mais la plupart d’entre vous peuvent probablement parcourir le code (et en fait tout ce notebook) sans l’exĂ©cuter puisque nous ne faisons qu’illustrer une idĂ©e.

    # Créer un planificateur
    noise_scheduler = DDPMScheduler(num_train_timesteps=1000, beta_schedule='squaredcos_cap_v2')
    
    # Redéfinition du chargeur de données pour fixer la taille du batch à un niveau supérieur à la démonstration de 8
    train_dataloader = DataLoader(dataset, batch_size=128, shuffle=True)
    
    # Combien de fois devrions-nous passer les données en revue ?
    n_epochs = 10
    
    # Notre réseau 
    net = ClassConditionedUNet().to(device)
    
    # Notre fonction de perte
    loss_fn = nn.MSELoss()
    
    # L'optimiseur
    opt = torch.optim.Adam(net.parameters(), lr=1e-3) 
    
    # Conserver une trace des pertes pour les consulter ultérieurement
    losses = []
    
    # La boucle d'entraĂźnement
    for epoch in range(n_epochs):
        for x, y in tqdm(train_dataloader):
            
            # Obtenir des données et préparer la version corrompue
            x = x.to(device) * 2 - 1 # Données sur le GPU (sur (-1, 1))
            y = y.to(device)
            noise = torch.randn_like(x)
            timesteps = torch.randint(0, 999, (x.shape[0],)).long().to(device)
            noisy_x = noise_scheduler.add_noise(x, noise, timesteps)
    
            # Obtenir la prédiction du modÚle
            pred = net(noisy_x, timesteps, y) # Notez que nous passons les étiquettes y
    
            # Calculer la perte
            loss = loss_fn(pred, noise) # Quelle est la distance entre la sortie et le bruit ?
    
            # Rétropopagation et mise à jour des paramÚtres :
            opt.zero_grad()
            loss.backward()
            opt.step()
    
            # Stocker la perte pour plus tard
            losses.append(loss.item())
    
        # Afficher la moyenne des 100 derniÚres valeurs de perte pour vous faire une idée de la progression :
        avg_loss = sum(losses[-100:])/100
        print(f'Finished epoch {epoch}. Average of the last 100 loss values: {avg_loss:05f}')
    
    # Visualiser la courbe des pertes
    plt.plot(losses)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Une fois l’entraĂźnement terminĂ©, nous pouvons Ă©chantillonner quelques images en introduisant diffĂ©rentes Ă©tiquettes comme conditionnement :

    # Préparer un x aléatoire comme point de départ, ainsi que les étiquettes souhaitées y
    x = torch.randn(80, 1, 28, 28).to(device)
    y = torch.tensor([[i]*8 for i in range(10)]).flatten().to(device)
    
    # Boucle d'échantillonnage
    for i, t in tqdm(enumerate(noise_scheduler.timesteps)):
    
        # Obtenir la prédiction du modÚle
        with torch.no_grad():
            residual = net(x, t, y)  # Notez à nouveau que nous transmettons nos étiquettes y
    
        # Mise à jour de l'échantillon avec l'étape
        x = noise_scheduler.step(residual, t, x).prev_sample
    
    # Montrer les résultats
    fig, ax = plt.subplots(1, 1, figsize=(12, 12))
    ax.imshow(torchvision.utils.make_grid(x.detach().cpu().clip(-1, 1), nrow=8)[0], cmap='Greys')
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Nous y voilĂ  ! Nous pouvons maintenant contrĂŽler les images produites.

    Nous espĂ©rons que cet exemple vous a plu. Comme toujours, n’hĂ©sitez pas Ă  poser des questions sur Discord.

    ✏ À votre tour !
    Essayez de refaire la mĂȘme chose avec FashionMNIST. Modifiez le taux d’apprentissage, la taille du batch et le nombre d’époques.

    Pouvez-vous obtenir des images de mode dĂ©centes avec moins de temps d’entraĂźnement que dans l’exemple ci-dessus ?

    3. Vue d'ensemble

    Dans cette unitĂ©, vous allez dĂ©couvrir un puissant modĂšle de diffusion appelĂ© Stable Diffusion (SD) et explorer ce qu’il peut faire.

    Vue d’ensemble de cette unitĂ©

    Les différentes étapes à suivre pour cette unité :

    • Lisez le matĂ©riel ci-dessous pour avoir une vue d’ensemble des idĂ©es clĂ©s de cette unitĂ©
    • Consultez le notebook Introduction Ă  Stable Diffusion pour voir l’application pratique de SD dans des cas d’utilisation courants.
    • (Facultatif) Consultez la vidĂ©o Stable Diffusion Deep Dive (en anglais) et le notebook qui l’accompagne pour une exploration plus approfondie des diffĂ©rents composants et de la façon dont ils peuvent ĂȘtre adaptĂ©s Ă  diffĂ©rents effets. Ce matĂ©riel a Ă©tĂ© créé pour le cours de FastAI, Stable Diffusion from the Foundations (en anglais), ce qui en fait un excellent complĂ©ment Ă  ce cours pour tous ceux qui sont curieux de construire ce type de modĂšles Ă  partir de zĂ©ro.

    Introduction

    SD example images
    Exemples d’images gĂ©nĂ©rĂ©es Ă  l’aide de Stable Diffusion

    Stable Diffusion est un puissant modÚle de diffusion latent conditionné par le texte. Ne vous inquiétez pas, nous expliquerons ces mots dans quelques instants ! Sa capacité à créer des images étonnantes à partir de descriptions textuelles en a fait une sensation sur Internet. Dans cette unité, nous allons explorer le fonctionnement du modÚle de diffusion latent et voir quels sont ses autres atouts.

    Diffusion latente

    Plus la taille des images augmente, plus la puissance de calcul nĂ©cessaire pour travailler avec ces images s’accroĂźt. Ceci est particuliĂšrement prononcĂ© dans une opĂ©ration appelĂ©e auto-attention, oĂč le nombre d’opĂ©rations croĂźt de façon quadratique avec le nombre d’entrĂ©es. Une image carrĂ©e de 128 pixels a 4 fois plus de pixels qu’une image carrĂ©e de 64 pixels, et nĂ©cessite donc 16 fois (c’est-Ă -dire 4ÂČ) la mĂ©moire et le calcul dans une couche d’auto-attention. Ce problĂšme se pose pour tous ceux qui souhaitent gĂ©nĂ©rer des images en haute rĂ©solution !

    latent diffusion diagram
    Diagramme provenant du papier High-Resolution Image Synthesis with Latent Diffusion Models

    La diffusion latente permet d’attĂ©nuer ce problĂšme en utilisant un modĂšle distinct appelĂ© auto-encodeur variationnel (VAE pour Variational Auto-Encoder) pour compresser les images dans une dimension spatiale plus petite. Le raisonnement sous-jacent est que les images ont tendance Ă  contenir une grande quantitĂ© d’informations redondantes. Avec suffisamment de donnĂ©es d’entraĂźnement, un VAE peut espĂ©rer apprendre Ă  produire une reprĂ©sentation beaucoup plus petite d’une image d’entrĂ©e et ensuite reconstruire l’image sur la base de cette petite reprĂ©sentation latente avec un haut degrĂ© de fidĂ©litĂ©. Le VAE utilisĂ© dans SD prend des images Ă  3 canaux et produit une reprĂ©sentation latente Ă  4 canaux avec un facteur de rĂ©duction de 8 pour chaque dimension spatiale. En d’autres termes, une image d’entrĂ©e carrĂ©e de 512 pixels sera compressĂ©e en une reprĂ©sentation latente de 4x64x64.

    En appliquant le processus de diffusion Ă  ces reprĂ©sentations latentes plutĂŽt qu’à des images en pleine rĂ©solution, nous pouvons bĂ©nĂ©ficier de nombreux avantages liĂ©s Ă  l’utilisation d’images plus petites (moins d’utilisation de mĂ©moire, moins de couches nĂ©cessaires dans le UNet, des temps de gĂ©nĂ©ration plus rapides
) tout en dĂ©codant le rĂ©sultat en une image en haute rĂ©solution une fois que nous sommes prĂȘts Ă  voir le rĂ©sultat final. Cette solution permet de rĂ©duire considĂ©rablement le coĂ»t de l’entraĂźnement et d’exĂ©cution de ces modĂšles.

    Conditionnement

    Dans l’unitĂ© 2, nous avons montrĂ© comment l’apport d’informations supplĂ©mentaires au UNet nous permet d’exercer un contrĂŽle supplĂ©mentaire sur les types d’images gĂ©nĂ©rĂ©es. C’est ce que nous appelons le conditionnement. Étant donnĂ© une version bruitĂ©e d’une image, le modĂšle est chargĂ© de prĂ©dire la version dĂ©bruitĂ©e en fonction d’indices supplĂ©mentaires tels qu’une Ă©tiquette de classe ou, dans le cas de Stable Diffusion, une description textuelle de l’image. Au moment de l’infĂ©rence, nous pouvons introduire la description d’une image que nous aimerions voir et un peu de bruit pur comme point de dĂ©part, et le modĂšle fait de son mieux pour « dĂ©bruiter » l’entrĂ©e alĂ©atoire en quelque chose qui corresponde Ă  la lĂ©gende.

    text encoder diagram
    Diagramme montrant le processus d’encodage de texte qui transforme le prompt d’entrĂ©e en un ensemble d’enchĂąssements de texte (les encoder_hidden_states) qui peuvent ensuite ĂȘtre introduits dans l’UNet en tant que condition.

    Pour que cela fonctionne, nous devons crĂ©er une reprĂ©sentation numĂ©rique du texte qui capture des informations pertinentes sur ce qu’il dĂ©crit. Pour ce faire, SD s’appuie sur un transformer prĂ©-entraĂźnĂ© basĂ© sur ce que l’on appelle CLIP. L’encodeur textuel de CLIP a Ă©tĂ© conçu pour traiter les lĂ©gendes d’images sous une forme pouvant ĂȘtre utilisĂ©e pour comparer les images et le texte, il est donc bien adaptĂ© Ă  la tĂąche de crĂ©ation de reprĂ©sentations utiles Ă  partir de descriptions d’images. Un prompt est d’abord tokenizer (sur la base d’un large vocabulaire oĂč chaque mot ou sous-mot se voit attribuer un token spĂ©cifique), puis transmis Ă  l’encodeur textuel de CLIP, qui produit un vecteur Ă  768 dimensions (dans le cas de SD 1.X) ou Ă  1024 dimensions (SD 2.X) pour chaque tokens. Pour que les choses restent cohĂ©rentes, les prompts sont toujours rembourrĂ©s/tronquĂ©s pour avoir une longueur de 77 tokens, de sorte que la reprĂ©sentation finale que nous utilisons comme conditionnement est un tenseur de forme 77x1024 par prompt.

    conditioning diagram

    Alors, comment introduire ces informations de conditionnement dans l’UNet pour qu’il les utilise dans ses prĂ©dictions ? La rĂ©ponse est ce que l’on appelle l’attention croisĂ©e. Des couches d’attention croisĂ©e sont dissĂ©minĂ©es dans l’UNet. Chaque emplacement spatial de l’UNet peut « s’intĂ©resser » Ă  diffĂ©rents tokens dans le conditionnement du texte, en apportant des informations pertinentes provenant du prompt. Le diagramme ci-dessus montre comment ce conditionnement textuel (ainsi que le conditionnement basĂ© sur le temps) est fourni Ă  diffĂ©rents endroits. Comme vous pouvez le constater, Ă  chaque niveau, l’UNet a de nombreuses possibilitĂ©s d’utiliser ce conditionnement !

    Guidage sans classification

    Il s’avĂšre que mĂȘme avec tous les efforts dĂ©ployĂ©s pour rendre le texte de conditionnement aussi utile que possible, le modĂšle a toujours tendance Ă  s’appuyer principalement sur l’image d’entrĂ©e bruyante plutĂŽt que sur le prompt lorsqu’il fait ses prĂ©dictions. D’une certaine maniĂšre, de nombreuses lĂ©gendes ne sont que vaguement liĂ©es aux images qui leur sont associĂ©es et le modĂšle apprend donc Ă  ne pas trop s’appuyer sur les descriptions ! Toutefois, cela n’est pas souhaitable lorsqu’il s’agit de gĂ©nĂ©rer de nouvelles images : si le modĂšle ne suit pas le prompt, nous risquons d’obtenir des images qui ne sont pas du tout liĂ©es Ă  notre description.

    CFG scale demo grid
    Images gĂ©nĂ©rĂ©es Ă  partir du prompt « Une peinture Ă  l’huile d’un colley avec un chapeau haut de forme » avec l’échelle CFG 0, 1, 2 et 10 (de gauche Ă  droite)

    Pour rĂ©soudre ce problĂšme, nous utilisons une astuce appelĂ©e « Classifier-Free Guidance » (CGF). Pendant l’entraĂźnement, le conditionnement du texte est parfois laissĂ© en blanc, ce qui oblige le modĂšle Ă  apprendre Ă  dĂ©bruiter les images sans aucune information textuelle (gĂ©nĂ©ration inconditionnelle). Ensuite, au moment de l’infĂ©rence, nous faisons deux prĂ©dictions distinctes : l’une avec le texte prompt comme conditionnement et l’autre sans. Nous pouvons ensuite utiliser la diffĂ©rence entre ces deux prĂ©dictions pour crĂ©er une prĂ©diction combinĂ©e finale qui pousse encore plus loin dans la direction indiquĂ©e par la prĂ©diction conditionnĂ©e par le texte selon un certain facteur d’échelle (l’échelle de guidage), avec l’espoir d’obtenir une image qui corresponde mieux au prompt. L’image ci-dessus montre les rĂ©sultats d’un prompt Ă  diffĂ©rentes Ă©chelles de guidage. Comme vous pouvez le voir, des valeurs plus Ă©levĂ©es donnent des images qui correspondent mieux Ă  la description.

    Autres types de conditionnement : super-rĂ©solution, peinture et profondeur d’image

    Il est possible de crĂ©er des versions de Stable Diffusion qui prennent en compte d’autres types de conditionnement. Par exemple, le modĂšle Depth-to-Image model possĂšde des canaux d’entrĂ©e supplĂ©mentaires qui recueillent des informations approfondies sur l’image en cours de dĂ©bruitage et, au moment de l’infĂ©rence, nous pouvons introduire la carte de profondeur d’une image cible (estimĂ©e Ă  l’aide d’un modĂšle distinct) pour espĂ©rer gĂ©nĂ©rer une image dont la structure globale est similaire.

    depth to image example
    SD conditionnĂ© par la profondeur est capable de gĂ©nĂ©rer des images diffĂ©rentes avec la mĂȘme structure globale (exemple provenant de StabilityAI)

    De la mĂȘme maniĂšre, nous pouvons introduire une image basse rĂ©solution comme conditionnement et demander au modĂšle de gĂ©nĂ©rer la version haute rĂ©solution (comme utilisĂ© par le Stable Diffusion Upscaler). Enfin, nous pouvons introduire un masque montrant une rĂ©gion de l’image Ă  rĂ©gĂ©nĂ©rer dans le cadre de la tĂąche de complĂ©tion d’image (inpainting), oĂč les rĂ©gions non masquĂ©es doivent rester intactes tandis que le nouveau contenu est gĂ©nĂ©rĂ© pour la zone masquĂ©e.

    Finetuning avec DreamBooth

    dreambooth diagram Image provenant de la page du projet dreambooth basée sur le modÚle Imagen

    DreamBooth est une technique permettant de finetuner un modĂšle texte-image afin de lui « apprendre » un nouveau concept, tel qu’un objet ou un style spĂ©cifique. La technique a Ă©tĂ© dĂ©veloppĂ©e Ă  l’origine pour le modĂšle Imagen de Google, mais a Ă©tĂ© rapidement adaptĂ©e pour fonctionner pour Stable Diffusion. Les rĂ©sultats peuvent ĂȘtre extrĂȘmement impressionnants (si vous avez vu quelqu’un avec une photo de profil IA sur les mĂ©dias sociaux rĂ©cemment, il y a de fortes chances qu’elle provienne d’un service basĂ© sur Dreambooth), mais la technique est aussi sensible aux paramĂštres utilisĂ©s, alors consultez notre notebook et cet article de blog sur les diffĂ©rents paramĂštres d’entraĂźnement pour obtenir des conseils sur la façon de la faire fonctionner le mieux possible.

    Notebooks

    Chapitre Colab Kaggle Gradient Studio Lab
    Introduction Ă  Stable Diffusion Open In Colab Kaggle Gradient Open In SageMaker Studio Lab
    Plongée dans Stable Diffusion Open In Colab Kaggle Gradient Open In SageMaker Studio Lab

    Le notebook Stable Diffusion Introduction est une courte introduction Ă  Stable Diffusion avec la bibliothĂšque đŸ€— Diffusers, prĂ©sentant quelques exemples d’utilisation de base en utilisant des pipelines pour gĂ©nĂ©rer et modifier des images.

    Enfin, le notebook et la vidĂ©o Stable Diffusion Deep Dive dĂ©composent chaque Ă©tape d’un pipeline de gĂ©nĂ©ration typique, en suggĂ©rant de nouvelles façons de modifier chaque Ă©tape pour un contrĂŽle crĂ©atif supplĂ©mentaire.

    Project

    LIEN VERS L’EVENT

    Ressources complémentaires

    Une liste non exhaustive de ressources (en anglais) Ă  consulter :

    • High-Resolution Image Synthesis with Latent Diffusion Models, le papier qui a introduit l’approche derriĂšre Stable Diffusion
    • CLIP apprend Ă  relier le texte aux images et l’encodeur textuel est utilisĂ© pour transformer un prompt textuel en la riche reprĂ©sentation numĂ©rique utilisĂ©e par SD. Voir Ă©galement cet article sur OpenCLIP pour en savoir plus sur les rĂ©centes variantes de CLIP open-source (dont l’une est utilisĂ©e pour la version 2 de SD).
    • GLIDE: Towards Photorealistic Image Generation and Editing with Text-Guided Diffusion Models un papier prĂ©coce dĂ©montrant le conditionnement de texte et le CFG

    Vous avez identifiĂ© d’autres ressources intĂ©ressantes ? Faites-le nous savoir et nous les ajouterons Ă  cette liste.

    3.1. Introduction Ă  Stable Diffusion

    Ce notebook va couvrir les bases de l’utilisation de Stable Diffusion pour crĂ©er et modifier des images en utilisant les pipelines existants. Nous allons Ă©galement jeter un bref coup d’Ɠil aux composants clĂ©s au sein du pipeline, tout en laissant une exploration plus approfondie de ces derniers au notebook de plongĂ©e profonde. Plus prĂ©cisĂ©ment, nous aborderons les points suivants :

    • GĂ©nĂ©rer des images Ă  partir d’un texte en utilisant le StableDiffusionPipeline et en expĂ©rimentant avec les arguments disponibles
    • Voir certains des composants clĂ©s du pipeline en action
      • Le VAE qui en fait un « modĂšle de diffusion latent ».
      • Le tokenizer et l’encodeur qui traitent le prompt textuel
      • L’UNet lui-mĂȘme
      • Le planificateur et l’exploration de diffĂ©rents planificateurs
    • Reproduction de la boucle d’échantillonnage avec les composants du pipeline
    • Édition d’images existantes avec le pipeline Img2Img
    • Utilisation des pipelines de complĂ©tion d’image et Depth2Img

    Configuration

    !pip install -Uq diffusers ftfy accelerate
    
    # Installer transformers Ă  partir de la source car nous avons besoin de la derniĂšre version pour Depth2Img
    !pip install -Uq git+https://github.com/huggingface/transformers 
    
    import torch
    import requests
    from PIL import Image
    from io import BytesIO
    from matplotlib import pyplot as plt
    
    # Nous allons explorer un certain nombre de pipelines aujourd'hui !
    from diffusers import (
        StableDiffusionPipeline, 
        StableDiffusionImg2ImgPipeline,
        StableDiffusionInpaintPipeline, 
        StableDiffusionDepth2ImgPipeline
        )       
    
    # Nous utiliserons quelques images de démonstration plus loin dans le notebook
    def download_image(url):
        response = requests.get(url)
        return Image.open(BytesIO(response.content)).convert("RGB")
    
    # Télécharger des images pour l'exemple de complétion d'image
    img_url = "https://raw.githubusercontent.com/CompVis/latent-diffusion/main/data/inpainting_examples/overture-creations-5sI6fQgYIuo.png"
    mask_url = "https://raw.githubusercontent.com/CompVis/latent-diffusion/main/data/inpainting_examples/overture-creations-5sI6fQgYIuo_mask.png"
    
    init_image = download_image(img_url).resize((512, 512))
    mask_image = download_image(mask_url).resize((512, 512))
    
    # Définir l'appareil
    device = (
        "mps"
        if torch.backends.mps.is_available()
        else "cuda"
        if torch.cuda.is_available()
        else "cpu"
    )
    

    GĂ©nĂ©rer des images Ă  partir d’un texte

    Chargeons un pipeline Stable Diffusion et voyons ce qu’il peut faire. Il existe plusieurs versions de Stable Diffusion, la plus rĂ©cente Ă©tant la version 2.1. Si vous souhaitez explorer une version plus ancienne, remplacez simplement l’ID du modĂšle par le modĂšle appropriĂ© (par exemple, vous pouvez essayer “CompVis/stable-diffusion-v1-4” ou choisir un modĂšle Ă  partir de la bibliothĂšque de concepts dreambooth).

    # Charger le pipeline
    model_id = "stabilityai/stable-diffusion-2-1-base"
    pipe = StableDiffusionPipeline.from_pretrained(model_id).to(device)
    

    Si vous manquez de mĂ©moire GPU, vous pouvez faire certaines choses pour rĂ©duire l’utilisation de la RAM :

    • Chargez la version FP16 (non supportĂ©e par tous les systĂšmes). Avec cette version, vous devrez peut-ĂȘtre convertir les tenseurs en torch.float16 lorsque vous expĂ©rimenterez avec les composants individuels du pipeline :
      pipe = StableDiffusionPipeline.from_pretrained(model_id, revision="fp16", torch_dtype=torch.float16).to(device)
      
    • Activer le dĂ©coupage de l’attention. Cela permet de rĂ©duire l’utilisation de la mĂ©moire du GPU au prix d’une lĂ©gĂšre rĂ©duction de la vitesse :
      pipe.enable_attention_slicing()
      
    • RĂ©duire la taille des images gĂ©nĂ©rĂ©es

    Une fois le pipeline chargĂ©, nous pouvons gĂ©nĂ©rer une image sur la base d’un prompt avec le code suivant :

    # Mise en place d'un générateur pour la reproductibilité
    generator = torch.Generator(device=device).manual_seed(42)
    
    # Exécuter le pipeline, en montrant certains des arguments disponibles
    pipe_output = pipe(
        prompt="Palette knife painting of an autumn cityscape", # Ce qu'il faut générer
        negative_prompt="Oversaturated, blurry, low quality", # Ce qu'il ne faut PAS générer
        height=480, width=640,     # Spécifier la taille de l'image
        guidance_scale=8,          # Comment suivre avec fermeté le prompt
        num_inference_steps=35,    # Nombre d'étapes
        generator=generator        # Graine aléatoire fixe
    )
    
    # Visualiser l'image obtenue :
    pipe_output.images[0]
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    ✏ À votre tour !
    Passez un peu de temps Ă  jouer avec la cellule ci-dessus en utilisant vos propres prompts et en modifiant les paramĂštres pour voir comment ils affectent la sortie. Utilisez une graine alĂ©atoire diffĂ©rente ou supprimez l’argument du gĂ©nĂ©rateur pour obtenir des rĂ©sultats diffĂ©rents Ă  chaque fois. Arguments clĂ©s Ă  modifier :

    • La largeur et la hauteur spĂ©cifient la taille de l’image gĂ©nĂ©rĂ©e. Elles doivent ĂȘtre divisibles par 8 pour que le VAE fonctionne (ce que nous verrons dans une prochaine section).
    • Le nombre de pas influence la qualitĂ© de la gĂ©nĂ©ration. La valeur par dĂ©faut (50) fonctionne bien, mais dans certains cas, on peut se contenter de 20 pas, ce qui est pratique pour l’expĂ©rimentation.
    • Le prompt nĂ©gatif est utilisĂ© pendant le processus d’orientation sans classifieur et peut ĂȘtre un moyen utile d’ajouter un contrĂŽle supplĂ©mentaire. Vous pouvez ne pas l’utiliser, mais de nombreux utilisateurs trouvent utile de lister certaines descriptions indĂ©sirables dans le prompt nĂ©gatif, comme illustrĂ© ci-dessus.
    • L’argument guidance_scale dĂ©termine l’intensitĂ© du guidage sans classifieur (CFG pour classifier-free guidance). Des Ă©chelles plus Ă©levĂ©es poussent les images gĂ©nĂ©rĂ©es Ă  mieux correspondre au prompt, mais si l’échelle est trop Ă©levĂ©e, les rĂ©sultats peuvent devenir sursaturĂ©s et dĂ©sagrĂ©ables.

    Si vous souhaitez vous inspirer d’un prompt, le Stable Diffusion Prompt Book est un bon point de dĂ©part.

    Vous pouvez voir l’effet de l’augmentation de l’échelle d’orientation dans la cellule suivante :

    cfg_scales = [1.1, 8, 12] #@param
    prompt = "A collie with a pink hat" #@param
    fig, axs = plt.subplots(1, len(cfg_scales), figsize=(16, 5))
    for i, ax in enumerate(axs):
      im = pipe(prompt, height=480, width=480,
        guidance_scale=cfg_scales[i], num_inference_steps=35,
        generator=torch.Generator(device=device).manual_seed(42)).images[0]
      ax.imshow(im); ax.set_title(f'CFG Scale {cfg_scales[i]}');
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Modifiez les valeurs ci-dessus pour essayer diffĂ©rentes Ă©chelles et diffĂ©rents prompts. L’interprĂ©tation est bien sĂ»r subjective, mais par expĂ©rience, toute valeur comprise entre 8 et 12 donne de meilleurs rĂ©sultats que les valeurs infĂ©rieures ou supĂ©rieures Ă  cette fourchette.

    Composants du pipeline

    Le StableDiffusionPipeline que nous utilisons est un peu plus complexe que le DDPMPipeline que nous avons explorĂ© dans les unitĂ©s prĂ©cĂ©dentes. En plus du UNet et du planificateur, il y a un certain nombre d’autres composants inclus dans le pipeline :

    print(list(pipe.components.keys())) # Liste des composants
    
    ['vae', 'text_encoder', 'tokenizer', 'unet', 'scheduler', 'safety_checker', 'feature_extractor']
    

    Pour mieux comprendre le fonctionnement du pipeline, voyons briÚvement chaque composant en action individuellement, puis assemblons-les pour reproduire la fonctionnalité du pipeline.

    Le VAE

    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Le VAE (auto-encodeur variationnel) est un type de modĂšle capable d’encoder son entrĂ©e dans une reprĂ©sentation comprimĂ©e, puis de dĂ©coder cette reprĂ©sentation latente pour la rendre proche de l’entrĂ©e d’origine. Lors de la gĂ©nĂ©ration d’images avec diffusion stable, nous gĂ©nĂ©rons d’abord les latents en appliquant le processus de diffusion dans l’espace latent du VAE, puis nous les dĂ©codons Ă  la fin pour visualiser l’image rĂ©sultante.

    Voici un code qui prend une image d’entrĂ©e, l’encode dans une reprĂ©sentation latente et la dĂ©code Ă  nouveau Ă  l’aide de la VAE :

    # Créez de fausses données (une image aléatoire, une plage (-1, 1))
    images = torch.rand(1, 3, 512, 512).to(device) * 2 - 1 
    print("Input images shape:", images.shape)
    
    # Encoder dans l'espace latent
    with torch.no_grad():
      latents = 0.18215 * pipe.vae.encode(images).latent_dist.mean
    print("Encoded latents shape:", latents.shape)
    
    # Décoder à nouveau
    with torch.no_grad():
      decoded_images = pipe.vae.decode(latents / 0.18215).sample
    print("Decoded images shape:", decoded_images.shape)
    
    Input images shape: torch.Size([1, 3, 512, 512])
    Encoded latents shape: torch.Size([1, 4, 64, 64])
    Decoded images shape: torch.Size([1, 3, 512, 512])
    

    Comme vous pouvez le constater, l’image 512x512 est compressĂ©e en une reprĂ©sentation latente 64x64 (avec quatre canaux). Cette rĂ©duction par 8 de chaque dimension spatiale est la raison pour laquelle la largeur et la hauteur spĂ©cifiĂ©es doivent ĂȘtre des multiples de 8.

    Travailler avec ces latents 4x64x64 riches en informations est plus efficace que de travailler avec des images massives de 512 px, ce qui permet d’obtenir des modĂšles de diffusion plus rapides dont l’entraĂźnement et l’utilisation nĂ©cessitent moins de ressources. Le processus de dĂ©codage du VAE n’est pas parfait, mais il est suffisamment bon pour que le petit compromis de qualitĂ© en vaille gĂ©nĂ©ralement la peine.

    NB : L’exemple de code ci-dessus inclut un facteur d’échelle de 0,18215 nĂ©cessaire pour correspondre au traitement utilisĂ© lors de l’entraĂźnement de SD.

    Le tokenizer et l’encodeur

    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    L’objectif de l’encodeur est de transformer une chaĂźne d’entrĂ©e (le prompt) en une reprĂ©sentation numĂ©rique qui peut ĂȘtre transmise Ă  l’UNet en tant que conditionnement. Le texte est d’abord transformĂ© en une sĂ©rie de tokens Ă  l’aide du tokenizer du pipeline. L’encodeur dispose d’un vocabulaire d’environ 50K tokens et tout mot ne figurant pas dans ce vocabulaire est divisĂ© en sous-mots plus petits. Les tokens sont ensuite transmis Ă  l’encodeur lui-mĂȘme : un transformer qui a Ă©tĂ© entraĂźnĂ© Ă  l’origine comme encodeur pour CLIP. Nous espĂ©rons que ce transformer prĂ©-entraĂźnĂ© a appris des reprĂ©sentations riches du texte qui seront Ă©galement utiles pour la tĂąche de diffusion.

    Testons ce processus en encodant un prompt d’exemple, d’abord en le tokenizant manuellement et en le faisant passer par l’encodeur puis en utilisant la mĂ©thode _encode_prompt pour montrer le processus complet, y compris le rembourrage/la troncature de la longueur jusqu’à la longueur maximale de 77 tokens :

    # Tokenizer et encoder un prompt d'exemple manuellement :
    
    # Tokenizer
    input_ids = pipe.tokenizer(["A painting of a flooble"])['input_ids']
    print("Input ID -> decoded token")
    for input_id in input_ids[0]:
      print(f"{input_id} -> {pipe.tokenizer.decode(input_id)}")
    
    # Passage par l'encodeur de texte CLIP
    input_ids = torch.tensor(input_ids).to(device)
    with torch.no_grad():
      text_embeddings = pipe.text_encoder(input_ids)['last_hidden_state']
    print("Text embeddings shape:", text_embeddings.shape)
    
    Input ID -> decoded token
    49406 -> <|startoftext|>
    320 -> a
    3086 -> painting
    539 -> of
    320 -> a
    4062 -> floo
    1059 -> ble
    49407 -> <|endoftext|>
    Text embeddings shape: torch.Size([1, 8, 1024])
    
    # Obtenir les enchĂąssements finaux Ă  l'aide de la fonction _encode_prompt du pipeline :
    text_embeddings = pipe._encode_prompt("A painting of a flooble", device, 1, False, '')
    text_embeddings.shape
    
    torch.Size([1, 77, 1024])
    

    Ces enchĂąssements (les â€œĂ©tats cachĂ©s” du dernier bloc de transformation dans le modĂšle de l’encodeur) seront transmis Ă  l’UNet en tant qu’argument supplĂ©mentaire de la mĂ©thode forward, que nous verrons dans la section suivante.

    L’UNet

    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    L’UNet prend une entrĂ©e bruitĂ©e et prĂ©dit le bruit, tout comme les UNets que nous avons vus dans les unitĂ©s prĂ©cĂ©dentes. Contrairement aux exemples prĂ©cĂ©dents, l’entrĂ©e n’est pas une image mais une reprĂ©sentation latente d’une image. En plus du conditionnement du pas de temps, ce UNet prend Ă©galement en compte l’enchĂąssement du prompt en tant qu’entrĂ©e supplĂ©mentaire. Ici, il fait des prĂ©dictions sur des donnĂ©es fictives :

    # Entrées fictives
    timestep = pipe.scheduler.timesteps[0]
    latents = torch.randn(1, 4, 64, 64).to(device)
    text_embeddings = torch.randn(1, 77, 1024).to(device)
    
    # Prédiction du modÚle
    with torch.no_grad():
      unet_output = pipe.unet(latents, timestep, text_embeddings).sample
    print('UNet output shape:', unet_output.shape) # MĂȘme forme que les latents d'entrĂ©e
    
    UNet output shape: torch.Size([1, 4, 64, 64])
    

    Le planificateur

    Le planificateur stocke le plan de bruit et gĂšre la mise Ă  jour de l’échantillon bruitĂ© sur la base des prĂ©dictions du modĂšle. Le planificateur par dĂ©faut est un PNDMScheduler, mais vous pouvez en utiliser d’autres (comme LMSDiscreteScheduler) tant qu’ils sont initialisĂ©s avec la mĂȘme configuration.

    Nous pouvons tracer le plan de bruit pour voir le niveau de bruit (basé sur $bar{\alpha}$) au fil du temps :

    plt.plot(pipe.scheduler.alphas_cumprod, label=r'$\bar{\alpha}$')
    plt.xlabel('Timestep (high noise to low noise ->)')
    plt.title('Noise schedule');plt.legend()
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Si vous souhaitez essayer un autre planificateur, vous pouvez le remplacer comme suit :

    from diffusers import LMSDiscreteScheduler
    
    # Remplacer le planificateur
    pipe.scheduler = LMSDiscreteScheduler.from_config(pipe.scheduler.config)
    
    # Afficher la configuration
    print('Scheduler config:', pipe.scheduler)
    
    # Générer une image avec ce nouveau planificateur
    pipe(prompt="Palette knife painting of an winter cityscape", height=480, width=480,
         generator=torch.Generator(device=device).manual_seed(42)).images[0]
    
    Scheduler config: LMSDiscreteScheduler {
      "_class_name": "LMSDiscreteScheduler",
      "_diffusers_version": "0.11.1",
      "beta_end": 0.012,
      "beta_schedule": "scaled_linear",
      "beta_start": 0.00085,
      "clip_sample": false,
      "num_train_timesteps": 1000,
      "prediction_type": "epsilon",
      "set_alpha_to_one": false,
      "skip_prk_steps": true,
      "steps_offset": 1,
      "trained_betas": null
    }
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Vous pouvez lire plus de dĂ©tails sur l’utilisation de diffĂ©rents planificateurs ici.

    Une boucle d’échantillonnage par vous-mĂȘme

    Maintenant que nous avons vu tous ces composants en action, nous pouvons les assembler pour reproduire la fonctionnalité du pipeline :

    guidance_scale = 8 #@param
    num_inference_steps=30 #@param
    prompt = "Beautiful picture of a wave breaking" #@param
    negative_prompt = "zoomed in, blurry, oversaturated, warped" #@param
    
    # Encoder le prompt
    text_embeddings = pipe._encode_prompt(prompt, device, 1, True, negative_prompt)
    
    # Créer notre point de départ aléatoire
    latents = torch.randn((1, 4, 64, 64), device=device, generator=generator)
    latents *= pipe.scheduler.init_noise_sigma
    
    # Preparer le planificateur
    pipe.scheduler.set_timesteps(num_inference_steps, device=device)
    
    # Boucle sur les pas de temps d'échantillonnage
    for i, t in enumerate(pipe.scheduler.timesteps):
    
      # développer les latents si l'on procÚde à un guidage sans classifieur
      latent_model_input = torch.cat([latents] * 2)
    
      # Appliquer tout échelonnement requis par le planificateur
      latent_model_input = pipe.scheduler.scale_model_input(latent_model_input, t)
    
      # prédire le bruit résiduel avec l'UNet
      with torch.no_grad():
        noise_pred = pipe.unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample
    
      # réaliser un guidage
      noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
      noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
    
      # calculer l'échantillon bruité précédent x_t -> x_t-1
      latents = pipe.scheduler.step(noise_pred, t, latents).prev_sample
    
    # Décoder les latents résultants en une image
    with torch.no_grad():
      image = pipe.decode_latents(latents.detach())
    
    # Visualiser
    pipe.numpy_to_pil(image)[0]
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Dans la plupart des cas, il sera plus facile d’utiliser les pipelines existants, mais le fait de disposer de cette boucle d’échantillonnage bidouillable peut s’avĂ©rer utile pour comprendre et modifier le fonctionnement de chaque composant. Si vous souhaitez voir ce code et tous les diffĂ©rents composants explorĂ©s et modifiĂ©s en profondeur, consultez le notebook et la video ‘Stable Diffusion Deep Dive’ pour une exploration plus approfondie.

    Pipelines supplémentaires

    Que pouvons-nous faire d’autre que de gĂ©nĂ©rer des images Ă  partir d’un prompt ? Beaucoup de choses ! Dans cette section, nous allons dĂ©montrer quelques pipelines sympas pour vous donner un avant-goĂ»t des autres tĂąches pour lesquelles Stable Diffusion peut ĂȘtre utilisĂ©. Plusieurs d’entre eux nĂ©cessitent le tĂ©lĂ©chargement de nouveaux modĂšles, donc si vous ĂȘtes pressĂ©, vous pouvez parcourir cette section en vous contentant de regarder les rĂ©sultats existants plutĂŽt que de tĂ©lĂ©charger et d’exĂ©cuter tous les modĂšles vous-mĂȘme.

    Img2Img

    Dans les exemples prĂ©sentĂ©s jusqu’à prĂ©sent, nous avons gĂ©nĂ©rĂ© des images en partant de latents alĂ©atoires et en appliquant la boucle complĂšte d’échantillonnage par diffusion. Mais il n’est pas nĂ©cessaire de partir de zĂ©ro. Le pipeline Img2Img encode d’abord une image existante dans un ensemble de latents, puis ajoute du bruit aux latents et utilise cette image comme point de dĂ©part. La quantitĂ© de bruit ajoutĂ©e et le nombre d’étapes de dĂ©bruitage appliquĂ©es dĂ©terminent la « force » du processus Img2Img. L’ajout d’une petite quantitĂ© de bruit (force faible) n’entraĂźnera que trĂšs peu de changements, tandis que l’ajout d’une quantitĂ© maximale de bruit et l’exĂ©cution du processus de dĂ©bruitage complet donneront une image qui ne ressemblera guĂšre Ă  l’image d’entrĂ©e, hormis quelques similitudes au niveau de la structure gĂ©nĂ©rale.

    Ce pipeline ne nĂ©cessite pas de modĂšles particuliers, et tant que l’ID du modĂšle est le mĂȘme que celui de notre exemple texte-image ci-dessus, aucun nouveau fichier ne devra ĂȘtre tĂ©lĂ©chargĂ©.

    # Chargement d'un pipeline Img2Img
    model_id = "stabilityai/stable-diffusion-2-1-base"
    img2img_pipe = StableDiffusionImg2ImgPipeline.from_pretrained(model_id).to(device)
    

    Dans la section Configuration nous avons chargé un exemple init_image à utiliser pour cette démo, mais vous pouvez le remplacer par votre propre image si vous préférez. Voici le pipeline en action :

    # Appliquer Img2Img
    result_image = img2img_pipe(
        prompt="An oil painting of a man on a bench",
        image = init_image, # L'image de départ
        strength = 0.6, # 0 pour aucun changement, 1.0 pour une force maximale
    ).images[0]
    
    # Voir le résultat
    fig, axs = plt.subplots(1, 2, figsize=(12, 5))
    axs[0].imshow(init_image);axs[0].set_title('Input Image')
    axs[1].imshow(result_image);axs[1].set_title('Result')
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    ✏ À votre tour !
    ExpĂ©rimentez avec ce pipeline. Essayez vos propres images, ou jouez avec diffĂ©rentes forces et diffĂ©rents prompts. Vous pouvez utiliser les mĂȘmes arguments que pour le pipeline texte-image, alors n’hĂ©sitez pas Ă  essayer diffĂ©rentes tailles, diffĂ©rents nombres d’étapes, etc.

    ComplĂ©tion d’image (inpainting)

    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Que se passerait-il si nous voulions conserver une partie de l’image d’entrĂ©e inchangĂ©e mais gĂ©nĂ©rer quelque chose de nouveau dans d’autres parties ? C’est ce qu’on appelle la complĂ©tion d’image (inpainting). Bien qu’il soit possible de le faire avec le mĂȘme modĂšle que les dĂ©monstrations prĂ©cĂ©dentes (via StableDiffusionInpaintPipelineLegacy), nous pouvons obtenir de meilleurs rĂ©sultats en utilisant une version finetunĂ©e personnalisĂ©e de Stable Diffusion qui prend un masque comme condition supplĂ©mentaire. L’image du masque doit avoir la mĂȘme forme que l’image d’entrĂ©e, avec du blanc dans les zones Ă  remplacer et du noir dans les zones Ă  garder inchangĂ©es. Voici comment charger un tel pipeline et l’appliquer Ă  l’image d’exemple et au masque chargĂ©s dans la section Configuration :

    # Charger le pipeline de complétion d'image (nécessite un modÚle de complétion d'image approprié)
    pipe = StableDiffusionInpaintPipeline.from_pretrained("runwayml/stable-diffusion-inpainting")
    pipe = pipe.to(device)
    
    # Complétion d'image avec un prompt pour avoir le résultat souhaité
    prompt = "A small robot, high resolution, sitting on a park bench"
    image = pipe(prompt=prompt, image=init_image, mask_image=mask_image).images[0]
    
    # Voir le résultat
    fig, axs = plt.subplots(1, 3, figsize=(16, 5))
    axs[0].imshow(init_image);axs[0].set_title('Input Image')
    axs[1].imshow(mask_image);axs[1].set_title('Mask')
    axs[2].imshow(image);axs[2].set_title('Result')
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Ce modĂšle peut ĂȘtre particuliĂšrement puissant lorsqu’il est combinĂ© Ă  un autre modĂšle pour gĂ©nĂ©rer automatiquement des masques. Par exemple, ce Space utilise un modĂšle appelĂ© CLIPSeg pour masquer un objet Ă  remplacer sur la base d’une description textuelle.

    En marge : gestion du cache de votre modĂšle

    L’exploration de diffĂ©rents pipelines et variantes de modĂšles peut remplir votre espace disque. Vous pouvez voir quels modĂšles sont actuellement tĂ©lĂ©chargĂ©s avec :

    !ls ~/.cache/huggingface/diffusers/ # Liste du contenu du répertoire cache
    

    Consultez la documentation sur la mise en cache pour savoir comment visualiser et gérer efficacement votre cache.

    Depth2Image

    depth to image examples Image d’entrĂ©e, image de profondeur et exemples gĂ©nĂ©rĂ©s (source de l’image : StabilityAI)

    Img2Img est trĂšs bien, mais parfois nous voulons crĂ©er une nouvelle image avec la composition de l’original mais avec des couleurs ou des textures complĂštement diffĂ©rentes. Il peut ĂȘtre difficile de trouver une force d’Img2Img qui prĂ©serve ce que nous voulons de la mise en page sans conserver les couleurs d’entrĂ©e.

    Il est temps d’adopter un autre modĂšle finetunĂ© ! Celui-ci prend en compte les informations de profondeur comme condition supplĂ©mentaire lors de la gĂ©nĂ©ration. Le pipeline utilise un modĂšle d’estimation de la profondeur pour crĂ©er une carte de profondeur, qui est ensuite transmise au UNet finetunĂ© lors de la gĂ©nĂ©ration d’images afin de prĂ©server (si possible) la profondeur et la structure de l’image initiale tout en remplissant un contenu complĂštement nouveau.

    # Charger le pipeline Depth2Img (nécessite un modÚle approprié)
    pipe = StableDiffusionDepth2ImgPipeline.from_pretrained("stabilityai/stable-diffusion-2-depth")
    pipe = pipe.to(device)
    
    # Complétion d'image avec un prompt pour avoir le résultat souhaité
    prompt = "An oil painting of a man on a bench"
    image = pipe(prompt=prompt, image=init_image).images[0]
    
    # Voir le résultat
    fig, axs = plt.subplots(1, 2, figsize=(16, 5))
    axs[0].imshow(init_image);axs[0].set_title('Input Image')
    axs[1].imshow(image);axs[1].set_title('Result');
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Notez la comparaison avec l’exemple img2img. Ici, la variation de couleur est beaucoup plus importante mais la structure globale reste fidĂšle Ă  l’original. Ce n’est pas idĂ©al dans ce cas, car l’homme a Ă©tĂ© dotĂ© d’une anatomie extrĂȘmement bizarre pour correspondre Ă  la forme du chien, mais dans certains cas, c’est extraordinairement utile. Pour un exemple de cette approche, regardez ce tweet montrant le modĂšle de profondeur utilisĂ© pour texturer une scĂšne en 3D !

    Et maintenant ?

    Nous espĂ©rons vous avoir donnĂ© un avant-goĂ»t des nombreuses possibilitĂ©s offertes par Stable Diffusion ! Une fois que vous en aurez assez de jouer avec les exemples de ce notebook, allez voir le notebook du hackathon DreamBooth pour voir comment finetuner votre propre version de Stable Diffusion qui peut ĂȘtre utilisĂ©e avec les pipelines texte-image ou img2img que nous avons vus ici.

    Si vous ĂȘtes curieux d’approfondir le fonctionnement des diffĂ©rents composants, consultez le notebook Stable Diffusion Deep Dive qui va beaucoup plus loin dans les dĂ©tails et montre quelques astuces supplĂ©mentaires que nous pouvons faire.

    N’oubliez pas de partager vos crĂ©ations avec nous et la communautĂ© !

    3.2. Stable Diffusion : plongée en profondeur

    Stable Diffusion est un puissant modĂšle de texte Ă  image. Il existe plusieurs sites web et outils pour rendre son utilisation aussi simple que possible. Il est Ă©galement intĂ©grĂ© Ă  la bibliothĂšque de Diffusers d’Huggingface, ce qui permet de gĂ©nĂ©rer des images en toute simplicitĂ© :

    from diffusers import StableDiffusionPipeline
    pipe = StableDiffusionPipeline.from_pretrained("CompVis/stable-diffusion-v1-4", revision="fp16", torch_dtype=torch.float16, use_auth_token=True).to("cuda")
    image = pipe("An astronaught scuba diving").images[0]
    

    Dans ce notebook, nous allons nous plonger dans le code qui se cache derriĂšre ces interfaces faciles Ă  utiliser, pour voir ce qui se passe sous le capot. Nous commencerons par recrĂ©er la fonctionnalitĂ© ci-dessus sous la forme d’un morceau de code effrayant, puis, un par un, nous inspecterons les diffĂ©rents composants et comprendrons ce qu’ils font. À la fin de ce notebook, cette mĂȘme boucle d’échantillonnage devrait ressembler Ă  quelque chose que vous pouvez peaufiner et modifier Ă  votre guise.

    Configuration et importations

    Vous devrez vous connecter Ă  Hugging Face et accepter les termes de la licence pour ce modĂšle (voir la carte de modĂšle pour plus de dĂ©tails). Lorsque vous exĂ©cuterez ce notebook pour la premiĂšre fois, vous devrez dĂ©commenter les deux cellules suivantes pour installer les prĂ©requis et vous connecter au Hub avec un token d’accĂšs.

    # !pip install -q --upgrade transformers diffusers ftfy
    
    from base64 import b64encode
    
    import numpy
    import torch
    from diffusers import AutoencoderKL, LMSDiscreteScheduler, UNet2DConditionModel
    from huggingface_hub import notebook_login
    
    # Pour l'affichage vidéo
    from IPython.display import HTML
    from matplotlib import pyplot as plt
    from pathlib import Path
    from PIL import Image
    from torch import autocast
    from torchvision import transforms as tfms
    from tqdm.auto import tqdm
    from transformers import CLIPTextModel, CLIPTokenizer, logging
    
    torch.manual_seed(1)
    if not (Path.home()/'.huggingface'/'token').exists(): notebook_login()
    
    # Suppression de certains avertissements inutiles lors du chargement de CLIPTextModel
    logging.set_verbosity_error()
    
    # Définir l'appareil
    torch_device = "cuda" if torch.cuda.is_available() else "cpu"
    

    Chargement des modĂšles

    Ce code (et celui de la section suivante) provient du notebook illustratif d’Huggingface.

    Il tĂ©lĂ©charge et configure les modĂšles et les composants que nous utiliserons. ExĂ©cutons-le pour l’instant et passons Ă  la section suivante pour vĂ©rifier que tout fonctionne avant d’aller plus loin.

    Si vous avez chargé un pipeline, vous pouvez aussi accéder à ces composants en utilisant pipe.unet, pipe.vae et ainsi de suite.

    Dans ce notebook, nous ne faisons pas d’économies de mĂ©moire. Si vous vous retrouvez Ă  court de RAM GPU, regardez le code du pipeline pour vous inspirer avec des choses comme le dĂ©coupage de l’attention, le passage Ă  la demi-prĂ©cision (fp16), le maintien du VAE sur le CPU et d’autres modifications.

    # Charger le modÚle auto-encodeur qui sera utilisé pour décoder les latents dans l'espace de l'image
    vae = AutoencoderKL.from_pretrained("CompVis/stable-diffusion-v1-4", subfolder="vae")
    
    # Charger le tokenizer et l'encodeur
    tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-large-patch14")
    text_encoder = CLIPTextModel.from_pretrained("openai/clip-vit-large-patch14")
    
    # Le modÚle UNet pour générer les latents
    unet = UNet2DConditionModel.from_pretrained("CompVis/stable-diffusion-v1-4", subfolder="unet")
    
    # Le planificateur de bruit
    scheduler = LMSDiscreteScheduler(beta_start=0.00085, beta_end=0.012, beta_schedule="scaled_linear", num_train_timesteps=1000)
    
    # Nous allons au GPU !
    vae = vae.to(torch_device)
    text_encoder = text_encoder.to(torch_device)
    unet = unet.to(torch_device)
    

    Une boucle de diffusion

    Si tout ce que vous voulez, c’est crĂ©er une image avec du texte, vous pouvez ignorer ce notebook et utiliser l’un des outils existants (comme DreamStudio) ou utiliser le pipeline simplifiĂ© d’Hugging Face comme documentĂ© ici.

    Ce que nous voulons faire ici, c’est approfondir un peu plus la façon dont cela fonctionne. Nous allons donc commencer par vĂ©rifier que le code de l’exemple s’exĂ©cute. Il ressemble beaucoup Ă  ce que vous trouverez si vous inspectez la mĂ©thode call() du pipeline de Stable Diffusion.

    # Quelques paramĂštres
    prompt = ["A watercolor painting of an otter"]
    height = 512                        # hauteur par défaut de Stable Diffusion
    width = 512                         # largeur par défaut de Stable Diffusion
    num_inference_steps = 30            # Nombre d'étapes de débruitage
    guidance_scale = 7.5                # Échelle pour un guidage sans classifieur
    generator = torch.manual_seed(32)   # Générateur de la graine pour créer le bruit latent initial
    batch_size = 1
    
    # Preparation du texte
    text_input = tokenizer(prompt, padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt")
    with torch.no_grad():
        text_embeddings = text_encoder(text_input.input_ids.to(torch_device))[0]
    max_length = text_input.input_ids.shape[-1]
    uncond_input = tokenizer(
        [""] * batch_size, padding="max_length", max_length=max_length, return_tensors="pt"
    )
    with torch.no_grad():
        uncond_embeddings = text_encoder(uncond_input.input_ids.to(torch_device))[0] 
    text_embeddings = torch.cat([uncond_embeddings, text_embeddings])
    
    # Preparation du planificateur
    scheduler.set_timesteps(num_inference_steps)
    
    # Preparation des latents
    latents = torch.randn(
      (batch_size, unet.in_channels, height // 8, width // 8),
      generator=generator,
    )
    latents = latents.to(torch_device)
    latents = latents * scheduler.init_noise_sigma # Mise à l'échelle (versions précédentes) latents = latents * self.scheduler.sigmas[0]
    
    # Boucle
    with autocast("cuda"):
        for i, t in tqdm(enumerate(scheduler.timesteps)):
            # étendre les latents si nous procédons à un guidage sans classifieur afin d'éviter de faire deux passages en avant        
    		latent_model_input = torch.cat([latents] * 2)
            sigma = scheduler.sigmas[i]
            # mettre à l'échelle les latents (préconditionnement)
            # latent_model_input = latent_model_input / ((sigma**2 + 1) ** 0.5) # Diffusers 0.3 et moins
            latent_model_input = scheduler.scale_model_input(latent_model_input, t)
    
            # prédire le bruit résiduel
            with torch.no_grad():
                noise_pred = unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample
    
            # effectuer le guidage
            noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
            noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
    
            # calculer l'échantillon bruité précédent x_t -> x_t-1
            # latents = scheduler.step(noise_pred, i, latents)["prev_sample"] # Diffusers 0.3 et moins
            latents = scheduler.step(noise_pred, t, latents).prev_sample
    
    # mettre à l'échelle et décoder les latents de l'image à l'aide du vae
    latents = 1 / 0.18215 * latents
    with torch.no_grad():
        image = vae.decode(latents).sample
    
    # Affichage
    image = (image / 2 + 0.5).clamp(0, 1)
    image = image.detach().cpu().permute(0, 2, 3, 1).numpy()
    images = (image * 255).round().astype("uint8")
    pil_images = [Image.fromarray(image) for image in images]
    pil_images[0]
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Cela fonctionne, mais cela fait beaucoup de code ! Examinons les composants un par un.

    L’auto-encodeur (AE)

    L’AE peut encoder une image dans une sorte de reprĂ©sentation latente, et la dĂ©coder Ă  nouveau en une image. Nous avons regroupĂ© le code dans quelques fonctions pour que nous puissions voir Ă  quoi cela ressemble en action :

    def pil_to_latent(input_im):
        # Une seule image -> un seul latent dans un batch (donc taille 1, 4, 64, 64)
        with torch.no_grad():
            latent = vae.encode(tfms.ToTensor()(input_im).unsqueeze(0).to(torch_device)*2-1) # Note scaling
        return 0.18215 * latent.latent_dist.sample()
    
    def latents_to_pil(latents):
        # bain de latents -> liste d'images
        latents = (1 / 0.18215) * latents
        with torch.no_grad():
            image = vae.decode(latents).sample
        image = (image / 2 + 0.5).clamp(0, 1)
        image = image.detach().cpu().permute(0, 2, 3, 1).numpy()
        images = (image * 255).round().astype("uint8")
        pil_images = [Image.fromarray(image) for image in images]
        return pil_images
    

    Nous utiliserons ici une image provenant du web, mais vous pouvez charger la vÎtre en la téléchargeant et en modifiant le nom du fichier dans la cellule suivante.

    # Télécharger une image de démonstration
    !curl --output macaw.jpg 'https://lafeber.com/pet-birds/wp-content/uploads/2018/06/Scarlet-Macaw-2.jpg'
    
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100 62145  100 62145    0     0  10874      0  0:00:05  0:00:05 --:--:-- 15633
    
    # Charger l'image avec PIL
    input_image = Image.open('macaw.jpg').resize((512, 512))
    input_image
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    L’encodage dans l’espace latent de l’AE Ă  l’aide de la fonction dĂ©finie ci-dessus se prĂ©sente comme suit :

    # Encoder dans l'espace latent
    encoded = pil_to_latent(input_image)
    encoded.shape
    
    torch.Size([1, 4, 64, 64])
    
    # Visualisons les quatre canaux de cette représentation latente :
    fig, axs = plt.subplots(1, 4, figsize=(16, 4))
    for c in range(4):
        axs[c].imshow(encoded[0][c].cpu(), cmap='Greys')
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Ce tenseur 4x64x64 capture de nombreuses informations sur l’image, suffisamment, espĂ©rons-le, pour que lorsque nous l’introduisons dans le dĂ©codeur, nous obtenions en retour quelque chose de trĂšs proche de notre image d’entrĂ©e :

    # Décoder cette représentation latente en une image
    decoded = latents_to_pil(encoded)[0]
    decoded
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Vous verrez de petites diffĂ©rences si vous plissez les yeux ! Concentrez-vous sur l’Ɠil si vous ne voyez rien d’évident. C’est assez impressionnant : cette image latente de 4x64x64 semble contenir beaucoup plus d’informations qu’une image de 64px.

    Cet auto-encodeur a Ă©tĂ© entraĂźnĂ© Ă  rĂ©duire une image Ă  une reprĂ©sentation plus petite, puis Ă  recrĂ©er l’image Ă  partir de cette version compressĂ©e.

    Dans ce cas particulier, le facteur de compression est de 48, nous partons d’une image 3x512x512(cannaux x hauteur x largeur) et elle est compressĂ©e en un vecteur latent 4x64x64. Chaque volume de 3x8x8 pixels dans l’image d’entrĂ©e est compressĂ© en seulement 4 nombres (4x1x1). Il est possible de trouver des AEs avec un taux de compression plus Ă©levĂ© (par exemple f16 comme certains modĂšles populaires de VQGAN) mais Ă  un moment donnĂ©, ils commencent Ă  introduire des artefacts que nous ne voulons pas.

    Pourquoi utiliser un auto-encodeur ? Nous pouvons faire de la diffusion dans l’espace des pixels oĂč le modĂšle reçoit toutes les donnĂ©es de l’image comme entrĂ©es et produit une prĂ©diction de sortie de la mĂȘme forme. Mais cela implique le traitement d’un grand nombre de donnĂ©es et rend la gĂ©nĂ©ration d’images Ă  haute rĂ©solution trĂšs coĂ»teuse sur le plan informatique. Certaines solutions consistent Ă  effectuer la diffusion Ă  basse rĂ©solution (64 px par exemple), puis Ă  entraĂźner un modĂšle distinct pour augmenter l’échelle de maniĂšre rĂ©pĂ©tĂ©e (comme avec D2/Imagen). La diffusion latente, quant Ă  elle, effectue le processus de diffusion dans cet espace latent, en utilisant les reprĂ©sentations compressĂ©es de notre AE plutĂŽt que des images brutes. Ces reprĂ©sentations sont riches en informations et peuvent ĂȘtre suffisamment petites pour ĂȘtre gĂ©rĂ©es par du matĂ©riel grand public. Une fois que nous avons gĂ©nĂ©rĂ© une nouvelle image en tant que reprĂ©sentation latente, l’auto-encodeur peut prendre ces sorties latentes finales et les transformer en pixels rĂ©els.

    Le planificateur

    Nous devons maintenant parler de l’ajout de bruit.

    Pendant l’entraĂźnement, nous ajoutons du bruit Ă  une image, puis nous demandons au modĂšle d’essayer de prĂ©dire le bruit. Si nous ajoutons toujours beaucoup de bruit, le modĂšle risque de ne pas avoir grand-chose Ă  faire. Si nous n’en ajoutons qu’une infime quantitĂ©, le modĂšle ne pourra pas faire grand-chose avec les points de dĂ©part alĂ©atoires que nous utilisons pour l’échantillonnage. Au cours de l’entraĂźnement, la quantitĂ© de bruit varie donc en fonction d’une certaine distribution.

    Pendant l’échantillonnage, nous voulons « dĂ©bruiter » sur un certain nombre d’étapes. Le nombre d’étapes et la quantitĂ© de bruit que nous devons viser Ă  chaque Ă©tape affecteront le rĂ©sultat final.

    Le planificateur est chargé de gérer tous ces détails. Par exemple : scheduler = LMSDiscreteScheduler(beta_start=0.00085, beta_end=0.012, beta_schedule="scaled_linear", num_train_timesteps=1000) met en place un scheduler qui correspond à celui utilisé pour entraßner ce modÚle. Lorsque nous voulons échantillonner sur un plus petit nombre de pas, nous le faisons avec scheduler.set_timesteps :

    # Réglage du nombre de pas d'échantillonnage :
    scheduler.set_timesteps(15)
    

    Vous pouvez voir comment notre nouvel ensemble d’étapes correspond Ă  celles utilisĂ©es dans l’entraĂźnement :

    # Voyez ça en termes de 1000 étapes originales utilisées pour l'entraßnement :
    print(scheduler.timesteps)
    
    tensor([999.0000, 927.6429, 856.2857, 784.9286, 713.5714, 642.2143, 570.8571,
            499.5000, 428.1429, 356.7857, 285.4286, 214.0714, 142.7143,  71.3571,
              0.0000], dtype=torch.float64)
    

    Et quelle est la quantité de bruit présente à chaque endroit :

    # Examinez les niveaux de bruit équivalents :
    print(scheduler.sigmas)
    
    tensor([14.6146,  9.6826,  6.6780,  4.7746,  3.5221,  2.6666,  2.0606,  1.6156,
             1.2768,  1.0097,  0.7913,  0.6056,  0.4397,  0.2780,  0.0292,  0.0000])
    

    Pendant l’échantillonnage, nous partons d’un niveau de bruit Ă©levĂ© (en fait, notre entrĂ©e sera du bruit pur) et nous « dĂ©bruitons » progressivement jusqu’à obtenir une image, selon ce calendrier.

    # Affichage du planificateur de bruit :
    plt.plot(scheduler.sigmas)
    plt.title('Noise Schedule')
    plt.xlabel('Sampling step')
    plt.ylabel('sigma')
    plt.show()
    

    Ce « sigma » est la quantité de bruit ajoutée à la représentation latente. Voyons ce que cela donne en ajoutant un peu de bruit à notre image codée, puis en décodant cette version bruitée :

    noise = torch.randn_like(encoded) # Bruit aléatoire
    sampling_step = 10 # Equivalent à une étape 10 sur 15 dans la grille ci-dessus
    # encoded_and_noised = scheduler.add_noise(encoded, noise, timestep) # Diffusers 0.3 et en dessous
    encoded_and_noised = scheduler.add_noise(encoded, noise, timesteps=torch.tensor([scheduler.timesteps[sampling_step]]))
    latents_to_pil(encoded_and_noised.float())[0] # Affichage
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    À quoi cela ressemble-t-il Ă  diffĂ©rents pas de temps ? Faites l’expĂ©rience et voyez par vous-mĂȘme !

    Si vous dĂ©commentez la cellule ci-dessous, vous verrez que dans ce cas, la fonction scheduler.add_noise ne fait qu’ajouter du bruit Ă  l’échelle sigma : noisy_samples = original_samples + noise * sigmas

    # ??scheduler.add_noise
    

    D’autres modĂšles de diffusion peuvent ĂȘtre entraĂźnĂ©s avec diffĂ©rentes approches de bruits et d’ordonnancement, dont certaines maintiennent la variance relativement constante entre les niveaux de bruit (« prĂ©servation de la variance ») avec diffĂ©rentes astuces de mise Ă  l’échelle et de mĂ©lange au lieu d’avoir des latents bruitĂ©s avec une variance de plus en plus Ă©levĂ©e au fur et Ă  mesure que l’on ajoute du bruit (« explosion de la variance »).

    Si nous voulons partir d’un bruit alĂ©atoire au lieu d’une image bruitĂ©e, nous devons la mettre Ă  l’échelle de la plus grande valeur sigma utilisĂ©e pendant l’entraĂźnement, soit ~14 dans ce cas. Et avant que ces latents bruitĂ©s ne soient introduits dans le modĂšle, ils sont Ă  nouveau mis Ă  l’échelle dans l’étape dite de prĂ©-conditionnement : latent_model_input = latent_model_input / ((sigma**2 + 1) ** 0.5) (maintenant gĂ©rĂ© par latent_model_input = scheduler.scale_model_input(latent_model_input, t)).

    Encore une fois, cette mise Ă  l’échelle/prĂ©-conditionnement diffĂšre entre les articles et les implĂ©mentations, alors gardez un Ɠil sur ce point si vous travaillez avec un type diffĂ©rent de modĂšle de diffusion.

    Boucle Ă  partir de la version bruitĂ©e de l’entrĂ©e (AKA image2image)

    Voyons ce qui se passe lorsque nous utilisons notre image comme point de départ, en ajoutant un peu de bruit et en effectuant les derniÚres étapes de débruitage dans la boucle avec un nouveau prompt.

    Nous allons utiliser une boucle similaire à celle de la premiÚre démonstration, mais nous allons sauter les premiÚres étapes start_step.

    Pour bruiter notre image, nous utiliserons un code comme celui montrĂ© ci-dessus, en utilisant le planificateur pour la bruiter Ă  un niveau Ă©quivalent Ă  l’étape 10 (start_step).

    # ParamĂštres (les mĂȘmes que prĂ©cĂ©demment, Ă  l'exception du nouveau prompt)
    prompt = ["A colorful dancer, nat geo photo"]
    height = 512                        # hauteur par défaut de Stable Diffusion
    width = 512                         # largeur par défaut de Stable Diffusion
    num_inference_steps = 30            # Nombre d'étapes de débruitage
    guidance_scale = 7.5                # Échelle pour un guidage sans classifieur
    generator = torch.manual_seed(32)   # Générateur de la graine pour créer le bruit latent initial
    batch_size = 1
    
    # Preparation du texte (comme précédemment)
    text_input = tokenizer(prompt, padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt")
    with torch.no_grad():
        text_embeddings = text_encoder(text_input.input_ids.to(torch_device))[0]
    max_length = text_input.input_ids.shape[-1]
    uncond_input = tokenizer(
        [""] * batch_size, padding="max_length", max_length=max_length, return_tensors="pt"
    )
    with torch.no_grad():
        uncond_embeddings = text_encoder(uncond_input.input_ids.to(torch_device))[0] 
    text_embeddings = torch.cat([uncond_embeddings, text_embeddings])
    
    # Preparation du planificateur (définition du nombre d'étapes de l'inférence)
    scheduler.set_timesteps(num_inference_steps)
    
    # Preparation des latents (bruitage approprié pour start_step)
    start_step = 10
    start_sigma = scheduler.sigmas[start_step]
    noise = torch.randn_like(encoded)
    latents = scheduler.add_noise(encoded, noise, timesteps=torch.tensor([scheduler.timesteps[start_step]]))
    latents = latents.to(torch_device).float()
    
    # Boucle
    for i, t in tqdm(enumerate(scheduler.timesteps)):
        if i >= start_step: # << C'est la seule modification que nous apportons Ă  la boucle.
            
            # étendre les latents si nous procédons à un guidage sans classifieur afin d'éviter de faire deux passages en avant  
            latent_model_input = torch.cat([latents] * 2)
            sigma = scheduler.sigmas[i]
            latent_model_input = scheduler.scale_model_input(latent_model_input, t)
    
            # prédire le bruit résiduel
            with torch.no_grad():
                noise_pred = unet(latent_model_input, t, encoder_hidden_states=text_embeddings)["sample"]
    
            # effectuer le guidage
            noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
            noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
    
            # calculer l'échantillon bruité précédent x_t -> x_t-1
            latents = scheduler.step(noise_pred, t, latents).prev_sample
    
    latents_to_pil(latents)[0]
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Vous pouvez voir que certaines couleurs et structures de l’image sont conservĂ©es, mais nous avons maintenant une nouvelle image ! Plus vous ajoutez de bruit et plus vous effectuez d’étapes, plus l’image s’éloigne de l’image d’entrĂ©e.

    C’est ainsi que fonctionne le cĂ©lĂšbre pipeline img2img. Encore une fois, si c’est votre objectif final, il existe des outils qui facilitent la tĂąche !

    Mais vous pouvez voir que sous le capot, c’est la mĂȘme chose que la boucle de gĂ©nĂ©ration, en sautant les premiĂšres Ă©tapes et en partant d’une image bruitĂ©e plutĂŽt que d’une image purement bruitĂ©e.

    Essayez de changer le nombre d’étapes sautĂ©es et de voir comment cela affecte la quantitĂ© de changement de l’image par rapport Ă  l’entrĂ©e.

    Exploration du pipeline texte -> enchĂąssement

    Nous utilisons un modùle d’encodage de texte pour transformer notre texte en un ensemble d’enchñssements qui sont transmis au modùle de diffusion en tant que conditionnement. Suivons un morceau de texte tout au long de ce processus et voyons comment il fonctionne.

    # Notre prompt textuel
    prompt = 'A picture of a puppy'
    

    Nous commençons par la tokenisation :

    # Transformer le texte en une séquence de tokens :
    text_input = tokenizer(prompt, padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt")
    text_input['input_ids'][0] # Voir les tokens
    
    tensor([49406,   320,  1674,   539,   320,  6829, 49407, 49407, 49407, 49407,
            49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407,
            49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407,
            49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407,
            49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407,
            49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407,
            49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407,
            49407, 49407, 49407, 49407, 49407, 49407, 49407])
    
    # Voir les tokens individuels
    for t in text_input['input_ids'][0][:8]: # Nous nous contenterons d'examiner les 7 premiers pour vous éviter un mur d'<|endoftext|>'
        print(t, tokenizer.decoder.get(int(t)))
    
    tensor(49406) <|startoftext|>
    tensor(320) a</w>
    tensor(1674) picture</w>
    tensor(539) of</w>
    tensor(320) a</w>
    tensor(6829) puppy</w>
    tensor(49407) <|endoftext|>
    tensor(49407) <|endoftext|>
    

    Nous pouvons passer directement aux enchĂąssements finaux (de sortie) de la maniĂšre suivante :

    # Récupérer les enchùssements de sortie
    output_embeddings = text_encoder(text_input.input_ids.to(torch_device))[0]
    print('Shape:', output_embeddings.shape)
    output_embeddings
    
    Shape: torch.Size([1, 77, 768])
    
    tensor([[[-0.3884,  0.0229, -0.0522,  ..., -0.4899, -0.3066,  0.0675],
             [ 0.0290, -1.3258,  0.3085,  ..., -0.5257,  0.9768,  0.6652],
             [ 0.6942,  0.3538,  1.0991,  ..., -1.5716, -1.2643, -0.0121],
             ...,
             [-0.0221, -0.0053, -0.0089,  ..., -0.7303, -1.3830, -0.3011],
             [-0.0062, -0.0246,  0.0065,  ..., -0.7326, -1.3745, -0.2953],
             [-0.0536,  0.0269,  0.0444,  ..., -0.7159, -1.3634, -0.3075]]],
           device='cuda:0', grad_fn=<NativeLayerNormBackward0>)
    

    Nous passons nos tokens Ă  travers text_encoder et nous obtenons comme par magie des nombres que nous pouvons introduire dans le modĂšle.

    Comment ces chiffres sont-ils gĂ©nĂ©rĂ©s ? Les tokens sont transformĂ©s en un ensemble d’enchĂąssements d’entrĂ©e, qui sont ensuite introduits dans le transformer pour obtenir les enchĂąssements de sortie finaux.

    Pour obtenir ces enchĂąssements d’entrĂ©e, il y a en fait deux Ă©tapes comme le rĂ©vĂšle l’inspection de text_encoder.text_model.embeddings :

    text_encoder.text_model.embeddings
    
    CLIPTextEmbeddings(
      (token_embedding): Embedding(49408, 768)
      (position_embedding): Embedding(77, 768)
    )
    

    EnchĂąssement de tokens

    Le token est envoyĂ© Ă  la fonction token_embedding pour le transformer en vecteur. Le nom de la fonction get_input_embeddings est trompeur puisque ces enchĂąssements de tokens doivent ĂȘtre combinĂ©s avec les enchĂąssements de positions avant d’ĂȘtre utilisĂ©s comme entrĂ©es dans le modĂšle ! Quoi qu’il en soit, examinons d’abord la partie relative Ă  l’enchĂąssements des tokens.

    Nous pouvons regarder la couche d’enchñssement :

    # Accéder à la couche enchùssement
    token_emb_layer = text_encoder.text_model.embeddings.token_embedding
    token_emb_layer # Taille du vocabulaire 49408, emb_dim 768
    
    Embedding(49408, 768)
    

    Et enchĂąsser un token comme suit :

    # EnchĂąsser un *token*, dans ce cas, celui du "chiot"
    embedding = token_emb_layer(torch.tensor(6829, device=torch_device))
    embedding.shape # représentation en 768-dim
    
    torch.Size([768])
    

    Cet unique tokens a été associé avec un vecteur à 768 dimensions.

    Nous pouvons faire la mĂȘme chose avec tous les tokens du prompt pour obtenir tous les enchĂąssements de tokens :

    token_embeddings = token_emb_layer(text_input.input_ids.to(torch_device))
    print(token_embeddings.shape) # taille du batch 1, 77 *tokens*, 768 valeurs pour chaque
    token_embeddings
    
    torch.Size([1, 77, 768])
    
    tensor([[[ 0.0011,  0.0032,  0.0003,  ..., -0.0018,  0.0003,  0.0019],
             [ 0.0013, -0.0011, -0.0126,  ..., -0.0124,  0.0120,  0.0080],
             [ 0.0235, -0.0118,  0.0110,  ...,  0.0049,  0.0078,  0.0160],
             ...,
             [ 0.0012,  0.0077, -0.0011,  ..., -0.0015,  0.0009,  0.0052],
             [ 0.0012,  0.0077, -0.0011,  ..., -0.0015,  0.0009,  0.0052],
             [ 0.0012,  0.0077, -0.0011,  ..., -0.0015,  0.0009,  0.0052]]],
           device='cuda:0', grad_fn=<EmbeddingBackward0>)
    

    EnchĂąssements positionnels

    Les enchĂąssements positionnels indiquent au modĂšle Ă  quel endroit d’une sĂ©quence se trouve un token. Tout comme l’enchĂąssement de * tokens, il s’agit d’un ensemble de paramĂštres (qui peuvent Ă©ventuellement ĂȘtre appris). Mais maintenant, au lieu de traiter ~50k *tokens nous avons juste besoin d’un pour chaque position (77 au total) :

    pos_emb_layer = text_encoder.text_model.embeddings.position_embedding
    pos_emb_layer
    
    Embedding(77, 768)
    

    Nous pouvons obtenir l’enchñssement positionnel pour chaque position :

    position_ids = text_encoder.text_model.embeddings.position_ids[:, :77]
    position_embeddings = pos_emb_layer(position_ids)
    print(position_embeddings.shape)
    position_embeddings
    
    torch.Size([1, 77, 768])
    
    tensor([[[ 0.0016,  0.0020,  0.0002,  ..., -0.0013,  0.0008,  0.0015],
             [ 0.0042,  0.0029,  0.0002,  ...,  0.0010,  0.0015, -0.0012],
             [ 0.0018,  0.0007, -0.0012,  ..., -0.0029, -0.0009,  0.0026],
             ...,
             [ 0.0216,  0.0055, -0.0101,  ..., -0.0065, -0.0029,  0.0037],
             [ 0.0188,  0.0073, -0.0077,  ..., -0.0025, -0.0009,  0.0057],
             [ 0.0330,  0.0281,  0.0289,  ...,  0.0160,  0.0102, -0.0310]]],
           device='cuda:0', grad_fn=<EmbeddingBackward0>)
    

    Combiner les enchĂąssements de tokens et de positions

    Il est temps de combiner les deux. Comment faire ? Il suffit de les additionner ! D’autres approches sont possibles, mais pour ce modĂšle, c’est ainsi que nous procĂ©dons.

    En les combinant de cette maniĂšre, nous obtenons les enchĂąssements d’entrĂ©e finaux, prĂȘts Ă  ĂȘtre introduits dans le transformer :

    # En les combinant, nous obtenons les enchùssements d'entrée finaux
    input_embeddings = token_embeddings + position_embeddings
    print(input_embeddings.shape)
    input_embeddings
    
    torch.Size([1, 77, 768])
    
    tensor([[[ 2.6770e-03,  5.2133e-03,  4.9323e-04,  ..., -3.1321e-03,
               1.0659e-03,  3.4316e-03],
             [ 5.5371e-03,  1.7510e-03, -1.2381e-02,  ..., -1.1410e-02,
               1.3508e-02,  6.8378e-03],
             [ 2.5356e-02, -1.1019e-02,  9.7663e-03,  ...,  1.9460e-03,
               6.8375e-03,  1.8573e-02],
             ...,
             [ 2.2781e-02,  1.3262e-02, -1.1241e-02,  ..., -8.0054e-03,
              -2.0560e-03,  8.9366e-03],
             [ 2.0026e-02,  1.5015e-02, -8.7638e-03,  ..., -4.0313e-03,
               1.8487e-05,  1.0885e-02],
             [ 3.4206e-02,  3.5826e-02,  2.7768e-02,  ...,  1.4465e-02,
               1.1110e-02, -2.5745e-02]]], device='cuda:0', grad_fn=<AddBackward0>)
    

    Nous pouvons vĂ©rifier que ces rĂ©sultats sont les mĂȘmes que ceux obtenus avec text_encoder.text_model.embeddings :

    # La procédure suivante combine toutes les étapes ci-dessus (mais ne nous permet pas de les modifier !)
    text_encoder.text_model.embeddings(text_input.input_ids.to(torch_device))
    
    tensor([[[ 2.6770e-03,  5.2133e-03,  4.9323e-04,  ..., -3.1321e-03,
               1.0659e-03,  3.4316e-03],
             [ 5.5371e-03,  1.7510e-03, -1.2381e-02,  ..., -1.1410e-02,
               1.3508e-02,  6.8378e-03],
             [ 2.5356e-02, -1.1019e-02,  9.7663e-03,  ...,  1.9460e-03,
               6.8375e-03,  1.8573e-02],
             ...,
             [ 2.2781e-02,  1.3262e-02, -1.1241e-02,  ..., -8.0054e-03,
              -2.0560e-03,  8.9366e-03],
             [ 2.0026e-02,  1.5015e-02, -8.7638e-03,  ..., -4.0313e-03,
               1.8487e-05,  1.0885e-02],
             [ 3.4206e-02,  3.5826e-02,  2.7768e-02,  ...,  1.4465e-02,
               1.1110e-02, -2.5745e-02]]], device='cuda:0', grad_fn=<AddBackward0>)
    

    Passage dans le transformer

    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Nous voulons modifier les enchĂąssements d’entrĂ©e (en particulier les enchĂąssements de tokens) avant de les envoyer dans le reste du modĂšle, mais nous devons d’abord nous assurer que nous savons comment le faire. Nous avons lu le code de la mĂ©thode forward du text_encoder, et nous nous sommes basĂ©s sur ce code pour la mĂ©thode forward du text_model que le text_encoder englobe. Pour l’inspecter vous-mĂȘme, tapez ??text_encoder.text_model.forward et vous obtiendrez les informations sur la fonction et le code source, une astuce de dĂ©bogage utile !

    Quoi qu’il en soit, nous pouvons copier les bits dont nous avons besoin pour obtenir ce que l’on appelle le « dernier Ă©tat cachĂ© » et ainsi gĂ©nĂ©rer nos enchĂąssements finaux :

    def get_output_embeds(input_embeddings):
        # Le modÚle de texte de CLIP utilise le masquage causal, c'est pourquoi nous le préparons ici :
        bsz, seq_len = input_embeddings.shape[:2]
        causal_attention_mask = text_encoder.text_model._build_causal_attention_mask(bsz, seq_len, dtype=input_embeddings.dtype)
    
    	# Obtenir les enchĂąssements de sortie implique d'appeler le modĂšle en passant output_hidden_states=True 
        # afin qu'il ne renvoie pas uniquement les prédictions finales regroupées :
        encoder_outputs = text_encoder.text_model.encoder(
            inputs_embeds=input_embeddings,
            attention_mask=None, # Nous n'utilisons pas de masque d'attention, cela peut donc ĂȘtre None.
            causal_attention_mask=causal_attention_mask.to(torch_device),
            output_attentions=None,
            output_hidden_states=True, # Nous voulons le résultat des enchùssements et non le résultat final.
            return_dict=None,
        )
    
        # Seul l'état caché de sortie nous intéresse
        output = encoder_outputs[0]
    
        # Il existe une normalisation de couche finale par laquelle nous devons passer
        output = text_encoder.text_model.final_layer_norm(output)
    
        # Et maintenant, elles sont prĂȘtes !
        return output
    
    out_embs_test = get_output_embeds(input_embeddings) # Alimenter le modĂšle Ă  l'aide de notre nouvelle fonction
    print(out_embs_test.shape) # Vérifier la forme de la sortie
    out_embs_test # Inspecter la sortie
    
    torch.Size([1, 77, 768])
    
    tensor([[[-0.3884,  0.0229, -0.0522,  ..., -0.4899, -0.3066,  0.0675],
             [ 0.0290, -1.3258,  0.3085,  ..., -0.5257,  0.9768,  0.6652],
             [ 0.6942,  0.3538,  1.0991,  ..., -1.5716, -1.2643, -0.0121],
             ...,
             [-0.0221, -0.0053, -0.0089,  ..., -0.7303, -1.3830, -0.3011],
             [-0.0062, -0.0246,  0.0065,  ..., -0.7326, -1.3745, -0.2953],
             [-0.0536,  0.0269,  0.0444,  ..., -0.7159, -1.3634, -0.3075]]],
           device='cuda:0', grad_fn=<NativeLayerNormBackward0>)
    

    Notez que cela correspond aux output_embeddings que nous avons vu au dĂ©but. Nous avons trouvĂ© comment diviser cette Ă©tape (“obtenir les enchĂąssements”) en plusieurs sous-Ă©tapes prĂȘtes Ă  ĂȘtre modifiĂ©es.

    Maintenant que nous avons mis en place ce processus, nous pouvons remplacer l’encodage d’entrĂ©e d’un token par un nouvel encodage de notre choix, ce qui dans notre cas d’utilisation final, sera quelque chose que nous apprendrons. Pour dĂ©montrer le concept, remplaçons l’encodage d’entrĂ©e de « puppy » dans le prompt avec lequel nous avons jouĂ© avec l’enchĂąssement du token 2368, obtenons un nouvel ensemble d’enchĂąssement de sortie basĂ©s sur celui-ci et utilisons-les pour gĂ©nĂ©rer une image afin de voir ce que nous obtenons :

    prompt = 'A picture of a puppy'
    
    # Tokeniser
    text_input = tokenizer(prompt, padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt")
    input_ids = text_input.input_ids.to(torch_device)
    
    # Obtenir les enchĂąssements des tokens
    token_embeddings = token_emb_layer(input_ids)
    
    # Le nouvel enchùssement. Dans ce cas, il s'agit simplement de l'enchùssement d'entrée du token 2368
    replacement_token_embedding = text_encoder.get_input_embeddings()(torch.tensor(2368, device=torch_device))
    
    # Insérer ceci dans les enchùssements de token
    token_embeddings[0, torch.where(input_ids[0]==6829)] = replacement_token_embedding.to(torch_device)
    
    # Combiner avec le'enchĂąssement positionnel
    input_embeddings = token_embeddings + position_embeddings
    
    # Passage dans le transformer pour obtenir les enchĂąssements finaux
    modified_output_embeddings = get_output_embeds(input_embeddings)
    
    print(modified_output_embeddings.shape)
    modified_output_embeddings
    
    torch.Size([1, 77, 768])
    
    tensor([[[-0.3884,  0.0229, -0.0522,  ..., -0.4899, -0.3066,  0.0675],
             [ 0.0290, -1.3258,  0.3085,  ..., -0.5257,  0.9768,  0.6652],
             [ 0.6942,  0.3538,  1.0991,  ..., -1.5716, -1.2643, -0.0121],
             ...,
             [-0.6034, -0.5322,  0.0629,  ..., -0.3964,  0.0877, -0.9558],
             [-0.5936, -0.5407,  0.0731,  ..., -0.3876,  0.0906, -0.9436],
             [-0.6393, -0.4703,  0.1103,  ..., -0.3904,  0.1351, -0.9726]]],
           device='cuda:0', grad_fn=<NativeLayerNormBackward0>)
    

    Les premiers sont identiques, les derniers ne le sont pas. Tout ce qui se trouve à la position du token que nous remplaçons et aprÚs sera affecté.

    Si tout s’est bien passĂ©, nous devrions voir autre chose qu’un chiot lorsque nous les utiliserons pour gĂ©nĂ©rer une image. Et bien sĂ»r, c’est le cas !

    # Génération d'une image avec ces enchùssements modifiés
    
    def generate_with_embs(text_embeddings):
    	height = 512                        # hauteur par défaut de Stable Diffusion
    	width = 512                         # largeur par défaut de Stable Diffusion
    	num_inference_steps = 30            # Nombre d'étapes de débruitage
    	guidance_scale = 7.5                # Échelle pour un guidage sans classifieur
    	generator = torch.manual_seed(32)   # Générateur de la graine pour créer le bruit latent initial
        batch_size = 1
    
        max_length = text_input.input_ids.shape[-1]
        uncond_input = tokenizer(
          [""] * batch_size, padding="max_length", max_length=max_length, return_tensors="pt"
        )
        with torch.no_grad():
            uncond_embeddings = text_encoder(uncond_input.input_ids.to(torch_device))[0] 
        text_embeddings = torch.cat([uncond_embeddings, text_embeddings])
    
        # Preparation du planificateur
        scheduler.set_timesteps(num_inference_steps)
    
        # Preparation des latents
        latents = torch.randn(
        (batch_size, unet.in_channels, height // 8, width // 8),
        generator=generator,
        )
        latents = latents.to(torch_device)
        latents = latents * scheduler.init_noise_sigma
    
        # Boucle
        for i, t in tqdm(enumerate(scheduler.timesteps)):
            # étendre les latents si nous procédons à un guidage sans classifieur afin d'éviter de faire deux passages en avant
            latent_model_input = torch.cat([latents] * 2)
            sigma = scheduler.sigmas[i]
            latent_model_input = scheduler.scale_model_input(latent_model_input, t)
    
            # prédire le bruit résiduel
            with torch.no_grad():
                noise_pred = unet(latent_model_input, t, encoder_hidden_states=text_embeddings)["sample"]
    
            # réaliser un guidage
            noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
            noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
    
            # calculer l'échantillon bruité précédent x_t -> x_t-1
            latents = scheduler.step(noise_pred, t, latents).prev_sample
    
        return latents_to_pil(latents)[0]
    
    generate_with_embs(modified_output_embeddings)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Surprise ! Vous savez maintenant ce que signifie le token 2368.

    Que pouvons-nous en faire ? Pourquoi nous sommes-nous donnĂ© tout ce mal ? Eh bien, nous verrons bientĂŽt un cas d’utilisation plus convaincant, mais en rĂ©sumĂ©, une fois que nous pouvons accĂ©der aux enchĂąssements de tokens et les modifier, nous pouvons faire des choses comme les remplacer par autre chose. Dans l’exemple que nous venons de faire, il s’agissait simplement d’un autre enchĂąssement de tokens du vocabulaire du modĂšle, ce qui Ă©quivaut Ă  une simple modification du prompt. Mais nous pouvons Ă©galement mĂ©langer les tokens. Par exemple, voici un mi-chiot / mi-mouflette :

    # Au cas oĂč vous vous demanderiez comment obtenir le *token* d'un mot, ou l'enchĂąssement d'un *token* :
    prompt = 'skunk'
    print('tokenizer(prompt):', tokenizer(prompt))
    print('token_emb_layer([token_id]) shape:', token_emb_layer(torch.tensor([8797], device=torch_device)).shape)
    
    tokenizer(prompt): {'input_ids': [49406, 42194, 49407], 'attention_mask': [1, 1, 1]}
    token_emb_layer([token_id]) shape: torch.Size([1, 768])
    
    prompt = 'A picture of a puppy'
    
    # Tokeniser
    text_input = tokenizer(prompt, padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt")
    input_ids = text_input.input_ids.to(torch_device)
    
    # Obtenir les enchĂąssements des tokens
    token_embeddings = token_emb_layer(input_ids)
    
    # Le nouvel enchùssement. Il s'agit maintenant d'un mélange d'enchùssement des tokens "puppy" et "skunk"
    puppy_token_embedding = token_emb_layer(torch.tensor(6829, device=torch_device))
    skunk_token_embedding = token_emb_layer(torch.tensor(42194, device=torch_device))
    replacement_token_embedding = 0.5*puppy_token_embedding + 0.5*skunk_token_embedding
    
    # Insérer ceci dans les enchùssements de token
    token_embeddings[0, torch.where(input_ids[0]==6829)] = replacement_token_embedding.to(torch_device)
    
    # Combiner avec le'enchĂąssement positionnel
    input_embeddings = token_embeddings + position_embeddings
    
    # Passage dans le transformer pour obtenir les enchĂąssements finaux
    modified_output_embeddings = get_output_embeds(input_embeddings)
    
    # Générer une image
    generate_with_embs(modified_output_embeddings)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Inversion textuelle

    Nous pouvons donc insĂ©rer un enchĂąssement de token modifiĂ© et l’utiliser pour gĂ©nĂ©rer une image. Nous avons utilisĂ© l’enchĂąssement de token pour « chat » dans l’exemple ci-dessus, mais que se passerait-il si nous pouvions « apprendre » un nouvel enchĂąssement de token pour un concept spĂ©cifique ? C’est l’idĂ©e qui sous-tend l’« Inversion textuelle », dans laquelle quelques exemples d’images sont utilisĂ©s pour crĂ©er un nouvel enchĂąssement de token :

    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Diagramme tirĂ© de l’article de blog sur l’inversion textuelle. Notez qu’il ne montre pas l’étape des enchĂąssements positionnels pour des raisons de simplicitĂ©.

    Nous ne verrons pas comment cet entraĂźnement fonctionne, mais nous pouvons essayer de charger l’un de ces nouveaux “concepts” Ă  partir de la bibliothĂšque de concepts SD créée par la communautĂ© et voir comment il s’intĂšgre dans notre exemple ci-dessus. Nous utiliserons https://huggingface.co/sd-concepts-library/birb-style puisque c’est le premier que nous avons créé. TĂ©lĂ©chargez le fichier learned_embeds.bin Ă  partir de lĂ  et tĂ©lĂ©chargez-le Ă  l’endroit oĂč se trouve ce notebook avant d’exĂ©cuter la cellule suivante :

    birb_embed = torch.load('learned_embeds.bin')
    birb_embed.keys(), birb_embed['<birb-style>'].shape
    
    (dict_keys(['<birb-style>']), torch.Size([768]))
    

    Nous obtenons un dictionnaire avec une clĂ© et l’enchĂąssement de token correspondant. Comme dans l’exemple prĂ©cĂ©dent, remplaçons l’enchĂąssement de « puppy » par celui-ci et voyons ce qui se passe :

    prompt = 'A mouse in the style of puppy'
    
    # Tokeniser
    text_input = tokenizer(prompt, padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt")
    input_ids = text_input.input_ids.to(torch_device)
    
    # Obtenir les enchĂąssements des tokens
    token_embeddings = token_emb_layer(input_ids)
    
    # Le nouvel enchùssement, notre mot d'ordre spécial
    replacement_token_embedding = birb_embed['<birb-style>'].to(torch_device)
    
    # Insérer ceci dans les enchùssements de token
    token_embeddings[0, torch.where(input_ids[0]==6829)] = replacement_token_embedding.to(torch_device)
    
    # Combiner avec le'enchĂąssement positionnel
    input_embeddings = token_embeddings + position_embeddings
    
    # Passage dans le transformer pour obtenir les enchĂąssements finaux
    modified_output_embeddings = get_output_embeds(input_embeddings)
    
    # Générer une image
    generate_with_embs(modified_output_embeddings)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Le token a Ă©tĂ© remplacĂ© par une expression qui reprĂ©sente un style particulier de peinture, mais il pourrait tout aussi bien reprĂ©senter un objet ou une classe d’objets spĂ©cifique.

    Encore une fois, il existe un beau notebook d’infĂ©rence d’Hugging Face pour faciliter l’utilisation des diffĂ©rents concepts, qui gĂšre correctement l’utilisation des noms dans les prompts (“*A in the style of *") sans se prĂ©occuper de toutes ces choses manuelles.

    Mélanger les enchùssements

    Outre le simple remplacement de l’enchĂąssement des tokens d’un seul mot, il existe d’autres astuces que nous pouvons essayer. Par exemple, que se passe-t-il si nous crĂ©ons une « chimĂšre » en calculant la moyenne des enchĂąssements de deux prompts diffĂ©rents ?

    # EnchĂąsser deux prompts
    text_input1 = tokenizer(["A mouse"], padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt")
    text_input2 = tokenizer(["A leopard"], padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt")
    with torch.no_grad():
        text_embeddings1 = text_encoder(text_input1.input_ids.to(torch_device))[0]
        text_embeddings2 = text_encoder(text_input2.input_ids.to(torch_device))[0]
    
    # Les mixer ensemble
    mix_factor = 0.35
    mixed_embeddings = (text_embeddings1*mix_factor + \
                       text_embeddings2*(1-mix_factor))
    
    # Generer
    generate_with_embs(mixed_embeddings)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    L’UNet et le CFG (Classifier Free Guidance)

    Il est maintenant temps d’examiner le modĂšle de diffusion proprement dit. Il s’agit gĂ©nĂ©ralement d’un UNet qui prend en compte les latents bruyants (x) et prĂ©dit le bruit. Nous utilisons un modĂšle conditionnel qui prend Ă©galement en compte le pas de temps (t) et notre enchĂąssement de texte (aka encoder_hidden_states) comme conditionnement. L’introduction de tous ces Ă©lĂ©ments dans le modĂšle se prĂ©sente comme suit : noise_pred = unet(latents, t, encoder_hidden_states=text_embeddings)["sample"]

    Nous pouvons l’essayer et voir Ă  quoi ressemble le rĂ©sultat :

    # Preparation du planificateur
    scheduler.set_timesteps(num_inference_steps)
    
    # Quel est notre pas de temps ?
    t = scheduler.timesteps[0]
    sigma = scheduler.sigmas[0]
    
    # Un latent bruyant
    latents = torch.randn(
      (batch_size, unet.in_channels, height // 8, width // 8),
      generator=generator,
    )
    latents = latents.to(torch_device)
    latents = latents * scheduler.init_noise_sigma
    
    # L'enchĂąssement du texte
    text_input = tokenizer(['A macaw'], padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt")
    with torch.no_grad():
        text_embeddings = text_encoder(text_input.input_ids.to(torch_device))[0]
    
    # Passage dans l'UNet pour prédire le bruit résiduel
    with torch.no_grad():
        noise_pred = unet(latents, t, encoder_hidden_states=text_embeddings)["sample"]
    
    latents.shape, noise_pred.shape # Nous obtenons des prĂ©dictions de la mĂȘme forme que l'entrĂ©e
    
    (torch.Size([1, 4, 64, 64]), torch.Size([1, 4, 64, 64]))
    

    Étant donnĂ© un ensemble de latents bruyants, le modĂšle prĂ©dit la composante de bruit. Nous pouvons retirer ce bruit des latents bruyants pour voir Ă  quoi ressemble l’image de sortie (latents_x0 = latents - sigma * noise_pred). Et nous pouvons ajouter la plus grande partie du bruit Ă  cette sortie prĂ©dite pour obtenir l’entrĂ©e (lĂ©gĂšrement moins bruitĂ©e, espĂ©rons-le) pour l’étape de diffusion suivante. Pour visualiser cela, gĂ©nĂ©rons une autre image, en sauvegardant Ă  la fois la sortie prĂ©dite (x0) et l’étape suivante (xt-1) aprĂšs chaque Ă©tape :

    prompt = 'Oil painting of an otter in a top hat'
    height = 512                      
    width = 512                        
    num_inference_steps = 50
    guidance_scale = 8    
    generator = torch.manual_seed(32)
    batch_size = 1
    
    # Créer un dossier pour stocker les résultats
    !rm -rf steps/
    !mkdir -p steps/
    
    # Preparation du texte 
    text_input = tokenizer([prompt], padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt")
    with torch.no_grad():
        text_embeddings = text_encoder(text_input.input_ids.to(torch_device))[0]
    max_length = text_input.input_ids.shape[-1]
    uncond_input = tokenizer(
        [""] * batch_size, padding="max_length", max_length=max_length, return_tensors="pt"
    )
    with torch.no_grad():
        uncond_embeddings = text_encoder(uncond_input.input_ids.to(torch_device))[0] 
    text_embeddings = torch.cat([uncond_embeddings, text_embeddings])
    
    # Preparation du planificateur
    scheduler.set_timesteps(num_inference_steps)
    
    # Preparation des latents
    latents = torch.randn(
      (batch_size, unet.in_channels, height // 8, width // 8),
      generator=generator,
    )
    latents = latents.to(torch_device)
    latents = latents * scheduler.init_noise_sigma
    
    # Boucle
    for i, t in tqdm(enumerate(scheduler.timesteps)):
        # étendre les latents si nous procédons à un guidage sans classifieur afin d'éviter de faire deux passages en avant
        latent_model_input = torch.cat([latents] * 2)
        sigma = scheduler.sigmas[i]
        latent_model_input = scheduler.scale_model_input(latent_model_input, t)
    
        # prédire le bruit résiduel
        with torch.no_grad():
            noise_pred = unet(latent_model_input, t, encoder_hidden_states=text_embeddings)["sample"]
    
        # effectuer le guidage
        noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
        noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
        
        # Obtenir la valeur prédite x0 :
        # latents_x0 = latents - sigma * noise_pred # Calculer nous-mĂȘmes
        latents_x0 = scheduler.step(noise_pred, t, latents).pred_original_sample # Utilisation du planificateur (Diffuseurs 0.4 et plus)
    
        # calculer l'échantillon bruité précédent x_t -> x_t-1
        latents = scheduler.step(noise_pred, t, latents).prev_sample
    
        # Vers des images PIL
        im_t0 = latents_to_pil(latents_x0)[0]
        im_next = latents_to_pil(latents)[0]
    
        # Combinez les deux images et enregistrez-les pour une visualisation ultérieure
        im = Image.new('RGB', (1024, 512))
        im.paste(im_next, (0, 0))
        im.paste(im_t0, (512, 0))
        im.save(f'steps/{i:04}.jpeg')
    
    # Réaliser et diffuser la vidéo sur l'état d'avancement (modifier la largeur à 1024 pour une pleine résolution)
    !ffmpeg -v 1 -y -f image2 -framerate 12 -i steps/%04d.jpeg -c:v libx264 -preset slow -qp 18 -pix_fmt yuv420p out.mp4
    mp4 = open('out.mp4','rb').read()
    data_url = "data:video/mp4;base64," + b64encode(mp4).decode()
    HTML("""
    <video width=600 controls>
          <source src="%s" type="video/mp4">
    </video>
    """ % data_url)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    La version de droite montre la « sortie finale » prĂ©dite (x0) Ă  chaque Ă©tape, et c’est ce qui est gĂ©nĂ©ralement utilisĂ© pour les vidĂ©os de progression, etc. La version de gauche reprĂ©sente l’étape suivante. Nous trouvons intĂ©ressant de comparer les deux, en regardant les vidĂ©os de progression, on pourrait penser que des changements radicaux se produisent, en particulier aux premiers stades, mais comme les changements apportĂ©s Ă  chaque Ă©tape sont relativement faibles, le processus rĂ©el est beaucoup plus progressif.

    CFG (Classifier Free Guidance)

    Par dĂ©faut, le modĂšle ne fait pas souvent ce que nous lui demandons. Si nous voulons qu’il suive mieux le prompt, nous utilisons un hack appelĂ© CFG. Il y a une bonne explication dans cette vidĂ©o video d’AI Coffee Break with Letitia.

    Dans le code, cela revient Ă  faire :

    noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)

    Cela fonctionne Ă©tonnamment bien :) Essayez de changer le guidance_scale dans le code ci-dessus et voyez comment cela affecte les rĂ©sultats. Jusqu’oĂč pouvez-vous aller avant que les rĂ©sultats n’empirent ?

    Échantillonnage

    Il y a encore de la complexitĂ© cachĂ©e dans latents = scheduler.step(noise_pred, i, latents)["prev_sample"]. Comment l’échantillonneur passe-t-il exactement des latents bruyants actuels Ă  une version lĂ©gĂšrement moins bruyante ? Pourquoi ne pas utiliser le modĂšle en une seule Ă©tape ? Existe-t-il d’autres façons de voir les choses ?

    Le modĂšle tente de prĂ©dire le bruit dans une image. Pour des valeurs de bruit faibles, nous supposons qu’il fait un assez bon travail. Pour des niveaux de bruit plus Ă©levĂ©s, la tĂąche est ardue ! Ainsi, au lieu de produire une image parfaite, les rĂ©sultats ont tendance Ă  ressembler Ă  un dĂ©sordre flou. Voir le dĂ©but de la vidĂ©o citĂ©e Ă  l’instant pour une illustration ! Les Ă©chantillonneurs utilisent donc les prĂ©dictions du modĂšle pour s’en rapprocher lĂ©gĂšrement (en Ă©liminant une partie du bruit), puis obtiennent une autre prĂ©diction basĂ©e sur cette entrĂ©e marginalement moins mauvaise, en espĂ©rant que cela amĂ©liorera le rĂ©sultat de maniĂšre itĂ©rative.

    Les diffĂ©rents Ă©chantillonneurs procĂšdent de diffĂ©rentes maniĂšres. Vous pouvez essayer d’inspecter le code de l’échantillonneur LMS par dĂ©faut avec :

    # ??scheduler.step
    

    Guidage

    Ok, derniÚre astuce ! Comment pouvons-nous ajouter un contrÎle supplémentaire à ce processus de génération ?

    À chaque Ă©tape, nous allons utiliser notre modĂšle comme prĂ©cĂ©demment pour prĂ©dire la composante bruit de $x$. Ensuite, nous allons l’utiliser pour produire une image de sortie prĂ©dite, et appliquer une fonction de perte Ă  cette image.

    Cette fonction peut ĂȘtre n’importe quoi, mais nous allons faire une dĂ©monstration avec un exemple trĂšs simple. Si nous voulons des images avec beaucoup de bleu, nous pouvons crĂ©er une fonction de perte qui donne une perte Ă©levĂ©e si les pixels ont une faible composante bleue :

    def blue_loss(images):
        # Quelle est la distance entre les valeurs du canal bleu et 0,9 ?
        error = torch.abs(images[:,2] - 0.9).mean() # [:,2] -> toutes les images dans le batch, seulement le canal bleu
        return error
    

    Lors de chaque Ă©tape de mise Ă  jour, nous trouvons le gradient de la perte par rapport aux latents bruyants actuels et nous les modifions dans la direction qui rĂ©duit cette perte tout en effectuant l’étape de mise Ă  jour normale :

    prompt = 'A campfire (oil on canvas)' #@param
    height = 512                        # hauteur par défaut de Stable Diffusion
    width = 512                         # largeur par défaut de Stable Diffusion
    num_inference_steps = 50  #@param           # Nombre d'étapes de débruitage
    guidance_scale = 8 #@param               # Échelle pour un guidage sans classifieur
    generator = torch.manual_seed(32)   # Générateur de graines pour créer le bruit latent initial
    batch_size = 1
    blue_loss_scale = 200 #@param
    
    # Preparation du texte 
    text_input = tokenizer([prompt], padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt")
    with torch.no_grad():
        text_embeddings = text_encoder(text_input.input_ids.to(torch_device))[0]
    
    # Et l'entrée non conditionnelle comme précédemment :
    max_length = text_input.input_ids.shape[-1]
    uncond_input = tokenizer(
        [""] * batch_size, padding="max_length", max_length=max_length, return_tensors="pt"
    )
    with torch.no_grad():
        uncond_embeddings = text_encoder(uncond_input.input_ids.to(torch_device))[0] 
    text_embeddings = torch.cat([uncond_embeddings, text_embeddings])
    
    # Preparation du planificateur
    scheduler.set_timesteps(num_inference_steps)
    
    # Preparation des latents
    latents = torch.randn(
      (batch_size, unet.in_channels, height // 8, width // 8),
      generator=generator,
    )
    latents = latents.to(torch_device)
    latents = latents * scheduler.init_noise_sigma
    
    # Boucle
    for i, t in tqdm(enumerate(scheduler.timesteps)):
        # étendre les latents si nous procédons à un guidage sans classifieur afin d'éviter de faire deux passages en avant
        latent_model_input = torch.cat([latents] * 2)
        sigma = scheduler.sigmas[i]
        latent_model_input = scheduler.scale_model_input(latent_model_input, t)
    
        # prédire le bruit résiduel
        with torch.no_grad():
            noise_pred = unet(latent_model_input, t, encoder_hidden_states=text_embeddings)["sample"]
    
        # réaliser le CFG
        noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
        noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
        
        #### GUIDAGES SUPPLÉMENTAIRES ###
        if i%5 == 0:
            # Requires_grad sur les latents
            latents = latents.detach().requires_grad_()
    
            # Obtenir la valeur prédite x0 :
            # latents_x0 = latents - sigma * noise_pred
            latents_x0 = scheduler.step(noise_pred, t, latents).pred_original_sample
    
            # Décodage vers l'espace d'image
            denoised_images = vae.decode((1 / 0.18215) * latents_x0).sample / 2 + 0.5 # range (0, 1)
    
            # Calculer la perte
            loss = blue_loss(denoised_images) * blue_loss_scale
            
            # Imprimer occasionnellement
            if i%10==0:
                print(i, 'loss:', loss.item())
    
            # Obtenir le gradient
            cond_grad = torch.autograd.grad(loss, latents)[0]
    
            # Modifier les latents en fonction de ce gradient
            latents = latents.detach() - cond_grad * sigma**2
        
        # Etape avec le planificateur
        latents = scheduler.step(noise_pred, t, latents).prev_sample
    
    
    latents_to_pil(latents)[0]
    
    0 loss: 182.02133178710938 
    10 loss: 43.55351257324219 
    20 loss: 15.30621337890625 
    30 loss: 9.746519088745117 
    40 loss: 8.846868515014648
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Ajustez l’échelle (blue_loss_scale). A faible valeur, l’image est principalement rouge et orange grĂące au prompt. Avec des valeurs plus Ă©levĂ©es, l’image est surtout bleutĂ©e ! Si l’échelle est trop Ă©levĂ©e, l’image devient bleue.

    Comme c’est lent, vous remarquerez que nous appliquons cette perte seulement une fois toutes les 5 itĂ©rations. Pour vos propres tests, vous pouvez envisager d’utiliser une Ă©chelle plus basse pour la perte et de l’appliquer Ă  chaque itĂ©ration Ă  la place :)

    NB : Nous devrions mettre latents requires_grad=True avant de faire la passe avant de l’UNet (en enlevant avec torch.no_grad()) si nous voulons des gradients prĂ©cis. MAIS cela nĂ©cessite beaucoup de mĂ©moire supplĂ©mentaire. Vous verrez les deux approches utilisĂ©es en fonction de l’implĂ©mentation que vous regardez.

    Le guidage avec des modĂšles de classification peut vous donner des images d’une classe spĂ©cifique. L’utilisation d’un modĂšle comme CLIP permet de mieux faire correspondre un prompt Ă  un texte. L’utilisation d’une perte de style permet d’ajouter un style particulier. Le guidage avec une sorte de perte de perception peut l’orienter vers l’aspect gĂ©nĂ©ral d’une image cible. Et ainsi de suite.

    Conclusion

    Nous espĂ©rons que vous avez maintenant une idĂ©e un peu plus prĂ©cise de ce qui se passe lorsque vous crĂ©ez une image avec l’un de ces modĂšles, et de la façon dont vous pouvez modifier le processus de maniĂšre crĂ©ative. En espĂ©rant que cela vous donne envie de faire quelque chose d’amusant :)

    Ce notebook a Ă©tĂ© Ă©crit par Jonathan Whitaker, adaptĂ© de Grokking Stable Diffusion qui Ă©tait sa premiĂšre tentative pour comprendre ces composants par lui-mĂȘme. Si vous repĂ©rez des bugs ou si vous avez des questions, n’hĂ©sitez pas Ă  le contacter via @johnowhitaker :) Bonne lecture !

    4. Vue d'ensemble

    Dans cette unité, nous examinerons certaines des nombreuses améliorations et extensions des modÚles de diffusion apparaissant dans les recherches les plus récentes. Elle sera moins axée sur le code que les unités précédentes et est conçue pour vous donner un point de départ pour des recherches plus approfondies.

    Vue d’ensemble de cette unitĂ© 🚀

    Les différentes étapes à suivre pour cette unité :

    • Lisez le matĂ©riel ci-dessous pour avoir une vue d’ensemble des idĂ©es clĂ©s de cette unitĂ©
    • Approfondissez les sujets spĂ©cifiques grĂące aux vidĂ©os et aux ressources associĂ©es.
    • Explorez les notebooks de dĂ©monstration, puis lisez la section « Et ensuite ? » pour obtenir des suggestions de projets.

    Échantillonnage plus rapide par distillation

    La distillation progressive est une technique permettant de prendre un modĂšle de diffusion existant et de l’utiliser pour entraĂźner une nouvelle version du modĂšle qui nĂ©cessite moins d’étapes pour l’infĂ©rence. Le modĂšle â€œĂ©lĂšve” est initialisĂ© Ă  partir des poids du modĂšle “enseignant”. Pendant l’entraĂźnement, le modĂšle enseignant effectue deux Ă©tapes d’échantillonnage et le modĂšle de Ă©tudiant tente de faire correspondre la prĂ©diction rĂ©sultante en une seule Ă©tape. Ce processus peut ĂȘtre rĂ©pĂ©tĂ© plusieurs fois, le modĂšle Ă©tudiant de l’itĂ©ration prĂ©cĂ©dente devenant le modĂšle enseignant pour l’étape suivante. Le rĂ©sultat est un modĂšle qui peut produire des Ă©chantillons dĂ©cents en beaucoup moins d’étapes (gĂ©nĂ©ralement 4 ou 8) que le modĂšle enseignant d’origine. Le mĂ©canisme de base est illustrĂ© dans ce diagramme tirĂ© de l’article qui a introduit l’idĂ©e :

    image

    Illustration de la distillation progressive issue de ce papier

    L’idĂ©e d’utiliser un modĂšle existant pour “enseigner” un nouveau modĂšle peut ĂȘtre Ă©tendue pour crĂ©er des modĂšles guidĂ©s dans lesquels la technique de guidage sans classifieur est utilisĂ©e par le modĂšle enseignant et le modĂšle Ă©tudiant doit apprendre Ă  produire un rĂ©sultat Ă©quivalent en une seule Ă©tape sur la base d’une entrĂ©e supplĂ©mentaire spĂ©cifiant l’échelle de guidage ciblĂ©e. Cela permet de rĂ©duire encore le nombre d’évaluations de modĂšles nĂ©cessaires pour produire des Ă©chantillons de haute qualitĂ©. Cette vidĂ©o (en anglais) donne un aperçu de l’approche.

    Références principales :

    • Progressive Distillation For Fast Sampling Of Diffusion Models
    • On Distillation Of Guided Diffusion Models

    AmĂ©lioration de l’entraĂźnement

    Plusieurs astuces supplĂ©mentaires ont Ă©tĂ© mises au point pour amĂ©liorer l’entraĂźnement des modĂšles de diffusion. Dans cette section, nous avons essayĂ© de prĂ©senter les idĂ©es principales des articles rĂ©cents. Il y a un flux constant de recherches qui sortent avec des amĂ©liorations supplĂ©mentaires, donc si vous voyez un article qui devrait ĂȘtre ajoutĂ© ici, veuillez nous le faire savoir !

    image Figure 2 du papier ERNIE-ViLG 2.0

    AmĂ©liorations principales de l’entraĂźnement :

    • RĂ©glage du planificateur du bruit, de la pondĂ©ration de la perte et des trajectoires d’échantillonnage pour un entraĂźnement plus efficace. Un excellent papier explorant certains de ces choix de conception est Elucidating the Design Space of Diffusion-Based Generative Models par Karras et al.
    • EntraĂźnement sur divers rapports d’aspect, comme dĂ©crit dans cette vidĂ©o du lancement du cours (en anglais).
    • ModĂšles de diffusion en cascade, entraĂźnant un modĂšle Ă  basse rĂ©solution, puis un ou plusieurs modĂšles en super-rĂ©solution. UtilisĂ©s dans DALLE-2, Imagen et d’autres pour la gĂ©nĂ©ration d’images Ă  haute rĂ©solution.
    • Meilleur conditionnement, incorporation d’enchĂąssement textuels riches (Imagen utilise un grand modĂšle de langage appelĂ© T5) ou plusieurs types de conditionnement (eDiffi).
    • “AmĂ©lioration des connaissances” : incorporation de modĂšles de sous-titrage d’images et de dĂ©tection d’objets prĂ©-entraĂźnĂ©s dans le processus d’entraĂźnement afin de crĂ©er des sous-titres plus informatifs et d’obtenir de meilleures performances (ERNIE-ViLG 2.0).
    • “MĂ©lange d’experts de dĂ©bruitage” (MoDE) : entraĂźner diffĂ©rentes variantes du modĂšle (“experts”) pour diffĂ©rents niveaux de bruit, comme illustrĂ© dans l’image ci-dessus tirĂ©e du papier ERNIE-ViLG 2.0.

    Références principales :

    • Elucidating the Design Space of Diffusion-Based Generative Models
    • eDiffi: Text-to-Image Diffusion Models with an Ensemble of Expert Denoisers
    • ERNIE-ViLG 2.0: Improving Text-to-Image Diffusion Model with Knowledge-Enhanced Mixture-of-Denoising-Experts
    • Imagen - Photorealistic Text-to-Image Diffusion Models with Deep Language Understanding (site dĂ©mo)

    Plus de contrĂŽle pour la gĂ©nĂ©ration et l’édition

    Outre les amĂ©liorations apportĂ©es Ă  l’entraĂźnement, plusieurs innovations ont Ă©tĂ© apportĂ©es Ă  la phase d’échantillonnage et d’infĂ©rence, y compris de nombreuses approches qui peuvent ajouter de nouvelles capacitĂ©s aux modĂšles de diffusion existants.

    image Échantillons gĂ©nĂ©rĂ©s par eDiffi

    La vidĂ©o ‘Editing Images with Diffusion Models’ (en anglais) donne un aperçu des diffĂ©rentes mĂ©thodes utilisĂ©es pour Ă©diter des images existantes avec des modĂšles de diffusion. Les techniques disponibles peuvent ĂȘtre divisĂ©es en quatre catĂ©gories principales :

    1) Ajouter du bruit, puis dĂ©bruiter avec un nouveau prompt. C’est l’idĂ©e qui sous-tend le pipeline img2img, qui a Ă©tĂ© modifiĂ© et Ă©tendu dans plusieurs articles :

    • SDEdit et MagicMix s’inspirent de cette idĂ©e
    • DDIM inversion utilise le modĂšle pour “inverser” la trajectoire d’échantillonnage plutĂŽt que d’ajouter un bruit alĂ©atoire, ce qui permet un meilleur contrĂŽle
    • Null-text Inversion amĂ©liore considĂ©rablement les performances de ce type d’approche en optimisant Ă  chaque Ă©tape les enchĂąssements de texte inconditionnels utilisĂ©s pour le guidage sans classifieur, ce qui permet d’obtenir une Ă©dition d’images textuelles de trĂšs haute qualitĂ©. 2) Extension des idĂ©es du point (1), mais avec un masque permettant de contrĂŽler l’endroit oĂč l’effet est appliquĂ©
    • Blended Diffusion introduit l’idĂ©e de base
    • Cette dĂ©mo utilise un modĂšle de segmentation existant (CLIPSeg) pour crĂ©er le masque sur la base d’une description textuelle
    • DiffEdit est un excellent papier montrant comment le modĂšle de diffusion lui-mĂȘme peut ĂȘtre utilisĂ© pour gĂ©nĂ©rer un masque appropriĂ© pour l’édition de l’image en fonction du texte
    • SmartBrush: Text and Shape Guided Object Inpainting with Diffusion Model finetune un modĂšle de diffusion pour une peinture guidĂ©e par un masque 3) ContrĂŽle de l’attention croisĂ©e : utilisation du mĂ©canisme d’attention croisĂ©e dans les modĂšles de diffusion pour contrĂŽler l’emplacement spatial des modifications afin d’exercer un contrĂŽle plus fin
    • Prompt-to-Prompt Image Editing with Cross Attention Control est l’article clĂ© qui a introduit cette idĂ©e, et la technique a depuis Ă©tĂ© appliquĂ©e Ă  Stable Diffusion
    • TCette idĂ©e est Ă©galement utilisĂ©e pour ‘paint-with-words’ (eDiffi, voir ci-dessus) 4) Finetuner (surapprendre) sur une seule image, puis gĂ©nĂ©rer avec le modĂšle finetunĂ©. Les articles suivants ont tous deux publiĂ© des variantes de cette idĂ©e Ă  peu prĂšs au mĂȘme moment :
    • Imagic: Text-Based Real Image Editing with Diffusion Models
    • UniTune: Text-Driven Image Editing by Fine Tuning an Image Generation Model on a Single Image

    Le papier InstructPix2Pix : Learning to Follow Image Editing Instructions est remarquable en ce sens qu’il utilise certaines des techniques d’édition d’images dĂ©crites ci-dessus pour construire un jeu de donnĂ©es synthĂ©tique de paires d’images accompagnĂ©es d’instructions d’édition d’images (gĂ©nĂ©rĂ©es avec GPT3.5) afin d’entraĂźner un nouveau modĂšle capable d’éditer des images sur la base d’instructions en langage naturel.

    Video

    image Images fixes d’exemples de vidĂ©os gĂ©nĂ©rĂ©es avec Imagen Video

    Une vidĂ©o peut ĂȘtre reprĂ©sentĂ©e comme une sĂ©quence d’images, et les idĂ©es fondamentales des modĂšles de diffusion peuvent ĂȘtre appliquĂ©es Ă  ces sĂ©quences. Les travaux rĂ©cents se sont concentrĂ©s sur la recherche d’architectures appropriĂ©es (telles que les « 3D UNets » qui opĂšrent sur des sĂ©quences entiĂšres) et sur l’utilisation efficace des donnĂ©es vidĂ©o. Étant donnĂ© que les vidĂ©os Ă  haute frĂ©quence d’images comportent beaucoup plus de donnĂ©es que les images fixes, les approches actuelles tendent Ă  gĂ©nĂ©rer d’abord des vidĂ©os Ă  faible rĂ©solution et Ă  faible frĂ©quence d’images, puis Ă  appliquer la super-rĂ©solution spatiale et temporelle pour produire les sorties vidĂ©o finales de haute qualitĂ©.

    Références principales :

    • Video Diffusion Models
    • Imagen Video: High Definition Video Generation With Diffusion Models

    Audio

    image

    Un spectrogramme généré avec Riffusion

    Bien que des travaux aient Ă©tĂ© rĂ©alisĂ©s pour gĂ©nĂ©rer du son directement Ă  l’aide de modĂšles de diffusion (par exemple DiffWave), l’approche la plus fructueuse jusqu’à prĂ©sent a consistĂ© Ă  convertir le signal audio en ce que l’on appelle un spectrogramme, qui “encode” effectivement le son sous la forme d’une “image” en 2D qui peut ensuite ĂȘtre utilisĂ©e pour entraĂźner les modĂšles de diffusion que nous avons l’habitude d’utiliser pour la gĂ©nĂ©ration d’images. Les spectrogrammes ainsi gĂ©nĂ©rĂ©s peuvent ensuite ĂȘtre convertis en donnĂ©es audio Ă  l’aide des mĂ©thodes existantes. Cette approche est Ă  l’origine de Riffusion, qui a rĂ©cemment Ă©tĂ© publiĂ© et a permis de finetuner Stable Diffusion pour gĂ©nĂ©rer des spectrogrammes conditionnĂ©s par le texte. Essayez-le ici.

    Le domaine de la gĂ©nĂ©ration d’audio Ă©volue trĂšs rapidement. Au cours de la semaine derniĂšre (Ă  l’heure oĂč nous Ă©crivons ces lignes), au moins cinq nouvelles avancĂ©es ont Ă©tĂ© annoncĂ©es, qui sont marquĂ©es d’une Ă©toile dans la liste ci-dessous :

    Références principales :

    • DiffWave: A Versatile Diffusion Model for Audio Synthesis
    • Riffusion (et son code)
    • ⭐ MusicLM de Google gĂ©nĂšre un son cohĂ©rent Ă  partir d’un texte et peut ĂȘtre conditionnĂ© avec des mĂ©lodies fredonnĂ©es ou sifflĂ©es.
    • ⭐ RAVE2, une nouvelle version d’un auto-encodeur variationnel qui sera utile pour la diffusion latente dans les tĂąches audio. Il est utilisĂ© dans le modĂšle AudioLDM.
    • ⭐ Noise2Music, un modĂšle de diffusion entraĂźnĂ© Ă  produire des clips audio de 30 secondes en haute qualitĂ© sur la base de descriptions textuelles.
    • ⭐ Make-An-Audio: Text-To-Audio Generation with Prompt-Enhanced Diffusion Models, un modĂšle de diffusion entraĂźnĂ© Ă  gĂ©nĂ©rer divers sons Ă  partir d’un texte.
    • ⭐ MoĂ»sai: Text-to-Music Generation with Long-Context Latent Diffusion

    Nouvelles architectures et approches : vers un « raffinement itératif »

    image

    Figure 1 du papier Cold Diffusion

    Nous dĂ©passons peu Ă  peu la dĂ©finition Ă©troite initiale des modĂšles de « diffusion » pour nous orienter vers une classe plus gĂ©nĂ©rale de modĂšles qui effectuent un raffinement itĂ©ratif, oĂč une certaine forme de corruption (comme l’ajout d’un bruit gaussien dans le processus de diffusion vers l’avant) est progressivement inversĂ©e pour gĂ©nĂ©rer des Ă©chantillons. L’article « Cold Diffusion » a dĂ©montrĂ© que de nombreux autres types de corruption peuvent ĂȘtre « dĂ©faits » de maniĂšre itĂ©rative pour gĂ©nĂ©rer des images (exemples ci-dessus), et des approches rĂ©centes basĂ©es sur des transfomers ont dĂ©montrĂ© l’efficacitĂ© du remplacement ou du masquage de token en tant que stratĂ©gie de bruitage.

    image

    Pipeline de MaskGIT

    L’architecture UNet au cƓur de nombreux modĂšles de diffusion actuels est Ă©galement remplacĂ©e par d’autres solutions, notamment diverses architectures basĂ©es sur des transformers. Dans Scalable Diffusion Models with Transformers (DiT), un transformer est utilisĂ© Ă  la place du UNet pour une approche de modĂšle de diffusion assez standard, avec d’excellents rĂ©sultats. Recurrent Interface Networks applique une nouvelle architecture basĂ©e sur un transformer et une stratĂ©gie d’entraĂźnement Ă  la recherche d’une efficacitĂ© accrue. MaskGIT et MUSE utilisent des transformers pour travailler avec des reprĂ©sentations d’images par tokens, bien que le modĂšle Paella dĂ©montre qu’un UNet peut Ă©galement ĂȘtre appliquĂ© avec succĂšs Ă  ces rĂ©gimes basĂ©s sur des tokens.

    Avec chaque nouveau papier, des approches plus efficaces sont dĂ©veloppĂ©es, et il faudra peut-ĂȘtre attendre un certain temps avant de voir Ă  quoi ressemblent les performances maximales pour ce type de tĂąches d’affinage itĂ©ratif. Il reste encore beaucoup de choses Ă  explorer !

    Références principales :

    • Cold Diffusion: Inverting Arbitrary Image Transforms Without Noise
    • Scalable Diffusion Models with Transformers (DiT)
    • MaskGIT: Masked Generative Image Transformer
    • Muse: Text-To-Image Generation via Masked Generative Transformers
    • Fast Text-Conditional Discrete Denoising on Vector-Quantized Latent Spaces (Paella)
    • Recurrent Interface Networks : une nouvelle architecture prometteuse qui permet de gĂ©nĂ©rer des images Ă  haute rĂ©solution sans recourir Ă  la diffusion latente ou Ă  la super-rĂ©solution. Voir Ă©galement simple diffusion : End-to-end diffusion for high-resolution images qui souligne l’importance du planificateur du bruit pour l’entraĂźnement Ă  des rĂ©solutions plus Ă©levĂ©es.

    Notebooks

    Chapitre Colab Kaggle Gradient Studio Lab
    Débruitage inverse des modÚles de diffusion implicites Open In Colab Kaggle Gradient Open In SageMaker Studio Lab
    Diffusion pour l’audio Open In Colab Kaggle Gradient Open In SageMaker Studio Lab

    Nous avons abordĂ© un grand nombre d’idĂ©es diffĂ©rentes dans cette unitĂ©, dont beaucoup mĂ©riteraient de faire l’objet de leçons plus dĂ©taillĂ©es Ă  l’avenir. Pour l’instant, vous pouvez aborder deux de ces nombreux sujets via les notebook que nous avons prĂ©parĂ©s.

    • Le dĂ©bruitage inverse des modĂšles de diffusion implicites montre comment une technique appelĂ©e inversion peut ĂȘtre utilisĂ©e pour Ă©diter des images Ă  l’aide de modĂšles de diffusion existants.
    • Diffusion pour l’audio introduit l’idĂ©e de spectrogrammes et montre un exemple minimal de finetuning d’un modĂšle de diffusion pour l’audio sur un genre de musique spĂ©cifique.

    Et ensuite ?

    Il s’agit de la derniĂšre unitĂ© de ce cours actuellement, ce qui signifie que la suite ne dĂ©pend que de vous ! N’oubliez pas que vous pouvez toujours poser des questions et discuter de vos projets sur le Discord d’Hugging Face. Nous avons hĂąte de voir ce que vous allez crĂ©er đŸ€—

    4.1. Débruitage inverse des modÚles de diffusion implicites (DDIM)

    Dans ce notebook, nous allons explorer l’inversion, voir comment elle est liĂ©e Ă  l’échantillonnage, et l’appliquer Ă  la tĂąche d’édition d’images avec Stable Diffusion. Ce que vous allez apprendre :

    • Comment fonctionne l’échantillonnage DDIM
    • Échantillonneurs dĂ©terministes et stochastiques
    • La thĂ©orie derriĂšre l’inversion DDIM
    • L’édition d’images avec l’inversion

    Commençons !

    Configuration

    # !pip install -q transformers diffusers accelerate
    
    import torch
    import requests
    import torch.nn as nn
    import torch.nn.functional as F
    from PIL import Image
    from io import BytesIO
    from tqdm.auto import tqdm
    from matplotlib import pyplot as plt
    from torchvision import transforms as tfms
    from diffusers import StableDiffusionPipeline, DDIMScheduler
    
    # Une fonction utile pour plus tard
    def load_image(url, size=None):
        response = requests.get(url,timeout=0.2)
        img = Image.open(BytesIO(response.content)).convert('RGB')
        if size is not None:
            img = img.resize(size)
        return img
    
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    

    Chargement d’un pipeline existant

    # Charger un pipeline
    pipe = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5").to(device)
    
    # Mettre en place un planificateur DDIM
    pipe.scheduler = DDIMScheduler.from_config(pipe.scheduler.config)
    
    # Échantillon d'une image pour s'assurer que tout fonctionne bien
    prompt = 'Beautiful DSLR Photograph of a penguin on the beach, golden hour'
    negative_prompt = 'blurry, ugly, stock photo'
    im = pipe(prompt, negative_prompt=negative_prompt).images[0]
    im.resize((256, 256)) # redimensionner pour une meilleure visualisation
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Echantillonage DDIM

    À un moment donnĂ© $t$, l’image bruitĂ©e $x_t$ est un mĂ©lange de l’image originale ($x_0$) et de bruit ($\epsilon$). Voici la formule pour $x_t$ tirĂ©e de l’article DDIM, Ă  laquelle nous nous rĂ©fĂ©rerons dans cette section :

    \[x_t = \sqrt{\alpha_t}x_0 + \sqrt{1-\alpha_t}\epsilon\]

    $\epsilon$ est un bruit gaussien de variance unitaire $\alpha_t$ (“alpha”) est la valeur qui est appelĂ©e de maniĂšre confuse $\bar{\alpha}$ (“alpha_bar”) dans le papier DDPM ( !!) et qui dĂ©finit le planificateur de bruit. Dans đŸ€— Diffusers, le planificateur alpha est calculĂ© et les valeurs sont stockĂ©es dans le scheduler.alphas_cumprod. Je sais que c’est dĂ©routant ! Traçons ces valeurs, et n’oubliez pas que pour le reste de ce notebook, nous utiliserons la notation de DDIM.

    # Tracer 'alpha' (alpha_bar dans DDPM, alphas_cumprod dans Diffusers)
    timesteps = pipe.scheduler.timesteps.cpu()
    alphas = pipe.scheduler.alphas_cumprod[timesteps]
    plt.plot(timesteps, alphas, label='alpha_t');
    plt.legend()
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Au départ (étape 0, cÎté gauche du graphique), nous commençons avec une image propre et sans bruit. $\alpha_t = 1$. Au fur et à mesure que nous passons à des pas de temps plus élevés, nous nous retrouvons avec presque tout le bruit et $\alpha_t$ chute vers 0.

    Lors de l’échantillonnage, nous commençons avec du bruit pur au pas de temps $1000$ et nous nous rapprochons lentement du pas de temps $0$. Pour calculer le prochain $t$ de la trajectoire d’échantillonnage ($x_{t-1}$ puisque nous passons d’un $t$ Ă©levĂ© Ă  un $t$ faible), nous prĂ©disons le bruit ($\epsilon_\theta(x_t)$, qui est la sortie de notre modĂšle) et nous l’utilisons pour calculer l’image dĂ©bruitĂ©e prĂ©dite $x_0$. Nous utilisons ensuite cette prĂ©diction pour nous dĂ©placer sur une petite distance dans la « direction pointant vers $x_t$ ». Enfin, nous pouvons ajouter du bruit supplĂ©mentaire Ă  l’échelle de $\sigma_t$. Voici la section de l’article qui montre cette mĂ©thode en action :

    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Nous disposons donc d’une Ă©quation permettant de passer de $x_t$ Ă  $x_{t-1}$, avec une quantitĂ© de bruit contrĂŽlable. Dans notre cas prĂ©sent, nous nous intĂ©ressons plus particuliĂšrement au cas oĂč nous n’ajoutons aucun bruit supplĂ©mentaire, ce qui nous donne un Ă©chantillonnage DDIM entiĂšrement dĂ©terministe. Voyons ce que cela donne en code :

    # Fonction d'échantillonnage (DDIM standard)
    @torch.no_grad()
    def sample(prompt, start_step=0, start_latents=None,
               guidance_scale=3.5, num_inference_steps=30,
               num_images_per_prompt=1, do_classifier_free_guidance=True,
               negative_prompt='', device=device):
      
        # Encoder le prompt
        text_embeddings = pipe._encode_prompt(
                prompt, device, num_images_per_prompt, do_classifier_free_guidance, negative_prompt
        )
    
        # Nombre d'étapes d'inférence
        pipe.scheduler.set_timesteps(num_inference_steps, device=device)
    
        # Créer un point de départ aléatoire si nous n'en avons pas déjà un
        if start_latents is None:
            start_latents = torch.randn(1, 4, 64, 64, device=device)
            start_latents *= pipe.scheduler.init_noise_sigma
    
        latents = start_latents.clone()
    
        for i in tqdm(range(start_step, num_inference_steps)):
        
            t = pipe.scheduler.timesteps[i]
    
            # développer les latents si l'on procÚde à un guidage sans classifieur
            latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents
            latent_model_input = pipe.scheduler.scale_model_input(latent_model_input, t)
    
            # prédire le bruit résiduel
            noise_pred = pipe.unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample
    
            #réaliser un guidage
            if do_classifier_free_guidance:
                noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
                noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
    
    
            # Normalement, nous devrions nous fier au planificateur pour gérer l'étape de mise à jour :
            # latents = pipe.scheduler.step(noise_pred, t, latents).prev_sample
    
            # Au lieu de cela, faisons-le nous-mĂȘmes :
            prev_t = max(1, t.item() - (1000//num_inference_steps)) # t-1
            alpha_t = pipe.scheduler.alphas_cumprod[t.item()]
            alpha_t_prev = pipe.scheduler.alphas_cumprod[prev_t]
            predicted_x0 = (latents - (1-alpha_t).sqrt()*noise_pred) / alpha_t.sqrt()
            direction_pointing_to_xt = (1-alpha_t_prev).sqrt()*noise_pred
            latents = alpha_t_prev.sqrt()*predicted_x0 + direction_pointing_to_xt
    
        # Post-traitement
        images = pipe.decode_latents(latents)
        images = pipe.numpy_to_pil(images)
    
        return images
    
    # Tester notre fonction d'échantillonnage en générant une image
    sample('Watercolor painting of a beach sunset', negative_prompt=negative_prompt, num_inference_steps=50)[0].resize((256, 256))
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Voyez si vous pouvez faire correspondre le code avec l’équation de l’article. Notez que $\sigma$=0 puisque nous ne nous intĂ©ressons qu’au cas oĂč il n’y a pas de bruit supplĂ©mentaire, nous pouvons donc laisser de cĂŽtĂ© ces Ă©lĂ©ments de l’équation.

    Inversion

    L’objectif est d’inverser le processus d’échantillonnage. Nous voulons obtenir un latent bruitĂ© qui, s’il est utilisĂ© comme point de dĂ©part de notre procĂ©dure d’échantillonnage habituelle, permet de gĂ©nĂ©rer l’image originale.

    Ici, nous chargeons une image comme image initiale, mais vous pouvez Ă©galement en gĂ©nĂ©rer une vous-mĂȘme pour l’utiliser Ă  la place.

    # https://www.pexels.com/photo/a-beagle-on-green-grass-field-8306128/
    input_image = load_image('https://images.pexels.com/photos/8306128/pexels-photo-8306128.jpeg', size=(512, 512))
    input_image
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Nous allons Ă©galement utiliser un prompt pour effectuer l’inversion avec l’aide d’un classifieur libre, alors entrez une description de l’image :

    input_image_prompt = "Photograph of a puppy on the grass"
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Ensuite, nous devons transformer cette image PIL en un ensemble de latents que nous utiliserons comme point de départ de notre inversion :

    # encoder avec le VAE
    with torch.no_grad(): latent = pipe.vae.encode(tfms.functional.to_tensor(input_image).unsqueeze(0).to(device)*2-1)
    l = 0.18215 * latent.latent_dist.sample()
    

    TrĂšs bien, il est temps de passer Ă  la partie amusante. Cette fonction ressemble Ă  la fonction d’échantillonnage ci-dessus, mais nous nous dĂ©plaçons Ă  travers les pas de temps dans la direction opposĂ©e, en commençant Ă  $t=0$ et en nous dĂ©plaçant vers un bruit de plus en plus Ă©levĂ©. Et au lieu de mettre Ă  jour nos latents pour qu’ils soient moins bruyants, nous estimons le bruit prĂ©dit et l’utilisons pour ANNULER une Ă©tape de mise Ă  jour, en les dĂ©plaçant de $t$ Ă  $t+1$.

    ## Inversion
    @torch.no_grad()
    def invert(start_latents, prompt, guidance_scale=3.5, num_inference_steps=80,
               num_images_per_prompt=1, do_classifier_free_guidance=True,
               negative_prompt='', device=device):
      
        # Encoder le prompt
        text_embeddings = pipe._encode_prompt(
                prompt, device, num_images_per_prompt, do_classifier_free_guidance, negative_prompt
        )
    
        # les latents sont maintenant les latents de départ spécifiés
        latents = start_latents.clone()
    
        # Nous garderons une liste des latents inversés au fur et à mesure du processus
        intermediate_latents = []
    
        # Définir le nombre d'étapes de l'inférence
        pipe.scheduler.set_timesteps(num_inference_steps, device=device)
    
        # Pas de temps inversés <<<<<<<<<<<<<<<<<<<<
        timesteps = reversed(pipe.scheduler.timesteps)
    
        for i in tqdm(range(1, num_inference_steps), total=num_inference_steps-1):
    
            # Nous allons sauter l'itération finale
            if i >= num_inference_steps - 1: continue
    
            t = timesteps[i]
    
    		# développer les latents si l'on fait de l'orientation sans classifieur
            latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents
            latent_model_input = pipe.scheduler.scale_model_input(latent_model_input, t)
    
            # prédire le bruit résiduel
            noise_pred = pipe.unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample
    
            # effectuer un guidage
            if do_classifier_free_guidance:
                noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
                noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
    
            current_t = max(0, t.item() - (1000//num_inference_steps))#t
            next_t = t # min(999, t.item() + (1000//num_inference_steps)) # t+1
            alpha_t = pipe.scheduler.alphas_cumprod[current_t]
            alpha_t_next = pipe.scheduler.alphas_cumprod[next_t]
    
            # Étape de mise Ă  jour inversĂ©e (rĂ©organisation de l'Ă©tape de mise Ă  jour pour obtenir x(t) (nouveaux latents) en fonction de x(t-1) (latents actuels)
            latents = (latents - (1-alpha_t).sqrt()*noise_pred)*(alpha_t_next.sqrt()/alpha_t.sqrt()) + (1-alpha_t_next).sqrt()*noise_pred
    
    
            # Stockage
            intermediate_latents.append(latents)
                
        return torch.cat(intermediate_latents)
    

    En l’exĂ©cutant sur la reprĂ©sentation latente de notre photo de chiot, nous obtenons un ensemble de tous les latents intermĂ©diaires créés au cours du processus d’inversion :

    inverted_latents = invert(l, input_image_prompt,num_inference_steps=50)
    inverted_latents.shape
    
    torch.Size([48, 4, 64, 64])
    

    Nous pouvons visualiser l’ensemble final de latents - ceux-ci constitueront, nous l’espĂ©rons, le point de dĂ©part bruyant de nos nouvelles tentatives d’échantillonnage :

    # Décoder les latents inversés finaux
    with torch.no_grad():
      im = pipe.decode_latents(inverted_latents[-1].unsqueeze(0))
    pipe.numpy_to_pil(im)[0]
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Vous pouvez transmettre ces latents inversés au pipeline en utilisant la méthode__call__ normale :

    pipe(input_image_prompt, latents=inverted_latents[-1][None], num_inference_steps=50, guidance_scale=3.5).images[0]
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Mais c’est lĂ  que nous voyons notre premier problĂšme : ce n’est pas tout Ă  fait l’image avec laquelle nous avons commencĂ© ! En effet, l’inversion DDIM repose sur une hypothĂšse critique selon laquelle la prĂ©diction du bruit Ă  l’instant $t$ et Ă  l’instant $t+1$ sera la mĂȘme, ce qui n’est pas vrai lorsque l’inversion ne porte que sur $50$ ou $100$ pas de temps. Nous pourrions utiliser davantage de pas de temps pour espĂ©rer obtenir une inversion plus prĂ©cise, mais nous pouvons Ă©galement tricher et commencer Ă  partir de, disons, $20/50$ pas d’échantillonnage avec les latents intermĂ©diaires correspondants que nous avons sauvegardĂ©s lors de l’inversion :

    # La raison pour laquelle nous voulons pouvoir spécifier l'étape de démarrage
    start_step=20
    sample(input_image_prompt, start_latents=inverted_latents[-(start_step+1)][None], 
           start_step=start_step, num_inference_steps=50)[0]
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    TrĂšs proche de notre image d’entrĂ©e ! Pourquoi faisons-nous cela ? Eh bien, l’espoir est que si nous Ă©chantillonnons maintenant avec un nouveau prompt, nous obtiendrons une image qui correspond Ă  l’original SAUF aux endroits pertinents pour le nouveau prompt. Par exemple, en remplaçant « puppy » par « cat », nous devrions voir un chat avec un dos et un arriĂšre-plan presque identiques :

    # Échantillonnage avec un nouveau prompt
    start_step=10
    new_prompt = input_image_prompt.replace('puppy', 'cat')
    sample(new_prompt, start_latents=inverted_latents[-(start_step+1)][None], 
           start_step=start_step, num_inference_steps=50)[0]
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Pourquoi ne pas utiliser img2img ?

    Pourquoi s’embĂȘter Ă  inverser ? Ne peut-on pas simplement ajouter du bruit Ă  l’image d’entrĂ©e et la dĂ©bruiter avec le nouveau prompt ? Nous le pouvons, mais cela entraĂźnera des changements beaucoup plus radicaux partout (si nous ajoutons beaucoup de bruit) ou des changements insuffisants partout (si nous ajoutons moins de bruit). Essayez vous-mĂȘme :

    start_step = 10
    num_inference_steps=50
    pipe.scheduler.set_timesteps(num_inference_steps)
    noisy_l = pipe.scheduler.add_noise(l, torch.randn_like(l), pipe.scheduler.timesteps[start_step])
    sample(new_prompt, start_latents=noisy_l, start_step=start_step, num_inference_steps=num_inference_steps)[0]
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Notez la modification beaucoup plus importante de la pelouse et de l’arriùre-plan.

    Rassembler le tout

    Rassemblons le code que nous avons Ă©crit jusqu’à prĂ©sent dans une fonction simple qui prend une image et deux prompts et effectue une modification en utilisant l’inversion :

    def edit(input_image, input_image_prompt, edit_prompt, num_steps=100, start_step=30, guidance_scale=3.5):
        with torch.no_grad(): latent = pipe.vae.encode(tfms.functional.to_tensor(input_image).unsqueeze(0).to(device)*2-1)
        l = 0.18215 * latent.latent_dist.sample()
        inverted_latents = invert(l, input_image_prompt,num_inference_steps=num_steps)
        final_im = sample(edit_prompt, start_latents=inverted_latents[-(start_step+1)][None], 
                          start_step=start_step, num_inference_steps=num_steps, guidance_scale=guidance_scale)[0]
        return final_im
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Et en action :

    edit(input_image, 'A puppy on the grass', 'an old grey dog on the grass', num_steps=50, start_step=10)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.
    edit(input_image, 'A puppy on the grass', 'A blue dog on the lawn', num_steps=50, start_step=12, guidance_scale=6)
    

    ✏ À votre tour ! Essayez ceci sur d’autres images ! Explorez les diffĂ©rents paramĂštres.

    Plus de pas = meilleure performance

    Si vous avez des problĂšmes avec des inversions moins prĂ©cises, vous pouvez essayer d’utiliser plus de pas (au prix d’un temps d’exĂ©cution plus long). Pour tester l’inversion, vous pouvez utiliser notre fonction d’édition avec le mĂȘme prompt :

    # Test d'inversion avec beaucoup plus d'étapes :
    edit(input_image, 'A puppy on the grass', 'A puppy on the grass', num_steps=350, start_step=1)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    C’est beaucoup mieux ! Et en essayant de l’éditer :

    edit(input_image, 'A photograph of a puppy', 'A photograph of a grey cat', num_steps=150, start_step=30, guidance_scale=5.5)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.
    # source: https://www.pexels.com/photo/girl-taking-photo-1493111/
    face = load_image('https://images.pexels.com/photos/1493111/pexels-photo-1493111.jpeg', size=(512, 512))
    face
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.
    edit(face, 'A photograph of a face', 'A photograph of a face with sunglasses', num_steps=250, start_step=30, guidance_scale=3.5)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.
    edit(face, 'A photograph of a face', 'Acrylic palette knife painting of a face, colorful', num_steps=250, start_step=65, guidance_scale=5.5)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Et ensuite ?

    ArmĂ© des connaissances de ce notebook, nous vous recommandons d’étudier Null-text Inversion qui s’appuie sur DDIM en optimisant le texte nul (prompt inconditionnel) lors de l’inversion pour des inversions plus prĂ©cises et de meilleures Ă©ditions.

    4.2. Diffusion pour l'audio

    Dans ce notebook, nous allons jeter un bref coup d’Ɠil Ă  la gĂ©nĂ©ration d’audio avec des modĂšles de diffusion. Ce que vous allez apprendre :

    • Comment l’audio est reprĂ©sentĂ© dans un ordinateur
    • Les mĂ©thodes de conversion entre les donnĂ©es audio brutes et les spectrogrammes
    • Comment prĂ©parer un chargeur de donnĂ©es avec une fonction personnalisĂ©e pour convertir des tranches d’audio en spectrogrammes
    • Finetuner un modĂšle de diffusion audio existant sur un genre de musique spĂ©cifique
    • TĂ©lĂ©charger votre pipeline personnalisĂ© sur le Hub d’Hugging Face

    Mise en garde : il s’agit principalement d’un objectif pĂ©dagogique - rien ne garantit que notre modĂšle sonnera bien 😉

    Commençons !

    Configuration et importations

    # !pip install -q datasets diffusers torchaudio accelerate
    
    import torch, random
    import numpy as np
    import torch.nn.functional as F
    from tqdm.auto import tqdm
    from IPython.display import Audio
    from matplotlib import pyplot as plt
    from diffusers import DiffusionPipeline
    from torchaudio import transforms as AT
    from torchvision import transforms as IT
    

    Echantillonnage Ă  partir d’un pipeline audio prĂ©-entraĂźnĂ©

    Commençons par suivre la documentation pour charger un modÚle de diffusion audio préexistant :

    # Chargement d'un pipeline de diffusion audio pré-entraßné
    device = "cuda" if torch.cuda.is_available() else "cpu"
    pipe = DiffusionPipeline.from_pretrained("teticio/audio-diffusion-instrumental-hiphop-256").to(device)
    

    Comme pour les pipelines que nous avons utilisés dans les unités précédentes, nous pouvons créer des échantillons en appelant le pipeline comme suit :

    # Échantillonner Ă  partir du pipeline et afficher les rĂ©sultats
    output = pipe()
    display(output.images[0])
    display(Audio(output.audios[0], rate=pipe.mel.get_sample_rate()))
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Ici, l’argument rate spĂ©cifie la frĂ©quence d’échantillonnage de l’audio ; nous y reviendrons plus tard. Vous remarquerez Ă©galement que le pipeline renvoie plusieurs choses. Que se passe-t-il ici ? Examinons de plus prĂšs les deux sorties.

    La premiĂšre est un tableau de donnĂ©es, reprĂ©sentant l’audio gĂ©nĂ©rĂ© :

    # Le tableau audio :
    output.audios[0].shape
    
    (1, 130560)
    

    La seconde ressemble Ă  une image en niveaux de gris :

    # L'image de sortie (spectrogramme)
    output.images[0].size
    
    (256, 256)
    

    Cela nous donne un aperçu du fonctionnement de ce pipeline. L’audio n’est pas directement gĂ©nĂ©rĂ© par diffusion. Au lieu de cela, le pipeline a le mĂȘme type d’UNet 2D que les pipelines de gĂ©nĂ©ration d’images inconditionnelles que nous avons vus dans l’unitĂ© 1, qui est utilisĂ© pour gĂ©nĂ©rer le spectrogramme, qui est ensuite post-traitĂ© dans l’audio final.

    Le pipeline possÚde un composant supplémentaire qui gÚre ces conversions, auquel nous pouvons accéder via pipe.mel :

    pipe.mel
    
    Mel {
      "_class_name": "Mel",
      "_diffusers_version": "0.12.0.dev0",
      "hop_length": 512,
      "n_fft": 2048,
      "n_iter": 32,
      "sample_rate": 22050,
      "top_db": 80,
      "x_res": 256,
      "y_res": 256
    }
    

    De l’audio à l’image et inversement

    Une « forme d’onde » encode les Ă©chantillons audio bruts dans le temps. Il peut s’agir du signal Ă©lectrique reçu d’un microphone, par exemple. Travailler avec cette reprĂ©sentation du « domaine temporel » peut s’avĂ©rer dĂ©licat, c’est pourquoi il est courant de la convertir sous une autre forme, communĂ©ment appelĂ©e spectrogramme. Un spectrogramme montre l’intensitĂ© de diffĂ©rentes frĂ©quences (axe y) en fonction du temps (axe x) :

    # Calculer et afficher un spectrogramme pour notre échantillon audio généré en utilisant torchaudio
    spec_transform = AT.Spectrogram(power=2)
    spectrogram = spec_transform(torch.tensor(output.audios[0]))
    print(spectrogram.min(), spectrogram.max())
    log_spectrogram = spectrogram.log()
    plt.imshow(log_spectrogram[0], cmap='gray');
    
    tensor(0.) tensor(6.0842)
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Le spectrogramme que nous venons de crĂ©er contient des valeurs comprises entre 0,0000000000001 et 1, la plupart d’entre elles Ă©tant proches de la limite infĂ©rieure de cette plage. Ce n’est pas l’idĂ©al pour la visualisation ou la modĂ©lisation. En fait, nous avons dĂ» prendre le logarithme de ces valeurs pour obtenir un tracĂ© en niveaux de gris qui montre des dĂ©tails. Pour cette raison, nous utilisons gĂ©nĂ©ralement un type spĂ©cial de spectrogramme appelĂ© Mel spectrogramme, qui est conçu pour capturer les types d’informations qui sont importantes pour l’audition humaine en appliquant certaines transformations aux diffĂ©rentes composantes de frĂ©quence du signal.

    Quelques transformations audio de la documentation torchaudio

    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Heureusement pour nous, nous n’avons pas besoin de nous prĂ©occuper de ces transformations, la fonctionnalitĂ© mel du pipeline s’occupe de ces dĂ©tails pour nous. En l’utilisant, nous pouvons convertir une image de spectrogramme en audio comme suit :

    a = pipe.mel.image_to_audio(output.images[0])
    a.shape
    
    (130560,)
    

    Nous pouvons Ă©galement convertir un tableau de donnĂ©es audio en images de spectrogramme en chargeant d’abord les donnĂ©es audio brutes, puis en appelant la fonction audio_slice_to_image(). Les clips plus longs sont automatiquement dĂ©coupĂ©s en morceaux de la bonne longueur pour produire une image de spectrogramme de 256x256 :

    pipe.mel.load_audio(raw_audio=a)
    im = pipe.mel.audio_slice_to_image(0)
    im
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    L’audio est reprĂ©sentĂ© sous la forme d’un long tableau de nombres. Pour l’écouter nous avons besoin d’une autre information clĂ© : la frĂ©quence d’échantillonnage. Combien d’échantillons (valeurs individuelles) utilisons-nous pour reprĂ©senter une seconde d’audio ?

    Nous pouvons voir la frĂ©quence d’échantillonnage utilisĂ©e lors de l’entraĂźnement de ce pipeline avec :

    sample_rate_pipeline = pipe.mel.get_sample_rate()
    sample_rate_pipeline
    
    22050
    

    Si nous spĂ©cifions mal la frĂ©quence d’échantillonnage, nous obtenons un son accĂ©lĂ©rĂ© ou ralenti :

    display(Audio(output.audios[0], rate=44100)) # Vitesse x2
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Finetuning du pipeline

    Maintenant que nous avons une compréhension approximative du fonctionnement du pipeline, nous allons le finetuner sur de nouvelles données audio !

    Le jeu de données est une collection de clips audio de différents genres, que nous pouvons charger depuis le Hub de la maniÚre suivante :

    from datasets import load_dataset
    dataset = load_dataset('lewtun/music_genres', split='train')
    dataset
    
    Dataset({
        features: ['audio', 'song_id', 'genre_id', 'genre'],
        num_rows: 19909
    })
    

    Vous pouvez utiliser le code ci-dessous pour voir les diffĂ©rents genres dans le jeu de donnĂ©es et combien d’échantillons sont contenus dans chacun d’eux :

    for g in list(set(dataset['genre'])):
      print(g, sum(x==g for x in dataset['genre']))
    
    Pop 945
    Blues 58
    Punk 2582
    Old-Time / Historic 408
    Experimental 1800
    Folk 1214
    Electronic 3071
    Spoken 94
    Classical 495
    Country 142
    Instrumental 1044
    Chiptune / Glitch 1181
    International 814
    Ambient Electronic 796
    Jazz 306
    Soul-RnB 94
    Hip-Hop 1757
    Easy Listening 13
    Rock 3095
    

    Le jeu de données contient les données audio sous forme de tableaux :

    audio_array = dataset[0]['audio']['array']
    sample_rate_dataset = dataset[0]['audio']['sampling_rate']
    print('Audio array shape:', audio_array.shape)
    print('Sample rate:', sample_rate_dataset)
    display(Audio(audio_array, rate=sample_rate_dataset))
    
    Audio array shape: (1323119,)
    Sample rate: 44100
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Notez que la frĂ©quence d’échantillonnage de cet audio est plus Ă©levĂ©e. Si nous voulons utiliser le pipeline existant, nous devrons le « rééchantillonner » pour qu’il corresponde Ă  la frĂ©quence d’échantillonnage. Les clips sont Ă©galement plus longs que ceux pour lesquels le pipeline est configurĂ©. Heureusement, lorsque nous chargeons l’audio Ă  l’aide de pipe.mel, il dĂ©coupe automatiquement le clip en sections plus petites :

    a = dataset[0]['audio']['array'] # Obtenir le tableau audio
    pipe.mel.load_audio(raw_audio=a) # Le charger avec pipe.mel
    pipe.mel.audio_slice_to_image(0) # Visualiser la premiĂšre "tranche" sous forme de spectrogramme
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Nous devons penser Ă  ajuster le taux d’échantillonnage, car les donnĂ©es de ce jeu de donnĂ©es comportent deux fois plus d’échantillons par seconde :

    sample_rate_dataset = dataset[0]['audio']['sampling_rate']
    sample_rate_dataset
    
    44100
    

    Ici, nous utilisons les transformations de torchaudio (importĂ©es sous le nom AT) pour effectuer le rééchantillonnage, le pipeline mel pour transformer l’audio en image et les transformations de torchvision (importĂ©es sous le nom IT) pour transformer les images en tenseurs. Nous obtenons ainsi une fonction qui transforme un clip audio en un tenseur de spectrogramme que nous pouvons utiliser pour nous entraĂźner :

    resampler = AT.Resample(sample_rate_dataset, sample_rate_pipeline, dtype=torch.float32)
    to_t = IT.ToTensor()
    
    def to_image(audio_array):
      audio_tensor = torch.tensor(audio_array).to(torch.float32)
      audio_tensor = resampler(audio_tensor)
      pipe.mel.load_audio(raw_audio=np.array(audio_tensor))
      num_slices = pipe.mel.get_number_of_slices()
      slice_idx = random.randint(0, num_slices-1) # Piocher une tranche aléatoire à chaque fois (à l'exception de la derniÚre tranche courte)
      im = pipe.mel.audio_slice_to_image(slice_idx) 
      return im
    

    Nous utiliserons notre fonction to_image() dans le cadre d’une fonction collate personnalisĂ©e pour transformer notre jeu de donnĂ©es en un chargeur de donnĂ©es utilisable pour l’entraĂźnement. La fonction collate dĂ©finit la maniĂšre de transformer un batch d’exemples du jeu de donnĂ©es en un batch final de donnĂ©es prĂȘtes Ă  ĂȘtre entraĂźnĂ©es. Dans ce cas, nous transformons chaque Ă©chantillon audio en une image de spectrogramme et nous empilons les tenseurs rĂ©sultants :

    def collate_fn(examples):
      # vers l'image -> vers le tenseur -> redimensionnement vers (-1, 1) -> empiler dans le batch
      audio_ims = [to_t(to_image(x['audio']['array']))*2-1 for x in examples]
      return torch.stack(audio_ims)
    
    # Créer un jeu de données avec uniquement le genre de chansons 'Chiptune / Glitch'
    batch_size=4 # 4 sur Colab, 12 sur A100
    chosen_genre = 'Electronic' # <<< Essayer d'entraßner sur des genres différents <<<
    indexes = [i for i, g in enumerate(dataset['genre']) if g == chosen_genre]
    filtered_dataset = dataset.select(indexes)
    dl = torch.utils.data.DataLoader(filtered_dataset.shuffle(), batch_size=batch_size, collate_fn=collate_fn, shuffle=True)
    batch = next(iter(dl))
    print(batch.shape)
    
    torch.Size([4, 1, 256, 256])
    

    NB : Vous devrez utiliser une taille de batch infĂ©rieure (par exemple 4) Ă  moins que vous ne disposiez d’une grande quantitĂ© de vRAM GPU.

    Boucle d’entraünement

    Voici une boucle d’entraĂźnement simple qui s’exĂ©cute Ă  travers le chargeur de donnĂ©es pour quelques Ă©poques afin de finetuner le pipeline UNet. Vous pouvez Ă©galement ignorer cette cellule et charger le pipeline avec le code de la cellule suivante.

    epochs = 3
    lr = 1e-4
    
    pipe.unet.train()
    pipe.scheduler.set_timesteps(1000)
    optimizer = torch.optim.AdamW(pipe.unet.parameters(), lr=lr)
    
    for epoch in range(epochs):
        for step, batch in tqdm(enumerate(dl), total=len(dl)):
            
            # Préparer les images d'entrée
            clean_images = batch.to(device)
            bs = clean_images.shape[0]
    
            # Échantillonner un pas de temps alĂ©atoire pour chaque image
            timesteps = torch.randint(
                0, pipe.scheduler.num_train_timesteps, (bs,), device=clean_images.device
            ).long()
    
            # Ajouter du bruit aux images propres en fonction de l'ampleur du bruit à chaque étape
            noise = torch.randn(clean_images.shape).to(clean_images.device)
            noisy_images = pipe.scheduler.add_noise(clean_images, noise, timesteps)
    
            # Obtenir la prédiction du modÚle
            noise_pred = pipe.unet(noisy_images, timesteps, return_dict=False)[0]
    
            # Calculer la perte
            loss = F.mse_loss(noise_pred, noise)
            loss.backward(loss)
    
            # Mise Ă  jour des paramĂštres du modĂšle Ă  l'aide de l'optimiseur
            optimizer.step()
            optimizer.zero_grad()
    
    # OU : Charger la version entraßnée précédemment
    pipe = DiffusionPipeline.from_pretrained("johnowhitaker/Electronic_test").to(device)
    
    output = pipe()
    display(output.images[0])
    display(Audio(output.audios[0], rate=22050))
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.
    # Créer un échantillon plus long en passant un tenseur de bruit de départ avec une forme différente
    noise = torch.randn(1, 1, pipe.unet.sample_size[0],pipe.unet.sample_size[1]*4).to(device)
    output = pipe(noise=noise)
    display(output.images[0])
    display(Audio(output.audios[0], rate=22050))
    
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.
    Bref aperçu du contenu du cours. Bref aperçu des différents chapitres du cours.

    Ce ne sont pas les rĂ©sultats les plus impressionnants mais c’est un dĂ©but :) Essayez d’ajuster le taux d’apprentissage et le nombre d’époques, et partagez vos meilleurs rĂ©sultats sur Discord pour que nous puissions nous amĂ©liorer ensemble !

    Quelques éléments à prendre en compte

    • Nous travaillons avec des images de spectrogrammes carrĂ©s de 256 pixels ce qui limite la taille de nos batchs. Pouvez-vous rĂ©cupĂ©rer de l’audio de qualitĂ© suffisante Ă  partir d’un spectrogramme de 128x128 ?
    • Au lieu d’une augmentation alĂ©atoire de l’image, nous choisissons Ă  chaque fois des tranches diffĂ©rentes du clip audio, mais cela pourrait-il ĂȘtre amĂ©liorĂ© avec diffĂ©rents types d’augmentation lorsque l’on s’entraĂźne pendant de nombreuses Ă©poques ?
    • Comment pourrions-nous utiliser cette mĂ©thode pour gĂ©nĂ©rer des clips plus longs ? Peut-ĂȘtre pourriez-vous gĂ©nĂ©rer un clip de dĂ©part de 5 secondes, puis utiliser des idĂ©es inspirĂ©es de la complĂ©tion d’images (inpainting) pour continuer Ă  gĂ©nĂ©rer des segments audio supplĂ©mentaires Ă  partir du clip initial

    • Quel est l’équivalent d’une image Ă  image dans ce contexte de diffusion de spectrogrammes ?

    Pousser sur le Hub

    Une fois que vous ĂȘtes satisfait de votre modĂšle, vous pouvez le sauvegarder et le transfĂ©rer sur le Hub pour que d’autres personnes puissent en profiter :

    from huggingface_hub import get_full_repo_name, HfApi, create_repo, ModelCard
    
    # Choisir un nom pour le modĂšle
    model_name = "audio-diffusion-electronic"
    hub_model_id = get_full_repo_name(model_name)
    
    # Sauvegarder le pipeline localement
    pipe.save_pretrained(model_name)
    
    # Inspecter le contenu du dossier
    !ls {model_name}
    
    mel  model_index.json  scheduler  unet
    
    # Créer un dépÎt
    create_repo(hub_model_id)
    
    # Télécharger les fichiers
    api = HfApi()
    api.upload_folder(
        folder_path=f"{model_name}/scheduler", path_in_repo="scheduler", repo_id=hub_model_id
    )
    api.upload_folder(
        folder_path=f"{model_name}/mel", path_in_repo="mel", repo_id=hub_model_id
    )
    api.upload_folder(folder_path=f"{model_name}/unet", path_in_repo="unet", repo_id=hub_model_id)
    api.upload_file(
        path_or_fileobj=f"{model_name}/model_index.json",
        path_in_repo="model_index.json",
        repo_id=hub_model_id,
    )
    
    # Pousser une carte de modĂšle
    content = f"""
    ---
    license: mit
    tags:
    - pytorch
    - diffusers
    - unconditional-audio-generation
    - diffusion-models-class
    ---
    
    # Model Card for Unit 4 of the [Diffusion Models Class 🧹](https://github.com/huggingface/diffusion-models-class)
    
    This model is a diffusion model for unconditional audio generation of music in the genre {chosen_genre}
    
    ## Usage
    
    ```python
    from IPython.display import Audio
    from diffusers import DiffusionPipeline
    
    pipe = DiffusionPipeline.from_pretrained("{hub_model_id}")
    output = pipe()
    display(output.images[0])
    display(Audio(output.audios[0], rate=pipe.mel.get_sample_rate()))
    ```python
    """
    
    card = ModelCard(content)
    card.push_to_hub(hub_model_id)
    

    Conclusion

    Ce notebook vous a donnĂ©, nous l’espĂ©rons, un petit aperçu du potentiel de la gĂ©nĂ©ration audio. Consultez certaines des rĂ©fĂ©rences liĂ©es Ă  la vue d’ensemble de cette unitĂ© pour voir des mĂ©thodes plus fantaisistes et des Ă©chantillons stupĂ©fiants qu’elles peuvent crĂ©er !

    5. ÉvĂ©nement pour la sortie du cours

    Pour accompagner la sortie du cours, nous organisons un Ă©vĂ©nement communautaire en direct le 30 novembre 2022 auquel vous ĂȘtes conviĂ©s ! Au programme, des interventions passionnantes des crĂ©ateurs de Stable Diffusion, des chercheurs de Stability AI et de Meta, et bien d’autres encore !

    Les interventions se concentreront sur une présentation de haut niveau des modÚles de diffusion et des outils permettant de créer des applications.

    Intelligence collective et IA créative par David Ha
    David Ha est responsable de la stratĂ©gie chez Stability AI. Auparavant, il a travaillĂ© comme chercheur chez Google, au sein de l’équipe Brain au Japon. Ses recherches portent sur les systĂšmes complexes, l’auto-organisation et les applications crĂ©atives de l’apprentissage automatique. Avant de rejoindre Google, il a travaillĂ© chez Goldman Sachs en tant que Managing Director, oĂč il a codirigĂ© les activitĂ©s de nĂ©gociation de titres Ă  revenu fixe au Japon. Il a obtenu une licence et une maĂźtrise Ă  l’universitĂ© de Toronto, ainsi qu’un doctorat Ă  l’universitĂ© de Tokyo. Vous pouvez le trouver sur Twitter ou sur son site personnel.

    IA pour augmenter la créativité humaine par Devi Parikh
    Devi Parikh est directrice de recherche au laboratoire Fundamental AI Research (FAIR) de Meta et professeur associĂ© Ă  l’école d’informatique interactive de Georgia Tech. Elle a occupĂ© des postes d’intervenante Ă  l’universitĂ© Cornell, Ă  l’universitĂ© du Texas Ă  Austin, Ă  Microsoft Research, au MIT, Ă  l’universitĂ© Carnegie Mellon et Ă  Facebook AI Research. Elle a obtenu sa maĂźtrise et son doctorat du dĂ©partement d’ingĂ©nierie Ă©lectrique et informatique de l’universitĂ© Carnegie Mellon en 2007 et 2009 respectivement. Ses recherches portent sur la vision artificielle, le traitement du langage naturel, l’intelligence artificielle incarnĂ©e, la collaboration entre l’homme et l’intelligence artificielle et l’intelligence artificielle au service de la crĂ©ativitĂ©. Vous pouvez la trouver sur Twitter ou sur son site personnel.

    Nourriture pour Diffusion par Patrick Esser
    Patrick Esser est chercheur principal chez Runway, oĂč il dirige les efforts de recherche appliquĂ©e, notamment le modĂšle de base de Stable Diffusion, Ă©galement connu sous le nom de High-Resolution Image Synthesis with Latent Diffusion Models. Vous pouvez le trouver sur Twitter.

    Au-delĂ  du texte : Donner de nouvelles capacitĂ©s Ă  Stable Diffusion par Justin Pinkney Justin est chercheur senior en apprentissage automatique chez Lambda Labs. Il travaille sur la gĂ©nĂ©ration et l’édition d’images, en particulier pour les applications artistiques et crĂ©atives. Il adore jouer et bidouiller des modĂšles prĂ©-entraĂźnĂ©s pour leur ajouter de nouvelles capacitĂ©s, et est probablement mieux connu pour des modĂšles comme : Toonify, Stable Diffusion Image Variations, et Text-to-Pokemon. Vous pouvez le trouver sur Twitter ou sur son site personnel.

    Les modùles de diffusion sont cool mais qu’arrive t’il aprùs aprùs l’engouement ? par Apolinário Passos
    ApolinĂĄrio Passos est un ingĂ©nieur en apprentissage automatique chez Hugging Face et un artiste qui se concentre sur l’art gĂ©nĂ©ratif et les mĂ©dias gĂ©nĂ©ratifs. Il a fondĂ© la plateforme multimodal.art et le compte Twitter correspondant, et travaille sur l’organisation, l’agrĂ©gation et la plateformisation des modĂšles d’apprentissage automatique des mĂ©dias gĂ©nĂ©ratifs open-source. Vous pouvez le trouver sur Twitter.

    Stable Diffusion et amis : SynthĂšse d’images en haute rĂ©solution via des modĂšles gĂ©nĂ©ratifs en deux Ă©tapes par Robin Rombach
    Robin est chercheur Ă  Stability AI. AprĂšs avoir Ă©tudiĂ© la physique Ă  l’UniversitĂ© de Heidelberg de 2013 Ă  2020, il a commencĂ© un doctorat en informatique dans le groupe Computer Vision Ă  Heidelberg en 2020 sous la supervision de Björn Ommer et a dĂ©mĂ©nagĂ© Ă  LMU Munich avec le groupe de recherche en 2021. Ses recherches portent sur les modĂšles gĂ©nĂ©ratifs d’apprentissage profond, en particulier les systĂšmes texte-image. Au cours de son doctorat, Robin a jouĂ© un rĂŽle dĂ©terminant dans le dĂ©veloppement et la publication de plusieurs projets dĂ©sormais largement utilisĂ©s, tels que VQGAN et Taming Transformers, et Latent Diffusion Models. En collaboration avec Stability AI, Robin a mis Ă  l’échelle l’approche de diffusion latente et a publiĂ© une sĂ©rie de modĂšles maintenant connus sous le nom de Stable Diffusion, qui ont Ă©tĂ© largement adaptĂ©s par la communautĂ©. Vous pouvez le trouver sur Twitter.

    5.1. Hackathon DreamBooth 🏆

    📣 Le hackathon est maintenant terminĂ© et les gagnants ont Ă©tĂ© annoncĂ©s sur Discord. Vous ĂȘtes toujours invitĂ©s Ă  entraĂźner des modĂšles et Ă  les soumettre au classement, mais nous n’offrirons plus de prix ou de certificats pour le moment.

    Bienvenue au Hackathon DreamBooth !
    C’est un Ă©vĂ©nement communautaire oĂč vous personnaliserez un modĂšle de Stable Diffusion en le finetunant sur une poignĂ©e de vos images. Pour ce faire, vous utiliserez une technique puissante appelĂ©e DreamBooth, qui permet d’implanter un sujet (par exemple votre animal de compagnie ou votre plat prĂ©fĂ©rĂ©) dans le domaine de sortie du modĂšle de sorte qu’il puisse ĂȘtre synthĂ©tisĂ© avec un unique identifiant dans le prompt.

    Cette compĂ©tition est composĂ©e de 5 thĂšmes, oĂč chacun rassemblera des modĂšles appartenant aux catĂ©gories suivantes :

    • Animal 🐹 : Utilisez ce thĂšme pour gĂ©nĂ©rer des images de votre animal de compagnie ou de votre animal favori en train de se promener dans l’Acropole, de nager ou de voler dans l’espace.
    • Science 🔬 : Utilisez ce thĂšme pour gĂ©nĂ©rer des images synthĂ©tiques de galaxies, de protĂ©ines ou de tout autre domaine des sciences naturelles et mĂ©dicales.
    • Nourriture 🍔 : Utilisez ce thĂšme pour finetuner un modĂšle sur votre plat ou cuisine prĂ©fĂ©rĂ©.
    • Paysage 🏔 : Utilisez ce thĂšme pour crĂ©er de magnifiques paysages de votre montagne, lac ou jardin prĂ©fĂ©rĂ©.
    • Carte blanche đŸ”„ : Utilisez ce thĂšme pour crĂ©er des modĂšles dans la catĂ©gorie de votre choix !

    Nous attribuerons des prix aux 3 modĂšles les plus apprĂ©ciĂ©s par thĂšme, et vous ĂȘtes encouragĂ©s Ă  soumettre autant de modĂšles que vous le souhaitez !

    Pour commencer

    Suivez les étapes ci-dessous pour participer à cet événement :

    1. Rejoignez le serveur Discord d’Hugging Face et consultez le canal #dreambooth-hackathon pour vous tenir au courant du dĂ©roulement de l’évĂ©nement.
    2. Lancez et exĂ©cutez le notebook pour entraĂźner vos modĂšles en cliquant sur l’un des liens ci-dessous. Veillez Ă  sĂ©lectionner l’exĂ©cution sur GPU pour vous assurer que vos modĂšles s’entraĂźnent rapidement !
    Notebook Colab Kaggle Gradient Studio Lab
    EntraĂźnement de DreamBooth Open In Colab Kaggle Gradient Open In SageMaker Studio Lab

    Note 👋 :
    Le notebook DreamBooth utilise le checkpoint CompVis/stable-diffusion-v1-4 comme modĂšle de Diffusion Stable Ă  finetuner. Cependant, vous ĂȘtes totalement libre d’utiliser n’importe quel checkpoint, il vous suffira d’ajuster le code pour charger les composants appropriĂ©s et le vĂ©rificateur de sĂ©curitĂ© (s’il existe). Voici quelques modĂšles intĂ©ressants Ă  finetuner :

    • runwayml/stable-diffusion-v1-5
    • prompthero/openjourney
    • stabilityai/stable-diffusion-2
    • hakurei/waifu-diffusion
    • stabilityai/stable-diffusion-2-1
    • nitrosocke/elden-ring-diffusion

    Evaluation & Classement

    Pour ĂȘtre dans la course aux prix, poussez un ou plusieurs modĂšles DreamBooth sur le Hub avec le tag dreambooth-hackathon dans la carte du modĂšle (exemple). Ce tag est créé automatiquement par le notebook de DreamBooth, mais vous devrez l’ajouter si vous exĂ©cutez vos propres scripts.

    Les modĂšles sont Ă©valuĂ©s en fonction du nombre de j’aimes et vous pouvez suivre le classement de votre modĂšle sur le tableau de classement du hackathon :

    • Classement DreamBooth

    Chronologie

    • 21 dĂ©cembre 2022 : Date de dĂ©marrage
    • 31 dĂ©cembre 2022 : Date limite d’inscription Ă  Colab Pro
    • 22 janvier 2023 : Date limite de soumission (clĂŽture du classement)
    • 23-27 janvier 2023 : Annonce des gagnants de chaque thĂšme

    Tous les délais sont fixés à 23h59 UTC le jour correspondant, sauf indication contraire.

    Prix

    Nous attribuerons 3 prix par thĂšme, les gagnants Ă©tant dĂ©terminĂ©s par les modĂšles ayant le plus de j’aimes sur le tableau de classement :

    1Ăšre place

    • Abonnement Hugging Face Pro pour 1 an ou un bon d’achat de 100 $ pour le magasin de produits dĂ©rivĂ©s d’Hugging Face

    2Ăšme place

    • Une copie du livre NLP with Transformers ou un bon d’achat de 50 $ pour le magasin de produits dĂ©rivĂ©s d’Hugging Face

    3Ăšme place

    • Abonnement Hugging Face Pro pour 1 mois ou un bon d’achat de 15 $ pour le magasin de produits dĂ©rivĂ©s d’Hugging Face

    Nous fournirons Ă©galement un certificat d’achĂšvement Ă  tous les participants qui soumettront au moins 1 modĂšle DreamBooth au hackathon đŸ”„.

    Calculs

    Google Colab sponsorisera cet Ă©vĂ©nement en offrant des crĂ©dits Colab Pro Ă  100 participants (sĂ©lectionnĂ©s au hasard). Les crĂ©dits seront distribuĂ©s en janvier 2023 et vous avez jusqu’au 31 dĂ©cembre pour vous inscrire. Pour vous inscrire, veuillez remplir ce formulaire.

    FAQ

    Quelles sont les données autorisées pour le finetuning ?

    Vous pouvez utiliser toutes les images qui vous appartiennent ou pour lesquelles une licence permissive vous autorise Ă  le faire. Si vous souhaitez soumettre un modĂšle entraĂźnĂ© sur des visages (par exemple dans le cadre d’une carte blanche), nous vous recommandons d’utiliser vos propres traits. IdĂ©alement, utilisez vos propres donnĂ©es lorsque vous le pouvez - nous aimerions voir vos animaux de compagnie ou vos paysages locaux prĂ©fĂ©rĂ©s, et nous soupçonnons que les j’aimes et les prix iront Ă  ceux qui font quelque chose de gentil et de personnel 😁.

    D’autres techniques de finetuning comme l’inversion textuelle sont-elles autorisĂ©es ?

    Absolument ! Bien que ce hackathon soit centrĂ© sur DreamBooth, vous ĂȘtes les bienvenus (et encouragĂ©s) Ă  expĂ©rimenter d’autres techniques de finetuning. Cela signifie aussi que vous pouvez utiliser n’importe quel framework, code ou service qui vous aide Ă  crĂ©er des modĂšles magnifiques pour que la communautĂ© puisse en profiter đŸ„°.

    5.2. Sprint Dreambooth en Keras

    Cette paget rĂ©sume toutes les informations pertinentes requises pour l’évĂ©nement. 📋.

    Introduction

    Dreambooth est une technique de finetuning permettant d’enseigner de nouveaux concepts visuels Ă  des modĂšles de diffusion conditionnĂ©s par le texte en utilisant seulement 3 Ă  5 images. Avec Dreambooth, vous pouvez gĂ©nĂ©rer des images drĂŽles et rĂ©alistes de votre chien, de vous-mĂȘme et de n’importe quel concept avec quelques images en utilisant Stable Diffusion. DreamBooth a Ă©tĂ© proposĂ© dans DreamBooth : Fine Tuning Text-to-Image Diffusion Models for Subject-Driven Generation par Ruiz et al. (2022)

    Nous allons entraĂźner les modĂšles Dreambooth Ă  l’aide de KerasCV et construire des dĂ©monstrations sur ces modĂšles.

    Dates importantes

    • Lancement de l’évĂ©nement : 6 mars 2023
      Avec Nataniel Ruiz sur DreamBooth, François Chollet sur KerasCV et Apolinario Passos sur đŸ€— Diffusers
    • DĂ©but du sprint : 7 mars 2023
    • Fin du sprint : 1er avril 2023
    • RĂ©sultats : 7 avril 2023

    DĂ©marrer 🚀

    Pour commencer, rejoignez-nous sur hf.co/join/discord et prenez le rĂŽle #open-source, et rencontrez-nous sur le canal #keras-working-group.

    Nous hĂ©bergerons nos dĂ©monstrations dans cette organisation sur Hugging Face Hub : keras-dreambooth, envoyez une demande ici si vous souhaitez soumettre une proposition 🙂

    Nous allons :

    1. Finetuner Stable Diffusion sur n’importe quel concept que nous voulons en utilisant Dreambooth,
    2. Pousser le modùle vers le Hub d’Hugging Face,
    3. Remplir la carte du modĂšle,
    4. Construire une démo à partir du modÚle.

    Avertissement: Les modĂšles entraĂźnĂ©s doivent ĂȘtre dans l’une des 4 catĂ©gories mentionnĂ©es dans la section Soumission. Veuillez y jeter un coup d’Ɠil avant d’entraĂźner votre modĂšle.

    EntraĂźnement du modĂšle

    Vous pouvez trouver le notebook ici (en anglais) et l’adapter Ă  votre propre jeu de donnĂ©es.

    Quelques inspirations pour le finetuning :

    1. Lowpoly World : Ce modĂšle gĂ©nĂšre des mondes low poly đŸ€ŻđŸŒ
    2. Future Diffusion : Ce modĂšle gĂ©nĂšre des images dans des concepts de science-fiction futuristes đŸ€–
    3. Fantasy sword : Ce modĂšle gĂ©nĂšre des Ă©pĂ©es pour des jeux Ă  thĂšme fantastique đŸ§™â€â™‚ïž

    Si vous avez besoin de plus d’indications sur l’implĂ©mentation de Dreambooth avec Keras, vous pouvez consulter ce dĂ©pĂŽt.

    Dreambooth avec KerasCV

    Pour l’instant, les options d’infĂ©rence et de dĂ©ploiement de KerasCV sont limitĂ©es, et c’est lĂ  que la bibliothĂšque diffusers vient Ă  la rescousse. Avec seulement quelques lignes de code, nous pouvons convertir un modĂšle KerasCV en un modĂšle diffusers et utiliser les pipelines diffusers pour effectuer l’infĂ©rence. Vous pouvez obtenir plus d’informations ici. Consultez aussi ce Space pour convertir votre modĂšle KerasCV en un modĂšle diffusers.

    Les dĂ©pĂŽts diffusers sur le Hub bĂ©nĂ©ficient d’une API d’infĂ©rence gratuite et de petits widgets dans la page du modĂšle oĂč les utilisateurs peuvent jouer avec le modĂšle.

    from diffusers import StableDiffusionPipeline
    
    # point de contrĂŽle de Stable Diffusion converti de KerasCV
    model_ckpt = "sayakpaul/text-unet-dogs-kerascv_sd_diffusers_pipeline"
    pipeline = StableDiffusionPipeline.from_pretrained(model_ckpt)
    pipeline.to("cuda")
    
    unique_id = "sks"
    class_label = "dog"
    prompt = f"A photo of {unique_id} {class_label} in a bucket"
    image = pipeline(prompt, num_inference_steps=50).images[0]
    

    Hébergement du modÚle

    À la fin du notebook vous verrez une section dĂ©diĂ©e Ă  l’hĂ©bergement, et une section sĂ©parĂ©e pour l’infĂ©rence. Nous utiliserons les fonctions de chargement et de poussĂ©e de modĂšles spĂ©cifiques Ă  Keras de la bibliothĂšque huggingface_hub : push_to_hub_keras et from_pretrained_keras. Nous allons d’abord pousser le modĂšle en utilisant push_to_hub_keras. Une fois le modĂšle poussĂ©, vous verrez que le modĂšle est hĂ©bergĂ© avec une carte de modĂšle comme ci-dessous :

    Référentiel

    Pour mieux versionner les modĂšles, permettre la dĂ©couvrabilitĂ© et la reproductibilitĂ©, nous allons remplir la carte de modĂšle. Cliquez sur *Edit model card*. Nous allons d’abord remplir la section Metadata de la fiche de modĂšle. Si votre modĂšle est entraĂźnĂ© avec un jeu de donnĂ©es du Hub, vous pouvez remplir la section des jeux de donnĂ©es avec le jeu de donnĂ©es. Nous allons remplir pipeline_tag avec text-to-image et choisir une licence pour notre modĂšle.

    Métadonnées

    Ensuite, nous remplirons la partie markdown. Les hyperparamĂštres et le graphe sont automatiquement gĂ©nĂ©rĂ©s, nous pouvons donc Ă©crire une courte explication pour la description, l’utilisation prĂ©vue et le jeu de donnĂ©es.

    Vous pouvez trouver l’exemple de dĂ©pĂŽt ci-dessous ici.

    Démo

    Nous allons utiliser Gradio pour construire nos dĂ©monstrations pour les modĂšles que nous avons entraĂźnĂ©s. Avec la classe Interface, c’est simple :

    from huggingface_hub import from_pretrained_keras
    from keras_cv import models
    import gradio as gr
    
    sd_dreambooth_model = models.StableDiffusion(
        img_width=512, img_height=512
    )
    db_diffusion_model = from_pretrained_keras("merve/dreambooth_diffusion_model")
    sd_dreambooth_model._diffusion_model = db_diffusion_model
    
    # générer des images
    def infer(prompt):
        generated_images = sd_dreambooth_model.text_to_image(
            prompt
        )
        return generated_images 
        
        
    output = gr.Gallery(label="Outputs").style(grid=(2,2))
    
    # la fonction de passage, le type d'entrée pour le prompt, la sortie pour les images multiples
    gr.Interface(infer, inputs=["text"], outputs=[output]).launch()
    

    Vous pouvez consulter le fichier app.py de l’application ci-dessous et le rĂ©utiliser pour votre modĂšle !

    Dreambooth Submission - a Hugging Face Space par keras-dreambooth

    Cette application gĂ©nĂšre des images d’un corgi đŸ¶

    Dreambooth App

    Hébergement de la démonstration sur Spaces

    Une fois notre application terminĂ©e, nous pouvons crĂ©er un Space sur Hugging Face pour hĂ©berger notre application. Vous pouvez aller sur huggingface.co, cliquer sur votre profil en haut Ă  droite et sĂ©lectionner “New Space”.

    New Space

    Nous pouvons nommer notre Space, choisir une licence et sélectionner « Gradio » comme Space SDK.

    Space Configuration

    AprĂšs avoir créé le Space, vous pouvez utiliser soit les instructions ci-dessous pour cloner le dĂ©pĂŽt localement, ajouter vos fichiers et pousser, OU, l’interface graphique pour crĂ©er les fichiers et Ă©crire le code dans le navigateur.

    Spaces Landing

    Pour télécharger votre fichier, cliquez sur « Add File » et faites glisser/déposer votre fichier.

    New Space Landing

    Enfin, nous devons créer un fichier appelé requirements.txt et ajouter les conditions du projet Dreambooth comme ci-dessous :

    keras-cv
    tensorflow
    huggingface-hub
    

    Et votre application devrait ĂȘtre opĂ©rationnelle !

    Nous hĂ©bergerons nos modĂšles et nos Spaces sous cette organisation. Vous pouvez transporter vos modĂšles et Spaces dans l’onglet paramĂštres sous Rename or transfer this model et sĂ©lectionner keras-dreambooth dans le menu dĂ©roulant.

    Si vous ne voyez pas keras-dreambooth dans la liste dĂ©roulante, il est probable que vous ne soyez pas membre de l’organisation. Utilisez ce lien pour demander Ă  rejoindre l’organisation.

    Soumission

    Vous pouvez soumettre votre projet dans trois thĂšmes :

    • Nature et animaux (nature)
    • Univers de science-fiction/fantastique (sci-fi)
    • Conscient (consentful) : Associez-vous Ă  un artiste pour finetuner son style avec son consentement ! Assurez-vous d’inclure une rĂ©fĂ©rence au consentement explicite de l’artiste (par exemple un tweet) dans votre carte de modĂšle.
    • Carte blanche (wild-card) : Si votre soumission appartient Ă  une catĂ©gorie qui n’est pas mentionnĂ©e ci-dessus, n’hĂ©sitez pas Ă  l’étiqueter avec wild-card afin que nous puissions l’évaluer en dehors de cette catĂ©gorie.

    Ajoutez les catégories et leurs identifiants à votre carte modÚle et ajoutez keras-dreambooth aux métadonnées dans la section des tags. Voici un exemple de carte de modÚle. Toutes les soumissions seront compilées dans ce classement et classées en fonction du nombre de likes sur un espace donné afin de déterminer les gagnants.

    Prix

    Nous choisirons trois gagnants parmi les applications soumises, en fonction du nombre de likes accordés à un espace dans une catégorie donnée.

    đŸ›ïž Le premier remportera un bon d’achat de 100$ sur hf.co/shop ou un an d’abonnement Ă  Hugging Face Pro

    đŸ›ïž La deuxiĂšme remportera un bon d’achat de 50$ sur hf.co/shop ou le livre Natural Language Processing with Transformers.

    đŸ›ïž Le troisiĂšme remportera un bon d’achat de 30$ sur hf.co/shop ou trois mois d’abonnement Ă  Hugging Face Pro

    5.3. Sprint ControlNet en JAX/Diffusers

    Bienvenue au sprint communautaire en JAX/Diffusers ! L’objectif de ce sprint est de travailler sur des modĂšles de diffusion amusants et crĂ©atifs en utilisant JAX et Diffusers.

    Lors de cet événement, nous créerons diverses applications avec des modÚles de diffusion en JAX/Flax et Diffusers en utilisant des heures TPU gratuites généreusement fournies par Google Cloud.

    Ce document présente toutes les informations importantes pour faire une soumission au sprint.

    Organisation

    Les participants peuvent proposer des idées pour un projet intéressant impliquant des modÚles de diffusion. Des équipes de 3 à 5 personnes seront ensuite formées autour des projets les plus prometteurs et les plus intéressants. Assurez-vous de lire la section Communication pour savoir comment proposer des projets, commenter les idées de projet des autres participants et créer une équipe.

    Pour aider chaque Ă©quipe Ă  mener Ă  bien son projet, nous organiserons des confĂ©rences donnĂ©es par des scientifiques et des ingĂ©nieurs de Google, de Hugging Face et de la communautĂ© open source. Les confĂ©rences auront lieu le 17 avril. Assurez-vous d’assister aux confĂ©rences pour tirer le meilleur parti de votre participation ! Consultez la section ConfĂ©rences pour avoir une vue d’ensemble des confĂ©rences, y compris l’orateur et l’heure de la confĂ©rence.

    Chaque Ă©quipe bĂ©nĂ©ficiera ensuite d’un accĂšs gratuit Ă  une VM TPU v4-8 du 14 avril au 1er mai. De plus, nous fournirons un exemple d’entraĂźnement en JAX/Flax et Diffusers pour entraĂźner un ControlNet afin de lancer votre projet. Nous fournirons Ă©galement des exemples sur la façon de prĂ©parer les jeux de donnĂ©es. Pendant le sprint, nous nous assurerons de rĂ©pondre Ă  toutes les questions que vous pourriez avoir sur JAX/Flax et Diffusers et nous aiderons chaque Ă©quipe autant que possible !

    Nous ne distribuerons pas de TPU pour les Ă©quipes composĂ©es d’un seul membre. Nous vous encourageons donc Ă  rejoindre une Ă©quipe ou Ă  trouver des coĂ©quipiers pour votre idĂ©e.

    À la fin du sprint, chaque soumission sera Ă©valuĂ©e par un jury et les trois meilleures dĂ©monstrations recevront un prix. Consultez la section Comment soumettre une dĂ©mo pour plus d’informations et de suggestions sur la maniĂšre de soumettre votre projet.

    Note : MĂȘme si nous fournissons un exemple pour entraĂźner ControlNet, les participants peuvent proposer des idĂ©es qui n’impliquent pas du tout un ControlNet du moment qu’elles sont centrĂ©es sur les modĂšles de diffusion.

    Dates importantes

    • 29/03 : Annonce officielle de la semaine de la communautĂ©.
    • 31/03 : Commencez Ă  former des groupes dans le canal #jax-diffusers-ideas sur Discord.
    • 10/04 : Collecte des donnĂ©es.
    • 13/04 & 14/04 & 17/04 : ConfĂ©rences de lancement sur YouTube.
    • 14/04 Ă  17/04 : DĂ©but de l’accĂšs aux TPU.
    • 01/05 : Fermeture de l’accĂšs aux TPU.
    • 08/05 : Annonce des 10 meilleurs projets et des prix.

    Note : Nous accepterons les candidatures tout au long du sprint.

    Communication

    Toutes les communications importantes auront lieu sur notre serveur Discord. Rejoignez le serveur en utilisant ce lien. AprĂšs avoir rejoint le serveur, prenez le rĂŽle Diffusers dans le canal #role-assignment et dirigez-vous vers le canal #jax-diffusers-ideas pour partager votre idĂ©e sous la forme d’un message de forum. Pour vous inscrire, remplissez le formulaire d’inscription et nous vous donnerons accĂšs Ă  deux canaux Discord supplĂ©mentaires pour les discussions et le support technique, ainsi qu’un accĂšs aux TPU. Les annonces importantes de l’équipe Hugging Face, Flax/JAX et Google Cloud seront publiĂ©es sur le serveur.

    Le serveur Discord sera le lieu central oĂč les participants pourront publier leurs rĂ©sultats, partager leurs expĂ©riences d’apprentissage, poser des questions et obtenir une assistance technique pour les divers obstacles qu’ils rencontrent.

    Pour les problÚmes liés à Flax/JAX, Diffusers, Datasets ou pour des questions spécifiques à votre projet, nous interagirons à travers les dépÎts publics et les forums :

    • Flax : Issues, Questions
    • JAX : Issues, Questions
    • đŸ€— Diffusers : Issues, Questions
    • đŸ€— Dataset s: Issues, Questions
    • Questions spĂ©cifiques aux projets : Elles peuvent ĂȘtre posĂ©es sur le canal #jax-diffusers-ideas sur Discord.
    • Questions relatives au TPU : Canal #jax-diffusers-tpu-support sur Discord.
    • Discussion gĂ©nĂ©rale : #jax-diffusers-sprint channel sur Discord. Vous aurez accĂšs aux canaux #jax-diffusers-tpu-support et #jax-diffusers-sprint une fois que vous aurez Ă©tĂ© acceptĂ© pour participer au sprint.

    Lorsque vous demandez de l’aide, nous vous encourageons Ă  poster le lien vers le forum sur le serveur Discord, plutĂŽt que de poster directement des issues ou des questions. De cette façon, nous nous assurons que tout le monde peut bĂ©nĂ©ficier de vos questions, mĂȘme aprĂšs la fin du sprint.

    Note : AprĂšs le 10 avril, si vous vous ĂȘtes inscrit sur le formulaire Google, mais que vous n’ĂȘtes pas dans le canal Discord, veuillez laisser un message sur l’annonce officielle du forum et envoyer un ping Ă  @mervenoyan, @sayakpaul, et @patrickvonplaten. Il se peut que nous prenions un jour pour traiter ces demandes.

    Conférences

    Nous avons invitĂ© d’éminents chercheurs et ingĂ©nieurs de Google, Hugging Face, et de la communautĂ© open-source qui travaillent dans le domaine de l’IA gĂ©nĂ©rative. Nous mettrons Ă  jour cette section avec des liens vers les confĂ©rences, alors gardez un Ɠil ici ou sur Discord dans le canal diffusion models core-announcements et programmez vos rappels !

    13 avril 2023

    Intervenant Sujet Horaire Video
    Emiel Hoogeboom, Google Brain Pixel-Space Diffusion models for High Resolution Images 4.00pm-4.40pm CEST / 7.00am-7.40am PST Youtube
    ApolinĂĄrio Passos, Hugging Face Introduction to Diffusers library 4.40pm-5.20pm CEST / 7.40am-08.20am PST Youtube
    Ting Chen, Google Brain Diffusion++: discrete data and high-dimensional generation 5.45pm-6.25pm CEST / 08.45am-09.25am PST Youtube

    14 avril 2023

    Intervenant Sujet Horaire Video
    Tim Salimans, Google Brain Efficient image and video generation with distilled diffusion models 4.00pm-4.40pm CEST / 7.00am-7.40am PST Youtube
    Suraj Patil, Hugging Face Masked Generative Models: MaskGIT/Muse 4.40pm-5.20pm CEST / 7.40am-08.20am PST Youtube
    Sabrina Mielke, John Hopkins University From stateful code to purified JAX: how to build your neural net framework 5.20pm-6.00pm CEST / 08.20am-09.00am PST Youtube

    17 avril 2023

    Intervenant Sujet Horaire Video
    Andreas Steiner, Google Brain JAX & ControlNet 4.00pm-4.40pm CEST / 7.00am-7.40am PST Youtube
    Boris Dayma, craiyon DALL-E Mini 4.40pm-5.20pm CEST / 7.40am-08.20am PST Youtube
    Margaret Mitchell, Hugging Face Ethics of Text-to-Image 5.20pm-6.00pm CEST / 08.20am-09.00am PST Youtube

    Données et prétraitement

    Dans cette section, nous verrons comment construire votre propre jeu de données pour entraßner ControlNet.

    Préparer un grand jeu de données local

    Monter un disque

    Si vous avez besoin d’espace supplĂ©mentaire, vous pouvez suivre ce guide pour crĂ©er un disque persistant, l’attacher Ă  votre VM TPU et crĂ©er un rĂ©pertoire pour monter le disque. Vous pouvez ensuite utiliser ce rĂ©pertoire pour stocker votre jeu de donnĂ©es.

    Par ailleurs, la VM TPU attribuĂ©e Ă  votre Ă©quipe dispose d’un disque de stockage persistant de 3 To. Pour apprendre Ă  l’utiliser, consultez ce guide.

    Prétraitement des données

    Nous montrons ici comment préparer un grand jeu de données pour entraßner un modÚle ControlNet avec filtre de Canny. Plus précisément, nous fournissons un exemple de script qui :

    • SĂ©lectionne 1 million de paires image-texte Ă  partir d’un jeu de donnĂ©es existant COYO-700M.
    • TĂ©lĂ©charge chaque image et utilise le filtre de Canny pour gĂ©nĂ©rer l’image de conditionnement.
    • CrĂ©e un mĂ©tafichier qui relie toutes les images et les images traitĂ©es Ă  leurs lĂ©gendes.

    Utilisez la commande suivante pour exĂ©cuter le script de prĂ©traitement des donnĂ©es de l’exemple. Si vous avez montĂ© un disque sur votre TPU, vous devez placer vos fichiers train_data_dir et cache_dir sur le disque montĂ©.

    python3 coyo_1m_dataset_preprocess.py \
     --train_data_dir="/mnt/disks/persist/data" \
     --cache_dir="/mnt/disks/persist" \
     --max_train_samples=1000000 \
     --num_proc=16
    

    Une fois le script exécuté, vous trouverez un dossier de données dans le répertoire train_data_dir spécifié avec la structure de dossier ci-dessous :

    data
    ├── images
    │   ├── image_1.png
    │   ├── .......
    │   └── image_1000000.jpeg
    ├── processed_images
    │   ├── image_1.png
    │   ├── .......
    │   └── image_1000000.jpeg
    └── meta.jsonl
    

    Charger un jeu de données

    Pour charger un jeu de donnĂ©es Ă  partir du dossier de donnĂ©es que vous venez de crĂ©er, vous devez ajouter un script de chargement de jeu de donnĂ©es Ă  votre dossier de donnĂ©es. Le script de chargement de donnĂ©es doit porter le mĂȘme nom que le dossier. Par exemple, si votre dossier de donnĂ©es est data, vous devez ajouter un script de chargement de donnĂ©es nommĂ© data.py. Nous fournissons un exemple de script de chargement de donnĂ©es que vous pouvez utiliser. Tout ce que vous avez Ă  faire est de mettre Ă  jour le DATA_DIR avec le chemin correct de votre dossier de donnĂ©es. Pour plus de dĂ©tails sur l’écriture d’un script de chargement de donnĂ©es, reportez-vous Ă  la documentation.

    Une fois que le script de chargement de données est ajouté à votre dossier de données, vous pouvez le charger avec :

    dataset = load_dataset("/mnt/disks/persist/data", cache_dir="/mnt/disks/persist" )
    

    Notez que vous pouvez utiliser --train_data_dir pour passer le rĂ©pertoire de votre dossier de donnĂ©es au script d’entraĂźnement et gĂ©nĂ©rer votre jeu de donnĂ©es automatiquement pendant l’entraĂźnement.

    Pour les grands jeux de donnĂ©es, nous recommandons de gĂ©nĂ©rer le jeu de donnĂ©es une seule fois et de le sauvegarder sur le disque Ă  l’aide de la commande

    dataset.save_to_disk("/mnt/disks/persist/dataset")
    

    Vous pouvez ensuite réutiliser le jeu de données sauvegardé pour votre entraßnement en passant --load_from_disk.

    Voici un exemple d’exĂ©cution d’un script d’entraĂźnement qui chargera le jeu de donnĂ©es depuis le disque.

    export MODEL_DIR="runwayml/stable-diffusion-v1-5"
    export OUTPUT_DIR="/mnt/disks/persist/canny_model"
    export DATASET_DIR="/mnt/disks/persist/dataset"
    export DISK_DIR="/mnt/disks/persist"
    
    python3 train_controlnet_flax.py \
     --pretrained_model_name_or_path=$MODEL_DIR \
     --output_dir=$OUTPUT_DIR \
     --train_data_dir=$DATASET_DIR \
     --load_from_disk \
     --cache_dir=$DISK_DIR \
     --resolution=512 \
     --learning_rate=1e-5 \
     --train_batch_size=2 \
     --revision="non-ema" \
     --from_pt \
     --max_train_steps=500000 \
     --checkpointing_steps=10000 \
     --dataloader_num_workers=16 
    

    Préparer un jeu de données avec MediaPipe et Hugging Face

    Nous fournissons un notebook ( Open In Colab) qui vous montre comment préparer un jeu de données pour entraßner ControlNet en utilisant MediaPipe et Hugging Face. Plus précisément, dans le notebook, nous montrons :

    • Comment tirer parti des solutions MediaPipe pour extraire les articulations du corps de la pose Ă  partir des images d’entrĂ©e.
    • PrĂ©dire les lĂ©gendes en utilisant BLIP-2 Ă  partir des images d’entrĂ©e en utilisant đŸ€— Transformers.
    • Construire et pousser le jeu de donnĂ©es final vers le Hugging Face Hub en utilisant đŸ€— Datasets.

    Vous pouvez vous rĂ©fĂ©rer au notebook pour crĂ©er vos propres jeux de donnĂ©es en utilisant d’autres solutions MediaPipe. Ci-dessous, nous listons toutes les solutions pertinentes :

    • DĂ©tection des points de repĂšre de pose
    • DĂ©tection de repĂšres de visage
    • Segmentation de selfie

    EntraĂźner ControlNet

    C’est peut-ĂȘtre la partie la plus amusante et la plus intĂ©ressante de ce document, car nous vous montrons ici comment entraĂźner un modĂšle ControlNet personnalisĂ©.

    Note : Pour ce sprint, vous n’ĂȘtes PAS limitĂ© Ă  entraĂźner des ControlNets. Nous fournissons ce script d’entraĂźnement comme rĂ©fĂ©rence pour vous permettre de dĂ©marrer.

    Pour un entraĂźnement plus rapide sur les TPU et les GPU, vous pouvez tirer parti de l’exemple d’entraĂźnement Flax. Suivez les instructions ci-dessus pour obtenir le modĂšle et le jeu de donnĂ©es avant d’exĂ©cuter le script.

    Mise en place de la VM TPU

    Avant de continuer avec le reste de cette section, vous devez vous assurer que l’adresse email que vous utilisez a Ă©tĂ© ajoutĂ©e au projet hf-flax sur Google Cloud Platform. Si ce n’est pas le cas, merci de nous le faire savoir sur le serveur Discord (vous pouvez taguer @sayakpaul, @merve, et @patrickvonplaten).

    Dans ce qui suit, nous allons dĂ©crire comment le faire en utilisant une console standard, mais vous devriez Ă©galement ĂȘtre en mesure de vous connecter Ă  la VM TPU via des IDE, comme Visual Studio Code, etc.

    1. Vous devez installer le Google Cloud SDK. Veuillez suivre les instructions sur https://cloud.google.com/sdk.

    2. Une fois le Google Cloud SDK installé, vous devez configurer votre compte en exécutant la commande suivante. Assurez-vous que correspond à l'adresse gmail que vous avez utilisée pour vous inscrire à cet événement.

       gcloud config set account <your-email-adress>
      
    3. Assurons-nous Ă©galement que le bon projet est dĂ©fini au cas oĂč votre email serait utilisĂ© pour plusieurs projets gcloud :

       gcloud config set project hf-flax
      
    4. Ensuite, vous devez vous authentifier. Vous pouvez le faire en exécutant la commande

       gcloud auth login
      

      Vous devriez obtenir un lien vers un site web oĂč vous pouvez authentifier votre compte gmail.

    5. Enfin, vous pouvez Ă©tablir un tunnel SSH dans la VM TPU ! Veuillez exĂ©cuter la commande suivante en rĂ©glant la “–zone” sur us-central2-b et sur le nom de la TPU qui vous a Ă©tĂ© envoyĂ© par email par l’équipe de Hugging Face.

       gcloud alpha compute tpus tpu-vm ssh <tpu-name> --zone <zone> --project hf-flax
      

    Cela devrait établir un tunnel SSH dans la VM TPU !

    Note : Vous n’ĂȘtes PAS supposĂ© avoir accĂšs Ă  la console Google Cloud. Aussi, il se peut que vous ne receviez pas de lien d’invitation pour rejoindre le projet hf-flax. Mais vous devriez tout de mĂȘme pouvoir accĂ©der Ă  la VM TPU en suivant les Ă©tapes ci-dessus .

    Note : Les VM TPU sont dĂ©jĂ  attachĂ©es Ă  des disques de stockage persistants (de 3 TB). Cela sera utile au cas oĂč votre Ă©quipe souhaiterait entraĂźner localement un jeu de donnĂ©es volumineux. Le nom du disque de stockage devrait Ă©galement figurer dans l’e-mail que vous avez reçu. Suivez cette section pour plus de dĂ©tails.

    Installation de JAX

    Commençons par créer un environnement virtuel Python :

    python3 -m venv <your-venv-name>
    

    Nous pouvons activer l’environnement en lançant :

    source ~/<your-venv-name>/bin/activate
    

    Installez ensuite Diffusers et les dĂ©pendances d’entraĂźnement de la bibliothĂšque :

    pip install git+https://github.com/huggingface/diffusers.git
    

    Ensuite, clonez ce dépÎt et installez JAX, Flax et les autres dépendances :

    git clone https://github.com/huggingface/community-events
    cd community-events/jax-controlnet-sprint/training_scripts
    pip install -U -r requirements_flax.txt
    

    Pour vérifier que JAX a été correctement installé, vous pouvez exécuter la commande suivante :

    import jax
    jax.device_count()
    

    Cela devrait afficher le nombre de cƓurs de la TPU, qui devrait ĂȘtre de 4 sur une VM TPUv4-8. Si Python n’est pas capable de dĂ©tecter le pĂ©riphĂ©rique TPU, veuillez consulter la section des erreurs possibles plus bas pour des solutions.

    Si vous souhaitez utiliser le logging Weights and Biases, vous devez également installer wandb maintenant :

    pip install wandb
    

    Note : Weights & Biases est gratuit pour les Ă©tudiants, les Ă©ducateurs et les chercheurs universitaires. Tous les participants Ă  notre Ă©vĂ©nement sont qualifiĂ©s pour obtenir un compte d’équipe acadĂ©mique Weights & Biases. Pour crĂ©er votre Ă©quipe, vous pouvez visiter le site https://wandb.ai/create-team et choisir le type d’équipe “Academic”. Pour plus d’informations sur la crĂ©ation et la gestion d’une Ă©quipe Weights & Biases, vous pouvez consulter le site https://docs.wandb.ai/guides/app/features/teams.

    ExĂ©cution du script d’entraĂźnement

    Maintenant, tĂ©lĂ©chargeons deux images de conditionnement que nous utiliserons pour lancer la validation pendant l’entraĂźnement afin de suivre nos progrĂšs

    wget https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/controlnet_training/conditioning_image_1.png
    wget https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/controlnet_training/conditioning_image_2.png
    

    Nous vous encourageons Ă  stocker ou Ă  partager votre modĂšle avec la communautĂ©. Pour utiliser le Hub, veuillez vous connecter Ă  votre compte Hugging Face, ou (en crĂ©er un si vous n’en avez pas dĂ©jĂ  un) :

    huggingface-cli login
    

    Assurez-vous que les variables d’environnement MODEL_DIR, OUTPUT_DIR et HUB_MODEL_ID sont dĂ©finies. Les variables OUTPUT_DIR et HUB_MODEL_ID spĂ©cifient oĂč sauvegarder le modĂšle sur le Hub :

    export MODEL_DIR="runwayml/stable-diffusion-v1-5"
    export OUTPUT_DIR="runs/fill-circle-{timestamp}"
    export HUB_MODEL_ID="controlnet-fill-circle"
    

    Et enfin, dĂ©marrez l’entraĂźnement (assurez-vous d’ĂȘtre dans le rĂ©pertoire jax-controlnet-sprint/training_scripts) !

    python3 train_controlnet_flax.py \
     --pretrained_model_name_or_path=$MODEL_DIR \
     --output_dir=$OUTPUT_DIR \
     --dataset_name=fusing/fill50k \
     --resolution=512 \
     --learning_rate=1e-5 \
     --validation_image "./conditioning_image_1.png" "./conditioning_image_2.png" \
     --validation_prompt "red circle with blue background" "cyan circle with brown floral background" \
     --validation_steps=1000 \
     --train_batch_size=2 \
     --revision="non-ema" \
     --from_pt \
     --report_to="wandb" \
     --tracker_project_name=$HUB_MODEL_ID \
     --num_train_epochs=11 \
     --push_to_hub \
     --hub_model_id=$HUB_MODEL_ID
    

    Notez que l’argument --from_pt convertira votre point de contrîle pytorch en flax. Cependant, il ne fonctionnera qu’avec les points de contrîle au format diffusers. Si votre MODEL_DIR ne contient pas de points de contrîle au format diffusers, vous ne pouvez pas utiliser l’argument --from_pt. Vous pouvez convertir vos points de contrîle ckpt ou safetensors au format diffusers en utilisant ce script.

    Puisque nous avons passĂ© l’argument --push_to_hub, il va automatiquement crĂ©er un repo de modĂšle sous votre compte Hugging Face basĂ© sur $HUB_MODEL_ID. À la fin de l’entraĂźnement, le point de contrĂŽle final sera automatiquement stockĂ© sur le Hub. Vous pouvez trouver un exemple de modĂšle ici.

    Notre script d’entraĂźnement fournit Ă©galement un support limitĂ© pour le streaming de grands jeux de donnĂ©es Ă  partir du Hub. Afin d’activer le streaming, il faut Ă©galement dĂ©finir --max_train_samples. Voici un exemple de commande (tirĂ© de cet article de blog) :

    export MODEL_DIR="runwayml/stable-diffusion-v1-5"
    export OUTPUT_DIR="runs/uncanny-faces-{timestamp}"
    export HUB_MODEL_ID="controlnet-uncanny-faces"
    
    python3 train_controlnet_flax.py \
     --pretrained_model_name_or_path=$MODEL_DIR \
     --output_dir=$OUTPUT_DIR \
     --dataset_name=multimodalart/facesyntheticsspigacaptioned \
     --streaming \
     --conditioning_image_column=spiga_seg \
     --image_column=image \
     --caption_column=image_caption \
     --resolution=512 \
     --max_train_samples 100000 \
     --learning_rate=1e-5 \
     --train_batch_size=1 \
     --revision="flax" \
     --report_to="wandb" \
     --tracker_project_name=$HUB_MODEL_ID
    

    Notez cependant que les performances des TPUs peuvent ĂȘtre limitĂ©es car le streaming avec datasets n’est pas optimisĂ© pour les images. Pour assurer un dĂ©bit maximal, nous vous encourageons Ă  explorer les options suivantes :

    • Webdataset
    • TorchData
    • TensorFlow Datasets

    Lorsque vous travaillez avec un jeu de donnĂ©es plus important, vous pouvez avoir besoin d’exĂ©cuter le processus d’entraĂźnement pendant une longue pĂ©riode et il est utile d’enregistrer des points de contrĂŽle rĂ©guliers au cours du processus. Vous pouvez utiliser l’argument suivant pour activer les points de contrĂŽle intermĂ©diaires :

     --checkpointing_steps=500
    

    Cela permet d’enregistrer le modĂšle entraĂźnĂ© dans des sous-dossiers du dossier output_dir. Le nom des sous-dossiers correspond au nombre d’étapes effectuĂ©es jusqu’à prĂ©sent ; par exemple : un point de contrĂŽle sauvegardĂ© aprĂšs 500 Ă©tapes d’entraĂźnement serait sauvegardĂ© dans un sous-dossier nommĂ© 500

    Vous pouvez alors commencer votre entraßnement à partir de ce point de contrÎle sauvegardé avec

     --controlnet_model_name_or_path="./control_out/500" 
    

    Nous soutenons l’entraĂźnement avec la stratĂ©gie de pondĂ©ration Min-SNR proposĂ©e dans Efficient Diffusion Training via Min-SNR Weighting Strategy qui permet d’obtenir une convergence plus rapide en rééquilibrant la perte. Pour l’utiliser, il faut dĂ©finir l’argument --snr_gamma. La valeur recommandĂ©e est 5.0.

    Nous supportons Ă©galement l’accumulation de gradient, technique qui vous permet d’utiliser une taille de batch plus grande que celle que votre machine serait normalement capable de mettre en mĂ©moire. Vous pouvez utiliser l’argument gradient_accumulation_steps pour dĂ©finir les Ă©tapes d’accumulation du gradient. L’auteur de ControlNet recommande d’utiliser l’accumulation de gradient pour obtenir une meilleure convergence. Pour en savoir plus voir ici.

    Vous pouvez profiler votre code avec :

     --profile_steps==5
    

    Reportez-vous à la documentation JAX sur le profilage. Pour inspecter la trace de profil, vous devez installer et démarrer Tensorboard avec le plugin de profil :

    pip install tensorflow tensorboard-plugin-profile
    tensorboard --logdir runs/fill-circle-100steps-20230411_165612/
    

    Le profil peut alors ĂȘtre inspectĂ© Ă  l’adresse http://localhost:6006/#profile.

    Parfois vous obtiendrez des conflits de version (messages d’erreur comme Duplicate plugins for name projector), ce qui signifie que vous devez dĂ©sinstaller et rĂ©installer toutes les versions de Tensorflow/Tensorboard (par exemple avec pip uninstall tensorflow tf-nightly tensorboard tb-nightly tensorboard-plugin-profile && pip install tf-nightly tbp-nightly tensorboard-plugin-profile).

    Notez que la fonctionnalitĂ© de dĂ©bogage du plugin Tensorboard profile est toujours en cours de dĂ©veloppement. Toutes les vues ne sont pas entiĂšrement fonctionnelles, et par exemple le trace_viewer coupe les Ă©vĂ©nements aprĂšs 1M (ce qui peut rĂ©sulter en la perte de toutes vos traces de pĂ©riphĂ©riques si par exemple vous profilez l’étape de compilation par accident).

    Dépannage de votre VM TPU

    TRES IMPORTANT : Un seul processus peut accĂ©der aux cƓurs de la TPU Ă  la fois. Cela signifie que si plusieurs membres de l’équipe essaient de se connecter aux cƓurs de la TPU, vous obtiendrez des erreurs telles que :

    libtpu.so already in used by another process. Not attempting to load libtpu.so in this process.
    

    Nous recommandons Ă  chaque membre de l’équipe de crĂ©er son propre environnement virtuel, mais une seule personne devrait exĂ©cuter les processus d’entraĂźnement lourds. De plus, veuillez vous relayer lors de l’installation de la TPUv4-8 afin que tout le monde puisse vĂ©rifier que JAX est correctement installĂ©.

    Si les membres de votre Ă©quipe n’utilisent pas actuellement la TPU mais que vous obtenez toujours ce message d’erreur. Vous devez tuer le processus qui utilise la TPU avec :

    kill -9 PID
    

    vous devrez remplacer le terme « PID » par le PID du processus qui utilise TPU. Dans la plupart des cas, cette information est incluse dans le message d’erreur. Par exemple, si vous obtenez

    The TPU is already in use by a process with pid 1378725. Not attempting to load libtpu.so in this process.
    

    vous pouvez faire

    kill -9 1378725
    

    Vous pouvez Ă©galement utiliser la commande suivante pour trouver les processus utilisant chacune des puces TPU (par exemple, /dev/accel0 est l’une des puces TPU)

    sudo lsof -w /dev/accel0
    

    Pour tuer tous les processus à l’aide de /dev/accel0, il faut

    sudo lsof -t /dev/accel0 | xargs kill -9
    

    Si Python n’est pas capable de dĂ©tecter votre pĂ©riphĂ©rique TPU (i.e. quand vous faites jax.device_count() et qu’il sort 0), cela peut ĂȘtre dĂ» au fait que vous n’avez pas les droits d’accĂšs aux logs tpu, ou que vous avez un fichier tpu lock qui traĂźne. ExĂ©cutez les commandes suivantes pour rĂ©soudre le problĂšme

    sudo rm -f /tmp/libtpu_lockfile
    
    sudo chmod o+w /tmp/tpu_logs/
    

    Comment faire une soumission

    Pour faire une soumission complĂšte, vous devez avoir les Ă©lĂ©ments suivants sur le Hub d’Hugging Face :

    • Un dĂ©pĂŽt de modĂšle avec les poids du modĂšle et la carte du modĂšle,
    • (Facultatif) Un dĂ©pĂŽt de jeu de donnĂ©es avec une carte de jeu de donnĂ©es,
    • Un Space qui permet aux autres d’interagir avec votre modĂšle.

    Pousser les poids du modĂšle et la carte du modĂšle vers le Hub

    Si vous utilisez le script d’entraĂźnement (train_controlnet_flax.py) fourni dans ce rĂ©pertoire

    L’activation de l’argument push_to_hub dans les arguments d’entraünement va :

    • CrĂ©er un dĂ©pĂŽt de modĂšles localement et Ă  distance sur le Hub,
    • CrĂ©er une carte de modĂšle et l’écrire dans le dĂ©pĂŽt de modĂšles local,
    • Sauvegarder votre modĂšle dans le rĂ©fĂ©rentiel de modĂšles local,
    • Pousser le dĂ©pĂŽt local vers le Hub.

    Votre carte de modÚle générée automatiquement ressemblera à ceci : Carte de modÚle.

    Vous pouvez modifier la carte de modĂšle pour qu’elle soit plus informative. Les cartes de modĂšle qui sont plus informatives que les autres auront plus de poids lors de l’évaluation.

    Si vous avez entraĂźnĂ© un modĂšle personnalisĂ© et que vous n’avez pas utilisĂ© le script

    Vous devez vous authentifier avec huggingface-cli login comme indiqué ci-dessus. Si vous utilisez une des classes de modÚles disponibles dans diffusers, sauvegardez votre modÚle avec la méthode save_pretrained de votre modÚle.

    model.save_pretrained("path_to_your_model_repository")
    

    AprÚs avoir sauvegardé votre modÚle dans un dossier, vous pouvez simplement utiliser le script ci-dessous pour pousser votre modÚle vers le Hub :

    from huggingface_hub import create_repo, upload_folder
    
    create_repo("username/my-awesome-model")
    upload_folder(
        folder_path="path_to_your_model_repository",
        repo_id="username/my-awesome-model"
    )
    

    Ceci poussera votre modĂšle vers Hub. AprĂšs avoir poussĂ© cela, vous devez crĂ©er la carte de modĂšle vous-mĂȘme. Vous pouvez utiliser l’interface graphique pour l’éditer. Edit Model Card

    Chaque carte de modĂšle se compose de deux sections, les mĂ©tadonnĂ©es et le texte libre. Vous pouvez Ă©diter les mĂ©tadonnĂ©es Ă  partir des sections dans l’interface graphique. Si vous avez sauvegardĂ© votre modĂšle en utilisant save_pretrained, vous n’avez pas besoin de fournir pipeline_tag et library_name. Sinon, fournissez pipeline_tag, library_name et le jeu de donnĂ©es s’il existe sur Hugging Face Hub. En plus de cela, vous devez ajouter jax-diffusers-event Ă  la section tags.

    ---
    license: apache-2.0
    library_name: diffusers
    tags:
    - jax-diffusers-event
    datasets:
    - red_caps
    pipeline_tag: text-to-image
    ---
    

    Edit Metadata

    Créer notre Space

    Rédiger notre application

    Nous utiliserons Gradio pour crĂ©er nos applications. Gradio possĂšde deux API principales : Interface et Blocks. Interface est une API de haut niveau qui vous permet de crĂ©er une interface avec quelques lignes de code, et Blocks est une API de plus bas niveau qui vous donne plus de flexibilitĂ© sur les interfaces que vous pouvez construire. Le code doit ĂȘtre inclus dans un fichier appelĂ© app.py.

    Essayons de crĂ©er une application ControlNet comme exemple. L’API Interface fonctionne simplement comme suit :

    import gradio as gr
    
    # La fonction d'inférence prend en compte le prompt, le prompt négatif et l'image
    def infer(prompt, negative_prompt, image):
        # implémentez votre fonction d'inférence ici
        return output_image
    
    # vous devez passer les entrées et les sorties en fonction de la fonction d'inférence
    gr.Interface(fn = infer, inputs = ["text", "text", "image"], outputs = "image").launch()
    

    Vous pouvez personnaliser votre interface en passant title, description et examples Ă  la fonction Interface.

    title = "ControlNet on Canny Filter"
    description = "This is a demo on ControlNet based on canny filter."
    # vous devez passer vos exemples en fonction de vos entrées
    # chaque liste intérieure est un exemple, chaque élément de la liste correspondant à un composant des `inputs`.
    examples = [["a cat with cake texture", "low quality", "cat_image.png"]]
    gr.Interface(fn = infer, inputs = ["text", "text", "image"], outputs = "image",
                title = title, description = description, examples = examples, theme='gradio/soft').launch()
    

    Votre interface ressemblera Ă  ceci : ControlNet

    Avec les blocs, vous pouvez ajouter des marques, des onglets, des composants sous les colonnes et les lignes, etc. Supposons que nous ayons deux ControlNets et que nous voulions les inclure dans un Space. Nous les placerons sous différents onglets dans une démo comme ci-dessous :

    import gradio as gr
    
    def infer_segmentation(prompt, negative_prompt, image):
        # votre fonction d'inférence pour le contrÎle de la segmentation 
        return im
    
    def infer_canny(prompt, negative_prompt, image):
        # votre fonction d'inférence pour un contrÎle efficace 
        return im
    
    with gr.Blocks(theme='gradio/soft') as demo:
        gr.Markdown("## Stable Diffusion with Different Controls")
        gr.Markdown("In this app, you can find different ControlNets with different filters. ")
    
    
        with gr.Tab("ControlNet on Canny Filter "):
            prompt_input_canny = gr.Textbox(label="Prompt")
            negative_prompt_canny = gr.Textbox(label="Negative Prompt")
            canny_input = gr.Image(label="Input Image")
            canny_output = gr.Image(label="Output Image")
            submit_btn = gr.Button(value = "Submit")
            canny_inputs = [prompt_input_canny, negative_prompt_canny, canny_input]
            submit_btn.click(fn=infer_canny, inputs=canny_inputs, outputs=[canny_output])
            
        with gr.Tab("ControlNet with Semantic Segmentation"):
            prompt_input_seg = gr.Textbox(label="Prompt")
            negative_prompt_seg = gr.Textbox(label="Negative Prompt")
            seg_input = gr.Image(label="Image")
            seg_output = gr.Image(label="Output Image")
            submit_btn = gr.Button(value = "Submit")
            seg_inputs = [prompt_input_seg, negative_prompt_seg, seg_input]
            submit_btn.click(fn=infer_segmentation, inputs=seg_inputs, outputs=[seg_output])
    
    demo.launch()
    

    La démo ci-dessus ressemblera à ce qui suit : Gradio Blocks

    Créer notre Space

    Une fois notre application Ă©crite, nous pouvons crĂ©er un espace Hugging Face pour hĂ©berger notre application. Vous pouvez aller sur huggingface.co, cliquer sur votre profil en haut Ă  droite et sĂ©lectionner “New Space”.

    New Space

    Nous pouvons nommer notre Space, choisir une licence et sélectionner « Gradio » comme Space SDK.

    Space Configuration

    AprĂšs avoir créé le Space, vous pouvez soit utiliser les instructions ci-dessous pour cloner le dĂ©pĂŽt localement, ajouter vos fichiers et pousser, soit utiliser l’interface graphique pour crĂ©er les fichiers et Ă©crire le code dans le navigateur.

    Spaces Landing

    Pour télécharger votre fichier de candidature, cliquez sur « Add File » et faites glisser votre fichier.

    New Space Landing

    Enfin, nous devons crĂ©er un fichier appelĂ© requirements.txt et ajouter les conditions requises pour notre projet. Assurez-vous d’installer les versions de jax, diffusers et autres dĂ©pendances comme ci-dessous.

    -f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html
    jax[cuda11_cudnn805]
    jaxlib
    git+https://github.com/huggingface/diffusers@main
    opencv-python
    transformers
    flax
    

    Nous vous accorderons une dotation GPU afin que votre application puisse fonctionner sur GPU.

    Nous avons un classement hébergé ici et nous distribuerons des prix à partir de ce classement. Pour que votre Space apparaisse sur le leaderboard, éditez simplement README.md de votre Space pour avoir le tag jax-diffusers-event sous les tags comme ci-dessous :

    ---
    title: Canny Coyo1m
    emoji: 💜 
    ...py
    tags:
    - jax-diffusers-event
    ---
    

    Prix

    Pour ce sprint, nous aurons de nombreux prix. Nous choisirons les dix premiers projets de ce classement, vous devez donc tagger votre Space pour le classement afin que votre soumission soit complĂšte, comme indiquĂ© dans la section ci-dessus. Les projets sont classĂ©s en fonction du nombre de j’aimes, nous augmenterons donc partagerons vos Spaces pour en augmenter la visibilitĂ© pour que les gens puissent voter en laissant un j’aime sur votre Space. Nous sĂ©lectionnerons les dix premiers projets du classement et le jury votera pour dĂ©terminer les trois premiĂšres places. Ces projets seront mis en valeur par Google et Hugging Face. Les interfaces Ă©laborĂ©es ainsi que les projets dont les bases de code et les modĂšles sont en libre accĂšs augmenteront probablement les chances de gagner des prix.

    Les prix sont les suivants et sont remis Ă  chaque membre de l’équipe :

    PremiĂšre place : Un bon d’achat de 150 $ Ă  dĂ©penser sur le Hugging Face Store, un abonnement d’un an Ă  Hugging Face Hub PRO, le livre Natural Language Processing with Transformers.

    DeuxiĂšme place : Un bon d’achat de 125$ Ă  dĂ©penser sur le Hugging Face Store, un abonnement d’un an Ă  Hugging Face Hub PRO.

    TroisiĂšme place : Un bon d’achat de 100 $ Ă  dĂ©penser sur le Hugging Face Store, un abonnement d’un an Ă  Hugging Face Hub PRO.

    Les dix premiers projets du classement (indĂ©pendamment de la dĂ©cision du jury) gagneront un kit de merch exclusivement conçu pour ce sprint par Hugging Face, ainsi qu’un kit de merch sĂ©parĂ© JAX de Google.

    Jury

    Le jury de ce sprint était composé des personnes suivantes :

    1. Robin Rombach, Stability AI
    2. Huiwen Chang, Google Research
    3. Jun-Yan Zhu, Carnegie Mellon University
    4. Merve Noyan, Hugging Face

    FAQ

    Dans cette section, nous rassemblons les réponses aux questions fréquemment posées sur notre canal discord.

    Comment utiliser VSCode avec TPU VM ?

    Vous pouvez suivre ce guide gĂ©nĂ©ral sur la façon d’utiliser VSCode remote pour se connecter Ă  Google Cloud VMs. Une fois que c’est configurĂ©, vous pouvez dĂ©velopper sur la VM TPU en utilisant VSCode.

    Pour obtenir votre IP externe, utilisez cette commande :

    gcloud compute tpus tpu-vm describe <node_name> --zone=<zone>
    

    Elle devrait ĂȘtre listĂ©e sous ‘accessConfig’ -> ‘externalIp’

    Comment tester votre code localement ?

    Puisque les membres de l’équipe partagent la VM TPU, il peut ĂȘtre pratique d’écrire et de tester votre code localement sur une unitĂ© centrale pendant que vos coĂ©quipiers exĂ©cutent le processus d’entraĂźnement sur la VM. Pour effectuer des tests locaux, il est important de mettre le drapeau xla_force_host_platform_device_count Ă  4. Pour en savoir plus, consultez la documentation.

    Gagnants du sprint

    Les 10 meilleurs projets (basĂ©s sur le nombre de likes sur leurs dĂ©mos) sont disponibles sur ce classement. Nous avons soumis ce classement Ă  notre jury pour qu’il juge les 10 meilleurs projets sur la base de plusieurs facteurs tels que les points de contrĂŽle du modĂšle, les jeux de donnĂ©es et les bases de code open-source, l’exhaustivitĂ© du modĂšle et des cartes de jeux de donnĂ©es, etc. En consĂ©quence, les trois projets suivants sont sortis vainqueurs :

    1. ControlNet pour la décoration intérieure
    2. ControlNet pour le réglage de la luminosité
    3. Stable Diffusion avec contrĂŽle manuel