Sélection des Couches de Contenu et de Style
Une étape cruciale consiste à sélectionner les couches appropriées dans le CNN pour extraire les caractéristiques de contenu et de style. Les couches plus profondes capturent des caractéristiques de haut niveau, tandis que les couches plus superficielles capturent des caractéristiques de bas niveau.
pretrained_net = model_zoo.vision.vgg19(pretrained=True)
style_layers, content_layers = [0, 5, 10, 19, 28], [25]
net = nn.Sequential()
for i in range(max(content_layers + style_layers) + 1):
net.add(pretrained_net.features[i])
Ce code utilise le réseau VGG-19 pré-entraîné et sélectionne les couches 0, 5, 10, 19 et 28 pour extraire les caractéristiques de style, et la couche 25 pour extraire les caractéristiques de contenu. Ces couches sont ensuite ajoutées à un nouveau réseau séquentiel (net
).
Il est à noter que VGG19 possède 5 blocs, nous allons donc choisir les premières couches de chacun de ces blocs afin de récupérer un maximum d'information de styles différents.
Extraction des Caractéristiques
Une fois les couches de contenu et de style sélectionnées, vous pouvez extraire les caractéristiques correspondantes des images de contenu et de style.
def extract_features(X, content_layers, style_layers):
contents = []
styles = []
for i in range(len(net)):
X = net[i](X)
if i in style_layers:
styles.append(X)
if i in content_layers:
contents.append(X)
return contents, styles
Cette fonction prend en entrée un tenseur d'image (X
), les listes des couches de contenu (content_layers
) et de style (style_layers
), et parcourt les couches du réseau net
. Pour chaque couche, elle calcule la sortie et vérifie si la couche est dans la liste des couches de style ou de contenu. Si c'est le cas, elle ajoute la sortie à la liste correspondante.
Définition des Fonctions de Perte
Les fonctions de perte sont essentielles pour mesurer la qualité du transfert de style et guider le processus d'optimisation. Les fonctions de perte les plus courantes sont la perte de contenu, la perte de style et la perte de variation totale.
- Perte de Contenu : Cette perte mesure la différence entre le contenu de l'image générée et celui de l'image de contenu. Elle est généralement calculée comme la distance euclidienne entre les caractéristiques de contenu extraites des deux images.
def content_loss(Y_hat, Y):
return ((Y_hat - Y)**2).mean()
- Perte de Style : Cette perte mesure la différence entre le style de l'image générée et celui de l'image de style. Elle est généralement calculée en utilisant la matrice de Gram, qui capture les corrélations entre les différentes caractéristiques de style.
def gram(X):
n = X.shape[1]
X = X.reshape((n, -1))
return nd.dot(X, X.T) / (X.shape[1] * X.shape[1])
def style_loss(Y_hat, gram_Y):
return ((gram(Y_hat) - gram_Y)**2).mean()
- Perte de Variation Totale : Cette perte encourage la création d'images lisses et visuellement agréables en pénalisant les variations excessives entre les pixels voisins.
def tv_loss(Y_hat):
return 0.5 * ((Y_hat[:, :, 1:, :] - Y_hat[:, :, :-1, :]).abs().mean() +
(Y_hat[:, :, :, 1:] - Y_hat[:, :, :, :-1]).abs().mean())
En résumé, la perte de contenu assure que l'image générée ressemble à l'image d'origine, la perte de style permet d'appliquer le style de l'image du style à l'image générée et enfin la perte de variation permet de supprimer le bruit et améliorer la cohérence de l'image générée.
Optimisation de l'Image Générée
La dernière étape consiste à optimiser l'image générée en minimisant les fonctions de perte. Cela se fait généralement en utilisant un algorithme d'optimisation tel que la descente de gradient.
content_weight, style_weight, tv_weight = 1, 1e3, 10
def compute_loss(X, contents_Y_hat, styles_Y_hat, contents_Y, styles_Y_gram):
# Calcul des pertes de contenu, de style et de variation totale
contents_l = [content_loss(Y_hat, Y) * content_weight for Y_hat, Y in zip(contents_Y_hat, contents_Y)]
styles_l = [style_loss(Y_hat, Y_gram) * style_weight for Y_hat, Y_gram in zip(styles_Y_hat, styles_Y_gram)]
tv_l = tv_loss(X) * tv_weight
# Calcul de la perte totale
l = nd.add_n(*styles_l) + nd.add_n(*contents_l) + tv_l
return contents_l, styles_l, tv_l, l
class GeneratedImage(nn.Block):
def __init__(self, img_shape, **kwargs):
super(GeneratedImage, self).__init__(**kwargs)
self.weight = self.params.get('weight', shape=img_shape)
def forward(self, x):
return self.weight.data()
def get_init_img(X, ctx, lr, styles_Y):
gen_img = GeneratedImage(X.shape)
gen_img.initialize(init.Constant(X), ctx=ctx, force_reinit=True)
trainer = gluon.Trainer(gen_img.collect_params(), 'sgd', {'learning_rate': lr})
styles_Y_gram = [gram(Y) for Y in styles_Y]
return gen_img, styles_Y_gram, trainer
def train(X, contents_Y, styles_Y, ctx, max_epochs, lr_decay_epoch):
lr = 0.1
gen_img, styles_Y_gram, trainer = get_init_img(X, ctx, lr, styles_Y)
for epoch in range(max_epochs):
start = time.time()
with autograd.record():
contents_Y_hat, styles_Y_hat = extract_features(gen_img(X), content_layers, style_layers)
contents_l, styles_l, tv_l, l = compute_loss(gen_img(X), contents_Y_hat, styles_Y_hat, contents_Y, styles_Y_gram)
l.backward()
trainer.step(1)
nd.waitall()
if (epoch + 1) % 50 == 0:
print('epoch %3d, content loss %.2f, style loss %.2f, ' \
'TV loss %.2f, %.2f sec' % (
epoch + 1, nd.add_n(*contents_l).asscalar(),
nd.add_n(*styles_l).asscalar(), tv_l.asscalar(),
time.time() - start))
if (epoch + 1) % lr_decay_epoch == 0:
lr *= 0.1
trainer.set_learning_rate(trainer.learning_rate * 0.1)
print('change lr to %.1e' % trainer.learning_rate)
return gen_img(X)
Ce code définit les poids pour les pertes de contenu, de style et de variation totale. Il définit également la fonction compute_loss()
qui calcule les pertes individuelles et la perte totale. La fonction train()
initialise une image générée, définit un optimiseur et parcourt les époques d'entraînement. Pour chaque époque, elle calcule les pertes, effectue une rétropropagation et met à jour l'image générée. Elle affiche également les pertes et le temps d'exécution à intervalles réguliers. Enfin, le code fait appel à la fonction get_init_img afin d'initialiser tous les paramètres du modèle.