zaterdag 24 oktober 2015

Foute bussen - Deel tien - Specilalisten


De nauwkeurigheid lijkt nog steeds wat beperkt. Weliswaar is er heel wat bereikt t.o.v. de eerste resultaten maar er moet meer te halen zijn. Eens kijken als we 'specialisten' inroepen. Ik maak een NN voor de lengtegraad en een voor de breedtegraad. Het is niet moeilijk het programma daarop aan te passen maar elke slordigheid wreekt zich uiteraard. Zo is er ineens sprake van onsamenhangende coordinaten op de plattegrondjes. Oh ja. De 'shuffle' moet natuurlijk voor de breedte en de lengte gelijkvormig worden uitgevoerd. Na wat verder zoeken en experimenteren lukt het om e.e.a. weer goed aan het werk te krijgen. Het resultaat valt tegen. Of moet ik juist mee zeggen? In dit geval lijken specialisten weinig toe te voegen. Bij vergelijkbare parameters krijg je ongeveer dezelfde uitkomst. Wel duurt het 2x zo lang omdat er nu 2 netwerken getraind moeten worden.

Ik sleutel nog wat aan de parameters en dat levert wel wat beter resultaat:

count  2010.000000
mean      0.259858
std       0.200074
min       0.005205
25%       0.113360
50%       0.213954
75%       0.355295

max       1.706290

Al van 34% naar 26% door het aanpassen van de 'learning_rate' naar 0.000004 in plaats van 0.000001.
Maar ik zoek nog wat fundamentelers. Ik pas de optimalisatie methode aan van 'nesterov_momentum' naar rmsprop,  waar ik eerder bij de mnist karakterset gebruik van maakte. Het resultaat is veeel slechter. Hmmm. Misschien nog eens een nachtje over slapen.
Maar deze zien er in ieder geval heel goed uit:




 

woensdag 21 oktober 2015

Foute bussen - deel negen - Toch nog beter

Hoewel ik vorige keer eigelijk een punt achter de 'foute bussen' had gezet en inmiddels bezig ben met wat complexere medische data kon ik het niet laten toch nog verbeteringen in te voeren. De gemiddelde afwijking van een halve kilometer was een grote verbetering maar, gezien ook de relatieve eenvoud van route 22 zou het veel beter moeten kunnen.
Eerst maar eens nieuwe data laden inclusief een datum. Dat voorkomt dat tijden van verschillende dagen door elkaar gehusseld worden. Een fout die ik al gemeld had maar maar beperkt achteraf heb kunnen corrigeren.
Een datum-tijdveld wordt nu zo opgeslagen:


"20151021 12:05 AM"

Ook nog eens goed naar AM en PM tijden gekeken. Eenvoudig 12 uur optellen bij PM tijden blijkt niet helemaal goed. Tussen 00:00 en 00:59 (AM) wordt de tijd weergegeven als 12:XX Dus bijvoorbeeld 12:35 AM. Hetzelfde gebeurd tussen 12:00 en 12:49 hetgeen met 12 erbij bijvoorbeeld tot 24:45 PM kan leiden. Ook dus maar gecorrigeerd.  

Na enkele dagen de webservice elke 2 minuten leegzuigen kunnen we de boel weer laten draaien. Een beetje tweaken aan de parameters geeft nu een volgend resultaat:

count  1906.000000
mean      0.329606
std       0.206381
min       0.006579
25%       0.188810
50%       0.288062
75%       0.419695
max       2.791522

Een gemiddelde van 0.33 kilometer. Weer een stuk beter!  Toch nog eens denken of het nog beter kan. Op het oog moet het nog juister voorspellen van de route toch eenvoudiger zijn. Het lineair doortrekken van de betreffende coordinaten:


Misschien kan een re-current netwerk beter werken. Of wellicht 'specialisten netwerken' (Een voor de lengte- en een voor de breedtegraad?)

zaterdag 17 oktober 2015

Foute bussen - deel acht - Dataschoning

Bij het schonen van de data kom ik er eerst achter dat mijn tijdberekening niet goed werkt. Uiteraard mag je tijden niet altijd (11:06 - 10:58 ?!) gewoon van elkaar aftrekken om het verschil in minuten te krijgen. Wie heeft die 60 minuten per uur verzonnen? :-) Ok, kost even tijd om dat goed te fixen. Vervolgens kijken naar de max 100 km/uur om foute input te verwijderen. Deze fouten zijn vermoedelijk ontstaan omdat ik bij het opslaan van de data niet de datum heb meegenomen. Posities uit verschillende dagen kunnen dus bij elkaar zijn komen te staan. Lastig, maar een lering. Ik kan dus in ieder geval meten of de opeenvolgende punten niet meer dan 3,3 km uit elkaar liggen ervan uitgaande dat de chauffeurs 'normaal' deze snelheid niet halen met hun bussen.

Het levert prachtige resultaten op. Enerzijds juicht mijn statistiek:

count  1087.000000
mean      0.481143
std       0.286745
min       0.017168
25%       0.275199
50%       0.448470
75%       0.623757
max       2.952549  

Minder dan 0.5 km gemiddelde voorspellingsfout! Anderzijds blijken de 'anomalieën' beperkt. (maar 4 van de 1087 > 2.0 km afwijking) en lijken dat ook echt nog overgebleven fouten. Bijvoorbeeld:

Distance =  2.95254907837
Misschien is hier de buschauffeur razendsnel omgedraaid en met zo'n 87 (30 * 2.95...) km/uur naar huis geraced. Tja, het kan natuurlijk.

Maar ik krijg ook dit soort:

Distance =  2.08255048828

Een hele rare route die vermoedelijk dus met het eerdere probleem te maken heeft.

Wel, ik ben feitelijk waar ik wezen wilde. Het lijkt erop dat ik afwijkend gedrag van de bussen nu aardig kan waarnemen. Misschien nog een keertje het neuraal netwerk rechtstreeks koppelen aan de webservice. Of nu overstappen naar iets anders. We zien wel.

De code:

#execfile('/Users/DWW/Documents/Follow1bus outlier neural.py')
import webbrowser
import time
import numpy as np
from sklearn.externals import joblib
from sklearn import svm
import pandas as pd

Data = joblib.load('/Users/DWW/Documents/Bus_outlier.pkl')
Data = np.array(sorted(Data, key = lambda x: (x[0], x[1])))
busses = sorted(list(set([Colm[0] for Colm in Data])))

def dist(est,real): # Bereken afstand tussen werkelijke en voorspelde positie. Ook procentueel.
    if real == 0:
        perc = 0
    else:
        perc = np.absolute(100* (real-est)/real)
    dis = np.absolute(real-est) * 1.852 * 60
    return est, real, perc, dis

def dista(l1, b1, l2, b2):
    return (np.absolute(float(l1)-float(l2)) + np.absolute(float(b1)-float(b2))) * 1.852 * 60


def timdif(int_n, int_0): # Maak numerieke tijd op basis van bijv. '07:34 PM' en bereken het verschil in tijd in minuten.
    if len(int_n) == 7:
        int_n = '0' + int_n
    if len(int_0) == 7:
        int_0 = '0' + int_0
    if int_n[6:8] == 'PM':     # bereken uren
        u_n = 12 + int(int_n[0:2])
    else:
        u_n = int(int_n[0:2])
    if int_0[6:8] == 'PM':
        u_0 = 12 + int(int_0[0:2])
    else:
        u_0 = int(int_0[0:2])
    if u_n == u_0:    # kijk of uren gelijk zijn of 1 verschillen
        dif = int(int_n[3:5]) - int(int_0[3:5])
    else:
        if u_n == u_0 + 1:
            dif = int(int_n[3:5]) + (60-int(int_0[3:5]))
        #print int(int_n[3:5]) , (60-int(int_0[3:5]))
        else:
            dif = (u_n-u_0) * 60 # niet gelemaal goed maar wel onderscheidend
    return dif

# Korrecties op gps posities:
def cl(lon):
    return 100 * (float(lon) - 41.)

def rl(lon):
    return float(lon)/100 + 41.

def cb(lat):
    return 100 * (float(lat) + 87.)

def rb(lat):
    return float(lat)/100 - 87.

# Opbouwen positie bestand (4 eerdere waarnemingen en de 5e werkelijke 'doelpositie'.  
X = []
Y = []
t = 0
for i in busses:
    HData = Data[Data[:,0]==i]
    if len(HData) > 5: # Alleen voor bussen waar meer dan 5 waarnemingen van zijn.
        for j in range(len(HData)-4):
            if (timdif(HData[j+4][1],HData[j][1]) < 11 and
                dista(HData[j+0][2],HData[j+0][3],HData[j+1][2],HData[j+1][3])< 3.3 and
                dista(HData[j+1][2],HData[j+1][3],HData[j+2][2],HData[j+2][3])< 3.3 and
                dista(HData[j+2][2],HData[j+2][3],HData[j+3][2],HData[j+3][3])< 3.3 and
                dista(HData[j+3][2],HData[j+3][3],HData[j+4][2],HData[j+4][3])< 3.3): # Alleen de waarnemeningen die inderdaad in tijd bij elkaar liggen. Feitelijk maximaal 8 minuten : 11 genomen voor marge. En max 100 km / uur = 3.3 per 2 min uit elkaar.
                if X == []:
                    X = [cl(HData[j][2]), cl(HData[j+1][2]), cl(HData[j+2][2]), cl(HData[j+3][2]), cb(HData[j][3]), cb(HData[j+1][3]), cb(HData[j+2][3]), cb(HData[j+3][3])]
                    Y = [cl(HData[j+4][2]),cb(HData[j+4][3])]
                else:
                    X = np.vstack((X,[cl(HData[j][2]), cl(HData[j+1][2]), cl(HData[j+2][2]), cl(HData[j+3][2]), cb(HData[j][3]), cb(HData[j+1][3]), cb(HData[j+2][3]), cb(HData[j+3][3])]))
                    Y = np.vstack((Y,[cl(HData[j+4][2]),cb(HData[j+4][3])]))
            else:
                #print t, 'overgeslagen = ', timdif(HData[j+4][1],HData[j][1]), HData[j+4][1],HData[j][1]
                #print t,'Distance overgeslagen : ',dista(HData[j+4][2],HData[j+4][3],HData[j][2],HData[j][3])
                t += 1

si = -.1 * len(X)
print 'Aantal records in dataset (X) =', len(X), 'Aantal in testset (si) =', si

from sklearn.utils import shuffle
X, Y = shuffle(X,Y)
X = X.astype(np.float32)
Y = Y.astype(np.float32)

# Voor het neuraal nw maar eens Lasagne proberen.
from lasagne import layers
from lasagne.updates import nesterov_momentum
from nolearn.lasagne import NeuralNet

net1 = NeuralNet(
    layers=[  # three layers: one hidden layer
        ('input', layers.InputLayer),
        ('hidden1', layers.DenseLayer),
        ('output', layers.DenseLayer),
        ],
    # layer parameters:
    input_shape=(None, 8),  # 8 input values per batch
    hidden1_num_units=100# number of units in hidden layer
    output_nonlinearity=None # output layer uses identity function
    output_num_units=2# 2 target values - lengte , breedte
                 
    # optimization method:
    update=nesterov_momentum,
    update_learning_rate=0.000001,
    update_momentum=0.9,
                 
    regression=True,  # flag to indicate we're dealing with regression problem
    max_epochs=1600# we want to train this many epochs
    verbose=1,
    )

net1.fit(X[1:si], Y[1:si])

def Maakkaart(Cl1, Cl2, Cl3 , Cl4, Cb1, Cb2, Cb3, Cb4, Wl, Wb, El, Eb):
    flab = '&markers=color:blue%7Clabel:A%7C'
    la1 = str(Cl1)
    lo1 = str(Cb1)
    la2 = str(Cl2)
    lo2 = str(Cb2)
    la3 = str(Cl3)
    lo3 = str(Cb3)
    la4 = str(Cl4)
    lo4 = str(Cb4)
    la5 = str(Wl)
    lo5 = str(Wb)
    la6 = str(El)
    lo6 = str(Eb)
    lab = '&markers=color:blue%7Clabel:1%7C' + la1 + ',' + lo1 + '&markers=color:blue%7Clabel:2%7C' + la2 + ',' + lo2 + '&markers=color:blue%7Clabel:3%7C' + la3 + ',' + lo3 + '&markers=color:blue%7Clabel:4%7C' + la4 + ',' + lo4 + '&markers=color:green%7Clabel:5%7C' + la5 + ',' + lo5  + '&markers=color:red%7Clabel:E%7C' + la6 + ',' + lo6
    #print lab
    import webbrowser
    webbrowser.open('https://maps.googleapis.com/maps/api/staticmap?center=&zoom=14&size=1200x600&maptype=roadmap' + lab,1,True)

# Kijk in testset (waarden > si) of er anomalieën in zitten. In dit geval afwijking > 2 km. 
Tdist = np.array([])
printfirst = True
kaarten = 0
for i in range(len(X[si:])-1):
    j = si+i
    pred = net1.predict(X[j:j+1])
    pred = pred[0].tolist()
    lpred, bpred = pred[0], pred[1]
    l_est, l_real, l_perc, l_dis = dist(rl(lpred), rl(Y[j][0]))
    b_est, b_real, b_perc, b_dis = dist(rb(bpred), rb(Y[j][1]))
    Tdist = np.append(Tdist, [l_dis + b_dis])
    if ((l_dis + b_dis > 2.0) and kaarten < 10):
        kaarten += 1
        print 'Distance = ' , l_dis + b_dis
        Maakkaart(rl(X[j,0]),rl(X[j,1]),rl(X[j,2]),rl(X[j,3]),rb(X[j,4]),rb(X[j,5]),
        rb(X[j,6]),rb(X[j,7]),rl(Y[j,0]),rb(Y[j,1]),rl(lpred),rb(bpred))

# statistische analyse met Pandas
Td = pd.DataFrame(Tdist)
print(Td.describe())

Foute bussen - deel zeven - Optimalisatie

De eerdere testen met het neuraal netwerk waren wel veelbelovend maar zeker niet geschikt om anomaly detection uit te voeren. De afwijking was simpelweg veel te groot. Ik vermoed dat dit ondermeer te maken heeft met de nauwkeurigheid van de getallen. Een graad is al 111.12 km en eigelijk zou een nauwkeurigheid van bijvoorbeeld maximaal een halve kilometer gewenst zijn. Een halve kilometer is ongeveer 0.0044996 graden. Of te wel de nauwkeurigheid speelt zich af in de kleinere getallen. Voor de simulatie is de exacte aardpositie niet relevant. Het gaat om relatieve verschillen. Ik besluit de getallen dan ook in ieder geval terug te brengen naar rond het nulpunt. Simpelweg 41 graden aftrekken van de lengte-as en 87 graden optellen bij de breedte-as.  Daarna vermenigvuldig ik ze beide met 100. Om daarna weer de echte afstandsfout te berekenen en een kaartje te tekenen moet ik uiteraard de omgekeerde berekening uitvoeren. Het resultaat is indrukwekkend. Ik besluit het aantal epochs (minibatches) ook te verhogen naar 1600.  

Een gemiddelde van onder de 1 km. Dat is al een enorme stap in de goede richting.

count  1317.000000
mean      0.986176
std       1.551062
min       0.031843
25%       0.458521
50%       0.700672
75%       1.018873

max      20.588678
   
Hier een voorbeeld resultaat:
Distance =  0.268436288452


En hier een voorbeeld van een 'anomalie':

Distance =  18.6012228149
De anomalieën blijken vaak van deze aard. Onlogische samenhang van posities. Om een beter simulatie te maken kan ik waarschijnlijk deze fouten eruit halen. Hoe hard mag een bus eigelijk rijden in Chicago? Is 100 km/uur een anomalie? In 2 minuten zou dan 3,3 km gereden worden. In 8 minuten dus maximaal 13.2 km.  18.6 lijkt dan wel erg snel! Eens kijken of we de voorspellingen nog kunnen verbeteren.  


vrijdag 16 oktober 2015

Foute bussen - deel zes - Go graphical

Het aardige van zo'n experiment is natuurlijk om het echte effect van de simulatie waar te nemen. Daar kan Google maps ons bij helpen. Het blijkt niet ingewikkeld om e.e.a. aan de 'staticmap-service' aan te bieden. Wel is het uiteraard even uitzoeken welke parameters allemaal mee moeten / kunnen worden gegeven.
Na wat 'freubelen' is dit bijvoorbeeld een resultaat:

Distance =  1.51158874512

De blauwe 'druppels' geven de meetpunten per 2 minuten aan. De groene is het laatste werkelijke meetpunt. De rode is de simulatie. De afstand is hier ruim 1.5 km. Het maakt in ieder geval duidelijk dat er nog heel veel gewonnen moet worden in betrouwbaarheid. Of is dit hieronder een echte anomalie :-) :

Distance =  3.30887878418

donderdag 15 oktober 2015

Foute bussen - deel vijf - Neuraal

Het kost weer even moeite om de data 'om te bouwen' voor gebruik in een neuraal netwerk. Ik besluit gebruik te maken van Lasagne omdat het tot zeer overzichtelijke code leidt. Dat kan een chaoot als ik wel gebruiken. :-) 
Onderwater werkt Lasagne met Theano waardoor mijn grafische processor weer mee kan werken. Dat moet snelheidswinst opleveren. Wel moet ik het datatype daarvoor omzetten naar 'float32'. Gek genoeg werkt X = np.asarray(X, type=float32) niet. Gelukkig kom ik er snel achter dat X=X.astype(np.float32) wel werkt. Raar! 
De 'fit' functie van Lasagna werkt al snel. Ik kan de 'predict' functie echter niet zo snel aan de praat krijgen. Het blijkt een oude, bekende fout. Om het juiste inputdatatype te gebruiken moet er een range van 1 groot worden opgegeven. Bijvoorbeeld :
pred = net1.predict(X[si+i:si+i+1]) . Die was ik, volgens mij bij het neuraal bewerken van de Titanic, ook al eens tegengekomen.
Een 2e uitdaging blijkt om de output(hier 'pred') verder te verwerken. Ik krijg bijvoorbeeld dit resultaat: pred = [[ 41.93833542 -87.64888   ]]. Daar ontbreekt een komma! Uiteindelijk vind ik de functie 'tolist()': Die plaatst er gelukkig weer kommas bij. Ik gebruik hem met een verwijzing naar de eerste rij in het array. pred = pred[0].tolist() Dat zorgt voor de verwerking van de dubbele haken. Met lpred, bpred = pred[0], pred[1] kan ik vervolgens de lengte en breedte graad uit elkaar halen.  Draaien maar!


Het eerste wat opvalt is de snelheid! In slechts enkele seconden resultaat. En niet eens zo slecht:         
NN:
count  1329.000000
mean      6.280502
std       3.284298
min       0.057225
25%       3.642903
50%       6.513905
75%       8.674468

max      12.038028

Al iets beter dan de Support Vector Machine en wel, denk ik, 100x zo snel!:
SVM:  
count  443.000000
mean     6.405327
std      3.505544
min      0.067581
25%      3.960455
50%      6.419669
75%      8.952459
max     17.101152

Hoewel ik 400 'epochs' (Een soort minibatches om het NN te leren) heb gekozen omdat het voorbeeld dat aangaf blijkt hij er maar 7 nodig te hebben voor het eindresultaat. Lasagne geeft daar automatisch een mooi rapport over: 

  input             (None, 8)           produces       8 outputs
  hidden            (None, 100)         produces     100 outputs
  output            (None, 2)           produces       2 outputs
  epoch          train loss    valid loss     train/val  dur
-------  ------------------  ------------  ------------  -----
      1  214542254080.00000  515777.40625  415959.00000  0.06s
      2  1944574.12500     354.51895   5485.10645  0.07s
      3     391.01468       0.13764   2840.84082  0.07s
      4       0.07900       0.00129     61.01440  0.07s
      5       0.00122       0.00125      0.98046  0.06s
      6       0.00121       0.00125      0.96734  0.07s
      7       0.00121       0.00125      0.96732  0.07s 
      8       0.00121       0.00125      0.96732  0.07s 

Na 7 epochs leert 'hij' niets meer bij. Hmmmmm ... Nu maar eens lekker aan de parameters sleutelen. 100 hidden layers of 16 blijkt nu niets uit te maken. De grootste winst boek ik door de 'update_learning_rate' sterk te verkleinen van 0.01 naar 0.000001. Dat lijkt intuïtief ook wel logisch omdat de Lenkte- en Breedtegraad natuurlijk feitelijk heel grote getallen zijn in verhouding met de busverplaatsingen. Per leermoment moet er dus niet te grote stappen worden gemaakt.  Nu lijkt hij wel lekker tot de laatste epoch door te leren. Door de snelheid is het leuk om allerlei variaties uit te proberen. Ik zet de hidden units weer op 100. Na 400 epochs is dit het resultaat: 

count  1329.000000
mean      4.286737
std       2.489372
min       0.183120
25%       2.245341
50%       4.071879
75%       6.276527
max      14.031579

Kijk, daar kunnen we al mee thuiskomen :-) Even lekker verder testen. 
Oh ja, de code weer: 

#execfile('/Users/DWW/Documents/Follow1bus outlier neural.py')
import webbrowser
import time
import numpy as np
from sklearn.externals import joblib
from sklearn import svm
import pandas as pd

Data = joblib.load('/Users/DWW/Documents/Bus_outlier.pkl')
Data = np.array(sorted(Data, key = lambda x: (x[0], x[1])))
busses = sorted(list(set([Colm[0] for Colm in Data])))

def dist(est,real):
    if real == 0:
        perc = 0
    else:
        perc = np.absolute(100* (real-est)/real)
    dis = np.absolute(real-est) * 1.852 * 60
    return est, real, perc, dis

def tim(int):
    if len(int) == 7:
        int = '0' + int
    hr = int[0:2]
    mi = int[3:5]
    ds = int[6:8]
    if int[6:8] == 'PM':
        ds = 12
    else:
        ds = 0
    return float(ds) + float(hr) + (float(mi)/100)


X = np.array([0.,0.,0.,0.,0.,0.,0.,0.])
Y = np.array([0.,0.])
t = 0
for i in busses:
    HData = Data[Data[:,0]==i]
    if len(HData) > 5: # Alleen voor bussen waar meer dan 5 waarnemengen van zijn.
        for j in range(len(HData)-4):
            if tim(HData[j+4][1])-tim(HData[j][1]) < 14.: # Alleen de waarnemeningen die inderdaad in tijd bij elkaar liggen. Feitelijk maximaal 8 minuten : 14 genomen voor marge
                X = np.vstack((X,[float(HData[j][2]), float(HData[j+1][2]), float(HData[j+2][2]), float(HData[j+3][2]), float(HData[j][3]), float(HData[j+1][3]), float(HData[j+2][3]), float(HData[j+3][3])]))
                Y = np.vstack((Y,[float(HData[j+4][2]),float(HData[j+4][3])]))
            else:
                t += 1

si = -.1 * len(X)
print 'Aantal records in dataset (X) =', len(X), 'Aantal in testset (si) =', si
X = X.astype(np.float32)
Y = Y.astype(np.float32)

# Voor het neuraal nw maar eens Lasagne proberen.
from lasagne import layers
from lasagne.updates import nesterov_momentum
from nolearn.lasagne import NeuralNet

net1 = NeuralNet(
    layers=[  # three layers: one hidden layer
        ('input', layers.InputLayer),
        ('hidden', layers.DenseLayer),
        ('output', layers.DenseLayer),
        ],
    # layer parameters:
    input_shape=(None, 8),  # 8 input values per batch
    hidden_num_units=100# number of units in hidden layer
    output_nonlinearity=None# output layer uses identity function
    output_num_units=2# 2 target values - lengte , breedte
                 
    # optimization method:
    update=nesterov_momentum,
    update_learning_rate=0.000001,
    update_momentum=0.9,
                 
    regression=True,  # flag to indicate we're dealing with regression problem
    max_epochs=400# we want to train this many epochs
    verbose=1,
    )

net1.fit(X[1:si], Y[1:si])

Tdist = np.array([])
for i in range(len(X[si:])-1):
    pred = net1.predict(X[si+i:si+i+1])
    pred = pred[0].tolist()
    lpred, bpred = pred[0], pred[1]
    l_est, l_real, l_perc, l_dis = dist(lpred, Y[si+i][0])
    b_est, b_real, b_perc, b_dis = dist(bpred, Y[si+i][1])
    Tdist = np.append(Tdist, [l_dis + b_dis])

Td = pd.DataFrame(Tdist)
print(Td.describe())