Leibniz Universität Hannover Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Diplomarbeit vorgelegt von Jens Neugebauer am Institut für Informationssysteme Fachgebiet Programmiersprachen und Übersetzer Prof. Dr. R. Parchmann in Zusammenarbeit mit der Medizinischen Hochschule Hannover Klinische Forschergruppe OE6711 Prof. Dr. B. Tümmler, C. Davenport Gesetzt mit LATEX 2 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Inhaltsverzeichnis 1. Einführung 5 2. Wichtige Begriffe und Definitionen 8 3. Anforderungen 3.1 Problemgrößen . . . . . . . . . . . . . . . . . 3.2 Ergebnis . . . . . . . . . . . . . . . . . . . . . 3.3 Programmiersprache . . . . . . . . . . . . . . 3.3.1 Messungen zum Speicherbedarf . . . . 3.3.2 Messungen zur Laufzeit (Datei lesen) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 11 12 12 13 16 4. Algorithmen 4.1 Brute Force / Single Pattern Matching . . . . . . . . . . . . 4.2 Multipattern Karp-Rabin . . . . . . . . . . . . . . . . . . . 4.3 Aho-Corasick . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.1 Idee . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.2 Definition . . . . . . . . . . . . . . . . . . . . . . . . 4.3.3 Vorverarbeitung . . . . . . . . . . . . . . . . . . . . 4.3.4 Suchphase . . . . . . . . . . . . . . . . . . . . . . . . 4.3.5 Vermeidung unnötiger Übergänge der Fehlerfunktion 4.3.6 Verbesserung für kleine Alphabete . . . . . . . . . . 4.4 Wu-Manber . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.4.1 Beschreibung . . . . . . . . . . . . . . . . . . . . . . 4.4.2 Vorverarbeitung . . . . . . . . . . . . . . . . . . . . 4.4.3 Suchphase . . . . . . . . . . . . . . . . . . . . . . . . 4.4.4 Wahl der Blockgröße B . . . . . . . . . . . . . . . . 4.4.5 Verbesserungen . . . . . . . . . . . . . . . . . . . . . 4.5 q-Gramme . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.5.1 Boyer-Moore-Horspool mit q-Grammen . . . . . . . 4.6 Multiple Approximate String Matching with l-Grams . . . . 4.6.1 Vorverarbeitung . . . . . . . . . . . . . . . . . . . . 4.6.2 Suchphase . . . . . . . . . . . . . . . . . . . . . . . . 4.6.3 Anmerkung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 17 18 20 20 20 21 24 26 27 28 28 32 33 33 34 35 36 39 39 40 42 . . . . 43 45 48 49 51 5. Implementierung 5.1 Messungen: Vergleich der Laufzeiten 5.2 Details . . . . . . . . . . . . . . . . . 5.3 Messungen zur Implementierung . . 5.4 Ergebnisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6. Weitere Einsatzgebiete 56 7. Fazit 59 8. Literatur 63 Index 65 3 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 1. Einführung Diese Diplomarbeit befasst sich mit Algorithmen zur Multipatternsuche in der Metagenomik. Die Metagenomik ist ein Forschungsgebiet der Biologie/Bio-Informatik, deren Grundfragestellung es ist, welche Mikroorganismen in einem bestimmten Lebensraum (Biotop) vorkommen und in welcher Anzahl sie dort auftreten. Eine wichtige Methode der Metagenomik ist dabei die DNA-Sequenzierung, also das Ablesen der Nukleotidfolge der DNA. Die erste Methode zur DNA-Sequenzierung wurde 1975 von Frederick Sanger entwickelt. Nach diesem Verfahren wird die DNA zuerst auf biochemische Art in kleine Abschnitte zerlegt, von denen dann die Nukleotidfolge bestimmt werden kann. In jeder einzelnen Sequenzierreaktion werden auf Grund technischer Beschränkungen nur kurze DNA-Abschnitte, sogenannte Reads, von weniger als 1000 Basenpaaren abgelesen. Die Sanger Sequenzierung war bis vor 2 Jahren die dominante Sequenzierungsmethode. Die aus der Sequenzierung erhaltenen Reads werden anschließend auf verschiedene Arten ausgewertet: Bei klassischen Sequenzierungsprojekten wird versucht die Reads wie ein Puzzle zusammenzusetzen um so die Gesamtsequenz wieder rekonstruieren zu können. Ein bekanntes Beispiel dafür ist das Human-Genom-Projekt [Wat90]. Abbildung 1: Rekonstruktion einer Gesamtsequenz Dabei ist es notwendig, dass die DNA nur von einem Organismus kommt. Außerdem sind bei solchen Projekten möglichst lange Reads sehr wichtig, denn nur so kann beim Rekonstruieren der Gesamtsequenz eine ausreichende Überlappung der Reads und damit ein hinreichend verlässliches Ergebnis erreicht werden. Daher kommen bei klassischen Sequenzierungsprojekten meistens die älteren SangerTechnologien zum Einsatz, welche zwar weniger, dafür aber die benötigten längeren Reads liefern. Mit dieser Methode sind bis heute etwa 700 bakterielle Genome und mehr als 20 größere eukaryotische Genome komplett sequenziert worden. (Quelle: GOLD - Genomes OnLine Database, http://www.genomesonline.org/. Stand: Januar 2008.) 5 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Wir werden uns aber mit einer neueren Art der Anwendung von Sequenzierung befassen. Die Sequenzierung ist durch neue Verfahren in den letzten Jahren um den Faktor 100 billiger geworden. Eine Prognose1 von Hans-Peter Klenk vom DSMZ (Deutsche Sammlung von Mikroorganismen und Zellkulturen GmbH, Braunschweig) sagt voraus, dass die Kosten für Sequenzierungen bis 2013 um 50% pro Jahr sinken werden. Abbildung 2: Sequenzierungskosten bis 2013, Prognose der Deutschen Sammlung von Mikroorganismen und Zellkulturen GmbH. Aus [Kle08]. Moderne Sequenzierverfahren arbeiten miniaturisiert und parallel, benötigen immer weniger Probenmaterial und liefern dabei immer mehr Reads pro Sequenzierung. So können beispielsweise mit dem Genome Analyzer des US-Genomicsanbieters Illumina/Solexa [Ill] in einem Durchlauf 40 Millionen Reads mit Leseweiten von bis zu 35 Nukleotiden, also 1Gbp an Sequenzinformation bestimmt werden. Diese große Menge an Reads sowie die geringen Kosten moderner Verfahren machen es möglich, Sequenzierung in bisher nicht möglichen Anwendungsbereichen einzusetzen. So bringt beispielsweise das Sequenzieren metagenomischer Proben, also Proben die viele verschiedene Arten von Bakterien enthalten, erst dann verwertbare und verlässliche Ergebnisse, wenn man sehr viele Reads hat. Dieses ist vor allem von Vorteil, wenn man sich mit Bakterien beschäftigt, die im Labor nicht kultivierbar sind. Solche Bakterien müssen in einer Probe aus der Umwelt direkt untersucht werden, welche naturgemäß verschiedenste Bakterien enthält. Ein weiterer Vorteil moderner Sequenzierungsverfahren ist, dass sie weniger Probenmaterial benötigen. Allerdings haben die modernen Sequenzierungsverfahren nicht nur Vorteile. Der größte Nachteil besteht darin, dass die gelieferten Reads immer kürzer werden. Diese kurzen Reads sind schwerer einzelnen Bakterien zuzuordnen, da die bestehenden Algorithmen zum Großteil auf die langen Reads der älteren Sanger-Sequenzierungstechniken ausgelegt sind. So benötigt zum Beispiel der am häufigsten eingesetzte heuristische Algorithmus BLAST“ 2 mindestens Reads einer Länge von 100 Basenpaaren um eine verlässli” che Zuordnungen treffen zu können. 1 2 6 siehe [Kle08] Basic Local Alignment Search Tool Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Abbildung 3: Eigenschaften verschiedener DNA-Sequenzierungstechnologien aus [SBR07] Wir wollen uns daher mit einer neuen Methode der Zuordnung befassen, dem sogenannten Short Oligo Alignment“. Dafür wurden die bekannten Bakteriengenome auf über” repräsentierte DNA-Sequenzen einer Länge von 8 bis 14 Basenpaaren untersucht. Ein Auswahlkriterium hierfür ist ein häufiges und regelmäßiges Auftreten im Bakteriengenom, beispielsweise alle 10.000 Basenpaare mindestens einmal (siehe [DWRTed] sowie [ea]). Diese kurzen überrepräsentierten DNA-Sequenzen nennen wir im Folgenden Marker. Wir werden daher verschiedene Algorithmen untersuchen, welche möglichst schnell zu allen Reads bestimmen, wie viele Marker eines Bakteriums in ihm auftreten. Das Ziel dabei ist, Reads in denen mehrere Marker eines Bakteriums vorkommen, direkt dem Genom dieses Bakteriums zuordnen zu können. 7 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 2. Wichtige Begriffe und Definitionen Definition Ein Nukleotid ist ein Molekül, das als Grundbaustein von Nukleinsäuren (DNA und RNA) fungiert. Es ist aus mehreren Bestandteilen aufgebaut, unter anderem aus einer der fünf Nukleobasen, nämlich Adenin (A), Cytosin (C), Guanin (G), Thymin (T) oder Uracil (U). In der DNA werden nur vier dieser Basen (A, C, G, T) verwendet, in der RNA ist die Nukleobase Thymin (T) gegen Uracil (U) ausgetauscht. Die Nukleotide unterscheiden sich also durch die Base, die jeweils eingebaut ist. Da A, C, G und T in der DNA vorkommen, werden diese Moleküle auch als DNA-Basen bezeichnet. Definition Als Taxon bezeichnet man in der Biologie eine als systematische Einheit erkannte Gruppe von Lebewesen. Eine solche Gruppe wäre zum Beispiel ein bestimmter Bakterien-Stamm (bsp.: Pseudomonas aeruginosa PAO1, Pseudomonas putida KT2440, Pseudomonas aeruginosa PA7), eine Bakterien-Spezies (bsp.: Pseudomonas aeruginosa, Pseudomonas putida), oder eine ganze Bakterien-Gattung (bsp.: Pseudomonas). Definition Ein Read ist ein durch ein Sequenzierverfahren bestimmter Ausschnitt aus dem Genom eines Bakteriums. Da in der Metagenomik die sequenzierten Proben immer aus einer Gesellschaft verschiedenster Bakterien bestehen, ist eine der grundlegenden Aufgabenstellungen zu bestimmen, zu welchem Taxon ein Read zugeordnet werden kann. Definition Als Marker für ein bestimmtes Taxon bezeichnet man eine kurze DNA-Sequenz, welche charkteristisch für dieses Taxon ist. Definition Ein Hit ist ein Read, welcher einem Taxon zugeordnet werden konnte. Definition Als Basenpaar bezeichnet man zwei Basen der Nukleotide in der DNA oder RNA, die zueinander komplementär sind und durch Wasserstoffbrückenbindungen zusammengehalten werden. Die Anzahl der Basenpaare wird üblicherweise in bp (Basenpaaren) oder kbp (Kilo-Basenpaaren) gemessen. 1kbp sind dabei 1000 Basenpaare. Es gibt vier mögliche Basenpaare: Adenin-Thymin, Thymin-Adenin, Guanin-Cytosin und Cytosin-Guanin. Ein bp kann also vier verschiedene Werte darstellen und entspricht folglich einer Informationsmenge von 2 Bit. 8 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Definition In der DNA-Sequenzierung ist ein contig (englisch von contiguous) eine DNA Sequenz, welche aus einer Menge von sich überlappenden Reads zusammengesetzt wurde. Die Reads dürfen dabei nur aus einer genetischen Quelle, also von einem Genom, stammen [Sta79]. Definition Ein deterministischer endlicher Automat ( deterministic finite automaton) wird durch ein Tupel (Q, Σ, δ, q0 , F ) beschrieben. Dabei gelten folgende Bedingungen: • • • • • • Q ist eine endliche Menge von Zuständen Σ ist eine endliche Menge von Eingabesymbolen. Q∩Σ=∅ q0 ∈ Q ist der Startzustand F ⊆ Q ist die Menge der Endzustände δ : Q × Σ → Q heißt Zustandsübergangsfunktion. Ein deterministischer endlicher Automat kann durch einen Zustandsgraphen veranschaulicht werden. Dabei werden Zustände als Knoten und Übergänge als Kanten dargestellt. Eine mit a ∈ Σ markierte Kante vom Knoten u zum Knoten v im Zustandsgraphen des Automaten stellt also δ(u, a) = v dar. Definition Ein Baum ist ein gerichteter Graph B = (V, E) mit folgenden Eigenschaften: • Es gibt genau einen Knoten w ∈ V mit (v, w) ∈ / E für alle v ∈ V . w heißt Wurzel von B. • Für alle v ∈ V , v 6= w existiert genau ein u 6= v mit (u, v) ∈ E. • Für alle v ∈ V existiert ein Weg von w nach v. Die Länge dieses eindeutigen Weges heißt Tiefe von v. Definition Ein Trie über einem Alphabet Σ ist ein Baum (V, E) mit einer zusätzlichen Markierungsfunktion µ : E → Σ, welche jeder Kante e ∈ E ein Symbol aus Σ zuweist. Es gilt weiterhin: (*) Sind (u, v1) ∈ E und (u, v2) ∈ E mit v1 6= v2, dann ist µ(u, v1) 6= µ(u, v2). Bemerkung Aus (*) folgt direkt, dass der maximale Verzweigungsgrad eines Knotens in Tries |Σ| ist. Bemerkung Jeder Knoten v im Trie repräsentiert ein Wort ρ(v), welches sich aus der Konkatenation der Kantenmarkierungen entlang des Weges von der Wurzel zu v ergibt. Für u ∈ V , v ∈ V mit u 6= v gilt: ρ(u) 6= ρ(v). 9 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Definition Ein binärer Suchbaum B ist ein Binärbaum für den zusätzlich gilt: • Jeder Knoten in B enthält einen Schlüssel, für den eine Ordnung definiert ist. Die Elemente eines binären Suchbaumes müssen also vergleichbar sein. • Existiert zu einem Knoten b ∈ B ein linker Unterbaum Bl , so ist dieser ebenfalls ein binärer Suchbaum. Außerdem müssen alle in Bl enthaltenen Schlüssel kleiner sein als der Schlüssel von b. • Existiert zu einem Knoten b ∈ B ein rechter Unterbaum Br , so ist dieser ebenfalls ein binärer Suchbaum. Außerdem müssen alle in Br enthaltenen Schlüssel größer sein als der Schlüssel von b. Bemerkung In binären Suchbäumen benötigt man maximal O(h) Schritte um ein Element zu finden, wobei h die Höhe des binären Baumes ist. Im Bestcase ist der binäre Suchbaum ausgeglichen. Dann benötigt man O(log(m)) Schritte um ein Element im Baum zu finden, wobei m die Anzahl der Elemente im Baum ist. Im Worstcase hingegen entartet der binäre Suchbaum zu einer linearen Liste und man benötigt O(m) Schritte. Definition Ein AVL-Baum ist ein binärer Suchbaum mit der zusätzlichen Eigenschaft: • Für jeden Knoten im AVL-Baum gilt, dass sich die Höhe des linken Unterbaumes höchstens um 1 von der Höhe des rechten Unterbaumes unterscheidet. Da diese Bedingung verhindert, dass der Baum aus der Balance gerät, nennt man ihn auch ausgeglichen“. Das Suchen in einem AVL-Baum benötigt immer O(log(m)) ” Schritte. 10 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 3. Anforderungen 3.1 Problemgrößen Gegeben sind etwa 700 verschiedene Bakterien, deren Genome komplett assembliert und frei zugänglich sind. Die Zahl der sequenzierten Bakteriengenome wächst sehr stark an, bis 2013 wird mit etwa 105 komplett sequenzierten Genomen gerechnet. Abbildung 4: Sequenzierte Genome bis 2013, eine Prognose der Deutschen Sammlung von Mikroorganismen und Zellkulturen GmbH. Aus [Kle08]. Zu jedem dieser Bakterien sind bis zu 1.000 verschiedene Marker gegeben. Ein Marker ist dabei ein Stück DNA Sequenz der Länge 8 bis 14 Basenpaare, welches im zugehörigen Bakteriengenom überrepräsentiert3 ist. Die Marker sind eindeutig, jeweils 2 Bakterien haben also keine gemeinsamen Marker. Des Weiteren ist eine sogenannte metagenomische Probe aus einer Gesellschaft verschiedenster Bakterien gegeben. Eine solche Probe ist eine mehrere 100MB bis einige GB große Datei im FASTA-Format4 . Diese Datei besteht jeweils aus Reads mit einer Länge von etwa 30 - 800 Basenpaaren. Abbildung 5: Voraussetzungen 3 4 siehe [DWRTed] sowie [ea] siehe [Fas] 11 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 3.2 Ergebnis Das Programm soll als Ergebnis eine Statistik zu den Reads erstellen. In dieser soll zu jedem Read aufgelistet werden, wie viele zu einen bestimmten Bakterium gehörenden Marker darin enthalten sind. Es interessieren nur Marker die innerhalb eines Reads gefunden werden. Wird ein Marker gefunden, dessen Anfang in einem Read und dessen Ende im folgenden Read liegt, so gilt dies nicht als Auftreten des entsprechenden Markers. Reads in denen keiner der Marker gefunden wurden, sollen verworfen werden. Abbildung 6: Ergebnis. In grün bzw. blau sind die Stellen markiert, an denen ein Marker gefunden wurde 3.3 Programmiersprache An den Anforderungen erkennen wir schon 2 entscheidende Größen, welche bei der Wahl der Programmiersprache wichtig sind: Zum einen gibt es sehr viele Marker. Für eine schnelle Suche sollten diese nach Möglichkeit im Arbeitsspeicher liegen. Wenn wir von 105 verschiedenen Bakterien ausgehen und als Mittelwert pro Bakterium 500 Marker der Länge 10 nehmen, so kommen wir auf 500.000.000 zu speichernde Basenpaare. Als int mit 4 Byte je Basenpaar abgespeichert, wären das 2.000.000.000 Byte, also rund 2 GB. Von der Größenordnung passen also alle Marker in den Arbeitsspeicher, sofern die Programmiersprache nicht zuviel Overhead für eine entsprechende Datenstruktur benötigt. Die zweite wichtige Größe ist die Zeit, welche die Programmiersprache benötigt um eine sehr große Datei einzulesen. Die Probendatei kann bis zu mehreren GB groß sein und muss voraussichtlich Zeichen für Zeichen eingelesen werden. Neben der Zeit, welche der spätere Algorithmus benötigen wird, um alle Auftreten der Marker zu finden, muss also auch die reine Lesezeit der Probendatei beachtet werden. 12 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik r r Alle Messungen wurden auf einem Laptop mit einem Intel Pentium M (1,8 Ghz) und 1048 MByte RAM unter Windows XP Professional mit Service Pack 2 durchgeführt. Verwendet wurde JavaTM Standard Edition 6 (build 1.6.0 05-b13). Als Entwicklungsumr gebung für C++ wurde Microsoft Visual C++ 6.0 benutzt. 3.3.1 Messungen zum Speicherbedarf Um die passende Programmiersprache und die passende Datenstruktur zu bestimmen, wurden die folgenden Messungen zum Speicherbedarf für 4 verschiedene Implementierungen gemacht: • Sprache: Java, Datenstruktur: eigene Klasse 5 • Sprache: Java, Datenstruktur: Vector • Sprache: Java, Datenstruktur: Collection • Sprache: Java, Datenstruktur: Array Bei den Java Implementierungen wurde jeweils der verfügbare Speicher der JVM festgesetzt und dann wurden in einer Endlosschleife Instanzen erzeugt, bis es zu einer OutOfMemoryError-Exception kommt. In Abbildung 7 sind die Messergebnisse in einem Diagramm dargestellt. Die genauen Messdaten zu dem Diagramm sind in Tabelle 1 aufgeführt. 5 public class Knoten { Knoten Nachfolger; int Wert; public void addNachfolger(Knoten n) { Nachfolger=n; } public void setWert(int i){ Wert=i; } public Knoten getNachfolger(){ return Nachfolger; } public int getWert(){ return Wert; } } 13 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Abbildung 7: Speichermessung für Java Implementierungen mit verschiedenen Datenstrukturen. verfügbarer Speicher 16MB 24MB 32MB 40MB 48MB 56MB 64MB 72MB 80MB 88MB 96MB Array 3.860.387 5.790.580 7.730.774 9.660.967 11.591.160 13.541.355 15.471.548 17.411.742 19.341.935 21.282.129 23.212.322 erzeugte Instanzen Vector Collection eigene Klasse 655.361 670.206 840.085 1.230.376 1.005.309 1.250.126 1.310.721 1.507.964 1.670.168 1.943.080 1.507.964 2.080.209 2.463.272 2.261.946 2.500.251 2.621.441 2.261.946 2.920.293 2.621.441 3.310.735 3.330.334 2.621.441 3.392.919 3.750.376 3.888.680 3.392.919 4.160.417 4.408.872 3.392.919 4.580.459 4.929.013 4.967.388 5.000.501 Tabelle 1: Speichermessung für Java Implementierungen mit verschiedenen Datenstrukturen. Man sieht, dass das Array in etwa dem theoretisch berechneten Wert von 4Byte pro Array Element entspricht. Alle anderen Datenstrukturen in Java weisen jedoch einen recht deutlichen Overhead auf. Zum Vergleich wurden die Messungen noch mal für eine Implentation in C++ mit der Datenstruktur Vector gemacht. Dazu wurde jeweils eine bestimmte Anzahl von int-Zahlen in einen Vector eingefügt. Anschließend wurde gemessen, wie viel Speicher das Programm benötigt. 14 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Instanzen 3.860.387 5.790.580 7.730.774 9.660.967 11.591.160 13.541.355 15.471.548 17.411.742 19.341.935 21.282.129 23.212.322 benötigter Speicher Java Array C++ Vector 16.384KB 17.248KB 24.576KB 33.672KB 32.768KB 33.672KB 40.960KB 66.520KB 49.152KB 66.520KB 57.344KB 66.520KB 65.536KB 66.520KB 73.728KB 132.216KB 81.920KB 132.216KB 90.112KB 132.216KB 98.304KB 132.216KB Tabelle 2: Speichermessung für eine C++ Implementierungen unter Verwendung eines Vectors, im Vergleich mit der Java Implementierung unter Verwendung eines Arrays. An den Ergebnissen kann man erkennen, dass der Vector in C++ kaum Overhead besitzt. Bei 3.860.387, 7.730.774 und 15.471.548 erzeugten Einträgen benötigt er etwa so viel Speicher wie ein entsprechend gefülltes Array in Java und erreicht damit nahezu den theoretisch optimalen Wert von 4Byte pro Eintrag. An den übrigen Werten können wir ein bestimmtes Verhalten des Vectors erkennen. Der Vector reserviert Speicher für eine bestimmte Anzahl Einträge. Sobald diese Anzahl an Einträgen in dem Vector abgespeichert wurden, verdoppelt der Vector den reservierten Platz. Dadurch kommt dieses sprunghafte Verhalten im Speicherbedarf zustande. Die Collection sowie der Vector in Java arbeiten auf dieselbe Art. Auch sie verdoppeln sich, wenn der reservierte Platz nicht mehr ausreicht. Allerdings wird jetzt auch eine Schwäche bei den Messungen in Java deutlich: Das Array wurde jedes Mal neu definiert, also nur um 1 Element vergrößert. Dadurch hat es natürlich den Speicher bis an die gegebene Grenze ausnutzen können. Der Vector hingegen verdoppelt seine Größe jedes Mal, wenn er keinen Platz mehr zum Speichern weiterer Elemente hat. Es kommt also schon zu einem Speicherüberlauf, wenn die Verdoppelung des Vectors nicht mehr möglich ist. Im Worstcase speichert der Vector in Java also nur halb so viele Elemente wie eigentlich möglich wären. Noch schlechter wird die Speicherauslastung, wenn der Vector sich nicht direkt vergrößern kann, sondern erst einen neuen Speicherbereich reservieren muss, der doppelt so groß wie der aktuelle ist. In diesem Fall würde der Vector in Java im Worstcase sogar nur ein Drittel der theoretisch möglichen Elemente speichern können. Gleiches gilt für die Collection. In C++ beeinflusst dieser Effekt unsere Messungen nicht, da das Programm zur Laufzeit beliebig viel Speicher nutzen konnte. Der Speicherbedarf wurde erst am Ende gemessen, also nachdem alle Elemente in den Vector eingetragen waren. 15 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 3.3.2 Messungen zur Laufzeit (Datei lesen) Es wurde jeweils eine 10MB, eine 101MB sowie eine 728MB große Textdatei eingelesen. Getestet wurden damit die folgenden 5 Implementierungsvarianten: • 1) Sprache: Java, Benutze Methode: Filereader • 2) Sprache: Java, Benutze Methode: Filereader mit festem Buffer (32.768 Zeichen) • 3) Sprache: Java, Benutze Methode: BufferedInputStream • 4) Sprache: Java, Benutze Methode: FileInputStream • 5) Sprache: C++, Benutze Methode: fopen Bei dem Filereader mit festem Buffer wurden anschließend jeweils die erhaltenen Strings Zeichen für Zeichen durchlaufen. Auf eine Implementierung mit einer Readline-Funktion wurde verzichtet, um das Programm später leichter erweiterbar auf weitere Anwendungsbereiche zu machen. So ist es denkbar, dass das Programm nicht nur für die Analyse kurzer Reads aus Ilumina/Solexa Sequenzierungsprojekten von 30-100 Basenpaarlänge verwendet werden könnte, sondern auch auf Daten aus bereits zu größeren Contigs zusammengefassten Reads oder sogar auf ganze Genome (siehe Kapitel 6: weitere Einsatzgebiete). Im Worstcase könnten solche Dateien dann keine Zeilenumbrüche mehr enthalten. Alle Messungen wurden 3 mal durchgeführt, angegeben ist jeweils der Mittelwert. Dateigröße in MB 10 101 728 1) 1sec 16 sec 215 sec Implementierungsvariante 2) 3) 4) 5) 1 sec 40 sec 55 sec 1 sec 3 sec 416 sec 556 sec 2 sec 35 sec 2845 sec 2832 sec 29 sec Tabelle 3: Lesezeit für eine Datei 16 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 4. Algorithmen Gegeben seien r Pattern, bezeichnet mit p1 , . . . , pr . Die Länge des i-ten Patterns pi sei li , l sei die Länge des längsten Patterns. Die summierte Länge aller Pattern bezeichnen wir r P li . mit m = i=1 Mit pi [x] bezeichnen wir den x-ten Buchstaben des Patterns pi , das Präfix der Länge q von pi wird mit pi [1..q] bezeichnet. Π sei die Menge aller Pattern p1 , . . . , pr . T sei der Eingabetext, die Länge des Eingabetextes sei n. Σ sei das den Pattern und dem Eingabetext zugrunde liegende Alphabet der Größe |Σ|. Wir werden in diesem Kapitel mehrere Algorithmen betrachten, mit denen man alle Auftreten der Pattern pi ∈ Π im Eingabetext bestimmen kann. 4.1 Brute Force / Single Pattern Matching Für das Single Pattern Matching, also das Suchen nach nur einem Pattern, gibt es viele bewährte Algorithmen (siehe [Par03]). Eine naheliegende Idee wäre, einfach diese Algorithmen für alle pi ∈ Π einzeln anzuwenden. Man hätte so ohne großen Aufwand einen Algorithmus, welcher unser Problem lösen würde. Allerdings hätten all diese Algorithmen eine Laufzeit von mindestens O(n · m), was nicht sehr effizient wäre. Für sehr wenige Pattern (≤ 10) und sehr große Alphabete kann sich das mehrfache Anwenden von Single Pattern Matching dennoch lohnen. Die Vorteile sind ein geringer Implementationsaufwand, sowie die Tatsache, dass einige der Single Pattern Algorithmen bei großen Alphabeten im Mittel (nicht im Worstcase!) eine sublineare Laufzeit haben. In unserem Fall haben wir aber extrem viele Pattern (≥ 105 ) und ein sehr kleines Alphabet (|Σ| = 4). Single Pattern Matching Algorithmen sind für diese Aufgabe also vollkommen ungeeignet. 17 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 4.2 Multipattern Karp-Rabin Dieser Algoritmus ist auf dem klassischen“ Karp-Rabin Algorithmus [KR87] für das ” Single Pattern Matching aufgebaut. Die Grundidee des Karp-Rabin Algorithmus ist, dass man vor dem Vergleich der Zeichen des Patterns mit denen einer gleich langen Zeichenkette des Eingabetextes prüft, ob die beiden Zeichenketten ähnlich“ sind (Fingerabdruck). ” Diese Ähnlichkeit bestimmt man mit einer Hashfunktion h : Σm → N. Haben zwei Zeichenketten einen unterschiedlichen Hashwert, so folgt daraus, dass sie ungleich sind. Für die Suche bedeutet dies, dass an dieser Stelle im Eingabetext das Pattern nicht vorkommen kann. Haben dagegen zwei Zeichenketten den selben Hashwert, so kann es sein, dass die beiden Zeichenketten gleich sind. Dies muss man dann Zeichen für Zeichen überprüfen. Damit ist der Karp-Rabin Algorithmus für das Single Pattern Matching auch schon beschrieben. Um einen effizienten Algorithmus zu erhalten, sollte sich der Wert der Hashfunktion für eine Zeichenkette T [i + 1..i + m + 1] leicht aus der vorangegangenen Zeichenkette T [i..i + m] errechnen lassen. Idealerweise gilt für die Hashfunktion also: i) h(T [i + 1..i + m + 1]) = f (h(T [i..i + m]), T [i + m + 1]) In der Praxis ist die Forderung aber zu stark, daher verwendet man stattdessen Hashfunktionen, welche die folgende abgeschwächte Bedingung erfüllen: ii) h(T [i + 1..i + m + 1]) = f (h(T [i..i + m]), T [i + m + 1], T [i]) Karp und Rabin haben als Hashfunktion folgende Funktion vorgeschlagen: h(x1 · · · xm ) = m X xj · bm−j mod q j=1 wobei xi ∈ Σ, q eine ausreichend große“ Primzahl und b eine passend gewählte“ Basis ” ” (oft b = |Σ|) ist. Man erkennt leicht, dass die so definierte Hash-Funktion ii) erfüllt, denn: h(T [i + 1..i + m + 1]) = ((h(T [i..i + m]) − T [i] · bm−1 ) · b + T [i + m + 1])mod q Lemma Der Karp-Rabin Algorithmus löst das Single Pattern Matching Problem in O(m · n) Zeiteinheiten und benötigt dabei O(m) Speicherplatz.6 Muth und Manber [MM96] sowie Gum und Lipton [GL00] haben den klassischen KarpRabin Algorithmus auf das Multi Pattern Matching erweitert, indem Sie ihn mit binären Suchbäumen kombiniert haben. In der Vorverarbeitung werden dazu als erstes alle Pattern pi ∈ Π auf dieselbe Länge gebracht, indem wir jeweils nur die ersten lmin = min {lp1 , · · · , lpr } Zeichen betrachten. Dann werden für all diese verkürzten Pattern die Hashwerte mit derselben Hashfunktion h wie in dem klassischen Karp-Rabin berechnet. Diese Hashwerte werden alle in einen Binären Suchbaum eingetragen. Die Suchphase läuft dann so ab, dass zuerst der Hashwert von der aktuellen Position im Eingabetext berechnet wird. Anschließend wird nachgeschaut, ob sich dieser Hashwert im binären Suchbaum befindet. Ist dies der Fall, so müssen alle Pattern pi ∈ Π, welche diesen Hashwert besitzen, einzeln gegen den Eingabetext an dieser Position getestet werden. Ist 6 Beim Single Pattern Matching gilt m = 1 P i=1 18 |pi | = |p1 | = |p| Schnelle Algorithmen zur Multipatternsuche in der Metagenomik dieser Hashwert nicht in dem Suchbaum vorhanden, kann an der aktuellen Position auch keins der Pattern vorkommen. Die aktuelle Position kann also um 1 nach rechts verschoben werden. Lemma Der Multipattern“ Karp-Rabin Algorithmus löst das Multi Pattern Matching Pro” blem in O(m · n) Zeiteinheiten und benötigt dabei O(m) Speicherplatz. Verbessern lässt sich die Laufzeit, indem man anstatt von binären Suchbäumen AVLBäume verwendet. Eine weitere Verbesserung, das sogenannte Two-Level-Hashing, schlagen Muth und Manber in [MM96] vor. Dabei wird zuerst wie vorher beschrieben der Hashwert für alle Pattern berechnet und in den Suchbaum eingetragen. Anschließend wird eine zweite Hashtabelle (auch Bitmap-Table genannt) erstellt, welche für eine Zahl x den Wert 1 enthält, wenn x in dem vorher berechneten Suchbaum enthalten ist. Alle anderen Werte in der zweiten Hashtabelle sind 0. In der Suchphase wird nun wie bisher der Hashwert über die aktuelle Position des Eingabetextes berechnet. Anschließend wird erst mal in der zweiten Hashtabelle geschaut, ob sich dieser Hashwert überhaupt in dem Suchbaum befindet. Dadurch spart man sich im Fall, dass der Hashwert nicht vorkommt, die O(log(m)) Zugriffe auf den Suchbaum. 19 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 4.3 Aho-Corasick 4.3.1 Idee Sei l die Länge des längsten Patterns aus Π. Liest man von links nach rechts über den Eingabetext, so kann man für alle pi aus Π anhand der letzten l gelesenen Zeichen bestimmen ob pi an der aktuellen Position im Eingabetext vorkommt oder nicht. Alles, was vor den letzten l Zeichen gelesen wurde, spielt keine Rolle mehr. Man braucht also nur die l zuletzt gelesenen Zeichen zu betrachten, was wiederum bedeutet, dass man bei einem Alphabet der Größe |Σ| nur |Σ|l verschiedene Möglichkeiten hat. Da man also nur endlich viele Möglichkeiten ( = Zustände) hat und der Eingabetext Zeichen für Zeichen eingelesen wird, bietet sich hier die Verwendung eines endlichen deterministischen Automaten an. 4.3.2 Definition Definition 4.1: Ein Aho-Corasick Automat wird durch ein Tupel (Q, Σ, g, next, q0 , out) beschrieben. Dabei sind Q die Menge von Zuständen, Σ das Eingabealphabet, g : Q × Σ → Q die partielle Zustandsübergangsfunktion und q0 ∈ Q der Startzustand jeweils wie beim DFA definiert. out (Q) ist die Ausgabefunktion, welche für jeden Zustand aus Q eine Ausgabe erzeugt. Die Fehlerfunktion next ist definiert als next : Q → Q. Wenn g(q, b) für q ∈ Q, b ∈ Σ nicht definiert ist, wird die Fehlerfunktion next(q) so oft angewendet, bis der Aho-Corasick Automat sich wieder in einem Zustand q 0 ∈ Q befindet für den g(q 0 , b) definiert ist. next ist genau wie g eine partielle Funktion. Außerdem gilt für den Aho-Corasick Automaten: 1) g(q0 , a) ist für alle a ∈ Σ definiert. 2) Gilt next(q) = q 0 , so muss der kürzeste Weg im Transitionsgraphen des Automaten von q0 nach q 0 kürzer sein, als der von q0 nach q. Lemma: Der Aho-Corasick Algorithmus arbeitet in O(n) Schritten. g(q0 , a) ist definiert für alle a ∈ Σ. Wenn man jetzt k-mal die Übergangsfunktion g anwendet, so ist der kürzeste Weg im Transitionsgraphen des Automaten von q0 zum aktuellen Zustand kleiner oder gleich k. Da bei jeder Anwendung der Fehlerfunktion dieser Weg nach 2) aber um mindestens 1 kürzer werden muss, sind maximal k Anwendungen möglich. Insgesamt kann also die Fehlerfunktion maximal so oft angewendet werden wie die Übergangsfunktion g. Pro Zeichen im Eingabetext wird genau einmal die Übergangsfunktion angewendet. Dies ist gewährleistet, da spätestens wenn man auf q0 zurückgesprungen ist, die Übergangsfunktion für alle Symbole aus Σ definiert ist. Daraus folgt, dass die Anzahl der Übergänge im Algorithmus kleiner oder gleich 2n sein muss. (Genauer sogar 2n − 1, weil der letzte Übergang im Algorithmus mit der Übergangsfunktion g gemacht wird und danach kein Übergang mit der Fehlerfunktion mehr folgen kann.) 20 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 4.3.3 Vorverarbeitung Wir konstruieren aus allen Pattern pi ∈ Π einen Trie. Die Konstruktion erfolgt sukzessiv aus allen Pattern pi ∈ Π. Wir starten mit einem Knoten q0 , welcher die Wurzel des Baumes sei. Der Knoten q0 sei unser aktueller Knoten. Jetzt betrachten wir den ersten Buchstaben p1 [1] des erste Patterns p1 ∈ Π. Gibt es eine Kante vom aktuellen Knoten aus, welche mit p1 [1] markiert ist und zu einem Knoten q führt, so sei der Knoten q unser aktueller Knoten. Gibt es keine solche Kante, so fügen wir einen neuen Knoten als Nachfolger vom aktuellen Knoten ein und markieren die neue Kante mit p1 [1]. Der aktuelle Knoten sei dann der neu eingefügte Knoten. Entsprechend verfahren wir mit den anderen Buchstaben p1 [2] bis p1 [l1 ] des ersten Patterns. Nachdem wir das erste Pattern abgearbeitet haben, nehmen wir wieder q0 als unseren aktuellen Knoten und setzen dann das Verfahren mit p2 fort. Abbildung 8: Hinzufügen vom Wort TEMP zu einem bestehenden Trie Nach Konstruktion erhalten wir in O(m) Schritten den gewünschten Trie, wobei r P m := |pi | gilt. i=1 Die Knoten des Tries entsprechen den Zuständen des Automaten, die Kanten ergeben folgendermaßen die Übergangsfunktion g: 1) g(q, b) := q 0 , falls es eine Kante von q nach q 0 gibt, welche mit b markiert ist. Da wir für unseren Aho-Corasick Automaten noch gefordert hatten, dass g(q0 , b) für alle b ∈ Σ definiert sein soll, setzen wir noch: 2) g(q0 , b) := q0 , falls g(q0 , b) nicht in 1) definiert wurde. 21 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Als nächstes konstruieren wir die Fehlerfunktion next : Q → Q. Die Fehlerfunktion soll immer dann verwendet werden, wenn g(q, b) für q ∈ Q, b ∈ Σ nicht definiert ist. Ziel ist es, dass wir nur soweit im Trie zurückspringen wie notwendig. Wir suchen also den Knoten q 0 , welcher das längste Suffix von dem aktuellen Knoten q repräsentiert und setzen dafür next(q) := q 0 . Gibt es keinen Knoten im Trie, welcher ein Suffix von q repräsentiert, so setzen wir next(q) := q0 . Lemma: Naive Konstruktion der Fehlerfunktion in O(r · l2 ) Wir durchlaufen jeden Knoten im Baum, wobei die Reihenfolge unwichtig ist. Sind die Knoten in einem Array oder Vector gespeichert, so kann man beispielsweise einfach das Array bzw. den Vector linear durchlaufen. q sei dabei der Knoten, bei dem wir uns gerade befinden und lq die Länge des vom Knoten q repräsentierten Wortes. Wir durchlaufen dann mit dem Suffix q[2..lq ] den bisher konstruierten DFA (Q, Σ, g, q0 , F )7 . Erreichen wir nach dem Abarbeiten des Eingabewortes q[2..lq ] einen Zustand qsuf f ix , ohne das wir zwischendurch auf einen Übergang g(q 0 , b) → q0 und ohne das wir auf einen nicht definierten Übergang treffen, so setzen wir next(q) = qsuf f ix . Andernfalls versuchen wir den DFA entsprechend mit den Suffixen q[3..lq ], . . . ,q[lq ..lq ] zu durchlaufen. Erreichen wir bei keinem der Durchläufe am Ende einen Zustand im DFA ohne vorher auf einen nicht definierten Übergang oder einen Übergang g(q 0 , b) → q0 zu treffen, so setzen wir next(q) := q0 . Dann setzen wir das Verfahren mit dem nächsten Knoten im Durchlauf fort. Insgesamt kommen wir auf m Knoten und pro Knoten auf maximal l Suffixe. Die Längen der Suffixe aufaddiert ergibt: l + (l − 1) + (l − 2) + . . . ≤ l2 , also haben wir eine Gesamtkomplexität von O(r · l2 ) Diese Konstruktion der Fehlerfunktion demonstriert bereits sehr schön die Arbeitsweise des Aho-Corasick Automaten. Allerdings ist sie nicht optimal, was die Anzahl der benötigten Schritte angeht. Mit etwas mehr Speicherbedarf lässt sich die Fehlerfunktion effizienter bestimmen, indem man rekursiv vorgeht: Lemma: Rekursive Konstruktion der Fehlerfunktion in O(m) Wir durchlaufen jeden Knoten in einem Breitendurchlauf. Im ersten Schritt setzen wir dabei für alle Knoten der Tiefe 1 die Fehlerfunktion auf q0 . Angenommen die Fehlerfunktion sei für alle Knoten mit Tiefe kleiner als t bereits bestimmt. Wir durchlaufen nun alle Knoten mit Tiefe t − 1 und bestimmen dabei die Menge Rt als Menge aller Tupel (r, a, s) für die gilt, dass g(r, a) = s, a ∈ Σ, s ∈ Q, r Knoten der Tiefe t − 1. Nach Konstruktion dieser Tupel gilt also für (r, a, s) ∈ Rt , dass s ein Knoten der Tiefe t ist. r ist jeweils der eindeutige Vorgänger von s und a die zugehörige Kantenmarkierung für die Kante von r nach s. Da die zugrundeliegende Datenstruktur ein Trie ist folgt weiterhin, dass für jeweils 2 Tupel (r1 , a1 , s1 ) ∈ Rt und (r2 , a2 , s2 ) ∈ Rt gilt: s1 6= s2 . Ebenfalls aus der Eigenschaft Trie folgt: {s| {r, a, s} ∈ Rt } ist genau die Menge aller Knoten mit Tiefe t. 7 setze dazu F = ∅ 22 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Aus Rt bestimmen wir nun die Fehlerfunktion für jeden Knoten der Tiefe t folgendermaßen: for every (r, a, s) in Rt : begin state := next(r) while (g(state, a) == nicht definiert) begin state = next(state) end f(s) = state end Da g(q0 , b) für alle b ∈ Σ definiert ist, liefert der Algorithmus in jedem Fall ein Ergebnis. Als letztes müssen wir noch die Endzustände des Automaten bestimmen. Endzustand sind natürlich alle Knoten des Tries, die eins der Pattern repräsentieren. Im Folgenden werden wir diese Endzustände als direkte Endzustände bezeichnen. Die direkten Endzustände können anfangs beim Erstellen des Tries ohne zusätzlichen Zeit und Platzbedarf bestimmt werden. Der Knoten, welcher beim Hinzufügen des letzten Zeichens eines Patterns erreicht wird, ist nämlich der direkte Endzustand für das jeweilige Pattern. Sei q ein direkter Endzustand für das Pattern p. Dann setzen wir für die Ausgabefunktion out(q) := P attern p gef unden“. ” Des Weiteren müssen alle Knoten ein Endzustand des Automaten sein, die ein Wort repräsentieren, welches als Suffix eins der Pattern enthält (Im Folgenden als indirekte Endzustände bezeichnet). Ist beispielsweise Haus ein Pattern, so ist der Knoten q im Baum, welcher das Wort HolzHaus repräsentiert ein Endzustand des Automaten (für das Wort Haus). Wurden die direkten Endzustände bereits vor dem Erstellen der Fehlerfunktion bestimmt, so können die indirekten Endzustände ohne zusätzlichen Zeit und Platzbedarf beim Erstellen der Fehlerfunktion mitbestimmt werden. Setzt man beim Breitendurchlauf für einen Knoten q die Fehlerfunktion next(q) := q 0 , so erweitern wir einfach die zugehörige Outputfunktion um den Wert der Outpuntfunktion an der Stelle q 0 , also: out(q) := out(q) + out(q 0 ). Lemma: Die Vorverarbeitung des Aho-Corasick Automaten benötigt O(m) Schritte. Dies folgt jetzt direkt aus unseren bisherigen Ergebnissen. Der DFA lässt sich in O(m) konstruieren, für die Fehlerfunktion benötigt man ebenfalls O(m) Schritte. Um die Endzustände zu bestimmen, benötigt man keine weitere Komplexität. Insgesamt erhält man also eine Komplexität von O(m) . ❏ 23 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 4.3.4 Suchphase Gegeben ist ein Eingabetext der Länge n, sowie der in der Vorverarbeitung bestimmte Aho-Corasick Automat (Q, Σ, g, next, q0 , out). Genau wie bei einem DFA arbeiten wir jetzt Zeichen für Zeichen den Eingabetext ab. Erreichen wir dabei einen Endzustand, so haben wir eins der Pattern pi ∈ Π gefunden. Im Gegensatz zu einem DFA ist die Zustandsübergangsfunktion aber nicht vollständig bestimmt. Es kann also vorkommen, dass wir uns im Zustand q ∈ Q befinden und ein Zeichen b ∈ Σ einlesen und g(q, b) nicht definiert ist. In diesem Fall wenden wir solange die Fehlerfunktion next(q) an, bis wir uns in einem Zustand q 0 befinden, für den g(q 0 , b) definiert ist. Ein solcher Knoten q 0 existiert nach Definition. Bemerkung: Für die Fehlerfunktion hatten wir gefordert, wenn next(q) = q 0 gilt, so muss der kürzeste Weg im Transitionsgraphen des Automaten von q0 nach q’ kürzer sein, als der von q0 nach q. Hat ein Knoten q also die Tiefe k im Transitionsgraphen des Automaten, so erreichen wir spätestens nach (k − 1)-maligen Anwenden der Fehlerfunktion den Wurzelknoten q0 . Und für q0 gilt wiederum nach Definition, dass g(q0 , a) für alle a ∈ Σ definiert ist. Zu beachten ist noch, dass wenn man mit der Fehlerfunktion von einem Zustand q des Automaten zu einem Zustand q 0 übergeht und q 0 ein Endzustand für ein Pattern pi ist, so war q bereis ein (indirekter) Endzustand für dieses Pattern. Man muss also bei der Implementierung aufpassen, dass man hierbei ein Auftreten für das Pattern pi nicht doppelt zählt. 24 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Beispiel: Abbildung 9: Suchphase eines Aho-Corasick Automaten. In Abbildung 9 ist die Suchphase des Aho-Corasick Automaten grafisch dargestellt. Zu sehen ist ein Aho-Corasick Automat für 2 Pattern ( TET“ und ETT“). Die Fehlerfunktion ” ” ist in Hellblau eingezeichnet, alle Endzustände sind mit Gelb hinterlegt. Der aktuelle Zustand und der letzte Übergang sind mit Rot markiert. Der Eingabetext lautet TTETT“, ” das Alphabet ist Σ = {E, T }. 25 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 4.3.5 Vermeidung unnötiger Übergänge der Fehlerfunktion Wir betrachten noch mal den Aho-Corasick Automaten aus Abbildung 9, nehmen diesmal als Eingabetext aber TEE“. ” Abbildung 10: Suchen von TEE“ in dem AC-Automaten aus Abbildung 9 ” Wir sehen, dass der Aho-Corasick Automat beim Anwenden der Fehlerfunktion next(q2 ) erst in q4 übergeht und dann mit next(q4 ) nach q0 . Im Prinzip hätte aber auch gleich von q2 nach q0 gesprungen werden können, da von q2 und q4 nur mit einem T“ beschriftete ” Kanten ausgehen. Wenn wir also in q2 die Fehlerfunktion verwenden müssen, weil g(q2 , b) nicht definiert ist, so ist klar, dass g(q4 , b) ebenfalls nicht definiert ist. 26 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Wir können also die Fehlerfunktion verbessern, indem wir zusätzlich fordern: Gilt next(q) = q 0 , so muss es mindestens ein b ∈ Σ geben mit g(q, b) nicht definiert und g(q 0 , b) = q 00 . 4.3.6 Verbesserung für kleine Alphabete Ist die Alphabetgröße bekannt, so kann man insbesondere bei kleinen Alphabeten eine weitere Veränderung am Aho-Corasick Automaten vornehmen. Und zwar definieren wir dazu folgendermaßen eine neue Fehlerfunktion next0 : (Q, Σ) → Q: next0 (q, b) = next(q), wenn g(next(q), b) definiert ist. Andernfalls wenden wir so oft unsere alte Fehlerfunktion next(q) an, bis wir einen Zustand qnext0 des Aho-Corasick Automaten erreichen, für den g(qnext0 , b) definiert ist und setzen: next0 (q, b) = qnext0 Wir haben somit für jeden Zustand und jeden Buchstaben eine eigene Fehlerfunktion definiert. Da wir die Fehlerfunktion immer nur dann verwenden, wenn g(q, b) nicht definiert ist, können wir unsere neue Fehlerfunktion und unsere Zustandsübergangsfunktion zu einer neuen Funktion g 0 verschmelzen: g 0 (q, b) = g(q, b), falls g(q, b) definiert ist, g 0 (q, b) = next0 (q, b) sonst. Wir erhalten somit einen DFA mit vollständig definierter Zustandsübergangsfunktion. Der Vorteil ist, dass wir die Eingabe somit ohne irgendwelche Fehlerfunktionsaufrufe oder unnötige Übergänge nur mit der Zustandsübergangsfunktion abarbeiten können. Während der klassische Aho-Corasick Automat im schlechtesten Fall noch 2n−1 Schritte benötigte, benötigt dieser vollständige DFA nur n Schritte. Der Nachteil ist allerdings, dass wir für jeden Zustand q ∈ Q und für jedes Eingabesymbol b ∈ Σ die Zustandsübergangsfunktion definieren müssen. Dies ist sehr Speicheraufwendig und lohnt daher nur bei recht kleinen Alphabeten. 27 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 4.4 Wu-Manber Bevor wir uns den Algorithmus anschauen, wollen wir uns auf 2 Schreibweisen festlegen: Sei T = T [1 · · · n] der Eingabetext. An Position i im Eingabetext steht dann das Zeichen T [i] ∈ Σ. Entsprechend definieren wir auch die Position für ein Teilwort der Länge l vom Eingabetext: Sei x = T [i · · · i + l] ein Teilwort der Länge l vom Eingabetext, so kommt x an der Position i in T vor. 4.4.1 Beschreibung Mit dem Aho-Corasick Algorithmus haben wir bereits einen Algorithmus kennengelernt, der das Problem in O(n) löst. Im Worstcase ist dies der optimale Fall. Die Idee des Wu-Manber Algorithmus ist es, nicht so sehr auf den Worstcase zu achten, sondern den Algorithmus im Mittel schneller als lineare Laufzeit zu machen. Vorbild dazu ist der BoyerMoore Algorithmus8 , welcher quasi auf das Multi Pattern Matching übertragen wird. Als erstes bringen wir dazu alle pi ∈ Π auf dieselbe Länge, indem wir jeweils nur die ersten lmin = min {lp1 , · · · , lpr } Zeichen betrachten. Aus diesen verkürzten Pattern bilden wir lmin Mengen P1 , · · · , Plmin , wobei die Menge Pk alle Zeichen aus Σ enthält, die in mindestens einem der pi ∈ Π an k-ter Position vorkommen: P1 = {p1 [1], · · · , pr [1]} , · · · , Plmin = {p1 [lmin ], · · · , pr [lmin ]}. Mit diesen Mengen Pi können wir einen Filteralgorithmus für das Multi Pattern Matching konstruieren. Dazu arbeiten wir den Eingabetext T folgendermaßen von Links nach Rechts ab: Unsere aktuelle Position im Eingabetext sei mit x (in den Grafiken durch den kleinen blauen Pfeil dargestellt) bezeichnet. Wir unterscheiden jetzt 3 Fälle: 1.) Wenn der Buchstabe T [x] in keiner der Mengen Pk vorkommt, so können wir die nächsten lmin Zeichen des Eingabetextes überspringen. Abbildung 11: Fall 1: Der Buchstabe T [x], in diesem Fall W“ kommt in keinem der ” Pi ’s vor. Wir können also 4 Zeichen des Eingabetextes überspringen. 8 Der Boyer-Moore ist ein Algorithmus für das Single Pattern Matching. Siehe dazu [BM77] und [Hor80] 28 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 2.) Wenn der Buchstabe T [x] in der Menge Plmin vorkommt, so kann es sein, dass wir ein pi ∈ Π gefunden haben. Um dies zu verifizieren müssen jetzt alle pi ’s, die an der lmin ’ten Position das Zeichen T [x] haben, einzeln getestet werden. Danach verschieben wir unsere aktuelle Position um 1. Abbildung 12: Fall 2: Der Buchstabe T [x], in diesem Fall T“ kommt in P4 vor. Wir ” müssen also testen, ob eines der Pattern pi an dieser Stelle auftritt. 3.) Andernfalls verschieben wir unsere aktuelle Position so, dass das gerade im Eingabetext gelesene Zeichen T [x] mit dem rechtesten Auftreten des Zeichens T [x] in einem der Pattern übereinstimmt. Wir überspringen also lmin − max {k|T [x] ∈ Pk } Zeik=1,··· ,lmin chen des Eingabetextes. Abbildung 13: Fall 3: Der Buchstabe T [x], in diesem Fall S“ kommt in P3 vor. Wir ” können also 4 - 3 = 1 Zeichen des Eingabetextes überspringen. Aus diesen 3 Fällen kann man schon 2 wichtige Feststellungen machen: 29 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Lemma 4.1: Der Algorithmus hat eine Laufzeit von O(n · m). In jedem der drei Fälle wird im Eingabetext um mindestens 1 nach rechts gesprungen. Im zweiten Fall kommt es zusätzlich noch zu einem Vergleich mit allen Pattern. Die Laufzeit beträgt also im Worstcase O(n · m). ❏ Lemma: 4.2 Der Algorithmus findet alle Auftreten der Pattern, insbesondere wird kein Auftreten übersprungen. Im ersten Fall wird ein Zeichen eingelesen, welches in keinem der Pattern vorkommt. Danach wird der Eingabetext so verschoben, dass genau dieses Zeichen übersprungen wird. Dabei ist es unmöglich, ein Auftreten eines Patterns zu überspringen, weil ja keins der Pattern diesen Buchstaben enthält. Im zweiten Fall testen wir alle Pattern und verschieben dann um 1. Ein Überlesen ist also unmöglich. Im dritten Fall wird das gelesene Zeichen T [x] mit dem rechtesten Auftreten dieses Zeichens in einem der Pattern in Übereinstimmung gebracht. Alle kleineren Verschiebungen würden keinen Treffer liefern, weil das Zeichen T [x] gegen ein Zeichen ungleich T [x] ausgerichtet wäre. ❏ Wir haben also einen Algorithmus, welcher unser Problem löst. Allerdings erkennt man schnell, dass dieser Algorithmus noch sehr ineffizient ist. Insbesondere bei relativ vielen Pattern würde oft der zweite Fall eintreten und der Algorithmus würde praktisch dem Brute Force entsprechen. Daher wollen wir jetzt zwei Verbesserungen betrachten, mit denen dieser Algorithmus konkurrenzfähig“ wird: ” Verbesserung 1: Vergrößern“ des Alphabetes ” Anstatt immer nur ein Zeichen des Eingabetextes zu betrachten, nehmen wir jetzt ein Lesefenster9 der Länge B. Damit verändern sich die 3 obigen Fälle folgendermaßen: 1.) Kommt der aktuelle Inhalt des Lesefensters in keinem der Pattern vor, so verschieben wir unser aktuelles Lesefenster um lmin − B + 1 Positionen. 9 in den Grafiken als hellblaues Rechteck dargestellt 30 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Abbildung 14: Fall 1: Der Inhalt des Lesefensters, in diesem Fall WT“ ” kommt in keinem der pi ’s vor. Wir können also das Lesefenster um lmin − B + 1 = 4 − 2 + 1 = 3 Positionen nach rechts verschieben. 2.) Ist der Inhalt des Lesefensters Suffix von einem der Pattern, so testen wir ob eins der Pattern an der aktuellen Position im Eingabetext vorkommt. Danach verschieben wir unsere Lesefenster um eine Position nach Rechts. Abbildung 15: Fall 2: Der Inhalt des Lesefensters, in diesem Fall AU“ kommt ” in p3 vor. Wir müssen also testen, ob eins der Pattern pi an dieser Stelle auftritt. 31 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 3.) Ansonsten verschieben wir das Lesefenster so, dass der gerade gelesene Block genau mit dem rechtesten Auftreten dieses Blocks in einem der Pattern übereinstimmt. Abbildung 16: Fall 3: Der Inhalt des Lesefensters, in diesem Fall TE“ kommt ” in p1 vor. Wir verschieben unser Lesefenster also um 2 Positionen, um das TE“ im Eingabetext dem TE“ in p1 gegenüberzustellen. ” ” Verbesserung 2: Optimierung der Vergleiche im Fall 2 Anstatt im zweiten Fall immer Brute Force gegen alle Pattern zu vergleichen, wollen wir nur die Pattern testen, die auch auf das aktuelle Zeichen enden. Dafür sortieren wir die Pattern einfach nach den letzten B Zeichen. Denkbar ist dazu auch die Implementierung eines Suchbaumes, ähnlich wie es beim Multi Pattern Karp-Rabin (siehe Kapitel 4.2) gemacht wird, sowie der Einsatz der von Muth und Manber in [MM96] vorgestellten Technik des Two-Level-Hashing. Besonders effizient ist eine solche Implementierung, wenn bereits eine Ordnung >“ auf Σ gegeben ist. Dies ist Beispielsweise der Fall, wenn die Zeichen des Ein” gabetextes im Algorithmus als Zahlen gespeichert werden (ASCII-Werte). 4.4.2 Vorverarbeitung Im ersten Schritt bringen wir alle pi ∈ Π auf dieselbe Länge, indem wir jeweils nur die ersten lmin = min {lp1 , · · · , lpr } Zeichen betrachten. Die verkürzten Pattern bezeichnen wir mit p0i . Anschließend sortieren wir die p0i ’s nach den letzten B Zeichen. Dies benötigt O(B · r · log(r)) Schritte. Nun bestimmen wir die Funktion Shif t : |Σ|B → {0, · · · , lmin }, welche uns für die aktuellen B Zeichen angeben soll, um wie viele wir unser Lesefenster nach rechts verschieben können. Dazu setzen wir zuerst Shif t(x) = lmin − B + 1 für alle x ∈ ΣB . Dies ist die Verschiebung für den Fall 1. Anschließend durchlaufen wir nacheinander sämtliche Pattern p0i ∈ Π0 , und bestimmen dabei für jedes Pattern alle Teilworte der Länge B. Sei t ein solches Teilwort und sei pos die Position des Teilwortes in dem aktuellen Pattern. Dann setzen wir: 32 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Shif t(x) = min(Shif t(x), lmin − B + 1 − pos). Kommt das Teilwort x genau am Ende des aktuellen Patterns vor, so ist pos = lmin −B+1,. Für dieses Teilwort wird die Shif t-Funktion also 0 (Entspricht Fall 2). Die Konstruktion der Shif t-Funktion geschieht in O(m). 4.4.3 Suchphase Die Suchphase ist jetzt eigentlich ganz einfach: Sei x der aktuelle Inhalt unseres Lesefensters. Solange Shif t(x) > 0 gilt, schieben wir unser Lesefenster um x Positionen nach rechts. Gilt Shif t(x) = 0, so testen wir alle p0i s ∈ Π deren zugehöriges p0i ∈ Π0 auf x endet, ob sie an der aktuellen Position vorkommen. Dazu verwenden wir einfach den Brute Force oder einen der bekannten Single Pattern Matching Algorithmen. 4.4.4 Wahl der Blockgröße B Bei der Wahl der Blockgröße B spielen neben der begrenzenden Größe durch den verfügbaren Speicher 2 Faktoren eine Rolle: Je größer B ist, desto mehr verschiedene Blöcke der Größe B gibt es. Damit steigt die Wahrscheinlichkeit, dass ein Block x in keinem der Pattern am Ende vorkommt und damit wird man im Mittel öfter einen Wert Shif t(x) > 0 haben. Wir können also öfter verschieben. Wenn ein Block aber nicht in einem Pattern vorkommt, dann können wir die nächsten lmin − B + 1 Positionen überspringen. Das bedeutet allerdings, dass wir umso weniger Positionen pro Verschiebung überspringen können je größer B ist. Sun Wu und Udi Manber schlagen in [WM94] als optimalen Wert für B daher log|Σ| 2m , r P m= |pi | vor. i=1 Lemma: Pr Für B = log|Σ| 2m , m = i=1 |pi | ist die Wahrscheinlichkeit, dass eine zufällige Zeichenkette der Länge B zu einem Shift größer 0 führt mindestens 12 . Beweis: Es gibt |Σ|B = |Σ|dlog|Σ| 2me ≥ |Σ|log|Σ| 2m = 2m verschiedene Worte der Länge B. m ist definiert als die Summe aller Patternlängen und repräsentiert somit alles, was in die Berechnung der Shiftfunktion eingeht. Folglich führen mindestens 2m − m Worte zu einem Shiftwert größer 0. Anmerkung: Was Wu und Manber dabei allerdings nicht bedacht haben, ist die Tatsache, dass das so berechnete B bei sehr vielen Pattern größer sein kann als lmin ! Das macht natürlich keinen Sinn, weil man ein Wort der Länge lmin nicht in Worte der Länge lmin + 1 zerlegen kann. Die Grenze, bis zu der eine Berechnung der Blockgröße B nach der von Wu und Manbers vorgeschlagenen Formel sinnvoll berechnet werden können, kann man leicht erkennen: Wenn man mehr als die Hälfte aller Worte der Länge lmin als Pattern verwendet, so ist ein zufällig gewähltes Wort der Länge lmin mit einer Wahrscheinlichkeit größer als 21 33 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik in der Menge der Pattern und somit muss der Wert der Shift-Funktion nach Definition 0 sein. Man kann dieses Problem technisch umgehen, indem man fordert, dass B maximal lmin groß sein darf. Allerdings gilt dann das Lemma wonach die Wahrscheinlichkeit eines Shifts wenigstens 12 ist nicht mehr. Daher sollte man sich überlegen, ob bei derartig großen Mengen an Pattern der Einsatz einer Filterfunktion generell noch sinnvoll ist (siehe dazu auch: Kapitel 5). 4.4.5 Verbesserungen Bei großen Alphabeten ist der Algorithmus sehr Speicheraufwendig, weil wir bisher den Wert der Shif t-Funktion für alle |Σ|B abgespeichert haben. Wir haben allerdings schon anhand von Lemma 4.1 und Lemma 4.2 gesehen, dass der Algorithmus, egal wo sich das Lesefenster befindet, kein Auftreten der Pattern überliest. Insbesondere bedeutet dies, dass wenn man das Lesefenster um weniger als Shif t(x) Positionen, also weniger als eigentlich möglich wäre, verschiebt, der Algorithmus trotzdem alle Auftreten der Pattern findet. Wir können also auch eine Hashfunktion (Hash (x) : |Σ|B → XHash ) verwenden, um x ∈ |Σ|B auf einen kleineren Wertebereich abzubilden. Dann können wir eine neue, kleinere Shiftfunktion definieren: Shif t0 (y) : XHash → N. Wichtig ist, dass die neue Shiftfunktion folgende Bedingung erfüllt: Shif t0 (Hash (x)) ≤ Shif t (x) für alle x ∈ |Σ|B . Dies lässt sich am einfachsten sicherstellen, indem man für Shif t0 (y) = min {Shif t(x)|Hash(x) = y} wählt. x∈|Σ|B Eine weitere Verbesserung des Algorithmus sei noch kurz angesprochen, welche insbesondere bei Sprachen sinnvoll ist. Und zwar ist es bei Sprachen häufig so, dass gewisse Endungen sehr oft vorkommen (bspw. die Endung ing“ im Englischen oder lich“ im ” ” Deutschen). Daher ist es in solchen Fällen sinnvoll, die Pattern neben der Sortierung der verkürzten Pattern außerdem noch nach den ersten10 Buchstaben zu sortieren. Dadurch lässt sich im Fall 2 die Anzahl der nötigen Vergleiche deutlich reduzieren, weil man nur noch die Pattern gegen den Eingabetext prüfen muss, welche auch ein passendes Präfix“ ” haben. In unserer konkreten Problemstellung sehen wir die Verteilung der DNA-Basen als komplett zufällig an, daher ist eine solche Erweiterung nicht notwendig.11 10 Es empfiehlt sich die ersten B Buchstaben zu verwenden, weil man dann die oben definierte Hashfunktion auch für die Präfixe wiederverwenden kann. 11 Wer dennoch mehr wissen möchte, dem empfehle ich [WM94] 34 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 4.5 q-Gramme Viele Algorithmen für das String Matching, insbesondere Varianten des Boyer-Moore Algorithmus, haben eine gute Performance für große Alphabete. In dem vorangegangen Kapitel haben wir dies auch beim Wu-Manber Algorithmus gesehen. Um die Performance dabei zu Verbessern, haben wir das Alphabet scheinbar vergrößert, indem wir mehrere Buchstaben sozusagen zu einem zusammengefasst haben. Diesen Trick wollen wir jetzt formalisieren und auf weitere Algorithmen anwenden. Definition 4.2: Ein q-Gramm ist ein Wort der Länge q. Sei w = w[1..m] ein Wort der Länge m ≥ q. Dann ist {w[1 .. q], w[2 .. q + 1], · · · , w[m − q .. q]} eine Zerlegung von w in überlappende q-Gramme. n hl m io Entsprechend ist w [1 .. q] , w [q + 1 .. 2 · q] , · · · , w m − 1 q .. q eine Zerlegung q hl m i von w in fortlaufende q-Gramme. Dabei ist zu beachten, dass w m − 1 q .. q q hl m i m eventuell nicht die Länge q hat. In diesem Fall ergänzen wir w q − 1 q .. q um hl m i − 1 q .. q / Σ zu einem q-Gramm. q − w m Zeichen $ ∈ q Beispiel: w = KATZE“ ” Zerlegung von w in überlappende q-Gramme mit q = 2: KA, AT, TZ, ZE Zerlegung von w in fortlaufende q-Gramme mit q = 2: KA, TZ, E$ Salmela, Tarhio und Kytöjoki haben 2006 in [STK06] gezeigt, wie man mittels q-Grammen verschiedene12 Single Pattern Matching Algorithmen effizient für Multi Pattern Matching einsetzen kann. Die Idee dabei ist zuerst ein verallgemeinertes Pattern aus q-Grammen zu erstellen, welches alle original Pattern enthält. Anschließend verwendet man einen Single Pattern Algorithmus um dieses verallgemeinerte Pattern im Eingabetext zu suchen. Die ursprünglichen Single Pattern Algorithmen arbeiten so als Filterfunktion für das Multi Pattern Matching. Die Arbeitsweise der so gewonnenen neuen Multipattern Algorithmen lässt sich also in 3 Schritte unterteilen: 1) Vorverarbeitung der Pattern zu einem verallgemeinerten q-Gramm Pattern. 2) Suche nach diesem verallgemeinerten Pattern mit einem Single Pattern Matching Algorithmus. Der Single Pattern Matching Algorithmus wird so modifiziert, dass er über einem durch Bildung von q-Grammen vergrößerten Alphabet sucht. 3) Überprüfen von den in Schritt 2 gefundenen Kandidaten mit einem (anderen) Single Pattern Matching Algorithmus, ob ein Auftreten eines Patterns vorliegt. 12 konkret gezeigt wird dies in dem Paper für Boyer-Moore-Horspool, Shift-Or und Backward Nondeterministic DAWG Matching (BNDM) 35 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Anstatt in Schritt 3 mit einem Single Pattern Matching Algorithmus alle Pattern zu überprüfen, schlagen L.Salmela et al. vor, dies mit einem modifizierten Karp-Rabin Algorithmus zu machen (vergleiche auch Kapitel 4.3). In der Vorverarbeitungsphase wird dazu für jedes Pattern ein Hashwert (Fingerprint) berechnet und die Pattern in einer nach den Hashwerten sortierten Liste gespeichert. Somit wird Schritt 3 noch mal in 2 Schritte aufgespaltet: 3a) Überprüfen, ob zu den in Schritt 2 gefundenen Kandidaten ein passender Hashwert abgespeichert ist. 3b) Bestimmung der zu diesem Hashwert passenden Pattern (Aufwand: O(m · log(m))) und anschließend vergleich dieser Pattern gegen den Eingabetext. 4.5.1 Boyer-Moore-Horspool mit q-Grammen Im folgenden Kapitel wollen wir jetzt obige Konstruktion eines Multi Pattern Algorithmus aus einem Single Pattern Algorithmus durch den Einsatz von q-Grammen am Beispiel Boyer-Moore-Horspool betrachten. Es gibt verschiedene Varianten des Boyer-Moore Algorithmus. Die Grundidee ist immer, dass der Vergleich der Pattern gegen den Eingabetext von rechts nach links gemacht wird. Bei großen Alphabeten ist die Chance recht groß, dass es früh zu einer Nichtübereinstimmung kommt. In dem Fall einer Nichtübereinstimmung wird dann mittels Match- und Occurrence Heuristik das Pattern bis zur nächsten sinnvollen Ausrichtung über den Eingabetext geschoben (siehe [BM77] und [Par03]). Wir wollen die von Hoorspool [Hor80] vorgeschlagene Variante des Boyer-Moore Algorithmus betrachten. Sie eignet sich besonders für große Alphabete und benutzt nur die Occurrence-Heuristik. Der Boyer-Moore-Horspool Algorithmus arbeitet folgendermaßen: In der Vorverarbeitungsphase wird die Bad Character Funktion“ B(x) für das Pattern ” p = [1..m] berechnet. Sie gibt an, um wie viel das Pattern bei einer Nichtübereinstimmung verschoben werden kann: Abbildung 17: Verschiebung bei Nichtübereinstimmung Die Bad Character Funktion“ ist definiert als Abstand des letzten Auftretens von x ” in p[1..m − 1] zum Ende des Patterns: B(x) = min {h|p[m − h] = x, h ≥ 1}. Kommt x nicht in p[1..m − 1] vor, so setzten wir B(x) = m. 36 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Abbildung 18: Kommt das a nicht im Pattern vor, so können wir das Pattern bis zu dem ersten Zeichen nach a im Eingabetext verschieben. In der Suchphase positionieren wir das Pattern über dem Eingabetext und vergleichen zuerst das letzte Zeichen vom Pattern p[m] gegen den Eingabetext. Kommt es dabei zu einer Nichtübereinstimmung, so verschieben wir das Pattern gegen den Eingabetext um B(p[m]) Positionen nach Rechts. Andernfalls überprüfen wir die restlichen Zeichen des Patterns gegen den Eingabetext. Bei einer Nichtübereinstimmung verschieben wir ebenfalls das Pattern um B(p[m]) Positionen nach rechts. Um diesen Algorithmus jetzt für das Multi Pattern Matching zu verwenden, benötigen wir die Vorraussetzung, dass alle Pattern dieselbe Länge haben. Dies erreichen wir, indem wir (wie schon beim Wu-Manber Algorithmus) nur die ersten lmin Zeichen jedes Patterns verwenden, mit lmin = min {lp1 , · · · , lpr }. Die Pattern werden anschließend in überlappende q-Gramme zerlegt. Aus diesen u = lmin − q + 1 q-Grammen pro Pattern werden nun u Tabellen erstellt, wobei die erste Tabelle alle q-Gramme enthält, die an erster Position in einem der Pattern auftauchen. Die zweite Tabelle enthält alle q-Gramme, die in einem der Pattern an erster oder an zweiter Position auftreten. Die dritte Tabelle alle q-Gramme, die an erster, zweiter oder dritter Stelle in einem der Pattern auftreten, usw. Abbildung 19 zeigt die 2-Gramm ” Tabellen“ für die Pattern HAUS, HAND, MIAU“. ” 1. Tabelle HA MI 2. Tabelle HA MI AU AN IA 3. Tabelle HA MI AU AN IA US ND Abbildung 19: 2-Gramm Tabellen“ für die Pattern HAUS, HAND, MIAU“ ” ” Diese Tabellen kann man nun folgendermaßen als Filteralgorithmus verwenden: Wir betrachten jeweils m Zeichen des Eingabetextes, sozusagen als Sliding Window. Wir vergleichen nun, ob das rechteste q-Gramm im Sliding Window in der u-ten Tabelle vorkommt. Ist dies nicht der Fall, so kommt dieses q-Gramm in keinem der Pattern an irgendeiner Position vor, wir können also die nächsten u Positionen überspringen. Andernfalls schauen wir nach, ob das 2t-rechteste q-Gramm in der u − 1-ten Tabelle vorkommt. Ist dies nicht der Fall, so kommt das q-Gramm in keinem der Pattern an Position 1, · · · , u − 1 vor, wir können also um u − 1 Positionen nach rechts verschieben. Sind wir bei der ersten Tabelle angelangt, und kommt das linkeste q-Gramm im Sliding Window in dieser Tabelle vor, so haben wir einen Kandidaten für ein mögliches Auftreten eines Patterns gefunden. Wie in Schritt 3 beschrieben, wendet man nun einen Single Pattern Algorithmus an, um zu überprüfen, ob an dieser Stelle wirklich ein Auftreten eines 37 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik der Pattern vorliegt. Beispiel: Eingabetext = DAUSIMIAUH Pattern={HAUS, HAND, MIAU} q = 2, die zugehörigen 2-Gramm Tabellen sind in Abbildung 19 dargestellt Abbildung 20: Suchphase des Boyer-Moore-Horspool mit q-Grammen 38 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 4.6 Multiple Approximate String Matching with l-Grams Dieser 2004 von Fredriksson und Navarro in [FN04] vorgeschlagene Algorithmus verwendet die im vorherigen Kapitel definierten fortlaufenden q-Gramme. Er wurde konstruiert, um das Multiple Approximate String Matching Problem zu lösen, also die approximative Suche nach mehreren Pattern. Da die exakte Suche nach mehreren Pattern ein Sonderfall (k = 0) der approximativen Suche von mehreren Pattern mit maximal k Unterschieden ist, lässt sich der Algorithmus auch für unser Problem verwenden. Zuerst wollen wir formal definieren, was k Unterschiede sind: Definition: Ein Tupel (r, s) ∈ Σ0 × Σ0 mit r 6= s heißt einfache Edit-Operation, Σ0 := Σ ∪ {} Es gibt drei Arten von einfachen Edit-Operationen: 1) gilt r = so heißt (r, s) Einfügung. 2) gilt s = so heißt (r, s) Löschung. 3) Andernfalls heißt (r, s) Substitution. Man schreibt auch r → s für die einfache Edit-Operation (r, s). Definition: 2 Worte a ∈ Σ∗ , b ∈ Σ∗ haben maximal k Unterschiede, wenn a mit maximal k einfachen Edit-Operationen in b überführt werden kann. a und b haben genau k Unterschiede wenn a mit maximal k einfachen Edit-Operationen in b überführt werden kann und es kein n < k gibt, so dass a mit maximal n einfachen Edit-Operationen in b überführt werden kann. Schreibweise: #U nterschiede (a, b) = k Beispiel: a = HUNDE b = HAND Σ = {A, D, E, H, N, U } a kann mit 2 einfachen Edit-Operationen in b überführt werden: (U,A) HUNDE → HANDE (E,) HANDE → HAND 4.6.1 Vorverarbeitung Sei q ein fester Wert. Als erstes Zerlegen wir alle Pattern in fortlaufende q-Gramme und bilden die Menge Gram, welche alle dieser dabei entstehenden q-Gramme enthält. Anschließend berechnen wir die Funktion D : Σq → N für jedes mögliche q-Gramm. Die Funktion D wird auch Distanz-Tabelle genannt. D(g), g ∈ Σq soll dabei die minimale Anzahl an Unterschieden des q-Gramm g zu einem beliebigen q-Gramm aus Gram sein: D(g) := min {#U nterschiede (g, h)|h ∈ Gram} 39 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 4.6.2 Suchphase Es sei m = min {|p1 |, · · · , |pr |} die kleinste gemeinsame Länge aller Pattern. Wir betrachten ein Lesefenster der Länge m − k über dem Eingabetext. Für jede Position i + 1, · · · , i + m − k des Lesefensters lesen wir sukzessiv fortlaufende q-Gramme ein, beginnend vom Ende des Lesefensters. Sei also S1 = T [i + m − k − q + 1..i + m − k] das rechteste q-Gramm im Fenster, S2 = T [i + m − k − 2q + 1..i + m − k − q] das zweitrechteste,... usw bis Sv = T [i + m − k − v · q + 1..i + m − k − (v − 1) · q], dem q-Gramm am Anfang des Lesefensters. Jedes Auftreten eines Patterns, welches am Anfang des Lesefensters beginnt, muss alle diese q-Gramme vollständig enthalten. Insbesondere bedeutet das, dass diese q-Gramme S1 , · · · , Sv bis auf k Ausnahmen in Gram enthalten sein müssen. Noch genauer, die Summe der P Abweichungen zu q-Grammen aus Gram darf k nicht überschreiten, es gilt also: k ≤ vt=1 D(St ). Diese Eigenschaft nutzen wir jetzt als Filteralgorithmus aus: Wir lesen sukzessiv fortlaufende q-Gramme beginnend vom Ende des Lesefensers ein und berechnen die Summe der P Abweichungen Mu = ut=1 D(St ). Wird die Summe irgendwann größer als k, dann kann kein Pattern im Eingabetext vorkommen, denn die Funktion D stellt die minimal notwendige Anzahl an Abweichungen zu beliebigen q-Grammen innerhalb aller Pattern da. Sobald Mu also größer als k wird, können wir das Lesefenster auf die erste Position, welche nicht alle der eingelesenen q-Gramme enthält, verschieben. In dem Fall also auf Position i + m − k − u · q + 2. Erreicht man beim Einlesen der q-Gramme den Anfang des Lesefensters ohne das Mu größer als k wird, so hat man einen Kandidaten für das Auftreten eines Patterns gefunden und muss nun mit einem anderen Algorithmus verifizieren, ob wirklich ein Auftreten eines Patterns vorliegt. Im Fall k = 0 kann man dies mit einem der bekannten Single Pattern Algorithmen für jedes Pattern einzeln prüfen. Alternativ bietet sich auch, wie es beim Boyer-Moore-Horspool mit q-Grammen vorgeschlagen wurde, die Verwendung des Karp-Rabin Algorithmus an. Für k > 0 benötigt man zum Verifizieren eines Treffers einen Algorithmus für das approximative Pattern Matching. Fredriksson und Navarro verwenden dafür den Myers Algorithmus [Mye99] bzw. den Algorithmus von Sellers [Par04]. Beispiel: p1 = AABC, p2 = BBAA, p3 = BCBB Σ = {A, B, C} T = CACCBAABCBAC, k = 0, also keine Approximative Suche! Vorverarbeitung: g Auftreten in Pattern mit D(g) Unterschieden AA 0 p1 : AABC AB 0 p1 : AABC AC 1 p1 : AABC BA 0 p2 : BBAA BB 0 p3 : BCBB BC 0 p1 : AABC CA 1 p3 : BCBB CB 0 p3 : BCBB CC 1 p3 : BCBB Tabelle 4: Distanz-Tabelle D für 2-Gramme 40 D(g) Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Suchphase: Abbildung 21: Suchphase des Algorithmus von Fredriksson und Navarro 41 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 4.6.3 Anmerkung In dem Beispiel können wir schon erkennen, dass der Algorithmus Multiple Approxi” mate String Matching with l-Grams“ für kleine Alphabete in der Distanz-Tabelle D nur sehr wenige Einträge ungleich 0 hat, welche überhaupt zu einem vorzeitigen Verschieben während der Suchphase führen könnten. In Übereinstimmung mit dieser Beobachtung berichten Salmela, Tariho und Kytöjki in [STK06], dass die Verwendung von fortlaufenden q-Grammen für das Multi Pattern Matching generell deutlich schlechter ist, als die Verwendung von überlappenden q-Grammen. Wie der Name Multiple Approximate String Matching with l-Grams“ allerdings schon ” verrät, wurde dieser Algorithmus nicht für das einfache Multi-Pattern-Matching entworfen, sondern für die approximative Suche. Welche Art von q-Grammen für die approximative Zeichenkettensuche besser geeignet wäre, ist eine andere interessante Fragestellung, welcher wir an dieser Stelle aber nicht weiter nachgehen werden. 42 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 5. Implementierung Für die Implementierung stellen sich zwei grundlegende Fragen: 1) In welcher Programmiersprache soll das Programm geschrieben werden? 2) Welcher Algorithmus soll verwendet werden? Die Frage der Programmiersprache haben wir schon in Kapitel 3.3 betrachtet. Zur Diskussion standen im Wesentlichen drei Varianten: a) sauberes, objektorientiertes Java b) dirty“ Java, also teilweise prozedurale Programmierung sowie Verwendung von Arrays ” anstatt höheren“ Objekten ” c) C++ Variante a) ist dabei deutlich langsamer und ineffizienter, was die Speicherausnutzung angeht. Dies wird aus den Messungen in Kapitel 3.3 deutlich. Da es bereits einige Programme zu verwandten metagenomischen Problemen gibt und diese alle in Java geschrieben sind und da die MHH bereits Erfahrungen mit Java gemacht hat, fiel die Entscheidung auf die Variante b) obwohl eine Implementierung in C++ nach den Messergebnissen wahrscheinlich geringfügig effizienter gewesen wäre. Um zu Entscheiden, welcher Algorithmus am besten für dieses Problem geeignet ist, wollen wir die Vor- und Nachteile der einzelnen Algorithmen gegenüberstellen: Laufzeit (Worstcase): Vorverarbeitung Suchphase Speicherbedarf Filteralgorithmus AC WM BMq MASM MKR O(m) O(n) O(m) Nein O(m · log(m)) O(m · n) O(|Σ|B ) Ja O(m) O(m · n) O(m) Ja O(m) O(m · n) O(|Σ|l ) Ja O(m) O(m · n) O(m) Ja Tabelle 5: Übersicht über die verschiedenen Algorithmen. In Tabelle 5 werden folgende Abkürzungen verwendet: Abkürzung AC WM BMq MASM MKR Volle Bezeichnung Aho Corasick Wu-Manber Boyer-Moore mit q-Grammen Multiple Approximate String Matching with l-Grams Multipattern Karp-Rabin Von der Laufzeit im Worstcase her schneidet der Aho Corasick am besten ab. Allerdings wissen wir bereits aus der Theorie, dass der Algorithmus von Wu und Manber, Multiple Approximate String Matching with l-Grams sowie der Boyer-Moore Algorithmus mit q-Grammen im Mittel schnellere Laufzeiten als im Worstcase haben. Ähnlich wie der Boyer-Moore-Algorithmus für das Single Pattern Matching sind nämlich alle drei Algorithmen in der Lage, gewisse Positionen ohne Vergleich zu überspringen. Der Multipattern 43 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Karp-Rabin kann zwar keine Positionen überspringen, im Bestcase arbeitet auch er aber in O(n). Der Aho Corasick hingegen arbeitet sowohl im Worstcase als auch im Bestcase immer in O(n). Beim Betrachten des Speicherbedarfes fällt auf, dass dieser bei dem Algorithmus von Wu und Manber nicht von der Anzahl der Pattern abhängt, sondern nur von der gegebenen Alphabetgröße |Σ| sowie der frei wählbaren Blockgröße B. Dies ist eine sehr positive Eigenschaft, allerdings muss man auch hier wieder relativieren, da dies nur der Speicherbedarf für die Filterfunktion ist. Findet man mit der Filterfunktion einen Kandidaten für einen möglichen Treffer, muss anschließend für jedes Pattern einzeln getestet werden, ob ein Auftreten vorliegt. Für diesen anschließenden Vergleich ist es zwar nicht nötig, dass sich alle Pattern gleichzeitig im Speicher befinden, in der Praxis wird dies aber aus Performancegründen vermutlich dennoch der Fall sein. Dasselbe gilt für den Algorithmus Multiple Approximate String Matching with l-Grams“, hier hängt der Speicherbedarf ” ebenfalls nicht von der Anzahl der Pattern ab, sondern von der gegebenen Alphabetgröße |Σ| sowie der frei wählbaren Größe q der q-Gramme. Betrachten wir also den letzten Wert in der Tabelle, also die Eigenschaft Filteralgorithmus zu sein. Wir haben bei unserem Problem ein kleines Alphabet |Σ| = 4 gegeben, sowie extrem viele Pattern13 . Filteralgorithmen scheinen dafür auf den ersten Blick vermutlich ungeeignet zu sein. Um das genauer zu quantifizieren, wollen wir den Filterwert für eine perfekte Filterfunktion14 bestimmen. Der Filterwert sei definiert als Anzahl der zu filternden Worte geteilt durch die Anzahl der möglicher Worte. Es gibt 414 = 268.435.456 verschiedene Worte der Länge 14 in dem zugrundeliegenden Alphabet. Wenn wir davon ausgehen, dass wir 105 = 100.000 verschiedene Pattern suchen, dann würde eine perfekte Filterfunktion einen Filterwert von gerundet 0.00037 haben. Wenn wir also zufällig Worte der Länge 14 wählen würden, würde unsere Filterfunktion 99, 963% aussortieren. Für eine Filterfunktion wäre dies ein extrem guter Wert. Alle vorgestellen Filteralgorithmen haben allerdings die für das Filtern verwendete Patternlänge auf lmin = {l1 , · · · , lm } reduziert. In diesem Fall müssen wir von einer gemeinsamen Mindestlänge von lmin = 8 der Pattern ausgehen (siehe Kapitel 3.1). Im zugrundeliegenden Alphabet gibt es aber nur 48 = 65.536 verschiedene Worte der Länge 8. Bei 105 = 100.000 verschiedenen Pattern hätten wir daher mehr Pattern als es überhaupt Worte gibt. Stochastisch entspricht die Belegung der 100.000 Pattern auf die 65.535 verschiedenen Worte der Länge 8 dem Verteilen von k ununterscheidbaren Kugeln auf n Fächer mit Mehrfachbesetzung. Betrachten wir dies als Binomialverteilung mit Wahrscheinlichkeit n1 und Gegenwahrscheinlichkeit n−1 n , so beträgt die Wahrscheinlichkeit dafür, dass ein Fach m-fach besetzt ist: m 1 n 1 n − 1 k−m · Bin k, ({m}) = · n k n n 13 aktuelle Markersätze bestehen aus etwa 2 · 104 Markern. Bis Ende des Jahres muss bereits mit Markersätzen von 105 Markern gerechnet werden 14 Eine perfekte Filterfunktion sei in diesem Zusammenhang eine Filterfunktion, welche keine False” Positive“’s liefert. 44 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Konkret interessiert uns die Wahrscheinlichkeit dafür, dass ein Fach leer bleibt. Denn übertragen auf unsere Filterfunktion bedeutet ein leeres Fach, dass es zu diesem Wort der Länge 8 kein Pattern gibt welches gesucht wird, und folglich die Filterfunktion diesen Wert ohne weitere Vergleich aussortieren (= ˆ kein Treffer) kann. In unserem Fall ist diese Wahrscheinlichkeit: Bin 100.000, 1 65.536 0 65.536 1 65.535 100.000 · · 0 65.536 65.536 100.000 65.535 = 65.536 ≈ 0, 2174 ({0}) = Im Mittel werden also 21, 74% der Fächer leer bleiben. Bei der Filterfunktion würde dies bedeuten, dass bei zufälligen Worten der Länge 8 nur 21, 74% der Worte aussortiert werden würden. Dies entspricht einem Filterwert von gerundet nur 0, 78. Berücksichtigt man nunnoch, dass die Anzahl der Pattern in den nächsten Jahren noch deutlich höher liegen wird und dass dies für eine Filterfunktion ohne False-Positives gerechnet ist, so erkennt man schnell, dass für die gegebenen Problemgrößen Filterfunktionen, welche auf der kleinsten gemeinsamen Patternlänge basieren, ungeeignet sind. Da bis auf den Aho Corasick Algorithmus alle vorgestellen Algorithmen auf derartigen Filterfunktionen basierten folgt alleine aus der Theorie, dass der beste Algorithmus für dieses Problem der Aho Corasick ist. 5.1 Messungen: Vergleich der Laufzeiten Nach dem theoretischen Teil folgen nun ein paar Messungen zu Implementierungen von einigen der vorgestellten Algorithmen. Alle Messungen wurden auf einem Laptop mit einem r r Intel Pentium M (1,8 Ghz) und 1048 MByte RAM unter Windows XP Professional mit Service Pack 2 durchgeführt. Verwendet wurde die JavaTM Standard Edition 6 (build 1.6.0 05-b13). Für die Messungen wurden folgende Algorithmen implementiert: • Aho Corasick • vollständiger“ Aho Corasick nach Kapitel 4.3.6 ” • Wu Manber • Brute Force In erster Linie interessant ist dabei der Vergleich des Aho Corasick Algorithmus mit dem vollständigen“ Aho Corasick. Die anderen Algorithmen kommen bei den gegebenen Ein” gabegrößen und nach der theoretischen Betrachtung für dieses spezielle Problem sowieso nicht mehr in Frage. Als Vergleichswert habe ich aber dennoch den Algorithmus von Wu und Manber (Als Referenzwert für die Filteralgorithmen) sowie den Brute Force (als Kontrollgröße) implementiert. 45 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Für den ersten Testlauf wurde ein Markersatz von 33.373 Markern verwendet. Als Probedatei wurde eine 3,09MB große Fasta-Datei genommen, welche 30.000 Reads mit Längen zwischen 17 und 44 Basenpaaren enthält. Die Fasta-Datei wurde künstlich erzeugt. Ergebnis mit dem Aho Corasick Algorithmus: ———————————————– Gelesene Marker: 33373 Gelesene Reads: 30000 Gesamtzahl Treffer: 465 (1,55%) ———————————————– Benötige Zeit: 6s davon Vorverarbeitung: 4s davon Suche: 2s Ergebnis mit dem vollständigen“ Aho Corasick Algorithmus: ” ———————————————– Gelesene Marker: 33373 Gelesene Reads: 30000 Gesamtzahl Treffer: 465 (1,55%) ———————————————– Benötige Zeit: 9s davon Vorverarbeitung: 6s davon Suche: 3s Ergebnis mit Brute Force: ———————————————– Gelesene Marker: 33373 Gelesene Reads: 30000 Gesamtzahl Treffer: 465 (1,55%) ———————————————– Benötige Zeit: 926s Ergebnis mit dem Algorithmus von Wu und Manber für B = 5: ———————————————– Gelesene Marker: 33373 Gelesene Reads: 30000 Gesamtzahl Treffer: 465 (1,55%) ———————————————– Benötige Zeit: 1276s davon Vorverarbeitung: 1120s davon Suche: 156s Als erstes fällt auf, dass der Algorithmus von Wu und Manber langsamer arbeitet als der Brute Force. Der Grund dafür ist, dass die Vorverarbeitung sehr viel Zeit beansprucht. Da bei diesem Durchlauf sehr viele Marker im Verhältnis zu recht wenigen und sehr kurzen Reads gegeben waren, fällt dies natürlich besonders auf. Andererseits muss auch erwähnt werden, dass die Vorverarbeitung sich bestimmt auch wesentlich eleganter und effizienter implementieren lässt, als es für diese Messung der Fall 46 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik war. Der Unterschied zwischen dem Aho Corasick Algorithmus und dem vollständigen“ Aho ” Corasick ist minimal. In der Vorverarbeitungsphase ist der Aho Corasick erwartungsgemäß etwas (2 Sekunden) schneller, weil für den vollständige“ Aho Corasick zusätzlich ” die Funktion next0 (q, b) für alle q ∈ Q und alle b ∈ Σ berechnet werden muss. Dass der Aho Corasick auch in der Suchphase minimal (1 Sekunde) schneller ist, überrascht ein bisschen. Für den zweiten Testlauf wurde wieder der Markersatz mit 33.373 Markern verwendet. Als Probe-Datei wurde diesmal eine 165MB große Fasta-Datei genommen, welche 138.453 Reads unterschiedlicher Längen enthielt. Die Probe stammt übrigens aus [oCI]. Ergebnis mit dem Aho Corasick Algorithmus: ———————————————– Gelesene Marker: 33373 Gelesene Reads: 138453 Gesamtzahl Treffer: 0 (0%) ———————————————– Benötige Zeit: 645s davon Vorverarbeitung: 4s davon Suche: 641s Ergebnis mit dem vollständigen“ Aho Corasick Algorithmus: ” ———————————————– Gelesene Marker: 33373 Gelesene Reads: 138453 Gesamtzahl Treffer: 0 (0%) ———————————————– Benötige Zeit: 635s davon Vorverarbeitung: 6s davon Suche: 629s Ergebnis mit Brute Force: ———————————————– Gelesene Marker: 33373 Gelesene Reads: 138453 Gesamtzahl Treffer: 0 (0%) ———————————————– Benötige Zeit: 143723s Ergebnis mit dem Algorithmus von Wu und Manber für B = 5: ———————————————– Gelesene Marker: 33373 Gelesene Reads: 138453 Gesamtzahl Treffer: 0 (0%) ———————————————– Benötige Zeit: 31420s davon Vorverarbeitung: 1120s davon Suche: 30300s 47 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Bei der größeren Probe ist der vollständige“ Aho Corasick jetzt minimal schneller als der ” Standard Aho Corasick. Der höhere Aufwand bei der Vorverarbeitung scheint sich also auszuzahlen. Auch bei dem Wu-Manber zahlt sich die Vorverarbeitung gegenüber dem Brute Force jetzt deutlich aus. Wie aus der Theorie zu erwarten war, schneidet der Algorithmus von Wu und Manber allerdings wesentlich schlechter ab als die Aho Corasick Varianten. 5.2 Details Als Algorithmus für unsere konkrete Aufgabenstellung wurde also folglich die in Kapitel 4.2.6 vorgestellte Variante des Aho Corasick Algorithmus ausgewählt. Dies war die Verbesserung für kleine Alphabete, bei der die next und g Funktion zu einer neuen Funktion verschmolzen werden. Ein weiterer wichtiger Punkt für die Implementierung ist die Verwendung von Arrays. Wie wir in Kapitel 3.3.1. gesehen haben, ist es bei Arrays sehr Platz- und Zeitaufwendig diese zu vergrößern. Idealerweise sollten also alle Arrays von Anfang an mit der richtigen Größe initialisiert werden. Um dies für eine Implementierung des Aho Corasick Algorithmus erreichen zu können, muss man beim Initialisieren wissen, wie viele Knoten der zugehörige Trie haben wird. Wir wollen dies abschätzen, indem wir als ersten Schritt der Vorverarbeitungsphase alle Pattern einlesen, und dabei folgende Mengen konstruieren: P ati := {p | p ∈ Π, |p| ≥ i} Aus diesen Mengen kann man exakt berechnen, wie viele Knoten der Trie im Aho Corasick Automaten haben wird. Allerdings stehen wir weiterhin vor demselben Problem, weil wir für die Konstruktion dieser Mengen die Elemente wieder in einem Array speichern müssten ohne vorher zu wissen, wie viele Elemente diese Mengen haben werden. Als Näherung wollen wir daher nicht die Mengen P ati , sondern die entsprechend definierten Multimengen15 P atmi := {p | p ∈ Π, |p| ≥ i}b betrachten. Die Anzahl der Elemente dieser Multimengen lässt sich durch einfaches Zählen ermitteln, da man aufgrund der Multimengeneigenschaft nicht kontrollieren muss, ob Elemente doppelt vorkommen. Somit ist ein Speichern der Elemente für das Errechnen der Anzahl nicht notwendig. Wir wissen bereits aus der Aufgabenstellung, dass nur Pattern mit einer Länge von maximal 14 Basenpaaren vorkommen. Die (|P atm1 |, · · · , |P atm14 |) können also in O(m) mit O(1) Speicherbedarf berechnet werden. Da der maximale Verzweigungsgrad im Trie |Σ| = 4 beträgt, gilt, dass die maximale Anzahl Knoten der Tiefe i im Trie 4i ist. Wir können also die Gesamtanzahl Knoten im Trie des Aho Corasick folgendermaßen abschätzen: AnzahlKnoten := 1; for (i := 1; i ≤ 14; i + +) begin AnzahlKnoten + = min(|P atmi |, 4i ); end 15 Der Unterschied einer normalen Menge zu einer Multimenge besteht darin, dass Elemente in einer Multimenge mehrfach vorkommen können. Wir bezeichnen Multimengen mit einem Index b (englisch Bag“) um sie von normalen Mengen unterscheiden zu können. ” 48 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 5.3 Messungen zur Implementierung Nachdem wir uns nun für den Ansatz vollständiger“ Aho Corasick entschieden haben, ” wollen wir einige Messungen zum konkreten Speicherbedarf und zur Laufzeit unserer Implementierung machen. Die Messungen wurden alle mit der Final Version der Implementierung gemacht, daher weichen die Laufzeiten etwas von denen in Kapitel 5.2 ab. r r Alle Messungen wurden wieder auf einem Laptop mit einem Intel Pentium M (1,8 Ghz) und 1048 MByte RAM unter Windows XP Professional mit Service Pack 2 durchgeführt. Verwendet wurde JavaTM Standard Edition 6 (build 1.6.0 05-b13). Gemessen wurde die Laufzeit in Abhängigkeit von der Größe der Probedatei. Als Marker wurde für alle Messungen ein Markersatz von 1734 Markern verwendet. Als Probedatei wurde eine selbsterzeugte Fasta-Datei verwendet. Um keinen zu großen Overhead durch den Fastaheader zu haben, bestand die Fasta-Datei nur aus einem Read. Der Read war eine zufällige Folge von Zeichen aus dem Alphabet Σ = {A, C, G, T }. Dateigröße 10 MB 20 MB 30 MB 40 MB 50 MB 60 MB 70 MB 80 MB 90 MB 100 MB Laufzeit 1 Sekunden 2 Sekunden 4 Sekunden 5 Sekunden 6 Sekunden 8 Sekunden 9 Sekunden 11 Sekunden 12 Sekunden 14 Sekunden Tabelle 6: Laufzeiten In Abbildung 22 ist das Ganze grafisch dargestellt. Man kann erkennen, dass sich die tatsächlich gemessene Laufzeit linear zur größe der Probendatei verhält. 49 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Abbildung 22: Laufzeit Um den Speicherbedarf zu bestimmen wurde folgende Messung durchgeführt. Gemessen wurde dabei, wie viele Marker bei festgesetzten Arbeitsspeicher in einem Durchlauf mit der Implementierung gesucht werden können. Die Marker wurden zufällig erzeugt und hatten alle eine Länge von 14 Basenpaaren. verfügbarer Speicher 4MB 8MB 32MB 64MB 96MB 128MB 160MB 192MB 224MB 256MB Marker 23.000 Marker 30.000 Marker 135.000 Marker 275.000 Marker 436.000 Marker 598.000 Marker 759.000 Marker 920.000 Marker 1.088.000 Marker 1.282.000 Marker Tabelle 7: Speicherbedarf In Abbildung 23 ist sind die Werte aus Tabelle 7 wieder grafisch dargestellt. Man kann gut erkennen, dass sich der Speicherbedarf ebenfalls linear zur Anzahl der Marker verhält. Abbildung 23: Speicherbedarf 50 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 5.4 Ergebnisse Um den ganzen Ansatz des Short Oligo Alignments“ zu testen, sowie die Arbeitsweise ” und Geschwindigkeit des Programms in der Praxis zu erproben, wurden im Folgenden einige Tests gemacht. Verwendet wurde dazu jeweils ein Markersatz mit 19.835 eindeutigen Markern zu insgesamt 207 verschiedenen Bakterien. Im ersten Testlauf wurden die Genome von 3 Bakterien zu Grunde gelegt und daraus künstlich eine metagenomische Probe erstellt. Folgende Bakterien wurden dafür ausgewählt: 1. Mycobacterium ulcerans Agy99 (NC 00861116 ) 2. Enterobacter sakazakii ATCCBAA-894 (NC 009778) 3. Pseudomonas entomophila L48 (NC 008027) Die verwendete Probendatei war insgesamt 52,3 MB groß und enthielt 300.000 Reads mit Längen zwischen 80 und 130 Basenpaaren. Ergebnis des Programms: NC 008611 (390 markers) :2904 hits (7,45 hits/marker) NC 008027 (121 markers) :1705 hits (14,09 hits/marker) NC 007760 (237 markers) :1627 hits (6,86 hits/marker) NC 009079 (218 markers) :427 hits (1,96 hits/marker) NC 005835 (400 markers) :402 hits (1 hits/marker) NC 006834 (293 markers) :304 hits (1,04 hits/marker) NC 009778 (24 markers) :285 hits (11,88 hits/marker) NC 009675 (139 markers) :264 hits (1,9 hits/marker) NC 007604 (104 markers) :243 hits (2,34 hits/marker) NC 002929 (500 markers) :218 hits (0,44 hits/marker) NC 008513 (210 markers) :212 hits (1,01 hits/marker) NC 008786 (500 markers) :212 hits (0,42 hits/marker) NC 002607 (35 markers) :196 hits (5,6 hits/marker) NC 000911 (79 markers) :184 hits (2,33 hits/marker) NC 009767 (500 markers) :145 hits (0,29 hits/marker) NC 007606 (500 markers) :142 hits (0,28 hits/marker) NC 009484 (26 markers) :140 hits (5,38 hits/marker) NC 007204 (500 markers) :122 hits (0,24 hits/marker) NC 008340 (313 markers) :112 hits (0,36 hits/marker) NC 002947 (54 markers) :110 hits (2,04 hits/marker) NC 009523 (175 markers) :104 hits (0,59 hits/marker) NC 002945 (10 markers) :97 hits (9,7 hits/marker) NC 010175 (274 markers) :82 hits (0,3 hits/marker) NC 007613 (338 markers) :68 hits (0,2 hits/marker) NC 007626 (24 markers) :66 hits (2,75 hits/marker) NC 004344 (281 markers) :64 hits (0,23 hits/marker) NC 009456 (226 markers) :58 hits (0,26 hits/marker) NC 005126 (500 markers) :57 hits (0,11 hits/marker) NC 002978 (470 markers) :55 hits (0,12 hits/marker) 16 NC XXXXXX ist eine Bezeichnung vom NCBI [CfBI]. NC steht dabei für komplettes Genom“, die ” Zahl XXXXXX ist eine Identifikationsnummer 51 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik NC 002771 (424 markers) :48 hits (0,11 hits/marker) NC 003116 (500 markers) :47 hits (0,09 hits/marker) ... ——————————————————– Markers read: 19835 Unique markers: 19835 (100%) Reads read: 300000 Total hits: 12075 (4,02%) Parameters Min. difference to other hits (per read): 2 Max. hits to second most common taxon: 10 Allowed maximum number of other taxa: 100 Der Durchlauf dauerte im Mittel 4 Sekunden pro 10MB. Insgesamt wurden inklusive Vorverarbeitung und anschließender Sortierung nach Anzahl der Treffer 24 Sekunden benötigt. Sehr gut zu erkennen ist, dass die enthaltenen Bakterien alle unter den ersten 7 Bakterien mit den meisten Treffern liegen. Des Weiteren kann man erkennen, dass bei den enthaltenen Bakterien die Anzahl der Treffer pro Marker höher ist als bei den nicht enthaltenen. Richtig eindeutig ist das Ergebnis dennoch nicht, NC 007760 mit 1627 Treffern und 6,86 Treffern pro Marker würde in diesem Fall wohl ebenfalls als enthalten“ gewertet werden. ” Enterobacter sakazakii ATCCBAA-894 (NC 009778) hingegen taucht etwas weiter hinten in der Statistik auf, einfach weil es verhältnismässig wenige Marker für dieses Bakterium gibt. Die Anzahl Treffer pro Marker ist hingegen ähnlich hoch wie bei den anderen beiden enthaltenen Bakterien. Das Ganze könnte man noch etwas optimieren, indem man neben der Statistik über die Hits auch noch eine Statistik zu den Markern erstellt. Bei einem erneuten Durchlauf wurde zusätzlich bestimmt, wie viele der Marker gefunden wurden und wie viele nicht. Der Durchlauf dauerte damit im Mittel 7 Sekunden pro 10MB. Inklusive Vorverarbeitung und anschließender Sortierung brauchte der gesamte Durchlauf 43 Sekunden. Das Ergebnis sah diesmal folgendermaßen aus: NC 008611 (390 markers) :2904 hits (7,45 hits/marker) markers found: 390 markers not found: 0 NC 008027 (121 markers) :1705 hits (14,09 hits/marker) markers found: 121 markers not found: 0 NC 007760 (237 markers) :1627 hits (6,86 hits/marker) markers found: 235 markers not found: 2 NC 009079 (218 markers) :427 hits (1,96 hits/marker) markers found: 173 markers not found: 45 NC 005835 (400 markers) :402 hits (1 hits/marker) markers found: 241 markers not found: 159 NC 006834 (293 markers) :304 hits (1,04 hits/marker) markers found: 224 markers not found: 69 NC 009778 (24 markers) :285 hits (11,88 hits/marker) markers found: 24 markers not found: 0 NC 009675 (139 markers) :264 hits (1,9 hits/marker) 52 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik markers found: 120 markers not found: 19 NC 007604 (104 markers) :243 hits (2,34 hits/marker) markers found: 96 markers not found: 8 NC 002929 (500 markers) :218 hits (0,44 hits/marker) markers found: 212 markers not found: 288 Die Verbesserung ist deutlich zu erkennen: Alle Bakterien die vorher noch als FalsePositives auftraten, haben Marker welche nicht gefunden wurden. Da die Marker so definiert wurden, dass sie im Genom mindestens alle 10.000 Basenpaare einmal vorkommen, ist davon auszugehen, dass alle Marker gefunden werden sollten, wenn das Genom enthalten ist und mit einer ausreichenden Abdeckung sequenziert wurde. Als Blindprobe lassen wir das Programm mit demselben Markersatz gegen eine Datei Zufall.fas“ laufen. Diese Datei enthält 300.000 Reads der Länge 80, wobei jeder Read ” eine zufällige Folge von Zeichen aus dem Alphabet Σ = {A, C, G, T } darstellt. Insgesamt ist diese Datei 27,1 MB groß. Das Ergebnis sah folgendermaßen aus: NC 005835 (400 markers) :109 hits (0,27 hits/marker) markers found: 112 markers not found: 288 NC 003116 (500 markers) :69 hits (0,14 hits/marker) markers found: 91 markers not found: 409 NC 005364 (500 markers) :61 hits (0,12 hits/marker) markers found: 62 markers not found: 438 NC 007204 (500 markers) :52 hits (0,1 hits/marker) markers found: 102 markers not found: 398 NC 010296 (291 markers) :48 hits (0,16 hits/marker) markers found: 70 markers not found: 221 NC 009488 (500 markers) :48 hits (0,1 hits/marker) markers found: 73 markers not found: 427 NC 007109 (371 markers) :47 hits (0,13 hits/marker) markers found: 60 markers not found: 311 NC 002978 (470 markers) :47 hits (0,1 hits/marker) markers found: 89 markers not found: 381 NC 002929 (500 markers) :38 hits (0,08 hits/marker) markers found: 67 markers not found: 433 NC 008340 (313 markers) :37 hits (0,12 hits/marker) markers found: 53 markers not found: 260 NC 007606 (500 markers) :37 hits (0,07 hits/marker) markers found: 68 markers not found: 432 NC 005126 (500 markers) :34 hits (0,07 hits/marker) markers found: 77 markers not found: 423 NC 008786 (500 markers) :34 hits (0,07 hits/marker) markers found: 58 markers not found: 442 NC 009767 (500 markers) :33 hits (0,07 hits/marker) markers found: 73 markers not found: 427 ... ——————————————————– Markers read: 19835 53 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Unique markers: 19835 (100%) Reads read: 300000 Total hits: 1818 (0,61%) Parameters Min. difference to other hits (per read): 2 Max. hits to second most common taxon: 10 Allowed maximum number of other taxa: 100 Der Durchlauf dauerte diesmal im Mittel 6 Sekunden pro 10MB, insgesamt wurden inklusive Vorverarbeitung und anschließender Sortierung nach der Anzahl der Treffer 21 Sekunden benötigt. Wie zu erwarten war, ist ein gewisses Hintergrundrauschen“ zu erkennen. Es gibt aber ” kein Bakterium von dem alle Marker gefunden wurden. Auch von der Anzahl der Hits gibt es keine großen Unterschiede zwischend den unterschiedlichen Bakterien. Als letztes wollen wir den obigen Markersatz noch mal an einer echten metagenomischen Probe testen. Verwendet haben wir diesmal wieder einen Markersatz aus 19.835 Markern zu insgesamt 207 verschiedenen Bakterien. Die Probedatei ist 150MB groß und besteht aus 121.590 Reads mit Längen von jeweils etwa 800 - 1200 Basenpaaren. Die Probe wurde im Rahmen vom Global Ocean Sampling Project“ 17 des J. Craig Venter Institute (JCVI) ” dem Golf von Mexiko entnommen. Die Sequenzierten Daten werden vom Community Cyberinfrastructure for Advanced Marine Microbial Ecology Research and Analysis (CAMERA) zur Verfügung gestellt18 . NC 004344 (281 markers) :4191 hits (14,91 hits/marker) markers found: 281 markers not found: 0 NC 009714 (238 markers) :2088 hits (8,77 hits/marker) markers found: 238 markers not found: 0 NC 008513 (210 markers) :2061 hits (9,81 hits/marker) markers found: 210 markers not found: 0 NC 009635 (234 markers) :1709 hits (7,3 hits/marker) markers found: 218 markers not found: 16 NC 007716 (273 markers) :1040 hits (3,81 hits/marker) markers found: 270 markers not found: 3 NC 008599 (155 markers) :772 hits (4,98 hits/marker) markers found: 143 markers not found: 12 NC 004432 (178 markers) :469 hits (2,63 hits/marker) markers found: 176 markers not found: 2 NC 004829 (221 markers) :384 hits (1,74 hits/marker) markers found: 166 markers not found: 55 NC 004342 (178 markers) :281 hits (1,58 hits/marker) markers found: 129 markers not found: 49 NC 009883 (207 markers) :258 hits (1,25 hits/marker) markers found: 107 markers not found: 100 NC 005364 (500 markers) :250 hits (0,5 hits/marker) 17 18 siehe [SGD+ 07] und [VRHea04] siehe [fAMMERC] 54 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik markers found: 342 markers not found: 158 NC 007797 (500 markers) :221 hits (0,44 hits/marker) markers found: 283 markers not found: 217 NC 007294 (299 markers) :221 hits (0,74 hits/marker) markers found: 209 markers not found: 90 NC 002528 (50 markers) :199 hits (3,98 hits/marker) markers found: 48 markers not found: 2 NC 009488 (500 markers) :167 hits (0,33 hits/marker) markers found: 285 markers not found: 215 NC 002771 (424 markers) :167 hits (0,39 hits/marker) markers found: 294 markers not found: 130 NC 005835 (400 markers) :135 hits (0,34 hits/marker) markers found: 194 markers not found: 206 NC 006570 (75 markers) :117 hits (1,56 hits/marker) markers found: 66 markers not found: 9 NC 003116 (500 markers) :113 hits (0,23 hits/marker) markers found: 221 markers not found: 279 NC 002978 (470 markers) :107 hits (0,23 hits/marker) markers found: 164 markers not found: 306 NC 010296 (291 markers) :104 hits (0,36 hits/marker) markers found: 142 markers not found: 149 ... ——————————————————– Markers read: 19835 Unique markers: 19835 (100%) Reads read: 121590 Total hits: 18430 (15,16%) Parameters Min. difference to other hits (per read): 3 Max. hits to second most common taxon: 10 Allowed maximum number of other taxa: 100 Der Durchlauf dauerte diesmal im Mittel 6 Sekunden pro 10MB, insgesamt wurden inklusive Vorverarbeitung und anschließender Sortierung nach der Anzahl der Treffer 111 Sekunden benötigt. Ähnlich wie bei dem künstlich erzeugten Metagenom scheint aber wieder eine kleine Gruppe von Bakterien sehr viele Hits zu haben. Einen Kontrollwert welche Bakterien jetzt wirklich in der Probe waren und welche nicht, gab es auf Stammesebene (NC-Nummer) leider nicht. Daher ist es an dieser Stelle schwer, eine Aussage zum biologischen Wert dieser Ergebnisse zu machen. Wichtig für uns soll an dieser Stelle sein, dass der Ansatz funktioniert und das die Implementierung in sehr kurzer Zeit Ergebnisse liefert. 55 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 6. Weitere Einsatzgebiete Neben der Analyse von metagenomischen Proben gibt es noch weitere Einsatzgebiete, bei denen die vorgestellten Algorithmen und das für dieses konkrete Problem entwickelte Programm verwendet werden können. So sind beispielsweise Resequenzierungsprojekte ein mögliches weiteres Einsatzgebiet. Bei der Resequenzierung geht es darum, dass bereits bekannte und sequenzierte Bakterien noch mal neu sequenziert werden. Der Grund für eine erneute Sequenzierung ist, dass oftmals die Genome zwar bekannt sind, wenn man genauer hinschaut, diese aber trotzdem noch einige unbekannte Stellen enthalten. Ein anderer Grund dafür ist, dass sich die Bakterien auch im Laufe der Zeit verändern. Es entstehen spontane Mutationen, manchmal bauen die Bakterien auch fremde DNA in ihre eigene mit ein. Untersucht man die Bedeutung der einzelnen Gene im Bakterium ist es sinnvoll, dies zu Resequenzieren um sicher zu gehen, dass das Bakterium auch das Genom besitzt von dem man ausgeht. Es kann nämlich vorkommen, dass ein Labor Bakterien von einem Referenzstamm besitzt, die ursprünglich das bekannte Genom besessen haben. Mit der Zeit können sich aber durch das immer wieder neue Vermehren der Bakterien im Labor kleine Veränderungen in das Genom eingeschlichen haben, so dass dies nicht mehr dem des Referenzstamm entspricht. Anstatt nun mit den aufwendigeren, teureren und älteren Sanger Sequenzierungsmethoden längere Reads zu produzieren und daraus das Genom noch mal komplett zusammenzusetzen kann man auch hier die moderneren Sequenzierungsmethoden einsetzen. Dazu nimmt man das früher sequenzierte Genom als Vorlage und sortiert erst mal alle Reads raus, die sich im früheren Genom wiederfinden lassen. Die übriggebliebenden Reads sind dann die Lücken bzw. die neue DNA. Mit diesen Reads kann dann anschließend weiter gearbeitet werden. Es gibt 2 Varianten, wie man die bisherige Implementierung im Aufgabenfeld der Resequenzierung anwenden kann: 1. Man verwendet die kompletten Reads aus dem Resequenzierungsprojekt anstatt der Marker als Pattern und anstatt einer metagenomische Probe verwendet man als Eingabetext das ursprüngliche Genom, welches resequenziert werden soll. Man sucht also die kompletten Reads in dem ursprünglichen Genom um damit die bereits bekannte Information aus den Reads herauszufiltern. Der Vorteil dieser Methode ist, dass man die bisherige Implementierung im Wesentlichen nur an einer Stelle abändern muss, nämlich dass auch Pattern mit einer größeren Länge als 14 gesucht werden können. Der Nachteil ist natürlich, dass je länger der Reads ist, die Wahrscheinlichkeit größer wird, dass der Read einen oder mehrere Sequenzierungsfehler enthält und sich somit nicht im ursprünglichen Genom wiederfinden lässt. Daher würde es sich für diese Variante anbieten, nicht den Aho Corasick Algorithmus zu verwenden, sondern stattdessen einen approximativen Multi Pattern Algorithmus wie beispielsweise den in Kapitel 4.6 vorgestellten Multiple Approximate String Matching with l-Grams von Fredriksson und Navarro. Will man dennoch exakt suchen, so sind für dieses Problem ebenfalls die vorgestellten Filteralgorithmen sinnvoll, weil hier als Pattern die Reads verwendet werden. 56 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Die Reads haben zwei angenehme Eigenschaften, welche für die Anwendung von Filteralgorithmen hilfreich sind: • Sie haben alle dieselbe Länge • Sie sind deutlich länger als die beim Short Oligo Alignment verwendeten Marker 2. Man zerlegt jeden Read in mehrere überlappende q-Gramme und verwendet diese dann als Marker für den zugehörigen Read. Anschließend zerlegt man das ursprüngliche Genom künstlich in Reads. Jetzt kann man mit den vorgestellten Algorithmen alle Auftreten der Marker in den künstlich erzeugten Reads suchen. Dabei ist dann allerdings nicht das Ziel, dem künstlich erzeugten Read eindeutig einen Read aus der Resequenzierung zuzuordnen, sondern es sollen die Reads der Resequenzierung den künstlichen Reads zugeordnet werden. Die bisherige Implementierung müsste also so abgeändert werden, dass Mehrfachzuordnungen möglich sind. Nach dem Suchlauf würden dann in einem zweiten Schritt alle Reads der Resequenzierung herausgesucht, welche keinem der künstlichen Reads zugeordnet werden konnten. Diese Reads enthalten die durch die Resequenzierung gewonnenen neuen Informationen gegenüber dem ursprünglichen Genom. Zu beachten ist, dass die Marker in diesem Fall nicht mehr eindeutig sind. Die bisherige Implementierung müsste also auch dafür angepasst werden. Der Vorteil dieser Methode ist, dass kurze Marker verwendet werden und man sich somit auf die exakte Suche beschränken kann. Tatsächlich angewendet wurde der zweite Ansatz. In dem konkreten Fall waren die Reads der Resequenzierung bereits in 2 Dateien aufgeteilt. Die erste Datei enthielt alle Reads, die mittels eines unbekannten Algorithmus19 dem original Genom zugeordnet werden konnten. Die zweite Datei enthielt 267.155 Reads, welche laut GATC20 nicht im original Genom zu finden waren. Bei genauerem Betrachten der Reads fiel auf, dass ein bestimmter Typ von Sequenzierungsfehlern scheinbar öfter vorkam. Und zwar kamen in den Reads immer wieder Sequenzen von nur einem Basenpaar vor, der Anfang und das Ende des Reads ließen sich jedoch im Genom wiederfinden. Es ist also anzunehmen, dass dies ein technischer Fehler bei der Sequenzierung ist. Abbildung 24: Sequenzierungsfehler in einem Read. Blau ist das original Genom, schwarz der Read. Approximative Algorithmen können schlecht mit solchen blockweise auftretenden Fehlern umgehen, da jede Abweichung als neuer Fehler gezählt wird. Das Short Oligo Alignment jedoch findet am Anfang und am Ende des Reads Sequenzen, die sich dem original Genom zuordnen lassen. Daher wurde der zweite Ansatz gewählt. 19 Die Resequenzierung wurde vom GATC durchgeführt. Welche Algorithmen für die Zuordnung der Reads verwendet wurden, wird vom GATC nicht bekannt gegeben. 20 [Kob] 57 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Gegeben waren insgesamt 267.155 Reads mit jeweils einer Länge von 34 Basenpaaren. Durch einen vorgeschalteten Algorithmus wurden erst mal 50.737 Reads als Trash“ 21 ” aussortiert. Von den verbliebenden 216.418 Reads konnten mit diesem Ansatz 82.293 dem original Genom zugeordnet werden. 114.611 Reads ließen sich weiterhin nicht zuordnen. Diese Reads enthalten vermutlich die von dem original Genom abweichende DNA. 21 Aussortiert wurden im Wesentlichen alle Reads, die zu viele N’s also nichtidentifizierte Basen enthielten 58 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 7. Fazit Die Metagenomik ist ein sehr aktuelles Forschungsgebiet, welche große Herausforderungen an die Bioinformatik stellt. Durch die neuen Sequenziertechniken und die dadurch immer geringer werdenden Kosten wird der Einsatz von Sequenzierungsprojekten in immer mehr Aufgabenbereichen möglich. Die Vorteile sind gewaltig, die anfallende Datenmenge allerdings auch. Bisher gibt es nur sehr wenige Programme, die dafür geeignet sind und alle haben ihre Probleme. Die erfolgversprechensten Ansätze bisher benötigen beispielsweise eine sehr rechenintensive Vorverarbeitung, welche meist nur auf High-Performance Rechenclustern ausgeführt werden können (z.B. MEGAN, siehe [HAQS07]). Daher ist es wichtig, Programme zu entwickeln, die mit diesen Datenmengen umgehen können. Geeignete Algorithmen aus dem Bereich der Zeichenkettensuche gibt es genug. Die Herausforderung besteht einerseits darin, den effizientesten Algorithmus für die jeweilige Problemstellung zu finden und ihn auf das konkrete Problem anzupassen und andererseits darin, den Algorithmus dann auch effizient zu implementieren. Gerade die vielen vorgegebenen Datenstrukturen einer Programmiersprache, sowie ein Programmieren unter strikten Paradigmen wie zum Beispiel strenger Objektorientierung, können dazu führen, dass effiziente Algorithmen nur durch die Implementierung langsam werden. In dem konkreten Fall dieser Diplomarbeit wurde für die Untersuchung einer metagenomischen Probe mittels Short Oligo Alignment mit dem Aho Corasick Algorithmus eine sehr effiziente Lösung gefunden und implementiert. Aktuelle Datensätze können in einigen Minuten damit durchsucht werden. Ein schnelles Suchprogramm, wie es im Rahmen dieser Arbeit entwickelt wurde, ist jedoch nur der Anfang. Das Hauptproblem bei der Analyse von metagenomischen Proben mittels Short Oligo Alignment stellen zur Zeit die Markersätze, sowie die daraus resultierende Interpretation der erhaltenen Ergebnisse dar. So sind die bisherigen Markersätze im Hinblick auf die Anzahl der Marker pro Bakterium alle sehr unterschiedlich. Für einige Bakterien gibt es sehr viele Marker, für andere hingegen fast gar keine. Auch die Qualität der einzelnen Marker scheint sich sehr stark zu unterscheiden. So sind einige Marker wirklich für nur ein Bakterium charakteristisch, während andere scheinbar in verschiedensten Genomen recht oft vorkommen. Aufgrund der Tatsache, dass es aber noch sehr viele nicht sequenzierte oder sogar unbekannte Bakterien gibt, ist es sehr schwierig zu bestimmen, wie charakteristisch“ ein bestimmter Marker ist. ” Zur Zeit werden verschiedene Ansätze effizientere Markersätze zu erhalten von Dr. Oleg N. Reva 22 untersucht und ausgetestet. Dennoch scheint der Ansatz des Short Oligo Alignments sehr erfolgversprechend zu sein. In Abbildung 25 ist die Anzahl der Treffer für die jeweils 13 am häufigsten in den Proben vorkommenden Bakterien für die drei Durchläufe aus Kapitel 5.4 grafisch dargestellt. Während in der Probe aus den zufälligen Reads die Verteilung der Hits auf die einzelnen Bakterien ebenfalls zufällig ist, erkennt man bei der künstlich erzeugten sowie der echten metagenomischen Proben deutliche Unterschiede in der Anzahl der Hits. 22 Dep. of Biochemistry, Bioinformatics and Computational Biology Unit, University of Pretoria 59 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Abbildung 25: Verteilung der häufigsten Treffer bei verschiedenen Proben Auch haben wir an der aus drei verschiedenen Genomen künstlich erzeugten metagenomischen Probe in Kapitel 5.4 gesehen, dass die Bakterien, deren Genome in der Probe vorhanden waren, unter den ersten Treffern auftauchen. Die Idee des Short Oligo Alignments scheint also zu funktionieren. Andererseits sehen wir bereits an unseren Ergebnissen sehr starke Streuungen, so dass es schwierig ist, eine Grenze zu ziehen und festzulegen, welches der Bakterien noch in der Probe enthalten war und welches nicht mehr. Auch ist es zur Zeit noch nötig, für jede Probe den Parameter neu zu bestimmen, ab wie vielen gefunden Markern in einem Read dieser als Hit gewertet werden soll. Die Hauptursache für dieses Problem dürfte, wie erwähnt, in den verwendeten Markersätzen liegen. Mögliche Lösungsansätze für das Problem der schlechten Markersätze wären: • Das Erstellen von zusätzlichen Statistiken über die einzelnen Marker. In Kapitel 5.4 haben wir bereits gesehen, dass allein die Information, wie viele der Marker gefunden und wie viele nicht gefunden wurden, eine interessante zusätzliche Information darstellt. Diese Idee könnte man noch weiter vertiefen und Statistiken über die Anzahl, wie oft die Marker insgesamt gefunden werden, erstellen. Aus der Verteilung der Marker könnte man so gegebenenfalls auch Informationen zu den in der Probe enthaltenen Bakterien bekommen. • Eine andere Idee wäre, generell etwas längere Marker zu verwenden. Der Vorteil wäre, dass längere Marker meistens charakteristischer für ein Bakterium sind und man somit weniger False-Positives erhält. Außerdem würde bei der Verwendung längerer Marker gegebenenfalls der Einsatz von Filterfunktionen wieder Sinn machen, so dass man gerade für kleinere Markersätze Algorithmen verwenden könnte, die im Mittel sogar schneller als in O(n) laufen würden. Auf der anderen Seite gibt es aber auch Probleme bei der Verwendung längerer 60 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik Reads. Zum einen wird es schwieriger Marker zu finden, da es wenige längere DNASequenzen gibt, die regelmäßig in einem Genom auftreten. Man müsste also Marker verwenden, die nur alle 30.000 oder alle 50.000 Basenpaare im Genom auftreten. Zum anderen steigt die Wahrscheinlichkeit, dass je länger ein Marker ist, er aufgrund eines Sequenzierungsfehlers nicht gefunden wird. Bei den kurzen Reads sinkt außerdem noch die Wahrscheinlichkeit, je größer ein Marker ist, dass dieser komplett in einem Read liegt. Eine mögliche Lösung hierzu wäre ein approximativer Suchalgorithmus, wie das in Kapitel 4.6 vorgestellte Multiple Approximate String ” Matching with l-Grams“ von Fredriksson und Navarro. 61 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 8. Literatur Literatur [AC75] Alfred V. Aho and Margaret J. Corasick. Efficient String Matching: An Aid to Bibliographic Search. Commun. ACM, 18(6):333–340, 1975. [AOSiA97] Kazuaki Ando, Makoto Okada, Masami Shishibori, and Jun ichi Aoe. Efficient Multi-Attribute Pattern Matching Using the Extended Aho-Corasick Method. IEEE International Conference, 4:3936–3941, 1997. [BM77] Robert S. Boyer and J. Strother Moore. A Fast String Searching Algorithm. Communications of the ACM, 20(10):762–772, 1977. [BYN97] R. A. Baeza-Yates and G. Navarro. Multiple Approximate String Matching. In F. K. H. A. Dehne, A. Rau-Chaplin, J.-R. Sack, and R. Tamassia, editors, Proceedings of the5th Workshop on Algorithms and Data Structures, number 1272, pages 174–184, Halifax, Nova Scotia, Canada, 1997. Springer-Verlag, Berlin. [CfBI] National Center for http://www.ncbi.nlm.nih.gov/. [CM94] William I. Chang and Thomas G. Marr. Approximate String Matching and Local Similarity. In CPM ’94: Proceedings of the 5th Annual Symposium on Combinatorial Pattern Matching, pages 259–273, London, UK, 1994. Springer-Verlag. [Dat] Genomes OnLine Database. http://www.genomesonline.org/. [DWRTed] Colin F. Davenport, Lutz Wiehlmann, Oleg N. Reva, and Burkhard Tümmler. Visualisation of pseudomonas genomic structure by abundant 8-14mer oligonucleotides, Environmental Microbiology, submitted. [ea] C. Davenport et al. OligoCounter. http://webhost1.mhhannover.de/davenport/oligocounter/docs oc params.html. Biotechnology Information. [fAMMERC] Community Cyberinfrastructure for Advanced Marine Microbial Ecology Research and Analysis (CAMERA). http://camera.calit2.net/. [Fas] NCBI Fastaformat. http://www.ncbi.nlm.nih.gov/blast/fasta.shtml. [FN03] K. Fredriksson and G. Navarro. Average-optimal multiple approximate string matching, 2003. [FN04] Kimmo Fredriksson and Gonzalo Navarro. Improved Single and Multiple Approximate String Matching. Combinatorial Pattern Matching (CPM’04), pages 457–471, 2004. [Fou04] J. Craig Venter Science Foundation. Global Ocean Sampling Expedition, 2004. [GL00] Ben Gum and Richard Lipton. Cheaper by the Dozen: Batched Algorithms. Proceedings of the 1st SIAM International Conference on Data Mining, 2000. 63 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik [HAQS07] Daniel H. Huson, Alexander F. Auch, Ji Qi, and Stephan C. Schuster. MEGAN analysis of metagenomic data. Genome Research, 17:377–386, 2007. [Hor80] R. Nigel Horspool. Practical Fast Searching in Strings. SOFTWAREPRACTICE AND EXPERIENCE, 10:501–506, 1980. [Ill] Illumina/Solexa. illumina sequencing http://www.illumina.com/pages.ilmn?id=203. [Kle08] Hans-Peter Klenk. Molecular Taxonomy of Bacteria: Sequencing of Type Strains. Technical report, 03.03.2008. [Kob] GATC Biotech Koblenz. http://www.gatc-biotech.com. [KR87] Richard M. Karp and Michael O. Rabin. Efficient randomized patternmatching algorithms. IBM Journal of Research and Development, 31(2):249–260, 1987. [KST03] Jari Kytöjoki, Leena Salmela, and Jorma Tarhio. Tuning String Matching for Huge Pattern Sets. Combinatorial Pattern Matching: 14th Annual Symposium, CPM 2003, 2676:1017ff, 2003. [MGK02] Jan Mrázek, Lisa H. Gaynon, and Samuel Karlin. Frequent oligonucleotide motifs in genomes of three streptococci. Nucleic Acids Research, 30(19):4216–4221, 2002. [MM96] R. Muth and U. Manber. Approximate Multiple String Search. In D. S. Hirschberg and E. W. Myers, editors, Proceedings of the7th Annual Symposium on Combinatorial Pattern Matching, number 1075, pages 75–86, Laguna Beach, CA, 1996. Springer-Verlag, Berlin. [Mye99] Gene Myers. A fast bit-vector algorithm for approximate string matching based on dynamic programming. Journal of the ACM, 46(3):395–415, 1999. [Nav97] G. Navarro. Multiple Approximate String Matching by Counting. In R. Baeza-Yates, editor, Proceedings of the 4th South American Workshop on String Processing, pages 95–111, Valparaiso, Chile, 1997. Carleton University Press. [Nie05] Janne Nieminen. Effcient implementation of Unicode string pattern matching automata in Java. Report A/2005/2, 2005. [oCI] University of California and DOE Joint Genome Institute. Drainage Metagenome, technology, Acid Mine http://web.camera.calit2.net/cameraweb/gwt/org.jcvi.camera.web.gwt.download.BrowseProjectsPage/BrowseProjectsPage.oa [Par03] R. Parchmann. Vorlesung Zeichenketten, 2003. [Par04] R. Parchmann. Vorlesung Approximative Zeichenketten, 2004. [SBR07] Kerstin Stangier, Christopher Bauser, and Johannes Regenbogen. Next Generation DNA-Sequenzierung. LABORWELT, 8(3), 2007. [SGD+ 07] Yooseph S, Sutton G, Rusch DB, Halpern AL, and Williamson SJ et al. The Sorcerer II Global Ocean Sampling Expedition: Expanding the Universe of Protein Families. PLoS Biology, 5(3), March 2007. 64 . Schnelle Algorithmen zur Multipatternsuche in der Metagenomik [Sta79] R. Staden. A strategy of DNA sequencing employing computer programs. Nucleic Acids Research, 6(7):2601–2610, 1979. [STK06] Leena Salmela, Jorma Tarhio, and Jari Kytöjoki. Multipattern String Matching with q-grams. Journal of Experimental Algorithmics, 11(Article No.1.1):1–19, 2006. [VRHea04] J. Craig Venter, Karin Remington, John F. Heidelberg, and Aaron L. Halpern et al. Environmental genome shotgun sequencing of the Sargasso Sea. Science Express, 304(5667):66–74, 2004. [Wat90] James D. Watson. Human Genome Project. U.S. National Institutes of Health, started in 1990. [WM94] S. Wu and U. Manber. A fast algorithm for multi-pattern searching. Technical Report TR-94-17, 1994. [WUO+ 02] Christian Weinel, David W. Ussery, Hakan Ohlsson, Thomas SicheritzPonten, Claudia Kiewitz, and Burkhard Tümmler. Comparative Genomics of Pseudomonas aeruginosa PAO1 and Pseudomonas putida KT2440: Orthologs, Codon Usage, Repetitive Extragenic Palindromic Elements, and Oligonucleotide Motif Signatures. Genome Letters, 1(4):175–187, 2002. 65 Schnelle Algorithmen zur Multipatternsuche in der Metagenomik 66 Index Aho Corasick Algorithmus, 45–48 Aho-Corasick Algorithmus, 28, 43 Aho-Corasick Automat, 20, 23, 25 ASCII-Werte, 32 AVL-Baum, 10, 19 Multi Pattern Matching, 19, 28, 35, 37, 42 Multimenge, 48 Multiple Approximate String Matching, 39, 42, 43, 56 Bad Character Funktion, 36 Basenpaar, 8 Basenpaaren, 8 Baum, 9 binärer Suchbaum, 10, 18 BLAST, 6 Booyer-Moore Algorithmus, 28 Boyer-Moore Algorithmus, 35, 36 Boyer-Moore Algorithmus mit q-Grammen, 36, 43 Boyer-Moore-Horspool, 36, 40 Brute Force, 17, 30, 32, 46, 47 Nukleotid, 8 contig, 9, 16 deterministischer endlicher Automat, 9, 20, 24, 27 Distanz-Tabelle, 39, 42 DNA-Basen, 34 DNA-Sequenzierung, 5, 9 Edit-Operationen, 39 Einfügung, 39 Outpuntfunktion, 23 Pattern, 17 q-Gramm, 35–37, 39, 42, 57 Read, 8, 12 Resequenzierung, 56 Sanger, 5 Sanger Sequenzierung, 5, 56 Sequenzierungsfehler, 57 Short Oligo Alignment, 7, 57, 59 Single Pattern Matching, 17, 33, 35 Substitution, 39 Suffix, 22, 23, 31 Taxon, 8 Trie, 9, 21, 48 Two-Level-Hashing, 19, 32 Wu-Manber Algorithmus, 28, 35, 43, 46, 47 Fehlerfunktion, 20, 22–26 Filteralgorithmus, 28, 37, 40, 57 Filterfunktion, 34, 35, 44, 45 Filterwert, 44 Fredriksson und Navarro, 39, 40 Hashfunktion, 18, 34 Hashwerten, 36 Hit, 8 Illumina/Solexa, 6 Karp-Rabin Algorithmus, 18, 36, 40 Löschung, 39 Marker, 8, 11 Markersätze, 59 Metagenomik, 5, 8, 59 metagenomische Probe, 11, 56 67 Hiermit versichere ich, dass ich diese Arbeit selbständig verfasst habe und keine anderen als die angegebenen Quellen und Hilfsmittel benutzt habe. Hannover, den 29. Juli 2008 (Jens Neugebauer)