Da ich Theano vor kurzem als dritte Bibliothek nach Chainer und Caffe verwendet habe, werde ich die Implementierung des Convolutional Neural Network (CNN) mit Theano erläutern. Andere Artikel und Offizielles Tutorial sind für die grundlegende Verwendung von Theano leicht zu verstehen. Bitte beziehen Sie sich darauf. Dieser Artikel wird gemäß Deep Convolutional Network of Deep Learning Tutorial erläutert. Der Code wurde zur Erklärung geändert, aber der allgemeine Ablauf ist der gleiche. Es gibt keine Erklärung für Deep Learning selbst oder die grundlegende Verwendung von Theano in der Erklärung der CNN-Implementierung mit Theano. Weitere Informationen zu diesem Bereich finden Sie in anderen Artikeln. Auch wenn darin die Erklärung der Implementierung steht, gibt es viele Punkte, die ich nicht erreichen kann, da die Implementierungsfähigkeit der Person selbst nicht so hoch ist und sie einige Tage nach Beginn der Verwendung von Theano nicht gut Englisch kann (offizielles Tutorial ist Englisch). (Erwarten Sie nicht so viel, weil es sich wie ein Memorandum der Person anfühlt.)
LeNet Wir werden die kleinste handschriftliche Zeichenerkennung basierend auf LeNet gemäß dem Deep-Learning-Tutorial durchführen. LeNet ist ein Basis-CNN, das aus 2 Faltungsschichten, 2 Poolschichten und einer vollständig verbundenen Schicht besteht. In diesem Artikel unterscheiden sich die Details wie die Aktivierungsfunktion von den ursprünglichen, aber die Grundstruktur ist dieselbe. (Bitte lesen Sie das Originalpapier für eine detaillierte Erklärung von LeNet.)
Wir werden CNN basierend auf dem oben genannten LeNet implementieren. Als Voraussetzung wird angenommen, dass Folgendes importiert wird.
import theano
import theano.tensor as T
import numpy as np
Zunächst werden wir die Faltungsschicht implementieren. Theano stellt T.nnet.conv.conv2d als Faltungssymbol bereit. Theano verwendet numpy und theano.shared, weil Sie die Gewichte und Vorurteile selbst schreiben müssen. Gewichte und Verzerrungen sind Werte, die durch Lernen aktualisiert werden. Daher definieren wir sie mit theano.shared. Da es schwierig ist, die Ebene jedes Mal zu beschreiben, wenn Sie eine Faltungsebene hinzufügen, definieren Sie sie als Klasse. Das Folgende ist ein Implementierungsbeispiel für die Faltungsschichtklasse.
class Conv2d(object):
def __init__(self, input, out_c, in_c, k_size)
self._input = input #Symbol eingegeben werden
self._out_c = out_c #Anzahl der Ausgangskanäle
self._in_c = in_c #Anzahl der Eingangskanäle
w_shp = (out_c, in_c, k_size, k_size) #Gewichtsform
w_bound = np.sqrt(6. / (in_c * k_size * k_size + \
out_c * k_size * k_size)) #Gewichtsbeschränkungen
#Definition des Gewichts
self.W = theano.shared( np.asarray(
np.random.uniform( #Mit Zufallszahlen initialisieren
low=-w_bound,
high=w_bound,
size=w_shp),
dtype=self._intype.dtype), name ='W', borrow=True)
b_shp = out_c, #Vorspannungsform
#Definition der Voreingenommenheit(Mit Null initialisieren)
self.b = theano.shared(np.zeros(b_shp,
dtype=self._input.dtype), name ='b', borrow=True)
#Definition von Faltungssymbolen
self.output = T.nnet.conv.conv2d(self._input, self.W) \
+ self.b.dimshuffle('x', 0, 'x', 'x')
#Speichern Sie aktualisierte Parameter
self.params = [self.W, self.b]
dimshuffle arbeitet so, dass es der Dimension des Bias-Terms von Vektor zu Tensor4 entspricht, der die Ausgabe von T.nnet.conv.conv2d ist. Es fühlt sich an wie eine Kombination aus Umformung und np.transponieren. Im Fall von ('x', 0, 'x', 'x') wird die Form von self.b zu (1, self.b.shape [0], 1, 1).
Ursprünglich ist die Aktivierungsfunktion von LeNet tanh, aber dieses Mal werden wir relu verwenden. relu ist eine Aktivierungsfunktion, die durch eine einfache Formel namens max (0, x) ausgedrückt wird. Da Theano kein Relu-Symbol hat, müssen Sie es selbst definieren. Das T.max () -Symbol von Theano ist etwas speziell geschrieben, da es keine reellen Werte enthalten kann (obwohl es einen Weg gibt) und die if-Anweisung nicht für das Symbol verwendet werden kann. Das Folgende ist ein Beispiel für die Implementierung von relu.
class relu(object):
def __init__(self, input):
self._input = input
self.output = T.switch(self._input < 0, 0, self._input)
Die Pooling-Ebene wird in Theano platziert und das Symbol wird in theano.tensor.signal.pool.pool_2d definiert. Die Pooling-Ebene ist einfach zu schreiben, da im Gegensatz zum Falten keine Symbole für die Aktualisierung wie Gewichte und Verzerrungen vorbereitet werden müssen. Unten finden Sie ein Beispiel für die Implementierung der Pooling-Schicht.
from theano.tensor.signal import pool
class Pool2d(object):
def __init__(self, input, k_size, st, pad=0, mode='max'):
self._input = input
#Definition von Pooling-Layer-Symbolen
self.output = pool.pool_2d(self._input,
(k_size, k_size), #Kernelgröße
ignore_border=True, #Kantenbearbeitung(Grundsätzlich wahr und ok,Einzelheiten finden Sie im offiziellen Dokument)
st=(st, st), #schreiten
padding=(pad, pad), #Polsterung
mode=mode) #Arten von Pooling('max', 'sum', 'average_inc_pad', 'average_exc_pad')
Die vollständig verbundene Schicht wird von mir beschrieben, weil das Symbol nicht in theano erstellt wurde, sondern durch die Berechnung des inneren Produkts der Matrix ausgedrückt werden kann und das Symbol für das innere Produkt von T.dot () angegeben wird, was nicht besonders schwierig ist. Wie bei der Faltungsschicht gibt es Gewichte und Vorspannungen, so dass jede definiert ist. Das Folgende ist ein Implementierungsbeispiel für die vollständig verbundene Schicht.
class FullyConnect(object):
def __init__(self, input, inunit, outunit):
self._input = input
#Definition des Gewichts
W = np.asarray(
np.random.uniform(
low=-np.sqrt(6. / (inunit + outunit)),
high=np.sqrt(6. / (inunit + outunit)),
size=(inunit, outunit)
),
dtype=theano.config.floatX)
self.W = theano.shared(value=W, name='W', borrow=True)
#Definition der Voreingenommenheit
b = np.zeros((outunit,), dtype=theano.config.floatX) #Mit Null initialisieren
self.b = theano.shared(value=b, name='b', borrow=True)
#Definition von vollständig verbundenen Ebenensymbolen
self.output = T.dot(self._input, self.W) + self.b
#Speichern Sie aktualisierte Parameter
self.params = [self.W, self.b]
Die Verlustfunktion verwendet die Softmax-Kreuzentropie, um die 10-Klassen-Klassifikation von Mnist zu lösen. Das Softmax-Symbol befindet sich in T.nnet.softmax () in Theano. Verwenden Sie dieses Symbol. Das Folgende ist ein Implementierungsbeispiel.
class softmax(object):
def __init__(self, input, y):
self._input = input
#Softmax-Symboldefinition
self.output = nnet.softmax(self._input)
#Symboldefinition der Kreuzentropie(Die Formel ist Summe, aber hier verwenden wir Mittelwert.)
self.cost = -T.mean(T.log(self.output)[T.arange(y.shape[0]), y])
y steht für das Lehrerbezeichnungssymbol. [T.arange (y.shape [0]), y] bedeutet, von y [0] zu y [y.shape [0] -1] zu addieren, wenn T.mean ausgeführt wird.
LeNet
Nachdem wir jede Ebene definiert haben, fahren wir mit der LeNet-Implementierung fort. Bereiten Sie zunächst die wichtigsten Daten vor. Die pkl-Daten sind unter http://www.iro.umontreal.ca/~lisa/deep/data/mnist/mnist.pkl.gz verfügbar. Laden Sie sie daher in den entsprechenden Ordner herunter. Extrahieren Sie hier Zug-, Validierungs- und Testdaten. Unten finden Sie ein Beispiel für das Laden von Daten.
import gzip
import cPickle
def shared_dataset(data_xy):
data_x, data_y = data_xy
set_x = theano.shared(np.asarray(data_x,
dtype=theano.config.floatX).reshape(-1,1,28,28),
borrow=True)
set_y = T.cast(theano.shared(np.asarray(data_y,
dtype=theano.config.floatX), borrow=True), 'int32')
return set_x, set_y
with open('/path/to/mnist.pkl.gz', 'rb') as f:
train_set, valid_set, test_set = cPickle.load(f)
train_set_x, train_set_y = shared_dataset(train_set)
valid_set_x, valid_set_y = shared_dataset(valid_set)
test_set_x, test_set_y = shared_dataset(test_set)
Zur Vereinfachung der Implementierung werden die Daten hier als Symbol für theano.shared definiert, die Eingabe kann jedoch ein Array von numpy sein. Wenn Sie es jedoch in theano.shared definieren, wird die Implementierung etwas sauberer. Definieren Sie als Nächstes die Eingabedaten und das Symbol der Lehrerbezeichnung. Da die Eingabedaten 4 Dimensionen haben (Anzahl der Stapel, Anzahl der Kanäle, Länge, Breite), ist es T.tensor4 (), und da die Lehrerbezeichnung ein Vektor von eindimensionalen ganzzahligen Werten ist, ist es T.ivector ().
x = T.tensor4() #Eingabedatensymbol
y = T.ivector() #Ausgabedatensymbol
Ab hier wird es zur Definition jeder Schicht. Ich habe jede Klasse oben erstellt, damit es etwas einfacher zu schreiben ist.
conv1 = Conv2d(x, 20, 1, 5) #Mit x als Eingang beträgt der Ausgang 20 Kanäle, der Eingang 1 Kanal, die Kernelgröße 5
relu1 = relu(conv1.output) #Nehmen Sie die Ausgabe von conv1 als Eingabe
pool1 = Pool2d(relu1.output, 2, 2) #Nehmen Sie den Ausgang von relu1 als Eingang,Kernel Größe 2, Schritt 2
conv2 = Conv2d(pool1.output, 50, 20, 5) #Der Ausgang von poo1 wird als Eingang genommen, der Ausgang ist 50 Kanäle, der Eingang ist 20 Kanäle und die Kernelgröße ist 5.
relu2 = relu(conv2.output) #Nehmen Sie die Ausgabe von conv2 als Eingabe
pool2 = Pool2d(relu2.output, 2, 2) #Nehmen Sie den Ausgang von relu2 als Eingang,Kernel Größe 2, Schritt 2
fc1_input = pool2.output.flatten(2) #Das Ausgabesymbol von Pool2 ist T..abflachen für Tensor4()Zum Anpassen des Eingabesymbols der vollständig verbundenen Ebene mit
fc1 = FullyConnect(fc1_input, 50*4*4, 500) #50 Eingabeeinheiten*4*4(Anzahl der Kanäle*Vertikal*Seite)Die Anzahl der Ausgabeeinheiten beträgt 500
relu3 = relu(fc1.output)
fc2 = FullyConnect(relu3.output, 500, 10) #500 Eingabeeinheiten, 10 Ausgabeeinheiten(Für 10 Klassifizierung)
loss = softmax(fc2.output, y)
Jetzt haben Sie die Definition von LeNet geschrieben. Durch Ändern des Ausgabesymbols jeder Ebene in das Eingabesymbol der nächsten Ebene werden alle Symbole verbunden und die Gradientenberechnung kann gleichzeitig durchgeführt werden. Das ist, N ・ ・ T.nnet.conv.conv2d (pool.pool2d (T.nnet.conv.conv2d ())) ・ ・ ・ Dies bedeutet, dass ein langes Symbol wie definiert ist. Wenn Sie T.grad () das letzte Symbol geben (diesmal verlust.Kosten), ist es daher möglich, den Gradienten aller Schichten einfach zu berechnen.
Schließlich definieren wir die Funktion von Training und Evaluierung durch Validierungsdaten und Testdaten. Bis zu diesem Punkt haben wir nur Symbole definiert, daher können wir keine tatsächlichen Werte für das Lernen eingeben. Daher definieren wir das Symbol als theano.function. Das Lernen kann durch Definieren der Aktualisierung der Parameter zu diesem Zeitpunkt erfolgen. Dieses Mal werden wir die Parameter mit SGD aktualisieren. Das Folgende ist ein Implementierungsbeispiel für diese Funktion zum Lernen.
#Listen Sie alle zu lernenden Parameter auf
params = conv1.params + conv2.params + fc1.params + fc2.params
#Berechnen Sie das Differential für jeden Parameter
grads = T.grad(loss.cost, params)
#Definition der Lernrate
learning_rate = 0.001
#Update-Ausdruck definieren
updates = [(param_i, param_i - learning_rate * grad_i) for param_i, grad_i in zip(params, grads)]
#Theano lernen.Funktion definieren
index = T.lscalar()
batch_size = 128
train_model = theano.function(inputs=[index], #Eingabe ist der Index der Trainingsdaten
outputs=loss.cost, #Ausgabe ist Verlust.cost
updates=updates, #Erneuerungsformel
givens={
x: train_set_x[index: index + batch_size], #Trainiere auf x in der Eingabe_set_Gib x
y: train_set_y[index: index + batch_size] #Trainiere, um y einzugeben_set_Gib y
})
Erstens ist params eine Liste von Gewichts- und Bias-Symbolen für jede Ebene (da dies das Hinzufügen der Listen ist). Bei einer gegebenen Liste von Variablen gibt T.grads () eine Liste von Symbolen zurück, die durch jede Variable differenziert sind. Gradds ist also eine Liste mit Symbolen, die durch jeden Parameter von loss.cost differenziert sind. Updates ist auch eine Liste von Update-Ausdrücken für jeden Parameter. Als nächstes folgt die Definition von train_model. Wie oben erwähnt, sind conv1 to loss.cost ein Symbol, das x und y als Eingaben verwendet. In train_model erhält x den Wert von train_set_x und y den Wert von train_set_y. train_set_x und train_set_y empfangen den Index und verweisen auf die Daten für batch_size aus dem empfangenen Index. Wenn Sie train_model nur index als Argument geben, werden die Werte von train_set_x und train_set_y von index zu index + batch_size für x und y angegeben. Danach können Sie lernen, indem Sie dieses train_model wiederholt mit einer for-Anweisung oder ähnlichem aufrufen.
for i in range(0, train_set_y.get_value().shape[0], batch_size):
train_model(i)
Definieren Sie abschließend die Funktion theano., um die Genauigkeit des Trainingsmodells zu bewerten. Da der Parameter in der Auswertung nicht aktualisiert wird, wird die Ausgabe auf Fehlerrate gesetzt. Da es in loss.output ein Softmax-Symbol gibt, definieren Sie damit das Symbol, das die Fehlerrate berechnet, und definieren Sie die Funktion, die anhand des Fehlerraten-Symbols ausgewertet wird.
pred = T.argmax(loss.output, axis=1) #Gibt die Klasse mit der höchsten vorhergesagten Wahrscheinlichkeit zurück
error = T.mean(T.neq(pred,y)) #Vergleichen Sie die vorhergesagte Klasse mit der richtigen Bezeichnung
test_model = theano.function(inputs=[index],
outputs=error,
givens={
x: test_set_x[index: index + batch_size],
y: test_set_y[index: index + batch_size]
})
val_model = theano.function(inputs=[index],
outputs=error,
givens={
x: test_set_x[index: index + batch_size],
y: test_set_y[index: index + batch_size]
})
Nachdem die Auswertungsfunktion für Validierungs- und Testdaten definiert wurde, kann sie mithilfe der for-Anweisung auf dieselbe Weise wie train_model ausgewertet werden.
test_losses = [test_model(i)
for i in range(0, test_set_y.get_value().shape[0], batch_size] #Speichern Sie den durchschnittlichen Verlust pro Charge in der Liste
mean_test_loss = np.mean(test_losses) #Berechnen Sie den Gesamtdurchschnitt
Mit dem Obigen wurde der Lern- und Bewertungscode von CNN unter Verwendung von theano geschrieben. Bitte überprüfen Sie das tatsächliche Lernergebnis, indem Sie den gesamten oben genannten Code verbinden (ich habe den Code, den ich für Quiita geschrieben habe, an einigen Stellen geändert und ohne Debugging geschrieben, sodass er möglicherweise nicht funktioniert lol). Wenn Sie Fragen, Code oder Erklärungen haben, teilen Sie uns dies bitte in den Kommentaren mit.
Ich erklärte die Implementierung des Faltungsnetzwerks mit Theano. Auf den ersten Blick mag der Code lang und langweilig erscheinen, aber sobald Sie die Ebenenklassen für Ihre eigene Bequemlichkeit definiert haben, ist es einfach zu schreiben. Der Vorteil von Theano ist, dass Sie es selbst definieren können (obwohl es problematisch ist). Andere Implementierungen als LeNet können verschiedene Modelle erstellen, indem der Layer-Definitionsteil geändert wird. Darüber hinaus können Verlustfunktionen und Parameteraktualisierungsmethoden durch Definieren von Symbolen frei beschrieben werden. Ich hoffe, es wird für diejenigen nützlich sein, die Theano von nun an verwenden werden.
Recommended Posts