Foto's schalen met TensorFlow
Afbeeldingen schalen met neurale netwerken en TensorFlow
Googles bibliotheek TensorFlow is een universele tool om met neurale netwerken te experimenteren. We hebben die gebruikt om een netwerk te leren hoe het de resolutie van foto’s kan verbeteren. Omdat het netwerk vormen in de afbeelding herkent en ontbrekende pixels toevoegt, maakt het foto’s zichtbaar scherper dan de standaardschaling van fotobewerkingsprogramma’s.
Neurale netwerken draaien meestal in de cloud, maar toch kun je er zelf mee experimenteren. De software die je ervoor nodig hebt, is opensource en draait op doodnormale computers. Zelflerende frameworks zoals Theano, TensorFlow, Torch 7 en Caffe gebruiken daar twee verschillende methodes voor. Bij de methode 'gecontroleerd leren' worden vooraf gedefinieerde neuronenlagen met synapsen met elkaar verbonden. Het netwerk krijgt voorbeelden van invoer en de bijbehorende uitvoer, waarbij het algoritme leert hoe de eigenschappen van de invoer bepalend zijn voor de uitvoer. De ontwikkelaar bepaalt in de code hoe de neuronenactiviteit (forward pass) alsmede de afgeleide (backward pass) berekend moeten worden. Na de leer of trainingsfase kan het algoritme voor nieuwe invoer dan zelfstandig de juiste uitvoer produceren. De methode 'ongecontroleerd leren' is flexibeler. De ontwikkelaar geeft daar nog wel voorbeelden van de invoer, maar niet van de gewenste uitvoer. Het algoritme ontdekt dan zelf een structuur in de gegeven invoer. De ontwikkelaar definieert nog wel een forward pass, maar de afgeleide wordt automatisch berekend.
Omdat Googles TensorFlow volgens de tweede methode werkt, kun je er allerlei experimenten met neurale netwerken mee uitvoeren. In principe is het framework geschikt voor elk probleem waarbij een optimalisatiealgoritme op basis van trainingsdata automatisch de parameters van een formule berekent.
In TensorFlow definieer je de formules als normale broncode. Het framework heeft bindings voor de programmeertalen Python, Go en Java. We hebben de PythonAPI gebruikt omdat die alle functies ondersteunt en omdat die op GitHub met afstand het vaakst voor TensorFlowprojecten wordt ingezet. De Pythoncode
x = (a + b) / c
zorgt er zoals gebruikelijk voor dat in x de som van a en b gedeeld door c staat. In normale Pythoncode moet je eerst waarden aan de variabelen a, b en c toekennen voordat de interpreter x kan berekenen. Bij TensorFlow definieer je variabelen echter als Variableof placeholderobjecten die aanvankelijk geen waarden hebben. Placeholders zijn er voor het opslaan van invoerdata, terwijl Variableobjecten met waarden geïnitialiseerd worden die door optimalisatiealgoritmes aangepast kunnen worden.
TensorFlow volgt wat er met zijn variabelen gebeurt en bouwt intern een graaf op die de formule representeert. De knopen van de graaf zijn tensoren, oftewel multidimensionale arrays, en de lijnen daartussen zijn de berekeningen. Met die graaf optimaliseert het framework de formule zo goed mogelijk zonder specifieke waarden te gebruiken. Optimalisatiealgoritmen zoals de stochastische gradientdescent breiden de graaf zelfstandig uit. Om dit efficiënt te doen hebben ze een fitnessfunctie voor de afgeleide nodig. TensorFlow berekent die automatisch.
Om met specifieke waarden te rekenen, maak je een TensorFlowSession aan en roep je de methode run() aan. Je geeft run() een lijst met de symbolische variabelen mee die je wilt berekenen, bijvoorbeeld x uit de formule hierboven, en een dictionary met de variabelenamen en de waarden die TensorFlow in de formule moet gebruiken. De complete code om x te berekenen voor a=2, b=3 en c=4, ziet er dan zo uit: import tensorflow as tf a = tf.placeholder(tf.float32) b = tf.placeholder(tf.float32) c = tf.placeholder(tf.float32) x =(a + b)/c sess = tf.Session() result = sess.run([x], {
a: 2, b: 3, c:4
}) print(result) # 1.25
Netwerken bouwen
Wanneer je een neuraal netwerk bouwt, begin je met het voorbereiden van de invoergegevens. Dat moeten floatingpointgetallen tussen 0 en 1 zijn. Bij klassieke machinelearning zou je een vector berekenen van de meest kenmerkende eigenschappen. Voor gezichtsherkenning zouden dat bijvoorbeeld de afstand tussen de ogen, de lengte van de neus en de breedte van de mond en dergelijke zijn. Voor het extraheren van die gegevens is echter veel met de hand geschreven code nodig. Daar ligt de kracht van diepe neurale netwerken. Die kunnen meteen overweg met gestandaardiseerde invoergegevens en leren op hun diepere lagen welke eigenschappen relevant zijn voor de gewenste output. Net als bij klassieke machinelearning trekken ze op de bovenste lagen dan conclusies.
Voor het schalen van foto’s moet het neurale netwerk daarom uit diverse neuronenlagen bestaan die vormen herkennen. De invoerdata zijn een vierdimensionale tensor: batchgrootte × fotobreedte × fotohoogte × drie kleurkanalen.
In ons voorbeeld bestaat een batch telkens uit vijf afbeeldingen. Om de tensor te normaliseren, is het voldoende dat je de integerkleurwaarden van de pixels deelt door 256 en de resulterende floatingpointgetallen gebruikt. De berekeningen in de verborgen lagen van het netwerk houden rekening met naburige pixels, maar kennen elk neuron op de inputlaag aan een neuron op de outputlaag toe. Het netwerk kan dus niet de resolutie verhogen, maar een wazige foto wel scherper maken. Als voorbereiding daarop schaalt de code de originele afbeelding eerst bicubisch.
resized = tf.image.resize_bicubic(
inputs / 256.0,[480, 640])
Op de hoogste laag moeten de neuronen zo 'vuren' dat er een scherpe afbeelding met de geschaalde resolutie ontstaat. Omdat daar getallen tussen 0 en 1 ontstaan, moet de tensor weer met 256 vermenigvuldigd en op gehele getallen afgerond worden om een afbeelding te maken.
Netwerklagen
De eenvoudigste laag van een neuraal netwerk bestaat uit neuronen die een synaps (verbinding) naar elk neuron op de daaronder liggende laag hebben (een zogeheten fully connected layer). Voor een foto zou dat betekenen dat het netwerk elke vorm en elk patroon voor elke positie in de af