Configuration de l'environnement
Pour implémenter le transfert de style et les rêves profonds, nous utiliserons PyTorch, une bibliothèque d'apprentissage profond open source très populaire. Assurez-vous d'avoir installé PyTorch et les bibliothèques nécessaires. Vous pouvez installer PyTorch en suivant les instructions sur le site officiel de PyTorch, en choisissant la configuration appropriée pour votre système d'exploitation et votre matériel. Une fois PyTorch installé, vous aurez également besoin des bibliothèques suivantes :
- NumPy : pour la manipulation de tableaux et les opérations mathématiques.
- Torchvision : pour le chargement et la transformation d'images.
- PIL (Pillow) : pour la manipulation d'images.
- Matplotlib : pour l'affichage des images.
Vous pouvez installer ces bibliothèques à l'Aide de pip, le gestionnaire de paquets de Python. Ouvrez un terminal et exécutez la commande suivante :
pip install numpy torchvision pillow matplotlib
Une fois l'environnement configuré, vous êtes prêt à commencer à implémenter le transfert de style et les rêves profonds.
Chargement et prétraitement des images
La première étape consiste à charger les images de contenu et de style, et à les prétraiter pour les rendre compatibles avec le CNN. Nous allons redimensionner les images, les convertir en tenseurs PyTorch, et les normaliser. Voici un exemple de code pour charger et prétraiter les images :
import torch
import torch.nn as nn
import torch.optim as optim
from PIL import Image
import torchvision.transforms as transforms
import torchvision.models as models
import matplotlib.pyplot as plt
# Définir les transformations
IMSIZE = 512 if torch.cuda.is_available() else 128 # Utiliser une taille plus petite si le GPU n'est pas disponible
loader = transforms.Compose([
transforms.Resize(IMSIZE),
transforms.ToTensor()])
def image_loader(image_name):
image = Image.open(image_name)
image = loader(image).unsqueeze(0)
return image.to(device, torch.float)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
style_img = image_loader("images/impressionism.jpg")
content_img = image_loader("images/dancing.jpg")
assert style_img.size() == content_img.size(), \
"Nous avons besoin d'images de style et de contenu de même taille"
Ce code définit une fonction image_loader
qui charge une image à partir d'un fichier, la redimensionne, la convertit en un tenseur PyTorch, et la transfère sur le GPU si disponible. Il charge également les images de style et de contenu, et vérifie qu'elles ont la même taille.
Utilisation d'un CNN pré-entraîné
Nous allons utiliser un CNN pré-entraîné, tel que VGG19, pour extraire les caractéristiques de style et de contenu des images. VGG19 est un réseau neuronal convolutif qui a été entraîné sur un grand ensemble de données d'images, et qui a la capacité d'extraire des caractéristiques visuelles complexes. Nous allons charger le modèle VGG19 pré-entraîné à partir de torchvision.models, et désactiver son entraînement pour ne pas modifier ses poids. Voici un exemple de code pour charger le modèle VGG19 :
unloader = transforms.ToPILImage() # Pour convertir le tenseur en image PIL
plt.ion() # Mode interactif
def imshow(tensor, title=None):
image = tensor.cpu().clone() # Nous clonons pour ne pas modifier le tenseur
image = image.squeeze(0) # Suppression de la fausse dimension de batch
image = unloader(image)
plt.imshow(image)
if title is not None:
plt.title(title)
plt.pause(0.001) # Pause un peu pour que les trames soient mises à jour
model = models.vgg19(pretrained=True).features.to(device).eval()
Ce code charge le modèle VGG19 pré-entraîné, transfère ses paramètres sur le GPU si disponible, et le met en mode évaluation pour désactiver l'entraînement. Il définit également une fonction imshow
pour afficher les tenseurs PyTorch sous forme d'images.
Calcul des pertes de style et de contenu
L'étape suivante consiste à calculer les pertes de style et de contenu, qui mesurent la différence entre les caractéristiques extraites des images de contenu et de style, et les caractéristiques extraites de l'image générée. Nous allons définir des fonctions de perte personnalisées pour mesurer ces différences. La perte de contenu mesure la différence entre les caractéristiques de contenu de l'image générée et de l'image de contenu, tandis que la perte de style mesure la différence entre les matrices de Gram des caractéristiques de style de l'image générée et de l'image de style. La matrice de Gram est une représentation statistique du style d'une image, qui capture les corrélations entre les différentes caractéristiques. Voici un exemple de code pour définir les fonctions de perte :
class ContentLoss(nn.Module):
def __init__(self, target,):
super(ContentLoss, self).__init__()
# Nous "découvrons" le faux paramètre de feuille cible du graphique de calcul
# Ceci est un moyen statique de dire que cela ne devrait pas être un Tensor
# rencontré comme un paramètre.
self.target = target.detach()
def forward(self, input):
self.loss = F.mse_loss(input, self.target)
return input
def gram_matrix(input):
a, b, c, d = input.size()
features = input.view(a * b, c * d)
G = torch.mm(features, features.t())
return G.div(a * b * c * d)
class StyleLoss(nn.Module):
def __init__(self, target_feature):
super(StyleLoss, self).__init__()
self.target = gram_matrix(target_feature).detach()
def forward(self, input):
G = gram_matrix(input)
self.loss = F.mse_loss(G, self.target)
return input
Ce code définit deux classes de perte personnalisées, ContentLoss
et StyleLoss
, qui mesurent les différences de contenu et de style, respectivement. Il définit également une fonction gram_matrix
pour calculer la matrice de Gram d'un tenseur.
Boucle d'optimisation
La dernière étape consiste à optimiser l'image générée pour minimiser les pertes de style et de contenu. Nous allons utiliser un algorithme d'optimisation, tel que L-BFGS, pour mettre à jour les pixels de l'image générée. Nous allons également ajuster les poids des pertes de style et de contenu pour contrôler l'importance relative du style et du contenu dans l'image générée. Voici un exemple de code pour la boucle d'optimisation :
# Import nécessaire pour que le contenu loss soit calculé a chaque passe
import torch.nn.functional as F
content_layers_default = ["conv_4"]
style_layers_default = ["conv_1", "conv_2", "conv_3", "conv_4", "conv_5"]
def get_style_model_and_losses(cnn, normalization_mean, normalization_std,style_img, content_img,content_layers=content_layers_default,style_layers=style_layers_default):
cnn = copy.deepcopy(cnn)
# Normalisation instance pour que l'on puisse avoir une mean et un std
normalization = Normalization(normalization_mean, normalization_std).to(device)
# Des loss a travers le modele afin de pouvoir effectuer la back prop plus tard
content_losses = []
style_losses = []
# On regarde a travers le CNN afin de tracker les loss
model = nn.Sequential(normalization)
i = 0 # Increment de chaque layer conv
for layer in cnn.children():
if isinstance(layer, nn.Conv2d):
i += 1
name = 'conv_{}'.format(i)
elif isinstance(layer, nn.ReLU):
name = 'relu_{}'.format(i)
layer = nn.ReLU(inplace=False)
elif isinstance(layer, nn.MaxPool2d):
name = 'pooling_{}'.format(i)
elif isinstance(layer, nn.BatchNorm2d):
name = 'bn_{}'.format(i)
else:
raise RuntimeError('Unrecognized layer: {}'.format(layer.__class__.__name__))
model.add_module(name, layer)
if name in content_layers:
# add content loss:
target = model(content_img).detach()
content_loss = ContentLoss(target)
model.add_module("content_loss_{}".format(i), content_loss)
content_losses.append(content_loss)
if name in style_layers:
# add style loss:
target_feature = model(style_img).detach()
style_loss = StyleLoss(target_feature)
model.add_module("style_loss_{}".format(i), style_loss)
style_losses.append(style_loss)
return model, style_losses, content_losses
input_img = content_img.clone()
optimizer = optim.LBFGS([input_img.requires_grad_()])
def run_style_transfer(cnn, normalization_mean, normalization_std,style_img, content_img, input_img, num_steps=300,style_weight=1000000, content_weight=1):
"""Run the style transfer."""
print('Building the style transfer model..')
model, style_losses, content_losses = get_style_model_and_losses(cnn,normalization_mean, normalization_std, style_img, content_img)
optimizer = optim.LBFGS([input_img.requires_grad_()])
print('Optimizing..')
run = [0]
while run[0] <= num_steps:
def closure():
# Correction des valeurs de l'image input
input_img.data.clamp_(0, 1)
optimizer.zero_grad()
model(input_img)
style_score = 0
content_score = 0
for sl in style_losses:
style_score += sl.loss
for cl in content_losses:
content_score += cl.loss
style_score *= style_weight
content_score *= content_weight
loss = style_score + content_score
loss.backward()
run[0] += 1
if run[0] % 50 == 0:
print("run {}:\