zondag 25 september 2016

13 - EEGs - 'Onevenwichtige resultaten'.

Ook deze keuze lijkt nog niet echt tot veelbelovende resultaten te leiden. Ik besluit mij eens wat verder te verdiepen in het werken met onevenwichtige uitkomsten. Tenslotte is maar ~ 7% van de resultaten 'positief'. Dat maakt dat een NN hoog blijft scoren door gewoon alles op 'nul' te laten uitkomen.
Ik vindt een goed artikel die hier wat duidelijkheid in geeft:

Learning from Imbalanced Classes



In het kort staan daar de volgende opties:

  • Do nothing. Sometimes you get lucky and nothing needs to be done. You can train on the so-called natural (or stratified) distribution and sometimes it works without need for modification.
  • Balance the training set in some way:
    • Oversample the minority class.
    • Undersample the majority class.
    • Synthesize new minority classes.
  • Throw away minority examples and switch to an anomaly detection framework.
  • At the algorithm level, or after it:
    • Adjust the class weight (misclassification costs).
    • Adjust the decision threshold.
    • Modify an existing algorithm to be more sensitive to rare classes.
  • Construct an entirely new algorithm to perform well on imbalanced data.
Er zijn echter weinig aanwijzingen wat wanneer te doen. Ik besluit eerst mijn loss-functie gevoeliger te maken voor de minderheids klasse. Daar heb ik nog maar weinig eerder aan gesleuteld maar een model in een vorige competitie geeft wat aanwijzingen. Ik besluit het te proberen met de 'wortel van de gemiddelde, gekwadrateerde afwijking.:

Orgineel:

def root_mean_squared_error(y_true, y_pred):
    """
    RMSE loss function
    """

    return K.sqrt(K.mean(K.square(y_pred - y_true), axis=-1))

Aangepast:

def root_mean_squared_error(y_true, y_pred):
    """
        RMSE loss function
        """

    return K.sqrt(K.mean(K.square(y_pred - y_true) + 3 * (y_true) * K.square(y_pred - y_true), axis=-1))

In dit geval maak ik de error 4x zo groot als y_true 1 is.

Ik zie helaas nog weinig verbetering. Of mijn toepassing van de formule is fout of (en?) ik moet een andere strategie bedenken om meer balans in mijn dataset te krijgen.








 




zaterdag 17 september 2016

12 - EEGs - Histogrammen 2

Het neemt enkele uren voordat de data in zijn geheel is omgezet naar mooie histogrammetjes. Meestal mooi klusje voor de nacht. Ik bouw het NN om naar een heel eenvoudig model:

def create_model_hist():
    model = Sequential()
    model.add(Dense(640, input_dim=1600 , init='uniform', activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(320, activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(1, activation='sigmoid'))

    model.compile(loss='binary_crossentropy', optimizer='rmsprop', metrics=['accuracy'])

    return model

Maar drie laagjes. Ik ben benieuwd! Inderdaad gaat het leren nu supersnel. Er wordt zelfs 0 seconden per epoch aangegeven! Wow! Hij doet echter geenszins wat ik ervan verwacht. De scores blijven slechter dan de nul-score :-(

Teveel nullen voor mijn AI :-)
Na wat onderzoek kom ik erachter dat de data veel lage waarden bevat. Dat kan idd problemen geven. Ik bedenk mij dat ik alles deel door de maximum waarde om e.e.a. tussen de nul en de 1 te krijgen. Bij 10 minuten geen registratie zijn er vermoedelijk een heleboek 'nul-hoeken' gevonden. Ik moet die eruit halen. Helaas ... opnieuw de data aanmaken. Misschien moet ik een tussenstap gaan maken door de ruwe data als 1 numpy file op te slaan. Nu pak ik elk bestand weer afzonderlijk. Ik weet echter niet of dat wel in het geheugen zal passen?!
Nou ja. Laat eerst maar even zo lopen.

  

11 - EEGs - Misschien met histogrammen?

De gebruikte grafieken zijn opgebouwd uit verbindingen tussen opeenvolgende punten met uitslagen groter dan 100 of kleiner dan -100. De hoeken tussen deze waarden geven in feite een soort frequenties weer. Wellicht kan ik deze gebruiken als 'features' om te bepalen of er een epilepsie aanval aankomt. Ik ga eens proberen of ik ze kan classificeren. Daar lijkt een histogram de aangewezen weg voor:


Het histogram geeft dus de verdeling weer in de 10 minuten registratie van alle hoeken tussen -90 en 90 graden. Als bepaalde frequenties veel gaan voorkomen zou dat indicatief kunnen zijn. Dit levert tevens een veel compactere dataset op. 16 * 100 cijfers per 10 minuten patient-meting.  Ook kan ik  van gewone, niet convolutie, netwerken gebruik maken. Dat moet veel tijdswinst geven!

Wel weer 'even' alle data converteren. Gelukkig geeft de histogram functie van numpy de aantallen en de 'bins' (vakjes?) netjes terug in een numpy array. Ik hoef dus nu niet eerst alles als afbeelding op te slaan.



zondag 11 september 2016

10 - EEGs - terug naar de tekentafel?

Met zulke tegenvallen resultaten is het denk ik eerst zaak om de gebruikte programma's te controleren. In navolging van voorbeeld toepassingen maak ik gebruik van 3 aparte programma's:

  • data.py - om de data in een toegankelijke vorm bijeen te krijgen. In dit geval heb ik daarin de data omgezet naar grafieken die de waarden buiten 100 en min 100 weergeven. 
  • train.py - zoals de naam al aangeeft wordt hier het NN getraind op de trainingsdata. Ook kunnen er nog data conversie gedaan worden indien meerdere mogelijkheden onderzocht gaan worden. In dit geval breng ik de data naar het juiste type en terug naar waardes tussen 0 en 1.
  • submission.py - hier wordt de testdata opgepakt en aan het getrainde model aangeboden. Het resultaat wort omgezet naar een submission bestand zoals door de competitie gespecificeerd. 
Ik maak een extra module 'lib.py' waarin ik de gemeenschappelijke componenten onderbreng zoals het model, constanten en dataconversie functies. Zo voorkom je dat er verschillen ontstaan in de opeenvolgende stappen.

Mede op basis hiervan maak ik ook een testdata programma om te kijken of mijn data, mijn 'grafieken' nog wel kloppen. Hier een voorbeeld van de output met een vergelijking tussen een grafieken verzameling met 'seizure' en zonder. Zie jij het verschil? :-)
Wellicht is het noodzakelijk om de data in de grafieken eerst te 'middelen'. Bijvoorbeeld corrigeren voor het gemiddelde en delen door het maximum. Wellicht is er in de verkleining van de afbeeldingen toch teveel relevante informatie verloren gegaan. Misschien moet ik de karakteristieken van het EEG op een andere wijze gaan vastleggen. Eens even over nadenken.
Het goede nieuws is iig dat de data aan het NN aangeboden lijkt zoals bedoeld. Het slechte is uiteraard de daarbij tegenvallende resultaten.

  

09 - EEGs - Eerste submissions

Vanochtend vroeg, in een kortstondige, slapeloze bui, heb ik mijn eerste submission aangeboden. Fijn dat het in 1 keer werkt (vaak krijg ik eerst de nodige foutmeldingen) maar het resultaat is verder behoorlijk teleurstellend:


Net even boven de 'All zeros benchmark' van 0.5.

Het ging om een aanbieding op basis van de vreemde resultaten uit de SGD optimizer (zie vorig bericht). Er is daar geen enkele waarde waarbij de voorspelling boven de .5 uitkomt.

>>> np.max(results)

0.4208566

Mijn werkwijze is dan om te kijken bij welke grens ik de verwachte aantallen bereik. Dat moet dus even worden onderzocht.
De traindata bestaat in totaal uit 6.042 samples waarvan er 450 'positief zijn'. D.w.z. maximaal een uur voor een epileptisch aanval zijn gemeten. Dat is 7.45%. Er weinig, daar zal ik nog wat mee moeten in latere metingen.  
Er zijn 6126 testmetingen. Uitgaande van een gelijke verhouding bij de train en bij de test data verwacht ik dan 7.45% van 6126 is 456 'positieve uitkomsten'. Door alles met een waarschijnlijkheid > 0.297 positief te beschouwen krijg ik 469 positieve uitkomsten.

Maar, zoals aangegeven, valt het resultaat erg tegen. Ik besluit om terug te gaan naar de Adams optimizer en gewoon het beste resultaat in 200 epochs te proberen. Het goede nieuws is dat de maximum voorspelling nu gestegen is naar 0.67 waarbij er 62 waarden boven de .5 uitkomen. Bij een grens van 0.349 krijg ik er 454, vlakbij de verwachte 456.

Dit zijn de bijbehorende grafieken:


    Het blijft, met name in de accuracy, voor mij een vreemd, onverklaarbaar verloop. Het resultaat geeft aan dat ik het hiermee slechter doe dan 'All zeros' (0.5) :-(




zaterdag 10 september 2016

08 - EEGs - Wat krijgen we nou? !!!!

Twee andere relevante parameters om een NN in Keras te trainen betreft de test en train accuracy. Eerst kreeg ik die dat niet gevonden totdat ik zag dat ik het verkeerde model hierop had aangepast :-(
Nu dus wel:

Eerste 100 epochs

Tweede 100 epochs
 Deze begonnen dus pas na ongeveer 115 epochs iets zinvols te doen. Wat googelen leert mij dat de keuze van de optimizer daar iets mee te maken zou kunnen hebben. Die staat op 'Adam' en men suggereerd die toch eens naar de gewone 'standaard gradient' terug te zetten.

Het resultaat in de accuracy doet weinig maar de loss grafieken zijn verbluffend!!


De test-set doet het beter dan de train-set !!!! Daar moeten we mee kunnen scoren. Snel maak ik een submission programma om de gegevens in een juist format aan de competitie aan te kunnen bieden. Helaas zie ik dan dat ik eerst de proef data van patient 2 en 3 nog in 'grafieken' moet ombouwen. Dat neemt heel veel tijd. Nou ja het is toch al filmtijd. Laat putertje maar even lekker doorblokken. Efkes zo'n 36000 grafiekjes per patient plotten!   

07 - EEGs - Zwaar overfitten?

Als ik grotere leerstappen (leercoefficient lr=1e-4 ipv 1e-5) gebeurt er iets vreemds:
Al na 20 epochs gaat de testset 'sky-high'. Ik weet niet zeker wat dat betekent. Een beetje googelen brengt bij bij de volgende grafiek:


Looks familiar, no?
Toch maar terug naar de langzamer learningrate. Het blijft vervelend dat er zulke variatie in de train-uitkomsten blijven. Overfitting kan o.a. verminderen door het model eenvoudiger te maken, meer random 'dropout' in te voeren of meer data aan te bieden. Dat laatste kan ondermeer door 'data-augmentation'. Bij afbeeldingen bijvoorbeeld ze random te draaien en/of te verschuiven en/of te zoomen. Voor mijn 'plot-afbeeldingen' weet ik niet of dat wel hulp biedt. op en neer schuiven en ietsjes zoomen wellicht.
Ik probeer eerst maar eens een (veel) eenvoudiger model om te kijken of dat wat oplevert. In ieder geval tijdswinst want de epochs duren nu maar 12 seconden. Dit is het resultaat:

More simple model
Wow! Dat vraagt om een voortzetting!

100 more epochs
Je zou zeggen dat hij  (de testset) vanaf ongeveer 160 epochs niet meet beter wordt. De trainset gaat door tot het gaatje :-)

06 - EEGs - En nu verder trainen!

Ik ben ook wellicht veel te ongeduldig. Als ik 100 epochs train lijkt er wel degelijk verbetering op te treden. De trend is interessant:

Trainen met 100 epochs
Nog steeds vindt ik de variatie in de test voorbeelden zorgwekkend. De trend is ook wel neergaand maar in het begin wordt er even een vergelijkbare waarde gehaald als op het einde. Maar wellicht nog meer geduld hebben. Ik zal met het beste resultaat (is tussendoor opgeslagen als het goed is) nogeens 100 keer trainen:

Nog een keer 100 epochs verder

Ja. Dit lijkt toch echt een gevalletje overfitting. Rond de 86 epochs wordt het beste resultaat bereikt.

vrijdag 9 september 2016

05 - EEGs - En nu trainen!

De data zit netjes omgebouwd en opgeslagen. Nu kunnen we er een NN op los gaan laten. Dat gaat mij steeds gemakkelijker af merk ik. Je bouwt toch behoorlijk wat ervaring op van al die dagen intensief code weven en fouten zoeken. Niet dat het nu makkelijk is. De lessons learned uit de voorgaande competitie was dat ik veel alerter moest zijn op bugs. Ik probeer dus regelmatig tussenresultaten te verifiëren. Met name 'snelle dataconversies', zoals even door het maximum delen om alles tussen 0 en 1 te brengen en dergelijke, even niet opletten en je werkt met lege of verstoorde data.

Eerst dus nog wat sleutelen aan de data. De afbeeldingen blijken een 'wit-waarde' van 29 te hebben. Deze zet ik om naar  0. Na omzetten naar datatype np.float32, dat geschikt is voor het werken met de grafische processor, deel ik de waarde inderdaad door het maximum zodat we een waarde tussen 0 en 1 krijgen. (In feite alleen 0 en 1 omdat de grafiekpunten allemaal het maximum, 255, waren.

Het NN moet een convolutional NN worden. We werken tenslotte met grafieken die als afbeelding 'uitgelezen' moeten worden. Ik hoop dat het karakter van de grafieken op die manier het beste uitgelezen kan worden. Hoe NN het er precies uit moet gaan zien is een beetje arbitrair. Er zijn helaas weinig eenduidige regels voor te geven. Ik baseer mij dus maar op wat voorbeelden met wat aanpassingen daar waar het mij belangrijk lijkt. Een van de afwegingen is dat het aantal input dimensies niet heel veel groter moet zijn dan het aantal train-samples. Anders zou er makkelijk 'over-fitting' plaats kunnen vinden.  Dat wil zeggen dat het model wel goed werkt voor de trainingsvoorbeelden maar geen 'generalisatie' meer doet. En daarmee dus niet geschikt om de test data te voorspellen.

Hier wordt de som van de 'X' in het blok genomen. 
Het huidige gekozen model:

Indrukwekkend nietwaar?
De resultaten zijn in eerste instantie wat teleurstellend. Er is wel sprake van een aardige, geleidelijke leercurve maar de testdata lijkt zich er weinig van aan te trekken:


Misschien dat ik te snel resultaten verwacht. Ik denk dat ik het aantal batches opvoer naar 100 en de testsize (nu 20%) wat groter maak(30%). Neemt ongeveer 20 seconde per epoch dus over een ruim half uur moeten we meer weten.





 

03 - EEGs - Voor het echie ...

Het (eerste) plan, dankzij de onderscheidende grafieken uit de eerste tests, is dus om de ingedikte data (kleiner dan -100, groter dan 100) in grafieken te plotten. Deze ontdoen van legenda en andere, voor een NN, overbodige zaken en deze dan aan een convolutional (het zijn nu afbeeldingen geworden) NN aan te bieden. Ik tegenstelling tot de eerdere plot verklein ik de afbeeldingen van 480x620 pixels naar 30*310. De gedachte is dat de informatie vooral in de tijdlijn zit en minder in de echte hoogte van de signaalpieken.  Ook moeten het nog zwart/wit beelden worden.  
Zo komen dus uiteindelijk de 10 minuten EEG recordings eruit te zien (zonder de balk uiteraard):


Dat moet resultaat geven! Het 20K prijzengeld is al bijna van mij :-)


Omdat er 16 elektroden in he hoofd van die arme patienten geprikt zitten (ze noemen het iEEG: intracranial EEG (iEEG) which involves electrodes positioned on the surface of the cerebral cortex !!!)
wil ik de resultaten samenvoegen in een 'tensor' met een dimensie dus van (x, 16, 30, 310) die als output dus een 0 of een 1 moeten genereren. Met onderstaande code lukt dit uiteindelijk. Per datagroep (3 patienten / train en test) heeft dit wel een lange doorlooptijd. Nu ja. Putertje werkt ook graag 's nachts. :-)


'''
    execfile('/Users/DWW/Documents/_EEG/data.py')
'''
import os
import numpy as np
from scipy.io import loadmat
import matplotlib.pyplot as plt
import cv2

#data_path = '/Users/DWW/EEG recordings/'
data_path = '/Volumes/Slot 4 2tB/EEG recordings/'
work_path = '/Users/DWW/EEG recordings/'
image_rows = 480
image_cols = 620

def create_data(subdir, train=True):
    train_data_path = os.path.join(data_path, subdir)
    imagelist = os.listdir(train_data_path)
    total = 16 * len(imagelist) # 16 electrodes
    
    t = 0
    print('-'*30)
    print('Creating test images...')
    print('-'*30)
    images, imageset, outcome = [],[], []
    for image_name in imagelist: #[0:3]:
        if image_name[-4:] == '.mat':
            try:
                single_matdata = loadmat(os.path.join(train_data_path, image_name))
            except ValueError:
                print "File ", image_name, " is corrupted"
            else:
                data = single_matdata['dataStruct']['data'][0][0]
                imageset = []
                for i in range(data.shape[1]):
                    x = np.where(np.abs(data[:,0])>100)
                    y = data[x,i]
                    plt.plot(x[0],y[0])
                    plt.axis('off')
                    plt.set_cmap('hot')
                    ax = plt.gca()
                    ax.set_axis_off()
                    ax.autoscale(False)
                    extent = ax.get_window_extent().transformed(plt.gcf().dpi_scale_trans.inverted())
                    plt.savefig('figuur', bbox_inches=extent)
                    plt.clf()
                    img = cv2.imread('figuur.png', cv2.IMREAD_GRAYSCALE)
                    #img = cv2.resize(img,(155,120), interpolation = cv2.INTER_AREA)
                    img = cv2.resize(img,(310,30), interpolation = cv2.INTER_AREA)
                    imageset.append(img)
                    #print np.asarray(imageset).shape
                    if t % 100 == 0:
                        print('Done: {0}/{1} images'.format(t, total))
                    t += 1
                '''
                cv2.imshow('figuur',img)
                cv2.waitKey(0)
                cv2.destroyAllWindows()
                '''
            images.append(imageset)
            if train: # Traindata - not test
                outcome.append(image_name[-5])
            #print np.asarray(images).shape, image_name, image_name[-5]

    print('Loading done.')
    return images, outcome

if __name__ == '__main__':
    sources = ['train_1']#, 'test_1', 'train_2', 'test_2', 'train_3', 'test_3']
    for subdir in sources:
        if subdir[0:4] == 'trai':
            output = True
        else:
            output = False
        print 'Processing ' + subdir + ' ...'
        images, outcome = create_data(subdir, output)
        np.save(work_path + 'images_' + subdir + '.npy', images)
        np.save(work_path + 'outcome_' + subdir + '.npy', outcome)
        print('Saving to .npy files done for ' + subdir + '.')

        '''
        print(')Showing example ... images[1][1] ...')
        cv2.imshow('figuur',images[1][1])
        cv2.waitKey(0)
        cv2.destroyAllWindows()
        ''' 

02 - EEGs - Eerste analyse

Dus 240.000 * 16 * 2.196 = 8.432.640.000 getallen voor alleen al patient 2 om het beoogde neurale netwerk iets zinnigs te leren. Dat lijkt wel heel erg veel.

De eerste uitdaging is dat de bestanden 'Matlab-files' zijn. Matlab is een commercieel AI product met een eigen bestandsindeling. Gelukkig vind ik al snel een alternatief : scipy.io De wetenschappelijke python uitbreiding waarmee het bestandstype uit elkaar te rafelen is. Ik kan er nu weer 'vertrouwde' numpy arrays van maken.

Ik besluit echter dat het eerst wel interessant is om eens te lijken of ik zo'n EEG kan plotten:

Een uur voor aanval

   Dit is er een uit "EEG recordings/train_1/1_2_1.mat" waarbij de laatste '1' voor de .mat aangeeft dat het er een is in een uur voor een epilepsie aanval. Deze is bijvoorbeeld uit een tussenperiode ('EEG recordings/train_1/1_1_0.mat':
Tussenmeting

Duidelijk toch? :-)
Ik besluit dat we in ieder geval minder data moeten hebben. Stel dat ik alleen de punten groter dan 100 of kleiner dan -100 pak? Ik wel dan wel de afstand tussen de punten bewaren en daarvoor moet ik ook de 'coördinaat' (zeg maar het volgnummer van de meting) meegegeven. Dat lukt met de volgende code: (E = de numpy file van 16 * 240.000 metingen)

x = np.where(np.abs(E[0])>100)

y = E[0,x]
plt.plot(x[0],y[0])
plt.show()

Dat lijkt al heel wat interessantere resultaten te geven:


Een uur voor aanval

Tussenmeting

Ik heb er maar een paar bekeken maar dit lijkt wel onderscheidend!  En de data is drastisch verminderd! Zou ik dat aan een NN kunnen aanbieden? Wellicht zelfs de grafieken zelf? Ik besluit het te proberen. Na heel veel experimenteren met code lukt het om de grafiek zonder schalen en randen om te vormen. Het lukt mij alleen (nog?) niet om de grafiek rechtsreeks aan een numpy array toe te voegen. Wel door het eerst als afbeelding op te slaan. Nu ja, dat kost waarschijnlijk wel veel meer tijd maar goed. Voorlopig maar zo dan.


01 - EEGs - Aftrap

Weer een nieuwe medische Kaggle competitie! Deze keer over EEGs en het voorspellen van Epilepsie. Is er een machine learning methode te vinden om betrouwbaar Epilepsie te voorspellen?


De data bestaat uit de EEG opnames (16 elektroden) van 3 patienten gedurende een langere periode. Ze zijn ingedeeld in opnames meer dan 4 uur verwijderd van een epileptische aanval en opnames 1 uur voor een aanval. De simpele opdracht is om hiertussen onderscheid te maken.  

De eerste uitdaging is al de hoeveelheid data. Traindata is ingepakt ongeveer 29gB en de testdata ongeveer evenveel. Het zijn allemaal 'pakketjes' van 10 minuten EEG opnames van 16 elektroden met een samplerate van 400 keer per seconde. Dat levert dus 240.000 floating point getallen (400*10*60) en dat maal 16 elektrodes! Per enkele 10 min dataset!  Geen wonder dat het zulke enorme bestanden zijn geworden. Even een indruk: Patient 2 heeft 2.196 van zulke 'train-bestandjes' en 2.256 test-bestanden. Oke, daar moeten we dus iets zinnigs van maken?!