Benutzer-Werkzeuge

Webseiten-Werkzeuge


Seitenleiste

ws2021:ekg-analyse

EKG-Analyse

Teilnehmer

Elena, Annika

Projektidee und Ziele

Unser grundsätzliches Ziel ist es, EKG-Daten auszuwerten und diese, mithilfe eines Algorithmus, nach krank und gesund zu unterscheiden. Da das Analysieren eines solchen EKGs eine für den Menschen sehr komplexe Aufgabe ist, wollten wir herausfinden, ob es einfacher ist, diese Problemstellung von einem Computer lösen zu lassen.

Zwischenziele und Zeitplan

Dokumentation

Einführung

Medizinische Grundlagen

Was ist ein EKG?

Ein Elektrokardiogramm ist ein weit verbreitetes Diagnoseverfahren, welches Medizinern Aufschlüsse über die Gesundheit des Herzens eines Patienten liefert. Es ist eine nicht-invasive Methode, die für Patienten kaum Risiken birgt, weshalb sie in der Praxis standardmäßig eingesetzt wird.

Wie und was misst ein EKG?

Ein Elektrokardiogramm misst die elektrische Erregung im Herzen, indem 10 Elektroden den Strom an der Hautoberfläche detektieren. Diese werden im Anschluss so miteinander verschaltet, dass 12 Spannungskurven entstehen, aus denen dann Rückschlüsse auf die Pumpaktivität des Herzens und somit die kardiologische Gesundheit eines Menschen gezogen werden können.

schematische Darstellung Sinuskurve Sinuskurve aus Datensatz

Hier seht ihr einen gesunden Sinusrhythmus (links schematisch und rechts ein Plot aus dem Datensatz), wie genau er aufgebaut ist könnt ihr hier erfahren.

Datensatz

Die Daten, die wir analysiert haben, stammen aus einem riesigen öffentlichen Datensatz im Internet, der Daten von 18.885 Patienten umfasst. Das Besondere an diesem Datensatz ist, dass auch sehr viele kardiologisch gesunde Patienten inbegriffen sind. Oft wird das vernachlässigt, ist für uns aber besonders entscheidend, da wir ja einen Algorithmus kreieren wollen, der auch gesunde EKGs erkennt. Der Datensatz ist relativ neu und stammt von 2020. Er enthält Angaben zu den Patienten (sogenannte Metadaten), ein diagnostisches Label, wobei der Datensatz fünf Ober- und 24 Unterklassen unterscheidet, und die EKG-Messdaten. An den Datensatz anknüpfend wurde auch ein Python-Code zur Verfügung gestellt, mithilfe dessen, auf die Daten zugegriffen werden kann und, der die Daten schon in Trainings- und Testdaten unterteilt.

Hier geht es zum Datensatz

Machine Learning

EKG-Analyse - Wie lassen sich Daten allgemein analysieren?

Mithilfe von mathematischer Funktionen lassen sich allgemeine Informationen aus Daten ableiten. Bei dieser Art von Daten ist der Zusammenhang recht offensichtlich. Doch EKGs sind weitaus komplexere Datenstrukturen, dementsprechend komplex gestaltet sich auch unsere mathematische Analyse der Daten. Um Zusammenhänge zwischen solch komplexen Daten zu ermitteln, bietet sich Machine Learning an.

Was ist Machine Learning?

Maschinelles Lernen ist das näherungsweise Optimieren einer Funktion. Diese Funktion ordnet, mittels eines neuronalen Netzes, einem komplexen Input einen vorgegebenen Output zu. Dies bedeutet im Grunde, dass neuronale Netze einen mathematischen Weg nutzen, um allgemeine Merkmale aus Daten zu abstrahieren, die sich zum Beispiel mittels objektiver Labels verifizieren lassen. Da das Finden dieser mathematischen Funktion näherungsweise und Schritt für Schritt passiert, und an unserem menschlichen Lernen orientiert ist, spricht man von maschinellem Lernen.

Was sind neuronale Netze?

Neuronale Netze sind von den neuronalen Verknüpfungen unserer Gehirne inspirierte Strukturen. So bestehen sie aus mehreren Schichten aus Neuronen bzw. Knotenpunkten, die zunächst keinen eigenen Wert besitzen. Zwischen den Neuronen der Schichten gibt es Verbindungen und zwar von jedem Neuron der vorhergehenden Schicht zu jedem Neuron der darauffolgenden Schicht. Diese Verbindungen sind essentieller Bestandteil des Netzes. Denn jede Verbindung hat eine Gewichtung, die bestimmt, wie relevant die Verknüpfung zwischen den beiden Neuronen ist.

Schematische Darstellung eines neuronalen Netzes mit linearen Schichten und gewichteten Verbindungen

Wie das Netz lernt erfahrt ihr hier

Hilfsmittel und Bibliotheken

PyTorch

Für unser Netzwerk haben wir mit Pytorch gearbeitet. Das ist eine Python-Bibliothek, die viele Bausteine für die Implementierung neuronaler Netze enthält. Es vereinfacht vor allem den mathematisch-rechnerischen Teil und erleichtert es den Benutzern, eine Netzwerk-Architektur mit Schichten und Hyperparametern zu erstellen.

Google Colab

Des Weiteren haben wir Google CoLab verwendet, eine Online-Programmierumgebung, die auf Jupyter-Notebooks basiert. Google stellt dort Server, also Rechenkapazität, zur Verfügung, was für neuronale Netze von großer Bedeutung ist, da diese sehr viel Speicher und Rechenleistung benötigen. Dabei haben wir mit CUDA gearbeitet, einer Technologie, mithilfe derer Teile des Codes auf einer Grafikkarte (GPU) ausgeführt werden können.

Entwicklung unseres Netzwerkes

Netzwerkarchitektur

Lineare Layers

Zuerst haben wir eine Netzwerk-Klasse, bestehend aus _init_-Konstruktor und forward-Funktion, mithilfe der Pytorch-Bibliothek implementiert. Dabei enthält der _init_-Konstruktor die Definition für die Schichten im Netz. In der forward-Funktion werden dann die Verknüpfungen zwischen diesen Schichten hergestellt, in unserem Fall mittels der ReLu-Aktivierungsfunktion. Zunächst haben wir nur linearen Schichten gebaut, allerdings konnten wir damit keinen Lernerfolg erzielen, da jeglicher räumlicher Zusammenhang der Datenpunkte verloren geht.

class Netzwerk(nn.Module):
    def __init__(self):
        self.lin1 = nn.Linear(12*61, 800)
        self.lin2 = nn.Linear(800, 40)
        self.lin3 = nn.Linear(40, 10)
        self.lin4 = nn.Linear(10, 2)
        
    def forward(self, x):      
        x = F.relu(self.lin1(x))
        x = F.relu(self.lin2(x))
        x = F.relu(self.lin3(x))
        x = self.lin4(x)
        return x

Convolutional Layer

Deshalb sind wir zu einer Architektur mit gefalteten Schichten (Convolutional Layern) übergegangen. Dabei läuft ein Filter (Kernel) über das Bild, der einen bestimmten Ausschnitt der Daten zusammenfasst, bevor diese den linearen Schichten übergeben werden. Dies war für unser Netz essentiell, da so nicht alle Datenpunkte individuell betrachtet werden, sondern die einzelnen Datenpunkte in ihrem räumlichen Zusammenhang erkannt werden. Mithilfe der Convolutional Layers konnten wir unseren ersten Lernerfolg erzielen.

Hier geht es zu unserem Code mit Convolutional Layern.

ResNet-Archithektur

ResNet

Im Anschluss haben wir eine ResNet-Architektur eingeführt, um einen besseren Lernerfolg durch ein tieferes Lernen zu erreichen. Bei der ResNet-Architektur werden noch zusätzliche Layer eingeführt, sogenannte Skip Connections, die immer ein paar Schichten überspringen und so einer späteren Schicht einen output von einer vorherigen Schicht geben. Dies ermöglicht mehr Schichten, ohne dass Informationen verloren gehen. Wir haben für die Implementierung die Netzwerk-Klasse minimal verändert (zur aktualisierten Klasse kommt ihr hier) und eine zusätzliche Klasse Resnet.

Netzwerkparameter

Im folgenden erzeugen wir ein Objekt der Netzwerk-Klasse: unser Netz, dass wir mit den EKG-Daten trainieren wollen. Außerdem definieren wir den optimizer, also die Art und Weise, wie die Gewichte im Netz optimiert werden sollen, sodass das Netz EKGs erkennt. Dafür benutzen wir den SGD (=stochastic Gradient Descent) optimizer aus der Pytorch-Bibliothek. Diesem übergeben wir eine learning_rate, die die Schrittweite entlang des Gradienten bestimmt. Auch die batch_size, also die Anzahl an Trainings-Daten, über die gemittelt der Gradient berechnet wird, legen wir im folgenden Code fest.

net = Netzwerk()
learning_rate = 0.3
batch_size = 50
optimizer = optim.SGD(net.parameters(), lr=learning_rate)

Trainingsfunktion

In der Trainingsfunktion werden zunächst die Input- und Ziel-Werte entsprechend der Batch-Size eingelesen, sodass sie dem Netzwerk übergeben werden können. Anschließend werden die Input-Werte vorwärts durch das Netz geschickt (forward propagation) und die Differenz der Ergebnisse mit den Zielwerten verglichen (Fehler berechnen). Daraufhin wird dieser rückwarts durch das Netz zurück geschickt (backward propagation), wobei die Neujustierung der Gewichte im Netzwerk berechnet wird. All dies passiert innerhalb einer for-Schleife, welche dafür sorgt, dass die Anpassung der Parameter, entsprechend der Epochen-Anzahl, wiederholt wird.

def train(epoch = 3000, b_s = batch_size):
    net.train()
    for i in range(epoch):
        random_batch = [np.random.randint(len(X_train)) for i in range(b_s)]

        # input-Werte
        my_in = np.swapaxes(X_train[random_batch], 1, 2)
        my_in = Variable(torch.Tensor(my_in))
        my_in.unsqueeze(2)

        # target-Werte
        ziel = y_train[y_train.index[random_batch]].tolist()
        zielw = Variable(torch.Tensor(ziel))
        zielw = zielw.type(torch.LongTensor)
        
        # forward propagation
        criterion = nn.CrossEntropyLoss()  
        optimizer.zero_grad()               
        out = net(my_in)                     

        # Fehler berechnen
        loss = criterion(out, zielw)         

        #backward propagation
        loss.backward()                       
        optimizer.step()                     
        net.history_loss.append(loss)

Evaluationsfunktion

Die Evaluationsfunktion dient der Überprüfung des Lernprozesses und -erfolges. Wir übergen ihr unabhängige Testdaten, die das Netzwerk während des Trainings noch nicht gesehen hat und schicken diese durch unser Netzwerk. Anschließend ermitteln wir, wie viele der Testdaten von dem trainierten Netz richtig erkannt wurden.

def evaluate(test_x = X_evaluation, test_y = y_evaluation):
    net.eval()
    z = 0
    for i in range(len(test_x)):
        x = np.swapaxes(test_x[i], 0, 1)
        x = Variable(torch.Tensor(x))
        x = x.unsqueeze(0)
        if torch.argmax(net(x)) == test_y[test_y.index[i]]:
            z += 1
    return z/len(test_x)

Ergebnis

Der Graph zeigt die Klassifizierungsgenauigkeit (blau) und den Fehler (orange) über 9.000 Epochen. Unser Netz erkennt also ungefähr 83% der unabhängigen Test-Daten richtig. Das ist insofern ein Erfolg, dass ohne Training nur in etwa die Hälfte der EKGs richtig zugeordnet werden, da circa eine Hälfte der Daten jeweils gesunde beziehungsweise ungesunde EKGs enthalten. Somit hat unser Netz erfolgreich gelernt!

Allerdings ist dies noch ausbaufähig, da in etwa jedes fünfte EKG falsch zugeordnet wird. Für eine Anwendung in der klinischen Praxis ist die Fehlerquote demnach viel zu hoch und der Algorithmus nicht zuverlässig. Dennoch ist es für uns, mit sehr wenig Programmier-Erfahrung, für ein Erst-Semester Projekt ein großer Erfolg.

Hier findet ihr den finalen Code

Verlauf der Projektarbeit

Protokolle

Fazit und Ausblick

Wir haben beide Python-Kenntnisse und medizinisches Wissen erworben und Erfahrungen im Bereich Machine Learning sammeln können. Fehlerbehebung mussten wir vor allem vornehmen, wenn es darum ging, dass bestimmte Tensor-Größen nicht zusammenpassten oder in- und output sich in ihrer Dimension unterschieden. Man könnte das Netz mit Sicherheit noch weiter verbessern, indem man die Hyperparameter optimal einstellt und die Netzwerkarchitektur noch ein wenig anpasst. Außerdem könnte man als Erweiterung, das Netz spezifische Krankheitsbilder erkennen lassen. Dies hatten wir ganz zu Beginn auch überlegt, allerdings hat die „Verbesserungsphase“ unseres Netzes sehr viel länger gedauert, als wir dachten und so sind wir aus zeitlichen Gründen leider nicht mehr dazu gekommen.

Literatur

Grundlagen Machine Learning

Implementierung unseres Netzes

Pytorch

Verbesserung des Netzes

ws2021/ekg-analyse.txt · Zuletzt geändert: 2021/04/06 21:36 von annika_cibis