System und Verfahren zur rechnerbasierten Analyse großer Datenmenqen
Beschreibung
Hintergrund
Derzeit verfügbare, kostengünstige Computerprogramme zur Datenanalyse (zum Beispiel DataCockpit® 1.04) sind in der Analyse nennenswert langsamer als konkurrierende Data Mining Workbenches (SPSS und andere), können nur erheblich kleinere Datenmengen verar- beiten, und haben andere Nachteile (sie sind als monolithischer Block programmiert, sie sind in ihrer Architektur und Datenbehandlung ungeeignet zur Client-Server-Architektur, etc.).
Ein etabliertes Verfahren zur Segmentierung von Daten OCIusteringO sowie zur Vorhersage ist das Verfahren der Selbstorganisierenden Merkmalskarten', englisch SOM (,self organizing maps'). Zur Segmentierung oder zur Vorhersage werden in diesem Verfahren die Daten auf ein ein-, zwei- drei-, oder mehrdimensionales selbstadaptierendes Neuronen-Netz abgebildet. [T. Kohonen. Self-Organization and Associative Memory, vol. 8 of Springer Series in Information Science, 3rd edition, Springer-Verlag, Berlin, 1989].
Bei der SOM-basierten Datenanalyse werden das sogenannte ,Kohonen Clustering' und die sogenannte SOM-Karten-Analyse unterschieden. Das Kohonen Clustering arbeitet nur mit sehr wenigen Neuronen, typischerweise zwischen etwa 4 und etwa 20 Neuronen. Jedes dieser Neuronen repräsentiert einen ,Cluster', also eine homogene Gruppe von Datensätzen. Diese Technik wird vor allem zur Datensegmentierung eingesetzt und ist in vielen Data Mi- ning Softwarepaketen implementiert, zum Beispiel in SPSS Clementine oder IBM DB2 Ware- house (siehe zum Beispiel Ch. Ballard et al., Dynamic Warehousing: Data Mining Made Easy, IBM Redbook, 2007).
Die SOM-Karten-Analyse benutzt demgegenüber relativ große Neuronennetze von zum Bei- spiel 30 bis 40 Neuronen zur Datenanalyse. Hierbei werden homogene Datensegmente durch lokale Gruppen von Neuronen mit ähnlichen Merkmalsausprägungen repräsentiert. SOM-Kar- ten werden zur Datenexploration, Segmentierung, Vorhersage, Simulation und Optimierung verwendet (siehe zum Beispiel R. Otte, V. Otte, V. Kaiser, Data Mining für die industrielle Praxis, Hanser Verlag, München, 2004).
Als Beispiele für weiteren technologischen Hintergrund seien die EP 97 11 56 54.2 und die EP 97 12 0787.3 genannt.
Um eine umfangreiche, auf einem Computer zusammengetragene Datensammlung - zum Beispiel Produktionsdaten aus einer Fertigungsanlage mit etwa 104 bis 1010 Datensätzen und etwa 3 bis 1000 Merkmalen pro Datensatz - zu analysieren und ggf. die Ergebnisse der Ana- lyse in den Fertigungsablauf zurückfließen zu lassen, werden die vorhandenen Datensätze immer wieder einem lernenden und sich selbst adaptierenden Neuronen-Netz präsentiert.
Dabei kann es sich um Produktionsdaten in der Maschinenbau-, Chemie-, Automobil-, Zuliefererindustrie handeln: Zum Beispiel 10 Millionen produzierte Einheiten, 10 nominale Kompo- nenten- und Produktionslinien-Informationen, 10 binäre Komponenten- und Ausstattungsinformationen, 10 numerische Produktionsdaten (gemessene ToleranzdateKv Sensordaten, erfasste Produktionszeiten, Maschinendaten, ...) Ziel der SOM-Analyse ist hier die Qualitätssicherung, Fehlerquellenanalyse, Frühwarnung, Produktionsprozess-Optimierung.
Ein anderes Beispiel wären Kundendaten in Einzelhandels-, Finanz- oder Versicherungsunternehmen: 10 Millionen Kunden, 10 nominale demografische Merkmale (Familienstand, Berufsgruppe, Region, Wohnungstyp, ...), 10 binäre Merkmale über Interessen und in Anspruch genommene Dienstleistungen / Produkte (Geschlecht; besitzt Kreditkarte; betreibt Online- Banking, ...), 10 numerische Merkmale (Jahreseinkommen, Alter, Jahresumsatz, Kreditwür- digkeit, ...)■ Ziel der SOM-Analyse ist hier die Kundensegmentierung, die Vorhersage von
Kundenwert, Kreditwürdigkeit, Schadensrisiko, ... sowie die Optimierung von Marketingkampagnen.
Jedes Neuron des sich selbst adaptierenden Neuronen-Netzes hat so viele Signaleingänge, wie jeder der einzelnen Datensätze Merkmale hat. Hat das Neuronen-Netz die Daten gelernt', können mit dem trainierten Neuronen-Netz unter Anderem folgende Aufgaben abgearbeitet werden:
• Visuelle interaktive Datenexploration: Interaktives Entdecken von interessanten Untergruppen, Korrelationen zwischen Merkmalen und allgemeinen Zusammenhängen mit Hilfe von verschiedenen Visualisierungen der Daten, welche aus selbstorganisierenden Merkmalskarten erzeugt werden.
• Segmentierung: Einteilen der gesamten Daten in homogene Gruppen.
• Vorhersage: Vorhersage von bisher unbekannten Merkmalsausprägungen in einzelnen Datensätzen. • Simulation: Wie würden sich gewisse Merkmalsausprägungen eines Datensatzes wahrscheinlich ändern, wenn bestimmte andere Merkmalsausprägungen gezielt geändert würden?
• Optimierung: Wenn für eine Teilmenge der Merkmale bestimmte optimale Ausprä¬ gungen erreicht werden sollen, wie sollten dann die übrigen Merkmalsausprägungen gewählt werden?
Bestehende Methoden und Implementierungen SOM-Karten-basierter Datenanalyse benötigen für deren kommerzielle Ersetzbarkeit derzeit zu lange Trainingszeiten der Neuronen- Netze. Diese Trainingszeiten übersteigen diejenigen anderer Data Mining Techniken auf denselben Daten um etwa das Hundertfache und behindern die Anwendung derartiger existierender Software-Pakete auf viele existierende Datensammlungen und Fragen mit der gegenwärtig zur Verfügung stehenden Rechnerleistung.
Um zum Beispiel mit der Software DataCockpit® ein SOM-Netzwerk von 30 ■ 40 Neuronen auf einer großen Datenbank von 60.000.000 Datensätzen mit 100 Merkmalen zu trainieren, müsste ein Server mit ein bis zwei 3 GHz Intel® CPUs und 64 GigaByte RAM) etwa 2 - 3 Monate ununterbrochen rechnen - dies wäre in der Praxis völlig inakzeptabel.
Technisches Problem
So besteht die technische Anforderung, diese Trainingszeit durch technische Vorkehrungen signifikant zu reduzieren, um die Auswertung großer Datenmengen in kurzer Zeit zu ermög- liehen. Beispielweise soll die Rechenzeit für das obengenannte Beispiel auf ca. 100 Stunden oder weniger verringert werden.
Kurzbeschreibung
Zur Problemlösung wird ein elektronisches Datenverarbeitungssystem zur Analyse von Daten mit wenigstens einem Analyse-Rechner vorgeschlagen, wobei der Analyse-Rechner dazu eingerichtet und programmiert ist, ein selbst adaptierendes Neuronen-Netz zu implementieren. Das Neuronen-Netz wird mit einer Vielzahl Datensätze mit vielen Merkmalen einem Training unterzogen, bei dem den Neuronen des Neuronen-Netzes aus der Vielzahl der Datensätze mit ihren vielen Merkmalen zu gewinnende Neuronengewichte zuzuordnen sind. Ein Training kann mehrere Trainingsphasen umfassen, wobei jede Trainingsphase eine bestimmte Anzahl Trainingsdurchläufe aufweist, und wobei zu Beginn jeder Trainingsphase entweder Neuronen in das Neuronen-Netz einzufügen sind, deren Neuronengewichte sich mindestens teilweise aus Gewichten vorhandener Neuronen ergeben, oder Neuronen aus dem Neuronen-Netz zu entfernen sind und die Neuronengewichte der verbleibenden Neuro- nen mindestens teilweise mit Teilen der Gewichte der entfernten Neuronen zu gewichten sind.
Weiter wird ein Verfahren zum Trainieren eines Neuronen-Netzes vorgeschlagen, das folgende Schritte umfasst: ■ Speichern der Anzahl der Merkmale (Spalten) in den Trainingsdaten in einem ersten
Wert. ■ Ausführen der folgenden Schritte für alle Änderungen der Neuronen-Netz-Größe: o Speichern der Anzahl der Neuronen im Netz in einem zweiten Wert, o Speichern von initialen Neuronengewichten in einem zweidimensionalen ersten Feld, wobei sich eine erste Dimension des Feldes nach dem ersten Wert und eine zweite Dimension des Feldes nach dem zweiten Wert bestimmt, o Ausführen der folgenden Schritte für alle Iterationsschritte:
■ Ausführen der folgenden Schritte für alle Datensätze oder eine Teilmenge der Trainingsdatensätze:
• Reservieren eines zweiten Feldes für das Speichern von Distanzen zwischen dem aktuellen Trainingsdatensatz und allen Neuronen, • Setzen aller Werte in diesem zweiten Feld auf einen einheitlichen initialen Wert,
• Setzen eines Wertes für eine minimale Distanz auf einen vorbestimmten Wert, der so groß gewählt ist, dass er sicher größer ist als alle tatächlichen Distanzen zwischen dem aktuellen Trainings- datensatz und jedem Neuron des Neuronennetzes,
• Ausführen der folgenden Schritte für alle Merkmale, die im aktuellen Datensatz einen validen Merkmalswert haben: o Ausführen des folgenden Schrittes für alle Neuronen:
■ Addieren des Distanzwertes zwischen dem Neuro- nengewicht, das an einer durch den ersten Wert und den zweiten Wert bestimmten Stelle des ersten Feldes gespeichert ist, und dem validen Merkmalswert zu einem Wert an einer durch den zweiten Wert bestimmten Stelle des zweiten Feldes, • Ausführen der folgenden Schritte für alle Neuronen, für die der an der durch den zweiten Wert bestimmten Stelle des zweiten Feldes gespeicherte Wert kleiner ist als der Wert für die minimale Distanz: o Setzen der minimalen Distanz auf den Wert, der an der durch den zweiten Wert bestimmten Stelle des zweiten FeI- des gespeichert ist, o Setzen des aktuellen Neurons als bestes Neuron,
• Ausführen der folgenden Schritte für alle Merkmale m, die im aktu¬ ellen Trainingsdatensatz einen validen Wert haben: o Verschieben derjenigen in dem ersten Feld gespeicherten
Neuronengewichte des besten Neurons, welche Merkmalen entsprechen, die im aktuellen Trainingsdatensatz valide
Werte haben, in Richtung auf die entsprechenden validen Merkmalswerte des aktuellen Trainingsdatensatzes, und o Verschieben derjenigen in dem ersten Feld gespeicherten Neuronengewichte gewisser Nachbarneuronen des besten Neurons, welche Merkmalen entsprechen, die im aktuellen
Trainingsdatensatz valide Werte haben, in Richtung auf die entsprechenden validen Merkmalswerte des aktuellen Trainingsdatensatzes.
Die erste Schleife über alle Merkmale kann durch mehrere Schleifen ersetzt werden.
Eine der Schleifen kann über numerische Merkmale, eine Schleife kann über binäre Merkmale und/oder eine Schleife kann über textuelle Merkmale iterieren.
Das erste Feld kann so angelegt sein, dass es aus einer dem ersten Wert entsprechenden Anzahl lückenloser Folgen von je einer dem zweiten Wert entsprechenden Anzahl numerischer Feldzellen besteht.
Die Distanzen zwischen den Neurongewichten und den Merkmalswerten des aktuellen Trainingsdatensatzes können quadratische Distanzen sein.
Die Trainingsdaten können vor zu Beginn des Verfahrens komprimiert und indiziert werden, wobei textuelle Werte durch ganzzahlige Wert-Indices und/oder Fließkomma-Werte in diskrete Intervalle diskretisiert werden.
Bei einer Vergrößerung des Neuronen-Netzes (Expansionsschritt) können die Gewichte der neu eingefügten Neuronen durch lineare, kubische oder sonstige Interpolation bestimmt werden, falls es sich um innere Neuronen handelt und/oder die Gewichte der neu eingefügten Neuronen können durch Extrapolation bestimmt werden, falls es sich um Randneuronen handelt.
Bei einer Verkleinerung (Reduktionsschritt) des Neuronen-Netzes kann jedes Neuron mehre¬ re benachbarte vorhandene Neuronen ersetzen und in jedem seiner Neuronengewichte den Mittelwert der entsprechenden Neuronengewichte der ersetzten Neuronen erben.
Die Neuronen-Netz-Größe kann zu Beginn jeder Trainingsphase entweder durch ein Einfügen von Neuronen in das Neuronen-Netz, deren Neuronengewichte sich mindestens teilweise aus Gewichten vorhandener Neuronen ergeben, vergrößert werden (Expansionsschritt) oder das Neuronen-Netz kann durch ein Entfernen von Neuronen aus dem Neuronen-Netz verkleinert werden (Reduktionsschritt), wobei die Neuronengewichte der verbleibenden Neuronen bei dem Entfernen mindestens teilweise mit Teilen der Gewichte der entfernten Neuronen zu gewichten sind.
Alle Distanzen zwischen der vorbestimmten Anzahl Neuronen und dem aktuellen Trainingsdatensatz können quadratische Distanzen sein oder die Distanzen können jeweils ein Dis- tanzmaß aufweisen, das die Eigenschaften einer Metrik hat.
Für jede Trainingsphase kann zumindest eine Auswahl der Datensätze verwendet werden, um die Neuronengewichte der Neuronen des Neuronen-Netzes zu gewichten, wobei für jede Trainingsphase abhängig von der aktuellen Größe des Neuronen-Netzes eine unterschiedli- che Anzahl von Trainingsdurchläufen für das Training der Neuronen mit den Merkmalen gewählt werden kann, wobei die Trainingsdurchläufe so oft auszuführen sind, bis die maximale vorgegebene Anzahl von Trainingsdurchläufen erreicht ist, oder das Training insofern konvergiert als dass sich die Merkmalsgewichte der Neuronen nicht mehr wesentlich ändern.
Weiter kann vorgesehen sein, dass zwischen zwei Trainingsphasen, für die Neuronen in das Netz einzufügen sind, wenigstens eine Trainingsphase auszuführen ist, für die Neuronen aus dem Netz zu entfernen sind. Diese Vorgehensweise führt zu einem sehr schnellen Konvergieren der Werte, mithin zum Ende des Trainings.
In einer weiteren Ausführungsform kann das Entfernen eines Neurons so geschehen, dass bei dem Entfernen des Neurons nur die unmittelbar an das zu entfernende Neuron angrenzenden verbleibenden Neuronen neu zu gewichten sind, oder die verbleibenden Neuronen mittels einer linearen oder kubischen oder Exponential-Spline-Interpolation oder einer sonstigen Interpolationsvorschrift unter Einbeziehung mehrerer Nachbarneuronen neu zu gewich- ten sind.
Die Neuronen des Neuronen-Netzes können als Knoten einer mehrdimensionalen, zum Beispiel zweidimensionalen Matrix anzuordnen sein. In einem solchen Fall können beim Entfer¬ nen oder Einfügen von Neuronen aus dem / in das Neuronen-Netz aus der Matrix Zeilen oder Spalten zu entfernen / einzufügen sein.
Die Gewichte aller Neuronen für ein bestimmtes Merkmal können dabei so strukturiert sein, dass sie in einem zusammenhängenden Speicherbereich eines Analyse-Rechners zu speichern sind.
Die anfänglichen Neuronengewichte der Neuronen des Neuronen-Netzes können durch ein heuristisches Verfahren zu bestimmen sein. Das Verfahren kann so gestaltet sein, dass die Merkmale vor dem Start des Trainings lediglich einmal gelesen werden müssen und lediglich einmal auf numerische Merkmale zu transformieren sind. Die Merkmale können vor dem Training als Trainingsdaten komprimiert zu speichern sein.
Ein Analyse-Rechner kann eine Initialkonfiguration des Neuronen-Netzes und Trainings- Parameter erstellen und die Initialkonfiguration und die Trainings-Parameter an mindestens einen weiteren Analyse-Rechner versenden. Die Initialkonfiguration des Neuronen-Netzes und die Training-Parameter können von dem mindestens einen weiteren Analyse-Rechner eingelesen werden.
Der Analyse-Rechner kann für alle Trainingsphasen und/oder für alle Trainingsläufe die Neuronengewichte und oder eine Lernrate und/oder einen Radius und / oder die Anzahl von Iterationsschritten an mindestens einen weiteren Analyse-Rechner versenden. Der mindes- tens eine weitere Analyse-Rechner kann die von dem Analyse-Rechner versandten Informationen einlesen und für die eingelesene Anzahl von Trainingsläufen jeweils Distanzen zwischen einer Vielzahl von Neuronen berechnen, ein Gewinnerneuron ermitteln, das Gewinnerneuron jeweils in einer Liste speichern und die Liste nach der Anzahl von Iterationsschritten an den Analyse-Rechner senden. Der Analyse-Rechner kann dann die Liste der Ge- winnerneuronen von dem mindestens einen weiteren Analyse-Rechner empfangen und darauf basierend die Gewichte der Gewinnerneuronen und ihrer Nachbarn modifizieren.
Die Trainingsdaten können von dem Analyse-Rechner in Daten-Objekte aufgeteilt werden und die Daten-Objekte an mindestens einen weiteren Analyse-Rechner versendet werden, wobei die Daten-Objekte vor dem Versenden so dimensioniert werden können, dass sie vollständig in den Arbeitsspeicher des mindestens einen weiteren Analyse-Rechner passen.
Zur Bestimmung der Distanzen zwischen den Neuronen und dem aktuellen Trainingsdatensatz kann das Verfahren so gestaltet sein, dass in seinem rechenzeitintensivsten Teil nur auf lückenlose Folgen von Speicherfeldern zugegriffen wird.
Zur Bestimmung der Distanzen zwischen den Neuronen und dem aktuellen Trainingsdatensatz können lange Schleifen über höchstens 2 Speicherfeld-Variablen verwendet werden.
Für jedes nominale Merkmal in den Trainingsdaten können vorkommende Nominalwerte in einem Verzeichnis gespeichert werden, in dem jedem Merkmalswert ein vorläufiger Index zuordnet wird und das zusätzlich die Vorkommenshäufigkeit eines Merkmals zählt, und jeder Nominalwert kann durch den vorläufigen Index ersetzt werden.
Das erstellte Verzeichnis kann nach Vorkommenshäufigkeit sortiert sein, einer Anzahl häufiger Werte kann jeweils ein neuer Index zugeordnet werden und die vorläufigen Indices können durch die neuen Indices ersetzt werden.
Kurzbeschreibung der Zeichnungen
Fig. 1 zeigt ein elektronisches Datenverarbeitungssystem zur Analyse von Daten. Fig. 2 zeigt schematisch einen Expansionschritt des Mehrgitterverfahrens. Fig. 3 zeigt schematisch einen Reduktionsschritt des Mehrgitterverfahrens. Fig. 4 zeigt einen ersten Teil der Technik, der dem Verfahren zugrunde liegt. Fig. 5 zeigt einen zweiten Teil der Technik, der dem Verfahren zugrunde liegt. Fig. 6 zeigt eine Variante der Technik aus Fig. 1.
Ausführliche Beschreibung
Die vorgeschlagene Ausgestaltung hat die technische Wirkung, die Effizienz und die Sicherheit der Datenanalyse zu erhöhen. Eine weitere technische Wirkung besteht darin, die Anforderungen an die erforderlichen Computerressourcen gegenüber der herkömmlichen Vorgehensweise zu senken. Schließlich wird die Datenübertragungsgeschwindigkeit und die anschließende Datenverarbeitung positiv beeinflusst.
Dies ermöglicht effiziente Analysen und Auswertungen, z.B. Neuronen-Netz-Analysen auf Analyse-Servern mit relativ wenig Hauptspeicher (RAM). Demgegenüber kann zum Beispiel die bisherige Implementierung der DataCockpit-Software softwaretechnisch bedingt nur Daten bis etwa 200 - 400 Megabytes Größe verarbeiten. Dies sind Einschränkungen, die eine SOM-Karten-basierte Datenanalyse basierend auf dieser herkömmlichen oder damit vergleichbarer Softwaretechnik deutlich benachteiligen. Somit werden hier technische Maßnah-
men beschrieben, welche die Vorteile der SOM-Karten-basierten Datenanalyse für größere Datenmengen auf kleineren Rechnern zur Verfügung stellen.
Unter Bezugnahme auf Fig. 1 dient ein elektronisches Datenverarbeitungssystem zur Analyse von Daten. Das elektronische Datenverarbeitungssystem hat einen Analyse-Server 10 und einen oder mehrere Vor-Ort-Client-Rechner 12. Der Analyse-Server ist zum Beispiel ein PC mit mehreren 3 GHz Intel® CPUs und 64 GigaByte RAM als Hauptspeicher. Darin ist ein selbst adaptierendes Neuronen-Netz als Datenobjekt zu implementieren, das auf eine große Datenbank mit einer Vielzahl Datensätzen mit vielen Merkmalen zu trainieren ist. Der Vor-Ort- Client-Rechner 12 ist dazu eingerichtet und programmiert, ihm zugeführte Daten einer Da- tenvorverarbeitung und/oder einer Datenkompression zu unterziehen, bevor die Daten über ein elektronisches Netzwerk 14, zum Beispiel das Internet, an den Analyse-Server 10 gesendet werden. Der Analyse-Server 10 ist außerdem dazu eingerichtet und programmiert, mit den empfangenen, vorverarbeiteten / komprimierten Daten das selbst adaptierende Neuro- nen-Netz zu trainieren, indem die Daten dem sich selbst adaptierenden Neuronen-Netz wiederholt präsentiert werden und anschließend eine Analyse durchzuführen um ein selbst adaptierende Neuronen-Netz-Modell zu erstellen. Der Analyse-Server bewirkt anschließend ein Versenden des selbst adaptierenden Neuronen-Netz-Modells von dem Analyse-Server 10 an den Vor-Ort-Client-Rechner 12 ebenfalls über das Netzwerk 14. Der Vor-Ort-Client- Rechner 12 ist schließlich dazu eingerichtet und programmiert, die Daten des selbst adaptierenden Neuronen-Netz-Modells einer Dekomprimierung zu unterziehen.
Beim Training der SOM-Netze wird von einem heuristisch gewählten Startzustand des Netzes ausgegangen, der dann iterativ verbessert wird, bis das Lernverfahren konvergiert. Bei SOM- Netzen sind für verschiedene Arten von Fragestellungen unterschiedliche Netzgrößen geeignet.
Relativ kleine Netze von 10 bis 100 Neuronen sind ausreichend, um die Grobstrukturen und Cluster in den Daten herauszuarbeiten, und um zunächst einmal die heuristische Anfangslö- sung in diejenigen Bereiche des Datenraumes zu bewegen, die überhaupt mit Datenpunkten gefüllt sind. In einem Datenraum mit zum Beispiel 50 Merkmalen mit je 4 Ausprägungen von Produktionsdaten gibt es bereits 450 ~ 1030 Punkte, die im Prinzip von Datensätzen besetzbar sind. Wenn es aber nur 107 oder weniger Datensätze gibt, ist nur einer von 1023 möglichen Punkten im Datenraum tatsächlich von einem Datensatz besetzt. Ein großer Teil der Lernite- rationen wird dabei verwendet, die Gewichte der Neuronen zunächst in die Nähe von mit Daten besetzten Regionen im Datenraum zu verschieben.
Um zum Beispiel feine Unterschiede innerhalb großer Datencluster richtig wiederzugeben, oder um nur selten vorkommende Merkmalsausprägungen richtig zu repräsentieren sind große SOM-Netze erforderlich.
Hier setzt der hier vorgeschlagene Mehrgitter-Ansatz ein. Dabei werden auf einem kleinen Netz mit vergleichsweise wenig Rechenaufwand - und daher in kurzer Zeit und/oder mit geringen Hardware-Resourcen - die besetzten Neuronen in die interessanten Regionen des Datenraums umgelagert; auf einem derart „verdichteten" Netz werden dann die Feinadjustierungen des SOM-Netzes vorgenommen. Mit weniger Neuronen zu starten bietet dabei einen zweifachen Geschwindigkeitsvorteil. Die Rechenzeit pro Iteration ist proportional zur Neuronenzahl. Außerdem ist aber auch die Konvergenzgeschwindigkeit bei weniger Neuronen schneller, weil jedem Neuron mehr Datensätze zugeordnet werden, so dass jedes Neuron pro Iteration mehr ,Anstöße' bekommt, welche seine Eigenschaften (Gewichte) in der gewünschten Richtung verändern.
Bei der praktischen Ausführung eines SOM-Expansionsschrittes ist es möglich, die ,Maschenweite' des Netzes zu halbieren. Dabei wird zum Beispiel bei einem als zweidimensionale Matrix organisierten Netz zwischen je zwei benachbarte Neuronen in x- Richtung mittig ein weiteres Neuron eingesetzt. Anschließend wird zwischen je zwei (alte oder neu eingefügte) in y-Richtung benachbarte Neuronen mittig ein weiteres Neuron in das SOM-Netzwerk eingesetzt. Die Gewichte der neu eingefügten Neuronen können dabei als Interpolation der Gewichte der beiden bestehenden Nachbarneuronen gewählt werden.
Im einfachsten Fall kann dies eine lineare Interpolation sein, bei der jedes Merkmalsgewicht des neuen Neurons der Mittelwert des entsprechenden Merkmalgewichts der beiden Nachbarneuronen ist. Anstelle der linearen Interpolation kann auch eine SpIi- ne-Interpolation (kubische oder Exponentialsplines) unter Einbeziehung mehrerer Nachbarneuronen erfolgen.
Um auch die Randneuronen des existierenden Netzes auf beiden Seiten durch neu eingefügte Neuronen zu flankieren, können die Gewichte der neuen Randneuronen zum Beispiel mittels linearer Extrapolation berechnet werden. Dabei kann das extrapolierte Gewicht des neuen Randneurons := 3/2 ■ (Gewicht des nächsten Nachbarn) - 1/2 • (Gewicht des übernächsten Nachbarn) betragen. Dies ist in Fig. 1 gezeigt, wobei die neu hinzugefügten Neuronen schraffiert dargestellt sind.
Bei der Extrapolation binärer oder nominaler Merkmalswerte wird außerdem sicher¬ gestellt, dass die extrapolierten Werte nicht den erlaubten Wertebereich von 0 bis 1 verlassen.
Zur Umkehrung der Netz-Expansion bietet es sich an, die ,Maschenweite' des Netzes zu verdoppeln, indem jedes zweite Neuronenreihe in x- und y-Richtung aus der Mat¬ rix entfernt wird. Vor der Entfernung werden dabei die in den zu entfernenden Neuronen enthaltenen Informationen den Neuronen zugeführt, die im aus dem Entfernen jeder zweiten Neuronenreihe resultierenden Netz enthalten sein werden. Dabei erbt das neue Netz die Eigenschaften jedes Neurons des alten Netzes mit derselben Gewichtung. Zu entfernende Neuronen mit 4 nächsten verbleibenden Nachbarn (in x- und y-Richtung) geben ihre Eigenschaften mit einen Wertungsfaktor von 1A an jeden der vier verbleibenden Nachbarn ab. Zu entfernende Neuronen mit 2 nächsten verbleibenden Nachbarn vererben ihre Eigenschaften mit Wertungsfaktoren von Vi and diese beiden Nachbarn. Verbleibende Neuronen vererben sich ihre eigenen Eigenschaften mit dem Wertungsfaktor 1. Fig. 2 zeigt schematisch einen Reduktionsschritt. Die zu entfernenden Neuronen sind in Fig. 2 schraffiert dargestellt.
Bei dem vorgestellten Mehrgitterverfahren kann zum Beispiel ein Gitterexpansions- schema eingesetzt werden, das nicht nur vom gröbsten zum feinsten Gitter voranschreitet, sondern dabei mindestens einmal von einer bereits erreichten feinen Gitterstufe zur nächstgröberen Gitterstufe zurückkehrt. Der technische Vorteil dieser Vorgehensweise besteht darin, dass eine gleichmäßige, raschere Konvergenz sämtlicher Lösungsvektoren erzielt wird. Der Rechenaufwand nimmt beim SOM-Netz bis zur Erreichung von Konvergenz bei jeder Gittervergröberung sogar überlinear ab. Daher bewirken bei der SOM-Expansion in allen Expansionsstufen außer der letzten viele Iterationen und auch das zwischenzeitliche Zurückgehen zur nächstgröberen Stufe praktisch keine Rechenzeitverlängerung. Die Gesamt-Rechenzeit wird fast ausschließlich von der letzten, feinsten Expansionsstufe bestimmt.
Die vorgesehenen Iterationen pro Expansionsschritt können dabei Obergrenzen darstellen. Sofern das jeweilige SOM-Netz schon vorher so weit auskonvergiert ist, dass nur noch minimale Änderungen an den Neuronen auftreten, kann die jeweilige Stufe schon vorzeitig beendet werden.
In einer Beispielimplementierung, die im folgenden näher erläutert wird, wird eine Art von Gitterexpansionsschritt implementiert, welche die Halbierung der Maschen-
weite und das Hinzufügung neuer Randneuronen (Extrapolation) beinhaltet. Es werden SOM-Karten mit offenen Randbedingungen implementiert. Dies bedeutet, dass eine Variante des SOM-Netzes angeboten wird, bei der jedes Neuron am linken Rand nicht der rechte Nachbar eines Neurons am rechten Rand ist und jedes Neuron am unteren Rand nicht der obere Nachbar eines Neurons am oberen Rand ist.
Zu beachten ist, dass SOM-Netze nur numerische Merkmalen mit Wertebereichen zwischen 0 und 1 verarbeiten. Vor dem eigentlichen Start des SOM-Trainings werden die Originaldaten deshalb einmal gelesen, und die Originalmerkmale werden auf rein numerische, normalisierte Merkmale transformiert.
Die Beispielimplementierung besteht aus mindestens einer Hilfskiasse und mindestens einer Hauptklasse.
Eine Klasse ,SOMParameters' ist eine Hilfskiasse, die alle Parameter des SOM-
Algorithmus aus einer Parameterdatei lesen und einzeln zur Verfügung stellen kann.
Eine Klasse ,SOMTraining' ist eine Hauptklasse, die nach Zuweisung eines Parameter-Objekts und einer oder mehrerer Datenverarbeitungsobjekte unter Durchführung mehrerer Netzex- pansionsschritte ein SOM-Netz trainiert und das trainierte Netz in eine Datei ausgeben kann.
class SOMParameters
{ public: // public methods interface
// the constructor reads a parameter file and Stores the parameter settings in // the member variables of this class SOMParameters( const string& paramFile="" )
: ivNbNeuronsX(4), ivNbNeuronsY(3), ivNbExpansions(3), ivMaxNeighborDist(2.1), ivLearningRate(0.3), ivMaxMemSizeInMB(512), ivModelName("som"), ivTempDir("c:\\") { if (paramFile == "") return; ifstream file( paramFile.c_str() ); if (!file.is_open() 11 file.eof()) { cout << "Unable to open parameter file "'<<paramFile<<""'<<endl; return;
} string line, param, value; while (Ifile.eofO) { getline( file, line ); size_t posi = line.find( '=' ); param = line.substr( 0, posi ); while (param.findC ') < param. length()) param. erase( param.find(' '), 1 ); value = line.substr( posi+1, posi<line.lengthQ?line.length()-posi-l:0 ); if (param.substr(0,10)=="nbNeuronsX") ivNbNeuronsX = atoi( value. c_str() ); eise if (param.substr(0,10)=="nbNeuronsY") ivNbNeuronsY = atoi( value. c_str() ); eise if (param. substr(0,12)=="nbExpansions") ivNbExpansions = atoi( value. c_str() ); eise if (param. substr(0,15)=="maxNeighborDist") ivMaxNeighborDist = atof( value. c_str() ); eise if (param. substr(0,12)=="learningRate") ivLearningRate = atof( value. c_str() ); eise if (param.substr(0,14)=="maxMemSizeInMB") ivMaxMemSizelnMB = atoi( value. c_str() ); eise if (param. substr(0,9)=="modelName") ivModelName = value; eise if (param. substr(0,7)=="tempDir") ivTempDir = value; eise if (param.substr(0,2) != "//" && param.substr(O,l) != "#" &&
! param. empty()) { cout << "Ignoring unknown SOM parameter '"<<param<<"'" ) } } file.closeO;
}
// public functions for retrieving each parameter's value size_t getlNbOfNeuronsX() const { return ivNbNeuronsX; } size_t getNbOfNeuronsY() const { return ivNbNeuronsY; } size_t getNbOfSOMExpansions() const { return ivNbExpansions; } double getMaxNeighborDistQ const { return ivMaxNeighborDist; }
double getLearningRateO const { return ivLeamingRate; } size_t getMaxMemSizeInMB() const { return ivMaxMemSizelnMB; } const DCString& getModelName() const { return ivModelName; } const DCString& getTempDirectory() const { return ivTempDir; }
private: // private member variables
size_t ivNbNeuronsX; size_t ivNbNeuronsY; size_t ivNbExpansions; double ivMaxNeighborDist; double ivLeamingRate; size_t ivMaxMemSizelnMB; string ivModelName; string ivTempDir;
};
Die Klasse ,SOMTraining' ist eine Hauptklasse der Implementierung mit Netzexpansion. Die Klasse trainiert nach Zuweisung eines Parameter-Objekts und einer oder mehrerer Datenver- arbeitungsobjekte unter Durchführung mehrerer Netzexpansionsschritte ein SOM-Netz (Methode trainSOM()) und gibt das trainierte Netz in eine Datei aus.
Die Klasse ,SOMTraining' enthält vier interne Methoden, welche zur Netzexpansion und -reduktion dienen:
Eine Methode ,initializeNeighborhood()' stellt für eine gegebene Neuronennetzgröße die Topologie- und Nachbarschaftsinformationen zusammen. Dadurch kann ermittelt werden, welches Neuron ist mit welcher Distanz zu welchem Neuron benachbart ist.
Eine zweite Methode ,initializeSOMNetwork()' wählt mittels einer Heuristik Startwerte für die Neuronengewichte des kleinsten, gröbsten SOM-Netzes.
Eine dritte Methode ,expandSOMNetwork()' führt einen Netz-Expansionsschritt von nχ-ny Neuronen auf (2nx+l)-(2ny+l) Neuronen durch.
Eine vierte Methode ,shrinkSOMNetwork()' führt einen Netz-Reduktionsschritt von (2nx+l)"(2ny+l) Neuronen auf nx'ny Neuronen durch.
AIIe vorstehend genannten Methoden mit Ausnahme der Methode ,trainSOM()' werden im folgenden ausführlich dargestellt. Die Methode ,trainSOM()' wird weiter unten in ihrer Implementierung dargestellt.
class SOMTraining
{ public: // public methods interface
// constructor
SOMTraining( const SOMParameters& params, const vector<DataPage*>& data );
// Training of the SOM */ bool trainSOMO;
// write the SOM, i.e. the neuron coordinates and weights, into a csv data file bool writeCSVFileO const;
private: // private methods
// fiil the array pivNeighborhood with topological neighborhood infos, using // the current values of nbNeuronsX, nbNeuronsY, and invMaxNeighborDist. bool initializeNeighborhood();
// choose initial neuron values for each of the normalized fields void initiaiizeSOMNetworkO;
// increase the number of neurons by inserting new neurons between the
// existing neurons */ bool expandSOMNetwork();
private: // private member variables
// const references to the external objects used in the constructor const DataDescription& ivDescr; const vector<DataPage*>& ivData; const SOMParameters& ivParams;
// properties of the training data size_t ivNbRecords; size_t ivNbNumFIds; // numeric fields size_t ivNbBinFIds; // binary (boolean) fields size_t ivNbNomFIds; // nominal fields
// in SOM, each nominal field is expanded into n normalized fields, where n is
// the number of the field's valid field values. 'ivNbNormalizedFIds' is the // total number of normalized fields, including the numeric and binary fields size_t ivNbNormalizedFIds;
// array of length 'ivNbNomFIds1 which returns for each nominal field the
// number of valid values of this field size_t* pivNbNomValues;
// current number of neurons (in X and Y direction and total) size_t ivNbNeuronsX; size_t ivNbNeuronsY; size_t ivNbNeurons;
// number of neural network expansion steps to be performed. Each expansion
// step increases ivNbNeurons[X/Y] to 2*ivNbNeurons[X,Y]+l. size_t ivNbExpansions;
// the total number of neurons after the last expansion step size_t ivMaxNbNeurons;
// array of length ivMaxNbNeurons * ivNbNormalizedFIds, contains the properties // of all normalized fields (outer index) in all neurons (inner index) double* pivSOM;
// topology of the neural net: which neuron is neighbor of which one struct NeighborDistance: public pair<size_t,double> { NeighborDistance( size_t neu=0, double dist=O )
: pair<size_t,double>(neu,dist) {}; bool operator<( const NeighborDistance& d )
{ return second«±second | | second==d.second && first<d.first; }
}; typedef pair<size_t,NeighborDistance*> NeighborData; NeighborData* pivNeighborhood;
// array of length ivNbNeurons in which the distances between the current
// data record and each of the neurons are calculated double* pivDistances;
};
Die Methode SOMTraining::initializeNeighborhood():
bool SOMTraining: :initializeNeighborhood()
{ // some initializations size_t maxDist = (size_t)( ivParams.getMaxNeighborDist() ); size_t maxDistSqr = (size_t)( ivParams.getMaxNeighborDist() * ivParams.getMaxNeighborDist() ); size_t iNeuron = 0; vector<NeighborDistance> tmpNeigh( (2*maxDist+l) * (2*maxDist+l) );
// allocate the array of neighborhood infos (length ivNbNeurons) pivNeighborhood = new NeighborData[ ivNbNeurons ]; if (IpivNeighborhood) return false; // error: out of memory;
// loop over all neuron y coordinates and determine all neighbored
// y-coordinates within maxDist for( size_t iY=0; iY<ivNbNeuronsY; iY++ ) { size_t yMin = (iY >= maxDist) ? iY-maxDist : 0; size_t yMax = (iY+maxDist < ivNbNeuronsY) ? iY+maxDist : ivNbNeuronsY-1;
// loop over all neuron x coordinates and determine all neighbored
// x-coordinates within maxDist ... for( size_t iX=0; iX<ivNbNeuronsX; iX++, iNeuron++ ) { size_t xMin = (iX >= maxDist) ? iX-maxDist : 0; size_t xMax = (iX+maxDist < ivNbNeuronsX) ? iX+maxDist : ivNbNeuronsX-1;
// determine the number of neighbors withiπ maxDist and their neuron
// indexes and störe them in the preliminary array tmpNeighbors.
NeighborData& neighbors = pivNeighborhoodfiNeuron]; neighbors.first = 0;
5 for( size_t nX=xMin; nX<=xMax; nX++ ) { for( size_t nY=yMin; nY<=yMax; nY++ ) { sizej distSqr = (iX-nX)*(iX-nX) + (iY-nY)*(iY-nY); if (distSqr > maxDistSqr | | distSqr == 0) continue; tmpNeigh[neighbors.first].fιrst = nX + ivNbNeuronsX*nY; lo tmpNeigh[neighbors.first].second = sqrt(distSqr); neighbors.fi rst++; } }
i5 // sort the temporary array of neighbors by ascending distance sort( tmpNeigh.beginO, tmpNeigh.begin()+neighbors.first );
// copy the neighbors from the preliminary to the final neighbor array neighbors.second = new NeighborDistance[ neighbors.first ]; 2o if (Ineighbors.second) { ok = false; break; } // out of memory for( size_t i=0; kneighbors.first; i++ ) neighbors.second[i] = tmpNeighp];
} // end of loop over neuron coordinate x 25 } // end of loop over neuron coordinate y
return true; }
30 Die Funktion inverseErf( double c ) ist eine hier nicht in der kompletten Implementierung aufgelistete Funktion. Diese Funktion berechnet die inverse Gaußsche Fehlerfunktion erf^c). Das heißt, die Funktion berechnet die Intervallbreite w zu einer gegebenen Konfi- denz c (mit 0 < c < 1), so dass das Integral der Gaußschen Glockenkurvenfunktion G(x) = l/(V(2π)s) e(x"m)2/(2s2) über dem Intervall [m-w*s, m+w*s] genau den Wert c annimmt.
35 Spezielle Werte von erf^c) sind:
• erf 1CO-O) = 0.0
• erf^O.βδS) = erf^Wahrscheinlichkeit für x, in [m-Ts, m+l-s] zu liegen) = 1.0
• erf ^0.95S) = erf ^Wahrscheinlichkeit für x, in [m-2-s, m+2-sj zu liegen) = 2.0
• erf 1CO.^?) = erf ^Wahrscheinlichkeit für x, in [m-3-s, m+3-s] zu liegen) = 3.0
• erf 1Cc^l) -» co
Die Methode SOMTraining::initializeSOMNetwork():
void SOMTraining: :initializeSOMNetwork()
{ srand( 0 ); const double rand_denom = 1. / RAND_MAX; double* p = pivSOM; valarray<double> sumNom( 1., ivNbNeurons );
// cases 1+2: numeric and binary fields for( size_t fld=O; fld<ivNbNumFlds+ivNbBinFlds; fld++ ) { for( size_t n=0; n<ivNbNeurons; n++, p++ ) { int rndjnt = rand(); double rnd = rand_denom * rndjnt;
// case 1: numeric field: choose a normally distributed random number // with mean = 0 and stdDev = 0.25. This makes sure that the maximum expected // difference between two values, 4 * sigma, is 1.0 and equals the maximum // difference between binary and nominal field values. if (fld<ivNbNumFlds) { const GaussianCompress& stats = ivDescr.getNumericStats( fld ); *P = ((rndjnt&l)? 0.25: -0.25) * inverseErf(rnd); }
// case 2: binary field: choose a random probability of the 'yes' value eise if (fld<ivNbNumFlds+ivNbBinFlds) {
*p = rnd; } } }
// case 3: nominal fields: choose equally distributed random probabilities
// between 0 and 2/nbValues for all values, observing the requirement that // all values' probabilities must sum up to 1. for( size_t fld=O; fld<ivNbNomFlds; fld++ ) { size_t i = 0; for( ; i+l<pivNbNomValues[fld]; i++ ) { for( size_t n=0; n<ivNbNeurons; n++, p++ ) { int rndjnt = rand(); double rnd = rand_denom * rndjnt;
*p = rand_denom * rand() * 2. * sumNom[n] / (pivNbNomValues[fld] - i); sumNom[n] -= *p;
} } for( size_t n=0; n<ivNbNeurons; n++, p++ ) {
*p = sumNom[n]; sumNom[n] = 1.;
} } }
Die Methode SOMTraining::expandSOMNetwork() :
bool SOMTraining: :expandSOMNetwork()
{ // deallocate the existing neighborhood info of the old, small network if (pivNeighborhood) { for( size_t i=0; i<ivNbNeurons; i++ ) { NeighborData& p = pivNeighborhood[i]; delete[] p.second; } delete pivNeighborhood; }
// expand the network by inserting new neurons around each existing neuron at // half the old neuron distance both in x and in y direction size_t nbNeuronsXOld = ivNbNeuronsX; size_t nbNeuronsYOId = ivNbNeuronsY;
size_t nbNeuronsOld = ivNbNeurons; ivNbNeuronsX = 2*ivNbNeuronsX + 1; ivNbNeuronsY = 2*ivNbNeuronsY + 1; ivNbNeurons = ivNbNeuronsX * ivNbNeuronsY;
// Update the neighborhood info if (initializeNeighborhood() != true) { return false; // an error occurred, e.g. out of memory }
// create a new neural net of double neuron density. The newly added neurons' // properties are linear interpolations of the two nearest existing neurons' // properties.
// First, we copy the existing neurons' properties into the larger net.
// Note: we have to run through all neuron indexes and fields in inverse // direction, otherwise we would overwrite data which is needed later. for( int fld=ivNbNormalizedFlds-l; fld>=0; fld~ ) { for( int iY=nbNeuronsYOId-l; iY>=0; iY~ ) { for( int iX=nbNeuronsXOId-l; iX>=0; iX- ) { size_t iOld = iX + nbNeuronsXOId * iY; size_t iNew = 2*iX+l + ivNbNeuronsX * (2*iY+l); pivSOM[iNew+fld*ivNbNeurons] = pivSOM[iOld+fld*nbNeuronsOld]; } }
}
// At this stage, the existing neurons are situated at odd x and y coordinates.
// Now we have to calculate the inserted neurons' properties, i.e. the // properties of the neurons with at least one even coordinate.
// We statt with the neurons with odd y and even x coordinate. These neurons have
// existing neurons as positions (x-l,y) and (x+l,y) whose properties we can
// interpolate. double* const pStop = pivSOM + ivNbNormalizedFlds*ivNbNeurons; double* const pBinStart = pivSOM + ivNbNumFlds*ivNbNeurons; double* const pNomStart = pivSOM + (ivNbNumFlds+ivNbBinFlds)*ivNbNeurons; for( int iY=l; iY<ivNbNeuronsY; iY+=2 ) {
double* p = pivSOM + iY*ivNbNeuronsX;
// special case: neurons with x==0.
// Here, we have no left neighbour, therefore we extrapolate the properties of // the first and second neighbors to the right, i.e. (x+l,y) and (x+3,y):
// properties(x,y) := 1.5*properties(x+l,y) - 0.5*properties(x+3,y). // First, we calculate the numeric field values for( ; p<pBinStart; p+=ivNbNeurons )
*p = 1.5 * *(p+l) - 0.5 * *(p+3); // For binary and nominal fields, the above formula for properties(x,y) has to
// be modified because we have the additional constraints that all results must // be between 0 and 1, whereas the above formula might produce values <0 or >1. // The correction for binary fields is simple: replace <0 by 0 and >1 by 1. for( ; p<pNomStart; p+=ivNbNeurons ) { *p = 1.5 * *(p+l) - 0.5 * *(p+3); if (*p > 1.) *p = 1.; eise if (*p < 0.) *p = 0;
}
// The correction for nominal fields is more difficult because we have the // additional constraint that all values' probabilities must sum up to 1. // Therefore, we Start with the extrapolation formulas d := 0.5 and
// properties(x,y) := (1 + d)*properties(x+l,y) - d*properties(x+3,y) // and reduce d until all generated probabilities are in the ränge [0..I]. // The constraint that all probabilities sum up to 1 is always fulfilled. size_t nomFId = 0; double d = 0.5; for( size_t nomVal=0; p<pStop; ) {
// calculate the current extrapolated value. Check whether it is in [0,1] *p = (1. + d) * *(p+l) - d * *(p+3); if (*p > l. | | *p < 0.) { // find the maximum d which leaves all probabilities in the valid ränge d = (((*p>l)? 1.: 0.) - *(p+l)) / (*(p+l) + *(p+3)); // ... and statt recalculating all probabilities for the current field // by resetting pointer p to the first value of the current field. p -= nomVal * ivNbNeurons; nomVal = 0;
} eise {
nomVal++; p+=ivNbNeurons; if (nomVal == pivNbNomValues[nomFld]) { // we are done with all values of the current field. Go to next field. nomFld++; nomVal = 0; d = 0.5; }
> }
// general case: neurons with 0 < x < nbNeuronsX-1. // Here, we have two neighbors between which we can interpolate:
// properties(x,y) := 0.5*properties(x-l,y) + 0.5*properties(x+l,y). // Therefore, the Situation <0 or >1 can never occur for binary or nominal // fields, and no special treatment for these fields is needed. for( int iX=2; iX+KivNbNeuronsX; iX+=2 ) for( p=pivSOM+iX+iY*ivNbNeuronsX; p<pStop; p+=ivNbNeurons ) {
*p = 0.5 * (*(p-l) + *(p+l)); }
// special case: neurons with x==nbNeuronsX-l // This case is treated in füll analogy to the special case x==0. p = pivSOM + (iY+l)*ivNbNeuronsX - 1; for( ; p<pBinStart; p+=ivNbNeurons ) // numeric fields
*p = 1.5 * *(p-l) - 0.5 * *(p-3); for( ; p<pNomStart; p+=ivNbNeurons ) { // binary fields *p = 1.5 * *(p-l) - 0.5 * *(p-3); if (*p > 1.) *p = 1.; eise if (*p < 0.) *p = 0;
} nomFId = 0; d = 0.5; for( size_t nomVal =0; p<pStop; ) { // nominal field
*p = (1. + d) * *(p-l) - d * *(p-3); if (*p > l. | | *p < 0.) {
// find the maximum d which leaves all probabilities in the valid ränge d = (((*p>l)? 1.: 0.) - *(p-l)) / (*(p-l) + *(p-3)); // ... and recalculate all probabilities for the current field p -= nomVal * ivNbNeurons; nomVal = 0;
} eise { nomVal++; p+=ivNbNeurons; if (nomVal == pivNbNomValues[nomFld]) { nomFld++; nomVal = 0; d = 0.5;
} } } } // end of loop over neurons' y coordinates
// Next, we calculate new neurons' properties for even y coordinates. // At this stage, valid neuron properties exist for both even and odd x and for // odd y coordinates. That means, for each pair (x,y), y even, we can calculate: // properties(x,y) := 0.5*properties(x,y-l) + 0.5*properties(x,y+l), resp. // properties(x,0) := 1.5*properties(x,l) - 0.5*properties(x,3), resp.
// properties(x,nY-l) :- 1.5*properties(x,nY-2) - 0.5*properties(x,nY-4).
// special case: neurons with y == 0 for( int iX=0; iX<ivl\lblMeuronsX; iX++ ) { double* p = pivSOM + iX; for( ; p<pBinStart; p+=ivNbNeurons ) // numeric fields
*p = 1.5 * *(p+ivNbNeuronsX) - 0.5 * *(p+3*ivNbNeuronsX); for( ; p<pNomStart; p+=ivNbNeurons ) { // binary fields
*p = 1.5 * *(p+ivNbNeuronsX) - 0.5 * *(p+3*ivNbNeuronsX); if (*p > 1.) *p = 1.; eise if (*p < 0.) *p = 0;
} size_t nomFId = 0; double d = 0.5; for( size_t nomVal=0; p<pStop; ) { // nominal fields *p = (1. + d) * *(p+ivNbNeuronsX) - d * *(p+3*ivNbl\leuronsX); if (*p > 1. I l *p < 0.) {
// find the maximum d which leaves all probabilities in the valid ränge d = (((*p>l)? 1.: 0.) - *(p+ivNbNeuronsX)) /
(*(p+ivNbNeuronsX) + *(p+3*ivNbNeuronsX)); // ... and recalculate all probabilities for the current field p -= nomVal * ivNbNeurons; nomVal = 0;
} eise { nomVal++; p+=ivNbNeurons; if (nomVal == pivNbNomValues[nomFld]) { nomFld++; nomVal = 0; d = 0.5;
} } } }
// general case: neurons with 0 < y < nbNeuronsY for( int iY=2; iY+l<ivNbNeuronsY; iY+=2 ) for( int iX=0; iX<ivNbNeuronsX; iX++ ) { for( double* p=pivSOM+iX+iY*ivNbNeuronsX; p<pStop; p+=ivNbNeurons ) *p = 0.5 * (*(p+ivNbNeuronsX) + *(p-ivNbNeuronsX));
}
// special case: neurons with y == nbNeuronsY for( int iX=0; iX<ivNbNeuronsX; iX++ ) { double* p = pivSOM + iX + ivl\lbNeuronsX*(ivNbNeuronsY-l); for( ; p<pBinStart; p+=ivl\lbNeurons ) // numeric fields
*p = 1.5 * *(p-ivNbNeuronsX) - 0.5 * *(p-3*ivNbNeuronsX); for( ; p<pNomStart; p+=ivNbNeurons ) { // binary fields
*p = 1.5 * *(p-ivNbNeuronsX) - 0.5 * *(p-3*ivNbNeuronsX); if (*p > 1.) *p = 1.; eise if (*p < 0.) *p = 0;
} size_t nomFId = 0; double d = 0.5; for( size_t nomVal=0; p<pStop; ) { // nominal fields *p = (1. + d) * *(p-ivl\lbNeuronsX) - d * *(p-3*ivNbNeuronsX); if (*p > 1. I l *p < 0.) {
// find the maximum d which leaves all probabilities in the valid ränge d = (((*p>l)? 1.: 0.) - *(p-ivNbNeuronsX)) /
(*(p-ivNbNeuronsX) + *(p-3*ivNbNeuronsX)); // ... and recalculate all probabilities for the current field p -= nomVal * ivNbNeurons; nomVal = 0;
} eise { nomVal++; p+=ivNbNeurons; if (nomVal == pivNbNomValues[nomFld]) { nomFld++; nomVal = 0; d = 0.5;
} } } }
return true; }
Die Methode shrinkSOMNetwork():
bool SOMTraining::shrinkSOMNetwork()
{
// deallocate the existing neighborhood info of the old, larger network if (pivNeighborhood) { for( size_t i=0; i<ivNbNeuroπs; i++ ) {
NeighborData& p = pivNeighborhood[i]; delete[] p.second;
} delete pivNeighborhood; }
// by inserting new neurons around each existing neuron at // half the old neuron distance both in x and in y direction size_t nbNeuronsXOId = ivNbNeuronsX; size_t nbNeuronsOld = ivNbNeurons; ivNbNeuronsX = ivNbNeuronsX / 2; ivNbNeuronsY = ivNbNeuronsY / 2; ivNbNeurons = ivNbNeuronsX * ivNbNeuronsY;
// update the neighborhood info if (initializeNeighborhood() != true) { return false; // an error occurred, e.g. out of memory
}
// shrink the network. The neurons with odd x and y coordinates will remain, // all other neurons will be dropped, but before dropping them, their properties // are merged into the properties of the remaining neurons. for( size_t fld = 0; fld < ivNbNormalizedFIds; fld++ ) { for( size_t iY = 0; iY < ϊvNbNeuronsY; iY++ ) { for( size_t iX = 0; iX < ivNbNeuronsX; iX++ ) { size_t iOId = (2*iX+l) + nbNeuronsXOId * (2*iY + 1); size_t iNew = iX + ivNbNeuronsX * iY; pivSOM[iNew + fld*ivNbNeurons] = 0.25 * pivSOM[iOld + fld*nbNeuronsOld] + 0.125 * pivSOM[iOld-l + fld*nbNeuronsOld] + 0.125 * pivSOM[iOld+l + fld*nbNeuronsOld] + 0.125 * pivSOM[iOld-nbNeuronsXOId + fld*nbNeuronsOld] + 0.125 * pivSOM[iOld+nbNeuronsXOId + fld*nbNeuronsOld]
+ 0.0625 * pivSOM[iOld-l-nbNeuronsXOId + fld*nbNeuronsOld] + 0.0625 * pivSOM[iOld-l+nbNeuronsXOId + fld*nbNeuronsOld] + 0.0625 * pivSOM[iOld+l-nbNeuronsXOId + fld*nbNeuronsOld] + 0.0625 * pivSOM[iOld+l+nbNeuronsXOId + fld*nbNeuronsOld]; }
} } }
Eine Methode SOMTraining::writeCSVFile() gibt das trainierte Netz (Neuronenpositionen und Neuronengewichte) in eine Datei, hier eine csv-Datei, aus.
bool SOMTraining::writeCSVFile() const
{ // open the output file string fileName( ivParams.getModelName() ); fileName += ".csv"; ofstream fϊle( fileName.c_str() ); if (!file.is_open()) { cout << "Unable to open output file "'<<fileName<<""'<<endl; return false; }
// write the header row of the Output fiie file << "neuron_x" << "," << "neuron_y"; for( size_t i=0; i<ivNbNumFlds; i++ ) file << "," << ivDescr.getFieldName( i ); for( size_t i=0; i<ivNbBinFlds; i++ ) { file << "," << ivDescr.getFieldName( i+ivNbNumFIds ); file << "_" << ivDescr.getFirstBinaryValue( i );
} for( size_t i=0; i<ivNbNomFlds; i++ ) { for( size_t j=0; j<pivNbNomValues[i]; j++ ) { file << "," << ivDescr.getFieldName( i+ivNbNumFIds+ivNbBinFIds ); file << "_" << ivDescr.getNominalValue( i, j ); } } file << endl;
// write the data rows of the output file size_t i = 0; for( size_t x=0; x<ivNbNeuronsX; x++ ) { for( size_t y=0; y<ivNbNeuronsY; y++, i++ ) { file << x << "," << y; double* pNeur = pivSOM + i; for( size_t j=0; j<ivNbNormalizedFlds; j++, pNeur+=ivNbNeurons ) { double value = *pNeur; if (j < ivNbNumFIds) { // denormalize numeric values const GaussianCompress& stats = ivDescr.getNumericStats(j); value = value * 4. * stats. getStdDev() + stats.getMean();
} file << "," << value;
} file << endl;
} }
return file.good(); }
Derzeit verfügbare Softwarepakete, zum Beispiel DataCockpit 1.03 verwenden zum Training eines SOM-Netzwerkes das folgende Prinzip:
Für alle Iterationsschritte (Zum Beispiel ca. 200) {
Für alle Trainingsdatensätze d (ca. 104-109) { minimum_Distanz := 10300 bestes_Neuron :- -1 Für alle Neuronen n (ca. 1000) { Distanz_d_n := 0
Gewicht_n := Gewichtfn]
Für alle (normalisierten) Merkmale m (zum Beispiel ca. 5 - 300) { Wenn d[m] vorhanden und valide ist { Distanz_d_n += (Gewicht_n[m] - d[m])2 * Wertigkeitsfaktor[m] }
}
Wenn Distanz_d_n < minimum_Distanz { minimum_Distanz := Distanz_d_n bestes_Neuron := n }
}
Verschiebe die Gewichte von bestes_Neuron und seinen Nachbarn in Richtung d
}
Verändere Lernrate und maximalen Nachbarschaftsradius }
Hierbei werden in jedem Iterationsschritt und für jeden Datensatz alle Neuronen des Netzes durchlaufen, und für jedes Neuron wird der euklidische Abstand zwischen seinen Gewichten und den normalisierten Merkmalswerten des Datensatzes berechnet. Anschließend wird das Neuron mit dem niedrigsten Abstand bestimmt, und dieses und seine Nachbarneuronen werden in ihren Gewichten in Richtung auf den Datensatz angepasst. Die kursiv oder fett geschriebenen Teile des Pseudocodes sind die rechenzeit-kritischen Teile; in der fett geschriebenen, innersten Schleife über alle normalisierten Merkmale wird die meiste Rechenzeit verbraucht.
Ein Korrekturfaktor Wertigkeitsfaktor[m] kommt dadurch zustande, dass aus einem ursprünglichen nominalen Merkmal mehrere normalisierte Merkmale entstanden sind, nämlich
so viele wie das ursprüngliche Merkmal valide (gültige) Werte hat. Wenn diese Zahl N ist, dann müssen alle N normalisierten Merkmale, die aus diesem Merkmal entstanden, mit dem Wertigkeitsfaktor l/N multipliziert werden. Normalisierte numerische und binäre Merkmale haben dagegen den Wertigkeitsfaktor 1. Diese Maßnahme dient dazu, den Gesamteinfluss eines nominalen Feldes auf das SOM-Netz durch die Normalisierung nicht größer werden zu lassen als den eines numerischen oder binären Merkmals.
Beispielsweise enthält ein Datenbestand aus Produktionsdaten 10 Millionen Datensätze und jeder Datensatz enthält 10 numerische, 10 binäre und 10 nominale Merkmale. Die nominalen Merkmale enthalten jeweils 10 verschiedene Werte. Dieser Datenbestand benötigt etwa (107*(10*8 +10*1 + 10*16) Bytes Speicherplatz, also etwa 2.5 Gigabytes. In der Praxis bekannte Beispiele für solche Daten sind Produktionsdaten in der Maschinenbau-, Chemie-, Automobil-, Zuliefererindustrie, oder Kundendaten in Einzelhandels-, Finanz- oder Versiche¬ rungsunternehmen.
Aus den 30 ursprünglichen Merkmalen werden nach einer Normalisierung 120 normalisierte Merkmale. Der in vorgestellte Algorithmus benötigt daher bei 200 Iterationen und 30*40 Neuronen etwa die folgende Anzahl an Elementaroperationen (=CPU-Taktzyklen):
200 * 107 * 30*40 * (120*15 + 20) = 4.37 * 1015.
Der Ausdruck in Klammern kommt dadurch zustande, dass innerhalb der 120 Mal durchlaufenen Schleife über alle normalisierten Merkmale 11 Elementaroperationen durchgeführt wurden: 4 Berechnungen (+,-,*,*), 4 Feldzugriffe ([ ]), ein Vergleich (<), eine Verzweigung (Wenn), sowie eine Schleifenzählererhöhung und Abbruchprüfung. In der Schleife wird auf drei verschiedene Felder sowie eine skalare Größe zugegriffen: Gewicht_n[m], d[m], Wertig- keitsfaktor[m] und Distanz_d_n. Auf modernen CPUs ist der Zugriff auf einen Feldwert [m] innerhalb eines Taktzyklus möglich. Den Feldwert unvorbereitet aus dem Speicher zu lesen kostet jedoch mindestens 4-5 Taktzyklen. Im vorliegenden Fall eignen sich zwar die drei Feldzugriffe prinzipiell für das Pipelining-Verfahren, da der Zugriff auf jedes Feld streng sequentiell erfolgt (d.h. im Schleifendurchlauf m an der Stelle m), aber mit dem gleichzeitigen Bereithalten von 3 verschiedenen Feldwerten aus drei verschiedenen langen Feldern, plus einem Skalar, dürfte die Pipelining-Heuristik überfordert sein, so dass für mindestens einen der Feldzugriffe eine Zugriffsdauer von 4-5 Taktzyklen angenommen werden muss. Zusammen mit den 11 Elementaroperationen führt das zu dem Wert von 15 Taktzyklen pro Schleifendurchlauf. Die zusätzlichen 20 Taktzyklen in dem Ausdruck in Klammern werden für
den Programmcode außerhalb der innersten Schleife und für das Initialisieren und Starten der Schleife selbst, welches ca. 10 Taktzyklen erfordert, benötigt.
Auf einen PC mit einer 2 GHz Intel CPU werden ca. 25 Tage benötigt, um 4.37*1015 Taktzyk- len abzuarbeiten.
Eine Rechenzeit von 25 Tagen ist für die Praxis inakzeptabel lang, so dass mit dem gegenwärtigen Stand der Technik die SOM-Analyse in vielen wichtigen Einsatzgebiete selbst für nur moderat große Datenquellen nicht eingesetzt werden kann.
Einige Faktoren wie die Anzahl Datensätze und die Anzahl Neuronen sind vorgegeben und daher keiner Optimierung zugänglich. Auch die Tatsache, dass 4 Schleifen benötigt werden (über die Iterationen, die Datensätze, die Neuronen, die Merkmale) kann nicht verändert werden. Beschleunigungsansätze müssen daher bei den anderen Termen in dem Produkt 200 * 107 * 30*40 * (120*15 + 20) ansetzen:
Der Faktor ,Für Konvergenz benötigte Anzahl an Iterationen' (200) kann durch den Mehrgitter-Ansatz reduziert werden. Eine Reduktion des Rechenaufwandes um etwa einen Faktor 4 ist durch die vorgeschlagene Technik möglich.
Bei dem Faktor ,Anzahl normalisierter Merkmale' (120) kann die innerste Schleife durch eine Umstellung der Anweisungen nur über die 30 ursprünglichen Merkmale laufen.
Bei dem Faktor ,Taktzyklen pro innerstem Schleifendurchlauf' (15) kann eine Algorithmus- und Speicherstruktur-Umstellung helfen, durch die zum Beispiel die Wenn-Abfrage aus der innersten Schleife herausgeholt werden kann. Außerdem kann in der innersten Schleife die Anzahl an verschiedenen durchlaufenen Feldern von 3 auf 2 reduziert werden.
Bei dem Faktor ,Aufwand rund um die innerste Schleife und Schleifeninitialisierung' (20) ist es vorteilhaft, die Schleifenreihenfolge zu vertauschen. Bei Daten mit wenigen Merkmalen und/oder vorwiegend numerisch und/oder binären Merkmalen kann die innerste Schleife oft nur aus 5-10 Durchläufen bestehen. Das ist numerisch ungünstig. Hoher Rechendurchsatz wird durch lange innerste Schleifen ermöglicht.
Folgend wird eine neue Technik vorgestellt, welche gegenüber den bekannten Ansätzen eine erhebliche Verbesserung darstellt und die den Mehrgitteransatz umsetzt. Die vorgeschlagene Vorgehensweise ist nachstehend in Pseudocode-Form wiedergegeben. Die Zeilenummem
sind Kommentarmarken und dienen der Erläuterung mit Bezug auf den vorstehend erläuter¬ ten bekannten Ansatz. Die Funktionsweise ist schematisch in den Rg. 3 und 4 gezeigt.
Für alle Iterationsschritte i (z.B. ca. 200) { Für alle Trainingsdatensätze d (ca. 104- 109) { minimum_Distanz := 10300 bestes_Neuron := -1
(1) Setze alle Distanz_zu_d[n] := 0
(2) Für alle numerischen Merkmale m (z.B. ca. 0 - 50) { (3) Wenn d[m] vorhanden und valide ist {
(4) Wert := d[m]
(5) Gewicht_m := Gewicht[m]
(6) Für alle Neuronen n (z.B. ca. 1000) {
(7) Distanz_zu_d[n] += (Gewicht_m[n] - Wert)2 }
} } Für alle binären Merkmale m (z.B. ca. 0 - 50) {
Wenn d[m] vorhanden und valide ist { (8) Wert := (d[m]==0)
Gewicht_m := Gewicht[m]
Für alle Neuronen n (ca. 1000) {
Distanz_zu_d[n] ■+-= (Gewicht_m[n] - Wert)2 } }
}
(9) Für alle nominalen Merkmale m (z.B. ca. 0 - 50) { Wenn d[m] vorhanden und valide ist {
(10) Gewichtjn := Gewicht[normalisierter_Index[m]+d[m]] Für alle Neuronen n (z.B. ca. 1000) {
(11) Distanz_zu_d[n] += (Gewicht_m[n] - I)2 }
} } (12) Für alle Neuronen n (z.B. ca. 1000) {
Wenn Distanz_zu_d[n] < minimum_Distanz { minimumJDistanz := Distanz_zu_d[n]
bestes_Neuron := n
} } Verschiebe die Gewichte von bestes_Neuron und seinen Nachbarn in Richtung d }
}
Zu (1): Die beiden innersten Schleifen sind gegenüber der bisherigen Technik vertauscht. Daher wird keine skalare Variable benötigt, um die über alle Merkmale aufsummierte quadra- tische Distanz zwischen Neuron und Datensatz zu speichern, sondern lediglich ein Feld von der Länge ,Anzahl Neuronen'.
Zu (2): Die Schleife läuft über die Anzahl numerischer Merkmale. Durch einen Schleifentausch gegenüber der bisherigen Technik können die drei Merkmalstypen getrennt behandelt werden, was die Performanz der neuen Technik gegenüber der bisherigen zusätzlich steigert. Zudem verringert dies die Komplexität der innersten Schleife.
Zu (3): In der bisherigen Technik resultiert die Prüfung auf Vorhandensein des Merkmals¬ werts im Datensatz in einer Verlangsamung, welche in der innersten Schleife mindestens 3 Taktzyklen benötigt. In der neuen Technik ist dieselbe Prüfung erheblich beschleunigt, da sie außerhalb einer inneren Schleife stattfindet und eventuell das Durchlaufen dieser Schleife unnötig macht.
Zu (4): Der aktuelle Merkmalswert aus dem Datensatz ist eine Invariante der innersten Schleife. Der Feldzugriff kann einmalig außerhalb der Schleife erfolgen. Dies Taktzyklen ein und reduziert gleichzeitig die Anzahl an verschiedenen Feldern, mit denen in der innersten Schleife gearbeitet werden muss, von 3 auf 2. Die Pipelining-Heuristik der CPU kann so die Feldwerte der verbleibenden beiden Felder schneller bestimmen.
Zu (5): Ein Aspekt der neuen Technik ist, dass sie auf Grund einer umorganisierten Speicherstruktur der Neuronengewichte effizienter funktioniert. In bisherigen SOM-Implementierun- gen werden alle Gewichte eines Neurons in einem zusammenhängenden Speicherbereich gespeichert. Bei der bisherigen Technik werden die Merkmalsgewichte eines einzelnen Neurons im Speicher „verstreut" gespeichert und die Gewichte aller Neuronen für ein bestimmtes Merkmal in einem zusammenhängenden Speicherbereich abgelegt.
Zu (6), (7): Die innerste Schleife ist für eine effiziente Zahlenverarbeitung durch eine CPU umgestaltet. Wegen der Länge der Schleife (≥IOOO, unabhängig von der Datencharakteris¬ tik) fällt der Schleifen-Overhead und der gesamte Rechenaufwand außerhalb der Schleife nicht ins Gewicht. Außerdem ist der Code innerhalb der Schleife sehr einfach gehalten und besteht nur aus einem parallelen sequentiellen Durchlaufen zweier Gleitkommazahl-Felder und wenigen elementaren Gleitkommaoperationen auf den Feldwerten. Auf Vektorrechner- Architekturen lässt sich die Schleife gut vektorisieren, wodurch die gesamte Schleife in einigen wenigen Taktzyklen anstelle von mehreren tausend Taktzyklen abgearbeitet werden kann.
Zu (8): Die Behandlung der binären Merkmalswerte innerhalb der Schleife über alle binäre Merkmale impliziert, dass ein ,true'- oder 1-Wert in einer komprimierten Speicherung den Wertindex 0 hat, ein ,false' oder O-Wert den Index 1. Ein Neuronengewicht von x (0 < x < 1) besagt, dass das Neuron mit der Wahrscheinlichkeit x den ,true'- oder 1-Wert erwartet.
Zu (9), (10): Die Schleife läuft über die Anzahl ursprünglicher nominaler Merkmale. Da aber die Matrix der Neuronengewichte immer noch mehrere verschiedene Gewichte pro nominalem Merkmal enthält, muss der zum aktuellen Merkmalswert-Index d[m] des ursprünglichen Merkmals m gehörende normalisierte Merkmalsindex in (10) unter Benutzung eines Hilfsfel- des berechnet werden. Dieses Hilfsfeld, normalisierter_Index[m], kann die Position (Index) desjenigen normalisierten Merkmals bestimmen, welches dem Wert mit Index 0 des ursprünglichen Merkmals m entspricht. Wenn zu diesem Index noch d[m] dazugezählt wird, wird der korrekte Index des zum ursprünglichen Merkmal m und dessen Wertindex d[m] gehörenden normalisierten Merkmals erzeugt.
Zu (11): Durch die Vorarbeiten in (9) und (10) ist auch im Fall nominaler Merkmale die innerste Schleife über alle Neuronen einfach. Insbesondere wird kein korrigierender merkmalabhängiger Wertigkeitsfaktor mehr benötigt, da nur noch ein Schleifendurchlauf und Distanzbeitrag für jedes nominale Merkmal berechnet werden.
Zu (12): Das Ermitteln des Neurons mit der geringsten quadratischen Distanz zum Datensatz geschieht jetzt an einer Stelle außerhalb zweier innerer Schleifen, so dass der hierfür aufgebrachte Rechenaufwand nicht ins Gewicht fällt.
Bei den Distanzen, die berechnet und verwendet werden, muss es sich nicht um quadratische Distanzen handeln. Wichtig ist vielmehr nur, dass die Distanzen jeweils die Eigenschaften einer Metrik aufweisen. Eine Metrik ist dabei eine mathematische Funktion, die je zwei
Elementen eines Raums einen nicht negativen reellen Wert zuordnet, der als Abstand der beiden Elemente voneinander aufgefasst werden kann.
Im Vergleich zu den Betrachtungen für die bisherige Technik und unter der Annahme, dass 5% aller Merkmalswerte in den Daten nicht vorhanden oder ungültig sind, ergibt sich mit dem neuen Algorithmus folgende Abschätzung über die benötigten Taktzyklen:
200 * 107 * (3*9.5*(15+1200*6) + 200 + 1200*4 + 10000) = 4.4 * 1014.
Es folgt eine Erläuterung einiger Bestandteile der Berechnung:
• 200: Geschätzter Aufwand für das Null-Setzen des Feldes der Distanzen zum Beispiel durch schnelles Speicherblock-Kopieren sowie für alle anderen Nicht-Schleifen- Operationen, die einmal pro Datensatz ausgeführt werden.
• 1200 * 4: die abschließende Schleife (12) zur Ermittlung des besten Neurons. « 3: Die drei Merkmalstypen werden nacheinander abgearbeitet.
• 9.5: Je Merkmalstyp gibt es 10 Merkmale zu durchlaufen. Falls der Merkmalswert im aktuellen Datensatz fehlt - was mit 5% Wahrscheinlichkeit der Fall ist, muss der weitere Code nicht durchlaufen werden - siehe Bedingung (3).
• 15: Aufwand für das Vorbereiten und Starten der Schleifen über alle Neuronen ((4), (5), (6)).
• 1200 * 6: 6 Taktzyklen pro Schleifendurchlauf über die Neuronen (7): zwei sequentielle Feldzugriffe, drei elementare Rechenoperationen (+,-,*), eine Schleifenzählerin- krementierung.
• 10000: Abschätzung des benötigten Aufwands, um das Gewinnerneuron und dessen Nachbarn in ihren Gewichten zu modifizieren.
Damit sinkt die Rechenzeit für das Beispiel von ca. 25 Tagen auf ca. 2.5 Tage, also um etwa den Faktor 10.
Wenn zusätzlich der Mehrgitteransatz angewendet wird und diese technische Maßnahme einen Beschleunigungsfaktor von 4 ergibt, dann beträgt die Rechenzeit es obigen Beispiels ca. 14 Stunden. Das heißt, eine Analyse kann z.B. in einer Nacht durchgeführt werden und das Ergebnis in den Produktionsprozess bereits am nächsten Tag zurückgekoppelt werden.
Im obigen Beispiel hat die Tatsache, dass ein Drittel der Merkmale nominale Merkmale sind, die relativ viele unterschiedliche Werte haben, einen Faktor 4 zum Gesamtfaktor von 40 beigetragen. Für rein numerische und binäre Daten sinkt der Geschwindigkeitsgewinn zu-
nächst auf etwa den Faktor 10-12. Allerdings tritt dann oft ein weiterer technischer Effekt auf: die Schleife über alle normalisierten Merkmale ist bei Fehlen nominaler Merkmale recht kurz: etwa 5 - 20 Durchläufe. Dadurch nimmt der im bisherigen Vorgehen vorhandene Overhead rund um die innerste Schleife einen beträchtlichen Teil der Rechenzeit ein. Dieser Negativeffekt der kurzen inneren Schleife entfällt in der neuen Technik, so dass der Geschwindigkeitsgewinn insgesamt auf den Faktor 15 oder mehr steigt.
Somit ist durch die vorgeschlagene Technik eine Verbesserung um einen Faktor zwischen etwa 15 (Daten ohne nominale Merkmale) und bis zu 100 (rein nominale Daten mit vielen Werten pro Merkmal) zu erwarten. Die bisher durchgeführten Vergleichsrechnungen zwischen der hier vorgeschlagenen Technik und dem Stand der Technik in Form der Implementierung DataCockpit 1.03 bestätigen dies.
Die neue Technik kann in einer Pseudocode-Formulierung auch wie folgt dargestellt werden: ■ Definiere m := Anzahl der Merkmale (Spalten) in den Trainingsdaten ■ Für alle Änderungen der Neuronen-Netz-Größe o Definiere n := Anzahl der Neuronen im Netz (z.B. ca. 100 - 1000) o Schreibe die Neuronengewichte in ein 2-dimensionales nummerisches Feld namens ,Gewichte' mit den Dimensionen m und n. o Für alle Iterationssch ritte (z.B. ca. 10 - 200)
■ Für alle Datensätze oder eine Teilmenge der Trainingsdatensätze (z.B. 10000 - alle)
• Definiere ds := Index (Position) des aktuellen Datensatzes in der gewählten Teilmenge • Allokiere eine Feldvariable »Distanzen' der Länge n, welche die Distanzen zwischen dem aktuellen Datensatz und sämtlichen Neuronen enthalten wird. Setze alle Anfangswerte in diesem Feld auf 0.
• Definiere eine nummerische Variable minimum_Distanz und setze sie auf einen sehr großen Wert, zum Beispiel den maximal dar- stellbaren Fließkomma-Zahlenwert.
• Für alle Merkmale m, die im Datensatz ds einen validen Wert w[m] haben o Für alle Neuronen n Addiere die Distanz zwischen Gewich- te[m][n] und w[m] zum Feldelement Distanzen[n]. • Für alle Neuronen n, für die gilt: Distanzen[n] < minimum_Distanz o minimum_Distanz := Distanzenfn] o bestes Neuron : = n
• Für alle Merkmale m, die im Datensatz ds einen validen Wert w[m] haben o Verschiebe die Gewichte, Gewichte[m][bestes_Neuron], und die Gewichte bestimmter Nachbar-Neuronen von bes- tes_Neuron, Gewichte[m][nachbar_Neuron] in Richtung auf w[m].
Die im Ablaufplan erste Schleife über alle Merkmale kann durch mehrere Schleifen ersetzt werden, von denen jede nur über einen Teil aller Merkmale iteriert. So kann zum Beispiel eine Schleife über numerische, eine Schleife über binäre und/oder eine Schleife über textuel- Ie Merkmale iterieren. Dies kann vorteilhaft sein, weil die Art der Distanzberechnung zwischen Gewichte[m][n] und w[m] für verschiedene Merkmalstypen verschieden definiert sein kann und man sich zeitraubende Verzweigungen (Wenn-Dann-Abfragen) in der innersten, rechenzeitintensivsten, Schleife sparen kann.
Das Feld der Neuronengewichte, im obigen Ablaufplan ,Gewichte' genannt, kann insbesondere so implementiert sein, dass es aus m lückenlosen Folgen von je n numerischen Feldzellen besteht. Dies ist besonders effizient, weil alle Rechenzeitintensiven inneren Schleifen, die auf Gewichte[m][n] zugreifen, dann über eine lückenlose Folge von Speicheradressen iterieren. Diese Zugriffsweise ist für modere CPUs mit Pipelining-Architektur optimal. Unter Umständen können so sogar mehrere Feldzugriffe in einem CPU-Taktzyklus durchgeführt werden (,Vek- torisierung'). Auch die Distanzen können durch Anwendung dieses Prinzips besonders gut durch mindestens einen auf einer Pipelining-Architektur basierenden Rechner berechnet werden.
Die Distanzberechnung zwischen den Neurongewichten und den Merkmalsausprägungen des aktuellen Trainingsdatensatzes kann zum Beispiel über die minimale quadratische Distanz (euklidische Distanz) erfolgen. Es können aber auch beliebige andere Distanzmaße zur Anwendung kommen.
Die Trainingsdaten könnten vor dem Eintritt in obigen Ablaufplan komprimiert und indiziert worden sein, um schneller auf sie zugreifen zu können. Eine solche Vorverarbeitung kann zum Beispiel textuelle Werte durch ganzzahlige Wert-Indizes ersetzen oder Fließkomma- Werte in diskrete Intervalle diskretisieren.
Die vorgestellte Speicher- und Kontrollflussorganisation wird in der Beispielimplementierung, zusammen mit dem Mehrgitteransatz, in der Methode ,trainSOM()' der bereits vorgestellten Klasse ,SOMTraining' implementiert.
Die Methode SOMTraining::trainSOM():
bool SOMTraining::trainSOM()
{ DataRecord record( ivDescr.getNbOfNumericFields(), ivDescr.getNbOfCategoricalFields() ); bool ok = true;
// loop over all neural network expansion Steps while (ivNbNeurons <= ivMaxNbNeurons && ok == true) {
// loop over all iterations for a fixed network size size_t iteration = 0; double maxNeighborDist = ivParams.getMaxNeighborDist(); if (maxNeighborDist < 1.) maxNeighborDist - 2.1; double invMaxNeighborDist = 1. / maxNeighborDist; double learningRate = ivParams.getLearningRate(); double changelnlteration = DBL_MAX;
while (changelnlteration > 0.01*ivNbRecords && iteration < 50 &&. ok) { iteration++; changelnlteration = 0.;
// loop over all training data records vector<DataPage*>:: constjterator pagelt = ivData.begin(); (*pageIt)->initRetrievalMechanism(); for( size_t iRec=0; iRec<ivNbRecords; iRec++ ) { rc = (*pageIt)->retrieveNextDataRecord( record ); if (!ok) { pagelt++; if (pagelt == ivData.end()) { if (iRec+1 < ivNbRecords) ok = false; eise ok = true; }
else {
(*pageIt)->initRetrievalMechanism(); ok = (*pageIt)->retrieveNextDataRecord( record );
} if (!ok) break;
}
// clean up the squared distances buffer double* const pDistStop = pivDistances + ivNbNeurons; for( double* pDist=pivDistances; pDist<pDistStop; pDist++ ) *pDist = 0.;
// calculate squared distances between record and neurons for all numeric // fields double* pNeur = pivSOM; for( size_t JFId=O; iFld<ivNbNumFlds; iFld++ ) { double value = record.getNormalizedNumericValue( iFId ); if (value == DBL_MAX) pNeur += ivNbNeurons; eise for( double* pDist=pivDistances; pDist<pDistStop; pDist++, pNeur++ ) { double dist = value - *pNeur; *pDist += dist*dist; } >
// calculate squared distances between record and neurons for all binary
// fields for( size_t IFId=O; iFld<ivNbBinFlds; iFld++ ) { double value = 1. - record.getCategoricalIndex( iFId ); if (value < -0.01) pNeur += ivNbNeurons; eise for( double* pDist=pivDistances; pDist<pDistStop; pDist++, pNeur++ ) { double dist = value - *pNeur; *pDist += dist * dist;
} }
// calculate squared distances between record and neurons for all nominal // fields for( size_t SFId=O; iFld<ivNbNomFlds; iFld++ ) { unsigned char index = record. getCategoricalIndex( ivNbBinFIds+iFld ); if (index != 255) { pNeur += ivNbNeurons * index; for( double* pDist=pivDistances; pDist<pDistStop; pDist++, pNeur++ ) { double dist = 1. - *pl\leur; *pDist += dist * dist;
} pNeur -= ivNbNeurons * (index + 1);
} pNeur += ivNbNeurons * pivNbNomValues[iFld]; }
// find the neuron which mininizes the squared distance double minDistSqr = pivDistances[0]; double* pMinDist = pivDistances; for( double* pDist=pivDistances+l; pDist<pDistStop; pDist++ ) if (*pDist < minDistSqr) { minDistSqr = *pDist; pMinDist = pDist;
} size_t iBest = pMinDist - pivDistances;
// move the winning neuron towards the record pNeur = pivSOM + iBest; for( size_t i=0; kivNbNumFIds; i++, pNeur+=ivNbNeurons ) { double normVal = record. getNormalizedNumericValue(i); if (normVal != DBLJMAX) { normVal -= *pNeur; normVal *= learningRate; *pNeur += normVal; changelnlteration += fabs(normVal);
} }
for( size_t i=0; i<ivNbBinFlds; i++, pNeur+=ivNbNeurons ) if (record.getCategoricallndex(i) != 255) { double change =learningRate*(l.-record.getCategoricalIndex(i)-*pNeur); *pNeur += change; changelnlteration += fabs(change);
} for( size_t i=0; kivNbNomFIds; i++ ) { unsigned char index = record.getCategoricalIndex( i + ivNbBinFIds ); if (index != 255) { double* pNeurValue = pNeur + ivNbNeurons*index; for( size_t j=0; j<pivNbNomValues[i]; j++, pNeur+=ivNbNeurons ) { *pNeur += learningRate * ((pNeur==pNeurValue?l.:O.)-*pNeur);
} changelnlteration += fabs(leamingRate * (l.-*pNeurValue)); }
}
// move the winning neuron's neighbors towards the record size_t nbNeighbors = pivNeighborhood[iBest].first; const NeighborDistance* const neighbors = pivNeighborhood[iBest].second; for( size_t n=0; n<nbNeighbors; n++ ) { const pair<size_t,double>& neigh = neighbors[n]; if (neigh.second >= maxNeighborDist) break; double factor = (1. - neigh.second * invMaxNeighborDist); factor *= factor * learningRate; pNeur = pivSOM + neigh.first;
for( size_t i=0; i<ivNbNumFlds; i++, pNeur+=ivNbNeurons ) { double normVal = record. getNormalizedNumericValue(i); if (normVal != DBL_MAX) {
*pl\leur += factor * (normVal - *pNeur); } } for( size_t i=0; i<ivNbBinFlds; i++, pNeur+=ivNbNeurons ) if (record. getCategoricalIndex(i) != 255) {
*pNeur += factor * (record. getCategoricallndex(i) - *pNeur); }
for( size_t i=0; i<ivNbNomFlds; i++ ) { unsigned char index = record.getCategoricalIndex( i + ivNbBinFIds ); if (index != 255) { double* pNeurValue = pNeur + ivNbNeurons*index; for( size_t j=0; j<pivNbNomVa!ues[i]; j++, pNeur+=ivNbNeurons ) {
*pNeur += factor * ((pNeur==pNeurVa!ue?l.:O.) - *pNeur);
} }
} } // end of loop over all neighbors
} // end of loop over all training data records
// This sample code reduces both the leaming rate and the maximum distance // (up to which neighbors of the winning neuron are modified) by a factor of
// 0.88 after each 8-th iteration.
// Many other modification schemes are possible here, e.g. smaller adjustment // after each Single iteration, or different adjustment factors for leaming // rate and maximum neighbour distance. if ((iteration % 8) == 0) { maxNeighborDist *= 0.88; invMaxNeighborDist /= 0.88; learningRate *= 0.88;
} } // end of loop over all iterations for a fixed network size
// expand the neural network.
// This sample code implements the most elementary multi grid approach: we
// Start with the smallest, coarsest grid and perform a series of expansion // steps until the largest, finest grid of neurons is reached. No grid
// shrinking steps are performed.
// In the field of multi grid approaches for solving large linear equation // Systems, studies have shown that intermixing grid expansion and grid // shrinking steps can increase overall convergence speed of the method. // The same might hold for the multi grid approach to training SOM networks. if (ivNbNeurons < ivMaxNbNeurons && ok) { ok = expandSOMNetworkQ;
} eise break;
> // end of loop over all network expansion Steps return ok; }
Mit der vorgestellten Technik zur Datenvorbereitung und SOM-Analyse können mit preiswerter, allgemeinverfügbarer Hardware in vertretbarer Zeit SOM-Analysen für Daten bis zur Größe von etwa 10 - 20 GB erstellt werden, selbst wenn diese Rechner lediglich über einen CPU- Kern verfügen.
Darüber hinaus erlaubt die vorgestellte Technik auch, den SOM-Analyseprozess zu paralleli- sieren, um noch bessere Antwortzeiten zu erreichen. Für die Parallelisierung der vorgestellten Technik eignen sich besonders die folgenden Ansätze: • Vektorrechner
• SMP-Rechner (Shared-Memory-Parallelism)
• MPP-Rechner (Massiv Parallele Shared-Nothing Architekturen)
• Vernetzte Rechner-Cluster aus mehreren Analyse-Rechnern (10, 20)
Vektorrechner sind Computer mit einer CPU7 welche aber über mehrere Vektorregister für z.B. 128, 256 oder 512 Fließkommazahlen verfügen (z.B. die SX-Supercomputer-Reihe von NEC). Mit Hilfe dieser Vektorregister können elementare numerische Operationen zwischen Zahlenfeldern (Vektoren) in einem Taktzyklus erledigt werden. Die vorgestellte SOM- Trainingstechnik ist so gestaltet, dass numerische Berechnungen abgearbeitet werden müs- sen, bei denen der Hauptteil der Rechenarbeit im Innern langer Schleifen in Form von elementaren Rechenoperationen zwischen numerischen Feldern und skalaren Werten anfällt und bei denen die Berechnungen eines Schleifendurchlaufs nicht von den Ergebnissen vorheriger Schleifendurchläufe abhängen. Daher kann die vorgestellte Technik praktisch unverändert auf Vektorrechnern eingesetzt werden und wird dort einen beinahe linearen Geschwindigkeitszuwachs (um einen Faktor 100 - 200) liefern.
Auch moderne ,MultiCore'-Rechnerarchitekturen wie Intel Mehrkern-Prozessoren oder CeII- Prozessoren von IBM können bereits durch Parallelverarbeitung einen Geschwindigkeitszuwachs erzielen.
Auf einem Parallelrechner mit verteiltem Speicher bzw. auf einem Netzwerk von Rechnern kann die vorgestellte SOM-Trainingstechnik mit Hilfe eines Message Passing Interfaces wie zum Beispiel MPI I oder MPI II paraileüsiert werden.
s Die folgende Modifikation ist eine Möglichkeit, dies zu erreichen. Die Codezeilen mit [Mas¬ ter:] am Anfang werden nur vom Master- oder Koordinator-Prozess durchlaufen, alle anderen Codezeilen von allen parallelen Prozessen;
[Master:] Teile die komprimierten Trainingsdaten in viele Datenverarbeitungsobjekte auf 10 [Master:] Versende die Datenverarbeitungsobjekte an unterschiedliche Siaves
Lies die vom Master versandten Datenverarbeitungsobjekte in den Arbeitsspeicher [Master:] konstruiere Initialkonfiguration des SOM-Netzes und Trainings-Parameter Lies die vom Master versandte Initialkonfiguration und Parameter Für alle SOM-Netzexpansionsschritte (ca. 4 - 10) ls Für alle Iterationsschritte (ca. 20 - 200) {
Solange noch Trainingsdatensätze vorhanden sind (ca. 10 - 1000 Wiederh.) { [Master:] sende SOM-Netz (Gewichte), Lernrate, Radius, DatensätzeProSchritt Lies die vom Master versandten Informationen Für DatensätzeProSchritt Datensätze d (ca. 105 - 106) {
20
(berechne Distanzen zwischen d und allen Neuronen wie im seriellen Fall) (finde bestes_Neuron wie im seriellen Fall)
Speichere Index des Gewinnerneurons in der Liste Gewinnend]
K }
Sende Liste Gewinner an Master
(1) [Master:] Sammle die Listen Gewinner von allen Siaves ein [Master:] Für alle Slave-Prozesse (ca. 4 - 128) {
[Master:] Für alle Einträge der Gewinner-Liste n (ca. 105 - 10δ) { 30 [Master;] Lies den zugehörigen Trainingsdatensatz
[Master:] Modifiziere die Neuronengewichte (Gewinnerneuron + Nachbarn) [Master:] >
(2) [Master:] > }
35 }
}
Hier erfolgt das Anpassen des SOM-Netzes nicht mehr synchron (d.h. sofort nach dem Ermit¬ teln des Gewinnerneurons für einen Datensatz), sondern asynchron. Die einzelnen Slave- Prozesse ermitteln die Gewinner-Neuronen für eine bestimmte Anzahl an Datensätzen und melden die Identiftkatoren dieser Neuronen an den Master-Prozess zurück. Dieser sammeit die Gewinnerlisten aller Slave- Prozesse ein, führt dann alle Modifikationen der Neuronengewichte durch und sendet das neue SOM-Netz an die Slave-Prozesse zurück.
Um die Asynchronität nicht beliebig groß werden zu lassen, sieht die oben dargestellte Tech¬ nik vor, die Slave-Prozesse nicht ihren gesamten Datenbestand abarbeiten zu lassen, bevor sie die Gewinnerlisten an den Master-Prozess schicken, sondern immer nur eine bestimmte Anzahl Datensätze (z.B. 10000, 100000 oder 1 Million). Dadurch wird trotz der Asynchronität mehrmals innerhalb einer Iteration das jeweils neueste SOM-Netz an alle Prozesse kommuniziert und als Grundlage der weiteren Berechnungen genutzt.
Der Kommunikationsbedarf zwischen den einzelnen Prozessen ist dabei so gering, dass diese Art der Parallelisierung sogar auf einem Netzwerk von Computern funktioniert, welche nur über eine Intra- oder Internetverbindung von DSL-Qualität (6MBits/sec) verbunden sind.
Mit dem oben dargestellten Prozess ist für eine kleine Anzahl von parallelen Prozessen ein annähernd linearer Geschwindigkeitsgewinn mit der Prozess-Anzahl möglich. Bei einer größeren Anzahl paralleler Prozesse konvergiert der Geschwindigkeitsgewinn gegen einen konstanten Faktor zwischen 20 und 30.
Wenn diese Umitierung der Geschwindigkeitssteigerung von etwa 20-30 überwunden wer- den soll, kann der Algorithmus z.B. so modifiziert werden, dass die Slave-Prozesse schon auf Basis der nächsten Datensatz-Tranche arbeiten, während der Master-Prozess noch mit Hilfe der gesammelten Gewinnerlisten der vorherigen Tranche das SOM-Netz modifiziert, Dies bedeutet, dass die Slave-Prozesse immer auf dem zweitaktuellsten Netz anstelle des aktuellsten Netzes ihre Gewinnerneuronen bestimmen.
Es ist dabei klar, dass die Prozesse (Slave- und Master-Prozess) auf verschiedenen Analyse- Rechnern (10, 20) ausgeführt werden können, die z.B. durch ein Netzwerk oder andere Datenverbindung (14) miteinander verbunden sind. Dabei arbeitet mindestens ein Analyse- Rechner (10), der den mindestens einen Master-Prozess ausführt, mit mindestens einem weiteren Analyse-Rechner (20) zusammen, auf dem mindestens ein Slave-Prozess ausgeführt wird. Dies ist schematisch in Rg. 6 dargestellt.