====== 1. Einleitung ====== {{:ws1920:apollon_overview.png?300 |}} Der Name unseres Projektes, **Apollon** steht für affective, personal, online, located, laudable, overdue und network. Diese Wörter haben wir zusammen ausgewählt, weil sie unser Projekt sehr gut beschreiben. Die Anfangsbuchstaben der Wörter bilden unseren Projekt Namen **Apollon**. Dieser ist in der griechischen und römischen Mythologie der Gott des Lichts, der Heilung und der **Musik**. Die Musik spielt in unserem Projekt eine bedeutsame Rolle, weshalb wir den Namen so wählten. Das Projekt Apollon ermöglicht durch ein trainiertes Netzwerk die Grundstimmung einer Person zu bestimmen. Das Netzwerk wurde mit TensorFlow und Keras trainiert. Die vier Grundstimmungen sind glücklich, traurig, aggressiv und entspannt. Anhand des Gesichts der abgebildeten Person, wird die Stimmung erkannt. Schließlich wird ein Spotify Link zu einem Lied aus einer der vier erstellten Playlists gesendet, das am Besten zu der Stimmung passt. Das Lied kann anschließend abgespielt werden. Somit ermöglicht Apollon Lieder zur passenden Stimmung durch nicht langes suchen zu finden. Zusammenfassend kann man zu der Umsetzung unseres Projekts sagen, dass ein Selfie an unseren Telegram Bot gesendet werden kann, das Bild analysiert und durch Preprocessing verändert wird und schlussendlich die Stimmungen erkannt wird, ein passender Song wird zurückgesendet. --------------------------------------------------------------------------------------------------------- ====== 2. Projektbestandteile ====== Unser Projekt besteht aus den folgenden Python-Skripten bzw. Teilen: * Skript zum Aufsetzen + Trainieren des Neuronalen Netzwerks * **Apollon-Programm** (hier sind die einzelnen Bestandteile des Projekts gelistet, als Funktionen) * **Apollon-Bot** (quasi die Oberfläche, welcher das Apollon-Programm dann aufruft und ausführt) Im Folgenden werden zunächst die einzelnen Bestandteile des Apollon-Programms erklärt und im Anschluss der Apollon-Bot dargestellt. ===== 2.1 Neuronales Netzwerk zur Emotionserkennung ===== Bevor wir euch erklären, **wie** wir Machine Learning konkret bei APOLLON eingesetzt haben, gibt es erstmal eine kurze Erklärung, **warum** wir Machine Learning genutzt haben. Alle, die sich mit Machine Learning schon auskennen, können gerne den nächsten Absatz überspringen. === ⇒ Exkurs: Warum braucht APOLLON Machine Learning + CNN? === Unsere Problemstellung bei APOLLON ist ja relativ komplex, schließlich soll ein Computer Emotionen von Menschen anhand ihres Gesichts erkennen. (Häufig fällt das ja schon manchen Menschen nicht so leicht, LOL) Wir haben uns entschieden, dies anhand Fotos, die Menschen von ihrem Gesicht machen (sogenannte Selfies) zu operationalisieren. Da Nutzer*innen eigene Selfies an APOLLON schicken, muss unser Programm in der Lage sein, Emotionen auf **neuen, dem Programm unbekannten** Bilder zu erkennen und zu klassifizieren. Einfaches "auswendig" lernen reicht also nicht! Eine Generalisierung ist erforderlich. Dies kann man mit Machine Learning Ansätze ermöglichen. Beim Maschine Learning füttert man einen Algorithmus mit einem (gelabelten) Trainingsdatensatzes. Ohne dass wir weitere Anweisungen geben müssen, "erkennt" der Algorithmus beim Trainings über die Zeit Muster in dem Datensatz und entwickelt und optimiert ein statistisches Modell. Wenn wir mit dem Modell zufrieden sind, können wir es speichern und dann damit neue Bilder (mit einer gewissen Wahrscheinlichkeit) klassifizieren. (Das heißt auch, dass Machine Learning genau genommen auch "nur" Statistik ist!) Es gibt verschiedene Algorithmen, die für verschiedene Datenstrukturen besonders gut geeignet sind. Da wir Fotos benutzen, enthalten unsere Daten einiges an Information (48x48 Pixel, die alle einen (Grau-)Wert haben und dann auch nicht für sich betrachtet werden können, sondern in Clustern angeordnet sind etc.). Um solche Daten mit Machine Learning möglichst gut zu Klassifizieren, eigenen sich ** Convolutional Neural Networks**. So eins haben wir auch bei Apollon genutzt, mit Keras ist die Umsetzung relativ einfach. Unser Netzwerk bekommt als Input eine zwei-dimensionale Matrix (das jeweilige Foto in Graustufen). Dieses Bild wird dann durch mehrere convolutional layers, pooling layers und zum Schluss fully-connected layers geschickt (genauer Netzwerk-Aufbau siehe weiter unten). Das besondere an den convolutional Layers ist, dass sie sozusagen kleine Filterkernel über den Input legen (ähnlich wie rezeptive Felder in der Biologie), und so Features extrahieren können. In den Pooling layers werden nicht benötigte Informationen verworfen. Am Ende gibt uns unser Netz aus, mit welcher jeweiligen Wahrscheinlichkeit das neue Bild die Emotion zeigt. === Umsetzung in APOLLON === Mithilfe von **Keras** und **TensorFlow** haben wir ganz im vorhinein ein neuronales Netzwerk aufgesetzt und trainiert. Dies passierte in einem separaten Skript. Als Trainingsdatensatz haben wir **fer2013** von der Plattform kaggle benutzt. Dieser Datensatz enthält über 28.000 Bilder, 48x48 Pixel in Graustufen, welche auf 7 Emotionen hin beschriftet sind. Für unser Projekt mussten wir zunächst den Datensatz exportieren + aufbereiten. Zum Einlesen wurde die **pandas** library benutzt. Wir haben uns entschieden, nur 4 verschiedene Emotionen zu berücksichtigen, und mussten daher den Datensatz um 3 reduzieren, das wurde wie folgt gelöst: # EXCLUDE data = raw_data[(raw_data.emotion != 1) & (raw_data.emotion != 2) & (raw_data.emotion != 5)] # data is the raw_data without the rows where emotion = 1, etc # RELABEL data.loc[data['emotion'] == 4, 'emotion'] = 1 data.loc[data['emotion'] == 6, 'emotion'] = 2 # relabel: 4, 6 to 1, 2 Für den Aufbau des neuronalen Netzwerks haben wir uns an einem ähnlichen Beispiel aus dem Buch von **Aurélian Géron** orientiert. Er beschreibt dort den Aufbau eines Netzwerks, welches Kleidungsstücke in Kategorien einordnet. Der Kleidungsstücke-Datensatz besteht genau wie fer2013 ebenfalls aus sehr kleinen Bildern in Graustufen, und da wir selber bisher keine Erfahrung mit neuronalen Netzwerken mitbringen, haben wir uns für diesen Aufbau entschieden. Das Netzwerk besteht aus 14 layers. Zunächst sind 2D convolutional layers und pooling layers verbaut, im Anschluss fully connected layers. Die finale Netzwerk-Architektur sieht so aus: from functools import partial DefaultConv2D = partial(keras.layers.Conv2D, kernel_size=[3,3], activation='relu', padding="SAME") model_2 = keras.models.Sequential([ #keras.layers.InputLayer(input_shape = 48, 48,), DefaultConv2D(filters=64, kernel_size= [7,7], input_shape=[48, 48, 1]), keras.layers.MaxPooling2D(pool_size=2), DefaultConv2D(filters=128), DefaultConv2D(filters=128), keras.layers.MaxPooling2D(pool_size=2), DefaultConv2D(filters=256), DefaultConv2D(filters=256), keras.layers.MaxPooling2D(pool_size=2), keras.layers.Flatten(), keras.layers.Dense(units=128, activation='relu'), keras.layers.Dropout(0.5), keras.layers.Dense(units=64, activation='relu'), keras.layers.Dropout(0.5), keras.layers.Dense(units=7, activation='softmax'), ]) Trainiert wurde das Netzwerk mit den folgenden Parametern: history = model_2.fit(train_pixels, train.emotion, epochs=50, validation_data=(test_pixels, test.emotion)) Auf dem Trainingsdatensatz erzielte es so eine Akkuratheit von mehr als 90%, aber die für den Test-Datensatz schwankte sie um die 64%, auch nach wiederholtem Training. Wenn man das Netzwerk weiter optimieren würde, hätte Apollon hier vermutlich das größte Verbesserungs-Potential. Das Netzwerk haben wir gespeichert und exportiert. Mit der folgenden Funktion aus unserem Apollon-Programm wird das Netzwerk von unserem Bot genutzt, um die Emotion eines Selfies zu bestimmen: # FOR PREDICT import tensorflow as tf from tensorflow.keras.models import load_model from numpy import asarray #load network that we trained before! model = load_model('netz_v1.h5') ## PREDICT def stimmungs_analyse(file_name): """diese Funktion analysiert das Bild und gibt eine Stimmung in Form einer Zahl von 0-3 aus""" image = Image.open(file_name) # convert image to numpy array + reshape data = asarray(image) data = data.reshape(-1, 48,48, 1) # this returns us a numpy.ndarray with 4 cells # in each cell is the probability that it belongs to this class probabilities = model.predict(data) # find maximum, this is the mood # x value gives us the class! #labels = (0 = "Angry", 1 ="Sad", 2="Neutral", 3 = "Happy") x = np.argmax(probabilities) return x Mit der Funktion model.predict() aus Keras erhält man für ein Bild für jede der 4 möglichen Kategorien eine Wahrscheinlichkeit, die jeweils angibt, wie sicher sich unser Modell ist, dass das Bild in diese Kategorie gehört. Der Einfachheit halber haben wir uns dafür entschieden, das Maximum zu nutzen (das heißt das Bild wird in die Kategorie eingeordnet, in welcher die Wahrscheinlichkeit am höchsten ist). ===== 2.2 Bild-Preprocessing ===== Das Machine Learning ist vom richtigen Preprocessing abhängig. Das Netzwerk wird unter bestimmten Bedingungen und Einstellungen trainiert und kann mit diesen bestmöglich arbeiten. Bei der Bildvorverarbeitung wird das Bild für das Netzwerk kompatible geändert. Da unser Netzwerk mit 48x48 Pixel Bildern trainiert wurde, ist es wichtig dass die gesendeten Selfies zu diesem Bildformat verändert werden. Somit wird beim Bild-Preprocessing die Höhe und Breite verändert und das Bild in Graustufen umgewandelt und abgespeichert. Die Größe des Bildes wird mithilfe der PIL-Bildbibliothek geändert, die zuvor installiert werden sollte. Die Bilddatei kann als bmp, jpg, png oder gif gespeichert werden, in unserem Code als png. Im Folgenden ist der entsprechende Code zum Bild-Preprocessing dargestellt, formuliert als Funktion mit dem Namen prepare(). Diese Funktion wird später von unserem Bot aufgerufen. # FOR PREPARE # Import the Image from the PIL library from PIL import Image import numpy as np from skimage import transform ## PREPARE def prepare(filename_as_string, dithering=True): """diese Funktion bereitet das Bild vor, dass unser Netzwerk es analysieren kann""" # öffne das entsprechende File imageFile = filename_as_string im1 = Image.open(imageFile) # RESIZE und CROP to improve quality # width and height width = 48 height = 48 cropbox = (20, 30, 20, 35) teil = im1.crop(cropbox) # one of these filter options is used to resize the image im2 = im1.resize((width, height), Image.NEAREST) # use nearest neighbour # best down-sizing filter # CHANGE COLOR # converting the test-image into gray color_image = im2 if dithering: # color_image.convert is the function in the PIL library (to convert the colors of the function) # .convert('L') is the gray code conversion g = color_image.convert('L') else: g = color_image.convert('L', dither=Image.NONE) # SPEICHERE DAS BILD # new file name ext = ".png" new_file_name = "sg_{}{}".format(filename_as_string, ext) g.save(new_file_name) return new_file_name ===== 2.3 Spotipy ===== Um Spotipy verwenden zu können wird zuallererst eine Nutzungsberechtigung (Token) benötigt. Diesen erhält man auf der Spotify for developers Seite. Dafür muss man einigen einfach beschriebenen Schritten folgen. Hat man das Token kann man anfangen Spotipy zu nutzen. Die Musiksuche im Anschluss funktioniert sehr einfach, da das Programm anhand von einem Zahlwert zwischen 0 und 4 entscheidet, aus welcher Liste ein Lied auswählt wird. Die ausgewählte Liste wird folgend Zeile für Zeile eingelesen und in zwei Listen eingefügt. In der ersten Liste stehen die Namen der Lieder und in der zweiten die Namen der Interpreten. Das Lied welches abgespielt werden soll wird letztendlich durch eine zufällig generierte Zahl ermittelt, welche zwischen 1 und 100 liegt. Im Anschluss wird eine Suche bei Spotify nach dem Namen des Liedes und des Interpreten gestartet. Im Folgenden ist der entsprechende Code dargestellt, formuliert als Funktion. Diese Funktion wird später von unserem Bot aufgerufen: # FOR SELECT import spotipy import spotipy.util as util import webbrowser from os import system import time import random ## SELECT def Musik_Suche(x): """diese Funktion sucht zu einer Stimmung x (einer Zahl von 0-3) einen Song aus und bereitet einen spotify Link vor:)""" token = util.oauth2.SpotifyClientCredentials(client_id="GEHEIM",client_secret="GEHEIM") cache_token = token.get_access_token() spotify = spotipy.Spotify(cache_token) y=[] a=[] z = random.randint(1, 100) #random zahl zur auswahl des liedes b="" if x == 0: f=open('angry_list.txt', 'r' ,encoding ='utf8', errors ='ignore') for i in range (100): b=(f.readline()) print(b) c = b.split(":",3) a.append(c[1]) y.append(c[2]) elif x == 2: f=open('relaxed_list.txt', 'r' ,encoding ='utf8', errors ='ignore') for i in range (100): b=(f.readline()) print(b) c = b.split(":",3) a.append(c[1]) y.append(c[2]) elif x == 1: f=open('sad_list.txt', 'r' ,encoding ='utf8', errors ='ignore') for i in range (100): b=(f.readline()) print(b) c = b.split(":",3) a.append(c[1]) y.append(c[2]) elif x == 3: f=open('happy_list.txt', 'r' ,encoding ='utf8', errors ='ignore') for i in range (100): b=(f.readline()) print(b) c = b.split(":",3) a.append(c[1]) y.append(c[2]) print (z) name = y[z-1] music =a[z-1] # Diesen track moechte ich abspielen results1 = spotify.search(q='artist:'+ name + ' track:'+ music, type='track', limit=1) test = results1["tracks"]["items"][0]["album"]["external_urls"]["spotify"] #gebe mir die URL des Album auf dem der Track ist test2 =results1["tracks"]["items"][0]["external_urls"]["spotify"] # gebe mir die URL des Tracks nachricht = test2 return nachricht #print(test) #Ich betrachte die URL von test #print(test2) #Ich betrachte die URL von test2 # die beiden print befehle habe ich gemacht um die URLs persönlich zu vergleichen # webbrowser.open(test2, new=1) # hiermit öffne ich die URL des Tracks, was mir dann den Track aus dem zughörigen Album öffnet aber leider nicht abspielt ===== 2.4 Telegram Bot ===== Als “Benutzeroberfläche” für unser Programm haben wir uns für Telegram und einen Bot entschieden. Diesen haben wir mit der library python-telegram-bot erstellt. Der Bot ruft das Apollon-Programm auf und führt nacheinander die einzelnen Bestandteile von Apollon aus. Die Bestandteile sind im Programm als Funktionen definiert. Die ganze Funktion von Apollon steckt im folgenden Code-Ausschnitt des Bots: #APOLLON Foto Stimmungs-Analyse und Bild Empfehlung def photo(update, context): # teil 1: receive + save selfie from user user = update.message.from_user photo = update.message.photo[-1].get_file() #add current time to file name to create unique file name in case user sends multiple pics now = datetime.now() current_time = now.strftime("%H:%M:%S") #define filename, add user name filename = 'selfie_{}{}.jpg'.format(user.first_name, current_time) # download + save foto under previously defined filename photo.download(filename) #respond this when the bot received a picture: context.bot.send_message(chat_id=update.effective_chat.id, text='Danke für dein Foto, ich berechne deine Stimmung') #teil 2: apollon programm #1 bild pre-processing image = apollon.prepare(filename) #2 analyse der stimmung stimmung = apollon.stimmungs_analyse(image) #3 auswahl des songs link = apollon.Musik_Suche(stimmung) # teil 3: Antwort von Apollon: sende den link nachricht = "Hier, für dich, probiere es mal mit diesem Lied: {}".format(link) context.bot.send_message(chat_id=update.effective_chat.id, text= nachricht) # set handler + dispatcher photo_handler = MessageHandler(Filters.photo, photo) dispatcher.add_handler(photo_handler) Hier werden nacheinander die Funktionen des Apollon-Programms ausgeführt. (Der Aufbau und die Funktionsweise der einzelnen Funktionen wurden oben bereits beschrieben) Ansonsten ist unser Bot so programmiert, dass dieser auf jede mögliche Nachricht mit der Aufforderung:"Um eine Musikempfehlung zu erhalten, schicke mir bitte ein Selfie!" antwortet, außer die Person schickt ein Foto. Alles, was Nutzer*innen von unserem Bot mitbekommen, ist die Oberfläche auf telegram. Dort sieht das dann so aus: {{:ws1920:apollon_screenshot.png |}} **Unter dem folgenden Link können Telegram-Nutzer*innen unseren Bot finden:** https://t.me/Apollonmusik_bot (Achtung, er funktioniert derzeit nur, wenn das Programm auf einem PC läuft.) ------------------------------ ====== 3. Bericht über den Verlauf ====== Die erste Idee, die wir hatten, war es ein Programm zu schreiben, welches Musik abspielt aufgrund dessen welche Stimmung eine Person hat.Dazu haben wir uns in drei Teilbereiche unterteilt (Stimmungserkennung, Musikwiedergabe und Wiki-Dokumentation schreiben). Relativ schnell war klar, dass die Stimmungserkennung über Fotos stattfinden soll, und zwar mit Hilfe eines neuronalen Netzwerkes, weil uns hier die Umsetzung interessierte (weitere Erläuterungen hier ⇒ //Exkurs: Warum Machine Learning + CNN?// ). Unsere zweite Überlegung war dann, ob die Musik die Stimmung heben soll oder ob die Musik die Stimmung widerspiegeln soll und über welches Medium wir die Musik ausgeben lassen (Youtube, Spotify, nur eine Titel, etc.). Letztendlich sind wir zu dem Schluss gekommen, das sich Spotify besonders eignet, da ein Python Package zur Anbindung an Spotify existiert (Spotipy). Des Weiteren entschieden wir uns dafür, die Anwendung mit einem Telegram Bot zu verbinden, sodass ein über Telegram an den Bot geschicktes Bild in das Programm eingelesen werden kann. Nach diesen anfänglichen gemeinsamen Überlegungen und Planungen in den ersten Terminen, verbrachten wir die nächste Zeit damit, uns zu unseren Bereichen zu informieren und die gesammelten Informationen einerseits in unserem Programm umzusetzen, aber auch damit die Info und den Fortschritt mit dem Rest des Teams zu teilen. Die letzten Schritte unserer Arbeit waren die fertigen Programmteile zusammenzufügen und die Dokumentation und Präsentation anzufertigen. Dies erfolgte größtenteils während der Blocktage. Detaillierteres zum Ablauf des Projekts (nämlich unsere Protokolle) findest du unter ⇒ [[Projekt - Dokumentationen]] :) -------------------------------- ====== 4. Fazit und Ausblick ====== Insgesamt sind wir mit dem Projekt-Verlauf und dem Ergebnis sehr zufrieden, da wir alle vorher keinerlei Python-Kenntnisse hatten. Wir haben bei der Umsetzung des Projekts viel gelernt und haben gut zusammen gearbeitet. Allerdings könnte man mit mehr Zeit unser Apollon-Programm erheblich verbessern, gerade ist es eher eine Art Musik-Zufalls-Generator ;-). Um die Stimmung-Erkennung zu verbessern, müsste man das Netzwerk optimiert, sei es durch die Auswahl eines besseren Traings-Data-Sets (hier würde sich eine gelabelte Selfie-Datenbank anbieten, wenn es so etwas gibt), die Auswahl einer besseren Architektur oder die Optimierung der Trainingsparameter. Auch eine Feedback-Funktion für die Nutzer*innen (Passte der Song zu deiner Stimmung?) könnte den Bot durch die Möglichkeit zum online-lernen verbessern. Des Weiteren wäre es schön, die Musik-Datenbank zu vergrößern (aktuell enthält jede Liste nur 100 Songs) und es wäre auch schön, eine Möglichkeit zu finden, die Songs ohne Spotify zu verschicken. Was den Bot betrifft, läuft unser Programm lokal auf einem Computer - daher funktioniert es nur, wenn der Computer auch online ist. Es wäre interessant, verschiedene Hosting-Optionen auszuprobieren (zB. Raspberry Pi), um eine dauerhafte Funktion zu gewährleisten. --------------------- ====== 5. Literaturangabe und verwendete Software ====== === Neuronales Netzwerk und Emotionen:=== Géron, A. (2019). Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow: Concepts, Tools, and Techniques to Build Intelligent Systems. O'Reilly Media. https://medium.com/themlblog/how-to-do-facial-emotion-recognition-using-a-cnn-b7bbae79cd8f https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6514572/ https://iopscience.iop.org/article/10.1088/1742-6596/1193/1/012004/pdf https://medium.com/ai-theory-practice-business/ai-scholar-deep-learning-facial-expression-recognition-research-fcaa0a9984b6 https://www.kaggle.com/c/challenges-in-representation-learning-facial-expression-recognition-challenge/overview https://towardsdatascience.com/face-detection-recognition-and-emotion-detection-in-8-lines-of-code-b2ce32d4d5de https://developers.google.com/machine-learning/crash-course https://vas3k.com/blog/machine_learning/ Fer von Kaggle: https://www.kaggle.com/c/challenges-in-representation-learning-facial-expression-recognition-challenge/data -------- === Alles zum Preprocessing: === Pillow instalation: https://www.youtube.com/watch?v=xvhjcQY2lqM&feature=youtu.be https://www.youtube.com/watch?v=6Qs3wObeWwc&feature=youtu.be https://pillow.readthedocs.io/en/stable/installation.html https://pillow.readthedocs.io/en/3.0.x/handbook/tutorial.html https://pillow.readthedocs.io/en/3.1.x/reference/Image.html http://www.pythonware.com/media/data/pil-handbook.pdf ------- === Alles zur Musik und Spotipy:=== https://cs.nju.edu.cn/sufeng/data/musicmood.htm https://buildmedia.readthedocs.org/media/pdf/spotipy/latest/spotipy.pdf https://developer.spotify.com/documentation/web-api/ https://spotipy.readthedocs.io/en/latest/ ------ === Telegram Bot:=== https://python-telegram-bot.org https://github.com/python-telegram-bot/python-telegram-bot/wiki/Introduction-to-the-API https://www.youtube.com/watch?v=EympPr04WVw https://stackoverflow.com/questions/31172302/how-to-receive-images-with-the-telegram-api https://core.telegram.org/bots/api#getfile ------- Es war ein tolles Projekt! Viel Spaß bei euren :)