Dies ist der Artikel am 15. Tag von Chainer Adventskalender 2016 (Es tut mir leid, dass ich zu spät gekommen bin ...).
In diesem Artikel fragte ich mich: "Ich kann trainieren, indem ich Chainers Beispiel ausführe, aber wie kann ich den Originaldatensatz trainieren und das Modell wie einen Web-API-Server praktisch machen?" Es ist ein Artikel für diejenigen, die es sind (ich denke, dass es relativ für Anfänger ist).
Hier erstellen wir einen API-Server für die Bildklassifizierung basierend auf dem Beispielcode imagenet im offiziellen Chainer GitHub-Repository. Ist das Endziel.
In diesem Artikel haben wir in der folgenden Umgebung entwickelt.
~~ Es war schwierig, Daten zu sammeln. Lassen Sie uns der Einfachheit halber ein neuronales Netz aufbauen, das drei Arten von Tieren klassifiziert: Hunde, Katzen und Kaninchen.
Der erste Schritt ist das Sammeln von Bildern. Dieses Mal haben wir Bilder von Tieren von den folgenden Seiten gesammelt.
Speichern Sie die gesammelten Bilder im Originalordner mit der im Screenshot gezeigten Konfiguration (tatsächlich müssen Sie eine größere Anzahl von Bildern sammeln).
Darüber hinaus verwaltet die folgende Datei die Korrespondenz zwischen Tieren und Etiketten. Beschreiben Sie für jede Klasse die ID (Ordnername zum Speichern von Bildern), den Anzeigenamen der Klasse und die Bezeichnung (Index im Ausgabevektor des neuronalen Netzes), die durch Leerzeichen halber Breite getrennt sind.
label_master.txt
000_Hund Hund 0
001_Katze Katze 1
002_Kaninchen Kaninchen 2
Chainers Imagenet scheint 256x256 Bilder zu verarbeiten, also ändern Sie die Größe der gesammelten Bilder. Wenn Sie mit imagenet trainieren, benötigen Sie außerdem eine Textdatei, die den Pfad des Bildes und die Beschriftung dieses Bildes beschreibt. Erstellen Sie sie daher hier.
preprocess.py
# coding: utf-8
import os
import shutil
import re
import random
import cv2
import numpy as np
WIDTH = 256 #Breite nach Größenänderung
HEIGHT = 256 #Höhe nach Größenänderung
SRC_BASE_PATH = './original' #Basisverzeichnis mit heruntergeladenen Bildern
DST_BASE_PATH = './resized' #Basisverzeichnis zum Speichern von Bildern mit geänderter Größe
LABEL_MASTER_PATH = 'label_master.txt' #Eine Datei, die die Entsprechung zwischen Klassen und Beschriftungen zusammenfasst
TRAIN_LABEL_PATH = 'train_label.txt' #Etikettendatei zum Lernen
VAL_LABEL_PATH = 'val_label.txt' #Etikettendatei zur Überprüfung
VAL_RATE = 0.2 #Prozentsatz der Validierungsdaten
if __name__ == '__main__':
with open(LABEL_MASTER_PATH, 'r') as f:
classes = [line.strip().split(' ') for line in f.readlines()]
#Initialisieren Sie den Speicherort des Bildes nach der Größenänderung
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])
#Holen Sie sich nur JPEG- oder PNG-Bilder
files = [
file for file in os.listdir(class_dir_path)
if re.search(r'\.(jpe?g|png)$', file, re.IGNORECASE)
]
#Ändern Sie die Größe und geben Sie die Datei aus
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)
#Erstellen Sie Lern- / Verifizierungsetikettendaten
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])
#Lernetikettendatei ausgeben
with open(TRAIN_LABEL_PATH, 'w') as f:
for d in train_dataset:
f.write(' '.join(d) + '\n')
#Ausgabe-Bestätigungsetikettendatei
with open(VAL_LABEL_PATH, 'w') as f:
for d in val_dataset:
f.write(' '.join(d) + '\n')
Führen Sie den obigen Code aus.
$ python preprocess.py
Hoffentlich wird ein Bild mit einer Größe von 256 x 256 unter dem Verzeichnis mit geänderter Größe erstellt, und im Projektstamm wird "train_label.txt", "val_label.txt" erstellt.
Sie können das Verhältnis von Trainingsdaten zu Verifizierungsdaten ändern, indem Sie den Wert von "VAL_RATE" in preprocess.py ändern. Im obigen Code lautet das Verhältnis "Lernen: Validierung = 8: 2".
Nach dem Ändern der Bildgröße besteht der nächste Schritt darin, ein Durchschnittsbild für den Trainingsdatensatz zu erstellen (das Subtrahieren des Durchschnittsbilds vom Eingabebild ist eine Art Normalisierungsprozess, bei dem Sie das Durchschnittsbild dafür erstellen). Platzieren Sie compute_mean.py im Bild des GitHub-Repositorys von Chainer in Ihrem Projekt und führen Sie den folgenden Befehl aus:
$ python compute_mean.py train_label.txt -R ./resized/
Nach der Ausführung wird mean.npy generiert.
Wir werden lernen, das verkleinerte Bild zu verwenden.
imagenet bietet mehrere Neuralnet-Architekturen, aber dieses Mal werde ich versuchen, "GoogleNetBN" zu verwenden (einige Codeverbesserungen werden im nächsten Abschnitt vorgenommen). Platzieren Sie train_imagenet.py und googlenetbn.py von imagenet in Ihrem Projekt.
Das Lernen wird ausgeführt, wenn der folgende Befehl ausgeführt wird. Geben Sie für die Anzahl der Epochen (-E
) einen geeigneten Wert an, der der Datenmenge und der Aufgabe entspricht. Geben Sie außerdem die GPU-ID (-g
) entsprechend Ihrer Umgebung an (die Option -g
ist beim Lernen mit der CPU nicht erforderlich).
$ python train_imagenet.py -a googlenetbn -E 100 -g 0 -R ./resized/ ./train_label.txt ./val_label.txt --test
Geschulte Modelle und Protokolle werden im Ergebnisordner gespeichert.
Verwenden Sie das trainierte Modell, um beliebige Bilder zu klassifizieren (zu schätzen). Der Imagenet-Beispielcode enthält nur den Code für Trainings- und Validierungsdaten, und Sie müssen den Code hinzufügen, um die Schätzung vorzunehmen.
Grundsätzlich sollte jedoch basierend auf der Verarbeitung von "call ()" der Teil, der den Verlustwert zurückgibt, in den Wahrscheinlichkeitswert geändert werden. Erstellen wir eine neue Methode namens "pred ()" und beschreiben diesen Prozess.
googlenetbn.py(Auszug)
class GoogLeNetBN(chainer.Chain):
# --- (Kürzung) ---
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)
Den vollständigen Code der verbesserten Version von googlenetbn.py finden Sie unter hier.
Wenn Sie sich den obigen Code ansehen, werden Sie feststellen, dass er dem Umgang mit __call __ ()
ziemlich ähnlich ist.
Obwohl GoogleNet über 3 Ausgänge verfügt (Haupt + 2 Hilfsausgänge), sind zum Zeitpunkt der Schätzung keine 2 Hilfsausgänge erforderlich (dieser Hilfsklassifizierer wird als Gegenmaßnahme für das Verschwinden des Gradienten während des Lernens eingeführt). ) [^ 1]. Der auskommentierte Teil entspricht diesem Teil.
Der obige Code wendet die Softmax-Funktion am Ende an, aber es ist in Ordnung, Softmax als "return h" wegzulassen. Wenn Sie Ihre Punktzahl nicht auf den Bereich 0 bis 1 normalisieren müssen und den Rechenaufwand so gering wie möglich halten möchten, können Sie ihn weglassen.
Ich habe hier GoogleNetBN verwendet, aber natürlich können andere Architekturen im Imagenet-Beispiel, wie z. B. AlexNet, auf die gleiche Weise geändert werden. Ich denke auch, dass es gut ist, ResNet usw. zu erstellen.
Erstellen Sie als Nächstes einen Web-API-Server. Hier erstellen wir einen Server mit dem Python-Webframework Flask.
Schreiben Sie als Image Code, der die Verarbeitung ausführt, z. B. das Senden eines Images vom Client an den Server per HTTP-POST, das Klassifizieren des Images auf der Serverseite und das Zurückgeben des Ergebnisses in 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 #Wenn Sie eine andere Architektur verwenden möchten, schreiben Sie diese bitte hier neu
WIDTH = 256 #Breite nach Größenänderung
HEIGHT = 256 #Höhe nach Größenänderung
LIMIT = 3 #Anzahl der Klassen
model = googlenetbn.GoogLeNetBN() #Wenn Sie eine andere Architektur verwenden möchten, schreiben Sie diese bitte hier neu
app = Flask(__name__)
#Vermeiden Sie die Konvertierung von Japanisch in JSON in ASCII-Code(Um es einfacher zu machen, mit dem Befehl curl zu sehen. Es gibt kein Problem, auch wenn es in ASCII konvertiert wird)
app.config['JSON_AS_ASCII'] = False
# train_imagenet.Holen Sie sich py PreprocessedDataset_example()Referenz
def preproduce(image, crop_size, mean):
#Größe ändern
image = cv2.resize(image, (WIDTH, HEIGHT))
# (height, width, channel) -> (channel, height, width)Umstellung auf
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!'
#Bildklassifizierungs-API
# http://localhost:8090/Wenn Sie ein Bild zur Vorhersage werfen, wird ein Ergebnis in JSON zurückgegeben
@app.route('/predict', methods=['POST'])
def predict():
#Bild wird geladen
file = request.files['image']
image = cv2.imdecode(np.fromstring(file.stream.read(), np.uint8), cv2.IMREAD_COLOR)
#Vorverarbeitung
image = preproduce(image.astype(np.float32), model.insize, mean)
#Geschätzt
p = model.predict(np.array([image]))[0].data
indexes = np.argsort(p)[::-1][:LIMIT]
#Gibt das Ergebnis als JSON zurück
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)
Das Klassifizierungsergebnis JSON nimmt die folgende Struktur an. Im inneren Array ist das erste Element der Klassenname und das zweite Element die Punktzahl. Jede Klasse ist in absteigender Reihenfolge der Punktzahl sortiert.
{
"result": [
[
"Hund",
0.4107133746147156
],
[
"Kaninchen",
0.3368038833141327
],
[
"Katze",
0.2524118423461914
]
]
}
Sie können auch angeben, wie viele Top-Klassen mit der Konstante LIMIT
erhalten werden sollen. Da es diesmal nur 3 Tierarten gibt, setzen wir "LIMIT = 3". Wenn es jedoch insgesamt 100 Arten von Klassen gibt und Sie die Top 10 von ihnen wollen, benötigen Sie nur "LIMIT = 10", 1. Platz. In diesem Fall können Sie etwas wie "LIMIT = 1" angeben.
Nachdem der Code vollständig ist, starten wir den Server.
$ python server.py --initmodel ./result/model_iter_120
* Running on http://0.0.0.0:8090/ (Press CTRL+C to quit)
Bereiten Sie in diesem Zustand eine andere Shell vor und senden Sie das Image mit dem Befehl curl an den Server (bereiten Sie ein Test-Image entsprechend vor). Wenn das Ergebnis zurückgegeben wird, ist es ein Erfolg.
$ curl -X POST -F [email protected] http://localhost:8090/predict
{
"result": [
[
"Kaninchen",
0.4001327157020569
],
[
"Katze",
0.36795011162757874
],
[
"Hund",
0.23191720247268677
]
]
}
Der API-Server ist jetzt fertig! Wenn Sie danach ein Front-End frei erstellen und einen Mechanismus für den Zugriff auf den API-Server implementieren, können Sie es als Webdienst veröffentlichen.
TODO: Ich habe vor, zu einem späteren Zeitpunkt einen separaten Artikel zu schreiben.
In diesem Artikel habe ich die Schritte zum Erstellen eines Web-API-Servers anhand der Methode zum Erlernen eines neuronalen Netzes mit Chainer durchlaufen (obwohl gesagt wurde, dass es sich um Anfänger handelt, gab es einige Stellen, an denen die Erklärung angemessen war, aber lesen Sie diese bis jetzt. Danke für Ihre Kooperation).
In Anbetracht der Fehlerbehandlung und der Feineinstellung muss ich es etwas fester machen, aber ich denke, dass es ungefähr so ist. Ich denke auch, dass die Bildverarbeitung durchaus angemessen ist, sodass die Genauigkeit erheblich verbessert werden kann.
Verwenden wir Chainer immer mehr, um Deep-Learning-Produkte herzustellen!
Beispielcode für diesen Artikel
Recommended Posts