donderdag 31 december 2015

MRI - 08 - Onderzoek SliceLocation

Na een kleine worsteling om de juiste sortering terug te krijgen uit een list met alle studies, sax-lagen en bestandsnamen (Op zich eenvoudig - numeriek sorteren op 2 kolommen) lukt het om inzicht te krijgen in achtereenvolgende SliceLocations. En dat blijkt zeer noodzakelijk! De meestgebruikte afstand is 10 mm. Soms, om onduidelijke reden, afgewisseld met 8 mm. De leeftijd lijkt daar in ieder geval niet altijd een indicator voor. De positie hoogt soms op en soms neer. De 'hartrichting' lijkt gelukkig in dezelfde richting. Dat wil zeggen dat er steeds van het brede gedeelte naar 'de punt' lijkt te worden gescand. (Ik weet nog niet of dat relevant gaat zijn) Zie hier een voorbeeld stukje van de output: (kolommen: study, sax slice, file, location, distance, age)

51 sax_14 /Users/DWW/data/train/51/study/sax_14/IM-3536-0001.dcm 95.953565415562 10.0 055Y
51 sax_15 /Users/DWW/data/train/51/study/sax_15/IM-3537-0001.dcm 105.95356694642 10.0 055Y
51 sax_16 /Users/DWW/data/train/51/study/sax_16/IM-3538-0001.dcm 115.95356847727 10.0 055Y
51 sax_17 /Users/DWW/data/train/51/study/sax_17/IM-3539-0001.dcm 125.95356469696 10.0 055Y
51 sax_19 /Users/DWW/data/train/51/study/sax_19/IM-3540-0001.dcm 135.95362064292 10.0 055Y
52 sax_12 /Users/DWW/data/train/52/study/sax_12/IM-0128-0001.dcm 4.5424673200997 0.0 014Y
52 sax_13 /Users/DWW/data/train/52/study/sax_13/IM-0129-0001.dcm -5.4575311672849 -10.0 014Y
52 sax_14 /Users/DWW/data/train/52/study/sax_14/IM-0130-0001.dcm -15.457530568612 -10.0 014Y
52 sax_15 /Users/DWW/data/train/52/study/sax_15/IM-0131-0001.dcm -25.457532441208 -10.0 014Y
52 sax_16 /Users/DWW/data/train/52/study/sax_16/IM-0132-0001.dcm -35.45753052627 -10.0 014Y

52 sax_17 /Users/DWW/data/train/52/study/sax_17/IM-0133-0001.dcm -45.457531243862 -10.0 014Y

De meeste lijken verder consistent maar er zitten ook dit soort bij:

49 sax_5 /Users/DWW/data/train/49/study/sax_5/IM-6818-0001.dcm -37.262080772284 0.0 040Y
49 sax_6 /Users/DWW/data/train/49/study/sax_6/IM-6819-0001.dcm -47.262081561521 -10.0 040Y
49 sax_7 /Users/DWW/data/train/49/study/sax_7/IM-6820-0001.dcm -57.262082906974 -10.0 040Y
49 sax_8 /Users/DWW/data/train/49/study/sax_8/IM-6821-0001.dcm -67.262083002317 -10.0 040Y
49 sax_9 /Users/DWW/data/train/49/study/sax_9/IM-6822-0001.dcm -77.262088061653 -10.0 040Y
49 sax_10 /Users/DWW/data/train/49/study/sax_10/IM-6823-0001.dcm -87.262089407106 -10.0 040Y
49 sax_11 /Users/DWW/data/train/49/study/sax_11/IM-6824-0001.dcm -97.262090752559 -10.0 040Y
49 sax_12 /Users/DWW/data/train/49/study/sax_12/IM-6825-0001.dcm -87.262089407106 10.0 040Y
49 sax_13 /Users/DWW/data/train/49/study/sax_13/IM-6826-0001.dcm -107.26209099658 -20.0 040Y
49 sax_14 /Users/DWW/data/train/49/study/sax_14/IM-6827-0001.dcm -117.26209094324 -10.0 040Y
49 sax_15 /Users/DWW/data/train/49/study/sax_15/IM-6828-0001.dcm -127.26209368749 -10.0 040Y
49 sax_16 /Users/DWW/data/train/49/study/sax_16/IM-6829-0001.dcm -137.26209363415 -10.0 040Y

49 sax_20 /Users/DWW/data/train/49/study/sax_20/IM-6832-0001.dcm -97.261893063264 40.0 040Y

Hier doet slice sax_12 nog eens sax_10 over om daarna weer 'netjes' verder te gaan tot op het eind waar sax_20 nog even sax_11 ook herhaald! In het bij de wedstrijd behorende blog wordt aangegeven dat dit vaak te maken heeft met 'scan-issues' (beeldverstoringen) en dat er dan vanuit gegaan mag worden dat de 2e scan de beste is. Dat verklaart dus misschien ook (deels) de variatie in lagen. Onderdeel van de voorbereiding zal dus zijn het laten vervallen van de eerste van 'dubbele slices'.

De totale afstand tussen de eerste en de laatste slice varieert van zo'n 64 tot 120 mm. Dat lijkt maar deels met de leeftijd te maken te hebben. Nooit gedacht dat er zoveel verschil kan zijn.



  




woensdag 30 december 2015

MRI - 07 - Plannen maken

Het is een complex project met vele mogelijke insteken. Ik ben veel aan het experimenteren maar wellicht wordt het tijd om eens een plan van aanpak op te zetten. Heb ik in ieder geval iets om naar toe te werken.

Strategie deel 1 - Metadata

Ik wil eigelijk gebruik maken van de Dicom metadata om de oplossing een beetje te sturen. Dat zou een arts denk ik ook doen. Een baby van enkele maanden heeft nou eenmaal een heel ander volumeverwachting dan een man van 40. Er is niet veel metadata beschikbaar. Wel is er leeftijd en geslacht wat al een aardige indicatie zal geven.

Daarnaast kan je wellicht aannemen dat de totale afstand van de 'sax-slices' ook iets zegt over de grootte van het hart en dus het mogelijke volume. Er is ook een 'slice thickness' maar die lijkt weinig relatie met de afstanden te hebben. Wellicht dat de 'patient position' later nog kan helpen bij het bepalen van de locatie van de LV. In ieder geval heb ik ook de 'pixel spacing' nodig om de werkelijke grootte van de afbeelding te kunnen vaststellen.  

Strategie deel 2 - Afbeeldingen

Bij de afbeeldingen is het mij dus gelukt om bij de meeste, per slice, een behoorlijk juiste positie te bepalen van het hart. Dit door te kijken waar de meeste 'beweging' zit. Daarmee kan ik het relevante deel van de slice 'uitsnijden'. Ik corrigeer de uitsnede met behulp van de 'pixel spacing' naar een uitsnede van 15 x 15 cm zodat er eenzelfde schaal gebruikt wordt. Daarna maak ik de afbeeldingen allemaal even groot.   In eerste instantie kies ik 106 x 106 pixels wat het beste lijkt aan te sluiten bij een groot deel van de uitsneden. Maar wellicht volg ik een van de voorbeelden en breng ik alles terug naar 64x64. Dat maakt e.e.a. wellicht beter behapbaar voor de GPU.

Een van de mogelijkheden is om alle 30 afbeeldingen per slice samen te pakken in 1 'Tensor' van 30 x 64 x 64 en deze aan en (convolutional) neuraal netwerk aan te bieden. Dat wordt zo te zien in bovengenoemd voorbeeld gedaan. Het betekent dat er gemiddeld zo'n 10 van deze matrixes worden aangeboden per studie. (variërend van 8 tot 18 slices) . 10 x 30 x 64 x 64 is 1.228.800 input variabelen per study om 2 volumes uit te rekenen. Bij het betreffende voorbeeld worden de tensor-waarden volgens mij 1 voor 1 met het target volume aangeboden. Dus niet voor alle slices tegelijk. Er is dus geen samenhang tussen de slices in 1 studie behalve de systolische en diastolische volume uitkomsten. Voordeel is wel dat alle beschikbare afbeelding - informatie gebruikt lijkt te worden.

Wellicht is het beter om met de afbeeldingen de w.o.w. van de arts te gebruiken. Voor zover ik het begrijp kiest deze eerst de bovenste 'slice' die nog de buitenwand van de LV weergeeft. Tevens kiest hij / zij de slice waar de hartkleppen op te zien zijn. Dan wordt het moment (de afbeelding) gekozen waarop het kleinste volume te zien is (systole) en de tijd ( afbeelding) waarop het grootste volume (diastole) te zien is. Op deze selectie (hoogste / laagste slice - kleinste / grootste volumemoment) worden dan oppervlakte metingen gedaan zodat, vermenigvuldigd met de slice-afstand, er een volume berekening uit komt. Diastole is eenvoudig te vinden. Die zit aan het begin van de 'hartcyclus' en betreft dus volgens mij 'frame1'. Systole zit vaak ergens tussen 1/4 en 1/2 van de hartcyclus (Frame 7 tot frame 15). De rest van de cyclus wordt gebruikt om 'passief' het hart weer vol bloed te laten stromen. Ik denk dat ik het 'systolisch moment' (frame) probeer te vinden door het kleinste verschil tussen 2 beelden te zoeken. Van de 2 neem ik dan de 'laagste gemiddelde waarde' omdat bloed meestal witter (dus hoger) wordt weergegeven.

Voor het leeralgoritme zou ik dus frame 1 van de slices kunnen combineren voor diastool volume en frame X (nog te vinden) voor systolisch. Probleem is dus wel dat er een verschillend aantal slices per study is. Daarnaast is de afstand tussen de slices verschillend. Stel dat we alle slices terugbrengen naar bijvoorbeeld 10 stuks dan geven sommige een 'hoogte maat van bijvoorbeeld 10cm en andere een hoogtemaat van bijvoorbeeld 15 cm'. Wellicht is dit te compenseren door het 'doelvolume' te corrigeren (vermenigvuldigen) met de sliceafstand. De simulatiewaarde moet dan na de training uiteraard weer terug-berekend worden.

Bij het terugbrengen of ophogen van het aantal slices naar , zeg 10, is denk ik lastig. Het mag namelijk geen of nauwelijks invloed hebben op het meetvolume. Ophogen kan, denk ik, eenvoudig door 'zwartlagen' toe te voegen. (Of zou herhaling van de laatste laag minder verstoren?)

Studie 341 heeft bijvoorbeeld 21 sax slices en studie 436 heeft er 22. Dat zijn wel uitzonderingen. De meeste zitten zo rond de 10. Bij nadere bestudering blijkt bij deze 2 de hart MRI 2x te zijn uitgevoerd. Pfff. Ook nog mee rekening houden. Misschien ook met de 'SlicePosition'? Ik besluit deze eerst wat verder te onderzoeken voordat we verder gaan met 'de strategie'.

   

dinsdag 29 december 2015

MRI - 06 - Continuous Ranked Probability Score (CRPS)

Beetje bij beetje begin ik te begrijpen wat er nou gewenst is in de wedstrijd. (Statistiek is wat weggezonken :-)
De CRPS is de uiteindelijke waarde die bepaald hoe goed de voorspelling is van de gehele validatieset.

In de dataset moet er dus per volume eenheid van n= 0 tot maximaal 599 ml, apart voor systotisch en diastolisch  volume, aangeven worden wat de cumulatieve kans (P) is dat het werkelijk volume kleiner of gelijk is aan het de betreffende volume eenheid (n).

De CRPS wordt vervolgens berekend door van deze kansen (P) het getal 1 af te trekken als volume (n) min het werkelijk volume V groter dan 0 is en anders nul ervan af te trekken. De hier genoemde 'Heavyside step function'. Dit verschil wordt gekwadrateerd bij elkaar opgeteld. Dat doen we dus voor alle aangeleverde validatie waarden (200 x systolisch en 200 x diastolisch = 400 (N)) die we ook weer bij elkaar optellen. Daarna delen we het geheel door 600 * N zodat er weer een mooi getal van tussen de 0 en de 1 uitkomt. Hoe lager hoe beter. Momenteel, 29 dec,  staat de beste hiermee op 0.025617.   Wow! Lijkt mij een knappe prestatie. Terwijl ik zelfs nog worstel met het begrijpen van de manier van aanleveren :-) (Ok, wel wat voorbereidend werk gedaan)

      
Grappig is dat er al een heel aardige score werd bereikt door alleen uit te gaan van de verdeling van de aangeleverde test gegeven. Zonder naar de afbeeldingen of meta data te kijken!

Door alle waarden tussen de meetwaarde en 600 ml steeds met 1 op te hogen en vervolgens door het aantal meetwaarden te delen krijg je al een CRPS score van 0.48785
Dit is vaak al veel beter dan de, waarschijnlijk met veel moeite, gevonden waarden van andere deelnemers.
 

vrijdag 25 december 2015

MRI - 05 - Hartvolume ok maar de Continuous Ranked Probability Score (CRPS) ??

Het laten inschatten van de systolische en diastolische  volumes is een ding maar als ik kijk naar het submission voorbeeld moeten er 2 x 600 = 1200 waardes worden opgegeven per study. Hmmm ik zoek even verder en lees de beschrijving

The CRPS is computed as follows:
C=1600Nm=1Nn=0599(P(yn)H(nVm))2,


where P is the predicted distribution, N is the number of rows in the test set (equal to twice the number of cases), V is the actual volume (in mL) and H(x) is the Heaviside step function (\\(H(x) = 1\\) for \\(x \ge 0\\) and zero otherwise).
Ik begrijp er nog niets van. Een grafiek geeft een beetje aan wat de bedoeling is:
P_i means the cumulative probability that the volume is less or equal to i mL.
Ik kan mij voorstellen dat het groene gedeelte de kans op een foute diagnose weergeeft. Links van de oranje lijn een te hoge inschatting van de pompfunctie. En rechts de kans op een betere hartfunctie dan geschat. Hoe ik deze echter moet gaan uitrekenen is mij nog niet duidelijk. Gelukkig zijn er een aantal voorbeeld 'oplossingen' bij de contest gepubliceerd. Misschien dat ik daaruit wat meer af kan leiden.    

donderdag 24 december 2015

MRI - 04 - Hart chirurgie

Met de gevonden positie van het hart moet ik nu de juiste uitsnede kunnen gaan maken zodat zo weinig mogelijk - niet ter zake data - wordt overgehouden. Ik maak een functie waarmee ik uitsneden in millimeters kan maken. Daarvoor maak ik gebruik van de bijgeleverde metadata: 'PixelSpacing'. Het aantal pixels per millimenter. Op zich eenvoudig alleen vragen de randen als altijd wat extra zorg.

def CropIm(InIm, x,y,pix,size): # pix = PixelSpacing, Size = gewenst aantal mm.
    pi = int(size/pix)
    x = x-pi/2
    if x < 0:
         x = 0
    y = y-pi/2
    if y<0:
        y = 0
    h,w = InIm.shape
    if x + pi > h:
        x = h - pi
    if y + pi > w:
        y = w - pi
    return InIm[x:x+pi, y:y+pi] 



Met een size van 150 mm lijken de harten er goed op te komen. Wel moet de grootte nog aan elkaar gelijk gemaakt worden. Dar kan ik in OpenCV vast wel een functie voor vinden.

/validate/509/study/sax_7
validate/526/study/sax_32
validate/546/study/sax_1

MRI - 03 - Aim for the hearth

Per 'slice' zijn de afbeeldingen vrijwel hetzelfde behalve het kloppen van het hart. Daar moet gebruik van te maken zijn om het hart te lokaliseren en daarna 'de rest' weg te snijden. Briljant!! (Later vindt ik een uitleg met een foerier-transformatie die ik (nog) niet begrijp met hetzelfde doel) .

Ik selecteer voor enkele random studies per slice afbeeldingen 1 en 15. Deze trek ik van elkaar af. Er blijkt toch nog heel wat ruis over te blijven. Ik maak een eenvoudige functie om de pixelwaarden eerst naar 'klasses' toe te brengen. Dat moet in ieder geval een deel van de ruis wegnemen.

def reImage(ImIn):
    f = 100.0 # Bepaald afrondings klasses Hoe groter f Hoe minder klasses.

    return np.round(ImIn/f) * f

Dat lijkt al aardig te werken. Ik krijg dit soort afbeeldingen:


Daar moet ik iets mee kunnen! Na wat zoeken vind ik een mogelijkheid om de positie van 'het zwaartepunt' van een afbeelding vast te stellen. Ik teken een cirkel op dat punt met OpenCV:


from scipy import ndimage
x,y = ndimage.measurements.center_of_mass(combi)
cv2.circle(combi, (int(y),int(x)),int(0.3 * w), (255,255,255), 1)

We zitten al heel vaak in de goede richting maar vaak is de overgebleven ruis toch nog sterk genoeg om de cirkel 'uit target' te halen. Dat moet beter kunnen:



Ik maak een functie die de ruis weg haalt. Per fragment van de afbeelding kijk ik of het gemiddelde groter of kleiner is dan het totale gemiddelde. Indien kleiner zet ik de waardes op nul. Voor het gemak doe ik dat ook al even vooraf voor de randen.

def WhitenIm(ImIn):
    hx,wx = ImIn.shape
    #print ImIn[h-1][w-1]
    si = 20
    m = np.average(ImIn)
    ImIn[0:si,0:wx] = 0
    ImIn[hx-si:hx,0:wx] = 0
    ImIn[0:hx,0:si] = 0
    ImIn[0:hx,wx-si:wx] = 0
    
    for wi in range(0,wx-si,si):
        for ho in range(0,hx-si,si):
            if np.average(ImIn[ho:ho+si,wi:wi+si]) < m:
                ImIn[ho:ho+si,wi:wi+si] = 0.

    return ImIn

Het hart wordt nu vrijwel altijd juist gevonden. Nu eens kijken of we de juiste uitsnijdingen kunnen gaan maken.



MRI - 02 - Data schonen

Nadat de probleemstelling ongeveer duidelijk is geworden (behalve nog de vorm van de uiteindelijke 'submission' - kom ik later op terug) een even een strategie bedenken. Waarschijnlijk iets met convolutional neural networks. Het betreft tenslotte afbeeldingen. De bepaling van de beste slices en daarin het juiste systolisch en diastolisch moment zal echter alleen al lastig zijn. Daarna moet de relatie met de volumes nog worden gelegd.


Voordat ik echter daaraan toe ben eerst maar eens zien of ik de data kan 'schonen' en 'normaliseren'. Het eerste gaat vooral om het verwijderen van ruis en overbodige informatie. Het tweede gaat erom dat de afbeeldingen een vergelijkbare intensiteit hebben, dezelfde pixel nauwkeurigheid (8/16 bits) en bijvoorbeeld even groot zijn. Tevens wil ik dat ze eenzelfde schaal gebruiken. Dezelfde hoeveelheid pixels per mm.

Dankzij de module pydicom kan ik de dicom afbeeldingen in Python inlezen. Ook de metadata blijkt geen probleem.

import numpy

import dicom
ds = dicom.read_file('IM-7310-0001.dcm')
di = ds.pixel_array                       # afbeelding in numpy array laden
print ds.PatientAge, ds.Rows, ds.Columns, ds.PixelSpacing[0]

De studies zitten in een diepe directory structuur. Gelukkig heeft de Python 'os-module' daar een charmante oplossing voor. Ik zorg gelijk dat ik 'random' 'sax' (short axis) bestanden kan selecteren:

for root, dirs, files in os.walk(RootDir):
    ra = random.random()
    if ra < .005 and "sax" in root:
        print '******************************'
        print root
        for name in files:
            if ".dcm" in name:

                ds = dicom.read_file(os.path.join(root, name))

Voor weergave gebruik ik de OpenCV module. Ik verwacht meerdere functies uit die module voor beeldmanipulatie nodig te gaan hebben.  Bijvoorbeeld tonen van pixelarray 'combi':

import cv2
cv2.imshow("combi", combi)
cv2.moveWindow("combi", 100, 150)
cv2.waitKey()


cv2.destroyAllWindows()

Sommige afbeeldingen hebben veel last van 'outliers'. Extreme (grijs) waarden die ver af staan van het gemiddelde. Om te normaliseren wil ik alle afbeeldingspixels naar eenzelfde 'range' toebrengen. Sklearn - preprocessing heeft daar standaard routines voor. Ik probeer 'MinMaxScaler'. Ik krijg echter rare 'artefacten' - strepen over mijn afbeeldingen. Ook bij enkele andere 'scalers' van Scikit verschijnen deze.

Rare strepen bij sklearn pre-processing.
Raar! Ook de RobustScaler, die minder 'outlier-gevoelig is' geeft hetzelfde.  Ik besluit zelf maar een eenvoudige scaler te bouwen. Met het verwijderen van de outliers. De afbeeldingen beginnen nu een beetje met gelijke donkerte op het scherm te komen.:


def NormIm(InIm):              # Verwijder outliers en normalize naar waarde.
    Perc = 95                  # alles boven Perc% wordt op maximum gezet
    Max = 500.
    Percentiel = np.percentile(InIm, Perc)
    h=np.clip(InIm, 0, Percentiel)
    return h * Max / h.max()   # breng alles naar schaal 'Max'  

Een belangrijk deel van het 'data schonen' is ook zoveel mogelijk van de afbeelding wegsnijden dat niet met het hart te maken heeft. Ik ga opzoek naar een manier om dat automatisch te selecteren.


  

MRI - 01 - Heavy duty

Nu het zwaardere werk! Ben ik daar al klaar voor? Nou ... nee. Maar een van mijn voornemens toen ik met DL begon was om te kijken of cardio echo-beelden automatisch zouden kunnen worden gediagnosticeerd. Die heb ik helaas (nog?) niet beschikbaar. Kaggle heeft echter een nieuwe 'contest' waar met MRI scans van harten moet worden gewerkt. "Second Annual Data Science Bowl - Transforming How We Diagnose Heart Disease".

Dat lijkt er natuurlijk wel een beetje op. Complexe 2d beelden van het hart. Op basis hiervan moet de EF (ejection fraction) worden gemeten. Dat is een maat voor de hoeveelheid bloed die uit het linker ventrikel de aorta instroomt. Feitelijk blijkt er een schatting te moeten worden gemaakt van het systolische volume (het kleinste) en het diastolische volume (het grootste) van de linker hartkamer. Het verschil gedeeld door de diastolische waarde geeft dan de EF.

100VDVSVD.


Uitdaging!!!

Er zijn 500 training studies gegeven en 200 test studies. Bij de teststudies staan geen uitkomsten. De ingeschatte waarden moeten berekend worden en vervolgens aan Kaggle aangeboden om een positie in 'het klassement' te kunnen verkrijgen. De waarden bij de 500 training studies uiteraard wel:

Id,Systole,Diastole
1,108.3,246.7
2,54.6,137.2
3,32.7,99.3
4,57.7,154.5
5,83.3,235.5
...

Het gaat om veel data. Zo'n 30gB alleen al voor de training. Elke studie bestaat uit meerdere, vaak 15 of meer, 'slices' oftewel doorsnedes van het hart waar meestal 30 plaatjes per slice zijn gemaakt. Steeds, zo te zien, gedurende 1 hartslag. De meeste slices snijden het hart 'dwars op de kamer' door zodat er als het ware 'van boven' in de kamers wordt gekeken. (sax- s ... axis) Vaak is er ook een 2ch-slice waarin de 2 kamers 'van de zijdoorsnede' te zien zijn en een 4ch-slice, waarin de 2 kamers en de 2 boezems zich tonen. De afbeeldingen zijn in het, blijkbaar medisch veelgebruikte dicom-formaat. (Digital Imaging and Communications in Medicine). Adobe Photoshop blijkt het te kunnen lezen en laat tussendoor (maar met een iets te klein venster) ook zien dat er veel metadata aan toegevoegd is. Om er iets meer gevoel voor te krijgen download ik ook OsiriX lite (geïnspireerd op AsteriX? :-) Een viewer waarmee dit soort beelden mooi kunnen worden 'afgespeeld'.




      
 


 

woensdag 2 december 2015

LSTMs - met James Potter - 2

Ja, de nieuwe 'auto-boek-generator' lijkt nu toch al aardig te werken. Het roept in ieder geval mysterie op. Ik heb de originele 'letter-generator' omgebouwd naar een 'woorden-generator'. Zoals eerder aangegeven bestaat het boek uit 13.075 verschillende woorden. Als ik daar de meestgebruikte 5000 uit selecteer krijg ik ongeveer de woorden die meer dan 2x in het boek gebruikt worden. Een van de 'truukjes' daarvoor is 'collections':

import collections
counter = collections.Counter(text)
words = []
for word, number in counter.most_common(5000):

    words += [word]

Daarmee heb ik dus in 'words' de 5000 meestgebruikte woorden. Met de volgende eenvoudige regel verwijder ik alle woorden uit de tekst (text) die niet in deze 5000 meestgebruikte woordenlijst staan:


text = [x for x in text if x in words]

Wat is Python toch makkelijk voor dit soort dingen! Voor de rest zitten er in het oorspronkelijke programma nog wat 'string opdrachten' die aangepast moeten worden daar ik nu met 'lists' werk in plaats van losse karakters. Bijvoorbeeld:

['', '_', 'james', 'potter', 'en', 'de', 'vloek', 'van', 'de', 'poortwachter', '_', '', '.', 'norman', 'lippert', '_', 'gebaseerd', 'op', 'de', 'en']



Na wat debugging werkt het zonder klagen en krijg ik ondermeer deze teksten terug:

Iteration 41
Epoch 1/1
78474/78474 [==============================] - 105s - loss: 2.9526     

----- diversity: 0.2
----- Generating with seed: "['.', 'dan', 'kom', 'hij', 'eenvoudig', 'de', 'spiegel', 'aan', 'deze', 'kant']"
. dan kom hij eenvoudig de spiegel aan deze kant idee . hij had het zelf zijn , maar hij wist het niet precies . hij had hij geen kans zien . hij had het geweest om het soort van de wereld te laten .  _ het is een soort ,  zei james .  het moet een manier van de rol , dus iedereen ,  zei ralph .  het is een reden , en ze van een . maar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
----- diversity: 0.5
----- Generating with seed: "['.', 'dan', 'kom', 'hij', 'eenvoudig', 'de', 'spiegel', 'aan', 'deze', 'kant']"

. dan kom hij eenvoudig de spiegel aan deze kant zijn ,  zei ze een hand op haar stoel .  je weet het niet , maar we het niet . maar je moet niet dat je wilt wil . laten je dat je kunt niet helpen _ _ _ james haalde haar hoofd .  maar we hebben op het echt .  _ de waarheid van de spitsroede is het geen probleem van mijn . we we zijn . misschien misschien denk je dat hij in de hand wist dat je geen zwadderaar kon .  _ _ je moet hem van mijn voorhoofd dat ik niet te houden .  _ james keek naar james ,  vroeg ?  dit zal wel niet komen . we hebben niet te helpen .  _ dat we het niet ,  zei roos .  maar je kunt geen manier moeten hoor dat je niet direct .  _ dat ga het niet gaan ,  zei james .  als je het niet wil ga waarom .  _ de groep van de groep zag james de stopten naar de compartiment en dwingend in het veld . _ het is er niet te herinneren ,  zei ralph .  maar als u niet vertrouwen .  _ _ albus scheen te gaan van de deur ,  zei roos met een stem stem .  ik wil niet dat we wilt gaan als het niet echt .  _ james ,  zei roos ,  de hele steen van de problemen waarop van de baken doorgang stond in de ruimte ?  vroeg james . _ _ ik bedoel dat het niet ,  vroeg albus , maar het was op een lange enorm . het was bijna niets op beide kanten . james , en en leek harry te gaan wat het al zou geleerd . maar als je het soort doen . . ik heb het niet veel best in de rol van as . ze wil er mij van .  _ de vond dat we ook om meer meer meer te helpen tot zijn potter . hij was met voldemort om die een paar van te gaan als hij meer een andere gehouden zou aan het gedragen . het was , en nu , hij zelfs niet dat petra er niet aan de tijd en zou het daarbij in de

Toch al bijna een leesbaar verhaal :-). Veel taalstructuur begint herkenbaar te worden. Maar wat (voor mij) belangrijker is is dat het algoritme weinig moeite lijkt te hebben met de input en output van 5000 verschillende woorden. De iteraties gaan ook behoorlijk wat sneller dan bij de karakter-generator. Zou het uitmaken als ik alle 13.075 woorden zou gebruiken? 

Dat blijkt ook probleemloos te gaan. Fantastisch dat het geen probleem blijkt om een matrix van 13.075 woorden x 10 zinlengte x 78.474 zinsconstructies (sequences)  aan het LSTM aan te bieden. De zinnen worden dan vollediger, meer samenhangend. Toch nog maar even een voorbeeld:

Iteration 59
Epoch 1/1
81730/81730 [==============================] - 202s - loss: 0.8225     

----- diversity: 0.2
----- Generating with seed: "['zijn', 'zesde', 'jaars', ',', '', 'wees', 'gerard', 'terecht', '.', '']"

zijn zesde jaars ,  wees gerard terecht .  _ ik leek hem van de wereld van de ratten ,  zei hij , zijn toverstok op de ruimte halend en keek langs van de richting met james s ogen .  hij is niet niet wat we hem .  _ james had zijn ogen tussen de kamer en keek over hem schouder . hij keek op op en keek naar zijn hand , en keek draaide op . _ james keek alleen op naar de kamer , en donkerder , en haalde james .  _ wat weet je james ,  zei james met een beetje en  het is niet niets om te maken . ik zie het er niet uit ,  zei james , een zitten gezicht door de ruimte en en en rood vallend op het comite . het is niet van het soort voorbij .  _ ralph knikte .  ik heb je niet maar maar hij zag er niet niet in de andere persoon van de zwadderich ,  zei hij op ,  _ we gebruikte hij hem ,  zei ralph , springend op de kloof , en james stem om . de kleine knipperde , en wees langzaam . james keek door naar de richting en ging op het bureau . hij keek nog niet niet aan de vloer . zijn gezicht was hij uit zijn voorhoofd , ralph in de bibliotheek en liet hem op james mompelend naast de vlaggen . toen toen ze toen ze stopte keek , en zag de schaduw iets . _ 47 begon naar het te denken en harry op een weg schaduw open .  ik heb niet niet dat niet worden .  _ ze is dat ik wil nodig op de overige als een soort heks - beetje - - heer - mijn productie eruit van een ruimte .  _ geloof _ het was iets !  riep james , en zijn gezicht was zijn toverstok en stond . hij sprong door james , en de leerlingen vleugel gleed , de grond van zijn hoofd achter wierp en sprong . _ james _ james keek op , maar ze was een stukje grotere liet dan ze half geven - gezicht .  ik heb ze niet op je !  _ ze is geen nteresseerd ,  fluisterde govert .  ik ben alleen was ,







zondag 29 november 2015

LSTMs - met James Potter

Ok, wie is James Potter en wat heeft hij met LSTMs te maken? Wel, het is natuurlijk leuk om automatisch teksten van Nietzsche te generen maar om een beetje te begrijpen hoe 'goed' die het doet is Nietzsche misschien toch wat te ingewikkeld. Maar eens even gezocht naar een beschikbare Nederlandse tekst: "James Potter en de vloek van de poortwachter". Een boek van G. Norman Lippert dat gebaseerd is op de karakters en werelden van J.K. Rowling. Je weet wel, die van Harry Potter.

Het blijkt inderdaad niet moeilijk om deze tekst aan het LSTM te voeden. Wel eerst even naar 'plain text' omzetten om de opmaak en de plaatjes kwijt te raken en dan laden maar.

De tekst bestaat uit 1.180.397 karakters (65 verschillende) en levert 'dus' 393.459 sequences op.

Het duurt toch lang voordat de 60 iteraties zijn uitgevoerd. Per iteratie ongeveer 9 minuten. Dus even tijd om na te denken en de Keras documentatie wat verder door te lezen.

Wat mij nog ontgaat is hoe de volgordelijkheid in dit algoritme wordt meegenomen. Natuurlijk, de letters volgen elkaar op maar de bieden we in feite in 20 letters per keer aan en de 21e moet 'hij' dan voorspellen. Dat er een volgorde in die 20 letters ziet het netwerk feitelijk niet. De 19e letter is even belangrijk als de 20e en dus even belangrijk als bijvoorbeeld de 1e. Dat lijkt toch 'tegen intuïtief' ?! De letters dichtbij de te voorspellen letter zouden toch 'belangrijker' moeten zijn?

De vraag is dan ook of een 'gewoon' neuraal netwerk dit zo niet even goed zou kunnen doen. Er worden dan ook outputs berekend op basis van 'een reeks' inputs. (features genoemd) Zoek de verschillen?! Maar wellicht zie ik een stukje logica over het hoofd.

Wat leuker is om te bedenken is of er, in plaats van letter generatie, ook met woorden generatie gewerkt kan worden. Zou dat geen nog effectievere teksten opleveren? De uitdaging is dan wel dat er zowel aan input zijde als aan output zijde veel grotere vectoren ontstaan. Even groot als het aantal gebruikte woorden in de betreffende tekst. Maar misschien kunnen we (en moeten we) het formaat limiteren tot bijvoorbeeld de 1000 meestgebruikte woorden. Eens kijken of we dit voor elkaar kunnen krijgen.

De eerste resultaten geven 241.236 woorden. Er zijn 14.069 unieke woorden! Toch wel veel. Zou dat lukken?


Het eerste programma heeft ondertussen doorgewerkt op de LSTM met karakters. Bij iteratie 24 stop het programma met een of andere memory error. Mogelijk omdat het 2e programma (LSTM met woorden) ook al de nodige capaciteit heeft gevraagd. 

"Error allocating 5200 bytes of device memory (unknown error). Driver report 4003065856 bytes free and 4294770688 bytes total "

Gelukkig geeft hij tussendoor resultaten. Het begint toch al ergens op te lijken. Straks een nieuw boek uitgeven? :-)

--------------------------------------------------
Iteration 23
Epoch 1/1
393459/393459 [==============================] - 464s - loss: 0.9507     

----- diversity: 0.2
----- Generating with seed: "ereen die had meeged"
ereen die had meegedaan? zelfs jullie zijn dat je me herinneren dat hij het ook zij, alsof hij had gezien in een van de standbeeld van de deur en stapte op de trein.
‘wat?’ zei james zijn hand op. ‘ik heb nog niet kon zien. hij keek naar james op het totel behoorlijk gegeven door een stekelijke verdering om te bezichten te zijn om hem te verschenen tot hij de deur van de deur van de stok van de deur van de deur
----- diversity: 0.5
----- Generating with seed: "ereen die had meeged"
ereen die had meegede leeg. hij perste zijn stem op zijn hoofd naar haar. en ze had de andere kant van de aansluiterde kon de hand tegen de vloek van de poortwachter tegen de donkere voeten onder de vloer van de droom van een verhaal zien. ze heeft zijn hand en stond omhoog de deur van de voorstelling van de hartstelling van zweinstein, als de leste gesprek hem open en james had geluk in zijn hand en zag hem en begon
----- diversity: 1.0
----- Generating with seed: "ereen die had meeged"
ereen die had meegede leerden. het p?dat west. ik heb geen zie aan het pad om deze langei? van vervangen spiegel door de kluis niet ermie magie,’ zei het soort van zweinstein.’
‘laten we komen ze genoeg was. james besloot om hem te zueren. ik had gezicht van ons laat.een. hoe was het??2 het lang ik doe, natuurlijk niet langen waren. ‘en we samen een dolk was ver wacht toen ik hij niet verdien tussen haar blik
----- diversity: 1.2
----- Generating with seed: "ereen die had meeged"
ereen die had meegedaan?.’
een streep vloer langs randpen rusfening.
‘ankazekmezing was om te leggen, ol ze voor iets wat treis hij invererde dat je stopte hhabden om te gereiste ;ogevong. en bereikte, een laatste belondig liet zalazar rechtschieven we op ginat de trein als1. zalazar?’verdeerde de stem telug. ze zei dat ze erg nooi mee als daarom.’
765

harry nam laatheer. het valt morgen gegaan dat. hojjs roo