Benutzer-Werkzeuge

Webseiten-Werkzeuge


Seitenleiste

ws1920:projekt_-_bericht

1. Einleitung

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 (48×48 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, 48×48 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 48×48 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:

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:

Alles zum Preprocessing:

Alles zur Musik und Spotipy:

Telegram Bot:

ws1920/projekt_-_bericht.txt · Zuletzt geändert: 2020/04/02 20:45 von justanni