Dies ist eine Fortsetzung des vorherigen Artikels (# https://qiita.com/kuroitu/items/d22c8750e34d5d75fb6c). In diesem Artikel werden wir die Backpropagation anhand eines Berechnungsdiagramms kurz erläutern. Die Kettenregel wird in [hier] kurz erwähnt (# https://qiita.com/kuroitu/items/221e8c477ffdd0774b6b#chain-Regel), daher werde ich sie weglassen. Backpropagation hat in der Regel viele mathematische Formeln, aber ich möchte es mit der Implementierung erklären, damit es so intuitiv wie möglich verstanden werden kann.
Beginnen wir wie im Beispiel mit der Backpropagation mit Skalaren. Die Rückausbreitung ist jedoch ziemlich schwierig, wenn Sie sie theoretisch auch mit Skalaren verfolgen. Ich werde es jedoch sehr kurz erklären.
Betrachten wir zunächst kurz das folgende Berechnungsdiagramm. Wenn Sie die Vorwärtsausbreitung sorgfältig in die Abbildung schreiben
\begin{align}
m &= wx \\
n &= m + b \\
y &= \sigma(n)
\end{align}
Es sieht aus wie das. Betrachten Sie diese Backpropagation. Betrachten Sie jede teilweise Differenzierung.
** << Achtung >> ** ** Da die Höhen zwangsweise ausgerichtet sind, wird an einigen Stellen "$ \ frac {} {} $" gemischt. Bitte lassen Sie mich wissen, ob es einen guten Weg gibt. Es gibt auch andere Möglichkeiten, der Formel Farbe hinzuzufügen ... **
Es wird sein. Wenn der Fehler $ E $ vom Upstream durchlaufen wird, ist die Rückausbreitung wie in der Abbildung gezeigt. Immerhin gibt es viele Formeln und es sieht kompliziert aus, das heißt, es multipliziert nur das partielle Differential, wenn es durch den ** Rechenknoten geht. ** ** ** Auch wenn dies ein Skalar ist, kann es einfacher sein.
Lassen Sie es uns implementieren. Code gemäß der Formel. Der Implementierungszielcode lautet [hier](https://qiita.com/kuroitu/items/884c62c48c2daa3def08#layer Modulcodevorbereitung).
baselayer.py
def backward(self, grad):
"""
Implementierung der Backpropagation
"""
dact = grad*self.act.backward(self.x, self.y)
self.dw = dact*self.x
self.db = dact
self.dx = dact*self.w
return self.dx
Die mittlere Ebene ist in Ordnung, es ist jedoch erforderlich, eine gewisse Verarbeitung für die Ausgabeebene hinzuzufügen. Ich werde später darauf zurückkommen.
Als nächstes betrachten wir die Rückausbreitung in einer Matrix. Dies ist eine Menge Formeln und schwierig, aber Sie können es sehen, wenn Sie es richtig befolgen. Nach wie vor füge ich der Formel Farbe hinzu, damit sie auf einen Blick sichtbar ist. ~~ Ich fühle mich schwer ... ~~
Betrachten Sie das folgende Berechnungsdiagramm. Es ist dasselbe wie in [Forward Propagation](https://qiita.com/kuroitu/items/d22c8750e34d5d75fb6c# Forward Propagation in Matrix). Es ist ein Schichtmodell mit nur 2 Neuronen, aber wie Sie sehen können, gibt es ziemlich viele Formeln. Lassen Sie uns zunächst die teilweise Differenzierung jedes Teils wie im Fall des Skalars ermitteln.
\Rightarrow
\boldsymbol{dW} =
\left(
\begin{array}{cc}
\cfrac{\partial y_1}{\partial w_{1, 1}} & \cfrac{\partial y_2}{\partial w_{1, 2}} \\
\cfrac{\partial y_1}{\partial w_{2, 1}} & \cfrac{\partial y_2}{\partial w_{2, 2}}
\end{array}
\right)
=
\left(
\begin{array}{c}
x_1 \\
x_2
\end{array}
\right)
\left(
\begin{array}{cc}
\sigma'_1(m) & \sigma'_2(q)
\end{array}
\right)
=
\boldsymbol{X}^{\top}\boldsymbol{\sigma'}
\Rightarrow
\boldsymbol{dB} =
\left(
\begin{array}{cc}
\cfrac{\partial y_1}{\partial b_1} & \cfrac{\partial y_2}{\partial b_2}
\end{array}
\right)
=
\left(
\begin{array}{cc}
\sigma'_1(m) & \sigma'_2(q)
\end{array}
\right)
\Rightarrow
\boldsymbol{dX} =
\left(
\begin{array}{cc}
\cfrac{\partial y_1}{\partial x_1} + \cfrac{\partial y_2}{\partial x_1} & \cfrac{\partial y_1}{\partial x_2} + \cfrac{\partial y_2}{\partial x_2}
\end{array}
\right)
=
\left(
\begin{array}{cc}
\sigma'_1(m) & \sigma'_2(q)
\end{array}
\right)
\left(
\begin{array}{cc}
w_{1, 1} & w_{2, 1} \\
w_{1, 2} & w_{2, 2}
\end{array}
\right)
=
\boldsymbol{\sigma'}\boldsymbol{W}^{\top}
Es sieht aus wie. Fügen Sie den Fehler aus dem Upstream zu diesen [Elementprodukten] hinzu (https://qiita.com/kuroitu/items/d22c8750e34d5d75fb6c#%E8%A1%8C%E5%88%97%E3%81%AE%E8%A6%81 Durch Multiplizieren mit% E7% B4% A0% E7% A9% 8D) wird der in der obigen Abbildung gezeigte Fehler weitergegeben. Wie Sie an der Tatsache sehen können, dass $ x_1 $ und $ x_2 $ verzweigt und die Werte an andere Berechnungsknoten gesendet wurden, müssen die fließenden Teildifferenzen addiert und berechnet werden. Also </ font>
\left(
\begin{array}{cc}
\cfrac{\partial y_1}{\partial x_1} + \cfrac{\partial y_2}{\partial x_1} & \cfrac{\partial y_1}{\partial x_2} + \cfrac{\partial y_2}{\partial x_2}
\end{array}
\right)
Es ist . Diese Formel repräsentiert intuitiv (und theoretisch) die Auswirkung der Eingabe $ x_1 $ und $ x_2 $ auf die Ausgabe $ y_1 $ und $ y_2 $. </ font>
Übrigens wird bis zu diesem Punkt implizit die Stapelgröße $ N = 1 $ angenommen. Was passiert also, wenn es $ N \ ne 1 $ ist? Die Antwort ist einfach. Nur der Fehler in Bezug auf die Vorspannung $ B $ ändert sich. Schauen wir uns das mit einer Formel an. Geben Sie $ N \ mal L $ und $ N \ mal M $ </ font> ein
\begin{align}
\underbrace{\boldsymbol{dW}}_{L \times M} &= \underbrace{\boldsymbol{X}^{\top}}_{L \times N} \underbrace{\boldsymbol{\sigma'}}_{N \times M} \\
\underbrace{\boldsymbol{dB}}_{1 \times M} &= \underbrace{\boldsymbol{\sigma'}}_{N\times M} \\
\underbrace{\boldsymbol{dX}}_{N \times L} &= \underbrace{\boldsymbol{\sigma'}}_{N \times M} \underbrace{\boldsymbol{W}^{\top}}_{M \times L}
\end{align}
Es wird sein. Da der fließende Gradient zum Zeitpunkt der Eingabe mit jeder Form identisch sein muss, $ \ underbrace {\ boldsymbol {dW}} \ _ {L \ times M} $ und $ \ underbrace {\ boldsymbol {dB}} \ _ {1 \ times M} $, $ \ underbrace {\ boldsymbol {dX}} \ _ {N \ times L} $, davon $ \ underbrace {\ boldsymbol {dB}} \ _ {1 \ Die Formen stimmen nicht mit den Zeiten M} $ überein. Wenn also der Bias $ \ underbrace {\ boldsymbol {B}} _ {1 \ times M} $ vorwärts übertragen wird, wird die ** Broadcast-Funktion automatisch $ \ underbrace {\ boldsymbol {B}} \ Denken Sie daran, dass ** _ {N \ times M} $ sein sollte. Mit anderen Worten, das Anwenden der exakt gleichen Verzerrung auf alle Batchdaten ist gleichbedeutend mit ** Verzweigen der gleichen Verzerrung in $ N $ Stücke **. Wenn Sie also die Summe nehmen, Es wird gut sein **. </ font>
\underbrace{\boldsymbol{dB}}_{1 \times M} = \sum_{i=1}^{N}{\underbrace{\boldsymbol{\sigma'}_i}_{1\times M}} \xrightarrow{\textrm{coding}} \textrm{sum}(\boldsymbol{\sigma'}, \textrm{axis}=0)
Damit ist die Theorie zur Umsetzung abgeschlossen.
Lassen Sie es uns implementieren. Schreiben Sie die Implementierung in Skalar um.
baselayer.py
def backward(self, grad):
"""
Implementierung der Backpropagation
"""
dact = grad*self.act.backward(self.x, self.y)
self.dw = self.x.T@dact
self.db = np.sum(dact, axis=0)
self.dx = [email protected]
return self.dx
Jedes hat sich zur Matrixberechnung geändert. Beachten Sie auch, dass die Funktion numpy.sum
das Ergebnis des Hinzufügens aller Elemente zurückgibt, sofern Sie nicht axis = 0
angeben.
Der Rückausbreitung der Ausgabeschicht muss ein wenig Verarbeitung hinzugefügt werden.
outputlayer.py
def backward(self, t):
"""
Implementierung der Backpropagation
"""
#Wenn die Aktivierungsfunktion der Ausgangsschicht die Softmax-Funktion und die Verlustfunktion der Kreuzentropiefehler ist
#Separate Fälle von Fehlerausbreitung
if isinstance(self.act, Softmax) \
and isinstance(self.errfunc, CrossEmtropy):
dact = self.y - t
self.dw = self.x.T@dact
self.db = np.sum(dact, axis=0)
self.dx = [email protected]
return self.dx
else:
grad = self.errfunc.backward(self.y, t)
return super().__init__(grad)
Der Inhalt der Überschreibung selbst ist einfach. Wenn die Aktivierungsfunktion der Ausgabeschicht eine Softmax-Funktion ist und die Verlustfunktion einen Kreuzentropiefehler verwendet, können Sie zum Fehler des Zurückpropagierens der Aktivierungsfunktion wie eines Codes springen. In anderen Fällen ist es notwendig, die Differenz der Verlustfunktion zu berechnen und den Fehler zu übergeben.
Lassen Sie uns nun den Mitgliedern die oben erwähnte errfunc
geben.
baselayer.py
def __init__(self, *, prev=1, n=1,
name="", wb_width=1,
act="ReLU", err_func="square",
**kwds):
self.prev = prev #Anzahl der Ausgänge der vorherigen Ebene=Anzahl der Eingaben in diese Ebene
self.n = n #Anzahl der Ausgänge in dieser Ebene=Anzahl der Eingaben in die nächste Ebene
self.name = name #Der Name dieser Ebene
#Stellen Sie Gewicht und Vorspannung ein
self.w = wb_width*np.random.randn(prev, n)
self.b = wb_width*np.random.randn(n)
#Aktivierungsfunktion(Klasse)Erhalten
self.act = get_act(act)
#Verlustfunktion(Klasse)Erhalten
self.errfunc = get_errfunc(err_func)
Ich werde irgendwann einen Artikel über die Verlustfunktion schreiben.
Ich frage mich, ob die Schwierigkeit, die Formeln zu färben, gelöst werden kann ...
Recommended Posts