Benutzer-Werkzeuge

Webseiten-Werkzeuge


ss19:eigenfaces

Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen gezeigt.

Link zu dieser Vergleichsansicht

Beide Seiten der vorigen Revision Vorhergehende Überarbeitung
Nächste Überarbeitung
Vorhergehende Überarbeitung
ss19:eigenfaces [2019/09/22 22:29]
Labo [Planung/Verlauf]
ss19:eigenfaces [2021/01/05 23:43] (aktuell)
Labo [Ergebnis]
Zeile 1: Zeile 1:
 ====== Eigengesichter ====== ====== Eigengesichter ======
 Analysieren und Generieren von menschlichen Gesichtern mit Hilfe eines Autoencoder-Netzwerkes und einer Hauptkomponentenanalyse. Analysieren und Generieren von menschlichen Gesichtern mit Hilfe eines Autoencoder-Netzwerkes und einer Hauptkomponentenanalyse.
 +
 +Code: https://​git.tu-berlin.de/​labo/​eigenfaces
  
 Gruppe: Lars Bonczek Gruppe: Lars Bonczek
Zeile 53: Zeile 55:
 Ein CNN besteht aus einer Abfolge von je einer oder mehreren Convolutions,​ gefolgt von je einer Pooling-Schicht. Ist die Größe der Neuronen-Matrix ausreichend reduziert, folgen noch eine oder mehrere Fully-connected-Schichten,​ um die Daten abschließend auszuwerten und beispielsweise zu kategorisieren. Nach jeder Schicht folgt meist die ReLU-Aktivierungsfunktion,​ die definiert ist als $f(x)=\text{max}(0,​x)$. Ein CNN besteht aus einer Abfolge von je einer oder mehreren Convolutions,​ gefolgt von je einer Pooling-Schicht. Ist die Größe der Neuronen-Matrix ausreichend reduziert, folgen noch eine oder mehrere Fully-connected-Schichten,​ um die Daten abschließend auszuwerten und beispielsweise zu kategorisieren. Nach jeder Schicht folgt meist die ReLU-Aktivierungsfunktion,​ die definiert ist als $f(x)=\text{max}(0,​x)$.
 ===== Planung/​Verlauf ===== ===== Planung/​Verlauf =====
 +
 +Protokoll in Stichpunkten:​ {{:​ss19:​eigenfaces_protokoll.pdf|}}
  
 Mein erster Schritt war es, eine einfache Hauptkomponentenanalyse von Porträtfotos durchzuführen. Dafür suchte ich mir im Internet einen öffentlich zugänglichen Datensatz von Fotos, die von einer Universität zu Forschungszwecken veröffentlicht wurden. Dann implementierte ich die Hauptkomponentenanalyse in Python. Dabei unterliefen mir einige Fehler, sodass die Ergebnisse zunächst nicht meinen Erwartungen entsprachen. Mein erster Schritt war es, eine einfache Hauptkomponentenanalyse von Porträtfotos durchzuführen. Dafür suchte ich mir im Internet einen öffentlich zugänglichen Datensatz von Fotos, die von einer Universität zu Forschungszwecken veröffentlicht wurden. Dann implementierte ich die Hauptkomponentenanalyse in Python. Dabei unterliefen mir einige Fehler, sodass die Ergebnisse zunächst nicht meinen Erwartungen entsprachen.
Zeile 94: Zeile 98:
 {{ :​ss19:​aligner_demo.png?​nolink |Abbildung 11}} {{ :​ss19:​aligner_demo.png?​nolink |Abbildung 11}}
  
-Bilder, die zu niedrig aufgelöst sind, oder wo sich das Gesicht zu nah am Rand des Bildes befindet, werden von dem Skript automatisch verworfen. ​Außerdem ​gibt es einige ​weitere Kriterien, nach denen Bilder aussortiert werden. So wird das Verhältnis zwischen dem Augenabstand und dem Abstand von Augen und Mund überprüft. Ist dieses zu groß oder zu klein, so schaut die Person auf dem Bild vermutlich nicht gerade in die Kamera und das Bild wird verworfen. Außerdem ​werden die Abstände der beiden Augen zur Nase verglichen. Diese sollten nicht zu verschieden sein, da die Person sonst vermutlich nach links oder rechts schaut. ​Schließlich ​werden Bilder verworfen, die unscharf oder monochrom sind. Dies wird im Abschnitt [[eigenfaces#​aligner.py]] näher beschrieben.+Bilder, die zu niedrig aufgelöst sind, oder wo sich das Gesicht zu nah am Rand des Bildes befindet, werden von dem Skript automatisch verworfen. ​Es gibt noch weitere Kriterien, nach denen Bilder aussortiert werden. So werden die Abstände der beiden Augen zur Nase verglichen. Diese sollten nicht zu verschieden sein, da die Person sonst vermutlich nach links oder rechts schaut. ​Außerdem ​werden Bilder verworfen, die unscharf oder monochrom sind. Dies wird im Abschnitt [[eigenfaces#​aligner.py]] näher beschrieben.
  
 Mithilfe dieses Skripts wandelte ich nun eine Datenbank mit über hundert Gigabyte an Bildern von berühmten Persönlichkeiten aus der Google Bildersuche in einen brauchbaren Datensatz mit gut 60.000 ausgerichteten Bildern um. Also galt es nun, mit diesen Bildern ein Netzwerk zu trainieren. Zunächst versuchte ich, ein eigenes Autoencoder-Netzwerk von Grund auf zu entwerfen und trainieren. Dies scheiterte aber an meiner fehlenden Erfahrung mit Neuronalen Netzwerken und schlicht an fehlender Rechenleistung. Deswegen entschied ich mich dafür, ein bewährtes CNN zu verwenden, das auf die Klassifizierung von Bildern trainiert wurde, und dieses für meine Zwecke anzupassen und weiter zu trainieren. Meine Wahl viel nach einiger Recherche auf das VGG16-Netzwerk. Dabei handelt es sich um ein CNN mit 13 Convolutions und 3 Fully-connected-Schichten (siehe Abbildung 11), welches bei der ImageNet Large Scale Visual Recognition Challenge 2014 eingereicht wurde und eine Genauigkeit von 92,7% bei der top-5 Klassifizierung der Bilder aus der ImageNet-Datenbank erreichte (Simonyan & Zisserman, 2014). Mithilfe dieses Skripts wandelte ich nun eine Datenbank mit über hundert Gigabyte an Bildern von berühmten Persönlichkeiten aus der Google Bildersuche in einen brauchbaren Datensatz mit gut 60.000 ausgerichteten Bildern um. Also galt es nun, mit diesen Bildern ein Netzwerk zu trainieren. Zunächst versuchte ich, ein eigenes Autoencoder-Netzwerk von Grund auf zu entwerfen und trainieren. Dies scheiterte aber an meiner fehlenden Erfahrung mit Neuronalen Netzwerken und schlicht an fehlender Rechenleistung. Deswegen entschied ich mich dafür, ein bewährtes CNN zu verwenden, das auf die Klassifizierung von Bildern trainiert wurde, und dieses für meine Zwecke anzupassen und weiter zu trainieren. Meine Wahl viel nach einiger Recherche auf das VGG16-Netzwerk. Dabei handelt es sich um ein CNN mit 13 Convolutions und 3 Fully-connected-Schichten (siehe Abbildung 11), welches bei der ImageNet Large Scale Visual Recognition Challenge 2014 eingereicht wurde und eine Genauigkeit von 92,7% bei der top-5 Klassifizierung der Bilder aus der ImageNet-Datenbank erreichte (Simonyan & Zisserman, 2014).
Zeile 207: Zeile 211:
  
 Dabei ist ''<​out_dir>''​ das Ausgabeverzeichnis,​ in dem die Netzwerk-Datei gespeichert wird. Weitere Information zu diesem Befehl gibt es im Abschnitt [[eigenfaces#​autoencoder.py]]. Dabei ist ''<​out_dir>''​ das Ausgabeverzeichnis,​ in dem die Netzwerk-Datei gespeichert wird. Weitere Information zu diesem Befehl gibt es im Abschnitt [[eigenfaces#​autoencoder.py]].
 +
 +Um die Gewichte des VGG16-Netzwerkes zu laden, benutzte ich das Skript [[eigenfaces#​pretrain_vgg16.py]] mit den Parametern ''​pretrained_layers = 18''​ und ''​locked_layers = 15''​.
  
 Dieses Netzwerk trainierte ich zunächst mit einem kleinen Teil der Trainingsdaten und, nachdem ich vielversprechende Ergebnisse erhielt, mit allen 63.943 Bildern. Davon verwendete ich 20%, also 12788 Bilder, zur Validierung. Auf den folgenden Bildern ist links ein Originalbild zu sehen und rechts die Rekonstruktion des Autoencoders nach 50 Epochen Training: Dieses Netzwerk trainierte ich zunächst mit einem kleinen Teil der Trainingsdaten und, nachdem ich vielversprechende Ergebnisse erhielt, mit allen 63.943 Bildern. Davon verwendete ich 20%, also 12788 Bilder, zur Validierung. Auf den folgenden Bildern ist links ein Originalbild zu sehen und rechts die Rekonstruktion des Autoencoders nach 50 Epochen Training:
Zeile 217: Zeile 223:
 Wie im letzten Beispiel zu erkennen ist, gab es unter den ausgerichteten Bildern auch einige Bilder, die eigentlich keine menschlichen Gesichter enthalten, aber trotzdem so erkannt wurden. Dies kann zu interessanten Ergebnissen führen. Wie im letzten Beispiel zu erkennen ist, gab es unter den ausgerichteten Bildern auch einige Bilder, die eigentlich keine menschlichen Gesichter enthalten, aber trotzdem so erkannt wurden. Dies kann zu interessanten Ergebnissen führen.
  
-Die ursprünglich entwickelte Webanwendung konnte ich so nicht mehr verwenden. Bisher hatte die Anwendung ja alle Bilder auf der Seite des Benutzers im Browser berechnet. Dies war nun nicht mehr möglich, da der Browser die Kodierung und Dekodierung der Bilder mithilfe des Autoencoders hätte vornehmen müssen. Also entschied ich mich dazu, die Anwendung so umzugestalten,​ dass der Browser nur die Werte der Schieberegler an den Server sendet und dieser dann das Bild dazu generiert. Dazu verwendete ich das Framework ​[[https://palletsprojects.com/p/flask/|Flask]] und für die Darstellung im Browser noch [[https://​getbootstrap.com/​|Bootstrap]] ​und [[https://​jquery.com/​|jQuery]]Die neue Version ​der Webanwendung sah dann so aus:+Für das Trainieren ​verwendete ich [[https://colab.research.google.com|Google Colab]]. Eigentlich ist dieser Dienst nicht dafür gedacht, große Netzwerke lange zu trainieren, deswegen hatte ich auch einige Probleme damit. Man kann seinen Code zum Beispiel nicht länger als 12 Stunden am Stück laufen lassen, ​und selbst das klappt nur manchmalDeswegen war ich schnell dazu gezwungen mit Checkpoints zu arbeiten ​und den Vorgang regelmäßig neuzustartenDie Ergebnisse sicherte ich direkt in meinem Google DriveDummerweise kam ich erst sehr spät auf die Idee, auch den Verlauf des Trainings in einer Log-Datei auf Google Drive zu sichern. So ging der Verlauf der ersten Anläufe leider verloren. Dieses Diagramm zeigt den Verlauf des Trainings eines späteren Anlaufes:
  
-{{ :ss19:webapp_v2.png?nolink |}}+{{ :ss19:autoencoder1000_training_67_epochs.png?nolink |Abbildung 17}}
  
-Eine neue Funktion dieser Version ist die Möglichkeit,​ ein eigenes Bild hochzuladen. Der Server sucht dann nach einem Gesicht in dem Bild, richtet dieses aus und versucht es möglichst gut zu rekonstruieren.+Die ursprünglich entwickelte Webanwendung konnte ich an diesem Punkt nicht mehr verwenden. Bisher hatte die Anwendung ja alle Bilder auf der Seite des Benutzers im Browser berechnet. Dies war nun nicht mehr möglich, da der Browser die Kodierung und Dekodierung der Bilder mithilfe des Autoencoders hätte vornehmen müssen. Also entschied ich mich dazu, die Anwendung so umzugestalten,​ dass der Browser nur die Werte der Schieberegler an den Server sendet und dieser dann das Bild dazu generiert. Dazu verwendete ich das Framework [[https://​palletsprojects.com/​p/​flask/​|Flask]] und für die Darstellung im Browser noch [[https://​getbootstrap.com/​|Bootstrap]] und [[https://​jquery.com/​|jQuery]]. Die neue Version der Webanwendung sah dann so aus: 
 + 
 +{{ :​ss19:​webapp_v2.png?​nolink |Abbildung 18}} 
 + 
 +Eine neue Funktion dieser Version ist die Möglichkeit,​ ein eigenes Bild hochzuladen. Der Server sucht dann nach einem Gesicht in dem Bild, richtet dieses aus und versucht es möglichst gut zu rekonstruieren. Außerdem gibt es nun die Möglichkeit,​ zufällige Gesichter zu generieren. Dabei wird die Box-Muller-Methode eingesetzt, sodass die zufällig erzeugten Gesichter normalverteilt sind und dieselbe Standardabweichung aufweisen wie die Trainingsdaten. Die Färbung der Schieberegler zeigt jeweils die entsprechende Standardabweichung. In dem obigen Beispiel entspricht der grüne, der gelbe und der rote Bereich jeweils drei Standardabweichungen. 
 + 
 +An diesem Punkt musste ich mein Projekt aus zeitlichen Gründen zu einem Ende bringen. Im Folgenden werden noch die verschiedenen Komponenten des finalen Standes beschrieben.
 ===== Ergebnis ===== ===== Ergebnis =====
  
Zeile 232: Zeile 244:
  
 Außerdem gibt es eine [[eigenfaces#​Webanwendung]],​ die für eine beliebige Gesichts-Kodierung das entsprechende dekodierte Gesicht anzeigen kann. Die Parameter des kodierten Gesichts können mit Schiebereglern angepasst werden, sodass man in Echtzeit den Einfluss der Parameter auf das dekodierte Gesicht sieht. Außerdem gibt es eine [[eigenfaces#​Webanwendung]],​ die für eine beliebige Gesichts-Kodierung das entsprechende dekodierte Gesicht anzeigen kann. Die Parameter des kodierten Gesichts können mit Schiebereglern angepasst werden, sodass man in Echtzeit den Einfluss der Parameter auf das dekodierte Gesicht sieht.
 +
 +Der aktuelle Code des Projektes ist auf [[https://​git.tu-berlin.de/​labo/​eigenfaces|GitLab]] zu finden und kann dort auch als ZIP-Datei heruntergeladen werden.
 +
 +Die zuletzt von mir trainierte Version des Autoencoders (trainiert mit gut 64.000 Bildern) inklusive der Ergebnisse der Hauptkomponentenanalyse kann [[https://​tubcloud.tu-berlin.de/​s/​BgY73PWkQdLSjX3|hier]] heruntergeladen werden. Mit diesem Paket kann die Webanwendung ohne weitere Vorbereitungen (außer der [[eigenfaces#​Webanwendung|Anpassung der Konfigurationsdatei]]) verwendet werden. Ein neuer Trainingsdatensatz mit gut 140.000 Bildern kann [[https://​tubcloud.tu-berlin.de/​s/​Tp8tpP757tFP3yy|hier]] heruntergeladen werden.
 ==== aligner.py ==== ==== aligner.py ====
  
Zeile 238: Zeile 254:
 === Berechnung der Schärfe === === Berechnung der Schärfe ===
  
-Die Schärfe eines Bildes wird durch die Formel $$LAP(I)=\sum_m^M\sum_n^N\left|L(m,​n)\right|$$ berechnet, wobei $L(m,n)$ die Convolution des Bildes mit der Laplace'​schen Maske +Die Schärfe eines Bildes wird durch die Formel $$LAP\_VAR(I)=\sum_m^M\sum_n^N\left(\left|L(m,​n)\right|-\bar{L}\right)^2$$ berechnet, wobei $\bar{L}$ definiert ist als $$\bar{L}=\frac{1}{NM}\sum_m^M\sum_n^N\left|L(m,​n)\right|$$ und $L(m,n)$ die Convolution des Bildes mit der Laplace'​schen Maske 
 $$L=\frac{1}{6} $$L=\frac{1}{6}
 \left(\begin{array}{rrr} ​                               ​ \left(\begin{array}{rrr} ​                               ​
Zeile 249: Zeile 265:
 <​code>​ <​code>​
 def get_sharpness(image):​ def get_sharpness(image):​
-    return np.sum(np.abs(ndimage.laplace(np.mean(image,​ axis=2))))+    return np.var(np.abs(ndimage.laplace(np.mean(image,​ axis=2))))
 </​code>​ </​code>​
  
Zeile 299: Zeile 315:
   * ''​%%--%%skip=<​n>''​   * ''​%%--%%skip=<​n>''​
     * Überspringt die ersten $n$ Dateien. Kann verwendet werden, um einen abgebrochenen Vorgang fortzusetzen.     * Überspringt die ersten $n$ Dateien. Kann verwendet werden, um einen abgebrochenen Vorgang fortzusetzen.
 +  * ''​%%--%%max-nose-ratio=<​value>''​ 
 +    * Legt das maximale Verhältnis zwischen den Abständen der beiden Augen zur Nase einer Person fest. Standard: $1.7$ 
 +  * ''​%%--%%min-color=<​value>''​ 
 +    * Legt das minimale Farb-Level eines Bildes fest. Siehe [[eigenfaces#​Berechnung der Farbigkeit]]. Standard: $15.0$ 
 +  * ''​%%--%%min-sharpness=<​value>''​ 
 +    * Legt das minimale Schärfe-Level eines Bildes fest. Siehe [[eigenfaces#​Berechnung der Schärfe]]. Standard: $100.0$
 ==== grouper.py ==== ==== grouper.py ====
  
Zeile 355: Zeile 376:
   * ''​%%--%%shape=<​height>,<​width>,<​depth>''​   * ''​%%--%%shape=<​height>,<​width>,<​depth>''​
     * Gibt das Format der Bilder an, die der neu erstellte Autoencoder akzeptieren soll. Alternativ können auch mit ''​%%--%%images''​ die Trainingsdaten angegeben werden (s.u.), das Programm erkennt das Format dann automatisch.     * Gibt das Format der Bilder an, die der neu erstellte Autoencoder akzeptieren soll. Alternativ können auch mit ''​%%--%%images''​ die Trainingsdaten angegeben werden (s.u.), das Programm erkennt das Format dann automatisch.
 +
 +Optionen zum Kompilieren eines Autoencoders:​
 +  * ''​%%--%%optimizer=<​optimizer>''​
 +    * Legt den neuen Optimizer fest. Verfügbare Werte: ''​sgd'',​ ''​adam''​ und ''​rmsprop''​
 +  * ''​%%--%%lr=<​learn_rate>''​
 +    * Legt die ''​learn_rate''​ des neuen Optimizers fest.
 +  * ''​%%--%%momentum=<​momentum>''​
 +    * Legt das ''​momentum''​ des neuen Optimizers fest.
 +  * ''​%%--%%decay=<​decay>''​
 +    * Legt den ''​decay''​ des neuen Optimizers fest.
 +  * ''​%%--%%nesterov''​
 +    * Aktiviert die ''​nesterov''​-Option für den neuen Optimizer.
 +  * ''​%%--%%loss=<​loss_function>''​
 +    * Legt die ''​loss''​-Funktion fest. Standard: ''​mean_squared_error''​
  
 Optionen zum Laden von (kodierten) Bildern: Optionen zum Laden von (kodierten) Bildern:
Zeile 428: Zeile 463:
 _________________________________________________________________ _________________________________________________________________
 </​code>​ </​code>​
 +
 +==== pretrain_vgg16.py ====
 +
 +Das Skript ''​pretrain_vgg16''​ dient dazu, die Gewichte des VGG16-Netzwerkes in ein anderes Netzwerk zu kopieren.
 +
 +=== Verwendung ===
 +
 +  python pretrain_vgg16.py <​model_path>​ <​target_path>​ <​pretrained_layers>​ <​locked_layers>​
 +  ​
 +Lädt das Netzwerk aus der Datei ''​model_path'',​ übernimmt die Gewichte des VGG16 und speichert das Ergebnis in ''​target_path''​. ''​pretrained_layers''​ gibt an, für wie viele Schichten des Netzwerks die Gewichte übernommen werden sollen, und ''​locked_layers''​ gibt an, wie viele der Schichten gesperrt werden sollen, sodass sie sich beim Training nicht mehr verändern.
 ==== pca.py ==== ==== pca.py ====
  
Zeile 464: Zeile 509:
   ​   ​
 Die Webanwendung ist dann unter der URL http://​localhost:​5000 zu finden. Die Webanwendung ist dann unter der URL http://​localhost:​5000 zu finden.
-===== Ausblick ===== 
  
 ===== Fazit ===== ===== Fazit =====
  
 +Insgesamt bin ich mit dem Ergebnis dieses Projektes sehr zufrieden. Ich habe beim Trainieren des Autoencoders schneller gute Ergebnisse erzielt als erwartet. Das, was mich wirklich aufgehalten hat, war, dass mir keine vernünftige Hardware zur Verfügung stand. Google Colab ist für solche Aufgaben eigentlich nicht gedacht, und das merkte man auch. Ich werde sehr wahrscheinlich weiter an dem Projekt arbeiten, falls meine sonstigen Verpflichtungen es zulassen. Dann werde ich mir aber eine bessere Möglichkeit suchen als Google Colab.
 +
 +Mich hat es sehr begeistert im Laufe dieses Projektes die Funktionsweise eines Autoencoders und von Neuronalen Netzwerken im Allgemeinen besser kennen zu lernen. Besonders spannend fand ich es, zu sehen, welche Korrelationen der Autoencoder in Verbindung mit der Hauptkomponentenanalyse in den Trainingsdaten finden konnte. So hingen manchmal die Haarfarbe einer Person und die Neigung des Gesichtes zusammen. Dies lag höchstwahrscheinlich einfach an den Trainingsdaten,​ die zufällig diese Korrelation aufwiesen. Manche der Merkmale, die ich ursprünglich trennen wollte, hingen aber offensichtlich zu sehr miteinander zusammen, um getrennt zu werden. So waren lange Haare meist in Verbindung mit einem schmaleren Gesicht und mehr Make-Up zu sehen. Blonde Haare meist im Zusammenhang mit heller Haut. Das Programm kann nichts dafür, es hat keine Vorurteile. Alle Vorurteile, die das Programm zu haben scheint, lassen sich durch die Zusammensetzung der Trainingsdaten begründen. Diese Daten bilden (in diesem Projekt) im Optimalfall einen Querschnitt durch die Promi-Welt. Es wurden also nur Zusammenhänge gelernt, die wirklich existieren. Die Gefahr dabei ist, Randgruppen außen vor zu lassen. Dieses Problem gibt es potentiell immer, wenn Machine Learning eingesetzt wird, um Entscheidungen über Menschen zu treffen. Im Endeffekt ist dies oft ein Zeichen für "​underfitting",​ also eine zu starke Abstraktion der Realität. Vermeiden lässt sich dies nicht wirklich, aber es ist wichtig, sich diese Tatsache vor Augen zu führen. Mir hat dieses Projekt dabei geholfen.
 ===== Quellen ===== ===== Quellen =====
  
ss19/eigenfaces.1569184159.txt.gz · Zuletzt geändert: 2019/09/22 22:29 von Labo