[PYTHON] Apprendre un réseau neuronal à l'aide de Chainer

Ceci est l'article sur le 15e jour du Calendrier de l'Avent Chainer 2016 (je suis désolé d'être en retard ...).

Dans cet article, je me suis demandé: "Je peux m'entraîner en exécutant l'exemple de Chainer, mais comment puis-je entraîner l'ensemble de données d'origine et rendre le modèle pratique comme un serveur API Web?" Ce sera un article pour ceux qui le sont (je pense que c'est relativement pour les débutants).

Ici, nous allons créer un serveur API pour la classification d'images basé sur l'exemple de code imagenet dans le référentiel officiel Chainer GitHub. Est le but final.

supposition

Dans cet article, nous avons développé dans l'environnement suivant.

Collectez des images

~~ Il était difficile de collecter des données, donc par souci de simplicité, construisons un réseau neuronal qui classe trois types d'animaux: les chiens, les chats et les lapins.

La première étape consiste à collecter des images. Cette fois, nous avons collecté des images d'animaux à partir des sites suivants.

Pixabay

Stockez les images collectées dans le dossier d'origine avec la configuration indiquée dans la capture d'écran (en fait, vous devez collecter un plus grand nombre d'images).

imagenet-webapi-sample-1

De plus, le fichier suivant gère la correspondance entre les animaux et les étiquettes. Pour chaque classe, décrivez l'ID (nom du dossier pour stocker les images), le nom d'affichage de la classe et le libellé (index dans le vecteur de sortie du réseau neuronal) séparés par des espaces demi-largeur.

label_master.txt


000_chien chien 0
001_chat chat 1
002_lapin lapin 2

Prétraitez l'image

L'imagenet de Chainer semble être censé gérer des images 256x256, donc redimensionnez les images collectées. De plus, lors de l'entraînement avec imagenet, vous aurez besoin d'un fichier texte décrivant le chemin de l'image et le libellé de cette image, alors créez-le ici.

preprocess.py


# coding: utf-8

import os
import shutil
import re
import random

import cv2
import numpy as np


WIDTH = 256                             #Largeur après redimensionnement
HEIGHT = 256                            #Hauteur après redimensionnement

SRC_BASE_PATH = './original'            #Répertoire de base contenant les images téléchargées
DST_BASE_PATH = './resized'             #Répertoire de base pour stocker des images redimensionnées

LABEL_MASTER_PATH = 'label_master.txt'  #Un fichier qui résume la correspondance entre les classes et les étiquettes
TRAIN_LABEL_PATH = 'train_label.txt'    #Fichier d'étiquette pour l'apprentissage
VAL_LABEL_PATH = 'val_label.txt'        #Fichier d'étiquette pour vérification

VAL_RATE = 0.2                          #Pourcentage de données de validation


if __name__ == '__main__':
    with open(LABEL_MASTER_PATH, 'r') as f:
        classes = [line.strip().split(' ') for line in f.readlines()]

    #Initialiser l'emplacement de stockage de l'image après le redimensionnement
    if os.path.exists(DST_BASE_PATH):
        shutil.rmtree(DST_BASE_PATH)

    os.mkdir(DST_BASE_PATH)

    train_dataset = []
    val_dataset = []

    for c in classes:
        os.mkdir(os.path.join(DST_BASE_PATH, c[0]))

        class_dir_path = os.path.join(SRC_BASE_PATH, c[0])

        #Obtenez uniquement des images JPEG ou PNG
        files = [
            file for file in os.listdir(class_dir_path)
            if re.search(r'\.(jpe?g|png)$', file, re.IGNORECASE)
        ]

        #Redimensionner et exporter le fichier
        for file in files:
            src_path = os.path.join(class_dir_path, file)
            image = cv2.imread(src_path)
            resized_image = cv2.resize(image, (WIDTH, HEIGHT))
            cv2.imwrite(os.path.join(DST_BASE_PATH, c[0], file), resized_image)

        #Créer des données d'étiquette d'apprentissage / de vérification
        bound = int(len(files) * (1 - VAL_RATE))
        random.shuffle(files)
        train_files = files[:bound]
        val_files = files[bound:]

        train_dataset.extend([(os.path.join(c[0], file), c[2]) for file in train_files])
        val_dataset.extend([(os.path.join(c[0], file), c[2]) for file in val_files])

    #Fichier d'étiquette d'apprentissage de sortie
    with open(TRAIN_LABEL_PATH, 'w') as f:
        for d in train_dataset:
            f.write(' '.join(d) + '\n')

    #Fichier d'étiquette de vérification de sortie
    with open(VAL_LABEL_PATH, 'w') as f:
        for d in val_dataset:
            f.write(' '.join(d) + '\n')

Exécutez le code ci-dessus.

$ python preprocess.py

Espérons qu'une image redimensionnée de 256x256 sera créée sous le répertoire redimensionné, et train_label.txt, val_label.txt seront créés à la racine du projet.

Vous pouvez modifier le rapport entre les données d'entraînement et les données de vérification en modifiant la valeur de «VAL_RATE» dans preprocess.py. Dans le code ci-dessus, le ratio est «apprentissage: validation = 8: 2».

Après avoir redimensionné l'image, l'étape suivante consiste à créer une image moyenne pour l'ensemble de données d'entraînement (la soustraction de l'image moyenne de l'image d'entrée est une sorte de processus de normalisation, dans lequel vous créez l'image moyenne pour cela). Placez compute_mean.py dans l'ensemble imagen du référentiel GitHub de Chainer dans votre projet et exécutez la commande suivante:

$ python compute_mean.py train_label.txt -R ./resized/

Après exécution, mean.npy sera généré.

imagenet-webapi-sample-2

apprendre

Nous allons apprendre à utiliser l'image redimensionnée.

imagenet fournit plusieurs architectures neuralnet, mais cette fois j'essaierai d'utiliser GoogleNetBN (quelques améliorations de code seront apportées dans la section suivante). Placez train_imagenet.py et googlenetbn.py depuis imagenet dans votre projet.

L'apprentissage sera exécuté lorsque la commande suivante sera exécutée. Pour le nombre d'époques («-E»), spécifiez une valeur appropriée en fonction de la quantité de données et de la tâche. Spécifiez également l'ID GPU (-g) en fonction de votre environnement (l'option -g n'est pas requise lors de l'apprentissage avec le CPU).

$ python train_imagenet.py -a googlenetbn -E 100 -g 0 -R ./resized/ ./train_label.txt ./val_label.txt --test

Les modèles formés et les journaux sont stockés dans le dossier de résultats.

Améliorez le code imagenet

Utilisez le modèle entraîné pour classer (estimer) des images arbitraires. L'exemple de code imagenet contient uniquement le code des données d'entraînement et de validation, et vous devez ajouter le code pour effectuer l'estimation.

Cependant, fondamentalement, sur la base du traitement de __call__ (), la partie qui renvoie la valeur de la perte doit être remplacée par la valeur de probabilité. Créons une nouvelle méthode appelée «predict ()» et décrivons ce processus.

googlenetbn.py(Extrait)


class GoogLeNetBN(chainer.Chain):


    # --- (réduction) ---


    def predict(self, x):
        test = True

        h = F.max_pooling_2d(
            F.relu(self.norm1(self.conv1(x), test=test)), 3, stride=2, pad=1)
        h = F.max_pooling_2d(
            F.relu(self.norm2(self.conv2(h), test=test)), 3, stride=2, pad=1)

        h = self.inc3a(h)
        h = self.inc3b(h)
        h = self.inc3c(h)
        h = self.inc4a(h)

        # a = F.average_pooling_2d(h, 5, stride=3)
        # a = F.relu(self.norma(self.conva(a), test=test))
        # a = F.relu(self.norma2(self.lina(a), test=test))
        # a = self.outa(a)
        # a = F.softmax(a)

        h = self.inc4b(h)
        h = self.inc4c(h)
        h = self.inc4d(h)

        # b = F.average_pooling_2d(h, 5, stride=3)
        # b = F.relu(self.normb(self.convb(b), test=test))
        # b = F.relu(self.normb2(self.linb(b), test=test))
        # b = self.outb(b)
        # b = F.softmax(b)

        h = self.inc4e(h)
        h = self.inc5a(h)
        h = F.average_pooling_2d(self.inc5b(h), 7)
        h = self.out(h)

        return F.softmax(h)

Voir ici pour le code complet de la version améliorée de googlenetbn.py.

Si vous regardez le code ci-dessus, vous verrez que c'est à peu près la même chose que la gestion de __call__ (). Cependant, bien que GoogleNet dispose de 3 sorties (principale + 2 auxiliaires), 2 sorties auxiliaires ne sont pas nécessaires au moment de l'estimation (ce classificateur auxiliaire est introduit comme contre-mesure de la disparition du gradient pendant l'apprentissage). ) [^ 1]. La partie commentée correspond à cette partie.

Dans le code ci-dessus, la fonction softmax est appliquée à la fin, mais il est normal d'omettre softmax comme return h. Si vous n'avez pas besoin de normaliser votre score dans la plage de 0 à 1 et que vous souhaitez maintenir la quantité de calcul aussi faible que possible, vous pouvez l'omettre.

J'ai utilisé GoogleNetBN ici, mais bien sûr, d'autres architectures de l'exemple imagenet, comme AlexNet, peuvent être modifiées de la même manière. De plus, je pense qu'il est bon de créer ResNet, etc.

Créer un serveur API Web

Ensuite, créez un serveur API Web. Ici, nous allons construire un serveur en utilisant le framework Web Python Flask.

En tant qu'image, écrivez du code qui effectue un traitement tel que l'envoi d'une image du client au serveur par HTTP POST, la classification de l'image côté serveur et le renvoi du résultat dans JSON.

server.py


# coding: utf-8

from __future__ import print_function
from flask import Flask, request, jsonify
import argparse

import cv2
import numpy as np
import chainer

import googlenetbn                  #Si vous souhaitez utiliser une autre architecture, veuillez réécrire ici


WIDTH = 256                         #Largeur après redimensionnement
HEIGHT = 256                        #Hauteur après redimensionnement
LIMIT = 3                           #Nombre de cours

model = googlenetbn.GoogLeNetBN()   #Si vous souhaitez utiliser une autre architecture, veuillez réécrire ici

app = Flask(__name__)

#Évitez de convertir le japonais en JSON en code ASCII(Pour faciliter la visualisation avec la commande curl. Il n'y a pas de problème même s'il est converti en ASCII)
app.config['JSON_AS_ASCII'] = False


# train_imagenet.get py PreprocessedDataset_example()Référence
def preproduce(image, crop_size, mean):
    #redimensionner
    image = cv2.resize(image, (WIDTH, HEIGHT))

    # (height, width, channel) -> (channel, height, width)Conversion en
    image = image.transpose(2, 0, 1)

    _, h, w = image.shape

    top = (h - crop_size) // 2
    left = (w - crop_size) // 2
    bottom = top + crop_size
    right = left + crop_size

    image = image[:, top:bottom, left:right]
    image -= mean[:, top:bottom, left:right]
    image /= 255

    return image


@app.route('/')
def hello():
    return 'Hello!'


#API de classification d'images
# http://localhost:8090/Lancer une image pour prédire renvoie un résultat en JSON
@app.route('/predict', methods=['POST'])
def predict():
    #Chargement d'image
    file = request.files['image']
    image = cv2.imdecode(np.fromstring(file.stream.read(), np.uint8), cv2.IMREAD_COLOR)

    #Prétraitement
    image = preproduce(image.astype(np.float32), model.insize, mean)

    #Estimation
    p = model.predict(np.array([image]))[0].data
    indexes = np.argsort(p)[::-1][:LIMIT]

    #Renvoie le résultat au format JSON
    return jsonify({
        'result': [[classes[index][1], float(p[index])] for index in indexes]
    })


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--initmodel', type=str, default='',
                        help='Initialize the model from given file')
    parser.add_argument('--mean', '-m', default='mean.npy',
                        help='Mean file (computed by compute_mean.py)')
    parser.add_argument('--labelmaster', '-l', type=str, default='label_master.txt',
                        help='Label master file')
    parser.add_argument('--gpu', '-g', type=int, default=-1,
                        help='GPU ID (negative value indicates CPU')
    args = parser.parse_args()

    mean = np.load(args.mean)
    chainer.serializers.load_npz(args.initmodel, model)

    with open(args.labelmaster, 'r') as f:
        classes = [line.strip().split(' ') for line in f.readlines()]

    if args.gpu >= 0:
        chainer.cuda.get_device(args.gpu).use()
        model.to_gpu()

    app.run(host='0.0.0.0', port=8090)

Le résultat de la classification JSON suppose la structure suivante. Dans le tableau interne, le premier élément est le nom de la classe et le deuxième élément est le score. Chaque classe est triée par ordre décroissant de score.

{
  "result": [
    [
      "chien",
      0.4107133746147156
    ], 
    [
      "Lapins",
      0.3368038833141327
    ], 
    [
      "Chat",
      0.2524118423461914
    ]
  ]
}

Vous pouvez également spécifier le nombre de classes supérieures à obtenir avec la constante LIMIT. Comme il n'y a que 3 types d'animaux cette fois, nous définissons LIMIT = 3, mais par exemple, s'il y a 100 types de classes au total et que vous voulez le top 10 d'entre eux, vous n'avez besoin que de LIMIT = 10, 1ère place. Dans ce cas, vous pouvez spécifier quelque chose comme «LIMIT = 1».

Maintenant que le code est complet, démarrons réellement le serveur.

$ python server.py --initmodel ./result/model_iter_120
 * Running on http://0.0.0.0:8090/ (Press CTRL+C to quit)

Dans cet état, préparez un autre shell et utilisez la commande curl pour envoyer l'image au serveur (préparez une image de test de manière appropriée). Si le résultat est renvoyé, c'est un succès.

$ curl -X POST -F [email protected] http://localhost:8090/predict
{
  "result": [
    [
      "Lapins", 
      0.4001327157020569
    ], 
    [
      "Chat", 
      0.36795011162757874
    ], 
    [
      "chien", 
      0.23191720247268677
    ]
  ]
}

Le serveur API est maintenant terminé! Après cela, si vous créez librement un frontal et implémentez un mécanisme pour accéder au serveur API, vous pouvez le publier en tant que service Web.

Pour faire un frontal

TODO: Je prévois d'écrire un article séparé à une date ultérieure.

en conclusion

Dans cet article, j'ai parcouru les étapes de création d'un serveur API Web à partir de la méthode d'apprentissage d'un réseau de neurones à l'aide de Chainer (bien qu'il ait été dit que c'était pour les débutants, il y avait des endroits où l'explication était appropriée, mais lisez jusqu'ici. Merci de votre collaboration).

Compte tenu de la gestion des erreurs et du réglage fin, je dois le rendre un peu plus ferme, mais je pense que c'est à peu près comme ça. De plus, je pense que le traitement d'image est tout à fait approprié, il y a donc place à une amélioration considérable en termes de précision.

Utilisons de plus en plus Chainer pour créer des produits d'apprentissage en profondeur!

Exemple de code pour cet article

Recommended Posts

Apprendre un réseau neuronal à l'aide de Chainer
Créez un serveur Web API à une vitesse explosive en utilisant HUG
Apprentissage par renforcement 10 Essayez d'utiliser un réseau neuronal formé.
Préparer un pseudo serveur API à l'aide d'actions GitHub
Créer un pseudo serveur d'API REST à l'aide de pages GitHub
Construire un modèle seq2seq en utilisant la création et l'apprentissage du modèle d'API fonctionnelle de Keras
Créez un serveur API Web ultra-rapide avec Falcon
Optimisation d'image côté serveur à l'aide de l'API Web de TinyPNG
Transformez votre téléphone intelligent Android en serveur Web à l'aide de python.
Contre-mesures contre le proxy lors de l'utilisation de l'API WEB
Créer une application Web avec Flask ②
Créer une application Web avec Flask ①
Créer un modèle d'apprentissage à l'aide de MNIST
Créer une application Web avec Flask ③
Créer une API CRUD à l'aide de l'API rapide
Créer une application Web avec Flask ④
J'ai créé Chatbot en utilisant l'API LINE Messaging et Python (2) ~ Server ~
Configuration du serveur d'API Web d'inférence de modèle d'apprentissage automatique [Exemple d'implémentation d'API rapide disponible]
Démarrez un serveur Web en utilisant Bottle et Flask (j'ai également essayé d'utiliser Apache)
Créez facilement un serveur DNS en utilisant Twisted
Redirection de port d'un serveur Web à l'aide d'iptables
Configurer un serveur de messagerie avec Twisted
Configurons un serveur WEB avec Chromebook
J'ai créé une API Web
J'ai essayé de comprendre attentivement la fonction d'apprentissage dans le réseau de neurones sans utiliser la bibliothèque d'apprentissage automatique (première moitié)
Une histoire sur l'apprentissage automatique simple avec TensorFlow
Procédure pour utiliser l'API WEB de TeamGant (en utilisant python)
Livre mis à jour: Évolution vers un "serveur Web décent"
Accédez à l'API Web à l'aide de requêtes Exemple: Flickr
Créez facilement un serveur API à l'aide du module go-json-rest
Créons une API REST en utilisant SpringBoot + MongoDB
Ecrire un serveur TCP à l'aide du module SocketServer
Créer une carte Web en utilisant Python et GDAL
Lancer un serveur Web avec Python et Flask
Comment héberger le traitement du backend d'application Web en Python à l'aide d'un sous-domaine de serveur de location