Comment faire un bot qui transforme une photo en bande dessinée. Troisième partie. Modèle d'hébergement gratuit sans serveur + GPU

⇨ Partie 1
⇨ Partie 2


Eh bien, reposé et ça suffit. Bienvenue à nouveau!


Dans la série précédente, vous et moi avons collecté des données et formé notre premier modèle.
Puis, horrifiés par les résultats, ils en ont entraîné une douzaine de plus.
Il est temps de montrer notre création au monde!


Exporter le modèle


Pour commencer, nous ré-enregistrerons le modèle du générateur dans un format approprié afin de ne pas avoir à faire glisser les déclarations de classe vers l'hébergement.


Créez un petit fichier avec l'extension * .py et copiez le code sous le spoiler ci-dessous.
Que ce soit jit.py:


Le code à copier sans réfléchir en remplaçant path, output_path par le vôtre
#  path       #    *G_A.pth -   ->  # output_path -      #      *.jit,  -  ,     path= '/checkpoints/resnet9_nowd_nodo_128to400_c8/60_net_G_A.pth' output_path ='/checkpoints/resnet9_nowd_nodo_128to400_c8/resnet9_nowd_nodo_128to400_c8_160-50-60_1.jit' import torch from torch import nn class ResnetGenerator(nn.Module): """Resnet-based generator that consists of Resnet blocks between a few downsampling/upsampling operations. We adapt Torch code and idea from Justin Johnson's neural style transfer project(https://github.com/jcjohnson/fast-neural-style) """ def __init__(self, input_nc, output_nc, ngf=64, norm_layer=nn.BatchNorm2d, use_dropout=False, n_blocks=6, padding_type='reflect'): """Construct a Resnet-based generator Parameters: input_nc (int) -- the number of channels in input images output_nc (int) -- the number of channels in output images ngf (int) -- the number of filters in the last conv layer norm_layer -- normalization layer use_dropout (bool) -- if use dropout layers n_blocks (int) -- the number of ResNet blocks padding_type (str) -- the name of padding layer in conv layers: reflect | replicate | zero """ assert(n_blocks >= 0) super(ResnetGenerator, self).__init__() if type(norm_layer) == functools.partial: use_bias = norm_layer.func == nn.InstanceNorm2d else: use_bias = norm_layer == nn.InstanceNorm2d model = [nn.ReflectionPad2d(3), nn.Conv2d(input_nc, ngf, kernel_size=7, padding=0, bias=use_bias), norm_layer(ngf), nn.ReLU(True)] n_downsampling = 2 for i in range(n_downsampling): # add downsampling layers mult = 2 ** i model += [nn.Conv2d(ngf * mult, ngf * mult * 2, kernel_size=3, stride=2, padding=1, bias=use_bias), norm_layer(ngf * mult * 2), nn.ReLU(True)] mult = 2 ** n_downsampling for i in range(n_blocks): # add ResNet blocks model += [ResnetBlock(ngf * mult, padding_type=padding_type, norm_layer=norm_layer, use_dropout=use_dropout, use_bias=use_bias)] for i in range(n_downsampling): # add upsampling layers mult = 2 ** (n_downsampling - i) model += [nn.ConvTranspose2d(ngf * mult, int(ngf * mult / 2), kernel_size=3, stride=2, padding=1, output_padding=1, bias=use_bias), norm_layer(int(ngf * mult / 2)), nn.ReLU(True)] model += [nn.ReflectionPad2d(3)] model += [nn.Conv2d(ngf, output_nc, kernel_size=7, padding=0)] model += [nn.Tanh()] self.model = nn.Sequential(*model) def forward(self, input): """Standard forward""" return self.model(input) class ResnetBlock(nn.Module): """Define a Resnet block""" def __init__(self, dim, padding_type, norm_layer, use_dropout, use_bias): """Initialize the Resnet block A resnet block is a conv block with skip connections We construct a conv block with build_conv_block function, and implement skip connections in <forward> function. Original Resnet paper: https://arxiv.org/pdf/1512.03385.pdf """ super(ResnetBlock, self).__init__() self.conv_block = self.build_conv_block(dim, padding_type, norm_layer, use_dropout, use_bias) def build_conv_block(self, dim, padding_type, norm_layer, use_dropout, use_bias): """Construct a convolutional block. Parameters: dim (int) -- the number of channels in the conv layer. padding_type (str) -- the name of padding layer: reflect | replicate | zero norm_layer -- normalization layer use_dropout (bool) -- if use dropout layers. use_bias (bool) -- if the conv layer uses bias or not Returns a conv block (with a conv layer, a normalization layer, and a non-linearity layer (ReLU)) """ conv_block = [] p = 0 if padding_type == 'reflect': conv_block += [nn.ReflectionPad2d(1)] elif padding_type == 'replicate': conv_block += [nn.ReplicationPad2d(1)] elif padding_type == 'zero': p = 1 else: raise NotImplementedError('padding [%s] is not implemented' % padding_type) conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), norm_layer(dim), nn.ReLU(True)] if use_dropout: conv_block += [nn.Dropout(0.5)] p = 0 if padding_type == 'reflect': conv_block += [nn.ReflectionPad2d(1)] elif padding_type == 'replicate': conv_block += [nn.ReplicationPad2d(1)] elif padding_type == 'zero': p = 1 else: raise NotImplementedError('padding [%s] is not implemented' % padding_type) conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), norm_layer(dim)] return nn.Sequential(*conv_block) def forward(self, x): """Forward function (with skip connections)""" out = x + self.conv_block(x) # add skip connections return out import functools norm_layer = functools.partial(nn.InstanceNorm2d, affine=False, track_running_stats=False) model = ResnetGenerator(3,3,64, norm_layer=norm_layer, use_dropout=False, n_blocks=9) model.load_state_dict(torch.load(path)) model.eval() model.cuda() model.half() trace_input = torch.ones(1,3,256,256).cuda() model = model.eval() jit_model = torch.jit.trace(model.float(), trace_input) torch.jit.save(jit_model, output_path) 

Remplacez les variables par les vôtres:


  • chemin - le chemin vers l'itération de modèle que vous aimez.
    Nous avons besoin d'un fichier * G_A.pth - générateur de photos -> bande dessinée.
  • chemin_sortie - le nom du fichier pour le modèle exporté, il peut être n'importe quoi avec l'extension * .jit, l'essentiel est de ne pas oublier où nous l'avons enregistré.

Il n'est pas nécessaire d'exporter le générateur de la dernière ère de formation, prenez celui dont vous aimez le plus les résultats.

Ensuite, allez dans la console, allez dans le dossier avec notre fichier et écrivez:


 python jit.py 

Voila! Le modèle est prêt à se familiariser avec le monde extérieur.


Plus d'informations sur torch jit

Documentation: https://pytorch.org/docs/stable/jit.html
En bref, l'exportation vers jit vous permet de sérialiser le modèle et de ne pas faire glisser l'environnement python avec lui, toutes les dépendances et modules externes qu'il pourrait utiliser. Sauf torche, bien sûr.
Bien que nous l'hébergerons dans un environnement python, les modèles jit peuvent être utilisés dans des applications autonomes.


Choix d'hébergement


Je serai très franc et admettrai immédiatement: mon passionné de deep learning interne est décédé quelque part dans la deuxième heure de l'exploration des possibilités d'hébergement avec le support du GPU. Donc, si quelqu'un me dit un hébergement GPU sans serveur peu coûteux, je serai plus que reconnaissant.

Je n'avais pas prévu de payer pour un serveur à part entière pour mes expériences, donc je ne cherchais que des solutions sans serveur.


Après une atroce rampe sur les plans tarifaires multi-pages de Google et d'Amazon, mon choix s'est porté sur algorithmia.com


Il y a plusieurs raisons:


  • Web IDE est idéal pour les nuls, bien que terriblement lent, car vous devez attendre la fin de la construction pour la vérification. En dehors de ce tutoriel, je recommanderais de tout tester localement, car la plupart des erreurs se produisent au stade du téléchargement et de l'enregistrement des fichiers.


  • Le nombre minimum d'options - il est difficile de mourir d'une vieillesse prématurée, sans avoir lu la liste des options jusqu'au bout.


  • Eh bien, le dernier argument - pendant près de six mois d'expériences, je n'ai toujours pas dépensé un solde de départ gratuit.



Bien que maintenant, lors de l'enregistrement d'un compte personnel, ils donnent moins que l'été dernier, cela devrait encore suffire pendant un certain temps, et certainement pour toutes nos expériences. À la fin du mois, plus de prêts sont accordés, ce qui m'a plus d'une fois sauvé d'une ruine potentielle.


Parmi les inconvénients, il convient de noter que les cartes vidéo ne sont que l'ancienne Tesla K80 avec 12 Go de RAM, ce qui impose des restrictions correspondantes. Dans tous les cas, jusqu'à ce que nous arrivions à la production, nous comprendrons déjà ce dont nous avons besoin du serveur.


Déployer des modèles


Eh bien, à la bataille!


Inscription


Nous allons sur https://algorithmia.com/signup et nous enregistrons. Je ne suis pas sûr qu'il y ait une différence quant à la profession \ type de compte à choisir, mais si vous trouvez un combo en or qui donne un maximum de crédits, assurez-vous de me le faire savoir dans les commentaires!


Télécharger le modèle


Après l'inscription, nous serons dans votre profil.
Nous devons créer des dossiers pour le modèle et les images qu'il va générer.
Pour ce faire, sélectionnez Sources de données dans le menu de gauche.


Cliquez sur Nouvelle source de données -> Collecte de données hébergée
Nommons le dossier «Mes modèles».
En conséquence, nous devrions être transférés sur une page avec une liste de nos dossiers.


Créez un autre dossier: Nouvelle collection -> «photo2comics_out»


Il est temps de télécharger notre modèle fraîchement exporté!
Accédez au dossier Mes modèles et faites glisser le fichier de modèle vers le navigateur, ou sélectionnez Télécharger des fichiers dans le menu.


Copiez maintenant le lien vers notre modèle, il nous sera utile ci-dessous. Pour ce faire, cliquez sur les points de suspension à droite du nom de fichier.


Les données sont terminées, allez à l'algorithme lui-même.


Algorithme


Nous revenons au profil en cliquant sur Accueil dans le menu de gauche.


Ensuite, cliquez sur Créer nouveau -> Algorithme et sélectionnez le nom de notre algorithme. Nous remplissons les options restantes comme dans l'image ci-dessous.


Image ci-dessous


Cliquez sur Créer un nouvel algorithme et sélectionnez WebIDE dans la fenêtre qui apparaît.
Si vous avez accidentellement fermé le popup, vous pouvez ouvrir le code source en cliquant sur Code source dans le menu de notre algorithme.


Nous supprimons le code du modèle et insérons le nôtre:


Un peu plus de code pour une copie encore plus irréfléchie
 import Algorithmia import torch import torchvision import torchvision.transforms as transforms import cv2 from torch import * import uuid import gc import requests import numpy as np client = Algorithmia.client() #     file_path,      #   model_file   . def load_model(): file_path = "{      }" model_file = client.file(file_path).getFile().name model = torch.jit.load(model_file).half().cuda() return model model = load_model().eval() torch.cuda.empty_cache() torch.cuda.ipc_collect() torch.backends.cudnn.benchmark = True #    ,    , #     , # ..      def preprocessing(image_path, max_size): response = requests.get(image_path) response = response.content nparr = np.frombuffer(response, np.uint8) img_res = cv2.imdecode(nparr, cv2.IMREAD_COLOR) img_res = cv2.cvtColor(img_res, cv2.COLOR_BGR2RGB) x = img_res.shape[0] y = img_res.shape[1] #if image is bigger than the target max_size, downscale it if x>max_size and x<=y: y = y*(max_size/x) x = max_size if y>max_size and y<x: x = x*(max_size/y) y = max_size size = (int(y),int(x)) img_res = cv2.resize(img_res,size) t = Tensor(img_res/255.)[None,:] t = t.permute(0,3,1,2).half().cuda() # standartize t = (t-0.5)/0.5 return(t) def predict(input): gc.collect() with torch.no_grad(): res = model(input).detach() return res #   ,     #      def save_file(res, file_uri): #de-standartize res = (res*0.5)+0.5 tempfile = "/tmp/"+str(uuid.uuid4())+".jpg" torchvision.utils.save_image(res,tempfile) client.file(file_uri).putFile(tempfile) # API calls will begin at the apply() method, with the request body passed as 'input' # For more details, see algorithmia.com/developers/algorithm-development/languages def apply(input): processed_data= preprocessing(input["in"], input["size"]) res = predict(processed_data) save_file(res, input["out"]) input = None res = None processed_data = None gc.collect() return "Success" 

N'oubliez pas d'insérer un lien vers le modèle téléchargé. Nous l'avons copié dans la section précédente lors du chargement du modèle.


Dans WebIDE, cliquez sur DÉPENDANCES en haut à droite et remplacez le texte par une liste de nos dépendances:


 algorithmia>=1.0.0,<2.0 opencv-python six torch==1.3.0 torchvision numpy 

La version de la torche doit être identique ou plus récente que celle sur laquelle nous avons enregistré le modèle. Sinon, il peut y avoir des erreurs lors de l'importation du modèle jit.


Cliquez sur ENREGISTRER, CONSTRUIRE et attendez la fin de la génération. Dès qu'un message sur une construction réussie apparaît dans la console ci-dessous, nous pouvons vérifier les performances du modèle en envoyant une demande de test à la console:


 {"in":"https://cdn3.sportngin.com/attachments/photo/9226/3971/JABC-9u_medium.JPG", "out":"data://username/photo2comics_out/test.jpg", "size":512} 

Où {nom d'utilisateur} est votre nom d'utilisateur. Si tout s'est bien passé, «Success» apparaîtra dans la console, et l'image générée apparaîtra dans le dossier que nous avons spécifié (dans ce cas photo2comics_out).


Résumé


Félicitations, nous avons reporté à bon marché et avec colère notre humble modèle!


Dans le prochain numéro, nous nous lierons d'amitié avec le modèle de bot de télégramme et, enfin, nous publierons tout cela.
Si vous ne pouvez pas attendre pour essayer le modèle, vous pouvez toujours lire la documentation officielle: https://algorithmia.com/developers/api/


Eh bien, afin de passer le temps jusqu'au prochain article, vous pouvez pousser quelques bots, modèles pour lesquels j'ai posté sur l'algorithme:


@ selfie2animebot - Transforme un selfie en un anime
@pimpmyresbot - Augmente la résolution x2 (maximum à 1400x1400)
@photozoombot - Crée une vidéo zoom 3D à partir d'une photo
@ photo2comicsbot - En fait, le héros de l' occasion


N'oubliez pas de partager les résultats, les idées et les problèmes rencontrés dans les commentaires.
C'est tout pour aujourd'hui. A très bientôt!

Source: https://habr.com/ru/post/fr485824/


All Articles