Il existe de nombreux types de frameworks Deep Learning tels que PyTorch, Tensorflow et Keras. Cette fois, je ferai attention à ** PyTorch **, que j'utilise souvent!
Saviez-vous que la ** version C ++ a été publiée ** ainsi que la version PyTorch et Python? Cela facilite l'intégration si vous souhaitez utiliser le Deep Learning dans le cadre du traitement de votre programme C ++!
Une telle version C ++ de PyTorch, je me demandais ** "C ++ est un langage compilé, alors peut-être est-il plus rapide que la version Python?" **.
Donc, cette fois, j'ai en fait étudié ** "Quelle est la différence de vitesse entre C ++ et Python?" </ Font> **! De plus, j'étais préoccupé par la précision, alors je l'ai vérifié.
Cette fois, comme le titre l'indique, nous utiliserons la version C ++ de "PyTorch". Vous pouvez le télécharger sur le site suivant, alors essayez-le!
Officiel PyTorch: https://pytorch.org/
Je l'ai téléchargé avec les paramètres ci-dessus. La «version Preview (Nightly)» contient toujours les derniers fichiers. Cependant, il est toujours en développement, donc si vous souhaitez utiliser la version stable, sélectionnez "Stable (1.4)".
De plus, «Exécuter cette commande» en bas est assez important, et si vous avez une version de construction de CXX de 11 ou plus, nous vous recommandons de sélectionner celle du bas. Actuellement, c'est presque CXX17, donc je pense que ça va ci-dessous. Si vous sélectionnez ce qui précède, des erreurs de lien d'autres bibliothèques se produiront et ce sera beaucoup de problèmes.
Cette fois, nous utiliserons ** autoencoder convolutif </ font> ** (Convolutional Autoencoder). Disponible sur mon GitHub → https://github.com/koba-jon/pytorch_cpp
Ce modèle mappe ** l'image d'entrée (haute dimension) ** à ** l'espace latent (faible dimension) **, et cette fois en fonction de cette ** variable latente (faible dimension) ** ** image ( Le but est de générer de grandes dimensions) ** et de minimiser l'erreur entre celle-ci et l'image d'entrée. Après la formation, ce modèle peut générer à nouveau une image de grande dimension à partir d'une image de grande dimension à travers un espace de faible dimension, de sorte que ** un espace latent qui caractérise davantage l'image de formation ** puisse être obtenu. En d'autres termes, elle a le rôle de compression de dimension et peut être appelée analyse en composantes principales non linéaire. Ceci est très pratique car il a diverses utilisations telles que ** élimination de la malédiction dimensionnelle **, ** apprentissage par transfert ** et ** détection d'anomalie **.
Maintenant, je vais vous expliquer la structure du modèle à utiliser.
Dans l'attente de ces effets, nous avons construit le réseau suivant.
Operation | Kernel Size | Stride | Padding | Bias | Feature Map | BN | Activation | ||
---|---|---|---|---|---|---|---|---|---|
Input | Output | ||||||||
1 | Convolution | 4 | 2 | 1 | False | 3 | 64 | ReLU | 2 | 64 | 128 | True | ReLU |
3 | 128 | 256 | True | ReLU | |||||
4 | 256 | 512 | True | ReLU | |||||
5 | 512 | 512 | True | ReLU | |||||
6 | 512 | 512 | |||||||
7 | Transposed Convolution | 512 | 512 | True | ReLU | ||||
8 | 512 | 512 | True | ReLU | |||||
9 | 512 | 256 | True | ReLU | |||||
10 | 256 | 128 | True | ReLU | |||||
11 | 128 | 64 | True | ReLU | |||||
12 | 64 | 3 | tanh |
Cette fois, nous utiliserons l'ensemble de données CelebA, qui est une collection de 202599 images de visages de célébrités (couleur). La taille de l'image est de 178 x 218 [pixel], ce qui cause des inconvénients lors du pliage inversé, donc cette fois je l'ai redimensionnée en ** 64 x 64 [pixel] **. Parmi ceux-ci, ** 90% (182 340) ont été utilisés pour l'apprentissage d'images **, et ** 10% (20 259) ont été utilisés pour des images de test **.
Lorsqu'il est entré dans le modèle précédent, l'espace latent devient (C, H, W) = (512,1,1). Si vous entrez une image de 128 x 128 [pixels] ou plus, la couche intermédiaire devient un espace latent spatial.
Cette fois, je vais principalement étudier ** "Quelle est la différence de vitesse entre C ++ et Python" **, mais j'aimerais comparer la vitesse et les performances dans les 5 types d'environnements suivants. ..
Comme le montrent les fonctionnalités ci-dessus, dans le Deep Learning qui gère les images, le GPU est extrêmement avantageux en termes de vitesse de calcul.
CPU.py
device = torch.device('cpu') #Utiliser le processeur
model.to(device) #Déplacer le modèle vers le processeur
image = image.to(device) #Déplacer les données vers le processeur
GPU.py
device = torch.device('cuda') #Utiliser le GPU par défaut
device = torch.device('cuda:0') #Utilisez le premier GPU
device = torch.device('cuda:1') #Utilisez le deuxième GPU
model.to(device) #Déplacer le modèle vers le GPU
image = image.to(device) #Déplacer les données vers le GPU
CPU.cpp
torch::Device device(torch::kCPU); //Utiliser le processeur
model->to(device); //Déplacer le modèle vers le processeur
image = image.to(device); //Déplacer les données vers le processeur
GPU.cpp
torch::Device device(torch::kCUDA); //Utiliser le GPU par défaut
torch::Device device(torch::kCUDA, 0); //Utilisez le premier GPU
torch::Device device(torch::kCUDA, 1); //Utilisez le deuxième GPU
model->to(device); //Déplacer le modèle vers le GPU
image = image.to(device); //Déplacer les données vers le GPU
Dans la version Python de PyTorch, dans le cas de l'apprentissage par GPU, cuDNN est utilisé pour ** améliorer la vitesse d'apprentissage **.
Cependant, contrairement au C ++, ce n'est pas parce que la vitesse d'apprentissage est améliorée que la même situation peut être reproduite en tournant à nouveau l'apprentissage.
Par conséquent, la formule PyTorch indique que pour assurer la reproductibilité, il est nécessaire de rendre le comportement de cuDNN déterministe comme suit, et en même temps, la vitesse diminue.
https://pytorch.org/docs/stable/notes/randomness.html
Deterministic mode can have a performance impact, depending on your model. This means that due to the deterministic nature of the model, the processing speed (i.e. processed batch items per second) can be lower than when the model is non-deterministic.
Du point de vue de l'ingénieur, je l'ai inclus dans cette comparaison de vitesse parce que je peux être préoccupé par la présence ou l'absence de reproductibilité, et parce que la vitesse change en fonction de la présence ou de l'absence de reproductibilité.
Contrairement à la fonction "rand" de C ++, si vous ne définissez aucune valeur initiale du nombre aléatoire, ce sera aléatoire, donc afin d'assurer la reproductibilité en Python, ** explicitement ** la valeur initiale du nombre aléatoire Vous devez le définir. (La définition de la valeur initiale du nombre aléatoire n'affecte pas la vitesse.)
La mise en œuvre est la suivante.
deterministic.py
seed = 0
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
np.random.seed(seed)
random.seed(seed)
torch.backends.cudnn.deterministic = True #Définitif au lieu de ralentir
torch.backends.cudnn.benchmark = False #Définitif au lieu de ralentir
non_deterministic.py
torch.backends.cudnn.deterministic = False #Plus rapide que non déterministe
torch.backends.cudnn.benchmark = True #Accélérez lorsque la taille de l'image ne change pas
Même si le contenu que vous souhaitez implémenter est le même, si le langage de programmation change, ** la notation et les règles ** peuvent changer et les ** bibliothèques requises ** peuvent changer. Comme Python et C ++ sont des langages orientés objet, les concepts eux-mêmes sont similaires, mais surtout, puisque Python est un type d'interpréteur et C ++ est un type de compilation, il doit être implémenté étant donné que le typage dynamique ne fonctionne pas pour C ++. Il faut également tenir compte du fait que certaines fonctionnalités ne sont pas disponibles car l'API C ++ de PyTorch est actuellement en cours de développement.
Sur la base de ces points, je présenterai la différence d'implémentation entre Python et C ++, et le programme que j'ai implémenté.
En plus de l'état d'utilisation de la bibliothèque Python qui est généralement écrite à l'heure actuelle, la bibliothèque recommandée pour l'implémentation en C ++ et l'état d'utilisation de la bibliothèque du programme que j'ai réellement écrit sont également décrits.
Python (recommandé) | C++(Recommandation) | C++(fait maison) | |
---|---|---|---|
Gestion des arguments de ligne de commande | argparse | boost::program_options | boost::program_options |
Conception de modèle | torch.nn | torch::nn | torch::nn |
Prétraitement (transformation) | torchvision.transforms | torch::data::transformations (pour divers pré-traitements avant exécution) or Self-made (lors de divers prétraitements après l'exécution) |
Fait soi-même (en utilisant OpenCV) |
Obtenir des ensembles de données (ensembles de données) | torchvision.ensembles de données (utilisant Pillow) | Fait soi-même (en utilisant OpenCV) | Fait soi-même (en utilisant OpenCV) |
Chargeur de données | torch.utils.data.DataLoader | torch::data::make_data_chargeur (pour classification) or Fabrication artisanale (autre que classification) |
Fait soi-même (en utilisant OpenMP) |
Fonction de perte (perte) | torch.nn | torch::nn | torch::nn |
Méthode d'optimisation (optimiseur) | torch.optim | torch::optim | torch::optim |
Méthode de propagation de l'erreur arrière (arrière) | torch.Tensor.backward() | torch::Tensor::backward() | torch::Tensor::backward() |
barre de progression | tqdm | boost | fait maison |
** Pour le moment (2020/03/24) </ font> **, cela ressemble à ce qui précède.
Lors de l'utilisation de la bibliothèque PyTorch en C ++, les noms de classe et les noms de fonction sont presque les mêmes que Python. Cela semble être dû à la considération de l'utilisateur du côté du producteur. Je suis très reconnaissant!
Ensuite, je décrirai les points auxquels vous devez faire particulièrement attention lors de l'écriture de programmes PyTorch en C ++.
Ce qui suit est un extrait d'une partie du programme que j'ai écrit.
networks.hpp (extrait partiel)
using namespace torch;
namespace po = boost::program_options;
struct ConvolutionalAutoEncoderImpl : nn::Module{
private:
nn::Sequential encoder, decoder;
public:
ConvolutionalAutoEncoderImpl(po::Variables_map &vm);
torch::Tensor forward(torch::Tensor x);
}
TORCH_MODULE(ConvolutionalAutoEncoder);
Lors de la conception d'un modèle, utilisez la classe "torch :: nn" comme en Python. De plus, lors de la création d'un modèle, utilisez une structure. (Il existe aussi une version classe, mais cela semble un peu compliqué) À ce stade, il convient de noter que ** nn :: Module est hérité </ font> ** comme en Python. C'est la même chose que la façon d'écrire Python.
La prochaine chose importante est de nommer la ** structure "[model name] Impl" </ font> ** et sous la structure ** Ajoutez "TORCH_MODULE ([nom du modèle])" </ font> **. Si vous ne le faites pas, vous ne pourrez pas enregistrer ni charger le modèle. De plus, en définissant "TORCH_MODULE ([nom du modèle])", la structure ordinaire "ConvolutionalAutoEncoderImpl" peut être déclarée comme structure du modèle "ConvolutionalAutoEncoder", mais probablement en héritant de la classe en interne. Êtes-vous là? (Attendu) Par conséquent, lors de l'accès aux variables membres, telles que "model-> to (device)", ** "->" (opérateur de flèche) </ font> ** Veuillez noter que vous devez utiliser.
Ensuite, concernant les points ci-dessus, nous expliquerons les points à noter lors de l'utilisation du module de classe nn. Vous pouvez utiliser "nn :: Sequential" comme en Python. Pour ajouter un module à "nn :: Sequential" en C ++, utilisez ** "push_back" </ font> ** comme le type de vecteur. Notez que vous devez utiliser ** "->" (opérateur de flèche) </ font> ** pour appeler la fonction "push_back". L'exemple d'implémentation ressemble à ce qui suit.
networks.cpp (extrait partiel / modification)
nn::Sequential sq;
sq->push_back(nn::Conv2d(nn::Conv2dOptions(3, 64, /*kernel_size=*/4).stride(2).padding(1).bias(false)));
sq->push_back(nn::BatchNorm2d(64));
sq->push_back(nn::ReLU(nn::ReLUOptions().inplace(true)));
Lors de la création de la transformation, des ensembles de données et du chargeur de données par vous-même, ** ".clone ()" est utilisé pour transmettre ** lors du passage de données de type tensoriel à d'autres variables. J'étais accro ici. Le type de tenseur est-il lié à la manipulation des graphiques de calcul? (Attendu), la valeur du tenseur peut changer si elle n'est pas définie de cette manière.
transforms.cpp (extrait partiel)
void transforms::Normalize::forward(torch::Tensor &data_in, torch::Tensor &data_out){
torch::Tensor data_out_src = (data_in - this->mean) / this->std;
data_out = data_out_src.clone();
return;
}
D'autres programmes sont presque les mêmes que la version Python, et il n'y a rien de particulièrement addictif. De plus, j'ai créé ma propre classe que je trouvais un peu difficile à utiliser car elle était différente de la version Python. Veuillez vous référer au GitHub suivant pour le programme spécifique. https://github.com/koba-jon/pytorch_cpp/tree/master/ConvAE
Je vais peut-être écrire un article de commentaire sur le code source. Si vous pensez que "c'est étrange", nous vous souhaitons la bienvenue, alors veuillez commenter.
En gros, on peut penser que c'est presque la même chose sauf pour la partie qui ne peut pas être aidée, comme la bibliothèque qui existe en Python n'existe pas en C ++. En outre, vous pouvez penser que vous n'avez pas changé depuis le programme GitHub.
Plus précisément, les contenus suivants ont été unifiés lors de la comparaison de la version Python et de la version C ++.
Pour chaque objet à comparer, 182 340 images 64x64 de celebA ont été utilisées, et un apprentissage en mini-lot d'un modèle d'auto-encodeur avec convolution 1 [d'époque] a été réalisé afin de minimiser l'erreur L1. ** "Time per [epoch]" </ font> ** et ** "GPU memory usage" </ font> J'ai vérifié **.
Ici, «temps par [époque]» inclut le temps de traitement de tqdm et des fonctions auto-créées. J'ai inclus cela parce que cela n'avait presque aucun effet sur le temps de traitement total, et parce qu'il est plus pratique d'avoir une visualisation lors de l'utilisation réelle de PyTorch, de nombreuses personnes l'utilisent.
De plus, en utilisant le modèle entraîné, 20 259 images de test ont été entrées une par une dans le modèle et testées. À ce moment-là ** "Vitesse moyenne de propagation avant" </ font> ** et ** "Erreur L1 entre l'image d'entrée et l'image de sortie" </ font> J'ai également vérifié> **.
Ensuite, j'ai appris et testé sans lancer autre chose que le "fichier d'exécution" et "nvidia-smi" (celui qui fonctionnait depuis le début quand Ubuntu a été lancé).
CPU(Core i7-8700) | GPU(GeForce GTX 1070) | |||||
---|---|---|---|---|---|---|
Python | C++ | Python | C++ | |||
Non déterministe th> | Définitif th> | |||||
Apprentissage td> | Heure [heure / époque] td> | 1 heure 04 minutes 49 secondes td> | 1 heure 03:00 td> | 5 minutes 53 secondes td> | 7 minutes 42 secondes td> | 17 minutes 36 secondes td> |
Mémoire GPU [Mio] td> | 2 | 9 | 933 | 913 | 2941 | |
Test td> | Vitesse [secondes / données] td> | 0.01189 | 0.01477 | 0.00102 | 0.00101 | 0.00101 |
Erreur L1 (MAE) td> | 0.12621 | 0.12958 | 0.12325 | 0.12104 | 0.13158 |
C ++ est un langage compilé. Par conséquent, je pensais que je battrais le langage d'interprétation Python ... mais ** les deux étaient de bons matchs **.
En termes de temps d'apprentissage, nous avons constaté que le CPU est presque le même et que le GPU est plus de deux fois plus lent que C ++ que Python. (Pourquoi?) Quant à ce résultat, le CPU est à peu près le même, mais il ne diffère grandement que lorsqu'il s'agit de GPU.
Est susceptible d'être mentionné. Alors que les personnes suivantes expérimentent, il semble qu'il ne fait aucun doute que ** Python est plus rapide </ font> ** lorsque le GPU est en cours d'exécution. https://www.noconote.work/entry/2019/01/11/151624
De plus, la vitesse et les performances de l'inférence (tests) sont presque les mêmes que celles de Python, donc ** Actuellement, Python peut être meilleur </ font> **.
L'utilisation de la mémoire du GPU est également importante pour une raison quelconque. (Même si la place de ReLU est définie sur True ...)
C'est le résultat de Python (GPU) déterministe et non déterministe, mais comme la formule l'indique clairement, déterministe est plus lent.
Après tout, l'heure va changer ici.
Vitesse d'apprentissage
1ère place: version Python (non déterministe, opération principale GPU)
2ème place: version Python (déterministe, opération principale GPU)
3e place: version C ++ (opération principale du GPU)
4ème place: opération principale du processeur (version Python, version C ++ similaire)
Vitesse de raisonnement
1ère place: opération principale du GPU (version Python, version C ++ similaire)
2ème place: opération principale du CPU (même niveau que la version Python et la version C ++)
Performance
Tous sont à peu près pareils
Cette fois, j'ai comparé la vitesse et les performances de PyTorch pour Python et C ++.
En conséquence, Python et C ++ sont presque les mêmes en termes de performances, j'ai donc pensé qu'il n'y aurait aucun problème à utiliser PyTorch de ** C ++ **. Cependant, ** À ce stade, il peut ne pas être recommandé de faire C ++ PyTorch pour la vitesse </ font> **.
Peut-être que l'API C ++ est toujours en évolution et pourrait être considérablement améliorée à l'avenir! A partir de maintenant, c'est mon attente!
Recommended Posts