TU-Chemnitz-Zwickau
Chemnitz, 4.2.97
Fakultät für Informatik
Lehrstuhl Datenverwaltungssysteme
Praktikum: Datenbanken und Neuronale Netze
Thema: Schreibfehler-tolerante SQL-Anfragen

Inhalt:
1. Spezifikation der funktionellen Anforderungen
1.1. Aufgabenstellung
1.2. Modelle funktioneller Anforderungen
1.2.1. Kontextdiagramm
1.2.2. Primäres Verhaltensmodell
2. Modul Neuronales Netz" (Falk Seifert)
2.1. Schnittstelle des Moduls Neuronales Netz" (snnsapp.h)
2.2. Nutzung des Moduls Neuronales Netz" im Normalbetrieb
2.2.1. Netz aufbauen: int NNCreate (char* name)
2.2.2. Netz trainieren: int NNTrain (char* name, int override)
2.2.3. Worterkennung mit dem trainierten Netz: int NNPropagate (char* name, char* word, float* activation)
2.3. Nutzung des Moduls Neuronales Netz" im Online"-Betrieb
2.4. Die C-Schnittstelle des SNNS-Kernels
2.5. Erläuterungen zum Fehlergenerator (psdoerr.c)
2.5.1. Die Arbeitsweise des Pseudo-Fehlergenerators (PseudoError())
2.5.2. Die Kodierung der Buchstaben (Tobias Neubert)
2.6. Verbesserungsmöglichkeiten
3. Interaktiver Editor (Tobias Neubert)
4. Datenbankanbindung (Tobias Neubert)
4.1. Statische Datenbankanfrage
4.2. Dynamische Datenbankanfrage
5. Der Parser (Frank Seifert)
1 Spezifikation der funktionellen Anforderungen
1.1 Aufgabenstellung
SQL-Anfragen an eine Datenbank unterliegen einer exakten Syntax. Diese Tatsache erfordert vom Anwender die genaue Kenntnis der Schreibweise von datenbankspezifischen Namen, die Relationen und Attribute bezeichnen. Weitere Voraussetzung ist das Beherrschen von SQL. Das Ziel ist es, den Anwender beim Finden korrekter Namen zu unterstützen.
Beispiel: Die Datenbank enthält die Relation Personal". Der Anwender stellt eine Anfrage, die statt des Relationennamens Personal" nun Personen" enthält. Gibt es keine Relation mit dem Namen Personen", so soll das zu entwerfende System in der Lage sein, einen entsprechend ähnlich geschriebenen Relationennamen vorzuschlagen. In diesem Falle wäre das Personal".
Der zweite Aufgabenbereich des Systems liegt in der Korrektur von Schreibfehlern. Das betrifft Schlüsselwörter sowie datenbankspezifische Namen. Jedoch sollte die Struktur der SQL-Anfrage, wie z.B. Klammerung und Anordnung der Schlüsselwörter, syntaktisch korrekt sein. Eine weitere Forderung ist die korrekte Schreibweise des Schlüsselwortes from".
Eine vom Nutzer entworfene SQL-Anfrage kann, bevor diese an die Datenbank gestellt wird, zur Untersuchung bzw. Korrektur an das zu entwerfende System gegeben werden.
Im Rahmen dieses Praktikums ist ein System zu realisieren, das mit einer Testdatenbank auf einem an der TU verfügbaren DBMS arbeitet. Die Erkennung und Korrektur von Schreibfehlern und ähnlichen Namen ist mit Hilfe eines neuronalen Netzes zu realisieren.
1.2 Modelle funktioneller Anforderungen 1.2.1 Kontextdiagramm
1.2.2 Primäres Verhaltensmodell

/* Fehlercodes des Moduls */
#define ERROR_PSEUDOERROR -1
#define ERROR_NET_EXISTS -2
#define ERROR_PATTERN_EXISTS -3
#define ERROR_NO_WORDS -4
#define ERROR_NO_NET -5
#define ERROR_NO_PATTERNS -6
#define ERROR_TOO_FEW_WORDS -7
#define ERROR_NO_VALIDSET -8
/* Parameter für die Generierung der Lern- und Testmuster */
#define NN_NOISE 8
#define NN_PATTERN_CYCLES 10
/* Parameter für das Lernen und Testen */
#define NN_LEARN_CYCLES 0
#define NN_LEARN_RATE 0.2
#define NN_DELTA_MAX 0.1
#define NN_LEARN_ABORT 0.01
#define VALIDATION_DISTANCE 10
#define PROPAGATE_WEIGHTED_CHARS
/* Funktionen zum Normal-Betrieb: */
int NNCreate (char* name);
int NNTrain (char* name, int override);
int NNPropagate (char* name, char* word, float* activation);
/* Funktion zum Online-Betrieb: Erzeugen von Netz und Pattern, trainiern, propagieren
und löschen nacheinander */
int GetBestMatchOnline (char* name, char* word, float *activation);
/* Ausgabe eines Fehlertextes entsprechend des Fehlercodes "error_code",
siehe ERROR_* - Konstanten */
void OnSNNSAppError (int error_code);
Das Ziel der drei Funktionen
NNCreate(), NNTrain() und NNPropagate() ist es, ein fehlerhaft geschriebenes Wort zu erkennen. Das geschieht, indem das fehlerhafte Wort einem Wort aus einer vorgegebenen Menge von Wörtern mit Hilfe eines Backpropagation-Netzes zugeordnet wird. Zu diesem Zweck sind die erwähnten Funktionen in der angegebenen Reihenfolge zu benutzten.NNCreate()
generiert ein neuronales Netz im Format des SNNS. An passender Stelle kann die Funktion NNTrain() aufgerufen werden, um das generierte Netz zu trainieren. Das Training kann länger dauern, da dieser Prozeß exponentielles Zeitverhalten besitzt. Die Traningsdauer ist abhängig von der Anzahl der zu lernenden Wörter und von der Länge des längsten zu lernenden Wortes.Soll ein fragliches/ fehlerhaftes Wort einem gelernten Wort der Liste zugeordnet werden, ist die Funktion
NNPropagate() anzuwenden. 2.2.1 Netz aufbauen: int NNCreate (char* name)Der Parameter name" dient als Basisname. Vor dem Aufruf dieser Funktion muß eine Datei mit dem Name name.wrd" existieren, welche mindestens 2 durch newline" getrennte Wörter enthält. (Diese Wörter werden dann später vom Netz gelernt.) Gibt es keine Datei name.wrd", liefert die Funktion
NNCreate einen Fehlercode < 0. Eine verbale Beschreibung des Fehlers kann mit Hilfe der Funktion OnSNNSAppError()ausgegeben werden.Anschließend wird geprüft, ob schon ein neuronales Netz unter dem Name name.net" generiert wurde. Ist das der Fall, kehrt die Funktion mit Fehlercode zurück. Andernfalls wird
zu 1.: Die Größe des Netzes umfaßt die Anzahl der Neuronen in den 3 Schichten des Netzwerkes:
Anzahl der Neuronen der Input-Schicht = Anzahl der Buchstaben des
längsten Wortes in name.wrd" * Breite der Kodierung eines Buchstabens (6 Bits)
Anzahl der Neuronen der Output-Schicht = Anzahl der Wörter (Zeilen) in
name.wrd"
Anzahl der Neuronen der Hidden-Schicht = Mittelwert aus Anzahlen in der Input- und
Output-Schicht
Beispiel: Die Datei name.wrd" enthält folgende 3 Zeilen:
SELECT
FROM
WHERE
Dann hat das Netzwerk folgende Dimensionen:
Anzahl der Neuronen der Input-Schicht : 6 Buchstaben * 6 Bit => 36
Neuronen
Anzahl der Neuronen der Output-Schicht : 3 Wörter => 3 Neuronen
Anzahl der Neuronen der Hidden-Schicht : (36+3)/2=19.5 => 19 Neuronen
Der Parameter name" dient wiederum als Basisname. Voraussetzung für die korrekte Abarbeitung der Funktion ist das Vorhandensein der Datei mit den zu lernenden Wörtern name.wrd" und das Vorhandensein der Datei, die die Beschreibung des zu trainierenden Netzes enthält name.net" (vorher von NNCreate() erzeugt ).
Existiert bereits eine entsprechende, von
NNTrain() erzeugte Lernmusterdatei name.pat" kann durch den Parameter override > 0 das Überschreiben der alten Datei veranlaßt werden. Ist override gleich 0 (also False) und existiert name.pat" wird diese Funktion abgebrochen und liefert einen entsprechenden Fehlercode.Kann die Abarbeitung der Funktion fortgesetzt werden, dann wird unter Zuhilfenahme des Pseudo-Fehlergenerators (
PseudoError() in Datei psdoerr.c) eine Datei name.pat" mit Lernmustern im .pat-Format des SNNS generiert. Diese Datei enthält mehrfach die Wörter aus name.wrd" in kodierter Form, welche zusätzlich noch zufällig mit Fehlern versehen wurden. Anhand der zufällig generierten Fehler soll das Netz für mehrere fehlerhafte Ausprägungen des originalen Wortes angelernt werden. Ähnlich wie die Lernmuster werden noch sogenannte Validation- bzw. Testmuster generiert und in name.val" abgelegt. Diese Generation der Testmuster erfolgt genau wie die Generation der Lernmuster nur mit dem Unterschied, daß die Testmuster im ASCII-Format und nicht im .pat-Format gespeichert werden.Nachdem nun das Neuronale Netz name.net" in den SNNS-Kernel geladen und initialisiert wurde, kann nun das eigentliche Lernen beginnen. Die Lernmuster werden als Mustersatz 0 und die Testmuster als Mustersatz 1 in den SNNS-Kernel geladen. Während der Trainingsphase werden mehrere Trainingszyklen ,unterbrochen von einigen Testzyklen, durchgeführt. Bei Bedarf kann zwischen beiden Mustersätzen - 0 und 1 - umgeschaltet werden. Während eines Trainingszyklus werden dem Netz alle Lernmuster einmal präsentiert. Nach einer gewissen Anzahl von Trainingszyklen (z.B. 10) empfiehlt es sich, die Testmuster für einen Testzyklus an das Netz anzulegen und für diese, dem Netz unbekannten Muster, den Gesamtfehler zu bestimmen. Ist ein solcher Fehler größer als der Fehler im vorhergehenden Testzyklus, heißt das, daß das Netz auf unbekannte Eingabemuster zunehmend schlechter reagiert. Obwohl der Fehler für die Trainingsmuster immer noch weiter abnehmen könnte, ist nun das Trainieren des Netzes zu beenden. Darüber hinaus gehende Trainingszyklen führen zum sogenannten Übertraining. Die Lernmuster werden zwar zunehmend besser erkannt, jedoch verliert das jetzt Netz zunehmend die Fähigkeit der Generalisierung und damit wird die Erkennung von unbekannten Mustern (den Testmustern) immer schlechter.

Nach erfolgreichem Training wird das Netz im gegenwärtigen Zustand in name.net" gespeichert. Vor dem Beenden der Funktion werden die Muster und das Netz aus dem Kernel entfernt. Der Rückgabewert 0 signalisiert den ordnungsgemäßen Ablauf des Trainings.
Mit folgenden Konstanten kann die Lernphase gesteuert werden:
NN_NOISE
gibt an, wieviel Fehler in den Lern- und Testmustern zufällig generiert werden sollen. Z.B: wird mit einem Wert von 10 in Blöcken von jeweils 10 Buchstaben jeweils ein Fehler generiert. Die Alternative, für die einzelnen Buchstaben mit einer Wahrscheinlichkeit von 0.1 einen Fehler zu generieren, wurde verworfen. Die mit dieser Methode generierten Fehler waren nicht gleichmäßig genug verteilt.NN_PATTERN_CYCLES
gibt an, wie oft die zu lernenden Wörter aus name.wrd" in den Dateien der Lern- und Testmuster name.pat" und name.val" zu wiederholen bzw. variieren sind.NN_LEARN_CYCLES
enthält die Anzahl der Lernzyklen, die beim Training des neuronalen Netzes durchzuführen sind. Bei Werten kleiner als 1 wird das Training erst abgebrochen, bis der Wert NN_LEARN_ABORT unterschritten wurde oder der Fehler für die Testmuster (wie oben beschrieben) steigt.NN_LEARN_RATE
gibt die Lernrate an. Ein typischer Wert liegt bei 0.2.NN_DELTA_MAX
ist ebenfalls ein Parameter für das Lernen. Typischer Wert 0.1VALIDATION_DISTANCE
gibt an, aller wieviel Lernschritte ein Testzyklus durchzuführen ist. Nach einem Testzyklus kann geprüft werden, ob das Lernen aufgrund einer Verschlechterung bei der Erkennung von unbekannten Wörtern abgebrochen werden muß.Die Anwendung des trainierten Netzes wird in dieser Funktion realisiert. der Parameter name" ist hier wieder der Basisname für die Dateien name.wrd", name.net", name.pat" und name.val". Fehlen die Dateien name.net" oder name.pat" meldet sich der SNNS-Kernel mit einer Fehlermeldung und das Programm wird beendet. Das Vorhandensein dieser Dateien sollte also sichergestellt sein. Das neuronale Netz wird nun in den Simulator-Kernel geladen. Anschließend wird die Eingabedatei name.wrd" analysiert. Dabei wird eine Tabelle aufgestellt, die die Vorkommen der einzelnen Buchstaben enthält.
Der Parameter word" enthält das zu erkennende Wort. Dieses Wort wird nun in ein für das Netzwerk entsprechendes Format umgewandelt und an das Netzwerk angelegt.
Wurde das Symbol
PROPAGATE_WEIGHTED_CHARS definiert, so werden die Buchstaben des (fehlerhaften) Wortes word" dabei nach der Häufigkeit der Buchstaben in name.wrd" gewichtet.Beispiel: Die Datei name.wrd" enthält folgende 3 Zeilen:
SELECT
FROM
WHERE
Das Wort word" soll in diesem Beispiel WELECZ" sein. Die Buchstaben werden wie folgt gewichtet:
W" mit 1, weil W" in name.wrd" genau einmal vorkommt.
E" mit 1/4, weil E" in name.wrd" viermal vorkommt.
...
Z" mit 0, weil Z" nicht in name.wrd" vorkommt.
Eine Gewichtung der Buchstaben kann vorteilhaft sein. Ein W" ist beispielsweise ein sehr charakteristischer Buchstabe für das Wort WHERE". Wird ein W" angelegt, so sorgt die hohe Gewichtung von 1 dafür, daß diesem Buchstabe mehr Aufmerksamkeit geschenkt wird als z.B. dem E". Nachteilig dagegen können sich Tippfehler auswirken, die genau solche hoch gewichtete Buchstaben enthalten.
Das Symbol
PROPAGATE_WEIGHTED_CHARS erlaubt das Experimentieren mit und ohne Gewichtung.Nachdem nun das angelegte, und evtl. gewichtete, Wort durch das Netzwerk propagiert wurde, wird das Outputneuron mit der größten Aktivation durch Vergleich ermittelt. Dieses Neuron ist das Gewinnerneuron und symbolisiert das zugeordnete Wort aus name.wrd".
Das erste Outputneuron verkörpert das erste Wort aus name.wrd", das zweite Outputneuron verkörpert das zweite Wort aus name.wrd" usw.
Die Nummer der Zeile in name.wrd", welche durch das Gewinnerneuron indiziert wird, ist der Rückgabewert dieser Funktion. Als weiterer Rückgabeparameter liefert *activation" die Aktivation des Gewinnerneurons. Diese liegt zwischen 0 und 1.
Vor dem Ende der Funktion wird der Speicher des SNNS-Kernel wieder aufgeräumt.
2.3 Nutzung des Moduls Neuronales Netz" im Online"-BetriebHier werden die Funktionen NNCreate(), NNTrain() und NNPropagate() in einer Funktion zusammengefaßt:
int GetBestMatchOnline (char* name, char* word, float *activation);
Die Parameter dieser Funktion stimmen mit denen der Funktion
NNPropagate()überein. Da die Funktionen NNCreate(), NNTrain() und NNPropagate()von GetBestMatchOnline() nur der Reihe nach aufgerufen werden, wird hier auf die Beschreibung in den betreffenden Abschnitten verwiesen. 2.4 Die C-Schnittstelle des SNNS-KernelsDer Stuttgarter Neuronale Netze Simulator (SNNS) besteht aus 2 wesentlichen Teilen, dem Kernel und der graphischen Benutzeroberfläche. Der Kernel besitzt eine C-Schnittstelle, mit deren Hilfe eigene Applikationen Neuronale Netze auf dem Kernel simulieren können. Diese C-Schnittstelle ist im SNNS-Manual und im WWW knapp beschrieben. Um die vom Kernel bereitgestellten Funktionen (
krui_*) in der eigenen C-Applikation benutzen zu können, müssen die entsprechenden Headerfiles eingebunden und die 2 Kernelbibliotheken gelinkt werden (Reihenfolge beachten !). Für das Erzeugen von Backpropagation-Netzen sind im Kernel schon viele Parameter voreingestellt.Headerfiles:
Bibliotheken:
| infile: | Name der Eingabedatei, in der Regel eine .wrd"- Datei |
| outfile: | Name der Ausgabedatei, in der Regel eine .pat" oder .val"-Datei |
| noise: | Häufigkeit der zu generierenden Fehler, siehe Beschreibung von NN_NOISE |
| cycles: | gibt an, wie viele fehlerbehaftete Exemplare eines Wortes zu erzeugen sind, siehe Beschreibung von NN_PATTERN_CYCLES |
| asPattern: | ist dieser Parameter gleich 0, dann werden die erzeugten Muster im ASCII-Format gespeichert (verwendet für .val"-Dateien) andernfalls wird das Ausgabefile im .pat"-Format des SNNS geschrieben |
| return value: | -1 wenn Fehler aufgetreten ist, 0 sonst |
Zunächst wird versucht, die beiden Dateien zu öffnen - infile" zum Lesen und outfile" zum Schreiben. Anschließend wird ein Fehlerschema generiert (
GenerateErrorScheme()). Für dieses Fehlerschema wird. Speicher in der Größe von Länge der Datei infile" * Anzahl der Zyklen bei der Fehlergenerierung reserviert. Initialisiert wird dieser Speicherbereich mit T"'s (für TRUE). Anschließend wird in jedem Abschnitt, der noise" Zeichen lang ist, zufällig ein Fehler generiert und mit F" (für FALSE) gekennzeichnet. An diesen Stellen wird dann später das Lern- bzw. Testmuster zufällig verändert und somit werden Tippfehler simuliert. Abhängig davon, in welchem Format (ASCII oder SNNS-Pattern) die Ausgabedatei gespeichert werden soll, wird nun weiter verfahren.Für den Fall, daß die Ausgabe im SNNS-Pattern-Format zu erfolgen hat, wird zunächst die Eingabedatei hinsichtlich ihrer Zeilen- und Spaltenanzahl analysiert. Die gewonnenen Werte werden benutzt, um den Kopf der Ausgabedatei entsprechend SNNS-Pattern-Format zu schreiben. Eine Beschreibung der Syntax des SNNS-Pattern-Formates (und somit des Kopfes) ist im SNNS-Manual zu finden. Nun wird die Eingabedatei zeilenweise eingelesen und verarbeitet. Für eine Zeile der Eingabedatei, welche ein Wort repräsentiert, werden folgende Schritte durchgeführt:
1. Modifizieren des Wortes entsprechend des vorher generierten
Fehlerschemas (siehe unten).
2. Schreiben des modifizierten Wortes entsprechend SNNS-Pattern-Format in die
Ausgabedatei.
3. Wiederholen der Schritte 1. und 2. so oft, wie mittels cycles" vorgegeben wurde.
Im anderen Fall, der Ausgabe im ASCII-Format, wird prinzipiell wie eben beschrieben verfahren. Hier ist aber kein Kopf erforderlich, und die modifizierten Wörter werden im ASCII-Format (und nicht binär wie im SNNS-Pattern-Format) geschrieben. Das Modifizieren eines Wortes erfolgt genau an den Stellen, an denen das korrespondierende Fehlerschema ein F" enthält.
Beispiel: Zeile der Eingabedatei: SELECT
korrespondierendes Fehlerschema: TTFTTT
An der Stelle, an der das L" auftritt, wird ein Fehler generiert. Die Art des
zu generierenden Fehlers wird zufällig bestimmt. Alle Fehlerarten können mit gleicher
Wahrscheinlichkeit auftreten. An dieser Stelle könnte man durch statistische
Untersuchungen, die den Rahmen dieses Praktikums sprengen würden, eine bessere Abstimmung
finden. Folgende Fehlerarten werden unterschieden:
Für das obige Beispiel könnten diese Fehlerarten folgende zufällige" Fehler generieren:
Nachdem die Eingabedatei auf diese Art und Weise vollständig abgearbeitet wurde, werden die Dateien geschlossen und die Funktion beendet.
2.5.2 Die Kodierung der Buchstaben (Tobias Neubert)Die übliche Codierung der Buchstaben im ASCII-Format erwies sich für unsere Zwecke sehr ungünstig, da die Zeichen auf der Tastatur in einer anderen Reihenfolge angeordnet sind und Schreibfehler eher im Zusammenhang mit der Verwechslung benachbarter Buchstaben auftreten. Weiterhin können beim Schreiben mit zehn Fingern korrespondierende Buchstaben der einen mit denen der anderen Hand verwechselt werden. Das neuronale Netz kann falsche Buchstaben aber eher zuordnen, wenn deren Bitmuster dem des richtigen Buchstabens sehr nahe kommt. So entwickelten wir eine Codierung, die dies berücksichtigt und deren Code sich bei benachbarten sowie bei korrespondierenden Buchstaben jeweils nur an einer Bitstelle ändert.
Weitere Merkmale der Codierung:
Hier nun der Entwurf der Codierung:
| 5. + 4. Bitstelle | ||||||||||||||||||||||||||||||||||||
00 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
* 8 |
( 9 |
) 0 |
_ - |
+ = |
||||||||||||||||||||||||
01 |
Q |
W |
E |
R |
T |
Y |
U |
I |
O |
P |
||||||||||||||||||||||||||
11 |
A |
S |
D |
F |
G |
H |
J |
K |
L |
" |
||||||||||||||||||||||||||
10 |
Z |
X |
C |
V |
B |
N |
M |
< , |
> . |
/ |
||||||||||||||||||||||||||
3. 2. 1. Bitstelle |
0. Bitstelle |
|||
außen |
innen |
|||
X kleiner Finger |
001 |
011 |
Linke Hand: 0 |
|
| X Ringfinger | 111 |
Rechte Hand: 1 |
||
| X Mittelfinger | 110 |
|||
| X Zeigefinger links | 100 |
000 |
||
| X Zeigefinger rechts | 100 |
000 |
||
Damit ergeben sich folgende Codes (spezieller Code für Leertaste):
Zeichen |
Bitstellen 543210 |
Hexadezimal |
Zeichen |
Bitstellen 543210 |
Hexadezimal |
Zeichen |
Bitstellen 543210 |
Hexadezimal |
A |
110110 |
38 |
R |
011000 |
28 |
8 |
001001 |
09 |
B |
100000 |
20 |
S |
111110 |
3E |
9 |
001101 |
0D |
C |
101100 |
2C |
T |
010000 |
10 |
_ |
101010 |
2A |
D |
111100 |
3C |
U |
011001 |
19 |
- |
000111 |
07 |
E |
011100 |
1C |
V |
101000 |
28 |
" |
wie |
|
F |
111000 |
38 |
W |
011110 |
1E |
|
110011 |
33 |
G |
110000 |
30 |
X |
101110 |
2E |
, |
101101 |
2D |
H |
110001 |
31 |
Y |
010001 |
11 |
+ |
wie = |
|
I |
011101 |
1D |
Z |
100110 |
26 |
* |
wie 8 |
|
J |
111001 |
39 |
0 |
001111 |
0F |
/ |
100111 |
27 |
K |
111101 |
3D |
1 |
000010 |
02 |
< |
wie , |
|
L |
111111 |
3F |
2 |
000110 |
06 |
> |
wie . |
|
M |
101001 |
29 |
3 |
001110 |
0E |
= |
000011 |
03 |
N |
100001 |
21 |
4 |
001100 |
0C |
. |
101111 |
2F |
O |
0111111 |
1F |
5 |
001000 |
08 |
( |
wie 9 |
|
P |
010111 |
17 |
6 |
000000 |
00 |
) |
wie 0 |
|
Q |
010110 |
16 |
7 |
000001 |
01 |
Leertaste |
101010 |
2A |
2.6 Verbesserungsmöglichkeiten
Von uns wurden keinerlei statistische Untersuchungen unternommen, wie z.B. Analysen von Tippfehlern. Der Algorithmus zu Fehlergenerierung (für Lern- und Testmuster) beruht auf Erfahrungswerten bzw. Abschätzungen. Hier besteht die Möglichkeit, realistischere Lern- und Testmuster zu verwenden oder zu generieren. Eine bessere Erkennungsrate des neuronalen Netzes wäre dann zu erwarten.
Das Trainieren der neuronalen Netze ist sehr rechen- und speicherintensiv. Noch vertretbarer Aufwand ist das Erzeugen und Trainieren eines Netzes, welches z.B. 100 Wörter mit je maximal 20 Buchstaben unterscheiden kann. Hierfür wären dann 330 Neuronen und 24.200 Verbindungen zwischen den Neuronen erforderlich. Der Rechenaufwand und der Speicherplatzbedarf wächst exponentiell. Somit sind Netze, die 1000 Wörter und mehr unterscheiden sollen, auf einem Rechner kaum noch erzeugbar und noch schwerer trainierbar. Neben den Netzen wachsen ja auch noch die Lern- und Testmuster gewaltig an. Eine Möglichkeit, dieses exponentielle Zeit- und Speicherplatzverhalten aufzuknacken, wäre die Kaskadierung mehrerer kleiner neuronaler Netze anstelle eines einzigen, riesigen Netzes. Ein weiterer Vorteil ist, daß eine Veränderung in der Menge der zu lernenden bzw. erkennenden Wörter nur Auswirkungen auf das entsprechende Teilnetz hat. Dadurch beschränkt sich das Training auf das veränderte Teilnetz. Das Kriterium für die Auswahl der Wörter zu kleineren Gruppen könnte die Länge der Wörter sein. Grund: ausschlaggebend für die Anzahl der Neuronen in der Eingabeschicht ist die Länge des längsten Wortes der Gruppe. Also ist es günstig, etwa gleichlange Wörter zusammenzufassen. Nachteilig ist, daß alle nachfolgenden Kaskadierungsstufen (rot) online benutzt werden müssen, da die Gewinner der vorherigen Stufe erst ermittelt werden müssen.

Eine weitere Möglichkeit, die Ausmaße des Netzes zu reduzieren, ist eine entsprechende Kodierung der Ausgabeschicht. Bisher wurde jedem Wort ein Ausgabeneuron zugeordnet. Das Neuron mit der größten Aktivation entspricht dem erkannten Wort. Bei einem Netz für 100 Wörter sind also 100 Neuronen in der Ausgabeschicht. Eine binäre Kodierung mit z.B. 7 Neuronen würde 128 Wörter in der Ausgabeschicht unterscheiden können. Dazu muß ein Grenzwert festgelegt werden, der zwischen den Zuständen 0 und 1 für ein Neuron unterscheidet (z.B. Aktivation >= 0.5 ist 1, sonst 0). Es ist aber zu prüfen, ob die Wörter durch die drastische Einsparung an Neuronen noch hinreichend gut unterscheiden werden können.
3 Interaktiver Editor (Tobias Neubert)Quelldatei: QueryEdit.c
Der Editor ist ein einfaches Xlib- Programm. Er stellt folgende Funktionen zur Verfügung:
1
Die genannten Funktionen sind in dbconnect.sc.c implementiert und werden im Kapitel Statische Datenbankanfrage" behandelt.Erläuterung der Funktionalität:
Der Inhalt des Editorfensters wird in die Datei User.Query gespeichert. Anschließen wird der Parser aufgerufen. Bei Rückkehr des Parsers wird zunächst die Datei Query.Error auf eventuell anzuzeigende Fehlermeldungen untersucht. Anschließend wird das Ergebnis aus der Datei Revised.Query in den Editor geladen.
Der Inhalt des Editorfensters wird in die Datei Sent.Query gespeichert. Anschließend wird die Funktion SendQuery()2 aufgerufen, die eine dynamische Datenbankanfrage durchführt und das Ergebnis in der Datei Query.Result speichert. Diese wird anschließend in den Editor geladen
| User Query | Laden der Datei User.Query in den Editor. |
| Revised Query | Laden der Datei Revised.Query in den Editor. |
| Sent Query | Laden der Datei Sent.Query in den Editor. |
| Query Result | Laden der Datei Query.Result in den Editor. |
| New | Löschen des Editorinhaltes |
| Load | Laden einer Datei |
| Save As | Speichern des Editorinhaltes in eine Datei |
| Quit | Beenden des Programmes |
2
Die Funktion SendQuery() ist in dynqyery.sc.c implementiert und wird im Kapitel Dynamische Datenbankanfrage" behandelt.4 Datenbankanbindung (Tobias Neubert)
Die Datenbankanbindung erfolgt mittels ESQL an eine INGRES - Datenbank
(hier: AIR). Diese Datenbank ist zur Zeit fest verdrahtet", d. h. direkt im
Quelltext angegeben (ä dbconnect.sc.c, Funktion dbconnect()
). Der Zugriff auf die Datenbank erfolgt auf prinzipiell zwei verschiedene Arten, je
nachdem ob es sich um feste, zum Programmierzeitpunkt bekannte Anfragen (s. Statische
Datenbankanfrage) oder um Nutzeranfragen, die erst zur Laufzeit feststehen (ä Dynamische Datenbankanfrage) handelt.
In beiden Fällen wird der Quelltext von einem Precompiler überarbeitet, weshalb die
ESQL- Anweisungen nicht der üblichen C- Syntax entsprechen.
Headerdatei: dbconnect.h
Quelldatei: dbconnect.sc.c
Die statische Datenbankanfrage kommt zum Einsatz, um die Tabellen- und Spaltennamen der
Relationen zu ermitteln. Diese sind in den Systemtabellen von INGRES gespeichert. Folgende
Abfragen werden durchgeführt:
1.
Diese Abfrage ermittelt die Tabellennamen aller Basistabellen und Views, aber nicht der Index- Tabellen. Wir haben uns hier auf Systemtabellen und Tabellen mit dem Eigentümer ingres beschränkt, da zum einen auf andere Tabellen kein allgemeiner Zugriff möglich ist und zum anderen eine Erweiterung des Namensraumes für die Tabellennamen erforderlich wäre (verschiedene Nutzer können Tabellen mit gleichem Namen haben), die hier nicht implementiert ist.
2.
Diese Abfrage dient der Ermittlung aller Spaltennamen einer angegebenen Tabelle (Variable :tab_name). Wiederum gilt hier die Beschränkung auf Basistabellen und Views, sowie bzgl. des Owners auf ingres und Systemtabellen.
Beide Abfragen wurden in C- Funktionen gekapselt und stehen in verschiedenen Varianten zur Verfügung:
| int dbGetRelationNames(char**) | Liest die Tabellennamen und gibt sie in einem Array zurück. Der Speicher für das Array muß initialisiert sein, der für die jeweiligen char- Strings nicht. |
| int dbCreateRelationFile() | Erzeugt unter Benutzung der Funktion dbGetRelationNames() eine Datei mit allen Tabellennamen. Der Name der Datei wird mit der Konstante RELATIONS_FILE festgelegt. |
| int dbGetColumnNames(char* TableName, char** ColumnNames) | Liest die Spaltennamen der angegebenen Tabelle und gibt sie in einem Array zurück. Es gelten die selben Einschränkungen, wie bei dbGetRelationNames(). |
| Int dbCreateColumnFile(char* TableName) | Erzeugt unter Benutzung der Funktion dbGetColumnNames() eine Datei mit allen Spaltennamen der angegebenen Tabelle. Der Name der Datei ergibt sich aus dem Tabellennamen und einer Extension entsprechend der Konstante COLUMNS_FILE_EXT. Wurde ein NULL- Pointer anstatt eines Namens angegeben, wird eine Datei mit den Spaltennamen aller verfügbaren Relationen (Einschränkung s. o. - Erläuterung zur Abfrage 1) erzeugt. Der Name ergibt sich dann aus der Konstante COLUMNS_FILE. |
| Void dbCreateColumnFiles() | Erzeugt unter Benutzung der Funktionen dbGetRelationNames() und dbGetColumnNames() eine Datei für jede verfügbare Relation. |
Außerdem werden in dieser Datei noch die Funktionen dbconnect() und dbdisconnect() zum Öffnen und Schließen der Datenbank zur Verfügung gestellt.
4.2 Dynamische DatenbankanfrageHeaderdatei: dynquery.h
Quelldatei: dynquery.sc.c
Um die vom neuronalen Netz ausgewertete Anfrage schließlich auch an die Datenbank senden zu können, bedarf es der Möglichkeiten einer dynamischen Datenbankanfrage. Diese wird mit der Funktion SendQuery(char* inp, char* outp) bereitgestellt. Die Funktion erwartet als Parameter die Namen einer Datei mit der Anfrage (*inp) und einer Datei für das Resultat (*outp). Falls die mit *outp bezeichnete Datei schon existiert, wird sie überschrieben. Sie enthält nach Rückkehr der Funktion nicht nur das Anfrageergebnis sondern auch eventuelle Fehlermeldungen.
Ein zentrales Element für die Implementierung einer dynamischen SQL Anfrage ist das SQL Descriptor Area (SQLDA), eine Struktur, die folgende Elemente besitzt:
sqlaid |
Ein 8- Byte String mit Inhalt SQLDA " zur Identifizierung in Speicherblöcken, hat sonst keine Bedeutung |
| sqlabc | 4- Byte integer, gibt Größe des SQLDA an |
| sqln | 2- Byte integer, gibt die Anzahl der sqlvar - Elemente an, für die Speicher reserviert wurde (Anzahl der Spalten) |
| sqld | 2- Byte integer, gibt die Anzahl der zurück gelieferten Spalten an; falls größer als sqln, muß für sqlvar mehr Speicher reserviert werden |
| sqlvar | Ein Feld mit sqln Elementen, jeweils bestehend aus: |
| sqltype | Codierung der Domaine der Spalte |
| sqllen | Größe der Spalte |
| sqldata | Zeiger auf die eigentlichen Daten |
| sqlind | Zeiger auf eine Indikatorvariable |
| sqlname | Spaltenname |
(Ausführliche Informationen zum SQLDA sind zu finden in INGRES/SQL Reference Manual for the UNIX and VMS Operating System" ab Seite 5-1.)
Vorgehensweise bei der Auswertung des Statements:
Die Funktion des Parsers ist die Überprüfung der Syntax von SQL-Anfragen. Dazu testet der Parser mit Hilfe gegebener Grammatikregeln die Zulässigkeit von Schlüsselwörtern, Relations- und Attributnamen sowie diverser Sonderzeichen an ihrer Position.
Konnte eine Wort der Anfrage nicht identifiziert werden, wird es ans Netz geschickt (Wörter sind hier ASCII - Zeichenketten gefolgt von einer Integerzahl). Im Netz wird das ähnlichste an dieser Position zulässige Wort und dessen Wahrscheinlichkeit ermittelt. Falls diese Wahrscheinlichkeit einen bestimmten Grenzwert übersteigt, wird das nicht erkannte durch das korrigierte Wort ersetzt.
Es ist möglich, daß in einer Anfrage per Alias auf eine Relation zugegriffen wird. Der Alias wird aber erst später in der Anfrage mit einer Relation assoziiert. Um nun die Überprüfbarkeit und Korrigierbarkeit der Anfrage zu gewährleisten, muß der Parser vorausschauend jeden Alias einer Relation zuordnen. Deshalb ist es notwendig, daß der Parser die Zuordnungsschemata erkennt. Zu diesem Zweck benötigt er einen Fixpunkt, das Schlüsselwort FROM (denn hinter FROM erfolgt die Assoziation von Alias und Relation). Aus diesem Grunde wird gefordert, daß in jeder SELECT-Teilanfrage das Schlüsselwort FROM vorkommt und richtig geschrieben ist.
Weiterhin sollte die Klammerung richtig erfolgen, da deren Korrektur nur in eindeutigen Fällen (fehlerfrei) geschehen kann.
SQL - Grammatik in Backus - Naur - Form sql_query ::=
data_definition_statement
| basic_data_manipulation_statement
| transaction_processing_statement
data_definition_statement ::=
CREATE TABLE table ( table_def_item_list )
| CREATE VIEW view [ ( column_list ) ]
AS query-spec [ WITH CHECK OPTION ]
| GRANT { ALL [ PRIVILEGES ] | privilege-list }
ON table
TO { PUBLIC | user_list }
[ WITH GRANT OPTION ]
basic_data_manipulation_statement ::=
select_statement
| INSERT INTO table [ ( column-list ) ]
VALUES { ( insert_item_list ) | select_statement }
| DELETE FROM table [ WHERE search_condition ]
| UPDATE table SET assignment_list [ WHERE search_condition ]
transaction_processing_statement ::=
COMMIT WORK
| ROLLBACK WORK
query_expr ::= query_item [UNION [ ALL ] query_item
query_item ::= select_statement | ( query_expr )
subquery ::= ( select_statement )
select_statement ::=
SELECT [ ALL | DISTINCT ] { select_item_list | * }
FROM table_ref_list
[ WHERE search_condition ]
[ GROUP BY column_ref_list ]
[ HAVING search_condition ]
search_condition ::= search_item [ { AND | OR } search_item ]
search_item ::= [ NOT ] { search_test | ( search_condition ) }
search_test ::= comparison | between | like | null | set | quantified | existence
comparison ::= expr { = | <> | < | <= | > | >= } { expr | subquery }
between ::= expr [ NOT ] BETWEEN expr AND expr
like ::= column_ref [ NOT ] LIKE value [ ESCAPE value ]
null ::= column_ref IS [ NOT ] NULL
set ::= expr [ NOT ] IN { value_list | subquery }
quantified ::= expr { = | <> | < | <= | > | >= } [ ALL | ANY | SOME ] subquery
existence ::= EXISTS subquery
expr ::= expr_item [ { + | - | * | / } expr_item ]
expr_item ::= [ + | - ] { USER | literal | column_ref | function | ( expr ) }
function ::= COUNT ( * ) | {AVG | MAX | MIN | SUM | COUNT } {distinct_function | all_function}
distinct_function ::= ( DISTINCT column_ref )
all_function ::= ( [ ALL ] expr )
assignment_list ::= assignment [ , assignment_list ]
insert_item_list ::= insert_item [ , insert_item_list ]
select_item_list ::= expr [ , select_item_list ]
table_ref_list ::= table_ref [ , table_ref_list ]
column_ref_list ::= column_ref [ , column_ref_list ]
table_def_item_list ::= table_def_item [ , table_def_item_list ]
column_list ::= column [ , column_list ]
privilege_list ::= privilege [ , privilege_list ]
user_list ::= user [ , user_list ]
assignment ::= column = { expr | NULL }
insert_item ::= { USER | NULL | literal }
table_ref ::= table [ alias ]
column_ref ::= [ { table | alias } . ] column
table_def_item ::=
column_def
| { UNIQUE | PRIMARY KEY | FOREIGN KEY } ( column_list )
| REFERENCES table [ ( column_list )
column_def ::= column data_type [ DEFAULT { literal | USER | NULL } ]
privilege ::= SELECT | INSERT | DELETE | UPDATE [ ( column_list ) ]
table ::= Relation der Datenbank
column ::= Attribut der Datenbank
user ::= Name eines Datenbankanwenders (wird nicht korrigiert)
literal ::= eine Zahl oder eine Zeichenkette in Anführungszeichen
data_type ::= ein SQL Datentyp
alias ::= Alias für eine Relation