Assignment 9

Neuronales Netz Implementierung

Disclaimer: Dieser Thread wurde aus dem alten Forum importiert. Daher werden eventuell nicht alle Formatierungen richtig angezeigt. Der ursprüngliche Thread beginnt im zweiten Post dieses Threads.

Assignment 9
In der Klasse EuclidianLoss sollen wir die Ableitung der Verlustfunktion berechnen. Allerdings ist mir noch nicht klar, nach welchen Variablen abgeleitet wird. Normalerweise leitet man ja nach Gewichten und Bias ab. Steckt das dann beides in einem Blob drin? Wenn die letzte Ebene eine Aktivierungsfunktion hat, würde diese ja auch die Ableitung eingehen, aber weder die Methode noch die Klasse bekommen ja die Aktivierungsfunktion übergeben.

1 „Gefällt mir“

Es ist tatsächlich nur die partielle Ableitung des EuclideanLoss gemeint. Tatsächlich leitet man in Neuronalen Netzen „2 Funktionen“ ab. Das sind einmal die Aktivierungsfunktionen und die Loss-Funktionen. Die Gewichte berechnen sich, dann je nach Layer entweder durch die Loss-Funktion oder eben durch die „zurückpropagierten Fehlerterme“. Das heißt die Aktivierungsfunktion-Ableitung brauchst du bei jedem Neuron und in jeder Schicht und die Loss-Ableitung, dann nur… (die Vervollständigung des Satzes überlasse ich euch :P).

Was du ja erst einmal minimieren willst, wenn du ohne Vorwissen u.ä. rangehst, ist |Wirkliches_Ergebnis_des_Netzes - Erwartetes_Ergebnis|, genau diese zwei Parameter bekommst du in Form von Blobs auch rein (natürlich pro Neuron ein Eintrag in den Blobs). Dementsprechend sieht auch deine LossFunktion aus. Die Ableitung ist demnach nur eine partielle Ableitung nach „Wirkliches_Ergebnis_des_Netzes“. Wenn dir noch nicht klar ist, wie mathematisch die Gewichte, Bias, Loss-Funktion u.ä. zusammenhängen und warum man die „einzeln berechnen“ kann, schau dir die Kettenregel für partielle Ableitungen nochmal an. :wink:

Als Beispiel das Ganze analog mit einer anderen Funktion, stellen wir uns vor unsere Loss-Funktion sähe so aus (ist zwar total unsinnig aber wir wollen ja nur das Prinzip sehen):

ln(u-v)

Jetzt müssen wir das Ganze partiell ableiten nach der Ausgabe, ich definiere Ausgabe jetzt einfach einmal als v und nach etwas Magic-Ableitungs-Regeln drauf anwenden kommt man für diesen Fall raus bei:

Edit:
Traut euch ruhig fragen, die Mathematik hinter Neuronalen Netzen ist alles andere als trivial. Sie braucht zwar nicht unmengen an komplexen Regeln/Berechnungen o.ä. aber man kann sich extrem schnell verzetteln und Sachen durcheinander werfen. Also keine Sorge, einfach fragen, fragen, fragen! :smiley:

Edit2:
Ich habe soeben einmal den Test-Case zwecks EuclideanLoss angepasst und hochgeladen, mehr wurde nicht verändert. Mir ist erst jetzt klar geworden, dass es einige Möglichkeiten gibt den EuclideanLoss zu definieren und sogar in der Literatur findet man verschiedenste Ansätze. Demnach sollte mit der jetzigen Version alle „Standard-Fälle“ abgefangen werden, falls nicht: Solange der Rest funzt und ihr sicher seid, dass die Loss-Funktion passt, ignoriert den Test-Case einfach. :wink:


Kurze Frage zum Testcase Zeile 133:
Bist du dir sicher Marcel, dass der expectedOutput = [-0.09868, 0.5277] ist ? Weil von den Zahlen ist es ja im Prinzip das beschnittene Problem aus testCaseFullyForward(), und da ist der expectedOutput {0.099668f, 0.3884727f, 0.6169093f, 0.7739084f, 0.8716722f}. Wenn ich das Ganze mal in python durchrechne, komme ich auf einen expectedOutput von {0.099668f, 0.3884727f}, was für mich auch intuitiv Sinn macht. Kann aber natürlich gut sein, dass ich was übersehe…

Sänks for considering

EDIT: Hat sich erledigt, sorry. Das Ergebnis bezieht sich natürlich auf das berechnete Delta und nicht auf f.forward :nuts:

1 „Gefällt mir“

Ach man kann nie Fehler ausschließen, deswegen habe ich es jetzt sicherheitshalber noch einmal per Hand ausgerechnet. Je nach der konkreten Definition von EuclideanLoss’ kommt natürlich ein Vielfaches (bzw. anderes Vorzeichen) von dem Ergebnis raus, aber prinzipiell passt es, zumindest komm ich per Hand auf dasselbe Ergebnis (mit meiner EuclidLoss’ Funktion).

Das Ergebnis scheint wirklich falsch zu sein, ich werde den Testcase rausnhemen, danke fürs aufmerksam machen. :slight_smile:


Also ich komme mittlerweile auf dein zuvor ermitteltes Ergebnis für die deltas. Also ist das Ergebnis sicher falsch? Weil dann stimmt wohl was mit meiner Implementierung nicht…


Hi,
ich hab mal eine Frage zur Update Gleichung für die Gewichte, da die 100.000 Indizes irgendwie bisschen verwirrend sind…

Also ich habe ein Gewicht w_ji , welches das i-te Neuron in der vorherigen layer mit dem j-ten Neuron in der jetzigen layer verbindet. Nun weiß ich mein delta_j für mein j-tes Neuron in der jetzigen layer.
Ist dann mein Update-Schritt:
w_ji = w_ji + learning_rate * g(in_j) * delta_j

oder

w_ji = w_ji + learning_rate * g(in_i) * delta_j

oder keines von beiden ? :smiley: Also ich bin mir grad irgendwie unsicher ob ich jetzt die Aktivierung des j-ten Neuron der jetzigen Layer nehmen soll oder die Aktivierung, die mir das i-te Neuron aus der vorherigen layer schickt…


Wenn du grob hinkommst, sollte es gehen (also irgendwo ±0.1). Der Testcase lässt ja auch so viel Spielraum zu, aber meine Implementierung war was das anging lustigerweise falsch. Es kommt aber ein sehr ähnliches Ergebnis raus (0.02 weniger oder so). Das liegt an der Aktivierungsfunktion, die was das angeht relativ großzügig ist. Deswegen hat auch mein restliches Model super funktioniert, bis ich eine andere Aktivierungsfunktion ausprobiert habe…

Richtig ist Zweiteres, soweit ich keinen Denkfehler habe, denn deine Definition der Gewichte bzw. der Zusammenhänge ist etwas „verwirrend“ gewählt. :smiley:
Jedenfalls solltet w_ji = w_ji + learning_rate * g(in_i) * delta_j passen.
Vorausgesetzt g() ist deine Aktivierungsfunktion. :slight_smile:

Ich persönlich würde das so definieren (und ist auch eigentlich im Code so angedacht :D):
w_ij = w_ij + rate * input_i * delta_j

Aber es geht natürlich fast in beliebiger Reihenfolge, wenn dir das also anders lieber ist, ist das auch vollkommen möglich. :slight_smile:

Edit:
Ich hab jetzt auch endlich die „Fehler/Typos“ in den Übungsfolien ausgebessert (Sorry, dass das etwas gedauert hat…), die mir schon in der Übung aufgefallen sind und dementsprechend manche sicher schon „unbewusst ausgebessert haben“. Konkret war der Fehler, dass ich das delta berechnet habe über „Loss’ * activationOut’(activationOut(in))“, was falsch ist. Richtig ist: „Loss’ * activationOut’(in)“


Erstmal danke dir für die ausführliche Antwort. Meine Notation ist deswegen etwas strange, da man mit der Notation besser mit der Matrixrechnung zurecht kommt :wink:
Nur zur Sicherheit, um deine Notation zu verstehen:

w_ij: Gewicht von Neuron i aus vorheriger Layer mit Neuron j aus aktueller Layer
input_i : Aktivierung die von Neuron i weggeschossen wird
delta_j : das delta, dass man sich für Neuron j berechnet hat

Stimmt das so?

Und nur zum Abgleich (weil mich die Folien bisschen verwirren), ist das Folgende in deiner Notation die korrekte delta Berechnung?

delta_i = g’ (in_i) * \sum_j ( w_ij * delta_j)

i: i-tes neuron in derzeitiger layer
j : j-tes neuron in der anschließenden layer (bzw layer zuvor wenn man es rückwärts betrachtet)
w_ij: Gewicht von neuron i in derzeitiger layer nach neuron j in anschließender layer
g’ (in_i) : Ableitung der Aktivierung die mein i-tes Neuron in der derzeitigen layer abschießt
delta_j : delta des j-ten neurons in anschließender layer

Insbesondere bin ich mir hier unsicher ob es g’(in_i) oder g’(in_j) heißen soll, da das in den Folien in Definition 8.13 etwas verwirrend ist, da über den Index i summiert wird, aber g’(in_i) außerhalb der Summe steht.

Ich danke dir für die clarification =)

1 „Gefällt mir“

Wenn in_i genau die gewichtete Summe aller Eingänge zu Neuron i ist ( + Bias aber ohne anschließende Aktivierungsfunktion), dann ist das genau wie es aussehen könnte bzw. wie ich es definiert habe. :slight_smile:


Ich bin verwirrt von der Aufgabe 2.1.

Sollen wir inputBlob verändern und dann output auf inputBlob zeigen lassen, oder soll output ein neuer Blob sein, während inputBlob unverändert bleibt?


Prinzipiell ist es egal, solange der Rest vom Code noch funktioniert bzw. du ihn so anpasst wie du ihn brauchst.
Angedacht war aber input nicht zu verändern und in output das Ergebnis (gewichte*input+ bias und aktivierungsfunktion usw) zu schreiben und denselben output auch zu returnen.
Output ist ja auch schon im Konstruktor angelegt. Du bist aber total frei wie du die Variablen prüfst und benutzt. Du kannst auch beliebig hinzufügen/entfernen, notfalls musst du halt das training etwas anpassen, aber das ist auch nicht so dramatisch.